summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2022-02-12 17:47:03 +0000
committerMatt A. Tobin <email@mattatobin.com>2022-02-12 14:23:18 -0600
commitf66babd8b8368ada3e5aa29cdef1c77291ee4ddd (patch)
treee3842e2a6bf19090185f9c475b3846e1bb79ac97 /components
downloadGRE-f66babd8b8368ada3e5aa29cdef1c77291ee4ddd.tar.gz
Create the Goanna Runtime Environment
Diffstat (limited to 'components')
-rw-r--r--components/.eslintrc.js11
-rw-r--r--components/aboutcache/content/aboutCache.js48
-rw-r--r--components/aboutcache/jar.mn6
-rw-r--r--components/aboutcache/moz.build6
-rw-r--r--components/aboutcheckerboard/content/aboutCheckerboard.css49
-rw-r--r--components/aboutcheckerboard/content/aboutCheckerboard.js276
-rw-r--r--components/aboutcheckerboard/content/aboutCheckerboard.xhtml55
-rw-r--r--components/aboutcheckerboard/jar.mn8
-rw-r--r--components/aboutcheckerboard/moz.build6
-rw-r--r--components/aboutmemory/content/aboutCompartments.xhtml16
-rw-r--r--components/aboutmemory/content/aboutMemory.css148
-rw-r--r--components/aboutmemory/content/aboutMemory.js2013
-rw-r--r--components/aboutmemory/content/aboutMemory.xhtml15
-rw-r--r--components/aboutmemory/jar.mn8
-rw-r--r--components/aboutmemory/moz.build6
-rw-r--r--components/aboutperformance/content/aboutPerformance.js1076
-rw-r--r--components/aboutperformance/content/aboutPerformance.xhtml188
-rw-r--r--components/aboutperformance/jar.mn7
-rw-r--r--components/aboutperformance/moz.build6
-rw-r--r--components/addoncompat/CompatWarning.jsm102
-rw-r--r--components/addoncompat/Prefetcher.jsm557
-rw-r--r--components/addoncompat/RemoteAddonsChild.jsm576
-rw-r--r--components/addoncompat/RemoteAddonsParent.jsm1080
-rw-r--r--components/addoncompat/ShimWaiver.jsm15
-rw-r--r--components/addoncompat/addoncompat.manifest4
-rw-r--r--components/addoncompat/defaultShims.js39
-rw-r--r--components/addoncompat/moz.build18
-rw-r--r--components/addoncompat/multiprocessShims.js182
-rw-r--r--components/addons/content/about.js97
-rw-r--r--components/addons/content/about.xul57
-rw-r--r--components/addons/content/blocklist.css11
-rw-r--r--components/addons/content/blocklist.js72
-rw-r--r--components/addons/content/blocklist.xml58
-rw-r--r--components/addons/content/blocklist.xul46
-rw-r--r--components/addons/content/eula.js21
-rw-r--r--components/addons/content/eula.xul35
-rw-r--r--components/addons/content/extensions.css237
-rw-r--r--components/addons/content/extensions.js3553
-rw-r--r--components/addons/content/extensions.xml2031
-rw-r--r--components/addons/content/extensions.xul658
-rw-r--r--components/addons/content/list.js165
-rw-r--r--components/addons/content/list.xul44
-rw-r--r--components/addons/content/newaddon.js129
-rw-r--r--components/addons/content/newaddon.xul66
-rw-r--r--components/addons/content/pluginPrefs.xul20
-rw-r--r--components/addons/content/selectAddons.css22
-rw-r--r--components/addons/content/selectAddons.js347
-rw-r--r--components/addons/content/selectAddons.xml235
-rw-r--r--components/addons/content/selectAddons.xul124
-rw-r--r--components/addons/content/setting.xml508
-rw-r--r--components/addons/content/update.js691
-rw-r--r--components/addons/content/update.xul180
-rw-r--r--components/addons/content/updateinfo.xsl41
-rw-r--r--components/addons/content/xpinstallConfirm.css8
-rw-r--r--components/addons/content/xpinstallConfirm.js192
-rw-r--r--components/addons/content/xpinstallConfirm.xul37
-rw-r--r--components/addons/content/xpinstallItem.xml51
-rw-r--r--components/addons/extensions.manifest15
-rw-r--r--components/addons/jar.mn35
-rw-r--r--components/addons/locale/about.dtd9
-rw-r--r--components/addons/locale/blocklist.dtd17
-rw-r--r--components/addons/locale/extensions.dtd219
-rw-r--r--components/addons/locale/extensions.properties134
-rw-r--r--components/addons/locale/newaddon.dtd15
-rw-r--r--components/addons/locale/newaddon.properties10
-rw-r--r--components/addons/locale/selectAddons.dtd49
-rw-r--r--components/addons/locale/selectAddons.properties21
-rw-r--r--components/addons/locale/update.dtd65
-rw-r--r--components/addons/locale/update.properties21
-rw-r--r--components/addons/locale/xpinstallConfirm.dtd13
-rw-r--r--components/addons/locale/xpinstallConfirm.properties16
-rw-r--r--components/addons/moz.build65
-rw-r--r--components/addons/public/amIAddonManager.idl29
-rw-r--r--components/addons/public/amIAddonPathService.idl37
-rw-r--r--components/addons/public/amIWebInstallListener.idl134
-rw-r--r--components/addons/public/amIWebInstaller.idl82
-rw-r--r--components/addons/src/AddonLogging.jsm187
-rw-r--r--components/addons/src/AddonManager.jsm2939
-rw-r--r--components/addons/src/AddonPathService.cpp243
-rw-r--r--components/addons/src/AddonPathService.h55
-rw-r--r--components/addons/src/AddonRepository.jsm2014
-rw-r--r--components/addons/src/AddonRepository_SQLiteMigrator.jsm522
-rw-r--r--components/addons/src/AddonUpdateChecker.jsm967
-rw-r--r--components/addons/src/ChromeManifestParser.jsm157
-rw-r--r--components/addons/src/Content.js38
-rw-r--r--components/addons/src/DeferredSave.jsm270
-rw-r--r--components/addons/src/LightweightThemeConsumer.jsm164
-rw-r--r--components/addons/src/LightweightThemeImageOptimizer.jsm199
-rw-r--r--components/addons/src/LightweightThemeManager.jsm801
-rw-r--r--components/addons/src/PluginProvider.jsm595
-rw-r--r--components/addons/src/SpellCheckDictionaryBootstrap.js17
-rw-r--r--components/addons/src/XPIProvider.jsm7732
-rw-r--r--components/addons/src/XPIProviderUtils.js1430
-rw-r--r--components/addons/src/addonManager.js200
-rw-r--r--components/addons/src/amContentHandler.js100
-rw-r--r--components/addons/src/amInstallTrigger.js230
-rw-r--r--components/addons/src/amWebInstallListener.js338
-rw-r--r--components/alerts/content/alert.css33
-rw-r--r--components/alerts/content/alert.js366
-rw-r--r--components/alerts/content/alert.xul68
-rw-r--r--components/alerts/jar.mn8
-rw-r--r--components/alerts/locale/alert.dtd6
-rw-r--r--components/alerts/locale/alert.properties23
-rw-r--r--components/alerts/moz.build26
-rw-r--r--components/alerts/public/nsIAlertsService.idl258
-rw-r--r--components/alerts/src/AlertNotification.cpp361
-rw-r--r--components/alerts/src/AlertNotification.h81
-rw-r--r--components/alerts/src/AlertNotificationIPCSerializer.h122
-rw-r--r--components/alerts/src/nsAlertsService.cpp301
-rw-r--r--components/alerts/src/nsAlertsService.h31
-rw-r--r--components/alerts/src/nsAlertsUtils.cpp43
-rw-r--r--components/alerts/src/nsAlertsUtils.h32
-rw-r--r--components/alerts/src/nsXULAlerts.cpp380
-rw-r--r--components/alerts/src/nsXULAlerts.h84
-rw-r--r--components/apppicker/content/appPicker.js200
-rw-r--r--components/apppicker/content/appPicker.xul40
-rw-r--r--components/apppicker/jar.mn8
-rw-r--r--components/apppicker/moz.build6
-rw-r--r--components/appshell/moz.build34
-rw-r--r--components/appshell/public/nsIAppShellService.idl150
-rw-r--r--components/appshell/public/nsIPopupWindowManager.idl35
-rw-r--r--components/appshell/public/nsIWindowMediator.idl206
-rw-r--r--components/appshell/public/nsIWindowMediatorListener.idl19
-rw-r--r--components/appshell/public/nsIWindowlessBrowser.idl27
-rw-r--r--components/appshell/public/nsIXULBrowserWindow.idl82
-rw-r--r--components/appshell/public/nsIXULWindow.idl168
-rw-r--r--components/appshell/src/nsAppShellCID.h10
-rw-r--r--components/appshell/src/nsAppShellFactory.cpp56
-rw-r--r--components/appshell/src/nsAppShellService.cpp942
-rw-r--r--components/appshell/src/nsAppShellService.h58
-rw-r--r--components/appshell/src/nsAppShellWindowEnumerator.cpp495
-rw-r--r--components/appshell/src/nsAppShellWindowEnumerator.h166
-rw-r--r--components/appshell/src/nsChromeTreeOwner.cpp561
-rw-r--r--components/appshell/src/nsChromeTreeOwner.h53
-rw-r--r--components/appshell/src/nsContentTreeOwner.cpp1096
-rw-r--r--components/appshell/src/nsContentTreeOwner.h63
-rw-r--r--components/appshell/src/nsWebShellWindow.cpp911
-rw-r--r--components/appshell/src/nsWebShellWindow.h116
-rw-r--r--components/appshell/src/nsWindowMediator.cpp844
-rw-r--r--components/appshell/src/nsWindowMediator.h78
-rw-r--r--components/appshell/src/nsXULWindow.cpp2325
-rw-r--r--components/appshell/src/nsXULWindow.h199
-rw-r--r--components/asyncshutdown/AsyncShutdown.jsm1020
-rw-r--r--components/asyncshutdown/moz.build14
-rw-r--r--components/asyncshutdown/nsAsyncShutdown.js276
-rw-r--r--components/asyncshutdown/nsAsyncShutdown.manifest2
-rw-r--r--components/asyncshutdown/nsIAsyncShutdown.idl215
-rw-r--r--components/autocomplete/moz.build22
-rw-r--r--components/autocomplete/nsAutoCompleteController.cpp2084
-rw-r--r--components/autocomplete/nsAutoCompleteController.h176
-rw-r--r--components/autocomplete/nsAutoCompleteSimpleResult.cpp313
-rw-r--r--components/autocomplete/nsAutoCompleteSimpleResult.h46
-rw-r--r--components/autocomplete/nsIAutoCompleteController.idl173
-rw-r--r--components/autocomplete/nsIAutoCompleteInput.idl181
-rw-r--r--components/autocomplete/nsIAutoCompletePopup.idl71
-rw-r--r--components/autocomplete/nsIAutoCompleteResult.idl96
-rw-r--r--components/autocomplete/nsIAutoCompleteSearch.idl74
-rw-r--r--components/autocomplete/nsIAutoCompleteSimpleResult.idl122
-rw-r--r--components/autoconfig/moz.build21
-rw-r--r--components/autoconfig/public/nsIAutoConfig.idl26
-rw-r--r--components/autoconfig/public/nsIReadConfig.idl24
-rw-r--r--components/autoconfig/src/nsAutoConfig.cpp532
-rw-r--r--components/autoconfig/src/nsAutoConfig.h54
-rw-r--r--components/autoconfig/src/nsConfigFactory.cpp41
-rw-r--r--components/autoconfig/src/nsJSConfigTriggers.cpp138
-rw-r--r--components/autoconfig/src/nsReadConfig.cpp305
-rw-r--r--components/autoconfig/src/nsReadConfig.h41
-rw-r--r--components/autoconfig/src/prefcalls.js230
-rw-r--r--components/bindings/content/autocomplete.xml2520
-rw-r--r--components/bindings/content/browser.xml1507
-rw-r--r--components/bindings/content/button.xml389
-rw-r--r--components/bindings/content/calendar.js171
-rw-r--r--components/bindings/content/checkbox.xml84
-rw-r--r--components/bindings/content/colorpicker.xml565
-rw-r--r--components/bindings/content/datekeeper.js336
-rw-r--r--components/bindings/content/datepicker.js376
-rw-r--r--components/bindings/content/datetimebox.css55
-rw-r--r--components/bindings/content/datetimebox.xml1443
-rw-r--r--components/bindings/content/datetimepicker.xml1301
-rw-r--r--components/bindings/content/datetimepopup.xml322
-rw-r--r--components/bindings/content/dialog.xml444
-rw-r--r--components/bindings/content/editor.xml195
-rw-r--r--components/bindings/content/expander.xml86
-rw-r--r--components/bindings/content/filefield.xml96
-rw-r--r--components/bindings/content/findbar.xml1371
-rw-r--r--components/bindings/content/general.xml231
-rw-r--r--components/bindings/content/groupbox.xml44
-rw-r--r--components/bindings/content/listbox.xml1144
-rw-r--r--components/bindings/content/menu.xml286
-rw-r--r--components/bindings/content/menulist.xml620
-rw-r--r--components/bindings/content/notification.xml551
-rw-r--r--components/bindings/content/numberbox.xml304
-rw-r--r--components/bindings/content/optionsDialog.xml32
-rw-r--r--components/bindings/content/popup.xml663
-rw-r--r--components/bindings/content/preferences.xml1403
-rw-r--r--components/bindings/content/progressmeter.xml116
-rw-r--r--components/bindings/content/radio.xml526
-rw-r--r--components/bindings/content/remote-browser.xml591
-rw-r--r--components/bindings/content/resizer.xml39
-rw-r--r--components/bindings/content/richlistbox.xml589
-rw-r--r--components/bindings/content/scale.xml232
-rw-r--r--components/bindings/content/scrollbar.xml35
-rw-r--r--components/bindings/content/scrollbox.xml895
-rw-r--r--components/bindings/content/spinbuttons.xml96
-rw-r--r--components/bindings/content/spinner.js501
-rw-r--r--components/bindings/content/splitter.xml37
-rw-r--r--components/bindings/content/stringbundle.xml96
-rw-r--r--components/bindings/content/tabbox.xml870
-rw-r--r--components/bindings/content/text.xml386
-rw-r--r--components/bindings/content/textbox.xml646
-rw-r--r--components/bindings/content/timekeeper.js418
-rw-r--r--components/bindings/content/timepicker.js270
-rw-r--r--components/bindings/content/toolbar.xml631
-rw-r--r--components/bindings/content/toolbarbutton.xml115
-rw-r--r--components/bindings/content/tree.xml1559
-rw-r--r--components/bindings/content/videocontrols.css128
-rw-r--r--components/bindings/content/videocontrols.xml2027
-rw-r--r--components/bindings/content/wizard.xml558
-rw-r--r--components/bindings/jar.mn52
-rw-r--r--components/bindings/moz.build6
-rw-r--r--components/blocklist/blocklist.manifest5
-rw-r--r--components/blocklist/moz.build12
-rw-r--r--components/blocklist/nsBlocklistService.js1611
-rw-r--r--components/build/moz.build56
-rw-r--r--components/build/nsEmbedCID.h58
-rw-r--r--components/build/nsEmbeddingModule.cpp107
-rw-r--r--components/build/nsRDFCID.h76
-rw-r--r--components/build/nsRDFModule.cpp159
-rw-r--r--components/build/nsToolkitCompsCID.h182
-rw-r--r--components/build/nsToolkitCompsModule.cpp203
-rw-r--r--components/captivedetect/CaptivePortalDetectComponents.manifest2
-rw-r--r--components/captivedetect/captivedetect.js476
-rw-r--r--components/captivedetect/moz.build13
-rw-r--r--components/captivedetect/nsICaptivePortalDetector.idl53
-rw-r--r--components/commandlines/moz.build17
-rw-r--r--components/commandlines/nsCommandLine.cpp633
-rw-r--r--components/commandlines/nsICommandLine.idl141
-rw-r--r--components/commandlines/nsICommandLineHandler.idl53
-rw-r--r--components/commandlines/nsICommandLineRunner.idl55
-rw-r--r--components/commandlines/nsICommandLineValidator.idl38
-rw-r--r--components/console/content/console.css74
-rw-r--r--components/console/content/console.js111
-rw-r--r--components/console/content/console.xul96
-rw-r--r--components/console/content/consoleBindings.xml547
-rw-r--r--components/console/jar.mn9
-rw-r--r--components/console/jsconsole-clhandler.js40
-rw-r--r--components/console/jsconsole-clhandler.manifest3
-rw-r--r--components/console/moz.build11
-rw-r--r--components/contentprefs/ContentPrefInstance.jsm75
-rw-r--r--components/contentprefs/ContentPrefService2.jsm885
-rw-r--r--components/contentprefs/ContentPrefServiceChild.jsm181
-rw-r--r--components/contentprefs/ContentPrefServiceParent.jsm136
-rw-r--r--components/contentprefs/ContentPrefStore.jsm123
-rw-r--r--components/contentprefs/ContentPrefUtils.jsm69
-rw-r--r--components/contentprefs/moz.build18
-rw-r--r--components/contentprefs/nsContentPrefService.js1332
-rw-r--r--components/contentprefs/nsContentPrefService.manifest5
-rw-r--r--components/crashes/CrashManager.jsm1285
-rw-r--r--components/crashes/CrashManagerTest.jsm186
-rw-r--r--components/crashes/CrashService.js68
-rw-r--r--components/crashes/CrashService.manifest3
-rw-r--r--components/crashes/docs/crash-events.rst176
-rw-r--r--components/crashes/docs/index.rst24
-rw-r--r--components/crashes/moz.build25
-rw-r--r--components/crashes/nsICrashService.idl29
-rw-r--r--components/crashmonitor/CrashMonitor.jsm224
-rw-r--r--components/crashmonitor/crashmonitor.manifest3
-rw-r--r--components/crashmonitor/moz.build13
-rw-r--r--components/crashmonitor/nsCrashMonitor.js29
-rw-r--r--components/ctypes/ctypes.cpp151
-rw-r--r--components/ctypes/ctypes.h30
-rw-r--r--components/ctypes/ctypes.jsm23
-rw-r--r--components/ctypes/moz.build12
-rw-r--r--components/directory/moz.build19
-rw-r--r--components/directory/public/nsIHTTPIndex.idl50
-rw-r--r--components/directory/src/nsDirectoryViewer.cpp1393
-rw-r--r--components/directory/src/nsDirectoryViewer.h126
-rw-r--r--components/directory/src/nsDirectoryViewerFactory.cpp44
-rw-r--r--components/downloads/content/DownloadProgressListener.js117
-rw-r--r--components/downloads/content/download.xml327
-rw-r--r--components/downloads/content/downloads.css50
-rw-r--r--components/downloads/content/downloads.js1314
-rw-r--r--components/downloads/content/downloads.xul154
-rw-r--r--components/downloads/content/unknownContentType.xul103
-rw-r--r--components/downloads/jar.mn12
-rw-r--r--components/downloads/locale/downloads.dtd52
-rw-r--r--components/downloads/locale/downloads.properties141
-rw-r--r--components/downloads/locale/settingsChange.dtd6
-rw-r--r--components/downloads/locale/unknownContentType.dtd26
-rw-r--r--components/downloads/locale/unknownContentType.properties19
-rw-r--r--components/downloads/moz.build57
-rw-r--r--components/downloads/nsDownloadManagerUI.manifest2
-rw-r--r--components/downloads/nsHelperAppDlg.manifest2
-rw-r--r--components/downloads/public/nsIDownload.idl175
-rw-r--r--components/downloads/public/nsIDownloadManager.idl358
-rw-r--r--components/downloads/public/nsIDownloadManagerUI.idl55
-rw-r--r--components/downloads/public/nsIDownloadProgressListener.idl60
-rw-r--r--components/downloads/src/DownloadLastDir.jsm195
-rw-r--r--components/downloads/src/DownloadPaths.jsm88
-rw-r--r--components/downloads/src/DownloadTaskbarProgress.jsm400
-rw-r--r--components/downloads/src/DownloadUtils.jsm600
-rw-r--r--components/downloads/src/SQLFunctions.cpp146
-rw-r--r--components/downloads/src/SQLFunctions.h46
-rw-r--r--components/downloads/src/nsDownloadManager.cpp3711
-rw-r--r--components/downloads/src/nsDownloadManager.h454
-rw-r--r--components/downloads/src/nsDownloadManagerUI.js107
-rw-r--r--components/downloads/src/nsDownloadProxy.h179
-rw-r--r--components/downloads/src/nsDownloadScanner.cpp728
-rw-r--r--components/downloads/src/nsDownloadScanner.h121
-rw-r--r--components/downloads/src/nsHelperAppDlg.js1138
-rw-r--r--components/exthelper/extApplication.js719
-rw-r--r--components/exthelper/extIApplication.idl416
-rw-r--r--components/exthelper/moz.build9
-rw-r--r--components/feeds/FeedProcessor.js1793
-rw-r--r--components/feeds/FeedProcessor.manifest14
-rw-r--r--components/feeds/moz.build24
-rw-r--r--components/feeds/nsIFeed.idl86
-rw-r--r--components/feeds/nsIFeedContainer.idl85
-rw-r--r--components/feeds/nsIFeedElementBase.idl28
-rw-r--r--components/feeds/nsIFeedEntry.idl46
-rw-r--r--components/feeds/nsIFeedGenerator.idl30
-rw-r--r--components/feeds/nsIFeedListener.idl87
-rw-r--r--components/feeds/nsIFeedPerson.idl30
-rw-r--r--components/feeds/nsIFeedProcessor.idl57
-rw-r--r--components/feeds/nsIFeedResult.idl65
-rw-r--r--components/feeds/nsIFeedTextConstruct.idl57
-rw-r--r--components/filepicker/content/filepicker.js833
-rw-r--r--components/filepicker/content/filepicker.xul80
-rw-r--r--components/filepicker/jar.mn8
-rw-r--r--components/filepicker/moz.build18
-rw-r--r--components/filepicker/nsFilePicker.js319
-rw-r--r--components/filepicker/nsFilePicker.manifest4
-rw-r--r--components/filepicker/nsFileView.cpp989
-rw-r--r--components/filepicker/nsIFileView.idl34
-rw-r--r--components/filewatcher/NativeFileWatcherNotSupported.h52
-rw-r--r--components/filewatcher/NativeFileWatcherWin.cpp1493
-rw-r--r--components/filewatcher/NativeFileWatcherWin.h50
-rw-r--r--components/filewatcher/moz.build16
-rw-r--r--components/filewatcher/nsINativeFileWatcher.idl110
-rw-r--r--components/finalizationwitness/FinalizationWitnessService.cpp247
-rw-r--r--components/finalizationwitness/FinalizationWitnessService.h32
-rw-r--r--components/finalizationwitness/moz.build24
-rw-r--r--components/finalizationwitness/nsIFinalizationWitnessService.idl34
-rw-r--r--components/find/moz.build20
-rw-r--r--components/find/public/nsIFind.idl34
-rw-r--r--components/find/public/nsIFindService.idl26
-rw-r--r--components/find/public/nsIWebBrowserFind.idl145
-rw-r--r--components/find/src/nsFind.cpp1388
-rw-r--r--components/find/src/nsFind.h82
-rw-r--r--components/find/src/nsFindService.cpp101
-rw-r--r--components/find/src/nsFindService.h46
-rw-r--r--components/find/src/nsWebBrowserFind.cpp866
-rw-r--r--components/find/src/nsWebBrowserFind.h94
-rw-r--r--components/formautofill/content/requestAutocomplete.js85
-rw-r--r--components/formautofill/content/requestAutocomplete.xhtml31
-rw-r--r--components/formautofill/formautofill.manifest7
-rw-r--r--components/formautofill/jar.mn8
-rw-r--r--components/formautofill/locale/requestAutocomplete.dtd5
-rw-r--r--components/formautofill/moz.build22
-rw-r--r--components/formautofill/public/nsIFormAutofillContentService.idl46
-rw-r--r--components/formautofill/src/FormAutofill.jsm85
-rw-r--r--components/formautofill/src/FormAutofillContentService.js272
-rw-r--r--components/formautofill/src/FormAutofillIntegration.jsm62
-rw-r--r--components/formautofill/src/FormAutofillStartup.js64
-rw-r--r--components/formautofill/src/RequestAutocompleteUI.jsm58
-rw-r--r--components/gfx/GfxSanityTest.manifest3
-rw-r--r--components/gfx/SanityTest.js274
-rw-r--r--components/gfx/content/gfxFrameScript.js62
-rw-r--r--components/gfx/content/sanityparent.html7
-rw-r--r--components/gfx/content/sanitytest.html6
-rw-r--r--components/gfx/content/videotest.mp4bin0 -> 1563 bytes
-rw-r--r--components/gfx/jar.mn10
-rw-r--r--components/gfx/moz.build14
-rw-r--r--components/global/Makefile.in8
-rw-r--r--components/global/content/TopLevelVideoDocument.js48
-rw-r--r--components/global/content/XPCNativeWrapper.js7
-rw-r--r--components/global/content/about.js54
-rw-r--r--components/global/content/about.xhtml42
-rw-r--r--components/global/content/aboutAbout.js47
-rw-r--r--components/global/content/aboutAbout.xhtml25
-rw-r--r--components/global/content/aboutNetworking.js414
-rw-r--r--components/global/content/aboutNetworking.xhtml168
-rw-r--r--components/global/content/aboutProfiles.js337
-rw-r--r--components/global/content/aboutProfiles.xhtml38
-rw-r--r--components/global/content/aboutRights-unbranded.xhtml59
-rw-r--r--components/global/content/aboutRights.xhtml88
-rw-r--r--components/global/content/aboutServiceWorkers.js184
-rw-r--r--components/global/content/aboutServiceWorkers.xhtml34
-rw-r--r--components/global/content/aboutSupport.js997
-rw-r--r--components/global/content/aboutSupport.xhtml557
-rw-r--r--components/global/content/autocomplete.css40
-rw-r--r--components/global/content/browser-child.js598
-rw-r--r--components/global/content/browser-content.js1756
-rw-r--r--components/global/content/buildconfig.html62
-rw-r--r--components/global/content/contentAreaUtils.js1297
-rw-r--r--components/global/content/customizeToolbar.css39
-rw-r--r--components/global/content/customizeToolbar.js852
-rw-r--r--components/global/content/customizeToolbar.xul67
-rw-r--r--components/global/content/datepicker.xhtml60
-rw-r--r--components/global/content/dialogOverlay.js107
-rw-r--r--components/global/content/dialogOverlay.xul58
-rw-r--r--components/global/content/directionDetector.html13
-rw-r--r--components/global/content/editMenuOverlay.js39
-rw-r--r--components/global/content/editMenuOverlay.xul108
-rw-r--r--components/global/content/filepicker.properties12
-rw-r--r--components/global/content/findUtils.js111
-rw-r--r--components/global/content/finddialog.js151
-rw-r--r--components/global/content/finddialog.xul58
-rw-r--r--components/global/content/globalOverlay.js161
-rw-r--r--components/global/content/globalOverlay.xul38
-rw-r--r--components/global/content/inlineSpellCheckUI.js7
-rw-r--r--components/global/content/license.html4186
-rw-r--r--components/global/content/logopage.xhtml43
-rw-r--r--components/global/content/menulist.css11
-rw-r--r--components/global/content/minimal-xul.css133
-rw-r--r--components/global/content/mozilla.xhtml76
-rw-r--r--components/global/content/nsClipboard.js64
-rw-r--r--components/global/content/nsUserSettings.js108
-rw-r--r--components/global/content/plugins.css88
-rw-r--r--components/global/content/plugins.html207
-rw-r--r--components/global/content/process-content.js77
-rw-r--r--components/global/content/resetProfile.css15
-rw-r--r--components/global/content/resetProfile.js20
-rw-r--r--components/global/content/resetProfile.xul35
-rw-r--r--components/global/content/resetProfileProgress.xul25
-rw-r--r--components/global/content/select-child.js26
-rw-r--r--components/global/content/strres.js28
-rw-r--r--components/global/content/tests/reftests/reftest-stylo.list6
-rw-r--r--components/global/content/tests/reftests/reftest.list2
-rw-r--r--components/global/content/textbox.css35
-rw-r--r--components/global/content/timepicker.xhtml36
-rw-r--r--components/global/content/treeUtils.js78
-rw-r--r--components/global/content/viewZoomOverlay.js117
-rw-r--r--components/global/content/xul.css1198
-rw-r--r--components/global/jar.mn66
-rw-r--r--components/global/locale/about.dtd31
-rw-r--r--components/global/locale/aboutAbout.dtd8
-rw-r--r--components/global/locale/aboutNetworking.dtd43
-rw-r--r--components/global/locale/aboutProfiles.dtd10
-rw-r--r--components/global/locale/aboutProfiles.properties42
-rw-r--r--components/global/locale/aboutReader.properties59
-rw-r--r--components/global/locale/aboutRights.dtd84
-rw-r--r--components/global/locale/aboutServiceWorkers.dtd12
-rw-r--r--components/global/locale/aboutServiceWorkers.properties36
-rw-r--r--components/global/locale/aboutSupport.dtd136
-rw-r--r--components/global/locale/aboutSupport.properties116
-rw-r--r--components/global/locale/aboutTelemetry.dtd169
-rw-r--r--components/global/locale/aboutTelemetry.properties83
-rw-r--r--components/global/locale/aboutWebrtc.properties120
-rw-r--r--components/global/locale/appPicker.dtd7
-rw-r--r--components/global/locale/autocomplete.properties28
-rw-r--r--components/global/locale/autoconfig.properties12
-rw-r--r--components/global/locale/browser.properties14
-rw-r--r--components/global/locale/charsetMenu.dtd6
-rw-r--r--components/global/locale/charsetMenu.properties116
-rw-r--r--components/global/locale/commonDialog.dtd13
-rw-r--r--components/global/locale/commonDialogs.properties32
-rw-r--r--components/global/locale/config.dtd50
-rw-r--r--components/global/locale/config.properties22
-rw-r--r--components/global/locale/console.dtd37
-rw-r--r--components/global/locale/console.properties17
-rw-r--r--components/global/locale/contentAreaCommands.properties22
-rw-r--r--components/global/locale/customizeToolbar.dtd16
-rw-r--r--components/global/locale/customizeToolbar.properties12
-rw-r--r--components/global/locale/dateFormat.properties58
-rw-r--r--components/global/locale/datetimebox.dtd9
-rw-r--r--components/global/locale/datetimepicker.dtd7
-rw-r--r--components/global/locale/dialog.properties12
-rw-r--r--components/global/locale/dialogOverlay.dtd10
-rw-r--r--components/global/locale/editMenuOverlay.dtd35
-rw-r--r--components/global/locale/extensions.properties29
-rw-r--r--components/global/locale/fallbackMenubar.properties8
-rw-r--r--components/global/locale/filefield.properties7
-rw-r--r--components/global/locale/filepicker.dtd21
-rw-r--r--components/global/locale/filepicker.properties55
-rw-r--r--components/global/locale/findbar.dtd23
-rw-r--r--components/global/locale/findbar.properties22
-rw-r--r--components/global/locale/finddialog.dtd22
-rw-r--r--components/global/locale/finddialog.properties6
-rw-r--r--components/global/locale/globalKeys.dtd6
-rw-r--r--components/global/locale/headsUpDisplay.properties15
-rw-r--r--components/global/locale/intl.css11
-rw-r--r--components/global/locale/intl.properties61
-rw-r--r--components/global/locale/keys.properties71
-rw-r--r--components/global/locale/languageNames.properties201
-rw-r--r--components/global/locale/narrate.properties19
-rw-r--r--components/global/locale/notification.dtd11
-rw-r--r--components/global/locale/nsTreeSorting.properties5
-rw-r--r--components/global/locale/preferences.dtd9
-rw-r--r--components/global/locale/printPageSetup.dtd66
-rw-r--r--components/global/locale/printPreview.dtd43
-rw-r--r--components/global/locale/printPreviewProgress.dtd9
-rw-r--r--components/global/locale/printProgress.dtd21
-rw-r--r--components/global/locale/printdialog.dtd44
-rw-r--r--components/global/locale/printdialog.properties63
-rw-r--r--components/global/locale/printjoboptions.dtd29
-rw-r--r--components/global/locale/regionNames.properties276
-rw-r--r--components/global/locale/resetProfile.dtd15
-rw-r--r--components/global/locale/resetProfile.properties14
-rw-r--r--components/global/locale/textcontext.dtd37
-rw-r--r--components/global/locale/tree.dtd5
-rw-r--r--components/global/locale/unix/intl.properties7
-rw-r--r--components/global/locale/unix/platformKeys.properties25
-rw-r--r--components/global/locale/videocontrols.dtd39
-rw-r--r--components/global/locale/viewSource.dtd86
-rw-r--r--components/global/locale/viewSource.properties18
-rw-r--r--components/global/locale/win/intl.properties7
-rw-r--r--components/global/locale/win/platformKeys.properties25
-rw-r--r--components/global/locale/wizard.dtd24
-rw-r--r--components/global/locale/wizard.properties8
-rw-r--r--components/global/moz.build16
-rw-r--r--components/gservice/moz.build35
-rw-r--r--components/gservice/src/nsAlertsIconListener.cpp327
-rw-r--r--components/gservice/src/nsAlertsIconListener.h91
-rw-r--r--components/gservice/src/nsGConfService.cpp305
-rw-r--r--components/gservice/src/nsGConfService.h31
-rw-r--r--components/gservice/src/nsGIOProtocolHandler.cpp1131
-rw-r--r--components/gservice/src/nsGIOService.cpp475
-rw-r--r--components/gservice/src/nsGIOService.h24
-rw-r--r--components/gservice/src/nsGSettingsService.cpp350
-rw-r--r--components/gservice/src/nsGSettingsService.h27
-rw-r--r--components/gservice/src/nsGnomeModule.cpp78
-rw-r--r--components/gservice/src/nsPackageKitService.cpp267
-rw-r--r--components/gservice/src/nsPackageKitService.h26
-rw-r--r--components/gservice/src/nsSystemAlertsService.cpp118
-rw-r--r--components/gservice/src/nsSystemAlertsService.h39
-rw-r--r--components/handling/content/dialog.js278
-rw-r--r--components/handling/content/dialog.xul57
-rw-r--r--components/handling/content/handler.css11
-rw-r--r--components/handling/content/handler.xml28
-rw-r--r--components/handling/jar.mn10
-rw-r--r--components/handling/locale/handling.dtd10
-rw-r--r--components/handling/locale/handling.properties12
-rw-r--r--components/handling/moz.build11
-rw-r--r--components/handling/nsContentDispatchChooser.manifest2
-rw-r--r--components/handling/src/nsContentDispatchChooser.js85
-rw-r--r--components/htmlfive/jArray.h155
-rw-r--r--components/htmlfive/java/Makefile44
-rw-r--r--components/htmlfive/java/README.txt78
-rw-r--r--components/htmlfive/java/htmlparser/HtmlParser-compile3
-rw-r--r--components/htmlfive/java/htmlparser/HtmlParser-compile-detailed3
-rw-r--r--components/htmlfive/java/htmlparser/HtmlParser-compile-detailed.launch24
-rw-r--r--components/htmlfive/java/htmlparser/HtmlParser-compile.launch22
-rw-r--r--components/htmlfive/java/htmlparser/HtmlParser-linux3
-rw-r--r--components/htmlfive/java/htmlparser/HtmlParser-shell3
-rw-r--r--components/htmlfive/java/htmlparser/HtmlParser.launch23
-rw-r--r--components/htmlfive/java/htmlparser/LICENSE.txt96
-rw-r--r--components/htmlfive/java/htmlparser/README.txt5
-rw-r--r--components/htmlfive/java/htmlparser/doc/README15
-rw-r--r--components/htmlfive/java/htmlparser/doc/named-character-references.html4
-rw-r--r--components/htmlfive/java/htmlparser/doc/tokenization.txt1147
-rw-r--r--components/htmlfive/java/htmlparser/doc/tree-construction.txt2201
-rw-r--r--components/htmlfive/java/htmlparser/generate-encoding-data.py745
-rw-r--r--components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/HtmlParser.gwt.xml12
-rw-r--r--components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/BrowserTreeBuilder.java477
-rw-r--r--components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/HtmlParser.java265
-rw-r--r--components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/HtmlParserModule.java87
-rw-r--r--components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/ParseEndListener.java46
-rw-r--r--components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/HtmlParser.html225
-rw-r--r--components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/LICENSE.Live-DOM-viewer.txt25
-rw-r--r--components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/blank.html2
-rw-r--r--components/htmlfive/java/htmlparser/mozilla-export-scripts/README.txt25
-rw-r--r--components/htmlfive/java/htmlparser/mozilla-export-scripts/export-all.sh24
-rw-r--r--components/htmlfive/java/htmlparser/mozilla-export-scripts/export-java-srcs.sh25
-rw-r--r--components/htmlfive/java/htmlparser/mozilla-export-scripts/export-translator.sh24
-rw-r--r--components/htmlfive/java/htmlparser/mozilla-export-scripts/make-translator-jar.sh63
-rw-r--r--components/htmlfive/java/htmlparser/mozilla-export-scripts/util.sh23
-rw-r--r--components/htmlfive/java/htmlparser/pom.xml240
-rw-r--r--components/htmlfive/java/htmlparser/ruby-gcj/DomUtils.java36
-rw-r--r--components/htmlfive/java/htmlparser/ruby-gcj/README65
-rw-r--r--components/htmlfive/java/htmlparser/ruby-gcj/Rakefile77
-rw-r--r--components/htmlfive/java/htmlparser/ruby-gcj/extconf.rb45
-rw-r--r--components/htmlfive/java/htmlparser/ruby-gcj/test/domencoding.rb5
-rw-r--r--components/htmlfive/java/htmlparser/ruby-gcj/test/fonts.rb11
-rw-r--r--components/htmlfive/java/htmlparser/ruby-gcj/test/google.html10
-rw-r--r--components/htmlfive/java/htmlparser/ruby-gcj/test/greek.xml2
-rw-r--r--components/htmlfive/java/htmlparser/ruby-gcj/validator.cpp210
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5.java59
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Data.java185
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Decoder.java184
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Encoder.java185
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Decoder.java80
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Encoder.java95
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Encoding.java886
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/EucJp.java57
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/EucKr.java64
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/FallibleSingleByteDecoder.java61
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Gb18030.java55
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Gbk.java63
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Ibm866.java184
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/InfallibleSingleByteDecoder.java57
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso10.java187
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso13.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso14.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso15.java186
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso16.java181
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso2.java189
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso2022Jp.java56
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso3.java189
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso4.java189
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso5.java188
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso6.java194
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso7.java192
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso8.java191
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso8I.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Koi8R.java185
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Koi8U.java182
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/MacCyrillic.java182
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Macintosh.java184
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Replacement.java59
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/ReplacementDecoder.java75
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/ShiftJis.java62
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/UserDefined.java55
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/UserDefinedDecoder.java56
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf16Be.java55
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf16Le.java56
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf8.java57
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1250.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1251.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1252.java197
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1253.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1254.java192
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1255.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1256.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1257.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1258.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows874.java186
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Auto.java27
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/CharacterName.java27
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Const.java34
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Creator.java30
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/HtmlCreator.java30
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/IdType.java34
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Inline.java33
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Literal.java34
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Local.java34
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/NoLength.java34
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/NsUri.java33
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Prefix.java33
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/QName.java33
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/SvgCreator.java30
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Unsigned.java30
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Virtual.java33
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/package.html30
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/ByteReadable.java44
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/CharacterHandler.java59
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DoctypeExpectation.java65
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DocumentMode.java47
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DocumentModeHandler.java46
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/EncodingDeclarationHandler.java58
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/Heuristics.java52
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/Interner.java35
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/TokenHandler.java183
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/TransitionHandler.java53
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/XmlViolationPolicy.java48
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/package.html29
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/DOMTreeBuilder.java357
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/Dom2Sax.java259
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/HtmlDocumentBuilder.java736
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/package.html29
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/ChardetSniffer.java84
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/IcuDetectorSniffer.java77
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/NormalizationChecker.java268
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/AttributeName.java2539
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/CoalescingTreeBuilder.java83
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/ElementName.java3068
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/ErrorReportingTokenizer.java772
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/HotSpotWorkaround.txt55
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/HtmlAttributes.java520
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/LocatorImpl.java60
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/MetaScanner.java856
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NCName.java495
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NamedCharacters.java944
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NamedCharactersAccel.java311
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/Portability.java165
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/PushedLocation.java136
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/StackNode.java332
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/StateSnapshot.java206
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TaintableLocatorImpl.java43
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/Tokenizer.java7140
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TreeBuilder.java6639
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TreeBuilderState.java129
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/UTF16Buffer.java153
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/package.html30
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/BomSniffer.java79
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Confidence.java27
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Driver.java597
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Encoding.java395
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/HtmlInputStreamReader.java512
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/MetaSniffer.java199
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/rewindable/Rewindable.java42
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/rewindable/RewindableInputStream.java235
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/HtmlParser.java1097
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/HtmlSerializer.java269
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/InfosetCoercingHtmlParser.java47
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/NameCheckingXmlSerializer.java51
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/SAXStreamer.java186
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/SAXTreeBuilder.java200
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/XmlSerializer.java737
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/package.html29
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/FormPointer.java49
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/FormPtrElement.java87
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/HtmlBuilder.java773
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/ModalDocument.java75
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/Mode.java48
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/SimpleNodeFactory.java102
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/XOMTreeBuilder.java351
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/package.html29
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/CDATA.java70
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/CharBufferNode.java62
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Characters.java65
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Comment.java66
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/DTD.java118
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Document.java70
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/DocumentFragment.java58
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Element.java172
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Entity.java86
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/IgnorableWhitespace.java65
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/LocatorImpl.java104
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Node.java307
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/NodeType.java76
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/NullLexicalHandler.java85
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/ParentNode.java208
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/PrefixMapping.java65
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/ProcessingInstruction.java94
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/SkippedEntity.java77
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/TreeBuilder.java250
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/TreeParser.java301
-rw-r--r--components/htmlfive/java/htmlparser/src/nu/validator/saxtree/package.html46
-rw-r--r--components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/java/io/IOException.java42
-rw-r--r--components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/Attributes.java257
-rw-r--r--components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/ErrorHandler.java139
-rw-r--r--components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/Locator.java136
-rw-r--r--components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/SAXException.java153
-rw-r--r--components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/SAXParseException.java269
-rw-r--r--components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/package.html297
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/encoding/test/Big5Tester.java96
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/encoding/test/EncodingTester.java491
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DecoderLoopTester.java115
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DomIdTester.java49
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DomTest.java40
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/EncodingTester.java123
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/JSONArrayTokenHandler.java185
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/ListErrorHandler.java66
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/SystemErrErrorHandler.java201
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TokenPrinter.java210
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TokenizerTester.java211
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreeDumpContentHandler.java239
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreePrinter.java50
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreeTester.java246
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/UntilHashInputStream.java97
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/XmlSerializerTester.java63
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/XomTest.java33
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/package.html29
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/HTML2HTML.java87
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/HTML2XML.java86
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XML2HTML.java89
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XML2XML.java89
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XSLT4HTML5.java237
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XSLT4HTML5XOM.java162
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XmlnsDropper.java169
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/package.html29
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/saxtree/test/PassThruPrinter.java67
-rw-r--r--components/htmlfive/java/htmlparser/test-src/nu/validator/saxtree/test/package.html29
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/AnnotationHelperVisitor.java155
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppOnlyInputStream.java70
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppTypes.java462
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppVisitor.java2418
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/GkAtomParser.java70
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/HVisitor.java291
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LabelVisitor.java84
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LicenseExtractor.java75
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LocalSymbolTable.java89
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/Main.java145
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/NoCppInputStream.java86
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/StringLiteralParser.java70
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/StringPair.java73
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/SymbolTable.java93
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/SymbolTableVisitor.java71
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/TranslatorUtils.java81
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/Type.java99
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/ApplyHotSpotWorkaround.java104
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/GenerateNamedCharacters.java182
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/GenerateNamedCharactersCpp.java580
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/DuplicatingFallThroughRemover.java79
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/JavaVisitor.java1349
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/LoopBreakAnalyzerVisitor.java183
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/Main.java144
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/ModeFallThroughRemover.java106
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/RustVisitor.java1586
-rw-r--r--components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/SwitchBreakAnalyzerVisitor.java191
-rw-r--r--components/htmlfive/java/javaparser/LICENSE165
-rw-r--r--components/htmlfive/java/javaparser/ant/ant-googlecode-0.0.1.jarbin0 -> 3268 bytes
-rw-r--r--components/htmlfive/java/javaparser/ant/build.xml86
-rw-r--r--components/htmlfive/java/javaparser/readme.txt138
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ASTHelper.java264
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ASTParser.java7803
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ASTParserConstants.java410
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ASTParserTokenManager.java2323
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/JavaCharStream.java634
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/JavaParser.java126
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ParseException.java216
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/Token.java142
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/TokenMgrError.java159
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/BlockComment.java58
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/Comment.java68
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/CompilationUnit.java188
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/ImportDeclaration.java141
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/LineComment.java57
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/Node.java187
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/PackageDeclaration.java122
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/TypeParameter.java118
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/AnnotationDeclaration.java60
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/AnnotationMemberDeclaration.java118
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/BodyDeclaration.java68
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/ClassOrInterfaceDeclaration.java110
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/ConstructorDeclaration.java141
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/EmptyMemberDeclaration.java52
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/EmptyTypeDeclaration.java52
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/EnumConstantDeclaration.java96
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/EnumDeclaration.java84
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/FieldDeclaration.java112
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/InitializerDeclaration.java82
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/JavadocComment.java53
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/MethodDeclaration.java174
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/ModifierSet.java118
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/Parameter.java125
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/TypeDeclaration.java90
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/VariableDeclarator.java82
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/body/VariableDeclaratorId.java76
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/AnnotationExpr.java36
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayAccessExpr.java76
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayCreationExpr.java118
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayInitializerExpr.java66
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/AssignExpr.java103
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/BinaryExpr.java110
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/BooleanLiteralExpr.java63
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/CastExpr.java77
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/CharLiteralExpr.java52
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ClassExpr.java65
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ConditionalExpr.java88
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/DoubleLiteralExpr.java52
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/EnclosedExpr.java64
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/Expression.java38
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/FieldAccessExpr.java90
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/InstanceOfExpr.java77
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/IntegerLiteralExpr.java62
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/IntegerLiteralMinValueExpr.java50
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LiteralExpr.java35
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LongLiteralExpr.java63
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LongLiteralMinValueExpr.java50
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MarkerAnnotationExpr.java64
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MemberValuePair.java77
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MethodCallExpr.java107
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NameExpr.java64
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NormalAnnotationExpr.java78
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NullLiteralExpr.java48
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ObjectCreationExpr.java115
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/QualifiedNameExpr.java65
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/SingleMemberAnnotationExpr.java76
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/StringLiteralExpr.java63
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/SuperExpr.java64
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ThisExpr.java64
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/UnaryExpr.java87
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/expr/VariableDeclarationExpr.java114
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/AssertStmt.java80
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/BlockStmt.java65
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/BreakStmt.java63
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/CatchClause.java77
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ContinueStmt.java63
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/DoStmt.java76
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/EmptyStmt.java48
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ExplicitConstructorInvocationStmt.java102
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ExpressionStmt.java64
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ForStmt.java102
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ForeachStmt.java89
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/IfStmt.java88
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/LabeledStmt.java75
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ReturnStmt.java64
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/Statement.java38
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SwitchEntryStmt.java78
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SwitchStmt.java78
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SynchronizedStmt.java77
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ThrowStmt.java64
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/TryStmt.java89
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/TypeDeclarationStmt.java64
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/WhileStmt.java76
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/type/ClassOrInterfaceType.java92
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/type/PrimitiveType.java68
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/type/ReferenceType.java80
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/type/Type.java38
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/type/VoidType.java49
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/type/WildcardType.java80
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/DumpVisitor.java1302
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/GenericVisitor.java277
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/GenericVisitorAdapter.java825
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/ModifierVisitorAdapter.java940
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/VoidVisitor.java277
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/VoidVisitorAdapter.java743
-rw-r--r--components/htmlfive/java/javaparser/src/japa/parser/java_1_5.jj3006
-rw-r--r--components/htmlfive/java/javaparser/test/ignore/TestRunner.java127
-rw-r--r--components/htmlfive/java/javaparser/test/japa/parser/ast/test/AllTests.java25
-rw-r--r--components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestAdapters.java80
-rw-r--r--components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestDumper.java86
-rw-r--r--components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestHelper.java61
-rw-r--r--components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestNodePositions.java639
-rw-r--r--components/htmlfive/java/javaparser/test/japa/parser/ast/test/classes/DumperTestClass.java364
-rw-r--r--components/htmlfive/java/javaparser/test/japa/parser/ast/test/classes/JavadocTestClass.java127
-rw-r--r--components/htmlfive/java/manifest.txt2
-rw-r--r--components/htmlfive/java/named-character-references.html7
-rw-r--r--components/htmlfive/moz.build110
-rw-r--r--components/htmlfive/nsAHtml5TreeBuilderState.h50
-rw-r--r--components/htmlfive/nsAHtml5TreeOpSink.h24
-rw-r--r--components/htmlfive/nsHtml5ArrayCopy.h80
-rw-r--r--components/htmlfive/nsHtml5Atom.cpp91
-rw-r--r--components/htmlfive/nsHtml5Atom.h28
-rw-r--r--components/htmlfive/nsHtml5AtomList.h1078
-rw-r--r--components/htmlfive/nsHtml5AtomTable.cpp67
-rw-r--r--components/htmlfive/nsHtml5AtomTable.h113
-rw-r--r--components/htmlfive/nsHtml5Atoms.cpp36
-rw-r--r--components/htmlfive/nsHtml5Atoms.h30
-rw-r--r--components/htmlfive/nsHtml5AttributeEntry.h91
-rw-r--r--components/htmlfive/nsHtml5AttributeName.cpp2560
-rw-r--r--components/htmlfive/nsHtml5AttributeName.h776
-rw-r--r--components/htmlfive/nsHtml5ByteReadable.h33
-rw-r--r--components/htmlfive/nsHtml5ContentCreatorFunction.h17
-rw-r--r--components/htmlfive/nsHtml5DependentUTF16Buffer.cpp33
-rw-r--r--components/htmlfive/nsHtml5DependentUTF16Buffer.h31
-rw-r--r--components/htmlfive/nsHtml5DocumentBuilder.cpp119
-rw-r--r--components/htmlfive/nsHtml5DocumentBuilder.h129
-rw-r--r--components/htmlfive/nsHtml5DocumentMode.h14
-rw-r--r--components/htmlfive/nsHtml5ElementName.cpp1702
-rw-r--r--components/htmlfive/nsHtml5ElementName.h620
-rw-r--r--components/htmlfive/nsHtml5Highlighter.cpp816
-rw-r--r--components/htmlfive/nsHtml5Highlighter.h418
-rw-r--r--components/htmlfive/nsHtml5HtmlAttributes.cpp263
-rw-r--r--components/htmlfive/nsHtml5HtmlAttributes.h103
-rw-r--r--components/htmlfive/nsHtml5Macros.h32
-rw-r--r--components/htmlfive/nsHtml5MetaScanner.cpp815
-rw-r--r--components/htmlfive/nsHtml5MetaScanner.h171
-rw-r--r--components/htmlfive/nsHtml5MetaScannerCppSupplement.h47
-rw-r--r--components/htmlfive/nsHtml5MetaScannerHSupplement.h12
-rw-r--r--components/htmlfive/nsHtml5Module.cpp137
-rw-r--r--components/htmlfive/nsHtml5Module.h28
-rw-r--r--components/htmlfive/nsHtml5NamedCharacters.cpp141
-rw-r--r--components/htmlfive/nsHtml5NamedCharacters.h52
-rw-r--r--components/htmlfive/nsHtml5NamedCharactersAccel.cpp313
-rw-r--r--components/htmlfive/nsHtml5NamedCharactersAccel.h24
-rw-r--r--components/htmlfive/nsHtml5NamedCharactersInclude.h2266
-rw-r--r--components/htmlfive/nsHtml5OplessBuilder.cpp48
-rw-r--r--components/htmlfive/nsHtml5OplessBuilder.h34
-rw-r--r--components/htmlfive/nsHtml5OwningUTF16Buffer.cpp86
-rw-r--r--components/htmlfive/nsHtml5OwningUTF16Buffer.h58
-rw-r--r--components/htmlfive/nsHtml5Parser.cpp753
-rw-r--r--components/htmlfive/nsHtml5Parser.h362
-rw-r--r--components/htmlfive/nsHtml5PlainTextUtils.cpp43
-rw-r--r--components/htmlfive/nsHtml5PlainTextUtils.h16
-rw-r--r--components/htmlfive/nsHtml5Portability.cpp151
-rw-r--r--components/htmlfive/nsHtml5Portability.h82
-rw-r--r--components/htmlfive/nsHtml5RefPtr.h449
-rw-r--r--components/htmlfive/nsHtml5SVGLoadDispatcher.cpp40
-rw-r--r--components/htmlfive/nsHtml5SVGLoadDispatcher.h21
-rw-r--r--components/htmlfive/nsHtml5Speculation.cpp36
-rw-r--r--components/htmlfive/nsHtml5Speculation.h75
-rw-r--r--components/htmlfive/nsHtml5SpeculativeLoad.cpp103
-rw-r--r--components/htmlfive/nsHtml5SpeculativeLoad.h295
-rw-r--r--components/htmlfive/nsHtml5StackNode.cpp244
-rw-r--r--components/htmlfive/nsHtml5StackNode.h106
-rw-r--r--components/htmlfive/nsHtml5StateSnapshot.cpp185
-rw-r--r--components/htmlfive/nsHtml5StateSnapshot.h97
-rw-r--r--components/htmlfive/nsHtml5StreamListener.cpp82
-rw-r--r--components/htmlfive/nsHtml5StreamListener.h55
-rw-r--r--components/htmlfive/nsHtml5StreamParser.cpp1726
-rw-r--r--components/htmlfive/nsHtml5StreamParser.h579
-rw-r--r--components/htmlfive/nsHtml5String.cpp210
-rw-r--r--components/htmlfive/nsHtml5String.h153
-rw-r--r--components/htmlfive/nsHtml5StringParser.cpp130
-rw-r--r--components/htmlfive/nsHtml5StringParser.h88
-rw-r--r--components/htmlfive/nsHtml5Tokenizer.cpp4125
-rw-r--r--components/htmlfive/nsHtml5Tokenizer.h468
-rw-r--r--components/htmlfive/nsHtml5TokenizerCppSupplement.h585
-rw-r--r--components/htmlfive/nsHtml5TokenizerHSupplement.h148
-rw-r--r--components/htmlfive/nsHtml5TokenizerLoopPolicies.h47
-rw-r--r--components/htmlfive/nsHtml5TreeBuilder.cpp4522
-rw-r--r--components/htmlfive/nsHtml5TreeBuilder.h503
-rw-r--r--components/htmlfive/nsHtml5TreeBuilderCppSupplement.h1724
-rw-r--r--components/htmlfive/nsHtml5TreeBuilderHSupplement.h248
-rw-r--r--components/htmlfive/nsHtml5TreeOpExecutor.cpp1101
-rw-r--r--components/htmlfive/nsHtml5TreeOpExecutor.h309
-rw-r--r--components/htmlfive/nsHtml5TreeOpStage.cpp56
-rw-r--r--components/htmlfive/nsHtml5TreeOpStage.h54
-rw-r--r--components/htmlfive/nsHtml5TreeOperation.cpp1237
-rw-r--r--components/htmlfive/nsHtml5TreeOperation.h546
-rw-r--r--components/htmlfive/nsHtml5UTF16Buffer.cpp122
-rw-r--r--components/htmlfive/nsHtml5UTF16Buffer.h83
-rw-r--r--components/htmlfive/nsHtml5UTF16BufferCppSupplement.h36
-rw-r--r--components/htmlfive/nsHtml5UTF16BufferHSupplement.h17
-rw-r--r--components/htmlfive/nsHtml5ViewSourceUtils.cpp56
-rw-r--r--components/htmlfive/nsHtml5ViewSourceUtils.h17
-rw-r--r--components/htmlfive/nsIContentHandle.h5
-rw-r--r--components/htmlfive/nsIParserUtils.idl130
-rw-r--r--components/htmlfive/nsIScriptableUnescapeHTML.idl54
-rw-r--r--components/htmlfive/nsParserUtils.cpp232
-rw-r--r--components/htmlfive/nsParserUtils.h23
-rw-r--r--components/htmlparser/moz.build50
-rw-r--r--components/htmlparser/public/nsIExpatSink.idl109
-rw-r--r--components/htmlparser/public/nsIExtendedExpatSink.idl72
-rw-r--r--components/htmlparser/src/CNavDTD.cpp90
-rw-r--r--components/htmlparser/src/CNavDTD.h35
-rw-r--r--components/htmlparser/src/CParserContext.cpp85
-rw-r--r--components/htmlparser/src/CParserContext.h70
-rw-r--r--components/htmlparser/src/nsCharsetSource.h26
-rw-r--r--components/htmlparser/src/nsElementTable.cpp210
-rw-r--r--components/htmlparser/src/nsElementTable.h21
-rw-r--r--components/htmlparser/src/nsExpatDriver.cpp1412
-rw-r--r--components/htmlparser/src/nsExpatDriver.h145
-rw-r--r--components/htmlparser/src/nsHTMLEntities.cpp205
-rw-r--r--components/htmlparser/src/nsHTMLEntities.h35
-rw-r--r--components/htmlparser/src/nsHTMLEntityList.h303
-rw-r--r--components/htmlparser/src/nsHTMLTagList.h197
-rw-r--r--components/htmlparser/src/nsHTMLTags.cpp259
-rw-r--r--components/htmlparser/src/nsHTMLTags.h100
-rw-r--r--components/htmlparser/src/nsHTMLTokenizer.cpp59
-rw-r--r--components/htmlparser/src/nsHTMLTokenizer.h35
-rw-r--r--components/htmlparser/src/nsIContentSink.h132
-rw-r--r--components/htmlparser/src/nsIDTD.h136
-rw-r--r--components/htmlparser/src/nsIFragmentContentSink.h77
-rw-r--r--components/htmlparser/src/nsIHTMLContentSink.h89
-rw-r--r--components/htmlparser/src/nsIParser.h272
-rw-r--r--components/htmlparser/src/nsIParserService.h98
-rw-r--r--components/htmlparser/src/nsITokenizer.h44
-rw-r--r--components/htmlparser/src/nsParser.cpp1599
-rw-r--r--components/htmlparser/src/nsParser.h398
-rw-r--r--components/htmlparser/src/nsParserBase.h20
-rw-r--r--components/htmlparser/src/nsParserCIID.h39
-rw-r--r--components/htmlparser/src/nsParserConstants.h38
-rw-r--r--components/htmlparser/src/nsParserModule.cpp107
-rw-r--r--components/htmlparser/src/nsParserMsgUtils.cpp65
-rw-r--r--components/htmlparser/src/nsParserMsgUtils.h21
-rw-r--r--components/htmlparser/src/nsParserService.cpp90
-rw-r--r--components/htmlparser/src/nsParserService.h58
-rw-r--r--components/htmlparser/src/nsScanner.cpp408
-rw-r--r--components/htmlparser/src/nsScanner.h190
-rw-r--r--components/htmlparser/src/nsScannerString.cpp650
-rw-r--r--components/htmlparser/src/nsScannerString.h604
-rw-r--r--components/htmlparser/src/nsToken.h19
-rw-r--r--components/jar/appnote.txt1192
-rw-r--r--components/jar/moz.build39
-rw-r--r--components/jar/public/nsIJARChannel.idl32
-rw-r--r--components/jar/public/nsIJARProtocolHandler.idl17
-rw-r--r--components/jar/public/nsIJARURI.idl38
-rw-r--r--components/jar/public/nsIZipReader.idl273
-rw-r--r--components/jar/public/nsIZipWriter.idl220
-rw-r--r--components/jar/src/StreamFunctions.cpp48
-rw-r--r--components/jar/src/StreamFunctions.h68
-rw-r--r--components/jar/src/ZipWriterModule.cpp37
-rw-r--r--components/jar/src/nsDeflateConverter.cpp188
-rw-r--r--components/jar/src/nsDeflateConverter.h63
-rw-r--r--components/jar/src/nsIJARFactory.h10
-rw-r--r--components/jar/src/nsJAR.cpp1396
-rw-r--r--components/jar/src/nsJAR.h224
-rw-r--r--components/jar/src/nsJARChannel.cpp1087
-rw-r--r--components/jar/src/nsJARChannel.h109
-rw-r--r--components/jar/src/nsJARFactory.cpp64
-rw-r--r--components/jar/src/nsJARInputStream.cpp416
-rw-r--r--components/jar/src/nsJARInputStream.h83
-rw-r--r--components/jar/src/nsJARProtocolHandler.cpp188
-rw-r--r--components/jar/src/nsJARProtocolHandler.h52
-rw-r--r--components/jar/src/nsJARURI.cpp912
-rw-r--r--components/jar/src/nsJARURI.h96
-rw-r--r--components/jar/src/nsZipArchive.cpp1336
-rw-r--r--components/jar/src/nsZipArchive.h438
-rw-r--r--components/jar/src/nsZipDataStream.cpp181
-rw-r--r--components/jar/src/nsZipDataStream.h45
-rw-r--r--components/jar/src/nsZipHeader.cpp389
-rw-r--r--components/jar/src/nsZipHeader.h94
-rw-r--r--components/jar/src/nsZipWriter.cpp1133
-rw-r--r--components/jar/src/nsZipWriter.h80
-rw-r--r--components/jar/src/zipstruct.h108
-rw-r--r--components/jetpack/app-extension/application.ini11
-rw-r--r--components/jetpack/app-extension/bootstrap.js362
-rw-r--r--components/jetpack/app-extension/install.rdf33
-rw-r--r--components/jetpack/dev/debuggee.js95
-rw-r--r--components/jetpack/dev/frame-script.js120
-rw-r--r--components/jetpack/dev/panel.js259
-rw-r--r--components/jetpack/dev/panel/view.js14
-rw-r--r--components/jetpack/dev/ports.js64
-rw-r--r--components/jetpack/dev/theme.js135
-rw-r--r--components/jetpack/dev/theme/hooks.js17
-rw-r--r--components/jetpack/dev/toolbox.js107
-rw-r--r--components/jetpack/dev/utils.js40
-rw-r--r--components/jetpack/dev/volcan.js3848
-rw-r--r--components/jetpack/diffpatcher/.travis.yml5
-rw-r--r--components/jetpack/diffpatcher/History.md14
-rw-r--r--components/jetpack/diffpatcher/License.md18
-rw-r--r--components/jetpack/diffpatcher/Readme.md70
-rw-r--r--components/jetpack/diffpatcher/diff.js45
-rw-r--r--components/jetpack/diffpatcher/index.js5
-rw-r--r--components/jetpack/diffpatcher/package.json54
-rw-r--r--components/jetpack/diffpatcher/patch.js21
-rw-r--r--components/jetpack/diffpatcher/rebase.js36
-rw-r--r--components/jetpack/diffpatcher/test/common.js3
-rw-r--r--components/jetpack/diffpatcher/test/diff.js59
-rw-r--r--components/jetpack/diffpatcher/test/index.js14
-rw-r--r--components/jetpack/diffpatcher/test/patch.js83
-rw-r--r--components/jetpack/diffpatcher/test/tap.js3
-rw-r--r--components/jetpack/framescript/FrameScriptManager.jsm27
-rw-r--r--components/jetpack/framescript/content.jsm94
-rw-r--r--components/jetpack/framescript/context-menu.js215
-rw-r--r--components/jetpack/framescript/manager.js26
-rw-r--r--components/jetpack/framescript/util.js25
-rw-r--r--components/jetpack/index.js3
-rw-r--r--components/jetpack/jetpack-id/index.js53
-rw-r--r--components/jetpack/jetpack-id/package.json28
-rw-r--r--components/jetpack/method/.travis.yml5
-rw-r--r--components/jetpack/method/History.md55
-rw-r--r--components/jetpack/method/License.md18
-rw-r--r--components/jetpack/method/Readme.md117
-rw-r--r--components/jetpack/method/core.js225
-rw-r--r--components/jetpack/method/package.json41
-rw-r--r--components/jetpack/method/test/browser.js20
-rw-r--r--components/jetpack/method/test/common.js272
-rw-r--r--components/jetpack/modules/system/Startup.js58
-rw-r--r--components/jetpack/modules/system/moz.build8
-rw-r--r--components/jetpack/moz.build486
-rw-r--r--components/jetpack/mozilla-toolkit-versioning/index.js112
-rw-r--r--components/jetpack/mozilla-toolkit-versioning/lib/utils.js15
-rw-r--r--components/jetpack/mozilla-toolkit-versioning/package.json21
-rw-r--r--components/jetpack/node/os.js90
-rw-r--r--components/jetpack/sdk/addon/bootstrap.js179
-rw-r--r--components/jetpack/sdk/addon/events.js56
-rw-r--r--components/jetpack/sdk/addon/host.js12
-rw-r--r--components/jetpack/sdk/addon/installer.js121
-rw-r--r--components/jetpack/sdk/addon/manager.js18
-rw-r--r--components/jetpack/sdk/addon/runner.js180
-rw-r--r--components/jetpack/sdk/addon/window.js66
-rw-r--r--components/jetpack/sdk/base64.js47
-rw-r--r--components/jetpack/sdk/browser/events.js20
-rw-r--r--components/jetpack/sdk/clipboard.js338
-rw-r--r--components/jetpack/sdk/console/plain-text.js78
-rw-r--r--components/jetpack/sdk/console/traceback.js86
-rw-r--r--components/jetpack/sdk/content/content-worker.js305
-rw-r--r--components/jetpack/sdk/content/content.js17
-rw-r--r--components/jetpack/sdk/content/context-menu.js408
-rw-r--r--components/jetpack/sdk/content/events.js57
-rw-r--r--components/jetpack/sdk/content/l10n-html.js133
-rw-r--r--components/jetpack/sdk/content/loader.js74
-rw-r--r--components/jetpack/sdk/content/mod.js68
-rw-r--r--components/jetpack/sdk/content/page-mod.js236
-rw-r--r--components/jetpack/sdk/content/page-worker.js154
-rw-r--r--components/jetpack/sdk/content/sandbox.js426
-rw-r--r--components/jetpack/sdk/content/sandbox/events.js12
-rw-r--r--components/jetpack/sdk/content/tab-events.js58
-rw-r--r--components/jetpack/sdk/content/thumbnail.js51
-rw-r--r--components/jetpack/sdk/content/utils.js105
-rw-r--r--components/jetpack/sdk/content/worker-child.js158
-rw-r--r--components/jetpack/sdk/content/worker.js180
-rw-r--r--components/jetpack/sdk/context-menu.js1189
-rw-r--r--components/jetpack/sdk/context-menu/context.js147
-rw-r--r--components/jetpack/sdk/context-menu/core.js384
-rw-r--r--components/jetpack/sdk/context-menu/readers.js112
-rw-r--r--components/jetpack/sdk/context-menu@2.js32
-rw-r--r--components/jetpack/sdk/core/disposable.js186
-rw-r--r--components/jetpack/sdk/core/heritage.js184
-rw-r--r--components/jetpack/sdk/core/namespace.js43
-rw-r--r--components/jetpack/sdk/core/observer.js89
-rw-r--r--components/jetpack/sdk/core/promise.js118
-rw-r--r--components/jetpack/sdk/core/reference.js29
-rw-r--r--components/jetpack/sdk/deprecated/api-utils.js197
-rw-r--r--components/jetpack/sdk/deprecated/events/assembler.js54
-rw-r--r--components/jetpack/sdk/deprecated/sync-worker.js288
-rw-r--r--components/jetpack/sdk/deprecated/unit-test-finder.js199
-rw-r--r--components/jetpack/sdk/deprecated/unit-test.js584
-rw-r--r--components/jetpack/sdk/deprecated/window-utils.js193
-rw-r--r--components/jetpack/sdk/dom/events-shimmed.js18
-rw-r--r--components/jetpack/sdk/dom/events.js192
-rw-r--r--components/jetpack/sdk/dom/events/keys.js63
-rw-r--r--components/jetpack/sdk/event/chrome.js65
-rw-r--r--components/jetpack/sdk/event/core.js193
-rw-r--r--components/jetpack/sdk/event/dom.js78
-rw-r--r--components/jetpack/sdk/event/target.js74
-rw-r--r--components/jetpack/sdk/event/utils.js328
-rw-r--r--components/jetpack/sdk/frame/hidden-frame.js115
-rw-r--r--components/jetpack/sdk/frame/utils.js94
-rw-r--r--components/jetpack/sdk/fs/path.js500
-rw-r--r--components/jetpack/sdk/hotkeys.js40
-rw-r--r--components/jetpack/sdk/indexed-db.js79
-rw-r--r--components/jetpack/sdk/input/browser.js73
-rw-r--r--components/jetpack/sdk/input/customizable-ui.js28
-rw-r--r--components/jetpack/sdk/input/frame.js85
-rw-r--r--components/jetpack/sdk/input/system.js113
-rw-r--r--components/jetpack/sdk/io/buffer.js351
-rw-r--r--components/jetpack/sdk/io/byte-streams.js104
-rw-r--r--components/jetpack/sdk/io/file.js196
-rw-r--r--components/jetpack/sdk/io/fs.js984
-rw-r--r--components/jetpack/sdk/io/stream.js440
-rw-r--r--components/jetpack/sdk/io/text-streams.js235
-rw-r--r--components/jetpack/sdk/keyboard/hotkeys.js110
-rw-r--r--components/jetpack/sdk/keyboard/observer.js58
-rw-r--r--components/jetpack/sdk/keyboard/utils.js189
-rw-r--r--components/jetpack/sdk/l10n.js91
-rw-r--r--components/jetpack/sdk/l10n/core.js9
-rw-r--r--components/jetpack/sdk/l10n/html.js32
-rw-r--r--components/jetpack/sdk/l10n/json/core.js36
-rw-r--r--components/jetpack/sdk/l10n/loader.js70
-rw-r--r--components/jetpack/sdk/l10n/locale.js127
-rw-r--r--components/jetpack/sdk/l10n/plural-rules.js407
-rw-r--r--components/jetpack/sdk/l10n/prefs.js51
-rw-r--r--components/jetpack/sdk/l10n/properties/core.js87
-rw-r--r--components/jetpack/sdk/lang/functional.js47
-rw-r--r--components/jetpack/sdk/lang/functional/concurrent.js110
-rw-r--r--components/jetpack/sdk/lang/functional/core.js290
-rw-r--r--components/jetpack/sdk/lang/functional/helpers.js29
-rw-r--r--components/jetpack/sdk/lang/type.js388
-rw-r--r--components/jetpack/sdk/lang/weak-set.js75
-rw-r--r--components/jetpack/sdk/loader/cuddlefish.js102
-rw-r--r--components/jetpack/sdk/loader/sandbox.js74
-rw-r--r--components/jetpack/sdk/messaging.js12
-rw-r--r--components/jetpack/sdk/model/core.js23
-rw-r--r--components/jetpack/sdk/net/url.js94
-rw-r--r--components/jetpack/sdk/net/xhr.js36
-rw-r--r--components/jetpack/sdk/notifications.js112
-rw-r--r--components/jetpack/sdk/output/system.js71
-rw-r--r--components/jetpack/sdk/page-mod.js190
-rw-r--r--components/jetpack/sdk/page-mod/match-pattern.js10
-rw-r--r--components/jetpack/sdk/page-worker.js194
-rw-r--r--components/jetpack/sdk/panel.js428
-rw-r--r--components/jetpack/sdk/panel/events.js27
-rw-r--r--components/jetpack/sdk/panel/utils.js451
-rw-r--r--components/jetpack/sdk/passwords.js61
-rw-r--r--components/jetpack/sdk/passwords/utils.js107
-rw-r--r--components/jetpack/sdk/places/bookmarks.js396
-rw-r--r--components/jetpack/sdk/places/contract.js73
-rw-r--r--components/jetpack/sdk/places/events.js129
-rw-r--r--components/jetpack/sdk/places/favicon.js50
-rw-r--r--components/jetpack/sdk/places/history.js66
-rw-r--r--components/jetpack/sdk/places/host/host-bookmarks.js239
-rw-r--r--components/jetpack/sdk/places/host/host-query.js180
-rw-r--r--components/jetpack/sdk/places/host/host-tags.js93
-rw-r--r--components/jetpack/sdk/places/utils.js269
-rw-r--r--components/jetpack/sdk/platform/xpcom.js241
-rw-r--r--components/jetpack/sdk/preferences/event-target.js61
-rw-r--r--components/jetpack/sdk/preferences/native-options.js193
-rw-r--r--components/jetpack/sdk/preferences/service.js137
-rw-r--r--components/jetpack/sdk/preferences/utils.js42
-rw-r--r--components/jetpack/sdk/private-browsing.js12
-rw-r--r--components/jetpack/sdk/private-browsing/utils.js54
-rw-r--r--components/jetpack/sdk/querystring.js121
-rw-r--r--components/jetpack/sdk/remote/child.js284
-rw-r--r--components/jetpack/sdk/remote/core.js8
-rw-r--r--components/jetpack/sdk/remote/parent.js338
-rw-r--r--components/jetpack/sdk/remote/utils.js39
-rw-r--r--components/jetpack/sdk/request.js248
-rw-r--r--components/jetpack/sdk/selection.js471
-rw-r--r--components/jetpack/sdk/self.js61
-rw-r--r--components/jetpack/sdk/simple-prefs.js26
-rw-r--r--components/jetpack/sdk/simple-storage.js235
-rw-r--r--components/jetpack/sdk/stylesheet/style.js71
-rw-r--r--components/jetpack/sdk/stylesheet/utils.js75
-rw-r--r--components/jetpack/sdk/system.js172
-rw-r--r--components/jetpack/sdk/system/child_process.js332
-rw-r--r--components/jetpack/sdk/system/child_process/subprocess.js186
-rw-r--r--components/jetpack/sdk/system/environment.js33
-rw-r--r--components/jetpack/sdk/system/events-shimmed.js16
-rw-r--r--components/jetpack/sdk/system/events.js181
-rw-r--r--components/jetpack/sdk/system/globals.js46
-rw-r--r--components/jetpack/sdk/system/process.js62
-rw-r--r--components/jetpack/sdk/system/runtime.js28
-rw-r--r--components/jetpack/sdk/system/unload.js104
-rw-r--r--components/jetpack/sdk/system/xul-app.js12
-rw-r--r--components/jetpack/sdk/system/xul-app.jsm244
-rw-r--r--components/jetpack/sdk/tab/events.js74
-rw-r--r--components/jetpack/sdk/tabs.js17
-rw-r--r--components/jetpack/sdk/tabs/common.js34
-rw-r--r--components/jetpack/sdk/tabs/events.js39
-rw-r--r--components/jetpack/sdk/tabs/helpers.js22
-rw-r--r--components/jetpack/sdk/tabs/namespace.js10
-rw-r--r--components/jetpack/sdk/tabs/observer.js113
-rw-r--r--components/jetpack/sdk/tabs/tab-fennec.js249
-rw-r--r--components/jetpack/sdk/tabs/tab-firefox.js353
-rw-r--r--components/jetpack/sdk/tabs/tab.js24
-rw-r--r--components/jetpack/sdk/tabs/tabs-firefox.js135
-rw-r--r--components/jetpack/sdk/tabs/utils.js370
-rw-r--r--components/jetpack/sdk/tabs/worker.js17
-rw-r--r--components/jetpack/sdk/test.js114
-rw-r--r--components/jetpack/sdk/test/assert.js366
-rw-r--r--components/jetpack/sdk/test/harness.js645
-rw-r--r--components/jetpack/sdk/test/httpd.js6
-rw-r--r--components/jetpack/sdk/test/loader.js123
-rw-r--r--components/jetpack/sdk/test/memory.js11
-rw-r--r--components/jetpack/sdk/test/options.js23
-rw-r--r--components/jetpack/sdk/test/runner.js131
-rw-r--r--components/jetpack/sdk/test/utils.js199
-rw-r--r--components/jetpack/sdk/timers.js105
-rw-r--r--components/jetpack/sdk/ui.js20
-rw-r--r--components/jetpack/sdk/ui/button/action.js115
-rw-r--r--components/jetpack/sdk/ui/button/contract.js73
-rw-r--r--components/jetpack/sdk/ui/button/toggle.js128
-rw-r--r--components/jetpack/sdk/ui/button/view.js286
-rw-r--r--components/jetpack/sdk/ui/button/view/events.js19
-rw-r--r--components/jetpack/sdk/ui/buttons.js217
-rw-r--r--components/jetpack/sdk/ui/component.js182
-rw-r--r--components/jetpack/sdk/ui/frame.js16
-rw-r--r--components/jetpack/sdk/ui/frame/model.js154
-rw-r--r--components/jetpack/sdk/ui/frame/view.html18
-rw-r--r--components/jetpack/sdk/ui/frame/view.js150
-rw-r--r--components/jetpack/sdk/ui/id.js27
-rw-r--r--components/jetpack/sdk/ui/sidebar.js311
-rw-r--r--components/jetpack/sdk/ui/sidebar/actions.js10
-rw-r--r--components/jetpack/sdk/ui/sidebar/contract.js27
-rw-r--r--components/jetpack/sdk/ui/sidebar/namespace.js15
-rw-r--r--components/jetpack/sdk/ui/sidebar/utils.js8
-rw-r--r--components/jetpack/sdk/ui/sidebar/view.js214
-rw-r--r--components/jetpack/sdk/ui/state.js240
-rw-r--r--components/jetpack/sdk/ui/state/events.js19
-rw-r--r--components/jetpack/sdk/ui/toolbar.js16
-rw-r--r--components/jetpack/sdk/ui/toolbar/model.js151
-rw-r--r--components/jetpack/sdk/ui/toolbar/view.js248
-rw-r--r--components/jetpack/sdk/uri/resource.js37
-rw-r--r--components/jetpack/sdk/url.js349
-rw-r--r--components/jetpack/sdk/url/utils.js29
-rw-r--r--components/jetpack/sdk/util/array.js123
-rw-r--r--components/jetpack/sdk/util/collection.js115
-rw-r--r--components/jetpack/sdk/util/contract.js55
-rw-r--r--components/jetpack/sdk/util/deprecate.js40
-rw-r--r--components/jetpack/sdk/util/dispatcher.js54
-rw-r--r--components/jetpack/sdk/util/list.js90
-rw-r--r--components/jetpack/sdk/util/match-pattern.js113
-rw-r--r--components/jetpack/sdk/util/object.js104
-rw-r--r--components/jetpack/sdk/util/rules.js53
-rw-r--r--components/jetpack/sdk/util/sequence.js593
-rw-r--r--components/jetpack/sdk/util/uuid.js19
-rw-r--r--components/jetpack/sdk/view/core.js26
-rw-r--r--components/jetpack/sdk/window/browser.js54
-rw-r--r--components/jetpack/sdk/window/events.js68
-rw-r--r--components/jetpack/sdk/window/helpers.js81
-rw-r--r--components/jetpack/sdk/window/namespace.js6
-rw-r--r--components/jetpack/sdk/window/utils.js460
-rw-r--r--components/jetpack/sdk/windows.js32
-rw-r--r--components/jetpack/sdk/windows/fennec.js83
-rw-r--r--components/jetpack/sdk/windows/firefox.js224
-rw-r--r--components/jetpack/sdk/windows/observer.js53
-rw-r--r--components/jetpack/sdk/windows/tabs-fennec.js172
-rw-r--r--components/jetpack/sdk/worker/utils.js19
-rw-r--r--components/jetpack/sdk/zip/utils.js16
-rw-r--r--components/jetpack/test.js11
-rw-r--r--components/jetpack/toolkit/loader.js1147
-rw-r--r--components/jetpack/toolkit/require.js91
-rw-r--r--components/jsdebugger/moz.build13
-rw-r--r--components/jsdebugger/public/IJSDebugger.idl20
-rw-r--r--components/jsdebugger/src/JSDebugger.cpp86
-rw-r--r--components/jsdebugger/src/JSDebugger.h30
-rw-r--r--components/jsdebugger/src/jsdebugger.jsm85
-rw-r--r--components/jsdownloads/moz.build6
-rw-r--r--components/jsdownloads/public/moz.build9
-rw-r--r--components/jsdownloads/public/mozIDownloadPlatform.idl57
-rw-r--r--components/jsdownloads/src/DownloadCore.jsm2799
-rw-r--r--components/jsdownloads/src/DownloadImport.jsm192
-rw-r--r--components/jsdownloads/src/DownloadIntegration.jsm1189
-rw-r--r--components/jsdownloads/src/DownloadLegacy.js308
-rw-r--r--components/jsdownloads/src/DownloadList.jsm558
-rw-r--r--components/jsdownloads/src/DownloadPlatform.cpp191
-rw-r--r--components/jsdownloads/src/DownloadPlatform.h34
-rw-r--r--components/jsdownloads/src/DownloadStore.jsm202
-rw-r--r--components/jsdownloads/src/DownloadUIHelper.jsm212
-rw-r--r--components/jsdownloads/src/Downloads.jsm298
-rw-r--r--components/jsdownloads/src/Downloads.manifest2
-rw-r--r--components/jsdownloads/src/moz.build30
-rw-r--r--components/jsinspector/moz.build11
-rw-r--r--components/jsinspector/public/nsIJSInspector.idl75
-rw-r--r--components/jsinspector/src/nsJSInspector.cpp146
-rw-r--r--components/jsinspector/src/nsJSInspector.h39
-rw-r--r--components/lz4/lz4.cpp73
-rw-r--r--components/lz4/lz4.js156
-rw-r--r--components/lz4/lz4_internal.js68
-rw-r--r--components/lz4/moz.build13
-rw-r--r--components/mediasniffer/moz.build14
-rw-r--r--components/mediasniffer/mp3sniff.c156
-rw-r--r--components/mediasniffer/mp3sniff.h15
-rw-r--r--components/mediasniffer/nsMediaSniffer.cpp200
-rw-r--r--components/mediasniffer/nsMediaSniffer.h47
-rw-r--r--components/mediasniffer/nsMediaSnifferModule.cpp37
-rw-r--r--components/microformats/microformat-shiv.js4523
-rw-r--r--components/microformats/moz.build6
-rw-r--r--components/microformats/update/package.json21
-rw-r--r--components/microformats/update/readme.txt33
-rw-r--r--components/microformats/update/update.js266
-rw-r--r--components/mork/build/moz.build23
-rw-r--r--components/mork/build/nsIMdbFactoryFactory.h30
-rw-r--r--components/mork/build/nsMorkCID.h21
-rw-r--r--components/mork/build/nsMorkFactory.cpp56
-rw-r--r--components/mork/moz.build9
-rw-r--r--components/mork/public/mdb.h2512
-rw-r--r--components/mork/public/moz.build8
-rw-r--r--components/mork/src/mork.h247
-rw-r--r--components/mork/src/morkArray.cpp297
-rw-r--r--components/mork/src/morkArray.h98
-rw-r--r--components/mork/src/morkAtom.cpp523
-rw-r--r--components/mork/src/morkAtom.h365
-rw-r--r--components/mork/src/morkAtomMap.cpp427
-rw-r--r--components/mork/src/morkAtomMap.h365
-rw-r--r--components/mork/src/morkAtomSpace.cpp270
-rw-r--r--components/mork/src/morkAtomSpace.h219
-rw-r--r--components/mork/src/morkBead.cpp425
-rw-r--r--components/mork/src/morkBead.h245
-rw-r--r--components/mork/src/morkBlob.cpp110
-rw-r--r--components/mork/src/morkBlob.h141
-rw-r--r--components/mork/src/morkBuilder.cpp1031
-rw-r--r--components/mork/src/morkBuilder.h303
-rw-r--r--components/mork/src/morkCell.cpp114
-rw-r--r--components/mork/src/morkCell.h89
-rw-r--r--components/mork/src/morkCellObject.cpp530
-rw-r--r--components/mork/src/morkCellObject.h173
-rw-r--r--components/mork/src/morkCh.cpp233
-rw-r--r--components/mork/src/morkCh.h126
-rw-r--r--components/mork/src/morkConfig.cpp201
-rw-r--r--components/mork/src/morkConfig.h148
-rw-r--r--components/mork/src/morkCursor.cpp201
-rw-r--r--components/mork/src/morkCursor.h127
-rw-r--r--components/mork/src/morkDeque.cpp288
-rw-r--r--components/mork/src/morkDeque.h239
-rw-r--r--components/mork/src/morkEnv.cpp615
-rw-r--r--components/mork/src/morkEnv.h218
-rw-r--r--components/mork/src/morkFactory.cpp610
-rw-r--r--components/mork/src/morkFactory.h203
-rw-r--r--components/mork/src/morkFile.cpp874
-rw-r--r--components/mork/src/morkFile.h355
-rw-r--r--components/mork/src/morkHandle.cpp423
-rw-r--r--components/mork/src/morkHandle.h176
-rw-r--r--components/mork/src/morkIntMap.cpp238
-rw-r--r--components/mork/src/morkIntMap.h145
-rw-r--r--components/mork/src/morkMap.cpp953
-rw-r--r--components/mork/src/morkMap.h394
-rw-r--r--components/mork/src/morkNode.cpp592
-rw-r--r--components/mork/src/morkNode.h292
-rw-r--r--components/mork/src/morkNodeMap.cpp155
-rw-r--r--components/mork/src/morkNodeMap.h98
-rw-r--r--components/mork/src/morkObject.cpp206
-rw-r--r--components/mork/src/morkObject.h143
-rw-r--r--components/mork/src/morkParser.cpp1568
-rw-r--r--components/mork/src/morkParser.h533
-rw-r--r--components/mork/src/morkPool.cpp552
-rw-r--r--components/mork/src/morkPool.h152
-rw-r--r--components/mork/src/morkPortTableCursor.cpp430
-rw-r--r--components/mork/src/morkPortTableCursor.h141
-rw-r--r--components/mork/src/morkProbeMap.cpp1234
-rw-r--r--components/mork/src/morkProbeMap.h431
-rw-r--r--components/mork/src/morkQuickSort.cpp187
-rw-r--r--components/mork/src/morkQuickSort.h25
-rw-r--r--components/mork/src/morkRow.cpp939
-rw-r--r--components/mork/src/morkRow.h226
-rw-r--r--components/mork/src/morkRowCellCursor.cpp253
-rw-r--r--components/mork/src/morkRowCellCursor.h116
-rw-r--r--components/mork/src/morkRowMap.cpp297
-rw-r--r--components/mork/src/morkRowMap.h211
-rw-r--r--components/mork/src/morkRowObject.cpp622
-rw-r--r--components/mork/src/morkRowObject.h200
-rw-r--r--components/mork/src/morkRowSpace.cpp632
-rw-r--r--components/mork/src/morkRowSpace.h233
-rw-r--r--components/mork/src/morkSearchRowCursor.cpp169
-rw-r--r--components/mork/src/morkSearchRowCursor.h105
-rw-r--r--components/mork/src/morkSink.cpp292
-rw-r--r--components/mork/src/morkSink.h161
-rw-r--r--components/mork/src/morkSpace.cpp152
-rw-r--r--components/mork/src/morkSpace.h110
-rw-r--r--components/mork/src/morkStore.cpp2290
-rw-r--r--components/mork/src/morkStore.h768
-rw-r--r--components/mork/src/morkStream.cpp859
-rw-r--r--components/mork/src/morkStream.h251
-rw-r--r--components/mork/src/morkTable.cpp1610
-rw-r--r--components/mork/src/morkTable.h729
-rw-r--r--components/mork/src/morkTableRowCursor.cpp493
-rw-r--r--components/mork/src/morkTableRowCursor.h146
-rw-r--r--components/mork/src/morkThumb.cpp523
-rw-r--r--components/mork/src/morkThumb.h180
-rw-r--r--components/mork/src/morkUniqRowCursor.h94
-rw-r--r--components/mork/src/morkWriter.cpp2206
-rw-r--r--components/mork/src/morkWriter.h343
-rw-r--r--components/mork/src/morkYarn.cpp75
-rw-r--r--components/mork/src/morkYarn.h75
-rw-r--r--components/mork/src/morkZone.cpp527
-rw-r--r--components/mork/src/morkZone.h321
-rw-r--r--components/mork/src/moz.build54
-rw-r--r--components/mork/src/orkinHeap.cpp84
-rw-r--r--components/mork/src/orkinHeap.h52
-rw-r--r--components/moz.build123
-rw-r--r--components/mozintl/MozIntl.cpp100
-rw-r--r--components/mozintl/MozIntl.h22
-rw-r--r--components/mozintl/moz.build12
-rw-r--r--components/mozintl/mozIMozIntl.idl13
-rw-r--r--components/mozprotocol/moz.build9
-rw-r--r--components/mozprotocol/mozProtocolHandler.js48
-rw-r--r--components/mozprotocol/mozProtocolHandler.manifest2
-rw-r--r--components/narrate/.eslintrc.js94
-rw-r--r--components/narrate/NarrateControls.jsm313
-rw-r--r--components/narrate/Narrator.jsm440
-rw-r--r--components/narrate/VoiceSelect.jsm300
-rw-r--r--components/narrate/moz.build10
-rw-r--r--components/nsDefaultCLH.js119
-rw-r--r--components/nsDefaultCLH.manifest3
-rw-r--r--components/osfile/NativeOSFileInternals.cpp915
-rw-r--r--components/osfile/NativeOSFileInternals.h25
-rw-r--r--components/osfile/modules/moz.build21
-rw-r--r--components/osfile/modules/osfile_async_front.jsm1476
-rw-r--r--components/osfile/modules/osfile_async_worker.js406
-rw-r--r--components/osfile/modules/osfile_native.jsm70
-rw-r--r--components/osfile/modules/osfile_shared_allthreads.jsm1315
-rw-r--r--components/osfile/modules/osfile_shared_front.jsm567
-rw-r--r--components/osfile/modules/osfile_unix_allthreads.jsm375
-rw-r--r--components/osfile/modules/osfile_unix_back.jsm736
-rw-r--r--components/osfile/modules/osfile_unix_front.jsm1188
-rw-r--r--components/osfile/modules/osfile_win_allthreads.jsm425
-rw-r--r--components/osfile/modules/osfile_win_back.jsm437
-rw-r--r--components/osfile/modules/osfile_win_front.jsm1266
-rw-r--r--components/osfile/modules/ospath.jsm45
-rw-r--r--components/osfile/modules/ospath_unix.jsm202
-rw-r--r--components/osfile/modules/ospath_win.jsm373
-rw-r--r--components/osfile/moz.build18
-rw-r--r--components/osfile/nsINativeOSFileInternals.idl92
-rw-r--r--components/osfile/osfile.jsm32
-rw-r--r--components/parentalcontrols/moz.build16
-rw-r--r--components/parentalcontrols/nsIParentalControlsService.idl103
-rw-r--r--components/parentalcontrols/nsParentalControlsService.h44
-rw-r--r--components/parentalcontrols/nsParentalControlsServiceDefault.cpp73
-rw-r--r--components/parentalcontrols/nsParentalControlsServiceWin.cpp347
-rw-r--r--components/passwordmgr/.eslintrc.js36
-rw-r--r--components/passwordmgr/content/passwordManager.js725
-rw-r--r--components/passwordmgr/content/passwordManager.xul131
-rw-r--r--components/passwordmgr/content/recipes.json31
-rw-r--r--components/passwordmgr/jar.mn9
-rw-r--r--components/passwordmgr/locale/passwordManager.dtd44
-rw-r--r--components/passwordmgr/locale/passwordmgr.properties81
-rw-r--r--components/passwordmgr/moz.build46
-rw-r--r--components/passwordmgr/passwordmgr.manifest12
-rw-r--r--components/passwordmgr/public/nsILoginInfo.idl120
-rw-r--r--components/passwordmgr/public/nsILoginManager.idl262
-rw-r--r--components/passwordmgr/public/nsILoginManagerCrypto.idl67
-rw-r--r--components/passwordmgr/public/nsILoginManagerPrompter.idl94
-rw-r--r--components/passwordmgr/public/nsILoginManagerStorage.idl211
-rw-r--r--components/passwordmgr/public/nsILoginMetaInfo.idl55
-rw-r--r--components/passwordmgr/src/InsecurePasswordUtils.jsm149
-rw-r--r--components/passwordmgr/src/LoginHelper.jsm725
-rw-r--r--components/passwordmgr/src/LoginImport.jsm172
-rw-r--r--components/passwordmgr/src/LoginManagerContent.jsm1614
-rw-r--r--components/passwordmgr/src/LoginManagerContextMenu.jsm199
-rw-r--r--components/passwordmgr/src/LoginManagerParent.jsm516
-rw-r--r--components/passwordmgr/src/LoginRecipes.jsm260
-rw-r--r--components/passwordmgr/src/LoginStore.jsm136
-rw-r--r--components/passwordmgr/src/OSCrypto.jsm21
-rw-r--r--components/passwordmgr/src/OSCrypto_win.js245
-rw-r--r--components/passwordmgr/src/crypto-SDR.js207
-rw-r--r--components/passwordmgr/src/nsLoginInfo.js93
-rw-r--r--components/passwordmgr/src/nsLoginManager.js461
-rw-r--r--components/passwordmgr/src/nsLoginManagerPrompter.js1731
-rw-r--r--components/passwordmgr/src/storage-json.js514
-rw-r--r--components/perf/.eslintrc.js7
-rw-r--r--components/perf/PerfMeasurement.cpp120
-rw-r--r--components/perf/PerfMeasurement.h30
-rw-r--r--components/perf/PerfMeasurement.jsm19
-rw-r--r--components/perf/moz.build18
-rw-r--r--components/perfmonitoring/AddonWatcher.jsm227
-rw-r--r--components/perfmonitoring/PerformanceStats-content.js144
-rw-r--r--components/perfmonitoring/PerformanceStats.jsm1000
-rw-r--r--components/perfmonitoring/PerformanceWatcher-content.js54
-rw-r--r--components/perfmonitoring/PerformanceWatcher.jsm367
-rw-r--r--components/perfmonitoring/README.md120
-rw-r--r--components/perfmonitoring/moz.build24
-rw-r--r--components/perfmonitoring/nsIPerformanceStats.idl332
-rw-r--r--components/perfmonitoring/nsPerformanceStats.cpp1565
-rw-r--r--components/perfmonitoring/nsPerformanceStats.h810
-rw-r--r--components/permissions/content/cookieAcceptDialog.js203
-rw-r--r--components/permissions/content/cookieAcceptDialog.xul118
-rw-r--r--components/permissions/jar.mn8
-rw-r--r--components/permissions/locale/cookieAcceptDialog.dtd21
-rw-r--r--components/permissions/locale/cookieAcceptDialog.properties21
-rw-r--r--components/permissions/moz.build31
-rw-r--r--components/permissions/public/nsICookieAcceptDialog.idl21
-rw-r--r--components/permissions/public/nsICookiePermission.idl109
-rw-r--r--components/permissions/public/nsICookiePromptService.idl45
-rw-r--r--components/permissions/public/nsIPermission.idl77
-rw-r--r--components/permissions/public/nsIPermissionManager.idl271
-rw-r--r--components/permissions/src/nsContentBlocker.cpp385
-rw-r--r--components/permissions/src/nsContentBlocker.h53
-rw-r--r--components/permissions/src/nsCookiePermission.cpp272
-rw-r--r--components/permissions/src/nsCookiePermission.h48
-rw-r--r--components/permissions/src/nsCookiePromptService.cpp101
-rw-r--r--components/permissions/src/nsCookiePromptService.h29
-rw-r--r--components/permissions/src/nsPermission.cpp201
-rw-r--r--components/permissions/src/nsPermission.h43
-rw-r--r--components/permissions/src/nsPermissionFactory.cpp60
-rw-r--r--components/permissions/src/nsPermissionManager.cpp2862
-rw-r--r--components/permissions/src/nsPermissionManager.h295
-rw-r--r--components/permissions/src/nsPopupWindowManager.cpp111
-rw-r--r--components/permissions/src/nsPopupWindowManager.h39
-rw-r--r--components/places/locale/places.properties33
-rw-r--r--components/places/moz.build89
-rw-r--r--components/places/nsPlacesAutoComplete.manifest6
-rw-r--r--components/places/public/mozIAsyncFavicons.idl174
-rw-r--r--components/places/public/mozIAsyncHistory.idl188
-rw-r--r--components/places/public/mozIAsyncLivemarks.idl190
-rw-r--r--components/places/public/mozIColorAnalyzer.idl52
-rw-r--r--components/places/public/mozIPlacesAutoComplete.idl134
-rw-r--r--components/places/public/mozIPlacesPendingOperation.idl14
-rw-r--r--components/places/public/nsIAnnotationService.idl422
-rw-r--r--components/places/public/nsIBrowserHistory.idl70
-rw-r--r--components/places/public/nsIDownloadHistory.idl53
-rw-r--r--components/places/public/nsIFaviconService.idl145
-rw-r--r--components/places/public/nsINavBookmarksService.idl697
-rw-r--r--components/places/public/nsINavHistoryService.idl1451
-rw-r--r--components/places/public/nsITaggingService.idl95
-rw-r--r--components/places/public/nsPIPlacesDatabase.idl52
-rw-r--r--components/places/src/BookmarkHTMLUtils.jsm1187
-rw-r--r--components/places/src/BookmarkJSONUtils.jsm581
-rw-r--r--components/places/src/Bookmarks.jsm1536
-rw-r--r--components/places/src/ClusterLib.js248
-rw-r--r--components/places/src/ColorAnalyzer.js90
-rw-r--r--components/places/src/ColorAnalyzer_worker.js392
-rw-r--r--components/places/src/ColorConversion.js64
-rw-r--r--components/places/src/Database.cpp2330
-rw-r--r--components/places/src/Database.h331
-rw-r--r--components/places/src/ExtensionSearchHandler.jsm292
-rw-r--r--components/places/src/FaviconHelpers.cpp906
-rw-r--r--components/places/src/FaviconHelpers.h273
-rw-r--r--components/places/src/Helpers.cpp386
-rw-r--r--components/places/src/Helpers.h275
-rw-r--r--components/places/src/History.cpp2978
-rw-r--r--components/places/src/History.h223
-rw-r--r--components/places/src/History.jsm1049
-rw-r--r--components/places/src/PageIconProtocolHandler.js128
-rw-r--r--components/places/src/PlaceInfo.cpp137
-rw-r--r--components/places/src/PlaceInfo.h50
-rw-r--r--components/places/src/PlacesBackups.jsm542
-rw-r--r--components/places/src/PlacesCategoriesStarter.js98
-rw-r--r--components/places/src/PlacesDBUtils.jsm952
-rw-r--r--components/places/src/PlacesRemoteTabsAutocompleteProvider.jsm144
-rw-r--r--components/places/src/PlacesSearchAutocompleteProvider.jsm295
-rw-r--r--components/places/src/PlacesSyncUtils.jsm1155
-rw-r--r--components/places/src/PlacesTransactions.jsm1645
-rw-r--r--components/places/src/PlacesUtils.jsm3870
-rw-r--r--components/places/src/SQLFunctions.cpp941
-rw-r--r--components/places/src/SQLFunctions.h394
-rw-r--r--components/places/src/Shutdown.cpp233
-rw-r--r--components/places/src/Shutdown.h171
-rw-r--r--components/places/src/UnifiedComplete.js2098
-rw-r--r--components/places/src/VisitInfo.cpp69
-rw-r--r--components/places/src/VisitInfo.h37
-rw-r--r--components/places/src/nsAnnoProtocolHandler.cpp367
-rw-r--r--components/places/src/nsAnnoProtocolHandler.h54
-rw-r--r--components/places/src/nsAnnotationService.cpp1990
-rw-r--r--components/places/src/nsAnnotationService.h161
-rw-r--r--components/places/src/nsFaviconService.cpp715
-rw-r--r--components/places/src/nsFaviconService.h147
-rw-r--r--components/places/src/nsLivemarkService.js891
-rw-r--r--components/places/src/nsMaybeWeakPtr.h145
-rw-r--r--components/places/src/nsNavBookmarks.cpp2924
-rw-r--r--components/places/src/nsNavBookmarks.h445
-rw-r--r--components/places/src/nsNavHistory.cpp4501
-rw-r--r--components/places/src/nsNavHistory.h655
-rw-r--r--components/places/src/nsNavHistoryQuery.cpp1694
-rw-r--r--components/places/src/nsNavHistoryQuery.h160
-rw-r--r--components/places/src/nsNavHistoryResult.cpp4812
-rw-r--r--components/places/src/nsNavHistoryResult.h782
-rw-r--r--components/places/src/nsPlacesAutoComplete.js1756
-rw-r--r--components/places/src/nsPlacesExpiration.js1069
-rw-r--r--components/places/src/nsPlacesIndexes.h124
-rw-r--r--components/places/src/nsPlacesMacros.h82
-rw-r--r--components/places/src/nsPlacesModule.cpp70
-rw-r--r--components/places/src/nsPlacesTables.h152
-rw-r--r--components/places/src/nsPlacesTriggers.h266
-rw-r--r--components/places/src/nsTaggingService.js713
-rw-r--r--components/places/toolkitplaces.manifest32
-rw-r--r--components/pluginproblem/content/pluginProblem.xml87
-rw-r--r--components/pluginproblem/content/pluginProblemBinding.css31
-rw-r--r--components/pluginproblem/content/pluginProblemContent.css122
-rw-r--r--components/pluginproblem/content/pluginReplaceBinding.css17
-rw-r--r--components/pluginproblem/jar.mn10
-rw-r--r--components/pluginproblem/locale/pluginproblem.dtd42
-rw-r--r--components/pluginproblem/moz.build10
-rw-r--r--components/pluginproblem/pluginGlue.manifest1
-rw-r--r--components/preferences/content/changemp.js243
-rw-r--r--components/preferences/content/changemp.xul68
-rw-r--r--components/preferences/content/fontbuilder.js126
-rw-r--r--components/preferences/content/removemp.js56
-rw-r--r--components/preferences/content/removemp.xul46
-rw-r--r--components/preferences/jar.mn11
-rw-r--r--components/preferences/locale/changemp.dtd13
-rw-r--r--components/preferences/locale/preferences.properties17
-rw-r--r--components/preferences/locale/removemp.dtd10
-rw-r--r--components/preferences/moz.build32
-rw-r--r--components/preferences/public/nsIPrefBranch.idl425
-rw-r--r--components/preferences/public/nsIPrefBranch2.idl16
-rw-r--r--components/preferences/public/nsIPrefBranchInternal.idl17
-rw-r--r--components/preferences/public/nsIPrefLocalizedString.idl64
-rw-r--r--components/preferences/public/nsIPrefService.idl160
-rw-r--r--components/preferences/public/nsIRelativeFilePref.idl69
-rw-r--r--components/preferences/src/Preferences.cpp1988
-rw-r--r--components/preferences/src/Preferences.h408
-rw-r--r--components/preferences/src/nsPrefBranch.cpp943
-rw-r--r--components/preferences/src/nsPrefBranch.h269
-rw-r--r--components/preferences/src/nsPrefsFactory.cpp54
-rw-r--r--components/preferences/src/prefapi.cpp1005
-rw-r--r--components/preferences/src/prefapi.h258
-rw-r--r--components/preferences/src/prefapi_private_data.h40
-rw-r--r--components/preferences/src/prefread.cpp657
-rw-r--r--components/preferences/src/prefread.h118
-rw-r--r--components/printing/content/printPageSetup.js471
-rw-r--r--components/printing/content/printPageSetup.xul234
-rw-r--r--components/printing/content/printPreviewBindings.xml419
-rw-r--r--components/printing/content/printPreviewProgress.js154
-rw-r--r--components/printing/content/printPreviewProgress.xul42
-rw-r--r--components/printing/content/printProgress.js282
-rw-r--r--components/printing/content/printProgress.xul60
-rw-r--r--components/printing/content/printUtils.js697
-rw-r--r--components/printing/content/printdialog.js417
-rw-r--r--components/printing/content/printdialog.xul126
-rw-r--r--components/printing/content/printjoboptions.js394
-rw-r--r--components/printing/content/printjoboptions.xul110
-rw-r--r--components/printing/content/simplifyMode.css22
-rw-r--r--components/printing/jar.mn20
-rw-r--r--components/printing/moz.build37
-rw-r--r--components/printing/public/PPrintProgressDialog.ipdl35
-rw-r--r--components/printing/public/PPrintSettingsDialog.ipdl29
-rw-r--r--components/printing/public/PPrinting.ipdl48
-rw-r--r--components/printing/public/PPrintingTypes.ipdlh126
-rw-r--r--components/printing/src/PrintDataUtils.cpp157
-rw-r--r--components/printing/src/PrintDataUtils.h38
-rw-r--r--components/printing/src/PrintProgressDialogChild.cpp132
-rw-r--r--components/printing/src/PrintProgressDialogChild.h40
-rw-r--r--components/printing/src/PrintProgressDialogParent.cpp113
-rw-r--r--components/printing/src/PrintProgressDialogParent.h64
-rw-r--r--components/printing/src/PrintSettingsDialogChild.cpp39
-rw-r--r--components/printing/src/PrintSettingsDialogChild.h35
-rw-r--r--components/printing/src/PrintSettingsDialogParent.cpp28
-rw-r--r--components/printing/src/PrintSettingsDialogParent.h29
-rw-r--r--components/printing/src/PrintingParent.cpp335
-rw-r--r--components/printing/src/PrintingParent.h108
-rw-r--r--components/printing/src/nsPrintingProxy.cpp269
-rw-r--r--components/printing/src/nsPrintingProxy.h57
-rw-r--r--components/printing/src/unix/moz.build12
-rw-r--r--components/printing/src/unix/nsPrintProgress.cpp267
-rw-r--r--components/printing/src/unix/nsPrintProgress.h46
-rw-r--r--components/printing/src/unix/nsPrintProgressParams.cpp47
-rw-r--r--components/printing/src/unix/nsPrintProgressParams.h28
-rw-r--r--components/printing/src/unix/nsPrintingPromptService.cpp299
-rw-r--r--components/printing/src/unix/nsPrintingPromptService.h58
-rw-r--r--components/printing/src/win/moz.build15
-rw-r--r--components/printing/src/win/nsPrintDialogUtil.cpp854
-rw-r--r--components/printing/src/win/nsPrintDialogUtil.h12
-rw-r--r--components/printing/src/win/nsPrintProgress.cpp293
-rw-r--r--components/printing/src/win/nsPrintProgress.h43
-rw-r--r--components/printing/src/win/nsPrintProgressParams.cpp47
-rw-r--r--components/printing/src/win/nsPrintProgressParams.h27
-rw-r--r--components/printing/src/win/nsPrintingPromptService.cpp341
-rw-r--r--components/printing/src/win/nsPrintingPromptService.h58
-rw-r--r--components/privatebrowsing/PrivateBrowsing.manifest2
-rw-r--r--components/privatebrowsing/PrivateBrowsingTrackingProtectionWhitelist.js68
-rw-r--r--components/privatebrowsing/moz.build15
-rw-r--r--components/privatebrowsing/nsIPrivateBrowsingTrackingProtectionWhitelist.idl46
-rw-r--r--components/processsingleton/ContentProcessSingleton.js117
-rw-r--r--components/processsingleton/MainProcessSingleton.js90
-rw-r--r--components/processsingleton/ProcessSingleton.manifest7
-rw-r--r--components/processsingleton/moz.build10
-rw-r--r--components/profile/content/createProfileWizard.js220
-rw-r--r--components/profile/content/createProfileWizard.xul70
-rw-r--r--components/profile/content/profileSelection.js267
-rw-r--r--components/profile/content/profileSelection.xul70
-rw-r--r--components/profile/jar.mn9
-rw-r--r--components/profile/locale/createProfileWizard.dtd25
-rw-r--r--components/profile/locale/profileSelection.dtd31
-rw-r--r--components/profile/locale/profileSelection.properties51
-rw-r--r--components/profile/mimeTypes.rdf86
-rw-r--r--components/profile/moz.build32
-rw-r--r--components/profile/notifications.txt61
-rw-r--r--components/profile/public/nsIProfileMigrator.idl69
-rw-r--r--components/profile/public/nsIProfileUnlocker.idl21
-rw-r--r--components/profile/public/nsIToolkitProfile.idl89
-rw-r--r--components/profile/public/nsIToolkitProfileService.idl108
-rw-r--r--components/profile/src/ProfileUnlockerWin.cpp277
-rw-r--r--components/profile/src/ProfileUnlockerWin.h59
-rw-r--r--components/profile/src/nsProfileLock.cpp576
-rw-r--r--components/profile/src/nsProfileLock.h95
-rw-r--r--components/profile/src/nsProfileStringTypes.h32
-rw-r--r--components/profile/src/nsToolkitProfileService.cpp1043
-rw-r--r--components/profile/userChrome-example.css58
-rw-r--r--components/profile/userContent-example.css47
-rw-r--r--components/promiseworker/PromiseWorker.jsm390
-rw-r--r--components/promiseworker/moz.build8
-rw-r--r--components/promiseworker/worker/PromiseWorker.js206
-rw-r--r--components/promiseworker/worker/moz.build8
-rw-r--r--components/prompts/content/commonDialog.css22
-rw-r--r--components/prompts/content/commonDialog.js60
-rw-r--r--components/prompts/content/commonDialog.xul87
-rw-r--r--components/prompts/content/selectDialog.js67
-rw-r--r--components/prompts/content/selectDialog.xul22
-rw-r--r--components/prompts/content/tabprompts.css35
-rw-r--r--components/prompts/content/tabprompts.xml339
-rw-r--r--components/prompts/jar.mn12
-rw-r--r--components/prompts/moz.build8
-rw-r--r--components/prompts/src/CommonDialog.jsm308
-rw-r--r--components/prompts/src/SharedPromptUtils.jsm157
-rw-r--r--components/prompts/src/moz.build15
-rw-r--r--components/prompts/src/nsPrompter.js964
-rw-r--r--components/prompts/src/nsPrompter.manifest6
-rw-r--r--components/proxy/moz.build18
-rw-r--r--components/proxy/src/ProxyUtils.cpp182
-rw-r--r--components/proxy/src/ProxyUtils.h21
-rw-r--r--components/proxy/src/nsLibProxySettings.cpp141
-rw-r--r--components/proxy/src/nsUnixSystemProxySettings.cpp543
-rw-r--r--components/proxy/src/nsWindowsSystemProxySettings.cpp304
-rw-r--r--components/rdf/moz.build63
-rw-r--r--components/rdf/public/nsIRDFCompositeDataSource.idl68
-rw-r--r--components/rdf/public/nsIRDFContainer.idl94
-rw-r--r--components/rdf/public/nsIRDFContainerUtils.idl82
-rw-r--r--components/rdf/public/nsIRDFDataSource.idl181
-rw-r--r--components/rdf/public/nsIRDFDelegateFactory.idl40
-rw-r--r--components/rdf/public/nsIRDFInMemoryDataSource.idl14
-rw-r--r--components/rdf/public/nsIRDFInferDataSource.idl28
-rw-r--r--components/rdf/public/nsIRDFLiteral.idl68
-rw-r--r--components/rdf/public/nsIRDFNode.idl15
-rw-r--r--components/rdf/public/nsIRDFObserver.idl94
-rw-r--r--components/rdf/public/nsIRDFPropagatableDataSource.idl27
-rw-r--r--components/rdf/public/nsIRDFPurgeableDataSource.idl19
-rw-r--r--components/rdf/public/nsIRDFRemoteDataSource.idl44
-rw-r--r--components/rdf/public/nsIRDFResource.idl81
-rw-r--r--components/rdf/public/nsIRDFService.idl146
-rw-r--r--components/rdf/public/nsIRDFXMLParser.idl33
-rw-r--r--components/rdf/public/nsIRDFXMLSerializer.idl27
-rw-r--r--components/rdf/public/nsIRDFXMLSink.idl130
-rw-r--r--components/rdf/public/nsIRDFXMLSource.idl20
-rw-r--r--components/rdf/public/rdfIDataSource.idl38
-rw-r--r--components/rdf/public/rdfISerializer.idl30
-rw-r--r--components/rdf/public/rdfITripleVisitor.idl31
-rw-r--r--components/rdf/src/nsCompositeDataSource.cpp1358
-rw-r--r--components/rdf/src/nsContainerEnumerator.cpp263
-rw-r--r--components/rdf/src/nsDefaultResourceFactory.cpp30
-rw-r--r--components/rdf/src/nsFileSystemDataSource.cpp1328
-rw-r--r--components/rdf/src/nsFileSystemDataSource.h79
-rw-r--r--components/rdf/src/nsILocalStore.h34
-rw-r--r--components/rdf/src/nsIRDFContentSink.h62
-rw-r--r--components/rdf/src/nsIRDFFTP.h33
-rw-r--r--components/rdf/src/nsInMemoryDataSource.cpp1964
-rw-r--r--components/rdf/src/nsLocalStore.cpp480
-rw-r--r--components/rdf/src/nsNameSpaceMap.cpp64
-rw-r--r--components/rdf/src/nsNameSpaceMap.h98
-rw-r--r--components/rdf/src/nsRDFBaseDataSources.h32
-rw-r--r--components/rdf/src/nsRDFBuiltInDataSources.h27
-rw-r--r--components/rdf/src/nsRDFContainer.cpp726
-rw-r--r--components/rdf/src/nsRDFContainerUtils.cpp515
-rw-r--r--components/rdf/src/nsRDFContentSink.cpp1476
-rw-r--r--components/rdf/src/nsRDFContentSinkAtomList.h18
-rw-r--r--components/rdf/src/nsRDFService.cpp1551
-rw-r--r--components/rdf/src/nsRDFService.h79
-rw-r--r--components/rdf/src/nsRDFXMLDataSource.cpp1179
-rw-r--r--components/rdf/src/nsRDFXMLParser.cpp137
-rw-r--r--components/rdf/src/nsRDFXMLParser.h31
-rw-r--r--components/rdf/src/nsRDFXMLSerializer.cpp1126
-rw-r--r--components/rdf/src/nsRDFXMLSerializer.h117
-rw-r--r--components/rdf/src/rdf.h61
-rw-r--r--components/rdf/src/rdfTriplesSerializer.cpp151
-rw-r--r--components/rdf/src/rdfutil.cpp111
-rw-r--r--components/rdf/src/rdfutil.h40
-rw-r--r--components/rdf/util/internal/moz.build10
-rw-r--r--components/rdf/util/moz.build23
-rw-r--r--components/rdf/util/nsRDFResource.cpp221
-rw-r--r--components/rdf/util/nsRDFResource.h59
-rw-r--r--components/rdf/util/objs.mozbuild8
-rw-r--r--components/reader/.eslintrc.js196
-rw-r--r--components/reader/AboutReader.jsm935
-rw-r--r--components/reader/JSDOMParser.js1196
-rw-r--r--components/reader/Readability-readerable.js107
-rw-r--r--components/reader/Readability.js2078
-rw-r--r--components/reader/ReaderMode.jsm590
-rw-r--r--components/reader/ReaderWorker.js53
-rw-r--r--components/reader/ReaderWorker.jsm17
-rw-r--r--components/reader/Readerable.js96
-rw-r--r--components/reader/Readerable.jsm10
-rw-r--r--components/reader/content/aboutReader.html66
-rw-r--r--components/reader/content/aboutReader.js9
-rw-r--r--components/reader/jar.mn7
-rw-r--r--components/reader/moz.build20
-rw-r--r--components/reflect/moz.build14
-rw-r--r--components/reflect/reflect.cpp77
-rw-r--r--components/reflect/reflect.h30
-rw-r--r--components/reflect/reflect.jsm24
-rw-r--r--components/registry/moz.build34
-rw-r--r--components/registry/public/nsIChromeRegistry.idl116
-rw-r--r--components/registry/public/nsIToolkitChromeRegistry.idl28
-rw-r--r--components/registry/src/RegistryMessageUtils.h209
-rw-r--r--components/registry/src/nsChromeProtocolHandler.cpp214
-rw-r--r--components/registry/src/nsChromeProtocolHandler.h37
-rw-r--r--components/registry/src/nsChromeRegistry.cpp730
-rw-r--r--components/registry/src/nsChromeRegistry.h166
-rw-r--r--components/registry/src/nsChromeRegistryChrome.cpp986
-rw-r--r--components/registry/src/nsChromeRegistryChrome.h180
-rw-r--r--components/registry/src/nsChromeRegistryContent.cpp317
-rw-r--r--components/registry/src/nsChromeRegistryContent.h84
-rw-r--r--components/remote/moz.build23
-rw-r--r--components/remote/nsGTKRemoteService.cpp181
-rw-r--r--components/remote/nsGTKRemoteService.h49
-rw-r--r--components/remote/nsIRemoteService.idl45
-rw-r--r--components/remote/nsXRemoteService.cpp324
-rw-r--r--components/remote/nsXRemoteService.h62
-rw-r--r--components/remotebrowserutils/RemoteWebNavigation.js139
-rw-r--r--components/remotebrowserutils/moz.build9
-rw-r--r--components/remotebrowserutils/remotebrowserutils.manifest2
-rw-r--r--components/satchel/AutoCompletePopup.jsm317
-rw-r--r--components/satchel/FormHistory.jsm1118
-rw-r--r--components/satchel/FormHistoryStartup.js146
-rw-r--r--components/satchel/formSubmitListener.js190
-rw-r--r--components/satchel/jar.mn7
-rw-r--r--components/satchel/moz.build35
-rw-r--r--components/satchel/nsFormAutoComplete.js623
-rw-r--r--components/satchel/nsFormAutoCompleteResult.jsm187
-rw-r--r--components/satchel/nsFormFillController.cpp1363
-rw-r--r--components/satchel/nsFormFillController.h125
-rw-r--r--components/satchel/nsFormHistory.js872
-rw-r--r--components/satchel/nsIFormAutoComplete.idl47
-rw-r--r--components/satchel/nsIFormFillController.idl56
-rw-r--r--components/satchel/nsIFormHistory.idl74
-rw-r--r--components/satchel/nsIInputListAutoComplete.idl17
-rw-r--r--components/satchel/nsInputListAutoComplete.js64
-rw-r--r--components/satchel/satchel.manifest10
-rw-r--r--components/satchel/towel5
-rw-r--r--components/scache/moz.build20
-rw-r--r--components/scache/public/nsIStartupCache.idl61
-rw-r--r--components/scache/src/StartupCache.cpp795
-rw-r--r--components/scache/src/StartupCache.h228
-rw-r--r--components/scache/src/StartupCacheModule.cpp45
-rw-r--r--components/scache/src/StartupCacheUtils.cpp253
-rw-r--r--components/scache/src/StartupCacheUtils.h43
-rw-r--r--components/search/locale/search.properties20
-rw-r--r--components/search/moz.build21
-rw-r--r--components/search/src/SearchStaticData.jsm43
-rw-r--r--components/search/src/SearchSuggestionController.jsm396
-rw-r--r--components/search/src/nsSearchService.js4524
-rw-r--r--components/search/src/nsSearchSuggestions.js197
-rw-r--r--components/search/src/nsSidebar.js57
-rw-r--r--components/search/toolkitsearch.manifest10
-rw-r--r--components/startup/moz.build27
-rw-r--r--components/startup/mozprofilerprobe.mof29
-rw-r--r--components/startup/public/nsIAppStartup.idl195
-rw-r--r--components/startup/public/nsIUserInfo.idl32
-rw-r--r--components/startup/src/StartupTimeline.cpp35
-rw-r--r--components/startup/src/StartupTimeline.h81
-rw-r--r--components/startup/src/nsAppStartup.cpp970
-rw-r--r--components/startup/src/nsAppStartup.h76
-rw-r--r--components/startup/src/nsAppStartupNotifier.cpp90
-rw-r--r--components/startup/src/nsAppStartupNotifier.h30
-rw-r--r--components/startup/src/nsIAppStartupNotifier.h59
-rw-r--r--components/startup/src/nsUserInfo.h23
-rw-r--r--components/startup/src/nsUserInfoUnix.cpp167
-rw-r--r--components/startup/src/nsUserInfoWin.cpp133
-rw-r--r--components/statusfilter/moz.build10
-rw-r--r--components/statusfilter/nsBrowserStatusFilter.cpp392
-rw-r--r--components/statusfilter/nsBrowserStatusFilter.h80
-rw-r--r--components/storage/.eslintrc.js7
-rw-r--r--components/storage/moz.build95
-rw-r--r--components/storage/public/mozIStorageAggregateFunction.idl38
-rw-r--r--components/storage/public/mozIStorageAsyncConnection.idl220
-rw-r--r--components/storage/public/mozIStorageAsyncStatement.idl37
-rw-r--r--components/storage/public/mozIStorageBaseStatement.idl158
-rw-r--r--components/storage/public/mozIStorageBindingParams.idl86
-rw-r--r--components/storage/public/mozIStorageBindingParamsArray.idl34
-rw-r--r--components/storage/public/mozIStorageCompletionCallback.idl24
-rw-r--r--components/storage/public/mozIStorageConnection.idl268
-rw-r--r--components/storage/public/mozIStorageError.idl149
-rw-r--r--components/storage/public/mozIStorageFunction.idl40
-rw-r--r--components/storage/public/mozIStoragePendingStatement.idl20
-rw-r--r--components/storage/public/mozIStorageProgressHandler.idl26
-rw-r--r--components/storage/public/mozIStorageResultSet.idl21
-rw-r--r--components/storage/public/mozIStorageRow.idl33
-rw-r--r--components/storage/public/mozIStorageService.idl193
-rw-r--r--components/storage/public/mozIStorageStatement.idl307
-rw-r--r--components/storage/public/mozIStorageStatementCallback.idl48
-rw-r--r--components/storage/public/mozIStorageStatementParams.idl11
-rw-r--r--components/storage/public/mozIStorageStatementRow.idl12
-rw-r--r--components/storage/public/mozIStorageVacuumParticipant.idl57
-rw-r--r--components/storage/public/mozIStorageValueArray.idl145
-rw-r--r--components/storage/src/FileSystemModule.cpp304
-rw-r--r--components/storage/src/FileSystemModule.h22
-rw-r--r--components/storage/src/IStorageBindingParamsInternal.h51
-rw-r--r--components/storage/src/SQLCollations.cpp242
-rw-r--r--components/storage/src/SQLCollations.h249
-rw-r--r--components/storage/src/SQLiteMutex.h173
-rw-r--r--components/storage/src/StatementCache.h141
-rw-r--r--components/storage/src/StorageBaseStatementInternal.cpp221
-rw-r--r--components/storage/src/StorageBaseStatementInternal.h353
-rw-r--r--components/storage/src/TelemetryVFS.cpp827
-rw-r--r--components/storage/src/VacuumManager.cpp388
-rw-r--r--components/storage/src/VacuumManager.h45
-rw-r--r--components/storage/src/Variant.h446
-rw-r--r--components/storage/src/Variant_inl.h229
-rw-r--r--components/storage/src/mozStorageArgValueArray.cpp213
-rw-r--r--components/storage/src/mozStorageArgValueArray.h36
-rw-r--r--components/storage/src/mozStorageAsyncStatement.cpp383
-rw-r--r--components/storage/src/mozStorageAsyncStatement.h107
-rw-r--r--components/storage/src/mozStorageAsyncStatementExecution.cpp580
-rw-r--r--components/storage/src/mozStorageAsyncStatementExecution.h253
-rw-r--r--components/storage/src/mozStorageAsyncStatementJSHelper.cpp153
-rw-r--r--components/storage/src/mozStorageAsyncStatementJSHelper.h54
-rw-r--r--components/storage/src/mozStorageAsyncStatementParams.cpp131
-rw-r--r--components/storage/src/mozStorageAsyncStatementParams.h45
-rw-r--r--components/storage/src/mozStorageBindingParams.cpp525
-rw-r--r--components/storage/src/mozStorageBindingParams.h113
-rw-r--r--components/storage/src/mozStorageBindingParamsArray.cpp90
-rw-r--r--components/storage/src/mozStorageBindingParamsArray.h116
-rw-r--r--components/storage/src/mozStorageCID.h30
-rw-r--r--components/storage/src/mozStorageConnection.cpp1994
-rw-r--r--components/storage/src/mozStorageConnection.h439
-rw-r--r--components/storage/src/mozStorageError.cpp49
-rw-r--r--components/storage/src/mozStorageError.h35
-rw-r--r--components/storage/src/mozStorageHelper.h209
-rw-r--r--components/storage/src/mozStorageModule.cpp53
-rw-r--r--components/storage/src/mozStoragePrivateHelpers.cpp277
-rw-r--r--components/storage/src/mozStoragePrivateHelpers.h143
-rw-r--r--components/storage/src/mozStorageResultSet.cpp59
-rw-r--r--components/storage/src/mozStorageResultSet.h53
-rw-r--r--components/storage/src/mozStorageRow.cpp247
-rw-r--r--components/storage/src/mozStorageRow.h60
-rw-r--r--components/storage/src/mozStorageSQLFunctions.cpp406
-rw-r--r--components/storage/src/mozStorageSQLFunctions.h76
-rw-r--r--components/storage/src/mozStorageService.cpp930
-rw-r--r--components/storage/src/mozStorageService.h197
-rw-r--r--components/storage/src/mozStorageStatement.cpp889
-rw-r--r--components/storage/src/mozStorageStatement.h118
-rw-r--r--components/storage/src/mozStorageStatementData.h150
-rw-r--r--components/storage/src/mozStorageStatementJSHelper.cpp287
-rw-r--r--components/storage/src/mozStorageStatementJSHelper.h70
-rw-r--r--components/storage/src/mozStorageStatementParams.cpp184
-rw-r--r--components/storage/src/mozStorageStatementParams.h43
-rw-r--r--components/storage/src/mozStorageStatementRow.cpp157
-rw-r--r--components/storage/src/mozStorageStatementRow.h40
-rw-r--r--components/storage/src/storage.h39
-rw-r--r--components/storage/src/variantToSQLiteT_impl.h127
-rw-r--r--components/storage/style.txt141
-rw-r--r--components/terminator/moz.build14
-rw-r--r--components/terminator/nsTerminator.cpp303
-rw-r--r--components/terminator/nsTerminator.h40
-rw-r--r--components/terminator/terminator.manifest1
-rw-r--r--components/thumbnails/BackgroundPageThumbs.jsm416
-rw-r--r--components/thumbnails/BrowserPageThumbs.manifest2
-rw-r--r--components/thumbnails/PageThumbUtils.jsm343
-rw-r--r--components/thumbnails/PageThumbs.jsm902
-rw-r--r--components/thumbnails/PageThumbsProtocol.js154
-rw-r--r--components/thumbnails/PageThumbsWorker.js176
-rw-r--r--components/thumbnails/blankthumb.inc87
-rw-r--r--components/thumbnails/content/backgroundPageThumbsContent.js201
-rw-r--r--components/thumbnails/jar.mn6
-rw-r--r--components/thumbnails/moz.build21
-rw-r--r--components/timermanager/moz.build13
-rw-r--r--components/timermanager/nsIUpdateTimerManager.idl54
-rw-r--r--components/timermanager/nsUpdateTimerManager.js319
-rw-r--r--components/timermanager/nsUpdateTimerManager.manifest3
-rw-r--r--components/tooltiptext/TooltipTextProvider.js148
-rw-r--r--components/tooltiptext/TooltipTextProvider.manifest2
-rw-r--r--components/tooltiptext/moz.build9
-rw-r--r--components/typeaheadfind/content/notfound.wavbin0 -> 1422 bytes
-rw-r--r--components/typeaheadfind/jar.mn6
-rw-r--r--components/typeaheadfind/moz.build21
-rw-r--r--components/typeaheadfind/nsITypeAheadFind.idl95
-rw-r--r--components/typeaheadfind/nsTypeAheadFind.cpp1328
-rw-r--r--components/typeaheadfind/nsTypeAheadFind.h134
-rw-r--r--components/uriloader/moz.build31
-rw-r--r--components/uriloader/public/nsCURILoader.idl49
-rw-r--r--components/uriloader/public/nsIContentHandler.idl35
-rw-r--r--components/uriloader/public/nsIDocumentLoader.idl36
-rw-r--r--components/uriloader/public/nsITransfer.idl106
-rw-r--r--components/uriloader/public/nsIURIContentListener.idl135
-rw-r--r--components/uriloader/public/nsIURILoader.idl140
-rw-r--r--components/uriloader/public/nsIWebProgress.idl153
-rw-r--r--components/uriloader/public/nsIWebProgressListener.idl425
-rw-r--r--components/uriloader/public/nsIWebProgressListener2.idl69
-rw-r--r--components/uriloader/src/nsDocLoader.cpp1592
-rw-r--r--components/uriloader/src/nsDocLoader.h356
-rw-r--r--components/uriloader/src/nsURILoader.cpp956
-rw-r--r--components/uriloader/src/nsURILoader.h59
-rw-r--r--components/urlformatter/api_keys.in3
-rw-r--r--components/urlformatter/moz.build16
-rw-r--r--components/urlformatter/nsIURLFormatter.idl50
-rw-r--r--components/urlformatter/nsURLFormatter.js151
-rw-r--r--components/urlformatter/nsURLFormatter.manifest2
-rw-r--r--components/utils/moz.build9
-rw-r--r--components/utils/simpleServices.js313
-rw-r--r--components/utils/utils.manifest6
-rw-r--r--components/viewconfig/content/config.js635
-rw-r--r--components/viewconfig/content/config.xul101
-rw-r--r--components/viewconfig/jar.mn7
-rw-r--r--components/viewconfig/moz.build6
-rw-r--r--components/viewsource/ViewSourceBrowser.jsm331
-rw-r--r--components/viewsource/content/viewPartialSource.js22
-rw-r--r--components/viewsource/content/viewPartialSource.xul161
-rw-r--r--components/viewsource/content/viewSource-content.js979
-rw-r--r--components/viewsource/content/viewSource.css11
-rw-r--r--components/viewsource/content/viewSource.js843
-rw-r--r--components/viewsource/content/viewSource.xul221
-rw-r--r--components/viewsource/content/viewSourceUtils.js509
-rw-r--r--components/viewsource/jar.mn12
-rw-r--r--components/viewsource/moz.build8
-rw-r--r--components/weave/WeaveComponents.manifest13
-rw-r--r--components/weave/content/aboutSyncTabs-bindings.xml46
-rw-r--r--components/weave/content/aboutSyncTabs.css11
-rw-r--r--components/weave/content/aboutSyncTabs.js313
-rw-r--r--components/weave/content/aboutSyncTabs.xul67
-rw-r--r--components/weave/content/addDevice.js157
-rw-r--r--components/weave/content/addDevice.xul129
-rw-r--r--components/weave/content/genericChange.js234
-rw-r--r--components/weave/content/genericChange.xul123
-rw-r--r--components/weave/content/key.xhtml54
-rw-r--r--components/weave/content/notification.xml129
-rw-r--r--components/weave/content/progress.js71
-rw-r--r--components/weave/content/progress.xhtml55
-rw-r--r--components/weave/content/quota.js247
-rw-r--r--components/weave/content/quota.xul65
-rw-r--r--components/weave/content/setup.js1071
-rw-r--r--components/weave/content/setup.xul491
-rw-r--r--components/weave/content/utils.js218
-rw-r--r--components/weave/jar.mn37
-rw-r--r--components/weave/locales/en-US/aboutSyncTabs.dtd21
-rw-r--r--components/weave/locales/en-US/errors.properties27
-rw-r--r--components/weave/locales/en-US/sync.properties55
-rw-r--r--components/weave/locales/en-US/syncGenericChange.properties37
-rw-r--r--components/weave/locales/en-US/syncKey.dtd18
-rw-r--r--components/weave/locales/en-US/syncProgress.dtd15
-rw-r--r--components/weave/locales/en-US/syncQuota.dtd8
-rw-r--r--components/weave/locales/en-US/syncQuota.properties42
-rw-r--r--components/weave/locales/en-US/syncSetup.dtd116
-rw-r--r--components/weave/locales/en-US/syncSetup.properties51
-rw-r--r--components/weave/locales/jar.mn18
-rw-r--r--components/weave/locales/l10n.ini9
-rw-r--r--components/weave/locales/moz.build6
-rw-r--r--components/weave/moz.build78
-rw-r--r--components/weave/public/nsISyncJPAKE.idl103
-rw-r--r--components/weave/skin/aboutSyncTabs.css101
-rw-r--r--components/weave/skin/sync-128.pngbin0 -> 20229 bytes
-rw-r--r--components/weave/skin/sync-16.pngbin0 -> 1847 bytes
-rw-r--r--components/weave/skin/sync-32.pngbin0 -> 3384 bytes
-rw-r--r--components/weave/skin/sync-bg.pngbin0 -> 21309 bytes
-rw-r--r--components/weave/skin/sync-desktopIcon.pngbin0 -> 291 bytes
-rw-r--r--components/weave/skin/sync-mobileIcon.pngbin0 -> 352 bytes
-rw-r--r--components/weave/skin/syncCommon.css53
-rw-r--r--components/weave/skin/syncProgress.css46
-rw-r--r--components/weave/skin/syncQuota.css26
-rw-r--r--components/weave/skin/syncSetup.css134
-rw-r--r--components/weave/src/WeaveService.js177
-rw-r--r--components/weave/src/common/hawkclient.js346
-rw-r--r--components/weave/src/common/hawkrequest.js198
-rw-r--r--components/weave/src/common/logmanager.js331
-rw-r--r--components/weave/src/common/observers.js150
-rw-r--r--components/weave/src/common/rest.js764
-rw-r--r--components/weave/src/common/services-common.js11
-rw-r--r--components/weave/src/common/stringbundle.js203
-rw-r--r--components/weave/src/common/tokenserverclient.js459
-rw-r--r--components/weave/src/common/utils.js649
-rw-r--r--components/weave/src/crypto/WeaveCrypto.js262
-rw-r--r--components/weave/src/crypto/utils.js588
-rw-r--r--components/weave/src/engines/addons.js706
-rw-r--r--components/weave/src/engines/bookmarks.js1542
-rw-r--r--components/weave/src/engines/clients.js476
-rw-r--r--components/weave/src/engines/forms.js246
-rw-r--r--components/weave/src/engines/history.js417
-rw-r--r--components/weave/src/engines/passwords.js305
-rw-r--r--components/weave/src/engines/prefs.js260
-rw-r--r--components/weave/src/engines/tabs.js376
-rw-r--r--components/weave/src/nsSyncJPAKE.cpp484
-rw-r--r--components/weave/src/nsSyncJPAKE.h38
-rw-r--r--components/weave/src/stages/cluster.js111
-rw-r--r--components/weave/src/stages/declined.js76
-rw-r--r--components/weave/src/stages/enginesync.js326
-rw-r--r--components/weave/src/sync/addonsreconciler.js674
-rw-r--r--components/weave/src/sync/addonutils.js474
-rw-r--r--components/weave/src/sync/constants.js192
-rw-r--r--components/weave/src/sync/engines.js1613
-rw-r--r--components/weave/src/sync/identity.js603
-rw-r--r--components/weave/src/sync/jpakeclient.js773
-rw-r--r--components/weave/src/sync/keys.js214
-rw-r--r--components/weave/src/sync/main.js31
-rw-r--r--components/weave/src/sync/notifications.js131
-rw-r--r--components/weave/src/sync/policies.js967
-rw-r--r--components/weave/src/sync/record.js628
-rw-r--r--components/weave/src/sync/resource.js678
-rw-r--r--components/weave/src/sync/rest.js106
-rw-r--r--components/weave/src/sync/service.js1586
-rw-r--r--components/weave/src/sync/status.js142
-rw-r--r--components/weave/src/sync/userapi.js224
-rw-r--r--components/weave/src/sync/util.js693
-rw-r--r--components/weave/weave-prefs.js83
-rw-r--r--components/webbrowser/moz.build46
-rw-r--r--components/webbrowser/public/nsCWebBrowser.idl34
-rw-r--r--components/webbrowser/public/nsICommandHandler.idl40
-rw-r--r--components/webbrowser/public/nsIEmbeddingSiteWindow.idl157
-rw-r--r--components/webbrowser/public/nsIPrintPreviewNavigation.idl52
-rw-r--r--components/webbrowser/public/nsIPrintingPromptService.idl165
-rw-r--r--components/webbrowser/public/nsIWebBrowser.idl163
-rw-r--r--components/webbrowser/public/nsIWebBrowserChrome.idl144
-rw-r--r--components/webbrowser/public/nsIWebBrowserChrome2.idl33
-rw-r--r--components/webbrowser/public/nsIWebBrowserChrome3.idl66
-rw-r--r--components/webbrowser/public/nsIWebBrowserChromeFocus.idl32
-rw-r--r--components/webbrowser/public/nsIWebBrowserFocus.idl76
-rw-r--r--components/webbrowser/public/nsIWebBrowserPrint.idl152
-rw-r--r--components/webbrowser/public/nsIWebBrowserSetup.idl109
-rw-r--r--components/webbrowser/public/nsIWebBrowserStream.idl56
-rw-r--r--components/webbrowser/src/nsCommandHandler.cpp138
-rw-r--r--components/webbrowser/src/nsCommandHandler.h34
-rw-r--r--components/webbrowser/src/nsEmbedStream.cpp100
-rw-r--r--components/webbrowser/src/nsEmbedStream.h36
-rw-r--r--components/webbrowser/src/nsWebBrowser.cpp1952
-rw-r--r--components/webbrowser/src/nsWebBrowser.h184
-rw-r--r--components/webbrowser/src/nsWebBrowserContentPolicy.cpp107
-rw-r--r--components/webbrowser/src/nsWebBrowserContentPolicy.h25
-rw-r--r--components/webbrowser/src/nsWebBrowserModule.cpp52
-rw-r--r--components/windowcreator/moz.build12
-rw-r--r--components/windowcreator/public/nsIWindowCreator.idl41
-rw-r--r--components/windowcreator/public/nsIWindowCreator2.idl70
-rw-r--r--components/windowcreator/public/nsIWindowProvider.idl103
-rw-r--r--components/windowds/moz.build11
-rw-r--r--components/windowds/public/nsIWindowDataSource.idl17
-rw-r--r--components/windowds/src/nsWindowDataSource.cpp519
-rw-r--r--components/windowds/src/nsWindowDataSource.h59
-rw-r--r--components/windowwatcher/moz.build39
-rw-r--r--components/windowwatcher/public/nsIDialogParamBlock.idl42
-rw-r--r--components/windowwatcher/public/nsIPromptFactory.idl22
-rw-r--r--components/windowwatcher/public/nsIPromptService.idl346
-rw-r--r--components/windowwatcher/public/nsIPromptService2.idl45
-rw-r--r--components/windowwatcher/public/nsIWindowWatcher.idl167
-rw-r--r--components/windowwatcher/public/nsPIPromptService.idl31
-rw-r--r--components/windowwatcher/public/nsPIWindowWatcher.idl146
-rw-r--r--components/windowwatcher/src/nsAutoWindowStateHelper.cpp72
-rw-r--r--components/windowwatcher/src/nsAutoWindowStateHelper.h34
-rw-r--r--components/windowwatcher/src/nsDialogParamBlock.cpp100
-rw-r--r--components/windowwatcher/src/nsDialogParamBlock.h44
-rw-r--r--components/windowwatcher/src/nsPromptUtils.h140
-rw-r--r--components/windowwatcher/src/nsWindowWatcher.cpp2576
-rw-r--r--components/windowwatcher/src/nsWindowWatcher.h155
-rw-r--r--components/workerloader/moz.build6
-rw-r--r--components/workerloader/require.js161
-rw-r--r--components/xmlparser/moz.build32
-rw-r--r--components/xmlparser/public/nsIMozSAXXMLDeclarationHandler.idl15
-rw-r--r--components/xmlparser/public/nsISAXAttributes.idl150
-rw-r--r--components/xmlparser/public/nsISAXContentHandler.idl225
-rw-r--r--components/xmlparser/public/nsISAXDTDHandler.idl77
-rw-r--r--components/xmlparser/public/nsISAXErrorHandler.idl95
-rw-r--r--components/xmlparser/public/nsISAXLexicalHandler.idl118
-rw-r--r--components/xmlparser/public/nsISAXLocator.idl89
-rw-r--r--components/xmlparser/public/nsISAXMutableAttributes.idl127
-rw-r--r--components/xmlparser/public/nsISAXXMLFilter.idl29
-rw-r--r--components/xmlparser/public/nsISAXXMLReader.idl207
-rw-r--r--components/xmlparser/src/nsSAXAttributes.cpp332
-rw-r--r--components/xmlparser/src/nsSAXAttributes.h43
-rw-r--r--components/xmlparser/src/nsSAXLocator.cpp47
-rw-r--r--components/xmlparser/src/nsSAXLocator.h39
-rw-r--r--components/xmlparser/src/nsSAXXMLReader.cpp719
-rw-r--r--components/xmlparser/src/nsSAXXMLReader.h105
-rw-r--r--components/xulstore/XULStore.js346
-rw-r--r--components/xulstore/XULStore.manifest2
-rw-r--r--components/xulstore/moz.build13
-rw-r--r--components/xulstore/nsIXULStore.idl75
2256 files changed, 589975 insertions, 0 deletions
diff --git a/components/.eslintrc.js b/components/.eslintrc.js
new file mode 100644
index 000000000..e6cf2032e
--- /dev/null
+++ b/components/.eslintrc.js
@@ -0,0 +1,11 @@
+"use strict";
+
+module.exports = {
+ "rules": {
+ "no-unused-vars": ["error", {
+ "vars": "local",
+ "varsIgnorePattern": "^Cc|Ci|Cu|Cr|EXPORTED_SYMBOLS",
+ "args": "none",
+ }]
+ }
+};
diff --git a/components/aboutcache/content/aboutCache.js b/components/aboutcache/content/aboutCache.js
new file mode 100644
index 000000000..e945d683e
--- /dev/null
+++ b/components/aboutcache/content/aboutCache.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// First, parse and save the incoming arguments ("?storage=name&context=key")
+// Note: window.location.search doesn't work with nsSimpleURIs used for about:* addresses.
+var search = window.location.href.match(/^.*\?(.*)$/);
+var searchParams = new URLSearchParams(search ? search[1] : '');
+var storage = searchParams.get('storage');
+var cacheContext = searchParams.get('context');
+
+// The context is in a format as used by the HTTP cache v2 back end
+if (cacheContext)
+ var [context, isAnon, isInBrowser, appId, isPrivate] = cacheContext.match(/(a,)?(b,)?(i\d+,)?(p,)?/);
+if (appId)
+ appId = appId.match(/i(\d+),/)[1];
+
+
+function $(id) { return document.getElementById(id) || {}; }
+
+// Initialize the context UI controls at the start according what we got in the "context=" argument
+addEventListener('DOMContentLoaded', function() {
+ $('anon').checked = !!isAnon;
+ $('inbrowser').checked = !!isInBrowser;
+ $('appid').value = appId || '';
+ $('priv').checked = !!isPrivate;
+}, false);
+
+// When user presses the [Update] button, we build a new context key according the UI control
+// values and navigate to a new about:cache?storage=<name>&context=<key> URL.
+function navigate()
+{
+ context = '';
+ if ($('anon').checked)
+ context += 'a,';
+ if ($('inbrowser').checked)
+ context += 'b,';
+ if ($('appid').value)
+ context += 'i' + $('appid').value + ',';
+ if ($('priv').checked)
+ context += 'p,';
+
+ if (storage == null) {
+ storage = "";
+ }
+
+ window.location.href = 'about:cache?storage=' + storage + '&context=' + context;
+}
diff --git a/components/aboutcache/jar.mn b/components/aboutcache/jar.mn
new file mode 100644
index 000000000..b414a8f6d
--- /dev/null
+++ b/components/aboutcache/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/.
+
+toolkit.jar:
+ content/global/aboutCache.js (content/aboutCache.js)
diff --git a/components/aboutcache/moz.build b/components/aboutcache/moz.build
new file mode 100644
index 000000000..aee4b90a5
--- /dev/null
+++ b/components/aboutcache/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/aboutcheckerboard/content/aboutCheckerboard.css b/components/aboutcheckerboard/content/aboutCheckerboard.css
new file mode 100644
index 000000000..7f88612db
--- /dev/null
+++ b/components/aboutcheckerboard/content/aboutCheckerboard.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/. */
+
+table.listing {
+ width: 100%;
+}
+
+table th, table td {
+ padding: 5px;
+ border: inset 2px black;
+ margin: 0px;
+ width: 50%;
+ vertical-align: top;
+}
+
+hr {
+ clear: both;
+ margin: 10px;
+}
+
+iframe {
+ width: 100%;
+ height: 900px;
+}
+
+#player, #raw {
+ width: 800px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+#controls {
+ text-align: center;
+}
+
+#canvas {
+ border: solid 1px black;
+}
+
+#active {
+ width: 100%;
+ border: solid 1px black;
+ margin-top: 0;
+}
+
+#trace {
+ width: 100%;
+}
diff --git a/components/aboutcheckerboard/content/aboutCheckerboard.js b/components/aboutcheckerboard/content/aboutCheckerboard.js
new file mode 100644
index 000000000..c64a80a05
--- /dev/null
+++ b/components/aboutcheckerboard/content/aboutCheckerboard.js
@@ -0,0 +1,276 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 trace;
+var service;
+var reports;
+
+function onLoad() {
+ trace = document.getElementById('trace');
+ service = new CheckerboardReportService();
+ updateEnabled();
+ reports = service.getReports();
+ for (var i = 0; i < reports.length; i++) {
+ let text = "Severity " + reports[i].severity + " at " + new Date(reports[i].timestamp).toString();
+ let link = document.createElement('a');
+ link.href = 'javascript:showReport(' + i + ')';
+ link.textContent = text;
+ let bullet = document.createElement('li');
+ bullet.appendChild(link);
+ document.getElementById(reports[i].reason).appendChild(bullet);
+ }
+}
+
+function updateEnabled() {
+ let enabled = document.getElementById('enabled');
+ if (service.isRecordingEnabled()) {
+ enabled.textContent = 'enabled';
+ enabled.style.color = 'green';
+ } else {
+ enabled.textContent = 'disabled';
+ enabled.style.color = 'red';
+ }
+}
+
+function toggleEnabled() {
+ service.setRecordingEnabled(!service.isRecordingEnabled());
+ updateEnabled();
+}
+
+function flushReports() {
+ service.flushActiveReports();
+}
+
+function showReport(index) {
+ trace.value = reports[index].log;
+ loadData();
+}
+
+// -- Code to load and render the trace --
+
+const CANVAS_USE_RATIO = 0.75;
+const FRAME_INTERVAL_MS = 50;
+const VECTOR_NORMALIZED_MAGNITUDE = 30.0;
+
+var renderData = new Array();
+var currentFrame = 0;
+var playing = false;
+var timerId = 0;
+
+var minX = undefined;
+var minY = undefined;
+var maxX = undefined;
+var maxY = undefined;
+
+function log(x) {
+ if (console) {
+ console.log(x);
+ }
+}
+
+function getFlag(flag) {
+ return document.getElementById(flag).checked;
+}
+
+// parses the lines in the textarea, ignoring anything that doesn't have RENDERTRACE.
+// for each matching line, tokenizes on whitespace and ignores all tokens prior to
+// RENDERTRACE. Additional info can be included at the end of the line, and will be
+// displayed but not parsed. Allowed syntaxes:
+// <junk> RENDERTRACE <timestamp> rect <color> <x> <y> <width> <height> [extraInfo]
+function loadData() {
+ stopPlay();
+ renderData = new Array();
+ currentFrame = 0;
+ minX = undefined;
+ minY = undefined;
+ maxX = undefined;
+ maxY = undefined;
+
+ var charPos = 0;
+ var lastLineLength = 0;
+ var lines = trace.value.split(/\r|\n/);
+ for (var i = 0; i < lines.length; i++) {
+ charPos += lastLineLength;
+ lastLineLength = lines[i].length + 1;
+ // skip lines without RENDERTRACE
+ if (! /RENDERTRACE/.test(lines[i])) {
+ continue;
+ }
+
+ var tokens = lines[i].split(/\s+/);
+ var j = 0;
+ // skip tokens until RENDERTRACE
+ while (j < tokens.length && tokens[j++] != "RENDERTRACE"); // empty loop body
+ if (j >= tokens.length - 2) {
+ log("Error parsing line: " + lines[i]);
+ continue;
+ }
+
+ var timestamp = tokens[j++];
+ var destIndex = renderData.length;
+ if (destIndex == 0) {
+ // create the initial frame
+ renderData.push({
+ timestamp: timestamp,
+ rects: {},
+ });
+ } else if (renderData[destIndex - 1].timestamp == timestamp) {
+ // timestamp hasn't changed use, so update the previous object
+ destIndex--;
+ } else {
+ // clone a new copy of the last frame and update timestamp
+ renderData.push(JSON.parse(JSON.stringify(renderData[destIndex - 1])));
+ renderData[destIndex].timestamp = timestamp;
+ }
+
+ switch (tokens[j++]) {
+ case "rect":
+ if (j > tokens.length - 5) {
+ log("Error parsing line: " + lines[i]);
+ continue;
+ }
+
+ var rect = {};
+ var color = tokens[j++];
+ renderData[destIndex].rects[color] = rect;
+ rect.x = parseFloat(tokens[j++]);
+ rect.y = parseFloat(tokens[j++]);
+ rect.width = parseFloat(tokens[j++]);
+ rect.height = parseFloat(tokens[j++]);
+ rect.dataText = trace.value.substring(charPos, charPos + lines[i].length);
+
+ if (!getFlag('excludePageFromZoom') || color != 'brown') {
+ if (typeof minX == "undefined") {
+ minX = rect.x;
+ minY = rect.y;
+ maxX = rect.x + rect.width;
+ maxY = rect.y + rect.height;
+ } else {
+ minX = Math.min(minX, rect.x);
+ minY = Math.min(minY, rect.y);
+ maxX = Math.max(maxX, rect.x + rect.width);
+ maxY = Math.max(maxY, rect.y + rect.height);
+ }
+ }
+ break;
+
+ default:
+ log("Error parsing line " + lines[i]);
+ break;
+ }
+ }
+
+ if (! renderFrame()) {
+ alert("No data found; nothing to render!");
+ }
+}
+
+// render the current frame (i.e. renderData[currentFrame])
+// returns false if currentFrame is out of bounds, true otherwise
+function renderFrame() {
+ var frame = currentFrame;
+ if (frame < 0 || frame >= renderData.length) {
+ log("Invalid frame index");
+ return false;
+ }
+
+ var canvas = document.getElementById('canvas');
+ if (! canvas.getContext) {
+ log("No canvas context");
+ }
+
+ var context = canvas.getContext('2d');
+
+ // midpoint of the bounding box
+ var midX = (minX + maxX) / 2.0;
+ var midY = (minY + maxY) / 2.0;
+
+ // midpoint of the canvas
+ var cmx = canvas.width / 2.0;
+ var cmy = canvas.height / 2.0;
+
+ // scale factor
+ var scale = CANVAS_USE_RATIO * Math.min(canvas.width / (maxX - minX), canvas.height / (maxY - minY));
+
+ function projectX(value) {
+ return cmx + ((value - midX) * scale);
+ }
+
+ function projectY(value) {
+ return cmy + ((value - midY) * scale);
+ }
+
+ function drawRect(color, rect) {
+ context.strokeStyle = color;
+ context.strokeRect(
+ projectX(rect.x),
+ projectY(rect.y),
+ rect.width * scale,
+ rect.height * scale);
+ }
+
+ // clear canvas
+ context.fillStyle = 'white';
+ context.fillRect(0, 0, canvas.width, canvas.height);
+ var activeData = '';
+ // draw rects
+ for (var i in renderData[frame].rects) {
+ drawRect(i, renderData[frame].rects[i]);
+ activeData += "\n" + renderData[frame].rects[i].dataText;
+ }
+ // draw timestamp and frame counter
+ context.fillStyle = 'black';
+ context.fillText((frame + 1) + "/" + renderData.length + ": " + renderData[frame].timestamp, 5, 15);
+
+ document.getElementById('active').textContent = activeData;
+
+ return true;
+}
+
+// -- Player controls --
+
+function reset(beginning) {
+ currentFrame = (beginning ? 0 : renderData.length - 1);
+ renderFrame();
+}
+
+function step(backwards) {
+ if (playing) {
+ togglePlay();
+ }
+ currentFrame += (backwards ? -1 : 1);
+ if (! renderFrame()) {
+ currentFrame -= (backwards ? -1 : 1);
+ }
+}
+
+function pause() {
+ clearInterval(timerId);
+ playing = false;
+}
+
+function togglePlay() {
+ if (playing) {
+ pause();
+ } else {
+ timerId = setInterval(function() {
+ currentFrame++;
+ if (! renderFrame()) {
+ currentFrame--;
+ togglePlay();
+ }
+ }, FRAME_INTERVAL_MS);
+ playing = true;
+ }
+}
+
+function stopPlay() {
+ if (playing) {
+ togglePlay();
+ }
+ currentFrame = 0;
+ renderFrame();
+}
diff --git a/components/aboutcheckerboard/content/aboutCheckerboard.xhtml b/components/aboutcheckerboard/content/aboutCheckerboard.xhtml
new file mode 100644
index 000000000..6a8a61896
--- /dev/null
+++ b/components/aboutcheckerboard/content/aboutCheckerboard.xhtml
@@ -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/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="viewport" content="width=device-width"/>
+ <link rel="stylesheet" href="chrome://global/content/aboutCheckerboard.css" type="text/css"/>
+ <script type="text/javascript;version=1.8" src="chrome://global/content/aboutCheckerboard.js"></script>
+ </head>
+
+ <body onload="onLoad()">
+ <p>Checkerboard recording is <span id="enabled" style="color: red">undetermined</span>.
+ <button onclick="toggleEnabled()">Toggle it!</button>.</p>
+ <p>If there are active reports in progress, you can stop and flush them by clicking here:
+ <button onclick="flushReports()">Flush active reports</button></p>
+ <table class="listing" cellspacing="0">
+ <tr>
+ <th>Most severe checkerboarding reports</th>
+ <th>Most recent checkerboarding reports</th>
+ </tr>
+ <tr>
+ <td><ul id="severe"></ul></td>
+ <td><ul id="recent"></ul></td>
+ </tr>
+ </table>
+
+ <hr/>
+
+ <div id="player">
+ <div id="controls">
+ <button onclick="reset(true)">&#171;</button><!-- rewind button -->
+ <button onclick="step(true)">&lt;</button><!-- step back button -->
+ <button onclick="togglePlay()">|| &#9654;</button><!-- pause button -->
+ <button onclick="stopPlay()">&#9744;</button><!-- stop button -->
+ <button onclick="step(false)">&gt;</button><!-- step forward button -->
+ <button onclick="reset(false)">&#187;</button><!-- forward button -->
+ </div>
+ <canvas id="canvas" width="800" height="600">Canvas not supported!</canvas>
+ <pre id="active">(Details for currently visible replay frame)</pre>
+ </div>
+
+ <hr/>
+
+ <div id="raw">
+ Raw log:<br/>
+ <textarea id="trace" rows="10"></textarea>
+ <div>
+ <input type="checkbox" id="excludePageFromZoom" onclick="loadData()"/><label for="excludePageFromZoom">Exclude page coordinates from zoom calculations</label><br/>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/components/aboutcheckerboard/jar.mn b/components/aboutcheckerboard/jar.mn
new file mode 100644
index 000000000..64d5bfc8e
--- /dev/null
+++ b/components/aboutcheckerboard/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/.
+
+toolkit.jar:
+ content/global/aboutCheckerboard.js (content/aboutCheckerboard.js)
+ content/global/aboutCheckerboard.xhtml (content/aboutCheckerboard.xhtml)
+ content/global/aboutCheckerboard.css (content/aboutCheckerboard.css)
diff --git a/components/aboutcheckerboard/moz.build b/components/aboutcheckerboard/moz.build
new file mode 100644
index 000000000..635fa39c9
--- /dev/null
+++ b/components/aboutcheckerboard/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/aboutmemory/content/aboutCompartments.xhtml b/components/aboutmemory/content/aboutCompartments.xhtml
new file mode 100644
index 000000000..83432295a
--- /dev/null
+++ b/components/aboutmemory/content/aboutCompartments.xhtml
@@ -0,0 +1,16 @@
+<?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/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>about:compartments</title>
+ <meta name="viewport" content="width=device-width"/>
+ </head>
+
+ <body>about:compartments no longer exists. The lists of compartments and
+ ghost windows can now be found in the "Other Measurements" section of
+ about:memory.</body>
+</html>
diff --git a/components/aboutmemory/content/aboutMemory.css b/components/aboutmemory/content/aboutMemory.css
new file mode 100644
index 000000000..cf4d36de8
--- /dev/null
+++ b/components/aboutmemory/content/aboutMemory.css
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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: message-box;
+}
+
+body {
+ padding: 0 2em;
+ margin: 0;
+ min-width: 45em;
+ margin: auto;
+}
+
+div.ancillary {
+ margin: 0.5em 0;
+ -moz-user-select: none;
+}
+
+div.section {
+ padding: 2em;
+ margin: 1em 0em;
+ border: 1px solid ThreeDShadow;
+ border-radius: 10px;
+ background: -moz-Field;
+}
+
+div.opsRow {
+ padding: 0.5em;
+ margin-right: 0.5em;
+ margin-top: 0.5em;
+ border: 1px solid ThreeDShadow;
+ border-radius: 10px;
+ background: -moz-Field;
+ display: inline-block;
+}
+
+div.opsRowLabel {
+ display: block;
+ margin-bottom: 0.2em;
+ font-weight: bold;
+}
+
+.opsRowLabel label {
+ margin-left: 1em;
+ font-weight: normal;
+}
+
+div.non-verbose pre.entries {
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+}
+
+h1 {
+ padding: 0;
+ margin: 0;
+ display: inline; /* allow subsequent text to the right of the heading */
+}
+
+h2 {
+ background: #ddd;
+ padding-left: .1em;
+}
+
+h3 {
+ display: inline; /* allow subsequent text to the right of the heading */
+}
+
+a.upDownArrow {
+ font-size: 130%;
+ text-decoration: none;
+ -moz-user-select: none; /* no need to include this when cutting+pasting */
+}
+
+.accuracyWarning {
+ color: #d22;
+}
+
+.badInputWarning {
+ color: #f00;
+}
+
+.treeline {
+ color: #888;
+}
+
+.mrValue {
+ font-weight: bold;
+ color: #400;
+}
+
+.mrPerc {
+}
+
+.mrSep {
+}
+
+.mrName {
+ color: #004;
+}
+
+.mrNote {
+ color: #604;
+}
+
+.hasKids {
+ cursor: pointer;
+}
+
+.hasKids:hover {
+ text-decoration: underline;
+}
+
+.noselect {
+ -moz-user-select: none; /* no need to include this when cutting+pasting */
+}
+
+.option {
+ font-size: 80%;
+ -moz-user-select: none; /* no need to include this when cutting+pasting */
+}
+
+.legend {
+ font-size: 80%;
+ -moz-user-select: none; /* no need to include this when cutting+pasting */
+}
+
+.debug {
+ font-size: 80%;
+}
+
+.hidden {
+ display: none;
+}
+
+.invalid {
+ color: #fff;
+ background-color: #f00;
+}
+
+/* Desktop-specific parts go here. */
+
+.hasKids:hover {
+ text-decoration: underline;
+}
+
diff --git a/components/aboutmemory/content/aboutMemory.js b/components/aboutmemory/content/aboutMemory.js
new file mode 100644
index 000000000..1411c3120
--- /dev/null
+++ b/components/aboutmemory/content/aboutMemory.js
@@ -0,0 +1,2013 @@
+/* -*- 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/. */
+
+// You can direct about:memory to immediately load memory reports from a file
+// by providing a file= query string. For example,
+//
+// about:memory?file=/home/username/reports.json.gz
+//
+// "file=" is not case-sensitive. We'll URI-unescape the contents of the
+// "file=" argument, and obviously the filename is case-sensitive iff you're on
+// a case-sensitive filesystem. If you specify more than one "file=" argument,
+// only the first one is used.
+
+"use strict";
+
+// ---------------------------------------------------------------------------
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var CC = Components.Constructor;
+
+const KIND_NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
+const KIND_HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
+const KIND_OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
+
+const UNITS_BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
+const UNITS_COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
+const UNITS_COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
+const UNITS_PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "nsBinaryStream",
+ () => CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"));
+XPCOMUtils.defineLazyGetter(this, "nsFile",
+ () => CC("@mozilla.org/file/local;1",
+ "nsIFile", "initWithPath"));
+XPCOMUtils.defineLazyGetter(this, "nsGzipConverter",
+ () => CC("@mozilla.org/streamconv;1?from=gzip&to=uncompressed",
+ "nsIStreamConverter"));
+
+var gMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
+ .getService(Ci.nsIMemoryReporterManager);
+
+const gPageName = 'about:memory';
+document.title = gPageName;
+
+const gUnnamedProcessStr = "Main Process";
+
+var gIsDiff = false;
+
+// ---------------------------------------------------------------------------
+
+// Forward slashes in URLs in paths are represented with backslashes to avoid
+// being mistaken for path separators. Paths/names where this hasn't been
+// undone are prefixed with "unsafe"; the rest are prefixed with "safe".
+function flipBackslashes(aUnsafeStr)
+{
+ // Save memory by only doing the replacement if it's necessary.
+ return (aUnsafeStr.indexOf('\\') === -1)
+ ? aUnsafeStr
+ : aUnsafeStr.replace(/\\/g, '/');
+}
+
+const gAssertionFailureMsgPrefix = "aboutMemory.js assertion failed: ";
+
+// This is used for things that should never fail, and indicate a defect in
+// this file if they do.
+function assert(aCond, aMsg)
+{
+ if (!aCond) {
+ reportAssertionFailure(aMsg)
+ throw new Error(gAssertionFailureMsgPrefix + aMsg);
+ }
+}
+
+// This is used for malformed input from memory reporters.
+function assertInput(aCond, aMsg)
+{
+ if (!aCond) {
+ throw new Error("Invalid memory report(s): " + aMsg);
+ }
+}
+
+function handleException(ex)
+{
+ let str = "" + ex;
+ if (str.startsWith(gAssertionFailureMsgPrefix)) {
+ // Argh, assertion failure within this file! Give up.
+ throw ex;
+ } else {
+ // File or memory reporter problem. Print a message.
+ updateMainAndFooter(str, HIDE_FOOTER, "badInputWarning");
+ }
+}
+
+function reportAssertionFailure(aMsg)
+{
+ let debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
+ if (debug.isDebugBuild) {
+ debug.assertion(aMsg, "false", "aboutMemory.js", 0);
+ }
+}
+
+function debug(x)
+{
+ let section = appendElement(document.body, 'div', 'section');
+ appendElementWithText(section, "div", "debug", JSON.stringify(x));
+}
+
+// ---------------------------------------------------------------------------
+
+function onUnload()
+{
+}
+
+// ---------------------------------------------------------------------------
+
+// The <div> holding everything but the header and footer (if they're present).
+// It's what is updated each time the page changes.
+var gMain;
+
+// The <div> holding the footer.
+var gFooter;
+
+// The "verbose" checkbox.
+var gVerbose;
+
+// The "anonymize" checkbox.
+var gAnonymize;
+
+// Values for the |aFooterAction| argument to updateTitleMainAndFooter.
+var HIDE_FOOTER = 0;
+var SHOW_FOOTER = 1;
+
+function updateTitleMainAndFooter(aTitleNote, aMsg, aFooterAction, aClassName)
+{
+ document.title = gPageName;
+ if (aTitleNote) {
+ document.title += " (" + aTitleNote + ")";
+ }
+
+ // Clear gMain by replacing it with an empty node.
+ let tmp = gMain.cloneNode(false);
+ gMain.parentNode.replaceChild(tmp, gMain);
+ gMain = tmp;
+
+ gMain.classList.remove('hidden');
+ gMain.classList.remove('verbose');
+ gMain.classList.remove('non-verbose');
+ if (gVerbose) {
+ gMain.classList.add(gVerbose.checked ? 'verbose' : 'non-verbose');
+ }
+
+ let msgElement;
+ if (aMsg) {
+ let className = "section"
+ if (aClassName) {
+ className = className + " " + aClassName;
+ }
+ msgElement = appendElementWithText(gMain, 'div', className, aMsg);
+ }
+
+ switch (aFooterAction) {
+ case HIDE_FOOTER: gFooter.classList.add('hidden'); break;
+ case SHOW_FOOTER: gFooter.classList.remove('hidden'); break;
+ default: assert(false, "bad footer action in updateTitleMainAndFooter");
+ }
+ return msgElement;
+}
+
+function updateMainAndFooter(aMsg, aFooterAction, aClassName)
+{
+ return updateTitleMainAndFooter("", aMsg, aFooterAction, aClassName);
+}
+
+function appendTextNode(aP, aText)
+{
+ let e = document.createTextNode(aText);
+ aP.appendChild(e);
+ return e;
+}
+
+function appendElement(aP, aTagName, aClassName)
+{
+ let e = document.createElement(aTagName);
+ if (aClassName) {
+ e.className = aClassName;
+ }
+ aP.appendChild(e);
+ return e;
+}
+
+function appendElementWithText(aP, aTagName, aClassName, aText)
+{
+ let e = appendElement(aP, aTagName, aClassName);
+ // Setting textContent clobbers existing children, but there are none. More
+ // importantly, it avoids creating a JS-land object for the node, saving
+ // memory.
+ e.textContent = aText;
+ return e;
+}
+
+// ---------------------------------------------------------------------------
+
+const explicitTreeDescription =
+"This tree covers explicit memory allocations by the application. It includes \
+\n\n\
+* allocations made at the operating system level (via calls to functions such as \
+VirtualAlloc, vm_allocate, and mmap), \
+\n\n\
+* allocations made at the heap allocation level (via functions such as malloc, \
+calloc, realloc, memalign, operator new, and operator new[]) that have not been \
+explicitly decommitted (i.e. evicted from memory and swap), and \
+\n\n\
+* where possible, the overhead of the heap allocator itself.\
+\n\n\
+It excludes memory that is mapped implicitly such as code and data segments, \
+and thread stacks. \
+\n\n\
+'explicit' is not guaranteed to cover every explicit allocation, but it does cover \
+most (including the entire heap), and therefore it is the single best number to \
+focus on when trying to reduce memory usage.";
+
+// ---------------------------------------------------------------------------
+
+function appendButton(aP, aTitle, aOnClick, aText, aId)
+{
+ let b = appendElementWithText(aP, "button", "", aText);
+ b.title = aTitle;
+ b.onclick = aOnClick;
+ if (aId) {
+ b.id = aId;
+ }
+ return b;
+}
+
+function appendHiddenFileInput(aP, aId, aChangeListener)
+{
+ let input = appendElementWithText(aP, "input", "hidden", "");
+ input.type = "file";
+ input.id = aId; // used in testing
+ input.addEventListener("change", aChangeListener);
+ return input;
+}
+
+function onLoad()
+{
+ // Generate the header.
+
+ let header = appendElement(document.body, "div", "ancillary");
+
+ // A hidden file input element that can be invoked when necessary.
+ let fileInput1 = appendHiddenFileInput(header, "fileInput1", function() {
+ let file = this.files[0];
+ let filename = file.mozFullPath;
+ updateAboutMemoryFromFile(filename);
+ });
+
+ // Ditto.
+ let fileInput2 =
+ appendHiddenFileInput(header, "fileInput2", function(e) {
+ let file = this.files[0];
+ // First time around, we stash a copy of the filename and reinvoke. Second
+ // time around we do the diff and display.
+ if (!this.filename1) {
+ this.filename1 = file.mozFullPath;
+
+ // e.skipClick is only true when testing -- it allows fileInput2's
+ // onchange handler to be re-called without having to go via the file
+ // picker.
+ if (!e.skipClick) {
+ this.click();
+ }
+ } else {
+ let filename1 = this.filename1;
+ delete this.filename1;
+ updateAboutMemoryFromTwoFiles(filename1, file.mozFullPath);
+ }
+ });
+
+ const CuDesc = "Measure current memory reports and show.";
+ const LdDesc = "Load memory reports from file and show.";
+ const DfDesc = "Load memory report data from two files and show the " +
+ "difference.";
+
+ const SvDesc = "Save memory reports to file.";
+
+ const GCDesc = "Do a global garbage collection.";
+ const CCDesc = "Do a cycle collection.";
+ const MMDesc = "Send three \"heap-minimize\" notifications in a " +
+ "row. Each notification triggers a global garbage " +
+ "collection followed by a cycle collection, and causes the " +
+ "process to reduce memory usage in other ways, e.g. by " +
+ "flushing various caches.";
+
+ const GCAndCCLogDesc = "Save garbage collection log and concise cycle " +
+ "collection log.\n" +
+ "WARNING: These logs may be large (>1GB).";
+ const GCAndCCAllLogDesc = "Save garbage collection log and verbose cycle " +
+ "collection log.\n" +
+ "WARNING: These logs may be large (>1GB).";
+
+ const DMDEnabledDesc = "Analyze memory reports coverage and save the " +
+ "output to the temp directory.\n";
+ const DMDDisabledDesc = "DMD is not running. Please re-start with $DMD and " +
+ "the other relevant environment variables set " +
+ "appropriately.";
+
+ let ops = appendElement(header, "div", "");
+
+ let row1 = appendElement(ops, "div", "opsRow");
+
+ let labelDiv1 =
+ appendElementWithText(row1, "div", "opsRowLabel", "Show memory reports");
+ let label1 = appendElementWithText(labelDiv1, "label", "");
+ gVerbose = appendElement(label1, "input", "");
+ gVerbose.type = "checkbox";
+ gVerbose.id = "verbose"; // used for testing
+ appendTextNode(label1, "verbose");
+
+ const kEllipsis = "\u2026";
+
+ // The "measureButton" id is used for testing.
+ appendButton(row1, CuDesc, doMeasure, "Measure", "measureButton");
+ appendButton(row1, LdDesc, () => fileInput1.click(), "Load" + kEllipsis);
+ appendButton(row1, DfDesc, () => fileInput2.click(),
+ "Load and diff" + kEllipsis);
+
+ let row2 = appendElement(ops, "div", "opsRow");
+
+ let labelDiv2 =
+ appendElementWithText(row2, "div", "opsRowLabel", "Save memory reports");
+ appendButton(row2, SvDesc, saveReportsToFile, "Measure and save" + kEllipsis);
+
+ // XXX: this isn't a great place for this checkbox, but I can't think of
+ // anywhere better.
+ let label2 = appendElementWithText(labelDiv2, "label", "");
+ gAnonymize = appendElement(label2, "input", "");
+ gAnonymize.type = "checkbox";
+ appendTextNode(label2, "anonymize");
+
+ let row3 = appendElement(ops, "div", "opsRow");
+
+ appendElementWithText(row3, "div", "opsRowLabel", "Free memory");
+ appendButton(row3, GCDesc, doGC, "GC");
+ appendButton(row3, CCDesc, doCC, "CC");
+ appendButton(row3, MMDesc, doMMU, "Minimize memory usage");
+
+ let row4 = appendElement(ops, "div", "opsRow");
+
+ appendElementWithText(row4, "div", "opsRowLabel", "Save GC & CC logs");
+ appendButton(row4, GCAndCCLogDesc,
+ saveGCLogAndConciseCCLog, "Save concise", 'saveLogsConcise');
+ appendButton(row4, GCAndCCAllLogDesc,
+ saveGCLogAndVerboseCCLog, "Save verbose", 'saveLogsVerbose');
+
+ // Generate the main div, where content ("section" divs) will go. It's
+ // hidden at first.
+
+ gMain = appendElement(document.body, 'div', '');
+ gMain.id = 'mainDiv';
+
+ // Generate the footer. It's hidden at first.
+
+ gFooter = appendElement(document.body, 'div', 'ancillary hidden');
+
+ let a = appendElementWithText(gFooter, "a", "option",
+ "Troubleshooting information");
+ a.href = "about:support";
+
+ let legendText1 = "Click on a non-leaf node in a tree to expand ('++') " +
+ "or collapse ('--') its children.";
+ let legendText2 = "Hover the pointer over the name of a memory report " +
+ "to see a description of what it measures.";
+
+ appendElementWithText(gFooter, "div", "legend", legendText1);
+ appendElementWithText(gFooter, "div", "legend hiddenOnMobile", legendText2);
+
+ // See if we're loading from a file. (Because about:memory is a non-standard
+ // URL, location.search is undefined, so we have to use location.href
+ // instead.)
+ let search = location.href.split('?')[1];
+ if (search) {
+ let searchSplit = search.split('&');
+ for (let i = 0; i < searchSplit.length; i++) {
+ if (searchSplit[i].toLowerCase().startsWith('file=')) {
+ let filename = searchSplit[i].substring('file='.length);
+ updateAboutMemoryFromFile(decodeURIComponent(filename));
+ return;
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+function doGC()
+{
+ Services.obs.notifyObservers(null, "child-gc-request", null);
+ Cu.forceGC();
+ updateMainAndFooter("Garbage collection completed", HIDE_FOOTER);
+}
+
+function doCC()
+{
+ Services.obs.notifyObservers(null, "child-cc-request", null);
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .cycleCollect();
+ updateMainAndFooter("Cycle collection completed", HIDE_FOOTER);
+}
+
+function doMMU()
+{
+ Services.obs.notifyObservers(null, "child-mmu-request", null);
+ gMgr.minimizeMemoryUsage(
+ () => updateMainAndFooter("Memory minimization completed", HIDE_FOOTER));
+}
+
+function doMeasure()
+{
+ updateAboutMemoryFromReporters();
+}
+
+function saveGCLogAndConciseCCLog()
+{
+ dumpGCLogAndCCLog(false);
+}
+
+function saveGCLogAndVerboseCCLog()
+{
+ dumpGCLogAndCCLog(true);
+}
+
+function doDMD()
+{
+ updateMainAndFooter("Saving memory reports and DMD output...", HIDE_FOOTER);
+ try {
+ let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
+ .getService(Ci.nsIMemoryInfoDumper);
+
+ dumper.dumpMemoryInfoToTempDir(/* identifier = */ "",
+ gAnonymize.checked,
+ /* minimize = */ false);
+ updateMainAndFooter("Saved memory reports and DMD reports analysis " +
+ "to the temp directory",
+ HIDE_FOOTER);
+ } catch (ex) {
+ updateMainAndFooter(ex.toString(), HIDE_FOOTER);
+ }
+}
+
+function dumpGCLogAndCCLog(aVerbose)
+{
+ let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
+ .getService(Ci.nsIMemoryInfoDumper);
+
+ let inProgress = updateMainAndFooter("Saving logs...", HIDE_FOOTER);
+ let section = appendElement(gMain, 'div', "section");
+
+ function displayInfo(gcLog, ccLog, isParent) {
+ appendElementWithText(section, 'div', "",
+ "Saved GC log to " + gcLog.path);
+
+ let ccLogType = aVerbose ? "verbose" : "concise";
+ appendElementWithText(section, 'div', "",
+ "Saved " + ccLogType + " CC log to " + ccLog.path);
+ }
+
+ dumper.dumpGCAndCCLogsToFile("", aVerbose, /* dumpChildProcesses = */ true,
+ { onDump: displayInfo,
+ onFinish: function() {
+ inProgress.remove();
+ }
+ });
+}
+
+/**
+ * Top-level function that does the work of generating the page from the memory
+ * reporters.
+ */
+function updateAboutMemoryFromReporters()
+{
+ updateMainAndFooter("Measuring...", HIDE_FOOTER);
+
+ try {
+ let processLiveMemoryReports =
+ function(aHandleReport, aDisplayReports) {
+ let handleReport = function(aProcess, aUnsafePath, aKind, aUnits,
+ aAmount, aDescription) {
+ aHandleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
+ aDescription, /* presence = */ undefined);
+ }
+
+ let displayReportsAndFooter = function() {
+ updateTitleMainAndFooter("live measurement", "", SHOW_FOOTER);
+ aDisplayReports();
+ }
+
+ gMgr.getReports(handleReport, null, displayReportsAndFooter, null,
+ gAnonymize.checked);
+ }
+
+ // Process the reports from the live memory reporters.
+ appendAboutMemoryMain(processLiveMemoryReports,
+ gMgr.hasMozMallocUsableSize);
+
+ } catch (ex) {
+ handleException(ex);
+ }
+}
+
+// Increment this if the JSON format changes.
+//
+var gCurrentFileFormatVersion = 1;
+
+
+/**
+ * Parse a string as JSON and extract the |memory_report| property if it has
+ * one, which indicates the string is from a crash dump.
+ *
+ * @param aStr
+ * The string.
+ * @return The extracted object.
+ */
+function parseAndUnwrapIfCrashDump(aStr) {
+ let obj = JSON.parse(aStr);
+ if (obj.memory_report !== undefined) {
+ // It looks like a crash dump. The memory reports should be in the
+ // |memory_report| property.
+ obj = obj.memory_report;
+ }
+ return obj;
+}
+
+/**
+ * Populate about:memory using the data in the given JSON object.
+ *
+ * @param aObj
+ * An object that (hopefully!) conforms to the JSON schema used by
+ * nsIMemoryInfoDumper.
+ */
+function updateAboutMemoryFromJSONObject(aObj)
+{
+ try {
+ assertInput(aObj.version === gCurrentFileFormatVersion,
+ "data version number missing or doesn't match");
+ assertInput(aObj.hasMozMallocUsableSize !== undefined,
+ "missing 'hasMozMallocUsableSize' property");
+ assertInput(aObj.reports && aObj.reports instanceof Array,
+ "missing or non-array 'reports' property");
+
+ let processMemoryReportsFromFile =
+ function(aHandleReport, aDisplayReports) {
+ for (let i = 0; i < aObj.reports.length; i++) {
+ let r = aObj.reports[i];
+
+ // A hack: for a brief time (late in the FF26 and early in the FF27
+ // cycle) we were dumping memory report files that contained reports
+ // whose path began with "redundant/". Such reports were ignored by
+ // about:memory. These reports are no longer produced, but some older
+ // builds are still floating around and producing files that contain
+ // them, so we need to still handle them (i.e. ignore them). This hack
+ // can be removed once FF26 and associated products (e.g. B2G 1.2) are
+ // no longer in common use.
+ if (!r.path.startsWith("redundant/")) {
+ aHandleReport(r.process, r.path, r.kind, r.units, r.amount,
+ r.description, r._presence);
+ }
+ }
+ aDisplayReports();
+ }
+ appendAboutMemoryMain(processMemoryReportsFromFile,
+ aObj.hasMozMallocUsableSize);
+ } catch (ex) {
+ handleException(ex);
+ }
+}
+
+/**
+ * Populate about:memory using the data in the given JSON string.
+ *
+ * @param aStr
+ * A string containing JSON data conforming to the schema used by
+ * nsIMemoryReporterManager::dumpReports.
+ */
+function updateAboutMemoryFromJSONString(aStr)
+{
+ try {
+ let obj = parseAndUnwrapIfCrashDump(aStr);
+ updateAboutMemoryFromJSONObject(obj);
+ } catch (ex) {
+ handleException(ex);
+ }
+}
+
+/**
+ * Loads the contents of a file into a string and passes that to a callback.
+ *
+ * @param aFilename
+ * The name of the file being read from.
+ * @param aTitleNote
+ * A description to put in the page title upon completion.
+ * @param aFn
+ * The function to call and pass the read string to upon completion.
+ */
+function loadMemoryReportsFromFile(aFilename, aTitleNote, aFn)
+{
+ updateMainAndFooter("Loading...", HIDE_FOOTER);
+
+ try {
+ let reader = new FileReader();
+ reader.onerror = () => { throw new Error("FileReader.onerror"); };
+ reader.onabort = () => { throw new Error("FileReader.onabort"); };
+ reader.onload = (aEvent) => {
+ // Clear "Loading..." from above.
+ updateTitleMainAndFooter(aTitleNote, "", SHOW_FOOTER);
+ aFn(aEvent.target.result);
+ };
+
+ // If it doesn't have a .gz suffix, read it as a (legacy) ungzipped file.
+ if (!aFilename.endsWith(".gz")) {
+ reader.readAsText(File.createFromFileName(aFilename));
+ return;
+ }
+
+ // Read compressed gzip file.
+ let converter = new nsGzipConverter();
+ converter.asyncConvertData("gzip", "uncompressed", {
+ data: [],
+ onStartRequest: function(aR, aC) {},
+ onDataAvailable: function(aR, aC, aStream, aO, aCount) {
+ let bi = new nsBinaryStream(aStream);
+ this.data.push(bi.readBytes(aCount));
+ },
+ onStopRequest: function(aR, aC, aStatusCode) {
+ try {
+ if (!Components.isSuccessCode(aStatusCode)) {
+ throw new Components.Exception("Error while reading gzip file", aStatusCode);
+ }
+ reader.readAsText(new Blob(this.data));
+ } catch (ex) {
+ handleException(ex);
+ }
+ }
+ }, null);
+
+ let file = new nsFile(aFilename);
+ let fileChan = NetUtil.newChannel({
+ uri: Services.io.newFileURI(file),
+ loadUsingSystemPrincipal: true
+ });
+ fileChan.asyncOpen2(converter);
+
+ } catch (ex) {
+ handleException(ex);
+ }
+}
+
+/**
+ * Like updateAboutMemoryFromReporters(), but gets its data from a file instead
+ * of the memory reporters.
+ *
+ * @param aFilename
+ * The name of the file being read from. The expected format of the
+ * file's contents is described in a comment in nsIMemoryInfoDumper.idl.
+ */
+function updateAboutMemoryFromFile(aFilename)
+{
+ loadMemoryReportsFromFile(aFilename, /* title note */ aFilename,
+ updateAboutMemoryFromJSONString);
+}
+
+/**
+ * Like updateAboutMemoryFromFile(), but gets its data from a two files and
+ * diffs them.
+ *
+ * @param aFilename1
+ * The name of the first file being read from.
+ * @param aFilename2
+ * The name of the first file being read from.
+ */
+function updateAboutMemoryFromTwoFiles(aFilename1, aFilename2)
+{
+ let titleNote = "diff of " + aFilename1 + " and " + aFilename2;
+ loadMemoryReportsFromFile(aFilename1, titleNote, function(aStr1) {
+ loadMemoryReportsFromFile(aFilename2, titleNote, function(aStr2) {
+ try {
+ let obj1 = parseAndUnwrapIfCrashDump(aStr1);
+ let obj2 = parseAndUnwrapIfCrashDump(aStr2);
+ gIsDiff = true;
+ updateAboutMemoryFromJSONObject(diffJSONObjects(obj1, obj2));
+ gIsDiff = false;
+ } catch (ex) {
+ handleException(ex);
+ }
+ });
+ });
+}
+
+// ---------------------------------------------------------------------------
+
+// Something unlikely to appear in a process name.
+var kProcessPathSep = "^:^:^";
+
+// Short for "diff report".
+function DReport(aKind, aUnits, aAmount, aDescription, aNMerged, aPresence)
+{
+ this._kind = aKind;
+ this._units = aUnits;
+ this._amount = aAmount;
+ this._description = aDescription;
+ this._nMerged = aNMerged;
+ if (aPresence !== undefined) {
+ this._presence = aPresence;
+ }
+}
+
+DReport.prototype = {
+ assertCompatible: function(aKind, aUnits)
+ {
+ assert(this._kind == aKind, "Mismatched kinds");
+ assert(this._units == aUnits, "Mismatched units");
+
+ // We don't check that the "description" properties match. This is because
+ // on Linux we can get cases where the paths are the same but the
+ // descriptions differ, like this:
+ //
+ // "path": "size/other-files/icon-theme.cache/[r--p]",
+ // "description": "/usr/share/icons/gnome/icon-theme.cache (read-only, not executable, private)"
+ //
+ // "path": "size/other-files/icon-theme.cache/[r--p]"
+ // "description": "/usr/share/icons/hicolor/icon-theme.cache (read-only, not executable, private)"
+ //
+ // In those cases, we just use the description from the first-encountered
+ // one, which is what about:memory also does.
+ // (Note: reports with those paths are no longer generated, but allowing
+ // the descriptions to differ seems reasonable.)
+ },
+
+ merge: function(aJr) {
+ this.assertCompatible(aJr.kind, aJr.units);
+ this._amount += aJr.amount;
+ this._nMerged++;
+ },
+
+ toJSON: function(aProcess, aPath, aAmount) {
+ return {
+ process: aProcess,
+ path: aPath,
+ kind: this._kind,
+ units: this._units,
+ amount: aAmount,
+ description: this._description,
+ _presence: this._presence
+ };
+ }
+};
+
+// Constants that indicate if a DReport was present only in one of the data
+// sets, or had to be added for balance.
+DReport.PRESENT_IN_FIRST_ONLY = 1;
+DReport.PRESENT_IN_SECOND_ONLY = 2;
+DReport.ADDED_FOR_BALANCE = 3;
+
+/**
+ * Make a report map, which has combined path+process strings for keys, and
+ * DReport objects for values.
+ *
+ * @param aJSONReports
+ * The |reports| field of a JSON object.
+ * @return The constructed report map.
+ */
+function makeDReportMap(aJSONReports)
+{
+ let dreportMap = {};
+ for (let i = 0; i < aJSONReports.length; i++) {
+ let jr = aJSONReports[i];
+
+ assert(jr.process !== undefined, "Missing process");
+ assert(jr.path !== undefined, "Missing path");
+ assert(jr.kind !== undefined, "Missing kind");
+ assert(jr.units !== undefined, "Missing units");
+ assert(jr.amount !== undefined, "Missing amount");
+ assert(jr.description !== undefined, "Missing description");
+
+ // Strip out some non-deterministic stuff that prevents clean diffs.
+ // Ideally the memory reports themselves would contain information about
+ // which parts of the the process and path need to be stripped -- saving us
+ // from hardwiring knowledge of specific reporters here -- but we have no
+ // mechanism for that. (Any future redesign of how memory reporters work
+ // should include such a mechanism.)
+
+ // Strip PIDs:
+ // - pid 123
+ // - pid=123
+ let pidRegex = /pid([ =])\d+/g;
+ let pidSubst = "pid$1NNN";
+ let process = jr.process.replace(pidRegex, pidSubst);
+ let path = jr.path.replace(pidRegex, pidSubst);
+
+ // Strip addresses:
+ // - .../js-zone(0x12345678)/...
+ // - .../zone(0x12345678)/...
+ // - .../worker(<URL>, 0x12345678)/...
+ path = path.replace(/zone\(0x[0-9A-Fa-f]+\)\//, "zone(0xNNN)/");
+ path = path.replace(/\/worker\((.+), 0x[0-9A-Fa-f]+\)\//,
+ "/worker($1, 0xNNN)/");
+
+ // Strip top window IDs:
+ // - explicit/window-objects/top(<URL>, id=123)/...
+ path = path.replace(/^(explicit\/window-objects\/top\(.*, id=)\d+\)/,
+ "$1NNN)");
+
+ // Strip null principal UUIDs (but not other UUIDs, because they may be
+ // deterministic, such as those used by add-ons).
+ path = path.replace(
+ /moz-nullprincipal:{........-....-....-....-............}/g,
+ "moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN}");
+
+ // Normalize omni.ja! paths.
+ path = path.replace(/jar:file:\\\\\\(.+)\\omni.ja!/,
+ "jar:file:\\\\\\...\\omni.ja!");
+
+ let processPath = process + kProcessPathSep + path;
+ let rOld = dreportMap[processPath];
+ if (rOld === undefined) {
+ dreportMap[processPath] =
+ new DReport(jr.kind, jr.units, jr.amount, jr.description, 1, undefined);
+ } else {
+ rOld.merge(jr);
+ }
+ }
+ return dreportMap;
+}
+
+// Return a new dreportMap which is the diff of two dreportMaps. Empties
+// aDReportMap2 along the way.
+function diffDReportMaps(aDReportMap1, aDReportMap2)
+{
+ let result = {};
+
+ for (let processPath in aDReportMap1) {
+ let r1 = aDReportMap1[processPath];
+ let r2 = aDReportMap2[processPath];
+ let r2_amount, r2_nMerged;
+ let presence;
+ if (r2 !== undefined) {
+ r1.assertCompatible(r2._kind, r2._units);
+ r2_amount = r2._amount;
+ r2_nMerged = r2._nMerged;
+ delete aDReportMap2[processPath];
+ presence = undefined; // represents that it's present in both
+ } else {
+ r2_amount = 0;
+ r2_nMerged = 0;
+ presence = DReport.PRESENT_IN_FIRST_ONLY;
+ }
+ result[processPath] =
+ new DReport(r1._kind, r1._units, r2_amount - r1._amount, r1._description,
+ Math.max(r1._nMerged, r2_nMerged), presence);
+ }
+
+ for (let processPath in aDReportMap2) {
+ let r2 = aDReportMap2[processPath];
+ result[processPath] = new DReport(r2._kind, r2._units, r2._amount,
+ r2._description, r2._nMerged,
+ DReport.PRESENT_IN_SECOND_ONLY);
+ }
+
+ return result;
+}
+
+function makeJSONReports(aDReportMap)
+{
+ let reports = [];
+ for (let processPath in aDReportMap) {
+ let r = aDReportMap[processPath];
+ if (r._amount !== 0) {
+ // If _nMerged > 1, we give the full (aggregated) amount in the first
+ // copy, and then use amount=0 in the remainder. When viewed in
+ // about:memory, this shows up as an entry with a "[2]"-style suffix
+ // and the correct amount.
+ let split = processPath.split(kProcessPathSep);
+ assert(split.length >= 2);
+ let process = split.shift();
+ let path = split.join();
+ reports.push(r.toJSON(process, path, r._amount));
+ for (let i = 1; i < r._nMerged; i++) {
+ reports.push(r.toJSON(process, path, 0));
+ }
+ }
+ }
+
+ return reports;
+}
+
+// Diff two JSON objects holding memory reports.
+function diffJSONObjects(aJson1, aJson2)
+{
+ function simpleProp(aProp)
+ {
+ assert(aJson1[aProp] !== undefined && aJson1[aProp] === aJson2[aProp],
+ aProp + " properties don't match");
+ return aJson1[aProp];
+ }
+
+ return {
+ version: simpleProp("version"),
+
+ hasMozMallocUsableSize: simpleProp("hasMozMallocUsableSize"),
+
+ reports: makeJSONReports(diffDReportMaps(makeDReportMap(aJson1.reports),
+ makeDReportMap(aJson2.reports)))
+ };
+}
+
+// ---------------------------------------------------------------------------
+
+// |PColl| is short for "process collection".
+function PColl()
+{
+ this._trees = {};
+ this._degenerates = {};
+ this._heapTotal = 0;
+}
+
+/**
+ * Processes reports (whether from reporters or from a file) and append the
+ * main part of the page.
+ *
+ * @param aProcessReports
+ * Function that extracts the memory reports from the reporters or from
+ * file.
+ * @param aHasMozMallocUsableSize
+ * Boolean indicating if moz_malloc_usable_size works.
+ */
+function appendAboutMemoryMain(aProcessReports, aHasMozMallocUsableSize)
+{
+ let pcollsByProcess = {};
+
+ function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
+ aDescription, aPresence)
+ {
+ if (aUnsafePath.startsWith("explicit/")) {
+ assertInput(aKind === KIND_HEAP || aKind === KIND_NONHEAP,
+ "bad explicit kind");
+ assertInput(aUnits === UNITS_BYTES, "bad explicit units");
+ }
+
+ assert(aPresence === undefined ||
+ aPresence == DReport.PRESENT_IN_FIRST_ONLY ||
+ aPresence == DReport.PRESENT_IN_SECOND_ONLY,
+ "bad presence");
+
+ let process = aProcess === "" ? gUnnamedProcessStr : aProcess;
+ let unsafeNames = aUnsafePath.split('/');
+ let unsafeName0 = unsafeNames[0];
+ let isDegenerate = unsafeNames.length === 1;
+
+ // Get the PColl table for the process, creating it if necessary.
+ let pcoll = pcollsByProcess[process];
+ if (!pcollsByProcess[process]) {
+ pcoll = pcollsByProcess[process] = new PColl();
+ }
+
+ // Get the root node, creating it if necessary.
+ let psubcoll = isDegenerate ? pcoll._degenerates : pcoll._trees;
+ let t = psubcoll[unsafeName0];
+ if (!t) {
+ t = psubcoll[unsafeName0] =
+ new TreeNode(unsafeName0, aUnits, isDegenerate);
+ }
+
+ if (!isDegenerate) {
+ // Add any missing nodes in the tree implied by aUnsafePath, and fill in
+ // the properties that we can with a top-down traversal.
+ for (let i = 1; i < unsafeNames.length; i++) {
+ let unsafeName = unsafeNames[i];
+ let u = t.findKid(unsafeName);
+ if (!u) {
+ u = new TreeNode(unsafeName, aUnits, isDegenerate);
+ if (!t._kids) {
+ t._kids = [];
+ }
+ t._kids.push(u);
+ }
+ t = u;
+ }
+
+ // Update the heap total if necessary.
+ if (unsafeName0 === "explicit" && aKind == KIND_HEAP) {
+ pcollsByProcess[process]._heapTotal += aAmount;
+ }
+ }
+
+ if (t._amount) {
+ // Duplicate! Sum the values and mark it as a dup.
+ t._amount += aAmount;
+ t._nMerged = t._nMerged ? t._nMerged + 1 : 2;
+ assert(t._presence === aPresence, "presence mismatch");
+ } else {
+ // New leaf node. Fill in extra node details from the report.
+ t._amount = aAmount;
+ t._description = aDescription;
+ if (aPresence !== undefined) {
+ t._presence = aPresence;
+ }
+ }
+ }
+
+ function displayReports()
+ {
+ // Sort the processes.
+ let processes = Object.keys(pcollsByProcess);
+ processes.sort(function(aProcessA, aProcessB) {
+ assert(aProcessA != aProcessB,
+ "Elements of Object.keys() should be unique, but " +
+ "saw duplicate '" + aProcessA + "' elem.");
+
+ // Always put the main process first.
+ if (aProcessA == gUnnamedProcessStr) {
+ return -1;
+ }
+ if (aProcessB == gUnnamedProcessStr) {
+ return 1;
+ }
+
+ // Then sort by resident size.
+ let nodeA = pcollsByProcess[aProcessA]._degenerates['resident'];
+ let nodeB = pcollsByProcess[aProcessB]._degenerates['resident'];
+ let residentA = nodeA ? nodeA._amount : -1;
+ let residentB = nodeB ? nodeB._amount : -1;
+
+ if (residentA > residentB) {
+ return -1;
+ }
+ if (residentA < residentB) {
+ return 1;
+ }
+
+ // Then sort by process name.
+ if (aProcessA < aProcessB) {
+ return -1;
+ }
+ if (aProcessA > aProcessB) {
+ return 1;
+ }
+
+ return 0;
+ });
+
+ // Generate output for each process.
+ for (let i = 0; i < processes.length; i++) {
+ let process = processes[i];
+ let section = appendElement(gMain, 'div', 'section');
+
+ appendProcessAboutMemoryElements(section, i, process,
+ pcollsByProcess[process]._trees,
+ pcollsByProcess[process]._degenerates,
+ pcollsByProcess[process]._heapTotal,
+ aHasMozMallocUsableSize);
+ }
+ }
+
+ aProcessReports(handleReport, displayReports);
+}
+
+// ---------------------------------------------------------------------------
+
+// There are two kinds of TreeNode.
+// - Leaf TreeNodes correspond to reports.
+// - Non-leaf TreeNodes are just scaffolding nodes for the tree; their values
+// are derived from their children.
+// Some trees are "degenerate", i.e. they contain a single node, i.e. they
+// correspond to a report whose path has no '/' separators.
+function TreeNode(aUnsafeName, aUnits, aIsDegenerate)
+{
+ this._units = aUnits;
+ this._unsafeName = aUnsafeName;
+ if (aIsDegenerate) {
+ this._isDegenerate = true;
+ }
+
+ // Leaf TreeNodes have these properties added immediately after construction:
+ // - _amount
+ // - _description
+ // - _nMerged (only defined if > 1)
+ // - _presence (only defined if value is PRESENT_IN_{FIRST,SECOND}_ONLY)
+ //
+ // Non-leaf TreeNodes have these properties added later:
+ // - _kids
+ // - _amount
+ // - _description
+ // - _hideKids (only defined if true)
+ // - _maxAbsDescendant (on-demand, only when gIsDiff is set)
+}
+
+TreeNode.prototype = {
+ findKid: function(aUnsafeName) {
+ if (this._kids) {
+ for (let i = 0; i < this._kids.length; i++) {
+ if (this._kids[i]._unsafeName === aUnsafeName) {
+ return this._kids[i];
+ }
+ }
+ }
+ return undefined;
+ },
+
+ // When gIsDiff is false, tree operations -- sorting and determining if a
+ // sub-tree is significant -- are straightforward. But when gIsDiff is true,
+ // the combination of positive and negative values within a tree complicates
+ // things. So for a non-leaf node, instead of just looking at _amount, we
+ // instead look at the maximum absolute value of the node and all of its
+ // descendants.
+ maxAbsDescendant: function() {
+ if (!this._kids) {
+ // No kids? Just return the absolute value of the amount.
+ return Math.abs(this._amount);
+ }
+
+ if ('_maxAbsDescendant' in this) {
+ // We've computed this before? Return the saved value.
+ return this._maxAbsDescendant;
+ }
+
+ // Compute the maximum absolute value of all descendants.
+ let max = Math.abs(this._amount);
+ for (let i = 0; i < this._kids.length; i++) {
+ max = Math.max(max, this._kids[i].maxAbsDescendant());
+ }
+ this._maxAbsDescendant = max;
+ return max;
+ },
+
+ toString: function() {
+ switch (this._units) {
+ case UNITS_BYTES: return formatBytes(this._amount);
+ case UNITS_COUNT:
+ case UNITS_COUNT_CUMULATIVE: return formatInt(this._amount);
+ case UNITS_PERCENTAGE: return formatPercentage(this._amount);
+ default:
+ throw "Invalid memory report(s): bad units in TreeNode.toString";
+ }
+ }
+};
+
+// Sort TreeNodes first by size, then by name. The latter is important for the
+// about:memory tests, which need a predictable ordering of reporters which
+// have the same amount.
+TreeNode.compareAmounts = function(aA, aB) {
+ let a, b;
+ if (gIsDiff) {
+ a = aA.maxAbsDescendant();
+ b = aB.maxAbsDescendant();
+ } else {
+ a = aA._amount;
+ b = aB._amount;
+ }
+ if (a > b) {
+ return -1;
+ }
+ if (a < b) {
+ return 1;
+ }
+ return TreeNode.compareUnsafeNames(aA, aB);
+};
+
+TreeNode.compareUnsafeNames = function(aA, aB) {
+ return aA._unsafeName.localeCompare(aB._unsafeName);
+};
+
+
+/**
+ * Fill in the remaining properties for the specified tree in a bottom-up
+ * fashion.
+ *
+ * @param aRoot
+ * The tree root.
+ */
+function fillInTree(aRoot)
+{
+ // Fill in the remaining properties bottom-up.
+ function fillInNonLeafNodes(aT)
+ {
+ if (!aT._kids) {
+ // Leaf node. Has already been filled in.
+
+ } else if (aT._kids.length === 1 && aT != aRoot) {
+ // Non-root, non-leaf node with one child. Merge the child with the node
+ // to avoid redundant entries.
+ let kid = aT._kids[0];
+ let kidBytes = fillInNonLeafNodes(kid);
+ aT._unsafeName += '/' + kid._unsafeName;
+ if (kid._kids) {
+ aT._kids = kid._kids;
+ } else {
+ delete aT._kids;
+ }
+ aT._amount = kidBytes;
+ aT._description = kid._description;
+ if (kid._nMerged !== undefined) {
+ aT._nMerged = kid._nMerged
+ }
+ assert(!aT._hideKids && !kid._hideKids, "_hideKids set when merging");
+
+ } else {
+ // Non-leaf node with multiple children. Derive its _amount and
+ // _description entirely from its children...
+ let kidsBytes = 0;
+ for (let i = 0; i < aT._kids.length; i++) {
+ kidsBytes += fillInNonLeafNodes(aT._kids[i]);
+ }
+
+ // ... except in one special case. When diffing two memory report sets,
+ // if one set has a node with children and the other has the same node
+ // but without children -- e.g. the first has "a/b/c" and "a/b/d", but
+ // the second only has "a/b" -- we need to add a fake node "a/b/(fake)"
+ // to the second to make the trees comparable. It's ugly, but it works.
+ if (aT._amount !== undefined &&
+ (aT._presence === DReport.PRESENT_IN_FIRST_ONLY ||
+ aT._presence === DReport.PRESENT_IN_SECOND_ONLY)) {
+ aT._amount += kidsBytes;
+ let fake = new TreeNode('(fake child)', aT._units);
+ fake._presence = DReport.ADDED_FOR_BALANCE;
+ fake._amount = aT._amount - kidsBytes;
+ aT._kids.push(fake);
+ delete aT._presence;
+ } else {
+ assert(aT._amount === undefined,
+ "_amount already set for non-leaf node")
+ aT._amount = kidsBytes;
+ }
+ aT._description = "The sum of all entries below this one.";
+ }
+ return aT._amount;
+ }
+
+ // cannotMerge is set because don't want to merge into a tree's root node.
+ fillInNonLeafNodes(aRoot);
+}
+
+/**
+ * Compute the "heap-unclassified" value and insert it into the "explicit"
+ * tree.
+ *
+ * @param aT
+ * The "explicit" tree.
+ * @param aHeapAllocatedNode
+ * The "heap-allocated" tree node.
+ * @param aHeapTotal
+ * The sum of all explicit HEAP reports for this process.
+ * @return A boolean indicating if "heap-allocated" is known for the process.
+ */
+function addHeapUnclassifiedNode(aT, aHeapAllocatedNode, aHeapTotal)
+{
+ if (aHeapAllocatedNode === undefined)
+ return false;
+
+ if (aT.findKid("heap-unclassified")) {
+ // heap-unclassified was already calculated, there's nothing left to do.
+ // This can happen when memory reports are exported from areweslimyet.com.
+ return true;
+ }
+
+ assert(aHeapAllocatedNode._isDegenerate, "heap-allocated is not degenerate");
+ let heapAllocatedBytes = aHeapAllocatedNode._amount;
+ let heapUnclassifiedT = new TreeNode("heap-unclassified", UNITS_BYTES);
+ heapUnclassifiedT._amount = heapAllocatedBytes - aHeapTotal;
+ heapUnclassifiedT._description =
+ "Memory not classified by a more specific report. This includes " +
+ "slop bytes due to internal fragmentation in the heap allocator " +
+ "(caused when the allocator rounds up request sizes).";
+ aT._kids.push(heapUnclassifiedT);
+ aT._amount += heapUnclassifiedT._amount;
+ return true;
+}
+
+/**
+ * Sort all kid nodes from largest to smallest, and insert aggregate nodes
+ * where appropriate.
+ *
+ * @param aTotalBytes
+ * The size of the tree's root node.
+ * @param aT
+ * The tree.
+ */
+function sortTreeAndInsertAggregateNodes(aTotalBytes, aT)
+{
+ const kSignificanceThresholdPerc = 1;
+
+ function isInsignificant(aT)
+ {
+ if (gVerbose.checked)
+ return false;
+
+ let perc = gIsDiff
+ ? 100 * aT.maxAbsDescendant() / Math.abs(aTotalBytes)
+ : 100 * aT._amount / aTotalBytes;
+ return perc < kSignificanceThresholdPerc;
+ }
+
+ if (!aT._kids) {
+ return;
+ }
+
+ aT._kids.sort(TreeNode.compareAmounts);
+
+ // If the first child is insignificant, they all are, and there's no point
+ // creating an aggregate node that lacks siblings. Just set the parent's
+ // _hideKids property and process all children.
+ if (isInsignificant(aT._kids[0])) {
+ aT._hideKids = true;
+ for (let i = 0; i < aT._kids.length; i++) {
+ sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
+ }
+ return;
+ }
+
+ // Look at all children except the last one.
+ let i;
+ for (i = 0; i < aT._kids.length - 1; i++) {
+ if (isInsignificant(aT._kids[i])) {
+ // This child is below the significance threshold. If there are other
+ // (smaller) children remaining, move them under an aggregate node.
+ let i0 = i;
+ let nAgg = aT._kids.length - i0;
+ // Create an aggregate node. Inherit units from the parent; everything
+ // in the tree should have the same units anyway (we test this later).
+ let aggT = new TreeNode("(" + nAgg + " tiny)", aT._units);
+ aggT._kids = [];
+ let aggBytes = 0;
+ for ( ; i < aT._kids.length; i++) {
+ aggBytes += aT._kids[i]._amount;
+ aggT._kids.push(aT._kids[i]);
+ }
+ aggT._hideKids = true;
+ aggT._amount = aggBytes;
+ aggT._description =
+ nAgg + " sub-trees that are below the " + kSignificanceThresholdPerc +
+ "% significance threshold.";
+ aT._kids.splice(i0, nAgg, aggT);
+ aT._kids.sort(TreeNode.compareAmounts);
+
+ // Process the moved children.
+ for (i = 0; i < aggT._kids.length; i++) {
+ sortTreeAndInsertAggregateNodes(aTotalBytes, aggT._kids[i]);
+ }
+ return;
+ }
+
+ sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
+ }
+
+ // The first n-1 children were significant. Don't consider if the last child
+ // is significant; there's no point creating an aggregate node that only has
+ // one child. Just process it.
+ sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
+}
+
+// Global variable indicating if we've seen any invalid values for this
+// process; it holds the unsafePaths of any such reports. It is reset for
+// each new process.
+var gUnsafePathsWithInvalidValuesForThisProcess = [];
+
+function appendWarningElements(aP, aHasKnownHeapAllocated,
+ aHasMozMallocUsableSize)
+{
+ if (!aHasKnownHeapAllocated && !aHasMozMallocUsableSize) {
+ appendElementWithText(aP, "p", "",
+ "WARNING: the 'heap-allocated' memory reporter and the " +
+ "moz_malloc_usable_size() function do not work for this platform " +
+ "and/or configuration. This means that 'heap-unclassified' is not " +
+ "shown and the 'explicit' tree shows much less memory than it should.\n\n");
+
+ } else if (!aHasKnownHeapAllocated) {
+ appendElementWithText(aP, "p", "",
+ "WARNING: the 'heap-allocated' memory reporter does not work for this " +
+ "platform and/or configuration. This means that 'heap-unclassified' " +
+ "is not shown and the 'explicit' tree shows less memory than it should.\n\n");
+
+ } else if (!aHasMozMallocUsableSize) {
+ appendElementWithText(aP, "p", "",
+ "WARNING: the moz_malloc_usable_size() function does not work for " +
+ "this platform and/or configuration. This means that much of the " +
+ "heap-allocated memory is not measured by individual memory reporters " +
+ "and so will fall under 'heap-unclassified'.\n\n");
+ }
+
+ if (gUnsafePathsWithInvalidValuesForThisProcess.length > 0) {
+ let div = appendElement(aP, "div");
+ appendElementWithText(div, "p", "",
+ "WARNING: the following values are negative or unreasonably large.\n");
+
+ let ul = appendElement(div, "ul");
+ for (let i = 0;
+ i < gUnsafePathsWithInvalidValuesForThisProcess.length;
+ i++)
+ {
+ appendTextNode(ul, " ");
+ appendElementWithText(ul, "li", "",
+ flipBackslashes(gUnsafePathsWithInvalidValuesForThisProcess[i]) + "\n");
+ }
+
+ appendElementWithText(div, "p", "",
+ "This indicates a defect in one or more memory reporters. The " +
+ "invalid values are highlighted.\n\n");
+ gUnsafePathsWithInvalidValuesForThisProcess = []; // reset for the next process
+ }
+}
+
+/**
+ * Appends the about:memory elements for a single process.
+ *
+ * @param aP
+ * The parent DOM node.
+ * @param aN
+ * The number of the process, starting at 0.
+ * @param aProcess
+ * The name of the process.
+ * @param aTrees
+ * The table of non-degenerate trees for this process.
+ * @param aDegenerates
+ * The table of degenerate trees for this process.
+ * @param aHasMozMallocUsableSize
+ * Boolean indicating if moz_malloc_usable_size works.
+ * @return The generated text.
+ */
+function appendProcessAboutMemoryElements(aP, aN, aProcess, aTrees,
+ aDegenerates, aHeapTotal,
+ aHasMozMallocUsableSize)
+{
+ const kUpwardsArrow = "\u2191",
+ kDownwardsArrow = "\u2193";
+
+ let appendLink = function(aHere, aThere, aArrow) {
+ let link = appendElementWithText(aP, "a", "upDownArrow", aArrow);
+ link.href = "#" + aThere + aN;
+ link.id = aHere + aN;
+ link.title = "Go to the " + aThere + " of " + aProcess;
+ link.style = "text-decoration: none";
+
+ // This jumps to the anchor without the page location getting the anchor
+ // name tacked onto its end, which is what happens with a vanilla link.
+ link.addEventListener("click", function(event) {
+ document.documentElement.scrollTop =
+ document.querySelector(event.target.href).offsetTop;
+ event.preventDefault();
+ }, false);
+
+ // This gives nice spacing when we copy and paste.
+ appendElementWithText(aP, "span", "", "\n");
+ }
+
+ appendElementWithText(aP, "h1", "", aProcess);
+ appendLink("start", "end", kDownwardsArrow);
+
+ // We'll fill this in later.
+ let warningsDiv = appendElement(aP, "div", "accuracyWarning");
+
+ // The explicit tree.
+ let hasExplicitTree;
+ let hasKnownHeapAllocated;
+ {
+ let treeName = "explicit";
+ let t = aTrees[treeName];
+ if (t) {
+ let pre = appendSectionHeader(aP, "Explicit Allocations");
+ hasExplicitTree = true;
+ fillInTree(t);
+ // Using the "heap-allocated" reporter here instead of
+ // nsMemoryReporterManager.heapAllocated goes against the usual pattern.
+ // But the "heap-allocated" node will go in the tree like the others, so
+ // we have to deal with it, and once we're dealing with it, it's easier
+ // to keep doing so rather than switching to the distinguished amount.
+ hasKnownHeapAllocated =
+ aDegenerates &&
+ addHeapUnclassifiedNode(t, aDegenerates["heap-allocated"], aHeapTotal);
+ sortTreeAndInsertAggregateNodes(t._amount, t);
+ t._description = explicitTreeDescription;
+ appendTreeElements(pre, t, aProcess, "");
+ delete aTrees[treeName];
+ }
+ appendTextNode(aP, "\n"); // gives nice spacing when we copy and paste
+ }
+
+ // Fill in and sort all the non-degenerate other trees.
+ let otherTrees = [];
+ for (let unsafeName in aTrees) {
+ let t = aTrees[unsafeName];
+ assert(!t._isDegenerate, "tree is degenerate");
+ fillInTree(t);
+ sortTreeAndInsertAggregateNodes(t._amount, t);
+ otherTrees.push(t);
+ }
+ otherTrees.sort(TreeNode.compareUnsafeNames);
+
+ // Get the length of the longest root value among the degenerate other trees,
+ // and sort them as well.
+ let otherDegenerates = [];
+ let maxStringLength = 0;
+ for (let unsafeName in aDegenerates) {
+ let t = aDegenerates[unsafeName];
+ assert(t._isDegenerate, "tree is not degenerate");
+ let length = t.toString().length;
+ if (length > maxStringLength) {
+ maxStringLength = length;
+ }
+ otherDegenerates.push(t);
+ }
+ otherDegenerates.sort(TreeNode.compareUnsafeNames);
+
+ // Now generate the elements, putting non-degenerate trees first.
+ let pre = appendSectionHeader(aP, "Other Measurements");
+ for (let i = 0; i < otherTrees.length; i++) {
+ let t = otherTrees[i];
+ appendTreeElements(pre, t, aProcess, "");
+ appendTextNode(pre, "\n"); // blank lines after non-degenerate trees
+ }
+ for (let i = 0; i < otherDegenerates.length; i++) {
+ let t = otherDegenerates[i];
+ let padText = pad("", maxStringLength - t.toString().length, ' ');
+ appendTreeElements(pre, t, aProcess, padText);
+ }
+ appendTextNode(aP, "\n"); // gives nice spacing when we copy and paste
+
+ // Add any warnings about inaccuracies in the "explicit" tree due to platform
+ // limitations. These must be computed after generating all the text. The
+ // newlines give nice spacing if we copy+paste into a text buffer.
+ if (hasExplicitTree) {
+ appendWarningElements(warningsDiv, hasKnownHeapAllocated,
+ aHasMozMallocUsableSize);
+ }
+
+ appendElementWithText(aP, "h3", "", "End of " + aProcess);
+ appendLink("end", "start", kUpwardsArrow);
+}
+
+/**
+ * Determines if a number has a negative sign when converted to a string.
+ * Works even for -0.
+ *
+ * @param aN
+ * The number.
+ * @return A boolean.
+ */
+function hasNegativeSign(aN)
+{
+ if (aN === 0) { // this succeeds for 0 and -0
+ return 1 / aN === -Infinity; // this succeeds for -0
+ }
+ return aN < 0;
+}
+
+/**
+ * Formats an int as a human-readable string.
+ *
+ * @param aN
+ * The integer to format.
+ * @param aExtra
+ * An extra string to tack onto the end.
+ * @return A human-readable string representing the int.
+ *
+ * Note: building an array of chars and converting that to a string with
+ * Array.join at the end is more memory efficient than using string
+ * concatenation. See bug 722972 for details.
+ */
+function formatInt(aN, aExtra)
+{
+ let neg = false;
+ if (hasNegativeSign(aN)) {
+ neg = true;
+ aN = -aN;
+ }
+ let s = [];
+ while (true) {
+ let k = aN % 1000;
+ aN = Math.floor(aN / 1000);
+ if (aN > 0) {
+ if (k < 10) {
+ s.unshift(",00", k);
+ } else if (k < 100) {
+ s.unshift(",0", k);
+ } else {
+ s.unshift(",", k);
+ }
+ } else {
+ s.unshift(k);
+ break;
+ }
+ }
+ if (neg) {
+ s.unshift("-");
+ }
+ if (aExtra) {
+ s.push(aExtra);
+ }
+ return s.join("");
+}
+
+/**
+ * Converts a byte count to an appropriate string representation.
+ *
+ * @param aBytes
+ * The byte count.
+ * @return The string representation.
+ */
+function formatBytes(aBytes)
+{
+ let unit = gVerbose.checked ? " B" : " MB";
+
+ let s;
+ if (gVerbose.checked) {
+ s = formatInt(aBytes, unit);
+ } else {
+ let mbytes = (aBytes / (1024 * 1024)).toFixed(2);
+ let a = String(mbytes).split(".");
+ // If the argument to formatInt() is -0, it will print the negative sign.
+ s = formatInt(Number(a[0])) + "." + a[1] + unit;
+ }
+ return s;
+}
+
+/**
+ * Converts a percentage to an appropriate string representation.
+ *
+ * @param aPerc100x
+ * The percentage, multiplied by 100 (see nsIMemoryReporter).
+ * @return The string representation
+ */
+function formatPercentage(aPerc100x)
+{
+ return (aPerc100x / 100).toFixed(2) + "%";
+}
+
+/**
+ * Right-justifies a string in a field of a given width, padding as necessary.
+ *
+ * @param aS
+ * The string.
+ * @param aN
+ * The field width.
+ * @param aC
+ * The char used to pad.
+ * @return The string representation.
+ */
+function pad(aS, aN, aC)
+{
+ let padding = "";
+ let n2 = aN - aS.length;
+ for (let i = 0; i < n2; i++) {
+ padding += aC;
+ }
+ return padding + aS;
+}
+
+// There's a subset of the Unicode "light" box-drawing chars that is widely
+// implemented in terminals, and this code sticks to that subset to maximize
+// the chance that copying and pasting about:memory output to a terminal will
+// work correctly.
+const kHorizontal = "\u2500",
+ kVertical = "\u2502",
+ kUpAndRight = "\u2514",
+ kUpAndRight_Right_Right = "\u2514\u2500\u2500",
+ kVerticalAndRight = "\u251c",
+ kVerticalAndRight_Right_Right = "\u251c\u2500\u2500",
+ kVertical_Space_Space = "\u2502 ";
+
+const kNoKidsSep = " \u2500\u2500 ",
+ kHideKidsSep = " ++ ",
+ kShowKidsSep = " -- ";
+
+function appendMrNameSpan(aP, aDescription, aUnsafeName, aIsInvalid, aNMerged,
+ aPresence)
+{
+ let safeName = flipBackslashes(aUnsafeName);
+ if (!aIsInvalid && !aNMerged && !aPresence) {
+ safeName += "\n";
+ }
+ let nameSpan = appendElementWithText(aP, "span", "mrName", safeName);
+ nameSpan.title = aDescription;
+
+ if (aIsInvalid) {
+ let noteText = " [?!]";
+ if (!aNMerged) {
+ noteText += "\n";
+ }
+ let noteSpan = appendElementWithText(aP, "span", "mrNote", noteText);
+ noteSpan.title =
+ "Warning: this value is invalid and indicates a bug in one or more " +
+ "memory reporters. ";
+ }
+
+ if (aNMerged) {
+ let noteText = " [" + aNMerged + "]";
+ if (!aPresence) {
+ noteText += "\n";
+ }
+ let noteSpan = appendElementWithText(aP, "span", "mrNote", noteText);
+ noteSpan.title =
+ "This value is the sum of " + aNMerged +
+ " memory reports that all have the same path.";
+ }
+
+ if (aPresence) {
+ let c, title;
+ switch (aPresence) {
+ case DReport.PRESENT_IN_FIRST_ONLY:
+ c = '-';
+ title = "This value was only present in the first set of memory reports.";
+ break;
+ case DReport.PRESENT_IN_SECOND_ONLY:
+ c = '+';
+ title = "This value was only present in the second set of memory reports.";
+ break;
+ case DReport.ADDED_FOR_BALANCE:
+ c = '!';
+ title = "One of the sets of memory reports lacked children for this " +
+ "node's parent. This is a fake child node added to make the " +
+ "two memory sets comparable.";
+ break;
+ default: assert(false, "bad presence");
+ break;
+ }
+ let noteSpan = appendElementWithText(aP, "span", "mrNote",
+ " [" + c + "]\n");
+ noteSpan.title = title;
+ }
+}
+
+// This is used to record the (safe) IDs of which sub-trees have been manually
+// expanded (marked as true) and collapsed (marked as false). It's used to
+// replicate the collapsed/expanded state when the page is updated. It can end
+// up holding IDs of nodes that no longer exist, e.g. for compartments that
+// have been closed. This doesn't seem like a big deal, because the number is
+// limited by the number of entries the user has changed from their original
+// state.
+var gShowSubtreesBySafeTreeId = {};
+
+function assertClassListContains(e, className) {
+ assert(e, "undefined " + className);
+ assert(e.classList.contains(className), "classname isn't " + className);
+}
+
+function toggle(aEvent)
+{
+ // This relies on each line being a span that contains at least four spans:
+ // mrValue, mrPerc, mrSep, mrName, and then zero or more mrNotes. All
+ // whitespace must be within one of these spans for this function to find the
+ // right nodes. And the span containing the children of this line must
+ // immediately follow. Assertions check this.
+
+ // |aEvent.target| will be one of the spans. Get the outer span.
+ let outerSpan = aEvent.target.parentNode;
+ assertClassListContains(outerSpan, "hasKids");
+
+ // Toggle the '++'/'--' separator.
+ let isExpansion;
+ let sepSpan = outerSpan.childNodes[2];
+ assertClassListContains(sepSpan, "mrSep");
+ if (sepSpan.textContent === kHideKidsSep) {
+ isExpansion = true;
+ sepSpan.textContent = kShowKidsSep;
+ } else if (sepSpan.textContent === kShowKidsSep) {
+ isExpansion = false;
+ sepSpan.textContent = kHideKidsSep;
+ } else {
+ assert(false, "bad sepSpan textContent");
+ }
+
+ // Toggle visibility of the span containing this node's children.
+ let subTreeSpan = outerSpan.nextSibling;
+ assertClassListContains(subTreeSpan, "kids");
+ subTreeSpan.classList.toggle("hidden");
+
+ // Record/unrecord that this sub-tree was toggled.
+ let safeTreeId = outerSpan.id;
+ if (gShowSubtreesBySafeTreeId[safeTreeId] !== undefined) {
+ delete gShowSubtreesBySafeTreeId[safeTreeId];
+ } else {
+ gShowSubtreesBySafeTreeId[safeTreeId] = isExpansion;
+ }
+}
+
+function expandPathToThisElement(aElement)
+{
+ if (aElement.classList.contains("kids")) {
+ // Unhide the kids.
+ aElement.classList.remove("hidden");
+ expandPathToThisElement(aElement.previousSibling); // hasKids
+
+ } else if (aElement.classList.contains("hasKids")) {
+ // Change the separator to '--'.
+ let sepSpan = aElement.childNodes[2];
+ assertClassListContains(sepSpan, "mrSep");
+ sepSpan.textContent = kShowKidsSep;
+ expandPathToThisElement(aElement.parentNode); // kids or pre.entries
+
+ } else {
+ assertClassListContains(aElement, "entries");
+ }
+}
+
+/**
+ * Appends the elements for the tree, including its heading.
+ *
+ * @param aP
+ * The parent DOM node.
+ * @param aRoot
+ * The tree root.
+ * @param aProcess
+ * The process the tree corresponds to.
+ * @param aPadText
+ * A string to pad the start of each entry.
+ */
+function appendTreeElements(aP, aRoot, aProcess, aPadText)
+{
+ /**
+ * Appends the elements for a particular tree, without a heading.
+ *
+ * @param aP
+ * The parent DOM node.
+ * @param aProcess
+ * The process the tree corresponds to.
+ * @param aUnsafeNames
+ * An array of the names forming the path to aT.
+ * @param aRoot
+ * The root of the tree this sub-tree belongs to.
+ * @param aT
+ * The tree.
+ * @param aTreelineText1
+ * The first part of the treeline for this entry and this entry's
+ * children.
+ * @param aTreelineText2a
+ * The second part of the treeline for this entry.
+ * @param aTreelineText2b
+ * The second part of the treeline for this entry's children.
+ * @param aParentStringLength
+ * The length of the formatted byte count of the top node in the tree.
+ */
+ function appendTreeElements2(aP, aProcess, aUnsafeNames, aRoot, aT,
+ aTreelineText1, aTreelineText2a,
+ aTreelineText2b, aParentStringLength)
+ {
+ function appendN(aS, aC, aN)
+ {
+ for (let i = 0; i < aN; i++) {
+ aS += aC;
+ }
+ return aS;
+ }
+
+ // The tree line. Indent more if this entry is narrower than its parent.
+ let valueText = aT.toString();
+ let extraTreelineLength =
+ Math.max(aParentStringLength - valueText.length, 0);
+ if (extraTreelineLength > 0) {
+ aTreelineText2a =
+ appendN(aTreelineText2a, kHorizontal, extraTreelineLength);
+ aTreelineText2b =
+ appendN(aTreelineText2b, " ", extraTreelineLength);
+ }
+ let treelineText = aTreelineText1 + aTreelineText2a;
+ appendElementWithText(aP, "span", "treeline", treelineText);
+
+ // Detect and record invalid values. But not if gIsDiff is true, because
+ // we expect negative values in that case.
+ assertInput(aRoot._units === aT._units,
+ "units within a tree are inconsistent");
+ let tIsInvalid = false;
+ if (!gIsDiff && !(0 <= aT._amount && aT._amount <= aRoot._amount)) {
+ tIsInvalid = true;
+ let unsafePath = aUnsafeNames.join("/");
+ gUnsafePathsWithInvalidValuesForThisProcess.push(unsafePath);
+ reportAssertionFailure("Invalid value (" + aT._amount + " / " +
+ aRoot._amount + ") for " +
+ flipBackslashes(unsafePath));
+ }
+
+ // For non-leaf nodes, the entire sub-tree is put within a span so it can
+ // be collapsed if the node is clicked on.
+ let d;
+ let sep;
+ let showSubtrees;
+ if (aT._kids) {
+ // Determine if we should show the sub-tree below this entry; this
+ // involves reinstating any previous toggling of the sub-tree.
+ let unsafePath = aUnsafeNames.join("/");
+ let safeTreeId = aProcess + ":" + flipBackslashes(unsafePath);
+ showSubtrees = !aT._hideKids;
+ if (gShowSubtreesBySafeTreeId[safeTreeId] !== undefined) {
+ showSubtrees = gShowSubtreesBySafeTreeId[safeTreeId];
+ }
+ d = appendElement(aP, "span", "hasKids");
+ d.id = safeTreeId;
+ d.onclick = toggle;
+ sep = showSubtrees ? kShowKidsSep : kHideKidsSep;
+ } else {
+ assert(!aT._hideKids, "leaf node with _hideKids set")
+ sep = kNoKidsSep;
+ d = aP;
+ }
+
+ // The value.
+ appendElementWithText(d, "span", "mrValue" + (tIsInvalid ? " invalid" : ""),
+ valueText);
+
+ // The percentage (omitted for single entries).
+ let percText;
+ if (!aT._isDegenerate) {
+ // Treat 0 / 0 as 100%.
+ let num = aRoot._amount === 0 ? 100 : (100 * aT._amount / aRoot._amount);
+ let numText = num.toFixed(2);
+ percText = numText === "100.00"
+ ? " (100.0%)"
+ : (0 <= num && num < 10 ? " (0" : " (") + numText + "%)";
+ appendElementWithText(d, "span", "mrPerc", percText);
+ }
+
+ // The separator.
+ appendElementWithText(d, "span", "mrSep", sep);
+
+ // The entry's name.
+ appendMrNameSpan(d, aT._description, aT._unsafeName,
+ tIsInvalid, aT._nMerged, aT._presence);
+
+ // In non-verbose mode, invalid nodes can be hidden in collapsed sub-trees.
+ // But it's good to always see them, so force this.
+ if (!gVerbose.checked && tIsInvalid) {
+ expandPathToThisElement(d);
+ }
+
+ // Recurse over children.
+ if (aT._kids) {
+ // The 'kids' class is just used for sanity checking in toggle().
+ d = appendElement(aP, "span", showSubtrees ? "kids" : "kids hidden");
+
+ let kidTreelineText1 = aTreelineText1 + aTreelineText2b;
+ for (let i = 0; i < aT._kids.length; i++) {
+ let kidTreelineText2a, kidTreelineText2b;
+ if (i < aT._kids.length - 1) {
+ kidTreelineText2a = kVerticalAndRight_Right_Right;
+ kidTreelineText2b = kVertical_Space_Space;
+ } else {
+ kidTreelineText2a = kUpAndRight_Right_Right;
+ kidTreelineText2b = " ";
+ }
+ aUnsafeNames.push(aT._kids[i]._unsafeName);
+ appendTreeElements2(d, aProcess, aUnsafeNames, aRoot, aT._kids[i],
+ kidTreelineText1, kidTreelineText2a,
+ kidTreelineText2b, valueText.length);
+ aUnsafeNames.pop();
+ }
+ }
+ }
+
+ let rootStringLength = aRoot.toString().length;
+ appendTreeElements2(aP, aProcess, [aRoot._unsafeName], aRoot, aRoot,
+ aPadText, "", "", rootStringLength);
+}
+
+// ---------------------------------------------------------------------------
+
+function appendSectionHeader(aP, aText)
+{
+ appendElementWithText(aP, "h2", "", aText + "\n");
+ return appendElement(aP, "pre", "entries");
+}
+
+// ---------------------------------------------------------------------------
+
+function saveReportsToFile()
+{
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ fp.appendFilter("Zipped JSON files", "*.json.gz");
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.filterIndex = 0;
+ fp.addToRecentDocs = true;
+ fp.defaultString = "memory-report.json.gz";
+
+ let fpFinish = function(file) {
+ let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
+ .getService(Ci.nsIMemoryInfoDumper);
+ let finishDumping = () => {
+ updateMainAndFooter("Saved memory reports to " + file.path, HIDE_FOOTER);
+ }
+ dumper.dumpMemoryReportsToNamedFile(file.path, finishDumping, null,
+ gAnonymize.checked);
+ }
+
+ let fpCallback = function(aResult) {
+ if (aResult == Ci.nsIFilePicker.returnOK ||
+ aResult == Ci.nsIFilePicker.returnReplace) {
+ fpFinish(fp.file);
+ }
+ };
+
+ fp.init(window, "Save Memory Reports", Ci.nsIFilePicker.modeSave);
+ fp.open(fpCallback);
+}
diff --git a/components/aboutmemory/content/aboutMemory.xhtml b/components/aboutmemory/content/aboutMemory.xhtml
new file mode 100644
index 000000000..e20b3b624
--- /dev/null
+++ b/components/aboutmemory/content/aboutMemory.xhtml
@@ -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/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta name="viewport" content="width=device-width"/>
+ <link rel="stylesheet" href="chrome://global/skin/aboutMemory.css" type="text/css"/>
+ <script type="text/javascript;version=1.8" src="chrome://global/content/aboutMemory.js"/>
+ </head>
+
+ <body onload="onLoad()" onunload="onUnload()"></body>
+</html>
diff --git a/components/aboutmemory/jar.mn b/components/aboutmemory/jar.mn
new file mode 100644
index 000000000..0a6b01ed7
--- /dev/null
+++ b/components/aboutmemory/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/.
+
+toolkit.jar:
+ content/global/aboutMemory.js (content/aboutMemory.js)
+ content/global/aboutMemory.xhtml (content/aboutMemory.xhtml)
+ content/global/aboutMemory.css (content/aboutMemory.css)
diff --git a/components/aboutmemory/moz.build b/components/aboutmemory/moz.build
new file mode 100644
index 000000000..aee4b90a5
--- /dev/null
+++ b/components/aboutmemory/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/aboutperformance/content/aboutPerformance.js b/components/aboutperformance/content/aboutPerformance.js
new file mode 100644
index 000000000..042aac258
--- /dev/null
+++ b/components/aboutperformance/content/aboutPerformance.js
@@ -0,0 +1,1076 @@
+/* -*- 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/. */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+const { AddonWatcher } = Cu.import("resource://gre/modules/AddonWatcher.jsm", {});
+const { PerformanceStats } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {});
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+const { ObjectUtils } = Cu.import("resource://gre/modules/ObjectUtils.jsm", {});
+const { Memory } = Cu.import("resource://gre/modules/Memory.jsm");
+const { DownloadUtils } = Cu.import("resource://gre/modules/DownloadUtils.jsm");
+
+// about:performance observes notifications on this topic.
+// if a notification is sent, this causes the page to be updated immediately,
+// regardless of whether the page is on pause.
+const TEST_DRIVER_TOPIC = "test-about:performance-test-driver";
+
+// about:performance posts notifications on this topic whenever the page
+// is updated.
+const UPDATE_COMPLETE_TOPIC = "about:performance-update-complete";
+
+// How often we should add a sample to our buffer.
+const BUFFER_SAMPLING_RATE_MS = 1000;
+
+// The age of the oldest sample to keep.
+const BUFFER_DURATION_MS = 10000;
+
+// How often we should update
+const UPDATE_INTERVAL_MS = 5000;
+
+// The name of the application
+const BRAND_BUNDLE = Services.strings.createBundle(
+ "chrome://branding/locale/brand.properties");
+const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
+
+// The maximal number of items to display before showing a "Show All"
+// button.
+const MAX_NUMBER_OF_ITEMS_TO_DISPLAY = 3;
+
+// If the frequency of alerts is below this value,
+// we consider that the feature has no impact.
+const MAX_FREQUENCY_FOR_NO_IMPACT = .05;
+// If the frequency of alerts is above `MAX_FREQUENCY_FOR_NO_IMPACT`
+// and below this value, we consider that the feature impacts the
+// user rarely.
+const MAX_FREQUENCY_FOR_RARE = .1;
+// If the frequency of alerts is above `MAX_FREQUENCY_FOR_FREQUENT`
+// and below this value, we consider that the feature impacts the
+// user frequently. Anything above is consider permanent.
+const MAX_FREQUENCY_FOR_FREQUENT = .5;
+
+// If the number of high-impact alerts among all alerts is above
+// this value, we consider that the feature has a major impact
+// on user experience.
+const MIN_PROPORTION_FOR_MAJOR_IMPACT = .05;
+// Otherwise and if the number of medium-impact alerts among all
+// alerts is above this value, we consider that the feature has
+// a noticeable impact on user experience.
+const MIN_PROPORTION_FOR_NOTICEABLE_IMPACT = .1;
+
+// The current mode. Either `MODE_GLOBAL` to display a summary of results
+// since we opened about:performance or `MODE_RECENT` to display the latest
+// BUFFER_DURATION_MS ms.
+const MODE_GLOBAL = "global";
+const MODE_RECENT = "recent";
+
+let tabFinder = {
+ update: function() {
+ this._map = new Map();
+ let windows = Services.wm.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ let tabbrowser = win.gBrowser;
+ for (let browser of tabbrowser.browsers) {
+ let id = browser.outerWindowID; // May be `null` if the browser isn't loaded yet
+ if (id != null) {
+ this._map.set(id, browser);
+ }
+ }
+ }
+ },
+
+ /**
+ * Find the <xul:tab> for a window id.
+ *
+ * This is useful e.g. for reloading or closing tabs.
+ *
+ * @return null If the xul:tab could not be found, e.g. if the
+ * windowId is that of a chrome window.
+ * @return {{tabbrowser: <xul:tabbrowser>, tab: <xul.tab>}} The
+ * tabbrowser and tab if the latter could be found.
+ */
+ get: function(id) {
+ let browser = this._map.get(id);
+ if (!browser) {
+ return null;
+ }
+ let tabbrowser = browser.getTabBrowser();
+ return {tabbrowser, tab:tabbrowser.getTabForBrowser(browser)};
+ },
+
+ getAny: function(ids) {
+ for (let id of ids) {
+ let result = this.get(id);
+ if (result) {
+ return result;
+ }
+ }
+ return null;
+ }
+};
+
+/**
+ * Returns a Promise that's resolved after the next turn of the event loop.
+ *
+ * Just returning a resolved Promise would mean that any `then` callbacks
+ * would be called right after the end of the current turn, so `setTimeout`
+ * is used to delay Promise resolution until the next turn.
+ *
+ * In mochi tests, it's possible for this to be called after the
+ * about:performance window has been torn down, which causes `setTimeout` to
+ * throw an NS_ERROR_NOT_INITIALIZED exception. In that case, returning
+ * `undefined` is fine.
+ */
+function wait(ms = 0) {
+ try {
+ let resolve;
+ let p = new Promise(resolve_ => { resolve = resolve_ });
+ setTimeout(resolve, ms);
+ return p;
+ } catch (e) {
+ dump("WARNING: wait aborted because of an invalid Window state in aboutPerformance.js.\n");
+ return undefined;
+ }
+}
+
+/**
+ * The performance of a webpage or an add-on between two instants.
+ *
+ * Clients should call `promiseInit()` before using the methods of this object.
+ *
+ * @param {PerformanceDiff} The underlying performance data.
+ * @param {"addons"|"webpages"} The kind of delta represented by this object.
+ * @param {Map<groupId, timestamp>} ageMap A map containing the oldest known
+ * appearance of each groupId, used to determine how long we have been monitoring
+ * this item.
+ * @param {Map<Delta key, Array>} alertMap A map containing the alerts that each
+ * item has already triggered in the past.
+ */
+function Delta(diff, kind, snapshotDate, ageMap, alertMap) {
+ if (kind != "addons" && kind != "webpages") {
+ throw new TypeError(`Unknown kind: ${kind}`);
+ }
+
+ /**
+ * Either "addons" or "webpages".
+ */
+ this.kind = kind;
+
+ /**
+ * The underlying PerformanceDiff.
+ * @type {PerformanceDiff}
+ */
+ this.diff = diff;
+
+ /**
+ * A key unique to the item (webpage or add-on), shared by successive
+ * instances of `Delta`.
+ * @type{string}
+ */
+ this.key = kind + diff.key;
+
+ // Find the oldest occurrence of this item.
+ let creationDate = snapshotDate;
+ for (let groupId of diff.groupIds) {
+ let date = ageMap.get(groupId);
+ if (date && date <= creationDate) {
+ creationDate = date;
+ }
+ }
+
+ /**
+ * The timestamp at which the data was measured.
+ */
+ this.creationDate = creationDate;
+
+ /**
+ * Number of milliseconds since the start of the measure.
+ */
+ this.age = snapshotDate - creationDate;
+
+ /**
+ * A UX-friendly, human-readable name for this item.
+ */
+ this.readableName = null;
+
+ /**
+ * A complete name, possibly useful for power users or debugging.
+ */
+ this.fullName = null;
+
+
+ // `true` once initialization is complete.
+ this._initialized = false;
+ // `true` if this item should be displayed
+ this._show = false;
+
+ /**
+ * All the alerts that this item has caused since about:performance
+ * was opened.
+ */
+ this.alerts = (alertMap.get(this.key) || []).slice();
+ switch (this.slowness) {
+ case 0: break;
+ case 1: this.alerts[0] = (this.alerts[0] || 0) + 1; break;
+ case 2: this.alerts[1] = (this.alerts[1] || 0) + 1; break;
+ default: throw new Error();
+ }
+}
+Delta.prototype = {
+ /**
+ * `true` if this item should be displayed, `false` otherwise.
+ */
+ get show() {
+ this._ensureInitialized();
+ return this._show;
+ },
+
+ /**
+ * Estimate the slowness of this item.
+ *
+ * @return 0 if the item has good performance.
+ * @return 1 if the item has average performance.
+ * @return 2 if the item has poor performance.
+ */
+ get slowness() {
+ if (Delta.compare(this, Delta.MAX_DELTA_FOR_GOOD_RECENT_PERFORMANCE) <= 0) {
+ return 0;
+ }
+ if (Delta.compare(this, Delta.MAX_DELTA_FOR_AVERAGE_RECENT_PERFORMANCE) <= 0) {
+ return 1;
+ }
+ return 2;
+ },
+ _ensureInitialized() {
+ if (!this._initialized) {
+ throw new Error();
+ }
+ },
+
+ /**
+ * Initialize, asynchronously.
+ */
+ promiseInit: function() {
+ if (this.kind == "webpages") {
+ return this._initWebpage();
+ } else if (this.kind == "addons") {
+ return this._promiseInitAddon();
+ }
+ throw new TypeError();
+ },
+ _initWebpage: function() {
+ this._initialized = true;
+ let found = tabFinder.getAny(this.diff.windowIds);
+ if (!found || found.tab.linkedBrowser.contentTitle == null) {
+ // Either this is not a real page or the page isn't restored yet.
+ return;
+ }
+
+ this.readableName = found.tab.linkedBrowser.contentTitle;
+ this.fullName = this.diff.names.join(", ");
+ this._show = true;
+ },
+ _promiseInitAddon: Task.async(function*() {
+ let found = yield (new Promise(resolve =>
+ AddonManager.getAddonByID(this.diff.addonId, a => {
+ if (a) {
+ this.readableName = a.name;
+ resolve(true);
+ } else {
+ resolve(false);
+ }
+ })));
+
+ this._initialized = true;
+
+ // If the add-on manager doesn't know about an add-on, it's
+ // probably not a real add-on.
+ this._show = found;
+ this.fullName = this.diff.addonId;
+ }),
+ toString: function() {
+ return `[Delta] ${this.diff.key} => ${this.readableName}, ${this.fullName}`;
+ }
+};
+
+Delta.compare = function(a, b) {
+ return (
+ (a.diff.jank.longestDuration - b.diff.jank.longestDuration) ||
+ (a.diff.jank.totalUserTime - b.diff.jank.totalUserTime) ||
+ (a.diff.jank.totalSystemTime - b.diff.jank.totalSystemTime) ||
+ (a.diff.cpow.totalCPOWTime - b.diff.cpow.totalCPOWTime) ||
+ (a.diff.ticks.ticks - b.diff.ticks.ticks) ||
+ 0
+ );
+};
+
+Delta.revCompare = function(a, b) {
+ return -Delta.compare(a, b);
+};
+
+/**
+ * The highest value considered "good performance".
+ */
+Delta.MAX_DELTA_FOR_GOOD_RECENT_PERFORMANCE = {
+ diff: {
+ cpow: {
+ totalCPOWTime: 0,
+ },
+ jank: {
+ longestDuration: 3,
+ totalUserTime: Number.POSITIVE_INFINITY,
+ totalSystemTime: Number.POSITIVE_INFINITY
+ },
+ ticks: {
+ ticks: Number.POSITIVE_INFINITY,
+ }
+ }
+};
+
+/**
+ * The highest value considered "average performance".
+ */
+Delta.MAX_DELTA_FOR_AVERAGE_RECENT_PERFORMANCE = {
+ diff: {
+ cpow: {
+ totalCPOWTime: Number.POSITIVE_INFINITY,
+ },
+ jank: {
+ longestDuration: 7,
+ totalUserTime: Number.POSITIVE_INFINITY,
+ totalSystemTime: Number.POSITIVE_INFINITY
+ },
+ ticks: {
+ ticks: Number.POSITIVE_INFINITY,
+ }
+ }
+};
+
+/**
+ * Utilities for dealing with state
+ */
+var State = {
+ _monitor: PerformanceStats.getMonitor([
+ "jank", "cpow", "ticks",
+ ]),
+
+ /**
+ * Indexed by the number of minutes since the snapshot was taken.
+ *
+ * @type {Array<ApplicationSnapshot>}
+ */
+ _buffer: [],
+ /**
+ * The first snapshot since opening the page.
+ *
+ * @type ApplicationSnapshot
+ */
+ _oldest: null,
+
+ /**
+ * The latest snapshot.
+ *
+ * @type ApplicationSnapshot
+ */
+ _latest: null,
+
+ /**
+ * The performance alerts for each group.
+ *
+ * This map is cleaned up during each update to avoid leaking references
+ * to groups that have been gc-ed.
+ *
+ * @type{Map<Delta key, Array<number>} A map in which the keys are provided
+ * by property `key` of instances of `Delta` and the values are arrays
+ * [number of moderate-impact alerts, number of high-impact alerts]
+ */
+ _alerts: new Map(),
+
+ /**
+ * The date at which each group was first seen.
+ *
+ * This map is cleaned up during each update to avoid leaking references
+ * to groups that have been gc-ed.
+ *
+ * @type{Map<string, timestamp} A map in which keys are
+ * values for `delta.groupId` and values are approximate
+ * dates at which the group was first encountered, as provided
+ * by `Cu.now()``.
+ */
+ _firstSeen: new Map(),
+
+ /**
+ * Update the internal state.
+ *
+ * @return {Promise}
+ */
+ update: Task.async(function*() {
+ // If the buffer is empty, add one value for bootstraping purposes.
+ if (this._buffer.length == 0) {
+ if (this._oldest) {
+ throw new Error("Internal Error, we shouldn't have a `_oldest` value yet.");
+ }
+ this._latest = this._oldest = yield this._monitor.promiseSnapshot();
+ this._buffer.push(this._oldest);
+ yield wait(BUFFER_SAMPLING_RATE_MS * 1.1);
+ }
+
+
+ let now = Cu.now();
+
+ // If we haven't sampled in a while, add a sample to the buffer.
+ let latestInBuffer = this._buffer[this._buffer.length - 1];
+ let deltaT = now - latestInBuffer.date;
+ if (deltaT > BUFFER_SAMPLING_RATE_MS) {
+ this._latest = yield this._monitor.promiseSnapshot();
+ this._buffer.push(this._latest);
+ }
+
+ // If we have too many samples, remove the oldest sample.
+ let oldestInBuffer = this._buffer[0];
+ if (oldestInBuffer.date + BUFFER_DURATION_MS < this._latest.date) {
+ this._buffer.shift();
+ }
+ }),
+
+ /**
+ * @return {Promise}
+ */
+ promiseDeltaSinceStartOfTime: function() {
+ return this._promiseDeltaSince(this._oldest);
+ },
+
+ /**
+ * @return {Promise}
+ */
+ promiseDeltaSinceStartOfBuffer: function() {
+ return this._promiseDeltaSince(this._buffer[0]);
+ },
+
+ /**
+ * @return {Promise}
+ * @resolve {{
+ * addons: Array<Delta>,
+ * webpages: Array<Delta>,
+ * deltas: Set<Delta key>,
+ * duration: number of milliseconds
+ * }}
+ */
+ _promiseDeltaSince: Task.async(function*(oldest) {
+ let current = this._latest;
+ if (!oldest) {
+ throw new TypeError();
+ }
+ if (!current) {
+ throw new TypeError();
+ }
+
+ tabFinder.update();
+ // We rebuild the maps during each iteration to make sure that
+ // we do not maintain references to groups that has been removed
+ // (e.g. pages that have been closed).
+ let oldFirstSeen = this._firstSeen;
+ let cleanedUpFirstSeen = new Map();
+
+ let oldAlerts = this._alerts;
+ let cleanedUpAlerts = new Map();
+
+ let result = {
+ addons: [],
+ webpages: [],
+ deltas: new Set(),
+ duration: current.date - oldest.date
+ };
+
+ for (let kind of ["webpages", "addons"]) {
+ for (let [key, value] of current[kind]) {
+ let item = ObjectUtils.strict(new Delta(value.subtract(oldest[kind].get(key)), kind, current.date, oldFirstSeen, oldAlerts));
+ yield item.promiseInit();
+
+ if (!item.show) {
+ continue;
+ }
+ result[kind].push(item);
+ result.deltas.add(item.key);
+
+ for (let groupId of item.diff.groupIds) {
+ cleanedUpFirstSeen.set(groupId, item.creationDate);
+ }
+ cleanedUpAlerts.set(item.key, item.alerts);
+ }
+ }
+
+ this._firstSeen = cleanedUpFirstSeen;
+ this._alerts = cleanedUpAlerts;
+ return result;
+ }),
+};
+
+var View = {
+ /**
+ * A cache for all the per-item DOM elements that are reused across refreshes.
+ *
+ * Reusing the same elements means that elements that were hidden (respectively
+ * visible) in an iteration remain hidden (resp visible) in the next iteration.
+ */
+ DOMCache: {
+ _map: new Map(),
+ /**
+ * @param {string} deltaKey The key for the item that we are displaying.
+ * @return {null} If the `deltaKey` doesn't have a component cached yet.
+ * Otherwise, the value stored with `set`.
+ */
+ get: function(deltaKey) {
+ return this._map.get(deltaKey);
+ },
+ set: function(deltaKey, value) {
+ this._map.set(deltaKey, value);
+ },
+ /**
+ * Remove all the elements whose key does not appear in `set`.
+ *
+ * @param {Set} set a set of deltaKey.
+ */
+ trimTo: function(set) {
+ let remove = [];
+ for (let key of this._map.keys()) {
+ if (!set.has(key)) {
+ remove.push(key);
+ }
+ }
+ for (let key of remove) {
+ this._map.delete(key);
+ }
+ }
+ },
+ /**
+ * Display the items in a category.
+ *
+ * @param {Array<PerformanceDiff>} subset The items to display. They will
+ * be displayed in the order of `subset`.
+ * @param {string} id The id of the DOM element that will contain the items.
+ * @param {string} nature The nature of the subset. One of "addons", "webpages" or "system".
+ * @param {string} currentMode The current display mode. One of MODE_GLOBAL or MODE_RECENT.
+ */
+ updateCategory: function(subset, id, nature, currentMode) {
+ subset = subset.slice().sort(Delta.revCompare);
+
+ let watcherAlerts = null;
+ if (nature == "addons") {
+ watcherAlerts = AddonWatcher.alerts;
+ }
+
+ // Grab everything from the DOM before cleaning up
+ this._setupStructure(id);
+
+ // An array of `cachedElements` that need to be added
+ let toAdd = [];
+ for (let delta of subset) {
+ if (!(delta instanceof Delta)) {
+ throw new TypeError();
+ }
+ let cachedElements = this._grabOrCreateElements(delta, nature);
+ toAdd.push(cachedElements);
+ cachedElements.eltTitle.textContent = delta.readableName;
+ cachedElements.eltName.textContent = `Full name: ${delta.fullName}.`;
+ cachedElements.eltLoaded.textContent = `Measure start: ${Math.round(delta.age/1000)} seconds ago.`
+
+ let processes = delta.diff.processes.map(proc => `${proc.processId} (${proc.isChildProcess?"child":"parent"})`);
+ cachedElements.eltProcess.textContent = `Processes: ${processes.join(", ")}`;
+ let jankSuffix = "";
+ if (watcherAlerts) {
+ let deltaAlerts = watcherAlerts.get(delta.diff.addonId);
+ if (deltaAlerts) {
+ if (deltaAlerts.occurrences) {
+ jankSuffix = ` (${deltaAlerts.occurrences} alerts)`;
+ }
+ }
+ }
+
+ let eltImpact = cachedElements.eltImpact;
+ if (currentMode == MODE_RECENT) {
+ cachedElements.eltRoot.setAttribute("impact", delta.diff.jank.longestDuration + 1);
+ if (Delta.compare(delta, Delta.MAX_DELTA_FOR_GOOD_RECENT_PERFORMANCE) <= 0) {
+ eltImpact.textContent = ` currently performs well.`;
+ } else if (Delta.compare(delta, Delta.MAX_DELTA_FOR_AVERAGE_RECENT_PERFORMANCE)) {
+ eltImpact.textContent = ` may currently be slowing down ${BRAND_NAME}.`;
+ } else {
+ eltImpact.textContent = ` is currently considerably slowing down ${BRAND_NAME}.`;
+ }
+
+ cachedElements.eltFPS.textContent = `Impact on framerate: ${delta.diff.jank.longestDuration + 1}/${delta.diff.jank.durations.length}${jankSuffix}.`;
+ cachedElements.eltCPU.textContent = `CPU usage: ${Math.ceil(delta.diff.jank.totalCPUTime/delta.diff.deltaT/10)}%.`;
+ cachedElements.eltSystem.textContent = `System usage: ${Math.ceil(delta.diff.jank.totalSystemTime/delta.diff.deltaT/10)}%.`;
+ cachedElements.eltCPOW.textContent = `Blocking process calls: ${Math.ceil(delta.diff.cpow.totalCPOWTime/delta.diff.deltaT/10)}%.`;
+ } else {
+ if (delta.alerts.length == 0) {
+ eltImpact.textContent = " has performed well so far.";
+ cachedElements.eltFPS.textContent = `Impact on framerate: no impact.`;
+ cachedElements.eltRoot.setAttribute("impact", 0);
+ } else {
+ let impact = 0;
+ let sum = /* medium impact */ delta.alerts[0] + /* high impact */ delta.alerts[1];
+ let frequency = sum * 1000 / delta.diff.deltaT;
+
+ let describeFrequency;
+ if (frequency <= MAX_FREQUENCY_FOR_NO_IMPACT) {
+ describeFrequency = `has no impact on the performance of ${BRAND_NAME}.`
+ } else {
+ let describeImpact;
+ if (frequency <= MAX_FREQUENCY_FOR_RARE) {
+ describeFrequency = `rarely slows down ${BRAND_NAME}.`;
+ impact += 1;
+ } else if (frequency <= MAX_FREQUENCY_FOR_FREQUENT) {
+ describeFrequency = `has slown down ${BRAND_NAME} frequently.`;
+ impact += 2.5;
+ } else {
+ describeFrequency = `seems to have slown down ${BRAND_NAME} very often.`;
+ impact += 5;
+ }
+ // At this stage, `sum != 0`
+ if (delta.alerts[1] / sum > MIN_PROPORTION_FOR_MAJOR_IMPACT) {
+ describeImpact = "When this happens, the slowdown is generally important."
+ impact *= 2;
+ } else {
+ describeImpact = "When this happens, the slowdown is generally noticeable."
+ }
+
+ eltImpact.textContent = ` ${describeFrequency} ${describeImpact}`;
+ cachedElements.eltFPS.textContent = `Impact on framerate: ${delta.alerts[1] || 0} high-impacts, ${delta.alerts[0] || 0} medium-impact${jankSuffix}.`;
+ }
+ cachedElements.eltRoot.setAttribute("impact", Math.round(impact));
+ }
+
+ cachedElements.eltCPU.textContent = `CPU usage: ${Math.ceil(delta.diff.jank.totalCPUTime/delta.diff.deltaT/10)}% (total ${delta.diff.jank.totalUserTime}ms).`;
+ cachedElements.eltSystem.textContent = `System usage: ${Math.ceil(delta.diff.jank.totalSystemTime/delta.diff.deltaT/10)}% (total ${delta.diff.jank.totalSystemTime}ms).`;
+ cachedElements.eltCPOW.textContent = `Blocking process calls: ${Math.ceil(delta.diff.cpow.totalCPOWTime/delta.diff.deltaT/10)}% (total ${delta.diff.cpow.totalCPOWTime}ms).`;
+ }
+ }
+ this._insertElements(toAdd, id);
+ },
+
+ _insertElements: function(elements, id) {
+ let eltContainer = document.getElementById(id);
+ eltContainer.classList.remove("measuring");
+ eltContainer.eltVisibleContent.innerHTML = "";
+ eltContainer.eltHiddenContent.innerHTML = "";
+ eltContainer.appendChild(eltContainer.eltShowMore);
+
+ for (let i = 0; i < elements.length && i < MAX_NUMBER_OF_ITEMS_TO_DISPLAY; ++i) {
+ let cachedElements = elements[i];
+ eltContainer.eltVisibleContent.appendChild(cachedElements.eltRoot);
+ }
+ for (let i = MAX_NUMBER_OF_ITEMS_TO_DISPLAY; i < elements.length; ++i) {
+ let cachedElements = elements[i];
+ eltContainer.eltHiddenContent.appendChild(cachedElements.eltRoot);
+ }
+ if (elements.length <= MAX_NUMBER_OF_ITEMS_TO_DISPLAY) {
+ eltContainer.eltShowMore.classList.add("hidden");
+ } else {
+ eltContainer.eltShowMore.classList.remove("hidden");
+ }
+ if (elements.length == 0) {
+ eltContainer.textContent = "Nothing";
+ }
+ },
+ _setupStructure: function(id) {
+ let eltContainer = document.getElementById(id);
+ if (!eltContainer.eltVisibleContent) {
+ eltContainer.eltVisibleContent = document.createElement("ul");
+ eltContainer.eltVisibleContent.classList.add("visible_items");
+ eltContainer.appendChild(eltContainer.eltVisibleContent);
+ }
+ if (!eltContainer.eltHiddenContent) {
+ eltContainer.eltHiddenContent = document.createElement("ul");
+ eltContainer.eltHiddenContent.classList.add("hidden");
+ eltContainer.eltHiddenContent.classList.add("hidden_additional_items");
+ eltContainer.appendChild(eltContainer.eltHiddenContent);
+ }
+ if (!eltContainer.eltShowMore) {
+ eltContainer.eltShowMore = document.createElement("button");
+ eltContainer.eltShowMore.textContent = "Show all";
+ eltContainer.eltShowMore.classList.add("show_all_items");
+ eltContainer.appendChild(eltContainer.eltShowMore);
+ eltContainer.eltShowMore.addEventListener("click", function() {
+ if (eltContainer.eltHiddenContent.classList.contains("hidden")) {
+ eltContainer.eltHiddenContent.classList.remove("hidden");
+ eltContainer.eltShowMore.textContent = "Hide";
+ } else {
+ eltContainer.eltHiddenContent.classList.add("hidden");
+ eltContainer.eltShowMore.textContent = "Show all";
+ }
+ });
+ }
+ return eltContainer;
+ },
+
+ _grabOrCreateElements: function(delta, nature) {
+ let cachedElements = this.DOMCache.get(delta.key);
+ if (cachedElements) {
+ if (cachedElements.eltRoot.parentElement) {
+ cachedElements.eltRoot.parentElement.removeChild(cachedElements.eltRoot);
+ }
+ } else {
+ this.DOMCache.set(delta.key, cachedElements = {});
+
+ let eltDelta = document.createElement("li");
+ eltDelta.classList.add("delta");
+ cachedElements.eltRoot = eltDelta;
+
+ let eltSpan = document.createElement("span");
+ eltDelta.appendChild(eltSpan);
+
+ let eltSummary = document.createElement("span");
+ eltSummary.classList.add("summary");
+ eltSpan.appendChild(eltSummary);
+
+ let eltTitle = document.createElement("span");
+ eltTitle.classList.add("title");
+ eltSummary.appendChild(eltTitle);
+ cachedElements.eltTitle = eltTitle;
+
+ let eltImpact = document.createElement("span");
+ eltImpact.classList.add("impact");
+ eltSummary.appendChild(eltImpact);
+ cachedElements.eltImpact = eltImpact;
+
+ let eltShowMore = document.createElement("a");
+ eltShowMore.classList.add("more");
+ eltSpan.appendChild(eltShowMore);
+ eltShowMore.textContent = "more";
+ eltShowMore.href = "";
+ eltShowMore.addEventListener("click", () => {
+ if (eltDetails.classList.contains("hidden")) {
+ eltDetails.classList.remove("hidden");
+ eltShowMore.textContent = "less";
+ } else {
+ eltDetails.classList.add("hidden");
+ eltShowMore.textContent = "more";
+ }
+ });
+
+ // Add buttons
+ if (nature == "addons") {
+ eltSpan.appendChild(document.createElement("br"));
+ let eltDisable = document.createElement("button");
+ eltDisable.textContent = "Disable";
+ eltSpan.appendChild(eltDisable);
+
+ let eltUninstall = document.createElement("button");
+ eltUninstall.textContent = "Uninstall";
+ eltSpan.appendChild(eltUninstall);
+
+ let eltRestart = document.createElement("button");
+ eltRestart.textContent = `Restart ${BRAND_NAME} to apply your changes.`
+ eltRestart.classList.add("hidden");
+ eltSpan.appendChild(eltRestart);
+
+ eltRestart.addEventListener("click", () => {
+ Services.startup.quit(Services.startup.eForceQuit | Services.startup.eRestart);
+ });
+ AddonManager.getAddonByID(delta.diff.addonId, addon => {
+ eltDisable.addEventListener("click", () => {
+ addon.userDisabled = true;
+ if (addon.pendingOperations == addon.PENDING_NONE) {
+ // Restartless add-on is now disabled.
+ return;
+ }
+ eltDisable.classList.add("hidden");
+ eltUninstall.classList.add("hidden");
+ eltRestart.classList.remove("hidden");
+ });
+
+ eltUninstall.addEventListener("click", () => {
+ addon.uninstall();
+ if (addon.pendingOperations == addon.PENDING_NONE) {
+ // Restartless add-on is now disabled.
+ return;
+ }
+ eltDisable.classList.add("hidden");
+ eltUninstall.classList.add("hidden");
+ eltRestart.classList.remove("hidden");
+ });
+ });
+ } else if (nature == "webpages") {
+ eltSpan.appendChild(document.createElement("br"));
+
+ let eltCloseTab = document.createElement("button");
+ eltCloseTab.textContent = "Close tab";
+ eltSpan.appendChild(eltCloseTab);
+ let windowIds = delta.diff.windowIds;
+ eltCloseTab.addEventListener("click", () => {
+ let found = tabFinder.getAny(windowIds);
+ if (!found) {
+ // Cannot find the tab. Maybe it is closed already?
+ return;
+ }
+ let {tabbrowser, tab} = found;
+ tabbrowser.removeTab(tab);
+ });
+
+ let eltReloadTab = document.createElement("button");
+ eltReloadTab.textContent = "Reload tab";
+ eltSpan.appendChild(eltReloadTab);
+ eltReloadTab.addEventListener("click", () => {
+ let found = tabFinder.getAny(windowIds);
+ if (!found) {
+ // Cannot find the tab. Maybe it is closed already?
+ return;
+ }
+ let {tabbrowser, tab} = found;
+ tabbrowser.reloadTab(tab);
+ });
+ }
+
+ // Prepare details
+ let eltDetails = document.createElement("ul");
+ eltDetails.classList.add("details");
+ eltDetails.classList.add("hidden");
+ eltSpan.appendChild(eltDetails);
+
+ for (let [name, className] of [
+ ["eltName", "name"],
+ ["eltFPS", "fps"],
+ ["eltCPU", "cpu"],
+ ["eltSystem", "system"],
+ ["eltCPOW", "cpow"],
+ ["eltLoaded", "loaded"],
+ ["eltProcess", "process"],
+ ]) {
+ let elt = document.createElement("li");
+ elt.classList.add(className);
+ eltDetails.appendChild(elt);
+ cachedElements[name] = elt;
+ }
+ }
+
+ return cachedElements;
+ },
+};
+
+var Control = {
+ init: function() {
+ this._initAutorefresh();
+ this._initDisplayMode();
+ },
+ update: Task.async(function*() {
+ let mode = this._displayMode;
+ if (this._autoRefreshInterval || !State._buffer[0]) {
+ // Update the state only if we are not on pause.
+ yield State.update();
+ }
+ yield wait(0);
+ let state = yield (mode == MODE_GLOBAL?
+ State.promiseDeltaSinceStartOfTime():
+ State.promiseDeltaSinceStartOfBuffer());
+
+ for (let category of ["webpages", "addons"]) {
+ yield wait(0);
+ yield View.updateCategory(state[category], category, category, mode);
+ }
+ yield wait(0);
+
+ // Make sure that we do not keep obsolete stuff around.
+ View.DOMCache.trimTo(state.deltas);
+
+ yield wait(0);
+
+ // Inform watchers
+ Services.obs.notifyObservers(null, UPDATE_COMPLETE_TOPIC, mode);
+ }),
+ _setOptions: function(options) {
+ dump(`about:performance _setOptions ${JSON.stringify(options)}\n`);
+ let eltRefresh = document.getElementById("check-autorefresh");
+ if ((options.autoRefresh > 0) != eltRefresh.checked) {
+ eltRefresh.click();
+ }
+ let eltCheckRecent = document.getElementById("check-display-recent");
+ if (!!options.displayRecent != eltCheckRecent.checked) {
+ eltCheckRecent.click();
+ }
+ },
+ _initAutorefresh: function() {
+ let onRefreshChange = (shouldUpdateNow = false) => {
+ if (eltRefresh.checked == !!this._autoRefreshInterval) {
+ // Nothing to change.
+ return;
+ }
+ if (eltRefresh.checked) {
+ this._autoRefreshInterval = window.setInterval(() => Control.update(), UPDATE_INTERVAL_MS);
+ if (shouldUpdateNow) {
+ Control.update();
+ }
+ } else {
+ window.clearInterval(this._autoRefreshInterval);
+ this._autoRefreshInterval = null;
+ }
+ }
+
+ let eltRefresh = document.getElementById("check-autorefresh");
+ eltRefresh.addEventListener("change", () => onRefreshChange(true));
+
+ onRefreshChange(false);
+ },
+ _autoRefreshInterval: null,
+ _initDisplayMode: function() {
+ let onModeChange = (shouldUpdateNow) => {
+ if (eltCheckRecent.checked) {
+ this._displayMode = MODE_RECENT;
+ } else {
+ this._displayMode = MODE_GLOBAL;
+ }
+ if (shouldUpdateNow) {
+ Control.update();
+ }
+ };
+
+ let eltCheckRecent = document.getElementById("check-display-recent");
+ let eltLabelRecent = document.getElementById("label-display-recent");
+ eltCheckRecent.addEventListener("click", () => onModeChange(true));
+ eltLabelRecent.textContent = `Display only the latest ${Math.round(BUFFER_DURATION_MS/1000)}s`;
+
+ onModeChange(false);
+ },
+ // The display mode. One of `MODE_GLOBAL` or `MODE_RECENT`.
+ _displayMode: MODE_GLOBAL,
+};
+
+/**
+ * This functionality gets memory related information of sub-processes and
+ * updates the performance table regularly.
+ * If the page goes hidden, it also handles visibility change by not
+ * querying the content processes unnecessarily.
+ */
+var SubprocessMonitor = {
+ _timeout: null,
+
+ /**
+ * Init will start the process of updating the table if the page is not hidden,
+ * and set up an event listener for handling visibility changes.
+ */
+ init: function() {
+ if (!document.hidden) {
+ SubprocessMonitor.updateTable();
+ }
+ document.addEventListener("visibilitychange", SubprocessMonitor.handleVisibilityChange);
+ },
+
+ /**
+ * This function updates the table after an interval if the page is visible
+ * and clears the interval otherwise.
+ */
+ handleVisibilityChange: function() {
+ if (!document.hidden) {
+ SubprocessMonitor.queueUpdate();
+ } else {
+ clearTimeout(this._timeout);
+ this._timeout = null;
+ }
+ },
+
+ /**
+ * This function queues a timer to request the next summary using updateTable
+ * after some delay.
+ */
+ queueUpdate: function() {
+ this._timeout = setTimeout(() => this.updateTable(), UPDATE_INTERVAL_MS);
+ },
+
+ /**
+ * This is a helper function for updateTable, which updates a particular row.
+ * @param {<tr> node} row The row to be updated.
+ * @param {object} summaries The object with the updated RSS and USS values.
+ * @param {string} pid The pid represented by the row for which we update.
+ */
+ updateRow: function(row, summaries, pid) {
+ row.cells[0].textContent = pid;
+ let RSSval = DownloadUtils.convertByteUnits(summaries[pid].rss);
+ row.cells[1].textContent = RSSval.join(" ");
+ let USSval = DownloadUtils.convertByteUnits(summaries[pid].uss);
+ row.cells[2].textContent = USSval.join(" ");
+ },
+
+ /**
+ * This function adds a row to the subprocess-performance table for every new pid
+ * and populates and regularly updates it with RSS/USS measurements.
+ */
+ updateTable: function() {
+ if (!document.hidden) {
+ Memory.summary().then((summaries) => {
+ if (!(Object.keys(summaries).length)) {
+ // The summaries list was empty, which means we timed out getting
+ // the memory reports. We'll try again later.
+ SubprocessMonitor.queueUpdate();
+ return;
+ }
+ let resultTable = document.getElementById("subprocess-reports");
+ let recycle = [];
+ // We first iterate the table to check if summaries exist for rowPids,
+ // if yes, update them and delete the pid's summary or else hide the row
+ // for recycling it. Start at row 1 instead of 0 (to skip the header row).
+ for (let i = 1, row; row = resultTable.rows[i]; i++) {
+ let rowPid = row.dataset.pid;
+ let summary = summaries[rowPid];
+ if (summary) {
+ // Now we update the values in the row, which is hardcoded for now,
+ // but we might want to make this more adaptable in the future.
+ SubprocessMonitor.updateRow(row, summaries, rowPid);
+ delete summaries[rowPid];
+ } else {
+ // Take this unnecessary row, hide it and stash it for potential re-use.
+ row.hidden = true;
+ recycle.push(row);
+ }
+ }
+ // For the remaining pids in summaries, we choose from the recyclable
+ // (hidden) nodes, and if they get exhausted, append a row to the table.
+ for (let pid in summaries) {
+ let row = recycle.pop();
+ if (row) {
+ row.hidden = false;
+ } else {
+ // We create a new row here, and set it to row
+ row = document.createElement("tr");
+ // Insert cell for pid
+ row.insertCell();
+ // Insert a cell for USS.
+ row.insertCell();
+ // Insert another cell for RSS.
+ row.insertCell();
+ }
+ row.dataset.pid = pid;
+ // Update the row and put it at the bottom
+ SubprocessMonitor.updateRow(row, summaries, pid);
+ resultTable.appendChild(row);
+ }
+ });
+ SubprocessMonitor.queueUpdate();
+ }
+ },
+};
+
+var go = Task.async(function*() {
+
+ SubprocessMonitor.init();
+ Control.init();
+
+ // Setup a hook to allow tests to configure and control this page
+ let testUpdate = function(subject, topic, value) {
+ let options = JSON.parse(value);
+ Control._setOptions(options);
+ Control.update();
+ };
+ Services.obs.addObserver(testUpdate, TEST_DRIVER_TOPIC, false);
+ window.addEventListener("unload", () => Services.obs.removeObserver(testUpdate, TEST_DRIVER_TOPIC));
+
+ yield Control.update();
+ yield wait(BUFFER_SAMPLING_RATE_MS * 1.1);
+ yield Control.update();
+});
diff --git a/components/aboutperformance/content/aboutPerformance.xhtml b/components/aboutperformance/content/aboutPerformance.xhtml
new file mode 100644
index 000000000..6e0d14802
--- /dev/null
+++ b/components/aboutperformance/content/aboutPerformance.xhtml
@@ -0,0 +1,188 @@
+<?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/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>about:performance</title>
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet" href="chrome://global/skin/in-content/common.css"
+ type="text/css"/>
+ <script type="text/javascript;version=1.8" src="chrome://global/content/aboutPerformance.js"></script>
+ <style>
+ @import url("chrome://global/skin/in-content/common.css");
+
+ html {
+ --aboutSupport-table-background: #ebebeb;
+ background-color: var(--in-content-page-background);
+ }
+ body {
+ margin: 40px 48px;
+ }
+ .hidden {
+ display: none;
+ }
+ .summary .title {
+ font-weight: bold;
+ }
+ a {
+ text-decoration: none;
+ }
+ a.more {
+ margin-left: 2ch;
+ }
+ ul.hidden_additional_items {
+ padding-top: 0;
+ margin-top: 0;
+ }
+ ul.visible_items {
+ padding-bottom: 0;
+ margin-bottom: 0;
+ }
+ li.delta {
+ margin-top: .5em;
+ }
+ h2 {
+ margin-top: 1cm;
+ }
+ button.show_all_items {
+ margin-top: .5cm;
+ margin-left: 1cm;
+ }
+ body {
+ margin-left: 1cm;
+ }
+ div.measuring {
+ background: url(chrome://global/skin/media/throbber.png) no-repeat center;
+ min-width: 36px;
+ min-height: 36px;
+ }
+ li.delta {
+ border-left-width: 5px;
+ border-left-style: solid;
+ padding-left: 1em;
+ list-style: none;
+ }
+ li.delta[impact="0"] {
+ border-left-color: rgb(0, 255, 0);
+ }
+ li.delta[impact="1"] {
+ border-left-color: rgb(24, 231, 0);
+ }
+ li.delta[impact="2"] {
+ border-left-color: rgb(48, 207, 0);
+ }
+ li.delta[impact="3"] {
+ border-left-color: rgb(72, 183, 0);
+ }
+ li.delta[impact="4"] {
+ border-left-color: rgb(96, 159, 0);
+ }
+ li.delta[impact="5"] {
+ border-left-color: rgb(120, 135, 0);
+ }
+ li.delta[impact="6"] {
+ border-left-color: rgb(144, 111, 0);
+ }
+ li.delta[impact="7"] {
+ border-left-color: rgb(168, 87, 0);
+ }
+ li.delta[impact="8"] {
+ border-left-color: rgb(192, 63, 0);
+ }
+ li.delta[impact="9"] {
+ border-left-color: rgb(216, 39, 0);
+ }
+ li.delta[impact="10"] {
+ border-left-color: rgb(240, 15, 0);
+ }
+ li.delta[impact="11"] {
+ border-left-color: rgb(255, 0, 0);
+ }
+
+ #subprocess-reports {
+ background-color: var(--aboutSupport-table-background);
+ color: var(--in-content-text-color);
+ font: message-box;
+ text-align: start;
+ border: 1px solid var(--in-content-border-color);
+ border-spacing: 0px;
+ float: right;
+ margin-bottom: 20px;
+ -moz-margin-start: 20px;
+ -moz-margin-end: 0;
+ width: 100%;
+ }
+ #subprocess-reports:-moz-dir(rtl) {
+ float: left;
+ }
+ #subprocess-reports th,
+ #subprocess-reports td {
+ border: 1px solid var(--in-content-border-color);
+ padding: 4px;
+ }
+ #subprocess-reports thead th {
+ text-align: center;
+ }
+ #subprocess-reports th {
+ text-align: start;
+ background-color: var(--in-content-table-header-background);
+ color: var(--in-content-selected-text);
+ }
+ #subprocess-reports th.column {
+ white-space: nowrap;
+ width: 0px;
+ }
+ #subprocess-reports td {
+ background-color: #ebebeb;
+ text-align: start;
+ border-color: var(--in-content-table-border-dark-color);
+ border-spacing: 40px;
+ }
+ .options {
+ width: 100%;
+ }
+ .options > .toggle-container-with-text {
+ display: inline-flex;
+ }
+ .options > .toggle-container-with-text:not(:first-child) {
+ margin-inline-start: 2ch;
+ }
+ </style>
+ </head>
+ <body onload="go()">
+ <div>
+ <h2>Memory usage of Subprocesses</h2>
+ <table id="subprocess-reports">
+ <tr>
+ <th>Process ID</th>
+ <th title="RSS measures the pages resident in the main memory for the process">Resident Set Size</th>
+ <th title="USS gives a count of unshared pages, unique to the process">Unique Set Size</th>
+ </tr>
+ </table>
+ </div>
+ <div class="options">
+ <div class="toggle-container-with-text">
+ <input type="checkbox" checked="false" id="check-display-recent"></input>
+ <label for="check-display-recent" id="label-display-recent">Display only the last few seconds.</label>
+ </div>
+ <div class="toggle-container-with-text">
+ <input type="checkbox" checked="true" id="check-autorefresh"></input>
+ <label for="check-autorefresh">Refresh automatically</label>
+ </div>
+ </div>
+ <div>
+ <h2>Performance of Add-ons</h2>
+ <div id="addons" class="measuring">
+ </div>
+ </div>
+ <div>
+ <h2>Performance of Web pages</h2>
+ <div id="webpages" class="measuring">
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/components/aboutperformance/jar.mn b/components/aboutperformance/jar.mn
new file mode 100644
index 000000000..96e046d8e
--- /dev/null
+++ b/components/aboutperformance/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/.
+
+toolkit.jar:
+ content/global/aboutPerformance.xhtml (content/aboutPerformance.xhtml)
+ content/global/aboutPerformance.js (content/aboutPerformance.js)
diff --git a/components/aboutperformance/moz.build b/components/aboutperformance/moz.build
new file mode 100644
index 000000000..aee4b90a5
--- /dev/null
+++ b/components/aboutperformance/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/addoncompat/CompatWarning.jsm b/components/addoncompat/CompatWarning.jsm
new file mode 100644
index 000000000..c4d45adee
--- /dev/null
+++ b/components/addoncompat/CompatWarning.jsm
@@ -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/.
+
+this.EXPORTED_SYMBOLS = ["CompatWarning"];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+
+function section(number, url)
+{
+ const baseURL = "https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts";
+ return { number, url: baseURL + url };
+}
+
+var CompatWarning = {
+ // Sometimes we want to generate a warning, but put off issuing it
+ // until later. For example, if someone registers a listener, we
+ // might only want to warn about it if the listener actually
+ // fires. However, we want the warning to show a stack for the
+ // registration site.
+ delayedWarning: function(msg, addon, warning) {
+ function isShimLayer(filename) {
+ return filename.indexOf("CompatWarning.jsm") != -1 ||
+ filename.indexOf("RemoteAddonsParent.jsm") != -1 ||
+ filename.indexOf("RemoteAddonsChild.jsm") != -1 ||
+ filename.indexOf("multiprocessShims.js") != -1;
+ }
+
+ let stack = Components.stack;
+ while (stack && isShimLayer(stack.filename))
+ stack = stack.caller;
+
+ let alreadyWarned = false;
+
+ return function() {
+ if (alreadyWarned) {
+ return;
+ }
+ alreadyWarned = true;
+
+ if (!Preferences.get("dom.ipc.shims.enabledWarnings", false))
+ return;
+
+ let error = Cc['@mozilla.org/scripterror;1'].createInstance(Ci.nsIScriptError);
+ if (!error || !Services.console) {
+ // Too late during shutdown to use the nsIConsole
+ return;
+ }
+
+ let message = `Warning: ${msg}`;
+ if (warning)
+ message += `\nMore info at: ${warning.url}`;
+
+ error.init(
+ /* message*/ message,
+ /* sourceName*/ stack ? stack.filename : "",
+ /* sourceLine*/ stack ? stack.sourceLine : "",
+ /* lineNumber*/ stack ? stack.lineNumber : 0,
+ /* columnNumber*/ 0,
+ /* flags*/ Ci.nsIScriptError.warningFlag,
+ /* category*/ "chrome javascript");
+ Services.console.logMessage(error);
+
+ if (Preferences.get("dom.ipc.shims.dumpWarnings", false)) {
+ dump(message + "\n");
+ while (stack) {
+ dump(stack + "\n");
+ stack = stack.caller;
+ }
+ dump("\n");
+ }
+ };
+ },
+
+ warn: function(msg, addon, warning) {
+ let delayed = this.delayedWarning(msg, addon, warning);
+ delayed();
+ },
+
+ warnings: {
+ content: section(1, "#gBrowser.contentWindow.2C_window.content..."),
+ limitations_of_CPOWs: section(2, "#Limitations_of_CPOWs"),
+ nsIContentPolicy: section(3, "#nsIContentPolicy"),
+ nsIWebProgressListener: section(4, "#nsIWebProgressListener"),
+ observers: section(5, "#Observers_in_the_chrome_process"),
+ DOM_events: section(6, "#DOM_Events"),
+ sandboxes: section(7, "#Sandboxes"),
+ JSMs: section(8, "#JavaScript_code_modules_(JSMs)"),
+ nsIAboutModule: section(9, "#nsIAboutModule"),
+ // If more than 14 values appear here, you need to change the
+ // ADDON_SHIM_USAGE histogram definition in Histograms.json.
+ },
+};
diff --git a/components/addoncompat/Prefetcher.jsm b/components/addoncompat/Prefetcher.jsm
new file mode 100644
index 000000000..2d836690c
--- /dev/null
+++ b/components/addoncompat/Prefetcher.jsm
@@ -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/.
+
+this.EXPORTED_SYMBOLS = ["Prefetcher"];
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+ "resource://gre/modules/Preferences.jsm");
+
+// Rules are defined at the bottom of this file.
+var PrefetcherRules = {};
+
+/*
+ * When events that trigger in the content process are forwarded to
+ * add-ons in the chrome process, we expect the add-ons to send a lot
+ * of CPOWs to query content nodes while processing the events. To
+ * speed this up, the prefetching system anticipates which properties
+ * will be read and reads them ahead of time. The prefetched
+ * properties are passed to the chrome process along with each
+ * event. A typical scenario might work like this:
+ *
+ * 1. "load" event fires in content
+ * 2. Content process prefetches:
+ * event.target.defaultView = <win 1>
+ * <win 1>.location = <location obj>
+ * event.target.getElementsByTagName("form") = [<elt 1>, <elt 2>]
+ * <elt 1>.id = "login-form"
+ * <elt 2>.id = "subscribe-form"
+ * 3. Content process forwards "load" event to add-on along with
+ * prefetched data
+ * 4. Add-on reads:
+ * event.target.defaultView (already prefetched)
+ * event.target.getElementsByTagName("form") (already prefetched)
+ * <elt 1>.id (already prefetched)
+ * <elt 1>.className (not prefetched; CPOW must be sent)
+ *
+ * The amount of data to prefetch is determined based on the add-on ID
+ * and the event type. The specific data to select is determined using
+ * a set of Datalog-like rules (http://en.wikipedia.org/wiki/Datalog).
+ *
+ * Rules operate on a series of "tables" like in a database. Each
+ * table contains a set of content-process objects. When an event
+ * handler runs, it seeds some initial tables with objects of
+ * interest. For example, the Event table might start out containing
+ * the event that fired.
+ *
+ * Objects are added to tables using a set of rules of the form "if X
+ * is in table A, then add F(X) to table B", where F(X) is typically a
+ * property access or a method call. The most common functions F are:
+ *
+ * PropertyOp(destTable, sourceTable, property):
+ * For each object X in sourceTable, add X.property to destTable.
+ * MethodOp(destTable, sourceTable, method, args):
+ * For each object X in sourceTable, add X.method(args) to destTable.
+ * CollectionOp(destTable, sourceTable):
+ * For each object X in sourceTable, add X[i] to destTable for
+ * all i from 0 to X.length - 1.
+ *
+ * To generate the prefetching in the example above, the following
+ * rules would work:
+ *
+ * 1. PropertyOp("EventTarget", "Event", "target")
+ * 2. PropertyOp("Window", "EventTarget", "defaultView")
+ * 3. MethodOp("FormCollection", "EventTarget", "getElementsByTagName", "form")
+ * 4. CollectionOp("Form", "FormCollection")
+ * 5. PropertyOp(null, "Form", "id")
+ *
+ * Rules are written at the bottom of this file.
+ *
+ * When a rule runs, it will usually generate some cache entries that
+ * will be passed to the chrome process. For example, when PropertyOp
+ * prefetches obj.prop and gets the value X, it caches the value of
+ * obj and X. When the chrome process receives this data, it creates a
+ * two-level map [obj -> [prop -> X]]. When the add-on accesses a
+ * property on obj, the add-on shim code consults this map to see if
+ * the property has already been cached.
+ */
+
+const PREF_PREFETCHING_ENABLED = "extensions.interposition.prefetching";
+
+function isPrimitive(v) {
+ if (!v)
+ return true;
+ let type = typeof(v);
+ return type !== "object" && type !== "function";
+}
+
+function objAddr(obj)
+{
+/*
+ if (!isPrimitive(obj)) {
+ return String(obj) + "[" + Cu.getJSTestingFunctions().objectAddress(obj) + "]";
+ }
+ return String(obj);
+*/
+}
+
+function log(/* ...args*/)
+{
+/*
+ for (let arg of args) {
+ dump(arg);
+ dump(" ");
+ }
+ dump("\n");
+*/
+}
+
+function logPrefetch(/* kind, value1, component, value2*/)
+{
+/*
+ log("prefetching", kind, objAddr(value1) + "." + component, "=", objAddr(value2));
+*/
+}
+
+/*
+ * All the Op classes (representing Datalog rules) have the same interface:
+ * outputTable: Table that objects generated by the rule are added to.
+ * Note that this can be null.
+ * inputTable: Table that the rule draws objects from.
+ * addObject(database, obj): Called when an object is added to inputTable.
+ * This code should take care of adding objects to outputTable.
+ * Data to be cached should be stored by calling database.cache.
+ * makeCacheEntry(item, cache):
+ * Called by the chrome process to create the two-level map of
+ * prefetched objects. |item| holds the cached data
+ * generated by the content process. |cache| is the map to be
+ * generated.
+ */
+
+function PropertyOp(outputTable, inputTable, prop)
+{
+ this.outputTable = outputTable;
+ this.inputTable = inputTable;
+ this.prop = prop;
+}
+
+PropertyOp.prototype.addObject = function(database, obj)
+{
+ let has = false, propValue;
+ try {
+ if (this.prop in obj) {
+ has = true;
+ propValue = obj[this.prop];
+ }
+ } catch (e) {
+ // Don't cache anything if an exception is thrown.
+ return;
+ }
+
+ logPrefetch("prop", obj, this.prop, propValue);
+ database.cache(this.index, obj, has, propValue);
+ if (has && !isPrimitive(propValue) && this.outputTable) {
+ database.add(this.outputTable, propValue);
+ }
+}
+
+PropertyOp.prototype.makeCacheEntry = function(item, cache)
+{
+ let [, obj, , propValue] = item;
+
+ let desc = { configurable: false, enumerable: true, writable: false, value: propValue };
+
+ if (!cache.has(obj)) {
+ cache.set(obj, new Map());
+ }
+ let propMap = cache.get(obj);
+ propMap.set(this.prop, desc);
+}
+
+function MethodOp(outputTable, inputTable, method, ...args)
+{
+ this.outputTable = outputTable;
+ this.inputTable = inputTable;
+ this.method = method;
+ this.args = args;
+}
+
+MethodOp.prototype.addObject = function(database, obj)
+{
+ let result;
+ try {
+ result = obj[this.method].apply(obj, this.args);
+ } catch (e) {
+ // Don't cache anything if an exception is thrown.
+ return;
+ }
+
+ logPrefetch("method", obj, this.method + "(" + this.args + ")", result);
+ database.cache(this.index, obj, result);
+ if (!isPrimitive(result) && this.outputTable) {
+ database.add(this.outputTable, result);
+ }
+}
+
+MethodOp.prototype.makeCacheEntry = function(item, cache)
+{
+ let [, obj, result] = item;
+
+ if (!cache.has(obj)) {
+ cache.set(obj, new Map());
+ }
+ let propMap = cache.get(obj);
+ let fallback = propMap.get(this.method);
+
+ let method = this.method;
+ let selfArgs = this.args;
+ let methodImpl = function(...args) {
+ if (args.length == selfArgs.length && args.every((v, i) => v === selfArgs[i])) {
+ return result;
+ }
+
+ if (fallback) {
+ return fallback.value(...args);
+ }
+ return obj[method](...args);
+ };
+
+ let desc = { configurable: false, enumerable: true, writable: false, value: methodImpl };
+ propMap.set(this.method, desc);
+}
+
+function CollectionOp(outputTable, inputTable)
+{
+ this.outputTable = outputTable;
+ this.inputTable = inputTable;
+}
+
+CollectionOp.prototype.addObject = function(database, obj)
+{
+ let elements = [];
+ try {
+ let len = obj.length;
+ for (let i = 0; i < len; i++) {
+ logPrefetch("index", obj, i, obj[i]);
+ elements.push(obj[i]);
+ }
+ } catch (e) {
+ // Don't cache anything if an exception is thrown.
+ return;
+ }
+
+ database.cache(this.index, obj, ...elements);
+ for (let i = 0; i < elements.length; i++) {
+ if (!isPrimitive(elements[i]) && this.outputTable) {
+ database.add(this.outputTable, elements[i]);
+ }
+ }
+}
+
+CollectionOp.prototype.makeCacheEntry = function(item, cache)
+{
+ let [, obj, ...elements] = item;
+
+ if (!cache.has(obj)) {
+ cache.set(obj, new Map());
+ }
+ let propMap = cache.get(obj);
+
+ let lenDesc = { configurable: false, enumerable: true, writable: false, value: elements.length };
+ propMap.set("length", lenDesc);
+
+ for (let i = 0; i < elements.length; i++) {
+ let desc = { configurable: false, enumerable: true, writable: false, value: elements[i] };
+ propMap.set(i, desc);
+ }
+}
+
+function CopyOp(outputTable, inputTable)
+{
+ this.outputTable = outputTable;
+ this.inputTable = inputTable;
+}
+
+CopyOp.prototype.addObject = function(database, obj)
+{
+ database.add(this.outputTable, obj);
+}
+
+function Database(trigger, addons)
+{
+ // Create a map of rules that apply to this specific trigger and set
+ // of add-ons. The rules are indexed based on their inputTable.
+ this.rules = new Map();
+ for (let addon of addons) {
+ let addonRules = PrefetcherRules[addon] || {};
+ let triggerRules = addonRules[trigger] || [];
+ for (let rule of triggerRules) {
+ let inTable = rule.inputTable;
+ if (!this.rules.has(inTable)) {
+ this.rules.set(inTable, new Set());
+ }
+ let set = this.rules.get(inTable);
+ set.add(rule);
+ }
+ }
+
+ // this.tables maps table names to sets of objects contained in them.
+ this.tables = new Map();
+
+ // todo is a worklist of items added to tables that have not had
+ // rules run on them yet.
+ this.todo = [];
+
+ // Cached data to be sent to the chrome process.
+ this.cached = [];
+}
+
+Database.prototype = {
+ // Add an object to a table.
+ add: function(table, obj) {
+ if (!this.tables.has(table)) {
+ this.tables.set(table, new Set());
+ }
+ let tableSet = this.tables.get(table);
+ if (tableSet.has(obj)) {
+ return;
+ }
+ tableSet.add(obj);
+
+ this.todo.push([table, obj]);
+ },
+
+ cache: function(...args) {
+ this.cached.push(args);
+ },
+
+ // Run a fixed-point iteration that adds objects to table based on
+ // this.rules until there are no more objects to add.
+ process: function() {
+ while (this.todo.length) {
+ let [table, obj] = this.todo.pop();
+ let rules = this.rules.get(table);
+ if (!rules) {
+ continue;
+ }
+ for (let rule of rules) {
+ rule.addObject(this, obj);
+ }
+ }
+ },
+};
+
+var Prefetcher = {
+ init: function() {
+ // Give an index to each rule and store it in this.ruleMap based
+ // on the index. The index is used to serialize and deserialize
+ // data from content to chrome.
+ let counter = 0;
+ this.ruleMap = new Map();
+ for (let addon in PrefetcherRules) {
+ for (let trigger in PrefetcherRules[addon]) {
+ for (let rule of PrefetcherRules[addon][trigger]) {
+ rule.index = counter++;
+ this.ruleMap.set(rule.index, rule);
+ }
+ }
+ }
+
+ this.prefetchingEnabled = Preferences.get(PREF_PREFETCHING_ENABLED, false);
+ Services.prefs.addObserver(PREF_PREFETCHING_ENABLED, this, false);
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic == "xpcom-shutdown") {
+ Services.prefs.removeObserver(PREF_PREFETCHING_ENABLED, this);
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ } else if (topic == PREF_PREFETCHING_ENABLED) {
+ this.prefetchingEnabled = Preferences.get(PREF_PREFETCHING_ENABLED, false);
+ }
+ },
+
+ // Called when an event occurs in the content process. The event is
+ // described by the trigger string. |addons| is a list of addons
+ // that have listeners installed for the event. |args| is
+ // event-specific data (such as the event object).
+ prefetch: function(trigger, addons, args) {
+ if (!this.prefetchingEnabled) {
+ return [[], []];
+ }
+
+ let db = new Database(trigger, addons);
+ for (let table in args) {
+ log("root", table, "=", objAddr(args[table]));
+ db.add(table, args[table]);
+ }
+
+ // Prefetch objects and add them to tables.
+ db.process();
+
+ // Data passed to sendAsyncMessage must be split into a JSON
+ // portion and a CPOW portion. This code splits apart db.cached
+ // into these two pieces. Any object in db.cache is added to an
+ // array of CPOWs and replaced with {cpow: <index in array>}.
+ let cpowIndexes = new Map();
+ let prefetched = [];
+ let cpows = [];
+ for (let item of db.cached) {
+ item = item.map((elt) => {
+ if (!isPrimitive(elt)) {
+ if (!cpowIndexes.has(elt)) {
+ let index = cpows.length;
+ cpows.push(elt);
+ cpowIndexes.set(elt, index);
+ }
+ return {cpow: cpowIndexes.get(elt)};
+ }
+ return elt;
+ });
+
+ prefetched.push(item);
+ }
+
+ return [prefetched, cpows];
+ },
+
+ cache: null,
+
+ // Generate a two-level mapping based on cached data received from
+ // the content process.
+ generateCache: function(prefetched, cpows) {
+ let cache = new Map();
+ for (let item of prefetched) {
+ // Replace anything of the form {cpow: <index>} with the actual
+ // object in |cpows|.
+ item = item.map((elt) => {
+ if (!isPrimitive(elt)) {
+ return cpows[elt.cpow];
+ }
+ return elt;
+ });
+
+ let index = item[0];
+ let op = this.ruleMap.get(index);
+ op.makeCacheEntry(item, cache);
+ }
+ return cache;
+ },
+
+ // Run |func|, using the prefetched data in |prefetched| and |cpows|
+ // as a cache.
+ withPrefetching: function(prefetched, cpows, func) {
+ if (!this.prefetchingEnabled) {
+ return func();
+ }
+
+ this.cache = this.generateCache(prefetched, cpows);
+
+ try {
+ log("Prefetching on");
+ return func();
+ } finally {
+ // After we return from this event handler, the content process
+ // is free to continue executing, so we invalidate our cache.
+ log("Prefetching off");
+ this.cache = null;
+ }
+ },
+
+ // Called by shim code in the chrome process to check if target.prop
+ // is cached.
+ lookupInCache: function(addon, target, prop) {
+ if (!this.cache || !Cu.isCrossProcessWrapper(target)) {
+ return null;
+ }
+
+ let propMap = this.cache.get(target);
+ if (!propMap) {
+ return null;
+ }
+
+ return propMap.get(prop);
+ },
+};
+
+var AdblockId = "{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}";
+var AdblockRules = {
+ "ContentPolicy.shouldLoad": [
+ new MethodOp("Node", "InitNode", "QueryInterface", Ci.nsISupports),
+ new PropertyOp("Document", "Node", "ownerDocument"),
+ new PropertyOp("Window", "Node", "defaultView"),
+ new PropertyOp("Window", "Document", "defaultView"),
+ new PropertyOp("TopWindow", "Window", "top"),
+ new PropertyOp("WindowLocation", "Window", "location"),
+ new PropertyOp(null, "WindowLocation", "href"),
+ new PropertyOp("Window", "Window", "parent"),
+ new PropertyOp(null, "Window", "name"),
+ new PropertyOp("Document", "Window", "document"),
+ new PropertyOp("TopDocumentElement", "Document", "documentElement"),
+ new MethodOp(null, "TopDocumentElement", "getAttribute", "data-adblockkey"),
+ ]
+};
+PrefetcherRules[AdblockId] = AdblockRules;
+
+var LastpassId = "support@lastpass.com";
+var LastpassRules = {
+ "EventTarget.handleEvent": [
+ new PropertyOp("EventTarget", "Event", "target"),
+ new PropertyOp("EventOriginalTarget", "Event", "originalTarget"),
+ new PropertyOp("Window", "EventOriginalTarget", "defaultView"),
+
+ new CopyOp("Frame", "Window"),
+ new PropertyOp("FrameCollection", "Window", "frames"),
+ new CollectionOp("Frame", "FrameCollection"),
+ new PropertyOp("FrameCollection", "Frame", "frames"),
+ new PropertyOp("FrameDocument", "Frame", "document"),
+ new PropertyOp(null, "Frame", "window"),
+ new PropertyOp(null, "FrameDocument", "defaultView"),
+
+ new PropertyOp("FrameDocumentLocation", "FrameDocument", "location"),
+ new PropertyOp(null, "FrameDocumentLocation", "href"),
+ new PropertyOp("FrameLocation", "Frame", "location"),
+ new PropertyOp(null, "FrameLocation", "href"),
+
+ new MethodOp("FormCollection", "FrameDocument", "getElementsByTagName", "form"),
+ new MethodOp("FormCollection", "FrameDocument", "getElementsByTagName", "FORM"),
+ new CollectionOp("Form", "FormCollection"),
+ new PropertyOp("FormElementCollection", "Form", "elements"),
+ new CollectionOp("FormElement", "FormElementCollection"),
+ new PropertyOp("Style", "Form", "style"),
+
+ new PropertyOp(null, "FormElement", "type"),
+ new PropertyOp(null, "FormElement", "name"),
+ new PropertyOp(null, "FormElement", "value"),
+ new PropertyOp(null, "FormElement", "tagName"),
+ new PropertyOp(null, "FormElement", "id"),
+ new PropertyOp("Style", "FormElement", "style"),
+
+ new PropertyOp(null, "Style", "visibility"),
+
+ new MethodOp("MetaElementsCollection", "EventOriginalTarget", "getElementsByTagName", "meta"),
+ new CollectionOp("MetaElement", "MetaElementsCollection"),
+ new PropertyOp(null, "MetaElement", "httpEquiv"),
+
+ new MethodOp("InputElementCollection", "FrameDocument", "getElementsByTagName", "input"),
+ new MethodOp("InputElementCollection", "FrameDocument", "getElementsByTagName", "INPUT"),
+ new CollectionOp("InputElement", "InputElementCollection"),
+ new PropertyOp(null, "InputElement", "type"),
+ new PropertyOp(null, "InputElement", "name"),
+ new PropertyOp(null, "InputElement", "tagName"),
+ new PropertyOp(null, "InputElement", "form"),
+
+ new PropertyOp("BodyElement", "FrameDocument", "body"),
+ new PropertyOp("BodyInnerText", "BodyElement", "innerText"),
+
+ new PropertyOp("DocumentFormCollection", "FrameDocument", "forms"),
+ new CollectionOp("DocumentForm", "DocumentFormCollection"),
+ ]
+};
+PrefetcherRules[LastpassId] = LastpassRules;
diff --git a/components/addoncompat/RemoteAddonsChild.jsm b/components/addoncompat/RemoteAddonsChild.jsm
new file mode 100644
index 000000000..1aacc7f7a
--- /dev/null
+++ b/components/addoncompat/RemoteAddonsChild.jsm
@@ -0,0 +1,576 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.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 = ["RemoteAddonsChild"];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+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, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Prefetcher",
+ "resource://gre/modules/Prefetcher.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "SystemPrincipal",
+ "@mozilla.org/systemprincipal;1", "nsIPrincipal");
+
+XPCOMUtils.defineLazyServiceGetter(this, "contentSecManager",
+ "@mozilla.org/contentsecuritymanager;1",
+ "nsIContentSecurityManager");
+
+// Similar to Python. Returns dict[key] if it exists. Otherwise,
+// sets dict[key] to default_ and returns default_.
+function setDefault(dict, key, default_)
+{
+ if (key in dict) {
+ return dict[key];
+ }
+ dict[key] = default_;
+ return default_;
+}
+
+// This code keeps track of a set of paths of the form [component_1,
+// ..., component_n]. The components can be strings or booleans. The
+// child is notified whenever a path is added or removed, and new
+// children can request the current set of paths. The purpose is to
+// keep track of all the observers and events that the child should
+// monitor for the parent.
+//
+// In the child, clients can watch for changes to all paths that start
+// with a given component.
+var NotificationTracker = {
+ init: function() {
+ let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsISyncMessageSender);
+ cpmm.addMessageListener("Addons:ChangeNotification", this);
+ this._paths = cpmm.initialProcessData.remoteAddonsNotificationPaths;
+ this._registered = new Map();
+ this._watchers = {};
+ },
+
+ receiveMessage: function(msg) {
+ let path = msg.data.path;
+ let count = msg.data.count;
+
+ let tracked = this._paths;
+ for (let component of path) {
+ tracked = setDefault(tracked, component, {});
+ }
+
+ tracked._count = count;
+
+ if (this._watchers[path[0]]) {
+ for (let watcher of this._watchers[path[0]]) {
+ this.runCallback(watcher, path, count);
+ }
+ }
+ },
+
+ runCallback: function(watcher, path, count) {
+ let pathString = path.join("/");
+ let registeredSet = this._registered.get(watcher);
+ let registered = registeredSet.has(pathString);
+ if (count && !registered) {
+ watcher.track(path, true);
+ registeredSet.add(pathString);
+ } else if (!count && registered) {
+ watcher.track(path, false);
+ registeredSet.delete(pathString);
+ }
+ },
+
+ findPaths: function(prefix) {
+ if (!this._paths) {
+ return [];
+ }
+
+ let tracked = this._paths;
+ for (let component of prefix) {
+ tracked = setDefault(tracked, component, {});
+ }
+
+ let result = [];
+ let enumerate = (tracked, curPath) => {
+ for (let component in tracked) {
+ if (component == "_count") {
+ result.push([curPath, tracked._count]);
+ } else {
+ let path = curPath.slice();
+ if (component === "true") {
+ component = true;
+ } else if (component === "false") {
+ component = false;
+ }
+ path.push(component);
+ enumerate(tracked[component], path);
+ }
+ }
+ }
+ enumerate(tracked, prefix);
+
+ return result;
+ },
+
+ findSuffixes: function(prefix) {
+ let paths = this.findPaths(prefix);
+ return paths.map(([path, count]) => path[path.length - 1]);
+ },
+
+ watch: function(component1, watcher) {
+ setDefault(this._watchers, component1, []).push(watcher);
+ this._registered.set(watcher, new Set());
+
+ let paths = this.findPaths([component1]);
+ for (let [path, count] of paths) {
+ this.runCallback(watcher, path, count);
+ }
+ },
+
+ unwatch: function(component1, watcher) {
+ let watchers = this._watchers[component1];
+ let index = watchers.lastIndexOf(watcher);
+ if (index > -1) {
+ watchers.splice(index, 1);
+ }
+
+ this._registered.delete(watcher);
+ },
+
+ getCount(component1) {
+ return this.findPaths([component1]).length;
+ },
+};
+
+// This code registers an nsIContentPolicy in the child process. When
+// it runs, it notifies the parent that it needs to run its own
+// nsIContentPolicy list. If any policy in the parent rejects a
+// resource load, that answer is returned to the child.
+var ContentPolicyChild = {
+ _classDescription: "Addon shim content policy",
+ _classID: Components.ID("6e869130-635c-11e2-bcfd-0800200c9a66"),
+ _contractID: "@mozilla.org/addon-child/policy;1",
+
+ init: function() {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(this._classID, this._classDescription, this._contractID, this);
+
+ NotificationTracker.watch("content-policy", this);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIObserver,
+ Ci.nsIChannelEventSink, Ci.nsIFactory,
+ Ci.nsISupportsWeakReference]),
+
+ track: function(path, register) {
+ let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+ if (register) {
+ catMan.addCategoryEntry("content-policy", this._contractID, this._contractID, false, true);
+ } else {
+ catMan.deleteCategoryEntry("content-policy", this._contractID, false);
+ }
+ },
+
+ shouldLoad: function(contentType, contentLocation, requestOrigin,
+ node, mimeTypeGuess, extra, requestPrincipal) {
+ let addons = NotificationTracker.findSuffixes(["content-policy"]);
+ let [prefetched, cpows] = Prefetcher.prefetch("ContentPolicy.shouldLoad",
+ addons, {InitNode: node});
+ cpows.node = node;
+
+ let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsISyncMessageSender);
+ let rval = cpmm.sendRpcMessage("Addons:ContentPolicy:Run", {
+ contentType: contentType,
+ contentLocation: contentLocation.spec,
+ requestOrigin: requestOrigin ? requestOrigin.spec : null,
+ mimeTypeGuess: mimeTypeGuess,
+ requestPrincipal: requestPrincipal,
+ prefetched: prefetched,
+ }, cpows);
+ if (rval.length != 1) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+
+ return rval[0];
+ },
+
+ shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+
+ createInstance: function(outer, iid) {
+ if (outer) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(iid);
+ },
+};
+
+// This is a shim channel whose only purpose is to return some string
+// data from an about: protocol handler.
+function AboutProtocolChannel(uri, contractID, loadInfo)
+{
+ this.URI = uri;
+ this.originalURI = uri;
+ this._contractID = contractID;
+ this._loadingPrincipal = loadInfo.loadingPrincipal;
+ this._securityFlags = loadInfo.securityFlags;
+ this._contentPolicyType = loadInfo.externalContentPolicyType;
+}
+
+AboutProtocolChannel.prototype = {
+ contentCharset: "utf-8",
+ contentLength: 0,
+ owner: SystemPrincipal,
+ securityInfo: null,
+ notificationCallbacks: null,
+ loadFlags: 0,
+ loadGroup: null,
+ name: null,
+ status: Cr.NS_OK,
+
+ asyncOpen: function(listener, context) {
+ // Ask the parent to synchronously read all the data from the channel.
+ let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsISyncMessageSender);
+ let rval = cpmm.sendRpcMessage("Addons:AboutProtocol:OpenChannel", {
+ uri: this.URI.spec,
+ contractID: this._contractID,
+ loadingPrincipal: this._loadingPrincipal,
+ securityFlags: this._securityFlags,
+ contentPolicyType: this._contentPolicyType
+ }, {
+ notificationCallbacks: this.notificationCallbacks,
+ loadGroupNotificationCallbacks: this.loadGroup ? this.loadGroup.notificationCallbacks : null,
+ });
+
+ if (rval.length != 1) {
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ let {data, contentType} = rval[0];
+ this.contentType = contentType;
+
+ // Return the data via an nsIStringInputStream.
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ stream.setData(data, data.length);
+
+ let runnable = {
+ run: () => {
+ try {
+ listener.onStartRequest(this, context);
+ } catch (e) {}
+ try {
+ listener.onDataAvailable(this, context, stream, 0, stream.available());
+ } catch (e) {}
+ try {
+ listener.onStopRequest(this, context, Cr.NS_OK);
+ } catch (e) {}
+ }
+ };
+ Services.tm.currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
+ },
+
+ asyncOpen2: function(listener) {
+ // throws an error if security checks fail
+ var outListener = contentSecManager.performSecurityCheck(this, listener);
+ this.asyncOpen(outListener, null);
+ },
+
+ open: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ open2: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ isPending: function() {
+ return false;
+ },
+
+ cancel: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ suspend: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ resume: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
+};
+
+// This shim protocol handler is used when content fetches an about: URL.
+function AboutProtocolInstance(contractID)
+{
+ this._contractID = contractID;
+ this._uriFlags = undefined;
+}
+
+AboutProtocolInstance.prototype = {
+ createInstance: function(outer, iid) {
+ if (outer != null) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+
+ return this.QueryInterface(iid);
+ },
+
+ getURIFlags: function(uri) {
+ // Cache the result to avoid the extra IPC.
+ if (this._uriFlags !== undefined) {
+ return this._uriFlags;
+ }
+
+ let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsISyncMessageSender);
+
+ let rval = cpmm.sendRpcMessage("Addons:AboutProtocol:GetURIFlags", {
+ uri: uri.spec,
+ contractID: this._contractID
+ });
+
+ if (rval.length != 1) {
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ this._uriFlags = rval[0];
+ return this._uriFlags;
+ },
+
+ // We take some shortcuts here. Ideally, we would return a CPOW that
+ // wraps the add-on's nsIChannel. However, many of the methods
+ // related to nsIChannel are marked [noscript], so they're not
+ // available to CPOWs. Consequently, we return a shim channel that,
+ // when opened, asks the parent to open the channel and read out all
+ // the data.
+ newChannel: function(uri, loadInfo) {
+ return new AboutProtocolChannel(uri, this._contractID, loadInfo);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory, Ci.nsIAboutModule])
+};
+
+var AboutProtocolChild = {
+ _classDescription: "Addon shim about: protocol handler",
+
+ init: function() {
+ // Maps contractIDs to instances
+ this._instances = new Map();
+ // Maps contractIDs to classIDs
+ this._classIDs = new Map();
+ NotificationTracker.watch("about-protocol", this);
+ },
+
+ track: function(path, register) {
+ let contractID = path[1];
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ if (register) {
+ let instance = new AboutProtocolInstance(contractID);
+ let classID = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator)
+ .generateUUID();
+
+ this._instances.set(contractID, instance);
+ this._classIDs.set(contractID, classID);
+ registrar.registerFactory(classID, this._classDescription, contractID, instance);
+ } else {
+ let instance = this._instances.get(contractID);
+ let classID = this._classIDs.get(contractID);
+ registrar.unregisterFactory(classID, instance);
+ this._instances.delete(contractID);
+ this._classIDs.delete(contractID);
+ }
+ },
+};
+
+// This code registers observers in the child whenever an add-on in
+// the parent asks for notifications on the given topic.
+var ObserverChild = {
+ init: function() {
+ NotificationTracker.watch("observer", this);
+ },
+
+ track: function(path, register) {
+ let topic = path[1];
+ if (register) {
+ Services.obs.addObserver(this, topic, false);
+ } else {
+ Services.obs.removeObserver(this, topic);
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsISyncMessageSender);
+ cpmm.sendRpcMessage("Addons:Observer:Run", {}, {
+ topic: topic,
+ subject: subject,
+ data: data
+ });
+ }
+};
+
+// There is one of these objects per browser tab in the child. When an
+// add-on in the parent listens for an event, this child object
+// listens for that event in the child.
+function EventTargetChild(childGlobal)
+{
+ this._childGlobal = childGlobal;
+ this.capturingHandler = (event) => this.handleEvent(true, event);
+ this.nonCapturingHandler = (event) => this.handleEvent(false, event);
+ NotificationTracker.watch("event", this);
+}
+
+EventTargetChild.prototype = {
+ uninit: function() {
+ NotificationTracker.unwatch("event", this);
+ },
+
+ track: function(path, register) {
+ let eventType = path[1];
+ let useCapture = path[2];
+ let listener = useCapture ? this.capturingHandler : this.nonCapturingHandler;
+ if (register) {
+ this._childGlobal.addEventListener(eventType, listener, useCapture, true);
+ } else {
+ this._childGlobal.removeEventListener(eventType, listener, useCapture);
+ }
+ },
+
+ handleEvent: function(capturing, event) {
+ let addons = NotificationTracker.findSuffixes(["event", event.type, capturing]);
+ let [prefetched, cpows] = Prefetcher.prefetch("EventTarget.handleEvent",
+ addons,
+ {Event: event,
+ Window: this._childGlobal.content});
+ cpows.event = event;
+ cpows.eventTarget = event.target;
+
+ this._childGlobal.sendRpcMessage("Addons:Event:Run",
+ {type: event.type,
+ capturing: capturing,
+ isTrusted: event.isTrusted,
+ prefetched: prefetched},
+ cpows);
+ }
+};
+
+// The parent can create a sandbox to run code in the child
+// process. We actually create the sandbox in the child so that the
+// code runs there. However, managing the lifetime of these sandboxes
+// can be tricky. The parent references these sandboxes using CPOWs,
+// which only keep weak references. So we need to create a strong
+// reference in the child. For simplicity, we kill off these strong
+// references whenever we navigate away from the page for which the
+// sandbox was created.
+function SandboxChild(chromeGlobal)
+{
+ this.chromeGlobal = chromeGlobal;
+ this.sandboxes = [];
+}
+
+SandboxChild.prototype = {
+ uninit: function() {
+ this.clearSandboxes();
+ },
+
+ addListener: function() {
+ let webProgress = this.chromeGlobal.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
+ },
+
+ removeListener: function() {
+ let webProgress = this.chromeGlobal.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.removeProgressListener(this);
+ },
+
+ onLocationChange: function(webProgress, request, location, flags) {
+ this.clearSandboxes();
+ },
+
+ addSandbox: function(sandbox) {
+ if (this.sandboxes.length == 0) {
+ this.addListener();
+ }
+ this.sandboxes.push(sandbox);
+ },
+
+ clearSandboxes: function() {
+ if (this.sandboxes.length) {
+ this.removeListener();
+ }
+ this.sandboxes = [];
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference])
+};
+
+var RemoteAddonsChild = {
+ _ready: false,
+
+ makeReady: function() {
+ let shims = [
+ Prefetcher,
+ NotificationTracker,
+ ContentPolicyChild,
+ AboutProtocolChild,
+ ObserverChild,
+ ];
+
+ for (let shim of shims) {
+ try {
+ shim.init();
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ init: function(global) {
+
+ if (!this._ready) {
+ if (!Services.cpmm.initialProcessData.remoteAddonsParentInitted) {
+ return null;
+ }
+
+ this.makeReady();
+ this._ready = true;
+ }
+
+ global.sendAsyncMessage("Addons:RegisterGlobal", {}, {global: global});
+
+ let sandboxChild = new SandboxChild(global);
+ global.addSandbox = sandboxChild.addSandbox.bind(sandboxChild);
+
+ // Return this so it gets rooted in the content script.
+ return [new EventTargetChild(global), sandboxChild];
+ },
+
+ uninit: function(perTabShims) {
+ for (let shim of perTabShims) {
+ try {
+ shim.uninit();
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ get useSyncWebProgress() {
+ return NotificationTracker.getCount("web-progress") > 0;
+ },
+};
diff --git a/components/addoncompat/RemoteAddonsParent.jsm b/components/addoncompat/RemoteAddonsParent.jsm
new file mode 100644
index 000000000..5cadc2902
--- /dev/null
+++ b/components/addoncompat/RemoteAddonsParent.jsm
@@ -0,0 +1,1080 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.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 = ["RemoteAddonsParent"];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/RemoteWebProgress.jsm");
+Cu.import('resource://gre/modules/Services.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Prefetcher",
+ "resource://gre/modules/Prefetcher.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CompatWarning",
+ "resource://gre/modules/CompatWarning.jsm");
+
+Cu.permitCPOWsInScope(this);
+
+// Similar to Python. Returns dict[key] if it exists. Otherwise,
+// sets dict[key] to default_ and returns default_.
+function setDefault(dict, key, default_)
+{
+ if (key in dict) {
+ return dict[key];
+ }
+ dict[key] = default_;
+ return default_;
+}
+
+// This code keeps track of a set of paths of the form [component_1,
+// ..., component_n]. The components can be strings or booleans. The
+// child is notified whenever a path is added or removed, and new
+// children can request the current set of paths. The purpose is to
+// keep track of all the observers and events that the child should
+// monitor for the parent.
+var NotificationTracker = {
+ // _paths is a multi-level dictionary. Let's add paths [A, B] and
+ // [A, C]. Then _paths will look like this:
+ // { 'A': { 'B': { '_count': 1 }, 'C': { '_count': 1 } } }
+ // Each component in a path will be a key in some dictionary. At the
+ // end, the _count property keeps track of how many instances of the
+ // given path are present in _paths.
+ _paths: {},
+
+ init: function() {
+ let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+ ppmm.initialProcessData.remoteAddonsNotificationPaths = this._paths;
+ },
+
+ add: function(path) {
+ let tracked = this._paths;
+ for (let component of path) {
+ tracked = setDefault(tracked, component, {});
+ }
+ let count = tracked._count || 0;
+ count++;
+ tracked._count = count;
+
+ let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+ ppmm.broadcastAsyncMessage("Addons:ChangeNotification", {path: path, count: count});
+ },
+
+ remove: function(path) {
+ let tracked = this._paths;
+ for (let component of path) {
+ tracked = setDefault(tracked, component, {});
+ }
+ tracked._count--;
+
+ let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+ ppmm.broadcastAsyncMessage("Addons:ChangeNotification", {path: path, count: tracked._count});
+ },
+};
+NotificationTracker.init();
+
+// An interposition is an object with three properties: methods,
+// getters, and setters. See multiprocessShims.js for an explanation
+// of how these are used. The constructor here just allows one
+// interposition to inherit members from another.
+function Interposition(name, base)
+{
+ this.name = name;
+ if (base) {
+ this.methods = Object.create(base.methods);
+ this.getters = Object.create(base.getters);
+ this.setters = Object.create(base.setters);
+ } else {
+ this.methods = Object.create(null);
+ this.getters = Object.create(null);
+ this.setters = Object.create(null);
+ }
+}
+
+// This object is responsible for notifying the child when a new
+// content policy is added or removed. It also runs all the registered
+// add-on content policies when the child asks it to do so.
+var ContentPolicyParent = {
+ init: function() {
+ let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+ ppmm.addMessageListener("Addons:ContentPolicy:Run", this);
+
+ this._policies = new Map();
+ },
+
+ addContentPolicy: function(addon, name, cid) {
+ this._policies.set(name, cid);
+ NotificationTracker.add(["content-policy", addon]);
+ },
+
+ removeContentPolicy: function(addon, name) {
+ this._policies.delete(name);
+ NotificationTracker.remove(["content-policy", addon]);
+ },
+
+ receiveMessage: function (aMessage) {
+ switch (aMessage.name) {
+ case "Addons:ContentPolicy:Run":
+ return this.shouldLoad(aMessage.data, aMessage.objects);
+ }
+ return undefined;
+ },
+
+ shouldLoad: function(aData, aObjects) {
+ for (let policyCID of this._policies.values()) {
+ let policy;
+ try {
+ policy = Cc[policyCID].getService(Ci.nsIContentPolicy);
+ } catch (e) {
+ // Current Gecko behavior is to ignore entries that don't QI.
+ continue;
+ }
+ try {
+ let contentLocation = BrowserUtils.makeURI(aData.contentLocation);
+ let requestOrigin = aData.requestOrigin ? BrowserUtils.makeURI(aData.requestOrigin) : null;
+
+ let result = Prefetcher.withPrefetching(aData.prefetched, aObjects, () => {
+ return policy.shouldLoad(aData.contentType,
+ contentLocation,
+ requestOrigin,
+ aObjects.node,
+ aData.mimeTypeGuess,
+ null,
+ aData.requestPrincipal);
+ });
+ if (result != Ci.nsIContentPolicy.ACCEPT && result != 0)
+ return result;
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+};
+ContentPolicyParent.init();
+
+// This interposition intercepts calls to add or remove new content
+// policies and forwards these requests to ContentPolicyParent.
+var CategoryManagerInterposition = new Interposition("CategoryManagerInterposition");
+
+CategoryManagerInterposition.methods.addCategoryEntry =
+ function(addon, target, category, entry, value, persist, replace) {
+ if (category == "content-policy") {
+ CompatWarning.warn("content-policy should be added from the child process only.",
+ addon, CompatWarning.warnings.nsIContentPolicy);
+ ContentPolicyParent.addContentPolicy(addon, entry, value);
+ }
+
+ target.addCategoryEntry(category, entry, value, persist, replace);
+ };
+
+CategoryManagerInterposition.methods.deleteCategoryEntry =
+ function(addon, target, category, entry, persist) {
+ if (category == "content-policy") {
+ CompatWarning.warn("content-policy should be removed from the child process only.",
+ addon, CompatWarning.warnings.nsIContentPolicy);
+ ContentPolicyParent.removeContentPolicy(addon, entry);
+ }
+
+ target.deleteCategoryEntry(category, entry, persist);
+ };
+
+// This shim handles the case where an add-on registers an about:
+// protocol handler in the parent and we want the child to be able to
+// use it. This code is pretty specific to Adblock's usage.
+var AboutProtocolParent = {
+ init: function() {
+ let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+ ppmm.addMessageListener("Addons:AboutProtocol:GetURIFlags", this);
+ ppmm.addMessageListener("Addons:AboutProtocol:OpenChannel", this);
+ this._protocols = [];
+ },
+
+ registerFactory: function(addon, class_, className, contractID, factory) {
+ this._protocols.push({contractID: contractID, factory: factory});
+ NotificationTracker.add(["about-protocol", contractID, addon]);
+ },
+
+ unregisterFactory: function(addon, class_, factory) {
+ for (let i = 0; i < this._protocols.length; i++) {
+ if (this._protocols[i].factory == factory) {
+ NotificationTracker.remove(["about-protocol", this._protocols[i].contractID, addon]);
+ this._protocols.splice(i, 1);
+ break;
+ }
+ }
+ },
+
+ receiveMessage: function (msg) {
+ switch (msg.name) {
+ case "Addons:AboutProtocol:GetURIFlags":
+ return this.getURIFlags(msg);
+ case "Addons:AboutProtocol:OpenChannel":
+ return this.openChannel(msg);
+ }
+ return undefined;
+ },
+
+ getURIFlags: function(msg) {
+ let uri = BrowserUtils.makeURI(msg.data.uri);
+ let contractID = msg.data.contractID;
+ let module = Cc[contractID].getService(Ci.nsIAboutModule);
+ try {
+ return module.getURIFlags(uri);
+ } catch (e) {
+ Cu.reportError(e);
+ return undefined;
+ }
+ },
+
+ // We immediately read all the data out of the channel here and
+ // return it to the child.
+ openChannel: function(msg) {
+ function wrapGetInterface(cpow) {
+ return {
+ getInterface: function(intf) { return cpow.getInterface(intf); }
+ };
+ }
+
+ let uri = BrowserUtils.makeURI(msg.data.uri);
+ let channelParams;
+ if (msg.data.contentPolicyType === Ci.nsIContentPolicy.TYPE_DOCUMENT) {
+ // For TYPE_DOCUMENT loads, we cannot recreate the loadinfo here in the
+ // parent. In that case, treat this as a chrome (addon)-requested
+ // subload. When we use the data in the child, we'll load it into the
+ // correctly-principaled document.
+ channelParams = {
+ uri,
+ contractID: msg.data.contractID,
+ loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+ };
+ } else {
+ // We can recreate the loadinfo here in the parent for non TYPE_DOCUMENT
+ // loads.
+ channelParams = {
+ uri,
+ contractID: msg.data.contractID,
+ loadingPrincipal: msg.data.loadingPrincipal,
+ securityFlags: msg.data.securityFlags,
+ contentPolicyType: msg.data.contentPolicyType
+ };
+ }
+
+ try {
+ let channel = NetUtil.newChannel(channelParams);
+
+ // We're not allowed to set channel.notificationCallbacks to a
+ // CPOW, since the setter for notificationCallbacks is in C++,
+ // which can't tolerate CPOWs. Instead we just use a JS object
+ // that wraps the CPOW.
+ channel.notificationCallbacks = wrapGetInterface(msg.objects.notificationCallbacks);
+ if (msg.objects.loadGroupNotificationCallbacks) {
+ channel.loadGroup = {notificationCallbacks: msg.objects.loadGroupNotificationCallbacks};
+ } else {
+ channel.loadGroup = null;
+ }
+ let stream = channel.open2();
+ let data = NetUtil.readInputStreamToString(stream, stream.available(), {});
+ return {
+ data: data,
+ contentType: channel.contentType
+ };
+ } catch (e) {
+ Cu.reportError(e);
+ return undefined;
+ }
+ },
+};
+AboutProtocolParent.init();
+
+var ComponentRegistrarInterposition = new Interposition("ComponentRegistrarInterposition");
+
+ComponentRegistrarInterposition.methods.registerFactory =
+ function(addon, target, class_, className, contractID, factory) {
+ if (contractID && contractID.startsWith("@mozilla.org/network/protocol/about;1?")) {
+ CompatWarning.warn("nsIAboutModule should be registered in the content process" +
+ " as well as the chrome process. (If you do that already, ignore" +
+ " this warning.)",
+ addon, CompatWarning.warnings.nsIAboutModule);
+ AboutProtocolParent.registerFactory(addon, class_, className, contractID, factory);
+ }
+
+ target.registerFactory(class_, className, contractID, factory);
+ };
+
+ComponentRegistrarInterposition.methods.unregisterFactory =
+ function(addon, target, class_, factory) {
+ AboutProtocolParent.unregisterFactory(addon, class_, factory);
+ target.unregisterFactory(class_, factory);
+ };
+
+// This object manages add-on observers that might fire in the child
+// process. Rather than managing the observers itself, it uses the
+// parent's observer service. When an add-on listens on topic T,
+// ObserverParent asks the child process to listen on T. It also adds
+// an observer in the parent for the topic e10s-T. When the T observer
+// fires in the child, the parent fires all the e10s-T observers,
+// passing them CPOWs for the subject and data. We don't want to use T
+// in the parent because there might be non-add-on T observers that
+// won't expect to get notified in this case.
+var ObserverParent = {
+ init: function() {
+ let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+ ppmm.addMessageListener("Addons:Observer:Run", this);
+ },
+
+ addObserver: function(addon, observer, topic, ownsWeak) {
+ Services.obs.addObserver(observer, "e10s-" + topic, ownsWeak);
+ NotificationTracker.add(["observer", topic, addon]);
+ },
+
+ removeObserver: function(addon, observer, topic) {
+ Services.obs.removeObserver(observer, "e10s-" + topic);
+ NotificationTracker.remove(["observer", topic, addon]);
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "Addons:Observer:Run":
+ this.notify(msg.objects.subject, msg.objects.topic, msg.objects.data);
+ break;
+ }
+ },
+
+ notify: function(subject, topic, data) {
+ let e = Services.obs.enumerateObservers("e10s-" + topic);
+ while (e.hasMoreElements()) {
+ let obs = e.getNext().QueryInterface(Ci.nsIObserver);
+ try {
+ obs.observe(subject, topic, data);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }
+};
+ObserverParent.init();
+
+// We only forward observers for these topics.
+var TOPIC_WHITELIST = [
+ "content-document-global-created",
+ "document-element-inserted",
+ "dom-window-destroyed",
+ "inner-window-destroyed",
+ "outer-window-destroyed",
+ "csp-on-violate-policy",
+];
+
+// This interposition listens for
+// nsIObserverService.{add,remove}Observer.
+var ObserverInterposition = new Interposition("ObserverInterposition");
+
+ObserverInterposition.methods.addObserver =
+ function(addon, target, observer, topic, ownsWeak) {
+ if (TOPIC_WHITELIST.indexOf(topic) >= 0) {
+ CompatWarning.warn(`${topic} observer should be added from the child process only.`,
+ addon, CompatWarning.warnings.observers);
+
+ ObserverParent.addObserver(addon, observer, topic);
+ }
+
+ target.addObserver(observer, topic, ownsWeak);
+ };
+
+ObserverInterposition.methods.removeObserver =
+ function(addon, target, observer, topic) {
+ if (TOPIC_WHITELIST.indexOf(topic) >= 0) {
+ ObserverParent.removeObserver(addon, observer, topic);
+ }
+
+ target.removeObserver(observer, topic);
+ };
+
+// This object is responsible for forwarding events from the child to
+// the parent.
+var EventTargetParent = {
+ init: function() {
+ // The _listeners map goes from targets (either <browser> elements
+ // or windows) to a dictionary from event types to listeners.
+ this._listeners = new WeakMap();
+
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].
+ getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("Addons:Event:Run", this);
+ },
+
+ // If target is not on the path from a <browser> element to the
+ // window root, then we return null here to ignore the
+ // target. Otherwise, if the target is a browser-specific element
+ // (the <browser> or <tab> elements), then we return the
+ // <browser>. If it's some generic element, then we return the
+ // window itself.
+ redirectEventTarget: function(target) {
+ if (Cu.isCrossProcessWrapper(target)) {
+ return null;
+ }
+
+ if (target instanceof Ci.nsIDOMChromeWindow) {
+ return target;
+ }
+
+ if (target instanceof Ci.nsIDOMXULElement) {
+ if (target.localName == "browser") {
+ return target;
+ } else if (target.localName == "tab") {
+ return target.linkedBrowser;
+ }
+
+ // Check if |target| is somewhere on the patch from the
+ // <tabbrowser> up to the root element.
+ let window = target.ownerDocument.defaultView;
+ if (window && target.contains(window.gBrowser)) {
+ return window;
+ }
+ }
+
+ return null;
+ },
+
+ // When a given event fires in the child, we fire it on the
+ // <browser> element and the window since those are the two possible
+ // results of redirectEventTarget.
+ getTargets: function(browser) {
+ let window = browser.ownerDocument.defaultView;
+ return [browser, window];
+ },
+
+ addEventListener: function(addon, target, type, listener, useCapture, wantsUntrusted, delayedWarning) {
+ let newTarget = this.redirectEventTarget(target);
+ if (!newTarget) {
+ return;
+ }
+
+ useCapture = useCapture || false;
+ wantsUntrusted = wantsUntrusted || false;
+
+ NotificationTracker.add(["event", type, useCapture, addon]);
+
+ let listeners = this._listeners.get(newTarget);
+ if (!listeners) {
+ listeners = {};
+ this._listeners.set(newTarget, listeners);
+ }
+ let forType = setDefault(listeners, type, []);
+
+ // If there's already an identical listener, don't do anything.
+ for (let i = 0; i < forType.length; i++) {
+ if (forType[i].listener === listener &&
+ forType[i].target === target &&
+ forType[i].useCapture === useCapture &&
+ forType[i].wantsUntrusted === wantsUntrusted) {
+ return;
+ }
+ }
+
+ forType.push({listener: listener,
+ target: target,
+ wantsUntrusted: wantsUntrusted,
+ useCapture: useCapture,
+ delayedWarning: delayedWarning});
+ },
+
+ removeEventListener: function(addon, target, type, listener, useCapture) {
+ let newTarget = this.redirectEventTarget(target);
+ if (!newTarget) {
+ return;
+ }
+
+ useCapture = useCapture || false;
+
+ let listeners = this._listeners.get(newTarget);
+ if (!listeners) {
+ return;
+ }
+ let forType = setDefault(listeners, type, []);
+
+ for (let i = 0; i < forType.length; i++) {
+ if (forType[i].listener === listener &&
+ forType[i].target === target &&
+ forType[i].useCapture === useCapture) {
+ forType.splice(i, 1);
+ NotificationTracker.remove(["event", type, useCapture, addon]);
+ break;
+ }
+ }
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "Addons:Event:Run":
+ this.dispatch(msg.target, msg.data.type, msg.data.capturing,
+ msg.data.isTrusted, msg.data.prefetched, msg.objects);
+ break;
+ }
+ },
+
+ dispatch: function(browser, type, capturing, isTrusted, prefetched, cpows) {
+ let event = cpows.event;
+ let eventTarget = cpows.eventTarget;
+ let targets = this.getTargets(browser);
+ for (let target of targets) {
+ let listeners = this._listeners.get(target);
+ if (!listeners) {
+ continue;
+ }
+ let forType = setDefault(listeners, type, []);
+
+ // Make a copy in case they call removeEventListener in the listener.
+ let handlers = [];
+ for (let {listener, target, wantsUntrusted, useCapture, delayedWarning} of forType) {
+ if ((wantsUntrusted || isTrusted) && useCapture == capturing) {
+ // Issue a warning for this listener.
+ delayedWarning();
+
+ handlers.push([listener, target]);
+ }
+ }
+
+ for (let [handler, target] of handlers) {
+ let EventProxy = {
+ get: function(knownProps, name) {
+ if (knownProps.hasOwnProperty(name))
+ return knownProps[name];
+ return event[name];
+ }
+ }
+ let proxyEvent = new Proxy({
+ currentTarget: target,
+ target: eventTarget,
+ type: type,
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIDOMEventTarget))
+ return proxyEvent;
+ // If event deson't support the interface this will throw. If it
+ // does we want to return the proxy
+ event.QueryInterface(iid);
+ return proxyEvent;
+ }
+ }, EventProxy);
+
+ try {
+ Prefetcher.withPrefetching(prefetched, cpows, () => {
+ if ("handleEvent" in handler) {
+ handler.handleEvent(proxyEvent);
+ } else {
+ handler.call(eventTarget, proxyEvent);
+ }
+ });
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }
+ }
+};
+EventTargetParent.init();
+
+// This function returns a listener that will not fire on events where
+// the target is a remote xul:browser element itself. We'd rather let
+// the child process handle the event and pass it up via
+// EventTargetParent.
+var filteringListeners = new WeakMap();
+function makeFilteringListener(eventType, listener)
+{
+ // Some events are actually targeted at the <browser> element
+ // itself, so we only handle the ones where know that won't happen.
+ let eventTypes = ["mousedown", "mouseup", "click"];
+ if (!eventTypes.includes(eventType) || !listener ||
+ (typeof listener != "object" && typeof listener != "function")) {
+ return listener;
+ }
+
+ if (filteringListeners.has(listener)) {
+ return filteringListeners.get(listener);
+ }
+
+ function filter(event) {
+ let target = event.originalTarget;
+ if (target instanceof Ci.nsIDOMXULElement &&
+ target.localName == "browser" &&
+ target.isRemoteBrowser) {
+ return;
+ }
+
+ if ("handleEvent" in listener) {
+ listener.handleEvent(event);
+ } else {
+ listener.call(event.target, event);
+ }
+ }
+ filteringListeners.set(listener, filter);
+ return filter;
+}
+
+// This interposition redirects addEventListener and
+// removeEventListener to EventTargetParent.
+var EventTargetInterposition = new Interposition("EventTargetInterposition");
+
+EventTargetInterposition.methods.addEventListener =
+ function(addon, target, type, listener, useCapture, wantsUntrusted) {
+ let delayed = CompatWarning.delayedWarning(
+ `Registering a ${type} event listener on content DOM nodes` +
+ " needs to happen in the content process.",
+ addon, CompatWarning.warnings.DOM_events);
+
+ EventTargetParent.addEventListener(addon, target, type, listener, useCapture, wantsUntrusted, delayed);
+ target.addEventListener(type, makeFilteringListener(type, listener), useCapture, wantsUntrusted);
+ };
+
+EventTargetInterposition.methods.removeEventListener =
+ function(addon, target, type, listener, useCapture) {
+ EventTargetParent.removeEventListener(addon, target, type, listener, useCapture);
+ target.removeEventListener(type, makeFilteringListener(type, listener), useCapture);
+ };
+
+// This interposition intercepts accesses to |rootTreeItem| on a child
+// process docshell. In the child, each docshell is its own
+// root. However, add-ons expect the root to be the chrome docshell,
+// so we make that happen here.
+var ContentDocShellTreeItemInterposition = new Interposition("ContentDocShellTreeItemInterposition");
+
+ContentDocShellTreeItemInterposition.getters.rootTreeItem =
+ function(addon, target) {
+ // The chrome global in the child.
+ let chromeGlobal = target.rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+
+ // Map it to a <browser> element and window.
+ let browser = RemoteAddonsParent.globalToBrowser.get(chromeGlobal);
+ if (!browser) {
+ // Somehow we have a CPOW from the child, but it hasn't sent us
+ // its global yet. That shouldn't happen, but return null just
+ // in case.
+ return null;
+ }
+
+ let chromeWin = browser.ownerDocument.defaultView;
+
+ // Return that window's docshell.
+ return chromeWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem);
+ };
+
+function chromeGlobalForContentWindow(window)
+{
+ return window
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+}
+
+// This object manages sandboxes created with content principals in
+// the parent. We actually create these sandboxes in the child process
+// so that the code loaded into them runs there. The resulting sandbox
+// object is a CPOW. This is primarly useful for Greasemonkey.
+var SandboxParent = {
+ componentsMap: new WeakMap(),
+
+ makeContentSandbox: function(addon, chromeGlobal, principals, ...rest) {
+ CompatWarning.warn("This sandbox should be created from the child process.",
+ addon, CompatWarning.warnings.sandboxes);
+ if (rest.length) {
+ // Do a shallow copy of the options object into the child
+ // process. This way we don't have to access it through a Chrome
+ // object wrapper, which would require __exposedProps__.
+ //
+ // The only object property here is sandboxPrototype. We assume
+ // it's a child process object (since that's what Greasemonkey
+ // does) and leave it alone.
+ let options = rest[0];
+ let optionsCopy = new chromeGlobal.Object();
+ for (let prop in options) {
+ optionsCopy[prop] = options[prop];
+ }
+ rest[0] = optionsCopy;
+ }
+
+ // Make a sandbox in the child.
+ let cu = chromeGlobal.Components.utils;
+ let sandbox = cu.Sandbox(principals, ...rest);
+
+ // We need to save the sandbox in the child so it won't get
+ // GCed. The child will drop this reference at the next
+ // navigation.
+ chromeGlobal.addSandbox(sandbox);
+
+ // The sandbox CPOW will be kept alive by whomever we return it
+ // to. Its lifetime is unrelated to that of the sandbox object in
+ // the child.
+ this.componentsMap.set(sandbox, cu);
+ return sandbox;
+ },
+
+ evalInSandbox: function(code, sandbox, ...rest) {
+ let cu = this.componentsMap.get(sandbox);
+ return cu.evalInSandbox(code, sandbox, ...rest);
+ }
+};
+
+// This interposition redirects calls to Cu.Sandbox and
+// Cu.evalInSandbox to SandboxParent if the principals are content
+// principals.
+var ComponentsUtilsInterposition = new Interposition("ComponentsUtilsInterposition");
+
+ComponentsUtilsInterposition.methods.Sandbox =
+ function(addon, target, principals, ...rest) {
+ // principals can be a window object, a list of window objects, or
+ // something else (a string, for example).
+ if (principals &&
+ typeof(principals) == "object" &&
+ Cu.isCrossProcessWrapper(principals) &&
+ principals instanceof Ci.nsIDOMWindow) {
+ let chromeGlobal = chromeGlobalForContentWindow(principals);
+ return SandboxParent.makeContentSandbox(addon, chromeGlobal, principals, ...rest);
+ } else if (principals &&
+ typeof(principals) == "object" &&
+ "every" in principals &&
+ principals.length &&
+ principals.every(e => e instanceof Ci.nsIDOMWindow && Cu.isCrossProcessWrapper(e))) {
+ let chromeGlobal = chromeGlobalForContentWindow(principals[0]);
+
+ // The principals we pass to the content process must use an
+ // Array object from the content process.
+ let array = new chromeGlobal.Array();
+ for (let i = 0; i < principals.length; i++) {
+ array[i] = principals[i];
+ }
+ return SandboxParent.makeContentSandbox(addon, chromeGlobal, array, ...rest);
+ }
+ return Components.utils.Sandbox(principals, ...rest);
+ };
+
+ComponentsUtilsInterposition.methods.evalInSandbox =
+ function(addon, target, code, sandbox, ...rest) {
+ if (sandbox && Cu.isCrossProcessWrapper(sandbox)) {
+ return SandboxParent.evalInSandbox(code, sandbox, ...rest);
+ }
+ return Components.utils.evalInSandbox(code, sandbox, ...rest);
+ };
+
+// This interposition handles cases where an add-on tries to import a
+// chrome XUL node into a content document. It doesn't actually do the
+// import, which we can't support. It just avoids throwing an
+// exception.
+var ContentDocumentInterposition = new Interposition("ContentDocumentInterposition");
+
+ContentDocumentInterposition.methods.importNode =
+ function(addon, target, node, deep) {
+ if (!Cu.isCrossProcessWrapper(node)) {
+ // Trying to import a node from the parent process into the
+ // child process. We don't support this now. Video Download
+ // Helper does this in domhook-service.js to add a XUL
+ // popupmenu to content.
+ Cu.reportError("Calling contentDocument.importNode on a XUL node is not allowed.");
+ return node;
+ }
+
+ return target.importNode(node, deep);
+ };
+
+// This interposition ensures that calling browser.docShell from an
+// add-on returns a CPOW around the dochell.
+var RemoteBrowserElementInterposition = new Interposition("RemoteBrowserElementInterposition",
+ EventTargetInterposition);
+
+RemoteBrowserElementInterposition.getters.docShell = function(addon, target) {
+ CompatWarning.warn("Direct access to content docshell will no longer work in the chrome process.",
+ addon, CompatWarning.warnings.content);
+ let remoteChromeGlobal = RemoteAddonsParent.browserToGlobal.get(target);
+ if (!remoteChromeGlobal) {
+ // We may not have any messages from this tab yet.
+ return null;
+ }
+ return remoteChromeGlobal.docShell;
+};
+
+RemoteBrowserElementInterposition.getters.sessionHistory = function(addon, target) {
+ CompatWarning.warn("Direct access to browser.sessionHistory will no longer " +
+ "work in the chrome process.",
+ addon, CompatWarning.warnings.content);
+
+ return getSessionHistory(target);
+}
+
+// We use this in place of the real browser.contentWindow if we
+// haven't yet received a CPOW for the child process's window. This
+// happens if the tab has just started loading.
+function makeDummyContentWindow(browser) {
+ let dummyContentWindow = {
+ set location(url) {
+ browser.loadURI(url, null, null);
+ },
+ document: {
+ readyState: "loading",
+ location: { href: "about:blank" }
+ },
+ frames: [],
+ };
+ dummyContentWindow.top = dummyContentWindow;
+ dummyContentWindow.document.defaultView = dummyContentWindow;
+ browser._contentWindow = dummyContentWindow;
+ return dummyContentWindow;
+}
+
+RemoteBrowserElementInterposition.getters.contentWindow = function(addon, target) {
+ CompatWarning.warn("Direct access to browser.contentWindow will no longer work in the chrome process.",
+ addon, CompatWarning.warnings.content);
+
+ // If we don't have a CPOW yet, just return something we can use for
+ // setting the location. This is useful for tests that create a tab
+ // and immediately set contentWindow.location.
+ if (!target.contentWindowAsCPOW) {
+ CompatWarning.warn("CPOW to the content window does not exist yet, dummy content window is created.");
+ return makeDummyContentWindow(target);
+ }
+ return target.contentWindowAsCPOW;
+};
+
+function getContentDocument(addon, browser)
+{
+ if (!browser.contentWindowAsCPOW) {
+ return makeDummyContentWindow(browser).document;
+ }
+
+ let doc = Prefetcher.lookupInCache(addon, browser.contentWindowAsCPOW, "document");
+ if (doc) {
+ return doc;
+ }
+
+ return browser.contentWindowAsCPOW.document;
+}
+
+function getSessionHistory(browser) {
+ let remoteChromeGlobal = RemoteAddonsParent.browserToGlobal.get(browser);
+ if (!remoteChromeGlobal) {
+ CompatWarning.warn("CPOW for the remote browser docShell hasn't been received yet.");
+ // We may not have any messages from this tab yet.
+ return null;
+ }
+ return remoteChromeGlobal.docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
+}
+
+RemoteBrowserElementInterposition.getters.contentDocument = function(addon, target) {
+ CompatWarning.warn("Direct access to browser.contentDocument will no longer work in the chrome process.",
+ addon, CompatWarning.warnings.content);
+
+ return getContentDocument(addon, target);
+};
+
+var TabBrowserElementInterposition = new Interposition("TabBrowserElementInterposition",
+ EventTargetInterposition);
+
+TabBrowserElementInterposition.getters.contentWindow = function(addon, target) {
+ CompatWarning.warn("Direct access to gBrowser.contentWindow will no longer work in the chrome process.",
+ addon, CompatWarning.warnings.content);
+
+ if (!target.selectedBrowser.contentWindowAsCPOW) {
+ return makeDummyContentWindow(target.selectedBrowser);
+ }
+ return target.selectedBrowser.contentWindowAsCPOW;
+};
+
+TabBrowserElementInterposition.getters.contentDocument = function(addon, target) {
+ CompatWarning.warn("Direct access to gBrowser.contentDocument will no longer work in the chrome process.",
+ addon, CompatWarning.warnings.content);
+
+ let browser = target.selectedBrowser;
+ return getContentDocument(addon, browser);
+};
+
+TabBrowserElementInterposition.getters.sessionHistory = function(addon, target) {
+ CompatWarning.warn("Direct access to gBrowser.sessionHistory will no " +
+ "longer work in the chrome process.",
+ addon, CompatWarning.warnings.content);
+ let browser = target.selectedBrowser;
+ if (!browser.isRemoteBrowser) {
+ return browser.sessionHistory;
+ }
+ return getSessionHistory(browser);
+};
+
+// This function returns a wrapper around an
+// nsIWebProgressListener. When the wrapper is invoked, it calls the
+// real listener but passes CPOWs for the nsIWebProgress and
+// nsIRequest arguments.
+var progressListeners = {global: new WeakMap(), tabs: new WeakMap()};
+function wrapProgressListener(kind, listener)
+{
+ if (progressListeners[kind].has(listener)) {
+ return progressListeners[kind].get(listener);
+ }
+
+ let ListenerHandler = {
+ get: function(target, name) {
+ if (name.startsWith("on")) {
+ return function(...args) {
+ listener[name].apply(listener, RemoteWebProgressManager.argumentsForAddonListener(kind, args));
+ };
+ }
+
+ return listener[name];
+ }
+ };
+ let listenerProxy = new Proxy(listener, ListenerHandler);
+
+ progressListeners[kind].set(listener, listenerProxy);
+ return listenerProxy;
+}
+
+TabBrowserElementInterposition.methods.addProgressListener = function(addon, target, listener) {
+ if (!target.ownerDocument.defaultView.gMultiProcessBrowser) {
+ return target.addProgressListener(listener);
+ }
+
+ NotificationTracker.add(["web-progress", addon]);
+ return target.addProgressListener(wrapProgressListener("global", listener));
+};
+
+TabBrowserElementInterposition.methods.removeProgressListener = function(addon, target, listener) {
+ if (!target.ownerDocument.defaultView.gMultiProcessBrowser) {
+ return target.removeProgressListener(listener);
+ }
+
+ NotificationTracker.remove(["web-progress", addon]);
+ return target.removeProgressListener(wrapProgressListener("global", listener));
+};
+
+TabBrowserElementInterposition.methods.addTabsProgressListener = function(addon, target, listener) {
+ if (!target.ownerDocument.defaultView.gMultiProcessBrowser) {
+ return target.addTabsProgressListener(listener);
+ }
+
+ NotificationTracker.add(["web-progress", addon]);
+ return target.addTabsProgressListener(wrapProgressListener("tabs", listener));
+};
+
+TabBrowserElementInterposition.methods.removeTabsProgressListener = function(addon, target, listener) {
+ if (!target.ownerDocument.defaultView.gMultiProcessBrowser) {
+ return target.removeTabsProgressListener(listener);
+ }
+
+ NotificationTracker.remove(["web-progress", addon]);
+ return target.removeTabsProgressListener(wrapProgressListener("tabs", listener));
+};
+
+var ChromeWindowInterposition = new Interposition("ChromeWindowInterposition",
+ EventTargetInterposition);
+
+// _content is for older add-ons like pinboard and all-in-one gestures
+// that should be using content instead.
+ChromeWindowInterposition.getters.content =
+ChromeWindowInterposition.getters._content = function(addon, target) {
+ CompatWarning.warn("Direct access to chromeWindow.content will no longer work in the chrome process.",
+ addon, CompatWarning.warnings.content);
+
+ let browser = target.gBrowser.selectedBrowser;
+ if (!browser.contentWindowAsCPOW) {
+ return makeDummyContentWindow(browser);
+ }
+ return browser.contentWindowAsCPOW;
+};
+
+var RemoteWebNavigationInterposition = new Interposition("RemoteWebNavigation");
+
+RemoteWebNavigationInterposition.getters.sessionHistory = function(addon, target) {
+ CompatWarning.warn("Direct access to webNavigation.sessionHistory will no longer " +
+ "work in the chrome process.",
+ addon, CompatWarning.warnings.content);
+
+ if (target instanceof Ci.nsIDocShell) {
+ // We must have a non-remote browser, so we can go ahead
+ // and just return the real sessionHistory.
+ return target.sessionHistory;
+ }
+
+ let impl = target.wrappedJSObject;
+ if (!impl) {
+ return null;
+ }
+
+ let browser = impl._browser;
+
+ return getSessionHistory(browser);
+}
+
+var RemoteAddonsParent = {
+ init: function() {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("Addons:RegisterGlobal", this);
+
+ Services.ppmm.initialProcessData.remoteAddonsParentInitted = true;
+
+ this.globalToBrowser = new WeakMap();
+ this.browserToGlobal = new WeakMap();
+ },
+
+ getInterfaceInterpositions: function() {
+ let result = {};
+
+ function register(intf, interp) {
+ result[intf.number] = interp;
+ }
+
+ register(Ci.nsICategoryManager, CategoryManagerInterposition);
+ register(Ci.nsIComponentRegistrar, ComponentRegistrarInterposition);
+ register(Ci.nsIObserverService, ObserverInterposition);
+ register(Ci.nsIXPCComponents_Utils, ComponentsUtilsInterposition);
+ register(Ci.nsIWebNavigation, RemoteWebNavigationInterposition);
+
+ return result;
+ },
+
+ getTaggedInterpositions: function() {
+ let result = {};
+
+ function register(tag, interp) {
+ result[tag] = interp;
+ }
+
+ register("EventTarget", EventTargetInterposition);
+ register("ContentDocShellTreeItem", ContentDocShellTreeItemInterposition);
+ register("ContentDocument", ContentDocumentInterposition);
+ register("RemoteBrowserElement", RemoteBrowserElementInterposition);
+ register("TabBrowserElement", TabBrowserElementInterposition);
+ register("ChromeWindow", ChromeWindowInterposition);
+
+ return result;
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "Addons:RegisterGlobal":
+ this.browserToGlobal.set(msg.target, msg.objects.global);
+ this.globalToBrowser.set(msg.objects.global, msg.target);
+ break;
+ }
+ }
+};
diff --git a/components/addoncompat/ShimWaiver.jsm b/components/addoncompat/ShimWaiver.jsm
new file mode 100644
index 000000000..402ab4c32
--- /dev/null
+++ b/components/addoncompat/ShimWaiver.jsm
@@ -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/.
+
+this.EXPORTED_SYMBOLS = ["ShimWaiver"];
+
+this.ShimWaiver = {
+ getProperty: function(obj, prop) {
+ let rv = obj[prop];
+ if (rv instanceof Function) {
+ rv = rv.bind(obj);
+ }
+ return rv;
+ }
+};
diff --git a/components/addoncompat/addoncompat.manifest b/components/addoncompat/addoncompat.manifest
new file mode 100644
index 000000000..fe38f47d8
--- /dev/null
+++ b/components/addoncompat/addoncompat.manifest
@@ -0,0 +1,4 @@
+component {1363d5f0-d95e-11e3-9c1a-0800200c9a66} multiprocessShims.js
+contract @mozilla.org/addons/multiprocess-shims;1 {1363d5f0-d95e-11e3-9c1a-0800200c9a66}
+component {50bc93ce-602a-4bef-bf3a-61fc749c4caf} defaultShims.js
+contract @mozilla.org/addons/default-addon-shims;1 {50bc93ce-602a-4bef-bf3a-61fc749c4caf}
diff --git a/components/addoncompat/defaultShims.js b/components/addoncompat/defaultShims.js
new file mode 100644
index 000000000..a786efed7
--- /dev/null
+++ b/components/addoncompat/defaultShims.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * Using multiprocessShims is optional, and if an add-on is e10s compatible it should not
+ * use it. But in some cases we still want to use the interposition service for various
+ * features so we have a default shim service.
+ */
+
+function DefaultInterpositionService() {
+}
+
+DefaultInterpositionService.prototype = {
+ classID: Components.ID("{50bc93ce-602a-4bef-bf3a-61fc749c4caf}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]),
+
+ getWhitelist: function() {
+ return [];
+ },
+
+ interposeProperty: function(addon, target, iid, prop) {
+ return null;
+ },
+
+ interposeCall: function(addonId, originalFunc, originalThis, args) {
+ args.splice(0, 0, addonId);
+ return originalFunc.apply(originalThis, args);
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DefaultInterpositionService]);
diff --git a/components/addoncompat/moz.build b/components/addoncompat/moz.build
new file mode 100644
index 000000000..a8ce00cec
--- /dev/null
+++ b/components/addoncompat/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'addoncompat.manifest',
+ 'defaultShims.js',
+ 'multiprocessShims.js',
+]
+
+EXTRA_JS_MODULES += [
+ 'CompatWarning.jsm',
+ 'Prefetcher.jsm',
+ 'RemoteAddonsChild.jsm',
+ 'RemoteAddonsParent.jsm',
+ 'ShimWaiver.jsm'
+]
diff --git a/components/addoncompat/multiprocessShims.js b/components/addoncompat/multiprocessShims.js
new file mode 100644
index 000000000..8b252a0c4
--- /dev/null
+++ b/components/addoncompat/multiprocessShims.js
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Prefetcher",
+ "resource://gre/modules/Prefetcher.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RemoteAddonsParent",
+ "resource://gre/modules/RemoteAddonsParent.jsm");
+
+/**
+ * This service overlays the API that the browser exposes to
+ * add-ons. The overlay tries to make a multiprocess browser appear as
+ * much as possible like a single process browser. An overlay can
+ * replace methods, getters, and setters of arbitrary browser objects.
+ *
+ * Most of the actual replacement code is implemented in
+ * RemoteAddonsParent. The code in this service simply decides how to
+ * replace code. For a given type of object (say, an
+ * nsIObserverService) the code in RemoteAddonsParent can register a
+ * set of replacement methods. This set is called an
+ * "interposition". The service keeps track of all the different
+ * interpositions. Whenever a method is called on some part of the
+ * browser API, this service gets a chance to replace it. To do so, it
+ * consults its map based on the type of object. If an interposition
+ * is found, the given method is looked up on it and called
+ * instead. If no method (or no interposition) is found, then the
+ * original target method is called as normal.
+ *
+ * For each method call, we need to determine the type of the target
+ * object. If the object is an old-style XPConnect wrapped native,
+ * then the type is simply the interface that the method was called on
+ * (Ci.nsIObserverService, say). For all other objects (WebIDL
+ * objects, CPOWs, and normal JS objects), the type is determined by
+ * calling getObjectTag.
+ *
+ * The interpositions defined in RemoteAddonsParent have three
+ * properties: methods, getters, and setters. When accessing a
+ * property, we first consult methods. If nothing is found, then we
+ * consult getters or setters, depending on whether the access is a
+ * get or a set.
+ *
+ * The methods in |methods| are functions that will be called whenever
+ * the given method is called on the target object. They are passed
+ * the same parameters as the original function except for two
+ * additional ones at the beginning: the add-on ID and the original
+ * target object that the method was called on. Additionally, the
+ * value of |this| is set to the original target object.
+ *
+ * The values in |getters| and |setters| should also be
+ * functions. They are called immediately when the given property is
+ * accessed. The functions in |getters| take two parameters: the
+ * add-on ID and the original target object. The functions in
+ * |setters| take those arguments plus the value that the property is
+ * being set to.
+ */
+
+function AddonInterpositionService()
+{
+ Prefetcher.init();
+ RemoteAddonsParent.init();
+
+ // These maps keep track of the interpositions for all different
+ // kinds of objects.
+ this._interfaceInterpositions = RemoteAddonsParent.getInterfaceInterpositions();
+ this._taggedInterpositions = RemoteAddonsParent.getTaggedInterpositions();
+
+ let wl = [];
+ for (let v in this._interfaceInterpositions) {
+ let interp = this._interfaceInterpositions[v];
+ wl.push(...Object.getOwnPropertyNames(interp.methods));
+ wl.push(...Object.getOwnPropertyNames(interp.getters));
+ wl.push(...Object.getOwnPropertyNames(interp.setters));
+ }
+
+ for (let v in this._taggedInterpositions) {
+ let interp = this._taggedInterpositions[v];
+ wl.push(...Object.getOwnPropertyNames(interp.methods));
+ wl.push(...Object.getOwnPropertyNames(interp.getters));
+ wl.push(...Object.getOwnPropertyNames(interp.setters));
+ }
+
+ let nameSet = new Set();
+ wl = wl.filter(function(item) {
+ if (nameSet.has(item))
+ return true;
+
+ nameSet.add(item);
+ return true;
+ });
+
+ this._whitelist = wl;
+}
+
+AddonInterpositionService.prototype = {
+ classID: Components.ID("{1363d5f0-d95e-11e3-9c1a-0800200c9a66}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, Ci.nsISupportsWeakReference]),
+
+ getWhitelist: function() {
+ return this._whitelist;
+ },
+
+ // When the interface is not known for a method call, this code
+ // determines the type of the target object.
+ getObjectTag: function(target) {
+ if (Cu.isCrossProcessWrapper(target)) {
+ return Cu.getCrossProcessWrapperTag(target);
+ }
+
+ if (target instanceof Ci.nsIDOMXULElement) {
+ if (target.localName == "browser" && target.isRemoteBrowser) {
+ return "RemoteBrowserElement";
+ }
+
+ if (target.localName == "tabbrowser") {
+ return "TabBrowserElement";
+ }
+ }
+
+ if (target instanceof Ci.nsIDOMChromeWindow && target.gMultiProcessBrowser) {
+ return "ChromeWindow";
+ }
+
+ if (target instanceof Ci.nsIDOMEventTarget) {
+ return "EventTarget";
+ }
+
+ return "generic";
+ },
+
+ interposeProperty: function(addon, target, iid, prop) {
+ let interp;
+ if (iid) {
+ interp = this._interfaceInterpositions[iid];
+ } else {
+ try {
+ interp = this._taggedInterpositions[this.getObjectTag(target)];
+ }
+ catch (e) {
+ Cu.reportError(new Components.Exception("Failed to interpose object", e.result, Components.stack.caller));
+ }
+ }
+
+ if (!interp) {
+ return Prefetcher.lookupInCache(addon, target, prop);
+ }
+
+ let desc = { configurable: false, enumerable: true };
+
+ if ("methods" in interp && prop in interp.methods) {
+ desc.writable = false;
+ desc.value = function(...args) {
+ return interp.methods[prop](addon, target, ...args);
+ }
+
+ return desc;
+ } else if ("getters" in interp && prop in interp.getters) {
+ desc.get = function() { return interp.getters[prop](addon, target); };
+
+ if ("setters" in interp && prop in interp.setters) {
+ desc.set = function(v) { return interp.setters[prop](addon, target, v); };
+ }
+
+ return desc;
+ }
+
+ return Prefetcher.lookupInCache(addon, target, prop);
+ },
+
+ interposeCall: function(addonId, originalFunc, originalThis, args) {
+ args.splice(0, 0, addonId);
+ return originalFunc.apply(originalThis, args);
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonInterpositionService]);
diff --git a/components/addons/content/about.js b/components/addons/content/about.js
new file mode 100644
index 000000000..49ca4acc1
--- /dev/null
+++ b/components/addons/content/about.js
@@ -0,0 +1,97 @@
+// -*- 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/. */
+
+"use strict";
+
+function init() {
+ var addon = window.arguments[0];
+ var extensionsStrings = document.getElementById("extensionsStrings");
+
+ document.documentElement.setAttribute("addontype", addon.type);
+
+ if (addon.iconURL) {
+ var extensionIcon = document.getElementById("extensionIcon");
+ extensionIcon.src = addon.iconURL;
+ }
+
+ document.title = extensionsStrings.getFormattedString("aboutWindowTitle", [addon.name]);
+ var extensionName = document.getElementById("extensionName");
+ extensionName.textContent = addon.name;
+
+ var extensionVersion = document.getElementById("extensionVersion");
+ if (addon.version)
+ extensionVersion.setAttribute("value", extensionsStrings.getFormattedString("aboutWindowVersionString", [addon.version]));
+ else
+ extensionVersion.hidden = true;
+
+ var extensionDescription = document.getElementById("extensionDescription");
+ if (addon.description)
+ extensionDescription.textContent = addon.description;
+ else
+ extensionDescription.hidden = true;
+
+ var numDetails = 0;
+
+ var extensionCreator = document.getElementById("extensionCreator");
+ if (addon.creator) {
+ extensionCreator.setAttribute("value", addon.creator);
+ numDetails++;
+ } else {
+ extensionCreator.hidden = true;
+ var extensionCreatorLabel = document.getElementById("extensionCreatorLabel");
+ extensionCreatorLabel.hidden = true;
+ }
+
+ var extensionHomepage = document.getElementById("extensionHomepage");
+ var homepageURL = addon.homepageURL;
+ if (homepageURL) {
+ extensionHomepage.setAttribute("homepageURL", homepageURL);
+ extensionHomepage.setAttribute("tooltiptext", homepageURL);
+ numDetails++;
+ } else {
+ extensionHomepage.hidden = true;
+ }
+
+ numDetails += appendToList("extensionDevelopers", "developersBox", addon.developers);
+ numDetails += appendToList("extensionTranslators", "translatorsBox", addon.translators);
+ numDetails += appendToList("extensionContributors", "contributorsBox", addon.contributors);
+
+ if (numDetails == 0) {
+ var groove = document.getElementById("groove");
+ groove.hidden = true;
+ var extensionDetailsBox = document.getElementById("extensionDetailsBox");
+ extensionDetailsBox.hidden = true;
+ }
+
+ var acceptButton = document.documentElement.getButton("accept");
+ acceptButton.label = extensionsStrings.getString("aboutWindowCloseButton");
+
+ setTimeout(sizeToContent, 0);
+}
+
+function appendToList(aHeaderId, aNodeId, aItems) {
+ var header = document.getElementById(aHeaderId);
+ var node = document.getElementById(aNodeId);
+
+ if (!aItems || aItems.length == 0) {
+ header.hidden = true;
+ return 0;
+ }
+
+ for (let currentItem of aItems) {
+ var label = document.createElement("label");
+ label.textContent = currentItem;
+ label.setAttribute("class", "contributor");
+ node.appendChild(label);
+ }
+
+ return aItems.length;
+}
+
+function loadHomepage(aEvent) {
+ window.close();
+ openURL(aEvent.target.getAttribute("homepageURL"));
+}
diff --git a/components/addons/content/about.xul b/components/addons/content/about.xul
new file mode 100644
index 000000000..6effcf37a
--- /dev/null
+++ b/components/addons/content/about.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"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/about.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://mozapps/locale/extensions/about.dtd">
+
+<dialog id="genericAbout"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="init();"
+ buttons="accept"
+ buttoniconaccept="close"
+ onaccept="close();">
+
+ <script type="application/javascript" src="chrome://mozapps/content/extensions/about.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+
+ <stringbundleset id="aboutSet">
+ <stringbundle id="extensionsStrings" src="chrome://mozapps/locale/extensions/extensions.properties"/>
+ </stringbundleset>
+
+ <vbox id="clientBox" flex="1">
+ <hbox class="basic-info">
+ <vbox pack="center">
+ <image id="extensionIcon"/>
+ </vbox>
+ <vbox flex="1">
+ <label id="extensionName"/>
+ <label id="extensionVersion" crop="end"/>
+ </vbox>
+ </hbox>
+ <description id="extensionDescription" class="boxIndent"/>
+
+ <separator id="groove" class="groove"/>
+
+ <vbox id="extensionDetailsBox" flex="1">
+ <label id="extensionCreatorLabel" class="sectionTitle">&creator.label;</label>
+ <hbox id="creatorBox" class="boxIndent">
+ <label id="extensionCreator" flex="1" crop="end"/>
+ <label id="extensionHomepage" onclick="if (event.button == 0) { loadHomepage(event); }"
+ class="text-link" value="&homepage.label;"/>
+ </hbox>
+
+ <label id="extensionDevelopers" class="sectionTitle">&developers.label;</label>
+ <vbox flex="1" id="developersBox" class="boxIndent"/>
+ <label id="extensionTranslators" class="sectionTitle">&translators.label;</label>
+ <vbox flex="1" id="translatorsBox" class="boxIndent"/>
+ <label id="extensionContributors" class="sectionTitle">&contributors.label;</label>
+ <vbox flex="1" id="contributorsBox" class="boxIndent"/>
+ </vbox>
+ </vbox>
+
+</dialog>
diff --git a/components/addons/content/blocklist.css b/components/addons/content/blocklist.css
new file mode 100644
index 000000000..cb48005a2
--- /dev/null
+++ b/components/addons/content/blocklist.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/. */
+
+.hardBlockedAddon {
+ -moz-binding: url("chrome://mozapps/content/extensions/blocklist.xml#hardblockedaddon");
+}
+
+.softBlockedAddon {
+ -moz-binding: url("chrome://mozapps/content/extensions/blocklist.xml#softblockedaddon");
+}
diff --git a/components/addons/content/blocklist.js b/components/addons/content/blocklist.js
new file mode 100644
index 000000000..6d524e6ee
--- /dev/null
+++ b/components/addons/content/blocklist.js
@@ -0,0 +1,72 @@
+// -*- 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/. */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gArgs;
+
+function init() {
+ var hasHardBlocks = false;
+ var hasSoftBlocks = false;
+ gArgs = window.arguments[0].wrappedJSObject;
+
+ // NOTE: We use strings from the "updates.properties" bundleset to change the
+ // text on the "Cancel" button to "Restart Later". (bug 523784)
+ let bundle = Services.strings.
+ createBundle("chrome://mozapps/locale/update/updates.properties");
+ let cancelButton = document.documentElement.getButton("cancel");
+ cancelButton.setAttribute("label", bundle.GetStringFromName("restartLaterButton"));
+ cancelButton.setAttribute("accesskey",
+ bundle.GetStringFromName("restartLaterButton.accesskey"));
+
+ var richlist = document.getElementById("addonList");
+ var list = gArgs.list;
+ list.sort(function(a, b) { return String.localeCompare(a.name, b.name); });
+ for (let listItem of list) {
+ let item = document.createElement("richlistitem");
+ item.setAttribute("name", listItem.name);
+ item.setAttribute("version", listItem.version);
+ item.setAttribute("icon", listItem.icon);
+ if (listItem.blocked) {
+ item.setAttribute("class", "hardBlockedAddon");
+ hasHardBlocks = true;
+ }
+ else {
+ item.setAttribute("class", "softBlockedAddon");
+ hasSoftBlocks = true;
+ }
+ richlist.appendChild(item);
+ }
+
+ if (hasHardBlocks && hasSoftBlocks)
+ document.getElementById("bothMessage").hidden = false;
+ else if (hasHardBlocks)
+ document.getElementById("hardBlockMessage").hidden = false;
+ else
+ document.getElementById("softBlockMessage").hidden = false;
+
+ var link = document.getElementById("moreInfo");
+ if (list.length == 1 && list[0].url) {
+ link.setAttribute("href", list[0].url);
+ }
+ else {
+ var url = Services.urlFormatter.formatURLPref("extensions.blocklist.detailsURL");
+ link.setAttribute("href", url);
+ }
+}
+
+function finish(shouldRestartNow) {
+ gArgs.restart = shouldRestartNow;
+ var list = gArgs.list;
+ var items = document.getElementById("addonList").childNodes;
+ for (let i = 0; i < list.length; i++) {
+ if (!list[i].blocked)
+ list[i].disable = items[i].checked;
+ }
+ return true;
+}
diff --git a/components/addons/content/blocklist.xml b/components/addons/content/blocklist.xml
new file mode 100644
index 000000000..74474392f
--- /dev/null
+++ b/components/addons/content/blocklist.xml
@@ -0,0 +1,58 @@
+<?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 % blocklistDTD SYSTEM "chrome://mozapps/locale/extensions/blocklist.dtd" >
+ %blocklistDTD;
+]>
+
+<bindings id="blocklistBindings"
+ 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="hardblockedaddon">
+ <content align="start">
+ <xul:image xbl:inherits="src=icon"/>
+ <xul:vbox flex="1">
+ <xul:hbox class="addon-name-version">
+ <xul:label class="addonName" crop="end" xbl:inherits="value=name"/>
+ <xul:label class="addonVersion" xbl:inherits="value=version"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:spacer flex="1"/>
+ <xul:label class="blockedLabel" value="&blocklist.blocked.label;"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ </binding>
+
+ <binding id="softblockedaddon">
+ <content align="start">
+ <xul:image xbl:inherits="src=icon"/>
+ <xul:vbox flex="1">
+ <xul:hbox class="addon-name-version">
+ <xul:label class="addonName" crop="end" xbl:inherits="value=name"/>
+ <xul:label class="addonVersion" xbl:inherits="value=version"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:spacer flex="1"/>
+ <xul:checkbox class="disableCheckbox" checked="true" label="&blocklist.checkbox.label;"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <field name="_checkbox">
+ document.getAnonymousElementByAttribute(this, "class", "disableCheckbox")
+ </field>
+ <property name="checked" readonly="true">
+ <getter>
+ return this._checkbox.checked;
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/components/addons/content/blocklist.xul b/components/addons/content/blocklist.xul
new file mode 100644
index 000000000..240d9e4e1
--- /dev/null
+++ b/components/addons/content/blocklist.xul
@@ -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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/blocklist.css"?>
+<?xml-stylesheet href="chrome://mozapps/content/extensions/blocklist.css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/blocklist.dtd">
+%extensionsDTD;
+]>
+
+<dialog windowtype="Addons:Blocklist" title="&blocklist.title;" align="stretch"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="init();" ondialogaccept="return finish(true)"
+ ondialogcancel="return finish(false)"
+ buttons="accept,cancel" style="&blocklist.style;"
+ buttonlabelaccept="&blocklist.accept.label;"
+ buttonaccesskeyaccept="&blocklist.accept.accesskey;">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://mozapps/content/extensions/blocklist.js"/>
+
+ <hbox align="stretch" flex="1">
+ <vbox pack="start">
+ <image class="error-icon"/>
+ </vbox>
+ <vbox flex="1">
+ <label>&blocklist.summary;</label>
+ <separator class="thin"/>
+ <richlistbox id="addonList" flex="1"/>
+ <separator class="thin"/>
+ <description id="bothMessage" hidden="true" class="bold">&blocklist.softandhard;</description>
+ <description id="hardBlockMessage" hidden="true" class="bold">&blocklist.hardblocked;</description>
+ <description id="softBlockMessage" hidden="true" class="bold">&blocklist.softblocked;</description>
+ <hbox pack="start">
+ <label id="moreInfo" class="text-link" value="&blocklist.moreinfo;"/>
+ </hbox>
+ </vbox>
+ </hbox>
+</dialog>
diff --git a/components/addons/content/eula.js b/components/addons/content/eula.js
new file mode 100644
index 000000000..a05f7fe1c
--- /dev/null
+++ b/components/addons/content/eula.js
@@ -0,0 +1,21 @@
+// -*- 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/. */
+
+"use strict";
+
+function Startup() {
+ var bundle = document.getElementById("extensionsStrings");
+ var addon = window.arguments[0].addon;
+
+ document.documentElement.setAttribute("addontype", addon.type);
+
+ if (addon.iconURL)
+ document.getElementById("icon").src = addon.iconURL;
+
+ var label = document.createTextNode(bundle.getFormattedString("eulaHeader", [addon.name]));
+ document.getElementById("heading").appendChild(label);
+ document.getElementById("eula").value = addon.eula;
+}
diff --git a/components/addons/content/eula.xul b/components/addons/content/eula.xul
new file mode 100644
index 000000000..10e657951
--- /dev/null
+++ b/components/addons/content/eula.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/eula.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
+%extensionsDTD;
+]>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&eula.title;" width="&eula.width;" height="&eula.height;"
+ buttons="accept,cancel" buttonlabelaccept="&eula.accept;"
+ ondialogaccept="window.arguments[0].accepted = true"
+ onload="Startup();">
+
+ <script type="application/javascript" src="chrome://mozapps/content/extensions/eula.js"/>
+
+ <stringbundleset id="extensionsSet">
+ <stringbundle id="extensionsStrings" src="chrome://mozapps/locale/extensions/extensions.properties"/>
+ </stringbundleset>
+
+ <hbox id="heading-container">
+ <image id="icon"/>
+ <label id="heading" flex="1"/>
+ </hbox>
+
+ <textbox id="eula" multiline="true" readonly="true" flex="1"/>
+</dialog>
diff --git a/components/addons/content/extensions.css b/components/addons/content/extensions.css
new file mode 100644
index 000000000..c483bcd56
--- /dev/null
+++ b/components/addons/content/extensions.css
@@ -0,0 +1,237 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 xhtml "http://www.w3.org/1999/xhtml";
+
+/* HTML link elements do weird things to the layout if they are not hidden */
+xhtml|link {
+ display: none;
+}
+
+#categories {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#categories-list");
+}
+
+.category {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#category");
+}
+
+.sort-controls {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#sorters");
+}
+
+.addon[status="installed"] {
+ -moz-box-orient: vertical;
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-generic");
+}
+
+.addon[status="installing"] {
+ -moz-box-orient: vertical;
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-installing");
+}
+
+.addon[pending="uninstall"] {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-uninstalled");
+}
+
+.creator {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#creator-link");
+}
+
+.translators {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#translators-list");
+}
+
+.meta-rating {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#rating");
+}
+
+.download-progress, .download-progress[mode="undetermined"] {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#download-progress");
+}
+
+.install-status {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#install-status");
+}
+
+.detail-row {
+ -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#detail-row");
+}
+
+.text-list {
+ white-space: pre-line;
+ -moz-user-select: element;
+}
+
+setting, row[unsupported="true"] {
+ display: none;
+}
+
+setting[type="bool"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-bool");
+}
+
+setting[type="bool"][localized="true"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-localized-bool");
+}
+
+setting[type="bool"]:not([learnmore]) .preferences-learnmore {
+ visibility: collapse;
+}
+
+setting[type="boolint"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-boolint");
+}
+
+setting[type="integer"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-integer");
+}
+
+setting[type="integer"]:not([size]) textbox {
+ -moz-box-flex: 1;
+}
+
+setting[type="control"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-control");
+}
+
+setting[type="string"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-string");
+}
+
+setting[type="color"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-color");
+}
+
+setting[type="file"],
+setting[type="directory"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-path");
+}
+
+setting[type="radio"],
+setting[type="menulist"] {
+ display: -moz-grid-line;
+ -moz-binding: url("chrome://mozapps/content/extensions/setting.xml#setting-multi");
+}
+
+#addonitem-popup > menuitem[disabled="true"] {
+ display: none;
+}
+
+#addonitem-popup[addontype="theme"] > #menuitem_enableItem,
+#addonitem-popup[addontype="theme"] > #menuitem_disableItem,
+#addonitem-popup:not([addontype="theme"]) > #menuitem_enableTheme,
+#addonitem-popup:not([addontype="theme"]) > #menuitem_disableTheme {
+ display: none;
+}
+
+#header-searching:not([active]) {
+ visibility: hidden;
+}
+
+#search-list[local="false"] > .addon[remote="false"],
+#search-list[remote="false"] > .addon[remote="true"] {
+ visibility: collapse;
+}
+
+#detail-view {
+ overflow: auto;
+}
+
+.addon:not([notification="warning"]) .warning,
+.addon:not([notification="error"]) .error,
+.addon:not([notification="info"]) .info,
+.addon:not([pending]) .pending,
+.addon:not([upgrade="true"]) .update-postfix,
+.addon[active="true"] .disabled-postfix,
+.addon[pending="install"] .update-postfix,
+.addon[pending="install"] .disabled-postfix,
+#detail-view:not([notification="warning"]) .warning,
+#detail-view:not([notification="error"]) .error,
+#detail-view:not([notification="info"]) .info,
+#detail-view:not([pending]) .pending,
+#detail-view:not([upgrade="true"]) .update-postfix,
+#detail-view[active="true"] .disabled-postfix,
+#detail-view[loading] .detail-view-container,
+#detail-view:not([loading]) .alert-container,
+.detail-row:not([value]),
+#search-list[remote="false"] #search-allresults-link {
+ display: none;
+}
+
+#addons-page:not([warning]) #list-view > .global-warning-container {
+ display: none;
+}
+#addon-list .date-updated {
+ display: none;
+}
+
+.view-pane:not(#updates-view) .addon .relnotes-toggle,
+.view-pane:not(#updates-view) .addon .include-update,
+#updates-view:not([updatetype="available"]) .addon .include-update,
+#updates-view[updatetype="available"] .addon .update-available-notice {
+ display: none;
+}
+
+#addons-page:not([warning]) .global-warning,
+#addons-page:not([warning="safemode"]) .global-warning-safemode,
+#addons-page:not([warning="checkcompatibility"]) .global-warning-checkcompatibility,
+#addons-page:not([warning="updatesecurity"]) .global-warning-updatesecurity {
+ display: none;
+}
+
+/* Plugins aren't yet disabled by safemode (bug 342333),
+ so don't show that warning when viewing plugins. */
+#addons-page[warning="safemode"] .view-pane[type="plugin"] .global-warning-container,
+#addons-page[warning="safemode"] #detail-view[loading="true"] .global-warning {
+ display: none;
+}
+
+#addons-page .view-pane:not([type="plugin"]) .plugin-info-container {
+ display: none;
+}
+
+.addon .relnotes {
+ -moz-user-select: text;
+}
+#detail-name, #detail-desc, #detail-fulldesc {
+ -moz-user-select: text;
+}
+
+/* Make sure we're not animating hidden images. See bug 623739. */
+#view-port:not([selectedIndex="0"]) #discover-view .loading,
+#discover-view:not([selectedIndex="0"]) .loading {
+ display: none;
+}
+
+/* Elements in unselected richlistitems cannot be focused */
+richlistitem:not([selected]) * {
+ -moz-user-focus: ignore;
+}
+
+#header-search {
+ width: 22em;
+}
+
+#header-utils-btn {
+ -moz-user-focus: normal;
+}
+
+.discover-button[disabled="true"] {
+ display: none;
+}
+
+/* Translators for Language Pack details */
+.translators > label {
+ -moz-margin-start: 0px;
+ -moz-margin-end: 0px;
+}
diff --git a/components/addons/content/extensions.js b/components/addons/content/extensions.js
new file mode 100644
index 000000000..11c853524
--- /dev/null
+++ b/components/addons/content/extensions.js
@@ -0,0 +1,3553 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+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/addons/AddonRepository.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+#ifdef MOZ_DEVTOOLS
+XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () {
+ return Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}).
+ BrowserToolboxProcess;
+});
+#endif
+
+const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
+const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
+const PREF_XPI_ENABLED = "xpinstall.enabled";
+const PREF_MAXRESULTS = "extensions.getAddons.maxResults";
+const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
+const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled";
+const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden";
+const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";
+const PREF_ADDON_DEBUGGING_ENABLED = "devtools.chrome.enabled";
+const PREF_REMOTE_DEBUGGING_ENABLED = "devtools.debugger.remote-enabled";
+
+const LOADING_MSG_DELAY = 100;
+
+const SEARCH_SCORE_MULTIPLIER_NAME = 2;
+const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2;
+
+// Use integers so search scores are sortable by nsIXULSortService
+const SEARCH_SCORE_MATCH_WHOLEWORD = 10;
+const SEARCH_SCORE_MATCH_WORDBOUNDRY = 6;
+const SEARCH_SCORE_MATCH_SUBSTRING = 3;
+
+const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds)
+const UPDATES_RELEASENOTES_TRANSFORMFILE = "chrome://mozapps/content/extensions/updateinfo.xsl";
+
+const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
+
+var gViewDefault = "addons://discover/";
+
+var gStrings = {};
+XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc",
+ "@mozilla.org/intl/stringbundle;1",
+ "nsIStringBundleService");
+
+XPCOMUtils.defineLazyGetter(gStrings, "brand", function brandLazyGetter() {
+ return this.bundleSvc.createBundle("chrome://branding/locale/brand.properties");
+});
+XPCOMUtils.defineLazyGetter(gStrings, "ext", function extLazyGetter() {
+ return this.bundleSvc.createBundle("chrome://mozapps/locale/extensions/extensions.properties");
+});
+XPCOMUtils.defineLazyGetter(gStrings, "dl", function dlLazyGetter() {
+ return this.bundleSvc.createBundle("chrome://mozapps/locale/downloads/downloads.properties");
+});
+
+XPCOMUtils.defineLazyGetter(gStrings, "brandShortName", function brandShortNameLazyGetter() {
+ return this.brand.GetStringFromName("brandShortName");
+});
+XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function appVersionLazyGetter() {
+ return Services.appinfo.version;
+});
+
+document.addEventListener("load", initialize, true);
+window.addEventListener("unload", shutdown, false);
+
+var gPendingInitializations = 1;
+this.__defineGetter__("gIsInitializing", function gIsInitializingGetter() gPendingInitializations > 0);
+
+function initialize(event) {
+ // XXXbz this listener gets _all_ load events for all nodes in the
+ // document... but relies on not being called "too early".
+ if (event.target instanceof XMLStylesheetProcessingInstruction) {
+ return;
+ }
+ document.removeEventListener("load", initialize, true);
+
+ let globalCommandSet = document.getElementById("globalCommandSet");
+ globalCommandSet.addEventListener("command", function(event) {
+ gViewController.doCommand(event.target.id);
+ });
+
+ let viewCommandSet = document.getElementById("viewCommandSet");
+ viewCommandSet.addEventListener("commandupdate", function(event) {
+ gViewController.updateCommands();
+ });
+ viewCommandSet.addEventListener("command", function(event) {
+ gViewController.doCommand(event.target.id);
+ });
+
+ let detailScreenshot = document.getElementById("detail-screenshot");
+ detailScreenshot.addEventListener("load", function(event) {
+ this.removeAttribute("loading");
+ });
+ detailScreenshot.addEventListener("error", function(event) {
+ this.setAttribute("loading", "error");
+ });
+
+ let addonPage = document.getElementById("addons-page");
+ addonPage.addEventListener("dragenter", function(event) {
+ gDragDrop.onDragOver(event);
+ });
+ addonPage.addEventListener("dragover", function(event) {
+ gDragDrop.onDragOver(event);
+ });
+ addonPage.addEventListener("drop", function(event) {
+ gDragDrop.onDrop(event);
+ });
+ addonPage.addEventListener("keypress", function(event) {
+ gHeader.onKeyPress(event);
+ });
+
+ if (!isDiscoverEnabled()) {
+ gViewDefault = "addons://list/extension";
+ }
+
+ gViewController.initialize();
+ gCategories.initialize();
+ gHeader.initialize();
+ gEventManager.initialize();
+ Services.obs.addObserver(sendEMPong, "EM-ping", false);
+ Services.obs.notifyObservers(window, "EM-loaded", "");
+
+ // If the initial view has already been selected (by a call to loadView from
+ // the above notifications) then bail out now
+ if (gViewController.initialViewSelected)
+ return;
+
+ // If there is a history state to restore then use that
+ if (window.history.state) {
+ gViewController.updateState(window.history.state);
+ return;
+ }
+
+ // Default to the last selected category
+ var view = gCategories.node.value;
+
+ // Allow passing in a view through the window arguments
+ if ("arguments" in window && window.arguments.length > 0 &&
+ window.arguments[0] !== null && "view" in window.arguments[0]) {
+ view = window.arguments[0].view;
+ }
+
+ gViewController.loadInitialView(view);
+
+ Services.prefs.addObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged, false);
+ Services.prefs.addObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged, false);
+}
+
+function notifyInitialized() {
+ if (!gIsInitializing)
+ return;
+
+ gPendingInitializations--;
+ if (!gIsInitializing) {
+ var event = document.createEvent("Events");
+ event.initEvent("Initialized", true, true);
+ document.dispatchEvent(event);
+ }
+}
+
+function shutdown() {
+ gCategories.shutdown();
+ gSearchView.shutdown();
+ gEventManager.shutdown();
+ gViewController.shutdown();
+ Services.obs.removeObserver(sendEMPong, "EM-ping");
+ Services.prefs.removeObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged);
+ Services.prefs.removeObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged);
+}
+
+function sendEMPong(aSubject, aTopic, aData) {
+ Services.obs.notifyObservers(window, "EM-pong", "");
+}
+
+// Used by external callers to load a specific view into the manager
+function loadView(aViewId) {
+ if (!gViewController.initialViewSelected) {
+ // The caller opened the window and immediately loaded the view so it
+ // should be the initial history entry
+
+ gViewController.loadInitialView(aViewId);
+ } else {
+ gViewController.loadView(aViewId);
+ }
+}
+
+function isDiscoverEnabled() {
+ if (Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID)
+ return false;
+
+ try {
+ if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED))
+ return false;
+ } catch (e) {}
+
+ try {
+ if (!Services.prefs.getBoolPref(PREF_XPI_ENABLED))
+ return false;
+ } catch (e) {}
+
+ return true;
+}
+
+/**
+ * Obtain the main DOMWindow for the current context.
+ */
+function getMainWindow() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+}
+
+function getBrowserElement() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+}
+
+/**
+ * Obtain the DOMWindow that can open a preferences pane.
+ *
+ * This is essentially "get the browser chrome window" with the added check
+ * that the supposed browser chrome window is capable of opening a preferences
+ * pane.
+ *
+ * This may return null if we can't find the browser chrome window.
+ */
+function getMainWindowWithPreferencesPane() {
+ let mainWindow = getMainWindow();
+ if (mainWindow && "openAdvancedPreferences" in mainWindow) {
+ return mainWindow;
+ } else {
+ return null;
+ }
+}
+
+/**
+ * A wrapper around the HTML5 session history service that allows the browser
+ * back/forward controls to work within the manager
+ */
+var HTML5History = {
+ get index() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .sessionHistory.index;
+ },
+
+ get canGoBack() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .canGoBack;
+ },
+
+ get canGoForward() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .canGoForward;
+ },
+
+ back: function HTML5History_back() {
+ window.history.back();
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ },
+
+ forward: function HTML5History_forward() {
+ window.history.forward();
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ },
+
+ pushState: function HTML5History_pushState(aState) {
+ window.history.pushState(aState, document.title);
+ },
+
+ replaceState: function HTML5History_replaceState(aState) {
+ window.history.replaceState(aState, document.title);
+ },
+
+ popState: function HTML5History_popState() {
+ function onStatePopped(aEvent) {
+ window.removeEventListener("popstate", onStatePopped, true);
+ // TODO To ensure we can't go forward again we put an additional entry
+ // for the current state into the history. Ideally we would just strip
+ // the history but there doesn't seem to be a way to do that. Bug 590661
+ window.history.pushState(aEvent.state, document.title);
+ }
+ window.addEventListener("popstate", onStatePopped, true);
+ window.history.back();
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ }
+};
+
+/**
+ * A wrapper around a fake history service
+ */
+var FakeHistory = {
+ pos: 0,
+ states: [null],
+
+ get index() {
+ return this.pos;
+ },
+
+ get canGoBack() {
+ return this.pos > 0;
+ },
+
+ get canGoForward() {
+ return (this.pos + 1) < this.states.length;
+ },
+
+ back: function FakeHistory_back() {
+ if (this.pos == 0)
+ throw Components.Exception("Cannot go back from this point");
+
+ this.pos--;
+ gViewController.updateState(this.states[this.pos]);
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ },
+
+ forward: function FakeHistory_forward() {
+ if ((this.pos + 1) >= this.states.length)
+ throw Components.Exception("Cannot go forward from this point");
+
+ this.pos++;
+ gViewController.updateState(this.states[this.pos]);
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ },
+
+ pushState: function FakeHistory_pushState(aState) {
+ this.pos++;
+ this.states.splice(this.pos, this.states.length);
+ this.states.push(aState);
+ },
+
+ replaceState: function FakeHistory_replaceState(aState) {
+ this.states[this.pos] = aState;
+ },
+
+ popState: function FakeHistory_popState() {
+ if (this.pos == 0)
+ throw Components.Exception("Cannot popState from this view");
+
+ this.states.splice(this.pos, this.states.length);
+ this.pos--;
+
+ gViewController.updateState(this.states[this.pos]);
+ gViewController.updateCommand("cmd_back");
+ gViewController.updateCommand("cmd_forward");
+ }
+};
+
+// If the window has a session history then use the HTML5 History wrapper
+// otherwise use our fake history implementation
+if (window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .sessionHistory) {
+ var gHistory = HTML5History;
+}
+else {
+ gHistory = FakeHistory;
+}
+
+var gEventManager = {
+ _listeners: {},
+ _installListeners: [],
+
+ initialize: function gEM_initialize() {
+ var self = this;
+ const ADDON_EVENTS = ["onEnabling", "onEnabled", "onDisabling",
+ "onDisabled", "onUninstalling", "onUninstalled",
+ "onInstalled", "onOperationCancelled",
+ "onUpdateAvailable", "onUpdateFinished",
+ "onCompatibilityUpdateAvailable",
+ "onPropertyChanged"];
+ for (let evt of ADDON_EVENTS) {
+ let event = evt;
+ self[event] = function initialize_delegateAddonEvent(...aArgs) {
+ self.delegateAddonEvent(event, aArgs);
+ };
+ }
+
+ const INSTALL_EVENTS = ["onNewInstall", "onDownloadStarted",
+ "onDownloadEnded", "onDownloadFailed",
+ "onDownloadProgress", "onDownloadCancelled",
+ "onInstallStarted", "onInstallEnded",
+ "onInstallFailed", "onInstallCancelled",
+ "onExternalInstall"];
+ for (let evt of INSTALL_EVENTS) {
+ let event = evt;
+ self[event] = function initialize_delegateInstallEvent(...aArgs) {
+ self.delegateInstallEvent(event, aArgs);
+ };
+ }
+
+ AddonManager.addManagerListener(this);
+ AddonManager.addInstallListener(this);
+ AddonManager.addAddonListener(this);
+
+ this.refreshGlobalWarning();
+ this.refreshAutoUpdateDefault();
+
+ var contextMenu = document.getElementById("addonitem-popup");
+ contextMenu.addEventListener("popupshowing", function contextMenu_onPopupshowing() {
+ var addon = gViewController.currentViewObj.getSelectedAddon();
+ contextMenu.setAttribute("addontype", addon.type);
+
+ var menuSep = document.getElementById("addonitem-menuseparator");
+ var countMenuItemsBeforeSep = 0;
+ for (let child of contextMenu.children) {
+ if (child == menuSep) {
+ break;
+ }
+ if (child.nodeName == "menuitem" &&
+ gViewController.isCommandEnabled(child.command)) {
+ countMenuItemsBeforeSep++;
+ }
+ }
+
+ // Hide the separator if there are no visible menu items before it
+ menuSep.hidden = (countMenuItemsBeforeSep == 0);
+
+ }, false);
+ },
+
+ shutdown: function gEM_shutdown() {
+ AddonManager.removeManagerListener(this);
+ AddonManager.removeInstallListener(this);
+ AddonManager.removeAddonListener(this);
+ },
+
+ registerAddonListener: function gEM_registerAddonListener(aListener, aAddonId) {
+ if (!(aAddonId in this._listeners))
+ this._listeners[aAddonId] = [];
+ else if (this._listeners[aAddonId].indexOf(aListener) != -1)
+ return;
+ this._listeners[aAddonId].push(aListener);
+ },
+
+ unregisterAddonListener: function gEM_unregisterAddonListener(aListener, aAddonId) {
+ if (!(aAddonId in this._listeners))
+ return;
+ var index = this._listeners[aAddonId].indexOf(aListener);
+ if (index == -1)
+ return;
+ this._listeners[aAddonId].splice(index, 1);
+ },
+
+ registerInstallListener: function gEM_registerInstallListener(aListener) {
+ if (this._installListeners.indexOf(aListener) != -1)
+ return;
+ this._installListeners.push(aListener);
+ },
+
+ unregisterInstallListener: function gEM_unregisterInstallListener(aListener) {
+ var i = this._installListeners.indexOf(aListener);
+ if (i == -1)
+ return;
+ this._installListeners.splice(i, 1);
+ },
+
+ delegateAddonEvent: function gEM_delegateAddonEvent(aEvent, aParams) {
+ var addon = aParams.shift();
+ if (!(addon.id in this._listeners))
+ return;
+
+ var listeners = this._listeners[addon.id];
+ for (let listener of listeners) {
+ if (!(aEvent in listener))
+ continue;
+ try {
+ listener[aEvent].apply(listener, aParams);
+ } catch(e) {
+ // this shouldn't be fatal
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ delegateInstallEvent: function gEM_delegateInstallEvent(aEvent, aParams) {
+ var existingAddon = aEvent == "onExternalInstall" ? aParams[1] : aParams[0].existingAddon;
+ // If the install is an update then send the event to all listeners
+ // registered for the existing add-on
+ if (existingAddon)
+ this.delegateAddonEvent(aEvent, [existingAddon].concat(aParams));
+
+ for (let listener of this._installListeners) {
+ if (!(aEvent in listener))
+ continue;
+ try {
+ listener[aEvent].apply(listener, aParams);
+ } catch(e) {
+ // this shouldn't be fatal
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ refreshGlobalWarning: function gEM_refreshGlobalWarning() {
+ var page = document.getElementById("addons-page");
+
+ if (Services.appinfo.inSafeMode) {
+ page.setAttribute("warning", "safemode");
+ return;
+ }
+
+ if (AddonManager.checkUpdateSecurityDefault &&
+ !AddonManager.checkUpdateSecurity) {
+ page.setAttribute("warning", "updatesecurity");
+ return;
+ }
+
+ if (!AddonManager.checkCompatibility) {
+ page.setAttribute("warning", "checkcompatibility");
+ return;
+ }
+
+ page.removeAttribute("warning");
+ },
+
+ refreshAutoUpdateDefault: function gEM_refreshAutoUpdateDefault() {
+ var updateEnabled = AddonManager.updateEnabled;
+ var autoUpdateDefault = AddonManager.autoUpdateDefault;
+
+ // The checkbox needs to reflect that both prefs need to be true
+ // for updates to be checked for and applied automatically
+ document.getElementById("utils-autoUpdateDefault")
+ .setAttribute("checked", updateEnabled && autoUpdateDefault);
+
+ document.getElementById("utils-resetAddonUpdatesToAutomatic").hidden = !autoUpdateDefault;
+ document.getElementById("utils-resetAddonUpdatesToManual").hidden = autoUpdateDefault;
+ },
+
+ onCompatibilityModeChanged: function gEM_onCompatibilityModeChanged() {
+ this.refreshGlobalWarning();
+ },
+
+ onCheckUpdateSecurityChanged: function gEM_onCheckUpdateSecurityChanged() {
+ this.refreshGlobalWarning();
+ },
+
+ onUpdateModeChanged: function gEM_onUpdateModeChanged() {
+ this.refreshAutoUpdateDefault();
+ }
+};
+
+
+var gViewController = {
+ viewPort: null,
+ currentViewId: "",
+ currentViewObj: null,
+ currentViewRequest: 0,
+ viewObjects: {},
+ viewChangeCallback: null,
+ initialViewSelected: false,
+ lastHistoryIndex: -1,
+
+ initialize: function gVC_initialize() {
+ this.viewPort = document.getElementById("view-port");
+
+ this.viewObjects["search"] = gSearchView;
+ this.viewObjects["discover"] = gDiscoverView;
+ this.viewObjects["list"] = gListView;
+ this.viewObjects["detail"] = gDetailView;
+ this.viewObjects["updates"] = gUpdatesView;
+
+ for each (let view in this.viewObjects)
+ view.initialize();
+
+ window.controllers.appendController(this);
+
+ window.addEventListener("popstate",
+ function window_onStatePopped(e) {
+ gViewController.updateState(e.state);
+ },
+ false);
+ },
+
+ shutdown: function gVC_shutdown() {
+ if (this.currentViewObj)
+ this.currentViewObj.hide();
+ this.currentViewRequest = 0;
+
+ for each(let view in this.viewObjects) {
+ if ("shutdown" in view) {
+ try {
+ view.shutdown();
+ } catch(e) {
+ // this shouldn't be fatal
+ Cu.reportError(e);
+ }
+ }
+ }
+
+ window.controllers.removeController(this);
+ },
+
+ updateState: function gVC_updateState(state) {
+ try {
+ this.loadViewInternal(state.view, state.previousView, state);
+ this.lastHistoryIndex = gHistory.index;
+ }
+ catch (e) {
+ // The attempt to load the view failed, try moving further along history
+ if (this.lastHistoryIndex > gHistory.index) {
+ if (gHistory.canGoBack)
+ gHistory.back();
+ else
+ gViewController.replaceView(gViewDefault);
+ } else {
+ if (gHistory.canGoForward)
+ gHistory.forward();
+ else
+ gViewController.replaceView(gViewDefault);
+ }
+ }
+ },
+
+ parseViewId: function gVC_parseViewId(aViewId) {
+ var matchRegex = /^addons:\/\/([^\/]+)\/(.*)$/;
+ var [,viewType, viewParam] = aViewId.match(matchRegex) || [];
+ return {type: viewType, param: decodeURIComponent(viewParam)};
+ },
+
+ get isLoading() {
+ return !this.currentViewObj || this.currentViewObj.node.hasAttribute("loading");
+ },
+
+ loadView: function gVC_loadView(aViewId) {
+ var isRefresh = false;
+ if (aViewId == this.currentViewId) {
+ if (this.isLoading)
+ return;
+ if (!("refresh" in this.currentViewObj))
+ return;
+ if (!this.currentViewObj.canRefresh())
+ return;
+ isRefresh = true;
+ }
+
+ var state = {
+ view: aViewId,
+ previousView: this.currentViewId
+ };
+ if (!isRefresh) {
+ gHistory.pushState(state);
+ this.lastHistoryIndex = gHistory.index;
+ }
+ this.loadViewInternal(aViewId, this.currentViewId, state);
+ },
+
+ // Replaces the existing view with a new one, rewriting the current history
+ // entry to match.
+ replaceView: function gVC_replaceView(aViewId) {
+ if (aViewId == this.currentViewId)
+ return;
+
+ var state = {
+ view: aViewId,
+ previousView: null
+ };
+ gHistory.replaceState(state);
+ this.loadViewInternal(aViewId, null, state);
+ },
+
+ loadInitialView: function gVC_loadInitialView(aViewId) {
+ var state = {
+ view: aViewId,
+ previousView: null
+ };
+ gHistory.replaceState(state);
+
+ this.loadViewInternal(aViewId, null, state);
+ this.initialViewSelected = true;
+ notifyInitialized();
+ },
+
+ loadViewInternal: function gVC_loadViewInternal(aViewId, aPreviousView, aState) {
+ var view = this.parseViewId(aViewId);
+
+ if (!view.type || !(view.type in this.viewObjects))
+ throw Components.Exception("Invalid view: " + view.type);
+
+ var viewObj = this.viewObjects[view.type];
+ if (!viewObj.node)
+ throw Components.Exception("Root node doesn't exist for '" + view.type + "' view");
+
+ if (this.currentViewObj && aViewId != aPreviousView) {
+ try {
+ let canHide = this.currentViewObj.hide();
+ if (canHide === false)
+ return;
+ this.viewPort.selectedPanel.removeAttribute("loading");
+ } catch (e) {
+ // this shouldn't be fatal
+ Cu.reportError(e);
+ }
+ }
+
+ gCategories.select(aViewId, aPreviousView);
+
+ this.currentViewId = aViewId;
+ this.currentViewObj = viewObj;
+
+ this.viewPort.selectedPanel = this.currentViewObj.node;
+ this.viewPort.selectedPanel.setAttribute("loading", "true");
+ this.currentViewObj.node.focus();
+
+ if (aViewId == aPreviousView)
+ this.currentViewObj.refresh(view.param, ++this.currentViewRequest, aState);
+ else
+ this.currentViewObj.show(view.param, ++this.currentViewRequest, aState);
+ },
+
+ // Moves back in the document history and removes the current history entry
+ popState: function gVC_popState(aCallback) {
+ this.viewChangeCallback = aCallback;
+ gHistory.popState();
+ },
+
+ notifyViewChanged: function gVC_notifyViewChanged() {
+ this.viewPort.selectedPanel.removeAttribute("loading");
+
+ if (this.viewChangeCallback) {
+ this.viewChangeCallback();
+ this.viewChangeCallback = null;
+ }
+
+ var event = document.createEvent("Events");
+ event.initEvent("ViewChanged", true, true);
+ this.currentViewObj.node.dispatchEvent(event);
+ },
+
+ commands: {
+ cmd_back: {
+ isEnabled: function cmd_back_isEnabled() {
+ return gHistory.canGoBack;
+ },
+ doCommand: function cmd_back_doCommand() {
+ gHistory.back();
+ }
+ },
+
+ cmd_forward: {
+ isEnabled: function cmd_forward_isEnabled() {
+ return gHistory.canGoForward;
+ },
+ doCommand: function cmd_forward_doCommand() {
+ gHistory.forward();
+ }
+ },
+
+ cmd_focusSearch: {
+ isEnabled: () => true,
+ doCommand: function cmd_focusSearch_doCommand() {
+ gHeader.focusSearchBox();
+ }
+ },
+
+ cmd_restartApp: {
+ isEnabled: function cmd_restartApp_isEnabled() true,
+ doCommand: function cmd_restartApp_doCommand() {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+ if (cancelQuit.data)
+ return; // somebody canceled our quit request
+
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
+ getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+ }
+ },
+
+ cmd_enableCheckCompatibility: {
+ isEnabled: function cmd_enableCheckCompatibility_isEnabled() true,
+ doCommand: function cmd_enableCheckCompatibility_doCommand() {
+ AddonManager.checkCompatibility = true;
+ }
+ },
+
+ cmd_enableUpdateSecurity: {
+ isEnabled: function cmd_enableUpdateSecurity_isEnabled() true,
+ doCommand: function cmd_enableUpdateSecurity_doCommand() {
+ AddonManager.checkUpdateSecurity = true;
+ }
+ },
+
+ cmd_toggleAutoUpdateDefault: {
+ isEnabled: function cmd_toggleAutoUpdateDefault_isEnabled() true,
+ doCommand: function cmd_toggleAutoUpdateDefault_doCommand() {
+ if (!AddonManager.updateEnabled || !AddonManager.autoUpdateDefault) {
+ // One or both of the prefs is false, i.e. the checkbox is not checked.
+ // Now toggle both to true. If the user wants us to auto-update
+ // add-ons, we also need to auto-check for updates.
+ AddonManager.updateEnabled = true;
+ AddonManager.autoUpdateDefault = true;
+ } else {
+ // Both prefs are true, i.e. the checkbox is checked.
+ // Toggle the auto pref to false, but don't touch the enabled check.
+ AddonManager.autoUpdateDefault = false;
+ }
+ }
+ },
+
+ cmd_resetAddonAutoUpdate: {
+ isEnabled: function cmd_resetAddonAutoUpdate_isEnabled() true,
+ doCommand: function cmd_resetAddonAutoUpdate_doCommand() {
+ AddonManager.getAllAddons(function cmd_resetAddonAutoUpdate_getAllAddons(aAddonList) {
+ for (let addon of aAddonList) {
+ if ("applyBackgroundUpdates" in addon)
+ addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+ }
+ });
+ }
+ },
+
+ cmd_goToDiscoverPane: {
+ isEnabled: function cmd_goToDiscoverPane_isEnabled() {
+ return gDiscoverView.enabled;
+ },
+ doCommand: function cmd_goToDiscoverPane_doCommand() {
+ gViewController.loadView("addons://discover/");
+ }
+ },
+
+ cmd_goToRecentUpdates: {
+ isEnabled: function cmd_goToRecentUpdates_isEnabled() true,
+ doCommand: function cmd_goToRecentUpdates_doCommand() {
+ gViewController.loadView("addons://updates/recent");
+ }
+ },
+
+ cmd_goToAvailableUpdates: {
+ isEnabled: function cmd_goToAvailableUpdates_isEnabled() true,
+ doCommand: function cmd_goToAvailableUpdates_doCommand() {
+ gViewController.loadView("addons://updates/available");
+ }
+ },
+
+ cmd_showItemDetails: {
+ isEnabled: function cmd_showItemDetails_isEnabled(aAddon) {
+ return !!aAddon && (gViewController.currentViewObj != gDetailView);
+ },
+ doCommand: function cmd_showItemDetails_doCommand(aAddon, aScrollToPreferences) {
+ gViewController.loadView("addons://detail/" +
+ encodeURIComponent(aAddon.id) +
+ (aScrollToPreferences ? "/preferences" : ""));
+ }
+ },
+
+ cmd_findAllUpdates: {
+ inProgress: false,
+ isEnabled: function cmd_findAllUpdates_isEnabled() !this.inProgress,
+ doCommand: function cmd_findAllUpdates_doCommand() {
+ this.inProgress = true;
+ gViewController.updateCommand("cmd_findAllUpdates");
+ document.getElementById("updates-noneFound").hidden = true;
+ document.getElementById("updates-progress").hidden = false;
+ document.getElementById("updates-manualUpdatesFound-btn").hidden = true;
+
+ var pendingChecks = 0;
+ var numUpdated = 0;
+ var numManualUpdates = 0;
+ var restartNeeded = false;
+ var self = this;
+
+ function updateStatus() {
+ if (pendingChecks > 0)
+ return;
+
+ self.inProgress = false;
+ gViewController.updateCommand("cmd_findAllUpdates");
+ document.getElementById("updates-progress").hidden = true;
+ gUpdatesView.maybeRefresh();
+
+ if (numManualUpdates > 0 && numUpdated == 0) {
+ document.getElementById("updates-manualUpdatesFound-btn").hidden = false;
+ return;
+ }
+
+ if (numUpdated == 0) {
+ document.getElementById("updates-noneFound").hidden = false;
+ return;
+ }
+
+ if (restartNeeded) {
+ document.getElementById("updates-downloaded").hidden = false;
+ document.getElementById("updates-restart-btn").hidden = false;
+ } else {
+ document.getElementById("updates-installed").hidden = false;
+ }
+ }
+
+ var updateInstallListener = {
+ onDownloadFailed: function cmd_findAllUpdates_downloadFailed() {
+ pendingChecks--;
+ updateStatus();
+ },
+ onInstallFailed: function cmd_findAllUpdates_installFailed() {
+ pendingChecks--;
+ updateStatus();
+ },
+ onInstallEnded: function cmd_findAllUpdates_installEnded(aInstall, aAddon) {
+ pendingChecks--;
+ numUpdated++;
+ if (isPending(aInstall.existingAddon, "upgrade"))
+ restartNeeded = true;
+ updateStatus();
+ }
+ };
+
+ var updateCheckListener = {
+ onUpdateAvailable: function cmd_findAllUpdates_updateAvailable(aAddon, aInstall) {
+ gEventManager.delegateAddonEvent("onUpdateAvailable",
+ [aAddon, aInstall]);
+ if (AddonManager.shouldAutoUpdate(aAddon)) {
+ aInstall.addListener(updateInstallListener);
+ aInstall.install();
+ } else {
+ pendingChecks--;
+ numManualUpdates++;
+ updateStatus();
+ }
+ },
+ onNoUpdateAvailable: function cmd_findAllUpdates_noUpdateAvailable(aAddon) {
+ pendingChecks--;
+ updateStatus();
+ },
+ onUpdateFinished: function cmd_findAllUpdates_updateFinished(aAddon, aError) {
+ gEventManager.delegateAddonEvent("onUpdateFinished",
+ [aAddon, aError]);
+ }
+ };
+
+ AddonManager.getAddonsByTypes(null, function cmd_findAllUpdates_getAddonsByTypes(aAddonList) {
+ for (let addon of aAddonList) {
+ if (addon.permissions & AddonManager.PERM_CAN_UPGRADE) {
+ pendingChecks++;
+ addon.findUpdates(updateCheckListener,
+ AddonManager.UPDATE_WHEN_USER_REQUESTED);
+ }
+ }
+
+ if (pendingChecks == 0)
+ updateStatus();
+ });
+ }
+ },
+
+ cmd_findItemUpdates: {
+ isEnabled: function cmd_findItemUpdates_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return hasPermission(aAddon, "upgrade");
+ },
+ doCommand: function cmd_findItemUpdates_doCommand(aAddon) {
+ var listener = {
+ onUpdateAvailable: function cmd_findItemUpdates_updateAvailable(aAddon, aInstall) {
+ gEventManager.delegateAddonEvent("onUpdateAvailable",
+ [aAddon, aInstall]);
+ if (AddonManager.shouldAutoUpdate(aAddon))
+ aInstall.install();
+ },
+ onNoUpdateAvailable: function cmd_findItemUpdates_noUpdateAvailable(aAddon) {
+ gEventManager.delegateAddonEvent("onNoUpdateAvailable",
+ [aAddon]);
+ }
+ };
+ gEventManager.delegateAddonEvent("onCheckingUpdate", [aAddon]);
+ aAddon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
+ }
+ },
+
+#ifdef MOZ_DEVTOOLS
+ cmd_debugItem: {
+ doCommand: function cmd_debugItem_doCommand(aAddon) {
+ BrowserToolboxProcess.init({ addonID: aAddon.id });
+ },
+
+ isEnabled: function cmd_debugItem_isEnabled(aAddon) {
+ let debuggerEnabled = Services.prefs.
+ getBoolPref(PREF_ADDON_DEBUGGING_ENABLED);
+ let remoteEnabled = Services.prefs.
+ getBoolPref(PREF_REMOTE_DEBUGGING_ENABLED);
+ return aAddon && aAddon.isDebuggable && debuggerEnabled && remoteEnabled;
+ }
+ },
+#endif
+
+ cmd_showItemPreferences: {
+ isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) {
+ if (!aAddon ||
+ !aAddon.isActive ||
+ !aAddon.optionsURL) {
+ return false;
+ }
+ if (gViewController.currentViewObj == gDetailView &&
+ aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
+ return false;
+ }
+ if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO)
+ return false;
+ return true;
+ },
+ doCommand: function cmd_showItemPreferences_doCommand(aAddon) {
+ if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
+ gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);
+ return;
+ }
+ var optionsURL = aAddon.optionsURL;
+ if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_TAB &&
+ openOptionsInTab(optionsURL)) {
+ return;
+ }
+ var windows = Services.wm.getEnumerator(null);
+ while (windows.hasMoreElements()) {
+ var win = windows.getNext();
+ if (win.closed) {
+ continue;
+ }
+ if (win.document.documentURI == optionsURL) {
+ win.focus();
+ return;
+ }
+ }
+ var features = "chrome,titlebar,toolbar,centerscreen";
+ try {
+ var instantApply = Services.prefs.getBoolPref("browser.preferences.instantApply");
+ features += instantApply ? ",dialog=no" : ",modal";
+ } catch (e) {
+ features += ",modal";
+ }
+ openDialog(optionsURL, "", features);
+ }
+ },
+
+ cmd_showItemAbout: {
+ isEnabled: function cmd_showItemAbout_isEnabled(aAddon) {
+ // XXXunf This may be applicable to install items too. See bug 561260
+ return !!aAddon;
+ },
+ doCommand: function cmd_showItemAbout_doCommand(aAddon) {
+ var aboutURL = aAddon.aboutURL;
+ if (aboutURL)
+ openDialog(aboutURL, "", "chrome,centerscreen,modal", aAddon);
+ else
+ openDialog("chrome://mozapps/content/extensions/about.xul",
+ "", "chrome,centerscreen,modal", aAddon);
+ }
+ },
+
+ cmd_enableItem: {
+ isEnabled: function cmd_enableItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ let addonType = AddonManager.addonTypes[aAddon.type];
+ return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ hasPermission(aAddon, "enable"));
+ },
+ doCommand: function cmd_enableItem_doCommand(aAddon) {
+ aAddon.userDisabled = false;
+ },
+ getTooltip: function cmd_enableItem_getTooltip(aAddon) {
+ if (!aAddon)
+ return "";
+ if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE)
+ return gStrings.ext.GetStringFromName("enableAddonRestartRequiredTooltip");
+ return gStrings.ext.GetStringFromName("enableAddonTooltip");
+ }
+ },
+
+ cmd_disableItem: {
+ isEnabled: function cmd_disableItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ let addonType = AddonManager.addonTypes[aAddon.type];
+ return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ hasPermission(aAddon, "disable"));
+ },
+ doCommand: function cmd_disableItem_doCommand(aAddon) {
+ aAddon.userDisabled = true;
+ },
+ getTooltip: function cmd_disableItem_getTooltip(aAddon) {
+ if (!aAddon)
+ return "";
+ if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE)
+ return gStrings.ext.GetStringFromName("disableAddonRestartRequiredTooltip");
+ return gStrings.ext.GetStringFromName("disableAddonTooltip");
+ }
+ },
+
+ cmd_installItem: {
+ isEnabled: function cmd_installItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return aAddon.install && aAddon.install.state == AddonManager.STATE_AVAILABLE;
+ },
+ doCommand: function cmd_installItem_doCommand(aAddon) {
+ function doInstall() {
+ gViewController.currentViewObj.getListItemForID(aAddon.id)._installStatus.installRemote();
+ }
+
+ if (gViewController.currentViewObj == gDetailView)
+ gViewController.popState(doInstall);
+ else
+ doInstall();
+ }
+ },
+
+ cmd_purchaseItem: {
+ isEnabled: function cmd_purchaseItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return !!aAddon.purchaseURL;
+ },
+ doCommand: function cmd_purchaseItem_doCommand(aAddon) {
+ openURL(aAddon.purchaseURL);
+ }
+ },
+
+ cmd_uninstallItem: {
+ isEnabled: function cmd_uninstallItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return hasPermission(aAddon, "uninstall");
+ },
+ doCommand: function cmd_uninstallItem_doCommand(aAddon) {
+ if (gViewController.currentViewObj != gDetailView) {
+ aAddon.uninstall();
+ return;
+ }
+
+ gViewController.popState(function cmd_uninstallItem_popState() {
+ gViewController.currentViewObj.getListItemForID(aAddon.id).uninstall();
+ });
+ },
+ getTooltip: function cmd_uninstallItem_getTooltip(aAddon) {
+ if (!aAddon)
+ return "";
+ if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL)
+ return gStrings.ext.GetStringFromName("uninstallAddonRestartRequiredTooltip");
+ return gStrings.ext.GetStringFromName("uninstallAddonTooltip");
+ }
+ },
+
+ cmd_cancelUninstallItem: {
+ isEnabled: function cmd_cancelUninstallItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return isPending(aAddon, "uninstall");
+ },
+ doCommand: function cmd_cancelUninstallItem_doCommand(aAddon) {
+ aAddon.cancelUninstall();
+ }
+ },
+
+ cmd_installFromFile: {
+ isEnabled: function cmd_installFromFile_isEnabled() true,
+ doCommand: function cmd_installFromFile_doCommand() {
+ const nsIFilePicker = Ci.nsIFilePicker;
+ var fp = Cc["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ fp.init(window,
+ gStrings.ext.GetStringFromName("installFromFile.dialogTitle"),
+ nsIFilePicker.modeOpenMultiple);
+ try {
+ fp.appendFilter(gStrings.ext.GetStringFromName("installFromFile.filterName"),
+ "*.xpi;*.jar");
+ fp.appendFilters(nsIFilePicker.filterAll);
+ } catch (e) { }
+
+ if (fp.show() != nsIFilePicker.returnOK)
+ return;
+
+ var files = fp.files;
+ var installs = [];
+
+ function buildNextInstall() {
+ if (!files.hasMoreElements()) {
+ if (installs.length > 0) {
+ // Display the normal install confirmation for the installs
+ let webInstaller = Cc["@mozilla.org/addons/web-install-listener;1"].
+ getService(Ci.amIWebInstallListener);
+ webInstaller.onWebInstallRequested(getBrowserElement(),
+ document.documentURIObject,
+ installs, installs.length);
+ }
+ return;
+ }
+
+ var file = files.getNext();
+ AddonManager.getInstallForFile(file, function cmd_installFromFile_getInstallForFile(aInstall) {
+ installs.push(aInstall);
+ buildNextInstall();
+ });
+ }
+
+ buildNextInstall();
+ }
+ },
+
+ cmd_cancelOperation: {
+ isEnabled: function cmd_cancelOperation_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return aAddon.pendingOperations != AddonManager.PENDING_NONE;
+ },
+ doCommand: function cmd_cancelOperation_doCommand(aAddon) {
+ if (isPending(aAddon, "install")) {
+ aAddon.install.cancel();
+ } else if (isPending(aAddon, "upgrade")) {
+ aAddon.pendingUpgrade.install.cancel();
+ } else if (isPending(aAddon, "uninstall")) {
+ aAddon.cancelUninstall();
+ } else if (isPending(aAddon, "enable")) {
+ aAddon.userDisabled = true;
+ } else if (isPending(aAddon, "disable")) {
+ aAddon.userDisabled = false;
+ }
+ }
+ },
+
+ cmd_contribute: {
+ isEnabled: function cmd_contribute_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ return ("contributionURL" in aAddon && aAddon.contributionURL);
+ },
+ doCommand: function cmd_contribute_doCommand(aAddon) {
+ openURL(aAddon.contributionURL);
+ }
+ },
+
+ cmd_askToActivateItem: {
+ isEnabled: function cmd_askToActivateItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ let addonType = AddonManager.addonTypes[aAddon.type];
+ return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ hasPermission(aAddon, "ask_to_activate"));
+ },
+ doCommand: function cmd_askToActivateItem_doCommand(aAddon) {
+ aAddon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;
+ }
+ },
+
+ cmd_alwaysActivateItem: {
+ isEnabled: function cmd_alwaysActivateItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ let addonType = AddonManager.addonTypes[aAddon.type];
+ return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ hasPermission(aAddon, "enable"));
+ },
+ doCommand: function cmd_alwaysActivateItem_doCommand(aAddon) {
+ aAddon.userDisabled = false;
+ }
+ },
+
+ cmd_neverActivateItem: {
+ isEnabled: function cmd_neverActivateItem_isEnabled(aAddon) {
+ if (!aAddon)
+ return false;
+ let addonType = AddonManager.addonTypes[aAddon.type];
+ return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ hasPermission(aAddon, "disable"));
+ },
+ doCommand: function cmd_neverActivateItem_doCommand(aAddon) {
+ aAddon.userDisabled = true;
+ }
+ }
+ },
+
+ supportsCommand: function gVC_supportsCommand(aCommand) {
+ return (aCommand in this.commands);
+ },
+
+ isCommandEnabled: function gVC_isCommandEnabled(aCommand) {
+ if (!this.supportsCommand(aCommand))
+ return false;
+ var addon = this.currentViewObj.getSelectedAddon();
+ return this.commands[aCommand].isEnabled(addon);
+ },
+
+ updateCommands: function gVC_updateCommands() {
+ // wait until the view is initialized
+ if (!this.currentViewObj)
+ return;
+ var addon = this.currentViewObj.getSelectedAddon();
+ for (let commandId in this.commands)
+ this.updateCommand(commandId, addon);
+ },
+
+ updateCommand: function gVC_updateCommand(aCommandId, aAddon) {
+ if (typeof aAddon == "undefined")
+ aAddon = this.currentViewObj.getSelectedAddon();
+ var cmd = this.commands[aCommandId];
+ var cmdElt = document.getElementById(aCommandId);
+ cmdElt.setAttribute("disabled", !cmd.isEnabled(aAddon));
+ if ("getTooltip" in cmd) {
+ let tooltip = cmd.getTooltip(aAddon);
+ if (tooltip)
+ cmdElt.setAttribute("tooltiptext", tooltip);
+ else
+ cmdElt.removeAttribute("tooltiptext");
+ }
+ },
+
+ doCommand: function gVC_doCommand(aCommand, aAddon) {
+ if (!this.supportsCommand(aCommand))
+ return;
+ var cmd = this.commands[aCommand];
+ if (!aAddon)
+ aAddon = this.currentViewObj.getSelectedAddon();
+ if (!cmd.isEnabled(aAddon))
+ return;
+ cmd.doCommand(aAddon);
+ },
+
+ onEvent: function gVC_onEvent() {}
+};
+
+function hasInlineOptions(aAddon) {
+ return (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE ||
+ aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO);
+}
+
+function openOptionsInTab(optionsURL) {
+ let mainWindow = getMainWindow();
+ if ("switchToTabHavingURI" in mainWindow) {
+ mainWindow.switchToTabHavingURI(optionsURL, true);
+ return true;
+ }
+ return false;
+}
+
+function formatDate(aDate) {
+ return Cc["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Ci.nsIScriptableDateFormat)
+ .FormatDate("",
+ Ci.nsIScriptableDateFormat.dateFormatLong,
+ aDate.getFullYear(),
+ aDate.getMonth() + 1,
+ aDate.getDate()
+ );
+}
+
+
+function hasPermission(aAddon, aPerm) {
+ var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
+ return !!(aAddon.permissions & perm);
+}
+
+
+function isPending(aAddon, aAction) {
+ var action = AddonManager["PENDING_" + aAction.toUpperCase()];
+ return !!(aAddon.pendingOperations & action);
+}
+
+function isInState(aInstall, aState) {
+ var state = AddonManager["STATE_" + aState.toUpperCase()];
+ return aInstall.state == state;
+}
+
+function shouldShowVersionNumber(aAddon) {
+ if (!aAddon.version)
+ return false;
+
+ // The version number is hidden for lightweight themes.
+ if (aAddon.type == "theme")
+ return !/@personas\.mozilla\.org$/.test(aAddon.id);
+
+ return true;
+}
+
+function createItem(aObj, aIsInstall, aIsRemote) {
+ let item = document.createElement("richlistitem");
+
+ item.setAttribute("class", "addon addon-view");
+ item.setAttribute("name", aObj.name);
+ item.setAttribute("type", aObj.type);
+ item.setAttribute("remote", !!aIsRemote);
+
+ if (aIsInstall) {
+ item.mInstall = aObj;
+
+ if (aObj.state != AddonManager.STATE_INSTALLED) {
+ item.setAttribute("status", "installing");
+ return item;
+ }
+ aObj = aObj.addon;
+ }
+
+ item.mAddon = aObj;
+
+ item.setAttribute("status", "installed");
+
+ // set only attributes needed for sorting and XBL binding,
+ // the binding handles the rest
+ item.setAttribute("value", aObj.id);
+
+ return item;
+}
+
+function sortElements(aElements, aSortBy, aAscending) {
+ // aSortBy is an Array of attributes to sort by, in decending
+ // order of priority.
+
+ const DATE_FIELDS = ["updateDate"];
+ const NUMERIC_FIELDS = ["size", "relevancescore", "purchaseAmount"];
+
+ // We're going to group add-ons into the following buckets:
+ //
+ // enabledInstalled
+ // * Enabled
+ // * Incompatible but enabled because compatibility checking is off
+ // * Waiting to be installed
+ // * Waiting to be enabled
+ //
+ // pendingDisable
+ // * Waiting to be disabled
+ //
+ // pendingUninstall
+ // * Waiting to be removed
+ //
+ // disabledIncompatibleBlocked
+ // * Disabled
+ // * Incompatible
+ // * Blocklisted
+
+ const UISTATE_ORDER = ["enabled", "askToActivate", "pendingDisable",
+ "pendingUninstall", "disabled"];
+
+ function dateCompare(a, b) {
+ var aTime = a.getTime();
+ var bTime = b.getTime();
+ if (aTime < bTime)
+ return -1;
+ if (aTime > bTime)
+ return 1;
+ return 0;
+ }
+
+ function numberCompare(a, b) {
+ return a - b;
+ }
+
+ function stringCompare(a, b) {
+ return a.localeCompare(b);
+ }
+
+ function uiStateCompare(a, b) {
+ // If we're in descending order, swap a and b, because
+ // we don't ever want to have descending uiStates
+ if (!aAscending)
+ [a, b] = [b, a];
+
+ return (UISTATE_ORDER.indexOf(a) - UISTATE_ORDER.indexOf(b));
+ }
+
+ function getValue(aObj, aKey) {
+ if (!aObj)
+ return null;
+
+ if (aObj.hasAttribute(aKey))
+ return aObj.getAttribute(aKey);
+
+ var addon = aObj.mAddon || aObj.mInstall;
+ var addonType = aObj.mAddon && AddonManager.addonTypes[aObj.mAddon.type];
+
+ if (!addon)
+ return null;
+
+ if (aKey == "uiState") {
+ if (addon.pendingOperations == AddonManager.PENDING_DISABLE)
+ return "pendingDisable";
+ if (addon.pendingOperations == AddonManager.PENDING_UNINSTALL)
+ return "pendingUninstall";
+ if (!addon.isActive &&
+ (addon.pendingOperations != AddonManager.PENDING_ENABLE &&
+ addon.pendingOperations != AddonManager.PENDING_INSTALL))
+ return "disabled";
+ if (addonType && (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
+ addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE)
+ return "askToActivate";
+ else
+ return "enabled";
+ }
+
+ return addon[aKey];
+ }
+
+ // aSortFuncs will hold the sorting functions that we'll
+ // use per element, in the correct order.
+ var aSortFuncs = [];
+
+ for (let i = 0; i < aSortBy.length; i++) {
+ var sortBy = aSortBy[i];
+
+ aSortFuncs[i] = stringCompare;
+
+ if (sortBy == "uiState")
+ aSortFuncs[i] = uiStateCompare;
+ else if (DATE_FIELDS.indexOf(sortBy) != -1)
+ aSortFuncs[i] = dateCompare;
+ else if (NUMERIC_FIELDS.indexOf(sortBy) != -1)
+ aSortFuncs[i] = numberCompare;
+ }
+
+
+ aElements.sort(function elementsSort(a, b) {
+ if (!aAscending)
+ [a, b] = [b, a];
+
+ for (let i = 0; i < aSortFuncs.length; i++) {
+ var sortBy = aSortBy[i];
+ var aValue = getValue(a, sortBy);
+ var bValue = getValue(b, sortBy);
+
+ if (!aValue && !bValue)
+ return 0;
+ if (!aValue)
+ return -1;
+ if (!bValue)
+ return 1;
+ if (aValue != bValue) {
+ var result = aSortFuncs[i](aValue, bValue);
+
+ if (result != 0)
+ return result;
+ }
+ }
+
+ // If we got here, then all values of a and b
+ // must have been equal.
+ return 0;
+
+ });
+}
+
+function sortList(aList, aSortBy, aAscending) {
+ var elements = Array.slice(aList.childNodes, 0);
+ sortElements(elements, [aSortBy], aAscending);
+
+ while (aList.listChild)
+ aList.removeChild(aList.lastChild);
+
+ for (let element of elements)
+ aList.appendChild(element);
+}
+
+function getAddonsAndInstalls(aType, aCallback) {
+ let addons = null, installs = null;
+ let types = (aType != null) ? [aType] : null;
+
+ AddonManager.getAddonsByTypes(types, function getAddonsAndInstalls_getAddonsByTypes(aAddonsList) {
+ addons = aAddonsList;
+ if (installs != null)
+ aCallback(addons, installs);
+ });
+
+ AddonManager.getInstallsByTypes(types, function getAddonsAndInstalls_getInstallsByTypes(aInstallsList) {
+ // skip over upgrade installs and non-active installs
+ installs = aInstallsList.filter(function installsFilter(aInstall) {
+ return !(aInstall.existingAddon ||
+ aInstall.state == AddonManager.STATE_AVAILABLE);
+ });
+
+ if (addons != null)
+ aCallback(addons, installs)
+ });
+}
+
+function doPendingUninstalls(aListBox) {
+ // Uninstalling add-ons can mutate the list so find the add-ons first then
+ // uninstall them
+ var items = [];
+ var listitem = aListBox.firstChild;
+ while (listitem) {
+ if (listitem.getAttribute("pending") == "uninstall" &&
+ !(listitem.opRequiresRestart("uninstall")))
+ items.push(listitem.mAddon);
+ listitem = listitem.nextSibling;
+ }
+
+ for (let addon of items)
+ addon.uninstall();
+}
+
+var gCategories = {
+ node: null,
+ _search: null,
+
+ initialize: function gCategories_initialize() {
+ this.node = document.getElementById("categories");
+ this._search = this.get("addons://search/");
+
+ var types = AddonManager.addonTypes;
+ for (var type in types)
+ this.onTypeAdded(types[type]);
+
+ AddonManager.addTypeListener(this);
+
+ try {
+ this.node.value = Services.prefs.getCharPref(PREF_UI_LASTCATEGORY);
+ } catch (e) { }
+
+ // If there was no last view or no existing category matched the last view
+ // then the list will default to selecting the search category and we never
+ // want to show that as the first view so switch to the default category
+ if (!this.node.selectedItem || this.node.selectedItem == this._search)
+ this.node.value = gViewDefault;
+
+ var self = this;
+ this.node.addEventListener("select", function node_onSelected() {
+ self.maybeHideSearch();
+ gViewController.loadView(self.node.selectedItem.value);
+ }, false);
+
+ this.node.addEventListener("click", function node_onClicked(aEvent) {
+ var selectedItem = self.node.selectedItem;
+ if (aEvent.target.localName == "richlistitem" &&
+ aEvent.target == selectedItem) {
+ var viewId = selectedItem.value;
+
+ if (gViewController.parseViewId(viewId).type == "search") {
+ viewId += encodeURIComponent(gHeader.searchQuery);
+ }
+
+ gViewController.loadView(viewId);
+ }
+ }, false);
+ },
+
+ shutdown: function gCategories_shutdown() {
+ AddonManager.removeTypeListener(this);
+ },
+
+ _insertCategory: function gCategories_insertCategory(aId, aName, aView, aPriority, aStartHidden) {
+ // If this category already exists then don't re-add it
+ if (document.getElementById("category-" + aId))
+ return;
+
+ var category = document.createElement("richlistitem");
+ category.setAttribute("id", "category-" + aId);
+ category.setAttribute("value", aView);
+ category.setAttribute("class", "category");
+ category.setAttribute("name", aName);
+ category.setAttribute("tooltiptext", aName);
+ category.setAttribute("priority", aPriority);
+ category.setAttribute("hidden", aStartHidden);
+
+ var node;
+ for (node of this.node.children) {
+ var nodePriority = parseInt(node.getAttribute("priority"));
+ // If the new type's priority is higher than this one then this is the
+ // insertion point
+ if (aPriority < nodePriority)
+ break;
+ // If the new type's priority is lower than this one then this is isn't
+ // the insertion point
+ if (aPriority > nodePriority)
+ continue;
+ // If the priorities are equal and the new type's name is earlier
+ // alphabetically then this is the insertion point
+ if (String.localeCompare(aName, node.getAttribute("name")) < 0)
+ break;
+ }
+
+ this.node.insertBefore(category, node);
+ },
+
+ _removeCategory: function gCategories_removeCategory(aId) {
+ var category = document.getElementById("category-" + aId);
+ if (!category)
+ return;
+
+ // If this category is currently selected then switch to the default view
+ if (this.node.selectedItem == category)
+ gViewController.replaceView(gViewDefault);
+
+ this.node.removeChild(category);
+ },
+
+ onTypeAdded: function gCategories_onTypeAdded(aType) {
+ // Ignore types that we don't have a view object for
+ if (!(aType.viewType in gViewController.viewObjects))
+ return;
+
+ var aViewId = "addons://" + aType.viewType + "/" + aType.id;
+
+ var startHidden = false;
+ if (aType.flags & AddonManager.TYPE_UI_HIDE_EMPTY) {
+ var prefName = PREF_UI_TYPE_HIDDEN.replace("%TYPE%", aType.id);
+ try {
+ startHidden = Services.prefs.getBoolPref(prefName);
+ }
+ catch (e) {
+ // Default to hidden
+ startHidden = true;
+ }
+
+ var self = this;
+ gPendingInitializations++;
+ getAddonsAndInstalls(aType.id, function onTypeAdded_getAddonsAndInstalls(aAddonsList, aInstallsList) {
+ var hidden = (aAddonsList.length == 0 && aInstallsList.length == 0);
+ var item = self.get(aViewId);
+
+ // Don't load view that is becoming hidden
+ if (hidden && aViewId == gViewController.currentViewId)
+ gViewController.loadView(gViewDefault);
+
+ item.hidden = hidden;
+ Services.prefs.setBoolPref(prefName, hidden);
+
+ if (aAddonsList.length > 0 || aInstallsList.length > 0) {
+ notifyInitialized();
+ return;
+ }
+
+ gEventManager.registerInstallListener({
+ onDownloadStarted: function gCategories_onDownloadStarted(aInstall) {
+ this._maybeShowCategory(aInstall);
+ },
+
+ onInstallStarted: function gCategories_onInstallStarted(aInstall) {
+ this._maybeShowCategory(aInstall);
+ },
+
+ onInstallEnded: function gCategories_onInstallEnded(aInstall, aAddon) {
+ this._maybeShowCategory(aAddon);
+ },
+
+ onExternalInstall: function gCategories_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) {
+ this._maybeShowCategory(aAddon);
+ },
+
+ _maybeShowCategory: function gCategories_maybeShowCategory(aAddon) {
+ if (aType.id == aAddon.type) {
+ self.get(aViewId).hidden = false;
+ Services.prefs.setBoolPref(prefName, false);
+ gEventManager.unregisterInstallListener(this);
+ }
+ }
+ });
+
+ notifyInitialized();
+ });
+ }
+
+ this._insertCategory(aType.id, aType.name, aViewId, aType.uiPriority,
+ startHidden);
+ },
+
+ onTypeRemoved: function gCategories_onTypeRemoved(aType) {
+ this._removeCategory(aType.id);
+ },
+
+ get selected() {
+ return this.node.selectedItem ? this.node.selectedItem.value : null;
+ },
+
+ select: function gCategories_select(aId, aPreviousView) {
+ var view = gViewController.parseViewId(aId);
+ if (view.type == "detail" && aPreviousView) {
+ aId = aPreviousView;
+ view = gViewController.parseViewId(aPreviousView);
+ }
+
+ Services.prefs.setCharPref(PREF_UI_LASTCATEGORY, aId);
+
+ if (this.node.selectedItem &&
+ this.node.selectedItem.value == aId) {
+ this.node.selectedItem.hidden = false;
+ this.node.selectedItem.disabled = false;
+ return;
+ }
+
+ if (view.type == "search")
+ var item = this._search;
+ else
+ var item = this.get(aId);
+
+ if (item) {
+ item.hidden = false;
+ item.disabled = false;
+ this.node.suppressOnSelect = true;
+ this.node.selectedItem = item;
+ this.node.suppressOnSelect = false;
+ this.node.ensureElementIsVisible(item);
+
+ this.maybeHideSearch();
+ }
+ },
+
+ get: function gCategories_get(aId) {
+ var items = document.getElementsByAttribute("value", aId);
+ if (items.length)
+ return items[0];
+ return null;
+ },
+
+ setBadge: function gCategories_setBadge(aId, aCount) {
+ let item = this.get(aId);
+ if (item)
+ item.badgeCount = aCount;
+ },
+
+ maybeHideSearch: function gCategories_maybeHideSearch() {
+ var view = gViewController.parseViewId(this.node.selectedItem.value);
+ this._search.disabled = view.type != "search";
+ }
+};
+
+
+var gHeader = {
+ _search: null,
+ _dest: "",
+
+ initialize: function gHeader_initialize() {
+ this._search = document.getElementById("header-search");
+
+ this._search.addEventListener("command", function search_onCommand(aEvent) {
+ var query = aEvent.target.value;
+ if (query.length == 0)
+ return;
+
+ gViewController.loadView("addons://search/" + encodeURIComponent(query));
+ }, false);
+
+ function updateNavButtonVisibility() {
+ var shouldShow = gHeader.shouldShowNavButtons;
+ document.getElementById("back-btn").hidden = !shouldShow;
+ document.getElementById("forward-btn").hidden = !shouldShow;
+ }
+
+ window.addEventListener("focus", function window_onFocus(aEvent) {
+ if (aEvent.target == window)
+ updateNavButtonVisibility();
+ }, false);
+
+ updateNavButtonVisibility();
+ },
+
+ focusSearchBox: function gHeader_focusSearchBox() {
+ this._search.focus();
+ },
+
+ onKeyPress: function gHeader_onKeyPress(aEvent) {
+ if (String.fromCharCode(aEvent.charCode) == "/") {
+ this.focusSearchBox();
+ return;
+ }
+
+ // XXXunf Temporary until bug 371900 is fixed.
+ let key = document.getElementById("focusSearch").getAttribute("key");
+ let keyModifier = aEvent.ctrlKey;
+ if (String.fromCharCode(aEvent.charCode) == key && keyModifier) {
+ this.focusSearchBox();
+ return;
+ }
+ },
+
+ get shouldShowNavButtons() {
+ var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem);
+
+ // If there is no outer frame then make the buttons visible
+ if (docshellItem.rootTreeItem == docshellItem)
+ return true;
+
+ var outerWin = docshellItem.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ var outerDoc = outerWin.document;
+ var node = outerDoc.getElementById("back-button");
+ // If the outer frame has no back-button then make the buttons visible
+ if (!node)
+ return true;
+
+ // If the back-button or any of its parents are hidden then make the buttons
+ // visible
+ while (node != outerDoc) {
+ var style = outerWin.getComputedStyle(node, "");
+ if (style.display == "none")
+ return true;
+ if (style.visibility != "visible")
+ return true;
+ node = node.parentNode;
+ }
+
+ return false;
+ },
+
+ get searchQuery() {
+ return this._search.value;
+ },
+
+ set searchQuery(aQuery) {
+ this._search.value = aQuery;
+ },
+};
+
+
+var gDiscoverView = {
+ node: null,
+ enabled: true,
+ // Set to true after the view is first shown. If initialization completes
+ // after this then it must also load the discover homepage
+ loaded: false,
+ _browser: null,
+ _loading: null,
+ _error: null,
+ homepageURL: null,
+ _loadListeners: [],
+
+ initialize: function gDiscoverView_initialize() {
+ this.enabled = isDiscoverEnabled();
+ if (!this.enabled) {
+ gCategories.get("addons://discover/").hidden = true;
+ return;
+ }
+
+ this.node = document.getElementById("discover-view");
+ this._loading = document.getElementById("discover-loading");
+ this._error = document.getElementById("discover-error");
+ this._browser = document.getElementById("discover-browser");
+
+ let compatMode = "normal";
+ if (!AddonManager.checkCompatibility)
+ compatMode = "ignore";
+ else if (AddonManager.strictCompatibility)
+ compatMode = "strict";
+
+ var url = Services.prefs.getCharPref(PREF_DISCOVERURL);
+ url = url.replace("%COMPATIBILITY_MODE%", compatMode);
+ url = Services.urlFormatter.formatURL(url);
+
+ var self = this;
+
+ function setURL(aURL) {
+ try {
+ self.homepageURL = Services.io.newURI(aURL, null, null);
+ } catch (e) {
+ self.showError();
+ notifyInitialized();
+ return;
+ }
+
+ self._browser.homePage = self.homepageURL.spec;
+ self._browser.addProgressListener(self);
+
+ if (self.loaded)
+ self._loadURL(self.homepageURL.spec, false, notifyInitialized);
+ else
+ notifyInitialized();
+ }
+
+ if (Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED) == false) {
+ setURL(url);
+ return;
+ }
+
+ gPendingInitializations++;
+ AddonManager.getAllAddons(function initialize_getAllAddons(aAddons) {
+ var list = {};
+ for (let addon of aAddons) {
+ var prefName = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%",
+ addon.id);
+ try {
+ if (!Services.prefs.getBoolPref(prefName))
+ continue;
+ } catch (e) { }
+ list[addon.id] = {
+ name: addon.name,
+ version: addon.version,
+ type: addon.type,
+ userDisabled: addon.userDisabled,
+ isCompatible: addon.isCompatible,
+ isBlocklisted: addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED
+ }
+ }
+
+ setURL(url + "#" + JSON.stringify(list));
+ });
+ },
+
+ destroy: function gDiscoverView_destroy() {
+ try {
+ this._browser.removeProgressListener(this);
+ }
+ catch (e) {
+ // Ignore the case when the listener wasn't already registered
+ }
+ },
+
+ show: function gDiscoverView_show(aParam, aRequest, aState, aIsRefresh) {
+ gViewController.updateCommands();
+
+ // If we're being told to load a specific URL then just do that
+ if (aState && "url" in aState) {
+ this.loaded = true;
+ this._loadURL(aState.url);
+ }
+
+ // If the view has loaded before and still at the homepage (if refreshing),
+ // and the error page is not visible then there is nothing else to do
+ if (this.loaded && this.node.selectedPanel != this._error &&
+ (!aIsRefresh || (this._browser.currentURI &&
+ this._browser.currentURI.spec == this._browser.homePage))) {
+ gViewController.notifyViewChanged();
+ return;
+ }
+
+ this.loaded = true;
+
+ // No homepage means initialization isn't complete, the browser will get
+ // loaded once initialization is complete
+ if (!this.homepageURL) {
+ this._loadListeners.push(gViewController.notifyViewChanged.bind(gViewController));
+ return;
+ }
+
+ this._loadURL(this.homepageURL.spec, aIsRefresh,
+ gViewController.notifyViewChanged.bind(gViewController));
+ },
+
+ canRefresh: function gDiscoverView_canRefresh() {
+ if (this._browser.currentURI &&
+ this._browser.currentURI.spec == this._browser.homePage)
+ return false;
+ return true;
+ },
+
+ refresh: function gDiscoverView_refresh(aParam, aRequest, aState) {
+ this.show(aParam, aRequest, aState, true);
+ },
+
+ hide: function gDiscoverView_hide() { },
+
+ showError: function gDiscoverView_showError() {
+ this.node.selectedPanel = this._error;
+ },
+
+ _loadURL: function gDiscoverView_loadURL(aURL, aKeepHistory, aCallback) {
+ if (this._browser.currentURI.spec == aURL) {
+ if (aCallback)
+ aCallback();
+ return;
+ }
+
+ if (aCallback)
+ this._loadListeners.push(aCallback);
+
+ var flags = 0;
+ if (!aKeepHistory)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
+
+ this._browser.loadURIWithFlags(aURL, flags);
+ },
+
+ onLocationChange: function gDiscoverView_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ // Ignore the about:blank load
+ if (aLocation.spec == "about:blank")
+ return;
+
+ // When using the real session history the inner-frame will update the
+ // session history automatically, if using the fake history though it must
+ // be manually updated
+ if (gHistory == FakeHistory) {
+ var docshell = aWebProgress.QueryInterface(Ci.nsIDocShell);
+
+ var state = {
+ view: "addons://discover/",
+ url: aLocation.spec
+ };
+
+ var replaceHistory = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY << 16;
+ if (docshell.loadType & replaceHistory)
+ gHistory.replaceState(state);
+ else
+ gHistory.pushState(state);
+ gViewController.lastHistoryIndex = gHistory.index;
+ }
+
+ gViewController.updateCommands();
+
+ // If the hostname is the same as the new location's host and either the
+ // default scheme is insecure or the new location is secure then continue
+ // with the load
+ if (aLocation.host == this.homepageURL.host &&
+ (!this.homepageURL.schemeIs("https") || aLocation.schemeIs("https")))
+ return;
+
+ // Canceling the request will send an error to onStateChange which will show
+ // the error page
+ aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+ },
+
+ onSecurityChange: function gDiscoverView_onSecurityChange(aWebProgress, aRequest, aState) {
+ // Don't care about security if the page is not https
+ if (!this.homepageURL.schemeIs("https"))
+ return;
+
+ // If the request was secure then it is ok
+ if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE)
+ return;
+
+ // Canceling the request will send an error to onStateChange which will show
+ // the error page
+ aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+ },
+
+ onStateChange: function gDiscoverView_onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ let transferStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_IS_REQUEST |
+ Ci.nsIWebProgressListener.STATE_TRANSFERRING;
+ // Once transferring begins show the content
+ if ((aStateFlags & transferStart) === transferStart)
+ this.node.selectedPanel = this._browser;
+
+ // Only care about the network events
+ if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK)))
+ return;
+
+ // If this is the start of network activity then show the loading page
+ if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START))
+ this.node.selectedPanel = this._loading;
+
+ // Ignore anything except stop events
+ if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP)))
+ return;
+
+ // Consider the successful load of about:blank as still loading
+ if (aRequest instanceof Ci.nsIChannel && aRequest.URI.spec == "about:blank")
+ return;
+
+ // If there was an error loading the page or the new hostname is not the
+ // same as the default hostname or the default scheme is secure and the new
+ // scheme is insecure then show the error page
+ const NS_ERROR_PARSED_DATA_CACHED = 0x805D0021;
+ if (!(Components.isSuccessCode(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED) ||
+ (aRequest && aRequest instanceof Ci.nsIHttpChannel && !aRequest.requestSucceeded)) {
+ this.showError();
+ } else {
+ // Got a successful load, make sure the browser is visible
+ this.node.selectedPanel = this._browser;
+ gViewController.updateCommands();
+ }
+
+ var listeners = this._loadListeners;
+ this._loadListeners = [];
+
+ for (let listener of listeners)
+ listener();
+ },
+
+ onProgressChange: function gDiscoverView_onProgressChange() { },
+ onStatusChange: function gDiscoverView_onStatusChange() { },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+
+ getSelectedAddon: function gDiscoverView_getSelectedAddon() null
+};
+
+
+var gCachedAddons = {};
+
+var gSearchView = {
+ node: null,
+ _filter: null,
+ _sorters: null,
+ _loading: null,
+ _listBox: null,
+ _emptyNotice: null,
+ _allResultsLink: null,
+ _lastQuery: null,
+ _lastRemoteTotal: 0,
+ _pendingSearches: 0,
+
+ initialize: function gSearchView_initialize() {
+ this.node = document.getElementById("search-view");
+ this._filter = document.getElementById("search-filter-radiogroup");
+ this._sorters = document.getElementById("search-sorters");
+ this._sorters.handler = this;
+ this._loading = document.getElementById("search-loading");
+ this._listBox = document.getElementById("search-list");
+ this._emptyNotice = document.getElementById("search-list-empty");
+ this._allResultsLink = document.getElementById("search-allresults-link");
+
+ if (!AddonManager.isInstallEnabled("application/x-xpinstall"))
+ this._filter.hidden = true;
+
+ var self = this;
+ this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) {
+ if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
+ var item = self._listBox.selectedItem;
+ if (item)
+ item.showInDetailView();
+ }
+ }, false);
+
+ this._filter.addEventListener("command", function filter_onCommand() self.updateView(), false);
+ },
+
+ shutdown: function gSearchView_shutdown() {
+ if (AddonRepository.isSearching)
+ AddonRepository.cancelSearch();
+ },
+
+ get isSearching() {
+ return this._pendingSearches > 0;
+ },
+
+ show: function gSearchView_show(aQuery, aRequest) {
+ gEventManager.registerInstallListener(this);
+
+ this.showEmptyNotice(false);
+ this.showAllResultsLink(0);
+ this.showLoading(true);
+ this._sorters.showprice = false;
+
+ gHeader.searchQuery = aQuery;
+ aQuery = aQuery.trim().toLocaleLowerCase();
+ if (this._lastQuery == aQuery) {
+ this.updateView();
+ gViewController.notifyViewChanged();
+ return;
+ }
+ this._lastQuery = aQuery;
+
+ if (AddonRepository.isSearching)
+ AddonRepository.cancelSearch();
+
+ while (this._listBox.firstChild.localName == "richlistitem")
+ this._listBox.removeChild(this._listBox.firstChild);
+
+ var self = this;
+ gCachedAddons = {};
+ this._pendingSearches = 2;
+ this._sorters.setSort("relevancescore", false);
+
+ var elements = [];
+
+ function createSearchResults(aObjsList, aIsInstall, aIsRemote) {
+ for (let index in aObjsList) {
+ let obj = aObjsList[index];
+ let score = aObjsList.length - index;
+ if (!aIsRemote && aQuery.length > 0) {
+ score = self.getMatchScore(obj, aQuery);
+ if (score == 0)
+ continue;
+ }
+
+ let item = createItem(obj, aIsInstall, aIsRemote);
+ item.setAttribute("relevancescore", score);
+ if (aIsRemote) {
+ gCachedAddons[obj.id] = obj;
+ if (obj.purchaseURL)
+ self._sorters.showprice = true;
+ }
+
+ elements.push(item);
+ }
+ }
+
+ function finishSearch(createdCount) {
+ if (elements.length > 0) {
+ sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
+ for (let element of elements)
+ self._listBox.insertBefore(element, self._listBox.lastChild);
+ self.updateListAttributes();
+ }
+
+ self._pendingSearches--;
+ self.updateView();
+
+ if (!self.isSearching)
+ gViewController.notifyViewChanged();
+ }
+
+ getAddonsAndInstalls(null, function show_getAddonsAndInstalls(aAddons, aInstalls) {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ createSearchResults(aAddons, false, false);
+ createSearchResults(aInstalls, true, false);
+ finishSearch();
+ });
+
+ var maxRemoteResults = 0;
+ try {
+ maxRemoteResults = Services.prefs.getIntPref(PREF_MAXRESULTS);
+ } catch(e) {}
+
+ if (maxRemoteResults <= 0) {
+ finishSearch(0);
+ return;
+ }
+
+ AddonRepository.searchAddons(aQuery, maxRemoteResults, {
+ searchFailed: function show_SearchFailed() {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ self._lastRemoteTotal = 0;
+
+ // XXXunf Better handling of AMO search failure. See bug 579502
+ finishSearch(0); // Silently fail
+ },
+
+ searchSucceeded: function show_SearchSucceeded(aAddonsList, aAddonCount, aTotalResults) {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ if (aTotalResults > maxRemoteResults)
+ self._lastRemoteTotal = aTotalResults;
+ else
+ self._lastRemoteTotal = 0;
+
+ var createdCount = createSearchResults(aAddonsList, false, true);
+ finishSearch(createdCount);
+ }
+ });
+ },
+
+ showLoading: function gSearchView_showLoading(aLoading) {
+ this._loading.hidden = !aLoading;
+ this._listBox.hidden = aLoading;
+ },
+
+ updateView: function gSearchView_updateView() {
+ var showLocal = this._filter.value == "local";
+
+ if (!showLocal && !AddonManager.isInstallEnabled("application/x-xpinstall"))
+ showLocal = true;
+
+ this._listBox.setAttribute("local", showLocal);
+ this._listBox.setAttribute("remote", !showLocal);
+
+ this.showLoading(this.isSearching && !showLocal);
+ if (!this.isSearching) {
+ var isEmpty = true;
+ var results = this._listBox.getElementsByTagName("richlistitem");
+ for (let result of results) {
+ var isRemote = (result.getAttribute("remote") == "true");
+ if ((isRemote && !showLocal) || (!isRemote && showLocal)) {
+ isEmpty = false;
+ break;
+ }
+ }
+
+ this.showEmptyNotice(isEmpty);
+ this.showAllResultsLink(this._lastRemoteTotal);
+ }
+
+ gViewController.updateCommands();
+ },
+
+ hide: function gSearchView_hide() {
+ gEventManager.unregisterInstallListener(this);
+ doPendingUninstalls(this._listBox);
+ },
+
+ getMatchScore: function gSearchView_getMatchScore(aObj, aQuery) {
+ var score = 0;
+ score += this.calculateMatchScore(aObj.name, aQuery,
+ SEARCH_SCORE_MULTIPLIER_NAME);
+ score += this.calculateMatchScore(aObj.description, aQuery,
+ SEARCH_SCORE_MULTIPLIER_DESCRIPTION);
+ return score;
+ },
+
+ calculateMatchScore: function gSearchView_calculateMatchScore(aStr, aQuery, aMultiplier) {
+ var score = 0;
+ if (!aStr || aQuery.length == 0)
+ return score;
+
+ aStr = aStr.trim().toLocaleLowerCase();
+ var haystack = aStr.split(/\s+/);
+ var needles = aQuery.split(/\s+/);
+
+ for (let needle of needles) {
+ for (let hay of haystack) {
+ if (hay == needle) {
+ // matching whole words is best
+ score += SEARCH_SCORE_MATCH_WHOLEWORD;
+ } else {
+ let i = hay.indexOf(needle);
+ if (i == 0) // matching on word boundries is also good
+ score += SEARCH_SCORE_MATCH_WORDBOUNDRY;
+ else if (i > 0) // substring matches not so good
+ score += SEARCH_SCORE_MATCH_SUBSTRING;
+ }
+ }
+ }
+
+ // give progressively higher score for longer queries, since longer queries
+ // are more likely to be unique and therefore more relevant.
+ if (needles.length > 1 && aStr.indexOf(aQuery) != -1)
+ score += needles.length;
+
+ return score * aMultiplier;
+ },
+
+ showEmptyNotice: function gSearchView_showEmptyNotice(aShow) {
+ this._emptyNotice.hidden = !aShow;
+ this._listBox.hidden = aShow;
+ },
+
+ showAllResultsLink: function gSearchView_showAllResultsLink(aTotalResults) {
+ if (aTotalResults == 0) {
+ this._allResultsLink.hidden = true;
+ return;
+ }
+
+ var linkStr = gStrings.ext.GetStringFromName("showAllSearchResults");
+ linkStr = PluralForm.get(aTotalResults, linkStr);
+ linkStr = linkStr.replace("#1", aTotalResults);
+ this._allResultsLink.setAttribute("value", linkStr);
+
+ this._allResultsLink.setAttribute("href",
+ AddonRepository.getSearchURL(this._lastQuery));
+ this._allResultsLink.hidden = false;
+ },
+
+ updateListAttributes: function gSearchView_updateListAttributes() {
+ var item = this._listBox.querySelector("richlistitem[remote='true'][first]");
+ if (item)
+ item.removeAttribute("first");
+ item = this._listBox.querySelector("richlistitem[remote='true'][last]");
+ if (item)
+ item.removeAttribute("last");
+ var items = this._listBox.querySelectorAll("richlistitem[remote='true']");
+ if (items.length > 0) {
+ items[0].setAttribute("first", true);
+ items[items.length - 1].setAttribute("last", true);
+ }
+
+ item = this._listBox.querySelector("richlistitem:not([remote='true'])[first]");
+ if (item)
+ item.removeAttribute("first");
+ item = this._listBox.querySelector("richlistitem:not([remote='true'])[last]");
+ if (item)
+ item.removeAttribute("last");
+ items = this._listBox.querySelectorAll("richlistitem:not([remote='true'])");
+ if (items.length > 0) {
+ items[0].setAttribute("first", true);
+ items[items.length - 1].setAttribute("last", true);
+ }
+
+ },
+
+ onSortChanged: function gSearchView_onSortChanged(aSortBy, aAscending) {
+ var footer = this._listBox.lastChild;
+ this._listBox.removeChild(footer);
+
+ sortList(this._listBox, aSortBy, aAscending);
+ this.updateListAttributes();
+
+ this._listBox.appendChild(footer);
+ },
+
+ onDownloadCancelled: function gSearchView_onDownloadCancelled(aInstall) {
+ this.removeInstall(aInstall);
+ },
+
+ onInstallCancelled: function gSearchView_onInstallCancelled(aInstall) {
+ this.removeInstall(aInstall);
+ },
+
+ removeInstall: function gSearchView_removeInstall(aInstall) {
+ for (let item of this._listBox.childNodes) {
+ if (item.mInstall == aInstall) {
+ this._listBox.removeChild(item);
+ return;
+ }
+ }
+ },
+
+ getSelectedAddon: function gSearchView_getSelectedAddon() {
+ var item = this._listBox.selectedItem;
+ if (item)
+ return item.mAddon;
+ return null;
+ },
+
+ getListItemForID: function gSearchView_getListItemForID(aId) {
+ var listitem = this._listBox.firstChild;
+ while (listitem) {
+ if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
+ return listitem;
+ listitem = listitem.nextSibling;
+ }
+ return null;
+ }
+};
+
+
+var gListView = {
+ node: null,
+ _listBox: null,
+ _emptyNotice: null,
+ _type: null,
+
+ initialize: function gListView_initialize() {
+ this.node = document.getElementById("list-view");
+ this._listBox = document.getElementById("addon-list");
+ this._emptyNotice = document.getElementById("addon-list-empty");
+
+ var self = this;
+ this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) {
+ if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
+ var item = self._listBox.selectedItem;
+ if (item)
+ item.showInDetailView();
+ }
+ }, false);
+ },
+
+ show: function gListView_show(aType, aRequest) {
+ if (!(aType in AddonManager.addonTypes))
+ throw Components.Exception("Attempting to show unknown type " + aType, Cr.NS_ERROR_INVALID_ARG);
+
+ this._type = aType;
+ this.node.setAttribute("type", aType);
+ this.showEmptyNotice(false);
+
+ while (this._listBox.itemCount > 0)
+ this._listBox.removeItemAt(0);
+
+ if (aType == "plugin") {
+ navigator.plugins.refresh(false);
+ }
+
+ var self = this;
+ getAddonsAndInstalls(aType, function show_getAddonsAndInstalls(aAddonsList, aInstallsList) {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ var elements = [];
+
+ for (let addonItem of aAddonsList)
+ elements.push(createItem(addonItem));
+
+ for (let installItem of aInstallsList)
+ elements.push(createItem(installItem, true));
+
+ self.showEmptyNotice(elements.length == 0);
+ if (elements.length > 0) {
+ sortElements(elements, ["uiState", "name"], true);
+ for (let element of elements)
+ self._listBox.appendChild(element);
+ }
+
+ gEventManager.registerInstallListener(self);
+ gViewController.updateCommands();
+ gViewController.notifyViewChanged();
+ });
+ },
+
+ hide: function gListView_hide() {
+ gEventManager.unregisterInstallListener(this);
+ doPendingUninstalls(this._listBox);
+ },
+
+ showEmptyNotice: function gListView_showEmptyNotice(aShow) {
+ this._emptyNotice.hidden = !aShow;
+ },
+
+ onSortChanged: function gListView_onSortChanged(aSortBy, aAscending) {
+ sortList(this._listBox, aSortBy, aAscending);
+ },
+
+ onExternalInstall: function gListView_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) {
+ // The existing list item will take care of upgrade installs
+ if (aExistingAddon)
+ return;
+
+ this.addItem(aAddon);
+ },
+
+ onDownloadStarted: function gListView_onDownloadStarted(aInstall) {
+ this.addItem(aInstall, true);
+ },
+
+ onInstallStarted: function gListView_onInstallStarted(aInstall) {
+ this.addItem(aInstall, true);
+ },
+
+ onDownloadCancelled: function gListView_onDownloadCancelled(aInstall) {
+ this.removeItem(aInstall, true);
+ },
+
+ onInstallCancelled: function gListView_onInstallCancelled(aInstall) {
+ this.removeItem(aInstall, true);
+ },
+
+ onInstallEnded: function gListView_onInstallEnded(aInstall) {
+ // Remove any install entries for upgrades, their status will appear against
+ // the existing item
+ if (aInstall.existingAddon)
+ this.removeItem(aInstall, true);
+ },
+
+ addItem: function gListView_addItem(aObj, aIsInstall) {
+ if (aObj.type != this._type)
+ return;
+
+ if (aIsInstall && aObj.existingAddon)
+ return;
+
+ let prop = aIsInstall ? "mInstall" : "mAddon";
+ for (let item of this._listBox.childNodes) {
+ if (item[prop] == aObj)
+ return;
+ }
+
+ let item = createItem(aObj, aIsInstall);
+ this._listBox.insertBefore(item, this._listBox.firstChild);
+ this.showEmptyNotice(false);
+ },
+
+ removeItem: function gListView_removeItem(aObj, aIsInstall) {
+ let prop = aIsInstall ? "mInstall" : "mAddon";
+
+ for (let item of this._listBox.childNodes) {
+ if (item[prop] == aObj) {
+ this._listBox.removeChild(item);
+ this.showEmptyNotice(this._listBox.itemCount == 0);
+ return;
+ }
+ }
+ },
+
+ getSelectedAddon: function gListView_getSelectedAddon() {
+ var item = this._listBox.selectedItem;
+ if (item)
+ return item.mAddon;
+ return null;
+ },
+
+ getListItemForID: function gListView_getListItemForID(aId) {
+ var listitem = this._listBox.firstChild;
+ while (listitem) {
+ if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
+ return listitem;
+ listitem = listitem.nextSibling;
+ }
+ return null;
+ }
+};
+
+
+var gDetailView = {
+ node: null,
+ _addon: null,
+ _loadingTimer: null,
+ _autoUpdate: null,
+
+ initialize: function gDetailView_initialize() {
+ this.node = document.getElementById("detail-view");
+
+ this._autoUpdate = document.getElementById("detail-autoUpdate");
+
+ var self = this;
+ this._autoUpdate.addEventListener("command", function autoUpdate_onCommand() {
+ self._addon.applyBackgroundUpdates = self._autoUpdate.value;
+ }, true);
+ },
+
+ shutdown: function gDetailView_shutdown() {
+ AddonManager.removeManagerListener(this);
+ },
+
+ onUpdateModeChanged: function gDetailView_onUpdateModeChanged() {
+ this.onPropertyChanged(["applyBackgroundUpdates"]);
+ },
+
+ _updateView: function gDetailView_updateView(aAddon, aIsRemote, aScrollToPreferences) {
+ AddonManager.addManagerListener(this);
+ this.clearLoading();
+
+ this._addon = aAddon;
+ gEventManager.registerAddonListener(this, aAddon.id);
+ gEventManager.registerInstallListener(this);
+
+ this.node.setAttribute("type", aAddon.type);
+
+ // If the search category isn't selected then make sure to select the
+ // correct category
+ if (gCategories.selected != "addons://search/")
+ gCategories.select("addons://list/" + aAddon.type);
+
+ document.getElementById("detail-name").textContent = aAddon.name;
+ var icon = aAddon.icon64URL ? aAddon.icon64URL : aAddon.iconURL;
+ document.getElementById("detail-icon").src = icon ? icon : "";
+ document.getElementById("detail-creator").setCreator(aAddon.creator, aAddon.homepageURL);
+ document.getElementById("detail-translators").setTranslators(aAddon.translators, aAddon.type);
+
+ var version = document.getElementById("detail-version");
+ if (shouldShowVersionNumber(aAddon)) {
+ version.hidden = false;
+ version.value = aAddon.version;
+ } else {
+ version.hidden = true;
+ }
+
+ var screenshot = document.getElementById("detail-screenshot");
+ if (aAddon.screenshots && aAddon.screenshots.length > 0) {
+ if (aAddon.screenshots[0].thumbnailURL) {
+ screenshot.src = aAddon.screenshots[0].thumbnailURL;
+ screenshot.width = aAddon.screenshots[0].thumbnailWidth;
+ screenshot.height = aAddon.screenshots[0].thumbnailHeight;
+ } else {
+ screenshot.src = aAddon.screenshots[0].url;
+ screenshot.width = aAddon.screenshots[0].width;
+ screenshot.height = aAddon.screenshots[0].height;
+ }
+ screenshot.setAttribute("loading", "true");
+ screenshot.hidden = false;
+ } else {
+ screenshot.hidden = true;
+ }
+
+ var desc = document.getElementById("detail-desc");
+ desc.textContent = aAddon.description;
+
+ var fullDesc = document.getElementById("detail-fulldesc");
+ if (aAddon.fullDescription) {
+ fullDesc.textContent = aAddon.fullDescription;
+ fullDesc.hidden = false;
+ } else {
+ fullDesc.hidden = true;
+ }
+
+ var contributions = document.getElementById("detail-contributions");
+ if ("contributionURL" in aAddon && aAddon.contributionURL) {
+ contributions.hidden = false;
+ var amount = document.getElementById("detail-contrib-suggested");
+ if (aAddon.contributionAmount) {
+ amount.value = gStrings.ext.formatStringFromName("contributionAmount2",
+ [aAddon.contributionAmount],
+ 1);
+ amount.hidden = false;
+ } else {
+ amount.hidden = true;
+ }
+ } else {
+ contributions.hidden = true;
+ }
+
+ if ("purchaseURL" in aAddon && aAddon.purchaseURL) {
+ var purchase = document.getElementById("detail-purchase-btn");
+ purchase.label = gStrings.ext.formatStringFromName("cmd.purchaseAddon.label",
+ [aAddon.purchaseDisplayAmount],
+ 1);
+ purchase.accesskey = gStrings.ext.GetStringFromName("cmd.purchaseAddon.accesskey");
+ }
+
+ var updateDateRow = document.getElementById("detail-dateUpdated");
+ if (aAddon.updateDate) {
+ var date = formatDate(aAddon.updateDate);
+ updateDateRow.value = date;
+ } else {
+ updateDateRow.value = null;
+ }
+
+ // TODO if the add-on was downloaded from releases.mozilla.org link to the
+ // AMO profile (bug 590344)
+ if (false) {
+ document.getElementById("detail-repository-row").hidden = false;
+ document.getElementById("detail-homepage-row").hidden = true;
+ var repository = document.getElementById("detail-repository");
+ repository.value = aAddon.homepageURL;
+ repository.href = aAddon.homepageURL;
+ } else if (aAddon.homepageURL) {
+ document.getElementById("detail-repository-row").hidden = true;
+ document.getElementById("detail-homepage-row").hidden = false;
+ var homepage = document.getElementById("detail-homepage");
+ homepage.value = aAddon.homepageURL;
+ homepage.href = aAddon.homepageURL;
+ } else {
+ document.getElementById("detail-repository-row").hidden = true;
+ document.getElementById("detail-homepage-row").hidden = true;
+ }
+
+ var rating = document.getElementById("detail-rating");
+ if (aAddon.averageRating) {
+ rating.averageRating = aAddon.averageRating;
+ rating.hidden = false;
+ } else {
+ rating.hidden = true;
+ }
+
+ var reviews = document.getElementById("detail-reviews");
+ if (aAddon.reviewURL) {
+ var text = gStrings.ext.GetStringFromName("numReviews");
+ text = PluralForm.get(aAddon.reviewCount, text)
+ text = text.replace("#1", aAddon.reviewCount);
+ reviews.value = text;
+ reviews.hidden = false;
+ reviews.href = aAddon.reviewURL;
+ } else {
+ reviews.hidden = true;
+ }
+
+ document.getElementById("detail-rating-row").hidden = !aAddon.averageRating && !aAddon.reviewURL;
+
+ var sizeRow = document.getElementById("detail-size");
+ if (aAddon.size && aIsRemote) {
+ let [size, unit] = DownloadUtils.convertByteUnits(parseInt(aAddon.size));
+ let formatted = gStrings.dl.GetStringFromName("doneSize");
+ formatted = formatted.replace("#1", size).replace("#2", unit);
+ sizeRow.value = formatted;
+ } else {
+ sizeRow.value = null;
+ }
+
+ var downloadsRow = document.getElementById("detail-downloads");
+ if (aAddon.totalDownloads && aIsRemote) {
+ var downloads = aAddon.totalDownloads;
+ downloadsRow.value = downloads;
+ } else {
+ downloadsRow.value = null;
+ }
+
+ var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade");
+ document.getElementById("detail-updates-row").hidden = !canUpdate;
+
+ if ("applyBackgroundUpdates" in aAddon) {
+ this._autoUpdate.hidden = false;
+ this._autoUpdate.value = aAddon.applyBackgroundUpdates;
+ let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
+ document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
+ } else {
+ this._autoUpdate.hidden = true;
+ document.getElementById("detail-findUpdates-btn").hidden = false;
+ }
+
+ document.getElementById("detail-prefs-btn").hidden = !aIsRemote &&
+ !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon);
+
+ var gridRows = document.querySelectorAll("#detail-grid rows row");
+ let first = true;
+ for (let gridRow of gridRows) {
+ if (first && window.getComputedStyle(gridRow, null).getPropertyValue("display") != "none") {
+ gridRow.setAttribute("first-row", true);
+ first = false;
+ } else {
+ gridRow.removeAttribute("first-row");
+ }
+ }
+
+ this.fillSettingsRows(aScrollToPreferences, (function updateView_fillSettingsRows() {
+ this.updateState();
+ gViewController.notifyViewChanged();
+ }).bind(this));
+ },
+
+ show: function gDetailView_show(aAddonId, aRequest) {
+ let index = aAddonId.indexOf("/preferences");
+ let scrollToPreferences = false;
+ if (index >= 0) {
+ aAddonId = aAddonId.substring(0, index);
+ scrollToPreferences = true;
+ }
+
+ var self = this;
+ this._loadingTimer = setTimeout(function loadTimeOutTimer() {
+ self.node.setAttribute("loading-extended", true);
+ }, LOADING_MSG_DELAY);
+
+ var view = gViewController.currentViewId;
+
+ AddonManager.getAddonByID(aAddonId, function show_getAddonByID(aAddon) {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ if (aAddon) {
+ self._updateView(aAddon, false, scrollToPreferences);
+ return;
+ }
+
+ // Look for an add-on pending install
+ AddonManager.getAllInstalls(function show_getAllInstalls(aInstalls) {
+ for (let install of aInstalls) {
+ if (install.state == AddonManager.STATE_INSTALLED &&
+ install.addon.id == aAddonId) {
+ self._updateView(install.addon, false);
+ return;
+ }
+ }
+
+ if (aAddonId in gCachedAddons) {
+ self._updateView(gCachedAddons[aAddonId], true);
+ return;
+ }
+
+ // This might happen due to session restore restoring us back to an
+ // add-on that doesn't exist but otherwise shouldn't normally happen.
+ // Either way just revert to the default view.
+ gViewController.replaceView(gViewDefault);
+ });
+ });
+ },
+
+ hide: function gDetailView_hide() {
+ AddonManager.removeManagerListener(this);
+ this.clearLoading();
+ if (this._addon) {
+ if (hasInlineOptions(this._addon)) {
+ Services.obs.notifyObservers(document,
+ AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
+ this._addon.id);
+ }
+
+ gEventManager.unregisterAddonListener(this, this._addon.id);
+ gEventManager.unregisterInstallListener(this);
+ this._addon = null;
+
+ // Flush the preferences to disk so they survive any crash
+ if (this.node.getElementsByTagName("setting").length)
+ Services.prefs.savePrefFile(null);
+ }
+ },
+
+ updateState: function gDetailView_updateState() {
+ gViewController.updateCommands();
+
+ var pending = this._addon.pendingOperations;
+ if (pending != AddonManager.PENDING_NONE) {
+ this.node.removeAttribute("notification");
+
+ var pending = null;
+ const PENDING_OPERATIONS = ["enable", "disable", "install", "uninstall",
+ "upgrade"];
+ for (let op of PENDING_OPERATIONS) {
+ if (isPending(this._addon, op))
+ pending = op;
+ }
+
+ this.node.setAttribute("pending", pending);
+ document.getElementById("detail-pending").textContent = gStrings.ext.formatStringFromName(
+ "details.notification." + pending,
+ [this._addon.name, gStrings.brandShortName], 2
+ );
+ } else {
+ this.node.removeAttribute("pending");
+
+ if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ this.node.setAttribute("notification", "error");
+ document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.blocked",
+ [this._addon.name], 1
+ );
+ var errorLink = document.getElementById("detail-error-link");
+ errorLink.value = gStrings.ext.GetStringFromName("details.notification.blocked.link");
+ errorLink.href = this._addon.blocklistURL;
+ errorLink.hidden = false;
+ } else if (!this._addon.isCompatible && (AddonManager.checkCompatibility ||
+ (this._addon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) {
+ this.node.setAttribute("notification", "warning");
+ document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.incompatible",
+ [this._addon.name, gStrings.brandShortName, gStrings.appVersion], 3
+ );
+ document.getElementById("detail-warning-link").hidden = true;
+ } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
+ this.node.setAttribute("notification", "warning");
+ document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.softblocked",
+ [this._addon.name], 1
+ );
+ var warningLink = document.getElementById("detail-warning-link");
+ warningLink.value = gStrings.ext.GetStringFromName("details.notification.softblocked.link");
+ warningLink.href = this._addon.blocklistURL;
+ warningLink.hidden = false;
+ } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+ this.node.setAttribute("notification", "warning");
+ document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.outdated",
+ [this._addon.name], 1
+ );
+ document.getElementById("detail-warning-link").hidden = true;
+ } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
+ this.node.setAttribute("notification", "error");
+ document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.vulnerableUpdatable",
+ [this._addon.name], 1
+ );
+ var errorLink = document.getElementById("detail-error-link");
+ errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableUpdatable.link");
+ errorLink.href = this._addon.blocklistURL;
+ errorLink.hidden = false;
+ } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
+ this.node.setAttribute("notification", "error");
+ document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
+ "details.notification.vulnerableNoUpdate",
+ [this._addon.name], 1
+ );
+ var errorLink = document.getElementById("detail-error-link");
+ errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link");
+ errorLink.href = this._addon.blocklistURL;
+ errorLink.hidden = false;
+ } else {
+ this.node.removeAttribute("notification");
+ }
+ }
+
+ let menulist = document.getElementById("detail-state-menulist");
+ let addonType = AddonManager.addonTypes[this._addon.type];
+ if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) {
+ let askItem = document.getElementById("detail-ask-to-activate-menuitem");
+ let alwaysItem = document.getElementById("detail-always-activate-menuitem");
+ let neverItem = document.getElementById("detail-never-activate-menuitem");
+ let hasActivatePermission =
+ ["ask_to_activate", "enable", "disable"].some(perm => hasPermission(this._addon, perm));
+
+ if (!this._addon.isActive) {
+ menulist.selectedItem = neverItem;
+ } else if (this._addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
+ menulist.selectedItem = askItem;
+ } else {
+ menulist.selectedItem = alwaysItem;
+ }
+
+ menulist.disabled = !hasActivatePermission;
+ menulist.hidden = false;
+ menulist.classList.add('no-auto-hide');
+ } else {
+ menulist.hidden = true;
+ }
+
+ this.node.setAttribute("active", this._addon.isActive);
+ },
+
+ clearLoading: function gDetailView_clearLoading() {
+ if (this._loadingTimer) {
+ clearTimeout(this._loadingTimer);
+ this._loadingTimer = null;
+ }
+
+ this.node.removeAttribute("loading-extended");
+ },
+
+ emptySettingsRows: function gDetailView_emptySettingsRows() {
+ var lastRow = document.getElementById("detail-downloads");
+ var rows = lastRow.parentNode;
+ while (lastRow.nextSibling)
+ rows.removeChild(rows.lastChild);
+ },
+
+ fillSettingsRows: function gDetailView_fillSettingsRows(aScrollToPreferences, aCallback) {
+ this.emptySettingsRows();
+ if (!hasInlineOptions(this._addon)) {
+ if (aCallback)
+ aCallback();
+ return;
+ }
+
+ // This function removes and returns the text content of aNode without
+ // removing any child elements. Removing the text nodes ensures any XBL
+ // bindings apply properly.
+ function stripTextNodes(aNode) {
+ var text = '';
+ for (var i = 0; i < aNode.childNodes.length; i++) {
+ if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) {
+ text += aNode.childNodes[i].textContent;
+ aNode.removeChild(aNode.childNodes[i--]);
+ } else {
+ text += stripTextNodes(aNode.childNodes[i]);
+ }
+ }
+ return text;
+ }
+
+ var rows = document.getElementById("detail-downloads").parentNode;
+
+ try {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", this._addon.optionsURL, true);
+ xhr.responseType = "xml";
+ xhr.onload = (function fillSettingsRows_onload() {
+ var xml = xhr.responseXML;
+ var settings = xml.querySelectorAll(":root > setting");
+
+ var firstSetting = null;
+ for (var setting of settings) {
+
+ var desc = stripTextNodes(setting).trim();
+ if (!setting.hasAttribute("desc"))
+ setting.setAttribute("desc", desc);
+
+ var type = setting.getAttribute("type");
+ if (type == "file" || type == "directory")
+ setting.setAttribute("fullpath", "true");
+
+ setting = document.importNode(setting, true);
+ var style = setting.getAttribute("style");
+ if (style) {
+ setting.removeAttribute("style");
+ setting.setAttribute("style", style);
+ }
+
+ rows.appendChild(setting);
+ var visible = window.getComputedStyle(setting, null).getPropertyValue("display") != "none";
+ if (!firstSetting && visible) {
+ setting.setAttribute("first-row", true);
+ firstSetting = setting;
+ }
+ }
+
+ // Ensure the page has loaded and force the XBL bindings to be synchronously applied,
+ // then notify observers.
+ if (gViewController.viewPort.selectedPanel.hasAttribute("loading")) {
+ gDetailView.node.addEventListener("ViewChanged", function viewChangedEventListener() {
+ gDetailView.node.removeEventListener("ViewChanged", viewChangedEventListener, false);
+ if (firstSetting)
+ firstSetting.clientTop;
+ Services.obs.notifyObservers(document,
+ AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
+ gDetailView._addon.id);
+ if (aScrollToPreferences)
+ gDetailView.scrollToPreferencesRows();
+ }, false);
+ } else {
+ if (firstSetting)
+ firstSetting.clientTop;
+ Services.obs.notifyObservers(document,
+ AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
+ this._addon.id);
+ if (aScrollToPreferences)
+ gDetailView.scrollToPreferencesRows();
+ }
+ if (aCallback)
+ aCallback();
+ }).bind(this);
+ xhr.onerror = function fillSettingsRows_onerror(aEvent) {
+ Cu.reportError("Error " + aEvent.target.status +
+ " occurred while receiving " + this._addon.optionsURL);
+ if (aCallback)
+ aCallback();
+ };
+ xhr.send();
+ } catch(e) {
+ Cu.reportError(e);
+ if (aCallback)
+ aCallback();
+ }
+ },
+
+ scrollToPreferencesRows: function gDetailView_scrollToPreferencesRows() {
+ // We find this row, rather than remembering it from above,
+ // in case it has been changed by the observers.
+ let firstRow = gDetailView.node.querySelector('setting[first-row="true"]');
+ if (firstRow) {
+ let top = firstRow.boxObject.y;
+ top -= parseInt(window.getComputedStyle(firstRow, null).getPropertyValue("margin-top"));
+
+ let detailViewBoxObject = gDetailView.node.boxObject;
+ top -= detailViewBoxObject.y;
+
+ detailViewBoxObject.scrollTo(0, top);
+ }
+ },
+
+ getSelectedAddon: function gDetailView_getSelectedAddon() {
+ return this._addon;
+ },
+
+ onEnabling: function gDetailView_onEnabling() {
+ this.updateState();
+ },
+
+ onEnabled: function gDetailView_onEnabled() {
+ this.updateState();
+ this.fillSettingsRows();
+ },
+
+ onDisabling: function gDetailView_onDisabling(aNeedsRestart) {
+ this.updateState();
+ if (!aNeedsRestart && hasInlineOptions(this._addon)) {
+ Services.obs.notifyObservers(document,
+ AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
+ this._addon.id);
+ }
+ },
+
+ onDisabled: function gDetailView_onDisabled() {
+ this.updateState();
+ this.emptySettingsRows();
+ },
+
+ onUninstalling: function gDetailView_onUninstalling() {
+ this.updateState();
+ },
+
+ onUninstalled: function gDetailView_onUninstalled() {
+ gViewController.popState();
+ },
+
+ onOperationCancelled: function gDetailView_onOperationCancelled() {
+ this.updateState();
+ },
+
+ onPropertyChanged: function gDetailView_onPropertyChanged(aProperties) {
+ if (aProperties.indexOf("applyBackgroundUpdates") != -1) {
+ this._autoUpdate.value = this._addon.applyBackgroundUpdates;
+ let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
+ document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
+ }
+
+ if (aProperties.indexOf("appDisabled") != -1 ||
+ aProperties.indexOf("userDisabled") != -1)
+ this.updateState();
+ },
+
+ onExternalInstall: function gDetailView_onExternalInstall(aAddon, aExistingAddon, aNeedsRestart) {
+ // Only care about upgrades for the currently displayed add-on
+ if (!aExistingAddon || aExistingAddon.id != this._addon.id)
+ return;
+
+ if (!aNeedsRestart)
+ this._updateView(aAddon, false);
+ else
+ this.updateState();
+ },
+
+ onInstallCancelled: function gDetailView_onInstallCancelled(aInstall) {
+ if (aInstall.addon.id == this._addon.id)
+ gViewController.popState();
+ }
+};
+
+
+var gUpdatesView = {
+ node: null,
+ _listBox: null,
+ _emptyNotice: null,
+ _sorters: null,
+ _updateSelected: null,
+ _categoryItem: null,
+
+ initialize: function gUpdatesView_initialize() {
+ this.node = document.getElementById("updates-view");
+ this._listBox = document.getElementById("updates-list");
+ this._emptyNotice = document.getElementById("updates-list-empty");
+ this._sorters = document.getElementById("updates-sorters");
+ this._sorters.handler = this;
+
+ this._categoryItem = gCategories.get("addons://updates/available");
+
+ this._updateSelected = document.getElementById("update-selected-btn");
+ this._updateSelected.addEventListener("command", function updateSelected_onCommand() {
+ gUpdatesView.installSelected();
+ }, false);
+
+ this.updateAvailableCount(true);
+
+ AddonManager.addAddonListener(this);
+ AddonManager.addInstallListener(this);
+ },
+
+ shutdown: function gUpdatesView_shutdown() {
+ AddonManager.removeAddonListener(this);
+ AddonManager.removeInstallListener(this);
+ },
+
+ show: function gUpdatesView_show(aType, aRequest) {
+ document.getElementById("empty-availableUpdates-msg").hidden = aType != "available";
+ document.getElementById("empty-recentUpdates-msg").hidden = aType != "recent";
+ this.showEmptyNotice(false);
+
+ while (this._listBox.itemCount > 0)
+ this._listBox.removeItemAt(0);
+
+ this.node.setAttribute("updatetype", aType);
+ if (aType == "recent")
+ this._showRecentUpdates(aRequest);
+ else
+ this._showAvailableUpdates(false, aRequest);
+ },
+
+ hide: function gUpdatesView_hide() {
+ this._updateSelected.hidden = true;
+ this._categoryItem.disabled = this._categoryItem.badgeCount == 0;
+ doPendingUninstalls(this._listBox);
+ },
+
+ _showRecentUpdates: function gUpdatesView_showRecentUpdates(aRequest) {
+ var self = this;
+ AddonManager.getAllAddons(function showRecentUpdates_getAllAddons(aAddonsList) {
+ if (gViewController && aRequest != gViewController.currentViewRequest)
+ return;
+
+ var elements = [];
+ let threshold = Date.now() - UPDATES_RECENT_TIMESPAN;
+ for (let addon of aAddonsList) {
+ if (!addon.updateDate || addon.updateDate.getTime() < threshold)
+ continue;
+
+ elements.push(createItem(addon));
+ }
+
+ self.showEmptyNotice(elements.length == 0);
+ if (elements.length > 0) {
+ sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
+ for (let element of elements)
+ self._listBox.appendChild(element);
+ }
+
+ gViewController.notifyViewChanged();
+ });
+ },
+
+ _showAvailableUpdates: function gUpdatesView_showAvailableUpdates(aIsRefresh, aRequest) {
+ /* Disable the Update Selected button so it can't get clicked
+ before everything is initialized asynchronously.
+ It will get re-enabled by maybeDisableUpdateSelected(). */
+ this._updateSelected.disabled = true;
+
+ var self = this;
+ AddonManager.getAllInstalls(function showAvailableUpdates_getAllInstalls(aInstallsList) {
+ if (!aIsRefresh && gViewController && aRequest &&
+ aRequest != gViewController.currentViewRequest)
+ return;
+
+ if (aIsRefresh) {
+ self.showEmptyNotice(false);
+ self._updateSelected.hidden = true;
+
+ while (self._listBox.itemCount > 0)
+ self._listBox.removeItemAt(0);
+ }
+
+ var elements = [];
+
+ for (let install of aInstallsList) {
+ if (!self.isManualUpdate(install))
+ continue;
+
+ let item = createItem(install.existingAddon);
+ item.setAttribute("upgrade", true);
+ item.addEventListener("IncludeUpdateChanged", function item_onIncludeUpdateChanged() {
+ self.maybeDisableUpdateSelected();
+ }, false);
+ elements.push(item);
+ }
+
+ self.showEmptyNotice(elements.length == 0);
+ if (elements.length > 0) {
+ self._updateSelected.hidden = false;
+ sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
+ for (let element of elements)
+ self._listBox.appendChild(element);
+ }
+
+ // ensure badge count is in sync
+ self._categoryItem.badgeCount = self._listBox.itemCount;
+
+ gViewController.notifyViewChanged();
+ });
+ },
+
+ showEmptyNotice: function gUpdatesView_showEmptyNotice(aShow) {
+ this._emptyNotice.hidden = !aShow;
+ },
+
+ isManualUpdate: function gUpdatesView_isManualUpdate(aInstall, aOnlyAvailable) {
+ var isManual = aInstall.existingAddon &&
+ !AddonManager.shouldAutoUpdate(aInstall.existingAddon);
+ if (isManual && aOnlyAvailable)
+ return isInState(aInstall, "available");
+ return isManual;
+ },
+
+ maybeRefresh: function gUpdatesView_maybeRefresh() {
+ if (gViewController.currentViewId == "addons://updates/available")
+ this._showAvailableUpdates(true);
+ this.updateAvailableCount();
+ },
+
+ updateAvailableCount: function gUpdatesView_updateAvailableCount(aInitializing) {
+ if (aInitializing)
+ gPendingInitializations++;
+ var self = this;
+ AddonManager.getAllInstalls(function updateAvailableCount_getAllInstalls(aInstallsList) {
+ var count = aInstallsList.filter(function installListFilter(aInstall) {
+ return self.isManualUpdate(aInstall, true);
+ }).length;
+ self._categoryItem.disabled = gViewController.currentViewId != "addons://updates/available" &&
+ count == 0;
+ self._categoryItem.badgeCount = count;
+ if (aInitializing)
+ notifyInitialized();
+ });
+ },
+
+ maybeDisableUpdateSelected: function gUpdatesView_maybeDisableUpdateSelected() {
+ for (let item of this._listBox.childNodes) {
+ if (item.includeUpdate) {
+ this._updateSelected.disabled = false;
+ return;
+ }
+ }
+ this._updateSelected.disabled = true;
+ },
+
+ installSelected: function gUpdatesView_installSelected() {
+ for (let item of this._listBox.childNodes) {
+ if (item.includeUpdate)
+ item.upgrade();
+ }
+
+ this._updateSelected.disabled = true;
+ },
+
+ getSelectedAddon: function gUpdatesView_getSelectedAddon() {
+ var item = this._listBox.selectedItem;
+ if (item)
+ return item.mAddon;
+ return null;
+ },
+
+ getListItemForID: function gUpdatesView_getListItemForID(aId) {
+ var listitem = this._listBox.firstChild;
+ while (listitem) {
+ if (listitem.mAddon.id == aId)
+ return listitem;
+ listitem = listitem.nextSibling;
+ }
+ return null;
+ },
+
+ onSortChanged: function gUpdatesView_onSortChanged(aSortBy, aAscending) {
+ sortList(this._listBox, aSortBy, aAscending);
+ },
+
+ onNewInstall: function gUpdatesView_onNewInstall(aInstall) {
+ if (!this.isManualUpdate(aInstall))
+ return;
+ this.maybeRefresh();
+ },
+
+ onInstallStarted: function gUpdatesView_onInstallStarted(aInstall) {
+ this.updateAvailableCount();
+ },
+
+ onInstallCancelled: function gUpdatesView_onInstallCancelled(aInstall) {
+ if (!this.isManualUpdate(aInstall))
+ return;
+ this.maybeRefresh();
+ },
+
+ onPropertyChanged: function gUpdatesView_onPropertyChanged(aAddon, aProperties) {
+ if (aProperties.indexOf("applyBackgroundUpdates") != -1)
+ this.updateAvailableCount();
+ }
+};
+
+function debuggingPrefChanged() {
+ gViewController.updateState();
+ gViewController.updateCommands();
+ gViewController.notifyViewChanged();
+}
+
+var gDragDrop = {
+ onDragOver: function gDragDrop_onDragOver(aEvent) {
+ var types = aEvent.dataTransfer.types;
+ if (types.contains("text/uri-list") ||
+ types.contains("text/x-moz-url") ||
+ types.contains("application/x-moz-file"))
+ aEvent.preventDefault();
+ },
+
+ onDrop: function gDragDrop_onDrop(aEvent) {
+ var dataTransfer = aEvent.dataTransfer;
+ var urls = [];
+
+ // Convert every dropped item into a url
+ for (var i = 0; i < dataTransfer.mozItemCount; i++) {
+ var url = dataTransfer.mozGetDataAt("text/uri-list", i);
+ if (url) {
+ urls.push(url);
+ continue;
+ }
+
+ url = dataTransfer.mozGetDataAt("text/x-moz-url", i);
+ if (url) {
+ urls.push(url.split("\n")[0]);
+ continue;
+ }
+
+ var file = dataTransfer.mozGetDataAt("application/x-moz-file", i);
+ if (file) {
+ urls.push(Services.io.newFileURI(file).spec);
+ continue;
+ }
+ }
+
+ var pos = 0;
+ var installs = [];
+
+ function buildNextInstall() {
+ if (pos == urls.length) {
+ if (installs.length > 0) {
+ // Display the normal install confirmation for the installs
+ let webInstaller = Cc["@mozilla.org/addons/web-install-listener;1"].
+ getService(Ci.amIWebInstallListener);
+ webInstaller.onWebInstallRequested(getBrowserElement(),
+ document.documentURIObject,
+ installs, installs.length);
+ }
+ return;
+ }
+
+ AddonManager.getInstallForURL(urls[pos++], function onDrop_getInstallForURL(aInstall) {
+ installs.push(aInstall);
+ buildNextInstall();
+ }, "application/x-xpinstall");
+ }
+
+ buildNextInstall();
+
+ aEvent.preventDefault();
+ }
+};
diff --git a/components/addons/content/extensions.xml b/components/addons/content/extensions.xml
new file mode 100644
index 000000000..9ea6a50df
--- /dev/null
+++ b/components/addons/content/extensions.xml
@@ -0,0 +1,2031 @@
+<?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 page [
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
+%extensionsDTD;
+<!ENTITY % aboutDTD SYSTEM "chrome://mozapps/locale/extensions/about.dtd">
+%aboutDTD;
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+]>
+
+<bindings id="addonBindings"
+ 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">
+
+
+ <!-- Rating - displays current/average rating, allows setting user rating -->
+ <binding id="rating">
+ <content>
+ <xul:image class="star"
+ onmouseover="document.getBindingParent(this)._hover(1);"
+ onclick="document.getBindingParent(this).userRating = 1;"/>
+ <xul:image class="star"
+ onmouseover="document.getBindingParent(this)._hover(2);"
+ onclick="document.getBindingParent(this).userRating = 2;"/>
+ <xul:image class="star"
+ onmouseover="document.getBindingParent(this)._hover(3);"
+ onclick="document.getBindingParent(this).userRating = 3;"/>
+ <xul:image class="star"
+ onmouseover="document.getBindingParent(this)._hover(4);"
+ onclick="document.getBindingParent(this).userRating = 4;"/>
+ <xul:image class="star"
+ onmouseover="document.getBindingParent(this)._hover(5);"
+ onclick="document.getBindingParent(this).userRating = 5;"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ this._updateStars();
+ ]]></constructor>
+
+ <property name="stars" readonly="true">
+ <getter><![CDATA[
+ return document.getAnonymousNodes(this);
+ ]]></getter>
+ </property>
+
+ <property name="averageRating">
+ <getter><![CDATA[
+ if (this.hasAttribute("averagerating"))
+ return this.getAttribute("averagerating");
+ return -1;
+ ]]></getter>
+ <setter><![CDATA[
+ this.setAttribute("averagerating", val);
+ if (this.showRating == "average")
+ this._updateStars();
+ ]]></setter>
+ </property>
+
+ <property name="userRating">
+ <getter><![CDATA[
+ if (this.hasAttribute("userrating"))
+ return this.getAttribute("userrating");
+ return -1;
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.showRating != "user")
+ return;
+ this.setAttribute("userrating", val);
+ if (this.showRating == "user")
+ this._updateStars();
+ ]]></setter>
+ </property>
+
+ <property name="showRating">
+ <getter><![CDATA[
+ if (this.hasAttribute("showrating"))
+ return this.getAttribute("showrating");
+ return "average";
+ ]]></getter>
+ <setter><![CDATA[
+ if (val != "average" || val != "user")
+ throw Components.Exception("Invalid value", Components.results.NS_ERROR_ILLEGAL_VALUE);
+ this.setAttribute("showrating", val);
+ this._updateStars();
+ ]]></setter>
+ </property>
+
+ <method name="_updateStars">
+ <body><![CDATA[
+ var stars = this.stars;
+ var rating = this[this.showRating + "Rating"];
+ // average ratings can be non-whole numbers, round them so they
+ // match to their closest star
+ rating = Math.round(rating);
+ for (let i = 0; i < stars.length; i++)
+ stars[i].setAttribute("on", rating > i);
+ ]]></body>
+ </method>
+
+ <method name="_hover">
+ <parameter name="aScore"/>
+ <body><![CDATA[
+ if (this.showRating != "user")
+ return;
+ var stars = this.stars;
+ for (let i = 0; i < stars.length; i++)
+ stars[i].setAttribute("on", i <= (aScore -1));
+ ]]></body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="mouseout">
+ this._updateStars();
+ </handler>
+ </handlers>
+ </binding>
+
+ <!-- Download progress - shows graphical progress of download and any
+ related status message. -->
+ <binding id="download-progress">
+ <content>
+ <xul:stack flex="1">
+ <xul:hbox flex="1">
+ <xul:hbox class="start-cap"/>
+ <xul:progressmeter anonid="progress" class="progress" flex="1"
+ min="0" max="100"/>
+ <xul:hbox class="end-cap"/>
+ </xul:hbox>
+ <xul:hbox class="status-container">
+ <xul:spacer flex="1"/>
+ <xul:label anonid="status" class="status"/>
+ <xul:spacer flex="1"/>
+ <xul:button anonid="cancel-btn" class="cancel"
+ tooltiptext="&progress.cancel.tooltip;"
+ oncommand="document.getBindingParent(this).cancel();"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ var progress = 0;
+ if (this.hasAttribute("progress"))
+ progress = parseInt(this.getAttribute("progress"));
+ this.progress = progress;
+ ]]></constructor>
+
+ <field name="_progress">
+ document.getAnonymousElementByAttribute(this, "anonid", "progress");
+ </field>
+ <field name="_cancel">
+ document.getAnonymousElementByAttribute(this, "anonid", "cancel-btn");
+ </field>
+ <field name="_status">
+ document.getAnonymousElementByAttribute(this, "anonid", "status");
+ </field>
+
+ <property name="progress">
+ <getter><![CDATA[
+ return this._progress.value;
+ ]]></getter>
+ <setter><![CDATA[
+ this._progress.value = val;
+ if (val == this._progress.max)
+ this.setAttribute("complete", true);
+ else
+ this.removeAttribute("complete");
+ ]]></setter>
+ </property>
+
+ <property name="maxProgress">
+ <getter><![CDATA[
+ return this._progress.max;
+ ]]></getter>
+ <setter><![CDATA[
+ if (val == -1) {
+ this._progress.mode = "undetermined";
+ } else {
+ this._progress.mode = "determined";
+ this._progress.max = val;
+ }
+ this.setAttribute("mode", this._progress.mode);
+ ]]></setter>
+ </property>
+
+ <property name="status">
+ <getter><![CDATA[
+ return this._status.value;
+ ]]></getter>
+ <setter><![CDATA[
+ this._status.value = val;
+ ]]></setter>
+ </property>
+
+ <method name="cancel">
+ <body><![CDATA[
+ this.mInstall.cancel();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Sorters - displays and controls the sort state of a list. -->
+ <binding id="sorters">
+ <content orient="horizontal">
+ <xul:button anonid="name-btn" class="sorter"
+ label="&sort.name.label;" tooltiptext="&sort.name.tooltip;"
+ oncommand="this.parentNode._handleChange('name');"/>
+ <xul:button anonid="date-btn" class="sorter"
+ label="&sort.dateUpdated.label;"
+ tooltiptext="&sort.dateUpdated.tooltip;"
+ oncommand="this.parentNode._handleChange('updateDate');"/>
+ <xul:button anonid="price-btn" class="sorter" hidden="true"
+ label="&sort.price.label;"
+ tooltiptext="&sort.price.tooltip;"
+ oncommand="this.parentNode._handleChange('purchaseAmount');"/>
+ <xul:button anonid="relevance-btn" class="sorter" hidden="true"
+ label="&sort.relevance.label;"
+ tooltiptext="&sort.relevance.tooltip;"
+ oncommand="this.parentNode._handleChange('relevancescore');"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (!this.hasAttribute("sortby"))
+ this.setAttribute("sortby", "name");
+
+ if (this.getAttribute("showrelevance") == "true")
+ this._btnRelevance.hidden = false;
+
+ if (this.getAttribute("showprice") == "true")
+ this._btnPrice.hidden = false;
+
+ this._refreshState();
+ ]]></constructor>
+
+ <field name="handler">null</field>
+ <field name="_btnName">
+ document.getAnonymousElementByAttribute(this, "anonid", "name-btn");
+ </field>
+ <field name="_btnDate">
+ document.getAnonymousElementByAttribute(this, "anonid", "date-btn");
+ </field>
+ <field name="_btnPrice">
+ document.getAnonymousElementByAttribute(this, "anonid", "price-btn");
+ </field>
+ <field name="_btnRelevance">
+ document.getAnonymousElementByAttribute(this, "anonid", "relevance-btn");
+ </field>
+
+ <property name="sortBy">
+ <getter><![CDATA[
+ return this.getAttribute("sortby");
+ ]]></getter>
+ <setter><![CDATA[
+ if (val != this.sortBy) {
+ this.setAttribute("sortBy", val);
+ this._refreshState();
+ }
+ ]]></setter>
+ </property>
+
+ <property name="ascending">
+ <getter><![CDATA[
+ return (this.getAttribute("ascending") == "true");
+ ]]></getter>
+ <setter><![CDATA[
+ val = !!val;
+ if (val != this.ascending) {
+ this.setAttribute("ascending", val);
+ this._refreshState();
+ }
+ ]]></setter>
+ </property>
+
+ <property name="showrelevance">
+ <getter><![CDATA[
+ return (this.getAttribute("showrelevance") == "true");
+ ]]></getter>
+ <setter><![CDATA[
+ val = !!val;
+ this.setAttribute("showrelevance", val);
+ this._btnRelevance.hidden = !val;
+ ]]></setter>
+ </property>
+
+ <property name="showprice">
+ <getter><![CDATA[
+ return (this.getAttribute("showprice") == "true");
+ ]]></getter>
+ <setter><![CDATA[
+ val = !!val;
+ this.setAttribute("showprice", val);
+ this._btnPrice.hidden = !val;
+ ]]></setter>
+ </property>
+
+ <method name="setSort">
+ <parameter name="aSort"/>
+ <parameter name="aAscending"/>
+ <body><![CDATA[
+ var sortChanged = false;
+ if (aSort != this.sortBy) {
+ this.setAttribute("sortby", aSort);
+ sortChanged = true;
+ }
+
+ aAscending = !!aAscending;
+ if (this.ascending != aAscending) {
+ this.setAttribute("ascending", aAscending);
+ sortChanged = true;
+ }
+
+ if (sortChanged)
+ this._refreshState();
+ ]]></body>
+ </method>
+
+ <method name="_handleChange">
+ <parameter name="aSort"/>
+ <body><![CDATA[
+ const ASCENDING_SORT_FIELDS = ["name", "purchaseAmount"];
+
+ // Toggle ascending if sort by is not changing, otherwise
+ // name sorting defaults to ascending, others to descending
+ if (aSort == this.sortBy)
+ this.ascending = !this.ascending;
+ else
+ this.setSort(aSort, ASCENDING_SORT_FIELDS.indexOf(aSort) >= 0);
+ ]]></body>
+ </method>
+
+ <method name="_refreshState">
+ <body><![CDATA[
+ var sortBy = this.sortBy;
+ var checkState = this.ascending ? 2 : 1;
+
+ if (sortBy == "name") {
+ this._btnName.checkState = checkState;
+ this._btnName.checked = true;
+ } else {
+ this._btnName.checkState = 0;
+ this._btnName.checked = false;
+ }
+
+ if (sortBy == "updateDate") {
+ this._btnDate.checkState = checkState;
+ this._btnDate.checked = true;
+ } else {
+ this._btnDate.checkState = 0;
+ this._btnDate.checked = false;
+ }
+
+ if (sortBy == "purchaseAmount") {
+ this._btnPrice.checkState = checkState;
+ this._btnPrice.checked = true;
+ } else {
+ this._btnPrice.checkState = 0;
+ this._btnPrice.checked = false;
+ }
+
+ if (sortBy == "relevancescore") {
+ this._btnRelevance.checkState = checkState;
+ this._btnRelevance.checked = true;
+ } else {
+ this._btnRelevance.checkState = 0;
+ this._btnRelevance.checked = false;
+ }
+
+ if (this.handler && "onSortChanged" in this.handler)
+ this.handler.onSortChanged(sortBy, this.ascending);
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Categories list - displays the list of categories on the left pane. -->
+ <binding id="categories-list"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
+ <implementation>
+ <!-- This needs to be overridden to allow the fancy animation while not
+ allowing that item to be selected when hiding. -->
+ <method name="_canUserSelect">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (aItem.hasAttribute("disabled") &&
+ aItem.getAttribute("disabled") == "true")
+ return false;
+ var style = document.defaultView.getComputedStyle(aItem, "");
+ return style.display != "none" && style.visibility == "visible";
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Category item - an item in the category list. -->
+ <binding id="category"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content align="center">
+ <xul:image anonid="icon" class="category-icon"/>
+ <xul:label anonid="name" class="category-name" flex="1" xbl:inherits="value=name"/>
+ <xul:label anonid="badge" class="category-badge" xbl:inherits="value=count"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (!this.hasAttribute("count"))
+ this.setAttribute("count", 0);
+ ]]></constructor>
+
+ <property name="badgeCount">
+ <getter><![CDATA[
+ return this.getAttribute("count");
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.getAttribute("count") == val)
+ return;
+
+ this.setAttribute("count", val);
+ var event = document.createEvent("Events");
+ event.initEvent("CategoryBadgeUpdated", true, true);
+ this.dispatchEvent(event);
+ ]]></setter>
+ </property>
+ </implementation>
+ </binding>
+
+
+ <!-- Creator link - Name of a user/developer, providing a link if relevant. -->
+ <binding id="creator-link">
+ <content>
+ <xul:label anonid="label" value="&addon.createdBy.label;"/>
+ <xul:label anonid="creator-link" class="creator-link text-link"/>
+ <xul:label anonid="creator-name" class="creator-name"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (this.hasAttribute("nameonly") &&
+ this.getAttribute("nameonly") == "true") {
+ this._label.hidden = true;
+ }
+ ]]></constructor>
+
+ <field name="_label">
+ document.getAnonymousElementByAttribute(this, "anonid", "label");
+ </field>
+ <field name="_creatorLink">
+ document.getAnonymousElementByAttribute(this, "anonid", "creator-link");
+ </field>
+ <field name="_creatorName">
+ document.getAnonymousElementByAttribute(this, "anonid", "creator-name");
+ </field>
+
+ <method name="setCreator">
+ <parameter name="aCreator"/>
+ <parameter name="aHomepageURL"/>
+ <body><![CDATA[
+ if (!aCreator) {
+ this.collapsed = true;
+ return;
+ }
+ this.collapsed = false;
+ var url = aCreator.url || aHomepageURL;
+ var showLink = !!url;
+ if (showLink) {
+ this._creatorLink.value = aCreator.name;
+ this._creatorLink.href = url;
+ } else {
+ this._creatorName.value = aCreator.name;
+ }
+ this._creatorLink.hidden = !showLink;
+ this._creatorName.hidden = showLink;
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Translators list - Names of a translators of Language Pack. -->
+ <binding id="translators-list">
+ <content>
+ <xul:label anonid="label" value="&translators.label;" class="sectionTitle"/>
+ <xul:vbox flex="1" anonid="translatorsBox" class="boxIndent"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (this.hasAttribute("nameonly") &&
+ this.getAttribute("nameonly") == "true") {
+ this._label.hidden = true;
+ }
+ ]]></constructor>
+
+ <field name="_label">
+ document.getAnonymousElementByAttribute(this, "anonid", "label");
+ </field>
+ <field name="_translatorsBox">
+ document.getAnonymousElementByAttribute(this, "anonid", "translatorsBox");
+ </field>
+
+ <method name="setTranslators">
+ <parameter name="aTranslators"/>
+ <parameter name="aType"/>
+ <body><![CDATA[
+ if (aType != "locale" || !aTranslators || aTranslators.length == 0) {
+ this.collapsed = true;
+ return;
+ }
+ this.collapsed = false;
+ while (this._translatorsBox.firstChild) {
+ this._translatorsBox.removeChild(this._translatorsBox.firstChild);
+ }
+ for (let currentItem of aTranslators) {
+ var label = document.createElement("label");
+ label.textContent = currentItem;
+ label.setAttribute("class", "contributor");
+ this._translatorsBox.appendChild(label);
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Install status - Displays the status of an install/upgrade. -->
+ <binding id="install-status">
+ <content>
+ <xul:label anonid="message"/>
+ <xul:progressmeter anonid="progress" class="download-progress"/>
+ <xul:button anonid="purchase-remote-btn" hidden="true"
+ class="addon-control"
+ oncommand="document.getBindingParent(this).purchaseRemote();"/>
+ <xul:button anonid="install-remote-btn" hidden="true"
+ class="addon-control install" label="&addon.install.label;"
+ tooltiptext="&addon.install.tooltip;"
+ oncommand="document.getBindingParent(this).installRemote();"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ if (this.mInstall)
+ this.initWithInstall(this.mInstall);
+ else if (this.mControl.mAddon.install)
+ this.initWithInstall(this.mControl.mAddon.install);
+ else
+ this.refreshState();
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ if (this.mInstall)
+ this.mInstall.removeListener(this);
+ ]]></destructor>
+
+ <field name="_message">
+ document.getAnonymousElementByAttribute(this, "anonid", "message");
+ </field>
+ <field name="_progress">
+ document.getAnonymousElementByAttribute(this, "anonid", "progress");
+ </field>
+ <field name="_purchaseRemote">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "purchase-remote-btn");
+ </field>
+ <field name="_installRemote">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "install-remote-btn");
+ </field>
+ <field name="_restartNeeded">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "restart-needed");
+ </field>
+ <field name="_undo">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "undo-btn");
+ </field>
+
+ <method name="initWithInstall">
+ <parameter name="aInstall"/>
+ <body><![CDATA[
+ if (this.mInstall) {
+ this.mInstall.removeListener(this);
+ this.mInstall = null;
+ }
+ this.mInstall = aInstall;
+ this._progress.mInstall = aInstall;
+ this.refreshState();
+ this.mInstall.addListener(this);
+ ]]></body>
+ </method>
+
+ <method name="refreshState">
+ <body><![CDATA[
+ var showInstallRemote = false;
+ var showPurchase = false;
+
+ if (this.mInstall) {
+
+ switch (this.mInstall.state) {
+ case AddonManager.STATE_AVAILABLE:
+ if (this.mControl.getAttribute("remote") != "true")
+ break;
+
+ this._progress.hidden = true;
+ showInstallRemote = true;
+ break;
+ case AddonManager.STATE_DOWNLOADING:
+ this.showMessage("installDownloading");
+ break;
+ case AddonManager.STATE_CHECKING:
+ this.showMessage("installVerifying");
+ break;
+ case AddonManager.STATE_DOWNLOADED:
+ this.showMessage("installDownloaded");
+ break;
+ case AddonManager.STATE_DOWNLOAD_FAILED:
+ // XXXunf expose what error occured (bug 553487)
+ this.showMessage("installDownloadFailed", true);
+ break;
+ case AddonManager.STATE_INSTALLING:
+ this.showMessage("installInstalling");
+ break;
+ case AddonManager.STATE_INSTALL_FAILED:
+ // XXXunf expose what error occured (bug 553487)
+ this.showMessage("installFailed", true);
+ break;
+ case AddonManager.STATE_CANCELLED:
+ this.showMessage("installCancelled", true);
+ break;
+ }
+
+ } else if (this.mControl.mAddon.purchaseURL) {
+ this._progress.hidden = true;
+ showPurchase = true;
+ this._purchaseRemote.label =
+ gStrings.ext.formatStringFromName("addon.purchase.label",
+ [this.mControl.mAddon.purchaseDisplayAmount], 1);
+ this._purchaseRemote.tooltiptext =
+ gStrings.ext.GetStringFromName("addon.purchase.tooltip");
+ }
+
+ this._purchaseRemote.hidden = !showPurchase;
+ this._installRemote.hidden = !showInstallRemote;
+
+ if ("refreshInfo" in this.mControl)
+ this.mControl.refreshInfo();
+ ]]></body>
+ </method>
+
+ <method name="showMessage">
+ <parameter name="aMsgId"/>
+ <parameter name="aHideProgress"/>
+ <body><![CDATA[
+ this._message.setAttribute("hidden", !aHideProgress);
+ this._progress.setAttribute("hidden", !!aHideProgress);
+
+ var msg = gStrings.ext.GetStringFromName(aMsgId);
+ if (aHideProgress)
+ this._message.value = msg;
+ else
+ this._progress.status = msg;
+ ]]></body>
+ </method>
+
+ <method name="purchaseRemote">
+ <body><![CDATA[
+ openURL(this.mControl.mAddon.purchaseURL);
+ ]]></body>
+ </method>
+
+ <method name="installRemote">
+ <body><![CDATA[
+ if (this.mControl.getAttribute("remote") != "true")
+ return;
+
+ if (this.mControl.mAddon.eula) {
+ var data = {
+ addon: this.mControl.mAddon,
+ accepted: false
+ };
+ window.openDialog("chrome://mozapps/content/extensions/eula.xul", "_blank",
+ "chrome,dialog,modal,centerscreen,resizable=no", data);
+ if (!data.accepted)
+ return;
+ }
+
+ delete this.mControl.mAddon;
+ this.mControl.mInstall = this.mInstall;
+ this.mControl.setAttribute("status", "installing");
+ this.mInstall.install();
+ ]]></body>
+ </method>
+
+ <method name="undoAction">
+ <body><![CDATA[
+ if (!this.mAddon)
+ return;
+ var pending = this.mAddon.pendingOperations;
+ if (pending & AddonManager.PENDING_ENABLE)
+ this.mAddon.userDisabled = true;
+ else if (pending & AddonManager.PENDING_DISABLE)
+ this.mAddon.userDisabled = false;
+ this.refreshState();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadStarted">
+ <body><![CDATA[
+ this.refreshState();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadEnded">
+ <body><![CDATA[
+ this.refreshState();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadFailed">
+ <body><![CDATA[
+ this.refreshState();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadProgress">
+ <body><![CDATA[
+ this._progress.maxProgress = this.mInstall.maxProgress;
+ this._progress.progress = this.mInstall.progress;
+ ]]></body>
+ </method>
+
+ <method name="onInstallStarted">
+ <body><![CDATA[
+ this._progress.progress = 0;
+ this.refreshState();
+ ]]></body>
+ </method>
+
+ <method name="onInstallEnded">
+ <body><![CDATA[
+ this.refreshState();
+ if ("onInstallCompleted" in this.mControl)
+ this.mControl.onInstallCompleted();
+ ]]></body>
+ </method>
+
+ <method name="onInstallFailed">
+ <body><![CDATA[
+ this.refreshState();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Addon - base - parent binding of any item representing an addon. -->
+ <binding id="addon-base"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <implementation>
+ <method name="hasPermission">
+ <parameter name="aPerm"/>
+ <body><![CDATA[
+ var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
+ return !!(this.mAddon.permissions & perm);
+ ]]></body>
+ </method>
+
+ <method name="opRequiresRestart">
+ <parameter name="aOperation"/>
+ <body><![CDATA[
+ var operation = AddonManager["OP_NEEDS_RESTART_" + aOperation.toUpperCase()];
+ return !!(this.mAddon.operationsRequiringRestart & operation);
+ ]]></body>
+ </method>
+
+ <method name="typeHasFlag">
+ <parameter name="aFlag"/>
+ <body><![CDATA[
+ let flag = AddonManager["TYPE_" + aFlag];
+ let type = AddonManager.addonTypes[this.mAddon.type];
+
+ return !!(type.flags & flag);
+ ]]></body>
+ </method>
+
+ <method name="isPending">
+ <parameter name="aAction"/>
+ <body><![CDATA[
+ var action = AddonManager["PENDING_" + aAction.toUpperCase()];
+ return !!(this.mAddon.pendingOperations & action);
+ ]]></body>
+ </method>
+
+ <method name="onUninstalled">
+ <body><![CDATA[
+ this.parentNode.removeChild(this);
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Addon - generic - A normal addon item, or an update to one -->
+ <binding id="addon-generic"
+ extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
+ <content>
+ <xul:hbox anonid="warning-container"
+ class="warning">
+ <xul:image class="warning-icon"/>
+ <xul:label anonid="warning" flex="1"/>
+ <xul:label anonid="warning-link" class="text-link"/>
+ <xul:button anonid="warning-btn" class="button-link"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </xul:hbox>
+ <xul:hbox anonid="error-container"
+ class="error">
+ <xul:image class="error-icon"/>
+ <xul:label anonid="error" flex="1"/>
+ <xul:label anonid="error-link" class="text-link"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </xul:hbox>
+ <xul:hbox anonid="pending-container"
+ class="pending">
+ <xul:image class="pending-icon"/>
+ <xul:label anonid="pending" flex="1"/>
+ <xul:button anonid="restart-btn" class="button-link"
+ label="&addon.restartNow.label;"
+ oncommand="document.getBindingParent(this).restart();"/>
+ <xul:button anonid="undo-btn" class="button-link"
+ label="&addon.undoAction.label;"
+ tooltipText="&addon.undoAction.tooltip;"
+ oncommand="document.getBindingParent(this).undo();"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </xul:hbox>
+
+ <xul:hbox class="content-container">
+ <xul:vbox class="icon-container">
+ <xul:image anonid="icon" class="icon"/>
+ </xul:vbox>
+ <xul:vbox class="content-inner-container" flex="1">
+ <xul:hbox class="basicinfo-container">
+ <xul:hbox class="name-container">
+ <xul:label anonid="name" class="name" crop="end" flex="1"
+ xbl:inherits="value=name,tooltiptext=name"/>
+ <xul:label anonid="version" class="version"/>
+ <xul:label class="disabled-postfix" value="&addon.disabled.postfix;"/>
+ <xul:label class="update-postfix" value="&addon.update.postfix;"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to make the name crop -->
+ </xul:hbox>
+ <xul:label anonid="date-updated" class="date-updated"
+ unknown="&addon.unknownDate;"/>
+ </xul:hbox>
+ <xul:hbox class="advancedinfo-container" flex="1">
+ <xul:vbox class="description-outer-container" flex="1">
+ <xul:hbox class="description-container">
+ <xul:label anonid="description" class="description" crop="end" flex="1"/>
+ <xul:button anonid="details-btn" class="details button-link"
+ label="&addon.details.label;"
+ tooltiptext="&addon.details.tooltip;"
+ oncommand="document.getBindingParent(this).showInDetailView();"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to make the description crop -->
+ </xul:hbox>
+ <xul:vbox anonid="relnotes-container" class="relnotes-container">
+ <xul:label class="relnotes-header" value="&addon.releaseNotes.label;"/>
+ <xul:label anonid="relnotes-loading" value="&addon.loadingReleaseNotes.label;"/>
+ <xul:label anonid="relnotes-error" hidden="true"
+ value="&addon.errorLoadingReleaseNotes.label;"/>
+ <xul:vbox anonid="relnotes" class="relnotes"/>
+ </xul:vbox>
+ <xul:hbox class="relnotes-toggle-container">
+ <xul:button anonid="relnotes-toggle-btn" class="relnotes-toggle"
+ hidden="true" label="&cmd.showReleaseNotes.label;"
+ tooltiptext="&cmd.showReleaseNotes.tooltip;"
+ showlabel="&cmd.showReleaseNotes.label;"
+ showtooltip="&cmd.showReleaseNotes.tooltip;"
+ hidelabel="&cmd.hideReleaseNotes.label;"
+ hidetooltip="&cmd.hideReleaseNotes.tooltip;"
+ oncommand="document.getBindingParent(this).toggleReleaseNotes();"/>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:vbox class="status-control-wrapper">
+ <xul:hbox class="status-container">
+ <xul:hbox anonid="checking-update" hidden="true">
+ <xul:image class="spinner"/>
+ <xul:label value="&addon.checkingForUpdates.label;"/>
+ </xul:hbox>
+ <xul:vbox anonid="update-available" class="update-available"
+ hidden="true">
+ <xul:checkbox anonid="include-update" class="include-update"
+ label="&addon.includeUpdate.label;" checked="true"
+ oncommand="document.getBindingParent(this).onIncludeUpdateChanged();"/>
+ <xul:hbox class="update-info-container">
+ <xul:label class="update-available-notice"
+ value="&addon.updateAvailable.label;"/>
+ <xul:button anonid="update-btn" class="addon-control update"
+ label="&addon.updateNow.label;"
+ tooltiptext="&addon.updateNow.tooltip;"
+ oncommand="document.getBindingParent(this).upgrade();"/>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:hbox anonid="install-status" class="install-status"
+ hidden="true"/>
+ </xul:hbox>
+ <xul:hbox anonid="control-container" class="control-container">
+ <xul:button anonid="preferences-btn"
+ class="addon-control preferences"
+#ifdef XP_WIN
+ label="&cmd.showPreferencesWin.label;"
+ tooltiptext="&cmd.showPreferencesWin.tooltip;"
+#else
+ label="&cmd.showPreferencesUnix.label;"
+ tooltiptext="&cmd.showPreferencesUnix.tooltip;"
+#endif
+ oncommand="document.getBindingParent(this).showPreferences();"/>
+ <!-- label="&cmd.debugAddon.label;" -->
+ <xul:button anonid="enable-btn" class="addon-control enable"
+ label="&cmd.enableAddon.label;"
+ oncommand="document.getBindingParent(this).userDisabled = false;"/>
+ <xul:button anonid="disable-btn" class="addon-control disable"
+ label="&cmd.disableAddon.label;"
+ oncommand="document.getBindingParent(this).userDisabled = true;"/>
+ <xul:button anonid="remove-btn" class="addon-control remove"
+ label="&cmd.uninstallAddon.label;"
+ oncommand="document.getBindingParent(this).uninstall();"/>
+ <xul:menulist anonid="state-menulist"
+ class="addon-control state"
+ tooltiptext="&cmd.stateMenu.tooltip;">
+ <xul:menupopup>
+ <xul:menuitem anonid="ask-to-activate-menuitem"
+ class="addon-control"
+ label="&cmd.askToActivate.label;"
+ tooltiptext="&cmd.askToActivate.tooltip;"
+ oncommand="document.getBindingParent(this).userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;"/>
+ <xul:menuitem anonid="always-activate-menuitem"
+ class="addon-control"
+ label="&cmd.alwaysActivate.label;"
+ tooltiptext="&cmd.alwaysActivate.tooltip;"
+ oncommand="document.getBindingParent(this).userDisabled = false;"/>
+ <xul:menuitem anonid="never-activate-menuitem"
+ class="addon-control"
+ label="&cmd.neverActivate.label;"
+ tooltiptext="&cmd.neverActivate.tooltip;"
+ oncommand="document.getBindingParent(this).userDisabled = true;"/>
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ this._installStatus.mControl = this;
+
+ this.setAttribute("contextmenu", "addonitem-popup");
+
+ this._showStatus("none");
+
+ this._initWithAddon(this.mAddon);
+
+ gEventManager.registerAddonListener(this, this.mAddon.id);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ gEventManager.unregisterAddonListener(this, this.mAddon.id);
+ ]]></destructor>
+
+ <field name="_warningContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "warning-container");
+ </field>
+ <field name="_warning">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "warning");
+ </field>
+ <field name="_warningLink">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "warning-link");
+ </field>
+ <field name="_warningBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "warning-btn");
+ </field>
+ <field name="_errorContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "error-container");
+ </field>
+ <field name="_error">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "error");
+ </field>
+ <field name="_errorLink">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "error-link");
+ </field>
+ <field name="_pendingContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "pending-container");
+ </field>
+ <field name="_pending">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "pending");
+ </field>
+ <field name="_infoContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "info-container");
+ </field>
+ <field name="_info">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "info");
+ </field>
+ <field name="_version">
+ document.getAnonymousElementByAttribute(this, "anonid", "version");
+ </field>
+ <field name="_icon">
+ document.getAnonymousElementByAttribute(this, "anonid", "icon");
+ </field>
+ <field name="_dateUpdated">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "date-updated");
+ </field>
+ <field name="_description">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "description");
+ </field>
+ <field name="_stateMenulist">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "state-menulist");
+ </field>
+ <field name="_askToActivateMenuitem">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "ask-to-activate-menuitem");
+ </field>
+ <field name="_alwaysActivateMenuitem">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "always-activate-menuitem");
+ </field>
+ <field name="_neverActivateMenuitem">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "never-activate-menuitem");
+ </field>
+ <field name="_preferencesBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "preferences-btn");
+ </field>
+ <field name="_enableBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "enable-btn");
+ </field>
+ <field name="_disableBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "disable-btn");
+ </field>
+ <field name="_removeBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "remove-btn");
+ </field>
+ <field name="_updateBtn">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "update-btn");
+ </field>
+ <field name="_controlContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "control-container");
+ </field>
+ <field name="_installStatus">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "install-status");
+ </field>
+ <field name="_checkingUpdate">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "checking-update");
+ </field>
+ <field name="_updateAvailable">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "update-available");
+ </field>
+ <field name="_includeUpdate">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "include-update");
+ </field>
+ <field name="_relNotesLoaded">false</field>
+ <field name="_relNotesToggle">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "relnotes-toggle-btn");
+ </field>
+ <field name="_relNotesLoading">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "relnotes-loading");
+ </field>
+ <field name="_relNotesError">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "relnotes-error");
+ </field>
+ <field name="_relNotesContainer">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "relnotes-container");
+ </field>
+ <field name="_relNotes">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "relnotes");
+ </field>
+
+ <property name="userDisabled">
+ <getter><![CDATA[
+ return this.mAddon.userDisabled;
+ ]]></getter>
+ <setter><![CDATA[
+ this.mAddon.userDisabled = val;
+ ]]></setter>
+ </property>
+
+ <property name="includeUpdate">
+ <getter><![CDATA[
+ return this._includeUpdate.checked && !!this.mManualUpdate;
+ ]]></getter>
+ <setter><![CDATA[
+ //XXXunf Eventually, we'll want to persist this for individual
+ // updates - see bug 594619.
+ this._includeUpdate.checked = !!val;
+ ]]></setter>
+ </property>
+
+ <method name="_initWithAddon">
+ <parameter name="aAddon"/>
+ <body><![CDATA[
+ this.mAddon = aAddon;
+
+ this._installStatus.mAddon = this.mAddon;
+ this._updateDates();
+ this._updateState();
+
+ this.setAttribute("name", aAddon.name);
+
+ var iconURL = this.mAddon.iconURL;
+ if (iconURL)
+ this._icon.src = iconURL;
+ else
+ this._icon.src = "";
+
+ if (shouldShowVersionNumber(this.mAddon))
+ this._version.value = this.mAddon.version;
+ else
+ this._version.hidden = true;
+
+ if (this.mAddon.description)
+ this._description.value = this.mAddon.description;
+ else
+ this._description.hidden = true;
+
+ if (!("applyBackgroundUpdates" in this.mAddon) ||
+ (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE ||
+ (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
+ !AddonManager.autoUpdateDefault))) {
+ var self = this;
+ AddonManager.getAllInstalls(function(aInstallsList) {
+ // This can return after the binding has been destroyed,
+ // so try to detect that and return early
+ if (!("onNewInstall" in self))
+ return;
+ for (let install of aInstallsList) {
+ if (install.existingAddon &&
+ install.existingAddon.id == self.mAddon.id &&
+ install.state == AddonManager.STATE_AVAILABLE) {
+ self.onNewInstall(install);
+ self.onIncludeUpdateChanged();
+ }
+ }
+ });
+ }
+ ]]></body>
+ </method>
+
+ <method name="_showStatus">
+ <parameter name="aType"/>
+ <body><![CDATA[
+ this._controlContainer.hidden = aType != "none" &&
+ !(aType == "update-available" && !this.hasAttribute("upgrade"));
+
+ this._installStatus.hidden = aType != "progress";
+ if (aType == "progress")
+ this._installStatus.refreshState();
+ this._checkingUpdate.hidden = aType != "checking-update";
+ this._updateAvailable.hidden = aType != "update-available";
+ this._relNotesToggle.hidden = !(this.mManualUpdate ?
+ this.mManualUpdate.releaseNotesURI :
+ this.mAddon.releaseNotesURI);
+ ]]></body>
+ </method>
+
+ <method name="_updateDates">
+ <body><![CDATA[
+ function formatDate(aDate) {
+ return Cc["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Ci.nsIScriptableDateFormat)
+ .FormatDate("",
+ Ci.nsIScriptableDateFormat.dateFormatLong,
+ aDate.getFullYear(),
+ aDate.getMonth() + 1,
+ aDate.getDate()
+ );
+ }
+
+ if (this.mAddon.updateDate)
+ this._dateUpdated.value = formatDate(this.mAddon.updateDate);
+ else
+ this._dateUpdated.value = this._dateUpdated.getAttribute("unknown");
+ ]]></body>
+ </method>
+
+ <method name="_updateState">
+ <body><![CDATA[
+ if (this.parentNode.selectedItem == this)
+ gViewController.updateCommands();
+
+ var pending = this.mAddon.pendingOperations;
+ if (pending != AddonManager.PENDING_NONE) {
+ this.removeAttribute("notification");
+
+ var pending = null;
+ const PENDING_OPERATIONS = ["enable", "disable", "install",
+ "uninstall", "upgrade"];
+ for (let op of PENDING_OPERATIONS) {
+ if (this.isPending(op))
+ pending = op;
+ }
+
+ this.setAttribute("pending", pending);
+ this._pending.textContent = gStrings.ext.formatStringFromName(
+ "notification." + pending,
+ [this.mAddon.name, gStrings.brandShortName], 2
+ );
+ } else {
+ this.removeAttribute("pending");
+
+ var isUpgrade = this.hasAttribute("upgrade");
+ var install = this._installStatus.mInstall;
+
+ if (install && install.state == AddonManager.STATE_DOWNLOAD_FAILED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.downloadError",
+ [this.mAddon.name], 1
+ );
+ this._warningBtn.label = gStrings.ext.GetStringFromName("notification.downloadError.retry");
+ this._warningBtn.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+ this._warningBtn.setAttribute("oncommand", "document.getBindingParent(this).retryInstall();");
+ this._warningBtn.hidden = false;
+ this._warningLink.hidden = true;
+ } else if (install && install.state == AddonManager.STATE_INSTALL_FAILED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.installError",
+ [this.mAddon.name], 1
+ );
+ this._warningBtn.label = gStrings.ext.GetStringFromName("notification.installError.retry");
+ this._warningBtn.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+ this._warningBtn.setAttribute("oncommand", "document.getBindingParent(this).retryInstall();");
+ this._warningBtn.hidden = false;
+ this._warningLink.hidden = true;
+ } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ this.setAttribute("notification", "error");
+ this._error.textContent = gStrings.ext.formatStringFromName(
+ "notification.blocked",
+ [this.mAddon.name], 1
+ );
+ this._errorLink.value = gStrings.ext.GetStringFromName("notification.blocked.link");
+ this._errorLink.href = this.mAddon.blocklistURL;
+ this._errorLink.hidden = false;
+ } else if ((!isUpgrade && !this.mAddon.isCompatible) && (AddonManager.checkCompatibility
+ || (this.mAddon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.incompatible",
+ [this.mAddon.name, gStrings.brandShortName, gStrings.appVersion], 3
+ );
+ this._warningLink.hidden = true;
+ this._warningBtn.hidden = true;
+ } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.softblocked",
+ [this.mAddon.name], 1
+ );
+ this._warningLink.value = gStrings.ext.GetStringFromName("notification.softblocked.link");
+ this._warningLink.href = this.mAddon.blocklistURL;
+ this._warningLink.hidden = false;
+ this._warningBtn.hidden = true;
+ } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.outdated",
+ [this.mAddon.name], 1
+ );
+ this._warningLink.hidden = true;
+ this._warningBtn.hidden = true;
+ } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
+ this.setAttribute("notification", "error");
+ this._error.textContent = gStrings.ext.formatStringFromName(
+ "notification.vulnerableUpdatable",
+ [this.mAddon.name], 1
+ );
+ this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableUpdatable.link");
+ this._errorLink.href = this.mAddon.blocklistURL;
+ this._errorLink.hidden = false;
+ } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
+ this.setAttribute("notification", "error");
+ this._error.textContent = gStrings.ext.formatStringFromName(
+ "notification.vulnerableNoUpdate",
+ [this.mAddon.name], 1
+ );
+ this._errorLink.value = gStrings.ext.GetStringFromName("notification.vulnerableNoUpdate.link");
+ this._errorLink.href = this.mAddon.blocklistURL;
+ this._errorLink.hidden = false;
+ } else {
+ this.removeAttribute("notification");
+ }
+ }
+
+ this._preferencesBtn.hidden = (!this.mAddon.optionsURL) ||
+ this.mAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO;
+
+ if (this.typeHasFlag("SUPPORTS_ASK_TO_ACTIVATE")) {
+ this._enableBtn.disabled = true;
+ this._disableBtn.disabled = true;
+ this._askToActivateMenuitem.disabled = !this.hasPermission("ask_to_activate");
+ this._alwaysActivateMenuitem.disabled = !this.hasPermission("enable");
+ this._neverActivateMenuitem.disabled = !this.hasPermission("disable");
+ if (!this.mAddon.isActive) {
+ this._stateMenulist.selectedItem = this._neverActivateMenuitem;
+ } else if (this.mAddon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
+ this._stateMenulist.selectedItem = this._askToActivateMenuitem;
+ } else {
+ this._stateMenulist.selectedItem = this._alwaysActivateMenuitem;
+ }
+ let hasActivatePermission =
+ ["ask_to_activate", "enable", "disable"].some(perm => this.hasPermission(perm));
+ this._stateMenulist.disabled = !hasActivatePermission;
+ this._stateMenulist.hidden = false;
+ this._stateMenulist.classList.add('no-auto-hide');
+ } else {
+ this._stateMenulist.hidden = true;
+ if (this.hasPermission("enable")) {
+ this._enableBtn.hidden = false;
+ let tooltip = gViewController.commands["cmd_enableItem"]
+ .getTooltip(this.mAddon);
+ this._enableBtn.setAttribute("tooltiptext", tooltip);
+ } else {
+ this._enableBtn.hidden = true;
+ }
+
+ if (this.hasPermission("disable")) {
+ this._disableBtn.hidden = false;
+ let tooltip = gViewController.commands["cmd_disableItem"]
+ .getTooltip(this.mAddon);
+ this._disableBtn.setAttribute("tooltiptext", tooltip);
+ } else {
+ this._disableBtn.hidden = true;
+ }
+ }
+
+ if (this.hasPermission("uninstall")) {
+ this._removeBtn.hidden = false;
+ let tooltip = gViewController.commands["cmd_uninstallItem"]
+ .getTooltip(this.mAddon);
+ this._removeBtn.setAttribute("tooltiptext", tooltip);
+ } else {
+ this._removeBtn.hidden = true;
+ }
+
+ this.setAttribute("active", this.mAddon.isActive);
+
+ var showProgress = this.mAddon.purchaseURL || (this.mAddon.install &&
+ this.mAddon.install.state != AddonManager.STATE_INSTALLED);
+ this._showStatus(showProgress ? "progress" : "none");
+ ]]></body>
+ </method>
+
+ <method name="_updateUpgradeInfo">
+ <body><![CDATA[
+ // Only update the version string if we're displaying the upgrade info
+ if (this.hasAttribute("upgrade") && shouldShowVersionNumber(this.mAddon))
+ this._version.value = this.mManualUpdate.version;
+ ]]></body>
+ </method>
+
+ <method name="_fetchReleaseNotes">
+ <parameter name="aURI"/>
+ <body><![CDATA[
+ var self = this;
+ if (!aURI || this._relNotesLoaded) {
+ sendToggleEvent();
+ return;
+ }
+
+ var relNotesData = null, transformData = null;
+
+ this._relNotesLoaded = true;
+ this._relNotesLoading.hidden = false;
+ this._relNotesError.hidden = true;
+
+ function sendToggleEvent() {
+ var event = document.createEvent("Events");
+ event.initEvent("RelNotesToggle", true, true);
+ self.dispatchEvent(event);
+ }
+
+ function showRelNotes() {
+ if (!relNotesData || !transformData)
+ return;
+
+ self._relNotesLoading.hidden = true;
+
+ var processor = Components.classes["@mozilla.org/document-transformer;1?type=xslt"]
+ .createInstance(Components.interfaces.nsIXSLTProcessor);
+ processor.flags |= Components.interfaces.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
+
+ processor.importStylesheet(transformData);
+ var fragment = processor.transformToFragment(relNotesData, document);
+ self._relNotes.appendChild(fragment);
+ if (self.hasAttribute("show-relnotes")) {
+ var container = self._relNotesContainer;
+ container.style.height = container.scrollHeight + "px";
+ }
+ sendToggleEvent();
+ }
+
+ function handleError() {
+ dataReq.abort();
+ styleReq.abort();
+ self._relNotesLoading.hidden = true;
+ self._relNotesError.hidden = false;
+ self._relNotesLoaded = false; // allow loading to be re-tried
+ sendToggleEvent();
+ }
+
+ function handleResponse(aEvent) {
+ var req = aEvent.target;
+ var ct = req.getResponseHeader("content-type");
+ if ((!ct || ct.indexOf("text/html") < 0) &&
+ req.responseXML &&
+ req.responseXML.documentElement.namespaceURI != XMLURI_PARSE_ERROR) {
+ if (req == dataReq)
+ relNotesData = req.responseXML;
+ else
+ transformData = req.responseXML;
+ showRelNotes();
+ } else {
+ handleError();
+ }
+ }
+
+ var dataReq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Components.interfaces.nsIXMLHttpRequest);
+ dataReq.open("GET", aURI.spec, true);
+ dataReq.addEventListener("load", handleResponse, false);
+ dataReq.addEventListener("error", handleError, false);
+ dataReq.send(null);
+
+ var styleReq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Components.interfaces.nsIXMLHttpRequest);
+ styleReq.open("GET", UPDATES_RELEASENOTES_TRANSFORMFILE, true);
+ styleReq.addEventListener("load", handleResponse, false);
+ styleReq.addEventListener("error", handleError, false);
+ styleReq.send(null);
+ ]]></body>
+ </method>
+
+ <method name="toggleReleaseNotes">
+ <body><![CDATA[
+ if (this.hasAttribute("show-relnotes")) {
+ this._relNotesContainer.style.height = "0px";
+ this.removeAttribute("show-relnotes");
+ this._relNotesToggle.setAttribute(
+ "label",
+ this._relNotesToggle.getAttribute("showlabel")
+ );
+ this._relNotesToggle.setAttribute(
+ "tooltiptext",
+ this._relNotesToggle.getAttribute("showtooltip")
+ );
+ var event = document.createEvent("Events");
+ event.initEvent("RelNotesToggle", true, true);
+ this.dispatchEvent(event);
+ } else {
+ this._relNotesContainer.style.height = this._relNotesContainer.scrollHeight +
+ "px";
+ this.setAttribute("show-relnotes", true);
+ this._relNotesToggle.setAttribute(
+ "label",
+ this._relNotesToggle.getAttribute("hidelabel")
+ );
+ this._relNotesToggle.setAttribute(
+ "tooltiptext",
+ this._relNotesToggle.getAttribute("hidetooltip")
+ );
+ var uri = this.mManualUpdate ?
+ this.mManualUpdate.releaseNotesURI :
+ this.mAddon.releaseNotesURI;
+ this._fetchReleaseNotes(uri);
+ }
+ ]]></body>
+ </method>
+
+ <method name="restart">
+ <body><![CDATA[
+ gViewController.commands["cmd_restartApp"].doCommand();
+ ]]></body>
+ </method>
+
+ <method name="undo">
+ <body><![CDATA[
+ gViewController.commands["cmd_cancelOperation"].doCommand(this.mAddon);
+ ]]></body>
+ </method>
+
+ <method name="uninstall">
+ <body><![CDATA[
+ // If uninstalling does not require a restart and the type doesn't
+ // support undoing of restartless uninstalls, then we fake it by
+ // just disabling it it, and doing the real uninstall later.
+ if (!this.opRequiresRestart("uninstall") &&
+ !this.typeHasFlag("SUPPORTS_UNDO_RESTARTLESS_UNINSTALL")) {
+ this.setAttribute("wasDisabled", this.mAddon.userDisabled);
+
+ // We must set userDisabled to true first, this will call
+ // _updateState which will clear any pending attribute set.
+ this.mAddon.userDisabled = true;
+
+ // This won't update any other add-on manager views (bug 582002)
+ this.setAttribute("pending", "uninstall");
+ } else {
+ this.mAddon.uninstall(true);
+ }
+ ]]></body>
+ </method>
+
+#ifdef MOZ_DEVTOOLS
+ <method name="debug">
+ <body><![CDATA[
+ gViewController.doCommand("cmd_debugItem", this.mAddon);
+ ]]></body>
+ </method>
+#endif
+
+ <method name="showPreferences">
+ <body><![CDATA[
+ gViewController.doCommand("cmd_showItemPreferences", this.mAddon);
+ ]]></body>
+ </method>
+
+ <method name="upgrade">
+ <body><![CDATA[
+ var install = this.mManualUpdate;
+ delete this.mManualUpdate;
+ install.install();
+ ]]></body>
+ </method>
+
+ <method name="retryInstall">
+ <body><![CDATA[
+ var install = this._installStatus.mInstall;
+ if (!install)
+ return;
+ if (install.state != AddonManager.STATE_DOWNLOAD_FAILED &&
+ install.state != AddonManager.STATE_INSTALL_FAILED)
+ return;
+ install.install();
+ ]]></body>
+ </method>
+
+ <method name="showInDetailView">
+ <body><![CDATA[
+ gViewController.loadView("addons://detail/" +
+ encodeURIComponent(this.mAddon.id));
+ ]]></body>
+ </method>
+
+ <method name="onIncludeUpdateChanged">
+ <body><![CDATA[
+ var event = document.createEvent("Events");
+ event.initEvent("IncludeUpdateChanged", true, true);
+ this.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="onEnabling">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onEnabled">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onDisabling">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onDisabled">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onUninstalling">
+ <parameter name="aRestartRequired"/>
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onOperationCancelled">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onPropertyChanged">
+ <parameter name="aProperties"/>
+ <body><![CDATA[
+ if (aProperties.indexOf("appDisabled") != -1 ||
+ aProperties.indexOf("userDisabled") != -1)
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onNoUpdateAvailable">
+ <body><![CDATA[
+ this._showStatus("none");
+ ]]></body>
+ </method>
+
+ <method name="onCheckingUpdate">
+ <body><![CDATA[
+ this._showStatus("checking-update");
+ ]]></body>
+ </method>
+
+ <method name="onCompatibilityUpdateAvailable">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onExternalInstall">
+ <parameter name="aAddon"/>
+ <parameter name="aExistingAddon"/>
+ <parameter name="aNeedsRestart"/>
+ <body><![CDATA[
+ if (aExistingAddon.id != this.mAddon.id)
+ return;
+
+ // If the install completed without needing a restart then switch to
+ // using the new Addon
+ if (!aNeedsRestart)
+ this._initWithAddon(aAddon);
+ else
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onNewInstall">
+ <parameter name="aInstall"/>
+ <body><![CDATA[
+ if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
+ return;
+ if (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
+ AddonManager.autoUpdateDefault)
+ return;
+
+ this.mManualUpdate = aInstall;
+ this._showStatus("update-available");
+ this._updateUpgradeInfo();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadStarted">
+ <parameter name="aInstall"/>
+ <body><![CDATA[
+ this._updateState();
+ this._showStatus("progress");
+ this._installStatus.initWithInstall(aInstall);
+ ]]></body>
+ </method>
+
+ <method name="onInstallStarted">
+ <parameter name="aInstall"/>
+ <body><![CDATA[
+ this._updateState();
+ this._showStatus("progress");
+ this._installStatus.initWithInstall(aInstall);
+ ]]></body>
+ </method>
+
+ <method name="onInstallEnded">
+ <parameter name="aInstall"/>
+ <parameter name="aAddon"/>
+ <body><![CDATA[
+ // If the install completed without needing a restart then switch to
+ // using the new Addon
+ if (!(aAddon.pendingOperations & AddonManager.PENDING_INSTALL))
+ this._initWithAddon(aAddon);
+ else
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadFailed">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onInstallFailed">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+
+ <method name="onInstallCancelled">
+ <body><![CDATA[
+ this._updateState();
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="click" button="0"><![CDATA[
+ switch (event.detail) {
+ case 1:
+ // Prevent double-click where the UI changes on the first click
+ this._lastClickTarget = event.originalTarget;
+ break;
+ case 2:
+ if (event.originalTarget.localName != 'button' &&
+ !event.originalTarget.classList.contains('text-link') &&
+ event.originalTarget == this._lastClickTarget) {
+ this.showInDetailView();
+ }
+ break;
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+
+
+ <!-- Addon - uninstalled - An uninstalled addon that can be re-installed. -->
+ <binding id="addon-uninstalled"
+ extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
+ <content>
+ <xul:hbox class="pending">
+ <xul:image class="pending-icon"/>
+ <xul:label anonid="notice" flex="1"/>
+ <xul:button anonid="restart-btn" class="button-link"
+ label="&addon.restartNow.label;"
+ command="cmd_restartApp"/>
+ <xul:button anonid="undo-btn" class="button-link"
+ label="&addon.undoRemove.label;"
+ tooltiptext="&addon.undoRemove.tooltip;"
+ oncommand="document.getBindingParent(this).cancelUninstall();"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ this._notice.textContent = gStrings.ext.formatStringFromName("uninstallNotice",
+ [this.mAddon.name],
+ 1);
+
+ if (!this.opRequiresRestart("uninstall"))
+ this._restartBtn.setAttribute("hidden", true);
+
+ gEventManager.registerAddonListener(this, this.mAddon.id);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ gEventManager.unregisterAddonListener(this, this.mAddon.id);
+ ]]></destructor>
+
+ <field name="_notice" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "notice");
+ </field>
+ <field name="_restartBtn" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "restart-btn");
+ </field>
+
+ <method name="cancelUninstall">
+ <body><![CDATA[
+ // This assumes that disabling does not require a restart when
+ // uninstalling doesn't. Things will still work if not, the add-on
+ // will just still be active until finally getting uninstalled.
+
+ if (this.isPending("uninstall"))
+ this.mAddon.cancelUninstall();
+ else if (this.getAttribute("wasDisabled") != "true")
+ this.mAddon.userDisabled = false;
+
+ this.removeAttribute("pending");
+ ]]></body>
+ </method>
+
+ <method name="onOperationCancelled">
+ <body><![CDATA[
+ if (!this.isPending("uninstall"))
+ this.removeAttribute("pending");
+ ]]></body>
+ </method>
+
+ <method name="onExternalInstall">
+ <parameter name="aAddon"/>
+ <parameter name="aExistingAddon"/>
+ <parameter name="aNeedsRestart"/>
+ <body><![CDATA[
+ if (aExistingAddon.id != this.mAddon.id)
+ return;
+
+ // Make sure any newly installed add-on has the correct disabled state
+ if (this.hasAttribute("wasDisabled"))
+ aAddon.userDisabled = this.getAttribute("wasDisabled") == "true";
+
+ // If the install completed without needing a restart then switch to
+ // using the new Addon
+ if (!aNeedsRestart)
+ this.mAddon = aAddon;
+
+ this.removeAttribute("pending");
+ ]]></body>
+ </method>
+
+ <method name="onInstallStarted">
+ <parameter name="aInstall"/>
+ <body><![CDATA[
+ // Make sure any newly installed add-on has the correct disabled state
+ if (this.hasAttribute("wasDisabled"))
+ aInstall.addon.userDisabled = this.getAttribute("wasDisabled") == "true";
+ ]]></body>
+ </method>
+
+ <method name="onInstallEnded">
+ <parameter name="aInstall"/>
+ <parameter name="aAddon"/>
+ <body><![CDATA[
+ // If the install completed without needing a restart then switch to
+ // using the new Addon
+ if (!(aAddon.pendingOperations & AddonManager.PENDING_INSTALL))
+ this.mAddon = aAddon;
+
+ this.removeAttribute("pending");
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+
+ <!-- Addon - installing - an addon item that is currently being installed -->
+ <binding id="addon-installing"
+ extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
+ <content>
+ <xul:hbox anonid="warning-container" class="warning">
+ <xul:image class="warning-icon"/>
+ <xul:label anonid="warning" flex="1"/>
+ <xul:button anonid="warning-link" class="button-link"
+ oncommand="document.getBindingParent(this).retryInstall();"/>
+ <xul:spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </xul:hbox>
+ <xul:hbox class="content-container">
+ <xul:vbox class="icon-outer-container">
+ <xul:vbox class="icon-container">
+ <xul:image anonid="icon" class="icon"/>
+ </xul:vbox>
+ </xul:vbox>
+ <xul:vbox class="fade name-outer-container" flex="1">
+ <xul:hbox class="name-container">
+ <xul:label anonid="name" class="name" crop="end"/>
+ <xul:label anonid="version" class="version" hidden="true"/>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:vbox class="install-status-container">
+ <xul:hbox anonid="install-status" class="install-status"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ this._installStatus.mControl = this;
+ this._installStatus.mInstall = this.mInstall;
+ this.refreshInfo();
+ ]]></constructor>
+
+ <field name="_icon">
+ document.getAnonymousElementByAttribute(this, "anonid", "icon");
+ </field>
+ <field name="_name">
+ document.getAnonymousElementByAttribute(this, "anonid", "name");
+ </field>
+ <field name="_version">
+ document.getAnonymousElementByAttribute(this, "anonid", "version");
+ </field>
+ <field name="_warning">
+ document.getAnonymousElementByAttribute(this, "anonid", "warning");
+ </field>
+ <field name="_warningLink">
+ document.getAnonymousElementByAttribute(this, "anonid", "warning-link");
+ </field>
+ <field name="_installStatus">
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "install-status");
+ </field>
+
+ <method name="onInstallCompleted">
+ <body><![CDATA[
+ this.mAddon = this.mInstall.addon;
+ this.setAttribute("name", this.mAddon.name);
+ this.setAttribute("value", this.mAddon.id);
+ this.setAttribute("status", "installed");
+ ]]></body>
+ </method>
+
+ <method name="refreshInfo">
+ <body><![CDATA[
+ this.mAddon = this.mAddon || this.mInstall.addon;
+ if (this.mAddon) {
+ this._icon.src = this.mAddon.iconURL ||
+ (this.mInstall ? this.mInstall.iconURL : "");
+ this._name.value = this.mAddon.name;
+
+ if (this.mAddon.version) {
+ this._version.value = this.mAddon.version;
+ this._version.hidden = false;
+ } else {
+ this._version.hidden = true;
+ }
+
+ } else {
+ this._icon.src = this.mInstall.iconURL;
+ // AddonInstall.name isn't always available - fallback to filename
+ if (this.mInstall.name) {
+ this._name.value = this.mInstall.name;
+ } else if (this.mInstall.sourceURI) {
+ var url = Components.classes["@mozilla.org/network/standard-url;1"]
+ .createInstance(Components.interfaces.nsIStandardURL);
+ url.init(url.URLTYPE_STANDARD, 80, this.mInstall.sourceURI.spec,
+ null, null);
+ url.QueryInterface(Components.interfaces.nsIURL);
+ this._name.value = url.fileName;
+ }
+
+ if (this.mInstall.version) {
+ this._version.value = this.mInstall.version;
+ this._version.hidden = false;
+ } else {
+ this._version.hidden = true;
+ }
+ }
+
+ if (this.mInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.downloadError",
+ [this._name.value], 1
+ );
+ this._warningLink.label = gStrings.ext.GetStringFromName("notification.downloadError.retry");
+ this._warningLink.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+ } else if (this.mInstall.state == AddonManager.STATE_INSTALL_FAILED) {
+ this.setAttribute("notification", "warning");
+ this._warning.textContent = gStrings.ext.formatStringFromName(
+ "notification.installError",
+ [this._name.value], 1
+ );
+ this._warningLink.label = gStrings.ext.GetStringFromName("notification.installError.retry");
+ this._warningLink.tooltipText = gStrings.ext.GetStringFromName("notification.downloadError.retry.tooltip");
+ } else {
+ this.removeAttribute("notification");
+ }
+ ]]></body>
+ </method>
+
+ <method name="retryInstall">
+ <body><![CDATA[
+ this.mInstall.install();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="detail-row">
+ <content>
+ <xul:label class="detail-row-label" xbl:inherits="value=label"/>
+ <xul:label class="detail-row-value" xbl:inherits="value"/>
+ </content>
+
+ <implementation>
+ <property name="value">
+ <getter><![CDATA[
+ return this.getAttribute("value");
+ ]]></getter>
+ <setter><![CDATA[
+ if (!val)
+ this.removeAttribute("value");
+ else
+ this.setAttribute("value", val);
+ ]]></setter>
+ </property>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/addons/content/extensions.xul b/components/addons/content/extensions.xul
new file mode 100644
index 000000000..a61bb6162
--- /dev/null
+++ b/components/addons/content/extensions.xul
@@ -0,0 +1,658 @@
+<?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://mozapps/content/extensions/extensions.css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/extensions.css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/about.css"?>
+
+<!DOCTYPE page [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
+%extensionsDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ id="addons-page" title="&addons.windowTitle;"
+ role="application" windowtype="Addons:Manager"
+ disablefastfind="true">
+
+ <xhtml:link rel="shortcut icon"
+ href="chrome://mozapps/skin/extensions/extensionGeneric-16.png"/>
+
+ <script type="application/javascript"
+ src="chrome://mozapps/content/extensions/extensions.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/contentAreaUtils.js"/>
+
+ <popupset>
+ <!-- menu for an addon item -->
+ <menupopup id="addonitem-popup">
+ <menuitem id="menuitem_showDetails" command="cmd_showItemDetails"
+ default="true" label="&cmd.showDetails.label;"
+ accesskey="&cmd.showDetails.accesskey;"/>
+ <menuitem id="menuitem_enableItem" command="cmd_enableItem"
+ label="&cmd.enableAddon.label;"
+ accesskey="&cmd.enableAddon.accesskey;"/>
+ <menuitem id="menuitem_disableItem" command="cmd_disableItem"
+ label="&cmd.disableAddon.label;"
+ accesskey="&cmd.disableAddon.accesskey;"/>
+ <menuitem id="menuitem_enableTheme" command="cmd_enableItem"
+ label="&cmd.enableTheme.label;"
+ accesskey="&cmd.enableTheme.accesskey;"/>
+ <menuitem id="menuitem_disableTheme" command="cmd_disableItem"
+ label="&cmd.disableTheme.label;"
+ accesskey="&cmd.disableTheme.accesskey;"/>
+ <menuitem id="menuitem_installItem" command="cmd_installItem"
+ label="&cmd.installAddon.label;"
+ accesskey="&cmd.installAddon.accesskey;"/>
+ <menuitem id="menuitem_uninstallItem" command="cmd_uninstallItem"
+ label="&cmd.uninstallAddon.label;"
+ accesskey="&cmd.uninstallAddon.accesskey;"/>
+#ifdef MOZ_DEVTOOLS
+ <menuitem id="menuitem_debugItem" command="cmd_debugItem"
+ label="&cmd.debugAddon.label;"/>
+#endif
+ <menuseparator id="addonitem-menuseparator" />
+ <menuitem id="menuitem_preferences" command="cmd_showItemPreferences"
+#ifdef XP_WIN
+ label="&cmd.preferencesWin.label;"
+ accesskey="&cmd.preferencesWin.accesskey;"/>
+#else
+ label="&cmd.preferencesUnix.label;"
+ accesskey="&cmd.preferencesUnix.accesskey;"/>
+#endif
+ <menuitem id="menuitem_findUpdates" command="cmd_findItemUpdates"
+ label="&cmd.findUpdates.label;"
+ accesskey="&cmd.findUpdates.accesskey;"/>
+ <menuitem id="menuitem_about" command="cmd_showItemAbout"
+ label="&cmd.about.label;"
+ accesskey="&cmd.about.accesskey;"/>
+ </menupopup>
+ </popupset>
+
+ <!-- global commands - these act on all addons, or affect the addons manager
+ in some other way -->
+ <commandset id="globalCommandSet">
+ <command id="cmd_focusSearch"/>
+ <command id="cmd_findAllUpdates"/>
+ <command id="cmd_restartApp"/>
+ <command id="cmd_goToDiscoverPane"/>
+ <command id="cmd_goToRecentUpdates"/>
+ <command id="cmd_goToAvailableUpdates"/>
+ <command id="cmd_installFromFile"/>
+ <command id="cmd_back"/>
+ <command id="cmd_forward"/>
+ <command id="cmd_enableCheckCompatibility"/>
+ <command id="cmd_enableUpdateSecurity"/>
+ <command id="cmd_toggleAutoUpdateDefault"/>
+ <command id="cmd_resetAddonAutoUpdate"/>
+ </commandset>
+
+ <!-- view commands - these act on the selected addon -->
+ <commandset id="viewCommandSet"
+ events="richlistbox-select" commandupdater="true">
+ <command id="cmd_showItemDetails"/>
+ <command id="cmd_findItemUpdates"/>
+ <command id="cmd_showItemPreferences"/>
+ <command id="cmd_showItemAbout"/>
+#ifdef MOZ_DEVTOOLS
+ <command id="cmd_debugItem"/>
+#endif
+ <command id="cmd_enableItem"/>
+ <command id="cmd_disableItem"/>
+ <command id="cmd_installItem"/>
+ <command id="cmd_purchaseItem"/>
+ <command id="cmd_uninstallItem"/>
+ <command id="cmd_cancelUninstallItem"/>
+ <command id="cmd_cancelOperation"/>
+ <command id="cmd_contribute"/>
+ <command id="cmd_askToActivateItem"/>
+ <command id="cmd_alwaysActivateItem"/>
+ <command id="cmd_neverActivateItem"/>
+ </commandset>
+
+ <keyset>
+ <!-- XXXunf Disabled until bug 371900 is fixed. -->
+ <key id="focusSearch" key="&search.commandkey;" modifiers="accel"
+ disabled="true"/>
+ </keyset>
+
+ <!-- main header -->
+ <hbox id="header" align="center">
+ <toolbarbutton id="back-btn" class="nav-button header-button" command="cmd_back"
+ tooltiptext="&cmd.back.tooltip;" hidden="true" disabled="true"/>
+ <toolbarbutton id="forward-btn" class="nav-button header-button" command="cmd_forward"
+ tooltiptext="&cmd.forward.tooltip;" hidden="true" disabled="true"/>
+ <spacer flex="1"/>
+ <hbox id="updates-container" align="center">
+ <image class="spinner"/>
+ <label id="updates-noneFound" hidden="true"
+ value="&updates.noneFound.label;"/>
+ <button id="updates-manualUpdatesFound-btn" class="button-link"
+ hidden="true" label="&updates.manualUpdatesFound.label;"
+ command="cmd_goToAvailableUpdates"/>
+ <label id="updates-progress" hidden="true"
+ value="&updates.updating.label;"/>
+ <label id="updates-installed" hidden="true"
+ value="&updates.installed.label;"/>
+ <label id="updates-downloaded" hidden="true"
+ value="&updates.downloaded.label;"/>
+ <button id="updates-restart-btn" class="button-link" hidden="true"
+ label="&updates.restart.label;"
+ command="cmd_restartApp"/>
+ </hbox>
+ <toolbarbutton id="header-utils-btn" class="header-button" type="menu"
+ tooltiptext="&toolsMenu.tooltip;">
+ <menupopup id="utils-menu">
+ <menuitem id="utils-updateNow"
+ label="&updates.checkForUpdates.label;"
+ accesskey="&updates.checkForUpdates.accesskey;"
+ command="cmd_findAllUpdates"/>
+ <menuitem id="utils-viewUpdates"
+ label="&updates.viewUpdates.label;"
+ accesskey="&updates.viewUpdates.accesskey;"
+ command="cmd_goToRecentUpdates"/>
+ <menuseparator id="utils-installFromFile-separator"/>
+ <menuitem id="utils-installFromFile"
+ label="&installAddonFromFile.label;"
+ accesskey="&installAddonFromFile.accesskey;"
+ command="cmd_installFromFile"/>
+ <menuseparator/>
+ <menuitem id="utils-autoUpdateDefault"
+ label="&updates.updateAddonsAutomatically.label;"
+ accesskey="&updates.updateAddonsAutomatically.accesskey;"
+ type="checkbox" autocheck="false"
+ command="cmd_toggleAutoUpdateDefault"/>
+ <menuitem id="utils-resetAddonUpdatesToAutomatic"
+ label="&updates.resetUpdatesToAutomatic.label;"
+ accesskey="&updates.resetUpdatesToAutomatic.accesskey;"
+ command="cmd_resetAddonAutoUpdate"/>
+ <menuitem id="utils-resetAddonUpdatesToManual"
+ label="&updates.resetUpdatesToManual.label;"
+ accesskey="&updates.resetUpdatesToManual.accesskey;"
+ command="cmd_resetAddonAutoUpdate"/>
+ </menupopup>
+ </toolbarbutton>
+ <textbox id="header-search" type="search" searchbutton="true"
+ searchbuttonlabel="&search.buttonlabel;"
+ placeholder="&search.placeholder;"/>
+ </hbox>
+
+ <hbox id="main" flex="1">
+
+ <!-- category list -->
+ <richlistbox id="categories">
+ <richlistitem id="category-search" value="addons://search/"
+ class="category"
+ name="&view.search.label;" priority="0"
+ tooltiptext="&view.search.label;" disabled="true"/>
+ <richlistitem id="category-discover" value="addons://discover/"
+ class="category"
+ name="&view.discover.label;" priority="1000"
+ tooltiptext="&view.discover.label;"/>
+ <richlistitem id="category-availableUpdates" value="addons://updates/available"
+ class="category"
+ name="&view.availableUpdates.label;" priority="100000"
+ tooltiptext="&view.availableUpdates.label;"
+ disabled="true"/>
+ <richlistitem id="category-recentUpdates" value="addons://updates/recent"
+ class="category"
+ name="&view.recentUpdates.label;" priority="101000"
+ tooltiptext="&view.recentUpdates.label;" disabled="true"/>
+ </richlistbox>
+
+ <box id="view-port-container" class="main-content" flex="1">
+
+ <!-- view port -->
+ <deck id="view-port" flex="1" selectedIndex="0">
+
+ <!-- discover view -->
+ <deck id="discover-view" flex="1" class="view-pane" selectedIndex="0" tabindex="0">
+ <vbox id="discover-loading" align="center" pack="stretch" flex="1" class="alert-container">
+ <spacer class="alert-spacer-before"/>
+ <hbox class="alert loading" align="center">
+ <image/>
+ <label value="&loading.label;"/>
+ </hbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <vbox id="discover-error" align="center" pack="stretch" flex="1" class="alert-container">
+ <spacer class="alert-spacer-before"/>
+ <hbox>
+ <spacer class="discover-spacer-before"/>
+ <hbox class="alert" align="center">
+ <image class="discover-logo"/>
+ <vbox flex="1" align="stretch">
+ <label class="discover-title">&discover.title;</label>
+ <description class="discover-description">&discover.description2;</description>
+ <description class="discover-footer">&discover.footer;</description>
+ </vbox>
+ </hbox>
+ <spacer class="discover-spacer-after"/>
+ </hbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <browser id="discover-browser" type="content" flex="1"
+ disablehistory="true" homepage="about:blank"/>
+ </deck>
+
+ <!-- search view -->
+ <vbox id="search-view" flex="1" class="view-pane" tabindex="0">
+ <hbox class="view-header global-warning-container" align="center">
+ <!-- global warnings -->
+ <hbox class="global-warning" flex="1">
+ <hbox class="global-warning-safemode" flex="1" align="center"
+ tooltiptext="&warning.safemode.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.safemode.label;"/>
+ </hbox>
+ <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+ tooltiptext="&warning.checkcompatibility.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.checkcompatibility.label;"/>
+ </hbox>
+ <button class="button-link global-warning-checkcompatibility"
+ label="&warning.checkcompatibility.enable.label;"
+ tooltiptext="&warning.checkcompatibility.enable.tooltip;"
+ command="cmd_enableCheckCompatibility"/>
+ <hbox class="global-warning-updatesecurity" flex="1" align="center"
+ tooltiptext="&warning.updatesecurity.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.updatesecurity.label;"/>
+ </hbox>
+ <button class="button-link global-warning-updatesecurity"
+ label="&warning.updatesecurity.enable.label;"
+ tooltiptext="&warning.updatesecurity.enable.tooltip;"
+ command="cmd_enableUpdateSecurity"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ <spacer flex="1"/>
+ <hbox id="search-sorters" class="sort-controls"
+ showrelevance="true" sortby="relevancescore" ascending="false"/>
+ </hbox>
+ <hbox id="search-filter" align="center">
+ <label id="search-filter-label" value="&search.filter2.label;"/>
+ <radiogroup id="search-filter-radiogroup" orient="horizontal"
+ align="center" persist="value" value="remote">
+ <radio id="search-filter-local" class="search-filter-radio"
+ label="&search.filter2.installed.label;" value="local"
+ tooltiptext="&search.filter2.installed.tooltip;"/>
+ <radio id="search-filter-remote" class="search-filter-radio"
+ label="&search.filter2.available.label;" value="remote"
+ tooltiptext="&search.filter2.available.tooltip;"/>
+ </radiogroup>
+ </hbox>
+ <vbox id="search-loading" class="alert-container"
+ flex="1" hidden="true">
+ <spacer class="alert-spacer-before"/>
+ <hbox class="alert loading" align="center">
+ <image/>
+ <label value="&loading.label;"/>
+ </hbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <vbox id="search-list-empty" class="alert-container"
+ flex="1" hidden="true">
+ <spacer class="alert-spacer-before"/>
+ <vbox class="alert">
+ <label value="&listEmpty.search.label;"/>
+ <button class="discover-button"
+ id="discover-button-search"
+ label="&listEmpty.button.label;"
+ command="cmd_goToDiscoverPane"/>
+ </vbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <richlistbox id="search-list" class="list" flex="1">
+ <hbox pack="center">
+ <label id="search-allresults-link" class="text-link"/>
+ </hbox>
+ </richlistbox>
+ </vbox>
+
+ <!-- list view -->
+ <vbox id="list-view" flex="1" class="view-pane" align="stretch" tabindex="0">
+ <hbox class="view-header global-warning-container">
+ <!-- global warnings -->
+ <hbox class="global-warning" flex="1">
+ <hbox class="global-warning-safemode" flex="1" align="center"
+ tooltiptext="&warning.safemode.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.safemode.label;"/>
+ </hbox>
+ <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+ tooltiptext="&warning.checkcompatibility.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.checkcompatibility.label;"/>
+ </hbox>
+ <button class="button-link global-warning-checkcompatibility"
+ label="&warning.checkcompatibility.enable.label;"
+ tooltiptext="&warning.checkcompatibility.enable.tooltip;"
+ command="cmd_enableCheckCompatibility"/>
+ <hbox class="global-warning-updatesecurity" flex="1" align="center"
+ tooltiptext="&warning.updatesecurity.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.updatesecurity.label;"/>
+ </hbox>
+ <button class="button-link global-warning-updatesecurity"
+ label="&warning.updatesecurity.enable.label;"
+ tooltiptext="&warning.updatesecurity.enable.tooltip;"
+ command="cmd_enableUpdateSecurity"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ </hbox>
+ <vbox id="addon-list-empty" class="alert-container"
+ flex="1" hidden="true">
+ <spacer class="alert-spacer-before"/>
+ <vbox class="alert">
+ <label value="&listEmpty.installed.label;"/>
+ <button class="discover-button"
+ id="discover-button-install"
+ label="&listEmpty.button.label;"
+ command="cmd_goToDiscoverPane"/>
+ </vbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <richlistbox id="addon-list" class="list" flex="1"/>
+ </vbox>
+
+ <!-- updates view -->
+ <vbox id="updates-view" flex="1" class="view-pane" tabindex="0">
+ <hbox class="view-header global-warning-container" align="center">
+ <!-- global warnings -->
+ <hbox class="global-warning" flex="1">
+ <hbox class="global-warning-safemode" flex="1" align="center"
+ tooltiptext="&warning.safemode.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.safemode.label;"/>
+ </hbox>
+ <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+ tooltiptext="&warning.checkcompatibility.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.checkcompatibility.label;"/>
+ </hbox>
+ <button class="button-link global-warning-checkcompatibility"
+ label="&warning.checkcompatibility.enable.label;"
+ tooltiptext="&warning.checkcompatibility.enable.tooltip;"
+ command="cmd_enableCheckCompatibility"/>
+ <hbox class="global-warning-updatesecurity" flex="1" align="center"
+ tooltiptext="&warning.updatesecurity.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.updatesecurity.label;"/>
+ </hbox>
+ <button class="button-link global-warning-updatesecurity"
+ label="&warning.updatesecurity.enable.label;"
+ tooltiptext="&warning.updatesecurity.enable.tooltip;"
+ command="cmd_enableUpdateSecurity"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ <spacer flex="1"/>
+ <hbox id="updates-sorters" class="sort-controls" sortby="updateDate"
+ ascending="false"/>
+ </hbox>
+ <vbox id="updates-list-empty" class="alert-container"
+ flex="1" hidden="true">
+ <spacer class="alert-spacer-before"/>
+ <vbox class="alert">
+ <label id="empty-availableUpdates-msg" value="&listEmpty.availableUpdates.label;"/>
+ <label id="empty-recentUpdates-msg" value="&listEmpty.recentUpdates.label;"/>
+ <button label="&listEmpty.findUpdates.label;"
+ command="cmd_findAllUpdates"/>
+ </vbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <hbox id="update-actions" pack="center">
+ <button id="update-selected-btn" hidden="true"
+ label="&updates.updateSelected.label;"
+ tooltiptext="&updates.updateSelected.tooltip;"/>
+ </hbox>
+ <richlistbox id="updates-list" class="list" flex="1"/>
+ </vbox>
+
+ <!-- detail view -->
+ <scrollbox id="detail-view" flex="1" class="view-pane addon-view" orient="vertical" tabindex="0"
+ role="document">
+ <!-- global warnings -->
+ <hbox class="global-warning-container global-warning">
+ <hbox class="global-warning-safemode" flex="1" align="center"
+ tooltiptext="&warning.safemode.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.safemode.label;"/>
+ </hbox>
+ <hbox class="global-warning-checkcompatibility" flex="1" align="center"
+ tooltiptext="&warning.checkcompatibility.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.checkcompatibility.label;"/>
+ </hbox>
+ <button class="button-link global-warning-checkcompatibility"
+ label="&warning.checkcompatibility.enable.label;"
+ tooltiptext="&warning.checkcompatibility.enable.tooltip;"
+ command="cmd_enableCheckCompatibility"/>
+ <hbox class="global-warning-updatesecurity" flex="1" align="center"
+ tooltiptext="&warning.updatesecurity.label;">
+ <image class="warning-icon"/>
+ <label class="global-warning-text" flex="1" crop="end"
+ value="&warning.updatesecurity.label;"/>
+ </hbox>
+ <button class="button-link global-warning-updatesecurity"
+ label="&warning.updatesecurity.enable.label;"
+ tooltiptext="&warning.updatesecurity.enable.tooltip;"
+ command="cmd_enableUpdateSecurity"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ <hbox flex="1">
+ <spacer flex="1"/>
+ <!-- "loading" splash screen -->
+ <vbox class="alert-container">
+ <spacer class="alert-spacer-before"/>
+ <hbox class="alert loading">
+ <image/>
+ <label value="&loading.label;"/>
+ </hbox>
+ <spacer class="alert-spacer-after"/>
+ </vbox>
+ <!-- actual detail view -->
+ <vbox class="detail-view-container" flex="3" contextmenu="addonitem-popup">
+ <vbox id="detail-notifications">
+ <hbox id="warning-container" align="center" class="warning">
+ <image class="warning-icon"/>
+ <label id="detail-warning" flex="1"/>
+ <label id="detail-warning-link" class="text-link"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ <hbox id="error-container" align="center" class="error">
+ <image class="error-icon"/>
+ <label id="detail-error" flex="1"/>
+ <label id="detail-error-link" class="text-link"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ <hbox id="pending-container" align="center" class="pending">
+ <image class="pending-icon"/>
+ <label id="detail-pending" flex="1"/>
+ <button id="detail-restart-btn" class="button-link"
+ label="&addon.restartNow.label;"
+ command="cmd_restartApp"/>
+ <button id="detail-undo-btn" class="button-link"
+ label="&addon.undoAction.label;"
+ tooltipText="&addon.undoAction.tooltip;"
+ command="cmd_cancelOperation"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the message to wrap -->
+ </hbox>
+ </vbox>
+ <hbox align="start">
+ <vbox id="detail-icon-container" align="end">
+ <image id="detail-icon" class="icon"/>
+ </vbox>
+ <vbox flex="1">
+ <vbox id="detail-summary">
+ <hbox id="detail-name-container" class="name-container"
+ align="start">
+ <label id="detail-name" flex="1"/>
+ <label id="detail-version"/>
+ <label class="disabled-postfix" value="&addon.disabled.postfix;"/>
+ <label class="update-postfix" value="&addon.update.postfix;"/>
+ <spacer flex="5000"/> <!-- Necessary to allow the name to wrap -->
+ </hbox>
+ <label id="detail-creator" class="creator"/>
+ <label id="detail-translators" class="translators"/>
+ </vbox>
+ <hbox id="detail-desc-container" align="start">
+ <vbox pack="center"> <!-- Necessary to work around bug 394738 -->
+ <image id="detail-screenshot" hidden="true"/>
+ </vbox>
+ <vbox flex="1">
+ <description id="detail-desc"/>
+ <description id="detail-fulldesc"/>
+ </vbox>
+ </hbox>
+ <vbox id="detail-contributions">
+ <description id="detail-contrib-description">
+ &detail.contributions.description;
+ </description>
+ <hbox align="center">
+ <label id="detail-contrib-suggested"/>
+ <spacer flex="1"/>
+ <button id="detail-contrib-btn"
+ label="&cmd.contribute.label;"
+ accesskey="&cmd.contribute.accesskey;"
+ tooltiptext="&cmd.contribute.tooltip;"
+ command="cmd_contribute"/>
+ </hbox>
+ </vbox>
+ <grid id="detail-grid">
+ <columns>
+ <column flex="1"/>
+ <column flex="2"/>
+ </columns>
+ <rows id="detail-rows">
+ <row class="detail-row-complex" id="detail-updates-row">
+ <label class="detail-row-label" value="&detail.updateType;"/>
+ <hbox align="center">
+ <radiogroup id="detail-autoUpdate" orient="horizontal">
+ <!-- The values here need to match the values of
+ AddonManager.AUTOUPDATE_* -->
+ <radio label="&detail.updateDefault.label;"
+ tooltiptext="&detail.updateDefault.tooltip;"
+ value="1"/>
+ <radio label="&detail.updateAutomatic.label;"
+ tooltiptext="&detail.updateAutomatic.tooltip;"
+ value="2"/>
+ <radio label="&detail.updateManual.label;"
+ tooltiptext="&detail.updateManual.tooltip;"
+ value="0"/>
+ </radiogroup>
+ <button id="detail-findUpdates-btn" class="button-link"
+ label="&detail.checkForUpdates.label;"
+ accesskey="&detail.checkForUpdates.accesskey;"
+ tooltiptext="&detail.checkForUpdates.tooltip;"
+ command="cmd_findItemUpdates"/>
+ </hbox>
+ </row>
+ <row class="detail-row" id="detail-dateUpdated" label="&detail.lastupdated.label;"/>
+ <row class="detail-row-complex" id="detail-homepage-row" label="&detail.home;">
+ <label class="detail-row-label" value="&detail.home;"/>
+ <label id="detail-homepage" class="detail-row-value text-link" crop="end"/>
+ </row>
+ <row class="detail-row-complex" id="detail-repository-row" label="&detail.repository;">
+ <label class="detail-row-label" value="&detail.repository;"/>
+ <label id="detail-repository" class="detail-row-value text-link"/>
+ </row>
+ <row class="detail-row" id="detail-size" label="&detail.size;"/>
+ <row class="detail-row-complex" id="detail-rating-row">
+ <label class="detail-row-label" value="&rating2.label;"/>
+ <hbox>
+ <label id="detail-rating" class="meta-value meta-rating"
+ showrating="average"/>
+ <label id="detail-reviews" class="text-link"/>
+ </hbox>
+ </row>
+ <row class="detail-row" id="detail-downloads" label="&detail.numberOfDownloads.label;"/>
+ </rows>
+ </grid>
+ <hbox id="detail-controls">
+ <button id="detail-prefs-btn" class="addon-control preferences"
+#ifdef XP_WIN
+ label="&detail.showPreferencesWin.label;"
+ accesskey="&detail.showPreferencesWin.accesskey;"
+ tooltiptext="&detail.showPreferencesWin.tooltip;"
+#else
+ label="&detail.showPreferencesUnix.label;"
+ accesskey="&detail.showPreferencesUnix.accesskey;"
+ tooltiptext="&detail.showPreferencesUnix.tooltip;"
+#endif
+ command="cmd_showItemPreferences"/>
+ <spacer flex="1"/>
+#ifdef MOZ_DEVTOOLS
+ <button id="detail-debug-btn" class="addon-control debug"
+ label="Debug"
+ command="cmd_debugItem" />
+#endif
+ <button id="detail-enable-btn" class="addon-control enable"
+ label="&cmd.enableAddon.label;"
+ accesskey="&cmd.enableAddon.accesskey;"
+ command="cmd_enableItem"/>
+ <button id="detail-disable-btn" class="addon-control disable"
+ label="&cmd.disableAddon.label;"
+ accesskey="&cmd.disableAddon.accesskey;"
+ command="cmd_disableItem"/>
+ <button id="detail-uninstall-btn" class="addon-control remove"
+ label="&cmd.uninstallAddon.label;"
+ accesskey="&cmd.uninstallAddon.accesskey;"
+ command="cmd_uninstallItem"/>
+ <button id="detail-purchase-btn" class="addon-control purchase"
+ command="cmd_purchaseItem"/>
+ <button id="detail-install-btn" class="addon-control install"
+ label="&cmd.installAddon.label;"
+ accesskey="&cmd.installAddon.accesskey;"
+ command="cmd_installItem"/>
+ <menulist id="detail-state-menulist"
+ crop="none" sizetopopup="always"
+ tooltiptext="&cmd.stateMenu.tooltip;">
+ <menupopup>
+ <menuitem id="detail-ask-to-activate-menuitem"
+ class="addon-control"
+ label="&cmd.askToActivate.label;"
+ tooltiptext="&cmd.askToActivate.tooltip;"
+ command="cmd_askToActivateItem"/>
+ <menuitem id="detail-always-activate-menuitem"
+ class="addon-control"
+ label="&cmd.alwaysActivate.label;"
+ tooltiptext="&cmd.alwaysActivate.tooltip;"
+ command="cmd_alwaysActivateItem"/>
+ <menuitem id="detail-never-activate-menuitem"
+ class="addon-control"
+ label="&cmd.neverActivate.label;"
+ tooltiptext="&cmd.neverActivate.tooltip;"
+ command="cmd_neverActivateItem"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </vbox>
+ </hbox>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ </scrollbox>
+
+ </deck>
+
+ </box>
+ </hbox>
+
+</page>
diff --git a/components/addons/content/list.js b/components/addons/content/list.js
new file mode 100644
index 000000000..a31922703
--- /dev/null
+++ b/components/addons/content/list.js
@@ -0,0 +1,165 @@
+// -*- 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 kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const kDialog = "dialog";
+
+/**
+ * This dialog can be initialized from parameters supplied via window.arguments
+ * or it can be used to display blocklist notification and blocklist blocked
+ * installs via nsIDialogParamBlock as is done by nsIExtensionManager.
+ *
+ * When using this dialog with window.arguments it must be opened modally, the
+ * caller can inspect the user action after the dialog closes by inspecting the
+ * value of the |result| parameter on this object which is set to the dlgtype
+ * of the button used to close the dialog.
+ *
+ * window.arguments[0] is an array of strings to display in the tree. If the
+ * array is empty the tree will not be displayed.
+ * window.arguments[1] a JS Object with the following properties:
+ *
+ * title: A title string, to be displayed in the title bar of the dialog.
+ * message1: A message string, displayed above the addon list
+ * message2: A message string, displayed below the addon list
+ * message3: A bolded message string, displayed below the addon list
+ * moreInfoURL: An url for displaying more information
+ * iconClass : Can be one of the following values (default is alert-icon)
+ * alert-icon, error-icon, or question-icon
+ *
+ * If no value is supplied for message1, message2, message3, or moreInfoURL,
+ * the element is not displayed.
+ *
+ * buttons: {
+ * accept: { label: "A Label for the Accept button",
+ * focused: true },
+ * cancel: { label: "A Label for the Cancel button" },
+ * ...
+ * },
+ *
+ * result: The dlgtype of button that was used to dismiss the dialog.
+ */
+
+"use strict";
+
+var gButtons = { };
+
+function init() {
+ var de = document.documentElement;
+ var items = [];
+ if (window.arguments[0] instanceof Components.interfaces.nsIDialogParamBlock) {
+ // This is a warning about a blocklisted item the user is trying to install
+ var args = window.arguments[0];
+ var softblocked = args.GetInt(0) == 1 ? true : false;
+
+ var extensionsBundle = document.getElementById("extensionsBundle");
+ try {
+ var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter);
+ var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
+ }
+ catch (e) { }
+
+ var params = {
+ moreInfoURL: url,
+ };
+
+ if (softblocked) {
+ params.title = extensionsBundle.getString("softBlockedInstallTitle");
+ params.message1 = extensionsBundle.getFormattedString("softBlockedInstallMsg",
+ [args.GetString(0)]);
+ var accept = de.getButton("accept");
+ accept.label = extensionsBundle.getString("softBlockedInstallAcceptLabel");
+ accept.accessKey = extensionsBundle.getString("softBlockedInstallAcceptKey");
+ de.getButton("cancel").focus();
+ document.addEventListener("dialogaccept", allowInstall, false);
+ }
+ else {
+ params.title = extensionsBundle.getString("blocklistedInstallTitle2");
+ params.message1 = extensionsBundle.getFormattedString("blocklistedInstallMsg2",
+ [args.GetString(0)]);
+ de.buttons = "accept";
+ de.getButton("accept").focus();
+ }
+ }
+ else {
+ items = window.arguments[0];
+ params = window.arguments[1];
+ }
+
+ var addons = document.getElementById("addonsChildren");
+ if (items.length > 0)
+ document.getElementById("addonsTree").hidden = false;
+
+ // Fill the addons list
+ for (var item of items) {
+ var treeitem = document.createElementNS(kXULNS, "treeitem");
+ var treerow = document.createElementNS(kXULNS, "treerow");
+ var treecell = document.createElementNS(kXULNS, "treecell");
+ treecell.setAttribute("label", item);
+ treerow.appendChild(treecell);
+ treeitem.appendChild(treerow);
+ addons.appendChild(treeitem);
+ }
+
+ // Set the messages
+ var messages = ["message1", "message2", "message3"];
+ for (let messageEntry of messages) {
+ if (messageEntry in params) {
+ var message = document.getElementById(messageEntry);
+ message.hidden = false;
+ message.appendChild(document.createTextNode(params[messageEntry]));
+ }
+ }
+
+ document.getElementById("infoIcon").className =
+ params["iconClass"] ? "spaced " + params["iconClass"] : "spaced alert-icon";
+
+ if ("moreInfoURL" in params && params["moreInfoURL"]) {
+ message = document.getElementById("moreInfo");
+ message.value = extensionsBundle.getString("moreInfoText");
+ message.setAttribute("href", params["moreInfoURL"]);
+ document.getElementById("moreInfoBox").hidden = false;
+ }
+
+ // Set the window title
+ if ("title" in params)
+ document.title = params.title;
+
+ // Set up the buttons
+ if ("buttons" in params) {
+ gButtons = params.buttons;
+ var buttonString = "";
+ for (var buttonType in gButtons)
+ buttonString += "," + buttonType;
+ de.buttons = buttonString.substr(1);
+ for (buttonType in gButtons) {
+ var button = de.getButton(buttonType);
+ button.label = gButtons[buttonType].label;
+ if (gButtons[buttonType].focused)
+ button.focus();
+ document.addEventListener(kDialog + buttonType, handleButtonCommand, true);
+ }
+ }
+}
+
+function shutdown() {
+ for (var buttonType in gButtons)
+ document.removeEventListener(kDialog + buttonType, handleButtonCommand, true);
+}
+
+function allowInstall() {
+ var args = window.arguments[0];
+ args.SetInt(1, 1);
+}
+
+/**
+ * Watch for the user hitting one of the buttons to dismiss the dialog
+ * and report the result back to the caller through the |result| property on
+ * the arguments object.
+ */
+function handleButtonCommand(event) {
+ window.arguments[1].result = event.type.substr(kDialog.length);
+}
diff --git a/components/addons/content/list.xul b/components/addons/content/list.xul
new file mode 100644
index 000000000..65efeb6a2
--- /dev/null
+++ b/components/addons/content/list.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://global/skin/"?>
+
+<dialog id="addonList" windowtype="Addons:List"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onunload="shutdown();"
+ buttons="accept,cancel" onload="init();">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://mozapps/content/extensions/list.js"/>
+
+ <stringbundle id="extensionsBundle"
+ src="chrome://mozapps/locale/extensions/extensions.properties"/>
+ <stringbundle id="brandBundle"
+ src="chrome://branding/locale/brand.properties"/>
+
+ <hbox align="start">
+ <vbox>
+ <image id="infoIcon"/>
+ </vbox>
+ <vbox class="spaced" style="min-width: 20em; max-width: 40em">
+ <label id="message1" class="spaced" hidden="true"/>
+ <separator class="thin"/>
+ <tree id="addonsTree" rows="6" hidecolumnpicker="true" hidden="true" class="spaced">
+ <treecols style="max-width: 25em;">
+ <treecol flex="1" id="nameColumn" hideheader="true"/>
+ </treecols>
+ <treechildren id="addonsChildren"/>
+ </tree>
+ <label id="message2" class="spaced" hidden="true"/>
+ <label class="bold spaced" id="message3" hidden="true"/>
+ <hbox id="moreInfoBox" hidden="true">
+ <label id="moreInfo" class="text-link spaced"/>
+ <spacer flex="1"/>
+ </hbox>
+ </vbox>
+ </hbox>
+</dialog>
diff --git a/components/addons/content/newaddon.js b/components/addons/content/newaddon.js
new file mode 100644
index 000000000..aab556a62
--- /dev/null
+++ b/components/addons/content/newaddon.js
@@ -0,0 +1,129 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Services.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+
+var gAddon = null;
+
+// If the user enables the add-on through some other UI close this window
+var EnableListener = {
+ onEnabling: function(aAddon) {
+ if (aAddon.id == gAddon.id)
+ window.close();
+ }
+}
+AddonManager.addAddonListener(EnableListener);
+
+function initialize() {
+ // About URIs don't implement nsIURL so we have to find the query string
+ // manually
+ let spec = document.location.href;
+ let pos = spec.indexOf("?");
+ let query = "";
+ if (pos >= 0)
+ query = spec.substring(pos + 1);
+
+ // Just assume the query is "id=<id>"
+ let id = query.substring(3);
+ if (!id) {
+ window.location = "about:blank";
+ return;
+ }
+
+ let bundle = Services.strings.createBundle("chrome://mozapps/locale/extensions/newaddon.properties");
+
+ AddonManager.getAddonByID(id, function(aAddon) {
+ // If the add-on doesn't exist or it is already enabled or it has already
+ // been seen or it cannot be enabled then this UI is useless, just close it.
+ // This shouldn't normally happen unless session restore restores the tab.
+ if (!aAddon || !aAddon.userDisabled ||
+ !(aAddon.permissions & AddonManager.PERM_CAN_ENABLE)) {
+ window.close();
+ return;
+ }
+
+ gAddon = aAddon;
+
+ document.getElementById("addon-info").setAttribute("type", aAddon.type);
+
+ let icon = document.getElementById("icon");
+ if (aAddon.icon64URL)
+ icon.src = aAddon.icon64URL;
+ else if (aAddon.iconURL)
+ icon.src = aAddon.iconURL;
+
+ let name = bundle.formatStringFromName("name", [aAddon.name, aAddon.version],
+ 2);
+ document.getElementById("name").value = name;
+
+ if (aAddon.creator) {
+ let creator = bundle.formatStringFromName("author", [aAddon.creator], 1);
+ document.getElementById("author").value = creator;
+ } else {
+ document.getElementById("author").hidden = true;
+ }
+
+ let uri = "getResourceURI" in aAddon ? aAddon.getResourceURI() : null;
+ let locationLabel = document.getElementById("location");
+ if (uri instanceof Ci.nsIFileURL) {
+ let location = bundle.formatStringFromName("location", [uri.file.path], 1);
+ locationLabel.value = location;
+ locationLabel.setAttribute("tooltiptext", location);
+ } else {
+ document.getElementById("location").hidden = true;
+ }
+
+ var event = document.createEvent("Events");
+ event.initEvent("AddonDisplayed", true, true);
+ document.dispatchEvent(event);
+ });
+}
+
+function unload() {
+ AddonManager.removeAddonListener(EnableListener);
+}
+
+function continueClicked() {
+ AddonManager.removeAddonListener(EnableListener);
+
+ if (document.getElementById("allow").checked) {
+ gAddon.userDisabled = false;
+
+ if (gAddon.pendingOperations & AddonManager.PENDING_ENABLE) {
+ document.getElementById("allow").disabled = true;
+ document.getElementById("buttonDeck").selectedPanel = document.getElementById("restartPanel");
+ return;
+ }
+ }
+
+ window.close();
+}
+
+function restartClicked() {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+ if (cancelQuit.data)
+ return; // somebody canceled our quit request
+
+ window.close();
+
+ let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
+ getService(Components.interfaces.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+}
+
+function cancelClicked() {
+ gAddon.userDisabled = true;
+ AddonManager.addAddonListener(EnableListener);
+
+ document.getElementById("allow").disabled = false;
+ document.getElementById("buttonDeck").selectedPanel = document.getElementById("continuePanel");
+}
diff --git a/components/addons/content/newaddon.xul b/components/addons/content/newaddon.xul
new file mode 100644
index 000000000..0806f2799
--- /dev/null
+++ b/components/addons/content/newaddon.xul
@@ -0,0 +1,66 @@
+<?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://mozapps/skin/extensions/newaddon.css"?>
+
+<!DOCTYPE page [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % newaddonDTD SYSTEM "chrome://mozapps/locale/extensions/newaddon.dtd">
+%newaddonDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml" title="&title;"
+ disablefastfind="true" id="addon-page" onload="initialize()"
+ onunload="unload()" role="application" align="stretch" pack="stretch">
+
+ <xhtml:link rel="shortcut icon" style="display: none"
+ href="chrome://mozapps/skin/extensions/extensionGeneric-16.png"/>
+
+ <script type="application/javascript"
+ src="chrome://mozapps/content/extensions/newaddon.js"/>
+
+ <scrollbox id="addon-scrollbox" align="center">
+ <spacer id="spacer-start"/>
+
+ <vbox id="addon-container" class="main-content">
+ <description>&intro;</description>
+
+ <hbox id="addon-info">
+ <image id="icon"/>
+ <vbox flex="1">
+ <label id="name"/>
+ <label id="author"/>
+ <label id="location" crop="end"/>
+ </vbox>
+ </hbox>
+
+ <hbox id="warning">
+ <image id="warning-icon"/>
+ <description flex="1">&warning;</description>
+ </hbox>
+
+ <checkbox id="allow" label="&allow;"/>
+ <description id="later">&later;</description>
+
+ <deck id="buttonDeck">
+ <hbox id="continuePanel">
+ <button id="continue-button" label="&continue;"
+ oncommand="continueClicked()"/>
+ </hbox>
+ <hbox id="restartPanel">
+ <spacer id="restartSpacer"/>
+ <description id="restartMessage" flex="1">&restartMessage;</description>
+ <button id="restart-button" label="&restartButton;" oncommand="restartClicked()"/>
+ <button id="cancel-button" label="&cancelButton;" oncommand="cancelClicked()"/>
+ </hbox>
+ </deck>
+ </vbox>
+
+ <spacer id="spacer-end"/>
+ </scrollbox>
+</page>
diff --git a/components/addons/content/pluginPrefs.xul b/components/addons/content/pluginPrefs.xul
new file mode 100644
index 000000000..c3fdbfa5b
--- /dev/null
+++ b/components/addons/content/pluginPrefs.xul
@@ -0,0 +1,20 @@
+<?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 window SYSTEM "chrome://pluginproblem/locale/pluginproblem.dtd">
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <setting type="control" title="&plugin.file;">
+ <label class="text-list" id="pluginLibraries"/>
+ </setting>
+ <setting type="control" title="&plugin.mimeTypes;">
+ <label class="text-list" id="pluginMimeTypes"/>
+ </setting>
+ <setting type="bool" pref="dom.ipc.plugins.flash.disable-protected-mode"
+ inverted="true" title="&plugin.flashProtectedMode.label;"
+ id="pluginEnableProtectedMode"
+ learnmore="https://support.mozilla.org/kb/flash-protected-mode-settings" />
+</vbox>
diff --git a/components/addons/content/selectAddons.css b/components/addons/content/selectAddons.css
new file mode 100644
index 000000000..636757721
--- /dev/null
+++ b/components/addons/content/selectAddons.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/. */
+
+#select .addon {
+ -moz-binding: url("chrome://mozapps/content/extensions/selectAddons.xml#addon-select");
+}
+
+#confirm .addon {
+ -moz-binding: url("chrome://mozapps/content/extensions/selectAddons.xml#addon-confirm");
+}
+
+#select-scrollbox,
+#confirm-scrollbox {
+ overflow-y: auto;
+ -moz-box-orient: vertical;
+}
+
+.addon:not([optionalupdate]) .addon-action-update,
+.addon[optionalupdate] .addon-action-message {
+ display: none;
+}
diff --git a/components/addons/content/selectAddons.js b/components/addons/content/selectAddons.js
new file mode 100644
index 000000000..01b9030ee
--- /dev/null
+++ b/components/addons/content/selectAddons.js
@@ -0,0 +1,347 @@
+// -*- 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/. */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/AddonManager.jsm");
+Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+var gView = null;
+
+function showView(aView) {
+ gView = aView;
+
+ gView.show();
+
+ // If the view's show method immediately showed a different view then don't
+ // do anything else
+ if (gView != aView)
+ return;
+
+ let viewNode = document.getElementById(gView.nodeID);
+ viewNode.parentNode.selectedPanel = viewNode;
+
+ // For testing dispatch an event when the view changes
+ var event = document.createEvent("Events");
+ event.initEvent("ViewChanged", true, true);
+ viewNode.dispatchEvent(event);
+}
+
+function showButtons(aCancel, aBack, aNext, aDone) {
+ document.getElementById("cancel").hidden = !aCancel;
+ document.getElementById("back").hidden = !aBack;
+ document.getElementById("next").hidden = !aNext;
+ document.getElementById("done").hidden = !aDone;
+}
+
+function isAddonDistroInstalled(aID) {
+ let branch = Services.prefs.getBranch("extensions.installedDistroAddon.");
+ if (!branch.prefHasUserValue(aID))
+ return false;
+
+ return branch.getBoolPref(aID);
+}
+
+function orderForScope(aScope) {
+ return aScope == AddonManager.SCOPE_PROFILE ? 1 : 0;
+}
+
+var gAddons = {};
+
+var gChecking = {
+ nodeID: "checking",
+
+ _progress: null,
+ _addonCount: 0,
+ _completeCount: 0,
+
+ show: function gChecking_show() {
+ showButtons(true, false, false, false);
+ this._progress = document.getElementById("checking-progress");
+
+ AddonManager.getAllAddons(aAddons => {
+ if (aAddons.length == 0) {
+ window.close();
+ return;
+ }
+
+ aAddons = aAddons.filter(function gChecking_filterAddons(aAddon) {
+ if (aAddon.type == "plugin" || aAddon.type == "service")
+ return false;
+
+ if (aAddon.type == "theme") {
+ // Don't show application shipped themes
+ if (aAddon.scope == AddonManager.SCOPE_APPLICATION)
+ return false;
+ // Don't show already disabled themes
+ if (aAddon.userDisabled)
+ return false;
+ }
+
+ return true;
+ });
+
+ this._addonCount = aAddons.length;
+ this._progress.value = 0;
+ this._progress.max = aAddons.length;
+ this._progress.mode = "determined";
+
+ AddonRepository.repopulateCache().then(() => {
+ for (let addonItem of aAddons) {
+ // Ignore disabled themes
+ if (addonItem.type != "theme" || !addonItem.userDisabled) {
+ gAddons[addonItem.id] = {
+ addon: addonItem,
+ install: null,
+ wasActive: addonItem.isActive
+ }
+ }
+
+ addonItem.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
+ }
+ });
+ });
+ },
+
+ onUpdateAvailable: function gChecking_onUpdateAvailable(aAddon, aInstall) {
+ // If the add-on can be upgraded then remember the new version
+ if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE)
+ gAddons[aAddon.id].install = aInstall;
+ },
+
+ onUpdateFinished: function gChecking_onUpdateFinished(aAddon, aError) {
+ this._completeCount++;
+ this._progress.value = this._completeCount;
+
+ if (this._completeCount < this._addonCount)
+ return;
+
+ // Tycho: var addons = [gAddons[id] for (id in gAddons)];
+ var addons = [];
+ for (let id in gAddons) {
+ addons.push(gAddons[id])
+ }
+
+ addons.sort(function sortAddons(a, b) {
+ let orderA = orderForScope(a.addon.scope);
+ let orderB = orderForScope(b.addon.scope);
+
+ if (orderA != orderB)
+ return orderA - orderB;
+
+ return String.localeCompare(a.addon.name, b.addon.name);
+ });
+
+ let rows = document.getElementById("select-rows");
+ let lastAddon = null;
+ for (let entry of addons) {
+ if (lastAddon &&
+ orderForScope(entry.addon.scope) != orderForScope(lastAddon.scope)) {
+ let separator = document.createElement("separator");
+ rows.appendChild(separator);
+ }
+
+ let row = document.createElement("row");
+ row.setAttribute("id", entry.addon.id);
+ row.setAttribute("class", "addon");
+ rows.appendChild(row);
+ row.setAddon(entry.addon, entry.install, entry.wasActive,
+ isAddonDistroInstalled(entry.addon.id));
+
+ if (entry.install)
+ entry.install.addListener(gUpdate);
+
+ lastAddon = entry.addon;
+ }
+
+ showView(gSelect);
+ }
+};
+
+var gSelect = {
+ nodeID: "select",
+
+ show: function gSelect_show() {
+ this.updateButtons();
+ },
+
+ updateButtons: function gSelect_updateButtons() {
+ for (let row = document.getElementById("select-rows").firstChild;
+ row; row = row.nextSibling) {
+ if (row.localName == "separator")
+ continue;
+
+ if (row.action) {
+ showButtons(false, false, true, false);
+ return;
+ }
+ }
+
+ showButtons(false, false, false, true);
+ },
+
+ next: function gSelect_next() {
+ showView(gConfirm);
+ },
+
+ done: function gSelect_done() {
+ window.close();
+ }
+};
+
+var gConfirm = {
+ nodeID: "confirm",
+
+ show: function gConfirm_show() {
+ showButtons(false, true, false, true);
+
+ let box = document.getElementById("confirm-scrollbox").firstChild;
+ while (box) {
+ box.hidden = true;
+ while (box.lastChild != box.firstChild)
+ box.removeChild(box.lastChild);
+ box = box.nextSibling;
+ }
+
+ for (let row = document.getElementById("select-rows").firstChild;
+ row; row = row.nextSibling) {
+ if (row.localName == "separator")
+ continue;
+
+ let action = row.action;
+ if (!action)
+ continue;
+
+ let list = document.getElementById(action + "-list");
+
+ list.hidden = false;
+ let item = document.createElement("hbox");
+ item.setAttribute("id", row._addon.id);
+ item.setAttribute("class", "addon");
+ item.setAttribute("type", row._addon.type);
+ item.setAttribute("name", row._addon.name);
+ if (action == "update" || action == "enable")
+ item.setAttribute("active", "true");
+ list.appendChild(item);
+
+ if (action == "update")
+ showButtons(false, true, true, false);
+ }
+ },
+
+ back: function gConfirm_back() {
+ showView(gSelect);
+ },
+
+ next: function gConfirm_next() {
+ showView(gUpdate);
+ },
+
+ done: function gConfirm_done() {
+ for (let row = document.getElementById("select-rows").firstChild;
+ row; row = row.nextSibling) {
+ if (row.localName != "separator")
+ row.apply();
+ }
+
+ window.close();
+ }
+}
+
+var gUpdate = {
+ nodeID: "update",
+
+ _progress: null,
+ _waitingCount: 0,
+ _completeCount: 0,
+ _errorCount: 0,
+
+ show: function gUpdate_show() {
+ showButtons(true, false, false, false);
+
+ this._progress = document.getElementById("update-progress");
+
+ for (let row = document.getElementById("select-rows").firstChild;
+ row; row = row.nextSibling) {
+ if (row.localName != "separator")
+ row.apply();
+ }
+
+ this._progress.mode = "determined";
+ this._progress.max = this._waitingCount;
+ this._progress.value = this._completeCount;
+ },
+
+ checkComplete: function gUpdate_checkComplete() {
+ this._progress.value = this._completeCount;
+ if (this._completeCount < this._waitingCount)
+ return;
+
+ if (this._errorCount > 0) {
+ showView(gErrors);
+ return;
+ }
+
+ window.close();
+ },
+
+ onDownloadStarted: function gUpdate_onDownloadStarted(aInstall) {
+ this._waitingCount++;
+ },
+
+ onDownloadFailed: function gUpdate_onDownloadFailed(aInstall) {
+ this._errorCount++;
+ this._completeCount++;
+ this.checkComplete();
+ },
+
+ onInstallFailed: function gUpdate_onInstallFailed(aInstall) {
+ this._errorCount++;
+ this._completeCount++;
+ this.checkComplete();
+ },
+
+ onInstallEnded: function gUpdate_onInstallEnded(aInstall) {
+ this._completeCount++;
+ this.checkComplete();
+ }
+};
+
+var gErrors = {
+ nodeID: "errors",
+
+ show: function gErrors_show() {
+ showButtons(false, false, false, true);
+ },
+
+ done: function gErrors_done() {
+ window.close();
+ }
+};
+
+window.addEventListener("load", function loadEventListener() {
+ showView(gChecking); }, false);
+
+// When closing the window cancel any pending or in-progress installs
+window.addEventListener("unload", function unloadEventListener() {
+ for (let id in gAddons) {
+ let entry = gAddons[id];
+ if (!entry.install)
+ return;
+
+ aEntry.install.removeListener(gUpdate);
+
+ if (entry.install.state != AddonManager.STATE_INSTALLED &&
+ entry.install.state != AddonManager.STATE_DOWNLOAD_FAILED &&
+ entry.install.state != AddonManager.STATE_INSTALL_FAILED) {
+ entry.install.cancel();
+ }
+ }
+}, false);
diff --git a/components/addons/content/selectAddons.xml b/components/addons/content/selectAddons.xml
new file mode 100644
index 000000000..dbfc0d400
--- /dev/null
+++ b/components/addons/content/selectAddons.xml
@@ -0,0 +1,235 @@
+<?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 window [
+<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/selectAddons.dtd">
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%updateDTD;
+%brandDTD;
+]>
+
+<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="addon-select">
+ <content>
+ <xul:hbox class="select-keep select-cell">
+ <xul:checkbox class="addon-keep-checkbox" anonid="keep"
+ xbl:inherits="tooltiptext=name"
+ oncommand="document.getBindingParent(this).keepChanged();"/>
+ </xul:hbox>
+ <xul:hbox class="select-icon select-cell">
+ <xul:image class="addon-icon" xbl:inherits="type"/>
+ </xul:hbox>
+ <xul:hbox class="select-name select-cell">
+ <xul:label class="addon-name" crop="end" style="&select.name.style;"
+ xbl:inherits="xbl:text=name"/>
+ </xul:hbox>
+ <xul:hbox class="select-action select-cell">
+ <xul:label class="addon-action-message" style="&select.action.style;"
+ xbl:inherits="xbl:text=action"/>
+ <xul:checkbox anonid="update" checked="true" class="addon-action-update"
+ oncommand="document.getBindingParent(this).updateChanged();"
+ style="&select.action.style;" xbl:inherits="label=action"/>
+ </xul:hbox>
+ <xul:hbox class="select-source select-cell">
+ <xul:label class="addon-source" xbl:inherits="xbl:text=source"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <field name="_addon"/>
+ <field name="_disabled"/>
+ <field name="_install"/>
+ <field name="_wasActive"/>
+ <field name="_keep">document.getAnonymousElementByAttribute(this, "anonid", "keep");</field>
+ <field name="_update">document.getAnonymousElementByAttribute(this, "anonid", "update");</field>
+ <field name="_strings">document.getElementById("strings");</field>
+
+ <property name="action" readonly="true">
+ <getter><![CDATA[
+ if (!this._keep.checked) {
+ if (this._wasActive)
+ return "disable";
+ else
+ return null;
+ }
+
+ if (this._addon.appDisabled && !this._install)
+ return "incompatible";
+
+ if (this._install && (AddonManager.shouldAutoUpdate(this._addon) || this._update.checked))
+ return "update";
+
+ if (this._addon.permissions & AddonManager.PERM_CAN_ENABLE)
+ return "enable";
+
+ return null;
+ ]]></getter>
+ </property>
+
+ <method name="setAddon">
+ <parameter name="aAddon"/>
+ <parameter name="aInstall"/>
+ <parameter name="aWasActive"/>
+ <parameter name="aDistroInstalled"/>
+ <body><![CDATA[
+ this._addon = aAddon;
+ this._install = aInstall;
+ this._wasActive = aWasActive;
+
+ this.setAttribute("name", aAddon.name);
+ this.setAttribute("type", aAddon.type);
+
+ // User and bundled add-ons default to staying enabled,
+ // others default to disabled.
+ switch (aAddon.scope) {
+ case AddonManager.SCOPE_PROFILE:
+ this._keep.checked = !aAddon.userDisabled;
+ if (aDistroInstalled)
+ this.setAttribute("source", this._strings.getString("source.bundled"));
+ else
+ this.setAttribute("source", this._strings.getString("source.profile"));
+ break;
+ default:
+ this._keep.checked = false;
+ this.setAttribute("source", this._strings.getString("source.other"));
+ }
+
+ this.updateAction();
+ ]]></body>
+ </method>
+
+ <method name="setActionMessage">
+ <body><![CDATA[
+ if (!this._keep.checked) {
+ // If the user no longer wants this add-on then it is either being
+ // disabled or nothing is changing. Don't complicate matters by
+ // talking about updates for this case
+
+ if (this._wasActive)
+ this.setAttribute("action", this._strings.getString("action.disabled"));
+ else
+ this.setAttribute("action", "");
+
+ this.removeAttribute("optionalupdate");
+ return;
+ }
+
+ if (this._addon.appDisabled && !this._install) {
+ // If the add-on is incompatible and there is no update available
+ // then it will remain disabled
+
+ this.setAttribute("action", this._strings.getString("action.incompatible"));
+
+ this.removeAttribute("optionalupdate");
+ return;
+ }
+
+ if (this._install) {
+ if (!AddonManager.shouldAutoUpdate(this._addon)) {
+ this.setAttribute("optionalupdate", "true");
+
+ // If manual updating for the add-on then display the right
+ // text depending on whether the update is required to make
+ // the add-on work or not
+ if (this._addon.appDisabled)
+ this.setAttribute("action", this._strings.getString("action.neededupdate"));
+ else
+ this.setAttribute("action", this._strings.getString("action.unneededupdate"));
+ return;
+ }
+
+ this.removeAttribute("optionalupdate");
+
+ // If the update is needed to make the add-on compatible then
+ // say so otherwise just say nothing about it
+ if (this._addon.appDisabled) {
+ this.setAttribute("action", this._strings.getString("action.autoupdate"));
+ return;
+ }
+ }
+ else {
+ this.removeAttribute("optionalupdate");
+ }
+
+ // If the add-on didn't used to be active and it now is (via a
+ // compatibility update) or it can be enabled then the action is to
+ // enable the add-on
+ if (!this._wasActive && (this._addon.isActive || this._addon.permissions & AddonManager.PERM_CAN_ENABLE)) {
+ this.setAttribute("action", this._strings.getString("action.enabled"));
+ return;
+ }
+
+ // In all other cases the add-on is simply remaining enabled
+ this.setAttribute("action", "");
+ ]]></body>
+ </method>
+
+ <method name="updateAction">
+ <body><![CDATA[
+ this.setActionMessage();
+ let installingUpdate = this._install &&
+ (AddonManager.shouldAutoUpdate(this._addon) ||
+ this._update.checked);
+
+ if (this._keep.checked && (!this._addon.appDisabled || installingUpdate))
+ this.setAttribute("active", "true");
+ else
+ this.removeAttribute("active");
+
+ gSelect.updateButtons();
+ ]]></body>
+ </method>
+
+ <method name="updateChanged">
+ <body><![CDATA[
+ this.updateAction();
+ ]]></body>
+ </method>
+
+ <method name="keepChanged">
+ <body><![CDATA[
+ this.updateAction();
+ ]]></body>
+ </method>
+
+ <method name="keep">
+ <body><![CDATA[
+ this._keep.checked = true;
+ this.keepChanged();
+ ]]></body>
+ </method>
+
+ <method name="disable">
+ <body><![CDATA[
+ this._keep.checked = false;
+ this.keepChanged();
+ ]]></body>
+ </method>
+
+ <method name="apply">
+ <body><![CDATA[
+ this._addon.userDisabled = !this._keep.checked;
+
+ if (!this._install || !this._keep.checked)
+ return;
+
+ if (AddonManager.shouldAutoUpdate(this._addon) || this._update.checked)
+ this._install.install();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="addon-confirm">
+ <content>
+ <xul:image class="addon-icon" xbl:inherits="type"/>
+ <xul:label class="addon-name" xbl:inherits="xbl:text=name"/>
+ </content>
+ </binding>
+</bindings>
diff --git a/components/addons/content/selectAddons.xul b/components/addons/content/selectAddons.xul
new file mode 100644
index 000000000..0fa292ecf
--- /dev/null
+++ b/components/addons/content/selectAddons.xul
@@ -0,0 +1,124 @@
+<?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://mozapps/content/extensions/selectAddons.css" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/selectAddons.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/selectAddons.dtd">
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%updateDTD;
+%brandDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="&upgrade.style;" id="select-window">
+
+ <script type="application/javascript" src="chrome://mozapps/content/extensions/selectAddons.js"/>
+ <stringbundle id="strings" src="chrome://mozapps/locale/extensions/selectAddons.properties"/>
+
+ <deck id="view-deck" flex="1" align="stretch" pack="stretch">
+ <vbox id="checking" align="stretch">
+ <vbox flex="1">
+ <label id="checking-heading" class="heading">&checking.heading;</label>
+ </vbox>
+ <progressmeter id="checking-progress" class="progress" mode="undetermined"/>
+ <vbox flex="1">
+ <label id="checking-progress-label" class="progress-label">&checking.progress.label;</label>
+ </vbox>
+ </vbox>
+
+ <vbox id="select" align="stretch">
+ <label id="select-heading" class="heading">&select.heading;</label>
+
+ <description id="select-description">&select.description;</description>
+
+ <vbox id="select-list" align="stretch" flex="1">
+ <hbox id="select-header">
+ <hbox class="select-keep select-cell" style="&select.keep.style;">
+ <label value="&select.keep;"/>
+ </hbox>
+ <hbox class="select-icon select-cell"/>
+ <hbox id="heading-name" class="select-name select-cell" style="&select.name.style;">
+ <label value="&select.name;"/>
+ </hbox>
+ <hbox id="heading-action" class="select-action select-cell" style="&select.action.style;">
+ <label value="&select.action;"/>
+ </hbox>
+ <hbox class="select-source select-cell" flex="1">
+ <label value="&select.source;"/>
+ </hbox>
+ </hbox>
+
+ <scrollbox id="select-scrollbox" flex="1">
+ <grid id="select-grid" flex="1">
+ <columns>
+ <column style="&select.keep.style;"/>
+ <column/>
+ <column id="column-name"/>
+ <column id="column-action" class="select-action"/>
+ <column class="select-source" flex="1"/>
+ </columns>
+
+ <rows id="select-rows"/>
+ </grid>
+ </scrollbox>
+ </vbox>
+ </vbox>
+
+ <vbox id="confirm" align="stretch">
+ <label id="confirm-heading" class="heading">&confirm.heading;</label>
+
+ <description id="confirm-description">&confirm.description;</description>
+
+ <scrollbox id="confirm-scrollbox" flex="1">
+ <vbox id="disable-list" class="action-list" hidden="true">
+ <label class="action-header">&action.disable.heading;</label>
+ </vbox>
+
+ <vbox id="incompatible-list" class="action-list" hidden="true">
+ <label class="action-header">&action.incompatible.heading;</label>
+ </vbox>
+
+ <vbox id="update-list" class="action-list" hidden="true">
+ <label class="action-header">&action.update.heading;</label>
+ </vbox>
+
+ <vbox id="enable-list" class="action-list" hidden="true">
+ <label class="action-header">&action.enable.heading;</label>
+ </vbox>
+ </scrollbox>
+ </vbox>
+
+ <vbox id="update" align="stretch">
+ <vbox flex="1">
+ <label id="update-heading" class="heading">&update.heading;</label>
+ </vbox>
+ <progressmeter id="update-progress" class="progress" mode="undetermined"/>
+ <vbox flex="1">
+ <label id="update-progress-label" class="progress-label">&update.progress.label;</label>
+ </vbox>
+ </vbox>
+
+ <vbox id="errors">
+ <vbox flex="1">
+ <label id="errors-heading" class="heading">&errors.heading;</label>
+ </vbox>
+ <description id="errors-description" value="&errors.description;"/>
+ <spacer flex="1"/>
+ </vbox>
+ </deck>
+
+ <hbox id="footer" align="center">
+ <label id="footer-label" flex="1">&footer.label;</label>
+ <button id="cancel" label="&cancel.label;" oncommand="window.close()"/>
+ <button id="back" label="&back.label;" oncommand="gView.back()" hidden="true"/>
+ <button id="next" label="&next.label;" oncommand="gView.next()" hidden="true"/>
+ <button id="done" label="&done.label;" oncommand="gView.done()" hidden="true"/>
+ </hbox>
+
+</window>
diff --git a/components/addons/content/setting.xml b/components/addons/content/setting.xml
new file mode 100644
index 000000000..c4eae1fd3
--- /dev/null
+++ b/components/addons/content/setting.xml
@@ -0,0 +1,508 @@
+<?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 page [
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/extensions.dtd">
+%extensionsDTD;
+]>
+
+<bindings 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="settings">
+ <content orient="vertical">
+ <xul:label class="settings-title" xbl:inherits="xbl:text=label" flex="1"/>
+ <children/>
+ </content>
+ </binding>
+
+ <binding id="setting-base">
+ <implementation>
+ <constructor><![CDATA[
+ this.preferenceChanged();
+
+ this.addEventListener("keypress", function(event) {
+ event.stopPropagation();
+ }, false);
+
+ if (this.usePref)
+ Services.prefs.addObserver(this.pref, this._observer, true);
+ ]]></constructor>
+
+ <field name="_observer"><![CDATA[({
+ _self: this,
+
+ QueryInterface: function(aIID) {
+ const Ci = Components.interfaces;
+ if (aIID.equals(Ci.nsIObserver) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE);
+ },
+
+ observe: function(aSubject, aTopic, aPrefName) {
+ if (aTopic != "nsPref:changed")
+ return;
+
+ if (this._self.pref == aPrefName)
+ this._self.preferenceChanged();
+ }
+ })]]>
+ </field>
+
+ <method name="fireEvent">
+ <parameter name="eventName"/>
+ <parameter name="funcStr"/>
+ <body>
+ <![CDATA[
+ let body = funcStr || this.getAttribute(eventName);
+ if (!body)
+ return;
+
+ try {
+ let event = document.createEvent("Events");
+ event.initEvent(eventName, true, true);
+ let f = new Function("event", body);
+ f.call(this, event);
+ }
+ catch (e) {
+ Cu.reportError(e);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ // Should be code to set the from the preference input.value
+ throw Components.Exception("No valueFromPreference implementation",
+ Components.results.NS_ERROR_NOT_IMPLEMENTED);
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ // Should be code to set the input.value from the preference
+ throw Components.Exception("No valueToPreference implementation",
+ Components.results.NS_ERROR_NOT_IMPLEMENTED);
+ ]]>
+ </body>
+ </method>
+
+ <method name="inputChanged">
+ <body>
+ <![CDATA[
+ if (this.usePref && !this._updatingInput) {
+ this.valueToPreference();
+ this.fireEvent("oninputchanged");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="preferenceChanged">
+ <body>
+ <![CDATA[
+ if (this.usePref) {
+ this._updatingInput = true;
+ try {
+ this.valueFromPreference();
+ this.fireEvent("onpreferencechanged");
+ } catch (e) {}
+ this._updatingInput = false;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="usePref" readonly="true" onget="return this.hasAttribute('pref');"/>
+ <property name="pref" readonly="true" onget="return this.getAttribute('pref');"/>
+ <property name="type" readonly="true" onget="return this.getAttribute('type');"/>
+ <property name="value" onget="return this.input.value;" onset="return this.input.value = val;"/>
+
+ <field name="_updatingInput">false</field>
+ <field name="input">document.getAnonymousElementByAttribute(this, "anonid", "input");</field>
+ <field name="settings">
+ this.parentNode.localName == "settings" ? this.parentNode : null;
+ </field>
+ </implementation>
+ </binding>
+
+ <binding id="setting-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ <xul:label class="preferences-learnmore text-link"
+ onclick="document.getBindingParent(this).openLearnMore()">&setting.learnmore;</xul:label>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:checkbox anonid="input" xbl:inherits="disabled,onlabel,offlabel,label=checkboxlabel" oncommand="inputChanged();"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ let val = Services.prefs.getBoolPref(this.pref);
+ this.value = this.inverted ? !val : val;
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ let val = this.value;
+ Services.prefs.setBoolPref(this.pref, this.inverted ? !val : val);
+ ]]>
+ </body>
+ </method>
+
+ <property name="value" onget="return this.input.checked;" onset="return this.input.setChecked(val);"/>
+ <property name="inverted" readonly="true" onget="return this.getAttribute('inverted');"/>
+
+ <method name="openLearnMore">
+ <body>
+ <![CDATA[
+ window.open(this.getAttribute("learnmore"), "_blank");
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="setting-boolint" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool">
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ let val = Services.prefs.getIntPref(this.pref);
+ this.value = (val == this.getAttribute("on"));
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ Services.prefs.setIntPref(this.pref, this.getAttribute(this.value ? "on" : "off"));
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="setting-localized-bool" extends="chrome://mozapps/content/extensions/setting.xml#setting-bool">
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ let val = Services.prefs.getComplexValue(this.pref, Components.interfaces.nsIPrefLocalizedString).data;
+ if(this.inverted) val = !val;
+ this.value = (val == "true");
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ let val = this.value;
+ if(this.inverted) val = !val;
+ let pref = Components.classes["@mozilla.org/pref-localizedstring;1"].createInstance(Components.interfaces.nsIPrefLocalizedString);
+ pref.data = this.inverted ? (!val).toString() : val.toString();
+ Services.prefs.setComplexValue(this.pref, Components.interfaces.nsIPrefLocalizedString, pref);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="setting-integer" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:textbox type="number" anonid="input" oninput="inputChanged();" onchange="inputChanged();"
+ xbl:inherits="disabled,emptytext,min,max,increment,hidespinbuttons,wraparound,size"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ let val = Services.prefs.getIntPref(this.pref);
+ this.value = val;
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ Services.prefs.setIntPref(this.pref, this.value);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="setting-control" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <children/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="setting-string" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:textbox anonid="input" flex="1" oninput="inputChanged();"
+ xbl:inherits="disabled,emptytext,type=inputtype,min,max,increment,hidespinbuttons,decimalplaces,wraparound"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ const nsISupportsString = Components.interfaces.nsISupportsString;
+ this.value = Services.prefs.getComplexValue(this.pref, nsISupportsString).data;
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ const nsISupportsString = Components.interfaces.nsISupportsString;
+ let iss = Components.classes["@mozilla.org/supports-string;1"].createInstance(nsISupportsString);
+ iss.data = this.value;
+ Services.prefs.setComplexValue(this.pref, nsISupportsString, iss);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="setting-color" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:colorpicker type="button" anonid="input" xbl:inherits="disabled" onchange="document.getBindingParent(this).inputChanged();"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ // We must wait for the colorpicker's binding to be applied before setting the value
+ if (!this.input.color)
+ this.input.initialize();
+ this.value = Services.prefs.getCharPref(this.pref);
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ Services.prefs.setCharPref(this.pref, this.value);
+ ]]>
+ </body>
+ </method>
+
+ <property name="value" onget="return this.input.color;" onset="return this.input.color = val;"/>
+ </implementation>
+ </binding>
+
+ <binding id="setting-path" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:button type="button" anonid="button" label="&settings.path.button.label;" xbl:inherits="disabled" oncommand="showPicker();"/>
+ <xul:label anonid="input" flex="1" crop="center" xbl:inherits="disabled"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <method name="showPicker">
+ <body>
+ <![CDATA[
+ var filePicker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ filePicker.init(window, this.getAttribute("title"),
+ this.type == "file" ? Ci.nsIFilePicker.modeOpen : Ci.nsIFilePicker.modeGetFolder);
+ if (this.value) {
+ try {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(this.value);
+ filePicker.displayDirectory = this.type == "file" ? file.parent : file;
+ if (this.type == "file") {
+ filePicker.defaultString = file.leafName;
+ }
+ } catch (e) {}
+ }
+ if (filePicker.show() != Ci.nsIFilePicker.returnCancel) {
+ this.value = filePicker.file.path;
+ this.inputChanged();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ this.value = Services.prefs.getCharPref(this.pref);
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ Services.prefs.setCharPref(this.pref, this.value);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_value"></field>
+
+ <property name="value">
+ <getter>
+ <![CDATA[
+ return this._value;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this._value = val;
+ let label = "";
+ if (val) {
+ try {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(val);
+ label = this.hasAttribute("fullpath") ? file.path : file.leafName;
+ } catch (e) {}
+ }
+ this.input.tooltipText = val;
+ return this.input.value = label;
+ ]]>
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="setting-multi" extends="chrome://mozapps/content/extensions/setting.xml#setting-base">
+ <content>
+ <xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <xul:label class="preferences-title" flex="1" xbl:inherits="xbl:text=title"/>
+ </xul:hbox>
+ <xul:description class="preferences-description" flex="1" xbl:inherits="xbl:text=desc"/>
+ </xul:vbox>
+ <xul:hbox class="preferences-alignment">
+ <children includes="radiogroup|menulist"/>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ this.control.addEventListener("command", this.inputChanged.bind(this), false);
+ ]]>
+ </constructor>
+
+ <method name="valueFromPreference">
+ <body>
+ <![CDATA[
+ let val;
+ switch (Services.prefs.getPrefType(this.pref)) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ val = Services.prefs.getCharPref(this.pref);
+ break;
+ case Ci.nsIPrefBranch.PREF_INT:
+ val = Services.prefs.getIntPref(this.pref);
+ break;
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ val = Services.prefs.getBoolPref(this.pref).toString();
+ break;
+ default:
+ return;
+ }
+
+ if ("itemCount" in this.control) {
+ for (let i = 0; i < this.control.itemCount; i++) {
+ if (this.control.getItemAtIndex(i).value == val) {
+ this.control.selectedIndex = i;
+ break;
+ }
+ }
+ } else {
+ this.control.setAttribute("value", val);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueToPreference">
+ <body>
+ <![CDATA[
+ // We might not have a pref already set, so we guess the type from the value attribute
+ let val = this.control.selectedItem.value;
+ if (val == "true" || val == "false")
+ Services.prefs.setBoolPref(this.pref, val == "true");
+ else if (/^-?\d+$/.test(val))
+ Services.prefs.setIntPref(this.pref, val);
+ else
+ Services.prefs.setCharPref(this.pref, val);
+ ]]>
+ </body>
+ </method>
+
+ <field name="control">this.getElementsByTagName(this.getAttribute("type") == "radio" ? "radiogroup" : "menulist")[0];</field>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/components/addons/content/update.js b/components/addons/content/update.js
new file mode 100644
index 000000000..98495b426
--- /dev/null
+++ b/components/addons/content/update.js
@@ -0,0 +1,691 @@
+// -*- 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/. */
+
+// This UI is only opened from the Extension Manager when the app is upgraded.
+
+"use strict";
+
+const PREF_UPDATE_EXTENSIONS_ENABLED = "extensions.update.enabled";
+const PREF_XPINSTALL_ENABLED = "xpinstall.enabled";
+
+// timeout (in milliseconds) to wait for response to the metadata ping
+const METADATA_TIMEOUT = 30000;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", "resource://gre/modules/addons/AddonRepository.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Log", "resource://gre/modules/Log.jsm");
+var logger = null;
+
+var gUpdateWizard = {
+ // When synchronizing app compatibility info this contains all installed
+ // add-ons. When checking for compatible versions this contains only
+ // incompatible add-ons.
+ addons: [],
+ // Contains a Set of IDs for add-on that were disabled by the application update.
+ affectedAddonIDs: null,
+ // The add-ons that we found updates available for
+ addonsToUpdate: [],
+ shouldSuggestAutoChecking: false,
+ shouldAutoCheck: false,
+ xpinstallEnabled: true,
+ xpinstallLocked: false,
+ // cached AddonInstall entries for add-ons we might want to update,
+ // keyed by add-on ID
+ addonInstalls: new Map(),
+ shuttingDown: false,
+ // Count the add-ons disabled by this update, enabled/disabled by
+ // metadata checks, and upgraded.
+ disabled: 0,
+ metadataEnabled: 0,
+ metadataDisabled: 0,
+ upgraded: 0,
+ upgradeFailed: 0,
+ upgradeDeclined: 0,
+
+ init: function gUpdateWizard_init()
+ {
+ logger = Log.repository.getLogger("addons.update-dialog");
+ // XXX could we pass the addons themselves rather than the IDs?
+ this.affectedAddonIDs = new Set(window.arguments[0]);
+
+ try {
+ this.shouldSuggestAutoChecking =
+ !Services.prefs.getBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED);
+ }
+ catch (e) {
+ }
+
+ try {
+ this.xpinstallEnabled = Services.prefs.getBoolPref(PREF_XPINSTALL_ENABLED);
+ this.xpinstallLocked = Services.prefs.prefIsLocked(PREF_XPINSTALL_ENABLED);
+ }
+ catch (e) {
+ }
+
+ if (Services.io.offline)
+ document.documentElement.currentPage = document.getElementById("offline");
+ else
+ document.documentElement.currentPage = document.getElementById("versioninfo");
+ },
+
+ onWizardFinish: function gUpdateWizard_onWizardFinish ()
+ {
+ if (this.shouldSuggestAutoChecking)
+ Services.prefs.setBoolPref(PREF_UPDATE_EXTENSIONS_ENABLED, this.shouldAutoCheck);
+ },
+
+ _setUpButton: function gUpdateWizard_setUpButton(aButtonID, aButtonKey, aDisabled)
+ {
+ var strings = document.getElementById("updateStrings");
+ var button = document.documentElement.getButton(aButtonID);
+ if (aButtonKey) {
+ button.label = strings.getString(aButtonKey);
+ try {
+ button.setAttribute("accesskey", strings.getString(aButtonKey + "Accesskey"));
+ }
+ catch (e) {
+ }
+ }
+ button.disabled = aDisabled;
+ },
+
+ setButtonLabels: function gUpdateWizard_setButtonLabels(aBackButton, aBackButtonIsDisabled,
+ aNextButton, aNextButtonIsDisabled,
+ aCancelButton, aCancelButtonIsDisabled)
+ {
+ this._setUpButton("back", aBackButton, aBackButtonIsDisabled);
+ this._setUpButton("next", aNextButton, aNextButtonIsDisabled);
+ this._setUpButton("cancel", aCancelButton, aCancelButtonIsDisabled);
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Update Errors
+ errorItems: [],
+
+ checkForErrors: function gUpdateWizard_checkForErrors(aElementIDToShow)
+ {
+ if (this.errorItems.length > 0)
+ document.getElementById(aElementIDToShow).hidden = false;
+ },
+
+ onWizardClose: function gUpdateWizard_onWizardClose(aEvent)
+ {
+ return this.onWizardCancel();
+ },
+
+ onWizardCancel: function gUpdateWizard_onWizardCancel()
+ {
+ gUpdateWizard.shuttingDown = true;
+ // Allow add-ons to continue downloading and installing
+ // in the background, though some may require a later restart
+ // Pages that are waiting for user input go into the background
+ // on cancel
+ if (gMismatchPage.waiting) {
+ logger.info("Dialog closed in mismatch page");
+ if (gUpdateWizard.addonInstalls.size > 0) {
+ // Tycho: gInstallingPage.startInstalls([i for ([, i] of gUpdateWizard.addonInstalls)]);
+ let results = [];
+ for (let [, i] of gUpdateWizard.addonInstalls) {
+ results.push(i);
+ }
+
+ gInstallingPage.startInstalls(results);
+ }
+ return true;
+ }
+
+ // Pages that do asynchronous things will just keep running and check
+ // gUpdateWizard.shuttingDown to trigger background behaviour
+ if (!gInstallingPage.installing) {
+ logger.info("Dialog closed while waiting for updated compatibility information");
+ }
+ else {
+ logger.info("Dialog closed while downloading and installing updates");
+ }
+ return true;
+ }
+};
+
+var gOfflinePage = {
+ onPageAdvanced: function gOfflinePage_onPageAdvanced()
+ {
+ Services.io.offline = false;
+ return true;
+ },
+
+ toggleOffline: function gOfflinePage_toggleOffline()
+ {
+ var nextbtn = document.documentElement.getButton("next");
+ nextbtn.disabled = !nextbtn.disabled;
+ }
+}
+
+// Addon listener to count addons enabled/disabled by metadata checks
+var listener = {
+ onDisabled: function listener_onDisabled(aAddon) {
+ gUpdateWizard.affectedAddonIDs.add(aAddon.id);
+ gUpdateWizard.metadataDisabled++;
+ },
+ onEnabled: function listener_onEnabled(aAddon) {
+ gUpdateWizard.affectedAddonIDs.delete(aAddon.id);
+ gUpdateWizard.metadataEnabled++;
+ }
+};
+
+var gVersionInfoPage = {
+ _completeCount: 0,
+ _totalCount: 0,
+ _versionInfoDone: false,
+ onPageShow: Task.async(function* gVersionInfoPage_onPageShow() {
+ gUpdateWizard.setButtonLabels(null, true,
+ "nextButtonText", true,
+ "cancelButtonText", false);
+
+ gUpdateWizard.disabled = gUpdateWizard.affectedAddonIDs.size;
+
+ // Ensure compatibility overrides are up to date before checking for
+ // individual addon updates.
+ AddonManager.addAddonListener(listener);
+ if (AddonRepository.isMetadataStale()) {
+ // Do the metadata ping, listening for any newly enabled/disabled add-ons.
+ yield AddonRepository.repopulateCache(METADATA_TIMEOUT);
+ if (gUpdateWizard.shuttingDown) {
+ logger.debug("repopulateCache completed after dialog closed");
+ }
+ }
+ // Fetch the add-ons that are still affected by this update.
+ // Tycho: let idlist = [id for (id of gUpdateWizard.affectedAddonIDs)];
+
+ let idlist = [];
+ for (let id of gUpdateWizard.affectedAddonIDs) {
+ idlist.push(id);
+ }
+
+ if (idlist.length < 1) {
+ gVersionInfoPage.onAllUpdatesFinished();
+ return;
+ }
+
+ logger.debug("Fetching affected addons " + idlist.toSource());
+ let fetchedAddons = yield new Promise((resolve, reject) =>
+ AddonManager.getAddonsByIDs(idlist, resolve));
+ // We shouldn't get nulls here, but let's be paranoid...
+ // Tycho: gUpdateWizard.addons = [a for (a of fetchedAddons) if (a)];
+ let results = [];
+ for (let a of fetchedAddons) {
+ if (a) {
+ results.push(a);
+ }
+ }
+
+ gUpdateWizard.addons = results;
+
+ if (gUpdateWizard.addons.length < 1) {
+ gVersionInfoPage.onAllUpdatesFinished();
+ return;
+ }
+
+ gVersionInfoPage._totalCount = gUpdateWizard.addons.length;
+
+ for (let addon of gUpdateWizard.addons) {
+ logger.debug("VersionInfo Finding updates for ${id}", addon);
+ addon.findUpdates(gVersionInfoPage, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
+ }
+ }),
+
+ onAllUpdatesFinished: function gVersionInfoPage_onAllUpdatesFinished() {
+ AddonManager.removeAddonListener(listener);
+ // Filter out any add-ons that are now enabled.
+ // Tycho:
+ // logger.debug("VersionInfo updates finished: found " +
+ // [addon.id + ":" + addon.appDisabled for (addon of gUpdateWizard.addons)].toSource());
+
+ let logDisabledAddons = [];
+ for (let addon of gUpdateWizard.addons) {
+ if (addon.appDisabled) {
+ logDisabledAddons.push(addon.id + ":" + addon.appDisabled);
+ }
+ }
+ logger.debug("VersionInfo updates finished: found " + logDisabledAddons.toSource());
+
+ let filteredAddons = [];
+ for (let a of gUpdateWizard.addons) {
+ if (a.appDisabled) {
+ logger.debug("Continuing with add-on " + a.id);
+ filteredAddons.push(a);
+ }
+ else if (gUpdateWizard.addonInstalls.has(a.id)) {
+ gUpdateWizard.addonInstalls.get(a.id).cancel();
+ gUpdateWizard.addonInstalls.delete(a.id);
+ }
+ }
+ gUpdateWizard.addons = filteredAddons;
+
+ if (gUpdateWizard.shuttingDown) {
+ // jump directly to updating auto-update add-ons in the background
+ if (gUpdateWizard.addonInstalls.size > 0) {
+ // Tycho: gInstallingPage.startInstalls([i for ([, i] of gUpdateWizard.addonInstalls)]);
+ let results = [];
+ for (let [, i] of gUpdateWizard.addonInstalls) {
+ results.push(i);
+ }
+
+ gInstallingPage.startInstalls(results);
+ }
+ return;
+ }
+
+ if (filteredAddons.length > 0) {
+ if (!gUpdateWizard.xpinstallEnabled && gUpdateWizard.xpinstallLocked) {
+ document.documentElement.currentPage = document.getElementById("adminDisabled");
+ return;
+ }
+ document.documentElement.currentPage = document.getElementById("mismatch");
+ }
+ else {
+ logger.info("VersionInfo: No updates require further action");
+ // VersionInfo compatibility updates resolved all compatibility problems,
+ // close this window and continue starting the application...
+ //XXX Bug 314754 - We need to use setTimeout to close the window due to
+ // the EM using xmlHttpRequest when checking for updates.
+ setTimeout(close, 0);
+ }
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // UpdateListener
+ onUpdateFinished: function gVersionInfoPage_onUpdateFinished(aAddon, status) {
+ ++this._completeCount;
+
+ if (status != AddonManager.UPDATE_STATUS_NO_ERROR) {
+ logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount +
+ " failed for " + aAddon.id + ": " + status);
+ gUpdateWizard.errorItems.push(aAddon);
+ }
+ else {
+ logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount +
+ " finished for " + aAddon.id);
+ }
+
+ // If we're not in the background, just make a list of add-ons that have
+ // updates available
+ if (!gUpdateWizard.shuttingDown) {
+ // If we're still in the update check window and the add-on is now active
+ // then it won't have been disabled by startup
+ if (aAddon.active) {
+ AddonManagerPrivate.removeStartupChange(AddonManager.STARTUP_CHANGE_DISABLED, aAddon.id);
+ gUpdateWizard.metadataEnabled++;
+ }
+
+ // Update the status text and progress bar
+ var updateStrings = document.getElementById("updateStrings");
+ var statusElt = document.getElementById("versioninfo.status");
+ var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]);
+ statusElt.setAttribute("value", statusString);
+
+ // Update the status text and progress bar
+ var progress = document.getElementById("versioninfo.progress");
+ progress.mode = "normal";
+ progress.value = Math.ceil((this._completeCount / this._totalCount) * 100);
+ }
+
+ if (this._completeCount == this._totalCount)
+ this.onAllUpdatesFinished();
+ },
+
+ onUpdateAvailable: function gVersionInfoPage_onUpdateAvailable(aAddon, aInstall) {
+ logger.debug("VersionInfo got an install for " + aAddon.id + ": " + aAddon.version);
+ gUpdateWizard.addonInstalls.set(aAddon.id, aInstall);
+ },
+};
+
+var gMismatchPage = {
+ waiting: false,
+
+ onPageShow: function gMismatchPage_onPageShow()
+ {
+ gMismatchPage.waiting = true;
+ gUpdateWizard.setButtonLabels(null, true,
+ "mismatchCheckNow", false,
+ "mismatchDontCheck", false);
+ document.documentElement.getButton("next").focus();
+
+ var incompatible = document.getElementById("mismatch.incompatible");
+ for (let addon of gUpdateWizard.addons) {
+ var listitem = document.createElement("listitem");
+ listitem.setAttribute("label", addon.name + " " + addon.version);
+ incompatible.appendChild(listitem);
+ }
+ }
+};
+
+var gUpdatePage = {
+ _totalCount: 0,
+ _completeCount: 0,
+ onPageShow: function gUpdatePage_onPageShow()
+ {
+ gMismatchPage.waiting = false;
+ gUpdateWizard.setButtonLabels(null, true,
+ "nextButtonText", true,
+ "cancelButtonText", false);
+ document.documentElement.getButton("next").focus();
+
+ gUpdateWizard.errorItems = [];
+
+ this._totalCount = gUpdateWizard.addons.length;
+ for (let addon of gUpdateWizard.addons) {
+ logger.debug("UpdatePage requesting update for " + addon.id);
+ // Redundant call to find updates again here when we already got them
+ // in the VersionInfo page: https://bugzilla.mozilla.org/show_bug.cgi?id=960597
+ addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
+ }
+ },
+
+ onAllUpdatesFinished: function gUpdatePage_onAllUpdatesFinished() {
+ if (gUpdateWizard.shuttingDown)
+ return;
+
+ var nextPage = document.getElementById("noupdates");
+ if (gUpdateWizard.addonsToUpdate.length > 0)
+ nextPage = document.getElementById("found");
+ document.documentElement.currentPage = nextPage;
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // UpdateListener
+ onUpdateAvailable: function gUpdatePage_onUpdateAvailable(aAddon, aInstall) {
+ logger.debug("UpdatePage got an update for " + aAddon.id + ": " + aAddon.version);
+ gUpdateWizard.addonsToUpdate.push(aInstall);
+ },
+
+ onUpdateFinished: function gUpdatePage_onUpdateFinished(aAddon, status) {
+ if (status != AddonManager.UPDATE_STATUS_NO_ERROR)
+ gUpdateWizard.errorItems.push(aAddon);
+
+ ++this._completeCount;
+
+ if (!gUpdateWizard.shuttingDown) {
+ // Update the status text and progress bar
+ var updateStrings = document.getElementById("updateStrings");
+ var statusElt = document.getElementById("checking.status");
+ var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]);
+ statusElt.setAttribute("value", statusString);
+
+ var progress = document.getElementById("checking.progress");
+ progress.value = Math.ceil((this._completeCount / this._totalCount) * 100);
+ }
+
+ if (this._completeCount == this._totalCount)
+ this.onAllUpdatesFinished()
+ },
+};
+
+var gFoundPage = {
+ onPageShow: function gFoundPage_onPageShow()
+ {
+ gUpdateWizard.setButtonLabels(null, true,
+ "installButtonText", false,
+ null, false);
+
+ var foundUpdates = document.getElementById("found.updates");
+ var itemCount = gUpdateWizard.addonsToUpdate.length;
+ for (let install of gUpdateWizard.addonsToUpdate) {
+ let listItem = foundUpdates.appendItem(install.name + " " + install.version);
+ listItem.setAttribute("type", "checkbox");
+ listItem.setAttribute("checked", "true");
+ listItem.install = install;
+ }
+
+ if (!gUpdateWizard.xpinstallEnabled) {
+ document.getElementById("xpinstallDisabledAlert").hidden = false;
+ document.getElementById("enableXPInstall").focus();
+ document.documentElement.getButton("next").disabled = true;
+ }
+ else {
+ document.documentElement.getButton("next").focus();
+ document.documentElement.getButton("next").disabled = false;
+ }
+ },
+
+ toggleXPInstallEnable: function gFoundPage_toggleXPInstallEnable(aEvent)
+ {
+ var enabled = aEvent.target.checked;
+ gUpdateWizard.xpinstallEnabled = enabled;
+ var pref = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ pref.setBoolPref(PREF_XPINSTALL_ENABLED, enabled);
+ this.updateNextButton();
+ },
+
+ updateNextButton: function gFoundPage_updateNextButton()
+ {
+ if (!gUpdateWizard.xpinstallEnabled) {
+ document.documentElement.getButton("next").disabled = true;
+ return;
+ }
+
+ var oneChecked = false;
+ var foundUpdates = document.getElementById("found.updates");
+ var updates = foundUpdates.getElementsByTagName("listitem");
+ for (let update of updates) {
+ if (!update.checked)
+ continue;
+ oneChecked = true;
+ break;
+ }
+
+ gUpdateWizard.setButtonLabels(null, true,
+ "installButtonText", true,
+ null, false);
+ document.getElementById("found").setAttribute("next", "installing");
+ document.documentElement.getButton("next").disabled = !oneChecked;
+ }
+};
+
+var gInstallingPage = {
+ _installs : [],
+ _errors : [],
+ _strings : null,
+ _currentInstall : -1,
+ _installing : false,
+
+ // Initialize fields we need for installing and tracking progress,
+ // and start iterating through the installations
+ startInstalls: function gInstallingPage_startInstalls(aInstallList) {
+ if (!gUpdateWizard.xpinstallEnabled) {
+ return;
+ }
+
+ // Tycho:
+ // logger.debug("Start installs for "
+ // + [i.existingAddon.id for (i of aInstallList)].toSource());
+
+ let logInstallAddons = [];
+ for (let i of aInstallList) {
+ logInstallAddons.push(i.existingAddon.id);
+ }
+ logger.debug("Start installs for " + logInstallAddons.toSource());
+
+ this._errors = [];
+ this._installs = aInstallList;
+ this._installing = true;
+ this.startNextInstall();
+ },
+
+ onPageShow: function gInstallingPage_onPageShow()
+ {
+ gUpdateWizard.setButtonLabels(null, true,
+ "nextButtonText", true,
+ null, true);
+
+ var foundUpdates = document.getElementById("found.updates");
+ var updates = foundUpdates.getElementsByTagName("listitem");
+ let toInstall = [];
+ for (let update of updates) {
+ if (!update.checked) {
+ logger.info("User chose to cancel update of " + update.label);
+ gUpdateWizard.upgradeDeclined++;
+ update.install.cancel();
+ continue;
+ }
+ toInstall.push(update.install);
+ }
+ this._strings = document.getElementById("updateStrings");
+
+ this.startInstalls(toInstall);
+ },
+
+ startNextInstall: function gInstallingPage_startNextInstall() {
+ if (this._currentInstall >= 0) {
+ this._installs[this._currentInstall].removeListener(this);
+ }
+
+ this._currentInstall++;
+
+ if (this._installs.length == this._currentInstall) {
+ Services.obs.notifyObservers(null, "TEST:all-updates-done", null);
+ this._installing = false;
+ if (gUpdateWizard.shuttingDown) {
+ return;
+ }
+ var nextPage = this._errors.length > 0 ? "installerrors" : "finished";
+ document.getElementById("installing").setAttribute("next", nextPage);
+ document.documentElement.advance();
+ return;
+ }
+
+ let install = this._installs[this._currentInstall];
+
+ if (gUpdateWizard.shuttingDown && !AddonManager.shouldAutoUpdate(install.existingAddon)) {
+ logger.debug("Don't update " + install.existingAddon.id + " in background");
+ gUpdateWizard.upgradeDeclined++;
+ install.cancel();
+ this.startNextInstall();
+ return;
+ }
+ install.addListener(this);
+ install.install();
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // InstallListener
+ onDownloadStarted: function gInstallingPage_onDownloadStarted(aInstall) {
+ if (gUpdateWizard.shuttingDown) {
+ return;
+ }
+ var strings = document.getElementById("updateStrings");
+ var label = strings.getFormattedString("downloadingPrefix", [aInstall.name]);
+ var actionItem = document.getElementById("actionItem");
+ actionItem.value = label;
+ },
+
+ onDownloadProgress: function gInstallingPage_onDownloadProgress(aInstall) {
+ if (gUpdateWizard.shuttingDown) {
+ return;
+ }
+ var downloadProgress = document.getElementById("downloadProgress");
+ downloadProgress.value = Math.ceil(100 * aInstall.progress / aInstall.maxProgress);
+ },
+
+ onDownloadEnded: function gInstallingPage_onDownloadEnded(aInstall) {
+ },
+
+ onDownloadFailed: function gInstallingPage_onDownloadFailed(aInstall) {
+ this._errors.push(aInstall);
+
+ gUpdateWizard.upgradeFailed++;
+ this.startNextInstall();
+ },
+
+ onInstallStarted: function gInstallingPage_onInstallStarted(aInstall) {
+ if (gUpdateWizard.shuttingDown) {
+ return;
+ }
+ var strings = document.getElementById("updateStrings");
+ var label = strings.getFormattedString("installingPrefix", [aInstall.name]);
+ var actionItem = document.getElementById("actionItem");
+ actionItem.value = label;
+ },
+
+ onInstallEnded: function gInstallingPage_onInstallEnded(aInstall, aAddon) {
+ if (!gUpdateWizard.shuttingDown) {
+ // Remember that this add-on was updated during startup
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
+ aAddon.id);
+ }
+
+ gUpdateWizard.upgraded++;
+ this.startNextInstall();
+ },
+
+ onInstallFailed: function gInstallingPage_onInstallFailed(aInstall) {
+ this._errors.push(aInstall);
+
+ gUpdateWizard.upgradeFailed++;
+ this.startNextInstall();
+ }
+};
+
+var gInstallErrorsPage = {
+ onPageShow: function gInstallErrorsPage_onPageShow()
+ {
+ gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
+ document.documentElement.getButton("finish").focus();
+ },
+};
+
+// Displayed when there are incompatible add-ons and the xpinstall.enabled
+// pref is false and locked.
+var gAdminDisabledPage = {
+ onPageShow: function gAdminDisabledPage_onPageShow()
+ {
+ gUpdateWizard.setButtonLabels(null, true, null, true,
+ "cancelButtonText", true);
+ document.documentElement.getButton("finish").focus();
+ }
+};
+
+// Displayed when selected add-on updates have been installed without error.
+// There can still be add-ons that are not compatible and don't have an update.
+var gFinishedPage = {
+ onPageShow: function gFinishedPage_onPageShow()
+ {
+ gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
+ document.documentElement.getButton("finish").focus();
+
+ if (gUpdateWizard.shouldSuggestAutoChecking) {
+ document.getElementById("finishedCheckDisabled").hidden = false;
+ gUpdateWizard.shouldAutoCheck = true;
+ }
+ else
+ document.getElementById("finishedCheckEnabled").hidden = false;
+
+ document.documentElement.getButton("finish").focus();
+ }
+};
+
+// Displayed when there are incompatible add-ons and there are no available
+// updates.
+var gNoUpdatesPage = {
+ onPageShow: function gNoUpdatesPage_onPageLoad(aEvent)
+ {
+ gUpdateWizard.setButtonLabels(null, true, null, true, null, true);
+ if (gUpdateWizard.shouldSuggestAutoChecking) {
+ document.getElementById("noupdatesCheckDisabled").hidden = false;
+ gUpdateWizard.shouldAutoCheck = true;
+ }
+ else
+ document.getElementById("noupdatesCheckEnabled").hidden = false;
+
+ gUpdateWizard.checkForErrors("updateCheckErrorNotFound");
+ document.documentElement.getButton("finish").focus();
+ }
+};
diff --git a/components/addons/content/update.xul b/components/addons/content/update.xul
new file mode 100644
index 000000000..9f5f35196
--- /dev/null
+++ b/components/addons/content/update.xul
@@ -0,0 +1,180 @@
+<?xml version="1.0"?>
+
+<!-- -*- Mode: XML; 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://mozapps/skin/extensions/update.css" type="text/css"?>
+
+<!DOCTYPE wizard [
+<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/update.dtd">
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%updateDTD;
+%brandDTD;
+]>
+
+<wizard id="updateWizard"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&updateWizard.title;"
+ windowtype="Addons:Compatibility"
+ branded="true"
+ onload="gUpdateWizard.init();"
+ onwizardfinish="gUpdateWizard.onWizardFinish();"
+ onwizardcancel="return gUpdateWizard.onWizardCancel();"
+ onclose="return gUpdateWizard.onWizardClose(event);"
+ buttons="accept,cancel">
+
+ <script type="application/javascript" src="chrome://mozapps/content/extensions/update.js"/>
+
+ <stringbundleset id="updateSet">
+ <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="updateStrings" src="chrome://mozapps/locale/extensions/update.properties"/>
+ </stringbundleset>
+
+ <wizardpage id="dummy" pageid="dummy"/>
+
+ <wizardpage id="offline" pageid="offline" next="versioninfo"
+ label="&offline.title;"
+ onpageadvanced="return gOfflinePage.onPageAdvanced();">
+ <description>&offline.description;</description>
+ <checkbox id="toggleOffline"
+ checked="true"
+ label="&offline.toggleOffline.label;"
+ accesskey="&offline.toggleOffline.accesskey;"
+ oncommand="gOfflinePage.toggleOffline();"/>
+ </wizardpage>
+
+ <wizardpage id="versioninfo" pageid="versioninfo" next="mismatch"
+ label="&versioninfo.wizard.title;"
+ onpageshow="gVersionInfoPage.onPageShow();">
+ <label>&versioninfo.top.label;</label>
+ <separator class="thin"/>
+ <progressmeter id="versioninfo.progress" mode="undetermined"/>
+ <hbox align="center">
+ <image id="versioninfo.throbber" class="throbber"/>
+ <label flex="1" id="versioninfo.status" crop="right">&versioninfo.waiting;</label>
+ </hbox>
+ <separator/>
+ </wizardpage>
+
+ <wizardpage id="mismatch" pageid="mismatch" next="checking"
+ label="&mismatch.win.title;"
+ onpageshow="gMismatchPage.onPageShow();">
+ <label>&mismatch.top.label;</label>
+ <separator class="thin"/>
+ <listbox id="mismatch.incompatible" flex="1"/>
+ <separator class="thin"/>
+ <label>&mismatch.bottom.label;</label>
+ </wizardpage>
+
+ <wizardpage id="checking" pageid="checking" next="noupdates"
+ label="&checking.wizard.title;"
+ onpageshow="gUpdatePage.onPageShow();">
+ <label>&checking.top.label;</label>
+ <separator class="thin"/>
+ <progressmeter id="checking.progress"/>
+ <hbox align="center">
+ <image id="checking.throbber" class="throbber"/>
+ <label id="checking.status" flex="1" crop="right">&checking.status;</label>
+ </hbox>
+ </wizardpage>
+
+ <wizardpage id="noupdates" pageid="noupdates"
+ label="&noupdates.wizard.title;"
+ onpageshow="gNoUpdatesPage.onPageShow();">
+ <description>&noupdates.intro.desc;</description>
+ <separator class="thin"/>
+ <hbox id="updateCheckErrorNotFound" class="alertBox" hidden="true" align="top">
+ <image id="alert"/>
+ <description flex="1">&noupdates.error.desc;</description>
+ </hbox>
+ <separator class="thin"/>
+ <description id="noupdatesCheckEnabled" hidden="true">
+ &noupdates.checkEnabled.desc;
+ </description>
+ <vbox id="noupdatesCheckDisabled" hidden="true">
+ <description>&finished.checkDisabled.desc;</description>
+ <checkbox label="&enableChecking.label;" checked="true"
+ oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
+ </vbox>
+ <separator flex="1"/>
+ <label>&clickFinish.label;</label>
+ <separator class="thin"/>
+ </wizardpage>
+
+ <wizardpage id="found" pageid="found" next="installing"
+ label="&found.wizard.title;"
+ onpageshow="gFoundPage.onPageShow();">
+ <label>&found.top.label;</label>
+ <separator class="thin"/>
+ <listbox id="found.updates" flex="1" seltype="multiple"
+ onclick="gFoundPage.updateNextButton();"/>
+ <separator class="thin"/>
+ <vbox align="left" id="xpinstallDisabledAlert" hidden="true">
+ <description>&found.disabledXPinstall.label;</description>
+ <checkbox label="&found.enableXPInstall.label;"
+ id="enableXPInstall"
+ accesskey="&found.enableXPInstall.accesskey;"
+ oncommand="gFoundPage.toggleXPInstallEnable(event);"/>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="installing" pageid="installing" next="finished"
+ label="&installing.wizard.title;"
+ onpageshow="gInstallingPage.onPageShow();">
+ <label>&installing.top.label;</label>
+ <progressmeter id="downloadProgress"/>
+ <hbox align="center">
+ <image id="installing.throbber" class="throbber"/>
+ <label id="actionItem" flex="1" crop="right"/>
+ </hbox>
+ <separator/>
+ </wizardpage>
+
+ <wizardpage id="installerrors" pageid="installerrors"
+ label="&installerrors.wizard.title;"
+ onpageshow="gInstallErrorsPage.onPageShow();">
+ <hbox align="top" class="alertBox">
+ <description flex="1">&installerrors.intro.label;</description>
+ </hbox>
+ <separator flex="1"/>
+ <label>&clickFinish.label;</label>
+ <separator class="thin"/>
+ </wizardpage>
+
+ <wizardpage id="adminDisabled" pageid="adminDisabled"
+ label="&adminDisabled.wizard.title;"
+ onpageshow="gAdminDisabledPage.onPageShow();">
+ <separator/>
+ <hbox class="alertBox" align="top">
+ <image id="alert"/>
+ <description flex="1">&adminDisabled.warning.label;</description>
+ </hbox>
+ <separator flex="1"/>
+ <label>&clickFinish.label;</label>
+ <separator class="thin"/>
+ </wizardpage>
+
+ <wizardpage id="finished" pageid="finished"
+ label="&finished.wizard.title;"
+ onpageshow="gFinishedPage.onPageShow();">
+
+ <label>&finished.top.label;</label>
+ <separator/>
+ <description id="finishedCheckEnabled" hidden="true">
+ &finished.checkEnabled.desc;
+ </description>
+ <vbox id="finishedCheckDisabled" hidden="true">
+ <description>&finished.checkDisabled.desc;</description>
+ <checkbox label="&enableChecking.label;" checked="true"
+ oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
+ </vbox>
+ <separator flex="1"/>
+ <label>&clickFinish.label;</label>
+ <separator class="thin"/>
+ </wizardpage>
+
+</wizard>
+
diff --git a/components/addons/content/updateinfo.xsl b/components/addons/content/updateinfo.xsl
new file mode 100644
index 000000000..5fcccd6d7
--- /dev/null
+++ b/components/addons/content/updateinfo.xsl
@@ -0,0 +1,41 @@
+<?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/. -->
+
+<xsl:stylesheet version="1.0" xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <!-- Any elements not otherwise specified will be stripped but the contents
+ will be displayed. All attributes are stripped from copied elements. -->
+
+ <!-- Block these elements and their contents -->
+ <xsl:template match="xhtml:head|xhtml:script|xhtml:style">
+ </xsl:template>
+
+ <!-- Allowable styling elements -->
+ <xsl:template match="xhtml:b|xhtml:i|xhtml:em|xhtml:strong|xhtml:u|xhtml:q|xhtml:sub|xhtml:sup|xhtml:code">
+ <xsl:copy><xsl:apply-templates/></xsl:copy>
+ </xsl:template>
+
+ <!-- Allowable block formatting elements -->
+ <xsl:template match="xhtml:h1|xhtml:h2|xhtml:h3|xhtml:p|xhtml:div|xhtml:blockquote|xhtml:pre">
+ <xsl:copy><xsl:apply-templates/></xsl:copy>
+ </xsl:template>
+
+ <!-- Allowable list formatting elements -->
+ <xsl:template match="xhtml:ul|xhtml:ol|xhtml:li|xhtml:dl|xhtml:dt|xhtml:dd">
+ <xsl:copy><xsl:apply-templates/></xsl:copy>
+ </xsl:template>
+
+ <!-- These elements are copied and their contents dropped -->
+ <xsl:template match="xhtml:br|xhtml:hr">
+ <xsl:copy/>
+ </xsl:template>
+
+ <!-- The root document -->
+ <xsl:template match="/">
+ <xhtml:body><xsl:apply-templates/></xhtml:body>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/components/addons/content/xpinstallConfirm.css b/components/addons/content/xpinstallConfirm.css
new file mode 100644
index 000000000..583facfec
--- /dev/null
+++ b/components/addons/content/xpinstallConfirm.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/. */
+
+installitem {
+ -moz-binding: url("chrome://mozapps/content/xpinstall/xpinstallItem.xml#installitem");
+ display: -moz-box;
+}
diff --git a/components/addons/content/xpinstallConfirm.js b/components/addons/content/xpinstallConfirm.js
new file mode 100644
index 000000000..29be5f5e9
--- /dev/null
+++ b/components/addons/content/xpinstallConfirm.js
@@ -0,0 +1,192 @@
+// -*- 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 XPInstallConfirm = {};
+
+XPInstallConfirm.init = function()
+{
+ Components.utils.import("resource://gre/modules/AddonManager.jsm");
+
+ var _installCountdown;
+ var _installCountdownInterval;
+ var _focused;
+ var _timeout;
+
+ // Default to cancelling the install when the window unloads
+ XPInstallConfirm._installOK = false;
+
+ var bundle = document.getElementById("xpinstallConfirmStrings");
+
+ let args = window.arguments[0].wrappedJSObject;
+
+ // If all installs have already been cancelled in some way then just close
+ // the window
+ if (args.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
+ window.close();
+ return;
+ }
+
+ var _installCountdownLength = 5;
+ try {
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ var delay_in_milliseconds = prefs.getIntPref("security.dialog_enable_delay");
+ _installCountdownLength = Math.round(delay_in_milliseconds / 500);
+ } catch (ex) { }
+
+ var itemList = document.getElementById("itemList");
+
+ let installMap = new WeakMap();
+ let installListener = {
+ onDownloadCancelled: function(install) {
+ itemList.removeChild(installMap.get(install));
+ if (--numItemsToInstall == 0)
+ window.close();
+ }
+ };
+
+ var numItemsToInstall = args.installs.length;
+ for (let install of args.installs) {
+ var installItem = document.createElement("installitem");
+ itemList.appendChild(installItem);
+
+ installItem.name = install.addon.name;
+ installItem.url = install.sourceURI.spec;
+ var icon = install.iconURL;
+ if (icon)
+ installItem.icon = icon;
+ var type = install.type;
+ if (type)
+ installItem.type = type;
+ if (install.certName) {
+ installItem.cert = bundle.getFormattedString("signed", [install.certName]);
+ }
+ else {
+ installItem.cert = bundle.getString("unverified");
+ }
+ installItem.signed = install.certName ? "true" : "false";
+
+ installMap.set(install, installItem);
+ install.addListener(installListener);
+ }
+
+ var introString = bundle.getString("itemWarnIntroSingle");
+ if (numItemsToInstall > 4)
+ introString = bundle.getFormattedString("itemWarnIntroMultiple", [numItemsToInstall]);
+ var textNode = document.createTextNode(introString);
+ var introNode = document.getElementById("itemWarningIntro");
+ while (introNode.hasChildNodes())
+ introNode.removeChild(introNode.firstChild);
+ introNode.appendChild(textNode);
+
+ var okButton = document.documentElement.getButton("accept");
+ okButton.focus();
+
+ function okButtonCountdown() {
+ _installCountdown -= 1;
+
+ if (_installCountdown < 1) {
+ okButton.label = bundle.getString("installButtonLabel");
+ okButton.disabled = false;
+ clearInterval(_installCountdownInterval);
+ }
+ else
+ okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
+ }
+
+ function myfocus() {
+ // Clear the timeout if it exists so only the last one will be used.
+ if (_timeout)
+ clearTimeout(_timeout);
+
+ // Use setTimeout since the last focus or blur event to fire is the one we
+ // want
+ _timeout = setTimeout(setWidgetsAfterFocus, 0);
+ }
+
+ function setWidgetsAfterFocus() {
+ if (_focused)
+ return;
+
+ _installCountdown = _installCountdownLength;
+ _installCountdownInterval = setInterval(okButtonCountdown, 500);
+ okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
+ _focused = true;
+ }
+
+ function myblur() {
+ // Clear the timeout if it exists so only the last one will be used.
+ if (_timeout)
+ clearTimeout(_timeout);
+
+ // Use setTimeout since the last focus or blur event to fire is the one we
+ // want
+ _timeout = setTimeout(setWidgetsAfterBlur, 0);
+ }
+
+ function setWidgetsAfterBlur() {
+ if (!_focused)
+ return;
+
+ // Set _installCountdown to the inital value set in setWidgetsAfterFocus
+ // plus 1 so when the window is focused there is immediate ui feedback.
+ _installCountdown = _installCountdownLength + 1;
+ okButton.label = bundle.getFormattedString("installButtonDisabledLabel", [_installCountdown]);
+ okButton.disabled = true;
+ clearInterval(_installCountdownInterval);
+ _focused = false;
+ }
+
+ function myUnload() {
+ if (_installCountdownLength > 0) {
+ document.removeEventListener("focus", myfocus, true);
+ document.removeEventListener("blur", myblur, true);
+ }
+ window.removeEventListener("unload", myUnload, false);
+
+ for (let install of args.installs)
+ install.removeListener(installListener);
+
+ // Now perform the desired action - either install the
+ // addons or cancel the installations
+ if (XPInstallConfirm._installOK) {
+ for (let install of args.installs)
+ install.install();
+ }
+ else {
+ for (let install of args.installs) {
+ if (install.state != AddonManager.STATE_CANCELLED)
+ install.cancel();
+ }
+ }
+ }
+
+ window.addEventListener("unload", myUnload, false);
+
+ if (_installCountdownLength > 0) {
+ document.addEventListener("focus", myfocus, true);
+ document.addEventListener("blur", myblur, true);
+
+ okButton.disabled = true;
+ setWidgetsAfterFocus();
+ }
+ else
+ okButton.label = bundle.getString("installButtonLabel");
+}
+
+XPInstallConfirm.onOK = function()
+{
+ // Perform the install or cancel after the window has unloaded
+ XPInstallConfirm._installOK = true;
+ return true;
+}
+
+XPInstallConfirm.onCancel = function()
+{
+ // Perform the install or cancel after the window has unloaded
+ XPInstallConfirm._installOK = false;
+ return true;
+}
diff --git a/components/addons/content/xpinstallConfirm.xul b/components/addons/content/xpinstallConfirm.xul
new file mode 100644
index 000000000..f1c29eb73
--- /dev/null
+++ b/components/addons/content/xpinstallConfirm.xul
@@ -0,0 +1,37 @@
+<?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://mozapps/content/xpinstall/xpinstallConfirm.css" type="text/css"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/xpinstall/xpinstallConfirm.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://mozapps/locale/xpinstall/xpinstallConfirm.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="xpinstallConfirm" title="&dialog.title;" style="&dialog.style;"
+ windowtype="Addons:Install"
+ onload="XPInstallConfirm.init()"
+ ondialogaccept="return XPInstallConfirm.onOK();"
+ ondialogcancel="return XPInstallConfirm.onCancel();">
+
+ <script src="chrome://mozapps/content/xpinstall/xpinstallConfirm.js" type="application/javascript"/>
+
+ <stringbundle id="xpinstallConfirmStrings"
+ src="chrome://mozapps/locale/xpinstall/xpinstallConfirm.properties"/>
+
+ <vbox flex="1" id="dialogContentBox">
+ <hbox id="xpinstallheader" align="start">
+ <image class="alert-icon"/>
+ <vbox flex="1">
+ <description class="warning">&warningPrimary.label;</description>
+ <description>&warningSecondary.label;</description>
+ </vbox>
+ </hbox>
+ <label id="itemWarningIntro"/>
+ <vbox id="itemList" class="listbox" flex="1" style="overflow: auto;"/>
+ </vbox>
+
+</dialog>
diff --git a/components/addons/content/xpinstallItem.xml b/components/addons/content/xpinstallItem.xml
new file mode 100644
index 000000000..5146af84f
--- /dev/null
+++ b/components/addons/content/xpinstallItem.xml
@@ -0,0 +1,51 @@
+<?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 SYSTEM "chrome://mozapps/locale/xpinstall/xpinstallConfirm.dtd">
+
+<bindings id="xpinstallItemBindings"
+ 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="installitem">
+ <resources>
+ <stylesheet src="chrome://mozapps/skin/xpinstall/xpinstallConfirm.css"/>
+ </resources>
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox align="center" pack="center" class="xpinstallIconContainer">
+ <xul:image class="xpinstallItemIcon" xbl:inherits="src=icon"/>
+ </xul:vbox>
+ <xul:vbox flex="1" pack="center">
+ <xul:hbox class="xpinstallItemNameRow" align="center">
+ <xul:label class="xpinstallItemName" xbl:inherits="value=name" crop="right"/>
+ <xul:label class="xpinstallItemSigned" xbl:inherits="value=cert,signed"/>
+ </xul:hbox>
+ <xul:hbox class="xpinstallItemDetailsRow" align="center">
+ <xul:textbox class="xpinstallItemURL" xbl:inherits="value=url" flex="1" readonly="true" crop="right"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ <implementation>
+ <property name="name" onset="this.setAttribute('name', val); return val;"
+ onget="return this.getAttribute('name');"/>
+ <property name="cert" onset="this.setAttribute('cert', val); return val;"
+ onget="return this.getAttribute('cert');"/>
+ <property name="signed" onset="this.setAttribute('signed', val); return val;"
+ onget="return this.getAttribute('signed');"/>
+ <property name="url" onset="this.setAttribute('url', val); return val;"
+ onget="return this.getAttribute('url');"/>
+ <property name="icon" onset="this.setAttribute('icon', val); return val;"
+ onget="return this.getAttribute('icon');"/>
+ <property name="type" onset="this.setAttribute('type', val); return val;"
+ onget="return this.getAttribute('type');"/>
+ </implementation>
+ </binding>
+
+</bindings>
+
diff --git a/components/addons/extensions.manifest b/components/addons/extensions.manifest
new file mode 100644
index 000000000..35d605575
--- /dev/null
+++ b/components/addons/extensions.manifest
@@ -0,0 +1,15 @@
+component {4399533d-08d1-458c-a87a-235f74451cfa} addonManager.js
+contract @mozilla.org/addons/integration;1 {4399533d-08d1-458c-a87a-235f74451cfa}
+category update-timer addonManager @mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400
+
+component {7beb3ba8-6ec3-41b4-b67c-da89b8518922} amContentHandler.js
+contract @mozilla.org/uriloader/content-handler;1?type=application/x-xpinstall {7beb3ba8-6ec3-41b4-b67c-da89b8518922}
+
+component {0f38e086-89a3-40a5-8ffc-9b694de1d04a} amWebInstallListener.js
+contract @mozilla.org/addons/web-install-listener;1 {0f38e086-89a3-40a5-8ffc-9b694de1d04a}
+
+component {9df8ef2b-94da-45c9-ab9f-132eb55fddf1} amInstallTrigger.js
+contract @mozilla.org/addons/installtrigger;1 {9df8ef2b-94da-45c9-ab9f-132eb55fddf1}
+category JavaScript-global-property InstallTrigger @mozilla.org/addons/installtrigger;1
+
+category addon-provider-module PluginProvider resource://gre/modules/addons/PluginProvider.jsm
diff --git a/components/addons/jar.mn b/components/addons/jar.mn
new file mode 100644
index 000000000..0f4bfce26
--- /dev/null
+++ b/components/addons/jar.mn
@@ -0,0 +1,35 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+% content mozapps %content/mozapps/
+* content/mozapps/extensions/extensions.xul (content/extensions.xul)
+ content/mozapps/extensions/extensions.css (content/extensions.css)
+* content/mozapps/extensions/extensions.js (content/extensions.js)
+* content/mozapps/extensions/extensions.xml (content/extensions.xml)
+ content/mozapps/extensions/updateinfo.xsl (content/updateinfo.xsl)
+ content/mozapps/extensions/about.xul (content/about.xul)
+ content/mozapps/extensions/about.js (content/about.js)
+ content/mozapps/extensions/list.xul (content/list.xul)
+ content/mozapps/extensions/list.js (content/list.js)
+ content/mozapps/extensions/blocklist.xul (content/blocklist.xul)
+ content/mozapps/extensions/blocklist.js (content/blocklist.js)
+ content/mozapps/extensions/blocklist.css (content/blocklist.css)
+ content/mozapps/extensions/blocklist.xml (content/blocklist.xml)
+ content/mozapps/extensions/selectAddons.xul (content/selectAddons.xul)
+ content/mozapps/extensions/selectAddons.xml (content/selectAddons.xml)
+ content/mozapps/extensions/selectAddons.js (content/selectAddons.js)
+ content/mozapps/extensions/selectAddons.css (content/selectAddons.css)
+ content/mozapps/extensions/update.xul (content/update.xul)
+ content/mozapps/extensions/update.js (content/update.js)
+ content/mozapps/extensions/eula.xul (content/eula.xul)
+ content/mozapps/extensions/eula.js (content/eula.js)
+ content/mozapps/extensions/newaddon.xul (content/newaddon.xul)
+ content/mozapps/extensions/newaddon.js (content/newaddon.js)
+ content/mozapps/extensions/setting.xml (content/setting.xml)
+ content/mozapps/extensions/pluginPrefs.xul (content/pluginPrefs.xul)
+ content/mozapps/xpinstall/xpinstallConfirm.xul (content/xpinstallConfirm.xul)
+ content/mozapps/xpinstall/xpinstallConfirm.js (content/xpinstallConfirm.js)
+ content/mozapps/xpinstall/xpinstallConfirm.css (content/xpinstallConfirm.css)
+ content/mozapps/xpinstall/xpinstallItem.xml (content/xpinstallItem.xml)
diff --git a/components/addons/locale/about.dtd b/components/addons/locale/about.dtd
new file mode 100644
index 000000000..4f9098966
--- /dev/null
+++ b/components/addons/locale/about.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 creator.label "Created By:">
+<!ENTITY developers.label "Developers:">
+<!ENTITY translators.label "Translators:">
+<!ENTITY contributors.label "Contributors:">
+<!ENTITY homepage.label "Visit Home Page">
diff --git a/components/addons/locale/blocklist.dtd b/components/addons/locale/blocklist.dtd
new file mode 100644
index 000000000..a9490db5e
--- /dev/null
+++ b/components/addons/locale/blocklist.dtd
@@ -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/. -->
+
+<!ENTITY blocklist.title "Add-ons may be causing problems">
+<!ENTITY blocklist.style "width: 45em; height: 30em">
+<!ENTITY blocklist.summary "&brandShortName; has determined that the following add-ons are known to cause issues:">
+<!ENTITY blocklist.softblocked "It is highly recommended, but not required, that you restart with these add-ons disabled.">
+<!ENTITY blocklist.hardblocked "These add-ons have a high risk of causing stability or security problems and have been blocked, but a restart is required to disable them completely.">
+<!ENTITY blocklist.softandhard "Some listed add-ons have a high risk of causing stability or security problems and have been blocked. For the others it is highly recommended, but not required, that you restart with them disabled.">
+<!ENTITY blocklist.moreinfo "More information">
+
+<!ENTITY blocklist.accept.label "Restart &brandShortName;">
+<!ENTITY blocklist.accept.accesskey "R">
+
+<!ENTITY blocklist.blocked.label "Blocked">
+<!ENTITY blocklist.checkbox.label "Disable">
diff --git a/components/addons/locale/extensions.dtd b/components/addons/locale/extensions.dtd
new file mode 100644
index 000000000..14f4973fb
--- /dev/null
+++ b/components/addons/locale/extensions.dtd
@@ -0,0 +1,219 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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.windowTitle "Add-ons Manager">
+
+<!ENTITY search.placeholder "Search all add-ons">
+<!ENTITY search.buttonlabel "Search">
+<!-- LOCALIZATION NOTE (search.commandKey):
+ The search command key should match findOnCmd.commandkey from browser.dtd -->
+<!ENTITY search.commandkey "f">
+
+<!ENTITY loading.label "Loading…">
+<!ENTITY listEmpty.installed.label "You don't have any add-ons of this type installed">
+<!ENTITY listEmpty.availableUpdates.label "No updates found">
+<!ENTITY listEmpty.recentUpdates.label "You haven't recently updated any add-ons">
+<!ENTITY listEmpty.findUpdates.label "Check For Updates">
+<!ENTITY listEmpty.search.label "Could not find any matching add-ons">
+<!ENTITY listEmpty.button.label "Learn more about add-ons">
+<!ENTITY installAddonFromFile.label "Install Add-on From File…">
+<!ENTITY installAddonFromFile.accesskey "I">
+<!ENTITY toolsMenu.tooltip "Tools for all add-ons">
+
+<!ENTITY cmd.back.tooltip "Go back one page">
+<!ENTITY cmd.forward.tooltip "Go forward one page">
+
+<!-- global warnings -->
+<!ENTITY warning.safemode.label "All add-ons have been disabled by safe mode.">
+<!ENTITY warning.checkcompatibility.label "Add-on compatibility checking is disabled. You may have incompatible add-ons.">
+<!ENTITY warning.checkcompatibility.enable.label "Enable">
+<!ENTITY warning.checkcompatibility.enable.tooltip "Enable add-on compatibility checking">
+<!ENTITY warning.updatesecurity.label "Add-on update security checking is disabled. You may be compromised by updates.">
+<!ENTITY warning.updatesecurity.enable.label "Enable">
+<!ENTITY warning.updatesecurity.enable.tooltip "Enable add-on update security checking">
+
+<!-- categories / views -->
+<!ENTITY view.search.label "Search">
+<!ENTITY view.discover.label "Get Add-ons">
+<!ENTITY view.recentUpdates.label "Recent Updates">
+<!ENTITY view.availableUpdates.label "Available Updates">
+
+<!-- addon updates -->
+<!ENTITY updates.checkForUpdates.label "Check for Updates">
+<!ENTITY updates.checkForUpdates.accesskey "C">
+<!ENTITY updates.viewUpdates.label "View Recent Updates">
+<!ENTITY updates.viewUpdates.accesskey "V">
+<!-- LOCALIZATION NOTE (updates.updateAddonsAutomatically.label): This menu item
+ is a checkbox that toggles the default global behavior for add-on update
+ checking. -->
+<!ENTITY updates.updateAddonsAutomatically.label "Update Add-ons Automatically">
+<!ENTITY updates.updateAddonsAutomatically.accesskey "A">
+<!-- LOCALIZATION NOTE (updates.resetUpdatesToAutomatic.label, updates.resetUpdatesToManual.label):
+ Specific addons can have custom update checking behaviors ("Manually",
+ "Automatically", "Use default global behavior"). These menu items reset the
+ update checking behavior for all add-ons to the default global behavior
+ (which itself is either "Automatically" or "Manually", controlled by the
+ updates.updateAddonsAutomatically.label menu item). -->
+<!ENTITY updates.resetUpdatesToAutomatic.label "Reset All Add-ons to Update Automatically">
+<!ENTITY updates.resetUpdatesToAutomatic.accesskey "R">
+<!ENTITY updates.resetUpdatesToManual.label "Reset All Add-ons to Update Manually">
+<!ENTITY updates.resetUpdatesToManual.accesskey "R">
+<!ENTITY updates.updating.label "Updating add-ons">
+<!ENTITY updates.installed.label "Your add-ons have been updated.">
+<!ENTITY updates.downloaded.label "Your add-on updates have been downloaded.">
+<!ENTITY updates.restart.label "Restart now to complete installation">
+<!ENTITY updates.noneFound.label "No updates found">
+<!ENTITY updates.manualUpdatesFound.label "View Available Updates">
+<!ENTITY updates.updateSelected.label "Install Updates">
+<!ENTITY updates.updateSelected.tooltip "Install available updates in this list">
+
+<!-- addon actions -->
+<!ENTITY cmd.showDetails.label "Show More Information">
+<!ENTITY cmd.showDetails.accesskey "S">
+<!ENTITY cmd.findUpdates.label "Find Updates">
+<!ENTITY cmd.findUpdates.accesskey "F">
+<!ENTITY cmd.preferencesWin.label "Options">
+<!ENTITY cmd.preferencesWin.accesskey "O">
+<!ENTITY cmd.preferencesUnix.label "Preferences">
+<!ENTITY cmd.preferencesUnix.accesskey "P">
+<!ENTITY cmd.about.label "About">
+<!ENTITY cmd.about.accesskey "A">
+
+<!ENTITY cmd.enableAddon.label "Enable">
+<!ENTITY cmd.enableAddon.accesskey "E">
+<!ENTITY cmd.disableAddon.label "Disable">
+<!ENTITY cmd.disableAddon.accesskey "D">
+<!ENTITY cmd.enableTheme.label "Wear Theme">
+<!ENTITY cmd.enableTheme.accesskey "W">
+<!ENTITY cmd.disableTheme.label "Stop Wearing Theme">
+<!ENTITY cmd.disableTheme.accesskey "W">
+<!ENTITY cmd.askToActivate.label "Ask to Activate">
+<!ENTITY cmd.askToActivate.tooltip "Ask to use this add-on each time">
+<!ENTITY cmd.alwaysActivate.label "Always Activate">
+<!ENTITY cmd.alwaysActivate.tooltip "Always use this add-on">
+<!ENTITY cmd.neverActivate.label "Never Activate">
+<!ENTITY cmd.neverActivate.tooltip "Never use this add-on">
+<!ENTITY cmd.stateMenu.tooltip "Change when this add-on runs">
+<!ENTITY cmd.installAddon.label "Install">
+<!ENTITY cmd.installAddon.accesskey "I">
+<!ENTITY cmd.uninstallAddon.label "Remove">
+<!ENTITY cmd.uninstallAddon.accesskey "R">
+<!ENTITY cmd.debugAddon.label "Debug">
+<!ENTITY cmd.showPreferencesWin.label "Options">
+<!ENTITY cmd.showPreferencesWin.tooltip "Change this add-on's options">
+<!ENTITY cmd.showPreferencesUnix.label "Preferences">
+<!ENTITY cmd.showPreferencesUnix.tooltip "Change this add-on's preferences">
+<!ENTITY cmd.contribute.label "Contribute">
+<!ENTITY cmd.contribute.accesskey "C">
+<!ENTITY cmd.contribute.tooltip "Contribute to the development of this add-on">
+
+<!ENTITY cmd.showReleaseNotes.label "Show Release Notes">
+<!ENTITY cmd.showReleaseNotes.tooltip "Show the release notes for this update">
+<!ENTITY cmd.hideReleaseNotes.label "Hide Release Notes">
+<!ENTITY cmd.hideReleaseNotes.tooltip "Hide the release notes for this update">
+
+<!-- discovery view -->
+<!-- LOCALIZATION NOTE (discover.title,discover.description,discover.footer):
+ Displayed in the center of the Get Add-ons view, see bug 601143 for mockups. -->
+<!ENTITY discover.title "What are Add-ons?">
+<!ENTITY discover.description2 "Add-ons are applications that let you personalize &brandShortName; with
+ extra functionality or style. Try a time-saving sidebar, a weather notifier, or a themed look to make &brandShortName;
+ your own.">
+<!ENTITY discover.footer "When you're connected to the internet, this pane will feature
+ some of the best and most popular add-ons for you to try out.">
+
+<!-- detail view -->
+<!ENTITY detail.version.label "Version">
+<!ENTITY detail.lastupdated.label "Last Updated">
+<!ENTITY detail.creator.label "Developer">
+<!ENTITY detail.homepage.label "Homepage">
+<!ENTITY detail.numberOfDownloads.label "Downloads">
+
+<!ENTITY detail.contributions.description "The developer of this add-on asks that you help support its continued development by making a small contribution.">
+
+<!ENTITY detail.updateType "Automatic Updates">
+<!ENTITY detail.updateDefault.label "Default">
+<!ENTITY detail.updateDefault.tooltip "Automatically install updates only if that's the default">
+<!ENTITY detail.updateAutomatic.label "On">
+<!ENTITY detail.updateAutomatic.tooltip "Automatically install updates">
+<!ENTITY detail.updateManual.label "Off">
+<!ENTITY detail.updateManual.tooltip "Don't automatically install updates">
+<!ENTITY detail.home "Homepage">
+<!ENTITY detail.repository "Add-on Profile">
+<!ENTITY detail.size "Size">
+
+<!ENTITY detail.checkForUpdates.label "Check for Updates">
+<!ENTITY detail.checkForUpdates.accesskey "F">
+<!ENTITY detail.checkForUpdates.tooltip "Check for updates for this add-on">
+<!ENTITY detail.showPreferencesWin.label "Options">
+<!ENTITY detail.showPreferencesWin.accesskey "O">
+<!ENTITY detail.showPreferencesWin.tooltip "Change this add-on's options">
+<!ENTITY detail.showPreferencesUnix.label "Preferences">
+<!ENTITY detail.showPreferencesUnix.accesskey "P">
+<!ENTITY detail.showPreferencesUnix.tooltip "Change this add-on's preferences">
+
+
+<!-- ratings -->
+<!ENTITY rating2.label "Rating">
+
+<!-- download/install progress -->
+<!ENTITY progress.pause.tooltip "Pause">
+<!ENTITY progress.cancel.tooltip "Cancel">
+
+
+<!-- list sorting -->
+<!ENTITY sort.name.label "Name">
+<!ENTITY sort.name.tooltip "Sort by name">
+<!ENTITY sort.dateUpdated.label "Last Updated">
+<!ENTITY sort.dateUpdated.tooltip "Sort by date updated">
+<!ENTITY sort.relevance.label "Best match">
+<!ENTITY sort.relevance.tooltip "Sort by relevance">
+<!ENTITY sort.price.label "Price">
+<!ENTITY sort.price.tooltip "Sort by price">
+
+<!ENTITY search.filter2.label "Search:">
+<!ENTITY search.filter2.installed.label "My Add-ons">
+<!ENTITY search.filter2.installed.tooltip "Show installed add-ons">
+<!ENTITY search.filter2.available.label "Available Add-ons">
+<!ENTITY search.filter2.available.tooltip "Show add-ons available to install">
+
+<!ENTITY addon.homepage "Homepage">
+<!ENTITY addon.details.label "More">
+<!ENTITY addon.details.tooltip "Show more details about this add-on">
+<!ENTITY addon.unknownDate "Unknown">
+<!-- LOCALIZATION NOTE (addon.disabled.postfix): This is used in a normal list
+ to signify that an add-on is disabled, in the form
+ "<Addon name> <1.0> (disabled)" -->
+<!ENTITY addon.disabled.postfix "(disabled)">
+<!-- LOCALIZATION NOTE (addon.update.postfix): This is used in the available
+ updates list to signify that an item is an update, in the form
+ "<Addon name> <1.1> Update". It is fine to use constructs like brackets if
+ necessary -->
+<!ENTITY addon.update.postfix "Update">
+<!ENTITY addon.undoAction.label "Undo">
+<!ENTITY addon.undoAction.tooltip "Undo this action">
+<!ENTITY addon.undoRemove.label "Undo">
+<!ENTITY addon.undoRemove.tooltip "Keep this add-on installed">
+<!ENTITY addon.restartNow.label "Restart now">
+<!ENTITY addon.install.label "Install">
+<!ENTITY addon.install.tooltip "Install this add-on">
+<!ENTITY addon.updateNow.label "Update Now">
+<!ENTITY addon.updateNow.tooltip "Install the update for this add-on">
+<!ENTITY addon.includeUpdate.label "Include in Update">
+<!ENTITY addon.updateAvailable.label "An update is available">
+<!ENTITY addon.checkingForUpdates.label "Checking for updates…">
+<!ENTITY addon.releaseNotes.label "Release Notes:">
+<!ENTITY addon.loadingReleaseNotes.label "Loading…">
+<!ENTITY addon.errorLoadingReleaseNotes.label "Sorry, but there was an error loading the release notes.">
+
+<!ENTITY addon.createdBy.label "By ">
+
+<!ENTITY eula.title "End-User License Agreement">
+<!ENTITY eula.width "560px">
+<!ENTITY eula.height "400px">
+<!ENTITY eula.accept "Accept and Install…">
+
+<!ENTITY settings.path.button.label "Browse…">
+
+<!ENTITY setting.learnmore "Learn More…">
diff --git a/components/addons/locale/extensions.properties b/components/addons/locale/extensions.properties
new file mode 100644
index 000000000..43ab02a47
--- /dev/null
+++ b/components/addons/locale/extensions.properties
@@ -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/.
+
+#LOCALIZATION NOTE (aboutWindowTitle) %S is the addon name
+aboutWindowTitle=About %S
+aboutWindowCloseButton=Close
+#LOCALIZATION NOTE (aboutWindowVersionString) %S is the addon version
+aboutWindowVersionString=version %S
+#LOCALIZATION NOTE (aboutAddon) %S is the addon name
+aboutAddon=About %S
+
+#LOCALIZATION NOTE (uninstallNotice) %S is the add-on name
+uninstallNotice=%S has been removed.
+
+#LOCALIZATION NOTE (numReviews): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of reviews
+numReviews=#1 review;#1 reviews
+
+#LOCALIZATION NOTE (dateUpdated) %S is the date the addon was last updated
+dateUpdated=Updated %S
+
+#LOCALIZATION NOTE (notification.incompatible) %1$S is the add-on name, %2$S is brand name, %3$S is application version
+notification.incompatible=%1$S is incompatible with %2$S %3$S.
+notification.jetsdk=This is a Jetpack/SDK extension which are not supported in %1$S %2$S.
+#LOCALIZATION NOTE (notification.blocked) %1$S is the add-on name
+notification.blocked=%1$S has been disabled due to security or stability issues.
+notification.blocked.link=More Information
+#LOCALIZATION NOTE (notification.softblocked) %1$S is the add-on name
+notification.softblocked=%1$S is known to cause issues.
+notification.softblocked.link=More Information
+#LOCALIZATION NOTE (notification.outdated) %1$S is the add-on name
+notification.outdated=An important update is available for %1$S.
+notification.outdated.link=Update Now
+#LOCALIZATION NOTE (notification.vulnerableUpdatable) %1$S is the add-on name
+notification.vulnerableUpdatable=%1$S is known to be vulnerable and should be updated.
+notification.vulnerableUpdatable.link=Update Now
+#LOCALIZATION NOTE (notification.vulnerableNoUpdate) %1$S is the add-on name
+notification.vulnerableNoUpdate=%1$S is known to be vulnerable. Use with caution.
+notification.vulnerableNoUpdate.link=More Information
+#LOCALIZATION NOTE (notification.enable) %1$S is the add-on name, %2$S is brand name
+notification.enable=%1$S will be enabled after you restart %2$S.
+#LOCALIZATION NOTE (notification.disable) %1$S is the add-on name, %2$S is brand name
+notification.disable=%1$S will be disabled after you restart %2$S.
+#LOCALIZATION NOTE (notification.install) %1$S is the add-on name, %2$S is brand name
+notification.install=%1$S will be installed after you restart %2$S.
+#LOCALIZATION NOTE (notification.uninstall) %1$S is the add-on name, %2$S is brand name
+notification.uninstall=%1$S will be uninstalled after you restart %2$S.
+#LOCALIZATION NOTE (notification.upgrade) %1$S is the add-on name, %2$S is brand name
+notification.upgrade=%1$S will be updated after you restart %2$S.
+#LOCALIZATION NOTE (notification.downloadError) %1$S is the add-on name.
+notification.downloadError=There was an error downloading %1$S.
+notification.downloadError.retry=Try again
+notification.downloadError.retry.tooltip=Try downloading this add-on again
+#LOCALIZATION NOTE (notification.installError) %1$S is the add-on name.
+notification.installError=There was an error installing %1$S.
+notification.installError.retry=Try again
+notification.installError.retry.tooltip=Try downloading and installing this add-on again
+
+#LOCALIZATION NOTE (contributionAmount2) %S is the currency amount recommended for contributions
+contributionAmount2=Suggested Contribution: %S
+
+installDownloading=Downloading
+installDownloaded=Downloaded
+installDownloadFailed=Error downloading
+installVerifying=Verifying
+installInstalling=Installing
+installEnablePending=Restart to enable
+installDisablePending=Restart to disable
+installFailed=Error installing
+installCancelled=Install cancelled
+
+#LOCALIZATION NOTE (details.notification.incompatible) %1$S is the add-on name, %2$S is brand name, %3$S is application version
+details.notification.incompatible=%1$S is incompatible with %2$S %3$S.
+#LOCALIZATION NOTE (details.notification.blocked) %1$S is the add-on name
+details.notification.blocked=%1$S has been disabled due to security or stability issues.
+details.notification.blocked.link=More Information
+#LOCALIZATION NOTE (details.notification.softblocked) %1$S is the add-on name
+details.notification.softblocked=%1$S is known to cause issues.
+details.notification.softblocked.link=More Information
+#LOCALIZATION NOTE (details.notification.outdated) %1$S is the add-on name
+details.notification.outdated=An important update is available for %1$S.
+details.notification.outdated.link=Update Now
+#LOCALIZATION NOTE (details.notification.vulnerableUpdatable) %1$S is the add-on name
+details.notification.vulnerableUpdatable=%1$S is known to be vulnerable and should be updated.
+details.notification.vulnerableUpdatable.link=Update Now
+#LOCALIZATION NOTE (details.notification.vulnerableNoUpdate) %1$S is the add-on name
+details.notification.vulnerableNoUpdate=%1$S is known to be vulnerable. Use with caution.
+details.notification.vulnerableNoUpdate.link=More Information
+#LOCALIZATION NOTE (details.notification.enable) %1$S is the add-on name, %2$S is brand name
+details.notification.enable=%1$S will be enabled after you restart %2$S.
+#LOCALIZATION NOTE (details.notification.disable) %1$S is the add-on name, %2$S is brand name
+details.notification.disable=%1$S will be disabled after you restart %2$S.
+#LOCALIZATION NOTE (details.notification.install) %1$S is the add-on name, %2$S is brand name
+details.notification.install=%1$S will be installed after you restart %2$S.
+#LOCALIZATION NOTE (details.notification.uninstall) %1$S is the add-on name, %2$S is brand name
+details.notification.uninstall=%1$S will be uninstalled after you restart %2$S.
+#LOCALIZATION NOTE (details.notification.upgrade) %1$S is the add-on name, %2$S is brand name
+details.notification.upgrade=%1$S will be updated after you restart %2$S.
+
+installFromFile.dialogTitle=Select add-on to install
+installFromFile.filterName=Add-ons
+
+uninstallAddonTooltip=Uninstall this add-on
+uninstallAddonRestartRequiredTooltip=Uninstall this add-on (restart required)
+enableAddonTooltip=Enable this add-on
+enableAddonRestartRequiredTooltip=Enable this add-on (restart required)
+disableAddonTooltip=Disable this add-on
+disableAddonRestartRequiredTooltip=Disable this add-on (restart required)
+
+#LOCALIZATION NOTE (showAllSearchResults): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the total number of search results
+showAllSearchResults=See one result;See all #1 results
+
+#LOCALIZATION NOTE (addon.purchase.label) displayed on a button in the list
+# view, %S is the price of the add-on including currency symbol
+addon.purchase.label=Purchase for %S…
+addon.purchase.tooltip=Visit the add-ons gallery to purchase this add-on
+#LOCALIZATION NOTE (cmd.purchaseAddon.label) displayed on a button in the detail
+# view, %S is the price of the add-on including currency symbol
+cmd.purchaseAddon.label=Purchase for %S…
+cmd.purchaseAddon.accesskey=u
+
+#LOCALIZATION NOTE (eulaHeader) %S is name of the add-on asking the user to agree to the EULA
+eulaHeader=%S requires that you accept the following End User License Agreement before installation can proceed:
+
+type.extension.name=Extensions
+type.theme.name=Themes
+type.locale.name=Languages
+type.plugin.name=Plugins
+type.dictionary.name=Dictionaries
+type.service.name=Services
diff --git a/components/addons/locale/newaddon.dtd b/components/addons/locale/newaddon.dtd
new file mode 100644
index 000000000..1307cebb9
--- /dev/null
+++ b/components/addons/locale/newaddon.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 title "Install Add-on">
+<!ENTITY intro "Another program on your computer would like to modify
+ &brandShortName; with the following add-on:">
+<!ENTITY warning "Install add-ons only from authors whom you trust.">
+<!ENTITY allow "Allow this installation">
+<!ENTITY later "You can always change your mind at any time by going
+ to the Add-ons Manager.">
+<!ENTITY continue "Continue">
+<!ENTITY restartMessage "You must restart &brandShortName; to finish installing this add-on.">
+<!ENTITY restartButton "Restart &brandShortName;">
+<!ENTITY cancelButton "Cancel">
diff --git a/components/addons/locale/newaddon.properties b/components/addons/locale/newaddon.properties
new file mode 100644
index 000000000..bd5997a26
--- /dev/null
+++ b/components/addons/locale/newaddon.properties
@@ -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/.
+
+#LOCALIZATION NOTE (name) %1$S is the add-on name, %2$S is the add-on version
+name=%1$S %2$S
+#LOCALIZATION NOTE (author) %S is the author of the add-on
+author=By %S
+#LOCALIZATION NOTE (location) %S is the path the add-on is installed in
+location=Location: %S
diff --git a/components/addons/locale/selectAddons.dtd b/components/addons/locale/selectAddons.dtd
new file mode 100644
index 000000000..2f6f1cd57
--- /dev/null
+++ b/components/addons/locale/selectAddons.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 upgrade.style "width: 93ch; height: 448px;">
+
+<!ENTITY checking.heading "Checking Your Add-ons">
+<!ENTITY checking.progress.label "Checking your add-ons for compatibility with this version of &brandShortName;.">
+
+<!ENTITY select.heading "Select Your Add-ons">
+<!-- LOCALIZATION NOTE (select.description): The term used for "third parties"
+ here should match the string source.other in selectAddons.properties. -->
+<!ENTITY select.description "Make &brandShortName; even faster by disabling add-ons you no longer use. Add-ons already installed by third parties will be disabled automatically unless you select them below.">
+<!ENTITY select.keep "Keep">
+<!-- LOCALIZATION NOTE (select.keep.style): Should be a width wide enough for
+ the string in select.keep above. -->
+<!ENTITY select.keep.style "width: 6ch;">
+<!ENTITY select.action "Action">
+<!-- LOCALIZATION NOTE (select.action.style): Should be a width wide enough for
+ the action strings in selectAddons.properties or brandShortName. -->
+<!ENTITY select.action.style "width: 35ch;">
+<!ENTITY select.source "Installed By">
+<!ENTITY select.name "Name">
+<!-- LOCALIZATION NOTE (select.name.style): Should be a width small enough so
+ the source column still has enough room for the source strings in
+ selectAddons.properties. -->
+<!ENTITY select.name.style "width: 33ch;">
+
+<!ENTITY confirm.heading "Select Your Add-ons">
+<!-- LOCALIZATION NOTE (confirm.description): The term used for "third parties"
+ here should match the string source.other in selectAddons.properties. -->
+<!ENTITY confirm.description "Make &brandShortName; even faster by disabling add-ons you no longer use. Add-ons already installed by third parties will be disabled automatically unless you select them below.">
+
+<!ENTITY action.disable.heading "The following add-ons will be disabled:">
+<!ENTITY action.incompatible.heading "The following add-ons are disabled, but will be enabled as soon as they are compatible:">
+<!ENTITY action.update.heading "The following add-ons will be updated:">
+<!ENTITY action.enable.heading "The following add-ons will be enabled:">
+
+<!ENTITY update.heading "Updating Your Add-ons">
+<!ENTITY update.progress.label "Downloading and installing updates for your selected add-ons.">
+
+<!ENTITY errors.heading "&brandShortName; could not update some of your add-ons.">
+<!ENTITY errors.description "Installing updates for some of your add-ons failed. &brandShortName; will automatically try to update them again later.">
+
+<!ENTITY footer.label "You can always change your add-ons by going to the Add-ons Manager.">
+<!ENTITY cancel.label "Cancel">
+<!ENTITY back.label "Back">
+<!ENTITY next.label "Next">
+<!ENTITY done.label "Done">
diff --git a/components/addons/locale/selectAddons.properties b/components/addons/locale/selectAddons.properties
new file mode 100644
index 000000000..2824758d6
--- /dev/null
+++ b/components/addons/locale/selectAddons.properties
@@ -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 (source.profile) add-ons installed by the user, this may be
+# translated as "You" or "User" depending on the locale
+source.profile=You
+#LOCALIZATION NOTE (source.bundled) add-ons shipped with the application, and thus
+# treated as installed by the user. This may be
+# translated as "You" or "User" depending on the locale
+source.bundled=You (Bundled)
+#LOCALIZATION NOTE (source.other) add-ons installed by other applications
+# installed on the computer
+source.other=Third Party
+
+action.enabled=Will be enabled
+action.disabled=Will be disabled
+action.autoupdate=Will be updated to be compatible
+action.incompatible=Will be enabled when compatible
+action.neededupdate=Update to make compatible
+action.unneededupdate=Optional update
diff --git a/components/addons/locale/update.dtd b/components/addons/locale/update.dtd
new file mode 100644
index 000000000..6c820e088
--- /dev/null
+++ b/components/addons/locale/update.dtd
@@ -0,0 +1,65 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY updateWizard.title "&brandShortName; Update">
+
+<!ENTITY offline.title "&brandShortName; is working offline">
+<!ENTITY offline.description "&brandShortName; needs to go online in order to see if updates
+ are available for your add-ons to make them compatible with this
+ version.">
+<!ENTITY offline.toggleOffline.label "Go online now.">
+<!ENTITY offline.toggleOffline.accesskey "G">
+
+<!ENTITY mismatch.win.title "Incompatible Add-ons">
+<!ENTITY mismatch.top.label "The following add-ons are not compatible with this version of
+ &brandShortName; and have been disabled:">
+<!ENTITY mismatch.bottom.label "&brandShortName; can check if there are compatible versions
+ of these add-ons available.">
+
+<!ENTITY checking.wizard.title "Checking for Compatible Add-ons">
+<!ENTITY checking.top.label "Checking your incompatible add-ons for updates…">
+<!ENTITY checking.status "This may take a few minutes…">
+
+<!ENTITY found.wizard.title "Found Compatible Add-ons">
+<!ENTITY found.top.label "Select the add-ons you would like to install:">
+<!ENTITY found.disabledXPinstall.label "These updates can't be installed because software installation is currently
+ disabled. You can change this setting below.">
+<!ENTITY found.enableXPInstall.label "Allow websites to install software">
+<!ENTITY found.enableXPInstall.accesskey "A">
+
+<!ENTITY installing.wizard.title "Installing Compatible Add-ons">
+<!ENTITY installing.top.label "Downloading and installing updates to your add-ons…">
+
+<!ENTITY noupdates.wizard.title "No Compatible Add-ons Found">
+<!ENTITY noupdates.intro.desc "&brandShortName; was unable to find updates to your
+ incompatible add-ons.">
+<!ENTITY noupdates.error.desc "Some problems were encountered when trying to find updates.">
+<!ENTITY noupdates.checkEnabled.desc "&brandShortName; will check periodically and inform you
+ when compatible updates for these add-ons are found.">
+
+<!ENTITY finished.wizard.title "Compatible Add-ons Installed">
+<!ENTITY finished.top.label "&brandShortName; has installed the updates to your add-ons.">
+<!ENTITY finished.checkDisabled.desc "&brandShortName; can check periodically and inform you
+ when updates for add-ons are found.">
+<!ENTITY finished.checkEnabled.desc "&brandShortName; will check periodically and inform you
+ when updates for add-ons are found.">
+
+<!ENTITY adminDisabled.wizard.title "Unable to Check for Updates">
+<!ENTITY adminDisabled.warning.label "It is not possible to check for updates to incompatible add-ons
+ because software installation for &brandShortName; has been disabled.
+ Please contact your System Administrator for assistance.">
+
+<!ENTITY versioninfo.wizard.title "Checking Compatibility of Add-ons">
+<!ENTITY versioninfo.top.label "Checking your add-ons for compatibility with this
+ version of &brandShortName;.">
+<!ENTITY versioninfo.waiting "This may take a few minutes…">
+
+<!ENTITY installerrors.wizard.title "Problems Installing Updates">
+<!ENTITY installerrors.intro.label "&brandShortName; encountered problems when updating
+ some of your add-ons.">
+
+<!-- general strings used by several of the finish pages -->
+<!ENTITY clickFinish.label "Click Finish to continue starting &brandShortName;.">
+<!ENTITY clickFinish.labelMac "Click Done to continue starting &brandShortName;.">
+<!ENTITY enableChecking.label "Allow &brandShortName; to check for updates.">
diff --git a/components/addons/locale/update.properties b/components/addons/locale/update.properties
new file mode 100644
index 000000000..7cf79f9c1
--- /dev/null
+++ b/components/addons/locale/update.properties
@@ -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/.
+
+mismatchCheckNow=Check Now
+mismatchCheckNowAccesskey=C
+mismatchDontCheck=Don't Check
+mismatchDontCheckAccesskey=D
+installButtonText=Install Now
+installButtonTextAccesskey=I
+nextButtonText=Next >
+nextButtonTextAccesskey=N
+cancelButtonText=Cancel
+cancelButtonTextAccesskey=C
+statusPrefix=Finished checking %S
+downloadingPrefix=Downloading: %S
+installingPrefix=Installing: %S
+closeButton=Close
+installErrors=%S was unable to install updates for the following add-ons:
+checkingErrors=%S was unable to check for updates for the following add-ons:
+installErrorItemFormat=%S (%S)
diff --git a/components/addons/locale/xpinstallConfirm.dtd b/components/addons/locale/xpinstallConfirm.dtd
new file mode 100644
index 000000000..6a7d17a16
--- /dev/null
+++ b/components/addons/locale/xpinstallConfirm.dtd
@@ -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/. -->
+
+<!-- extracted from institems.xul -->
+
+<!ENTITY dialog.title "Software Installation">
+<!ENTITY dialog.style "width: 45em">
+<!ENTITY warningPrimary.label "Install add-ons only from authors whom you trust.">
+<!ENTITY warningSecondary.label "Malicious software can damage your computer or violate your privacy.">
+
+<!ENTITY from.label "from:">
+
diff --git a/components/addons/locale/xpinstallConfirm.properties b/components/addons/locale/xpinstallConfirm.properties
new file mode 100644
index 000000000..b538e9e90
--- /dev/null
+++ b/components/addons/locale/xpinstallConfirm.properties
@@ -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/.
+
+unverified=(Author not verified)
+signed=(%S)
+
+itemWarnIntroMultiple=You have asked to install the following %S items:
+itemWarnIntroSingle=You have asked to install the following item:
+installButtonDisabledLabel=Install (%S)
+installButtonLabel=Install Now
+
+installComplete=Software Installation is complete. You will have to restart %S for changes to take effect.
+installCompleteTitle=Installation Complete
+
+error-203=Error Installing Item
diff --git a/components/addons/moz.build b/components/addons/moz.build
new file mode 100644
index 000000000..151008dff
--- /dev/null
+++ b/components/addons/moz.build
@@ -0,0 +1,65 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 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/.
+
+# This is used in multiple places, so is defined here to avoid it getting
+# out of sync.
+DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 16
+
+if CONFIG['MC_APP_ID']:
+ DEFINES['MC_APP_ID'] = CONFIG['MC_APP_ID']
+
+# Additional debugging info is exposed in debug builds
+if CONFIG['MOZ_EM_DEBUG']:
+ DEFINES['MOZ_EM_DEBUG'] = 1
+
+XPIDL_SOURCES += [
+ 'public/amIAddonManager.idl',
+ 'public/amIAddonPathService.idl',
+ 'public/amIWebInstaller.idl',
+ 'public/amIWebInstallListener.idl',
+]
+
+EXPORTS.mozilla += ['src/AddonPathService.h']
+
+SOURCES += ['src/AddonPathService.cpp']
+
+EXTRA_COMPONENTS += [
+ 'extensions.manifest',
+ 'src/addonManager.js',
+ 'src/amContentHandler.js',
+ 'src/amInstallTrigger.js',
+ 'src/amWebInstallListener.js',
+]
+
+EXTRA_JS_MODULES += [
+ 'src/ChromeManifestParser.jsm',
+ 'src/DeferredSave.jsm',
+ 'src/LightweightThemeConsumer.jsm',
+ 'src/LightweightThemeManager.jsm',
+]
+
+EXTRA_PP_JS_MODULES += [
+ 'src/AddonManager.jsm',
+]
+
+EXTRA_JS_MODULES.addons += [
+ 'src/AddonLogging.jsm',
+ 'src/AddonRepository_SQLiteMigrator.jsm',
+ 'src/Content.js',
+ 'src/LightweightThemeImageOptimizer.jsm',
+ 'src/PluginProvider.jsm',
+ 'src/SpellCheckDictionaryBootstrap.js',
+]
+
+EXTRA_PP_JS_MODULES.addons += [
+ 'src/AddonRepository.jsm',
+ 'src/AddonUpdateChecker.jsm',
+ 'src/XPIProvider.jsm',
+ 'src/XPIProviderUtils.js',
+]
+
+XPIDL_MODULE = 'extensions'
+FINAL_LIBRARY = 'xul'
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/addons/public/amIAddonManager.idl b/components/addons/public/amIAddonManager.idl
new file mode 100644
index 000000000..58a58b62d
--- /dev/null
+++ b/components/addons/public/amIAddonManager.idl
@@ -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 "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * A service to make some AddonManager functionality available to C++ callers.
+ * Javascript callers should still use AddonManager.jsm directly.
+ */
+[scriptable, function, uuid(7b45d82d-7ad5-48d7-9b05-f32eb9818cd4)]
+interface amIAddonManager : nsISupports
+{
+ /**
+ * Synchronously map a URI to the corresponding Addon ID.
+ *
+ * Mappable URIs are limited to in-application resources belonging to the
+ * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc.
+ * but do not include URIs from meta data, such as the add-on homepage.
+ *
+ * @param aURI
+ * The nsIURI to map
+ * @return
+ * true if the URI has been mapped successfully to an Addon ID
+ */
+ boolean mapURIToAddonID(in nsIURI aURI, out AUTF8String aID);
+};
diff --git a/components/addons/public/amIAddonPathService.idl b/components/addons/public/amIAddonPathService.idl
new file mode 100644
index 000000000..9c9197a61
--- /dev/null
+++ b/components/addons/public/amIAddonPathService.idl
@@ -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 "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * This service maps file system paths where add-ons reside to the ID
+ * of the add-on. Paths are added by the add-on manager. They can
+ * looked up by anyone.
+ */
+[scriptable, uuid(fcd9e270-dfb1-11e3-8b68-0800200c9a66)]
+interface amIAddonPathService : nsISupports
+{
+ /**
+ * Given a path to a file, return the ID of the add-on that the file belongs
+ * to. Returns an empty string if there is no add-on there. Note that if an
+ * add-on is located at /a/b/c, then looking up the path /a/b/c/d will return
+ * that add-on.
+ */
+ AString findAddonId(in AString path);
+
+ /**
+ * Call this function to inform the service that the given file system path is
+ * associated with the given add-on ID.
+ */
+ void insertPath(in AString path, in AString addonId);
+
+ /**
+ * Given a URI to a file, return the ID of the add-on that the file belongs
+ * to. Returns an empty string if there is no add-on there.
+ */
+ AString mapURIToAddonId(in nsIURI aURI);
+};
diff --git a/components/addons/public/amIWebInstallListener.idl b/components/addons/public/amIWebInstallListener.idl
new file mode 100644
index 000000000..eed108097
--- /dev/null
+++ b/components/addons/public/amIWebInstallListener.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMElement;
+interface nsIURI;
+interface nsIVariant;
+
+/**
+ * amIWebInstallInfo is used by the default implementation of
+ * amIWebInstallListener to communicate with the running application and allow
+ * it to warn the user about blocked installs and start the installs running.
+ */
+[scriptable, uuid(fa0b47a3-f819-47ac-bc66-4bd1d7f67b1d)]
+interface amIWebInstallInfo : nsISupports
+{
+ readonly attribute nsIDOMElement browser;
+ readonly attribute nsIURI originatingURI;
+ readonly attribute nsIVariant installs;
+
+ /**
+ * Starts all installs.
+ */
+ void install();
+};
+
+/**
+ * The registered amIWebInstallListener is used to notify about new installs
+ * triggered by websites. The default implementation displays a confirmation
+ * dialog when add-ons are ready to install and uses the observer service to
+ * notify when installations are blocked.
+ */
+[scriptable, uuid(d9240d4b-6b3a-4cad-b402-de6c93337e0c)]
+interface amIWebInstallListener : nsISupports
+{
+ /**
+ * Called when installation by websites is currently disabled.
+ *
+ * @param aBrowser
+ * The browser that triggered the installs
+ * @param aUri
+ * The URI of the site that triggered the installs
+ * @param aInstalls
+ * The AddonInstalls that were blocked
+ * @param aCount
+ * The number of AddonInstalls
+ */
+ void onWebInstallDisabled(in nsIDOMElement aBrowser, in nsIURI aUri,
+ [array, size_is(aCount)] in nsIVariant aInstalls,
+ [optional] in uint32_t aCount);
+
+ /**
+ * Called when the website is not allowed to directly prompt the user to
+ * install add-ons.
+ *
+ * @param aBrowser
+ * The browser that triggered the installs
+ * @param aUri
+ * The URI of the site that triggered the installs
+ * @param aInstalls
+ * The AddonInstalls that were blocked
+ * @param aCount
+ * The number of AddonInstalls
+ * @return true if the caller should start the installs
+ */
+ boolean onWebInstallBlocked(in nsIDOMElement aBrowser, in nsIURI aUri,
+ [array, size_is(aCount)] in nsIVariant aInstalls,
+ [optional] in uint32_t aCount);
+
+ /**
+ * Called when a website wants to ask the user to install add-ons.
+ *
+ * @param aBrowser
+ * The browser that triggered the installs
+ * @param aUri
+ * The URI of the site that triggered the installs
+ * @param aInstalls
+ * The AddonInstalls that were requested
+ * @param aCount
+ * The number of AddonInstalls
+ * @return true if the caller should start the installs
+ */
+ boolean onWebInstallRequested(in nsIDOMElement aBrowser, in nsIURI aUri,
+ [array, size_is(aCount)] in nsIVariant aInstalls,
+ [optional] in uint32_t aCount);
+};
+
+[scriptable, uuid(a80b89ad-bb1a-4c43-9cb7-3ae656556f78)]
+interface amIWebInstallListener2 : nsISupports
+{
+ /**
+ * Called when a non-same-origin resource attempted to initiate an install.
+ * Installs will have already been cancelled and cannot be restarted.
+ *
+ * @param aBrowser
+ * The browser that triggered the installs
+ * @param aUri
+ * The URI of the site that triggered the installs
+ * @param aInstalls
+ * The AddonInstalls that were blocked
+ * @param aCount
+ * The number of AddonInstalls
+ */
+ boolean onWebInstallOriginBlocked(in nsIDOMElement aBrowser, in nsIURI aUri,
+ [array, size_is(aCount)] in nsIVariant aInstalls,
+ [optional] in uint32_t aCount);
+};
+
+/**
+ * amIWebInstallPrompt is used, if available, by the default implementation of
+ * amIWebInstallInfo to display a confirmation UI to the user before running
+ * installs.
+ */
+[scriptable, uuid(386906f1-4d18-45bf-bc81-5dcd68e42c3b)]
+interface amIWebInstallPrompt : nsISupports
+{
+ /**
+ * Get a confirmation that the user wants to start the installs.
+ *
+ * @param aBrowser
+ * The browser that triggered the installs
+ * @param aUri
+ * The URI of the site that triggered the installs
+ * @param aInstalls
+ * The AddonInstalls that were requested
+ * @param aCount
+ * The number of AddonInstalls
+ */
+ void confirm(in nsIDOMElement aBrowser, in nsIURI aUri,
+ [array, size_is(aCount)] in nsIVariant aInstalls,
+ [optional] in uint32_t aCount);
+};
diff --git a/components/addons/public/amIWebInstaller.idl b/components/addons/public/amIWebInstaller.idl
new file mode 100644
index 000000000..6c5ebca67
--- /dev/null
+++ b/components/addons/public/amIWebInstaller.idl
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMElement;
+interface nsIVariant;
+interface nsIURI;
+
+/**
+ * A callback function used to notify webpages when a requested install has
+ * ended.
+ *
+ * NOTE: This is *not* the same as InstallListener.
+ */
+[scriptable, function, uuid(bb22f5c0-3ca1-48f6-873c-54e87987700f)]
+interface amIInstallCallback : nsISupports
+{
+ /**
+ * Called when an install completes or fails.
+ *
+ * @param aUrl
+ * The url of the add-on being installed
+ * @param aStatus
+ * 0 if the install was successful or negative if not
+ */
+ void onInstallEnded(in AString aUrl, in int32_t aStatus);
+};
+
+
+/**
+ * This interface is used to allow webpages to start installing add-ons.
+ */
+[scriptable, uuid(658d6c09-15e0-4688-bee8-8551030472a9)]
+interface amIWebInstaller : nsISupports
+{
+ /**
+ * Checks if installation is enabled for a webpage.
+ *
+ * @param aMimetype
+ * The mimetype for the add-on to be installed
+ * @param referer
+ * The URL of the webpage trying to install an add-on
+ * @return true if installation is enabled
+ */
+ boolean isInstallEnabled(in AString aMimetype, in nsIURI aReferer);
+
+ /**
+ * Installs an array of add-ons at the request of a webpage
+ *
+ * @param aMimetype
+ * The mimetype for the add-ons
+ * @param aBrowser
+ * The browser installing the add-ons.
+ * @param aReferer
+ * The URI for the webpage installing the add-ons
+ * @param aUris
+ * The URIs of add-ons to be installed
+ * @param aHashes
+ * The hashes for the add-ons to be installed
+ * @param aNames
+ * The names for the add-ons to be installed
+ * @param aIcons
+ * The icons for the add-ons to be installed
+ * @param aCallback
+ * An optional callback to notify about installation success and
+ * failure
+ * @param aInstallCount
+ * An optional argument including the number of add-ons to install
+ * @return true if the installation was successfully started
+ */
+ boolean installAddonsFromWebpage(in AString aMimetype,
+ in nsIDOMElement aBrowser,
+ in nsIURI aReferer,
+ [array, size_is(aInstallCount)] in wstring aUris,
+ [array, size_is(aInstallCount)] in wstring aHashes,
+ [array, size_is(aInstallCount)] in wstring aNames,
+ [array, size_is(aInstallCount)] in wstring aIcons,
+ [optional] in amIInstallCallback aCallback,
+ [optional] in uint32_t aInstallCount);
+};
diff --git a/components/addons/src/AddonLogging.jsm b/components/addons/src/AddonLogging.jsm
new file mode 100644
index 000000000..ffa92c791
--- /dev/null
+++ b/components/addons/src/AddonLogging.jsm
@@ -0,0 +1,187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+const KEY_PROFILEDIR = "ProfD";
+const FILE_EXTENSIONS_LOG = "extensions.log";
+const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
+
+const LOGGER_FILE_PERM = parseInt("666", 8);
+
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+this.EXPORTED_SYMBOLS = [ "LogManager" ];
+
+var gDebugLogEnabled = false;
+
+function formatLogMessage(aType, aName, aStr, aException) {
+ let message = aType.toUpperCase() + " " + aName + ": " + aStr;
+ if (aException) {
+ if (typeof aException == "number")
+ return message + ": " + Components.Exception("", aException).name;
+
+ message = message + ": " + aException;
+ // instanceOf doesn't work here, let's duck type
+ if (aException.fileName)
+ message = message + " (" + aException.fileName + ":" + aException.lineNumber + ")";
+
+ if (aException.message == "too much recursion")
+ dump(message + "\n" + aException.stack + "\n");
+ }
+ return message;
+}
+
+function getStackDetails(aException) {
+ // Defensively wrap all this to ensure that failing to get the message source
+ // doesn't stop the message from being logged
+ try {
+ if (aException) {
+ if (aException instanceof Ci.nsIException) {
+ return {
+ sourceName: aException.filename,
+ lineNumber: aException.lineNumber
+ };
+ }
+
+ if (typeof aException == "object") {
+ return {
+ sourceName: aException.fileName,
+ lineNumber: aException.lineNumber
+ };
+ }
+ }
+
+ let stackFrame = Components.stack.caller.caller.caller;
+ return {
+ sourceName: stackFrame.filename,
+ lineNumber: stackFrame.lineNumber
+ };
+ }
+ catch (e) {
+ return {
+ sourceName: null,
+ lineNumber: 0
+ };
+ }
+}
+
+function AddonLogger(aName) {
+ this.name = aName;
+}
+
+AddonLogger.prototype = {
+ name: null,
+
+ error: function(aStr, aException) {
+ let message = formatLogMessage("error", this.name, aStr, aException);
+
+ let stack = getStackDetails(aException);
+
+ let consoleMessage = Cc["@mozilla.org/scripterror;1"].
+ createInstance(Ci.nsIScriptError);
+ consoleMessage.init(message, stack.sourceName, null, stack.lineNumber, 0,
+ Ci.nsIScriptError.errorFlag, "component javascript");
+ Services.console.logMessage(consoleMessage);
+
+ // Always dump errors, in case the Console Service isn't listening yet
+ dump("*** " + message + "\n");
+
+ function formatTimestamp(date) {
+ // Format timestamp as: "%Y-%m-%d %H:%M:%S"
+ let year = String(date.getFullYear());
+ let month = String(date.getMonth() + 1).padStart(2, "0");
+ let day = String(date.getDate()).padStart(2, "0");
+ let hours = String(date.getHours()).padStart(2, "0");
+ let minutes = String(date.getMinutes()).padStart(2, "0");
+ let seconds = String(date.getSeconds()).padStart(2, "0");
+
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+ }
+
+ try {
+ var tstamp = new Date();
+ var logfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_LOG]);
+ var stream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ stream.init(logfile, 0x02 | 0x08 | 0x10, LOGGER_FILE_PERM, 0); // write, create, append
+ var writer = Cc["@mozilla.org/intl/converter-output-stream;1"].
+ createInstance(Ci.nsIConverterOutputStream);
+ writer.init(stream, "UTF-8", 0, 0x0000);
+ writer.writeString(formatTimestamp(tstamp) + " " +
+ message + " at " + stack.sourceName + ":" +
+ stack.lineNumber + "\n");
+ writer.close();
+ }
+ catch (e) { }
+ },
+
+ warn: function(aStr, aException) {
+ let message = formatLogMessage("warn", this.name, aStr, aException);
+
+ let stack = getStackDetails(aException);
+
+ let consoleMessage = Cc["@mozilla.org/scripterror;1"].
+ createInstance(Ci.nsIScriptError);
+ consoleMessage.init(message, stack.sourceName, null, stack.lineNumber, 0,
+ Ci.nsIScriptError.warningFlag, "component javascript");
+ Services.console.logMessage(consoleMessage);
+
+ if (gDebugLogEnabled)
+ dump("*** " + message + "\n");
+ },
+
+ log: function(aStr, aException) {
+ if (gDebugLogEnabled) {
+ let message = formatLogMessage("log", this.name, aStr, aException);
+ dump("*** " + message + "\n");
+ Services.console.logStringMessage(message);
+ }
+ }
+};
+
+this.LogManager = {
+ getLogger: function(aName, aTarget) {
+ let logger = new AddonLogger(aName);
+
+ if (aTarget) {
+ ["error", "warn", "log"].forEach(function(name) {
+ let fname = name.toUpperCase();
+ delete aTarget[fname];
+ aTarget[fname] = function(aStr, aException) {
+ logger[name](aStr, aException);
+ };
+ });
+ }
+
+ return logger;
+ }
+};
+
+var PrefObserver = {
+ init: function() {
+ Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "xpcom-shutdown") {
+ Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ }
+ else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
+ gDebugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED, false);
+ }
+ }
+};
+
+PrefObserver.init();
diff --git a/components/addons/src/AddonManager.jsm b/components/addons/src/AddonManager.jsm
new file mode 100644
index 000000000..b1cc0978e
--- /dev/null
+++ b/components/addons/src/AddonManager.jsm
@@ -0,0 +1,2939 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cr = Components.results;
+const Cu = Components.utils;
+
+// Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as
+// most tests later register different nsIAppInfo implementations, which
+// wouldn't be reflected in Services.appinfo anymore, as the lazy getter
+// underlying it would have been initialized if we used it here.
+if ("@mozilla.org/xre/app-info;1" in Cc) {
+ let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+ if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ // Refuse to run in child processes.
+ throw new Error("You cannot use the AddonManager in child processes!");
+ }
+}
+
+
+const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
+const PREF_DEFAULT_PROVIDERS_ENABLED = "extensions.defaultProviders.enabled";
+const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled";
+const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion";
+const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion";
+const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault";
+const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility";
+const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
+const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
+const PREF_APP_UPDATE_AUTO = "app.update.auto";
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+const UNKNOWN_XPCOM_ABI = "unknownABI";
+
+const UPDATE_REQUEST_VERSION = 2;
+const CATEGORY_UPDATE_PARAMS = "extension-update-params";
+
+const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist";
+
+const KEY_PROFILEDIR = "ProfD";
+const KEY_APPDIR = "XCurProcD";
+const FILE_BLOCKLIST = "blocklist.xml";
+
+const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
+const PREF_EM_CHECK_COMPATIBILITY = "extensions.enableCompatibilityChecking";
+
+const TOOLKIT_ID = "toolkit@mozilla.org";
+
+const VALID_TYPES_REGEXP = /^[\w\-]+$/;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Async.jsm");
+Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
+ "resource://gre/modules/addons/AddonRepository.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() {
+ let certUtils = {};
+ Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
+ return certUtils;
+});
+
+
+this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ];
+
+const CATEGORY_PROVIDER_MODULE = "addon-provider-module";
+
+// A list of providers to load by default
+const DEFAULT_PROVIDERS = [
+ "resource://gre/modules/addons/XPIProvider.jsm",
+ "resource://gre/modules/LightweightThemeManager.jsm"
+];
+
+Cu.import("resource://gre/modules/Log.jsm");
+// Configure a logger at the parent 'addons' level to format
+// messages for all the modules under addons.*
+const PARENT_LOGGER_ID = "addons";
+var parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID);
+parentLogger.level = Log.Level.Warn;
+var formatter = new Log.BasicFormatter();
+// Set parent logger (and its children) to append to
+// the Javascript section of the Browser Console
+parentLogger.addAppender(new Log.ConsoleAppender(formatter));
+// Set parent logger (and its children) to
+// also append to standard out
+parentLogger.addAppender(new Log.DumpAppender(formatter));
+
+// Create a new logger (child of 'addons' logger)
+// for use by the Addons Manager
+const LOGGER_ID = "addons.manager";
+var logger = Log.repository.getLogger(LOGGER_ID);
+
+// Provide the ability to enable/disable logging
+// messages at runtime.
+// If the "extensions.logging.enabled" preference is
+// missing or 'false', messages at the WARNING and higher
+// severity should be logged to the JS console and standard error.
+// If "extensions.logging.enabled" is set to 'true', messages
+// at DEBUG and higher should go to JS console and standard error.
+const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+
+const UNNAMED_PROVIDER = "<unnamed-provider>";
+function providerName(aProvider) {
+ return aProvider.name || UNNAMED_PROVIDER;
+}
+
+/**
+ * Preference listener which listens for a change in the
+ * "extensions.logging.enabled" preference and changes the logging level of the
+ * parent 'addons' level logger accordingly.
+ */
+var PrefObserver = {
+ init: function() {
+ Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "xpcom-shutdown") {
+ Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ }
+ else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
+ let debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED, false);
+ if (debugLogEnabled) {
+ parentLogger.level = Log.Level.Debug;
+ }
+ else {
+ parentLogger.level = Log.Level.Warn;
+ }
+ }
+ }
+};
+
+PrefObserver.init();
+
+/**
+ * Calls a callback method consuming any thrown exception. Any parameters after
+ * the callback parameter will be passed to the callback.
+ *
+ * @param aCallback
+ * The callback method to call
+ */
+function safeCall(aCallback, ...aArgs) {
+ try {
+ aCallback.apply(null, aArgs);
+ }
+ catch (e) {
+ logger.warn("Exception calling callback", e);
+ }
+}
+
+/**
+ * Report an exception thrown by a provider API method.
+ */
+function reportProviderError(aProvider, aMethod, aError) {
+ let method = `provider ${providerName(aProvider)}.${aMethod}`;
+ logger.error("Exception calling " + method, aError);
+}
+
+/**
+ * Calls a method on a provider if it exists and consumes any thrown exception.
+ * Any parameters after the aDefault parameter are passed to the provider's method.
+ *
+ * @param aProvider
+ * The provider to call
+ * @param aMethod
+ * The method name to call
+ * @param aDefault
+ * A default return value if the provider does not implement the named
+ * method or throws an error.
+ * @return the return value from the provider, or aDefault if the provider does not
+ * implement method or throws an error
+ */
+function callProvider(aProvider, aMethod, aDefault, ...aArgs) {
+ if (!(aMethod in aProvider))
+ return aDefault;
+
+ try {
+ return aProvider[aMethod].apply(aProvider, aArgs);
+ }
+ catch (e) {
+ reportProviderError(aProvider, aMethod, e);
+ return aDefault;
+ }
+}
+
+/**
+ * Calls a method on a provider if it exists and consumes any thrown exception.
+ * Parameters after aMethod are passed to aProvider.aMethod().
+ * The last parameter must be a callback function.
+ * If the provider does not implement the method, or the method throws, calls
+ * the callback with 'undefined'.
+ *
+ * @param aProvider
+ * The provider to call
+ * @param aMethod
+ * The method name to call
+ */
+function callProviderAsync(aProvider, aMethod, ...aArgs) {
+ let callback = aArgs[aArgs.length - 1];
+ if (!(aMethod in aProvider)) {
+ callback(undefined);
+ return;
+ }
+ try {
+ return aProvider[aMethod].apply(aProvider, aArgs);
+ }
+ catch (e) {
+ reportProviderError(aProvider, aMethod, e);
+ callback(undefined);
+ return;
+ }
+}
+
+/**
+ * Gets the currently selected locale for display.
+ * @return the selected locale or "en-US" if none is selected
+ */
+function getLocale() {
+ try {
+ if (Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE))
+ return Services.locale.getLocaleComponentForUserAgent();
+ }
+ catch (e) { }
+
+ try {
+ let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
+ Ci.nsIPrefLocalizedString);
+ if (locale)
+ return locale;
+ }
+ catch (e) { }
+
+ try {
+ return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
+ }
+ catch (e) { }
+
+ return "en-US";
+}
+
+/**
+ * Previously the APIs for installing add-ons from webpages accepted nsIURI
+ * arguments for the installing page. They now take an nsIPrincipal but for now
+ * maintain backwards compatibility by converting an nsIURI to an nsIPrincipal.
+ *
+ * @param aPrincipalOrURI
+ * The argument passed to the API function. Can be null, an nsIURI or
+ * an nsIPrincipal.
+ * @return an nsIPrincipal.
+ */
+function ensurePrincipal(principalOrURI) {
+ if (principalOrURI instanceof Ci.nsIPrincipal)
+ return principalOrURI;
+
+ logger.warn("Deprecated API call, please pass a non-null nsIPrincipal instead of an nsIURI");
+
+ // Previously a null installing URI meant allowing the install regardless.
+ if (!principalOrURI) {
+ return Services.scriptSecurityManager.getSystemPrincipal();
+ }
+
+ if (principalOrURI instanceof Ci.nsIURI) {
+ return Services.scriptSecurityManager.createCodebasePrincipal(principalOrURI, {
+ inBrowser: true
+ });
+ }
+
+ // Just return whatever we have, the API method will log an error about it.
+ return principalOrURI;
+}
+
+/**
+ * A helper class to repeatedly call a listener with each object in an array
+ * optionally checking whether the object has a method in it.
+ *
+ * @param aObjects
+ * The array of objects to iterate through
+ * @param aMethod
+ * An optional method name, if not null any objects without this method
+ * will not be passed to the listener
+ * @param aListener
+ * A listener implementing nextObject and noMoreObjects methods. The
+ * former will be called with the AsyncObjectCaller as the first
+ * parameter and the object as the second. noMoreObjects will be passed
+ * just the AsyncObjectCaller
+ */
+function AsyncObjectCaller(aObjects, aMethod, aListener) {
+ this.objects = [...aObjects];
+ this.method = aMethod;
+ this.listener = aListener;
+
+ this.callNext();
+}
+
+AsyncObjectCaller.prototype = {
+ objects: null,
+ method: null,
+ listener: null,
+
+ /**
+ * Passes the next object to the listener or calls noMoreObjects if there
+ * are none left.
+ */
+ callNext: function() {
+ if (this.objects.length == 0) {
+ this.listener.noMoreObjects(this);
+ return;
+ }
+
+ let object = this.objects.shift();
+ if (!this.method || this.method in object)
+ this.listener.nextObject(this, object);
+ else
+ this.callNext();
+ }
+};
+
+/**
+ * Listens for a browser changing origin and cancels the installs that were
+ * started by it.
+ */
+function BrowserListener(aBrowser, aInstallingPrincipal, aInstalls) {
+ this.browser = aBrowser;
+ this.principal = aInstallingPrincipal;
+ this.installs = aInstalls;
+ this.installCount = aInstalls.length;
+
+ aBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
+ Services.obs.addObserver(this, "message-manager-disconnect", true);
+
+ for (let install of this.installs)
+ install.addListener(this);
+
+ this.registered = true;
+}
+
+BrowserListener.prototype = {
+ browser: null,
+ installs: null,
+ installCount: null,
+ registered: false,
+
+ unregister: function() {
+ if (!this.registered)
+ return;
+ this.registered = false;
+
+ Services.obs.removeObserver(this, "message-manager-disconnect");
+ // The browser may have already been detached
+ if (this.browser.removeProgressListener)
+ this.browser.removeProgressListener(this);
+
+ for (let install of this.installs)
+ install.removeListener(this);
+ this.installs = null;
+ },
+
+ cancelInstalls: function() {
+ for (let install of this.installs) {
+ try {
+ install.cancel();
+ }
+ catch (e) {
+ // Some installs may have already failed or been cancelled, ignore these
+ }
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ if (subject != this.browser.messageManager)
+ return;
+
+ // The browser's message manager has closed and so the browser is
+ // going away, cancel all installs
+ this.cancelInstalls();
+ },
+
+ onLocationChange: function(webProgress, request, location) {
+ if (this.browser.contentPrincipal && this.principal.subsumes(this.browser.contentPrincipal))
+ return;
+
+ // The browser has navigated to a new origin so cancel all installs
+ this.cancelInstalls();
+ },
+
+ onDownloadCancelled: function(install) {
+ // Don't need to hear more events from this install
+ install.removeListener(this);
+
+ // Once all installs have ended unregister everything
+ if (--this.installCount == 0)
+ this.unregister();
+ },
+
+ onDownloadFailed: function(install) {
+ this.onDownloadCancelled(install);
+ },
+
+ onInstallFailed: function(install) {
+ this.onDownloadCancelled(install);
+ },
+
+ onInstallEnded: function(install) {
+ this.onDownloadCancelled(install);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
+ Ci.nsIWebProgressListener,
+ Ci.nsIObserver])
+};
+
+/**
+ * This represents an author of an add-on (e.g. creator or developer)
+ *
+ * @param aName
+ * The name of the author
+ * @param aURL
+ * The URL of the author's profile page
+ */
+function AddonAuthor(aName, aURL) {
+ this.name = aName;
+ this.url = aURL;
+}
+
+AddonAuthor.prototype = {
+ name: null,
+ url: null,
+
+ // Returns the author's name, defaulting to the empty string
+ toString: function() {
+ return this.name || "";
+ }
+}
+
+/**
+ * This represents an screenshot for an add-on
+ *
+ * @param aURL
+ * The URL to the full version of the screenshot
+ * @param aWidth
+ * The width in pixels of the screenshot
+ * @param aHeight
+ * The height in pixels of the screenshot
+ * @param aThumbnailURL
+ * The URL to the thumbnail version of the screenshot
+ * @param aThumbnailWidth
+ * The width in pixels of the thumbnail version of the screenshot
+ * @param aThumbnailHeight
+ * The height in pixels of the thumbnail version of the screenshot
+ * @param aCaption
+ * The caption of the screenshot
+ */
+function AddonScreenshot(aURL, aWidth, aHeight, aThumbnailURL,
+ aThumbnailWidth, aThumbnailHeight, aCaption) {
+ this.url = aURL;
+ if (aWidth) this.width = aWidth;
+ if (aHeight) this.height = aHeight;
+ if (aThumbnailURL) this.thumbnailURL = aThumbnailURL;
+ if (aThumbnailWidth) this.thumbnailWidth = aThumbnailWidth;
+ if (aThumbnailHeight) this.thumbnailHeight = aThumbnailHeight;
+ if (aCaption) this.caption = aCaption;
+}
+
+AddonScreenshot.prototype = {
+ url: null,
+ width: null,
+ height: null,
+ thumbnailURL: null,
+ thumbnailWidth: null,
+ thumbnailHeight: null,
+ caption: null,
+
+ // Returns the screenshot URL, defaulting to the empty string
+ toString: function() {
+ return this.url || "";
+ }
+}
+
+
+/**
+ * This represents a compatibility override for an addon.
+ *
+ * @param aType
+ * Overrride type - "compatible" or "incompatible"
+ * @param aMinVersion
+ * Minimum version of the addon to match
+ * @param aMaxVersion
+ * Maximum version of the addon to match
+ * @param aAppID
+ * Application ID used to match appMinVersion and appMaxVersion
+ * @param aAppMinVersion
+ * Minimum version of the application to match
+ * @param aAppMaxVersion
+ * Maximum version of the application to match
+ */
+function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID,
+ aAppMinVersion, aAppMaxVersion) {
+ this.type = aType;
+ this.minVersion = aMinVersion;
+ this.maxVersion = aMaxVersion;
+ this.appID = aAppID;
+ this.appMinVersion = aAppMinVersion;
+ this.appMaxVersion = aAppMaxVersion;
+}
+
+AddonCompatibilityOverride.prototype = {
+ /**
+ * Type of override - "incompatible" or "compatible".
+ * Only "incompatible" is supported for now.
+ */
+ type: null,
+
+ /**
+ * Min version of the addon to match.
+ */
+ minVersion: null,
+
+ /**
+ * Max version of the addon to match.
+ */
+ maxVersion: null,
+
+ /**
+ * Application ID to match.
+ */
+ appID: null,
+
+ /**
+ * Min version of the application to match.
+ */
+ appMinVersion: null,
+
+ /**
+ * Max version of the application to match.
+ */
+ appMaxVersion: null
+};
+
+
+/**
+ * A type of add-on, used by the UI to determine how to display different types
+ * of add-ons.
+ *
+ * @param aID
+ * The add-on type ID
+ * @param aLocaleURI
+ * The URI of a localized properties file to get the displayable name
+ * for the type from
+ * @param aLocaleKey
+ * The key for the string in the properties file or the actual display
+ * name if aLocaleURI is null. Include %ID% to include the type ID in
+ * the key
+ * @param aViewType
+ * The optional type of view to use in the UI
+ * @param aUIPriority
+ * The priority is used by the UI to list the types in order. Lower
+ * values push the type higher in the list.
+ * @param aFlags
+ * An option set of flags that customize the display of the add-on in
+ * the UI.
+ */
+function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) {
+ if (!aID)
+ throw Components.Exception("An AddonType must have an ID", Cr.NS_ERROR_INVALID_ARG);
+
+ if (aViewType && aUIPriority === undefined)
+ throw Components.Exception("An AddonType with a defined view must have a set UI priority",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aLocaleKey)
+ throw Components.Exception("An AddonType must have a displayable name",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ this.id = aID;
+ this.uiPriority = aUIPriority;
+ this.viewType = aViewType;
+ this.flags = aFlags;
+
+ if (aLocaleURI) {
+ this.__defineGetter__("name", function nameGetter() {
+ delete this.name;
+ let bundle = Services.strings.createBundle(aLocaleURI);
+ this.name = bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID));
+ return this.name;
+ });
+ }
+ else {
+ this.name = aLocaleKey;
+ }
+}
+
+var gStarted = false;
+var gStartupComplete = false;
+var gCheckCompatibility = true;
+var gStrictCompatibility = true;
+var gCheckUpdateSecurityDefault = true;
+var gCheckUpdateSecurity = gCheckUpdateSecurityDefault;
+var gUpdateEnabled = true;
+var gAutoUpdateDefault = true;
+var gShutdownBarrier = null;
+var gRepoShutdownState = "";
+var gShutdownInProgress = false;
+
+/**
+ * This is the real manager, kept here rather than in AddonManager to keep its
+ * contents hidden from API users.
+ */
+var AddonManagerInternal = {
+ managerListeners: [],
+ installListeners: [],
+ addonListeners: [],
+ typeListeners: [],
+ pendingProviders: new Set(),
+ providers: new Set(),
+ providerShutdowns: new Map(),
+ types: {},
+ startupChanges: {},
+
+ validateBlocklist: function() {
+ let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
+
+ // If there is no application shipped blocklist then there is nothing to do
+ if (!appBlocklist.exists())
+ return;
+
+ let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
+
+ // If there is no blocklist in the profile then copy the application shipped
+ // one there
+ if (!profileBlocklist.exists()) {
+ try {
+ appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
+ }
+ catch (e) {
+ logger.warn("Failed to copy the application shipped blocklist to the profile", e);
+ }
+ return;
+ }
+
+ let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ try {
+ let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+ cstream.init(fileStream, "UTF-8", 0, 0);
+
+ let data = "";
+ let str = {};
+ let read = 0;
+ do {
+ read = cstream.readString(0xffffffff, str);
+ data += str.value;
+ } while (read != 0);
+
+ let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+ var doc = parser.parseFromString(data, "text/xml");
+ }
+ catch (e) {
+ logger.warn("Application shipped blocklist could not be loaded", e);
+ return;
+ }
+ finally {
+ try {
+ fileStream.close();
+ }
+ catch (e) {
+ logger.warn("Unable to close blocklist file stream", e);
+ }
+ }
+
+ // If the namespace is incorrect then ignore the application shipped
+ // blocklist
+ if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
+ logger.warn("Application shipped blocklist has an unexpected namespace (" +
+ doc.documentElement.namespaceURI + ")");
+ return;
+ }
+
+ // If there is no lastupdate information then ignore the application shipped
+ // blocklist
+ if (!doc.documentElement.hasAttribute("lastupdate"))
+ return;
+
+ // If the application shipped blocklist is older than the profile blocklist
+ // then do nothing
+ if (doc.documentElement.getAttribute("lastupdate") <=
+ profileBlocklist.lastModifiedTime)
+ return;
+
+ // Otherwise copy the application shipped blocklist to the profile
+ try {
+ appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
+ }
+ catch (e) {
+ logger.warn("Failed to copy the application shipped blocklist to the profile", e);
+ }
+ },
+
+ /**
+ * Start up a provider, and register its shutdown hook if it has one
+ */
+ _startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ logger.debug(`Starting provider: ${providerName(aProvider)}`);
+ callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion);
+ if ('shutdown' in aProvider) {
+ let name = providerName(aProvider);
+ let AMProviderShutdown = () => {
+ // If the provider has been unregistered, it will have been removed from
+ // this.providers. If it hasn't been unregistered, then this is a normal
+ // shutdown - and we move it to this.pendingProviders incase we're
+ // running in a test that will start AddonManager again.
+ if (this.providers.has(aProvider)) {
+ this.providers.delete(aProvider);
+ this.pendingProviders.add(aProvider);
+ }
+
+ return new Promise((resolve, reject) => {
+ logger.debug("Calling shutdown blocker for " + name);
+ resolve(aProvider.shutdown());
+ })
+ .catch(err => {
+ logger.warn("Failure during shutdown of " + name, err);
+ });
+ };
+ logger.debug("Registering shutdown blocker for " + name);
+ this.providerShutdowns.set(aProvider, AMProviderShutdown);
+ AddonManager.shutdown.addBlocker(name, AMProviderShutdown);
+ }
+
+ this.pendingProviders.delete(aProvider);
+ this.providers.add(aProvider);
+ logger.debug(`Provider finished startup: ${providerName(aProvider)}`);
+ },
+
+ /**
+ * Initializes the AddonManager, loading any known providers and initializing
+ * them.
+ */
+ startup: function() {
+ try {
+ if (gStarted)
+ return;
+
+ let appChanged = undefined;
+
+ let oldAppVersion = null;
+ try {
+ oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION);
+ appChanged = Services.appinfo.version != oldAppVersion;
+ }
+ catch (e) { }
+
+ let oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION, "");
+
+ if (appChanged !== false) {
+ logger.debug("Application has been upgraded");
+ Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
+ Services.appinfo.version);
+ Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
+ Services.appinfo.greVersion);
+ Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION,
+ (appChanged === undefined ? 0 : -1));
+ this.validateBlocklist();
+ }
+
+ gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY,
+ gCheckCompatibility);
+ Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false);
+
+ gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY,
+ gStrictCompatibility);
+ Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false);
+
+ let defaultBranch = Services.prefs.getDefaultBranch("");
+ gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY,
+ gCheckUpdateSecurityDefault);
+
+ gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY,
+ gCheckUpdateSecurity);
+ Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false);
+
+ gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED, gUpdateEnabled);
+ Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false);
+
+ gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT,
+ gAutoUpdateDefault);
+ Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false);
+
+ let defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED, true);
+
+ // Ensure all default providers have had a chance to register themselves
+ if (defaultProvidersEnabled) {
+ for (let url of DEFAULT_PROVIDERS) {
+ try {
+ let scope = {};
+ Components.utils.import(url, scope);
+ // Sanity check - make sure the provider exports a symbol that
+ // has a 'startup' method
+ let syms = Object.keys(scope);
+ if ((syms.length < 1) ||
+ (typeof scope[syms[0]].startup != "function")) {
+ logger.warn("Provider " + url + " has no startup()");
+ }
+ logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource());
+ }
+ catch (e) {
+ logger.error("Exception loading default provider \"" + url + "\"", e);
+ }
+ };
+ }
+
+ // Load any providers registered in the category manager
+ let catman = Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager);
+ let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE);
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
+ let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry);
+
+ try {
+ Components.utils.import(url, {});
+ logger.debug(`Loaded provider scope for ${url}`);
+ }
+ catch (e) {
+ logger.error("Exception loading provider " + entry + " from category \"" +
+ url + "\"", e);
+ }
+ }
+
+ // Register our shutdown handler with the AsyncShutdown manager
+ gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for providers to shut down.");
+ AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down.",
+ this.shutdownManager.bind(this),
+ {fetchState: this.shutdownState.bind(this)});
+
+ // Once we start calling providers we must allow all normal methods to work.
+ gStarted = true;
+
+ for (let provider of this.pendingProviders) {
+ this._startProvider(provider, appChanged, oldAppVersion, oldPlatformVersion);
+ }
+
+ // If this is a new profile just pretend that there were no changes
+ if (appChanged === undefined) {
+ for (let type in this.startupChanges)
+ delete this.startupChanges[type];
+ }
+
+ gStartupComplete = true;
+ }
+ catch (e) {
+ logger.error("startup failed", e);
+ }
+
+ logger.debug("Completed startup sequence");
+ this.callManagerListeners("onStartup");
+ },
+
+ /**
+ * Registers a new AddonProvider.
+ *
+ * @param aProvider
+ * The provider to register
+ * @param aTypes
+ * An optional array of add-on types
+ */
+ registerProvider: function(aProvider, aTypes) {
+ if (!aProvider || typeof aProvider != "object")
+ throw Components.Exception("aProvider must be specified",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aTypes && !Array.isArray(aTypes))
+ throw Components.Exception("aTypes must be an array or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ this.pendingProviders.add(aProvider);
+
+ if (aTypes) {
+ aTypes.forEach(function(aType) {
+ if (!(aType.id in this.types)) {
+ if (!VALID_TYPES_REGEXP.test(aType.id)) {
+ logger.warn("Ignoring invalid type " + aType.id);
+ return;
+ }
+
+ this.types[aType.id] = {
+ type: aType,
+ providers: [aProvider]
+ };
+
+ let typeListeners = this.typeListeners.slice(0);
+ for (let listener of typeListeners) {
+ safeCall(function listenerSafeCall() {
+ listener.onTypeAdded(aType);
+ });
+ }
+ }
+ else {
+ this.types[aType.id].providers.push(aProvider);
+ }
+ }, this);
+ }
+
+ // If we're registering after startup call this provider's startup.
+ if (gStarted) {
+ this._startProvider(aProvider);
+ }
+ },
+
+ /**
+ * Unregisters an AddonProvider.
+ *
+ * @param aProvider
+ * The provider to unregister
+ * @return Whatever the provider's 'shutdown' method returns (if anything).
+ * For providers that have async shutdown methods returning Promises,
+ * the caller should wait for that Promise to resolve.
+ */
+ unregisterProvider: function(aProvider) {
+ if (!aProvider || typeof aProvider != "object")
+ throw Components.Exception("aProvider must be specified",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ this.providers.delete(aProvider);
+ // The test harness will unregister XPIProvider *after* shutdown, which is
+ // after the provider will have been moved from providers to
+ // pendingProviders.
+ this.pendingProviders.delete(aProvider);
+
+ for (let type in this.types) {
+ this.types[type].providers = this.types[type].providers.filter(function filterProvider(p) p != aProvider);
+ if (this.types[type].providers.length == 0) {
+ let oldType = this.types[type].type;
+ delete this.types[type];
+
+ let typeListeners = this.typeListeners.slice(0);
+ for (let listener of typeListeners) {
+ safeCall(function listenerSafeCall() {
+ listener.onTypeRemoved(oldType);
+ });
+ }
+ }
+ }
+
+ // If we're unregistering after startup but before shutting down,
+ // remove the blocker for this provider's shutdown and call it.
+ // If we're already shutting down, just let gShutdownBarrier call it to avoid races.
+ if (gStarted && !gShutdownInProgress) {
+ logger.debug("Unregistering shutdown blocker for " + providerName(aProvider));
+ let shutter = this.providerShutdowns.get(aProvider);
+ if (shutter) {
+ this.providerShutdowns.delete(aProvider);
+ gShutdownBarrier.client.removeBlocker(shutter);
+ return shutter();
+ }
+ }
+ return undefined;
+ },
+
+ /**
+ * Mark a provider as safe to access via AddonManager APIs, before its
+ * startup has completed.
+ *
+ * Normally a provider isn't marked as safe until after its (synchronous)
+ * startup() method has returned. Until a provider has been marked safe,
+ * it won't be used by any of the AddonManager APIs. markProviderSafe()
+ * allows a provider to mark itself as safe during its startup; this can be
+ * useful if the provider wants to perform tasks that block startup, which
+ * happen after its required initialization tasks and therefore when the
+ * provider is in a safe state.
+ *
+ * @param aProvider Provider object to mark safe
+ */
+ markProviderSafe: function(aProvider) {
+ if (!gStarted) {
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ if (!aProvider || typeof aProvider != "object") {
+ throw Components.Exception("aProvider must be specified",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ if (!this.pendingProviders.has(aProvider)) {
+ return;
+ }
+
+ this.pendingProviders.delete(aProvider);
+ this.providers.add(aProvider);
+ },
+
+ /**
+ * Calls a method on all registered providers if it exists and consumes any
+ * thrown exception. Return values are ignored. Any parameters after the
+ * method parameter are passed to the provider's method.
+ * WARNING: Do not use for asynchronous calls; callProviders() does not
+ * invoke callbacks if provider methods throw synchronous exceptions.
+ *
+ * @param aMethod
+ * The method name to call
+ * @see callProvider
+ */
+ callProviders: function(aMethod, ...aArgs) {
+ if (!aMethod || typeof aMethod != "string")
+ throw Components.Exception("aMethod must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let providers = [...this.providers];
+ for (let provider of providers) {
+ try {
+ if (aMethod in provider)
+ provider[aMethod].apply(provider, aArgs);
+ }
+ catch (e) {
+ reportProviderError(aProvider, aMethod, e);
+ }
+ }
+ },
+
+ /**
+ * Report the current state of asynchronous shutdown
+ */
+ shutdownState() {
+ let state = [];
+ if (gShutdownBarrier) {
+ state.push({
+ name: gShutdownBarrier.client.name,
+ state: gShutdownBarrier.state
+ });
+ }
+ state.push({
+ name: "AddonRepository: async shutdown",
+ state: gRepoShutdownState
+ });
+ return state;
+ },
+
+ /**
+ * Shuts down the addon manager and all registered providers, this must clean
+ * up everything in order for automated tests to fake restarts.
+ * @return Promise{null} that resolves when all providers and dependent modules
+ * have finished shutting down
+ */
+ shutdownManager: Task.async(function* () {
+ logger.debug("shutdown");
+ this.callManagerListeners("onShutdown");
+
+ gRepoShutdownState = "pending";
+ gShutdownInProgress = true;
+ // Clean up listeners
+ Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this);
+ Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
+ Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);
+ Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this);
+ Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
+
+ let savedError = null;
+ // Only shut down providers if they've been started.
+ if (gStarted) {
+ try {
+ yield gShutdownBarrier.wait();
+ }
+ catch(err) {
+ savedError = err;
+ logger.error("Failure during wait for shutdown barrier", err);
+ }
+ }
+
+ // Shut down AddonRepository after providers (if any).
+ try {
+ gRepoShutdownState = "in progress";
+ yield AddonRepository.shutdown();
+ gRepoShutdownState = "done";
+ }
+ catch(err) {
+ savedError = err;
+ logger.error("Failure during AddonRepository shutdown", err);
+ }
+
+ logger.debug("Async provider shutdown done");
+ this.managerListeners.splice(0, this.managerListeners.length);
+ this.installListeners.splice(0, this.installListeners.length);
+ this.addonListeners.splice(0, this.addonListeners.length);
+ this.typeListeners.splice(0, this.typeListeners.length);
+ this.providerShutdowns.clear();
+ for (let type in this.startupChanges)
+ delete this.startupChanges[type];
+ gStarted = false;
+ gStartupComplete = false;
+ gShutdownBarrier = null;
+ gShutdownInProgress = false;
+ if (savedError) {
+ throw savedError;
+ }
+ }),
+
+ /**
+ * Notified when a preference we're interested in has changed.
+ *
+ * @see nsIObserver
+ */
+ observe: function(aSubject, aTopic, aData) {
+ switch (aData) {
+ case PREF_EM_CHECK_COMPATIBILITY: {
+ let oldValue = gCheckCompatibility;
+ gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY, true);
+
+ this.callManagerListeners("onCompatibilityModeChanged");
+
+ if (gCheckCompatibility != oldValue)
+ this.updateAddonAppDisabledStates();
+
+ break;
+ }
+ case PREF_EM_STRICT_COMPATIBILITY: {
+ let oldValue = gStrictCompatibility;
+ gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);
+
+ this.callManagerListeners("onCompatibilityModeChanged");
+
+ if (gStrictCompatibility != oldValue)
+ this.updateAddonAppDisabledStates();
+
+ break;
+ }
+ case PREF_EM_CHECK_UPDATE_SECURITY: {
+ let oldValue = gCheckUpdateSecurity;
+ gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, true);
+
+ this.callManagerListeners("onCheckUpdateSecurityChanged");
+
+ if (gCheckUpdateSecurity != oldValue)
+ this.updateAddonAppDisabledStates();
+
+ break;
+ }
+ case PREF_EM_UPDATE_ENABLED: {
+ let oldValue = gUpdateEnabled;
+ gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED, true);
+
+ this.callManagerListeners("onUpdateModeChanged");
+ break;
+ }
+ case PREF_EM_AUTOUPDATE_DEFAULT: {
+ let oldValue = gAutoUpdateDefault;
+ gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, true);
+
+ this.callManagerListeners("onUpdateModeChanged");
+ break;
+ }
+ }
+ },
+
+ /**
+ * Replaces %...% strings in an addon url (update and updateInfo) with
+ * appropriate values.
+ *
+ * @param aAddon
+ * The Addon representing the add-on
+ * @param aUri
+ * The string representation of the URI to escape
+ * @param aAppVersion
+ * The optional application version to use for %APP_VERSION%
+ * @return The appropriately escaped URI.
+ */
+ escapeAddonURI: function(aAddon, aUri, aAppVersion)
+ {
+ if (!aAddon || typeof aAddon != "object")
+ throw Components.Exception("aAddon must be an Addon object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aUri || typeof aUri != "string")
+ throw Components.Exception("aUri must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aAppVersion && typeof aAppVersion != "string")
+ throw Components.Exception("aAppVersion must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled"
+ : "userEnabled";
+
+ if (!aAddon.isCompatible)
+ addonStatus += ",incompatible";
+ if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
+ addonStatus += ",blocklisted";
+ if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+ addonStatus += ",softblocked";
+
+ try {
+ var xpcomABI = Services.appinfo.XPCOMABI;
+ } catch (ex) {
+ xpcomABI = UNKNOWN_XPCOM_ABI;
+ }
+
+ let uri = aUri.replace(/%ITEM_ID%/g, aAddon.id);
+ uri = uri.replace(/%ITEM_VERSION%/g, aAddon.version);
+ uri = uri.replace(/%ITEM_STATUS%/g, addonStatus);
+ uri = uri.replace(/%APP_ID%/g, Services.appinfo.ID);
+ uri = uri.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion :
+ Services.appinfo.version);
+ uri = uri.replace(/%REQ_VERSION%/g, UPDATE_REQUEST_VERSION);
+ uri = uri.replace(/%APP_OS%/g, Services.appinfo.OS);
+ uri = uri.replace(/%APP_ABI%/g, xpcomABI);
+ uri = uri.replace(/%APP_LOCALE%/g, getLocale());
+ uri = uri.replace(/%CURRENT_APP_VERSION%/g, Services.appinfo.version);
+
+ // Replace custom parameters (names of custom parameters must have at
+ // least 3 characters to prevent lookups for something like %D0%C8)
+ var catMan = null;
+ uri = uri.replace(/%(\w{3,})%/g, function parameterReplace(aMatch, aParam) {
+ if (!catMan) {
+ catMan = Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager);
+ }
+
+ try {
+ var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, aParam);
+ var paramHandler = Cc[contractID].getService(Ci.nsIPropertyBag2);
+ return paramHandler.getPropertyAsAString(aParam);
+ }
+ catch(e) {
+ return aMatch;
+ }
+ });
+
+ // escape() does not properly encode + symbols in any embedded FVF strings.
+ return uri.replace(/\+/g, "%2B");
+ },
+
+ /**
+ * Performs a background update check by starting an update for all add-ons
+ * that can be updated.
+ * @return Promise{null} Resolves when the background update check is complete
+ * (the resulting addon installations may still be in progress).
+ */
+ backgroundUpdateCheck: function() {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ let buPromise = Task.spawn(function* backgroundUpdateTask() {
+ logger.debug("Background update check beginning");
+
+ Services.obs.notifyObservers(null, "addons-background-update-start", null);
+
+ if (this.updateEnabled) {
+ let scope = {};
+ Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope);
+ scope.LightweightThemeManager.updateCurrentTheme();
+
+ let allAddons = yield new Promise((resolve, reject) => this.getAllAddons(resolve));
+
+ // Repopulate repository cache first, to ensure compatibility overrides
+ // are up to date before checking for addon updates.
+ yield AddonRepository.backgroundUpdateCheck();
+
+ // Keep track of all the async add-on updates happening in parallel
+ let updates = [];
+
+ for (let addon of allAddons) {
+ // Check all add-ons for updates so that any compatibility updates will
+ // be applied
+ updates.push(new Promise((resolve, reject) => {
+ addon.findUpdates({
+ onUpdateAvailable: function(aAddon, aInstall) {
+ // Start installing updates when the add-on can be updated and
+ // background updates should be applied.
+ logger.debug("Found update for add-on ${id}", aAddon);
+ if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE &&
+ AddonManager.shouldAutoUpdate(aAddon)) {
+ // XXX we really should resolve when this install is done,
+ // not when update-available check completes, no?
+ logger.debug("Starting install of ${id}", aAddon);
+ aInstall.install();
+ }
+ },
+
+ onUpdateFinished: aAddon => { logger.debug("onUpdateFinished for ${id}", aAddon); resolve(); }
+ }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+ }));
+ }
+ yield Promise.all(updates);
+ }
+
+ logger.debug("Background update check complete");
+ Services.obs.notifyObservers(null,
+ "addons-background-update-complete",
+ null);
+ }.bind(this));
+ // Fork the promise chain so we can log the error and let our caller see it too.
+ buPromise.then(null, e => logger.warn("Error in background update", e));
+ return buPromise;
+ },
+
+ /**
+ * Adds a add-on to the list of detected changes for this startup. If
+ * addStartupChange is called multiple times for the same add-on in the same
+ * startup then only the most recent change will be remembered.
+ *
+ * @param aType
+ * The type of change as a string. Providers can define their own
+ * types of changes or use the existing defined STARTUP_CHANGE_*
+ * constants
+ * @param aID
+ * The ID of the add-on
+ */
+ addStartupChange: function(aType, aID) {
+ if (!aType || typeof aType != "string")
+ throw Components.Exception("aType must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aID || typeof aID != "string")
+ throw Components.Exception("aID must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (gStartupComplete)
+ return;
+
+ // Ensure that an ID is only listed in one type of change
+ for (let type in this.startupChanges)
+ this.removeStartupChange(type, aID);
+
+ if (!(aType in this.startupChanges))
+ this.startupChanges[aType] = [];
+ this.startupChanges[aType].push(aID);
+ },
+
+ /**
+ * Removes a startup change for an add-on.
+ *
+ * @param aType
+ * The type of change
+ * @param aID
+ * The ID of the add-on
+ */
+ removeStartupChange: function(aType, aID) {
+ if (!aType || typeof aType != "string")
+ throw Components.Exception("aType must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aID || typeof aID != "string")
+ throw Components.Exception("aID must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (gStartupComplete)
+ return;
+
+ if (!(aType in this.startupChanges))
+ return;
+
+ this.startupChanges[aType] = this.startupChanges[aType].filter(
+ function filterItem(aItem) aItem != aID);
+ },
+
+ /**
+ * Calls all registered AddonManagerListeners with an event. Any parameters
+ * after the method parameter are passed to the listener.
+ *
+ * @param aMethod
+ * The method on the listeners to call
+ */
+ callManagerListeners: function(aMethod, ...aArgs) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMethod || typeof aMethod != "string")
+ throw Components.Exception("aMethod must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let managerListeners = this.managerListeners.slice(0);
+ for (let listener of managerListeners) {
+ try {
+ if (aMethod in listener)
+ listener[aMethod].apply(listener, aArgs);
+ }
+ catch (e) {
+ logger.warn("AddonManagerListener threw exception when calling " + aMethod, e);
+ }
+ }
+ },
+
+ /**
+ * Calls all registered InstallListeners with an event. Any parameters after
+ * the extraListeners parameter are passed to the listener.
+ *
+ * @param aMethod
+ * The method on the listeners to call
+ * @param aExtraListeners
+ * An optional array of extra InstallListeners to also call
+ * @return false if any of the listeners returned false, true otherwise
+ */
+ callInstallListeners: function(aMethod,
+ aExtraListeners, ...aArgs) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMethod || typeof aMethod != "string")
+ throw Components.Exception("aMethod must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aExtraListeners && !Array.isArray(aExtraListeners))
+ throw Components.Exception("aExtraListeners must be an array or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let result = true;
+ let listeners;
+ if (aExtraListeners)
+ listeners = aExtraListeners.concat(this.installListeners);
+ else
+ listeners = this.installListeners.slice(0);
+
+ for (let listener of listeners) {
+ try {
+ if (aMethod in listener) {
+ if (listener[aMethod].apply(listener, aArgs) === false)
+ result = false;
+ }
+ }
+ catch (e) {
+ logger.warn("InstallListener threw exception when calling " + aMethod, e);
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Calls all registered AddonListeners with an event. Any parameters after
+ * the method parameter are passed to the listener.
+ *
+ * @param aMethod
+ * The method on the listeners to call
+ */
+ callAddonListeners: function(aMethod, ...aArgs) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMethod || typeof aMethod != "string")
+ throw Components.Exception("aMethod must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let addonListeners = this.addonListeners.slice(0);
+ for (let listener of addonListeners) {
+ try {
+ if (aMethod in listener)
+ listener[aMethod].apply(listener, aArgs);
+ }
+ catch (e) {
+ logger.warn("AddonListener threw exception when calling " + aMethod, e);
+ }
+ }
+ },
+
+ /**
+ * Notifies all providers that an add-on has been enabled when that type of
+ * add-on only supports a single add-on being enabled at a time. This allows
+ * the providers to disable theirs if necessary.
+ *
+ * @param aID
+ * The ID of the enabled add-on
+ * @param aType
+ * The type of the enabled add-on
+ * @param aPendingRestart
+ * A boolean indicating if the change will only take place the next
+ * time the application is restarted
+ */
+ notifyAddonChanged: function(aID, aType, aPendingRestart) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (aID && typeof aID != "string")
+ throw Components.Exception("aID must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aType || typeof aType != "string")
+ throw Components.Exception("aType must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ // Temporary hack until bug 520124 lands.
+ // We can get here during synchronous startup, at which point it's
+ // considered unsafe (and therefore disallowed by AddonManager.jsm) to
+ // access providers that haven't been initialized yet. Since this is when
+ // XPIProvider is starting up, XPIProvider can't access itself via APIs
+ // going through AddonManager.jsm. Furthermore, LightweightThemeManager may
+ // not be initialized until after XPIProvider is, and therefore would also
+ // be unaccessible during XPIProvider startup. Thankfully, these are the
+ // only two uses of this API, and we know it's safe to use this API with
+ // both providers; so we have this hack to allow bypassing the normal
+ // safetey guard.
+ // The notifyAddonChanged/addonChanged API will be unneeded and therefore
+ // removed by bug 520124, so this is a temporary quick'n'dirty hack.
+ let providers = [...this.providers, ...this.pendingProviders];
+ for (let provider of providers) {
+ callProvider(provider, "addonChanged", null, aID, aType, aPendingRestart);
+ }
+ },
+
+ /**
+ * Notifies all providers they need to update the appDisabled property for
+ * their add-ons in response to an application change such as a blocklist
+ * update.
+ */
+ updateAddonAppDisabledStates: function() {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ this.callProviders("updateAddonAppDisabledStates");
+ },
+
+ /**
+ * Notifies all providers that the repository has updated its data for
+ * installed add-ons.
+ *
+ * @param aCallback
+ * Function to call when operation is complete.
+ */
+ updateAddonRepositoryData: function(aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", {
+ nextObject: function(aCaller, aProvider) {
+ callProviderAsync(aProvider, "updateAddonRepositoryData",
+ aCaller.callNext.bind(aCaller));
+ },
+ noMoreObjects: function(aCaller) {
+ safeCall(aCallback);
+ // only tests should care about this
+ Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated", null);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets an AddonInstall for a URL.
+ *
+ * @param aUrl
+ * The string represenation of the URL the add-on is located at
+ * @param aCallback
+ * A callback to pass the AddonInstall to
+ * @param aMimetype
+ * The mimetype of the add-on
+ * @param aHash
+ * An optional hash of the add-on
+ * @param aName
+ * An optional placeholder name while the add-on is being downloaded
+ * @param aIcons
+ * Optional placeholder icons while the add-on is being downloaded
+ * @param aVersion
+ * An optional placeholder version while the add-on is being downloaded
+ * @param aLoadGroup
+ * An optional nsILoadGroup to associate any network requests with
+ * @throws if the aUrl, aCallback or aMimetype arguments are not specified
+ */
+ getInstallForURL: function(aUrl, aCallback, aMimetype,
+ aHash, aName, aIcons,
+ aVersion, aBrowser) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aUrl || typeof aUrl != "string")
+ throw Components.Exception("aURL must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aMimetype || typeof aMimetype != "string")
+ throw Components.Exception("aMimetype must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aHash && typeof aHash != "string")
+ throw Components.Exception("aHash must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aName && typeof aName != "string")
+ throw Components.Exception("aName must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aIcons) {
+ if (typeof aIcons == "string")
+ aIcons = { "32": aIcons };
+ else if (typeof aIcons != "object")
+ throw Components.Exception("aIcons must be a string, an object or null",
+ Cr.NS_ERROR_INVALID_ARG);
+ } else {
+ aIcons = {};
+ }
+
+ if (aVersion && typeof aVersion != "string")
+ throw Components.Exception("aVersion must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aBrowser && (!(aBrowser instanceof Ci.nsIDOMElement)))
+ throw Components.Exception("aBrowser must be a nsIDOMElement or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let providers = [...this.providers];
+ for (let provider of providers) {
+ if (callProvider(provider, "supportsMimetype", false, aMimetype)) {
+ callProviderAsync(provider, "getInstallForURL",
+ aUrl, aHash, aName, aIcons, aVersion, aBrowser,
+ function getInstallForURL_safeCall(aInstall) {
+ safeCall(aCallback, aInstall);
+ });
+ return;
+ }
+ }
+ safeCall(aCallback, null);
+ },
+
+ /**
+ * Asynchronously gets an AddonInstall for an nsIFile.
+ *
+ * @param aFile
+ * The nsIFile where the add-on is located
+ * @param aCallback
+ * A callback to pass the AddonInstall to
+ * @param aMimetype
+ * An optional mimetype hint for the add-on
+ * @throws if the aFile or aCallback arguments are not specified
+ */
+ getInstallForFile: function(aFile, aCallback, aMimetype) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!(aFile instanceof Ci.nsIFile))
+ throw Components.Exception("aFile must be a nsIFile",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aMimetype && typeof aMimetype != "string")
+ throw Components.Exception("aMimetype must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ new AsyncObjectCaller(this.providers, "getInstallForFile", {
+ nextObject: function(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getInstallForFile", aFile,
+ function getInstallForFile_safeCall(aInstall) {
+ if (aInstall)
+ safeCall(aCallback, aInstall);
+ else
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function(aCaller) {
+ safeCall(aCallback, null);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets all current AddonInstalls optionally limiting to a list
+ * of types.
+ *
+ * @param aTypes
+ * An optional array of types to retrieve. Each type is a string name
+ * @param aCallback
+ * A callback which will be passed an array of AddonInstalls
+ * @throws If the aCallback argument is not specified
+ */
+ getInstallsByTypes: function(aTypes, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (aTypes && !Array.isArray(aTypes))
+ throw Components.Exception("aTypes must be an array or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let installs = [];
+
+ new AsyncObjectCaller(this.providers, "getInstallsByTypes", {
+ nextObject: function(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getInstallsByTypes", aTypes,
+ function getInstallsByTypes_safeCall(aProviderInstalls) {
+ if (aProviderInstalls) {
+ installs = installs.concat(aProviderInstalls);
+ }
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function(aCaller) {
+ safeCall(aCallback, installs);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets all current AddonInstalls.
+ *
+ * @param aCallback
+ * A callback which will be passed an array of AddonInstalls
+ */
+ getAllInstalls: function(aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ this.getInstallsByTypes(null, aCallback);
+ },
+
+ /**
+ * Synchronously map a URI to the corresponding Addon ID.
+ *
+ * Mappable URIs are limited to in-application resources belonging to the
+ * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc.
+ * but do not include URIs from meta data, such as the add-on homepage.
+ *
+ * @param aURI
+ * nsIURI to map to an addon id
+ * @return string containing the Addon ID or null
+ * @see amIAddonManager.mapURIToAddonID
+ */
+ mapURIToAddonID: function(aURI) {
+ if (!(aURI instanceof Ci.nsIURI)) {
+ throw Components.Exception("aURI is not a nsIURI",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // Try all providers
+ let providers = [...this.providers];
+ for (let provider of providers) {
+ var id = callProvider(provider, "mapURIToAddonID", null, aURI);
+ if (id !== null) {
+ return id;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Checks whether installation is enabled for a particular mimetype.
+ *
+ * @param aMimetype
+ * The mimetype to check
+ * @return true if installation is enabled for the mimetype
+ */
+ isInstallEnabled: function(aMimetype) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMimetype || typeof aMimetype != "string")
+ throw Components.Exception("aMimetype must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let providers = [...this.providers];
+ for (let provider of providers) {
+ if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
+ callProvider(provider, "isInstallEnabled"))
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Checks whether a particular source is allowed to install add-ons of a
+ * given mimetype.
+ *
+ * @param aMimetype
+ * The mimetype of the add-on
+ * @param aInstallingPrincipal
+ * The nsIPrincipal that initiated the install
+ * @return true if the source is allowed to install this mimetype
+ */
+ isInstallAllowed: function(aMimetype, aInstallingPrincipal) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMimetype || typeof aMimetype != "string")
+ throw Components.Exception("aMimetype must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal))
+ throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let providers = [...this.providers];
+ for (let provider of providers) {
+ if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
+ callProvider(provider, "isInstallAllowed", null, aInstallingPrincipal))
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Starts installation of an array of AddonInstalls notifying the registered
+ * web install listener of blocked or started installs.
+ *
+ * @param aMimetype
+ * The mimetype of add-ons being installed
+ * @param aBrowser
+ * The optional browser element that started the installs
+ * @param aInstallingPrincipal
+ * The nsIPrincipal that initiated the install
+ * @param aInstalls
+ * The array of AddonInstalls to be installed
+ */
+ installAddonsFromWebpage: function(aMimetype, aBrowser, aInstallingPrincipal, aInstalls) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMimetype || typeof aMimetype != "string")
+ throw Components.Exception("aMimetype must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aBrowser && !(aBrowser instanceof Ci.nsIDOMElement))
+ throw Components.Exception("aSource must be a nsIDOMElement, or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal))
+ throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!Array.isArray(aInstalls))
+ throw Components.Exception("aInstalls must be an array",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) {
+ logger.warn("No web installer available, cancelling all installs");
+ aInstalls.forEach(function(aInstall) {
+ aInstall.cancel();
+ });
+ return;
+ }
+
+ // When a chrome in-content UI has loaded a <browser> inside to host a
+ // website we want to do our security checks on the inner-browser but
+ // notify front-end that install events came from the outer-browser (the
+ // main tab's browser). Check this by seeing if the browser we've been
+ // passed is in a content type docshell and if so get the outer-browser.
+ let topBrowser = aBrowser;
+ let docShell = aBrowser.ownerDocument.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIDocShellTreeItem);
+ if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent)
+ topBrowser = docShell.chromeEventHandler;
+
+ try {
+ let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
+ getService(Ci.amIWebInstallListener);
+
+ if (!this.isInstallEnabled(aMimetype)) {
+ for (let install of aInstalls)
+ install.cancel();
+
+ weblistener.onWebInstallDisabled(topBrowser, aInstallingPrincipal.URI,
+ aInstalls, aInstalls.length);
+ return;
+ }
+ else if (!aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) {
+ for (let install of aInstalls)
+ install.cancel();
+
+ if (weblistener instanceof Ci.amIWebInstallListener2) {
+ weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI,
+ aInstalls, aInstalls.length);
+ }
+ return;
+ }
+
+ // The installs may start now depending on the web install listener,
+ // listen for the browser navigating to a new origin and cancel the
+ // installs in that case.
+ new BrowserListener(aBrowser, aInstallingPrincipal, aInstalls);
+
+ if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
+ if (weblistener.onWebInstallBlocked(topBrowser, aInstallingPrincipal.URI,
+ aInstalls, aInstalls.length)) {
+ aInstalls.forEach(function(aInstall) {
+ aInstall.install();
+ });
+ }
+ }
+ else if (weblistener.onWebInstallRequested(topBrowser, aInstallingPrincipal.URI,
+ aInstalls, aInstalls.length)) {
+ aInstalls.forEach(function(aInstall) {
+ aInstall.install();
+ });
+ }
+ }
+ catch (e) {
+ // In the event that the weblistener throws during instantiation or when
+ // calling onWebInstallBlocked or onWebInstallRequested all of the
+ // installs should get cancelled.
+ logger.warn("Failure calling web installer", e);
+ aInstalls.forEach(function(aInstall) {
+ aInstall.cancel();
+ });
+ }
+ },
+
+ /**
+ * Adds a new InstallListener if the listener is not already registered.
+ *
+ * @param aListener
+ * The InstallListener to add
+ */
+ addInstallListener: function(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be a InstallListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!this.installListeners.some(function addInstallListener_matchListener(i) {
+ return i == aListener; }))
+ this.installListeners.push(aListener);
+ },
+
+ /**
+ * Removes an InstallListener if the listener is registered.
+ *
+ * @param aListener
+ * The InstallListener to remove
+ */
+ removeInstallListener: function(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be a InstallListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let pos = 0;
+ while (pos < this.installListeners.length) {
+ if (this.installListeners[pos] == aListener)
+ this.installListeners.splice(pos, 1);
+ else
+ pos++;
+ }
+ },
+
+ /**
+ * Asynchronously gets an add-on with a specific ID.
+ *
+ * @param aID
+ * The ID of the add-on to retrieve
+ * @param aCallback
+ * The callback to pass the retrieved add-on to
+ * @throws if the aID or aCallback arguments are not specified
+ */
+ getAddonByID: function(aID, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aID || typeof aID != "string")
+ throw Components.Exception("aID must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ new AsyncObjectCaller(this.providers, "getAddonByID", {
+ nextObject: function(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getAddonByID", aID,
+ function getAddonByID_safeCall(aAddon) {
+ if (aAddon)
+ safeCall(aCallback, aAddon);
+ else
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function(aCaller) {
+ safeCall(aCallback, null);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously get an add-on with a specific Sync GUID.
+ *
+ * @param aGUID
+ * String GUID of add-on to retrieve
+ * @param aCallback
+ * The callback to pass the retrieved add-on to.
+ * @throws if the aGUID or aCallback arguments are not specified
+ */
+ getAddonBySyncGUID: function(aGUID, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aGUID || typeof aGUID != "string")
+ throw Components.Exception("aGUID must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", {
+ nextObject: function(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getAddonBySyncGUID", aGUID,
+ function getAddonBySyncGUID_safeCall(aAddon) {
+ if (aAddon) {
+ safeCall(aCallback, aAddon);
+ } else {
+ aCaller.callNext();
+ }
+ });
+ },
+
+ noMoreObjects: function(aCaller) {
+ safeCall(aCallback, null);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets an array of add-ons.
+ *
+ * @param aIDs
+ * The array of IDs to retrieve
+ * @param aCallback
+ * The callback to pass an array of Addons to
+ * @throws if the aID or aCallback arguments are not specified
+ */
+ getAddonsByIDs: function(aIDs, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!Array.isArray(aIDs))
+ throw Components.Exception("aIDs must be an array",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let addons = [];
+
+ new AsyncObjectCaller(aIDs, null, {
+ nextObject: function(aCaller, aID) {
+ AddonManagerInternal.getAddonByID(aID,
+ function getAddonsByIDs_getAddonByID(aAddon) {
+ addons.push(aAddon);
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function(aCaller) {
+ safeCall(aCallback, addons);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets add-ons of specific types.
+ *
+ * @param aTypes
+ * An optional array of types to retrieve. Each type is a string name
+ * @param aCallback
+ * The callback to pass an array of Addons to.
+ * @throws if the aCallback argument is not specified
+ */
+ getAddonsByTypes: function(aTypes, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (aTypes && !Array.isArray(aTypes))
+ throw Components.Exception("aTypes must be an array or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let addons = [];
+
+ new AsyncObjectCaller(this.providers, "getAddonsByTypes", {
+ nextObject: function(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getAddonsByTypes", aTypes,
+ function getAddonsByTypes_concatAddons(aProviderAddons) {
+ if (aProviderAddons) {
+ addons = addons.concat(aProviderAddons);
+ }
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function(aCaller) {
+ safeCall(aCallback, addons);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets all installed add-ons.
+ *
+ * @param aCallback
+ * A callback which will be passed an array of Addons
+ */
+ getAllAddons: function(aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ this.getAddonsByTypes(null, aCallback);
+ },
+
+ /**
+ * Asynchronously gets add-ons that have operations waiting for an application
+ * restart to complete.
+ *
+ * @param aTypes
+ * An optional array of types to retrieve. Each type is a string name
+ * @param aCallback
+ * The callback to pass the array of Addons to
+ * @throws if the aCallback argument is not specified
+ */
+ getAddonsWithOperationsByTypes: function(aTypes, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (aTypes && !Array.isArray(aTypes))
+ throw Components.Exception("aTypes must be an array or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let addons = [];
+
+ new AsyncObjectCaller(this.providers, "getAddonsWithOperationsByTypes", {
+ nextObject: function(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getAddonsWithOperationsByTypes", aTypes,
+ function getAddonsWithOperationsByTypes_concatAddons
+ (aProviderAddons) {
+ if (aProviderAddons) {
+ addons = addons.concat(aProviderAddons);
+ }
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function(caller) {
+ safeCall(aCallback, addons);
+ }
+ });
+ },
+
+ /**
+ * Adds a new AddonManagerListener if the listener is not already registered.
+ *
+ * @param aListener
+ * The listener to add
+ */
+ addManagerListener: function(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be an AddonManagerListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!this.managerListeners.some(function addManagerListener_matchListener(i) {
+ return i == aListener; }))
+ this.managerListeners.push(aListener);
+ },
+
+ /**
+ * Removes an AddonManagerListener if the listener is registered.
+ *
+ * @param aListener
+ * The listener to remove
+ */
+ removeManagerListener: function(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be an AddonManagerListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let pos = 0;
+ while (pos < this.managerListeners.length) {
+ if (this.managerListeners[pos] == aListener)
+ this.managerListeners.splice(pos, 1);
+ else
+ pos++;
+ }
+ },
+
+ /**
+ * Adds a new AddonListener if the listener is not already registered.
+ *
+ * @param aListener
+ * The AddonListener to add
+ */
+ addAddonListener: function(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be an AddonListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!this.addonListeners.some(function addAddonListener_matchListener(i) {
+ return i == aListener; }))
+ this.addonListeners.push(aListener);
+ },
+
+ /**
+ * Removes an AddonListener if the listener is registered.
+ *
+ * @param aListener
+ * The AddonListener to remove
+ */
+ removeAddonListener: function(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be an AddonListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let pos = 0;
+ while (pos < this.addonListeners.length) {
+ if (this.addonListeners[pos] == aListener)
+ this.addonListeners.splice(pos, 1);
+ else
+ pos++;
+ }
+ },
+
+ /**
+ * Adds a new TypeListener if the listener is not already registered.
+ *
+ * @param aListener
+ * The TypeListener to add
+ */
+ addTypeListener: function(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be a TypeListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!this.typeListeners.some(function addTypeListener_matchListener(i) {
+ return i == aListener; }))
+ this.typeListeners.push(aListener);
+ },
+
+ /**
+ * Removes an TypeListener if the listener is registered.
+ *
+ * @param aListener
+ * The TypeListener to remove
+ */
+ removeTypeListener: function(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be a TypeListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let pos = 0;
+ while (pos < this.typeListeners.length) {
+ if (this.typeListeners[pos] == aListener)
+ this.typeListeners.splice(pos, 1);
+ else
+ pos++;
+ }
+ },
+
+ get addonTypes() {
+ // A read-only wrapper around the types dictionary
+ return new Proxy(this.types, {
+ defineProperty(target, property, descriptor) {
+ // Not allowed to define properties
+ return false;
+ },
+
+ deleteProperty(target, property) {
+ // Not allowed to delete properties
+ return false;
+ },
+
+ get(target, property, receiver) {
+ if (!target.hasOwnProperty(property))
+ return undefined;
+
+ return target[property].type;
+ },
+
+ getOwnPropertyDescriptor(target, property) {
+ if (!target.hasOwnProperty(property))
+ return undefined;
+
+ return {
+ value: target[property].type,
+ writable: false,
+ // Claim configurability to maintain the proxy invariants.
+ configurable: true,
+ enumerable: true
+ }
+ },
+
+ preventExtensions(target) {
+ // Not allowed to prevent adding new properties
+ return false;
+ },
+
+ set(target, property, value, receiver) {
+ // Not allowed to set properties
+ return false;
+ },
+
+ setPrototypeOf(target, prototype) {
+ // Not allowed to change prototype
+ return false;
+ }
+ });
+ },
+
+ get autoUpdateDefault() {
+ return gAutoUpdateDefault;
+ },
+
+ set autoUpdateDefault(aValue) {
+ aValue = !!aValue;
+ if (aValue != gAutoUpdateDefault)
+ Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue);
+ return aValue;
+ },
+
+ get checkCompatibility() {
+ return gCheckCompatibility;
+ },
+
+ set checkCompatibility(aValue) {
+ aValue = !!aValue;
+ if (aValue != gCheckCompatibility) {
+ if (!aValue)
+ Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false);
+ else
+ Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY);
+ }
+ return aValue;
+ },
+
+ get strictCompatibility() {
+ return gStrictCompatibility;
+ },
+
+ set strictCompatibility(aValue) {
+ aValue = !!aValue;
+ if (aValue != gStrictCompatibility)
+ Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue);
+ return aValue;
+ },
+
+ get checkUpdateSecurityDefault() {
+ return gCheckUpdateSecurityDefault;
+ },
+
+ get checkUpdateSecurity() {
+ return gCheckUpdateSecurity;
+ },
+
+ set checkUpdateSecurity(aValue) {
+ aValue = !!aValue;
+ if (aValue != gCheckUpdateSecurity) {
+ if (aValue != gCheckUpdateSecurityDefault)
+ Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue);
+ else
+ Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY);
+ }
+ return aValue;
+ },
+
+ get updateEnabled() {
+ return gUpdateEnabled;
+ },
+
+ set updateEnabled(aValue) {
+ aValue = !!aValue;
+ if (aValue != gUpdateEnabled)
+ Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue);
+ return aValue;
+ },
+};
+
+/**
+ * Should not be used outside of core Mozilla code. This is a private API for
+ * the startup and platform integration code to use. Refer to the methods on
+ * AddonManagerInternal for documentation however note that these methods are
+ * subject to change at any time.
+ */
+this.AddonManagerPrivate = {
+ startup: function() {
+ AddonManagerInternal.startup();
+ },
+
+ registerProvider: function(aProvider, aTypes) {
+ AddonManagerInternal.registerProvider(aProvider, aTypes);
+ },
+
+ unregisterProvider: function(aProvider) {
+ AddonManagerInternal.unregisterProvider(aProvider);
+ },
+
+ markProviderSafe: function(aProvider) {
+ AddonManagerInternal.markProviderSafe(aProvider);
+ },
+
+ backgroundUpdateCheck: function() {
+ return AddonManagerInternal.backgroundUpdateCheck();
+ },
+
+ backgroundUpdateTimerHandler() {
+ // Don't call through to the real update check if no checks are enabled.
+ if (!AddonManagerInternal.updateEnabled) {
+ logger.info("Skipping background update check");
+ return;
+ }
+ // Don't return the promise here, since the caller doesn't care.
+ AddonManagerInternal.backgroundUpdateCheck();
+ },
+
+ addStartupChange: function(aType, aID) {
+ AddonManagerInternal.addStartupChange(aType, aID);
+ },
+
+ removeStartupChange: function(aType, aID) {
+ AddonManagerInternal.removeStartupChange(aType, aID);
+ },
+
+ notifyAddonChanged: function(aID, aType, aPendingRestart) {
+ AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart);
+ },
+
+ updateAddonAppDisabledStates: function() {
+ AddonManagerInternal.updateAddonAppDisabledStates();
+ },
+
+ updateAddonRepositoryData: function(aCallback) {
+ AddonManagerInternal.updateAddonRepositoryData(aCallback);
+ },
+
+ callInstallListeners: function(...aArgs) {
+ return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal,
+ aArgs);
+ },
+
+ callAddonListeners: function(...aArgs) {
+ AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs);
+ },
+
+ AddonAuthor: AddonAuthor,
+
+ AddonScreenshot: AddonScreenshot,
+
+ AddonCompatibilityOverride: AddonCompatibilityOverride,
+
+ AddonType: AddonType,
+
+ /**
+ * Helper to call update listeners when no update is available.
+ *
+ * This can be used as an implementation for Addon.findUpdates() when
+ * no update mechanism is available.
+ */
+ callNoUpdateListeners: function(addon, listener, reason, appVersion, platformVersion) {
+ if ("onNoCompatibilityUpdateAvailable" in listener) {
+ safeCall(listener.onNoCompatibilityUpdateAvailable.bind(listener), addon);
+ }
+ if ("onNoUpdateAvailable" in listener) {
+ safeCall(listener.onNoUpdateAvailable.bind(listener), addon);
+ }
+ if ("onUpdateFinished" in listener) {
+ safeCall(listener.onUpdateFinished.bind(listener), addon);
+ }
+ },
+};
+
+/**
+ * This is the public API that UI and developers should be calling. All methods
+ * just forward to AddonManagerInternal.
+ */
+this.AddonManager = {
+ // Constants for the AddonInstall.state property
+ // The install is available for download.
+ STATE_AVAILABLE: 0,
+ // The install is being downloaded.
+ STATE_DOWNLOADING: 1,
+ // The install is checking for compatibility information.
+ STATE_CHECKING: 2,
+ // The install is downloaded and ready to install.
+ STATE_DOWNLOADED: 3,
+ // The download failed.
+ STATE_DOWNLOAD_FAILED: 4,
+ // The add-on is being installed.
+ STATE_INSTALLING: 5,
+ // The add-on has been installed.
+ STATE_INSTALLED: 6,
+ // The install failed.
+ STATE_INSTALL_FAILED: 7,
+ // The install has been cancelled.
+ STATE_CANCELLED: 8,
+
+ // Constants representing different types of errors while downloading an
+ // add-on.
+ // The download failed due to network problems.
+ ERROR_NETWORK_FAILURE: -1,
+ // The downloaded file did not match the provided hash.
+ ERROR_INCORRECT_HASH: -2,
+ // The downloaded file seems to be corrupted in some way.
+ ERROR_CORRUPT_FILE: -3,
+ // An error occured trying to write to the filesystem.
+ ERROR_FILE_ACCESS: -4,
+ // The downloaded file seems to be Jetpack.
+ ERROR_JETPACKSDK_FILE: -8,
+ // The downloaded file seems to be WebExtension.
+ ERROR_WEBEXT_FILE: -9,
+
+ // These must be kept in sync with AddonUpdateChecker.
+ // No error was encountered.
+ UPDATE_STATUS_NO_ERROR: 0,
+ // The update check timed out
+ UPDATE_STATUS_TIMEOUT: -1,
+ // There was an error while downloading the update information.
+ UPDATE_STATUS_DOWNLOAD_ERROR: -2,
+ // The update information was malformed in some way.
+ UPDATE_STATUS_PARSE_ERROR: -3,
+ // The update information was not in any known format.
+ UPDATE_STATUS_UNKNOWN_FORMAT: -4,
+ // The update information was not correctly signed or there was an SSL error.
+ UPDATE_STATUS_SECURITY_ERROR: -5,
+ // The update was cancelled.
+ UPDATE_STATUS_CANCELLED: -6,
+
+ // Constants to indicate why an update check is being performed
+ // Update check has been requested by the user.
+ UPDATE_WHEN_USER_REQUESTED: 1,
+ // Update check is necessary to see if the Addon is compatibile with a new
+ // version of the application.
+ UPDATE_WHEN_NEW_APP_DETECTED: 2,
+ // Update check is necessary because a new application has been installed.
+ UPDATE_WHEN_NEW_APP_INSTALLED: 3,
+ // Update check is a regular background update check.
+ UPDATE_WHEN_PERIODIC_UPDATE: 16,
+ // Update check is needed to check an Addon that is being installed.
+ UPDATE_WHEN_ADDON_INSTALLED: 17,
+
+ // Constants for operations in Addon.pendingOperations
+ // Indicates that the Addon has no pending operations.
+ PENDING_NONE: 0,
+ // Indicates that the Addon will be enabled after the application restarts.
+ PENDING_ENABLE: 1,
+ // Indicates that the Addon will be disabled after the application restarts.
+ PENDING_DISABLE: 2,
+ // Indicates that the Addon will be uninstalled after the application restarts.
+ PENDING_UNINSTALL: 4,
+ // Indicates that the Addon will be installed after the application restarts.
+ PENDING_INSTALL: 8,
+ PENDING_UPGRADE: 16,
+
+ // Constants for operations in Addon.operationsRequiringRestart
+ // Indicates that restart isn't required for any operation.
+ OP_NEEDS_RESTART_NONE: 0,
+ // Indicates that restart is required for enabling the addon.
+ OP_NEEDS_RESTART_ENABLE: 1,
+ // Indicates that restart is required for disabling the addon.
+ OP_NEEDS_RESTART_DISABLE: 2,
+ // Indicates that restart is required for uninstalling the addon.
+ OP_NEEDS_RESTART_UNINSTALL: 4,
+ // Indicates that restart is required for installing the addon.
+ OP_NEEDS_RESTART_INSTALL: 8,
+
+ // Constants for permissions in Addon.permissions.
+ // Indicates that the Addon can be uninstalled.
+ PERM_CAN_UNINSTALL: 1,
+ // Indicates that the Addon can be enabled by the user.
+ PERM_CAN_ENABLE: 2,
+ // Indicates that the Addon can be disabled by the user.
+ PERM_CAN_DISABLE: 4,
+ // Indicates that the Addon can be upgraded.
+ PERM_CAN_UPGRADE: 8,
+ // Indicates that the Addon can be set to be optionally enabled
+ // on a case-by-case basis.
+ PERM_CAN_ASK_TO_ACTIVATE: 16,
+
+ // General descriptions of where items are installed.
+ // Installed in this profile.
+ SCOPE_PROFILE: 1,
+ // Installed for all of this user's profiles.
+ SCOPE_USER: 2,
+ // Installed and owned by the application.
+ SCOPE_APPLICATION: 4,
+ // Installed for all users of the computer.
+ SCOPE_SYSTEM: 8,
+ // The combination of all scopes.
+ SCOPE_ALL: 15,
+
+ // Add-on type is expected to be displayed in the UI in a list.
+ VIEW_TYPE_LIST: "list",
+
+ // Constants describing how add-on types behave.
+
+ // If no add-ons of a type are installed, then the category for that add-on
+ // type should be hidden in the UI.
+ TYPE_UI_HIDE_EMPTY: 16,
+ // Indicates that this add-on type supports the ask-to-activate state.
+ // That is, add-ons of this type can be set to be optionally enabled
+ // on a case-by-case basis.
+ TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32,
+ // The add-on type natively supports undo for restartless uninstalls.
+ // If this flag is not specified, the UI is expected to handle this via
+ // disabling the add-on, and performing the actual uninstall at a later time.
+ TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL: 64,
+
+ // Constants for Addon.applyBackgroundUpdates.
+ // Indicates that the Addon should not update automatically.
+ AUTOUPDATE_DISABLE: 0,
+ // Indicates that the Addon should update automatically only if
+ // that's the global default.
+ AUTOUPDATE_DEFAULT: 1,
+ // Indicates that the Addon should update automatically.
+ AUTOUPDATE_ENABLE: 2,
+
+ // Constants for how Addon options should be shown.
+ // Options will be opened in a new window
+ OPTIONS_TYPE_DIALOG: 1,
+ // Options will be displayed within the AM detail view
+ OPTIONS_TYPE_INLINE: 2,
+ // Options will be displayed in a new tab, if possible
+ OPTIONS_TYPE_TAB: 3,
+ // Same as OPTIONS_TYPE_INLINE, but no Preferences button will be shown.
+ // Used to indicate that only non-interactive information will be shown.
+ OPTIONS_TYPE_INLINE_INFO: 4,
+
+ // Constants for displayed or hidden options notifications
+ // Options notification will be displayed
+ OPTIONS_NOTIFICATION_DISPLAYED: "addon-options-displayed",
+ // Options notification will be hidden
+ OPTIONS_NOTIFICATION_HIDDEN: "addon-options-hidden",
+
+ // Constants for getStartupChanges, addStartupChange and removeStartupChange
+ // Add-ons that were detected as installed during startup. Doesn't include
+ // add-ons that were pending installation the last time the application ran.
+ STARTUP_CHANGE_INSTALLED: "installed",
+ // Add-ons that were detected as changed during startup. This includes an
+ // add-on moving to a different location, changing version or just having
+ // been detected as possibly changed.
+ STARTUP_CHANGE_CHANGED: "changed",
+ // Add-ons that were detected as uninstalled during startup. Doesn't include
+ // add-ons that were pending uninstallation the last time the application ran.
+ STARTUP_CHANGE_UNINSTALLED: "uninstalled",
+ // Add-ons that were detected as disabled during startup, normally because of
+ // an application change making an add-on incompatible. Doesn't include
+ // add-ons that were pending being disabled the last time the application ran.
+ STARTUP_CHANGE_DISABLED: "disabled",
+ // Add-ons that were detected as enabled during startup, normally because of
+ // an application change making an add-on compatible. Doesn't include
+ // add-ons that were pending being enabled the last time the application ran.
+ STARTUP_CHANGE_ENABLED: "enabled",
+
+ // Constants for the Addon.userDisabled property
+ // Indicates that the userDisabled state of this add-on is currently
+ // ask-to-activate. That is, it can be conditionally enabled on a
+ // case-by-case basis.
+ STATE_ASK_TO_ACTIVATE: "askToActivate",
+
+#ifdef MOZ_EM_DEBUG
+ get __AddonManagerInternal__() {
+ return AddonManagerInternal;
+ },
+#endif
+
+ get isReady() {
+ return gStartupComplete && !gShutdownInProgress;
+ },
+
+ getInstallForURL: function(aUrl, aCallback, aMimetype, aHash, aName, aIcons,
+ aVersion, aBrowser) {
+ AddonManagerInternal.getInstallForURL(aUrl, aCallback, aMimetype, aHash,
+ aName, aIcons, aVersion, aBrowser);
+ },
+
+ getInstallForFile: function(aFile, aCallback, aMimetype) {
+ AddonManagerInternal.getInstallForFile(aFile, aCallback, aMimetype);
+ },
+
+ /**
+ * Gets an array of add-on IDs that changed during the most recent startup.
+ *
+ * @param aType
+ * The type of startup change to get
+ * @return An array of add-on IDs
+ */
+ getStartupChanges: function(aType) {
+ if (!(aType in AddonManagerInternal.startupChanges))
+ return [];
+ return AddonManagerInternal.startupChanges[aType].slice(0);
+ },
+
+ getAddonByID: function(aID, aCallback) {
+ // If no callback is specified in the public function call
+ // then use Async.jsm to provide pseudo-sync functionality.
+ if (!aCallback) {
+ aCallback = Async.makeSyncCallback();
+ AddonManagerInternal.getAddonByID(aID, aCallback);
+ return Async.waitForSyncCallback(aCallback);
+ }
+
+ // Use the mozilla documented asynchronous behavior.
+ AddonManagerInternal.getAddonByID(aID, aCallback);
+ },
+
+ getAddonBySyncGUID: function(aGUID, aCallback) {
+ AddonManagerInternal.getAddonBySyncGUID(aGUID, aCallback);
+ },
+
+ getAddonsByIDs: function(aIDs, aCallback) {
+ // If no callback is specified in the public function call
+ // then use Async.jsm to provide pseudo-sync functionality.
+ if (!aCallback) {
+ aCallback = Async.makeSyncCallback();
+ AddonManagerInternal.getAddonsByIDs(aIDs, aCallback);
+ return Async.waitForSyncCallback(aCallback);
+ }
+
+ // Use the mozilla documented asynchronous behavior.
+ AddonManagerInternal.getAddonsByIDs(aIDs, aCallback);
+ },
+
+ getAddonsWithOperationsByTypes: function(aTypes, aCallback) {
+ AddonManagerInternal.getAddonsWithOperationsByTypes(aTypes, aCallback);
+ },
+
+ getAddonsByTypes: function(aTypes, aCallback) {
+ // If no callback is specified in the public function call
+ // then use Async.jsm to provide pseudo-sync functionality.
+ if (!aCallback) {
+ aCallback = Async.makeSyncCallback();
+ AddonManagerInternal.getAddonsByTypes(aTypes, aCallback);
+ return Async.waitForSyncCallback(aCallback);
+ }
+
+ // Use the mozilla documented asynchronous behavior.
+ AddonManagerInternal.getAddonsByTypes(aTypes, aCallback);
+ },
+
+ getAllAddons: function(aCallback) {
+ // If no callback is specified in the public function call
+ // then use Async.jsm to provide pseudo-sync functionality.
+ if (!aCallback) {
+ aCallback = Async.makeSyncCallback();
+ AddonManagerInternal.getAllAddons(aCallback);
+ return Async.waitForSyncCallback(aCallback);
+ }
+
+ // Use the mozilla documented asynchronous behavior.
+ AddonManagerInternal.getAllAddons(aCallback);
+ },
+
+ getInstallsByTypes: function(aTypes, aCallback) {
+ AddonManagerInternal.getInstallsByTypes(aTypes, aCallback);
+ },
+
+ getAllInstalls: function(aCallback) {
+ AddonManagerInternal.getAllInstalls(aCallback);
+ },
+
+ mapURIToAddonID: function(aURI) {
+ return AddonManagerInternal.mapURIToAddonID(aURI);
+ },
+
+ isInstallEnabled: function(aType) {
+ return AddonManagerInternal.isInstallEnabled(aType);
+ },
+
+ isInstallAllowed: function(aType, aInstallingPrincipal) {
+ return AddonManagerInternal.isInstallAllowed(aType, ensurePrincipal(aInstallingPrincipal));
+ },
+
+ installAddonsFromWebpage: function(aType, aBrowser, aInstallingPrincipal, aInstalls) {
+ AddonManagerInternal.installAddonsFromWebpage(aType, aBrowser,
+ ensurePrincipal(aInstallingPrincipal),
+ aInstalls);
+ },
+
+ addManagerListener: function(aListener) {
+ AddonManagerInternal.addManagerListener(aListener);
+ },
+
+ removeManagerListener: function(aListener) {
+ AddonManagerInternal.removeManagerListener(aListener);
+ },
+
+ addInstallListener: function(aListener) {
+ AddonManagerInternal.addInstallListener(aListener);
+ },
+
+ removeInstallListener: function(aListener) {
+ AddonManagerInternal.removeInstallListener(aListener);
+ },
+
+ addAddonListener: function(aListener) {
+ AddonManagerInternal.addAddonListener(aListener);
+ },
+
+ removeAddonListener: function(aListener) {
+ AddonManagerInternal.removeAddonListener(aListener);
+ },
+
+ addTypeListener: function(aListener) {
+ AddonManagerInternal.addTypeListener(aListener);
+ },
+
+ removeTypeListener: function(aListener) {
+ AddonManagerInternal.removeTypeListener(aListener);
+ },
+
+ get addonTypes() {
+ return AddonManagerInternal.addonTypes;
+ },
+
+ /**
+ * Determines whether an Addon should auto-update or not.
+ *
+ * @param aAddon
+ * The Addon representing the add-on
+ * @return true if the addon should auto-update, false otherwise.
+ */
+ shouldAutoUpdate: function(aAddon) {
+ if (!aAddon || typeof aAddon != "object")
+ throw Components.Exception("aAddon must be specified",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!("applyBackgroundUpdates" in aAddon))
+ return false;
+ if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
+ return true;
+ if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE)
+ return false;
+ return this.autoUpdateDefault;
+ },
+
+ get checkCompatibility() {
+ return AddonManagerInternal.checkCompatibility;
+ },
+
+ set checkCompatibility(aValue) {
+ AddonManagerInternal.checkCompatibility = aValue;
+ },
+
+ get strictCompatibility() {
+ return AddonManagerInternal.strictCompatibility;
+ },
+
+ set strictCompatibility(aValue) {
+ AddonManagerInternal.strictCompatibility = aValue;
+ },
+
+ get checkUpdateSecurityDefault() {
+ return AddonManagerInternal.checkUpdateSecurityDefault;
+ },
+
+ get checkUpdateSecurity() {
+ return AddonManagerInternal.checkUpdateSecurity;
+ },
+
+ set checkUpdateSecurity(aValue) {
+ AddonManagerInternal.checkUpdateSecurity = aValue;
+ },
+
+ get updateEnabled() {
+ return AddonManagerInternal.updateEnabled;
+ },
+
+ set updateEnabled(aValue) {
+ AddonManagerInternal.updateEnabled = aValue;
+ },
+
+ get autoUpdateDefault() {
+ return AddonManagerInternal.autoUpdateDefault;
+ },
+
+ set autoUpdateDefault(aValue) {
+ AddonManagerInternal.autoUpdateDefault = aValue;
+ },
+
+ escapeAddonURI: function(aAddon, aUri, aAppVersion) {
+ return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion);
+ },
+
+ get shutdown() {
+ return gShutdownBarrier.client;
+ },
+};
+
+// load the timestamps module into AddonManagerInternal
+Object.freeze(AddonManagerInternal);
+Object.freeze(AddonManagerPrivate);
+Object.freeze(AddonManager);
diff --git a/components/addons/src/AddonPathService.cpp b/components/addons/src/AddonPathService.cpp
new file mode 100644
index 000000000..ddfdbe817
--- /dev/null
+++ b/components/addons/src/AddonPathService.cpp
@@ -0,0 +1,243 @@
+/* -*- 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 "AddonPathService.h"
+
+#include "amIAddonManager.h"
+#include "nsIURI.h"
+#include "nsXULAppAPI.h"
+#include "jsapi.h"
+#include "nsServiceManagerUtils.h"
+#include "nsLiteralString.h"
+#include "nsThreadUtils.h"
+#include "nsIIOService.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsIResProtocolHandler.h"
+#include "nsIChromeRegistry.h"
+#include "nsIJARURI.h"
+#include "nsJSUtils.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/AddonPathService.h"
+#include "mozilla/Omnijar.h"
+
+#include <algorithm>
+
+namespace mozilla {
+
+struct PathEntryComparator
+{
+ typedef AddonPathService::PathEntry PathEntry;
+
+ bool Equals(const PathEntry& entry1, const PathEntry& entry2) const
+ {
+ return entry1.mPath == entry2.mPath;
+ }
+
+ bool LessThan(const PathEntry& entry1, const PathEntry& entry2) const
+ {
+ return entry1.mPath < entry2.mPath;
+ }
+};
+
+AddonPathService::AddonPathService()
+{
+}
+
+AddonPathService::~AddonPathService()
+{
+ sInstance = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(AddonPathService, amIAddonPathService)
+
+AddonPathService *AddonPathService::sInstance;
+
+/* static */ AddonPathService*
+AddonPathService::GetInstance()
+{
+ if (!sInstance) {
+ sInstance = new AddonPathService();
+ }
+ NS_ADDREF(sInstance);
+ return sInstance;
+}
+
+static JSAddonId*
+ConvertAddonId(const nsAString& addonIdString)
+{
+ AutoSafeJSContext cx;
+ JS::RootedValue strv(cx);
+ if (!mozilla::dom::ToJSValue(cx, addonIdString, &strv)) {
+ return nullptr;
+ }
+ JS::RootedString str(cx, strv.toString());
+ return JS::NewAddonId(cx, str);
+}
+
+JSAddonId*
+AddonPathService::Find(const nsAString& path)
+{
+ // Use binary search to find the nearest entry that is <= |path|.
+ PathEntryComparator comparator;
+ unsigned index = mPaths.IndexOfFirstElementGt(PathEntry(path, nullptr), comparator);
+ if (index == 0) {
+ return nullptr;
+ }
+ const PathEntry& entry = mPaths[index - 1];
+
+ // Return the entry's addon if its path is a prefix of |path|.
+ if (StringBeginsWith(path, entry.mPath)) {
+ return entry.mAddonId;
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+AddonPathService::FindAddonId(const nsAString& path, nsAString& addonIdString)
+{
+ if (JSAddonId* id = Find(path)) {
+ JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id));
+ AssignJSFlatString(addonIdString, flat);
+ }
+ return NS_OK;
+}
+
+/* static */ JSAddonId*
+AddonPathService::FindAddonId(const nsAString& path)
+{
+ // If no service has been created, then we're not going to find anything.
+ if (!sInstance) {
+ return nullptr;
+ }
+
+ return sInstance->Find(path);
+}
+
+NS_IMETHODIMP
+AddonPathService::InsertPath(const nsAString& path, const nsAString& addonIdString)
+{
+ JSAddonId* addonId = ConvertAddonId(addonIdString);
+
+ // Add the new path in sorted order.
+ PathEntryComparator comparator;
+ mPaths.InsertElementSorted(PathEntry(path, addonId), comparator);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AddonPathService::MapURIToAddonId(nsIURI* aURI, nsAString& addonIdString)
+{
+ if (JSAddonId* id = MapURIToAddonID(aURI)) {
+ JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id));
+ AssignJSFlatString(addonIdString, flat);
+ }
+ return NS_OK;
+}
+
+static nsresult
+ResolveURI(nsIURI* aURI, nsAString& out)
+{
+ bool equals;
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString spec;
+
+ // Resolve resource:// URIs. At the end of this if/else block, we
+ // have both spec and uri variables identifying the same URI.
+ if (NS_SUCCEEDED(aURI->SchemeIs("resource", &equals)) && equals) {
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+
+ nsCOMPtr<nsIProtocolHandler> ph;
+ rv = ioService->GetProtocolHandler("resource", getter_AddRefs(ph));
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+
+ nsCOMPtr<nsIResProtocolHandler> irph(do_QueryInterface(ph, &rv));
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+
+ rv = irph->ResolveURI(aURI, spec);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+
+ rv = ioService->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ } else if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &equals)) && equals) {
+ nsCOMPtr<nsIChromeRegistry> chromeReg =
+ mozilla::services::GetChromeRegistryService();
+ if (NS_WARN_IF(!chromeReg))
+ return NS_ERROR_UNEXPECTED;
+
+ rv = chromeReg->ConvertChromeURL(aURI, getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+ } else {
+ uri = aURI;
+ }
+
+ if (NS_SUCCEEDED(uri->SchemeIs("jar", &equals)) && equals) {
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+
+ nsCOMPtr<nsIURI> jarFileURI;
+ rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI));
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+
+ return ResolveURI(jarFileURI, out);
+ }
+
+ if (NS_SUCCEEDED(uri->SchemeIs("file", &equals)) && equals) {
+ nsCOMPtr<nsIFileURL> baseFileURL = do_QueryInterface(uri, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+
+ nsCOMPtr<nsIFile> file;
+ rv = baseFileURL->GetFile(getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv)))
+ return rv;
+
+ return file->GetPath(out);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+JSAddonId*
+MapURIToAddonID(nsIURI* aURI)
+{
+ if (!NS_IsMainThread() || !XRE_IsParentProcess()) {
+ return nullptr;
+ }
+
+ nsAutoString filePath;
+ nsresult rv = ResolveURI(aURI, filePath);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ nsCOMPtr<nsIFile> greJar = Omnijar::GetPath(Omnijar::GRE);
+ nsCOMPtr<nsIFile> appJar = Omnijar::GetPath(Omnijar::APP);
+ if (greJar && appJar) {
+ nsAutoString greJarString, appJarString;
+ if (NS_FAILED(greJar->GetPath(greJarString)) || NS_FAILED(appJar->GetPath(appJarString)))
+ return nullptr;
+
+ // If |aURI| is part of either Omnijar, then it can't be part of an
+ // add-on. This catches pretty much all URLs for Firefox content.
+ if (filePath.Equals(greJarString) || filePath.Equals(appJarString))
+ return nullptr;
+ }
+
+ // If it's not part of Firefox, we resort to binary searching through the
+ // add-on paths.
+ return AddonPathService::FindAddonId(filePath);
+}
+
+}
diff --git a/components/addons/src/AddonPathService.h b/components/addons/src/AddonPathService.h
new file mode 100644
index 000000000..f739b018f
--- /dev/null
+++ b/components/addons/src/AddonPathService.h
@@ -0,0 +1,55 @@
+//* -*- 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/. */
+
+#ifndef AddonPathService_h
+#define AddonPathService_h
+
+#include "amIAddonPathService.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsIURI;
+class JSAddonId;
+
+namespace mozilla {
+
+JSAddonId*
+MapURIToAddonID(nsIURI* aURI);
+
+class AddonPathService final : public amIAddonPathService
+{
+public:
+ AddonPathService();
+
+ static AddonPathService* GetInstance();
+
+ JSAddonId* Find(const nsAString& path);
+ static JSAddonId* FindAddonId(const nsAString& path);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_AMIADDONPATHSERVICE
+
+ struct PathEntry
+ {
+ nsString mPath;
+ JSAddonId* mAddonId;
+
+ PathEntry(const nsAString& aPath, JSAddonId* aAddonId)
+ : mPath(aPath), mAddonId(aAddonId)
+ {}
+ };
+
+private:
+ virtual ~AddonPathService();
+
+ // Paths are stored sorted in order of their mPath.
+ nsTArray<PathEntry> mPaths;
+
+ static AddonPathService* sInstance;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/components/addons/src/AddonRepository.jsm b/components/addons/src/AddonRepository.jsm
new file mode 100644
index 000000000..4569abdd1
--- /dev/null
+++ b/components/addons/src/AddonRepository.jsm
@@ -0,0 +1,2014 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+const Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/AddonManager.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
+ "resource://gre/modules/DeferredSave.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository_SQLiteMigrator",
+ "resource://gre/modules/addons/AddonRepository_SQLiteMigrator.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+
+this.EXPORTED_SYMBOLS = [ "AddonRepository" ];
+
+const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
+const PREF_GETADDONS_CACHE_TYPES = "extensions.getAddons.cache.types";
+const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled"
+const PREF_GETADDONS_BROWSEADDONS = "extensions.getAddons.browseAddons";
+const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url";
+const PREF_GETADDONS_BYIDS_PERFORMANCE = "extensions.getAddons.getWithPerformance.url";
+const PREF_GETADDONS_BROWSERECOMMENDED = "extensions.getAddons.recommended.browseURL";
+const PREF_GETADDONS_GETRECOMMENDED = "extensions.getAddons.recommended.url";
+const PREF_GETADDONS_BROWSESEARCHRESULTS = "extensions.getAddons.search.browseURL";
+const PREF_GETADDONS_GETSEARCHRESULTS = "extensions.getAddons.search.url";
+const PREF_GETADDONS_DB_SCHEMA = "extensions.getAddons.databaseSchema"
+
+const PREF_METADATA_LASTUPDATE = "extensions.getAddons.cache.lastUpdate";
+const PREF_METADATA_UPDATETHRESHOLD_SEC = "extensions.getAddons.cache.updateThreshold";
+const DEFAULT_METADATA_UPDATETHRESHOLD_SEC = 172800; // two days
+
+const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml";
+
+const API_VERSION = "1.5";
+const DEFAULT_CACHE_TYPES = "extension,theme,locale,dictionary";
+
+const KEY_PROFILEDIR = "ProfD";
+const FILE_DATABASE = "addons.json";
+const DB_SCHEMA = 5;
+const DB_MIN_JSON_SCHEMA = 5;
+const DB_BATCH_TIMEOUT_MS = 50;
+
+const BLANK_DB = function() {
+ return {
+ addons: new Map(),
+ schema: DB_SCHEMA
+ };
+}
+
+const TOOLKIT_ID = "toolkit@mozilla.org";
+
+#ifdef MC_APP_ID
+#expand const ALT_APP_ID = "__MC_APP_ID__";
+#endif
+
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.repository";
+
+// Create a new logger for use by the Addons Repository
+// (Requires AddonManager.jsm)
+var logger = Log.repository.getLogger(LOGGER_ID);
+
+// A map between XML keys to AddonSearchResult keys for string values
+// that require no extra parsing from XML
+const STRING_KEY_MAP = {
+ name: "name",
+ version: "version",
+ homepage: "homepageURL",
+ support: "supportURL"
+};
+
+// A map between XML keys to AddonSearchResult keys for string values
+// that require parsing from HTML
+const HTML_KEY_MAP = {
+ summary: "description",
+ description: "fullDescription",
+ developer_comments: "developerComments",
+ eula: "eula"
+};
+
+// A map between XML keys to AddonSearchResult keys for integer values
+// that require no extra parsing from XML
+const INTEGER_KEY_MAP = {
+ total_downloads: "totalDownloads",
+ weekly_downloads: "weeklyDownloads",
+ daily_users: "dailyUsers"
+};
+
+// Wrap the XHR factory so that tests can override with a mock
+var XHRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1",
+ "nsIXMLHttpRequest");
+
+function convertHTMLToPlainText(html) {
+ if (!html)
+ return html;
+ var converter = Cc["@mozilla.org/widget/htmlformatconverter;1"].
+ createInstance(Ci.nsIFormatConverter);
+
+ var input = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ input.data = html.replace(/\n/g, "<br>");
+
+ var output = {};
+ converter.convert("text/html", input, input.data.length, "text/unicode",
+ output, {});
+
+ if (output.value instanceof Ci.nsISupportsString)
+ return output.value.data.replace(/\r\n/g, "\n");
+ return html;
+}
+
+function getAddonsToCache(aIds, aCallback) {
+ try {
+ var types = Services.prefs.getCharPref(PREF_GETADDONS_CACHE_TYPES);
+ }
+ catch (e) { }
+ if (!types)
+ types = DEFAULT_CACHE_TYPES;
+
+ types = types.split(",");
+
+ AddonManager.getAddonsByIDs(aIds, function getAddonsToCache_getAddonsByIDs(aAddons) {
+ let enabledIds = [];
+ for (var i = 0; i < aIds.length; i++) {
+ var preference = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%", aIds[i]);
+ try {
+ if (!Services.prefs.getBoolPref(preference))
+ continue;
+ } catch(e) {
+ // If the preference doesn't exist caching is enabled by default
+ }
+
+ // The add-ons manager may not know about this ID yet if it is a pending
+ // install. In that case we'll just cache it regardless
+ if (aAddons[i] && (types.indexOf(aAddons[i].type) == -1))
+ continue;
+
+ enabledIds.push(aIds[i]);
+ }
+
+ aCallback(enabledIds);
+ });
+}
+
+function AddonSearchResult(aId) {
+ this.id = aId;
+ this.icons = {};
+ this._unsupportedProperties = {};
+}
+
+AddonSearchResult.prototype = {
+ /**
+ * The ID of the add-on
+ */
+ id: null,
+
+ /**
+ * The add-on type (e.g. "extension" or "theme")
+ */
+ type: null,
+
+ /**
+ * The name of the add-on
+ */
+ name: null,
+
+ /**
+ * The version of the add-on
+ */
+ version: null,
+
+ /**
+ * The creator of the add-on
+ */
+ creator: null,
+
+ /**
+ * The developers of the add-on
+ */
+ developers: null,
+
+ /**
+ * A short description of the add-on
+ */
+ description: null,
+
+ /**
+ * The full description of the add-on
+ */
+ fullDescription: null,
+
+ /**
+ * The developer comments for the add-on. This includes any information
+ * that may be helpful to end users that isn't necessarily applicable to
+ * the add-on description (e.g. known major bugs)
+ */
+ developerComments: null,
+
+ /**
+ * The end-user licensing agreement (EULA) of the add-on
+ */
+ eula: null,
+
+ /**
+ * The url of the add-on's icon
+ */
+ get iconURL() {
+ return this.icons && this.icons[32];
+ },
+
+ /**
+ * The URLs of the add-on's icons, as an object with icon size as key
+ */
+ icons: null,
+
+ /**
+ * An array of screenshot urls for the add-on
+ */
+ screenshots: null,
+
+ /**
+ * The homepage for the add-on
+ */
+ homepageURL: null,
+
+ /**
+ * The homepage for the add-on
+ */
+ learnmoreURL: null,
+
+ /**
+ * The support URL for the add-on
+ */
+ supportURL: null,
+
+ /**
+ * The contribution url of the add-on
+ */
+ contributionURL: null,
+
+ /**
+ * The suggested contribution amount
+ */
+ contributionAmount: null,
+
+ /**
+ * The URL to visit in order to purchase the add-on
+ */
+ purchaseURL: null,
+
+ /**
+ * The numerical cost of the add-on in some currency, for sorting purposes
+ * only
+ */
+ purchaseAmount: null,
+
+ /**
+ * The display cost of the add-on, for display purposes only
+ */
+ purchaseDisplayAmount: null,
+
+ /**
+ * The rating of the add-on, 0-5
+ */
+ averageRating: null,
+
+ /**
+ * The number of reviews for this add-on
+ */
+ reviewCount: null,
+
+ /**
+ * The URL to the list of reviews for this add-on
+ */
+ reviewURL: null,
+
+ /**
+ * The total number of times the add-on was downloaded
+ */
+ totalDownloads: null,
+
+ /**
+ * The number of times the add-on was downloaded the current week
+ */
+ weeklyDownloads: null,
+
+ /**
+ * The number of daily users for the add-on
+ */
+ dailyUsers: null,
+
+ /**
+ * AddonInstall object generated from the add-on XPI url
+ */
+ install: null,
+
+ /**
+ * nsIURI storing where this add-on was installed from
+ */
+ sourceURI: null,
+
+ /**
+ * The status of the add-on in the repository (e.g. 4 = "Public")
+ */
+ repositoryStatus: null,
+
+ /**
+ * The size of the add-on's files in bytes. For an add-on that have not yet
+ * been downloaded this may be an estimated value.
+ */
+ size: null,
+
+ /**
+ * The Date that the add-on was most recently updated
+ */
+ updateDate: null,
+
+ /**
+ * True or false depending on whether the add-on is compatible with the
+ * current version of the application
+ */
+ isCompatible: true,
+
+ /**
+ * True or false depending on whether the add-on is compatible with the
+ * current platform
+ */
+ isPlatformCompatible: true,
+
+ /**
+ * Array of AddonCompatibilityOverride objects, that describe overrides for
+ * compatibility with an application versions.
+ **/
+ compatibilityOverrides: null,
+
+ /**
+ * True if the add-on has a secure means of updating
+ */
+ providesUpdatesSecurely: true,
+
+ /**
+ * The current blocklist state of the add-on
+ */
+ blocklistState: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+
+ /**
+ * True if this add-on cannot be used in the application based on version
+ * compatibility, dependencies and blocklisting
+ */
+ appDisabled: false,
+
+ /**
+ * True if the user wants this add-on to be disabled
+ */
+ userDisabled: false,
+
+ /**
+ * Indicates what scope the add-on is installed in, per profile, user,
+ * system or application
+ */
+ scope: AddonManager.SCOPE_PROFILE,
+
+ /**
+ * True if the add-on is currently functional
+ */
+ isActive: true,
+
+ /**
+ * A bitfield holding all of the current operations that are waiting to be
+ * performed for this add-on
+ */
+ pendingOperations: AddonManager.PENDING_NONE,
+
+ /**
+ * A bitfield holding all the the operations that can be performed on
+ * this add-on
+ */
+ permissions: 0,
+
+ /**
+ * Tests whether this add-on is known to be compatible with a
+ * particular application and platform version.
+ *
+ * @param appVersion
+ * An application version to test against
+ * @param platformVersion
+ * A platform version to test against
+ * @return Boolean representing if the add-on is compatible
+ */
+ isCompatibleWith: function ASR_isCompatibleWith(aAppVerison, aPlatformVersion) {
+ return true;
+ },
+
+ /**
+ * Starts an update check for this add-on. This will perform
+ * asynchronously and deliver results to the given listener.
+ *
+ * @param aListener
+ * An UpdateListener for the update process
+ * @param aReason
+ * A reason code for performing the update
+ * @param aAppVersion
+ * An application version to check for updates for
+ * @param aPlatformVersion
+ * A platform version to check for updates for
+ */
+ findUpdates: function ASR_findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) {
+ if ("onNoCompatibilityUpdateAvailable" in aListener)
+ aListener.onNoCompatibilityUpdateAvailable(this);
+ if ("onNoUpdateAvailable" in aListener)
+ aListener.onNoUpdateAvailable(this);
+ if ("onUpdateFinished" in aListener)
+ aListener.onUpdateFinished(this);
+ },
+
+ toJSON: function() {
+ let json = {};
+
+ for (let [property, value] of Iterator(this)) {
+ if (property.startsWith("_") ||
+ typeof(value) === "function")
+ continue;
+
+ try {
+ switch (property) {
+ case "sourceURI":
+ json.sourceURI = value ? value.spec : "";
+ break;
+
+ case "updateDate":
+ json.updateDate = value ? value.getTime() : "";
+ break;
+
+ default:
+ json[property] = value;
+ }
+ } catch (ex) {
+ logger.warn("Error writing property value for " + property);
+ }
+ }
+
+ for (let [property, value] of Iterator(this._unsupportedProperties)) {
+ if (!property.startsWith("_"))
+ json[property] = value;
+ }
+
+ return json;
+ }
+}
+
+/**
+ * The add-on repository is a source of add-ons that can be installed. It can
+ * be searched in three ways. The first takes a list of IDs and returns a
+ * list of the corresponding add-ons. The second returns a list of add-ons that
+ * come highly recommended. This list should change frequently. The third is to
+ * search for specific search terms entered by the user. Searches are
+ * asynchronous and results should be passed to the provided callback object
+ * when complete. The results passed to the callback should only include add-ons
+ * that are compatible with the current application and are not already
+ * installed.
+ */
+this.AddonRepository = {
+ /**
+ * Whether caching is currently enabled
+ */
+ get cacheEnabled() {
+ // Act as though caching is disabled if there was an unrecoverable error
+ // openning the database.
+ if (!AddonDatabase.databaseOk) {
+ logger.warn("Cache is disabled because database is not OK");
+ return false;
+ }
+
+ let preference = PREF_GETADDONS_CACHE_ENABLED;
+ return Services.prefs.getBoolPref(preference, false);
+ },
+
+ // A cache of the add-ons stored in the database
+ _addons: null,
+
+ // Whether a search is currently in progress
+ _searching: false,
+
+ // XHR associated with the current request
+ _request: null,
+
+ /*
+ * Addon search results callback object that contains two functions
+ *
+ * searchSucceeded - Called when a search has suceeded.
+ *
+ * @param aAddons
+ * An array of the add-on results. In the case of searching for
+ * specific terms the ordering of results may be determined by
+ * the search provider.
+ * @param aAddonCount
+ * The length of aAddons
+ * @param aTotalResults
+ * The total results actually available in the repository
+ *
+ *
+ * searchFailed - Called when an error occurred when performing a search.
+ */
+ _callback: null,
+
+ // Maximum number of results to return
+ _maxResults: null,
+
+ /**
+ * Shut down AddonRepository
+ * return: promise{integer} resolves with the result of flushing
+ * the AddonRepository database
+ */
+ shutdown: function AddonRepo_shutdown() {
+ this.cancelSearch();
+
+ this._addons = null;
+ return AddonDatabase.shutdown(false);
+ },
+
+ metadataAge: function() {
+ let now = Math.round(Date.now() / 1000);
+ let lastUpdate = Services.prefs.getIntPref(PREF_METADATA_LASTUPDATE, 0);
+
+ // Handle clock jumps
+ if (now < lastUpdate) {
+ return now;
+ }
+ return now - lastUpdate;
+ },
+
+ isMetadataStale: function AddonRepo_isMetadataStale() {
+ let threshold = Services.prefs.getIntPref(PREF_METADATA_UPDATETHRESHOLD_SEC,
+ DEFAULT_METADATA_UPDATETHRESHOLD_SEC);
+ return (this.metadataAge() > threshold);
+ },
+
+ /**
+ * Asynchronously get a cached add-on by id. The add-on (or null if the
+ * add-on is not found) is passed to the specified callback. If caching is
+ * disabled, null is passed to the specified callback.
+ *
+ * @param aId
+ * The id of the add-on to get
+ * @param aCallback
+ * The callback to pass the result back to
+ */
+ getCachedAddonByID: Task.async(function* (aId, aCallback) {
+ if (!aId || !this.cacheEnabled) {
+ aCallback(null);
+ return;
+ }
+
+ function getAddon(aAddons) {
+ aCallback(aAddons.get(aId) || null);
+ }
+
+ if (this._addons == null) {
+ AddonDatabase.retrieveStoredData().then(aAddons => {
+ this._addons = aAddons;
+ getAddon(aAddons);
+ });
+
+ return;
+ }
+
+ getAddon(this._addons);
+ }),
+
+ /**
+ * Asynchronously repopulate cache so it only contains the add-ons
+ * corresponding to the specified ids. If caching is disabled,
+ * the cache is completely removed.
+ *
+ * @param aTimeout
+ * (Optional) timeout in milliseconds to abandon the XHR request
+ * if we have not received a response from the server.
+ * @return Promise{null}
+ * Resolves when the metadata ping is complete
+ */
+ repopulateCache: function(aTimeout) {
+ return this._repopulateCacheInternal(false, aTimeout);
+ },
+
+ /*
+ * Clear and delete the AddonRepository database
+ * @return Promise{null} resolves when the database is deleted
+ */
+ _clearCache: function () {
+ this._addons = null;
+ return AddonDatabase.delete().then(() =>
+ new Promise((resolve, reject) =>
+ AddonManagerPrivate.updateAddonRepositoryData(resolve))
+ );
+ },
+
+ _repopulateCacheInternal: Task.async(function* (aSendPerformance, aTimeout) {
+ let allAddons = yield new Promise((resolve, reject) =>
+ AddonManager.getAllAddons(resolve));
+
+ // Completely remove cache if caching is not enabled
+ if (!this.cacheEnabled) {
+ logger.debug("Clearing cache because it is disabled");
+ return this._clearCache();
+ }
+
+ // Tycho: let ids = [a.id for (a of allAddons)];
+ let ids = [];
+ for (let a of allAddons) {
+ ids.push(a.id);
+ }
+
+ logger.debug("Repopulate add-on cache with " + ids.toSource());
+
+ let self = this;
+ let addonsToCache = yield new Promise((resolve, reject) =>
+ getAddonsToCache(ids, resolve));
+
+ // Completely remove cache if there are no add-ons to cache
+ if (addonsToCache.length == 0) {
+ logger.debug("Clearing cache because 0 add-ons were requested");
+ return this._clearCache();
+ }
+
+ yield new Promise((resolve, reject) =>
+ self._beginGetAddons(addonsToCache, {
+ searchSucceeded: function repopulateCacheInternal_searchSucceeded(aAddons) {
+ self._addons = new Map();
+ for (let addon of aAddons) {
+ self._addons.set(addon.id, addon);
+ }
+ AddonDatabase.repopulate(aAddons, resolve);
+ },
+ searchFailed: function repopulateCacheInternal_searchFailed() {
+ logger.warn("Search failed when repopulating cache");
+ resolve();
+ }
+ }, aSendPerformance, aTimeout));
+
+ // Always call AddonManager updateAddonRepositoryData after we refill the cache
+ yield new Promise((resolve, reject) =>
+ AddonManagerPrivate.updateAddonRepositoryData(resolve));
+ }),
+
+ /**
+ * Asynchronously add add-ons to the cache corresponding to the specified
+ * ids. If caching is disabled, the cache is unchanged and the callback is
+ * immediately called if it is defined.
+ *
+ * @param aIds
+ * The array of add-on ids to add to the cache
+ * @param aCallback
+ * The optional callback to call once complete
+ */
+ cacheAddons: function AddonRepo_cacheAddons(aIds, aCallback) {
+ logger.debug("cacheAddons: enabled " + this.cacheEnabled + " IDs " + aIds.toSource());
+ if (!this.cacheEnabled) {
+ if (aCallback)
+ aCallback();
+ return;
+ }
+
+ let self = this;
+ getAddonsToCache(aIds, function cacheAddons_getAddonsToCache(aAddons) {
+ // If there are no add-ons to cache, act as if caching is disabled
+ if (aAddons.length == 0) {
+ if (aCallback)
+ aCallback();
+ return;
+ }
+
+ self.getAddonsByIDs(aAddons, {
+ searchSucceeded: function cacheAddons_searchSucceeded(aAddons) {
+ for (let addon of aAddons) {
+ self._addons.set(addon.id, addon);
+ }
+ AddonDatabase.insertAddons(aAddons, aCallback);
+ },
+ searchFailed: function cacheAddons_searchFailed() {
+ logger.warn("Search failed when adding add-ons to cache");
+ if (aCallback)
+ aCallback();
+ }
+ });
+ });
+ },
+
+ /**
+ * The homepage for visiting this repository. If the corresponding preference
+ * is not defined, defaults to about:blank.
+ */
+ get homepageURL() {
+ let url = this._formatURLPref(PREF_GETADDONS_BROWSEADDONS, {});
+ return (url != null) ? url : "about:blank";
+ },
+
+ /**
+ * Returns whether this instance is currently performing a search. New
+ * searches will not be performed while this is the case.
+ */
+ get isSearching() {
+ return this._searching;
+ },
+
+ /**
+ * The url that can be visited to see recommended add-ons in this repository.
+ * If the corresponding preference is not defined, defaults to about:blank.
+ */
+ getRecommendedURL: function AddonRepo_getRecommendedURL() {
+ let url = this._formatURLPref(PREF_GETADDONS_BROWSERECOMMENDED, {});
+ return (url != null) ? url : "about:blank";
+ },
+
+ /**
+ * Retrieves the url that can be visited to see search results for the given
+ * terms. If the corresponding preference is not defined, defaults to
+ * about:blank.
+ *
+ * @param aSearchTerms
+ * Search terms used to search the repository
+ */
+ getSearchURL: function AddonRepo_getSearchURL(aSearchTerms) {
+ let url = this._formatURLPref(PREF_GETADDONS_BROWSESEARCHRESULTS, {
+ TERMS : encodeURIComponent(aSearchTerms)
+ });
+ return (url != null) ? url : "about:blank";
+ },
+
+ /**
+ * Cancels the search in progress. If there is no search in progress this
+ * does nothing.
+ */
+ cancelSearch: function AddonRepo_cancelSearch() {
+ this._searching = false;
+ if (this._request) {
+ this._request.abort();
+ this._request = null;
+ }
+ this._callback = null;
+ },
+
+ /**
+ * Begins a search for add-ons in this repository by ID. Results will be
+ * passed to the given callback.
+ *
+ * @param aIDs
+ * The array of ids to search for
+ * @param aCallback
+ * The callback to pass results to
+ */
+ getAddonsByIDs: function AddonRepo_getAddonsByIDs(aIDs, aCallback) {
+ return this._beginGetAddons(aIDs, aCallback, false);
+ },
+
+ /**
+ * Begins a search of add-ons, potentially sending performance data.
+ *
+ * @param aIDs
+ * Array of ids to search for.
+ * @param aCallback
+ * Function to pass results to.
+ * @param aSendPerformance
+ * Boolean indicating whether to send performance data with the
+ * request.
+ * @param aTimeout
+ * (Optional) timeout in milliseconds to abandon the XHR request
+ * if we have not received a response from the server.
+ */
+ _beginGetAddons: function(aIDs, aCallback, aSendPerformance, aTimeout) {
+ let ids = aIDs.slice(0);
+
+ let params = {
+ API_VERSION : API_VERSION,
+ IDS : ids.map(encodeURIComponent).join(',')
+ };
+
+ let pref = PREF_GETADDONS_BYIDS;
+
+ if (aSendPerformance) {
+ let type = Services.prefs.getPrefType(PREF_GETADDONS_BYIDS_PERFORMANCE);
+ if (type == Services.prefs.PREF_STRING) {
+ pref = PREF_GETADDONS_BYIDS_PERFORMANCE;
+
+ let startupInfo = Cc["@mozilla.org/toolkit/app-startup;1"].
+ getService(Ci.nsIAppStartup).
+ getStartupInfo();
+
+ params.TIME_MAIN = "";
+ params.TIME_FIRST_PAINT = "";
+ params.TIME_SESSION_RESTORED = "";
+ if (startupInfo.process) {
+ if (startupInfo.main) {
+ params.TIME_MAIN = startupInfo.main - startupInfo.process;
+ }
+ if (startupInfo.firstPaint) {
+ params.TIME_FIRST_PAINT = startupInfo.firstPaint -
+ startupInfo.process;
+ }
+ if (startupInfo.sessionRestored) {
+ params.TIME_SESSION_RESTORED = startupInfo.sessionRestored -
+ startupInfo.process;
+ }
+ }
+ }
+ }
+
+ let url = this._formatURLPref(pref, params);
+
+ let self = this;
+ function handleResults(aElements, aTotalResults, aCompatData) {
+ // Don't use this._parseAddons() so that, for example,
+ // incompatible add-ons are not filtered out
+ let results = [];
+ for (let i = 0; i < aElements.length && results.length < self._maxResults; i++) {
+ let result = self._parseAddon(aElements[i], null, aCompatData);
+ if (result == null)
+ continue;
+
+ // Ignore add-on if it wasn't actually requested
+ let idIndex = ids.indexOf(result.addon.id);
+ if (idIndex == -1)
+ continue;
+
+ // Ignore add-on if the add-on manager doesn't know about its type:
+ if (!(result.addon.type in AddonManager.addonTypes)) {
+ continue;
+ }
+
+ results.push(result);
+ // Ignore this add-on from now on
+ ids.splice(idIndex, 1);
+ }
+
+ // Include any compatibility overrides for addons not hosted by the
+ // remote repository.
+ for each (let addonCompat in aCompatData) {
+ if (addonCompat.hosted)
+ continue;
+
+ let addon = new AddonSearchResult(addonCompat.id);
+ // Compatibility overrides can only be for extensions.
+ addon.type = "extension";
+ addon.compatibilityOverrides = addonCompat.compatRanges;
+ let result = {
+ addon: addon,
+ xpiURL: null,
+ xpiHash: null
+ };
+ results.push(result);
+ }
+
+ // aTotalResults irrelevant
+ self._reportSuccess(results, -1);
+ }
+
+ this._beginSearch(url, ids.length, aCallback, handleResults, aTimeout);
+ },
+
+ /**
+ * Performs the daily background update check.
+ *
+ * This API both searches for the add-on IDs specified and sends performance
+ * data. It is meant to be called as part of the daily update ping. It should
+ * not be used for any other purpose. Use repopulateCache instead.
+ *
+ * @return Promise{null} Resolves when the metadata update is complete.
+ */
+ backgroundUpdateCheck: function () {
+ return this._repopulateCacheInternal(true);
+ },
+
+ /**
+ * Begins a search for recommended add-ons in this repository. Results will
+ * be passed to the given callback.
+ *
+ * @param aMaxResults
+ * The maximum number of results to return
+ * @param aCallback
+ * The callback to pass results to
+ */
+ retrieveRecommendedAddons: function AddonRepo_retrieveRecommendedAddons(aMaxResults, aCallback) {
+ let url = this._formatURLPref(PREF_GETADDONS_GETRECOMMENDED, {
+ API_VERSION : API_VERSION,
+
+ // Get twice as many results to account for potential filtering
+ MAX_RESULTS : 2 * aMaxResults
+ });
+
+ let self = this;
+ function handleResults(aElements, aTotalResults) {
+ self._getLocalAddonIds(function retrieveRecommendedAddons_getLocalAddonIds(aLocalAddonIds) {
+ // aTotalResults irrelevant
+ self._parseAddons(aElements, -1, aLocalAddonIds);
+ });
+ }
+
+ this._beginSearch(url, aMaxResults, aCallback, handleResults);
+ },
+
+ /**
+ * Begins a search for add-ons in this repository. Results will be passed to
+ * the given callback.
+ *
+ * @param aSearchTerms
+ * The terms to search for
+ * @param aMaxResults
+ * The maximum number of results to return
+ * @param aCallback
+ * The callback to pass results to
+ */
+ searchAddons: function AddonRepo_searchAddons(aSearchTerms, aMaxResults, aCallback) {
+ let compatMode = "normal";
+ if (!AddonManager.checkCompatibility)
+ compatMode = "ignore";
+ else if (AddonManager.strictCompatibility)
+ compatMode = "strict";
+
+ let substitutions = {
+ API_VERSION : API_VERSION,
+ TERMS : encodeURIComponent(aSearchTerms),
+ // Get twice as many results to account for potential filtering
+ MAX_RESULTS : 2 * aMaxResults,
+ COMPATIBILITY_MODE : compatMode,
+ };
+
+ let url = this._formatURLPref(PREF_GETADDONS_GETSEARCHRESULTS, substitutions);
+
+ let self = this;
+ function handleResults(aElements, aTotalResults) {
+ self._getLocalAddonIds(function searchAddons_getLocalAddonIds(aLocalAddonIds) {
+ self._parseAddons(aElements, aTotalResults, aLocalAddonIds);
+ });
+ }
+
+ this._beginSearch(url, aMaxResults, aCallback, handleResults);
+ },
+
+ // Posts results to the callback
+ _reportSuccess: function AddonRepo_reportSuccess(aResults, aTotalResults) {
+ this._searching = false;
+ this._request = null;
+ // The callback may want to trigger a new search so clear references early
+ // Tycho: let addons = [result.addon for each(result in aResults)];
+ let addons = [];
+ for each(let result in aResults) {
+ addons.push(result.addon);
+ }
+
+ let callback = this._callback;
+ this._callback = null;
+ callback.searchSucceeded(addons, addons.length, aTotalResults);
+ },
+
+ // Notifies the callback of a failure
+ _reportFailure: function AddonRepo_reportFailure() {
+ this._searching = false;
+ this._request = null;
+ // The callback may want to trigger a new search so clear references early
+ let callback = this._callback;
+ this._callback = null;
+ callback.searchFailed();
+ },
+
+ // Get descendant by unique tag name. Returns null if not unique tag name.
+ _getUniqueDescendant: function AddonRepo_getUniqueDescendant(aElement, aTagName) {
+ let elementsList = aElement.getElementsByTagName(aTagName);
+ return (elementsList.length == 1) ? elementsList[0] : null;
+ },
+
+ // Get direct descendant by unique tag name.
+ // Returns null if not unique tag name.
+ _getUniqueDirectDescendant: function AddonRepo_getUniqueDirectDescendant(aElement, aTagName) {
+ let elementsList = Array.filter(aElement.children,
+ function arrayFiltering(aChild) aChild.tagName == aTagName);
+ return (elementsList.length == 1) ? elementsList[0] : null;
+ },
+
+ // Parse out trimmed text content. Returns null if text content empty.
+ _getTextContent: function AddonRepo_getTextContent(aElement) {
+ let textContent = aElement.textContent.trim();
+ return (textContent.length > 0) ? textContent : null;
+ },
+
+ // Parse out trimmed text content of a descendant with the specified tag name
+ // Returns null if the parsing unsuccessful.
+ _getDescendantTextContent: function AddonRepo_getDescendantTextContent(aElement, aTagName) {
+ let descendant = this._getUniqueDescendant(aElement, aTagName);
+ return (descendant != null) ? this._getTextContent(descendant) : null;
+ },
+
+ // Parse out trimmed text content of a direct descendant with the specified
+ // tag name.
+ // Returns null if the parsing unsuccessful.
+ _getDirectDescendantTextContent: function AddonRepo_getDirectDescendantTextContent(aElement, aTagName) {
+ let descendant = this._getUniqueDirectDescendant(aElement, aTagName);
+ return (descendant != null) ? this._getTextContent(descendant) : null;
+ },
+
+ /*
+ * Creates an AddonSearchResult by parsing an <addon> element
+ *
+ * @param aElement
+ * The <addon> element to parse
+ * @param aSkip
+ * Object containing ids and sourceURIs of add-ons to skip.
+ * @param aCompatData
+ * Array of parsed addon_compatibility elements to accosiate with the
+ * resulting AddonSearchResult. Optional.
+ * @return Result object containing the parsed AddonSearchResult, xpiURL and
+ * xpiHash if the parsing was successful. Otherwise returns null.
+ */
+ _parseAddon: function AddonRepo_parseAddon(aElement, aSkip, aCompatData) {
+ let skipIDs = (aSkip && aSkip.ids) ? aSkip.ids : [];
+ let skipSourceURIs = (aSkip && aSkip.sourceURIs) ? aSkip.sourceURIs : [];
+
+ let guid = this._getDescendantTextContent(aElement, "guid");
+ if (guid == null || skipIDs.indexOf(guid) != -1)
+ return null;
+
+ let addon = new AddonSearchResult(guid);
+ let result = {
+ addon: addon,
+ xpiURL: null,
+ xpiHash: null
+ };
+
+ if (aCompatData && guid in aCompatData)
+ addon.compatibilityOverrides = aCompatData[guid].compatRanges;
+
+ let self = this;
+ for (let node = aElement.firstChild; node; node = node.nextSibling) {
+ if (!(node instanceof Ci.nsIDOMElement))
+ continue;
+
+ let localName = node.localName;
+
+ // Handle case where the wanted string value is located in text content
+ // but only if the content is not empty
+ if (localName in STRING_KEY_MAP) {
+ addon[STRING_KEY_MAP[localName]] = this._getTextContent(node) || addon[STRING_KEY_MAP[localName]];
+ continue;
+ }
+
+ // Handle case where the wanted string value is html located in text content
+ if (localName in HTML_KEY_MAP) {
+ addon[HTML_KEY_MAP[localName]] = convertHTMLToPlainText(this._getTextContent(node));
+ continue;
+ }
+
+ // Handle case where the wanted integer value is located in text content
+ if (localName in INTEGER_KEY_MAP) {
+ let value = parseInt(this._getTextContent(node));
+ if (value >= 0)
+ addon[INTEGER_KEY_MAP[localName]] = value;
+ continue;
+ }
+
+ // Handle cases that aren't as simple as grabbing the text content
+ switch (localName) {
+ case "type":
+ // Map AMO's type id to corresponding string
+ // https://github.com/mozilla/olympia/blob/master/apps/constants/base.py#L127
+ // These definitions need to be updated whenever AMO adds a new type.
+ let id = parseInt(node.getAttribute("id"));
+ switch (id) {
+ case 1:
+ addon.type = "extension";
+ break;
+ case 2:
+ addon.type = "theme";
+ break;
+ case 3:
+ addon.type = "dictionary";
+ break;
+ case 4:
+ addon.type = "search";
+ break;
+ case 5:
+ case 6:
+ addon.type = "locale";
+ break;
+ case 7:
+ addon.type = "plugin";
+ break;
+ case 8:
+ addon.type = "api";
+ break;
+ case 9:
+ addon.type = "lightweight-theme";
+ break;
+ case 11:
+ addon.type = "webapp";
+ break;
+ default:
+ logger.info("Unknown type id " + id + " found when parsing response for GUID " + guid);
+ }
+ break;
+ case "authors":
+ let authorNodes = node.getElementsByTagName("author");
+ for (let authorNode of authorNodes) {
+ let name = self._getDescendantTextContent(authorNode, "name");
+ let link = self._getDescendantTextContent(authorNode, "link");
+ if (name == null || link == null)
+ continue;
+
+ let author = new AddonManagerPrivate.AddonAuthor(name, link);
+ if (addon.creator == null)
+ addon.creator = author;
+ else {
+ if (addon.developers == null)
+ addon.developers = [];
+
+ addon.developers.push(author);
+ }
+ }
+ break;
+ case "previews":
+ let previewNodes = node.getElementsByTagName("preview");
+ for (let previewNode of previewNodes) {
+ let full = self._getUniqueDescendant(previewNode, "full");
+ if (full == null)
+ continue;
+
+ let fullURL = self._getTextContent(full);
+ let fullWidth = full.getAttribute("width");
+ let fullHeight = full.getAttribute("height");
+
+ let thumbnailURL, thumbnailWidth, thumbnailHeight;
+ let thumbnail = self._getUniqueDescendant(previewNode, "thumbnail");
+ if (thumbnail) {
+ thumbnailURL = self._getTextContent(thumbnail);
+ thumbnailWidth = thumbnail.getAttribute("width");
+ thumbnailHeight = thumbnail.getAttribute("height");
+ }
+ let caption = self._getDescendantTextContent(previewNode, "caption");
+ let screenshot = new AddonManagerPrivate.AddonScreenshot(fullURL, fullWidth, fullHeight,
+ thumbnailURL, thumbnailWidth,
+ thumbnailHeight, caption);
+
+ if (addon.screenshots == null)
+ addon.screenshots = [];
+
+ if (previewNode.getAttribute("primary") == 1)
+ addon.screenshots.unshift(screenshot);
+ else
+ addon.screenshots.push(screenshot);
+ }
+ break;
+ case "learnmore":
+ addon.learnmoreURL = this._getTextContent(node);
+ addon.homepageURL = addon.homepageURL || addon.learnmoreURL;
+ break;
+ case "contribution_data":
+ let meetDevelopers = this._getDescendantTextContent(node, "meet_developers");
+ let suggestedAmount = this._getDescendantTextContent(node, "suggested_amount");
+ if (meetDevelopers != null) {
+ addon.contributionURL = meetDevelopers;
+ addon.contributionAmount = suggestedAmount;
+ }
+ break
+ case "payment_data":
+ let link = this._getDescendantTextContent(node, "link");
+ let amountTag = this._getUniqueDescendant(node, "amount");
+ let amount = parseFloat(amountTag.getAttribute("amount"));
+ let displayAmount = this._getTextContent(amountTag);
+ if (link != null && amount != null && displayAmount != null) {
+ addon.purchaseURL = link;
+ addon.purchaseAmount = amount;
+ addon.purchaseDisplayAmount = displayAmount;
+ }
+ break
+ case "rating":
+ let averageRating = parseInt(this._getTextContent(node));
+ if (averageRating >= 0)
+ addon.averageRating = Math.min(5, averageRating);
+ break;
+ case "reviews":
+ let url = this._getTextContent(node);
+ let num = parseInt(node.getAttribute("num"));
+ if (url != null && num >= 0) {
+ addon.reviewURL = url;
+ addon.reviewCount = num;
+ }
+ break;
+ case "status":
+ let repositoryStatus = parseInt(node.getAttribute("id"));
+ if (!isNaN(repositoryStatus))
+ addon.repositoryStatus = repositoryStatus;
+ break;
+ case "all_compatible_os":
+ let nodes = node.getElementsByTagName("os");
+ addon.isPlatformCompatible = Array.some(nodes, function parseAddon_platformCompatFilter(aNode) {
+ let text = aNode.textContent.toLowerCase().trim();
+ return text == "all" || text == Services.appinfo.OS.toLowerCase();
+ });
+ break;
+ case "install":
+ // No os attribute means the xpi is compatible with any os
+ if (node.hasAttribute("os")) {
+ let os = node.getAttribute("os").trim().toLowerCase();
+ // If the os is not ALL and not the current OS then ignore this xpi
+ if (os != "all" && os != Services.appinfo.OS.toLowerCase())
+ break;
+ }
+
+ let xpiURL = this._getTextContent(node);
+ if (xpiURL == null)
+ break;
+
+ if (skipSourceURIs.indexOf(xpiURL) != -1)
+ return null;
+
+ result.xpiURL = xpiURL;
+ addon.sourceURI = NetUtil.newURI(xpiURL);
+
+ let size = parseInt(node.getAttribute("size"));
+ addon.size = (size >= 0) ? size : null;
+
+ let xpiHash = node.getAttribute("hash");
+ if (xpiHash != null)
+ xpiHash = xpiHash.trim();
+ result.xpiHash = xpiHash ? xpiHash : null;
+ break;
+ case "last_updated":
+ let epoch = parseInt(node.getAttribute("epoch"));
+ if (!isNaN(epoch))
+ addon.updateDate = new Date(1000 * epoch);
+ break;
+ case "icon":
+ addon.icons[node.getAttribute("size")] = this._getTextContent(node);
+ break;
+ }
+ }
+
+ return result;
+ },
+
+ _parseAddons: function AddonRepo_parseAddons(aElements, aTotalResults, aSkip) {
+ let self = this;
+ let results = [];
+
+ function isSameApplication(aAppNode) {
+#ifdef MC_APP_ID
+ if (self._getTextContent(aAppNode) == ALT_APP_ID || self._getTextContent(aAppNode) == Services.appinfo.ID) {
+#else
+ if (self._getTextContent(aAppNode) == Services.appinfo.ID) {
+#endif
+ return true;
+ }
+ return false;
+ }
+
+ for (let i = 0; i < aElements.length && results.length < this._maxResults; i++) {
+ let element = aElements[i];
+
+ let tags = this._getUniqueDescendant(element, "compatible_applications");
+ if (tags == null)
+ continue;
+
+ let applications = tags.getElementsByTagName("appID");
+ let compatible = Array.some(applications, function parseAddons_applicationsCompatFilter(aAppNode) {
+ if (!isSameApplication(aAppNode))
+ return false;
+
+ let parent = aAppNode.parentNode;
+ let minVersion = self._getDescendantTextContent(parent, "min_version");
+ let maxVersion = self._getDescendantTextContent(parent, "max_version");
+ if (minVersion == null || maxVersion == null)
+ return false;
+
+ let currentVersion = Services.appinfo.version;
+ return (Services.vc.compare(minVersion, currentVersion) <= 0 &&
+ ((!AddonManager.strictCompatibility) ||
+ Services.vc.compare(currentVersion, maxVersion) <= 0));
+ });
+
+ // Ignore add-ons not compatible with this Application
+ if (!compatible) {
+ if (AddonManager.checkCompatibility)
+ continue;
+
+ if (!Array.some(applications, isSameApplication))
+ continue;
+ }
+
+ // Add-on meets all requirements, so parse out data.
+ // Don't pass in compatiblity override data, because that's only returned
+ // in GUID searches, which don't use _parseAddons().
+ let result = this._parseAddon(element, aSkip);
+ if (result == null)
+ continue;
+
+ // Ignore add-on missing a required attribute
+ let requiredAttributes = ["id", "name", "version", "type", "creator"];
+ if (requiredAttributes.some(function parseAddons_attributeFilter(aAttribute) !result.addon[aAttribute]))
+ continue;
+
+ // Ignore add-on with a type AddonManager doesn't understand:
+ if (!(result.addon.type in AddonManager.addonTypes))
+ continue;
+
+ // Add only if the add-on is compatible with the platform
+ if (!result.addon.isPlatformCompatible)
+ continue;
+
+ // Add only if there was an xpi compatible with this OS or there was a
+ // way to purchase the add-on
+ if (!result.xpiURL && !result.addon.purchaseURL)
+ continue;
+
+ result.addon.isCompatible = compatible;
+
+ results.push(result);
+ // Ignore this add-on from now on by adding it to the skip array
+ aSkip.ids.push(result.addon.id);
+ }
+
+ // Immediately report success if no AddonInstall instances to create
+ let pendingResults = results.length;
+ if (pendingResults == 0) {
+ this._reportSuccess(results, aTotalResults);
+ return;
+ }
+
+ // Create an AddonInstall for each result
+ results.forEach(function(aResult) {
+ let addon = aResult.addon;
+ let callback = function addonInstallCallback(aInstall) {
+ addon.install = aInstall;
+ pendingResults--;
+ if (pendingResults == 0)
+ self._reportSuccess(results, aTotalResults);
+ }
+
+ if (aResult.xpiURL) {
+ AddonManager.getInstallForURL(aResult.xpiURL, callback,
+ "application/x-xpinstall", aResult.xpiHash,
+ addon.name, addon.icons, addon.version);
+ }
+ else {
+ callback(null);
+ }
+ });
+ },
+
+ // Parses addon_compatibility nodes, that describe compatibility overrides.
+ _parseAddonCompatElement: function AddonRepo_parseAddonCompatElement(aResultObj, aElement) {
+ let guid = this._getDescendantTextContent(aElement, "guid");
+ if (!guid) {
+ logger.debug("Compatibility override is missing guid.");
+ return;
+ }
+
+ let compat = {id: guid};
+ compat.hosted = aElement.getAttribute("hosted") != "false";
+
+ function findMatchingAppRange(aNodes) {
+ let toolkitAppRange = null;
+ for (let node of aNodes) {
+ let appID = this._getDescendantTextContent(node, "appID");
+ if (appID != Services.appinfo.ID && appID != TOOLKIT_ID)
+ continue;
+
+ let minVersion = this._getDescendantTextContent(node, "min_version");
+ let maxVersion = this._getDescendantTextContent(node, "max_version");
+ if (minVersion == null || maxVersion == null)
+ continue;
+
+ let appRange = { appID: appID,
+ appMinVersion: minVersion,
+ appMaxVersion: maxVersion };
+
+ // Only use Toolkit app ranges if no ranges match the application ID.
+ if (appID == TOOLKIT_ID)
+ toolkitAppRange = appRange;
+ else
+ return appRange;
+ }
+ return toolkitAppRange;
+ }
+
+ function parseRangeNode(aNode) {
+ let type = aNode.getAttribute("type");
+ // Only "incompatible" (blacklisting) is supported for now.
+ if (type != "incompatible") {
+ logger.debug("Compatibility override of unsupported type found.");
+ return null;
+ }
+
+ let override = new AddonManagerPrivate.AddonCompatibilityOverride(type);
+
+ override.minVersion = this._getDirectDescendantTextContent(aNode, "min_version");
+ override.maxVersion = this._getDirectDescendantTextContent(aNode, "max_version");
+
+ if (!override.minVersion) {
+ logger.debug("Compatibility override is missing min_version.");
+ return null;
+ }
+ if (!override.maxVersion) {
+ logger.debug("Compatibility override is missing max_version.");
+ return null;
+ }
+
+ let appRanges = aNode.querySelectorAll("compatible_applications > application");
+ let appRange = findMatchingAppRange.bind(this)(appRanges);
+ if (!appRange) {
+ logger.debug("Compatibility override is missing a valid application range.");
+ return null;
+ }
+
+ override.appID = appRange.appID;
+ override.appMinVersion = appRange.appMinVersion;
+ override.appMaxVersion = appRange.appMaxVersion;
+
+ return override;
+ }
+
+ let rangeNodes = aElement.querySelectorAll("version_ranges > version_range");
+ compat.compatRanges = Array.map(rangeNodes, parseRangeNode.bind(this))
+ .filter(function compatRangesFilter(aItem) !!aItem);
+ if (compat.compatRanges.length == 0)
+ return;
+
+ aResultObj[compat.id] = compat;
+ },
+
+ // Parses addon_compatibility elements.
+ _parseAddonCompatData: function AddonRepo_parseAddonCompatData(aElements) {
+ let compatData = {};
+ Array.forEach(aElements, this._parseAddonCompatElement.bind(this, compatData));
+ return compatData;
+ },
+
+ // Begins a new search if one isn't currently executing
+ _beginSearch: function(aURI, aMaxResults, aCallback, aHandleResults, aTimeout) {
+ if (this._searching || aURI == null || aMaxResults <= 0) {
+ logger.warn("AddonRepository search failed: searching " + this._searching + " aURI " + aURI +
+ " aMaxResults " + aMaxResults);
+ aCallback.searchFailed();
+ return;
+ }
+
+ this._searching = true;
+ this._callback = aCallback;
+ this._maxResults = aMaxResults;
+
+ logger.debug("Requesting " + aURI);
+
+ this._request = new XHRequest();
+ this._request.mozBackgroundRequest = true;
+ this._request.open("GET", aURI, true);
+ this._request.overrideMimeType("text/xml");
+ if (aTimeout) {
+ this._request.timeout = aTimeout;
+ }
+
+ this._request.addEventListener("error", aEvent => this._reportFailure(), false);
+ this._request.addEventListener("timeout", aEvent => this._reportFailure(), false);
+ this._request.addEventListener("load", aEvent => {
+ logger.debug("Got metadata search load event");
+ let request = aEvent.target;
+ let responseXML = request.responseXML;
+
+ if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
+ (request.status != 200 && request.status != 0)) {
+ this._reportFailure();
+ return;
+ }
+
+ let documentElement = responseXML.documentElement;
+ let elements = documentElement.getElementsByTagName("addon");
+ let totalResults = elements.length;
+ let parsedTotalResults = parseInt(documentElement.getAttribute("total_results"));
+ // Parsed value of total results only makes sense if >= elements.length
+ if (parsedTotalResults >= totalResults)
+ totalResults = parsedTotalResults;
+
+ let compatElements = documentElement.getElementsByTagName("addon_compatibility");
+ let compatData = this._parseAddonCompatData(compatElements);
+
+ aHandleResults(elements, totalResults, compatData);
+ }, false);
+ this._request.send(null);
+ },
+
+ // Gets the id's of local add-ons, and the sourceURI's of local installs,
+ // passing the results to aCallback
+ _getLocalAddonIds: function AddonRepo_getLocalAddonIds(aCallback) {
+ let self = this;
+ let localAddonIds = {ids: null, sourceURIs: null};
+
+ AddonManager.getAllAddons(function getLocalAddonIds_getAllAddons(aAddons) {
+ // Tycho: localAddonIds.ids = [a.id for each (a in aAddons)];
+ localAddonIds.ids = [];
+
+ for each(let a in aAddons) {
+ localAddonIds.ids.push(a.id);
+ }
+
+ if (localAddonIds.sourceURIs)
+ aCallback(localAddonIds);
+ });
+
+ AddonManager.getAllInstalls(function getLocalAddonIds_getAllInstalls(aInstalls) {
+ localAddonIds.sourceURIs = [];
+ aInstalls.forEach(function(aInstall) {
+ if (aInstall.state != AddonManager.STATE_AVAILABLE)
+ localAddonIds.sourceURIs.push(aInstall.sourceURI.spec);
+ });
+
+ if (localAddonIds.ids)
+ aCallback(localAddonIds);
+ });
+ },
+
+ // Create url from preference, returning null if preference does not exist
+ _formatURLPref: function AddonRepo_formatURLPref(aPreference, aSubstitutions) {
+ let url = Services.prefs.getCharPref(aPreference, "");
+ if (!url) {
+ logger.warn("_formatURLPref: Couldn't get pref: " + aPreference);
+ return null;
+ }
+
+ url = url.replace(/%([A-Z_]+)%/g, function urlSubstitution(aMatch, aKey) {
+ return (aKey in aSubstitutions) ? aSubstitutions[aKey] : aMatch;
+ });
+
+ return Services.urlFormatter.formatURL(url);
+ },
+
+ // Find a AddonCompatibilityOverride that matches a given aAddonVersion and
+ // application/platform version.
+ findMatchingCompatOverride: function AddonRepo_findMatchingCompatOverride(aAddonVersion,
+ aCompatOverrides,
+ aAppVersion,
+ aPlatformVersion) {
+ for (let override of aCompatOverrides) {
+
+ let appVersion = null;
+ if (override.appID == TOOLKIT_ID)
+ appVersion = aPlatformVersion || Services.appinfo.greVersion;
+ else
+ appVersion = aAppVersion || Services.appinfo.version;
+
+ if (Services.vc.compare(override.minVersion, aAddonVersion) <= 0 &&
+ Services.vc.compare(aAddonVersion, override.maxVersion) <= 0 &&
+ Services.vc.compare(override.appMinVersion, appVersion) <= 0 &&
+ Services.vc.compare(appVersion, override.appMaxVersion) <= 0) {
+ return override;
+ }
+ }
+ return null;
+ },
+
+ flush: function() {
+ return AddonDatabase.flush();
+ }
+};
+
+var AddonDatabase = {
+ // false if there was an unrecoverable error opening the database
+ databaseOk: true,
+
+ connectionPromise: null,
+ // the in-memory database
+ DB: BLANK_DB(),
+
+ /**
+ * A getter to retrieve the path to the DB
+ */
+ get jsonFile() {
+ return OS.Path.join(OS.Constants.Path.profileDir, FILE_DATABASE);
+ },
+
+ /**
+ * Asynchronously opens a new connection to the database file.
+ *
+ * @return {Promise} a promise that resolves to the database.
+ */
+ openConnection: function() {
+ if (!this.connectionPromise) {
+ this.connectionPromise = Task.spawn(function*() {
+ this.DB = BLANK_DB();
+
+ let inputDB, schema;
+
+ try {
+ let data = yield OS.File.read(this.jsonFile, { encoding: "utf-8"})
+ inputDB = JSON.parse(data);
+
+ if (!inputDB.hasOwnProperty("addons") ||
+ !Array.isArray(inputDB.addons)) {
+ throw new Error("No addons array.");
+ }
+
+ if (!inputDB.hasOwnProperty("schema")) {
+ throw new Error("No schema specified.");
+ }
+
+ schema = parseInt(inputDB.schema, 10);
+
+ if (!Number.isInteger(schema) ||
+ schema < DB_MIN_JSON_SCHEMA) {
+ throw new Error("Invalid schema value.");
+ }
+ } catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
+ logger.debug("No " + FILE_DATABASE + " found.");
+
+ // Create a blank addons.json file
+ this._saveDBToDisk();
+
+ let dbSchema = Services.prefs.getIntPref(PREF_GETADDONS_DB_SCHEMA, 0);
+
+ if (dbSchema < DB_MIN_JSON_SCHEMA) {
+ let results = yield new Promise((resolve, reject) => {
+ AddonRepository_SQLiteMigrator.migrate(resolve);
+ });
+
+ if (results.length) {
+ yield this._insertAddons(results);
+ }
+
+ Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
+ }
+
+ return this.DB;
+ } catch (e) {
+ logger.error("Malformed " + FILE_DATABASE + ": " + e);
+ this.databaseOk = false;
+
+ return this.DB;
+ }
+
+ Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
+
+ // We use _insertAddon manually instead of calling
+ // insertAddons to avoid the write to disk which would
+ // be a waste since this is the data that was just read.
+ for (let addon of inputDB.addons) {
+ this._insertAddon(addon);
+ }
+
+ return this.DB;
+ }.bind(this));
+ }
+
+ return this.connectionPromise;
+ },
+
+ /**
+ * A lazy getter for the database connection.
+ */
+ get connection() {
+ return this.openConnection();
+ },
+
+ /**
+ * Asynchronously shuts down the database connection and releases all
+ * cached objects
+ *
+ * @param aCallback
+ * An optional callback to call once complete
+ * @param aSkipFlush
+ * An optional boolean to skip flushing data to disk. Useful
+ * when the database is going to be deleted afterwards.
+ */
+ shutdown: function AD_shutdown(aSkipFlush) {
+ this.databaseOk = true;
+
+ if (!this.connectionPromise) {
+ return Promise.resolve();
+ }
+
+ this.connectionPromise = null;
+
+ if (aSkipFlush) {
+ return Promise.resolve();
+ } else {
+ return this.Writer.flush();
+ }
+ },
+
+ /**
+ * Asynchronously deletes the database, shutting down the connection
+ * first if initialized
+ *
+ * @param aCallback
+ * An optional callback to call once complete
+ * @return Promise{null} resolves when the database has been deleted
+ */
+ delete: function AD_delete(aCallback) {
+ this.DB = BLANK_DB();
+
+ this._deleting = this.Writer.flush()
+ .then(null, () => {})
+ // shutdown(true) never rejects
+ .then(() => this.shutdown(true))
+ .then(() => OS.File.remove(this.jsonFile, {}))
+ .then(null, error => logger.error("Unable to delete Addon Repository file " +
+ this.jsonFile, error))
+ .then(() => this._deleting = null)
+ .then(aCallback);
+ return this._deleting;
+ },
+
+ toJSON: function AD_toJSON() {
+ let json = {
+ schema: this.DB.schema,
+ addons: []
+ }
+
+ for (let [, value] of this.DB.addons)
+ json.addons.push(value);
+
+ return json;
+ },
+
+ /*
+ * This is a deferred task writer that is used
+ * to batch operations done within 50ms of each
+ * other and thus generating only one write to disk
+ */
+ get Writer() {
+ delete this.Writer;
+ this.Writer = new DeferredSave(
+ this.jsonFile,
+ () => { return JSON.stringify(this); },
+ DB_BATCH_TIMEOUT_MS
+ );
+ return this.Writer;
+ },
+
+ /**
+ * Flush any pending I/O on the addons.json file
+ * @return: Promise{null}
+ * Resolves when the pending I/O (writing out or deleting
+ * addons.json) completes
+ */
+ flush: function() {
+ if (this._deleting) {
+ return this._deleting;
+ }
+ return this.Writer.flush();
+ },
+
+ /**
+ * Asynchronously retrieve all add-ons from the database
+ * @return: Promise{Map}
+ * Resolves when the add-ons are retrieved from the database
+ */
+ retrieveStoredData: function (){
+ return this.openConnection().then(db => db.addons);
+ },
+
+ /**
+ * Asynchronously repopulates the database so it only contains the
+ * specified add-ons
+ *
+ * @param aAddons
+ * The array of add-ons to repopulate the database with
+ * @param aCallback
+ * An optional callback to call once complete
+ */
+ repopulate: function AD_repopulate(aAddons, aCallback) {
+ this.DB.addons.clear();
+ this.insertAddons(aAddons, function repopulate_insertAddons() {
+ let now = Math.round(Date.now() / 1000);
+ logger.debug("Cache repopulated, setting " + PREF_METADATA_LASTUPDATE + " to " + now);
+ Services.prefs.setIntPref(PREF_METADATA_LASTUPDATE, now);
+ if (aCallback)
+ aCallback();
+ });
+ },
+
+ /**
+ * Asynchronously inserts an array of add-ons into the database
+ *
+ * @param aAddons
+ * The array of add-ons to insert
+ * @param aCallback
+ * An optional callback to call once complete
+ */
+ insertAddons: Task.async(function* (aAddons, aCallback) {
+ yield this.openConnection();
+ yield this._insertAddons(aAddons, aCallback);
+ }),
+
+ _insertAddons: Task.async(function* (aAddons, aCallback) {
+ for (let addon of aAddons) {
+ this._insertAddon(addon);
+ }
+
+ yield this._saveDBToDisk();
+ aCallback && aCallback();
+ }),
+
+ /**
+ * Inserts an individual add-on into the database. If the add-on already
+ * exists in the database (by id), then the specified add-on will not be
+ * inserted.
+ *
+ * @param aAddon
+ * The add-on to insert into the database
+ * @param aCallback
+ * The callback to call once complete
+ */
+ _insertAddon: function AD__insertAddon(aAddon) {
+ let newAddon = this._parseAddon(aAddon);
+ if (!newAddon ||
+ !newAddon.id ||
+ this.DB.addons.has(newAddon.id))
+ return;
+
+ this.DB.addons.set(newAddon.id, newAddon);
+ },
+
+ /*
+ * Creates an AddonSearchResult by parsing an object structure
+ * retrieved from the DB JSON representation.
+ *
+ * @param aObj
+ * The object to parse
+ * @return Returns an AddonSearchResult object.
+ */
+ _parseAddon: function (aObj) {
+ if (aObj instanceof AddonSearchResult)
+ return aObj;
+
+ let id = aObj.id;
+ if (!aObj.id)
+ return null;
+
+ let addon = new AddonSearchResult(id);
+
+ for (let [expectedProperty,] of Iterator(AddonSearchResult.prototype)) {
+ if (!(expectedProperty in aObj) ||
+ typeof(aObj[expectedProperty]) === "function")
+ continue;
+
+ let value = aObj[expectedProperty];
+
+ try {
+ switch (expectedProperty) {
+ case "sourceURI":
+ addon.sourceURI = value ? NetUtil.newURI(value) : null;
+ break;
+
+ case "creator":
+ addon.creator = value
+ ? this._makeDeveloper(value)
+ : null;
+ break;
+
+ case "updateDate":
+ addon.updateDate = value ? new Date(value) : null;
+ break;
+
+ case "developers":
+ if (!addon.developers) addon.developers = [];
+ for (let developer of value) {
+ addon.developers.push(this._makeDeveloper(developer));
+ }
+ break;
+
+ case "screenshots":
+ if (!addon.screenshots) addon.screenshots = [];
+ for (let screenshot of value) {
+ addon.screenshots.push(this._makeScreenshot(screenshot));
+ }
+ break;
+
+ case "compatibilityOverrides":
+ if (!addon.compatibilityOverrides) addon.compatibilityOverrides = [];
+ for (let override of value) {
+ addon.compatibilityOverrides.push(
+ this._makeCompatOverride(override)
+ );
+ }
+ break;
+
+ case "icons":
+ if (!addon.icons) addon.icons = {};
+ for (let [size, url] of Iterator(aObj.icons)) {
+ addon.icons[size] = url;
+ }
+ break;
+
+ case "iconURL":
+ break;
+
+ default:
+ addon[expectedProperty] = value;
+ }
+ } catch (ex) {
+ logger.warn("Error in parsing property value for " + expectedProperty + " | " + ex);
+ }
+
+ // delete property from obj to indicate we've already
+ // handled it. The remaining public properties will
+ // be stored separately and just passed through to
+ // be written back to the DB.
+ delete aObj[expectedProperty];
+ }
+
+ // Copy remaining properties to a separate object
+ // to prevent accidental access on downgraded versions.
+ // The properties will be merged in the same object
+ // prior to being written back through toJSON.
+ for (let remainingProperty of Object.keys(aObj)) {
+ switch (typeof(aObj[remainingProperty])) {
+ case "boolean":
+ case "number":
+ case "string":
+ case "object":
+ // these types are accepted
+ break;
+ default:
+ continue;
+ }
+
+ if (!remainingProperty.startsWith("_"))
+ addon._unsupportedProperties[remainingProperty] =
+ aObj[remainingProperty];
+ }
+
+ return addon;
+ },
+
+ /**
+ * Write the in-memory DB to disk, after waiting for
+ * the DB_BATCH_TIMEOUT_MS timeout.
+ *
+ * @return Promise A promise that resolves after the
+ * write to disk has completed.
+ */
+ _saveDBToDisk: function() {
+ return this.Writer.saveChanges().then(
+ null,
+ e => logger.error("SaveDBToDisk failed", e));
+ },
+
+ /**
+ * Make a developer object from a vanilla
+ * JS object from the JSON database
+ *
+ * @param aObj
+ * The JS object to use
+ * @return The created developer
+ */
+ _makeDeveloper: function (aObj) {
+ let name = aObj.name;
+ let url = aObj.url;
+ return new AddonManagerPrivate.AddonAuthor(name, url);
+ },
+
+ /**
+ * Make a screenshot object from a vanilla
+ * JS object from the JSON database
+ *
+ * @param aObj
+ * The JS object to use
+ * @return The created screenshot
+ */
+ _makeScreenshot: function (aObj) {
+ let url = aObj.url;
+ let width = aObj.width;
+ let height = aObj.height;
+ let thumbnailURL = aObj.thumbnailURL;
+ let thumbnailWidth = aObj.thumbnailWidth;
+ let thumbnailHeight = aObj.thumbnailHeight;
+ let caption = aObj.caption;
+ return new AddonManagerPrivate.AddonScreenshot(url, width, height, thumbnailURL,
+ thumbnailWidth, thumbnailHeight, caption);
+ },
+
+ /**
+ * Make a CompatibilityOverride from a vanilla
+ * JS object from the JSON database
+ *
+ * @param aObj
+ * The JS object to use
+ * @return The created CompatibilityOverride
+ */
+ _makeCompatOverride: function (aObj) {
+ let type = aObj.type;
+ let minVersion = aObj.minVersion;
+ let maxVersion = aObj.maxVersion;
+ let appID = aObj.appID;
+ let appMinVersion = aObj.appMinVersion;
+ let appMaxVersion = aObj.appMaxVersion;
+ return new AddonManagerPrivate.AddonCompatibilityOverride(type,
+ minVersion,
+ maxVersion,
+ appID,
+ appMinVersion,
+ appMaxVersion);
+ },
+};
diff --git a/components/addons/src/AddonRepository_SQLiteMigrator.jsm b/components/addons/src/AddonRepository_SQLiteMigrator.jsm
new file mode 100644
index 000000000..66147b9aa
--- /dev/null
+++ b/components/addons/src/AddonRepository_SQLiteMigrator.jsm
@@ -0,0 +1,522 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+const KEY_PROFILEDIR = "ProfD";
+const FILE_DATABASE = "addons.sqlite";
+const LAST_DB_SCHEMA = 4;
+
+// Add-on properties present in the columns of the database
+const PROP_SINGLE = ["id", "type", "name", "version", "creator", "description",
+ "fullDescription", "developerComments", "eula",
+ "homepageURL", "supportURL", "contributionURL",
+ "contributionAmount", "averageRating", "reviewCount",
+ "reviewURL", "totalDownloads", "weeklyDownloads",
+ "dailyUsers", "sourceURI", "repositoryStatus", "size",
+ "updateDate"];
+
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.repository.sqlmigrator";
+
+// Create a new logger for use by the Addons Repository SQL Migrator
+// (Requires AddonManager.jsm)
+var logger = Log.repository.getLogger(LOGGER_ID);
+
+this.EXPORTED_SYMBOLS = ["AddonRepository_SQLiteMigrator"];
+
+
+this.AddonRepository_SQLiteMigrator = {
+
+ /**
+ * Migrates data from a previous SQLite version of the
+ * database to the JSON version.
+ *
+ * @param structFunctions an object that contains functions
+ * to create the various objects used
+ * in the new JSON format
+ * @param aCallback A callback to be called when migration
+ * finishes, with the results in an array
+ * @returns bool True if a migration will happen (DB was
+ * found and succesfully opened)
+ */
+ migrate: function(aCallback) {
+ if (!this._openConnection()) {
+ this._closeConnection();
+ aCallback([]);
+ return false;
+ }
+
+ logger.debug("Importing addon repository from previous " + FILE_DATABASE + " storage.");
+
+ this._retrieveStoredData((results) => {
+ this._closeConnection();
+ // Tycho: let resultArray = [addon for ([,addon] of Iterator(results))];
+ let resultArray = [];
+ for (let [,addon] of Iterator(results)) {
+ resultArray.push(addon);
+ }
+ logger.debug(resultArray.length + " addons imported.")
+ aCallback(resultArray);
+ });
+
+ return true;
+ },
+
+ /**
+ * Synchronously opens a new connection to the database file.
+ *
+ * @return bool Whether the DB was opened successfully.
+ */
+ _openConnection: function AD_openConnection() {
+ delete this.connection;
+
+ let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
+ if (!dbfile.exists())
+ return false;
+
+ try {
+ this.connection = Services.storage.openUnsharedDatabase(dbfile);
+ } catch (e) {
+ return false;
+ }
+
+ this.connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
+
+ // Any errors in here should rollback
+ try {
+ this.connection.beginTransaction();
+
+ switch (this.connection.schemaVersion) {
+ case 0:
+ return false;
+
+ case 1:
+ logger.debug("Upgrading database schema to version 2");
+ this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN width INTEGER");
+ this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN height INTEGER");
+ this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailWidth INTEGER");
+ this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailHeight INTEGER");
+ case 2:
+ logger.debug("Upgrading database schema to version 3");
+ this.connection.createTable("compatibility_override",
+ "addon_internal_id INTEGER, " +
+ "num INTEGER, " +
+ "type TEXT, " +
+ "minVersion TEXT, " +
+ "maxVersion TEXT, " +
+ "appID TEXT, " +
+ "appMinVersion TEXT, " +
+ "appMaxVersion TEXT, " +
+ "PRIMARY KEY (addon_internal_id, num)");
+ case 3:
+ logger.debug("Upgrading database schema to version 4");
+ this.connection.createTable("icon",
+ "addon_internal_id INTEGER, " +
+ "size INTEGER, " +
+ "url TEXT, " +
+ "PRIMARY KEY (addon_internal_id, size)");
+ this._createIndices();
+ this._createTriggers();
+ this.connection.schemaVersion = LAST_DB_SCHEMA;
+ case LAST_DB_SCHEMA:
+ break;
+ default:
+ return false;
+ }
+ this.connection.commitTransaction();
+ } catch (e) {
+ logger.error("Failed to open " + FILE_DATABASE + ". Data import will not happen.", e);
+ this.logSQLError(this.connection.lastError, this.connection.lastErrorString);
+ this.connection.rollbackTransaction();
+ return false;
+ }
+
+ return true;
+ },
+
+ _closeConnection: function() {
+ for each (let stmt in this.asyncStatementsCache)
+ stmt.finalize();
+ this.asyncStatementsCache = {};
+
+ if (this.connection)
+ this.connection.asyncClose();
+
+ delete this.connection;
+ },
+
+ /**
+ * Asynchronously retrieve all add-ons from the database, and pass it
+ * to the specified callback
+ *
+ * @param aCallback
+ * The callback to pass the add-ons back to
+ */
+ _retrieveStoredData: function AD_retrieveStoredData(aCallback) {
+ let self = this;
+ let addons = {};
+
+ // Retrieve all data from the addon table
+ function getAllAddons() {
+ self.getAsyncStatement("getAllAddons").executeAsync({
+ handleResult: function getAllAddons_handleResult(aResults) {
+ let row = null;
+ while ((row = aResults.getNextRow())) {
+ let internal_id = row.getResultByName("internal_id");
+ addons[internal_id] = self._makeAddonFromAsyncRow(row);
+ }
+ },
+
+ handleError: self.asyncErrorLogger,
+
+ handleCompletion: function getAllAddons_handleCompletion(aReason) {
+ if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ logger.error("Error retrieving add-ons from database. Returning empty results");
+ aCallback({});
+ return;
+ }
+
+ getAllDevelopers();
+ }
+ });
+ }
+
+ // Retrieve all data from the developer table
+ function getAllDevelopers() {
+ self.getAsyncStatement("getAllDevelopers").executeAsync({
+ handleResult: function getAllDevelopers_handleResult(aResults) {
+ let row = null;
+ while ((row = aResults.getNextRow())) {
+ let addon_internal_id = row.getResultByName("addon_internal_id");
+ if (!(addon_internal_id in addons)) {
+ logger.warn("Found a developer not linked to an add-on in database");
+ continue;
+ }
+
+ let addon = addons[addon_internal_id];
+ if (!addon.developers)
+ addon.developers = [];
+
+ addon.developers.push(self._makeDeveloperFromAsyncRow(row));
+ }
+ },
+
+ handleError: self.asyncErrorLogger,
+
+ handleCompletion: function getAllDevelopers_handleCompletion(aReason) {
+ if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ logger.error("Error retrieving developers from database. Returning empty results");
+ aCallback({});
+ return;
+ }
+
+ getAllScreenshots();
+ }
+ });
+ }
+
+ // Retrieve all data from the screenshot table
+ function getAllScreenshots() {
+ self.getAsyncStatement("getAllScreenshots").executeAsync({
+ handleResult: function getAllScreenshots_handleResult(aResults) {
+ let row = null;
+ while ((row = aResults.getNextRow())) {
+ let addon_internal_id = row.getResultByName("addon_internal_id");
+ if (!(addon_internal_id in addons)) {
+ logger.warn("Found a screenshot not linked to an add-on in database");
+ continue;
+ }
+
+ let addon = addons[addon_internal_id];
+ if (!addon.screenshots)
+ addon.screenshots = [];
+ addon.screenshots.push(self._makeScreenshotFromAsyncRow(row));
+ }
+ },
+
+ handleError: self.asyncErrorLogger,
+
+ handleCompletion: function getAllScreenshots_handleCompletion(aReason) {
+ if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ logger.error("Error retrieving screenshots from database. Returning empty results");
+ aCallback({});
+ return;
+ }
+
+ getAllCompatOverrides();
+ }
+ });
+ }
+
+ function getAllCompatOverrides() {
+ self.getAsyncStatement("getAllCompatOverrides").executeAsync({
+ handleResult: function getAllCompatOverrides_handleResult(aResults) {
+ let row = null;
+ while ((row = aResults.getNextRow())) {
+ let addon_internal_id = row.getResultByName("addon_internal_id");
+ if (!(addon_internal_id in addons)) {
+ logger.warn("Found a compatibility override not linked to an add-on in database");
+ continue;
+ }
+
+ let addon = addons[addon_internal_id];
+ if (!addon.compatibilityOverrides)
+ addon.compatibilityOverrides = [];
+ addon.compatibilityOverrides.push(self._makeCompatOverrideFromAsyncRow(row));
+ }
+ },
+
+ handleError: self.asyncErrorLogger,
+
+ handleCompletion: function getAllCompatOverrides_handleCompletion(aReason) {
+ if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ logger.error("Error retrieving compatibility overrides from database. Returning empty results");
+ aCallback({});
+ return;
+ }
+
+ getAllIcons();
+ }
+ });
+ }
+
+ function getAllIcons() {
+ self.getAsyncStatement("getAllIcons").executeAsync({
+ handleResult: function getAllIcons_handleResult(aResults) {
+ let row = null;
+ while ((row = aResults.getNextRow())) {
+ let addon_internal_id = row.getResultByName("addon_internal_id");
+ if (!(addon_internal_id in addons)) {
+ logger.warn("Found an icon not linked to an add-on in database");
+ continue;
+ }
+
+ let addon = addons[addon_internal_id];
+ let { size, url } = self._makeIconFromAsyncRow(row);
+ addon.icons[size] = url;
+ if (size == 32)
+ addon.iconURL = url;
+ }
+ },
+
+ handleError: self.asyncErrorLogger,
+
+ handleCompletion: function getAllIcons_handleCompletion(aReason) {
+ if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ logger.error("Error retrieving icons from database. Returning empty results");
+ aCallback({});
+ return;
+ }
+
+ let returnedAddons = {};
+ for each (let addon in addons)
+ returnedAddons[addon.id] = addon;
+ aCallback(returnedAddons);
+ }
+ });
+ }
+
+ // Begin asynchronous process
+ getAllAddons();
+ },
+
+ // A cache of statements that are used and need to be finalized on shutdown
+ asyncStatementsCache: {},
+
+ /**
+ * Gets a cached async statement or creates a new statement if it doesn't
+ * already exist.
+ *
+ * @param aKey
+ * A unique key to reference the statement
+ * @return a mozIStorageAsyncStatement for the SQL corresponding to the
+ * unique key
+ */
+ getAsyncStatement: function AD_getAsyncStatement(aKey) {
+ if (aKey in this.asyncStatementsCache)
+ return this.asyncStatementsCache[aKey];
+
+ let sql = this.queries[aKey];
+ try {
+ return this.asyncStatementsCache[aKey] = this.connection.createAsyncStatement(sql);
+ } catch (e) {
+ logger.error("Error creating statement " + aKey + " (" + sql + ")");
+ throw Components.Exception("Error creating statement " + aKey + " (" + sql + "): " + e,
+ e.result);
+ }
+ },
+
+ // The queries used by the database
+ queries: {
+ getAllAddons: "SELECT internal_id, id, type, name, version, " +
+ "creator, creatorURL, description, fullDescription, " +
+ "developerComments, eula, homepageURL, supportURL, " +
+ "contributionURL, contributionAmount, averageRating, " +
+ "reviewCount, reviewURL, totalDownloads, weeklyDownloads, " +
+ "dailyUsers, sourceURI, repositoryStatus, size, updateDate " +
+ "FROM addon",
+
+ getAllDevelopers: "SELECT addon_internal_id, name, url FROM developer " +
+ "ORDER BY addon_internal_id, num",
+
+ getAllScreenshots: "SELECT addon_internal_id, url, width, height, " +
+ "thumbnailURL, thumbnailWidth, thumbnailHeight, caption " +
+ "FROM screenshot ORDER BY addon_internal_id, num",
+
+ getAllCompatOverrides: "SELECT addon_internal_id, type, minVersion, " +
+ "maxVersion, appID, appMinVersion, appMaxVersion " +
+ "FROM compatibility_override " +
+ "ORDER BY addon_internal_id, num",
+
+ getAllIcons: "SELECT addon_internal_id, size, url FROM icon " +
+ "ORDER BY addon_internal_id, size",
+ },
+
+ /**
+ * Make add-on structure from an asynchronous row.
+ *
+ * @param aRow
+ * The asynchronous row to use
+ * @return The created add-on
+ */
+ _makeAddonFromAsyncRow: function AD__makeAddonFromAsyncRow(aRow) {
+ // This is intentionally not an AddonSearchResult object in order
+ // to allow AddonDatabase._parseAddon to parse it, same as if it
+ // was read from the JSON database.
+
+ let addon = { icons: {} };
+
+ for (let prop of PROP_SINGLE) {
+ addon[prop] = aRow.getResultByName(prop)
+ };
+
+ return addon;
+ },
+
+ /**
+ * Make a developer from an asynchronous row
+ *
+ * @param aRow
+ * The asynchronous row to use
+ * @return The created developer
+ */
+ _makeDeveloperFromAsyncRow: function AD__makeDeveloperFromAsyncRow(aRow) {
+ let name = aRow.getResultByName("name");
+ let url = aRow.getResultByName("url")
+ return new AddonManagerPrivate.AddonAuthor(name, url);
+ },
+
+ /**
+ * Make a screenshot from an asynchronous row
+ *
+ * @param aRow
+ * The asynchronous row to use
+ * @return The created screenshot
+ */
+ _makeScreenshotFromAsyncRow: function AD__makeScreenshotFromAsyncRow(aRow) {
+ let url = aRow.getResultByName("url");
+ let width = aRow.getResultByName("width");
+ let height = aRow.getResultByName("height");
+ let thumbnailURL = aRow.getResultByName("thumbnailURL");
+ let thumbnailWidth = aRow.getResultByName("thumbnailWidth");
+ let thumbnailHeight = aRow.getResultByName("thumbnailHeight");
+ let caption = aRow.getResultByName("caption");
+ return new AddonManagerPrivate.AddonScreenshot(url, width, height, thumbnailURL,
+ thumbnailWidth, thumbnailHeight, caption);
+ },
+
+ /**
+ * Make a CompatibilityOverride from an asynchronous row
+ *
+ * @param aRow
+ * The asynchronous row to use
+ * @return The created CompatibilityOverride
+ */
+ _makeCompatOverrideFromAsyncRow: function AD_makeCompatOverrideFromAsyncRow(aRow) {
+ let type = aRow.getResultByName("type");
+ let minVersion = aRow.getResultByName("minVersion");
+ let maxVersion = aRow.getResultByName("maxVersion");
+ let appID = aRow.getResultByName("appID");
+ let appMinVersion = aRow.getResultByName("appMinVersion");
+ let appMaxVersion = aRow.getResultByName("appMaxVersion");
+ return new AddonManagerPrivate.AddonCompatibilityOverride(type,
+ minVersion,
+ maxVersion,
+ appID,
+ appMinVersion,
+ appMaxVersion);
+ },
+
+ /**
+ * Make an icon from an asynchronous row
+ *
+ * @param aRow
+ * The asynchronous row to use
+ * @return An object containing the size and URL of the icon
+ */
+ _makeIconFromAsyncRow: function AD_makeIconFromAsyncRow(aRow) {
+ let size = aRow.getResultByName("size");
+ let url = aRow.getResultByName("url");
+ return { size: size, url: url };
+ },
+
+ /**
+ * A helper function to log an SQL error.
+ *
+ * @param aError
+ * The storage error code associated with the error
+ * @param aErrorString
+ * An error message
+ */
+ logSQLError: function AD_logSQLError(aError, aErrorString) {
+ logger.error("SQL error " + aError + ": " + aErrorString);
+ },
+
+ /**
+ * A helper function to log any errors that occur during async statements.
+ *
+ * @param aError
+ * A mozIStorageError to log
+ */
+ asyncErrorLogger: function AD_asyncErrorLogger(aError) {
+ logger.error("Async SQL error " + aError.result + ": " + aError.message);
+ },
+
+ /**
+ * Synchronously creates the triggers in the database.
+ */
+ _createTriggers: function AD__createTriggers() {
+ this.connection.executeSimpleSQL("DROP TRIGGER IF EXISTS delete_addon");
+ this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " +
+ "ON addon BEGIN " +
+ "DELETE FROM developer WHERE addon_internal_id=old.internal_id; " +
+ "DELETE FROM screenshot WHERE addon_internal_id=old.internal_id; " +
+ "DELETE FROM compatibility_override WHERE addon_internal_id=old.internal_id; " +
+ "DELETE FROM icon WHERE addon_internal_id=old.internal_id; " +
+ "END");
+ },
+
+ /**
+ * Synchronously creates the indices in the database.
+ */
+ _createIndices: function AD__createIndices() {
+ this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS developer_idx " +
+ "ON developer (addon_internal_id)");
+ this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS screenshot_idx " +
+ "ON screenshot (addon_internal_id)");
+ this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS compatibility_override_idx " +
+ "ON compatibility_override (addon_internal_id)");
+ this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS icon_idx " +
+ "ON icon (addon_internal_id)");
+ }
+}
diff --git a/components/addons/src/AddonUpdateChecker.jsm b/components/addons/src/AddonUpdateChecker.jsm
new file mode 100644
index 000000000..f436ca4dd
--- /dev/null
+++ b/components/addons/src/AddonUpdateChecker.jsm
@@ -0,0 +1,967 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 AddonUpdateChecker is responsible for retrieving the update information
+ * from an add-on's remote update manifest.
+ */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "AddonUpdateChecker" ];
+
+const TIMEOUT = 60 * 1000;
+const PREFIX_NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
+const PREFIX_ITEM = "urn:mozilla:item:";
+const PREFIX_EXTENSION = "urn:mozilla:extension:";
+const PREFIX_THEME = "urn:mozilla:theme:";
+const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml";
+
+const TOOLKIT_ID = "toolkit@mozilla.org";
+const FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
+
+#ifdef MC_APP_ID
+#expand const ALT_APP_ID = "__MC_APP_ID__";
+#endif
+
+const PREF_UPDATE_REQUIREBUILTINCERTS = "extensions.update.requireBuiltInCerts";
+const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+ "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
+ "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
+ "resource://gre/modules/addons/AddonRepository.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
+ "resource://gre/modules/ServiceRequest.jsm");
+
+
+// Shared code for suppressing bad cert dialogs.
+XPCOMUtils.defineLazyGetter(this, "CertUtils", function() {
+ let certUtils = {};
+ Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
+ return certUtils;
+});
+
+var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
+ getService(Ci.nsIRDFService);
+
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.update-checker";
+
+// Create a new logger for use by the Addons Update Checker
+// (Requires AddonManager.jsm)
+var logger = Log.repository.getLogger(LOGGER_ID);
+
+/**
+ * A serialisation method for RDF data that produces an identical string
+ * for matching RDF graphs.
+ * The serialisation is not complete, only assertions stemming from a given
+ * resource are included, multiple references to the same resource are not
+ * permitted, and the RDF prolog and epilog are not included.
+ * RDF Blob and Date literals are not supported.
+ */
+function RDFSerializer() {
+ this.cUtils = Cc["@mozilla.org/rdf/container-utils;1"].
+ getService(Ci.nsIRDFContainerUtils);
+ this.resources = [];
+}
+
+RDFSerializer.prototype = {
+ INDENT: " ", // The indent used for pretty-printing
+ resources: null, // Array of the resources that have been found
+
+ /**
+ * Escapes characters from a string that should not appear in XML.
+ *
+ * @param aString
+ * The string to be escaped
+ * @return a string with all characters invalid in XML character data
+ * converted to entity references.
+ */
+ escapeEntities: function(aString) {
+ aString = aString.replace(/&/g, "&amp;");
+ aString = aString.replace(/</g, "&lt;");
+ aString = aString.replace(/>/g, "&gt;");
+ return aString.replace(/"/g, "&quot;");
+ },
+
+ /**
+ * Serializes all the elements of an RDF container.
+ *
+ * @param aDs
+ * The RDF datasource
+ * @param aContainer
+ * The RDF container to output the child elements of
+ * @param aIndent
+ * The current level of indent for pretty-printing
+ * @return a string containing the serialized elements.
+ */
+ serializeContainerItems: function(aDs, aContainer, aIndent) {
+ var result = "";
+ var items = aContainer.GetElements();
+ while (items.hasMoreElements()) {
+ var item = items.getNext().QueryInterface(Ci.nsIRDFResource);
+ result += aIndent + "<RDF:li>\n"
+ result += this.serializeResource(aDs, item, aIndent + this.INDENT);
+ result += aIndent + "</RDF:li>\n"
+ }
+ return result;
+ },
+
+ /**
+ * Serializes all em:* (see EM_NS) properties of an RDF resource except for
+ * the em:signature property. As this serialization is to be compared against
+ * the manifest signature it cannot contain the em:signature property itself.
+ *
+ * @param aDs
+ * The RDF datasource
+ * @param aResource
+ * The RDF resource that contains the properties to serialize
+ * @param aIndent
+ * The current level of indent for pretty-printing
+ * @return a string containing the serialized properties.
+ * @throws if the resource contains a property that cannot be serialized
+ */
+ serializeResourceProperties: function(aDs, aResource, aIndent) {
+ var result = "";
+ var items = [];
+ var arcs = aDs.ArcLabelsOut(aResource);
+ while (arcs.hasMoreElements()) {
+ var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource);
+ if (arc.ValueUTF8.substring(0, PREFIX_NS_EM.length) != PREFIX_NS_EM)
+ continue;
+ var prop = arc.ValueUTF8.substring(PREFIX_NS_EM.length);
+ if (prop == "signature")
+ continue;
+
+ var targets = aDs.GetTargets(aResource, arc, true);
+ while (targets.hasMoreElements()) {
+ var target = targets.getNext();
+ if (target instanceof Ci.nsIRDFResource) {
+ var item = aIndent + "<em:" + prop + ">\n";
+ item += this.serializeResource(aDs, target, aIndent + this.INDENT);
+ item += aIndent + "</em:" + prop + ">\n";
+ items.push(item);
+ }
+ else if (target instanceof Ci.nsIRDFLiteral) {
+ items.push(aIndent + "<em:" + prop + ">" +
+ this.escapeEntities(target.Value) + "</em:" + prop + ">\n");
+ }
+ else if (target instanceof Ci.nsIRDFInt) {
+ items.push(aIndent + "<em:" + prop + " NC:parseType=\"Integer\">" +
+ target.Value + "</em:" + prop + ">\n");
+ }
+ else {
+ throw Components.Exception("Cannot serialize unknown literal type");
+ }
+ }
+ }
+ items.sort();
+ result += items.join("");
+ return result;
+ },
+
+ /**
+ * Recursively serializes an RDF resource and all resources it links to.
+ * This will only output EM_NS properties and will ignore any em:signature
+ * property.
+ *
+ * @param aDs
+ * The RDF datasource
+ * @param aResource
+ * The RDF resource to serialize
+ * @param aIndent
+ * The current level of indent for pretty-printing. If undefined no
+ * indent will be added
+ * @return a string containing the serialized resource.
+ * @throws if the RDF data contains multiple references to the same resource.
+ */
+ serializeResource: function(aDs, aResource, aIndent) {
+ if (this.resources.indexOf(aResource) != -1 ) {
+ // We cannot output multiple references to the same resource.
+ throw Components.Exception("Cannot serialize multiple references to " + aResource.Value);
+ }
+ if (aIndent === undefined)
+ aIndent = "";
+
+ this.resources.push(aResource);
+ var container = null;
+ var type = "Description";
+ if (this.cUtils.IsSeq(aDs, aResource)) {
+ type = "Seq";
+ container = this.cUtils.MakeSeq(aDs, aResource);
+ }
+ else if (this.cUtils.IsAlt(aDs, aResource)) {
+ type = "Alt";
+ container = this.cUtils.MakeAlt(aDs, aResource);
+ }
+ else if (this.cUtils.IsBag(aDs, aResource)) {
+ type = "Bag";
+ container = this.cUtils.MakeBag(aDs, aResource);
+ }
+
+ var result = aIndent + "<RDF:" + type;
+ if (!gRDF.IsAnonymousResource(aResource))
+ result += " about=\"" + this.escapeEntities(aResource.ValueUTF8) + "\"";
+ result += ">\n";
+
+ if (container)
+ result += this.serializeContainerItems(aDs, container, aIndent + this.INDENT);
+
+ result += this.serializeResourceProperties(aDs, aResource, aIndent + this.INDENT);
+
+ result += aIndent + "</RDF:" + type + ">\n";
+ return result;
+ }
+}
+
+/**
+ * Sanitizes the update URL in an update item, as returned by
+ * parseRDFManifest and parseJSONManifest. Ensures that:
+ *
+ * - The URL is secure, or secured by a strong enough hash.
+ * - The security principal of the update manifest has permission to
+ * load the URL.
+ *
+ * @param aUpdate
+ * The update item to sanitize.
+ * @param aRequest
+ * The XMLHttpRequest used to load the manifest.
+ * @param aHashPattern
+ * The regular expression used to validate the update hash.
+ * @param aHashString
+ * The human-readable string specifying which hash functions
+ * are accepted.
+ */
+function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) {
+ if (aUpdate.updateURL) {
+ let scriptSecurity = Services.scriptSecurityManager;
+ let principal = scriptSecurity.getChannelURIPrincipal(aRequest.channel);
+ try {
+ // This logs an error on failure, so no need to log it a second time
+ scriptSecurity.checkLoadURIStrWithPrincipal(principal, aUpdate.updateURL,
+ scriptSecurity.DISALLOW_SCRIPT);
+ } catch (e) {
+ delete aUpdate.updateURL;
+ return;
+ }
+
+ if (AddonManager.checkUpdateSecurity &&
+ !aUpdate.updateURL.startsWith("https:") &&
+ !aHashPattern.test(aUpdate.updateHash)) {
+ logger.warn(`Update link ${aUpdate.updateURL} is not secure and is not verified ` +
+ `by a strong enough hash (needs to be ${aHashString}).`);
+ delete aUpdate.updateURL;
+ delete aUpdate.updateHash;
+ }
+ }
+}
+
+/**
+ * Parses an RDF style update manifest into an array of update objects.
+ *
+ * @param aId
+ * The ID of the add-on being checked for updates
+ * @param aUpdateKey
+ * An optional update key for the add-on
+ * @param aRequest
+ * The XMLHttpRequest that has retrieved the update manifest
+ * @param aManifestData
+ * The pre-parsed manifest, as a bare XML DOM document
+ * @return an array of update objects
+ * @throws if the update manifest is invalid in any way
+ */
+function parseRDFManifest(aId, aUpdateKey, aRequest, aManifestData) {
+ if (aManifestData.documentElement.namespaceURI != PREFIX_NS_RDF) {
+ throw Components.Exception("update.rdf: Update manifest had an unrecognised namespace: " +
+ aManifestData.documentElement.namespaceURI);
+ }
+
+ function EM_R(aProp) {
+ return gRDF.GetResource(PREFIX_NS_EM + aProp);
+ }
+
+ function getValue(aLiteral) {
+ if (aLiteral instanceof Ci.nsIRDFLiteral)
+ return aLiteral.Value;
+ if (aLiteral instanceof Ci.nsIRDFResource)
+ return aLiteral.Value;
+ if (aLiteral instanceof Ci.nsIRDFInt)
+ return aLiteral.Value;
+ return null;
+ }
+
+ function getProperty(aDs, aSource, aProperty) {
+ return getValue(aDs.GetTarget(aSource, EM_R(aProperty), true));
+ }
+
+ function getBooleanProperty(aDs, aSource, aProperty) {
+ let propValue = aDs.GetTarget(aSource, EM_R(aProperty), true);
+ if (!propValue)
+ return undefined;
+ return getValue(propValue) == "true";
+ }
+
+ function getRequiredProperty(aDs, aSource, aProperty) {
+ let value = getProperty(aDs, aSource, aProperty);
+ if (!value)
+ throw Components.Exception("update.rdf: Update manifest is missing a required " + aProperty + " property.");
+ return value;
+ }
+
+ let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
+ createInstance(Ci.nsIRDFXMLParser);
+ let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
+ createInstance(Ci.nsIRDFDataSource);
+ rdfParser.parseString(ds, aRequest.channel.URI, aRequest.responseText);
+
+ // Differentiating between add-on types is deprecated
+ let extensionRes = gRDF.GetResource(PREFIX_EXTENSION + aId);
+ let themeRes = gRDF.GetResource(PREFIX_THEME + aId);
+ let itemRes = gRDF.GetResource(PREFIX_ITEM + aId);
+ let addonRes;
+ if (ds.ArcLabelsOut(extensionRes).hasMoreElements())
+ addonRes = extensionRes;
+ else if (ds.ArcLabelsOut(themeRes).hasMoreElements())
+ addonRes = themeRes;
+ else
+ addonRes = itemRes;
+
+ // If we have an update key then the update manifest must be signed
+ if (aUpdateKey) {
+ let signature = getProperty(ds, addonRes, "signature");
+ if (!signature)
+ throw Components.Exception("update.rdf: Update manifest for " + aId + " does not contain a required signature");
+ let serializer = new RDFSerializer();
+ let updateString = null;
+
+ try {
+ updateString = serializer.serializeResource(ds, addonRes);
+ }
+ catch (e) {
+ throw Components.Exception("update.rdf: Failed to generate signed string for " + aId + ". Serializer threw " + e,
+ e.result);
+ }
+
+ let result = false;
+
+ try {
+ let verifier = Cc["@mozilla.org/security/datasignatureverifier;1"].
+ getService(Ci.nsIDataSignatureVerifier);
+ result = verifier.verifyData(updateString, signature, aUpdateKey);
+ }
+ catch (e) {
+ throw Components.Exception("update.rdf: The signature or updateKey for " + aId + " is malformed." +
+ "Verifier threw " + e, e.result);
+ }
+
+ if (!result)
+ throw Components.Exception("The signature for " + aId + " was not created by the add-on's updateKey");
+ }
+
+ let updates = ds.GetTarget(addonRes, EM_R("updates"), true);
+
+ // A missing updates property doesn't count as a failure, just as no avialable
+ // update information
+ if (!updates) {
+ logger.warn("update.rdf: Update manifest for " + aId + " did not contain an updates property");
+ return [];
+ }
+
+ if (!(updates instanceof Ci.nsIRDFResource))
+ throw Components.Exception("Missing updates property for " + addonRes.Value);
+
+ let cu = Cc["@mozilla.org/rdf/container-utils;1"].
+ getService(Ci.nsIRDFContainerUtils);
+ if (!cu.IsContainer(ds, updates))
+ throw Components.Exception("update.rdf: Updates property was not an RDF container");
+
+ let results = [];
+ let ctr = Cc["@mozilla.org/rdf/container;1"].
+ createInstance(Ci.nsIRDFContainer);
+ ctr.Init(ds, updates);
+ let items = ctr.GetElements();
+ while (items.hasMoreElements()) {
+ let item = items.getNext().QueryInterface(Ci.nsIRDFResource);
+ let version = getProperty(ds, item, "version");
+ if (!version) {
+ logger.warn("update.rdf: Update manifest is missing a required version property.");
+ continue;
+ }
+
+ logger.debug("update.rdf: Found an update entry for " + aId + " version " + version);
+
+ let targetApps = ds.GetTargets(item, EM_R("targetApplication"), true);
+ while (targetApps.hasMoreElements()) {
+ let targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource);
+
+ let appEntry = {};
+ try {
+ appEntry.id = getRequiredProperty(ds, targetApp, "id");
+ appEntry.minVersion = getRequiredProperty(ds, targetApp, "minVersion");
+ appEntry.maxVersion = getRequiredProperty(ds, targetApp, "maxVersion");
+ }
+ catch (e) {
+ logger.warn(e);
+ continue;
+ }
+
+ let result = {
+ id: aId,
+ version: version,
+ multiprocessCompatible: getBooleanProperty(ds, item, "multiprocessCompatible"),
+ updateURL: getProperty(ds, targetApp, "updateLink"),
+ updateHash: getProperty(ds, targetApp, "updateHash"),
+ updateInfoURL: getProperty(ds, targetApp, "updateInfoURL"),
+ strictCompatibility: !!getBooleanProperty(ds, targetApp, "strictCompatibility"),
+ targetApplications: [appEntry]
+ };
+
+ // The JSON update protocol requires an SHA-2 hash. RDF still
+ // supports SHA-1, for compatibility reasons.
+ sanitizeUpdateURL(result, aRequest, /^sha/, "sha1 or stronger");
+
+ results.push(result);
+ }
+ }
+ return results;
+}
+
+/**
+ * Parses an JSON update manifest into an array of update objects.
+ *
+ * @param aId
+ * The ID of the add-on being checked for updates
+ * @param aUpdateKey
+ * An optional update key for the add-on
+ * @param aRequest
+ * The XMLHttpRequest that has retrieved the update manifest
+ * @param aManifestData
+ * The pre-parsed manifest, as a JSON object tree
+ * @return an array of update objects
+ * @throws if the update manifest is invalid in any way
+ */
+function parseJSONManifest(aId, aUpdateKey, aRequest, aManifestData) {
+ if (aUpdateKey)
+ throw Components.Exception("update.json: Update keys are not supported for JSON update manifests");
+
+ let TYPE_CHECK = {
+ "array": val => Array.isArray(val),
+ "object": val => val && typeof val == "object" && !Array.isArray(val),
+ };
+
+ function getProperty(aObj, aProperty, aType, aDefault = undefined) {
+ if (!(aProperty in aObj))
+ return aDefault;
+
+ let value = aObj[aProperty];
+
+ let matchesType = aType in TYPE_CHECK ? TYPE_CHECK[aType](value) : typeof value == aType;
+ if (!matchesType)
+ throw Components.Exception(`update.json: Update manifest property '${aProperty}' has incorrect type (expected ${aType})`);
+
+ return value;
+ }
+
+ function getRequiredProperty(aObj, aProperty, aType) {
+ let value = getProperty(aObj, aProperty, aType);
+ if (value === undefined)
+ throw Components.Exception(`update.json: Update manifest is missing a required ${aProperty} property.`);
+ return value;
+ }
+
+ let manifest = aManifestData;
+
+ if (!TYPE_CHECK["object"](manifest))
+ throw Components.Exception("update.json: Root element of update manifest must be a JSON object literal");
+
+ // The set of add-ons this manifest has updates for
+ let addons = getRequiredProperty(manifest, "addons", "object");
+
+ // The entry for this particular add-on
+ let addon = getProperty(addons, aId, "object");
+
+ // A missing entry doesn't count as a failure, just as no avialable update
+ // information
+ if (!addon) {
+ logger.warn("update.json: Update manifest did not contain an entry for " + aId);
+ return [];
+ }
+
+ let appID = Services.appinfo.ID;
+ let platformVersion = Services.appinfo.greVersion;
+
+ // The list of available updates
+ let updates = getProperty(addon, "updates", "array", []);
+
+ let results = [];
+
+ for (let update of updates) {
+ let version = getRequiredProperty(update, "version", "string");
+ logger.debug(`update.json: Found an update entry for ${aId} version ${version}`);
+
+ let applications = getRequiredProperty(update, "applications", "object");
+
+ let app;
+ let appEntry;
+
+ if (appID in applications) {
+ logger.debug("update.json: Native targetApplication");
+ app = getProperty(applications, appID, "object");
+
+ appEntry = {
+ id: appID,
+ minVersion: getRequiredProperty(app, "min_version", "string"),
+ maxVersion: getRequiredProperty(app, "max_version", "string"),
+ }
+ }
+ else if (TOOLKIT_ID in applications) {
+ logger.debug("update.json: Toolkit targetApplication");
+ app = getProperty(applications, TOOLKIT_ID, "object");
+
+ appEntry = {
+ id: TOOLKIT_ID,
+ minVersion: getRequiredProperty(app, "min_version", "string"),
+ maxVersion: getRequiredProperty(app, "max_version", "string"),
+ }
+ }
+ else if ("gecko" in applications) {
+ logger.debug("update.json: Mozilla Compatiblity Mode");
+ app = getProperty(applications, "gecko", "object");
+
+ appEntry = {
+#ifdef MOZ_PHOENIX
+ id: FIREFOX_ID,
+ minVersion: getProperty(app, "strict_min_version", "string",
+ Services.prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION)),
+#else
+ id: TOOLKIT_ID,
+ minVersion: platformVersion,
+#endif
+ maxVersion: '*',
+ };
+ }
+ else {
+ continue;
+ }
+
+ let result = {
+ id: aId,
+ version: version,
+ multiprocessCompatible: getProperty(update, "multiprocess_compatible", "boolean", false),
+ updateURL: getProperty(update, "update_link", "string"),
+ updateHash: getProperty(update, "update_hash", "string"),
+ updateInfoURL: getProperty(update, "update_info_url", "string"),
+ strictCompatibility: getProperty(app, "strict_compatibility", "boolean", false),
+ targetApplications: [appEntry],
+ };
+
+ // The JSON update protocol requires an SHA-2 hash. RDF still
+ // supports SHA-1, for compatibility reasons.
+ sanitizeUpdateURL(result, aRequest, /^sha(256|512):/, "sha256 or sha512");
+
+ results.push(result);
+ }
+ return results;
+}
+
+/**
+ * Starts downloading an update manifest and then passes it to an appropriate
+ * parser to convert to an array of update objects
+ *
+ * @param aId
+ * The ID of the add-on being checked for updates
+ * @param aUpdateKey
+ * An optional update key for the add-on
+ * @param aUrl
+ * The URL of the update manifest
+ * @param aObserver
+ * An observer to pass results to
+ */
+function UpdateParser(aId, aUpdateKey, aUrl, aObserver) {
+ this.id = aId;
+ this.updateKey = aUpdateKey;
+ this.observer = aObserver;
+ this.url = aUrl;
+
+ let requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS, true);
+
+ logger.debug("Requesting " + aUrl);
+ try {
+ this.request = new ServiceRequest();
+ this.request.open("GET", this.url, true);
+ this.request.channel.notificationCallbacks = new CertUtils.BadCertHandler(!requireBuiltIn);
+ this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ // Prevent the request from writing to cache.
+ this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ this.request.overrideMimeType("text/plain");
+ this.request.setRequestHeader("Moz-XPI-Update", "1", true);
+ this.request.timeout = TIMEOUT;
+ this.request.addEventListener("load", () => this.onLoad(), false);
+ this.request.addEventListener("error", () => this.onError(), false);
+ this.request.addEventListener("timeout", () => this.onTimeout(), false);
+ this.request.send(null);
+ }
+ catch (e) {
+ logger.error("Failed to request update manifest", e);
+ }
+}
+
+UpdateParser.prototype = {
+ id: null,
+ updateKey: null,
+ observer: null,
+ request: null,
+ url: null,
+
+ /**
+ * Called when the manifest has been successfully loaded.
+ */
+ onLoad: function() {
+ let request = this.request;
+ this.request = null;
+ this._doneAt = new Error("place holder");
+
+ let requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS, true);
+
+ try {
+ CertUtils.checkCert(request.channel, !requireBuiltIn);
+ }
+ catch (e) {
+ logger.warn("Request failed: " + this.url + " - " + e);
+ this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
+ return;
+ }
+
+ if (!Components.isSuccessCode(request.status)) {
+ logger.warn("Request failed: " + this.url + " - " + request.status);
+ this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
+ return;
+ }
+
+ let channel = request.channel;
+ if (channel instanceof Ci.nsIHttpChannel && !channel.requestSucceeded) {
+ logger.warn("Request failed: " + this.url + " - " + channel.responseStatus +
+ ": " + channel.responseStatusText);
+ this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
+ return;
+ }
+
+ // Detect the manifest type by first attempting to parse it as
+ // JSON, and falling back to parsing it as XML if that fails.
+ let parser;
+ try {
+ try {
+ let json = JSON.parse(request.responseText);
+
+ parser = () => parseJSONManifest(this.id, this.updateKey, request, json);
+ } catch (e) {
+ if (!(e instanceof SyntaxError))
+ throw e;
+ let domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
+ let xml = domParser.parseFromString(request.responseText, "text/xml");
+
+ if (xml.documentElement.namespaceURI == XMLURI_PARSE_ERROR)
+ throw new Error("Update manifest was not valid XML or JSON");
+
+ parser = () => parseRDFManifest(this.id, this.updateKey, request, xml);
+ }
+ } catch (e) {
+ logger.warn("onUpdateCheckComplete failed to determine manifest type");
+ this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT);
+ return;
+ }
+
+ let results;
+ try {
+ results = parser();
+ }
+ catch (e) {
+ logger.warn("onUpdateCheckComplete failed to parse update manifest", e);
+ this.notifyError(AddonUpdateChecker.ERROR_PARSE_ERROR);
+ return;
+ }
+
+ if ("onUpdateCheckComplete" in this.observer) {
+ try {
+ this.observer.onUpdateCheckComplete(results);
+ }
+ catch (e) {
+ logger.warn("onUpdateCheckComplete notification failed", e);
+ }
+ }
+ else {
+ logger.warn("onUpdateCheckComplete may not properly cancel", new Error("stack marker"));
+ }
+ },
+
+ /**
+ * Called when the request times out
+ */
+ onTimeout: function() {
+ this.request = null;
+ this._doneAt = new Error("Timed out");
+ logger.warn("Request for " + this.url + " timed out");
+ this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT);
+ },
+
+ /**
+ * Called when the manifest failed to load.
+ */
+ onError: function() {
+ if (!Components.isSuccessCode(this.request.status)) {
+ logger.warn("Request failed: " + this.url + " - " + this.request.status);
+ }
+ else if (this.request.channel instanceof Ci.nsIHttpChannel) {
+ try {
+ if (this.request.channel.requestSucceeded) {
+ logger.warn("Request failed: " + this.url + " - " +
+ this.request.channel.responseStatus + ": " +
+ this.request.channel.responseStatusText);
+ }
+ }
+ catch (e) {
+ logger.warn("HTTP Request failed for an unknown reason");
+ }
+ }
+ else {
+ logger.warn("Request failed for an unknown reason");
+ }
+
+ this.request = null;
+ this._doneAt = new Error("UP_onError");
+
+ this.notifyError(AddonUpdateChecker.ERROR_DOWNLOAD_ERROR);
+ },
+
+ /**
+ * Helper method to notify the observer that an error occured.
+ */
+ notifyError: function(aStatus) {
+ if ("onUpdateCheckError" in this.observer) {
+ try {
+ this.observer.onUpdateCheckError(aStatus);
+ }
+ catch (e) {
+ logger.warn("onUpdateCheckError notification failed", e);
+ }
+ }
+ },
+
+ /**
+ * Called to cancel an in-progress update check.
+ */
+ cancel: function() {
+ if (!this.request) {
+ logger.error("Trying to cancel already-complete request", this._doneAt);
+ return;
+ }
+ this.request.abort();
+ this.request = null;
+ this._doneAt = new Error("UP_cancel");
+ this.notifyError(AddonUpdateChecker.ERROR_CANCELLED);
+ }
+};
+
+/**
+ * Tests if an update matches a version of the application or platform
+ *
+ * @param aUpdate
+ * The available update
+ * @param aAppVersion
+ * The application version to use
+ * @param aPlatformVersion
+ * The platform version to use
+ * @param aIgnoreMaxVersion
+ * Ignore maxVersion when testing if an update matches. Optional.
+ * @param aIgnoreStrictCompat
+ * Ignore strictCompatibility when testing if an update matches. Optional.
+ * @param aCompatOverrides
+ * AddonCompatibilityOverride objects to match against. Optional.
+ * @return true if the update is compatible with the application/platform
+ */
+function matchesVersions(aUpdate, aAppVersion, aPlatformVersion,
+ aIgnoreMaxVersion, aIgnoreStrictCompat,
+ aCompatOverrides) {
+ if (aCompatOverrides) {
+ let override = AddonRepository.findMatchingCompatOverride(aUpdate.version,
+ aCompatOverrides,
+ aAppVersion,
+ aPlatformVersion);
+ if (override && override.type == "incompatible")
+ return false;
+ }
+
+ if (aUpdate.strictCompatibility && !aIgnoreStrictCompat)
+ aIgnoreMaxVersion = false;
+
+ let result = false;
+ for (let app of aUpdate.targetApplications) {
+#ifdef MC_APP_ID
+ if (app.id == ALT_APP_ID || app.id == Services.appinfo.ID) {
+#else
+ if (app.id == Services.appinfo.ID) {
+#endif
+ return (Services.vc.compare(aAppVersion, app.minVersion) >= 0) &&
+ (aIgnoreMaxVersion || (Services.vc.compare(aAppVersion, app.maxVersion) <= 0));
+ }
+ if (app.id == TOOLKIT_ID) {
+ result = (Services.vc.compare(aPlatformVersion, app.minVersion) >= 0) &&
+ (aIgnoreMaxVersion || (Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0));
+ }
+ }
+ return result;
+}
+
+this.AddonUpdateChecker = {
+ // These must be kept in sync with AddonManager
+ // The update check timed out
+ ERROR_TIMEOUT: -1,
+ // There was an error while downloading the update information.
+ ERROR_DOWNLOAD_ERROR: -2,
+ // The update information was malformed in some way.
+ ERROR_PARSE_ERROR: -3,
+ // The update information was not in any known format.
+ ERROR_UNKNOWN_FORMAT: -4,
+ // The update information was not correctly signed or there was an SSL error.
+ ERROR_SECURITY_ERROR: -5,
+ // The update was cancelled
+ ERROR_CANCELLED: -6,
+
+ /**
+ * Retrieves the best matching compatibility update for the application from
+ * a list of available update objects.
+ *
+ * @param aUpdates
+ * An array of update objects
+ * @param aVersion
+ * The version of the add-on to get new compatibility information for
+ * @param aIgnoreCompatibility
+ * An optional parameter to get the first compatibility update that
+ * is compatible with any version of the application or toolkit
+ * @param aAppVersion
+ * The version of the application or null to use the current version
+ * @param aPlatformVersion
+ * The version of the platform or null to use the current version
+ * @param aIgnoreMaxVersion
+ * Ignore maxVersion when testing if an update matches. Optional.
+ * @param aIgnoreStrictCompat
+ * Ignore strictCompatibility when testing if an update matches. Optional.
+ * @return an update object if one matches or null if not
+ */
+ getCompatibilityUpdate: function(aUpdates, aVersion, aIgnoreCompatibility,
+ aAppVersion, aPlatformVersion,
+ aIgnoreMaxVersion, aIgnoreStrictCompat) {
+ if (!aAppVersion)
+ aAppVersion = Services.appinfo.version;
+ if (!aPlatformVersion)
+ aPlatformVersion = Services.appinfo.greVersion;
+
+ for (let update of aUpdates) {
+ if (Services.vc.compare(update.version, aVersion) == 0) {
+ if (aIgnoreCompatibility) {
+ for (let targetApp of update.targetApplications) {
+ let id = targetApp.id;
+#ifdef MC_APP_ID
+ if (id == ALT_APP_ID || id == Services.appinfo.ID || id == TOOLKIT_ID)
+#else
+ if (id == Services.appinfo.ID || id == TOOLKIT_ID)
+#endif
+ return update;
+ }
+ }
+ else if (matchesVersions(update, aAppVersion, aPlatformVersion,
+ aIgnoreMaxVersion, aIgnoreStrictCompat)) {
+ return update;
+ }
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Returns the newest available update from a list of update objects.
+ *
+ * @param aUpdates
+ * An array of update objects
+ * @param aAppVersion
+ * The version of the application or null to use the current version
+ * @param aPlatformVersion
+ * The version of the platform or null to use the current version
+ * @param aIgnoreMaxVersion
+ * When determining compatible updates, ignore maxVersion. Optional.
+ * @param aIgnoreStrictCompat
+ * When determining compatible updates, ignore strictCompatibility. Optional.
+ * @param aCompatOverrides
+ * Array of AddonCompatibilityOverride to take into account. Optional.
+ * @return an update object if one matches or null if not
+ */
+ getNewestCompatibleUpdate: function(aUpdates, aAppVersion, aPlatformVersion,
+ aIgnoreMaxVersion, aIgnoreStrictCompat,
+ aCompatOverrides) {
+ if (!aAppVersion)
+ aAppVersion = Services.appinfo.version;
+ if (!aPlatformVersion)
+ aPlatformVersion = Services.appinfo.greVersion;
+
+ let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
+ getService(Ci.nsIBlocklistService);
+
+ let newest = null;
+ for (let update of aUpdates) {
+ if (!update.updateURL)
+ continue;
+ let state = blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion);
+ if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
+ continue;
+ if ((newest == null || (Services.vc.compare(newest.version, update.version) < 0)) &&
+ matchesVersions(update, aAppVersion, aPlatformVersion,
+ aIgnoreMaxVersion, aIgnoreStrictCompat,
+ aCompatOverrides)) {
+ newest = update;
+ }
+ }
+ return newest;
+ },
+
+ /**
+ * Starts an update check.
+ *
+ * @param aId
+ * The ID of the add-on being checked for updates
+ * @param aUpdateKey
+ * An optional update key for the add-on
+ * @param aUrl
+ * The URL of the add-on's update manifest
+ * @param aObserver
+ * An observer to notify of results
+ * @return UpdateParser so that the caller can use UpdateParser.cancel() to shut
+ * down in-progress update requests
+ */
+ checkForUpdates: function(aId, aUpdateKey, aUrl, aObserver) {
+ // Define an array of internally used IDs to NOT send to AUS.
+ let internalIDS = [
+ '{972ce4c6-7e08-4474-a285-3208198ce6fd}', // Global Default Theme
+ 'modern@themes.mozilla.org', // Modern Theme for Borealis/Suite-based Applications
+ 'xplatform@interlink.projects.binaryoutcast.com', // Pref-set default theme for Interlink
+ '{e2fda1a4-762b-4020-b5ad-a41df1933103}', // Lightning/Calendar Extension
+ '{a62ef8ec-5fdc-40c2-873c-223b8a6925cc}' // Provider for Google Calendar (gdata) Extension
+ ];
+
+ // If the ID is not in the array then go ahead and query AUS
+ if (internalIDS.indexOf(aId) == -1) {
+ return new UpdateParser(aId, aUpdateKey, aUrl, aObserver);
+ }
+ }
+};
diff --git a/components/addons/src/ChromeManifestParser.jsm b/components/addons/src/ChromeManifestParser.jsm
new file mode 100644
index 000000000..63f1db785
--- /dev/null
+++ b/components/addons/src/ChromeManifestParser.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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["ChromeManifestParser"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const MSG_JAR_FLUSH = "AddonJarFlush";
+
+
+/**
+ * Sends local and remote notifications to flush a JAR file cache entry
+ *
+ * @param aJarFile
+ * The ZIP/XPI/JAR file as a nsIFile
+ */
+function flushJarCache(aJarFile) {
+ Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null);
+ Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster)
+ .broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
+}
+
+
+/**
+ * Parses chrome manifest files.
+ */
+this.ChromeManifestParser = {
+
+ /**
+ * Reads and parses a chrome manifest file located at a specified URI, and all
+ * secondary manifests it references.
+ *
+ * @param aURI
+ * A nsIURI pointing to a chrome manifest.
+ * Typically a file: or jar: URI.
+ * @return Array of objects describing each manifest instruction, in the form:
+ * { type: instruction-type, baseURI: string-uri, args: [arguments] }
+ **/
+ parseSync: function(aURI) {
+ function parseLine(aLine) {
+ let line = aLine.trim();
+ if (line.length == 0 || line.charAt(0) == '#')
+ return;
+ let tokens = line.split(/\s+/);
+ let type = tokens.shift();
+ if (type == "manifest") {
+ let uri = NetUtil.newURI(tokens.shift(), null, aURI);
+ data = data.concat(this.parseSync(uri));
+ } else {
+ data.push({type: type, baseURI: baseURI, args: tokens});
+ }
+ }
+
+ let contents = "";
+ try {
+ if (aURI.scheme == "jar")
+ contents = this._readFromJar(aURI);
+ else
+ contents = this._readFromFile(aURI);
+ } catch (e) {
+ // Silently fail.
+ }
+
+ if (!contents)
+ return [];
+
+ let baseURI = NetUtil.newURI(".", null, aURI).spec;
+
+ let data = [];
+ let lines = contents.split("\n");
+ lines.forEach(parseLine.bind(this));
+ return data;
+ },
+
+ _readFromJar: function(aURI) {
+ let data = "";
+ let entries = [];
+ let readers = [];
+
+ try {
+ // Deconstrict URI, which can be nested jar: URIs.
+ let uri = aURI.clone();
+ while (uri instanceof Ci.nsIJARURI) {
+ entries.push(uri.JAREntry);
+ uri = uri.JARFile;
+ }
+
+ // Open the base jar.
+ let reader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ reader.open(uri.QueryInterface(Ci.nsIFileURL).file);
+ readers.push(reader);
+
+ // Open the nested jars.
+ for (let i = entries.length - 1; i > 0; i--) {
+ let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ innerReader.openInner(reader, entries[i]);
+ readers.push(innerReader);
+ reader = innerReader;
+ }
+
+ // First entry is the actual file we want to read.
+ let zis = reader.getInputStream(entries[0]);
+ data = NetUtil.readInputStreamToString(zis, zis.available());
+ }
+ finally {
+ // Close readers in reverse order.
+ for (let i = readers.length - 1; i >= 0; i--) {
+ readers[i].close();
+ flushJarCache(readers[i].file);
+ }
+ }
+
+ return data;
+ },
+
+ _readFromFile: function(aURI) {
+ let file = aURI.QueryInterface(Ci.nsIFileURL).file;
+ if (!file.exists() || !file.isFile())
+ return "";
+
+ let data = "";
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ try {
+ fis.init(file, -1, -1, false);
+ data = NetUtil.readInputStreamToString(fis, fis.available());
+ } finally {
+ fis.close();
+ }
+ return data;
+ },
+
+ /**
+ * Detects if there were any instructions of a specified type in a given
+ * chrome manifest.
+ *
+ * @param aManifest
+ * Manifest data, as returned by ChromeManifestParser.parseSync().
+ * @param aType
+ * Instruction type to filter by.
+ * @return True if any matching instructions were found in the manifest.
+ */
+ hasType: function(aManifest, aType) {
+ return aManifest.some(entry => entry.type == aType);
+ }
+};
diff --git a/components/addons/src/Content.js b/components/addons/src/Content.js
new file mode 100644
index 000000000..9f366ba32
--- /dev/null
+++ b/components/addons/src/Content.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* globals addMessageListener*/
+
+"use strict";
+
+(function() {
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+var {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+var nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
+ "initWithPath");
+
+const MSG_JAR_FLUSH = "AddonJarFlush";
+const MSG_MESSAGE_MANAGER_CACHES_FLUSH = "AddonMessageManagerCachesFlush";
+
+
+try {
+ if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
+ // Propagate JAR cache flush notifications across process boundaries.
+ addMessageListener(MSG_JAR_FLUSH, function(message) {
+ let file = new nsIFile(message.data);
+ Services.obs.notifyObservers(file, "flush-cache-entry", null);
+ });
+ // Propagate message manager caches flush notifications across processes.
+ addMessageListener(MSG_MESSAGE_MANAGER_CACHES_FLUSH, function() {
+ Services.obs.notifyObservers(null, "message-manager-flush-caches", null);
+ });
+ }
+} catch (e) {
+ Cu.reportError(e);
+}
+
+})();
diff --git a/components/addons/src/DeferredSave.jsm b/components/addons/src/DeferredSave.jsm
new file mode 100644
index 000000000..f1537fe4b
--- /dev/null
+++ b/components/addons/src/DeferredSave.jsm
@@ -0,0 +1,270 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/osfile.jsm");
+/* globals OS*/
+Cu.import("resource://gre/modules/Promise.jsm");
+
+// Make it possible to mock out timers for testing
+var MakeTimer = () => Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+this.EXPORTED_SYMBOLS = ["DeferredSave"];
+
+// If delay parameter is not provided, default is 50 milliseconds.
+const DEFAULT_SAVE_DELAY_MS = 50;
+
+Cu.import("resource://gre/modules/Log.jsm");
+// Configure a logger at the parent 'DeferredSave' level to format
+// messages for all the modules under DeferredSave.*
+const DEFERREDSAVE_PARENT_LOGGER_ID = "DeferredSave";
+var parentLogger = Log.repository.getLogger(DEFERREDSAVE_PARENT_LOGGER_ID);
+parentLogger.level = Log.Level.Warn;
+var formatter = new Log.BasicFormatter();
+// Set parent logger (and its children) to append to
+// the Javascript section of the Browser Console
+parentLogger.addAppender(new Log.ConsoleAppender(formatter));
+// Set parent logger (and its children) to
+// also append to standard out
+parentLogger.addAppender(new Log.DumpAppender(formatter));
+
+// Provide the ability to enable/disable logging
+// messages at runtime.
+// If the "extensions.logging.enabled" preference is
+// missing or 'false', messages at the WARNING and higher
+// severity should be logged to the JS console and standard error.
+// If "extensions.logging.enabled" is set to 'true', messages
+// at DEBUG and higher should go to JS console and standard error.
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+
+/**
+* Preference listener which listens for a change in the
+* "extensions.logging.enabled" preference and changes the logging level of the
+* parent 'addons' level logger accordingly.
+*/
+var PrefObserver = {
+ init: function() {
+ Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "xpcom-shutdown") {
+ Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ }
+ else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
+ let debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED, false);
+ if (debugLogEnabled) {
+ parentLogger.level = Log.Level.Debug;
+ }
+ else {
+ parentLogger.level = Log.Level.Warn;
+ }
+ }
+ }
+};
+
+PrefObserver.init();
+
+/**
+ * A module to manage deferred, asynchronous writing of data files
+ * to disk. Writing is deferred by waiting for a specified delay after
+ * a request to save the data, before beginning to write. If more than
+ * one save request is received during the delay, all requests are
+ * fulfilled by a single write.
+ *
+ * @constructor
+ * @param aPath
+ * String representing the full path of the file where the data
+ * is to be written.
+ * @param aDataProvider
+ * Callback function that takes no argument and returns the data to
+ * be written. If aDataProvider returns an ArrayBufferView, the
+ * bytes it contains are written to the file as is.
+ * If aDataProvider returns a String the data are UTF-8 encoded
+ * and then written to the file.
+ * @param [optional] aDelay
+ * The delay in milliseconds between the first saveChanges() call
+ * that marks the data as needing to be saved, and when the DeferredSave
+ * begins writing the data to disk. Default 50 milliseconds.
+ */
+this.DeferredSave = function(aPath, aDataProvider, aDelay) {
+ // Create a new logger (child of 'DeferredSave' logger)
+ // for use by this particular instance of DeferredSave object
+ let leafName = OS.Path.basename(aPath);
+ let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName;
+ this.logger = Log.repository.getLogger(logger_id);
+
+ // @type {Deferred|null}, null when no data needs to be written
+ // @resolves with the result of OS.File.writeAtomic when all writes complete
+ // @rejects with the error from OS.File.writeAtomic if the write fails,
+ // or with the error from aDataProvider() if that throws.
+ this._pending = null;
+
+ // @type {Promise}, completes when the in-progress write (if any) completes,
+ // kept as a resolved promise at other times to simplify logic.
+ // Because _deferredSave() always uses _writing.then() to execute
+ // its next action, we don't need a special case for whether a write
+ // is in progress - if the previous write is complete (and the _writing
+ // promise is already resolved/rejected), _writing.then() starts
+ // the next action immediately.
+ //
+ // @resolves with the result of OS.File.writeAtomic
+ // @rejects with the error from OS.File.writeAtomic
+ this._writing = Promise.resolve(0);
+
+ // Are we currently waiting for a write to complete
+ this.writeInProgress = false;
+
+ this._path = aPath;
+ this._dataProvider = aDataProvider;
+
+ this._timer = null;
+
+ // Some counters for telemetry
+ // The total number of times the file was written
+ this.totalSaves = 0;
+
+ // The number of times the data became dirty while
+ // another save was in progress
+ this.overlappedSaves = 0;
+
+ // Error returned by the most recent write (if any)
+ this._lastError = null;
+
+ if (aDelay && (aDelay > 0))
+ this._delay = aDelay;
+ else
+ this._delay = DEFAULT_SAVE_DELAY_MS;
+}
+
+this.DeferredSave.prototype = {
+ get dirty() {
+ return this._pending || this.writeInProgress;
+ },
+
+ get lastError() {
+ return this._lastError;
+ },
+
+ // Start the pending timer if data is dirty
+ _startTimer: function() {
+ if (!this._pending) {
+ return;
+ }
+
+ this.logger.debug("Starting timer");
+ if (!this._timer)
+ this._timer = MakeTimer();
+ this._timer.initWithCallback(() => this._deferredSave(),
+ this._delay, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ /**
+ * Mark the current stored data dirty, and schedule a flush to disk
+ * @return A Promise<integer> that will be resolved after the data is written to disk;
+ * the promise is resolved with the number of bytes written.
+ */
+ saveChanges: function() {
+ this.logger.debug("Save changes");
+ if (!this._pending) {
+ if (this.writeInProgress) {
+ this.logger.debug("Data changed while write in progress");
+ this.overlappedSaves++;
+ }
+ this._pending = Promise.defer();
+ // Wait until the most recent write completes or fails (if it hasn't already)
+ // and then restart our timer
+ this._writing.then(count => this._startTimer(), error => this._startTimer());
+ }
+ return this._pending.promise;
+ },
+
+ _deferredSave: function() {
+ let pending = this._pending;
+ this._pending = null;
+ let writing = this._writing;
+ this._writing = pending.promise;
+
+ // In either the success or the exception handling case, we don't need to handle
+ // the error from _writing here; it's already being handled in another then()
+ let toSave = null;
+ try {
+ toSave = this._dataProvider();
+ }
+ catch (e) {
+ this.logger.error("Deferred save dataProvider failed", e);
+ writing.then(null, error => {})
+ .then(count => {
+ pending.reject(e);
+ });
+ return;
+ }
+
+ writing.then(null, error => { return 0; })
+ .then(count => {
+ this.logger.debug("Starting write");
+ this.totalSaves++;
+ this.writeInProgress = true;
+
+ OS.File.writeAtomic(this._path, toSave, {tmpPath: this._path + ".tmp"})
+ .then(
+ result => {
+ this._lastError = null;
+ this.writeInProgress = false;
+ this.logger.debug("Write succeeded");
+ pending.resolve(result);
+ },
+ error => {
+ this._lastError = error;
+ this.writeInProgress = false;
+ this.logger.warn("Write failed", error);
+ pending.reject(error);
+ });
+ });
+ },
+
+ /**
+ * Immediately save the dirty data to disk, skipping
+ * the delay of normal operation. Note that the write
+ * still happens asynchronously in the worker
+ * thread from OS.File.
+ *
+ * There are four possible situations:
+ * 1) Nothing to flush
+ * 2) Data is not currently being written, in-memory copy is dirty
+ * 3) Data is currently being written, in-memory copy is clean
+ * 4) Data is being written and in-memory copy is dirty
+ *
+ * @return Promise<integer> that will resolve when all in-memory data
+ * has finished being flushed, returning the number of bytes
+ * written. If all in-memory data is clean, completes with the
+ * result of the most recent write.
+ */
+ flush: function() {
+ // If we have pending changes, cancel our timer and set up the write
+ // immediately (_deferredSave queues the write for after the most
+ // recent write completes, if it hasn't already)
+ if (this._pending) {
+ this.logger.debug("Flush called while data is dirty");
+ if (this._timer) {
+ this._timer.cancel();
+ this._timer = null;
+ }
+ this._deferredSave();
+ }
+
+ return this._writing;
+ }
+};
diff --git a/components/addons/src/LightweightThemeConsumer.jsm b/components/addons/src/LightweightThemeConsumer.jsm
new file mode 100644
index 000000000..9419fdcf2
--- /dev/null
+++ b/components/addons/src/LightweightThemeConsumer.jsm
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["LightweightThemeConsumer"];
+
+const {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer",
+ "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+this.LightweightThemeConsumer =
+ function LightweightThemeConsumer(aDocument) {
+ this._doc = aDocument;
+ this._win = aDocument.defaultView;
+ this._footerId = aDocument.documentElement.getAttribute("lightweightthemesfooter");
+
+/* XXX: If we want to disable LWTs for PB mode, this would be needed.
+ * Perhaps make this pref-controlled in the future if people want it?
+ if (PrivateBrowsingUtils.isWindowPrivate(this._win) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ return;
+ } */
+
+ let screen = this._win.screen;
+ this._lastScreenWidth = screen.width;
+ this._lastScreenHeight = screen.height;
+
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+
+ var temp = {};
+ Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
+ this._update(temp.LightweightThemeManager.currentThemeForDisplay);
+ this._win.addEventListener("resize", this);
+}
+
+LightweightThemeConsumer.prototype = {
+ _lastData: null,
+ _lastScreenWidth: null,
+ _lastScreenHeight: null,
+ // Whether the active lightweight theme should be shown on the window.
+ _enabled: true,
+ // Whether a lightweight theme is enabled.
+ _active: false,
+
+ enable: function() {
+ this._enabled = true;
+ this._update(this._lastData);
+ },
+
+ disable: function() {
+ // Dance to keep the data, but reset the applied styles:
+ let lastData = this._lastData
+ this._update(null);
+ this._enabled = false;
+ this._lastData = lastData;
+ },
+
+ getData: function() {
+ return this._enabled ? Cu.cloneInto(this._lastData, this._win) : null;
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ if (aTopic != "lightweight-theme-styling-update")
+ return;
+
+ this._update(JSON.parse(aData));
+ },
+
+ handleEvent: function (aEvent) {
+ let {width, height} = this._win.screen;
+
+ if (this._lastScreenWidth != width || this._lastScreenHeight != height) {
+ this._lastScreenWidth = width;
+ this._lastScreenHeight = height;
+ if (!this._active)
+ return;
+ this._update(this._lastData);
+ Services.obs.notifyObservers(this._win, "lightweight-theme-optimized",
+ JSON.stringify(this._lastData));
+ }
+ },
+
+ destroy: function () {
+/* XXX: If we want to disable LWTs for PB mode, this would be needed.
+ if (!PrivateBrowsingUtils.isWindowPrivate(this._win) ||
+ PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+
+ this._win.removeEventListener("resize", this);
+ } */
+
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+ this._win.removeEventListener("resize", this);
+
+ this._win = this._doc = null;
+ },
+
+ _update: function (aData) {
+ if (!aData) {
+ aData = { headerURL: "", footerURL: "", textcolor: "", accentcolor: "" };
+ this._lastData = aData;
+ } else {
+ this._lastData = aData;
+ aData = LightweightThemeImageOptimizer.optimize(aData, this._win.screen);
+ }
+ if (!this._enabled)
+ return;
+
+ let root = this._doc.documentElement;
+ let active = !!aData.headerURL;
+ let stateChanging = (active != this._active);
+
+ // We need to clear these either way: either because the theme is being removed,
+ // or because we are applying a new theme and the data might be bogus CSS,
+ // so if we don't reset first, it'll keep the old value.
+ root.style.removeProperty("color");
+ root.style.removeProperty("background-color");
+ if (active) {
+ root.style.color = aData.textcolor || "black";
+ root.style.backgroundColor = aData.accentcolor || "white";
+ let [r, g, b] = _parseRGB(this._doc.defaultView.getComputedStyle(root, "").color);
+ let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
+ root.setAttribute("lwthemetextcolor", luminance <= 110 ? "dark" : "bright");
+ root.setAttribute("lwtheme", "true");
+ } else {
+ root.removeAttribute("lwthemetextcolor");
+ root.removeAttribute("lwtheme");
+ }
+
+ this._active = active;
+
+ _setImage(root, active, aData.headerURL);
+ if (this._footerId) {
+ let footer = this._doc.getElementById(this._footerId);
+ footer.style.backgroundColor = active ? aData.accentcolor || "white" : "";
+ _setImage(footer, active, aData.footerURL);
+ if (active && aData.footerURL)
+ footer.setAttribute("lwthemefooter", "true");
+ else
+ footer.removeAttribute("lwthemefooter");
+ }
+
+ Services.obs.notifyObservers(this._win, "lightweight-theme-window-updated",
+ JSON.stringify(aData));
+ }
+}
+
+function _setImage(aElement, aActive, aURL) {
+ aElement.style.backgroundImage =
+ (aActive && aURL) ? 'url("' + aURL.replace(/"/g, '\\"') + '")' : "";
+}
+
+function _parseRGB(aColorString) {
+ var rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
+ rgb.shift();
+ return rgb.map(x => parseInt(x));
+}
diff --git a/components/addons/src/LightweightThemeImageOptimizer.jsm b/components/addons/src/LightweightThemeImageOptimizer.jsm
new file mode 100644
index 000000000..a9201c3da
--- /dev/null
+++ b/components/addons/src/LightweightThemeImageOptimizer.jsm
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["LightweightThemeImageOptimizer"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+
+const ORIGIN_TOP_RIGHT = 1;
+const ORIGIN_BOTTOM_LEFT = 2;
+
+this.LightweightThemeImageOptimizer = {
+ optimize: function LWTIO_optimize(aThemeData, aScreen) {
+ let data = Utils.createCopy(aThemeData);
+ if (!data.headerURL) {
+ return data;
+ }
+
+ data.headerURL = ImageCropper.getCroppedImageURL(
+ data.headerURL, aScreen, ORIGIN_TOP_RIGHT);
+
+ if (data.footerURL) {
+ data.footerURL = ImageCropper.getCroppedImageURL(
+ data.footerURL, aScreen, ORIGIN_BOTTOM_LEFT);
+ }
+
+ return data;
+ },
+
+ purge: function LWTIO_purge() {
+ let dir = FileUtils.getDir("ProfD", ["lwtheme"]);
+ dir.followLinks = false;
+ try {
+ dir.remove(true);
+ } catch (e) {}
+ }
+};
+
+Object.freeze(LightweightThemeImageOptimizer);
+
+var ImageCropper = {
+ _inProgress: {},
+
+ getCroppedImageURL:
+ function ImageCropper_getCroppedImageURL(aImageURL, aScreen, aOrigin) {
+ // We can crop local files, only.
+ if (!aImageURL.startsWith("file://")) {
+ return aImageURL;
+ }
+
+ try {
+ if (Services.prefs.getBoolPref("lightweightThemes.animation.enabled")) {
+ //Don't crop if animated
+ return aImageURL;
+ }
+ } catch(e) {
+ // Continue of pref doesn't exist.
+ }
+
+ // Generate the cropped image's file name using its
+ // base name and the current screen size.
+ let uri = Services.io.newURI(aImageURL, null, null);
+ let file = uri.QueryInterface(Ci.nsIFileURL).file;
+
+ // Make sure the source file exists.
+ if (!file.exists()) {
+ return aImageURL;
+ }
+
+ let fileName = file.leafName + "-" + aScreen.width + "x" + aScreen.height;
+ let croppedFile = FileUtils.getFile("ProfD", ["lwtheme", fileName]);
+
+ // If we have a local file that is not in progress, return it.
+ if (croppedFile.exists() && !(croppedFile.path in this._inProgress)) {
+ let fileURI = Services.io.newFileURI(croppedFile);
+
+ // Copy the query part to avoid wrong caching.
+ fileURI.QueryInterface(Ci.nsIURL).query = uri.query;
+ return fileURI.spec;
+ }
+
+ // Crop the given image in the background.
+ this._crop(uri, croppedFile, aScreen, aOrigin);
+
+ // Return the original image while we're waiting for the cropped version
+ // to be written to disk.
+ return aImageURL;
+ },
+
+ _crop: function ImageCropper_crop(aURI, aTargetFile, aScreen, aOrigin) {
+ let inProgress = this._inProgress;
+ inProgress[aTargetFile.path] = true;
+
+ function resetInProgress() {
+ delete inProgress[aTargetFile.path];
+ }
+
+ ImageFile.read(aURI, function crop_readImageFile(aInputStream, aContentType) {
+ if (aInputStream && aContentType) {
+ let image = ImageTools.decode(aInputStream, aContentType);
+ if (image && image.width && image.height) {
+ let stream = ImageTools.encode(image, aScreen, aOrigin, aContentType);
+ if (stream) {
+ ImageFile.write(aTargetFile, stream, resetInProgress);
+ return;
+ }
+ }
+ }
+
+ resetInProgress();
+ });
+ }
+};
+
+var ImageFile = {
+ read: function(aURI, aCallback) {
+ this._netUtil.asyncFetch({
+ uri: aURI,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
+ }, function(aInputStream, aStatus, aRequest) {
+ if (Components.isSuccessCode(aStatus) && aRequest instanceof Ci.nsIChannel) {
+ let channel = aRequest.QueryInterface(Ci.nsIChannel);
+ aCallback(aInputStream, channel.contentType);
+ } else {
+ aCallback();
+ }
+ });
+ },
+
+ write: function ImageFile_write(aFile, aInputStream, aCallback) {
+ let fos = FileUtils.openSafeFileOutputStream(aFile);
+ this._netUtil.asyncCopy(aInputStream, fos, function write_asyncCopy(aResult) {
+ FileUtils.closeSafeFileOutputStream(fos);
+
+ // Remove the file if writing was not successful.
+ if (!Components.isSuccessCode(aResult)) {
+ try {
+ aFile.remove(false);
+ } catch (e) {}
+ }
+
+ aCallback();
+ });
+ }
+};
+
+XPCOMUtils.defineLazyModuleGetter(ImageFile, "_netUtil",
+ "resource://gre/modules/NetUtil.jsm", "NetUtil");
+
+var ImageTools = {
+ decode: function ImageTools_decode(aInputStream, aContentType) {
+ let outParam = {value: null};
+
+ try {
+ this._imgTools.decodeImageData(aInputStream, aContentType, outParam);
+ } catch (e) {}
+
+ return outParam.value;
+ },
+
+ encode: function ImageTools_encode(aImage, aScreen, aOrigin, aContentType) {
+ let stream;
+ let width = Math.min(aImage.width, aScreen.width);
+ let height = Math.min(aImage.height, aScreen.height);
+ let x = aOrigin == ORIGIN_TOP_RIGHT ? aImage.width - width : 0;
+
+ try {
+ stream = this._imgTools.encodeCroppedImage(aImage, aContentType, x, 0,
+ width, height);
+ } catch (e) {}
+
+ return stream;
+ }
+};
+
+XPCOMUtils.defineLazyServiceGetter(ImageTools, "_imgTools",
+ "@mozilla.org/image/tools;1", "imgITools");
+
+var Utils = {
+ createCopy: function Utils_createCopy(aData) {
+ let copy = {};
+ for (let [k, v] in Iterator(aData)) {
+ copy[k] = v;
+ }
+ return copy;
+ }
+};
diff --git a/components/addons/src/LightweightThemeManager.jsm b/components/addons/src/LightweightThemeManager.jsm
new file mode 100644
index 000000000..a4cbf3833
--- /dev/null
+++ b/components/addons/src/LightweightThemeManager.jsm
@@ -0,0 +1,801 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["LightweightThemeManager"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/AddonManager.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const ID_SUFFIX = "@personas.mozilla.org";
+const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect";
+const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
+const PREF_EM_DSS_ENABLED = "extensions.dss.enabled";
+const ADDON_TYPE = "theme";
+
+const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
+
+const STRING_TYPE_NAME = "type.%ID%.name";
+
+const DEFAULT_MAX_USED_THEMES_COUNT = 30;
+
+const MAX_PREVIEW_SECONDS = 30;
+
+const MANDATORY = ["id", "name", "headerURL"];
+const OPTIONAL = ["footerURL", "textcolor", "accentcolor", "iconURL",
+ "previewURL", "author", "description", "homepageURL",
+ "updateURL", "version"];
+
+const PERSIST_ENABLED = true;
+const PERSIST_BYPASS_CACHE = false;
+const PERSIST_FILES = {
+ headerURL: "lightweighttheme-header",
+ footerURL: "lightweighttheme-footer"
+};
+
+XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeImageOptimizer",
+ "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
+
+this.__defineGetter__("_prefs", function prefsGetter() {
+ delete this._prefs;
+ return this._prefs = Services.prefs.getBranch("lightweightThemes.");
+});
+
+this.__defineGetter__("_maxUsedThemes", function maxUsedThemesGetter() {
+ delete this._maxUsedThemes;
+ this._maxUsedThemes = _prefs.getIntPref("maxUsedThemes", DEFAULT_MAX_USED_THEMES_COUNT);
+ return this._maxUsedThemes;
+});
+
+this.__defineSetter__("_maxUsedThemes", function maxUsedThemesSetter(aVal) {
+ delete this._maxUsedThemes;
+ return this._maxUsedThemes = aVal;
+});
+
+// Holds the ID of the theme being enabled or disabled while sending out the
+// events so cached AddonWrapper instances can return correct values for
+// permissions and pendingOperations
+var _themeIDBeingEnabled = null;
+var _themeIDBeingDisabled = null;
+
+this.LightweightThemeManager = {
+ get name() "LightweightThemeManager",
+
+ get usedThemes () {
+ try {
+ return JSON.parse(_prefs.getComplexValue("usedThemes",
+ Ci.nsISupportsString).data);
+ } catch (e) {
+ return [];
+ }
+ },
+
+ get currentTheme () {
+ try {
+ if (_prefs.getBoolPref("isThemeSelected"))
+ var data = this.usedThemes[0];
+ } catch (e) {}
+
+ return data || null;
+ },
+
+ get currentThemeForDisplay () {
+ var data = this.currentTheme;
+
+ if (data && PERSIST_ENABLED) {
+ for (let key in PERSIST_FILES) {
+ try {
+ if (data[key] && _prefs.getBoolPref("persisted." + key))
+ data[key] = _getLocalImageURI(PERSIST_FILES[key]).spec
+ + "?" + data.id + ";" + _version(data);
+ } catch (e) {}
+ }
+ }
+
+ return data;
+ },
+
+ set currentTheme (aData) {
+ return _setCurrentTheme(aData, false);
+ },
+
+ setLocalTheme: function(aData) {
+ _setCurrentTheme(aData, true);
+ },
+
+ getUsedTheme: function(aId) {
+ var usedThemes = this.usedThemes;
+ for (let usedTheme of usedThemes) {
+ if (usedTheme.id == aId)
+ return usedTheme;
+ }
+ return null;
+ },
+
+ forgetUsedTheme: function(aId) {
+ let theme = this.getUsedTheme(aId);
+ if (!theme)
+ return;
+
+ let wrapper = new AddonWrapper(theme);
+ AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
+
+ var currentTheme = this.currentTheme;
+ if (currentTheme && currentTheme.id == aId) {
+ this.themeChanged(null);
+ AddonManagerPrivate.notifyAddonChanged(null, ADDON_TYPE, false);
+ }
+
+ _updateUsedThemes(_usedThemesExceptId(aId));
+ AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
+ },
+
+ previewTheme: function(aData) {
+ let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+ cancel.data = false;
+ Services.obs.notifyObservers(cancel, "lightweight-theme-preview-requested",
+ JSON.stringify(aData));
+ if (cancel.data)
+ return;
+
+ if (_previewTimer)
+ _previewTimer.cancel();
+ else
+ _previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ _previewTimer.initWithCallback(_previewTimerCallback,
+ MAX_PREVIEW_SECONDS * 1000,
+ _previewTimer.TYPE_ONE_SHOT);
+
+ _notifyWindows(aData);
+ },
+
+ resetPreview: function() {
+ if (_previewTimer) {
+ _previewTimer.cancel();
+ _previewTimer = null;
+ _notifyWindows(this.currentThemeForDisplay);
+ }
+ },
+
+ parseTheme: function(aString, aBaseURI) {
+ try {
+ return _sanitizeTheme(JSON.parse(aString), aBaseURI, false);
+ } catch (e) {
+ return null;
+ }
+ },
+
+ updateCurrentTheme: function() {
+ try {
+ if (!_prefs.getBoolPref("update.enabled"))
+ return;
+ } catch (e) {
+ return;
+ }
+
+ var theme = this.currentTheme;
+ if (!theme || !theme.updateURL)
+ return;
+
+ var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+
+ req.mozBackgroundRequest = true;
+ req.overrideMimeType("text/plain");
+ req.open("GET", theme.updateURL, true);
+ // Prevent the request from reading from the cache.
+ req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ // Prevent the request from writing to the cache.
+ req.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+
+ var self = this;
+ req.addEventListener("load", function loadEventListener() {
+ if (req.status != 200)
+ return;
+
+ let newData = self.parseTheme(req.responseText, theme.updateURL);
+ if (!newData ||
+ newData.id != theme.id ||
+ _version(newData) == _version(theme))
+ return;
+
+ var currentTheme = self.currentTheme;
+ if (currentTheme && currentTheme.id == theme.id)
+ self.currentTheme = newData;
+ }, false);
+
+ req.send(null);
+ },
+
+ /**
+ * Switches to a new lightweight theme.
+ *
+ * @param aData
+ * The lightweight theme to switch to
+ */
+ themeChanged: function(aData) {
+ if (_previewTimer) {
+ _previewTimer.cancel();
+ _previewTimer = null;
+ }
+
+ if (aData) {
+ let usedThemes = _usedThemesExceptId(aData.id);
+ usedThemes.unshift(aData);
+ _updateUsedThemes(usedThemes);
+ if (PERSIST_ENABLED) {
+ LightweightThemeImageOptimizer.purge();
+ _persistImages(aData, function themeChanged_persistImages() {
+ _notifyWindows(this.currentThemeForDisplay);
+ }.bind(this));
+ }
+ }
+
+ _prefs.setBoolPref("isThemeSelected", aData != null);
+ _notifyWindows(aData);
+ Services.obs.notifyObservers(null, "lightweight-theme-changed", null);
+ },
+
+ /**
+ * Starts the Addons provider and enables the new lightweight theme if
+ * necessary.
+ */
+ startup: function() {
+ if (Services.prefs.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
+ let id = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
+ if (id)
+ this.themeChanged(this.getUsedTheme(id));
+ else
+ this.themeChanged(null);
+ Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT);
+ }
+
+ _prefs.addObserver("", _prefObserver, false);
+ },
+
+ /**
+ * Shuts down the provider.
+ */
+ shutdown: function() {
+ _prefs.removeObserver("", _prefObserver);
+ },
+
+ /**
+ * Called when a new add-on has been enabled when only one add-on of that type
+ * can be enabled.
+ *
+ * @param aId
+ * The ID of the newly enabled add-on
+ * @param aType
+ * The type of the newly enabled add-on
+ * @param aPendingRestart
+ * true if the newly enabled add-on will only become enabled after a
+ * restart
+ */
+ addonChanged: function(aId, aType, aPendingRestart) {
+ if (aType != ADDON_TYPE)
+ return;
+
+ let id = _getInternalID(aId);
+ let current = this.currentTheme;
+
+ try {
+ let next = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
+ if (id == next && aPendingRestart)
+ return;
+
+ Services.prefs.clearUserPref(PREF_LWTHEME_TO_SELECT);
+ if (next) {
+ AddonManagerPrivate.callAddonListeners("onOperationCancelled",
+ new AddonWrapper(this.getUsedTheme(next)));
+ }
+ else {
+ if (id == current.id) {
+ AddonManagerPrivate.callAddonListeners("onOperationCancelled",
+ new AddonWrapper(current));
+ return;
+ }
+ }
+ }
+ catch (e) {
+ }
+
+ if (current) {
+ if (current.id == id)
+ return;
+ _themeIDBeingDisabled = current.id;
+ let wrapper = new AddonWrapper(current);
+ if (aPendingRestart) {
+ Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, "");
+ AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, true);
+ }
+ else {
+ AddonManagerPrivate.callAddonListeners("onDisabling", wrapper, false);
+ this.themeChanged(null);
+ AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
+ }
+ _themeIDBeingDisabled = null;
+ }
+
+ if (id) {
+ let theme = this.getUsedTheme(id);
+ _themeIDBeingEnabled = id;
+ let wrapper = new AddonWrapper(theme);
+ if (aPendingRestart) {
+ AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, true);
+ Services.prefs.setCharPref(PREF_LWTHEME_TO_SELECT, id);
+
+ // Flush the preferences to disk so they survive any crash
+ Services.prefs.savePrefFile(null);
+ }
+ else {
+ AddonManagerPrivate.callAddonListeners("onEnabling", wrapper, false);
+ this.themeChanged(theme);
+ AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
+ }
+ _themeIDBeingEnabled = null;
+ }
+ },
+
+ /**
+ * Called to get an Addon with a particular ID.
+ *
+ * @param aId
+ * The ID of the add-on to retrieve
+ * @param aCallback
+ * A callback to pass the Addon to
+ */
+ getAddonByID: function(aId, aCallback) {
+ let id = _getInternalID(aId);
+ if (!id) {
+ aCallback(null);
+ return;
+ }
+
+ let theme = this.getUsedTheme(id);
+ if (!theme) {
+ aCallback(null);
+ return;
+ }
+
+ aCallback(new AddonWrapper(theme));
+ },
+
+ /**
+ * Called to get Addons of a particular type.
+ *
+ * @param aTypes
+ * An array of types to fetch. Can be null to get all types.
+ * @param aCallback
+ * A callback to pass an array of Addons to
+ */
+ getAddonsByTypes: function(aTypes, aCallback) {
+ if (aTypes && aTypes.indexOf(ADDON_TYPE) == -1) {
+ aCallback([]);
+ return;
+ }
+
+ // Tycho: aCallback([new AddonWrapper(a) for each (a in this.usedThemes)]);
+ let result = [];
+ for each(let a in this.usedThemes) {
+ result.push(new AddonWrapper(a));
+ }
+
+ aCallback(result);
+ },
+};
+
+/**
+ * The AddonWrapper wraps lightweight theme to provide the data visible to
+ * consumers of the AddonManager API.
+ */
+function AddonWrapper(aTheme) {
+ this.__defineGetter__("id", function AddonWrapper_idGetter() aTheme.id + ID_SUFFIX);
+ this.__defineGetter__("type", function AddonWrapper_typeGetter() ADDON_TYPE);
+ this.__defineGetter__("isActive", function AddonWrapper_isActiveGetter() {
+ let current = LightweightThemeManager.currentTheme;
+ if (current)
+ return aTheme.id == current.id;
+ return false;
+ });
+
+ this.__defineGetter__("name", function AddonWrapper_nameGetter() aTheme.name);
+ this.__defineGetter__("version", function AddonWrapper_versionGetter() {
+ return "version" in aTheme ? aTheme.version : "";
+ });
+
+ ["description", "homepageURL", "iconURL"].forEach(function(prop) {
+ this.__defineGetter__(prop, function AddonWrapper_optionalPropGetter() {
+ return prop in aTheme ? aTheme[prop] : null;
+ });
+ }, this);
+
+ ["installDate", "updateDate"].forEach(function(prop) {
+ this.__defineGetter__(prop, function AddonWrapper_datePropGetter() {
+ return prop in aTheme ? new Date(aTheme[prop]) : null;
+ });
+ }, this);
+
+ this.__defineGetter__("creator", function AddonWrapper_creatorGetter() {
+ return new AddonManagerPrivate.AddonAuthor(aTheme.author);
+ });
+
+ this.__defineGetter__("screenshots", function AddonWrapper_screenshotsGetter() {
+ let url = aTheme.previewURL;
+ return [new AddonManagerPrivate.AddonScreenshot(url)];
+ });
+
+ this.__defineGetter__("pendingOperations",
+ function AddonWrapper_pendingOperationsGetter() {
+ let pending = AddonManager.PENDING_NONE;
+ if (this.isActive == this.userDisabled)
+ pending |= this.isActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE;
+ return pending;
+ });
+
+ this.__defineGetter__("operationsRequiringRestart",
+ function AddonWrapper_operationsRequiringRestartGetter() {
+ // If a non-default theme is in use then a restart will be required to
+ // enable lightweight themes unless dynamic theme switching is enabled
+ if (Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) {
+ try {
+ if (Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED))
+ return AddonManager.OP_NEEDS_RESTART_NONE;
+ }
+ catch (e) {
+ }
+ return AddonManager.OP_NEEDS_RESTART_ENABLE;
+ }
+
+ return AddonManager.OP_NEEDS_RESTART_NONE;
+ });
+
+ this.__defineGetter__("size", function AddonWrapper_sizeGetter() {
+ // The size changes depending on whether the theme is in use or not, this is
+ // probably not worth exposing.
+ return null;
+ });
+
+ this.__defineGetter__("permissions", function AddonWrapper_permissionsGetter() {
+ let permissions = AddonManager.PERM_CAN_UNINSTALL;
+ if (this.userDisabled)
+ permissions |= AddonManager.PERM_CAN_ENABLE;
+ else
+ permissions |= AddonManager.PERM_CAN_DISABLE;
+ return permissions;
+ });
+
+ this.__defineGetter__("userDisabled", function AddonWrapper_userDisabledGetter() {
+ if (_themeIDBeingEnabled == aTheme.id)
+ return false;
+ if (_themeIDBeingDisabled == aTheme.id)
+ return true;
+
+ try {
+ let toSelect = Services.prefs.getCharPref(PREF_LWTHEME_TO_SELECT);
+ return aTheme.id != toSelect;
+ }
+ catch (e) {
+ let current = LightweightThemeManager.currentTheme;
+ return !current || current.id != aTheme.id;
+ }
+ });
+
+ this.__defineSetter__("userDisabled", function AddonWrapper_userDisabledSetter(val) {
+ if (val == this.userDisabled)
+ return val;
+
+ if (val)
+ LightweightThemeManager.currentTheme = null;
+ else
+ LightweightThemeManager.currentTheme = aTheme;
+
+ return val;
+ });
+
+ this.uninstall = function AddonWrapper_uninstall() {
+ LightweightThemeManager.forgetUsedTheme(aTheme.id);
+ };
+
+ this.cancelUninstall = function AddonWrapper_cancelUninstall() {
+ throw new Error("Theme is not marked to be uninstalled");
+ };
+
+ this.findUpdates = function AddonWrapper_findUpdates(listener, reason, appVersion, platformVersion) {
+ AddonManagerPrivate.callNoUpdateListeners(this, listener, reason, appVersion, platformVersion);
+ };
+}
+
+AddonWrapper.prototype = {
+ // Lightweight themes are never disabled by the application
+ get appDisabled() {
+ return false;
+ },
+
+ // Lightweight themes are always compatible
+ get isCompatible() {
+ return true;
+ },
+
+ get isPlatformCompatible() {
+ return true;
+ },
+
+ get scope() {
+ return AddonManager.SCOPE_PROFILE;
+ },
+
+ get foreignInstall() {
+ return false;
+ },
+
+ // Lightweight themes are always compatible
+ isCompatibleWith: function(appVersion, platformVersion) {
+ return true;
+ },
+
+ // Lightweight themes are always securely updated
+ get providesUpdatesSecurely() {
+ return true;
+ },
+
+ // Lightweight themes are never blocklisted
+ get blocklistState() {
+ return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+ }
+};
+
+/**
+ * Converts the ID used by the public AddonManager API to an lightweight theme
+ * ID.
+ *
+ * @param id
+ * The ID to be converted
+ *
+ * @return the lightweight theme ID or null if the ID was not for a lightweight
+ * theme.
+ */
+function _getInternalID(id) {
+ if (!id)
+ return null;
+ let len = id.length - ID_SUFFIX.length;
+ if (len > 0 && id.substring(len) == ID_SUFFIX)
+ return id.substring(0, len);
+ return null;
+}
+
+function _setCurrentTheme(aData, aLocal) {
+ aData = _sanitizeTheme(aData, null, aLocal);
+
+ let needsRestart = (ADDON_TYPE == "theme") &&
+ Services.prefs.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN);
+
+ let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+ cancel.data = false;
+ Services.obs.notifyObservers(cancel, "lightweight-theme-change-requested",
+ JSON.stringify(aData));
+
+ if (aData) {
+ let theme = LightweightThemeManager.getUsedTheme(aData.id);
+ let isInstall = !theme || theme.version != aData.version;
+ if (isInstall) {
+ aData.updateDate = Date.now();
+ if (theme && "installDate" in theme)
+ aData.installDate = theme.installDate;
+ else
+ aData.installDate = aData.updateDate;
+
+ var oldWrapper = theme ? new AddonWrapper(theme) : null;
+ var wrapper = new AddonWrapper(aData);
+ AddonManagerPrivate.callInstallListeners("onExternalInstall", null,
+ wrapper, oldWrapper, false);
+ AddonManagerPrivate.callAddonListeners("onInstalling", wrapper, false);
+ }
+
+ let current = LightweightThemeManager.currentTheme;
+ let usedThemes = _usedThemesExceptId(aData.id);
+ if (current && current.id != aData.id)
+ usedThemes.splice(1, 0, aData);
+ else
+ usedThemes.unshift(aData);
+ _updateUsedThemes(usedThemes);
+
+ if (isInstall)
+ AddonManagerPrivate.callAddonListeners("onInstalled", wrapper);
+ }
+
+ if (cancel.data)
+ return null;
+
+ AddonManagerPrivate.notifyAddonChanged(aData ? aData.id + ID_SUFFIX : null,
+ ADDON_TYPE, needsRestart);
+
+ return LightweightThemeManager.currentTheme;
+}
+
+function _sanitizeTheme(aData, aBaseURI, aLocal) {
+ if (!aData || typeof aData != "object")
+ return null;
+
+ var resourceProtocols = ["http", "https", "resource"];
+ if (aLocal)
+ resourceProtocols.push("file");
+ var resourceProtocolExp = new RegExp("^(" + resourceProtocols.join("|") + "):");
+
+ function sanitizeProperty(prop) {
+ if (!(prop in aData))
+ return null;
+ if (typeof aData[prop] != "string")
+ return null;
+ let val = aData[prop].trim();
+ if (!val)
+ return null;
+
+ if (!/URL$/.test(prop))
+ return val;
+
+ try {
+ val = _makeURI(val, aBaseURI ? _makeURI(aBaseURI) : null).spec;
+ if ((prop == "updateURL" ? /^https:/ : resourceProtocolExp).test(val))
+ return val;
+ return null;
+ }
+ catch (e) {
+ return null;
+ }
+ }
+
+ let result = {};
+ for (let mandatoryProperty of MANDATORY) {
+ let val = sanitizeProperty(mandatoryProperty);
+ if (!val)
+ throw Components.results.NS_ERROR_INVALID_ARG;
+ result[mandatoryProperty] = val;
+ }
+
+ for (let optionalProperty of OPTIONAL) {
+ let val = sanitizeProperty(optionalProperty);
+ if (!val)
+ continue;
+ result[optionalProperty] = val;
+ }
+
+ return result;
+}
+
+function _usedThemesExceptId(aId)
+ LightweightThemeManager.usedThemes.filter(
+ function usedThemesExceptId_filterID(t) "id" in t && t.id != aId);
+
+function _version(aThemeData)
+ aThemeData.version || "";
+
+function _makeURI(aURL, aBaseURI)
+ Services.io.newURI(aURL, null, aBaseURI);
+
+function _updateUsedThemes(aList) {
+ // Send uninstall events for all themes that need to be removed.
+ while (aList.length > _maxUsedThemes) {
+ let wrapper = new AddonWrapper(aList[aList.length - 1]);
+ AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper, false);
+ aList.pop();
+ AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
+ }
+
+ var str = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ str.data = JSON.stringify(aList);
+ _prefs.setComplexValue("usedThemes", Ci.nsISupportsString, str);
+
+ Services.obs.notifyObservers(null, "lightweight-theme-list-changed", null);
+}
+
+function _notifyWindows(aThemeData) {
+ Services.obs.notifyObservers(null, "lightweight-theme-styling-update",
+ JSON.stringify(aThemeData));
+}
+
+var _previewTimer;
+var _previewTimerCallback = {
+ notify: function() {
+ LightweightThemeManager.resetPreview();
+ }
+};
+
+/**
+ * Called when any of the lightweightThemes preferences are changed.
+ */
+function _prefObserver(aSubject, aTopic, aData) {
+ switch (aData) {
+ case "maxUsedThemes":
+ _maxUsedThemes = _prefs.getIntPref(aData, DEFAULT_MAX_USED_THEMES_COUNT);
+
+ // Update the theme list to remove any themes over the number we keep
+ _updateUsedThemes(LightweightThemeManager.usedThemes);
+ break;
+ }
+}
+
+function _persistImages(aData, aCallback) {
+ function onSuccess(key) function () {
+ let current = LightweightThemeManager.currentTheme;
+ if (current && current.id == aData.id) {
+ _prefs.setBoolPref("persisted." + key, true);
+ }
+ if (--numFilesToPersist == 0 && aCallback) {
+ aCallback();
+ }
+ };
+
+ let numFilesToPersist = 0;
+ for (let key in PERSIST_FILES) {
+ _prefs.setBoolPref("persisted." + key, false);
+ if (aData[key]) {
+ numFilesToPersist++;
+ _persistImage(aData[key], PERSIST_FILES[key], onSuccess(key));
+ }
+ }
+}
+
+function _getLocalImageURI(localFileName) {
+ var localFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ localFile.append(localFileName);
+ return Services.io.newFileURI(localFile);
+}
+
+function _persistImage(sourceURL, localFileName, successCallback) {
+ if (/^(file|resource):/.test(sourceURL))
+ return;
+
+ var targetURI = _getLocalImageURI(localFileName);
+ var sourceURI = _makeURI(sourceURL);
+
+ var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+ .createInstance(Ci.nsIWebBrowserPersist);
+
+ persist.persistFlags =
+ Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION |
+ (PERSIST_BYPASS_CACHE ?
+ Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE :
+ Ci.nsIWebBrowserPersist.PERSIST_FLAGS_FROM_CACHE);
+
+ persist.progressListener = new _persistProgressListener(successCallback);
+
+ persist.saveURI(sourceURI, null,
+ null, Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE,
+ null, null, targetURI, null);
+}
+
+function _persistProgressListener(successCallback) {
+ this.onLocationChange = function persistProgressListener_onLocationChange() {};
+ this.onProgressChange = function persistProgressListener_onProgressChange() {};
+ this.onStatusChange = function persistProgressListener_onStatusChange() {};
+ this.onSecurityChange = function persistProgressListener_onSecurityChange() {};
+ this.onStateChange = function persistProgressListener_onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aRequest &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ try {
+ if (aRequest.QueryInterface(Ci.nsIHttpChannel).requestSucceeded) {
+ // success
+ successCallback();
+ return;
+ }
+ } catch (e) { }
+ // failure
+ }
+ };
+}
+
+AddonManagerPrivate.registerProvider(LightweightThemeManager, [
+ new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
+ STRING_TYPE_NAME,
+ AddonManager.VIEW_TYPE_LIST, 5000)
+]);
diff --git a/components/addons/src/PluginProvider.jsm b/components/addons/src/PluginProvider.jsm
new file mode 100644
index 000000000..cb07dcb12
--- /dev/null
+++ b/components/addons/src/PluginProvider.jsm
@@ -0,0 +1,595 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [];
+
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
+const STRING_TYPE_NAME = "type.%ID%.name";
+const LIST_UPDATED_TOPIC = "plugins-list-updated";
+const FLASH_MIME_TYPE = "application/x-shockwave-flash";
+
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.plugins";
+
+// Create a new logger for use by the Addons Plugin Provider
+// (Requires AddonManager.jsm)
+var logger = Log.repository.getLogger(LOGGER_ID);
+
+function getIDHashForString(aStr) {
+ // return the two-digit hexadecimal code for a byte
+ function toHexString(charCode)
+ ("0" + charCode.toString(16)).slice(-2);
+
+ let hasher = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ hasher.init(Ci.nsICryptoHash.MD5);
+ let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stringStream.data = aStr ? aStr : "null";
+ hasher.updateFromStream(stringStream, -1);
+
+ // convert the binary hash data to a hex string.
+ let binary = hasher.finish(false);
+
+ // Tycho: let hash = [toHexString(binary.charCodeAt(i)) for (i in binary)].join("").toLowerCase();
+ let hash = [];
+
+ for (let i in binary) {
+ hash.push(toHexString(binary.charCodeAt(i)));
+ }
+
+ hash = hash.join("").toLowerCase();
+
+ return "{" + hash.substr(0, 8) + "-" +
+ hash.substr(8, 4) + "-" +
+ hash.substr(12, 4) + "-" +
+ hash.substr(16, 4) + "-" +
+ hash.substr(20) + "}";
+}
+
+var PluginProvider = {
+ get name() "PluginProvider",
+
+ // A dictionary mapping IDs to names and descriptions
+ plugins: null,
+
+ startup: function PL_startup() {
+ Services.obs.addObserver(this, LIST_UPDATED_TOPIC, false);
+ Services.obs.addObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, false);
+ },
+
+ /**
+ * Called when the application is shutting down. Only necessary for tests
+ * to be able to simulate a shutdown.
+ */
+ shutdown: function PL_shutdown() {
+ this.plugins = null;
+ Services.obs.removeObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
+ Services.obs.removeObserver(this, LIST_UPDATED_TOPIC);
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case AddonManager.OPTIONS_NOTIFICATION_DISPLAYED:
+ this.getAddonByID(aData, function PL_displayPluginInfo(plugin) {
+ if (!plugin)
+ return;
+
+ let libLabel = aSubject.getElementById("pluginLibraries");
+ libLabel.textContent = plugin.pluginLibraries.join(", ");
+
+ let typeLabel = aSubject.getElementById("pluginMimeTypes"), types = [];
+ for (let type of plugin.pluginMimeTypes) {
+ let extras = [type.description.trim(), type.suffixes].
+ filter(function(x) x).join(": ");
+ types.push(type.type + (extras ? " (" + extras + ")" : ""));
+ }
+ typeLabel.textContent = types.join(",\n");
+ let showProtectedModePref = canDisableFlashProtectedMode(plugin);
+ aSubject.getElementById("pluginEnableProtectedMode")
+ .setAttribute("collapsed", showProtectedModePref ? "" : "true");
+ });
+ break;
+ case LIST_UPDATED_TOPIC:
+ if (this.plugins)
+ this.updatePluginList();
+ break;
+ }
+ },
+
+ /**
+ * Creates a PluginWrapper for a plugin object.
+ */
+ buildWrapper: function PL_buildWrapper(aPlugin) {
+ return new PluginWrapper(aPlugin.id,
+ aPlugin.name,
+ aPlugin.description,
+ aPlugin.tags);
+ },
+
+ /**
+ * Called to get an Addon with a particular ID.
+ *
+ * @param aId
+ * The ID of the add-on to retrieve
+ * @param aCallback
+ * A callback to pass the Addon to
+ */
+ getAddonByID: function PL_getAddon(aId, aCallback) {
+ if (!this.plugins)
+ this.buildPluginList();
+
+ if (aId in this.plugins)
+ aCallback(this.buildWrapper(this.plugins[aId]));
+ else
+ aCallback(null);
+ },
+
+ /**
+ * Called to get Addons of a particular type.
+ *
+ * @param aTypes
+ * An array of types to fetch. Can be null to get all types.
+ * @param callback
+ * A callback to pass an array of Addons to
+ */
+ getAddonsByTypes: function PL_getAddonsByTypes(aTypes, aCallback) {
+ if (aTypes && aTypes.indexOf("plugin") < 0) {
+ aCallback([]);
+ return;
+ }
+
+ if (!this.plugins)
+ this.buildPluginList();
+
+ let results = [];
+
+ for (let id in this.plugins) {
+ this.getAddonByID(id, function(aAddon) {
+ results.push(aAddon);
+ });
+ }
+
+ aCallback(results);
+ },
+
+ /**
+ * Called to get Addons that have pending operations.
+ *
+ * @param aTypes
+ * An array of types to fetch. Can be null to get all types
+ * @param aCallback
+ * A callback to pass an array of Addons to
+ */
+ getAddonsWithOperationsByTypes: function PL_getAddonsWithOperationsByTypes(aTypes, aCallback) {
+ aCallback([]);
+ },
+
+ /**
+ * Called to get the current AddonInstalls, optionally restricting by type.
+ *
+ * @param aTypes
+ * An array of types or null to get all types
+ * @param aCallback
+ * A callback to pass the array of AddonInstalls to
+ */
+ getInstallsByTypes: function PL_getInstallsByTypes(aTypes, aCallback) {
+ aCallback([]);
+ },
+
+ /**
+ * Builds a list of the current plugins reported by the plugin host
+ *
+ * @return a dictionary of plugins indexed by our generated ID
+ */
+ getPluginList: function PL_getPluginList() {
+ let tags = Cc["@mozilla.org/plugin/host;1"].
+ getService(Ci.nsIPluginHost).
+ getPluginTags({});
+
+ let list = {};
+ let seenPlugins = {};
+ for (let tag of tags) {
+ if (!(tag.name in seenPlugins))
+ seenPlugins[tag.name] = {};
+ if (!(tag.description in seenPlugins[tag.name])) {
+ let plugin = {
+ id: getIDHashForString(tag.name + tag.description),
+ // XXX Flash name substitution like in browser-plugins.js, aboutPermissions.js, permissions.js
+ name: tag.name == "Shockwave Flash" ? "Adobe Flash" : tag.name,
+ description: tag.description,
+ tags: [tag]
+ };
+
+ seenPlugins[tag.name][tag.description] = plugin;
+ list[plugin.id] = plugin;
+ }
+ else {
+ seenPlugins[tag.name][tag.description].tags.push(tag);
+ }
+ }
+
+ return list;
+ },
+
+ /**
+ * Builds the list of known plugins from the plugin host
+ */
+ buildPluginList: function PL_buildPluginList() {
+ this.plugins = this.getPluginList();
+ },
+
+ /**
+ * Updates the plugins from the plugin host by comparing the current plugins
+ * to the last known list sending out any necessary API notifications for
+ * changes.
+ */
+ updatePluginList: function PL_updatePluginList() {
+ let newList = this.getPluginList();
+
+ // Tycho:
+ // let lostPlugins = [this.buildWrapper(this.plugins[id])
+ // for each (id in Object.keys(this.plugins)) if (!(id in newList))];
+
+ // let newPlugins = [this.buildWrapper(newList[id])
+ // for each (id in Object.keys(newList)) if (!(id in this.plugins))];
+
+ // let matchedIDs = [id for each (id in Object.keys(newList)) if (id in this.plugins)];
+
+ let lostPlugins = [];
+ let newPlugins = [];
+ let matchedIDs = [];
+
+ // lostPlugins
+ for each(let id in Object.keys(this.plugins)) {
+ if (!(id in newList)) {
+ lostPlugins.push(this.buildWrapper(this.plugins[id]));
+ }
+ }
+
+ // newPlugins and matchedIDs
+ for each(let id in Object.keys(newList)) {
+ if (!(id in this.plugins)) {
+ newPlugins.push(this.buildWrapper(newList[id]));
+ }
+
+ if (id in this.plugins) {
+ matchedIDs.push(id);
+ }
+ }
+
+
+ // The plugin host generates new tags for every plugin after a scan and
+ // if the plugin's filename has changed then the disabled state won't have
+ // been carried across, send out notifications for anything that has
+ // changed (see bug 830267).
+ let changedWrappers = [];
+ for (let id of matchedIDs) {
+ let oldWrapper = this.buildWrapper(this.plugins[id]);
+ let newWrapper = this.buildWrapper(newList[id]);
+
+ if (newWrapper.isActive != oldWrapper.isActive) {
+ AddonManagerPrivate.callAddonListeners(newWrapper.isActive ?
+ "onEnabling" : "onDisabling",
+ newWrapper, false);
+ changedWrappers.push(newWrapper);
+ }
+ }
+
+ // Notify about new installs
+ for (let plugin of newPlugins) {
+ AddonManagerPrivate.callInstallListeners("onExternalInstall", null,
+ plugin, null, false);
+ AddonManagerPrivate.callAddonListeners("onInstalling", plugin, false);
+ }
+
+ // Notify for any plugins that have vanished.
+ for (let plugin of lostPlugins)
+ AddonManagerPrivate.callAddonListeners("onUninstalling", plugin, false);
+
+ this.plugins = newList;
+
+ // Signal that new installs are complete
+ for (let plugin of newPlugins)
+ AddonManagerPrivate.callAddonListeners("onInstalled", plugin);
+
+ // Signal that enables/disables are complete
+ for (let wrapper of changedWrappers) {
+ AddonManagerPrivate.callAddonListeners(wrapper.isActive ?
+ "onEnabled" : "onDisabled",
+ wrapper);
+ }
+
+ // Signal that uninstalls are complete
+ for (let plugin of lostPlugins)
+ AddonManagerPrivate.callAddonListeners("onUninstalled", plugin);
+ }
+};
+
+function isFlashPlugin(aPlugin) {
+ for (let type of aPlugin.pluginMimeTypes) {
+ if (type.type == FLASH_MIME_TYPE) {
+ return true;
+ }
+ }
+ return false;
+}
+// Protected mode is win32-only, not win64
+function canDisableFlashProtectedMode(aPlugin) {
+ return isFlashPlugin(aPlugin) && Services.appinfo.XPCOMABI == "x86-msvc";
+}
+
+/**
+ * The PluginWrapper wraps a set of nsIPluginTags to provide the data visible to
+ * public callers through the API.
+ */
+function PluginWrapper(aId, aName, aDescription, aTags) {
+ let safedesc = aDescription.replace(/<\/?[a-z][^>]*>/gi, " ");
+ let homepageURL = null;
+ if (/<A\s+HREF=[^>]*>/i.test(aDescription))
+ homepageURL = /<A\s+HREF=["']?([^>"'\s]*)/i.exec(aDescription)[1];
+
+ this.__defineGetter__("id", function() aId);
+ this.__defineGetter__("type", function() "plugin");
+ this.__defineGetter__("name", function() aName);
+ this.__defineGetter__("creator", function() null);
+ this.__defineGetter__("description", function() safedesc);
+ this.__defineGetter__("version", function() aTags[0].version);
+ this.__defineGetter__("homepageURL", function() homepageURL);
+
+ this.__defineGetter__("isActive", function() !aTags[0].blocklisted && !aTags[0].disabled);
+ this.__defineGetter__("appDisabled", function() aTags[0].blocklisted);
+
+ this.__defineGetter__("userDisabled", function() {
+ if (aTags[0].disabled)
+ return true;
+
+ if ((Services.prefs.getBoolPref("plugins.click_to_play") && aTags[0].clicktoplay) ||
+ this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE ||
+ this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE)
+ return AddonManager.STATE_ASK_TO_ACTIVATE;
+
+ return false;
+ });
+
+ this.__defineSetter__("userDisabled", function(aVal) {
+ let previousVal = this.userDisabled;
+ if (aVal === previousVal)
+ return aVal;
+
+ for (let tag of aTags) {
+ if (aVal === true)
+ tag.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
+ else if (aVal === false)
+ tag.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ else if (aVal == AddonManager.STATE_ASK_TO_ACTIVATE)
+ tag.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ }
+
+ // If 'userDisabled' was 'true' and we're going to a state that's not
+ // that, we're enabling, so call those listeners.
+ if (previousVal === true && aVal !== true) {
+ AddonManagerPrivate.callAddonListeners("onEnabling", this, false);
+ AddonManagerPrivate.callAddonListeners("onEnabled", this);
+ }
+
+ // If 'userDisabled' was not 'true' and we're going to a state where
+ // it is, we're disabling, so call those listeners.
+ if (previousVal !== true && aVal === true) {
+ AddonManagerPrivate.callAddonListeners("onDisabling", this, false);
+ AddonManagerPrivate.callAddonListeners("onDisabled", this);
+ }
+
+ // If the 'userDisabled' value involved AddonManager.STATE_ASK_TO_ACTIVATE,
+ // call the onPropertyChanged listeners.
+ if (previousVal == AddonManager.STATE_ASK_TO_ACTIVATE ||
+ aVal == AddonManager.STATE_ASK_TO_ACTIVATE) {
+ AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["userDisabled"]);
+ }
+
+ return aVal;
+ });
+
+
+ this.__defineGetter__("blocklistState", function() {
+ let bs = Cc["@mozilla.org/extensions/blocklist;1"].
+ getService(Ci.nsIBlocklistService);
+ return bs.getPluginBlocklistState(aTags[0]);
+ });
+
+ this.__defineGetter__("blocklistURL", function() {
+ let bs = Cc["@mozilla.org/extensions/blocklist;1"].
+ getService(Ci.nsIBlocklistService);
+ return bs.getPluginBlocklistURL(aTags[0]);
+ });
+
+ this.__defineGetter__("size", function() {
+ function getDirectorySize(aFile) {
+ let size = 0;
+ let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+ let entry;
+ while ((entry = entries.nextFile)) {
+ if (entry.isSymlink() || !entry.isDirectory())
+ size += entry.fileSize;
+ else
+ size += getDirectorySize(entry);
+ }
+ entries.close();
+ return size;
+ }
+
+ let size = 0;
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ for (let tag of aTags) {
+ file.initWithPath(tag.fullpath);
+ if (file.isDirectory())
+ size += getDirectorySize(file);
+ else
+ size += file.fileSize;
+ }
+ return size;
+ });
+
+ this.__defineGetter__("pluginLibraries", function() {
+ let libs = [];
+ for (let tag of aTags)
+ libs.push(tag.filename);
+ return libs;
+ });
+
+ this.__defineGetter__("pluginFullpath", function() {
+ let paths = [];
+ for (let tag of aTags)
+ paths.push(tag.fullpath);
+ return paths;
+ })
+
+ this.__defineGetter__("pluginMimeTypes", function() {
+ let types = [];
+ for (let tag of aTags) {
+ let mimeTypes = tag.getMimeTypes({});
+ let mimeDescriptions = tag.getMimeDescriptions({});
+ let extensions = tag.getExtensions({});
+ for (let i = 0; i < mimeTypes.length; i++) {
+ let type = {};
+ type.type = mimeTypes[i];
+ type.description = mimeDescriptions[i];
+ type.suffixes = extensions[i];
+
+ types.push(type);
+ }
+ }
+ return types;
+ });
+
+ this.__defineGetter__("installDate", function() {
+ let date = 0;
+ for (let tag of aTags) {
+ date = Math.max(date, tag.lastModifiedTime);
+ }
+ return new Date(date);
+ });
+
+ this.__defineGetter__("scope", function() {
+ let path = aTags[0].fullpath;
+ // Plugins inside the application directory are in the application scope
+ let dir = Services.dirsvc.get("APlugns", Ci.nsIFile);
+ if (path.startsWith(dir.path))
+ return AddonManager.SCOPE_APPLICATION;
+
+ // Plugins inside the profile directory are in the profile scope
+ dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ if (path.startsWith(dir.path))
+ return AddonManager.SCOPE_PROFILE;
+
+ // Plugins anywhere else in the user's home are in the user scope,
+ // but not all platforms have a home directory.
+ try {
+ dir = Services.dirsvc.get("Home", Ci.nsIFile);
+ if (path.startsWith(dir.path))
+ return AddonManager.SCOPE_USER;
+ } catch (e if (e.result && e.result == Components.results.NS_ERROR_FAILURE)) {
+ // Do nothing: missing "Home".
+ }
+
+ // Any other locations are system scope
+ return AddonManager.SCOPE_SYSTEM;
+ });
+
+ this.__defineGetter__("pendingOperations", function() {
+ return AddonManager.PENDING_NONE;
+ });
+
+ this.__defineGetter__("operationsRequiringRestart", function() {
+ return AddonManager.OP_NEEDS_RESTART_NONE;
+ });
+
+ this.__defineGetter__("permissions", function() {
+ let permissions = 0;
+ if (aTags[0].isEnabledStateLocked) {
+ return permissions;
+ }
+ if (!this.appDisabled) {
+
+ if (this.userDisabled !== true)
+ permissions |= AddonManager.PERM_CAN_DISABLE;
+
+ let blocklistState = this.blocklistState;
+ let isCTPBlocklisted =
+ (blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE ||
+ blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);
+
+ if (this.userDisabled !== AddonManager.STATE_ASK_TO_ACTIVATE &&
+ (Services.prefs.getBoolPref("plugins.click_to_play") ||
+ isCTPBlocklisted)) {
+ permissions |= AddonManager.PERM_CAN_ASK_TO_ACTIVATE;
+ }
+
+ if (this.userDisabled !== false && !isCTPBlocklisted) {
+ permissions |= AddonManager.PERM_CAN_ENABLE;
+ }
+ }
+ return permissions;
+ });
+
+ this.__defineGetter__("optionsType", function() {
+ if (canDisableFlashProtectedMode(this)) {
+ return AddonManager.OPTIONS_TYPE_INLINE;
+ }
+ return AddonManager.OPTIONS_TYPE_INLINE_INFO;
+ });
+}
+
+PluginWrapper.prototype = {
+ optionsURL: "chrome://mozapps/content/extensions/pluginPrefs.xul",
+
+ get updateDate() {
+ return this.installDate;
+ },
+
+ get isCompatible() {
+ return true;
+ },
+
+ get isPlatformCompatible() {
+ return true;
+ },
+
+ get providesUpdatesSecurely() {
+ return true;
+ },
+
+ get foreignInstall() {
+ return true;
+ },
+
+ isCompatibleWith: function(aAppVerison, aPlatformVersion) {
+ return true;
+ },
+
+ findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
+ if ("onNoCompatibilityUpdateAvailable" in aListener)
+ aListener.onNoCompatibilityUpdateAvailable(this);
+ if ("onNoUpdateAvailable" in aListener)
+ aListener.onNoUpdateAvailable(this);
+ if ("onUpdateFinished" in aListener)
+ aListener.onUpdateFinished(this);
+ }
+};
+
+AddonManagerPrivate.registerProvider(PluginProvider, [
+ new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
+ STRING_TYPE_NAME,
+ AddonManager.VIEW_TYPE_LIST, 6000,
+ AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
+]);
diff --git a/components/addons/src/SpellCheckDictionaryBootstrap.js b/components/addons/src/SpellCheckDictionaryBootstrap.js
new file mode 100644
index 000000000..f4f557fc2
--- /dev/null
+++ b/components/addons/src/SpellCheckDictionaryBootstrap.js
@@ -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/. */
+
+var hunspell, dir;
+
+function startup(data) {
+ hunspell = Components.classes["@mozilla.org/spellchecker/engine;1"]
+ .getService(Components.interfaces.mozISpellCheckingEngine);
+ dir = data.installPath.clone();
+ dir.append("dictionaries");
+ hunspell.addDirectory(dir);
+}
+
+function shutdown() {
+ hunspell.removeDirectory(dir);
+}
diff --git a/components/addons/src/XPIProvider.jsm b/components/addons/src/XPIProvider.jsm
new file mode 100644
index 000000000..e1ecfedcb
--- /dev/null
+++ b/components/addons/src/XPIProvider.jsm
@@ -0,0 +1,7732 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cr = Components.results;
+const Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = ["XPIProvider"];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/AddonManager.jsm");
+Components.utils.import("resource://gre/modules/Preferences.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
+ "resource://gre/modules/addons/AddonRepository.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ChromeManifestParser",
+ "resource://gre/modules/ChromeManifestParser.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
+ "resource://gre/modules/LightweightThemeManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils",
+ "resource://gre/modules/ZipUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
+ "resource://gre/modules/PermissionsUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+#ifdef MOZ_DEVTOOLS
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserToolboxProcess",
+ "resource://devtools/client/framework/ToolboxProcess.jsm");
+#endif
+XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI",
+ "resource://gre/modules/Console.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "Blocklist",
+ "@mozilla.org/extensions/blocklist;1",
+ Ci.nsIBlocklistService);
+XPCOMUtils.defineLazyServiceGetter(this,
+ "ChromeRegistry",
+ "@mozilla.org/chrome/chrome-registry;1",
+ "nsIChromeRegistry");
+XPCOMUtils.defineLazyServiceGetter(this,
+ "ResProtocolHandler",
+ "@mozilla.org/network/protocol;1?name=resource",
+ "nsIResProtocolHandler");
+XPCOMUtils.defineLazyServiceGetter(this,
+ "AddonPathService",
+ "@mozilla.org/addon-path-service;1",
+ "amIAddonPathService");
+
+const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
+ "initWithPath");
+
+const PREF_DB_SCHEMA = "extensions.databaseSchema";
+const PREF_INSTALL_CACHE = "extensions.installCache";
+const PREF_XPI_STATE = "extensions.xpiState";
+const PREF_BOOTSTRAP_ADDONS = "extensions.bootstrappedAddons";
+const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+const PREF_EM_DSS_ENABLED = "extensions.dss.enabled";
+const PREF_DSS_SWITCHPENDING = "extensions.dss.switchPending";
+const PREF_DSS_SKIN_TO_SELECT = "extensions.lastSelectedSkin";
+const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
+const PREF_EM_UPDATE_URL = "extensions.update.url";
+const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons";
+const PREF_EM_EXTENSION_FORMAT = "extensions.";
+const PREF_EM_ENABLED_SCOPES = "extensions.enabledScopes";
+const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes";
+const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI";
+const PREF_XPI_ENABLED = "xpinstall.enabled";
+const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required";
+const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest";
+const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest";
+const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall.";
+const PREF_XPI_UNPACK = "extensions.alwaysUnpack";
+const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
+const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
+const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons";
+const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon.";
+const PREF_SHOWN_SELECTION_UI = "extensions.shownSelectionUI";
+const PREF_INTERPOSITION_ENABLED = "extensions.interposition.enabled";
+
+const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion";
+const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
+
+const URI_EXTENSION_SELECT_DIALOG = "chrome://mozapps/content/extensions/selectAddons.xul";
+const URI_EXTENSION_UPDATE_DIALOG = "chrome://mozapps/content/extensions/update.xul";
+const URI_EXTENSION_STRINGS = "chrome://mozapps/locale/extensions/extensions.properties";
+
+const STRING_TYPE_NAME = "type.%ID%.name";
+
+const DIR_EXTENSIONS = "extensions";
+const DIR_STAGE = "staged";
+const DIR_XPI_STAGE = "staged-xpis";
+const DIR_TRASH = "trash";
+
+const FILE_DATABASE = "extensions.json";
+const FILE_OLD_CACHE = "extensions.cache";
+const FILE_INSTALL_MANIFEST = "install.rdf";
+#ifndef MOZ_JETPACK
+const FILE_JETPACK_MANIFEST_1 = "harness-options.json";
+const FILE_JETPACK_MANIFEST_2 = "package.json";
+#endif
+const FILE_WEBEXT_MANIFEST = "manifest.json";
+const FILE_XPI_ADDONS_LIST = "extensions.ini";
+
+const KEY_PROFILEDIR = "ProfD";
+const KEY_APPDIR = "XCurProcD";
+const KEY_TEMPDIR = "TmpD";
+const KEY_APP_DISTRIBUTION = "XREAppDist";
+
+const KEY_APP_PROFILE = "app-profile";
+const KEY_APP_GLOBAL = "app-global";
+const KEY_APP_SYSTEM_LOCAL = "app-system-local";
+const KEY_APP_SYSTEM_SHARE = "app-system-share";
+const KEY_APP_SYSTEM_USER = "app-system-user";
+
+const NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions";
+const XPI_PERMISSION = "install";
+
+const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest";
+const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
+
+const TOOLKIT_ID = "toolkit@mozilla.org";
+
+#ifdef MC_APP_ID
+#expand const ALT_APP_ID = "__MC_APP_ID__";
+#endif
+
+// The value for this is in Makefile.in
+#expand const DB_SCHEMA = __MOZ_EXTENSIONS_DB_SCHEMA__;
+XPCOMUtils.defineConstant(this, "DB_SCHEMA", DB_SCHEMA);
+#ifdef MOZ_DEVTOOLS
+const NOTIFICATION_TOOLBOXPROCESS_LOADED = "ToolboxProcessLoaded";
+#endif
+
+// Properties that exist in the install manifest
+const PROP_METADATA = ["id", "version", "type", "internalName", "updateURL",
+ "updateKey", "optionsURL", "optionsType", "aboutURL",
+ "iconURL", "icon64URL"];
+const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
+const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
+const PROP_TARGETAPP = ["id", "minVersion", "maxVersion"];
+
+// Properties that should be migrated where possible from an old database. These
+// shouldn't include properties that can be read directly from install.rdf files
+// or calculated
+const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled",
+ "sourceURI", "applyBackgroundUpdates",
+ "releaseNotesURI", "foreignInstall", "syncGUID"];
+// Properties to cache and reload when an addon installation is pending
+const PENDING_INSTALL_METADATA =
+ ["syncGUID", "targetApplications", "userDisabled", "softDisabled",
+ "existingAddonID", "sourceURI", "releaseNotesURI", "installDate",
+ "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"];
+
+// Note: When adding/changing/removing items here, remember to change the
+// DB schema version to ensure changes are picked up ASAP.
+const STATIC_BLOCKLIST_PATTERNS = [
+ { creator: "Mozilla Corp.",
+ level: Blocklist.STATE_BLOCKED,
+ blockID: "i162" },
+ { creator: "Mozilla.org",
+ level: Blocklist.STATE_BLOCKED,
+ blockID: "i162" }
+];
+
+
+const BOOTSTRAP_REASONS = {
+ APP_STARTUP : 1,
+ APP_SHUTDOWN : 2,
+ ADDON_ENABLE : 3,
+ ADDON_DISABLE : 4,
+ ADDON_INSTALL : 5,
+ ADDON_UNINSTALL : 6,
+ ADDON_UPGRADE : 7,
+ ADDON_DOWNGRADE : 8
+};
+
+// Map new string type identifiers to old style nsIUpdateItem types
+const TYPES = {
+//app : 1,
+ extension : 2,
+ theme : 4,
+ locale : 8,
+ multipackage : 32,
+ dictionary : 64,
+//experiment : 128,
+//apiextension : 256,
+};
+
+const RESTARTLESS_TYPES = new Set([
+ "dictionary",
+ "locale",
+]);
+
+// Keep track of where we are in startup.
+// event happened during XPIDatabase.startup()
+const XPI_STARTING = "XPIStarting";
+// event happened after startup() but before the final-ui-startup event
+const XPI_BEFORE_UI_STARTUP = "BeforeFinalUIStartup";
+// event happened after final-ui-startup
+const XPI_AFTER_UI_STARTUP = "AfterFinalUIStartup";
+
+const COMPATIBLE_BY_DEFAULT_TYPES = {
+ extension: true,
+ dictionary: true
+};
+
+const MSG_JAR_FLUSH = "AddonJarFlush";
+const MSG_MESSAGE_MANAGER_CACHES_FLUSH = "AddonMessageManagerCachesFlush";
+
+var gGlobalScope = this;
+
+/**
+ * Valid IDs fit this pattern.
+ */
+var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
+
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.xpi";
+
+// Create a new logger for use by all objects in this Addons XPI Provider module
+// (Requires AddonManager.jsm)
+var logger = Log.repository.getLogger(LOGGER_ID);
+
+const LAZY_OBJECTS = ["XPIDatabase"];
+
+var gLazyObjectsLoaded = false;
+
+function loadLazyObjects() {
+ let scope = {};
+ scope.AddonInternal = AddonInternal;
+ scope.XPIProvider = XPIProvider;
+ scope.XPIStates = XPIStates;
+ Services.scriptloader.loadSubScript("resource://gre/modules/addons/XPIProviderUtils.js",
+ scope);
+
+ for (let name of LAZY_OBJECTS) {
+ delete gGlobalScope[name];
+ gGlobalScope[name] = scope[name];
+ }
+ gLazyObjectsLoaded = true;
+ return scope;
+}
+
+for (let name of LAZY_OBJECTS) {
+ Object.defineProperty(gGlobalScope, name, {
+ get: function lazyObjectGetter() {
+ let objs = loadLazyObjects();
+ return objs[name];
+ },
+ configurable: true
+ });
+}
+
+
+function findMatchingStaticBlocklistItem(aAddon) {
+ for (let item of STATIC_BLOCKLIST_PATTERNS) {
+ if ("creator" in item && typeof item.creator == "string") {
+ if ((aAddon.defaultLocale && aAddon.defaultLocale.creator == item.creator) ||
+ (aAddon.selectedLocale && aAddon.selectedLocale.creator == item.creator)) {
+ return item;
+ }
+ }
+ }
+ return null;
+}
+
+
+/**
+ * Sets permissions on a file
+ *
+ * @param aFile
+ * The file or directory to operate on.
+ * @param aPermissions
+ * The permisions to set
+ */
+function setFilePermissions(aFile, aPermissions) {
+ try {
+ aFile.permissions = aPermissions;
+ }
+ catch (e) {
+ logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " +
+ aFile.path, e);
+ }
+}
+
+/**
+ * A safe way to install a file or the contents of a directory to a new
+ * directory. The file or directory is moved or copied recursively and if
+ * anything fails an attempt is made to rollback the entire operation. The
+ * operation may also be rolled back to its original state after it has
+ * completed by calling the rollback method.
+ *
+ * Operations can be chained. Calling move or copy multiple times will remember
+ * the whole set and if one fails all of the operations will be rolled back.
+ */
+function SafeInstallOperation() {
+ this._installedFiles = [];
+ this._createdDirs = [];
+}
+
+SafeInstallOperation.prototype = {
+ _installedFiles: null,
+ _createdDirs: null,
+
+ _installFile: function SIO_installFile(aFile, aTargetDirectory, aCopy) {
+ let oldFile = aCopy ? null : aFile.clone();
+ let newFile = aFile.clone();
+ try {
+ if (aCopy)
+ newFile.copyTo(aTargetDirectory, null);
+ else
+ newFile.moveTo(aTargetDirectory, null);
+ }
+ catch (e) {
+ logger.error("Failed to " + (aCopy ? "copy" : "move") + " file " + aFile.path +
+ " to " + aTargetDirectory.path, e);
+ throw e;
+ }
+ this._installedFiles.push({ oldFile: oldFile, newFile: newFile });
+ },
+
+ _installDirectory: function SIO_installDirectory(aDirectory, aTargetDirectory, aCopy) {
+ let newDir = aTargetDirectory.clone();
+ newDir.append(aDirectory.leafName);
+ try {
+ newDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ }
+ catch (e) {
+ logger.error("Failed to create directory " + newDir.path, e);
+ throw e;
+ }
+ this._createdDirs.push(newDir);
+
+ // Use a snapshot of the directory contents to avoid possible issues with
+ // iterating over a directory while removing files from it (the YAFFS2
+ // embedded filesystem has this issue, see bug 772238), and to remove
+ // normal files before their resource forks on OSX (see bug 733436).
+ let entries = getDirectoryEntries(aDirectory, true);
+ entries.forEach(function(aEntry) {
+ try {
+ this._installDirEntry(aEntry, newDir, aCopy);
+ }
+ catch (e) {
+ logger.error("Failed to " + (aCopy ? "copy" : "move") + " entry " +
+ aEntry.path, e);
+ throw e;
+ }
+ }, this);
+
+ // If this is only a copy operation then there is nothing else to do
+ if (aCopy)
+ return;
+
+ // The directory should be empty by this point. If it isn't this will throw
+ // and all of the operations will be rolled back
+ try {
+ setFilePermissions(aDirectory, FileUtils.PERMS_DIRECTORY);
+ aDirectory.remove(false);
+ }
+ catch (e) {
+ logger.error("Failed to remove directory " + aDirectory.path, e);
+ throw e;
+ }
+
+ // Note we put the directory move in after all the file moves so the
+ // directory is recreated before all the files are moved back
+ this._installedFiles.push({ oldFile: aDirectory, newFile: newDir });
+ },
+
+ _installDirEntry: function SIO_installDirEntry(aDirEntry, aTargetDirectory, aCopy) {
+ let isDir = null;
+
+ try {
+ isDir = aDirEntry.isDirectory();
+ }
+ catch (e) {
+ // If the file has already gone away then don't worry about it, this can
+ // happen on OSX where the resource fork is automatically moved with the
+ // data fork for the file. See bug 733436.
+ if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ return;
+
+ logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
+ " to " + aTargetDirectory.path);
+ throw e;
+ }
+
+ try {
+ if (isDir)
+ this._installDirectory(aDirEntry, aTargetDirectory, aCopy);
+ else
+ this._installFile(aDirEntry, aTargetDirectory, aCopy);
+ }
+ catch (e) {
+ logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
+ " to " + aTargetDirectory.path);
+ throw e;
+ }
+ },
+
+ /**
+ * Moves a file or directory into a new directory. If an error occurs then all
+ * files that have been moved will be moved back to their original location.
+ *
+ * @param aFile
+ * The file or directory to be moved.
+ * @param aTargetDirectory
+ * The directory to move into, this is expected to be an empty
+ * directory.
+ */
+ moveUnder: function SIO_move(aFile, aTargetDirectory) {
+ try {
+ this._installDirEntry(aFile, aTargetDirectory, false);
+ }
+ catch (e) {
+ this.rollback();
+ throw e;
+ }
+ },
+
+ /**
+ * Renames a file to a new location. If an error occurs then all
+ * files that have been moved will be moved back to their original location.
+ *
+ * @param aOldLocation
+ * The old location of the file.
+ * @param aNewLocation
+ * The new location of the file.
+ */
+ moveTo: function(aOldLocation, aNewLocation) {
+ try {
+ let oldFile = aOldLocation.clone(), newFile = aNewLocation.clone();
+ oldFile.moveTo(newFile.parent, newFile.leafName);
+ this._installedFiles.push({ oldFile: oldFile, newFile: newFile, isMoveTo: true});
+ }
+ catch(e) {
+ this.rollback();
+ throw e;
+ }
+ },
+
+ /**
+ * Copies a file or directory into a new directory. If an error occurs then
+ * all new files that have been created will be removed.
+ *
+ * @param aFile
+ * The file or directory to be copied.
+ * @param aTargetDirectory
+ * The directory to copy into, this is expected to be an empty
+ * directory.
+ */
+ copy: function SIO_copy(aFile, aTargetDirectory) {
+ try {
+ this._installDirEntry(aFile, aTargetDirectory, true);
+ }
+ catch (e) {
+ this.rollback();
+ throw e;
+ }
+ },
+
+ /**
+ * Rolls back all the moves that this operation performed. If an exception
+ * occurs here then both old and new directories are left in an indeterminate
+ * state
+ */
+ rollback: function SIO_rollback() {
+ while (this._installedFiles.length > 0) {
+ let move = this._installedFiles.pop();
+ if (move.isMoveTo) {
+ move.newFile.moveTo(oldDir.parent, oldDir.leafName);
+ }
+ else if (move.newFile.isDirectory()) {
+ let oldDir = move.oldFile.parent.clone();
+ oldDir.append(move.oldFile.leafName);
+ oldDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ }
+ else if (!move.oldFile) {
+ // No old file means this was a copied file
+ move.newFile.remove(true);
+ }
+ else {
+ move.newFile.moveTo(move.oldFile.parent, null);
+ }
+ }
+
+ while (this._createdDirs.length > 0)
+ recursiveRemove(this._createdDirs.pop());
+ }
+};
+
+/**
+ * Gets the currently selected locale for display.
+ * @return the selected locale or "en-US" if none is selected
+ */
+function getLocale() {
+ if (Preferences.get(PREF_MATCH_OS_LOCALE, false))
+ return Services.locale.getLocaleComponentForUserAgent();
+ try {
+ let locale = Preferences.get(PREF_SELECTED_LOCALE, null, Ci.nsIPrefLocalizedString);
+ if (locale)
+ return locale;
+ }
+ catch (e) {}
+ return Preferences.get(PREF_SELECTED_LOCALE, "en-US");
+}
+
+/**
+ * Selects the closest matching locale from a list of locales.
+ *
+ * @param aLocales
+ * An array of locales
+ * @return the best match for the currently selected locale
+ */
+function findClosestLocale(aLocales) {
+ let appLocale = getLocale();
+
+ // Holds the best matching localized resource
+ var bestmatch = null;
+ // The number of locale parts it matched with
+ var bestmatchcount = 0;
+ // The number of locale parts in the match
+ var bestpartcount = 0;
+
+ var matchLocales = [appLocale.toLowerCase()];
+ /* If the current locale is English then it will find a match if there is
+ a valid match for en-US so no point searching that locale too. */
+ if (matchLocales[0].substring(0, 3) != "en-")
+ matchLocales.push("en-us");
+
+ for each (var locale in matchLocales) {
+ var lparts = locale.split("-");
+ for each (var localized in aLocales) {
+ for each (let found in localized.locales) {
+ found = found.toLowerCase();
+ // Exact match is returned immediately
+ if (locale == found)
+ return localized;
+
+ var fparts = found.split("-");
+ /* If we have found a possible match and this one isn't any longer
+ then we dont need to check further. */
+ if (bestmatch && fparts.length < bestmatchcount)
+ continue;
+
+ // Count the number of parts that match
+ var maxmatchcount = Math.min(fparts.length, lparts.length);
+ var matchcount = 0;
+ while (matchcount < maxmatchcount &&
+ fparts[matchcount] == lparts[matchcount])
+ matchcount++;
+
+ /* If we matched more than the last best match or matched the same and
+ this locale is less specific than the last best match. */
+ if (matchcount > bestmatchcount ||
+ (matchcount == bestmatchcount && fparts.length < bestpartcount)) {
+ bestmatch = localized;
+ bestmatchcount = matchcount;
+ bestpartcount = fparts.length;
+ }
+ }
+ }
+ // If we found a valid match for this locale return it
+ if (bestmatch)
+ return bestmatch;
+ }
+ return null;
+}
+
+/**
+ * Sets the userDisabled and softDisabled properties of an add-on based on what
+ * values those properties had for a previous instance of the add-on. The
+ * previous instance may be a previous install or in the case of an application
+ * version change the same add-on.
+ *
+ * NOTE: this may modify aNewAddon in place; callers should save the database if
+ * necessary
+ *
+ * @param aOldAddon
+ * The previous instance of the add-on
+ * @param aNewAddon
+ * The new instance of the add-on
+ * @param aAppVersion
+ * The optional application version to use when checking the blocklist
+ * or undefined to use the current application
+ * @param aPlatformVersion
+ * The optional platform version to use when checking the blocklist or
+ * undefined to use the current platform
+ */
+function applyBlocklistChanges(aOldAddon, aNewAddon, aOldAppVersion,
+ aOldPlatformVersion) {
+ // Copy the properties by default
+ aNewAddon.userDisabled = aOldAddon.userDisabled;
+ aNewAddon.softDisabled = aOldAddon.softDisabled;
+
+ let oldBlocklistState = Blocklist.getAddonBlocklistState(createWrapper(aOldAddon),
+ aOldAppVersion,
+ aOldPlatformVersion);
+ let newBlocklistState = Blocklist.getAddonBlocklistState(createWrapper(aNewAddon));
+
+ // If the blocklist state hasn't changed then the properties don't need to
+ // change
+ if (newBlocklistState == oldBlocklistState)
+ return;
+
+ if (newBlocklistState == Blocklist.STATE_SOFTBLOCKED) {
+ if (aNewAddon.type != "theme") {
+ // The add-on has become softblocked, set softDisabled if it isn't already
+ // userDisabled
+ aNewAddon.softDisabled = !aNewAddon.userDisabled;
+ }
+ else {
+ // Themes just get userDisabled to switch back to the default theme
+ aNewAddon.userDisabled = true;
+ }
+ }
+ else {
+ // If the new add-on is not softblocked then it cannot be softDisabled
+ aNewAddon.softDisabled = false;
+ }
+}
+
+/**
+ * Calculates whether an add-on should be appDisabled or not.
+ *
+ * @param aAddon
+ * The add-on to check
+ * @return true if the add-on should not be appDisabled
+ */
+function isUsableAddon(aAddon) {
+ // Hack to ensure the default theme is always usable
+ if (aAddon.type == "theme" && aAddon.internalName == XPIProvider.defaultSkin)
+ return true;
+
+ if (aAddon.blocklistState == Blocklist.STATE_BLOCKED)
+ return false;
+
+ if (AddonManager.checkUpdateSecurity && !aAddon.providesUpdatesSecurely)
+ return false;
+
+ if (!aAddon.isPlatformCompatible)
+ return false;
+
+ if (AddonManager.checkCompatibility) {
+ if (!aAddon.isCompatible)
+ return false;
+ }
+ else {
+ if (!aAddon.matchingTargetApplication)
+ return false;
+ }
+
+ return true;
+}
+
+XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1",
+ Ci.nsIRDFService);
+
+function EM_R(aProperty) {
+ return gRDF.GetResource(PREFIX_NS_EM + aProperty);
+}
+
+function createAddonDetails(id, aAddon) {
+ return {
+ id: id || aAddon.id,
+ type: aAddon.type,
+ version: aAddon.version
+ };
+}
+
+/**
+ * Converts an RDF literal, resource or integer into a string.
+ *
+ * @param aLiteral
+ * The RDF object to convert
+ * @return a string if the object could be converted or null
+ */
+function getRDFValue(aLiteral) {
+ if (aLiteral instanceof Ci.nsIRDFLiteral)
+ return aLiteral.Value;
+ if (aLiteral instanceof Ci.nsIRDFResource)
+ return aLiteral.Value;
+ if (aLiteral instanceof Ci.nsIRDFInt)
+ return aLiteral.Value;
+ return null;
+}
+
+/**
+ * Gets an RDF property as a string
+ *
+ * @param aDs
+ * The RDF datasource to read the property from
+ * @param aResource
+ * The RDF resource to read the property from
+ * @param aProperty
+ * The property to read
+ * @return a string if the property existed or null
+ */
+function getRDFProperty(aDs, aResource, aProperty) {
+ return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
+}
+
+/**
+ * Reads an AddonInternal object from an RDF stream.
+ *
+ * @param aUri
+ * The URI that the manifest is being read from
+ * @param aStream
+ * An open stream to read the RDF from
+ * @return an AddonInternal object
+ * @throws if the install manifest in the RDF stream is corrupt or could not
+ * be read
+ */
+function loadManifestFromRDF(aUri, aStream) {
+ function getPropertyArray(aDs, aSource, aProperty) {
+ let values = [];
+ let targets = aDs.GetTargets(aSource, EM_R(aProperty), true);
+ while (targets.hasMoreElements())
+ values.push(getRDFValue(targets.getNext()));
+
+ return values;
+ }
+
+ /**
+ * Reads locale properties from either the main install manifest root or
+ * an em:localized section in the install manifest.
+ *
+ * @param aDs
+ * The nsIRDFDatasource to read from
+ * @param aSource
+ * The nsIRDFResource to read the properties from
+ * @param isDefault
+ * True if the locale is to be read from the main install manifest
+ * root
+ * @param aSeenLocales
+ * An array of locale names already seen for this install manifest.
+ * Any locale names seen as a part of this function will be added to
+ * this array
+ * @return an object containing the locale properties
+ */
+ function readLocale(aDs, aSource, isDefault, aSeenLocales) {
+ let locale = { };
+ if (!isDefault) {
+ locale.locales = [];
+ let targets = ds.GetTargets(aSource, EM_R("locale"), true);
+ while (targets.hasMoreElements()) {
+ let localeName = getRDFValue(targets.getNext());
+ if (!localeName) {
+ logger.warn("Ignoring empty locale in localized properties");
+ continue;
+ }
+ if (aSeenLocales.indexOf(localeName) != -1) {
+ logger.warn("Ignoring duplicate locale in localized properties");
+ continue;
+ }
+ aSeenLocales.push(localeName);
+ locale.locales.push(localeName);
+ }
+
+ if (locale.locales.length == 0) {
+ logger.warn("Ignoring localized properties with no listed locales");
+ return null;
+ }
+ }
+
+ PROP_LOCALE_SINGLE.forEach(function(aProp) {
+ locale[aProp] = getRDFProperty(aDs, aSource, aProp);
+ });
+
+ PROP_LOCALE_MULTI.forEach(function(aProp) {
+ // Don't store empty arrays
+ let props = getPropertyArray(aDs, aSource,
+ aProp.substring(0, aProp.length - 1));
+ if (props.length > 0)
+ locale[aProp] = props;
+ });
+
+ return locale;
+ }
+
+ let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
+ createInstance(Ci.nsIRDFXMLParser)
+ let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
+ createInstance(Ci.nsIRDFDataSource);
+ let listener = rdfParser.parseAsync(ds, aUri);
+ let channel = Cc["@mozilla.org/network/input-stream-channel;1"].
+ createInstance(Ci.nsIInputStreamChannel);
+ channel.setURI(aUri);
+ channel.contentStream = aStream;
+ channel.QueryInterface(Ci.nsIChannel);
+ channel.contentType = "text/xml";
+
+ listener.onStartRequest(channel, null);
+
+ try {
+ let pos = 0;
+ let count = aStream.available();
+ while (count > 0) {
+ listener.onDataAvailable(channel, null, aStream, pos, count);
+ pos += count;
+ count = aStream.available();
+ }
+ listener.onStopRequest(channel, null, Components.results.NS_OK);
+ }
+ catch (e) {
+ listener.onStopRequest(channel, null, e.result);
+ throw e;
+ }
+
+ let root = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
+ let addon = new AddonInternal();
+ PROP_METADATA.forEach(function(aProp) {
+ addon[aProp] = getRDFProperty(ds, root, aProp);
+ });
+ addon.unpack = getRDFProperty(ds, root, "unpack") == "true";
+
+ if (!addon.type) {
+ addon.type = addon.internalName ? "theme" : "extension";
+ }
+ else {
+ let type = addon.type;
+ addon.type = null;
+ for (let name in TYPES) {
+ if (TYPES[name] == type) {
+ addon.type = name;
+ break;
+ }
+ }
+ }
+
+ if (!(addon.type in TYPES))
+ throw new Error("Install manifest specifies unknown type: " + addon.type);
+
+ if (addon.type != "multipackage") {
+ if (!addon.id)
+ throw new Error("No ID in install manifest");
+ if (!gIDTest.test(addon.id))
+ throw new Error("Illegal add-on ID " + addon.id);
+ if (!addon.version)
+ throw new Error("No version in install manifest");
+ }
+
+ addon.strictCompatibility = !(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
+ getRDFProperty(ds, root, "strictCompatibility") == "true";
+
+ // Only read these properties for extensions.
+ if (addon.type == "extension") {
+ addon.bootstrap = getRDFProperty(ds, root, "bootstrap") == "true";
+ addon.multiprocessCompatible = getRDFProperty(ds, root, "multiprocessCompatible") == "true";
+ if (addon.optionsType &&
+ addon.optionsType != AddonManager.OPTIONS_TYPE_DIALOG &&
+ addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE &&
+ addon.optionsType != AddonManager.OPTIONS_TYPE_TAB &&
+ addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_INFO) {
+ throw new Error("Install manifest specifies unknown type: " + addon.optionsType);
+ }
+ }
+ else {
+ // Some add-on types are always restartless.
+ if (RESTARTLESS_TYPES.has(addon.type)) {
+ addon.bootstrap = true;
+ }
+
+ // Only extensions are allowed to provide an optionsURL, optionsType or aboutURL. For
+ // all other types they are silently ignored
+ addon.optionsURL = null;
+ addon.optionsType = null;
+ addon.aboutURL = null;
+
+ if (addon.type == "theme") {
+ if (!addon.internalName)
+ throw new Error("Themes must include an internalName property");
+ addon.skinnable = getRDFProperty(ds, root, "skinnable") == "true";
+ }
+ }
+
+ addon.defaultLocale = readLocale(ds, root, true);
+
+ let seenLocales = [];
+ addon.locales = [];
+ let targets = ds.GetTargets(root, EM_R("localized"), true);
+ while (targets.hasMoreElements()) {
+ let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
+ let locale = readLocale(ds, target, false, seenLocales);
+ if (locale)
+ addon.locales.push(locale);
+ }
+
+ let seenApplications = [];
+ addon.targetApplications = [];
+ targets = ds.GetTargets(root, EM_R("targetApplication"), true);
+ while (targets.hasMoreElements()) {
+ let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
+ let targetAppInfo = {};
+ PROP_TARGETAPP.forEach(function(aProp) {
+ targetAppInfo[aProp] = getRDFProperty(ds, target, aProp);
+ });
+ if (!targetAppInfo.id || !targetAppInfo.minVersion ||
+ !targetAppInfo.maxVersion) {
+ logger.warn("Ignoring invalid targetApplication entry in install manifest");
+ continue;
+ }
+ if (seenApplications.indexOf(targetAppInfo.id) != -1) {
+ logger.warn("Ignoring duplicate targetApplication entry for " + targetAppInfo.id +
+ " in install manifest");
+ continue;
+ }
+ seenApplications.push(targetAppInfo.id);
+ addon.targetApplications.push(targetAppInfo);
+ }
+
+ // Note that we don't need to check for duplicate targetPlatform entries since
+ // the RDF service coalesces them for us.
+ let targetPlatforms = getPropertyArray(ds, root, "targetPlatform");
+ addon.targetPlatforms = [];
+ targetPlatforms.forEach(function(aPlatform) {
+ let platform = {
+ os: null,
+ abi: null
+ };
+
+ let pos = aPlatform.indexOf("_");
+ if (pos != -1) {
+ platform.os = aPlatform.substring(0, pos);
+ platform.abi = aPlatform.substring(pos + 1);
+ }
+ else {
+ platform.os = aPlatform;
+ }
+
+ addon.targetPlatforms.push(platform);
+ });
+
+ // A theme's userDisabled value is true if the theme is not the selected skin
+ // or if there is an active lightweight theme. We ignore whether softblocking
+ // is in effect since it would change the active theme.
+ if (addon.type == "theme") {
+ addon.userDisabled = !!LightweightThemeManager.currentTheme ||
+ addon.internalName != XPIProvider.selectedSkin;
+ }
+ else {
+ addon.userDisabled = false;
+ addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;
+ }
+
+ addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+
+ // Load the storage service before NSS (nsIRandomGenerator),
+ // to avoid a SQLite initialization error (bug 717904).
+ let storage = Services.storage;
+
+ // Generate random GUID used for Sync.
+ // This was lifted from util.js:makeGUID() from services-sync.
+ let guid = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator)
+ .generateUUID().toString();
+ addon.syncGUID = guid;
+
+ return addon;
+}
+
+/**
+ * Loads an AddonInternal object from an add-on extracted in a directory.
+ *
+ * @param aDir
+ * The nsIFile directory holding the add-on
+ * @return an AddonInternal object
+ * @throws if the directory does not contain a valid install manifest
+ */
+function loadManifestFromDir(aDir) {
+ function getFileSize(aFile) {
+ if (aFile.isSymlink())
+ return 0;
+
+ if (!aFile.isDirectory())
+ return aFile.fileSize;
+
+ let size = 0;
+ let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+ let entry;
+ while ((entry = entries.nextFile))
+ size += getFileSize(entry);
+ entries.close();
+ return size;
+ }
+
+ let file = aDir.clone();
+ file.append(FILE_INSTALL_MANIFEST);
+ if (!file.exists() || !file.isFile())
+ throw new Error("Directory " + aDir.path + " does not contain a valid " +
+ "install manifest");
+
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fis.init(file, -1, -1, false);
+ let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
+ createInstance(Ci.nsIBufferedInputStream);
+ bis.init(fis, 4096);
+
+ try {
+ let addon = loadManifestFromRDF(Services.io.newFileURI(file), bis);
+ addon._sourceBundle = aDir.clone();
+ addon.size = getFileSize(aDir);
+
+ file = aDir.clone();
+ file.append("chrome.manifest");
+ let chromeManifest = ChromeManifestParser.parseSync(Services.io.newFileURI(file));
+ addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
+ "binary-component");
+
+ addon.appDisabled = !isUsableAddon(addon);
+ return addon;
+ }
+ finally {
+ bis.close();
+ fis.close();
+ }
+}
+
+/**
+ * Loads an AddonInternal object from an nsIZipReader for an add-on.
+ *
+ * @param aZipReader
+ * An open nsIZipReader for the add-on's files
+ * @return an AddonInternal object
+ * @throws if the XPI file does not contain a valid install manifest.
+ * Throws with |webext:true| if a WebExtension manifest was found
+ * to distinguish between WebExtensions and corrupt files.
+ * Throws with |jetpacksdk:true| if a Jetpack files were found
+ * if Jetpack its self isn't built.
+ */
+function loadManifestFromZipReader(aZipReader) {
+ // If WebExtension but not install.rdf throw an error
+ if (aZipReader.hasEntry(FILE_WEBEXT_MANIFEST)) {
+ if (!aZipReader.hasEntry(FILE_INSTALL_MANIFEST)) {
+ throw {
+ name: "UnsupportedExtension",
+ message: Services.appinfo.name + " does not support WebExtensions",
+ webext: true
+ };
+ }
+ }
+
+#ifndef MOZ_JETPACK
+ // If Jetpack is not built throw an error
+ if (aZipReader.hasEntry(FILE_JETPACK_MANIFEST_1) ||
+ aZipReader.hasEntry(FILE_JETPACK_MANIFEST_2)) {
+ throw {
+ name: "UnsupportedExtension",
+ message: Services.appinfo.name + " does not support Jetpack Extensions",
+ jetpacksdk: true
+ };
+ }
+#endif
+
+ // Attempt to open install.rdf else throw normally
+ let zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST);
+ // Create a buffered input stream for install.rdf
+ let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
+ createInstance(Ci.nsIBufferedInputStream);
+ bis.init(zis, 4096);
+
+ try {
+ let uri = buildJarURI(aZipReader.file, FILE_INSTALL_MANIFEST);
+ let addon = loadManifestFromRDF(uri, bis);
+ addon._sourceBundle = aZipReader.file;
+
+ addon.size = 0;
+ let entries = aZipReader.findEntries(null);
+ while (entries.hasMore())
+ addon.size += aZipReader.getEntry(entries.getNext()).realSize;
+
+ // Binary components can only be loaded from unpacked addons.
+ if (addon.unpack) {
+ uri = buildJarURI(aZipReader.file, "chrome.manifest");
+ let chromeManifest = ChromeManifestParser.parseSync(uri);
+ addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
+ "binary-component");
+ } else {
+ addon.hasBinaryComponents = false;
+ }
+
+ addon.appDisabled = !isUsableAddon(addon);
+ return addon;
+ }
+ finally {
+ // Close the buffered input stream
+ bis.close();
+ // Close the input stream to install.rdf
+ zis.close();
+ }
+}
+
+/**
+ * Loads an AddonInternal object from an add-on in an XPI file.
+ *
+ * @param aXPIFile
+ * An nsIFile pointing to the add-on's XPI file
+ * @return an AddonInternal object
+ * @throws if the XPI file does not contain a valid install manifest
+ */
+function loadManifestFromZipFile(aXPIFile) {
+ let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ try {
+ zipReader.open(aXPIFile);
+
+ return loadManifestFromZipReader(zipReader);
+ }
+ finally {
+ zipReader.close();
+ }
+}
+
+function loadManifestFromFile(aFile) {
+ if (aFile.isFile())
+ return loadManifestFromZipFile(aFile);
+ else
+ return loadManifestFromDir(aFile);
+}
+
+/**
+ * Gets an nsIURI for a file within another file, either a directory or an XPI
+ * file. If aFile is a directory then this will return a file: URI, if it is an
+ * XPI file then it will return a jar: URI.
+ *
+ * @param aFile
+ * The file containing the resources, must be either a directory or an
+ * XPI file
+ * @param aPath
+ * The path to find the resource at, "/" separated. If aPath is empty
+ * then the uri to the root of the contained files will be returned
+ * @return an nsIURI pointing at the resource
+ */
+function getURIForResourceInFile(aFile, aPath) {
+ if (aFile.isDirectory()) {
+ let resource = aFile.clone();
+ if (aPath) {
+ aPath.split("/").forEach(function(aPart) {
+ resource.append(aPart);
+ });
+ }
+ return NetUtil.newURI(resource);
+ }
+
+ return buildJarURI(aFile, aPath);
+}
+
+/**
+ * Creates a jar: URI for a file inside a ZIP file.
+ *
+ * @param aJarfile
+ * The ZIP file as an nsIFile
+ * @param aPath
+ * The path inside the ZIP file
+ * @return an nsIURI for the file
+ */
+function buildJarURI(aJarfile, aPath) {
+ let uri = Services.io.newFileURI(aJarfile);
+ uri = "jar:" + uri.spec + "!/" + aPath;
+ return NetUtil.newURI(uri);
+}
+
+/**
+ * Sends local and remote notifications to flush a JAR file cache entry
+ *
+ * @param aJarFile
+ * The ZIP/XPI/JAR file as a nsIFile
+ */
+function flushJarCache(aJarFile) {
+ Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null);
+ Services.mm.broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
+}
+
+function flushChromeCaches() {
+ // Init this, so it will get the notification.
+ Services.obs.notifyObservers(null, "startupcache-invalidate", null);
+ // Flush message manager cached scripts
+ Services.obs.notifyObservers(null, "message-manager-flush-caches", null);
+ // Also dispatch this event to child processes
+ Services.mm.broadcastAsyncMessage(MSG_MESSAGE_MANAGER_CACHES_FLUSH, null);
+}
+
+/**
+ * Creates and returns a new unique temporary file. The caller should delete
+ * the file when it is no longer needed.
+ *
+ * @return an nsIFile that points to a randomly named, initially empty file in
+ * the OS temporary files directory
+ */
+function getTemporaryFile() {
+ let file = FileUtils.getDir(KEY_TEMPDIR, []);
+ let random = Math.random().toString(36).replace(/0./, '').substr(-3);
+ file.append("tmp-" + random + ".xpi");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+
+ return file;
+}
+
+/**
+ * Verifies that a zip file's contents are all signed by the same principal.
+ * Directory entries and anything in the META-INF directory are not checked.
+ *
+ * @param aZip
+ * A nsIZipReader to check
+ * @param aCertificate
+ * The nsIX509Cert to compare against
+ * @return true if all the contents that should be signed were signed by the
+ * principal
+ */
+function verifyZipSigning(aZip, aCertificate) {
+ var count = 0;
+ var entries = aZip.findEntries(null);
+ while (entries.hasMore()) {
+ var entry = entries.getNext();
+ // Nothing in META-INF is in the manifest.
+ if (entry.substr(0, 9) == "META-INF/")
+ continue;
+ // Directory entries aren't in the manifest.
+ if (entry.substr(-1) == "/")
+ continue;
+ count++;
+ var entryCertificate = aZip.getSigningCert(entry);
+ if (!entryCertificate || !aCertificate.equals(entryCertificate)) {
+ return false;
+ }
+ }
+ return aZip.manifestEntriesCount == count;
+}
+
+/**
+ * Replaces %...% strings in an addon url (update and updateInfo) with
+ * appropriate values.
+ *
+ * @param aAddon
+ * The AddonInternal representing the add-on
+ * @param aUri
+ * The uri to escape
+ * @param aUpdateType
+ * An optional number representing the type of update, only applicable
+ * when creating a url for retrieving an update manifest
+ * @param aAppVersion
+ * The optional application version to use for %APP_VERSION%
+ * @return the appropriately escaped uri.
+ */
+function escapeAddonURI(aAddon, aUri, aUpdateType, aAppVersion)
+{
+ let uri = AddonManager.escapeAddonURI(aAddon, aUri, aAppVersion);
+
+ // If there is an updateType then replace the UPDATE_TYPE string
+ if (aUpdateType)
+ uri = uri.replace(/%UPDATE_TYPE%/g, aUpdateType);
+
+ // If this add-on has compatibility information for either the current
+ // application or toolkit then replace the ITEM_MAXAPPVERSION with the
+ // maxVersion
+ let app = aAddon.matchingTargetApplication;
+ if (app)
+ var maxVersion = app.maxVersion;
+ else
+ maxVersion = "";
+ uri = uri.replace(/%ITEM_MAXAPPVERSION%/g, maxVersion);
+
+ let compatMode = "normal";
+ if (!AddonManager.checkCompatibility)
+ compatMode = "ignore";
+ else if (AddonManager.strictCompatibility)
+ compatMode = "strict";
+ uri = uri.replace(/%COMPATIBILITY_MODE%/g, compatMode);
+
+ return uri;
+}
+
+function removeAsync(aFile) {
+ return Task.spawn(function () {
+ let info = null;
+ try {
+ info = yield OS.File.stat(aFile.path);
+ if (info.isDir)
+ yield OS.File.removeDir(aFile.path);
+ else
+ yield OS.File.remove(aFile.path);
+ }
+ catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
+ // The file has already gone away
+ return;
+ }
+ });
+}
+
+/**
+ * Recursively removes a directory or file fixing permissions when necessary.
+ *
+ * @param aFile
+ * The nsIFile to remove
+ */
+function recursiveRemove(aFile) {
+ let isDir = null;
+
+ try {
+ isDir = aFile.isDirectory();
+ }
+ catch (e) {
+ // If the file has already gone away then don't worry about it, this can
+ // happen on OSX where the resource fork is automatically moved with the
+ // data fork for the file. See bug 733436.
+ if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ return;
+ if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND)
+ return;
+
+ throw e;
+ }
+
+ setFilePermissions(aFile, isDir ? FileUtils.PERMS_DIRECTORY
+ : FileUtils.PERMS_FILE);
+
+ try {
+ aFile.remove(true);
+ return;
+ }
+ catch (e) {
+ if (!aFile.isDirectory()) {
+ logger.error("Failed to remove file " + aFile.path, e);
+ throw e;
+ }
+ }
+
+ // Use a snapshot of the directory contents to avoid possible issues with
+ // iterating over a directory while removing files from it (the YAFFS2
+ // embedded filesystem has this issue, see bug 772238), and to remove
+ // normal files before their resource forks on OSX (see bug 733436).
+ let entries = getDirectoryEntries(aFile, true);
+ entries.forEach(recursiveRemove);
+
+ try {
+ aFile.remove(true);
+ }
+ catch (e) {
+ logger.error("Failed to remove empty directory " + aFile.path, e);
+ throw e;
+ }
+}
+
+/**
+ * Returns the timestamp and leaf file name of the most recently modified
+ * entry in a directory,
+ * or simply the file's own timestamp if it is not a directory.
+ * Also returns the total number of items (directories and files) visited in the scan
+ *
+ * @param aFile
+ * A non-null nsIFile object
+ * @return [File Name, Epoch time, items visited], as described above.
+ */
+function recursiveLastModifiedTime(aFile) {
+ try {
+ let modTime = aFile.lastModifiedTime;
+ let fileName = aFile.leafName;
+ if (aFile.isFile())
+ return [fileName, modTime, 1];
+
+ if (aFile.isDirectory()) {
+ let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+ let entry;
+ let totalItems = 1;
+ while ((entry = entries.nextFile)) {
+ let [subName, subTime, items] = recursiveLastModifiedTime(entry);
+ totalItems += items;
+ if (subTime > modTime) {
+ modTime = subTime;
+ fileName = subName;
+ }
+ }
+ entries.close();
+ return [fileName, modTime, totalItems];
+ }
+ }
+ catch (e) {
+ logger.warn("Problem getting last modified time for " + aFile.path, e);
+ }
+
+ // If the file is something else, just ignore it.
+ return ["", 0, 0];
+}
+
+/**
+ * Gets a snapshot of directory entries.
+ *
+ * @param aDir
+ * Directory to look at
+ * @param aSortEntries
+ * True to sort entries by filename
+ * @return An array of nsIFile, or an empty array if aDir is not a readable directory
+ */
+function getDirectoryEntries(aDir, aSortEntries) {
+ let dirEnum;
+ try {
+ dirEnum = aDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+ let entries = [];
+ while (dirEnum.hasMoreElements())
+ entries.push(dirEnum.nextFile);
+
+ if (aSortEntries) {
+ entries.sort(function sortDirEntries(a, b) {
+ return a.path > b.path ? -1 : 1;
+ });
+ }
+
+ return entries
+ }
+ catch (e) {
+ logger.warn("Can't iterate directory " + aDir.path, e);
+ return [];
+ }
+ finally {
+ if (dirEnum) {
+ dirEnum.close();
+ }
+ }
+}
+
+/**
+ * Wraps a function in an exception handler to protect against exceptions inside callbacks
+ * @param aFunction function(args...)
+ * @return function(args...), a function that takes the same arguments as aFunction
+ * and returns the same result unless aFunction throws, in which case it logs
+ * a warning and returns undefined.
+ */
+function makeSafe(aFunction) {
+ return function(...aArgs) {
+ try {
+ return aFunction(...aArgs);
+ }
+ catch(ex) {
+ logger.warn("XPIProvider callback failed", ex);
+ }
+ return undefined;
+ }
+}
+
+/**
+ * The on-disk state of an individual XPI, created from an Object
+ * as stored in the 'extensions.xpiState' pref.
+ */
+function XPIState(saved) {
+ for (let [short, long] of XPIState.prototype.fields) {
+ if (short in saved) {
+ this[long] = saved[short];
+ }
+ }
+}
+
+XPIState.prototype = {
+ fields: [['d', 'descriptor'],
+ ['e', 'enabled'],
+ ['v', 'version'],
+ ['st', 'scanTime'],
+ ['mt', 'manifestTime']],
+ /**
+ * Return the last modified time, based on enabled/disabled
+ */
+ get mtime() {
+ if (!this.enabled && ('manifestTime' in this) && this.manifestTime > this.scanTime) {
+ return this.manifestTime;
+ }
+ return this.scanTime;
+ },
+
+ toJSON() {
+ let json = {};
+ for (let [short, long] of XPIState.prototype.fields) {
+ if (long in this) {
+ json[short] = this[long];
+ }
+ }
+ return json;
+ },
+
+ /**
+ * Update the last modified time for an add-on on disk.
+ * @param aFile: nsIFile path of the add-on.
+ * @param aId: The add-on ID.
+ * @return True if the time stamp has changed.
+ */
+ getModTime(aFile, aId) {
+ let changed = false;
+ let scanStarted = Cu.now();
+ // For an unknown or enabled add-on, we do a full recursive scan.
+ if (!('scanTime' in this) || this.enabled) {
+ logger.debug('getModTime: Recursive scan of ' + aId);
+ let [modFile, modTime, items] = recursiveLastModifiedTime(aFile);
+ XPIProvider._mostRecentlyModifiedFile[aId] = modFile;
+ if (modTime != this.scanTime) {
+ this.scanTime = modTime;
+ changed = true;
+ }
+ }
+ // if the add-on is disabled, modified time is the install.rdf time, if any.
+ // If {path}/install.rdf doesn't exist, we assume this is a packed .xpi and use
+ // the time stamp of {path}
+ try {
+ // Get the install.rdf update time, if any.
+ // XXX This will eventually also need to check for package.json or whatever
+ // the new manifest is named.
+ let maniFile = aFile.clone();
+ maniFile.append(FILE_INSTALL_MANIFEST);
+ if (!(aId in XPIProvider._mostRecentlyModifiedFile)) {
+ XPIProvider._mostRecentlyModifiedFile[aId] = maniFile.leafName;
+ }
+ let maniTime = maniFile.lastModifiedTime;
+ if (maniTime != this.manifestTime) {
+ this.manifestTime = maniTime;
+ changed = true;
+ }
+ } catch (e) {
+ // No manifest
+ delete this.manifestTime;
+ try {
+ let dtime = aFile.lastModifiedTime;
+ if (dtime != this.scanTime) {
+ changed = true;
+ this.scanTime = dtime;
+ }
+ } catch (e) {
+ logger.warn("Can't get modified time of ${file}: ${e}", {file: aFile.path, e: e});
+ changed = true;
+ this.scanTime = 0;
+ }
+ }
+ return changed;
+ },
+
+ /**
+ * Update the XPIState to match an XPIDatabase entry; if 'enabled' is changed to true,
+ * update the last-modified time. This should probably be made async, but for now we
+ * don't want to maintain parallel sync and async versions of the scan.
+ * Caller is responsible for doing XPIStates.save() if necessary.
+ * @param aDBAddon The DBAddonInternal for this add-on.
+ * @param aUpdated The add-on was updated, so we must record new modified time.
+ */
+ syncWithDB(aDBAddon, aUpdated = false) {
+ logger.debug("Updating XPIState for " + JSON.stringify(aDBAddon));
+ // If the add-on changes from disabled to enabled, we should re-check the modified time.
+ // If this is a newly found add-on, it won't have an 'enabled' field but we
+ // did a full recursive scan in that case, so we don't need to do it again.
+ // We don't use aDBAddon.active here because it's not updated until after restart.
+ let mustGetMod = (aDBAddon.visible && !aDBAddon.disabled && !this.enabled);
+ this.enabled = (aDBAddon.visible && !aDBAddon.disabled);
+ this.version = aDBAddon.version;
+ // XXX Eventually also copy bootstrap, etc.
+ if (aUpdated || mustGetMod) {
+ this.getModTime(new nsIFile(this.descriptor), aDBAddon.id);
+ if (this.scanTime != aDBAddon.updateDate) {
+ aDBAddon.updateDate = this.scanTime;
+ XPIDatabase.saveChanges();
+ }
+ }
+ },
+};
+
+// Constructor for an ES6 Map that knows how to convert itself into a
+// regular object for toJSON().
+function SerializableMap() {
+ let m = new Map();
+ m.toJSON = function() {
+ let out = {}
+ for (let [key, val] of m) {
+ out[key] = val;
+ }
+ return out;
+ };
+ return m;
+}
+
+/**
+ * Keeps track of the state of XPI add-ons on the file system.
+ */
+this.XPIStates = {
+ // Map(location name -> Map(add-on ID -> XPIState))
+ db: null,
+
+ get size() {
+ if (!this.db) {
+ return 0;
+ }
+ let count = 0;
+ for (let location of this.db.values()) {
+ count += location.size;
+ }
+ return count;
+ },
+
+ /**
+ * Load extension state data from preferences.
+ */
+ loadExtensionState() {
+ let state = {};
+
+ // Clear out old directory state cache.
+ Preferences.reset(PREF_INSTALL_CACHE);
+
+ let cache = Preferences.get(PREF_XPI_STATE, "{}");
+ try {
+ state = JSON.parse(cache);
+ } catch (e) {
+ logger.warn("Error parsing extensions.xpiState ${state}: ${error}",
+ {state: cache, error: e});
+ }
+ logger.debug("Loaded add-on state from prefs: ${}", state);
+ return state;
+ },
+
+ /**
+ * Walk through all install locations, highest priority first,
+ * comparing the on-disk state of extensions to what is stored in prefs.
+ * @return true if anything has changed.
+ */
+ getInstallState() {
+ let oldState = this.loadExtensionState();
+ let changed = false;
+ this.db = new SerializableMap();
+
+ for (let location of XPIProvider.installLocations) {
+ // The list of add-on like file/directory names in the install location.
+ let addons = location.addonLocations;
+ // The results of scanning this location.
+ let foundAddons = new SerializableMap();
+
+ // What our old state thinks should be in this location.
+ let locState = {};
+ if (location.name in oldState) {
+ locState = oldState[location.name];
+ // We've seen this location.
+ delete oldState[location.name];
+ }
+
+ for (let file of addons) {
+ let id = location.getIDForLocation(file);
+
+ if (!(id in locState)) {
+ logger.debug("New add-on ${id} in ${location}", {id: id, location: location.name});
+ let xpiState = new XPIState({d: file.persistentDescriptor});
+ changed = xpiState.getModTime(file, id) || changed;
+ foundAddons.set(id, xpiState);
+ } else {
+ let xpiState = new XPIState(locState[id]);
+ // We found this add-on in the file system
+ delete locState[id];
+
+ changed = xpiState.getModTime(file, id) || changed;
+
+ if (file.persistentDescriptor != xpiState.descriptor) {
+ xpiState.descriptor = file.persistentDescriptor;
+ changed = true;
+ }
+ if (changed) {
+ logger.debug("Changed add-on ${id} in ${location}", {id: id, location: location.name});
+ }
+ foundAddons.set(id, xpiState);
+ }
+ }
+
+ // Anything left behind in oldState was removed from the file system.
+ for (let id in locState) {
+ changed = true;
+ break;
+ }
+ // If we found anything, add this location to our database.
+ if (foundAddons.size != 0) {
+ this.db.set(location.name, foundAddons);
+ }
+ }
+
+ // If there's anything left in oldState, an install location that held add-ons
+ // was removed from the browser configuration.
+ for (let location in oldState) {
+ changed = true;
+ break;
+ }
+
+ logger.debug("getInstallState changed: ${rv}, state: ${state}",
+ {rv: changed, state: this.db});
+ return changed;
+ },
+
+ /**
+ * Get the Map of XPI states for a particular location.
+ * @param aLocation The name of the install location.
+ * @return Map (id -> XPIState) or null if there are no add-ons in the location.
+ */
+ getLocation(aLocation) {
+ return this.db.get(aLocation);
+ },
+
+ /**
+ * Get the XPI state for a specific add-on in a location.
+ * If the state is not in our cache, return null.
+ * @param aLocation The name of the location where the add-on is installed.
+ * @param aId The add-on ID
+ * @return The XPIState entry for the add-on, or null.
+ */
+ getAddon(aLocation, aId) {
+ let location = this.db.get(aLocation);
+ if (!location) {
+ return null;
+ }
+ return location.get(aId);
+ },
+
+ /**
+ * Find the highest priority location of an add-on by ID and return the
+ * location and the XPIState.
+ * @param aId The add-on ID
+ * @return [locationName, XPIState] if the add-on is found, [undefined, undefined]
+ * if the add-on is not found.
+ */
+ findAddon(aId) {
+ // Fortunately the Map iterator returns in order of insertion, which is
+ // also our highest -> lowest priority order.
+ for (let [name, location] of this.db) {
+ if (location.has(aId)) {
+ return [name, location.get(aId)];
+ }
+ }
+ return [undefined, undefined];
+ },
+
+ /**
+ * Add a new XPIState for an add-on and synchronize it with the DBAddonInternal.
+ * @param aAddon DBAddonInternal for the new add-on.
+ */
+ addAddon(aAddon) {
+ let location = this.db.get(aAddon.location);
+ if (!location) {
+ // First add-on in this location.
+ location = new SerializableMap();
+ this.db.set(aAddon.location, location);
+ }
+ logger.debug("XPIStates adding add-on ${id} in ${location}: ${descriptor}", aAddon);
+ let xpiState = new XPIState({d: aAddon.descriptor});
+ location.set(aAddon.id, xpiState);
+ xpiState.syncWithDB(aAddon, true);
+ },
+
+ /**
+ * Save the current state of installed add-ons.
+ * XXX this *totally* should be a .json file using DeferredSave...
+ */
+ save() {
+ let cache = JSON.stringify(this.db);
+ Services.prefs.setCharPref(PREF_XPI_STATE, cache);
+ },
+
+ /**
+ * Remove the XPIState for an add-on and save the new state.
+ * @param aLocation The name of the add-on location.
+ * @param aId The ID of the add-on.
+ */
+ removeAddon(aLocation, aId) {
+ logger.debug("Removing XPIState for " + aLocation + ":" + aId);
+ let location = this.db.get(aLocation);
+ if (!location) {
+ return;
+ }
+ location.delete(aId);
+ if (location.size == 0) {
+ this.db.delete(aLocation);
+ }
+ this.save();
+ },
+};
+
+this.XPIProvider = {
+ get name() "XPIProvider",
+
+ // An array of known install locations
+ installLocations: null,
+ // A dictionary of known install locations by name
+ installLocationsByName: null,
+ // An array of currently active AddonInstalls
+ installs: null,
+ // The default skin for the application
+ defaultSkin: "classic/1.0",
+ // The current skin used by the application
+ currentSkin: null,
+ // The selected skin to be used by the application when it is restarted. This
+ // will be the same as currentSkin when it is the skin to be used when the
+ // application is restarted
+ selectedSkin: null,
+ // The value of the minCompatibleAppVersion preference
+ minCompatibleAppVersion: null,
+ // The value of the minCompatiblePlatformVersion preference
+ minCompatiblePlatformVersion: null,
+ // A dictionary of the file descriptors for bootstrappable add-ons by ID
+ bootstrappedAddons: {},
+ // A dictionary of JS scopes of loaded bootstrappable add-ons by ID
+ bootstrapScopes: {},
+ // True if the platform could have activated extensions
+ extensionsActive: false,
+ // True if all of the add-ons found during startup were installed in the
+ // application install location
+ allAppGlobal: true,
+ // A string listing the enabled add-ons for annotating crash reports
+ enabledAddons: null,
+ // Keep track of startup phases.
+ runPhase: XPI_STARTING,
+ // Keep track of the newest file in each add-on, in case we want to
+ // report it.
+ _mostRecentlyModifiedFile: {},
+ // A Map from an add-on install to its ID
+ _addonFileMap: new Map(),
+#ifdef MOZ_DEVTOOLS
+ // Flag to know if ToolboxProcess.jsm has already been loaded by someone or not
+ _toolboxProcessLoaded: false,
+#endif
+ // Have we started shutting down bootstrap add-ons?
+ _closing: false,
+
+ // Keep track of in-progress operations that support cancel()
+ _inProgress: [],
+
+ doing: function XPI_doing(aCancellable) {
+ this._inProgress.push(aCancellable);
+ },
+
+ done: function XPI_done(aCancellable) {
+ let i = this._inProgress.indexOf(aCancellable);
+ if (i != -1) {
+ this._inProgress.splice(i, 1);
+ return true;
+ }
+ return false;
+ },
+
+ cancelAll: function XPI_cancelAll() {
+ // Cancelling one may alter _inProgress, so don't use a simple iterator
+ while (this._inProgress.length > 0) {
+ let c = this._inProgress.shift();
+ try {
+ c.cancel();
+ }
+ catch (e) {
+ logger.warn("Cancel failed", e);
+ }
+ }
+ },
+
+ /**
+ * Adds or updates a URI mapping for an Addon.id.
+ *
+ * Mappings should not be removed at any point. This is so that the mappings
+ * will be still valid after an add-on gets disabled or uninstalled, as
+ * consumers may still have URIs of (leaked) resources they want to map.
+ */
+ _addURIMapping: function XPI__addURIMapping(aID, aFile) {
+ logger.info("Mapping " + aID + " to " + aFile.path);
+ this._addonFileMap.set(aID, aFile.path);
+
+ AddonPathService.insertPath(aFile.path, aID);
+ },
+
+ /**
+ * Resolve a URI back to physical file.
+ *
+ * Of course, this works only for URIs pointing to local resources.
+ *
+ * @param aURI
+ * URI to resolve
+ * @return
+ * resolved nsIFileURL
+ */
+ _resolveURIToFile: function XPI__resolveURIToFile(aURI) {
+ switch (aURI.scheme) {
+ case "jar":
+ case "file":
+ if (aURI instanceof Ci.nsIJARURI) {
+ return this._resolveURIToFile(aURI.JARFile);
+ }
+ return aURI;
+
+ case "chrome":
+ aURI = ChromeRegistry.convertChromeURL(aURI);
+ return this._resolveURIToFile(aURI);
+
+ case "resource":
+ aURI = Services.io.newURI(ResProtocolHandler.resolveURI(aURI), null,
+ null);
+ return this._resolveURIToFile(aURI);
+
+ case "view-source":
+ aURI = Services.io.newURI(aURI.path, null, null);
+ return this._resolveURIToFile(aURI);
+
+ case "about":
+ if (aURI.spec == "about:blank") {
+ // Do not attempt to map about:blank
+ return null;
+ }
+
+ let chan;
+ try {
+ chan = NetUtil.newChannel({
+ uri: aURI,
+ loadUsingSystemPrincipal: true
+ });
+ }
+ catch (ex) {
+ return null;
+ }
+ // Avoid looping
+ if (chan.URI.equals(aURI)) {
+ return null;
+ }
+ // We want to clone the channel URI to avoid accidentially keeping
+ // unnecessary references to the channel or implementation details
+ // around.
+ return this._resolveURIToFile(chan.URI.clone());
+
+ default:
+ return null;
+ }
+ },
+
+ /**
+ * Starts the XPI provider initializes the install locations and prefs.
+ *
+ * @param aAppChanged
+ * A tri-state value. Undefined means the current profile was created
+ * for this session, true means the profile already existed but was
+ * last used with an application with a different version number,
+ * false means that the profile was last used by this version of the
+ * application.
+ * @param aOldAppVersion
+ * The version of the application last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @param aOldPlatformVersion
+ * The version of the platform last run with this profile or null
+ * if it is a new profile or the version is unknown
+ */
+ startup: function XPI_startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
+ function addDirectoryInstallLocation(aName, aKey, aPaths, aScope, aLocked) {
+ try {
+ var dir = FileUtils.getDir(aKey, aPaths);
+ }
+ catch (e) {
+ // Some directories aren't defined on some platforms, ignore them
+ logger.debug("Skipping unavailable install location " + aName);
+ return;
+ }
+
+ try {
+ var location = new DirectoryInstallLocation(aName, dir, aScope, aLocked);
+ }
+ catch (e) {
+ logger.warn("Failed to add directory install location " + aName, e);
+ return;
+ }
+
+ XPIProvider.installLocations.push(location);
+ XPIProvider.installLocationsByName[location.name] = location;
+ }
+
+ function addRegistryInstallLocation(aName, aRootkey, aScope) {
+ try {
+ var location = new WinRegInstallLocation(aName, aRootkey, aScope);
+ }
+ catch (e) {
+ logger.warn("Failed to add registry install location " + aName, e);
+ return;
+ }
+
+ XPIProvider.installLocations.push(location);
+ XPIProvider.installLocationsByName[location.name] = location;
+ }
+
+ try {
+ logger.debug("startup");
+ this.runPhase = XPI_STARTING;
+ this.installs = [];
+ this.installLocations = [];
+ this.installLocationsByName = {};
+ // Hook for tests to detect when saving database at shutdown time fails
+ this._shutdownError = null;
+
+ let hasRegistry = ("nsIWindowsRegKey" in Ci);
+
+ let enabledScopes = Preferences.get(PREF_EM_ENABLED_SCOPES,
+ AddonManager.SCOPE_ALL);
+
+ // These must be in order of priority, highest to lowest,
+ // for processFileChanges etc. to work
+ // The profile location is always enabled
+ addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR,
+ [DIR_EXTENSIONS],
+ AddonManager.SCOPE_PROFILE, false);
+
+ if (enabledScopes & AddonManager.SCOPE_USER) {
+ addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt",
+ [Services.appinfo.ID],
+ AddonManager.SCOPE_USER, true);
+ if (hasRegistry) {
+ addRegistryInstallLocation("winreg-app-user",
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ AddonManager.SCOPE_USER);
+ }
+ }
+
+ if (enabledScopes & AddonManager.SCOPE_APPLICATION) {
+ addDirectoryInstallLocation(KEY_APP_GLOBAL, KEY_APPDIR,
+ [DIR_EXTENSIONS],
+ AddonManager.SCOPE_APPLICATION, true);
+ }
+
+ if (enabledScopes & AddonManager.SCOPE_SYSTEM) {
+ addDirectoryInstallLocation(KEY_APP_SYSTEM_SHARE, "XRESysSExtPD",
+ [Services.appinfo.ID],
+ AddonManager.SCOPE_SYSTEM, true);
+ addDirectoryInstallLocation(KEY_APP_SYSTEM_LOCAL, "XRESysLExtPD",
+ [Services.appinfo.ID],
+ AddonManager.SCOPE_SYSTEM, true);
+ if (hasRegistry) {
+ addRegistryInstallLocation("winreg-app-global",
+ Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
+ AddonManager.SCOPE_SYSTEM);
+ }
+ }
+
+ let defaultPrefs = new Preferences({ defaultBranch: true });
+ this.defaultSkin = defaultPrefs.get(PREF_GENERAL_SKINS_SELECTEDSKIN,
+ "classic/1.0");
+ this.currentSkin = Preferences.get(PREF_GENERAL_SKINS_SELECTEDSKIN,
+ this.defaultSkin);
+ this.selectedSkin = this.currentSkin;
+ this.applyThemeChange();
+
+ this.minCompatibleAppVersion = Preferences.get(PREF_EM_MIN_COMPAT_APP_VERSION,
+ null);
+ this.minCompatiblePlatformVersion = Preferences.get(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
+ null);
+ this.enabledAddons = "";
+
+ Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false);
+ Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false);
+ Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false);
+
+#ifdef MOZ_DEVTOOLS
+ if (Cu.isModuleLoaded("resource://devtools/client/framework/ToolboxProcess.jsm")) {
+ // If BrowserToolboxProcess is already loaded, set the boolean to true
+ // and do whatever is needed
+ this._toolboxProcessLoaded = true;
+ BrowserToolboxProcess.on("connectionchange",
+ this.onDebugConnectionChange.bind(this));
+ }
+ else {
+ // Else, wait for it to load
+ Services.obs.addObserver(this, NOTIFICATION_TOOLBOXPROCESS_LOADED, false);
+ }
+#endif
+
+ let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
+ aOldPlatformVersion);
+
+ // Changes to installed extensions may have changed which theme is selected
+ this.applyThemeChange();
+
+ AddonManagerPrivate.markProviderSafe(this);
+
+ if (aAppChanged === undefined) {
+ // For new profiles we will never need to show the add-on selection UI
+ Services.prefs.setBoolPref(PREF_SHOWN_SELECTION_UI, true);
+ }
+ else if (aAppChanged && !this.allAppGlobal &&
+ Preferences.get(PREF_EM_SHOW_MISMATCH_UI, true)) {
+ if (!Preferences.get(PREF_SHOWN_SELECTION_UI, false)) {
+ // Flip a flag to indicate that we interrupted startup with an interactive prompt
+ Services.startup.interrupted = true;
+ // This *must* be modal as it has to block startup.
+ var features = "chrome,centerscreen,dialog,titlebar,modal";
+ Services.ww.openWindow(null, URI_EXTENSION_SELECT_DIALOG, "", features, null);
+ Services.prefs.setBoolPref(PREF_SHOWN_SELECTION_UI, true);
+ // Ensure any changes to the add-ons list are flushed to disk
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
+ !XPIDatabase.writeAddonsList());
+ }
+ else {
+ let addonsToUpdate = this.shouldForceUpdateCheck(aAppChanged);
+ if (addonsToUpdate) {
+ this.showUpgradeUI(addonsToUpdate);
+ flushCaches = true;
+ }
+ }
+ }
+
+ if (flushCaches) {
+ Services.obs.notifyObservers(null, "startupcache-invalidate", null);
+ // UI displayed early in startup (like the compatibility UI) may have
+ // caused us to cache parts of the skin or locale in memory. These must
+ // be flushed to allow extension provided skins and locales to take full
+ // effect
+ Services.obs.notifyObservers(null, "chrome-flush-skin-caches", null);
+ Services.obs.notifyObservers(null, "chrome-flush-caches", null);
+ }
+
+ this.enabledAddons = Preferences.get(PREF_EM_ENABLED_ADDONS, "");
+
+ try {
+ for (let id in this.bootstrappedAddons) {
+ try {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = this.bootstrappedAddons[id].descriptor;
+ let reason = BOOTSTRAP_REASONS.APP_STARTUP;
+ // Eventually set INSTALLED reason when a bootstrap addon
+ // is dropped in profile folder and automatically installed
+ if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
+ .indexOf(id) !== -1)
+ reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
+ this.callBootstrapMethod(createAddonDetails(id, this.bootstrappedAddons[id]),
+ file, "startup", reason);
+ }
+ catch (e) {
+ logger.error("Failed to load bootstrap addon " + id + " from " +
+ this.bootstrappedAddons[id].descriptor, e);
+ }
+ }
+ }
+ catch (e) {
+ logger.error("bootstrap startup failed", e);
+ }
+
+ // Let these shutdown a little earlier when they still have access to most
+ // of XPCOM
+ Services.obs.addObserver({
+ observe: function shutdownObserver(aSubject, aTopic, aData) {
+ XPIProvider._closing = true;
+ for (let id in XPIProvider.bootstrappedAddons) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = XPIProvider.bootstrappedAddons[id].descriptor;
+ let addon = createAddonDetails(id, XPIProvider.bootstrappedAddons[id]);
+ XPIProvider.callBootstrapMethod(addon, file, "shutdown",
+ BOOTSTRAP_REASONS.APP_SHUTDOWN);
+ }
+ Services.obs.removeObserver(this, "quit-application-granted");
+ }
+ }, "quit-application-granted", false);
+
+ this.extensionsActive = true;
+ this.runPhase = XPI_BEFORE_UI_STARTUP;
+ }
+ catch (e) {
+ logger.error("startup failed", e);
+ }
+ },
+
+ /**
+ * Shuts down the database and releases all references.
+ * Return: Promise{integer} resolves / rejects with the result of
+ * flushing the XPI Database if it was loaded,
+ * 0 otherwise.
+ */
+ shutdown: function XPI_shutdown() {
+ logger.debug("shutdown");
+
+ // Stop anything we were doing asynchronously
+ this.cancelAll();
+
+ this.bootstrappedAddons = {};
+ this.bootstrapScopes = {};
+ this.enabledAddons = null;
+ this.allAppGlobal = true;
+
+ // If there are pending operations then we must update the list of active
+ // add-ons
+ if (Preferences.get(PREF_PENDING_OPERATIONS, false)) {
+ XPIDatabase.updateActiveAddons();
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
+ !XPIDatabase.writeAddonsList());
+ }
+
+ this.installs = null;
+ this.installLocations = null;
+ this.installLocationsByName = null;
+
+ // This is needed to allow xpcshell tests to simulate a restart
+ this.extensionsActive = false;
+ this._addonFileMap.clear();
+
+ if (gLazyObjectsLoaded) {
+ let done = XPIDatabase.shutdown();
+ done.then(
+ ret => {
+ logger.debug("Notifying XPI shutdown observers");
+ Services.obs.notifyObservers(null, "xpi-provider-shutdown", null);
+ },
+ err => {
+ logger.debug("Notifying XPI shutdown observers");
+ this._shutdownError = err;
+ Services.obs.notifyObservers(null, "xpi-provider-shutdown", err);
+ }
+ );
+ return done;
+ }
+ else {
+ logger.debug("Notifying XPI shutdown observers");
+ Services.obs.notifyObservers(null, "xpi-provider-shutdown", null);
+ }
+ },
+
+ /**
+ * Applies any pending theme change to the preferences.
+ */
+ applyThemeChange: function XPI_applyThemeChange() {
+ if (!Preferences.get(PREF_DSS_SWITCHPENDING, false))
+ return;
+
+ // Tell the Chrome Registry which Skin to select
+ try {
+ this.selectedSkin = Preferences.get(PREF_DSS_SKIN_TO_SELECT);
+ Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
+ this.selectedSkin);
+ Services.prefs.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
+ logger.debug("Changed skin to " + this.selectedSkin);
+ this.currentSkin = this.selectedSkin;
+ }
+ catch (e) {
+ logger.error("Error applying theme change", e);
+ }
+ Services.prefs.clearUserPref(PREF_DSS_SWITCHPENDING);
+ },
+
+ /**
+ * If the application has been upgraded and there are add-ons outside the
+ * application directory then we may need to synchronize compatibility
+ * information but only if the mismatch UI isn't disabled.
+ *
+ * @returns False if no update check is needed, otherwise an array of add-on
+ * IDs to check for updates. Array may be empty if no add-ons can be/need
+ * to be updated, but the metadata check needs to be performed.
+ */
+ shouldForceUpdateCheck: function XPI_shouldForceUpdateCheck(aAppChanged) {
+ let startupChanges = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
+ logger.debug("shouldForceUpdateCheck startupChanges: " + startupChanges.toSource());
+
+ let forceUpdate = [];
+ if (startupChanges.length > 0) {
+ let addons = XPIDatabase.getAddons();
+ for (let addon of addons) {
+ if ((startupChanges.indexOf(addon.id) != -1) &&
+ (addon.permissions() & AddonManager.PERM_CAN_UPGRADE)) {
+ logger.debug("shouldForceUpdateCheck: can upgrade disabled add-on " + addon.id);
+ forceUpdate.push(addon.id);
+ }
+ }
+ }
+
+ if (AddonRepository.isMetadataStale()) {
+ logger.debug("shouldForceUpdateCheck: metadata is stale");
+ return forceUpdate;
+ }
+ if (forceUpdate.length > 0) {
+ return forceUpdate;
+ }
+
+ return false;
+ },
+
+ /**
+ * Shows the "Compatibility Updates" UI.
+ *
+ * @param aAddonIDs
+ * Array opf addon IDs that were disabled by the application update, and
+ * should therefore be checked for updates.
+ */
+ showUpgradeUI: function XPI_showUpgradeUI(aAddonIDs) {
+ logger.debug("XPI_showUpgradeUI: " + aAddonIDs.toSource());
+ // Flip a flag to indicate that we interrupted startup with an interactive prompt
+ Services.startup.interrupted = true;
+
+ var variant = Cc["@mozilla.org/variant;1"].
+ createInstance(Ci.nsIWritableVariant);
+ variant.setFromVariant(aAddonIDs);
+
+ // This *must* be modal as it has to block startup.
+ var features = "chrome,centerscreen,dialog,titlebar,modal";
+ var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);
+
+ // Ensure any changes to the add-ons list are flushed to disk
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
+ !XPIDatabase.writeAddonsList());
+ },
+
+ /**
+ * Persists changes to XPIProvider.bootstrappedAddons to its store (a pref).
+ */
+ persistBootstrappedAddons: function XPI_persistBootstrappedAddons() {
+ let filtered = {};
+ for (let id in this.bootstrappedAddons) {
+ let entry = this.bootstrappedAddons[id];
+
+ filtered[id] = entry;
+ }
+
+ Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
+ JSON.stringify(filtered));
+ },
+
+ /**
+ * Adds a list of currently active add-ons to the next crash report.
+ */
+ addAddonsToCrashReporter: function XPI_addAddonsToCrashReporter() {
+ if (!("nsICrashReporter" in Ci) ||
+ !(Services.appinfo instanceof Ci.nsICrashReporter))
+ return;
+
+ // In safe mode no add-ons are loaded so we should not include them in the
+ // crash report
+ if (Services.appinfo.inSafeMode)
+ return;
+
+ let data = this.enabledAddons;
+ for (let id in this.bootstrappedAddons) {
+ data += (data ? "," : "") + encodeURIComponent(id) + ":" +
+ encodeURIComponent(this.bootstrappedAddons[id].version);
+ }
+
+ try {
+ Services.appinfo.annotateCrashReport("Add-ons", data);
+ }
+ catch (e) { }
+ },
+
+ /**
+ * Check the staging directories of install locations for any add-ons to be
+ * installed or add-ons to be uninstalled.
+ *
+ * @param aManifests
+ * A dictionary to add detected install manifests to for the purpose
+ * of passing through updated compatibility information
+ * @return true if an add-on was installed or uninstalled
+ */
+ processPendingFileChanges: function XPI_processPendingFileChanges(aManifests) {
+ let changed = false;
+ this.installLocations.forEach(function(aLocation) {
+ aManifests[aLocation.name] = {};
+ // We can't install or uninstall anything in locked locations
+ if (aLocation.locked)
+ return;
+
+ let stagedXPIDir = aLocation.getXPIStagingDir();
+ let stagingDir = aLocation.getStagingDir();
+
+ if (stagedXPIDir.exists() && stagedXPIDir.isDirectory()) {
+ let entries = stagedXPIDir.directoryEntries
+ .QueryInterface(Ci.nsIDirectoryEnumerator);
+ while (entries.hasMoreElements()) {
+ let stageDirEntry = entries.nextFile;
+
+ if (!stageDirEntry.isDirectory()) {
+ logger.warn("Ignoring file in XPI staging directory: " + stageDirEntry.path);
+ continue;
+ }
+
+ // Find the last added XPI file in the directory
+ let stagedXPI = null;
+ var xpiEntries = stageDirEntry.directoryEntries
+ .QueryInterface(Ci.nsIDirectoryEnumerator);
+ while (xpiEntries.hasMoreElements()) {
+ let file = xpiEntries.nextFile;
+ if (file.isDirectory())
+ continue;
+
+ let extension = file.leafName;
+ extension = extension.substring(extension.length - 4);
+
+ if (extension != ".xpi" && extension != ".jar")
+ continue;
+
+ stagedXPI = file;
+ }
+ xpiEntries.close();
+
+ if (!stagedXPI)
+ continue;
+
+ let addon = null;
+ try {
+ addon = loadManifestFromZipFile(stagedXPI);
+ }
+ catch (e) {
+ logger.error("Unable to read add-on manifest from " + stagedXPI.path, e);
+ continue;
+ }
+
+ logger.debug("Migrating staged install of " + addon.id + " in " + aLocation.name);
+
+ if (addon.unpack || Preferences.get(PREF_XPI_UNPACK, false)) {
+ let targetDir = stagingDir.clone();
+ targetDir.append(addon.id);
+ try {
+ targetDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ }
+ catch (e) {
+ logger.error("Failed to create staging directory for add-on " + addon.id, e);
+ continue;
+ }
+
+ try {
+ ZipUtils.extractFiles(stagedXPI, targetDir);
+ }
+ catch (e) {
+ logger.error("Failed to extract staged XPI for add-on " + addon.id + " in " +
+ aLocation.name, e);
+ }
+ }
+ else {
+ try {
+ stagedXPI.moveTo(stagingDir, addon.id + ".xpi");
+ }
+ catch (e) {
+ logger.error("Failed to move staged XPI for add-on " + addon.id + " in " +
+ aLocation.name, e);
+ }
+ }
+ }
+ entries.close();
+ }
+
+ if (stagedXPIDir.exists()) {
+ try {
+ recursiveRemove(stagedXPIDir);
+ }
+ catch (e) {
+ // Non-critical, just saves some perf on startup if we clean this up.
+ logger.debug("Error removing XPI staging dir " + stagedXPIDir.path, e);
+ }
+ }
+
+ try {
+ if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory())
+ return;
+ }
+ catch (e) {
+ logger.warn("Failed to find staging directory", e);
+ return;
+ }
+
+ let seenFiles = [];
+ // Use a snapshot of the directory contents to avoid possible issues with
+ // iterating over a directory while removing files from it (the YAFFS2
+ // embedded filesystem has this issue, see bug 772238), and to remove
+ // normal files before their resource forks on OSX (see bug 733436).
+ let stagingDirEntries = getDirectoryEntries(stagingDir, true);
+ for (let stageDirEntry of stagingDirEntries) {
+ let id = stageDirEntry.leafName;
+
+ let isDir;
+ try {
+ isDir = stageDirEntry.isDirectory();
+ }
+ catch (e if e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ // If the file has already gone away then don't worry about it, this
+ // can happen on OSX where the resource fork is automatically moved
+ // with the data fork for the file. See bug 733436.
+ continue;
+ }
+
+ if (!isDir) {
+ if (id.substring(id.length - 4).toLowerCase() == ".xpi") {
+ id = id.substring(0, id.length - 4);
+ }
+ else {
+ if (id.substring(id.length - 5).toLowerCase() != ".json") {
+ logger.warn("Ignoring file: " + stageDirEntry.path);
+ seenFiles.push(stageDirEntry.leafName);
+ }
+ continue;
+ }
+ }
+
+ // Check that the directory's name is a valid ID.
+ if (!gIDTest.test(id)) {
+ logger.warn("Ignoring directory whose name is not a valid add-on ID: " +
+ stageDirEntry.path);
+ seenFiles.push(stageDirEntry.leafName);
+ continue;
+ }
+
+ changed = true;
+
+ if (isDir) {
+ // Check if the directory contains an install manifest.
+ let manifest = stageDirEntry.clone();
+ manifest.append(FILE_INSTALL_MANIFEST);
+
+ // If the install manifest doesn't exist uninstall this add-on in this
+ // install location.
+ if (!manifest.exists()) {
+ logger.debug("Processing uninstall of " + id + " in " + aLocation.name);
+
+ try {
+ let addonFile = aLocation.getLocationForID(id);
+ let addonToUninstall = loadManifestFromFile(addonFile, aLocation);
+ if (addonToUninstall.bootstrap) {
+ this.callBootstrapMethod(addonToUninstall, addonToUninstall._sourceBundle,
+ "uninstall", BOOTSTRAP_REASONS.ADDON_UNINSTALL);
+ }
+ }
+ catch (e) {
+ // If called on startup this may fail due to staged folder still existing.
+ }
+
+ try {
+ aLocation.uninstallAddon(id);
+ seenFiles.push(stageDirEntry.leafName);
+ }
+ catch (e) {
+ logger.error("Failed to uninstall add-on " + id + " in " + aLocation.name, e);
+ }
+ // The file check later will spot the removal and cleanup the database
+ continue;
+ }
+ }
+
+ aManifests[aLocation.name][id] = null;
+ let existingAddonID = id;
+
+ let jsonfile = stagingDir.clone();
+ jsonfile.append(id + ".json");
+
+ try {
+ aManifests[aLocation.name][id] = loadManifestFromFile(stageDirEntry);
+ }
+ catch (e) {
+ logger.error("Unable to read add-on manifest from " + stageDirEntry.path, e);
+ // This add-on can't be installed so just remove it now
+ seenFiles.push(stageDirEntry.leafName);
+ seenFiles.push(jsonfile.leafName);
+ continue;
+ }
+
+ // Check for a cached metadata for this add-on, it may contain updated
+ // compatibility information
+ if (jsonfile.exists()) {
+ logger.debug("Found updated metadata for " + id + " in " + aLocation.name);
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ let json = Cc["@mozilla.org/dom/json;1"].
+ createInstance(Ci.nsIJSON);
+
+ try {
+ fis.init(jsonfile, -1, 0, 0);
+ let metadata = json.decodeFromStream(fis, jsonfile.fileSize);
+ aManifests[aLocation.name][id].importMetadata(metadata);
+ }
+ catch (e) {
+ // If some data can't be recovered from the cached metadata then it
+ // is unlikely to be a problem big enough to justify throwing away
+ // the install, just log and error and continue
+ logger.error("Unable to read metadata from " + jsonfile.path, e);
+ }
+ finally {
+ fis.close();
+ }
+ }
+ seenFiles.push(jsonfile.leafName);
+
+ existingAddonID = aManifests[aLocation.name][id].existingAddonID || id;
+
+ var oldBootstrap = null;
+ logger.debug("Processing install of " + id + " in " + aLocation.name);
+ if (existingAddonID in this.bootstrappedAddons) {
+ try {
+ var existingAddon = aLocation.getLocationForID(existingAddonID);
+ if (this.bootstrappedAddons[existingAddonID].descriptor ==
+ existingAddon.persistentDescriptor) {
+ oldBootstrap = this.bootstrappedAddons[existingAddonID];
+
+ // We'll be replacing a currently active bootstrapped add-on so
+ // call its uninstall method
+ let newVersion = aManifests[aLocation.name][id].version;
+ let oldVersion = oldBootstrap.version;
+ let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
+ BOOTSTRAP_REASONS.ADDON_UPGRADE :
+ BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
+
+ this.callBootstrapMethod(createAddonDetails(existingAddonID, oldBootstrap),
+ existingAddon, "uninstall", uninstallReason,
+ { newVersion: newVersion });
+ this.unloadBootstrapScope(existingAddonID);
+ flushChromeCaches();
+ }
+ }
+ catch (e) {
+ }
+ }
+
+ try {
+ var addonInstallLocation = aLocation.installAddon(id, stageDirEntry,
+ existingAddonID);
+ if (aManifests[aLocation.name][id])
+ aManifests[aLocation.name][id]._sourceBundle = addonInstallLocation;
+ }
+ catch (e) {
+ logger.error("Failed to install staged add-on " + id + " in " + aLocation.name,
+ e);
+ // Re-create the staged install
+ AddonInstall.createStagedInstall(aLocation, stageDirEntry,
+ aManifests[aLocation.name][id]);
+ // Make sure not to delete the cached manifest json file
+ seenFiles.pop();
+
+ delete aManifests[aLocation.name][id];
+
+ if (oldBootstrap) {
+ // Re-install the old add-on
+ this.callBootstrapMethod(createAddonDetails(existingAddonID, oldBootstrap),
+ existingAddon, "install",
+ BOOTSTRAP_REASONS.ADDON_INSTALL);
+ }
+ continue;
+ }
+ }
+
+ try {
+ aLocation.cleanStagingDir(seenFiles);
+ }
+ catch (e) {
+ // Non-critical, just saves some perf on startup if we clean this up.
+ logger.debug("Error cleaning staging dir " + stagingDir.path, e);
+ }
+ }, this);
+ return changed;
+ },
+
+ /**
+ * Installs any add-ons located in the extensions directory of the
+ * application's distribution specific directory into the profile unless a
+ * newer version already exists or the user has previously uninstalled the
+ * distributed add-on.
+ *
+ * @param aManifests
+ * A dictionary to add new install manifests to to save having to
+ * reload them later
+ * @return true if any new add-ons were installed
+ */
+ installDistributionAddons: function XPI_installDistributionAddons(aManifests) {
+ let distroDir;
+ try {
+ distroDir = FileUtils.getDir(KEY_APP_DISTRIBUTION, [DIR_EXTENSIONS]);
+ }
+ catch (e) {
+ return false;
+ }
+
+ if (!distroDir.exists())
+ return false;
+
+ if (!distroDir.isDirectory())
+ return false;
+
+ let changed = false;
+ let profileLocation = this.installLocationsByName[KEY_APP_PROFILE];
+
+ let entries = distroDir.directoryEntries
+ .QueryInterface(Ci.nsIDirectoryEnumerator);
+ let entry;
+ while ((entry = entries.nextFile)) {
+
+ let id = entry.leafName;
+
+ if (entry.isFile()) {
+ if (id.substring(id.length - 4).toLowerCase() == ".xpi") {
+ id = id.substring(0, id.length - 4);
+ }
+ else {
+ logger.debug("Ignoring distribution add-on that isn't an XPI: " + entry.path);
+ continue;
+ }
+ }
+ else if (!entry.isDirectory()) {
+ logger.debug("Ignoring distribution add-on that isn't a file or directory: " +
+ entry.path);
+ continue;
+ }
+
+ if (!gIDTest.test(id)) {
+ logger.debug("Ignoring distribution add-on whose name is not a valid add-on ID: " +
+ entry.path);
+ continue;
+ }
+
+ let addon;
+ try {
+ addon = loadManifestFromFile(entry);
+ }
+ catch (e) {
+ logger.warn("File entry " + entry.path + " contains an invalid add-on", e);
+ continue;
+ }
+
+ if (addon.id != id) {
+ logger.warn("File entry " + entry.path + " contains an add-on with an " +
+ "incorrect ID")
+ continue;
+ }
+
+ let existingEntry = null;
+ try {
+ existingEntry = profileLocation.getLocationForID(id);
+ }
+ catch (e) {
+ }
+
+ if (existingEntry) {
+ let existingAddon;
+ try {
+ existingAddon = loadManifestFromFile(existingEntry);
+
+ if (Services.vc.compare(addon.version, existingAddon.version) <= 0)
+ continue;
+ }
+ catch (e) {
+ // Bad add-on in the profile so just proceed and install over the top
+ logger.warn("Profile contains an add-on with a bad or missing install " +
+ "manifest at " + existingEntry.path + ", overwriting", e);
+ }
+ }
+ else if (Preferences.get(PREF_BRANCH_INSTALLED_ADDON + id, false)) {
+ continue;
+ }
+
+ // Install the add-on
+ try {
+ profileLocation.installAddon(id, entry, null, true);
+ logger.debug("Installed distribution add-on " + id);
+
+ Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true)
+
+ // aManifests may contain a copy of a newly installed add-on's manifest
+ // and we'll have overwritten that so instead cache our install manifest
+ // which will later be put into the database in processFileChanges
+ if (!(KEY_APP_PROFILE in aManifests))
+ aManifests[KEY_APP_PROFILE] = {};
+ aManifests[KEY_APP_PROFILE][id] = addon;
+ changed = true;
+ }
+ catch (e) {
+ logger.error("Failed to install distribution add-on " + entry.path, e);
+ }
+ }
+
+ entries.close();
+
+ return changed;
+ },
+
+ /**
+ * Compares the add-ons that are currently installed to those that were
+ * known to be installed when the application last ran and applies any
+ * changes found to the database. Also sends "startupcache-invalidate" signal to
+ * observerservice if it detects that data may have changed.
+ * Always called after XPIProviderUtils.js and extensions.json have been loaded.
+ *
+ * @param aManifests
+ * A dictionary of cached AddonInstalls for add-ons that have been
+ * installed
+ * @param aUpdateCompatibility
+ * true to update add-ons appDisabled property when the application
+ * version has changed
+ * @param aOldAppVersion
+ * The version of the application last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @param aOldPlatformVersion
+ * The version of the platform last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @return a boolean indicating if a change requiring flushing the caches was
+ * detected
+ */
+ processFileChanges: function XPI_processFileChanges(aManifests,
+ aUpdateCompatibility,
+ aOldAppVersion,
+ aOldPlatformVersion) {
+ let visibleAddons = {};
+ let oldBootstrappedAddons = this.bootstrappedAddons;
+ this.bootstrappedAddons = {};
+
+ /**
+ * Updates an add-on's metadata and determines if a restart of the
+ * application is necessary. This is called when either the add-on's
+ * install directory path or last modified time has changed.
+ *
+ * @param aInstallLocation
+ * The install location containing the add-on
+ * @param aOldAddon
+ * The AddonInternal as it appeared the last time the application
+ * ran
+ * @param aAddonState
+ * The new state of the add-on
+ * @return a boolean indicating if flushing caches is required to complete
+ * changing this add-on
+ */
+ function updateMetadata(aInstallLocation, aOldAddon, aAddonState) {
+ logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
+
+ // Check if there is an updated install manifest for this add-on
+ let newAddon = aManifests[aInstallLocation.name][aOldAddon.id];
+
+ try {
+ // If not load it
+ if (!newAddon) {
+ let file = aInstallLocation.getLocationForID(aOldAddon.id);
+ newAddon = loadManifestFromFile(file);
+ applyBlocklistChanges(aOldAddon, newAddon);
+
+ // Carry over any pendingUninstall state to add-ons modified directly
+ // in the profile. This is important when the attempt to remove the
+ // add-on in processPendingFileChanges failed and caused an mtime
+ // change to the add-ons files.
+ newAddon.pendingUninstall = aOldAddon.pendingUninstall;
+ }
+
+ // The ID in the manifest that was loaded must match the ID of the old
+ // add-on.
+ if (newAddon.id != aOldAddon.id)
+ throw new Error("Incorrect id in install manifest for existing add-on " + aOldAddon.id);
+ }
+ catch (e) {
+ logger.warn("updateMetadata: Add-on " + aOldAddon.id + " is invalid", e);
+ XPIDatabase.removeAddonMetadata(aOldAddon);
+ XPIStates.removeAddon(aOldAddon.location, aOldAddon.id);
+ if (!aInstallLocation.locked)
+ aInstallLocation.uninstallAddon(aOldAddon.id);
+ else
+ logger.warn("Could not uninstall invalid item from locked install location");
+ // If this was an active add-on then we must force a restart
+ if (aOldAddon.active)
+ return true;
+
+ return false;
+ }
+
+ // Set the additional properties on the new AddonInternal
+ newAddon._installLocation = aInstallLocation;
+ newAddon.updateDate = aAddonState.mtime;
+ newAddon.visible = !(newAddon.id in visibleAddons);
+
+ // Update the database
+ let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon,
+ aAddonState.descriptor);
+ if (newDBAddon.visible) {
+ visibleAddons[newDBAddon.id] = newDBAddon;
+ // Remember add-ons that were changed during startup
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
+ newDBAddon.id);
+
+ // If this was the active theme and it is now disabled then enable the
+ // default theme
+ if (aOldAddon.active && newDBAddon.disabled)
+ XPIProvider.enableDefaultTheme();
+
+ // If the new add-on is bootstrapped and active then call its install method
+ if (newDBAddon.active && newDBAddon.bootstrap) {
+ // Startup cache must be flushed before calling the bootstrap script
+ flushChromeCaches();
+
+ let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ?
+ BOOTSTRAP_REASONS.ADDON_UPGRADE :
+ BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
+
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = aAddonState.descriptor;
+ XPIProvider.callBootstrapMethod(newDBAddon, file, "install",
+ installReason, { oldVersion: aOldAddon.version });
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Updates an add-on's descriptor for when the add-on has moved in the
+ * filesystem but hasn't changed in any other way.
+ *
+ * @param aInstallLocation
+ * The install location containing the add-on
+ * @param aOldAddon
+ * The AddonInternal as it appeared the last time the application
+ * ran
+ * @param aAddonState
+ * The new state of the add-on
+ * @return a boolean indicating if flushing caches is required to complete
+ * changing this add-on
+ */
+ function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) {
+ logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
+
+ aOldAddon.descriptor = aAddonState.descriptor;
+ aOldAddon.visible = !(aOldAddon.id in visibleAddons);
+ XPIDatabase.saveChanges();
+
+ if (aOldAddon.visible) {
+ visibleAddons[aOldAddon.id] = aOldAddon;
+
+ if (aOldAddon.bootstrap && aOldAddon.active) {
+ let bootstrap = oldBootstrappedAddons[aOldAddon.id];
+ bootstrap.descriptor = aAddonState.descriptor;
+ XPIProvider.bootstrappedAddons[aOldAddon.id] = bootstrap;
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Called when no change has been detected for an add-on's metadata. The
+ * add-on may have become visible due to other add-ons being removed or
+ * the add-on may need to be updated when the application version has
+ * changed.
+ *
+ * @param aInstallLocation
+ * The install location containing the add-on
+ * @param aOldAddon
+ * The AddonInternal as it appeared the last time the application
+ * ran
+ * @param aAddonState
+ * The new state of the add-on
+ * @return a boolean indicating if flushing caches is required to complete
+ * changing this add-on
+ */
+ function updateVisibilityAndCompatibility(aInstallLocation, aOldAddon,
+ aAddonState) {
+ let changed = false;
+
+ // This add-ons metadata has not changed but it may have become visible
+ if (!(aOldAddon.id in visibleAddons)) {
+ visibleAddons[aOldAddon.id] = aOldAddon;
+
+ if (!aOldAddon.visible) {
+ // Remember add-ons that were changed during startup.
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
+ aOldAddon.id);
+ XPIDatabase.makeAddonVisible(aOldAddon);
+
+ if (aOldAddon.bootstrap) {
+ // The add-on is bootstrappable so call its install script
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = aAddonState.descriptor;
+ XPIProvider.callBootstrapMethod(aOldAddon, file,
+ "install",
+ BOOTSTRAP_REASONS.ADDON_INSTALL);
+
+ // If it should be active then mark it as active otherwise unload
+ // its scope
+ if (!aOldAddon.disabled) {
+ XPIDatabase.updateAddonActive(aOldAddon, true);
+ }
+ else {
+ XPIProvider.unloadBootstrapScope(newAddon.id);
+ }
+ }
+ else {
+ // Otherwise a restart is necessary
+ changed = true;
+ }
+ }
+ }
+
+ // App version changed, we may need to update the appDisabled property.
+ if (aUpdateCompatibility) {
+ let wasDisabled = aOldAddon.disabled;
+ let wasAppDisabled = aOldAddon.appDisabled;
+ let wasUserDisabled = aOldAddon.userDisabled;
+ let wasSoftDisabled = aOldAddon.softDisabled;
+
+ // This updates the addon's JSON cached data in place
+ applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion,
+ aOldPlatformVersion);
+ aOldAddon.appDisabled = !isUsableAddon(aOldAddon);
+
+ let isDisabled = aOldAddon.disabled;
+
+ // If either property has changed update the database.
+ if (wasAppDisabled != aOldAddon.appDisabled ||
+ wasUserDisabled != aOldAddon.userDisabled ||
+ wasSoftDisabled != aOldAddon.softDisabled) {
+ logger.debug("Add-on " + aOldAddon.id + " changed appDisabled state to " +
+ aOldAddon.appDisabled + ", userDisabled state to " +
+ aOldAddon.userDisabled + " and softDisabled state to " +
+ aOldAddon.softDisabled);
+ XPIDatabase.saveChanges();
+ }
+
+ // If this is a visible add-on and it has changed disabled state then we
+ // may need a restart or to update the bootstrap list.
+ if (aOldAddon.visible && wasDisabled != isDisabled) {
+ // Remember add-ons that became disabled or enabled by the application
+ // change
+ let change = isDisabled ? AddonManager.STARTUP_CHANGE_DISABLED
+ : AddonManager.STARTUP_CHANGE_ENABLED;
+ AddonManagerPrivate.addStartupChange(change, aOldAddon.id);
+ if (aOldAddon.bootstrap) {
+ // Update the add-ons active state
+ XPIDatabase.updateAddonActive(aOldAddon, !isDisabled);
+ }
+ else {
+ changed = true;
+ }
+ }
+ }
+
+ if (aOldAddon.visible && aOldAddon.active && aOldAddon.bootstrap) {
+ XPIProvider.bootstrappedAddons[aOldAddon.id] = {
+ version: aOldAddon.version,
+ type: aOldAddon.type,
+ descriptor: aAddonState.descriptor,
+ multiprocessCompatible: aOldAddon.multiprocessCompatible
+ };
+ }
+
+ return changed;
+ }
+
+ /**
+ * Called when an add-on has been removed.
+ *
+ * @param aOldAddon
+ * The AddonInternal as it appeared the last time the application
+ * ran
+ * @return a boolean indicating if flushing caches is required to complete
+ * changing this add-on
+ */
+ function removeMetadata(aOldAddon) {
+ // This add-on has disappeared
+ logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
+ XPIDatabase.removeAddonMetadata(aOldAddon);
+
+ // Remember add-ons that were uninstalled during startup
+ if (aOldAddon.visible) {
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED,
+ aOldAddon.id);
+ }
+ else if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
+ .indexOf(aOldAddon.id) != -1) {
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
+ aOldAddon.id);
+ }
+
+ if (aOldAddon.active) {
+ // Enable the default theme if the previously active theme has been
+ // removed
+ if (aOldAddon.type == "theme")
+ XPIProvider.enableDefaultTheme();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Called to add the metadata for an add-on in one of the install locations
+ * to the database. This can be called in three different cases. Either an
+ * add-on has been dropped into the location from outside of Firefox, or
+ * an add-on has been installed through the application, or the database
+ * has been upgraded or become corrupt and add-on data has to be reloaded
+ * into it.
+ *
+ * @param aInstallLocation
+ * The install location containing the add-on
+ * @param aId
+ * The ID of the add-on
+ * @param aAddonState
+ * The new state of the add-on
+ * @param aMigrateData
+ * If during startup the database had to be upgraded this will
+ * contain data that used to be held about this add-on
+ * @return a boolean indicating if flushing caches is required to complete
+ * changing this add-on
+ */
+ function addMetadata(aInstallLocation, aId, aAddonState, aMigrateData) {
+ logger.debug("New add-on " + aId + " installed in " + aInstallLocation.name);
+
+ let newAddon = null;
+ let sameVersion = false;
+ // Check the updated manifests lists for the install location, If there
+ // is no manifest for the add-on ID then newAddon will be undefined
+ if (aInstallLocation.name in aManifests)
+ newAddon = aManifests[aInstallLocation.name][aId];
+
+ // If we had staged data for this add-on or we aren't recovering from a
+ // corrupt database and we don't have migration data for this add-on then
+ // this must be a new install.
+ let isNewInstall = (!!newAddon) || (!XPIDatabase.activeBundles && !aMigrateData);
+
+ // If it's a new install and we haven't yet loaded the manifest then it
+ // must be something dropped directly into the install location
+ let isDetectedInstall = isNewInstall && !newAddon;
+
+ // Load the manifest if necessary and sanity check the add-on ID
+ try {
+ if (!newAddon) {
+ // Load the manifest from the add-on.
+ let file = aInstallLocation.getLocationForID(aId);
+ newAddon = loadManifestFromFile(file);
+ }
+ // The add-on in the manifest should match the add-on ID.
+ if (newAddon.id != aId) {
+ throw new Error("Invalid addon ID: expected addon ID " + aId +
+ ", found " + newAddon.id + " in manifest");
+ }
+ }
+ catch (e) {
+ logger.warn("addMetadata: Add-on " + aId + " is invalid", e);
+
+ // Remove the invalid add-on from the install location if the install
+ // location isn't locked, no restart will be necessary
+ if (!aInstallLocation.locked)
+ aInstallLocation.uninstallAddon(aId);
+ else
+ logger.warn("Could not uninstall invalid item from locked install location");
+ return false;
+ }
+
+ // Update the AddonInternal properties.
+ newAddon._installLocation = aInstallLocation;
+ newAddon.visible = !(newAddon.id in visibleAddons);
+ newAddon.installDate = aAddonState.mtime;
+ newAddon.updateDate = aAddonState.mtime;
+ newAddon.foreignInstall = isDetectedInstall;
+
+ if (aMigrateData) {
+ // If there is migration data then apply it.
+ logger.debug("Migrating data from old database");
+
+ DB_MIGRATE_METADATA.forEach(function(aProp) {
+ // A theme's disabled state is determined by the selected theme
+ // preference which is read in loadManifestFromRDF
+ if (aProp == "userDisabled" && newAddon.type == "theme")
+ return;
+
+ if (aProp in aMigrateData)
+ newAddon[aProp] = aMigrateData[aProp];
+ });
+
+ // Force all non-profile add-ons to be foreignInstalls since they can't
+ // have been installed through the API
+ newAddon.foreignInstall |= aInstallLocation.name != KEY_APP_PROFILE;
+
+ // Some properties should only be migrated if the add-on hasn't changed.
+ // The version property isn't a perfect check for this but covers the
+ // vast majority of cases.
+ if (aMigrateData.version == newAddon.version) {
+ logger.debug("Migrating compatibility info");
+ sameVersion = true;
+ if ("targetApplications" in aMigrateData)
+ newAddon.applyCompatibilityUpdate(aMigrateData, true);
+ }
+
+ // Since the DB schema has changed make sure softDisabled is correct
+ applyBlocklistChanges(newAddon, newAddon, aOldAppVersion,
+ aOldPlatformVersion);
+ }
+
+ // The default theme is never a foreign install
+ if (newAddon.type == "theme" && newAddon.internalName == XPIProvider.defaultSkin)
+ newAddon.foreignInstall = false;
+
+ if (isDetectedInstall && newAddon.foreignInstall) {
+ // If the add-on is a foreign install and is in a scope where add-ons
+ // that were dropped in should default to disabled then disable it
+ let disablingScopes = Preferences.get(PREF_EM_AUTO_DISABLED_SCOPES, 0);
+ if (aInstallLocation.scope & disablingScopes) {
+ logger.warn("Disabling foreign installed add-on " + newAddon.id + " in "
+ + aInstallLocation.name);
+ newAddon.userDisabled = true;
+ }
+ }
+
+ // If we have a list of what add-ons should be marked as active then use
+ // it to guess at migration data.
+ if (!isNewInstall && XPIDatabase.activeBundles) {
+ // For themes we know which is active by the current skin setting
+ if (newAddon.type == "theme")
+ newAddon.active = newAddon.internalName == XPIProvider.currentSkin;
+ else
+ newAddon.active = XPIDatabase.activeBundles.indexOf(aAddonState.descriptor) != -1;
+
+ // If the add-on wasn't active and it isn't already disabled in some way
+ // then it was probably either softDisabled or userDisabled
+ if (!newAddon.active && newAddon.visible && !newAddon.disabled) {
+ // If the add-on is softblocked then assume it is softDisabled
+ if (newAddon.blocklistState == Blocklist.STATE_SOFTBLOCKED)
+ newAddon.softDisabled = true;
+ else
+ newAddon.userDisabled = true;
+ }
+ }
+ else {
+ newAddon.active = (newAddon.visible && !newAddon.disabled);
+ }
+
+ let newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
+
+ if (newDBAddon.visible) {
+ // Remember add-ons that were first detected during startup.
+ if (isDetectedInstall) {
+ // If a copy from a higher priority location was removed then this
+ // add-on has changed
+ if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_UNINSTALLED)
+ .indexOf(newDBAddon.id) != -1) {
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
+ newDBAddon.id);
+ }
+ else {
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED,
+ newDBAddon.id);
+ }
+ }
+
+ // Note if any visible add-on is not in the application install location
+ if (newDBAddon._installLocation.name != KEY_APP_GLOBAL)
+ XPIProvider.allAppGlobal = false;
+
+ visibleAddons[newDBAddon.id] = newDBAddon;
+
+ let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
+ let extraParams = {};
+
+ // Copy add-on details (enabled, bootstrap, version, etc) to XPIState.
+ aAddonState.syncWithDB(newDBAddon);
+
+ // If we're hiding a bootstrapped add-on then call its uninstall method
+ if (newDBAddon.id in oldBootstrappedAddons) {
+ let oldBootstrap = oldBootstrappedAddons[newDBAddon.id];
+ extraParams.oldVersion = oldBootstrap.version;
+ XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap;
+
+ // If the old version is the same as the new version, or we're
+ // recovering from a corrupt DB, don't call uninstall and install
+ // methods.
+ if (sameVersion || !isNewInstall) {
+ logger.debug("addMetadata: early return, sameVersion " + sameVersion
+ + ", isNewInstall " + isNewInstall);
+ return false;
+ }
+
+ installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ?
+ BOOTSTRAP_REASONS.ADDON_UPGRADE :
+ BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
+
+ let oldAddonFile = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsIFile);
+ oldAddonFile.persistentDescriptor = oldBootstrap.descriptor;
+
+ XPIProvider.callBootstrapMethod(createAddonDetails(newDBAddon.id, oldBootstrap),
+ oldAddonFile, "uninstall", installReason,
+ { newVersion: newDBAddon.version });
+
+ XPIProvider.unloadBootstrapScope(newDBAddon.id);
+
+ // If the new add-on is bootstrapped then we must flush the caches
+ // before calling the new bootstrap script
+ if (newDBAddon.bootstrap)
+ flushChromeCaches();
+ }
+
+ if (!newDBAddon.bootstrap)
+ return true;
+
+ // Visible bootstrapped add-ons need to have their install method called
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.persistentDescriptor = aAddonState.descriptor;
+ XPIProvider.callBootstrapMethod(newDBAddon, file,
+ "install", installReason, extraParams);
+ if (!newDBAddon.active)
+ XPIProvider.unloadBootstrapScope(newDBAddon.id);
+ }
+
+ return false;
+ }
+
+ let changed = false;
+
+ // Get all the add-ons in the existing DB and Map them into Sets by install location
+ let allDBAddons = new Map();
+ for (let a of XPIDatabase.getAddons()) {
+ let locationSet = allDBAddons.get(a.location);
+ if (!locationSet) {
+ locationSet = new Set();
+ allDBAddons.set(a.location, locationSet);
+ }
+ locationSet.add(a);
+ }
+
+ for (let installLocation of this.installLocations) {
+ // Get all the on-disk XPI states for this location, and keep track of which
+ // ones we see in the database.
+ let states = XPIStates.getLocation(installLocation.name);
+ let seen = new Set();
+ // Iterate through the add-ons installed the last time the application
+ // ran
+ let dbAddons = allDBAddons.get(installLocation.name);
+ if (dbAddons) {
+ // we've processed this location
+ allDBAddons.delete(installLocation.name);
+
+ logger.debug("processFileChanges reconciling DB for location ${l} state ${s} db ${d}",
+ {l: installLocation.name, s: states, d: [for (a of dbAddons) a.id]});
+ for (let aOldAddon of dbAddons) {
+ // If a version of this add-on has been installed in an higher
+ // priority install location then count it as changed
+ if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
+ .indexOf(aOldAddon.id) != -1) {
+ AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
+ aOldAddon.id);
+ }
+
+ // Check if the add-on is still installed
+ let xpiState = states && states.get(aOldAddon.id);
+ if (xpiState) {
+ // in this block, the add-on is in both XPIStates and the DB
+ seen.add(xpiState);
+
+ // The add-on has changed if the modification time has changed, or
+ // we have an updated manifest for it. Also reload the metadata for
+ // add-ons in the application directory when the application version
+ // has changed
+ if (aOldAddon.id in aManifests[installLocation.name] ||
+ aOldAddon.updateDate != xpiState.mtime ||
+ (aUpdateCompatibility && installLocation.name == KEY_APP_GLOBAL)) {
+ changed = updateMetadata(installLocation, aOldAddon, xpiState) ||
+ changed;
+ }
+ else if (aOldAddon.descriptor != xpiState.descriptor) {
+ changed = updateDescriptor(installLocation, aOldAddon, xpiState) ||
+ changed;
+ }
+ else {
+ changed = updateMetadata(installLocation, aOldAddon, xpiState) ||
+ changed;
+
+ changed = updateVisibilityAndCompatibility(installLocation,
+ aOldAddon, xpiState) ||
+ changed;
+ }
+ if (aOldAddon.visible && aOldAddon._installLocation.name != KEY_APP_GLOBAL)
+ XPIProvider.allAppGlobal = false;
+ // Copy add-on details (enabled, bootstrap, version, etc) to XPIState.
+ xpiState.syncWithDB(aOldAddon);
+ }
+ else {
+ // The add-on is in the DB, but not in xpiState (and thus not on disk).
+ changed = removeMetadata(aOldAddon) || changed;
+ }
+ }
+ }
+
+ // Any add-on in our current location that we haven't seen needs to
+ // be added to the database.
+ // Get the migration data for this install location so we can include that as
+ // we add, in case this is a database upgrade or rebuild.
+ let locMigrateData = {};
+ if (XPIDatabase.migrateData && installLocation.name in XPIDatabase.migrateData)
+ locMigrateData = XPIDatabase.migrateData[installLocation.name];
+ if (states) {
+ for (let [id, xpiState] of states) {
+ if (!seen.has(xpiState)) {
+ changed = addMetadata(installLocation, id, xpiState,
+ (locMigrateData[id] || null)) || changed;
+ }
+ }
+ }
+ }
+
+ // Anything left in allDBAddons is a location where the database contains add-ons,
+ // but the browser is no longer configured to use that location. The metadata for those
+ // add-ons must be removed from the database.
+ for (let [locationName, addons] of allDBAddons) {
+ logger.debug("Removing orphaned DB add-on entries from " + locationName);
+ for (let a of addons) {
+ logger.debug("Remove ${location}:${id}", a);
+ changed = removeMetadata(a) || changed;
+ }
+ }
+
+ XPIStates.save();
+ this.persistBootstrappedAddons();
+
+ // Clear out any cached migration data.
+ XPIDatabase.migrateData = null;
+
+ return changed;
+ },
+
+ /**
+ * Imports the xpinstall permissions from preferences into the permissions
+ * manager for the user to change later.
+ */
+ importPermissions: function XPI_importPermissions() {
+ PermissionsUtils.importFromPrefs(PREF_XPI_PERMISSIONS_BRANCH,
+ XPI_PERMISSION);
+ },
+
+ /**
+ * Checks for any changes that have occurred since the last time the
+ * application was launched.
+ *
+ * @param aAppChanged
+ * A tri-state value. Undefined means the current profile was created
+ * for this session, true means the profile already existed but was
+ * last used with an application with a different version number,
+ * false means that the profile was last used by this version of the
+ * application.
+ * @param aOldAppVersion
+ * The version of the application last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @param aOldPlatformVersion
+ * The version of the platform last run with this profile or null
+ * if it is a new profile or the version is unknown
+ * @return true if a change requiring a restart was detected
+ */
+ checkForChanges: function XPI_checkForChanges(aAppChanged, aOldAppVersion,
+ aOldPlatformVersion) {
+ logger.debug("checkForChanges");
+
+ // Keep track of whether and why we need to open and update the database at
+ // startup time.
+ let updateReasons = [];
+ if (aAppChanged) {
+ updateReasons.push("appChanged");
+ }
+
+ // Load the list of bootstrapped add-ons first so processFileChanges can
+ // modify it
+ // XXX eventually get rid of bootstrappedAddons
+ try {
+ this.bootstrappedAddons = JSON.parse(Preferences.get(PREF_BOOTSTRAP_ADDONS,
+ "{}"));
+ } catch (e) {
+ logger.warn("Error parsing enabled bootstrapped extensions cache", e);
+ }
+
+ // First install any new add-ons into the locations, if there are any
+ // changes then we must update the database with the information in the
+ // install locations
+ let manifests = {};
+ let updated = this.processPendingFileChanges(manifests);
+ if (updated) {
+ updateReasons.push("pendingFileChanges");
+ }
+
+ // This will be true if the previous session made changes that affect the
+ // active state of add-ons but didn't commit them properly (normally due
+ // to the application crashing)
+ let hasPendingChanges = Preferences.get(PREF_PENDING_OPERATIONS);
+ if (hasPendingChanges) {
+ updateReasons.push("hasPendingChanges");
+ }
+
+ // If the application has changed then check for new distribution add-ons
+ if (aAppChanged !== false &&
+ Preferences.get(PREF_INSTALL_DISTRO_ADDONS, true))
+ {
+ updated = this.installDistributionAddons(manifests);
+ if (updated) {
+ updateReasons.push("installDistributionAddons");
+ }
+ }
+
+ let installChanged = XPIStates.getInstallState();
+ if (installChanged) {
+ updateReasons.push("directoryState");
+ }
+
+ let haveAnyAddons = (XPIStates.size > 0);
+
+ // If the schema appears to have changed then we should update the database
+ if (DB_SCHEMA != Preferences.get(PREF_DB_SCHEMA, 0)) {
+ // If we don't have any add-ons, just update the pref, since we don't need to
+ // write the database
+ if (!haveAnyAddons) {
+ logger.debug("Empty XPI database, setting schema version preference to " + DB_SCHEMA);
+ Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
+ }
+ else {
+ updateReasons.push("schemaChanged");
+ }
+ }
+
+ // If the database doesn't exist and there are add-ons installed then we
+ // must update the database however if there are no add-ons then there is
+ // no need to update the database.
+ let dbFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
+ if (!dbFile.exists() && haveAnyAddons) {
+ updateReasons.push("needNewDatabase");
+ }
+
+ // XXX This will go away when we fold bootstrappedAddons into XPIStates.
+ if (updateReasons.length == 0) {
+ let bootstrapDescriptors = new Set([for (b of Object.keys(this.bootstrappedAddons))
+ this.bootstrappedAddons[b].descriptor]);
+
+ for (let location of XPIStates.db.values()) {
+ for (let state of location.values()) {
+ bootstrapDescriptors.delete(state.descriptor);
+ }
+ }
+
+ if (bootstrapDescriptors.size > 0) {
+ logger.warn("Bootstrap state is invalid (missing add-ons: "
+ + [for (b of bootstrapDescriptors) b] + ")");
+ updateReasons.push("missingBootstrapAddon");
+ }
+ }
+
+ // Catch and log any errors during the main startup
+ try {
+ let extensionListChanged = false;
+ // If the database needs to be updated then open it and then update it
+ // from the filesystem
+ if (updateReasons.length > 0) {
+ XPIDatabase.syncLoadDB(false);
+ try {
+ extensionListChanged = this.processFileChanges(manifests,
+ aAppChanged,
+ aOldAppVersion,
+ aOldPlatformVersion);
+ }
+ catch (e) {
+ logger.error("Failed to process extension changes at startup", e);
+ }
+ }
+
+ if (aAppChanged) {
+ // When upgrading the app and using a custom skin make sure it is still
+ // compatible otherwise switch back the default
+ if (this.currentSkin != this.defaultSkin) {
+ let oldSkin = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin);
+ if (!oldSkin || oldSkin.disabled)
+ this.enableDefaultTheme();
+ }
+
+ // When upgrading remove the old extensions cache to force older
+ // versions to rescan the entire list of extensions
+ try {
+ let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true);
+ if (oldCache.exists())
+ oldCache.remove(true);
+ }
+ catch (e) {
+ logger.warn("Unable to remove old extension cache " + oldCache.path, e);
+ }
+ }
+
+ // If the application crashed before completing any pending operations then
+ // we should perform them now.
+ if (extensionListChanged || hasPendingChanges) {
+ logger.debug("Updating database with changes to installed add-ons");
+ XPIDatabase.updateActiveAddons();
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
+ !XPIDatabase.writeAddonsList());
+ Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
+ JSON.stringify(this.bootstrappedAddons));
+ return true;
+ }
+
+ logger.debug("No changes found");
+ }
+ catch (e) {
+ logger.error("Error during startup file checks", e);
+ }
+
+ // Check that the add-ons list still exists
+ let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
+ true);
+ // the addons list file should exist if and only if we have add-ons installed
+ if (addonsList.exists() != haveAnyAddons) {
+ logger.debug("Add-ons list is invalid, rebuilding");
+ XPIDatabase.writeAddonsList();
+ }
+
+ return false;
+ },
+
+ /**
+ * Called to test whether this provider supports installing a particular
+ * mimetype.
+ *
+ * @param aMimetype
+ * The mimetype to check for
+ * @return true if the mimetype is application/x-xpinstall
+ */
+ supportsMimetype: function XPI_supportsMimetype(aMimetype) {
+ return aMimetype == "application/x-xpinstall";
+ },
+
+ /**
+ * Called to test whether installing XPI add-ons is enabled.
+ *
+ * @return true if installing is enabled
+ */
+ isInstallEnabled: function XPI_isInstallEnabled() {
+ // Default to enabled if the preference does not exist
+ return Preferences.get(PREF_XPI_ENABLED, true);
+ },
+
+ /**
+ * Called to test whether installing XPI add-ons by direct URL requests is
+ * whitelisted.
+ *
+ * @return true if installing by direct requests is whitelisted
+ */
+ isDirectRequestWhitelisted: function XPI_isDirectRequestWhitelisted() {
+ // Default to whitelisted if the preference does not exist.
+ return Preferences.get(PREF_XPI_DIRECT_WHITELISTED, true);
+ },
+
+ /**
+ * Called to test whether installing XPI add-ons from file referrers is
+ * whitelisted.
+ *
+ * @return true if installing from file referrers is whitelisted
+ */
+ isFileRequestWhitelisted: function XPI_isFileRequestWhitelisted() {
+ // Default to whitelisted if the preference does not exist.
+ return Preferences.get(PREF_XPI_FILE_WHITELISTED, true);
+ },
+
+ /**
+ * Called to test whether installing XPI add-ons from a URI is allowed.
+ *
+ * @param aInstallingPrincipal
+ * The nsIPrincipal that initiated the install
+ * @return true if installing is allowed
+ */
+ isInstallAllowed: function XPI_isInstallAllowed(aInstallingPrincipal) {
+ if (!this.isInstallEnabled())
+ return false;
+
+ let uri = aInstallingPrincipal.URI;
+
+ // Direct requests without a referrer are either whitelisted or blocked.
+ if (!uri)
+ return this.isDirectRequestWhitelisted();
+
+ // Local referrers can be whitelisted.
+ if (this.isFileRequestWhitelisted() &&
+ (uri.schemeIs("chrome") || uri.schemeIs("file")))
+ return true;
+
+ this.importPermissions();
+
+ let permission = Services.perms.testPermissionFromPrincipal(aInstallingPrincipal, XPI_PERMISSION);
+ if (permission == Ci.nsIPermissionManager.DENY_ACTION)
+ return false;
+
+ let requireWhitelist = Preferences.get(PREF_XPI_WHITELIST_REQUIRED, true);
+ if (requireWhitelist && (permission != Ci.nsIPermissionManager.ALLOW_ACTION))
+ return false;
+
+ let requireSecureOrigin = Preferences.get(PREF_INSTALL_REQUIRESECUREORIGIN, true);
+ let safeSchemes = ["https", "chrome", "file"];
+ if (requireSecureOrigin && safeSchemes.indexOf(uri.scheme) == -1)
+ return false;
+
+ return true;
+ },
+
+ /**
+ * Called to get an AddonInstall to download and install an add-on from a URL.
+ *
+ * @param aUrl
+ * The URL to be installed
+ * @param aHash
+ * A hash for the install
+ * @param aName
+ * A name for the install
+ * @param aIcons
+ * Icon URLs for the install
+ * @param aVersion
+ * A version for the install
+ * @param aBrowser
+ * The browser performing the install
+ * @param aCallback
+ * A callback to pass the AddonInstall to
+ */
+ getInstallForURL: function XPI_getInstallForURL(aUrl, aHash, aName, aIcons,
+ aVersion, aBrowser, aCallback) {
+ AddonInstall.createDownload(function getInstallForURL_createDownload(aInstall) {
+ aCallback(aInstall.wrapper);
+ }, aUrl, aHash, aName, aIcons, aVersion, aBrowser);
+ },
+
+ /**
+ * Called to get an AddonInstall to install an add-on from a local file.
+ *
+ * @param aFile
+ * The file to be installed
+ * @param aCallback
+ * A callback to pass the AddonInstall to
+ */
+ getInstallForFile: function XPI_getInstallForFile(aFile, aCallback) {
+ AddonInstall.createInstall(function getInstallForFile_createInstall(aInstall) {
+ if (aInstall)
+ aCallback(aInstall.wrapper);
+ else
+ aCallback(null);
+ }, aFile);
+ },
+
+ /**
+ * Removes an AddonInstall from the list of active installs.
+ *
+ * @param install
+ * The AddonInstall to remove
+ */
+ removeActiveInstall: function XPI_removeActiveInstall(aInstall) {
+ let where = this.installs.indexOf(aInstall);
+ if (where == -1) {
+ logger.warn("removeActiveInstall: could not find active install for "
+ + aInstall.sourceURI.spec);
+ return;
+ }
+ this.installs.splice(where, 1);
+ },
+
+ /**
+ * Called to get an Addon with a particular ID.
+ *
+ * @param aId
+ * The ID of the add-on to retrieve
+ * @param aCallback
+ * A callback to pass the Addon to
+ */
+ getAddonByID: function XPI_getAddonByID(aId, aCallback) {
+ XPIDatabase.getVisibleAddonForID (aId, function getAddonByID_getVisibleAddonForID(aAddon) {
+ aCallback(createWrapper(aAddon));
+ });
+ },
+
+ /**
+ * Called to get Addons of a particular type.
+ *
+ * @param aTypes
+ * An array of types to fetch. Can be null to get all types.
+ * @param aCallback
+ * A callback to pass an array of Addons to
+ */
+ getAddonsByTypes: function XPI_getAddonsByTypes(aTypes, aCallback) {
+ XPIDatabase.getVisibleAddons(aTypes, function getAddonsByTypes_getVisibleAddons(aAddons) {
+ // Tycho: aCallback([createWrapper(a) for each (a in aAddons)]);
+ let result = [];
+ for each(let a in aAddons) {
+ result.push(createWrapper(a));
+ }
+ aCallback(result);
+ });
+ },
+
+ /**
+ * Obtain an Addon having the specified Sync GUID.
+ *
+ * @param aGUID
+ * String GUID of add-on to retrieve
+ * @param aCallback
+ * A callback to pass the Addon to. Receives null if not found.
+ */
+ getAddonBySyncGUID: function XPI_getAddonBySyncGUID(aGUID, aCallback) {
+ XPIDatabase.getAddonBySyncGUID(aGUID, function getAddonBySyncGUID_getAddonBySyncGUID(aAddon) {
+ aCallback(createWrapper(aAddon));
+ });
+ },
+
+ /**
+ * Called to get Addons that have pending operations.
+ *
+ * @param aTypes
+ * An array of types to fetch. Can be null to get all types
+ * @param aCallback
+ * A callback to pass an array of Addons to
+ */
+ getAddonsWithOperationsByTypes:
+ function XPI_getAddonsWithOperationsByTypes(aTypes, aCallback) {
+ XPIDatabase.getVisibleAddonsWithPendingOperations(aTypes,
+ function getAddonsWithOpsByTypes_getVisibleAddonsWithPendingOps(aAddons) {
+ // Tycho: let results = [createWrapper(a) for each (a in aAddons)];
+ let results = [];
+ for each(let a in aAddons) {
+ results.push(createWrapper(a));
+ }
+
+ XPIProvider.installs.forEach(function(aInstall) {
+ if (aInstall.state == AddonManager.STATE_INSTALLED &&
+ !(aInstall.addon.inDatabase))
+ results.push(createWrapper(aInstall.addon));
+ });
+ aCallback(results);
+ });
+ },
+
+ /**
+ * Called to get the current AddonInstalls, optionally limiting to a list of
+ * types.
+ *
+ * @param aTypes
+ * An array of types or null to get all types
+ * @param aCallback
+ * A callback to pass the array of AddonInstalls to
+ */
+ getInstallsByTypes: function XPI_getInstallsByTypes(aTypes, aCallback) {
+ let results = [];
+ this.installs.forEach(function(aInstall) {
+ if (!aTypes || aTypes.indexOf(aInstall.type) >= 0)
+ results.push(aInstall.wrapper);
+ });
+ aCallback(results);
+ },
+
+ /**
+ * Synchronously map a URI to the corresponding Addon ID.
+ *
+ * Mappable URIs are limited to in-application resources belonging to the
+ * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc.
+ * but do not include URIs from meta data, such as the add-on homepage.
+ *
+ * @param aURI
+ * nsIURI to map or null
+ * @return string containing the Addon ID
+ * @see AddonManager.mapURIToAddonID
+ * @see amIAddonManager.mapURIToAddonID
+ */
+ mapURIToAddonID: function XPI_mapURIToAddonID(aURI) {
+ // Returns `null` instead of empty string if the URI can't be mapped.
+ return AddonPathService.mapURIToAddonId(aURI) || null;
+ },
+
+ /**
+ * Called when a new add-on has been enabled when only one add-on of that type
+ * can be enabled.
+ *
+ * @param aId
+ * The ID of the newly enabled add-on
+ * @param aType
+ * The type of the newly enabled add-on
+ * @param aPendingRestart
+ * true if the newly enabled add-on will only become enabled after a
+ * restart
+ */
+ addonChanged: function XPI_addonChanged(aId, aType, aPendingRestart) {
+ // We only care about themes in this provider
+ if (aType != "theme")
+ return;
+
+ if (!aId) {
+ // Fallback to the default theme when no theme was enabled
+ this.enableDefaultTheme();
+ return;
+ }
+
+ // Look for the previously enabled theme and find the internalName of the
+ // currently selected theme
+ let previousTheme = null;
+ let newSkin = this.defaultSkin;
+ let addons = XPIDatabase.getAddonsByType("theme");
+ addons.forEach(function(aTheme) {
+ if (!aTheme.visible)
+ return;
+ if (aTheme.id == aId)
+ newSkin = aTheme.internalName;
+ else if (aTheme.userDisabled == false && !aTheme.pendingUninstall)
+ previousTheme = aTheme;
+ }, this);
+
+ if (aPendingRestart) {
+ Services.prefs.setBoolPref(PREF_DSS_SWITCHPENDING, true);
+ Services.prefs.setCharPref(PREF_DSS_SKIN_TO_SELECT, newSkin);
+ }
+ else if (newSkin == this.currentSkin) {
+ try {
+ Services.prefs.clearUserPref(PREF_DSS_SWITCHPENDING);
+ }
+ catch (e) { }
+ try {
+ Services.prefs.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
+ }
+ catch (e) { }
+ }
+ else {
+ Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, newSkin);
+ this.currentSkin = newSkin;
+ }
+ this.selectedSkin = newSkin;
+
+ // Flush the preferences to disk so they don't get out of sync with the
+ // database
+ Services.prefs.savePrefFile(null);
+
+ // Mark the previous theme as disabled. This won't cause recursion since
+ // only enabled calls notifyAddonChanged.
+ if (previousTheme)
+ this.updateAddonDisabledState(previousTheme, true);
+ },
+
+ /**
+ * Update the appDisabled property for all add-ons.
+ */
+ updateAddonAppDisabledStates: function XPI_updateAddonAppDisabledStates() {
+ let addons = XPIDatabase.getAddons();
+ addons.forEach(function(aAddon) {
+ this.updateAddonDisabledState(aAddon);
+ }, this);
+ },
+
+ /**
+ * Update the repositoryAddon property for all add-ons.
+ *
+ * @param aCallback
+ * Function to call when operation is complete.
+ */
+ updateAddonRepositoryData: function XPI_updateAddonRepositoryData(aCallback) {
+ let self = this;
+ XPIDatabase.getVisibleAddons(null, function UARD_getVisibleAddonsCallback(aAddons) {
+ let pending = aAddons.length;
+ logger.debug("updateAddonRepositoryData found " + pending + " visible add-ons");
+ if (pending == 0) {
+ aCallback();
+ return;
+ }
+
+ function notifyComplete() {
+ if (--pending == 0)
+ aCallback();
+ }
+
+ for (let addon of aAddons) {
+ AddonRepository.getCachedAddonByID(addon.id,
+ function UARD_getCachedAddonCallback(aRepoAddon) {
+ if (aRepoAddon) {
+ logger.debug("updateAddonRepositoryData got info for " + addon.id);
+ addon._repositoryAddon = aRepoAddon;
+ addon.compatibilityOverrides = aRepoAddon.compatibilityOverrides;
+ self.updateAddonDisabledState(addon);
+ }
+
+ notifyComplete();
+ });
+ };
+ });
+ },
+
+ /**
+ * When the previously selected theme is removed this method will be called
+ * to enable the default theme.
+ */
+ enableDefaultTheme: function XPI_enableDefaultTheme() {
+ logger.debug("Activating default theme");
+ let addon = XPIDatabase.getVisibleAddonForInternalName(this.defaultSkin);
+ if (addon) {
+ if (addon.userDisabled) {
+ this.updateAddonDisabledState(addon, false);
+ }
+ else if (!this.extensionsActive) {
+ // During startup we may end up trying to enable the default theme when
+ // the database thinks it is already enabled (see f.e. bug 638847). In
+ // this case just force the theme preferences to be correct
+ Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
+ addon.internalName);
+ this.currentSkin = this.selectedSkin = addon.internalName;
+ Preferences.reset(PREF_DSS_SKIN_TO_SELECT);
+ Preferences.reset(PREF_DSS_SWITCHPENDING);
+ }
+ else {
+ logger.warn("Attempting to activate an already active default theme");
+ }
+ }
+ else {
+ logger.warn("Unable to activate the default theme");
+ }
+ },
+
+ onDebugConnectionChange: function(aEvent, aWhat, aConnection) {
+ if (aWhat != "opened")
+ return;
+
+ for (let id of Object.keys(this.bootstrapScopes)) {
+ aConnection.setAddonOptions(id, { global: this.bootstrapScopes[id] });
+ }
+ },
+
+ /**
+ * Notified when a preference we're interested in has changed.
+ *
+ * @see nsIObserver
+ */
+ observe: function XPI_observe(aSubject, aTopic, aData) {
+ if (aTopic == NOTIFICATION_FLUSH_PERMISSIONS) {
+ if (!aData || aData == XPI_PERMISSION) {
+ this.importPermissions();
+ }
+ return;
+ }
+#ifdef MOZ_DEVTOOLS
+ else if (aTopic == NOTIFICATION_TOOLBOXPROCESS_LOADED) {
+ Services.obs.removeObserver(this, NOTIFICATION_TOOLBOXPROCESS_LOADED, false);
+ this._toolboxProcessLoaded = true;
+ BrowserToolboxProcess.on("connectionchange",
+ this.onDebugConnectionChange.bind(this));
+ }
+#endif
+
+ if (aTopic == "nsPref:changed") {
+ switch (aData) {
+ case PREF_EM_MIN_COMPAT_APP_VERSION:
+ case PREF_EM_MIN_COMPAT_PLATFORM_VERSION:
+ this.minCompatibleAppVersion = Preferences.get(PREF_EM_MIN_COMPAT_APP_VERSION,
+ null);
+ this.minCompatiblePlatformVersion = Preferences.get(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
+ null);
+ this.updateAddonAppDisabledStates();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Tests whether enabling an add-on will require a restart.
+ *
+ * @param aAddon
+ * The add-on to test
+ * @return true if the operation requires a restart
+ */
+ enableRequiresRestart: function XPI_enableRequiresRestart(aAddon) {
+ // If the platform couldn't have activated extensions then we can make
+ // changes without any restart.
+ if (!this.extensionsActive)
+ return false;
+
+ // If the application is in safe mode then any change can be made without
+ // restarting
+ if (Services.appinfo.inSafeMode)
+ return false;
+
+ // Anything that is active is already enabled
+ if (aAddon.active)
+ return false;
+
+ if (aAddon.type == "theme") {
+ // If dynamic theme switching is enabled then switching themes does not
+ // require a restart
+ if (Preferences.get(PREF_EM_DSS_ENABLED))
+ return false;
+
+ // If the theme is already the theme in use then no restart is necessary.
+ // This covers the case where the default theme is in use but a
+ // lightweight theme is considered active.
+ return aAddon.internalName != this.currentSkin;
+ }
+
+ return !aAddon.bootstrap;
+ },
+
+ /**
+ * Tests whether disabling an add-on will require a restart.
+ *
+ * @param aAddon
+ * The add-on to test
+ * @return true if the operation requires a restart
+ */
+ disableRequiresRestart: function XPI_disableRequiresRestart(aAddon) {
+ // If the platform couldn't have activated up extensions then we can make
+ // changes without any restart.
+ if (!this.extensionsActive)
+ return false;
+
+ // If the application is in safe mode then any change can be made without
+ // restarting
+ if (Services.appinfo.inSafeMode)
+ return false;
+
+ // Anything that isn't active is already disabled
+ if (!aAddon.active)
+ return false;
+
+ if (aAddon.type == "theme") {
+ // If dynamic theme switching is enabled then switching themes does not
+ // require a restart
+ if (Preferences.get(PREF_EM_DSS_ENABLED))
+ return false;
+
+ // Non-default themes always require a restart to disable since it will
+ // be switching from one theme to another or to the default theme and a
+ // lightweight theme.
+ if (aAddon.internalName != this.defaultSkin)
+ return true;
+
+ // The default theme requires a restart to disable if we are in the
+ // process of switching to a different theme. Note that this makes the
+ // disabled flag of operationsRequiringRestart incorrect for the default
+ // theme (it will be false most of the time). Bug 520124 would be required
+ // to fix it. For the UI this isn't a problem since we never try to
+ // disable or uninstall the default theme.
+ return this.selectedSkin != this.currentSkin;
+ }
+
+ return !aAddon.bootstrap;
+ },
+
+ /**
+ * Tests whether installing an add-on will require a restart.
+ *
+ * @param aAddon
+ * The add-on to test
+ * @return true if the operation requires a restart
+ */
+ installRequiresRestart: function XPI_installRequiresRestart(aAddon) {
+ // If the platform couldn't have activated up extensions then we can make
+ // changes without any restart.
+ if (!this.extensionsActive)
+ return false;
+
+ // If the application is in safe mode then any change can be made without
+ // restarting
+ if (Services.appinfo.inSafeMode)
+ return false;
+
+ // Add-ons that are already installed don't require a restart to install.
+ // This wouldn't normally be called for an already installed add-on (except
+ // for forming the operationsRequiringRestart flags) so is really here as
+ // a safety measure.
+ if (aAddon.inDatabase)
+ return false;
+
+ // If we have an AddonInstall for this add-on then we can see if there is
+ // an existing installed add-on with the same ID
+ if ("_install" in aAddon && aAddon._install) {
+ // If there is an existing installed add-on and uninstalling it would
+ // require a restart then installing the update will also require a
+ // restart
+ let existingAddon = aAddon._install.existingAddon;
+ if (existingAddon && this.uninstallRequiresRestart(existingAddon))
+ return true;
+ }
+
+ // If the add-on is not going to be active after installation then it
+ // doesn't require a restart to install.
+ if (aAddon.disabled)
+ return false;
+
+ // Themes will require a restart (even if dynamic switching is enabled due
+ // to some caching issues) and non-bootstrapped add-ons will require a
+ // restart
+ return aAddon.type == "theme" || !aAddon.bootstrap;
+ },
+
+ /**
+ * Tests whether uninstalling an add-on will require a restart.
+ *
+ * @param aAddon
+ * The add-on to test
+ * @return true if the operation requires a restart
+ */
+ uninstallRequiresRestart: function XPI_uninstallRequiresRestart(aAddon) {
+ // If the platform couldn't have activated up extensions then we can make
+ // changes without any restart.
+ if (!this.extensionsActive)
+ return false;
+
+ // If the application is in safe mode then any change can be made without
+ // restarting
+ if (Services.appinfo.inSafeMode)
+ return false;
+
+ // If the add-on can be disabled without a restart then it can also be
+ // uninstalled without a restart
+ return this.disableRequiresRestart(aAddon);
+ },
+
+ /**
+ * Loads a bootstrapped add-on's bootstrap.js into a sandbox and the reason
+ * values as constants in the scope. This will also add information about the
+ * add-on to the bootstrappedAddons dictionary and notify the crash reporter
+ * that new add-ons have been loaded.
+ *
+ * @param aId
+ * The add-on's ID
+ * @param aFile
+ * The nsIFile for the add-on
+ * @param aVersion
+ * The add-on's version
+ * @param aType
+ * The type for the add-on
+ * @param aMultiprocessCompatible
+ * Boolean indicating whether the add-on is compatible with electrolysis.
+ * @return a JavaScript scope
+ */
+ loadBootstrapScope: function XPI_loadBootstrapScope(aId, aFile, aVersion, aType,
+ aMultiprocessCompatible) {
+ // Mark the add-on as active for the crash reporter before loading
+ this.bootstrappedAddons[aId] = {
+ version: aVersion,
+ type: aType,
+ descriptor: aFile.persistentDescriptor,
+ multiprocessCompatible: aMultiprocessCompatible
+ };
+ this.persistBootstrappedAddons();
+ this.addAddonsToCrashReporter();
+
+ // Locales only contain chrome and can't have bootstrap scripts
+ if (aType == "locale") {
+ this.bootstrapScopes[aId] = null;
+ return;
+ }
+
+ logger.debug("Loading bootstrap scope from " + aFile.path);
+
+ let principal = Cc["@mozilla.org/systemprincipal;1"].
+ createInstance(Ci.nsIPrincipal);
+ if (!aMultiprocessCompatible && Preferences.get(PREF_INTERPOSITION_ENABLED, false)) {
+ let interposition = Cc["@mozilla.org/addons/multiprocess-shims;1"].
+ getService(Ci.nsIAddonInterposition);
+ Cu.setAddonInterposition(aId, interposition);
+ }
+
+ if (!aFile.exists()) {
+ this.bootstrapScopes[aId] =
+ new Cu.Sandbox(principal, { sandboxName: aFile.path,
+ wantGlobalProperties: ["indexedDB"],
+ addonId: aId,
+ metadata: { addonID: aId } });
+ logger.error("Attempted to load bootstrap scope from missing directory " + aFile.path);
+ return;
+ }
+
+ let uri = getURIForResourceInFile(aFile, "bootstrap.js").spec;
+ if (aType == "dictionary")
+ uri = "resource://gre/modules/addons/SpellCheckDictionaryBootstrap.js"
+
+ this.bootstrapScopes[aId] =
+ new Cu.Sandbox(principal, { sandboxName: uri,
+ wantGlobalProperties: ["indexedDB"],
+ addonId: aId,
+ metadata: { addonID: aId, URI: uri } });
+
+ let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ createInstance(Ci.mozIJSSubScriptLoader);
+
+ try {
+ // Copy the reason values from the global object into the bootstrap scope.
+ for (let name in BOOTSTRAP_REASONS)
+ this.bootstrapScopes[aId][name] = BOOTSTRAP_REASONS[name];
+
+ // Add other stuff that extensions want.
+ const features = [ "Worker", "ChromeWorker" ];
+
+ for (let feature of features)
+ this.bootstrapScopes[aId][feature] = gGlobalScope[feature];
+
+ // Define a console for the add-on
+ this.bootstrapScopes[aId]["console"] = new ConsoleAPI({ consoleID: "addon/" + aId });
+
+ // As we don't want our caller to control the JS version used for the
+ // bootstrap file, we run loadSubScript within the context of the
+ // sandbox with the latest JS version set explicitly.
+ this.bootstrapScopes[aId].__SCRIPT_URI_SPEC__ = uri;
+ Components.utils.evalInSandbox(
+ "Components.classes['@mozilla.org/moz/jssubscript-loader;1'] \
+ .createInstance(Components.interfaces.mozIJSSubScriptLoader) \
+ .loadSubScript(__SCRIPT_URI_SPEC__);", this.bootstrapScopes[aId], "ECMAv5");
+ }
+ catch (e) {
+ logger.warn("Error loading bootstrap.js for " + aId, e);
+ }
+
+#ifdef MOZ_DEVTOOLS
+ // Only access BrowserToolboxProcess if ToolboxProcess.jsm has been
+ // initialized as otherwise, when it will be initialized, all addons'
+ // globals will be added anyways
+ if (this._toolboxProcessLoaded) {
+ BrowserToolboxProcess.setAddonOptions(aId, { global: this.bootstrapScopes[aId] });
+ }
+#endif
+ },
+
+ /**
+ * Unloads a bootstrap scope by dropping all references to it and then
+ * updating the list of active add-ons with the crash reporter.
+ *
+ * @param aId
+ * The add-on's ID
+ */
+ unloadBootstrapScope: function XPI_unloadBootstrapScope(aId) {
+ // In case the add-on was not multiprocess-compatible, deregister
+ // any interpositions for it.
+ Cu.setAddonInterposition(aId, null);
+
+ delete this.bootstrapScopes[aId];
+ delete this.bootstrappedAddons[aId];
+ this.persistBootstrappedAddons();
+ this.addAddonsToCrashReporter();
+
+#ifdef MOZ_DEVTOOLS
+ // Only access BrowserToolboxProcess if ToolboxProcess.jsm has been
+ // initialized as otherwise, there won't be any addon globals added to it
+ if (this._toolboxProcessLoaded) {
+ BrowserToolboxProcess.setAddonOptions(aId, { global: null });
+ }
+#endif
+ },
+
+ /**
+ * Calls a bootstrap method for an add-on.
+ *
+ * @param aAddon
+ * An object representing the add-on, with `id`, `type` and `version`
+ * @param aFile
+ * The nsIFile for the add-on
+ * @param aMethod
+ * The name of the bootstrap method to call
+ * @param aReason
+ * The reason flag to pass to the bootstrap's startup method
+ * @param aExtraParams
+ * An object of additional key/value pairs to pass to the method in
+ * the params argument
+ */
+ callBootstrapMethod: function XPI_callBootstrapMethod(aAddon, aFile, aMethod, aReason, aExtraParams) {
+ // Never call any bootstrap methods in safe mode
+ if (Services.appinfo.inSafeMode)
+ return;
+
+ if (!aAddon.id || !aAddon.version || !aAddon.type) {
+ logger.error(new Error("aAddon must include an id, version, and type"));
+ return;
+ }
+
+ let timeStart = new Date();
+ if (aMethod == "startup") {
+ logger.debug("Registering manifest for " + aFile.path);
+ Components.manager.addBootstrappedManifestLocation(aFile);
+ }
+
+ try {
+ // Load the scope if it hasn't already been loaded
+ if (!(aAddon.id in this.bootstrapScopes))
+ this.loadBootstrapScope(aAddon.id, aFile, aAddon.version, aAddon.type,
+ aAddon.multiprocessCompatible || false);
+
+ // Nothing to call for locales
+ if (aAddon.type == "locale")
+ return;
+
+ let method = undefined;
+ try {
+ method = Components.utils.evalInSandbox(`${aMethod};`,
+ this.bootstrapScopes[aAddon.id],
+ "ECMAv5");
+ }
+ catch (e) {
+ // An exception will be caught if the expected method is not defined.
+ // That will be logged below.
+ }
+
+ if (!method) {
+ logger.warn("Add-on " + aAddon.id + " is missing bootstrap method " + aMethod);
+ return;
+ }
+
+ let params = {
+ id: aAddon.id,
+ version: aAddon.version,
+ installPath: aFile.clone(),
+ resourceURI: getURIForResourceInFile(aFile, "")
+ };
+
+ if (aExtraParams) {
+ for (let key in aExtraParams) {
+ params[key] = aExtraParams[key];
+ }
+ }
+
+ logger.debug("Calling bootstrap method " + aMethod + " on " + aAddon.id + " version " +
+ aAddon.version);
+ try {
+ method(params, aReason);
+ }
+ catch (e) {
+ logger.warn("Exception running bootstrap method " + aMethod + " on " + aAddon.id, e);
+ }
+ }
+ finally {
+ if (aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
+ logger.debug("Removing manifest for " + aFile.path);
+ Components.manager.removeBootstrappedManifestLocation(aFile);
+
+ let manifest = getURIForResourceInFile(aFile, "chrome.manifest");
+ for (let line of ChromeManifestParser.parseSync(manifest)) {
+ if (line.type == "resource") {
+ ResProtocolHandler.setSubstitution(line.args[0], null);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Updates the disabled state for an add-on. Its appDisabled property will be
+ * calculated and if the add-on is changed the database will be saved and
+ * appropriate notifications will be sent out to the registered AddonListeners.
+ *
+ * @param aAddon
+ * The DBAddonInternal to update
+ * @param aUserDisabled
+ * Value for the userDisabled property. If undefined the value will
+ * not change
+ * @param aSoftDisabled
+ * Value for the softDisabled property. If undefined the value will
+ * not change. If true this will force userDisabled to be true
+ * @throws if addon is not a DBAddonInternal
+ */
+ updateAddonDisabledState: function XPI_updateAddonDisabledState(aAddon,
+ aUserDisabled,
+ aSoftDisabled) {
+ if (!(aAddon.inDatabase))
+ throw new Error("Can only update addon states for installed addons.");
+ if (aUserDisabled !== undefined && aSoftDisabled !== undefined) {
+ throw new Error("Cannot change userDisabled and softDisabled at the " +
+ "same time");
+ }
+
+ if (aUserDisabled === undefined) {
+ aUserDisabled = aAddon.userDisabled;
+ }
+ else if (!aUserDisabled) {
+ // If enabling the add-on then remove softDisabled
+ aSoftDisabled = false;
+ }
+
+ // If not changing softDisabled or the add-on is already userDisabled then
+ // use the existing value for softDisabled
+ if (aSoftDisabled === undefined || aUserDisabled)
+ aSoftDisabled = aAddon.softDisabled;
+
+ let appDisabled = !isUsableAddon(aAddon);
+ // No change means nothing to do here
+ if (aAddon.userDisabled == aUserDisabled &&
+ aAddon.appDisabled == appDisabled &&
+ aAddon.softDisabled == aSoftDisabled)
+ return;
+
+ let wasDisabled = aAddon.disabled;
+ let isDisabled = aUserDisabled || aSoftDisabled || appDisabled;
+
+ // If appDisabled changes but addon.disabled doesn't,
+ // no onDisabling/onEnabling is sent - so send a onPropertyChanged.
+ let appDisabledChanged = aAddon.appDisabled != appDisabled;
+
+ // Update the properties in the database.
+ XPIDatabase.setAddonProperties(aAddon, {
+ userDisabled: aUserDisabled,
+ appDisabled: appDisabled,
+ softDisabled: aSoftDisabled
+ });
+
+ if (appDisabledChanged) {
+ AddonManagerPrivate.callAddonListeners("onPropertyChanged",
+ aAddon,
+ ["appDisabled"]);
+ }
+
+ // If the add-on is not visible or the add-on is not changing state then
+ // there is no need to do anything else
+ if (!aAddon.visible || (wasDisabled == isDisabled))
+ return;
+
+ // Flag that active states in the database need to be updated on shutdown
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+
+ let wrapper = createWrapper(aAddon);
+ // Have we just gone back to the current state?
+ if (isDisabled != aAddon.active) {
+ AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
+ }
+ else {
+ if (isDisabled) {
+ var needsRestart = this.disableRequiresRestart(aAddon);
+ AddonManagerPrivate.callAddonListeners("onDisabling", wrapper,
+ needsRestart);
+ }
+ else {
+ needsRestart = this.enableRequiresRestart(aAddon);
+ AddonManagerPrivate.callAddonListeners("onEnabling", wrapper,
+ needsRestart);
+ }
+
+ if (!needsRestart) {
+ XPIDatabase.updateAddonActive(aAddon, !isDisabled);
+ if (isDisabled) {
+ if (aAddon.bootstrap) {
+ let file = aAddon._installLocation.getLocationForID(aAddon.id);
+ this.callBootstrapMethod(aAddon, file, "shutdown",
+ BOOTSTRAP_REASONS.ADDON_DISABLE);
+ this.unloadBootstrapScope(aAddon.id);
+ }
+ AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
+ }
+ else {
+ if (aAddon.bootstrap) {
+ let file = aAddon._installLocation.getLocationForID(aAddon.id);
+ this.callBootstrapMethod(aAddon, file, "startup",
+ BOOTSTRAP_REASONS.ADDON_ENABLE);
+ }
+ AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
+ }
+ }
+ }
+
+ // Sync with XPIStates.
+ let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id);
+ if (xpiState) {
+ xpiState.syncWithDB(aAddon);
+ XPIStates.save();
+ } else {
+ // There should always be an xpiState
+ logger.warn("No XPIState for ${id} in ${location}", aAddon);
+ }
+
+ // Notify any other providers that a new theme has been enabled
+ if (aAddon.type == "theme" && !isDisabled)
+ AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, needsRestart);
+ },
+
+ /**
+ * Uninstalls an add-on, immediately if possible or marks it as pending
+ * uninstall if not.
+ *
+ * @param aAddon
+ * The DBAddonInternal to uninstall
+ * @param aForcePending
+ * Force this addon into the pending uninstall state, even if
+ * it isn't marked as requiring a restart (used e.g. while the
+ * add-on manager is open and offering an "undo" button)
+ * @throws if the addon cannot be uninstalled because it is in an install
+ * location that does not allow it
+ */
+ uninstallAddon: function XPI_uninstallAddon(aAddon, aForcePending) {
+ if (!(aAddon.inDatabase))
+ throw new Error("Cannot uninstall addon " + aAddon.id + " because it is not installed");
+
+ if (aAddon._installLocation.locked)
+ throw new Error("Cannot uninstall addon " + aAddon.id
+ + " from locked install location " + aAddon._installLocation.name);
+
+ // Inactive add-ons don't require a restart to uninstall
+ let requiresRestart = this.uninstallRequiresRestart(aAddon);
+
+ // if makePending is true, we don't actually apply the uninstall,
+ // we just mark the addon as having a pending uninstall
+ let makePending = aForcePending || requiresRestart;
+
+ if (makePending && aAddon.pendingUninstall)
+ throw new Error("Add-on is already marked to be uninstalled");
+
+ if ("_hasResourceCache" in aAddon)
+ aAddon._hasResourceCache = new Map();
+
+ if (aAddon._updateCheck) {
+ logger.debug("Cancel in-progress update check for " + aAddon.id);
+ aAddon._updateCheck.cancel();
+ }
+
+ let wasPending = aAddon.pendingUninstall;
+
+ if (makePending) {
+ // We create an empty directory in the staging directory to indicate
+ // that an uninstall is necessary on next startup.
+ let stage = aAddon._installLocation.getStagingDir();
+ stage.append(aAddon.id);
+ if (!stage.exists())
+ stage.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+ XPIDatabase.setAddonProperties(aAddon, {
+ pendingUninstall: true
+ });
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+ let xpiState = XPIStates.getAddon(aAddon.location, aAddon.id);
+ if (xpiState) {
+ xpiState.enabled = false;
+ XPIStates.save();
+ } else {
+ logger.warn("Can't find XPI state while uninstalling ${id} from ${location}", aAddon);
+ }
+ }
+
+ // If the add-on is not visible then there is no need to notify listeners.
+ if (!aAddon.visible)
+ return;
+
+ let wrapper = createWrapper(aAddon);
+
+ // If the add-on wasn't already pending uninstall then notify listeners.
+ if (!wasPending) {
+ // Passing makePending as the requiresRestart parameter is a little
+ // strange as in some cases this operation can complete without a restart
+ // so really this is now saying that the uninstall isn't going to happen
+ // immediately but will happen later.
+ AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper,
+ makePending);
+ }
+
+ // Reveal the highest priority add-on with the same ID
+ function revealAddon(aAddon) {
+ XPIDatabase.makeAddonVisible(aAddon);
+
+ let wrappedAddon = createWrapper(aAddon);
+ AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false);
+
+ if (!aAddon.disabled && !XPIProvider.enableRequiresRestart(aAddon)) {
+ XPIDatabase.updateAddonActive(aAddon, true);
+ }
+
+ if (aAddon.bootstrap) {
+ let file = aAddon._installLocation.getLocationForID(aAddon.id);
+ XPIProvider.callBootstrapMethod(aAddon, file,
+ "install", BOOTSTRAP_REASONS.ADDON_INSTALL);
+
+ if (aAddon.active) {
+ XPIProvider.callBootstrapMethod(aAddon, file,
+ "startup", BOOTSTRAP_REASONS.ADDON_INSTALL);
+ }
+ else {
+ XPIProvider.unloadBootstrapScope(aAddon.id);
+ }
+ }
+
+ // We always send onInstalled even if a restart is required to enable
+ // the revealed add-on
+ AddonManagerPrivate.callAddonListeners("onInstalled", wrappedAddon);
+ }
+
+ function findAddonAndReveal(aId) {
+ let [locationName, ] = XPIStates.findAddon(aId);
+ if (locationName) {
+ XPIDatabase.getAddonInLocation(aId, locationName, revealAddon);
+ }
+ }
+
+ if (!makePending) {
+ if (aAddon.bootstrap) {
+ let file = aAddon._installLocation.getLocationForID(aAddon.id);
+ if (aAddon.active) {
+ this.callBootstrapMethod(aAddon, file, "shutdown",
+ BOOTSTRAP_REASONS.ADDON_UNINSTALL);
+ }
+
+ this.callBootstrapMethod(aAddon, file, "uninstall",
+ BOOTSTRAP_REASONS.ADDON_UNINSTALL);
+ this.unloadBootstrapScope(aAddon.id);
+ flushChromeCaches();
+ }
+ aAddon._installLocation.uninstallAddon(aAddon.id);
+ XPIDatabase.removeAddonMetadata(aAddon);
+ XPIStates.removeAddon(aAddon.location, aAddon.id);
+ AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
+
+ findAddonAndReveal(aAddon.id);
+ }
+ else if (aAddon.bootstrap && aAddon.active && !this.disableRequiresRestart(aAddon)) {
+ this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "shutdown",
+ BOOTSTRAP_REASONS.ADDON_UNINSTALL);
+ this.unloadBootstrapScope(aAddon.id);
+ XPIDatabase.updateAddonActive(aAddon, false);
+ }
+
+ // Notify any other providers that a new theme has been enabled
+ if (aAddon.type == "theme" && aAddon.active)
+ AddonManagerPrivate.notifyAddonChanged(null, aAddon.type, requiresRestart);
+ },
+
+ /**
+ * Cancels the pending uninstall of an add-on.
+ *
+ * @param aAddon
+ * The DBAddonInternal to cancel uninstall for
+ */
+ cancelUninstallAddon: function XPI_cancelUninstallAddon(aAddon) {
+ if (!(aAddon.inDatabase))
+ throw new Error("Can only cancel uninstall for installed addons.");
+
+ if (!aAddon.pendingUninstall)
+ throw new Error("Add-on is not marked to be uninstalled");
+
+ aAddon._installLocation.cleanStagingDir([aAddon.id]);
+
+ XPIDatabase.setAddonProperties(aAddon, {
+ pendingUninstall: false
+ });
+
+ if (!aAddon.visible)
+ return;
+
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+
+ // TODO hide hidden add-ons (bug 557710)
+ let wrapper = createWrapper(aAddon);
+ AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
+
+ if (aAddon.bootstrap && !aAddon.disabled && !this.enableRequiresRestart(aAddon)) {
+ this.callBootstrapMethod(aAddon, aAddon._sourceBundle, "startup",
+ BOOTSTRAP_REASONS.ADDON_INSTALL);
+ XPIDatabase.updateAddonActive(aAddon, true);
+ }
+
+ // Notify any other providers that this theme is now enabled again.
+ if (aAddon.type == "theme" && aAddon.active)
+ AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
+ }
+};
+
+function getHashStringForCrypto(aCrypto) {
+ // return the two-digit hexadecimal code for a byte
+ let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2);
+
+ // convert the binary hash data to a hex string.
+ let binary = aCrypto.finish(false);
+ let hash = Array.from(binary, c => toHexString(c.charCodeAt(0)))
+ return hash.join("").toLowerCase();
+}
+
+/**
+ * Instantiates an AddonInstall.
+ *
+ * @param aInstallLocation
+ * The install location the add-on will be installed into
+ * @param aUrl
+ * The nsIURL to get the add-on from. If this is an nsIFileURL then
+ * the add-on will not need to be downloaded
+ * @param aHash
+ * An optional hash for the add-on
+ * @param aReleaseNotesURI
+ * An optional nsIURI of release notes for the add-on
+ * @param aExistingAddon
+ * The add-on this install will update if known
+ * @param aBrowser
+ * The browser performing the install
+ * @throws if the url is the url of a local file and the hash does not match
+ * or the add-on does not contain an valid install manifest
+ */
+function AddonInstall(aInstallLocation, aUrl, aHash, aReleaseNotesURI,
+ aExistingAddon, aBrowser) {
+ this.wrapper = new AddonInstallWrapper(this);
+ this.installLocation = aInstallLocation;
+ this.sourceURI = aUrl;
+ this.releaseNotesURI = aReleaseNotesURI;
+ if (aHash) {
+ let hashSplit = aHash.toLowerCase().split(":");
+ this.originalHash = {
+ algorithm: hashSplit[0],
+ data: hashSplit[1]
+ };
+ }
+ this.hash = this.originalHash;
+ this.browser = aBrowser;
+ this.listeners = [];
+ this.icons = {};
+ this.existingAddon = aExistingAddon;
+ this.error = 0;
+ this.window = aBrowser ? aBrowser.contentWindow : null;
+
+ // Giving each instance of AddonInstall a reference to the logger.
+ this.logger = logger;
+}
+
+AddonInstall.prototype = {
+ installLocation: null,
+ wrapper: null,
+ stream: null,
+ crypto: null,
+ originalHash: null,
+ hash: null,
+ browser: null,
+ badCertHandler: null,
+ listeners: null,
+ restartDownload: false,
+
+ name: null,
+ type: null,
+ version: null,
+ icons: null,
+ releaseNotesURI: null,
+ sourceURI: null,
+ file: null,
+ ownsTempFile: false,
+ certificate: null,
+ certName: null,
+
+ linkedInstalls: null,
+ existingAddon: null,
+ addon: null,
+
+ state: null,
+ error: null,
+ progress: null,
+ maxProgress: null,
+
+ /**
+ * Initialises this install to be a staged install waiting to be applied
+ *
+ * @param aManifest
+ * The cached manifest for the staged install
+ */
+ initStagedInstall: function AI_initStagedInstall(aManifest) {
+ this.name = aManifest.name;
+ this.type = aManifest.type;
+ this.version = aManifest.version;
+ this.icons = aManifest.icons;
+ this.releaseNotesURI = aManifest.releaseNotesURI ?
+ NetUtil.newURI(aManifest.releaseNotesURI) :
+ null
+ this.sourceURI = aManifest.sourceURI ?
+ NetUtil.newURI(aManifest.sourceURI) :
+ null;
+ this.file = null;
+ this.addon = aManifest;
+
+ this.state = AddonManager.STATE_INSTALLED;
+
+ XPIProvider.installs.push(this);
+ },
+
+ /**
+ * Initialises this install to be an install from a local file.
+ *
+ * @param aCallback
+ * The callback to pass the initialised AddonInstall to
+ */
+ initLocalInstall: function AI_initLocalInstall(aCallback) {
+ aCallback = makeSafe(aCallback);
+ this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;
+
+ if (!this.file.exists()) {
+ logger.warn("XPI file " + this.file.path + " does not exist");
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ this.error = AddonManager.ERROR_NETWORK_FAILURE;
+ aCallback(this);
+ return;
+ }
+
+ this.state = AddonManager.STATE_DOWNLOADED;
+ this.progress = this.file.fileSize;
+ this.maxProgress = this.file.fileSize;
+
+ if (this.hash) {
+ let crypto = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ try {
+ crypto.initWithString(this.hash.algorithm);
+ }
+ catch (e) {
+ logger.warn("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ this.error = AddonManager.ERROR_INCORRECT_HASH;
+ aCallback(this);
+ return;
+ }
+
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fis.init(this.file, -1, -1, false);
+ crypto.updateFromStream(fis, this.file.fileSize);
+ let calculatedHash = getHashStringForCrypto(crypto);
+ if (calculatedHash != this.hash.data) {
+ logger.warn("File hash (" + calculatedHash + ") did not match provided hash (" +
+ this.hash.data + ")");
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ this.error = AddonManager.ERROR_INCORRECT_HASH;
+ aCallback(this);
+ return;
+ }
+ }
+
+ try {
+ let self = this;
+ this.loadManifest(function initLocalInstall_loadManifest() {
+ XPIDatabase.getVisibleAddonForID(self.addon.id, function initLocalInstall_getVisibleAddon(aAddon) {
+ self.existingAddon = aAddon;
+ if (aAddon)
+ applyBlocklistChanges(aAddon, self.addon);
+ self.addon.updateDate = Date.now();
+ self.addon.installDate = aAddon ? aAddon.installDate : self.addon.updateDate;
+
+ if (!self.addon.isCompatible) {
+ // TODO Should we send some event here?
+ self.state = AddonManager.STATE_CHECKING;
+ new UpdateChecker(self.addon, {
+ onUpdateFinished: function updateChecker_onUpdateFinished(aAddon) {
+ self.state = AddonManager.STATE_DOWNLOADED;
+ XPIProvider.installs.push(self);
+ AddonManagerPrivate.callInstallListeners("onNewInstall",
+ self.listeners,
+ self.wrapper);
+
+ aCallback(self);
+ }
+ }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
+ }
+ else {
+ XPIProvider.installs.push(self);
+ AddonManagerPrivate.callInstallListeners("onNewInstall",
+ self.listeners,
+ self.wrapper);
+
+ aCallback(self);
+ }
+ });
+ });
+ }
+ catch (e) {
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ if (e.webext) {
+ logger.warn("WebExtension XPI", e);
+ this.error = AddonManager.ERROR_WEBEXT_FILE;
+#ifndef MOZ_JETPACK
+ } else if (e.jetpacksdk) {
+ logger.warn("Jetpack XPI", e);
+ this.error = AddonManager.ERROR_JETPACKSDK_FILE;
+#endif
+ } else {
+ logger.warn("Invalid XPI", e);
+ this.error = AddonManager.ERROR_CORRUPT_FILE;
+ }
+ aCallback(this);
+ return;
+ }
+ },
+
+ /**
+ * Initialises this install to be a download from a remote url.
+ *
+ * @param aCallback
+ * The callback to pass the initialised AddonInstall to
+ * @param aName
+ * An optional name for the add-on
+ * @param aType
+ * An optional type for the add-on
+ * @param aIcons
+ * Optional icons for the add-on
+ * @param aVersion
+ * An optional version for the add-on
+ */
+ initAvailableDownload: function AI_initAvailableDownload(aName, aType, aIcons, aVersion, aCallback) {
+ this.state = AddonManager.STATE_AVAILABLE;
+ this.name = aName;
+ this.type = aType;
+ this.version = aVersion;
+ this.icons = aIcons;
+ this.progress = 0;
+ this.maxProgress = -1;
+
+ XPIProvider.installs.push(this);
+ AddonManagerPrivate.callInstallListeners("onNewInstall", this.listeners,
+ this.wrapper);
+
+ makeSafe(aCallback)(this);
+ },
+
+ /**
+ * Starts installation of this add-on from whatever state it is currently at
+ * if possible.
+ *
+ * @throws if installation cannot proceed from the current state
+ */
+ install: function AI_install() {
+ switch (this.state) {
+ case AddonManager.STATE_AVAILABLE:
+ this.startDownload();
+ break;
+ case AddonManager.STATE_DOWNLOADED:
+ this.startInstall();
+ break;
+ case AddonManager.STATE_DOWNLOAD_FAILED:
+ case AddonManager.STATE_INSTALL_FAILED:
+ case AddonManager.STATE_CANCELLED:
+ this.removeTemporaryFile();
+ this.state = AddonManager.STATE_AVAILABLE;
+ this.error = 0;
+ this.progress = 0;
+ this.maxProgress = -1;
+ this.hash = this.originalHash;
+ XPIProvider.installs.push(this);
+ this.startDownload();
+ break;
+ case AddonManager.STATE_DOWNLOADING:
+ case AddonManager.STATE_CHECKING:
+ case AddonManager.STATE_INSTALLING:
+ // Installation is already running
+ return;
+ default:
+ throw new Error("Cannot start installing from this state");
+ }
+ },
+
+ /**
+ * Cancels installation of this add-on.
+ *
+ * @throws if installation cannot be cancelled from the current state
+ */
+ cancel: function AI_cancel() {
+ switch (this.state) {
+ case AddonManager.STATE_DOWNLOADING:
+ if (this.channel) {
+ logger.debug("Cancelling download of " + this.sourceURI.spec);
+ this.channel.cancel(Cr.NS_BINDING_ABORTED);
+ }
+ break;
+ case AddonManager.STATE_AVAILABLE:
+ case AddonManager.STATE_DOWNLOADED:
+ logger.debug("Cancelling download of " + this.sourceURI.spec);
+ this.state = AddonManager.STATE_CANCELLED;
+ XPIProvider.removeActiveInstall(this);
+ AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
+ this.listeners, this.wrapper);
+ this.removeTemporaryFile();
+ break;
+ case AddonManager.STATE_INSTALLED:
+ logger.debug("Cancelling install of " + this.addon.id);
+ let xpi = this.installLocation.getStagingDir();
+ xpi.append(this.addon.id + ".xpi");
+ flushJarCache(xpi);
+ this.installLocation.cleanStagingDir([this.addon.id, this.addon.id + ".xpi",
+ this.addon.id + ".json"]);
+ this.state = AddonManager.STATE_CANCELLED;
+ XPIProvider.removeActiveInstall(this);
+
+ if (this.existingAddon) {
+ delete this.existingAddon.pendingUpgrade;
+ this.existingAddon.pendingUpgrade = null;
+ }
+
+ AddonManagerPrivate.callAddonListeners("onOperationCancelled", createWrapper(this.addon));
+
+ AddonManagerPrivate.callInstallListeners("onInstallCancelled",
+ this.listeners, this.wrapper);
+ break;
+ default:
+ throw new Error("Cannot cancel install of " + this.sourceURI.spec +
+ " from this state (" + this.state + ")");
+ }
+ },
+
+ /**
+ * Adds an InstallListener for this instance if the listener is not already
+ * registered.
+ *
+ * @param aListener
+ * The InstallListener to add
+ */
+ addListener: function AI_addListener(aListener) {
+ if (!this.listeners.some(function addListener_matchListener(i) { return i == aListener; }))
+ this.listeners.push(aListener);
+ },
+
+ /**
+ * Removes an InstallListener for this instance if it is registered.
+ *
+ * @param aListener
+ * The InstallListener to remove
+ */
+ removeListener: function AI_removeListener(aListener) {
+ this.listeners = this.listeners.filter(function removeListener_filterListener(i) {
+ return i != aListener;
+ });
+ },
+
+ /**
+ * Removes the temporary file owned by this AddonInstall if there is one.
+ */
+ removeTemporaryFile: function AI_removeTemporaryFile() {
+ // Only proceed if this AddonInstall owns its XPI file
+ if (!this.ownsTempFile) {
+ this.logger.debug("removeTemporaryFile: " + this.sourceURI.spec + " does not own temp file");
+ return;
+ }
+
+ try {
+ this.logger.debug("removeTemporaryFile: " + this.sourceURI.spec + " removing temp file " +
+ this.file.path);
+ this.file.remove(true);
+ this.ownsTempFile = false;
+ }
+ catch (e) {
+ this.logger.warn("Failed to remove temporary file " + this.file.path + " for addon " +
+ this.sourceURI.spec,
+ e);
+ }
+ },
+
+ /**
+ * Updates the sourceURI and releaseNotesURI values on the Addon being
+ * installed by this AddonInstall instance.
+ */
+ updateAddonURIs: function AI_updateAddonURIs() {
+ this.addon.sourceURI = this.sourceURI.spec;
+ if (this.releaseNotesURI)
+ this.addon.releaseNotesURI = this.releaseNotesURI.spec;
+ },
+
+ /**
+ * Loads add-on manifests from a multi-package XPI file. Each of the
+ * XPI and JAR files contained in the XPI will be extracted. Any that
+ * do not contain valid add-ons will be ignored. The first valid add-on will
+ * be installed by this AddonInstall instance, the rest will have new
+ * AddonInstall instances created for them.
+ *
+ * @param aZipReader
+ * An open nsIZipReader for the multi-package XPI's files. This will
+ * be closed before this method returns.
+ * @param aCallback
+ * A function to call when all of the add-on manifests have been
+ * loaded. Because this loadMultipackageManifests is an internal API
+ * we don't exception-wrap this callback
+ */
+ _loadMultipackageManifests: function AI_loadMultipackageManifests(aZipReader,
+ aCallback) {
+ let files = [];
+ let entries = aZipReader.findEntries("(*.[Xx][Pp][Ii]|*.[Jj][Aa][Rr])");
+ while (entries.hasMore()) {
+ let entryName = entries.getNext();
+ var target = getTemporaryFile();
+ try {
+ aZipReader.extract(entryName, target);
+ files.push(target);
+ }
+ catch (e) {
+ logger.warn("Failed to extract " + entryName + " from multi-package " +
+ "XPI", e);
+ target.remove(false);
+ }
+ }
+
+ aZipReader.close();
+
+ if (files.length == 0) {
+ throw new Error("Multi-package XPI does not contain any packages " +
+ "to install");
+ }
+
+ let addon = null;
+
+ // Find the first file that has a valid install manifest and use it for
+ // the add-on that this AddonInstall instance will install.
+ while (files.length > 0) {
+ this.removeTemporaryFile();
+ this.file = files.shift();
+ this.ownsTempFile = true;
+ try {
+ addon = loadManifestFromZipFile(this.file);
+ break;
+ }
+ catch (e) {
+ logger.warn(this.file.leafName + " cannot be installed from multi-package " +
+ "XPI", e);
+ }
+ }
+
+ if (!addon) {
+ // No valid add-on was found
+ aCallback();
+ return;
+ }
+
+ this.addon = addon;
+
+ this.updateAddonURIs();
+
+ this.addon._install = this;
+ this.name = this.addon.selectedLocale.name || this.addon.defaultLocale.name;
+ this.type = this.addon.type;
+ this.version = this.addon.version;
+
+ // Setting the iconURL to something inside the XPI locks the XPI and
+ // makes it impossible to delete on Windows.
+ //let newIcon = createWrapper(this.addon).iconURL;
+ //if (newIcon)
+ // this.iconURL = newIcon;
+
+ // Create new AddonInstall instances for every remaining file
+ if (files.length > 0) {
+ this.linkedInstalls = [];
+ let count = 0;
+ let self = this;
+ files.forEach(function(file) {
+ AddonInstall.createInstall(function loadMultipackageManifests_createInstall(aInstall) {
+ // Ignore bad add-ons (createInstall will have logged the error)
+ if (aInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
+ // Manually remove the temporary file
+ file.remove(true);
+ }
+ else {
+ // Make the new install own its temporary file
+ aInstall.ownsTempFile = true;
+
+ self.linkedInstalls.push(aInstall)
+
+ aInstall.sourceURI = self.sourceURI;
+ aInstall.releaseNotesURI = self.releaseNotesURI;
+ aInstall.updateAddonURIs();
+ }
+
+ count++;
+ if (count == files.length)
+ aCallback();
+ }, file);
+ }, this);
+ }
+ else {
+ aCallback();
+ }
+ },
+
+ /**
+ * Called after the add-on is a local file and the signature and install
+ * manifest can be read.
+ *
+ * @param aCallback
+ * A function to call when the manifest has been loaded
+ * @throws if the add-on does not contain a valid install manifest or the
+ * XPI is incorrectly signed
+ */
+ loadManifest: function AI_loadManifest(aCallback) {
+ aCallback = makeSafe(aCallback);
+ let self = this;
+ function addRepositoryData(aAddon) {
+ // Try to load from the existing cache first
+ AddonRepository.getCachedAddonByID(aAddon.id, function loadManifest_getCachedAddonByID(aRepoAddon) {
+ if (aRepoAddon) {
+ aAddon._repositoryAddon = aRepoAddon;
+ self.name = self.name || aAddon._repositoryAddon.name;
+ aAddon.compatibilityOverrides = aRepoAddon.compatibilityOverrides;
+ aAddon.appDisabled = !isUsableAddon(aAddon);
+ aCallback();
+ return;
+ }
+
+ // It wasn't there so try to re-download it
+ AddonRepository.cacheAddons([aAddon.id], function loadManifest_cacheAddons() {
+ AddonRepository.getCachedAddonByID(aAddon.id, function loadManifest_getCachedAddonByID(aRepoAddon) {
+ aAddon._repositoryAddon = aRepoAddon;
+ self.name = self.name || aAddon._repositoryAddon.name;
+ aAddon.compatibilityOverrides = aRepoAddon ?
+ aRepoAddon.compatibilityOverrides :
+ null;
+ aAddon.appDisabled = !isUsableAddon(aAddon);
+ aCallback();
+ });
+ });
+ });
+ }
+
+ let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ try {
+ zipreader.open(this.file);
+ }
+ catch (e) {
+ zipreader.close();
+ throw e;
+ }
+
+ let x509 = zipreader.getSigningCert(null);
+ if (x509) {
+ logger.debug("Verifying XPI signature");
+ if (verifyZipSigning(zipreader, x509)) {
+ this.certificate = x509;
+ if (this.certificate.commonName.length > 0) {
+ this.certName = this.certificate.commonName;
+ } else {
+ this.certName = this.certificate.organization;
+ }
+ } else {
+ zipreader.close();
+ throw new Error("XPI is incorrectly signed");
+ }
+ }
+
+ try {
+ this.addon = loadManifestFromZipReader(zipreader);
+ }
+ catch (e) {
+ zipreader.close();
+ throw e;
+ }
+
+ if (this.addon.type == "multipackage") {
+ this._loadMultipackageManifests(zipreader, function loadManifest_loadMultipackageManifests() {
+ addRepositoryData(self.addon);
+ });
+ return;
+ }
+
+ zipreader.close();
+
+ this.updateAddonURIs();
+
+ this.addon._install = this;
+ this.name = this.addon.selectedLocale.name || this.addon.defaultLocale.name;
+ this.type = this.addon.type;
+ this.version = this.addon.version;
+
+ // Setting the iconURL to something inside the XPI locks the XPI and
+ // makes it impossible to delete on Windows.
+ //let newIcon = createWrapper(this.addon).iconURL;
+ //if (newIcon)
+ // this.iconURL = newIcon;
+
+ addRepositoryData(this.addon);
+ },
+
+ observe: function AI_observe(aSubject, aTopic, aData) {
+ // Network is going offline
+ this.cancel();
+ },
+
+ /**
+ * Starts downloading the add-on's XPI file.
+ */
+ startDownload: function AI_startDownload() {
+ this.state = AddonManager.STATE_DOWNLOADING;
+ if (!AddonManagerPrivate.callInstallListeners("onDownloadStarted",
+ this.listeners, this.wrapper)) {
+ logger.debug("onDownloadStarted listeners cancelled installation of addon " + this.sourceURI.spec);
+ this.state = AddonManager.STATE_CANCELLED;
+ XPIProvider.removeActiveInstall(this);
+ AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
+ this.listeners, this.wrapper)
+ return;
+ }
+
+ // If a listener changed our state then do not proceed with the download
+ if (this.state != AddonManager.STATE_DOWNLOADING)
+ return;
+
+ if (this.channel) {
+ // A previous download attempt hasn't finished cleaning up yet, signal
+ // that it should restart when complete
+ logger.debug("Waiting for previous download to complete");
+ this.restartDownload = true;
+ return;
+ }
+
+ this.openChannel();
+ },
+
+ openChannel: function AI_openChannel() {
+ this.restartDownload = false;
+
+ try {
+ this.file = getTemporaryFile();
+ this.ownsTempFile = true;
+ this.stream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ this.stream.init(this.file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
+ FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE, 0);
+ }
+ catch (e) {
+ logger.warn("Failed to start download for addon " + this.sourceURI.spec, e);
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ this.error = AddonManager.ERROR_FILE_ACCESS;
+ XPIProvider.removeActiveInstall(this);
+ AddonManagerPrivate.callInstallListeners("onDownloadFailed",
+ this.listeners, this.wrapper);
+ return;
+ }
+
+ let listener = Cc["@mozilla.org/network/stream-listener-tee;1"].
+ createInstance(Ci.nsIStreamListenerTee);
+ listener.init(this, this.stream);
+ try {
+ Components.utils.import("resource://gre/modules/CertUtils.jsm");
+ let requireBuiltIn = Preferences.get(PREF_INSTALL_REQUIREBUILTINCERTS, true);
+ this.badCertHandler = new BadCertHandler(!requireBuiltIn);
+
+ this.channel = NetUtil.newChannel({
+ uri: this.sourceURI,
+ loadUsingSystemPrincipal: true
+ });
+ this.channel.notificationCallbacks = this;
+ if (this.channel instanceof Ci.nsIHttpChannel) {
+ this.channel.setRequestHeader("Moz-XPI-Update", "1", true);
+ if (this.channel instanceof Ci.nsIHttpChannelInternal)
+ this.channel.forceAllowThirdPartyCookie = true;
+ }
+ this.channel.asyncOpen2(listener);
+
+ Services.obs.addObserver(this, "network:offline-about-to-go-offline", false);
+ }
+ catch (e) {
+ logger.warn("Failed to start download for addon " + this.sourceURI.spec, e);
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ this.error = AddonManager.ERROR_NETWORK_FAILURE;
+ XPIProvider.removeActiveInstall(this);
+ AddonManagerPrivate.callInstallListeners("onDownloadFailed",
+ this.listeners, this.wrapper);
+ }
+ },
+
+ /**
+ * Update the crypto hasher with the new data and call the progress listeners.
+ *
+ * @see nsIStreamListener
+ */
+ onDataAvailable: function AI_onDataAvailable(aRequest, aContext, aInputstream,
+ aOffset, aCount) {
+ this.crypto.updateFromStream(aInputstream, aCount);
+ this.progress += aCount;
+ if (!AddonManagerPrivate.callInstallListeners("onDownloadProgress",
+ this.listeners, this.wrapper)) {
+ // TODO cancel the download and make it available again (bug 553024)
+ }
+ },
+
+ /**
+ * Check the redirect response for a hash of the target XPI and verify that
+ * we don't end up on an insecure channel.
+ *
+ * @see nsIChannelEventSink
+ */
+ asyncOnChannelRedirect: function AI_asyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCallback) {
+ if (!this.hash && aOldChannel.originalURI.schemeIs("https") &&
+ aOldChannel instanceof Ci.nsIHttpChannel) {
+ try {
+ let hashStr = aOldChannel.getResponseHeader("X-Target-Digest");
+ let hashSplit = hashStr.toLowerCase().split(":");
+ this.hash = {
+ algorithm: hashSplit[0],
+ data: hashSplit[1]
+ };
+ }
+ catch (e) {
+ }
+ }
+
+ // Verify that we don't end up on an insecure channel if we haven't got a
+ // hash to verify with (see bug 537761 for discussion)
+ if (!this.hash)
+ this.badCertHandler.asyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCallback);
+ else
+ aCallback.onRedirectVerifyCallback(Cr.NS_OK);
+
+ this.channel = aNewChannel;
+ },
+
+ /**
+ * This is the first chance to get at real headers on the channel.
+ *
+ * @see nsIStreamListener
+ */
+ onStartRequest: function AI_onStartRequest(aRequest, aContext) {
+ this.crypto = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ if (this.hash) {
+ try {
+ this.crypto.initWithString(this.hash.algorithm);
+ }
+ catch (e) {
+ logger.warn("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ this.error = AddonManager.ERROR_INCORRECT_HASH;
+ XPIProvider.removeActiveInstall(this);
+ AddonManagerPrivate.callInstallListeners("onDownloadFailed",
+ this.listeners, this.wrapper);
+ aRequest.cancel(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+ }
+ else {
+ // We always need something to consume data from the inputstream passed
+ // to onDataAvailable so just create a dummy cryptohasher to do that.
+ this.crypto.initWithString("sha1");
+ }
+
+ this.progress = 0;
+ if (aRequest instanceof Ci.nsIChannel) {
+ try {
+ this.maxProgress = aRequest.contentLength;
+ }
+ catch (e) {
+ }
+ logger.debug("Download started for " + this.sourceURI.spec + " to file " +
+ this.file.path);
+ }
+ },
+
+ /**
+ * The download is complete.
+ *
+ * @see nsIStreamListener
+ */
+ onStopRequest: function AI_onStopRequest(aRequest, aContext, aStatus) {
+ this.stream.close();
+ this.channel = null;
+ this.badCerthandler = null;
+ Services.obs.removeObserver(this, "network:offline-about-to-go-offline");
+
+ // If the download was cancelled then update the state and send events
+ if (aStatus == Cr.NS_BINDING_ABORTED) {
+ if (this.state == AddonManager.STATE_DOWNLOADING) {
+ logger.debug("Cancelled download of " + this.sourceURI.spec);
+ this.state = AddonManager.STATE_CANCELLED;
+ XPIProvider.removeActiveInstall(this);
+ AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
+ this.listeners, this.wrapper);
+ // If a listener restarted the download then there is no need to
+ // remove the temporary file
+ if (this.state != AddonManager.STATE_CANCELLED)
+ return;
+ }
+
+ this.removeTemporaryFile();
+ if (this.restartDownload)
+ this.openChannel();
+ return;
+ }
+
+ logger.debug("Download of " + this.sourceURI.spec + " completed.");
+
+ if (Components.isSuccessCode(aStatus)) {
+ if (!(aRequest instanceof Ci.nsIHttpChannel) || aRequest.requestSucceeded) {
+ if (!this.hash && (aRequest instanceof Ci.nsIChannel)) {
+ try {
+ checkCert(aRequest,
+ !Preferences.get(PREF_INSTALL_REQUIREBUILTINCERTS, true));
+ }
+ catch (e) {
+ this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, e);
+ return;
+ }
+ }
+
+ // convert the binary hash data to a hex string.
+ let calculatedHash = getHashStringForCrypto(this.crypto);
+ this.crypto = null;
+ if (this.hash && calculatedHash != this.hash.data) {
+ this.downloadFailed(AddonManager.ERROR_INCORRECT_HASH,
+ "Downloaded file hash (" + calculatedHash +
+ ") did not match provided hash (" + this.hash.data + ")");
+ return;
+ }
+ try {
+ let self = this;
+ this.loadManifest(function onStopRequest_loadManifest() {
+ if (self.addon.isCompatible) {
+ self.downloadCompleted();
+ }
+ else {
+ // TODO Should we send some event here (bug 557716)?
+ self.state = AddonManager.STATE_CHECKING;
+ new UpdateChecker(self.addon, {
+ onUpdateFinished: function onStopRequest_onUpdateFinished(aAddon) {
+ self.downloadCompleted();
+ }
+ }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
+ }
+ });
+ }
+ catch (e) {
+ if (e.webext) {
+ this.downloadFailed(AddonManager.ERROR_WEBEXT_FILE, e);
+#ifndef MOZ_JETPACK
+ } else if (e.jetpacksdk) {
+ this.downloadFailed(AddonManager.ERROR_JETPACKSDK_FILE, e);
+#endif
+ } else {
+ this.downloadFailed(AddonManager.ERROR_CORRUPT_FILE, e);
+ }
+ }
+ }
+ else {
+ if (aRequest instanceof Ci.nsIHttpChannel)
+ this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE,
+ aRequest.responseStatus + " " +
+ aRequest.responseStatusText);
+ else
+ this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aStatus);
+ }
+ }
+ else {
+ this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aStatus);
+ }
+ },
+
+ /**
+ * Notify listeners that the download failed.
+ *
+ * @param aReason
+ * Something to log about the failure
+ * @param error
+ * The error code to pass to the listeners
+ */
+ downloadFailed: function AI_downloadFailed(aReason, aError) {
+ logger.warn("Download of " + this.sourceURI.spec + " failed", aError);
+ this.state = AddonManager.STATE_DOWNLOAD_FAILED;
+ this.error = aReason;
+ XPIProvider.removeActiveInstall(this);
+ AddonManagerPrivate.callInstallListeners("onDownloadFailed", this.listeners,
+ this.wrapper);
+
+ // If the listener hasn't restarted the download then remove any temporary
+ // file
+ if (this.state == AddonManager.STATE_DOWNLOAD_FAILED) {
+ logger.debug("downloadFailed: removing temp file for " + this.sourceURI.spec);
+ this.removeTemporaryFile();
+ }
+ else
+ logger.debug("downloadFailed: listener changed AddonInstall state for " +
+ this.sourceURI.spec + " to " + this.state);
+ },
+
+ /**
+ * Notify listeners that the download completed.
+ */
+ downloadCompleted: function AI_downloadCompleted() {
+ let self = this;
+ XPIDatabase.getVisibleAddonForID(this.addon.id, function downloadCompleted_getVisibleAddonForID(aAddon) {
+ if (aAddon)
+ self.existingAddon = aAddon;
+
+ self.state = AddonManager.STATE_DOWNLOADED;
+ self.addon.updateDate = Date.now();
+
+ if (self.existingAddon) {
+ self.addon.existingAddonID = self.existingAddon.id;
+ self.addon.installDate = self.existingAddon.installDate;
+ applyBlocklistChanges(self.existingAddon, self.addon);
+ }
+ else {
+ self.addon.installDate = self.addon.updateDate;
+ }
+
+ if (AddonManagerPrivate.callInstallListeners("onDownloadEnded",
+ self.listeners,
+ self.wrapper)) {
+ // If a listener changed our state then do not proceed with the install
+ if (self.state != AddonManager.STATE_DOWNLOADED)
+ return;
+
+ self.install();
+
+ if (self.linkedInstalls) {
+ self.linkedInstalls.forEach(function(aInstall) {
+ aInstall.install();
+ });
+ }
+ }
+ });
+ },
+
+ // TODO This relies on the assumption that we are always installing into the
+ // highest priority install location so the resulting add-on will be visible
+ // overriding any existing copy in another install location (bug 557710).
+ /**
+ * Installs the add-on into the install location.
+ */
+ startInstall: function AI_startInstall() {
+ this.state = AddonManager.STATE_INSTALLING;
+ if (!AddonManagerPrivate.callInstallListeners("onInstallStarted",
+ this.listeners, this.wrapper)) {
+ this.state = AddonManager.STATE_DOWNLOADED;
+ XPIProvider.removeActiveInstall(this);
+ AddonManagerPrivate.callInstallListeners("onInstallCancelled",
+ this.listeners, this.wrapper)
+ return;
+ }
+
+ // Find and cancel any pending installs for the same add-on in the same
+ // install location
+ for (let aInstall of XPIProvider.installs) {
+ if (aInstall.state == AddonManager.STATE_INSTALLED &&
+ aInstall.installLocation == this.installLocation &&
+ aInstall.addon.id == this.addon.id) {
+ logger.debug("Cancelling previous pending install of " + aInstall.addon.id);
+ aInstall.cancel();
+ }
+ }
+
+ let isUpgrade = this.existingAddon &&
+ this.existingAddon._installLocation == this.installLocation;
+ let requiresRestart = XPIProvider.installRequiresRestart(this.addon);
+
+ logger.debug("Starting install of " + this.addon.id + " from " + this.sourceURI.spec);
+ AddonManagerPrivate.callAddonListeners("onInstalling",
+ createWrapper(this.addon),
+ requiresRestart);
+
+ let stagingDir = this.installLocation.getStagingDir();
+ let stagedAddon = stagingDir.clone();
+
+ Task.spawn((function() {
+ let installedUnpacked = 0;
+ yield this.installLocation.requestStagingDir();
+
+ // Remove any staged items for this add-on
+ stagedAddon.append(this.addon.id);
+ yield removeAsync(stagedAddon);
+ stagedAddon.leafName = this.addon.id + ".xpi";
+ yield removeAsync(stagedAddon);
+
+ // First stage the file regardless of whether restarting is necessary
+ if (this.addon.unpack || Preferences.get(PREF_XPI_UNPACK, false)) {
+ logger.debug("Addon " + this.addon.id + " will be installed as " +
+ "an unpacked directory");
+ stagedAddon.leafName = this.addon.id;
+ yield OS.File.makeDir(stagedAddon.path);
+ yield ZipUtils.extractFilesAsync(this.file, stagedAddon);
+ installedUnpacked = 1;
+ }
+ else {
+ logger.debug("Addon " + this.addon.id + " will be installed as " +
+ "a packed xpi");
+ stagedAddon.leafName = this.addon.id + ".xpi";
+ yield OS.File.copy(this.file.path, stagedAddon.path);
+ }
+
+ if (requiresRestart) {
+ // Point the add-on to its extracted files as the xpi may get deleted
+ this.addon._sourceBundle = stagedAddon;
+
+ // Cache the AddonInternal as it may have updated compatibility info
+ let stagedJSON = stagedAddon.clone();
+ stagedJSON.leafName = this.addon.id + ".json";
+ if (stagedJSON.exists())
+ stagedJSON.remove(true);
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
+ createInstance(Ci.nsIConverterOutputStream);
+
+ try {
+ stream.init(stagedJSON, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
+ FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE,
+ 0);
+ converter.init(stream, "UTF-8", 0, 0x0000);
+ converter.writeString(JSON.stringify(this.addon));
+ }
+ finally {
+ converter.close();
+ stream.close();
+ }
+
+ logger.debug("Staged install of " + this.addon.id + " from " + this.sourceURI.spec + " ready; waiting for restart.");
+ this.state = AddonManager.STATE_INSTALLED;
+ if (isUpgrade) {
+ delete this.existingAddon.pendingUpgrade;
+ this.existingAddon.pendingUpgrade = this.addon;
+ }
+ AddonManagerPrivate.callInstallListeners("onInstallEnded",
+ this.listeners, this.wrapper,
+ createWrapper(this.addon));
+ }
+ else {
+ // The install is completed so it should be removed from the active list
+ XPIProvider.removeActiveInstall(this);
+
+ // TODO We can probably reduce the number of DB operations going on here
+ // We probably also want to support rolling back failed upgrades etc.
+ // See bug 553015.
+
+ // Deactivate and remove the old add-on as necessary
+ let reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
+ if (this.existingAddon) {
+ if (Services.vc.compare(this.existingAddon.version, this.addon.version) < 0)
+ reason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
+ else
+ reason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
+
+ if (this.existingAddon.bootstrap) {
+ let file = this.existingAddon._installLocation
+ .getLocationForID(this.existingAddon.id);
+ if (this.existingAddon.active) {
+ XPIProvider.callBootstrapMethod(this.existingAddon, file,
+ "shutdown", reason,
+ { newVersion: this.addon.version });
+ }
+
+ XPIProvider.callBootstrapMethod(this.existingAddon, file,
+ "uninstall", reason,
+ { newVersion: this.addon.version });
+ XPIProvider.unloadBootstrapScope(this.existingAddon.id);
+ flushChromeCaches();
+ }
+
+ if (!isUpgrade && this.existingAddon.active) {
+ XPIDatabase.updateAddonActive(this.existingAddon, false);
+ }
+ }
+
+ // Install the new add-on into its final location
+ let existingAddonID = this.existingAddon ? this.existingAddon.id : null;
+ let file = this.installLocation.installAddon(this.addon.id, stagedAddon,
+ existingAddonID);
+
+ // Update the metadata in the database
+ this.addon._sourceBundle = file;
+ this.addon._installLocation = this.installLocation;
+ this.addon.visible = true;
+
+ if (isUpgrade) {
+ this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
+ file.persistentDescriptor);
+ let state = XPIStates.getAddon(this.installLocation.name, this.addon.id);
+ if (state) {
+ state.syncWithDB(this.addon, true);
+ } else {
+ logger.warn("Unexpected missing XPI state for add-on ${id}", this.addon);
+ }
+ }
+ else {
+ this.addon.active = (this.addon.visible && !this.addon.disabled);
+ this.addon = XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor);
+ XPIStates.addAddon(this.addon);
+ this.addon.installDate = this.addon.updateDate;
+ XPIDatabase.saveChanges();
+ }
+ XPIStates.save();
+
+ let extraParams = {};
+ if (this.existingAddon) {
+ extraParams.oldVersion = this.existingAddon.version;
+ }
+
+ if (this.addon.bootstrap) {
+ XPIProvider.callBootstrapMethod(this.addon, file, "install",
+ reason, extraParams);
+ }
+
+ AddonManagerPrivate.callAddonListeners("onInstalled",
+ createWrapper(this.addon));
+
+ logger.debug("Install of " + this.sourceURI.spec + " completed.");
+ this.state = AddonManager.STATE_INSTALLED;
+ AddonManagerPrivate.callInstallListeners("onInstallEnded",
+ this.listeners, this.wrapper,
+ createWrapper(this.addon));
+
+ if (this.addon.bootstrap) {
+ if (this.addon.active) {
+ XPIProvider.callBootstrapMethod(this.addon, file, "startup",
+ reason, extraParams);
+ }
+ else {
+ // XXX this makes it dangerous to do some things in onInstallEnded
+ // listeners because important cleanup hasn't been done yet
+ XPIProvider.unloadBootstrapScope(this.addon.id);
+ }
+ }
+ }
+ }).bind(this)).then(null, (e) => {
+ logger.warn("Failed to install " + this.file.path + " from " + this.sourceURI.spec, e);
+ if (stagedAddon.exists())
+ recursiveRemove(stagedAddon);
+ this.state = AddonManager.STATE_INSTALL_FAILED;
+ this.error = AddonManager.ERROR_FILE_ACCESS;
+ XPIProvider.removeActiveInstall(this);
+ AddonManagerPrivate.callAddonListeners("onOperationCancelled",
+ createWrapper(this.addon));
+ AddonManagerPrivate.callInstallListeners("onInstallFailed",
+ this.listeners,
+ this.wrapper);
+ }).then(() => {
+ this.removeTemporaryFile();
+ return this.installLocation.releaseStagingDir();
+ });
+ },
+
+ getInterface: function AI_getInterface(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt2)) {
+ let win = this.window;
+ if (!win && this.browser)
+ win = this.browser.ownerDocument.defaultView;
+
+ let factory = Cc["@mozilla.org/prompter;1"].
+ getService(Ci.nsIPromptFactory);
+ let prompt = factory.getPrompt(win, Ci.nsIAuthPrompt2);
+
+ if (this.browser && this.browser.isRemoteBrowser && prompt instanceof Ci.nsILoginManagerPrompter)
+ prompt.setE10sData(this.browser, null);
+
+ return prompt;
+ }
+ else if (iid.equals(Ci.nsIChannelEventSink)) {
+ return this;
+ }
+
+ return this.badCertHandler.getInterface(iid);
+ }
+}
+
+/**
+ * Creates a new AddonInstall for an already staged install. Used when
+ * installing the staged install failed for some reason.
+ *
+ * @param aDir
+ * The directory holding the staged install
+ * @param aManifest
+ * The cached manifest for the install
+ */
+AddonInstall.createStagedInstall = function AI_createStagedInstall(aInstallLocation, aDir, aManifest) {
+ let url = Services.io.newFileURI(aDir);
+
+ let install = new AddonInstall(aInstallLocation, aDir);
+ install.initStagedInstall(aManifest);
+};
+
+/**
+ * Creates a new AddonInstall to install an add-on from a local file. Installs
+ * always go into the profile install location.
+ *
+ * @param aCallback
+ * The callback to pass the new AddonInstall to
+ * @param aFile
+ * The file to install
+ */
+AddonInstall.createInstall = function AI_createInstall(aCallback, aFile) {
+ let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
+ let url = Services.io.newFileURI(aFile);
+
+ try {
+ let install = new AddonInstall(location, url);
+ install.initLocalInstall(aCallback);
+ }
+ catch(e) {
+ logger.error("Error creating install", e);
+ makeSafe(aCallback)(null);
+ }
+};
+
+/**
+ * Creates a new AddonInstall to download and install a URL.
+ *
+ * @param aCallback
+ * The callback to pass the new AddonInstall to
+ * @param aUri
+ * The URI to download
+ * @param aHash
+ * A hash for the add-on
+ * @param aName
+ * A name for the add-on
+ * @param aIcons
+ * An icon URLs for the add-on
+ * @param aVersion
+ * A version for the add-on
+ * @param aBrowser
+ * The browser performing the install
+ */
+AddonInstall.createDownload = function AI_createDownload(aCallback, aUri, aHash, aName, aIcons,
+ aVersion, aBrowser) {
+ let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
+ let url = NetUtil.newURI(aUri);
+
+ let install = new AddonInstall(location, url, aHash, null, null, aBrowser);
+ if (url instanceof Ci.nsIFileURL)
+ install.initLocalInstall(aCallback);
+ else
+ install.initAvailableDownload(aName, null, aIcons, aVersion, aCallback);
+};
+
+/**
+ * Creates a new AddonInstall for an update.
+ *
+ * @param aCallback
+ * The callback to pass the new AddonInstall to
+ * @param aAddon
+ * The add-on being updated
+ * @param aUpdate
+ * The metadata about the new version from the update manifest
+ */
+AddonInstall.createUpdate = function AI_createUpdate(aCallback, aAddon, aUpdate) {
+ let url = NetUtil.newURI(aUpdate.updateURL);
+ let releaseNotesURI = null;
+ try {
+ if (aUpdate.updateInfoURL)
+ releaseNotesURI = NetUtil.newURI(escapeAddonURI(aAddon, aUpdate.updateInfoURL));
+ }
+ catch (e) {
+ // If the releaseNotesURI cannot be parsed then just ignore it.
+ }
+
+ let install = new AddonInstall(aAddon._installLocation, url,
+ aUpdate.updateHash, releaseNotesURI, aAddon);
+ if (url instanceof Ci.nsIFileURL) {
+ install.initLocalInstall(aCallback);
+ }
+ else {
+ install.initAvailableDownload(aAddon.selectedLocale.name ?
+ aAddon.selectedLocale.name : aAddon.defaultLocale.name,
+ aAddon.type, aAddon.icons, aUpdate.version, aCallback);
+ }
+};
+
+/**
+ * Creates a wrapper for an AddonInstall that only exposes the public API
+ *
+ * @param install
+ * The AddonInstall to create a wrapper for
+ */
+function AddonInstallWrapper(aInstall) {
+#ifdef MOZ_EM_DEBUG
+ this.__defineGetter__("__AddonInstallInternal__", function AIW_debugGetter() {
+ return aInstall;
+ });
+#endif
+
+ ["name", "type", "version", "icons", "releaseNotesURI", "file", "state", "error",
+ "progress", "maxProgress", "certificate", "certName"].forEach(function(aProp) {
+ this.__defineGetter__(aProp, function AIW_propertyGetter() aInstall[aProp]);
+ }, this);
+
+ this.__defineGetter__("iconURL", function AIW_iconURL() aInstall.icons[32]);
+
+ this.__defineGetter__("existingAddon", function AIW_existingAddonGetter() {
+ return createWrapper(aInstall.existingAddon);
+ });
+ this.__defineGetter__("addon", function AIW_addonGetter() createWrapper(aInstall.addon));
+ this.__defineGetter__("sourceURI", function AIW_sourceURIGetter() aInstall.sourceURI);
+
+ this.__defineGetter__("linkedInstalls", function AIW_linkedInstallsGetter() {
+ if (!aInstall.linkedInstalls)
+ return null;
+ // Tycho: return [i.wrapper for each (i in aInstall.linkedInstalls)];
+ let result = [];
+ for each (let i in aInstall.linkedInstalls) {
+ result.push(i.wrapper);
+ }
+
+ return result;
+ });
+
+ this.install = function AIW_install() {
+ aInstall.install();
+ }
+
+ this.cancel = function AIW_cancel() {
+ aInstall.cancel();
+ }
+
+ this.addListener = function AIW_addListener(listener) {
+ aInstall.addListener(listener);
+ }
+
+ this.removeListener = function AIW_removeListener(listener) {
+ aInstall.removeListener(listener);
+ }
+}
+
+AddonInstallWrapper.prototype = {};
+
+/**
+ * Creates a new update checker.
+ *
+ * @param aAddon
+ * The add-on to check for updates
+ * @param aListener
+ * An UpdateListener to notify of updates
+ * @param aReason
+ * The reason for the update check
+ * @param aAppVersion
+ * An optional application version to check for updates for
+ * @param aPlatformVersion
+ * An optional platform version to check for updates for
+ * @throws if the aListener or aReason arguments are not valid
+ */
+function UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion) {
+ if (!aListener || !aReason)
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm");
+
+ this.addon = aAddon;
+ aAddon._updateCheck = this;
+ XPIProvider.doing(this);
+ this.listener = aListener;
+ this.appVersion = aAppVersion;
+ this.platformVersion = aPlatformVersion;
+ this.syncCompatibility = (aReason == AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
+
+ let updateURL = aAddon.updateURL;
+ if (!updateURL) {
+ updateURL = Services.prefs.getCharPref(PREF_EM_UPDATE_URL);
+ }
+
+ const UPDATE_TYPE_COMPATIBILITY = 32;
+ const UPDATE_TYPE_NEWVERSION = 64;
+
+ aReason |= UPDATE_TYPE_COMPATIBILITY;
+ if ("onUpdateAvailable" in this.listener)
+ aReason |= UPDATE_TYPE_NEWVERSION;
+
+ let url = escapeAddonURI(aAddon, updateURL, aReason, aAppVersion);
+ this._parser = AddonUpdateChecker.checkForUpdates(aAddon.id, aAddon.updateKey,
+ url, this);
+}
+
+UpdateChecker.prototype = {
+ addon: null,
+ listener: null,
+ appVersion: null,
+ platformVersion: null,
+ syncCompatibility: null,
+
+ /**
+ * Calls a method on the listener passing any number of arguments and
+ * consuming any exceptions.
+ *
+ * @param aMethod
+ * The method to call on the listener
+ */
+ callListener: function UC_callListener(aMethod, ...aArgs) {
+ if (!(aMethod in this.listener))
+ return;
+
+ try {
+ this.listener[aMethod].apply(this.listener, aArgs);
+ }
+ catch (e) {
+ logger.warn("Exception calling UpdateListener method " + aMethod, e);
+ }
+ },
+
+ /**
+ * Called when AddonUpdateChecker completes the update check
+ *
+ * @param updates
+ * The list of update details for the add-on
+ */
+ onUpdateCheckComplete: function UC_onUpdateCheckComplete(aUpdates) {
+ XPIProvider.done(this.addon._updateCheck);
+ this.addon._updateCheck = null;
+ let AUC = AddonUpdateChecker;
+
+ let ignoreMaxVersion = false;
+ let ignoreStrictCompat = false;
+ if (!AddonManager.checkCompatibility) {
+ ignoreMaxVersion = true;
+ ignoreStrictCompat = true;
+ } else if (this.addon.type in COMPATIBLE_BY_DEFAULT_TYPES &&
+ !AddonManager.strictCompatibility &&
+ !this.addon.strictCompatibility &&
+ !this.addon.hasBinaryComponents) {
+ ignoreMaxVersion = true;
+ }
+
+ // Always apply any compatibility update for the current version
+ let compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version,
+ this.syncCompatibility,
+ null, null,
+ ignoreMaxVersion,
+ ignoreStrictCompat);
+ // Apply the compatibility update to the database
+ if (compatUpdate)
+ this.addon.applyCompatibilityUpdate(compatUpdate, this.syncCompatibility);
+
+ // If the request is for an application or platform version that is
+ // different to the current application or platform version then look for a
+ // compatibility update for those versions.
+ if ((this.appVersion &&
+ Services.vc.compare(this.appVersion, Services.appinfo.version) != 0) ||
+ (this.platformVersion &&
+ Services.vc.compare(this.platformVersion, Services.appinfo.greVersion) != 0)) {
+ compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version,
+ false, this.appVersion,
+ this.platformVersion,
+ ignoreMaxVersion,
+ ignoreStrictCompat);
+ }
+
+ if (compatUpdate)
+ this.callListener("onCompatibilityUpdateAvailable", createWrapper(this.addon));
+ else
+ this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon));
+
+ function sendUpdateAvailableMessages(aSelf, aInstall) {
+ if (aInstall) {
+ aSelf.callListener("onUpdateAvailable", createWrapper(aSelf.addon),
+ aInstall.wrapper);
+ }
+ else {
+ aSelf.callListener("onNoUpdateAvailable", createWrapper(aSelf.addon));
+ }
+ aSelf.callListener("onUpdateFinished", createWrapper(aSelf.addon),
+ AddonManager.UPDATE_STATUS_NO_ERROR);
+ }
+
+ let compatOverrides = AddonManager.strictCompatibility ?
+ null :
+ this.addon.compatibilityOverrides;
+
+ let update = AUC.getNewestCompatibleUpdate(aUpdates,
+ this.appVersion,
+ this.platformVersion,
+ ignoreMaxVersion,
+ ignoreStrictCompat,
+ compatOverrides);
+
+ if (update && Services.vc.compare(this.addon.version, update.version) < 0) {
+ for (let currentInstall of XPIProvider.installs) {
+ // Skip installs that don't match the available update
+ if (currentInstall.existingAddon != this.addon ||
+ currentInstall.version != update.version)
+ continue;
+
+ // If the existing install has not yet started downloading then send an
+ // available update notification. If it is already downloading then
+ // don't send any available update notification
+ if (currentInstall.state == AddonManager.STATE_AVAILABLE) {
+ logger.debug("Found an existing AddonInstall for " + this.addon.id);
+ sendUpdateAvailableMessages(this, currentInstall);
+ }
+ else
+ sendUpdateAvailableMessages(this, null);
+ return;
+ }
+
+ let self = this;
+ AddonInstall.createUpdate(function onUpdateCheckComplete_createUpdate(aInstall) {
+ sendUpdateAvailableMessages(self, aInstall);
+ }, this.addon, update);
+ }
+ else {
+ sendUpdateAvailableMessages(this, null);
+ }
+ },
+
+ /**
+ * Called when AddonUpdateChecker fails the update check
+ *
+ * @param aError
+ * An error status
+ */
+ onUpdateCheckError: function UC_onUpdateCheckError(aError) {
+ XPIProvider.done(this.addon._updateCheck);
+ this.addon._updateCheck = null;
+ this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon));
+ this.callListener("onNoUpdateAvailable", createWrapper(this.addon));
+ this.callListener("onUpdateFinished", createWrapper(this.addon), aError);
+ },
+
+ /**
+ * Called to cancel an in-progress update check
+ */
+ cancel: function UC_cancel() {
+ let parser = this._parser;
+ if (parser) {
+ this._parser = null;
+ // This will call back to onUpdateCheckError with a CANCELLED error
+ parser.cancel();
+ }
+ }
+};
+
+/**
+ * The AddonInternal is an internal only representation of add-ons. It may
+ * have come from the database (see DBAddonInternal in XPIProviderUtils.jsm)
+ * or an install manifest.
+ */
+function AddonInternal() {
+}
+
+AddonInternal.prototype = {
+ _selectedLocale: null,
+ active: false,
+ visible: false,
+ userDisabled: false,
+ appDisabled: false,
+ softDisabled: false,
+ sourceURI: null,
+ releaseNotesURI: null,
+ foreignInstall: false,
+
+ get selectedLocale() {
+ if (this._selectedLocale)
+ return this._selectedLocale;
+ let locale = findClosestLocale(this.locales);
+ this._selectedLocale = locale ? locale : this.defaultLocale;
+ return this._selectedLocale;
+ },
+
+ get providesUpdatesSecurely() {
+ return !!(this.updateKey || !this.updateURL ||
+ this.updateURL.substring(0, 6) == "https:");
+ },
+
+ get isCompatible() {
+ return this.isCompatibleWith();
+ },
+
+ get disabled() {
+ return (this.userDisabled || this.appDisabled || this.softDisabled);
+ },
+
+ get isPlatformCompatible() {
+ if (this.targetPlatforms.length == 0)
+ return true;
+
+ let matchedOS = false;
+
+ // If any targetPlatform matches the OS and contains an ABI then we will
+ // only match a targetPlatform that contains both the current OS and ABI
+ let needsABI = false;
+
+ // Some platforms do not specify an ABI, test against null in that case.
+ let abi = null;
+ try {
+ abi = Services.appinfo.XPCOMABI;
+ }
+ catch (e) { }
+
+ // Something is causing errors in here
+ try {
+ for (let platform of this.targetPlatforms) {
+ if (platform.os == Services.appinfo.OS) {
+ if (platform.abi) {
+ needsABI = true;
+ if (platform.abi === abi)
+ return true;
+ }
+ else {
+ matchedOS = true;
+ }
+ }
+ }
+ } catch (e) {
+ let message = "Problem with addon " + this.id + " targetPlatforms "
+ + JSON.stringify(this.targetPlatforms);
+ logger.error(message, e);
+ // don't trust this add-on
+ return false;
+ }
+
+ return matchedOS && !needsABI;
+ },
+
+ isCompatibleWith: function AddonInternal_isCompatibleWith(aAppVersion, aPlatformVersion) {
+ let app = this.matchingTargetApplication;
+ if (!app)
+ return false;
+
+ if (!aAppVersion)
+ aAppVersion = Services.appinfo.version;
+ if (!aPlatformVersion)
+ aPlatformVersion = Services.appinfo.greVersion;
+
+ let version;
+#ifdef MC_APP_ID
+ if (app.id == ALT_APP_ID || app.id == Services.appinfo.ID) {
+#else
+ if (app.id == Services.appinfo.ID) {
+#endif
+ version = aAppVersion;
+ }
+ else if (app.id == TOOLKIT_ID) {
+ version = aPlatformVersion;
+ }
+
+ // Only extensions and dictionaries can be compatible by default; themes
+ // and language packs always use strict compatibility checking.
+ if (this.type in COMPATIBLE_BY_DEFAULT_TYPES &&
+ !AddonManager.strictCompatibility && !this.strictCompatibility &&
+ !this.hasBinaryComponents) {
+
+ // The repository can specify compatibility overrides.
+ // Note: For now, only blacklisting is supported by overrides.
+ if (this._repositoryAddon &&
+ this._repositoryAddon.compatibilityOverrides) {
+ let overrides = this._repositoryAddon.compatibilityOverrides;
+ let override = AddonRepository.findMatchingCompatOverride(this.version,
+ overrides);
+ if (override && override.type == "incompatible")
+ return false;
+ }
+
+ // Extremely old extensions should not be compatible by default.
+ let minCompatVersion;
+#ifdef MC_APP_ID
+ if (app.id == ALT_APP_ID || app.id == Services.appinfo.ID)
+#else
+ if (app.id == Services.appinfo.ID)
+#endif
+ minCompatVersion = XPIProvider.minCompatibleAppVersion;
+ else if (app.id == TOOLKIT_ID)
+ minCompatVersion = XPIProvider.minCompatiblePlatformVersion;
+
+ if (minCompatVersion &&
+ Services.vc.compare(minCompatVersion, app.maxVersion) > 0)
+ return false;
+
+ return Services.vc.compare(version, app.minVersion) >= 0;
+ }
+
+ return (Services.vc.compare(version, app.minVersion) >= 0) &&
+ (Services.vc.compare(version, app.maxVersion) <= 0)
+ },
+
+ get matchingTargetApplication() {
+ let app = null;
+
+#ifdef MC_APP_ID
+ // We want to prefer the Pale Moon application ID
+ // over any other for the duration of this hack.
+ for (let targetApp of this.targetApplications) {
+ if (targetApp.id == ALT_APP_ID) {
+ logger.warn("getMatchingTargetApplication: Add-on " + this.defaultLocale.name +
+ " matches because Alternate Application ID " + ALT_APP_ID +
+ " is currently preferred over the Application or Toolkit's ID.");
+
+ return targetApp;
+ }
+ }
+#endif
+
+ for (let targetApp of this.targetApplications) {
+ if (targetApp.id == Services.appinfo.ID) {
+ return targetApp;
+ }
+
+ if (targetApp.id == TOOLKIT_ID) {
+ app = targetApp;
+ }
+ }
+
+ return app;
+ },
+
+ get blocklistState() {
+ let staticItem = findMatchingStaticBlocklistItem(this);
+ if (staticItem)
+ return staticItem.level;
+
+ return Blocklist.getAddonBlocklistState(createWrapper(this));
+ },
+
+ get blocklistURL() {
+ let staticItem = findMatchingStaticBlocklistItem(this);
+ if (staticItem) {
+ let url = Services.urlFormatter.formatURLPref("extensions.blocklist.itemURL");
+ return url.replace(/%blockID%/g, staticItem.blockID);
+ }
+
+ return Blocklist.getAddonBlocklistURL(createWrapper(this));
+ },
+
+ applyCompatibilityUpdate: function AddonInternal_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
+ if (this.strictCompatibility) {
+ return;
+ }
+ this.targetApplications.forEach(function(aTargetApp) {
+ aUpdate.targetApplications.forEach(function(aUpdateTarget) {
+ if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
+ Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) {
+ aTargetApp.minVersion = aUpdateTarget.minVersion;
+ aTargetApp.maxVersion = aUpdateTarget.maxVersion;
+ }
+ });
+ });
+ if (aUpdate.multiprocessCompatible !== undefined)
+ this.multiprocessCompatible = aUpdate.multiprocessCompatible;
+ this.appDisabled = !isUsableAddon(this);
+ },
+
+ /**
+ * getDataDirectory tries to execute the callback with two arguments:
+ * 1) the path of the data directory within the profile,
+ * 2) any exception generated from trying to build it.
+ */
+ getDataDirectory: function(callback) {
+ let parentPath = OS.Path.join(OS.Constants.Path.profileDir, "extension-data");
+ let dirPath = OS.Path.join(parentPath, this.id);
+
+ Task.spawn(function*() {
+ yield OS.File.makeDir(parentPath, {ignoreExisting: true});
+ yield OS.File.makeDir(dirPath, {ignoreExisting: true});
+ }).then(() => callback(dirPath, null),
+ e => callback(dirPath, e));
+ },
+
+ /**
+ * toJSON is called by JSON.stringify in order to create a filtered version
+ * of this object to be serialized to a JSON file. A new object is returned
+ * with copies of all non-private properties. Functions, getters and setters
+ * are not copied.
+ *
+ * @param aKey
+ * The key that this object is being serialized as in the JSON.
+ * Unused here since this is always the main object serialized
+ *
+ * @return an object containing copies of the properties of this object
+ * ignoring private properties, functions, getters and setters
+ */
+ toJSON: function AddonInternal_toJSON(aKey) {
+ let obj = {};
+ for (let prop in this) {
+ // Ignore private properties
+ if (prop.substring(0, 1) == "_")
+ continue;
+
+ // Ignore getters
+ if (this.__lookupGetter__(prop))
+ continue;
+
+ // Ignore setters
+ if (this.__lookupSetter__(prop))
+ continue;
+
+ // Ignore functions
+ if (typeof this[prop] == "function")
+ continue;
+
+ obj[prop] = this[prop];
+ }
+
+ return obj;
+ },
+
+ /**
+ * When an add-on install is pending its metadata will be cached in a file.
+ * This method reads particular properties of that metadata that may be newer
+ * than that in the install manifest, like compatibility information.
+ *
+ * @param aObj
+ * A JS object containing the cached metadata
+ */
+ importMetadata: function AddonInternal_importMetaData(aObj) {
+ PENDING_INSTALL_METADATA.forEach(function(aProp) {
+ if (!(aProp in aObj))
+ return;
+
+ this[aProp] = aObj[aProp];
+ }, this);
+
+ // Compatibility info may have changed so update appDisabled
+ this.appDisabled = !isUsableAddon(this);
+ },
+
+ permissions: function AddonInternal_permissions() {
+ let permissions = 0;
+
+ // Add-ons that aren't installed cannot be modified in any way
+ if (!(this.inDatabase))
+ return permissions;
+
+ if (!this.appDisabled) {
+ if (this.userDisabled || this.softDisabled) {
+ permissions |= AddonManager.PERM_CAN_ENABLE;
+ }
+ else if (this.type != "theme") {
+ permissions |= AddonManager.PERM_CAN_DISABLE;
+ }
+ }
+
+ // Add-ons that are in locked install locations, or are pending uninstall
+ // cannot be upgraded or uninstalled
+ if (!this._installLocation.locked && !this.pendingUninstall) {
+ // Add-ons that are installed by a file link cannot be upgraded
+ if (!this._installLocation.isLinkedAddon(this.id)) {
+ permissions |= AddonManager.PERM_CAN_UPGRADE;
+ }
+
+ permissions |= AddonManager.PERM_CAN_UNINSTALL;
+ }
+
+ return permissions;
+ },
+};
+
+/**
+ * Creates an AddonWrapper for an AddonInternal.
+ *
+ * @param addon
+ * The AddonInternal to wrap
+ * @return an AddonWrapper or null if addon was null
+ */
+function createWrapper(aAddon) {
+ if (!aAddon)
+ return null;
+ if (!aAddon._wrapper) {
+ aAddon._hasResourceCache = new Map();
+ aAddon._wrapper = new AddonWrapper(aAddon);
+ }
+ return aAddon._wrapper;
+}
+
+/**
+ * The AddonWrapper wraps an Addon to provide the data visible to consumers of
+ * the public API.
+ */
+function AddonWrapper(aAddon) {
+#ifdef MOZ_EM_DEBUG
+ this.__defineGetter__("__AddonInternal__", function AW_debugGetter() {
+ return aAddon;
+ });
+#endif
+
+ function chooseValue(aObj, aProp) {
+ let repositoryAddon = aAddon._repositoryAddon;
+ let objValue = aObj[aProp];
+
+ if (repositoryAddon && (aProp in repositoryAddon) &&
+ (objValue === undefined || objValue === null)) {
+ return [repositoryAddon[aProp], true];
+ }
+
+ return [objValue, false];
+ }
+
+ ["id", "syncGUID", "version", "type", "isCompatible", "isPlatformCompatible",
+ "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
+ "softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents",
+ "strictCompatibility", "compatibilityOverrides", "updateURL",
+ "getDataDirectory", "multiprocessCompatible", "native"].forEach(function(aProp) {
+ this.__defineGetter__(aProp, function AddonWrapper_propertyGetter() aAddon[aProp]);
+ }, this);
+
+ ["fullDescription", "developerComments", "eula", "supportURL",
+ "contributionURL", "contributionAmount", "averageRating", "reviewCount",
+ "reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers",
+ "repositoryStatus"].forEach(function(aProp) {
+ this.__defineGetter__(aProp, function AddonWrapper_repoPropertyGetter() {
+ if (aAddon._repositoryAddon)
+ return aAddon._repositoryAddon[aProp];
+
+ return null;
+ });
+ }, this);
+
+ this.__defineGetter__("aboutURL", function AddonWrapper_aboutURLGetter() {
+ return this.isActive ? aAddon["aboutURL"] : null;
+ });
+
+ ["installDate", "updateDate"].forEach(function(aProp) {
+ this.__defineGetter__(aProp, function AddonWrapper_datePropertyGetter() new Date(aAddon[aProp]));
+ }, this);
+
+ ["sourceURI", "releaseNotesURI"].forEach(function(aProp) {
+ this.__defineGetter__(aProp, function AddonWrapper_URIPropertyGetter() {
+ let [target, fromRepo] = chooseValue(aAddon, aProp);
+ if (!target)
+ return null;
+ if (fromRepo)
+ return target;
+ return NetUtil.newURI(target);
+ });
+ }, this);
+
+ this.__defineGetter__("optionsURL", function AddonWrapper_optionsURLGetter() {
+ if (this.isActive && aAddon.optionsURL)
+ return aAddon.optionsURL;
+
+ if (this.isActive && this.hasResource("options.xul"))
+ return this.getResourceURI("options.xul").spec;
+
+ return null;
+ }, this);
+
+ this.__defineGetter__("optionsType", function AddonWrapper_optionsTypeGetter() {
+ if (!this.isActive)
+ return null;
+
+ let hasOptionsXUL = this.hasResource("options.xul");
+ let hasOptionsURL = !!this.optionsURL;
+
+ if (aAddon.optionsType) {
+ switch (parseInt(aAddon.optionsType, 10)) {
+ case AddonManager.OPTIONS_TYPE_DIALOG:
+ case AddonManager.OPTIONS_TYPE_TAB:
+ return hasOptionsURL ? aAddon.optionsType : null;
+ case AddonManager.OPTIONS_TYPE_INLINE:
+ case AddonManager.OPTIONS_TYPE_INLINE_INFO:
+ return (hasOptionsXUL || hasOptionsURL) ? aAddon.optionsType : null;
+ }
+ return null;
+ }
+
+ if (hasOptionsXUL)
+ return AddonManager.OPTIONS_TYPE_INLINE;
+
+ if (hasOptionsURL)
+ return AddonManager.OPTIONS_TYPE_DIALOG;
+
+ return null;
+ }, this);
+
+ this.__defineGetter__("iconURL", function AddonWrapper_iconURLGetter() {
+ return this.icons[32] || undefined;
+ }, this);
+
+ this.__defineGetter__("icon64URL", function AddonWrapper_icon64URLGetter() {
+ return this.icons[64] || undefined;
+ }, this);
+
+ this.__defineGetter__("icons", function AddonWrapper_iconsGetter() {
+ let icons = {};
+ if (aAddon._repositoryAddon) {
+ for (let size in aAddon._repositoryAddon.icons) {
+ icons[size] = aAddon._repositoryAddon.icons[size];
+ }
+ }
+ if (this.isActive && aAddon.iconURL) {
+ icons[32] = aAddon.iconURL;
+ } else if (this.hasResource("icon.png")) {
+ icons[32] = this.getResourceURI("icon.png").spec;
+ }
+ if (this.isActive && aAddon.icon64URL) {
+ icons[64] = aAddon.icon64URL;
+ } else if (this.hasResource("icon64.png")) {
+ icons[64] = this.getResourceURI("icon64.png").spec;
+ }
+ Object.freeze(icons);
+ return icons;
+ }, this);
+
+ PROP_LOCALE_SINGLE.forEach(function(aProp) {
+ this.__defineGetter__(aProp, function AddonWrapper_singleLocaleGetter() {
+ // Override XPI creator if repository creator is defined
+ if (aProp == "creator" &&
+ aAddon._repositoryAddon && aAddon._repositoryAddon.creator) {
+ return aAddon._repositoryAddon.creator;
+ }
+
+ let result = null;
+
+ if (aAddon.active) {
+ try {
+ let pref = PREF_EM_EXTENSION_FORMAT + aAddon.id + "." + aProp;
+ let value = Preferences.get(pref, null, Ci.nsIPrefLocalizedString);
+ if (value)
+ result = value;
+ }
+ catch (e) {
+ }
+ }
+
+ if (result == null) {
+ if (typeof aAddon.selectedLocale[aProp] == "string" && aAddon.selectedLocale[aProp].length)
+ [result, ] = chooseValue(aAddon.selectedLocale, aProp);
+ else
+ [result, ] = chooseValue(aAddon.defaultLocale, aProp);
+ }
+
+ if (aProp == "creator")
+ return result ? new AddonManagerPrivate.AddonAuthor(result) : null;
+
+ return result;
+ });
+ }, this);
+
+ PROP_LOCALE_MULTI.forEach(function(aProp) {
+ this.__defineGetter__(aProp, function AddonWrapper_multiLocaleGetter() {
+ let results = null;
+ let usedRepository = false;
+
+ if (aAddon.active) {
+ let pref = PREF_EM_EXTENSION_FORMAT + aAddon.id + "." +
+ aProp.substring(0, aProp.length - 1);
+ let list = Services.prefs.getChildList(pref, {});
+ if (list.length > 0) {
+ list.sort();
+ results = [];
+ list.forEach(function(aPref) {
+ let value = Preferences.get(aPref, null, Ci.nsIPrefLocalizedString);
+ if (value)
+ results.push(value);
+ });
+ }
+ }
+
+ if (results == null) {
+ if (aAddon.selectedLocale[aProp] instanceof Array && aAddon.selectedLocale[aProp].length)
+ [results, usedRepository] = chooseValue(aAddon.selectedLocale, aProp);
+ else
+ [results, usedRepository] = chooseValue(aAddon.defaultLocale, aProp);
+ }
+
+ if (results && !usedRepository) {
+ results = results.map(function mapResult(aResult) {
+ return new AddonManagerPrivate.AddonAuthor(aResult);
+ });
+ }
+
+ return results;
+ });
+ }, this);
+
+ this.__defineGetter__("screenshots", function AddonWrapper_screenshotsGetter() {
+ let repositoryAddon = aAddon._repositoryAddon;
+ if (repositoryAddon && ("screenshots" in repositoryAddon)) {
+ let repositoryScreenshots = repositoryAddon.screenshots;
+ if (repositoryScreenshots && repositoryScreenshots.length > 0)
+ return repositoryScreenshots;
+ }
+
+ if (aAddon.type == "theme" && this.hasResource("preview.png")) {
+ let url = this.getResourceURI("preview.png").spec;
+ return [new AddonManagerPrivate.AddonScreenshot(url)];
+ }
+
+ return null;
+ });
+
+ this.__defineGetter__("applyBackgroundUpdates", function AddonWrapper_applyBackgroundUpdatesGetter() {
+ return aAddon.applyBackgroundUpdates;
+ });
+ this.__defineSetter__("applyBackgroundUpdates", function AddonWrapper_applyBackgroundUpdatesSetter(val) {
+ if (val != AddonManager.AUTOUPDATE_DEFAULT &&
+ val != AddonManager.AUTOUPDATE_DISABLE &&
+ val != AddonManager.AUTOUPDATE_ENABLE) {
+ val = val ? AddonManager.AUTOUPDATE_DEFAULT :
+ AddonManager.AUTOUPDATE_DISABLE;
+ }
+
+ if (val == aAddon.applyBackgroundUpdates)
+ return val;
+
+ XPIDatabase.setAddonProperties(aAddon, {
+ applyBackgroundUpdates: val
+ });
+ AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]);
+
+ return val;
+ });
+
+ this.__defineSetter__("syncGUID", function AddonWrapper_syncGUIDGetter(val) {
+ if (aAddon.syncGUID == val)
+ return val;
+
+ if (aAddon.inDatabase)
+ XPIDatabase.setAddonSyncGUID(aAddon, val);
+
+ aAddon.syncGUID = val;
+
+ return val;
+ });
+
+ this.__defineGetter__("install", function AddonWrapper_installGetter() {
+ if (!("_install" in aAddon) || !aAddon._install)
+ return null;
+ return aAddon._install.wrapper;
+ });
+
+ this.__defineGetter__("pendingUpgrade", function AddonWrapper_pendingUpgradeGetter() {
+ return createWrapper(aAddon.pendingUpgrade);
+ });
+
+ this.__defineGetter__("scope", function AddonWrapper_scopeGetter() {
+ if (aAddon._installLocation)
+ return aAddon._installLocation.scope;
+
+ return AddonManager.SCOPE_PROFILE;
+ });
+
+ this.__defineGetter__("pendingOperations", function AddonWrapper_pendingOperationsGetter() {
+ let pending = 0;
+ if (!(aAddon.inDatabase)) {
+ // Add-on is pending install if there is no associated install (shouldn't
+ // happen here) or if the install is in the process of or has successfully
+ // completed the install. If an add-on is pending install then we ignore
+ // any other pending operations.
+ if (!aAddon._install || aAddon._install.state == AddonManager.STATE_INSTALLING ||
+ aAddon._install.state == AddonManager.STATE_INSTALLED)
+ return AddonManager.PENDING_INSTALL;
+ }
+ else if (aAddon.pendingUninstall) {
+ // If an add-on is pending uninstall then we ignore any other pending
+ // operations
+ return AddonManager.PENDING_UNINSTALL;
+ }
+
+ // Extensions have an intentional inconsistency between what the DB says is
+ // enabled and what we say to the ouside world. so we need to cover up that
+ // lie here as well.
+ if (aAddon.active && aAddon.disabled)
+ pending |= AddonManager.PENDING_DISABLE;
+ else if (!aAddon.active && !aAddon.disabled)
+ pending |= AddonManager.PENDING_ENABLE;
+
+ if (aAddon.pendingUpgrade)
+ pending |= AddonManager.PENDING_UPGRADE;
+
+ return pending;
+ });
+
+ this.__defineGetter__("operationsRequiringRestart", function AddonWrapper_operationsRequiringRestartGetter() {
+ let ops = 0;
+ if (XPIProvider.installRequiresRestart(aAddon))
+ ops |= AddonManager.OP_NEEDS_RESTART_INSTALL;
+ if (XPIProvider.uninstallRequiresRestart(aAddon))
+ ops |= AddonManager.OP_NEEDS_RESTART_UNINSTALL;
+ if (XPIProvider.enableRequiresRestart(aAddon))
+ ops |= AddonManager.OP_NEEDS_RESTART_ENABLE;
+ if (XPIProvider.disableRequiresRestart(aAddon))
+ ops |= AddonManager.OP_NEEDS_RESTART_DISABLE;
+
+ return ops;
+ });
+
+ this.__defineGetter__("isDebuggable", function AddonWrapper_isDebuggable() {
+ return this.isActive && aAddon.bootstrap;
+ });
+
+ this.__defineGetter__("permissions", function AddonWrapper_permisionsGetter() {
+ return aAddon.permissions();
+ });
+
+ this.__defineGetter__("isActive", function AddonWrapper_isActiveGetter() {
+ if (Services.appinfo.inSafeMode)
+ return false;
+ return aAddon.active;
+ });
+
+ this.__defineGetter__("userDisabled", function AddonWrapper_userDisabledGetter() {
+ return aAddon.softDisabled || aAddon.userDisabled;
+ });
+ this.__defineSetter__("userDisabled", function AddonWrapper_userDisabledSetter(val) {
+ if (val == this.userDisabled) {
+ return val;
+ }
+
+ if (aAddon.inDatabase) {
+ if (aAddon.type == "theme" && val) {
+ if (aAddon.internalName == XPIProvider.defaultSkin)
+ throw new Error("Cannot disable the default theme");
+ XPIProvider.enableDefaultTheme();
+ }
+ else {
+ XPIProvider.updateAddonDisabledState(aAddon, val);
+ }
+ }
+ else {
+ aAddon.userDisabled = val;
+ // When enabling remove the softDisabled flag
+ if (!val)
+ aAddon.softDisabled = false;
+ }
+
+ return val;
+ });
+
+ this.__defineSetter__("softDisabled", function AddonWrapper_softDisabledSetter(val) {
+ if (val == aAddon.softDisabled)
+ return val;
+
+ if (aAddon.inDatabase) {
+ // When softDisabling a theme just enable the active theme
+ if (aAddon.type == "theme" && val && !aAddon.userDisabled) {
+ if (aAddon.internalName == XPIProvider.defaultSkin)
+ throw new Error("Cannot disable the default theme");
+ XPIProvider.enableDefaultTheme();
+ }
+ else {
+ XPIProvider.updateAddonDisabledState(aAddon, undefined, val);
+ }
+ }
+ else {
+ // Only set softDisabled if not already disabled
+ if (!aAddon.userDisabled)
+ aAddon.softDisabled = val;
+ }
+
+ return val;
+ });
+
+ this.isCompatibleWith = function AddonWrapper_isCompatiblewith(aAppVersion, aPlatformVersion) {
+ return aAddon.isCompatibleWith(aAppVersion, aPlatformVersion);
+ };
+
+ this.uninstall = function AddonWrapper_uninstall(alwaysAllowUndo) {
+ XPIProvider.uninstallAddon(aAddon, alwaysAllowUndo);
+ };
+
+ this.cancelUninstall = function AddonWrapper_cancelUninstall() {
+ XPIProvider.cancelUninstallAddon(aAddon);
+ };
+
+ this.findUpdates = function AddonWrapper_findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) {
+ new UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion);
+ };
+
+ // Returns true if there was an update in progress, false if there was no update to cancel
+ this.cancelUpdate = function AddonWrapper_cancelUpdate() {
+ if (aAddon._updateCheck) {
+ aAddon._updateCheck.cancel();
+ return true;
+ }
+ return false;
+ };
+
+ this.hasResource = function AddonWrapper_hasResource(aPath) {
+ if (aAddon._hasResourceCache.has(aPath))
+ return aAddon._hasResourceCache.get(aPath);
+
+ let bundle = aAddon._sourceBundle.clone();
+
+ // Bundle may not exist any more if the addon has just been uninstalled,
+ // but explicitly first checking .exists() results in unneeded file I/O.
+ try {
+ var isDir = bundle.isDirectory();
+ } catch (e) {
+ aAddon._hasResourceCache.set(aPath, false);
+ return false;
+ }
+
+ if (isDir) {
+ if (aPath) {
+ aPath.split("/").forEach(function(aPart) {
+ bundle.append(aPart);
+ });
+ }
+ let result = bundle.exists();
+ aAddon._hasResourceCache.set(aPath, result);
+ return result;
+ }
+
+ let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ try {
+ zipReader.open(bundle);
+ let result = zipReader.hasEntry(aPath);
+ aAddon._hasResourceCache.set(aPath, result);
+ return result;
+ }
+ catch (e) {
+ aAddon._hasResourceCache.set(aPath, false);
+ return false;
+ }
+ finally {
+ zipReader.close();
+ }
+ },
+
+ /**
+ * Returns a URI to the selected resource or to the add-on bundle if aPath
+ * is null. URIs to the bundle will always be file: URIs. URIs to resources
+ * will be file: URIs if the add-on is unpacked or jar: URIs if the add-on is
+ * still an XPI file.
+ *
+ * @param aPath
+ * The path in the add-on to get the URI for or null to get a URI to
+ * the file or directory the add-on is installed as.
+ * @return an nsIURI
+ */
+ this.getResourceURI = function AddonWrapper_getResourceURI(aPath) {
+ if (!aPath)
+ return NetUtil.newURI(aAddon._sourceBundle);
+
+ return getURIForResourceInFile(aAddon._sourceBundle, aPath);
+ }
+}
+
+/**
+ * An object which identifies a directory install location for add-ons. The
+ * location consists of a directory which contains the add-ons installed in the
+ * location.
+ *
+ * Each add-on installed in the location is either a directory containing the
+ * add-on's files or a text file containing an absolute path to the directory
+ * containing the add-ons files. The directory or text file must have the same
+ * name as the add-on's ID.
+ *
+ * There may also a special directory named "staged" which can contain
+ * directories with the same name as an add-on ID. If the directory is empty
+ * then it means the add-on will be uninstalled from this location during the
+ * next startup. If the directory contains the add-on's files then they will be
+ * installed during the next startup.
+ *
+ * @param aName
+ * The string identifier for the install location
+ * @param aDirectory
+ * The nsIFile directory for the install location
+ * @param aScope
+ * The scope of add-ons installed in this location
+ * @param aLocked
+ * true if add-ons cannot be installed, uninstalled or upgraded in the
+ * install location
+ */
+function DirectoryInstallLocation(aName, aDirectory, aScope, aLocked) {
+ this._name = aName;
+ this.locked = aLocked;
+ this._directory = aDirectory;
+ this._scope = aScope
+ this._IDToFileMap = {};
+ this._FileToIDMap = {};
+ this._linkedAddons = [];
+ this._stagingDirLock = 0;
+
+ if (!aDirectory.exists())
+ return;
+ if (!aDirectory.isDirectory())
+ throw new Error("Location must be a directory.");
+
+ this._readAddons();
+}
+
+DirectoryInstallLocation.prototype = {
+ _name : "",
+ _directory : null,
+ _IDToFileMap : null, // mapping from add-on ID to nsIFile
+ _FileToIDMap : null, // mapping from add-on path to add-on ID
+
+ /**
+ * Reads a directory linked to in a file.
+ *
+ * @param file
+ * The file containing the directory path
+ * @return An nsIFile object representing the linked directory.
+ */
+ _readDirectoryFromFile: function DirInstallLocation__readDirectoryFromFile(aFile) {
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fis.init(aFile, -1, -1, false);
+ let line = { value: "" };
+ if (fis instanceof Ci.nsILineInputStream)
+ fis.readLine(line);
+ fis.close();
+ if (line.value) {
+ let linkedDirectory = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsIFile);
+
+ try {
+ linkedDirectory.initWithPath(line.value);
+ }
+ catch (e) {
+ linkedDirectory.setRelativeDescriptor(aFile.parent, line.value);
+ }
+
+ if (!linkedDirectory.exists()) {
+ logger.warn("File pointer " + aFile.path + " points to " + linkedDirectory.path +
+ " which does not exist");
+ return null;
+ }
+
+ if (!linkedDirectory.isDirectory()) {
+ logger.warn("File pointer " + aFile.path + " points to " + linkedDirectory.path +
+ " which is not a directory");
+ return null;
+ }
+
+ return linkedDirectory;
+ }
+
+ logger.warn("File pointer " + aFile.path + " does not contain a path");
+ return null;
+ },
+
+ /**
+ * Finds all the add-ons installed in this location.
+ */
+ _readAddons: function DirInstallLocation__readAddons() {
+ // Use a snapshot of the directory contents to avoid possible issues with
+ // iterating over a directory while removing files from it (the YAFFS2
+ // embedded filesystem has this issue, see bug 772238).
+ let entries = getDirectoryEntries(this._directory);
+ for (let entry of entries) {
+ let id = entry.leafName;
+
+ if (id == DIR_STAGE || id == DIR_XPI_STAGE || id == DIR_TRASH)
+ continue;
+
+ let directLoad = false;
+ if (entry.isFile() &&
+ id.substring(id.length - 4).toLowerCase() == ".xpi") {
+ directLoad = true;
+ id = id.substring(0, id.length - 4);
+ }
+
+ if (!gIDTest.test(id)) {
+ logger.debug("Ignoring file entry whose name is not a valid add-on ID: " +
+ entry.path);
+ continue;
+ }
+
+ if (entry.isFile() && !directLoad) {
+ let newEntry = this._readDirectoryFromFile(entry);
+ if (!newEntry) {
+ logger.debug("Deleting stale pointer file " + entry.path);
+ try {
+ entry.remove(true);
+ }
+ catch (e) {
+ logger.warn("Failed to remove stale pointer file " + entry.path, e);
+ // Failing to remove the stale pointer file is ignorable
+ }
+ continue;
+ }
+
+ entry = newEntry;
+ this._linkedAddons.push(id);
+ }
+
+ this._IDToFileMap[id] = entry;
+ this._FileToIDMap[entry.path] = id;
+ XPIProvider._addURIMapping(id, entry);
+ }
+ },
+
+ /**
+ * Gets the name of this install location.
+ */
+ get name() {
+ return this._name;
+ },
+
+ /**
+ * Gets the scope of this install location.
+ */
+ get scope() {
+ return this._scope;
+ },
+
+ /**
+ * Gets an array of nsIFiles for add-ons installed in this location.
+ */
+ get addonLocations() {
+ let locations = [];
+ for (let id in this._IDToFileMap) {
+ locations.push(this._IDToFileMap[id].clone());
+ }
+ return locations;
+ },
+
+ /**
+ * Gets the staging directory to put add-ons that are pending install and
+ * uninstall into.
+ *
+ * @return an nsIFile
+ */
+ getStagingDir: function DirInstallLocation_getStagingDir() {
+ let dir = this._directory.clone();
+ dir.append(DIR_STAGE);
+ return dir;
+ },
+
+ requestStagingDir: function() {
+ this._stagingDirLock++;
+
+ if (this._stagingDirPromise)
+ return this._stagingDirPromise;
+
+ OS.File.makeDir(this._directory.path);
+ let stagepath = OS.Path.join(this._directory.path, DIR_STAGE);
+ return this._stagingDirPromise = OS.File.makeDir(stagepath).then(null, (e) => {
+ if (e instanceof OS.File.Error && e.becauseExists)
+ return;
+ logger.error("Failed to create staging directory", e);
+ throw e;
+ });
+ },
+
+ releaseStagingDir: function() {
+ this._stagingDirLock--;
+
+ if (this._stagingDirLock == 0) {
+ this._stagingDirPromise = null;
+ this.cleanStagingDir();
+ }
+
+ return Promise.resolve();
+ },
+
+ /**
+ * Removes the specified files or directories in the staging directory and
+ * then if the staging directory is empty attempts to remove it.
+ *
+ * @param aLeafNames
+ * An array of file or directory to remove from the directory, the
+ * array may be empty
+ */
+ cleanStagingDir: function(aLeafNames = []) {
+ let dir = this.getStagingDir();
+
+ for (let name of aLeafNames) {
+ let file = dir.clone();
+ file.append(name);
+ recursiveRemove(file);
+ }
+
+ if (this._stagingDirLock > 0)
+ return;
+
+ let dirEntries = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+ try {
+ if (dirEntries.nextFile)
+ return;
+ }
+ finally {
+ dirEntries.close();
+ }
+
+ try {
+ setFilePermissions(dir, FileUtils.PERMS_DIRECTORY);
+ dir.remove(false);
+ }
+ catch (e) {
+ logger.warn("Failed to remove staging dir", e);
+ // Failing to remove the staging directory is ignorable
+ }
+ },
+
+ /**
+ * Gets the directory used by old versions for staging XPI and JAR files ready
+ * to be installed.
+ *
+ * @return an nsIFile
+ */
+ getXPIStagingDir: function DirInstallLocation_getXPIStagingDir() {
+ let dir = this._directory.clone();
+ dir.append(DIR_XPI_STAGE);
+ return dir;
+ },
+
+ /**
+ * Returns a directory that is normally on the same filesystem as the rest of
+ * the install location and can be used for temporarily storing files during
+ * safe move operations. Calling this method will delete the existing trash
+ * directory and its contents.
+ *
+ * @return an nsIFile
+ */
+ getTrashDir: function DirInstallLocation_getTrashDir() {
+ let trashDir = this._directory.clone();
+ trashDir.append(DIR_TRASH);
+ let trashDirExists = trashDir.exists();
+ try {
+ if (trashDirExists)
+ recursiveRemove(trashDir);
+ trashDirExists = false;
+ } catch (e) {
+ logger.warn("Failed to remove trash directory", e);
+ }
+ if (!trashDirExists)
+ trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+ return trashDir;
+ },
+
+ /**
+ * Installs an add-on into the install location.
+ *
+ * @param aId
+ * The ID of the add-on to install
+ * @param aSource
+ * The source nsIFile to install from
+ * @param aExistingAddonID
+ * The ID of an existing add-on to uninstall at the same time
+ * @param aCopy
+ * If false the source files will be moved to the new location,
+ * otherwise they will only be copied
+ * @return an nsIFile indicating where the add-on was installed to
+ */
+ installAddon: function DirInstallLocation_installAddon(aId, aSource,
+ aExistingAddonID,
+ aCopy) {
+ let trashDir = this.getTrashDir();
+
+ let transaction = new SafeInstallOperation();
+
+ let self = this;
+ function moveOldAddon(aId) {
+ let file = self._directory.clone();
+ file.append(aId);
+
+ if (file.exists())
+ transaction.moveUnder(file, trashDir);
+
+ file = self._directory.clone();
+ file.append(aId + ".xpi");
+ if (file.exists()) {
+ flushJarCache(file);
+ transaction.moveUnder(file, trashDir);
+ }
+ }
+
+ // If any of these operations fails the finally block will clean up the
+ // temporary directory
+ try {
+ moveOldAddon(aId);
+ if (aExistingAddonID && aExistingAddonID != aId) {
+ moveOldAddon(aExistingAddonID);
+
+ {
+ // Move the data directories.
+ /* XXX ajvincent We can't use OS.File: installAddon isn't compatible
+ * with Promises, nor is SafeInstallOperation. Bug 945540 has been filed
+ * for porting to OS.File.
+ */
+ let oldDataDir = FileUtils.getDir(
+ KEY_PROFILEDIR, ["extension-data", aExistingAddonID], false, true
+ );
+
+ if (oldDataDir.exists()) {
+ let newDataDir = FileUtils.getDir(
+ KEY_PROFILEDIR, ["extension-data", aId], false, true
+ );
+ if (newDataDir.exists()) {
+ let trashData = trashDir.clone();
+ trashData.append("data-directory");
+ transaction.moveUnder(newDataDir, trashData);
+ }
+
+ transaction.moveTo(oldDataDir, newDataDir);
+ }
+ }
+ }
+
+ if (aCopy) {
+ transaction.copy(aSource, this._directory);
+ }
+ else {
+ if (aSource.isFile())
+ flushJarCache(aSource);
+
+ transaction.moveUnder(aSource, this._directory);
+ }
+ }
+ finally {
+ // It isn't ideal if this cleanup fails but it isn't worth rolling back
+ // the install because of it.
+ try {
+ recursiveRemove(trashDir);
+ }
+ catch (e) {
+ logger.warn("Failed to remove trash directory when installing " + aId, e);
+ }
+ }
+
+ let newFile = this._directory.clone();
+ newFile.append(aSource.leafName);
+ try {
+ newFile.lastModifiedTime = Date.now();
+ } catch (e) {
+ logger.warn("failed to set lastModifiedTime on " + newFile.path, e);
+ }
+ this._FileToIDMap[newFile.path] = aId;
+ this._IDToFileMap[aId] = newFile;
+ XPIProvider._addURIMapping(aId, newFile);
+
+ if (aExistingAddonID && aExistingAddonID != aId &&
+ aExistingAddonID in this._IDToFileMap) {
+ delete this._FileToIDMap[this._IDToFileMap[aExistingAddonID]];
+ delete this._IDToFileMap[aExistingAddonID];
+ }
+
+ return newFile;
+ },
+
+ /**
+ * Uninstalls an add-on from this location.
+ *
+ * @param aId
+ * The ID of the add-on to uninstall
+ * @throws if the ID does not match any of the add-ons installed
+ */
+ uninstallAddon: function DirInstallLocation_uninstallAddon(aId) {
+ let file = this._IDToFileMap[aId];
+ if (!file) {
+ logger.warn("Attempted to remove " + aId + " from " +
+ this._name + " but it was already gone");
+ return;
+ }
+
+ file = this._directory.clone();
+ file.append(aId);
+ if (!file.exists())
+ file.leafName += ".xpi";
+
+ if (!file.exists()) {
+ logger.warn("Attempted to remove " + aId + " from " +
+ this._name + " but it was already gone");
+
+ delete this._FileToIDMap[file.path];
+ delete this._IDToFileMap[aId];
+ return;
+ }
+
+ let trashDir = this.getTrashDir();
+
+ if (file.leafName != aId) {
+ logger.debug("uninstallAddon: flushing jar cache " + file.path + " for addon " + aId);
+ flushJarCache(file);
+ }
+
+ let transaction = new SafeInstallOperation();
+
+ try {
+ transaction.moveUnder(file, trashDir);
+ }
+ finally {
+ // It isn't ideal if this cleanup fails, but it is probably better than
+ // rolling back the uninstall at this point
+ try {
+ recursiveRemove(trashDir);
+ }
+ catch (e) {
+ logger.warn("Failed to remove trash directory when uninstalling " + aId, e);
+ }
+ }
+
+ delete this._FileToIDMap[file.path];
+ delete this._IDToFileMap[aId];
+ },
+
+ /**
+ * Gets the ID of the add-on installed in the given nsIFile.
+ *
+ * @param aFile
+ * The nsIFile to look in
+ * @return the ID
+ * @throws if the file does not represent an installed add-on
+ */
+ getIDForLocation: function DirInstallLocation_getIDForLocation(aFile) {
+ if (aFile.path in this._FileToIDMap)
+ return this._FileToIDMap[aFile.path];
+ throw new Error("Unknown add-on location " + aFile.path);
+ },
+
+ /**
+ * Gets the directory that the add-on with the given ID is installed in.
+ *
+ * @param aId
+ * The ID of the add-on
+ * @return The nsIFile
+ * @throws if the ID does not match any of the add-ons installed
+ */
+ getLocationForID: function DirInstallLocation_getLocationForID(aId) {
+ if (aId in this._IDToFileMap)
+ return this._IDToFileMap[aId].clone();
+ throw new Error("Unknown add-on ID " + aId);
+ },
+
+ /**
+ * Returns true if the given addon was installed in this location by a text
+ * file pointing to its real path.
+ *
+ * @param aId
+ * The ID of the addon
+ */
+ isLinkedAddon: function DirInstallLocation__isLinkedAddon(aId) {
+ return this._linkedAddons.indexOf(aId) != -1;
+ }
+};
+
+#ifdef XP_WIN
+/**
+ * An object that identifies a registry install location for add-ons. The location
+ * consists of a registry key which contains string values mapping ID to the
+ * path where an add-on is installed
+ *
+ * @param aName
+ * The string identifier of this Install Location.
+ * @param aRootKey
+ * The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
+ * @param scope
+ * The scope of add-ons installed in this location
+ */
+function WinRegInstallLocation(aName, aRootKey, aScope) {
+ this.locked = true;
+ this._name = aName;
+ this._rootKey = aRootKey;
+ this._scope = aScope;
+ this._IDToFileMap = {};
+ this._FileToIDMap = {};
+
+ let path = this._appKeyPath + "\\Extensions";
+ let key = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+
+ // Reading the registry may throw an exception, and that's ok. In error
+ // cases, we just leave ourselves in the empty state.
+ try {
+ key.open(this._rootKey, path, Ci.nsIWindowsRegKey.ACCESS_READ);
+ }
+ catch (e) {
+ return;
+ }
+
+ this._readAddons(key);
+ key.close();
+}
+
+WinRegInstallLocation.prototype = {
+ _name : "",
+ _rootKey : null,
+ _scope : null,
+ _IDToFileMap : null, // mapping from ID to nsIFile
+ _FileToIDMap : null, // mapping from path to ID
+
+ /**
+ * Retrieves the path of this Application's data key in the registry.
+ */
+ get _appKeyPath() {
+ let appVendor = Services.appinfo.vendor;
+ let appName = Services.appinfo.name;
+
+ // .xulapp-based apps may intentionally not specify a vendor
+ if (appVendor != "")
+ appVendor += "\\";
+
+ return "SOFTWARE\\" + appVendor + appName;
+ },
+
+ /**
+ * Read the registry and build a mapping between ID and path for each
+ * installed add-on.
+ *
+ * @param key
+ * The key that contains the ID to path mapping
+ */
+ _readAddons: function RegInstallLocation__readAddons(aKey) {
+ let count = aKey.valueCount;
+ for (let i = 0; i < count; ++i) {
+ let id = aKey.getValueName(i);
+
+ let file = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsIFile);
+ file.initWithPath(aKey.readStringValue(id));
+
+ if (!file.exists()) {
+ logger.warn("Ignoring missing add-on in " + file.path);
+ continue;
+ }
+
+ this._IDToFileMap[id] = file;
+ this._FileToIDMap[file.path] = id;
+ XPIProvider._addURIMapping(id, file);
+ }
+ },
+
+ /**
+ * Gets the name of this install location.
+ */
+ get name() {
+ return this._name;
+ },
+
+ /**
+ * Gets the scope of this install location.
+ */
+ get scope() {
+ return this._scope;
+ },
+
+ /**
+ * Gets an array of nsIFiles for add-ons installed in this location.
+ */
+ get addonLocations() {
+ let locations = [];
+ for (let id in this._IDToFileMap) {
+ locations.push(this._IDToFileMap[id].clone());
+ }
+ return locations;
+ },
+
+ /**
+ * Gets the ID of the add-on installed in the given nsIFile.
+ *
+ * @param aFile
+ * The nsIFile to look in
+ * @return the ID
+ * @throws if the file does not represent an installed add-on
+ */
+ getIDForLocation: function RegInstallLocation_getIDForLocation(aFile) {
+ if (aFile.path in this._FileToIDMap)
+ return this._FileToIDMap[aFile.path];
+ throw new Error("Unknown add-on location");
+ },
+
+ /**
+ * Gets the nsIFile that the add-on with the given ID is installed in.
+ *
+ * @param aId
+ * The ID of the add-on
+ * @return the nsIFile
+ */
+ getLocationForID: function RegInstallLocation_getLocationForID(aId) {
+ if (aId in this._IDToFileMap)
+ return this._IDToFileMap[aId].clone();
+ throw new Error("Unknown add-on ID");
+ },
+
+ /**
+ * @see DirectoryInstallLocation
+ */
+ isLinkedAddon: function RegInstallLocation_isLinkedAddon(aId) {
+ return true;
+ }
+};
+#endif
+
+var addonTypes = [
+ new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS,
+ STRING_TYPE_NAME,
+ AddonManager.VIEW_TYPE_LIST, 4000,
+ AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL),
+ new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
+ STRING_TYPE_NAME,
+ AddonManager.VIEW_TYPE_LIST, 5000),
+ new AddonManagerPrivate.AddonType("dictionary", URI_EXTENSION_STRINGS,
+ STRING_TYPE_NAME,
+ AddonManager.VIEW_TYPE_LIST, 7000,
+ AddonManager.TYPE_UI_HIDE_EMPTY | AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL),
+ new AddonManagerPrivate.AddonType("locale", URI_EXTENSION_STRINGS,
+ STRING_TYPE_NAME,
+ AddonManager.VIEW_TYPE_LIST, 8000,
+ AddonManager.TYPE_UI_HIDE_EMPTY | AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL),
+];
+
+AddonManagerPrivate.registerProvider(XPIProvider, addonTypes);
diff --git a/components/addons/src/XPIProviderUtils.js b/components/addons/src/XPIProviderUtils.js
new file mode 100644
index 000000000..9f3273b1a
--- /dev/null
+++ b/components/addons/src/XPIProviderUtils.js
@@ -0,0 +1,1430 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cc = Components.classes;
+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");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
+ "resource://gre/modules/addons/AddonRepository.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredSave",
+ "resource://gre/modules/DeferredSave.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.xpi-utils";
+
+// Create a new logger for use by the Addons XPI Provider Utils
+// (Requires AddonManager.jsm)
+var logger = Log.repository.getLogger(LOGGER_ID);
+
+const KEY_PROFILEDIR = "ProfD";
+const FILE_DATABASE = "extensions.sqlite";
+const FILE_JSON_DB = "extensions.json";
+const FILE_OLD_DATABASE = "extensions.rdf";
+const FILE_XPI_ADDONS_LIST = "extensions.ini";
+
+// The value for this is in Makefile.in
+#expand const DB_SCHEMA = __MOZ_EXTENSIONS_DB_SCHEMA__;
+
+// The last version of DB_SCHEMA implemented in SQLITE
+const LAST_SQLITE_DB_SCHEMA = 14;
+const PREF_DB_SCHEMA = "extensions.databaseSchema";
+const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
+const PREF_EM_ENABLED_ADDONS = "extensions.enabledAddons";
+const PREF_EM_DSS_ENABLED = "extensions.dss.enabled";
+
+// Properties that only exist in the database
+const DB_METADATA = ["syncGUID",
+ "installDate",
+ "updateDate",
+ "size",
+ "sourceURI",
+ "releaseNotesURI",
+ "applyBackgroundUpdates"];
+const DB_BOOL_METADATA = ["visible", "active", "userDisabled", "appDisabled",
+ "pendingUninstall", "bootstrap", "skinnable",
+ "softDisabled", "isForeignInstall",
+ "hasBinaryComponents", "strictCompatibility"];
+
+// Properties to save in JSON file
+const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type",
+ "internalName", "updateURL", "updateKey", "optionsURL",
+ "optionsType", "aboutURL", "iconURL", "icon64URL",
+ "defaultLocale", "visible", "active", "userDisabled",
+ "appDisabled", "pendingUninstall", "descriptor", "installDate",
+ "updateDate", "applyBackgroundUpdates", "bootstrap",
+ "skinnable", "size", "sourceURI", "releaseNotesURI",
+ "softDisabled", "foreignInstall", "hasBinaryComponents",
+ "strictCompatibility", "locales", "targetApplications",
+ "targetPlatforms", "multiprocessCompatible",
+ ];
+
+// Time to wait before async save of XPI JSON database, in milliseconds
+const ASYNC_SAVE_DELAY_MS = 20;
+
+const PREFIX_ITEM_URI = "urn:mozilla:item:";
+const RDFURI_ITEM_ROOT = "urn:mozilla:item:root"
+const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
+
+XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1",
+ Ci.nsIRDFService);
+
+function EM_R(aProperty) {
+ return gRDF.GetResource(PREFIX_NS_EM + aProperty);
+}
+
+/**
+ * Converts an RDF literal, resource or integer into a string.
+ *
+ * @param aLiteral
+ * The RDF object to convert
+ * @return a string if the object could be converted or null
+ */
+function getRDFValue(aLiteral) {
+ if (aLiteral instanceof Ci.nsIRDFLiteral)
+ return aLiteral.Value;
+ if (aLiteral instanceof Ci.nsIRDFResource)
+ return aLiteral.Value;
+ if (aLiteral instanceof Ci.nsIRDFInt)
+ return aLiteral.Value;
+ return null;
+}
+
+/**
+ * Gets an RDF property as a string
+ *
+ * @param aDs
+ * The RDF datasource to read the property from
+ * @param aResource
+ * The RDF resource to read the property from
+ * @param aProperty
+ * The property to read
+ * @return a string if the property existed or null
+ */
+function getRDFProperty(aDs, aResource, aProperty) {
+ return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
+}
+
+/**
+ * Asynchronously fill in the _repositoryAddon field for one addon
+ */
+function getRepositoryAddon(aAddon, aCallback) {
+ if (!aAddon) {
+ aCallback(aAddon);
+ return;
+ }
+ function completeAddon(aRepositoryAddon) {
+ aAddon._repositoryAddon = aRepositoryAddon;
+ aAddon.compatibilityOverrides = aRepositoryAddon ?
+ aRepositoryAddon.compatibilityOverrides :
+ null;
+ aCallback(aAddon);
+ }
+ AddonRepository.getCachedAddonByID(aAddon.id, completeAddon);
+}
+
+/**
+ * Wrap an API-supplied function in an exception handler to make it safe to call
+ */
+function makeSafe(aCallback) {
+ return function(...aArgs) {
+ try {
+ aCallback(...aArgs);
+ }
+ catch(ex) {
+ logger.warn("XPI Database callback failed", ex);
+ }
+ }
+}
+
+/**
+ * A helper method to asynchronously call a function on an array
+ * of objects, calling a callback when function(x) has been gathered
+ * for every element of the array.
+ * WARNING: not currently error-safe; if the async function does not call
+ * our internal callback for any of the array elements, asyncMap will not
+ * call the callback parameter.
+ *
+ * @param aObjects
+ * The array of objects to process asynchronously
+ * @param aMethod
+ * Function with signature function(object, function aCallback(f_of_object))
+ * @param aCallback
+ * Function with signature f([aMethod(object)]), called when all values
+ * are available
+ */
+function asyncMap(aObjects, aMethod, aCallback) {
+ var resultsPending = aObjects.length;
+ var results = []
+ if (resultsPending == 0) {
+ aCallback(results);
+ return;
+ }
+
+ function asyncMap_gotValue(aIndex, aValue) {
+ results[aIndex] = aValue;
+ if (--resultsPending == 0) {
+ aCallback(results);
+ }
+ }
+
+ aObjects.map(function asyncMap_each(aObject, aIndex, aArray) {
+ try {
+ aMethod(aObject, function asyncMap_callback(aResult) {
+ asyncMap_gotValue(aIndex, aResult);
+ });
+ }
+ catch (e) {
+ logger.warn("Async map function failed", e);
+ asyncMap_gotValue(aIndex, undefined);
+ }
+ });
+}
+
+/**
+ * A generator to synchronously return result rows from an mozIStorageStatement.
+ *
+ * @param aStatement
+ * The statement to execute
+ */
+function resultRows(aStatement) {
+ try {
+ while (stepStatement(aStatement))
+ yield aStatement.row;
+ }
+ finally {
+ aStatement.reset();
+ }
+}
+
+/**
+ * A helper function to log an SQL error.
+ *
+ * @param aError
+ * The storage error code associated with the error
+ * @param aErrorString
+ * An error message
+ */
+function logSQLError(aError, aErrorString) {
+ logger.error("SQL error " + aError + ": " + aErrorString);
+}
+
+/**
+ * A helper function to log any errors that occur during async statements.
+ *
+ * @param aError
+ * A mozIStorageError to log
+ */
+function asyncErrorLogger(aError) {
+ logSQLError(aError.result, aError.message);
+}
+
+/**
+ * A helper function to step a statement synchronously and log any error that
+ * occurs.
+ *
+ * @param aStatement
+ * A mozIStorageStatement to execute
+ */
+function stepStatement(aStatement) {
+ try {
+ return aStatement.executeStep();
+ }
+ catch (e) {
+ logSQLError(XPIDatabase.connection.lastError,
+ XPIDatabase.connection.lastErrorString);
+ throw e;
+ }
+}
+
+/**
+ * Copies properties from one object to another. If no target object is passed
+ * a new object will be created and returned.
+ *
+ * @param aObject
+ * An object to copy from
+ * @param aProperties
+ * An array of properties to be copied
+ * @param aTarget
+ * An optional target object to copy the properties to
+ * @return the object that the properties were copied onto
+ */
+function copyProperties(aObject, aProperties, aTarget) {
+ if (!aTarget)
+ aTarget = {};
+ aProperties.forEach(function(aProp) {
+ aTarget[aProp] = aObject[aProp];
+ });
+ return aTarget;
+}
+
+/**
+ * Copies properties from a mozIStorageRow to an object. If no target object is
+ * passed a new object will be created and returned.
+ *
+ * @param aRow
+ * A mozIStorageRow to copy from
+ * @param aProperties
+ * An array of properties to be copied
+ * @param aTarget
+ * An optional target object to copy the properties to
+ * @return the object that the properties were copied onto
+ */
+function copyRowProperties(aRow, aProperties, aTarget) {
+ if (!aTarget)
+ aTarget = {};
+ aProperties.forEach(function(aProp) {
+ aTarget[aProp] = aRow.getResultByName(aProp);
+ });
+ return aTarget;
+}
+
+/**
+ * The DBAddonInternal is a special AddonInternal that has been retrieved from
+ * the database. The constructor will initialize the DBAddonInternal with a set
+ * of fields, which could come from either the JSON store or as an
+ * XPIProvider.AddonInternal created from an addon's manifest
+ * @constructor
+ * @param aLoaded
+ * Addon data fields loaded from JSON or the addon manifest.
+ */
+function DBAddonInternal(aLoaded) {
+ copyProperties(aLoaded, PROP_JSON_FIELDS, this);
+
+ if (aLoaded._installLocation) {
+ this._installLocation = aLoaded._installLocation;
+ this.location = aLoaded._installLocation._name;
+ }
+ else if (aLoaded.location) {
+ this._installLocation = XPIProvider.installLocationsByName[this.location];
+ }
+
+ this._key = this.location + ":" + this.id;
+
+ try {
+ this._sourceBundle = this._installLocation.getLocationForID(this.id);
+ }
+ catch (e) {
+ // An exception will be thrown if the add-on appears in the database but
+ // not on disk. In general this should only happen during startup as
+ // this change is being detected.
+ }
+
+ XPCOMUtils.defineLazyGetter(this, "pendingUpgrade",
+ function DBA_pendingUpgradeGetter() {
+ for (let install of XPIProvider.installs) {
+ if (install.state == AddonManager.STATE_INSTALLED &&
+ !(install.addon.inDatabase) &&
+ install.addon.id == this.id &&
+ install.installLocation == this._installLocation) {
+ delete this.pendingUpgrade;
+ return this.pendingUpgrade = install.addon;
+ }
+ };
+ return null;
+ });
+}
+
+function DBAddonInternalPrototype()
+{
+ this.applyCompatibilityUpdate =
+ function(aUpdate, aSyncCompatibility) {
+ this.targetApplications.forEach(function(aTargetApp) {
+ aUpdate.targetApplications.forEach(function(aUpdateTarget) {
+ if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
+ Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) {
+ aTargetApp.minVersion = aUpdateTarget.minVersion;
+ aTargetApp.maxVersion = aUpdateTarget.maxVersion;
+ XPIDatabase.saveChanges();
+ }
+ });
+ });
+ if (aUpdate.multiprocessCompatible !== undefined &&
+ aUpdate.multiprocessCompatible != this.multiprocessCompatible) {
+ this.multiprocessCompatible = aUpdate.multiprocessCompatible;
+ XPIDatabase.saveChanges();
+ }
+ XPIProvider.updateAddonDisabledState(this);
+ };
+
+ this.toJSON =
+ function() {
+ return copyProperties(this, PROP_JSON_FIELDS);
+ };
+
+ Object.defineProperty(this, "inDatabase",
+ { get: function() { return true; },
+ enumerable: true,
+ configurable: true });
+}
+DBAddonInternalPrototype.prototype = AddonInternal.prototype;
+
+DBAddonInternal.prototype = new DBAddonInternalPrototype();
+
+/**
+ * Internal interface: find an addon from an already loaded addonDB
+ */
+function _findAddon(addonDB, aFilter) {
+ for (let addon of addonDB.values()) {
+ if (aFilter(addon)) {
+ return addon;
+ }
+ }
+ return null;
+}
+
+/**
+ * Internal interface to get a filtered list of addons from a loaded addonDB
+ */
+function _filterDB(addonDB, aFilter) {
+ return [for (addon of addonDB.values()) if (aFilter(addon)) addon];
+}
+
+this.XPIDatabase = {
+ // true if the database connection has been opened
+ initialized: false,
+ // The database file
+ jsonFile: FileUtils.getFile(KEY_PROFILEDIR, [FILE_JSON_DB], true),
+ // Migration data loaded from an old version of the database.
+ migrateData: null,
+ // Active add-on directories loaded from extensions.ini and prefs at startup.
+ activeBundles: null,
+
+ // Saved error object if we fail to read an existing database
+ _loadError: null,
+
+ // Error reported by our most recent attempt to read or write the database, if any
+ get lastError() {
+ if (this._loadError)
+ return this._loadError;
+ if (this._deferredSave)
+ return this._deferredSave.lastError;
+ return null;
+ },
+
+ /**
+ * Mark the current stored data dirty, and schedule a flush to disk
+ */
+ saveChanges: function() {
+ if (!this.initialized) {
+ throw new Error("Attempt to use XPI database when it is not initialized");
+ }
+
+ if (XPIProvider._closing) {
+ // use an Error here so we get a stack trace.
+ let err = new Error("XPI database modified after shutdown began");
+ logger.warn(err);
+ }
+
+ if (!this._deferredSave) {
+ this._deferredSave = new DeferredSave(this.jsonFile.path,
+ () => JSON.stringify(this),
+ ASYNC_SAVE_DELAY_MS);
+ }
+
+ let promise = this._deferredSave.saveChanges();
+ if (!this._schemaVersionSet) {
+ this._schemaVersionSet = true;
+ promise.then(
+ count => {
+ // Update the XPIDB schema version preference the first time we successfully
+ // save the database.
+ logger.debug("XPI Database saved, setting schema version preference to " + DB_SCHEMA);
+ Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
+ // Reading the DB worked once, so we don't need the load error
+ this._loadError = null;
+ },
+ error => {
+ // Need to try setting the schema version again later
+ this._schemaVersionSet = false;
+ logger.warn("Failed to save XPI database", error);
+ // this._deferredSave.lastError has the most recent error so we don't
+ // need this any more
+ this._loadError = null;
+ });
+ }
+ },
+
+ flush: function() {
+ // handle the "in memory only" and "saveChanges never called" cases
+ if (!this._deferredSave) {
+ return Promise.resolve(0);
+ }
+
+ return this._deferredSave.flush();
+ },
+
+ /**
+ * Converts the current internal state of the XPI addon database to
+ * a JSON.stringify()-ready structure
+ */
+ toJSON: function() {
+ if (!this.addonDB) {
+ // We never loaded the database?
+ throw new Error("Attempt to save database without loading it first");
+ }
+
+ let toSave = {
+ schemaVersion: DB_SCHEMA,
+ addons: [...this.addonDB.values()]
+ };
+ return toSave;
+ },
+
+ /**
+ * Pull upgrade information from an existing SQLITE database
+ *
+ * @return false if there is no SQLITE database
+ * true and sets this.migrateData to null if the SQLITE DB exists
+ * but does not contain useful information
+ * true and sets this.migrateData to
+ * {location: {id1:{addon1}, id2:{addon2}}, location2:{...}, ...}
+ * if there is useful information
+ */
+ getMigrateDataFromSQLITE: function XPIDB_getMigrateDataFromSQLITE() {
+ let connection = null;
+ let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
+ // Attempt to open the database
+ try {
+ connection = Services.storage.openUnsharedDatabase(dbfile);
+ }
+ catch (e) {
+ logger.warn("Failed to open sqlite database " + dbfile.path + " for upgrade", e);
+ return null;
+ }
+ logger.debug("Migrating data from sqlite");
+ let migrateData = this.getMigrateDataFromDatabase(connection);
+ connection.close();
+ return migrateData;
+ },
+
+ /**
+ * Synchronously opens and reads the database file, upgrading from old
+ * databases or making a new DB if needed.
+ *
+ * The possibilities, in order of priority, are:
+ * 1) Perfectly good, up to date database
+ * 2) Out of date JSON database needs to be upgraded => upgrade
+ * 3) JSON database exists but is mangled somehow => build new JSON
+ * 4) no JSON DB, but a useable SQLITE db we can upgrade from => upgrade
+ * 5) useless SQLITE DB => build new JSON
+ * 6) useable RDF DB => upgrade
+ * 7) useless RDF DB => build new JSON
+ * 8) Nothing at all => build new JSON
+ * @param aRebuildOnError
+ * A boolean indicating whether add-on information should be loaded
+ * from the install locations if the database needs to be rebuilt.
+ * (if false, caller is XPIProvider.checkForChanges() which will rebuild)
+ */
+ syncLoadDB: function XPIDB_syncLoadDB(aRebuildOnError) {
+ this.migrateData = null;
+ let fstream = null;
+ let data = "";
+ try {
+ logger.debug("Opening XPI database " + this.jsonFile.path);
+ fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ fstream.init(this.jsonFile, -1, 0, 0);
+ let cstream = null;
+ try {
+ cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Components.interfaces.nsIConverterInputStream);
+ cstream.init(fstream, "UTF-8", 0, 0);
+
+ let str = {};
+ let read = 0;
+ do {
+ read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
+ data += str.value;
+ } while (read != 0);
+
+ this.parseDB(data, aRebuildOnError);
+ }
+ catch(e) {
+ logger.error("Failed to load XPI JSON data from profile", e);
+ this.rebuildDatabase(aRebuildOnError);
+ }
+ finally {
+ if (cstream)
+ cstream.close();
+ }
+ }
+ catch (e) {
+ if (e.result === Cr.NS_ERROR_FILE_NOT_FOUND) {
+ this.upgradeDB(aRebuildOnError);
+ }
+ else {
+ this.rebuildUnreadableDB(e, aRebuildOnError);
+ }
+ }
+ finally {
+ if (fstream)
+ fstream.close();
+ }
+ // If an async load was also in progress, resolve that promise with our DB;
+ // otherwise create a resolved promise
+ if (this._dbPromise) {
+ this._dbPromise.resolve(this.addonDB);
+ }
+ else
+ this._dbPromise = Promise.resolve(this.addonDB);
+ },
+
+ /**
+ * Parse loaded data, reconstructing the database if the loaded data is not valid
+ * @param aRebuildOnError
+ * If true, synchronously reconstruct the database from installed add-ons
+ */
+ parseDB: function(aData, aRebuildOnError) {
+ try {
+ // dump("Loaded JSON:\n" + aData + "\n");
+ let inputAddons = JSON.parse(aData);
+ // Now do some sanity checks on our JSON db
+ if (!("schemaVersion" in inputAddons) || !("addons" in inputAddons)) {
+ // Content of JSON file is bad, need to rebuild from scratch
+ logger.error("bad JSON file contents");
+ this.rebuildDatabase(aRebuildOnError);
+ return;
+ }
+ if (inputAddons.schemaVersion != DB_SCHEMA) {
+ // Handle mismatched JSON schema version. For now, we assume
+ // compatibility for JSON data, though we throw away any fields we
+ // don't know about (bug 902956)
+ logger.debug("JSON schema mismatch: expected " + DB_SCHEMA +
+ ", actual " + inputAddons.schemaVersion);
+ // When we rev the schema of the JSON database, we need to make sure we
+ // force the DB to save so that the DB_SCHEMA value in the JSON file and
+ // the preference are updated.
+ }
+ // If we got here, we probably have good data
+ // Make AddonInternal instances from the loaded data and save them
+ let addonDB = new Map();
+ for (let loadedAddon of inputAddons.addons) {
+ let newAddon = new DBAddonInternal(loadedAddon);
+ addonDB.set(newAddon._key, newAddon);
+ };
+ this.addonDB = addonDB;
+ logger.debug("Successfully read XPI database");
+ this.initialized = true;
+ }
+ catch(e) {
+ // If we catch and log a SyntaxError from the JSON
+ // parser, the xpcshell test harness fails the test for us: bug 870828
+ if (e.name == "SyntaxError") {
+ logger.error("Syntax error parsing saved XPI JSON data");
+ }
+ else {
+ logger.error("Failed to load XPI JSON data from profile", e);
+ }
+ this.rebuildDatabase(aRebuildOnError);
+ }
+ },
+
+ /**
+ * Upgrade database from earlier (sqlite or RDF) version if available
+ */
+ upgradeDB: function(aRebuildOnError) {
+ try {
+ let schemaVersion = Services.prefs.getIntPref(PREF_DB_SCHEMA);
+ if (schemaVersion <= LAST_SQLITE_DB_SCHEMA) {
+ // we should have an older SQLITE database
+ logger.debug("Attempting to upgrade from SQLITE database");
+ this.migrateData = this.getMigrateDataFromSQLITE();
+ }
+ else {
+ // we've upgraded before but the JSON file is gone, fall through
+ // and rebuild from scratch
+ }
+ }
+ catch(e) {
+ // No schema version pref means either a really old upgrade (RDF) or
+ // a new profile
+ this.migrateData = this.getMigrateDataFromRDF();
+ }
+
+ this.rebuildDatabase(aRebuildOnError);
+ },
+
+ /**
+ * Reconstruct when the DB file exists but is unreadable
+ * (for example because read permission is denied)
+ */
+ rebuildUnreadableDB: function(aError, aRebuildOnError) {
+ logger.warn("Extensions database " + this.jsonFile.path +
+ " exists but is not readable; rebuilding", aError);
+ // Remember the error message until we try and write at least once, so
+ // we know at shutdown time that there was a problem
+ this._loadError = aError;
+ this.rebuildDatabase(aRebuildOnError);
+ },
+
+ /**
+ * Open and read the XPI database asynchronously, upgrading if
+ * necessary. If any DB load operation fails, we need to
+ * synchronously rebuild the DB from the installed extensions.
+ *
+ * @return Promise<Map> resolves to the Map of loaded JSON data stored
+ * in this.addonDB; never rejects.
+ */
+ asyncLoadDB: function XPIDB_asyncLoadDB() {
+ // Already started (and possibly finished) loading
+ if (this._dbPromise) {
+ return this._dbPromise;
+ }
+
+ logger.debug("Starting async load of XPI database " + this.jsonFile.path);
+ let readOptions = {
+ outExecutionDuration: 0
+ };
+ return this._dbPromise = OS.File.read(this.jsonFile.path, null, readOptions).then(
+ byteArray => {
+ logger.debug("Async JSON file read took " + readOptions.outExecutionDuration + " MS");
+ if (this._addonDB) {
+ logger.debug("Synchronous load completed while waiting for async load");
+ return this.addonDB;
+ }
+ logger.debug("Finished async read of XPI database, parsing...");
+ let decoder = new TextDecoder();
+ let data = decoder.decode(byteArray);
+ this.parseDB(data, true);
+ return this.addonDB;
+ })
+ .then(null,
+ error => {
+ if (this._addonDB) {
+ logger.debug("Synchronous load completed while waiting for async load");
+ return this.addonDB;
+ }
+ if (error.becauseNoSuchFile) {
+ this.upgradeDB(true);
+ }
+ else {
+ // it's there but unreadable
+ this.rebuildUnreadableDB(error, true);
+ }
+ return this.addonDB;
+ });
+ },
+
+ /**
+ * Rebuild the database from addon install directories. If this.migrateData
+ * is available, uses migrated information for settings on the addons found
+ * during rebuild
+ * @param aRebuildOnError
+ * A boolean indicating whether add-on information should be loaded
+ * from the install locations if the database needs to be rebuilt.
+ * (if false, caller is XPIProvider.checkForChanges() which will rebuild)
+ */
+ rebuildDatabase: function XIPDB_rebuildDatabase(aRebuildOnError) {
+ this.addonDB = new Map();
+ this.initialized = true;
+
+ if (XPIStates.size == 0) {
+ // No extensions installed, so we're done
+ logger.debug("Rebuilding XPI database with no extensions");
+ return;
+ }
+
+ // If there is no migration data then load the list of add-on directories
+ // that were active during the last run
+ if (!this.migrateData)
+ this.activeBundles = this.getActiveBundles();
+
+ if (aRebuildOnError) {
+ logger.warn("Rebuilding add-ons database from installed extensions.");
+ try {
+ XPIProvider.processFileChanges({}, false);
+ }
+ catch (e) {
+ logger.error("Failed to rebuild XPI database from installed extensions", e);
+ }
+ // Make sure to update the active add-ons and add-ons list on shutdown
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+ }
+ },
+
+ /**
+ * Gets the list of file descriptors of active extension directories or XPI
+ * files from the add-ons list. This must be loaded from disk since the
+ * directory service gives no easy way to get both directly. This list doesn't
+ * include themes as preferences already say which theme is currently active
+ *
+ * @return an array of persistent descriptors for the directories
+ */
+ getActiveBundles: function XPIDB_getActiveBundles() {
+ let bundles = [];
+
+ // non-bootstrapped extensions
+ let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
+ true);
+
+ if (!addonsList.exists())
+ // XXX Irving believes this is broken in the case where there is no
+ // extensions.ini but there are bootstrap extensions
+ return null;
+
+ try {
+ let iniFactory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
+ .getService(Ci.nsIINIParserFactory);
+ let parser = iniFactory.createINIParser(addonsList);
+ let keys = parser.getKeys("ExtensionDirs");
+
+ while (keys.hasMore())
+ bundles.push(parser.getString("ExtensionDirs", keys.getNext()));
+ }
+ catch (e) {
+ logger.warn("Failed to parse extensions.ini", e);
+ return null;
+ }
+
+ // Also include the list of active bootstrapped extensions
+ for (let id in XPIProvider.bootstrappedAddons)
+ bundles.push(XPIProvider.bootstrappedAddons[id].descriptor);
+
+ return bundles;
+ },
+
+ /**
+ * Retrieves migration data from the old extensions.rdf database.
+ *
+ * @return an object holding information about what add-ons were previously
+ * userDisabled and any updated compatibility information
+ */
+ getMigrateDataFromRDF: function XPIDB_getMigrateDataFromRDF(aDbWasMissing) {
+
+ // Migrate data from extensions.rdf
+ let rdffile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_DATABASE], true);
+ if (!rdffile.exists())
+ return null;
+
+ logger.debug("Migrating data from " + FILE_OLD_DATABASE);
+ let migrateData = {};
+
+ try {
+ let ds = gRDF.GetDataSourceBlocking(Services.io.newFileURI(rdffile).spec);
+ let root = Cc["@mozilla.org/rdf/container;1"].
+ createInstance(Ci.nsIRDFContainer);
+ root.Init(ds, gRDF.GetResource(RDFURI_ITEM_ROOT));
+ let elements = root.GetElements();
+
+ while (elements.hasMoreElements()) {
+ let source = elements.getNext().QueryInterface(Ci.nsIRDFResource);
+
+ let location = getRDFProperty(ds, source, "installLocation");
+ if (location) {
+ if (!(location in migrateData))
+ migrateData[location] = {};
+ let id = source.ValueUTF8.substring(PREFIX_ITEM_URI.length);
+ migrateData[location][id] = {
+ version: getRDFProperty(ds, source, "version"),
+ userDisabled: false,
+ targetApplications: []
+ }
+
+ let disabled = getRDFProperty(ds, source, "userDisabled");
+ if (disabled == "true" || disabled == "needs-disable")
+ migrateData[location][id].userDisabled = true;
+
+ let targetApps = ds.GetTargets(source, EM_R("targetApplication"),
+ true);
+ while (targetApps.hasMoreElements()) {
+ let targetApp = targetApps.getNext()
+ .QueryInterface(Ci.nsIRDFResource);
+ let appInfo = {
+ id: getRDFProperty(ds, targetApp, "id")
+ };
+
+ let minVersion = getRDFProperty(ds, targetApp, "updatedMinVersion");
+ if (minVersion) {
+ appInfo.minVersion = minVersion;
+ appInfo.maxVersion = getRDFProperty(ds, targetApp, "updatedMaxVersion");
+ }
+ else {
+ appInfo.minVersion = getRDFProperty(ds, targetApp, "minVersion");
+ appInfo.maxVersion = getRDFProperty(ds, targetApp, "maxVersion");
+ }
+ migrateData[location][id].targetApplications.push(appInfo);
+ }
+ }
+ }
+ }
+ catch (e) {
+ logger.warn("Error reading " + FILE_OLD_DATABASE, e);
+ migrateData = null;
+ }
+
+ return migrateData;
+ },
+
+ /**
+ * Retrieves migration data from a database that has an older or newer schema.
+ *
+ * @return an object holding information about what add-ons were previously
+ * userDisabled and any updated compatibility information
+ */
+ getMigrateDataFromDatabase: function XPIDB_getMigrateDataFromDatabase(aConnection) {
+ let migrateData = {};
+
+ // Attempt to migrate data from a different (even future!) version of the
+ // database
+ try {
+ var stmt = aConnection.createStatement("PRAGMA table_info(addon)");
+
+ const REQUIRED = ["internal_id", "id", "location", "userDisabled",
+ "installDate", "version"];
+
+ let reqCount = 0;
+ let props = [];
+ for (let row in resultRows(stmt)) {
+ if (REQUIRED.indexOf(row.name) != -1) {
+ reqCount++;
+ props.push(row.name);
+ }
+ else if (DB_METADATA.indexOf(row.name) != -1) {
+ props.push(row.name);
+ }
+ else if (DB_BOOL_METADATA.indexOf(row.name) != -1) {
+ props.push(row.name);
+ }
+ }
+
+ if (reqCount < REQUIRED.length) {
+ logger.error("Unable to read anything useful from the database");
+ return null;
+ }
+ stmt.finalize();
+
+ stmt = aConnection.createStatement("SELECT " + props.join(",") + " FROM addon");
+ for (let row in resultRows(stmt)) {
+ if (!(row.location in migrateData))
+ migrateData[row.location] = {};
+ let addonData = {
+ targetApplications: []
+ }
+ migrateData[row.location][row.id] = addonData;
+
+ props.forEach(function(aProp) {
+ if (aProp == "isForeignInstall")
+ addonData.foreignInstall = (row[aProp] == 1);
+ if (DB_BOOL_METADATA.indexOf(aProp) != -1)
+ addonData[aProp] = row[aProp] == 1;
+ else
+ addonData[aProp] = row[aProp];
+ })
+ }
+
+ var taStmt = aConnection.createStatement("SELECT id, minVersion, " +
+ "maxVersion FROM " +
+ "targetApplication WHERE " +
+ "addon_internal_id=:internal_id");
+
+ for (let location in migrateData) {
+ for (let id in migrateData[location]) {
+ taStmt.params.internal_id = migrateData[location][id].internal_id;
+ delete migrateData[location][id].internal_id;
+ for (let row in resultRows(taStmt)) {
+ migrateData[location][id].targetApplications.push({
+ id: row.id,
+ minVersion: row.minVersion,
+ maxVersion: row.maxVersion
+ });
+ }
+ }
+ }
+ }
+ catch (e) {
+ // An error here means the schema is too different to read
+ logger.error("Error migrating data", e);
+ return null;
+ }
+ finally {
+ if (taStmt)
+ taStmt.finalize();
+ if (stmt)
+ stmt.finalize();
+ }
+
+ return migrateData;
+ },
+
+ /**
+ * Shuts down the database connection and releases all cached objects.
+ * Return: Promise{integer} resolves / rejects with the result of the DB
+ * flush after the database is flushed and
+ * all cleanup is done
+ */
+ shutdown: function XPIDB_shutdown() {
+ logger.debug("shutdown");
+ if (this.initialized) {
+ // If our last database I/O had an error, try one last time to save.
+ if (this.lastError)
+ this.saveChanges();
+
+ this.initialized = false;
+
+ // Return a promise that any pending writes of the DB are complete and we
+ // are finished cleaning up
+ let flushPromise = this.flush();
+ flushPromise.then(null, error => {
+ logger.error("Flush of XPI database failed", error);
+ // If our last attempt to read or write the DB failed, force a new
+ // extensions.ini to be written to disk on the next startup
+ Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
+ })
+ .then(count => {
+ // Clear out the cached addons data loaded from JSON
+ delete this.addonDB;
+ delete this._dbPromise;
+ // same for the deferred save
+ delete this._deferredSave;
+ // re-enable the schema version setter
+ delete this._schemaVersionSet;
+ });
+ return flushPromise;
+ }
+ return Promise.resolve(0);
+ },
+
+ /**
+ * Asynchronously list all addons that match the filter function
+ * @param aFilter
+ * Function that takes an addon instance and returns
+ * true if that addon should be included in the selected array
+ * @param aCallback
+ * Called back with an array of addons matching aFilter
+ * or an empty array if none match
+ */
+ getAddonList: function(aFilter, aCallback) {
+ this.asyncLoadDB().then(
+ addonDB => {
+ let addonList = _filterDB(addonDB, aFilter);
+ asyncMap(addonList, getRepositoryAddon, makeSafe(aCallback));
+ })
+ .then(null,
+ error => {
+ logger.error("getAddonList failed", error);
+ makeSafe(aCallback)([]);
+ });
+ },
+
+ /**
+ * (Possibly asynchronously) get the first addon that matches the filter function
+ * @param aFilter
+ * Function that takes an addon instance and returns
+ * true if that addon should be selected
+ * @param aCallback
+ * Called back with the addon, or null if no matching addon is found
+ */
+ getAddon: function(aFilter, aCallback) {
+ return this.asyncLoadDB().then(
+ addonDB => {
+ getRepositoryAddon(_findAddon(addonDB, aFilter), makeSafe(aCallback));
+ })
+ .then(null,
+ error => {
+ logger.error("getAddon failed", e);
+ makeSafe(aCallback)(null);
+ });
+ },
+
+ /**
+ * Asynchronously gets an add-on with a particular ID in a particular
+ * install location.
+ *
+ * @param aId
+ * The ID of the add-on to retrieve
+ * @param aLocation
+ * The name of the install location
+ * @param aCallback
+ * A callback to pass the DBAddonInternal to
+ */
+ getAddonInLocation: function XPIDB_getAddonInLocation(aId, aLocation, aCallback) {
+ this.asyncLoadDB().then(
+ addonDB => getRepositoryAddon(addonDB.get(aLocation + ":" + aId),
+ makeSafe(aCallback)));
+ },
+
+ /**
+ * Asynchronously gets the add-on with the specified ID that is visible.
+ *
+ * @param aId
+ * The ID of the add-on to retrieve
+ * @param aCallback
+ * A callback to pass the DBAddonInternal to
+ */
+ getVisibleAddonForID: function XPIDB_getVisibleAddonForID(aId, aCallback) {
+ this.getAddon(aAddon => ((aAddon.id == aId) && aAddon.visible),
+ aCallback);
+ },
+
+ /**
+ * Asynchronously gets the visible add-ons, optionally restricting by type.
+ *
+ * @param aTypes
+ * An array of types to include or null to include all types
+ * @param aCallback
+ * A callback to pass the array of DBAddonInternals to
+ */
+ getVisibleAddons: function XPIDB_getVisibleAddons(aTypes, aCallback) {
+ this.getAddonList(aAddon => (aAddon.visible &&
+ (!aTypes || (aTypes.length == 0) ||
+ (aTypes.indexOf(aAddon.type) > -1))),
+ aCallback);
+ },
+
+ /**
+ * Synchronously gets all add-ons of a particular type.
+ *
+ * @param aType
+ * The type of add-on to retrieve
+ * @return an array of DBAddonInternals
+ */
+ getAddonsByType: function XPIDB_getAddonsByType(aType) {
+ if (!this.addonDB) {
+ // jank-tastic! Must synchronously load DB if the theme switches from
+ // an XPI theme to a lightweight theme before the DB has loaded,
+ // because we're called from sync XPIProvider.addonChanged
+ logger.warn("Synchronous load of XPI database due to getAddonsByType(" + aType + ")");
+ this.syncLoadDB(true);
+ }
+ return _filterDB(this.addonDB, aAddon => (aAddon.type == aType));
+ },
+
+ /**
+ * Synchronously gets an add-on with a particular internalName.
+ *
+ * @param aInternalName
+ * The internalName of the add-on to retrieve
+ * @return a DBAddonInternal
+ */
+ getVisibleAddonForInternalName: function XPIDB_getVisibleAddonForInternalName(aInternalName) {
+ if (!this.addonDB) {
+ // This may be called when the DB hasn't otherwise been loaded
+ logger.warn("Synchronous load of XPI database due to getVisibleAddonForInternalName");
+ this.syncLoadDB(true);
+ }
+
+ return _findAddon(this.addonDB,
+ aAddon => aAddon.visible &&
+ (aAddon.internalName == aInternalName));
+ },
+
+ /**
+ * Asynchronously gets all add-ons with pending operations.
+ *
+ * @param aTypes
+ * The types of add-ons to retrieve or null to get all types
+ * @param aCallback
+ * A callback to pass the array of DBAddonInternal to
+ */
+ getVisibleAddonsWithPendingOperations:
+ function XPIDB_getVisibleAddonsWithPendingOperations(aTypes, aCallback) {
+
+ this.getAddonList(
+ aAddon => (aAddon.visible &&
+ (aAddon.pendingUninstall ||
+ // Logic here is tricky. If we're active but disabled,
+ // we're pending disable; !active && !disabled, we're pending enable
+ (aAddon.active == aAddon.disabled)) &&
+ (!aTypes || (aTypes.length == 0) || (aTypes.indexOf(aAddon.type) > -1))),
+ aCallback);
+ },
+
+ /**
+ * Asynchronously get an add-on by its Sync GUID.
+ *
+ * @param aGUID
+ * Sync GUID of add-on to fetch
+ * @param aCallback
+ * A callback to pass the DBAddonInternal record to. Receives null
+ * if no add-on with that GUID is found.
+ *
+ */
+ getAddonBySyncGUID: function XPIDB_getAddonBySyncGUID(aGUID, aCallback) {
+ this.getAddon(aAddon => aAddon.syncGUID == aGUID,
+ aCallback);
+ },
+
+ /**
+ * Synchronously gets all add-ons in the database.
+ * This is only called from the preference observer for the default
+ * compatibility version preference, so we can return an empty list if
+ * we haven't loaded the database yet.
+ *
+ * @return an array of DBAddonInternals
+ */
+ getAddons: function XPIDB_getAddons() {
+ if (!this.addonDB) {
+ return [];
+ }
+ return _filterDB(this.addonDB, aAddon => true);
+ },
+
+ /**
+ * Synchronously adds an AddonInternal's metadata to the database.
+ *
+ * @param aAddon
+ * AddonInternal to add
+ * @param aDescriptor
+ * The file descriptor of the add-on
+ * @return The DBAddonInternal that was added to the database
+ */
+ addAddonMetadata: function XPIDB_addAddonMetadata(aAddon, aDescriptor) {
+ if (!this.addonDB) {
+ this.syncLoadDB(false);
+ }
+
+ let newAddon = new DBAddonInternal(aAddon);
+ newAddon.descriptor = aDescriptor;
+ this.addonDB.set(newAddon._key, newAddon);
+ if (newAddon.visible) {
+ this.makeAddonVisible(newAddon);
+ }
+
+ this.saveChanges();
+ return newAddon;
+ },
+
+ /**
+ * Synchronously updates an add-on's metadata in the database. Currently just
+ * removes and recreates.
+ *
+ * @param aOldAddon
+ * The DBAddonInternal to be replaced
+ * @param aNewAddon
+ * The new AddonInternal to add
+ * @param aDescriptor
+ * The file descriptor of the add-on
+ * @return The DBAddonInternal that was added to the database
+ */
+ updateAddonMetadata: function XPIDB_updateAddonMetadata(aOldAddon, aNewAddon,
+ aDescriptor) {
+ this.removeAddonMetadata(aOldAddon);
+ aNewAddon.syncGUID = aOldAddon.syncGUID;
+ aNewAddon.installDate = aOldAddon.installDate;
+ aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
+ aNewAddon.foreignInstall = aOldAddon.foreignInstall;
+ aNewAddon.active = (aNewAddon.visible && !aNewAddon.disabled && !aNewAddon.pendingUninstall);
+
+ // addAddonMetadata does a saveChanges()
+ return this.addAddonMetadata(aNewAddon, aDescriptor);
+ },
+
+ /**
+ * Synchronously removes an add-on from the database.
+ *
+ * @param aAddon
+ * The DBAddonInternal being removed
+ */
+ removeAddonMetadata: function XPIDB_removeAddonMetadata(aAddon) {
+ this.addonDB.delete(aAddon._key);
+ this.saveChanges();
+ },
+
+ /**
+ * Synchronously marks a DBAddonInternal as visible marking all other
+ * instances with the same ID as not visible.
+ *
+ * @param aAddon
+ * The DBAddonInternal to make visible
+ */
+ makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) {
+ logger.debug("Make addon " + aAddon._key + " visible");
+ for (let [, otherAddon] of this.addonDB) {
+ if ((otherAddon.id == aAddon.id) && (otherAddon._key != aAddon._key)) {
+ logger.debug("Hide addon " + otherAddon._key);
+ otherAddon.visible = false;
+ }
+ }
+ aAddon.visible = true;
+ this.saveChanges();
+ },
+
+ /**
+ * Synchronously sets properties for an add-on.
+ *
+ * @param aAddon
+ * The DBAddonInternal being updated
+ * @param aProperties
+ * A dictionary of properties to set
+ */
+ setAddonProperties: function XPIDB_setAddonProperties(aAddon, aProperties) {
+ for (let key in aProperties) {
+ aAddon[key] = aProperties[key];
+ }
+ this.saveChanges();
+ },
+
+ /**
+ * Synchronously sets the Sync GUID for an add-on.
+ * Only called when the database is already loaded.
+ *
+ * @param aAddon
+ * The DBAddonInternal being updated
+ * @param aGUID
+ * GUID string to set the value to
+ * @throws if another addon already has the specified GUID
+ */
+ setAddonSyncGUID: function XPIDB_setAddonSyncGUID(aAddon, aGUID) {
+ // Need to make sure no other addon has this GUID
+ function excludeSyncGUID(otherAddon) {
+ return (otherAddon._key != aAddon._key) && (otherAddon.syncGUID == aGUID);
+ }
+ let otherAddon = _findAddon(this.addonDB, excludeSyncGUID);
+ if (otherAddon) {
+ throw new Error("Addon sync GUID conflict for addon " + aAddon._key +
+ ": " + otherAddon._key + " already has GUID " + aGUID);
+ }
+ aAddon.syncGUID = aGUID;
+ this.saveChanges();
+ },
+
+ /**
+ * Synchronously updates an add-on's active flag in the database.
+ *
+ * @param aAddon
+ * The DBAddonInternal to update
+ */
+ updateAddonActive: function XPIDB_updateAddonActive(aAddon, aActive) {
+ logger.debug("Updating active state for add-on " + aAddon.id + " to " + aActive);
+
+ aAddon.active = aActive;
+ this.saveChanges();
+ },
+
+ /**
+ * Synchronously calculates and updates all the active flags in the database.
+ */
+ updateActiveAddons: function XPIDB_updateActiveAddons() {
+ if (!this.addonDB) {
+ logger.warn("updateActiveAddons called when DB isn't loaded");
+ // force the DB to load
+ this.syncLoadDB(true);
+ }
+ logger.debug("Updating add-on states");
+ for (let [, addon] of this.addonDB) {
+ let newActive = (addon.visible && !addon.disabled && !addon.pendingUninstall);
+ if (newActive != addon.active) {
+ addon.active = newActive;
+ this.saveChanges();
+ }
+ }
+ },
+
+ /**
+ * Writes out the XPI add-ons list for the platform to read.
+ * @return true if the file was successfully updated, false otherwise
+ */
+ writeAddonsList: function XPIDB_writeAddonsList() {
+ if (!this.addonDB) {
+ // force the DB to load
+ this.syncLoadDB(true);
+ }
+ Services.appinfo.invalidateCachesOnRestart();
+
+ let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
+ true);
+ let enabledAddons = [];
+ let text = "[ExtensionDirs]\r\n";
+ let count = 0;
+ let fullCount = 0;
+
+ let activeAddons = _filterDB(
+ this.addonDB,
+ aAddon => aAddon.active && !aAddon.bootstrap && (aAddon.type != "theme"));
+
+ for (let row of activeAddons) {
+ text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
+ enabledAddons.push(encodeURIComponent(row.id) + ":" +
+ encodeURIComponent(row.version));
+ }
+ fullCount += count;
+
+ // The selected skin may come from an inactive theme (the default theme
+ // when a lightweight theme is applied for example)
+ text += "\r\n[ThemeDirs]\r\n";
+
+ let dssEnabled = Services.prefs.getBoolPref(PREF_EM_DSS_ENABLED, false);
+
+ let themes = [];
+ if (dssEnabled) {
+ themes = _filterDB(this.addonDB, aAddon => aAddon.type == "theme");
+ }
+ else {
+ let activeTheme = _findAddon(
+ this.addonDB,
+ aAddon => (aAddon.type == "theme") &&
+ (aAddon.internalName == XPIProvider.selectedSkin));
+ if (activeTheme) {
+ themes.push(activeTheme);
+ }
+ }
+
+ if (themes.length > 0) {
+ count = 0;
+ for (let row of themes) {
+ text += "Extension" + (count++) + "=" + row.descriptor + "\r\n";
+ enabledAddons.push(encodeURIComponent(row.id) + ":" +
+ encodeURIComponent(row.version));
+ }
+ fullCount += count;
+ }
+
+ text += "\r\n[MultiprocessIncompatibleExtensions]\r\n";
+
+ count = 0;
+ for (let row of activeAddons) {
+ if (!row.multiprocessCompatible) {
+ text += "Extension" + (count++) + "=" + row.id + "\r\n";
+ }
+ }
+
+ if (fullCount > 0) {
+ logger.debug("Writing add-ons list");
+
+ try {
+ let addonsListTmp = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST + ".tmp"],
+ true);
+ var fos = FileUtils.openFileOutputStream(addonsListTmp);
+ fos.write(text, text.length);
+ fos.close();
+ addonsListTmp.moveTo(addonsListTmp.parent, FILE_XPI_ADDONS_LIST);
+
+ Services.prefs.setCharPref(PREF_EM_ENABLED_ADDONS, enabledAddons.join(","));
+ }
+ catch (e) {
+ logger.error("Failed to write add-ons list to profile directory", e);
+ return false;
+ }
+ }
+ else {
+ if (addonsList.exists()) {
+ logger.debug("Deleting add-ons list");
+ try {
+ addonsList.remove(false);
+ }
+ catch (e) {
+ logger.error("Failed to remove " + addonsList.path, e);
+ return false;
+ }
+ }
+
+ Services.prefs.clearUserPref(PREF_EM_ENABLED_ADDONS);
+ }
+ return true;
+ }
+};
diff --git a/components/addons/src/addonManager.js b/components/addons/src/addonManager.js
new file mode 100644
index 000000000..2628ea87b
--- /dev/null
+++ b/components/addons/src/addonManager.js
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 serves as integration between the platform and AddonManager.
+ * It is responsible for initializing and shutting down the AddonManager as well
+ * as passing new installs from webpages to the AddonManager.
+ */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+const PREF_EM_UPDATE_INTERVAL = "extensions.update.interval";
+
+// The old XPInstall error codes
+const EXECUTION_ERROR = -203;
+const CANT_READ_ARCHIVE = -207;
+const USER_CANCELLED = -210;
+const DOWNLOAD_ERROR = -228;
+const UNSUPPORTED_TYPE = -244;
+const SUCCESS = 0;
+
+const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
+const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
+const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
+
+const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var gSingleton = null;
+
+var gParentMM = null;
+
+
+function amManager() {
+ Cu.import("resource://gre/modules/AddonManager.jsm");
+
+ let globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ globalMM.loadFrameScript(CHILD_SCRIPT, true);
+ globalMM.addMessageListener(MSG_INSTALL_ADDONS, this);
+
+ gParentMM = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ gParentMM.addMessageListener(MSG_INSTALL_ENABLED, this);
+
+ // Needed so receiveMessage can be called directly by JS callers
+ this.wrappedJSObject = this;
+}
+
+amManager.prototype = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "addons-startup")
+ AddonManagerPrivate.startup();
+ },
+
+ /**
+ * @see amIAddonManager.idl
+ */
+ mapURIToAddonID: function(uri, id) {
+ id.value = AddonManager.mapURIToAddonID(uri);
+ return !!id.value;
+ },
+
+ /**
+ * @see amIWebInstaller.idl
+ */
+ isInstallEnabled: function(aMimetype, aReferer) {
+ return AddonManager.isInstallEnabled(aMimetype);
+ },
+
+ /**
+ * @see amIWebInstaller.idl
+ */
+ installAddonsFromWebpage: function(aMimetype, aBrowser, aInstallingPrincipal,
+ aUris, aHashes, aNames, aIcons, aCallback) {
+ if (aUris.length == 0)
+ return false;
+
+ let retval = true;
+ if (!AddonManager.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
+ aCallback = null;
+ retval = false;
+ }
+
+ let installs = [];
+ function buildNextInstall() {
+ if (aUris.length == 0) {
+ AddonManager.installAddonsFromWebpage(aMimetype, aBrowser, aInstallingPrincipal, installs);
+ return;
+ }
+ let uri = aUris.shift();
+ AddonManager.getInstallForURL(uri, function buildNextInstall_getInstallForURL(aInstall) {
+ function callCallback(aUri, aStatus) {
+ try {
+ aCallback.onInstallEnded(aUri, aStatus);
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+
+ if (aInstall) {
+ installs.push(aInstall);
+ if (aCallback) {
+ aInstall.addListener({
+ onDownloadCancelled: function(aInstall) {
+ callCallback(uri, USER_CANCELLED);
+ },
+
+ onDownloadFailed: function(aInstall) {
+ if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE)
+ callCallback(uri, CANT_READ_ARCHIVE);
+ else
+ callCallback(uri, DOWNLOAD_ERROR);
+ },
+
+ onInstallFailed: function(aInstall) {
+ callCallback(uri, EXECUTION_ERROR);
+ },
+
+ onInstallEnded: function(aInstall, aStatus) {
+ callCallback(uri, SUCCESS);
+ }
+ });
+ }
+ }
+ else if (aCallback) {
+ aCallback.onInstallEnded(uri, UNSUPPORTED_TYPE);
+ }
+ buildNextInstall();
+ }, aMimetype, aHashes.shift(), aNames.shift(), aIcons.shift(), null, aBrowser);
+ }
+ buildNextInstall();
+
+ return retval;
+ },
+
+ notify: function(aTimer) {
+ AddonManagerPrivate.backgroundUpdateTimerHandler();
+ },
+
+ /**
+ * messageManager callback function.
+ *
+ * Listens to requests from child processes for InstallTrigger
+ * activity, and sends back callbacks.
+ */
+ receiveMessage: function(aMessage) {
+ let payload = aMessage.data;
+
+ switch (aMessage.name) {
+ case MSG_INSTALL_ENABLED:
+ return AddonManager.isInstallEnabled(payload.mimetype);
+
+ case MSG_INSTALL_ADDONS: {
+ let callback = null;
+ if (payload.callbackID != -1) {
+ callback = {
+ onInstallEnded: function(url, status) {
+ gParentMM.broadcastAsyncMessage(MSG_INSTALL_CALLBACK, {
+ callbackID: payload.callbackID,
+ url: url,
+ status: status
+ });
+ },
+ };
+ }
+
+ return this.installAddonsFromWebpage(payload.mimetype,
+ aMessage.target, payload.triggeringPrincipal, payload.uris,
+ payload.hashes, payload.names, payload.icons, callback);
+ }
+ }
+ },
+
+ classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"),
+ _xpcom_factory: {
+ createInstance: function(aOuter, aIid) {
+ if (aOuter != null)
+ throw Components.Exception("Component does not support aggregation",
+ Cr.NS_ERROR_NO_AGGREGATION);
+
+ if (!gSingleton)
+ gSingleton = new amManager();
+ return gSingleton.QueryInterface(aIid);
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.amIAddonManager,
+ Ci.amIWebInstaller,
+ Ci.nsITimerCallback,
+ Ci.nsIObserver,
+ Ci.nsIMessageListener])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amManager]);
diff --git a/components/addons/src/amContentHandler.js b/components/addons/src/amContentHandler.js
new file mode 100644
index 000000000..8dc4dfecd
--- /dev/null
+++ b/components/addons/src/amContentHandler.js
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cr = Components.results;
+
+const XPI_CONTENT_TYPE = "application/x-xpinstall";
+const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function amContentHandler() {
+}
+
+amContentHandler.prototype = {
+ /**
+ * Handles a new request for an application/x-xpinstall file.
+ *
+ * @param aMimetype
+ * The mimetype of the file
+ * @param aContext
+ * The context passed to nsIChannel.asyncOpen
+ * @param aRequest
+ * The nsIRequest dealing with the content
+ */
+ handleContent: function(aMimetype, aContext, aRequest) {
+ if (aMimetype != XPI_CONTENT_TYPE)
+ throw Cr.NS_ERROR_WONT_HANDLE_CONTENT;
+
+ if (!(aRequest instanceof Ci.nsIChannel))
+ throw Cr.NS_ERROR_WONT_HANDLE_CONTENT;
+
+ let uri = aRequest.URI;
+
+ let window = null;
+ let callbacks = aRequest.notificationCallbacks ?
+ aRequest.notificationCallbacks :
+ aRequest.loadGroup.notificationCallbacks;
+ if (callbacks)
+ window = callbacks.getInterface(Ci.nsIDOMWindow);
+
+ aRequest.cancel(Cr.NS_BINDING_ABORTED);
+
+ let installs = {
+ uris: [uri.spec],
+ hashes: [null],
+ names: [null],
+ icons: [null],
+ mimetype: XPI_CONTENT_TYPE,
+ triggeringPrincipal: aRequest.loadInfo.triggeringPrincipal,
+ callbackID: -1
+ };
+
+ if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ // When running in the main process this might be a frame inside an
+ // in-content UI page, walk up to find the first frame element in a chrome
+ // privileged document
+ let element = window.frameElement;
+ let ssm = Services.scriptSecurityManager;
+ while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal))
+ element = element.ownerDocument.defaultView.frameElement;
+
+ if (element) {
+ let listener = Cc["@mozilla.org/addons/integration;1"].
+ getService(Ci.nsIMessageListener);
+ listener.wrappedJSObject.receiveMessage({
+ name: MSG_INSTALL_ADDONS,
+ target: element,
+ data: installs,
+ });
+ return;
+ }
+ }
+
+ // Fall back to sending through the message manager
+ let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+
+ messageManager.sendAsyncMessage(MSG_INSTALL_ADDONS, installs);
+ },
+
+ classID: Components.ID("{7beb3ba8-6ec3-41b4-b67c-da89b8518922}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler]),
+
+ log : function(aMsg) {
+ let msg = "amContentHandler.js: " + (aMsg.join ? aMsg.join("") : aMsg);
+ Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
+ logStringMessage(msg);
+ dump(msg + "\n");
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amContentHandler]);
diff --git a/components/addons/src/amInstallTrigger.js b/components/addons/src/amInstallTrigger.js
new file mode 100644
index 000000000..5fc0e1717
--- /dev/null
+++ b/components/addons/src/amInstallTrigger.js
@@ -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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+
+const XPINSTALL_MIMETYPE = "application/x-xpinstall";
+
+const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
+const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
+const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
+
+
+var log = Log.repository.getLogger("AddonManager.InstallTrigger");
+log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"];
+
+function CallbackObject(id, callback, urls, mediator) {
+ this.id = id;
+ this.callback = callback;
+ this.urls = new Set(urls);
+ this.callCallback = function(url, status) {
+ try {
+ this.callback(url, status);
+ }
+ catch (e) {
+ log.warn("InstallTrigger callback threw an exception: " + e);
+ }
+
+ this.urls.delete(url);
+ if (this.urls.size == 0)
+ mediator._callbacks.delete(id);
+ };
+}
+
+function RemoteMediator(windowID) {
+ this._windowID = windowID;
+ this._lastCallbackID = 0;
+ this._callbacks = new Map();
+ this.mm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsISyncMessageSender);
+ this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this);
+}
+
+RemoteMediator.prototype = {
+ receiveMessage: function(message) {
+ if (message.name == MSG_INSTALL_CALLBACK) {
+ let payload = message.data;
+ let callbackHandler = this._callbacks.get(payload.callbackID);
+ if (callbackHandler) {
+ callbackHandler.callCallback(payload.url, payload.status);
+ }
+ }
+ },
+
+ enabled: function(url) {
+ let params = {
+ mimetype: XPINSTALL_MIMETYPE
+ };
+ return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0];
+ },
+
+ install: function(installs, principal, callback, window) {
+ let callbackID = this._addCallback(callback, installs.uris);
+
+ installs.mimetype = XPINSTALL_MIMETYPE;
+ installs.triggeringPrincipal = principal;
+ installs.callbackID = callbackID;
+
+ if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ // When running in the main process this might be a frame inside an
+ // in-content UI page, walk up to find the first frame element in a chrome
+ // privileged document
+ let element = window.frameElement;
+ let ssm = Services.scriptSecurityManager;
+ while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal))
+ element = element.ownerDocument.defaultView.frameElement;
+
+ if (element) {
+ let listener = Cc["@mozilla.org/addons/integration;1"].
+ getService(Ci.nsIMessageListener);
+ return listener.wrappedJSObject.receiveMessage({
+ name: MSG_INSTALL_ADDONS,
+ target: element,
+ data: installs,
+ });
+ }
+ }
+
+ // Fall back to sending through the message manager
+ let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+
+ return messageManager.sendSyncMessage(MSG_INSTALL_ADDONS, installs)[0];
+ },
+
+ _addCallback: function(callback, urls) {
+ if (!callback || typeof callback != "function")
+ return -1;
+
+ let callbackID = this._windowID + "-" + ++this._lastCallbackID;
+ let callbackObject = new CallbackObject(callbackID, callback, urls, this);
+ this._callbacks.set(callbackID, callbackObject);
+ return callbackID;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference])
+};
+
+
+function InstallTrigger() {
+}
+
+InstallTrigger.prototype = {
+ // Here be magic. We've declared ourselves as providing the
+ // nsIDOMGlobalPropertyInitializer interface, and are registered in the
+ // "JavaScript-global-property" category in the XPCOM category manager. This
+ // means that for newly created windows, XPCOM will createinstance this
+ // object, and then call init, passing in the window for which we need to
+ // provide an instance. We then initialize ourselves and return the webidl
+ // version of this object using the webidl-provided _create method, which
+ // XPCOM will then duly expose as a property value on the window. All this
+ // indirection is necessary because webidl does not (yet) support statics
+ // (bug 863952). See bug 926712 for more details about this implementation.
+ init: function(window) {
+ this._window = window;
+ this._principal = window.document.nodePrincipal;
+ this._url = window.document.documentURIObject;
+
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ let utils = window.getInterface(Components.interfaces.nsIDOMWindowUtils);
+ this._mediator = new RemoteMediator(utils.currentInnerWindowID);
+
+ return window.InstallTriggerImpl._create(window, this);
+ },
+
+ enabled: function() {
+ return this._mediator.enabled(this._url.spec);
+ },
+
+ updateEnabled: function() {
+ return this.enabled();
+ },
+
+ install: function(installs, callback) {
+ let installData = {
+ uris: [],
+ hashes: [],
+ names: [],
+ icons: [],
+ };
+
+ for (let name of Object.keys(installs)) {
+ let item = installs[name];
+ if (typeof item === "string") {
+ item = { URL: item };
+ }
+ if (!item.URL) {
+ throw new this._window.DOMError("Error", "Missing URL property for '" + name + "'");
+ }
+
+ let url = this._resolveURL(item.URL);
+ if (!this._checkLoadURIFromScript(url)) {
+ throw new this._window.DOMError("SecurityError", "Insufficient permissions to install: " + url.spec);
+ }
+
+ let iconUrl = null;
+ if (item.IconURL) {
+ iconUrl = this._resolveURL(item.IconURL);
+ if (!this._checkLoadURIFromScript(iconUrl)) {
+ iconUrl = null; // If page can't load the icon, just ignore it
+ }
+ }
+
+ installData.uris.push(url.spec);
+ installData.hashes.push(item.Hash || null);
+ installData.names.push(name);
+ installData.icons.push(iconUrl ? iconUrl.spec : null);
+ }
+
+ return this._mediator.install(installData, this._principal, callback, this._window);
+ },
+
+ startSoftwareUpdate: function(url, flags) {
+ let filename = Services.io.newURI(url, null, null)
+ .QueryInterface(Ci.nsIURL)
+ .filename;
+ let args = {};
+ args[filename] = { "URL": url };
+ return this.install(args);
+ },
+
+ installChrome: function(type, url, skin) {
+ return this.startSoftwareUpdate(url);
+ },
+
+ _resolveURL: function(url) {
+ return Services.io.newURI(url, null, this._url);
+ },
+
+ _checkLoadURIFromScript: function(uri) {
+ let secman = Services.scriptSecurityManager;
+ try {
+ secman.checkLoadURIWithPrincipal(this._principal,
+ uri,
+ secman.DISALLOW_INHERIT_PRINCIPAL);
+ return true;
+ }
+ catch(e) {
+ return false;
+ }
+ },
+
+ classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"),
+ contractID: "@mozilla.org/addons/installtrigger;1",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer])
+};
+
+
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]);
diff --git a/components/addons/src/amWebInstallListener.js b/components/addons/src/amWebInstallListener.js
new file mode 100644
index 000000000..9b9c53f44
--- /dev/null
+++ b/components/addons/src/amWebInstallListener.js
@@ -0,0 +1,338 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 is a default implementation of amIWebInstallListener that should work
+ * for most applications but can be overriden. It notifies the observer service
+ * about blocked installs. For normal installs it pops up an install
+ * confirmation when all the add-ons have been downloaded.
+ */
+
+"use strict";
+
+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/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PromptUtils", "resource://gre/modules/SharedPromptUtils.jsm");
+
+const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
+
+// Installation can begin from any of these states
+const READY_STATES = [
+ AddonManager.STATE_AVAILABLE,
+ AddonManager.STATE_DOWNLOAD_FAILED,
+ AddonManager.STATE_INSTALL_FAILED,
+ AddonManager.STATE_CANCELLED
+];
+
+Cu.import("resource://gre/modules/Log.jsm");
+const LOGGER_ID = "addons.weblistener";
+
+// Create a new logger for use by the Addons Web Listener
+// (Requires AddonManager.jsm)
+var logger = Log.repository.getLogger(LOGGER_ID);
+
+function notifyObservers(aTopic, aBrowser, aUri, aInstalls) {
+ let info = {
+ browser: aBrowser,
+ originatingURI: aUri,
+ installs: aInstalls,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
+ };
+ Services.obs.notifyObservers(info, aTopic, null);
+}
+
+/**
+ * Creates a new installer to monitor downloads and prompt to install when
+ * ready
+ *
+ * @param aBrowser
+ * The browser that started the installations
+ * @param aUrl
+ * The URL that started the installations
+ * @param aInstalls
+ * An array of AddonInstalls
+ */
+function Installer(aBrowser, aUrl, aInstalls) {
+ this.browser = aBrowser;
+ this.url = aUrl;
+ this.downloads = aInstalls;
+ this.installed = [];
+
+ notifyObservers("addon-install-started", aBrowser, aUrl, aInstalls);
+
+ aInstalls.forEach(function(aInstall) {
+ aInstall.addListener(this);
+
+ // Start downloading if it hasn't already begun
+ if (READY_STATES.indexOf(aInstall.state) != -1)
+ aInstall.install();
+ }, this);
+
+ this.checkAllDownloaded();
+}
+
+Installer.prototype = {
+ browser: null,
+ downloads: null,
+ installed: null,
+ isDownloading: true,
+
+ /**
+ * Checks if all downloads are now complete and if so prompts to install.
+ */
+ checkAllDownloaded: function() {
+ // Prevent re-entrancy caused by the confirmation dialog cancelling unwanted
+ // installs.
+ if (!this.isDownloading)
+ return;
+
+ var failed = [];
+ var installs = [];
+
+ for (let install of this.downloads) {
+ switch (install.state) {
+ case AddonManager.STATE_AVAILABLE:
+ case AddonManager.STATE_DOWNLOADING:
+ // Exit early if any add-ons haven't started downloading yet or are
+ // still downloading
+ return;
+ case AddonManager.STATE_DOWNLOAD_FAILED:
+ failed.push(install);
+ break;
+ case AddonManager.STATE_DOWNLOADED:
+ // App disabled items are not compatible and so fail to install
+ if (install.addon.appDisabled)
+ failed.push(install);
+ else
+ installs.push(install);
+
+ if (install.linkedInstalls) {
+ install.linkedInstalls.forEach(function(aInstall) {
+ aInstall.addListener(this);
+ // App disabled items are not compatible and so fail to install
+ if (aInstall.addon.appDisabled)
+ failed.push(aInstall);
+ else
+ installs.push(aInstall);
+ }, this);
+ }
+ break;
+ case AddonManager.STATE_CANCELLED:
+ // Just ignore cancelled downloads
+ break;
+ default:
+ logger.warn("Download of " + install.sourceURI.spec + " in unexpected state " +
+ install.state);
+ }
+ }
+
+ this.isDownloading = false;
+ this.downloads = installs;
+
+ if (failed.length > 0) {
+ // Stop listening and cancel any installs that are failed because of
+ // compatibility reasons.
+ failed.forEach(function(aInstall) {
+ if (aInstall.state == AddonManager.STATE_DOWNLOADED) {
+ aInstall.removeListener(this);
+ aInstall.cancel();
+ }
+ }, this);
+ notifyObservers("addon-install-failed", this.browser, this.url, failed);
+ }
+
+ // If none of the downloads were successful then exit early
+ if (this.downloads.length == 0)
+ return;
+
+ // Check for a custom installation prompt that may be provided by the
+ // applicaton
+ if ("@mozilla.org/addons/web-install-prompt;1" in Cc) {
+ try {
+ let prompt = Cc["@mozilla.org/addons/web-install-prompt;1"].
+ getService(Ci.amIWebInstallPrompt);
+ prompt.confirm(this.browser, this.url, this.downloads, this.downloads.length);
+ return;
+ }
+ catch (e) {}
+ }
+
+ let args = {};
+ args.url = this.url;
+ args.installs = this.downloads;
+ args.wrappedJSObject = args;
+
+ try {
+ let parentWindow = null;
+ if (this.browser) {
+ parentWindow = this.browser.ownerDocument.defaultView;
+ PromptUtils.fireDialogEvent(parentWindow, "DOMWillOpenModalDialog", this.browser);
+ }
+ Services.ww.openWindow(parentWindow, URI_XPINSTALL_DIALOG,
+ null, "chrome,modal,centerscreen", args);
+ } catch (e) {
+ logger.warn("Exception showing install confirmation dialog", e);
+ this.downloads.forEach(function(aInstall) {
+ aInstall.removeListener(this);
+ // Cancel the installs, as currently there is no way to make them fail
+ // from here.
+ aInstall.cancel();
+ }, this);
+ notifyObservers("addon-install-cancelled", this.browser, this.url,
+ this.downloads);
+ }
+ },
+
+ /**
+ * Checks if all installs are now complete and if so notifies observers.
+ */
+ checkAllInstalled: function() {
+ var failed = [];
+
+ for (let install of this.downloads) {
+ switch(install.state) {
+ case AddonManager.STATE_DOWNLOADED:
+ case AddonManager.STATE_INSTALLING:
+ // Exit early if any add-ons haven't started installing yet or are
+ // still installing
+ return;
+ case AddonManager.STATE_INSTALL_FAILED:
+ failed.push(install);
+ break;
+ }
+ }
+
+ this.downloads = null;
+
+ if (failed.length > 0)
+ notifyObservers("addon-install-failed", this.browser, this.url, failed);
+
+ if (this.installed.length > 0)
+ notifyObservers("addon-install-complete", this.browser, this.url, this.installed);
+ this.installed = null;
+ },
+
+ onDownloadCancelled: function(aInstall) {
+ aInstall.removeListener(this);
+ this.checkAllDownloaded();
+ },
+
+ onDownloadFailed: function(aInstall) {
+ aInstall.removeListener(this);
+ this.checkAllDownloaded();
+ },
+
+ onDownloadEnded: function(aInstall) {
+ this.checkAllDownloaded();
+ return false;
+ },
+
+ onInstallCancelled: function(aInstall) {
+ aInstall.removeListener(this);
+ this.checkAllInstalled();
+ },
+
+ onInstallFailed: function(aInstall) {
+ aInstall.removeListener(this);
+ this.checkAllInstalled();
+ },
+
+ onInstallEnded: function(aInstall) {
+ aInstall.removeListener(this);
+ this.installed.push(aInstall);
+
+ // If installing a theme that is disabled and can be enabled then enable it
+ if (aInstall.addon.type == "theme" &&
+ aInstall.addon.userDisabled == true &&
+ aInstall.addon.appDisabled == false) {
+ aInstall.addon.userDisabled = false;
+ }
+
+ this.checkAllInstalled();
+ }
+};
+
+function extWebInstallListener() {
+}
+
+extWebInstallListener.prototype = {
+ /**
+ * @see amIWebInstallListener.idl
+ */
+ onWebInstallDisabled: function(aBrowser, aUri, aInstalls) {
+ let info = {
+ browser: aBrowser,
+ originatingURI: aUri,
+ installs: aInstalls,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
+ };
+ Services.obs.notifyObservers(info, "addon-install-disabled", null);
+ },
+
+ /**
+ * @see amIWebInstallListener.idl
+ */
+ onWebInstallOriginBlocked: function(aBrowser, aUri, aInstalls) {
+ let info = {
+ browser: aBrowser,
+ originatingURI: aUri,
+ installs: aInstalls,
+
+ install: function() {
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
+ };
+ Services.obs.notifyObservers(info, "addon-install-origin-blocked", null);
+
+ return false;
+ },
+
+ /**
+ * @see amIWebInstallListener.idl
+ */
+ onWebInstallBlocked: function(aBrowser, aUri, aInstalls) {
+ let info = {
+ browser: aBrowser,
+ originatingURI: aUri,
+ installs: aInstalls,
+
+ install: function() {
+ new Installer(this.browser, this.originatingURI, this.installs);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
+ };
+ Services.obs.notifyObservers(info, "addon-install-blocked", null);
+
+ return false;
+ },
+
+ /**
+ * @see amIWebInstallListener.idl
+ */
+ onWebInstallRequested: function(aBrowser, aUri, aInstalls) {
+ new Installer(aBrowser, aUri, aInstalls);
+
+ // We start the installs ourself
+ return false;
+ },
+
+ classDescription: "XPI Install Handler",
+ contractID: "@mozilla.org/addons/web-install-listener;1",
+ classID: Components.ID("{0f38e086-89a3-40a5-8ffc-9b694de1d04a}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallListener,
+ Ci.amIWebInstallListener2])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([extWebInstallListener]);
diff --git a/components/alerts/content/alert.css b/components/alerts/content/alert.css
new file mode 100644
index 000000000..81e5cdd35
--- /dev/null
+++ b/components/alerts/content/alert.css
@@ -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/. */
+
+#alertBox[animate] {
+ animation-fill-mode: both;
+ animation-name: alert-animation;
+}
+
+#alertBox[animate]:not([clicked]):not([closing]):hover {
+ animation-play-state: paused;
+}
+
+#alertBox:not([hasOrigin]) > box > #alertTextBox > #alertFooter,
+#alertBox:not([hasIcon]) > box > #alertIcon,
+#alertImage:not([src]) {
+ display: none;
+}
+
+#alertTitleBox {
+ -moz-box-pack: center;
+ -moz-box-align: center;
+}
+
+.alertText {
+ white-space: pre-wrap;
+}
+
+@keyframes alert-animation {
+ to {
+ visibility: hidden;
+ }
+}
diff --git a/components/alerts/content/alert.js b/components/alerts/content/alert.js
new file mode 100644
index 000000000..b2ebedbc0
--- /dev/null
+++ b/components/alerts/content/alert.js
@@ -0,0 +1,366 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Services.jsm");
+
+/*
+ * This indicates from which corner of the screen alerts slide in,
+ * and from which direction (horizontal/vertical).
+ * 0, the default, represents bottom right, sliding vertically.
+ * Use any bitwise combination of the following constants:
+ * NS_ALERT_HORIZONTAL (1), NS_ALERT_LEFT (2), NS_ALERT_TOP (4).
+ *
+ * 6 4
+ * +-----------+
+ * 7| |5
+ * | |
+ * 3| |1
+ * +-----------+
+ * 2 0
+ */
+const NS_ALERT_HORIZONTAL = 1;
+const NS_ALERT_LEFT = 2;
+const NS_ALERT_TOP = 4;
+
+#ifdef XP_WIN
+const WINDOW_MARGIN = 0;
+const WINDOW_SHADOW_SPREAD = 10;
+#else
+const WINDOW_MARGIN = 10;
+const WINDOW_SHADOW_SPREAD = 0;
+#endif
+
+const BODY_TEXT_LIMIT = 200;
+
+var gOrigin = 0; // Default value: alert from bottom right.
+var gReplacedWindow = null;
+var gAlertListener = null;
+var gAlertTextClickable = false;
+var gAlertCookie = "";
+var gIsReplaced = false;
+var gRequireInteraction = false;
+
+function prefillAlertInfo() {
+ // unwrap all the args....
+ // arguments[0] --> the image src url
+ // arguments[1] --> the alert title
+ // arguments[2] --> the alert text
+ // arguments[3] --> is the text clickable?
+ // arguments[4] --> the alert cookie to be passed back to the listener
+ // arguments[5] --> the alert origin reported by the look and feel
+ // arguments[6] --> bidi
+ // arguments[7] --> lang
+ // arguments[8] --> requires interaction
+ // arguments[9] --> replaced alert window (nsIDOMWindow)
+ // arguments[10] --> an optional callback listener (nsIObserver)
+ // arguments[11] -> the nsIURI.hostPort of the origin, optional
+ // arguments[12] -> the alert icon URL, optional
+
+ document.getElementById('alertTime').setAttribute('value', (new Date).getTime());
+
+ switch (window.arguments.length) {
+ default:
+ case 13: {
+ if (window.arguments[12]) {
+ let alertBox = document.getElementById("alertBox");
+ alertBox.setAttribute("hasIcon", true);
+
+ let icon = document.getElementById("alertIcon");
+ icon.src = window.arguments[12];
+ }
+ }
+ case 12: {
+ if (window.arguments[11]) {
+ let alertBox = document.getElementById("alertBox");
+ alertBox.setAttribute("hasOrigin", true);
+
+ let hostPort = window.arguments[11];
+ const ALERT_BUNDLE = Services.strings.createBundle(
+ "chrome://alerts/locale/alert.properties");
+ const BRAND_BUNDLE = Services.strings.createBundle(
+ "chrome://branding/locale/brand.properties");
+ const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
+ let label = document.getElementById("alertSourceLabel");
+ label.setAttribute("value",
+ ALERT_BUNDLE.formatStringFromName("source.label",
+ [hostPort],
+ 1));
+ let doNotDisturbMenuItem = document.getElementById("doNotDisturbMenuItem");
+ doNotDisturbMenuItem.setAttribute("label",
+ ALERT_BUNDLE.formatStringFromName("doNotDisturb.label",
+ [BRAND_NAME],
+ 1));
+ let disableForOrigin = document.getElementById("disableForOriginMenuItem");
+ disableForOrigin.setAttribute("label",
+ ALERT_BUNDLE.formatStringFromName("webActions.disableForOrigin.label",
+ [hostPort],
+ 1));
+ let openSettings = document.getElementById("openSettingsMenuItem");
+ openSettings.setAttribute("label",
+ ALERT_BUNDLE.GetStringFromName("webActions.settings.label"));
+ }
+ }
+ case 11:
+ gAlertListener = window.arguments[10];
+ case 10:
+ gReplacedWindow = window.arguments[9];
+ case 9:
+ gRequireInteraction = window.arguments[8];
+ case 8:
+ if (window.arguments[7]) {
+ document.getElementById("alertTitleLabel").setAttribute("lang", window.arguments[7]);
+ document.getElementById("alertTextLabel").setAttribute("lang", window.arguments[7]);
+ }
+ case 7:
+ if (window.arguments[6]) {
+ document.getElementById("alertNotification").style.direction = window.arguments[6];
+ }
+ case 6:
+ gOrigin = window.arguments[5];
+ case 5:
+ gAlertCookie = window.arguments[4];
+ case 4:
+ gAlertTextClickable = window.arguments[3];
+ if (gAlertTextClickable) {
+ document.getElementById("alertNotification").setAttribute("clickable", true);
+ document.getElementById("alertTextLabel").setAttribute("clickable", true);
+ }
+ case 3:
+ if (window.arguments[2]) {
+ document.getElementById("alertBox").setAttribute("hasBodyText", true);
+ let bodyText = window.arguments[2];
+ let bodyTextLabel = document.getElementById("alertTextLabel");
+
+ if (bodyText.length > BODY_TEXT_LIMIT) {
+ bodyTextLabel.setAttribute("tooltiptext", bodyText);
+
+ let ellipsis = "\u2026";
+ try {
+ ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
+ Ci.nsIPrefLocalizedString).data;
+ } catch (e) { }
+
+ // Copied from nsContextMenu.js' formatSearchContextItem().
+ // If the JS character after our truncation point is a trail surrogate,
+ // include it in the truncated string to avoid splitting a surrogate pair.
+ let truncLength = BODY_TEXT_LIMIT;
+ let truncChar = bodyText[BODY_TEXT_LIMIT].charCodeAt(0);
+ if (truncChar >= 0xDC00 && truncChar <= 0xDFFF) {
+ truncLength++;
+ }
+
+ bodyText = bodyText.substring(0, truncLength) +
+ ellipsis;
+ }
+ bodyTextLabel.textContent = bodyText;
+ }
+ case 2:
+ document.getElementById("alertTitleLabel").setAttribute("value", window.arguments[1]);
+ case 1:
+ if (window.arguments[0]) {
+ document.getElementById("alertBox").setAttribute("hasImage", true);
+ document.getElementById("alertImage").setAttribute("src", window.arguments[0]);
+ }
+ case 0:
+ break;
+ }
+}
+
+function onAlertLoad() {
+ const ALERT_DURATION_IMMEDIATE_MIN = 4000;
+ const ALERT_DURATION_IMMEDIATE_MAX = 60000;
+ let alertDurationImmediate = Services.prefs.getIntPref("alerts.durationImmediate", ALERT_DURATION_IMMEDIATE_MIN);
+ alertDurationImmediate = alertDurationImmediate >= ALERT_DURATION_IMMEDIATE_MIN
+ && alertDurationImmediate <= ALERT_DURATION_IMMEDIATE_MAX
+ ? alertDurationImmediate : ALERT_DURATION_IMMEDIATE_MIN;
+ let alertTextBox = document.getElementById("alertTextBox");
+ let alertImageBox = document.getElementById("alertImageBox");
+ alertImageBox.style.minHeight = alertTextBox.scrollHeight + "px";
+
+ sizeToContent();
+
+ if (gReplacedWindow && !gReplacedWindow.closed) {
+ moveWindowToReplace(gReplacedWindow);
+ gReplacedWindow.gIsReplaced = true;
+ gReplacedWindow.close();
+ } else {
+ moveWindowToEnd();
+ }
+
+ window.addEventListener("XULAlertClose", function() { window.close(); });
+
+ // If the require interaction flag is set, prevent auto-closing the notification.
+ if (!gRequireInteraction) {
+ if (Services.prefs.getBoolPref("alerts.disableSlidingEffect")) {
+ setTimeout(function() { window.close(); }, alertDurationImmediate);
+ } else {
+ let alertBox = document.getElementById("alertBox");
+ alertBox.addEventListener("animationend", function hideAlert(event) {
+ if (event.animationName == "alert-animation" ||
+ event.animationName == "alert-clicked-animation" ||
+ event.animationName == "alert-closing-animation") {
+ alertBox.removeEventListener("animationend", hideAlert, false);
+ window.close();
+ }
+ }, false);
+ alertBox.style.animationDuration = Math.round(alertDurationImmediate / 1000).toString() + "s";
+ alertBox.setAttribute("animate", true);
+ }
+ }
+
+ let alertSettings = document.getElementById("alertSettings");
+ alertSettings.addEventListener("focus", onAlertSettingsFocus);
+ alertSettings.addEventListener("click", onAlertSettingsClick);
+
+ let ev = new CustomEvent("AlertActive", {bubbles: true, cancelable: true});
+ document.documentElement.dispatchEvent(ev);
+
+ if (gAlertListener) {
+ gAlertListener.observe(null, "alertshow", gAlertCookie);
+ }
+}
+
+function moveWindowToReplace(aReplacedAlert) {
+ let heightDelta = window.outerHeight - aReplacedAlert.outerHeight;
+
+ // Move windows that come after the replaced alert if the height is different.
+ if (heightDelta != 0) {
+ let windows = Services.wm.getEnumerator("alert:alert");
+ while (windows.hasMoreElements()) {
+ let alertWindow = windows.getNext();
+ // boolean to determine if the alert window is after the replaced alert.
+ let alertIsAfter = gOrigin & NS_ALERT_TOP ?
+ alertWindow.screenY > aReplacedAlert.screenY :
+ aReplacedAlert.screenY > alertWindow.screenY;
+ if (alertIsAfter) {
+ // The new Y position of the window.
+ let adjustedY = gOrigin & NS_ALERT_TOP ?
+ alertWindow.screenY + heightDelta :
+ alertWindow.screenY - heightDelta;
+ alertWindow.moveTo(alertWindow.screenX, adjustedY);
+ }
+ }
+ }
+
+ let adjustedY = gOrigin & NS_ALERT_TOP ? aReplacedAlert.screenY :
+ aReplacedAlert.screenY - heightDelta;
+ window.moveTo(aReplacedAlert.screenX, adjustedY);
+}
+
+function moveWindowToEnd() {
+ // Determine position
+ let x = gOrigin & NS_ALERT_LEFT ? screen.availLeft :
+ screen.availLeft + screen.availWidth - window.outerWidth;
+ let y = gOrigin & NS_ALERT_TOP ? screen.availTop :
+ screen.availTop + screen.availHeight - window.outerHeight;
+
+ // Position the window at the end of all alerts.
+ let windows = Services.wm.getEnumerator("alert:alert");
+ while (windows.hasMoreElements()) {
+ let alertWindow = windows.getNext();
+ let alertWindowTime = Number(
+ alertWindow.document.getElementById('alertTime').getAttribute('value'));
+ let windowTime = Number(
+ window.document.getElementById('alertTime').getAttribute('value'));
+ // The time of window creation.
+ // Otherwise calling the notification twice (and more) in a row
+ // does not work.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1263155
+ if ((alertWindow != window) && (alertWindowTime <= windowTime)) {
+ if (gOrigin & NS_ALERT_TOP) {
+ y = Math.max(y, alertWindow.screenY + alertWindow.outerHeight - WINDOW_SHADOW_SPREAD);
+ } else {
+ y = Math.min(y, alertWindow.screenY - window.outerHeight + WINDOW_SHADOW_SPREAD);
+ }
+ }
+ }
+
+ // Offset the alert by WINDOW_MARGIN pixels from the edge of the screen
+ y += gOrigin & NS_ALERT_TOP ? WINDOW_MARGIN : -WINDOW_MARGIN;
+ x += gOrigin & NS_ALERT_LEFT ? WINDOW_MARGIN : -WINDOW_MARGIN;
+
+ window.moveTo(x, y);
+}
+
+function onAlertBeforeUnload() {
+ if (!gIsReplaced) {
+ // Move other alert windows to fill the gap left by closing alert.
+ let heightDelta = window.outerHeight + WINDOW_MARGIN - WINDOW_SHADOW_SPREAD;
+ let windows = Services.wm.getEnumerator("alert:alert");
+ while (windows.hasMoreElements()) {
+ let alertWindow = windows.getNext();
+ if (alertWindow != window) {
+ if (gOrigin & NS_ALERT_TOP) {
+ if (alertWindow.screenY > window.screenY) {
+ alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY - heightDelta);
+ }
+ } else if (window.screenY > alertWindow.screenY) {
+ alertWindow.moveTo(alertWindow.screenX, alertWindow.screenY + heightDelta);
+ }
+ }
+ }
+ }
+
+ if (gAlertListener) {
+ gAlertListener.observe(null, "alertfinished", gAlertCookie);
+ }
+}
+
+function onAlertClick() {
+ if (gAlertListener && gAlertTextClickable) {
+ gAlertListener.observe(null, "alertclickcallback", gAlertCookie);
+ }
+
+ let alertBox = document.getElementById("alertBox");
+ if (alertBox.getAttribute("animate") == "true") {
+ // Closed when the animation ends.
+ alertBox.style.animationDuration = ".6s";
+ alertBox.setAttribute("clicked", "true");
+ } else {
+ window.close();
+ }
+}
+
+function doNotDisturb() {
+ const alertService = Cc["@mozilla.org/alerts-service;1"]
+ .getService(Ci.nsIAlertsService)
+ .QueryInterface(Ci.nsIAlertsDoNotDisturb);
+ alertService.manualDoNotDisturb = true;
+ onAlertClose();
+}
+
+function disableForOrigin() {
+ gAlertListener.observe(null, "alertdisablecallback", gAlertCookie);
+ onAlertClose();
+}
+
+function onAlertSettingsFocus(event) {
+ event.target.removeAttribute("focusedViaMouse");
+}
+
+function onAlertSettingsClick(event) {
+ // XXXjaws Hack used to remove the focus-ring only
+ // from mouse interaction, but focus-ring drawing
+ // should only be enabled when interacting via keyboard.
+ event.target.setAttribute("focusedViaMouse", true);
+ event.stopPropagation();
+}
+
+function openSettings() {
+ gAlertListener.observe(null, "alertsettingscallback", gAlertCookie);
+ onAlertClose();
+}
+
+function onAlertClose() {
+ let alertBox = document.getElementById("alertBox");
+ if (alertBox.getAttribute("animate") == "true") {
+ // Closed when the animation ends.
+ alertBox.style.animationDuration = ".6s";
+ alertBox.setAttribute("closing", "true");
+ } else {
+ window.close();
+ }
+}
diff --git a/components/alerts/content/alert.xul b/components/alerts/content/alert.xul
new file mode 100644
index 000000000..1549f4530
--- /dev/null
+++ b/components/alerts/content/alert.xul
@@ -0,0 +1,68 @@
+<?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 window [
+<!ENTITY % alertDTD SYSTEM "chrome://alerts/locale/alert.dtd">
+%alertDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/content/alerts/alert.css" type="text/css"?>
+<?xml-stylesheet href="chrome://global/skin/alerts/alert.css" type="text/css"?>
+
+<window id="alertNotification"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="alert:alert"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ role="alert"
+ pack="start"
+ onload="onAlertLoad();"
+ onclick="onAlertClick();"
+ onbeforeunload="onAlertBeforeUnload();">
+
+ <script type="application/javascript" src="chrome://global/content/alerts/alert.js"/>
+
+ <vbox id="alertBox" class="alertBox">
+ <label id="alertTime" value="" hidden="true"/>
+ <box id="alertTitleBox">
+ <image id="alertIcon"/>
+ <label id="alertTitleLabel" class="alertTitle plain" crop="end"/>
+ <vbox class="alertCloseBox">
+ <toolbarbutton class="alertCloseButton close-icon"
+ tooltiptext="&closeAlert.tooltip;"
+ onclick="event.stopPropagation();"
+ oncommand="onAlertClose();"/>
+ </vbox>
+ </box>
+ <box>
+ <hbox id="alertImageBox" class="alertImageBox" align="center" pack="center">
+ <image id="alertImage"/>
+ </hbox>
+
+ <vbox id="alertTextBox" class="alertTextBox">
+ <label id="alertTextLabel" class="alertText plain"/>
+ <spacer flex="1"/>
+ <box id="alertFooter">
+ <label id="alertSourceLabel" class="alertSource plain"/>
+ <button type="menu" id="alertSettings" tooltiptext="&settings.label;">
+ <menupopup position="after_end">
+ <menuitem id="doNotDisturbMenuItem"
+ oncommand="doNotDisturb();"/>
+ <menuseparator/>
+ <menuitem id="disableForOriginMenuItem"
+ oncommand="disableForOrigin();"/>
+ <menuitem id="openSettingsMenuItem"
+ oncommand="openSettings();"/>
+ </menupopup>
+ </button>
+ </box>
+ </vbox>
+ </box>
+ </vbox>
+
+ <!-- This method is called inline because we want to make sure we establish the width
+ and height of the alert before we fire the onload handler. -->
+ <script type="application/javascript">prefillAlertInfo();</script>
+</window>
+
diff --git a/components/alerts/jar.mn b/components/alerts/jar.mn
new file mode 100644
index 000000000..55a90ce9a
--- /dev/null
+++ b/components/alerts/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/.
+
+toolkit.jar:
+ content/global/alerts/alert.css (content/alert.css)
+ content/global/alerts/alert.xul (content/alert.xul)
+* content/global/alerts/alert.js (content/alert.js)
diff --git a/components/alerts/locale/alert.dtd b/components/alerts/locale/alert.dtd
new file mode 100644
index 000000000..5f0f49d22
--- /dev/null
+++ b/components/alerts/locale/alert.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 closeAlert.tooltip "Close this notification">
+<!ENTITY settings.label "Settings">
diff --git a/components/alerts/locale/alert.properties b/components/alerts/locale/alert.properties
new file mode 100644
index 000000000..af482adb1
--- /dev/null
+++ b/components/alerts/locale/alert.properties
@@ -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/.
+
+# LOCALIZATION NOTE(closeButton.title): Used as the close button text for web notifications on OS X.
+# This should ideally match the string that OS X uses for the close button on alert-type
+# notifications. OS X will truncate the value if it's too long.
+closeButton.title = Close
+# LOCALIZATION NOTE(actionButton.label): Used as the button label to provide more actions on OS X notifications. OS X will truncate this if it's too long.
+actionButton.label = …
+# LOCALIZATION NOTE(webActions.disableForOrigin.label): %S is replaced
+# with the hostname origin of the notification.
+webActions.disableForOrigin.label = Disable notifications from %S
+
+# LOCALIZATION NOTE(source.label): Used to show the URL of the site that
+# sent the notification (e.g., "via mozilla.org"). "%1$S" is the source host
+# and port.
+source.label=via %1$S
+webActions.settings.label = Notification settings
+
+# LOCALIZATION NOTE(doNotDisturb.label): %S is replaced with the
+# brandShortName of the application.
+doNotDisturb.label = Do not disturb me until I restart %S
diff --git a/components/alerts/moz.build b/components/alerts/moz.build
new file mode 100644
index 000000000..286391d99
--- /dev/null
+++ b/components/alerts/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['public/nsIAlertsService.idl']
+
+EXPORTS += ['src/nsAlertsUtils.h']
+
+EXPORTS.mozilla += [
+ 'src/AlertNotification.h',
+ 'src/AlertNotificationIPCSerializer.h',
+]
+
+UNIFIED_SOURCES += [
+ 'src/AlertNotification.cpp',
+ 'src/nsAlertsService.cpp',
+ 'src/nsAlertsUtils.cpp',
+ 'src/nsXULAlerts.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+XPIDL_MODULE = 'alerts'
+FINAL_LIBRARY = 'xul'
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/alerts/public/nsIAlertsService.idl b/components/alerts/public/nsIAlertsService.idl
new file mode 100644
index 000000000..d22d85a2e
--- /dev/null
+++ b/components/alerts/public/nsIAlertsService.idl
@@ -0,0 +1,258 @@
+/* -*- Mode: IDL; 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"
+#include "nsIObserver.idl"
+
+interface imgIRequest;
+interface nsICancelable;
+interface nsIPrincipal;
+interface nsIURI;
+
+%{C++
+#define ALERT_NOTIFICATION_CONTRACTID "@mozilla.org/alert-notification;1"
+%}
+
+[scriptable, uuid(a71a637d-de1d-47c6-a8d2-c60b2596f471)]
+interface nsIAlertNotificationImageListener : nsISupports
+{
+ /**
+ * Called when the image finishes loading.
+ *
+ * @param aUserData An opaque parameter passed to |loadImage|.
+ * @param aRequest The image request.
+ */
+ void onImageReady(in nsISupports aUserData, in imgIRequest aRequest);
+
+ /**
+ * Called if the alert doesn't have an image, or if the image request times
+ * out or fails.
+ *
+ * @param aUserData An opaque parameter passed to |loadImage|.
+ */
+ void onImageMissing(in nsISupports aUserData);
+};
+
+[scriptable, uuid(cf2e4cb6-4b8f-4eca-aea9-d51a8f9f7a50)]
+interface nsIAlertNotification : nsISupports
+{
+ /** Initializes an alert notification. */
+ void init([optional] in AString aName,
+ [optional] in AString aImageURL,
+ [optional] in AString aTitle,
+ [optional] in AString aText,
+ [optional] in boolean aTextClickable,
+ [optional] in AString aCookie,
+ [optional] in AString aDir,
+ [optional] in AString aLang,
+ [optional] in AString aData,
+ [optional] in nsIPrincipal aPrincipal,
+ [optional] in boolean aInPrivateBrowsing,
+ [optional] in boolean aRequireInteraction);
+
+ /**
+ * The name of the notification. Notifications will replace previous
+ * notifications with the same name.
+ */
+ readonly attribute AString name;
+
+ /**
+ * A URL identifying the image to put in the alert. The OS X backend limits
+ * the amount of time it will wait for the image to load to six seconds. After
+ * that time, the alert will show without an image.
+ */
+ readonly attribute AString imageURL;
+
+ /** The title for the alert. */
+ readonly attribute AString title;
+
+ /** The contents of the alert. */
+ readonly attribute AString text;
+
+ /**
+ * Controls the click behavior. If true, the alert listener will be notified
+ * when the user clicks on the alert.
+ */
+ readonly attribute boolean textClickable;
+
+ /**
+ * An opaque cookie that will be passed to the alert listener for each
+ * callback.
+ */
+ readonly attribute AString cookie;
+
+ /**
+ * Bidi override for the title and contents. Valid values are "auto", "ltr",
+ * or "rtl". Ignored if the backend doesn't support localization.
+ */
+ readonly attribute AString dir;
+
+ /**
+ * Language of the title and text. Ignored if the backend doesn't support
+ * localization.
+ */
+ readonly attribute AString lang;
+
+ /**
+ * A Base64-encoded structured clone buffer containing data associated with
+ * this alert. Only used for web notifications. Chrome callers should use a
+ * cookie instead.
+ */
+ readonly attribute AString data;
+
+ /**
+ * The principal of the page that created the alert. Used for IPC security
+ * checks, and to determine whether the alert is actionable.
+ */
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * The URI of the page that created the alert. |null| if the alert is not
+ * actionable.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * Controls the image loading behavior. If true, the image request will be
+ * loaded anonymously (without cookies or authorization tokens).
+ */
+ readonly attribute boolean inPrivateBrowsing;
+
+ /**
+ * Indicates that the notification should remain readily available until
+ * the user activates or dismisses the notification.
+ */
+ readonly attribute boolean requireInteraction;
+
+ /**
+ * Indicates whether this alert should show the source string and action
+ * buttons. False for system alerts (which can omit the principal), or
+ * expanded, system, and null principals.
+ */
+ readonly attribute boolean actionable;
+
+ /**
+ * The host and port of the originating page, or an empty string if the alert
+ * is not actionable.
+ */
+ readonly attribute AString source;
+
+ /**
+ * Loads the image associated with this alert.
+ *
+ * @param aTimeout The number of milliseconds to wait before cancelling the
+ * image request. If zero, there is no timeout.
+ * @param aListener An |nsIAlertNotificationImageListener| implementation,
+ * notified when the image loads. The listener is kept alive
+ * until the request completes.
+ * @param aUserData An opaque parameter passed to the listener's methods.
+ * Not used by the libnotify backend, but the OS X backend
+ * passes the pending notification.
+ */
+ nsICancelable loadImage(in unsigned long aTimeout,
+ in nsIAlertNotificationImageListener aListener,
+ [optional] in nsISupports aUserData);
+};
+
+[scriptable, uuid(f7a36392-d98b-4141-a7d7-4e46642684e3)]
+interface nsIAlertsService : nsISupports
+{
+ void showPersistentNotification(in AString aPersistentData,
+ in nsIAlertNotification aAlert,
+ [optional] in nsIObserver aAlertListener);
+
+ void showAlert(in nsIAlertNotification aAlert,
+ [optional] in nsIObserver aAlertListener);
+ /**
+ * Initializes and shows an |nsIAlertNotification| with the given parameters.
+ *
+ * @param aAlertListener Used for callbacks. May be null if the caller
+ * doesn't care about callbacks.
+ * @see nsIAlertNotification for descriptions of all other parameters.
+ * @throws NS_ERROR_NOT_AVAILABLE If the notification cannot be displayed.
+ *
+ * The following arguments will be passed to the alertListener's observe()
+ * method:
+ * subject - null
+ * topic - "alertfinished" when the alert goes away
+ * "alertdisablecallback" when alerts should be disabled for the principal
+ * "alertsettingscallback" when alert settings should be opened
+ * "alertclickcallback" when the text is clicked
+ * "alertshow" when the alert is shown
+ * data - the value of the cookie parameter passed to showAlertNotification.
+ *
+ * @note Depending on current circumstances (if the user's in a fullscreen
+ * application, for instance), the alert might not be displayed at all.
+ * In that case, if an alert listener is passed in it will receive the
+ * "alertfinished" notification immediately.
+ */
+ void showAlertNotification(in AString aImageURL,
+ in AString aTitle,
+ in AString aText,
+ [optional] in boolean aTextClickable,
+ [optional] in AString aCookie,
+ [optional] in nsIObserver aAlertListener,
+ [optional] in AString aName,
+ [optional] in AString aDir,
+ [optional] in AString aLang,
+ [optional] in AString aData,
+ [optional] in nsIPrincipal aPrincipal,
+ [optional] in boolean aInPrivateBrowsing,
+ [optional] in boolean aRequireInteraction);
+
+ /**
+ * Close alerts created by the service.
+ *
+ * @param aName The name of the notification to close. If no name
+ * is provided then only a notification created with
+ * no name (if any) will be closed.
+ */
+ void closeAlert([optional] in AString aName,
+ [optional] in nsIPrincipal aPrincipal);
+
+};
+
+[scriptable, uuid(c5d63e3a-259d-45a8-b964-8377967cb4d2)]
+interface nsIAlertsDoNotDisturb : nsISupports
+{
+ /**
+ * Toggles a manual Do Not Disturb mode for the service to reduce the amount
+ * of disruption that alerts cause the user.
+ * This may mean only displaying them in a notification tray/center or not
+ * displaying them at all. If a system backend already supports a similar
+ * feature controlled by the user, enabling this may not have any impact on
+ * code to show an alert. e.g. on OS X, the system will take care not
+ * disrupting a user if we simply create a notification like usual.
+ */
+ attribute bool manualDoNotDisturb;
+};
+
+[scriptable, uuid(fc6d7f0a-0cf6-4268-8c71-ab640842b9b1)]
+interface nsIAlertsIconData : nsISupports
+{
+ /**
+ * Shows an alert with an icon. Web notifications use the favicon of the
+ * page that created the alert. If the favicon is not in the Places database,
+ * |aIconSize| will be zero.
+ */
+ void showAlertWithIconData(in nsIAlertNotification aAlert,
+ [optional] in nsIObserver aAlertListener,
+ [optional] in uint32_t aIconSize,
+ [const, array, size_is(aIconSize)] in uint8_t
+ aIconData);
+};
+
+[scriptable, uuid(f3c82915-bf60-41ea-91ce-6c46b22e381a)]
+interface nsIAlertsIconURI : nsISupports
+{
+ /**
+ * Shows an alert with an icon URI. Web notifications use |moz-anno:|
+ * URIs to reference favicons from Places. If the page doesn't have a
+ * favicon, |aIconURI| will be |null|.
+ */
+ void showAlertWithIconURI(in nsIAlertNotification aAlert,
+ [optional] in nsIObserver aAlertListener,
+ [optional] in nsIURI aIconURI);
+};
diff --git a/components/alerts/src/AlertNotification.cpp b/components/alerts/src/AlertNotification.cpp
new file mode 100644
index 000000000..b828f1100
--- /dev/null
+++ b/components/alerts/src/AlertNotification.cpp
@@ -0,0 +1,361 @@
+/* This Source Code Form is subject to the terms of the Mozilla Pub
+ * License, v. 2.0. If a copy of the MPL was not distributed with t
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/AlertNotification.h"
+
+#include "imgIContainer.h"
+#include "imgINotificationObserver.h"
+#include "imgIRequest.h"
+#include "imgLoader.h"
+#include "nsAlertsUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(AlertNotification, nsIAlertNotification)
+
+AlertNotification::AlertNotification()
+ : mTextClickable(false)
+ , mInPrivateBrowsing(false)
+{}
+
+AlertNotification::~AlertNotification()
+{}
+
+NS_IMETHODIMP
+AlertNotification::Init(const nsAString& aName, const nsAString& aImageURL,
+ const nsAString& aTitle, const nsAString& aText,
+ bool aTextClickable, const nsAString& aCookie,
+ const nsAString& aDir, const nsAString& aLang,
+ const nsAString& aData, nsIPrincipal* aPrincipal,
+ bool aInPrivateBrowsing, bool aRequireInteraction)
+{
+ mName = aName;
+ mImageURL = aImageURL;
+ mTitle = aTitle;
+ mText = aText;
+ mTextClickable = aTextClickable;
+ mCookie = aCookie;
+ mDir = aDir;
+ mLang = aLang;
+ mData = aData;
+ mPrincipal = aPrincipal;
+ mInPrivateBrowsing = aInPrivateBrowsing;
+ mRequireInteraction = aRequireInteraction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetName(nsAString& aName)
+{
+ aName = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetImageURL(nsAString& aImageURL)
+{
+ aImageURL = mImageURL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetTitle(nsAString& aTitle)
+{
+ aTitle = mTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetText(nsAString& aText)
+{
+ aText = mText;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetTextClickable(bool* aTextClickable)
+{
+ *aTextClickable = mTextClickable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetCookie(nsAString& aCookie)
+{
+ aCookie = mCookie;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetDir(nsAString& aDir)
+{
+ aDir = mDir;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetLang(nsAString& aLang)
+{
+ aLang = mLang;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetRequireInteraction(bool* aRequireInteraction)
+{
+ *aRequireInteraction = mRequireInteraction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetData(nsAString& aData)
+{
+ aData = mData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetPrincipal(nsIPrincipal** aPrincipal)
+{
+ NS_IF_ADDREF(*aPrincipal = mPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetURI(nsIURI** aURI)
+{
+ if (!nsAlertsUtils::IsActionablePrincipal(mPrincipal)) {
+ *aURI = nullptr;
+ return NS_OK;
+ }
+ return mPrincipal->GetURI(aURI);
+}
+
+NS_IMETHODIMP
+AlertNotification::GetInPrivateBrowsing(bool* aInPrivateBrowsing)
+{
+ *aInPrivateBrowsing = mInPrivateBrowsing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetActionable(bool* aActionable)
+{
+ *aActionable = nsAlertsUtils::IsActionablePrincipal(mPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::GetSource(nsAString& aSource)
+{
+ nsAlertsUtils::GetSourceHostPort(mPrincipal, aSource);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertNotification::LoadImage(uint32_t aTimeout,
+ nsIAlertNotificationImageListener* aListener,
+ nsISupports* aUserData,
+ nsICancelable** aRequest)
+{
+ NS_ENSURE_ARG(aListener);
+ NS_ENSURE_ARG_POINTER(aRequest);
+ *aRequest = nullptr;
+
+ // Exit early if this alert doesn't have an image.
+ if (mImageURL.IsEmpty()) {
+ return aListener->OnImageMissing(aUserData);
+ }
+ nsCOMPtr<nsIURI> imageURI;
+ NS_NewURI(getter_AddRefs(imageURI), mImageURL);
+ if (!imageURI) {
+ return aListener->OnImageMissing(aUserData);
+ }
+
+ RefPtr<AlertImageRequest> request = new AlertImageRequest(imageURI, mPrincipal,
+ mInPrivateBrowsing,
+ aTimeout, aListener,
+ aUserData);
+ nsresult rv = request->Start();
+ request.forget(aRequest);
+ return rv;
+}
+
+NS_IMPL_CYCLE_COLLECTION(AlertImageRequest, mURI, mPrincipal, mListener,
+ mUserData)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AlertImageRequest)
+ NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsICancelable)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, imgINotificationObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AlertImageRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AlertImageRequest)
+
+AlertImageRequest::AlertImageRequest(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ bool aInPrivateBrowsing, uint32_t aTimeout,
+ nsIAlertNotificationImageListener* aListener,
+ nsISupports* aUserData)
+ : mURI(aURI)
+ , mPrincipal(aPrincipal)
+ , mInPrivateBrowsing(aInPrivateBrowsing)
+ , mTimeout(aTimeout)
+ , mListener(aListener)
+ , mUserData(aUserData)
+{}
+
+AlertImageRequest::~AlertImageRequest()
+{
+ if (mRequest) {
+ mRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ }
+}
+
+NS_IMETHODIMP
+AlertImageRequest::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData)
+{
+ MOZ_ASSERT(aRequest == mRequest);
+
+ uint32_t imgStatus = imgIRequest::STATUS_ERROR;
+ nsresult rv = aRequest->GetImageStatus(&imgStatus);
+ if (NS_WARN_IF(NS_FAILED(rv)) ||
+ (imgStatus & imgIRequest::STATUS_ERROR)) {
+ return NotifyMissing();
+ }
+
+ // If the image is already decoded, `FRAME_COMPLETE` will fire before
+ // `LOAD_COMPLETE`, so we can notify the listener immediately. Otherwise,
+ // we'll need to request a decode when `LOAD_COMPLETE` fires, and wait
+ // for the first frame.
+
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ if (!(imgStatus & imgIRequest::STATUS_FRAME_COMPLETE)) {
+ nsCOMPtr<imgIContainer> image;
+ rv = aRequest->GetImage(getter_AddRefs(image));
+ if (NS_WARN_IF(NS_FAILED(rv) || !image)) {
+ return NotifyMissing();
+ }
+
+ // Ask the image to decode at its intrinsic size.
+ int32_t width = 0, height = 0;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ image->RequestDecodeForSize(gfx::IntSize(width, height), imgIContainer::FLAG_NONE);
+ }
+ return NS_OK;
+ }
+
+ if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+ return NotifyComplete();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AlertImageRequest::Notify(nsITimer* aTimer)
+{
+ MOZ_ASSERT(aTimer == mTimer);
+ return NotifyMissing();
+}
+
+NS_IMETHODIMP
+AlertImageRequest::Cancel(nsresult aReason)
+{
+ if (mRequest) {
+ mRequest->Cancel(aReason);
+ }
+ // We call `NotifyMissing` here because we won't receive a `LOAD_COMPLETE`
+ // notification if we cancel the request before it loads (bug 1233086,
+ // comment 33). Once that's fixed, `nsIAlertNotification::loadImage` could
+ // return the underlying `imgIRequest` instead of the wrapper.
+ return NotifyMissing();
+}
+
+nsresult
+AlertImageRequest::Start()
+{
+ // Keep the request alive until we notify the image listener.
+ NS_ADDREF_THIS();
+
+ nsresult rv;
+ if (mTimeout > 0) {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (NS_WARN_IF(!mTimer)) {
+ return NotifyMissing();
+ }
+ rv = mTimer->InitWithCallback(this, mTimeout,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NotifyMissing();
+ }
+ }
+
+ // Begin loading the image.
+ imgLoader* il = imgLoader::NormalLoader();
+ if (!il) {
+ return NotifyMissing();
+ }
+
+ // Bug 1237405: `LOAD_ANONYMOUS` disables cookies, but we want to use a
+ // temporary cookie jar instead. We should also use
+ // `imgLoader::PrivateBrowsingLoader()` instead of the normal loader.
+ // Unfortunately, the PB loader checks the load group, and asserts if its
+ // load context's PB flag isn't set. The fix is to pass the load group to
+ // `nsIAlertNotification::loadImage`.
+ int32_t loadFlags = mInPrivateBrowsing ? nsIRequest::LOAD_ANONYMOUS :
+ nsIRequest::LOAD_NORMAL;
+
+ rv = il->LoadImageXPCOM(mURI, nullptr, nullptr,
+ NS_LITERAL_STRING("default"), mPrincipal, nullptr,
+ this, nullptr, loadFlags, nullptr,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE,
+ getter_AddRefs(mRequest));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NotifyMissing();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+AlertImageRequest::NotifyMissing()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (nsCOMPtr<nsIAlertNotificationImageListener> listener = mListener.forget()) {
+ nsresult rv = listener->OnImageMissing(mUserData);
+ NS_RELEASE_THIS();
+ return rv;
+ }
+ return NS_OK;
+}
+
+nsresult
+AlertImageRequest::NotifyComplete()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (nsCOMPtr<nsIAlertNotificationImageListener> listener = mListener.forget()) {
+ nsresult rv = listener->OnImageReady(mUserData, mRequest);
+ NS_RELEASE_THIS();
+ return rv;
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/components/alerts/src/AlertNotification.h b/components/alerts/src/AlertNotification.h
new file mode 100644
index 000000000..c0bcc0ba9
--- /dev/null
+++ b/components/alerts/src/AlertNotification.h
@@ -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/. */
+
+#ifndef mozilla_AlertNotification_h__
+#define mozilla_AlertNotification_h__
+
+#include "imgINotificationObserver.h"
+#include "nsIAlertsService.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsICancelable.h"
+#include "nsIPrincipal.h"
+#include "nsString.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+
+class AlertImageRequest final : public imgINotificationObserver,
+ public nsICancelable,
+ public nsITimerCallback
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(AlertImageRequest,
+ imgINotificationObserver)
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+ NS_DECL_NSICANCELABLE
+ NS_DECL_NSITIMERCALLBACK
+
+ AlertImageRequest(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ bool aInPrivateBrowsing, uint32_t aTimeout,
+ nsIAlertNotificationImageListener* aListener,
+ nsISupports* aUserData);
+
+ nsresult Start();
+
+private:
+ virtual ~AlertImageRequest();
+
+ nsresult NotifyMissing();
+ nsresult NotifyComplete();
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ bool mInPrivateBrowsing;
+ uint32_t mTimeout;
+ nsCOMPtr<nsIAlertNotificationImageListener> mListener;
+ nsCOMPtr<nsISupports> mUserData;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<imgIRequest> mRequest;
+};
+
+class AlertNotification final : public nsIAlertNotification
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTNOTIFICATION
+ AlertNotification();
+
+protected:
+ virtual ~AlertNotification();
+
+private:
+ nsString mName;
+ nsString mImageURL;
+ nsString mTitle;
+ nsString mText;
+ bool mTextClickable;
+ nsString mCookie;
+ nsString mDir;
+ nsString mLang;
+ bool mRequireInteraction;
+ nsString mData;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ bool mInPrivateBrowsing;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_AlertNotification_h__ */
diff --git a/components/alerts/src/AlertNotificationIPCSerializer.h b/components/alerts/src/AlertNotificationIPCSerializer.h
new file mode 100644
index 000000000..9544f9633
--- /dev/null
+++ b/components/alerts/src/AlertNotificationIPCSerializer.h
@@ -0,0 +1,122 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AlertNotificationIPCSerializer_h__
+#define mozilla_AlertNotificationIPCSerializer_h__
+
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIAlertsService.h"
+#include "nsIPrincipal.h"
+#include "nsString.h"
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "mozilla/dom/PermissionMessageUtils.h"
+
+typedef nsIAlertNotification* AlertNotificationType;
+
+namespace IPC {
+
+template <>
+struct ParamTraits<AlertNotificationType>
+{
+ typedef AlertNotificationType paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ bool isNull = !aParam;
+ if (isNull) {
+ WriteParam(aMsg, isNull);
+ return;
+ }
+
+ nsString name, imageURL, title, text, cookie, dir, lang, data;
+ bool textClickable, inPrivateBrowsing, requireInteraction;
+ nsCOMPtr<nsIPrincipal> principal;
+
+ if (NS_WARN_IF(NS_FAILED(aParam->GetName(name))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetImageURL(imageURL))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetTitle(title))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetText(text))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetTextClickable(&textClickable))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetCookie(cookie))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetDir(dir))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetLang(lang))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetData(data))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetPrincipal(getter_AddRefs(principal)))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetInPrivateBrowsing(&inPrivateBrowsing))) ||
+ NS_WARN_IF(NS_FAILED(aParam->GetRequireInteraction(&requireInteraction)))) {
+
+ // Write a `null` object if any getter returns an error. Otherwise, the
+ // receiver will try to deserialize an incomplete object and crash.
+ WriteParam(aMsg, /* isNull */ true);
+ return;
+ }
+
+ WriteParam(aMsg, isNull);
+ WriteParam(aMsg, name);
+ WriteParam(aMsg, imageURL);
+ WriteParam(aMsg, title);
+ WriteParam(aMsg, text);
+ WriteParam(aMsg, textClickable);
+ WriteParam(aMsg, cookie);
+ WriteParam(aMsg, dir);
+ WriteParam(aMsg, lang);
+ WriteParam(aMsg, data);
+ WriteParam(aMsg, IPC::Principal(principal));
+ WriteParam(aMsg, inPrivateBrowsing);
+ WriteParam(aMsg, requireInteraction);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ bool isNull;
+ NS_ENSURE_TRUE(ReadParam(aMsg, aIter, &isNull), false);
+ if (isNull) {
+ *aResult = nullptr;
+ return true;
+ }
+
+ nsString name, imageURL, title, text, cookie, dir, lang, data;
+ bool textClickable, inPrivateBrowsing, requireInteraction;
+ IPC::Principal principal;
+
+ if (!ReadParam(aMsg, aIter, &name) ||
+ !ReadParam(aMsg, aIter, &imageURL) ||
+ !ReadParam(aMsg, aIter, &title) ||
+ !ReadParam(aMsg, aIter, &text) ||
+ !ReadParam(aMsg, aIter, &textClickable) ||
+ !ReadParam(aMsg, aIter, &cookie) ||
+ !ReadParam(aMsg, aIter, &dir) ||
+ !ReadParam(aMsg, aIter, &lang) ||
+ !ReadParam(aMsg, aIter, &data) ||
+ !ReadParam(aMsg, aIter, &principal) ||
+ !ReadParam(aMsg, aIter, &inPrivateBrowsing) ||
+ !ReadParam(aMsg, aIter, &requireInteraction)) {
+
+ return false;
+ }
+
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ if (NS_WARN_IF(!alert)) {
+ *aResult = nullptr;
+ return true;
+ }
+ nsresult rv = alert->Init(name, imageURL, title, text, textClickable,
+ cookie, dir, lang, data, principal,
+ inPrivateBrowsing, requireInteraction);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ *aResult = nullptr;
+ return true;
+ }
+ alert.forget(aResult);
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#endif /* mozilla_AlertNotificationIPCSerializer_h__ */
diff --git a/components/alerts/src/nsAlertsService.cpp b/components/alerts/src/nsAlertsService.cpp
new file mode 100644
index 000000000..4f248c861
--- /dev/null
+++ b/components/alerts/src/nsAlertsService.cpp
@@ -0,0 +1,301 @@
+/* -*- 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/dom/ContentChild.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsXULAppAPI.h"
+
+#include "nsAlertsService.h"
+
+#include "nsXPCOM.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMWindow.h"
+#include "nsPromiseFlatString.h"
+#include "nsToolkitCompsCID.h"
+
+#ifdef MOZ_PLACES
+#include "mozIAsyncFavicons.h"
+#include "nsIFaviconService.h"
+#endif // MOZ_PLACES
+
+#ifdef XP_WIN
+#include <shellapi.h>
+#endif
+
+using namespace mozilla;
+
+using mozilla::dom::ContentChild;
+
+namespace {
+
+#ifdef MOZ_PLACES
+
+class IconCallback final : public nsIFaviconDataCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ IconCallback(nsIAlertsService* aBackend,
+ nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+ : mBackend(aBackend)
+ , mAlert(aAlert)
+ , mAlertListener(aAlertListener)
+ {}
+
+ NS_IMETHOD
+ OnComplete(nsIURI *aIconURI, uint32_t aIconSize, const uint8_t *aIconData,
+ const nsACString &aMimeType) override
+ {
+ nsresult rv = NS_ERROR_FAILURE;
+ if (aIconSize > 0) {
+ nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(mBackend));
+ if (alertsIconData) {
+ rv = alertsIconData->ShowAlertWithIconData(mAlert, mAlertListener,
+ aIconSize, aIconData);
+ }
+ } else if (aIconURI) {
+ nsCOMPtr<nsIAlertsIconURI> alertsIconURI(do_QueryInterface(mBackend));
+ if (alertsIconURI) {
+ rv = alertsIconURI->ShowAlertWithIconURI(mAlert, mAlertListener,
+ aIconURI);
+ }
+ }
+ if (NS_FAILED(rv)) {
+ rv = mBackend->ShowAlert(mAlert, mAlertListener);
+ }
+ return rv;
+ }
+
+private:
+ virtual ~IconCallback() {}
+
+ nsCOMPtr<nsIAlertsService> mBackend;
+ nsCOMPtr<nsIAlertNotification> mAlert;
+ nsCOMPtr<nsIObserver> mAlertListener;
+};
+
+NS_IMPL_ISUPPORTS(IconCallback, nsIFaviconDataCallback)
+
+#endif // MOZ_PLACES
+
+nsresult
+ShowWithIconBackend(nsIAlertsService* aBackend, nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+#ifdef MOZ_PLACES
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aAlert->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv) || !uri) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Ensure the backend supports favicons.
+ nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(aBackend));
+ nsCOMPtr<nsIAlertsIconURI> alertsIconURI;
+ if (!alertsIconData) {
+ alertsIconURI = do_QueryInterface(aBackend);
+ }
+ if (!alertsIconData && !alertsIconURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsCOMPtr<mozIAsyncFavicons> favicons(do_GetService(
+ "@mozilla.org/browser/favicon-service;1"));
+ NS_ENSURE_TRUE(favicons, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIFaviconDataCallback> callback =
+ new IconCallback(aBackend, aAlert, aAlertListener);
+ if (alertsIconData) {
+ return favicons->GetFaviconDataForPage(uri, callback);
+ }
+ return favicons->GetFaviconURLForPage(uri, callback);
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif // !MOZ_PLACES
+}
+
+nsresult
+ShowWithBackend(nsIAlertsService* aBackend, nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener, const nsAString& aPersistentData)
+{
+ if (!aPersistentData.IsEmpty()) {
+ return aBackend->ShowPersistentNotification(
+ aPersistentData, aAlert, aAlertListener);
+ }
+
+ if (Preferences::GetBool("alerts.showFavicons")) {
+ nsresult rv = ShowWithIconBackend(aBackend, aAlert, aAlertListener);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+ }
+
+ // If favicons are disabled, or the backend doesn't support them, show the
+ // alert without one.
+ return aBackend->ShowAlert(aAlert, aAlertListener);
+}
+
+} // anonymous namespace
+
+NS_IMPL_ISUPPORTS(nsAlertsService, nsIAlertsService, nsIAlertsDoNotDisturb)
+
+nsAlertsService::nsAlertsService() :
+ mBackend(nullptr)
+{
+ mBackend = do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID);
+}
+
+nsAlertsService::~nsAlertsService()
+{}
+
+bool nsAlertsService::ShouldShowAlert()
+{
+ bool result = true;
+
+#ifdef XP_WIN
+ QUERY_USER_NOTIFICATION_STATE qstate;
+ if (SUCCEEDED(SHQueryUserNotificationState(&qstate))) {
+ if (qstate != QUNS_ACCEPTS_NOTIFICATIONS) {
+ result = false;
+ }
+ }
+#endif
+
+ return result;
+}
+
+NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle,
+ const nsAString & aAlertText, bool aAlertTextClickable,
+ const nsAString & aAlertCookie,
+ nsIObserver * aAlertListener,
+ const nsAString & aAlertName,
+ const nsAString & aBidi,
+ const nsAString & aLang,
+ const nsAString & aData,
+ nsIPrincipal * aPrincipal,
+ bool aInPrivateBrowsing,
+ bool aRequireInteraction)
+{
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
+ nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle,
+ aAlertText, aAlertTextClickable,
+ aAlertCookie, aBidi, aLang, aData,
+ aPrincipal, aInPrivateBrowsing,
+ aRequireInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowAlert(alert, aAlertListener);
+}
+
+
+NS_IMETHODIMP nsAlertsService::ShowAlert(nsIAlertNotification * aAlert,
+ nsIObserver * aAlertListener)
+{
+ return ShowPersistentNotification(EmptyString(), aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP nsAlertsService::ShowPersistentNotification(const nsAString & aPersistentData,
+ nsIAlertNotification * aAlert,
+ nsIObserver * aAlertListener)
+{
+ NS_ENSURE_ARG(aAlert);
+
+ nsAutoString cookie;
+ nsresult rv = aAlert->GetCookie(cookie);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (XRE_IsContentProcess()) {
+ ContentChild* cpc = ContentChild::GetSingleton();
+
+ if (aAlertListener)
+ cpc->AddRemoteAlertObserver(cookie, aAlertListener);
+
+ cpc->SendShowAlert(aAlert);
+ return NS_OK;
+ }
+
+ // Check if there is an optional service that handles system-level notifications
+ if (mBackend) {
+ rv = ShowWithBackend(mBackend, aAlert, aAlertListener, aPersistentData);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+ // If the system backend failed to show the alert, clear the backend and
+ // retry with XUL notifications. Future alerts will always use XUL.
+ mBackend = nullptr;
+ }
+
+ if (!ShouldShowAlert()) {
+ // Do not display the alert. Instead call alertfinished and get out.
+ if (aAlertListener)
+ aAlertListener->Observe(nullptr, "alertfinished", cookie.get());
+ return NS_OK;
+ }
+
+ // Use XUL notifications as a fallback if above methods have failed.
+ nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance());
+ NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE);
+ return ShowWithBackend(xulBackend, aAlert, aAlertListener, aPersistentData);
+}
+
+NS_IMETHODIMP nsAlertsService::CloseAlert(const nsAString& aAlertName,
+ nsIPrincipal* aPrincipal)
+{
+ if (XRE_IsContentProcess()) {
+ ContentChild* cpc = ContentChild::GetSingleton();
+ cpc->SendCloseAlert(nsAutoString(aAlertName), IPC::Principal(aPrincipal));
+ return NS_OK;
+ }
+
+ nsresult rv;
+ // Try the system notification service.
+ if (mBackend) {
+ rv = mBackend->CloseAlert(aAlertName, aPrincipal);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If the system backend failed to close the alert, fall back to XUL for
+ // future alerts.
+ mBackend = nullptr;
+ }
+ } else {
+ nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance());
+ NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE);
+ rv = xulBackend->CloseAlert(aAlertName, aPrincipal);
+ }
+ return rv;
+}
+
+
+// nsIAlertsDoNotDisturb
+NS_IMETHODIMP nsAlertsService::GetManualDoNotDisturb(bool* aRetVal)
+{
+ nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend());
+ NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED);
+ return alertsDND->GetManualDoNotDisturb(aRetVal);
+}
+
+NS_IMETHODIMP nsAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb)
+{
+ nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(GetDNDBackend());
+ NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED);
+
+ nsresult rv = alertsDND->SetManualDoNotDisturb(aDoNotDisturb);
+ return rv;
+}
+
+already_AddRefed<nsIAlertsDoNotDisturb>
+nsAlertsService::GetDNDBackend()
+{
+ // Try the system notification service.
+ nsCOMPtr<nsIAlertsService> backend = mBackend;
+ if (!backend) {
+ backend = nsXULAlerts::GetInstance();
+ }
+
+ nsCOMPtr<nsIAlertsDoNotDisturb> alertsDND(do_QueryInterface(backend));
+ return alertsDND.forget();
+}
diff --git a/components/alerts/src/nsAlertsService.h b/components/alerts/src/nsAlertsService.h
new file mode 100644
index 000000000..d2b2e1e6c
--- /dev/null
+++ b/components/alerts/src/nsAlertsService.h
@@ -0,0 +1,31 @@
+// /* -*- 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 nsAlertsService_h__
+#define nsAlertsService_h__
+
+#include "nsIAlertsService.h"
+#include "nsCOMPtr.h"
+#include "nsXULAlerts.h"
+
+class nsAlertsService : public nsIAlertsService,
+ public nsIAlertsDoNotDisturb
+{
+public:
+ NS_DECL_NSIALERTSDONOTDISTURB
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_ISUPPORTS
+
+ nsAlertsService();
+
+protected:
+ virtual ~nsAlertsService();
+
+ bool ShouldShowAlert();
+ already_AddRefed<nsIAlertsDoNotDisturb> GetDNDBackend();
+ nsCOMPtr<nsIAlertsService> mBackend;
+};
+
+#endif /* nsAlertsService_h__ */
diff --git a/components/alerts/src/nsAlertsUtils.cpp b/components/alerts/src/nsAlertsUtils.cpp
new file mode 100644
index 000000000..5f7d92d2a
--- /dev/null
+++ b/components/alerts/src/nsAlertsUtils.cpp
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAlertsUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIURI.h"
+#include "nsXPIDLString.h"
+
+/* static */
+bool
+nsAlertsUtils::IsActionablePrincipal(nsIPrincipal* aPrincipal)
+{
+ return aPrincipal &&
+ !nsContentUtils::IsSystemOrExpandedPrincipal(aPrincipal) &&
+ !aPrincipal->GetIsNullPrincipal();
+}
+
+/* static */
+void
+nsAlertsUtils::GetSourceHostPort(nsIPrincipal* aPrincipal,
+ nsAString& aHostPort)
+{
+ if (!IsActionablePrincipal(aPrincipal)) {
+ return;
+ }
+ nsCOMPtr<nsIURI> principalURI;
+ if (NS_WARN_IF(NS_FAILED(
+ aPrincipal->GetURI(getter_AddRefs(principalURI))))) {
+ return;
+ }
+ if (!principalURI) {
+ return;
+ }
+ nsAutoCString hostPort;
+ if (NS_WARN_IF(NS_FAILED(principalURI->GetHostPort(hostPort)))) {
+ return;
+ }
+ CopyUTF8toUTF16(hostPort, aHostPort);
+}
diff --git a/components/alerts/src/nsAlertsUtils.h b/components/alerts/src/nsAlertsUtils.h
new file mode 100644
index 000000000..bc11f6351
--- /dev/null
+++ b/components/alerts/src/nsAlertsUtils.h
@@ -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/. */
+
+#ifndef nsAlertsUtils_h
+#define nsAlertsUtils_h
+
+#include "nsIPrincipal.h"
+#include "nsString.h"
+
+class nsAlertsUtils final
+{
+private:
+ nsAlertsUtils() = delete;
+
+public:
+ /**
+ * Indicates whether an alert from |aPrincipal| should include the source
+ * string and action buttons. Returns false if |aPrincipal| is |nullptr|, or
+ * a system, expanded, or null principal.
+ */
+ static bool
+ IsActionablePrincipal(nsIPrincipal* aPrincipal);
+
+ /**
+ * Sets |aHostPort| to the host and port from |aPrincipal|'s URI, or an
+ * empty string if |aPrincipal| is not actionable.
+ */
+ static void
+ GetSourceHostPort(nsIPrincipal* aPrincipal, nsAString& aHostPort);
+};
+#endif /* nsAlertsUtils_h */
diff --git a/components/alerts/src/nsXULAlerts.cpp b/components/alerts/src/nsXULAlerts.cpp
new file mode 100644
index 000000000..47e72a27f
--- /dev/null
+++ b/components/alerts/src/nsXULAlerts.cpp
@@ -0,0 +1,380 @@
+/* -*- 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 "nsXULAlerts.h"
+
+#include "nsArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/dom/Notification.h"
+#include "mozilla/Unused.h"
+#include "nsIServiceManager.h"
+#include "nsISupportsPrimitives.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWindowWatcher.h"
+
+using namespace mozilla;
+
+#define ALERT_CHROME_URL "chrome://global/content/alerts/alert.xul"
+
+namespace {
+StaticRefPtr<nsXULAlerts> gXULAlerts;
+} // anonymous namespace
+
+NS_IMPL_CYCLE_COLLECTION(nsXULAlertObserver, mAlertWindow)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULAlertObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULAlertObserver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULAlertObserver)
+
+NS_IMETHODIMP
+nsXULAlertObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp("alertfinished", aTopic)) {
+ mozIDOMWindowProxy* currentAlert = mXULAlerts->mNamedWindows.GetWeak(mAlertName);
+ // The window in mNamedWindows might be a replacement, thus it should only
+ // be removed if it is the same window that is associated with this listener.
+ if (currentAlert == mAlertWindow) {
+ mXULAlerts->mNamedWindows.Remove(mAlertName);
+
+ if (mIsPersistent) {
+ mXULAlerts->PersistentAlertFinished();
+ }
+ }
+ }
+
+ nsresult rv = NS_OK;
+ if (mObserver) {
+ rv = mObserver->Observe(aSubject, aTopic, aData);
+ }
+ return rv;
+}
+
+// We don't cycle collect nsXULAlerts since gXULAlerts will keep the instance
+// alive till shutdown anyway.
+NS_IMPL_ISUPPORTS(nsXULAlerts, nsIAlertsService, nsIAlertsDoNotDisturb, nsIAlertsIconURI)
+
+/* static */ already_AddRefed<nsXULAlerts>
+nsXULAlerts::GetInstance()
+{
+ if (!gXULAlerts) {
+ gXULAlerts = new nsXULAlerts();
+ ClearOnShutdown(&gXULAlerts);
+ }
+ RefPtr<nsXULAlerts> instance = gXULAlerts.get();
+ return instance.forget();
+}
+
+void
+nsXULAlerts::PersistentAlertFinished()
+{
+ MOZ_ASSERT(mPersistentAlertCount);
+ mPersistentAlertCount--;
+
+ // Show next pending persistent alert if any.
+ if (!mPendingPersistentAlerts.IsEmpty()) {
+ ShowAlertWithIconURI(mPendingPersistentAlerts[0].mAlert,
+ mPendingPersistentAlerts[0].mListener,
+ nullptr);
+ mPendingPersistentAlerts.RemoveElementAt(0);
+ }
+}
+
+NS_IMETHODIMP
+nsXULAlerts::ShowAlertNotification(const nsAString& aImageUrl, const nsAString& aAlertTitle,
+ const nsAString& aAlertText, bool aAlertTextClickable,
+ const nsAString& aAlertCookie, nsIObserver* aAlertListener,
+ const nsAString& aAlertName, const nsAString& aBidi,
+ const nsAString& aLang, const nsAString& aData,
+ nsIPrincipal* aPrincipal, bool aInPrivateBrowsing,
+ bool aRequireInteraction)
+{
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
+ nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle,
+ aAlertText, aAlertTextClickable,
+ aAlertCookie, aBidi, aLang, aData,
+ aPrincipal, aInPrivateBrowsing,
+ aRequireInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP
+nsXULAlerts::ShowPersistentNotification(const nsAString& aPersistentData,
+ nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP
+nsXULAlerts::ShowAlert(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ nsAutoString name;
+ nsresult rv = aAlert->GetName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there is a pending alert with the same name in the list of
+ // pending alerts, replace it.
+ if (!mPendingPersistentAlerts.IsEmpty()) {
+ for (uint32_t i = 0; i < mPendingPersistentAlerts.Length(); i++) {
+ nsAutoString pendingAlertName;
+ nsCOMPtr<nsIAlertNotification> pendingAlert = mPendingPersistentAlerts[i].mAlert;
+ rv = pendingAlert->GetName(pendingAlertName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (pendingAlertName.Equals(name)) {
+ nsAutoString cookie;
+ rv = pendingAlert->GetCookie(cookie);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mPendingPersistentAlerts[i].mListener) {
+ rv = mPendingPersistentAlerts[i].mListener->Observe(nullptr, "alertfinished", cookie.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mPendingPersistentAlerts[i].Init(aAlert, aAlertListener);
+ return NS_OK;
+ }
+ }
+ }
+
+ bool requireInteraction;
+ rv = aAlert->GetRequireInteraction(&requireInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (requireInteraction &&
+ !mNamedWindows.Contains(name) &&
+ static_cast<int32_t>(mPersistentAlertCount) >=
+ Preferences::GetInt("dom.webnotifications.requireinteraction.count", 0)) {
+ PendingAlert* pa = mPendingPersistentAlerts.AppendElement();
+ pa->Init(aAlert, aAlertListener);
+ return NS_OK;
+ } else {
+ return ShowAlertWithIconURI(aAlert, aAlertListener, nullptr);
+ }
+}
+
+NS_IMETHODIMP
+nsXULAlerts::ShowAlertWithIconURI(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener,
+ nsIURI* aIconURI)
+{
+ bool inPrivateBrowsing;
+ nsresult rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString cookie;
+ rv = aAlert->GetCookie(cookie);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mDoNotDisturb) {
+ if (aAlertListener)
+ aAlertListener->Observe(nullptr, "alertfinished", cookie.get());
+ return NS_OK;
+ }
+
+ nsAutoString name;
+ rv = aAlert->GetName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString imageUrl;
+ rv = aAlert->GetImageURL(imageUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString title;
+ rv = aAlert->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString text;
+ rv = aAlert->GetText(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool textClickable;
+ rv = aAlert->GetTextClickable(&textClickable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString bidi;
+ rv = aAlert->GetDir(bidi);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString lang;
+ rv = aAlert->GetLang(lang);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString source;
+ rv = aAlert->GetSource(source);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool requireInteraction;
+ rv = aAlert->GetRequireInteraction(&requireInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+
+ nsCOMPtr<nsIMutableArray> argsArray = nsArray::Create();
+
+ // create scriptable versions of our strings that we can store in our nsIMutableArray....
+ nsCOMPtr<nsISupportsString> scriptableImageUrl (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableImageUrl, NS_ERROR_FAILURE);
+
+ scriptableImageUrl->SetData(imageUrl);
+ rv = argsArray->AppendElement(scriptableImageUrl, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsString> scriptableAlertTitle (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableAlertTitle, NS_ERROR_FAILURE);
+
+ scriptableAlertTitle->SetData(title);
+ rv = argsArray->AppendElement(scriptableAlertTitle, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsString> scriptableAlertText (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableAlertText, NS_ERROR_FAILURE);
+
+ scriptableAlertText->SetData(text);
+ rv = argsArray->AppendElement(scriptableAlertText, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsPRBool> scriptableIsClickable (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableIsClickable, NS_ERROR_FAILURE);
+
+ scriptableIsClickable->SetData(textClickable);
+ rv = argsArray->AppendElement(scriptableIsClickable, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsString> scriptableAlertCookie (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableAlertCookie, NS_ERROR_FAILURE);
+
+ scriptableAlertCookie->SetData(cookie);
+ rv = argsArray->AppendElement(scriptableAlertCookie, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsPRInt32> scriptableOrigin (do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableOrigin, NS_ERROR_FAILURE);
+
+ int32_t origin =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_AlertNotificationOrigin);
+ scriptableOrigin->SetData(origin);
+
+ rv = argsArray->AppendElement(scriptableOrigin, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsString> scriptableBidi (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableBidi, NS_ERROR_FAILURE);
+
+ scriptableBidi->SetData(bidi);
+ rv = argsArray->AppendElement(scriptableBidi, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsString> scriptableLang (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableLang, NS_ERROR_FAILURE);
+
+ scriptableLang->SetData(lang);
+ rv = argsArray->AppendElement(scriptableLang, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsPRBool> scriptableRequireInteraction (do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableRequireInteraction, NS_ERROR_FAILURE);
+
+ scriptableRequireInteraction->SetData(requireInteraction);
+ rv = argsArray->AppendElement(scriptableRequireInteraction, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Alerts with the same name should replace the old alert in the same position.
+ // Provide the new alert window with a pointer to the replaced window so that
+ // it may take the same position.
+ nsCOMPtr<nsISupportsInterfacePointer> replacedWindow = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(replacedWindow, NS_ERROR_FAILURE);
+ mozIDOMWindowProxy* previousAlert = mNamedWindows.GetWeak(name);
+ replacedWindow->SetData(previousAlert);
+ replacedWindow->SetDataIID(&NS_GET_IID(mozIDOMWindowProxy));
+ rv = argsArray->AppendElement(replacedWindow, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (requireInteraction) {
+ mPersistentAlertCount++;
+ }
+
+ // Add an observer (that wraps aAlertListener) to remove the window from
+ // mNamedWindows when it is closed.
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ RefPtr<nsXULAlertObserver> alertObserver = new nsXULAlertObserver(this, name, aAlertListener, requireInteraction);
+ nsCOMPtr<nsISupports> iSupports(do_QueryInterface(alertObserver));
+ ifptr->SetData(iSupports);
+ ifptr->SetDataIID(&NS_GET_IID(nsIObserver));
+ rv = argsArray->AppendElement(ifptr, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The source contains the host and port of the site that sent the
+ // notification. It is empty for system alerts.
+ nsCOMPtr<nsISupportsString> scriptableAlertSource (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableAlertSource, NS_ERROR_FAILURE);
+ scriptableAlertSource->SetData(source);
+ rv = argsArray->AppendElement(scriptableAlertSource, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsCString> scriptableIconURL (do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableIconURL, NS_ERROR_FAILURE);
+ if (aIconURI) {
+ nsAutoCString iconURL;
+ rv = aIconURI->GetSpec(iconURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ scriptableIconURL->SetData(iconURL);
+ }
+ rv = argsArray->AppendElement(scriptableIconURL, /*weak =*/ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ nsAutoCString features("chrome,dialog=yes,titlebar=no,popup=yes");
+ if (inPrivateBrowsing) {
+ features.AppendLiteral(",private");
+ }
+ rv = wwatch->OpenWindow(nullptr, ALERT_CHROME_URL, "_blank", features.get(),
+ argsArray, getter_AddRefs(newWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mNamedWindows.Put(name, newWindow);
+ alertObserver->SetAlertWindow(newWindow);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULAlerts::SetManualDoNotDisturb(bool aDoNotDisturb)
+{
+ mDoNotDisturb = aDoNotDisturb;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULAlerts::GetManualDoNotDisturb(bool* aRetVal)
+{
+ *aRetVal = mDoNotDisturb;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULAlerts::CloseAlert(const nsAString& aAlertName,
+ nsIPrincipal* aPrincipal)
+{
+ mozIDOMWindowProxy* alert = mNamedWindows.GetWeak(aAlertName);
+ if (nsCOMPtr<nsPIDOMWindowOuter> domWindow = nsPIDOMWindowOuter::From(alert)) {
+ domWindow->DispatchCustomEvent(NS_LITERAL_STRING("XULAlertClose"));
+ }
+ return NS_OK;
+}
+
diff --git a/components/alerts/src/nsXULAlerts.h b/components/alerts/src/nsXULAlerts.h
new file mode 100644
index 000000000..557716ee6
--- /dev/null
+++ b/components/alerts/src/nsXULAlerts.h
@@ -0,0 +1,84 @@
+/* -*- 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 nsXULAlerts_h__
+#define nsXULAlerts_h__
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsInterfaceHashtable.h"
+
+#include "mozIDOMWindow.h"
+#include "nsIObserver.h"
+
+struct PendingAlert
+{
+ void Init(nsIAlertNotification* aAlert, nsIObserver* aListener)
+ {
+ mAlert = aAlert;
+ mListener = aListener;
+ }
+ nsCOMPtr<nsIAlertNotification> mAlert;
+ nsCOMPtr<nsIObserver> mListener;
+};
+
+class nsXULAlerts : public nsIAlertsService,
+ public nsIAlertsDoNotDisturb,
+ public nsIAlertsIconURI
+{
+ friend class nsXULAlertObserver;
+public:
+ NS_DECL_NSIALERTSICONURI
+ NS_DECL_NSIALERTSDONOTDISTURB
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_ISUPPORTS
+
+ nsXULAlerts()
+ {
+ }
+
+ static already_AddRefed<nsXULAlerts> GetInstance();
+
+protected:
+ virtual ~nsXULAlerts() {}
+ void PersistentAlertFinished();
+
+ nsInterfaceHashtable<nsStringHashKey, mozIDOMWindowProxy> mNamedWindows;
+ uint32_t mPersistentAlertCount = 0;
+ nsTArray<PendingAlert> mPendingPersistentAlerts;
+ bool mDoNotDisturb = false;
+};
+
+/**
+ * This class wraps observers for alerts and watches
+ * for the "alertfinished" event in order to release
+ * the reference on the nsIDOMWindow of the XUL alert.
+ */
+class nsXULAlertObserver : public nsIObserver {
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsXULAlertObserver)
+
+ nsXULAlertObserver(nsXULAlerts* aXULAlerts, const nsAString& aAlertName,
+ nsIObserver* aObserver, bool aIsPersistent)
+ : mXULAlerts(aXULAlerts), mAlertName(aAlertName),
+ mObserver(aObserver), mIsPersistent(aIsPersistent) {}
+
+ void SetAlertWindow(mozIDOMWindowProxy* aWindow) { mAlertWindow = aWindow; }
+
+protected:
+ virtual ~nsXULAlertObserver() {}
+
+ RefPtr<nsXULAlerts> mXULAlerts;
+ nsString mAlertName;
+ nsCOMPtr<mozIDOMWindowProxy> mAlertWindow;
+ nsCOMPtr<nsIObserver> mObserver;
+ bool mIsPersistent;
+};
+
+#endif /* nsXULAlerts_h__ */
+
diff --git a/components/apppicker/content/appPicker.js b/components/apppicker/content/appPicker.js
new file mode 100644
index 000000000..6922fce39
--- /dev/null
+++ b/components/apppicker/content/appPicker.js
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 AppPicker() {}
+
+AppPicker.prototype =
+{
+ // Class members
+ _incomingParams:null,
+
+ /**
+ * Init the dialog and populate the application list
+ */
+ appPickerLoad: function appPickerLoad() {
+ const nsILocalHandlerApp = Components.interfaces.nsILocalHandlerApp;
+
+ this._incomingParams = window.arguments[0];
+ this._incomingParams.handlerApp = null;
+
+ document.title = this._incomingParams.title;
+
+ // Header creation - at the very least, we must have
+ // a mime type:
+ //
+ // (icon) Zip File
+ // (icon) filename
+ //
+ // (icon) Web Feed
+ // (icon) mime/type
+ //
+ // (icon) mime/type
+ // (icon)
+
+ var mimeInfo = this._incomingParams.mimeInfo;
+ var filename = this._incomingParams.filename;
+ if (!filename) {
+ filename = mimeInfo.MIMEType;
+ }
+ var description = this._incomingParams.description;
+ if (!description) {
+ description = filename;
+ filename = "";
+ }
+
+ // Setup the dialog header information
+ document.getElementById("content-description").setAttribute("value",
+ description);
+ document.getElementById("suggested-filename").setAttribute("value",
+ filename);
+ document.getElementById("content-icon").setAttribute("src",
+ "moz-icon://" + filename + "?size=32&contentType=" +
+ mimeInfo.MIMEType);
+
+ // Grab a list of nsILocalHandlerApp application helpers to list
+ var fileList = mimeInfo.possibleLocalHandlers;
+
+ var list = document.getElementById("app-picker-listbox");
+
+ var primaryCount = 0;
+
+ if (!fileList || fileList.length == 0) {
+ // display a message saying nothing is configured
+ document.getElementById("app-picker-notfound").removeAttribute("hidden");
+ return;
+ }
+
+ for (var idx = 0; idx < fileList.length; idx++) {
+ var file = fileList.queryElementAt(idx, nsILocalHandlerApp);
+ try {
+ if (!file.executable || !file.executable.isFile())
+ continue;
+ } catch (err) {
+ continue;
+ }
+
+ var item = document.createElement("listitem");
+ item.className = "listitem-iconic";
+ item.handlerApp = file;
+ item.setAttribute("label", this.getFileDisplayName(file.executable));
+ item.setAttribute("image", this.getFileIconURL(file.executable));
+ list.appendChild(item);
+
+ primaryCount++;
+ }
+
+ if ( primaryCount == 0 ) {
+ // display a message saying nothing is configured
+ document.getElementById("app-picker-notfound").removeAttribute("hidden");
+ }
+ },
+
+ /**
+ * Retrieve the moz-icon for the app
+ */
+ getFileIconURL: function getFileIconURL(file) {
+ var ios = Components.classes["@mozilla.org/network/io-service;1"].
+ getService(Components.interfaces.nsIIOService);
+
+ if (!ios) return "";
+ const nsIFileProtocolHandler =
+ Components.interfaces.nsIFileProtocolHandler;
+
+ var fph = ios.getProtocolHandler("file")
+ .QueryInterface(nsIFileProtocolHandler);
+ if (!fph) return "";
+
+ var urlSpec = fph.getURLSpecFromFile(file);
+ return "moz-icon://" + urlSpec + "?size=32";
+ },
+
+ /**
+ * Retrieve the pretty description from the file
+ */
+ getFileDisplayName: function getFileDisplayName(file) {
+#ifdef XP_WIN
+ if (file instanceof Components.interfaces.nsILocalFileWin) {
+ try {
+ return file.getVersionInfoField("FileDescription");
+ } catch (e) {}
+ }
+#endif
+ return file.leafName;
+ },
+
+ /**
+ * Double click accepts an app
+ */
+ appDoubleClick: function appDoubleClick() {
+ var list = document.getElementById("app-picker-listbox");
+ var selItem = list.selectedItem;
+
+ if (!selItem) {
+ this._incomingParams.handlerApp = null;
+ return true;
+ }
+
+ this._incomingParams.handlerApp = selItem.handlerApp;
+ window.close();
+
+ return true;
+ },
+
+ appPickerOK: function appPickerOK() {
+ if (this._incomingParams.handlerApp) return true;
+
+ var list = document.getElementById("app-picker-listbox");
+ var selItem = list.selectedItem;
+
+ if (!selItem) {
+ this._incomingParams.handlerApp = null;
+ return true;
+ }
+ this._incomingParams.handlerApp = selItem.handlerApp;
+
+ return true;
+ },
+
+ appPickerCancel: function appPickerCancel() {
+ this._incomingParams.handlerApp = null;
+ return true;
+ },
+
+ /**
+ * User browse for an app.
+ */
+ appPickerBrowse: function appPickerBrowse() {
+ var nsIFilePicker = Components.interfaces.nsIFilePicker;
+ var fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(nsIFilePicker);
+
+ fp.init(window, this._incomingParams.title, nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterApps);
+
+ var fileLoc = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ var startLocation;
+#ifdef XP_WIN
+ startLocation = "ProgF"; // Program Files
+#else
+ startLocation = "Home";
+#endif
+ fp.displayDirectory =
+ fileLoc.get(startLocation, Components.interfaces.nsILocalFile);
+
+ if (fp.show() == nsIFilePicker.returnOK && fp.file) {
+ var localHandlerApp =
+ Components.classes["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Components.interfaces.nsILocalHandlerApp);
+ localHandlerApp.executable = fp.file;
+
+ this._incomingParams.handlerApp = localHandlerApp;
+ window.close();
+ }
+ return true;
+ }
+}
+
+// Global object
+var g_dialog = new AppPicker();
diff --git a/components/apppicker/content/appPicker.xul b/components/apppicker/content/appPicker.xul
new file mode 100644
index 000000000..3a50483c1
--- /dev/null
+++ b/components/apppicker/content/appPicker.xul
@@ -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/. -->
+
+ <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+ <?xml-stylesheet href="chrome://global/skin/appPicker.css" type="text/css"?>
+
+ <!DOCTYPE dialog SYSTEM "chrome://global/locale/appPicker.dtd" >
+
+ <dialog id="app-picker"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="g_dialog.appPickerLoad();"
+ buttons="accept,cancel,extra2"
+ buttonlabelextra2="&BrowseButton.label;"
+ ondialogextra2="g_dialog.appPickerBrowse();"
+ defaultButton="cancel"
+ ondialogaccept="return g_dialog.appPickerOK();"
+ ondialogcancel="return g_dialog.appPickerCancel();"
+ aria-describedby="content-description suggested-filename"
+ persist="screenX screenY">
+
+ <script type="application/javascript" src="chrome://global/content/appPicker.js"/>
+
+ <hbox id="file-info" align="center">
+ <image id="content-icon" src=""/>
+ <vbox flex="1">
+ <label id="content-description" crop="center" value=""/>
+ <label id="suggested-filename" crop="center" value=""/>
+ </vbox>
+ </hbox>
+
+ <label id="sendto-message" value="&SendMsg.label;" control="app-picker-listbox"/>
+
+ <listbox id="app-picker-listbox" rows="5"
+ ondblclick="g_dialog.appDoubleClick();"/>
+
+ <label id="app-picker-notfound" value="&NoAppFound.label;" hidden="true"/>
+ </dialog>
diff --git a/components/apppicker/jar.mn b/components/apppicker/jar.mn
new file mode 100644
index 000000000..d8431c3fe
--- /dev/null
+++ b/components/apppicker/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/.
+
+toolkit.jar:
+ content/global/appPicker.xul (content/appPicker.xul)
+* content/global/appPicker.js (content/appPicker.js)
+
diff --git a/components/apppicker/moz.build b/components/apppicker/moz.build
new file mode 100644
index 000000000..635fa39c9
--- /dev/null
+++ b/components/apppicker/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/appshell/moz.build b/components/appshell/moz.build
new file mode 100644
index 000000000..b81b329fe
--- /dev/null
+++ b/components/appshell/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsIAppShellService.idl',
+ 'public/nsIPopupWindowManager.idl',
+ 'public/nsIWindowlessBrowser.idl',
+ 'public/nsIWindowMediator.idl',
+ 'public/nsIWindowMediatorListener.idl',
+ 'public/nsIXULBrowserWindow.idl',
+ 'public/nsIXULWindow.idl',
+]
+
+EXPORTS += ['src/nsAppShellCID.h']
+
+UNIFIED_SOURCES += [
+ 'src/nsAppShellFactory.cpp',
+ 'src/nsAppShellService.cpp',
+ 'src/nsAppShellWindowEnumerator.cpp',
+ 'src/nsChromeTreeOwner.cpp',
+ 'src/nsContentTreeOwner.cpp',
+ 'src/nsWebShellWindow.cpp',
+ 'src/nsWindowMediator.cpp',
+ 'src/nsXULWindow.cpp',
+]
+
+LOCAL_INCLUDES += ['/dom/base']
+
+XPIDL_MODULE = 'appshell'
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild') \ No newline at end of file
diff --git a/components/appshell/public/nsIAppShellService.idl b/components/appshell/public/nsIAppShellService.idl
new file mode 100644
index 000000000..e7d96a5d1
--- /dev/null
+++ b/components/appshell/public/nsIAppShellService.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIXULWindow;
+interface nsIWindowlessBrowser;
+interface nsIURI;
+interface mozIDOMWindowProxy;
+interface nsIAppShell;
+interface nsITabParent;
+
+[ptr] native JSContext(JSContext);
+
+%{C++
+#include "js/TypeDecls.h"
+%}
+
+[scriptable, uuid(19266025-354c-4bb9-986b-3483b2b1cdef)]
+interface nsIAppShellService : nsISupports
+{
+ /**
+ * Create a window, which will be initially invisible.
+ * @param aParent the parent window. Can be null.
+ * @param aUrl the contents of the new window.
+ * @param aChromeMask chrome flags affecting the kind of OS border
+ * given to the window. see nsIBrowserWindow for
+ * bit/flag definitions.
+ * @param aCallbacks interface providing C++ hooks for window initialization
+ * before the window is made visible. Can be null.
+ * Deprecated.
+ * @param aInitialWidth width, in pixels, of the window. Width of window
+ * at creation. Can be overridden by the "width"
+ * tag in the XUL. Set to NS_SIZETOCONTENT to force
+ * the window to wrap to its contents.
+ * @param aInitialHeight like aInitialWidth, but subtly different.
+ * @param aOpeningTab The TabParent that requested that this window be opened.
+ * Can be left null.
+ * @param aOpenerWindow The Window Proxy which requested that this window be opened.
+ * Can be left null.
+ */
+ const long SIZE_TO_CONTENT = -1;
+ nsIXULWindow createTopLevelWindow(in nsIXULWindow aParent,
+ in nsIURI aUrl,
+ in uint32_t aChromeMask,
+ in long aInitialWidth,
+ in long aInitialHeight,
+ in nsITabParent aOpeningTab,
+ in mozIDOMWindowProxy aOpenerWindow);
+
+ /**
+ * This is the constructor for creating an invisible DocShell.
+ * It is used to simulate DOM windows without an actual physical
+ * representation.
+ * @param aIsChrome Set true if you want to use it for chrome content.
+ */
+ nsIWindowlessBrowser createWindowlessBrowser([optional] in bool aIsChrome);
+
+ [noscript]
+ void createHiddenWindow();
+
+ void destroyHiddenWindow();
+
+ /**
+ * B2G multi-screen support. When open another top-level window on b2g,
+ * a screen ID is needed for identifying which screen this window is
+ * opened to.
+ * @param aScreenId Differentiate screens of windows. It is platform-
+ * specific due to the hardware limitation for now.
+ */
+ [noscript]
+ void setScreenId(in uint32_t aScreenId);
+
+ /**
+ * Return the (singleton) application hidden window, automatically created
+ * and maintained by this AppShellService.
+ * @param aResult the hidden window. Do not unhide hidden window.
+ * Do not taunt hidden window.
+ */
+ readonly attribute nsIXULWindow hiddenWindow;
+
+ /**
+ * Return the (singleton) application hidden window, automatically created
+ * and maintained by this AppShellService.
+ * @param aResult the hidden window. Do not unhide hidden window.
+ * Do not taunt hidden window.
+ */
+ readonly attribute mozIDOMWindowProxy hiddenDOMWindow;
+
+ /**
+ * Return the (singleton) application hidden private window, automatically
+ * created and maintained by this AppShellService. This window is created
+ * in private browsing mode.
+ * @param aResult the hidden private window. Do not unhide hidden window.
+ * Do not taunt hidden window.
+ */
+ readonly attribute nsIXULWindow hiddenPrivateWindow;
+
+ /**
+ * Return the (singleton) application hidden private window, automatically
+ * created and maintained by this AppShellService. This window is created
+ * in private browsing mode.
+ * @param aResult the hidden private window. Do not unhide hidden window.
+ * Do not taunt hidden window.
+ */
+ readonly attribute mozIDOMWindowProxy hiddenPrivateDOMWindow;
+
+ /**
+ * Return true if the application hidden window was provided by the
+ * application. If it wasn't, the default hidden window was used. This will
+ * usually be false on all non-mac platforms.
+ */
+ readonly attribute boolean applicationProvidedHiddenWindow;
+
+ /**
+ * Add a window to the application's registry of windows. These windows
+ * are generally shown in the Windows taskbar, and the application
+ * knows it can't quit until it's out of registered windows.
+ * @param aWindow the window to register
+ * @note When this method is successful, it fires the global notification
+ * "xul-window-registered"
+ */
+ void registerTopLevelWindow(in nsIXULWindow aWindow);
+
+ /**
+ * Remove a window from the application's window registry. Note that
+ * this method won't automatically attempt to quit the app when
+ * the last window is unregistered. For that, see Quit().
+ * @param aWindow you see the pattern
+ */
+ void unregisterTopLevelWindow(in nsIXULWindow aWindow);
+
+ /**
+ * Whether the hidden private window has been lazily created.
+ */
+ [noscript]
+ readonly attribute boolean hasHiddenPrivateWindow;
+
+ /**
+ * Start/stop tracking lags in the event loop.
+ * If the event loop gets unresponsive, a "event-loop-lag" notification
+ * is sent. Note that calling `startEventLoopLagTracking` when tracking
+ * is already enabled has no effect.
+ * @return true if tracking succeeded.
+ */
+ bool startEventLoopLagTracking();
+ void stopEventLoopLagTracking();
+};
diff --git a/components/appshell/public/nsIPopupWindowManager.idl b/components/appshell/public/nsIPopupWindowManager.idl
new file mode 100644
index 000000000..4e6cb99b3
--- /dev/null
+++ b/components/appshell/public/nsIPopupWindowManager.idl
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+/**
+ * This is the interface to the Popup Window Manager: an object which
+ * maintains popup window permissions by website.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+
+[scriptable, uuid(66386aa9-2088-4bae-82c7-9f58bc02be64)]
+interface nsIPopupWindowManager : nsISupports {
+
+ /**
+ * These values are returned by the testPermission method
+ */
+ const uint32_t ALLOW_POPUP = 1;
+ const uint32_t DENY_POPUP = 2;
+ const uint32_t ALLOW_POPUP_WITH_PREJUDICE = 3;
+
+ /**
+ * Test whether a website has permission to show a popup window.
+ * @param principal is the principal to be tested
+ * @return one of the enumerated permission actions defined above
+ */
+ uint32_t testPermission(in nsIPrincipal principal);
+};
+
+%{ C++
+#define NS_POPUPWINDOWMANAGER_CONTRACTID "@mozilla.org/PopupWindowManager;1"
+%}
diff --git a/components/appshell/public/nsIWindowMediator.idl b/components/appshell/public/nsIWindowMediator.idl
new file mode 100644
index 000000000..b38297594
--- /dev/null
+++ b/components/appshell/public/nsIWindowMediator.idl
@@ -0,0 +1,206 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsISimpleEnumerator.idl"
+
+%{C++
+#define NS_WINDOWMEDIATOR_CID \
+{ 0x79a2b7cc, 0xf05b, 0x4605, \
+ { 0xbf, 0xa0, 0xfa, 0xc5, 0x4f, 0x27, 0xee, 0xc8 } }
+
+#define NS_WINDOWMEDIATOR_CONTRACTID \
+ "@mozilla.org/appshell/window-mediator;1"
+%}
+
+interface mozIDOMWindow;
+interface mozIDOMWindowProxy;
+interface nsIXULWindow;
+interface nsIWidget;
+interface nsIWindowMediatorListener;
+
+[scriptable, uuid(df0da056-357d-427f-bafd-e6cbf19c9381)]
+interface nsIWindowMediator: nsISupports
+{
+ /** Return an enumerator which iterates over all windows of type aWindowType
+ * from the oldest window to the youngest.
+ * @param aWindowType the returned enumerator will enumerate only
+ * windows of this type. ("type" is the
+ * |windowtype| attribute of the XML <window> element.)
+ * If null, all windows will be enumerated.
+ * @return an enumerator of nsIDOMWindows. Note that windows close
+ * asynchronously in many cases, so windows returned from this
+ * enumerator can have .closed set to true. Caveat enumerator!
+ */
+ nsISimpleEnumerator getEnumerator(in wstring aWindowType);
+
+ /** Identical to getEnumerator except:
+ * @return an enumerator of nsIXULWindows
+ */
+ nsISimpleEnumerator getXULWindowEnumerator(in wstring aWindowType);
+
+ /** Return an enumerator which iterates over all windows of type aWindowType
+ * in their z (front-to-back) order. Note this interface makes
+ * no requirement that a window couldn't be revisited if windows
+ * are re-ordered while z-order enumerators are active.
+ * @param aWindowType the returned enumerator will enumerate only
+ * windows of this type. ("type" is the
+ * |windowtype| attribute of the XML <window> element.)
+ * If null, all windows will be enumerated.
+ * @param aFrontToBack if true, the enumerator enumerates windows in order
+ * from front to back. back to front if false.
+ * @return an enumerator of nsIDOMWindows
+ */
+ nsISimpleEnumerator getZOrderDOMWindowEnumerator(in wstring aWindowType,
+ in boolean aFrontToBack);
+
+ /** Identical to getZOrderDOMWindowEnumerator except:
+ * @return an enumerator of nsIXULWindows
+ */
+ nsISimpleEnumerator getZOrderXULWindowEnumerator(in wstring aWindowType,
+ in boolean aFrontToBack);
+
+ /** This is a shortcut for simply fetching the first window in
+ * front to back order.
+ * @param aWindowType return the topmost window of this type.
+ * ("type" is the |windowtype| attribute of
+ * the XML <window> element.)
+ * If null, return the topmost window of any type.
+ * @return the topmost window
+ */
+ mozIDOMWindowProxy getMostRecentWindow(in wstring aWindowType);
+
+ /**
+ * Return the outer window with the given ID, if any. Can return null.
+ */
+ mozIDOMWindowProxy getOuterWindowWithId(in unsigned long long aOuterWindowID);
+
+ /**
+ * Return the inner window with the given current window ID, if any.
+ * Can return null if no inner window with the ID exists or if it's not
+ * a current inner anymore.
+ */
+ mozIDOMWindow getCurrentInnerWindowWithId(in unsigned long long aInnerWindowID);
+
+ /** Add the window to the list of known windows. Listeners (see
+ * addListener) will be notified through their onOpenWindow method.
+ * @param aWindow the window to add
+ */
+ [noscript] void registerWindow(in nsIXULWindow aWindow);
+
+ /** Remove the window from the list of known windows. Listeners (see
+ * addListener) will be be notified through their onCloseWindow method.
+ * @param aWindow the window to remove
+ */
+ [noscript] void unregisterWindow(in nsIXULWindow aWindow);
+
+ /** Call this method when a window gains focus. It's a primitive means of
+ * determining the most recent window. It's no longer necessary and it
+ * really should be removed.
+ * @param aWindow the window which has gained focus
+ */
+ [noscript] void updateWindowTimeStamp(in nsIXULWindow aWindow);
+
+ /** Call this method when a window's title changes. Listeners (see
+ * addListener) will be notified through their onWindowTitleChange method.
+ * @param aWindow the window whose title has changed
+ * @param inTitle the window's new title
+ */
+ [noscript] void updateWindowTitle(in nsIXULWindow aWindow,
+ in wstring inTitle );
+
+ /* z-ordering: */
+
+ const unsigned long zLevelTop = 1;
+ const unsigned long zLevelBottom = 2;
+ const unsigned long zLevelBelow = 3; // below some window
+
+ /** A window wants to be moved in z-order. Calculate whether and how
+ * it should be constrained. Note this method is advisory only:
+ * it changes nothing either in WindowMediator's internal state
+ * or with the window.
+ * Note it compares the nsIXULWindow to nsIWidgets. A pure interface
+ * would use all nsIXULWindows. But we expect this to be called from
+ * callbacks originating in native window code. They are expected to
+ * hand us comparison values which are pulled from general storage
+ * in the native widget, and may not correspond to an nsIWidget at all.
+ * For that reason this interface requires only objects one step
+ * removed from the native window (nsIWidgets), and its implementation
+ * must be very understanding of what may be completely invalid
+ * pointers in those parameters.
+ *
+ * @param inWindow the window in question
+ * @param inPosition requested position
+ * values: zLevelTop: topmost window. zLevelBottom: bottom.
+ * zLevelBelow: below ioBelow. (the value of ioBelow will
+ * be ignored for zLevelTop and Bottom.)
+ * @param inBelow if inPosition==zLevelBelow, the window
+ * below which inWindow wants to be placed. Otherwise this
+ * variable is ignored.
+ * @param outPosition constrained position, values like inPosition.
+ * @param outBelow if outPosition==zLevelBelow, the window
+ * below which inWindow should be placed. Otherwise this
+ * this value will be null.
+ * @return PR_TRUE if the position returned is different from
+ * the position given.
+ */
+
+ [noscript] boolean calculateZPosition(in nsIXULWindow inWindow,
+ in unsigned long inPosition,
+ in nsIWidget inBelow,
+ out unsigned long outPosition,
+ out nsIWidget outBelow);
+
+ /** A window has been positioned behind another. Inform WindowMediator
+ * @param inWindow the window in question
+ * @param inPosition new position. values:
+ * zLevelTop: topmost window.
+ * zLevelBottom: bottom.
+ * zLevelBelow: below inBelow. (inBelow is ignored
+ * for other values of inPosition.)
+ * @param inBelow the window inWindow is behind, if zLevelBelow
+ */
+ [noscript] void setZPosition(in nsIXULWindow inWindow,
+ in unsigned long inPosition,
+ in nsIXULWindow inBelow);
+
+ /** Return the window's Z level (as defined in nsIXULWindow).
+ * @param aWindow the window in question
+ * @return aWindow's z level
+ */
+ [noscript] uint32_t getZLevel(in nsIXULWindow aWindow);
+
+ /** Set the window's Z level (as defined in nsIXULWindow). The implementation
+ * will reposition the window as necessary to match its new Z level.
+ * The implementation will assume a window's Z level to be
+ * nsIXULWindow::normalZ until it has been informed of a different level.
+ * @param aWindow the window in question
+ * @param aZLevel the window's new Z level
+ */
+ [noscript] void setZLevel(in nsIXULWindow aWindow, in uint32_t aZLevel);
+
+ /** Register a listener for window status changes.
+ * keeps strong ref? (to be decided)
+ * @param aListener the listener to register
+ */
+ void addListener(in nsIWindowMediatorListener aListener);
+
+ /** Unregister a listener of window status changes.
+ * @param aListener the listener to unregister
+ */
+ void removeListener(in nsIWindowMediatorListener aListener);
+};
+
+// XXXcatalinb: This should be merged to nsIWindowMediator. Using this
+// to avoid UUID change in aurora.
+[scriptable, uuid(b9ed4063-39a2-4302-8e5c-7287eef021fe)]
+interface nsIWindowMediator_44 : nsIWindowMediator
+{
+ /**
+ * Same as getMostRecentWindow, but ignores private browsing
+ * windows.
+ */
+ mozIDOMWindowProxy getMostRecentNonPBWindow(in wstring aWindowType);
+};
diff --git a/components/appshell/public/nsIWindowMediatorListener.idl b/components/appshell/public/nsIWindowMediatorListener.idl
new file mode 100644
index 000000000..459e3b6ab
--- /dev/null
+++ b/components/appshell/public/nsIWindowMediatorListener.idl
@@ -0,0 +1,19 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsIXULWindow;
+
+[scriptable, uuid(2F276982-0D60-4377-A595-D350BA516395)]
+interface nsIWindowMediatorListener : nsISupports
+{
+ void onWindowTitleChange(in nsIXULWindow window,
+ in wstring newTitle);
+
+ void onOpenWindow(in nsIXULWindow window);
+ void onCloseWindow(in nsIXULWindow window);
+};
+
diff --git a/components/appshell/public/nsIWindowlessBrowser.idl b/components/appshell/public/nsIWindowlessBrowser.idl
new file mode 100644
index 000000000..c959e47a4
--- /dev/null
+++ b/components/appshell/public/nsIWindowlessBrowser.idl
@@ -0,0 +1,27 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsIWebNavigation.idl"
+
+/**
+ * This interface represents a nsIWebBrowser instance with no associated OS
+ * window. Its main function is to manage the lifetimes of those windows.
+ * A strong reference to this object must be held until the window is
+ * ready to be destroyed.
+ */
+[scriptable, uuid(abb46f48-abfc-41bf-aa9a-7feccefcf977)]
+interface nsIWindowlessBrowser : nsIWebNavigation
+{
+ /**
+ * "Closes" the windowless browser and destroys its associated nsIWebBrowser
+ * and docshell.
+ *
+ * This method *must* be called for every windowless browser before its last
+ * reference is released.
+ */
+ void close();
+};
+
diff --git a/components/appshell/public/nsIXULBrowserWindow.idl b/components/appshell/public/nsIXULBrowserWindow.idl
new file mode 100644
index 000000000..5dbc2d409
--- /dev/null
+++ b/components/appshell/public/nsIXULBrowserWindow.idl
@@ -0,0 +1,82 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+#include "nsIURI.idl"
+#include "nsIDOMNode.idl"
+
+interface nsIRequest;
+interface nsIDOMElement;
+interface nsIInputStream;
+interface nsIDocShell;
+interface nsITabParent;
+interface nsIPrincipal;
+interface mozIDOMWindowProxy;
+
+/**
+ * The nsIXULBrowserWindow supplies the methods that may be called from the
+ * internals of the browser area to tell the containing xul window to update
+ * its ui.
+ */
+[scriptable, uuid(a8675fa9-c8b4-4350-9803-c38f344a9e38)]
+interface nsIXULBrowserWindow : nsISupports
+{
+ /**
+ * Sets the status according to JS' version of status.
+ */
+ void setJSStatus(in AString status);
+
+ /**
+ * Tells the object implementing this function what link we are currently
+ * over.
+ */
+ void setOverLink(in AString link, in nsIDOMElement element);
+
+ /**
+ * Determines the appropriate target for a link.
+ */
+ AString onBeforeLinkTraversal(in AString originalTarget,
+ in nsIURI linkURI,
+ in nsIDOMNode linkNode,
+ in boolean isAppTab);
+
+ /**
+ * Find the initial browser of the window and set its remote attribute.
+ * This can be used to ensure that there is a remote browser in a new
+ * window when it first spawns.
+ *
+ */
+ nsITabParent forceInitialBrowserRemote();
+ void forceInitialBrowserNonRemote(in mozIDOMWindowProxy openerWindow);
+
+ /**
+ * Determines whether a load should continue.
+ *
+ * @param aDocShell
+ * The docshell performing the load.
+ * @param aURI
+ * The URI being loaded.
+ * @param aReferrer
+ * The referrer of the load.
+ * @param aTriggeringPrincipal
+ * The principal that initiated the load of aURI.
+ */
+ bool shouldLoadURI(in nsIDocShell aDocShell,
+ in nsIURI aURI,
+ in nsIURI aReferrer,
+ in nsIPrincipal aTriggeringPrincipal);
+ /**
+ * Show/hide a tooltip (when the user mouses over a link, say).
+ */
+ void showTooltip(in long x, in long y, in AString tooltip, in AString direction);
+ void hideTooltip();
+
+ /**
+ * Return the number of tabs in this window.
+ */
+ uint32_t getTabCount();
+};
+
diff --git a/components/appshell/public/nsIXULWindow.idl b/components/appshell/public/nsIXULWindow.idl
new file mode 100644
index 000000000..8db12adb6
--- /dev/null
+++ b/components/appshell/public/nsIXULWindow.idl
@@ -0,0 +1,168 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * The nsIXULWindow
+ *
+ * When the window is destroyed, it will fire a "xul-window-destroyed"
+ * notification through the global observer service.
+ */
+
+interface nsIDocShell;
+interface nsIDocShellTreeItem;
+interface nsIXULBrowserWindow;
+interface nsITabParent;
+interface mozIDOMWindowProxy;
+
+[scriptable, uuid(d6d7a014-e28d-4c9d-8727-1cf6d870619b)]
+interface nsIXULWindow : nsISupports
+{
+ /**
+ * The docshell owning the XUL for this window.
+ */
+ readonly attribute nsIDocShell docShell;
+
+ /**
+ * Indicates if this window is instrinsically sized.
+ */
+ attribute boolean intrinsicallySized;
+
+ /**
+ * The primary content shell.
+ *
+ * Note that this is a docshell tree item and therefore can not be assured of
+ * what object it is. It could be an editor, a docshell, or a browser object.
+ * Or down the road any other object that supports being a DocShellTreeItem
+ * Query accordingly to determine the capabilities.
+ */
+ readonly attribute nsIDocShellTreeItem primaryContentShell;
+
+ /**
+ * In multiprocess case we may not have primaryContentShell but
+ * primaryTabParent.
+ */
+ readonly attribute nsITabParent primaryTabParent;
+
+ void tabParentAdded(in nsITabParent aTab, in boolean aPrimary);
+ void tabParentRemoved(in nsITabParent aTab);
+
+ /**
+ * The content shell specified by the supplied id.
+ *
+ * Note that this is a docshell tree item and therefore can not be assured of
+ * what object it is. It could be an editor, a docshell, or a browser object.
+ * Or down the road any other object that supports being a DocShellTreeItem
+ * Query accordingly to determine the capabilities.
+ */
+ nsIDocShellTreeItem getContentShellById(in wstring ID);
+
+ /**
+ * Tell this window that it has picked up a child XUL window
+ * @param aChild the child window being added
+ */
+ void addChildWindow(in nsIXULWindow aChild);
+
+ /**
+ * Tell this window that it has lost a child XUL window
+ * @param aChild the child window being removed
+ */
+ void removeChildWindow(in nsIXULWindow aChild);
+
+ /**
+ * Move the window to a centered position.
+ * @param aRelative If not null, the window relative to which the window is
+ * moved. See aScreen parameter for details.
+ * @param aScreen PR_TRUE to center the window relative to the screen
+ * containing aRelative if aRelative is not null. If
+ * aRelative is null then relative to the screen of the
+ * opener window if it was initialized by passing it to
+ * nsWebShellWindow::Initialize. Failing that relative to
+ * the main screen.
+ * PR_FALSE to center it relative to aRelative itself.
+ * @param aAlert PR_TRUE to move the window to an alert position,
+ * generally centered horizontally and 1/3 down from the top.
+ */
+ void center(in nsIXULWindow aRelative, in boolean aScreen, in boolean aAlert);
+
+ /**
+ * Shows the window as a modal window. That is, ensures that it is visible
+ * and runs a local event loop, exiting only once the window has been closed.
+ */
+ void showModal();
+
+ const unsigned long lowestZ = 0;
+ const unsigned long loweredZ = 4; /* "alwaysLowered" attribute */
+ const unsigned long normalZ = 5;
+ const unsigned long raisedZ = 6; /* "alwaysRaised" attribute */
+ const unsigned long highestZ = 9;
+
+ attribute unsigned long zLevel;
+
+ /**
+ * contextFlags are from nsIWindowCreator2
+ */
+ attribute uint32_t contextFlags;
+
+ attribute uint32_t chromeFlags;
+
+ /**
+ * Begin assuming |chromeFlags| don't change hereafter, and assert
+ * if they do change. The state change is one-way and idempotent.
+ */
+ void assumeChromeFlagsAreFrozen();
+
+ /**
+ * Create a new window.
+ * @param aChromeFlags see nsIWebBrowserChrome
+ * @param aOpeningTab the TabParent that requested this new window be opened.
+ * Can be left null.
+ * @param aOpener The window which is requesting that this new window be opened.
+ * @return the newly minted window
+ */
+ nsIXULWindow createNewWindow(in int32_t aChromeFlags,
+ in nsITabParent aOpeningTab,
+ in mozIDOMWindowProxy aOpener);
+
+ attribute nsIXULBrowserWindow XULBrowserWindow;
+
+ /**
+ * Back-door method to force application of chrome flags at a particular
+ * time. Do NOT call this unless you know what you're doing! In particular,
+ * calling this when this XUL window doesn't yet have a document in its
+ * docshell could cause problems.
+ */
+ [noscript] void applyChromeFlags();
+
+ /**
+ * Given the dimensions of some content area held within this
+ * XUL window, and assuming that that content area will change
+ * its dimensions in linear proportion to the dimensions of this
+ * XUL window, changes the size of the XUL window so that the
+ * content area reaches a particular size.
+ *
+ * We need to supply the content area dimensions because sometimes
+ * the child's nsDocShellTreeOwner needs to propagate a SizeShellTo
+ * call to the parent. But the shellItem argument of the call will
+ * not be available on the parent side.
+ *
+ * Note: this is an internal method, other consumers should never call this.
+ *
+ * @param aDesiredWidth
+ * The desired width of the content area in device pixels.
+ * @param aDesiredHeight
+ * The desired height of the content area in device pixels.
+ * @param shellItemWidth
+ * The current width of the content area.
+ * @param shellItemHeight
+ * The current height of the content area.
+ */
+ [noscript, notxpcom] void sizeShellToWithLimit(in int32_t aDesiredWidth,
+ in int32_t aDesiredHeight,
+ in int32_t shellItemWidth,
+ in int32_t shellItemHeight);
+};
diff --git a/components/appshell/src/nsAppShellCID.h b/components/appshell/src/nsAppShellCID.h
new file mode 100644
index 000000000..2b0edd4ac
--- /dev/null
+++ b/components/appshell/src/nsAppShellCID.h
@@ -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/. */
+
+#ifndef nsAppShellCID_h__
+#define nsAppShellCID_h__
+
+#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
+
+#endif
diff --git a/components/appshell/src/nsAppShellFactory.cpp b/components/appshell/src/nsAppShellFactory.cpp
new file mode 100644
index 000000000..0b6d3c47a
--- /dev/null
+++ b/components/appshell/src/nsAppShellFactory.cpp
@@ -0,0 +1,56 @@
+/* -*- 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/ModuleUtils.h"
+#include "nscore.h"
+#include "nsIWindowMediator.h"
+
+#include "nsIAppShellService.h"
+#include "nsAppShellService.h"
+#include "nsWindowMediator.h"
+#include "nsChromeTreeOwner.h"
+#include "nsAppShellCID.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAppShellService)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowMediator, Init)
+
+NS_DEFINE_NAMED_CID(NS_APPSHELLSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_WINDOWMEDIATOR_CID);
+
+static const mozilla::Module::CIDEntry kAppShellCIDs[] = {
+ { &kNS_APPSHELLSERVICE_CID, false, nullptr, nsAppShellServiceConstructor },
+ { &kNS_WINDOWMEDIATOR_CID, false, nullptr, nsWindowMediatorConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kAppShellContracts[] = {
+ { NS_APPSHELLSERVICE_CONTRACTID, &kNS_APPSHELLSERVICE_CID },
+ { NS_WINDOWMEDIATOR_CONTRACTID, &kNS_WINDOWMEDIATOR_CID },
+ { nullptr }
+};
+
+static nsresult
+nsAppShellModuleConstructor()
+{
+ return nsChromeTreeOwner::InitGlobals();
+}
+
+static void
+nsAppShellModuleDestructor()
+{
+ nsChromeTreeOwner::FreeGlobals();
+}
+
+static const mozilla::Module kAppShellModule = {
+ mozilla::Module::kVersion,
+ kAppShellCIDs,
+ kAppShellContracts,
+ nullptr,
+ nullptr,
+ nsAppShellModuleConstructor,
+ nsAppShellModuleDestructor
+};
+
+NSMODULE_DEFN(appshell) = &kAppShellModule;
diff --git a/components/appshell/src/nsAppShellService.cpp b/components/appshell/src/nsAppShellService.cpp
new file mode 100644
index 000000000..427428fdf
--- /dev/null
+++ b/components/appshell/src/nsAppShellService.cpp
@@ -0,0 +1,942 @@
+/* -*- 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 "nsIAppShellService.h"
+#include "nsIComponentManager.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIServiceManager.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsIXPConnect.h"
+#include "nsIXULRuntime.h"
+
+#include "nsIWindowMediator.h"
+#include "nsIWindowWatcher.h"
+#include "nsPIWindowWatcher.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsWebShellWindow.h"
+
+#include "prprf.h"
+
+#include "nsWidgetInitData.h"
+#include "nsWidgetsCID.h"
+#include "nsIWidget.h"
+#include "nsIRequestObserver.h"
+#include "nsIEmbeddingSiteWindow.h"
+
+#include "nsAppShellService.h"
+#include "nsContentUtils.h"
+#include "nsThreadUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIChromeRegistry.h"
+#include "nsILoadContext.h"
+#include "nsIWebNavigation.h"
+#include "nsIWindowlessBrowser.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StartupTimeline.h"
+
+#include "nsEmbedCID.h"
+#include "nsIWebBrowser.h"
+#include "nsIDocShell.h"
+
+#ifdef MOZ_INSTRUMENT_EVENT_LOOP
+#include "EventTracer.h"
+#endif
+
+using namespace mozilla;
+
+// Default URL for the hidden window, can be overridden by a pref on Mac
+#define DEFAULT_HIDDENWINDOW_URL "resource://gre-resources/hiddenWindow.html"
+
+class nsIAppShell;
+
+nsAppShellService::nsAppShellService() :
+ mXPCOMWillShutDown(false),
+ mXPCOMShuttingDown(false),
+ mModalWindowCount(0),
+ mApplicationProvidedHiddenWindow(false),
+ mScreenId(0)
+{
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+
+ if (obs) {
+ obs->AddObserver(this, "xpcom-will-shutdown", false);
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ }
+}
+
+nsAppShellService::~nsAppShellService()
+{
+}
+
+
+/*
+ * Implement the nsISupports methods...
+ */
+NS_IMPL_ISUPPORTS(nsAppShellService,
+ nsIAppShellService,
+ nsIObserver)
+
+NS_IMETHODIMP
+nsAppShellService::CreateHiddenWindow()
+{
+ return CreateHiddenWindowHelper(false);
+}
+
+NS_IMETHODIMP
+nsAppShellService::SetScreenId(uint32_t aScreenId)
+{
+ mScreenId = aScreenId;
+ return NS_OK;
+}
+
+void
+nsAppShellService::EnsurePrivateHiddenWindow()
+{
+ if (!mHiddenPrivateWindow) {
+ CreateHiddenWindowHelper(true);
+ }
+}
+
+nsresult
+nsAppShellService::CreateHiddenWindowHelper(bool aIsPrivate)
+{
+ nsresult rv;
+ int32_t initialHeight = 100, initialWidth = 100;
+
+ static const char hiddenWindowURL[] = DEFAULT_HIDDENWINDOW_URL;
+ uint32_t chromeMask = nsIWebBrowserChrome::CHROME_ALL;
+
+ nsCOMPtr<nsIURI> url;
+ rv = NS_NewURI(getter_AddRefs(url), hiddenWindowURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsWebShellWindow> newWindow;
+ if (!aIsPrivate) {
+ rv = JustCreateTopWindow(nullptr, url,
+ chromeMask, initialWidth, initialHeight,
+ true, nullptr, nullptr, getter_AddRefs(newWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHiddenWindow.swap(newWindow);
+ } else {
+ // Create the hidden private window
+ chromeMask |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
+
+ rv = JustCreateTopWindow(nullptr, url,
+ chromeMask, initialWidth, initialHeight,
+ true, nullptr, nullptr, getter_AddRefs(newWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocShell> docShell;
+ newWindow->GetDocShell(getter_AddRefs(docShell));
+ if (docShell) {
+ docShell->SetAffectPrivateSessionLifetime(false);
+ }
+
+ mHiddenPrivateWindow.swap(newWindow);
+ }
+
+ // RegisterTopLevelWindow(newWindow); -- Mac only
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppShellService::DestroyHiddenWindow()
+{
+ if (mHiddenWindow) {
+ mHiddenWindow->Destroy();
+
+ mHiddenWindow = nullptr;
+ }
+
+ if (mHiddenPrivateWindow) {
+ mHiddenPrivateWindow->Destroy();
+
+ mHiddenPrivateWindow = nullptr;
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Create a new top level window and display the given URL within it...
+ */
+NS_IMETHODIMP
+nsAppShellService::CreateTopLevelWindow(nsIXULWindow *aParent,
+ nsIURI *aUrl,
+ uint32_t aChromeMask,
+ int32_t aInitialWidth,
+ int32_t aInitialHeight,
+ nsITabParent *aOpeningTab,
+ mozIDOMWindowProxy *aOpenerWindow,
+ nsIXULWindow **aResult)
+
+{
+ nsresult rv;
+
+ StartupTimeline::RecordOnce(StartupTimeline::CREATE_TOP_LEVEL_WINDOW);
+
+ RefPtr<nsWebShellWindow> newWindow;
+ rv = JustCreateTopWindow(aParent, aUrl,
+ aChromeMask, aInitialWidth, aInitialHeight,
+ false, aOpeningTab, aOpenerWindow,
+ getter_AddRefs(newWindow));
+ newWindow.forget(aResult);
+
+ if (NS_SUCCEEDED(rv)) {
+ // the addref resulting from this is the owning addref for this window
+ RegisterTopLevelWindow(*aResult);
+ nsCOMPtr<nsIXULWindow> parent;
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_DEPENDENT)
+ parent = aParent;
+ (*aResult)->SetZLevel(CalculateWindowZLevel(parent, aChromeMask));
+ }
+
+ return rv;
+}
+
+/*
+ * This class provides a stub implementation of nsIWebBrowserChrome2, as needed
+ * by nsAppShellService::CreateWindowlessBrowser
+ */
+class WebBrowserChrome2Stub : public nsIWebBrowserChrome2,
+ public nsIEmbeddingSiteWindow,
+ public nsIInterfaceRequestor,
+ public nsSupportsWeakReference {
+protected:
+ virtual ~WebBrowserChrome2Stub() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBBROWSERCHROME
+ NS_DECL_NSIWEBBROWSERCHROME2
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIEMBEDDINGSITEWINDOW
+};
+
+NS_INTERFACE_MAP_BEGIN(WebBrowserChrome2Stub)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserChrome)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome2)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIEmbeddingSiteWindow)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(WebBrowserChrome2Stub)
+NS_IMPL_RELEASE(WebBrowserChrome2Stub)
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::SetStatus(uint32_t aStatusType, const char16_t* aStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::GetWebBrowser(nsIWebBrowser** aWebBrowser)
+{
+ NS_NOTREACHED("WebBrowserChrome2Stub::GetWebBrowser is not supported");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::SetWebBrowser(nsIWebBrowser* aWebBrowser)
+{
+ NS_NOTREACHED("WebBrowserChrome2Stub::SetWebBrowser is not supported");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::GetChromeFlags(uint32_t* aChromeFlags)
+{
+ *aChromeFlags = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::SetChromeFlags(uint32_t aChromeFlags)
+{
+ NS_NOTREACHED("WebBrowserChrome2Stub::SetChromeFlags is not supported");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::DestroyBrowserWindow()
+{
+ NS_NOTREACHED("WebBrowserChrome2Stub::DestroyBrowserWindow is not supported");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::SizeBrowserTo(int32_t aCX, int32_t aCY)
+{
+ NS_NOTREACHED("WebBrowserChrome2Stub::SizeBrowserTo is not supported");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::ShowAsModal()
+{
+ NS_NOTREACHED("WebBrowserChrome2Stub::ShowAsModal is not supported");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::IsWindowModal(bool* aResult)
+{
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::ExitModalEventLoop(nsresult aStatus)
+{
+ NS_NOTREACHED("WebBrowserChrome2Stub::ExitModalEventLoop is not supported");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::SetStatusWithContext(uint32_t aStatusType,
+ const nsAString& aStatusText,
+ nsISupports* aStatusContext)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::GetInterface(const nsIID& aIID, void** aSink)
+{
+ return QueryInterface(aIID, aSink);
+}
+
+// nsIEmbeddingSiteWindow impl
+NS_IMETHODIMP
+WebBrowserChrome2Stub::GetDimensions(uint32_t flags, int32_t* x, int32_t* y, int32_t* cx, int32_t* cy)
+{
+ if (x) {
+ *x = 0;
+ }
+
+ if (y) {
+ *y = 0;
+ }
+
+ if (cx) {
+ *cx = 0;
+ }
+
+ if (cy) {
+ *cy = 0;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::SetDimensions(uint32_t flags, int32_t x, int32_t y, int32_t cx, int32_t cy)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::SetFocus()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::GetVisibility(bool* aVisibility)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+WebBrowserChrome2Stub::SetVisibility(bool aVisibility)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::GetTitle(char16_t** aTitle)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+WebBrowserChrome2Stub::SetTitle(const char16_t* aTitle)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::GetSiteWindow(void** aSiteWindow)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+WebBrowserChrome2Stub::Blur()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+class BrowserDestroyer final : public Runnable
+{
+public:
+ BrowserDestroyer(nsIWebBrowser *aBrowser, nsISupports *aContainer) :
+ mBrowser(aBrowser),
+ mContainer(aContainer)
+ {
+ }
+
+ NS_IMETHOD
+ Run() override
+ {
+ // Explicitly destroy the browser, in case this isn't the last reference.
+ nsCOMPtr<nsIBaseWindow> window = do_QueryInterface(mBrowser);
+ return window->Destroy();
+ }
+
+protected:
+ virtual ~BrowserDestroyer() {}
+
+private:
+ nsCOMPtr<nsIWebBrowser> mBrowser;
+ nsCOMPtr<nsISupports> mContainer;
+};
+
+// This is the "stub" we return from CreateWindowlessBrowser - it exists
+// to manage the lifetimes of the nsIWebBrowser and container window.
+// In particular, it keeps a strong reference to both, to prevent them from
+// being collected while this object remains alive, and ensures that they
+// aren't destroyed when it's not safe to run scripts.
+class WindowlessBrowser final : public nsIWindowlessBrowser,
+ public nsIInterfaceRequestor
+{
+public:
+ WindowlessBrowser(nsIWebBrowser *aBrowser, nsISupports *aContainer) :
+ mBrowser(aBrowser),
+ mContainer(aContainer),
+ mClosed(false)
+ {
+ mWebNavigation = do_QueryInterface(aBrowser);
+ mInterfaceRequestor = do_QueryInterface(aBrowser);
+ }
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIWEBNAVIGATION(mWebNavigation)
+ NS_FORWARD_SAFE_NSIINTERFACEREQUESTOR(mInterfaceRequestor)
+
+ NS_IMETHOD
+ Close() override
+ {
+ NS_ENSURE_TRUE(!mClosed, NS_ERROR_UNEXPECTED);
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
+ "WindowlessBrowser::Close called when not safe to run scripts");
+
+ mClosed = true;
+
+ mWebNavigation = nullptr;
+ mInterfaceRequestor = nullptr;
+
+ nsCOMPtr<nsIBaseWindow> window = do_QueryInterface(mBrowser);
+ return window->Destroy();
+ }
+
+protected:
+ virtual ~WindowlessBrowser()
+ {
+ if (mClosed) {
+ return;
+ }
+
+ NS_WARNING("Windowless browser was not closed prior to destruction");
+
+ // The docshell destructor needs to dispatch events, and can only run
+ // when it's safe to run scripts. If this was triggered by GC, it may
+ // not always be safe to run scripts, in which cases we need to delay
+ // destruction until it is.
+ nsCOMPtr<nsIRunnable> runnable = new BrowserDestroyer(mBrowser, mContainer);
+ nsContentUtils::AddScriptRunner(runnable);
+ }
+
+private:
+ nsCOMPtr<nsIWebBrowser> mBrowser;
+ nsCOMPtr<nsIWebNavigation> mWebNavigation;
+ nsCOMPtr<nsIInterfaceRequestor> mInterfaceRequestor;
+ // we don't use the container but just hold a reference to it.
+ nsCOMPtr<nsISupports> mContainer;
+
+ bool mClosed;
+};
+
+NS_IMPL_ISUPPORTS(WindowlessBrowser, nsIWindowlessBrowser, nsIWebNavigation, nsIInterfaceRequestor)
+
+
+NS_IMETHODIMP
+nsAppShellService::CreateWindowlessBrowser(bool aIsChrome, nsIWindowlessBrowser **aResult)
+{
+ /* First, we create an instance of nsWebBrowser. Instances of this class have
+ * an associated doc shell, which is what we're interested in.
+ */
+ nsCOMPtr<nsIWebBrowser> browser = do_CreateInstance(NS_WEBBROWSER_CONTRACTID);
+ if (!browser) {
+ NS_ERROR("Couldn't create instance of nsWebBrowser!");
+ return NS_ERROR_FAILURE;
+ }
+
+ /* Next, we set the container window for our instance of nsWebBrowser. Since
+ * we don't actually have a window, we instead set the container window to be
+ * an instance of WebBrowserChrome2Stub, which provides a stub implementation
+ * of nsIWebBrowserChrome2.
+ */
+ RefPtr<WebBrowserChrome2Stub> stub = new WebBrowserChrome2Stub();
+ browser->SetContainerWindow(stub);
+
+ nsCOMPtr<nsIWebNavigation> navigation = do_QueryInterface(browser);
+
+ nsCOMPtr<nsIDocShellTreeItem> item = do_QueryInterface(navigation);
+ item->SetItemType(aIsChrome ? nsIDocShellTreeItem::typeChromeWrapper
+ : nsIDocShellTreeItem::typeContentWrapper);
+
+ /* A windowless web browser doesn't have an associated OS level window. To
+ * accomplish this, we initialize the window associated with our instance of
+ * nsWebBrowser with an instance of PuppetWidget, which provides a stub
+ * implementation of nsIWidget.
+ */
+ nsCOMPtr<nsIWidget> widget = nsIWidget::CreatePuppetWidget(nullptr);
+ if (!widget) {
+ NS_ERROR("Couldn't create instance of PuppetWidget");
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv =
+ widget->Create(nullptr, 0, LayoutDeviceIntRect(0, 0, 0, 0), nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIBaseWindow> window = do_QueryInterface(navigation);
+ window->InitWindow(0, widget, 0, 0, 0, 0);
+ window->Create();
+
+ nsISupports *isstub = NS_ISUPPORTS_CAST(nsIWebBrowserChrome2*, stub);
+ RefPtr<nsIWindowlessBrowser> result = new WindowlessBrowser(browser, isstub);
+ nsCOMPtr<nsIDocShell> docshell = do_GetInterface(result);
+ docshell->SetInvisible(true);
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+uint32_t
+nsAppShellService::CalculateWindowZLevel(nsIXULWindow *aParent,
+ uint32_t aChromeMask)
+{
+ uint32_t zLevel;
+
+ zLevel = nsIXULWindow::normalZ;
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_RAISED)
+ zLevel = nsIXULWindow::raisedZ;
+ else if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_LOWERED)
+ zLevel = nsIXULWindow::loweredZ;
+
+ /* Platforms with native support for dependent windows (that's everyone
+ but pre-Mac OS X, right?) know how to stack dependent windows. On these
+ platforms, give the dependent window the same level as its parent,
+ so we won't try to override the normal platform behaviour. */
+ if ((aChromeMask & nsIWebBrowserChrome::CHROME_DEPENDENT) && aParent)
+ aParent->GetZLevel(&zLevel);
+
+ return zLevel;
+}
+
+#ifdef XP_WIN
+/*
+ * Checks to see if any existing window is currently in fullscreen mode.
+ */
+static bool
+CheckForFullscreenWindow()
+{
+ nsCOMPtr<nsIWindowMediator> wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (!wm)
+ return false;
+
+ nsCOMPtr<nsISimpleEnumerator> windowList;
+ wm->GetXULWindowEnumerator(nullptr, getter_AddRefs(windowList));
+ if (!windowList)
+ return false;
+
+ for (;;) {
+ bool more = false;
+ windowList->HasMoreElements(&more);
+ if (!more)
+ return false;
+
+ nsCOMPtr<nsISupports> supportsWindow;
+ windowList->GetNext(getter_AddRefs(supportsWindow));
+ nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(supportsWindow));
+ if (baseWin) {
+ nsCOMPtr<nsIWidget> widget;
+ baseWin->GetMainWidget(getter_AddRefs(widget));
+ if (widget && widget->SizeMode() == nsSizeMode_Fullscreen) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+#endif
+
+/*
+ * Just do the window-making part of CreateTopLevelWindow
+ */
+nsresult
+nsAppShellService::JustCreateTopWindow(nsIXULWindow *aParent,
+ nsIURI *aUrl,
+ uint32_t aChromeMask,
+ int32_t aInitialWidth,
+ int32_t aInitialHeight,
+ bool aIsHiddenWindow,
+ nsITabParent *aOpeningTab,
+ mozIDOMWindowProxy *aOpenerWindow,
+ nsWebShellWindow **aResult)
+{
+ *aResult = nullptr;
+ NS_ENSURE_STATE(!mXPCOMWillShutDown);
+
+ nsCOMPtr<nsIXULWindow> parent;
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_DEPENDENT)
+ parent = aParent;
+
+ RefPtr<nsWebShellWindow> window = new nsWebShellWindow(aChromeMask);
+
+#ifdef XP_WIN
+ // If the parent is currently fullscreen, tell the child to ignore persisted
+ // full screen states. This way new browser windows open on top of fullscreen
+ // windows normally.
+ if (window && CheckForFullscreenWindow())
+ window->IgnoreXULSizeMode(true);
+#endif
+
+ nsWidgetInitData widgetInitData;
+
+ if (aIsHiddenWindow)
+ widgetInitData.mWindowType = eWindowType_invisible;
+ else
+ widgetInitData.mWindowType = aChromeMask & nsIWebBrowserChrome::CHROME_OPENAS_DIALOG ?
+ eWindowType_dialog : eWindowType_toplevel;
+
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_POPUP)
+ widgetInitData.mWindowType = eWindowType_popup;
+
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_MAC_SUPPRESS_ANIMATION)
+ widgetInitData.mIsAnimationSuppressed = true;
+
+#if defined(XP_WIN)
+ if (widgetInitData.mWindowType == eWindowType_toplevel ||
+ widgetInitData.mWindowType == eWindowType_dialog)
+ widgetInitData.clipChildren = true;
+#endif
+
+ // note default chrome overrides other OS chrome settings, but
+ // not internal chrome
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_DEFAULT)
+ widgetInitData.mBorderStyle = eBorderStyle_default;
+ else if ((aChromeMask & nsIWebBrowserChrome::CHROME_ALL) == nsIWebBrowserChrome::CHROME_ALL)
+ widgetInitData.mBorderStyle = eBorderStyle_all;
+ else {
+ widgetInitData.mBorderStyle = eBorderStyle_none; // assumes none == 0x00
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_BORDERS)
+ widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_border);
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_TITLEBAR)
+ widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_title);
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_CLOSE)
+ widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_close);
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) {
+ widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_resizeh);
+ // only resizable windows get the maximize button (but not dialogs)
+ if (!(aChromeMask & nsIWebBrowserChrome::CHROME_OPENAS_DIALOG))
+ widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_maximize);
+ }
+ // all windows (except dialogs) get minimize buttons and the system menu
+ if (!(aChromeMask & nsIWebBrowserChrome::CHROME_OPENAS_DIALOG))
+ widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_minimize | eBorderStyle_menu);
+ // but anyone can explicitly ask for a minimize button
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_MIN) {
+ widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_minimize);
+ }
+ }
+
+ if (aInitialWidth == nsIAppShellService::SIZE_TO_CONTENT ||
+ aInitialHeight == nsIAppShellService::SIZE_TO_CONTENT) {
+ aInitialWidth = 1;
+ aInitialHeight = 1;
+ window->SetIntrinsicallySized(true);
+ }
+
+ bool center = aChromeMask & nsIWebBrowserChrome::CHROME_CENTER_SCREEN;
+
+ nsCOMPtr<nsIXULChromeRegistry> reg =
+ mozilla::services::GetXULChromeRegistryService();
+ if (reg) {
+ nsAutoCString package;
+ package.AssignLiteral("global");
+ bool isRTL = false;
+ reg->IsLocaleRTL(package, &isRTL);
+ widgetInitData.mRTL = isRTL;
+ }
+
+ nsresult rv = window->Initialize(parent, center ? aParent : nullptr,
+ aUrl, aInitialWidth, aInitialHeight,
+ aIsHiddenWindow, aOpeningTab,
+ aOpenerWindow, widgetInitData);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Enforce the Private Browsing autoStart pref first.
+ bool isPrivateBrowsingWindow =
+ Preferences::GetBool("browser.privatebrowsing.autostart");
+
+ if (aChromeMask & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW) {
+ // Caller requested a private window
+ isPrivateBrowsingWindow = true;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWin = do_GetInterface(aParent);
+ nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(domWin);
+ nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(webNav);
+
+ if (!isPrivateBrowsingWindow && parentContext) {
+ // Ensure that we propagate any existing private browsing status
+ // from the parent, even if it will not actually be used
+ // as a parent value.
+ isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing();
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> newDomWin =
+ do_GetInterface(NS_ISUPPORTS_CAST(nsIBaseWindow*, window));
+ nsCOMPtr<nsIWebNavigation> newWebNav = do_GetInterface(newDomWin);
+ nsCOMPtr<nsILoadContext> thisContext = do_GetInterface(newWebNav);
+ if (thisContext) {
+ thisContext->SetPrivateBrowsing(isPrivateBrowsingWindow);
+ }
+
+ window.forget(aResult);
+ if (parent)
+ parent->AddChildWindow(*aResult);
+
+ if (center)
+ rv = (*aResult)->Center(parent, parent ? false : true, false);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAppShellService::GetHiddenWindow(nsIXULWindow **aWindow)
+{
+ NS_ENSURE_ARG_POINTER(aWindow);
+
+ *aWindow = mHiddenWindow;
+ NS_IF_ADDREF(*aWindow);
+ return *aWindow ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsAppShellService::GetHiddenDOMWindow(mozIDOMWindowProxy **aWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDocShell> docShell;
+ NS_ENSURE_TRUE(mHiddenWindow, NS_ERROR_FAILURE);
+
+ rv = mHiddenWindow->GetDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowOuter> hiddenDOMWindow(docShell->GetWindow());
+ hiddenDOMWindow.forget(aWindow);
+ return *aWindow ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsAppShellService::GetHiddenPrivateWindow(nsIXULWindow **aWindow)
+{
+ NS_ENSURE_ARG_POINTER(aWindow);
+
+ EnsurePrivateHiddenWindow();
+
+ *aWindow = mHiddenPrivateWindow;
+ NS_IF_ADDREF(*aWindow);
+ return *aWindow ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsAppShellService::GetHiddenPrivateDOMWindow(mozIDOMWindowProxy **aWindow)
+{
+ EnsurePrivateHiddenWindow();
+
+ nsresult rv;
+ nsCOMPtr<nsIDocShell> docShell;
+ NS_ENSURE_TRUE(mHiddenPrivateWindow, NS_ERROR_FAILURE);
+
+ rv = mHiddenPrivateWindow->GetDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowOuter> hiddenPrivateDOMWindow(docShell->GetWindow());
+ hiddenPrivateDOMWindow.forget(aWindow);
+ return *aWindow ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsAppShellService::GetHasHiddenPrivateWindow(bool* aHasPrivateWindow)
+{
+ NS_ENSURE_ARG_POINTER(aHasPrivateWindow);
+
+ *aHasPrivateWindow = !!mHiddenPrivateWindow;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppShellService::GetApplicationProvidedHiddenWindow(bool* aAPHW)
+{
+ *aAPHW = mApplicationProvidedHiddenWindow;
+ return NS_OK;
+}
+
+/*
+ * Register a new top level window (created elsewhere)
+ */
+NS_IMETHODIMP
+nsAppShellService::RegisterTopLevelWindow(nsIXULWindow* aWindow)
+{
+ NS_ENSURE_ARG_POINTER(aWindow);
+
+ nsCOMPtr<nsIDocShell> docShell;
+ aWindow->GetDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow(docShell->GetWindow());
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ domWindow->SetInitialPrincipalToSubject();
+
+ // tell the window mediator about the new window
+ nsCOMPtr<nsIWindowMediator> mediator
+ ( do_GetService(NS_WINDOWMEDIATOR_CONTRACTID) );
+ NS_ASSERTION(mediator, "Couldn't get window mediator.");
+
+ if (mediator)
+ mediator->RegisterWindow(aWindow);
+
+ // tell the window watcher about the new window
+ nsCOMPtr<nsPIWindowWatcher> wwatcher ( do_GetService(NS_WINDOWWATCHER_CONTRACTID) );
+ NS_ASSERTION(wwatcher, "No windowwatcher?");
+ if (wwatcher && domWindow) {
+ wwatcher->AddWindow(domWindow, 0);
+ }
+
+ // an ongoing attempt to quit is stopped by a newly opened window
+ nsCOMPtr<nsIObserverService> obssvc = services::GetObserverService();
+ NS_ASSERTION(obssvc, "Couldn't get observer service.");
+
+ if (obssvc) {
+ obssvc->NotifyObservers(aWindow, "xul-window-registered", nullptr);
+ nsXULWindow* xulWindow = static_cast<nsXULWindow*>(aWindow);
+ xulWindow->WasRegistered();
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAppShellService::UnregisterTopLevelWindow(nsIXULWindow* aWindow)
+{
+ if (mXPCOMShuttingDown) {
+ /* return an error code in order to:
+ - avoid doing anything with other member variables while we are in
+ the destructor
+ - notify the caller not to release the AppShellService after
+ unregistering the window
+ (we don't want to be deleted twice consecutively to
+ mHiddenWindow->Destroy() in our destructor)
+ */
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ENSURE_ARG_POINTER(aWindow);
+
+ if (aWindow == mHiddenWindow) {
+ // CreateHiddenWindow() does not register the window, so we're done.
+ return NS_OK;
+ }
+ if (aWindow == mHiddenPrivateWindow) {
+ // CreateHiddenWindow() does not register the window, so we're done.
+ return NS_OK;
+ }
+
+ // tell the window mediator
+ nsCOMPtr<nsIWindowMediator> mediator
+ ( do_GetService(NS_WINDOWMEDIATOR_CONTRACTID) );
+ NS_ASSERTION(mediator, "Couldn't get window mediator. Doing xpcom shutdown?");
+
+ if (mediator)
+ mediator->UnregisterWindow(aWindow);
+
+ // tell the window watcher
+ nsCOMPtr<nsPIWindowWatcher> wwatcher ( do_GetService(NS_WINDOWWATCHER_CONTRACTID) );
+ NS_ASSERTION(wwatcher, "Couldn't get windowwatcher, doing xpcom shutdown?");
+ if (wwatcher) {
+ nsCOMPtr<nsIDocShell> docShell;
+ aWindow->GetDocShell(getter_AddRefs(docShell));
+ if (docShell) {
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow(docShell->GetWindow());
+ if (domWindow)
+ wwatcher->RemoveWindow(domWindow);
+ }
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAppShellService::Observe(nsISupports* aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ if (!strcmp(aTopic, "xpcom-will-shutdown")) {
+ mXPCOMWillShutDown = true;
+ } else if (!strcmp(aTopic, "xpcom-shutdown")) {
+ mXPCOMShuttingDown = true;
+ if (mHiddenWindow) {
+ mHiddenWindow->Destroy();
+ }
+ if (mHiddenPrivateWindow) {
+ mHiddenPrivateWindow->Destroy();
+ }
+ } else {
+ NS_ERROR("Unexpected observer topic!");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppShellService::StartEventLoopLagTracking(bool* aResult)
+{
+#ifdef MOZ_INSTRUMENT_EVENT_LOOP
+ *aResult = mozilla::InitEventTracing(true);
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppShellService::StopEventLoopLagTracking()
+{
+#ifdef MOZ_INSTRUMENT_EVENT_LOOP
+ mozilla::ShutdownEventTracing();
+#endif
+ return NS_OK;
+}
diff --git a/components/appshell/src/nsAppShellService.h b/components/appshell/src/nsAppShellService.h
new file mode 100644
index 000000000..feb7b9947
--- /dev/null
+++ b/components/appshell/src/nsAppShellService.h
@@ -0,0 +1,58 @@
+/* -*- 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 __nsAppShellService_h
+#define __nsAppShellService_h
+
+#include "nsIAppShellService.h"
+#include "nsIObserver.h"
+
+//Interfaces Needed
+#include "nsWebShellWindow.h"
+#include "nsStringFwd.h"
+#include "nsAutoPtr.h"
+#include "nsITabParent.h"
+#include "mozilla/Attributes.h"
+
+// {0099907D-123C-4853-A46A-43098B5FB68C}
+#define NS_APPSHELLSERVICE_CID \
+{ 0x99907d, 0x123c, 0x4853, { 0xa4, 0x6a, 0x43, 0x9, 0x8b, 0x5f, 0xb6, 0x8c } }
+
+class nsAppShellService final : public nsIAppShellService,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAPPSHELLSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsAppShellService();
+
+protected:
+ ~nsAppShellService();
+
+ nsresult CreateHiddenWindowHelper(bool aIsPrivate);
+ void EnsurePrivateHiddenWindow();
+
+ nsresult JustCreateTopWindow(nsIXULWindow *aParent,
+ nsIURI *aUrl,
+ uint32_t aChromeMask,
+ int32_t aInitialWidth, int32_t aInitialHeight,
+ bool aIsHiddenWindow,
+ nsITabParent *aOpeningTab,
+ mozIDOMWindowProxy *aOpenerWindow,
+ nsWebShellWindow **aResult);
+ uint32_t CalculateWindowZLevel(nsIXULWindow *aParent, uint32_t aChromeMask);
+
+ RefPtr<nsWebShellWindow> mHiddenWindow;
+ RefPtr<nsWebShellWindow> mHiddenPrivateWindow;
+ bool mXPCOMWillShutDown;
+ bool mXPCOMShuttingDown;
+ uint16_t mModalWindowCount;
+ bool mApplicationProvidedHiddenWindow;
+ uint32_t mScreenId;
+};
+
+#endif
diff --git a/components/appshell/src/nsAppShellWindowEnumerator.cpp b/components/appshell/src/nsAppShellWindowEnumerator.cpp
new file mode 100644
index 000000000..dc5fc120a
--- /dev/null
+++ b/components/appshell/src/nsAppShellWindowEnumerator.cpp
@@ -0,0 +1,495 @@
+/* -*- 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 "nsAppShellWindowEnumerator.h"
+
+#include "nsIContentViewer.h"
+#include "nsIDocShell.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMWindow.h"
+#include "nsIFactory.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIXULWindow.h"
+
+#include "nsWindowMediator.h"
+
+//
+// static helper functions
+//
+
+static nsCOMPtr<nsIDOMNode> GetDOMNodeFromDocShell(nsIDocShell *aShell);
+static void GetAttribute(nsIXULWindow *inWindow, const nsAString &inAttribute,
+ nsAString &outValue);
+static void GetWindowType(nsIXULWindow* inWindow, nsString &outType);
+
+nsCOMPtr<nsIDOMNode> GetDOMNodeFromDocShell(nsIDocShell *aShell)
+{
+ nsCOMPtr<nsIDOMNode> node;
+
+ nsCOMPtr<nsIContentViewer> cv;
+ aShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(cv->GetDocument()));
+ if (domdoc) {
+ nsCOMPtr<nsIDOMElement> element;
+ domdoc->GetDocumentElement(getter_AddRefs(element));
+ if (element)
+ node = element;
+ }
+ }
+
+ return node;
+}
+
+// generic "retrieve the value of a XUL attribute" function
+void GetAttribute(nsIXULWindow *inWindow, const nsAString &inAttribute,
+ nsAString &outValue)
+{
+ nsCOMPtr<nsIDocShell> shell;
+ if (inWindow && NS_SUCCEEDED(inWindow->GetDocShell(getter_AddRefs(shell)))) {
+ nsCOMPtr<nsIDOMNode> node(GetDOMNodeFromDocShell(shell));
+ if (node) {
+ nsCOMPtr<nsIDOMElement> webshellElement(do_QueryInterface(node));
+ if (webshellElement)
+ webshellElement->GetAttribute(inAttribute, outValue);
+ }
+ }
+}
+
+// retrieve the window type, stored as the value of a particular
+// attribute in its XUL window tag
+void GetWindowType(nsIXULWindow* aWindow, nsString &outType)
+{
+ GetAttribute(aWindow, NS_LITERAL_STRING("windowtype"), outType);
+}
+
+//
+// nsWindowInfo
+//
+
+nsWindowInfo::nsWindowInfo(nsIXULWindow* inWindow, int32_t inTimeStamp) :
+ mWindow(inWindow),mTimeStamp(inTimeStamp),mZLevel(nsIXULWindow::normalZ)
+{
+ ReferenceSelf(true, true);
+}
+
+nsWindowInfo::~nsWindowInfo()
+{
+}
+
+// return true if the window described by this WindowInfo has a type
+// equal to the given type
+bool nsWindowInfo::TypeEquals(const nsAString &aType)
+{
+ nsAutoString rtnString;
+ GetWindowType(mWindow, rtnString);
+ return rtnString == aType;
+}
+
+// insert the struct into their two linked lists, in position after the
+// given (independent) method arguments
+void nsWindowInfo::InsertAfter(nsWindowInfo *inOlder , nsWindowInfo *inHigher)
+{
+ if (inOlder) {
+ mOlder = inOlder;
+ mYounger = inOlder->mYounger;
+ mOlder->mYounger = this;
+ if (mOlder->mOlder == mOlder)
+ mOlder->mOlder = this;
+ mYounger->mOlder = this;
+ if (mYounger->mYounger == mYounger)
+ mYounger->mYounger = this;
+ }
+ if (inHigher) {
+ mHigher = inHigher;
+ mLower = inHigher->mLower;
+ mHigher->mLower = this;
+ if (mHigher->mHigher == mHigher)
+ mHigher->mHigher = this;
+ mLower->mHigher = this;
+ if (mLower->mLower == mLower)
+ mLower->mLower = this;
+ }
+}
+
+// remove the struct from its linked lists
+void nsWindowInfo::Unlink(bool inAge, bool inZ)
+{
+ if (inAge) {
+ mOlder->mYounger = mYounger;
+ mYounger->mOlder = mOlder;
+ }
+ if (inZ) {
+ mLower->mHigher = mHigher;
+ mHigher->mLower = mLower;
+ }
+ ReferenceSelf(inAge, inZ);
+}
+
+// initialize the struct to be a valid linked list of one element
+void nsWindowInfo::ReferenceSelf(bool inAge, bool inZ)
+{
+ if (inAge) {
+ mYounger = this;
+ mOlder = this;
+ }
+ if (inZ) {
+ mLower = this;
+ mHigher = this;
+ }
+}
+
+//
+// nsAppShellWindowEnumerator
+//
+
+NS_IMPL_ISUPPORTS(nsAppShellWindowEnumerator, nsISimpleEnumerator)
+
+nsAppShellWindowEnumerator::nsAppShellWindowEnumerator(
+ const char16_t* aTypeString,
+ nsWindowMediator& aMediator) :
+ mWindowMediator(&aMediator), mType(aTypeString), mCurrentPosition(nullptr)
+{
+ mWindowMediator->AddEnumerator(this);
+ NS_ADDREF(mWindowMediator);
+}
+
+nsAppShellWindowEnumerator::~nsAppShellWindowEnumerator()
+{
+ mWindowMediator->RemoveEnumerator(this);
+ NS_RELEASE(mWindowMediator);
+}
+
+// after mCurrentPosition has been initialized to point to the beginning
+// of the appropriate list, adjust it if necessary
+void nsAppShellWindowEnumerator::AdjustInitialPosition()
+{
+ if (!mType.IsEmpty() && mCurrentPosition && !mCurrentPosition->TypeEquals(mType))
+ mCurrentPosition = FindNext();
+}
+
+NS_IMETHODIMP nsAppShellWindowEnumerator::HasMoreElements(bool *retval)
+{
+ if (!retval)
+ return NS_ERROR_INVALID_ARG;
+
+ *retval = mCurrentPosition ? true : false;
+ return NS_OK;
+}
+
+// if a window is being removed adjust the iterator's current position
+void nsAppShellWindowEnumerator::WindowRemoved(nsWindowInfo *inInfo)
+{
+ if (mCurrentPosition == inInfo)
+ mCurrentPosition = FindNext();
+}
+
+//
+// nsASDOMWindowEnumerator
+//
+
+nsASDOMWindowEnumerator::nsASDOMWindowEnumerator(
+ const char16_t* aTypeString,
+ nsWindowMediator& aMediator) :
+ nsAppShellWindowEnumerator(aTypeString, aMediator)
+{
+}
+
+nsASDOMWindowEnumerator::~nsASDOMWindowEnumerator()
+{
+}
+
+NS_IMETHODIMP nsASDOMWindowEnumerator::GetNext(nsISupports **retval)
+{
+ if (!retval)
+ return NS_ERROR_INVALID_ARG;
+
+ *retval = nullptr;
+ while (mCurrentPosition) {
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow;
+ nsWindowMediator::GetDOMWindow(mCurrentPosition->mWindow, domWindow);
+ mCurrentPosition = FindNext();
+ if (domWindow)
+ return CallQueryInterface(domWindow, retval);
+ }
+ return NS_OK;
+}
+
+//
+// nsASXULWindowEnumerator
+//
+
+nsASXULWindowEnumerator::nsASXULWindowEnumerator(
+ const char16_t* aTypeString,
+ nsWindowMediator& aMediator) :
+ nsAppShellWindowEnumerator(aTypeString, aMediator)
+{
+}
+
+nsASXULWindowEnumerator::~nsASXULWindowEnumerator()
+{
+}
+
+NS_IMETHODIMP nsASXULWindowEnumerator::GetNext(nsISupports **retval)
+{
+ if (!retval)
+ return NS_ERROR_INVALID_ARG;
+
+ *retval = nullptr;
+ if (mCurrentPosition) {
+ CallQueryInterface(mCurrentPosition->mWindow, retval);
+ mCurrentPosition = FindNext();
+ }
+ return NS_OK;
+}
+
+//
+// nsASDOMWindowEarlyToLateEnumerator
+//
+
+nsASDOMWindowEarlyToLateEnumerator::nsASDOMWindowEarlyToLateEnumerator(
+ const char16_t *aTypeString,
+ nsWindowMediator &aMediator) :
+ nsASDOMWindowEnumerator(aTypeString, aMediator)
+{
+ mCurrentPosition = aMediator.mOldestWindow;
+ AdjustInitialPosition();
+}
+
+nsASDOMWindowEarlyToLateEnumerator::~nsASDOMWindowEarlyToLateEnumerator()
+{
+}
+
+nsWindowInfo *nsASDOMWindowEarlyToLateEnumerator::FindNext()
+{
+ nsWindowInfo *info,
+ *listEnd;
+ bool allWindows = mType.IsEmpty();
+
+ // see nsXULWindowEarlyToLateEnumerator::FindNext
+ if (!mCurrentPosition)
+ return nullptr;
+
+ info = mCurrentPosition->mYounger;
+ listEnd = mWindowMediator->mOldestWindow;
+
+ while (info != listEnd) {
+ if (allWindows || info->TypeEquals(mType))
+ return info;
+ info = info->mYounger;
+ }
+
+ return nullptr;
+}
+
+//
+// nsASXULWindowEarlyToLateEnumerator
+//
+
+nsASXULWindowEarlyToLateEnumerator::nsASXULWindowEarlyToLateEnumerator(
+ const char16_t *aTypeString,
+ nsWindowMediator &aMediator) :
+ nsASXULWindowEnumerator(aTypeString, aMediator)
+{
+ mCurrentPosition = aMediator.mOldestWindow;
+ AdjustInitialPosition();
+}
+
+nsASXULWindowEarlyToLateEnumerator::~nsASXULWindowEarlyToLateEnumerator()
+{
+}
+
+nsWindowInfo *nsASXULWindowEarlyToLateEnumerator::FindNext()
+{
+ nsWindowInfo *info,
+ *listEnd;
+ bool allWindows = mType.IsEmpty();
+
+ /* mCurrentPosition null is assumed to mean that the enumerator has run
+ its course and is now basically useless. It could also be interpreted
+ to mean that it was created at a time when there were no windows. In
+ that case it would probably be more appropriate to check to see whether
+ windows have subsequently been added. But it's not guaranteed that we'll
+ pick up newly added windows anyway (if they occurred previous to our
+ current position) so we just don't worry about that. */
+ if (!mCurrentPosition)
+ return nullptr;
+
+ info = mCurrentPosition->mYounger;
+ listEnd = mWindowMediator->mOldestWindow;
+
+ while (info != listEnd) {
+ if (allWindows || info->TypeEquals(mType))
+ return info;
+ info = info->mYounger;
+ }
+
+ return nullptr;
+}
+
+//
+// nsASDOMWindowFrontToBackEnumerator
+//
+
+nsASDOMWindowFrontToBackEnumerator::nsASDOMWindowFrontToBackEnumerator(
+ const char16_t *aTypeString,
+ nsWindowMediator &aMediator) :
+ nsASDOMWindowEnumerator(aTypeString, aMediator)
+{
+ mCurrentPosition = aMediator.mTopmostWindow;
+ AdjustInitialPosition();
+}
+
+nsASDOMWindowFrontToBackEnumerator::~nsASDOMWindowFrontToBackEnumerator()
+{
+}
+
+nsWindowInfo *nsASDOMWindowFrontToBackEnumerator::FindNext()
+{
+ nsWindowInfo *info,
+ *listEnd;
+ bool allWindows = mType.IsEmpty();
+
+ // see nsXULWindowEarlyToLateEnumerator::FindNext
+ if (!mCurrentPosition)
+ return nullptr;
+
+ info = mCurrentPosition->mLower;
+ listEnd = mWindowMediator->mTopmostWindow;
+
+ while (info != listEnd) {
+ if (allWindows || info->TypeEquals(mType))
+ return info;
+ info = info->mLower;
+ }
+
+ return nullptr;
+}
+
+//
+// nsASXULWindowFrontToBackEnumerator
+//
+
+nsASXULWindowFrontToBackEnumerator::nsASXULWindowFrontToBackEnumerator(
+ const char16_t *aTypeString,
+ nsWindowMediator &aMediator) :
+ nsASXULWindowEnumerator(aTypeString, aMediator)
+{
+ mCurrentPosition = aMediator.mTopmostWindow;
+ AdjustInitialPosition();
+}
+
+nsASXULWindowFrontToBackEnumerator::~nsASXULWindowFrontToBackEnumerator()
+{
+}
+
+nsWindowInfo *nsASXULWindowFrontToBackEnumerator::FindNext()
+{
+ nsWindowInfo *info,
+ *listEnd;
+ bool allWindows = mType.IsEmpty();
+
+ // see nsXULWindowEarlyToLateEnumerator::FindNext
+ if (!mCurrentPosition)
+ return nullptr;
+
+ info = mCurrentPosition->mLower;
+ listEnd = mWindowMediator->mTopmostWindow;
+
+ while (info != listEnd) {
+ if (allWindows || info->TypeEquals(mType))
+ return info;
+ info = info->mLower;
+ }
+
+ return nullptr;
+}
+
+//
+// nsASDOMWindowBackToFrontEnumerator
+//
+
+nsASDOMWindowBackToFrontEnumerator::nsASDOMWindowBackToFrontEnumerator(
+ const char16_t *aTypeString,
+ nsWindowMediator &aMediator) :
+ nsASDOMWindowEnumerator(aTypeString, aMediator)
+{
+ mCurrentPosition = aMediator.mTopmostWindow ?
+ aMediator.mTopmostWindow->mHigher : nullptr;
+ AdjustInitialPosition();
+}
+
+nsASDOMWindowBackToFrontEnumerator::~nsASDOMWindowBackToFrontEnumerator()
+{
+}
+
+nsWindowInfo *nsASDOMWindowBackToFrontEnumerator::FindNext()
+{
+ nsWindowInfo *info,
+ *listEnd;
+ bool allWindows = mType.IsEmpty();
+
+ // see nsXULWindowEarlyToLateEnumerator::FindNext
+ if (!mCurrentPosition)
+ return nullptr;
+
+ info = mCurrentPosition->mHigher;
+ listEnd = mWindowMediator->mTopmostWindow;
+ if (listEnd)
+ listEnd = listEnd->mHigher;
+
+ while (info != listEnd) {
+ if (allWindows || info->TypeEquals(mType))
+ return info;
+ info = info->mHigher;
+ }
+
+ return nullptr;
+}
+
+//
+// nsASXULWindowBackToFrontEnumerator
+//
+
+nsASXULWindowBackToFrontEnumerator::nsASXULWindowBackToFrontEnumerator(
+ const char16_t *aTypeString,
+ nsWindowMediator &aMediator) :
+ nsASXULWindowEnumerator(aTypeString, aMediator)
+{
+ mCurrentPosition = aMediator.mTopmostWindow ?
+ aMediator.mTopmostWindow->mHigher : nullptr;
+ AdjustInitialPosition();
+}
+
+nsASXULWindowBackToFrontEnumerator::~nsASXULWindowBackToFrontEnumerator()
+{
+}
+
+nsWindowInfo *nsASXULWindowBackToFrontEnumerator::FindNext()
+{
+ nsWindowInfo *info,
+ *listEnd;
+ bool allWindows = mType.IsEmpty();
+
+ // see nsXULWindowEarlyToLateEnumerator::FindNext
+ if (!mCurrentPosition)
+ return nullptr;
+
+ info = mCurrentPosition->mHigher;
+ listEnd = mWindowMediator->mTopmostWindow;
+ if (listEnd)
+ listEnd = listEnd->mHigher;
+
+ while (info != listEnd) {
+ if (allWindows || info->TypeEquals(mType))
+ return info;
+ info = info->mHigher;
+ }
+
+ return nullptr;
+}
diff --git a/components/appshell/src/nsAppShellWindowEnumerator.h b/components/appshell/src/nsAppShellWindowEnumerator.h
new file mode 100644
index 000000000..f9a8a2d52
--- /dev/null
+++ b/components/appshell/src/nsAppShellWindowEnumerator.h
@@ -0,0 +1,166 @@
+/* -*- 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 nsAppShellWindowEnumerator_h
+#define nsAppShellWindowEnumerator_h
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+#include "nsISimpleEnumerator.h"
+#include "nsIXULWindow.h"
+
+class nsWindowMediator;
+
+//
+// nsWindowInfo
+//
+
+struct nsWindowInfo
+{
+ nsWindowInfo(nsIXULWindow* inWindow, int32_t inTimeStamp);
+ ~nsWindowInfo();
+
+ nsCOMPtr<nsIXULWindow> mWindow;
+ int32_t mTimeStamp;
+ uint32_t mZLevel;
+
+ // each struct is in two, independent, circular, doubly-linked lists
+ nsWindowInfo *mYounger, // next younger in sequence
+ *mOlder;
+ nsWindowInfo *mLower, // next lower in z-order
+ *mHigher;
+
+ bool TypeEquals(const nsAString &aType);
+ void InsertAfter(nsWindowInfo *inOlder, nsWindowInfo *inHigher);
+ void Unlink(bool inAge, bool inZ);
+ void ReferenceSelf(bool inAge, bool inZ);
+};
+
+//
+// virtual enumerators
+//
+
+class nsAppShellWindowEnumerator : public nsISimpleEnumerator {
+
+friend class nsWindowMediator;
+
+public:
+ nsAppShellWindowEnumerator(const char16_t* aTypeString,
+ nsWindowMediator& inMediator);
+ NS_IMETHOD GetNext(nsISupports **retval) override = 0;
+ NS_IMETHOD HasMoreElements(bool *retval) override;
+
+ NS_DECL_ISUPPORTS
+
+protected:
+
+ virtual ~nsAppShellWindowEnumerator();
+
+ void AdjustInitialPosition();
+ virtual nsWindowInfo *FindNext() = 0;
+
+ void WindowRemoved(nsWindowInfo *inInfo);
+
+ nsWindowMediator *mWindowMediator;
+ nsString mType;
+ nsWindowInfo *mCurrentPosition;
+};
+
+class nsASDOMWindowEnumerator : public nsAppShellWindowEnumerator {
+
+public:
+ nsASDOMWindowEnumerator(const char16_t* aTypeString,
+ nsWindowMediator& inMediator);
+ virtual ~nsASDOMWindowEnumerator();
+ NS_IMETHOD GetNext(nsISupports **retval);
+};
+
+class nsASXULWindowEnumerator : public nsAppShellWindowEnumerator {
+
+public:
+ nsASXULWindowEnumerator(const char16_t* aTypeString,
+ nsWindowMediator& inMediator);
+ virtual ~nsASXULWindowEnumerator();
+ NS_IMETHOD GetNext(nsISupports **retval);
+};
+
+//
+// concrete enumerators
+//
+
+class nsASDOMWindowEarlyToLateEnumerator : public nsASDOMWindowEnumerator {
+
+public:
+ nsASDOMWindowEarlyToLateEnumerator(const char16_t* aTypeString,
+ nsWindowMediator& inMediator);
+
+ virtual ~nsASDOMWindowEarlyToLateEnumerator();
+
+protected:
+ virtual nsWindowInfo *FindNext();
+};
+
+class nsASXULWindowEarlyToLateEnumerator : public nsASXULWindowEnumerator {
+
+public:
+ nsASXULWindowEarlyToLateEnumerator(const char16_t* aTypeString,
+ nsWindowMediator& inMediator);
+
+ virtual ~nsASXULWindowEarlyToLateEnumerator();
+
+protected:
+ virtual nsWindowInfo *FindNext();
+};
+
+class nsASDOMWindowFrontToBackEnumerator : public nsASDOMWindowEnumerator {
+
+public:
+ nsASDOMWindowFrontToBackEnumerator(const char16_t* aTypeString,
+ nsWindowMediator& inMediator);
+
+ virtual ~nsASDOMWindowFrontToBackEnumerator();
+
+protected:
+ virtual nsWindowInfo *FindNext();
+};
+
+class nsASXULWindowFrontToBackEnumerator : public nsASXULWindowEnumerator {
+
+public:
+ nsASXULWindowFrontToBackEnumerator(const char16_t* aTypeString,
+ nsWindowMediator& inMediator);
+
+ virtual ~nsASXULWindowFrontToBackEnumerator();
+
+protected:
+ virtual nsWindowInfo *FindNext();
+};
+
+class nsASDOMWindowBackToFrontEnumerator : public nsASDOMWindowEnumerator {
+
+public:
+ nsASDOMWindowBackToFrontEnumerator(const char16_t* aTypeString,
+ nsWindowMediator& inMediator);
+
+ virtual ~nsASDOMWindowBackToFrontEnumerator();
+
+protected:
+ virtual nsWindowInfo *FindNext();
+};
+
+class nsASXULWindowBackToFrontEnumerator : public nsASXULWindowEnumerator {
+
+public:
+ nsASXULWindowBackToFrontEnumerator(const char16_t* aTypeString,
+ nsWindowMediator& inMediator);
+
+ virtual ~nsASXULWindowBackToFrontEnumerator();
+
+protected:
+ virtual nsWindowInfo *FindNext();
+};
+
+#endif
diff --git a/components/appshell/src/nsChromeTreeOwner.cpp b/components/appshell/src/nsChromeTreeOwner.cpp
new file mode 100644
index 000000000..63fa86373
--- /dev/null
+++ b/components/appshell/src/nsChromeTreeOwner.cpp
@@ -0,0 +1,561 @@
+/* -*- Mode: C++; tab-width: 3; 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/. */
+
+// Local Includes
+#include "nsChromeTreeOwner.h"
+#include "nsXULWindow.h"
+
+// Helper Classes
+#include "nsString.h"
+#include "nsIEmbeddingSiteWindow.h"
+#include "nsIServiceManager.h"
+#include "nsIDocShellTreeItem.h"
+
+// Interfaces needed to include
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsIWebProgress.h"
+#include "nsIWidget.h"
+#include "nsIWindowMediator.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMXULElement.h"
+#include "nsIXULBrowserWindow.h"
+#include "mozilla/dom/Element.h"
+
+using namespace mozilla;
+
+//*****************************************************************************
+// nsChromeTreeOwner string literals
+//*****************************************************************************
+
+struct nsChromeTreeOwnerLiterals
+{
+ const nsLiteralString kPersist;
+ const nsLiteralString kScreenX;
+ const nsLiteralString kScreenY;
+ const nsLiteralString kWidth;
+ const nsLiteralString kHeight;
+ const nsLiteralString kSizemode;
+ const nsLiteralString kSpace;
+
+ nsChromeTreeOwnerLiterals()
+ : NS_LITERAL_STRING_INIT(kPersist,"persist")
+ , NS_LITERAL_STRING_INIT(kScreenX,"screenX")
+ , NS_LITERAL_STRING_INIT(kScreenY,"screenY")
+ , NS_LITERAL_STRING_INIT(kWidth,"width")
+ , NS_LITERAL_STRING_INIT(kHeight,"height")
+ , NS_LITERAL_STRING_INIT(kSizemode,"sizemode")
+ , NS_LITERAL_STRING_INIT(kSpace," ")
+ {}
+};
+
+static nsChromeTreeOwnerLiterals *gLiterals;
+
+nsresult
+nsChromeTreeOwner::InitGlobals()
+{
+ NS_ASSERTION(gLiterals == nullptr, "already initialized");
+ gLiterals = new nsChromeTreeOwnerLiterals();
+ return NS_OK;
+}
+
+void
+nsChromeTreeOwner::FreeGlobals()
+{
+ delete gLiterals;
+ gLiterals = nullptr;
+}
+
+//*****************************************************************************
+//*** nsChromeTreeOwner: Object Management
+//*****************************************************************************
+
+nsChromeTreeOwner::nsChromeTreeOwner() : mXULWindow(nullptr)
+{
+}
+
+nsChromeTreeOwner::~nsChromeTreeOwner()
+{
+}
+
+//*****************************************************************************
+// nsChromeTreeOwner::nsISupports
+//*****************************************************************************
+
+NS_IMPL_ADDREF(nsChromeTreeOwner)
+NS_IMPL_RELEASE(nsChromeTreeOwner)
+
+NS_INTERFACE_MAP_BEGIN(nsChromeTreeOwner)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+//*****************************************************************************
+// nsChromeTreeOwner::nsIInterfaceRequestor
+//*****************************************************************************
+
+NS_IMETHODIMP nsChromeTreeOwner::GetInterface(const nsIID& aIID, void** aSink)
+{
+ NS_ENSURE_ARG_POINTER(aSink);
+
+ if(aIID.Equals(NS_GET_IID(nsIPrompt))) {
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetInterface(aIID, aSink);
+ }
+ if(aIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetInterface(aIID, aSink);
+ }
+ if(aIID.Equals(NS_GET_IID(nsIWebBrowserChrome))) {
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetInterface(aIID, aSink);
+ }
+ if (aIID.Equals(NS_GET_IID(nsIEmbeddingSiteWindow))) {
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetInterface(aIID, aSink);
+ }
+ if (aIID.Equals(NS_GET_IID(nsIXULWindow))) {
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->QueryInterface(aIID, aSink);
+ }
+
+ return QueryInterface(aIID, aSink);
+}
+
+//*****************************************************************************
+// nsChromeTreeOwner::nsIDocShellTreeOwner
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsChromeTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell,
+ bool aPrimary, bool aTargetable,
+ const nsAString& aID)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->ContentShellAdded(aContentShell, aPrimary, aTargetable,
+ aID);
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::ContentShellRemoved(nsIDocShellTreeItem* aContentShell)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->ContentShellRemoved(aContentShell);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetPrimaryContentShell(nsIDocShellTreeItem** aShell)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetPrimaryContentShell(aShell);
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::TabParentAdded(nsITabParent* aTab, bool aPrimary)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->TabParentAdded(aTab, aPrimary);
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::TabParentRemoved(nsITabParent* aTab)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->TabParentRemoved(aTab);
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::GetPrimaryTabParent(nsITabParent** aTab)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetPrimaryTabParent(aTab);
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::GetPrimaryContentSize(int32_t* aWidth,
+ int32_t* aHeight)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetPrimaryContentSize(aWidth, aHeight);
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::SetPrimaryContentSize(int32_t aWidth,
+ int32_t aHeight)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetPrimaryContentSize(aWidth, aHeight);
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::GetRootShellSize(int32_t* aWidth,
+ int32_t* aHeight)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetRootShellSize(aWidth, aHeight);
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::SetRootShellSize(int32_t aWidth,
+ int32_t aHeight)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetRootShellSize(aWidth, aHeight);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SizeShellTo(nsIDocShellTreeItem* aShellItem,
+ int32_t aCX, int32_t aCY)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SizeShellTo(aShellItem, aCX, aCY);
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::SetPersistence(bool aPersistPosition,
+ bool aPersistSize,
+ bool aPersistSizeMode)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement();
+ if (!docShellElement)
+ return NS_ERROR_FAILURE;
+
+ nsAutoString persistString;
+ docShellElement->GetAttribute(gLiterals->kPersist, persistString);
+
+ bool saveString = false;
+ int32_t index;
+
+#define FIND_PERSIST_STRING(aString, aCond) \
+ index = persistString.Find(aString); \
+ if (!aCond && index > kNotFound) { \
+ persistString.Cut(index, aString.Length()); \
+ saveString = true; \
+ } else if (aCond && index == kNotFound) { \
+ persistString.Append(gLiterals->kSpace + aString); \
+ saveString = true; \
+ }
+ FIND_PERSIST_STRING(gLiterals->kScreenX, aPersistPosition);
+ FIND_PERSIST_STRING(gLiterals->kScreenY, aPersistPosition);
+ FIND_PERSIST_STRING(gLiterals->kWidth, aPersistSize);
+ FIND_PERSIST_STRING(gLiterals->kHeight, aPersistSize);
+ FIND_PERSIST_STRING(gLiterals->kSizemode, aPersistSizeMode);
+
+ ErrorResult rv;
+ if (saveString) {
+ docShellElement->SetAttribute(gLiterals->kPersist, persistString, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::GetPersistence(bool* aPersistPosition,
+ bool* aPersistSize,
+ bool* aPersistSizeMode)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement();
+ if (!docShellElement)
+ return NS_ERROR_FAILURE;
+
+ nsAutoString persistString;
+ docShellElement->GetAttribute(gLiterals->kPersist, persistString);
+
+ // data structure doesn't quite match the question, but it's close enough
+ // for what we want (since this method is never actually called...)
+ if (aPersistPosition)
+ *aPersistPosition = persistString.Find(gLiterals->kScreenX) > kNotFound ||
+ persistString.Find(gLiterals->kScreenY) > kNotFound;
+ if (aPersistSize)
+ *aPersistSize = persistString.Find(gLiterals->kWidth) > kNotFound ||
+ persistString.Find(gLiterals->kHeight) > kNotFound;
+ if (aPersistSizeMode)
+ *aPersistSizeMode = persistString.Find(gLiterals->kSizemode) > kNotFound;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::GetTargetableShellCount(uint32_t* aResult)
+{
+ *aResult = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::GetHasPrimaryContent(bool* aResult)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetHasPrimaryContent(aResult);
+}
+
+//*****************************************************************************
+// nsChromeTreeOwner::nsIBaseWindow
+//*****************************************************************************
+
+NS_IMETHODIMP nsChromeTreeOwner::InitWindow(nativeWindow aParentNativeWindow,
+ nsIWidget* parentWidget, int32_t x, int32_t y, int32_t cx, int32_t cy)
+{
+ // Ignore widget parents for now. Don't think those are a vaild thing to call.
+ NS_ENSURE_SUCCESS(SetPositionAndSize(x, y, cx, cy, 0), NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::Create()
+{
+ NS_ASSERTION(false, "You can't call this");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::Destroy()
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->Destroy();
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetUnscaledDevicePixelsPerCSSPixel(double *aScale)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetUnscaledDevicePixelsPerCSSPixel(aScale);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetDevicePixelsPerDesktopPixel(double *aScale)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetDevicePixelsPerDesktopPixel(aScale);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SetPositionDesktopPix(int32_t x, int32_t y)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetPositionDesktopPix(x, y);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SetPosition(int32_t x, int32_t y)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetPosition(x, y);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetPosition(int32_t* x, int32_t* y)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetPosition(x, y);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SetSize(int32_t cx, int32_t cy, bool fRepaint)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetSize(cx, cy, fRepaint);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetSize(int32_t* cx, int32_t* cy)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetSize(cx, cy);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SetPositionAndSize(int32_t x, int32_t y, int32_t cx,
+ int32_t cy, uint32_t aFlags)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetPositionAndSize(x, y, cx, cy, aFlags);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetPositionAndSize(int32_t* x, int32_t* y, int32_t* cx,
+ int32_t* cy)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetPositionAndSize(x, y, cx, cy);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::Repaint(bool aForce)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->Repaint(aForce);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetParentWidget(nsIWidget** aParentWidget)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetParentWidget(aParentWidget);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SetParentWidget(nsIWidget* aParentWidget)
+{
+ NS_ASSERTION(false, "You can't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetParentNativeWindow(nativeWindow* aParentNativeWindow)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetParentNativeWindow(aParentNativeWindow);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SetParentNativeWindow(nativeWindow aParentNativeWindow)
+{
+ NS_ASSERTION(false, "You can't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetNativeHandle(nsAString& aNativeHandle)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetNativeHandle(aNativeHandle);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetVisibility(bool* aVisibility)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetVisibility(aVisibility);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SetVisibility(bool aVisibility)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetVisibility(aVisibility);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetEnabled(bool *aEnabled)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetEnabled(aEnabled);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SetEnabled(bool aEnable)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetEnabled(aEnable);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetMainWidget(nsIWidget** aMainWidget)
+{
+ NS_ENSURE_ARG_POINTER(aMainWidget);
+ NS_ENSURE_STATE(mXULWindow);
+
+ *aMainWidget = mXULWindow->mWindow;
+ NS_IF_ADDREF(*aMainWidget);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SetFocus()
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetFocus();
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::GetTitle(char16_t** aTitle)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetTitle(aTitle);
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::SetTitle(const char16_t* aTitle)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetTitle(aTitle);
+}
+
+//*****************************************************************************
+// nsChromeTreeOwner::nsIWebProgressListener
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsChromeTreeOwner::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aProgressStateFlags,
+ nsresult aStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsChromeTreeOwner::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI* aLocation,
+ uint32_t aFlags)
+{
+ bool itsForYou = true;
+
+ if (aWebProgress) {
+ NS_ENSURE_STATE(mXULWindow);
+ nsCOMPtr<mozIDOMWindowProxy> progressWin;
+ aWebProgress->GetDOMWindow(getter_AddRefs(progressWin));
+
+ nsCOMPtr<nsIDocShell> docshell;
+ mXULWindow->GetDocShell(getter_AddRefs(docshell));
+ // XXXkhuey this is totally wrong, bug 1223303.
+ nsCOMPtr<mozIDOMWindowProxy> ourWin(do_QueryInterface(docshell));
+
+ if (ourWin != progressWin)
+ itsForYou = false;
+ }
+
+ // If loading a new root .xul document, then redo chrome.
+ if (itsForYou) {
+ NS_ENSURE_STATE(mXULWindow);
+ mXULWindow->mChromeLoaded = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeTreeOwner::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+nsChromeTreeOwner::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsChromeTreeOwner: Helpers
+//*****************************************************************************
+
+//*****************************************************************************
+// nsChromeTreeOwner: Accessors
+//*****************************************************************************
+
+void nsChromeTreeOwner::XULWindow(nsXULWindow* aXULWindow)
+{
+ mXULWindow = aXULWindow;
+}
+
+nsXULWindow* nsChromeTreeOwner::XULWindow()
+{
+ return mXULWindow;
+}
diff --git a/components/appshell/src/nsChromeTreeOwner.h b/components/appshell/src/nsChromeTreeOwner.h
new file mode 100644
index 000000000..77b0dea89
--- /dev/null
+++ b/components/appshell/src/nsChromeTreeOwner.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef nsChromeTreeOwner_h__
+#define nsChromeTreeOwner_h__
+
+// Helper Classes
+#include "nsCOMPtr.h"
+
+// Interfaces Needed
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+
+class nsXULWindow;
+
+class nsChromeTreeOwner : public nsIDocShellTreeOwner,
+ public nsIBaseWindow,
+ public nsIInterfaceRequestor,
+ public nsIWebProgressListener,
+ public nsSupportsWeakReference
+{
+friend class nsXULWindow;
+
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIBASEWINDOW
+ NS_DECL_NSIDOCSHELLTREEOWNER
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ static nsresult InitGlobals();
+ static void FreeGlobals();
+
+protected:
+ nsChromeTreeOwner();
+ virtual ~nsChromeTreeOwner();
+
+ void XULWindow(nsXULWindow* aXULWindow);
+ nsXULWindow* XULWindow();
+
+protected:
+ nsXULWindow* mXULWindow;
+};
+
+#endif /* nsChromeTreeOwner_h__ */
diff --git a/components/appshell/src/nsContentTreeOwner.cpp b/components/appshell/src/nsContentTreeOwner.cpp
new file mode 100644
index 000000000..c916f74d0
--- /dev/null
+++ b/components/appshell/src/nsContentTreeOwner.cpp
@@ -0,0 +1,1096 @@
+/* -*- Mode: C++; tab-width: 3; 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/. */
+
+// Local Includes
+#include "nsContentTreeOwner.h"
+#include "nsXULWindow.h"
+
+// Helper Classes
+#include "nsIServiceManager.h"
+#include "nsAutoPtr.h"
+
+// Interfaces needed to be included
+#include "nsIDOMNode.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsIDOMXULElement.h"
+#include "nsIEmbeddingSiteWindow.h"
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsIWindowMediator.h"
+#include "nsIXULBrowserWindow.h"
+#include "nsIPrincipal.h"
+#include "nsIURIFixup.h"
+#include "nsCDefaultURIFixup.h"
+#include "nsIWebNavigation.h"
+#include "nsDocShellCID.h"
+#include "nsIExternalURLHandlerService.h"
+#include "nsIMIMEInfo.h"
+#include "nsIWidget.h"
+#include "nsWindowWatcher.h"
+#include "mozilla/BrowserElementParent.h"
+
+#include "nsIDOMDocument.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIURI.h"
+#include "nsIDocument.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+using namespace mozilla;
+
+//*****************************************************************************
+//*** nsSiteWindow declaration
+//*****************************************************************************
+
+class nsSiteWindow : public nsIEmbeddingSiteWindow
+{
+ // nsSiteWindow shares a lifetime with nsContentTreeOwner, and proxies it's
+ // AddRef and Release calls to said object.
+ // When nsContentTreeOwner is destroyed, nsSiteWindow will be destroyed as well.
+ // nsContentTreeOwner is a friend class of nsSiteWindow such that it can call
+ // nsSiteWindow's destructor, which is private, as public destructors
+ // on reference counted classes are generally unsafe.
+ friend class nsContentTreeOwner;
+
+public:
+ explicit nsSiteWindow(nsContentTreeOwner *aAggregator);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIEMBEDDINGSITEWINDOW
+
+private:
+ virtual ~nsSiteWindow();
+ nsContentTreeOwner *mAggregator;
+};
+
+//*****************************************************************************
+//*** nsContentTreeOwner: Object Management
+//*****************************************************************************
+
+nsContentTreeOwner::nsContentTreeOwner(bool fPrimary) : mXULWindow(nullptr),
+ mPrimary(fPrimary), mContentTitleSetting(false)
+{
+ // note if this fails, QI on nsIEmbeddingSiteWindow(2) will simply fail
+ mSiteWindow = new nsSiteWindow(this);
+}
+
+nsContentTreeOwner::~nsContentTreeOwner()
+{
+ delete mSiteWindow;
+}
+
+//*****************************************************************************
+// nsContentTreeOwner::nsISupports
+//*****************************************************************************
+
+NS_IMPL_ADDREF(nsContentTreeOwner)
+NS_IMPL_RELEASE(nsContentTreeOwner)
+
+NS_INTERFACE_MAP_BEGIN(nsContentTreeOwner)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome2)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome3)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIWindowProvider)
+ // NOTE: This is using aggregation because there are some properties and
+ // method on nsIBaseWindow (which we implement) and on
+ // nsIEmbeddingSiteWindow (which we also implement) that have the same name.
+ // And it just so happens that we want different behavior for these methods
+ // and properties depending on the interface through which they're called
+ // (SetFocus() is a good example here). If it were not for that, we could
+ // ditch the aggregation and just deal with not being able to use NS_DECL_*
+ // macros for this stuff....
+ NS_INTERFACE_MAP_ENTRY_AGGREGATED(nsIEmbeddingSiteWindow, mSiteWindow)
+NS_INTERFACE_MAP_END
+
+//*****************************************************************************
+// nsContentTreeOwner::nsIInterfaceRequestor
+//*****************************************************************************
+
+NS_IMETHODIMP nsContentTreeOwner::GetInterface(const nsIID& aIID, void** aSink)
+{
+ NS_ENSURE_ARG_POINTER(aSink);
+ *aSink = 0;
+
+ if(aIID.Equals(NS_GET_IID(nsIPrompt))) {
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetInterface(aIID, aSink);
+ }
+ if(aIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetInterface(aIID, aSink);
+ }
+ if (aIID.Equals(NS_GET_IID(nsIDocShellTreeItem))) {
+ NS_ENSURE_STATE(mXULWindow);
+ nsCOMPtr<nsIDocShell> shell;
+ mXULWindow->GetDocShell(getter_AddRefs(shell));
+ if (shell)
+ return shell->QueryInterface(aIID, aSink);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIDOMWindow)) ||
+ aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter))) {
+ NS_ENSURE_STATE(mXULWindow);
+ nsCOMPtr<nsIDocShellTreeItem> shell;
+ mXULWindow->GetPrimaryContentShell(getter_AddRefs(shell));
+ if (shell) {
+ nsCOMPtr<nsIInterfaceRequestor> thing(do_QueryInterface(shell));
+ if (thing)
+ return thing->GetInterface(aIID, aSink);
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIXULWindow))) {
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->QueryInterface(aIID, aSink);
+ }
+
+ return QueryInterface(aIID, aSink);
+}
+
+//*****************************************************************************
+// nsContentTreeOwner::nsIDocShellTreeOwner
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsContentTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell,
+ bool aPrimary, bool aTargetable,
+ const nsAString& aID)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->ContentShellAdded(aContentShell, aPrimary, aTargetable,
+ aID);
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::ContentShellRemoved(nsIDocShellTreeItem* aContentShell)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->ContentShellRemoved(aContentShell);
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::GetPrimaryContentShell(nsIDocShellTreeItem** aShell)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetPrimaryContentShell(aShell);
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::TabParentAdded(nsITabParent* aTab, bool aPrimary)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->TabParentAdded(aTab, aPrimary);
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::TabParentRemoved(nsITabParent* aTab)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->TabParentRemoved(aTab);
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::GetPrimaryTabParent(nsITabParent** aTab)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetPrimaryTabParent(aTab);
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::GetPrimaryContentSize(int32_t* aWidth,
+ int32_t* aHeight)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetPrimaryContentSize(aWidth, aHeight);
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::SetPrimaryContentSize(int32_t aWidth,
+ int32_t aHeight)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetPrimaryContentSize(aWidth, aHeight);
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::GetRootShellSize(int32_t* aWidth,
+ int32_t* aHeight)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetRootShellSize(aWidth, aHeight);
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::SetRootShellSize(int32_t aWidth,
+ int32_t aHeight)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetRootShellSize(aWidth, aHeight);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SizeShellTo(nsIDocShellTreeItem* aShellItem,
+ int32_t aCX, int32_t aCY)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SizeShellTo(aShellItem, aCX, aCY);
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::SetPersistence(bool aPersistPosition,
+ bool aPersistSize,
+ bool aPersistSizeMode)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement();
+ if (!docShellElement)
+ return NS_ERROR_FAILURE;
+
+ nsAutoString persistString;
+ docShellElement->GetAttribute(NS_LITERAL_STRING("persist"), persistString);
+
+ bool saveString = false;
+ int32_t index;
+
+ // Set X
+ index = persistString.Find("screenX");
+ if (!aPersistPosition && index >= 0) {
+ persistString.Cut(index, 7);
+ saveString = true;
+ } else if (aPersistPosition && index < 0) {
+ persistString.AppendLiteral(" screenX");
+ saveString = true;
+ }
+ // Set Y
+ index = persistString.Find("screenY");
+ if (!aPersistPosition && index >= 0) {
+ persistString.Cut(index, 7);
+ saveString = true;
+ } else if (aPersistPosition && index < 0) {
+ persistString.AppendLiteral(" screenY");
+ saveString = true;
+ }
+ // Set CX
+ index = persistString.Find("width");
+ if (!aPersistSize && index >= 0) {
+ persistString.Cut(index, 5);
+ saveString = true;
+ } else if (aPersistSize && index < 0) {
+ persistString.AppendLiteral(" width");
+ saveString = true;
+ }
+ // Set CY
+ index = persistString.Find("height");
+ if (!aPersistSize && index >= 0) {
+ persistString.Cut(index, 6);
+ saveString = true;
+ } else if (aPersistSize && index < 0) {
+ persistString.AppendLiteral(" height");
+ saveString = true;
+ }
+ // Set SizeMode
+ index = persistString.Find("sizemode");
+ if (!aPersistSizeMode && (index >= 0)) {
+ persistString.Cut(index, 8);
+ saveString = true;
+ } else if (aPersistSizeMode && (index < 0)) {
+ persistString.AppendLiteral(" sizemode");
+ saveString = true;
+ }
+
+ ErrorResult rv;
+ if(saveString) {
+ docShellElement->SetAttribute(NS_LITERAL_STRING("persist"), persistString, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::GetPersistence(bool* aPersistPosition,
+ bool* aPersistSize,
+ bool* aPersistSizeMode)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement();
+ if (!docShellElement)
+ return NS_ERROR_FAILURE;
+
+ nsAutoString persistString;
+ docShellElement->GetAttribute(NS_LITERAL_STRING("persist"), persistString);
+
+ // data structure doesn't quite match the question, but it's close enough
+ // for what we want (since this method is never actually called...)
+ if (aPersistPosition)
+ *aPersistPosition = persistString.Find("screenX") >= 0 || persistString.Find("screenY") >= 0 ? true : false;
+ if (aPersistSize)
+ *aPersistSize = persistString.Find("width") >= 0 || persistString.Find("height") >= 0 ? true : false;
+ if (aPersistSizeMode)
+ *aPersistSizeMode = persistString.Find("sizemode") >= 0 ? true : false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::GetTargetableShellCount(uint32_t* aResult)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ *aResult = mXULWindow->mTargetableShells.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentTreeOwner::GetHasPrimaryContent(bool* aResult)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetHasPrimaryContent(aResult);
+}
+
+//*****************************************************************************
+// nsContentTreeOwner::nsIWebBrowserChrome3
+//*****************************************************************************
+
+NS_IMETHODIMP nsContentTreeOwner::OnBeforeLinkTraversal(const nsAString &originalTarget,
+ nsIURI *linkURI,
+ nsIDOMNode *linkNode,
+ bool isAppTab,
+ nsAString &_retval)
+{
+ NS_ENSURE_STATE(mXULWindow);
+
+ nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow;
+ mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow));
+
+ if (xulBrowserWindow)
+ return xulBrowserWindow->OnBeforeLinkTraversal(originalTarget, linkURI,
+ linkNode, isAppTab, _retval);
+
+ _retval = originalTarget;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::ShouldLoadURI(nsIDocShell *aDocShell,
+ nsIURI *aURI,
+ nsIURI *aReferrer,
+ nsIPrincipal* aTriggeringPrincipal,
+ bool *_retval)
+{
+ NS_ENSURE_STATE(mXULWindow);
+
+ nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow;
+ mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow));
+
+ if (xulBrowserWindow)
+ return xulBrowserWindow->ShouldLoadURI(aDocShell, aURI, aReferrer,
+ aTriggeringPrincipal, _retval);
+
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::ReloadInFreshProcess(nsIDocShell* aDocShell,
+ nsIURI* aURI,
+ nsIURI* aReferrer,
+ nsIPrincipal* aTriggeringPrincipal,
+ bool* aRetVal)
+{
+ NS_WARNING("Cannot reload in fresh process from a nsContentTreeOwner!");
+ *aRetVal = false;
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsContentTreeOwner::nsIWebBrowserChrome2
+//*****************************************************************************
+
+NS_IMETHODIMP nsContentTreeOwner::SetStatusWithContext(uint32_t aStatusType,
+ const nsAString &aStatusText,
+ nsISupports *aStatusContext)
+{
+ // We only allow the status to be set from the primary content shell
+ if (!mPrimary && aStatusType != STATUS_LINK)
+ return NS_OK;
+
+ NS_ENSURE_STATE(mXULWindow);
+
+ nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow;
+ mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow));
+
+ if (xulBrowserWindow)
+ {
+ switch(aStatusType)
+ {
+ case STATUS_SCRIPT:
+ xulBrowserWindow->SetJSStatus(aStatusText);
+ break;
+ case STATUS_LINK:
+ {
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aStatusContext);
+ xulBrowserWindow->SetOverLink(aStatusText, element);
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsContentTreeOwner::nsIWebBrowserChrome
+//*****************************************************************************
+
+NS_IMETHODIMP nsContentTreeOwner::SetStatus(uint32_t aStatusType,
+ const char16_t* aStatus)
+{
+ return SetStatusWithContext(aStatusType,
+ aStatus ? static_cast<const nsString &>(nsDependentString(aStatus))
+ : EmptyString(),
+ nullptr);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetWebBrowser(nsIWebBrowser* aWebBrowser)
+{
+ NS_ERROR("Haven't Implemented this yet");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetWebBrowser(nsIWebBrowser** aWebBrowser)
+{
+ // Unimplemented, and probably will remain so; xpfe windows have docshells,
+ // not webbrowsers.
+ NS_ENSURE_ARG_POINTER(aWebBrowser);
+ *aWebBrowser = 0;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetChromeFlags(uint32_t aChromeFlags)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetChromeFlags(aChromeFlags);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetChromeFlags(uint32_t* aChromeFlags)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetChromeFlags(aChromeFlags);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::DestroyBrowserWindow()
+{
+ NS_ERROR("Haven't Implemented this yet");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SizeBrowserTo(int32_t aCX, int32_t aCY)
+{
+ NS_ERROR("Haven't Implemented this yet");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::ShowAsModal()
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->ShowModal();
+}
+
+NS_IMETHODIMP nsContentTreeOwner::IsWindowModal(bool *_retval)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ *_retval = mXULWindow->mContinueModalLoop;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::ExitModalEventLoop(nsresult aStatus)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->ExitModalLoop(aStatus);
+}
+
+//*****************************************************************************
+// nsContentTreeOwner::nsIBaseWindow
+//*****************************************************************************
+
+NS_IMETHODIMP nsContentTreeOwner::InitWindow(nativeWindow aParentNativeWindow,
+ nsIWidget* parentWidget, int32_t x, int32_t y, int32_t cx, int32_t cy)
+{
+ // Ignore wigdet parents for now. Don't think those are a vaild thing to call.
+ NS_ENSURE_SUCCESS(SetPositionAndSize(x, y, cx, cy, 0), NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::Create()
+{
+ NS_ASSERTION(false, "You can't call this");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::Destroy()
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->Destroy();
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetUnscaledDevicePixelsPerCSSPixel(double* aScale)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetUnscaledDevicePixelsPerCSSPixel(aScale);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetDevicePixelsPerDesktopPixel(double* aScale)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetDevicePixelsPerDesktopPixel(aScale);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetPositionDesktopPix(int32_t aX, int32_t aY)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetPositionDesktopPix(aX, aY);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetPosition(int32_t aX, int32_t aY)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetPosition(aX, aY);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetPosition(int32_t* aX, int32_t* aY)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetPosition(aX, aY);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetSize(int32_t aCX, int32_t aCY, bool aRepaint)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetSize(aCX, aCY, aRepaint);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetSize(int32_t* aCX, int32_t* aCY)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetSize(aCX, aCY);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetPositionAndSize(int32_t aX, int32_t aY,
+ int32_t aCX, int32_t aCY, uint32_t aFlags)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetPositionAndSize(aX, aY, aCX, aCY, aFlags);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetPositionAndSize(int32_t* aX, int32_t* aY,
+ int32_t* aCX, int32_t* aCY)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetPositionAndSize(aX, aY, aCX, aCY);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::Repaint(bool aForce)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->Repaint(aForce);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetParentWidget(nsIWidget** aParentWidget)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetParentWidget(aParentWidget);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetParentWidget(nsIWidget* aParentWidget)
+{
+ NS_ASSERTION(false, "You can't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetParentNativeWindow(nativeWindow* aParentNativeWindow)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetParentNativeWindow(aParentNativeWindow);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetParentNativeWindow(nativeWindow aParentNativeWindow)
+{
+ NS_ASSERTION(false, "You can't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetNativeHandle(nsAString& aNativeHandle)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetNativeHandle(aNativeHandle);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetVisibility(bool* aVisibility)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetVisibility(aVisibility);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetVisibility(bool aVisibility)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetVisibility(aVisibility);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetEnabled(bool *aEnabled)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->GetEnabled(aEnabled);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetEnabled(bool aEnable)
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetEnabled(aEnable);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetMainWidget(nsIWidget** aMainWidget)
+{
+ NS_ENSURE_ARG_POINTER(aMainWidget);
+ NS_ENSURE_STATE(mXULWindow);
+
+ *aMainWidget = mXULWindow->mWindow;
+ NS_IF_ADDREF(*aMainWidget);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetFocus()
+{
+ NS_ENSURE_STATE(mXULWindow);
+ return mXULWindow->SetFocus();
+}
+
+NS_IMETHODIMP nsContentTreeOwner::GetTitle(char16_t** aTitle)
+{
+ NS_ENSURE_ARG_POINTER(aTitle);
+ NS_ENSURE_STATE(mXULWindow);
+
+ return mXULWindow->GetTitle(aTitle);
+}
+
+NS_IMETHODIMP nsContentTreeOwner::SetTitle(const char16_t* aTitle)
+{
+ // We only allow the title to be set from the primary content shell
+ if(!mPrimary || !mContentTitleSetting)
+ return NS_OK;
+
+ NS_ENSURE_STATE(mXULWindow);
+
+ nsAutoString title;
+ nsAutoString docTitle(aTitle);
+
+ if (docTitle.IsEmpty())
+ docTitle.Assign(mTitleDefault);
+
+ if (!docTitle.IsEmpty()) {
+ if (!mTitlePreface.IsEmpty()) {
+ // Title will be: "Preface: Doc Title - Mozilla"
+ title.Assign(mTitlePreface);
+ title.Append(docTitle);
+ }
+ else {
+ // Title will be: "Doc Title - Mozilla"
+ title = docTitle;
+ }
+
+ if (!mWindowTitleModifier.IsEmpty())
+ title += mTitleSeparator + mWindowTitleModifier;
+ }
+ else
+ title.Assign(mWindowTitleModifier); // Title will just be plain "Mozilla"
+
+ //
+ // if there is no location bar we modify the title to display at least
+ // the scheme and host (if any) as an anti-spoofing measure.
+ //
+ nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement();
+
+ if (docShellElement) {
+ nsAutoString chromeString;
+ docShellElement->GetAttribute(NS_LITERAL_STRING("chromehidden"), chromeString);
+ if (chromeString.Find(NS_LITERAL_STRING("location")) != kNotFound) {
+ //
+ // location bar is turned off, find the browser location
+ //
+ // use the document's nsPrincipal to find the true owner
+ // in case of javascript: or data: documents
+ //
+ nsCOMPtr<nsIDocShellTreeItem> dsitem;
+ GetPrimaryContentShell(getter_AddRefs(dsitem));
+ nsCOMPtr<nsIScriptObjectPrincipal> doc =
+ do_QueryInterface(dsitem ? dsitem->GetDocument() : nullptr);
+ if (doc) {
+ nsCOMPtr<nsIURI> uri;
+ nsIPrincipal* principal = doc->GetPrincipal();
+ if (principal) {
+ principal->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ //
+ // remove any user:pass information
+ //
+ nsCOMPtr<nsIURIFixup> fixup(do_GetService(NS_URIFIXUP_CONTRACTID));
+ if (fixup) {
+ nsCOMPtr<nsIURI> tmpuri;
+ nsresult rv = fixup->CreateExposableURI(uri,getter_AddRefs(tmpuri));
+ if (NS_SUCCEEDED(rv) && tmpuri) {
+ // (don't bother if there's no host)
+ nsAutoCString host;
+ nsAutoCString prepath;
+ tmpuri->GetHost(host);
+ tmpuri->GetPrePath(prepath);
+ if (!host.IsEmpty()) {
+ //
+ // We have a scheme/host, update the title
+ //
+ title.Insert(NS_ConvertUTF8toUTF16(prepath) +
+ mTitleSeparator, 0);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ nsIDocument* document = docShellElement->OwnerDoc();
+ ErrorResult rv;
+ document->SetTitle(title, rv);
+ return rv.StealNSResult();
+ }
+
+ return mXULWindow->SetTitle(title.get());
+}
+
+//*****************************************************************************
+// nsContentTreeOwner: nsIWindowProvider
+//*****************************************************************************
+NS_IMETHODIMP
+nsContentTreeOwner::ProvideWindow(mozIDOMWindowProxy* aParent,
+ uint32_t aChromeFlags,
+ bool aCalledFromJS,
+ bool aPositionSpecified,
+ bool aSizeSpecified,
+ nsIURI* aURI,
+ const nsAString& aName,
+ const nsACString& aFeatures,
+ bool aForceNoOpener,
+ bool* aWindowIsNew,
+ mozIDOMWindowProxy** aReturn)
+{
+ NS_ENSURE_ARG_POINTER(aParent);
+
+ auto* parent = nsPIDOMWindowOuter::From(aParent);
+
+ *aReturn = nullptr;
+
+ if (!mXULWindow) {
+ // Nothing to do here
+ return NS_OK;
+ }
+
+#ifdef DEBUG
+ nsCOMPtr<nsIWebNavigation> parentNav = do_GetInterface(aParent);
+ nsCOMPtr<nsIDocShellTreeOwner> parentOwner = do_GetInterface(parentNav);
+ NS_ASSERTION(SameCOMIdentity(parentOwner,
+ static_cast<nsIDocShellTreeOwner*>(this)),
+ "Parent from wrong docshell tree?");
+#endif
+
+ // If aParent is inside an <iframe mozbrowser> and this isn't a request to
+ // open a modal-type window, we're going to create a new <iframe mozbrowser>
+ // and return its window here.
+ nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent);
+ if (docshell && docshell->GetIsInMozBrowserOrApp() &&
+ !(aChromeFlags & (nsIWebBrowserChrome::CHROME_MODAL |
+ nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
+ nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) {
+
+ BrowserElementParent::OpenWindowResult opened =
+ BrowserElementParent::OpenWindowInProcess(parent, aURI, aName,
+ aFeatures, aForceNoOpener, aReturn);
+
+ // If OpenWindowInProcess handled the open (by opening it or blocking the
+ // popup), tell our caller not to proceed trying to create a new window
+ // through other means.
+ if (opened != BrowserElementParent::OPEN_WINDOW_IGNORED) {
+ *aWindowIsNew = opened == BrowserElementParent::OPEN_WINDOW_ADDED;
+ return *aWindowIsNew ? NS_OK : NS_ERROR_ABORT;
+ }
+
+ // If we're in an app and the target is _blank, send the url to the OS
+ if (aName.LowerCaseEqualsLiteral("_blank")) {
+ nsCOMPtr<nsIExternalURLHandlerService> exUrlServ(
+ do_GetService(NS_EXTERNALURLHANDLERSERVICE_CONTRACTID));
+ if (exUrlServ) {
+
+ nsCOMPtr<nsIHandlerInfo> info;
+ bool found;
+ exUrlServ->GetURLHandlerInfoFromOS(aURI, &found, getter_AddRefs(info));
+
+ if (info && found) {
+ info->LaunchWithURI(aURI, nullptr);
+ return NS_ERROR_ABORT;
+ }
+
+ }
+ }
+ }
+
+ int32_t openLocation =
+ nsWindowWatcher::GetWindowOpenLocation(parent, aChromeFlags, aCalledFromJS,
+ aPositionSpecified, aSizeSpecified);
+
+ if (openLocation != nsIBrowserDOMWindow::OPEN_NEWTAB &&
+ openLocation != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) {
+ // Just open a window normally
+ return NS_OK;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWin;
+ mXULWindow->GetWindowDOMWindow(getter_AddRefs(domWin));
+ nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(domWin);
+ if (!chromeWin) {
+ // Really odd... but whatever
+ NS_WARNING("nsXULWindow's DOMWindow is not a chrome window");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIBrowserDOMWindow> browserDOMWin;
+ chromeWin->GetBrowserDOMWindow(getter_AddRefs(browserDOMWin));
+ if (!browserDOMWin) {
+ return NS_OK;
+ }
+
+ *aWindowIsNew = (openLocation != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW);
+
+ {
+ dom::AutoNoJSAPI nojsapi;
+
+ uint32_t flags = nsIBrowserDOMWindow::OPEN_NEW;
+ if (aForceNoOpener) {
+ flags |= nsIBrowserDOMWindow::OPEN_NO_OPENER;
+ }
+
+ // Get a new rendering area from the browserDOMWin. We don't want
+ // to be starting any loads here, so get it with a null URI.
+ //
+ // This method handles setting the opener for us, so we don't need to set it
+ // ourselves.
+ return browserDOMWin->OpenURI(nullptr, aParent,
+ openLocation,
+ flags, aReturn);
+ }
+}
+
+//*****************************************************************************
+// nsContentTreeOwner: Accessors
+//*****************************************************************************
+
+void nsContentTreeOwner::XULWindow(nsXULWindow* aXULWindow)
+{
+ mXULWindow = aXULWindow;
+ if (mXULWindow && mPrimary) {
+ // Get the window title modifiers
+ nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement();
+
+ nsAutoString contentTitleSetting;
+
+ if(docShellElement)
+ {
+ docShellElement->GetAttribute(NS_LITERAL_STRING("contenttitlesetting"), contentTitleSetting);
+ if(contentTitleSetting.EqualsLiteral("true"))
+ {
+ mContentTitleSetting = true;
+ docShellElement->GetAttribute(NS_LITERAL_STRING("titledefault"), mTitleDefault);
+ docShellElement->GetAttribute(NS_LITERAL_STRING("titlemodifier"), mWindowTitleModifier);
+ docShellElement->GetAttribute(NS_LITERAL_STRING("titlepreface"), mTitlePreface);
+ docShellElement->GetAttribute(NS_LITERAL_STRING("titlemenuseparator"), mTitleSeparator);
+ }
+ }
+ else
+ {
+ NS_ERROR("This condition should never happen. If it does, "
+ "we just won't get a modifier, but it still shouldn't happen.");
+ }
+ }
+}
+
+nsXULWindow* nsContentTreeOwner::XULWindow()
+{
+ return mXULWindow;
+}
+
+//*****************************************************************************
+//*** nsSiteWindow implementation
+//*****************************************************************************
+
+nsSiteWindow::nsSiteWindow(nsContentTreeOwner *aAggregator)
+{
+ mAggregator = aAggregator;
+}
+
+nsSiteWindow::~nsSiteWindow()
+{
+}
+
+NS_IMPL_ADDREF_USING_AGGREGATOR(nsSiteWindow, mAggregator)
+NS_IMPL_RELEASE_USING_AGGREGATOR(nsSiteWindow, mAggregator)
+
+NS_INTERFACE_MAP_BEGIN(nsSiteWindow)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIEmbeddingSiteWindow)
+NS_INTERFACE_MAP_END_AGGREGATED(mAggregator)
+
+NS_IMETHODIMP
+nsSiteWindow::SetDimensions(uint32_t aFlags,
+ int32_t aX, int32_t aY, int32_t aCX, int32_t aCY)
+{
+ // XXX we're ignoring aFlags
+ return mAggregator->SetPositionAndSize(aX, aY, aCX, aCY,
+ nsIBaseWindow::eRepaint);
+}
+
+NS_IMETHODIMP
+nsSiteWindow::GetDimensions(uint32_t aFlags,
+ int32_t *aX, int32_t *aY, int32_t *aCX, int32_t *aCY)
+{
+ // XXX we're ignoring aFlags
+ return mAggregator->GetPositionAndSize(aX, aY, aCX, aCY);
+}
+
+NS_IMETHODIMP
+nsSiteWindow::SetFocus(void)
+{
+#if 0
+ /* This implementation focuses the main document and could make sense.
+ However this method is actually being used from within
+ nsGlobalWindow::Focus (providing a hook for MDI embedding apps)
+ and it's better for our purposes to not pick a document and
+ focus it, but allow nsGlobalWindow to carry on unhindered.
+ */
+ nsXULWindow *window = mAggregator->XULWindow();
+ if (window) {
+ nsCOMPtr<nsIDocShell> docshell;
+ window->GetDocShell(getter_AddRefs(docshell));
+ if (docShell) {
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow(docShell->GetWindow());
+ if (domWindow)
+ domWindow->Focus();
+ }
+ }
+#endif
+ return NS_OK;
+}
+
+/* this implementation focuses another window. if there isn't another
+ window to focus, we do nothing. */
+NS_IMETHODIMP
+nsSiteWindow::Blur(void)
+{
+ NS_DEFINE_CID(kWindowMediatorCID, NS_WINDOWMEDIATOR_CID);
+
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+ nsCOMPtr<nsIXULWindow> xulWindow;
+ bool more, foundUs;
+ nsXULWindow *ourWindow = mAggregator->XULWindow();
+
+ {
+ nsCOMPtr<nsIWindowMediator> windowMediator(do_GetService(kWindowMediatorCID));
+ if (windowMediator)
+ windowMediator->GetZOrderXULWindowEnumerator(0, true,
+ getter_AddRefs(windowEnumerator));
+ }
+
+ if (!windowEnumerator)
+ return NS_ERROR_FAILURE;
+
+ // step through the top-level windows
+ foundUs = false;
+ windowEnumerator->HasMoreElements(&more);
+ while (more) {
+
+ nsCOMPtr<nsISupports> nextWindow;
+ nsCOMPtr<nsIXULWindow> nextXULWindow;
+
+ windowEnumerator->GetNext(getter_AddRefs(nextWindow));
+ nextXULWindow = do_QueryInterface(nextWindow);
+
+ // got it!(?)
+ if (foundUs) {
+ xulWindow = nextXULWindow;
+ break;
+ }
+
+ // remember the very first one, in case we have to wrap
+ if (!xulWindow)
+ xulWindow = nextXULWindow;
+
+ // look for us
+ if (nextXULWindow == ourWindow)
+ foundUs = true;
+
+ windowEnumerator->HasMoreElements(&more);
+ }
+
+ // change focus to the window we just found
+ if (xulWindow) {
+ nsCOMPtr<nsIDocShell> docshell;
+ xulWindow->GetDocShell(getter_AddRefs(docshell));
+ if (!docshell) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = docshell->GetWindow();
+ if (domWindow)
+ domWindow->Focus();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSiteWindow::GetVisibility(bool *aVisibility)
+{
+ return mAggregator->GetVisibility(aVisibility);
+}
+
+NS_IMETHODIMP
+nsSiteWindow::SetVisibility(bool aVisibility)
+{
+ return mAggregator->SetVisibility(aVisibility);
+}
+
+NS_IMETHODIMP
+nsSiteWindow::GetTitle(char16_t * *aTitle)
+{
+ return mAggregator->GetTitle(aTitle);
+}
+
+NS_IMETHODIMP
+nsSiteWindow::SetTitle(const char16_t * aTitle)
+{
+ return mAggregator->SetTitle(aTitle);
+}
+
+NS_IMETHODIMP
+nsSiteWindow::GetSiteWindow(void **aSiteWindow)
+{
+ return mAggregator->GetParentNativeWindow(aSiteWindow);
+}
+
diff --git a/components/appshell/src/nsContentTreeOwner.h b/components/appshell/src/nsContentTreeOwner.h
new file mode 100644
index 000000000..d6a0d42b3
--- /dev/null
+++ b/components/appshell/src/nsContentTreeOwner.h
@@ -0,0 +1,63 @@
+/* -*- Mode: IDL; 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/. */
+
+#ifndef nsContentTreeOwner_h__
+#define nsContentTreeOwner_h__
+
+// Helper Classes
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+// Interfaces Needed
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebBrowserChrome3.h"
+#include "nsIWindowProvider.h"
+
+class nsXULWindow;
+class nsSiteWindow;
+
+class nsContentTreeOwner final : public nsIDocShellTreeOwner,
+ public nsIBaseWindow,
+ public nsIInterfaceRequestor,
+ public nsIWebBrowserChrome3,
+ public nsIWindowProvider
+{
+friend class nsXULWindow;
+friend class nsSiteWindow;
+
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIBASEWINDOW
+ NS_DECL_NSIDOCSHELLTREEOWNER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIWEBBROWSERCHROME
+ NS_DECL_NSIWEBBROWSERCHROME2
+ NS_DECL_NSIWEBBROWSERCHROME3
+ NS_DECL_NSIWINDOWPROVIDER
+
+protected:
+ explicit nsContentTreeOwner(bool fPrimary);
+ virtual ~nsContentTreeOwner();
+
+ void XULWindow(nsXULWindow* aXULWindow);
+ nsXULWindow* XULWindow();
+
+protected:
+ nsXULWindow *mXULWindow;
+ nsSiteWindow *mSiteWindow;
+ bool mPrimary;
+ bool mContentTitleSetting;
+ nsString mWindowTitleModifier;
+ nsString mTitleSeparator;
+ nsString mTitlePreface;
+ nsString mTitleDefault;
+};
+
+#endif /* nsContentTreeOwner_h__ */
diff --git a/components/appshell/src/nsWebShellWindow.cpp b/components/appshell/src/nsWebShellWindow.cpp
new file mode 100644
index 000000000..7ec39a9cd
--- /dev/null
+++ b/components/appshell/src/nsWebShellWindow.cpp
@@ -0,0 +1,911 @@
+/* -*- 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 "nsWebShellWindow.h"
+
+#include "nsLayoutCID.h"
+#include "nsContentCID.h"
+#include "nsIWeakReference.h"
+#include "nsIContentViewer.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsIURL.h"
+#include "nsIIOService.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsIStringBundle.h"
+#include "nsReadableUtils.h"
+
+#include "nsContentUtils.h"
+#include "nsEscape.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWebNavigation.h"
+#include "nsIWindowWatcher.h"
+
+#include "nsIDOMXULElement.h"
+
+#include "nsWidgetInitData.h"
+#include "nsWidgetsCID.h"
+#include "nsIWidget.h"
+#include "nsIWidgetListener.h"
+
+#include "nsIDOMCharacterData.h"
+#include "nsIDOMNodeList.h"
+
+#include "nsITimer.h"
+#include "nsXULPopupManager.h"
+
+
+#include "nsIDOMXULDocument.h"
+
+#include "nsFocusManager.h"
+
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMElement.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsIObserverService.h"
+#include "prprf.h"
+
+#include "nsIScreenManager.h"
+#include "nsIScreen.h"
+
+#include "nsIContent.h" // for menus
+#include "nsIScriptSecurityManager.h"
+
+// For calculating size
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeItem.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/MouseEvents.h"
+
+#include "nsPIWindowRoot.h"
+
+#if defined(MOZ_WIDGET_GTK)
+#include "nsINativeMenuService.h"
+#define USE_NATIVE_MENUS
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/* Define Class IDs */
+static NS_DEFINE_CID(kWindowCID, NS_WINDOW_CID);
+
+#define SIZE_PERSISTENCE_TIMEOUT 500 // msec
+
+nsWebShellWindow::nsWebShellWindow(uint32_t aChromeFlags)
+ : nsXULWindow(aChromeFlags)
+ , mSPTimerLock("nsWebShellWindow.mSPTimerLock")
+ , mWidgetListenerDelegate(this)
+{
+}
+
+nsWebShellWindow::~nsWebShellWindow()
+{
+ MutexAutoLock lock(mSPTimerLock);
+ if (mSPTimer)
+ mSPTimer->Cancel();
+}
+
+NS_IMPL_ADDREF_INHERITED(nsWebShellWindow, nsXULWindow)
+NS_IMPL_RELEASE_INHERITED(nsWebShellWindow, nsXULWindow)
+
+NS_INTERFACE_MAP_BEGIN(nsWebShellWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+NS_INTERFACE_MAP_END_INHERITING(nsXULWindow)
+
+nsresult nsWebShellWindow::Initialize(nsIXULWindow* aParent,
+ nsIXULWindow* aOpener,
+ nsIURI* aUrl,
+ int32_t aInitialWidth,
+ int32_t aInitialHeight,
+ bool aIsHiddenWindow,
+ nsITabParent *aOpeningTab,
+ mozIDOMWindowProxy *aOpenerWindow,
+ nsWidgetInitData& widgetInitData)
+{
+ nsresult rv;
+ nsCOMPtr<nsIWidget> parentWidget;
+
+ mIsHiddenWindow = aIsHiddenWindow;
+
+ int32_t initialX = 0, initialY = 0;
+ nsCOMPtr<nsIBaseWindow> base(do_QueryInterface(aOpener));
+ if (base) {
+ rv = base->GetPositionAndSize(&mOpenerScreenRect.x,
+ &mOpenerScreenRect.y,
+ &mOpenerScreenRect.width,
+ &mOpenerScreenRect.height);
+ if (NS_FAILED(rv)) {
+ mOpenerScreenRect.SetEmpty();
+ } else {
+ double scale;
+ if (NS_SUCCEEDED(base->GetUnscaledDevicePixelsPerCSSPixel(&scale))) {
+ mOpenerScreenRect.x = NSToIntRound(mOpenerScreenRect.x / scale);
+ mOpenerScreenRect.y = NSToIntRound(mOpenerScreenRect.y / scale);
+ mOpenerScreenRect.width = NSToIntRound(mOpenerScreenRect.width / scale);
+ mOpenerScreenRect.height = NSToIntRound(mOpenerScreenRect.height / scale);
+ }
+ initialX = mOpenerScreenRect.x;
+ initialY = mOpenerScreenRect.y;
+ ConstrainToOpenerScreen(&initialX, &initialY);
+ }
+ }
+
+ // XXX: need to get the default window size from prefs...
+ // Doesn't come from prefs... will come from CSS/XUL/RDF
+ DesktopIntRect deskRect(initialX, initialY, aInitialWidth, aInitialHeight);
+
+ // Create top level window
+ mWindow = do_CreateInstance(kWindowCID, &rv);
+ if (NS_OK != rv) {
+ return rv;
+ }
+
+ /* This next bit is troublesome. We carry two different versions of a pointer
+ to our parent window. One is the parent window's widget, which is passed
+ to our own widget. The other is a weak reference we keep here to our
+ parent WebShellWindow. The former is useful to the widget, and we can't
+ trust its treatment of the parent reference because they're platform-
+ specific. The latter is useful to this class.
+ A better implementation would be one in which the parent keeps strong
+ references to its children and closes them before it allows itself
+ to be closed. This would mimic the behaviour of OSes that support
+ top-level child windows in OSes that do not. Later.
+ */
+ nsCOMPtr<nsIBaseWindow> parentAsWin(do_QueryInterface(aParent));
+ if (parentAsWin) {
+ parentAsWin->GetMainWidget(getter_AddRefs(parentWidget));
+ mParentWindow = do_GetWeakReference(aParent);
+ }
+
+ mWindow->SetWidgetListener(&mWidgetListenerDelegate);
+ rv = mWindow->Create((nsIWidget *)parentWidget, // Parent nsIWidget
+ nullptr, // Native parent widget
+ deskRect, // Widget dimensions
+ &widgetInitData); // Widget initialization data
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LayoutDeviceIntRect r = mWindow->GetClientBounds();
+ // Match the default background color of content. Important on windows
+ // since we no longer use content child widgets.
+ mWindow->SetBackgroundColor(NS_RGB(255,255,255));
+
+ // Create web shell
+ mDocShell = do_CreateInstance("@mozilla.org/docshell;1");
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+
+ mDocShell->SetOpener(aOpeningTab);
+
+ // Make sure to set the item type on the docshell _before_ calling
+ // Create() so it knows what type it is.
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(mDocShell));
+ NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_FAILURE);
+ NS_ENSURE_SUCCESS(EnsureChromeTreeOwner(), NS_ERROR_FAILURE);
+
+ docShellAsItem->SetTreeOwner(mChromeTreeOwner);
+ docShellAsItem->SetItemType(nsIDocShellTreeItem::typeChrome);
+
+ r.x = r.y = 0;
+ nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell));
+ NS_ENSURE_SUCCESS(docShellAsWin->InitWindow(nullptr, mWindow,
+ r.x, r.y, r.width, r.height), NS_ERROR_FAILURE);
+ NS_ENSURE_SUCCESS(docShellAsWin->Create(), NS_ERROR_FAILURE);
+
+ // Attach a WebProgress listener.during initialization...
+ nsCOMPtr<nsIWebProgress> webProgress(do_GetInterface(mDocShell, &rv));
+ if (webProgress) {
+ webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_NETWORK);
+ }
+
+ if (aOpenerWindow) {
+ nsPIDOMWindowOuter* window = mDocShell->GetWindow();
+ MOZ_ASSERT(window);
+ window->SetOpenerWindow(nsPIDOMWindowOuter::From(aOpenerWindow), true);
+ }
+
+ // Eagerly create an about:blank content viewer with the right principal here,
+ // rather than letting it happening in the upcoming call to
+ // SetInitialPrincipalToSubject. This avoids creating the about:blank document
+ // and then blowing it away with a second one, which can cause problems for the
+ // top-level chrome window case. See bug 789773.
+ // Note that we don't accept expanded principals here, similar to
+ // SetInitialPrincipalToSubject.
+ if (nsContentUtils::IsInitialized()) { // Sometimes this happens really early See bug 793370.
+ MOZ_ASSERT(mDocShell->ItemType() == nsIDocShellTreeItem::typeChrome);
+ nsCOMPtr<nsIPrincipal> principal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
+ if (nsContentUtils::IsExpandedPrincipal(principal)) {
+ principal = nullptr;
+ }
+ rv = mDocShell->CreateAboutBlankContentViewer(principal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDocument> doc = mDocShell->GetDocument();
+ NS_ENSURE_TRUE(!!doc, NS_ERROR_FAILURE);
+ doc->SetIsInitialDocument(true);
+ }
+
+ if (nullptr != aUrl) {
+ nsCString tmpStr;
+
+ rv = aUrl->GetSpec(tmpStr);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ConvertUTF8toUTF16 urlString(tmpStr);
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE);
+ rv = webNav->LoadURI(urlString.get(),
+ nsIWebNavigation::LOAD_FLAGS_NONE,
+ nullptr,
+ nullptr,
+ nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+nsIPresShell*
+nsWebShellWindow::GetPresShell()
+{
+ if (!mDocShell)
+ return nullptr;
+
+ return mDocShell->GetPresShell();
+}
+
+bool
+nsWebShellWindow::WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y)
+{
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ nsCOMPtr<nsPIDOMWindowOuter> window =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ pm->AdjustPopupsOnWindowChange(window);
+ }
+
+ // Notify all tabs that the widget moved.
+ if (mDocShell && mDocShell->GetWindow()) {
+ nsCOMPtr<EventTarget> eventTarget = mDocShell->GetWindow()->GetTopWindowRoot();
+ nsContentUtils::DispatchChromeEvent(mDocShell->GetDocument(),
+ eventTarget,
+ NS_LITERAL_STRING("MozUpdateWindowPos"),
+ false, false, nullptr);
+ }
+
+ // Persist position, but not immediately, in case this OS is firing
+ // repeated move events as the user drags the window
+ SetPersistenceTimer(PAD_POSITION);
+ return false;
+}
+
+bool
+nsWebShellWindow::WindowResized(nsIWidget* aWidget, int32_t aWidth, int32_t aHeight)
+{
+ nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(mDocShell));
+ if (shellAsWin) {
+ shellAsWin->SetPositionAndSize(0, 0, aWidth, aHeight, 0);
+ }
+ // Persist size, but not immediately, in case this OS is firing
+ // repeated size events as the user drags the sizing handle
+ if (!IsLocked())
+ SetPersistenceTimer(PAD_POSITION | PAD_SIZE | PAD_MISC);
+ return true;
+}
+
+bool
+nsWebShellWindow::RequestWindowClose(nsIWidget* aWidget)
+{
+ // Maintain a reference to this as it is about to get destroyed.
+ nsCOMPtr<nsIXULWindow> xulWindow(this);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window(mDocShell ? mDocShell->GetWindow() : nullptr);
+ nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(window);
+
+ nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ mozilla::DebugOnly<bool> dying;
+ MOZ_ASSERT(NS_SUCCEEDED(mDocShell->IsBeingDestroyed(&dying)) && dying,
+ "No presShell, but window is not being destroyed");
+ } else if (eventTarget) {
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eClose, nullptr,
+ WidgetMouseEvent::eReal);
+ if (NS_SUCCEEDED(eventTarget->DispatchDOMEvent(&event, nullptr, presContext, &status)) &&
+ status == nsEventStatus_eConsumeNoDefault)
+ return false;
+ }
+
+ Destroy();
+ return false;
+}
+
+void
+nsWebShellWindow::SizeModeChanged(nsSizeMode sizeMode)
+{
+ // An alwaysRaised (or higher) window will hide any newly opened normal
+ // browser windows, so here we just drop a raised window to the normal
+ // zlevel if it's maximized. We make no provision for automatically
+ // re-raising it when restored.
+ if (sizeMode == nsSizeMode_Maximized || sizeMode == nsSizeMode_Fullscreen) {
+ uint32_t zLevel;
+ GetZLevel(&zLevel);
+ if (zLevel > nsIXULWindow::normalZ)
+ SetZLevel(nsIXULWindow::normalZ);
+ }
+ mWindow->SetSizeMode(sizeMode);
+
+ // Persist mode, but not immediately, because in many (all?)
+ // cases this will merge with the similar call in NS_SIZE and
+ // write the attribute values only once.
+ SetPersistenceTimer(PAD_MISC);
+ nsCOMPtr<nsPIDOMWindowOuter> ourWindow =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ if (ourWindow) {
+ MOZ_ASSERT(ourWindow->IsOuterWindow());
+
+ // Ensure that the fullscreen state is synchronized between
+ // the widget and the outer window object.
+ if (sizeMode == nsSizeMode_Fullscreen) {
+ ourWindow->SetFullScreen(true);
+ }
+ else if (sizeMode != nsSizeMode_Minimized) {
+ if (ourWindow->GetFullScreen()) {
+ // The first SetFullscreenInternal call below ensures that we do
+ // not trigger any fullscreen transition even if the window was
+ // put in fullscreen only for the Fullscreen API. The second
+ // SetFullScreen call ensures that the window really exit from
+ // fullscreen even if it entered fullscreen for both Fullscreen
+ // Mode and Fullscreen API.
+ ourWindow->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen, false);
+ ourWindow->SetFullScreen(false);
+ }
+ }
+
+ // And always fire a user-defined sizemodechange event on the window
+ ourWindow->DispatchCustomEvent(NS_LITERAL_STRING("sizemodechange"));
+ }
+
+ nsIPresShell* presShell;
+ if ((presShell = GetPresShell())) {
+ presShell->GetPresContext()->SizeModeChanged(sizeMode);
+ }
+
+ // Note the current implementation of SetSizeMode just stores
+ // the new state; it doesn't actually resize. So here we store
+ // the state and pass the event on to the OS. The day is coming
+ // when we'll handle the event here, and the return result will
+ // then need to be different.
+}
+
+void
+nsWebShellWindow::UIResolutionChanged()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> ourWindow =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ if (ourWindow) {
+ MOZ_ASSERT(ourWindow->IsOuterWindow());
+ ourWindow->DispatchCustomEvent(NS_LITERAL_STRING("resolutionchange"));
+ }
+}
+
+void
+nsWebShellWindow::FullscreenChanged(bool aInFullscreen)
+{
+ if (mDocShell) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> ourWindow = mDocShell->GetWindow()) {
+ ourWindow->FinishFullscreenChange(aInFullscreen);
+ }
+ }
+}
+
+void
+nsWebShellWindow::OSToolbarButtonPressed()
+{
+ // Keep a reference as setting the chrome flags can fire events.
+ nsCOMPtr<nsIXULWindow> xulWindow(this);
+
+ // rjc: don't use "nsIWebBrowserChrome::CHROME_EXTRA"
+ // due to components with multiple sidebar components
+ // (such as Mail/News, Addressbook, etc)... and frankly,
+ // Mac IE, OmniWeb, and other Mac OS X apps all work this way
+ uint32_t chromeMask = (nsIWebBrowserChrome::CHROME_TOOLBAR |
+ nsIWebBrowserChrome::CHROME_LOCATIONBAR |
+ nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR);
+
+ nsCOMPtr<nsIWebBrowserChrome> wbc(do_GetInterface(xulWindow));
+ if (!wbc)
+ return;
+
+ uint32_t chromeFlags, newChromeFlags = 0;
+ wbc->GetChromeFlags(&chromeFlags);
+ newChromeFlags = chromeFlags & chromeMask;
+ if (!newChromeFlags) chromeFlags |= chromeMask;
+ else chromeFlags &= (~newChromeFlags);
+ wbc->SetChromeFlags(chromeFlags);
+}
+
+bool
+nsWebShellWindow::ZLevelChanged(bool aImmediate, nsWindowZ *aPlacement,
+ nsIWidget* aRequestBelow, nsIWidget** aActualBelow)
+{
+ if (aActualBelow)
+ *aActualBelow = nullptr;
+
+ return ConstrainToZLevel(aImmediate, aPlacement, aRequestBelow, aActualBelow);
+}
+
+void
+nsWebShellWindow::WindowActivated()
+{
+ nsCOMPtr<nsIXULWindow> xulWindow(this);
+
+ // focusing the window could cause it to close, so keep a reference to it
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell ? mDocShell->GetWindow() : nullptr;
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ if (fm && window)
+ fm->WindowRaised(window);
+
+ if (mChromeLoaded) {
+ PersistentAttributesDirty(PAD_POSITION | PAD_SIZE | PAD_MISC);
+ SavePersistentAttributes();
+ }
+}
+
+void
+nsWebShellWindow::WindowDeactivated()
+{
+ nsCOMPtr<nsIXULWindow> xulWindow(this);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ if (fm && window)
+ fm->WindowLowered(window);
+}
+
+#ifdef USE_NATIVE_MENUS
+static void LoadNativeMenus(nsIDOMDocument *aDOMDoc, nsIWidget *aParentWindow)
+{
+ nsCOMPtr<nsINativeMenuService> nms = do_GetService("@mozilla.org/widget/nativemenuservice;1");
+ if (!nms) {
+ return;
+ }
+
+ // Find the menubar tag (if there is more than one, we ignore all but
+ // the first).
+ nsCOMPtr<nsIDOMNodeList> menubarElements;
+ aDOMDoc->GetElementsByTagNameNS(NS_LITERAL_STRING("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"),
+ NS_LITERAL_STRING("menubar"),
+ getter_AddRefs(menubarElements));
+
+ nsCOMPtr<nsIDOMNode> menubarNode;
+ if (menubarElements)
+ menubarElements->Item(0, getter_AddRefs(menubarNode));
+
+ if (menubarNode) {
+ nsCOMPtr<nsIContent> menubarContent(do_QueryInterface(menubarNode));
+ nms->CreateNativeMenuBar(aParentWindow, menubarContent);
+ } else {
+ nms->CreateNativeMenuBar(aParentWindow, nullptr);
+ }
+}
+#endif
+
+namespace mozilla {
+
+class WebShellWindowTimerCallback final : public nsITimerCallback
+{
+public:
+ explicit WebShellWindowTimerCallback(nsWebShellWindow* aWindow)
+ : mWindow(aWindow)
+ {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD Notify(nsITimer* aTimer) override
+ {
+ // Although this object participates in a refcount cycle (this -> mWindow
+ // -> mSPTimer -> this), mSPTimer is a one-shot timer and releases this
+ // after it fires. So we don't need to release mWindow here.
+
+ mWindow->FirePersistenceTimer();
+ return NS_OK;
+ }
+
+private:
+ ~WebShellWindowTimerCallback() {}
+
+ RefPtr<nsWebShellWindow> mWindow;
+};
+
+NS_IMPL_ISUPPORTS(WebShellWindowTimerCallback, nsITimerCallback)
+
+} // namespace mozilla
+
+void
+nsWebShellWindow::SetPersistenceTimer(uint32_t aDirtyFlags)
+{
+ MutexAutoLock lock(mSPTimerLock);
+ if (!mSPTimer) {
+ mSPTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mSPTimer) {
+ NS_WARNING("Couldn't create @mozilla.org/timer;1 instance?");
+ return;
+ }
+ }
+
+ RefPtr<WebShellWindowTimerCallback> callback =
+ new WebShellWindowTimerCallback(this);
+ mSPTimer->InitWithCallback(callback, SIZE_PERSISTENCE_TIMEOUT,
+ nsITimer::TYPE_ONE_SHOT);
+
+ PersistentAttributesDirty(aDirtyFlags);
+}
+
+void
+nsWebShellWindow::FirePersistenceTimer()
+{
+ MutexAutoLock lock(mSPTimerLock);
+ SavePersistentAttributes();
+}
+
+
+//----------------------------------------
+// nsIWebProgessListener implementation
+//----------------------------------------
+NS_IMETHODIMP
+nsWebShellWindow::OnProgressChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebShellWindow::OnStateChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ // If the notification is not about a document finishing, then just
+ // ignore it...
+ if (!(aStateFlags & nsIWebProgressListener::STATE_STOP) ||
+ !(aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK)) {
+ return NS_OK;
+ }
+
+ if (mChromeLoaded)
+ return NS_OK;
+
+ // If this document notification is for a frame then ignore it...
+ nsCOMPtr<mozIDOMWindowProxy> eventWin;
+ aProgress->GetDOMWindow(getter_AddRefs(eventWin));
+ auto* eventPWin = nsPIDOMWindowOuter::From(eventWin);
+ if (eventPWin) {
+ nsPIDOMWindowOuter *rootPWin = eventPWin->GetPrivateRoot();
+ if (eventPWin != rootPWin)
+ return NS_OK;
+ }
+
+ mChromeLoaded = true;
+ mLockedUntilChromeLoad = false;
+
+#ifdef USE_NATIVE_MENUS
+ ///////////////////////////////
+ // Find the Menubar DOM and Load the menus, hooking them up to the loaded commands
+ ///////////////////////////////
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ nsCOMPtr<nsIDOMDocument> menubarDOMDoc(do_QueryInterface(cv->GetDocument()));
+ if (menubarDOMDoc)
+ LoadNativeMenus(menubarDOMDoc, mWindow);
+ }
+#endif // USE_NATIVE_MENUS
+
+ OnChromeLoaded();
+ LoadContentAreas();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebShellWindow::OnLocationChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ nsIURI *aURI,
+ uint32_t aFlags)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebShellWindow::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebShellWindow::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t state)
+{
+ NS_NOTREACHED("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+
+//----------------------------------------
+
+// if the main document URL specified URLs for any content areas, start them loading
+void nsWebShellWindow::LoadContentAreas() {
+
+ nsAutoString searchSpec;
+
+ // fetch the chrome document URL
+ nsCOMPtr<nsIContentViewer> contentViewer;
+ // yes, it's possible for the docshell to be null even this early
+ // see bug 57514.
+ if (mDocShell)
+ mDocShell->GetContentViewer(getter_AddRefs(contentViewer));
+ if (contentViewer) {
+ nsIDocument* doc = contentViewer->GetDocument();
+ if (doc) {
+ nsIURI* mainURL = doc->GetDocumentURI();
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(mainURL);
+ if (url) {
+ nsAutoCString search;
+ url->GetQuery(search);
+
+ AppendUTF8toUTF16(search, searchSpec);
+ }
+ }
+ }
+
+ // content URLs are specified in the search part of the URL
+ // as <contentareaID>=<escapedURL>[;(repeat)]
+ if (!searchSpec.IsEmpty()) {
+ int32_t begPos,
+ eqPos,
+ endPos;
+ nsString contentAreaID,
+ contentURL;
+ char *urlChar;
+ nsresult rv;
+ for (endPos = 0; endPos < (int32_t)searchSpec.Length(); ) {
+ // extract contentAreaID and URL substrings
+ begPos = endPos;
+ eqPos = searchSpec.FindChar('=', begPos);
+ if (eqPos < 0)
+ break;
+
+ endPos = searchSpec.FindChar(';', eqPos);
+ if (endPos < 0)
+ endPos = searchSpec.Length();
+ searchSpec.Mid(contentAreaID, begPos, eqPos-begPos);
+ searchSpec.Mid(contentURL, eqPos+1, endPos-eqPos-1);
+ endPos++;
+
+ // see if we have a docshell with a matching contentAreaID
+ nsCOMPtr<nsIDocShellTreeItem> content;
+ rv = GetContentShellById(contentAreaID.get(), getter_AddRefs(content));
+ if (NS_SUCCEEDED(rv) && content) {
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(content));
+ if (webNav) {
+ urlChar = ToNewCString(contentURL);
+ if (urlChar) {
+ nsUnescape(urlChar);
+ contentURL.AssignWithConversion(urlChar);
+ webNav->LoadURI(contentURL.get(),
+ nsIWebNavigation::LOAD_FLAGS_NONE,
+ nullptr,
+ nullptr,
+ nullptr);
+ free(urlChar);
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * ExecuteCloseHandler - Run the close handler, if any.
+ * @return true iff we found a close handler to run.
+ */
+bool nsWebShellWindow::ExecuteCloseHandler()
+{
+ /* If the event handler closes this window -- a likely scenario --
+ things get deleted out of order without this death grip.
+ (The problem may be the death grip in nsWindow::windowProc,
+ which forces this window's widget to remain alive longer
+ than it otherwise would.) */
+ nsCOMPtr<nsIXULWindow> kungFuDeathGrip(this);
+
+ nsCOMPtr<EventTarget> eventTarget;
+ if (mDocShell) {
+ eventTarget = do_QueryInterface(mDocShell->GetWindow());
+ }
+
+ if (eventTarget) {
+ nsCOMPtr<nsIContentViewer> contentViewer;
+ mDocShell->GetContentViewer(getter_AddRefs(contentViewer));
+ if (contentViewer) {
+ RefPtr<nsPresContext> presContext;
+ contentViewer->GetPresContext(getter_AddRefs(presContext));
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eClose, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsresult rv =
+ eventTarget->DispatchDOMEvent(&event, nullptr, presContext, &status);
+ if (NS_SUCCEEDED(rv) && status == nsEventStatus_eConsumeNoDefault)
+ return true;
+ // else fall through and return false
+ }
+ }
+
+ return false;
+} // ExecuteCloseHandler
+
+void nsWebShellWindow::ConstrainToOpenerScreen(int32_t* aX, int32_t* aY)
+{
+ if (mOpenerScreenRect.IsEmpty()) {
+ *aX = *aY = 0;
+ return;
+ }
+
+ int32_t left, top, width, height;
+ // Constrain initial positions to the same screen as opener
+ nsCOMPtr<nsIScreenManager> screenmgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (screenmgr) {
+ nsCOMPtr<nsIScreen> screen;
+ screenmgr->ScreenForRect(mOpenerScreenRect.x, mOpenerScreenRect.y,
+ mOpenerScreenRect.width, mOpenerScreenRect.height,
+ getter_AddRefs(screen));
+ if (screen) {
+ screen->GetAvailRectDisplayPix(&left, &top, &width, &height);
+ if (*aX < left || *aX > left + width) {
+ *aX = left;
+ }
+ if (*aY < top || *aY > top + height) {
+ *aY = top;
+ }
+ }
+ }
+}
+
+// nsIBaseWindow
+NS_IMETHODIMP nsWebShellWindow::Destroy()
+{
+ nsresult rv;
+ nsCOMPtr<nsIWebProgress> webProgress(do_GetInterface(mDocShell, &rv));
+ if (webProgress) {
+ webProgress->RemoveProgressListener(this);
+ }
+
+ nsCOMPtr<nsIXULWindow> kungFuDeathGrip(this);
+ {
+ MutexAutoLock lock(mSPTimerLock);
+ if (mSPTimer) {
+ mSPTimer->Cancel();
+ SavePersistentAttributes();
+ mSPTimer = nullptr;
+ }
+ }
+ return nsXULWindow::Destroy();
+}
+
+nsIXULWindow*
+nsWebShellWindow::WidgetListenerDelegate::GetXULWindow()
+{
+ return mWebShellWindow->GetXULWindow();
+}
+
+nsIPresShell*
+nsWebShellWindow::WidgetListenerDelegate::GetPresShell()
+{
+ return mWebShellWindow->GetPresShell();
+}
+
+bool
+nsWebShellWindow::WidgetListenerDelegate::WindowMoved(
+ nsIWidget* aWidget, int32_t aX, int32_t aY)
+{
+ RefPtr<nsWebShellWindow> holder = mWebShellWindow;
+ return holder->WindowMoved(aWidget, aX, aY);
+}
+
+bool
+nsWebShellWindow::WidgetListenerDelegate::WindowResized(
+ nsIWidget* aWidget, int32_t aWidth, int32_t aHeight)
+{
+ RefPtr<nsWebShellWindow> holder = mWebShellWindow;
+ return holder->WindowResized(aWidget, aWidth, aHeight);
+}
+
+bool
+nsWebShellWindow::WidgetListenerDelegate::RequestWindowClose(nsIWidget* aWidget)
+{
+ RefPtr<nsWebShellWindow> holder = mWebShellWindow;
+ return holder->RequestWindowClose(aWidget);
+}
+
+void
+nsWebShellWindow::WidgetListenerDelegate::SizeModeChanged(nsSizeMode aSizeMode)
+{
+ RefPtr<nsWebShellWindow> holder = mWebShellWindow;
+ holder->SizeModeChanged(aSizeMode);
+}
+
+void
+nsWebShellWindow::WidgetListenerDelegate::UIResolutionChanged()
+{
+ RefPtr<nsWebShellWindow> holder = mWebShellWindow;
+ holder->UIResolutionChanged();
+}
+
+void
+nsWebShellWindow::WidgetListenerDelegate::FullscreenChanged(bool aInFullscreen)
+{
+ RefPtr<nsWebShellWindow> holder = mWebShellWindow;
+ holder->FullscreenChanged(aInFullscreen);
+}
+
+void
+nsWebShellWindow::WidgetListenerDelegate::OSToolbarButtonPressed()
+{
+ RefPtr<nsWebShellWindow> holder = mWebShellWindow;
+ holder->OSToolbarButtonPressed();
+}
+
+bool
+nsWebShellWindow::WidgetListenerDelegate::ZLevelChanged(
+ bool aImmediate, nsWindowZ *aPlacement, nsIWidget* aRequestBelow,
+ nsIWidget** aActualBelow)
+{
+ RefPtr<nsWebShellWindow> holder = mWebShellWindow;
+ return holder->ZLevelChanged(aImmediate,
+ aPlacement,
+ aRequestBelow,
+ aActualBelow);
+}
+
+void
+nsWebShellWindow::WidgetListenerDelegate::WindowActivated()
+{
+ RefPtr<nsWebShellWindow> holder = mWebShellWindow;
+ holder->WindowActivated();
+}
+
+void
+nsWebShellWindow::WidgetListenerDelegate::WindowDeactivated()
+{
+ RefPtr<nsWebShellWindow> holder = mWebShellWindow;
+ holder->WindowDeactivated();
+}
diff --git a/components/appshell/src/nsWebShellWindow.h b/components/appshell/src/nsWebShellWindow.h
new file mode 100644
index 000000000..e2b796161
--- /dev/null
+++ b/components/appshell/src/nsWebShellWindow.h
@@ -0,0 +1,116 @@
+/* -*- 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 nsWebShellWindow_h__
+#define nsWebShellWindow_h__
+
+#include "mozilla/Mutex.h"
+#include "nsIWebProgressListener.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsXULWindow.h"
+#include "nsIWidgetListener.h"
+#include "nsITabParent.h"
+
+/* Forward declarations.... */
+class nsIURI;
+
+struct nsWidgetInitData;
+
+namespace mozilla {
+class WebShellWindowTimerCallback;
+} // namespace mozilla
+
+class nsWebShellWindow final : public nsXULWindow,
+ public nsIWebProgressListener
+{
+public:
+
+ // The implementation of non-refcounted nsIWidgetListener, which would hold a
+ // strong reference on stack before calling nsWebShellWindow
+ class WidgetListenerDelegate : public nsIWidgetListener
+ {
+ public:
+ explicit WidgetListenerDelegate(nsWebShellWindow* aWebShellWindow)
+ : mWebShellWindow(aWebShellWindow) {}
+
+ virtual nsIXULWindow* GetXULWindow() override;
+ virtual nsIPresShell* GetPresShell() override;
+ virtual bool WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y) override;
+ virtual bool WindowResized(nsIWidget* aWidget, int32_t aWidth, int32_t aHeight) override;
+ virtual bool RequestWindowClose(nsIWidget* aWidget) override;
+ virtual void SizeModeChanged(nsSizeMode sizeMode) override;
+ virtual void UIResolutionChanged() override;
+ virtual void FullscreenChanged(bool aInFullscreen) override;
+ virtual void OSToolbarButtonPressed() override;
+ virtual bool ZLevelChanged(bool aImmediate,
+ nsWindowZ *aPlacement,
+ nsIWidget* aRequestBelow,
+ nsIWidget** aActualBelow) override;
+ virtual void WindowActivated() override;
+ virtual void WindowDeactivated() override;
+
+ private:
+ // The lifetime of WidgetListenerDelegate is bound to nsWebShellWindow so
+ // we just use a raw pointer here.
+ nsWebShellWindow* mWebShellWindow;
+ };
+
+ explicit nsWebShellWindow(uint32_t aChromeFlags);
+
+ // nsISupports interface...
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsWebShellWindow methods...
+ nsresult Initialize(nsIXULWindow * aParent, nsIXULWindow * aOpener,
+ nsIURI* aUrl,
+ int32_t aInitialWidth, int32_t aInitialHeight,
+ bool aIsHiddenWindow,
+ nsITabParent *aOpeningTab,
+ mozIDOMWindowProxy *aOpenerWIndow,
+ nsWidgetInitData& widgetInitData);
+
+ nsresult Toolbar();
+
+ // nsIWebProgressListener
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ // nsIBaseWindow
+ NS_IMETHOD Destroy() override;
+
+ // nsIWidgetListener
+ nsIXULWindow* GetXULWindow() { return this; }
+ nsIPresShell* GetPresShell();
+ bool WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y);
+ bool WindowResized(nsIWidget* aWidget, int32_t aWidth, int32_t aHeight);
+ bool RequestWindowClose(nsIWidget* aWidget);
+ void SizeModeChanged(nsSizeMode sizeMode);
+ void UIResolutionChanged();
+ void FullscreenChanged(bool aInFullscreen);
+ void OSToolbarButtonPressed();
+ bool ZLevelChanged(bool aImmediate, nsWindowZ *aPlacement,
+ nsIWidget* aRequestBelow, nsIWidget** aActualBelow);
+ void WindowActivated();
+ void WindowDeactivated();
+
+protected:
+ friend class mozilla::WebShellWindowTimerCallback;
+
+ virtual ~nsWebShellWindow();
+
+ void LoadContentAreas();
+ bool ExecuteCloseHandler();
+ void ConstrainToOpenerScreen(int32_t* aX, int32_t* aY);
+
+ nsCOMPtr<nsITimer> mSPTimer;
+ mozilla::Mutex mSPTimerLock;
+ WidgetListenerDelegate mWidgetListenerDelegate;
+
+ void SetPersistenceTimer(uint32_t aDirtyFlags);
+ void FirePersistenceTimer();
+};
+
+
+#endif /* nsWebShellWindow_h__ */
diff --git a/components/appshell/src/nsWindowMediator.cpp b/components/appshell/src/nsWindowMediator.cpp
new file mode 100644
index 000000000..35ae550ae
--- /dev/null
+++ b/components/appshell/src/nsWindowMediator.cpp
@@ -0,0 +1,844 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsTArray.h"
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+#include "nsIDOMWindow.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "nsISimpleEnumerator.h"
+#include "nsAppShellWindowEnumerator.h"
+#include "nsWindowMediator.h"
+#include "nsIWindowMediatorListener.h"
+#include "nsXPIDLString.h"
+#include "nsGlobalWindow.h"
+
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIXULWindow.h"
+
+using namespace mozilla;
+
+static bool notifyOpenWindow(nsIWindowMediatorListener *aElement, void* aData);
+static bool notifyCloseWindow(nsIWindowMediatorListener *aElement, void* aData);
+static bool notifyWindowTitleChange(nsIWindowMediatorListener *aElement, void* aData);
+
+// for notifyWindowTitleChange
+struct WindowTitleData {
+ nsIXULWindow* mWindow;
+ const char16_t *mTitle;
+};
+
+nsresult
+nsWindowMediator::GetDOMWindow(nsIXULWindow* inWindow,
+ nsCOMPtr<nsPIDOMWindowOuter>& outDOMWindow)
+{
+ nsCOMPtr<nsIDocShell> docShell;
+
+ outDOMWindow = nullptr;
+ inWindow->GetDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ outDOMWindow = docShell->GetWindow();
+ return outDOMWindow ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsWindowMediator::nsWindowMediator() :
+ mEnumeratorList(), mOldestWindow(nullptr), mTopmostWindow(nullptr),
+ mTimeStamp(0), mSortingZOrder(false), mReady(false)
+{
+}
+
+nsWindowMediator::~nsWindowMediator()
+{
+ while (mOldestWindow)
+ UnregisterWindow(mOldestWindow);
+}
+
+nsresult nsWindowMediator::Init()
+{
+ nsresult rv;
+ nsCOMPtr<nsIObserverService> obsSvc =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = obsSvc->AddObserver(this, "xpcom-shutdown", true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mReady = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowMediator::RegisterWindow(nsIXULWindow* inWindow)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_STATE(mReady);
+
+ if (GetInfoFor(inWindow)) {
+ NS_ERROR("multiple window registration");
+ return NS_ERROR_FAILURE;
+ }
+
+ mTimeStamp++;
+
+ // Create window info struct and add to list of windows
+ nsWindowInfo* windowInfo = new nsWindowInfo(inWindow, mTimeStamp);
+
+ WindowTitleData winData = { inWindow, nullptr };
+ mListeners.EnumerateForwards(notifyOpenWindow, &winData);
+
+ if (mOldestWindow)
+ windowInfo->InsertAfter(mOldestWindow->mOlder, nullptr);
+ else
+ mOldestWindow = windowInfo;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::UnregisterWindow(nsIXULWindow* inWindow)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_STATE(mReady);
+ nsWindowInfo *info = GetInfoFor(inWindow);
+ if (info)
+ return UnregisterWindow(info);
+ return NS_ERROR_INVALID_ARG;
+}
+
+nsresult
+nsWindowMediator::UnregisterWindow(nsWindowInfo *inInfo)
+{
+ // Inform the iterators
+ uint32_t index = 0;
+ while (index < mEnumeratorList.Length()) {
+ mEnumeratorList[index]->WindowRemoved(inInfo);
+ index++;
+ }
+
+ WindowTitleData winData = { inInfo->mWindow.get(), nullptr };
+ mListeners.EnumerateForwards(notifyCloseWindow, &winData);
+
+ // Remove from the lists and free up
+ if (inInfo == mOldestWindow)
+ mOldestWindow = inInfo->mYounger;
+ if (inInfo == mTopmostWindow)
+ mTopmostWindow = inInfo->mLower;
+ inInfo->Unlink(true, true);
+ if (inInfo == mOldestWindow)
+ mOldestWindow = nullptr;
+ if (inInfo == mTopmostWindow)
+ mTopmostWindow = nullptr;
+ delete inInfo;
+
+ return NS_OK;
+}
+
+nsWindowInfo*
+nsWindowMediator::GetInfoFor(nsIXULWindow *aWindow)
+{
+ nsWindowInfo *info,
+ *listEnd;
+
+ if (!aWindow)
+ return nullptr;
+
+ info = mOldestWindow;
+ listEnd = nullptr;
+ while (info != listEnd) {
+ if (info->mWindow.get() == aWindow)
+ return info;
+ info = info->mYounger;
+ listEnd = mOldestWindow;
+ }
+ return nullptr;
+}
+
+nsWindowInfo*
+nsWindowMediator::GetInfoFor(nsIWidget *aWindow)
+{
+ nsWindowInfo *info,
+ *listEnd;
+
+ if (!aWindow)
+ return nullptr;
+
+ info = mOldestWindow;
+ listEnd = nullptr;
+
+ nsCOMPtr<nsIWidget> scanWidget;
+ while (info != listEnd) {
+ nsCOMPtr<nsIBaseWindow> base(do_QueryInterface(info->mWindow));
+ if (base)
+ base->GetMainWidget(getter_AddRefs(scanWidget));
+ if (aWindow == scanWidget.get())
+ return info;
+ info = info->mYounger;
+ listEnd = mOldestWindow;
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::GetEnumerator(const char16_t* inType, nsISimpleEnumerator** outEnumerator)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(outEnumerator);
+ NS_ENSURE_STATE(mReady);
+
+ RefPtr<nsAppShellWindowEnumerator> enumerator = new nsASDOMWindowEarlyToLateEnumerator(inType, *this);
+ enumerator.forget(outEnumerator);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::GetXULWindowEnumerator(const char16_t* inType, nsISimpleEnumerator** outEnumerator)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(outEnumerator);
+ NS_ENSURE_STATE(mReady);
+
+ RefPtr<nsAppShellWindowEnumerator> enumerator = new nsASXULWindowEarlyToLateEnumerator(inType, *this);
+ enumerator.forget(outEnumerator);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::GetZOrderDOMWindowEnumerator(
+ const char16_t *aWindowType, bool aFrontToBack,
+ nsISimpleEnumerator **_retval)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_ENSURE_STATE(mReady);
+
+ RefPtr<nsAppShellWindowEnumerator> enumerator;
+ if (aFrontToBack)
+ enumerator = new nsASDOMWindowFrontToBackEnumerator(aWindowType, *this);
+ else
+ enumerator = new nsASDOMWindowBackToFrontEnumerator(aWindowType, *this);
+
+ enumerator.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::GetZOrderXULWindowEnumerator(
+ const char16_t *aWindowType, bool aFrontToBack,
+ nsISimpleEnumerator **_retval)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_ENSURE_STATE(mReady);
+
+ RefPtr<nsAppShellWindowEnumerator> enumerator;
+ if (aFrontToBack)
+ enumerator = new nsASXULWindowFrontToBackEnumerator(aWindowType, *this);
+ else
+ enumerator = new nsASXULWindowBackToFrontEnumerator(aWindowType, *this);
+
+ enumerator.forget(_retval);
+ return NS_OK;
+}
+
+int32_t
+nsWindowMediator::AddEnumerator(nsAppShellWindowEnumerator * inEnumerator)
+{
+ return mEnumeratorList.AppendElement(inEnumerator) != nullptr;
+}
+
+int32_t
+nsWindowMediator::RemoveEnumerator(nsAppShellWindowEnumerator * inEnumerator)
+{
+ return mEnumeratorList.RemoveElement(inEnumerator);
+}
+
+// Returns the window of type inType ( if null return any window type ) which has the most recent
+// time stamp
+NS_IMETHODIMP
+nsWindowMediator::GetMostRecentWindow(const char16_t* inType,
+ mozIDOMWindowProxy** outWindow)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(outWindow);
+ *outWindow = nullptr;
+ if (!mReady)
+ return NS_OK;
+
+ // Find the most window with the highest time stamp that matches
+ // the requested type
+ nsWindowInfo* info = MostRecentWindowInfo(inType, false);
+ if (info && info->mWindow) {
+ nsCOMPtr<nsPIDOMWindowOuter> DOMWindow;
+ if (NS_SUCCEEDED(GetDOMWindow(info->mWindow, DOMWindow))) {
+ DOMWindow.forget(outWindow);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::GetMostRecentNonPBWindow(const char16_t* aType, mozIDOMWindowProxy** aWindow)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(aWindow);
+ *aWindow = nullptr;
+
+ nsWindowInfo *info = MostRecentWindowInfo(aType, true);
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow;
+ if (info && info->mWindow) {
+ GetDOMWindow(info->mWindow, domWindow);
+ }
+
+ if (!domWindow) {
+ return NS_ERROR_FAILURE;
+ }
+
+ domWindow.forget(aWindow);
+ return NS_OK;
+}
+
+nsWindowInfo*
+nsWindowMediator::MostRecentWindowInfo(const char16_t* inType,
+ bool aSkipPrivateBrowsingOrClosed)
+{
+ int32_t lastTimeStamp = -1;
+ nsAutoString typeString(inType);
+ bool allWindows = !inType || typeString.IsEmpty();
+
+ // Find the most recent window with the highest time stamp that matches
+ // the requested type and has the correct browsing mode.
+ nsWindowInfo* searchInfo = mOldestWindow;
+ nsWindowInfo* listEnd = nullptr;
+ nsWindowInfo* foundInfo = nullptr;
+ for (; searchInfo != listEnd; searchInfo = searchInfo->mYounger) {
+ listEnd = mOldestWindow;
+
+ if (!allWindows && !searchInfo->TypeEquals(typeString)) {
+ continue;
+ }
+ if (searchInfo->mTimeStamp < lastTimeStamp) {
+ continue;
+ }
+ if (!searchInfo->mWindow) {
+ continue;
+ }
+ if (aSkipPrivateBrowsingOrClosed) {
+ nsCOMPtr<nsIDocShell> docShell;
+ searchInfo->mWindow->GetDocShell(getter_AddRefs(docShell));
+ nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
+ if (!loadContext || loadContext->UsePrivateBrowsing()) {
+ continue;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> piwindow = docShell->GetWindow();
+ if (!piwindow || piwindow->Closed()) {
+ continue;
+ }
+ }
+
+ foundInfo = searchInfo;
+ lastTimeStamp = searchInfo->mTimeStamp;
+ }
+
+ return foundInfo;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::GetOuterWindowWithId(uint64_t aWindowID,
+ mozIDOMWindowProxy** aWindow)
+{
+ RefPtr<nsGlobalWindow> window = nsGlobalWindow::GetOuterWindowWithId(aWindowID);
+ nsCOMPtr<nsPIDOMWindowOuter> outer = window ? window->AsOuter() : nullptr;
+ outer.forget(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::GetCurrentInnerWindowWithId(uint64_t aWindowID,
+ mozIDOMWindow** aWindow)
+{
+ RefPtr<nsGlobalWindow> window = nsGlobalWindow::GetInnerWindowWithId(aWindowID);
+
+ // not found
+ if (!window)
+ return NS_OK;
+
+ nsCOMPtr<nsPIDOMWindowInner> inner = window->AsInner();
+ nsCOMPtr<nsPIDOMWindowOuter> outer = inner->GetOuterWindow();
+ NS_ENSURE_TRUE(outer, NS_ERROR_UNEXPECTED);
+
+ // outer is already using another inner, so it's same as not found
+ if (outer->GetCurrentInnerWindow() != inner)
+ return NS_OK;
+
+ inner.forget(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::UpdateWindowTimeStamp(nsIXULWindow* inWindow)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_STATE(mReady);
+ nsWindowInfo *info = GetInfoFor(inWindow);
+ if (info) {
+ // increment the window's time stamp
+ info->mTimeStamp = ++mTimeStamp;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::UpdateWindowTitle(nsIXULWindow* inWindow,
+ const char16_t* inTitle)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_STATE(mReady);
+ if (GetInfoFor(inWindow)) {
+ WindowTitleData winData = { inWindow, inTitle };
+ mListeners.EnumerateForwards(notifyWindowTitleChange, &winData);
+ }
+
+ return NS_OK;
+}
+
+/* This method's plan is to intervene only when absolutely necessary.
+ We will get requests to place our windows behind unknown windows.
+ For the most part, we need to leave those alone (turning them into
+ explicit requests to be on top breaks Windows.) So generally we
+ calculate a change as seldom as possible.
+*/
+NS_IMETHODIMP
+nsWindowMediator::CalculateZPosition(
+ nsIXULWindow *inWindow,
+ uint32_t inPosition,
+ nsIWidget *inBelow,
+ uint32_t *outPosition,
+ nsIWidget **outBelow,
+ bool *outAltered)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(outBelow);
+ NS_ENSURE_STATE(mReady);
+
+ *outBelow = nullptr;
+
+ if (!inWindow || !outPosition || !outAltered)
+ return NS_ERROR_NULL_POINTER;
+
+ if (inPosition != nsIWindowMediator::zLevelTop &&
+ inPosition != nsIWindowMediator::zLevelBottom &&
+ inPosition != nsIWindowMediator::zLevelBelow)
+ return NS_ERROR_INVALID_ARG;
+
+ nsWindowInfo *info = mTopmostWindow;
+ nsIXULWindow *belowWindow = nullptr;
+ bool found = false;
+ nsresult result = NS_OK;
+
+ *outPosition = inPosition;
+ *outAltered = false;
+
+ if (mSortingZOrder) { // don't fight SortZOrder()
+ *outBelow = inBelow;
+ NS_IF_ADDREF(*outBelow);
+ return NS_OK;
+ }
+
+ uint32_t inZ;
+ GetZLevel(inWindow, &inZ);
+
+ if (inPosition == nsIWindowMediator::zLevelBelow) {
+ // locate inBelow. use topmost if it can't be found or isn't in the
+ // z-order list
+ info = GetInfoFor(inBelow);
+ if (!info || (info->mYounger != info && info->mLower == info))
+ info = mTopmostWindow;
+ else
+ found = true;
+
+ if (!found) {
+ /* Treat unknown windows as a request to be on top.
+ Not as it should be, but that's what Windows gives us.
+ Note we change inPosition, but not *outPosition. This forces
+ us to go through the "on top" calculation just below, without
+ necessarily changing the output parameters. */
+ inPosition = nsIWindowMediator::zLevelTop;
+ }
+ }
+
+ if (inPosition == nsIWindowMediator::zLevelTop) {
+ if (mTopmostWindow && mTopmostWindow->mZLevel > inZ) {
+ // asked for topmost, can't have it. locate highest allowed position.
+ do {
+ if (info->mZLevel <= inZ)
+ break;
+ info = info->mLower;
+ } while (info != mTopmostWindow);
+
+ *outPosition = nsIWindowMediator::zLevelBelow;
+ belowWindow = info->mHigher->mWindow;
+ *outAltered = true;
+ }
+ } else if (inPosition == nsIWindowMediator::zLevelBottom) {
+ if (mTopmostWindow && mTopmostWindow->mHigher->mZLevel < inZ) {
+ // asked for bottommost, can't have it. locate lowest allowed position.
+ do {
+ info = info->mHigher;
+ if (info->mZLevel >= inZ)
+ break;
+ } while (info != mTopmostWindow);
+
+ *outPosition = nsIWindowMediator::zLevelBelow;
+ belowWindow = info->mWindow;
+ *outAltered = true;
+ }
+ } else {
+ unsigned long relativeZ;
+
+ // check that we're in the right z-plane
+ if (found) {
+ belowWindow = info->mWindow;
+ relativeZ = info->mZLevel;
+ if (relativeZ > inZ) {
+ // might be OK. is lower window, if any, lower?
+ if (info->mLower != info && info->mLower->mZLevel > inZ) {
+ do {
+ if (info->mZLevel <= inZ)
+ break;
+ info = info->mLower;
+ } while (info != mTopmostWindow);
+
+ belowWindow = info->mHigher->mWindow;
+ *outAltered = true;
+ }
+ } else if (relativeZ < inZ) {
+ // nope. look for a higher window to be behind.
+ do {
+ info = info->mHigher;
+ if (info->mZLevel >= inZ)
+ break;
+ } while (info != mTopmostWindow);
+
+ if (info->mZLevel >= inZ)
+ belowWindow = info->mWindow;
+ else
+ *outPosition = nsIWindowMediator::zLevelTop;
+ *outAltered = true;
+ } // else they're equal, so it's OK
+ }
+ }
+
+ if (NS_SUCCEEDED(result) && belowWindow) {
+ nsCOMPtr<nsIBaseWindow> base(do_QueryInterface(belowWindow));
+ if (base)
+ base->GetMainWidget(outBelow);
+ else
+ result = NS_ERROR_NO_INTERFACE;
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::SetZPosition(
+ nsIXULWindow *inWindow,
+ uint32_t inPosition,
+ nsIXULWindow *inBelow)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsWindowInfo *inInfo,
+ *belowInfo;
+
+ if ((inPosition != nsIWindowMediator::zLevelTop &&
+ inPosition != nsIWindowMediator::zLevelBottom &&
+ inPosition != nsIWindowMediator::zLevelBelow) ||
+ !inWindow) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mSortingZOrder) // don't fight SortZOrder()
+ return NS_OK;
+
+ NS_ENSURE_STATE(mReady);
+
+ /* Locate inWindow and unlink it from the z-order list.
+ It's important we look for it in the age list, not the z-order list.
+ This is because the former is guaranteed complete, while
+ now may be this window's first exposure to the latter. */
+ inInfo = GetInfoFor(inWindow);
+ if (!inInfo)
+ return NS_ERROR_INVALID_ARG;
+
+ // locate inBelow, place inWindow behind it
+ if (inPosition == nsIWindowMediator::zLevelBelow) {
+ belowInfo = GetInfoFor(inBelow);
+ // it had better also be in the z-order list
+ if (belowInfo &&
+ belowInfo->mYounger != belowInfo && belowInfo->mLower == belowInfo) {
+ belowInfo = nullptr;
+ }
+ if (!belowInfo) {
+ if (inBelow)
+ return NS_ERROR_INVALID_ARG;
+ else
+ inPosition = nsIWindowMediator::zLevelTop;
+ }
+ }
+ if (inPosition == nsIWindowMediator::zLevelTop ||
+ inPosition == nsIWindowMediator::zLevelBottom)
+ belowInfo = mTopmostWindow ? mTopmostWindow->mHigher : nullptr;
+
+ if (inInfo != belowInfo) {
+ inInfo->Unlink(false, true);
+ inInfo->InsertAfter(nullptr, belowInfo);
+ }
+ if (inPosition == nsIWindowMediator::zLevelTop)
+ mTopmostWindow = inInfo;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::GetZLevel(nsIXULWindow *aWindow, uint32_t *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nsIXULWindow::normalZ;
+ // This can fail during window destruction.
+ nsWindowInfo *info = GetInfoFor(aWindow);
+ if (info) {
+ *_retval = info->mZLevel;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::SetZLevel(nsIXULWindow *aWindow, uint32_t aZLevel)
+{
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ NS_ENSURE_STATE(mReady);
+
+ nsWindowInfo *info = GetInfoFor(aWindow);
+ NS_ASSERTION(info, "setting z level of unregistered window");
+ if (!info)
+ return NS_ERROR_FAILURE;
+
+ if (info->mZLevel != aZLevel) {
+ bool lowered = info->mZLevel > aZLevel;
+ info->mZLevel = aZLevel;
+ if (lowered)
+ SortZOrderFrontToBack();
+ else
+ SortZOrderBackToFront();
+ }
+ return NS_OK;
+}
+
+/* Fix potentially out-of-order windows by performing an insertion sort
+ on the z-order list. The method will work no matter how broken the
+ list, but its assumed usage is immediately after one window's z level
+ has been changed, so one window is potentially out of place. Such a sort
+ is most efficiently done in a particular direction. Use this one
+ if a window's z level has just been reduced, so the sort is most efficiently
+ done front to back.
+ Note it's hardly worth going to all the trouble to write two versions
+ of this method except that if we choose the inefficient sorting direction,
+ on slow systems windows could visibly bubble around the window that
+ was moved.
+*/
+void
+nsWindowMediator::SortZOrderFrontToBack()
+{
+ nsWindowInfo *scan, // scans list looking for problems
+ *search, // searches for correct placement for scan window
+ *prev, // previous search element
+ *lowest; // bottom-most window in list
+ bool finished;
+
+ if (!mTopmostWindow) // early during program execution there's no z list yet
+ return; // there's also only one window, so this is not dangerous
+
+ mSortingZOrder = true;
+
+ /* Step through the list from top to bottom. If we find a window which
+ should be moved down in the list, move it to its highest legal position. */
+ do {
+ finished = true;
+ lowest = mTopmostWindow->mHigher;
+ scan = mTopmostWindow;
+ while (scan != lowest) {
+ uint32_t scanZ = scan->mZLevel;
+ if (scanZ < scan->mLower->mZLevel) { // out of order
+ search = scan->mLower;
+ do {
+ prev = search;
+ search = search->mLower;
+ } while (prev != lowest && scanZ < search->mZLevel);
+
+ // reposition |scan| within the list
+ if (scan == mTopmostWindow)
+ mTopmostWindow = scan->mLower;
+ scan->Unlink(false, true);
+ scan->InsertAfter(nullptr, prev);
+
+ // fix actual window order
+ nsCOMPtr<nsIBaseWindow> base;
+ nsCOMPtr<nsIWidget> scanWidget;
+ nsCOMPtr<nsIWidget> prevWidget;
+ base = do_QueryInterface(scan->mWindow);
+ if (base)
+ base->GetMainWidget(getter_AddRefs(scanWidget));
+ base = do_QueryInterface(prev->mWindow);
+ if (base)
+ base->GetMainWidget(getter_AddRefs(prevWidget));
+ if (scanWidget)
+ scanWidget->PlaceBehind(eZPlacementBelow, prevWidget, false);
+
+ finished = false;
+ break;
+ }
+ scan = scan->mLower;
+ }
+ } while (!finished);
+
+ mSortingZOrder = false;
+}
+
+// see comment for SortZOrderFrontToBack
+void
+nsWindowMediator::SortZOrderBackToFront()
+{
+ nsWindowInfo *scan, // scans list looking for problems
+ *search, // searches for correct placement for scan window
+ *lowest; // bottom-most window in list
+ bool finished;
+
+ if (!mTopmostWindow) // early during program execution there's no z list yet
+ return; // there's also only one window, so this is not dangerous
+
+ mSortingZOrder = true;
+
+ /* Step through the list from bottom to top. If we find a window which
+ should be moved up in the list, move it to its lowest legal position. */
+ do {
+ finished = true;
+ lowest = mTopmostWindow->mHigher;
+ scan = lowest;
+ while (scan != mTopmostWindow) {
+ uint32_t scanZ = scan->mZLevel;
+ if (scanZ > scan->mHigher->mZLevel) { // out of order
+ search = scan;
+ do {
+ search = search->mHigher;
+ } while (search != lowest && scanZ > search->mZLevel);
+
+ // reposition |scan| within the list
+ if (scan != search && scan != search->mLower) {
+ scan->Unlink(false, true);
+ scan->InsertAfter(nullptr, search);
+ }
+ if (search == lowest)
+ mTopmostWindow = scan;
+
+ // fix actual window order
+ nsCOMPtr<nsIBaseWindow> base;
+ nsCOMPtr<nsIWidget> scanWidget;
+ nsCOMPtr<nsIWidget> searchWidget;
+ base = do_QueryInterface(scan->mWindow);
+ if (base)
+ base->GetMainWidget(getter_AddRefs(scanWidget));
+ if (mTopmostWindow != scan) {
+ base = do_QueryInterface(search->mWindow);
+ if (base)
+ base->GetMainWidget(getter_AddRefs(searchWidget));
+ }
+ if (scanWidget)
+ scanWidget->PlaceBehind(eZPlacementBelow, searchWidget, false);
+ finished = false;
+ break;
+ }
+ scan = scan->mHigher;
+ }
+ } while (!finished);
+
+ mSortingZOrder = false;
+}
+
+NS_IMPL_ISUPPORTS(nsWindowMediator,
+ nsIWindowMediator_44,
+ nsIWindowMediator,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+NS_IMETHODIMP
+nsWindowMediator::AddListener(nsIWindowMediatorListener* aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListeners.AppendObject(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::RemoveListener(nsIWindowMediatorListener* aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListeners.RemoveObject(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMediator::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp(aTopic, "xpcom-shutdown") && mReady) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ while (mOldestWindow)
+ UnregisterWindow(mOldestWindow);
+ mReady = false;
+ }
+ return NS_OK;
+}
+
+bool
+notifyOpenWindow(nsIWindowMediatorListener *aListener, void* aData)
+{
+ WindowTitleData* winData = static_cast<WindowTitleData*>(aData);
+ aListener->OnOpenWindow(winData->mWindow);
+
+ return true;
+}
+
+bool
+notifyCloseWindow(nsIWindowMediatorListener *aListener, void* aData)
+{
+ WindowTitleData* winData = static_cast<WindowTitleData*>(aData);
+ aListener->OnCloseWindow(winData->mWindow);
+
+ return true;
+}
+
+bool
+notifyWindowTitleChange(nsIWindowMediatorListener *aListener, void* aData)
+{
+ WindowTitleData* titleData = reinterpret_cast<WindowTitleData*>(aData);
+ aListener->OnWindowTitleChange(titleData->mWindow, titleData->mTitle);
+
+ return true;
+}
diff --git a/components/appshell/src/nsWindowMediator.h b/components/appshell/src/nsWindowMediator.h
new file mode 100644
index 000000000..a0eaab56d
--- /dev/null
+++ b/components/appshell/src/nsWindowMediator.h
@@ -0,0 +1,78 @@
+/* -*- 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 nsWindowMediator_h_
+#define nsWindowMediator_h_
+
+#include "nsCOMPtr.h"
+#include "nsIWindowMediator.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "nsXPIDLString.h"
+#include "nsWeakReference.h"
+#include "nsCOMArray.h"
+
+class nsAppShellWindowEnumerator;
+class nsASXULWindowEarlyToLateEnumerator;
+class nsASDOMWindowEarlyToLateEnumerator;
+class nsASDOMWindowFrontToBackEnumerator;
+class nsASXULWindowFrontToBackEnumerator;
+class nsASDOMWindowBackToFrontEnumerator;
+class nsASXULWindowBackToFrontEnumerator;
+class nsIWindowMediatorListener;
+struct nsWindowInfo;
+
+class nsWindowMediator :
+ public nsIWindowMediator_44,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+friend class nsAppShellWindowEnumerator;
+friend class nsASXULWindowEarlyToLateEnumerator;
+friend class nsASDOMWindowEarlyToLateEnumerator;
+friend class nsASDOMWindowFrontToBackEnumerator;
+friend class nsASXULWindowFrontToBackEnumerator;
+friend class nsASDOMWindowBackToFrontEnumerator;
+friend class nsASXULWindowBackToFrontEnumerator;
+
+protected:
+ virtual ~nsWindowMediator();
+
+public:
+ nsWindowMediator();
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWINDOWMEDIATOR
+ NS_DECL_NSIWINDOWMEDIATOR_44
+ NS_DECL_NSIOBSERVER
+
+ static nsresult GetDOMWindow(nsIXULWindow* inWindow,
+ nsCOMPtr<nsPIDOMWindowOuter>& outDOMWindow);
+
+private:
+ int32_t AddEnumerator(nsAppShellWindowEnumerator* inEnumerator);
+ int32_t RemoveEnumerator(nsAppShellWindowEnumerator* inEnumerator);
+ nsWindowInfo* MostRecentWindowInfo(const char16_t* inType,
+ bool aSkipPrivateBrowsingOrClosed = false);
+
+ nsresult UnregisterWindow(nsWindowInfo *inInfo);
+ nsWindowInfo *GetInfoFor(nsIXULWindow *aWindow);
+ nsWindowInfo *GetInfoFor(nsIWidget *aWindow);
+ void SortZOrderFrontToBack();
+ void SortZOrderBackToFront();
+
+ nsTArray<nsAppShellWindowEnumerator*> mEnumeratorList;
+ nsWindowInfo *mOldestWindow;
+ nsWindowInfo *mTopmostWindow;
+ int32_t mTimeStamp;
+ bool mSortingZOrder;
+ bool mReady;
+
+ nsCOMArray<nsIWindowMediatorListener> mListeners;
+};
+
+#endif
diff --git a/components/appshell/src/nsXULWindow.cpp b/components/appshell/src/nsXULWindow.cpp
new file mode 100644
index 000000000..45403b2b0
--- /dev/null
+++ b/components/appshell/src/nsXULWindow.cpp
@@ -0,0 +1,2325 @@
+/* -*- 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/MathAlgorithms.h"
+
+// Local includes
+#include "nsXULWindow.h"
+#include <algorithm>
+
+// Helper classes
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsWidgetsCID.h"
+#include "nsThreadUtils.h"
+#include "nsNetCID.h"
+#include "nsQueryObject.h"
+#include "mozilla/Sprintf.h"
+
+//Interfaces needed to be included
+#include "nsIAppShell.h"
+#include "nsIAppShellService.h"
+#include "nsIServiceManager.h"
+#include "nsIContentViewer.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMXULElement.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMScreen.h"
+#include "nsIEmbeddingSiteWindow.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIIOService.h"
+#include "nsILoadContext.h"
+#include "nsIObserverService.h"
+#include "nsIWindowMediator.h"
+#include "nsIScreenManager.h"
+#include "nsIScreen.h"
+#include "nsIScrollable.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIWindowWatcher.h"
+#include "nsIURI.h"
+#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsAppShellCID.h"
+#include "nsReadableUtils.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsContentUtils.h"
+#include "nsWebShellWindow.h" // get rid of this one, too...
+#include "nsGlobalWindow.h"
+
+#include "prenv.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/BarProps.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/TabParent.h"
+
+using namespace mozilla;
+using dom::AutoNoJSAPI;
+
+#define SIZEMODE_NORMAL NS_LITERAL_STRING("normal")
+#define SIZEMODE_MAXIMIZED NS_LITERAL_STRING("maximized")
+#define SIZEMODE_MINIMIZED NS_LITERAL_STRING("minimized")
+#define SIZEMODE_FULLSCREEN NS_LITERAL_STRING("fullscreen")
+
+#define WINDOWTYPE_ATTRIBUTE NS_LITERAL_STRING("windowtype")
+
+#define PERSIST_ATTRIBUTE NS_LITERAL_STRING("persist")
+#define SCREENX_ATTRIBUTE NS_LITERAL_STRING("screenX")
+#define SCREENY_ATTRIBUTE NS_LITERAL_STRING("screenY")
+#define WIDTH_ATTRIBUTE NS_LITERAL_STRING("width")
+#define HEIGHT_ATTRIBUTE NS_LITERAL_STRING("height")
+#define MODE_ATTRIBUTE NS_LITERAL_STRING("sizemode")
+#define ZLEVEL_ATTRIBUTE NS_LITERAL_STRING("zlevel")
+
+
+//*****************************************************************************
+//*** nsXULWindow: Object Management
+//*****************************************************************************
+
+nsXULWindow::nsXULWindow(uint32_t aChromeFlags)
+ : mChromeTreeOwner(nullptr),
+ mContentTreeOwner(nullptr),
+ mPrimaryContentTreeOwner(nullptr),
+ mModalStatus(NS_OK),
+ mContinueModalLoop(false),
+ mDebuting(false),
+ mChromeLoaded(false),
+ mShowAfterLoad(false),
+ mIntrinsicallySized(false),
+ mCenterAfterLoad(false),
+ mIsHiddenWindow(false),
+ mLockedUntilChromeLoad(false),
+ mIgnoreXULSize(false),
+ mIgnoreXULPosition(false),
+ mChromeFlagsFrozen(false),
+ mIgnoreXULSizeMode(false),
+ mDestroying(false),
+ mRegistered(false),
+ mContextFlags(0),
+ mPersistentAttributesDirty(0),
+ mPersistentAttributesMask(0),
+ mChromeFlags(aChromeFlags)
+{
+}
+
+nsXULWindow::~nsXULWindow()
+{
+ Destroy();
+}
+
+//*****************************************************************************
+// nsXULWindow::nsISupports
+//*****************************************************************************
+
+NS_IMPL_ADDREF(nsXULWindow)
+NS_IMPL_RELEASE(nsXULWindow)
+
+NS_INTERFACE_MAP_BEGIN(nsXULWindow)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIXULWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ if (aIID.Equals(NS_GET_IID(nsXULWindow)))
+ foundInterface = reinterpret_cast<nsISupports*>(this);
+ else
+NS_INTERFACE_MAP_END
+
+//*****************************************************************************
+// nsXULWindow::nsIIntefaceRequestor
+//*****************************************************************************
+
+NS_IMETHODIMP nsXULWindow::GetInterface(const nsIID& aIID, void** aSink)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aSink);
+
+ if (aIID.Equals(NS_GET_IID(nsIPrompt))) {
+ rv = EnsurePrompter();
+ if (NS_FAILED(rv)) return rv;
+ return mPrompter->QueryInterface(aIID, aSink);
+ }
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ rv = EnsureAuthPrompter();
+ if (NS_FAILED(rv)) return rv;
+ return mAuthPrompter->QueryInterface(aIID, aSink);
+ }
+ if (aIID.Equals(NS_GET_IID(mozIDOMWindowProxy))) {
+ return GetWindowDOMWindow(reinterpret_cast<mozIDOMWindowProxy**>(aSink));
+ }
+ if (aIID.Equals(NS_GET_IID(nsIDOMWindow))) {
+ nsCOMPtr<mozIDOMWindowProxy> window = nullptr;
+ rv = GetWindowDOMWindow(getter_AddRefs(window));
+ nsCOMPtr<nsIDOMWindow> domWindow = do_QueryInterface(window);
+ domWindow.forget(aSink);
+ return rv;
+ }
+ if (aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) {
+ nsCOMPtr<mozIDOMWindowProxy> window = nullptr;
+ rv = GetWindowDOMWindow(getter_AddRefs(window));
+ nsCOMPtr<nsIDOMWindowInternal> domWindowInternal = do_QueryInterface(window);
+ domWindowInternal.forget(aSink);
+ return rv;
+ }
+ if (aIID.Equals(NS_GET_IID(nsIWebBrowserChrome)) &&
+ NS_SUCCEEDED(EnsureContentTreeOwner()) &&
+ NS_SUCCEEDED(mContentTreeOwner->QueryInterface(aIID, aSink)))
+ return NS_OK;
+
+ if (aIID.Equals(NS_GET_IID(nsIEmbeddingSiteWindow)) &&
+ NS_SUCCEEDED(EnsureContentTreeOwner()) &&
+ NS_SUCCEEDED(mContentTreeOwner->QueryInterface(aIID, aSink)))
+ return NS_OK;
+
+ return QueryInterface(aIID, aSink);
+}
+
+//*****************************************************************************
+// nsXULWindow::nsIXULWindow
+//*****************************************************************************
+
+NS_IMETHODIMP nsXULWindow::GetDocShell(nsIDocShell** aDocShell)
+{
+ NS_ENSURE_ARG_POINTER(aDocShell);
+
+ *aDocShell = mDocShell;
+ NS_IF_ADDREF(*aDocShell);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetZLevel(uint32_t *outLevel)
+{
+ nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (mediator)
+ mediator->GetZLevel(this, outLevel);
+ else
+ *outLevel = normalZ;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetZLevel(uint32_t aLevel)
+{
+ nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (!mediator)
+ return NS_ERROR_FAILURE;
+
+ uint32_t zLevel;
+ mediator->GetZLevel(this, &zLevel);
+ if (zLevel == aLevel)
+ return NS_OK;
+
+ /* refuse to raise a maximized window above the normal browser level,
+ for fear it could hide newly opened browser windows */
+ if (aLevel > nsIXULWindow::normalZ && mWindow) {
+ nsSizeMode sizeMode = mWindow->SizeMode();
+ if (sizeMode == nsSizeMode_Maximized || sizeMode == nsSizeMode_Fullscreen) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // do it
+ mediator->SetZLevel(this, aLevel);
+ PersistentAttributesDirty(PAD_MISC);
+ SavePersistentAttributes();
+
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ nsCOMPtr<nsIDocument> doc = cv->GetDocument();
+ if (doc) {
+ ErrorResult rv;
+ RefPtr<dom::Event> event =
+ doc->CreateEvent(NS_LITERAL_STRING("Events"),rv);
+ if (event) {
+ event->InitEvent(NS_LITERAL_STRING("windowZLevel"), true, false);
+
+ event->SetTrusted(true);
+
+ bool defaultActionEnabled;
+ doc->DispatchEvent(event, &defaultActionEnabled);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetContextFlags(uint32_t *aContextFlags)
+{
+ NS_ENSURE_ARG_POINTER(aContextFlags);
+ *aContextFlags = mContextFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetContextFlags(uint32_t aContextFlags)
+{
+ mContextFlags = aContextFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetChromeFlags(uint32_t *aChromeFlags)
+{
+ NS_ENSURE_ARG_POINTER(aChromeFlags);
+ *aChromeFlags = mChromeFlags;
+ /* mChromeFlags is kept up to date, except for scrollbar visibility.
+ That can be changed directly by the content DOM window, which
+ doesn't know to update the chrome window. So that we must check
+ separately. */
+
+ // however, it's pointless to ask if the window isn't set up yet
+ if (!mChromeLoaded)
+ return NS_OK;
+
+ if (GetContentScrollbarVisibility())
+ *aChromeFlags |= nsIWebBrowserChrome::CHROME_SCROLLBARS;
+ else
+ *aChromeFlags &= ~nsIWebBrowserChrome::CHROME_SCROLLBARS;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetChromeFlags(uint32_t aChromeFlags)
+{
+ NS_ASSERTION(!mChromeFlagsFrozen,
+ "SetChromeFlags() after AssumeChromeFlagsAreFrozen()!");
+
+ mChromeFlags = aChromeFlags;
+ if (mChromeLoaded)
+ NS_ENSURE_SUCCESS(ApplyChromeFlags(), NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::AssumeChromeFlagsAreFrozen()
+{
+ mChromeFlagsFrozen = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetIntrinsicallySized(bool aIntrinsicallySized)
+{
+ mIntrinsicallySized = aIntrinsicallySized;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetIntrinsicallySized(bool* aIntrinsicallySized)
+{
+ NS_ENSURE_ARG_POINTER(aIntrinsicallySized);
+
+ *aIntrinsicallySized = mIntrinsicallySized;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetPrimaryContentShell(nsIDocShellTreeItem**
+ aDocShellTreeItem)
+{
+ NS_ENSURE_ARG_POINTER(aDocShellTreeItem);
+ NS_IF_ADDREF(*aDocShellTreeItem = mPrimaryContentShell);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULWindow::TabParentAdded(nsITabParent* aTab, bool aPrimary)
+{
+ if (aPrimary) {
+ mPrimaryTabParent = aTab;
+ mPrimaryContentShell = nullptr;
+ } else if (mPrimaryTabParent == aTab) {
+ mPrimaryTabParent = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULWindow::TabParentRemoved(nsITabParent* aTab)
+{
+ if (aTab == mPrimaryTabParent) {
+ mPrimaryTabParent = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULWindow::GetPrimaryTabParent(nsITabParent** aTab)
+{
+ nsCOMPtr<nsITabParent> tab = mPrimaryTabParent;
+ tab.forget(aTab);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetContentShellById(const char16_t* aID,
+ nsIDocShellTreeItem** aDocShellTreeItem)
+{
+ NS_ENSURE_ARG_POINTER(aDocShellTreeItem);
+ *aDocShellTreeItem = nullptr;
+
+ uint32_t count = mContentShells.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsContentShellInfo* shellInfo = mContentShells.ElementAt(i);
+ if (shellInfo->id.Equals(aID)) {
+ *aDocShellTreeItem = nullptr;
+ if (shellInfo->child)
+ CallQueryReferent(shellInfo->child.get(), aDocShellTreeItem);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsXULWindow::AddChildWindow(nsIXULWindow *aChild)
+{
+ // we're not really keeping track of this right now
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::RemoveChildWindow(nsIXULWindow *aChild)
+{
+ // we're not really keeping track of this right now
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::ShowModal()
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
+
+ // Store locally so it doesn't die on us
+ nsCOMPtr<nsIWidget> window = mWindow;
+ nsCOMPtr<nsIXULWindow> tempRef = this;
+
+ window->SetModal(true);
+ mContinueModalLoop = true;
+ EnableParent(false);
+
+ {
+ AutoNoJSAPI nojsapi;
+ nsIThread *thread = NS_GetCurrentThread();
+ while (mContinueModalLoop) {
+ if (!NS_ProcessNextEvent(thread))
+ break;
+ }
+ }
+
+ mContinueModalLoop = false;
+ window->SetModal(false);
+ /* Note there's no EnableParent(true) here to match the false one
+ above. That's done in ExitModalLoop. It's important that the parent
+ be re-enabled before this window is made invisible; to do otherwise
+ causes bizarre z-ordering problems. At this point, the window is
+ already invisible.
+ No known current implementation of Enable would have a problem with
+ re-enabling the parent twice, so we could do it again here without
+ breaking any current implementation. But that's unnecessary if the
+ modal loop is always exited using ExitModalLoop (the other way would be
+ to change the protected member variable directly.)
+ */
+
+ return mModalStatus;
+}
+
+//*****************************************************************************
+// nsXULWindow::nsIBaseWindow
+//*****************************************************************************
+
+NS_IMETHODIMP nsXULWindow::InitWindow(nativeWindow aParentNativeWindow,
+ nsIWidget* parentWidget, int32_t x, int32_t y, int32_t cx, int32_t cy)
+{
+ //XXX First Check In
+ NS_ASSERTION(false, "Not Yet Implemented");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::Create()
+{
+ //XXX First Check In
+ NS_ASSERTION(false, "Not Yet Implemented");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::Destroy()
+{
+ if (!mWindow)
+ return NS_OK;
+
+ // Ensure we don't reenter this code
+ if (mDestroying)
+ return NS_OK;
+
+ nsCOMPtr<nsIXULWindow> kungFuDeathGrip(this);
+
+ mozilla::AutoRestore<bool> guard(mDestroying);
+ mDestroying = true;
+
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ NS_ASSERTION(appShell, "Couldn't get appShell... xpcom shutdown?");
+ if (appShell)
+ appShell->UnregisterTopLevelWindow(static_cast<nsIXULWindow*>(this));
+
+ nsCOMPtr<nsIXULWindow> parentWindow(do_QueryReferent(mParentWindow));
+ if (parentWindow)
+ parentWindow->RemoveChildWindow(this);
+
+ // Remove modality (if any) and hide while destroying. More than
+ // a convenience, the hide prevents user interaction with the partially
+ // destroyed window. This is especially necessary when the eldest window
+ // in a stack of modal windows is destroyed first. It happens.
+ ExitModalLoop(NS_OK);
+ // XXX: Skip unmapping the window on Linux due to GLX hangs on the compositor
+ // thread with NVIDIA driver 310.32. We don't need to worry about user
+ // interactions with destroyed windows on X11 either.
+#ifndef MOZ_WIDGET_GTK
+ if (mWindow)
+ mWindow->Show(false);
+#endif
+
+#if defined(XP_WIN)
+ // We need to explicitly set the focus on Windows, but
+ // only if the parent is visible.
+ nsCOMPtr<nsIBaseWindow> parent(do_QueryReferent(mParentWindow));
+ if (parent) {
+ nsCOMPtr<nsIWidget> parentWidget;
+ parent->GetMainWidget(getter_AddRefs(parentWidget));
+ if (!parentWidget || parentWidget->IsVisible()) {
+ nsCOMPtr<nsIBaseWindow> baseHiddenWindow;
+ if (appShell) {
+ nsCOMPtr<nsIXULWindow> hiddenWindow;
+ appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
+ if (hiddenWindow)
+ baseHiddenWindow = do_GetInterface(hiddenWindow);
+ }
+ // somebody screwed up somewhere. hiddenwindow shouldn't be anybody's
+ // parent. still, when it happens, skip activating it.
+ if (baseHiddenWindow != parent) {
+ nsCOMPtr<nsIWidget> parentWidget;
+ parent->GetMainWidget(getter_AddRefs(parentWidget));
+ if (parentWidget)
+ parentWidget->PlaceBehind(eZPlacementTop, 0, true);
+ }
+ }
+ }
+#endif
+
+ mDOMWindow = nullptr;
+ if (mDocShell) {
+ nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(mDocShell));
+ shellAsWin->Destroy();
+ mDocShell = nullptr; // this can cause reentrancy of this function
+ }
+
+ // Remove our ref on the content shells
+ uint32_t count = mContentShells.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ nsContentShellInfo* shellInfo = mContentShells.ElementAt(i);
+ delete shellInfo;
+ }
+ mContentShells.Clear();
+ mPrimaryContentShell = nullptr;
+
+ if (mContentTreeOwner) {
+ mContentTreeOwner->XULWindow(nullptr);
+ NS_RELEASE(mContentTreeOwner);
+ }
+ if (mPrimaryContentTreeOwner) {
+ mPrimaryContentTreeOwner->XULWindow(nullptr);
+ NS_RELEASE(mPrimaryContentTreeOwner);
+ }
+ if (mChromeTreeOwner) {
+ mChromeTreeOwner->XULWindow(nullptr);
+ NS_RELEASE(mChromeTreeOwner);
+ }
+ if (mWindow) {
+ mWindow->SetWidgetListener(nullptr); // nsWebShellWindow hackery
+ mWindow->Destroy();
+ mWindow = nullptr;
+ }
+
+ if (!mIsHiddenWindow && mRegistered) {
+ /* Inform appstartup we've destroyed this window and it could
+ quit now if it wanted. This must happen at least after mDocShell
+ is destroyed, because onunload handlers fire then, and those being
+ script, anything could happen. A new window could open, even.
+ See bug 130719. */
+ nsCOMPtr<nsIObserverService> obssvc = services::GetObserverService();
+ NS_ASSERTION(obssvc, "Couldn't get observer service?");
+
+ if (obssvc)
+ obssvc->NotifyObservers(nullptr, "xul-window-destroyed", nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetDevicePixelsPerDesktopPixel(double *aScale)
+{
+ *aScale = mWindow ? mWindow->GetDesktopToDeviceScale().scale : 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetUnscaledDevicePixelsPerCSSPixel(double *aScale)
+{
+ *aScale = mWindow ? mWindow->GetDefaultScale().scale : 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetPositionDesktopPix(int32_t aX, int32_t aY)
+{
+ nsresult rv = mWindow->Move(aX, aY);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ if (!mChromeLoaded) {
+ // If we're called before the chrome is loaded someone obviously wants this
+ // window at this position. We don't persist this one-time position.
+ mIgnoreXULPosition = true;
+ return NS_OK;
+ }
+ PersistentAttributesDirty(PAD_POSITION);
+ SavePersistentAttributes();
+ return NS_OK;
+}
+
+// The parameters here are device pixels; do the best we can to convert to
+// desktop px, using the window's current scale factor (if available).
+NS_IMETHODIMP nsXULWindow::SetPosition(int32_t aX, int32_t aY)
+{
+ // Don't reset the window's size mode here - platforms that don't want to move
+ // maximized windows should reset it in their respective Move implementation.
+ DesktopToLayoutDeviceScale currScale = mWindow->GetDesktopToDeviceScale();
+ DesktopPoint pos = LayoutDeviceIntPoint(aX, aY) / currScale;
+ return SetPositionDesktopPix(pos.x, pos.y);
+}
+
+NS_IMETHODIMP nsXULWindow::GetPosition(int32_t* aX, int32_t* aY)
+{
+ return GetPositionAndSize(aX, aY, nullptr, nullptr);
+}
+
+NS_IMETHODIMP nsXULWindow::SetSize(int32_t aCX, int32_t aCY, bool aRepaint)
+{
+ /* any attempt to set the window's size or position overrides the window's
+ zoom state. this is important when these two states are competing while
+ the window is being opened. but it should probably just always be so. */
+ mWindow->SetSizeMode(nsSizeMode_Normal);
+
+ mIntrinsicallySized = false;
+
+ DesktopToLayoutDeviceScale scale = mWindow->GetDesktopToDeviceScale();
+ DesktopSize size = LayoutDeviceIntSize(aCX, aCY) / scale;
+ nsresult rv = mWindow->Resize(size.width, size.height, aRepaint);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ if (!mChromeLoaded) {
+ // If we're called before the chrome is loaded someone obviously wants this
+ // window at this size & in the normal size mode (since it is the only mode
+ // in which setting dimensions makes sense). We don't persist this one-time
+ // size.
+ mIgnoreXULSize = true;
+ mIgnoreXULSizeMode = true;
+ return NS_OK;
+ }
+ PersistentAttributesDirty(PAD_SIZE);
+ SavePersistentAttributes();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetSize(int32_t* aCX, int32_t* aCY)
+{
+ return GetPositionAndSize(nullptr, nullptr, aCX, aCY);
+}
+
+NS_IMETHODIMP nsXULWindow::SetPositionAndSize(int32_t aX, int32_t aY,
+ int32_t aCX, int32_t aCY, uint32_t aFlags)
+{
+ /* any attempt to set the window's size or position overrides the window's
+ zoom state. this is important when these two states are competing while
+ the window is being opened. but it should probably just always be so. */
+ mWindow->SetSizeMode(nsSizeMode_Normal);
+
+ mIntrinsicallySized = false;
+
+ DesktopToLayoutDeviceScale scale = mWindow->GetDesktopToDeviceScale();
+ DesktopRect rect = LayoutDeviceIntRect(aX, aY, aCX, aCY) / scale;
+ nsresult rv = mWindow->Resize(rect.x, rect.y, rect.width, rect.height,
+ !!(aFlags & nsIBaseWindow::eRepaint));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ if (!mChromeLoaded) {
+ // If we're called before the chrome is loaded someone obviously wants this
+ // window at this size and position. We don't persist this one-time setting.
+ mIgnoreXULPosition = true;
+ mIgnoreXULSize = true;
+ mIgnoreXULSizeMode = true;
+ return NS_OK;
+ }
+ PersistentAttributesDirty(PAD_POSITION | PAD_SIZE);
+ SavePersistentAttributes();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetPositionAndSize(int32_t* x, int32_t* y, int32_t* cx,
+ int32_t* cy)
+{
+
+ if (!mWindow)
+ return NS_ERROR_FAILURE;
+
+ LayoutDeviceIntRect rect = mWindow->GetScreenBounds();
+
+ if (x)
+ *x = rect.x;
+ if (y)
+ *y = rect.y;
+ if (cx)
+ *cx = rect.width;
+ if (cy)
+ *cy = rect.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::Center(nsIXULWindow *aRelative, bool aScreen, bool aAlert)
+{
+ int32_t left, top, width, height,
+ ourWidth, ourHeight;
+ bool screenCoordinates = false,
+ windowCoordinates = false;
+ nsresult result;
+
+ if (!mChromeLoaded) {
+ // note we lose the parameters. at time of writing, this isn't a problem.
+ mCenterAfterLoad = true;
+ return NS_OK;
+ }
+
+ if (!aScreen && !aRelative)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIScreenManager> screenmgr = do_GetService("@mozilla.org/gfx/screenmanager;1", &result);
+ if (NS_FAILED(result))
+ return result;
+
+ nsCOMPtr<nsIScreen> screen;
+
+ if (aRelative) {
+ nsCOMPtr<nsIBaseWindow> base(do_QueryInterface(aRelative, &result));
+ if (base) {
+ // get window rect
+ result = base->GetPositionAndSize(&left, &top, &width, &height);
+ if (NS_SUCCEEDED(result)) {
+ double scale;
+ if (NS_SUCCEEDED(base->GetDevicePixelsPerDesktopPixel(&scale))) {
+ left = NSToIntRound(left / scale);
+ top = NSToIntRound(top / scale);
+ width = NSToIntRound(width / scale);
+ height = NSToIntRound(height / scale);
+ }
+ // if centering on screen, convert that to the corresponding screen
+ if (aScreen)
+ screenmgr->ScreenForRect(left, top, width, height, getter_AddRefs(screen));
+ else
+ windowCoordinates = true;
+ } else {
+ // something's wrong with the reference window.
+ // fall back to the primary screen
+ aRelative = 0;
+ aScreen = true;
+ }
+ }
+ }
+ if (!aRelative) {
+ if (!mOpenerScreenRect.IsEmpty()) {
+ // FIXME - check if these are device or display pixels
+ screenmgr->ScreenForRect(mOpenerScreenRect.x, mOpenerScreenRect.y,
+ mOpenerScreenRect.width, mOpenerScreenRect.height,
+ getter_AddRefs(screen));
+ } else {
+ screenmgr->GetPrimaryScreen(getter_AddRefs(screen));
+ }
+ }
+
+ if (aScreen && screen) {
+ screen->GetAvailRectDisplayPix(&left, &top, &width, &height);
+ screenCoordinates = true;
+ }
+
+ if (screenCoordinates || windowCoordinates) {
+ NS_ASSERTION(mWindow, "what, no window?");
+ double scale = mWindow->GetDesktopToDeviceScale().scale;
+ GetSize(&ourWidth, &ourHeight);
+ int32_t scaledWidth, scaledHeight;
+ scaledWidth = NSToIntRound(ourWidth / scale);
+ scaledHeight = NSToIntRound(ourHeight / scale);
+ left += (width - scaledWidth) / 2;
+ top += (height - scaledHeight) / (aAlert ? 3 : 2);
+ if (windowCoordinates) {
+ mWindow->ConstrainPosition(false, &left, &top);
+ }
+ SetPosition(left * scale, top * scale);
+
+ // If moving the window caused it to change size,
+ // re-do the centering.
+ int32_t newWidth, newHeight;
+ GetSize(&newWidth, &newHeight);
+ if (newWidth != ourWidth || newHeight != ourHeight) {
+ return Center(aRelative, aScreen, aAlert);
+ }
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsXULWindow::Repaint(bool aForce)
+{
+ //XXX First Check In
+ NS_ASSERTION(false, "Not Yet Implemented");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetParentWidget(nsIWidget** aParentWidget)
+{
+ NS_ENSURE_ARG_POINTER(aParentWidget);
+ NS_ENSURE_STATE(mWindow);
+
+ NS_IF_ADDREF(*aParentWidget = mWindow->GetParent());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetParentWidget(nsIWidget* aParentWidget)
+{
+ //XXX First Check In
+ NS_ASSERTION(false, "Not Yet Implemented");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetParentNativeWindow(nativeWindow* aParentNativeWindow)
+{
+ NS_ENSURE_ARG_POINTER(aParentNativeWindow);
+
+ nsCOMPtr<nsIWidget> parentWidget;
+ NS_ENSURE_SUCCESS(GetParentWidget(getter_AddRefs(parentWidget)), NS_ERROR_FAILURE);
+
+ if (parentWidget) {
+ *aParentNativeWindow = parentWidget->GetNativeData(NS_NATIVE_WIDGET);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetParentNativeWindow(nativeWindow aParentNativeWindow)
+{
+ //XXX First Check In
+ NS_ASSERTION(false, "Not Yet Implemented");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetNativeHandle(nsAString& aNativeHandle)
+{
+ nsCOMPtr<nsIWidget> mainWidget;
+ NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(mainWidget)), NS_ERROR_FAILURE);
+
+ if (mainWidget) {
+ nativeWindow nativeWindowPtr = mainWidget->GetNativeData(NS_NATIVE_WINDOW);
+ /* the nativeWindow pointer is converted to and exposed as a string. This
+ is a more reliable way not to lose information (as opposed to JS
+ |Number| for instance) */
+ aNativeHandle = NS_ConvertASCIItoUTF16(nsPrintfCString("0x%p", nativeWindowPtr));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetVisibility(bool* aVisibility)
+{
+ NS_ENSURE_ARG_POINTER(aVisibility);
+
+ // Always claim to be visible for now. See bug
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=306245.
+
+ *aVisibility = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetVisibility(bool aVisibility)
+{
+ if (!mChromeLoaded) {
+ mShowAfterLoad = aVisibility;
+ return NS_OK;
+ }
+
+ if (mDebuting) {
+ return NS_OK;
+ }
+ mDebuting = true; // (Show / Focus is recursive)
+
+ //XXXTAB Do we really need to show docshell and the window? Isn't
+ // the window good enough?
+ nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(mDocShell));
+ shellAsWin->SetVisibility(aVisibility);
+ // Store locally so it doesn't die on us. 'Show' can result in the window
+ // being closed with nsXULWindow::Destroy being called. That would set
+ // mWindow to null and posibly destroy the nsIWidget while its Show method
+ // is on the stack. We need to keep it alive until Show finishes.
+ nsCOMPtr<nsIWidget> window = mWindow;
+ window->Show(aVisibility);
+
+ nsCOMPtr<nsIWindowMediator> windowMediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (windowMediator)
+ windowMediator->UpdateWindowTimeStamp(static_cast<nsIXULWindow*>(this));
+
+ // notify observers so that we can hide the splash screen if possible
+ nsCOMPtr<nsIObserverService> obssvc = services::GetObserverService();
+ NS_ASSERTION(obssvc, "Couldn't get observer service.");
+ if (obssvc) {
+ obssvc->NotifyObservers(nullptr, "xul-window-visible", nullptr);
+ }
+
+ mDebuting = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetEnabled(bool *aEnabled)
+{
+ NS_ENSURE_ARG_POINTER(aEnabled);
+
+ if (mWindow) {
+ *aEnabled = mWindow->IsEnabled();
+ return NS_OK;
+ }
+
+ *aEnabled = true; // better guess than most
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsXULWindow::SetEnabled(bool aEnable)
+{
+ if (mWindow) {
+ mWindow->Enable(aEnable);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsXULWindow::GetMainWidget(nsIWidget** aMainWidget)
+{
+ NS_ENSURE_ARG_POINTER(aMainWidget);
+
+ *aMainWidget = mWindow;
+ NS_IF_ADDREF(*aMainWidget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetFocus()
+{
+ //XXX First Check In
+ NS_ASSERTION(false, "Not Yet Implemented");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetTitle(char16_t** aTitle)
+{
+ NS_ENSURE_ARG_POINTER(aTitle);
+
+ *aTitle = ToNewUnicode(mTitle);
+ if (!*aTitle)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetTitle(const char16_t* aTitle)
+{
+ NS_ENSURE_STATE(mWindow);
+ mTitle.Assign(aTitle);
+ mTitle.StripChars("\n\r");
+ NS_ENSURE_SUCCESS(mWindow->SetTitle(mTitle), NS_ERROR_FAILURE);
+
+ // Tell the window mediator that a title has changed
+ nsCOMPtr<nsIWindowMediator> windowMediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (!windowMediator)
+ return NS_OK;
+
+ windowMediator->UpdateWindowTitle(static_cast<nsIXULWindow*>(this), aTitle);
+
+ return NS_OK;
+}
+
+
+//*****************************************************************************
+// nsXULWindow: Helpers
+//*****************************************************************************
+
+NS_IMETHODIMP nsXULWindow::EnsureChromeTreeOwner()
+{
+ if (mChromeTreeOwner)
+ return NS_OK;
+
+ mChromeTreeOwner = new nsChromeTreeOwner();
+ NS_ADDREF(mChromeTreeOwner);
+ mChromeTreeOwner->XULWindow(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::EnsureContentTreeOwner()
+{
+ if (mContentTreeOwner)
+ return NS_OK;
+
+ mContentTreeOwner = new nsContentTreeOwner(false);
+ NS_ADDREF(mContentTreeOwner);
+ mContentTreeOwner->XULWindow(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::EnsurePrimaryContentTreeOwner()
+{
+ if (mPrimaryContentTreeOwner)
+ return NS_OK;
+
+ mPrimaryContentTreeOwner = new nsContentTreeOwner(true);
+ NS_ADDREF(mPrimaryContentTreeOwner);
+ mPrimaryContentTreeOwner->XULWindow(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::EnsurePrompter()
+{
+ if (mPrompter)
+ return NS_OK;
+
+ nsCOMPtr<mozIDOMWindowProxy> ourWindow;
+ nsresult rv = GetWindowDOMWindow(getter_AddRefs(ourWindow));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID);
+ if (wwatch)
+ wwatch->GetNewPrompter(ourWindow, getter_AddRefs(mPrompter));
+ }
+ return mPrompter ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsXULWindow::EnsureAuthPrompter()
+{
+ if (mAuthPrompter)
+ return NS_OK;
+
+ nsCOMPtr<mozIDOMWindowProxy> ourWindow;
+ nsresult rv = GetWindowDOMWindow(getter_AddRefs(ourWindow));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch)
+ wwatch->GetNewAuthPrompter(ourWindow, getter_AddRefs(mAuthPrompter));
+ }
+ return mAuthPrompter ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void nsXULWindow::OnChromeLoaded()
+{
+ nsresult rv = EnsureContentTreeOwner();
+
+ if (NS_SUCCEEDED(rv)) {
+ mChromeLoaded = true;
+ ApplyChromeFlags();
+ SyncAttributesToWidget();
+
+ int32_t specWidth = -1, specHeight = -1;
+ bool gotSize = false;
+
+ if (!mIgnoreXULSize) {
+ gotSize = LoadSizeFromXUL(specWidth, specHeight);
+ }
+
+ bool positionSet = !mIgnoreXULPosition;
+ nsCOMPtr<nsIXULWindow> parentWindow(do_QueryReferent(mParentWindow));
+#if defined(XP_UNIX)
+ // don't override WM placement on unix for independent, top-level windows
+ // (however, we think the benefits of intelligent dependent window placement
+ // trump that override.)
+ if (!parentWindow)
+ positionSet = false;
+#endif
+ if (positionSet) {
+ // We have to do this before sizing the window, because sizing depends
+ // on the resolution of the screen we're on. But positioning needs to
+ // know the size so that it can constrain to screen bounds.... as an
+ // initial guess here, we'll use the specified size (if any).
+ positionSet = LoadPositionFromXUL(specWidth, specHeight);
+ }
+
+ if (gotSize) {
+ SetSpecifiedSize(specWidth, specHeight);
+ }
+
+ if (mIntrinsicallySized) {
+ // (if LoadSizeFromXUL set the size, mIntrinsicallySized will be false)
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = do_QueryInterface(mDocShell);
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (treeOwner) {
+ // GetContentSize can fail, so initialise |width| and |height| to be
+ // on the safe side.
+ int32_t width = 0, height = 0;
+ if (NS_SUCCEEDED(cv->GetContentSize(&width, &height))) {
+ treeOwner->SizeShellTo(docShellAsItem, width, height);
+ // Update specified size for the final LoadPositionFromXUL call.
+ specWidth = width;
+ specHeight = height;
+ }
+ }
+ }
+ }
+
+ // Now that we have set the window's final size, we can re-do its
+ // positioning so that it is properly constrained to the screen.
+ if (positionSet) {
+ LoadPositionFromXUL(specWidth, specHeight);
+ }
+
+ LoadMiscPersistentAttributesFromXUL();
+
+ if (mCenterAfterLoad && !positionSet) {
+ Center(parentWindow, parentWindow ? false : true, false);
+ }
+
+ if (mShowAfterLoad) {
+ SetVisibility(true);
+ // At this point the window may have been closed during Show(), so
+ // nsXULWindow::Destroy may already have been called. Take care!
+ }
+ }
+ mPersistentAttributesMask |= PAD_POSITION | PAD_SIZE | PAD_MISC;
+}
+
+// If aSpecWidth and/or aSpecHeight are > 0, we will use these CSS px sizes
+// to fit to the screen when staggering windows; if they're negative,
+// we use the window's current size instead.
+bool nsXULWindow::LoadPositionFromXUL(int32_t aSpecWidth, int32_t aSpecHeight)
+{
+ bool gotPosition = false;
+
+ // if we're the hidden window, don't try to validate our size/position. We're
+ // special.
+ if (mIsHiddenWindow)
+ return false;
+
+ nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement();
+ NS_ENSURE_TRUE(windowElement, false);
+
+ int32_t currX = 0;
+ int32_t currY = 0;
+ int32_t currWidth = 0;
+ int32_t currHeight = 0;
+ nsresult errorCode;
+ int32_t temp;
+
+ GetPositionAndSize(&currX, &currY, &currWidth, &currHeight);
+
+ // Convert to global display pixels for consistent window management across
+ // screens with diverse resolutions
+ double devToDesktopScale = 1.0 / mWindow->GetDesktopToDeviceScale().scale;
+ currX = NSToIntRound(currX * devToDesktopScale);
+ currY = NSToIntRound(currY * devToDesktopScale);
+
+ // For size, use specified value if > 0, else current value
+ double devToCSSScale = 1.0 / mWindow->GetDefaultScale().scale;
+ int32_t cssWidth =
+ aSpecWidth > 0 ? aSpecWidth : NSToIntRound(currWidth * devToCSSScale);
+ int32_t cssHeight =
+ aSpecHeight > 0 ? aSpecHeight : NSToIntRound(currHeight * devToCSSScale);
+
+ // Obtain the position information from the <xul:window> element.
+ int32_t specX = currX;
+ int32_t specY = currY;
+ nsAutoString posString;
+
+ windowElement->GetAttribute(SCREENX_ATTRIBUTE, posString);
+ temp = posString.ToInteger(&errorCode);
+ if (NS_SUCCEEDED(errorCode)) {
+ specX = temp;
+ gotPosition = true;
+ }
+ windowElement->GetAttribute(SCREENY_ATTRIBUTE, posString);
+ temp = posString.ToInteger(&errorCode);
+ if (NS_SUCCEEDED(errorCode)) {
+ specY = temp;
+ gotPosition = true;
+ }
+
+ if (gotPosition) {
+ // our position will be relative to our parent, if any
+ nsCOMPtr<nsIBaseWindow> parent(do_QueryReferent(mParentWindow));
+ if (parent) {
+ int32_t parentX, parentY;
+ if (NS_SUCCEEDED(parent->GetPosition(&parentX, &parentY))) {
+ double scale;
+ if (NS_SUCCEEDED(parent->GetDevicePixelsPerDesktopPixel(&scale))) {
+ parentX = NSToIntRound(parentX / scale);
+ parentY = NSToIntRound(parentY / scale);
+ }
+ specX += parentX;
+ specY += parentY;
+ }
+ }
+ else {
+ StaggerPosition(specX, specY, cssWidth, cssHeight);
+ }
+ }
+ mWindow->ConstrainPosition(false, &specX, &specY);
+ if (specX != currX || specY != currY) {
+ SetPositionDesktopPix(specX, specY);
+ }
+
+ return gotPosition;
+}
+
+bool
+nsXULWindow::LoadSizeFromXUL(int32_t& aSpecWidth, int32_t& aSpecHeight)
+{
+ bool gotSize = false;
+
+ // if we're the hidden window, don't try to validate our size/position. We're
+ // special.
+ if (mIsHiddenWindow) {
+ return false;
+ }
+
+ nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement();
+ NS_ENSURE_TRUE(windowElement, false);
+
+ nsresult errorCode;
+ int32_t temp;
+
+ // Obtain the sizing information from the <xul:window> element.
+ aSpecWidth = 100;
+ aSpecHeight = 100;
+ nsAutoString sizeString;
+
+ windowElement->GetAttribute(WIDTH_ATTRIBUTE, sizeString);
+ temp = sizeString.ToInteger(&errorCode);
+ if (NS_SUCCEEDED(errorCode) && temp > 0) {
+ aSpecWidth = std::max(temp, 100);
+ gotSize = true;
+ }
+ windowElement->GetAttribute(HEIGHT_ATTRIBUTE, sizeString);
+ temp = sizeString.ToInteger(&errorCode);
+ if (NS_SUCCEEDED(errorCode) && temp > 0) {
+ aSpecHeight = std::max(temp, 100);
+ gotSize = true;
+ }
+
+ return gotSize;
+}
+
+void
+nsXULWindow::SetSpecifiedSize(int32_t aSpecWidth, int32_t aSpecHeight)
+{
+ // constrain to screen size
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ GetWindowDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ auto* window = nsPIDOMWindowOuter::From(domWindow);
+ nsCOMPtr<nsIDOMScreen> screen = window->GetScreen();
+ if (screen) {
+ int32_t screenWidth;
+ int32_t screenHeight;
+ screen->GetAvailWidth(&screenWidth); // CSS pixels
+ screen->GetAvailHeight(&screenHeight);
+ if (aSpecWidth > screenWidth) {
+ aSpecWidth = screenWidth;
+ }
+ if (aSpecHeight > screenHeight) {
+ aSpecHeight = screenHeight;
+ }
+ }
+ }
+
+ NS_ASSERTION(mWindow, "we expected to have a window already");
+
+ int32_t currWidth = 0;
+ int32_t currHeight = 0;
+ GetSize(&currWidth, &currHeight); // returns device pixels
+
+ // convert specified values to device pixels, and resize if needed
+ double cssToDevPx = mWindow ? mWindow->GetDefaultScale().scale : 1.0;
+ aSpecWidth = NSToIntRound(aSpecWidth * cssToDevPx);
+ aSpecHeight = NSToIntRound(aSpecHeight * cssToDevPx);
+ mIntrinsicallySized = false;
+ if (aSpecWidth != currWidth || aSpecHeight != currHeight) {
+ SetSize(aSpecWidth, aSpecHeight, false);
+ }
+}
+
+/* Miscellaneous persistent attributes are attributes named in the
+ |persist| attribute, other than size and position. Those are special
+ because it's important to load those before one of the misc
+ attributes (sizemode) and they require extra processing. */
+bool nsXULWindow::LoadMiscPersistentAttributesFromXUL()
+{
+ bool gotState = false;
+
+ /* There are no misc attributes of interest to the hidden window.
+ It's especially important not to try to validate that window's
+ size or position, because some platforms (Mac OS X) need to
+ make it visible and offscreen. */
+ if (mIsHiddenWindow)
+ return false;
+
+ nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement();
+ NS_ENSURE_TRUE(windowElement, false);
+
+ nsAutoString stateString;
+
+ // sizemode
+ windowElement->GetAttribute(MODE_ATTRIBUTE, stateString);
+ nsSizeMode sizeMode = nsSizeMode_Normal;
+ /* ignore request to minimize, to not confuse novices
+ if (stateString.Equals(SIZEMODE_MINIMIZED))
+ sizeMode = nsSizeMode_Minimized;
+ */
+ if (!mIgnoreXULSizeMode &&
+ (stateString.Equals(SIZEMODE_MAXIMIZED) || stateString.Equals(SIZEMODE_FULLSCREEN))) {
+ /* Honor request to maximize only if the window is sizable.
+ An unsizable, unmaximizable, yet maximized window confuses
+ Windows OS and is something of a travesty, anyway. */
+ if (mChromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) {
+ mIntrinsicallySized = false;
+
+ if (stateString.Equals(SIZEMODE_MAXIMIZED))
+ sizeMode = nsSizeMode_Maximized;
+ else
+ sizeMode = nsSizeMode_Fullscreen;
+ }
+ }
+
+ // If we are told to ignore the size mode attribute update the
+ // document so the attribute and window are in sync.
+ if (mIgnoreXULSizeMode) {
+ nsAutoString sizeString;
+ if (sizeMode == nsSizeMode_Maximized)
+ sizeString.Assign(SIZEMODE_MAXIMIZED);
+ else if (sizeMode == nsSizeMode_Fullscreen)
+ sizeString.Assign(SIZEMODE_FULLSCREEN);
+ else if (sizeMode == nsSizeMode_Normal)
+ sizeString.Assign(SIZEMODE_NORMAL);
+ if (!sizeString.IsEmpty()) {
+ ErrorResult rv;
+ windowElement->SetAttribute(MODE_ATTRIBUTE, sizeString, rv);
+ }
+ }
+
+ if (sizeMode == nsSizeMode_Fullscreen) {
+ nsCOMPtr<mozIDOMWindowProxy> ourWindow;
+ GetWindowDOMWindow(getter_AddRefs(ourWindow));
+ auto* piWindow = nsPIDOMWindowOuter::From(ourWindow);
+ piWindow->SetFullScreen(true);
+ } else {
+ mWindow->SetSizeMode(sizeMode);
+ }
+ gotState = true;
+
+ // zlevel
+ windowElement->GetAttribute(ZLEVEL_ATTRIBUTE, stateString);
+ if (!stateString.IsEmpty()) {
+ nsresult errorCode;
+ int32_t zLevel = stateString.ToInteger(&errorCode);
+ if (NS_SUCCEEDED(errorCode) && zLevel >= lowestZ && zLevel <= highestZ)
+ SetZLevel(zLevel);
+ }
+
+ return gotState;
+}
+
+/* Stagger windows of the same type so they don't appear on top of each other.
+ This code does have a scary double loop -- it'll keep passing through
+ the entire list of open windows until it finds a non-collision. Doesn't
+ seem to be a problem, but it deserves watching.
+ The aRequested{X,Y} parameters here are in desktop pixels;
+ the aSpec{Width,Height} parameters are CSS pixel dimensions.
+*/
+void nsXULWindow::StaggerPosition(int32_t &aRequestedX, int32_t &aRequestedY,
+ int32_t aSpecWidth, int32_t aSpecHeight)
+{
+ // These "constants" will be converted from CSS to desktop pixels
+ // for the appropriate screen, assuming we find a screen to use...
+ // hence they're not actually declared const here.
+ int32_t kOffset = 22;
+ uint32_t kSlop = 4;
+
+ bool keepTrying;
+ int bouncedX = 0, // bounced off vertical edge of screen
+ bouncedY = 0; // bounced off horizontal edge
+
+ // look for any other windows of this type
+ nsCOMPtr<nsIWindowMediator> wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (!wm)
+ return;
+
+ nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement();
+ if (!windowElement)
+ return;
+
+ nsCOMPtr<nsIXULWindow> ourXULWindow(this);
+
+ nsAutoString windowType;
+ windowElement->GetAttribute(WINDOWTYPE_ATTRIBUTE, windowType);
+
+ int32_t screenTop = 0, // it's pointless to initialize these ...
+ screenRight = 0, // ... but to prevent oversalubrious and ...
+ screenBottom = 0, // ... underbright compilers from ...
+ screenLeft = 0; // ... issuing warnings.
+ bool gotScreen = false;
+
+ { // fetch screen coordinates
+ nsCOMPtr<nsIScreenManager> screenMgr(do_GetService(
+ "@mozilla.org/gfx/screenmanager;1"));
+ if (screenMgr) {
+ nsCOMPtr<nsIScreen> ourScreen;
+ // the coordinates here are already display pixels
+ screenMgr->ScreenForRect(aRequestedX, aRequestedY,
+ aSpecWidth, aSpecHeight,
+ getter_AddRefs(ourScreen));
+ if (ourScreen) {
+ int32_t screenWidth, screenHeight;
+ ourScreen->GetAvailRectDisplayPix(&screenLeft, &screenTop,
+ &screenWidth, &screenHeight);
+ screenBottom = screenTop + screenHeight;
+ screenRight = screenLeft + screenWidth;
+ // Get the screen's scaling factors and convert staggering constants
+ // from CSS px to desktop pixel units
+ double desktopToDeviceScale = 1.0, cssToDeviceScale = 1.0;
+ ourScreen->GetContentsScaleFactor(&desktopToDeviceScale);
+ ourScreen->GetDefaultCSSScaleFactor(&cssToDeviceScale);
+ double cssToDesktopFactor = cssToDeviceScale / desktopToDeviceScale;
+ kOffset = NSToIntRound(kOffset * cssToDesktopFactor);
+ kSlop = NSToIntRound(kSlop * cssToDesktopFactor);
+ // Convert dimensions from CSS to desktop pixels
+ aSpecWidth = NSToIntRound(aSpecWidth * cssToDesktopFactor);
+ aSpecHeight = NSToIntRound(aSpecHeight * cssToDesktopFactor);
+ gotScreen = true;
+ }
+ }
+ }
+
+ // One full pass through all windows of this type, repeat until no collisions.
+ do {
+ keepTrying = false;
+ nsCOMPtr<nsISimpleEnumerator> windowList;
+ wm->GetXULWindowEnumerator(windowType.get(), getter_AddRefs(windowList));
+
+ if (!windowList)
+ break;
+
+ // One full pass through all windows of this type, offset and stop on collision.
+ do {
+ bool more;
+ windowList->HasMoreElements(&more);
+ if (!more)
+ break;
+
+ nsCOMPtr<nsISupports> supportsWindow;
+ windowList->GetNext(getter_AddRefs(supportsWindow));
+
+ nsCOMPtr<nsIXULWindow> listXULWindow(do_QueryInterface(supportsWindow));
+ if (listXULWindow != ourXULWindow) {
+ int32_t listX, listY;
+ nsCOMPtr<nsIBaseWindow> listBaseWindow(do_QueryInterface(supportsWindow));
+ listBaseWindow->GetPosition(&listX, &listY);
+ double scale;
+ if (NS_SUCCEEDED(listBaseWindow->GetDevicePixelsPerDesktopPixel(&scale))) {
+ listX = NSToIntRound(listX / scale);
+ listY = NSToIntRound(listY / scale);
+ }
+
+ if (Abs(listX - aRequestedX) <= kSlop && Abs(listY - aRequestedY) <= kSlop) {
+ // collision! offset and start over
+ if (bouncedX & 0x1)
+ aRequestedX -= kOffset;
+ else
+ aRequestedX += kOffset;
+ aRequestedY += kOffset;
+
+ if (gotScreen) {
+ // if we're moving to the right and we need to bounce...
+ if (!(bouncedX & 0x1) && ((aRequestedX + aSpecWidth) > screenRight)) {
+ aRequestedX = screenRight - aSpecWidth;
+ ++bouncedX;
+ }
+
+ // if we're moving to the left and we need to bounce...
+ if ((bouncedX & 0x1) && aRequestedX < screenLeft) {
+ aRequestedX = screenLeft;
+ ++bouncedX;
+ }
+
+ // if we hit the bottom then bounce to the top
+ if (aRequestedY + aSpecHeight > screenBottom) {
+ aRequestedY = screenTop;
+ ++bouncedY;
+ }
+ }
+
+ /* loop around again,
+ but it's time to give up once we've covered the screen.
+ there's a potential infinite loop with lots of windows. */
+ keepTrying = bouncedX < 2 || bouncedY == 0;
+ break;
+ }
+ }
+ } while(1);
+ } while (keepTrying);
+}
+
+void nsXULWindow::SyncAttributesToWidget()
+{
+ nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement();
+ if (!windowElement)
+ return;
+
+ nsAutoString attr;
+
+ // "hidechrome" attribute
+ if (windowElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidechrome,
+ nsGkAtoms::_true, eCaseMatters)) {
+ mWindow->HideWindowChrome(true);
+ }
+
+ // "chromemargin" attribute
+ nsIntMargin margins;
+ windowElement->GetAttribute(NS_LITERAL_STRING("chromemargin"), attr);
+ if (nsContentUtils::ParseIntMarginValue(attr, margins)) {
+ LayoutDeviceIntMargin tmp = LayoutDeviceIntMargin::FromUnknownMargin(margins);
+ mWindow->SetNonClientMargins(tmp);
+ }
+
+ // "windowtype" attribute
+ windowElement->GetAttribute(WINDOWTYPE_ATTRIBUTE, attr);
+ if (!attr.IsEmpty()) {
+ mWindow->SetWindowClass(attr);
+ }
+
+ // "id" attribute for icon
+ windowElement->GetAttribute(NS_LITERAL_STRING("id"), attr);
+ if (attr.IsEmpty()) {
+ attr.AssignLiteral("default");
+ }
+ mWindow->SetIcon(attr);
+
+ // "drawtitle" attribute
+ windowElement->GetAttribute(NS_LITERAL_STRING("drawtitle"), attr);
+ mWindow->SetDrawsTitle(attr.LowerCaseEqualsLiteral("true"));
+
+ // "toggletoolbar" attribute
+ windowElement->GetAttribute(NS_LITERAL_STRING("toggletoolbar"), attr);
+ mWindow->SetShowsToolbarButton(attr.LowerCaseEqualsLiteral("true"));
+
+ // "fullscreenbutton" attribute
+ windowElement->GetAttribute(NS_LITERAL_STRING("fullscreenbutton"), attr);
+ mWindow->SetShowsFullScreenButton(attr.LowerCaseEqualsLiteral("true"));
+
+ // "macanimationtype" attribute
+ windowElement->GetAttribute(NS_LITERAL_STRING("macanimationtype"), attr);
+ if (attr.EqualsLiteral("document")) {
+ mWindow->SetWindowAnimationType(nsIWidget::eDocumentWindowAnimation);
+ }
+}
+
+NS_IMETHODIMP nsXULWindow::SavePersistentAttributes()
+{
+ // can happen when the persistence timer fires at an inopportune time
+ // during window shutdown
+ if (!mDocShell)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement();
+ if (!docShellElement)
+ return NS_ERROR_FAILURE;
+
+ nsAutoString persistString;
+ docShellElement->GetAttribute(PERSIST_ATTRIBUTE, persistString);
+ if (persistString.IsEmpty()) { // quick check which sometimes helps
+ mPersistentAttributesDirty = 0;
+ return NS_OK;
+ }
+
+ bool isFullscreen = false;
+ if (nsPIDOMWindowOuter* domWindow = mDocShell->GetWindow()) {
+ isFullscreen = domWindow->GetFullScreen();
+ }
+
+ // get our size, position and mode to persist
+ LayoutDeviceIntRect rect;
+ bool gotRestoredBounds = NS_SUCCEEDED(mWindow->GetRestoredBounds(rect));
+
+ // we use CSS pixels for size, but desktop pixels for position
+ CSSToLayoutDeviceScale sizeScale = mWindow->GetDefaultScale();
+ DesktopToLayoutDeviceScale posScale = mWindow->GetDesktopToDeviceScale();
+
+ // make our position relative to our parent, if any
+ nsCOMPtr<nsIBaseWindow> parent(do_QueryReferent(mParentWindow));
+ if (parent && gotRestoredBounds) {
+ int32_t parentX, parentY;
+ if (NS_SUCCEEDED(parent->GetPosition(&parentX, &parentY))) {
+ rect.x -= parentX;
+ rect.y -= parentY;
+ }
+ }
+
+ char sizeBuf[10];
+ nsAutoString sizeString;
+ nsAutoString windowElementId;
+ nsCOMPtr<nsIDOMXULDocument> ownerXULDoc;
+
+ // fetch docShellElement's ID and XUL owner document
+ ownerXULDoc = do_QueryInterface(docShellElement->OwnerDoc());
+ if (docShellElement->IsXULElement()) {
+ docShellElement->GetId(windowElementId);
+ }
+
+ bool shouldPersist = !isFullscreen && ownerXULDoc;
+ ErrorResult rv;
+ // (only for size elements which are persisted)
+ if ((mPersistentAttributesDirty & PAD_POSITION) && gotRestoredBounds) {
+ if (persistString.Find("screenX") >= 0) {
+ SprintfLiteral(sizeBuf, "%d", NSToIntRound(rect.x / posScale.scale));
+ sizeString.AssignWithConversion(sizeBuf);
+ docShellElement->SetAttribute(SCREENX_ATTRIBUTE, sizeString, rv);
+ if (shouldPersist) {
+ ownerXULDoc->Persist(windowElementId, SCREENX_ATTRIBUTE);
+ }
+ }
+ if (persistString.Find("screenY") >= 0) {
+ SprintfLiteral(sizeBuf, "%d", NSToIntRound(rect.y / posScale.scale));
+ sizeString.AssignWithConversion(sizeBuf);
+ docShellElement->SetAttribute(SCREENY_ATTRIBUTE, sizeString, rv);
+ if (shouldPersist) {
+ ownerXULDoc->Persist(windowElementId, SCREENY_ATTRIBUTE);
+ }
+ }
+ }
+
+ if ((mPersistentAttributesDirty & PAD_SIZE) && gotRestoredBounds) {
+ if (persistString.Find("width") >= 0) {
+ SprintfLiteral(sizeBuf, "%d", NSToIntRound(rect.width / sizeScale.scale));
+ sizeString.AssignWithConversion(sizeBuf);
+ docShellElement->SetAttribute(WIDTH_ATTRIBUTE, sizeString, rv);
+ if (shouldPersist) {
+ ownerXULDoc->Persist(windowElementId, WIDTH_ATTRIBUTE);
+ }
+ }
+ if (persistString.Find("height") >= 0) {
+ SprintfLiteral(sizeBuf, "%d", NSToIntRound(rect.height / sizeScale.scale));
+ sizeString.AssignWithConversion(sizeBuf);
+ docShellElement->SetAttribute(HEIGHT_ATTRIBUTE, sizeString, rv);
+ if (shouldPersist) {
+ ownerXULDoc->Persist(windowElementId, HEIGHT_ATTRIBUTE);
+ }
+ }
+ }
+
+ if (mPersistentAttributesDirty & PAD_MISC) {
+ nsSizeMode sizeMode = mWindow->SizeMode();
+
+ if (sizeMode != nsSizeMode_Minimized) {
+ if (sizeMode == nsSizeMode_Maximized)
+ sizeString.Assign(SIZEMODE_MAXIMIZED);
+ else if (sizeMode == nsSizeMode_Fullscreen)
+ sizeString.Assign(SIZEMODE_FULLSCREEN);
+ else
+ sizeString.Assign(SIZEMODE_NORMAL);
+ docShellElement->SetAttribute(MODE_ATTRIBUTE, sizeString, rv);
+ if (shouldPersist && persistString.Find("sizemode") >= 0) {
+ ownerXULDoc->Persist(windowElementId, MODE_ATTRIBUTE);
+ }
+ }
+ if (persistString.Find("zlevel") >= 0) {
+ uint32_t zLevel;
+ nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (mediator) {
+ mediator->GetZLevel(this, &zLevel);
+ SprintfLiteral(sizeBuf, "%" PRIu32, zLevel);
+ sizeString.AssignWithConversion(sizeBuf);
+ docShellElement->SetAttribute(ZLEVEL_ATTRIBUTE, sizeString, rv);
+ if (shouldPersist) {
+ ownerXULDoc->Persist(windowElementId, ZLEVEL_ATTRIBUTE);
+ }
+ }
+ }
+ }
+
+ mPersistentAttributesDirty = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetWindowDOMWindow(mozIDOMWindowProxy** aDOMWindow)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ if (!mDOMWindow)
+ mDOMWindow = mDocShell->GetWindow();
+ NS_ENSURE_TRUE(mDOMWindow, NS_ERROR_FAILURE);
+
+ *aDOMWindow = mDOMWindow;
+ NS_ADDREF(*aDOMWindow);
+ return NS_OK;
+}
+
+dom::Element*
+nsXULWindow::GetWindowDOMElement() const
+{
+ NS_ENSURE_TRUE(mDocShell, nullptr);
+
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ NS_ENSURE_TRUE(cv, nullptr);
+
+ const nsIDocument* document = cv->GetDocument();
+ NS_ENSURE_TRUE(document, nullptr);
+
+ return document->GetRootElement();
+}
+
+nsresult nsXULWindow::ContentShellAdded(nsIDocShellTreeItem* aContentShell,
+ bool aPrimary, bool aTargetable, const nsAString& aID)
+{
+ nsContentShellInfo* shellInfo = nullptr;
+
+ uint32_t i, count = mContentShells.Length();
+ nsWeakPtr contentShellWeak = do_GetWeakReference(aContentShell);
+ for (i = 0; i < count; i++) {
+ nsContentShellInfo* info = mContentShells.ElementAt(i);
+ if (info->id.Equals(aID)) {
+ // We already exist. Do a replace.
+ info->child = contentShellWeak;
+ shellInfo = info;
+ }
+ else if (info->child == contentShellWeak)
+ info->child = nullptr;
+ }
+
+ if (!shellInfo) {
+ shellInfo = new nsContentShellInfo(aID, contentShellWeak);
+ mContentShells.AppendElement(shellInfo);
+ }
+
+ // Set the default content tree owner
+ if (aPrimary) {
+ NS_ENSURE_SUCCESS(EnsurePrimaryContentTreeOwner(), NS_ERROR_FAILURE);
+ aContentShell->SetTreeOwner(mPrimaryContentTreeOwner);
+ mPrimaryContentShell = aContentShell;
+ mPrimaryTabParent = nullptr;
+ }
+ else {
+ NS_ENSURE_SUCCESS(EnsureContentTreeOwner(), NS_ERROR_FAILURE);
+ aContentShell->SetTreeOwner(mContentTreeOwner);
+ if (mPrimaryContentShell == aContentShell)
+ mPrimaryContentShell = nullptr;
+ }
+
+ if (aTargetable) {
+#ifdef DEBUG
+ int32_t debugCount = mTargetableShells.Count();
+ int32_t debugCounter;
+ for (debugCounter = debugCount - 1; debugCounter >= 0; --debugCounter) {
+ nsCOMPtr<nsIDocShellTreeItem> curItem =
+ do_QueryReferent(mTargetableShells[debugCounter]);
+ NS_ASSERTION(!SameCOMIdentity(curItem, aContentShell),
+ "Adding already existing item to mTargetableShells");
+ }
+#endif
+
+ // put the new shell at the start of the targetable shells list if either
+ // it's the new primary shell or there is no existing primary shell (which
+ // means that chances are this one just stopped being primary). If we
+ // really cared, we could keep track of the "last no longer primary shell"
+ // explicitly, but it probably doesn't matter enough: the difference would
+ // only be felt in a situation where all shells were non-primary, which
+ // doesn't happen much. In a situation where there is one and only one
+ // primary shell, and in which shells get unmarked as primary before some
+ // other shell gets marked as primary, this effectively stores the list of
+ // targetable shells in "most recently primary first" order.
+ bool inserted;
+ if (aPrimary || !mPrimaryContentShell) {
+ inserted = mTargetableShells.InsertObjectAt(contentShellWeak, 0);
+ } else {
+ inserted = mTargetableShells.AppendObject(contentShellWeak);
+ }
+ NS_ENSURE_TRUE(inserted, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsXULWindow::ContentShellRemoved(nsIDocShellTreeItem* aContentShell)
+{
+ if (mPrimaryContentShell == aContentShell) {
+ mPrimaryContentShell = nullptr;
+ }
+
+ int32_t i, count = mContentShells.Length();
+ for (i = count - 1; i >= 0; --i) {
+ nsContentShellInfo* info = mContentShells.ElementAt(i);
+ nsCOMPtr<nsIDocShellTreeItem> curItem = do_QueryReferent(info->child);
+ if (!curItem || SameCOMIdentity(curItem, aContentShell)) {
+ mContentShells.RemoveElementAt(i);
+ delete info;
+ }
+ }
+
+ count = mTargetableShells.Count();
+ for (i = count - 1; i >= 0; --i) {
+ nsCOMPtr<nsIDocShellTreeItem> curItem =
+ do_QueryReferent(mTargetableShells[i]);
+ if (!curItem || SameCOMIdentity(curItem, aContentShell)) {
+ mTargetableShells.RemoveObjectAt(i);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULWindow::GetPrimaryContentSize(int32_t* aWidth,
+ int32_t* aHeight)
+{
+ if (mPrimaryTabParent) {
+ return GetPrimaryTabParentSize(aWidth, aHeight);
+ } else if (mPrimaryContentShell) {
+ return GetPrimaryContentShellSize(aWidth, aHeight);
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+nsXULWindow::GetPrimaryTabParentSize(int32_t* aWidth,
+ int32_t* aHeight)
+{
+ TabParent* tabParent = TabParent::GetFrom(mPrimaryTabParent);
+ // Need strong ref, since Client* can run script.
+ nsCOMPtr<Element> element = tabParent->GetOwnerElement();
+ NS_ENSURE_STATE(element);
+
+ *aWidth = element->ClientWidth();
+ *aHeight = element->ClientHeight();
+ return NS_OK;
+}
+
+nsresult
+nsXULWindow::GetPrimaryContentShellSize(int32_t* aWidth,
+ int32_t* aHeight)
+{
+ NS_ENSURE_STATE(mPrimaryContentShell);
+
+ nsCOMPtr<nsIBaseWindow> shellWindow(do_QueryInterface(mPrimaryContentShell));
+ NS_ENSURE_STATE(shellWindow);
+
+ int32_t devicePixelWidth, devicePixelHeight;
+ double shellScale = 1.0;
+ // We want to return CSS pixels. First, we get device pixels
+ // from the content area...
+ shellWindow->GetSize(&devicePixelWidth, &devicePixelHeight);
+ // And then get the device pixel scaling factor. Dividing device
+ // pixels by this scaling factor gives us CSS pixels.
+ shellWindow->GetUnscaledDevicePixelsPerCSSPixel(&shellScale);
+ *aWidth = NSToIntRound(devicePixelWidth / shellScale);
+ *aHeight = NSToIntRound(devicePixelHeight / shellScale);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULWindow::SetPrimaryContentSize(int32_t aWidth,
+ int32_t aHeight)
+{
+ if (mPrimaryTabParent) {
+ return SetPrimaryTabParentSize(aWidth, aHeight);
+ } else if (mPrimaryContentShell) {
+ return SizeShellTo(mPrimaryContentShell, aWidth, aHeight);
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult
+nsXULWindow::SetPrimaryTabParentSize(int32_t aWidth,
+ int32_t aHeight)
+{
+ int32_t shellWidth, shellHeight;
+ GetPrimaryTabParentSize(&shellWidth, &shellHeight);
+
+ double scale = 1.0;
+ GetUnscaledDevicePixelsPerCSSPixel(&scale);
+
+ SizeShellToWithLimit(aWidth, aHeight,
+ shellWidth * scale, shellHeight * scale);
+ return NS_OK;
+}
+
+nsresult
+nsXULWindow::GetRootShellSize(int32_t* aWidth,
+ int32_t* aHeight)
+{
+ nsCOMPtr<nsIBaseWindow> shellAsWin = do_QueryInterface(mDocShell);
+ NS_ENSURE_TRUE(shellAsWin, NS_ERROR_FAILURE);
+ return shellAsWin->GetSize(aWidth, aHeight);
+}
+
+nsresult
+nsXULWindow::SetRootShellSize(int32_t aWidth,
+ int32_t aHeight)
+{
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = do_QueryInterface(mDocShell);
+ return SizeShellTo(docShellAsItem, aWidth, aHeight);
+}
+
+NS_IMETHODIMP nsXULWindow::SizeShellTo(nsIDocShellTreeItem* aShellItem,
+ int32_t aCX, int32_t aCY)
+{
+ // XXXTAB This is wrong, we should actually reflow based on the passed in
+ // shell. For now we are hacking and doing delta sizing. This is bad
+ // because it assumes all size we add will go to the shell which probably
+ // won't happen.
+
+ nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(aShellItem));
+ NS_ENSURE_TRUE(shellAsWin, NS_ERROR_FAILURE);
+
+ int32_t width = 0;
+ int32_t height = 0;
+ shellAsWin->GetSize(&width, &height);
+
+ SizeShellToWithLimit(aCX, aCY, width, height);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::ExitModalLoop(nsresult aStatus)
+{
+ if (mContinueModalLoop)
+ EnableParent(true);
+ mContinueModalLoop = false;
+ mModalStatus = aStatus;
+ return NS_OK;
+}
+
+// top-level function to create a new window
+NS_IMETHODIMP nsXULWindow::CreateNewWindow(int32_t aChromeFlags,
+ nsITabParent *aOpeningTab,
+ mozIDOMWindowProxy *aOpener,
+ nsIXULWindow **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (aChromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME)
+ return CreateNewChromeWindow(aChromeFlags, aOpeningTab, aOpener, _retval);
+ return CreateNewContentWindow(aChromeFlags, aOpeningTab, aOpener, _retval);
+}
+
+NS_IMETHODIMP nsXULWindow::CreateNewChromeWindow(int32_t aChromeFlags,
+ nsITabParent *aOpeningTab,
+ mozIDOMWindowProxy *aOpener,
+ nsIXULWindow **_retval)
+{
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(appShell, NS_ERROR_FAILURE);
+
+ // Just do a normal create of a window and return.
+ nsCOMPtr<nsIXULWindow> newWindow;
+ appShell->CreateTopLevelWindow(this, nullptr, aChromeFlags,
+ nsIAppShellService::SIZE_TO_CONTENT,
+ nsIAppShellService::SIZE_TO_CONTENT,
+ aOpeningTab, aOpener,
+ getter_AddRefs(newWindow));
+
+ NS_ENSURE_TRUE(newWindow, NS_ERROR_FAILURE);
+
+ *_retval = newWindow;
+ NS_ADDREF(*_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::CreateNewContentWindow(int32_t aChromeFlags,
+ nsITabParent *aOpeningTab,
+ mozIDOMWindowProxy *aOpener,
+ nsIXULWindow **_retval)
+{
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(appShell, NS_ERROR_FAILURE);
+
+ // We need to create a new top level window and then enter a nested
+ // loop. Eventually the new window will be told that it has loaded,
+ // at which time we know it is safe to spin out of the nested loop
+ // and allow the opening code to proceed.
+
+ nsCOMPtr<nsIURI> uri;
+
+ nsAdoptingCString urlStr = Preferences::GetCString("browser.chromeURL");
+ if (urlStr.IsEmpty()) {
+ urlStr.AssignLiteral("chrome://navigator/content/navigator.xul");
+ }
+
+ nsCOMPtr<nsIIOService> service(do_GetService(NS_IOSERVICE_CONTRACTID));
+ if (service) {
+ service->NewURI(urlStr, nullptr, nullptr, getter_AddRefs(uri));
+ }
+ NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
+
+ // We need to create a chrome window to contain the content window we're about
+ // to pass back. The subject principal needs to be system while we're creating
+ // it to make things work right, so force a system caller. See bug 799348
+ // comment 13 for a description of what happens when we don't.
+ nsCOMPtr<nsIXULWindow> newWindow;
+ {
+ AutoNoJSAPI nojsapi;
+ // We actually want this toplevel window which we are creating to have a
+ // null opener, as we will be creating the content xul:browser window inside
+ // of it, so we pass nullptr as our aOpener.
+ appShell->CreateTopLevelWindow(this, uri,
+ aChromeFlags, 615, 480,
+ aOpeningTab, nullptr,
+ getter_AddRefs(newWindow));
+ NS_ENSURE_TRUE(newWindow, NS_ERROR_FAILURE);
+ }
+
+ // Specify that we want the window to remain locked until the chrome has loaded.
+ nsXULWindow *xulWin = static_cast<nsXULWindow*>
+ (static_cast<nsIXULWindow*>
+ (newWindow));
+
+ if (aOpener) {
+ nsCOMPtr<nsIDocShell> docShell;
+ xulWin->GetDocShell(getter_AddRefs(docShell));
+ MOZ_ASSERT(docShell);
+ nsCOMPtr<nsIDOMChromeWindow> chromeWindow =
+ do_QueryInterface(docShell->GetWindow());
+ MOZ_ASSERT(chromeWindow);
+
+ chromeWindow->SetOpenerForInitialContentBrowser(aOpener);
+ }
+
+ xulWin->LockUntilChromeLoad();
+
+ {
+ AutoNoJSAPI nojsapi;
+ nsIThread *thread = NS_GetCurrentThread();
+ while (xulWin->IsLocked()) {
+ if (!NS_ProcessNextEvent(thread))
+ break;
+ }
+ }
+
+ NS_ENSURE_STATE(xulWin->mPrimaryContentShell || xulWin->mPrimaryTabParent);
+
+ *_retval = newWindow;
+ NS_ADDREF(*_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetHasPrimaryContent(bool* aResult)
+{
+ *aResult = mPrimaryTabParent || mPrimaryContentShell;
+ return NS_OK;
+}
+
+void nsXULWindow::EnableParent(bool aEnable)
+{
+ nsCOMPtr<nsIBaseWindow> parentWindow;
+ nsCOMPtr<nsIWidget> parentWidget;
+
+ parentWindow = do_QueryReferent(mParentWindow);
+ if (parentWindow)
+ parentWindow->GetMainWidget(getter_AddRefs(parentWidget));
+ if (parentWidget)
+ parentWidget->Enable(aEnable);
+}
+
+// Constrain the window to its proper z-level
+bool nsXULWindow::ConstrainToZLevel(bool aImmediate,
+ nsWindowZ *aPlacement,
+ nsIWidget *aReqBelow,
+ nsIWidget **aActualBelow)
+{
+#if 0
+ /* Do we have a parent window? This means our z-order is already constrained,
+ since we're a dependent window. Our window list isn't hierarchical,
+ so we can't properly calculate placement for such a window.
+ Should we just abort? */
+ nsCOMPtr<nsIBaseWindow> parentWindow = do_QueryReferent(mParentWindow);
+ if (parentWindow)
+ return false;
+#endif
+
+ nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (!mediator)
+ return false;
+
+ bool altered;
+ uint32_t position,
+ newPosition,
+ zLevel;
+ nsIXULWindow *us = this;
+
+ altered = false;
+ mediator->GetZLevel(this, &zLevel);
+
+ // translate from WidgetGUIEvent to nsIWindowMediator constants
+ position = nsIWindowMediator::zLevelTop;
+ if (*aPlacement == nsWindowZBottom || zLevel == nsIXULWindow::lowestZ)
+ position = nsIWindowMediator::zLevelBottom;
+ else if (*aPlacement == nsWindowZRelative)
+ position = nsIWindowMediator::zLevelBelow;
+
+ if (NS_SUCCEEDED(mediator->CalculateZPosition(us, position, aReqBelow,
+ &newPosition, aActualBelow, &altered))) {
+ /* If we were asked to move to the top but constrained to remain
+ below one of our other windows, first move all windows in that
+ window's layer and above to the top. This allows the user to
+ click a window which can't be topmost and still bring mozilla
+ to the foreground. */
+ if (altered &&
+ (position == nsIWindowMediator::zLevelTop ||
+ (position == nsIWindowMediator::zLevelBelow && aReqBelow == 0)))
+ PlaceWindowLayersBehind(zLevel + 1, nsIXULWindow::highestZ, 0);
+
+ if (*aPlacement != nsWindowZBottom &&
+ position == nsIWindowMediator::zLevelBottom)
+ altered = true;
+ if (altered || aImmediate) {
+ if (newPosition == nsIWindowMediator::zLevelTop)
+ *aPlacement = nsWindowZTop;
+ else if (newPosition == nsIWindowMediator::zLevelBottom)
+ *aPlacement = nsWindowZBottom;
+ else
+ *aPlacement = nsWindowZRelative;
+
+ if (aImmediate) {
+ nsCOMPtr<nsIBaseWindow> ourBase = do_QueryObject(this);
+ if (ourBase) {
+ nsCOMPtr<nsIWidget> ourWidget;
+ ourBase->GetMainWidget(getter_AddRefs(ourWidget));
+ ourWidget->PlaceBehind(*aPlacement == nsWindowZBottom ?
+ eZPlacementBottom : eZPlacementBelow,
+ *aActualBelow, false);
+ }
+ }
+ }
+
+ /* CalculateZPosition can tell us to be below nothing, because it tries
+ not to change something it doesn't recognize. A request to verify
+ being below an unrecognized window, then, is treated as a request
+ to come to the top (below null) */
+ nsCOMPtr<nsIXULWindow> windowAbove;
+ if (newPosition == nsIWindowMediator::zLevelBelow && *aActualBelow) {
+ windowAbove = (*aActualBelow)->GetWidgetListener()->GetXULWindow();
+ }
+
+ mediator->SetZPosition(us, newPosition, windowAbove);
+ }
+
+ return altered;
+}
+
+/* Re-z-position all windows in the layers from aLowLevel to aHighLevel,
+ inclusive, to be behind aBehind. aBehind of null means on top.
+ Note this method actually does nothing to our relative window positions.
+ (And therefore there's no need to inform WindowMediator we're moving
+ things, because we aren't.) This method is useful for, say, moving
+ a range of layers of our own windows relative to windows belonging to
+ external applications.
+*/
+void nsXULWindow::PlaceWindowLayersBehind(uint32_t aLowLevel,
+ uint32_t aHighLevel,
+ nsIXULWindow *aBehind)
+{
+ // step through windows in z-order from top to bottommost window
+
+ nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (!mediator)
+ return;
+
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+ mediator->GetZOrderXULWindowEnumerator(0, true,
+ getter_AddRefs(windowEnumerator));
+ if (!windowEnumerator)
+ return;
+
+ // each window will be moved behind previousHighWidget, itself
+ // a moving target. initialize it.
+ nsCOMPtr<nsIWidget> previousHighWidget;
+ if (aBehind) {
+ nsCOMPtr<nsIBaseWindow> highBase(do_QueryInterface(aBehind));
+ if (highBase)
+ highBase->GetMainWidget(getter_AddRefs(previousHighWidget));
+ }
+
+ // get next lower window
+ bool more;
+ while (windowEnumerator->HasMoreElements(&more), more) {
+ uint32_t nextZ; // z-level of nextWindow
+ nsCOMPtr<nsISupports> nextWindow;
+ windowEnumerator->GetNext(getter_AddRefs(nextWindow));
+ nsCOMPtr<nsIXULWindow> nextXULWindow(do_QueryInterface(nextWindow));
+ nextXULWindow->GetZLevel(&nextZ);
+ if (nextZ < aLowLevel)
+ break; // we've processed all windows through aLowLevel
+
+ // move it just below its next higher window
+ nsCOMPtr<nsIBaseWindow> nextBase(do_QueryInterface(nextXULWindow));
+ if (nextBase) {
+ nsCOMPtr<nsIWidget> nextWidget;
+ nextBase->GetMainWidget(getter_AddRefs(nextWidget));
+ if (nextZ <= aHighLevel)
+ nextWidget->PlaceBehind(eZPlacementBelow, previousHighWidget, false);
+ previousHighWidget = nextWidget;
+ }
+ }
+}
+
+void nsXULWindow::SetContentScrollbarVisibility(bool aVisible)
+{
+ nsCOMPtr<nsPIDOMWindowOuter> contentWin(do_GetInterface(mPrimaryContentShell));
+ if (!contentWin) {
+ return;
+ }
+
+ nsContentUtils::SetScrollbarsVisibility(contentWin->GetDocShell(), aVisible);
+}
+
+bool nsXULWindow::GetContentScrollbarVisibility()
+{
+ // This code already exists in dom/src/base/nsBarProp.cpp, but we
+ // can't safely get to that from here as this function is called
+ // while the DOM window is being set up, and we need the DOM window
+ // to get to that code.
+ nsCOMPtr<nsIScrollable> scroller(do_QueryInterface(mPrimaryContentShell));
+
+ if (scroller) {
+ int32_t prefValue;
+ scroller->GetDefaultScrollbarPreferences(
+ nsIScrollable::ScrollOrientation_Y, &prefValue);
+ if (prefValue == nsIScrollable::Scrollbar_Never) // try the other way
+ scroller->GetDefaultScrollbarPreferences(
+ nsIScrollable::ScrollOrientation_X, &prefValue);
+
+ if (prefValue == nsIScrollable::Scrollbar_Never)
+ return false;
+ }
+
+ return true;
+}
+
+// during spinup, attributes that haven't been loaded yet can't be dirty
+void nsXULWindow::PersistentAttributesDirty(uint32_t aDirtyFlags)
+{
+ mPersistentAttributesDirty |= aDirtyFlags & mPersistentAttributesMask;
+}
+
+NS_IMETHODIMP nsXULWindow::ApplyChromeFlags()
+{
+ nsCOMPtr<dom::Element> window = GetWindowDOMElement();
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ if (mChromeLoaded) {
+ // The two calls in this block don't need to happen early because they
+ // don't cause a global restyle on the document. Not only that, but the
+ // scrollbar stuff needs a content area to toggle the scrollbars on anyway.
+ // So just don't do these until mChromeLoaded is true.
+
+ // Scrollbars have their own special treatment.
+ SetContentScrollbarVisibility(mChromeFlags &
+ nsIWebBrowserChrome::CHROME_SCROLLBARS ?
+ true : false);
+ }
+
+ /* the other flags are handled together. we have style rules
+ in navigator.css that trigger visibility based on
+ the 'chromehidden' attribute of the <window> tag. */
+ nsAutoString newvalue;
+
+ if (! (mChromeFlags & nsIWebBrowserChrome::CHROME_MENUBAR))
+ newvalue.AppendLiteral("menubar ");
+
+ if (! (mChromeFlags & nsIWebBrowserChrome::CHROME_TOOLBAR))
+ newvalue.AppendLiteral("toolbar ");
+
+ if (! (mChromeFlags & nsIWebBrowserChrome::CHROME_LOCATIONBAR))
+ newvalue.AppendLiteral("location ");
+
+ if (! (mChromeFlags & nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR))
+ newvalue.AppendLiteral("directories ");
+
+ if (! (mChromeFlags & nsIWebBrowserChrome::CHROME_STATUSBAR))
+ newvalue.AppendLiteral("status ");
+
+ if (! (mChromeFlags & nsIWebBrowserChrome::CHROME_EXTRA))
+ newvalue.AppendLiteral("extrachrome ");
+
+ // Note that if we're not actually changing the value this will be a no-op,
+ // so no need to compare to the old value.
+ ErrorResult rv;
+ window->SetAttribute(NS_LITERAL_STRING("chromehidden"), newvalue, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::GetXULBrowserWindow(nsIXULBrowserWindow * *aXULBrowserWindow)
+{
+ NS_IF_ADDREF(*aXULBrowserWindow = mXULBrowserWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsXULWindow::SetXULBrowserWindow(nsIXULBrowserWindow * aXULBrowserWindow)
+{
+ mXULBrowserWindow = aXULBrowserWindow;
+ return NS_OK;
+}
+
+void nsXULWindow::SizeShellToWithLimit(int32_t aDesiredWidth,
+ int32_t aDesiredHeight,
+ int32_t shellItemWidth,
+ int32_t shellItemHeight)
+{
+ int32_t widthDelta = aDesiredWidth - shellItemWidth;
+ int32_t heightDelta = aDesiredHeight - shellItemHeight;
+
+ if (widthDelta || heightDelta) {
+ int32_t winWidth = 0;
+ int32_t winHeight = 0;
+
+ GetSize(&winWidth, &winHeight);
+ // There's no point in trying to make the window smaller than the
+ // desired content area size --- that's not likely to work. This whole
+ // function assumes that the outer docshell is adding some constant
+ // "border" chrome to the content area.
+ winWidth = std::max(winWidth + widthDelta, aDesiredWidth);
+ winHeight = std::max(winHeight + heightDelta, aDesiredHeight);
+ SetSize(winWidth, winHeight, true);
+ }
+}
+
+//*****************************************************************************
+//*** nsContentShellInfo: Object Management
+//*****************************************************************************
+
+nsContentShellInfo::nsContentShellInfo(const nsAString& aID,
+ nsIWeakReference* aContentShell)
+ : id(aID),
+ child(aContentShell)
+{
+ MOZ_COUNT_CTOR(nsContentShellInfo);
+}
+
+nsContentShellInfo::~nsContentShellInfo()
+{
+ MOZ_COUNT_DTOR(nsContentShellInfo);
+ //XXX Set Tree Owner to null if the tree owner is nsXULWindow->mContentTreeOwner
+}
diff --git a/components/appshell/src/nsXULWindow.h b/components/appshell/src/nsXULWindow.h
new file mode 100644
index 000000000..eb059c939
--- /dev/null
+++ b/components/appshell/src/nsXULWindow.h
@@ -0,0 +1,199 @@
+/* -*- 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 nsXULWindow_h__
+#define nsXULWindow_h__
+
+// Local Includes
+#include "nsChromeTreeOwner.h"
+#include "nsContentTreeOwner.h"
+
+// Helper classes
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "nsCOMArray.h"
+#include "nsRect.h"
+#include "Units.h"
+
+// Interfaces needed
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDOMWindow.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIXULWindow.h"
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsIXULBrowserWindow.h"
+#include "nsIWeakReference.h"
+#include "nsIWidgetListener.h"
+#include "nsITabParent.h"
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+// nsXULWindow
+
+#define NS_XULWINDOW_IMPL_CID \
+{ /* 8eaec2f3-ed02-4be2-8e0f-342798477298 */ \
+ 0x8eaec2f3, \
+ 0xed02, \
+ 0x4be2, \
+ { 0x8e, 0x0f, 0x34, 0x27, 0x98, 0x47, 0x72, 0x98 } \
+}
+
+class nsContentShellInfo;
+
+class nsXULWindow : public nsIBaseWindow,
+ public nsIInterfaceRequestor,
+ public nsIXULWindow,
+ public nsSupportsWeakReference
+{
+friend class nsChromeTreeOwner;
+friend class nsContentTreeOwner;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIXULWINDOW
+ NS_DECL_NSIBASEWINDOW
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_XULWINDOW_IMPL_CID)
+
+ void LockUntilChromeLoad() { mLockedUntilChromeLoad = true; }
+ bool IsLocked() const { return mLockedUntilChromeLoad; }
+ void IgnoreXULSizeMode(bool aEnable) { mIgnoreXULSizeMode = aEnable; }
+ void WasRegistered() { mRegistered = true; }
+
+protected:
+ enum persistentAttributes {
+ PAD_MISC = 0x1,
+ PAD_POSITION = 0x2,
+ PAD_SIZE = 0x4
+ };
+
+ explicit nsXULWindow(uint32_t aChromeFlags);
+ virtual ~nsXULWindow();
+
+ NS_IMETHOD EnsureChromeTreeOwner();
+ NS_IMETHOD EnsureContentTreeOwner();
+ NS_IMETHOD EnsurePrimaryContentTreeOwner();
+ NS_IMETHOD EnsurePrompter();
+ NS_IMETHOD EnsureAuthPrompter();
+
+ void OnChromeLoaded();
+ void StaggerPosition(int32_t &aRequestedX, int32_t &aRequestedY,
+ int32_t aSpecWidth, int32_t aSpecHeight);
+ bool LoadPositionFromXUL(int32_t aSpecWidth, int32_t aSpecHeight);
+ bool LoadSizeFromXUL(int32_t& aSpecWidth, int32_t& aSpecHeight);
+ void SetSpecifiedSize(int32_t aSpecWidth, int32_t aSpecHeight);
+ bool LoadMiscPersistentAttributesFromXUL();
+ void SyncAttributesToWidget();
+ NS_IMETHOD SavePersistentAttributes();
+
+ NS_IMETHOD GetWindowDOMWindow(mozIDOMWindowProxy** aDOMWindow);
+ mozilla::dom::Element* GetWindowDOMElement() const;
+
+ // See nsIDocShellTreeOwner for docs on next two methods
+ nsresult ContentShellAdded(nsIDocShellTreeItem* aContentShell,
+ bool aPrimary, bool aTargetable,
+ const nsAString& aID);
+ nsresult ContentShellRemoved(nsIDocShellTreeItem* aContentShell);
+ NS_IMETHOD GetPrimaryContentSize(int32_t* aWidth,
+ int32_t* aHeight);
+ NS_IMETHOD SetPrimaryContentSize(int32_t aWidth,
+ int32_t aHeight);
+ nsresult GetRootShellSize(int32_t* aWidth,
+ int32_t* aHeight);
+ nsresult SetRootShellSize(int32_t aWidth,
+ int32_t aHeight);
+
+ NS_IMETHOD SizeShellTo(nsIDocShellTreeItem* aShellItem, int32_t aCX,
+ int32_t aCY);
+ NS_IMETHOD ExitModalLoop(nsresult aStatus);
+ NS_IMETHOD CreateNewChromeWindow(int32_t aChromeFlags, nsITabParent* aOpeningTab, mozIDOMWindowProxy* aOpenerWindow, nsIXULWindow **_retval);
+ NS_IMETHOD CreateNewContentWindow(int32_t aChromeFlags, nsITabParent* aOpeningTab, mozIDOMWindowProxy* aOpenerWindow, nsIXULWindow **_retval);
+ NS_IMETHOD GetHasPrimaryContent(bool* aResult);
+
+ void EnableParent(bool aEnable);
+ bool ConstrainToZLevel(bool aImmediate, nsWindowZ *aPlacement,
+ nsIWidget *aReqBelow, nsIWidget **aActualBelow);
+ void PlaceWindowLayersBehind(uint32_t aLowLevel, uint32_t aHighLevel,
+ nsIXULWindow *aBehind);
+ void SetContentScrollbarVisibility(bool aVisible);
+ bool GetContentScrollbarVisibility();
+ void PersistentAttributesDirty(uint32_t aDirtyFlags);
+
+ nsChromeTreeOwner* mChromeTreeOwner;
+ nsContentTreeOwner* mContentTreeOwner;
+ nsContentTreeOwner* mPrimaryContentTreeOwner;
+ nsCOMPtr<nsIWidget> mWindow;
+ nsCOMPtr<nsIDocShell> mDocShell;
+ nsCOMPtr<nsPIDOMWindowOuter> mDOMWindow;
+ nsCOMPtr<nsIWeakReference> mParentWindow;
+ nsCOMPtr<nsIPrompt> mPrompter;
+ nsCOMPtr<nsIAuthPrompt> mAuthPrompter;
+ nsCOMPtr<nsIXULBrowserWindow> mXULBrowserWindow;
+ nsCOMPtr<nsIDocShellTreeItem> mPrimaryContentShell;
+ nsTArray<nsContentShellInfo*> mContentShells; // array of doc shells by id
+ nsresult mModalStatus;
+ bool mContinueModalLoop;
+ bool mDebuting; // being made visible right now
+ bool mChromeLoaded; // True when chrome has loaded
+ bool mShowAfterLoad;
+ bool mIntrinsicallySized;
+ bool mCenterAfterLoad;
+ bool mIsHiddenWindow;
+ bool mLockedUntilChromeLoad;
+ bool mIgnoreXULSize;
+ bool mIgnoreXULPosition;
+ bool mChromeFlagsFrozen;
+ bool mIgnoreXULSizeMode;
+ // mDestroying is used to prevent reentry into into Destroy(), which can
+ // otherwise happen due to script running as we tear down various things.
+ bool mDestroying;
+ bool mRegistered;
+ uint32_t mContextFlags;
+ uint32_t mPersistentAttributesDirty; // persistentAttributes
+ uint32_t mPersistentAttributesMask;
+ uint32_t mChromeFlags;
+ nsString mTitle;
+ nsIntRect mOpenerScreenRect; // the screen rect of the opener
+
+ nsCOMArray<nsIWeakReference> mTargetableShells; // targetable shells only
+
+ nsCOMPtr<nsITabParent> mPrimaryTabParent;
+private:
+ nsresult GetPrimaryTabParentSize(int32_t* aWidth, int32_t* aHeight);
+ nsresult GetPrimaryContentShellSize(int32_t* aWidth, int32_t* aHeight);
+ nsresult SetPrimaryTabParentSize(int32_t aWidth, int32_t aHeight);
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsXULWindow, NS_XULWINDOW_IMPL_CID)
+
+// nsContentShellInfo
+// Used to map shell IDs to nsIDocShellTreeItems.
+
+class nsContentShellInfo
+{
+public:
+ nsContentShellInfo(const nsAString& aID,
+ nsIWeakReference* aContentShell);
+ ~nsContentShellInfo();
+
+public:
+ nsString id; // The identifier of the content shell
+ nsWeakPtr child; // content shell (weak reference to nsIDocShellTreeItem)
+};
+
+#endif /* nsXULWindow_h__ */
diff --git a/components/asyncshutdown/AsyncShutdown.jsm b/components/asyncshutdown/AsyncShutdown.jsm
new file mode 100644
index 000000000..43fdc6bf1
--- /dev/null
+++ b/components/asyncshutdown/AsyncShutdown.jsm
@@ -0,0 +1,1020 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Managing safe shutdown of asynchronous services.
+ *
+ * Firefox shutdown is composed of phases that take place
+ * sequentially. Typically, each shutdown phase removes some
+ * capabilities from the application. For instance, at the end of
+ * phase profileBeforeChange, no service is permitted to write to the
+ * profile directory. Consequently, if any service has requested I/O
+ * to the profile directory before or during phase profileBeforeChange,
+ * the system must be informed that these requests need to be completed
+ * before the end of phase profileBeforeChange. Failing to inform the
+ * system of this requirement can (and has been known to) cause data loss.
+ *
+ * Example: At some point during shutdown, the Add-On Manager needs to
+ * ensure that all add-ons have safely written their data to disk,
+ * before writing its own data. Since the data is saved to the
+ * profile, this must be completed during phase profileBeforeChange.
+ *
+ * AsyncShutdown.profileBeforeChange.addBlocker(
+ * "Add-on manager: shutting down",
+ * function condition() {
+ * // Do things.
+ * // Perform I/O that must take place during phase profile-before-change
+ * return promise;
+ * }
+ * });
+ *
+ * In this example, function |condition| will be called at some point
+ * during phase profileBeforeChange and phase profileBeforeChange
+ * itself is guaranteed to not terminate until |promise| is either
+ * resolved or rejected.
+ */
+
+"use strict";
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+ "resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "gDebug",
+ "@mozilla.org/xpcom/debug;1", "nsIDebug2");
+Object.defineProperty(this, "gCrashReporter", {
+ get: function() {
+ delete this.gCrashReporter;
+ try {
+ let reporter = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsICrashReporter);
+ return this.gCrashReporter = reporter;
+ } catch (ex) {
+ return this.gCrashReporter = null;
+ }
+ },
+ configurable: true
+});
+
+// `true` if this is a content process, `false` otherwise.
+// It would be nicer to go through `Services.appInfo`, but some tests need to be
+// able to replace that field with a custom implementation before it is first
+// called.
+const isContent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+
+// Display timeout warnings after 10 seconds
+const DELAY_WARNING_MS = 10 * 1000;
+
+
+// Crash the process if shutdown is really too long
+// (allowing for sleep).
+const PREF_DELAY_CRASH_MS = "toolkit.asyncshutdown.crash_timeout";
+var DELAY_CRASH_MS = Services.prefs.getIntPref(PREF_DELAY_CRASH_MS,
+ 60 * 1000); // One minute
+Services.prefs.addObserver(PREF_DELAY_CRASH_MS, function() {
+ DELAY_CRASH_MS = Services.prefs.getIntPref(PREF_DELAY_CRASH_MS);
+}, false);
+
+/**
+ * A set of Promise that supports waiting.
+ *
+ * Promise items may be added or removed during the wait. The wait will
+ * resolve once all Promise items have been resolved or removed.
+ */
+function PromiseSet() {
+ /**
+ * key: the Promise passed pass the client of the `PromiseSet`.
+ * value: an indirection on top of `key`, as an object with
+ * the following fields:
+ * - indirection: a Promise resolved if `key` is resolved or
+ * if `resolve` is called
+ * - resolve: a function used to resolve the indirection.
+ */
+ this._indirections = new Map();
+}
+PromiseSet.prototype = {
+ /**
+ * Wait until all Promise have been resolved or removed.
+ *
+ * Note that calling `wait()` causes Promise to be removed from the
+ * Set once they are resolved.
+ *
+ * @return {Promise} Resolved once all Promise have been resolved or removed,
+ * or rejected after at least one Promise has rejected.
+ */
+ wait: function() {
+ // Pick an arbitrary element in the map, if any exists.
+ let entry = this._indirections.entries().next();
+ if (entry.done) {
+ // No indirections left, we are done.
+ return Promise.resolve();
+ }
+
+ let [, indirection] = entry.value;
+ let promise = indirection.promise;
+ promise = promise.then(() =>
+ // At this stage, the entry has been cleaned up.
+ this.wait()
+ );
+ return promise;
+ },
+
+ /**
+ * Add a new Promise to the set.
+ *
+ * Calls to wait (including ongoing calls) will only return once
+ * `key` has either resolved or been removed.
+ */
+ add: function(key) {
+ this._ensurePromise(key);
+ let indirection = PromiseUtils.defer();
+ key.then(
+ x => {
+ // Clean up immediately.
+ // This needs to be done before the call to `resolve`, otherwise
+ // `wait()` may loop forever.
+ this._indirections.delete(key);
+ indirection.resolve(x);
+ },
+ err => {
+ this._indirections.delete(key);
+ indirection.reject(err);
+ });
+ this._indirections.set(key, indirection);
+ },
+
+ /**
+ * Remove a Promise from the set.
+ *
+ * Calls to wait (including ongoing calls) will ignore this promise,
+ * unless it is added again.
+ */
+ delete: function(key) {
+ this._ensurePromise(key);
+ let value = this._indirections.get(key);
+ if (!value) {
+ return false;
+ }
+ this._indirections.delete(key);
+ value.resolve();
+ return true;
+ },
+
+ _ensurePromise: function(key) {
+ if (!key || typeof key != "object") {
+ throw new Error("Expected an object");
+ }
+ if ((!("then" in key)) || typeof key.then != "function") {
+ throw new Error("Expected a Promise");
+ }
+ },
+
+};
+
+
+/**
+ * Display a warning.
+ *
+ * As this code is generally used during shutdown, there are chances
+ * that the UX will not be available to display warnings on the
+ * console. We therefore use dump() rather than Cu.reportError().
+ */
+function log(msg, prefix = "", error = null) {
+ try {
+ dump(prefix + msg + "\n");
+ if (error) {
+ dump(prefix + error + "\n");
+ if (typeof error == "object" && "stack" in error) {
+ dump(prefix + error.stack + "\n");
+ }
+ }
+ } catch (ex) {
+ dump("INTERNAL ERROR in AsyncShutdown: cannot log message.\n");
+ }
+}
+const PREF_DEBUG_LOG = "toolkit.asyncshutdown.log";
+var DEBUG_LOG = Services.prefs.getBoolPref(PREF_DEBUG_LOG, false);
+Services.prefs.addObserver(PREF_DEBUG_LOG, function() {
+ DEBUG_LOG = Services.prefs.getBoolPref(PREF_DEBUG_LOG);
+}, false);
+
+function debug(msg, error=null) {
+ if (DEBUG_LOG) {
+ log(msg, "DEBUG: ", error);
+ }
+}
+function warn(msg, error = null) {
+ log(msg, "WARNING: ", error);
+}
+function fatalerr(msg, error = null) {
+ log(msg, "FATAL ERROR: ", error);
+}
+
+// Utility function designed to get the current state of execution
+// of a blocker.
+// We are a little paranoid here to ensure that in case of evaluation
+// error we do not block the AsyncShutdown.
+function safeGetState(fetchState) {
+ if (!fetchState) {
+ return "(none)";
+ }
+ let data, string;
+ try {
+ // Evaluate fetchState(), normalize the result into something that we can
+ // safely stringify or upload.
+ let state = fetchState();
+ if (!state) {
+ return "(none)";
+ }
+ string = JSON.stringify(state);
+ data = JSON.parse(string);
+ // Simplify the rest of the code by ensuring that we can simply
+ // concatenate the result to a message.
+ if (data && typeof data == "object") {
+ data.toString = function() {
+ return string;
+ };
+ }
+ return data;
+ } catch (ex) {
+
+ // Make sure that this causes test failures
+ Promise.reject(ex);
+
+ if (string) {
+ return string;
+ }
+ try {
+ return "Error getting state: " + ex + " at " + ex.stack;
+ } catch (ex2) {
+ return "Error getting state but could not display error";
+ }
+ }
+}
+
+/**
+ * Countdown for a given duration, skipping beats if the computer is too busy,
+ * sleeping or otherwise unavailable.
+ *
+ * @param {number} delay An approximate delay to wait in milliseconds (rounded
+ * up to the closest second).
+ *
+ * @return Deferred
+ */
+function looseTimer(delay) {
+ let DELAY_BEAT = 1000;
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ let beats = Math.ceil(delay / DELAY_BEAT);
+ let deferred = Promise.defer();
+ timer.initWithCallback(function() {
+ if (beats <= 0) {
+ deferred.resolve();
+ }
+ --beats;
+ }, DELAY_BEAT, Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP);
+ // Ensure that the timer is both canceled once we are done with it
+ // and not garbage-collected until then.
+ deferred.promise.then(() => timer.cancel(), () => timer.cancel());
+ return deferred;
+}
+
+/**
+ * Given an nsIStackFrame object, find the caller filename, line number,
+ * and stack if necessary, and return them as an object.
+ *
+ * @param {nsIStackFrame} topFrame Top frame of the call stack.
+ * @param {string} filename Pre-supplied filename or null if unknown.
+ * @param {number} lineNumber Pre-supplied line number or null if unknown.
+ * @param {string} stack Pre-supplied stack or null if unknown.
+ *
+ * @return object
+ */
+function getOrigin(topFrame, filename = null, lineNumber = null, stack = null) {
+ try {
+ // Determine the filename and line number of the caller.
+ let frame = topFrame;
+
+ for (; frame && frame.filename == topFrame.filename; frame = frame.caller) {
+ // Climb up the stack
+ }
+
+ if (filename == null) {
+ filename = frame ? frame.filename : "?";
+ }
+ if (lineNumber == null) {
+ lineNumber = frame ? frame.lineNumber : 0;
+ }
+ if (stack == null) {
+ // Now build the rest of the stack as a string, using Task.jsm's rewriting
+ // to ensure that we do not lose information at each call to `Task.spawn`.
+ let frames = [];
+ while (frame != null) {
+ frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
+ frame = frame.caller;
+ }
+ stack = Task.Debugging.generateReadableStack(frames.join("\n")).split("\n");
+ }
+
+ return {
+ filename: filename,
+ lineNumber: lineNumber,
+ stack: stack,
+ };
+ } catch (ex) {
+ return {
+ filename: "<internal error: could not get origin>",
+ lineNumber: -1,
+ stack: "<internal error: could not get origin>",
+ }
+ }
+}
+
+this.EXPORTED_SYMBOLS = ["AsyncShutdown"];
+
+/**
+ * {string} topic -> phase
+ */
+var gPhases = new Map();
+
+this.AsyncShutdown = {
+ /**
+ * Access function getPhase. For testing purposes only.
+ */
+ get _getPhase() {
+ let accepted = Services.prefs.getBoolPref("toolkit.asyncshutdown.testing", false);
+ if (accepted) {
+ return getPhase;
+ }
+ return undefined;
+ }
+};
+
+/**
+ * Register a new phase.
+ *
+ * @param {string} topic The notification topic for this Phase.
+ * @see {https://developer.mozilla.org/en-US/docs/Observer_Notifications}
+ */
+function getPhase(topic) {
+ let phase = gPhases.get(topic);
+ if (phase) {
+ return phase;
+ }
+ let spinner = new Spinner(topic);
+ phase = Object.freeze({
+ /**
+ * Register a blocker for the completion of a phase.
+ *
+ * @param {string} name The human-readable name of the blocker. Used
+ * for debugging/error reporting. Please make sure that the name
+ * respects the following model: "Some Service: some action in progress" -
+ * for instance "OS.File: flushing all pending I/O";
+ * @param {function|promise|*} condition A condition blocking the
+ * completion of the phase. Generally, this is a function
+ * returning a promise. This function is evaluated during the
+ * phase and the phase is guaranteed to not terminate until the
+ * resulting promise is either resolved or rejected. If
+ * |condition| is not a function but another value |v|, it behaves
+ * as if it were a function returning |v|.
+ * @param {object*} details Optionally, an object with details
+ * that may be useful for error reporting, as a subset of of the following
+ * fields:
+ * - fetchState (strongly recommended) A function returning
+ * information about the current state of the blocker as an
+ * object. Used for providing more details when logging errors or
+ * crashing.
+ * - stack. A string containing stack information. This module can
+ * generally infer stack information if it is not provided.
+ * - lineNumber A number containing the line number for the caller.
+ * This module can generally infer this information if it is not
+ * provided.
+ * - filename A string containing the filename for the caller. This
+ * module can generally infer the information if it is not provided.
+ *
+ * Examples:
+ * AsyncShutdown.profileBeforeChange.addBlocker("Module: just a promise",
+ * promise); // profileBeforeChange will not complete until
+ * // promise is resolved or rejected
+ *
+ * AsyncShutdown.profileBeforeChange.addBlocker("Module: a callback",
+ * function callback() {
+ * // ...
+ * // Execute this code during profileBeforeChange
+ * return promise;
+ * // profileBeforeChange will not complete until promise
+ * // is resolved or rejected
+ * });
+ *
+ * AsyncShutdown.profileBeforeChange.addBlocker("Module: trivial callback",
+ * function callback() {
+ * // ...
+ * // Execute this code during profileBeforeChange
+ * // No specific guarantee about completion of profileBeforeChange
+ * });
+ */
+ addBlocker: function(name, condition, details = null) {
+ spinner.addBlocker(name, condition, details);
+ },
+ /**
+ * Remove the blocker for a condition.
+ *
+ * If several blockers have been registered for the same
+ * condition, remove all these blockers. If no blocker has been
+ * registered for this condition, this is a noop.
+ *
+ * @return {boolean} true if a blocker has been removed, false
+ * otherwise. Note that a result of false may mean either that
+ * the blocker has never been installed or that the phase has
+ * completed and the blocker has already been resolved.
+ */
+ removeBlocker: function(condition) {
+ return spinner.removeBlocker(condition);
+ },
+
+ get name() {
+ return spinner.name;
+ },
+
+ /**
+ * Trigger the phase without having to broadcast a
+ * notification. For testing purposes only.
+ */
+ get _trigger() {
+ let accepted = Services.prefs.getBoolPref("toolkit.asyncshutdown.testing", false);
+ if (accepted) {
+ return () => spinner.observe();
+ }
+ return undefined;
+ }
+ });
+ gPhases.set(topic, phase);
+ return phase;
+}
+
+/**
+ * Utility class used to spin the event loop until all blockers for a
+ * Phase are satisfied.
+ *
+ * @param {string} topic The xpcom notification for that phase.
+ */
+function Spinner(topic) {
+ this._barrier = new Barrier(topic);
+ this._topic = topic;
+ Services.obs.addObserver(this, topic, false);
+}
+
+Spinner.prototype = {
+ /**
+ * Register a new condition for this phase.
+ *
+ * See the documentation of `addBlocker` in property `client`
+ * of instances of `Barrier`.
+ */
+ addBlocker: function(name, condition, details) {
+ this._barrier.client.addBlocker(name, condition, details);
+ },
+ /**
+ * Remove the blocker for a condition.
+ *
+ * See the documentation of `removeBlocker` in rpoperty `client`
+ * of instances of `Barrier`
+ *
+ * @return {boolean} true if a blocker has been removed, false
+ * otherwise. Note that a result of false may mean either that
+ * the blocker has never been installed or that the phase has
+ * completed and the blocker has already been resolved.
+ */
+ removeBlocker: function(condition) {
+ return this._barrier.client.removeBlocker(condition);
+ },
+
+ get name() {
+ return this._barrier.client.name;
+ },
+
+ // nsIObserver.observe
+ observe: function() {
+ let topic = this._topic;
+ debug(`Starting phase ${ topic }`);
+ Services.obs.removeObserver(this, topic);
+
+ let satisfied = false; // |true| once we have satisfied all conditions
+ let promise;
+ try {
+ promise = this._barrier.wait({
+ warnAfterMS: DELAY_WARNING_MS,
+ crashAfterMS: DELAY_CRASH_MS
+ }).catch(
+ // Additional precaution to be entirely sure that we cannot reject.
+ );
+ } catch (ex) {
+ debug("Error waiting for notification");
+ throw ex;
+ }
+
+ // Now, spin the event loop
+ debug("Spinning the event loop");
+ promise.then(() => satisfied = true); // This promise cannot reject
+ let thread = Services.tm.mainThread;
+ while (!satisfied) {
+ try {
+ thread.processNextEvent(true);
+ } catch (ex) {
+ // An uncaught error should not stop us, but it should still
+ // be reported and cause tests to fail.
+ Promise.reject(ex);
+ }
+ }
+ debug(`Finished phase ${ topic }`);
+ }
+};
+
+/**
+ * A mechanism used to register blockers that prevent some action from
+ * happening.
+ *
+ * An instance of |Barrier| provides a capability |client| that
+ * clients can use to register blockers. The barrier is resolved once
+ * all registered blockers have been resolved. The owner of the
+ * |Barrier| may wait for the resolution of the barrier and obtain
+ * information on which blockers have not been resolved yet.
+ *
+ * @param {string} name The name of the blocker. Used mainly for error-
+ * reporting.
+ */
+function Barrier(name) {
+ if (!name) {
+ throw new TypeError("Instances of Barrier need a (non-empty) name");
+ }
+
+
+ /**
+ * The set of all Promise for which we need to wait before the barrier
+ * is lifted. Note that this set may be changed while we are waiting.
+ *
+ * Set to `null` once the wait is complete.
+ */
+ this._waitForMe = new PromiseSet();
+
+ /**
+ * A map from conditions, as passed by users during the call to `addBlocker`,
+ * to `promise`, as present in `this._waitForMe`.
+ *
+ * Used to let users perform cleanup through `removeBlocker`.
+ * Set to `null` once the wait is complete.
+ *
+ * Key: condition (any, as passed by user)
+ * Value: promise used as a key in `this._waitForMe`. Note that there is
+ * no guarantee that the key is still present in `this._waitForMe`.
+ */
+ this._conditionToPromise = new Map();
+
+ /**
+ * A map from Promise, as present in `this._waitForMe` or
+ * `this._conditionToPromise`, to information on blockers.
+ *
+ * Key: Promise (as present in this._waitForMe or this._conditionToPromise).
+ * Value: {
+ * trigger: function,
+ * promise,
+ * name,
+ * fetchState: function,
+ * stack,
+ * filename,
+ * lineNumber
+ * };
+ */
+ this._promiseToBlocker = new Map();
+
+ /**
+ * The name of the barrier.
+ */
+ if (typeof name != "string") {
+ throw new TypeError("The name of the barrier must be a string");
+ }
+ this._name = name;
+
+ /**
+ * A cache for the promise returned by wait().
+ */
+ this._promise = null;
+
+ /**
+ * `true` once we have started waiting.
+ */
+ this._isStarted = false;
+
+ /**
+ * The capability of adding blockers. This object may safely be returned
+ * or passed to clients.
+ */
+ this.client = {
+ /**
+ * The name of the barrier owning this client.
+ */
+ get name() {
+ return name;
+ },
+
+ /**
+ * Register a blocker for the completion of this barrier.
+ *
+ * @param {string} name The human-readable name of the blocker. Used
+ * for debugging/error reporting. Please make sure that the name
+ * respects the following model: "Some Service: some action in progress" -
+ * for instance "OS.File: flushing all pending I/O";
+ * @param {function|promise|*} condition A condition blocking the
+ * completion of the phase. Generally, this is a function
+ * returning a promise. This function is evaluated during the
+ * phase and the phase is guaranteed to not terminate until the
+ * resulting promise is either resolved or rejected. If
+ * |condition| is not a function but another value |v|, it behaves
+ * as if it were a function returning |v|.
+ * @param {object*} details Optionally, an object with details
+ * that may be useful for error reporting, as a subset of of the following
+ * fields:
+ * - fetchState (strongly recommended) A function returning
+ * information about the current state of the blocker as an
+ * object. Used for providing more details when logging errors or
+ * crashing.
+ * - stack. A string containing stack information. This module can
+ * generally infer stack information if it is not provided.
+ * - lineNumber A number containing the line number for the caller.
+ * This module can generally infer this information if it is not
+ * provided.
+ * - filename A string containing the filename for the caller. This
+ * module can generally infer the information if it is not provided.
+ */
+ addBlocker: (name, condition, details) => {
+ if (typeof name != "string") {
+ throw new TypeError("Expected a human-readable name as first argument");
+ }
+ if (details && typeof details == "function") {
+ details = {
+ fetchState: details
+ };
+ } else if (!details) {
+ details = {};
+ }
+ if (typeof details != "object") {
+ throw new TypeError("Expected an object as third argument to `addBlocker`, got " + details);
+ }
+ if (!this._waitForMe) {
+ throw new Error(`Phase "${ this._name }" is finished, it is too late to register completion condition "${ name }"`);
+ }
+ debug(`Adding blocker ${ name } for phase ${ this._name }`);
+
+ // Normalize the details
+
+ let fetchState = details.fetchState || null;
+ if (fetchState != null && typeof fetchState != "function") {
+ throw new TypeError("Expected a function for option `fetchState`");
+ }
+ let filename = details.filename || null;
+ let lineNumber = details.lineNumber || null;
+ let stack = details.stack || null;
+
+ // Split the condition between a trigger function and a promise.
+
+ // The function to call to notify the blocker that we have started waiting.
+ // This function returns a promise resolved/rejected once the
+ // condition is complete, and never throws.
+ let trigger;
+
+ // A promise resolved once the condition is complete.
+ let promise;
+ if (typeof condition == "function") {
+ promise = new Promise((resolve, reject) => {
+ trigger = () => {
+ try {
+ resolve(condition());
+ } catch (ex) {
+ reject(ex);
+ }
+ }
+ });
+ } else {
+ // If `condition` is not a function, `trigger` is not particularly
+ // interesting, and `condition` needs to be normalized to a promise.
+ trigger = () => {};
+ promise = Promise.resolve(condition);
+ }
+
+ // Make sure that `promise` never rejects.
+ promise = promise.then(null, error => {
+ let msg = `A blocker encountered an error while we were waiting.
+ Blocker: ${ name }
+ Phase: ${ this._name }
+ State: ${ safeGetState(fetchState) }`;
+ warn(msg, error);
+
+ // The error should remain uncaught, to ensure that it
+ // still causes tests to fail.
+ Promise.reject(error);
+ }).catch(
+ // Added as a last line of defense, in case `warn`, `this._name` or
+ // `safeGetState` somehow throws an error.
+ );
+
+ let topFrame = null;
+ if (filename == null || lineNumber == null || stack == null) {
+ topFrame = Components.stack;
+ }
+
+ let blocker = {
+ trigger: trigger,
+ promise: promise,
+ name: name,
+ fetchState: fetchState,
+ getOrigin: () => getOrigin(topFrame, filename, lineNumber, stack),
+ };
+
+ this._waitForMe.add(promise);
+ this._promiseToBlocker.set(promise, blocker);
+ this._conditionToPromise.set(condition, promise);
+
+ // As conditions may hold lots of memory, we attempt to cleanup
+ // as soon as we are done (which might be in the next tick, if
+ // we have been passed a resolved promise).
+ promise = promise.then(() => {
+ debug(`Completed blocker ${ name } for phase ${ this._name }`);
+ this._removeBlocker(condition);
+ });
+
+ if (this._isStarted) {
+ // The wait has already started. The blocker should be
+ // notified asap. We do it out of band as clients probably
+ // expect `addBlocker` to return immediately.
+ Promise.resolve().then(trigger);
+ }
+ },
+
+ /**
+ * Remove the blocker for a condition.
+ *
+ * If several blockers have been registered for the same
+ * condition, remove all these blockers. If no blocker has been
+ * registered for this condition, this is a noop.
+ *
+ * @return {boolean} true if at least one blocker has been
+ * removed, false otherwise.
+ */
+ removeBlocker: (condition) => {
+ return this._removeBlocker(condition);
+ }
+ };
+}
+Barrier.prototype = Object.freeze({
+ /**
+ * The current state of the barrier, as a JSON-serializable object
+ * designed for error-reporting.
+ */
+ get state() {
+ if (!this._isStarted) {
+ return "Not started";
+ }
+ if (!this._waitForMe) {
+ return "Complete";
+ }
+ let frozen = [];
+ for (let blocker of this._promiseToBlocker.values()) {
+ let {name, fetchState} = blocker;
+ let {stack, filename, lineNumber} = blocker.getOrigin();
+ frozen.push({
+ name: name,
+ state: safeGetState(fetchState),
+ filename: filename,
+ lineNumber: lineNumber,
+ stack: stack
+ });
+ }
+ return frozen;
+ },
+
+ /**
+ * Wait until all currently registered blockers are complete.
+ *
+ * Once this method has been called, any attempt to register a new blocker
+ * for this barrier will cause an error.
+ *
+ * Successive calls to this method always return the same value.
+ *
+ * @param {object=} options Optionally, an object that may contain
+ * the following fields:
+ * {number} warnAfterMS If provided and > 0, print a warning if the barrier
+ * has not been resolved after the given number of milliseconds.
+ * {number} crashAfterMS If provided and > 0, crash the process if the barrier
+ * has not been resolved after the give number of milliseconds (rounded up
+ * to the next second). To avoid crashing simply because the computer is busy
+ * or going to sleep, we actually wait for ceil(crashAfterMS/1000) successive
+ * periods of at least one second. Upon crashing, if a crash reporter is present,
+ * prepare a crash report with the state of this barrier.
+ *
+ *
+ * @return {Promise} A promise satisfied once all blockers are complete.
+ */
+ wait: function(options = {}) {
+ // This method only implements caching on top of _wait()
+ if (this._promise) {
+ return this._promise;
+ }
+ return this._promise = this._wait(options);
+ },
+ _wait: function(options) {
+
+ // Sanity checks
+ if (this._isStarted) {
+ throw new TypeError("Internal error: already started " + this._name);
+ }
+ if (!this._waitForMe || !this._conditionToPromise || !this._promiseToBlocker) {
+ throw new TypeError("Internal error: already finished " + this._name);
+ }
+
+ let topic = this._name;
+
+ // Notify blockers
+ for (let blocker of this._promiseToBlocker.values()) {
+ blocker.trigger(); // We have guarantees that this method will never throw
+ }
+
+ this._isStarted = true;
+
+ // Now, wait
+ let promise = this._waitForMe.wait();
+
+ promise = promise.then(null, function onError(error) {
+ // I don't think that this can happen.
+ // However, let's be overcautious with async/shutdown error reporting.
+ let msg = "An uncaught error appeared while completing the phase." +
+ " Phase: " + topic;
+ warn(msg, error);
+ });
+
+ promise = promise.then(() => {
+ // Cleanup memory
+ this._waitForMe = null;
+ this._promiseToBlocker = null;
+ this._conditionToPromise = null;
+ });
+
+ // Now handle warnings and crashes
+ let warnAfterMS = DELAY_WARNING_MS;
+ if (options && "warnAfterMS" in options) {
+ if (typeof options.warnAfterMS == "number"
+ || options.warnAfterMS == null) {
+ // Change the delay or deactivate warnAfterMS
+ warnAfterMS = options.warnAfterMS;
+ } else {
+ throw new TypeError("Wrong option value for warnAfterMS");
+ }
+ }
+
+ if (warnAfterMS && warnAfterMS > 0) {
+ // If the promise takes too long to be resolved/rejected,
+ // we need to notify the user.
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(() => {
+ let msg = "At least one completion condition is taking too long to complete." +
+ " Conditions: " + JSON.stringify(this.state) +
+ " Barrier: " + topic;
+ warn(msg);
+ }, warnAfterMS, Ci.nsITimer.TYPE_ONE_SHOT);
+
+ promise = promise.then(function onSuccess() {
+ timer.cancel();
+ // As a side-effect, this prevents |timer| from
+ // being garbage-collected too early.
+ });
+ }
+
+ let crashAfterMS = DELAY_CRASH_MS;
+ if (options && "crashAfterMS" in options) {
+ if (typeof options.crashAfterMS == "number"
+ || options.crashAfterMS == null) {
+ // Change the delay or deactivate crashAfterMS
+ crashAfterMS = options.crashAfterMS;
+ } else {
+ throw new TypeError("Wrong option value for crashAfterMS");
+ }
+ }
+
+ if (crashAfterMS > 0) {
+ let timeToCrash = null;
+
+ // If after |crashAfterMS| milliseconds (adjusted to take into
+ // account sleep and otherwise busy computer) we have not finished
+ // this shutdown phase, we assume that the shutdown is somehow
+ // frozen, presumably deadlocked. At this stage, the only thing we
+ // can do to avoid leaving the user's computer in an unstable (and
+ // battery-sucking) situation is report the issue and crash.
+ timeToCrash = looseTimer(crashAfterMS);
+ timeToCrash.promise.then(
+ function onTimeout() {
+ // Report the problem as best as we can, then crash.
+ let state = this.state;
+
+ // If you change the following message, please make sure
+ // that any information on the topic and state appears
+ // within the first 200 characters of the message. This
+ // helps automatically sort oranges.
+ let msg = "AsyncShutdown timeout in " + topic +
+ " Conditions: " + JSON.stringify(state) +
+ " At least one completion condition failed to complete" +
+ " within a reasonable amount of time. Causing a crash to" +
+ " ensure that we do not leave the user with an unresponsive" +
+ " process draining resources.";
+ fatalerr(msg);
+ if (gCrashReporter && gCrashReporter.enabled) {
+ let data = {
+ phase: topic,
+ conditions: state
+ };
+ gCrashReporter.annotateCrashReport("AsyncShutdownTimeout",
+ JSON.stringify(data));
+ } else {
+ warn("No crash reporter available");
+ }
+
+ // To help sorting out bugs, we want to make sure that the
+ // call to nsIDebug2.abort points to a guilty client, rather
+ // than to AsyncShutdown itself. We pick a client that is
+ // still blocking and use its filename/lineNumber,
+ // which have been determined during the call to `addBlocker`.
+ let filename = "?";
+ let lineNumber = -1;
+ for (let blocker of this._promiseToBlocker.values()) {
+ ({filename, lineNumber} = blocker.getOrigin());
+ break;
+ }
+ gDebug.abort(filename, lineNumber);
+ }.bind(this),
+ function onSatisfied() {
+ // The promise has been rejected, which means that we have satisfied
+ // all completion conditions.
+ });
+
+ promise = promise.then(function() {
+ timeToCrash.reject();
+ }/* No error is possible here*/);
+ }
+
+ return promise;
+ },
+
+ _removeBlocker: function(condition) {
+ if (!this._waitForMe || !this._promiseToBlocker || !this._conditionToPromise) {
+ // We have already cleaned up everything.
+ return false;
+ }
+
+ let promise = this._conditionToPromise.get(condition);
+ if (!promise) {
+ // The blocker has already been removed
+ return false;
+ }
+ this._conditionToPromise.delete(condition);
+ this._promiseToBlocker.delete(promise);
+ return this._waitForMe.delete(promise);
+ },
+
+});
+
+
+
+// List of well-known phases
+// Ideally, phases should be registered from the component that decides
+// when they start/stop. For compatibility with existing startup/shutdown
+// mechanisms, we register a few phases here.
+
+// Parent process
+if (!isContent) {
+ this.AsyncShutdown.profileChangeTeardown = getPhase("profile-change-teardown");
+ this.AsyncShutdown.profileBeforeChange = getPhase("profile-before-change");
+ this.AsyncShutdown.placesClosingInternalConnection = getPhase("places-will-close-connection");
+}
+
+// Notifications that fire in the parent and content process, but should
+// only have phases in the parent process.
+if (!isContent) {
+ this.AsyncShutdown.quitApplicationGranted = getPhase("quit-application-granted");
+}
+
+// Don't add a barrier for content-child-shutdown because this
+// makes it easier to cause shutdown hangs.
+
+// All processes
+this.AsyncShutdown.webWorkersShutdown = getPhase("web-workers-shutdown");
+this.AsyncShutdown.xpcomWillShutdown = getPhase("xpcom-will-shutdown");
+
+this.AsyncShutdown.Barrier = Barrier;
+
+Object.freeze(this.AsyncShutdown);
diff --git a/components/asyncshutdown/moz.build b/components/asyncshutdown/moz.build
new file mode 100644
index 000000000..861e991c7
--- /dev/null
+++ b/components/asyncshutdown/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_MODULE = 'toolkit_asyncshutdown'
+XPIDL_SOURCES += ['nsIAsyncShutdown.idl']
+
+EXTRA_JS_MODULES += ['AsyncShutdown.jsm']
+
+EXTRA_COMPONENTS += [
+ 'nsAsyncShutdown.js',
+ 'nsAsyncShutdown.manifest',
+]
diff --git a/components/asyncshutdown/nsAsyncShutdown.js b/components/asyncshutdown/nsAsyncShutdown.js
new file mode 100644
index 000000000..70fea9076
--- /dev/null
+++ b/components/asyncshutdown/nsAsyncShutdown.js
@@ -0,0 +1,276 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * An implementation of nsIAsyncShutdown* based on AsyncShutdown.jsm
+ */
+
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+var XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}).XPCOMUtils;
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+ "resource://gre/modules/AsyncShutdown.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+
+/**
+ * Conversion between nsIPropertyBag and JS object
+ */
+var PropertyBagConverter = {
+ // From nsIPropertyBag to JS
+ toObject: function(bag) {
+ if (!(bag instanceof Ci.nsIPropertyBag)) {
+ throw new TypeError("Not a property bag");
+ }
+ let result = {};
+ let enumerator = bag.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let {name, value: property} = enumerator.getNext().QueryInterface(Ci.nsIProperty);
+ let value = this.toValue(property);
+ result[name] = value;
+ }
+ return result;
+ },
+ toValue: function(property) {
+ if (typeof property != "object") {
+ return property;
+ }
+ if (Array.isArray(property)) {
+ return property.map(this.toValue, this);
+ }
+ if (property && property instanceof Ci.nsIPropertyBag) {
+ return this.toObject(property);
+ }
+ return property;
+ },
+
+ // From JS to nsIPropertyBag
+ fromObject: function(obj) {
+ if (obj == null || typeof obj != "object") {
+ throw new TypeError("Invalid object: " + obj);
+ }
+ let bag = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ for (let k of Object.keys(obj)) {
+ let value = this.fromValue(obj[k]);
+ bag.setProperty(k, value);
+ }
+ return bag;
+ },
+ fromValue: function(value) {
+ if (typeof value == "function") {
+ return null; // Emulating the behavior of JSON.stringify with functions
+ }
+ if (Array.isArray(value)) {
+ return value.map(this.fromValue, this);
+ }
+ if (value == null || typeof value != "object") {
+ // Auto-converted to nsIVariant
+ return value;
+ }
+ return this.fromObject(value);
+ },
+};
+
+
+
+/**
+ * Construct an instance of nsIAsyncShutdownClient from a
+ * AsyncShutdown.Barrier client.
+ *
+ * @param {object} moduleClient A client, as returned from the `client`
+ * property of an instance of `AsyncShutdown.Barrier`. This client will
+ * serve as back-end for methods `addBlocker` and `removeBlocker`.
+ * @constructor
+ */
+function nsAsyncShutdownClient(moduleClient) {
+ if (!moduleClient) {
+ throw new TypeError("nsAsyncShutdownClient expects one argument");
+ }
+ this._moduleClient = moduleClient;
+ this._byName = new Map();
+}
+nsAsyncShutdownClient.prototype = {
+ _getPromisified: function(xpcomBlocker) {
+ let candidate = this._byName.get(xpcomBlocker.name);
+ if (!candidate) {
+ return null;
+ }
+ if (candidate.xpcom === xpcomBlocker) {
+ return candidate.jsm;
+ }
+ return null;
+ },
+ _setPromisified: function(xpcomBlocker, moduleBlocker) {
+ let candidate = this._byName.get(xpcomBlocker.name);
+ if (!candidate) {
+ this._byName.set(xpcomBlocker.name, {xpcom: xpcomBlocker,
+ jsm: moduleBlocker});
+ return;
+ }
+ if (candidate.xpcom === xpcomBlocker) {
+ return;
+ }
+ throw new Error("We have already registered a distinct blocker with the same name: " + xpcomBlocker.name);
+ },
+ _deletePromisified: function(xpcomBlocker) {
+ let candidate = this._byName.get(xpcomBlocker.name);
+ if (!candidate || candidate.xpcom !== xpcomBlocker) {
+ return false;
+ }
+ this._byName.delete(xpcomBlocker.name);
+ return true;
+ },
+ get jsclient() {
+ return this._moduleClient;
+ },
+ get name() {
+ return this._moduleClient.name;
+ },
+ addBlocker: function(/* nsIAsyncShutdownBlocker*/ xpcomBlocker,
+ fileName, lineNumber, stack) {
+ // We need a Promise-based function with the same behavior as
+ // `xpcomBlocker`. Furthermore, to support `removeBlocker`, we
+ // need to ensure that we always get the same Promise-based
+ // function if we call several `addBlocker`/`removeBlocker` several
+ // times with the same `xpcomBlocker`.
+ //
+ // Ideally, this should be done with a WeakMap() with xpcomBlocker
+ // as a key, but XPConnect NativeWrapped objects cannot serve as
+ // WeakMap keys.
+ //
+ let moduleBlocker = this._getPromisified(xpcomBlocker);
+ if (!moduleBlocker) {
+ moduleBlocker = () => new Promise(
+ // This promise is never resolved. By opposition to AsyncShutdown
+ // blockers, `nsIAsyncShutdownBlocker`s are always lifted by calling
+ // `removeBlocker`.
+ () => xpcomBlocker.blockShutdown(this)
+ );
+
+ this._setPromisified(xpcomBlocker, moduleBlocker);
+ }
+
+ this._moduleClient.addBlocker(xpcomBlocker.name,
+ moduleBlocker,
+ {
+ fetchState: () => {
+ let state = xpcomBlocker.state;
+ if (state) {
+ return PropertyBagConverter.toValue(state);
+ }
+ return null;
+ },
+ filename: fileName,
+ lineNumber: lineNumber,
+ stack: stack,
+ });
+ },
+
+ removeBlocker: function(xpcomBlocker) {
+ let moduleBlocker = this._getPromisified(xpcomBlocker);
+ if (!moduleBlocker) {
+ return false;
+ }
+ this._deletePromisified(xpcomBlocker);
+ return this._moduleClient.removeBlocker(moduleBlocker);
+ },
+
+ /* ........ QueryInterface .............. */
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAsyncShutdownBarrier]),
+ classID: Components.ID("{314e9e96-cc37-4d5c-843b-54709ce11426}"),
+};
+
+/**
+ * Construct an instance of nsIAsyncShutdownBarrier from an instance
+ * of AsyncShutdown.Barrier.
+ *
+ * @param {object} moduleBarrier an instance if
+ * `AsyncShutdown.Barrier`. This instance will serve as back-end for
+ * all methods.
+ * @constructor
+ */
+function nsAsyncShutdownBarrier(moduleBarrier) {
+ this._client = new nsAsyncShutdownClient(moduleBarrier.client);
+ this._moduleBarrier = moduleBarrier;
+}
+nsAsyncShutdownBarrier.prototype = {
+ get state() {
+ return PropertyBagConverter.fromValue(this._moduleBarrier.state);
+ },
+ get client() {
+ return this._client;
+ },
+ wait: function(onReady) {
+ this._moduleBarrier.wait().then(() => {
+ onReady.done();
+ });
+ // By specification, _moduleBarrier.wait() cannot reject.
+ },
+
+ /* ........ QueryInterface .............. */
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAsyncShutdownBarrier]),
+ classID: Components.ID("{29a0e8b5-9111-4c09-a0eb-76cd02bf20fa}"),
+};
+
+function nsAsyncShutdownService() {
+ // Cache for the getters
+
+ for (let _k of
+ [// Parent process
+ "profileBeforeChange",
+ "profileChangeTeardown",
+ "quitApplicationGranted",
+
+ // Child processes
+ "contentChildShutdown",
+
+ // All processes
+ "webWorkersShutdown",
+ "xpcomWillShutdown",
+ ]) {
+ let k = _k;
+ Object.defineProperty(this, k, {
+ configurable: true,
+ get: function() {
+ delete this[k];
+ let wrapped = AsyncShutdown[k]; // May be undefined, if we're on the wrong process.
+ let result = wrapped ? new nsAsyncShutdownClient(wrapped) : undefined;
+ Object.defineProperty(this, k, {
+ value: result
+ });
+ return result;
+ }
+ });
+ }
+
+ // Hooks for testing purpose
+ this.wrappedJSObject = {
+ _propertyBagConverter: PropertyBagConverter
+ };
+}
+nsAsyncShutdownService.prototype = {
+ makeBarrier: function(name) {
+ return new nsAsyncShutdownBarrier(new AsyncShutdown.Barrier(name));
+ },
+
+ /* ........ QueryInterface .............. */
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAsyncShutdownService]),
+ classID: Components.ID("{35c496de-a115-475d-93b5-ffa3f3ae6fe3}"),
+};
+
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
+ nsAsyncShutdownService,
+ nsAsyncShutdownBarrier,
+ nsAsyncShutdownClient,
+]);
diff --git a/components/asyncshutdown/nsAsyncShutdown.manifest b/components/asyncshutdown/nsAsyncShutdown.manifest
new file mode 100644
index 000000000..67f247902
--- /dev/null
+++ b/components/asyncshutdown/nsAsyncShutdown.manifest
@@ -0,0 +1,2 @@
+component {35c496de-a115-475d-93b5-ffa3f3ae6fe3} nsAsyncShutdown.js
+contract @mozilla.org/async-shutdown-service;1 {35c496de-a115-475d-93b5-ffa3f3ae6fe3}
diff --git a/components/asyncshutdown/nsIAsyncShutdown.idl b/components/asyncshutdown/nsIAsyncShutdown.idl
new file mode 100644
index 000000000..2f8ca5046
--- /dev/null
+++ b/components/asyncshutdown/nsIAsyncShutdown.idl
@@ -0,0 +1,215 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A mechanism for specifying shutdown dependencies between
+ * asynchronous services.
+ *
+ * Note that this XPCOM component is designed primarily for C++
+ * clients. JavaScript clients should rather use AsyncShutdown.jsm,
+ * which provides a better API and better error reporting for them.
+ */
+
+
+#include "nsISupports.idl"
+#include "nsIPropertyBag.idl"
+#include "nsIVariant.idl"
+
+interface nsIAsyncShutdownClient;
+
+/**
+ * A blocker installed by a client to be informed during some stage of
+ * shutdown and block shutdown asynchronously until some condition is
+ * complete.
+ *
+ * If you wish to use AsyncShutdown, you will need to implement this
+ * interface (and only this interface).
+ */
+[scriptable, uuid(4ef43f29-6715-4b57-a750-2ff83695ddce)]
+interface nsIAsyncShutdownBlocker: nsISupports {
+ /**
+ * The *unique* name of the blocker.
+ *
+ * By convention, it should respect the following format:
+ * "MyModuleName: Doing something while it's time"
+ * e.g.
+ * "OS.File: Flushing before profile-before-change"
+ *
+ * This attribute is uploaded as part of crash reports.
+ */
+ readonly attribute AString name;
+
+ /**
+ * Inform the blocker that the stage of shutdown has started.
+ * Shutdown will NOT proceed until `aBarrierClient.removeBlocker(this)`
+ * has been called.
+ */
+ void blockShutdown(in nsIAsyncShutdownClient aBarrierClient);
+
+ /**
+ * The current state of the blocker.
+ *
+ * In case of crash, this is converted to JSON and attached to
+ * the crash report.
+ *
+ * This field may be used to provide JSON-style data structures.
+ * For this purpose, use
+ * - nsIPropertyBag to represent objects;
+ * - nsIVariant to represent field values (which may hold nsIPropertyBag
+ * themselves).
+ */
+ readonly attribute nsIPropertyBag state;
+};
+
+/**
+ * A client for a nsIAsyncShutdownBarrier.
+ */
+[scriptable, uuid(d2031049-b990-43a2-95be-59f8a3ca5954)]
+interface nsIAsyncShutdownClient: nsISupports {
+ /**
+ * The name of the barrier.
+ */
+ readonly attribute AString name;
+
+ /**
+ * Add a blocker.
+ *
+ * After a `blocker` has been added with `addBlocker`, if it is not
+ * removed with `removeBlocker`, this will, by design, eventually
+ * CAUSE A CRASH.
+ *
+ * Calling `addBlocker` once nsIAsyncShutdownBarrier::wait() has been
+ * called on the owning barrier returns an error.
+ *
+ * @param aBlocker The blocker to add. Once
+ * nsIAsyncShutdownBarrier::wait() has been called, it will not
+ * call its `aOnReady` callback until all blockers have been
+ * removed, each by a call to `removeBlocker`.
+ * @param aFileName The filename of the callsite, as given by `__FILE__`.
+ * @param aLineNumber The linenumber of the callsite, as given by `__LINE__`.
+ * @param aStack Information on the stack that lead to this call. Generally
+ * empty when called from C++.
+ */
+ void addBlocker(in nsIAsyncShutdownBlocker aBlocker,
+ in AString aFileName,
+ in long aLineNumber,
+ in AString aStack);
+
+ /**
+ * Remove a blocker.
+ *
+ * @param aBlocker A blocker previously added to this client through
+ * `addBlocker`. Noop if the blocker has never been added or has been
+ * removed already.
+ */
+ void removeBlocker(in nsIAsyncShutdownBlocker aBlocker);
+
+ /**
+ * The JS implementation of the client.
+ *
+ * It is strongly recommended that JS clients of this API use
+ * `jsclient` instead of the `nsIAsyncShutdownClient`. See
+ * AsyncShutdown.jsm for more information on the JS version of
+ * this API.
+ */
+ readonly attribute jsval jsclient;
+};
+
+/**
+ * Callback invoked once all blockers of a barrier have been removed.
+ */
+[scriptable, function, uuid(910c9309-1da0-4dd0-8bdb-a325a38c604e)]
+interface nsIAsyncShutdownCompletionCallback: nsISupports {
+ /**
+ * The operation has been completed.
+ */
+ void done();
+};
+
+/**
+ * A stage of shutdown that supports blocker registration.
+ */
+[scriptable, uuid(50fa8a86-9c91-4256-8389-17d310adec90)]
+interface nsIAsyncShutdownBarrier: nsISupports {
+
+ /**
+ * The blocker registration capability. Most services may wish to
+ * publish this capability to let services that depend on it register
+ * blockers.
+ */
+ readonly attribute nsIAsyncShutdownClient client;
+
+ /**
+ * The state of all the blockers of the barrier.
+ *
+ * See the documentation of `nsIAsyncShutdownBlocker` for the
+ * format.
+ */
+ readonly attribute nsIPropertyBag state;
+
+ /**
+ * Wait for all blockers to complete.
+ *
+ * Method `aOnReady` will be called once all blockers have finished.
+ * The callback always receives NS_OK.
+ */
+ void wait(in nsIAsyncShutdownCompletionCallback aOnReady);
+};
+
+/**
+ * A service that allows registering shutdown-time dependencies.
+ */
+[scriptable, uuid(db365c78-c860-4e64-9a63-25b73f89a016)]
+interface nsIAsyncShutdownService: nsISupports {
+ /**
+ * Create a new barrier.
+ *
+ * By convention, the name should respect the following format:
+ * "MyModuleName: Doing something while it's time"
+ * e.g.
+ * "OS.File: Waiting for clients to flush before shutting down"
+ *
+ * This attribute is uploaded as part of crash reports.
+ */
+ nsIAsyncShutdownBarrier makeBarrier(in AString aName);
+
+
+ // Barriers for global shutdown stages in the parent process.
+
+ /**
+ * Barrier for notification profile-before-change.
+ */
+ readonly attribute nsIAsyncShutdownClient profileBeforeChange;
+
+ /**
+ * Barrier for notification profile-change-teardown.
+ */
+ readonly attribute nsIAsyncShutdownClient profileChangeTeardown;
+
+ /**
+ * Barrier for notification quit-application-granted.
+ */
+ readonly attribute nsIAsyncShutdownClient quitApplicationGranted;
+
+
+ // Barriers for global shutdown stages in all processes.
+
+ /**
+ * Barrier for notification web-workers-shutdown.
+ */
+ readonly attribute nsIAsyncShutdownClient webWorkersShutdown;
+
+ /**
+ * Barrier for notification xpcom-will-shutdown.
+ */
+ readonly attribute nsIAsyncShutdownClient xpcomWillShutdown;
+
+ // Don't add a barrier for content-child-shutdown because this
+ // makes it easier to cause shutdown hangs.
+
+};
+
+%{C++
+#define NS_ASYNCSHUTDOWNSERVICE_CONTRACTID "@mozilla.org/async-shutdown-service;1"
+%}
diff --git a/components/autocomplete/moz.build b/components/autocomplete/moz.build
new file mode 100644
index 000000000..7ea920144
--- /dev/null
+++ b/components/autocomplete/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'nsIAutoCompleteController.idl',
+ 'nsIAutoCompleteInput.idl',
+ 'nsIAutoCompletePopup.idl',
+ 'nsIAutoCompleteResult.idl',
+ 'nsIAutoCompleteSearch.idl',
+ 'nsIAutoCompleteSimpleResult.idl',
+]
+
+XPIDL_MODULE = 'autocomplete'
+
+UNIFIED_SOURCES += [
+ 'nsAutoCompleteController.cpp',
+ 'nsAutoCompleteSimpleResult.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/autocomplete/nsAutoCompleteController.cpp b/components/autocomplete/nsAutoCompleteController.cpp
new file mode 100644
index 000000000..37b2e5dfa
--- /dev/null
+++ b/components/autocomplete/nsAutoCompleteController.cpp
@@ -0,0 +1,2084 @@
+/* -*- 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 "nsAutoCompleteController.h"
+#include "nsAutoCompleteSimpleResult.h"
+
+#include "nsAutoPtr.h"
+#include "nsNetCID.h"
+#include "nsIIOService.h"
+#include "nsToolkitCompsCID.h"
+#include "nsIServiceManager.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsITreeBoxObject.h"
+#include "nsITreeColumns.h"
+#include "nsIObserverService.h"
+#include "nsIDOMKeyEvent.h"
+#include "mozilla/Services.h"
+#include "mozilla/ModuleUtils.h"
+
+static const char *kAutoCompleteSearchCID = "@mozilla.org/autocomplete/search;1?name=";
+
+namespace {
+
+void
+SetTextValue(nsIAutoCompleteInput* aInput,
+ const nsString& aValue,
+ uint16_t aReason) {
+ nsresult rv = aInput->SetTextValueWithReason(aValue, aReason);
+ if (NS_FAILED(rv)) {
+ aInput->SetTextValue(aValue);
+ }
+}
+
+} // anon namespace
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsAutoCompleteController)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAutoCompleteController)
+ tmp->SetInput(nullptr);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAutoCompleteController)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInput)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSearches)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResults)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAutoCompleteController)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAutoCompleteController)
+NS_INTERFACE_TABLE_HEAD(nsAutoCompleteController)
+ NS_INTERFACE_TABLE(nsAutoCompleteController, nsIAutoCompleteController,
+ nsIAutoCompleteObserver, nsITimerCallback, nsITreeView)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsAutoCompleteController)
+NS_INTERFACE_MAP_END
+
+nsAutoCompleteController::nsAutoCompleteController() :
+ mDefaultIndexCompleted(false),
+ mPopupClosedByCompositionStart(false),
+ mProhibitAutoFill(false),
+ mUserClearedAutoFill(false),
+ mClearingAutoFillSearchesAgain(false),
+ mCompositionState(eCompositionState_None),
+ mSearchStatus(nsAutoCompleteController::STATUS_NONE),
+ mRowCount(0),
+ mSearchesOngoing(0),
+ mSearchesFailed(0),
+ mFirstSearchResult(false),
+ mImmediateSearchesCount(0),
+ mCompletedSelectionIndex(-1)
+{
+}
+
+nsAutoCompleteController::~nsAutoCompleteController()
+{
+ SetInput(nullptr);
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIAutoCompleteController
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetSearchStatus(uint16_t *aSearchStatus)
+{
+ *aSearchStatus = mSearchStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetMatchCount(uint32_t *aMatchCount)
+{
+ *aMatchCount = mRowCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetInput(nsIAutoCompleteInput **aInput)
+{
+ *aInput = mInput;
+ NS_IF_ADDREF(*aInput);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::SetInitiallySelectedIndex(int32_t aSelectedIndex)
+{
+ // First forward to the popup.
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+ NS_ENSURE_STATE(input);
+ nsCOMPtr<nsIAutoCompletePopup> popup;
+ input->GetPopup(getter_AddRefs(popup));
+ NS_ENSURE_STATE(popup);
+ popup->SetSelectedIndex(aSelectedIndex);
+
+ // Now take care of internal stuff.
+ bool completeSelection;
+ if (NS_SUCCEEDED(input->GetCompleteSelectedIndex(&completeSelection)) &&
+ completeSelection) {
+ mCompletedSelectionIndex = aSelectedIndex;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::SetInput(nsIAutoCompleteInput *aInput)
+{
+ // Don't do anything if the input isn't changing.
+ if (mInput == aInput)
+ return NS_OK;
+
+ // Clear out the current search context
+ if (mInput) {
+ // Stop all searches in case they are async.
+ StopSearch();
+ ClearResults();
+ ClosePopup();
+ mSearches.Clear();
+ }
+
+ mInput = aInput;
+
+ // Nothing more to do if the input was just being set to null.
+ if (!aInput)
+ return NS_OK;
+
+ nsAutoString newValue;
+ aInput->GetTextValue(newValue);
+
+ // Clear out this reference in case the new input's popup has no tree
+ mTree = nullptr;
+
+ // Reset all search state members to default values
+ mSearchString = newValue;
+ mPlaceholderCompletionString.Truncate();
+ mDefaultIndexCompleted = false;
+ mProhibitAutoFill = false;
+ mSearchStatus = nsIAutoCompleteController::STATUS_NONE;
+ mRowCount = 0;
+ mSearchesOngoing = 0;
+ mCompletedSelectionIndex = -1;
+
+ // Initialize our list of search objects
+ uint32_t searchCount;
+ aInput->GetSearchCount(&searchCount);
+ mResults.SetCapacity(searchCount);
+ mSearches.SetCapacity(searchCount);
+ mImmediateSearchesCount = 0;
+
+ const char *searchCID = kAutoCompleteSearchCID;
+
+ // Since the controller can be used as a service it's important to reset this.
+ mClearingAutoFillSearchesAgain = false;
+
+ for (uint32_t i = 0; i < searchCount; ++i) {
+ // Use the search name to create the contract id string for the search service
+ nsAutoCString searchName;
+ aInput->GetSearchAt(i, searchName);
+ nsAutoCString cid(searchCID);
+ cid.Append(searchName);
+
+ // Use the created cid to get a pointer to the search service and store it for later
+ nsCOMPtr<nsIAutoCompleteSearch> search = do_GetService(cid.get());
+ if (search) {
+ mSearches.AppendObject(search);
+
+ // Count immediate searches.
+ nsCOMPtr<nsIAutoCompleteSearchDescriptor> searchDesc =
+ do_QueryInterface(search);
+ if (searchDesc) {
+ uint16_t searchType = nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED;
+ if (NS_SUCCEEDED(searchDesc->GetSearchType(&searchType)) &&
+ searchType == nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_IMMEDIATE) {
+ mImmediateSearchesCount++;
+ }
+
+ if (!mClearingAutoFillSearchesAgain) {
+ searchDesc->GetClearingAutoFillSearchesAgain(&mClearingAutoFillSearchesAgain);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::StartSearch(const nsAString &aSearchString)
+{
+ mSearchString = aSearchString;
+ StartSearches();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::HandleText(bool *_retval)
+{
+ *_retval = false;
+ // Note: the events occur in the following order when IME is used.
+ // 1. a compositionstart event(HandleStartComposition)
+ // 2. some input events (HandleText), eCompositionState_Composing
+ // 3. a compositionend event(HandleEndComposition)
+ // 4. an input event(HandleText), eCompositionState_Committing
+ // We should do nothing during composition.
+ if (mCompositionState == eCompositionState_Composing) {
+ return NS_OK;
+ }
+
+ bool handlingCompositionCommit =
+ (mCompositionState == eCompositionState_Committing);
+ bool popupClosedByCompositionStart = mPopupClosedByCompositionStart;
+ if (handlingCompositionCommit) {
+ mCompositionState = eCompositionState_None;
+ mPopupClosedByCompositionStart = false;
+ }
+
+ if (!mInput) {
+ // Stop all searches in case they are async.
+ StopSearch();
+ // Note: if now is after blur and IME end composition,
+ // check mInput before calling.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=193544#c31
+ NS_ERROR("Called before attaching to the control or after detaching from the control");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+ nsAutoString newValue;
+ input->GetTextValue(newValue);
+
+ // Stop all searches in case they are async.
+ StopSearch();
+
+ if (!mInput) {
+ // StopSearch() can call PostSearchCleanup() which might result
+ // in a blur event, which could null out mInput, so we need to check it
+ // again. See bug #395344 for more details
+ return NS_OK;
+ }
+
+ bool disabled;
+ input->GetDisableAutoComplete(&disabled);
+ NS_ENSURE_TRUE(!disabled, NS_OK);
+
+ // Usually we don't search again if the new string is the same as the last one.
+ // However, if this is called immediately after compositionend event,
+ // we need to search the same value again since the search was canceled
+ // at compositionstart event handler.
+ // The new string might also be the same as the last search if the autofilled
+ // portion was cleared. In this case, we may want to search again.
+
+ // Whether the user removed some text at the end.
+ bool userRemovedText =
+ newValue.Length() < mSearchString.Length() &&
+ Substring(mSearchString, 0, newValue.Length()).Equals(newValue);
+
+ // Whether the user is repeating the previous search.
+ bool repeatingPreviousSearch = !userRemovedText &&
+ newValue.Equals(mSearchString);
+
+ mUserClearedAutoFill =
+ repeatingPreviousSearch &&
+ newValue.Length() < mPlaceholderCompletionString.Length() &&
+ Substring(mPlaceholderCompletionString, 0, newValue.Length()).Equals(newValue);
+ bool searchAgainOnAutoFillClear = mUserClearedAutoFill && mClearingAutoFillSearchesAgain;
+
+ if (!handlingCompositionCommit &&
+ !searchAgainOnAutoFillClear &&
+ newValue.Length() > 0 &&
+ repeatingPreviousSearch) {
+ return NS_OK;
+ }
+
+ if (userRemovedText || searchAgainOnAutoFillClear) {
+ if (userRemovedText) {
+ // We need to throw away previous results so we don't try to search
+ // through them again.
+ ClearResults();
+ }
+ mProhibitAutoFill = true;
+ mPlaceholderCompletionString.Truncate();
+ } else {
+ mProhibitAutoFill = false;
+ }
+
+ mSearchString = newValue;
+
+ // Don't search if the value is empty
+ if (newValue.Length() == 0) {
+ // If autocomplete popup was closed by compositionstart event handler,
+ // we should reopen it forcibly even if the value is empty.
+ if (popupClosedByCompositionStart && handlingCompositionCommit) {
+ bool cancel;
+ HandleKeyNavigation(nsIDOMKeyEvent::DOM_VK_DOWN, &cancel);
+ return NS_OK;
+ }
+ ClosePopup();
+ return NS_OK;
+ }
+
+ *_retval = true;
+ StartSearches();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::HandleEnter(bool aIsPopupSelection,
+ nsIDOMEvent *aEvent,
+ bool *_retval)
+{
+ *_retval = false;
+ if (!mInput)
+ return NS_OK;
+
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+
+ // allow the event through unless there is something selected in the popup
+ input->GetPopupOpen(_retval);
+ if (*_retval) {
+ nsCOMPtr<nsIAutoCompletePopup> popup;
+ input->GetPopup(getter_AddRefs(popup));
+
+ if (popup) {
+ int32_t selectedIndex;
+ popup->GetSelectedIndex(&selectedIndex);
+ *_retval = selectedIndex >= 0;
+ }
+ }
+
+ // Stop the search, and handle the enter.
+ StopSearch();
+ EnterMatch(aIsPopupSelection, aEvent);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::HandleEscape(bool *_retval)
+{
+ *_retval = false;
+ if (!mInput)
+ return NS_OK;
+
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+
+ // allow the event through if the popup is closed
+ input->GetPopupOpen(_retval);
+
+ // Stop all searches in case they are async.
+ StopSearch();
+ ClearResults();
+ RevertTextValue();
+ ClosePopup();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::HandleStartComposition()
+{
+ NS_ENSURE_TRUE(mCompositionState != eCompositionState_Composing, NS_OK);
+
+ mPopupClosedByCompositionStart = false;
+ mCompositionState = eCompositionState_Composing;
+
+ if (!mInput)
+ return NS_OK;
+
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+ bool disabled;
+ input->GetDisableAutoComplete(&disabled);
+ if (disabled)
+ return NS_OK;
+
+ // Stop all searches in case they are async.
+ StopSearch();
+
+ bool isOpen = false;
+ input->GetPopupOpen(&isOpen);
+ if (isOpen) {
+ ClosePopup();
+
+ bool stillOpen = false;
+ input->GetPopupOpen(&stillOpen);
+ mPopupClosedByCompositionStart = !stillOpen;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::HandleEndComposition()
+{
+ NS_ENSURE_TRUE(mCompositionState == eCompositionState_Composing, NS_OK);
+
+ // We can't yet retrieve the committed value from the editor, since it isn't
+ // completely committed yet. Set mCompositionState to
+ // eCompositionState_Committing, so that when HandleText() is called (in
+ // response to the "input" event), we know that we should handle the
+ // committed text.
+ mCompositionState = eCompositionState_Committing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::HandleTab()
+{
+ bool cancel;
+ return HandleEnter(false, nullptr, &cancel);
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::HandleKeyNavigation(uint32_t aKey, bool *_retval)
+{
+ // By default, don't cancel the event
+ *_retval = false;
+
+ if (!mInput) {
+ // Stop all searches in case they are async.
+ StopSearch();
+ // Note: if now is after blur and IME end composition,
+ // check mInput before calling.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=193544#c31
+ NS_ERROR("Called before attaching to the control or after detaching from the control");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+ nsCOMPtr<nsIAutoCompletePopup> popup;
+ input->GetPopup(getter_AddRefs(popup));
+ NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
+
+ bool disabled;
+ input->GetDisableAutoComplete(&disabled);
+ NS_ENSURE_TRUE(!disabled, NS_OK);
+
+ if (aKey == nsIDOMKeyEvent::DOM_VK_UP ||
+ aKey == nsIDOMKeyEvent::DOM_VK_DOWN ||
+ aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ||
+ aKey == nsIDOMKeyEvent::DOM_VK_PAGE_DOWN)
+ {
+ // Prevent the input from handling up/down events, as it may move
+ // the cursor to home/end on some systems
+ *_retval = true;
+
+ bool isOpen = false;
+ input->GetPopupOpen(&isOpen);
+ if (isOpen) {
+ bool reverse = aKey == nsIDOMKeyEvent::DOM_VK_UP ||
+ aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ? true : false;
+ bool page = aKey == nsIDOMKeyEvent::DOM_VK_PAGE_UP ||
+ aKey == nsIDOMKeyEvent::DOM_VK_PAGE_DOWN ? true : false;
+
+ // Fill in the value of the textbox with whatever is selected in the popup
+ // if the completeSelectedIndex attribute is set. We check this before
+ // calling SelectBy of an earlier attempt to avoid crashing.
+ bool completeSelection;
+ input->GetCompleteSelectedIndex(&completeSelection);
+
+ // Instruct the result view to scroll by the given amount and direction
+ popup->SelectBy(reverse, page);
+
+ if (completeSelection)
+ {
+ int32_t selectedIndex;
+ popup->GetSelectedIndex(&selectedIndex);
+ if (selectedIndex >= 0) {
+ // A result is selected, so fill in its value
+ nsAutoString value;
+ if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, false, value))) {
+ // If the result is the previously autofilled string, then restore
+ // the search string and selection that existed when the result was
+ // autofilled. Else, fill the result and move the caret to the end.
+ int32_t start;
+ if (value.Equals(mPlaceholderCompletionString,
+ nsCaseInsensitiveStringComparator())) {
+ start = mSearchString.Length();
+ value = mPlaceholderCompletionString;
+ SetTextValue(input, value,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
+ } else {
+ start = value.Length();
+ SetTextValue(input, value,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETESELECTED);
+ }
+
+ input->SelectTextRange(start, value.Length());
+ }
+ mCompletedSelectionIndex = selectedIndex;
+ } else {
+ // Nothing is selected, so fill in the last typed value
+ SetTextValue(input, mSearchString,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_REVERT);
+ input->SelectTextRange(mSearchString.Length(), mSearchString.Length());
+ mCompletedSelectionIndex = -1;
+ }
+ }
+ } else {
+ if (*_retval) {
+ // Open the popup if there has been a previous search, or else kick off a new search
+ if (!mResults.IsEmpty()) {
+ if (mRowCount) {
+ OpenPopup();
+ }
+ } else {
+ // Stop all searches in case they are async.
+ StopSearch();
+
+ if (!mInput) {
+ // StopSearch() can call PostSearchCleanup() which might result
+ // in a blur event, which could null out mInput, so we need to check it
+ // again. See bug #395344 for more details
+ return NS_OK;
+ }
+
+ // Some script may have changed the value of the text field since our
+ // last keypress or after our focus handler and we don't want to search
+ // for a stale string.
+ nsAutoString value;
+ input->GetTextValue(value);
+ mSearchString = value;
+
+ StartSearches();
+ }
+ }
+ }
+ } else if ( aKey == nsIDOMKeyEvent::DOM_VK_LEFT
+ || aKey == nsIDOMKeyEvent::DOM_VK_RIGHT
+ || aKey == nsIDOMKeyEvent::DOM_VK_HOME
+ )
+ {
+ // The user hit a text-navigation key.
+ bool isOpen = false;
+ input->GetPopupOpen(&isOpen);
+
+ // If minresultsforpopup > 1 and there's less matches than the minimum
+ // required, the popup is not open, but the search suggestion is showing
+ // inline, so we should proceed as if we had the popup.
+ uint32_t minResultsForPopup;
+ input->GetMinResultsForPopup(&minResultsForPopup);
+ if (isOpen || (mRowCount > 0 && mRowCount < minResultsForPopup)) {
+ // For completeSelectedIndex autocomplete fields, if the popup shouldn't
+ // close when the caret is moved, don't adjust the text value or caret
+ // position.
+ if (isOpen) {
+ bool noRollup;
+ input->GetNoRollupOnCaretMove(&noRollup);
+ if (noRollup) {
+ bool completeSelection;
+ input->GetCompleteSelectedIndex(&completeSelection);
+ if (completeSelection) {
+ return NS_OK;
+ }
+ }
+ }
+
+ int32_t selectedIndex;
+ popup->GetSelectedIndex(&selectedIndex);
+ bool shouldComplete;
+ input->GetCompleteDefaultIndex(&shouldComplete);
+ if (selectedIndex >= 0) {
+ // The pop-up is open and has a selection, take its value
+ nsAutoString value;
+ if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, false, value))) {
+ SetTextValue(input, value,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETESELECTED);
+ input->SelectTextRange(value.Length(), value.Length());
+ }
+ }
+ else if (shouldComplete) {
+ // We usually try to preserve the casing of what user has typed, but
+ // if he wants to autocomplete, we will replace the value with the
+ // actual autocomplete result. Note that the autocomplete input can also
+ // be showing e.g. "bar >> foo bar" if the search matched "bar", a
+ // word not at the start of the full value "foo bar".
+ // The user wants explicitely to use that result, so this ensures
+ // association of the result with the autocompleted text.
+ nsAutoString value;
+ nsAutoString inputValue;
+ input->GetTextValue(inputValue);
+ if (NS_SUCCEEDED(GetDefaultCompleteValue(-1, false, value))) {
+ nsAutoString suggestedValue;
+ int32_t pos = inputValue.Find(" >> ");
+ if (pos > 0) {
+ inputValue.Right(suggestedValue, inputValue.Length() - pos - 4);
+ } else {
+ suggestedValue = inputValue;
+ }
+
+ if (value.Equals(suggestedValue, nsCaseInsensitiveStringComparator())) {
+ SetTextValue(input, value,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
+ input->SelectTextRange(value.Length(), value.Length());
+ }
+ }
+ }
+
+ // Close the pop-up even if nothing was selected
+ ClearSearchTimer();
+ ClosePopup();
+ }
+ // Update last-searched string to the current input, since the input may
+ // have changed. Without this, subsequent backspaces look like text
+ // additions, not text deletions.
+ nsAutoString value;
+ input->GetTextValue(value);
+ mSearchString = value;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::HandleDelete(bool *_retval)
+{
+ *_retval = false;
+ if (!mInput)
+ return NS_OK;
+
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+ bool isOpen = false;
+ input->GetPopupOpen(&isOpen);
+ if (!isOpen || mRowCount <= 0) {
+ // Nothing left to delete, proceed as normal
+ bool unused = false;
+ HandleText(&unused);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAutoCompletePopup> popup;
+ input->GetPopup(getter_AddRefs(popup));
+
+ int32_t index, searchIndex, rowIndex;
+ popup->GetSelectedIndex(&index);
+ if (index == -1) {
+ // No row is selected in the list
+ bool unused = false;
+ HandleText(&unused);
+ return NS_OK;
+ }
+
+ RowIndexToSearch(index, &searchIndex, &rowIndex);
+ NS_ENSURE_TRUE(searchIndex >= 0 && rowIndex >= 0, NS_ERROR_FAILURE);
+
+ nsIAutoCompleteResult *result = mResults.SafeObjectAt(searchIndex);
+ NS_ENSURE_TRUE(result, NS_ERROR_FAILURE);
+
+ nsAutoString search;
+ input->GetSearchParam(search);
+
+ // Clear the row in our result and in the DB.
+ result->RemoveValueAt(rowIndex, true);
+ --mRowCount;
+
+ // We removed it, so make sure we cancel the event that triggered this call.
+ *_retval = true;
+
+ // Unselect the current item.
+ popup->SetSelectedIndex(-1);
+
+ // Tell the tree that the row count changed.
+ if (mTree)
+ mTree->RowCountChanged(mRowCount, -1);
+
+ // Adjust index, if needed.
+ if (index >= (int32_t)mRowCount)
+ index = mRowCount - 1;
+
+ if (mRowCount > 0) {
+ // There are still rows in the popup, select the current index again.
+ popup->SetSelectedIndex(index);
+
+ // Complete to the new current value.
+ bool shouldComplete = false;
+ input->GetCompleteDefaultIndex(&shouldComplete);
+ if (shouldComplete) {
+ nsAutoString value;
+ if (NS_SUCCEEDED(GetResultValueAt(index, false, value))) {
+ CompleteValue(value);
+ }
+ }
+
+ // Invalidate the popup.
+ popup->Invalidate(nsIAutoCompletePopup::INVALIDATE_REASON_DELETE);
+ } else {
+ // Nothing left in the popup, clear any pending search timers and
+ // close the popup.
+ ClearSearchTimer();
+ uint32_t minResults;
+ input->GetMinResultsForPopup(&minResults);
+ if (minResults) {
+ ClosePopup();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::GetResultAt(int32_t aIndex, nsIAutoCompleteResult** aResult,
+ int32_t* aRowIndex)
+{
+ int32_t searchIndex;
+ RowIndexToSearch(aIndex, &searchIndex, aRowIndex);
+ NS_ENSURE_TRUE(searchIndex >= 0 && *aRowIndex >= 0, NS_ERROR_FAILURE);
+
+ *aResult = mResults.SafeObjectAt(searchIndex);
+ NS_ENSURE_TRUE(*aResult, NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetValueAt(int32_t aIndex, nsAString & _retval)
+{
+ GetResultLabelAt(aIndex, _retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetLabelAt(int32_t aIndex, nsAString & _retval)
+{
+ GetResultLabelAt(aIndex, _retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetCommentAt(int32_t aIndex, nsAString & _retval)
+{
+ int32_t rowIndex;
+ nsIAutoCompleteResult* result;
+ nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return result->GetCommentAt(rowIndex, _retval);
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetStyleAt(int32_t aIndex, nsAString & _retval)
+{
+ int32_t rowIndex;
+ nsIAutoCompleteResult* result;
+ nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return result->GetStyleAt(rowIndex, _retval);
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetImageAt(int32_t aIndex, nsAString & _retval)
+{
+ int32_t rowIndex;
+ nsIAutoCompleteResult* result;
+ nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return result->GetImageAt(rowIndex, _retval);
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetFinalCompleteValueAt(int32_t aIndex,
+ nsAString & _retval)
+{
+ int32_t rowIndex;
+ nsIAutoCompleteResult* result;
+ nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return result->GetFinalCompleteValueAt(rowIndex, _retval);
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::SetSearchString(const nsAString &aSearchString)
+{
+ mSearchString = aSearchString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetSearchString(nsAString &aSearchString)
+{
+ aSearchString = mSearchString;
+ return NS_OK;
+}
+
+void
+nsAutoCompleteController::HandleSearchResult(nsIAutoCompleteSearch *aSearch,
+ nsIAutoCompleteResult *aResult)
+{
+ // Look up the index of the search which is returning.
+ for (uint32_t i = 0; i < mSearches.Length(); ++i) {
+ if (mSearches[i] == aSearch) {
+ ProcessResult(i, aResult);
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////
+//// nsIAutoCompleteObserver
+
+NS_IMETHODIMP
+nsAutoCompleteController::OnUpdateSearchResult(nsIAutoCompleteSearch *aSearch, nsIAutoCompleteResult* aResult)
+{
+ MOZ_ASSERT(mSearches.Contains(aSearch));
+
+ ClearResults();
+ HandleSearchResult(aSearch, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::OnSearchResult(nsIAutoCompleteSearch *aSearch, nsIAutoCompleteResult* aResult)
+{
+ MOZ_ASSERT(mSearchesOngoing > 0 && mSearches.Contains(aSearch));
+
+ // If this is the first search result we are processing
+ // we should clear out the previously cached results.
+ if (mFirstSearchResult) {
+ ClearResults();
+ mFirstSearchResult = false;
+ }
+
+ uint16_t result = 0;
+ if (aResult) {
+ aResult->GetSearchResult(&result);
+ }
+
+ // If our results are incremental, the search is still ongoing.
+ if (result != nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING &&
+ result != nsIAutoCompleteResult::RESULT_NOMATCH_ONGOING) {
+ --mSearchesOngoing;
+ }
+
+ HandleSearchResult(aSearch, aResult);
+
+ if (mSearchesOngoing == 0) {
+ // If this is the last search to return, cleanup.
+ PostSearchCleanup();
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsITimerCallback
+
+NS_IMETHODIMP
+nsAutoCompleteController::Notify(nsITimer *timer)
+{
+ mTimer = nullptr;
+
+ if (mImmediateSearchesCount == 0) {
+ // If there were no immediate searches, BeforeSearches has not yet been
+ // called, so do it now.
+ nsresult rv = BeforeSearches();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED);
+ AfterSearches();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+// nsITreeView
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetRowCount(int32_t *aRowCount)
+{
+ *aRowCount = mRowCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetRowProperties(int32_t index, nsAString& aProps)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetCellProperties(int32_t row, nsITreeColumn* col,
+ nsAString& aProps)
+{
+ if (row >= 0) {
+ GetStyleAt(row, aProps);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetColumnProperties(nsITreeColumn* col, nsAString& aProps)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+
+ if (NS_LITERAL_STRING("treecolAutoCompleteValue").Equals(colID))
+ return GetImageAt(row, _retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval)
+{
+ NS_NOTREACHED("tree has no progress cells");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ NS_NOTREACHED("all of our cells are text");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval)
+{
+ const char16_t* colID;
+ col->GetIdConst(&colID);
+
+ if (NS_LITERAL_STRING("treecolAutoCompleteValue").Equals(colID))
+ GetValueAt(row, _retval);
+ else if (NS_LITERAL_STRING("treecolAutoCompleteComment").Equals(colID))
+ GetCommentAt(row, _retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::IsContainer(int32_t index, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::IsContainerOpen(int32_t index, bool *_retval)
+{
+ NS_NOTREACHED("no container cells");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::IsContainerEmpty(int32_t index, bool *_retval)
+{
+ NS_NOTREACHED("no container cells");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetLevel(int32_t index, int32_t *_retval)
+{
+ *_retval = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetParentIndex(int32_t rowIndex, int32_t *_retval)
+{
+ *_retval = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::ToggleOpenState(int32_t index)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::SetTree(nsITreeBoxObject *tree)
+{
+ mTree = tree;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::GetSelection(nsITreeSelection * *aSelection)
+{
+ *aSelection = mSelection;
+ NS_IF_ADDREF(*aSelection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoCompleteController::SetSelection(nsITreeSelection * aSelection)
+{
+ mSelection = aSelection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::SelectionChanged()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::CycleHeader(nsITreeColumn* col)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::CycleCell(int32_t row, nsITreeColumn* col)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::IsEditable(int32_t row, nsITreeColumn* col, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::IsSelectable(int32_t row, nsITreeColumn* col, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::IsSeparator(int32_t index, bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::IsSorted(bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::CanDrop(int32_t index, int32_t orientation,
+ nsIDOMDataTransfer* dataTransfer, bool *_retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::Drop(int32_t row, int32_t orientation, nsIDOMDataTransfer* dataTransfer)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::PerformAction(const char16_t *action)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::PerformActionOnRow(const char16_t *action, int32_t row)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::PerformActionOnCell(const char16_t* action, int32_t row, nsITreeColumn* col)
+{
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsAutoCompleteController
+
+nsresult
+nsAutoCompleteController::OpenPopup()
+{
+ uint32_t minResults;
+ mInput->GetMinResultsForPopup(&minResults);
+
+ if (mRowCount >= minResults) {
+ return mInput->SetPopupOpen(true);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::ClosePopup()
+{
+ if (!mInput) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+
+ bool isOpen = false;
+ input->GetPopupOpen(&isOpen);
+ if (!isOpen)
+ return NS_OK;
+
+ nsCOMPtr<nsIAutoCompletePopup> popup;
+ input->GetPopup(getter_AddRefs(popup));
+ NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
+ popup->SetSelectedIndex(-1);
+ return input->SetPopupOpen(false);
+}
+
+nsresult
+nsAutoCompleteController::BeforeSearches()
+{
+ NS_ENSURE_STATE(mInput);
+
+ mSearchStatus = nsIAutoCompleteController::STATUS_SEARCHING;
+ mDefaultIndexCompleted = false;
+
+ // The first search result will clear mResults array, though we should pass
+ // the previous result to each search to allow them to reuse it. So we
+ // temporarily cache current results till AfterSearches().
+ if (!mResultCache.AppendObjects(mResults)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mSearchesOngoing = mSearches.Length();
+ mSearchesFailed = 0;
+ mFirstSearchResult = true;
+
+ // notify the input that the search is beginning
+ mInput->OnSearchBegin();
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::StartSearch(uint16_t aSearchType)
+{
+ NS_ENSURE_STATE(mInput);
+ nsCOMPtr<nsIAutoCompleteInput> input = mInput;
+
+ // Iterate a copy of |mSearches| so that we don't run into trouble if the
+ // array is mutated while we're still in the loop. An nsIAutoCompleteSearch
+ // implementation could synchronously start a new search when StartSearch()
+ // is called and that would lead to assertions down the way.
+ nsCOMArray<nsIAutoCompleteSearch> searchesCopy(mSearches);
+ for (uint32_t i = 0; i < searchesCopy.Length(); ++i) {
+ nsCOMPtr<nsIAutoCompleteSearch> search = searchesCopy[i];
+
+ // Filter on search type. Not all the searches implement this interface,
+ // in such a case just consider them delayed.
+ uint16_t searchType = nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED;
+ nsCOMPtr<nsIAutoCompleteSearchDescriptor> searchDesc =
+ do_QueryInterface(search);
+ if (searchDesc)
+ searchDesc->GetSearchType(&searchType);
+ if (searchType != aSearchType)
+ continue;
+
+ nsIAutoCompleteResult *result = mResultCache.SafeObjectAt(i);
+
+ if (result) {
+ uint16_t searchResult;
+ result->GetSearchResult(&searchResult);
+ if (searchResult != nsIAutoCompleteResult::RESULT_SUCCESS &&
+ searchResult != nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING &&
+ searchResult != nsIAutoCompleteResult::RESULT_NOMATCH)
+ result = nullptr;
+ }
+
+ nsAutoString searchParam;
+ nsresult rv = input->GetSearchParam(searchParam);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // FormFill expects the searchParam to only contain the input element id,
+ // other consumers may have other expectations, so this modifies it only
+ // for new consumers handling autoFill by themselves.
+ if (mProhibitAutoFill && mClearingAutoFillSearchesAgain) {
+ searchParam.AppendLiteral(" prohibit-autofill");
+ }
+
+ uint32_t userContextId;
+ rv = input->GetUserContextId(&userContextId);
+ if (NS_SUCCEEDED(rv) &&
+ userContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
+ searchParam.AppendLiteral(" user-context-id:");
+ searchParam.AppendInt(userContextId, 10);
+ }
+
+ rv = search->StartSearch(mSearchString, searchParam, result, static_cast<nsIAutoCompleteObserver *>(this));
+ if (NS_FAILED(rv)) {
+ ++mSearchesFailed;
+ MOZ_ASSERT(mSearchesOngoing > 0);
+ --mSearchesOngoing;
+ }
+ // Because of the joy of nested event loops (which can easily happen when some
+ // code uses a generator for an asynchronous AutoComplete search),
+ // nsIAutoCompleteSearch::StartSearch might cause us to be detached from our input
+ // field. The next time we iterate, we'd be touching something that we shouldn't
+ // be, and result in a crash.
+ if (!mInput) {
+ // The search operation has been finished.
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsAutoCompleteController::AfterSearches()
+{
+ mResultCache.Clear();
+ if (mSearchesFailed == mSearches.Length())
+ PostSearchCleanup();
+}
+
+NS_IMETHODIMP
+nsAutoCompleteController::StopSearch()
+{
+ // Stop the timer if there is one
+ ClearSearchTimer();
+
+ // Stop any ongoing asynchronous searches
+ if (mSearchStatus == nsIAutoCompleteController::STATUS_SEARCHING) {
+ for (uint32_t i = 0; i < mSearches.Length(); ++i) {
+ nsCOMPtr<nsIAutoCompleteSearch> search = mSearches[i];
+ search->StopSearch();
+ }
+ mSearchesOngoing = 0;
+ // since we were searching, but now we've stopped,
+ // we need to call PostSearchCleanup()
+ PostSearchCleanup();
+ }
+ return NS_OK;
+}
+
+void
+nsAutoCompleteController::MaybeCompletePlaceholder()
+{
+ MOZ_ASSERT(mInput);
+
+ if (!mInput) { // or mInput depending on what you choose
+ MOZ_ASSERT_UNREACHABLE("Input should always be valid at this point");
+ return;
+ }
+
+ int32_t selectionStart;
+ mInput->GetSelectionStart(&selectionStart);
+ int32_t selectionEnd;
+ mInput->GetSelectionEnd(&selectionEnd);
+
+ // Check if the current input should be completed with the placeholder string
+ // from the last completion until the actual search results come back.
+ // The new input string needs to be compatible with the last completed string.
+ // E.g. if the new value is "fob", but the last completion was "foobar",
+ // then the last completion is incompatible.
+ // If the search string is the same as the last completion value, then don't
+ // complete the value again (this prevents completion to happen e.g. if the
+ // cursor is moved and StartSeaches() is invoked).
+ // In addition, the selection must be at the end of the current input to
+ // trigger the placeholder completion.
+ bool usePlaceholderCompletion =
+ !mUserClearedAutoFill &&
+ !mPlaceholderCompletionString.IsEmpty() &&
+ mPlaceholderCompletionString.Length() > mSearchString.Length() &&
+ selectionEnd == selectionStart &&
+ selectionEnd == (int32_t)mSearchString.Length() &&
+ StringBeginsWith(mPlaceholderCompletionString, mSearchString,
+ nsCaseInsensitiveStringComparator());
+
+ if (usePlaceholderCompletion) {
+ CompleteValue(mPlaceholderCompletionString);
+ } else {
+ mPlaceholderCompletionString.Truncate();
+ }
+}
+
+nsresult
+nsAutoCompleteController::StartSearches()
+{
+ // Don't create a new search timer if we're already waiting for one to fire.
+ // If we don't check for this, we won't be able to cancel the original timer
+ // and may crash when it fires (bug 236659).
+ if (mTimer || !mInput)
+ return NS_OK;
+
+ // Check if the current input should be completed with the placeholder string
+ // from the last completion until the actual search results come back.
+ MaybeCompletePlaceholder();
+
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+
+ // Get the timeout for delayed searches.
+ uint32_t timeout;
+ input->GetTimeout(&timeout);
+
+ uint32_t immediateSearchesCount = mImmediateSearchesCount;
+ if (timeout == 0) {
+ // All the searches should be executed immediately.
+ immediateSearchesCount = mSearches.Length();
+ }
+
+ if (immediateSearchesCount > 0) {
+ nsresult rv = BeforeSearches();
+ if (NS_FAILED(rv))
+ return rv;
+ StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_IMMEDIATE);
+
+ if (mSearches.Length() == immediateSearchesCount) {
+ // Either all searches are immediate, or the timeout is 0. In the
+ // latter case we still have to execute the delayed searches, otherwise
+ // this will be a no-op.
+ StartSearch(nsIAutoCompleteSearchDescriptor::SEARCH_TYPE_DELAYED);
+
+ // All the searches have been started, just finish.
+ AfterSearches();
+ return NS_OK;
+ }
+ }
+
+ MOZ_ASSERT(timeout > 0, "Trying to delay searches with a 0 timeout!");
+
+ // Now start the delayed searches.
+ nsresult rv;
+ mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = mTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv))
+ mTimer = nullptr;
+
+ return rv;
+}
+
+nsresult
+nsAutoCompleteController::ClearSearchTimer()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::EnterMatch(bool aIsPopupSelection,
+ nsIDOMEvent *aEvent)
+{
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+ nsCOMPtr<nsIAutoCompletePopup> popup;
+ input->GetPopup(getter_AddRefs(popup));
+ NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
+
+ bool forceComplete;
+ input->GetForceComplete(&forceComplete);
+
+ // Ask the popup if it wants to enter a special value into the textbox
+ nsAutoString value;
+ popup->GetOverrideValue(value);
+ if (value.IsEmpty()) {
+ bool shouldComplete;
+ input->GetCompleteDefaultIndex(&shouldComplete);
+ bool completeSelection;
+ input->GetCompleteSelectedIndex(&completeSelection);
+
+ int32_t selectedIndex;
+ popup->GetSelectedIndex(&selectedIndex);
+ if (selectedIndex >= 0) {
+ nsAutoString inputValue;
+ input->GetTextValue(inputValue);
+ if (aIsPopupSelection || !completeSelection) {
+ // We need to fill-in the value if:
+ // * completeselectedindex is false
+ // * A row in the popup was confirmed
+ //
+ // TODO: This is not totally correct, cause it will also confirm
+ // a result selected with a simple mouseover, that could also have
+ // happened accidentally, maybe touching a touchpad.
+ // The reason is that autocomplete.xml sets selectedIndex on mousemove
+ // making impossible, in the !completeSelection case, to distinguish if
+ // the user wanted to confirm autoFill or the popup entry.
+ // The solution may be to change autocomplete.xml to set selectedIndex
+ // only on popupClick, but that requires changing the selection behavior.
+ GetResultValueAt(selectedIndex, true, value);
+ } else if (mDefaultIndexCompleted &&
+ inputValue.Equals(mPlaceholderCompletionString,
+ nsCaseInsensitiveStringComparator())) {
+ // We also need to fill-in the value if the default index completion was
+ // confirmed, though we cannot use the selectedIndex cause the selection
+ // may have been changed by the mouse in the meanwhile.
+ GetFinalDefaultCompleteValue(value);
+ } else if (mCompletedSelectionIndex != -1) {
+ // If completeselectedindex is true, and EnterMatch was not invoked by
+ // mouse-clicking a match (for example the user pressed Enter),
+ // don't fill in the value as it will have already been filled in as
+ // needed, unless the selected match has a final complete value that
+ // differs from the user-facing value.
+ nsAutoString finalValue;
+ GetResultValueAt(mCompletedSelectionIndex, true, finalValue);
+ if (!inputValue.Equals(finalValue)) {
+ value = finalValue;
+ }
+ // Note that if the user opens the popup, mouses over entries without
+ // ever selecting one with the keyboard, and then hits enter, none of
+ // the above cases will be hit, since mouseover doesn't activate
+ // completeselectedindex and thus mCompletedSelectionIndex would be
+ // -1.
+ }
+ } else if (shouldComplete) {
+ // We usually try to preserve the casing of what user has typed, but
+ // if he wants to autocomplete, we will replace the value with the
+ // actual autocomplete result.
+ // The user wants explicitely to use that result, so this ensures
+ // association of the result with the autocompleted text.
+ nsAutoString defaultIndexValue;
+ if (NS_SUCCEEDED(GetFinalDefaultCompleteValue(defaultIndexValue)))
+ value = defaultIndexValue;
+ }
+
+ if (forceComplete && value.IsEmpty() && shouldComplete) {
+ // See if inputValue is one of the autocomplete results. It can be an
+ // identical value, or if it matched the middle of a result it can be
+ // something like "bar >> foobar" (user entered bar and foobar is
+ // the result value).
+ // If the current search matches one of the autocomplete results, we
+ // should use that result, and not overwrite it with the default value.
+ // It's indeed possible EnterMatch gets called a second time (for example
+ // by the blur handler) and it should not overwrite the current match.
+ nsAutoString inputValue;
+ input->GetTextValue(inputValue);
+ nsAutoString suggestedValue;
+ int32_t pos = inputValue.Find(" >> ");
+ if (pos > 0) {
+ inputValue.Right(suggestedValue, inputValue.Length() - pos - 4);
+ } else {
+ suggestedValue = inputValue;
+ }
+
+ for (uint32_t i = 0; i < mResults.Length(); ++i) {
+ nsIAutoCompleteResult *result = mResults[i];
+ if (result) {
+ uint32_t matchCount = 0;
+ result->GetMatchCount(&matchCount);
+ for (uint32_t j = 0; j < matchCount; ++j) {
+ nsAutoString matchValue;
+ result->GetValueAt(j, matchValue);
+ if (suggestedValue.Equals(matchValue, nsCaseInsensitiveStringComparator())) {
+ nsAutoString finalMatchValue;
+ result->GetFinalCompleteValueAt(j, finalMatchValue);
+ value = finalMatchValue;
+ break;
+ }
+ }
+ }
+ }
+ // The value should have been set at this point. If not, then it's not
+ // a value that should be autocompleted.
+ }
+ else if (forceComplete && value.IsEmpty() && completeSelection) {
+ // Since nothing was selected, and forceComplete is specified, that means
+ // we have to find the first default match and enter it instead.
+ for (uint32_t i = 0; i < mResults.Length(); ++i) {
+ nsIAutoCompleteResult *result = mResults[i];
+ if (result) {
+ int32_t defaultIndex;
+ result->GetDefaultIndex(&defaultIndex);
+ if (defaultIndex >= 0) {
+ result->GetFinalCompleteValueAt(defaultIndex, value);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(obsSvc);
+ obsSvc->NotifyObservers(input, "autocomplete-will-enter-text", nullptr);
+
+ if (!value.IsEmpty()) {
+ SetTextValue(input, value, nsIAutoCompleteInput::TEXTVALUE_REASON_ENTERMATCH);
+ input->SelectTextRange(value.Length(), value.Length());
+ mSearchString = value;
+ }
+
+ obsSvc->NotifyObservers(input, "autocomplete-did-enter-text", nullptr);
+ ClosePopup();
+
+ bool cancel;
+ input->OnTextEntered(aEvent, &cancel);
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::RevertTextValue()
+{
+ // StopSearch() can call PostSearchCleanup() which might result
+ // in a blur event, which could null out mInput, so we need to check it
+ // again. See bug #408463 for more details
+ if (!mInput)
+ return NS_OK;
+
+ nsAutoString oldValue(mSearchString);
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+
+ bool cancel = false;
+ input->OnTextReverted(&cancel);
+
+ if (!cancel) {
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(obsSvc);
+ obsSvc->NotifyObservers(input, "autocomplete-will-revert-text", nullptr);
+
+ nsAutoString inputValue;
+ input->GetTextValue(inputValue);
+ // Don't change the value if it is the same to prevent sending useless events.
+ // NOTE: how can |RevertTextValue| be called with inputValue != oldValue?
+ if (!oldValue.Equals(inputValue)) {
+ SetTextValue(input, oldValue, nsIAutoCompleteInput::TEXTVALUE_REASON_REVERT);
+ }
+
+ obsSvc->NotifyObservers(input, "autocomplete-did-revert-text", nullptr);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::ProcessResult(int32_t aSearchIndex, nsIAutoCompleteResult *aResult)
+{
+ NS_ENSURE_STATE(mInput);
+ MOZ_ASSERT(aResult, "ProcessResult should always receive a result");
+ NS_ENSURE_ARG(aResult);
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+
+ uint16_t searchResult = 0;
+ aResult->GetSearchResult(&searchResult);
+
+ // The following code supports incremental updating results in 2 ways:
+ // * The search may reuse the same result, just by adding entries to it.
+ // * The search may send a new result every time. In this case we merge
+ // the results and proceed on the same code path as before.
+ // This way both mSearches and mResults can be indexed by the search index,
+ // cause we'll always have only one result per search.
+ if (mResults.IndexOf(aResult) == -1) {
+ nsIAutoCompleteResult* oldResult = mResults.SafeObjectAt(aSearchIndex);
+ if (oldResult) {
+ MOZ_ASSERT(false, "Passing new matches to OnSearchResult with a new "
+ "nsIAutoCompleteResult every time is deprecated, please "
+ "update the same result until the search is done");
+ // Build a new nsIAutocompleteSimpleResult and merge results into it.
+ RefPtr<nsAutoCompleteSimpleResult> mergedResult =
+ new nsAutoCompleteSimpleResult();
+ mergedResult->AppendResult(oldResult);
+ mergedResult->AppendResult(aResult);
+ mResults.ReplaceObjectAt(mergedResult, aSearchIndex);
+ } else {
+ // This inserts and grows the array if needed.
+ mResults.ReplaceObjectAt(aResult, aSearchIndex);
+ }
+ }
+ // When found the result should have the same index as the search.
+ MOZ_ASSERT_IF(mResults.IndexOf(aResult) != -1,
+ mResults.IndexOf(aResult) == aSearchIndex);
+ MOZ_ASSERT(mResults.Count() >= aSearchIndex + 1,
+ "aSearchIndex should always be valid for mResults");
+
+ bool isTypeAheadResult = false;
+ aResult->GetTypeAheadResult(&isTypeAheadResult);
+
+ if (!isTypeAheadResult) {
+ uint32_t oldRowCount = mRowCount;
+ // If the search failed, increase the match count to include the error
+ // description.
+ if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
+ nsAutoString error;
+ aResult->GetErrorDescription(error);
+ if (!error.IsEmpty()) {
+ ++mRowCount;
+ if (mTree) {
+ mTree->RowCountChanged(oldRowCount, 1);
+ }
+ }
+ } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
+ searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
+ // Increase the match count for all matches in this result.
+ uint32_t totalMatchCount = 0;
+ for (uint32_t i = 0; i < mResults.Length(); i++) {
+ nsIAutoCompleteResult* result = mResults.SafeObjectAt(i);
+ if (result) {
+ // not all results implement this, so it can likely fail.
+ bool typeAhead = false;
+ result->GetTypeAheadResult(&typeAhead);
+ if (!typeAhead) {
+ uint32_t matchCount = 0;
+ result->GetMatchCount(&matchCount);
+ totalMatchCount += matchCount;
+ }
+ }
+ }
+ uint32_t delta = totalMatchCount - oldRowCount;
+
+ mRowCount += delta;
+ if (mTree) {
+ mTree->RowCountChanged(oldRowCount, delta);
+ }
+ }
+
+ // Try to autocomplete the default index for this search.
+ // Do this before invalidating so the binding knows about it.
+ CompleteDefaultIndex(aSearchIndex);
+
+ // Refresh the popup view to display the new search results
+ nsCOMPtr<nsIAutoCompletePopup> popup;
+ input->GetPopup(getter_AddRefs(popup));
+ NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
+ popup->Invalidate(nsIAutoCompletePopup::INVALIDATE_REASON_NEW_RESULT);
+
+ uint32_t minResults;
+ input->GetMinResultsForPopup(&minResults);
+
+ // Make sure the popup is open, if necessary, since we now have at least one
+ // search result ready to display. Don't force the popup closed if we might
+ // get results in the future to avoid unnecessarily canceling searches.
+ if (mRowCount || !minResults) {
+ OpenPopup();
+ } else if (mSearchesOngoing == 0) {
+ ClosePopup();
+ }
+ } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
+ searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
+ // Try to autocomplete the default index for this search.
+ CompleteDefaultIndex(aSearchIndex);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::PostSearchCleanup()
+{
+ NS_ENSURE_STATE(mInput);
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+
+ uint32_t minResults;
+ input->GetMinResultsForPopup(&minResults);
+
+ if (mRowCount || minResults == 0) {
+ OpenPopup();
+ if (mRowCount)
+ mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_MATCH;
+ else
+ mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_NO_MATCH;
+ } else {
+ mSearchStatus = nsIAutoCompleteController::STATUS_COMPLETE_NO_MATCH;
+ ClosePopup();
+ }
+
+ // notify the input that the search is complete
+ input->OnSearchComplete();
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::ClearResults()
+{
+ int32_t oldRowCount = mRowCount;
+ mRowCount = 0;
+ mResults.Clear();
+ if (oldRowCount != 0) {
+ if (mTree)
+ mTree->RowCountChanged(0, -oldRowCount);
+ else if (mInput) {
+ nsCOMPtr<nsIAutoCompletePopup> popup;
+ mInput->GetPopup(getter_AddRefs(popup));
+ NS_ENSURE_TRUE(popup != nullptr, NS_ERROR_FAILURE);
+ // if we had a tree, RowCountChanged() would have cleared the selection
+ // when the selected row was removed. But since we don't have a tree,
+ // we need to clear the selection manually.
+ popup->SetSelectedIndex(-1);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::CompleteDefaultIndex(int32_t aResultIndex)
+{
+ if (mDefaultIndexCompleted || mProhibitAutoFill || mSearchString.Length() == 0 || !mInput)
+ return NS_OK;
+
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+
+ int32_t selectionStart;
+ input->GetSelectionStart(&selectionStart);
+ int32_t selectionEnd;
+ input->GetSelectionEnd(&selectionEnd);
+
+ bool isPlaceholderSelected =
+ selectionEnd == (int32_t)mPlaceholderCompletionString.Length() &&
+ selectionStart == (int32_t)mSearchString.Length() &&
+ StringBeginsWith(mPlaceholderCompletionString,
+ mSearchString, nsCaseInsensitiveStringComparator());
+
+ // Don't try to automatically complete to the first result if there's already
+ // a selection or the cursor isn't at the end of the input. In case the
+ // selection is from the current placeholder completion value, then still
+ // automatically complete.
+ if (!isPlaceholderSelected && (selectionEnd != selectionStart ||
+ selectionEnd != (int32_t)mSearchString.Length()))
+ return NS_OK;
+
+ bool shouldComplete;
+ input->GetCompleteDefaultIndex(&shouldComplete);
+ if (!shouldComplete)
+ return NS_OK;
+
+ nsAutoString resultValue;
+ if (NS_SUCCEEDED(GetDefaultCompleteValue(aResultIndex, true, resultValue))) {
+ CompleteValue(resultValue);
+
+ mDefaultIndexCompleted = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::GetDefaultCompleteResult(int32_t aResultIndex,
+ nsIAutoCompleteResult** _result,
+ int32_t* _defaultIndex)
+{
+ *_defaultIndex = -1;
+ int32_t resultIndex = aResultIndex;
+
+ // If a result index was not provided, find the first defaultIndex result.
+ for (int32_t i = 0; resultIndex < 0 && i < mResults.Count(); ++i) {
+ nsIAutoCompleteResult *result = mResults.SafeObjectAt(i);
+ if (result &&
+ NS_SUCCEEDED(result->GetDefaultIndex(_defaultIndex)) &&
+ *_defaultIndex >= 0) {
+ resultIndex = i;
+ }
+ }
+ if (resultIndex < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *_result = mResults.SafeObjectAt(resultIndex);
+ NS_ENSURE_TRUE(*_result, NS_ERROR_FAILURE);
+
+ if (*_defaultIndex < 0) {
+ // The search must explicitly provide a default index in order
+ // for us to be able to complete.
+ (*_result)->GetDefaultIndex(_defaultIndex);
+ }
+
+ if (*_defaultIndex < 0) {
+ // We were given a result index, but that result doesn't want to
+ // be autocompleted.
+ return NS_ERROR_FAILURE;
+ }
+
+ // If the result wrongly notifies a RESULT_SUCCESS with no matches, or
+ // provides a defaultIndex greater than its matchCount, avoid trying to
+ // complete to an empty value.
+ uint32_t matchCount = 0;
+ (*_result)->GetMatchCount(&matchCount);
+ // Here defaultIndex is surely non-negative, so can be cast to unsigned.
+ if ((uint32_t)(*_defaultIndex) >= matchCount) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::GetDefaultCompleteValue(int32_t aResultIndex,
+ bool aPreserveCasing,
+ nsAString &_retval)
+{
+ nsIAutoCompleteResult *result;
+ int32_t defaultIndex = -1;
+ nsresult rv = GetDefaultCompleteResult(aResultIndex, &result, &defaultIndex);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString resultValue;
+ result->GetValueAt(defaultIndex, resultValue);
+ if (aPreserveCasing &&
+ StringBeginsWith(resultValue, mSearchString,
+ nsCaseInsensitiveStringComparator())) {
+ // We try to preserve user casing, otherwise we would end up changing
+ // the case of what he typed, if we have a result with a different casing.
+ // For example if we have result "Test", and user starts writing "tuna",
+ // after digiting t, we would convert it to T trying to autocomplete "Test".
+ // We will still complete to cased "Test" if the user explicitely choose
+ // that result, by either selecting it in the results popup, or with
+ // keyboard navigation or if autocompleting in the middle.
+ nsAutoString casedResultValue;
+ casedResultValue.Assign(mSearchString);
+ // Use what the user has typed so far.
+ casedResultValue.Append(Substring(resultValue,
+ mSearchString.Length(),
+ resultValue.Length()));
+ _retval = casedResultValue;
+ }
+ else
+ _retval = resultValue;
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::GetFinalDefaultCompleteValue(nsAString &_retval)
+{
+ MOZ_ASSERT(mInput, "Must have a valid input");
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+ nsIAutoCompleteResult *result;
+ int32_t defaultIndex = -1;
+ nsresult rv = GetDefaultCompleteResult(-1, &result, &defaultIndex);
+ if (NS_FAILED(rv)) return rv;
+
+ result->GetValueAt(defaultIndex, _retval);
+ nsAutoString inputValue;
+ input->GetTextValue(inputValue);
+ if (!_retval.Equals(inputValue, nsCaseInsensitiveStringComparator())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString finalCompleteValue;
+ rv = result->GetFinalCompleteValueAt(defaultIndex, finalCompleteValue);
+ if (NS_SUCCEEDED(rv)) {
+ _retval = finalCompleteValue;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::CompleteValue(nsString &aValue)
+/* mInput contains mSearchString, which we want to autocomplete to aValue. If
+ * selectDifference is true, select the remaining portion of aValue not
+ * contained in mSearchString. */
+{
+ MOZ_ASSERT(mInput, "Must have a valid input");
+
+ nsCOMPtr<nsIAutoCompleteInput> input(mInput);
+ const int32_t mSearchStringLength = mSearchString.Length();
+ int32_t endSelect = aValue.Length(); // By default, select all of aValue.
+
+ if (aValue.IsEmpty() ||
+ StringBeginsWith(aValue, mSearchString,
+ nsCaseInsensitiveStringComparator())) {
+ // aValue is empty (we were asked to clear mInput), or mSearchString
+ // matches the beginning of aValue. In either case we can simply
+ // autocomplete to aValue.
+ mPlaceholderCompletionString = aValue;
+ SetTextValue(input, aValue,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
+ } else {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString scheme;
+ if (NS_SUCCEEDED(ios->ExtractScheme(NS_ConvertUTF16toUTF8(aValue), scheme))) {
+ // Trying to autocomplete a URI from somewhere other than the beginning.
+ // Only succeed if the missing portion is "http://"; otherwise do not
+ // autocomplete. This prevents us from "helpfully" autocompleting to a
+ // URI that isn't equivalent to what the user expected.
+ const int32_t findIndex = 7; // length of "http://"
+
+ if ((endSelect < findIndex + mSearchStringLength) ||
+ !scheme.LowerCaseEqualsLiteral("http") ||
+ !Substring(aValue, findIndex, mSearchStringLength).Equals(
+ mSearchString, nsCaseInsensitiveStringComparator())) {
+ return NS_OK;
+ }
+
+ mPlaceholderCompletionString = mSearchString +
+ Substring(aValue, mSearchStringLength + findIndex, endSelect);
+ SetTextValue(input, mPlaceholderCompletionString,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
+
+ endSelect -= findIndex; // We're skipping this many characters of aValue.
+ } else {
+ // Autocompleting something other than a URI from the middle.
+ // Use the format "searchstring >> full string" to indicate to the user
+ // what we are going to replace their search string with.
+ SetTextValue(input, mSearchString + NS_LITERAL_STRING(" >> ") + aValue,
+ nsIAutoCompleteInput::TEXTVALUE_REASON_COMPLETEDEFAULT);
+
+ endSelect = mSearchString.Length() + 4 + aValue.Length();
+
+ // Reset the last search completion.
+ mPlaceholderCompletionString.Truncate();
+ }
+ }
+
+ input->SelectTextRange(mSearchStringLength, endSelect);
+
+ return NS_OK;
+}
+
+nsresult
+nsAutoCompleteController::GetResultLabelAt(int32_t aIndex, nsAString & _retval)
+{
+ return GetResultValueLabelAt(aIndex, false, false, _retval);
+}
+
+nsresult
+nsAutoCompleteController::GetResultValueAt(int32_t aIndex, bool aGetFinalValue,
+ nsAString & _retval)
+{
+ return GetResultValueLabelAt(aIndex, aGetFinalValue, true, _retval);
+}
+
+nsresult
+nsAutoCompleteController::GetResultValueLabelAt(int32_t aIndex,
+ bool aGetFinalValue,
+ bool aGetValue,
+ nsAString & _retval)
+{
+ NS_ENSURE_TRUE(aIndex >= 0 && (uint32_t) aIndex < mRowCount, NS_ERROR_ILLEGAL_VALUE);
+
+ int32_t rowIndex;
+ nsIAutoCompleteResult *result;
+ nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint16_t searchResult;
+ result->GetSearchResult(&searchResult);
+
+ if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
+ if (aGetValue)
+ return NS_ERROR_FAILURE;
+ result->GetErrorDescription(_retval);
+ } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
+ searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
+ if (aGetFinalValue) {
+ // Some implementations may miss finalCompleteValue, try to be backwards
+ // compatible.
+ if (NS_FAILED(result->GetFinalCompleteValueAt(rowIndex, _retval))) {
+ result->GetValueAt(rowIndex, _retval);
+ }
+ } else if (aGetValue) {
+ result->GetValueAt(rowIndex, _retval);
+ } else {
+ result->GetLabelAt(rowIndex, _retval);
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Given the index of a row in the autocomplete popup, find the
+ * corresponding nsIAutoCompleteSearch index, and sub-index into
+ * the search's results list.
+ */
+nsresult
+nsAutoCompleteController::RowIndexToSearch(int32_t aRowIndex, int32_t *aSearchIndex, int32_t *aItemIndex)
+{
+ *aSearchIndex = -1;
+ *aItemIndex = -1;
+
+ uint32_t index = 0;
+
+ // Move index through the results of each registered nsIAutoCompleteSearch
+ // until we find the given row
+ for (uint32_t i = 0; i < mSearches.Length(); ++i) {
+ nsIAutoCompleteResult *result = mResults.SafeObjectAt(i);
+ if (!result)
+ continue;
+
+ uint32_t rowCount = 0;
+
+ // Skip past the result completely if it is marked as hidden
+ bool isTypeAheadResult = false;
+ result->GetTypeAheadResult(&isTypeAheadResult);
+
+ if (!isTypeAheadResult) {
+ uint16_t searchResult;
+ result->GetSearchResult(&searchResult);
+
+ // Find out how many results were provided by the
+ // current nsIAutoCompleteSearch.
+ if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
+ searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
+ result->GetMatchCount(&rowCount);
+ }
+ }
+
+ // If the given row index is within the results range
+ // of the current nsIAutoCompleteSearch then return the
+ // search index and sub-index into the results array
+ if ((rowCount != 0) && (index + rowCount-1 >= (uint32_t) aRowIndex)) {
+ *aSearchIndex = i;
+ *aItemIndex = aRowIndex - index;
+ return NS_OK;
+ }
+
+ // Advance the popup table index cursor past the
+ // results of the current search.
+ index += rowCount;
+ }
+
+ return NS_OK;
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAutoCompleteController)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAutoCompleteSimpleResult)
+
+NS_DEFINE_NAMED_CID(NS_AUTOCOMPLETECONTROLLER_CID);
+NS_DEFINE_NAMED_CID(NS_AUTOCOMPLETESIMPLERESULT_CID);
+
+static const mozilla::Module::CIDEntry kAutoCompleteCIDs[] = {
+ { &kNS_AUTOCOMPLETECONTROLLER_CID, false, nullptr, nsAutoCompleteControllerConstructor },
+ { &kNS_AUTOCOMPLETESIMPLERESULT_CID, false, nullptr, nsAutoCompleteSimpleResultConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kAutoCompleteContracts[] = {
+ { NS_AUTOCOMPLETECONTROLLER_CONTRACTID, &kNS_AUTOCOMPLETECONTROLLER_CID },
+ { NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID, &kNS_AUTOCOMPLETESIMPLERESULT_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kAutoCompleteModule = {
+ mozilla::Module::kVersion,
+ kAutoCompleteCIDs,
+ kAutoCompleteContracts
+};
+
+NSMODULE_DEFN(tkAutoCompleteModule) = &kAutoCompleteModule;
diff --git a/components/autocomplete/nsAutoCompleteController.h b/components/autocomplete/nsAutoCompleteController.h
new file mode 100644
index 000000000..62aa980f6
--- /dev/null
+++ b/components/autocomplete/nsAutoCompleteController.h
@@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 __nsAutoCompleteController__
+#define __nsAutoCompleteController__
+
+#include "nsIAutoCompleteController.h"
+
+#include "nsCOMPtr.h"
+#include "nsIAutoCompleteInput.h"
+#include "nsIAutoCompletePopup.h"
+#include "nsIAutoCompleteResult.h"
+#include "nsIAutoCompleteSearch.h"
+#include "nsString.h"
+#include "nsITreeView.h"
+#include "nsITreeSelection.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsAutoCompleteController final : public nsIAutoCompleteController,
+ public nsIAutoCompleteObserver,
+ public nsITimerCallback,
+ public nsITreeView
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsAutoCompleteController,
+ nsIAutoCompleteController)
+ NS_DECL_NSIAUTOCOMPLETECONTROLLER
+ NS_DECL_NSIAUTOCOMPLETEOBSERVER
+ NS_DECL_NSITREEVIEW
+ NS_DECL_NSITIMERCALLBACK
+
+ nsAutoCompleteController();
+
+protected:
+ virtual ~nsAutoCompleteController();
+
+ nsresult OpenPopup();
+ nsresult ClosePopup();
+
+ nsresult StartSearch(uint16_t aSearchType);
+
+ nsresult BeforeSearches();
+ nsresult StartSearches();
+ void AfterSearches();
+ nsresult ClearSearchTimer();
+ void MaybeCompletePlaceholder();
+
+ void HandleSearchResult(nsIAutoCompleteSearch *aSearch,
+ nsIAutoCompleteResult *aResult);
+ nsresult ProcessResult(int32_t aSearchIndex, nsIAutoCompleteResult *aResult);
+ nsresult PostSearchCleanup();
+
+ nsresult EnterMatch(bool aIsPopupSelection,
+ nsIDOMEvent *aEvent);
+ nsresult RevertTextValue();
+
+ nsresult CompleteDefaultIndex(int32_t aResultIndex);
+ nsresult CompleteValue(nsString &aValue);
+
+ nsresult GetResultAt(int32_t aIndex, nsIAutoCompleteResult** aResult,
+ int32_t* aRowIndex);
+ nsresult GetResultValueAt(int32_t aIndex, bool aGetFinalValue,
+ nsAString & _retval);
+ nsresult GetResultLabelAt(int32_t aIndex, nsAString & _retval);
+private:
+ nsresult GetResultValueLabelAt(int32_t aIndex, bool aGetFinalValue,
+ bool aGetValue, nsAString & _retval);
+protected:
+
+ /**
+ * Gets and validates the defaultComplete result and the relative
+ * defaultIndex value.
+ *
+ * @param aResultIndex
+ * Index of the defaultComplete result to be used. Pass -1 to search
+ * for the first result providing a valid defaultIndex.
+ * @param _result
+ * The found result.
+ * @param _defaultIndex
+ * The defaultIndex relative to _result.
+ */
+ nsresult GetDefaultCompleteResult(int32_t aResultIndex,
+ nsIAutoCompleteResult** _result,
+ int32_t* _defaultIndex);
+
+ /**
+ * Gets the defaultComplete value to be suggested to the user.
+ *
+ * @param aResultIndex
+ * Index of the defaultComplete result to be used.
+ * @param aPreserveCasing
+ * Whether user casing should be preserved.
+ * @param _retval
+ * The value to be completed.
+ */
+ nsresult GetDefaultCompleteValue(int32_t aResultIndex, bool aPreserveCasing,
+ nsAString &_retval);
+
+ /**
+ * Gets the defaultComplete value to be used when the user confirms the
+ * current match.
+ * The value is returned only if it case-insensitively matches the current
+ * input text, otherwise the method returns NS_ERROR_FAILURE.
+ * This happens because we don't want to replace text if the user backspaces
+ * just before Enter.
+ *
+ * @param _retval
+ * The value to be completed.
+ */
+ nsresult GetFinalDefaultCompleteValue(nsAString &_retval);
+
+ nsresult ClearResults();
+
+ nsresult RowIndexToSearch(int32_t aRowIndex,
+ int32_t *aSearchIndex, int32_t *aItemIndex);
+
+ // members //////////////////////////////////////////
+
+ nsCOMPtr<nsIAutoCompleteInput> mInput;
+
+ nsCOMArray<nsIAutoCompleteSearch> mSearches;
+ // This is used as a sparse array, always use SafeObjectAt to access it.
+ nsCOMArray<nsIAutoCompleteResult> mResults;
+ // Temporarily keeps the results alive while invoking startSearch() for each
+ // search. This is needed to allow the searches to reuse the previous result,
+ // since otherwise the first search clears mResults.
+ nsCOMArray<nsIAutoCompleteResult> mResultCache;
+
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsITreeSelection> mSelection;
+ nsCOMPtr<nsITreeBoxObject> mTree;
+
+ nsString mSearchString;
+ nsString mPlaceholderCompletionString;
+ bool mDefaultIndexCompleted;
+ bool mPopupClosedByCompositionStart;
+
+ // Whether autofill is allowed for the next search. May be retrieved by the
+ // search through the "prohibit-autofill" searchParam.
+ bool mProhibitAutoFill;
+
+ // Indicates whether the user cleared the autofilled part, returning to the
+ // originally entered search string.
+ bool mUserClearedAutoFill;
+
+ // Indicates whether clearing the autofilled string should issue a new search.
+ bool mClearingAutoFillSearchesAgain;
+
+ enum CompositionState {
+ eCompositionState_None,
+ eCompositionState_Composing,
+ eCompositionState_Committing
+ };
+ CompositionState mCompositionState;
+ uint16_t mSearchStatus;
+ uint32_t mRowCount;
+ uint32_t mSearchesOngoing;
+ uint32_t mSearchesFailed;
+ bool mFirstSearchResult;
+ uint32_t mImmediateSearchesCount;
+ // The index of the match on the popup that was selected using the keyboard,
+ // if the completeselectedindex attribute is set.
+ // This is used to distinguish that selection (which would have been put in
+ // the input on being selected) from a moused-over selectedIndex value. This
+ // distinction is used to prevent mouse moves from inadvertently changing
+ // what happens once the user hits Enter on the keyboard.
+ // See bug 1043584 for more details.
+ int32_t mCompletedSelectionIndex;
+};
+
+#endif /* __nsAutoCompleteController__ */
diff --git a/components/autocomplete/nsAutoCompleteSimpleResult.cpp b/components/autocomplete/nsAutoCompleteSimpleResult.cpp
new file mode 100644
index 000000000..683ac462a
--- /dev/null
+++ b/components/autocomplete/nsAutoCompleteSimpleResult.cpp
@@ -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/. */
+
+#include "nsAutoCompleteSimpleResult.h"
+
+#define CHECK_MATCH_INDEX(_index, _insert) \
+ if (_index < 0 || \
+ static_cast<MatchesArray::size_type>(_index) > mMatches.Length() || \
+ (!_insert && static_cast<MatchesArray::size_type>(_index) == mMatches.Length())) { \
+ MOZ_ASSERT(false, "Trying to use an invalid index on mMatches"); \
+ return NS_ERROR_ILLEGAL_VALUE; \
+ } \
+
+NS_IMPL_ISUPPORTS(nsAutoCompleteSimpleResult,
+ nsIAutoCompleteResult,
+ nsIAutoCompleteSimpleResult)
+
+struct AutoCompleteSimpleResultMatch
+{
+ AutoCompleteSimpleResultMatch(const nsAString& aValue,
+ const nsAString& aComment,
+ const nsAString& aImage,
+ const nsAString& aStyle,
+ const nsAString& aFinalCompleteValue,
+ const nsAString& aLabel)
+ : mValue(aValue)
+ , mComment(aComment)
+ , mImage(aImage)
+ , mStyle(aStyle)
+ , mFinalCompleteValue(aFinalCompleteValue)
+ , mLabel(aLabel)
+ {
+ }
+
+ nsString mValue;
+ nsString mComment;
+ nsString mImage;
+ nsString mStyle;
+ nsString mFinalCompleteValue;
+ nsString mLabel;
+};
+
+nsAutoCompleteSimpleResult::nsAutoCompleteSimpleResult() :
+ mDefaultIndex(-1),
+ mSearchResult(RESULT_NOMATCH),
+ mTypeAheadResult(false)
+{
+}
+
+nsresult
+nsAutoCompleteSimpleResult::AppendResult(nsIAutoCompleteResult* aResult)
+{
+ nsAutoString searchString;
+ nsresult rv = aResult->GetSearchString(searchString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSearchString = searchString;
+
+ uint16_t searchResult;
+ rv = aResult->GetSearchResult(&searchResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSearchResult = searchResult;
+
+ nsAutoString errorDescription;
+ if (NS_SUCCEEDED(aResult->GetErrorDescription(errorDescription)) &&
+ !errorDescription.IsEmpty()) {
+ mErrorDescription = errorDescription;
+ }
+
+ bool typeAheadResult = false;
+ if (NS_SUCCEEDED(aResult->GetTypeAheadResult(&typeAheadResult)) &&
+ typeAheadResult) {
+ mTypeAheadResult = typeAheadResult;
+ }
+
+ int32_t defaultIndex = -1;
+ if (NS_SUCCEEDED(aResult->GetDefaultIndex(&defaultIndex)) &&
+ defaultIndex >= 0) {
+ mDefaultIndex = defaultIndex;
+ }
+
+ nsCOMPtr<nsIAutoCompleteSimpleResult> simpleResult =
+ do_QueryInterface(aResult);
+ if (simpleResult) {
+ nsCOMPtr<nsIAutoCompleteSimpleResultListener> listener;
+ if (NS_SUCCEEDED(simpleResult->GetListener(getter_AddRefs(listener))) &&
+ listener) {
+ listener.swap(mListener);
+ }
+ }
+
+ // Copy matches.
+ uint32_t matchCount = 0;
+ rv = aResult->GetMatchCount(&matchCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (size_t i = 0; i < matchCount; ++i) {
+ nsAutoString value, comment, image, style, finalCompleteValue, label;
+
+ rv = aResult->GetValueAt(i, value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aResult->GetCommentAt(i, comment);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aResult->GetImageAt(i, image);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aResult->GetStyleAt(i, style);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aResult->GetFinalCompleteValueAt(i, finalCompleteValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aResult->GetLabelAt(i, label);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AppendMatch(value, comment, image, style, finalCompleteValue, label);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+// searchString
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetSearchString(nsAString &aSearchString)
+{
+ aSearchString = mSearchString;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::SetSearchString(const nsAString &aSearchString)
+{
+ mSearchString.Assign(aSearchString);
+ return NS_OK;
+}
+
+// searchResult
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetSearchResult(uint16_t *aSearchResult)
+{
+ *aSearchResult = mSearchResult;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::SetSearchResult(uint16_t aSearchResult)
+{
+ mSearchResult = aSearchResult;
+ return NS_OK;
+}
+
+// defaultIndex
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetDefaultIndex(int32_t *aDefaultIndex)
+{
+ *aDefaultIndex = mDefaultIndex;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::SetDefaultIndex(int32_t aDefaultIndex)
+{
+ mDefaultIndex = aDefaultIndex;
+ return NS_OK;
+}
+
+// errorDescription
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetErrorDescription(nsAString & aErrorDescription)
+{
+ aErrorDescription = mErrorDescription;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::SetErrorDescription(
+ const nsAString &aErrorDescription)
+{
+ mErrorDescription.Assign(aErrorDescription);
+ return NS_OK;
+}
+
+// typeAheadResult
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetTypeAheadResult(bool *aTypeAheadResult)
+{
+ *aTypeAheadResult = mTypeAheadResult;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::SetTypeAheadResult(bool aTypeAheadResult)
+{
+ mTypeAheadResult = aTypeAheadResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::InsertMatchAt(int32_t aIndex,
+ const nsAString& aValue,
+ const nsAString& aComment,
+ const nsAString& aImage,
+ const nsAString& aStyle,
+ const nsAString& aFinalCompleteValue,
+ const nsAString& aLabel)
+{
+ CHECK_MATCH_INDEX(aIndex, true);
+
+ AutoCompleteSimpleResultMatch match(aValue, aComment, aImage, aStyle, aFinalCompleteValue, aLabel);
+
+ if (!mMatches.InsertElementAt(aIndex, match)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::AppendMatch(const nsAString& aValue,
+ const nsAString& aComment,
+ const nsAString& aImage,
+ const nsAString& aStyle,
+ const nsAString& aFinalCompleteValue,
+ const nsAString& aLabel)
+{
+ return InsertMatchAt(mMatches.Length(), aValue, aComment, aImage, aStyle,
+ aFinalCompleteValue, aLabel);
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetMatchCount(uint32_t *aMatchCount)
+{
+ *aMatchCount = mMatches.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetValueAt(int32_t aIndex, nsAString& _retval)
+{
+ CHECK_MATCH_INDEX(aIndex, false);
+ _retval = mMatches[aIndex].mValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetLabelAt(int32_t aIndex, nsAString& _retval)
+{
+ CHECK_MATCH_INDEX(aIndex, false);
+ _retval = mMatches[aIndex].mLabel;
+ if (_retval.IsEmpty()) {
+ _retval = mMatches[aIndex].mValue;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetCommentAt(int32_t aIndex, nsAString& _retval)
+{
+ CHECK_MATCH_INDEX(aIndex, false);
+ _retval = mMatches[aIndex].mComment;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetImageAt(int32_t aIndex, nsAString& _retval)
+{
+ CHECK_MATCH_INDEX(aIndex, false);
+ _retval = mMatches[aIndex].mImage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetStyleAt(int32_t aIndex, nsAString& _retval)
+{
+ CHECK_MATCH_INDEX(aIndex, false);
+ _retval = mMatches[aIndex].mStyle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetFinalCompleteValueAt(int32_t aIndex,
+ nsAString& _retval)
+{
+ CHECK_MATCH_INDEX(aIndex, false);
+ _retval = mMatches[aIndex].mFinalCompleteValue;
+ if (_retval.IsEmpty()) {
+ _retval = mMatches[aIndex].mValue;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::SetListener(nsIAutoCompleteSimpleResultListener* aListener)
+{
+ mListener = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetListener(nsIAutoCompleteSimpleResultListener** aListener)
+{
+ nsCOMPtr<nsIAutoCompleteSimpleResultListener> listener(mListener);
+ listener.forget(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoCompleteSimpleResult::RemoveValueAt(int32_t aRowIndex,
+ bool aRemoveFromDb)
+{
+ CHECK_MATCH_INDEX(aRowIndex, false);
+
+ nsString value = mMatches[aRowIndex].mValue;
+ mMatches.RemoveElementAt(aRowIndex);
+
+ if (mListener) {
+ mListener->OnValueRemoved(this, value, aRemoveFromDb);
+ }
+
+ return NS_OK;
+}
diff --git a/components/autocomplete/nsAutoCompleteSimpleResult.h b/components/autocomplete/nsAutoCompleteSimpleResult.h
new file mode 100644
index 000000000..61ee542e4
--- /dev/null
+++ b/components/autocomplete/nsAutoCompleteSimpleResult.h
@@ -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/. */
+
+#ifndef __nsAutoCompleteSimpleResult__
+#define __nsAutoCompleteSimpleResult__
+
+#include "nsIAutoCompleteResult.h"
+#include "nsIAutoCompleteSimpleResult.h"
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+struct AutoCompleteSimpleResultMatch;
+
+class nsAutoCompleteSimpleResult final : public nsIAutoCompleteSimpleResult
+{
+public:
+ nsAutoCompleteSimpleResult();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTOCOMPLETERESULT
+ NS_DECL_NSIAUTOCOMPLETESIMPLERESULT
+
+ nsresult AppendResult(nsIAutoCompleteResult* aResult);
+
+private:
+ ~nsAutoCompleteSimpleResult() {}
+
+protected:
+ typedef nsTArray<AutoCompleteSimpleResultMatch> MatchesArray;
+ MatchesArray mMatches;
+
+ nsString mSearchString;
+ nsString mErrorDescription;
+ int32_t mDefaultIndex;
+ uint32_t mSearchResult;
+
+ bool mTypeAheadResult;
+
+ nsCOMPtr<nsIAutoCompleteSimpleResultListener> mListener;
+};
+
+#endif // __nsAutoCompleteSimpleResult__
diff --git a/components/autocomplete/nsIAutoCompleteController.idl b/components/autocomplete/nsIAutoCompleteController.idl
new file mode 100644
index 000000000..0b68032dc
--- /dev/null
+++ b/components/autocomplete/nsIAutoCompleteController.idl
@@ -0,0 +1,173 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIAutoCompleteInput;
+interface nsIDOMEvent;
+
+[scriptable, uuid(ff9f8465-204a-47a6-b3c9-0628b3856684)]
+interface nsIAutoCompleteController : nsISupports
+{
+ /*
+ * Possible values for the searchStatus attribute
+ */
+ const unsigned short STATUS_NONE = 1;
+ const unsigned short STATUS_SEARCHING = 2;
+ const unsigned short STATUS_COMPLETE_NO_MATCH = 3;
+ const unsigned short STATUS_COMPLETE_MATCH = 4;
+
+ /*
+ * The input widget that is currently being controlled.
+ */
+ attribute nsIAutoCompleteInput input;
+
+ /*
+ * State which indicates the status of possible ongoing searches
+ */
+ readonly attribute unsigned short searchStatus;
+
+ /*
+ * The number of matches
+ */
+ readonly attribute unsigned long matchCount;
+
+ /*
+ * Start a search on a string, assuming the input property is already set.
+ */
+ void startSearch(in AString searchString);
+
+ /*
+ * Stop all asynchronous searches
+ */
+ void stopSearch();
+
+ /*
+ * Notify the controller that the user has changed text in the textbox.
+ * This includes all means of changing the text value, including typing a
+ * character, backspacing, deleting, pasting, committing composition or
+ * canceling composition.
+ *
+ * NOTE: handleText() must be called after composition actually ends, even if
+ * the composition is canceled and the textbox value isn't changed.
+ * Then, implementation of handleText() can access the editor when
+ * it's not in composing mode. DOM compositionend event is not good
+ * timing for calling handleText(). DOM input event immediately after
+ * DOM compositionend event is the best timing to call this.
+ *
+ * @return whether this handler started a new search.
+ */
+ boolean handleText();
+
+ /*
+ * Notify the controller that the user wishes to enter the current text. If
+ * aIsPopupSelection is true, then a selection was made from the popup, so
+ * fill this value into the input field before continuing. If false, just
+ * use the current value of the input field.
+ *
+ * @param aIsPopupSelection
+ * Pass true if the selection was made from the popup.
+ * @param aEvent
+ * The event that triggered the enter, like a key event if the user
+ * pressed the Return key or a click event if the user clicked a popup
+ * item.
+ * @return Whether the controller wishes to prevent event propagation and
+ * default event.
+ */
+ boolean handleEnter(in boolean aIsPopupSelection,
+ [optional] in nsIDOMEvent aEvent);
+
+ /*
+ * Notify the controller that the user wishes to revert autocomplete
+ *
+ * @return Whether the controller wishes to prevent event propagation and
+ * default event.
+ */
+ boolean handleEscape();
+
+ /*
+ * Notify the controller that the user wishes to start composition
+ *
+ * NOTE: nsIAutoCompleteController implementation expects that this is called
+ * by DOM compositionstart handler.
+ */
+ void handleStartComposition();
+
+ /*
+ * Notify the controller that the user wishes to end composition
+ *
+ * NOTE: nsIAutoCompleteController implementation expects that this is called
+ * by DOM compositionend handler.
+ */
+ void handleEndComposition();
+
+ /*
+ * Handle tab. Just closes up.
+ */
+ void handleTab();
+
+ /*
+ * Notify the controller of the following key navigation events:
+ * up, down, left, right, page up, page down
+ *
+ * @return Whether the controller wishes to prevent event propagation and
+ * default event
+ */
+ boolean handleKeyNavigation(in unsigned long key);
+
+ /*
+ * Notify the controller that the user chose to delete the current
+ * auto-complete result.
+ *
+ * @return Whether the controller removed a result item.
+ */
+ boolean handleDelete();
+
+ /*
+ * Get the value of the result at a given index in the last completed search
+ */
+ AString getValueAt(in long index);
+
+ /*
+ * Get the label of the result at a given index in the last completed search
+ */
+ AString getLabelAt(in long index);
+
+ /*
+ * Get the comment of the result at a given index in the last completed search
+ */
+ AString getCommentAt(in long index);
+
+ /*
+ * Get the style hint for the result at a given index in the last completed search
+ */
+ AString getStyleAt(in long index);
+
+ /*
+ * Get the url of the image of the result at a given index in the last completed search
+ */
+ AString getImageAt(in long index);
+
+ /*
+ * For the last completed search, get the final value that should be completed
+ * when the user confirms the match at the given index
+ */
+ AString getFinalCompleteValueAt(in long index);
+
+ /*
+ * Get / set the current search string. Note, setting will not start searching
+ */
+ attribute AString searchString;
+
+ /*
+ * Set the index of the result item that should be initially selected.
+ * This should be used when a search wants to pre-select an element before
+ * the user starts using results.
+ *
+ * @note Setting this is not the same as just setting selectedIndex in
+ * nsIAutocompletePopup, since this will take care of updating any internal
+ * tracking variables of features like completeSelectedIndex.
+ */
+ void setInitiallySelectedIndex(in long index);
+};
diff --git a/components/autocomplete/nsIAutoCompleteInput.idl b/components/autocomplete/nsIAutoCompleteInput.idl
new file mode 100644
index 000000000..26a75ea77
--- /dev/null
+++ b/components/autocomplete/nsIAutoCompleteInput.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+#include "nsIAutoCompleteController.idl"
+
+interface nsIAutoCompletePopup;
+
+[scriptable, uuid(B068E70F-F82C-4C12-AD87-82E271C5C180)]
+interface nsIAutoCompleteInput : nsISupports
+{
+ /*
+ * The result view that will be used to display results
+ */
+ readonly attribute nsIAutoCompletePopup popup;
+
+ /*
+ * The controller.
+ */
+ readonly attribute nsIAutoCompleteController controller;
+
+ /*
+ * Indicates if the popup is currently open
+ */
+ attribute boolean popupOpen;
+
+ /*
+ * Option to disable autocomplete functionality
+ */
+ attribute boolean disableAutoComplete;
+
+ /*
+ * If a search result has its defaultIndex set, this will optionally
+ * try to complete the text in the textbox to the entire text of the
+ * result at the default index as the user types
+ */
+ attribute boolean completeDefaultIndex;
+
+ /*
+ * complete text in the textbox as the user selects from the dropdown
+ * options if set to true
+ */
+ attribute boolean completeSelectedIndex;
+
+ /*
+ * Option for completing to the default result whenever the user hits
+ * enter or the textbox loses focus
+ */
+ attribute boolean forceComplete;
+
+ /*
+ * Option to open the popup only after a certain number of results are available
+ */
+ attribute unsigned long minResultsForPopup;
+
+ /*
+ * The maximum number of rows to show in the autocomplete popup.
+ */
+ attribute unsigned long maxRows;
+
+ /*
+ * Option to show a second column in the popup which contains
+ * the comment for each autocomplete result
+ */
+ attribute boolean showCommentColumn;
+
+ /*
+ * Option to show a third column in the popup which contains
+ * an additional image for each autocomplete result
+ */
+ attribute boolean showImageColumn;
+
+ /*
+ * Number of milliseconds after a keystroke before a search begins
+ */
+ attribute unsigned long timeout;
+
+ /*
+ * An extra parameter to configure searches with.
+ */
+ attribute AString searchParam;
+
+ /*
+ * The number of autocomplete session to search
+ */
+ readonly attribute unsigned long searchCount;
+
+ /*
+ * Get the name of one of the autocomplete search session objects
+ */
+ ACString getSearchAt(in unsigned long index);
+
+ /*
+ * The value of text in the autocomplete textbox.
+ *
+ * @note when setting a new value, the controller always first tries to use
+ * setTextboxValueWithReason, and only if that throws (unimplemented),
+ * fallbacks to the textValue's setter. If a reason is not provided,
+ * the implementation should assume TEXTVALUE_REASON_UNKNOWN, but it
+ * should only happen in testing code.
+ */
+ attribute AString textValue;
+
+ /*
+ * Set the value of text in the autocomplete textbox, providing a reason to
+ * the autocomplete view.
+ */
+ const unsigned short TEXTVALUE_REASON_UNKNOWN = 0;
+ const unsigned short TEXTVALUE_REASON_COMPLETEDEFAULT = 1;
+ const unsigned short TEXTVALUE_REASON_COMPLETESELECTED = 2;
+ const unsigned short TEXTVALUE_REASON_REVERT = 3;
+ const unsigned short TEXTVALUE_REASON_ENTERMATCH = 4;
+
+ void setTextValueWithReason(in AString aValue,
+ in unsigned short aReason);
+
+ /*
+ * Report the starting index of the cursor in the textbox
+ */
+ readonly attribute long selectionStart;
+
+ /*
+ * Report the ending index of the cursor in the textbox
+ */
+ readonly attribute long selectionEnd;
+
+ /*
+ * Select a range of text in the autocomplete textbox
+ */
+ void selectTextRange(in long startIndex, in long endIndex);
+
+ /*
+ * Notification that the search has started
+ */
+ void onSearchBegin();
+
+ /*
+ * Notification that the search concluded successfully
+ */
+ void onSearchComplete();
+
+ /*
+ * Notification that the user selected and entered a result item
+ *
+ * @param aEvent
+ * The event that triggered the enter.
+ * @return True if the user wishes to prevent the enter
+ */
+ boolean onTextEntered([optional] in nsIDOMEvent aEvent);
+
+ /*
+ * Notification that the user cancelled the autocomplete session
+ *
+ * @return True if the user wishes to prevent the revert
+ */
+ boolean onTextReverted();
+
+ /*
+ * This popup should consume or dispatch the rollup event.
+ * TRUE: should consume; FALSE: should dispatch.
+ */
+ readonly attribute boolean consumeRollupEvent;
+
+ /*
+ * Indicates whether this input is in a "private browsing" context.
+ * nsIAutoCompleteSearches for these inputs should not persist any data to disk
+ * (such as a history database).
+ */
+ readonly attribute boolean inPrivateContext;
+
+ /*
+ * Don't rollup the popup when the caret is moved.
+ */
+ readonly attribute boolean noRollupOnCaretMove;
+
+ /**
+ * The userContextId of the current browser.
+ */
+ readonly attribute unsigned long userContextId;
+};
diff --git a/components/autocomplete/nsIAutoCompletePopup.idl b/components/autocomplete/nsIAutoCompletePopup.idl
new file mode 100644
index 000000000..cb5dda6f2
--- /dev/null
+++ b/components/autocomplete/nsIAutoCompletePopup.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMElement;
+interface nsIAutoCompleteInput;
+
+[scriptable, uuid(bd3c2662-a988-41ab-8c94-c15ed0e6ac7d)]
+interface nsIAutoCompletePopup : nsISupports
+{
+ /*
+ * The input object that the popup is currently bound to
+ */
+ readonly attribute nsIAutoCompleteInput input;
+
+ /*
+ * An alternative value to be used when text is entered, rather than the
+ * value of the selected item
+ */
+ readonly attribute AString overrideValue;
+
+ /*
+ * The index of the result item that is currently selected
+ */
+ attribute long selectedIndex;
+
+ /*
+ * Indicates if the popup is currently open
+ */
+ readonly attribute boolean popupOpen;
+
+ /*
+ * Bind the popup to an input object and display it with the given coordinates
+ *
+ * @param input - The input object that the popup will be bound to
+ * @param element - The element that the popup will be aligned with
+ */
+ void openAutocompletePopup(in nsIAutoCompleteInput input, in nsIDOMElement element);
+
+ /*
+ * Close the popup and detach from the bound input
+ */
+ void closePopup();
+
+ /*
+ * Instruct the result view to repaint itself to reflect the most current
+ * underlying data
+ *
+ * @param reason - The reason the popup needs to be invalidated, one of the
+ * INVALIDATE_REASON consts.
+ */
+ void invalidate(in unsigned short reason);
+
+ /*
+ * Possible values of invalidate()'s 'reason' argument.
+ */
+ const unsigned short INVALIDATE_REASON_NEW_RESULT = 0;
+ const unsigned short INVALIDATE_REASON_DELETE = 1;
+
+ /*
+ * Change the selection relative to the current selection and make sure
+ * the newly selected row is visible
+ *
+ * @param reverse - Select a row above the current selection
+ * @param page - Select a row that is a full visible page from the current selection
+ * @return The currently selected result item index
+ */
+ void selectBy(in boolean reverse, in boolean page);
+};
diff --git a/components/autocomplete/nsIAutoCompleteResult.idl b/components/autocomplete/nsIAutoCompleteResult.idl
new file mode 100644
index 000000000..9ae22ade7
--- /dev/null
+++ b/components/autocomplete/nsIAutoCompleteResult.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(9203c031-c4e7-4537-a4ec-81443d623d5a)]
+interface nsIAutoCompleteResult : nsISupports
+{
+ /**
+ * Possible values for the searchResult attribute
+ */
+ const unsigned short RESULT_IGNORED = 1; /* indicates invalid searchString */
+ const unsigned short RESULT_FAILURE = 2; /* indicates failure */
+ const unsigned short RESULT_NOMATCH = 3; /* indicates success with no matches
+ and that the search is complete */
+ const unsigned short RESULT_SUCCESS = 4; /* indicates success with matches
+ and that the search is complete */
+ const unsigned short RESULT_NOMATCH_ONGOING = 5; /* indicates success
+ with no matches
+ and that the search
+ is still ongoing */
+ const unsigned short RESULT_SUCCESS_ONGOING = 6; /* indicates success
+ with matches
+ and that the search
+ is still ongoing */
+ /**
+ * The original search string
+ */
+ readonly attribute AString searchString;
+
+ /**
+ * The result of the search
+ */
+ readonly attribute unsigned short searchResult;
+
+ /**
+ * Index of the default item that should be entered if none is selected
+ */
+ readonly attribute long defaultIndex;
+
+ /**
+ * A string describing the cause of a search failure
+ */
+ readonly attribute AString errorDescription;
+
+ /**
+ * The number of matches
+ */
+ readonly attribute unsigned long matchCount;
+
+ /**
+ * If true, the results will not be displayed in the popup. However,
+ * if a default index is specified, the default item will still be
+ * completed in the input.
+ */
+ readonly attribute boolean typeAheadResult;
+
+ /**
+ * Get the value of the result at the given index
+ */
+ AString getValueAt(in long index);
+
+ /**
+ * This returns the string that is displayed in the dropdown
+ */
+ AString getLabelAt(in long index);
+
+ /**
+ * Get the comment of the result at the given index
+ */
+ AString getCommentAt(in long index);
+
+ /**
+ * Get the style hint for the result at the given index
+ */
+ AString getStyleAt(in long index);
+
+ /**
+ * Get the image of the result at the given index
+ */
+ AString getImageAt(in long index);
+
+ /**
+ * Get the final value that should be completed when the user confirms
+ * the match at the given index.
+ */
+ AString getFinalCompleteValueAt(in long index);
+
+ /**
+ * Remove the value at the given index from the autocomplete results.
+ * If removeFromDb is set to true, the value should be removed from
+ * persistent storage as well.
+ */
+ void removeValueAt(in long rowIndex, in boolean removeFromDb);
+};
diff --git a/components/autocomplete/nsIAutoCompleteSearch.idl b/components/autocomplete/nsIAutoCompleteSearch.idl
new file mode 100644
index 000000000..188c333ac
--- /dev/null
+++ b/components/autocomplete/nsIAutoCompleteSearch.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIAutoCompleteResult;
+interface nsIAutoCompleteObserver;
+
+[scriptable, uuid(DE8DB85F-C1DE-4d87-94BA-7844890F91FE)]
+interface nsIAutoCompleteSearch : nsISupports
+{
+ /*
+ * Search for a given string and notify a listener (either synchronously
+ * or asynchronously) of the result
+ *
+ * @param searchString - The string to search for
+ * @param searchParam - An extra parameter
+ * @param previousResult - A previous result to use for faster searching
+ * @param listener - A listener to notify when the search is complete
+ */
+ void startSearch(in AString searchString,
+ in AString searchParam,
+ in nsIAutoCompleteResult previousResult,
+ in nsIAutoCompleteObserver listener);
+
+ /*
+ * Stop all searches that are in progress
+ */
+ void stopSearch();
+};
+
+[scriptable, uuid(8bd1dbbc-dcce-4007-9afa-b551eb687b61)]
+interface nsIAutoCompleteObserver : nsISupports
+{
+ /*
+ * Called when a search is complete and the results are ready
+ *
+ * @param search - The search object that processed this search
+ * @param result - The search result object
+ */
+ void onSearchResult(in nsIAutoCompleteSearch search, in nsIAutoCompleteResult result);
+
+ /*
+ * Called to update with new results
+ *
+ * @param search - The search object that processed this search
+ * @param result - The search result object
+ */
+ void onUpdateSearchResult(in nsIAutoCompleteSearch search, in nsIAutoCompleteResult result);
+};
+
+[scriptable, uuid(4c3e7462-fbfb-4310-8f4b-239238392b75)]
+interface nsIAutoCompleteSearchDescriptor : nsISupports
+{
+ // The search is started after the timeout specified by the corresponding
+ // nsIAutoCompleteInput implementation.
+ const unsigned short SEARCH_TYPE_DELAYED = 0;
+ // The search is started synchronously, before any delayed searches.
+ const unsigned short SEARCH_TYPE_IMMEDIATE = 1;
+
+ /**
+ * Identifies the search behavior.
+ * Should be one of the SEARCH_TYPE_* constants above.
+ * Defaults to SEARCH_TYPE_DELAYED.
+ */
+ readonly attribute unsigned short searchType;
+
+ /*
+ * Whether a new search should be triggered when the user deletes the
+ * autofilled part.
+ */
+ readonly attribute boolean clearingAutoFillSearchesAgain;
+};
diff --git a/components/autocomplete/nsIAutoCompleteSimpleResult.idl b/components/autocomplete/nsIAutoCompleteSimpleResult.idl
new file mode 100644
index 000000000..6a8827ab8
--- /dev/null
+++ b/components/autocomplete/nsIAutoCompleteSimpleResult.idl
@@ -0,0 +1,122 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIAutoCompleteResult.idl"
+
+interface nsIAutoCompleteSimpleResultListener;
+
+/**
+ * This class implements nsIAutoCompleteResult and provides simple methods
+ * for setting the value and result items. It can be used whenever some basic
+ * auto complete results are needed that can be pre-generated and filled into
+ * an array.
+ */
+
+[scriptable, uuid(23de9c96-becb-4d0d-a9bb-1d131ce361b5)]
+interface nsIAutoCompleteSimpleResult : nsIAutoCompleteResult
+{
+ /**
+ * A writer for the readonly attribute 'searchString' which should contain
+ * the string that the user typed.
+ */
+ void setSearchString(in AString aSearchString);
+
+ /**
+ * A writer for the readonly attribute 'errorDescription'.
+ */
+ void setErrorDescription(in AString aErrorDescription);
+
+ /**
+ * A writer for the readonly attribute 'defaultIndex' which should contain
+ * the index of the list that will be selected by default (normally 0).
+ */
+ void setDefaultIndex(in long aDefaultIndex);
+
+ /**
+ * A writer for the readonly attribute 'searchResult' which should contain
+ * one of the constants nsIAutoCompleteResult.RESULT_* indicating the success
+ * of the search.
+ */
+ void setSearchResult(in unsigned short aSearchResult);
+
+ /**
+ * A writer for the readonly attribute 'typeAheadResult', typically set
+ * because a result is only intended for type-ahead completion.
+ */
+ void setTypeAheadResult(in boolean aHidden);
+
+ /**
+ * Inserts a match consisting of the given value, comment, image, style and
+ * the value to use for defaultIndex completion at a given position.
+ * @param aIndex
+ * The index to insert at
+ * @param aValue
+ * The value to autocomplete to
+ * @param aComment
+ * Comment shown in the autocomplete widget to describe this match
+ * @param aImage
+ * Image shown in the autocomplete widget for this match.
+ * @param aStyle
+ * Describes how to style the match in the autocomplete widget
+ * @param aFinalCompleteValue
+ * Value used when the user confirms selecting this match. If not
+ * provided, aValue will be used.
+ */
+ void insertMatchAt(in long aIndex,
+ in AString aValue,
+ in AString aComment,
+ [optional] in AString aImage,
+ [optional] in AString aStyle,
+ [optional] in AString aFinalCompleteValue,
+ [optional] in AString aLabel);
+
+ /**
+ * Appends a match consisting of the given value, comment, image, style and
+ * the value to use for defaultIndex completion.
+ * @param aValue
+ * The value to autocomplete to
+ * @param aComment
+ * Comment shown in the autocomplete widget to describe this match
+ * @param aImage
+ * Image shown in the autocomplete widget for this match.
+ * @param aStyle
+ * Describes how to style the match in the autocomplete widget
+ * @param aFinalCompleteValue
+ * Value used when the user confirms selecting this match. If not
+ * provided, aValue will be used.
+ */
+ void appendMatch(in AString aValue,
+ in AString aComment,
+ [optional] in AString aImage,
+ [optional] in AString aStyle,
+ [optional] in AString aFinalCompleteValue,
+ [optional] in AString aLabel);
+
+ /**
+ * Gets the listener for changes in the result.
+ */
+ nsIAutoCompleteSimpleResultListener getListener();
+
+ /**
+ * Sets a listener for changes in the result.
+ */
+ void setListener(in nsIAutoCompleteSimpleResultListener aListener);
+};
+
+[scriptable, uuid(004efdc5-1989-4874-8a7a-345bf2fa33af)]
+interface nsIAutoCompleteSimpleResultListener : nsISupports
+{
+ /**
+ * Dispatched after a value is removed from the result.
+ * @param aResult
+ * The result from which aValue has been removed.
+ * @param aValue
+ * The removed value.
+ * @param aRemoveFromDb
+ * Whether the value should be removed from persistent storage as well.
+ */
+ void onValueRemoved(in nsIAutoCompleteSimpleResult aResult, in AString aValue,
+ in boolean aRemoveFromDb);
+};
diff --git a/components/autoconfig/moz.build b/components/autoconfig/moz.build
new file mode 100644
index 000000000..a2a528efc
--- /dev/null
+++ b/components/autoconfig/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsIAutoConfig.idl',
+ 'public/nsIReadConfig.idl',
+]
+
+SOURCES += [
+ 'src/nsAutoConfig.cpp',
+ 'src/nsConfigFactory.cpp',
+ 'src/nsJSConfigTriggers.cpp',
+ 'src/nsReadConfig.cpp',
+]
+
+FINAL_TARGET_FILES.defaults.autoconfig += ['src/prefcalls.js']
+
+XPIDL_MODULE = 'autoconfig'
+FINAL_LIBRARY = 'xul' \ No newline at end of file
diff --git a/components/autoconfig/public/nsIAutoConfig.idl b/components/autoconfig/public/nsIAutoConfig.idl
new file mode 100644
index 000000000..e76dc0614
--- /dev/null
+++ b/components/autoconfig/public/nsIAutoConfig.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: IDL; 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"
+
+
+%{C++
+
+#define NS_AUTOCONFIG_CID\
+ { 0xe036c738,\
+ 0x1dd1,\
+ 0x11b2,\
+ { 0x93, 0x92, 0x9d, 0x94, 0xaa, 0x74, 0xb0, 0xc5 }\
+ }
+
+#define NS_AUTOCONFIG_CONTRACTID \
+ "@mozilla.org/autoconfiguration;1"
+
+%}
+
+[uuid (80DB54AE-13F2-11d5-BE44-00108335A220)]
+interface nsIAutoConfig : nsISupports {
+ attribute string configURL;
+};
diff --git a/components/autoconfig/public/nsIReadConfig.idl b/components/autoconfig/public/nsIReadConfig.idl
new file mode 100644
index 000000000..e00c06e52
--- /dev/null
+++ b/components/autoconfig/public/nsIReadConfig.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: IDL; 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"
+
+%{C++
+
+#define NS_READCONFIG_CID\
+ { 0xba5bc4c6,\
+ 0x1dd1, \
+ 0x11b2, \
+ { 0xbb, 0x89, 0xb8, 0x44, 0xc6, 0xec, 0x03, 0x39 }\
+ }
+
+#define NS_READCONFIG_CONTRACTID \
+ "@mozilla.org/readconfig;1"
+
+%}
+
+[uuid (ba5bc4c6-1dd1-11b2-bb89-b844c6ec0339)]
+interface nsIReadConfig : nsISupports {
+};
diff --git a/components/autoconfig/src/nsAutoConfig.cpp b/components/autoconfig/src/nsAutoConfig.cpp
new file mode 100644
index 000000000..08f406093
--- /dev/null
+++ b/components/autoconfig/src/nsAutoConfig.cpp
@@ -0,0 +1,532 @@
+/* -*- 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 "nsAutoConfig.h"
+#include "nsIURI.h"
+#include "nsIHttpChannel.h"
+#include "nsIFileStreams.h"
+#include "nsThreadUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "prmem.h"
+#include "nsIObserverService.h"
+#include "nsLiteralString.h"
+#include "nsIPromptService.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nspr.h"
+#include <algorithm>
+
+#include "mozilla/Logging.h"
+
+using mozilla::LogLevel;
+
+mozilla::LazyLogModule MCD("MCD");
+
+extern nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length,
+ const char *filename,
+ bool bGlobalContext,
+ bool bCallbacks,
+ bool skipFirstLine);
+
+// nsISupports Implementation
+
+NS_IMPL_ISUPPORTS(nsAutoConfig, nsIAutoConfig, nsITimerCallback, nsIStreamListener, nsIObserver, nsIRequestObserver, nsISupportsWeakReference)
+
+nsAutoConfig::nsAutoConfig()
+{
+}
+
+nsresult nsAutoConfig::Init()
+{
+ // member initializers and constructor code
+
+ nsresult rv;
+ mLoaded = false;
+
+ // Registering the object as an observer to the profile-after-change topic
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = observerService->AddObserver(this,"profile-after-change", true);
+
+ return rv;
+}
+
+nsAutoConfig::~nsAutoConfig()
+{
+}
+
+// attribute string configURL
+NS_IMETHODIMP nsAutoConfig::GetConfigURL(char **aConfigURL)
+{
+ if (!aConfigURL)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mConfigURL.IsEmpty()) {
+ *aConfigURL = nullptr;
+ return NS_OK;
+ }
+
+ *aConfigURL = ToNewCString(mConfigURL);
+ if (!*aConfigURL)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoConfig::SetConfigURL(const char *aConfigURL)
+{
+ if (!aConfigURL)
+ return NS_ERROR_NULL_POINTER;
+ mConfigURL.Assign(aConfigURL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoConfig::OnStartRequest(nsIRequest *request, nsISupports *context)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAutoConfig::OnDataAvailable(nsIRequest *request,
+ nsISupports *context,
+ nsIInputStream *aIStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ uint32_t amt, size;
+ nsresult rv;
+ char buf[1024];
+
+ while (aLength) {
+ size = std::min<size_t>(aLength, sizeof(buf));
+ rv = aIStream->Read(buf, size, &amt);
+ if (NS_FAILED(rv))
+ return rv;
+ mBuf.Append(buf, amt);
+ aLength -= amt;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAutoConfig::OnStopRequest(nsIRequest *request, nsISupports *context,
+ nsresult aStatus)
+{
+ nsresult rv;
+
+ // If the request is failed, go read the failover.jsc file
+ if (NS_FAILED(aStatus)) {
+ MOZ_LOG(MCD, LogLevel::Debug, ("mcd request failed with status %x\n", aStatus));
+ return readOfflineFile();
+ }
+
+ // Checking for the http response, if failure go read the failover file.
+ nsCOMPtr<nsIHttpChannel> pHTTPCon(do_QueryInterface(request));
+ if (pHTTPCon) {
+ uint32_t httpStatus;
+ pHTTPCon->GetResponseStatus(&httpStatus);
+ if (httpStatus != 200)
+ {
+ MOZ_LOG(MCD, LogLevel::Debug, ("mcd http request failed with status %x\n", httpStatus));
+ return readOfflineFile();
+ }
+ }
+
+ // Send the autoconfig.jsc to javascript engine.
+
+ rv = EvaluateAdminConfigScript(mBuf.get(), mBuf.Length(),
+ nullptr, false,true, false);
+ if (NS_SUCCEEDED(rv)) {
+
+ // Write the autoconfig.jsc to failover.jsc (cached copy)
+ rv = writeFailoverFile();
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Error writing failover.jsc file");
+
+ // Releasing the lock to allow the main thread to start execution
+ mLoaded = true;
+
+ return NS_OK;
+ }
+ // there is an error in parsing of the autoconfig file.
+ NS_WARNING("Error reading autoconfig.jsc from the network, reading the offline version");
+ return readOfflineFile();
+}
+
+// Notify method as a TimerCallBack function
+NS_IMETHODIMP nsAutoConfig::Notify(nsITimer *timer)
+{
+ downloadAutoConfig();
+ return NS_OK;
+}
+
+/* Observe() is called twice: once at the instantiation time and other
+ after the profile is set. It doesn't do anything but return NS_OK during the
+ creation time. Second time it calls downloadAutoConfig().
+*/
+
+NS_IMETHODIMP nsAutoConfig::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *someData)
+{
+ nsresult rv = NS_OK;
+ if (!nsCRT::strcmp(aTopic, "profile-after-change")) {
+
+ // We will be calling downloadAutoConfig even if there is no profile
+ // name. Nothing will be passed as a parameter to the URL and the
+ // default case will be picked up by the script.
+
+ rv = downloadAutoConfig();
+
+ }
+
+ return rv;
+}
+
+nsresult nsAutoConfig::downloadAutoConfig()
+{
+ nsresult rv;
+ nsAutoCString emailAddr;
+ nsXPIDLCString urlName;
+ static bool firstTime = true;
+
+ if (mConfigURL.IsEmpty()) {
+ MOZ_LOG(MCD, LogLevel::Debug, ("global config url is empty - did you set autoadmin.global_config_url?\n"));
+ NS_WARNING("AutoConfig called without global_config_url");
+ return NS_OK;
+ }
+
+ // If there is an email address appended as an argument to the ConfigURL
+ // in the previous read, we need to remove it when timer kicks in and
+ // downloads the autoconfig file again.
+ // If necessary, the email address will be added again as an argument.
+ int32_t index = mConfigURL.RFindChar((char16_t)'?');
+ if (index != -1)
+ mConfigURL.Truncate(index);
+
+ // Clean up the previous read, the new read is going to use the same buffer
+ if (!mBuf.IsEmpty())
+ mBuf.Truncate(0);
+
+ // Get the preferences branch and save it to the member variable
+ if (!mPrefBranch) {
+ nsCOMPtr<nsIPrefService> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = prefs->GetBranch(nullptr,getter_AddRefs(mPrefBranch));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // Check to see if the network is online/offline
+ nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool offline;
+ rv = ios->GetOffline(&offline);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (offline) {
+ bool offlineFailover;
+ rv = mPrefBranch->GetBoolPref("autoadmin.offline_failover",
+ &offlineFailover);
+ // Read the failover.jsc if the network is offline and the pref says so
+ if (NS_SUCCEEDED(rv) && offlineFailover)
+ return readOfflineFile();
+ }
+
+ /* Append user's identity at the end of the URL if the pref says so.
+ First we are checking for the user's email address but if it is not
+ available in the case where the client is used without messenger, user's
+ profile name will be used as an unique identifier
+ */
+ bool appendMail;
+ rv = mPrefBranch->GetBoolPref("autoadmin.append_emailaddr", &appendMail);
+ if (NS_SUCCEEDED(rv) && appendMail) {
+ rv = getEmailAddr(emailAddr);
+ if (NS_SUCCEEDED(rv) && emailAddr.get()) {
+ /* Adding the unique identifier at the end of autoconfig URL.
+ In this case the autoconfig URL is a script and
+ emailAddr as passed as an argument
+ */
+ mConfigURL.Append('?');
+ mConfigURL.Append(emailAddr);
+ }
+ }
+
+ // create a new url
+ nsCOMPtr<nsIURI> url;
+ nsCOMPtr<nsIChannel> channel;
+
+ rv = NS_NewURI(getter_AddRefs(url), mConfigURL.get(), nullptr, nullptr);
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(MCD, LogLevel::Debug, ("failed to create URL - is autoadmin.global_config_url valid? - %s\n", mConfigURL.get()));
+ return rv;
+ }
+
+ MOZ_LOG(MCD, LogLevel::Debug, ("running MCD url %s\n", mConfigURL.get()));
+ // open a channel for the url
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ url,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ nsIRequest::INHIBIT_PERSISTENT_CACHING |
+ nsIRequest::LOAD_BYPASS_CACHE);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = channel->AsyncOpen2(this);
+ if (NS_FAILED(rv)) {
+ readOfflineFile();
+ return rv;
+ }
+
+ // Set a repeating timer if the pref is set.
+ // This is to be done only once.
+ // Also We are having the event queue processing only for the startup
+ // It is not needed with the repeating timer.
+ if (firstTime) {
+ firstTime = false;
+
+ // Getting the current thread. If we start an AsyncOpen, the thread
+ // needs to wait before the reading of autoconfig is done
+
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+ NS_ENSURE_STATE(thread);
+
+ /* process events until we're finished. AutoConfig.jsc reading needs
+ to be finished before the browser starts loading up
+ We are waiting for the mLoaded which will be set through
+ onStopRequest or readOfflineFile methods
+ There is a possibility of deadlock so we need to make sure
+ that mLoaded will be set to true in any case (success/failure)
+ */
+
+ while (!mLoaded)
+ NS_ENSURE_STATE(NS_ProcessNextEvent(thread));
+
+ int32_t minutes;
+ rv = mPrefBranch->GetIntPref("autoadmin.refresh_interval",
+ &minutes);
+ if (NS_SUCCEEDED(rv) && minutes > 0) {
+ // Create a new timer and pass this nsAutoConfig
+ // object as a timer callback.
+ mTimer = do_CreateInstance("@mozilla.org/timer;1",&rv);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = mTimer->InitWithCallback(this, minutes * 60 * 1000,
+ nsITimer::TYPE_REPEATING_SLACK);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ } //first_time
+
+ return NS_OK;
+} // nsPref::downloadAutoConfig()
+
+
+
+nsresult nsAutoConfig::readOfflineFile()
+{
+ nsresult rv;
+
+ /* Releasing the lock to allow main thread to start
+ execution. At this point we do not need to stall
+ the thread since all network activities are done.
+ */
+ mLoaded = true;
+
+ bool failCache;
+ rv = mPrefBranch->GetBoolPref("autoadmin.failover_to_cached", &failCache);
+ if (NS_SUCCEEDED(rv) && !failCache) {
+ // disable network connections and return.
+
+ nsCOMPtr<nsIIOService> ios =
+ do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool offline;
+ rv = ios->GetOffline(&offline);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!offline) {
+ rv = ios->SetOffline(true);
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // lock the "network.online" prference so user cannot toggle back to
+ // online mode.
+ rv = mPrefBranch->SetBoolPref("network.online", false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mPrefBranch->LockPref("network.online");
+ return NS_OK;
+ }
+
+ /* faiover_to_cached is set to true so
+ Open the file and read the content.
+ execute the javascript file
+ */
+
+ nsCOMPtr<nsIFile> failoverFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(failoverFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ failoverFile->AppendNative(NS_LITERAL_CSTRING("failover.jsc"));
+ rv = evaluateLocalFile(failoverFile);
+ if (NS_FAILED(rv))
+ NS_WARNING("Couldn't open failover.jsc, going back to default prefs");
+ return NS_OK;
+}
+
+nsresult nsAutoConfig::evaluateLocalFile(nsIFile *file)
+{
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> inStr;
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), file);
+ if (NS_FAILED(rv))
+ return rv;
+
+ int64_t fileSize;
+ file->GetFileSize(&fileSize);
+ uint32_t fs = fileSize; // Converting 64 bit structure to unsigned int
+ char *buf = (char *)PR_Malloc(fs * sizeof(char));
+ if (!buf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ uint32_t amt = 0;
+ rv = inStr->Read(buf, fs, &amt);
+ if (NS_SUCCEEDED(rv)) {
+ EvaluateAdminConfigScript(buf, fs, nullptr, false,
+ true, false);
+ }
+ inStr->Close();
+ PR_Free(buf);
+ return rv;
+}
+
+nsresult nsAutoConfig::writeFailoverFile()
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> failoverFile;
+ nsCOMPtr<nsIOutputStream> outStr;
+ uint32_t amt;
+
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(failoverFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ failoverFile->AppendNative(NS_LITERAL_CSTRING("failover.jsc"));
+
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStr), failoverFile);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = outStr->Write(mBuf.get(),mBuf.Length(),&amt);
+ outStr->Close();
+ return rv;
+}
+
+nsresult nsAutoConfig::getEmailAddr(nsACString & emailAddr)
+{
+
+ nsresult rv;
+ nsXPIDLCString prefValue;
+
+ /* Getting an email address through set of three preferences:
+ First getting a default account with
+ "mail.accountmanager.defaultaccount"
+ second getting an associated id with the default account
+ Third getting an email address with id
+ */
+
+ rv = mPrefBranch->GetCharPref("mail.accountmanager.defaultaccount",
+ getter_Copies(prefValue));
+ if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty()) {
+ emailAddr = NS_LITERAL_CSTRING("mail.account.") +
+ prefValue + NS_LITERAL_CSTRING(".identities");
+ rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(),
+ getter_Copies(prefValue));
+ if (NS_FAILED(rv) || prefValue.IsEmpty())
+ return PromptForEMailAddress(emailAddr);
+ int32_t commandIndex = prefValue.FindChar(',');
+ if (commandIndex != kNotFound)
+ prefValue.Truncate(commandIndex);
+ emailAddr = NS_LITERAL_CSTRING("mail.identity.") +
+ prefValue + NS_LITERAL_CSTRING(".useremail");
+ rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(),
+ getter_Copies(prefValue));
+ if (NS_FAILED(rv) || prefValue.IsEmpty())
+ return PromptForEMailAddress(emailAddr);
+ emailAddr = prefValue;
+ }
+ else {
+ // look for 4.x pref in case we just migrated.
+ rv = mPrefBranch->GetCharPref("mail.identity.useremail",
+ getter_Copies(prefValue));
+ if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty())
+ emailAddr = prefValue;
+ else
+ PromptForEMailAddress(emailAddr);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAutoConfig::PromptForEMailAddress(nsACString &emailAddress)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPromptService> promptService = do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://autoconfig/locale/autoconfig.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsXPIDLString title;
+ rv = bundle->GetStringFromName(u"emailPromptTitle", getter_Copies(title));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsXPIDLString err;
+ rv = bundle->GetStringFromName(u"emailPromptMsg", getter_Copies(err));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool check = false;
+ nsXPIDLString emailResult;
+ bool success;
+ rv = promptService->Prompt(nullptr, title.get(), err.get(), getter_Copies(emailResult), nullptr, &check, &success);
+ if (!success)
+ return NS_ERROR_FAILURE;
+ NS_ENSURE_SUCCESS(rv, rv);
+ LossyCopyUTF16toASCII(emailResult, emailAddress);
+ return NS_OK;
+}
diff --git a/components/autoconfig/src/nsAutoConfig.h b/components/autoconfig/src/nsAutoConfig.h
new file mode 100644
index 000000000..d805ad184
--- /dev/null
+++ b/components/autoconfig/src/nsAutoConfig.h
@@ -0,0 +1,54 @@
+/* -*- 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/. */
+
+#ifndef nsAutoConfig_h
+#define nsAutoConfig_h
+
+#include "nsIAutoConfig.h"
+#include "nsITimer.h"
+#include "nsIFile.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsWeakReference.h"
+#include "nsString.h"
+
+class nsAutoConfig : public nsIAutoConfig,
+ public nsITimerCallback,
+ public nsIStreamListener,
+ public nsIObserver,
+ public nsSupportsWeakReference
+
+{
+ public:
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIAUTOCONFIG
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+
+ nsAutoConfig();
+ nsresult Init();
+
+ protected:
+
+ virtual ~nsAutoConfig();
+ nsresult downloadAutoConfig();
+ nsresult readOfflineFile();
+ nsresult evaluateLocalFile(nsIFile *file);
+ nsresult writeFailoverFile();
+ nsresult getEmailAddr(nsACString & emailAddr);
+ nsresult PromptForEMailAddress(nsACString &emailAddress);
+ nsCString mBuf;
+ nsCOMPtr<nsIPrefBranch> mPrefBranch;
+ bool mLoaded;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCString mConfigURL;
+};
+
+#endif
diff --git a/components/autoconfig/src/nsConfigFactory.cpp b/components/autoconfig/src/nsConfigFactory.cpp
new file mode 100644
index 000000000..89f886b2a
--- /dev/null
+++ b/components/autoconfig/src/nsConfigFactory.cpp
@@ -0,0 +1,41 @@
+/* -*- 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/ModuleUtils.h"
+#include "nsAutoConfig.h"
+#include "nsReadConfig.h"
+#include "nsIAppStartupNotifier.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAutoConfig, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsReadConfig, Init)
+
+NS_DEFINE_NAMED_CID(NS_AUTOCONFIG_CID);
+NS_DEFINE_NAMED_CID(NS_READCONFIG_CID);
+
+static const mozilla::Module::CIDEntry kAutoConfigCIDs[] = {
+ { &kNS_AUTOCONFIG_CID, false, nullptr, nsAutoConfigConstructor },
+ { &kNS_READCONFIG_CID, false, nullptr, nsReadConfigConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kAutoConfigContracts[] = {
+ { NS_AUTOCONFIG_CONTRACTID, &kNS_AUTOCONFIG_CID },
+ { NS_READCONFIG_CONTRACTID, &kNS_READCONFIG_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kAutoConfigCategories[] = {
+ { "pref-config-startup", "ReadConfig Module", NS_READCONFIG_CONTRACTID },
+ { nullptr }
+};
+
+static const mozilla::Module kAutoConfigModule = {
+ mozilla::Module::kVersion,
+ kAutoConfigCIDs,
+ kAutoConfigContracts,
+ kAutoConfigCategories
+};
+
+NSMODULE_DEFN(nsAutoConfigModule) = &kAutoConfigModule;
diff --git a/components/autoconfig/src/nsJSConfigTriggers.cpp b/components/autoconfig/src/nsJSConfigTriggers.cpp
new file mode 100644
index 000000000..87f2bef20
--- /dev/null
+++ b/components/autoconfig/src/nsJSConfigTriggers.cpp
@@ -0,0 +1,138 @@
+/* -*- 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 "jsapi.h"
+#include "nsIXPConnect.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+#include "nsString.h"
+#include "nsIPrefService.h"
+#include "nspr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsJSPrincipals.h"
+#include "nsIScriptError.h"
+#include "jswrapper.h"
+
+extern mozilla::LazyLogModule MCD;
+using mozilla::AutoSafeJSContext;
+using mozilla::dom::AutoJSAPI;
+
+//*****************************************************************************
+
+static JS::PersistentRooted<JSObject *> autoconfigSb;
+
+nsresult CentralizedAdminPrefManagerInit()
+{
+ nsresult rv;
+
+ // If the sandbox is already created, no need to create it again.
+ if (autoconfigSb.initialized())
+ return NS_OK;
+
+ // Grab XPConnect.
+ nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Grab the system principal.
+ nsCOMPtr<nsIPrincipal> principal;
+ nsContentUtils::GetSecurityManager()->GetSystemPrincipal(getter_AddRefs(principal));
+
+
+ // Create a sandbox.
+ AutoSafeJSContext cx;
+ JS::Rooted<JSObject*> sandbox(cx);
+ rv = xpc->CreateSandbox(cx, principal, sandbox.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Unwrap, store and root the sandbox.
+ NS_ENSURE_STATE(sandbox);
+ autoconfigSb.init(cx, js::UncheckedUnwrap(sandbox));
+
+ return NS_OK;
+}
+
+nsresult CentralizedAdminPrefManagerFinish()
+{
+ if (autoconfigSb.initialized()) {
+ AutoSafeJSContext cx;
+ autoconfigSb.reset();
+ JS_MaybeGC(cx);
+ }
+ return NS_OK;
+}
+
+nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length,
+ const char *filename, bool bGlobalContext,
+ bool bCallbacks, bool skipFirstLine)
+{
+ nsresult rv = NS_OK;
+
+ if (skipFirstLine) {
+ /* In order to protect the privacy of the JavaScript preferences file
+ * from loading by the browser, we make the first line unparseable
+ * by JavaScript. We must skip that line here before executing
+ * the JavaScript code.
+ */
+ unsigned int i = 0;
+ while (i < length) {
+ char c = js_buffer[i++];
+ if (c == '\r') {
+ if (js_buffer[i] == '\n')
+ i++;
+ break;
+ }
+ if (c == '\n')
+ break;
+ }
+
+ length -= i;
+ js_buffer += i;
+ }
+
+ // Grab XPConnect.
+ nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(autoconfigSb)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = jsapi.cx();
+
+ nsAutoCString script(js_buffer, length);
+ JS::RootedValue v(cx);
+
+ nsString convertedScript;
+ bool isUTF8 = IsUTF8(script);
+ if (isUTF8) {
+ convertedScript = NS_ConvertUTF8toUTF16(script);
+ } else {
+ nsContentUtils::ReportToConsoleNonLocalized(
+ NS_LITERAL_STRING("Your AutoConfig file is ASCII. Please convert it to UTF-8."),
+ nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("autoconfig"),
+ nullptr);
+ /* If the length is 0, the conversion failed. Fallback to ASCII */
+ convertedScript = NS_ConvertASCIItoUTF16(script);
+ }
+ JS::Rooted<JS::Value> value(cx, JS::BooleanValue(isUTF8));
+ if (!JS_DefineProperty(cx, autoconfigSb, "gIsUTF8", value, JSPROP_ENUMERATE)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = xpc->EvalInSandboxObject(convertedScript, filename, cx,
+ autoconfigSb, JSVERSION_LATEST, &v);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
diff --git a/components/autoconfig/src/nsReadConfig.cpp b/components/autoconfig/src/nsReadConfig.cpp
new file mode 100644
index 000000000..2a8436377
--- /dev/null
+++ b/components/autoconfig/src/nsReadConfig.cpp
@@ -0,0 +1,305 @@
+/* -*- 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 "nsReadConfig.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIAppStartup.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIAutoConfig.h"
+#include "nsIComponentManager.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIPromptService.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsToolkitCompsCID.h"
+#include "nsXPIDLString.h"
+#include "nsNetUtil.h"
+#include "prmem.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nspr.h"
+#include "nsXULAppAPI.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/Logging.h"
+
+using mozilla::LogLevel;
+
+extern mozilla::LazyLogModule MCD;
+
+extern nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length,
+ const char *filename,
+ bool bGlobalContext,
+ bool bCallbacks,
+ bool skipFirstLine);
+extern nsresult CentralizedAdminPrefManagerInit();
+extern nsresult CentralizedAdminPrefManagerFinish();
+
+
+static void DisplayError(void)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIPromptService> promptService = do_GetService("@mozilla.org/embedcomp/prompt-service;1");
+ if (!promptService)
+ return;
+
+ nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleService)
+ return;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://autoconfig/locale/autoconfig.properties",
+ getter_AddRefs(bundle));
+ if (!bundle)
+ return;
+
+ nsXPIDLString title;
+ rv = bundle->GetStringFromName(u"readConfigTitle", getter_Copies(title));
+ if (NS_FAILED(rv))
+ return;
+
+ nsXPIDLString err;
+ rv = bundle->GetStringFromName(u"readConfigMsg", getter_Copies(err));
+ if (NS_FAILED(rv))
+ return;
+
+ promptService->Alert(nullptr, title.get(), err.get());
+}
+
+// nsISupports Implementation
+
+NS_IMPL_ISUPPORTS(nsReadConfig, nsIReadConfig, nsIObserver)
+
+nsReadConfig::nsReadConfig() :
+ mRead(false)
+{
+}
+
+nsresult nsReadConfig::Init()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+
+ if (observerService) {
+ rv = observerService->AddObserver(this, NS_PREFSERVICE_READ_TOPIC_ID, false);
+ }
+ return(rv);
+}
+
+nsReadConfig::~nsReadConfig()
+{
+ CentralizedAdminPrefManagerFinish();
+}
+
+NS_IMETHODIMP nsReadConfig::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ nsresult rv = NS_OK;
+
+ if (!nsCRT::strcmp(aTopic, NS_PREFSERVICE_READ_TOPIC_ID)) {
+ rv = readConfigFile();
+ if (NS_FAILED(rv)) {
+ DisplayError();
+
+ nsCOMPtr<nsIAppStartup> appStartup =
+ do_GetService(NS_APPSTARTUP_CONTRACTID);
+ if (appStartup)
+ appStartup->Quit(nsIAppStartup::eAttemptQuit);
+ }
+ }
+ return rv;
+}
+
+
+nsresult nsReadConfig::readConfigFile()
+{
+ nsresult rv = NS_OK;
+ nsXPIDLCString lockFileName;
+ nsXPIDLCString lockVendor;
+ uint32_t fileNameLen = 0;
+
+ nsCOMPtr<nsIPrefBranch> defaultPrefBranch;
+ nsCOMPtr<nsIPrefService> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = prefService->GetDefaultBranch(nullptr, getter_AddRefs(defaultPrefBranch));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // This preference is set in the all.js or all-ns.js (depending whether
+ // running mozilla or netscp6)
+
+ rv = defaultPrefBranch->GetCharPref("general.config.filename",
+ getter_Copies(lockFileName));
+
+
+ MOZ_LOG(MCD, LogLevel::Debug, ("general.config.filename = %s\n", lockFileName.get()));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // This needs to be read only once.
+ //
+ if (!mRead) {
+ // Initiate the new JS Context for Preference management
+
+ rv = CentralizedAdminPrefManagerInit();
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Open and evaluate function calls to set/lock/unlock prefs
+ rv = openAndEvaluateJSFile("prefcalls.js", 0, false, false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mRead = true;
+ }
+ // If the lockFileName is nullptr return ok, because no lockFile will be used
+
+
+ // Once the config file is read, we should check that the vendor name
+ // is consistent By checking for the vendor name after reading the config
+ // file we allow for the preference to be set (and locked) by the creator
+ // of the cfg file meaning the file can not be renamed (successfully).
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefService->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t obscureValue = 0;
+ (void) defaultPrefBranch->GetIntPref("general.config.obscure_value", &obscureValue);
+ MOZ_LOG(MCD, LogLevel::Debug, ("evaluating .cfg file %s with obscureValue %d\n", lockFileName.get(), obscureValue));
+ rv = openAndEvaluateJSFile(lockFileName.get(), obscureValue, true, true);
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(MCD, LogLevel::Debug, ("error evaluating .cfg file %s %x\n", lockFileName.get(), rv));
+ return rv;
+ }
+
+ rv = prefBranch->GetCharPref("general.config.filename",
+ getter_Copies(lockFileName));
+ if (NS_FAILED(rv))
+ // There is NO REASON we should ever get here. This is POST reading
+ // of the config file.
+ return NS_ERROR_FAILURE;
+
+
+ rv = prefBranch->GetCharPref("general.config.vendor",
+ getter_Copies(lockVendor));
+ // If vendor is not nullptr, do this check
+ if (NS_SUCCEEDED(rv)) {
+
+ fileNameLen = strlen(lockFileName);
+
+ // lockVendor and lockFileName should be the same with the addtion of
+ // .cfg to the filename by checking this post reading of the cfg file
+ // this value can be set within the cfg file adding a level of security.
+
+ if (PL_strncmp(lockFileName, lockVendor, fileNameLen - 4) != 0)
+ return NS_ERROR_FAILURE;
+ }
+
+ // get the value of the autoconfig url
+ nsXPIDLCString urlName;
+ rv = prefBranch->GetCharPref("autoadmin.global_config_url",
+ getter_Copies(urlName));
+ if (NS_SUCCEEDED(rv) && !urlName.IsEmpty()) {
+
+ // Instantiating nsAutoConfig object if the pref is present
+ mAutoConfig = do_CreateInstance(NS_AUTOCONFIG_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = mAutoConfig->SetConfigURL(urlName);
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ }
+
+ return NS_OK;
+} // ReadConfigFile
+
+
+nsresult nsReadConfig::openAndEvaluateJSFile(const char *aFileName, int32_t obscureValue,
+ bool isEncoded,
+ bool isBinDir)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIInputStream> inStr;
+ if (isBinDir) {
+ nsCOMPtr<nsIFile> jsFile;
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR,
+ getter_AddRefs(jsFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = jsFile->AppendNative(nsDependentCString(aFileName));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), jsFile);
+ if (NS_FAILED(rv))
+ return rv;
+
+ } else {
+ nsAutoCString location("resource://gre/defaults/autoconfig/");
+ location += aFileName;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), location);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = channel->Open2(getter_AddRefs(inStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint64_t fs64;
+ uint32_t amt = 0;
+ rv = inStr->Available(&fs64);
+ if (NS_FAILED(rv))
+ return rv;
+ // PR_Malloc dones't support over 4GB
+ if (fs64 > UINT32_MAX)
+ return NS_ERROR_FILE_TOO_BIG;
+ uint32_t fs = (uint32_t)fs64;
+
+ char *buf = (char *)PR_Malloc(fs * sizeof(char));
+ if (!buf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = inStr->Read(buf, (uint32_t)fs, &amt);
+ NS_ASSERTION((amt == fs), "failed to read the entire configuration file!!");
+ if (NS_SUCCEEDED(rv)) {
+ if (obscureValue > 0) {
+
+ // Unobscure file by subtracting some value from every char.
+ for (uint32_t i = 0; i < amt; i++)
+ buf[i] -= obscureValue;
+ }
+ rv = EvaluateAdminConfigScript(buf, amt, aFileName,
+ false, true,
+ isEncoded ? true:false);
+ }
+ inStr->Close();
+ PR_Free(buf);
+
+ return rv;
+}
diff --git a/components/autoconfig/src/nsReadConfig.h b/components/autoconfig/src/nsReadConfig.h
new file mode 100644
index 000000000..66aa9e5f0
--- /dev/null
+++ b/components/autoconfig/src/nsReadConfig.h
@@ -0,0 +1,41 @@
+/* -*- 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/. */
+
+#ifndef nsReadConfig_h
+#define nsReadConfig_h
+
+#include "nsCOMPtr.h"
+#include "nsIReadConfig.h"
+#include "nsIAutoConfig.h"
+#include "nsIObserver.h"
+
+
+class nsReadConfig : public nsIReadConfig,
+ public nsIObserver
+{
+
+ public:
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREADCONFIG
+ NS_DECL_NSIOBSERVER
+
+ nsReadConfig();
+
+ nsresult Init();
+
+ protected:
+
+ virtual ~nsReadConfig();
+
+ nsresult readConfigFile();
+ nsresult openAndEvaluateJSFile(const char *aFileName, int32_t obscureValue,
+ bool isEncoded, bool isBinDir);
+ bool mRead;
+private:
+ nsCOMPtr<nsIAutoConfig> mAutoConfig;
+};
+
+#endif
diff --git a/components/autoconfig/src/prefcalls.js b/components/autoconfig/src/prefcalls.js
new file mode 100644
index 000000000..d0bbd538e
--- /dev/null
+++ b/components/autoconfig/src/prefcalls.js
@@ -0,0 +1,230 @@
+/* -*- tab-width: 4; indent-tabs-mode: nil; js-indent-level: 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 nsILDAPURL = Components.interfaces.nsILDAPURL;
+const LDAPURLContractID = "@mozilla.org/network/ldap-url;1";
+const nsILDAPSyncQuery = Components.interfaces.nsILDAPSyncQuery;
+const LDAPSyncQueryContractID = "@mozilla.org/ldapsyncquery;1";
+const nsIPrefService = Components.interfaces.nsIPrefService;
+const PrefServiceContractID = "@mozilla.org/preferences-service;1";
+
+var gVersion;
+var gIsUTF8;
+
+function getPrefBranch() {
+
+ var prefService = Components.classes[PrefServiceContractID]
+ .getService(nsIPrefService);
+ return prefService.getBranch(null);
+}
+
+function pref(prefName, value) {
+
+ try {
+ var prefBranch = getPrefBranch();
+
+ if (typeof value == "string") {
+ if (gIsUTF8) {
+ const nsISupportsString = Components.interfaces.nsISupportsString;
+ let string = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ string.data = value;
+ prefBranch.setComplexValue(prefName, nsISupportsString, string);
+ return;
+ }
+ prefBranch.setCharPref(prefName, value);
+ }
+ else if (typeof value == "number") {
+ prefBranch.setIntPref(prefName, value);
+ }
+ else if (typeof value == "boolean") {
+ prefBranch.setBoolPref(prefName, value);
+ }
+ }
+ catch(e) {
+ displayError("pref", e);
+ }
+}
+
+function defaultPref(prefName, value) {
+
+ try {
+ var prefService = Components.classes[PrefServiceContractID]
+ .getService(nsIPrefService);
+ var prefBranch = prefService.getDefaultBranch(null);
+ if (typeof value == "string") {
+ if (gIsUTF8) {
+ const nsISupportsString = Components.interfaces.nsISupportsString;
+ let string = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ string.data = value;
+ prefBranch.setComplexValue(prefName, nsISupportsString, string);
+ return;
+ }
+ prefBranch.setCharPref(prefName, value);
+ }
+ else if (typeof value == "number") {
+ prefBranch.setIntPref(prefName, value);
+ }
+ else if (typeof value == "boolean") {
+ prefBranch.setBoolPref(prefName, value);
+ }
+ }
+ catch(e) {
+ displayError("defaultPref", e);
+ }
+}
+
+function lockPref(prefName, value) {
+
+ try {
+ var prefBranch = getPrefBranch();
+
+ if (prefBranch.prefIsLocked(prefName))
+ prefBranch.unlockPref(prefName);
+
+ defaultPref(prefName, value);
+
+ prefBranch.lockPref(prefName);
+ }
+ catch(e) {
+ displayError("lockPref", e);
+ }
+}
+
+function unlockPref(prefName) {
+
+ try {
+
+ var prefBranch = getPrefBranch();
+ prefBranch.unlockPref(prefName);
+ }
+ catch(e) {
+ displayError("unlockPref", e);
+ }
+}
+
+function getPref(prefName) {
+
+ try {
+ var prefBranch = getPrefBranch();
+
+ switch (prefBranch.getPrefType(prefName)) {
+
+ case prefBranch.PREF_STRING:
+ if (gIsUTF8) {
+ const nsISupportsString = Components.interfaces.nsISupportsString;
+ let string = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ return prefBranch.getComplexValue(prefName, nsISupportsString).data;
+ }
+ return prefBranch.getCharPref(prefName);
+
+ case prefBranch.PREF_INT:
+ return prefBranch.getIntPref(prefName);
+
+ case prefBranch.PREF_BOOL:
+ return prefBranch.getBoolPref(prefName);
+ default:
+ return null;
+ }
+ }
+ catch(e) {
+ displayError("getPref", e);
+ }
+}
+
+function clearPref(prefName) {
+
+ try {
+ var prefBranch = getPrefBranch();
+ prefBranch.clearUserPref(prefName);
+ }
+ catch(e) {
+ }
+
+}
+
+function setLDAPVersion(version) {
+ gVersion = version;
+}
+
+
+function getLDAPAttributes(host, base, filter, attribs, isSecure) {
+
+ try {
+ var urlSpec = "ldap" + (isSecure ? "s" : "") + "://" + host + (isSecure ? ":636" : "") + "/" + base + "?" + attribs + "?sub?" +
+ filter;
+
+ var url = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService)
+ .newURI(urlSpec, null, null)
+ .QueryInterface(Components.interfaces.nsILDAPURL);
+
+ var ldapquery = Components.classes[LDAPSyncQueryContractID]
+ .createInstance(nsILDAPSyncQuery);
+ // default to LDAP v3
+ if (!gVersion)
+ gVersion = Components.interfaces.nsILDAPConnection.VERSION3
+ // user supplied method
+ processLDAPValues(ldapquery.getQueryResults(url, gVersion));
+ }
+ catch(e) {
+ displayError("getLDAPAttibutes", e);
+ }
+}
+
+function getLDAPValue(str, key) {
+
+ try {
+ if (str == null || key == null)
+ return null;
+
+ var search_key = "\n" + key + "=";
+
+ var start_pos = str.indexOf(search_key);
+ if (start_pos == -1)
+ return null;
+
+ start_pos += search_key.length;
+
+ var end_pos = str.indexOf("\n", start_pos);
+ if (end_pos == -1)
+ end_pos = str.length;
+
+ return str.substring(start_pos, end_pos);
+ }
+ catch(e) {
+ displayError("getLDAPValue", e);
+ }
+}
+
+function displayError(funcname, message) {
+
+ try {
+ var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
+ .getService(Components.interfaces.nsIStringBundleService)
+ .createBundle("chrome://autoconfig/locale/autoconfig.properties");
+
+ var title = bundle.GetStringFromName("autoConfigTitle");
+ var msg = bundle.formatStringFromName("autoConfigMsg", [funcname], 1);
+ promptService.alert(null, title, msg + " " + message);
+ }
+ catch(e) { }
+}
+
+function getenv(name) {
+ try {
+ var environment = Components.classes["@mozilla.org/process/environment;1"].
+ getService(Components.interfaces.nsIEnvironment);
+ return environment.get(name);
+ }
+ catch(e) {
+ displayError("getEnvironment", e);
+ }
+}
+
diff --git a/components/bindings/content/autocomplete.xml b/components/bindings/content/autocomplete.xml
new file mode 100644
index 000000000..885eb2eab
--- /dev/null
+++ b/components/bindings/content/autocomplete.xml
@@ -0,0 +1,2520 @@
+<?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="autocompleteBindings"
+ 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="autocomplete" role="xul:combobox"
+ extends="chrome://global/content/bindings/textbox.xml#textbox">
+ <resources>
+ <stylesheet src="chrome://global/content/autocomplete.css"/>
+ <stylesheet src="chrome://global/skin/autocomplete.css"/>
+ </resources>
+
+ <content sizetopopup="pref">
+ <xul:hbox class="autocomplete-textbox-container" flex="1" xbl:inherits="focused">
+ <children includes="image|deck|stack|box">
+ <xul:image class="autocomplete-icon" allowevents="true"/>
+ </children>
+
+ <xul:hbox anonid="textbox-input-box" class="textbox-input-box" flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
+ <children/>
+ <html:input anonid="input" class="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="autocomplete-history-dropmarker"
+ allowevents="true"
+ xbl:inherits="open,enablehistory,parentfocused=focused"/>
+
+ <xul:popupset anonid="popupset" class="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="_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">null</field>
+ <property name="popup" readonly="true">
+ <getter><![CDATA[
+ // Memoize the result in a field rather than replacing this property,
+ // so that it can be reset along with the binding.
+ if (this._popup) {
+ return this._popup;
+ }
+
+ 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;
+
+ return this._popup = popup;
+ ]]></getter>
+ </property>
+
+ <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>
+
+ <property name="PrivateBrowsingUtils" readonly="true">
+ <getter><![CDATA[
+ let module = {};
+ Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", module);
+ Object.defineProperty(this, "PrivateBrowsingUtils", {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: module.PrivateBrowsingUtils
+ });
+ return module.PrivateBrowsingUtils;
+ ]]></getter>
+ </property>
+
+ <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>
+
+ <method name="setTextValueWithReason">
+ <parameter name="aValue"/>
+ <parameter name="aReason"/>
+ <body><![CDATA[
+ if (aReason == Components.interfaces.nsIAutoCompleteInput
+ .TEXTVALUE_REASON_COMPLETEDEFAULT) {
+ this._disableTrim = true;
+ }
+ this.textValue = aValue;
+ this._disableTrim = false;
+ ]]></body>
+ </method>
+
+ <property name="textValue">
+ <getter><![CDATA[
+ if (typeof this.onBeforeTextValueGet == "function") {
+ let result = this.onBeforeTextValueGet();
+ if (result) {
+ return result.value;
+ }
+ }
+ return this.value;
+ ]]></getter>
+ <setter><![CDATA[
+ if (typeof this.onBeforeTextValueSet == "function")
+ val = this.onBeforeTextValueSet(val);
+
+ this.value = val;
+
+ // 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="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">
+ <parameter name="event"/>
+ <body><![CDATA[
+ let rv = false;
+ if (this._textEnteredHandler) {
+ rv = this._textEnteredHandler(event);
+ }
+ 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;
+
+ // 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 = /Mac/.test(navigator.platform) ? 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 (/Mac/.test(navigator.platform)) {
+ // Prevent the default action, since it will beep on Mac
+ if (aEvent.metaKey)
+ aEvent.preventDefault();
+ }
+ if (this.mController.selection) {
+ this._selectionDetails = {
+ index: this.mController.selection.currentIndex,
+ kind: "key"
+ };
+ }
+ cancel = this.handleEnter(aEvent);
+ break;
+ case KeyEvent.DOM_VK_DELETE:
+ if (/Mac/.test(navigator.platform) && !aEvent.shiftKey) {
+ break;
+ }
+ cancel = this.handleDelete();
+ break;
+ case KeyEvent.DOM_VK_BACK_SPACE:
+ if (/Mac/.test(navigator.platform) && 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 (!/Mac/.test(navigator.platform)) {
+ this.toggleHistoryPopup();
+ }
+ break;
+ }
+
+ if (cancel) {
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+
+ return true;
+ ]]></body>
+ </method>
+
+ <method name="handleEnter">
+ <parameter name="event"/>
+ <body><![CDATA[
+ return this.mController.handleEnter(false, event || null);
+ ]]></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"><![CDATA[
+ this.attachController();
+ if (window.gBrowser && window.gBrowser.selectedBrowser.hasAttribute("usercontextid")) {
+ this.userContextId = parseInt(window.gBrowser.selectedBrowser.getAttribute("usercontextid"));
+ } else {
+ this.userContextId = 0;
+ }
+ ]]></handler>
+
+ <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="autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-base-popup">
+ <resources>
+ <stylesheet src="chrome://global/content/autocomplete.css"/>
+ <stylesheet src="chrome://global/skin/tree.css"/>
+ <stylesheet src="chrome://global/skin/autocomplete.css"/>
+ </resources>
+
+ <content ignorekeys="true" level="top" consumeoutsideclicks="never">
+ <xul:tree anonid="tree" class="autocomplete-tree plain" hidecolumnpicker="true" flex="1" seltype="single">
+ <xul:treecols anonid="treecols">
+ <xul:treecol id="treecolAutoCompleteValue" class="autocomplete-treecol" flex="1" overflow="true"/>
+ </xul:treecols>
+ <xul:treechildren class="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", "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[
+ if (!this.tree.view) {
+ // We don't have a view? return "no selection".
+ val = -1;
+ } else {
+ 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="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, aEvent);
+ ]]></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="autocomplete-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-base-popup">
+ <resources>
+ <stylesheet src="chrome://global/content/autocomplete.css"/>
+ <stylesheet src="chrome://global/skin/autocomplete.css"/>
+ </resources>
+
+ <content ignorekeys="true" level="top" consumeoutsideclicks="never">
+ <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox" flex="1"/>
+ <xul:hbox>
+ <children/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIAutoCompletePopup">
+ <field name="_currentIndex">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;
+
+ if (typeof this._onSearchBegin == "function") {
+ this._onSearchBegin();
+ }
+ ]]></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) {
+ // It's possible that the panel is hidden initially
+ // to avoid impacting startup / new window performance
+ aInput.popup.hidden = false;
+
+ 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);
+ }
+
+ if (this.mPopupOpen) {
+ delete this._adjustHeightOnPopupShown;
+ this._adjustHeightTimeout = setTimeout(() => this.adjustHeight(), 0);
+ } else {
+ this._adjustHeightOnPopupShown = true;
+ }
+
+ 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) {
+ let firstRowRect = rows[0].getBoundingClientRect();
+ if (this._rlbPadding == undefined) {
+ let style = window.getComputedStyle(this.richlistbox);
+
+ let transition = style.transitionProperty;
+ this._rlbAnimated = transition && transition != "none";
+
+ let paddingTop = parseInt(style.paddingTop) || 0;
+ let paddingBottom = parseInt(style.paddingBottom) || 0;
+ this._rlbPadding = paddingTop + paddingBottom;
+ }
+
+ if (numRows > this.maxRows) {
+ // Set a fixed max-height to avoid flicker when growing the panel.
+ let lastVisibleRowRect = rows[this.maxRows - 1].getBoundingClientRect();
+ let visibleHeight = lastVisibleRowRect.bottom - firstRowRect.top;
+ this.richlistbox.style.maxHeight =
+ visibleHeight + this._rlbPadding + "px";
+ }
+
+ // The class `forceHandleUnderflow` is for the item might need to
+ // handle OverUnderflow or Overflow when the height of an item will
+ // be changed dynamically.
+ for (let i = 0; i < numRows; i++) {
+ if (rows[i].classList.contains("forceHandleUnderflow")) {
+ rows[i].handleOverUnderflow();
+ }
+ }
+
+ let lastRowRect = rows[numRows - 1].getBoundingClientRect();
+ // Calculate the height to have the first row to last row shown
+ height = lastRowRect.bottom - firstRowRect.top +
+ this._rlbPadding;
+ }
+
+ 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];
+ item.setAttribute("dir", this.style.direction);
+
+ // 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)) {
+ // Additionally, if the item is a searchengine action, then it
+ // should only be reused if the engine name is the same as the
+ // popup's override engine name, if any.
+ let action = item._parseActionUrl(url);
+ if (!action ||
+ action.type != "searchengine" ||
+ !this.overrideSearchEngineName ||
+ action.params.engineName == this.overrideSearchEngineName) {
+ item.collapsed = false;
+ // Call adjustSiteIconStart only after setting collapsed=
+ // false. The calculations it does may be wrong otherwise.
+ item.adjustSiteIconStart(this._siteIconStart);
+ // The popup may have changed size between now and the last
+ // time the item was shown, so always handle over/underflow.
+ item.handleOverUnderflow();
+ this._currentIndex++;
+ continue;
+ }
+ }
+ }
+ else {
+ // need to create a new item
+ item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
+ item.setAttribute("dir", this.style.direction);
+ }
+
+ // 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("originaltype", 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 = "autocomplete-richlistitem";
+ this.richlistbox.appendChild(item);
+ }
+
+ // The binding may have not been applied yet.
+ setTimeout(() => {
+ let changed = item.adjustSiteIconStart(this._siteIconStart);
+ if (changed) {
+ item.handleOverUnderflow();
+ }
+ }, 0);
+
+ 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>
+
+ <!-- The x-coordinate relative to the leading edge of the window of the
+ items' site icons (favicons). -->
+ <property name="siteIconStart"
+ onget="return this._siteIconStart;">
+ <setter>
+ <![CDATA[
+ if (val != this._siteIconStart) {
+ this._siteIconStart = val;
+ for (let item of this.richlistbox.childNodes) {
+ let changed = item.adjustSiteIconStart(val);
+ if (changed) {
+ item.handleOverUnderflow();
+ }
+ }
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="overflowPadding"
+ onget="return Number(this.getAttribute('overflowpadding'))"
+ readonly="true" />
+
+ <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>
+ <handlers>
+ <handler event="popupshown">
+ <![CDATA[
+ if (this._adjustHeightOnPopupShown) {
+ delete this._adjustHeightOnPopupShown;
+ this.adjustHeight();
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="autocomplete-richlistitem-insecure-field" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem">
+ <content align="center"
+ onoverflow="this._onOverflow();"
+ onunderflow="this._onUnderflow();">
+ <xul:image anonid="type-icon"
+ class="ac-type-icon"
+ xbl:inherits="selected,current,type"/>
+ <xul:image anonid="site-icon"
+ class="ac-site-icon"
+ xbl:inherits="src=image,selected,type"/>
+ <xul:vbox class="ac-title"
+ align="left"
+ xbl:inherits="">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="title-text"
+ class="ac-title-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:vbox>
+ <xul:hbox anonid="tags"
+ class="ac-tags"
+ align="center"
+ xbl:inherits="selected">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="tags-text"
+ class="ac-tags-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:hbox>
+ <xul:hbox anonid="separator"
+ class="ac-separator"
+ align="center"
+ xbl:inherits="selected,actiontype,type">
+ <xul:description class="ac-separator-text">—</xul:description>
+ </xul:hbox>
+ <xul:hbox class="ac-url"
+ align="center"
+ xbl:inherits="selected,actiontype">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="url-text"
+ class="ac-url-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:hbox>
+ <xul:hbox class="ac-action"
+ align="center"
+ xbl:inherits="selected,actiontype">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="action-text"
+ class="ac-action-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:hbox>
+ </content>
+
+ <handlers>
+ <handler event="click" button="0"><![CDATA[
+ let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ window.openUILinkIn(baseURL + "insecure-password", "tab", {
+ relatedToCurrent: true,
+ });
+ ]]></handler>
+ </handlers>
+
+ <implementation>
+ <constructor><![CDATA[
+ // Unlike other autocomplete items, the height of the insecure warning
+ // increases by wrapping. So "forceHandleUnderflow" is for container to
+ // recalculate an item's height and width.
+ this.classList.add("forceHandleUnderflow");
+ ]]></constructor>
+
+ <property name="_learnMoreString">
+ <getter><![CDATA[
+ if (!this.__learnMoreString) {
+ this.__learnMoreString =
+ Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties").
+ GetStringFromName("insecureFieldWarningLearnMore");
+ }
+ return this.__learnMoreString;
+ ]]></getter>
+ </property>
+
+ <method name="_getSearchTokens">
+ <parameter name="aSearch"/>
+ <body>
+ <![CDATA[
+ return [this._learnMoreString.toLowerCase()];
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="autocomplete-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+
+ <content align="center"
+ onoverflow="this._onOverflow();"
+ onunderflow="this._onUnderflow();">
+ <xul:image anonid="type-icon"
+ class="ac-type-icon"
+ xbl:inherits="selected,current,type"/>
+ <xul:image anonid="site-icon"
+ class="ac-site-icon"
+ xbl:inherits="src=image,selected,type"/>
+ <xul:hbox class="ac-title"
+ align="center"
+ xbl:inherits="selected">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="title-text"
+ class="ac-title-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:hbox>
+ <xul:hbox anonid="tags"
+ class="ac-tags"
+ align="center"
+ xbl:inherits="selected">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="tags-text"
+ class="ac-tags-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:hbox>
+ <xul:hbox anonid="separator"
+ class="ac-separator"
+ align="center"
+ xbl:inherits="selected,actiontype,type">
+ <xul:description class="ac-separator-text">—</xul:description>
+ </xul:hbox>
+ <xul:hbox class="ac-url"
+ align="center"
+ xbl:inherits="selected,actiontype">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="url-text"
+ class="ac-url-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:hbox>
+ <xul:hbox class="ac-action"
+ align="center"
+ xbl:inherits="selected,actiontype">
+ <xul:description class="ac-text-overflow-container">
+ <xul:description anonid="action-text"
+ class="ac-action-text"
+ xbl:inherits="selected"/>
+ </xul:description>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <constructor>
+ <![CDATA[
+ this._typeIcon = document.getAnonymousElementByAttribute(
+ this, "anonid", "type-icon"
+ );
+ this._siteIcon = document.getAnonymousElementByAttribute(
+ this, "anonid", "site-icon"
+ );
+ this._titleText = document.getAnonymousElementByAttribute(
+ this, "anonid", "title-text"
+ );
+ this._tags = document.getAnonymousElementByAttribute(
+ this, "anonid", "tags"
+ );
+ this._tagsText = document.getAnonymousElementByAttribute(
+ this, "anonid", "tags-text"
+ );
+ this._separator = document.getAnonymousElementByAttribute(
+ this, "anonid", "separator"
+ );
+ this._urlText = document.getAnonymousElementByAttribute(
+ this, "anonid", "url-text"
+ );
+ this._actionText = document.getAnonymousElementByAttribute(
+ this, "anonid", "action-text"
+ );
+ 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>
+
+ <field name="_inOverflow">false</field>
+
+ <method name="_onOverflow">
+ <body>
+ <![CDATA[
+ this._inOverflow = true;
+ this._handleOverflow();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_onUnderflow">
+ <body>
+ <![CDATA[
+ this._inOverflow = false;
+ this._handleOverflow();
+ ]]>
+ </body>
+ </method>
+
+ <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
+ if (!aDescriptionElement) {
+ return;
+ }
+ 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);
+
+ this._appendDescriptionSpans(indices, aText, aDescriptionElement,
+ aDescriptionElement);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_appendDescriptionSpans">
+ <parameter name="indices"/>
+ <parameter name="text"/>
+ <parameter name="spansParentElement"/>
+ <parameter name="descriptionElement"/>
+ <body>
+ <![CDATA[
+ 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 spanText = text.substr(start, next - start);
+ start = next;
+
+ if (i % 2 == 0) {
+ // Emphasize the text for even indices
+ let span = spansParentElement.appendChild(
+ document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
+ this._setUpEmphasisSpan(span, descriptionElement);
+ span.textContent = spanText;
+ } else {
+ // Otherwise, it's plain text
+ spansParentElement.appendChild(document.createTextNode(spanText));
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_setUpTags">
+ <parameter name="tags"/>
+ <body>
+ <![CDATA[
+ while (this._tagsText.hasChildNodes()) {
+ this._tagsText.firstChild.remove();
+ }
+
+ let anyTagsMatch = false;
+
+ // Include only tags that match the search string.
+ for (let tag of tags) {
+ // Check if the tag matches the search string.
+ let search = this.getAttribute("text");
+ let tokens = this._getSearchTokens(search);
+ let indices = this._getBoundaryIndices(tag, tokens);
+
+ if (indices.length == 2 &&
+ indices[0] == 0 &&
+ indices[1] == tag.length) {
+ // The tag doesn't match the search string, so don't include it.
+ continue;
+ }
+
+ anyTagsMatch = true;
+
+ let tagSpan =
+ document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ tagSpan.classList.add("ac-tag");
+ this._tagsText.appendChild(tagSpan);
+
+ this._appendDescriptionSpans(indices, tag, tagSpan, this._tagsText);
+ }
+
+ return anyTagsMatch;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_setUpEmphasisSpan">
+ <parameter name="aSpan"/>
+ <parameter name="aDescriptionElement"/>
+ <body>
+ <![CDATA[
+ aSpan.classList.add("ac-emphasize-text");
+ switch (aDescriptionElement) {
+ case this._titleText:
+ aSpan.classList.add("ac-emphasize-text-title");
+ break;
+ case this._tagsText:
+ aSpan.classList.add("ac-emphasize-text-tag");
+ break;
+ case this._urlText:
+ aSpan.classList.add("ac-emphasize-text-url");
+ break;
+ case this._actionText:
+ aSpan.classList.add("ac-emphasize-text-action");
+ break;
+ }
+ ]]>
+ </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":
+ this._setUpEmphasisSpan(span, aDescriptionElement);
+ 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 popup = this.parentNode.parentNode;
+ if (!popup.popupOpen) {
+ // Removing the max-width and resetting it later when overflow is
+ // handled is jarring when the item is visible, so skip this when
+ // the popup is open.
+ this._removeMaxWidths();
+ }
+
+ let title = this.getAttribute("title");
+ let titleLooksLikeUrl = false;
+
+ let displayUrl;
+ let originalUrl = this.getAttribute("url");
+ let emphasiseUrl = true;
+
+ let type = this.getAttribute("originaltype");
+ let types = new Set(type.split(/\s+/));
+ let initialTypes = new Set(types);
+ // Remove types that should ultimately not be in the `type` string.
+ types.delete("action");
+ types.delete("autofill");
+ types.delete("heuristic");
+ type = [...types][0] || "";
+
+ let action;
+
+ if (initialTypes.has("autofill")) {
+ // Treat autofills as visiturl actions.
+ action = {
+ type: "visiturl",
+ params: {
+ url: originalUrl,
+ },
+ };
+ }
+
+ this.removeAttribute("actiontype");
+ this.classList.remove("overridable-action");
+
+ // If the type includes an action, set up the item appropriately.
+ if (initialTypes.has("action") || action) {
+ action = 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("switchToTab2");
+ this._setUpDescription(this._actionText, desc, true);
+ } else if (action.type == "remotetab") {
+ displayUrl = this._unescapeUrl(action.params.url);
+ let desc = action.params.deviceName;
+ this._setUpDescription(this._actionText, 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;
+
+ // Override the engine name if the popup defines an override.
+ let override = popup.overrideSearchEngineName;
+ if (override && override != engineName) {
+ engineName = override;
+ action.params.engineName = override;
+ let newURL =
+ PlacesUtils.mozActionURI(action.type, action.params);
+ this.setAttribute("url", newURL);
+ }
+
+ let engineStr =
+ this._stringBundle.formatStringFromName("searchWithEngine",
+ [engineName], 1);
+ this._setUpDescription(this._actionText, engineStr, true);
+
+ // 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, ""],
+ ];
+ }
+ 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 && !initialTypes.has("favicon")) {
+ this.removeAttribute("image");
+ }
+ } else if (action.type == "visiturl") {
+ emphasiseUrl = false;
+ displayUrl = this._unescapeUrl(action.params.url);
+ title = displayUrl;
+ titleLooksLikeUrl = true;
+ let visitStr = this._stringBundle.GetStringFromName("visit");
+ this._setUpDescription(this._actionText, visitStr, true);
+ } else if (action.type == "extension") {
+ let content = action.params.content;
+ displayUrl = content;
+ this._setUpDescription(this._actionText, content, true);
+ }
+ }
+
+ if (!displayUrl) {
+ let input = popup.input;
+ let url = typeof(input.trimValue) == "function" ?
+ input.trimValue(originalUrl) :
+ originalUrl;
+ displayUrl = this._unescapeUrl(url);
+ }
+ // For performance reasons we may want to limit the displayUrl size.
+ if (popup.textRunsMaxLen) {
+ displayUrl = displayUrl.substr(0, popup.textRunsMaxLen);
+ }
+ this.setAttribute("displayurl", displayUrl);
+
+ // Show the domain as the title if we don't have a title.
+ if (!title) {
+ title = displayUrl;
+ titleLooksLikeUrl = true;
+ try {
+ let uri = Services.io.newURI(originalUrl, null, null);
+ // Not all valid URLs have a domain.
+ if (uri.host)
+ title = uri.host;
+ } catch (e) {}
+ }
+
+ this._tags.setAttribute("empty", "true");
+
+ if (type == "tag" || type == "bookmark-tag") {
+ // 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(/\s*,\s*/).sort((a, b) => {
+ return a.localeCompare(a);
+ });
+
+ let anyTagsMatch = this._setUpTags(sortedTags);
+ if (anyTagsMatch) {
+ this._tags.removeAttribute("empty");
+ }
+ if (type == "bookmark-tag") {
+ type = "bookmark";
+ }
+ } else if (type == "keyword") {
+ // Note that this is a moz-action with action.type == keyword.
+ emphasiseUrl = false;
+ let keywordArg = this.getAttribute("text").replace(/^[^\s]+\s*/, "");
+ if (!keywordArg) {
+ // Treat keyword searches without arguments as visiturl actions.
+ type = "visiturl";
+ this.setAttribute("actiontype", "visiturl");
+ let visitStr = this._stringBundle.GetStringFromName("visit");
+ this._setUpDescription(this._actionText, visitStr, true);
+ } else {
+ let pairs = [[title, ""], [keywordArg, "match"]];
+ let interpStr =
+ this._stringBundle.GetStringFromName("bookmarkKeywordSearch");
+ title = this._generateEmphasisPairs(interpStr, pairs);
+ // The action box will be visible since this is a moz-action, but
+ // we want it to appear as if it were not visible, so set its text
+ // to the empty string.
+ this._setUpDescription(this._actionText, "", false);
+ }
+ }
+
+ this.setAttribute("type", type);
+
+ if (titleLooksLikeUrl) {
+ this._titleText.setAttribute("lookslikeurl", "true");
+ } else {
+ this._titleText.removeAttribute("lookslikeurl");
+ }
+
+ if (Array.isArray(title)) {
+ // For performance reasons we may want to limit the title size.
+ if (popup.textRunsMaxLen) {
+ title = title.map(t => t.substr(0, popup.textRunsMaxLen));
+ }
+ this._setUpEmphasisedSections(this._titleText, title);
+ } else {
+ // For performance reasons we may want to limit the title size.
+ if (popup.textRunsMaxLen) {
+ title = title.substr(0, popup.textRunsMaxLen);
+ }
+ this._setUpDescription(this._titleText, title, false);
+ }
+ this._setUpDescription(this._urlText, displayUrl, !emphasiseUrl);
+
+ if (this._inOverflow) {
+ this._handleOverflow();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_removeMaxWidths">
+ <body>
+ <![CDATA[
+ this._titleText.style.removeProperty("max-width");
+ this._tagsText.style.removeProperty("max-width");
+ this._urlText.style.removeProperty("max-width");
+ this._actionText.style.removeProperty("max-width");
+ ]]>
+ </body>
+ </method>
+
+ <!-- Sets the x-coordinate of the leading edge of the site icon (favicon)
+ relative the the leading edge of the window.
+ @param newStart The new x-coordinate, relative to the leading edge of
+ the window. Pass undefined to reset the icon's position to
+ whatever is specified in CSS.
+ @return True if the icon's position changed, false if not. -->
+ <method name="adjustSiteIconStart">
+ <parameter name="newStart"/>
+ <body>
+ <![CDATA[
+ if (typeof(newStart) != "number") {
+ this._typeIcon.style.removeProperty("margin-inline-start");
+ return true;
+ }
+ let rect = this._siteIcon.getBoundingClientRect();
+ let dir = this.getAttribute("dir");
+ let delta = dir == "rtl" ? rect.right - newStart
+ : newStart - rect.left;
+ let px = this._typeIcon.style.marginInlineStart;
+ if (!px) {
+ // Allow margin-inline-start not to be specified in CSS initially.
+ let style = window.getComputedStyle(this._typeIcon);
+ px = dir == "rtl" ? style.marginRight : style.marginLeft;
+ }
+ let typeIconStart = Number(px.substr(0, px.length - 2));
+ this._typeIcon.style.marginInlineStart = (typeIconStart + delta) + "px";
+ return delta > 0;
+ ]]>
+ </body>
+ </method>
+
+ <!-- This method truncates the displayed strings as necessary. -->
+ <method name="_handleOverflow">
+ <body><![CDATA[
+ let itemRect = this.parentNode.getBoundingClientRect();
+ let titleRect = this._titleText.getBoundingClientRect();
+ let tagsRect = this._tagsText.getBoundingClientRect();
+ let separatorRect = this._separator.getBoundingClientRect();
+ let urlRect = this._urlText.getBoundingClientRect();
+ let actionRect = this._actionText.getBoundingClientRect();
+ let separatorURLActionWidth =
+ separatorRect.width + Math.max(urlRect.width, actionRect.width);
+
+ // Total width for the title and URL/action is the width of the item
+ // minus the start of the title text minus a little optional extra padding.
+ // This extra padding amount is basically arbitrary but keeps the text
+ // from getting too close to the popup's edge.
+ let dir = this.getAttribute("dir");
+ let titleStart = dir == "rtl" ? itemRect.right - titleRect.right
+ : titleRect.left - itemRect.left;
+
+ let popup = this.parentNode.parentNode;
+ let itemWidth = itemRect.width - titleStart - popup.overflowPadding;
+
+ if (this._tags.hasAttribute("empty")) {
+ // The tags box is not displayed in this case.
+ tagsRect.width = 0;
+ }
+
+ let titleTagsWidth = titleRect.width + tagsRect.width;
+ if (titleTagsWidth + separatorURLActionWidth > itemWidth) {
+ // Title + tags + URL/action overflows the item width.
+
+ // The percentage of the item width allocated to the title and tags.
+ let titleTagsPct = 0.66;
+
+ let titleTagsAvailable = itemWidth - separatorURLActionWidth;
+ let titleTagsMaxWidth = Math.max(
+ titleTagsAvailable,
+ itemWidth * titleTagsPct
+ );
+ if (titleTagsWidth > titleTagsMaxWidth) {
+ // Title + tags overflows the max title + tags width.
+
+ // The percentage of the title + tags width allocated to the
+ // title.
+ let titlePct = 0.33;
+
+ let titleAvailable = titleTagsMaxWidth - tagsRect.width;
+ let titleMaxWidth = Math.max(
+ titleAvailable,
+ titleTagsMaxWidth * titlePct
+ );
+ let tagsAvailable = titleTagsMaxWidth - titleRect.width;
+ let tagsMaxWidth = Math.max(
+ tagsAvailable,
+ titleTagsMaxWidth * (1 - titlePct)
+ );
+ this._titleText.style.maxWidth = titleMaxWidth + "px";
+ this._tagsText.style.maxWidth = tagsMaxWidth + "px";
+ }
+ let urlActionMaxWidth = Math.max(
+ itemWidth - titleTagsWidth,
+ itemWidth * (1 - titleTagsPct)
+ );
+ urlActionMaxWidth -= separatorRect.width;
+ this._urlText.style.maxWidth = urlActionMaxWidth + "px";
+ this._actionText.style.maxWidth = urlActionMaxWidth + "px";
+ }
+ ]]></body>
+ </method>
+
+ <method name="handleOverUnderflow">
+ <body>
+ <![CDATA[
+ this._removeMaxWidths();
+ this._handleOverflow();
+ ]]>
+ </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 may happen for legacy
+ // search components.
+ action.params = {
+ url: params,
+ }
+ }
+
+ return action;
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="autocomplete-tree" extends="chrome://global/content/bindings/tree.xml#tree">
+ <content>
+ <children includes="treecols"/>
+ <xul:treerows class="autocomplete-treerows tree-rows" xbl:inherits="hidescrollbar" flex="1">
+ <children/>
+ </xul:treerows>
+ </content>
+ </binding>
+
+ <binding id="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="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="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="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/components/bindings/content/browser.xml b/components/bindings/content/browser.xml
new file mode 100644
index 000000000..712a11b73
--- /dev/null
+++ b/components/bindings/content/browser.xml
@@ -0,0 +1,1507 @@
+<?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="browserBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="browser" extends="xul:browser" role="outerdoc">
+ <content clickthrough="never">
+ <children/>
+ </content>
+ <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIBrowser">
+ <property name="autoscrollEnabled">
+ <getter>
+ <![CDATA[
+ if (this.getAttribute("autoscroll") == "false")
+ return false;
+
+ return this.mPrefs.getBoolPref("general.autoScroll", true);
+ ]]>
+ </getter>
+ </property>
+
+ <property name="canGoBack"
+ onget="return this.webNavigation.canGoBack;"
+ readonly="true"/>
+
+ <property name="canGoForward"
+ onget="return this.webNavigation.canGoForward;"
+ readonly="true"/>
+
+ <method name="_wrapURIChangeCall">
+ <parameter name="fn"/>
+ <body>
+ <![CDATA[
+ this.inLoadURI = true;
+ try {
+ fn();
+ } finally {
+ this.inLoadURI = false;
+ }
+ ]]>
+ </body>
+ </method>
+
+
+ <method name="goBack">
+ <body>
+ <![CDATA[
+ var webNavigation = this.webNavigation;
+ if (webNavigation.canGoBack)
+ this._wrapURIChangeCall(() => webNavigation.goBack());
+ ]]>
+ </body>
+ </method>
+
+ <method name="goForward">
+ <body>
+ <![CDATA[
+ var webNavigation = this.webNavigation;
+ if (webNavigation.canGoForward)
+ this._wrapURIChangeCall(() => webNavigation.goForward());
+ ]]>
+ </body>
+ </method>
+
+ <method name="reload">
+ <body>
+ <![CDATA[
+ const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
+ const flags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ this.reloadWithFlags(flags);
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadWithFlags">
+ <parameter name="aFlags"/>
+ <body>
+ <![CDATA[
+ this.webNavigation.reload(aFlags);
+ ]]>
+ </body>
+ </method>
+
+ <method name="stop">
+ <body>
+ <![CDATA[
+ const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
+ const flags = nsIWebNavigation.STOP_ALL;
+ this.webNavigation.stop(flags);
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURI">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <body>
+ <![CDATA[
+ const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
+ const flags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ this._wrapURIChangeCall(() =>
+ this.loadURIWithFlags(aURI, flags, 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[
+ if (!aURI)
+ aURI = "about:blank";
+
+ var aReferrerPolicy = Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT;
+ var aTriggeringPrincipal;
+
+ // Check for loadURIWithFlags(uri, { ... });
+ var params = arguments[1];
+ if (params && typeof(params) == "object") {
+ aFlags = params.flags;
+ aReferrerURI = params.referrerURI;
+ if ('referrerPolicy' in params) {
+ aReferrerPolicy = params.referrerPolicy;
+ }
+ if ("triggeringPrincipal" in params) {
+ aTriggeringPrincipal = params.triggeringPrincipal;
+ }
+ aCharset = params.charset;
+ aPostData = params.postData;
+ }
+
+ this._wrapURIChangeCall(() =>
+ this.webNavigation.loadURIWithOptions(
+ aURI, aFlags, aReferrerURI, aReferrerPolicy,
+ aPostData, null, null, aTriggeringPrincipal));
+ ]]>
+ </body>
+ </method>
+
+ <method name="goHome">
+ <body>
+ <![CDATA[
+ try {
+ this.loadURI(this.homePage);
+ }
+ catch (e) {
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="homePage">
+ <getter>
+ <![CDATA[
+ var uri;
+
+ if (this.hasAttribute("homepage"))
+ uri = this.getAttribute("homepage");
+ else
+ uri = "http://www.mozilla.org/"; // widget pride
+
+ return uri;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this.setAttribute("homepage", val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="gotoIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(aIndex));
+ ]]>
+ </body>
+ </method>
+
+ <property name="currentURI" readonly="true">
+ <getter><![CDATA[
+ if (this.webNavigation) {
+ return this.webNavigation.currentURI;
+ }
+ return null;
+ ]]>
+ </getter>
+ </property>
+
+ <!--
+ Used by session restore to ensure that currentURI is set so
+ that switch-to-tab works before the tab is fully
+ restored. This function also invokes onLocationChanged
+ listeners in tabbrowser.xml.
+ -->
+ <method name="_setCurrentURI">
+ <parameter name="aURI"/>
+ <body><![CDATA[
+ this.docShell.setCurrentURI(aURI);
+ ]]></body>
+ </method>
+
+ <property name="documentURI"
+ onget="return this.contentDocument.documentURIObject;"
+ readonly="true"/>
+
+ <property name="documentContentType"
+ onget="return this.contentDocument ? this.contentDocument.contentType : null;"
+ readonly="true"/>
+
+ <property name="preferences"
+ onget="return this.mPrefs.QueryInterface(Components.interfaces.nsIPrefService);"
+ readonly="true"/>
+
+ <!--
+ Weak reference to the related browser (see
+ nsIBrowser.getRelatedBrowser).
+ -->
+ <field name="_relatedBrowser">null</field>
+ <property name="relatedBrowser">
+ <getter><![CDATA[
+ return this._relatedBrowser && this._relatedBrowser.get();
+ ]]></getter>
+ <setter><![CDATA[
+ this._relatedBrowser = Cu.getWeakReference(val);
+ ]]></setter>
+ </property>
+
+ <field name="_docShell">null</field>
+
+ <property name="docShell" readonly="true">
+ <getter><![CDATA[
+ if (this._docShell)
+ return this._docShell;
+
+ let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ if (!frameLoader)
+ return null;
+ this._docShell = frameLoader.docShell;
+ return this._docShell;
+ ]]></getter>
+ </property>
+
+ <field name="_loadContext">null</field>
+
+ <property name="loadContext" readonly="true">
+ <getter><![CDATA[
+ if (this._loadContext)
+ return this._loadContext;
+
+ let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ if (!frameLoader)
+ return null;
+ this._loadContext = frameLoader.loadContext;
+ return this._loadContext;
+ ]]></getter>
+ </property>
+
+ <property name="autoCompletePopup"
+ onget="return document.getElementById(this.getAttribute('autocompletepopup'))"
+ readonly="true"/>
+
+ <property name="dateTimePicker"
+ onget="return document.getElementById(this.getAttribute('datetimepicker'))"
+ readonly="true"/>
+
+ <property name="docShellIsActive">
+ <getter>
+ <![CDATA[
+ return this.docShell && this.docShell.isActive;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (this.docShell)
+ return this.docShell.isActive = val;
+ return false;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="preserveLayers">
+ <parameter name="preserve"/>
+ <body>
+ <!-- Only useful for remote browsers. -->
+ </body>
+ </method>
+
+ <method name="makePrerenderedBrowserActive">
+ <body>
+ <![CDATA[
+ let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ if (frameLoader) {
+ frameLoader.makePrerenderedLoaderActive();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="imageDocument"
+ readonly="true">
+ <getter>
+ <![CDATA[
+ var document = this.contentDocument;
+ if (!document || !(document instanceof Components.interfaces.nsIImageDocument))
+ return null;
+
+ try {
+ return {width: document.imageRequest.image.width, height: document.imageRequest.image.height };
+ } catch (e) {}
+ return null;
+ ]]>
+ </getter>
+ </property>
+
+ <!-- stubbed to false until all callers are removed -->
+ <property name="isRemoteBrowser"
+ onget="return false;"
+ readonly="true"/>
+
+ <property name="messageManager"
+ readonly="true">
+ <getter>
+ <![CDATA[
+ var owner = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
+ if (!owner.frameLoader) {
+ return null;
+ }
+ return owner.frameLoader.messageManager;
+ ]]>
+ </getter>
+
+ </property>
+
+ <field name="_webNavigation">null</field>
+
+ <property name="webNavigation"
+ readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._webNavigation) {
+ if (!this.docShell) {
+ return null;
+ }
+ this._webNavigation = this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);
+ }
+ return this._webNavigation;
+ ]]>
+ </getter>
+ </property>
+
+ <field name="_webBrowserFind">null</field>
+
+ <property name="webBrowserFind"
+ readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._webBrowserFind)
+ this._webBrowserFind = this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebBrowserFind);
+ return this._webBrowserFind;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="getTabBrowser">
+ <body>
+ <![CDATA[
+ for (let node = this.parentNode; node instanceof Element; node = node.parentNode) {
+ if (node.localName == "tabbrowser")
+ return node;
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <field name="_finder">null</field>
+
+ <property name="finder" readonly="true">
+ <getter><![CDATA[
+ if (!this._finder) {
+ if (!this.docShell)
+ return null;
+
+ let Finder = Components.utils.import("resource://gre/modules/Finder.jsm", {}).Finder;
+ this._finder = new Finder(this.docShell);
+ }
+ return this._finder;
+ ]]></getter>
+ </property>
+
+ <field name="_fastFind">null</field>
+ <property name="fastFind" readonly="true">
+ <getter><![CDATA[
+ if (!this._fastFind) {
+ if (!("@mozilla.org/typeaheadfind;1" in Components.classes))
+ return null;
+
+ var tabBrowser = this.getTabBrowser();
+ if (tabBrowser && "fastFind" in tabBrowser)
+ return this._fastFind = tabBrowser.fastFind;
+
+ if (!this.docShell)
+ return null;
+
+ this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
+ .createInstance(Components.interfaces.nsITypeAheadFind);
+ this._fastFind.init(this.docShell);
+ }
+ return this._fastFind;
+ ]]></getter>
+ </property>
+
+ <property name="outerWindowID" readonly="true">
+ <getter><![CDATA[
+ return this.contentWindow
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .outerWindowID;
+ ]]></getter>
+ </property>
+
+ <property name="innerWindowID" readonly="true">
+ <getter><![CDATA[
+ try {
+ return this.contentWindow
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .currentInnerWindowID;
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw e;
+ }
+ return null;
+ }
+ ]]></getter>
+ </property>
+
+ <field name="_lastSearchString">null</field>
+
+ <property name="webProgress"
+ readonly="true"
+ onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);"/>
+
+ <field name="_contentWindow">null</field>
+
+ <property name="contentWindow"
+ readonly="true"
+ onget="return this._contentWindow || (this._contentWindow = this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow));"/>
+
+ <property name="contentWindowAsCPOW"
+ readonly="true"
+ onget="return this.contentWindow;"/>
+
+ <property name="sessionHistory"
+ onget="return this.webNavigation.sessionHistory;"
+ readonly="true"/>
+
+ <property name="markupDocumentViewer"
+ onget="return this.docShell.contentViewer;"
+ readonly="true"/>
+
+ <property name="contentViewerEdit"
+ onget="return this.docShell.contentViewer.QueryInterface(Components.interfaces.nsIContentViewerEdit);"
+ readonly="true"/>
+
+ <property name="contentViewerFile"
+ onget="return this.docShell.contentViewer.QueryInterface(Components.interfaces.nsIContentViewerFile);"
+ readonly="true"/>
+
+ <property name="contentDocument"
+ onget="return this.webNavigation.document;"
+ readonly="true"/>
+
+ <property name="contentDocumentAsCPOW"
+ onget="return this.contentDocument;"
+ readonly="true"/>
+
+ <property name="contentTitle"
+ onget="return this.contentDocument.title;"
+ readonly="true"/>
+
+ <property name="characterSet"
+ onget="return this.docShell.charset;">
+ <setter><![CDATA[
+ this.docShell.charset = val;
+ this.docShell.gatherCharsetMenuTelemetry();
+ ]]></setter>
+ </property>
+
+ <property name="mayEnableCharacterEncodingMenu"
+ onget="return this.docShell.mayEnableCharacterEncodingMenu;"
+ readonly="true"/>
+
+ <property name="contentPrincipal"
+ onget="return this.contentDocument.nodePrincipal;"
+ readonly="true"/>
+
+ <property name="showWindowResizer"
+ onset="if (val) this.setAttribute('showresizer', 'true');
+ else this.removeAttribute('showresizer');
+ return val;"
+ onget="return this.getAttribute('showresizer') == 'true';"/>
+
+ <property name="manifestURI"
+ readonly="true">
+ <getter><![CDATA[
+ return this.contentDocument.documentElement &&
+ this.contentDocument.documentElement.getAttribute("manifest");
+ ]]></getter>
+ </property>
+
+ <property name="fullZoom">
+ <getter><![CDATA[
+ return this.markupDocumentViewer.fullZoom;
+ ]]></getter>
+ <setter><![CDATA[
+ this.markupDocumentViewer.fullZoom = val;
+ ]]></setter>
+ </property>
+
+ <property name="textZoom">
+ <getter><![CDATA[
+ return this.markupDocumentViewer.textZoom;
+ ]]></getter>
+ <setter><![CDATA[
+ this.markupDocumentViewer.textZoom = val;
+ ]]></setter>
+ </property>
+
+ <property name="isSyntheticDocument">
+ <getter><![CDATA[
+ return this.contentDocument.mozSyntheticDocument;
+ ]]></getter>
+ </property>
+
+ <property name="hasContentOpener">
+ <getter><![CDATA[
+ return !!this.contentWindow.opener;
+ ]]></getter>
+ </property>
+
+ <field name="mPrefs" readonly="true">
+ Components.classes['@mozilla.org/preferences-service;1']
+ .getService(Components.interfaces.nsIPrefBranch);
+ </field>
+
+ <field name="_mStrBundle">null</field>
+
+ <property name="mStrBundle">
+ <getter>
+ <![CDATA[
+ if (!this._mStrBundle) {
+ // need to create string bundle manually instead of using <xul:stringbundle/>
+ // see bug 63370 for details
+ this._mStrBundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
+ .getService(Components.interfaces.nsIStringBundleService)
+ .createBundle("chrome://global/locale/browser.properties");
+ }
+ return this._mStrBundle;
+ ]]></getter>
+ </property>
+
+ <method name="addProgressListener">
+ <parameter name="aListener"/>
+ <parameter name="aNotifyMask"/>
+ <body>
+ <![CDATA[
+ if (!aNotifyMask) {
+ aNotifyMask = Components.interfaces.nsIWebProgress.NOTIFY_ALL;
+ }
+ this.webProgress.addProgressListener(aListener, aNotifyMask);
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ this.webProgress.removeProgressListener(aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="findChildShell">
+ <parameter name="aDocShell"/>
+ <parameter name="aSoughtURI"/>
+ <body>
+ <![CDATA[
+ if (aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation)
+ .currentURI.spec == aSoughtURI.spec)
+ 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, aSoughtURI);
+ if (docShell)
+ return docShell;
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="onPageHide">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ // Delete the feeds cache if we're hiding the topmost page
+ // (as opposed to one of its iframes).
+ if (this.feeds && aEvent.target == this.contentDocument)
+ this.feeds = null;
+ if (!this.docShell || !this.fastFind)
+ return;
+ var tabBrowser = this.getTabBrowser();
+ if (!tabBrowser || !("fastFind" in tabBrowser) ||
+ tabBrowser.selectedBrowser == this)
+ this.fastFind.setDocShell(this.docShell);
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateBlockedPopups">
+ <body>
+ <![CDATA[
+ let event = document.createEvent("Events");
+ event.initEvent("DOMUpdatePageReport", true, true);
+ this.dispatchEvent(event);
+ ]]>
+ </body>
+ </method>
+
+ <method name="retrieveListOfBlockedPopups">
+ <body>
+ <![CDATA[
+ this.messageManager.sendAsyncMessage("PopupBlocking:GetBlockedPopupList", null);
+ return new Promise(resolve => {
+ let self = this;
+ this.messageManager.addMessageListener("PopupBlocking:ReplyGetBlockedPopupList",
+ function replyReceived(msg) {
+ self.messageManager.removeMessageListener("PopupBlocking:ReplyGetBlockedPopupList",
+ replyReceived);
+ resolve(msg.data.popupData);
+ }
+ );
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="unblockPopup">
+ <parameter name="aPopupIndex"/>
+ <body><![CDATA[
+ this.messageManager.sendAsyncMessage("PopupBlocking:UnblockPopup",
+ {index: aPopupIndex});
+ ]]></body>
+ </method>
+
+ <field name="blockedPopups">null</field>
+
+ <!-- Obsolete name for blockedPopups. Maybe unused. -->
+ <property name="pageReport"
+ onget="return this.blockedPopups;"
+ readonly="true"/>
+
+ <method name="audioPlaybackStarted">
+ <body>
+ <![CDATA[
+ let event = document.createEvent("Events");
+ event.initEvent("DOMAudioPlaybackStarted", true, false);
+ this.dispatchEvent(event);
+ if (this._audioBlocked) {
+ this._audioBlocked = false;
+ event = document.createEvent("Events");
+ event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
+ this.dispatchEvent(event);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="audioPlaybackStopped">
+ <body>
+ <![CDATA[
+ let event = document.createEvent("Events");
+ event.initEvent("DOMAudioPlaybackStopped", true, false);
+ this.dispatchEvent(event);
+ ]]>
+ </body>
+ </method>
+
+ <method name="audioPlaybackBlocked">
+ <body>
+ <![CDATA[
+ this._audioBlocked = true;
+ let event = document.createEvent("Events");
+ event.initEvent("DOMAudioPlaybackBlockStarted", true, false);
+ this.dispatchEvent(event);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_audioMuted">false</field>
+ <property name="audioMuted"
+ onget="return this._audioMuted;"
+ readonly="true"/>
+
+
+ <field name="_audioBlocked">false</field>
+ <property name="audioBlocked"
+ onget="return this._audioBlocked;"
+ readonly="true"/>
+
+ <method name="mute">
+ <body>
+ <![CDATA[
+ this._audioMuted = true;
+ this.messageManager.sendAsyncMessage("AudioPlayback",
+ {type: "mute"});
+ ]]>
+ </body>
+ </method>
+
+ <method name="unmute">
+ <body>
+ <![CDATA[
+ this._audioMuted = false;
+ this.messageManager.sendAsyncMessage("AudioPlayback",
+ {type: "unmute"});
+ ]]>
+ </body>
+ </method>
+
+ <method name="pauseMedia">
+ <parameter name="disposable"/>
+ <body>
+ <![CDATA[
+ let suspendedReason;
+ if (disposable) {
+ suspendedReason = "mediaControlPaused";
+ } else {
+ suspendedReason = "lostAudioFocusTransiently";
+ }
+
+ this.messageManager.sendAsyncMessage("AudioPlayback",
+ {type: suspendedReason});
+ ]]>
+ </body>
+ </method>
+
+ <method name="stopMedia">
+ <body>
+ <![CDATA[
+ this.messageManager.sendAsyncMessage("AudioPlayback",
+ {type: "mediaControlStopped"});
+ ]]>
+ </body>
+ </method>
+
+ <method name="blockMedia">
+ <body>
+ <![CDATA[
+ this._audioBlocked = true;
+ this.messageManager.sendAsyncMessage("AudioPlayback",
+ {type: "blockInactivePageMedia"});
+ ]]>
+ </body>
+ </method>
+
+ <method name="resumeMedia">
+ <body>
+ <![CDATA[
+ this._audioBlocked = false;
+ this.messageManager.sendAsyncMessage("AudioPlayback",
+ {type: "resumeMedia"});
+ let event = document.createEvent("Events");
+ event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
+ this.dispatchEvent(event);
+ ]]>
+ </body>
+ </method>
+
+ <property name="securityUI">
+ <getter>
+ <![CDATA[
+ if (!this.docShell.securityUI) {
+ const SECUREBROWSERUI_CONTRACTID = "@mozilla.org/secure_browser_ui;1";
+ if (!this.hasAttribute("disablesecurity") &&
+ SECUREBROWSERUI_CONTRACTID in Components.classes) {
+ var securityUI = Components.classes[SECUREBROWSERUI_CONTRACTID]
+ .createInstance(Components.interfaces.nsISecureBrowserUI);
+ securityUI.init(this.contentWindow);
+ }
+ }
+
+ return this.docShell.securityUI;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this.docShell.securityUI = val;
+ ]]>
+ </setter>
+ </property>
+
+ <!-- increases or decreases the browser's network priority -->
+ <method name="adjustPriority">
+ <parameter name="adjustment"/>
+ <body><![CDATA[
+ let loadGroup = this.webNavigation.QueryInterface(Components.interfaces.nsIDocumentLoader)
+ .loadGroup.QueryInterface(Components.interfaces.nsISupportsPriority);
+ loadGroup.adjustPriority(adjustment);
+ ]]></body>
+ </method>
+
+ <!-- sets the browser's network priority to a discrete value -->
+ <method name="setPriority">
+ <parameter name="priority"/>
+ <body><![CDATA[
+ let loadGroup = this.webNavigation.QueryInterface(Components.interfaces.nsIDocumentLoader)
+ .loadGroup.QueryInterface(Components.interfaces.nsISupportsPriority);
+ loadGroup.priority = priority;
+ ]]></body>
+ </method>
+
+ <field name="urlbarChangeTracker">
+ ({
+ _startedLoadSinceLastUserTyping: false,
+
+ startedLoad() {
+ this._startedLoadSinceLastUserTyping = true;
+ },
+ finishedLoad() {
+ this._startedLoadSinceLastUserTyping = false;
+ },
+ userTyped() {
+ this._startedLoadSinceLastUserTyping = false;
+ },
+ })
+ </field>
+
+ <method name="didStartLoadSinceLastUserTyping">
+ <body><![CDATA[
+ return !this.inLoadURI &&
+ this.urlbarChangeTracker._startedLoadSinceLastUserTyping;
+ ]]></body>
+ </method>
+
+ <field name="_userTypedValue">
+ null
+ </field>
+
+ <property name="userTypedValue"
+ onget="return this._userTypedValue;">
+ <setter><![CDATA[
+ this.urlbarChangeTracker.userTyped();
+ this._userTypedValue = val;
+ return val;
+ ]]></setter>
+ </property>
+
+ <field name="mFormFillAttached">
+ false
+ </field>
+
+ <field name="isShowingMessage">
+ false
+ </field>
+
+ <field name="droppedLinkHandler">
+ null
+ </field>
+
+ <field name="mIconURL">null</field>
+
+ <property name="isAuthDOSProtected"
+ onget="return (this.getAttribute('authdosprotected') == 'true');"
+ readonly="true"/>
+
+ <!-- This is managed by the tabbrowser -->
+ <field name="lastURI">null</field>
+
+ <field name="mDestroyed">false</field>
+
+ <constructor>
+ <![CDATA[
+ try {
+ // |webNavigation.sessionHistory| will have been set by the frame
+ // loader when creating the docShell as long as this xul:browser
+ // doesn't have the 'disablehistory' attribute set.
+ if (this.docShell && this.webNavigation.sessionHistory) {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.addObserver(this, "browser:purge-session-history", true);
+
+ // enable global history if we weren't told otherwise
+ if (!this.hasAttribute("disableglobalhistory")) {
+ try {
+ this.docShell.useGlobalHistory = true;
+ } catch (ex) {
+ // This can occur if the Places database is locked
+ Components.utils.reportError("Error enabling browser global history: " + ex);
+ }
+ }
+ }
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ try {
+ var securityUI = this.securityUI;
+ }
+ catch (e) {
+ }
+
+ // XXX tabbrowser.xml sets "relatedBrowser" as a direct property on
+ // some browsers before they are put into a DOM (and get a binding).
+ // This hack makes sure that we hold a weak reference to the other
+ // browser (and go through the proper getter and setter).
+ if (this.hasOwnProperty("relatedBrowser")) {
+ var relatedBrowser = this.relatedBrowser;
+ delete this.relatedBrowser;
+ this.relatedBrowser = relatedBrowser;
+ }
+
+ this.addEventListener("pagehide", this.onPageHide, true);
+
+ if (this.messageManager) {
+ this.messageManager.addMessageListener("PopupBlocking:UpdateBlockedPopups", this);
+ this.messageManager.addMessageListener("Autoscroll:Start", this);
+ this.messageManager.addMessageListener("Autoscroll:Cancel", this);
+ this.messageManager.addMessageListener("AudioPlayback:Start", this);
+ this.messageManager.addMessageListener("AudioPlayback:Stop", this);
+ this.messageManager.addMessageListener("AudioPlayback:Block", this);
+ }
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ this.destroy();
+ ]]>
+ </destructor>
+
+ <!-- This is necessary because the destructor doesn't always get called when
+ we are removed from a tabbrowser. This will be explicitly called by tabbrowser.
+
+ Note: this function is overriden in remote-browser.xml, so any clean-up that
+ also applies to browser.isRemoteBrowser = true must be duplicated there. -->
+ <method name="destroy">
+ <body>
+ <![CDATA[
+ if (this.mDestroyed)
+ return;
+ this.mDestroyed = true;
+
+ if (this.docShell && this.webNavigation.sessionHistory) {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ try {
+ os.removeObserver(this, "browser:purge-session-history");
+ } catch (ex) {
+ // It's not clear why this sometimes throws an exception.
+ }
+ }
+
+ this._fastFind = null;
+ this._webBrowserFind = null;
+
+ // The feeds cache can keep the document inside this browser alive.
+ this.feeds = null;
+
+ this.lastURI = null;
+
+ this.removeEventListener("pagehide", this.onPageHide, true);
+
+ if (this._autoScrollNeedsCleanup) {
+ // we polluted the global scope, so clean it up
+ this._autoScrollPopup.parentNode.removeChild(this._autoScrollPopup);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!--
+ We call this _receiveMessage (and alias receiveMessage to it) so that
+ bindings that inherit from this one can delegate to it.
+ -->
+ <method name="_receiveMessage">
+ <parameter name="aMessage"/>
+ <body><![CDATA[
+ let data = aMessage.data;
+ switch (aMessage.name) {
+ case "PopupBlocking:UpdateBlockedPopups": {
+ this.blockedPopups = {
+ length: data.count,
+ reported: !data.freshPopup,
+ };
+
+ this.updateBlockedPopups();
+ break;
+ }
+ case "Autoscroll:Start": {
+ if (!this.autoscrollEnabled) {
+ return false;
+ }
+ this.startScroll(data.scrolldir, data.screenX, data.screenY);
+ return true;
+ }
+ case "Autoscroll:Cancel":
+ this._autoScrollPopup.hidePopup();
+ break;
+ case "AudioPlayback:Start":
+ this.audioPlaybackStarted();
+ break;
+ case "AudioPlayback:Stop":
+ this.audioPlaybackStopped();
+ break;
+ case "AudioPlayback:Block":
+ this.audioPlaybackBlocked();
+ break;
+ }
+ return undefined;
+ ]]></body>
+ </method>
+
+ <method name="receiveMessage">
+ <parameter name="aMessage"/>
+ <body><![CDATA[
+ return this._receiveMessage(aMessage);
+ ]]></body>
+ </method>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aState"/>
+ <body>
+ <![CDATA[
+ if (aTopic != "browser:purge-session-history")
+ return;
+
+ this.purgeSessionHistory();
+ ]]>
+ </body>
+ </method>
+
+ <method name="purgeSessionHistory">
+ <body>
+ <![CDATA[
+ this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
+ ]]>
+ </body>
+ </method>
+
+ <method name="createAboutBlankContentViewer">
+ <parameter name="aPrincipal"/>
+ <body>
+ <![CDATA[
+ let principal = BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
+ this.docShell.createAboutBlankContentViewer(principal);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_AUTOSCROLL_SNAP">10</field>
+ <field name="_scrolling">false</field>
+ <field name="_startX">null</field>
+ <field name="_startY">null</field>
+ <field name="_autoScrollPopup">null</field>
+ <field name="_autoScrollNeedsCleanup">false</field>
+
+ <method name="stopScroll">
+ <body>
+ <![CDATA[
+ if (this._scrolling) {
+ this._scrolling = false;
+ window.removeEventListener("mousemove", this, true);
+ window.removeEventListener("mousedown", this, true);
+ window.removeEventListener("mouseup", this, true);
+ window.removeEventListener("DOMMouseScroll", this, true);
+ window.removeEventListener("contextmenu", this, true);
+ window.removeEventListener("keydown", this, true);
+ window.removeEventListener("keypress", this, true);
+ window.removeEventListener("keyup", this, true);
+ this.messageManager.sendAsyncMessage("Autoscroll:Stop");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_createAutoScrollPopup">
+ <body>
+ <![CDATA[
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var popup = document.createElementNS(XUL_NS, "panel");
+ popup.className = "autoscroller";
+ // We set this attribute on the element so that mousemove
+ // events can be handled by browser-content.js.
+ popup.setAttribute("mousethrough", "always");
+ popup.setAttribute("rolluponmousewheel", "true");
+ return popup;
+ ]]>
+ </body>
+ </method>
+
+ <method name="startScroll">
+ <parameter name="scrolldir"/>
+ <parameter name="screenX"/>
+ <parameter name="screenY"/>
+ <body><![CDATA[
+ if (!this._autoScrollPopup) {
+ if (this.hasAttribute("autoscrollpopup")) {
+ // our creator provided a popup to share
+ this._autoScrollPopup = document.getElementById(this.getAttribute("autoscrollpopup"));
+ }
+ else {
+ // we weren't provided a popup; we have to use the global scope
+ this._autoScrollPopup = this._createAutoScrollPopup();
+ document.documentElement.appendChild(this._autoScrollPopup);
+ this._autoScrollNeedsCleanup = true;
+ }
+ }
+
+ // we need these attributes so themers don't need to create per-platform packages
+ if (screen.colorDepth > 8) { // need high color for transparency
+ // Exclude second-rate platforms
+ this._autoScrollPopup.setAttribute("transparent", !/BeOS|OS\/2/.test(navigator.appVersion));
+ // Enable translucency on Windows and Mac
+ this._autoScrollPopup.setAttribute("translucent", /Win|Mac/.test(navigator.platform));
+ }
+
+ this._autoScrollPopup.setAttribute("noautofocus", "true");
+ this._autoScrollPopup.setAttribute("scrolldir", scrolldir);
+ this._autoScrollPopup.addEventListener("popuphidden", this, true);
+ this._autoScrollPopup.showPopup(document.documentElement,
+ screenX,
+ screenY,
+ "popup", null, null);
+ this._ignoreMouseEvents = true;
+ this._scrolling = true;
+ this._startX = screenX;
+ this._startY = screenY;
+
+ window.addEventListener("mousemove", this, true);
+ window.addEventListener("mousedown", this, true);
+ window.addEventListener("mouseup", this, true);
+ window.addEventListener("DOMMouseScroll", this, true);
+ window.addEventListener("contextmenu", this, true);
+ window.addEventListener("keydown", this, true);
+ window.addEventListener("keypress", this, true);
+ window.addEventListener("keyup", this, true);
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (this._scrolling) {
+ switch (aEvent.type) {
+ case "mousemove": {
+ var x = aEvent.screenX - this._startX;
+ var y = aEvent.screenY - this._startY;
+
+ if ((x > this._AUTOSCROLL_SNAP || x < -this._AUTOSCROLL_SNAP) ||
+ (y > this._AUTOSCROLL_SNAP || y < -this._AUTOSCROLL_SNAP))
+ this._ignoreMouseEvents = false;
+ break;
+ }
+ case "mouseup":
+ case "mousedown":
+ case "contextmenu": {
+ if (!this._ignoreMouseEvents) {
+ // Use a timeout to prevent the mousedown from opening the popup again.
+ // Ideally, we could use preventDefault here, but contenteditable
+ // and middlemouse paste don't interact well. See bug 1188536.
+ setTimeout(() => this._autoScrollPopup.hidePopup(), 0);
+ }
+ this._ignoreMouseEvents = false;
+ break;
+ }
+ case "DOMMouseScroll": {
+ this._autoScrollPopup.hidePopup();
+ aEvent.preventDefault();
+ break;
+ }
+ case "popuphidden": {
+ this._autoScrollPopup.removeEventListener("popuphidden", this, true);
+ this.stopScroll();
+ break;
+ }
+ case "keydown": {
+ if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+ // the escape key will be processed by
+ // nsXULPopupManager::KeyDown and the panel will be closed.
+ // So, don't consume the key event here.
+ break;
+ }
+ // don't break here. we need to eat keydown events.
+ }
+ case "keypress":
+ case "keyup": {
+ // All keyevents should be eaten here during autoscrolling.
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ break;
+ }
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="closeBrowser">
+ <body>
+ <![CDATA[
+ // The request comes from a XPCOM component, we'd want to redirect
+ // the request to tabbrowser.
+ let tabbrowser = this.getTabBrowser();
+ if (tabbrowser) {
+ let tab = tabbrowser.getTabForBrowser(this);
+ if (tab) {
+ tabbrowser.removeTab(tab);
+ return;
+ }
+ }
+
+ throw new Error("Closing a browser which was not attached to a tabbrowser is unsupported.");
+ ]]>
+ </body>
+ </method>
+
+ <method name="swapBrowsers">
+ <parameter name="aOtherBrowser"/>
+ <body>
+ <![CDATA[
+ // The request comes from a XPCOM component, we'd want to redirect
+ // the request to tabbrowser so tabbrowser will be setup correctly,
+ // and it will eventually call swapDocShells.
+ let tabbrowser = this.getTabBrowser();
+ if (tabbrowser) {
+ let tab = tabbrowser.getTabForBrowser(this);
+ if (tab) {
+ tabbrowser.swapBrowsers(tab, aOtherBrowser);
+ return;
+ }
+ }
+
+ // If we're not attached to a tabbrowser, just swap.
+ this.swapDocShells(aOtherBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <method name="swapDocShells">
+ <parameter name="aOtherBrowser"/>
+ <body>
+ <![CDATA[
+ // Give others a chance to swap state.
+ // IMPORTANT: Since a swapDocShells call does not swap the messageManager
+ // instances attached to a browser to aOtherBrowser, others
+ // will need to add the message listeners to the new
+ // messageManager.
+ // This is not a bug in swapDocShells or the FrameLoader,
+ // merely a design decision: If message managers were swapped,
+ // so that no new listeners were needed, the new
+ // aOtherBrowser.messageManager would have listeners pointing
+ // to the JS global of the current browser, which would rather
+ // easily create leaks while swapping.
+ // IMPORTANT2: When the current browser element is removed from DOM,
+ // which is quite common after a swpDocShells call, its
+ // frame loader is destroyed, and that destroys the relevant
+ // message manager, which will remove the listeners.
+ let event = new CustomEvent("SwapDocShells", {"detail": aOtherBrowser});
+ this.dispatchEvent(event);
+ event = new CustomEvent("SwapDocShells", {"detail": this});
+ aOtherBrowser.dispatchEvent(event);
+
+ // We need to swap fields that are tied to our docshell or related to
+ // the loaded page
+ // Fields which are built as a result of notifactions (pageshow/hide,
+ // DOMLinkAdded/Removed, onStateChange) should not be swapped here,
+ // because these notifications are dispatched again once the docshells
+ // are swapped.
+ var fieldsToSwap = [
+ "_docShell",
+ "_webBrowserFind",
+ "_contentWindow",
+ "_webNavigation"
+ ];
+
+ var ourFieldValues = {};
+ var otherFieldValues = {};
+ for (let field of fieldsToSwap) {
+ ourFieldValues[field] = this[field];
+ otherFieldValues[field] = aOtherBrowser[field];
+ }
+
+ if (window.PopupNotifications)
+ PopupNotifications._swapBrowserNotifications(aOtherBrowser, this);
+
+ try {
+ this.swapFrameLoaders(aOtherBrowser);
+ } catch (ex) {
+ // This may not be implemented for browser elements that are not
+ // attached to a BrowserDOMWindow.
+ }
+
+ for (let field of fieldsToSwap) {
+ this[field] = otherFieldValues[field];
+ aOtherBrowser[field] = ourFieldValues[field];
+ }
+
+ // Null the current nsITypeAheadFind instances so that they're
+ // lazily re-created on access. We need to do this because they
+ // might have attached the wrong docShell.
+ this._fastFind = aOtherBrowser._fastFind = null;
+
+ event = new CustomEvent("EndSwapDocShells", {"detail": aOtherBrowser});
+ this.dispatchEvent(event);
+ event = new CustomEvent("EndSwapDocShells", {"detail": this});
+ aOtherBrowser.dispatchEvent(event);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getInPermitUnload">
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ if (!this.docShell || !this.docShell.contentViewer) {
+ aCallback(false);
+ return;
+ }
+ aCallback(this.docShell.contentViewer.inPermitUnload);
+ ]]>
+ </body>
+ </method>
+
+ <method name="permitUnload">
+ <body>
+ <![CDATA[
+ if (!this.docShell || !this.docShell.contentViewer) {
+ return {permitUnload: true, timedOut: false};
+ }
+ return {permitUnload: this.docShell.contentViewer.permitUnload(), timedOut: false};
+ ]]>
+ </body>
+ </method>
+
+ <method name="print">
+ <parameter name="aOuterWindowID"/>
+ <parameter name="aPrintSettings"/>
+ <parameter name="aPrintProgressListener"/>
+ <body>
+ <![CDATA[
+ var owner = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
+ if (!owner.frameLoader) {
+ throw Components.Exception("No frame loader.",
+ Components.results.NS_ERROR_FAILURE);
+ }
+
+ owner.frameLoader.print(aOuterWindowID, aPrintSettings,
+ aPrintProgressListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="dropLinks">
+ <parameter name="aLinksCount"/>
+ <parameter name="aLinks"/>
+ <body><![CDATA[
+ if (!this.droppedLinkHandler) {
+ return false;
+ }
+ let links = [];
+ for (let i = 0; i < aLinksCount; i += 3) {
+ links.push({
+ url: aLinks[i],
+ name: aLinks[i + 1],
+ type: aLinks[i + 2],
+ });
+ }
+ this.droppedLinkHandler(null, links);
+ return true;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="keypress" keycode="VK_F7" group="system">
+ <![CDATA[
+ if (event.defaultPrevented || !event.isTrusted)
+ return;
+
+ const kPrefShortcutEnabled = "accessibility.browsewithcaret_shortcut.enabled";
+ const kPrefWarnOnEnable = "accessibility.warn_on_browsewithcaret";
+ const kPrefCaretBrowsingOn = "accessibility.browsewithcaret";
+
+ var isEnabled = this.mPrefs.getBoolPref(kPrefShortcutEnabled);
+ if (!isEnabled)
+ return;
+
+ // Toggle browse with caret mode
+ var browseWithCaretOn = this.mPrefs.getBoolPref(kPrefCaretBrowsingOn, false);
+ var warn = this.mPrefs.getBoolPref(kPrefWarnOnEnable, true);
+ if (warn && !browseWithCaretOn) {
+ var checkValue = {value:false};
+ var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+
+ var buttonPressed = promptService.confirmEx(window,
+ this.mStrBundle.GetStringFromName('browsewithcaret.checkWindowTitle'),
+ this.mStrBundle.GetStringFromName('browsewithcaret.checkLabel'),
+ // Make "No" the default:
+ promptService.STD_YES_NO_BUTTONS | promptService.BUTTON_POS_1_DEFAULT,
+ null, null, null, this.mStrBundle.GetStringFromName('browsewithcaret.checkMsg'),
+ checkValue);
+ if (buttonPressed != 0) {
+ if (checkValue.value) {
+ try {
+ this.mPrefs.setBoolPref(kPrefShortcutEnabled, false);
+ } catch (ex) {
+ }
+ }
+ return;
+ }
+ if (checkValue.value) {
+ try {
+ this.mPrefs.setBoolPref(kPrefWarnOnEnable, false);
+ }
+ catch (ex) {
+ }
+ }
+ }
+
+ // Toggle the pref
+ try {
+ this.mPrefs.setBoolPref(kPrefCaretBrowsingOn, !browseWithCaretOn);
+ } catch (ex) {
+ }
+ ]]>
+ </handler>
+ <handler event="dragover" group="system">
+ <![CDATA[
+ if (!this.droppedLinkHandler || event.defaultPrevented)
+ return;
+
+ // For drags that appear to be internal text (for example, tab drags),
+ // set the dropEffect to 'none'. This prevents the drop even if some
+ // other listener cancelled the event.
+ var types = event.dataTransfer.types;
+ if (types.includes("text/x-moz-text-internal") && !types.includes("text/plain")) {
+ event.dataTransfer.dropEffect = "none";
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+ let linkHandler = Components.classes["@mozilla.org/content/dropped-link-handler;1"].
+ getService(Components.interfaces.nsIDroppedLinkHandler);
+ if (linkHandler.canDropLink(event, false))
+ event.preventDefault();
+ ]]>
+ </handler>
+ <handler event="drop" group="system">
+ <![CDATA[
+ if (!this.droppedLinkHandler || event.defaultPrevented)
+ return;
+
+ let name = { };
+ let linkHandler = Components.classes["@mozilla.org/content/dropped-link-handler;1"].
+ getService(Components.interfaces.nsIDroppedLinkHandler);
+ try {
+ // Pass true to prevent the dropping of javascript:/data: URIs
+ var links = linkHandler.dropLinks(event, true);
+ } catch (ex) {
+ return;
+ }
+
+ if (links.length) {
+ this.droppedLinkHandler(event, links);
+ }
+ ]]>
+ </handler>
+ </handlers>
+
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/button.xml b/components/bindings/content/button.xml
new file mode 100644
index 000000000..89d9d86c6
--- /dev/null
+++ b/components/bindings/content/button.xml
@@ -0,0 +1,389 @@
+<?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="buttonBindings"
+ 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="button-base" extends="chrome://global/content/bindings/general.xml#basetext" role="xul:button">
+ <implementation implements="nsIDOMXULButtonElement">
+ <property name="type"
+ onget="return this.getAttribute('type');"
+ onset="this.setAttribute('type', val); return val;"/>
+
+ <property name="dlgType"
+ onget="return this.getAttribute('dlgtype');"
+ onset="this.setAttribute('dlgtype', val); return val;"/>
+
+ <property name="group"
+ onget="return this.getAttribute('group');"
+ onset="this.setAttribute('group', val); return val;"/>
+
+ <property name="open" onget="return this.hasAttribute('open');">
+ <setter><![CDATA[
+ if (this.boxObject instanceof MenuBoxObject) {
+ this.boxObject.openMenu(val);
+ } else if (val) {
+ // Fall back to just setting the attribute
+ this.setAttribute('open', 'true');
+ } else {
+ this.removeAttribute('open');
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="checked" onget="return this.hasAttribute('checked');">
+ <setter><![CDATA[
+ if (this.type == "checkbox") {
+ this.checkState = val ? 1 : 0;
+ } else if (this.type == "radio" && val) {
+ var sibs = this.parentNode.getElementsByAttribute("group", this.group);
+ for (var i = 0; i < sibs.length; ++i)
+ sibs[i].removeAttribute("checked");
+ }
+
+ if (val)
+ this.setAttribute("checked", "true");
+ else
+ this.removeAttribute("checked");
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="checkState">
+ <getter><![CDATA[
+ var state = this.getAttribute("checkState");
+ if (state == "")
+ return this.checked ? 1 : 0;
+ if (state == "0")
+ return 0;
+ if (state == "2")
+ return 2;
+ return 1;
+ ]]></getter>
+ <setter><![CDATA[
+ this.setAttribute("checkState", val);
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="autoCheck"
+ onget="return this.getAttribute('autoCheck') == 'true';"
+ onset="this.setAttribute('autoCheck', val); return val;"/>
+
+ <method name ="filterButtons">
+ <parameter name="node"/>
+ <body>
+ <![CDATA[
+ // if the node isn't visible, don't descend into it.
+ var cs = node.ownerDocument.defaultView.getComputedStyle(node, null);
+ if (cs.visibility != "visible" || cs.display == "none") {
+ return NodeFilter.FILTER_REJECT;
+ }
+ // but it may be a popup element, in which case we look at "state"...
+ if (cs.display == "-moz-popup" && node.state != "open") {
+ return NodeFilter.FILTER_REJECT;
+ }
+ // OK - the node seems visible, so it is a candidate.
+ if (node.localName == "button" && node.accessKey && !node.disabled)
+ return NodeFilter.FILTER_ACCEPT;
+ return NodeFilter.FILTER_SKIP;
+ ]]>
+ </body>
+ </method>
+
+ <method name="fireAccessKeyButton">
+ <parameter name="aSubtree"/>
+ <parameter name="aAccessKeyLower"/>
+ <body>
+ <![CDATA[
+ var iterator = aSubtree.ownerDocument.createTreeWalker(aSubtree,
+ NodeFilter.SHOW_ELEMENT,
+ this.filterButtons);
+ while (iterator.nextNode()) {
+ var test = iterator.currentNode;
+ if (test.accessKey.toLowerCase() == aAccessKeyLower &&
+ !test.disabled && !test.collapsed && !test.hidden) {
+ test.focus();
+ test.click();
+ return true;
+ }
+ }
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleClick">
+ <body>
+ <![CDATA[
+ if (!this.disabled &&
+ (this.autoCheck || !this.hasAttribute("autoCheck"))) {
+
+ if (this.type == "checkbox") {
+ this.checked = !this.checked;
+ } else if (this.type == "radio") {
+ this.checked = true;
+ }
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <!-- While it would seem we could do this by handling oncommand, we can't
+ because any external oncommand handlers might get called before ours,
+ and then they would see the incorrect value of checked. Additionally
+ a command attribute would redirect the command events anyway.-->
+ <handler event="click" button="0" action="this._handleClick();"/>
+ <handler event="keypress" key=" ">
+ <![CDATA[
+ this._handleClick();
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ ]]>
+ </handler>
+
+ <handler event="keypress">
+ <![CDATA[
+ if (this.boxObject instanceof MenuBoxObject) {
+ if (this.open)
+ return;
+ } else {
+ if (event.keyCode == KeyEvent.DOM_VK_UP ||
+ (event.keyCode == KeyEvent.DOM_VK_LEFT &&
+ document.defaultView.getComputedStyle(this.parentNode, "")
+ .direction == "ltr") ||
+ (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
+ document.defaultView.getComputedStyle(this.parentNode, "")
+ .direction == "rtl")) {
+ event.preventDefault();
+ window.document.commandDispatcher.rewindFocus();
+ return;
+ }
+
+ if (event.keyCode == KeyEvent.DOM_VK_DOWN ||
+ (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
+ document.defaultView.getComputedStyle(this.parentNode, "")
+ .direction == "ltr") ||
+ (event.keyCode == KeyEvent.DOM_VK_LEFT &&
+ document.defaultView.getComputedStyle(this.parentNode, "")
+ .direction == "rtl")) {
+ event.preventDefault();
+ window.document.commandDispatcher.advanceFocus();
+ return;
+ }
+ }
+
+ if (event.keyCode || event.charCode <= 32 || event.altKey ||
+ event.ctrlKey || event.metaKey)
+ return; // No printable char pressed, not a potential accesskey
+
+ // Possible accesskey pressed
+ var charPressedLower = String.fromCharCode(event.charCode).toLowerCase();
+
+ // If the accesskey of the current button is pressed, just activate it
+ if (this.accessKey.toLowerCase() == charPressedLower) {
+ this.click();
+ return;
+ }
+
+ // Search for accesskey in the list of buttons for this doc and each subdoc
+ // Get the buttons for the main document and all sub-frames
+ for (var frameCount = -1; frameCount < window.top.frames.length; frameCount++) {
+ var doc = (frameCount == -1)? window.top.document:
+ window.top.frames[frameCount].document
+ if (this.fireAccessKeyButton(doc.documentElement, charPressedLower))
+ return;
+ }
+
+ // Test anonymous buttons
+ var dlg = window.top.document;
+ var buttonBox = dlg.getAnonymousElementByAttribute(dlg.documentElement,
+ "anonid", "buttons");
+ if (buttonBox)
+ this.fireAccessKeyButton(buttonBox, charPressedLower);
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="button" display="xul:button"
+ extends="chrome://global/content/bindings/button.xml#button-base">
+ <resources>
+ <stylesheet src="chrome://global/skin/button.css"/>
+ </resources>
+
+ <content>
+ <children includes="observes|template|menupopup|panel|tooltip"/>
+ <xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
+ align="center" pack="center" flex="1" anonid="button-box">
+ <children>
+ <xul:image class="button-icon" xbl:inherits="src=image"/>
+ <xul:label class="button-text" xbl:inherits="value=label,accesskey,crop"/>
+ </children>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="menu" display="xul:menu"
+ extends="chrome://global/content/bindings/button.xml#button">
+ <content>
+ <children includes="observes|template|menupopup|panel|tooltip"/>
+ <xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
+ align="center" pack="center" flex="1">
+ <children>
+ <xul:hbox class="box-inherit" xbl:inherits="align,dir,pack,orient"
+ align="center" pack="center" flex="1">
+ <xul:image class="button-icon" xbl:inherits="src=image"/>
+ <xul:label class="button-text" xbl:inherits="value=label,accesskey,crop"/>
+ </xul:hbox>
+ <xul:dropmarker class="button-menu-dropmarker" xbl:inherits="open,disabled,label"/>
+ </children>
+ </xul:hbox>
+ </content>
+
+ <handlers>
+ <handler event="keypress" keycode="VK_RETURN" action="this.open = true;"/>
+ <handler event="keypress" key=" ">
+ <![CDATA[
+ this.open = true;
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="menu-button-base"
+ extends="chrome://global/content/bindings/button.xml#button-base">
+ <implementation implements="nsIDOMEventListener">
+ <constructor>
+ this.init();
+ </constructor>
+
+ <method name="init">
+ <body>
+ <![CDATA[
+ var btn = document.getAnonymousElementByAttribute(this, "anonid", "button");
+ if (!btn)
+ throw "XBL binding for <button type=\"menu-button\"/> binding must contain an element with anonid=\"button\"";
+
+ var menubuttonParent = this;
+ btn.addEventListener("mouseover", function() {
+ if (!this.disabled)
+ menubuttonParent.buttonover = true;
+ }, true);
+ btn.addEventListener("mouseout", function() {
+ menubuttonParent.buttonover = false;
+ }, true);
+ btn.addEventListener("mousedown", function() {
+ if (!this.disabled) {
+ menubuttonParent.buttondown = true;
+ document.addEventListener("mouseup", menubuttonParent, true);
+ }
+ }, true);
+ ]]>
+ </body>
+ </method>
+
+ <property name="buttonover" onget="return this.getAttribute('buttonover');">
+ <setter>
+ <![CDATA[
+ var v = val || val == "true";
+ if (!v && this.buttondown) {
+ this.buttondown = false;
+ this._pendingActive = true;
+ }
+ else if (this._pendingActive) {
+ this.buttondown = true;
+ this._pendingActive = false;
+ }
+
+ if (v)
+ this.setAttribute("buttonover", "true");
+ else
+ this.removeAttribute("buttonover");
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="buttondown" onget="return this.getAttribute('buttondown') == 'true';">
+ <setter>
+ <![CDATA[
+ if (val || val == "true")
+ this.setAttribute("buttondown", "true");
+ else
+ this.removeAttribute("buttondown");
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <field name="_pendingActive">false</field>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this._pendingActive = false;
+ this.buttondown = false;
+ document.removeEventListener("mouseup", this, true);
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="keypress" keycode="VK_RETURN">
+ if (event.originalTarget == this)
+ this.open = true;
+ </handler>
+ <handler event="keypress" key=" ">
+ if (event.originalTarget == this) {
+ this.open = true;
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ }
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="menu-button" display="xul:menu"
+ extends="chrome://global/content/bindings/button.xml#menu-button-base">
+ <resources>
+ <stylesheet src="chrome://global/skin/button.css"/>
+ </resources>
+
+ <content>
+ <children includes="observes|template|menupopup|panel|tooltip"/>
+ <xul:button class="box-inherit button-menubutton-button"
+ anonid="button" flex="1" allowevents="true"
+ xbl:inherits="disabled,crop,image,label,accesskey,command,
+ buttonover,buttondown,align,dir,pack,orient">
+ <children/>
+ </xul:button>
+ <xul:dropmarker class="button-menubutton-dropmarker" xbl:inherits="open,disabled,label"/>
+ </content>
+ </binding>
+
+ <binding id="button-image" display="xul:button"
+ extends="chrome://global/content/bindings/button.xml#button">
+ <content>
+ <xul:image class="button-image-icon" xbl:inherits="src=image"/>
+ </content>
+ </binding>
+
+ <binding id="button-repeat" display="xul:autorepeatbutton"
+ extends="chrome://global/content/bindings/button.xml#button"/>
+
+</bindings>
diff --git a/components/bindings/content/calendar.js b/components/bindings/content/calendar.js
new file mode 100644
index 000000000..44ba67501
--- /dev/null
+++ b/components/bindings/content/calendar.js
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+/**
+ * Initialize the Calendar and generate nodes for week headers and days, and
+ * attach event listeners.
+ *
+ * @param {Object} options
+ * {
+ * {Number} calViewSize: Number of days to appear on a calendar view
+ * {Function} getDayString: Transform day number to string
+ * {Function} getWeekHeaderString: Transform day of week number to string
+ * {Function} setSelection: Set selection for dateKeeper
+ * }
+ * @param {Object} context
+ * {
+ * {DOMElement} weekHeader
+ * {DOMElement} daysView
+ * }
+ */
+function Calendar(options, context) {
+ const DAYS_IN_A_WEEK = 7;
+
+ this.context = context;
+ this.state = {
+ days: [],
+ weekHeaders: [],
+ setSelection: options.setSelection,
+ getDayString: options.getDayString,
+ getWeekHeaderString: options.getWeekHeaderString
+ };
+ this.elements = {
+ weekHeaders: this._generateNodes(DAYS_IN_A_WEEK, context.weekHeader),
+ daysView: this._generateNodes(options.calViewSize, context.daysView)
+ };
+
+ this._attachEventListeners();
+}
+
+{
+ Calendar.prototype = {
+
+ /**
+ * Set new properties and render them.
+ *
+ * @param {Object} props
+ * {
+ * {Boolean} isVisible: Whether or not the calendar is in view
+ * {Array<Object>} days: Data for days
+ * {
+ * {Date} dateObj
+ * {Number} content
+ * {Array<String>} classNames
+ * {Boolean} enabled
+ * }
+ * {Array<Object>} weekHeaders: Data for weekHeaders
+ * {
+ * {Number} content
+ * {Array<String>} classNames
+ * }
+ * }
+ */
+ setProps(props) {
+ if (props.isVisible) {
+ // Transform the days and weekHeaders array for rendering
+ const days = props.days.map(({ dateObj, content, classNames, enabled }) => {
+ return {
+ dateObj,
+ textContent: this.state.getDayString(content),
+ className: classNames.join(" "),
+ enabled
+ };
+ });
+ const weekHeaders = props.weekHeaders.map(({ content, classNames }) => {
+ return {
+ textContent: this.state.getWeekHeaderString(content),
+ className: classNames.join(" ")
+ };
+ });
+ // Update the DOM nodes states
+ this._render({
+ elements: this.elements.daysView,
+ items: days,
+ prevState: this.state.days
+ });
+ this._render({
+ elements: this.elements.weekHeaders,
+ items: weekHeaders,
+ prevState: this.state.weekHeaders,
+ });
+ // Update the state to current
+ this.state.days = days;
+ this.state.weekHeaders = weekHeaders;
+ }
+ },
+
+ /**
+ * Render the items onto the DOM nodes
+ * @param {Object}
+ * {
+ * {Array<DOMElement>} elements
+ * {Array<Object>} items
+ * {Array<Object>} prevState: state of items from last render
+ * }
+ */
+ _render({ elements, items, prevState }) {
+ for (let i = 0, l = items.length; i < l; i++) {
+ let el = elements[i];
+
+ // Check if state from last render has changed, if so, update the elements
+ if (!prevState[i] || prevState[i].textContent != items[i].textContent) {
+ el.textContent = items[i].textContent;
+ }
+ if (!prevState[i] || prevState[i].className != items[i].className) {
+ el.className = items[i].className;
+ }
+ }
+ },
+
+ /**
+ * Generate DOM nodes
+ *
+ * @param {Number} size: Number of nodes to generate
+ * @param {DOMElement} context: Element to append the nodes to
+ * @return {Array<DOMElement>}
+ */
+ _generateNodes(size, context) {
+ let frag = document.createDocumentFragment();
+ let refs = [];
+
+ for (let i = 0; i < size; i++) {
+ let el = document.createElement("div");
+ el.dataset.id = i;
+ refs.push(el);
+ frag.appendChild(el);
+ }
+ context.appendChild(frag);
+
+ return refs;
+ },
+
+ /**
+ * Handle events
+ * @param {DOMEvent} event
+ */
+ handleEvent(event) {
+ switch (event.type) {
+ case "click": {
+ if (event.target.parentNode == this.context.daysView) {
+ let targetId = event.target.dataset.id;
+ let targetObj = this.state.days[targetId];
+ if (targetObj.enabled) {
+ this.state.setSelection(targetObj.dateObj);
+ }
+ }
+ break;
+ }
+ }
+ },
+
+ /**
+ * Attach event listener to daysView
+ */
+ _attachEventListeners() {
+ this.context.daysView.addEventListener("click", this);
+ }
+ };
+}
diff --git a/components/bindings/content/checkbox.xml b/components/bindings/content/checkbox.xml
new file mode 100644
index 000000000..c6a5babfd
--- /dev/null
+++ b/components/bindings/content/checkbox.xml
@@ -0,0 +1,84 @@
+<?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="checkboxBindings"
+ 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="checkbox" extends="chrome://global/content/bindings/checkbox.xml#checkbox-baseline">
+ <resources>
+ <stylesheet src="chrome://global/skin/checkbox.css"/>
+ </resources>
+ </binding>
+
+ <binding id="checkbox-baseline" role="xul:checkbox"
+ extends="chrome://global/content/bindings/general.xml#basetext">
+ <content>
+ <xul:image class="checkbox-check" xbl:inherits="checked,disabled"/>
+ <xul:hbox class="checkbox-label-box" flex="1">
+ <xul:image class="checkbox-icon" xbl:inherits="src"/>
+ <xul:label class="checkbox-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMXULCheckboxElement">
+ <method name="setChecked">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var change = (aValue != (this.getAttribute('checked') == 'true'));
+ if (aValue)
+ this.setAttribute('checked', 'true');
+ else
+ this.removeAttribute('checked');
+ if (change) {
+ var event = document.createEvent('Events');
+ event.initEvent('CheckboxStateChange', true, true);
+ this.dispatchEvent(event);
+ }
+ return aValue;
+ ]]>
+ </body>
+ </method>
+
+ <!-- public implementation -->
+ <property name="checked" onset="return this.setChecked(val);"
+ onget="return this.getAttribute('checked') == 'true';"/>
+ </implementation>
+
+ <handlers>
+ <!-- While it would seem we could do this by handling oncommand, we need can't
+ because any external oncommand handlers might get called before ours, and
+ then they would see the incorrect value of checked. -->
+ <handler event="click" button="0" action="if (!this.disabled) this.checked = !this.checked;"/>
+ <handler event="keypress" key=" ">
+ <![CDATA[
+ this.checked = !this.checked;
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="checkbox-with-spacing"
+ extends="chrome://global/content/bindings/checkbox.xml#checkbox">
+
+ <content>
+ <xul:hbox class="checkbox-spacer-box">
+ <xul:image class="checkbox-check" xbl:inherits="checked,disabled"/>
+ </xul:hbox>
+ <xul:hbox class="checkbox-label-center-box" flex="1">
+ <xul:hbox class="checkbox-label-box" flex="1">
+ <xul:image class="checkbox-icon" xbl:inherits="src"/>
+ <xul:label class="checkbox-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
+ </xul:hbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/colorpicker.xml b/components/bindings/content/colorpicker.xml
new file mode 100644
index 000000000..30f8a6354
--- /dev/null
+++ b/components/bindings/content/colorpicker.xml
@@ -0,0 +1,565 @@
+<?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="colorpickerBindings"
+ 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="colorpicker" extends="chrome://global/content/bindings/general.xml#basecontrol">
+ <resources>
+ <stylesheet src="chrome://global/skin/colorpicker.css"/>
+ </resources>
+
+ <content>
+ <xul:vbox flex="1">
+
+ <xul:hbox>
+ <xul:image class="colorpickertile cp-light" color="#FFFFFF"/>
+ <xul:image class="colorpickertile cp-light" color="#FFCCCC"/>
+ <xul:image class="colorpickertile cp-light" color="#FFCC99"/>
+ <xul:image class="colorpickertile cp-light" color="#FFFF99"/>
+ <xul:image class="colorpickertile cp-light" color="#FFFFCC"/>
+ <xul:image class="colorpickertile cp-light" color="#99FF99"/>
+ <xul:image class="colorpickertile cp-light" color="#99FFFF"/>
+ <xul:image class="colorpickertile cp-light" color="#CCFFFF"/>
+ <xul:image class="colorpickertile cp-light" color="#CCCCFF"/>
+ <xul:image class="colorpickertile cp-light" color="#FFCCFF"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:image class="colorpickertile" color="#CCCCCC"/>
+ <xul:image class="colorpickertile" color="#FF6666"/>
+ <xul:image class="colorpickertile" color="#FF9966"/>
+ <xul:image class="colorpickertile cp-light" color="#FFFF66"/>
+ <xul:image class="colorpickertile cp-light" color="#FFFF33"/>
+ <xul:image class="colorpickertile cp-light" color="#66FF99"/>
+ <xul:image class="colorpickertile cp-light" color="#33FFFF"/>
+ <xul:image class="colorpickertile cp-light" color="#66FFFF"/>
+ <xul:image class="colorpickertile" color="#9999FF"/>
+ <xul:image class="colorpickertile" color="#FF99FF"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:image class="colorpickertile" color="#C0C0C0"/>
+ <xul:image class="colorpickertile" color="#FF0000"/>
+ <xul:image class="colorpickertile" color="#FF9900"/>
+ <xul:image class="colorpickertile" color="#FFCC66"/>
+ <xul:image class="colorpickertile cp-light" color="#FFFF00"/>
+ <xul:image class="colorpickertile cp-light" color="#33FF33"/>
+ <xul:image class="colorpickertile" color="#66CCCC"/>
+ <xul:image class="colorpickertile" color="#33CCFF"/>
+ <xul:image class="colorpickertile" color="#6666CC"/>
+ <xul:image class="colorpickertile" color="#CC66CC"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:image class="colorpickertile" color="#999999"/>
+ <xul:image class="colorpickertile" color="#CC0000"/>
+ <xul:image class="colorpickertile" color="#FF6600"/>
+ <xul:image class="colorpickertile" color="#FFCC33"/>
+ <xul:image class="colorpickertile" color="#FFCC00"/>
+ <xul:image class="colorpickertile" color="#33CC00"/>
+ <xul:image class="colorpickertile" color="#00CCCC"/>
+ <xul:image class="colorpickertile" color="#3366FF"/>
+ <xul:image class="colorpickertile" color="#6633FF"/>
+ <xul:image class="colorpickertile" color="#CC33CC"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:image class="colorpickertile" color="#666666"/>
+ <xul:image class="colorpickertile" color="#990000"/>
+ <xul:image class="colorpickertile" color="#CC6600"/>
+ <xul:image class="colorpickertile" color="#CC9933"/>
+ <xul:image class="colorpickertile" color="#999900"/>
+ <xul:image class="colorpickertile" color="#009900"/>
+ <xul:image class="colorpickertile" color="#339999"/>
+ <xul:image class="colorpickertile" color="#3333FF"/>
+ <xul:image class="colorpickertile" color="#6600CC"/>
+ <xul:image class="colorpickertile" color="#993399"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:image class="colorpickertile" color="#333333"/>
+ <xul:image class="colorpickertile" color="#660000"/>
+ <xul:image class="colorpickertile" color="#993300"/>
+ <xul:image class="colorpickertile" color="#996633"/>
+ <xul:image class="colorpickertile" color="#666600"/>
+ <xul:image class="colorpickertile" color="#006600"/>
+ <xul:image class="colorpickertile" color="#336666"/>
+ <xul:image class="colorpickertile" color="#000099"/>
+ <xul:image class="colorpickertile" color="#333399"/>
+ <xul:image class="colorpickertile" color="#663366"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:image class="colorpickertile" color="#000000"/>
+ <xul:image class="colorpickertile" color="#330000"/>
+ <xul:image class="colorpickertile" color="#663300"/>
+ <xul:image class="colorpickertile" color="#663333"/>
+ <xul:image class="colorpickertile" color="#333300"/>
+ <xul:image class="colorpickertile" color="#003300"/>
+ <xul:image class="colorpickertile" color="#003333"/>
+ <xul:image class="colorpickertile" color="#000066"/>
+ <xul:image class="colorpickertile" color="#330099"/>
+ <xul:image class="colorpickertile" color="#330033"/>
+ </xul:hbox>
+ </xul:vbox>
+ <!-- Something to take tab focus
+ <button style="border : 0px; width: 0px; height: 0px;"/>
+ -->
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <property name="color">
+ <getter><![CDATA[
+ return this.mSelectedCell ? this.mSelectedCell.getAttribute("color") : null;
+ ]]></getter>
+ <setter><![CDATA[
+ if (!val)
+ return val;
+ var uppercaseVal = val.toUpperCase();
+ // Translate standard HTML color strings:
+ if (uppercaseVal[0] != "#") {
+ switch (uppercaseVal) {
+ case "GREEN":
+ uppercaseVal = "#008000";
+ break;
+ case "LIME":
+ uppercaseVal = "#00FF00";
+ break;
+ case "OLIVE":
+ uppercaseVal = "#808000";
+ break;
+ case "TEAL":
+ uppercaseVal = "#008080";
+ break;
+ case "YELLOW":
+ uppercaseVal = "#FFFF00";
+ break;
+ case "RED":
+ uppercaseVal = "#FF0000";
+ break;
+ case "MAROON":
+ uppercaseVal = "#800000";
+ break;
+ case "PURPLE":
+ uppercaseVal = "#800080";
+ break;
+ case "FUCHSIA":
+ uppercaseVal = "#FF00FF";
+ break;
+ case "NAVY":
+ uppercaseVal = "#000080";
+ break;
+ case "BLUE":
+ uppercaseVal = "#0000FF";
+ break;
+ case "AQUA":
+ uppercaseVal = "#00FFFF";
+ break;
+ case "WHITE":
+ uppercaseVal = "#FFFFFF";
+ break;
+ case "SILVER":
+ uppercaseVal = "#C0C0C0";
+ break;
+ case "GRAY":
+ uppercaseVal = "#808080";
+ break;
+ default: // BLACK
+ uppercaseVal = "#000000";
+ break;
+ }
+ }
+ var cells = this.mBox.getElementsByAttribute("color", uppercaseVal);
+ if (cells.item(0)) {
+ this.selectCell(cells[0]);
+ this.hoverCell(this.mSelectedCell);
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <method name="initColor">
+ <parameter name="aColor"/>
+ <body><![CDATA[
+ // Use this to initialize color without
+ // triggering the "onselect" handler,
+ // which closes window when it's a popup
+ this.mDoOnSelect = false;
+ this.color = aColor;
+ this.mDoOnSelect = true;
+ ]]></body>
+ </method>
+
+ <method name="initialize">
+ <body><![CDATA[
+ this.mSelectedCell = null;
+ this.mHoverCell = null;
+ this.mBox = document.getAnonymousNodes(this)[0];
+ this.mIsPopup = false;
+ this.mDoOnSelect = true;
+
+ let imageEls = this.mBox.querySelectorAll("image");
+ // We set the background of the picker tiles here using images in
+ // order for the color to show up even when author colors are
+ // disabled or the user is using high contrast mode.
+ for (let el of imageEls) {
+ let dataURI = "data:image/svg+xml,<svg style='background-color: " +
+ encodeURIComponent(el.getAttribute("color")) +
+ "' xmlns='http://www.w3.org/2000/svg' />";
+ el.setAttribute("src", dataURI);
+ }
+
+ this.hoverCell(this.mBox.childNodes[0].childNodes[0]);
+
+ // used to capture keydown at the document level
+ this.mPickerKeyDown = function(aEvent)
+ {
+ document._focusedPicker.pickerKeyDown(aEvent);
+ }
+
+ ]]></body>
+ </method>
+
+ <method name="_fireEvent">
+ <parameter name="aTarget"/>
+ <parameter name="aEventName"/>
+ <body>
+ <![CDATA[
+ try {
+ var event = document.createEvent("Events");
+ event.initEvent(aEventName, true, true);
+ var cancel = !aTarget.dispatchEvent(event);
+ if (aTarget.hasAttribute("on" + aEventName)) {
+ var fn = new Function ("event", aTarget.getAttribute("on" + aEventName));
+ var rv = fn.call(aTarget, event);
+ if (rv == false)
+ cancel = true;
+ }
+ return !cancel;
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="resetHover">
+ <body><![CDATA[
+ if (this.mHoverCell)
+ this.mHoverCell.removeAttribute("hover");
+ ]]></body>
+ </method>
+
+ <method name="getColIndex">
+ <parameter name="aCell"/>
+ <body><![CDATA[
+ var cell = aCell;
+ var idx;
+ for (idx = -1; cell; idx++)
+ cell = cell.previousSibling;
+
+ return idx;
+ ]]></body>
+ </method>
+
+ <method name="isColorCell">
+ <parameter name="aCell"/>
+ <body><![CDATA[
+ return aCell && aCell.hasAttribute("color");
+ ]]></body>
+ </method>
+
+ <method name="hoverLeft">
+ <body><![CDATA[
+ var cell = this.mHoverCell.previousSibling;
+ this.hoverCell(cell);
+ ]]></body>
+ </method>
+
+ <method name="hoverRight">
+ <body><![CDATA[
+ var cell = this.mHoverCell.nextSibling;
+ this.hoverCell(cell);
+ ]]></body>
+ </method>
+
+ <method name="hoverUp">
+ <body><![CDATA[
+ var row = this.mHoverCell.parentNode.previousSibling;
+ if (row) {
+ var colIdx = this.getColIndex(this.mHoverCell);
+ var cell = row.childNodes[colIdx];
+ this.hoverCell(cell);
+ }
+ ]]></body>
+ </method>
+
+ <method name="hoverDown">
+ <body><![CDATA[
+ var row = this.mHoverCell.parentNode.nextSibling;
+ if (row) {
+ var colIdx = this.getColIndex(this.mHoverCell);
+ var cell = row.childNodes[colIdx];
+ this.hoverCell(cell);
+ }
+ ]]></body>
+ </method>
+
+ <method name="hoverTo">
+ <parameter name="aRow"/>
+ <parameter name="aCol"/>
+
+ <body><![CDATA[
+ var row = this.mBox.childNodes[aRow];
+ if (!row) return;
+ var cell = row.childNodes[aCol];
+ if (!cell) return;
+ this.hoverCell(cell);
+ ]]></body>
+ </method>
+
+ <method name="hoverCell">
+ <parameter name="aCell"/>
+
+ <body><![CDATA[
+ if (this.isColorCell(aCell)) {
+ this.resetHover();
+ aCell.setAttribute("hover", "true");
+ this.mHoverCell = aCell;
+ var event = document.createEvent('Events');
+ event.initEvent('DOMMenuItemActive', true, true);
+ aCell.dispatchEvent(event);
+ }
+ ]]></body>
+ </method>
+
+ <method name="selectHoverCell">
+ <body><![CDATA[
+ this.selectCell(this.mHoverCell);
+ ]]></body>
+ </method>
+
+ <method name="selectCell">
+ <parameter name="aCell"/>
+
+ <body><![CDATA[
+ if (this.isColorCell(aCell)) {
+ if (this.mSelectedCell)
+ this.mSelectedCell.removeAttribute("selected");
+
+ this.mSelectedCell = aCell;
+ aCell.setAttribute("selected", "true");
+
+ if (this.mDoOnSelect)
+ this._fireEvent(this, "select");
+ }
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.keyCode) {
+ case 37: // left
+ this.hoverLeft();
+ break;
+ case 38: // up
+ this.hoverUp();
+ break;
+ case 39: // right
+ this.hoverRight();
+ break;
+ case 40: // down
+ this.hoverDown();
+ break;
+ case 13: // enter
+ case 32: // space
+ this.selectHoverCell();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <constructor><![CDATA[
+ this.initialize();
+ ]]></constructor>
+
+ </implementation>
+
+ <handlers>
+ <handler event="mouseover"><![CDATA[
+ this.hoverCell(event.originalTarget);
+ ]]></handler>
+
+ <handler event="click"><![CDATA[
+ if (event.originalTarget.hasAttribute("color")) {
+ this.selectCell(event.originalTarget);
+ this.hoverCell(this.mSelectedCell);
+ }
+ ]]></handler>
+
+
+ <handler event="focus" phase="capturing">
+ <![CDATA[
+ if (!mIsPopup && this.getAttribute('focused') != 'true') {
+ this.setAttribute('focused', 'true');
+ document.addEventListener("keydown", this, true);
+ if (this.mSelectedCell)
+ this.hoverCell(this.mSelectedCell);
+ }
+ ]]>
+ </handler>
+
+ <handler event="blur" phase="capturing">
+ <![CDATA[
+ if (!mIsPopup && this.getAttribute('focused') == 'true') {
+ document.removeEventListener("keydown", this, true);
+ this.removeAttribute('focused');
+ this.resetHover();
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="colorpicker-button" display="xul:menu" role="xul:colorpicker"
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
+ <resources>
+ <stylesheet src="chrome://global/skin/colorpicker.css"/>
+ </resources>
+
+ <content>
+ <xul:image class="colorpicker-button-colorbox" anonid="colorbox" flex="1" xbl:inherits="disabled"/>
+
+ <xul:panel class="colorpicker-button-menupopup"
+ anonid="colorpopup" noautofocus="true" level="top"
+ onmousedown="event.stopPropagation()"
+ onpopupshowing="this._colorPicker.onPopupShowing()"
+ onpopuphiding="this._colorPicker.onPopupHiding()"
+ onselect="this._colorPicker.pickerChange()">
+ <xul:colorpicker xbl:inherits="palettename,disabled" allowevents="true" anonid="colorpicker"/>
+ </xul:panel>
+ </content>
+
+ <implementation>
+ <property name="open"
+ onget="return this.getAttribute('open') == 'true'"
+ onset="this.showPopup();"/>
+ <property name="color">
+ <getter><![CDATA[
+ return this.getAttribute("color");
+ ]]></getter>
+ <setter><![CDATA[
+ this.mColorBox.setAttribute("src",
+ "data:image/svg+xml,<svg style='background-color: " +
+ encodeURIComponent(val) +
+ "' xmlns='http://www.w3.org/2000/svg' />");
+ this.setAttribute("color", val);
+ return val;
+ ]]></setter>
+ </property>
+
+ <method name="initialize">
+ <body><![CDATA[
+ this.mColorBox = document.getAnonymousElementByAttribute(this, "anonid", "colorbox");
+ this.mColorBox.setAttribute("src",
+ "data:image/svg+xml,<svg style='background-color: " +
+ encodeURIComponent(this.color) +
+ "' xmlns='http://www.w3.org/2000/svg' />");
+
+ var popup = document.getAnonymousElementByAttribute(this, "anonid", "colorpopup")
+ popup._colorPicker = this;
+
+ this.mPicker = document.getAnonymousElementByAttribute(this, "anonid", "colorpicker")
+ ]]></body>
+ </method>
+
+ <method name="_fireEvent">
+ <parameter name="aTarget"/>
+ <parameter name="aEventName"/>
+ <body>
+ <![CDATA[
+ try {
+ var event = document.createEvent("Events");
+ event.initEvent(aEventName, true, true);
+ var cancel = !aTarget.dispatchEvent(event);
+ if (aTarget.hasAttribute("on" + aEventName)) {
+ var fn = new Function ("event", aTarget.getAttribute("on" + aEventName));
+ var rv = fn.call(aTarget, event);
+ if (rv == false)
+ cancel = true;
+ }
+ return !cancel;
+ }
+ catch (e) {
+ dump(e);
+ }
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="showPopup">
+ <body><![CDATA[
+ this.mPicker.parentNode.openPopup(this, "after_start", 0, 0, false, false);
+ ]]></body>
+ </method>
+
+ <method name="hidePopup">
+ <body><![CDATA[
+ this.mPicker.parentNode.hidePopup();
+ ]]></body>
+ </method>
+
+ <method name="onPopupShowing">
+ <body><![CDATA[
+ if ("resetHover" in this.mPicker)
+ this.mPicker.resetHover();
+ document.addEventListener("keydown", this.mPicker, true);
+ this.mPicker.mIsPopup = true;
+ // Initialize to current button's color
+ this.mPicker.initColor(this.color);
+ ]]></body>
+ </method>
+
+ <method name="onPopupHiding">
+ <body><![CDATA[
+ // Removes the key listener
+ document.removeEventListener("keydown", this.mPicker, true);
+ this.mPicker.mIsPopup = false;
+ ]]></body>
+ </method>
+
+ <method name="pickerChange">
+ <body><![CDATA[
+ this.color = this.mPicker.color;
+ setTimeout(function(aPopup) { aPopup.hidePopup() }, 1, this.mPicker.parentNode);
+
+ this._fireEvent(this, "change");
+ ]]></body>
+ </method>
+
+ <constructor><![CDATA[
+ this.initialize();
+ ]]></constructor>
+
+ </implementation>
+
+ <handlers>
+ <handler event="keydown"><![CDATA[
+ // open popup if key is space/up/left/right/down and popup is closed
+ if ( (event.keyCode == 32 || (event.keyCode > 36 && event.keyCode < 41)) && !this.open)
+ this.showPopup();
+ else if ( (event.keyCode == 27) && this.open)
+ this.hidePopup();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="colorpickertile" role="xul:colorpickertile">
+ </binding>
+
+</bindings>
+
diff --git a/components/bindings/content/datekeeper.js b/components/bindings/content/datekeeper.js
new file mode 100644
index 000000000..5d70416a9
--- /dev/null
+++ b/components/bindings/content/datekeeper.js
@@ -0,0 +1,336 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+/**
+ * DateKeeper keeps track of the date states.
+ */
+function DateKeeper(props) {
+ this.init(props);
+}
+
+{
+ const DAYS_IN_A_WEEK = 7,
+ MONTHS_IN_A_YEAR = 12,
+ YEAR_VIEW_SIZE = 200,
+ YEAR_BUFFER_SIZE = 10,
+ // The min value is 0001-01-01 based on HTML spec:
+ // https://html.spec.whatwg.org/#valid-date-string
+ MIN_DATE = -62135596800000,
+ // The max value is derived from the ECMAScript spec (275760-09-13):
+ // http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
+ MAX_DATE = 8640000000000000,
+ MAX_YEAR = 275760,
+ MAX_MONTH = 9;
+
+ DateKeeper.prototype = {
+ get year() {
+ return this.state.dateObj.getUTCFullYear();
+ },
+
+ get month() {
+ return this.state.dateObj.getUTCMonth();
+ },
+
+ get selection() {
+ return this.state.selection;
+ },
+
+ /**
+ * Initialize DateKeeper
+ * @param {Number} year
+ * @param {Number} month
+ * @param {Number} day
+ * @param {Number} min
+ * @param {Number} max
+ * @param {Number} step
+ * @param {Number} stepBase
+ * @param {Number} firstDayOfWeek
+ * @param {Array<Number>} weekends
+ * @param {Number} calViewSize
+ */
+ init({ year, month, day, min, max, step, stepBase, firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) {
+ const today = new Date();
+
+ this.state = {
+ step, firstDayOfWeek, weekends, calViewSize,
+ // min & max are NaN if empty or invalid
+ min: new Date(Number.isNaN(min) ? MIN_DATE : min),
+ max: new Date(Number.isNaN(max) ? MAX_DATE : max),
+ stepBase: new Date(stepBase),
+ today: this._newUTCDate(today.getFullYear(), today.getMonth(), today.getDate()),
+ weekHeaders: this._getWeekHeaders(firstDayOfWeek, weekends),
+ years: [],
+ dateObj: new Date(0),
+ selection: { year, month, day },
+ };
+
+ this.setCalendarMonth({
+ year: year === undefined ? today.getFullYear() : year,
+ month: month === undefined ? today.getMonth() : month
+ });
+ },
+ /**
+ * Set new calendar month. The year is always treated as full year, so the
+ * short-form is not supported.
+ * @param {Object} date parts
+ * {
+ * {Number} year [optional]
+ * {Number} month [optional]
+ * }
+ */
+ setCalendarMonth({ year = this.year, month = this.month }) {
+ // Make sure the date is valid before setting.
+ // Use setUTCFullYear so that year 99 doesn't get parsed as 1999
+ if (year > MAX_YEAR || year === MAX_YEAR && month >= MAX_MONTH) {
+ this.state.dateObj.setUTCFullYear(MAX_YEAR, MAX_MONTH - 1, 1);
+ } else if (year < 1 || year === 1 && month < 0) {
+ this.state.dateObj.setUTCFullYear(1, 0, 1);
+ } else {
+ this.state.dateObj.setUTCFullYear(year, month, 1);
+ }
+ },
+
+ /**
+ * Set selection date
+ * @param {Number} year
+ * @param {Number} month
+ * @param {Number} day
+ */
+ setSelection({ year, month, day }) {
+ this.state.selection.year = year;
+ this.state.selection.month = month;
+ this.state.selection.day = day;
+ },
+
+ /**
+ * Set month. Makes sure the day is <= the last day of the month
+ * @param {Number} month
+ */
+ setMonth(month) {
+ this.setCalendarMonth({ year: this.year, month });
+ },
+
+ /**
+ * Set year. Makes sure the day is <= the last day of the month
+ * @param {Number} year
+ */
+ setYear(year) {
+ this.setCalendarMonth({ year, month: this.month });
+ },
+
+ /**
+ * Set month by offset. Makes sure the day is <= the last day of the month
+ * @param {Number} offset
+ */
+ setMonthByOffset(offset) {
+ this.setCalendarMonth({ year: this.year, month: this.month + offset });
+ },
+
+ /**
+ * Generate the array of months
+ * @return {Array<Object>}
+ * {
+ * {Number} value: Month in int
+ * {Boolean} enabled
+ * }
+ */
+ getMonths() {
+ let months = [];
+
+ for (let i = 0; i < MONTHS_IN_A_YEAR; i++) {
+ months.push({
+ value: i,
+ enabled: true
+ });
+ }
+
+ return months;
+ },
+
+ /**
+ * Generate the array of years
+ * @return {Array<Object>}
+ * {
+ * {Number} value: Year in int
+ * {Boolean} enabled
+ * }
+ */
+ getYears() {
+ let years = [];
+
+ const firstItem = this.state.years[0];
+ const lastItem = this.state.years[this.state.years.length - 1];
+ const currentYear = this.year;
+
+ // Generate new years array when the year is outside of the first &
+ // last item range. If not, return the cached result.
+ if (!firstItem || !lastItem ||
+ currentYear <= firstItem.value + YEAR_BUFFER_SIZE ||
+ currentYear >= lastItem.value - YEAR_BUFFER_SIZE) {
+ // The year is set in the middle with items on both directions
+ for (let i = -(YEAR_VIEW_SIZE / 2); i < YEAR_VIEW_SIZE / 2; i++) {
+ const year = currentYear + i;
+ if (year >= 1 && year <= MAX_YEAR) {
+ years.push({
+ value: year,
+ enabled: true
+ });
+ }
+ }
+ this.state.years = years;
+ }
+ return this.state.years;
+ },
+
+ /**
+ * Get days for calendar
+ * @return {Array<Object>}
+ * {
+ * {Date} dateObj
+ * {Number} content
+ * {Array<String>} classNames
+ * {Boolean} enabled
+ * }
+ */
+ getDays() {
+ const firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek);
+ const month = this.month;
+ let days = [];
+
+ for (let i = 0; i < this.state.calViewSize; i++) {
+ const dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(),
+ firstDayOfMonth.getUTCMonth(),
+ firstDayOfMonth.getUTCDate() + i);
+
+ let classNames = [];
+ let enabled = true;
+
+ const isValid = dateObj.getTime() >= MIN_DATE && dateObj.getTime() <= MAX_DATE;
+ if (!isValid) {
+ classNames.push("out-of-range");
+ enabled = false;
+
+ days.push({
+ classNames,
+ enabled,
+ });
+ continue;
+ }
+
+ const isWeekend = this.state.weekends.includes(dateObj.getUTCDay());
+ const isCurrentMonth = month == dateObj.getUTCMonth();
+ const isSelection = this.state.selection.year == dateObj.getUTCFullYear() &&
+ this.state.selection.month == dateObj.getUTCMonth() &&
+ this.state.selection.day == dateObj.getUTCDate();
+ const isOutOfRange = dateObj.getTime() < this.state.min.getTime() ||
+ dateObj.getTime() > this.state.max.getTime();
+ const isToday = this.state.today.getTime() == dateObj.getTime();
+ const isOffStep = this._checkIsOffStep(dateObj,
+ this._newUTCDate(dateObj.getUTCFullYear(),
+ dateObj.getUTCMonth(),
+ dateObj.getUTCDate() + 1));
+
+ if (isWeekend) {
+ classNames.push("weekend");
+ }
+ if (!isCurrentMonth) {
+ classNames.push("outside");
+ }
+ if (isSelection && !isOutOfRange && !isOffStep) {
+ classNames.push("selection");
+ }
+ if (isOutOfRange) {
+ classNames.push("out-of-range");
+ enabled = false;
+ }
+ if (isToday) {
+ classNames.push("today");
+ }
+ if (isOffStep) {
+ classNames.push("off-step");
+ enabled = false;
+ }
+ days.push({
+ dateObj,
+ content: dateObj.getUTCDate(),
+ classNames,
+ enabled,
+ });
+ }
+ return days;
+ },
+
+ /**
+ * Check if a date is off step given a starting point and the next increment
+ * @param {Date} start
+ * @param {Date} next
+ * @return {Boolean}
+ */
+ _checkIsOffStep(start, next) {
+ // If the increment is larger or equal to the step, it must not be off-step.
+ if (next - start >= this.state.step) {
+ return false;
+ }
+ // Calculate the last valid date
+ const lastValidStep = Math.floor((next - 1 - this.state.stepBase) / this.state.step);
+ const lastValidTimeInMs = lastValidStep * this.state.step + this.state.stepBase.getTime();
+ // The date is off-step if the last valid date is smaller than the start date
+ return lastValidTimeInMs < start.getTime();
+ },
+
+ /**
+ * Get week headers for calendar
+ * @param {Number} firstDayOfWeek
+ * @param {Array<Number>} weekends
+ * @return {Array<Object>}
+ * {
+ * {Number} content
+ * {Array<String>} classNames
+ * }
+ */
+ _getWeekHeaders(firstDayOfWeek, weekends) {
+ let headers = [];
+ let dayOfWeek = firstDayOfWeek;
+
+ for (let i = 0; i < DAYS_IN_A_WEEK; i++) {
+ headers.push({
+ content: dayOfWeek % DAYS_IN_A_WEEK,
+ classNames: weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : []
+ });
+ dayOfWeek++;
+ }
+ return headers;
+ },
+
+ /**
+ * Get the first day on a calendar month
+ * @param {Date} dateObj
+ * @param {Number} firstDayOfWeek
+ * @return {Date}
+ */
+ _getFirstCalendarDate(dateObj, firstDayOfWeek) {
+ const daysOffset = 1 - DAYS_IN_A_WEEK;
+ let firstDayOfMonth = this._newUTCDate(dateObj.getUTCFullYear(), dateObj.getUTCMonth());
+ let dayOfWeek = firstDayOfMonth.getUTCDay();
+
+ return this._newUTCDate(
+ firstDayOfMonth.getUTCFullYear(),
+ firstDayOfMonth.getUTCMonth(),
+ // When first calendar date is the same as first day of the week, add
+ // another row on top of it.
+ firstDayOfWeek == dayOfWeek ? daysOffset : (firstDayOfWeek - dayOfWeek + daysOffset) % DAYS_IN_A_WEEK);
+ },
+
+ /**
+ * Helper function for creating UTC dates
+ * @param {...[Number]} parts
+ * @return {Date}
+ */
+ _newUTCDate(...parts) {
+ return new Date(new Date(0).setUTCFullYear(...parts));
+ },
+ };
+}
diff --git a/components/bindings/content/datepicker.js b/components/bindings/content/datepicker.js
new file mode 100644
index 000000000..0e9c9a6e6
--- /dev/null
+++ b/components/bindings/content/datepicker.js
@@ -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/. */
+
+"use strict";
+
+function DatePicker(context) {
+ this.context = context;
+ this._attachEventListeners();
+}
+
+{
+ const CAL_VIEW_SIZE = 42;
+
+ DatePicker.prototype = {
+ /**
+ * Initializes the date picker. Set the default states and properties.
+ * @param {Object} props
+ * {
+ * {Number} year [optional]
+ * {Number} month [optional]
+ * {Number} date [optional]
+ * {Number} min
+ * {Number} max
+ * {Number} step
+ * {Number} stepBase
+ * {Number} firstDayOfWeek
+ * {Array<Number>} weekends
+ * {Array<String>} monthStrings
+ * {Array<String>} weekdayStrings
+ * {String} locale [optional]: User preferred locale
+ * }
+ */
+ init(props = {}) {
+ this.props = props;
+ this._setDefaultState();
+ this._createComponents();
+ this._update();
+ document.dispatchEvent(new CustomEvent("PickerReady"));
+ },
+
+ /*
+ * Set initial date picker states.
+ */
+ _setDefaultState() {
+ const { year, month, day, min, max, step, stepBase, firstDayOfWeek, weekends,
+ monthStrings, weekdayStrings, locale } = this.props;
+ const dateKeeper = new DateKeeper({
+ year, month, day, min, max, step, stepBase, firstDayOfWeek, weekends,
+ calViewSize: CAL_VIEW_SIZE
+ });
+
+ this.state = {
+ dateKeeper,
+ locale,
+ isMonthPickerVisible: false,
+ getDayString: day => day ? new Intl.NumberFormat(locale).format(day) : "",
+ getWeekHeaderString: weekday => weekdayStrings[weekday],
+ getMonthString: month => monthStrings[month],
+ setSelection: date => {
+ dateKeeper.setSelection({
+ year: date.getUTCFullYear(),
+ month: date.getUTCMonth(),
+ day: date.getUTCDate(),
+ });
+ this._update();
+ this._dispatchState();
+ this._closePopup();
+ },
+ setYear: year => {
+ dateKeeper.setYear(year);
+ dateKeeper.setSelection({
+ year,
+ month: dateKeeper.selection.month,
+ day: dateKeeper.selection.day,
+ });
+ this._update();
+ this._dispatchState();
+ },
+ setMonth: month => {
+ dateKeeper.setMonth(month);
+ dateKeeper.setSelection({
+ year: dateKeeper.selection.year,
+ month,
+ day: dateKeeper.selection.day,
+ });
+ this._update();
+ this._dispatchState();
+ },
+ toggleMonthPicker: () => {
+ this.state.isMonthPickerVisible = !this.state.isMonthPickerVisible;
+ this._update();
+ }
+ };
+ },
+
+ /**
+ * Initalize the date picker components.
+ */
+ _createComponents() {
+ this.components = {
+ calendar: new Calendar({
+ calViewSize: CAL_VIEW_SIZE,
+ locale: this.state.locale,
+ setSelection: this.state.setSelection,
+ getDayString: this.state.getDayString,
+ getWeekHeaderString: this.state.getWeekHeaderString
+ }, {
+ weekHeader: this.context.weekHeader,
+ daysView: this.context.daysView
+ }),
+ monthYear: new MonthYear({
+ setYear: this.state.setYear,
+ setMonth: this.state.setMonth,
+ getMonthString: this.state.getMonthString,
+ locale: this.state.locale
+ }, {
+ monthYear: this.context.monthYear,
+ monthYearView: this.context.monthYearView
+ })
+ };
+ },
+
+ /**
+ * Update date picker and its components.
+ */
+ _update(options = {}) {
+ const { dateKeeper, isMonthPickerVisible } = this.state;
+
+ if (isMonthPickerVisible) {
+ this.state.months = dateKeeper.getMonths();
+ this.state.years = dateKeeper.getYears();
+ } else {
+ this.state.days = dateKeeper.getDays();
+ }
+
+ this.components.monthYear.setProps({
+ isVisible: isMonthPickerVisible,
+ dateObj: dateKeeper.state.dateObj,
+ months: this.state.months,
+ years: this.state.years,
+ toggleMonthPicker: this.state.toggleMonthPicker,
+ noSmoothScroll: options.noSmoothScroll
+ });
+ this.components.calendar.setProps({
+ isVisible: !isMonthPickerVisible,
+ days: this.state.days,
+ weekHeaders: dateKeeper.state.weekHeaders
+ });
+
+ isMonthPickerVisible ?
+ this.context.monthYearView.classList.remove("hidden") :
+ this.context.monthYearView.classList.add("hidden");
+ },
+
+ /**
+ * Use postMessage to close the picker.
+ */
+ _closePopup() {
+ window.postMessage({
+ name: "ClosePopup"
+ }, "*");
+ },
+
+ /**
+ * Use postMessage to pass the state of picker to the panel.
+ */
+ _dispatchState() {
+ const { year, month, day } = this.state.dateKeeper.selection;
+ // The panel is listening to window for postMessage event, so we
+ // do postMessage to itself to send data to input boxes.
+ window.postMessage({
+ name: "PickerPopupChanged",
+ detail: {
+ year,
+ month,
+ day,
+ }
+ }, "*");
+ },
+
+ /**
+ * Attach event listeners
+ */
+ _attachEventListeners() {
+ window.addEventListener("message", this);
+ document.addEventListener("mouseup", this, { passive: true });
+ document.addEventListener("mousedown", this);
+ },
+
+ /**
+ * Handle events.
+ *
+ * @param {Event} event
+ */
+ handleEvent(event) {
+ switch (event.type) {
+ case "message": {
+ this.handleMessage(event);
+ break;
+ }
+ case "mousedown": {
+ // Use preventDefault to keep focus on input boxes
+ event.preventDefault();
+ event.target.setCapture();
+
+ if (event.target == this.context.buttonLeft) {
+ event.target.classList.add("active");
+ this.state.dateKeeper.setMonthByOffset(-1);
+ this._update();
+ } else if (event.target == this.context.buttonRight) {
+ event.target.classList.add("active");
+ this.state.dateKeeper.setMonthByOffset(1);
+ this._update();
+ }
+ break;
+ }
+ case "mouseup": {
+ if (event.target == this.context.buttonLeft || event.target == this.context.buttonRight) {
+ event.target.classList.remove("active");
+ }
+
+ }
+ }
+ },
+
+ /**
+ * Handle postMessage events.
+ *
+ * @param {Event} event
+ */
+ handleMessage(event) {
+ switch (event.data.name) {
+ case "PickerSetValue": {
+ this.set(event.data.detail);
+ break;
+ }
+ case "PickerInit": {
+ this.init(event.data.detail);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Set the date state and update the components with the new state.
+ *
+ * @param {Object} dateState
+ * {
+ * {Number} year [optional]
+ * {Number} month [optional]
+ * {Number} date [optional]
+ * }
+ */
+ set({ year, month, day }) {
+ const { dateKeeper } = this.state;
+
+ dateKeeper.setCalendarMonth({
+ year, month
+ });
+ dateKeeper.setSelection({
+ year, month, day
+ });
+ this._update({ noSmoothScroll: true });
+ }
+ };
+
+ /**
+ * MonthYear is a component that handles the month & year spinners
+ *
+ * @param {Object} options
+ * {
+ * {String} locale
+ * {Function} setYear
+ * {Function} setMonth
+ * {Function} getMonthString
+ * }
+ * @param {DOMElement} context
+ */
+ function MonthYear(options, context) {
+ const spinnerSize = 5;
+ const yearFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric",
+ timeZone: "UTC" }).format;
+ const dateFormat = new Intl.DateTimeFormat(options.locale, { year: "numeric",
+ month: "long",
+ timeZone: "UTC" }).format;
+ this.context = context;
+ this.state = { dateFormat };
+ this.props = {};
+ this.components = {
+ month: new Spinner({
+ setValue: month => {
+ this.state.isMonthSet = true;
+ options.setMonth(month);
+ },
+ getDisplayString: options.getMonthString,
+ viewportSize: spinnerSize
+ }, context.monthYearView),
+ year: new Spinner({
+ setValue: year => {
+ this.state.isYearSet = true;
+ options.setYear(year);
+ },
+ getDisplayString: year => yearFormat(new Date(new Date(0).setUTCFullYear(year))),
+ viewportSize: spinnerSize
+ }, context.monthYearView)
+ };
+
+ this._attachEventListeners();
+ }
+
+ MonthYear.prototype = {
+
+ /**
+ * Set new properties and pass them to components
+ *
+ * @param {Object} props
+ * {
+ * {Boolean} isVisible
+ * {Date} dateObj
+ * {Array<Object>} months
+ * {Array<Object>} years
+ * {Function} toggleMonthPicker
+ * }
+ */
+ setProps(props) {
+ this.context.monthYear.textContent = this.state.dateFormat(props.dateObj);
+
+ if (props.isVisible) {
+ this.context.monthYear.classList.add("active");
+ this.components.month.setState({
+ value: props.dateObj.getUTCMonth(),
+ items: props.months,
+ isInfiniteScroll: true,
+ isValueSet: this.state.isMonthSet,
+ smoothScroll: !(this.state.firstOpened || props.noSmoothScroll)
+ });
+ this.components.year.setState({
+ value: props.dateObj.getUTCFullYear(),
+ items: props.years,
+ isInfiniteScroll: false,
+ isValueSet: this.state.isYearSet,
+ smoothScroll: !(this.state.firstOpened || props.noSmoothScroll)
+ });
+ this.state.firstOpened = false;
+ } else {
+ this.context.monthYear.classList.remove("active");
+ this.state.isMonthSet = false;
+ this.state.isYearSet = false;
+ this.state.firstOpened = true;
+ }
+
+ this.props = Object.assign(this.props, props);
+ },
+
+ /**
+ * Handle events
+ * @param {DOMEvent} event
+ */
+ handleEvent(event) {
+ switch (event.type) {
+ case "click": {
+ this.props.toggleMonthPicker();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Attach event listener to monthYear button
+ */
+ _attachEventListeners() {
+ this.context.monthYear.addEventListener("click", this);
+ }
+ };
+}
diff --git a/components/bindings/content/datetimebox.css b/components/bindings/content/datetimebox.css
new file mode 100644
index 000000000..ce638078f
--- /dev/null
+++ b/components/bindings/content/datetimebox.css
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url("http://www.w3.org/1999/xhtml");
+@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+.datetime-input-box-wrapper {
+ -moz-appearance: none;
+ display: inline-flex;
+ flex: 1;
+ cursor: default;
+ background-color: inherit;
+ color: inherit;
+ min-width: 0;
+ justify-content: space-between;
+}
+
+.datetime-input-edit-wrapper {
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.datetime-input {
+ -moz-appearance: none;
+ text-align: center;
+ padding: 0;
+ border: 0;
+ margin: 0;
+ ime-mode: disabled;
+ cursor: default;
+ -moz-user-select: none;
+}
+
+.datetime-separator {
+ margin: 0 !important;
+}
+
+.datetime-input[readonly],
+.datetime-input[disabled] {
+ color: GrayText;
+ -moz-user-select: none;
+}
+
+.datetime-reset-button {
+ background-image: url(chrome://global/skin/icons/input-clear.svg);
+ background-color: transparent;
+ background-repeat: no-repeat;
+ background-size: 12px, 12px;
+ border: none;
+ height: 12px;
+ width: 12px;
+ align-self: center;
+ flex: none;
+}
diff --git a/components/bindings/content/datetimebox.xml b/components/bindings/content/datetimebox.xml
new file mode 100644
index 000000000..94574038a
--- /dev/null
+++ b/components/bindings/content/datetimebox.xml
@@ -0,0 +1,1443 @@
+<?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 % datetimeboxDTD SYSTEM "chrome://global/locale/datetimebox.dtd">
+%datetimeboxDTD;
+]>
+
+<!--
+TODO
+Bug 1446342:
+Input type="date" not working if the other form elements has name="document"
+
+Any alternative solution:
+document === window.document
+document === this.ownerDocument
+-->
+
+<bindings id="datetimeboxBindings"
+ 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="date-input"
+ extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base">
+ <resources>
+ <stylesheet src="chrome://global/content/textbox.css"/>
+ <stylesheet src="chrome://global/skin/textbox.css"/>
+ <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
+ </resources>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ /* eslint-disable no-multi-spaces */
+ this.mYearPlaceHolder = ]]>"&date.year.placeholder;"<![CDATA[;
+ this.mMonthPlaceHolder = ]]>"&date.month.placeholder;"<![CDATA[;
+ this.mDayPlaceHolder = ]]>"&date.day.placeholder;"<![CDATA[;
+ this.mSeparatorText = "/";
+ /* eslint-enable no-multi-spaces */
+
+ this.mMinMonth = 1;
+ this.mMaxMonth = 12;
+ this.mMinDay = 1;
+ this.mMaxDay = 31;
+ this.mMinYear = 1;
+ // Maximum year limited by ECMAScript date object range, year <= 275760.
+ this.mMaxYear = 275760;
+ this.mMonthDayLength = 2;
+ this.mYearLength = 4;
+ this.mMonthPageUpDownInterval = 3;
+ this.mDayPageUpDownInterval = 7;
+ this.mYearPageUpDownInterval = 10;
+
+ // Default to en-US, month-day-year order.
+ this.mMonthField =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-one");
+ this.mDayField =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-two");
+ this.mYearField =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-three");
+ this.mYearField.size = this.mYearLength;
+ this.mYearField.maxLength = this.mMaxYear.toString().length;
+
+ this.mMonthField.placeholder = this.mMonthPlaceHolder;
+ this.mDayField.placeholder = this.mDayPlaceHolder;
+ this.mYearField.placeholder = this.mYearPlaceHolder;
+
+ this.mMonthField.setAttribute("min", this.mMinMonth);
+ this.mMonthField.setAttribute("max", this.mMaxMonth);
+ this.mMonthField.setAttribute("pginterval",
+ this.mMonthPageUpDownInterval);
+ this.mDayField.setAttribute("min", this.mMinDay);
+ this.mDayField.setAttribute("max", this.mMaxDay);
+ this.mDayField.setAttribute("pginterval", this.mDayPageUpDownInterval);
+ this.mYearField.setAttribute("min", this.mMinYear);
+ this.mYearField.setAttribute("max", this.mMaxYear);
+ this.mYearField.setAttribute("pginterval",
+ this.mYearPageUpDownInterval);
+
+ this.mDaySeparator =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
+ this.mDaySeparator.textContent = this.mSeparatorText;
+ this.mYearSeparator =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
+ this.mYearSeparator.textContent = this.mSeparatorText;
+
+ if (this.mInputElement.value) {
+ this.setFieldsFromInputValue();
+ }
+ this.updateResetButtonVisibility();
+ ]]>
+ </constructor>
+
+ <method name="clearInputFields">
+ <parameter name="aFromInputElement"/>
+ <body>
+ <![CDATA[
+ this.log("clearInputFields");
+
+ if (this.isDisabled() || this.isReadonly()) {
+ return;
+ }
+
+ if (this.mMonthField && !this.mMonthField.disabled &&
+ !this.mMonthField.readOnly) {
+ this.mMonthField.value = "";
+ this.mMonthField.setAttribute("typeBuffer", "");
+ }
+
+ if (this.mDayField && !this.mDayField.disabled &&
+ !this.mDayField.readOnly) {
+ this.mDayField.value = "";
+ this.mDayField.setAttribute("typeBuffer", "");
+ }
+
+ if (this.mYearField && !this.mYearField.disabled &&
+ !this.mYearField.readOnly) {
+ this.mYearField.value = "";
+ this.mYearField.setAttribute("typeBuffer", "");
+ }
+
+ if (!aFromInputElement && this.mInputElement.value) {
+ this.mInputElement.setUserInput("");
+ }
+
+ this.updateResetButtonVisibility();
+ ]]>
+ </body>
+ </method>
+
+ <method name="setFieldsFromInputValue">
+ <body>
+ <![CDATA[
+ let value = this.mInputElement.value;
+ if (!value) {
+ this.clearInputFields(true);
+ return;
+ }
+
+ this.log("setFieldsFromInputValue: " + value);
+ let [year, month, day] = value.split("-");
+
+ this.setFieldValue(this.mYearField, year);
+ this.setFieldValue(this.mMonthField, month);
+ this.setFieldValue(this.mDayField, day);
+
+ this.notifyPicker();
+ ]]>
+ </body>
+ </method>
+
+ <method name="getDaysInMonth">
+ <parameter name="aMonth"/>
+ <parameter name="aYear"/>
+ <body>
+ <![CDATA[
+ // Javascript's month is 0-based, so this means last day of the
+ // previous month.
+ return new Date(aYear, aMonth, 0).getDate();
+ ]]>
+ </body>
+ </method>
+
+ <method name="isFieldInvalid">
+ <parameter name="aField"/>
+ <body>
+ <![CDATA[
+ if (this.isEmpty(aField.value)) {
+ return true;
+ }
+
+ let min = Number(aField.getAttribute("min"));
+ let max = Number(aField.getAttribute("max"));
+
+ if (Number(aField.value) < min || Number(aField.value) > max) {
+ return true;
+ }
+
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="setInputValueFromFields">
+ <body>
+ <![CDATA[
+ if (!this.isAnyValueAvailable(false) && this.mInputElement.value) {
+ // Values in the input box was cleared, clear the input element's
+ // value if not empty.
+ this.mInputElement.setUserInput("");
+ return;
+ }
+
+ if (this.isFieldInvalid(this.mYearField) ||
+ this.isFieldInvalid(this.mMonthField) ||
+ this.isFieldInvalid(this.mDayField)) {
+ // We still need to notify picker in case any of the field has
+ // changed. If we can set input element value, then notifyPicker
+ // will be called in setFieldsFromInputValue().
+ this.notifyPicker();
+ return;
+ }
+
+ let year = this.mYearField.value;
+ let month = this.mMonthField.value;
+ let day = this.mDayField.value;
+
+ if (day > this.getDaysInMonth(month, year)) {
+ // Don't set invalid date, otherwise input element's value will be
+ // set to empty.
+ return;
+ }
+
+ let date = [year, month, day].join("-");
+
+ if (date == this.mInputElement.value) {
+ return;
+ }
+
+ this.log("setInputValueFromFields: " + date);
+ this.mInputElement.setUserInput(date);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setFieldsFromPicker">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ let year = aValue.year;
+ let month = aValue.month;
+ let day = aValue.day;
+
+ if (!this.isEmpty(year)) {
+ this.setFieldValue(this.mYearField, year);
+ }
+
+ if (!this.isEmpty(month)) {
+ this.setFieldValue(this.mMonthField, month);
+ }
+
+ if (!this.isEmpty(day)) {
+ this.setFieldValue(this.mDayField, day);
+ }
+
+ // Update input element's .value if needed.
+ this.setInputValueFromFields();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleKeypress">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (this.isDisabled() || this.isReadonly()) {
+ return;
+ }
+
+ let targetField = aEvent.originalTarget;
+ let key = aEvent.key;
+
+ if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) {
+ let buffer = targetField.getAttribute("typeBuffer") || "";
+
+ buffer = buffer.concat(key);
+ this.setFieldValue(targetField, buffer);
+ targetField.select();
+
+ let n = Number(buffer);
+ let max = targetField.getAttribute("max");
+ if (buffer.length >= targetField.maxLength || n * 10 > max) {
+ buffer = "";
+ this.advanceToNextField();
+ }
+ targetField.setAttribute("typeBuffer", buffer);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="incrementFieldValue">
+ <parameter name="aTargetField"/>
+ <parameter name="aTimes"/>
+ <body>
+ <![CDATA[
+ let value;
+
+ // Use current date if field is empty.
+ if (this.isEmpty(aTargetField.value)) {
+ let now = new Date();
+
+ if (aTargetField == this.mYearField) {
+ value = now.getFullYear();
+ } else if (aTargetField == this.mMonthField) {
+ value = now.getMonth() + 1;
+ } else if (aTargetField == this.mDayField) {
+ value = now.getDate();
+ } else {
+ this.log("Field not supported in incrementFieldValue.");
+ return;
+ }
+ } else {
+ value = Number(aTargetField.value);
+ }
+
+ let min = Number(aTargetField.getAttribute("min"));
+ let max = Number(aTargetField.getAttribute("max"));
+
+ value += Number(aTimes);
+ if (value > max) {
+ value -= (max - min + 1);
+ } else if (value < min) {
+ value += (max - min + 1);
+ }
+ this.setFieldValue(aTargetField, value);
+ aTargetField.select();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleKeyboardNav">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (this.isDisabled() || this.isReadonly()) {
+ return;
+ }
+
+ let targetField = aEvent.originalTarget;
+ let key = aEvent.key;
+
+ // Home/End key does nothing on year field.
+ if (targetField == this.mYearField && (key == "Home" ||
+ key == "End")) {
+ return;
+ }
+
+ switch (key) {
+ case "ArrowUp":
+ this.incrementFieldValue(targetField, 1);
+ break;
+ case "ArrowDown":
+ this.incrementFieldValue(targetField, -1);
+ break;
+ case "PageUp": {
+ let interval = targetField.getAttribute("pginterval");
+ this.incrementFieldValue(targetField, interval);
+ break;
+ }
+ case "PageDown": {
+ let interval = targetField.getAttribute("pginterval");
+ this.incrementFieldValue(targetField, 0 - interval);
+ break;
+ }
+ case "Home":
+ let min = targetField.getAttribute("min");
+ this.setFieldValue(targetField, min);
+ targetField.select();
+ break;
+ case "End":
+ let max = targetField.getAttribute("max");
+ this.setFieldValue(targetField, max);
+ targetField.select();
+ break;
+ }
+ this.setInputValueFromFields();
+ ]]>
+ </body>
+ </method>
+
+ <method name="getCurrentValue">
+ <body>
+ <![CDATA[
+ let year;
+ if (!this.isEmpty(this.mYearField.value)) {
+ year = Number(this.mYearField.value);
+ }
+
+ let month;
+ if (!this.isEmpty(this.mMonthField.value)) {
+ month = Number(this.mMonthField.value);
+ }
+
+ let day;
+ if (!this.isEmpty(this.mDayField.value)) {
+ day = Number(this.mDayField.value);
+ }
+
+ let date = { year, month, day };
+
+ this.log("getCurrentValue: " + JSON.stringify(date));
+ return date;
+ ]]>
+ </body>
+ </method>
+
+ <method name="setFieldValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ let value = Number(aValue);
+ if (isNaN(value)) {
+ this.log("NaN on setFieldValue!");
+ return;
+ }
+
+ if (aValue.length == aField.maxLength) {
+ let min = Number(aField.getAttribute("min"));
+ let max = Number(aField.getAttribute("max"));
+
+ if (aValue < min) {
+ value = min;
+ } else if (aValue > max) {
+ value = max;
+ }
+ }
+
+ if (aField == this.mMonthField ||
+ aField == this.mDayField) {
+ // prepend zero
+ if (value < 10) {
+ value = "0" + value;
+ }
+ } else {
+ // prepend zeroes
+ if (value < 10) {
+ value = "000" + value;
+ } else if (value < 100) {
+ value = "00" + value;
+ } else if (value < 1000) {
+ value = "0" + value;
+ }
+
+ if (value.toString().length > this.mYearLength &&
+ value.toString().length <= this.mMaxYear.toString().length) {
+ this.mYearField.size = value.toString().length;
+ }
+ }
+
+ aField.value = value;
+ this.updateResetButtonVisibility();
+ ]]>
+ </body>
+ </method>
+
+ <method name="isAnyValueAvailable">
+ <parameter name="aForPicker"/>
+ <body>
+ <![CDATA[
+ return !this.isEmpty(this.mMonthField.value) ||
+ !this.isEmpty(this.mDayField.value) ||
+ !this.isEmpty(this.mYearField.value);
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="time-input"
+ extends="chrome://global/content/bindings/datetimebox.xml#datetime-input-base">
+ <resources>
+ <stylesheet src="chrome://global/content/textbox.css"/>
+ <stylesheet src="chrome://global/skin/textbox.css"/>
+ <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
+ </resources>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ // TODO: Bug 1301312 - localization for input type=time input.
+ this.mHour12 = true;
+ this.mAMIndicator = "AM";
+ this.mPMIndicator = "PM";
+ this.mPlaceHolder = "--";
+ this.mSeparatorText = ":";
+ this.mMillisecSeparatorText = ".";
+ this.mMaxLength = 2;
+ this.mMillisecMaxLength = 3;
+ this.mDefaultStep = 60 * 1000; // in milliseconds
+
+ this.mMinHourInHour12 = 1;
+ this.mMaxHourInHour12 = 12;
+ this.mMinMinute = 0;
+ this.mMaxMinute = 59;
+ this.mMinSecond = 0;
+ this.mMaxSecond = 59;
+ this.mMinMillisecond = 0;
+ this.mMaxMillisecond = 999;
+
+ this.mHourPageUpDownInterval = 3;
+ this.mMinSecPageUpDownInterval = 10;
+
+ this.mHourField =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-one");
+ this.mHourField.setAttribute("typeBuffer", "");
+ this.mMinuteField =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-two");
+ this.mMinuteField.setAttribute("typeBuffer", "");
+ this.mDayPeriodField =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-three");
+ this.mDayPeriodField.classList.remove("numeric");
+
+ this.mHourField.placeholder = this.mPlaceHolder;
+ this.mMinuteField.placeholder = this.mPlaceHolder;
+ this.mDayPeriodField.placeholder = this.mPlaceHolder;
+
+ this.mHourField.setAttribute("min", this.mMinHourInHour12);
+ this.mHourField.setAttribute("max", this.mMaxHourInHour12);
+ this.mMinuteField.setAttribute("min", this.mMinMinute);
+ this.mMinuteField.setAttribute("max", this.mMaxMinute);
+
+ this.mMinuteSeparator =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
+ this.mMinuteSeparator.textContent = this.mSeparatorText;
+ this.mSpaceSeparator =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
+ // space between time and am/pm field
+ this.mSpaceSeparator.textContent = " ";
+
+ this.mSecondSeparator = null;
+ this.mSecondField = null;
+ this.mMillisecSeparator = null;
+ this.mMillisecField = null;
+
+ if (this.mInputElement.value) {
+ this.setFieldsFromInputValue();
+ }
+ this.updateResetButtonVisibility();
+ ]]>
+ </constructor>
+
+ <method name="insertSeparator">
+ <parameter name="aSeparatorText"/>
+ <body>
+ <![CDATA[
+ let container = this.mHourField.parentNode;
+ const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+ let separator = document.createElementNS(HTML_NS, "span");
+ separator.textContent = aSeparatorText;
+ separator.setAttribute("class", "datetime-separator");
+ container.insertBefore(separator, this.mSpaceSeparator);
+
+ return separator;
+ ]]>
+ </body>
+ </method>
+
+ <method name="insertAdditionalField">
+ <parameter name="aPlaceHolder"/>
+ <parameter name="aMin"/>
+ <parameter name="aMax"/>
+ <parameter name="aSize"/>
+ <parameter name="aMaxLength"/>
+ <body>
+ <![CDATA[
+ let container = this.mHourField.parentNode;
+ const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+ let field = document.createElementNS(HTML_NS, "input");
+ field.classList.add("textbox-input", "datetime-input", "numeric");
+ field.setAttribute("size", aSize);
+ field.setAttribute("maxlength", aMaxLength);
+ field.setAttribute("min", aMin);
+ field.setAttribute("max", aMax);
+ field.setAttribute("typeBuffer", "");
+ field.disabled = this.mInputElement.disabled;
+ field.readOnly = this.mInputElement.readOnly;
+ field.tabIndex = this.mInputElement.tabIndex;
+ field.placeholder = aPlaceHolder;
+ container.insertBefore(field, this.mSpaceSeparator);
+
+ return field;
+ ]]>
+ </body>
+ </method>
+
+ <method name="setFieldsFromInputValue">
+ <body>
+ <![CDATA[
+ let value = this.mInputElement.value;
+ if (!value) {
+ this.clearInputFields(true);
+ return;
+ }
+
+ this.log("setFieldsFromInputValue: " + value);
+ let [hour, minute, second] = value.split(":");
+
+ this.setFieldValue(this.mHourField, hour);
+ this.setFieldValue(this.mMinuteField, minute);
+ if (this.mHour12) {
+ this.mDayPeriodField.value = (hour >= this.mMaxHourInHour12) ?
+ this.mPMIndicator : this.mAMIndicator;
+ }
+
+ if (!this.isEmpty(second)) {
+ let index = second.indexOf(".");
+ let millisecond;
+ if (index != -1) {
+ millisecond = second.substring(index + 1);
+ second = second.substring(0, index);
+ }
+
+ if (!this.mSecondField) {
+ this.mSecondSeparator = this.insertSeparator(this.mSeparatorText);
+ this.mSecondField = this.insertAdditionalField(this.mPlaceHolder,
+ this.mMinSecond, this.mMaxSecond, this.mMaxLength,
+ this.mMaxLength);
+ }
+ this.setFieldValue(this.mSecondField, second);
+
+ if (!this.isEmpty(millisecond)) {
+ if (!this.mMillisecField) {
+ this.mMillisecSeparator = this.insertSeparator(
+ this.mMillisecSeparatorText);
+ this.mMillisecField = this.insertAdditionalField(
+ this.mPlaceHolder, this.mMinMillisecond, this.mMaxMillisecond,
+ this.mMillisecMaxLength, this.mMillisecMaxLength);
+ }
+ this.setFieldValue(this.mMillisecField, millisecond);
+ } else if (this.mMillisecField) {
+ this.mMillisecField.remove();
+ this.mMillisecField = null;
+
+ this.mMillisecSeparator.remove();
+ this.mMillisecSeparator = null;
+ }
+ } else {
+ if (this.mSecondField) {
+ this.mSecondField.remove();
+ this.mSecondField = null;
+
+ this.mSecondSeparator.remove();
+ this.mSecondSeparator = null;
+ }
+
+ if (this.mMillisecField) {
+ this.mMillisecField.remove();
+ this.mMillisecField = null;
+
+ this.mMillisecSeparator.remove();
+ this.mMillisecSeparator = null;
+ }
+ }
+ this.notifyPicker();
+ ]]>
+ </body>
+ </method>
+
+ <method name="setInputValueFromFields">
+ <body>
+ <![CDATA[
+ if (!this.isAnyValueAvailable(false) && this.mInputElement.value) {
+ // Values in the input box was cleared, clear the input element's
+ // value if not empty.
+ this.mInputElement.setUserInput("");
+ return;
+ }
+
+ if (this.isEmpty(this.mHourField.value) ||
+ this.isEmpty(this.mMinuteField.value) ||
+ (this.mDayPeriodField && this.isEmpty(this.mDayPeriodField.value)) ||
+ (this.mSecondField && this.isEmpty(this.mSecondField.value)) ||
+ (this.mMillisecField && this.isEmpty(this.mMillisecField.value))) {
+ // We still need to notify picker in case any of the field has
+ // changed. If we can set input element value, then notifyPicker
+ // will be called in setFieldsFromInputValue().
+ this.notifyPicker();
+ return;
+ }
+
+ let hour = Number(this.mHourField.value);
+ if (this.mHour12) {
+ let dayPeriod = this.mDayPeriodField.value;
+ if (dayPeriod == this.mPMIndicator &&
+ hour < this.mMaxHourInHour12) {
+ hour += this.mMaxHourInHour12;
+ } else if (dayPeriod == this.mAMIndicator &&
+ hour == this.mMaxHourInHour12) {
+ hour = 0;
+ }
+ }
+
+ hour = (hour < 10) ? ("0" + hour) : hour;
+
+ let time = hour + ":" + this.mMinuteField.value;
+ if (this.mSecondField) {
+ time += ":" + this.mSecondField.value;
+ }
+
+ if (this.mMillisecField) {
+ time += "." + this.mMillisecField.value;
+ }
+
+ if (time == this.mInputElement.value) {
+ return;
+ }
+
+ this.log("setInputValueFromFields: " + time);
+ this.mInputElement.setUserInput(time);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setFieldsFromPicker">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ let hour = aValue.hour;
+ let minute = aValue.minute;
+ this.log("setFieldsFromPicker: " + hour + ":" + minute);
+
+ if (!this.isEmpty(hour)) {
+ this.setFieldValue(this.mHourField, hour);
+ if (this.mHour12) {
+ this.mDayPeriodField.value =
+ (hour >= this.mMaxHourInHour12) ? this.mPMIndicator
+ : this.mAMIndicator;
+ }
+ }
+
+ if (!this.isEmpty(minute)) {
+ this.setFieldValue(this.mMinuteField, minute);
+ }
+
+ // Update input element's .value if needed.
+ this.setInputValueFromFields();
+ ]]>
+ </body>
+ </method>
+
+ <method name="clearInputFields">
+ <parameter name="aFromInputElement"/>
+ <body>
+ <![CDATA[
+ this.log("clearInputFields");
+
+ if (this.isDisabled() || this.isReadonly()) {
+ return;
+ }
+
+ if (this.mHourField && !this.mHourField.disabled &&
+ !this.mHourField.readOnly) {
+ this.mHourField.value = "";
+ this.mHourField.setAttribute("typeBuffer", "");
+ }
+
+ if (this.mMinuteField && !this.mMinuteField.disabled &&
+ !this.mMinuteField.readOnly) {
+ this.mMinuteField.value = "";
+ this.mMinuteField.setAttribute("typeBuffer", "");
+ }
+
+ if (this.mSecondField && !this.mSecondField.disabled &&
+ !this.mSecondField.readOnly) {
+ this.mSecondField.value = "";
+ this.mSecondField.setAttribute("typeBuffer", "");
+ }
+
+ if (this.mMillisecField && !this.mMillisecField.disabled &&
+ !this.mMillisecField.readOnly) {
+ this.mMillisecField.value = "";
+ this.mMillisecField.setAttribute("typeBuffer", "");
+ }
+
+ if (this.mDayPeriodField && !this.mDayPeriodField.disabled &&
+ !this.mDayPeriodField.readOnly) {
+ this.mDayPeriodField.value = "";
+ }
+
+ if (!aFromInputElement && this.mInputElement.value) {
+ this.mInputElement.setUserInput("");
+ }
+
+ this.updateResetButtonVisibility();
+ ]]>
+ </body>
+ </method>
+
+ <method name="incrementFieldValue">
+ <parameter name="aTargetField"/>
+ <parameter name="aTimes"/>
+ <body>
+ <![CDATA[
+ let value;
+
+ // Use current time if field is empty.
+ if (this.isEmpty(aTargetField.value)) {
+ let now = new Date();
+
+ if (aTargetField == this.mHourField) {
+ value = now.getHours() % this.mMaxHourInHour12 ||
+ this.mMaxHourInHour12;
+ } else if (aTargetField == this.mMinuteField) {
+ value = now.getMinutes();
+ } else if (aTargetField == this.mSecondField) {
+ value = now.getSeconds();
+ } else if (aTargetField == this.mMillisecField) {
+ value = now.getMilliseconds();
+ } else {
+ this.log("Field not supported in incrementFieldValue.");
+ return;
+ }
+ } else {
+ value = Number(aTargetField.value);
+ }
+
+ let min = aTargetField.getAttribute("min");
+ let max = aTargetField.getAttribute("max");
+
+ value += aTimes;
+ if (value > max) {
+ value -= (max - min + 1);
+ } else if (value < min) {
+ value += (max - min + 1);
+ }
+ this.setFieldValue(aTargetField, value);
+ aTargetField.select();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleKeyboardNav">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (this.isDisabled() || this.isReadonly()) {
+ return;
+ }
+
+ let targetField = aEvent.originalTarget;
+ let key = aEvent.key;
+
+ if (this.mDayPeriodField &&
+ targetField == this.mDayPeriodField) {
+ // Home/End key does nothing on AM/PM field.
+ if (key == "Home" || key == "End") {
+ return;
+ }
+
+ this.mDayPeriodField.value =
+ this.mDayPeriodField.value == this.mAMIndicator ?
+ this.mPMIndicator : this.mAMIndicator;
+ this.mDayPeriodField.select();
+ this.updateResetButtonVisibility();
+ this.setInputValueFromFields();
+ return;
+ }
+
+ switch (key) {
+ case "ArrowUp":
+ this.incrementFieldValue(targetField, 1);
+ break;
+ case "ArrowDown":
+ this.incrementFieldValue(targetField, -1);
+ break;
+ case "PageUp":
+ this.incrementFieldValue(targetField,
+ targetField == this.mHourField ? this.mHourPageUpDownInterval
+ : this.mMinSecPageUpDownInterval);
+ break;
+ case "PageDown":
+ this.incrementFieldValue(targetField,
+ targetField == this.mHourField ? (0 - this.mHourPageUpDownInterval)
+ : (0 - this.mMinSecPageUpDownInterval));
+ break;
+ case "Home":
+ let min = targetField.getAttribute("min");
+ this.setFieldValue(targetField, min);
+ targetField.select();
+ break;
+ case "End":
+ let max = targetField.getAttribute("max");
+ this.setFieldValue(targetField, max);
+ targetField.select();
+ break;
+ }
+ this.setInputValueFromFields();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleKeypress">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (this.isDisabled() || this.isReadonly()) {
+ return;
+ }
+
+ let targetField = aEvent.originalTarget;
+ let key = aEvent.key;
+
+ if (this.mDayPeriodField &&
+ targetField == this.mDayPeriodField) {
+ if (key == "a" || key == "A") {
+ this.mDayPeriodField.value = this.mAMIndicator;
+ this.mDayPeriodField.select();
+ } else if (key == "p" || key == "P") {
+ this.mDayPeriodField.value = this.mPMIndicator;
+ this.mDayPeriodField.select();
+ }
+ this.updateResetButtonVisibility();
+ return;
+ }
+
+ if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) {
+ let buffer = targetField.getAttribute("typeBuffer") || "";
+
+ buffer = buffer.concat(key);
+ this.setFieldValue(targetField, buffer);
+ targetField.select();
+
+ let n = Number(buffer);
+ let max = targetField.getAttribute("max");
+ if (buffer.length >= targetField.maxLength || n * 10 > max) {
+ buffer = "";
+ this.advanceToNextField();
+ }
+ targetField.setAttribute("typeBuffer", buffer);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="setFieldValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ let value = Number(aValue);
+ if (isNaN(value)) {
+ this.log("NaN on setFieldValue!");
+ return;
+ }
+
+ if (aField.maxLength == this.mMaxLength) { // For hour, minute and second
+ if (aField == this.mHourField && this.mHour12) {
+ value = (value > this.mMaxHourInHour12) ?
+ value - this.mMaxHourInHour12 : value;
+ if (aValue == "00") {
+ value = this.mMaxHourInHour12;
+ }
+ }
+ // prepend zero
+ if (value < 10) {
+ value = "0" + value;
+ }
+ } else if (aField.maxLength == this.mMillisecMaxLength) {
+ // prepend zeroes
+ if (value < 10) {
+ value = "00" + value;
+ } else if (value < 100) {
+ value = "0" + value;
+ }
+ }
+
+ aField.value = value;
+ this.updateResetButtonVisibility();
+ ]]>
+ </body>
+ </method>
+
+ <method name="isAnyValueAvailable">
+ <parameter name="aForPicker"/>
+ <body>
+ <![CDATA[
+ let available = !this.isEmpty(this.mHourField.value) ||
+ !this.isEmpty(this.mMinuteField.value);
+
+ if (available) {
+ return true;
+ }
+
+ // Picker only cares about hour:minute.
+ if (aForPicker) {
+ return false;
+ }
+
+ return (this.mDayPeriodField && !this.isEmpty(this.mDayPeriodField.value)) ||
+ (this.mSecondField && !this.isEmpty(this.mSecondField.value)) ||
+ (this.mMillisecField && !this.isEmpty(this.mMillisecField.value));
+ ]]>
+ </body>
+ </method>
+
+ <method name="getCurrentValue">
+ <body>
+ <![CDATA[
+ let hour;
+ if (!this.isEmpty(this.mHourField.value)) {
+ hour = Number(this.mHourField.value);
+ if (this.mHour12) {
+ let dayPeriod = this.mDayPeriodField.value;
+ if (dayPeriod == this.mPMIndicator &&
+ hour < this.mMaxHourInHour12) {
+ hour += this.mMaxHourInHour12;
+ } else if (dayPeriod == this.mAMIndicator &&
+ hour == this.mMaxHourInHour12) {
+ hour = 0;
+ }
+ }
+ }
+
+ let minute;
+ if (!this.isEmpty(this.mMinuteField.value)) {
+ minute = Number(this.mMinuteField.value);
+ }
+
+ // Picker only needs hour/minute.
+ let time = { hour, minute };
+
+ this.log("getCurrentValue: " + JSON.stringify(time));
+ return time;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="datetime-input-base">
+ <resources>
+ <stylesheet src="chrome://global/content/textbox.css"/>
+ <stylesheet src="chrome://global/skin/textbox.css"/>
+ <stylesheet src="chrome://global/content/bindings/datetimebox.css"/>
+ </resources>
+
+ <content>
+ <html:div class="datetime-input-box-wrapper"
+ xbl:inherits="context,disabled,readonly">
+ <html:span class="datetime-input-edit-wrapper"
+ anonid="edit-wrapper">
+ <html:input anonid="input-one"
+ class="textbox-input datetime-input numeric"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly,tabindex"/>
+ <html:span anonid="sep-first" class="datetime-separator"></html:span>
+ <html:input anonid="input-two"
+ class="textbox-input datetime-input numeric"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly,tabindex"/>
+ <html:span anonid="sep-second" class="datetime-separator"></html:span>
+ <html:input anonid="input-three"
+ class="textbox-input datetime-input numeric"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly,tabindex"/>
+ </html:span>
+
+ <html:button class="datetime-reset-button" anonid="reset-button"
+ tabindex="-1" xbl:inherits="disabled"/>
+ </html:div>
+ </content>
+
+ <implementation implements="nsIDateTimeInputArea">
+ <constructor>
+ <![CDATA[
+ this.DEBUG = false;
+ this.mInputElement = this.parentNode;
+
+ this.mMin = this.mInputElement.min;
+ this.mMax = this.mInputElement.max;
+ this.mStep = this.mInputElement.step;
+ this.mIsPickerOpen = false;
+
+ this.mResetButton =
+ window.document.getAnonymousElementByAttribute(this, "anonid", "reset-button");
+
+ this.EVENTS.forEach((eventName) => {
+ this.addEventListener(eventName, this, { mozSystemGroup: true });
+ });
+ // Handle keypress separately since we need to catch it on capturing.
+ this.addEventListener("keypress", this, {
+ capture: true,
+ mozSystemGroup: true
+ });
+ // This is to open the picker when input element is clicked (this
+ // includes padding area).
+ this.mInputElement.addEventListener("click", this,
+ { mozSystemGroup: true });
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ this.EVENTS.forEach((eventName) => {
+ this.removeEventListener(eventName, this, { mozSystemGroup: true });
+ });
+ this.removeEventListener("keypress", this, {
+ capture: true,
+ mozSystemGroup: true
+ });
+ this.mInputElement.removeEventListener("click", this,
+ { mozSystemGroup: true });
+
+ this.mInputElement = null;
+ ]]>
+ </destructor>
+
+ <property name="EVENTS" readonly="true">
+ <getter>
+ <![CDATA[
+ return ["focus", "blur", "copy", "cut", "paste", "mousedown"];
+ ]]>
+ </getter>
+ </property>
+
+ <method name="log">
+ <parameter name="aMsg"/>
+ <body>
+ <![CDATA[
+ if (this.DEBUG) {
+ dump("[DateTimeBox] " + aMsg + "\n");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateResetButtonVisibility">
+ <body>
+ <![CDATA[
+ if (this.isAnyValueAvailable(false)) {
+ this.mResetButton.style.visibility = "visible";
+ } else {
+ this.mResetButton.style.visibility = "hidden";
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="focusInnerTextBox">
+ <body>
+ <![CDATA[
+ this.log("focusInnerTextBox");
+ window.document.getAnonymousElementByAttribute(this, "anonid", "input-one").focus();
+ ]]>
+ </body>
+ </method>
+
+ <method name="blurInnerTextBox">
+ <body>
+ <![CDATA[
+ this.log("blurInnerTextBox");
+ if (this.mLastFocusedField) {
+ this.mLastFocusedField.blur();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="notifyInputElementValueChanged">
+ <body>
+ <![CDATA[
+ this.log("inputElementValueChanged");
+ this.setFieldsFromInputValue();
+ ]]>
+ </body>
+ </method>
+
+ <method name="setValueFromPicker">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ this.setFieldsFromPicker(aValue);
+ ]]>
+ </body>
+ </method>
+
+ <method name="advanceToNextField">
+ <parameter name="aReverse"/>
+ <body>
+ <![CDATA[
+ this.log("advanceToNextField");
+
+ let focusedInput = this.mLastFocusedField;
+ let next = aReverse ? focusedInput.previousElementSibling
+ : focusedInput.nextElementSibling;
+ if (!next && !aReverse) {
+ this.setInputValueFromFields();
+ return;
+ }
+
+ while (next) {
+ if (next.type == "text" && !next.disabled) {
+ next.focus();
+ break;
+ }
+ next = aReverse ? next.previousElementSibling
+ : next.nextElementSibling;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="setPickerState">
+ <parameter name="aIsOpen"/>
+ <body>
+ <![CDATA[
+ this.log("picker is now " + (aIsOpen ? "opened" : "closed"));
+ this.mIsPickerOpen = aIsOpen;
+ ]]>
+ </body>
+ </method>
+
+ <method name="isEmpty">
+ <parameter name="aValue"/>
+ <body>
+ return (aValue == undefined || 0 === aValue.length);
+ </body>
+ </method>
+
+ <method name="clearInputFields">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
+ <method name="setFieldsFromInputValue">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
+ <method name="setInputValueFromFields">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
+ <method name="setFieldsFromPicker">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
+ <method name="handleKeypress">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
+ <method name="handleKeyboardNav">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
+ <method name="getCurrentValue">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
+ <method name="isAnyValueAvailable">
+ <body>
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ </body>
+ </method>
+
+ <method name="notifyPicker">
+ <body>
+ <![CDATA[
+ if (this.mIsPickerOpen && this.isAnyValueAvailable(true)) {
+ this.mInputElement.updateDateTimePicker(this.getCurrentValue());
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="isDisabled">
+ <body>
+ <![CDATA[
+ return this.hasAttribute("disabled");
+ ]]>
+ </body>
+ </method>
+
+ <method name="isReadonly">
+ <body>
+ <![CDATA[
+ return this.hasAttribute("readonly");
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.log("handleEvent: " + aEvent.type);
+
+ switch (aEvent.type) {
+ case "keypress": {
+ this.onKeyPress(aEvent);
+ break;
+ }
+ case "click": {
+ this.onClick(aEvent);
+ break;
+ }
+ case "focus": {
+ this.onFocus(aEvent);
+ break;
+ }
+ case "blur": {
+ this.onBlur(aEvent);
+ break;
+ }
+ case "mousedown": {
+ if (aEvent.originalTarget == this.mResetButton) {
+ aEvent.preventDefault();
+ }
+ break;
+ }
+ case "copy":
+ case "cut":
+ case "paste": {
+ aEvent.preventDefault();
+ break;
+ }
+ default:
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="onFocus">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.log("onFocus originalTarget: " + aEvent.originalTarget);
+
+ let target = aEvent.originalTarget;
+ if ((target instanceof HTMLInputElement) && target.type == "text") {
+ this.mLastFocusedField = target;
+ target.select();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="onBlur">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.log("onBlur originalTarget: " + aEvent.originalTarget +
+ " target: " + aEvent.target);
+
+ let target = aEvent.originalTarget;
+ target.setAttribute("typeBuffer", "");
+ this.setInputValueFromFields();
+ ]]>
+ </body>
+ </method>
+
+ <method name="onKeyPress">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.log("onKeyPress key: " + aEvent.key);
+
+ switch (aEvent.key) {
+ // Close picker on Enter, Escape or Space key.
+ case "Enter":
+ case "Escape":
+ case " ": {
+ if (this.mIsPickerOpen) {
+ this.mInputElement.closeDateTimePicker();
+ aEvent.preventDefault();
+ }
+ break;
+ }
+ case "Backspace": {
+ let targetField = aEvent.originalTarget;
+ targetField.value = "";
+ targetField.setAttribute("typeBuffer", "");
+ this.updateResetButtonVisibility();
+ this.setInputValueFromFields();
+ aEvent.preventDefault();
+ break;
+ }
+ case "ArrowRight":
+ case "ArrowLeft": {
+ this.advanceToNextField(aEvent.key == "ArrowRight" ? false : true);
+ aEvent.preventDefault();
+ break;
+ }
+ case "ArrowUp":
+ case "ArrowDown":
+ case "PageUp":
+ case "PageDown":
+ case "Home":
+ case "End": {
+ this.handleKeyboardNav(aEvent);
+ aEvent.preventDefault();
+ break;
+ }
+ default: {
+ // printable characters
+ if (aEvent.keyCode == 0 &&
+ !(aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey)) {
+ this.handleKeypress(aEvent);
+ aEvent.preventDefault();
+ }
+ break;
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="onClick">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.log("onClick originalTarget: " + aEvent.originalTarget +
+ " target: " + aEvent.target);
+
+ if (aEvent.defaultPrevented || this.isDisabled() || this.isReadonly()) {
+ return;
+ }
+
+ if (aEvent.originalTarget == this.mResetButton) {
+ this.clearInputFields(false);
+ } else if (!this.mIsPickerOpen) {
+ this.mInputElement.openDateTimePicker(this.getCurrentValue());
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/datetimepicker.xml b/components/bindings/content/datetimepicker.xml
new file mode 100644
index 000000000..1d6a5e772
--- /dev/null
+++ b/components/bindings/content/datetimepicker.xml
@@ -0,0 +1,1301 @@
+<?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 % datetimepickerDTD SYSTEM "chrome://global/locale/datetimepicker.dtd">
+ %datetimepickerDTD;
+]>
+
+<bindings id="timepickerBindings"
+ 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="datetimepicker-base"
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
+
+ <resources>
+ <stylesheet src="chrome://global/content/textbox.css"/>
+ <stylesheet src="chrome://global/skin/textbox.css"/>
+ <stylesheet src="chrome://global/skin/dropmarker.css"/>
+ <stylesheet src="chrome://global/skin/datetimepicker.css"/>
+ </resources>
+
+ <content align="center">
+ <xul:hbox class="datetimepicker-input-box" align="center"
+ xbl:inherits="context,disabled,readonly">
+ <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-one"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ <xul:label anonid="sep-first" class="datetimepicker-separator" value=":"/>
+ <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-two"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ <xul:label anonid="sep-second" class="datetimepicker-separator" value=":"/>
+ <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-three"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ <xul:hbox class="textbox-input-box datetimepicker-input-subbox" align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-ampm"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ </xul:hbox>
+ <xul:spinbuttons anonid="buttons" xbl:inherits="disabled"
+ onup="this.parentNode._increaseOrDecrease(1);"
+ ondown="this.parentNode._increaseOrDecrease(-1);"/>
+ </content>
+
+ <implementation>
+ <field name="_dateValue">null</field>
+ <field name="_fieldOne">
+ document.getAnonymousElementByAttribute(this, "anonid", "input-one");
+ </field>
+ <field name="_fieldTwo">
+ document.getAnonymousElementByAttribute(this, "anonid", "input-two");
+ </field>
+ <field name="_fieldThree">
+ document.getAnonymousElementByAttribute(this, "anonid", "input-three");
+ </field>
+ <field name="_fieldAMPM">
+ document.getAnonymousElementByAttribute(this, "anonid", "input-ampm");
+ </field>
+ <field name="_separatorFirst">
+ document.getAnonymousElementByAttribute(this, "anonid", "sep-first");
+ </field>
+ <field name="_separatorSecond">
+ document.getAnonymousElementByAttribute(this, "anonid", "sep-second");
+ </field>
+ <field name="_lastFocusedField">null</field>
+ <field name="_hasEntry">true</field>
+ <field name="_valueEntered">false</field>
+ <field name="attachedControl">null</field>
+
+ <property name="_currentField" readonly="true">
+ <getter>
+ var focusedInput = document.activeElement;
+ if (focusedInput == this._fieldOne ||
+ focusedInput == this._fieldTwo ||
+ focusedInput == this._fieldThree ||
+ focusedInput == this._fieldAMPM)
+ return focusedInput;
+ return this._lastFocusedField || this._fieldOne;
+ </getter>
+ </property>
+
+ <property name="dateValue" onget="return new Date(this._dateValue);">
+ <setter>
+ <![CDATA[
+ if (!(val instanceof Date))
+ throw "Invalid Date";
+
+ this._setValueNoSync(val);
+ if (this.attachedControl)
+ this.attachedControl._setValueNoSync(val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="readOnly" onset="if (val) this.setAttribute('readonly', 'true');
+ else this.removeAttribute('readonly'); return val;"
+ onget="return this.getAttribute('readonly') == 'true';"/>
+
+ <method name="_fireEvent">
+ <parameter name="aEventName"/>
+ <parameter name="aTarget"/>
+ <body>
+ var event = document.createEvent("Events");
+ event.initEvent(aEventName, true, true);
+ return !aTarget.dispatchEvent(event);
+ </body>
+ </method>
+
+ <method name="_setValueOnChange">
+ <parameter name="aField"/>
+ <body>
+ <![CDATA[
+ if (!this._hasEntry)
+ return;
+
+ if (aField == this._fieldOne ||
+ aField == this._fieldTwo ||
+ aField == this._fieldThree) {
+ var value = Number(aField.value);
+ if (isNaN(value))
+ value = 0;
+
+ value = this._constrainValue(aField, value, true);
+ this._setFieldValue(aField, value);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_init">
+ <body/>
+ </method>
+
+ <constructor>
+ this._init();
+
+ var cval = this.getAttribute("value");
+ if (cval) {
+ try {
+ this.value = cval;
+ return;
+ } catch (ex) { }
+ }
+ this.dateValue = new Date();
+ </constructor>
+
+ <destructor>
+ if (this.attachedControl) {
+ this.attachedControl.attachedControl = null;
+ this.attachedControl = null;
+ }
+ </destructor>
+
+ </implementation>
+
+ <handlers>
+ <handler event="focus" phase="capturing">
+ <![CDATA[
+ var target = event.originalTarget;
+ if (target == this._fieldOne ||
+ target == this._fieldTwo ||
+ target == this._fieldThree ||
+ target == this._fieldAMPM)
+ this._lastFocusedField = target;
+ ]]>
+ </handler>
+
+ <handler event="keypress">
+ <![CDATA[
+ if (this._hasEntry && event.charCode &&
+ this._currentField != this._fieldAMPM &&
+ ! (event.altKey || event.ctrlKey || event.metaKey) &&
+ (event.charCode < 48 || event.charCode > 57))
+ event.preventDefault();
+ ]]>
+ </handler>
+
+ <handler event="keypress" keycode="VK_UP">
+ if (this._hasEntry)
+ this._increaseOrDecrease(1);
+ </handler>
+ <handler event="keypress" keycode="VK_DOWN">
+ if (this._hasEntry)
+ this._increaseOrDecrease(-1);
+ </handler>
+
+ <handler event="input">
+ this._valueEntered = true;
+ </handler>
+
+ <handler event="change">
+ this._setValueOnChange(event.originalTarget);
+ </handler>
+ </handlers>
+
+ </binding>
+
+ <binding id="timepicker"
+ extends="chrome://global/content/bindings/datetimepicker.xml#datetimepicker-base">
+
+ <implementation>
+ <field name="is24HourClock">false</field>
+ <field name="hourLeadingZero">false</field>
+ <field name="minuteLeadingZero">true</field>
+ <field name="secondLeadingZero">true</field>
+ <field name="amIndicator">"AM"</field>
+ <field name="pmIndicator">"PM"</field>
+
+ <field name="hourField">null</field>
+ <field name="minuteField">null</field>
+ <field name="secondField">null</field>
+
+ <property name="value">
+ <getter>
+ <![CDATA[
+ var minute = this._dateValue.getMinutes();
+ if (minute < 10)
+ minute = "0" + minute;
+
+ var second = this._dateValue.getSeconds();
+ if (second < 10)
+ second = "0" + second;
+ return this._dateValue.getHours() + ":" + minute + ":" + second;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ var items = val.match(/^([0-9]{1,2})\:([0-9]{1,2})\:?([0-9]{1,2})?$/);
+ if (!items)
+ throw "Invalid Time";
+
+ var dt = this.dateValue;
+ dt.setHours(items[1]);
+ dt.setMinutes(items[2]);
+ dt.setSeconds(items[3] ? items[3] : 0);
+ this.dateValue = dt;
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="hour" onget="return this._dateValue.getHours();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 0 || valnum > 23)
+ throw "Invalid Hour";
+ this._setFieldValue(this.hourField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="minute" onget="return this._dateValue.getMinutes();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 0 || valnum > 59)
+ throw "Invalid Minute";
+ this._setFieldValue(this.minuteField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="second" onget="return this._dateValue.getSeconds();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 0 || valnum > 59)
+ throw "Invalid Second";
+ this._setFieldValue(this.secondField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="isPM">
+ <getter>
+ <![CDATA[
+ return (this.hour >= 12);
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val) {
+ if (this.hour < 12)
+ this.hour += 12;
+ }
+ else if (this.hour >= 12)
+ this.hour -= 12;
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="hideSeconds">
+ <getter>
+ return (this.getAttribute("hideseconds") == "true");
+ </getter>
+ <setter>
+ if (val)
+ this.setAttribute("hideseconds", "true");
+ else
+ this.removeAttribute("hideseconds");
+ if (this.secondField)
+ this.secondField.parentNode.collapsed = val;
+ this._separatorSecond.collapsed = val;
+ return val;
+ </setter>
+ </property>
+ <property name="increment">
+ <getter>
+ <![CDATA[
+ var increment = this.getAttribute("increment");
+ increment = Number(increment);
+ if (isNaN(increment) || increment <= 0 || increment >= 60)
+ return 1;
+ return increment;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (typeof val == "number")
+ this.setAttribute("increment", val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="_setValueNoSync">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var dt = new Date(aValue);
+ if (!isNaN(dt)) {
+ this._dateValue = dt;
+ this.setAttribute("value", this.value);
+ this._updateUI(this.hourField, this.hour);
+ this._updateUI(this.minuteField, this.minute);
+ this._updateUI(this.secondField, this.second);
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_increaseOrDecrease">
+ <parameter name="aDir"/>
+ <body>
+ <![CDATA[
+ if (this.disabled || this.readOnly)
+ return;
+
+ var field = this._currentField;
+ if (this._valueEntered)
+ this._setValueOnChange(field);
+
+ if (field == this._fieldAMPM) {
+ this.isPM = !this.isPM;
+ this._fireEvent("change", this);
+ }
+ else {
+ var oldval;
+ var change = aDir;
+ if (field == this.hourField) {
+ oldval = this.hour;
+ }
+ else if (field == this.minuteField) {
+ oldval = this.minute;
+ change *= this.increment;
+ }
+ else if (field == this.secondField) {
+ oldval = this.second;
+ }
+
+ var newval = this._constrainValue(field, oldval + change, false);
+
+ if (field == this.hourField)
+ this.hour = newval;
+ else if (field == this.minuteField)
+ this.minute = newval;
+ else if (field == this.secondField)
+ this.second = newval;
+
+ if (oldval != newval)
+ this._fireEvent("change", this);
+ }
+ field.select();
+ ]]>
+ </body>
+ </method>
+ <method name="_setFieldValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ if (aField == this.hourField)
+ this._dateValue.setHours(aValue);
+ else if (aField == this.minuteField)
+ this._dateValue.setMinutes(aValue);
+ else if (aField == this.secondField)
+ this._dateValue.setSeconds(aValue);
+
+ this.setAttribute("value", this.value);
+ this._updateUI(aField, aValue);
+
+ if (this.attachedControl)
+ this.attachedControl._setValueNoSync(this._dateValue);
+ ]]>
+ </body>
+ </method>
+ <method name="_updateUI">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ this._valueEntered = false;
+
+ var prependZero = false;
+ if (aField == this.hourField) {
+ prependZero = this.hourLeadingZero;
+ if (!this.is24HourClock) {
+ if (aValue >= 12) {
+ if (aValue > 12)
+ aValue -= 12;
+ this._fieldAMPM.value = this.pmIndicator;
+ }
+ else {
+ if (aValue == 0)
+ aValue = 12;
+ this._fieldAMPM.value = this.amIndicator;
+ }
+ }
+ }
+ else if (aField == this.minuteField) {
+ prependZero = this.minuteLeadingZero;
+ }
+ else if (aField == this.secondField) {
+ prependZero = this.secondLeadingZero;
+ }
+
+ if (prependZero && aValue < 10)
+ aField.value = "0" + aValue;
+ else
+ aField.value = aValue;
+ ]]>
+ </body>
+ </method>
+ <method name="_constrainValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <parameter name="aNoWrap"/>
+ <body>
+ <![CDATA[
+ // aNoWrap is true when the user entered a value, so just
+ // constrain within limits. If false, the value is being
+ // incremented or decremented, so wrap around values
+ var max = (aField == this.hourField) ? 24 : 60;
+ if (aValue < 0)
+ return aNoWrap ? 0 : max + aValue;
+ if (aValue >= max)
+ return aNoWrap ? max - 1 : aValue - max;
+ return aValue;
+ ]]>
+ </body>
+ </method>
+ <method name="_init">
+ <body>
+ <![CDATA[
+ this.hourField = this._fieldOne;
+ this.minuteField = this._fieldTwo;
+ this.secondField = this._fieldThree;
+
+ var numberOrder = /^(\D*)\s*(\d+)(\D*)(\d+)(\D*)(\d+)\s*(\D*)$/;
+
+ var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory-nu-latn";
+
+ var pmTime = new Date(2000, 0, 1, 16, 7, 9).toLocaleTimeString(locale);
+ var numberFields = pmTime.match(numberOrder);
+ if (numberFields) {
+ this._separatorFirst.value = numberFields[3];
+ this._separatorSecond.value = numberFields[5];
+ if (Number(numberFields[2]) > 12)
+ this.is24HourClock = true;
+ else
+ this.pmIndicator = numberFields[1] || numberFields[7];
+ }
+
+ var amTime = new Date(2000, 0, 1, 1, 7, 9).toLocaleTimeString(locale);
+ numberFields = amTime.match(numberOrder);
+ if (numberFields) {
+ this.hourLeadingZero = (numberFields[2].length > 1);
+ this.minuteLeadingZero = (numberFields[4].length > 1);
+ this.secondLeadingZero = (numberFields[6].length > 1);
+
+ if (!this.is24HourClock) {
+ this.amIndicator = numberFields[1] || numberFields[7];
+ if (numberFields[1]) {
+ var mfield = this._fieldAMPM.parentNode;
+ var mcontainer = mfield.parentNode;
+ mcontainer.insertBefore(mfield, mcontainer.firstChild);
+ }
+ var size = (numberFields[1] || numberFields[7]).length;
+ if (this.pmIndicator.length > size)
+ size = this.pmIndicator.length;
+ this._fieldAMPM.size = size;
+ this._fieldAMPM.maxLength = size;
+ }
+ else {
+ this._fieldAMPM.parentNode.collapsed = true;
+ }
+ }
+
+ this.hideSeconds = this.hideSeconds;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="keypress">
+ <![CDATA[
+ // just allow any printable character to switch the AM/PM state
+ if (event.charCode && !this.disabled && !this.readOnly &&
+ this._currentField == this._fieldAMPM) {
+ this.isPM = !this.isPM;
+ this._fieldAMPM.select();
+ this._fireEvent("change", this);
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+ </handlers>
+
+ </binding>
+
+ <binding id="datepicker"
+ extends="chrome://global/content/bindings/datetimepicker.xml#datetimepicker-base">
+
+ <implementation>
+ <field name="yearLeadingZero">false</field>
+ <field name="monthLeadingZero">true</field>
+ <field name="dateLeadingZero">true</field>
+
+ <field name="yearField"/>
+ <field name="monthField"/>
+ <field name="dateField"/>
+
+ <property name="value">
+ <getter>
+ <![CDATA[
+ var month = this._dateValue.getMonth();
+ month = (month < 9) ? month = "0" + ++month : month + 1;
+
+ var date = this._dateValue.getDate();
+ if (date < 10)
+ date = "0" + date;
+ return this._dateValue.getFullYear() + "-" + month + "-" + date;
+ ]]>
+
+ </getter>
+ <setter>
+ <![CDATA[
+ var results = val.match(/^([0-9]{1,4})\-([0-9]{1,2})\-([0-9]{1,2})$/);
+ if (!results)
+ throw "Invalid Date";
+
+ this.dateValue = new Date(results[1] + "/" + results[2] + "/" + results[3]);
+ this.setAttribute("value", this.value);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="year" onget="return this._dateValue.getFullYear();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 1 || valnum > 9999)
+ throw "Invalid Year";
+ this._setFieldValue(this.yearField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="month" onget="return this._dateValue.getMonth();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 0 || valnum > 11)
+ throw "Invalid Month";
+ this._setFieldValue(this.monthField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="date" onget="return this._dateValue.getDate();">
+ <setter>
+ <![CDATA[
+ var valnum = Number(val);
+ if (isNaN(valnum) || valnum < 1 || valnum > 31)
+ throw "Invalid Date";
+ this._setFieldValue(this.dateField, valnum);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="open" onget="return false;" onset="return val;"/>
+
+ <property name="displayedMonth" onget="return this.month;"
+ onset="this.month = val; return val;"/>
+ <property name="displayedYear" onget="return this.year;"
+ onset="this.year = val; return val;"/>
+
+ <method name="_setValueNoSync">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var dt = new Date(aValue);
+ if (!isNaN(dt)) {
+ this._dateValue = dt;
+ this.setAttribute("value", this.value);
+ this._updateUI(this.yearField, this.year);
+ this._updateUI(this.monthField, this.month);
+ this._updateUI(this.dateField, this.date);
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_increaseOrDecrease">
+ <parameter name="aDir"/>
+ <body>
+ <![CDATA[
+ if (this.disabled || this.readOnly)
+ return;
+
+ var field = this._currentField;
+ if (this._valueEntered)
+ this._setValueOnChange(field);
+
+ var oldval;
+ if (field == this.yearField)
+ oldval = this.year;
+ else if (field == this.monthField)
+ oldval = this.month;
+ else if (field == this.dateField)
+ oldval = this.date;
+
+ var newval = this._constrainValue(field, oldval + aDir, false);
+
+ if (field == this.yearField)
+ this.year = newval;
+ else if (field == this.monthField)
+ this.month = newval;
+ else if (field == this.dateField)
+ this.date = newval;
+
+ if (oldval != newval)
+ this._fireEvent("change", this);
+ field.select();
+ ]]>
+ </body>
+ </method>
+ <method name="_setFieldValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ if (aField == this.yearField) {
+ let oldDate = this.date;
+ this._dateValue.setFullYear(aValue);
+ if (oldDate != this.date) {
+ this._dateValue.setDate(0);
+ this._updateUI(this.dateField, this.date);
+ }
+ }
+ else if (aField == this.monthField) {
+ let oldDate = this.date;
+ this._dateValue.setMonth(aValue);
+ if (oldDate != this.date) {
+ this._dateValue.setDate(0);
+ this._updateUI(this.dateField, this.date);
+ }
+ }
+ else if (aField == this.dateField) {
+ this._dateValue.setDate(aValue);
+ }
+
+ this.setAttribute("value", this.value);
+ this._updateUI(aField, aValue);
+
+ if (this.attachedControl)
+ this.attachedControl._setValueNoSync(this._dateValue);
+ ]]>
+ </body>
+ </method>
+ <method name="_updateUI">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ this._valueEntered = false;
+
+ var prependZero = false;
+ if (aField == this.yearField) {
+ if (this.yearLeadingZero) {
+ aField.value = ("000" + aValue).slice(-4);
+ return;
+ }
+ }
+ else if (aField == this.monthField) {
+ aValue++;
+ prependZero = this.monthLeadingZero;
+ }
+ else if (aField == this.dateField) {
+ prependZero = this.dateLeadingZero;
+ }
+ if (prependZero && aValue < 10)
+ aField.value = "0" + aValue;
+ else
+ aField.value = aValue;
+ ]]>
+ </body>
+ </method>
+ <method name="_constrainValue">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <parameter name="aNoWrap"/>
+ <body>
+ <![CDATA[
+ // the month will be 1 to 12 if entered by the user, so subtract 1
+ if (aNoWrap && aField == this.monthField)
+ aValue--;
+
+ if (aField == this.dateField) {
+ if (aValue < 1)
+ return new Date(this.year, this.month + 1, 0).getDate();
+
+ var currentMonth = this.month;
+ var dt = new Date(this.year, currentMonth, aValue);
+ return (dt.getMonth() != currentMonth ? 1 : aValue);
+ }
+ var min = (aField == this.monthField) ? 0 : 1;
+ var max = (aField == this.monthField) ? 11 : 9999;
+ if (aValue < min)
+ return aNoWrap ? min : max;
+ if (aValue > max)
+ return aNoWrap ? max : min;
+ return aValue;
+ ]]>
+ </body>
+ </method>
+ <method name="_init">
+ <body>
+ <![CDATA[
+ // We'll default to YYYY/MM/DD to start.
+ var yfield = "input-one";
+ var mfield = "input-two";
+ var dfield = "input-three";
+ var twoDigitYear = false;
+ this.yearLeadingZero = true;
+ this.monthLeadingZero = true;
+ this.dateLeadingZero = true;
+
+ var numberOrder = /^(\D*)\s*(\d+)(\D*)(\d+)(\D*)(\d+)\s*(\D*)$/;
+
+ var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory-nu-latn";
+
+ var dt = new Date(2002, 9, 4).toLocaleDateString(locale);
+ var numberFields = dt.match(numberOrder);
+ if (numberFields) {
+ this._separatorFirst.value = numberFields[3];
+ this._separatorSecond.value = numberFields[5];
+
+ var yi = 2, mi = 4, di = 6;
+
+ function fieldForNumber(i) {
+ if (i == 2)
+ return "input-one";
+ if (i == 4)
+ return "input-two";
+ return "input-three";
+ }
+
+ for (var i = 1; i < numberFields.length; i++) {
+ switch (Number(numberFields[i])) {
+ case 2:
+ twoDigitYear = true; // fall through
+ case 2002:
+ yi = i;
+ yfield = fieldForNumber(i);
+ break;
+ case 9, 10:
+ mi = i;
+ mfield = fieldForNumber(i);
+ break;
+ case 4:
+ di = i;
+ dfield = fieldForNumber(i);
+ break;
+ }
+ }
+
+ this.yearLeadingZero = (numberFields[yi].length > 1);
+ this.monthLeadingZero = (numberFields[mi].length > 1);
+ this.dateLeadingZero = (numberFields[di].length > 1);
+ }
+
+ this.yearField = document.getAnonymousElementByAttribute(this, "anonid", yfield);
+ if (!twoDigitYear)
+ this.yearField.parentNode.classList.add("datetimepicker-input-subbox", "datetimepicker-year");
+ this.monthField = document.getAnonymousElementByAttribute(this, "anonid", mfield);
+ this.dateField = document.getAnonymousElementByAttribute(this, "anonid", dfield);
+
+ this._fieldAMPM.parentNode.collapsed = true;
+ this.yearField.size = twoDigitYear ? 2 : 4;
+ this.yearField.maxLength = twoDigitYear ? 2 : 4;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ </binding>
+
+ <binding id="datepicker-grid"
+ extends="chrome://global/content/bindings/datetimepicker.xml#datepicker">
+
+ <content>
+ <vbox class="datepicker-mainbox"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <hbox class="datepicker-monthbox" align="center">
+ <button class="datepicker-previous datepicker-button" type="repeat"
+ xbl:inherits="disabled"
+ oncommand="document.getBindingParent(this)._increaseOrDecreaseMonth(-1);"/>
+ <spacer flex="1"/>
+ <deck anonid="monthlabeldeck">
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ <label class="datepicker-gridlabel" value=""/>
+ </deck>
+ <label anonid="yearlabel" class="datepicker-gridlabel"/>
+ <spacer flex="1"/>
+ <button class="datepicker-next datepicker-button" type="repeat"
+ xbl:inherits="disabled"
+ oncommand="document.getBindingParent(this)._increaseOrDecreaseMonth(1);"/>
+ </hbox>
+ <grid class="datepicker-grid" role="grid">
+ <columns>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ <column class="datepicker-gridrow" flex="1"/>
+ </columns>
+ <rows anonid="datebox">
+ <row anonid="dayofweekbox">
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ <label class="datepicker-weeklabel" role="columnheader"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ <row>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ <label class="datepicker-gridlabel" role="gridcell"/>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ </content>
+
+ <implementation>
+ <field name="_hasEntry">false</field>
+ <field name="_weekStart">&firstdayofweek.default;</field>
+ <field name="_displayedDate">null</field>
+ <field name="_todayItem">null</field>
+
+ <field name="yearField">
+ document.getAnonymousElementByAttribute(this, "anonid", "yearlabel");
+ </field>
+ <field name="monthField">
+ document.getAnonymousElementByAttribute(this, "anonid", "monthlabeldeck");
+ </field>
+ <field name="dateField">
+ document.getAnonymousElementByAttribute(this, "anonid", "datebox");
+ </field>
+
+ <field name="_selectedItem">null</field>
+
+ <property name="selectedItem" onget="return this._selectedItem">
+ <setter>
+ <![CDATA[
+ if (!val.value)
+ return val;
+ if (val.parentNode.parentNode != this.dateField)
+ return val;
+
+ if (this._selectedItem)
+ this._selectedItem.removeAttribute("selected");
+ this._selectedItem = val;
+ val.setAttribute("selected", "true");
+ this._displayedDate.setDate(val.value);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="displayedMonth">
+ <getter>
+ return this._displayedDate.getMonth();
+ </getter>
+ <setter>
+ this._updateUI(this.monthField, val, true);
+ return val;
+ </setter>
+ </property>
+ <property name="displayedYear">
+ <getter>
+ return this._displayedDate.getFullYear();
+ </getter>
+ <setter>
+ this._updateUI(this.yearField, val, true);
+ return val;
+ </setter>
+ </property>
+
+ <method name="_init">
+ <body>
+ <![CDATA[
+ var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory";
+ var dtfMonth = Intl.DateTimeFormat(locale, {month: "long", timeZone: "UTC"});
+ var dtfWeekday = Intl.DateTimeFormat(locale, {weekday: "narrow"});
+
+ var monthLabel = this.monthField.firstChild;
+ var tempDate = new Date(Date.UTC(2005, 0, 1));
+ for (var month = 0; month < 12; month++) {
+ tempDate.setUTCMonth(month);
+ monthLabel.setAttribute("value", dtfMonth.format(tempDate));
+ monthLabel = monthLabel.nextSibling;
+ }
+
+ var fdow = Number(this.getAttribute("firstdayofweek"));
+ if (!isNaN(fdow) && fdow >= 0 && fdow <= 6)
+ this._weekStart = fdow;
+
+ var weekbox = document.getAnonymousElementByAttribute(this, "anonid", "dayofweekbox").childNodes;
+ var date = new Date();
+ date.setDate(date.getDate() - (date.getDay() - this._weekStart));
+ for (var i = 0; i < weekbox.length; i++) {
+ weekbox[i].value = dtfWeekday.format(date);
+ date.setDate(date.getDate() + 1);
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_setValueNoSync">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var dt = new Date(aValue);
+ if (!isNaN(dt)) {
+ this._dateValue = dt;
+ this.setAttribute("value", this.value);
+ this._updateUI();
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_updateUI">
+ <parameter name="aField"/>
+ <parameter name="aValue"/>
+ <parameter name="aCheckMonth"/>
+ <body>
+ <![CDATA[
+ var date;
+ var currentMonth;
+ if (aCheckMonth) {
+ if (!this._displayedDate)
+ this._displayedDate = this.dateValue;
+
+ var expectedMonth = aValue;
+ if (aField == this.monthField) {
+ this._displayedDate.setMonth(aValue);
+ }
+ else {
+ expectedMonth = this._displayedDate.getMonth();
+ this._displayedDate.setFullYear(aValue);
+ }
+
+ if (expectedMonth != -1 && expectedMonth != 12 &&
+ expectedMonth != this._displayedDate.getMonth()) {
+ // If the month isn't what was expected, then the month overflowed.
+ // Setting the date to 0 will go back to the last day of the right month.
+ this._displayedDate.setDate(0);
+ }
+
+ date = new Date(this._displayedDate);
+ currentMonth = this._displayedDate.getMonth();
+ }
+ else {
+ var samemonth = (this._displayedDate &&
+ this._displayedDate.getMonth() == this.month &&
+ this._displayedDate.getFullYear() == this.year);
+ if (samemonth) {
+ var items = this.dateField.getElementsByAttribute("value", this.date);
+ if (items.length)
+ this.selectedItem = items[0];
+ return;
+ }
+
+ date = this.dateValue;
+ this._displayedDate = new Date(date);
+ currentMonth = this.month;
+ }
+
+ if (this._todayItem) {
+ this._todayItem.removeAttribute("today");
+ this._todayItem = null;
+ }
+
+ if (this._selectedItem) {
+ this._selectedItem.removeAttribute("selected");
+ this._selectedItem = null;
+ }
+
+ // Update the month and year title
+ this.monthField.selectedIndex = currentMonth;
+ this.yearField.setAttribute("value", date.getFullYear());
+
+ date.setDate(1);
+ var firstWeekday = (7 + date.getDay() - this._weekStart) % 7;
+ date.setDate(date.getDate() - firstWeekday);
+
+ var today = new Date();
+ var datebox = this.dateField;
+ for (var k = 1; k < datebox.childNodes.length; k++) {
+ var row = datebox.childNodes[k];
+ for (var i = 0; i < 7; i++) {
+ var item = row.childNodes[i];
+
+ if (currentMonth == date.getMonth()) {
+ item.value = date.getDate();
+
+ // highlight today
+ if (this._isSameDay(today, date)) {
+ this._todayItem = item;
+ item.setAttribute("today", "true");
+ }
+
+ // highlight the selected date
+ if (this._isSameDay(this._dateValue, date)) {
+ this._selectedItem = item;
+ item.setAttribute("selected", "true");
+ }
+ }
+ else {
+ item.value = "";
+ }
+
+ date.setDate(date.getDate() + 1);
+ }
+ }
+
+ this._fireEvent("monthchange", this);
+ if (this.hasAttribute("monthchange")) {
+ var fn = new Function("event", aTarget.getAttribute("onmonthchange"));
+ fn.call(aTarget, event);
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_increaseOrDecreaseDateFromEvent">
+ <parameter name="aEvent"/>
+ <parameter name="aDiff"/>
+ <body>
+ <![CDATA[
+ if (aEvent.originalTarget == this && !this.disabled && !this.readOnly) {
+ var newdate = this.dateValue;
+ newdate.setDate(newdate.getDate() + aDiff);
+ this.dateValue = newdate;
+ this._fireEvent("change", this);
+ }
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ ]]>
+ </body>
+ </method>
+ <method name="_increaseOrDecreaseMonth">
+ <parameter name="aDir"/>
+ <body>
+ <![CDATA[
+ if (!this.disabled) {
+ var month = this._displayedDate ? this._displayedDate.getMonth() :
+ this.month;
+ this._updateUI(this.monthField, month + aDir, true);
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="_isSameDay">
+ <parameter name="aDate1"/>
+ <parameter name="aDate2"/>
+ <body>
+ <![CDATA[
+ return (aDate1 && aDate2 &&
+ aDate1.getDate() == aDate2.getDate() &&
+ aDate1.getMonth() == aDate2.getMonth() &&
+ aDate1.getFullYear() == aDate2.getFullYear());
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="click">
+ <![CDATA[
+ if (event.button != 0 || this.disabled || this.readOnly)
+ return;
+
+ var target = event.originalTarget;
+ if (target.classList.contains("datepicker-gridlabel") &&
+ target != this.selectedItem) {
+ this.selectedItem = target;
+ this._dateValue = new Date(this._displayedDate);
+ if (this.attachedControl)
+ this.attachedControl._setValueNoSync(this._dateValue);
+ this._fireEvent("change", this);
+
+ if (this.attachedControl && "open" in this.attachedControl)
+ this.attachedControl.open = false; // close the popup
+ }
+ ]]>
+ </handler>
+ <handler event="MozMousePixelScroll" preventdefault="true"/>
+ <handler event="DOMMouseScroll" preventdefault="true">
+ <![CDATA[
+ this._increaseOrDecreaseMonth(event.detail < 0 ? -1 : 1);
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_LEFT"
+ action="this._increaseOrDecreaseDateFromEvent(event, -1);"/>
+ <handler event="keypress" keycode="VK_RIGHT"
+ action="this._increaseOrDecreaseDateFromEvent(event, 1);"/>
+ <handler event="keypress" keycode="VK_UP"
+ action="this._increaseOrDecreaseDateFromEvent(event, -7);"/>
+ <handler event="keypress" keycode="VK_DOWN"
+ action="this._increaseOrDecreaseDateFromEvent(event, 7);"/>
+ <handler event="keypress" keycode="VK_PAGE_UP" preventdefault="true"
+ action="this._increaseOrDecreaseMonth(-1);"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" preventdefault="true"
+ action="this._increaseOrDecreaseMonth(1);"/>
+ </handlers>
+ </binding>
+
+ <binding id="datepicker-popup" display="xul:menu"
+ extends="chrome://global/content/bindings/datetimepicker.xml#datepicker">
+ <content align="center">
+ <xul:hbox class="textbox-input-box datetimepicker-input-box" align="center"
+ allowevents="true" xbl:inherits="context,disabled,readonly">
+ <xul:hbox class="datetimepicker-input-subbox" align="baseline">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-one"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ <xul:label anonid="sep-first" class="datetimepicker-separator" value=":"/>
+ <xul:hbox class="datetimepicker-input-subbox" align="baseline">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-two"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ <xul:label anonid="sep-second" class="datetimepicker-separator" value=":"/>
+ <xul:hbox class="datetimepicker-input-subbox" align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-three"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ <xul:hbox class="datetimepicker-input-subbox" align="center">
+ <html:input class="datetimepicker-input textbox-input" anonid="input-ampm"
+ size="2" maxlength="2"
+ xbl:inherits="disabled,readonly"/>
+ </xul:hbox>
+ </xul:hbox>
+ <xul:spinbuttons anonid="buttons" xbl:inherits="disabled" allowevents="true"
+ onup="this.parentNode._increaseOrDecrease(1);"
+ ondown="this.parentNode._increaseOrDecrease(-1);"/>
+ <xul:dropmarker class="datepicker-dropmarker" xbl:inherits="disabled"/>
+ <xul:panel onpopupshown="this.firstChild.focus();" level="top">
+ <xul:datepicker anonid="grid" type="grid" class="datepicker-popupgrid"
+ xbl:inherits="disabled,readonly,firstdayofweek"/>
+ </xul:panel>
+ </content>
+ <implementation>
+ <constructor>
+ var grid = document.getAnonymousElementByAttribute(this, "anonid", "grid");
+ this.attachedControl = grid;
+ grid.attachedControl = this;
+ grid._setValueNoSync(this._dateValue);
+ </constructor>
+ <property name="open" onget="return this.hasAttribute('open');">
+ <setter>
+ <![CDATA[
+ if (this.boxObject instanceof MenuBoxObject)
+ this.boxObject.openMenu(val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="displayedMonth">
+ <getter>
+ return document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedMonth;
+ </getter>
+ <setter>
+ document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedMonth = val;
+ return val;
+ </setter>
+ </property>
+ <property name="displayedYear">
+ <getter>
+ return document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedYear;
+ </getter>
+ <setter>
+ document.getAnonymousElementByAttribute(this, "anonid", "grid").displayedYear = val;
+ return val;
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/datetimepopup.xml b/components/bindings/content/datetimepopup.xml
new file mode 100644
index 000000000..19e7b4f8a
--- /dev/null
+++ b/components/bindings/content/datetimepopup.xml
@@ -0,0 +1,322 @@
+<?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="dateTimePopupBindings"
+ 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="datetime-popup"
+ extends="chrome://global/content/bindings/popup.xml#arrowpanel">
+ <resources>
+ <stylesheet src="chrome://global/skin/datetimepopup.css"/>
+ </resources>
+ <implementation>
+ <field name="dateTimePopupFrame">
+ this.querySelector("#dateTimePopupFrame");
+ </field>
+ <field name="TIME_PICKER_WIDTH" readonly="true">"12em"</field>
+ <field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field>
+ <field name="DATE_PICKER_WIDTH" readonly="true">"23.1em"</field>
+ <field name="DATE_PICKER_HEIGHT" readonly="true">"20.7em"</field>
+ <constructor><![CDATA[
+ this.l10n = {};
+ const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
+ .getService(Components.interfaces.mozIMozIntl);
+ mozIntl.addGetCalendarInfo(this.l10n);
+ mozIntl.addGetDisplayNames(this.l10n);
+ // Notify DateTimePickerHelper.jsm that binding is ready.
+ this.dispatchEvent(new CustomEvent("DateTimePickerBindingReady"));
+ ]]></constructor>
+ <method name="openPicker">
+ <parameter name="type"/>
+ <parameter name="anchor"/>
+ <parameter name="detail"/>
+ <body><![CDATA[
+ this.type = type;
+ this.pickerState = {};
+ // TODO: Resize picker according to content zoom level
+ this.style.fontSize = "10px";
+ switch (type) {
+ case "time": {
+ this.detail = detail;
+ this.dateTimePopupFrame.addEventListener("load", this, true);
+ this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/timepicker.xhtml");
+ this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
+ this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
+ break;
+ }
+ case "date": {
+ this.detail = detail;
+ this.dateTimePopupFrame.addEventListener("load", this, true);
+ this.dateTimePopupFrame.setAttribute("src", "chrome://global/content/datepicker.xhtml");
+ this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
+ this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
+ break;
+ }
+ }
+ this.hidden = false;
+ this.openPopup(anchor, "after_start", 0, 0);
+ ]]></body>
+ </method>
+ <method name="closePicker">
+ <body><![CDATA[
+ this.setInputBoxValue(true);
+ this.pickerState = {};
+ this.type = undefined;
+ this.dateTimePopupFrame.removeEventListener("load", this, true);
+ this.dateTimePopupFrame.contentDocument.removeEventListener("message", this, false);
+ this.dateTimePopupFrame.setAttribute("src", "");
+ this.hidden = true;
+ ]]></body>
+ </method>
+ <method name="setPopupValue">
+ <parameter name="data"/>
+ <body><![CDATA[
+ switch (this.type) {
+ case "time": {
+ this.postMessageToPicker({
+ name: "PickerSetValue",
+ detail: data.value
+ });
+ break;
+ }
+ case "date": {
+ const { year, month, day } = data.value;
+ this.postMessageToPicker({
+ name: "PickerSetValue",
+ detail: {
+ year,
+ // Month value from input box starts from 1 instead of 0
+ month: month == undefined ? undefined : month - 1,
+ day
+ }
+ });
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+ <method name="initPicker">
+ <parameter name="detail"/>
+ <body><![CDATA[
+ const locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global");
+
+ switch (this.type) {
+ case "time": {
+ const { hour, minute } = detail.value;
+ const format = detail.format || "12";
+
+ this.postMessageToPicker({
+ name: "PickerInit",
+ detail: {
+ hour,
+ minute,
+ format,
+ locale,
+ min: detail.min,
+ max: detail.max,
+ step: detail.step,
+ }
+ });
+ break;
+ }
+ case "date": {
+ const { year, month, day } = detail.value;
+ const { firstDayOfWeek, weekends } =
+ this.getCalendarInfo(locale);
+ const monthStrings = this.getDisplayNames(
+ locale, [
+ "dates/gregorian/months/january",
+ "dates/gregorian/months/february",
+ "dates/gregorian/months/march",
+ "dates/gregorian/months/april",
+ "dates/gregorian/months/may",
+ "dates/gregorian/months/june",
+ "dates/gregorian/months/july",
+ "dates/gregorian/months/august",
+ "dates/gregorian/months/september",
+ "dates/gregorian/months/october",
+ "dates/gregorian/months/november",
+ "dates/gregorian/months/december",
+ ], "short");
+ const weekdayStrings = this.getDisplayNames(
+ locale, [
+ "dates/gregorian/weekdays/sunday",
+ "dates/gregorian/weekdays/monday",
+ "dates/gregorian/weekdays/tuesday",
+ "dates/gregorian/weekdays/wednesday",
+ "dates/gregorian/weekdays/thursday",
+ "dates/gregorian/weekdays/friday",
+ "dates/gregorian/weekdays/saturday",
+ ], "short");
+
+ this.postMessageToPicker({
+ name: "PickerInit",
+ detail: {
+ year,
+ // Month value from input box starts from 1 instead of 0
+ month: month == undefined ? undefined : month - 1,
+ day,
+ firstDayOfWeek,
+ weekends,
+ monthStrings,
+ weekdayStrings,
+ locale,
+ min: detail.min,
+ max: detail.max,
+ step: detail.step,
+ stepBase: detail.stepBase,
+ }
+ });
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+ <method name="setInputBoxValue">
+ <parameter name="passAllValues"/>
+ <body><![CDATA[
+ /**
+ * @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not
+ */
+ switch (this.type) {
+ case "time": {
+ const { hour, minute, isHourSet, isMinuteSet, isDayPeriodSet } = this.pickerState;
+ const isAnyValueSet = isHourSet || isMinuteSet || isDayPeriodSet;
+ if (passAllValues && isAnyValueSet) {
+ this.sendPickerValueChanged({ hour, minute });
+ } else {
+ this.sendPickerValueChanged({
+ hour: isHourSet || isDayPeriodSet ? hour : undefined,
+ minute: isMinuteSet ? minute : undefined
+ });
+ }
+ break;
+ }
+ case "date": {
+ this.sendPickerValueChanged(this.pickerState);
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+ <method name="sendPickerValueChanged">
+ <parameter name="value"/>
+ <body><![CDATA[
+ switch (this.type) {
+ case "time": {
+ this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
+ detail: {
+ hour: value.hour,
+ minute: value.minute
+ }
+ }));
+ break;
+ }
+ case "date": {
+ this.dispatchEvent(new CustomEvent("DateTimePickerValueChanged", {
+ detail: {
+ year: value.year,
+ // Month value from input box starts from 1 instead of 0
+ month: value.month == undefined ? undefined : value.month + 1,
+ day: value.day
+ }
+ }));
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+ <method name="getCalendarInfo">
+ <parameter name="locale"/>
+ <body><![CDATA[
+ const calendarInfo = this.l10n.getCalendarInfo(locale);
+
+ // Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
+ // so they need to be mapped to JavaScript convention with 0 as Sunday
+ // and 6 as Saturday
+ let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
+ weekendStart = calendarInfo.weekendStart - 1,
+ weekendEnd = calendarInfo.weekendEnd - 1;
+
+ let weekends = [];
+
+ // Make sure weekendEnd is greater than weekendStart
+ if (weekendEnd < weekendStart) {
+ weekendEnd += 7;
+ }
+
+ // We get the weekends by incrementing weekendStart up to weekendEnd.
+ // If the start and end is the same day, then weekends only has one day.
+ for (let day = weekendStart; day <= weekendEnd; day++) {
+ weekends.push(day % 7);
+ }
+
+ return {
+ firstDayOfWeek,
+ weekends
+ }
+ ]]></body>
+ </method>
+ <method name="getDisplayNames">
+ <parameter name="locale"/>
+ <parameter name="keys"/>
+ <parameter name="style"/>
+ <body><![CDATA[
+ const displayNames = this.l10n.getDisplayNames(locale, {keys, style});
+ return keys.map(key => displayNames.values[key]);
+ ]]></body>
+ </method>
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "load": {
+ this.initPicker(this.detail);
+ this.dateTimePopupFrame.contentWindow.addEventListener("message", this, false);
+ break;
+ }
+ case "message": {
+ this.handleMessage(aEvent);
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+ <method name="handleMessage">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (!this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
+ return;
+ }
+
+ switch (aEvent.data.name) {
+ case "PickerPopupChanged": {
+ this.pickerState = aEvent.data.detail;
+ this.setInputBoxValue();
+ break;
+ }
+ case "ClosePopup": {
+ this.hidePopup();
+ this.closePicker();
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+ <method name="postMessageToPicker">
+ <parameter name="data"/>
+ <body><![CDATA[
+ if (this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal) {
+ this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
+ }
+ ]]></body>
+ </method>
+
+ </implementation>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/dialog.xml b/components/bindings/content/dialog.xml
new file mode 100644
index 000000000..aff162196
--- /dev/null
+++ b/components/bindings/content/dialog.xml
@@ -0,0 +1,444 @@
+<?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="dialogBindings"
+ 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="dialog" extends="chrome://global/content/bindings/general.xml#root-element">
+ <resources>
+ <stylesheet src="chrome://global/skin/dialog.css"/>
+ </resources>
+ <content>
+ <xul:vbox class="box-inherit dialog-content-box" flex="1">
+ <children/>
+ </xul:vbox>
+
+ <xul:hbox class="dialog-button-box" anonid="buttons"
+ xbl:inherits="pack=buttonpack,align=buttonalign,dir=buttondir,orient=buttonorient"
+#ifdef XP_UNIX
+ >
+ <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="help" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
+ <xul:spacer anonid="spacer" flex="1"/>
+ <xul:button dlgtype="cancel" class="dialog-button"/>
+ <xul:button dlgtype="accept" class="dialog-button" xbl:inherits="disabled=buttondisabledaccept"/>
+#else
+ pack="end">
+ <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
+ <xul:spacer anonid="spacer" flex="1" hidden="true"/>
+ <xul:button dlgtype="accept" class="dialog-button" xbl:inherits="disabled=buttondisabledaccept"/>
+ <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="cancel" class="dialog-button"/>
+ <xul:button dlgtype="help" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
+#endif
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <field name="_mStrBundle">null</field>
+ <field name="_closeHandler">(function(event) {
+ if (!document.documentElement.cancelDialog())
+ event.preventDefault();
+ })</field>
+
+ <property name="buttons"
+ onget="return this.getAttribute('buttons');"
+ onset="this._configureButtons(val); return val;"/>
+
+ <property name="defaultButton">
+ <getter>
+ <![CDATA[
+ if (this.hasAttribute("defaultButton"))
+ return this.getAttribute("defaultButton");
+ return "accept"; // default to the accept button
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this._setDefaultButton(val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="acceptDialog">
+ <body>
+ <![CDATA[
+ return this._doButtonCommand("accept");
+ ]]>
+ </body>
+ </method>
+
+ <method name="cancelDialog">
+ <body>
+ <![CDATA[
+ return this._doButtonCommand("cancel");
+ ]]>
+ </body>
+ </method>
+
+ <method name="getButton">
+ <parameter name="aDlgType"/>
+ <body>
+ <![CDATA[
+ return this._buttons[aDlgType];
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveToAlertPosition">
+ <body>
+ <![CDATA[
+ // hack. we need this so the window has something like its final size
+ if (window.outerWidth == 1) {
+ dump("Trying to position a sizeless window; caller should have called sizeToContent() or sizeTo(). See bug 75649.\n");
+ sizeToContent();
+ }
+
+ if (opener) {
+ var xOffset = (opener.outerWidth - window.outerWidth) / 2;
+ var yOffset = opener.outerHeight / 5;
+
+ var newX = opener.screenX + xOffset;
+ var newY = opener.screenY + yOffset;
+ } else {
+ newX = (screen.availWidth - window.outerWidth) / 2;
+ newY = (screen.availHeight - window.outerHeight) / 2;
+ }
+
+ // ensure the window is fully onscreen (if smaller than the screen)
+ if (newX < screen.availLeft)
+ newX = screen.availLeft + 20;
+ if ((newX + window.outerWidth) > (screen.availLeft + screen.availWidth))
+ newX = (screen.availLeft + screen.availWidth) - window.outerWidth - 20;
+
+ if (newY < screen.availTop)
+ newY = screen.availTop + 20;
+ if ((newY + window.outerHeight) > (screen.availTop + screen.availHeight))
+ newY = (screen.availTop + screen.availHeight) - window.outerHeight - 60;
+
+ window.moveTo( newX, newY );
+ ]]>
+ </body>
+ </method>
+
+ <method name="centerWindowOnScreen">
+ <body>
+ <![CDATA[
+ var xOffset = screen.availWidth/2 - window.outerWidth/2;
+ var yOffset = screen.availHeight/2 - window.outerHeight/2;
+
+ xOffset = xOffset > 0 ? xOffset : 0;
+ yOffset = yOffset > 0 ? yOffset : 0;
+ window.moveTo(xOffset, yOffset);
+ ]]>
+ </body>
+ </method>
+
+ <constructor>
+ <![CDATA[
+ this._configureButtons(this.buttons);
+
+ // listen for when window is closed via native close buttons
+ window.addEventListener("close", this._closeHandler, false);
+
+ // for things that we need to initialize after onload fires
+ window.addEventListener("load", this.postLoadInit, false);
+
+ window.moveToAlertPosition = this.moveToAlertPosition;
+ window.centerWindowOnScreen = this.centerWindowOnScreen;
+ ]]>
+ </constructor>
+
+ <method name="postLoadInit">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ function focusInit() {
+ const dialog = document.documentElement;
+ const defaultButton = dialog.getButton(dialog.defaultButton);
+ // give focus to the first focusable element in the dialog
+ if (!document.commandDispatcher.focusedElement) {
+ document.commandDispatcher.advanceFocusIntoSubtree(dialog);
+
+ var focusedElt = document.commandDispatcher.focusedElement;
+ if (focusedElt) {
+ var initialFocusedElt = focusedElt;
+ while (focusedElt.localName == "tab" ||
+ focusedElt.getAttribute("noinitialfocus") == "true") {
+ document.commandDispatcher.advanceFocusIntoSubtree(focusedElt);
+ focusedElt = document.commandDispatcher.focusedElement;
+ if (focusedElt == initialFocusedElt)
+ break;
+ }
+
+ if (initialFocusedElt.localName == "tab") {
+ if (focusedElt.hasAttribute("dlgtype")) {
+ // We don't want to focus on anonymous OK, Cancel, etc. buttons,
+ // so return focus to the tab itself
+ initialFocusedElt.focus();
+ }
+ }
+ else if (!/Mac/.test(navigator.platform) &&
+ focusedElt.hasAttribute("dlgtype") && focusedElt != defaultButton) {
+ defaultButton.focus();
+ }
+ }
+ }
+
+ try {
+ if (defaultButton)
+ window.notifyDefaultButtonLoaded(defaultButton);
+ } catch (e) { }
+ }
+
+ // Give focus after onload completes, see bug 103197.
+ setTimeout(focusInit, 0);
+ ]]>
+ </body>
+ </method>
+
+ <property name="mStrBundle">
+ <getter>
+ <![CDATA[
+ if (!this._mStrBundle) {
+ // need to create string bundle manually instead of using <xul:stringbundle/>
+ // see bug 63370 for details
+ this._mStrBundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
+ .getService(Components.interfaces.nsIStringBundleService)
+ .createBundle("chrome://global/locale/dialog.properties");
+ }
+ return this._mStrBundle;
+ ]]></getter>
+ </property>
+
+ <method name="_configureButtons">
+ <parameter name="aButtons"/>
+ <body>
+ <![CDATA[
+ // by default, get all the anonymous button elements
+ var buttons = {};
+ this._buttons = buttons;
+ buttons.accept = document.getAnonymousElementByAttribute(this, "dlgtype", "accept");
+ buttons.cancel = document.getAnonymousElementByAttribute(this, "dlgtype", "cancel");
+ buttons.extra1 = document.getAnonymousElementByAttribute(this, "dlgtype", "extra1");
+ buttons.extra2 = document.getAnonymousElementByAttribute(this, "dlgtype", "extra2");
+ buttons.help = document.getAnonymousElementByAttribute(this, "dlgtype", "help");
+ buttons.disclosure = document.getAnonymousElementByAttribute(this, "dlgtype", "disclosure");
+
+ // look for any overriding explicit button elements
+ var exBtns = this.getElementsByAttribute("dlgtype", "*");
+ var dlgtype;
+ var i;
+ for (i = 0; i < exBtns.length; ++i) {
+ dlgtype = exBtns[i].getAttribute("dlgtype");
+ buttons[dlgtype].hidden = true; // hide the anonymous button
+ buttons[dlgtype] = exBtns[i];
+ }
+
+ // add the label and oncommand handler to each button
+ for (dlgtype in buttons) {
+ var button = buttons[dlgtype];
+ button.addEventListener("command", this._handleButtonCommand, true);
+
+ // don't override custom labels with pre-defined labels on explicit buttons
+ if (!button.hasAttribute("label")) {
+ // dialog attributes override the default labels in dialog.properties
+ if (this.hasAttribute("buttonlabel"+dlgtype)) {
+ button.setAttribute("label", this.getAttribute("buttonlabel"+dlgtype));
+ if (this.hasAttribute("buttonaccesskey"+dlgtype))
+ button.setAttribute("accesskey", this.getAttribute("buttonaccesskey"+dlgtype));
+ } else if (dlgtype != "extra1" && dlgtype != "extra2") {
+ button.setAttribute("label", this.mStrBundle.GetStringFromName("button-"+dlgtype));
+ var accessKey = this.mStrBundle.GetStringFromName("accesskey-"+dlgtype);
+ if (accessKey)
+ button.setAttribute("accesskey", accessKey);
+ }
+ }
+ // allow specifying alternate icons in the dialog header
+ if (!button.hasAttribute("icon")) {
+ // if there's an icon specified, use that
+ if (this.hasAttribute("buttonicon"+dlgtype))
+ button.setAttribute("icon", this.getAttribute("buttonicon"+dlgtype));
+ // otherwise set defaults
+ else
+ switch (dlgtype) {
+ case "accept":
+ button.setAttribute("icon", "accept");
+ break;
+ case "cancel":
+ button.setAttribute("icon", "cancel");
+ break;
+ case "disclosure":
+ button.setAttribute("icon", "properties");
+ break;
+ case "help":
+ button.setAttribute("icon", "help");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // ensure that hitting enter triggers the default button command
+ this.defaultButton = this.defaultButton;
+
+ // if there is a special button configuration, use it
+ if (aButtons) {
+ // expect a comma delimited list of dlgtype values
+ var list = aButtons.split(",");
+
+ // mark shown dlgtypes as true
+ var shown = { accept: false, cancel: false, help: false,
+ disclosure: false, extra1: false, extra2: false };
+ for (i = 0; i < list.length; ++i)
+ shown[list[i].replace(/ /g, "")] = true;
+
+ // hide/show the buttons we want
+ for (dlgtype in buttons)
+ buttons[dlgtype].hidden = !shown[dlgtype];
+
+ // show the spacer on Windows only when the extra2 button is present
+ if (/Win/.test(navigator.platform)) {
+ var spacer = document.getAnonymousElementByAttribute(this, "anonid", "spacer");
+ spacer.removeAttribute("hidden");
+ spacer.setAttribute("flex", shown["extra2"]?"1":"0");
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_setDefaultButton">
+ <parameter name="aNewDefault"/>
+ <body>
+ <![CDATA[
+ // remove the default attribute from the previous default button, if any
+ var oldDefaultButton = this.getButton(this.defaultButton);
+ if (oldDefaultButton)
+ oldDefaultButton.removeAttribute("default");
+
+ var newDefaultButton = this.getButton(aNewDefault);
+ if (newDefaultButton) {
+ this.setAttribute("defaultButton", aNewDefault);
+ newDefaultButton.setAttribute("default", "true");
+ }
+ else {
+ this.setAttribute("defaultButton", "none");
+ if (aNewDefault != "none")
+ dump("invalid new default button: " + aNewDefault + ", assuming: none\n");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleButtonCommand">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ return document.documentElement._doButtonCommand(
+ aEvent.target.getAttribute("dlgtype"));
+ ]]>
+ </body>
+ </method>
+
+ <method name="_doButtonCommand">
+ <parameter name="aDlgType"/>
+ <body>
+ <![CDATA[
+ var button = this.getButton(aDlgType);
+ if (!button.disabled) {
+ var noCancel = this._fireButtonEvent(aDlgType);
+ if (noCancel) {
+ if (aDlgType == "accept" || aDlgType == "cancel") {
+ var closingEvent = new CustomEvent("dialogclosing", {
+ bubbles: true,
+ detail: { button: aDlgType },
+ });
+ this.dispatchEvent(closingEvent);
+ window.close();
+ }
+ }
+ return noCancel;
+ }
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_fireButtonEvent">
+ <parameter name="aDlgType"/>
+ <body>
+ <![CDATA[
+ var event = document.createEvent("Events");
+ event.initEvent("dialog"+aDlgType, true, true);
+
+ // handle dom event handlers
+ var noCancel = this.dispatchEvent(event);
+
+ // handle any xml attribute event handlers
+ var handler = this.getAttribute("ondialog"+aDlgType);
+ if (handler != "") {
+ var fn = new Function("event", handler);
+ var returned = fn(event);
+ if (returned == false)
+ noCancel = false;
+ }
+
+ return noCancel;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_hitEnter">
+ <parameter name="evt"/>
+ <body>
+ <![CDATA[
+ if (evt.defaultPrevented)
+ return;
+
+ var btn = this.getButton(this.defaultButton);
+ if (btn)
+ this._doButtonCommand(this.defaultButton);
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="keypress" keycode="VK_RETURN"
+ group="system" action="this._hitEnter(event);"/>
+ <handler event="keypress" keycode="VK_ESCAPE" group="system">
+ if (!event.defaultPrevented)
+ this.cancelDialog();
+ </handler>
+ <handler event="focus" phase="capturing">
+ var btn = this.getButton(this.defaultButton);
+ if (btn)
+ btn.setAttribute("default", event.originalTarget == btn || !(event.originalTarget instanceof Components.interfaces.nsIDOMXULButtonElement));
+ </handler>
+ </handlers>
+
+ </binding>
+
+ <binding id="dialogheader">
+ <resources>
+ <stylesheet src="chrome://global/skin/dialog.css"/>
+ </resources>
+ <content>
+ <xul:label class="dialogheader-title" xbl:inherits="value=title,crop" crop="right" flex="1"/>
+ <xul:label class="dialogheader-description" xbl:inherits="value=description"/>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/editor.xml b/components/bindings/content/editor.xml
new file mode 100644
index 000000000..637586dc2
--- /dev/null
+++ b/components/bindings/content/editor.xml
@@ -0,0 +1,195 @@
+<?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="editorBindings"
+ 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="editor" role="outerdoc">
+ <implementation type="application/javascript">
+ <constructor>
+ <![CDATA[
+ // Make window editable immediately only
+ // if the "editortype" attribute is supplied
+ // This allows using same contentWindow for different editortypes,
+ // where the type is determined during the apps's window.onload handler.
+ if (this.editortype)
+ this.makeEditable(this.editortype, true);
+ ]]>
+ </constructor>
+ <destructor/>
+
+ <field name="_editorContentListener">
+ <![CDATA[
+ ({
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Components.interfaces.nsIURIContentListener) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onStartURIOpen: function(uri)
+ {
+ return false;
+ },
+ doContent: function(contentType, isContentPreferred, request, contentHandler)
+ {
+ return false;
+ },
+ isPreferred: function(contentType, desiredContentType)
+ {
+ return false;
+ },
+ canHandleContent: function(contentType, isContentPreferred, desiredContentType)
+ {
+ return false;
+ },
+ loadCookie: null,
+ parentContentListener: null
+ })
+ ]]>
+ </field>
+ <method name="makeEditable">
+ <parameter name="editortype"/>
+ <parameter name="waitForUrlLoad"/>
+ <body>
+ <![CDATA[
+ this.editingSession.makeWindowEditable(this.contentWindow, editortype, waitForUrlLoad, true, false);
+ this.setAttribute("editortype", editortype);
+
+ this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIURIContentListener)
+ .parentContentListener = this._editorContentListener;
+ ]]>
+ </body>
+ </method>
+ <method name="getEditor">
+ <parameter name="containingWindow"/>
+ <body>
+ <![CDATA[
+ return this.editingSession.getEditorForWindow(containingWindow);
+ ]]>
+ </body>
+ </method>
+ <method name="getHTMLEditor">
+ <parameter name="containingWindow"/>
+ <body>
+ <![CDATA[
+ var editor = this.editingSession.getEditorForWindow(containingWindow);
+ return editor.QueryInterface(Components.interfaces.nsIHTMLEditor);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_finder">null</field>
+ <property name="finder" readonly="true">
+ <getter><![CDATA[
+ if (!this._finder) {
+ if (!this.docShell)
+ return null;
+
+ let Finder = Components.utils.import("resource://gre/modules/Finder.jsm", {}).Finder;
+ this._finder = new Finder(this.docShell);
+ }
+ return this._finder;
+ ]]></getter>
+ </property>
+
+ <field name="_fastFind">null</field>
+ <property name="fastFind"
+ readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._fastFind) {
+ if (!("@mozilla.org/typeaheadfind;1" in Components.classes))
+ return null;
+
+ if (!this.docShell)
+ return null;
+
+ 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>
+
+ <property name="editortype"
+ onget="return this.getAttribute('editortype');"
+ onset="this.setAttribute('editortype', val); return val;"/>
+ <property name="webNavigation"
+ onget="return this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"
+ readonly="true"/>
+ <property name="contentDocument" readonly="true"
+ onget="return this.webNavigation.document;"/>
+ <property name="docShell" readonly="true">
+ <getter><![CDATA[
+ let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ return frameLoader ? frameLoader.docShell : null;
+ ]]></getter>
+ </property>
+ <property name="currentURI"
+ readonly="true"
+ onget="return this.webNavigation.currentURI;"/>
+ <property name="contentWindow"
+ readonly="true"
+ onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);"/>
+ <property name="contentWindowAsCPOW"
+ readonly="true"
+ onget="return this.contentWindow;"/>
+ <property name="webBrowserFind"
+ readonly="true"
+ onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebBrowserFind);"/>
+ <property name="markupDocumentViewer"
+ readonly="true"
+ onget="return this.docShell.contentViewer;"/>
+ <property name="editingSession"
+ readonly="true"
+ onget="return this.webNavigation.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIEditingSession);"/>
+ <property name="commandManager"
+ readonly="true"
+ onget="return this.webNavigation.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsICommandManager);"/>
+ <property name="fullZoom"
+ onget="return this.markupDocumentViewer.fullZoom;"
+ onset="this.markupDocumentViewer.fullZoom = val;"/>
+ <property name="textZoom"
+ onget="return this.markupDocumentViewer.textZoom;"
+ onset="this.markupDocumentViewer.textZoom = val;"/>
+ <property name="isSyntheticDocument"
+ onget="return this.contentDocument.isSyntheticDocument;"
+ readonly="true"/>
+ <property name="messageManager"
+ readonly="true">
+ <getter>
+ <![CDATA[
+ var owner = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
+ if (!owner.frameLoader) {
+ return null;
+ }
+ return owner.frameLoader.messageManager;
+ ]]>
+ </getter>
+ </property>
+ <property name="outerWindowID" readonly="true">
+ <getter><![CDATA[
+ return this.contentWindow
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .outerWindowID;
+ ]]></getter>
+ </property>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/expander.xml b/components/bindings/content/expander.xml
new file mode 100644
index 000000000..a4ffea313
--- /dev/null
+++ b/components/bindings/content/expander.xml
@@ -0,0 +1,86 @@
+<?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="expanderBindings"
+ 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="expander" display="xul:vbox">
+ <resources>
+ <stylesheet src="chrome://global/skin/expander.css"/>
+ </resources>
+ <content>
+ <xul:hbox align="center">
+ <xul:button type="disclosure" class="expanderButton" anonid="disclosure" xbl:inherits="disabled" mousethrough="always"/>
+ <xul:label class="header expanderButton" anonid="label" xbl:inherits="value=label,disabled" mousethrough="always" flex="1"/>
+ <xul:button anonid="clear-button" xbl:inherits="label=clearlabel,disabled=cleardisabled,hidden=clearhidden" mousethrough="always" icon="clear"/>
+ </xul:hbox>
+ <xul:vbox flex="1" anonid="settings" class="settingsContainer" collapsed="true" xbl:inherits="align">
+ <children/>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <constructor><![CDATA[
+ var settings = document.getAnonymousElementByAttribute(this, "anonid", "settings");
+ var expander = document.getAnonymousElementByAttribute(this, "anonid", "disclosure");
+ var open = this.getAttribute("open") == "true";
+ settings.collapsed = !open;
+ expander.open = open;
+ ]]></constructor>
+ <property name="open">
+ <setter>
+ <![CDATA[
+ var settings = document.getAnonymousElementByAttribute(this, "anonid", "settings");
+ var expander = document.getAnonymousElementByAttribute(this, "anonid", "disclosure");
+ settings.collapsed = !val;
+ expander.open = val;
+ if (val)
+ this.setAttribute("open", "true");
+ else
+ this.setAttribute("open", "false");
+ return val;
+ ]]>
+ </setter>
+ <getter>
+ return this.getAttribute("open");
+ </getter>
+ </property>
+ <method name="onCommand">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var element = aEvent.originalTarget;
+ var button = element.getAttribute("anonid");
+ switch (button) {
+ case "disclosure":
+ case "label":
+ if (this.open == "true")
+ this.open = false;
+ else
+ this.open = true;
+ break;
+ case "clear-button":
+ var event = document.createEvent("Events");
+ event.initEvent("clear", true, true);
+ this.dispatchEvent(event);
+ break;
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ <handlers>
+ <handler event="command"><![CDATA[
+ this.onCommand(event);
+ ]]></handler>
+ <handler event="click"><![CDATA[
+ if (event.originalTarget.localName == "label")
+ this.onCommand(event);
+ ]]></handler>
+ </handlers>
+ </binding>
+
+</bindings>
+
+
diff --git a/components/bindings/content/filefield.xml b/components/bindings/content/filefield.xml
new file mode 100644
index 000000000..f81761eb5
--- /dev/null
+++ b/components/bindings/content/filefield.xml
@@ -0,0 +1,96 @@
+<?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="filefieldBindings"
+ 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="filefield" extends="chrome://global/content/bindings/general.xml#basetext">
+ <resources>
+ <stylesheet src="chrome://global/skin/filefield.css"/>
+ </resources>
+ <content>
+ <xul:stringbundle anonid="bundle" src="chrome://global/locale/filefield.properties"/>
+ <xul:hbox class="fileFieldContentBox" align="center" flex="1" xbl:inherits="disabled">
+ <xul:image class="fileFieldIcon" xbl:inherits="src=image,disabled"/>
+ <xul:textbox class="fileFieldLabel" xbl:inherits="value=label,disabled,accesskey,tabindex,aria-labelledby" flex="1" readonly="true"/>
+ </xul:hbox>
+ </content>
+ <implementation implements="nsIDOMXULLabeledControlElement">
+ <property name="label" onget="return this.getAttribute('label');">
+ <setter>
+ this.setAttribute('label', val);
+ var elt = document.getAnonymousElementByAttribute(this, "class", "fileFieldLabel");
+ return (elt.value = val);
+ </setter>
+ </property>
+
+ <field name="_file">null</field>
+ <property name="file" onget="return this._file">
+ <setter>
+ <![CDATA[
+ this._file = val;
+ if (val) {
+ this.image = this._getIconURLForFile(val);
+ this.label = this._getDisplayNameForFile(val);
+ }
+ else {
+ this.removeAttribute("image");
+ var bundle = document.getAnonymousElementByAttribute(this, "anonid", "bundle");
+ this.label = bundle.getString("downloadHelperNoneSelected");
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <method name="_getDisplayNameForFile">
+ <parameter name="aFile"/>
+ <body>
+ <![CDATA[
+ if (/Win/.test(navigator.platform)) {
+ var lfw = aFile.QueryInterface(Components.interfaces.nsILocalFileWin);
+ try {
+ return lfw.getVersionInfoField("FileDescription");
+ }
+ catch (e) {
+ // fall through to the filename
+ }
+ } else if (/Mac/.test(navigator.platform)) {
+ var lfm = aFile.QueryInterface(Components.interfaces.nsILocalFileMac);
+ try {
+ return lfm.bundleDisplayName;
+ }
+ catch (e) {
+ // fall through to the file name
+ }
+ }
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var url = ios.newFileURI(aFile).QueryInterface(Components.interfaces.nsIURL);
+ return url.fileName;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getIconURLForFile">
+ <parameter name="aFile"/>
+ <body>
+ <![CDATA[
+ if (!aFile)
+ return "";
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var fph = ios.getProtocolHandler("file")
+ .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
+ var urlspec = fph.getURLSpecFromFile(aFile);
+ return "moz-icon://" + urlspec + "?size=16";
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/findbar.xml b/components/bindings/content/findbar.xml
new file mode 100644
index 000000000..aae799554
--- /dev/null
+++ b/components/bindings/content/findbar.xml
@@ -0,0 +1,1371 @@
+<?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 % findBarDTD SYSTEM "chrome://global/locale/findbar.dtd" >
+%findBarDTD;
+]>
+
+<bindings id="findbarBindings"
+ 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">
+
+ <!-- Private binding -->
+ <binding id="findbar-textbox"
+ extends="chrome://global/content/bindings/textbox.xml#textbox">
+ <implementation>
+
+ <field name="_findbar">null</field>
+ <property name="findbar" readonly="true">
+ <getter>
+ return this._findbar ?
+ this._findbar : this._findbar = document.getBindingParent(this);
+ </getter>
+ </property>
+
+ <method name="_handleEnter">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (this.findbar._findMode == this.findbar.FIND_NORMAL) {
+ let findString = this.findbar._findField;
+ if (!findString.value)
+ return;
+ if (aEvent.getModifierState("Accel")) {
+ this.findbar.getElement("highlight").click();
+ return;
+ }
+
+ this.findbar.onFindAgainCommand(aEvent.shiftKey);
+ } else {
+ this.findbar._finishFAYT(aEvent);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_handleTab">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ let shouldHandle = !aEvent.altKey && !aEvent.ctrlKey &&
+ !aEvent.metaKey;
+ if (shouldHandle &&
+ this.findbar._findMode != this.findbar.FIND_NORMAL) {
+
+ this.findbar._finishFAYT(aEvent);
+ }
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="input"><![CDATA[
+ // We should do nothing during composition. E.g., composing string
+ // before converting may matches a forward word of expected word.
+ // After that, even if user converts the composition string to the
+ // expected word, it may find second or later searching word in the
+ // document.
+ if (this.findbar._isIMEComposing) {
+ return;
+ }
+
+ if (this._hadValue && !this.value) {
+ this._willfullyDeleted = true;
+ this._hadValue = false;
+ } else if (this.value.trim()) {
+ this._hadValue = true;
+ this._willfullyDeleted = false;
+ }
+ this.findbar._find(this.value);
+ ]]></handler>
+
+ <handler event="keypress"><![CDATA[
+ let shouldHandle = !event.altKey && !event.ctrlKey &&
+ !event.metaKey && !event.shiftKey;
+
+ switch (event.keyCode) {
+ case KeyEvent.DOM_VK_RETURN:
+ this._handleEnter(event);
+ break;
+ case KeyEvent.DOM_VK_TAB:
+ this._handleTab(event);
+ break;
+ case KeyEvent.DOM_VK_PAGE_UP:
+ case KeyEvent.DOM_VK_PAGE_DOWN:
+ if (shouldHandle) {
+ this.findbar.browser.finder.keyPress(event);
+ event.preventDefault();
+ }
+ break;
+ case KeyEvent.DOM_VK_UP:
+ case KeyEvent.DOM_VK_DOWN:
+ this.findbar.browser.finder.keyPress(event);
+ event.preventDefault();
+ break;
+ }
+ ]]></handler>
+
+ <handler event="blur"><![CDATA[
+ let findbar = this.findbar;
+ // Note: This code used to remove the selection
+ // if it matched an editable.
+ findbar.browser.finder.enableSelection();
+ ]]></handler>
+
+ <handler event="focus"><![CDATA[
+ if (/Mac/.test(navigator.platform)) {
+ let findbar = this.findbar;
+ findbar._onFindFieldFocus();
+ }
+ ]]></handler>
+
+ <handler event="compositionstart"><![CDATA[
+ // Don't close the find toolbar while IME is composing.
+ let findbar = this.findbar;
+ findbar._isIMEComposing = true;
+ if (findbar._quickFindTimeout) {
+ clearTimeout(findbar._quickFindTimeout);
+ findbar._quickFindTimeout = null;
+ }
+ ]]></handler>
+
+ <handler event="compositionend"><![CDATA[
+ let findbar = this.findbar;
+ findbar._isIMEComposing = false;
+ if (findbar._findMode != findbar.FIND_NORMAL)
+ findbar._setFindCloseTimeout();
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ if (event.dataTransfer.types.includes("text/plain"))
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="drop"><![CDATA[
+ let value = event.dataTransfer.getData("text/plain");
+ this.value = value;
+ this.findbar._find(value);
+ event.stopPropagation();
+ event.preventDefault();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="findbar"
+ extends="chrome://global/content/bindings/toolbar.xml#toolbar">
+ <resources>
+ <stylesheet src="chrome://global/skin/findBar.css"/>
+ </resources>
+
+ <content hidden="true">
+ <xul:hbox anonid="findbar-container" class="findbar-container" flex="1" align="center">
+ <xul:hbox anonid="findbar-textbox-wrapper" align="stretch">
+ <xul:toolbarbutton anonid="find-closebutton"
+ class="findbar-closebutton close-icon"
+ tooltiptext="&findCloseButton.tooltip;"
+ oncommand="close();"/>
+ <xul:textbox anonid="findbar-textbox"
+ class="findbar-textbox findbar-find-fast"
+ xbl:inherits="flash"/>
+ <xul:toolbarbutton anonid="find-next"
+ class="findbar-find-next tabbable"
+ label="&next.label;"
+ accesskey="&next.accesskey;"
+ tooltiptext="&next.tooltip;"
+ oncommand="onFindAgainCommand(false);"
+ disabled="true"
+ xbl:inherits="accesskey=findnextaccesskey"/>
+ <xul:toolbarbutton anonid="find-previous"
+ class="findbar-find-previous tabbable"
+ label="&previous.label;"
+ accesskey="&previous.accesskey;"
+ tooltiptext="&previous.tooltip;"
+ oncommand="onFindAgainCommand(true);"
+ disabled="true"
+ xbl:inherits="accesskey=findpreviousaccesskey"/>
+ </xul:hbox>
+ <xul:toolbarbutton anonid="highlight"
+ class="findbar-highlight findbar-button tabbable"
+ label="&highlightAll.label;"
+ accesskey="&highlightAll.accesskey;"
+ tooltiptext="&highlightAll.tooltiptext;"
+ oncommand="toggleHighlight(this.checked);"
+ type="checkbox"
+ xbl:inherits="accesskey=highlightaccesskey"/>
+ <xul:checkbox anonid="find-case-sensitive"
+ class="findbar-case-sensitive findbar-button tabbable"
+ label="&caseSensitive.label;"
+ accesskey="&caseSensitive.accesskey;"
+ tooltiptext="&caseSensitive.tooltiptext;"
+ oncommand="_setCaseSensitivity(this.checked ? 1 : 0);"
+ type="checkbox"
+ xbl:inherits="accesskey=matchcaseaccesskey"/>
+ <xul:checkbox anonid="find-entire-word"
+ class="findbar-entire-word findbar-button tabbable"
+ label="&entireWord.label;"
+ accesskey="&entireWord.accesskey;"
+ tooltiptext="&entireWord.tooltiptext;"
+ oncommand="toggleEntireWord(this.checked);"
+ type="checkbox"
+ xbl:inherits="accesskey=entirewordaccesskey"/>
+ <xul:label anonid="match-case-status" class="findbar-find-fast"/>
+ <xul:label anonid="entire-word-status" class="findbar-find-fast"/>
+ <xul:label anonid="found-matches" class="findbar-find-fast found-matches" hidden="true"/>
+ <xul:image anonid="find-status-icon" class="findbar-find-fast find-status-icon"/>
+ <xul:description anonid="find-status"
+ control="findbar-textbox"
+ class="findbar-find-fast findbar-find-status">
+ <!-- Do not use value, first child is used because it provides a11y with text change events -->
+ </xul:description>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIMessageListener, nsIEditActionListener">
+ <!-- Please keep in sync with toolkit/content/browser-content.js -->
+ <field name="FIND_NORMAL">0</field>
+ <field name="FIND_TYPEAHEAD">1</field>
+ <field name="FIND_LINKS">2</field>
+
+ <field name="__findMode">0</field>
+ <property name="_findMode" onget="return this.__findMode;"
+ onset="this.__findMode = val; this._updateBrowserWithState(); return val;"/>
+
+ <field name="_flashFindBar">0</field>
+ <field name="_initialFlashFindBarCount">6</field>
+
+ <!--
+ - For tests that need to know when the find bar is finished
+ - initializing, we store a promise to notify on.
+ -->
+ <field name="_startFindDeferred">null</field>
+
+ <property name="prefillWithSelection"
+ onget="return this.getAttribute('prefillwithselection') != 'false'"
+ onset="this.setAttribute('prefillwithselection', val); return val;"/>
+
+ <method name="getElement">
+ <parameter name="aAnonymousID"/>
+ <body><![CDATA[
+ return document.getAnonymousElementByAttribute(this,
+ "anonid",
+ aAnonymousID)
+ ]]></body>
+ </method>
+
+ <property name="findMode"
+ readonly="true"
+ onget="return this._findMode;"/>
+
+ <property name="hasTransactions" readonly="true">
+ <getter><![CDATA[
+ if (this._findField.value)
+ return true;
+
+ // Watch out for lazy editor init
+ if (this._findField.editor) {
+ let tm = this._findField.editor.transactionManager;
+ return !!(tm.numberOfUndoItems || tm.numberOfRedoItems);
+ }
+ return false;
+ ]]></getter>
+ </property>
+
+ <field name="_browser">null</field>
+ <property name="browser">
+ <getter><![CDATA[
+ if (!this._browser) {
+ this._browser =
+ document.getElementById(this.getAttribute("browserid"));
+ }
+ return this._browser;
+ ]]></getter>
+ <setter><![CDATA[
+ let prefsvc = this._prefsvc;
+ if (this._browser) {
+ if (this._browser.messageManager) {
+ this._browser.messageManager.removeMessageListener("Findbar:Keypress", this);
+ this._browser.messageManager.removeMessageListener("Findbar:Mouseup", this);
+ }
+ let finder = this._browser.finder;
+ if (finder)
+ finder.removeResultListener(this);
+ }
+
+ this._browser = val;
+ if (this._browser) {
+ // Need to do this to ensure the correct initial state.
+ this._updateBrowserWithState();
+ this._browser.messageManager.addMessageListener("Findbar:Keypress", this);
+ this._browser.messageManager.addMessageListener("Findbar:Mouseup", this);
+ this._browser.finder.addResultListener(this);
+
+ if (prefsvc.getBoolPref("findbar.termPerTab") == true) {
+ this._findField.value = this._browser._lastSearchString;
+ }
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <field name="__prefsvc">null</field>
+ <property name="_prefsvc">
+ <getter><![CDATA[
+ if (!this.__prefsvc) {
+ this.__prefsvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ }
+ return this.__prefsvc;
+ ]]></getter>
+ </property>
+
+ <field name="_observer"><![CDATA[({
+ _self: this,
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIObserver) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ observe: function(aSubject, aTopic, aPrefName) {
+ if (aTopic != "nsPref:changed")
+ return;
+
+ let prefsvc = this._self._prefsvc;
+
+ switch (aPrefName) {
+ case "accessibility.typeaheadfind":
+ this._self._findAsYouType = prefsvc.getBoolPref(aPrefName);
+ break;
+ case "accessibility.typeaheadfind.linksonly":
+ this._self._typeAheadLinksOnly = prefsvc.getBoolPref(aPrefName);
+ break;
+ case "accessibility.typeaheadfind.casesensitive":
+ this._self._setCaseSensitivity(prefsvc.getIntPref(aPrefName));
+ break;
+ case "findbar.entireword":
+ this._self._entireWord = prefsvc.getBoolPref(aPrefName);
+ this._self.toggleEntireWord(this._self._entireWord, true);
+ break;
+ case "findbar.highlightAll":
+ this._self.toggleHighlight(prefsvc.getBoolPref(aPrefName), true);
+ break;
+ case "findbar.modalHighlight":
+ this._self._useModalHighlight = prefsvc.getBoolPref(aPrefName);
+ if (this._self.browser.finder)
+ this._self.browser.finder.onModalHighlightChange(this._self._useModalHighlight);
+ break;
+ }
+ }
+ })]]></field>
+
+ <field name="_destroyed">false</field>
+
+ <constructor><![CDATA[
+ // These elements are accessed frequently and are therefore cached
+ this._findField = this.getElement("findbar-textbox");
+ this._foundMatches = this.getElement("found-matches");
+ this._findStatusIcon = this.getElement("find-status-icon");
+ this._findStatusDesc = this.getElement("find-status");
+
+ this._foundURL = null;
+
+ let prefsvc = this._prefsvc;
+
+ this._quickFindTimeoutLength =
+ prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
+ this._flashFindBar =
+ prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
+ this._useModalHighlight = prefsvc.getBoolPref("findbar.modalHighlight");
+
+ prefsvc.addObserver("accessibility.typeaheadfind",
+ this._observer, false);
+ prefsvc.addObserver("accessibility.typeaheadfind.linksonly",
+ this._observer, false);
+ prefsvc.addObserver("accessibility.typeaheadfind.casesensitive",
+ this._observer, false);
+ prefsvc.addObserver("findbar.entireword", this._observer, false);
+ prefsvc.addObserver("findbar.highlightAll", this._observer, false);
+ prefsvc.addObserver("findbar.modalHighlight", this._observer, false);
+
+ this._findAsYouType =
+ prefsvc.getBoolPref("accessibility.typeaheadfind");
+ this._typeAheadLinksOnly =
+ prefsvc.getBoolPref("accessibility.typeaheadfind.linksonly");
+ this._typeAheadCaseSensitive =
+ prefsvc.getIntPref("accessibility.typeaheadfind.casesensitive");
+ this._entireWord = prefsvc.getBoolPref("findbar.entireword");
+ this._highlightAll = prefsvc.getBoolPref("findbar.highlightAll");
+
+ // Convenience
+ this.nsITypeAheadFind = Components.interfaces.nsITypeAheadFind;
+ this.nsISelectionController = Components.interfaces.nsISelectionController;
+ this._findSelection = this.nsISelectionController.SELECTION_FIND;
+
+ this._findResetTimeout = -1;
+
+ // Make sure the FAYT keypress listener is attached by initializing the
+ // browser property
+ if (this.getAttribute("browserid"))
+ setTimeout(function(aSelf) { aSelf.browser = aSelf.browser; }, 0, this);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this.destroy();
+ ]]></destructor>
+
+ <!-- This is necessary because the destructor isn't called when
+ we are removed from a document that is not destroyed. This
+ needs to be explicitly called in this case -->
+ <method name="destroy">
+ <body><![CDATA[
+ if (this._destroyed)
+ return;
+ this._destroyed = true;
+
+ if (this.browser.finder)
+ this.browser.finder.destroy();
+
+ this.browser = null;
+
+ let prefsvc = this._prefsvc;
+ prefsvc.removeObserver("accessibility.typeaheadfind",
+ this._observer);
+ prefsvc.removeObserver("accessibility.typeaheadfind.linksonly",
+ this._observer);
+ prefsvc.removeObserver("accessibility.typeaheadfind.casesensitive",
+ this._observer);
+ prefsvc.removeObserver("findbar.entireword", this._observer);
+ prefsvc.removeObserver("findbar.highlightAll", this._observer);
+ prefsvc.removeObserver("findbar.modalHighlight", this._observer);
+
+ // Clear all timers that might still be running.
+ this._cancelTimers();
+ ]]></body>
+ </method>
+
+ <method name="_cancelTimers">
+ <body><![CDATA[
+ if (this._flashFindBarTimeout) {
+ clearInterval(this._flashFindBarTimeout);
+ this._flashFindBarTimeout = null;
+ }
+ if (this._quickFindTimeout) {
+ clearTimeout(this._quickFindTimeout);
+ this._quickFindTimeout = null;
+ }
+ if (this._findResetTimeout) {
+ clearTimeout(this._findResetTimeout);
+ this._findResetTimeout = null;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_setFindCloseTimeout">
+ <body><![CDATA[
+ if (this._quickFindTimeout)
+ clearTimeout(this._quickFindTimeout);
+
+ // Don't close the find toolbar while IME is composing OR when the
+ // findbar is already hidden.
+ if (this._isIMEComposing || this.hidden) {
+ this._quickFindTimeout = null;
+ return;
+ }
+
+ this._quickFindTimeout = setTimeout(() => {
+ if (this._findMode != this.FIND_NORMAL)
+ this.close();
+ this._quickFindTimeout = null;
+ }, this._quickFindTimeoutLength);
+ ]]></body>
+ </method>
+
+ <field name="_pluralForm">null</field>
+ <property name="pluralForm">
+ <getter><![CDATA[
+ if (!this._pluralForm) {
+ this._pluralForm = Components.utils.import(
+ "resource://gre/modules/PluralForm.jsm", {}).PluralForm;
+ }
+ return this._pluralForm;
+ ]]></getter>
+ </property>
+
+ <!--
+ - Updates the search match count after each find operation on a new string.
+ - @param aRes
+ - the result of the find operation
+ -->
+ <method name="_updateMatchesCount">
+ <body><![CDATA[
+ if (!this._dispatchFindEvent("matchescount"))
+ return;
+
+ this.browser.finder.requestMatchesCount(this._findField.value,
+ this._findMode == this.FIND_LINKS);
+ ]]></body>
+ </method>
+
+ <!--
+ - Turns highlight on or off.
+ - @param aHighlight (boolean)
+ - Whether to turn the highlight on or off
+ - @param aFromPrefObserver (boolean)
+ - Whether the callee is the pref observer, which means we should
+ - not set the same pref again.
+ -->
+ <method name="toggleHighlight">
+ <parameter name="aHighlight"/>
+ <parameter name="aFromPrefObserver"/>
+ <body><![CDATA[
+ if (aHighlight === this._highlightAll) {
+ return;
+ }
+
+ this.browser.finder.onHighlightAllChange(aHighlight);
+
+ this._setHighlightAll(aHighlight, aFromPrefObserver);
+
+ if (!this._dispatchFindEvent("highlightallchange")) {
+ return;
+ }
+
+ let word = this._findField.value;
+ // Bug 429723. Don't attempt to highlight ""
+ if (aHighlight && !word)
+ return;
+
+ this.browser.finder.highlight(aHighlight, word,
+ this._findMode == this.FIND_LINKS);
+
+ // Update the matches count
+ this._updateMatchesCount(this.nsITypeAheadFind.FIND_FOUND);
+ ]]></body>
+ </method>
+
+ <!--
+ - Updates the highlight-all mode of the findbar and its UI.
+ - @param aHighlight (boolean)
+ - Whether to turn the highlight on or off.
+ - @param aFromPrefObserver (boolean)
+ - Whether the callee is the pref observer, which means we should
+ - not set the same pref again.
+ -->
+ <method name="_setHighlightAll">
+ <parameter name="aHighlight"/>
+ <parameter name="aFromPrefObserver"/>
+ <body><![CDATA[
+ if (typeof aHighlight != "boolean") {
+ aHighlight = this._highlightAll;
+ }
+ if (aHighlight !== this._highlightAll && !aFromPrefObserver) {
+ this._prefsvc.setBoolPref("findbar.highlightAll", aHighlight);
+ }
+ this._highlightAll = aHighlight;
+ let checkbox = this.getElement("highlight");
+ checkbox.checked = this._highlightAll;
+ ]]></body>
+ </method>
+
+ <method name="_maybeHighlightAll">
+ <body><![CDATA[
+ let word = this._findField.value;
+ // Bug 429723. Don't attempt to highlight ""
+ if (!this._highlightAll || !word)
+ return;
+
+ this.browser.finder.highlight(true, word,
+ this._findMode == this.FIND_LINKS);
+ ]]></body>
+ </method>
+
+ <!--
+ - Updates the case-sensitivity mode of the findbar and its UI.
+ - @param [optional] aString
+ - The string for which case sensitivity might be turned on.
+ - This only used when case-sensitivity is in auto mode,
+ - @see _shouldBeCaseSensitive. The default value for this
+ - parameter is the find-field value.
+ -->
+ <method name="_updateCaseSensitivity">
+ <parameter name="aString"/>
+ <body><![CDATA[
+ let val = aString || this._findField.value;
+
+ let caseSensitive = this._shouldBeCaseSensitive(val);
+ let checkbox = this.getElement("find-case-sensitive");
+ let statusLabel = this.getElement("match-case-status");
+ checkbox.checked = caseSensitive;
+
+ statusLabel.value = caseSensitive ? this._caseSensitiveStr : "";
+
+ // Show the checkbox on the full Find bar in non-auto mode.
+ // Show the label in all other cases.
+ let hideCheckbox = this._findMode != this.FIND_NORMAL ||
+ (this._typeAheadCaseSensitive != 0 &&
+ this._typeAheadCaseSensitive != 1);
+ checkbox.hidden = hideCheckbox;
+ statusLabel.hidden = !hideCheckbox;
+
+ this.browser.finder.caseSensitive = caseSensitive;
+ ]]></body>
+ </method>
+
+ <!--
+ - Sets the findbar case-sensitivity mode
+ - @param aCaseSensitivity (int)
+ - 0 - case insensitive
+ - 1 - case sensitive
+ - 2 - auto = case sensitive iff match string contains upper case letters
+ - @see _shouldBeCaseSensitive
+ -->
+ <method name="_setCaseSensitivity">
+ <parameter name="aCaseSensitivity"/>
+ <body><![CDATA[
+ this._typeAheadCaseSensitive = aCaseSensitivity;
+ this._updateCaseSensitivity();
+ this._findFailedString = null;
+ this._find();
+
+ this._dispatchFindEvent("casesensitivitychange");
+ ]]></body>
+ </method>
+
+ <!--
+ - Updates the entire-word mode of the findbar and its UI.
+ -->
+ <method name="_setEntireWord">
+ <body><![CDATA[
+ let entireWord = this._entireWord;
+ let checkbox = this.getElement("find-entire-word");
+ let statusLabel = this.getElement("entire-word-status");
+ checkbox.checked = entireWord;
+
+ statusLabel.value = entireWord ? this._entireWordStr : "";
+
+ // Show the checkbox on the full Find bar in non-auto mode.
+ // Show the label in all other cases.
+ let hideCheckbox = this._findMode != this.FIND_NORMAL;
+ checkbox.hidden = hideCheckbox;
+ statusLabel.hidden = !hideCheckbox;
+
+ this.browser.finder.entireWord = entireWord;
+ ]]></body>
+ </method>
+
+ <!--
+ - Sets the findbar entire-word mode
+ - @param aEntireWord (boolean)
+ - Whether or not entire-word mode should be turned on.
+ -->
+ <method name="toggleEntireWord">
+ <parameter name="aEntireWord"/>
+ <parameter name="aFromPrefObserver"/>
+ <body><![CDATA[
+ if (!aFromPrefObserver) {
+ // Just set the pref; our observer will change the find bar behavior.
+ this._prefsvc.setBoolPref("findbar.entireword", aEntireWord);
+ return;
+ }
+
+ this._findFailedString = null;
+ this._find();
+ ]]></body>
+ </method>
+
+ <field name="_strBundle">null</field>
+ <property name="strBundle">
+ <getter><![CDATA[
+ if (!this._strBundle) {
+ this._strBundle =
+ Components.classes["@mozilla.org/intl/stringbundle;1"]
+ .getService(Components.interfaces.nsIStringBundleService)
+ .createBundle("chrome://global/locale/findbar.properties");
+ }
+ return this._strBundle;
+ ]]></getter>
+ </property>
+
+ <!--
+ - Opens and displays the find bar.
+ -
+ - @param aMode
+ - the find mode to be used, which is either FIND_NORMAL,
+ - FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
+ - find mode if any or FIND_NORMAL.
+ - @returns true if the find bar wasn't previously open, false otherwise.
+ -->
+ <method name="open">
+ <parameter name="aMode"/>
+ <body><![CDATA[
+ if (aMode != undefined)
+ this._findMode = aMode;
+
+ if (!this._notFoundStr) {
+ var stringsBundle = this.strBundle;
+ this._notFoundStr = stringsBundle.GetStringFromName("NotFound");
+ this._wrappedToTopStr =
+ stringsBundle.GetStringFromName("WrappedToTop");
+ this._wrappedToBottomStr =
+ stringsBundle.GetStringFromName("WrappedToBottom");
+ this._normalFindStr =
+ stringsBundle.GetStringFromName("NormalFind");
+ this._fastFindStr =
+ stringsBundle.GetStringFromName("FastFind");
+ this._fastFindLinksStr =
+ stringsBundle.GetStringFromName("FastFindLinks");
+ this._caseSensitiveStr =
+ stringsBundle.GetStringFromName("CaseSensitive");
+ this._entireWordStr =
+ stringsBundle.GetStringFromName("EntireWord");
+ }
+
+ this._findFailedString = null;
+
+ this._updateFindUI();
+ if (this.hidden) {
+ this.removeAttribute("noanim");
+ this.hidden = false;
+
+ this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
+
+ let event = document.createEvent("Events");
+ event.initEvent("findbaropen", true, false);
+ this.dispatchEvent(event);
+
+ this.browser.finder.onFindbarOpen();
+
+ return true;
+ }
+ return false;
+ ]]></body>
+ </method>
+
+ <!--
+ - Closes the findbar.
+ -->
+ <method name="close">
+ <parameter name="aNoAnim"/>
+ <body><![CDATA[
+ if (this.hidden)
+ return;
+
+ if (aNoAnim)
+ this.setAttribute("noanim", true);
+ this.hidden = true;
+
+ // 'focusContent()' iterates over all listeners in the chrome
+ // process, so we need to call it from here.
+ this.browser.finder.focusContent();
+ this.browser.finder.onFindbarClose();
+
+ this._cancelTimers();
+
+ this._findFailedString = null;
+ ]]></body>
+ </method>
+
+ <method name="clear">
+ <body><![CDATA[
+ this.browser.finder.removeSelection();
+ this._findField.reset();
+ this.toggleHighlight(false);
+ this._updateStatusUI();
+ this._enableFindButtons(false);
+ ]]></body>
+ </method>
+
+ <method name="_dispatchKeypressEvent">
+ <parameter name="aTarget"/>
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (!aTarget)
+ return;
+
+ let event = document.createEvent("KeyboardEvent");
+ event.initKeyEvent(aEvent.type, aEvent.bubbles, aEvent.cancelable,
+ aEvent.view, aEvent.ctrlKey, aEvent.altKey,
+ aEvent.shiftKey, aEvent.metaKey, aEvent.keyCode,
+ aEvent.charCode);
+ aTarget.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <field name="_xulBrowserWindow">null</field>
+ <method name="_updateStatusUIBar">
+ <parameter name="aFoundURL"/>
+ <body><![CDATA[
+ if (!this._xulBrowserWindow) {
+ try {
+ this._xulBrowserWindow =
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
+ .treeOwner
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIXULWindow)
+ .XULBrowserWindow;
+ }
+ catch (ex) { }
+ if (!this._xulBrowserWindow)
+ return false;
+ }
+
+ // Call this has the same effect like hovering over link,
+ // the browser shows the URL as a tooltip.
+ this._xulBrowserWindow.setOverLink(aFoundURL || "", null);
+ return true;
+ ]]></body>
+ </method>
+
+ <method name="_finishFAYT">
+ <parameter name="aKeypressEvent"/>
+ <body><![CDATA[
+ this.browser.finder.focusContent();
+
+ if (aKeypressEvent)
+ aKeypressEvent.preventDefault();
+
+ this.browser.finder.keyPress(aKeypressEvent);
+
+ this.close();
+ return true;
+ ]]></body>
+ </method>
+
+ <method name="_shouldBeCaseSensitive">
+ <parameter name="aString"/>
+ <body><![CDATA[
+ if (this._typeAheadCaseSensitive == 0)
+ return false;
+ if (this._typeAheadCaseSensitive == 1)
+ return true;
+
+ return aString != aString.toLowerCase();
+ ]]></body>
+ </method>
+
+ <!-- We get a fake event object through an IPC message which contains the
+ data we need to make a decision. We then return |true| if and only if
+ the page gets to deal with the event itself. Everywhere we return
+ false, the message sender will take care of calling event.preventDefault
+ on the real event. -->
+ <method name="_onBrowserKeypress">
+ <parameter name="aFakeEvent"/>
+ <parameter name="aShouldFastFind"/>
+ <body><![CDATA[
+ const FAYT_LINKS_KEY = "'";
+ const FAYT_TEXT_KEY = "/";
+
+ // Fast keypresses can stack up when the content process is slow or
+ // hangs when in e10s mode. We make sure the findbar isn't 'opened'
+ // several times in a row, because then the find query is selected
+ // each time, losing characters typed initially.
+ let inputField = this._findField.inputField;
+ if (!this.hidden && document.activeElement == inputField) {
+ this._dispatchKeypressEvent(inputField, aFakeEvent);
+ return false;
+ }
+
+ if (this._findMode != this.FIND_NORMAL && this._quickFindTimeout) {
+ if (!aFakeEvent.charCode)
+ return true;
+
+ this._findField.select();
+ this._findField.focus();
+ this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
+ return false;
+ }
+
+ if (!aShouldFastFind)
+ return true;
+
+ let key = aFakeEvent.charCode ? String.fromCharCode(aFakeEvent.charCode) : null;
+ let manualstartFAYT = (key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY);
+ let autostartFAYT = !manualstartFAYT && this._findAsYouType &&
+ key && key != " ";
+ if (manualstartFAYT || autostartFAYT) {
+ let mode = (key == FAYT_LINKS_KEY ||
+ (autostartFAYT && this._typeAheadLinksOnly)) ?
+ this.FIND_LINKS : this.FIND_TYPEAHEAD;
+
+ // Clear bar first, so that when openFindBar() calls setCaseSensitivity()
+ // it doesn't get confused by a lingering value
+ this._findField.value = "";
+
+ this.open(mode);
+ this._setFindCloseTimeout();
+ this._findField.select();
+ this._findField.focus();
+
+ if (autostartFAYT)
+ this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
+ else
+ this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
+
+ return false;
+ }
+ return undefined;
+ ]]></body>
+ </method>
+
+ <!-- See nsIMessageListener -->
+ <method name="receiveMessage">
+ <parameter name="aMessage"/>
+ <body><![CDATA[
+ if (aMessage.target != this._browser) {
+ return undefined;
+ }
+ switch (aMessage.name) {
+ case "Findbar:Mouseup":
+ if (!this.hidden && this._findMode != this.FIND_NORMAL)
+ this.close();
+ break;
+
+ case "Findbar:Keypress":
+ return this._onBrowserKeypress(aMessage.data.fakeEvent,
+ aMessage.data.shouldFastFind);
+ }
+ return undefined;
+ ]]></body>
+ </method>
+
+ <method name="_updateBrowserWithState">
+ <body><![CDATA[
+ if (this._browser && this._browser.messageManager) {
+ this._browser.messageManager.sendAsyncMessage("Findbar:UpdateState", {
+ findMode: this._findMode
+ });
+ }
+ ]]></body>
+ </method>
+
+ <method name="_enableFindButtons">
+ <parameter name="aEnable"/>
+ <body><![CDATA[
+ this.getElement("find-next").disabled =
+ this.getElement("find-previous").disabled = !aEnable;
+ ]]></body>
+ </method>
+
+ <!--
+ - Determines whether minimalist or general-purpose search UI is to be
+ - displayed when the find bar is activated.
+ -->
+ <method name="_updateFindUI">
+ <body><![CDATA[
+ let showMinimalUI = this._findMode != this.FIND_NORMAL;
+
+ let nodes = this.getElement("findbar-container").childNodes;
+ let wrapper = this.getElement("findbar-textbox-wrapper");
+ let foundMatches = this._foundMatches;
+ for (let node of nodes) {
+ if (node == wrapper || node == foundMatches)
+ continue;
+ node.hidden = showMinimalUI;
+ }
+ this.getElement("find-next").hidden =
+ this.getElement("find-previous").hidden = showMinimalUI;
+ foundMatches.hidden = showMinimalUI || !foundMatches.value;
+ this._updateCaseSensitivity();
+ this._setEntireWord();
+ this._setHighlightAll();
+
+ if (showMinimalUI)
+ this._findField.classList.add("minimal");
+ else
+ this._findField.classList.remove("minimal");
+
+ if (this._findMode == this.FIND_TYPEAHEAD)
+ this._findField.placeholder = this._fastFindStr;
+ else if (this._findMode == this.FIND_LINKS)
+ this._findField.placeholder = this._fastFindLinksStr;
+ else
+ this._findField.placeholder = this._normalFindStr;
+ ]]></body>
+ </method>
+
+ <method name="_find">
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ if (!this._dispatchFindEvent(""))
+ return;
+
+ let val = aValue || this._findField.value;
+
+ // We have to carry around an explicit version of this,
+ // because finder.searchString doesn't update on failed
+ // searches.
+ this.browser._lastSearchString = val;
+
+ // Only search on input if we don't have a last-failed string,
+ // or if the current search string doesn't start with it.
+ // In entire-word mode we always attemp a find; since sequential matching
+ // is not guaranteed, the first character typed may not be a word (no
+ // match), but the with the second character it may well be a word,
+ // thus a match.
+ if (!this._findFailedString ||
+ !val.startsWith(this._findFailedString) ||
+ this._entireWord) {
+ // Getting here means the user commanded a find op. Make sure any
+ // initial prefilling is ignored if it hasn't happened yet.
+ if (this._startFindDeferred) {
+ this._startFindDeferred.resolve();
+ this._startFindDeferred = null;
+ }
+
+ this._enableFindButtons(val);
+ this._updateCaseSensitivity(val);
+ this._setEntireWord();
+
+ this.browser.finder.fastFind(val, this._findMode == this.FIND_LINKS,
+ this._findMode != this.FIND_NORMAL);
+ }
+
+ if (this._findMode != this.FIND_NORMAL)
+ this._setFindCloseTimeout();
+
+ if (this._findResetTimeout != -1)
+ clearTimeout(this._findResetTimeout);
+
+ // allow a search to happen on input again after a second has
+ // expired since the previous input, to allow for dynamic
+ // content and/or page loading
+ this._findResetTimeout = setTimeout(() => {
+ this._findFailedString = null;
+ this._findResetTimeout = -1;
+ }, 1000);
+ ]]></body>
+ </method>
+
+ <method name="_flash">
+ <body><![CDATA[
+ if (this._flashFindBarCount === undefined)
+ this._flashFindBarCount = this._initialFlashFindBarCount;
+
+ if (this._flashFindBarCount-- == 0) {
+ clearInterval(this._flashFindBarTimeout);
+ this.removeAttribute("flash");
+ this._flashFindBarCount = 6;
+ return;
+ }
+
+ this.setAttribute("flash",
+ (this._flashFindBarCount % 2 == 0) ?
+ "false" : "true");
+ ]]></body>
+ </method>
+
+ <method name="_findAgain">
+ <parameter name="aFindPrevious"/>
+ <body><![CDATA[
+ this.browser.finder.findAgain(aFindPrevious,
+ this._findMode == this.FIND_LINKS,
+ this._findMode != this.FIND_NORMAL);
+ ]]></body>
+ </method>
+
+ <method name="_updateStatusUI">
+ <parameter name="res"/>
+ <parameter name="aFindPrevious"/>
+ <body><![CDATA[
+ switch (res) {
+ case this.nsITypeAheadFind.FIND_WRAPPED:
+ this._findStatusIcon.setAttribute("status", "wrapped");
+ this._findStatusDesc.textContent =
+ aFindPrevious ? this._wrappedToBottomStr : this._wrappedToTopStr;
+ this._findField.removeAttribute("status");
+ break;
+ case this.nsITypeAheadFind.FIND_NOTFOUND:
+ this._findStatusIcon.setAttribute("status", "notfound");
+ this._findStatusDesc.textContent = this._notFoundStr;
+ this._findField.setAttribute("status", "notfound");
+ break;
+ case this.nsITypeAheadFind.FIND_PENDING:
+ this._findStatusIcon.setAttribute("status", "pending");
+ this._findStatusDesc.textContent = "";
+ this._findField.removeAttribute("status");
+ break;
+ case this.nsITypeAheadFind.FIND_FOUND:
+ default:
+ this._findStatusIcon.removeAttribute("status");
+ this._findStatusDesc.textContent = "";
+ this._findField.removeAttribute("status");
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="updateControlState">
+ <parameter name="aResult"/>
+ <parameter name="aFindPrevious"/>
+ <body><![CDATA[
+ this._updateStatusUI(aResult, aFindPrevious);
+ this._enableFindButtons(aResult !== this.nsITypeAheadFind.FIND_NOTFOUND);
+ ]]></body>
+ </method>
+
+ <method name="_dispatchFindEvent">
+ <parameter name="aType"/>
+ <parameter name="aFindPrevious"/>
+ <body><![CDATA[
+ let event = document.createEvent("CustomEvent");
+ event.initCustomEvent("find" + aType, true, true, {
+ query: this._findField.value,
+ caseSensitive: !!this._typeAheadCaseSensitive,
+ entireWord: this._entireWord,
+ highlightAll: this._highlightAll,
+ findPrevious: aFindPrevious
+ });
+ return this.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+
+ <!--
+ - Opens the findbar, focuses the findfield and selects its contents.
+ - Also flashes the findbar the first time it's used.
+ - @param aMode
+ - the find mode to be used, which is either FIND_NORMAL,
+ - FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
+ - find mode if any or FIND_NORMAL.
+ -->
+ <method name="startFind">
+ <parameter name="aMode"/>
+ <body><![CDATA[
+ let prefsvc = this._prefsvc;
+ let userWantsPrefill = true;
+ this.open(aMode);
+
+ if (this._flashFindBar) {
+ this._flashFindBarTimeout = setInterval(() => this._flash(), 500);
+ prefsvc.setIntPref("accessibility.typeaheadfind.flashBar",
+ --this._flashFindBar);
+ }
+
+ let {PromiseUtils} =
+ Components.utils.import("resource://gre/modules/PromiseUtils.jsm", {});
+ this._startFindDeferred = PromiseUtils.defer();
+ let startFindPromise = this._startFindDeferred.promise;
+
+ if (this.prefillWithSelection)
+ userWantsPrefill =
+ prefsvc.getBoolPref("accessibility.typeaheadfind.prefillwithselection");
+
+ if (this.prefillWithSelection && userWantsPrefill) {
+ // NB: We have to focus this._findField here so tests that send
+ // key events can open and close the find bar synchronously.
+ this._findField.focus();
+
+ // (e10s) since we focus lets also select it, otherwise that would
+ // only happen in this.onCurrentSelection and, because it is async,
+ // there's a chance keypresses could come inbetween, leading to
+ // jumbled up queries.
+ this._findField.select();
+
+ this.browser.finder.getInitialSelection();
+ return startFindPromise;
+ }
+
+ // If userWantsPrefill is false but prefillWithSelection is true,
+ // then we might need to check the selection clipboard. Call
+ // onCurrentSelection to do so.
+ // Note: this.onCurrentSelection clears this._startFindDeferred.
+ this.onCurrentSelection("", true);
+ return startFindPromise;
+ ]]></body>
+ </method>
+
+ <!--
+ - Convenient alias to startFind(gFindBar.FIND_NORMAL);
+ -
+ - You should generally map the window's find command to this method.
+ - e.g. <command name="cmd_find" oncommand="gFindBar.onFindCommand();"/>
+ -->
+ <method name="onFindCommand">
+ <body><![CDATA[
+ return this.startFind(this.FIND_NORMAL);
+ ]]></body>
+ </method>
+
+ <!--
+ - Stub for find-next and find-previous commands
+ - @param aFindPrevious
+ - true for find-previous, false otherwise.
+ -->
+ <method name="onFindAgainCommand">
+ <parameter name="aFindPrevious"/>
+ <body><![CDATA[
+ let findString = this._browser.finder.searchString || this._findField.value;
+ if (!findString)
+ return this.startFind();
+
+ // We dispatch the findAgain event here instead of in _findAgain since
+ // if there is a find event handler that prevents the default then
+ // finder.searchString will never get updated which in turn means
+ // there would never be findAgain events because of the logic below.
+ if (!this._dispatchFindEvent("again", aFindPrevious))
+ return undefined;
+
+ // user explicitly requested another search, so do it even if we think it'll fail
+ this._findFailedString = null;
+
+ // Ensure the stored SearchString is in sync with what we want to find
+ if (this._findField.value != this._browser.finder.searchString) {
+ this._find(this._findField.value);
+ } else {
+ this._findAgain(aFindPrevious);
+ if (this._useModalHighlight) {
+ this.open();
+ this._findField.select();
+ this._findField.focus();
+ }
+ }
+
+ return undefined;
+ ]]></body>
+ </method>
+
+ <!--
+ - This handles all the result changes for both
+ - type-ahead-find and highlighting.
+ - @param aResult
+ - One of the nsITypeAheadFind.FIND_* constants
+ - indicating the result of a search operation.
+ - @param aFindBackwards
+ - If the search was done from the bottom to
+ - the top. This is used for right error messages
+ - when reaching "the end of the page".
+ - @param aLinkURL
+ - When a link matched then its URK. Always null
+ - when not in FIND_LINKS mode.
+ -->
+ <method name="onFindResult">
+ <parameter name="aData"/>
+ <body><![CDATA[
+ if (aData.result == this.nsITypeAheadFind.FIND_NOTFOUND) {
+ // If an explicit Find Again command fails, re-open the toolbar.
+ if (aData.storeResult && this.open()) {
+ this._findField.select();
+ this._findField.focus();
+ }
+ this._findFailedString = aData.searchString;
+ } else {
+ this._findFailedString = null;
+ }
+
+ this._updateStatusUI(aData.result, aData.findBackwards);
+ this._updateStatusUIBar(aData.linkURL);
+
+ if (this._findMode != this.FIND_NORMAL)
+ this._setFindCloseTimeout();
+ ]]></body>
+ </method>
+
+ <!--
+ - This handles all the result changes for matches counts.
+ - @param aResult
+ - Result Object, containing the total amount of matches and a vector
+ - of the current result.
+ -->
+ <method name="onMatchesCountResult">
+ <parameter name="aResult"/>
+ <body><![CDATA[
+ if (aResult.total !== 0) {
+ if (aResult.total == -1) {
+ this._foundMatches.value = this.pluralForm.get(
+ aResult.limit,
+ this.strBundle.GetStringFromName("FoundMatchesCountLimit")
+ ).replace("#1", aResult.limit);
+ } else {
+ this._foundMatches.value = this.pluralForm.get(
+ aResult.total,
+ this.strBundle.GetStringFromName("FoundMatches")
+ ).replace("#1", aResult.current)
+ .replace("#2", aResult.total);
+ }
+ this._foundMatches.hidden = false;
+ } else {
+ this._foundMatches.hidden = true;
+ this._foundMatches.value = "";
+ }
+ ]]></body>
+ </method>
+
+ <method name="onHighlightFinished">
+ <parameter name="result"/>
+ <body><![CDATA[
+ // Noop.
+ ]]></body>
+ </method>
+
+ <method name="onCurrentSelection">
+ <parameter name="aSelectionString" />
+ <parameter name="aIsInitialSelection" />
+ <body><![CDATA[
+ // Ignore the prefill if the user has already typed in the findbar,
+ // it would have been overwritten anyway. See bug 1198465.
+ if (aIsInitialSelection && !this._startFindDeferred)
+ return;
+
+ if (/Mac/.test(navigator.platform) && aIsInitialSelection && !aSelectionString) {
+ let clipboardSearchString = this.browser.finder.clipboardSearchString;
+ if (clipboardSearchString)
+ aSelectionString = clipboardSearchString;
+ }
+
+ if (aSelectionString)
+ this._findField.value = aSelectionString;
+
+ if (aIsInitialSelection) {
+ this._enableFindButtons(!!this._findField.value);
+ this._findField.select();
+ this._findField.focus();
+
+ this._startFindDeferred.resolve();
+ this._startFindDeferred = null;
+ }
+ ]]></body>
+ </method>
+
+ <!--
+ - This handler may cancel a request to focus content by returning |false|
+ - explicitly.
+ -->
+ <method name="shouldFocusContent">
+ <body><![CDATA[
+ const fm = Components.classes["@mozilla.org/focus-manager;1"]
+ .getService(Components.interfaces.nsIFocusManager);
+ if (fm.focusedWindow != window)
+ return false;
+
+ let focusedElement = fm.focusedElement;
+ if (!focusedElement)
+ return false;
+
+ let bindingParent = document.getBindingParent(focusedElement);
+ if (bindingParent != this && bindingParent != this._findField)
+ return false;
+
+ return true;
+ ]]></body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <!--
+ - We have to guard against `this.close` being |null| due to an unknown
+ - issue, which is tracked in bug 957999.
+ -->
+ <handler event="keypress" keycode="VK_ESCAPE" phase="capturing"
+ action="if (this.close) this.close();" preventdefault="true"/>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/general.xml b/components/bindings/content/general.xml
new file mode 100644
index 000000000..b4538e41d
--- /dev/null
+++ b/components/bindings/content/general.xml
@@ -0,0 +1,231 @@
+<?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="generalBindings"
+ 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="basecontrol">
+ <implementation implements="nsIDOMXULControlElement">
+ <!-- public implementation -->
+ <property name="disabled" onset="if (val) this.setAttribute('disabled', 'true');
+ else this.removeAttribute('disabled');
+ return val;"
+ onget="return this.getAttribute('disabled') == 'true';"/>
+ <property name="tabIndex" onget="return parseInt(this.getAttribute('tabindex')) || 0"
+ onset="if (val) this.setAttribute('tabindex', val);
+ else this.removeAttribute('tabindex'); return val;"/>
+ </implementation>
+ </binding>
+
+ <binding id="basetext" extends="chrome://global/content/bindings/general.xml#basecontrol">
+ <implementation implements="nsIDOMXULLabeledControlElement">
+ <!-- public implementation -->
+ <property name="label" onset="this.setAttribute('label',val); return val;"
+ onget="return this.getAttribute('label');"/>
+ <property name="crop" onset="this.setAttribute('crop',val); return val;"
+ onget="return this.getAttribute('crop');"/>
+ <property name="image" onset="this.setAttribute('image',val); return val;"
+ onget="return this.getAttribute('image');"/>
+ <property name="command" onset="this.setAttribute('command',val); return val;"
+ onget="return this.getAttribute('command');"/>
+ <property name="accessKey">
+ <getter>
+ <![CDATA[
+ return this.labelElement ? this.labelElement.accessKey : this.getAttribute('accesskey');
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ // Always store on the control
+ this.setAttribute('accesskey', val);
+ // If there is a label, change the accesskey on the labelElement
+ // if it's also set there
+ if (this.labelElement) {
+ this.labelElement.accessKey = val;
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <field name="labelElement"/>
+ </implementation>
+ </binding>
+
+ <binding id="control-item" extends="chrome://global/content/bindings/general.xml#basetext">
+ <implementation>
+ <property name="value" onset="this.setAttribute('value', val); return val;"
+ onget="return this.getAttribute('value');"/>
+ </implementation>
+ </binding>
+
+ <binding id="root-element">
+ <implementation>
+ <field name="_lightweightTheme">null</field>
+ <constructor><![CDATA[
+ if (this.hasAttribute("lightweightthemes")) {
+ let temp = {};
+ Components.utils.import("resource://gre/modules/LightweightThemeConsumer.jsm", temp);
+ this._lightweightTheme = new temp.LightweightThemeConsumer(this.ownerDocument);
+ }
+ ]]></constructor>
+ <destructor><![CDATA[
+ if (this._lightweightTheme) {
+ this._lightweightTheme.destroy();
+ this._lightweightTheme = null;
+ }
+ ]]></destructor>
+ </implementation>
+ </binding>
+
+ <binding id="iframe" role="outerdoc">
+ <implementation>
+ <property name="docShell" readonly="true">
+ <getter><![CDATA[
+ let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ return frameLoader ? frameLoader.docShell : null;
+ ]]></getter>
+ </property>
+ <property name="contentWindow"
+ readonly="true"
+ onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);"/>
+ <property name="webNavigation"
+ onget="return this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"
+ readonly="true"/>
+ <property name="contentDocument" readonly="true"
+ onget="return this.webNavigation.document;"/>
+ </implementation>
+ </binding>
+
+ <binding id="statusbarpanel" display="xul:button">
+ <content>
+ <children>
+ <xul:label class="statusbarpanel-text" xbl:inherits="value=label,crop" crop="right" flex="1"/>
+ </children>
+ </content>
+
+ <implementation>
+ <property name="label"
+ onget="return this.getAttribute('label');"
+ onset="this.setAttribute('label',val); return val;"/>
+ <property name="image"
+ onget="return this.getAttribute('image');"
+ onset="this.setAttribute('image',val); return val;"/>
+ <property name="src"
+ onget="return this.getAttribute('src');"
+ onset="this.setAttribute('src',val); return val;"/>
+ </implementation>
+ </binding>
+
+ <binding id="statusbarpanel-menu-iconic" display="xul:menu"
+ extends="chrome://global/content/bindings/general.xml#statusbarpanel">
+ <content>
+ <xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
+ <children/>
+ </content>
+ </binding>
+
+ <binding id="statusbar" role="xul:statusbar">
+ <content>
+ <children/>
+ <xul:statusbarpanel class="statusbar-resizerpanel">
+ <xul:resizer dir="bottomend"/>
+ </xul:statusbarpanel>
+ </content>
+ </binding>
+
+ <binding id="statusbarpanel-iconic" display="xul:button" role="xul:button"
+ extends="chrome://global/content/bindings/general.xml#statusbarpanel">
+ <content>
+ <xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
+ </content>
+ </binding>
+
+ <binding id="statusbarpanel-iconic-text" display="xul:button" role="xul:button"
+ extends="chrome://global/content/bindings/general.xml#statusbarpanel">
+ <content>
+ <xul:image class="statusbarpanel-icon" xbl:inherits="src,src=image"/>
+ <xul:label class="statusbarpanel-text" xbl:inherits="value=label,crop"/>
+ </content>
+ </binding>
+
+ <binding id="image" role="xul:image">
+ <implementation implements="nsIDOMXULImageElement">
+ <property name="src"
+ onget="return this.getAttribute('src');"
+ onset="this.setAttribute('src',val); return val;"/>
+ </implementation>
+ </binding>
+
+ <binding id="deck">
+ <implementation>
+ <property name="selectedIndex"
+ onget="return this.getAttribute('selectedIndex') || '0'">
+ <setter>
+ <![CDATA[
+ if (this.selectedIndex == val)
+ return val;
+ this.setAttribute("selectedIndex", val);
+ var event = document.createEvent('Events');
+ event.initEvent('select', true, true);
+ this.dispatchEvent(event);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedPanel">
+ <getter>
+ <![CDATA[
+ return this.childNodes[this.selectedIndex];
+ ]]>
+ </getter>
+
+ <setter>
+ <![CDATA[
+ var selectedIndex = -1;
+ for (var panel = val; panel != null; panel = panel.previousSibling)
+ ++selectedIndex;
+ this.selectedIndex = selectedIndex;
+ return val;
+ ]]>
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="dropmarker" extends="xul:button" role="xul:dropmarker">
+ <resources>
+ <stylesheet src="chrome://global/skin/dropmarker.css"/>
+ </resources>
+
+ <content>
+ <xul:image class="dropmarker-icon"/>
+ </content>
+ </binding>
+
+ <binding id="windowdragbox">
+ <implementation>
+ <field name="_dragBindingAlive">true</field>
+ <constructor>
+ if (!this._draggableStarted) {
+ this._draggableStarted = true;
+ try {
+ let tmp = {};
+ Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
+ let draghandle = new tmp.WindowDraggingElement(this);
+ draghandle.mouseDownCheck = function () {
+ return this._dragBindingAlive;
+ };
+ } catch (e) {}
+ }
+ </constructor>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/groupbox.xml b/components/bindings/content/groupbox.xml
new file mode 100644
index 000000000..7cd3276b4
--- /dev/null
+++ b/components/bindings/content/groupbox.xml
@@ -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/. -->
+
+
+<bindings id="groupboxBindings"
+ 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="groupbox-base">
+ <resources>
+ <stylesheet src="chrome://global/skin/groupbox.css"/>
+ </resources>
+ </binding>
+
+ <binding id="groupbox" role="xul:groupbox"
+ extends="chrome://global/content/bindings/groupbox.xml#groupbox-base">
+ <content>
+ <xul:hbox class="groupbox-title" align="center" pack="start">
+ <children includes="caption"/>
+ </xul:hbox>
+ <xul:box flex="1" class="groupbox-body" xbl:inherits="orient,align,pack">
+ <children/>
+ </xul:box>
+ </content>
+ </binding>
+
+ <binding id="caption" extends="chrome://global/content/bindings/general.xml#basetext">
+ <resources>
+ <stylesheet src="chrome://global/skin/groupbox.css"/>
+ </resources>
+
+ <content>
+ <children>
+ <xul:image class="caption-icon" xbl:inherits="src=image"/>
+ <xul:label class="caption-text" flex="1"
+ xbl:inherits="default,value=label,crop,accesskey"/>
+ </children>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/listbox.xml b/components/bindings/content/listbox.xml
new file mode 100644
index 000000000..9fae61669
--- /dev/null
+++ b/components/bindings/content/listbox.xml
@@ -0,0 +1,1144 @@
+<?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="listboxBindings"
+ 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">
+
+ <!--
+ Interface binding that is base for bindings of xul:listbox and
+ xul:richlistbox elements. This binding assumes that successors bindings
+ will implement the following properties and methods:
+
+ /** Return the number of items */
+ readonly itemCount
+
+ /** Return index of given item
+ * @param aItem - given item element */
+ getIndexOfItem(aItem)
+
+ /** Return item at given index
+ * @param aIndex - index of item element */
+ getItemAtIndex(aIndex)
+
+ /** Return count of item elements */
+ getRowCount()
+
+ /** Return count of visible item elements */
+ getNumberOfVisibleRows()
+
+ /** Return index of first visible item element */
+ getIndexOfFirstVisibleRow()
+
+ /** Return true if item of given index is visible
+ * @param aIndex - index of item element
+ *
+ * @note XXX: this method should be removed after bug 364612 is fixed
+ */
+ ensureIndexIsVisible(aIndex)
+
+ /** Return true if item element is visible
+ * @param aElement - given item element */
+ ensureElementIsVisible(aElement)
+
+ /** Scroll list control to make visible item of given index
+ * @param aIndex - index of item element
+ *
+ * @note XXX: this method should be removed after bug 364612 is fixed
+ */
+ scrollToIndex(aIndex)
+
+ /** Create item element and append it to the end of listbox
+ * @param aLabel - label of new item element
+ * @param aValue - value of new item element */
+ appendItem(aLabel, aValue)
+
+ /** Create item element and insert it to given position
+ * @param aIndex - insertion position
+ * @param aLabel - label of new item element
+ * @param aValue - value of new item element */
+ insertItemAt(aIndex, aLabel, aValue)
+
+ /** Scroll up/down one page
+ * @param aDirection - specifies scrolling direction, should be either -1 or 1
+ * @return the number of elements the selection scrolled
+ */
+ scrollOnePage(aDirection)
+
+ /** Fire "select" event */
+ _fireOnSelect()
+ -->
+ <binding id="listbox-base" role="xul:listbox"
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
+
+ <implementation implements="nsIDOMXULMultiSelectControlElement">
+ <field name="_lastKeyTime">0</field>
+ <field name="_incrementalString">""</field>
+
+ <!-- nsIDOMXULSelectControlElement -->
+ <property name="selectedItem"
+ onset="this.selectItem(val);">
+ <getter>
+ <![CDATA[
+ return this.selectedItems.length > 0 ? this.selectedItems[0] : null;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="selectedIndex">
+ <getter>
+ <![CDATA[
+ if (this.selectedItems.length > 0)
+ return this.getIndexOfItem(this.selectedItems[0]);
+ return -1;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val >= 0) {
+ this.selectItem(this.getItemAtIndex(val));
+ } else {
+ this.clearSelection();
+ this.currentItem = null;
+ }
+ ]]>
+ </setter>
+ </property>
+
+ <property name="value">
+ <getter>
+ <![CDATA[
+ if (this.selectedItems.length > 0)
+ return this.selectedItem.value;
+ return null;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ var kids = this.getElementsByAttribute("value", val);
+ if (kids && kids.item(0))
+ this.selectItem(kids[0]);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="removeItemAt">
+ <parameter name="index"/>
+ <body>
+ <![CDATA[
+ var remove = this.getItemAtIndex(index);
+ if (remove)
+ this.removeChild(remove);
+ return remove;
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIDOMXULMultiSelectControlElement -->
+ <property name="selType"
+ onget="return this.getAttribute('seltype');"
+ onset="this.setAttribute('seltype', val); return val;"/>
+
+ <property name="currentItem" onget="return this._currentItem;">
+ <setter>
+ if (this._currentItem == val)
+ return val;
+
+ if (this._currentItem)
+ this._currentItem.current = false;
+ this._currentItem = val;
+
+ if (val)
+ val.current = true;
+
+ return val;
+ </setter>
+ </property>
+
+ <property name="currentIndex">
+ <getter>
+ return this.currentItem ? this.getIndexOfItem(this.currentItem) : -1;
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val >= 0)
+ this.currentItem = this.getItemAtIndex(val);
+ else
+ this.currentItem = null;
+ ]]>
+ </setter>
+ </property>
+
+ <field name="selectedItems">new ChromeNodeList()</field>
+
+ <method name="addItemToSelection">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (this.selType != "multiple" && this.selectedCount)
+ return;
+
+ if (aItem.selected)
+ return;
+
+ this.selectedItems.append(aItem);
+ aItem.selected = true;
+
+ this._fireOnSelect();
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeItemFromSelection">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (!aItem.selected)
+ return;
+
+ this.selectedItems.remove(aItem);
+ aItem.selected = false;
+ this._fireOnSelect();
+ ]]>
+ </body>
+ </method>
+
+ <method name="toggleItemSelection">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (aItem.selected)
+ this.removeItemFromSelection(aItem);
+ else
+ this.addItemToSelection(aItem);
+ ]]>
+ </body>
+ </method>
+
+ <method name="selectItem">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (!aItem)
+ return;
+
+ if (this.selectedItems.length == 1 && this.selectedItems[0] == aItem)
+ return;
+
+ this._selectionStart = null;
+
+ var suppress = this._suppressOnSelect;
+ this._suppressOnSelect = true;
+
+ this.clearSelection();
+ this.addItemToSelection(aItem);
+ this.currentItem = aItem;
+
+ this._suppressOnSelect = suppress;
+ this._fireOnSelect();
+ ]]>
+ </body>
+ </method>
+
+ <method name="selectItemRange">
+ <parameter name="aStartItem"/>
+ <parameter name="aEndItem"/>
+ <body>
+ <![CDATA[
+ if (this.selType != "multiple")
+ return;
+
+ if (!aStartItem)
+ aStartItem = this._selectionStart ?
+ this._selectionStart : this.currentItem;
+
+ if (!aStartItem)
+ aStartItem = aEndItem;
+
+ var suppressSelect = this._suppressOnSelect;
+ this._suppressOnSelect = true;
+
+ this._selectionStart = aStartItem;
+
+ var currentItem;
+ var startIndex = this.getIndexOfItem(aStartItem);
+ var endIndex = this.getIndexOfItem(aEndItem);
+ if (endIndex < startIndex) {
+ currentItem = aEndItem;
+ aEndItem = aStartItem;
+ aStartItem = currentItem;
+ } else {
+ currentItem = aStartItem;
+ }
+
+ while (currentItem) {
+ this.addItemToSelection(currentItem);
+ if (currentItem == aEndItem) {
+ currentItem = this.getNextItem(currentItem, 1);
+ break;
+ }
+ currentItem = this.getNextItem(currentItem, 1);
+ }
+
+ // Clear around new selection
+ // Don't use clearSelection() because it causes a lot of noise
+ // with respect to selection removed notifications used by the
+ // accessibility API support.
+ var userSelecting = this._userSelecting;
+ this._userSelecting = false; // that's US automatically unselecting
+ for (; currentItem; currentItem = this.getNextItem(currentItem, 1))
+ this.removeItemFromSelection(currentItem);
+
+ for (currentItem = this.getItemAtIndex(0); currentItem != aStartItem;
+ currentItem = this.getNextItem(currentItem, 1))
+ this.removeItemFromSelection(currentItem);
+ this._userSelecting = userSelecting;
+
+ this._suppressOnSelect = suppressSelect;
+
+ this._fireOnSelect();
+ ]]>
+ </body>
+ </method>
+
+ <method name="selectAll">
+ <body>
+ this._selectionStart = null;
+
+ var suppress = this._suppressOnSelect;
+ this._suppressOnSelect = true;
+
+ var item = this.getItemAtIndex(0);
+ while (item) {
+ this.addItemToSelection(item);
+ item = this.getNextItem(item, 1);
+ }
+
+ this._suppressOnSelect = suppress;
+ this._fireOnSelect();
+ </body>
+ </method>
+
+ <method name="invertSelection">
+ <body>
+ this._selectionStart = null;
+
+ var suppress = this._suppressOnSelect;
+ this._suppressOnSelect = true;
+
+ var item = this.getItemAtIndex(0);
+ while (item) {
+ if (item.selected)
+ this.removeItemFromSelection(item);
+ else
+ this.addItemToSelection(item);
+ item = this.getNextItem(item, 1);
+ }
+
+ this._suppressOnSelect = suppress;
+ this._fireOnSelect();
+ </body>
+ </method>
+
+ <method name="clearSelection">
+ <body>
+ <![CDATA[
+ if (this.selectedItems) {
+ while (this.selectedItems.length > 0) {
+ let item = this.selectedItems[0];
+ item.selected = false;
+ this.selectedItems.remove(item);
+ }
+ }
+
+ this._selectionStart = null;
+ this._fireOnSelect();
+ ]]>
+ </body>
+ </method>
+
+ <property name="selectedCount" readonly="true"
+ onget="return this.selectedItems.length;"/>
+
+ <method name="getSelectedItem">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return aIndex < this.selectedItems.length ?
+ this.selectedItems[aIndex] : null;
+ ]]>
+ </body>
+ </method>
+
+ <!-- Other public members -->
+ <property name="disableKeyNavigation"
+ onget="return this.hasAttribute('disableKeyNavigation');">
+ <setter>
+ if (val)
+ this.setAttribute("disableKeyNavigation", "true");
+ else
+ this.removeAttribute("disableKeyNavigation");
+ return val;
+ </setter>
+ </property>
+
+ <property name="suppressOnSelect"
+ onget="return this.getAttribute('suppressonselect') == 'true';"
+ onset="this.setAttribute('suppressonselect', val);"/>
+
+ <property name="_selectDelay"
+ onset="this.setAttribute('_selectDelay', val);"
+ onget="return this.getAttribute('_selectDelay') || 50;"/>
+
+ <method name="timedSelect">
+ <parameter name="aItem"/>
+ <parameter name="aTimeout"/>
+ <body>
+ <![CDATA[
+ var suppress = this._suppressOnSelect;
+ if (aTimeout != -1)
+ this._suppressOnSelect = true;
+
+ this.selectItem(aItem);
+
+ this._suppressOnSelect = suppress;
+
+ if (aTimeout != -1) {
+ if (this._selectTimeout)
+ window.clearTimeout(this._selectTimeout);
+ this._selectTimeout =
+ window.setTimeout(this._selectTimeoutHandler, aTimeout, this);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveByOffset">
+ <parameter name="aOffset"/>
+ <parameter name="aIsSelecting"/>
+ <parameter name="aIsSelectingRange"/>
+ <body>
+ <![CDATA[
+ if ((aIsSelectingRange || !aIsSelecting) &&
+ this.selType != "multiple")
+ return;
+
+ var newIndex = this.currentIndex + aOffset;
+ if (newIndex < 0)
+ newIndex = 0;
+
+ var numItems = this.getRowCount();
+ if (newIndex > numItems - 1)
+ newIndex = numItems - 1;
+
+ var newItem = this.getItemAtIndex(newIndex);
+ // make sure that the item is actually visible/selectable
+ if (this._userSelecting && newItem && !this._canUserSelect(newItem))
+ newItem =
+ aOffset > 0 ? this.getNextItem(newItem, 1) || this.getPreviousItem(newItem, 1) :
+ this.getPreviousItem(newItem, 1) || this.getNextItem(newItem, 1);
+ if (newItem) {
+ this.ensureIndexIsVisible(this.getIndexOfItem(newItem));
+ if (aIsSelectingRange)
+ this.selectItemRange(null, newItem);
+ else if (aIsSelecting)
+ this.selectItem(newItem);
+
+ this.currentItem = newItem;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- Private -->
+ <method name="getNextItem">
+ <parameter name="aStartItem"/>
+ <parameter name="aDelta"/>
+ <body>
+ <![CDATA[
+ while (aStartItem) {
+ aStartItem = aStartItem.nextSibling;
+ if (aStartItem && aStartItem instanceof
+ Components.interfaces.nsIDOMXULSelectControlItemElement &&
+ (!this._userSelecting || this._canUserSelect(aStartItem))) {
+ --aDelta;
+ if (aDelta == 0)
+ return aStartItem;
+ }
+ }
+ return null;
+ ]]></body>
+ </method>
+
+ <method name="getPreviousItem">
+ <parameter name="aStartItem"/>
+ <parameter name="aDelta"/>
+ <body>
+ <![CDATA[
+ while (aStartItem) {
+ aStartItem = aStartItem.previousSibling;
+ if (aStartItem && aStartItem instanceof
+ Components.interfaces.nsIDOMXULSelectControlItemElement &&
+ (!this._userSelecting || this._canUserSelect(aStartItem))) {
+ --aDelta;
+ if (aDelta == 0)
+ return aStartItem;
+ }
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_moveByOffsetFromUserEvent">
+ <parameter name="aOffset"/>
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (!aEvent.defaultPrevented) {
+ this._userSelecting = true;
+ this._mayReverse = true;
+ this.moveByOffset(aOffset, !aEvent.ctrlKey, aEvent.shiftKey);
+ this._userSelecting = false;
+ this._mayReverse = false;
+ aEvent.preventDefault();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_canUserSelect">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ var style = document.defaultView.getComputedStyle(aItem, "");
+ return style.display != "none" && style.visibility == "visible";
+ ]]>
+ </body>
+ </method>
+
+ <method name="_selectTimeoutHandler">
+ <parameter name="aMe"/>
+ <body>
+ aMe._fireOnSelect();
+ aMe._selectTimeout = null;
+ </body>
+ </method>
+
+ <field name="_suppressOnSelect">false</field>
+ <field name="_userSelecting">false</field>
+ <field name="_mayReverse">false</field>
+ <field name="_selectTimeout">null</field>
+ <field name="_currentItem">null</field>
+ <field name="_selectionStart">null</field>
+ </implementation>
+
+ <handlers>
+ <handler event="keypress" keycode="VK_UP" modifiers="control shift any"
+ action="this._moveByOffsetFromUserEvent(-1, event);"
+ group="system"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="control shift any"
+ action="this._moveByOffsetFromUserEvent(1, event);"
+ group="system"/>
+ <handler event="keypress" keycode="VK_HOME" modifiers="control shift any"
+ group="system">
+ <![CDATA[
+ this._mayReverse = true;
+ this._moveByOffsetFromUserEvent(-this.currentIndex, event);
+ this._mayReverse = false;
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_END" modifiers="control shift any"
+ group="system">
+ <![CDATA[
+ this._mayReverse = true;
+ this._moveByOffsetFromUserEvent(this.getRowCount() - this.currentIndex - 1, event);
+ this._mayReverse = false;
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_PAGE_UP" modifiers="control shift any"
+ group="system">
+ <![CDATA[
+ this._mayReverse = true;
+ this._moveByOffsetFromUserEvent(this.scrollOnePage(-1), event);
+ this._mayReverse = false;
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="control shift any"
+ group="system">
+ <![CDATA[
+ this._mayReverse = true;
+ this._moveByOffsetFromUserEvent(this.scrollOnePage(1), event);
+ this._mayReverse = false;
+ ]]>
+ </handler>
+ <handler event="keypress" key=" " modifiers="control" phase="target">
+ <![CDATA[
+ if (this.currentItem && this.selType == "multiple")
+ this.toggleItemSelection(this.currentItem);
+ ]]>
+ </handler>
+ <handler event="focus">
+ <![CDATA[
+ if (this.getRowCount() > 0) {
+ if (this.currentIndex == -1) {
+ this.currentIndex = this.getIndexOfFirstVisibleRow();
+ }
+ else {
+ this.currentItem._fireEvent("DOMMenuItemActive");
+ }
+ }
+ this._lastKeyTime = 0;
+ ]]>
+ </handler>
+ <handler event="keypress" phase="target">
+ <![CDATA[
+ if (this.disableKeyNavigation || !event.charCode ||
+ event.altKey || event.ctrlKey || event.metaKey)
+ return;
+
+ if (event.timeStamp - this._lastKeyTime > 1000)
+ this._incrementalString = "";
+
+ var key = String.fromCharCode(event.charCode).toLowerCase();
+ this._incrementalString += key;
+ this._lastKeyTime = event.timeStamp;
+
+ // If all letters in the incremental string are the same, just
+ // try to match the first one
+ var incrementalString = /^(.)\1+$/.test(this._incrementalString) ?
+ RegExp.$1 : this._incrementalString;
+ var length = incrementalString.length;
+
+ var rowCount = this.getRowCount();
+ var l = this.selectedItems.length;
+ var start = l > 0 ? this.getIndexOfItem(this.selectedItems[l - 1]) : -1;
+ // start from the first element if none was selected or from the one
+ // following the selected one if it's a new or a repeated-letter search
+ if (start == -1 || length == 1)
+ start++;
+
+ for (var i = 0; i < rowCount; i++) {
+ var k = (start + i) % rowCount;
+ var listitem = this.getItemAtIndex(k);
+ if (!this._canUserSelect(listitem))
+ continue;
+ // allow richlistitems to specify the string being searched for
+ var searchText = "searchLabel" in listitem ? listitem.searchLabel :
+ listitem.getAttribute("label"); // (see also bug 250123)
+ searchText = searchText.substring(0, length).toLowerCase();
+ if (searchText == incrementalString) {
+ this.ensureIndexIsVisible(k);
+ this.timedSelect(listitem, this._selectDelay);
+ break;
+ }
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+
+ <!-- Binding for xul:listbox element.
+ -->
+ <binding id="listbox"
+ extends="#listbox-base">
+
+ <resources>
+ <stylesheet src="chrome://global/skin/listbox.css"/>
+ </resources>
+
+ <content>
+ <children includes="listcols">
+ <xul:listcols>
+ <xul:listcol flex="1"/>
+ </xul:listcols>
+ </children>
+ <xul:listrows>
+ <children includes="listhead"/>
+ <xul:listboxbody xbl:inherits="rows,size,minheight">
+ <children includes="listitem"/>
+ </xul:listboxbody>
+ </xul:listrows>
+ </content>
+
+ <implementation>
+
+ <!-- ///////////////// public listbox members ///////////////// -->
+
+ <property name="listBoxObject"
+ onget="return this.boxObject;"
+ readonly="true"/>
+
+ <!-- ///////////////// private listbox members ///////////////// -->
+
+ <method name="_fireOnSelect">
+ <body>
+ <![CDATA[
+ if (!this._suppressOnSelect && !this.suppressOnSelect) {
+ var event = document.createEvent("Events");
+ event.initEvent("select", true, true);
+ this.dispatchEvent(event);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <constructor>
+ <![CDATA[
+ var count = this.itemCount;
+ for (var index = 0; index < count; index++) {
+ var item = this.getItemAtIndex(index);
+ if (item.getAttribute("selected") == "true")
+ this.selectedItems.append(item);
+ }
+ ]]>
+ </constructor>
+
+ <!-- ///////////////// nsIDOMXULSelectControlElement ///////////////// -->
+
+ <method name="appendItem">
+ <parameter name="aLabel"/>
+ <parameter name="aValue"/>
+ <body>
+ const XULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var item = this.ownerDocument.createElementNS(XULNS, "listitem");
+ item.setAttribute("label", aLabel);
+ item.setAttribute("value", aValue);
+ this.appendChild(item);
+ return item;
+ </body>
+ </method>
+
+ <method name="insertItemAt">
+ <parameter name="aIndex"/>
+ <parameter name="aLabel"/>
+ <parameter name="aValue"/>
+ <body>
+ const XULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var item = this.ownerDocument.createElementNS(XULNS, "listitem");
+ item.setAttribute("label", aLabel);
+ item.setAttribute("value", aValue);
+ var before = this.getItemAtIndex(aIndex);
+ if (before)
+ this.insertBefore(item, before);
+ else
+ this.appendChild(item);
+ return item;
+ </body>
+ </method>
+
+ <property name="itemCount" readonly="true"
+ onget="return this.listBoxObject.getRowCount()"/>
+
+ <!-- ///////////////// nsIListBoxObject ///////////////// -->
+ <method name="getIndexOfItem">
+ <parameter name="item"/>
+ <body>
+ return this.listBoxObject.getIndexOfItem(item);
+ </body>
+ </method>
+ <method name="getItemAtIndex">
+ <parameter name="index"/>
+ <body>
+ return this.listBoxObject.getItemAtIndex(index);
+ </body>
+ </method>
+ <method name="ensureIndexIsVisible">
+ <parameter name="index"/>
+ <body>
+ return this.listBoxObject.ensureIndexIsVisible(index);
+ </body>
+ </method>
+ <method name="ensureElementIsVisible">
+ <parameter name="element"/>
+ <body>
+ return this.ensureIndexIsVisible(this.listBoxObject.getIndexOfItem(element));
+ </body>
+ </method>
+ <method name="scrollToIndex">
+ <parameter name="index"/>
+ <body>
+ return this.listBoxObject.scrollToIndex(index);
+ </body>
+ </method>
+ <method name="getNumberOfVisibleRows">
+ <body>
+ return this.listBoxObject.getNumberOfVisibleRows();
+ </body>
+ </method>
+ <method name="getIndexOfFirstVisibleRow">
+ <body>
+ return this.listBoxObject.getIndexOfFirstVisibleRow();
+ </body>
+ </method>
+ <method name="getRowCount">
+ <body>
+ return this.listBoxObject.getRowCount();
+ </body>
+ </method>
+
+ <method name="scrollOnePage">
+ <parameter name="direction"/> <!-- Must be -1 or 1 -->
+ <body>
+ <![CDATA[
+ var pageOffset = this.getNumberOfVisibleRows() * direction;
+ // skip over invisible elements - the user won't care about them
+ for (var i = 0; i != pageOffset; i += direction) {
+ var item = this.getItemAtIndex(this.currentIndex + i);
+ if (item && !this._canUserSelect(item))
+ pageOffset += direction;
+ }
+ var newTop = this.getIndexOfFirstVisibleRow() + pageOffset;
+ if (direction == 1) {
+ var maxTop = this.getRowCount() - this.getNumberOfVisibleRows();
+ for (i = this.getRowCount(); i >= 0 && i > maxTop; i--) {
+ item = this.getItemAtIndex(i);
+ if (item && !this._canUserSelect(item))
+ maxTop--;
+ }
+ if (newTop >= maxTop)
+ newTop = maxTop;
+ }
+ if (newTop < 0)
+ newTop = 0;
+ this.scrollToIndex(newTop);
+ return pageOffset;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="keypress" key=" " phase="target">
+ <![CDATA[
+ if (this.currentItem) {
+ if (this.currentItem.getAttribute("type") != "checkbox") {
+ this.addItemToSelection(this.currentItem);
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ }
+ else if (!this.currentItem.disabled) {
+ this.currentItem.checked = !this.currentItem.checked;
+ this.currentItem.doCommand();
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ }
+ }
+ ]]>
+ </handler>
+
+ <handler event="MozSwipeGesture">
+ <![CDATA[
+ // Figure out which index to show
+ let targetIndex = 0;
+
+ // Only handle swipe gestures up and down
+ switch (event.direction) {
+ case event.DIRECTION_DOWN:
+ targetIndex = this.itemCount - 1;
+ // Fall through for actual action
+ case event.DIRECTION_UP:
+ this.ensureIndexIsVisible(targetIndex);
+ break;
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="listrows">
+
+ <resources>
+ <stylesheet src="chrome://global/skin/listbox.css"/>
+ </resources>
+
+ <handlers>
+ <handler event="DOMMouseScroll" phase="capturing">
+ <![CDATA[
+ if (event.axis == event.HORIZONTAL_AXIS)
+ return;
+
+ var listBox = this.parentNode.listBoxObject;
+ var rows = event.detail;
+ if (rows == UIEvent.SCROLL_PAGE_UP)
+ rows = -listBox.getNumberOfVisibleRows();
+ else if (rows == UIEvent.SCROLL_PAGE_DOWN)
+ rows = listBox.getNumberOfVisibleRows();
+
+ listBox.scrollByLines(rows);
+ event.preventDefault();
+ ]]>
+ </handler>
+
+ <handler event="MozMousePixelScroll" phase="capturing">
+ <![CDATA[
+ if (event.axis == event.HORIZONTAL_AXIS)
+ return;
+
+ // shouldn't be scrolled by pixel scrolling events before a line/page
+ // scrolling event.
+ event.preventDefault();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="listitem" role="xul:listitem"
+ extends="chrome://global/content/bindings/general.xml#basetext">
+ <resources>
+ <stylesheet src="chrome://global/skin/listbox.css"/>
+ </resources>
+
+ <content>
+ <children>
+ <xul:listcell xbl:inherits="label,crop,disabled,flexlabel"/>
+ </children>
+ </content>
+
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <property name="current" onget="return this.getAttribute('current') == 'true';">
+ <setter><![CDATA[
+ if (val)
+ this.setAttribute("current", "true");
+ else
+ this.removeAttribute("current");
+
+ let control = this.control;
+ if (!control || !control.suppressMenuItemEvent) {
+ this._fireEvent(val ? "DOMMenuItemActive" : "DOMMenuItemInactive");
+ }
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <!-- ///////////////// nsIDOMXULSelectControlItemElement ///////////////// -->
+
+ <property name="value" onget="return this.getAttribute('value');"
+ onset="this.setAttribute('value', val); return val;"/>
+ <property name="label" onget="return this.getAttribute('label');"
+ onset="this.setAttribute('label', val); return val;"/>
+
+ <property name="selected" onget="return this.getAttribute('selected') == 'true';">
+ <setter><![CDATA[
+ if (val)
+ this.setAttribute("selected", "true");
+ else
+ this.removeAttribute("selected");
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="control">
+ <getter><![CDATA[
+ var parent = this.parentNode;
+ while (parent) {
+ if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
+ return parent;
+ parent = parent.parentNode;
+ }
+ return null;
+ ]]></getter>
+ </property>
+
+ <method name="_fireEvent">
+ <parameter name="name"/>
+ <body>
+ <![CDATA[
+ var event = document.createEvent("Events");
+ event.initEvent(name, true, true);
+ this.dispatchEvent(event);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ <handlers>
+ <!-- If there is no modifier key, we select on mousedown, not
+ click, so that drags work correctly. -->
+ <handler event="mousedown">
+ <![CDATA[
+ var control = this.control;
+ if (!control || control.disabled)
+ return;
+ if ((!event.ctrlKey || (/Mac/.test(navigator.platform) && event.button == 2)) &&
+ !event.shiftKey && !event.metaKey) {
+ if (!this.selected) {
+ control.selectItem(this);
+ }
+ control.currentItem = this;
+ }
+ ]]>
+ </handler>
+
+ <!-- On a click (up+down on the same item), deselect everything
+ except this item. -->
+ <handler event="click" button="0">
+ <![CDATA[
+ var control = this.control;
+ if (!control || control.disabled)
+ return;
+ control._userSelecting = true;
+ if (control.selType != "multiple") {
+ control.selectItem(this);
+ }
+ else if (event.ctrlKey || event.metaKey) {
+ control.toggleItemSelection(this);
+ control.currentItem = this;
+ }
+ else if (event.shiftKey) {
+ control.selectItemRange(null, this);
+ control.currentItem = this;
+ }
+ else {
+ /* We want to deselect all the selected items except what was
+ clicked, UNLESS it was a right-click. We have to do this
+ in click rather than mousedown so that you can drag a
+ selected group of items */
+
+ // use selectItemRange instead of selectItem, because this
+ // doesn't de- and reselect this item if it is selected
+ control.selectItemRange(this, this);
+ }
+ control._userSelecting = false;
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="listitem-iconic"
+ extends="chrome://global/content/bindings/listbox.xml#listitem">
+ <content>
+ <children>
+ <xul:listcell class="listcell-iconic" xbl:inherits="label,image,crop,disabled,flexlabel"/>
+ </children>
+ </content>
+ </binding>
+
+ <binding id="listitem-checkbox"
+ extends="chrome://global/content/bindings/listbox.xml#listitem">
+ <content>
+ <children>
+ <xul:listcell type="checkbox" xbl:inherits="label,crop,checked,disabled,flexlabel"/>
+ </children>
+ </content>
+
+ <implementation>
+ <property name="checked"
+ onget="return this.getAttribute('checked') == 'true';">
+ <setter><![CDATA[
+ if (val)
+ this.setAttribute('checked', 'true');
+ else
+ this.removeAttribute('checked');
+ var event = document.createEvent('Events');
+ event.initEvent('CheckboxStateChange', true, true);
+ this.dispatchEvent(event);
+ return val;
+ ]]></setter>
+ </property>
+ </implementation>
+
+ <handlers>
+ <handler event="mousedown" button="0">
+ <![CDATA[
+ if (!this.disabled && !this.control.disabled) {
+ this.checked = !this.checked;
+ this.doCommand();
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="listitem-checkbox-iconic"
+ extends="chrome://global/content/bindings/listbox.xml#listitem-checkbox">
+ <content>
+ <children>
+ <xul:listcell type="checkbox" class="listcell-iconic" xbl:inherits="label,image,crop,checked,disabled,flexlabel"/>
+ </children>
+ </content>
+ </binding>
+
+ <binding id="listcell" role="xul:listcell"
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
+
+ <resources>
+ <stylesheet src="chrome://global/skin/listbox.css"/>
+ </resources>
+
+ <content>
+ <children>
+ <xul:label class="listcell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled" flex="1" crop="right"/>
+ </children>
+ </content>
+ </binding>
+
+ <binding id="listcell-iconic"
+ extends="chrome://global/content/bindings/listbox.xml#listcell">
+ <content>
+ <children>
+ <xul:image class="listcell-icon" xbl:inherits="src=image"/>
+ <xul:label class="listcell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled" flex="1" crop="right"/>
+ </children>
+ </content>
+ </binding>
+
+ <binding id="listcell-checkbox"
+ extends="chrome://global/content/bindings/listbox.xml#listcell">
+ <content>
+ <children>
+ <xul:image class="listcell-check" xbl:inherits="checked,disabled"/>
+ <xul:label class="listcell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled" flex="1" crop="right"/>
+ </children>
+ </content>
+ </binding>
+
+ <binding id="listcell-checkbox-iconic"
+ extends="chrome://global/content/bindings/listbox.xml#listcell-checkbox">
+ <content>
+ <children>
+ <xul:image class="listcell-check" xbl:inherits="checked,disabled"/>
+ <xul:image class="listcell-icon" xbl:inherits="src=image"/>
+ <xul:label class="listcell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled" flex="1" crop="right"/>
+ </children>
+ </content>
+ </binding>
+
+ <binding id="listhead" role="xul:listhead">
+
+ <resources>
+ <stylesheet src="chrome://global/skin/listbox.css"/>
+ </resources>
+
+ <content>
+ <xul:listheaditem>
+ <children includes="listheader"/>
+ </xul:listheaditem>
+ </content>
+ </binding>
+
+ <binding id="listheader" display="xul:button" role="xul:listheader">
+
+ <resources>
+ <stylesheet src="chrome://global/skin/listbox.css"/>
+ </resources>
+
+ <content>
+ <xul:image class="listheader-icon"/>
+ <xul:label class="listheader-label" xbl:inherits="value=label,crop" flex="1" crop="right"/>
+ <xul:image class="listheader-sortdirection" xbl:inherits="sortDirection"/>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/menu.xml b/components/bindings/content/menu.xml
new file mode 100644
index 000000000..26dcad454
--- /dev/null
+++ b/components/bindings/content/menu.xml
@@ -0,0 +1,286 @@
+<?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="menuitemBindings"
+ 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="menuitem-base" role="xul:menuitem"
+ extends="chrome://global/content/bindings/general.xml#control-item">
+ <resources>
+ <stylesheet src="chrome://global/skin/menu.css"/>
+ </resources>
+ <implementation implements="nsIDOMXULSelectControlItemElement, nsIDOMXULContainerItemElement">
+ <!-- nsIDOMXULSelectControlItemElement -->
+ <property name="selected" readonly="true"
+ onget="return this.getAttribute('selected') == 'true';"/>
+ <property name="control" readonly="true">
+ <getter>
+ <![CDATA[
+ var parent = this.parentNode;
+ if (parent &&
+ parent.parentNode instanceof Components.interfaces.nsIDOMXULSelectControlElement)
+ return parent.parentNode;
+ return null;
+ ]]>
+ </getter>
+ </property>
+
+ <!-- nsIDOMXULContainerItemElement -->
+ <property name="parentContainer" readonly="true">
+ <getter>
+ for (var parent = this.parentNode; parent; parent = parent.parentNode) {
+ if (parent instanceof Components.interfaces.nsIDOMXULContainerElement)
+ return parent;
+ }
+ return null;
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="menu-base"
+ extends="chrome://global/content/bindings/menu.xml#menuitem-base">
+
+ <implementation implements="nsIDOMXULContainerElement">
+ <property name="open" onget="return this.hasAttribute('open');">
+ <setter><![CDATA[
+ this.boxObject.openMenu(val);
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="openedWithKey" readonly="true">
+ <getter><![CDATA[
+ return this.boxObject.openedWithKey;
+ ]]></getter>
+ </property>
+
+ <!-- nsIDOMXULContainerElement interface -->
+ <method name="appendItem">
+ <parameter name="aLabel"/>
+ <parameter name="aValue"/>
+ <body>
+ return this.insertItemAt(-1, aLabel, aValue);
+ </body>
+ </method>
+
+ <method name="insertItemAt">
+ <parameter name="aIndex"/>
+ <parameter name="aLabel"/>
+ <parameter name="aValue"/>
+ <body>
+ const XUL_NS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var menupopup = this.menupopup;
+ if (!menupopup) {
+ menupopup = this.ownerDocument.createElementNS(XUL_NS, "menupopup");
+ this.appendChild(menupopup);
+ }
+
+ var menuitem = this.ownerDocument.createElementNS(XUL_NS, "menuitem");
+ menuitem.setAttribute("label", aLabel);
+ menuitem.setAttribute("value", aValue);
+
+ var before = this.getItemAtIndex(aIndex);
+ if (before)
+ return menupopup.insertBefore(menuitem, before);
+ return menupopup.appendChild(menuitem);
+ </body>
+ </method>
+
+ <method name="removeItemAt">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ var menupopup = this.menupopup;
+ if (menupopup) {
+ var item = this.getItemAtIndex(aIndex);
+ if (item)
+ return menupopup.removeChild(item);
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <property name="itemCount" readonly="true">
+ <getter>
+ var menupopup = this.menupopup;
+ return menupopup ? menupopup.childNodes.length : 0;
+ </getter>
+ </property>
+
+ <method name="getIndexOfItem">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ var menupopup = this.menupopup;
+ if (menupopup) {
+ var items = menupopup.childNodes;
+ var length = items.length;
+ for (var index = 0; index < length; ++index) {
+ if (items[index] == aItem)
+ return index;
+ }
+ }
+ return -1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getItemAtIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ var menupopup = this.menupopup;
+ if (!menupopup || aIndex < 0 || aIndex >= menupopup.childNodes.length)
+ return null;
+
+ return menupopup.childNodes[aIndex];
+ ]]>
+ </body>
+ </method>
+
+ <property name="menupopup" readonly="true">
+ <getter>
+ <![CDATA[
+ const XUL_NS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ for (var child = this.firstChild; child; child = child.nextSibling) {
+ if (child.namespaceURI == XUL_NS && child.localName == "menupopup")
+ return child;
+ }
+ return null;
+ ]]>
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="menu"
+ extends="chrome://global/content/bindings/menu.xml#menu-base">
+ <content>
+ <xul:label class="menu-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
+ <xul:hbox class="menu-accel-container" anonid="accel">
+ <xul:label class="menu-accel" xbl:inherits="value=acceltext"/>
+ </xul:hbox>
+ <xul:hbox align="center" class="menu-right" xbl:inherits="_moz-menuactive,disabled">
+ <xul:image/>
+ </xul:hbox>
+ <children includes="menupopup"/>
+ </content>
+ </binding>
+
+ <binding id="menuitem" extends="chrome://global/content/bindings/menu.xml#menuitem-base">
+ <content>
+ <xul:label class="menu-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
+ <xul:hbox class="menu-accel-container" anonid="accel">
+ <xul:label class="menu-accel" xbl:inherits="value=acceltext"/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="menucaption" extends="chrome://global/content/bindings/menu.xml#menu-base">
+ <content>
+ <xul:label class="menu-text" xbl:inherits="value=label,crop" crop="right"/>
+ </content>
+ </binding>
+
+ <binding id="menu-menubar"
+ extends="chrome://global/content/bindings/menu.xml#menu-base">
+ <content>
+ <xul:label class="menubar-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
+ <children includes="menupopup"/>
+ </content>
+ </binding>
+
+ <binding id="menu-menubar-iconic"
+ 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"/>
+ <children includes="menupopup"/>
+ </content>
+ </binding>
+
+ <binding id="menuitem-iconic" extends="chrome://global/content/bindings/menu.xml#menuitem">
+ <content>
+ <xul:hbox class="menu-iconic-left" align="center" pack="center"
+ xbl:inherits="selected,_moz-menuactive,disabled,checked">
+ <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
+ </xul:hbox>
+ <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop" crop="right"/>
+ <children/>
+ <xul:hbox class="menu-accel-container" anonid="accel">
+ <xul:label class="menu-iconic-accel" xbl:inherits="value=acceltext"/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="menuitem-iconic-noaccel" extends="chrome://global/content/bindings/menu.xml#menuitem">
+ <content>
+ <xul:hbox class="menu-iconic-left" align="center" pack="center"
+ xbl:inherits="selected,disabled,checked">
+ <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
+ </xul:hbox>
+ <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop" crop="right"/>
+ </content>
+ </binding>
+
+ <binding id="menucaption-inmenulist" extends="chrome://global/content/bindings/menu.xml#menucaption">
+ <content>
+ <xul:hbox class="menu-iconic-left" align="center" pack="center"
+ xbl:inherits="selected,disabled,checked">
+ <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
+ </xul:hbox>
+ <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,crop" crop="right"/>
+ </content>
+ </binding>
+
+ <binding id="menuitem-iconic-desc-noaccel" extends="chrome://global/content/bindings/menu.xml#menuitem">
+ <content>
+ <xul:hbox class="menu-iconic-left" align="center" pack="center"
+ xbl:inherits="selected,disabled,checked">
+ <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
+ </xul:hbox>
+ <xul:label class="menu-iconic-text" xbl:inherits="value=label,accesskey,crop" crop="right" flex="1"/>
+ <xul:label class="menu-iconic-text menu-description" xbl:inherits="value=description" crop="right" flex="10000"/>
+ </content>
+ </binding>
+
+ <binding id="menu-iconic"
+ extends="chrome://global/content/bindings/menu.xml#menu-base">
+ <content>
+ <xul:hbox class="menu-iconic-left" align="center" pack="center">
+ <xul:image class="menu-iconic-icon" xbl:inherits="src=image"/>
+ </xul:hbox>
+ <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop" crop="right"/>
+ <xul:hbox class="menu-accel-container" anonid="accel">
+ <xul:label class="menu-iconic-accel" xbl:inherits="value=acceltext"/>
+ </xul:hbox>
+ <xul:hbox align="center" class="menu-right" xbl:inherits="_moz-menuactive,disabled">
+ <xul:image/>
+ </xul:hbox>
+ <children includes="menupopup|template"/>
+ </content>
+ </binding>
+
+ <binding id="menubutton-item" extends="chrome://global/content/bindings/menu.xml#menuitem-base">
+ <content>
+ <xul:label class="menubutton-text" flex="1" xbl:inherits="value=label,accesskey,crop" crop="right"/>
+ <children includes="menupopup"/>
+ </content>
+ </binding>
+
+ <binding id="menuseparator" role="xul:menuseparator"
+ extends="chrome://global/content/bindings/menu.xml#menuitem-base">
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/menulist.xml b/components/bindings/content/menulist.xml
new file mode 100644
index 000000000..96c011809
--- /dev/null
+++ b/components/bindings/content/menulist.xml
@@ -0,0 +1,620 @@
+<?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="menulistBindings"
+ 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="menulist-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
+ <resources>
+ <stylesheet src="chrome://global/content/menulist.css"/>
+ <stylesheet src="chrome://global/skin/menulist.css"/>
+ </resources>
+ </binding>
+
+ <binding id="menulist" display="xul:menu" role="xul:menulist"
+ extends="chrome://global/content/bindings/menulist.xml#menulist-base">
+ <content sizetopopup="pref">
+ <xul:hbox class="menulist-label-box" flex="1">
+ <xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
+ <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
+ </xul:hbox>
+ <xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled,open"/>
+ <children includes="menupopup"/>
+ </content>
+
+ <handlers>
+ <handler event="command" phase="capturing"
+ action="if (event.target.parentNode.parentNode == this) this.selectedItem = event.target;"/>
+
+ <handler event="popupshowing">
+ <![CDATA[
+ if (event.target.parentNode == this) {
+ this.menuBoxObject.activeChild = null;
+ if (this.selectedItem)
+ // Not ready for auto-setting the active child in hierarchies yet.
+ // For now, only do this when the outermost menupopup opens.
+ this.menuBoxObject.activeChild = this.mSelectedInternal;
+ }
+ ]]>
+ </handler>
+
+ <handler event="keypress" modifiers="shift any" group="system">
+ <![CDATA[
+ if (!event.defaultPrevented &&
+ (event.keyCode == KeyEvent.DOM_VK_UP ||
+ event.keyCode == KeyEvent.DOM_VK_DOWN ||
+ event.keyCode == KeyEvent.DOM_VK_PAGE_UP ||
+ event.keyCode == KeyEvent.DOM_VK_PAGE_DOWN ||
+ event.keyCode == KeyEvent.DOM_VK_HOME ||
+ event.keyCode == KeyEvent.DOM_VK_END ||
+ event.keyCode == KeyEvent.DOM_VK_BACK_SPACE ||
+ event.charCode > 0)) {
+ // Moving relative to an item: start from the currently selected item
+ this.menuBoxObject.activeChild = this.mSelectedInternal;
+ if (this.menuBoxObject.handleKeyPress(event)) {
+ this.menuBoxObject.activeChild.doCommand();
+ event.preventDefault();
+ }
+ }
+ ]]>
+ </handler>
+ </handlers>
+
+ <implementation implements="nsIDOMXULMenuListElement">
+ <constructor>
+ this.mInputField = null;
+ this.mSelectedInternal = null;
+ this.mAttributeObserver = null;
+ this.menuBoxObject = this.boxObject;
+ this.setInitialSelection();
+ </constructor>
+
+ <method name="setInitialSelection">
+ <body>
+ <![CDATA[
+ var popup = this.menupopup;
+ if (popup) {
+ var arr = popup.getElementsByAttribute('selected', 'true');
+
+ var editable = this.editable;
+ var value = this.value;
+ if (!arr.item(0) && value)
+ arr = popup.getElementsByAttribute(editable ? 'label' : 'value', value);
+
+ if (arr.item(0))
+ this.selectedItem = arr[0];
+ else if (!editable)
+ this.selectedIndex = 0;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="value" onget="return this.getAttribute('value');">
+ <setter>
+ <![CDATA[
+ // if the new value is null, we still need to remove the old value
+ if (val == null)
+ return this.selectedItem = val;
+
+ var arr = null;
+ var popup = this.menupopup;
+ if (popup)
+ arr = popup.getElementsByAttribute('value', val);
+
+ if (arr && arr.item(0))
+ this.selectedItem = arr[0];
+ else {
+ this.selectedItem = null;
+ this.setAttribute('value', val);
+ }
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="inputField" readonly="true" onget="return null;"/>
+
+ <property name="crop" onset="this.setAttribute('crop',val); return val;"
+ onget="return this.getAttribute('crop');"/>
+ <property name="image" onset="this.setAttribute('image',val); return val;"
+ onget="return this.getAttribute('image');"/>
+ <property name="label" readonly="true" onget="return this.getAttribute('label');"/>
+ <property name="description" onset="this.setAttribute('description',val); return val;"
+ onget="return this.getAttribute('description');"/>
+
+ <property name="editable" onget="return this.getAttribute('editable') == 'true';">
+ <setter>
+ <![CDATA[
+ if (!val && this.editable) {
+ // If we were focused and transition from editable to not editable,
+ // focus the parent menulist so that the focus does not get stuck.
+ if (this.inputField == document.activeElement)
+ window.setTimeout(() => this.focus(), 0);
+ }
+
+ this.setAttribute("editable", val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="open" onset="this.menuBoxObject.openMenu(val);
+ return val;"
+ onget="return this.hasAttribute('open');"/>
+
+ <property name="itemCount" readonly="true"
+ onget="return this.menupopup ? this.menupopup.childNodes.length : 0"/>
+
+ <property name="menupopup" readonly="true">
+ <getter>
+ <![CDATA[
+ var popup = this.firstChild;
+ while (popup && popup.localName != "menupopup")
+ popup = popup.nextSibling;
+ return popup;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="contains">
+ <parameter name="item"/>
+ <body>
+ <![CDATA[
+ if (!item)
+ return false;
+
+ var parent = item.parentNode;
+ return (parent && parent.parentNode == this);
+ ]]>
+ </body>
+ </method>
+
+ <property name="selectedIndex">
+ <getter>
+ <![CDATA[
+ // Quick and dirty. We won't deal with hierarchical menulists yet.
+ if (!this.selectedItem ||
+ !this.mSelectedInternal.parentNode ||
+ this.mSelectedInternal.parentNode.parentNode != this)
+ return -1;
+
+ var children = this.mSelectedInternal.parentNode.childNodes;
+ var i = children.length;
+ while (i--)
+ if (children[i] == this.mSelectedInternal)
+ break;
+
+ return i;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ var popup = this.menupopup;
+ if (popup && 0 <= val) {
+ if (val < popup.childNodes.length)
+ this.selectedItem = popup.childNodes[val];
+ }
+ else
+ this.selectedItem = null;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedItem">
+ <getter>
+ <![CDATA[
+ return this.mSelectedInternal;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ var oldval = this.mSelectedInternal;
+ if (oldval == val)
+ return val;
+
+ if (val && !this.contains(val))
+ return val;
+
+ if (oldval) {
+ oldval.removeAttribute('selected');
+ this.mAttributeObserver.disconnect();
+ }
+
+ this.mSelectedInternal = val;
+ let attributeFilter = ["value", "label", "image", "description"];
+ if (val) {
+ val.setAttribute('selected', 'true');
+ for (let attr of attributeFilter) {
+ if (val.hasAttribute(attr)) {
+ this.setAttribute(attr, val.getAttribute(attr));
+ }
+ else {
+ this.removeAttribute(attr);
+ }
+ }
+
+ this.mAttributeObserver = new MutationObserver(this.handleMutation.bind(this));
+ this.mAttributeObserver.observe(val, { attributeFilter });
+ }
+ else {
+ for (let attr of attributeFilter) {
+ this.removeAttribute(attr);
+ }
+ }
+
+ var event = document.createEvent("Events");
+ event.initEvent("select", true, true);
+ this.dispatchEvent(event);
+
+ event = document.createEvent("Events");
+ event.initEvent("ValueChange", true, true);
+ this.dispatchEvent(event);
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="handleMutation">
+ <parameter name="aRecords"/>
+ <body>
+ <![CDATA[
+ for (let record of aRecords) {
+ let t = record.target;
+ if (t == this.mSelectedInternal) {
+ let attrName = record.attributeName;
+ switch (attrName) {
+ case "value":
+ case "label":
+ case "image":
+ case "description":
+ if (t.hasAttribute(attrName)) {
+ this.setAttribute(attrName, t.getAttribute(attrName));
+ }
+ else {
+ this.removeAttribute(attrName);
+ }
+ }
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="getIndexOfItem">
+ <parameter name="item"/>
+ <body>
+ <![CDATA[
+ var popup = this.menupopup;
+ if (popup) {
+ var children = popup.childNodes;
+ var i = children.length;
+ while (i--)
+ if (children[i] == item)
+ return i;
+ }
+ return -1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getItemAtIndex">
+ <parameter name="index"/>
+ <body>
+ <![CDATA[
+ var popup = this.menupopup;
+ if (popup) {
+ var children = popup.childNodes;
+ if (index >= 0 && index < children.length)
+ return children[index];
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="appendItem">
+ <parameter name="label"/>
+ <parameter name="value"/>
+ <parameter name="description"/>
+ <body>
+ <![CDATA[
+ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var popup = this.menupopup ||
+ this.appendChild(document.createElementNS(XULNS, "menupopup"));
+ var item = document.createElementNS(XULNS, "menuitem");
+ item.setAttribute("label", label);
+ item.setAttribute("value", value);
+ if (description)
+ item.setAttribute("description", description);
+
+ popup.appendChild(item);
+ return item;
+ ]]>
+ </body>
+ </method>
+
+ <method name="insertItemAt">
+ <parameter name="index"/>
+ <parameter name="label"/>
+ <parameter name="value"/>
+ <parameter name="description"/>
+ <body>
+ <![CDATA[
+ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var popup = this.menupopup ||
+ this.appendChild(document.createElementNS(XULNS, "menupopup"));
+ var item = document.createElementNS(XULNS, "menuitem");
+ item.setAttribute("label", label);
+ item.setAttribute("value", value);
+ if (description)
+ item.setAttribute("description", description);
+
+ if (index >= 0 && index < popup.childNodes.length)
+ popup.insertBefore(item, popup.childNodes[index]);
+ else
+ popup.appendChild(item);
+ return item;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeItemAt">
+ <parameter name="index"/>
+ <body>
+ <![CDATA[
+ var popup = this.menupopup;
+ if (popup && 0 <= index && index < popup.childNodes.length) {
+ var remove = popup.childNodes[index];
+ popup.removeChild(remove);
+ return remove;
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeAllItems">
+ <body>
+ <![CDATA[
+ this.selectedItem = null;
+ var popup = this.menupopup;
+ if (popup)
+ this.removeChild(popup);
+ ]]>
+ </body>
+ </method>
+
+ <destructor>
+ <![CDATA[
+ if (this.mAttributeObserver) {
+ this.mAttributeObserver.disconnect();
+ }
+ ]]>
+ </destructor>
+ </implementation>
+ </binding>
+
+ <binding id="menulist-editable" extends="chrome://global/content/bindings/menulist.xml#menulist">
+ <content sizetopopup="pref">
+ <xul:hbox class="menulist-editable-box textbox-input-box" xbl:inherits="context,disabled,readonly,focused" flex="1">
+ <html:input class="menulist-editable-input" anonid="input" allowevents="true"
+ xbl:inherits="value=label,value,disabled,tabindex,readonly,placeholder"/>
+ </xul:hbox>
+ <xul:dropmarker class="menulist-dropmarker" type="menu"
+ xbl:inherits="open,disabled,parentfocused=focused"/>
+ <children includes="menupopup"/>
+ </content>
+
+ <implementation>
+ <method name="_selectInputFieldValueInList">
+ <body>
+ <![CDATA[
+ if (this.hasAttribute("disableautoselect"))
+ return;
+
+ // Find and select the menuitem that matches inputField's "value"
+ var arr = null;
+ var popup = this.menupopup;
+
+ if (popup)
+ arr = popup.getElementsByAttribute('label', this.inputField.value);
+
+ this.setSelectionInternal(arr ? arr.item(0) : null);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setSelectionInternal">
+ <parameter name="val"/>
+ <body>
+ <![CDATA[
+ // This is called internally to set selected item
+ // without triggering infinite loop
+ // when using selectedItem's setter
+ if (this.mSelectedInternal == val)
+ return val;
+
+ if (this.mSelectedInternal)
+ this.mSelectedInternal.removeAttribute('selected');
+
+ this.mSelectedInternal = val;
+
+ if (val)
+ val.setAttribute('selected', 'true');
+
+ // Do NOT change the "value", which is owned by inputField
+ return val;
+ ]]>
+ </body>
+ </method>
+
+ <property name="inputField" readonly="true">
+ <getter><![CDATA[
+ if (!this.mInputField)
+ this.mInputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
+ return this.mInputField;
+ ]]></getter>
+ </property>
+
+ <property name="label" onset="this.inputField.value = val; return val;"
+ onget="return this.inputField.value;"/>
+
+ <property name="value" onget="return this.inputField.value;">
+ <setter>
+ <![CDATA[
+ // Override menulist's value setter to refer to the inputField's value
+ // (Allows using "menulist.value" instead of "menulist.inputField.value")
+ this.inputField.value = val;
+ this.setAttribute('value', val);
+ this.setAttribute('label', val);
+ this._selectInputFieldValueInList();
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedItem">
+ <getter>
+ <![CDATA[
+ // Make sure internally-selected item
+ // is in sync with inputField.value
+ this._selectInputFieldValueInList();
+ return this.mSelectedInternal;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ var oldval = this.mSelectedInternal;
+ if (oldval == val)
+ return val;
+
+ if (val && !this.contains(val))
+ return val;
+
+ // This doesn't touch inputField.value or "value" and "label" attributes
+ this.setSelectionInternal(val);
+ if (val) {
+ // Editable menulist uses "label" as its "value"
+ var label = val.getAttribute('label');
+ this.inputField.value = label;
+ this.setAttribute('value', label);
+ this.setAttribute('label', label);
+ }
+ else {
+ this.inputField.value = "";
+ this.removeAttribute('value');
+ this.removeAttribute('label');
+ }
+
+ var event = document.createEvent("Events");
+ event.initEvent("select", true, true);
+ this.dispatchEvent(event);
+
+ event = document.createEvent("Events");
+ event.initEvent("ValueChange", true, true);
+ this.dispatchEvent(event);
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="disableautoselect"
+ onset="if (val) this.setAttribute('disableautoselect','true');
+ else this.removeAttribute('disableautoselect'); return val;"
+ onget="return this.hasAttribute('disableautoselect');"/>
+
+ <property name="editor" readonly="true">
+ <getter><![CDATA[
+ const nsIDOMNSEditableElement = Components.interfaces.nsIDOMNSEditableElement;
+ return this.inputField.QueryInterface(nsIDOMNSEditableElement).editor;
+ ]]></getter>
+ </property>
+
+ <property name="readOnly" onset="this.inputField.readOnly = val;
+ if (val) this.setAttribute('readonly', 'true');
+ else this.removeAttribute('readonly'); return val;"
+ onget="return this.inputField.readOnly;"/>
+
+ <method name="select">
+ <body>
+ this.inputField.select();
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="focus" phase="capturing">
+ <![CDATA[
+ this.setAttribute('focused', 'true');
+ ]]>
+ </handler>
+
+ <handler event="blur" phase="capturing">
+ <![CDATA[
+ this.removeAttribute('focused');
+ ]]>
+ </handler>
+
+ <handler event="popupshowing">
+ <![CDATA[
+ // editable menulists elements aren't in the focus order,
+ // so when the popup opens we need to force the focus to the inputField
+ if (event.target.parentNode == this) {
+ if (document.commandDispatcher.focusedElement != this.inputField)
+ this.inputField.focus();
+
+ this.menuBoxObject.activeChild = null;
+ if (this.selectedItem)
+ // Not ready for auto-setting the active child in hierarchies yet.
+ // For now, only do this when the outermost menupopup opens.
+ this.menuBoxObject.activeChild = this.mSelectedInternal;
+ }
+ ]]>
+ </handler>
+
+ <handler event="keypress">
+ <![CDATA[
+ // open popup if key is up arrow, down arrow, or F4
+ if (!event.ctrlKey && !event.shiftKey) {
+ if (event.keyCode == KeyEvent.DOM_VK_UP ||
+ event.keyCode == KeyEvent.DOM_VK_DOWN ||
+ (event.keyCode == KeyEvent.DOM_VK_F4 && !event.altKey)) {
+ event.preventDefault();
+ this.open = true;
+ }
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="menulist-description" display="xul:menu"
+ extends="chrome://global/content/bindings/menulist.xml#menulist">
+ <content sizetopopup="pref">
+ <xul:hbox class="menulist-label-box" flex="1">
+ <xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
+ <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
+ <xul:label class="menulist-label menulist-description" xbl:inherits="value=description" crop="right" flex="10000"/>
+ </xul:hbox>
+ <xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled,open"/>
+ <children includes="menupopup"/>
+ </content>
+ </binding>
+
+ <binding id="menulist-popuponly" display="xul:menu"
+ extends="chrome://global/content/bindings/menulist.xml#menulist">
+ <content sizetopopup="pref">
+ <children includes="menupopup"/>
+ </content>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/notification.xml b/components/bindings/content/notification.xml
new file mode 100644
index 000000000..2cc2f4b2c
--- /dev/null
+++ b/components/bindings/content/notification.xml
@@ -0,0 +1,551 @@
+<?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">
+ <content>
+ <xul:stack xbl:inherits="hidden=notificationshidden"
+ class="notificationbox-stack">
+ <xul:spacer/>
+ <children includes="notification"/>
+ </xul:stack>
+ <children/>
+ </content>
+
+ <implementation>
+ <field name="PRIORITY_INFO_LOW" readonly="true">1</field>
+ <field name="PRIORITY_INFO_MEDIUM" readonly="true">2</field>
+ <field name="PRIORITY_INFO_HIGH" readonly="true">3</field>
+ <field name="PRIORITY_WARNING_LOW" readonly="true">4</field>
+ <field name="PRIORITY_WARNING_MEDIUM" readonly="true">5</field>
+ <field name="PRIORITY_WARNING_HIGH" readonly="true">6</field>
+ <field name="PRIORITY_CRITICAL_LOW" readonly="true">7</field>
+ <field name="PRIORITY_CRITICAL_MEDIUM" readonly="true">8</field>
+ <field name="PRIORITY_CRITICAL_HIGH" readonly="true">9</field>
+ <field name="PRIORITY_CRITICAL_BLOCK" readonly="true">10</field>
+
+ <field name="currentNotification">null</field>
+
+ <field name="_closedNotification">null</field>
+ <field name="_blockingCanvas">null</field>
+ <field name="_animating">false</field>
+
+ <property name="notificationsHidden"
+ onget="return this.getAttribute('notificationshidden') == 'true';">
+ <setter>
+ if (val)
+ this.setAttribute('notificationshidden', true);
+ else this.removeAttribute('notificationshidden');
+ return val;
+ </setter>
+ </property>
+
+ <property name="allNotifications" readonly="true">
+ <getter>
+ <![CDATA[
+ var closedNotification = this._closedNotification;
+ var notifications = this.getElementsByTagName('notification');
+ return Array.filter(notifications, n => n != closedNotification);
+ ]]>
+ </getter>
+ </property>
+
+ <method name="getNotificationWithValue">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var notifications = this.allNotifications;
+ for (var n = notifications.length - 1; n >= 0; n--) {
+ if (aValue == notifications[n].getAttribute("value"))
+ return notifications[n];
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="appendNotification">
+ <parameter name="aLabel"/>
+ <parameter name="aValue"/>
+ <parameter name="aImage"/>
+ <parameter name="aPriority"/>
+ <parameter name="aButtons"/>
+ <parameter name="aEventCallback"/>
+ <body>
+ <![CDATA[
+ if (aPriority < this.PRIORITY_INFO_LOW ||
+ aPriority > this.PRIORITY_CRITICAL_BLOCK)
+ throw "Invalid notification priority " + aPriority;
+
+ // check for where the notification should be inserted according to
+ // priority. If two are equal, the existing one appears on top.
+ var notifications = this.allNotifications;
+ var insertPos = null;
+ for (var n = notifications.length - 1; n >= 0; n--) {
+ if (notifications[n].priority < aPriority)
+ break;
+ insertPos = notifications[n];
+ }
+
+ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var newitem = document.createElementNS(XULNS, "notification");
+ // Can't use instanceof in case this was created from a different document:
+ let labelIsDocFragment = aLabel && typeof aLabel == "object" && aLabel.nodeType &&
+ aLabel.nodeType == aLabel.DOCUMENT_FRAGMENT_NODE;
+ if (!labelIsDocFragment)
+ newitem.setAttribute("label", aLabel);
+ newitem.setAttribute("value", aValue);
+ if (aImage)
+ newitem.setAttribute("image", aImage);
+ newitem.eventCallback = aEventCallback;
+
+ if (aButtons) {
+ // The notification-button-default class is added to the button
+ // with isDefault set to true. If there is no such button, it is
+ // added to the first button (unless that button has isDefault
+ // set to false). There cannot be multiple default buttons.
+ var defaultElem;
+
+ for (var b = 0; b < aButtons.length; b++) {
+ var button = aButtons[b];
+ var buttonElem = document.createElementNS(XULNS, "button");
+ buttonElem.setAttribute("label", button.label);
+ if (typeof button.accessKey == "string")
+ buttonElem.setAttribute("accesskey", button.accessKey);
+ if (typeof button.type == "string") {
+ buttonElem.setAttribute("type", button.type);
+ if ((button.type == "menu-button" || button.type == "menu") &&
+ "popup" in button) {
+ buttonElem.appendChild(button.popup);
+ delete button.popup;
+ }
+ if (typeof button.anchor == "string")
+ buttonElem.setAttribute("anchor", button.anchor);
+ }
+ buttonElem.classList.add("notification-button");
+
+ if (button.isDefault ||
+ b == 0 && !("isDefault" in button))
+ defaultElem = buttonElem;
+
+ newitem.appendChild(buttonElem);
+ buttonElem.buttonInfo = button;
+ }
+
+ if (defaultElem)
+ defaultElem.classList.add("notification-button-default");
+ }
+
+ newitem.setAttribute("priority", aPriority);
+ if (aPriority >= this.PRIORITY_CRITICAL_LOW)
+ newitem.setAttribute("type", "critical");
+ else if (aPriority <= this.PRIORITY_INFO_HIGH)
+ newitem.setAttribute("type", "info");
+ else
+ newitem.setAttribute("type", "warning");
+
+ if (!insertPos) {
+ newitem.style.position = "fixed";
+ newitem.style.top = "100%";
+ newitem.style.marginTop = "-15px";
+ newitem.style.opacity = "0";
+ }
+ this.insertBefore(newitem, insertPos);
+ // Can only insert the document fragment after the item has been created because
+ // otherwise the XBL structure isn't there yet:
+ if (labelIsDocFragment) {
+ document.getAnonymousElementByAttribute(newitem, "anonid", "messageText")
+ .appendChild(aLabel);
+ }
+
+ if (!insertPos)
+ this._showNotification(newitem, true);
+
+ // Fire event for accessibility APIs
+ var event = document.createEvent("Events");
+ event.initEvent("AlertActive", true, true);
+ newitem.dispatchEvent(event);
+
+ return newitem;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeNotification">
+ <parameter name="aItem"/>
+ <parameter name="aSkipAnimation"/>
+ <body>
+ <![CDATA[
+ if (aItem == this.currentNotification)
+ this.removeCurrentNotification(aSkipAnimation);
+ else if (aItem != this._closedNotification)
+ this._removeNotificationElement(aItem);
+ return aItem;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_removeNotificationElement">
+ <parameter name="aChild"/>
+ <body>
+ <![CDATA[
+ if (aChild.eventCallback)
+ aChild.eventCallback("removed");
+ this.removeChild(aChild);
+
+ // make sure focus doesn't get lost (workaround for bug 570835)
+ let fm = Components.classes["@mozilla.org/focus-manager;1"]
+ .getService(Components.interfaces.nsIFocusManager);
+ if (!fm.getFocusedElementForWindow(window, false, {}))
+ fm.moveFocus(window, this, fm.MOVEFOCUS_FORWARD, 0);
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeCurrentNotification">
+ <parameter name="aSkipAnimation"/>
+ <body>
+ <![CDATA[
+ this._showNotification(this.currentNotification, false, aSkipAnimation);
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeAllNotifications">
+ <parameter name="aImmediate"/>
+ <body>
+ <![CDATA[
+ var notifications = this.allNotifications;
+ for (var n = notifications.length - 1; n >= 0; n--) {
+ if (aImmediate)
+ this._removeNotificationElement(notifications[n]);
+ else
+ this.removeNotification(notifications[n]);
+ }
+ this.currentNotification = null;
+
+ // Must clear up any currently animating notification
+ if (aImmediate)
+ this._finishAnimation();
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeTransientNotifications">
+ <body>
+ <![CDATA[
+ var notifications = this.allNotifications;
+ for (var n = notifications.length - 1; n >= 0; n--) {
+ var notification = notifications[n];
+ if (notification.persistence)
+ notification.persistence--;
+ else if (Date.now() > notification.timeout)
+ this.removeNotification(notification);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_showNotification">
+ <parameter name="aNotification"/>
+ <parameter name="aSlideIn"/>
+ <parameter name="aSkipAnimation"/>
+ <body>
+ <![CDATA[
+ this._finishAnimation();
+
+ var height = aNotification.boxObject.height;
+ var skipAnimation = aSkipAnimation || (height == 0);
+
+ if (aSlideIn) {
+ this.currentNotification = aNotification;
+ aNotification.style.removeProperty("position");
+ aNotification.style.removeProperty("top");
+ aNotification.style.removeProperty("margin-top");
+ aNotification.style.removeProperty("opacity");
+
+ if (skipAnimation) {
+ this._setBlockingState(this.currentNotification);
+ return;
+ }
+ }
+ else {
+ this._closedNotification = aNotification;
+ var notifications = this.allNotifications;
+ var idx = notifications.length - 1;
+ this.currentNotification = (idx >= 0) ? notifications[idx] : null;
+
+ if (skipAnimation) {
+ this._removeNotificationElement(this._closedNotification);
+ this._closedNotification = null;
+ this._setBlockingState(this.currentNotification);
+ return;
+ }
+
+ aNotification.style.marginTop = -height + "px";
+ aNotification.style.opacity = 0;
+ }
+
+ this._animating = true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_finishAnimation">
+ <body><![CDATA[
+ if (this._animating) {
+ this._animating = false;
+ if (this._closedNotification) {
+ this._removeNotificationElement(this._closedNotification);
+ this._closedNotification = null;
+ }
+ this._setBlockingState(this.currentNotification);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_setBlockingState">
+ <parameter name="aNotification"/>
+ <body>
+ <![CDATA[
+ var isblock = aNotification &&
+ aNotification.priority == this.PRIORITY_CRITICAL_BLOCK;
+ var canvas = this._blockingCanvas;
+ if (isblock) {
+ if (!canvas)
+ canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let content = this.firstChild;
+ if (!content ||
+ content.namespaceURI != XULNS ||
+ content.localName != "browser")
+ return;
+
+ var width = content.boxObject.width;
+ var height = content.boxObject.height;
+ content.collapsed = true;
+
+ canvas.setAttribute("width", width);
+ canvas.setAttribute("height", height);
+ canvas.setAttribute("flex", "1");
+
+ this.appendChild(canvas);
+ this._blockingCanvas = canvas;
+
+ var bgcolor = "white";
+ try {
+ var prefService = Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+ bgcolor = prefService.getCharPref("browser.display.background_color");
+
+ var win = content.contentWindow;
+ var context = canvas.getContext("2d");
+ context.globalAlpha = 0.5;
+ context.drawWindow(win, win.scrollX, win.scrollY,
+ width, height, bgcolor);
+ }
+ catch (ex) { }
+ }
+ else if (canvas) {
+ canvas.parentNode.removeChild(canvas);
+ this._blockingCanvas = null;
+ let content = this.firstChild;
+ if (content)
+ content.collapsed = false;
+ }
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="transitionend"><![CDATA[
+ if (event.target.localName == "notification" &&
+ event.propertyName == "margin-top")
+ this._finishAnimation();
+ ]]></handler>
+ </handlers>
+
+ </binding>
+
+ <binding id="notification" role="xul:alert">
+ <content>
+ <xul:hbox class="notification-inner" flex="1" xbl:inherits="type">
+ <xul:hbox anonid="details" align="center" flex="1"
+ oncommand="this.parentNode.parentNode._doButtonCommand(event);">
+ <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type,value"/>
+ <xul:description anonid="messageText" class="messageText" flex="1" xbl:inherits="xbl:text=label"/>
+ <xul:spacer flex="1"/>
+ <children/>
+ </xul:hbox>
+ <xul:toolbarbutton ondblclick="event.stopPropagation();"
+ class="messageCloseButton close-icon tabbable"
+ xbl:inherits="hidden=hideclose"
+ tooltiptext="&closeNotification.tooltip;"
+ oncommand="document.getBindingParent(this).dismiss();"/>
+ </xul:hbox>
+ </content>
+ <resources>
+ <stylesheet src="chrome://global/skin/notification.css"/>
+ </resources>
+ <implementation>
+ <property name="label" onset="this.setAttribute('label', val); return val;"
+ onget="return this.getAttribute('label');"/>
+ <property name="value" onset="this.setAttribute('value', val); return val;"
+ onget="return this.getAttribute('value');"/>
+ <property name="image" onset="this.setAttribute('image', val); return val;"
+ onget="return this.getAttribute('image');"/>
+ <property name="type" onset="this.setAttribute('type', val); return val;"
+ onget="return this.getAttribute('type');"/>
+ <property name="priority" onget="return parseInt(this.getAttribute('priority')) || 0;"
+ onset="this.setAttribute('priority', val); return val;"/>
+ <property name="persistence" onget="return parseInt(this.getAttribute('persistence')) || 0;"
+ onset="this.setAttribute('persistence', val); return val;"/>
+ <field name="timeout">0</field>
+
+ <property name="control" readonly="true">
+ <getter>
+ <![CDATA[
+ var parent = this.parentNode;
+ while (parent) {
+ if (parent.localName == "notificationbox")
+ return parent;
+ parent = parent.parentNode;
+ }
+ return null;
+ ]]>
+ </getter>
+ </property>
+
+ <!-- This method should only be called when the user has
+ manually closed the notification. If you want to
+ programmatically close the notification, you should
+ call close() instead. -->
+ <method name="dismiss">
+ <body>
+ <![CDATA[
+ if (this.eventCallback) {
+ this.eventCallback("dismissed");
+ }
+ this.close();
+ ]]>
+ </body>
+ </method>
+
+ <method name="close">
+ <body>
+ <![CDATA[
+ var control = this.control;
+ if (control)
+ control.removeNotification(this);
+ else
+ this.hidden = true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_doButtonCommand">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (!("buttonInfo" in aEvent.target))
+ return;
+
+ var button = aEvent.target.buttonInfo;
+ if (button.popup) {
+ document.getElementById(button.popup).
+ openPopup(aEvent.originalTarget, "after_start", 0, 0, false, false, aEvent);
+ aEvent.stopPropagation();
+ }
+ else {
+ var callback = button.callback;
+ if (callback) {
+ var result = callback(this, button, aEvent.target);
+ if (!result)
+ this.close();
+ aEvent.stopPropagation();
+ }
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="popup-notification">
+ <content>
+ <xul:vbox>
+ <xul:image class="popup-notification-icon"
+ xbl:inherits="popupid,src=icon,class=iconclass"/>
+ </xul:vbox>
+ <xul:vbox class="popup-notification-body" xbl:inherits="popupid">
+ <xul:hbox align="start">
+ <xul:vbox flex="1">
+ <xul:label class="popup-notification-origin header"
+ xbl:inherits="value=origin,tooltiptext=origin"
+ crop="center"/>
+ <xul:description class="popup-notification-description"
+ xbl:inherits="xbl:text=label,popupid"/>
+ </xul:vbox>
+ <xul:toolbarbutton anonid="closebutton"
+ class="messageCloseButton close-icon popup-notification-closebutton tabbable"
+ xbl:inherits="oncommand=closebuttoncommand"
+ tooltiptext="&closeNotification.tooltip;"/>
+ </xul:hbox>
+ <children includes="popupnotificationcontent"/>
+ <xul:label class="text-link popup-notification-learnmore-link"
+ xbl:inherits="onclick=learnmoreclick,href=learnmoreurl">&learnMore;</xul:label>
+ <xul:checkbox anonid="checkbox"
+ xbl:inherits="hidden=checkboxhidden,checked=checkboxchecked,label=checkboxlabel,oncommand=checkboxcommand" />
+ <xul:description class="popup-notification-warning" xbl:inherits="hidden=warninghidden,xbl:text=warninglabel"/>
+ <xul:spacer flex="1"/>
+ <xul:hbox class="popup-notification-button-container"
+ pack="end" align="center">
+ <children includes="button"/>
+ <xul:button anonid="button"
+ class="popup-notification-menubutton"
+ type="menu-button"
+ xbl:inherits="oncommand=buttoncommand,onpopupshown=buttonpopupshown,label=buttonlabel,accesskey=buttonaccesskey,disabled=mainactiondisabled">
+ <xul:menupopup anonid="menupopup"
+ xbl:inherits="oncommand=menucommand">
+ <children/>
+ <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
+ label="&closeNotificationItem.label;"
+ xbl:inherits="oncommand=closeitemcommand,hidden=hidenotnow"/>
+ </xul:menupopup>
+ </xul:button>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ <resources>
+ <stylesheet src="chrome://global/skin/notification.css"/>
+ </resources>
+ <implementation>
+ <field name="checkbox" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "checkbox");
+ </field>
+ <field name="closebutton" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "closebutton");
+ </field>
+ <field name="button" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "button");
+ </field>
+ <field name="menupopup" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "menupopup");
+ </field>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/numberbox.xml b/components/bindings/content/numberbox.xml
new file mode 100644
index 000000000..0e1225f09
--- /dev/null
+++ b/components/bindings/content/numberbox.xml
@@ -0,0 +1,304 @@
+<?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="numberboxBindings"
+ 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="numberbox"
+ extends="chrome://global/content/bindings/textbox.xml#textbox">
+
+ <resources>
+ <stylesheet src="chrome://global/skin/numberbox.css"/>
+ </resources>
+
+ <content>
+ <xul:hbox class="textbox-input-box numberbox-input-box" flex="1" xbl:inherits="context,disabled,focused">
+ <html:input class="numberbox-input textbox-input" anonid="input"
+ xbl:inherits="value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
+ </xul:hbox>
+ <xul:spinbuttons anonid="buttons" xbl:inherits="disabled,hidden=hidespinbuttons"/>
+ </content>
+
+ <implementation>
+ <field name="_valueEntered">false</field>
+ <field name="_spinButtons">null</field>
+ <field name="_value">0</field>
+ <field name="decimalSymbol">"."</field>
+
+ <property name="spinButtons" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._spinButtons)
+ this._spinButtons = document.getAnonymousElementByAttribute(this, "anonid", "buttons");
+ return this._spinButtons;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="value" onget="return '' + this.valueNumber"
+ onset="return this.valueNumber = val;"/>
+
+ <property name="valueNumber">
+ <getter>
+ if (this._valueEntered) {
+ var newval = this.inputField.value;
+ newval = newval.replace(this.decimalSymbol, ".");
+ this._validateValue(newval, false);
+ }
+ return this._value;
+ </getter>
+ <setter>
+ this._validateValue(val, false);
+ return val;
+ </setter>
+ </property>
+
+ <property name="wrapAround">
+ <getter>
+ <![CDATA[
+ return (this.getAttribute('wraparound') == 'true')
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val)
+ this.setAttribute('wraparound', 'true');
+ else
+ this.removeAttribute('wraparound');
+ this._enableDisableButtons();
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="min">
+ <getter>
+ var min = this.getAttribute("min");
+ return min ? Number(min) : 0;
+ </getter>
+ <setter>
+ <![CDATA[
+ if (typeof val == "number") {
+ this.setAttribute("min", val);
+ if (this.valueNumber < val)
+ this._validateValue(val, false);
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="max">
+ <getter>
+ var max = this.getAttribute("max");
+ return max ? Number(max) : Infinity;
+ </getter>
+ <setter>
+ <![CDATA[
+ if (typeof val != "number")
+ return val;
+ var min = this.min;
+ if (val < min)
+ val = min;
+ this.setAttribute("max", val);
+ if (this.valueNumber > val)
+ this._validateValue(val, false);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="decimalPlaces">
+ <getter>
+ var places = this.getAttribute("decimalplaces");
+ return places ? Number(places) : 0;
+ </getter>
+ <setter>
+ if (typeof val == "number") {
+ this.setAttribute("decimalplaces", val);
+ this._validateValue(this.valueNumber, false);
+ }
+ return val;
+ </setter>
+ </property>
+
+ <property name="increment">
+ <getter>
+ var increment = this.getAttribute("increment");
+ return increment ? Number(increment) : 1;
+ </getter>
+ <setter>
+ <![CDATA[
+ if (typeof val == "number")
+ this.setAttribute("increment", val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="decrease">
+ <body>
+ return this._validateValue(this.valueNumber - this.increment, true);
+ </body>
+ </method>
+
+ <method name="increase">
+ <body>
+ return this._validateValue(this.valueNumber + this.increment, true);
+ </body>
+ </method>
+
+ <method name="_modifyUp">
+ <body>
+ <![CDATA[
+ if (this.disabled || this.readOnly)
+ return;
+ var oldval = this.valueNumber;
+ var newval = this.increase();
+ this.inputField.select();
+ if (oldval != newval)
+ this._fireChange();
+ ]]>
+ </body>
+ </method>
+ <method name="_modifyDown">
+ <body>
+ <![CDATA[
+ if (this.disabled || this.readOnly)
+ return;
+ var oldval = this.valueNumber;
+ var newval = this.decrease();
+ this.inputField.select();
+ if (oldval != newval)
+ this._fireChange();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_enableDisableButtons">
+ <body>
+ <![CDATA[
+ var buttons = this.spinButtons;
+ if (this.wrapAround) {
+ buttons.decreaseDisabled = buttons.increaseDisabled = false;
+ }
+ else if (this.disabled || this.readOnly) {
+ buttons.decreaseDisabled = buttons.increaseDisabled = true;
+ }
+ else {
+ buttons.decreaseDisabled = (this.valueNumber <= this.min);
+ buttons.increaseDisabled = (this.valueNumber >= this.max);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_validateValue">
+ <parameter name="aValue"/>
+ <parameter name="aIsIncDec"/>
+ <body>
+ <![CDATA[
+ aValue = Number(aValue) || 0;
+
+ var min = this.min;
+ var max = this.max;
+ var wrapAround = this.wrapAround &&
+ min != -Infinity && max != Infinity;
+ if (aValue < min)
+ aValue = (aIsIncDec && wrapAround ? max : min);
+ else if (aValue > max)
+ aValue = (aIsIncDec && wrapAround ? min : max);
+
+ var places = this.decimalPlaces;
+ aValue = (places == Infinity) ? "" + aValue : aValue.toFixed(places);
+
+ this._valueEntered = false;
+ this._value = Number(aValue);
+ this.inputField.value = aValue.replace(/\./, this.decimalSymbol);
+
+ if (!wrapAround)
+ this._enableDisableButtons();
+
+ return aValue;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_fireChange">
+ <body>
+ var evt = document.createEvent("Events");
+ evt.initEvent("change", true, true);
+ this.dispatchEvent(evt);
+ </body>
+ </method>
+
+ <constructor><![CDATA[
+ if (this.max < this.min)
+ this.max = this.min;
+
+ var dsymbol = (Number(5.4)).toLocaleString().match(/\D/);
+ if (dsymbol != null)
+ this.decimalSymbol = dsymbol[0];
+
+ var value = this.inputField.value || 0;
+ this._validateValue(value, false);
+ ]]></constructor>
+
+ </implementation>
+
+ <handlers>
+ <handler event="input" phase="capturing">
+ this._valueEntered = true;
+ </handler>
+
+ <handler event="keypress">
+ <![CDATA[
+ if (!event.ctrlKey && !event.metaKey && !event.altKey && event.charCode) {
+ if (event.charCode == this.decimalSymbol.charCodeAt(0) &&
+ this.decimalPlaces &&
+ String(this.inputField.value).indexOf(this.decimalSymbol) == -1)
+ return;
+
+ if (event.charCode == 45 && this.min < 0)
+ return;
+
+ if (event.charCode < 48 || event.charCode > 57)
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+
+ <handler event="keypress" keycode="VK_UP">
+ this._modifyUp();
+ </handler>
+
+ <handler event="keypress" keycode="VK_DOWN">
+ this._modifyDown();
+ </handler>
+
+ <handler event="up" preventdefault="true">
+ this._modifyUp();
+ </handler>
+
+ <handler event="down" preventdefault="true">
+ this._modifyDown();
+ </handler>
+
+ <handler event="change">
+ if (event.originalTarget == this.inputField) {
+ var newval = this.inputField.value;
+ newval = newval.replace(this.decimalSymbol, ".");
+ this._validateValue(newval, false);
+ }
+ </handler>
+ </handlers>
+
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/optionsDialog.xml b/components/bindings/content/optionsDialog.xml
new file mode 100644
index 000000000..efd20663e
--- /dev/null
+++ b/components/bindings/content/optionsDialog.xml
@@ -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/. -->
+
+
+<bindings id="optionsDialogBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="optionsDialog"
+ extends="chrome://global/content/bindings/dialog.xml#dialog">
+ <content>
+ <xul:hbox flex="1">
+ <xul:categoryBox anonid="prefsCategories">
+ <children/>
+ </xul:categoryBox>
+ <xul:vbox flex="1">
+ <xul:dialogheader id="panelHeader"/>
+ <xul:iframe anonid="panelFrame" name="panelFrame" style="width: 0px;" flex="1"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+
+
+ </implementation>
+
+ </binding>
+
+</bindings> \ No newline at end of file
diff --git a/components/bindings/content/popup.xml b/components/bindings/content/popup.xml
new file mode 100644
index 000000000..c8a395c40
--- /dev/null
+++ b/components/bindings/content/popup.xml
@@ -0,0 +1,663 @@
+<?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="popupBindings"
+ 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="popup-base">
+ <resources>
+ <stylesheet src="chrome://global/skin/popup.css"/>
+ </resources>
+
+ <implementation implements="nsIDOMXULPopupElement">
+ <property name="label" onget="return this.getAttribute('label');"
+ onset="this.setAttribute('label', val); return val;"/>
+ <property name="position" onget="return this.getAttribute('position');"
+ onset="this.setAttribute('position', val); return val;"/>
+ <property name="popupBoxObject">
+ <getter>
+ return this.boxObject;
+ </getter>
+ </property>
+
+#ifdef MOZ_WIDGET_GTK
+ <property name="state" readonly="true">
+ <getter>
+ <![CDATA[
+ if (this.hasAttribute('_moz-nativemenupopupstate'))
+ return this.getAttribute('_moz-nativemenupopupstate');
+ else
+ return this.popupBoxObject.popupState;
+ ]]>
+ </getter>
+ </property>
+#else
+ <property name="state" readonly="true"
+ onget="return this.popupBoxObject.popupState"/>
+#endif
+
+ <property name="triggerNode" readonly="true"
+ onget="return this.popupBoxObject.triggerNode"/>
+
+ <property name="anchorNode" readonly="true"
+ onget="return this.popupBoxObject.anchorNode"/>
+
+ <method name="openPopup">
+ <parameter name="aAnchorElement"/>
+ <parameter name="aPosition"/>
+ <parameter name="aX"/>
+ <parameter name="aY"/>
+ <parameter name="aIsContextMenu"/>
+ <parameter name="aAttributesOverride"/>
+ <parameter name="aTriggerEvent"/>
+ <body>
+ <![CDATA[
+ try {
+ var popupBox = this.popupBoxObject;
+ if (popupBox)
+ popupBox.openPopup(aAnchorElement, aPosition, aX, aY,
+ aIsContextMenu, aAttributesOverride, aTriggerEvent);
+ } catch (e) {}
+ ]]>
+ </body>
+ </method>
+
+ <method name="openPopupAtScreen">
+ <parameter name="aX"/>
+ <parameter name="aY"/>
+ <parameter name="aIsContextMenu"/>
+ <parameter name="aTriggerEvent"/>
+ <body>
+ <![CDATA[
+ try {
+ var popupBox = this.popupBoxObject;
+ if (popupBox)
+ popupBox.openPopupAtScreen(aX, aY, aIsContextMenu, aTriggerEvent);
+ } catch (e) {}
+ ]]>
+ </body>
+ </method>
+
+ <method name="openPopupAtScreenRect">
+ <parameter name="aPosition"/>
+ <parameter name="aX"/>
+ <parameter name="aY"/>
+ <parameter name="aWidth"/>
+ <parameter name="aHeight"/>
+ <parameter name="aIsContextMenu"/>
+ <parameter name="aAttributesOverride"/>
+ <parameter name="aTriggerEvent"/>
+ <body>
+ <![CDATA[
+ try {
+ var popupBox = this.popupBoxObject;
+ if (popupBox)
+ popupBox.openPopupAtScreenRect(aPosition, aX, aY, aWidth, aHeight,
+ aIsContextMenu, aAttributesOverride, aTriggerEvent);
+ } catch (e) {}
+ ]]>
+ </body>
+ </method>
+
+ <method name="showPopup">
+ <parameter name="element"/>
+ <parameter name="xpos"/>
+ <parameter name="ypos"/>
+ <parameter name="popuptype"/>
+ <parameter name="anchoralignment"/>
+ <parameter name="popupalignment"/>
+ <body>
+ <![CDATA[
+ var popupBox = null;
+ var menuBox = null;
+ try {
+ popupBox = this.popupBoxObject;
+ } catch (e) {}
+ try {
+ menuBox = this.parentNode.boxObject;
+ } catch (e) {}
+ if (menuBox instanceof MenuBoxObject)
+ menuBox.openMenu(true);
+ else if (popupBox)
+ popupBox.showPopup(element, this, xpos, ypos, popuptype, anchoralignment, popupalignment);
+ ]]>
+ </body>
+ </method>
+
+ <method name="hidePopup">
+ <parameter name="cancel"/>
+ <body>
+ <![CDATA[
+ var popupBox = null;
+ var menuBox = null;
+ try {
+ popupBox = this.popupBoxObject;
+ } catch (e) {}
+ try {
+ menuBox = this.parentNode.boxObject;
+ } catch (e) {}
+ if (menuBox instanceof MenuBoxObject)
+ menuBox.openMenu(false);
+ else if (popupBox instanceof PopupBoxObject)
+ popupBox.hidePopup(cancel);
+ ]]>
+ </body>
+ </method>
+
+ <property name="autoPosition">
+ <getter>
+ <![CDATA[
+ return this.popupBoxObject.autoPosition;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ return this.popupBoxObject.autoPosition = val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="alignmentPosition" readonly="true">
+ <getter>
+ <![CDATA[
+ return this.popupBoxObject.alignmentPosition;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="alignmentOffset" readonly="true">
+ <getter>
+ <![CDATA[
+ return this.popupBoxObject.alignmentOffset;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="enableKeyboardNavigator">
+ <parameter name="aEnableKeyboardNavigator"/>
+ <body>
+ <![CDATA[
+ this.popupBoxObject.enableKeyboardNavigator(aEnableKeyboardNavigator);
+ ]]>
+ </body>
+ </method>
+
+ <method name="enableRollup">
+ <parameter name="aEnableRollup"/>
+ <body>
+ <![CDATA[
+ this.popupBoxObject.enableRollup(aEnableRollup);
+ ]]>
+ </body>
+ </method>
+
+ <method name="sizeTo">
+ <parameter name="aWidth"/>
+ <parameter name="aHeight"/>
+ <body>
+ <![CDATA[
+ this.popupBoxObject.sizeTo(aWidth, aHeight);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTo">
+ <parameter name="aLeft"/>
+ <parameter name="aTop"/>
+ <body>
+ <![CDATA[
+ this.popupBoxObject.moveTo(aLeft, aTop);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveToAnchor">
+ <parameter name="aAnchorElement"/>
+ <parameter name="aPosition"/>
+ <parameter name="aX"/>
+ <parameter name="aY"/>
+ <parameter name="aAttributesOverride"/>
+ <body>
+ <![CDATA[
+ this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getOuterScreenRect">
+ <body>
+ <![CDATA[
+ return this.popupBoxObject.getOuterScreenRect();
+ ]]>
+ </body>
+ </method>
+
+ <method name="setConstraintRect">
+ <parameter name="aRect"/>
+ <body>
+ <![CDATA[
+ this.popupBoxObject.setConstraintRect(aRect);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ </binding>
+
+ <binding id="popup" role="xul:menupopup"
+ extends="chrome://global/content/bindings/popup.xml#popup-base">
+
+ <content>
+ <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
+ smoothscroll="false">
+ <children/>
+ </xul:arrowscrollbox>
+ </content>
+
+ <handlers>
+ <handler event="popupshowing" phase="target">
+ <![CDATA[
+ var array = [];
+ var width = 0;
+ for (var menuitem = this.firstChild; menuitem; menuitem = menuitem.nextSibling) {
+ if (menuitem.localName == "menuitem" && menuitem.hasAttribute("acceltext")) {
+ var accel = document.getAnonymousElementByAttribute(menuitem, "anonid", "accel");
+ if (accel && accel.boxObject) {
+ array.push(accel);
+ if (accel.boxObject.width > width)
+ width = accel.boxObject.width;
+ }
+ }
+ }
+ for (var i = 0; i < array.length; i++)
+ array[i].width = width;
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="panel" role="xul:panel"
+ extends="chrome://global/content/bindings/popup.xml#popup-base">
+ <implementation implements="nsIDOMXULPopupElement">
+ <field name="_prevFocus">0</field>
+ <field name="_dragBindingAlive">true</field>
+ <constructor>
+ <![CDATA[
+ if (this.getAttribute("backdrag") == "true" && !this._draggableStarted) {
+ this._draggableStarted = true;
+ try {
+ let tmp = {};
+ Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
+ let draghandle = new tmp.WindowDraggingElement(this);
+ draghandle.mouseDownCheck = function () {
+ return this._dragBindingAlive;
+ }
+ } catch (e) {}
+ }
+ ]]>
+ </constructor>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing"><![CDATA[
+ // Capture the previous focus before has a chance to get set inside the panel
+ try {
+ this._prevFocus = Components.utils
+ .getWeakReference(document.commandDispatcher.focusedElement);
+ if (this._prevFocus.get())
+ return;
+ } catch (ex) { }
+
+ this._prevFocus = Components.utils.getWeakReference(document.activeElement);
+ ]]></handler>
+ <handler event="popupshown"><![CDATA[
+ // Fire event for accessibility APIs
+ var alertEvent = document.createEvent("Events");
+ alertEvent.initEvent("AlertActive", true, true);
+ this.dispatchEvent(alertEvent);
+ ]]></handler>
+ <handler event="popuphiding"><![CDATA[
+ try {
+ this._currentFocus = document.commandDispatcher.focusedElement;
+ } catch (e) {
+ this._currentFocus = document.activeElement;
+ }
+ ]]></handler>
+ <handler event="popuphidden"><![CDATA[
+ function doFocus() {
+ // Focus was set on an element inside this panel,
+ // so we need to move it back to where it was previously
+ try {
+ let fm = Components.classes["@mozilla.org/focus-manager;1"]
+ .getService(Components.interfaces.nsIFocusManager);
+ fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
+ } catch (e) {
+ prevFocus.focus();
+ }
+ }
+ var currentFocus = this._currentFocus;
+ var prevFocus = this._prevFocus ? this._prevFocus.get() : null;
+ this._currentFocus = null;
+ this._prevFocus = null;
+
+ // Avoid changing focus if focus changed while we hide the popup
+ // (This can happen e.g. if the popup is hiding as a result of a
+ // click/keypress that focused something)
+ let nowFocus;
+ try {
+ nowFocus = document.commandDispatcher.focusedElement;
+ } catch (e) {
+ nowFocus = document.activeElement;
+ }
+ if (nowFocus && nowFocus != currentFocus)
+ return;
+
+ if (prevFocus && this.getAttribute("norestorefocus") != "true") {
+ // Try to restore focus
+ try {
+ if (document.commandDispatcher.focusedWindow != window)
+ return; // Focus has already been set to a window outside of this panel
+ } catch (ex) {}
+
+ if (!currentFocus) {
+ doFocus();
+ return;
+ }
+ while (currentFocus) {
+ if (currentFocus == this) {
+ doFocus();
+ return;
+ }
+ currentFocus = currentFocus.parentNode;
+ }
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
+ <content flip="both" side="top" position="bottomcenter topleft" consumeoutsideclicks="false">
+ <xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
+ xbl:inherits="side,panelopen">
+ <xul:box anonid="arrowbox" class="panel-arrowbox">
+ <xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
+ </xul:box>
+ <xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
+ <children/>
+ </xul:box>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <field name="_fadeTimer">null</field>
+ <method name="sizeTo">
+ <parameter name="aWidth"/>
+ <parameter name="aHeight"/>
+ <body>
+ <![CDATA[
+ this.popupBoxObject.sizeTo(aWidth, aHeight);
+ if (this.state == "open") {
+ this.adjustArrowPosition();
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="moveToAnchor">
+ <parameter name="aAnchorElement"/>
+ <parameter name="aPosition"/>
+ <parameter name="aX"/>
+ <parameter name="aY"/>
+ <parameter name="aAttributesOverride"/>
+ <body>
+ <![CDATA[
+ this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride);
+ ]]>
+ </body>
+ </method>
+ <method name="adjustArrowPosition">
+ <body>
+ <![CDATA[
+ var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
+
+ var anchor = this.anchorNode;
+ if (!anchor) {
+ return;
+ }
+
+ var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
+ var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
+
+ var position = this.alignmentPosition;
+ var offset = this.alignmentOffset;
+
+ this.setAttribute("arrowposition", position);
+
+ if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
+ container.orient = "horizontal";
+ arrowbox.orient = "vertical";
+ if (position.indexOf("_after") > 0) {
+ arrowbox.pack = "end";
+ } else {
+ arrowbox.pack = "start";
+ }
+ arrowbox.style.transform = "translate(0, " + -offset + "px)";
+
+ // The assigned side stays the same regardless of direction.
+ var isRTL = (window.getComputedStyle(this).direction == "rtl");
+
+ if (position.indexOf("start_") == 0) {
+ container.dir = "reverse";
+ this.setAttribute("side", isRTL ? "left" : "right");
+ }
+ else {
+ container.dir = "";
+ this.setAttribute("side", isRTL ? "right" : "left");
+ }
+ }
+ else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
+ container.orient = "";
+ arrowbox.orient = "";
+ if (position.indexOf("_end") > 0) {
+ arrowbox.pack = "end";
+ } else {
+ arrowbox.pack = "start";
+ }
+ arrowbox.style.transform = "translate(" + -offset + "px, 0)";
+
+ if (position.indexOf("before_") == 0) {
+ container.dir = "reverse";
+ this.setAttribute("side", "bottom");
+ }
+ else {
+ container.dir = "";
+ this.setAttribute("side", "top");
+ }
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ <handlers>
+ <handler event="popupshowing" phase="target">
+ <![CDATA[
+ var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
+ arrow.hidden = this.anchorNode == null;
+ document.getAnonymousElementByAttribute(this, "anonid", "arrowbox")
+ .style.removeProperty("transform");
+
+ this.adjustArrowPosition();
+
+ if (this.getAttribute("animate") != "false") {
+ this.setAttribute("animate", "open");
+ }
+
+ // set fading
+ var fade = this.getAttribute("fade");
+ var fadeDelay = 0;
+ if (fade == "fast") {
+ fadeDelay = 1;
+ }
+ else if (fade == "slow") {
+ fadeDelay = 4000;
+ }
+ else {
+ return;
+ }
+
+ this._fadeTimer = setTimeout(() => this.hidePopup(true), fadeDelay, this);
+ ]]>
+ </handler>
+ <handler event="popuphiding" phase="target">
+ let animate = (this.getAttribute("animate") != "false");
+
+ if (this._fadeTimer) {
+ clearTimeout(this._fadeTimer);
+ if (animate) {
+ this.setAttribute("animate", "fade");
+ }
+ }
+ else if (animate) {
+ this.setAttribute("animate", "cancel");
+ }
+ </handler>
+ <handler event="popupshown" phase="target">
+ this.setAttribute("panelopen", "true");
+ </handler>
+ <handler event="popuphidden" phase="target">
+ this.removeAttribute("panelopen");
+ if (this.getAttribute("animate") != "false") {
+ this.removeAttribute("animate");
+ }
+ </handler>
+ <handler event="popuppositioned" phase="target">
+ this.adjustArrowPosition();
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tooltip" role="xul:tooltip"
+ extends="chrome://global/content/bindings/popup.xml#popup-base">
+ <content>
+ <children>
+ <xul:label class="tooltip-label" xbl:inherits="xbl:text=label" flex="1"/>
+ </children>
+ </content>
+
+ <implementation>
+ <field name="_mouseOutCount">0</field>
+ <field name="_isMouseOver">false</field>
+
+ <property name="label"
+ onget="return this.getAttribute('label');"
+ onset="this.setAttribute('label', val); return val;"/>
+
+ <property name="page" onset="if (val) this.setAttribute('page', 'true');
+ else this.removeAttribute('page');
+ return val;"
+ onget="return this.getAttribute('page') == 'true';"/>
+ <property name="textProvider"
+ readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._textProvider) {
+ this._textProvider = Components.classes["@mozilla.org/embedcomp/default-tooltiptextprovider;1"]
+ .getService(Components.interfaces.nsITooltipTextProvider);
+ }
+ return this._textProvider;
+ ]]>
+ </getter>
+ </property>
+
+ <!-- Given the supplied element within a page, set the tooltip's text to the text
+ for that element. Returns true if text was assigned, and false if the no text
+ is set, which normally would be used to cancel tooltip display.
+ -->
+ <method name="fillInPageTooltip">
+ <parameter name="tipElement"/>
+ <body>
+ <![CDATA[
+ let tttp = this.textProvider;
+ let textObj = {}, dirObj = {};
+ let shouldChangeText = tttp.getNodeText(tipElement, textObj, dirObj);
+ if (shouldChangeText) {
+ this.style.direction = dirObj.value;
+ this.label = textObj.value;
+ }
+ return shouldChangeText;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="mouseover"><![CDATA[
+ var rel = event.relatedTarget;
+ if (!rel)
+ return;
+
+ // find out if the node we entered from is one of our anonymous children
+ while (rel) {
+ if (rel == this)
+ break;
+ rel = rel.parentNode;
+ }
+
+ // if the exited node is not a descendant of ours, we are entering for the first time
+ if (rel != this)
+ this._isMouseOver = true;
+ ]]></handler>
+
+ <handler event="mouseout"><![CDATA[
+ var rel = event.relatedTarget;
+
+ // relatedTarget is null when the titletip is first shown: a mouseout event fires
+ // because the mouse is exiting the main window and entering the titletip "window".
+ // relatedTarget is also null when the mouse exits the main window completely,
+ // so count how many times relatedTarget was null after titletip is first shown
+ // and hide popup the 2nd time
+ if (!rel) {
+ ++this._mouseOutCount;
+ if (this._mouseOutCount > 1)
+ this.hidePopup();
+ return;
+ }
+
+ // find out if the node we are entering is one of our anonymous children
+ while (rel) {
+ if (rel == this)
+ break;
+ rel = rel.parentNode;
+ }
+
+ // if the entered node is not a descendant of ours, hide the tooltip
+ if (rel != this && this._isMouseOver) {
+ this.hidePopup();
+ }
+ ]]></handler>
+
+ <handler event="popupshowing"><![CDATA[
+ if (this.page && !this.fillInPageTooltip(this.triggerNode)) {
+ event.preventDefault();
+ }
+ ]]></handler>
+
+ <handler event="popuphiding"><![CDATA[
+ this._isMouseOver = false;
+ this._mouseOutCount = 0;
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup">
+ <content>
+ <xul:hbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
+ <children/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/preferences.xml b/components/bindings/content/preferences.xml
new file mode 100644
index 000000000..f7cb10948
--- /dev/null
+++ b/components/bindings/content/preferences.xml
@@ -0,0 +1,1403 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE bindings [
+ <!ENTITY % preferencesDTD SYSTEM "chrome://global/locale/preferences.dtd">
+ %preferencesDTD;
+ <!ENTITY % globalKeysDTD SYSTEM "chrome://global/locale/globalKeys.dtd">
+ %globalKeysDTD;
+]>
+
+<bindings id="preferencesBindings"
+ 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">
+
+#
+# = Preferences Window Framework
+#
+# The syntax for use looks something like:
+#
+# <prefwindow>
+# <prefpane id="prefPaneA">
+# <preferences>
+# <preference id="preference1" name="app.preference1" type="bool" onchange="foo();"/>
+# <preference id="preference2" name="app.preference2" type="bool" useDefault="true"/>
+# </preferences>
+# <checkbox label="Preference" preference="preference1"/>
+# </prefpane>
+# </prefwindow>
+#
+
+ <binding id="preferences">
+ <implementation implements="nsIObserver">
+ <method name="_constructAfterChildren">
+ <body>
+ <![CDATA[
+ // This method will be called after each one of the child
+ // <preference> elements is constructed. Its purpose is to propagate
+ // the values to the associated form elements
+
+ var elements = this.getElementsByTagName("preference");
+ for (let element of elements) {
+ if (!element._constructed) {
+ return;
+ }
+ }
+ for (let element of elements) {
+ element.updateElements();
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ for (var i = 0; i < this.childNodes.length; ++i) {
+ var preference = this.childNodes[i];
+ if (preference.name == aData) {
+ preference.value = preference.valueFromPreferences;
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="fireChangedEvent">
+ <parameter name="aPreference"/>
+ <body>
+ <![CDATA[
+ // Value changed, synthesize an event
+ try {
+ var event = document.createEvent("Events");
+ event.initEvent("change", true, true);
+ aPreference.dispatchEvent(event);
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="service">
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ </field>
+ <field name="rootBranch">
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ </field>
+ <field name="defaultBranch">
+ this.service.getDefaultBranch("");
+ </field>
+ <field name="rootBranchInternal">
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranchInternal);
+ </field>
+ <property name="type" readonly="true">
+ <getter>
+ <![CDATA[
+ return document.documentElement.type || "";
+ ]]>
+ </getter>
+ </property>
+ <property name="instantApply" readonly="true">
+ <getter>
+ <![CDATA[
+ var doc = document.documentElement;
+ return this.type == "child" ? doc.instantApply
+ : doc.instantApply || this.rootBranch.getBoolPref("browser.preferences.instantApply");
+ ]]>
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="preference">
+ <implementation>
+ <constructor>
+ <![CDATA[
+ this._constructed = true;
+
+ // if the element has been inserted without the name attribute set,
+ // we have nothing to do here
+ if (!this.name)
+ return;
+
+ this.preferences.rootBranchInternal
+ .addObserver(this.name, this.preferences, false);
+ // In non-instant apply mode, we must try and use the last saved state
+ // from any previous opens of a child dialog instead of the value from
+ // preferences, to pick up any edits a user may have made.
+
+ var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Components.interfaces.nsIScriptSecurityManager);
+ if (this.preferences.type == "child" &&
+ !this.instantApply && window.opener &&
+ secMan.isSystemPrincipal(window.opener.document.nodePrincipal)) {
+ var pdoc = window.opener.document;
+
+ // Try to find a preference element for the same preference.
+ var preference = null;
+ var parentPreferences = pdoc.getElementsByTagName("preferences");
+ for (var k = 0; (k < parentPreferences.length && !preference); ++k) {
+ var parentPrefs = parentPreferences[k]
+ .getElementsByAttribute("name", this.name);
+ for (var l = 0; (l < parentPrefs.length && !preference); ++l) {
+ if (parentPrefs[l].localName == "preference")
+ preference = parentPrefs[l];
+ }
+ }
+
+ // Don't use the value setter here, we don't want updateElements to be prematurely fired.
+ this._value = preference ? preference.value : this.valueFromPreferences;
+ }
+ else
+ this._value = this.valueFromPreferences;
+ this.preferences._constructAfterChildren();
+ ]]>
+ </constructor>
+ <destructor>
+ this.preferences.rootBranchInternal
+ .removeObserver(this.name, this.preferences);
+ </destructor>
+ <field name="_constructed">false</field>
+ <property name="instantApply">
+ <getter>
+ if (this.getAttribute("instantApply") == "false")
+ return false;
+ return this.getAttribute("instantApply") == "true" || this.preferences.instantApply;
+ </getter>
+ </property>
+
+ <property name="preferences" onget="return this.parentNode"/>
+ <property name="name" onget="return this.getAttribute('name');">
+ <setter>
+ if (val == this.name)
+ return val;
+
+ this.preferences.rootBranchInternal
+ .removeObserver(this.name, this.preferences);
+ this.setAttribute('name', val);
+ this.preferences.rootBranchInternal
+ .addObserver(val, this.preferences, false);
+
+ return val;
+ </setter>
+ </property>
+ <property name="type" onget="return this.getAttribute('type');"
+ onset="this.setAttribute('type', val); return val;"/>
+ <property name="inverted" onget="return this.getAttribute('inverted') == 'true';"
+ onset="this.setAttribute('inverted', val); return val;"/>
+ <property name="readonly" onget="return this.getAttribute('readonly') == 'true';"
+ onset="this.setAttribute('readonly', val); return val;"/>
+
+ <field name="_value">null</field>
+ <method name="_setValue">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ if (this.value !== aValue) {
+ this._value = aValue;
+ if (this.instantApply)
+ this.valueFromPreferences = aValue;
+ this.preferences.fireChangedEvent(this);
+ }
+ return aValue;
+ ]]>
+ </body>
+ </method>
+ <property name="value" onget="return this._value" onset="return this._setValue(val);"/>
+
+ <property name="locked">
+ <getter>
+ return this.preferences.rootBranch.prefIsLocked(this.name);
+ </getter>
+ </property>
+
+ <property name="disabled">
+ <getter>
+ return this.getAttribute("disabled") == "true";
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val)
+ this.setAttribute("disabled", "true");
+ else
+ this.removeAttribute("disabled");
+
+ if (!this.id)
+ return val;
+
+ var elements = document.getElementsByAttribute("preference", this.id);
+ for (var i = 0; i < elements.length; ++i) {
+ elements[i].disabled = val;
+
+ var labels = document.getElementsByAttribute("control", elements[i].id);
+ for (var j = 0; j < labels.length; ++j)
+ labels[j].disabled = val;
+ }
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="tabIndex">
+ <getter>
+ return parseInt(this.getAttribute("tabindex"));
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val)
+ this.setAttribute("tabindex", val);
+ else
+ this.removeAttribute("tabindex");
+
+ if (!this.id)
+ return val;
+
+ var elements = document.getElementsByAttribute("preference", this.id);
+ for (var i = 0; i < elements.length; ++i) {
+ elements[i].tabIndex = val;
+
+ var labels = document.getElementsByAttribute("control", elements[i].id);
+ for (var j = 0; j < labels.length; ++j)
+ labels[j].tabIndex = val;
+ }
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="hasUserValue">
+ <getter>
+ <![CDATA[
+ return this.preferences.rootBranch.prefHasUserValue(this.name) &&
+ this.value !== undefined;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="reset">
+ <body>
+ // defer reset until preference update
+ this.value = undefined;
+ </body>
+ </method>
+
+ <field name="_useDefault">false</field>
+ <property name="defaultValue">
+ <getter>
+ <![CDATA[
+ this._useDefault = true;
+ var val = this.valueFromPreferences;
+ this._useDefault = false;
+ return val;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="_branch">
+ <getter>
+ return this._useDefault ? this.preferences.defaultBranch : this.preferences.rootBranch;
+ </getter>
+ </property>
+
+ <field name="batching">false</field>
+
+ <method name="_reportUnknownType">
+ <body>
+ <![CDATA[
+ var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService);
+ var msg = "<preference> with id='" + this.id + "' and name='" +
+ this.name + "' has unknown type '" + this.type + "'.";
+ consoleService.logStringMessage(msg);
+ ]]>
+ </body>
+ </method>
+
+ <property name="valueFromPreferences">
+ <getter>
+ <![CDATA[
+ try {
+ // Force a resync of value with preferences.
+ switch (this.type) {
+ case "int":
+ return this._branch.getIntPref(this.name);
+ case "bool":
+ var val = this._branch.getBoolPref(this.name);
+ return this.inverted ? !val : val;
+ case "wstring":
+ return this._branch
+ .getComplexValue(this.name, Components.interfaces.nsIPrefLocalizedString)
+ .data;
+ case "string":
+ case "unichar":
+ return this._branch
+ .getComplexValue(this.name, Components.interfaces.nsISupportsString)
+ .data;
+ case "fontname":
+ var family = this._branch
+ .getComplexValue(this.name, Components.interfaces.nsISupportsString)
+ .data;
+ var fontEnumerator = Components.classes["@mozilla.org/gfx/fontenumerator;1"]
+ .createInstance(Components.interfaces.nsIFontEnumerator);
+ return fontEnumerator.getStandardFamilyName(family);
+ case "file":
+ var f = this._branch
+ .getComplexValue(this.name, Components.interfaces.nsILocalFile);
+ return f;
+ default:
+ this._reportUnknownType();
+ }
+ }
+ catch (e) { }
+ return null;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ // Exit early if nothing to do.
+ if (this.readonly || this.valueFromPreferences == val)
+ return val;
+
+ // The special value undefined means 'reset preference to default'.
+ if (val === undefined) {
+ this.preferences.rootBranch.clearUserPref(this.name);
+ return val;
+ }
+
+ // Force a resync of preferences with value.
+ switch (this.type) {
+ case "int":
+ this.preferences.rootBranch.setIntPref(this.name, val);
+ break;
+ case "bool":
+ this.preferences.rootBranch.setBoolPref(this.name, this.inverted ? !val : val);
+ break;
+ case "wstring":
+ var pls = Components.classes["@mozilla.org/pref-localizedstring;1"]
+ .createInstance(Components.interfaces.nsIPrefLocalizedString);
+ pls.data = val;
+ this.preferences.rootBranch
+ .setComplexValue(this.name, Components.interfaces.nsIPrefLocalizedString, pls);
+ break;
+ case "string":
+ case "unichar":
+ case "fontname":
+ var iss = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ iss.data = val;
+ this.preferences.rootBranch
+ .setComplexValue(this.name, Components.interfaces.nsISupportsString, iss);
+ break;
+ case "file":
+ var lf;
+ if (typeof(val) == "string") {
+ lf = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsILocalFile);
+ lf.persistentDescriptor = val;
+ if (!lf.exists())
+ lf.initWithPath(val);
+ }
+ else
+ lf = val.QueryInterface(Components.interfaces.nsILocalFile);
+ this.preferences.rootBranch
+ .setComplexValue(this.name, Components.interfaces.nsILocalFile, lf);
+ break;
+ default:
+ this._reportUnknownType();
+ }
+ if (!this.batching)
+ this.preferences.service.savePrefFile(null);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="setElementValue">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ if (this.locked)
+ aElement.disabled = true;
+
+ if (!this.isElementEditable(aElement))
+ return;
+
+ var rv = undefined;
+ if (aElement.hasAttribute("onsyncfrompreference")) {
+ // Value changed, synthesize an event
+ try {
+ var event = document.createEvent("Events");
+ event.initEvent("syncfrompreference", true, true);
+ var f = new Function ("event",
+ aElement.getAttribute("onsyncfrompreference"));
+ rv = f.call(aElement, event);
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+ var val = rv;
+ if (val === undefined)
+ val = this.instantApply ? this.valueFromPreferences : this.value;
+ // if the preference is marked for reset, show default value in UI
+ if (val === undefined)
+ val = this.defaultValue;
+
+ /**
+ * Initialize a UI element property with a value. Handles the case
+ * where an element has not yet had a XBL binding attached for it and
+ * the property setter does not yet exist by setting the same attribute
+ * on the XUL element using DOM apis and assuming the element's
+ * constructor or property getters appropriately handle this state.
+ */
+ function setValue(element, attribute, value) {
+ if (attribute in element)
+ element[attribute] = value;
+ else
+ element.setAttribute(attribute, value);
+ }
+ if (aElement.localName == "checkbox" ||
+ aElement.localName == "listitem")
+ setValue(aElement, "checked", val);
+ else if (aElement.localName == "colorpicker")
+ setValue(aElement, "color", val);
+ else if (aElement.localName == "textbox") {
+ // XXXmano Bug 303998: Avoid a caret placement issue if either the
+ // preference observer or its setter calls updateElements as a result
+ // of the input event handler.
+ if (aElement.value !== val)
+ setValue(aElement, "value", val);
+ }
+ else
+ setValue(aElement, "value", val);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getElementValue">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ if (aElement.hasAttribute("onsynctopreference")) {
+ // Value changed, synthesize an event
+ try {
+ var event = document.createEvent("Events");
+ event.initEvent("synctopreference", true, true);
+ var f = new Function ("event",
+ aElement.getAttribute("onsynctopreference"));
+ var rv = f.call(aElement, event);
+ if (rv !== undefined)
+ return rv;
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+
+ /**
+ * Read the value of an attribute from an element, assuming the
+ * attribute is a property on the element's node API. If the property
+ * is not present in the API, then assume its value is contained in
+ * an attribute, as is the case before a binding has been attached.
+ */
+ function getValue(element, attribute) {
+ if (attribute in element)
+ return element[attribute];
+ return element.getAttribute(attribute);
+ }
+ if (aElement.localName == "checkbox" ||
+ aElement.localName == "listitem")
+ var value = getValue(aElement, "checked");
+ else if (aElement.localName == "colorpicker")
+ value = getValue(aElement, "color");
+ else
+ value = getValue(aElement, "value");
+
+ switch (this.type) {
+ case "int":
+ return parseInt(value, 10) || 0;
+ case "bool":
+ return typeof(value) == "boolean" ? value : value == "true";
+ }
+ return value;
+ ]]>
+ </body>
+ </method>
+
+ <method name="isElementEditable">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ switch (aElement.localName) {
+ case "checkbox":
+ case "colorpicker":
+ case "radiogroup":
+ case "textbox":
+ case "listitem":
+ case "listbox":
+ case "menulist":
+ return true;
+ }
+ return aElement.getAttribute("preference-editable") == "true";
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateElements">
+ <body>
+ <![CDATA[
+ if (!this.id)
+ return;
+
+ // This "change" event handler tracks changes made to preferences by
+ // sources other than the user in this window.
+ var elements = document.getElementsByAttribute("preference", this.id);
+ for (var i = 0; i < elements.length; ++i)
+ this.setElementValue(elements[i]);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="change">
+ this.updateElements();
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="prefwindow"
+ extends="chrome://global/content/bindings/dialog.xml#dialog">
+ <resources>
+ <stylesheet src="chrome://global/skin/preferences.css"/>
+ </resources>
+ <content dlgbuttons="accept,cancel" persist="lastSelected screenX screenY"
+ closebuttonlabel="&preferencesCloseButton.label;"
+ closebuttonaccesskey="&preferencesCloseButton.accesskey;"
+ role="dialog"
+#ifdef XP_WIN
+ title="&preferencesDefaultTitleWin.title;">
+#else
+ title="&preferencesDefaultTitleMac.title;">
+#endif
+ <xul:windowdragbox orient="vertical">
+ <xul:radiogroup anonid="selector" orient="horizontal" class="paneSelector chromeclass-toolbar"
+ role="listbox"/> <!-- Expose to accessibility APIs as a listbox -->
+ </xul:windowdragbox>
+ <xul:hbox flex="1" class="paneDeckContainer">
+ <xul:deck anonid="paneDeck" flex="1">
+ <children includes="prefpane"/>
+ </xul:deck>
+ </xul:hbox>
+ <xul:hbox anonid="dlg-buttons" class="prefWindow-dlgbuttons" pack="end">
+#ifdef XP_UNIX
+ <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/>
+ <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
+ <xul:spacer anonid="spacer" flex="1"/>
+ <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/>
+ <xul:button dlgtype="accept" class="dialog-button" icon="accept"/>
+#else
+ <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
+ <xul:spacer anonid="spacer" flex="1"/>
+ <xul:button dlgtype="accept" class="dialog-button" icon="accept"/>
+ <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
+ <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/>
+ <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/>
+ <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
+#endif
+ </xul:hbox>
+ <xul:hbox>
+ <children/>
+ </xul:hbox>
+ </content>
+ <implementation implements="nsITimerCallback">
+ <constructor>
+ <![CDATA[
+ if (this.type != "child") {
+ if (!this._instantApplyInitialized) {
+ let psvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ this.instantApply = psvc.getBoolPref("browser.preferences.instantApply");
+ }
+ if (this.instantApply) {
+ var docElt = document.documentElement;
+ var acceptButton = docElt.getButton("accept");
+ acceptButton.hidden = true;
+ var cancelButton = docElt.getButton("cancel");
+ if (/Mac/.test(navigator.platform)) {
+ // no buttons on Mac except Help
+ cancelButton.hidden = true;
+ // Move Help button to the end
+ document.getAnonymousElementByAttribute(this, "anonid", "spacer").hidden = true;
+ // Also, don't fire onDialogAccept on enter
+ acceptButton.disabled = true;
+ } else {
+ // morph the Cancel button into the Close button
+ cancelButton.setAttribute ("icon", "close");
+ cancelButton.label = docElt.getAttribute("closebuttonlabel");
+ cancelButton.accesskey = docElt.getAttribute("closebuttonaccesskey");
+ }
+ }
+ }
+ this.setAttribute("animated", this._shouldAnimate ? "true" : "false");
+ var panes = this.preferencePanes;
+
+ var lastPane = null;
+ if (this.lastSelected) {
+ lastPane = document.getElementById(this.lastSelected);
+ if (!lastPane) {
+ this.lastSelected = "";
+ }
+ }
+
+ var paneToLoad;
+ if ("arguments" in window && window.arguments[0] && document.getElementById(window.arguments[0]) && document.getElementById(window.arguments[0]).nodeName == "prefpane") {
+ paneToLoad = document.getElementById(window.arguments[0]);
+ this.lastSelected = paneToLoad.id;
+ }
+ else if (lastPane)
+ paneToLoad = lastPane;
+ else
+ paneToLoad = panes[0];
+
+ for (var i = 0; i < panes.length; ++i) {
+ this._makePaneButton(panes[i]);
+ if (panes[i].loaded) {
+ // Inline pane content, fire load event to force initialization.
+ this._fireEvent("paneload", panes[i]);
+ }
+ }
+ this.showPane(paneToLoad);
+
+ if (panes.length == 1)
+ this._selector.setAttribute("collapsed", "true");
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ // Release timers to avoid reference cycles.
+ if (this._animateTimer) {
+ this._animateTimer.cancel();
+ this._animateTimer = null;
+ }
+ if (this._fadeTimer) {
+ this._fadeTimer.cancel();
+ this._fadeTimer = null;
+ }
+ ]]>
+ </destructor>
+
+ <!-- Derived bindings can set this to true to cause us to skip
+ reading the browser.preferences.instantApply pref in the constructor.
+ Then they can set instantApply to their wished value. -->
+ <field name="_instantApplyInitialized">false</field>
+ <!-- Controls whether changed pref values take effect immediately. -->
+ <field name="instantApply">false</field>
+
+ <property name="preferencePanes"
+ onget="return this.getElementsByTagName('prefpane');"/>
+
+ <property name="type" onget="return this.getAttribute('type');"/>
+ <property name="_paneDeck"
+ onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'paneDeck');"/>
+ <property name="_paneDeckContainer"
+ onget="return document.getAnonymousElementByAttribute(this, 'class', 'paneDeckContainer');"/>
+ <property name="_selector"
+ onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'selector');"/>
+ <property name="lastSelected"
+ onget="return this.getAttribute('lastSelected');">
+ <setter>
+ this.setAttribute("lastSelected", val);
+ document.persist(this.id, "lastSelected");
+ return val;
+ </setter>
+ </property>
+ <property name="currentPane"
+ onset="return this._currentPane = val;">
+ <getter>
+ if (!this._currentPane)
+ this._currentPane = this.preferencePanes[0];
+
+ return this._currentPane;
+ </getter>
+ </property>
+ <field name="_currentPane">null</field>
+
+
+ <method name="_makePaneButton">
+ <parameter name="aPaneElement"/>
+ <body>
+ <![CDATA[
+ var radio = document.createElement("radio");
+ radio.setAttribute("pane", aPaneElement.id);
+ radio.setAttribute("label", aPaneElement.label);
+ // Expose preference group choice to accessibility APIs as an unchecked list item
+ // The parent group is exposed to accessibility APIs as a list
+ if (aPaneElement.image)
+ radio.setAttribute("src", aPaneElement.image);
+ radio.style.listStyleImage = aPaneElement.style.listStyleImage;
+ this._selector.appendChild(radio);
+ return radio;
+ ]]>
+ </body>
+ </method>
+
+ <method name="showPane">
+ <parameter name="aPaneElement"/>
+ <body>
+ <![CDATA[
+ if (!aPaneElement)
+ return;
+
+ this._selector.selectedItem = document.getAnonymousElementByAttribute(this, "pane", aPaneElement.id);
+ if (!aPaneElement.loaded) {
+ let OverlayLoadObserver = function(aPane)
+ {
+ this._pane = aPane;
+ }
+ OverlayLoadObserver.prototype = {
+ _outer: this,
+ observe: function (aSubject, aTopic, aData)
+ {
+ this._pane.loaded = true;
+ this._outer._fireEvent("paneload", this._pane);
+ this._outer._selectPane(this._pane);
+ }
+ };
+
+ var obs = new OverlayLoadObserver(aPaneElement);
+ document.loadOverlay(aPaneElement.src, obs);
+ }
+ else
+ this._selectPane(aPaneElement);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_fireEvent">
+ <parameter name="aEventName"/>
+ <parameter name="aTarget"/>
+ <body>
+ <![CDATA[
+ // Panel loaded, synthesize a load event.
+ try {
+ var event = document.createEvent("Events");
+ event.initEvent(aEventName, true, true);
+ var cancel = !aTarget.dispatchEvent(event);
+ if (aTarget.hasAttribute("on" + aEventName)) {
+ var fn = new Function ("event", aTarget.getAttribute("on" + aEventName));
+ var rv = fn.call(aTarget, event);
+ if (rv == false)
+ cancel = true;
+ }
+ return !cancel;
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <field name="_initialized">false</field>
+ <method name="_selectPane">
+ <parameter name="aPaneElement"/>
+ <body>
+ <![CDATA[
+ if (/Mac/.test(navigator.platform)) {
+ var paneTitle = aPaneElement.label;
+ if (paneTitle != "")
+ document.title = paneTitle;
+ }
+ var helpButton = document.documentElement.getButton("help");
+ if (aPaneElement.helpTopic)
+ helpButton.hidden = false;
+ else
+ helpButton.hidden = true;
+
+ // Find this pane's index in the deck and set the deck's
+ // selectedIndex to that value to switch to it.
+ var prefpanes = this.preferencePanes;
+ for (var i = 0; i < prefpanes.length; ++i) {
+ if (prefpanes[i] == aPaneElement) {
+ this._paneDeck.selectedIndex = i;
+
+ if (this.type != "child") {
+ if (aPaneElement.hasAttribute("flex") && this._shouldAnimate &&
+ prefpanes.length > 1)
+ aPaneElement.removeAttribute("flex");
+ // Calling sizeToContent after the first prefpane is loaded
+ // will size the windows contents so style information is
+ // available to calculate correct sizing.
+ if (!this._initialized && prefpanes.length > 1) {
+ if (this._shouldAnimate)
+ this.style.minHeight = 0;
+ window.sizeToContent();
+ }
+
+ var oldPane = this.lastSelected ? document.getElementById(this.lastSelected) : this.preferencePanes[0];
+ oldPane.selected = !(aPaneElement.selected = true);
+ this.lastSelected = aPaneElement.id;
+ this.currentPane = aPaneElement;
+ this._initialized = true;
+
+ // Only animate if we've switched between prefpanes
+ if (this._shouldAnimate && oldPane.id != aPaneElement.id) {
+ aPaneElement.style.opacity = 0.0;
+ this.animate(oldPane, aPaneElement);
+ }
+ else if (!this._shouldAnimate && prefpanes.length > 1) {
+ var targetHeight = parseInt(window.getComputedStyle(this._paneDeckContainer, "").height);
+ var verticalPadding = parseInt(window.getComputedStyle(aPaneElement, "").paddingTop);
+ verticalPadding += parseInt(window.getComputedStyle(aPaneElement, "").paddingBottom);
+ if (aPaneElement.contentHeight > targetHeight - verticalPadding) {
+ // To workaround the bottom border of a groupbox from being
+ // cutoff an hbox with a class of bottomBox may enclose it.
+ // This needs to include its padding to resize properly.
+ // See bug 394433
+ var bottomPadding = 0;
+ var bottomBox = aPaneElement.getElementsByAttribute("class", "bottomBox")[0];
+ if (bottomBox)
+ bottomPadding = parseInt(window.getComputedStyle(bottomBox, "").paddingBottom);
+ window.innerHeight += bottomPadding + verticalPadding + aPaneElement.contentHeight - targetHeight;
+ }
+
+ // XXX rstrong - extend the contents of the prefpane to
+ // prevent elements from being cutoff (see bug 349098).
+ if (aPaneElement.contentHeight + verticalPadding < targetHeight)
+ aPaneElement._content.style.height = targetHeight - verticalPadding + "px";
+ }
+ }
+ break;
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="_shouldAnimate">
+ <getter>
+ <![CDATA[
+ var psvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ return psvc.getBoolPref("browser.preferences.animateFadeIn",
+ /Mac/.test(navigator.platform));
+ ]]>
+ </getter>
+ </property>
+
+ <method name="animate">
+ <parameter name="aOldPane"/>
+ <parameter name="aNewPane"/>
+ <body>
+ <![CDATA[
+ // if we are already resizing, use currentHeight
+ var oldHeight = this._currentHeight ? this._currentHeight : aOldPane.contentHeight;
+
+ this._multiplier = aNewPane.contentHeight > oldHeight ? 1 : -1;
+ var sizeDelta = Math.abs(oldHeight - aNewPane.contentHeight);
+ this._animateRemainder = sizeDelta % this._animateIncrement;
+
+ this._setUpAnimationTimer(oldHeight);
+ ]]>
+ </body>
+ </method>
+
+ <property name="_sizeIncrement">
+ <getter>
+ <![CDATA[
+ var lastSelectedPane = document.getElementById(this.lastSelected);
+ var increment = this._animateIncrement * this._multiplier;
+ var newHeight = this._currentHeight + increment;
+ if ((this._multiplier > 0 && this._currentHeight >= lastSelectedPane.contentHeight) ||
+ (this._multiplier < 0 && this._currentHeight <= lastSelectedPane.contentHeight))
+ return 0;
+
+ if ((this._multiplier > 0 && newHeight > lastSelectedPane.contentHeight) ||
+ (this._multiplier < 0 && newHeight < lastSelectedPane.contentHeight))
+ increment = this._animateRemainder * this._multiplier;
+ return increment;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="notify">
+ <parameter name="aTimer"/>
+ <body>
+ <![CDATA[
+ if (!document)
+ aTimer.cancel();
+
+ if (aTimer == this._animateTimer) {
+ var increment = this._sizeIncrement;
+ if (increment != 0) {
+ window.innerHeight += increment;
+ this._currentHeight += increment;
+ }
+ else {
+ aTimer.cancel();
+ this._setUpFadeTimer();
+ }
+ } else if (aTimer == this._fadeTimer) {
+ var elt = document.getElementById(this.lastSelected);
+ var newOpacity = parseFloat(window.getComputedStyle(elt, "").opacity) + this._fadeIncrement;
+ if (newOpacity < 1.0)
+ elt.style.opacity = newOpacity;
+ else {
+ aTimer.cancel();
+ elt.style.opacity = 1.0;
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_setUpAnimationTimer">
+ <parameter name="aStartHeight"/>
+ <body>
+ <![CDATA[
+ if (!this._animateTimer)
+ this._animateTimer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ else
+ this._animateTimer.cancel();
+ this._currentHeight = aStartHeight;
+
+ this._animateTimer.initWithCallback(this, this._animateDelay,
+ Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_setUpFadeTimer">
+ <body>
+ <![CDATA[
+ if (!this._fadeTimer)
+ this._fadeTimer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ else
+ this._fadeTimer.cancel();
+
+ this._fadeTimer.initWithCallback(this, this._fadeDelay,
+ Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_animateTimer">null</field>
+ <field name="_fadeTimer">null</field>
+ <field name="_animateDelay">15</field>
+ <field name="_animateIncrement">40</field>
+ <field name="_fadeDelay">5</field>
+ <field name="_fadeIncrement">0.40</field>
+ <field name="_animateRemainder">0</field>
+ <field name="_currentHeight">0</field>
+ <field name="_multiplier">0</field>
+
+ <method name="addPane">
+ <parameter name="aPaneElement"/>
+ <body>
+ <![CDATA[
+ this.appendChild(aPaneElement);
+
+ // Set up pane button
+ this._makePaneButton(aPaneElement);
+ ]]>
+ </body>
+ </method>
+
+ <method name="openSubDialog">
+ <parameter name="aURL"/>
+ <parameter name="aFeatures"/>
+ <parameter name="aParams"/>
+ <body>
+ return openDialog(aURL, "", "modal,centerscreen,resizable=no" + (aFeatures != "" ? ("," + aFeatures) : ""), aParams);
+ </body>
+ </method>
+
+ <method name="openWindow">
+ <parameter name="aWindowType"/>
+ <parameter name="aURL"/>
+ <parameter name="aFeatures"/>
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var win = aWindowType ? wm.getMostRecentWindow(aWindowType) : null;
+ if (win) {
+ if ("initWithParams" in win)
+ win.initWithParams(aParams);
+ win.focus();
+ }
+ else {
+ var features = "resizable,dialog=no,centerscreen" + (aFeatures != "" ? ("," + aFeatures) : "");
+ var parentWindow = (this.instantApply || !window.opener || window.opener.closed) ? window : window.opener;
+ win = parentWindow.openDialog(aURL, "_blank", features, aParams);
+ }
+ return win;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ <handlers>
+ <handler event="dialogaccept">
+ <![CDATA[
+ if (!this._fireEvent("beforeaccept", this)) {
+ return false;
+ }
+
+ var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Components.interfaces.nsIScriptSecurityManager);
+ if (this.type == "child" && window.opener &&
+ secMan.isSystemPrincipal(window.opener.document.nodePrincipal)) {
+ let psvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ var pdocEl = window.opener.document.documentElement;
+ if (pdocEl.instantApply) {
+ let panes = this.preferencePanes;
+ for (let i = 0; i < panes.length; ++i)
+ panes[i].writePreferences(true);
+ }
+ else {
+ // Clone all the preferences elements from the child document and
+ // insert them into the pane collection of the parent.
+ var pdoc = window.opener.document;
+ if (pdoc.documentElement.localName == "prefwindow") {
+ var currentPane = pdoc.documentElement.currentPane;
+ var id = window.location.href + "#childprefs";
+ var childPrefs = pdoc.getElementById(id);
+ if (!childPrefs) {
+ childPrefs = pdoc.createElement("preferences");
+ currentPane.appendChild(childPrefs);
+ childPrefs.id = id;
+ }
+ let panes = this.preferencePanes;
+ for (let i = 0; i < panes.length; ++i) {
+ var preferences = panes[i].preferences;
+ for (var j = 0; j < preferences.length; ++j) {
+ // Try to find a preference element for the same preference.
+ var preference = null;
+ var parentPreferences = pdoc.getElementsByTagName("preferences");
+ for (var k = 0; (k < parentPreferences.length && !preference); ++k) {
+ var parentPrefs = parentPreferences[k]
+ .getElementsByAttribute("name", preferences[j].name);
+ for (var l = 0; (l < parentPrefs.length && !preference); ++l) {
+ if (parentPrefs[l].localName == "preference")
+ preference = parentPrefs[l];
+ }
+ }
+ if (!preference) {
+ // No matching preference in the parent window.
+ preference = pdoc.createElement("preference");
+ childPrefs.appendChild(preference);
+ preference.name = preferences[j].name;
+ preference.type = preferences[j].type;
+ preference.inverted = preferences[j].inverted;
+ preference.readonly = preferences[j].readonly;
+ preference.disabled = preferences[j].disabled;
+ }
+ preference.value = preferences[j].value;
+ }
+ }
+ }
+ }
+ }
+ else {
+ let panes = this.preferencePanes;
+ for (var i = 0; i < panes.length; ++i)
+ panes[i].writePreferences(false);
+
+ let psvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ psvc.savePrefFile(null);
+ }
+
+ return true;
+ ]]>
+ </handler>
+ <handler event="command">
+ if (event.originalTarget.hasAttribute("pane")) {
+ var pane = document.getElementById(event.originalTarget.getAttribute("pane"));
+ this.showPane(pane);
+ }
+ </handler>
+
+ <handler event="keypress" key="&windowClose.key;" modifiers="accel" phase="capturing">
+ <![CDATA[
+ if (this.instantApply)
+ window.close();
+ event.stopPropagation();
+ event.preventDefault();
+ ]]>
+ </handler>
+
+ <handler event="keypress"
+ keycode="&openHelp.commandkey;"
+ phase="capturing">
+ <![CDATA[
+ var helpButton = this.getButton("help");
+ if (helpButton.disabled || helpButton.hidden)
+ return;
+ this._fireEvent("dialoghelp", this);
+ event.stopPropagation();
+ event.preventDefault();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="prefpane">
+ <resources>
+ <stylesheet src="chrome://global/skin/preferences.css"/>
+ </resources>
+ <content>
+ <xul:vbox class="content-box" xbl:inherits="flex">
+ <children/>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <method name="writePreferences">
+ <parameter name="aFlushToDisk"/>
+ <body>
+ <![CDATA[
+ // Write all values to preferences.
+ if (this._deferredValueUpdateElements.size) {
+ this._finalizeDeferredElements();
+ }
+
+ var preferences = this.preferences;
+ for (var i = 0; i < preferences.length; ++i) {
+ var preference = preferences[i];
+ preference.batching = true;
+ preference.valueFromPreferences = preference.value;
+ preference.batching = false;
+ }
+ if (aFlushToDisk) {
+ var psvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ psvc.savePrefFile(null);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="src"
+ onget="return this.getAttribute('src');"
+ onset="this.setAttribute('src', val); return val;"/>
+ <property name="selected"
+ onget="return this.getAttribute('selected') == 'true';"
+ onset="this.setAttribute('selected', val); return val;"/>
+ <property name="image"
+ onget="return this.getAttribute('image');"
+ onset="this.setAttribute('image', val); return val;"/>
+ <property name="label"
+ onget="return this.getAttribute('label');"
+ onset="this.setAttribute('label', val); return val;"/>
+
+ <property name="preferenceElements"
+ onget="return this.getElementsByAttribute('preference', '*');"/>
+ <property name="preferences"
+ onget="return this.getElementsByTagName('preference');"/>
+
+ <property name="helpTopic">
+ <getter>
+ <![CDATA[
+ // if there are tabs, and the selected tab provides a helpTopic, return that
+ var box = this.getElementsByTagName("tabbox");
+ if (box[0]) {
+ var tab = box[0].selectedTab;
+ if (tab && tab.hasAttribute("helpTopic"))
+ return tab.getAttribute("helpTopic");
+ }
+
+ // otherwise, return the helpTopic of the current panel
+ return this.getAttribute("helpTopic");
+ ]]>
+ </getter>
+ </property>
+
+ <field name="_loaded">false</field>
+ <property name="loaded"
+ onget="return !this.src ? true : this._loaded;"
+ onset="this._loaded = val; return val;"/>
+
+ <method name="preferenceForElement">
+ <parameter name="aElement"/>
+ <body>
+ return document.getElementById(aElement.getAttribute("preference"));
+ </body>
+ </method>
+
+ <method name="getPreferenceElement">
+ <parameter name="aStartElement"/>
+ <body>
+ <![CDATA[
+ var temp = aStartElement;
+ while (temp && temp.nodeType == Node.ELEMENT_NODE &&
+ !temp.hasAttribute("preference"))
+ temp = temp.parentNode;
+ return temp.nodeType == Node.ELEMENT_NODE ? temp : aStartElement;
+ ]]>
+ </body>
+ </method>
+
+ <property name="DeferredTask" readonly="true">
+ <getter><![CDATA[
+ let module = {};
+ Components.utils.import("resource://gre/modules/DeferredTask.jsm", module);
+ Object.defineProperty(this, "DeferredTask", {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: module.DeferredTask
+ });
+ return module.DeferredTask;
+ ]]></getter>
+ </property>
+ <method name="_deferredValueUpdate">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ delete aElement._deferredValueUpdateTask;
+ let preference = document.getElementById(aElement.getAttribute("preference"));
+ let prefVal = preference.getElementValue(aElement);
+ preference.value = prefVal;
+ this._deferredValueUpdateElements.delete(aElement);
+ ]]>
+ </body>
+ </method>
+ <field name="_deferredValueUpdateElements">
+ new Set();
+ </field>
+ <method name="_finalizeDeferredElements">
+ <body>
+ <![CDATA[
+ for (let el of this._deferredValueUpdateElements) {
+ if (el._deferredValueUpdateTask) {
+ el._deferredValueUpdateTask.finalize();
+ }
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="userChangedValue">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ let element = this.getPreferenceElement(aElement);
+ if (element.hasAttribute("preference")) {
+ if (element.getAttribute("delayprefsave") != "true") {
+ var preference = document.getElementById(element.getAttribute("preference"));
+ var prefVal = preference.getElementValue(element);
+ preference.value = prefVal;
+ } else {
+ if (!element._deferredValueUpdateTask) {
+ element._deferredValueUpdateTask = new this.DeferredTask(this._deferredValueUpdate.bind(this, element), 1000);
+ this._deferredValueUpdateElements.add(element);
+ } else {
+ // Each time the preference is changed, restart the delay.
+ element._deferredValueUpdateTask.disarm();
+ }
+ element._deferredValueUpdateTask.arm();
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="contentHeight">
+ <getter>
+ var targetHeight = parseInt(window.getComputedStyle(this._content, "").height);
+ targetHeight += parseInt(window.getComputedStyle(this._content, "").marginTop);
+ targetHeight += parseInt(window.getComputedStyle(this._content, "").marginBottom);
+ return targetHeight;
+ </getter>
+ </property>
+ <field name="_content">
+ document.getAnonymousElementByAttribute(this, "class", "content-box");
+ </field>
+ </implementation>
+ <handlers>
+ <handler event="command">
+ // This "command" event handler tracks changes made to preferences by
+ // the user in this window.
+ if (event.sourceEvent)
+ event = event.sourceEvent;
+ this.userChangedValue(event.target);
+ </handler>
+ <handler event="select">
+ // This "select" event handler tracks changes made to colorpicker
+ // preferences by the user in this window.
+ if (event.target.localName == "colorpicker")
+ this.userChangedValue(event.target);
+ </handler>
+ <handler event="change">
+ // This "change" event handler tracks changes made to preferences by
+ // the user in this window.
+ this.userChangedValue(event.target);
+ </handler>
+ <handler event="input">
+ // This "input" event handler tracks changes made to preferences by
+ // the user in this window.
+ this.userChangedValue(event.target);
+ </handler>
+ <handler event="paneload">
+ <![CDATA[
+ // Initialize all values from preferences.
+ var elements = this.preferenceElements;
+ for (var i = 0; i < elements.length; ++i) {
+ try {
+ var preference = this.preferenceForElement(elements[i]);
+ preference.setElementValue(elements[i]);
+ }
+ catch (e) {
+ dump("*** No preference found for " + elements[i].getAttribute("preference") + "\n");
+ }
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="panebutton" role="xul:listitem"
+ extends="chrome://global/content/bindings/radio.xml#radio">
+ <resources>
+ <stylesheet src="chrome://global/skin/preferences.css"/>
+ </resources>
+ <content>
+ <xul:image class="paneButtonIcon" xbl:inherits="src"/>
+ <xul:label class="paneButtonLabel" xbl:inherits="value=label"/>
+ </content>
+ </binding>
+
+</bindings>
+
+# -*- 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/.
+
+#
+# This is PrefWindow 6. The Code Could Well Be Ready, Are You?
+#
+# Historical References:
+# PrefWindow V (February 1, 2003)
+# PrefWindow IV (April 24, 2000)
+# PrefWindow III (January 6, 2000)
+# PrefWindow II (???)
+# PrefWindow I (June 4, 1999)
+#
diff --git a/components/bindings/content/progressmeter.xml b/components/bindings/content/progressmeter.xml
new file mode 100644
index 000000000..82f28ffba
--- /dev/null
+++ b/components/bindings/content/progressmeter.xml
@@ -0,0 +1,116 @@
+<?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="progressmeterBindings"
+ 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="progressmeter" role="xul:progressmeter">
+ <resources>
+ <stylesheet src="chrome://global/skin/progressmeter.css"/>
+ </resources>
+
+ <content>
+ <xul:spacer class="progress-bar" xbl:inherits="mode"/>
+ <xul:spacer class="progress-remainder" xbl:inherits="mode"/>
+ </content>
+
+ <implementation>
+ <property name="mode" onset="if (this.mode != val) this.setAttribute('mode', val); return val;"
+ onget="return this.getAttribute('mode');"/>
+
+ <property name="value" onget="return this.getAttribute('value') || '0';">
+ <setter><![CDATA[
+ var p = Math.round(val);
+ var max = Math.round(this.max);
+ if (p < 0)
+ p = 0;
+ else if (p > max)
+ p = max;
+ var c = this.value;
+ if (p != c) {
+ var delta = p - c;
+ if (delta < 0)
+ delta = -delta;
+ if (delta > 3 || p == 0 || p == max) {
+ this.setAttribute("value", p);
+ // Fire DOM event so that accessible value change events occur
+ var event = document.createEvent('Events');
+ event.initEvent('ValueChange', true, true);
+ this.dispatchEvent(event);
+ }
+ }
+
+ return val;
+ ]]></setter>
+ </property>
+ <property name="max"
+ onget="return this.getAttribute('max') || '100';"
+ onset="this.setAttribute('max', isNaN(val) ? 100 : Math.max(val, 1));
+ this.value = this.value;
+ return val;" />
+ </implementation>
+ </binding>
+
+ <binding id="progressmeter-undetermined"
+ extends="chrome://global/content/bindings/progressmeter.xml#progressmeter">
+ <content>
+ <xul:stack class="progress-remainder" flex="1" anonid="stack" style="overflow: -moz-hidden-unscrollable;">
+ <xul:spacer class="progress-bar" anonid="spacer" top="0" style="margin-right: -1000px;"/>
+ </xul:stack>
+ </content>
+
+ <implementation>
+ <field name="_alive">true</field>
+ <method name="_init">
+ <body><![CDATA[
+ var stack =
+ document.getAnonymousElementByAttribute(this, "anonid", "stack");
+ var spacer =
+ document.getAnonymousElementByAttribute(this, "anonid", "spacer");
+ var isLTR =
+ document.defaultView.getComputedStyle(this, null).direction == "ltr";
+ var startTime = performance.now();
+ var self = this;
+
+ function nextStep(t) {
+ try {
+ var width = stack.boxObject.width;
+ if (!width) {
+ // Maybe we've been removed from the document.
+ if (self._alive)
+ requestAnimationFrame(nextStep);
+ return;
+ }
+
+ var elapsedTime = t - startTime;
+
+ // Width of chunk is 1/5 (determined by the ratio 2000:400) of the
+ // total width of the progress bar. The left edge of the chunk
+ // starts at -1 and moves all the way to 4. It covers the distance
+ // in 2 seconds.
+ var position = isLTR ? ((elapsedTime % 2000) / 400) - 1 :
+ ((elapsedTime % 2000) / -400) + 4;
+
+ width = width >> 2;
+ spacer.height = stack.boxObject.height;
+ spacer.width = width;
+ spacer.left = width * position;
+
+ requestAnimationFrame(nextStep);
+ } catch (e) {
+ }
+ }
+ requestAnimationFrame(nextStep);
+ ]]></body>
+ </method>
+
+ <constructor>this._init();</constructor>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/radio.xml b/components/bindings/content/radio.xml
new file mode 100644
index 000000000..de3acfbf6
--- /dev/null
+++ b/components/bindings/content/radio.xml
@@ -0,0 +1,526 @@
+<?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="radioBindings"
+ 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="radiogroup" role="xul:radiogroup"
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
+ <resources>
+ <stylesheet src="chrome://global/skin/radio.css"/>
+ </resources>
+
+ <implementation implements="nsIDOMXULSelectControlElement">
+ <constructor>
+ <![CDATA[
+ if (this.getAttribute("disabled") == "true")
+ this.disabled = true;
+
+ var children = this._getRadioChildren();
+ var length = children.length;
+ for (var i = 0; i < length; i++) {
+ if (children[i].getAttribute("selected") == "true") {
+ this.selectedIndex = i;
+ return;
+ }
+ }
+
+ var value = this.value;
+ if (value)
+ this.value = value;
+ else
+ this.selectedIndex = 0;
+ ]]>
+ </constructor>
+
+ <property name="value" onget="return this.getAttribute('value');">
+ <setter>
+ <![CDATA[
+ this.setAttribute("value", val);
+ var children = this._getRadioChildren();
+ for (var i = 0; i < children.length; i++) {
+ if (String(children[i].value) == String(val)) {
+ this.selectedItem = children[i];
+ break;
+ }
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+ <property name="disabled">
+ <getter>
+ <![CDATA[
+ if (this.getAttribute('disabled') == 'true')
+ return true;
+ var children = this._getRadioChildren();
+ for (var i = 0; i < children.length; ++i) {
+ if (!children[i].hidden && !children[i].collapsed && !children[i].disabled)
+ return false;
+ }
+ return true;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val)
+ this.setAttribute('disabled', 'true');
+ else
+ this.removeAttribute('disabled');
+ var children = this._getRadioChildren();
+ for (var i = 0; i < children.length; ++i) {
+ children[i].disabled = val;
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="itemCount" readonly="true"
+ onget="return this._getRadioChildren().length"/>
+
+ <property name="selectedIndex">
+ <getter>
+ <![CDATA[
+ var children = this._getRadioChildren();
+ for (var i = 0; i < children.length; ++i) {
+ if (children[i].selected)
+ return i;
+ }
+ return -1;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this.selectedItem = this._getRadioChildren()[val];
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedItem">
+ <getter>
+ <![CDATA[
+ var children = this._getRadioChildren();
+ for (var i = 0; i < children.length; ++i) {
+ if (children[i].selected)
+ return children[i];
+ }
+ return null;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ var focused = this.getAttribute("focused") == "true";
+ var alreadySelected = false;
+
+ if (val) {
+ alreadySelected = val.getAttribute("selected") == "true";
+ val.setAttribute("focused", focused);
+ val.setAttribute("selected", "true");
+ this.setAttribute("value", val.value);
+ }
+ else {
+ this.removeAttribute("value");
+ }
+
+ // uncheck all other group nodes
+ var children = this._getRadioChildren();
+ var previousItem = null;
+ for (var i = 0; i < children.length; ++i) {
+ if (children[i] != val) {
+ if (children[i].getAttribute("selected") == "true")
+ previousItem = children[i];
+
+ children[i].removeAttribute("selected");
+ children[i].removeAttribute("focused");
+ }
+ }
+
+ var event = document.createEvent("Events");
+ event.initEvent("select", false, true);
+ this.dispatchEvent(event);
+
+ if (!alreadySelected && focused) {
+ // Only report if actual change
+ var myEvent;
+ if (val) {
+ myEvent = document.createEvent("Events");
+ myEvent.initEvent("RadioStateChange", true, true);
+ val.dispatchEvent(myEvent);
+ }
+
+ if (previousItem) {
+ myEvent = document.createEvent("Events");
+ myEvent.initEvent("RadioStateChange", true, true);
+ previousItem.dispatchEvent(myEvent);
+ }
+ }
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="focusedItem">
+ <getter>
+ <![CDATA[
+ var children = this._getRadioChildren();
+ for (var i = 0; i < children.length; ++i) {
+ if (children[i].getAttribute("focused") == "true")
+ return children[i];
+ }
+ return null;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val) val.setAttribute("focused", "true");
+
+ // unfocus all other group nodes
+ var children = this._getRadioChildren();
+ for (var i = 0; i < children.length; ++i) {
+ if (children[i] != val)
+ children[i].removeAttribute("focused");
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="checkAdjacentElement">
+ <parameter name="aNextFlag"/>
+ <body>
+ <![CDATA[
+ var currentElement = this.focusedItem || this.selectedItem;
+ var i;
+ var children = this._getRadioChildren();
+ for (i = 0; i < children.length; ++i ) {
+ if (children[i] == currentElement)
+ break;
+ }
+ var index = i;
+
+ if (aNextFlag) {
+ do {
+ if (++i == children.length)
+ i = 0;
+ if (i == index)
+ break;
+ }
+ while (children[i].hidden || children[i].collapsed || children[i].disabled);
+ // XXX check for display/visibility props too
+
+ this.selectedItem = children[i];
+ children[i].doCommand();
+ }
+ else {
+ do {
+ if (i == 0)
+ i = children.length;
+ if (--i == index)
+ break;
+ }
+ while (children[i].hidden || children[i].collapsed || children[i].disabled);
+ // XXX check for display/visibility props too
+
+ this.selectedItem = children[i];
+ children[i].doCommand();
+ }
+ ]]>
+ </body>
+ </method>
+ <field name="_radioChildren">null</field>
+ <method name="_getRadioChildren">
+ <body>
+ <![CDATA[
+ if (this._radioChildren)
+ return this._radioChildren;
+
+ var radioChildren = [];
+ var doc = this.ownerDocument;
+
+ if (this.hasChildNodes()) {
+ // Don't store the collected child nodes immediately,
+ // collecting the child nodes could trigger constructors
+ // which would blow away our list.
+
+ const nsIDOMNodeFilter = Components.interfaces.nsIDOMNodeFilter;
+ var iterator = doc.createTreeWalker(this,
+ nsIDOMNodeFilter.SHOW_ELEMENT,
+ this._filterRadioGroup);
+ while (iterator.nextNode())
+ radioChildren.push(iterator.currentNode);
+ return this._radioChildren = radioChildren;
+ }
+
+ // We don't have child nodes.
+ const XUL_NS = "http://www.mozilla.org/keymaster/"
+ + "gatekeeper/there.is.only.xul";
+ var elems = doc.getElementsByAttribute("group", this.id);
+ for (var i = 0; i < elems.length; i++) {
+ if ((elems[i].namespaceURI == XUL_NS) &&
+ (elems[i].localName == "radio")) {
+ radioChildren.push(elems[i]);
+ }
+ }
+ return this._radioChildren = radioChildren;
+ ]]>
+ </body>
+ </method>
+ <method name="_filterRadioGroup">
+ <parameter name="node"/>
+ <body>
+ <![CDATA[
+ switch (node.localName) {
+ case "radio": return NodeFilter.FILTER_ACCEPT;
+ case "template":
+ case "radiogroup": return NodeFilter.FILTER_REJECT;
+ default: return NodeFilter.FILTER_SKIP;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="getIndexOfItem">
+ <parameter name="item"/>
+ <body>
+ return this._getRadioChildren().indexOf(item);
+ </body>
+ </method>
+
+ <method name="getItemAtIndex">
+ <parameter name="index"/>
+ <body>
+ <![CDATA[
+ var children = this._getRadioChildren();
+ return (index >= 0 && index < children.length) ? children[index] : null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="appendItem">
+ <parameter name="label"/>
+ <parameter name="value"/>
+ <body>
+ <![CDATA[
+ var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var radio = document.createElementNS(XULNS, "radio");
+ radio.setAttribute("label", label);
+ radio.setAttribute("value", value);
+ this.appendChild(radio);
+ this._radioChildren = null;
+ return radio;
+ ]]>
+ </body>
+ </method>
+
+ <method name="insertItemAt">
+ <parameter name="index"/>
+ <parameter name="label"/>
+ <parameter name="value"/>
+ <body>
+ <![CDATA[
+ var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var radio = document.createElementNS(XULNS, "radio");
+ radio.setAttribute("label", label);
+ radio.setAttribute("value", value);
+ var before = this.getItemAtIndex(index);
+ if (before)
+ before.parentNode.insertBefore(radio, before);
+ else
+ this.appendChild(radio);
+ this._radioChildren = null;
+ return radio;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeItemAt">
+ <parameter name="index"/>
+ <body>
+ <![CDATA[
+ var remove = this.getItemAtIndex(index);
+ if (remove) {
+ remove.parentNode.removeChild(remove);
+ this._radioChildren = null;
+ }
+ return remove;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="mousedown">
+ if (this.disabled)
+ event.preventDefault();
+ </handler>
+
+ <!-- keyboard navigation -->
+ <!-- Here's how keyboard navigation works in radio groups on Windows:
+ The group takes 'focus'
+ The user is then free to navigate around inside the group
+ using the arrow keys. Accessing previous or following radio buttons
+ is done solely through the arrow keys and not the tab button. Tab
+ takes you to the next widget in the tab order -->
+ <handler event="keypress" key=" " phase="target">
+ this.selectedItem = this.focusedItem;
+ this.selectedItem.doCommand();
+ // Prevent page from scrolling on the space key.
+ event.preventDefault();
+ </handler>
+ <handler event="keypress" keycode="VK_UP" phase="target">
+ this.checkAdjacentElement(false);
+ event.stopPropagation();
+ event.preventDefault();
+ </handler>
+ <handler event="keypress" keycode="VK_LEFT" phase="target">
+ // left arrow goes back when we are ltr, forward when we are rtl
+ this.checkAdjacentElement(document.defaultView.getComputedStyle(
+ this, "").direction == "rtl");
+ event.stopPropagation();
+ event.preventDefault();
+ </handler>
+ <handler event="keypress" keycode="VK_DOWN" phase="target">
+ this.checkAdjacentElement(true);
+ event.stopPropagation();
+ event.preventDefault();
+ </handler>
+ <handler event="keypress" keycode="VK_RIGHT" phase="target">
+ // right arrow goes forward when we are ltr, back when we are rtl
+ this.checkAdjacentElement(document.defaultView.getComputedStyle(
+ this, "").direction == "ltr");
+ event.stopPropagation();
+ event.preventDefault();
+ </handler>
+
+ <!-- set a focused attribute on the selected item when the group
+ receives focus so that we can style it as if it were focused even though
+ it is not (Windows platform behaviour is for the group to receive focus,
+ not the item -->
+ <handler event="focus" phase="target">
+ <![CDATA[
+ this.setAttribute("focused", "true");
+ if (this.focusedItem)
+ return;
+
+ var val = this.selectedItem;
+ if (!val || val.disabled || val.hidden || val.collapsed) {
+ var children = this._getRadioChildren();
+ for (var i = 0; i < children.length; ++i) {
+ if (!children[i].hidden && !children[i].collapsed && !children[i].disabled) {
+ val = children[i];
+ break;
+ }
+ }
+ }
+ this.focusedItem = val;
+ ]]>
+ </handler>
+ <handler event="blur" phase="target">
+ this.removeAttribute("focused");
+ this.focusedItem = null;
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="radio" role="xul:radiobutton"
+ extends="chrome://global/content/bindings/general.xml#control-item">
+ <resources>
+ <stylesheet src="chrome://global/skin/radio.css"/>
+ </resources>
+
+ <content>
+ <xul:image class="radio-check" xbl:inherits="disabled,selected"/>
+ <xul:hbox class="radio-label-box" align="center" flex="1">
+ <xul:image class="radio-icon" xbl:inherits="src"/>
+ <xul:label class="radio-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <constructor>
+ <![CDATA[
+ // Just clear out the parent's cached list of radio children
+ var control = this.control;
+ if (control)
+ control._radioChildren = null;
+ ]]>
+ </constructor>
+ <destructor>
+ <![CDATA[
+ if (!this.control)
+ return;
+
+ var radioList = this.control._radioChildren;
+ if (!radioList)
+ return;
+ for (var i = 0; i < radioList.length; ++i) {
+ if (radioList[i] == this) {
+ radioList.splice(i, 1);
+ return;
+ }
+ }
+ ]]>
+ </destructor>
+ <property name="selected" readonly="true">
+ <getter>
+ <![CDATA[
+ return this.hasAttribute('selected');
+ ]]>
+ </getter>
+ </property>
+ <property name="radioGroup" readonly="true" onget="return this.control"/>
+ <property name="control" readonly="true">
+ <getter>
+ <![CDATA[
+ const XUL_NS = "http://www.mozilla.org/keymaster/"
+ + "gatekeeper/there.is.only.xul";
+ var parent = this.parentNode;
+ while (parent) {
+ if ((parent.namespaceURI == XUL_NS) &&
+ (parent.localName == "radiogroup")) {
+ return parent;
+ }
+ parent = parent.parentNode;
+ }
+
+ var group = this.getAttribute("group");
+ if (!group) {
+ return null;
+ }
+
+ parent = this.ownerDocument.getElementById(group);
+ if (!parent ||
+ (parent.namespaceURI != XUL_NS) ||
+ (parent.localName != "radiogroup")) {
+ parent = null;
+ }
+ return parent;
+ ]]>
+ </getter>
+ </property>
+ </implementation>
+ <handlers>
+ <handler event="click" button="0">
+ <![CDATA[
+ if (!this.disabled)
+ this.control.selectedItem = this;
+ ]]>
+ </handler>
+
+ <handler event="mousedown" button="0">
+ <![CDATA[
+ if (!this.disabled)
+ this.control.focusedItem = this;
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/remote-browser.xml b/components/bindings/content/remote-browser.xml
new file mode 100644
index 000000000..b78179944
--- /dev/null
+++ b/components/bindings/content/remote-browser.xml
@@ -0,0 +1,591 @@
+<?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="firefoxBrowserBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="remote-browser" extends="chrome://global/content/bindings/browser.xml#browser">
+
+ <implementation type="application/javascript"
+ implements="nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIRemoteBrowser">
+
+ <field name="_securityUI">null</field>
+
+ <property name="securityUI"
+ readonly="true">
+ <getter><![CDATA[
+ if (!this._securityUI) {
+ // Don't attempt to create the remote web progress if the
+ // messageManager has already gone away
+ if (!this.messageManager)
+ return null;
+
+ let jsm = "resource://gre/modules/RemoteSecurityUI.jsm";
+ let RemoteSecurityUI = Components.utils.import(jsm, {}).RemoteSecurityUI;
+ this._securityUI = new RemoteSecurityUI();
+ }
+
+ // We want to double-wrap the JS implemented interface, so that QI and instanceof works.
+ var ptr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
+ .createInstance(Components.interfaces.nsISupportsInterfacePointer);
+ ptr.data = this._securityUI;
+ return ptr.data.QueryInterface(Components.interfaces.nsISecureBrowserUI);
+ ]]></getter>
+ </property>
+
+ <!-- increases or decreases the browser's network priority -->
+ <method name="adjustPriority">
+ <parameter name="adjustment"/>
+ <body><![CDATA[
+ this.messageManager.sendAsyncMessage("NetworkPrioritizer:AdjustPriority",
+ {adjustment});
+ ]]></body>
+ </method>
+
+ <!-- sets the browser's network priority to a discrete value -->
+ <method name="setPriority">
+ <parameter name="priority"/>
+ <body><![CDATA[
+ this.messageManager.sendAsyncMessage("NetworkPrioritizer:SetPriority",
+ {priority});
+ ]]></body>
+ </method>
+
+ <field name="_controller">null</field>
+
+ <field name="_selectParentHelper">null</field>
+
+ <field name="_remoteWebNavigation">null</field>
+
+ <property name="webNavigation"
+ onget="return this._remoteWebNavigation;"
+ readonly="true"/>
+
+ <field name="_remoteWebProgress">null</field>
+
+ <property name="webProgress" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._remoteWebProgress) {
+ // Don't attempt to create the remote web progress if the
+ // messageManager has already gone away
+ if (!this.messageManager)
+ return null;
+
+ let jsm = "resource://gre/modules/RemoteWebProgress.jsm";
+ let { RemoteWebProgressManager } = Components.utils.import(jsm, {});
+ this._remoteWebProgressManager = new RemoteWebProgressManager(this);
+ this._remoteWebProgress = this._remoteWebProgressManager.topLevelWebProgress;
+ }
+ return this._remoteWebProgress;
+ ]]>
+ </getter>
+ </property>
+
+ <field name="_remoteFinder">null</field>
+
+ <property name="finder" readonly="true">
+ <getter><![CDATA[
+ if (!this._remoteFinder) {
+ // Don't attempt to create the remote finder if the
+ // messageManager has already gone away
+ if (!this.messageManager)
+ return null;
+
+ let jsm = "resource://gre/modules/RemoteFinder.jsm";
+ let { RemoteFinder } = Components.utils.import(jsm, {});
+ this._remoteFinder = new RemoteFinder(this);
+ }
+ return this._remoteFinder;
+ ]]></getter>
+ </property>
+
+ <field name="_documentURI">null</field>
+
+ <field name="_documentContentType">null</field>
+
+ <!--
+ Used by session restore to ensure that currentURI is set so
+ that switch-to-tab works before the tab is fully
+ restored. This function also invokes onLocationChanged
+ listeners in tabbrowser.xml.
+ -->
+ <method name="_setCurrentURI">
+ <parameter name="aURI"/>
+ <body><![CDATA[
+ this._remoteWebProgressManager.setCurrentURI(aURI);
+ ]]></body>
+ </method>
+
+ <property name="documentURI"
+ onget="return this._documentURI;"
+ readonly="true"/>
+
+ <property name="documentContentType"
+ onget="return this._documentContentType;"
+ readonly="true"/>
+
+ <field name="_contentTitle">""</field>
+
+ <property name="contentTitle"
+ onget="return this._contentTitle"
+ readonly="true"/>
+
+ <field name="_characterSet">""</field>
+
+ <property name="characterSet"
+ onget="return this._characterSet">
+ <setter><![CDATA[
+ this.messageManager.sendAsyncMessage("UpdateCharacterSet", {value: val});
+ this._characterSet = val;
+ ]]></setter>
+ </property>
+
+ <field name="_mayEnableCharacterEncodingMenu">null</field>
+
+ <property name="mayEnableCharacterEncodingMenu"
+ onget="return this._mayEnableCharacterEncodingMenu;"
+ readonly="true"/>
+
+ <field name="_contentWindow">null</field>
+
+ <property name="contentWindow"
+ onget="return null"
+ readonly="true"/>
+
+ <property name="contentWindowAsCPOW"
+ onget="return this._contentWindow"
+ readonly="true"/>
+
+ <property name="contentDocument"
+ onget="return null"
+ readonly="true"/>
+
+ <field name="_contentPrincipal">null</field>
+
+ <property name="contentPrincipal"
+ onget="return this._contentPrincipal"
+ readonly="true"/>
+
+ <property name="contentDocumentAsCPOW"
+ onget="return this.contentWindowAsCPOW ? this.contentWindowAsCPOW.document : null"
+ readonly="true"/>
+
+ <field name="_imageDocument">null</field>
+
+ <property name="imageDocument"
+ onget="return this._imageDocument"
+ readonly="true"/>
+
+ <field name="_fullZoom">1</field>
+ <property name="fullZoom">
+ <getter><![CDATA[
+ return this._fullZoom;
+ ]]></getter>
+ <setter><![CDATA[
+ let changed = val.toFixed(2) != this._fullZoom.toFixed(2);
+
+ this._fullZoom = val;
+ this.messageManager.sendAsyncMessage("FullZoom", {value: val});
+
+ if (changed) {
+ let event = new Event("FullZoomChange", {bubbles: true});
+ this.dispatchEvent(event);
+ }
+ ]]></setter>
+ </property>
+
+ <field name="_textZoom">1</field>
+ <property name="textZoom">
+ <getter><![CDATA[
+ return this._textZoom;
+ ]]></getter>
+ <setter><![CDATA[
+ let changed = val.toFixed(2) != this._textZoom.toFixed(2);
+
+ this._textZoom = val;
+ this.messageManager.sendAsyncMessage("TextZoom", {value: val});
+
+ if (changed) {
+ let event = new Event("TextZoomChange", {bubbles: true});
+ this.dispatchEvent(event);
+ }
+ ]]></setter>
+ </property>
+
+ <field name="_isSyntheticDocument">false</field>
+ <property name="isSyntheticDocument">
+ <getter><![CDATA[
+ return this._isSyntheticDocument;
+ ]]></getter>
+ </property>
+
+ <property name="hasContentOpener">
+ <getter><![CDATA[
+ let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
+ return frameLoader.tabParent.hasContentOpener;
+ ]]></getter>
+ </property>
+
+ <field name="_outerWindowID">null</field>
+ <property name="outerWindowID"
+ onget="return this._outerWindowID"
+ readonly="true"/>
+
+ <field name="_innerWindowID">null</field>
+ <property name="innerWindowID">
+ <getter><![CDATA[
+ return this._innerWindowID;
+ ]]></getter>
+ </property>
+
+ <property name="docShellIsActive">
+ <getter>
+ <![CDATA[
+ let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
+ return frameLoader.tabParent.docShellIsActive;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
+ frameLoader.tabParent.docShellIsActive = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="preserveLayers">
+ <parameter name="preserve"/>
+ <body><![CDATA[
+ let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
+ if (frameLoader.tabParent) {
+ frameLoader.tabParent.preserveLayers(preserve);
+ }
+ ]]></body>
+ </method>
+
+ <field name="_manifestURI"/>
+ <property name="manifestURI"
+ onget="return this._manifestURI"
+ readonly="true"/>
+
+ <field name="mDestroyed">false</field>
+
+ <field name="_permitUnloadId">0</field>
+
+ <method name="getInPermitUnload">
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ let id = this._permitUnloadId++;
+ let mm = this.messageManager;
+ mm.sendAsyncMessage("InPermitUnload", {id});
+ mm.addMessageListener("InPermitUnload", function listener(msg) {
+ if (msg.data.id != id) {
+ return;
+ }
+ aCallback(msg.data.inPermitUnload);
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="permitUnload">
+ <body>
+ <![CDATA[
+ const kTimeout = 5000;
+
+ let finished = false;
+ let responded = false;
+ let permitUnload;
+ let id = this._permitUnloadId++;
+ let mm = this.messageManager;
+ let Services = Components.utils.import("resource://gre/modules/Services.jsm", {}).Services;
+
+ let msgListener = msg => {
+ if (msg.data.id != id) {
+ return;
+ }
+ if (msg.data.kind == "start") {
+ responded = true;
+ return;
+ }
+ done(msg.data.permitUnload);
+ };
+
+ let observer = subject => {
+ if (subject == mm) {
+ done(true);
+ }
+ };
+
+ function done(result) {
+ finished = true;
+ permitUnload = result;
+ mm.removeMessageListener("PermitUnload", msgListener);
+ Services.obs.removeObserver(observer, "message-manager-close");
+ }
+
+ mm.sendAsyncMessage("PermitUnload", {id});
+ mm.addMessageListener("PermitUnload", msgListener);
+ Services.obs.addObserver(observer, "message-manager-close", false);
+
+ let timedOut = false;
+ function timeout() {
+ if (!responded) {
+ timedOut = true;
+ }
+
+ // Dispatch something to ensure that the main thread wakes up.
+ Services.tm.mainThread.dispatch(function() {}, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+ }
+
+ let timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(timeout, kTimeout, timer.TYPE_ONE_SHOT);
+
+ while (!finished && !timedOut) {
+ Services.tm.currentThread.processNextEvent(true);
+ }
+
+ return {permitUnload, timedOut};
+ ]]>
+ </body>
+ </method>
+
+ <constructor>
+ <![CDATA[
+ /*
+ * Don't try to send messages from this function. The message manager for
+ * the <browser> element may not be initialized yet.
+ */
+
+ this._remoteWebNavigation = Components.classes["@mozilla.org/remote-web-navigation;1"]
+ .createInstance(Components.interfaces.nsIWebNavigation);
+ this._remoteWebNavigationImpl = this._remoteWebNavigation.wrappedJSObject;
+ this._remoteWebNavigationImpl.swapBrowser(this);
+
+ // Initialize contentPrincipal to the about:blank principal for this loadcontext
+ let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
+ let aboutBlank = Services.io.newURI("about:blank", null, null);
+ let ssm = Services.scriptSecurityManager;
+ this._contentPrincipal = ssm.getLoadContextCodebasePrincipal(aboutBlank, this.loadContext);
+
+ this.messageManager.addMessageListener("Browser:Init", this);
+ this.messageManager.addMessageListener("DOMTitleChanged", this);
+ this.messageManager.addMessageListener("ImageDocumentLoaded", this);
+ this.messageManager.addMessageListener("FullZoomChange", this);
+ this.messageManager.addMessageListener("TextZoomChange", this);
+ this.messageManager.addMessageListener("ZoomChangeUsingMouseWheel", this);
+ this.messageManager.addMessageListener("DOMFullscreen:RequestExit", this);
+ this.messageManager.addMessageListener("DOMFullscreen:RequestRollback", this);
+ this.messageManager.addMessageListener("MozApplicationManifest", this);
+ this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);
+
+ if (this.hasAttribute("selectmenulist")) {
+ this.messageManager.addMessageListener("Forms:ShowDropDown", this);
+ this.messageManager.addMessageListener("Forms:HideDropDown", this);
+ this.messageManager.loadFrameScript("chrome://global/content/select-child.js", true);
+ }
+
+ if (!this.hasAttribute("disablehistory")) {
+ Services.obs.addObserver(this, "browser:purge-session-history", true);
+ }
+
+ let jsm = "resource://gre/modules/RemoteController.jsm";
+ let RemoteController = Components.utils.import(jsm, {}).RemoteController;
+ this._controller = new RemoteController(this);
+ this.controllers.appendController(this._controller);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ this.destroy();
+ ]]>
+ </destructor>
+
+ <!-- This is necessary because the destructor doesn't always get called when
+ we are removed from a tabbrowser. This will be explicitly called by tabbrowser.
+
+ Note: This overrides the destroy() method from browser.xml. -->
+ <method name="destroy">
+ <body><![CDATA[
+ // Make sure that any open select is closed.
+ if (this._selectParentHelper) {
+ let menulist = document.getElementById(this.getAttribute("selectmenulist"));
+ this._selectParentHelper.hide(menulist, this);
+ }
+
+ if (this.mDestroyed)
+ return;
+ this.mDestroyed = true;
+
+ try {
+ this.controllers.removeController(this._controller);
+ } catch (ex) {
+ // This can fail when this browser element is not attached to a
+ // BrowserDOMWindow.
+ }
+
+ if (!this.hasAttribute("disablehistory")) {
+ let Services = Components.utils.import("resource://gre/modules/Services.jsm", {}).Services;
+ try {
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ } catch (ex) {
+ // It's not clear why this sometimes throws an exception.
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="receiveMessage">
+ <parameter name="aMessage"/>
+ <body><![CDATA[
+ let data = aMessage.data;
+ switch (aMessage.name) {
+ case "Browser:Init":
+ this._outerWindowID = data.outerWindowID;
+ break;
+ case "DOMTitleChanged":
+ this._contentTitle = data.title;
+ break;
+ case "ImageDocumentLoaded":
+ this._imageDocument = {
+ width: data.width,
+ height: data.height
+ };
+ break;
+
+ case "Forms:ShowDropDown": {
+ if (!this._selectParentHelper) {
+ this._selectParentHelper =
+ Cu.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
+ }
+
+ let menulist = document.getElementById(this.getAttribute("selectmenulist"));
+ menulist.menupopup.style.direction = data.direction;
+
+ let zoom = Services.prefs.getBoolPref("browser.zoom.full") ||
+ this.isSyntheticDocument ? this._fullZoom : this._textZoom;
+ this._selectParentHelper.populate(menulist, data.options, data.selectedIndex, zoom);
+ this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
+ break;
+ }
+
+ case "FullZoomChange": {
+ this._fullZoom = data.value;
+ let event = document.createEvent("Events");
+ event.initEvent("FullZoomChange", true, false);
+ this.dispatchEvent(event);
+ break;
+ }
+
+ case "TextZoomChange": {
+ this._textZoom = data.value;
+ let event = document.createEvent("Events");
+ event.initEvent("TextZoomChange", true, false);
+ this.dispatchEvent(event);
+ break;
+ }
+
+ case "ZoomChangeUsingMouseWheel": {
+ let event = document.createEvent("Events");
+ event.initEvent("ZoomChangeUsingMouseWheel", true, false);
+ this.dispatchEvent(event);
+ break;
+ }
+
+ case "Forms:HideDropDown": {
+ if (this._selectParentHelper) {
+ let menulist = document.getElementById(this.getAttribute("selectmenulist"));
+ this._selectParentHelper.hide(menulist, this);
+ }
+ break;
+ }
+
+ case "DOMFullscreen:RequestExit": {
+ let windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ windowUtils.exitFullscreen();
+ break;
+ }
+
+ case "DOMFullscreen:RequestRollback": {
+ let windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ windowUtils.remoteFrameFullscreenReverted();
+ break;
+ }
+
+ case "MozApplicationManifest":
+ this._manifestURI = aMessage.data.manifest;
+ break;
+
+ default:
+ // Delegate to browser.xml.
+ return this._receiveMessage(aMessage);
+ }
+ return undefined;
+ ]]></body>
+ </method>
+
+ <method name="enableDisableCommands">
+ <parameter name="aAction"/>
+ <parameter name="aEnabledLength"/>
+ <parameter name="aEnabledCommands"/>
+ <parameter name="aDisabledLength"/>
+ <parameter name="aDisabledCommands"/>
+ <body>
+ if (this._controller) {
+ this._controller.enableDisableCommands(aAction,
+ aEnabledLength, aEnabledCommands,
+ aDisabledLength, aDisabledCommands);
+ }
+ </body>
+ </method>
+
+ <method name="purgeSessionHistory">
+ <body>
+ <![CDATA[
+ try {
+ this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
+ } catch (ex) {
+ // This can throw if the browser has started to go away.
+ if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) {
+ throw ex;
+ }
+ }
+ this._remoteWebNavigationImpl.canGoBack = false;
+ this._remoteWebNavigationImpl.canGoForward = false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="createAboutBlankContentViewer">
+ <parameter name="aPrincipal"/>
+ <body>
+ <![CDATA[
+ this.messageManager.sendAsyncMessage("Browser:CreateAboutBlank", aPrincipal);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ <handlers>
+ <handler event="dragstart">
+ <![CDATA[
+ // If we're a remote browser dealing with a dragstart, stop it
+ // from propagating up, since our content process should be dealing
+ // with the mouse movement.
+ event.stopPropagation();
+ ]]>
+ </handler>
+ </handlers>
+
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/resizer.xml b/components/bindings/content/resizer.xml
new file mode 100644
index 000000000..006877a4f
--- /dev/null
+++ b/components/bindings/content/resizer.xml
@@ -0,0 +1,39 @@
+<?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="resizerBindings"
+ xmlns="http://www.mozilla.org/xbl">
+
+ <binding id="resizer">
+ <resources>
+ <stylesheet src="chrome://global/skin/resizer.css"/>
+ </resources>
+ <implementation>
+ <constructor>
+ <![CDATA[
+ // don't do this for viewport resizers; causes a crash related to
+ // bugs 563665 and 581536 otherwise
+ if (this.parentNode == this.ownerDocument.documentElement)
+ return;
+
+ // if the direction is rtl, set the rtl attribute so that the
+ // stylesheet can use this to make the cursor appear properly
+ var cs = window.getComputedStyle(this, "");
+ if (cs.writingMode === undefined || cs.writingMode == "horizontal-tb") {
+ if (cs.direction == "rtl") {
+ this.setAttribute("rtl", "true");
+ }
+ } else if (cs.writingMode.endsWith("-rl")) {
+ // writing-modes 'vertical-rl' and 'sideways-rl' want rtl resizers,
+ // as they will appear at the bottom left of the element
+ this.setAttribute("rtl", "true");
+ }
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/richlistbox.xml b/components/bindings/content/richlistbox.xml
new file mode 100644
index 000000000..dd04a0cff
--- /dev/null
+++ b/components/bindings/content/richlistbox.xml
@@ -0,0 +1,589 @@
+<?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="richlistboxBindings"
+ 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="richlistbox"
+ extends="chrome://global/content/bindings/listbox.xml#listbox-base">
+ <resources>
+ <stylesheet src="chrome://global/skin/richlistbox.css"/>
+ </resources>
+
+ <content>
+ <children includes="listheader"/>
+ <xul:scrollbox allowevents="true" orient="vertical" anonid="main-box"
+ flex="1" style="overflow: auto;" xbl:inherits="dir,pack">
+ <children/>
+ </xul:scrollbox>
+ </content>
+
+ <implementation>
+ <field name="_scrollbox">
+ document.getAnonymousElementByAttribute(this, "anonid", "main-box");
+ </field>
+ <field name="scrollBoxObject">
+ this._scrollbox.boxObject;
+ </field>
+ <constructor>
+ <![CDATA[
+ // add a template build listener
+ if (this.builder)
+ this.builder.addListener(this._builderListener);
+ else
+ this._refreshSelection();
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ // remove the template build listener
+ if (this.builder)
+ this.builder.removeListener(this._builderListener);
+ ]]>
+ </destructor>
+
+ <!-- Overriding baselistbox -->
+ <method name="_fireOnSelect">
+ <body>
+ <![CDATA[
+ // make sure not to modify last-selected when suppressing select events
+ // (otherwise we'll lose the selection when a template gets rebuilt)
+ if (this._suppressOnSelect || this.suppressOnSelect)
+ return;
+
+ // remember the current item and all selected items with IDs
+ var state = this.currentItem ? this.currentItem.id : "";
+ if (this.selType == "multiple" && this.selectedCount) {
+ let getId = function getId(aItem) { return aItem.id; }
+ state += " " + [... this.selectedItems].filter(getId).map(getId).join(" ");
+ }
+ if (state)
+ this.setAttribute("last-selected", state);
+ else
+ this.removeAttribute("last-selected");
+
+ // preserve the index just in case no IDs are available
+ if (this.currentIndex > -1)
+ this._currentIndex = this.currentIndex + 1;
+
+ var event = document.createEvent("Events");
+ event.initEvent("select", true, true);
+ this.dispatchEvent(event);
+
+ // always call this (allows a commandupdater without controller)
+ document.commandDispatcher.updateCommands("richlistbox-select");
+ ]]>
+ </body>
+ </method>
+
+ <!-- We override base-listbox here because those methods don't take dir
+ into account on listbox (which doesn't support dir yet) -->
+ <method name="getNextItem">
+ <parameter name="aStartItem"/>
+ <parameter name="aDelta"/>
+ <body>
+ <![CDATA[
+ var prop = this.dir == "reverse" && this._mayReverse ?
+ "previousSibling" :
+ "nextSibling";
+ while (aStartItem) {
+ aStartItem = aStartItem[prop];
+ if (aStartItem && aStartItem instanceof
+ Components.interfaces.nsIDOMXULSelectControlItemElement &&
+ (!this._userSelecting || this._canUserSelect(aStartItem))) {
+ --aDelta;
+ if (aDelta == 0)
+ return aStartItem;
+ }
+ }
+ return null;
+ ]]></body>
+ </method>
+
+ <method name="getPreviousItem">
+ <parameter name="aStartItem"/>
+ <parameter name="aDelta"/>
+ <body>
+ <![CDATA[
+ var prop = this.dir == "reverse" && this._mayReverse ?
+ "nextSibling" :
+ "previousSibling";
+ while (aStartItem) {
+ aStartItem = aStartItem[prop];
+ if (aStartItem && aStartItem instanceof
+ Components.interfaces.nsIDOMXULSelectControlItemElement &&
+ (!this._userSelecting || this._canUserSelect(aStartItem))) {
+ --aDelta;
+ if (aDelta == 0)
+ return aStartItem;
+ }
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="appendItem">
+ <parameter name="aLabel"/>
+ <parameter name="aValue"/>
+ <body>
+ return this.insertItemAt(-1, aLabel, aValue);
+ </body>
+ </method>
+
+ <method name="insertItemAt">
+ <parameter name="aIndex"/>
+ <parameter name="aLabel"/>
+ <parameter name="aValue"/>
+ <body>
+ const XULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var item =
+ this.ownerDocument.createElementNS(XULNS, "richlistitem");
+ item.setAttribute("value", aValue);
+
+ var label = this.ownerDocument.createElementNS(XULNS, "label");
+ label.setAttribute("value", aLabel);
+ label.setAttribute("flex", "1");
+ label.setAttribute("crop", "end");
+ item.appendChild(label);
+
+ var before = this.getItemAtIndex(aIndex);
+ if (!before)
+ this.appendChild(item);
+ else
+ this.insertBefore(item, before);
+
+ return item;
+ </body>
+ </method>
+
+ <property name="itemCount" readonly="true"
+ onget="return this.children.length"/>
+
+ <method name="getIndexOfItem">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ // don't search the children, if we're looking for none of them
+ if (aItem == null)
+ return -1;
+
+ return this.children.indexOf(aItem);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getItemAtIndex">
+ <parameter name="aIndex"/>
+ <body>
+ return this.children[aIndex] || null;
+ </body>
+ </method>
+
+ <method name="ensureIndexIsVisible">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ // work around missing implementation in scrollBoxObject
+ return this.ensureElementIsVisible(this.getItemAtIndex(aIndex));
+ ]]>
+ </body>
+ </method>
+
+ <method name="ensureElementIsVisible">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ if (!aElement)
+ return;
+ var targetRect = aElement.getBoundingClientRect();
+ var scrollRect = this._scrollbox.getBoundingClientRect();
+ var offset = targetRect.top - scrollRect.top;
+ if (offset >= 0) {
+ // scrollRect.bottom wouldn't take a horizontal scroll bar into account
+ let scrollRectBottom = scrollRect.top + this._scrollbox.clientHeight;
+ offset = targetRect.bottom - scrollRectBottom;
+ if (offset <= 0)
+ return;
+ }
+ this._scrollbox.scrollTop += offset;
+ ]]>
+ </body>
+ </method>
+
+ <method name="scrollToIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ var item = this.getItemAtIndex(aIndex);
+ if (item)
+ this.scrollBoxObject.scrollToElement(item);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getNumberOfVisibleRows">
+ <!-- returns the number of currently visible rows -->
+ <!-- don't rely on this function, if the items' height can vary! -->
+ <body>
+ <![CDATA[
+ var children = this.children;
+
+ for (var top = 0; top < children.length && !this._isItemVisible(children[top]); top++);
+ for (var ix = top; ix < children.length && this._isItemVisible(children[ix]); ix++);
+
+ return ix - top;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getIndexOfFirstVisibleRow">
+ <body>
+ <![CDATA[
+ var children = this.children;
+
+ for (var ix = 0; ix < children.length; ix++)
+ if (this._isItemVisible(children[ix]))
+ return ix;
+
+ return -1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getRowCount">
+ <body>
+ <![CDATA[
+ return this.children.length;
+ ]]>
+ </body>
+ </method>
+
+ <method name="scrollOnePage">
+ <parameter name="aDirection"/> <!-- Must be -1 or 1 -->
+ <body>
+ <![CDATA[
+ var children = this.children;
+
+ if (children.length == 0)
+ return 0;
+
+ // If nothing is selected, we just select the first element
+ // at the extreme we're moving away from
+ if (!this.currentItem)
+ return aDirection == -1 ? children.length : 0;
+
+ // If the current item is visible, scroll by one page so that
+ // the new current item is at approximately the same position as
+ // the existing current item.
+ if (this._isItemVisible(this.currentItem))
+ this.scrollBoxObject.scrollBy(0, this.scrollBoxObject.height * aDirection);
+
+ // Figure out, how many items fully fit into the view port
+ // (including the currently selected one), and determine
+ // the index of the first one lying (partially) outside
+ var height = this.scrollBoxObject.height;
+ var startBorder = this.currentItem.boxObject.y;
+ if (aDirection == -1)
+ startBorder += this.currentItem.boxObject.height;
+
+ var index = this.currentIndex;
+ for (var ix = index; 0 <= ix && ix < children.length; ix += aDirection) {
+ var boxObject = children[ix].boxObject;
+ if (boxObject.height == 0)
+ continue; // hidden children have a y of 0
+ var endBorder = boxObject.y + (aDirection == -1 ? boxObject.height : 0);
+ if ((endBorder - startBorder) * aDirection > height)
+ break; // we've reached the desired distance
+ index = ix;
+ }
+
+ return index != this.currentIndex ? index - this.currentIndex : aDirection;
+ ]]>
+ </body>
+ </method>
+
+ <!-- richlistbox specific -->
+ <property name="children" readonly="true">
+ <getter>
+ <![CDATA[
+ var childNodes = [];
+ var isReverse = this.dir == "reverse" && this._mayReverse;
+ var child = isReverse ? this.lastChild : this.firstChild;
+ var prop = isReverse ? "previousSibling" : "nextSibling";
+ while (child) {
+ if (child instanceof Components.interfaces.nsIDOMXULSelectControlItemElement)
+ childNodes.push(child);
+ child = child[prop];
+ }
+ return childNodes;
+ ]]>
+ </getter>
+ </property>
+
+ <field name="_builderListener" readonly="true">
+ <![CDATA[
+ ({
+ mOuter: this,
+ item: null,
+ willRebuild: function(builder) { },
+ didRebuild: function(builder) {
+ this.mOuter._refreshSelection();
+ }
+ });
+ ]]>
+ </field>
+
+ <method name="_refreshSelection">
+ <body>
+ <![CDATA[
+ // when this method is called, we know that either the currentItem
+ // and selectedItems we have are null (ctor) or a reference to an
+ // element no longer in the DOM (template).
+
+ // first look for the last-selected attribute
+ var state = this.getAttribute("last-selected");
+ if (state) {
+ var ids = state.split(" ");
+
+ var suppressSelect = this._suppressOnSelect;
+ this._suppressOnSelect = true;
+ this.clearSelection();
+ for (let i = 1; i < ids.length; i++) {
+ var selectedItem = document.getElementById(ids[i]);
+ if (selectedItem)
+ this.addItemToSelection(selectedItem);
+ }
+
+ var currentItem = document.getElementById(ids[0]);
+ if (!currentItem && this._currentIndex)
+ currentItem = this.getItemAtIndex(Math.min(
+ this._currentIndex - 1, this.getRowCount()));
+ if (currentItem) {
+ this.currentItem = currentItem;
+ if (this.selType != "multiple" && this.selectedCount == 0)
+ this.selectedItem = currentItem;
+
+ if (this.scrollBoxObject.height) {
+ this.ensureElementIsVisible(currentItem);
+ }
+ else {
+ // XXX hack around a bug in ensureElementIsVisible as it will
+ // scroll beyond the last element, bug 493645.
+ var previousElement = this.dir == "reverse" ? currentItem.nextSibling :
+ currentItem.previousSibling;
+ this.ensureElementIsVisible(previousElement);
+ }
+ }
+ this._suppressOnSelect = suppressSelect;
+ // XXX actually it's just a refresh, but at least
+ // the Extensions manager expects this:
+ this._fireOnSelect();
+ return;
+ }
+
+ // try to restore the selected items according to their IDs
+ // (applies after a template rebuild, if last-selected was not set)
+ if (this.selectedItems) {
+ let itemIds = [];
+ for (let i = this.selectedCount - 1; i >= 0; i--) {
+ let selectedItem = this.selectedItems[i];
+ itemIds.push(selectedItem.id);
+ this.selectedItems.remove(selectedItem);
+ }
+ for (let i = 0; i < itemIds.length; i++) {
+ let selectedItem = document.getElementById(itemIds[i]);
+ if (selectedItem) {
+ this.selectedItems.append(selectedItem);
+ }
+ }
+ }
+ if (this.currentItem && this.currentItem.id)
+ this.currentItem = document.getElementById(this.currentItem.id);
+ else
+ this.currentItem = null;
+
+ // if we have no previously current item or if the above check fails to
+ // find the previous nodes (which causes it to clear selection)
+ if (!this.currentItem && this.selectedCount == 0) {
+ this.currentIndex = this._currentIndex ? this._currentIndex - 1 : 0;
+
+ // cf. listbox constructor:
+ // select items according to their attributes
+ var children = this.children;
+ for (let i = 0; i < children.length; ++i) {
+ if (children[i].getAttribute("selected") == "true")
+ this.selectedItems.append(children[i]);
+ }
+ }
+
+ if (this.selType != "multiple" && this.selectedCount == 0)
+ this.selectedItem = this.currentItem;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_isItemVisible">
+ <parameter name="aItem"/>
+ <body>
+ <![CDATA[
+ if (!aItem)
+ return false;
+
+ var y = this.scrollBoxObject.positionY + this.scrollBoxObject.y;
+
+ // Partially visible items are also considered visible
+ return (aItem.boxObject.y + aItem.boxObject.height > y) &&
+ (aItem.boxObject.y < y + this.scrollBoxObject.height);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_currentIndex">null</field>
+
+ <!-- For backwards-compatibility and for convenience.
+ Use getIndexOfItem instead. -->
+ <method name="getIndexOf">
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ return this.getIndexOfItem(aElement);
+ ]]>
+ </body>
+ </method>
+
+ <!-- For backwards-compatibility and for convenience.
+ Use ensureElementIsVisible instead -->
+ <method name="ensureSelectedElementIsVisible">
+ <body>
+ <![CDATA[
+ return this.ensureElementIsVisible(this.selectedItem);
+ ]]>
+ </body>
+ </method>
+
+ <!-- For backwards-compatibility and for convenience.
+ Use moveByOffset instead. -->
+ <method name="goUp">
+ <body>
+ <![CDATA[
+ var index = this.currentIndex;
+ this.moveByOffset(-1, true, false);
+ return index != this.currentIndex;
+ ]]>
+ </body>
+ </method>
+ <method name="goDown">
+ <body>
+ <![CDATA[
+ var index = this.currentIndex;
+ this.moveByOffset(1, true, false);
+ return index != this.currentIndex;
+ ]]>
+ </body>
+ </method>
+
+ <!-- deprecated (is implied by currentItem and selectItem) -->
+ <method name="fireActiveItemEvent"><body/></method>
+ </implementation>
+
+ <handlers>
+ <handler event="click">
+ <![CDATA[
+ // clicking into nothing should unselect
+ if (event.originalTarget == this._scrollbox) {
+ this.clearSelection();
+ this.currentItem = null;
+ }
+ ]]>
+ </handler>
+
+ <handler event="MozSwipeGesture">
+ <![CDATA[
+ // Only handle swipe gestures up and down
+ switch (event.direction) {
+ case event.DIRECTION_DOWN:
+ this._scrollbox.scrollTop = this._scrollbox.scrollHeight;
+ break;
+ case event.DIRECTION_UP:
+ this._scrollbox.scrollTop = 0;
+ break;
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="richlistitem"
+ extends="chrome://global/content/bindings/listbox.xml#listitem">
+ <content>
+ <children/>
+ </content>
+
+ <resources>
+ <stylesheet src="chrome://global/skin/richlistbox.css"/>
+ </resources>
+
+ <implementation>
+ <destructor>
+ <![CDATA[
+ var control = this.control;
+ if (!control)
+ return;
+ // When we are destructed and we are current or selected, unselect ourselves
+ // so that richlistbox's selection doesn't point to something not in the DOM.
+ // We don't want to reset last-selected, so we set _suppressOnSelect.
+ if (this.selected) {
+ var suppressSelect = control._suppressOnSelect;
+ control._suppressOnSelect = true;
+ control.removeItemFromSelection(this);
+ control._suppressOnSelect = suppressSelect;
+ }
+ if (this.current)
+ control.currentItem = null;
+ ]]>
+ </destructor>
+
+ <property name="label" readonly="true">
+ <!-- Setter purposely not implemented; the getter returns a
+ concatentation of label text to expose via accessibility APIs -->
+ <getter>
+ <![CDATA[
+ const XULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ return Array.map(this.getElementsByTagNameNS(XULNS, "label"),
+ label => label.value)
+ .join(" ");
+ ]]>
+ </getter>
+ </property>
+
+ <property name="searchLabel">
+ <getter>
+ <![CDATA[
+ return this.hasAttribute("searchlabel") ?
+ this.getAttribute("searchlabel") : this.label;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ if (val !== null)
+ this.setAttribute("searchlabel", val);
+ else
+ // fall back to the label property (default value)
+ this.removeAttribute("searchlabel");
+ return val;
+ ]]>
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/scale.xml b/components/bindings/content/scale.xml
new file mode 100644
index 000000000..3e5f5aeb2
--- /dev/null
+++ b/components/bindings/content/scale.xml
@@ -0,0 +1,232 @@
+<?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="scaleBindings"
+ 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="scalethumb" extends="xul:button" role="xul:thumb">
+ <resources>
+ <stylesheet src="chrome://global/skin/scale.css"/>
+ </resources>
+ </binding>
+
+ <binding id="scaleslider" display="xul:slider"
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
+ <resources>
+ <stylesheet src="chrome://global/skin/scale.css"/>
+ </resources>
+ </binding>
+
+ <binding id="scale" role="xul:scale"
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
+ <resources>
+ <stylesheet src="chrome://global/skin/scale.css"/>
+ </resources>
+
+ <content align="center" pack="center">
+ <xul:slider anonid="slider" class="scale-slider" snap="true" flex="1"
+ xbl:inherits="disabled,orient,dir,curpos=value,minpos=min,maxpos=max,increment,pageincrement,movetoclick">
+ <xul:thumb class="scale-thumb" xbl:inherits="disabled,orient"/>
+ </xul:slider>
+ </content>
+
+ <implementation implements="nsISliderListener">
+ <property name="value" onget="return this._getIntegerAttribute('curpos', 0);"
+ onset="return this._setIntegerAttribute('curpos', val);"/>
+ <property name="min" onget="return this._getIntegerAttribute('minpos', 0);"
+ onset="return this._setIntegerAttribute('minpos', val);"/>
+ <property name="max" onget="return this._getIntegerAttribute('maxpos', 100);"
+ onset="return this._setIntegerAttribute('maxpos', val);"/>
+ <property name="increment" onget="return this._getIntegerAttribute('increment', 1);"
+ onset="return this._setIntegerAttribute('increment', val);"/>
+ <property name="pageIncrement" onget="return this._getIntegerAttribute('pageincrement', 10);"
+ onset="return this._setIntegerAttribute('pageincrement', val);"/>
+
+ <property name="_slider" readonly="true">
+ <getter>
+ if (!this._sliderElement)
+ this._sliderElement = document.getAnonymousElementByAttribute(this, "anonid", "slider");
+ return this._sliderElement;
+ </getter>
+ </property>
+
+ <constructor>
+ <![CDATA[
+ this._userChanged = false;
+ var value = parseInt(this.getAttribute("value"), 10);
+ if (!isNaN(value))
+ this.value = value;
+ else if (this.min > 0)
+ this.value = this.min;
+ else if (this.max < 0)
+ this.value = this.max;
+ ]]>
+ </constructor>
+
+ <method name="_getIntegerAttribute">
+ <parameter name="aAttr"/>
+ <parameter name="aDefaultValue"/>
+ <body>
+ var value = this._slider.getAttribute(aAttr);
+ var intvalue = parseInt(value, 10);
+ if (!isNaN(intvalue))
+ return intvalue;
+ return aDefaultValue;
+ </body>
+ </method>
+
+ <method name="_setIntegerAttribute">
+ <parameter name="aAttr"/>
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var intvalue = parseInt(aValue, 10);
+ if (!isNaN(intvalue)) {
+ if (aAttr == "curpos") {
+ if (intvalue < this.min)
+ intvalue = this.min;
+ else if (intvalue > this.max)
+ intvalue = this.max;
+ }
+ this._slider.setAttribute(aAttr, intvalue);
+ }
+ return aValue;
+ ]]>
+ </body>
+ </method>
+
+ <method name="decrease">
+ <body>
+ <![CDATA[
+ var newpos = this.value - this.increment;
+ var startpos = this.min;
+ this.value = (newpos > startpos) ? newpos : startpos;
+ ]]>
+ </body>
+ </method>
+ <method name="increase">
+ <body>
+ <![CDATA[
+ var newpos = this.value + this.increment;
+ var endpos = this.max;
+ this.value = (newpos < endpos) ? newpos : endpos;
+ ]]>
+ </body>
+ </method>
+
+ <method name="decreasePage">
+ <body>
+ <![CDATA[
+ var newpos = this.value - this.pageIncrement;
+ var startpos = this.min;
+ this.value = (newpos > startpos) ? newpos : startpos;
+ ]]>
+ </body>
+ </method>
+ <method name="increasePage">
+ <body>
+ <![CDATA[
+ var newpos = this.value + this.pageIncrement;
+ var endpos = this.max;
+ this.value = (newpos < endpos) ? newpos : endpos;
+ ]]>
+ </body>
+ </method>
+
+ <method name="valueChanged">
+ <parameter name="which"/>
+ <parameter name="newValue"/>
+ <parameter name="userChanged"/>
+ <body>
+ <![CDATA[
+ switch (which) {
+ case "curpos":
+ this.setAttribute("value", newValue);
+
+ // in the future, only fire the change event when userChanged
+ // or _userChanged is true
+ var changeEvent = document.createEvent("Events");
+ changeEvent.initEvent("change", true, true);
+ this.dispatchEvent(changeEvent);
+ break;
+
+ case "minpos":
+ this.setAttribute("min", newValue);
+ break;
+
+ case "maxpos":
+ this.setAttribute("max", newValue);
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="dragStateChanged">
+ <parameter name="isDragging"/>
+ <body/>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="keypress" keycode="VK_LEFT" preventdefault="true">
+ <![CDATA[
+ this._userChanged = true;
+ (this.orient != "vertical" && this.dir == "reverse") ? this.increase() : this.decrease();
+ this._userChanged = false;
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_RIGHT" preventdefault="true">
+ <![CDATA[
+ this._userChanged = true;
+ (this.orient != "vertical" && this.dir == "reverse") ? this.decrease() : this.increase();
+ this._userChanged = false;
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_UP" preventdefault="true">
+ <![CDATA[
+ this._userChanged = true;
+ (this.orient == "vertical" && this.dir != "reverse") ? this.decrease() : this.increase();
+ this._userChanged = false;
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_DOWN" preventdefault="true">
+ <![CDATA[
+ this._userChanged = true;
+ (this.orient == "vertical" && this.dir != "reverse") ? this.increase() : this.decrease();
+ this._userChanged = false;
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_PAGE_UP" preventdefault="true">
+ <![CDATA[
+ this._userChanged = true;
+ (this.orient == "vertical" && this.dir != "reverse") ? this.decreasePage() : this.increasePage();
+ this._userChanged = false;
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" preventdefault="true">
+ <![CDATA[
+ this._userChanged = true;
+ (this.orient == "vertical" && this.dir != "reverse") ? this.increasePage() : this.decreasePage();
+ this._userChanged = false;
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_HOME" preventdefault="true">
+ this._userChanged = true;
+ this.value = (this.dir == "reverse") ? this.max : this.min;
+ this._userChanged = false;
+ </handler>
+ <handler event="keypress" keycode="VK_END" preventdefault="true">
+ this._userChanged = true;
+ this.value = (this.dir == "reverse") ? this.min : this.max;
+ this._userChanged = false;
+ </handler>
+ </handlers>
+
+ </binding>
+</bindings>
diff --git a/components/bindings/content/scrollbar.xml b/components/bindings/content/scrollbar.xml
new file mode 100644
index 000000000..ce2eff11b
--- /dev/null
+++ b/components/bindings/content/scrollbar.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<bindings id="scrollbarBindings"
+ 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="thumb" extends="xul:button" />
+
+ <binding id="scrollbar-base" bindToUntrustedContent="true">
+ <handlers>
+ <handler event="contextmenu" preventdefault="true" action="event.stopPropagation();"/>
+ <handler event="click" preventdefault="true" action="event.stopPropagation();"/>
+ <handler event="dblclick" action="event.stopPropagation();"/>
+ <handler event="command" action="event.stopPropagation();"/>
+ </handlers>
+ </binding>
+
+ <binding id="scrollbar" bindToUntrustedContent="true" extends="chrome://global/content/bindings/scrollbar.xml#scrollbar-base">
+ <content clickthrough="always">
+ <xul:scrollbarbutton sbattr="scrollbar-up-top" type="decrement" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
+ <xul:scrollbarbutton sbattr="scrollbar-down-top" type="increment" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
+ <xul:slider flex="1" xbl:inherits="disabled,curpos,maxpos,pageincrement,increment,orient,sborient=orient">
+ <xul:thumb sbattr="scrollbar-thumb" xbl:inherits="orient,sborient=orient,collapsed=disabled"
+ align="center" pack="center"/>
+ </xul:slider>
+ <xul:scrollbarbutton sbattr="scrollbar-up-bottom" type="decrement" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
+ <xul:scrollbarbutton sbattr="scrollbar-down-bottom" type="increment" xbl:inherits="curpos,maxpos,disabled,sborient=orient"/>
+ </content>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/scrollbox.xml b/components/bindings/content/scrollbox.xml
new file mode 100644
index 000000000..219b7cb5e
--- /dev/null
+++ b/components/bindings/content/scrollbox.xml
@@ -0,0 +1,895 @@
+<?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="arrowscrollboxBindings"
+ 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="scrollbox-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
+ <resources>
+ <stylesheet src="chrome://global/skin/scrollbox.css"/>
+ </resources>
+ </binding>
+
+ <binding id="scrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
+ <content>
+ <xul:box class="box-inherit scrollbox-innerbox" xbl:inherits="orient,align,pack,dir" flex="1">
+ <children/>
+ </xul:box>
+ </content>
+ </binding>
+
+ <binding id="arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
+ <content>
+ <xul:autorepeatbutton class="autorepeatbutton-up"
+ anonid="scrollbutton-up"
+ xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtostart"
+ oncommand="_autorepeatbuttonScroll(event);"/>
+ <xul:spacer class="arrowscrollbox-overflow-start-indicator"
+ xbl:inherits="collapsed=scrolledtostart"/>
+ <xul:scrollbox class="arrowscrollbox-scrollbox"
+ anonid="scrollbox"
+ flex="1"
+ xbl:inherits="orient,align,pack,dir">
+ <children/>
+ </xul:scrollbox>
+ <xul:spacer class="arrowscrollbox-overflow-end-indicator"
+ xbl:inherits="collapsed=scrolledtoend"/>
+ <xul:autorepeatbutton class="autorepeatbutton-down"
+ anonid="scrollbutton-down"
+ xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtoend"
+ oncommand="_autorepeatbuttonScroll(event);"/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ this.setAttribute("notoverflowing", "true");
+ this._updateScrollButtonsDisabledState();
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this._stopSmoothScroll();
+ ]]></destructor>
+
+ <field name="_scrollbox">
+ document.getAnonymousElementByAttribute(this, "anonid", "scrollbox");
+ </field>
+ <field name="_scrollButtonUp">
+ document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-up");
+ </field>
+ <field name="_scrollButtonDown">
+ document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-down");
+ </field>
+
+ <field name="__prefBranch">null</field>
+ <property name="_prefBranch" readonly="true">
+ <getter><![CDATA[
+ if (this.__prefBranch === null) {
+ this.__prefBranch = Components.classes['@mozilla.org/preferences-service;1']
+ .getService(Components.interfaces.nsIPrefBranch);
+ }
+ return this.__prefBranch;
+ ]]></getter>
+ </property>
+
+ <field name="_scrollIncrement">null</field>
+ <property name="scrollIncrement" readonly="true">
+ <getter><![CDATA[
+ if (this._scrollIncrement === null) {
+ this._scrollIncrement = this._prefBranch
+ .getIntPref("toolkit.scrollbox.scrollIncrement", 20);
+ }
+ return this._scrollIncrement;
+ ]]></getter>
+ </property>
+
+ <field name="_smoothScroll">null</field>
+ <property name="smoothScroll">
+ <getter><![CDATA[
+ if (this._smoothScroll === null) {
+ if (this.hasAttribute("smoothscroll")) {
+ this._smoothScroll = (this.getAttribute("smoothscroll") == "true");
+ } else {
+ this._smoothScroll = this._prefBranch
+ .getBoolPref("toolkit.scrollbox.smoothScroll", true);
+ }
+ }
+ return this._smoothScroll;
+ ]]></getter>
+ <setter><![CDATA[
+ this._smoothScroll = val;
+ return val;
+ ]]></setter>
+ </property>
+
+ <field name="_scrollBoxObject">null</field>
+ <property name="scrollBoxObject" readonly="true">
+ <getter><![CDATA[
+ if (!this._scrollBoxObject) {
+ this._scrollBoxObject = this._scrollbox.boxObject;
+ }
+ return this._scrollBoxObject;
+ ]]></getter>
+ </property>
+
+ <property name="scrollClientRect" readonly="true">
+ <getter><![CDATA[
+ return this._scrollbox.getBoundingClientRect();
+ ]]></getter>
+ </property>
+
+ <property name="scrollClientSize" readonly="true">
+ <getter><![CDATA[
+ return this.orient == "vertical" ?
+ this._scrollbox.clientHeight :
+ this._scrollbox.clientWidth;
+ ]]></getter>
+ </property>
+
+ <property name="scrollSize" readonly="true">
+ <getter><![CDATA[
+ return this.orient == "vertical" ?
+ this._scrollbox.scrollHeight :
+ this._scrollbox.scrollWidth;
+ ]]></getter>
+ </property>
+ <property name="scrollPaddingRect" readonly="true">
+ <getter><![CDATA[
+ // This assumes that this._scrollbox doesn't have any border.
+ var outerRect = this.scrollClientRect;
+ var innerRect = {};
+ innerRect.left = outerRect.left - this._scrollbox.scrollLeft;
+ innerRect.top = outerRect.top - this._scrollbox.scrollTop;
+ innerRect.right = innerRect.left + this._scrollbox.scrollWidth;
+ innerRect.bottom = innerRect.top + this._scrollbox.scrollHeight;
+ return innerRect;
+ ]]></getter>
+ </property>
+ <property name="scrollboxPaddingStart" readonly="true">
+ <getter><![CDATA[
+ var ltr = (window.getComputedStyle(this, null).direction == "ltr");
+ var paddingStartName = ltr ? "padding-left" : "padding-right";
+ var scrollboxStyle = window.getComputedStyle(this._scrollbox, null);
+ return parseFloat(scrollboxStyle.getPropertyValue(paddingStartName));
+ ]]></getter>
+ </property>
+ <property name="scrollPosition">
+ <getter><![CDATA[
+ return this.orient == "vertical" ?
+ this._scrollbox.scrollTop :
+ this._scrollbox.scrollLeft;
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.orient == "vertical")
+ this._scrollbox.scrollTop = val;
+ else
+ this._scrollbox.scrollLeft = val;
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="_startEndProps" readonly="true">
+ <getter><![CDATA[
+ return this.orient == "vertical" ?
+ ["top", "bottom"] : ["left", "right"];
+ ]]></getter>
+ </property>
+
+ <field name="_isRTLScrollbox"><![CDATA[
+ this.orient != "vertical" &&
+ document.defaultView.getComputedStyle(this._scrollbox, "").direction == "rtl";
+ ]]></field>
+
+ <field name="_scrollTarget">null</field>
+
+ <method name="_canScrollToElement">
+ <parameter name="element"/>
+ <body><![CDATA[
+ return window.getComputedStyle(element).display != "none";
+ ]]></body>
+ </method>
+
+ <method name="ensureElementIsVisible">
+ <parameter name="element"/>
+ <parameter name="aSmoothScroll"/>
+ <body><![CDATA[
+ if (!this._canScrollToElement(element))
+ return;
+
+ var vertical = this.orient == "vertical";
+ var rect = this.scrollClientRect;
+ var containerStart = vertical ? rect.top : rect.left;
+ var containerEnd = vertical ? rect.bottom : rect.right;
+ rect = element.getBoundingClientRect();
+ var elementStart = vertical ? rect.top : rect.left;
+ var elementEnd = vertical ? rect.bottom : rect.right;
+
+ var scrollPaddingRect = this.scrollPaddingRect;
+ let style = window.getComputedStyle(this._scrollbox, null);
+ var scrollContentRect = {
+ left: scrollPaddingRect.left + parseFloat(style.paddingLeft),
+ top: scrollPaddingRect.top + parseFloat(style.paddingTop),
+ right: scrollPaddingRect.right - parseFloat(style.paddingRight),
+ bottom: scrollPaddingRect.bottom - parseFloat(style.paddingBottom)
+ };
+
+ // Provide an entry point for derived bindings to adjust these values.
+ if (this._adjustElementStartAndEnd) {
+ [elementStart, elementEnd] =
+ this._adjustElementStartAndEnd(element, elementStart, elementEnd);
+ }
+
+ if (elementStart <= (vertical ? scrollContentRect.top : scrollContentRect.left)) {
+ elementStart = vertical ? scrollPaddingRect.top : scrollPaddingRect.left;
+ }
+ if (elementEnd >= (vertical ? scrollContentRect.bottom : scrollContentRect.right)) {
+ elementEnd = vertical ? scrollPaddingRect.bottom : scrollPaddingRect.right;
+ }
+
+ var amountToScroll;
+
+ if (elementStart < containerStart) {
+ amountToScroll = elementStart - containerStart;
+ } else if (containerEnd < elementEnd) {
+ amountToScroll = elementEnd - containerEnd;
+ } else if (this._isScrolling) {
+ // decelerate if a currently-visible element is selected during the scroll
+ const STOP_DISTANCE = 15;
+ if (this._isScrolling == -1 && elementStart - STOP_DISTANCE < containerStart)
+ amountToScroll = elementStart - containerStart;
+ else if (this._isScrolling == 1 && containerEnd - STOP_DISTANCE < elementEnd)
+ amountToScroll = elementEnd - containerEnd;
+ else
+ amountToScroll = this._isScrolling * STOP_DISTANCE;
+ } else {
+ return;
+ }
+
+ this._stopSmoothScroll();
+
+ if (aSmoothScroll != false && this.smoothScroll) {
+ this._smoothScrollByPixels(amountToScroll, element);
+ } else {
+ this.scrollByPixels(amountToScroll);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_smoothScrollByPixels">
+ <parameter name="amountToScroll"/>
+ <parameter name="element"/><!-- optional -->
+ <body><![CDATA[
+ this._stopSmoothScroll();
+ if (amountToScroll == 0)
+ return;
+
+ this._scrollTarget = element;
+ // Positive amountToScroll makes us scroll right (elements fly left), negative scrolls left.
+ this._isScrolling = amountToScroll < 0 ? -1 : 1;
+
+ this._scrollAnim.start(amountToScroll);
+ ]]></body>
+ </method>
+
+ <field name="_scrollAnim"><![CDATA[({
+ scrollbox: this,
+ requestHandle: 0, /* 0 indicates there is no pending request */
+ start: function scrollAnim_start(distance) {
+ this.distance = distance;
+ this.startPos = this.scrollbox.scrollPosition;
+ this.duration = Math.min(1000, Math.round(50 * Math.sqrt(Math.abs(distance))));
+ this.startTime = window.performance.now();
+
+ if (!this.requestHandle)
+ this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
+ },
+ stop: function scrollAnim_stop() {
+ window.cancelAnimationFrame(this.requestHandle);
+ this.requestHandle = 0;
+ },
+ sample: function scrollAnim_handleEvent(timeStamp) {
+ const timePassed = timeStamp - this.startTime;
+ const pos = timePassed >= this.duration ? 1 :
+ 1 - Math.pow(1 - timePassed / this.duration, 4);
+
+ this.scrollbox.scrollPosition = this.startPos + (this.distance * pos);
+
+ if (pos == 1)
+ this.scrollbox._stopSmoothScroll();
+ else
+ this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
+ }
+ })]]></field>
+
+ <method name="scrollByIndex">
+ <parameter name="index"/>
+ <parameter name="aSmoothScroll"/>
+ <body><![CDATA[
+ if (index == 0)
+ return;
+
+ // Each scrollByIndex call is expected to scroll the given number of
+ // items. If a previous call is still in progress because of smooth
+ // scrolling, we need to complete it before starting a new one.
+ if (this._scrollTarget) {
+ let elements = this._getScrollableElements();
+ if (this._scrollTarget != elements[0] &&
+ this._scrollTarget != elements[elements.length - 1])
+ this.ensureElementIsVisible(this._scrollTarget, false);
+ }
+
+ var rect = this.scrollClientRect;
+ var [start, end] = this._startEndProps;
+ var x = index > 0 ? rect[end] + 1 : rect[start] - 1;
+ var nextElement = this._elementFromPoint(x, index);
+ if (!nextElement)
+ return;
+
+ var targetElement;
+ if (this._isRTLScrollbox)
+ index *= -1;
+ while (index < 0 && nextElement) {
+ if (this._canScrollToElement(nextElement))
+ targetElement = nextElement;
+ nextElement = nextElement.previousSibling;
+ index++;
+ }
+ while (index > 0 && nextElement) {
+ if (this._canScrollToElement(nextElement))
+ targetElement = nextElement;
+ nextElement = nextElement.nextSibling;
+ index--;
+ }
+ if (!targetElement)
+ return;
+
+ this.ensureElementIsVisible(targetElement, aSmoothScroll);
+ ]]></body>
+ </method>
+
+ <method name="scrollByPage">
+ <parameter name="pageDelta"/>
+ <parameter name="aSmoothScroll"/>
+ <body><![CDATA[
+ if (pageDelta == 0)
+ return;
+
+ // If a previous call is still in progress because of smooth
+ // scrolling, we need to complete it before starting a new one.
+ if (this._scrollTarget) {
+ let elements = this._getScrollableElements();
+ if (this._scrollTarget != elements[0] &&
+ this._scrollTarget != elements[elements.length - 1])
+ this.ensureElementIsVisible(this._scrollTarget, false);
+ }
+
+ var [start, end] = this._startEndProps;
+ var rect = this.scrollClientRect;
+ var containerEdge = pageDelta > 0 ? rect[end] + 1 : rect[start] - 1;
+ var pixelDelta = pageDelta * (rect[end] - rect[start]);
+ var destinationPosition = containerEdge + pixelDelta;
+ var nextElement = this._elementFromPoint(containerEdge, pageDelta);
+ if (!nextElement)
+ return;
+
+ // We need to iterate over our elements in the direction of pageDelta.
+ // pageDelta is the physical direction, so in a horizontal scroll box,
+ // positive values scroll to the right no matter if the scrollbox is
+ // LTR or RTL. But RTL changes how we need to advance the iteration
+ // (whether to get the next or the previous sibling of the current
+ // element).
+ var logicalAdvanceDir = pageDelta * (this._isRTLScrollbox ? -1 : 1);
+ var advance = logicalAdvanceDir > 0 ? (e => e.nextSibling) : (e => e.previousSibling);
+
+ var extendsPastTarget = (pageDelta > 0)
+ ? (e => e.getBoundingClientRect()[end] > destinationPosition)
+ : (e => e.getBoundingClientRect()[start] < destinationPosition);
+
+ // We want to scroll to the last element we encounter before we find
+ // an element which extends past destinationPosition.
+ var targetElement;
+ do {
+ if (this._canScrollToElement(nextElement))
+ targetElement = nextElement;
+ nextElement = advance(nextElement);
+ } while (nextElement && !extendsPastTarget(nextElement));
+
+ if (!targetElement)
+ return;
+
+ this.ensureElementIsVisible(targetElement, aSmoothScroll);
+ ]]></body>
+ </method>
+
+ <method name="_getScrollableElements">
+ <body><![CDATA[
+ var nodes = this.childNodes;
+ if (nodes.length == 1 &&
+ nodes[0].localName == "children" &&
+ nodes[0].namespaceURI == "http://www.mozilla.org/xbl") {
+ nodes = document.getBindingParent(this).childNodes;
+ }
+
+ return Array.filter(nodes, this._canScrollToElement, this);
+ ]]></body>
+ </method>
+
+ <method name="_elementFromPoint">
+ <parameter name="aX"/>
+ <parameter name="aPhysicalScrollDir"/>
+ <body><![CDATA[
+ var elements = this._getScrollableElements();
+ if (!elements.length)
+ return null;
+
+ if (this._isRTLScrollbox)
+ elements.reverse();
+
+ var [start, end] = this._startEndProps;
+ var low = 0;
+ var high = elements.length - 1;
+
+ if (aX < elements[low].getBoundingClientRect()[start] ||
+ aX > elements[high].getBoundingClientRect()[end])
+ return null;
+
+ var mid, rect;
+ while (low <= high) {
+ mid = Math.floor((low + high) / 2);
+ rect = elements[mid].getBoundingClientRect();
+ if (rect[start] > aX)
+ high = mid - 1;
+ else if (rect[end] < aX)
+ low = mid + 1;
+ else
+ return elements[mid];
+ }
+
+ // There's no element at the requested coordinate, but the algorithm
+ // from above yields an element next to it, in a random direction.
+ // The desired scrolling direction leads to the correct element.
+
+ if (!aPhysicalScrollDir)
+ return null;
+
+ if (aPhysicalScrollDir < 0 && rect[start] > aX)
+ mid = Math.max(mid - 1, 0);
+ else if (aPhysicalScrollDir > 0 && rect[end] < aX)
+ mid = Math.min(mid + 1, elements.length - 1);
+
+ return elements[mid];
+ ]]></body>
+ </method>
+
+ <method name="_autorepeatbuttonScroll">
+ <parameter name="event"/>
+ <body><![CDATA[
+ var dir = event.originalTarget == this._scrollButtonUp ? -1 : 1;
+ if (this._isRTLScrollbox)
+ dir *= -1;
+
+ this.scrollByPixels(this.scrollIncrement * dir);
+
+ event.stopPropagation();
+ ]]></body>
+ </method>
+
+ <method name="scrollByPixels">
+ <parameter name="px"/>
+ <body><![CDATA[
+ this.scrollPosition += px;
+ ]]></body>
+ </method>
+
+ <!-- 0: idle
+ 1: scrolling right
+ -1: scrolling left -->
+ <field name="_isScrolling">0</field>
+ <field name="_prevMouseScrolls">[null, null]</field>
+
+ <field name="_touchStart">-1</field>
+
+ <method name="_stopSmoothScroll">
+ <body><![CDATA[
+ if (this._isScrolling) {
+ this._scrollAnim.stop();
+ this._isScrolling = 0;
+ this._scrollTarget = null;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_updateScrollButtonsDisabledState">
+ <body><![CDATA[
+ var scrolledToStart = false;
+ var scrolledToEnd = false;
+
+ if (this.hasAttribute("notoverflowing")) {
+ scrolledToStart = true;
+ scrolledToEnd = true;
+ }
+ else if (this.scrollPosition == 0) {
+ // In the RTL case, this means the _last_ element in the
+ // scrollbox is visible
+ if (this._isRTLScrollbox)
+ scrolledToEnd = true;
+ else
+ scrolledToStart = true;
+ }
+ else if (this.scrollClientSize + this.scrollPosition == this.scrollSize) {
+ // In the RTL case, this means the _first_ element in the
+ // scrollbox is visible
+ if (this._isRTLScrollbox)
+ scrolledToStart = true;
+ else
+ scrolledToEnd = true;
+ }
+
+ if (scrolledToEnd)
+ this.setAttribute("scrolledtoend", "true");
+ else
+ this.removeAttribute("scrolledtoend");
+
+ if (scrolledToStart)
+ this.setAttribute("scrolledtostart", "true");
+ else
+ this.removeAttribute("scrolledtostart");
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="wheel"><![CDATA[
+ if (this.orient == "vertical") {
+ if (event.deltaMode == event.DOM_DELTA_PIXEL)
+ this.scrollByPixels(event.deltaY);
+ else if (event.deltaMode == event.DOM_DELTA_PAGE)
+ this.scrollByPage(event.deltaY);
+ else
+ this.scrollByIndex(event.deltaY);
+ }
+ // We allow vertical scrolling to scroll a horizontal scrollbox
+ // because many users have a vertical scroll wheel but no
+ // horizontal support.
+ // Because of this, we need to avoid scrolling chaos on trackpads
+ // and mouse wheels that support simultaneous scrolling in both axes.
+ // We do this by scrolling only when the last two scroll events were
+ // on the same axis as the current scroll event.
+ // For diagonal scroll events we only respect the dominant axis.
+ else {
+ let isVertical = Math.abs(event.deltaY) > Math.abs(event.deltaX);
+ let delta = isVertical ? event.deltaY : event.deltaX;
+ let scrollByDelta = isVertical && this._isRTLScrollbox ? -delta : delta;
+
+ if (this._prevMouseScrolls.every(prev => prev == isVertical)) {
+ if (event.deltaMode == event.DOM_DELTA_PIXEL)
+ this.scrollByPixels(scrollByDelta);
+ else if (event.deltaMode == event.DOM_DELTA_PAGE)
+ this.scrollByPage(scrollByDelta);
+ else
+ this.scrollByIndex(scrollByDelta);
+ }
+
+ if (this._prevMouseScrolls.length > 1)
+ this._prevMouseScrolls.shift();
+ this._prevMouseScrolls.push(isVertical);
+ }
+
+ event.stopPropagation();
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="touchstart"><![CDATA[
+ if (event.touches.length > 1) {
+ // Multiple touch points detected, abort. In particular this aborts
+ // the panning gesture when the user puts a second finger down after
+ // already panning with one finger. Aborting at this point prevents
+ // the pan gesture from being resumed until all fingers are lifted
+ // (as opposed to when the user is back down to one finger).
+ this._touchStart = -1;
+ } else {
+ this._touchStart = (this.orient == "vertical"
+ ? event.touches[0].screenY
+ : event.touches[0].screenX);
+ }
+ ]]></handler>
+
+ <handler event="touchmove"><![CDATA[
+ if (event.touches.length == 1 &&
+ this._touchStart >= 0) {
+ var touchPoint = (this.orient == "vertical"
+ ? event.touches[0].screenY
+ : event.touches[0].screenX);
+ var delta = this._touchStart - touchPoint;
+ if (Math.abs(delta) > 0) {
+ this.scrollByPixels(delta);
+ this._touchStart = touchPoint;
+ }
+ event.preventDefault();
+ }
+ ]]></handler>
+
+ <handler event="touchend"><![CDATA[
+ this._touchStart = -1;
+ ]]></handler>
+
+ <handler event="underflow" phase="capturing"><![CDATA[
+ // filter underflow events which were dispatched on nested scrollboxes
+ if (event.target != this)
+ return;
+
+ // Ignore events that doesn't match our orientation.
+ // Scrollport event orientation:
+ // 0: vertical
+ // 1: horizontal
+ // 2: both
+ if (this.orient == "vertical") {
+ if (event.detail == 1)
+ return;
+ }
+ else if (event.detail == 0) {
+ // horizontal scrollbox
+ return;
+ }
+
+ this.setAttribute("notoverflowing", "true");
+
+ try {
+ // See bug 341047 and comments in overflow handler as to why
+ // try..catch is needed here
+ this._updateScrollButtonsDisabledState();
+
+ let childNodes = this._getScrollableElements();
+ if (childNodes && childNodes.length)
+ this.ensureElementIsVisible(childNodes[0], false);
+ }
+ catch (e) {
+ this.removeAttribute("notoverflowing");
+ }
+ ]]></handler>
+
+ <handler event="overflow" phase="capturing"><![CDATA[
+ // filter underflow events which were dispatched on nested scrollboxes
+ if (event.target != this)
+ return;
+
+ // Ignore events that doesn't match our orientation.
+ // Scrollport event orientation:
+ // 0: vertical
+ // 1: horizontal
+ // 2: both
+ if (this.orient == "vertical") {
+ if (event.detail == 1)
+ return;
+ }
+ else if (event.detail == 0) {
+ // horizontal scrollbox
+ return;
+ }
+
+ this.removeAttribute("notoverflowing");
+
+ try {
+ // See bug 341047, the overflow event is dispatched when the
+ // scrollbox already is mostly destroyed. This causes some code in
+ // _updateScrollButtonsDisabledState() to throw an error. It also
+ // means that the notoverflowing attribute was removed erroneously,
+ // as the whole overflow event should not be happening in that case.
+ this._updateScrollButtonsDisabledState();
+ }
+ catch (e) {
+ this.setAttribute("notoverflowing", "true");
+ }
+ ]]></handler>
+
+ <handler event="scroll" action="this._updateScrollButtonsDisabledState()"/>
+ </handlers>
+ </binding>
+
+ <binding id="autorepeatbutton" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
+ <content repeat="hover">
+ <xul:image class="autorepeatbutton-icon"/>
+ </content>
+ </binding>
+
+ <binding id="arrowscrollbox-clicktoscroll" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox">
+ <content>
+ <xul:toolbarbutton class="scrollbutton-up"
+ xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtostart"
+ anonid="scrollbutton-up"
+ onclick="_distanceScroll(event);"
+ onmousedown="if (event.button == 0) _startScroll(-1);"
+ onmouseup="if (event.button == 0) _stopScroll();"
+ onmouseover="_continueScroll(-1);"
+ onmouseout="_pauseScroll();"/>
+ <xul:spacer class="arrowscrollbox-overflow-start-indicator"
+ xbl:inherits="collapsed=scrolledtostart"/>
+ <xul:scrollbox class="arrowscrollbox-scrollbox"
+ anonid="scrollbox"
+ flex="1"
+ xbl:inherits="orient,align,pack,dir">
+ <children/>
+ </xul:scrollbox>
+ <xul:spacer class="arrowscrollbox-overflow-end-indicator"
+ xbl:inherits="collapsed=scrolledtoend"/>
+ <xul:toolbarbutton class="scrollbutton-down"
+ xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtoend"
+ anonid="scrollbutton-down"
+ onclick="_distanceScroll(event);"
+ onmousedown="if (event.button == 0) _startScroll(1);"
+ onmouseup="if (event.button == 0) _stopScroll();"
+ onmouseover="_continueScroll(1);"
+ onmouseout="_pauseScroll();"/>
+ </content>
+ <implementation implements="nsITimerCallback, nsIDOMEventListener">
+ <constructor><![CDATA[
+ this._scrollDelay = this._prefBranch
+ .getIntPref("toolkit.scrollbox.clickToScroll.scrollDelay",
+ this._scrollDelay);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ // Release timer to avoid reference cycles.
+ if (this._scrollTimer) {
+ this._scrollTimer.cancel();
+ this._scrollTimer = null;
+ }
+ ]]></destructor>
+
+ <field name="_scrollIndex">0</field>
+ <field name="_scrollDelay">150</field>
+
+ <method name="notify">
+ <parameter name="aTimer"/>
+ <body>
+ <![CDATA[
+ if (!document)
+ aTimer.cancel();
+
+ this.scrollByIndex(this._scrollIndex);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_arrowScrollAnim"><![CDATA[({
+ scrollbox: this,
+ requestHandle: 0, /* 0 indicates there is no pending request */
+ start: function arrowSmoothScroll_start() {
+ this.lastFrameTime = window.performance.now();
+ if (!this.requestHandle)
+ this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
+ },
+ stop: function arrowSmoothScroll_stop() {
+ window.cancelAnimationFrame(this.requestHandle);
+ this.requestHandle = 0;
+ },
+ sample: function arrowSmoothScroll_handleEvent(timeStamp) {
+ const scrollIndex = this.scrollbox._scrollIndex;
+ const timePassed = timeStamp - this.lastFrameTime;
+ this.lastFrameTime = timeStamp;
+
+ const scrollDelta = 0.5 * timePassed * scrollIndex;
+ this.scrollbox.scrollPosition += scrollDelta;
+
+ this.requestHandle = window.requestAnimationFrame(this.sample.bind(this));
+ }
+ })]]></field>
+
+ <method name="_startScroll">
+ <parameter name="index"/>
+ <body><![CDATA[
+ if (this._isRTLScrollbox)
+ index *= -1;
+ this._scrollIndex = index;
+ this._mousedown = true;
+ if (this.smoothScroll) {
+ this._arrowScrollAnim.start();
+ return;
+ }
+
+ if (!this._scrollTimer)
+ this._scrollTimer =
+ Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ else
+ this._scrollTimer.cancel();
+
+ this._scrollTimer.initWithCallback(this, this._scrollDelay,
+ this._scrollTimer.TYPE_REPEATING_SLACK);
+ this.notify(this._scrollTimer);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_stopScroll">
+ <body><![CDATA[
+ if (this._scrollTimer)
+ this._scrollTimer.cancel();
+ this._mousedown = false;
+ if (!this._scrollIndex || !this.smoothScroll)
+ return;
+
+ this.scrollByIndex(this._scrollIndex);
+ this._scrollIndex = 0;
+ this._arrowScrollAnim.stop();
+ ]]></body>
+ </method>
+
+ <method name="_pauseScroll">
+ <body><![CDATA[
+ if (this._mousedown) {
+ this._stopScroll();
+ this._mousedown = true;
+ document.addEventListener("mouseup", this, false);
+ document.addEventListener("blur", this, true);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_continueScroll">
+ <parameter name="index"/>
+ <body><![CDATA[
+ if (this._mousedown)
+ this._startScroll(index);
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (aEvent.type == "mouseup" ||
+ aEvent.type == "blur" && aEvent.target == document) {
+ this._mousedown = false;
+ document.removeEventListener("mouseup", this, false);
+ document.removeEventListener("blur", this, true);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_distanceScroll">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (aEvent.detail < 2 || aEvent.detail > 3)
+ return;
+
+ var scrollBack = (aEvent.originalTarget == this._scrollButtonUp);
+ var scrollLeftOrUp = this._isRTLScrollbox ? !scrollBack : scrollBack;
+ var targetElement;
+
+ if (aEvent.detail == 2) {
+ // scroll by the size of the scrollbox
+ let [start, end] = this._startEndProps;
+ let x;
+ if (scrollLeftOrUp)
+ x = this.scrollClientRect[start] - this.scrollClientSize;
+ else
+ x = this.scrollClientRect[end] + this.scrollClientSize;
+ targetElement = this._elementFromPoint(x, scrollLeftOrUp ? -1 : 1);
+
+ // the next partly-hidden element will become fully visible,
+ // so don't scroll too far
+ if (targetElement)
+ targetElement = scrollBack ?
+ targetElement.nextSibling :
+ targetElement.previousSibling;
+ }
+
+ if (!targetElement) {
+ // scroll to the first resp. last element
+ let elements = this._getScrollableElements();
+ targetElement = scrollBack ?
+ elements[0] :
+ elements[elements.length - 1];
+ }
+
+ this.ensureElementIsVisible(targetElement);
+ ]]></body>
+ </method>
+
+ </implementation>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/spinbuttons.xml b/components/bindings/content/spinbuttons.xml
new file mode 100644
index 000000000..3a695beac
--- /dev/null
+++ b/components/bindings/content/spinbuttons.xml
@@ -0,0 +1,96 @@
+<?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="spinbuttonsBindings"
+ 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="spinbuttons"
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
+
+ <resources>
+ <stylesheet src="chrome://global/skin/spinbuttons.css"/>
+ </resources>
+
+ <content>
+ <xul:vbox class="spinbuttons-box" flex="1">
+ <xul:button anonid="increaseButton" type="repeat" flex="1"
+ class="spinbuttons-button spinbuttons-up"
+ xbl:inherits="disabled,disabled=increasedisabled"/>
+ <xul:button anonid="decreaseButton" type="repeat" flex="1"
+ class="spinbuttons-button spinbuttons-down"
+ xbl:inherits="disabled,disabled=decreasedisabled"/>
+ </xul:vbox>
+ </content>
+
+ <implementation>
+ <property name="_increaseButton" readonly="true">
+ <getter>
+ return document.getAnonymousElementByAttribute(this, "anonid", "increaseButton");
+ </getter>
+ </property>
+ <property name="_decreaseButton" readonly="true">
+ <getter>
+ return document.getAnonymousElementByAttribute(this, "anonid", "decreaseButton");
+ </getter>
+ </property>
+
+ <property name="increaseDisabled"
+ onget="return this._increaseButton.getAttribute('disabled') == 'true';"
+ onset="if (val) this._increaseButton.setAttribute('disabled', 'true');
+ else this._increaseButton.removeAttribute('disabled'); return val;"/>
+ <property name="decreaseDisabled"
+ onget="return this._decreaseButton.getAttribute('disabled') == 'true';"
+ onset="if (val) this._decreaseButton.setAttribute('disabled', 'true');
+ else this._decreaseButton.removeAttribute('disabled'); return val;"/>
+ </implementation>
+
+ <handlers>
+ <handler event="mousedown">
+ <![CDATA[
+ // on the Mac, the native theme draws the spinbutton as a single widget
+ // so a state attribute is set based on where the mouse button was pressed
+ if (event.originalTarget == this._increaseButton)
+ this.setAttribute("state", "up");
+ else if (event.originalTarget == this._decreaseButton)
+ this.setAttribute("state", "down");
+ ]]>
+ </handler>
+
+ <handler event="mouseup">
+ this.removeAttribute("state");
+ </handler>
+ <handler event="mouseout">
+ this.removeAttribute("state");
+ </handler>
+
+ <handler event="command">
+ <![CDATA[
+ var eventname;
+ if (event.originalTarget == this._increaseButton)
+ eventname = "up";
+ else if (event.originalTarget == this._decreaseButton)
+ eventname = "down";
+
+ var evt = document.createEvent("Events");
+ evt.initEvent(eventname, true, true);
+ var cancel = this.dispatchEvent(evt);
+
+ if (this.hasAttribute("on" + eventname)) {
+ var fn = new Function("event", this.getAttribute("on" + eventname));
+ if (fn.call(this, event) == false)
+ cancel = true;
+ }
+
+ return !cancel;
+ ]]>
+ </handler>
+
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/spinner.js b/components/bindings/content/spinner.js
new file mode 100644
index 000000000..4901320b5
--- /dev/null
+++ b/components/bindings/content/spinner.js
@@ -0,0 +1,501 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+/*
+ * The spinner is responsible for displaying the items, and does
+ * not care what the values represent. The setValue function is called
+ * when it detects a change in value triggered by scroll event.
+ * Supports scrolling, clicking on up or down, clicking on item, and
+ * dragging.
+ */
+
+function Spinner(props, context) {
+ this.context = context;
+ this._init(props);
+}
+
+{
+ const debug = 0 ? console.log.bind(console, "[spinner]") : function() {};
+
+ const ITEM_HEIGHT = 2.5,
+ VIEWPORT_SIZE = 7,
+ VIEWPORT_COUNT = 5,
+ SCROLL_TIMEOUT = 100;
+
+ Spinner.prototype = {
+ /**
+ * Initializes a spinner. Set the default states and properties, cache
+ * element references, create the HTML markup, and add event listeners.
+ *
+ * @param {Object} props [Properties passed in from parent]
+ * {
+ * {Function} setValue: Takes a value and set the state to
+ * the parent component.
+ * {Function} getDisplayString: Takes a value, and output it
+ * as localized strings.
+ * {Number} viewportSize [optional]: Number of items in a
+ * viewport.
+ * {Boolean} hideButtons [optional]: Hide up & down buttons
+ * {Number} rootFontSize [optional]: Used to support zoom in/out
+ * }
+ */
+ _init(props) {
+ const { setValue, getDisplayString, hideButtons, rootFontSize = 10 } = props;
+
+ const spinnerTemplate = document.getElementById("spinner-template");
+ const spinnerElement = document.importNode(spinnerTemplate.content, true);
+
+ // Make sure viewportSize is an odd number because we want to have the selected
+ // item in the center. If it's an even number, use the default size instead.
+ const viewportSize = props.viewportSize % 2 ? props.viewportSize : VIEWPORT_SIZE;
+
+ this.state = {
+ items: [],
+ isScrolling: false
+ };
+ this.props = {
+ setValue, getDisplayString, viewportSize, rootFontSize,
+ // We can assume that the viewportSize is an odd number. Calculate how many
+ // items we need to insert on top of the spinner so that the selected is at
+ // the center. Ex: if viewportSize is 5, we need 2 items on top.
+ viewportTopOffset: (viewportSize - 1) / 2
+ };
+ this.elements = {
+ container: spinnerElement.querySelector(".spinner-container"),
+ spinner: spinnerElement.querySelector(".spinner"),
+ up: spinnerElement.querySelector(".up"),
+ down: spinnerElement.querySelector(".down"),
+ itemsViewElements: []
+ };
+
+ this.elements.spinner.style.height = (ITEM_HEIGHT * viewportSize) + "rem";
+
+ if (hideButtons) {
+ this.elements.container.classList.add("hide-buttons");
+ }
+
+ this.context.appendChild(spinnerElement);
+ this._attachEventListeners();
+ },
+
+ /**
+ * Only the parent component calls setState on the spinner.
+ * It checks if the items have changed and updates the spinner.
+ * If only the value has changed, smooth scrolls to the new value.
+ *
+ * @param {Object} newState [The new spinner state]
+ * {
+ * {Number/String} value: The centered value
+ * {Array} items: The list of items for display
+ * {Boolean} isInfiniteScroll: Whether or not the spinner should
+ * have infinite scroll capability
+ * {Boolean} isValueSet: true if user has selected a value
+ * }
+ */
+ setState(newState) {
+ const { spinner } = this.elements;
+ const { value, items } = this.state;
+ const { value: newValue, items: newItems, isValueSet, isInvalid, smoothScroll = true } = newState;
+
+ if (this._isArrayDiff(newItems, items)) {
+ this.state = Object.assign(this.state, newState);
+ this._updateItems();
+ this._scrollTo(newValue, true);
+ } else if (newValue != value) {
+ this.state = Object.assign(this.state, newState);
+ if (smoothScroll) {
+ this._smoothScrollTo(newValue, true);
+ } else {
+ this._scrollTo(newValue, true);
+ }
+ }
+
+ if (isValueSet && !isInvalid) {
+ this._updateSelection();
+ } else {
+ this._removeSelection();
+ }
+ },
+
+ /**
+ * Whenever scroll event is detected:
+ * - Update the index state
+ * - If the value has changed, update the [value] state and call [setValue]
+ * - If infinite scrolling is on, reset the scrolling position if necessary
+ */
+ _onScroll() {
+ const { items, itemsView, isInfiniteScroll } = this.state;
+ const { viewportSize, viewportTopOffset } = this.props;
+ const { spinner, itemsViewElements } = this.elements;
+
+ this.state.index = this._getIndexByOffset(spinner.scrollTop);
+
+ const value = itemsView[this.state.index + viewportTopOffset].value;
+
+ // Call setValue if value has changed
+ if (this.state.value != value) {
+ this.state.value = value;
+ this.props.setValue(value);
+ }
+
+ // Do infinite scroll when items length is bigger or equal to viewport
+ // and isInfiniteScroll is not false.
+ if (items.length >= viewportSize && isInfiniteScroll) {
+ // If the scroll position is near the top or bottom, jump back to the middle
+ // so user can keep scrolling up or down.
+ if (this.state.index < viewportSize ||
+ this.state.index > itemsView.length - viewportSize) {
+ this._scrollTo(this.state.value, true);
+ }
+ }
+
+ // Use a timer to detect if a scroll event has not fired within some time
+ // (defined in SCROLL_TIMEOUT). This is required because we need to hide
+ // highlight and hover state when user is scrolling.
+ clearTimeout(this.state.scrollTimer);
+ this.elements.spinner.classList.add("scrolling");
+ this.state.scrollTimer = setTimeout(() => {
+ this.elements.spinner.classList.remove("scrolling");
+ this.elements.spinner.dispatchEvent(new CustomEvent("ScrollStop"));
+ }, SCROLL_TIMEOUT);
+ },
+
+ /**
+ * Updates the spinner items to the current states.
+ */
+ _updateItems() {
+ const { viewportSize, viewportTopOffset } = this.props;
+ const { items, isInfiniteScroll } = this.state;
+
+ // Prepends null elements so the selected value is centered in spinner
+ let itemsView = new Array(viewportTopOffset).fill({}).concat(items);
+
+ if (items.length >= viewportSize && isInfiniteScroll) {
+ // To achieve infinite scroll, we move the scroll position back to the
+ // center when it is near the top or bottom. The scroll momentum could
+ // be lost in the process, so to minimize that, we need at least 2 sets
+ // of items to act as buffer: one for the top and one for the bottom.
+ // But if the number of items is small ( < viewportSize * viewport count)
+ // we should add more sets.
+ let count = Math.ceil(viewportSize * VIEWPORT_COUNT / items.length) * 2;
+ for (let i = 0; i < count; i += 1) {
+ itemsView.push(...items);
+ }
+ }
+
+ // Reuse existing DOM nodes when possible. Create or remove
+ // nodes based on how big itemsView is.
+ this._prepareNodes(itemsView.length, this.elements.spinner);
+ // Once DOM nodes are ready, set display strings using textContent
+ this._setDisplayStringAndClass(itemsView, this.elements.itemsViewElements);
+
+ this.state.itemsView = itemsView;
+ },
+
+ /**
+ * Make sure the number or child elements is the same as length
+ * and keep the elements' references for updating textContent
+ *
+ * @param {Number} length [The number of child elements]
+ * @param {DOMElement} parent [The parent element reference]
+ */
+ _prepareNodes(length, parent) {
+ const diff = length - parent.childElementCount;
+
+ if (!diff) {
+ return;
+ }
+
+ if (diff > 0) {
+ // Add more elements if length is greater than current
+ let frag = document.createDocumentFragment();
+
+ // Remove margin bottom on the last element before appending
+ if (parent.lastChild) {
+ parent.lastChild.style.marginBottom = "";
+ }
+
+ for (let i = 0; i < diff; i++) {
+ let el = document.createElement("div");
+ frag.appendChild(el);
+ this.elements.itemsViewElements.push(el);
+ }
+ parent.appendChild(frag);
+ } else if (diff < 0) {
+ // Remove elements if length is less than current
+ for (let i = 0; i < Math.abs(diff); i++) {
+ parent.removeChild(parent.lastChild);
+ }
+ this.elements.itemsViewElements.splice(diff);
+ }
+
+ parent.lastChild.style.marginBottom =
+ (ITEM_HEIGHT * this.props.viewportTopOffset) + "rem";
+ },
+
+ /**
+ * Set the display string and class name to the elements.
+ *
+ * @param {Array<Object>} items
+ * [{
+ * {Number/String} value: The value in its original form
+ * {Boolean} enabled: Whether or not the item is enabled
+ * }]
+ * @param {Array<DOMElement>} elements
+ */
+ _setDisplayStringAndClass(items, elements) {
+ const { getDisplayString } = this.props;
+
+ items.forEach((item, index) => {
+ elements[index].textContent =
+ item.value != undefined ? getDisplayString(item.value) : "";
+ elements[index].className = item.enabled ? "" : "disabled";
+ });
+ },
+
+ /**
+ * Attach event listeners to the spinner and buttons.
+ */
+ _attachEventListeners() {
+ const { spinner, container } = this.elements;
+
+ spinner.addEventListener("scroll", this, { passive: true });
+ container.addEventListener("mouseup", this, { passive: true });
+ container.addEventListener("mousedown", this, { passive: true });
+ },
+
+ /**
+ * Handle events
+ * @param {DOMEvent} event
+ */
+ handleEvent(event) {
+ const { mouseState = {}, index, itemsView } = this.state;
+ const { viewportTopOffset, setValue } = this.props;
+ const { spinner, up, down } = this.elements;
+
+ switch (event.type) {
+ case "scroll": {
+ this._onScroll();
+ break;
+ }
+ case "mousedown": {
+ this.state.mouseState = {
+ down: true,
+ layerX: event.layerX,
+ layerY: event.layerY
+ };
+ if (event.target == up) {
+ // An "active" class is needed to simulate :active pseudo-class
+ // because element is not focused.
+ event.target.classList.add("active");
+ this._smoothScrollToIndex(index - 1);
+ }
+ if (event.target == down) {
+ event.target.classList.add("active");
+ this._smoothScrollToIndex(index + 1);
+ }
+ if (event.target.parentNode == spinner) {
+ // Listen to dragging events
+ spinner.addEventListener("mousemove", this, { passive: true });
+ spinner.addEventListener("mouseleave", this, { passive: true });
+ }
+ break;
+ }
+ case "mouseup": {
+ this.state.mouseState.down = false;
+ if (event.target == up || event.target == down) {
+ event.target.classList.remove("active");
+ }
+ if (event.target.parentNode == spinner) {
+ // Check if user clicks or drags, scroll to the item if clicked,
+ // otherwise get the current index and smooth scroll there.
+ if (event.layerX == mouseState.layerX && event.layerY == mouseState.layerY) {
+ const newIndex = this._getIndexByOffset(event.target.offsetTop) - viewportTopOffset;
+ if (index == newIndex) {
+ // Set value manually if the clicked element is already centered.
+ // This happens when the picker first opens, and user pick the
+ // default value.
+ setValue(itemsView[index + viewportTopOffset].value);
+ } else {
+ this._smoothScrollToIndex(newIndex);
+ }
+ } else {
+ this._smoothScrollToIndex(this._getIndexByOffset(spinner.scrollTop));
+ }
+ // Stop listening to dragging
+ spinner.removeEventListener("mousemove", this, { passive: true });
+ spinner.removeEventListener("mouseleave", this, { passive: true });
+ }
+ break;
+ }
+ case "mouseleave": {
+ if (event.target == spinner) {
+ // Stop listening to drag event if mouse is out of the spinner
+ this._smoothScrollToIndex(this._getIndexByOffset(spinner.scrollTop));
+ spinner.removeEventListener("mousemove", this, { passive: true });
+ spinner.removeEventListener("mouseleave", this, { passive: true });
+ }
+ break;
+ }
+ case "mousemove": {
+ // Change spinner position on drag
+ spinner.scrollTop -= event.movementY;
+ break;
+ }
+ }
+ },
+
+ /**
+ * Find the index by offset
+ * @param {Number} offset: Offset value in pixel.
+ * @return {Number} Index number
+ */
+ _getIndexByOffset(offset) {
+ return Math.round(offset / (ITEM_HEIGHT * this.props.rootFontSize));
+ },
+
+ /**
+ * Find the index of a value that is the closest to the current position.
+ * If centering is true, find the index closest to the center.
+ *
+ * @param {Number/String} value: The value to find
+ * @param {Boolean} centering: Whether or not to find the value closest to center
+ * @return {Number} index of the value, returns -1 if value is not found
+ */
+ _getScrollIndex(value, centering) {
+ const { itemsView } = this.state;
+ const { viewportTopOffset } = this.props;
+
+ // If index doesn't exist, or centering is true, start from the middle point
+ let currentIndex = centering || (this.state.index == undefined) ?
+ Math.round((itemsView.length - viewportTopOffset) / 2) :
+ this.state.index;
+ let closestIndex = itemsView.length;
+ let indexes = [];
+ let diff = closestIndex;
+ let isValueFound = false;
+
+ // Find indexes of items match the value
+ itemsView.forEach((item, index) => {
+ if (item.value == value) {
+ indexes.push(index);
+ }
+ });
+
+ // Find the index closest to currentIndex
+ indexes.forEach(index => {
+ let d = Math.abs(index - currentIndex);
+ if (d < diff) {
+ diff = d;
+ closestIndex = index;
+ isValueFound = true;
+ }
+ });
+
+ return isValueFound ? (closestIndex - viewportTopOffset) : -1;
+ },
+
+ /**
+ * Scroll to a value.
+ *
+ * @param {Number/String} value: Value to scroll to
+ * @param {Boolean} centering: Whether or not to scroll to center location
+ */
+ _scrollTo(value, centering) {
+ const index = this._getScrollIndex(value, centering);
+ // Do nothing if the value is not found
+ if (index > -1) {
+ this.state.index = index;
+ this.elements.spinner.scrollTop = this.state.index * ITEM_HEIGHT * this.props.rootFontSize;
+ }
+ },
+
+ /**
+ * Smooth scroll to a value.
+ *
+ * @param {Number/String} value: Value to scroll to
+ */
+ _smoothScrollTo(value) {
+ const index = this._getScrollIndex(value);
+ // Do nothing if the value is not found
+ if (index > -1) {
+ this.state.index = index;
+ this._smoothScrollToIndex(this.state.index);
+ }
+ },
+
+ /**
+ * Smooth scroll to a value based on the index
+ *
+ * @param {Number} index: Index number
+ */
+ _smoothScrollToIndex(index) {
+ const element = this.elements.spinner.children[index];
+ if (element) {
+ element.scrollIntoView({
+ behavior: "smooth", block: "start"
+ });
+ }
+ },
+
+ /**
+ * Update the selection state.
+ */
+ _updateSelection() {
+ const { itemsViewElements, selected } = this.elements;
+ const { itemsView, index } = this.state;
+ const { viewportTopOffset } = this.props;
+ const currentItemIndex = index + viewportTopOffset;
+
+ if (selected && selected != itemsViewElements[currentItemIndex]) {
+ this._removeSelection();
+ }
+
+ this.elements.selected = itemsViewElements[currentItemIndex];
+ if (itemsView[currentItemIndex] && itemsView[currentItemIndex].enabled) {
+ this.elements.selected.classList.add("selection");
+ }
+ },
+
+ /**
+ * Remove selection if selected exists and different from current
+ */
+ _removeSelection() {
+ const { selected } = this.elements;
+ if (selected) {
+ selected.classList.remove("selection");
+ }
+ },
+
+ /**
+ * Compares arrays of objects. It assumes the structure is an array of
+ * objects, and objects in a and b have the same number of properties.
+ *
+ * @param {Array<Object>} a
+ * @param {Array<Object>} b
+ * @return {Boolean} Returns true if a and b are different
+ */
+ _isArrayDiff(a, b) {
+ // Check reference first, exit early if reference is the same.
+ if (a == b) {
+ return false;
+ }
+
+ if (a.length != b.length) {
+ return true;
+ }
+
+ for (let i = 0; i < a.length; i++) {
+ for (let prop in a[i]) {
+ if (a[i][prop] != b[i][prop]) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ };
+}
diff --git a/components/bindings/content/splitter.xml b/components/bindings/content/splitter.xml
new file mode 100644
index 000000000..d23631fbe
--- /dev/null
+++ b/components/bindings/content/splitter.xml
@@ -0,0 +1,37 @@
+<?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="splitterBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="splitter" extends="xul:splitter">
+ <resources>
+ <stylesheet src="chrome://global/skin/splitter.css"/>
+ </resources>
+ </binding>
+
+ <binding id="grippy" extends="xul:button">
+ <resources>
+ <stylesheet src="chrome://global/skin/splitter.css"/>
+ </resources>
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ var splitter = this.parentNode;
+ if (splitter) {
+ var state = splitter.getAttribute("state");
+ if (state == "collapsed")
+ splitter.setAttribute("state", "open");
+ else
+ splitter.setAttribute("state", "collapsed");
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/stringbundle.xml b/components/bindings/content/stringbundle.xml
new file mode 100644
index 000000000..3365bd61f
--- /dev/null
+++ b/components/bindings/content/stringbundle.xml
@@ -0,0 +1,96 @@
+<?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="stringBundleBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="stringbundleset" extends="xul:box"/>
+
+ <binding id="stringbundle" extends="xul:spacer">
+ <implementation name="XStringBundle">
+
+ <method name="getString">
+ <parameter name="aStringKey"/>
+ <body>
+ <![CDATA[
+ try {
+ return this.stringBundle.GetStringFromName(aStringKey);
+ }
+ catch (e) {
+ dump("*** Failed to get string " + aStringKey + " in bundle: " + this.src + "\n");
+ throw e;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="getFormattedString">
+ <parameter name="aStringKey"/>
+ <parameter name="aStringsArray"/>
+ <body>
+ <![CDATA[
+ try {
+ return this.stringBundle.formatStringFromName(aStringKey, aStringsArray, aStringsArray.length);
+ }
+ catch (e) {
+ dump("*** Failed to format string " + aStringKey + " in bundle: " + this.src + "\n");
+ throw e;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="stringBundle" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._bundle) {
+ try {
+ this._bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
+ .getService(Components.interfaces.nsIStringBundleService)
+ .createBundle(this.src);
+ }
+ catch (e) {
+ dump("Failed to get stringbundle:\n");
+ dump(e + "\n");
+ }
+ }
+ return this._bundle;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="src">
+ <getter>
+ <![CDATA[
+ return this.getAttribute("src");
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this._bundle = null;
+ this.setAttribute("src", val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="strings">
+ <getter>
+ <![CDATA[
+ // Note: this is a sucky method name! Should be:
+ // readonly attribute nsISimpleEnumerator strings;
+ return this.stringBundle.getSimpleEnumeration();
+ ]]>
+ </getter>
+ </property>
+
+ <field name="_bundle">null</field>
+
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/tabbox.xml b/components/bindings/content/tabbox.xml
new file mode 100644
index 000000000..60c395c13
--- /dev/null
+++ b/components/bindings/content/tabbox.xml
@@ -0,0 +1,870 @@
+<?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-base">
+ <resources>
+ <stylesheet src="chrome://global/skin/tabbox.css"/>
+ </resources>
+ </binding>
+
+ <binding id="tabbox"
+ extends="chrome://global/content/bindings/tabbox.xml#tab-base">
+ <implementation implements="nsIDOMEventListener">
+ <property name="handleCtrlTab">
+ <setter>
+ <![CDATA[
+ this.setAttribute("handleCtrlTab", val);
+ return val;
+ ]]>
+ </setter>
+ <getter>
+ <![CDATA[
+ return (this.getAttribute("handleCtrlTab") != "false");
+ ]]>
+ </getter>
+ </property>
+
+ <property name="handleCtrlPageUpDown">
+ <setter>
+ <![CDATA[
+ this.setAttribute("handleCtrlPageUpDown", val);
+ return val;
+ ]]>
+ </setter>
+ <getter>
+ <![CDATA[
+ return (this.getAttribute("handleCtrlPageUpDown") != "false");
+ ]]>
+ </getter>
+ </property>
+
+ <field name="_handleMetaAltArrows" readonly="true">
+ /Mac/.test(navigator.platform)
+ </field>
+
+ <!-- _tabs and _tabpanels are deprecated, they exist only for
+ backwards compatibility. -->
+ <property name="_tabs" readonly="true" onget="return this.tabs;"/>
+ <property name="_tabpanels" readonly="true" onget="return this.tabpanels;"/>
+
+ <property name="tabs" readonly="true">
+ <getter>
+ <![CDATA[
+ return this.getElementsByTagNameNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "tabs").item(0);
+ ]]>
+ </getter>
+ </property>
+
+ <property name="tabpanels" readonly="true">
+ <getter>
+ <![CDATA[
+ return this.getElementsByTagNameNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "tabpanels").item(0);
+ ]]>
+ </getter>
+ </property>
+
+ <property name="selectedIndex">
+ <getter>
+ <![CDATA[
+ var tabs = this.tabs;
+ return tabs ? tabs.selectedIndex : -1;
+ ]]>
+ </getter>
+
+ <setter>
+ <![CDATA[
+ var tabs = this.tabs;
+ if (tabs)
+ tabs.selectedIndex = val;
+ this.setAttribute("selectedIndex", val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedTab">
+ <getter>
+ <![CDATA[
+ var tabs = this.tabs;
+ return tabs && tabs.selectedItem;
+ ]]>
+ </getter>
+
+ <setter>
+ <![CDATA[
+ if (val) {
+ var tabs = this.tabs;
+ if (tabs)
+ tabs.selectedItem = val;
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedPanel">
+ <getter>
+ <![CDATA[
+ var tabpanels = this.tabpanels;
+ return tabpanels && tabpanels.selectedPanel;
+ ]]>
+ </getter>
+
+ <setter>
+ <![CDATA[
+ if (val) {
+ var tabpanels = this.tabpanels;
+ if (tabpanels)
+ tabpanels.selectedPanel = val;
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="handleEvent">
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ if (!event.isTrusted) {
+ // Don't let untrusted events mess with tabs.
+ return;
+ }
+
+ // Don't check if the event was already consumed because tab
+ // navigation should always work for better user experience.
+
+ switch (event.keyCode) {
+ case event.DOM_VK_TAB:
+ if (event.ctrlKey && !event.altKey && !event.metaKey)
+ if (this.tabs && this.handleCtrlTab) {
+ this.tabs.advanceSelectedTab(event.shiftKey ? -1 : 1, true);
+ event.preventDefault();
+ }
+ break;
+ case event.DOM_VK_PAGE_UP:
+ if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
+ if (this.tabs && this.handleCtrlPageUpDown) {
+ this.tabs.advanceSelectedTab(-1, true);
+ event.preventDefault();
+ }
+ break;
+ case event.DOM_VK_PAGE_DOWN:
+ if (event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey)
+ if (this.tabs && this.handleCtrlPageUpDown) {
+ this.tabs.advanceSelectedTab(1, true);
+ event.preventDefault();
+ }
+ break;
+ case event.DOM_VK_LEFT:
+ if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
+ if (this.tabs && this._handleMetaAltArrows) {
+ var offset = window.getComputedStyle(this, "")
+ .direction == "ltr" ? -1 : 1;
+ this.tabs.advanceSelectedTab(offset, true);
+ event.preventDefault();
+ }
+ break;
+ case event.DOM_VK_RIGHT:
+ if (event.metaKey && event.altKey && !event.shiftKey && !event.ctrlKey)
+ if (this.tabs && this._handleMetaAltArrows) {
+ offset = window.getComputedStyle(this, "")
+ .direction == "ltr" ? 1 : -1;
+ this.tabs.advanceSelectedTab(offset, true);
+ event.preventDefault();
+ }
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="_eventNode">this</field>
+
+ <property name="eventNode" onget="return this._eventNode;">
+ <setter>
+ <![CDATA[
+ if (val != this._eventNode) {
+ const nsIEventListenerService =
+ Components.interfaces.nsIEventListenerService;
+ let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
+ .getService(nsIEventListenerService);
+ els.addSystemEventListener(val, "keydown", this, false);
+ els.removeSystemEventListener(this._eventNode, "keydown", this, false);
+ this._eventNode = val;
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <constructor>
+ switch (this.getAttribute("eventnode")) {
+ case "parent": this._eventNode = this.parentNode; break;
+ case "window": this._eventNode = window; break;
+ case "document": this._eventNode = document; break;
+ }
+ const nsIEventListenerService =
+ Components.interfaces.nsIEventListenerService;
+ let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
+ .getService(nsIEventListenerService);
+ els.addSystemEventListener(this._eventNode, "keydown", this, false);
+ </constructor>
+
+ <destructor>
+ const nsIEventListenerService =
+ Components.interfaces.nsIEventListenerService;
+ let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
+ .getService(nsIEventListenerService);
+ els.removeSystemEventListener(this._eventNode, "keydown", this, false);
+ </destructor>
+ </implementation>
+ </binding>
+
+ <binding id="tabs" role="xul:tabs"
+ extends="chrome://global/content/bindings/general.xml#basecontrol">
+ <resources>
+ <stylesheet src="chrome://global/skin/tabbox.css"/>
+ </resources>
+
+ <content>
+ <xul:spacer class="tabs-left"/>
+ <children/>
+ <xul:spacer class="tabs-right" flex="1"/>
+ </content>
+
+ <implementation implements="nsIDOMXULSelectControlElement, nsIDOMXULRelatedElement">
+ <constructor>
+ <![CDATA[
+ // first and last tabs need to be able to have unique styles
+ // and also need to select first tab on startup.
+ if (this.firstChild)
+ this.firstChild.setAttribute("first-tab", "true");
+ if (this.lastChild)
+ this.lastChild.setAttribute("last-tab", "true");
+
+ if (!this.hasAttribute("orient"))
+ this.setAttribute("orient", "horizontal");
+
+ if (this.tabbox && this.tabbox.hasAttribute("selectedIndex")) {
+ let selectedIndex = parseInt(this.tabbox.getAttribute("selectedIndex"));
+ this.selectedIndex = selectedIndex > 0 ? selectedIndex : 0;
+ return;
+ }
+
+ var children = this.childNodes;
+ var length = children.length;
+ for (var i = 0; i < length; i++) {
+ if (children[i].getAttribute("selected") == "true") {
+ this.selectedIndex = i;
+ return;
+ }
+ }
+
+ var value = this.value;
+ if (value)
+ this.value = value;
+ else
+ this.selectedIndex = 0;
+ ]]>
+ </constructor>
+
+ <!-- nsIDOMXULRelatedElement -->
+ <method name="getRelatedElement">
+ <parameter name="aTabElm"/>
+ <body>
+ <![CDATA[
+ if (!aTabElm)
+ return null;
+
+ let tabboxElm = this.tabbox;
+ if (!tabboxElm)
+ return null;
+
+ let tabpanelsElm = tabboxElm.tabpanels;
+ if (!tabpanelsElm)
+ return null;
+
+ // Get linked tab panel by 'linkedpanel' attribute on the given tab
+ // element.
+ let linkedPanelElm = null;
+
+ let linkedPanelId = aTabElm.linkedPanel;
+ if (linkedPanelId) {
+ let ownerDoc = this.ownerDocument;
+
+ // XXX bug 565858: if XUL tab element is anonymous element then
+ // suppose linked tab panel is hosted within the same XBL binding
+ // and search it by ID attribute inside an anonymous content of
+ // the binding. This is not robust assumption since tab elements may
+ // live outside a tabbox element so that for example tab elements
+ // can be explicit content but tab panels can be anonymous.
+
+ let bindingParent = ownerDoc.getBindingParent(aTabElm);
+ if (bindingParent)
+ return ownerDoc.getAnonymousElementByAttribute(bindingParent,
+ "id",
+ linkedPanelId);
+
+ return ownerDoc.getElementById(linkedPanelId);
+ }
+
+ // otherwise linked tabpanel element has the same index as the given
+ // tab element.
+ let tabElmIdx = this.getIndexOfItem(aTabElm);
+ return tabpanelsElm.childNodes[tabElmIdx];
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIDOMXULSelectControlElement -->
+ <property name="itemCount" readonly="true"
+ onget="return this.childNodes.length"/>
+
+ <property name="value" onget="return this.getAttribute('value');">
+ <setter>
+ <![CDATA[
+ this.setAttribute("value", val);
+ var children = this.childNodes;
+ for (var c = children.length - 1; c >= 0; c--) {
+ if (children[c].value == val) {
+ this.selectedIndex = c;
+ break;
+ }
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <field name="_tabbox">null</field>
+ <property name="tabbox" readonly="true">
+ <getter><![CDATA[
+ // Memoize the result in a field rather than replacing this property,
+ // so that it can be reset along with the binding.
+ if (this._tabbox) {
+ return this._tabbox;
+ }
+
+ let parent = this.parentNode;
+ while (parent) {
+ if (parent.localName == "tabbox") {
+ break;
+ }
+ parent = parent.parentNode;
+ }
+
+ return this._tabbox = parent;
+ ]]></getter>
+ </property>
+
+ <!-- _tabbox is deprecated, it exists only for backwards compatibility. -->
+ <field name="_tabbox" readonly="true"><![CDATA[
+ this.tabbox;
+ ]]></field>
+
+ <property name="selectedIndex">
+ <getter>
+ <![CDATA[
+ const tabs = this.childNodes;
+ for (var i = 0; i < tabs.length; i++) {
+ if (tabs[i].selected)
+ return i;
+ }
+ return -1;
+ ]]>
+ </getter>
+
+ <setter>
+ <![CDATA[
+ var tab = this.getItemAtIndex(val);
+ if (tab) {
+ var alreadySelected = tab.selected;
+
+ Array.forEach(this.childNodes, function (aTab) {
+ if (aTab.selected && aTab != tab)
+ aTab._selected = false;
+ });
+ tab._selected = true;
+
+ this.setAttribute("value", tab.value);
+
+ let linkedPanel = this.getRelatedElement(tab);
+ if (linkedPanel) {
+ this.tabbox.setAttribute("selectedIndex", val);
+
+ // This will cause an onselect event to fire for the tabpanel
+ // element.
+ this.tabbox.tabpanels.selectedPanel = linkedPanel;
+ }
+
+ if (!alreadySelected) {
+ // Fire an onselect event for the tabs element.
+ var event = document.createEvent('Events');
+ event.initEvent('select', true, true);
+ this.dispatchEvent(event);
+ }
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedItem">
+ <getter>
+ <![CDATA[
+ const tabs = this.childNodes;
+ for (var i = 0; i < tabs.length; i++) {
+ if (tabs[i].selected)
+ return tabs[i];
+ }
+ return null;
+ ]]>
+ </getter>
+
+ <setter>
+ <![CDATA[
+ if (val && !val.selected)
+ // The selectedIndex setter ignores invalid values
+ // such as -1 if |val| isn't one of our child nodes.
+ this.selectedIndex = this.getIndexOfItem(val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="getIndexOfItem">
+ <parameter name="item"/>
+ <body>
+ <![CDATA[
+ return Array.indexOf(this.childNodes, item);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getItemAtIndex">
+ <parameter name="index"/>
+ <body>
+ <![CDATA[
+ return this.childNodes.item(index);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_selectNewTab">
+ <parameter name="aNewTab"/>
+ <parameter name="aFallbackDir"/>
+ <parameter name="aWrap"/>
+ <body>
+ <![CDATA[
+ var requestedTab = aNewTab;
+ while (aNewTab.hidden || aNewTab.disabled || !this._canAdvanceToTab(aNewTab)) {
+ aNewTab = aFallbackDir == -1 ? aNewTab.previousSibling : aNewTab.nextSibling;
+ if (!aNewTab && aWrap)
+ aNewTab = aFallbackDir == -1 ? this.childNodes[this.childNodes.length - 1] :
+ this.childNodes[0];
+ if (!aNewTab || aNewTab == requestedTab)
+ return;
+ }
+
+ var isTabFocused = false;
+ try {
+ isTabFocused =
+ (document.commandDispatcher.focusedElement == this.selectedItem);
+ } catch (e) {}
+ this.selectedItem = aNewTab;
+ if (isTabFocused) {
+ aNewTab.focus();
+ }
+ else if (this.getAttribute("setfocus") != "false") {
+ let selectedPanel = this.tabbox.selectedPanel;
+ document.commandDispatcher.advanceFocusIntoSubtree(selectedPanel);
+
+ // Make sure that the focus doesn't move outside the tabbox
+ if (this.tabbox) {
+ try {
+ let el = document.commandDispatcher.focusedElement;
+ while (el && el != this.tabbox.tabpanels) {
+ if (el == this.tabbox || el == selectedPanel)
+ return;
+ el = el.parentNode;
+ }
+ aNewTab.focus();
+ } catch (e) {
+ }
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_canAdvanceToTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="advanceSelectedTab">
+ <parameter name="aDir"/>
+ <parameter name="aWrap"/>
+ <body>
+ <![CDATA[
+ var startTab = this.selectedItem;
+ var next = startTab[aDir == -1 ? "previousSibling" : "nextSibling"];
+ if (!next && aWrap) {
+ next = aDir == -1 ? this.childNodes[this.childNodes.length - 1] :
+ this.childNodes[0];
+ }
+ if (next && next != startTab) {
+ this._selectNewTab(next, aDir, aWrap);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="appendItem">
+ <parameter name="label"/>
+ <parameter name="value"/>
+ <body>
+ <![CDATA[
+ var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var tab = document.createElementNS(XULNS, "tab");
+ tab.setAttribute("label", label);
+ tab.setAttribute("value", value);
+ this.appendChild(tab);
+ return tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="insertItemAt">
+ <parameter name="index"/>
+ <parameter name="label"/>
+ <parameter name="value"/>
+ <body>
+ <![CDATA[
+ var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var tab = document.createElementNS(XULNS, "tab");
+ tab.setAttribute("label", label);
+ tab.setAttribute("value", value);
+ var before = this.getItemAtIndex(index);
+ if (before)
+ this.insertBefore(tab, before);
+ else
+ this.appendChild(tab);
+ return tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeItemAt">
+ <parameter name="index"/>
+ <body>
+ <![CDATA[
+ var remove = this.getItemAtIndex(index);
+ if (remove)
+ this.removeChild(remove);
+ return remove;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+#ifdef MOZ_WIDGET_GTK
+ <handlers>
+ <handler event="DOMMouseScroll">
+ <![CDATA[
+ if (event.detail > 0)
+ this.advanceSelectedTab(1, false);
+ else
+ this.advanceSelectedTab(-1, false);
+
+ event.stopPropagation();
+ ]]>
+ </handler>
+ </handlers>
+#endif
+ </binding>
+
+ <binding id="tabpanels" role="xul:tabpanels"
+ extends="chrome://global/content/bindings/tabbox.xml#tab-base">
+ <implementation implements="nsIDOMXULRelatedElement">
+ <!-- nsIDOMXULRelatedElement -->
+ <method name="getRelatedElement">
+ <parameter name="aTabPanelElm"/>
+ <body>
+ <![CDATA[
+ if (!aTabPanelElm)
+ return null;
+
+ let tabboxElm = this.tabbox;
+ if (!tabboxElm)
+ return null;
+
+ let tabsElm = tabboxElm.tabs;
+ if (!tabsElm)
+ return null;
+
+ // Return tab element having 'linkedpanel' attribute equal to the id
+ // of the tab panel or the same index as the tab panel element.
+ let tabpanelIdx = Array.indexOf(this.childNodes, aTabPanelElm);
+ if (tabpanelIdx == -1)
+ return null;
+
+ let tabElms = tabsElm.childNodes;
+ let tabElmFromIndex = tabElms[tabpanelIdx];
+
+ let tabpanelId = aTabPanelElm.id;
+ if (tabpanelId) {
+ for (let idx = 0; idx < tabElms.length; idx++) {
+ var tabElm = tabElms[idx];
+ if (tabElm.linkedPanel == tabpanelId)
+ return tabElm;
+ }
+ }
+
+ return tabElmFromIndex;
+ ]]>
+ </body>
+ </method>
+
+ <!-- public -->
+ <field name="_tabbox">null</field>
+ <property name="tabbox" readonly="true">
+ <getter><![CDATA[
+ // Memoize the result in a field rather than replacing this property,
+ // so that it can be reset along with the binding.
+ if (this._tabbox) {
+ return this._tabbox;
+ }
+
+ let parent = this.parentNode;
+ while (parent) {
+ if (parent.localName == "tabbox") {
+ break;
+ }
+ parent = parent.parentNode;
+ }
+
+ return this._tabbox = parent;
+ ]]></getter>
+ </property>
+
+ <field name="_selectedPanel">this.childNodes.item(this.selectedIndex)</field>
+
+ <property name="selectedIndex">
+ <getter>
+ <![CDATA[
+ var indexStr = this.getAttribute("selectedIndex");
+ return indexStr ? parseInt(indexStr) : -1;
+ ]]>
+ </getter>
+
+ <setter>
+ <![CDATA[
+ if (val < 0 || val >= this.childNodes.length)
+ return val;
+ var panel = this._selectedPanel;
+ this._selectedPanel = this.childNodes[val];
+ this.setAttribute("selectedIndex", val);
+ if (this._selectedPanel != panel) {
+ var event = document.createEvent("Events");
+ event.initEvent("select", true, true);
+ this.dispatchEvent(event);
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedPanel">
+ <getter>
+ <![CDATA[
+ return this._selectedPanel;
+ ]]>
+ </getter>
+
+ <setter>
+ <![CDATA[
+ var selectedIndex = -1;
+ for (var panel = val; panel != null; panel = panel.previousSibling)
+ ++selectedIndex;
+ this.selectedIndex = selectedIndex;
+ return val;
+ ]]>
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="tab" display="xul:button" role="xul:tab"
+ extends="chrome://global/content/bindings/general.xml#control-item">
+ <resources>
+ <stylesheet src="chrome://global/skin/tabbox.css"/>
+ </resources>
+
+ <content>
+ <xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected,visuallyselected" flex="1">
+ <xul:image class="tab-icon"
+ xbl:inherits="validate,src=image"
+ role="presentation"/>
+ <xul:label class="tab-text"
+ xbl:inherits="value=label,accesskey,crop,disabled"
+ flex="1"
+ role="presentation"/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <property name="control" readonly="true">
+ <getter>
+ <![CDATA[
+ var parent = this.parentNode;
+ if (parent instanceof Components.interfaces.nsIDOMXULSelectControlElement)
+ return parent;
+ return null;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="selected" readonly="true"
+ onget="return this.getAttribute('selected') == 'true';"/>
+
+ <property name="_selected">
+ <setter><![CDATA[
+ if (val) {
+ this.setAttribute("selected", "true");
+ this.setAttribute("visuallyselected", "true");
+ } else {
+ this.removeAttribute("selected");
+ this.removeAttribute("visuallyselected");
+ }
+
+ this._setPositionAttributes(val);
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <method name="_setPositionAttributes">
+ <parameter name="aSelected"/>
+ <body><![CDATA[
+ if (this.previousSibling && this.previousSibling.localName == "tab") {
+ if (aSelected)
+ this.previousSibling.setAttribute("beforeselected", "true");
+ else
+ this.previousSibling.removeAttribute("beforeselected");
+ this.removeAttribute("first-tab");
+ } else {
+ this.setAttribute("first-tab", "true");
+ }
+
+ if (this.nextSibling && this.nextSibling.localName == "tab") {
+ if (aSelected)
+ this.nextSibling.setAttribute("afterselected", "true");
+ else
+ this.nextSibling.removeAttribute("afterselected");
+ this.removeAttribute("last-tab");
+ } else {
+ this.setAttribute("last-tab", "true");
+ }
+ ]]></body>
+ </method>
+
+ <property name="linkedPanel" onget="return this.getAttribute('linkedpanel')"
+ onset="this.setAttribute('linkedpanel', val); return val;"/>
+
+ <field name="arrowKeysShouldWrap" readonly="true">
+ /Mac/.test(navigator.platform)
+ </field>
+ </implementation>
+
+ <handlers>
+ <handler event="mousedown" button="0">
+ <![CDATA[
+ if (this.disabled)
+ return;
+
+ if (this != this.parentNode.selectedItem) { // Not selected yet
+ // Call this before setting the 'ignorefocus' attribute because this
+ // will pass on focus if the formerly selected tab was focused as well.
+ this.parentNode._selectNewTab(this);
+
+ var isTabFocused = false;
+ try {
+ isTabFocused = (document.commandDispatcher.focusedElement == this);
+ } catch (e) {}
+
+ // Set '-moz-user-focus' to 'ignore' so that PostHandleEvent() can't
+ // focus the tab; we only want tabs to be focusable by the mouse if
+ // they are already focused. After a short timeout we'll reset
+ // '-moz-user-focus' so that tabs can be focused by keyboard again.
+ if (!isTabFocused) {
+ this.setAttribute("ignorefocus", "true");
+ setTimeout(tab => tab.removeAttribute("ignorefocus"), 0, this);
+ }
+ }
+ // Otherwise this tab is already selected and we will fall
+ // through to mousedown behavior which sets focus on the current tab,
+ // Only a click on an already selected tab should focus the tab itself.
+ ]]>
+ </handler>
+
+ <handler event="keydown" keycode="VK_LEFT" group="system" preventdefault="true">
+ <![CDATA[
+ var direction = window.getComputedStyle(this.parentNode, null).direction;
+ this.parentNode.advanceSelectedTab(direction == 'ltr' ? -1 : 1, this.arrowKeysShouldWrap);
+ ]]>
+ </handler>
+
+ <handler event="keydown" keycode="VK_RIGHT" group="system" preventdefault="true">
+ <![CDATA[
+ var direction = window.getComputedStyle(this.parentNode, null).direction;
+ this.parentNode.advanceSelectedTab(direction == 'ltr' ? 1 : -1, this.arrowKeysShouldWrap);
+ ]]>
+ </handler>
+
+ <handler event="keydown" keycode="VK_UP" group="system" preventdefault="true">
+ <![CDATA[
+ this.parentNode.advanceSelectedTab(-1, this.arrowKeysShouldWrap);
+ ]]>
+ </handler>
+
+ <handler event="keydown" keycode="VK_DOWN" group="system" preventdefault="true">
+ <![CDATA[
+ this.parentNode.advanceSelectedTab(1, this.arrowKeysShouldWrap);
+ ]]>
+ </handler>
+
+ <handler event="keydown" keycode="VK_HOME" group="system" preventdefault="true">
+ <![CDATA[
+ this.parentNode._selectNewTab(this.parentNode.childNodes[0]);
+ ]]>
+ </handler>
+
+ <handler event="keydown" keycode="VK_END" group="system" preventdefault="true">
+ <![CDATA[
+ var tabs = this.parentNode.childNodes;
+ this.parentNode._selectNewTab(tabs[tabs.length - 1], -1);
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+</bindings>
+
diff --git a/components/bindings/content/text.xml b/components/bindings/content/text.xml
new file mode 100644
index 000000000..ed998cee4
--- /dev/null
+++ b/components/bindings/content/text.xml
@@ -0,0 +1,386 @@
+<?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="textBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <!-- bound to <description>s -->
+ <binding id="text-base" role="xul:text">
+ <implementation implements="nsIDOMXULDescriptionElement">
+ <property name="disabled" onset="if (val) this.setAttribute('disabled', 'true');
+ else this.removeAttribute('disabled');
+ return val;"
+ onget="return this.getAttribute('disabled') == 'true';"/>
+ <property name="value" onget="return this.getAttribute('value');"
+ onset="this.setAttribute('value', val); return val;"/>
+ <property name="crop" onget="return this.getAttribute('crop');"
+ onset="this.setAttribute('crop', val); return val;"/>
+ </implementation>
+ </binding>
+
+ <binding id="text-label" extends="chrome://global/content/bindings/text.xml#text-base">
+ <implementation implements="nsIDOMXULLabelElement">
+ <property name="accessKey">
+ <getter>
+ <![CDATA[
+ var accessKey = this.getAttribute('accesskey');
+ return accessKey ? accessKey[0] : null;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this.setAttribute('accesskey', val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="control" onget="return getAttribute('control');">
+ <setter>
+ <![CDATA[
+ // After this gets set, the label will use the binding #label-control
+ this.setAttribute('control', val);
+ return val;
+ ]]>
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="label-control" extends="chrome://global/content/bindings/text.xml#text-label">
+ <content>
+ <children/><html:span anonid="accessKeyParens"></html:span>
+ </content>
+ <implementation implements="nsIDOMXULLabelElement">
+ <constructor>
+ <![CDATA[
+ this.formatAccessKey(true);
+ ]]>
+ </constructor>
+
+ <method name="formatAccessKey">
+ <parameter name="firstTime"/>
+ <body>
+ <![CDATA[
+ var control = this.labeledControlElement;
+ if (!control) {
+ var bindingParent = document.getBindingParent(this);
+ if (bindingParent instanceof Components.interfaces.nsIDOMXULLabeledControlElement) {
+ control = bindingParent; // For controls that make the <label> an anon child
+ }
+ }
+ if (control) {
+ control.labelElement = this;
+ }
+
+ var accessKey = this.accessKey;
+ // No need to remove existing formatting the first time.
+ if (firstTime && !accessKey)
+ return;
+
+ if (this.mInsertSeparator === undefined) {
+ try {
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+ this.mUnderlineAccesskey = (prefs.getIntPref("ui.key.menuAccessKey") != 0);
+
+ const nsIPrefLocalizedString =
+ Components.interfaces.nsIPrefLocalizedString;
+
+ const prefNameInsertSeparator =
+ "intl.menuitems.insertseparatorbeforeaccesskeys";
+ const prefNameAlwaysAppendAccessKey =
+ "intl.menuitems.alwaysappendaccesskeys";
+
+ var val = prefs.getComplexValue(prefNameInsertSeparator,
+ nsIPrefLocalizedString).data;
+ this.mInsertSeparator = (val == "true");
+
+ val = prefs.getComplexValue(prefNameAlwaysAppendAccessKey,
+ nsIPrefLocalizedString).data;
+ this.mAlwaysAppendAccessKey = (val == "true");
+ }
+ catch (e) {
+ this.mInsertSeparator = true;
+ }
+ }
+
+ if (!this.mUnderlineAccesskey)
+ return;
+
+ var afterLabel = document.getAnonymousElementByAttribute(this, "anonid", "accessKeyParens");
+ afterLabel.textContent = "";
+
+ var oldAccessKey = this.getElementsByAttribute('class', 'accesskey').item(0);
+ if (oldAccessKey) { // Clear old accesskey
+ this.mergeElement(oldAccessKey);
+ }
+
+ var oldHiddenSpan =
+ this.getElementsByAttribute('class', 'hiddenColon').item(0);
+ if (oldHiddenSpan) {
+ this.mergeElement(oldHiddenSpan);
+ }
+
+ var labelText = this.textContent;
+ if (!accessKey || !labelText || !control) {
+ return;
+ }
+ var accessKeyIndex = -1;
+ if (!this.mAlwaysAppendAccessKey) {
+ accessKeyIndex = labelText.indexOf(accessKey);
+ if (accessKeyIndex < 0) { // Try again in upper case
+ accessKeyIndex =
+ labelText.toUpperCase().indexOf(accessKey.toUpperCase());
+ }
+ }
+
+ const HTML_NS = "http://www.w3.org/1999/xhtml";
+ var span = document.createElementNS(HTML_NS, "span");
+ span.className = "accesskey";
+
+ // Note that if you change the following code, see the comment of
+ // nsTextBoxFrame::UpdateAccessTitle.
+
+ // If accesskey is not in string, append in parentheses
+ if (accessKeyIndex < 0) {
+ // If end is colon, we should insert before colon.
+ // i.e., "label:" -> "label(X):"
+ var colonHidden = false;
+ if (/:$/.test(labelText)) {
+ labelText = labelText.slice(0, -1);
+ var hiddenSpan = document.createElementNS(HTML_NS, "span");
+ hiddenSpan.className = "hiddenColon";
+ hiddenSpan.style.display = "none";
+ // Hide the last colon by using span element.
+ // I.e., label<span style="display:none;">:</span>
+ this.wrapChar(hiddenSpan, labelText.length);
+ colonHidden = true;
+ }
+ // If end is space(U+20),
+ // we should not add space before parentheses.
+ var endIsSpace = false;
+ if (/ $/.test(labelText)) {
+ endIsSpace = true;
+ }
+ if (this.mInsertSeparator && !endIsSpace)
+ afterLabel.textContent = " (";
+ else
+ afterLabel.textContent = "(";
+ span.textContent = accessKey.toUpperCase();
+ afterLabel.appendChild(span);
+ if (!colonHidden)
+ afterLabel.appendChild(document.createTextNode(")"));
+ else
+ afterLabel.appendChild(document.createTextNode("):"));
+ return;
+ }
+ this.wrapChar(span, accessKeyIndex);
+ ]]>
+ </body>
+ </method>
+
+ <method name="wrapChar">
+ <parameter name="element"/>
+ <parameter name="index"/>
+ <body>
+ <![CDATA[
+ var treeWalker = document.createTreeWalker(this,
+ NodeFilter.SHOW_TEXT,
+ null);
+ var node = treeWalker.nextNode();
+ while (index >= node.length) {
+ index -= node.length;
+ node = treeWalker.nextNode();
+ }
+ if (index) {
+ node = node.splitText(index);
+ }
+ node.parentNode.insertBefore(element, node);
+ if (node.length > 1) {
+ node.splitText(1);
+ }
+ element.appendChild(node);
+ ]]>
+ </body>
+ </method>
+
+ <method name="mergeElement">
+ <parameter name="element"/>
+ <body>
+ <![CDATA[
+ if (element.previousSibling instanceof Text) {
+ element.previousSibling.appendData(element.textContent)
+ }
+ else {
+ element.parentNode.insertBefore(element.firstChild, element);
+ }
+ element.parentNode.removeChild(element);
+ ]]>
+ </body>
+ </method>
+
+ <field name="mUnderlineAccesskey">
+ !/Mac/.test(navigator.platform)
+ </field>
+ <field name="mInsertSeparator"/>
+ <field name="mAlwaysAppendAccessKey">false</field>
+
+ <property name="accessKey">
+ <getter>
+ <![CDATA[
+ var accessKey = null;
+ var labeledEl = this.labeledControlElement;
+ if (labeledEl) {
+ accessKey = labeledEl.getAttribute('accesskey');
+ }
+ if (!accessKey) {
+ accessKey = this.getAttribute('accesskey');
+ }
+ return accessKey ? accessKey[0] : null;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ // If this label already has an accesskey attribute store it here as well
+ if (this.hasAttribute('accesskey')) {
+ this.setAttribute('accesskey', val);
+ }
+ var control = this.labeledControlElement;
+ if (control) {
+ control.setAttribute('accesskey', val);
+ }
+ this.formatAccessKey(false);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="labeledControlElement" readonly="true"
+ onget="var control = this.control; return control ? document.getElementById(control) : null;" />
+
+ <property name="control" onget="return this.getAttribute('control');">
+ <setter>
+ <![CDATA[
+ var control = this.labeledControlElement;
+ if (control) {
+ control.labelElement = null; // No longer pointed to be this label
+ }
+ this.setAttribute('control', val);
+ this.formatAccessKey(false);
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ </implementation>
+
+ <handlers>
+ <handler event="click" action="if (this.disabled) return;
+ var controlElement = this.labeledControlElement;
+ if(controlElement)
+ controlElement.focus();
+ "/>
+ </handlers>
+ </binding>
+
+ <binding id="text-link" extends="chrome://global/content/bindings/text.xml#text-label" role="xul:link">
+ <implementation>
+ <property name="href" onget="return this.getAttribute('href');"
+ onset="this.setAttribute('href', val); return val;" />
+ <method name="open">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ var href = this.href;
+ if (!href || this.disabled || aEvent.defaultPrevented)
+ return;
+
+ var uri = null;
+ try {
+ const nsISSM = Components.interfaces.nsIScriptSecurityManager;
+ const secMan =
+ Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(nsISSM);
+
+ const ioService =
+ Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ uri = ioService.newURI(href, null, null);
+
+ let principal;
+ if (this.getAttribute("useoriginprincipal") == "true") {
+ principal = this.nodePrincipal;
+ } else {
+ principal = secMan.createNullPrincipal({});
+ }
+ try {
+ secMan.checkLoadURIWithPrincipal(principal, uri,
+ nsISSM.DISALLOW_INHERIT_PRINCIPAL);
+ }
+ catch (ex) {
+ var msg = "Error: Cannot open a " + uri.scheme + ": link using \
+ the text-link binding.";
+ Components.utils.reportError(msg);
+ return;
+ }
+
+ const cID = "@mozilla.org/uriloader/external-protocol-service;1";
+ const nsIEPS = Components.interfaces.nsIExternalProtocolService;
+ var protocolSvc = Components.classes[cID].getService(nsIEPS);
+
+ // if the scheme is not an exposed protocol, then opening this link
+ // should be deferred to the system's external protocol handler
+ if (!protocolSvc.isExposedProtocol(uri.scheme)) {
+ protocolSvc.loadUrl(uri);
+ aEvent.preventDefault()
+ return;
+ }
+
+ }
+ catch (ex) {
+ Components.utils.reportError(ex);
+ }
+
+ aEvent.preventDefault();
+ href = uri ? uri.spec : href;
+
+ // Try handing off the link to the host application, e.g. for
+ // opening it in a tabbed browser.
+ var linkHandled = Components.classes["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Components.interfaces.nsISupportsPRBool);
+ linkHandled.data = false;
+ let {shiftKey, ctrlKey, metaKey, altKey, button} = aEvent;
+ let data = {shiftKey, ctrlKey, metaKey, altKey, button, href};
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .notifyObservers(linkHandled, "handle-xul-text-link", JSON.stringify(data));
+ if (linkHandled.data)
+ return;
+
+ // otherwise, fall back to opening the anchor directly
+ var win = window;
+ if (window instanceof Components.interfaces.nsIDOMChromeWindow) {
+ while (win.opener && !win.opener.closed)
+ win = win.opener;
+ }
+ win.open(href);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="click" phase="capturing" button="0" action="this.open(event)"/>
+ <handler event="click" phase="capturing" button="1" action="this.open(event)"/>
+ <handler event="keypress" preventdefault="true" keycode="VK_RETURN" action="this.click()" />
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/textbox.xml b/components/bindings/content/textbox.xml
new file mode 100644
index 000000000..f166fb78a
--- /dev/null
+++ b/components/bindings/content/textbox.xml
@@ -0,0 +1,646 @@
+<?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 % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
+ %textcontextDTD;
+]>
+
+<bindings id="textboxBindings"
+ 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="textbox" extends="xul:box" role="xul:textbox">
+ <resources>
+ <stylesheet src="chrome://global/content/textbox.css"/>
+ <stylesheet src="chrome://global/skin/textbox.css"/>
+ </resources>
+
+ <content>
+ <children/>
+ <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,spellcheck">
+ <html:input class="textbox-input" anonid="input"
+ xbl:inherits="value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,noinitialfocus,mozactionhint,spellcheck"/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMXULTextBoxElement, nsIDOMXULLabeledControlElement">
+ <!-- nsIDOMXULLabeledControlElement -->
+ <field name="crop">""</field>
+ <field name="image">""</field>
+ <field name="command">""</field>
+ <field name="accessKey">""</field>
+
+ <field name="mInputField">null</field>
+ <field name="mIgnoreClick">false</field>
+ <field name="mIgnoreFocus">false</field>
+ <field name="mEditor">null</field>
+
+ <property name="inputField" readonly="true">
+ <getter><![CDATA[
+ if (!this.mInputField)
+ this.mInputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
+ return this.mInputField;
+ ]]></getter>
+ </property>
+
+ <property name="value" onset="this.inputField.value = val; return val;"
+ onget="return this.inputField.value;"/>
+ <property name="defaultValue" onset="this.inputField.defaultValue = val; return val;"
+ onget="return this.inputField.defaultValue;"/>
+ <property name="label" onset="this.setAttribute('label', val); return val;"
+ onget="return this.getAttribute('label') ||
+ (this.labelElement ? this.labelElement.value :
+ this.placeholder);"/>
+ <property name="placeholder" onset="this.inputField.placeholder = val; return val;"
+ onget="return this.inputField.placeholder;"/>
+ <property name="emptyText" onset="this.placeholder = val; return val;"
+ onget="return this.placeholder;"/>
+ <property name="type" onset="if (val) this.setAttribute('type', val);
+ else this.removeAttribute('type'); return val;"
+ onget="return this.getAttribute('type');"/>
+ <property name="maxLength" onset="this.inputField.maxLength = val; return val;"
+ onget="return this.inputField.maxLength;"/>
+ <property name="disabled" onset="this.inputField.disabled = val;
+ if (val) this.setAttribute('disabled', 'true');
+ else this.removeAttribute('disabled'); return val;"
+ onget="return this.inputField.disabled;"/>
+ <property name="tabIndex" onget="return parseInt(this.getAttribute('tabindex'));"
+ onset="this.inputField.tabIndex = val;
+ if (val) this.setAttribute('tabindex', val);
+ else this.removeAttribute('tabindex'); return val;"/>
+ <property name="size" onset="this.inputField.size = val; return val;"
+ onget="return this.inputField.size;"/>
+ <property name="readOnly" onset="this.inputField.readOnly = val;
+ if (val) this.setAttribute('readonly', 'true');
+ else this.removeAttribute('readonly'); return val;"
+ onget="return this.inputField.readOnly;"/>
+ <property name="clickSelectsAll"
+ onget="return this.getAttribute('clickSelectsAll') == 'true';"
+ onset="if (val) this.setAttribute('clickSelectsAll', 'true');
+ else this.removeAttribute('clickSelectsAll'); return val;" />
+
+ <property name="editor" readonly="true">
+ <getter><![CDATA[
+ if (!this.mEditor) {
+ const nsIDOMNSEditableElement = Components.interfaces.nsIDOMNSEditableElement;
+ this.mEditor = this.inputField.QueryInterface(nsIDOMNSEditableElement).editor;
+ }
+ return this.mEditor;
+ ]]></getter>
+ </property>
+
+ <method name="reset">
+ <body><![CDATA[
+ this.value = this.defaultValue;
+ try {
+ this.editor.transactionManager.clear();
+ return true;
+ }
+ catch (e) {}
+ return false;
+ ]]></body>
+ </method>
+
+ <method name="select">
+ <body>
+ this.inputField.select();
+ </body>
+ </method>
+
+ <property name="controllers" readonly="true" onget="return this.inputField.controllers"/>
+ <property name="textLength" readonly="true"
+ onget="return this.inputField.textLength;"/>
+ <property name="selectionStart" onset="this.inputField.selectionStart = val; return val;"
+ onget="return this.inputField.selectionStart;"/>
+ <property name="selectionEnd" onset="this.inputField.selectionEnd = val; return val;"
+ onget="return this.inputField.selectionEnd;"/>
+
+ <method name="setSelectionRange">
+ <parameter name="aSelectionStart"/>
+ <parameter name="aSelectionEnd"/>
+ <body>
+ this.inputField.setSelectionRange( aSelectionStart, aSelectionEnd );
+ </body>
+ </method>
+
+ <method name="_setNewlineHandling">
+ <body><![CDATA[
+ var str = this.getAttribute("newlines");
+ if (str && this.editor) {
+ const nsIPlaintextEditor = Components.interfaces.nsIPlaintextEditor;
+ for (var x in nsIPlaintextEditor) {
+ if (/^eNewlines/.test(x)) {
+ if (str == RegExp.rightContext.toLowerCase()) {
+ this.editor.QueryInterface(nsIPlaintextEditor)
+ .newlineHandling = nsIPlaintextEditor[x];
+ break;
+ }
+ }
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_maybeSelectAll">
+ <body><![CDATA[
+ if (!this.mIgnoreClick && this.clickSelectsAll &&
+ document.activeElement == this.inputField &&
+ this.inputField.selectionStart == this.inputField.selectionEnd)
+ this.editor.selectAll();
+ ]]></body>
+ </method>
+
+ <constructor><![CDATA[
+ var str = this.boxObject.getProperty("value");
+ if (str) {
+ this.inputField.value = str;
+ this.boxObject.removeProperty("value");
+ }
+
+ this._setNewlineHandling();
+
+ if (this.hasAttribute("emptytext"))
+ this.placeholder = this.getAttribute("emptytext");
+ ]]></constructor>
+
+ <destructor>
+ <![CDATA[
+ var field = this.inputField;
+ if (field && field.value)
+ this.boxObject.setProperty('value', field.value);
+ this.mInputField = null;
+ ]]>
+ </destructor>
+
+ </implementation>
+
+ <handlers>
+ <handler event="focus" phase="capturing">
+ <![CDATA[
+ if (this.hasAttribute("focused"))
+ return;
+
+ switch (event.originalTarget) {
+ case this:
+ // Forward focus to actual HTML input
+ this.inputField.focus();
+ break;
+ case this.inputField:
+ if (this.mIgnoreFocus) {
+ this.mIgnoreFocus = false;
+ } else if (this.clickSelectsAll) {
+ try {
+ const nsIEditorIMESupport =
+ Components.interfaces.nsIEditorIMESupport;
+ let imeEditor = this.editor.QueryInterface(nsIEditorIMESupport);
+ if (!imeEditor || !imeEditor.composing)
+ this.editor.selectAll();
+ } catch (e) {}
+ }
+ break;
+ default:
+ // Allow other children (e.g. URL bar buttons) to get focus
+ return;
+ }
+ this.setAttribute("focused", "true");
+ ]]>
+ </handler>
+
+ <handler event="blur" phase="capturing">
+ <![CDATA[
+ this.removeAttribute("focused");
+
+ // don't trigger clickSelectsAll when switching application windows
+ if (window == window.top &&
+ window.constructor == ChromeWindow &&
+ document.activeElement == this.inputField)
+ this.mIgnoreFocus = true;
+ ]]>
+ </handler>
+
+ <handler event="mousedown">
+ <![CDATA[
+ this.mIgnoreClick = this.hasAttribute("focused");
+
+ if (!this.mIgnoreClick) {
+ this.mIgnoreFocus = true;
+ this.inputField.setSelectionRange(0, 0);
+ if (event.originalTarget == this ||
+ event.originalTarget == this.inputField.parentNode)
+ this.inputField.focus();
+ }
+ ]]>
+ </handler>
+
+ <handler event="click" action="this._maybeSelectAll();"/>
+
+#ifndef XP_WIN
+ <handler event="contextmenu">
+ // Only care about context clicks on the textbox itself.
+ if (event.target != this)
+ return;
+
+ if (!event.button) // context menu opened via keyboard shortcut
+ return;
+ this._maybeSelectAll();
+ // see bug 576135 comment 4
+ let box = this.inputField.parentNode;
+ let menu = document.getAnonymousElementByAttribute(box, "anonid", "input-box-contextmenu");
+ box._doPopupItemEnabling(menu);
+ </handler>
+#endif
+ </handlers>
+ </binding>
+
+ <binding id="timed-textbox" extends="chrome://global/content/bindings/textbox.xml#textbox">
+ <implementation>
+ <constructor><![CDATA[
+ try {
+ var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService);
+ var scriptError = Components.classes["@mozilla.org/scripterror;1"]
+ .createInstance(Components.interfaces.nsIScriptError);
+ scriptError.init("Timed textboxes are deprecated. Consider using type=\"search\" instead.",
+ this.ownerDocument.location.href, null, null,
+ null, scriptError.warningFlag, "XUL Widgets");
+ consoleService.logMessage(scriptError);
+ } catch (e) {}
+ ]]></constructor>
+ <field name="_timer">null</field>
+ <property name="timeout"
+ onset="this.setAttribute('timeout', val); return val;"
+ onget="return parseInt(this.getAttribute('timeout')) || 0;"/>
+ <property name="value"
+ onget="return this.inputField.value;">
+ <setter><![CDATA[
+ this.inputField.value = val;
+ if (this._timer)
+ clearTimeout(this._timer);
+ return val;
+ ]]></setter>
+ </property>
+ <method name="_fireCommand">
+ <parameter name="me"/>
+ <body>
+ <![CDATA[
+ me._timer = null;
+ me.doCommand();
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ <handlers>
+ <handler event="input">
+ <![CDATA[
+ if (this._timer)
+ clearTimeout(this._timer);
+ this._timer = this.timeout && setTimeout(this._fireCommand, this.timeout, this);
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_RETURN">
+ <![CDATA[
+ if (this._timer)
+ clearTimeout(this._timer);
+ this._fireCommand(this);
+ event.preventDefault();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="search-textbox" extends="chrome://global/content/bindings/textbox.xml#textbox">
+ <content>
+ <children/>
+ <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,spellcheck" align="center">
+ <html:input class="textbox-input" anonid="input" mozactionhint="search"
+ xbl:inherits="value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,spellcheck"/>
+ <xul:deck class="textbox-search-icons" anonid="search-icons">
+ <xul:image class="textbox-search-icon" anonid="searchbutton-icon"
+ xbl:inherits="src=image,label=searchbuttonlabel,searchbutton,disabled"/>
+ <xul:image class="textbox-search-clear"
+ onclick="document.getBindingParent(this)._clearSearch();"
+ label="&searchTextBox.clear.label;"
+ xbl:inherits="disabled"/>
+ </xul:deck>
+ </xul:hbox>
+ </content>
+ <implementation>
+ <field name="_timer">null</field>
+ <field name="_searchIcons">
+ document.getAnonymousElementByAttribute(this, "anonid", "search-icons");
+ </field>
+ <field name="_searchButtonIcon">
+ document.getAnonymousElementByAttribute(this, "anonid", "searchbutton-icon");
+ </field>
+ <property name="timeout"
+ onset="this.setAttribute('timeout', val); return val;"
+ onget="return parseInt(this.getAttribute('timeout')) || 500;"/>
+ <property name="searchButton"
+ onget="return this.getAttribute('searchbutton') == 'true';">
+ <setter><![CDATA[
+ if (val) {
+ this.setAttribute("searchbutton", "true");
+ this.removeAttribute("aria-autocomplete");
+ // Hack for the button to get the right accessible:
+ this._searchButtonIcon.setAttribute("onclick", "true");
+ } else {
+ this.removeAttribute("searchbutton");
+ this._searchButtonIcon.removeAttribute("onclick");
+ this.setAttribute("aria-autocomplete", "list");
+ }
+ return val;
+ ]]></setter>
+ </property>
+ <property name="value"
+ onget="return this.inputField.value;">
+ <setter><![CDATA[
+ this.inputField.value = val;
+
+ if (val)
+ this._searchIcons.selectedIndex = this.searchButton ? 0 : 1;
+ else
+ this._searchIcons.selectedIndex = 0;
+
+ if (this._timer)
+ clearTimeout(this._timer);
+
+ return val;
+ ]]></setter>
+ </property>
+ <constructor><![CDATA[
+ // Ensure the button state is up to date:
+ this.searchButton = this.searchButton;
+ this._searchButtonIcon.addEventListener("click", (e) => this._iconClick(e), false);
+ ]]></constructor>
+ <method name="_fireCommand">
+ <parameter name="me"/>
+ <body><![CDATA[
+ if (me._timer)
+ clearTimeout(me._timer);
+ me._timer = null;
+ me.doCommand();
+ ]]></body>
+ </method>
+ <method name="_iconClick">
+ <body><![CDATA[
+ if (this.searchButton)
+ this._enterSearch();
+ else
+ this.focus();
+ ]]></body>
+ </method>
+ <method name="_enterSearch">
+ <body><![CDATA[
+ if (this.disabled)
+ return;
+ if (this.searchButton && this.value && !this.readOnly)
+ this._searchIcons.selectedIndex = 1;
+ this._fireCommand(this);
+ ]]></body>
+ </method>
+ <method name="_clearSearch">
+ <body><![CDATA[
+ if (!this.disabled && !this.readOnly && this.value) {
+ this.value = "";
+ this._fireCommand(this);
+ this._searchIcons.selectedIndex = 0;
+ return true;
+ }
+ return false;
+ ]]></body>
+ </method>
+ </implementation>
+ <handlers>
+ <handler event="input">
+ <![CDATA[
+ if (this.searchButton) {
+ this._searchIcons.selectedIndex = 0;
+ return;
+ }
+ if (this._timer)
+ clearTimeout(this._timer);
+ this._timer = this.timeout && setTimeout(this._fireCommand, this.timeout, this);
+ this._searchIcons.selectedIndex = this.value ? 1 : 0;
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_ESCAPE">
+ <![CDATA[
+ if (this._clearSearch()) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ ]]>
+ </handler>
+ <handler event="keypress" keycode="VK_RETURN">
+ <![CDATA[
+ this._enterSearch();
+ event.preventDefault();
+ event.stopPropagation();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="textarea" extends="chrome://global/content/bindings/textbox.xml#textbox">
+ <content>
+ <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,spellcheck">
+ <html:textarea class="textbox-textarea" anonid="input"
+ xbl:inherits="xbl:text=value,disabled,tabindex,rows,cols,readonly,wrap,placeholder,mozactionhint,spellcheck"><children/></html:textarea>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="input-box">
+ <content context="_child">
+ <children/>
+ <xul:menupopup anonid="input-box-contextmenu"
+ class="textbox-contextmenu"
+ onpopupshowing="var input =
+ this.parentNode.getElementsByAttribute('anonid', 'input')[0];
+ if (document.commandDispatcher.focusedElement != input)
+ input.focus();
+ this.parentNode._doPopupItemEnabling(this);"
+ oncommand="var cmd = event.originalTarget.getAttribute('cmd'); if(cmd) { this.parentNode.doCommand(cmd); event.stopPropagation(); }">
+ <xul:menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" cmd="cmd_undo"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
+ <xul:menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
+ <xul:menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
+ <xul:menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
+ </xul:menupopup>
+ </content>
+
+ <implementation>
+ <method name="_doPopupItemEnabling">
+ <parameter name="popupNode"/>
+ <body>
+ <![CDATA[
+ var children = popupNode.childNodes;
+ for (var i = 0; i < children.length; i++) {
+ var command = children[i].getAttribute("cmd");
+ if (command) {
+ var controller = document.commandDispatcher.getControllerForCommand(command);
+ var enabled = controller.isCommandEnabled(command);
+ if (enabled)
+ children[i].removeAttribute("disabled");
+ else
+ children[i].setAttribute("disabled", "true");
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_setMenuItemVisibility">
+ <parameter name="anonid"/>
+ <parameter name="visible"/>
+ <body><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", anonid).
+ hidden = ! visible;
+ ]]></body>
+ </method>
+
+ <method name="doCommand">
+ <parameter name="command"/>
+ <body>
+ <![CDATA[
+ var controller = document.commandDispatcher.getControllerForCommand(command);
+ controller.doCommand(command);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="input-box-spell" extends="chrome://global/content/bindings/textbox.xml#input-box">
+ <content context="_child">
+ <children/>
+ <xul:menupopup anonid="input-box-contextmenu"
+ class="textbox-contextmenu"
+ onpopupshowing="var input =
+ this.parentNode.getElementsByAttribute('anonid', 'input')[0];
+ if (document.commandDispatcher.focusedElement != input)
+ input.focus();
+ this.parentNode._doPopupItemEnablingSpell(this);"
+ onpopuphiding="this.parentNode._doPopupItemDisabling(this);"
+ oncommand="var cmd = event.originalTarget.getAttribute('cmd'); if(cmd) { this.parentNode.doCommand(cmd); event.stopPropagation(); }">
+ <xul:menuitem label="&spellNoSuggestions.label;" anonid="spell-no-suggestions" disabled="true"/>
+ <xul:menuitem label="&spellAddToDictionary.label;" accesskey="&spellAddToDictionary.accesskey;" anonid="spell-add-to-dictionary"
+ oncommand="this.parentNode.parentNode.spellCheckerUI.addToDictionary();"/>
+ <xul:menuitem label="&spellUndoAddToDictionary.label;" accesskey="&spellUndoAddToDictionary.accesskey;" anonid="spell-undo-add-to-dictionary"
+ oncommand="this.parentNode.parentNode.spellCheckerUI.undoAddToDictionary();"/>
+ <xul:menuseparator anonid="spell-suggestions-separator"/>
+ <xul:menuitem label="&undoCmd.label;" accesskey="&undoCmd.accesskey;" cmd="cmd_undo"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&cutCmd.label;" accesskey="&cutCmd.accesskey;" cmd="cmd_cut"/>
+ <xul:menuitem label="&copyCmd.label;" accesskey="&copyCmd.accesskey;" cmd="cmd_copy"/>
+ <xul:menuitem label="&pasteCmd.label;" accesskey="&pasteCmd.accesskey;" cmd="cmd_paste"/>
+ <xul:menuitem label="&deleteCmd.label;" accesskey="&deleteCmd.accesskey;" cmd="cmd_delete"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&selectAllCmd.label;" accesskey="&selectAllCmd.accesskey;" cmd="cmd_selectAll"/>
+ <xul:menuseparator anonid="spell-check-separator"/>
+ <xul:menuitem label="&spellCheckToggle.label;" type="checkbox" accesskey="&spellCheckToggle.accesskey;" anonid="spell-check-enabled"
+ oncommand="this.parentNode.parentNode.spellCheckerUI.toggleEnabled();"/>
+ <xul:menu label="&spellDictionaries.label;" accesskey="&spellDictionaries.accesskey;" anonid="spell-dictionaries">
+ <xul:menupopup anonid="spell-dictionaries-menu"
+ onpopupshowing="event.stopPropagation();"
+ onpopuphiding="event.stopPropagation();"/>
+ </xul:menu>
+ </xul:menupopup>
+ </content>
+
+ <implementation>
+ <field name="_spellCheckInitialized">false</field>
+ <field name="_enabledCheckbox">
+ document.getAnonymousElementByAttribute(this, "anonid", "spell-check-enabled");
+ </field>
+ <field name="_suggestionsSeparator">
+ document.getAnonymousElementByAttribute(this, "anonid", "spell-no-suggestions");
+ </field>
+ <field name="_dictionariesMenu">
+ document.getAnonymousElementByAttribute(this, "anonid", "spell-dictionaries-menu");
+ </field>
+
+ <property name="spellCheckerUI" readonly="true">
+ <getter><![CDATA[
+ if (!this._spellCheckInitialized) {
+ this._spellCheckInitialized = true;
+
+ const CI = Components.interfaces;
+ if (!(document instanceof CI.nsIDOMXULDocument))
+ return null;
+
+ var textbox = document.getBindingParent(this);
+ if (!textbox || !(textbox instanceof CI.nsIDOMXULTextBoxElement))
+ return null;
+
+ try {
+ Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm", this);
+ this.InlineSpellCheckerUI = new this.InlineSpellChecker(textbox.editor);
+ } catch (ex) { }
+ }
+
+ return this.InlineSpellCheckerUI;
+ ]]></getter>
+ </property>
+
+ <method name="_doPopupItemEnablingSpell">
+ <parameter name="popupNode"/>
+ <body>
+ <![CDATA[
+ var spellui = this.spellCheckerUI;
+ if (!spellui || !spellui.canSpellCheck) {
+ this._setMenuItemVisibility("spell-no-suggestions", false);
+ this._setMenuItemVisibility("spell-check-enabled", false);
+ this._setMenuItemVisibility("spell-check-separator", false);
+ this._setMenuItemVisibility("spell-add-to-dictionary", false);
+ this._setMenuItemVisibility("spell-undo-add-to-dictionary", false);
+ this._setMenuItemVisibility("spell-suggestions-separator", false);
+ this._setMenuItemVisibility("spell-dictionaries", false);
+ return;
+ }
+
+ spellui.initFromEvent(document.popupRangeParent,
+ document.popupRangeOffset);
+
+ var enabled = spellui.enabled;
+ var showUndo = spellui.canSpellCheck && spellui.canUndo();
+ this._enabledCheckbox.setAttribute("checked", enabled);
+
+ var overMisspelling = spellui.overMisspelling;
+ this._setMenuItemVisibility("spell-add-to-dictionary", overMisspelling);
+ this._setMenuItemVisibility("spell-undo-add-to-dictionary", showUndo);
+ this._setMenuItemVisibility("spell-suggestions-separator", overMisspelling || showUndo);
+
+ // suggestion list
+ var numsug = spellui.addSuggestionsToMenu(popupNode, this._suggestionsSeparator, 5);
+ this._setMenuItemVisibility("spell-no-suggestions", overMisspelling && numsug == 0);
+
+ // dictionary list
+ var numdicts = spellui.addDictionaryListToMenu(this._dictionariesMenu, null);
+ this._setMenuItemVisibility("spell-dictionaries", enabled && numdicts > 1);
+
+ this._doPopupItemEnabling(popupNode);
+ ]]>
+ </body>
+ </method>
+ <method name="_doPopupItemDisabling">
+ <body><![CDATA[
+ if (this.spellCheckerUI) {
+ this.spellCheckerUI.clearSuggestionsFromMenu();
+ this.spellCheckerUI.clearDictionaryListFromMenu();
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/timekeeper.js b/components/bindings/content/timekeeper.js
new file mode 100644
index 000000000..3b4e7eb0a
--- /dev/null
+++ b/components/bindings/content/timekeeper.js
@@ -0,0 +1,418 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+/**
+ * TimeKeeper keeps track of the time states. Given min, max, step, and
+ * format (12/24hr), TimeKeeper will determine the ranges of possible
+ * selections, and whether or not the current time state is out of range
+ * or off step.
+ *
+ * @param {Object} props
+ * {
+ * {Date} min
+ * {Date} max
+ * {Number} step
+ * {String} format: Either "12" or "24"
+ * }
+ */
+function TimeKeeper(props) {
+ this.props = props;
+ this.state = { time: new Date(0), ranges: {} };
+}
+
+{
+ const debug = 0 ? console.log.bind(console, '[timekeeper]') : function() {};
+
+ const DAY_PERIOD_IN_HOURS = 12,
+ SECOND_IN_MS = 1000,
+ MINUTE_IN_MS = 60000,
+ HOUR_IN_MS = 3600000,
+ DAY_PERIOD_IN_MS = 43200000,
+ DAY_IN_MS = 86400000,
+ TIME_FORMAT_24 = "24";
+
+ TimeKeeper.prototype = {
+ /**
+ * Getters for different time units.
+ * @return {Number}
+ */
+ get hour() {
+ return this.state.time.getUTCHours();
+ },
+ get minute() {
+ return this.state.time.getUTCMinutes();
+ },
+ get second() {
+ return this.state.time.getUTCSeconds();
+ },
+ get millisecond() {
+ return this.state.time.getUTCMilliseconds();
+ },
+ get dayPeriod() {
+ // 0 stands for AM and 12 for PM
+ return this.state.time.getUTCHours() < DAY_PERIOD_IN_HOURS ? 0 : DAY_PERIOD_IN_HOURS;
+ },
+
+ /**
+ * Get the ranges of different time units.
+ * @return {Object}
+ * {
+ * {Array<Number>} dayPeriod
+ * {Array<Number>} hours
+ * {Array<Number>} minutes
+ * {Array<Number>} seconds
+ * {Array<Number>} milliseconds
+ * }
+ */
+ get ranges() {
+ return this.state.ranges;
+ },
+
+ /**
+ * Set new time, check if the current state is valid, and set ranges.
+ *
+ * @param {Object} timeState: The new time
+ * {
+ * {Number} hour [optional]
+ * {Number} minute [optional]
+ * {Number} second [optional]
+ * {Number} millisecond [optional]
+ * }
+ */
+ setState(timeState) {
+ const { min, max } = this.props;
+ const { hour, minute, second, millisecond } = timeState;
+
+ if (hour != undefined) {
+ this.state.time.setUTCHours(hour);
+ }
+ if (minute != undefined) {
+ this.state.time.setUTCMinutes(minute);
+ }
+ if (second != undefined) {
+ this.state.time.setUTCSeconds(second);
+ }
+ if (millisecond != undefined) {
+ this.state.time.setUTCMilliseconds(millisecond);
+ }
+
+ this.state.isOffStep = this._isOffStep(this.state.time);
+ this.state.isOutOfRange = (this.state.time < min || this.state.time > max);
+ this.state.isInvalid = this.state.isOutOfRange || this.state.isOffStep;
+
+ this._setRanges(this.dayPeriod, this.hour, this.minute, this.second);
+ },
+
+ /**
+ * Set day-period (AM/PM)
+ * @param {Number} dayPeriod: 0 as AM, 12 as PM
+ */
+ setDayPeriod(dayPeriod) {
+ if (dayPeriod == this.dayPeriod) {
+ return;
+ }
+
+ if (dayPeriod == 0) {
+ this.setState({ hour: this.hour - DAY_PERIOD_IN_HOURS });
+ } else {
+ this.setState({ hour: this.hour + DAY_PERIOD_IN_HOURS });
+ }
+ },
+
+ /**
+ * Set hour in 24hr format (0 ~ 23)
+ * @param {Number} hour
+ */
+ setHour(hour) {
+ this.setState({ hour });
+ },
+
+ /**
+ * Set minute (0 ~ 59)
+ * @param {Number} minute
+ */
+ setMinute(minute) {
+ this.setState({ minute });
+ },
+
+ /**
+ * Set second (0 ~ 59)
+ * @param {Number} second
+ */
+ setSecond(second) {
+ this.setState({ second });
+ },
+
+ /**
+ * Set millisecond (0 ~ 999)
+ * @param {Number} millisecond
+ */
+ setMillisecond(millisecond) {
+ this.setState({ millisecond });
+ },
+
+ /**
+ * Calculate the range of possible choices for each time unit.
+ * Reuse the old result if the input has not changed.
+ *
+ * @param {Number} dayPeriod
+ * @param {Number} hour
+ * @param {Number} minute
+ * @param {Number} second
+ */
+ _setRanges(dayPeriod, hour, minute, second) {
+ this.state.ranges.dayPeriod =
+ this.state.ranges.dayPeriod || this._getDayPeriodRange();
+
+ if (this.state.dayPeriod != dayPeriod) {
+ this.state.ranges.hours = this._getHoursRange(dayPeriod);
+ }
+
+ if (this.state.hour != hour) {
+ this.state.ranges.minutes = this._getMinutesRange(hour);
+ }
+
+ if (this.state.hour != hour || this.state.minute != minute) {
+ this.state.ranges.seconds = this._getSecondsRange(hour, minute);
+ }
+
+ if (this.state.hour != hour || this.state.minute != minute || this.state.second != second) {
+ this.state.ranges.milliseconds = this._getMillisecondsRange(hour, minute, second);
+ }
+
+ // Save the time states for comparison.
+ this.state.dayPeriod = dayPeriod;
+ this.state.hour = hour;
+ this.state.minute = minute;
+ this.state.second = second;
+ },
+
+ /**
+ * Get the AM/PM range. Return an empty array if in 24hr mode.
+ *
+ * @return {Array<Number>}
+ */
+ _getDayPeriodRange() {
+ if (this.props.format == TIME_FORMAT_24) {
+ return [];
+ }
+
+ const start = 0;
+ const end = DAY_IN_MS - 1;
+ const minStep = DAY_PERIOD_IN_MS;
+ const formatter = (time) =>
+ new Date(time).getUTCHours() < DAY_PERIOD_IN_HOURS ? 0 : DAY_PERIOD_IN_HOURS;
+
+ return this._getSteps(start, end, minStep, formatter);
+ },
+
+ /**
+ * Get the hours range.
+ *
+ * @param {Number} dayPeriod
+ * @return {Array<Number>}
+ */
+ _getHoursRange(dayPeriod) {
+ const { format } = this.props;
+ const start = format == "24" ? 0 : dayPeriod * HOUR_IN_MS;
+ const end = format == "24" ? DAY_IN_MS - 1 : start + DAY_PERIOD_IN_MS - 1;
+ const minStep = HOUR_IN_MS;
+ const formatter = (time) => new Date(time).getUTCHours();
+
+ return this._getSteps(start, end, minStep, formatter);
+ },
+
+ /**
+ * Get the minutes range
+ *
+ * @param {Number} hour
+ * @return {Array<Number>}
+ */
+ _getMinutesRange(hour) {
+ const start = hour * HOUR_IN_MS;
+ const end = start + HOUR_IN_MS - 1;
+ const minStep = MINUTE_IN_MS;
+ const formatter = (time) => new Date(time).getUTCMinutes();
+
+ return this._getSteps(start, end, minStep, formatter);
+ },
+
+ /**
+ * Get the seconds range
+ *
+ * @param {Number} hour
+ * @param {Number} minute
+ * @return {Array<Number>}
+ */
+ _getSecondsRange(hour, minute) {
+ const start = hour * HOUR_IN_MS + minute * MINUTE_IN_MS;
+ const end = start + MINUTE_IN_MS - 1;
+ const minStep = SECOND_IN_MS;
+ const formatter = (time) => new Date(time).getUTCSeconds();
+
+ return this._getSteps(start, end, minStep, formatter);
+ },
+
+ /**
+ * Get the milliseconds range
+ * @param {Number} hour
+ * @param {Number} minute
+ * @param {Number} second
+ * @return {Array<Number>}
+ */
+ _getMillisecondsRange(hour, minute, second) {
+ const start = hour * HOUR_IN_MS + minute * MINUTE_IN_MS + second * SECOND_IN_MS;
+ const end = start + SECOND_IN_MS - 1;
+ const minStep = 1;
+ const formatter = (time) => new Date(time).getUTCMilliseconds();
+
+ return this._getSteps(start, end, minStep, formatter);
+ },
+
+ /**
+ * Calculate the range of possible steps.
+ *
+ * @param {Number} startValue: Start time in ms
+ * @param {Number} endValue: End time in ms
+ * @param {Number} minStep: Smallest step in ms for the time unit
+ * @param {Function} formatter: Outputs time in a particular format
+ * @return {Array<Object>}
+ * {
+ * {Number} value
+ * {Boolean} enabled
+ * }
+ */
+ _getSteps(startValue, endValue, minStep, formatter) {
+ const { min, max, step } = this.props;
+ // The timeStep should be big enough so that there won't be
+ // duplications. Ex: minimum step for minute should be 60000ms,
+ // if smaller than that, next step might return the same minute.
+ const timeStep = Math.max(minStep, step);
+
+ // Make sure the starting point and end point is not off step
+ let time = min.valueOf() + Math.ceil((startValue - min.valueOf()) / timeStep) * timeStep;
+ let maxValue = min.valueOf() + Math.floor((max.valueOf() - min.valueOf()) / step) * step;
+ let steps = [];
+
+ // Increment by timeStep until reaching the end of the range.
+ while (time <= endValue) {
+ steps.push({
+ value: formatter(time),
+ // Check if the value is within the min and max. If it's out of range,
+ // also check for the case when minStep is too large, and has stepped out
+ // of range when it should be enabled.
+ enabled: (time >= min.valueOf() && time <= max.valueOf()) ||
+ (time > maxValue && startValue <= maxValue &&
+ endValue >= maxValue && formatter(time) == formatter(maxValue))
+ });
+ time += timeStep;
+ }
+
+ return steps;
+ },
+
+ /**
+ * A generic function for stepping up or down from a value of a range.
+ * It stops at the upper and lower limits.
+ *
+ * @param {Number} current: The current value
+ * @param {Number} offset: The offset relative to current value
+ * @param {Array<Object>} range: List of possible steps
+ * @return {Number} The new value
+ */
+ _step(current, offset, range) {
+ const index = range.findIndex(step => step.value == current);
+ const newIndex = offset > 0 ?
+ Math.min(index + offset, range.length - 1) :
+ Math.max(index + offset, 0);
+ return range[newIndex].value;
+ },
+
+ /**
+ * Step up or down AM/PM
+ *
+ * @param {Number} offset
+ */
+ stepDayPeriodBy(offset) {
+ const current = this.dayPeriod;
+ const dayPeriod = this._step(current, offset, this.state.ranges.dayPeriod);
+
+ if (current != dayPeriod) {
+ this.hour < DAY_PERIOD_IN_HOURS ?
+ this.setState({ hour: this.hour + DAY_PERIOD_IN_HOURS }) :
+ this.setState({ hour: this.hour - DAY_PERIOD_IN_HOURS });
+ }
+ },
+
+ /**
+ * Step up or down hours
+ *
+ * @param {Number} offset
+ */
+ stepHourBy(offset) {
+ const current = this.hour;
+ const hour = this._step(current, offset, this.state.ranges.hours);
+
+ if (current != hour) {
+ this.setState({ hour });
+ }
+ },
+
+ /**
+ * Step up or down minutes
+ *
+ * @param {Number} offset
+ */
+ stepMinuteBy(offset) {
+ const current = this.minute;
+ const minute = this._step(current, offset, this.state.ranges.minutes);
+
+ if (current != minute) {
+ this.setState({ minute });
+ }
+ },
+
+ /**
+ * Step up or down seconds
+ *
+ * @param {Number} offset
+ */
+ stepSecondBy(offset) {
+ const current = this.second;
+ const second = this._step(current, offset, this.state.ranges.seconds);
+
+ if (current != second) {
+ this.setState({ second });
+ }
+ },
+
+ /**
+ * Step up or down milliseconds
+ *
+ * @param {Number} offset
+ */
+ stepMillisecondBy(offset) {
+ const current = this.milliseconds;
+ const millisecond = this._step(current, offset, this.state.ranges.millisecond);
+
+ if (current != millisecond) {
+ this.setState({ millisecond });
+ }
+ },
+
+ /**
+ * Checks if the time state is off step.
+ *
+ * @param {Date} time
+ * @return {Boolean}
+ */
+ _isOffStep(time) {
+ const { min, step } = this.props;
+
+ return (time.valueOf() - min.valueOf()) % step != 0;
+ }
+ };
+}
diff --git a/components/bindings/content/timepicker.js b/components/bindings/content/timepicker.js
new file mode 100644
index 000000000..1f0463fe4
--- /dev/null
+++ b/components/bindings/content/timepicker.js
@@ -0,0 +1,270 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function TimePicker(context) {
+ this.context = context;
+ this._attachEventListeners();
+}
+
+{
+ const debug = 0 ? console.log.bind(console, "[timepicker]") : function() {};
+
+ const DAY_PERIOD_IN_HOURS = 12,
+ DAY_IN_MS = 86400000;
+
+ TimePicker.prototype = {
+ /**
+ * Initializes the time picker. Set the default states and properties.
+ * @param {Object} props
+ * {
+ * {Number} hour [optional]: Hour in 24 hours format (0~23), default is current hour
+ * {Number} minute [optional]: Minute (0~59), default is current minute
+ * {Number} min: Minimum time, in ms
+ * {Number} max: Maximum time, in ms
+ * {Number} step: Step size in ms
+ * {String} format [optional]: "12" for 12 hours, "24" for 24 hours format
+ * {String} locale [optional]: User preferred locale
+ * }
+ */
+ init(props) {
+ this.props = props || {};
+ this._setDefaultState();
+ this._createComponents();
+ this._setComponentStates();
+ },
+
+ /*
+ * Set initial time states. If there's no hour & minute, it will
+ * use the current time. The Time module keeps track of the time states,
+ * and calculates the valid options given the time, min, max, step,
+ * and format (12 or 24).
+ */
+ _setDefaultState() {
+ const { hour, minute, min, max, step, format } = this.props;
+ const now = new Date();
+
+ let timerHour = hour == undefined ? now.getHours() : hour;
+ let timerMinute = minute == undefined ? now.getMinutes() : minute;
+
+ let timeKeeper = new TimeKeeper({
+ min: new Date(Number.isNaN(min) ? 0 : min),
+ max: new Date(Number.isNaN(max) ? DAY_IN_MS - 1 : max),
+ step,
+ format: format || "12"
+ });
+ timeKeeper.setState({ hour: timerHour, minute: timerMinute });
+
+ this.state = { timeKeeper };
+ },
+
+ /**
+ * Initalize the spinner components.
+ */
+ _createComponents() {
+ const { locale, step, format } = this.props;
+ const { timeKeeper } = this.state;
+
+ const wrapSetValueFn = (setTimeFunction) => {
+ return (value) => {
+ setTimeFunction(value);
+ this._setComponentStates();
+ this._dispatchState();
+ };
+ };
+ const numberFormat = new Intl.NumberFormat(locale).format;
+
+ this.components = {
+ hour: new Spinner({
+ setValue: wrapSetValueFn(value => {
+ timeKeeper.setHour(value);
+ this.state.isHourSet = true;
+ }),
+ getDisplayString: hour => {
+ if (format == "24") {
+ return numberFormat(hour);
+ }
+ // Hour 0 in 12 hour format is displayed as 12.
+ const hourIn12 = hour % DAY_PERIOD_IN_HOURS;
+ return hourIn12 == 0 ? numberFormat(12)
+ : numberFormat(hourIn12);
+ }
+ }, this.context),
+ minute: new Spinner({
+ setValue: wrapSetValueFn(value => {
+ timeKeeper.setMinute(value);
+ this.state.isMinuteSet = true;
+ }),
+ getDisplayString: minute => numberFormat(minute)
+ }, this.context)
+ };
+
+ this._insertLayoutElement({
+ tag: "div",
+ textContent: ":",
+ className: "colon",
+ insertBefore: this.components.minute.elements.container
+ });
+
+ // The AM/PM spinner is only available in 12hr mode
+ // TODO: Replace AM & PM string with localized string
+ if (format == "12") {
+ this.components.dayPeriod = new Spinner({
+ setValue: wrapSetValueFn(value => {
+ timeKeeper.setDayPeriod(value);
+ this.state.isDayPeriodSet = true;
+ }),
+ getDisplayString: dayPeriod => dayPeriod == 0 ? "AM" : "PM",
+ hideButtons: true
+ }, this.context);
+
+ this._insertLayoutElement({
+ tag: "div",
+ className: "spacer",
+ insertBefore: this.components.dayPeriod.elements.container
+ });
+ }
+ },
+
+ /**
+ * Insert element for layout purposes.
+ *
+ * @param {Object}
+ * {
+ * {String} tag: The tag to create
+ * {DOMElement} insertBefore: The DOM node to insert before
+ * {String} className [optional]: Class name
+ * {String} textContent [optional]: Text content
+ * }
+ */
+ _insertLayoutElement({ tag, insertBefore, className, textContent }) {
+ let el = document.createElement(tag);
+ el.textContent = textContent;
+ el.className = className;
+ this.context.insertBefore(el, insertBefore);
+ },
+
+ /**
+ * Set component states.
+ */
+ _setComponentStates() {
+ const { timeKeeper, isHourSet, isMinuteSet, isDayPeriodSet } = this.state;
+ const isInvalid = timeKeeper.state.isInvalid;
+ // Value is set to min if it's first opened and time state is invalid
+ const setToMinValue = !isHourSet && !isMinuteSet && !isDayPeriodSet && isInvalid;
+
+ this.components.hour.setState({
+ value: setToMinValue ? timeKeeper.ranges.hours[0].value : timeKeeper.hour,
+ items: timeKeeper.ranges.hours,
+ isInfiniteScroll: true,
+ isValueSet: isHourSet,
+ isInvalid
+ });
+
+ this.components.minute.setState({
+ value: setToMinValue ? timeKeeper.ranges.minutes[0].value : timeKeeper.minute,
+ items: timeKeeper.ranges.minutes,
+ isInfiniteScroll: true,
+ isValueSet: isMinuteSet,
+ isInvalid
+ });
+
+ // The AM/PM spinner is only available in 12hr mode
+ if (this.props.format == "12") {
+ this.components.dayPeriod.setState({
+ value: setToMinValue ? timeKeeper.ranges.dayPeriod[0].value : timeKeeper.dayPeriod,
+ items: timeKeeper.ranges.dayPeriod,
+ isInfiniteScroll: false,
+ isValueSet: isDayPeriodSet,
+ isInvalid
+ });
+ }
+ },
+
+ /**
+ * Dispatch CustomEvent to pass the state of picker to the panel.
+ */
+ _dispatchState() {
+ const { hour, minute } = this.state.timeKeeper;
+ const { isHourSet, isMinuteSet, isDayPeriodSet } = this.state;
+ // The panel is listening to window for postMessage event, so we
+ // do postMessage to itself to send data to input boxes.
+ window.postMessage({
+ name: "PickerPopupChanged",
+ detail: {
+ hour,
+ minute,
+ isHourSet,
+ isMinuteSet,
+ isDayPeriodSet
+ }
+ }, "*");
+ },
+ _attachEventListeners() {
+ window.addEventListener("message", this);
+ document.addEventListener("mousedown", this);
+ },
+
+ /**
+ * Handle events.
+ *
+ * @param {Event} event
+ */
+ handleEvent(event) {
+ switch (event.type) {
+ case "message": {
+ this.handleMessage(event);
+ break;
+ }
+ case "mousedown": {
+ // Use preventDefault to keep focus on input boxes
+ event.preventDefault();
+ event.target.setCapture();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Handle postMessage events.
+ *
+ * @param {Event} event
+ */
+ handleMessage(event) {
+ switch (event.data.name) {
+ case "PickerSetValue": {
+ this.set(event.data.detail);
+ break;
+ }
+ case "PickerInit": {
+ this.init(event.data.detail);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Set the time state and update the components with the new state.
+ *
+ * @param {Object} timeState
+ * {
+ * {Number} hour [optional]
+ * {Number} minute [optional]
+ * {Number} second [optional]
+ * {Number} millisecond [optional]
+ * }
+ */
+ set(timeState) {
+ if (timeState.hour != undefined) {
+ this.state.isHourSet = true;
+ }
+ if (timeState.minute != undefined) {
+ this.state.isMinuteSet = true;
+ }
+ this.state.timeKeeper.setState(timeState);
+ this._setComponentStates();
+ }
+ };
+}
diff --git a/components/bindings/content/toolbar.xml b/components/bindings/content/toolbar.xml
new file mode 100644
index 000000000..55cef8244
--- /dev/null
+++ b/components/bindings/content/toolbar.xml
@@ -0,0 +1,631 @@
+<?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="toolbarBindings"
+ 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="toolbar-base">
+ <resources>
+ <stylesheet src="chrome://global/skin/toolbar.css"/>
+ </resources>
+ </binding>
+
+ <binding id="toolbox" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
+ <implementation>
+ <field name="palette">
+ null
+ </field>
+
+ <field name="toolbarset">
+ null
+ </field>
+
+ <field name="customToolbarCount">
+ 0
+ </field>
+
+ <field name="externalToolbars">
+ []
+ </field>
+
+ <!-- Set by customizeToolbar.js -->
+ <property name="customizing">
+ <getter><![CDATA[
+ return this.getAttribute("customizing") == "true";
+ ]]></getter>
+ <setter><![CDATA[
+ if (val)
+ this.setAttribute("customizing", "true");
+ else
+ this.removeAttribute("customizing");
+ return val;
+ ]]></setter>
+ </property>
+
+ <constructor>
+ <![CDATA[
+ this.toolbarInfoSeparators = ["|", "-"];
+ this.toolbarInfoLegacySeparator = ":";
+ // Look to see if there is a toolbarset.
+ this.toolbarset = this.firstChild;
+ while (this.toolbarset && this.toolbarset.localName != "toolbarset") {
+ this.toolbarset = this.toolbarset.nextSibling;
+ }
+
+ if (this.toolbarset) {
+ // Create each toolbar described by the toolbarset.
+ var index = 0;
+ while (this.toolbarset.hasAttribute("toolbar" + (++index))) {
+ let hiddingAttribute =
+ this.toolbarset.getAttribute("type") == "menubar"
+ ? "autohide" : "collapsed";
+ let toolbarInfo = this.toolbarset.getAttribute("toolbar" + index);
+ let infoSplit = toolbarInfo.split(this.toolbarInfoSeparators[0]);
+ if (infoSplit.length == 1) {
+ infoSplit = toolbarInfo.split(this.toolbarInfoLegacySeparator);
+ }
+ let infoName = infoSplit[0];
+ let infoHidingAttribute = [null, null];
+ let infoCurrentSet = "";
+ let infoSplitLen = infoSplit.length;
+ switch (infoSplitLen) {
+ case 3:
+ // Pale Moon 27.2+
+ // Basilisk (UXP)
+ infoHidingAttribute = infoSplit[1]
+ .split(this.toolbarInfoSeparators[1]);
+ infoCurrentSet = infoSplit[2];
+ break;
+ case 2:
+ // Legacy:
+ // - toolbars from Pale Moon 27.0 - 27.1.x
+ // - Basilisk (moebius)
+ // The previous value (hiddingAttribute) isn't stored.
+ infoHidingAttribute = [hiddingAttribute, "false"];
+ infoCurrentSet = infoSplit[1];
+ break;
+ default:
+ Components.utils.reportError(
+ "Customizable toolbars - an invalid value:" + "\n"
+ + '"toolbar' + index + '" = "' + toolbarInfo + '"');
+ break;
+ }
+ this.appendCustomToolbar(
+ infoName, infoCurrentSet, infoHidingAttribute);
+ }
+ }
+ ]]>
+ </constructor>
+
+ <method name="appendCustomToolbar">
+ <parameter name="aName"/>
+ <parameter name="aCurrentSet"/>
+ <parameter name="aHidingAttribute"/>
+ <body>
+ <![CDATA[
+ if (!this.toolbarset)
+ return null;
+ var toolbar = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "toolbar");
+ toolbar.id = "__customToolbar_" + aName.replace(" ", "_");
+ toolbar.setAttribute("customizable", "true");
+ toolbar.setAttribute("customindex", ++this.customToolbarCount);
+ toolbar.setAttribute("toolbarname", aName);
+ toolbar.setAttribute("currentset", aCurrentSet);
+ toolbar.setAttribute("mode", this.getAttribute("mode"));
+ toolbar.setAttribute("iconsize", this.getAttribute("iconsize"));
+ toolbar.setAttribute("context", this.toolbarset.getAttribute("context"));
+ toolbar.setAttribute("class", "chromeclass-toolbar");
+ // Restore persist the hiding attribute.
+ if (aHidingAttribute[0]) {
+ toolbar.setAttribute(aHidingAttribute[0], aHidingAttribute[1]);
+ }
+
+ this.insertBefore(toolbar, this.toolbarset);
+ return toolbar;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="toolbar" role="xul:toolbar"
+ extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
+ <implementation>
+ <property name="toolbarName"
+ onget="return this.getAttribute('toolbarname');"
+ onset="this.setAttribute('toolbarname', val); return val;"/>
+
+ <field name="_toolbox">null</field>
+ <property name="toolbox" readonly="true">
+ <getter><![CDATA[
+ if (this._toolbox)
+ return this._toolbox;
+
+ let toolboxId = this.getAttribute("toolboxid");
+ if (toolboxId) {
+ let toolbox = document.getElementById(toolboxId);
+ if (!toolbox) {
+ let tbName = this.toolbarName;
+ if (tbName)
+ tbName = " (" + tbName + ")";
+ else
+ tbName = "";
+ throw new Error(`toolbar ID ${this.id}${tbName}: toolboxid attribute '${toolboxId}' points to a toolbox that doesn't exist`);
+ }
+
+ if (toolbox.externalToolbars.indexOf(this) == -1)
+ toolbox.externalToolbars.push(this);
+
+ return this._toolbox = toolbox;
+ }
+
+ return this._toolbox = (this.parentNode &&
+ this.parentNode.localName == "toolbox") ?
+ this.parentNode : null;
+ ]]></getter>
+ </property>
+
+ <constructor>
+ <![CDATA[
+ if (document.readyState == "complete") {
+ this._init();
+ } else {
+ // Need to wait until XUL overlays are loaded. See bug 554279.
+ let self = this;
+ document.addEventListener("readystatechange", function (event) {
+ if (document.readyState != "complete")
+ return;
+ document.removeEventListener("readystatechange", arguments.callee, false);
+ self._init();
+ }, false);
+ }
+ ]]>
+ </constructor>
+
+ <method name="_init">
+ <body>
+ <![CDATA[
+ // Searching for the toolbox palette in the toolbar binding because
+ // toolbars are constructed first.
+ var toolbox = this.toolbox;
+ if (!toolbox)
+ return;
+
+ if (!toolbox.palette) {
+ // Look to see if there is a toolbarpalette.
+ var node = toolbox.firstChild;
+ while (node) {
+ if (node.localName == "toolbarpalette")
+ break;
+ node = node.nextSibling;
+ }
+
+ if (!node)
+ return;
+
+ // Hold on to the palette but remove it from the document.
+ toolbox.palette = node;
+ toolbox.removeChild(node);
+ }
+
+ // Build up our contents from the palette.
+ var currentSet = this.getAttribute("currentset");
+ if (!currentSet)
+ currentSet = this.getAttribute("defaultset");
+ if (currentSet)
+ this.currentSet = currentSet;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_idFromNode">
+ <parameter name="aNode"/>
+ <body>
+ <![CDATA[
+ if (aNode.getAttribute("skipintoolbarset") == "true")
+ return "";
+
+ switch (aNode.localName) {
+ case "toolbarseparator":
+ return "separator";
+ case "toolbarspring":
+ return "spring";
+ case "toolbarspacer":
+ return "spacer";
+ default:
+ return aNode.id;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="currentSet">
+ <getter>
+ <![CDATA[
+ var node = this.firstChild;
+ var currentSet = [];
+ while (node) {
+ var id = this._idFromNode(node);
+ if (id) {
+ currentSet.push(id);
+ }
+ node = node.nextSibling;
+ }
+
+ return currentSet.join(",") || "__empty";
+ ]]>
+ </getter>
+
+ <setter>
+ <![CDATA[
+ if (val == this.currentSet)
+ return val;
+
+ var ids = (val == "__empty") ? [] : val.split(",");
+
+ var nodeidx = 0;
+ var paletteItems = { }, added = { };
+
+ var palette = this.toolbox ? this.toolbox.palette : null;
+
+ // build a cache of items in the toolbarpalette
+ var paletteChildren = palette ? palette.childNodes : [];
+ for (let c = 0; c < paletteChildren.length; c++) {
+ let curNode = paletteChildren[c];
+ paletteItems[curNode.id] = curNode;
+ }
+
+ var children = this.childNodes;
+
+ // iterate over the ids to use on the toolbar
+ for (let i = 0; i < ids.length; i++) {
+ let id = ids[i];
+ // iterate over the existing nodes on the toolbar. nodeidx is the
+ // spot where we want to insert items.
+ let found = false;
+ for (let c = nodeidx; c < children.length; c++) {
+ let curNode = children[c];
+ if (this._idFromNode(curNode) == id) {
+ // the node already exists. If c equals nodeidx, we haven't
+ // iterated yet, so the item is already in the right position.
+ // Otherwise, insert it here.
+ if (c != nodeidx) {
+ this.insertBefore(curNode, children[nodeidx]);
+ }
+
+ added[curNode.id] = true;
+ nodeidx++;
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ // move on to the next id
+ continue;
+ }
+
+ // the node isn't already on the toolbar, so add a new one.
+ var nodeToAdd = paletteItems[id] || this._getToolbarItem(id);
+ if (nodeToAdd && !(nodeToAdd.id in added)) {
+ added[nodeToAdd.id] = true;
+ this.insertBefore(nodeToAdd, children[nodeidx] || null);
+ nodeToAdd.setAttribute("removable", "true");
+ nodeidx++;
+ }
+ }
+
+ // remove any leftover removable nodes
+ for (let i = children.length - 1; i >= nodeidx; i--) {
+ let curNode = children[i];
+
+ let curNodeId = this._idFromNode(curNode);
+ // skip over fixed items
+ if (curNodeId && curNode.getAttribute("removable") == "true") {
+ if (palette)
+ palette.appendChild(curNode);
+ else
+ this.removeChild(curNode);
+ }
+ }
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <field name="_newElementCount">0</field>
+ <method name="_getToolbarItem">
+ <parameter name="aId"/>
+ <body>
+ <![CDATA[
+ const XUL_NS = "http://www.mozilla.org/keymaster/" +
+ "gatekeeper/there.is.only.xul";
+
+ var newItem = null;
+ switch (aId) {
+ // Handle special cases
+ case "separator":
+ case "spring":
+ case "spacer":
+ newItem = document.createElementNS(XUL_NS, "toolbar" + aId);
+ // Due to timers resolution Date.now() can be the same for
+ // elements created in small timeframes. So ids are
+ // differentiated through a unique count suffix.
+ newItem.id = aId + Date.now() + (++this._newElementCount);
+ if (aId == "spring")
+ newItem.flex = 1;
+ break;
+ default:
+ var toolbox = this.toolbox;
+ if (!toolbox)
+ break;
+
+ // look for an item with the same id, as the item may be
+ // in a different toolbar.
+ var item = document.getElementById(aId);
+ if (item && item.parentNode &&
+ item.parentNode.localName == "toolbar" &&
+ item.parentNode.toolbox == toolbox) {
+ newItem = item;
+ break;
+ }
+
+ if (toolbox.palette) {
+ // Attempt to locate an item with a matching ID within
+ // the palette.
+ let paletteItem = this.toolbox.palette.firstChild;
+ while (paletteItem) {
+ if (paletteItem.id == aId) {
+ newItem = paletteItem;
+ break;
+ }
+ paletteItem = paletteItem.nextSibling;
+ }
+ }
+ break;
+ }
+
+ return newItem;
+ ]]>
+ </body>
+ </method>
+
+ <method name="insertItem">
+ <parameter name="aId"/>
+ <parameter name="aBeforeElt"/>
+ <parameter name="aWrapper"/>
+ <body>
+ <![CDATA[
+ var newItem = this._getToolbarItem(aId);
+ if (!newItem)
+ return null;
+
+ var insertItem = newItem;
+ // make sure added items are removable
+ newItem.setAttribute("removable", "true");
+
+ // Wrap the item in another node if so inclined.
+ if (aWrapper) {
+ aWrapper.appendChild(newItem);
+ insertItem = aWrapper;
+ }
+
+ // Insert the palette item into the toolbar.
+ if (aBeforeElt)
+ this.insertBefore(insertItem, aBeforeElt);
+ else
+ this.appendChild(insertItem);
+
+ return newItem;
+ ]]>
+ </body>
+ </method>
+
+ <method name="hasCustomInteractiveItems">
+ <parameter name="aCurrentSet"/>
+ <body><![CDATA[
+ if (aCurrentSet == "__empty")
+ return false;
+
+ var defaultOrNoninteractive = (this.getAttribute("defaultset") || "")
+ .split(",")
+ .concat(["separator", "spacer", "spring"]);
+ return aCurrentSet.split(",").some(function (item) {
+ return defaultOrNoninteractive.indexOf(item) == -1;
+ });
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="toolbar-menubar-autohide"
+ extends="chrome://global/content/bindings/toolbar.xml#toolbar">
+ <implementation>
+ <constructor>
+ this._setInactive();
+ </constructor>
+ <destructor>
+ this._setActive();
+ </destructor>
+
+ <field name="_inactiveTimeout">null</field>
+
+ <field name="_contextMenuListener"><![CDATA[({
+ toolbar: this,
+ contextMenu: null,
+
+ get active () {
+ return !!this.contextMenu;
+ },
+
+ init: function (event) {
+ var node = event.target;
+ while (node != this.toolbar) {
+ if (node.localName == "menupopup")
+ return;
+ node = node.parentNode;
+ }
+
+ var contextMenuId = this.toolbar.getAttribute("context");
+ if (!contextMenuId)
+ return;
+
+ this.contextMenu = document.getElementById(contextMenuId);
+ if (!this.contextMenu)
+ return;
+
+ this.contextMenu.addEventListener("popupshown", this, false);
+ this.contextMenu.addEventListener("popuphiding", this, false);
+ this.toolbar.addEventListener("mousemove", this, false);
+ },
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "popupshown":
+ this.toolbar.removeEventListener("mousemove", this, false);
+ break;
+ case "popuphiding":
+ case "mousemove":
+ this.toolbar._setInactiveAsync();
+ this.toolbar.removeEventListener("mousemove", this, false);
+ this.contextMenu.removeEventListener("popuphiding", this, false);
+ this.contextMenu.removeEventListener("popupshown", this, false);
+ this.contextMenu = null;
+ break;
+ }
+ }
+ })]]></field>
+
+ <method name="_setInactive">
+ <body><![CDATA[
+ this.setAttribute("inactive", "true");
+ ]]></body>
+ </method>
+
+ <method name="_setInactiveAsync">
+ <body><![CDATA[
+ this._inactiveTimeout = setTimeout(function (self) {
+ if (self.getAttribute("autohide") == "true") {
+ self._inactiveTimeout = null;
+ self._setInactive();
+ }
+ }, 0, this);
+ ]]></body>
+ </method>
+
+ <method name="_setActive">
+ <body><![CDATA[
+ if (this._inactiveTimeout) {
+ clearTimeout(this._inactiveTimeout);
+ this._inactiveTimeout = null;
+ }
+ this.removeAttribute("inactive");
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="DOMMenuBarActive" action="this._setActive();"/>
+ <handler event="popupshowing" action="this._setActive();"/>
+ <handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
+ <handler event="DOMMenuBarInactive"><![CDATA[
+ if (!this._contextMenuListener.active)
+ this._setInactiveAsync();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="toolbar-drag"
+ extends="chrome://global/content/bindings/toolbar.xml#toolbar">
+ <implementation>
+ <field name="_dragBindingAlive">true</field>
+ <constructor><![CDATA[
+ if (!this._draggableStarted) {
+ this._draggableStarted = true;
+ try {
+ let tmp = {};
+ Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
+ let draggableThis = new tmp.WindowDraggingElement(this);
+ draggableThis.mouseDownCheck = function(e) {
+ // Don't move while customizing.
+ return this._dragBindingAlive &&
+ this.getAttribute("customizing") != "true";
+ };
+ } catch (e) {}
+ }
+ ]]></constructor>
+ </implementation>
+ </binding>
+
+ <binding id="menubar" role="xul:menubar"
+ extends="chrome://global/content/bindings/toolbar.xml#toolbar-base" display="xul:menubar">
+ <implementation>
+ <field name="_active">false</field>
+ <field name="_statusbar">null</field>
+ <field name="_originalStatusText">null</field>
+ <property name="statusbar" onget="return this.getAttribute('statusbar');"
+ onset="this.setAttribute('statusbar', val); return val;"/>
+ <method name="_updateStatusText">
+ <parameter name="itemText"/>
+ <body>
+ <![CDATA[
+ if (!this._active)
+ return;
+ var newText = itemText ? itemText : this._originalStatusText;
+ if (newText != this._statusbar.label)
+ this._statusbar.label = newText;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ <handlers>
+ <handler event="DOMMenuBarActive">
+ <![CDATA[
+ if (!this.statusbar) return;
+ this._statusbar = document.getElementById(this.statusbar);
+ if (!this._statusbar)
+ return;
+ this._active = true;
+ this._originalStatusText = this._statusbar.label;
+ ]]>
+ </handler>
+ <handler event="DOMMenuBarInactive">
+ <![CDATA[
+ if (!this._active)
+ return;
+ this._active = false;
+ this._statusbar.label = this._originalStatusText;
+ ]]>
+ </handler>
+ <handler event="DOMMenuItemActive">this._updateStatusText(event.target.statusText);</handler>
+ <handler event="DOMMenuItemInactive">this._updateStatusText("");</handler>
+ </handlers>
+ </binding>
+
+ <binding id="toolbardecoration" role="xul:toolbarseparator" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base">
+ </binding>
+
+ <binding id="toolbarpaletteitem" extends="chrome://global/content/bindings/toolbar.xml#toolbar-base" display="xul:button">
+ <content>
+ <xul:hbox class="toolbarpaletteitem-box" flex="1" xbl:inherits="type,place">
+ <children/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="toolbarpaletteitem-palette" extends="chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem">
+ <content>
+ <xul:hbox class="toolbarpaletteitem-box" xbl:inherits="type,place">
+ <children/>
+ </xul:hbox>
+ <xul:label xbl:inherits="value=title"/>
+ </content>
+ </binding>
+
+</bindings>
+
diff --git a/components/bindings/content/toolbarbutton.xml b/components/bindings/content/toolbarbutton.xml
new file mode 100644
index 000000000..5de3f040d
--- /dev/null
+++ b/components/bindings/content/toolbarbutton.xml
@@ -0,0 +1,115 @@
+<?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="toolbarbuttonBindings"
+ 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" display="xul:button" role="xul:toolbarbutton"
+ extends="chrome://global/content/bindings/button.xml#button-base">
+ <resources>
+ <stylesheet src="chrome://global/skin/toolbarbutton.css"/>
+ </resources>
+
+ <content>
+ <children includes="observes|template|menupopup|panel|tooltip"/>
+ <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
+ <xul:label class="toolbarbutton-text" crop="right" flex="1"
+ xbl:inherits="value=label,accesskey,crop,wrap"/>
+ <xul:label class="toolbarbutton-multiline-text" flex="1"
+ xbl:inherits="xbl:text=label,accesskey,wrap"/>
+ </content>
+ </binding>
+
+ <binding id="menu" display="xul:menu"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+ <content>
+ <children includes="observes|template|menupopup|panel|tooltip"/>
+ <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,type,consumeanchor"/>
+ <xul:label class="toolbarbutton-text" crop="right" flex="1"
+ xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
+ <xul:label class="toolbarbutton-multiline-text" flex="1"
+ xbl:inherits="xbl:text=label,accesskey,wrap"/>
+ <xul:dropmarker anonid="dropmarker" type="menu"
+ class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
+ </content>
+ </binding>
+
+ <binding id="menu-vertical" display="xul:menu"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+ <content>
+ <children includes="observes|template|menupopup|panel|tooltip"/>
+ <xul:hbox flex="1" align="center">
+ <xul:vbox flex="1" align="center">
+ <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
+ <xul:label class="toolbarbutton-text" crop="right" flex="1"
+ xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
+ <xul:label class="toolbarbutton-multiline-text" flex="1"
+ xbl:inherits="xbl:text=label,accesskey,wrap"/>
+ </xul:vbox>
+ <xul:dropmarker anonid="dropmarker" type="menu"
+ class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="menu-button" display="xul:menu"
+ extends="chrome://global/content/bindings/button.xml#menu-button-base">
+ <resources>
+ <stylesheet src="chrome://global/skin/toolbarbutton.css"/>
+ </resources>
+
+ <content>
+ <children includes="observes|template|menupopup|panel|tooltip"/>
+ <xul:toolbarbutton class="box-inherit toolbarbutton-menubutton-button"
+ anonid="button" flex="1" allowevents="true"
+ xbl:inherits="disabled,crop,image,label,accesskey,command,wrap,badge,
+ align,dir,pack,orient,tooltiptext=buttontooltiptext"/>
+ <xul:dropmarker type="menu-button" class="toolbarbutton-menubutton-dropmarker"
+ anonid="dropmarker" xbl:inherits="align,dir,pack,orient,disabled,label,open,consumeanchor"/>
+ </content>
+ </binding>
+
+ <binding id="toolbarbutton-image"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+ <content>
+ <xul:image class="toolbarbutton-icon" xbl:inherits="src=image"/>
+ </content>
+ </binding>
+
+ <binding id="toolbarbutton-badged"
+ 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,consumeanchor"/>
+ <xul:label class="toolbarbutton-badge" xbl:inherits="value=badge" top="0" end="0" crop="none"/>
+ </xul:stack>
+ <xul:label class="toolbarbutton-text" crop="right" flex="1"
+ xbl:inherits="value=label,accesskey,crop,wrap"/>
+ <xul:label class="toolbarbutton-multiline-text" flex="1"
+ xbl:inherits="xbl:text=label,accesskey,wrap"/>
+ </content>
+ </binding>
+
+ <binding id="toolbarbutton-badged-menu" display="xul:menu"
+ 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,consumeanchor"/>
+ <xul:label class="toolbarbutton-badge" xbl:inherits="value=badge" top="0" end="0" crop="none"/>
+ </xul:stack>
+ <xul:label class="toolbarbutton-text" crop="right" flex="1"
+ xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
+ <xul:label class="toolbarbutton-multiline-text" flex="1"
+ xbl:inherits="xbl:text=label,accesskey,wrap"/>
+ <xul:dropmarker anonid="dropmarker" type="menu"
+ class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
+ </content>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/tree.xml b/components/bindings/content/tree.xml
new file mode 100644
index 000000000..19a1fa772
--- /dev/null
+++ b/components/bindings/content/tree.xml
@@ -0,0 +1,1559 @@
+<?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 % treeDTD SYSTEM "chrome://global/locale/tree.dtd">
+%treeDTD;
+]>
+
+<bindings id="treeBindings"
+ 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="tree-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
+ <resources>
+ <stylesheet src="chrome://global/skin/tree.css"/>
+ </resources>
+ <implementation>
+ <method name="_isAccelPressed">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ return aEvent.getModifierState("Accel");
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="tree" extends="chrome://global/content/bindings/tree.xml#tree-base" role="xul:tree">
+ <content hidevscroll="true" hidehscroll="true" clickthrough="never">
+ <children includes="treecols"/>
+ <xul:stack class="tree-stack" flex="1">
+ <xul:treerows class="tree-rows" flex="1" xbl:inherits="hidevscroll">
+ <children/>
+ </xul:treerows>
+ <xul:textbox anonid="input" class="tree-input" left="0" top="0" hidden="true"/>
+ </xul:stack>
+ <xul:hbox xbl:inherits="collapsed=hidehscroll">
+ <xul:scrollbar orient="horizontal" flex="1" increment="16" style="position:relative; z-index:2147483647;"/>
+ <xul:scrollcorner xbl:inherits="collapsed=hidevscroll"/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMXULTreeElement, nsIDOMXULMultiSelectControlElement">
+
+ <!-- ///////////////// nsIDOMXULTreeElement ///////////////// -->
+
+ <property name="columns"
+ onget="return this.treeBoxObject.columns;"/>
+
+ <property name="view"
+ onget="return this.treeBoxObject.view ?
+ this.treeBoxObject.view.QueryInterface(Components.interfaces.nsITreeView) :
+ null;"
+ onset="return this.treeBoxObject.view = val;"/>
+
+ <property name="body"
+ onget="return this.treeBoxObject.treeBody;"/>
+
+ <property name="editable"
+ onget="return this.getAttribute('editable') == 'true';"
+ onset="if (val) this.setAttribute('editable', 'true');
+ else this.removeAttribute('editable'); return val;"/>
+
+ <!-- ///////////////// nsIDOMXULSelectControlElement ///////////////// -->
+
+ <!-- ///////////////// nsIDOMXULMultiSelectControlElement ///////////////// -->
+
+ <property name="selType"
+ onget="return this.getAttribute('seltype')"
+ onset="this.setAttribute('seltype', val); return val;"/>
+
+ <property name="currentIndex"
+ onget="return this.view ? this.view.selection.currentIndex: - 1;"
+ onset="if (this.view) return this.view.selection.currentIndex = val; return val;"/>
+
+ <property name="treeBoxObject"
+ onget="return this.boxObject;"
+ readonly="true"/>
+<!-- contentView is obsolete (see bug 202391) -->
+ <property name="contentView"
+ onget="return this.view; /*.QueryInterface(Components.interfaces.nsITreeContentView)*/"
+ readonly="true"/>
+<!-- builderView is obsolete (see bug 202393) -->
+ <property name="builderView"
+ onget="return this.view; /*.QueryInterface(Components.interfaces.nsIXULTreeBuilder)*/"
+ readonly="true"/>
+ <field name="pageUpOrDownMovesSelection">
+ !/Mac/.test(navigator.platform)
+ </field>
+ <property name="keepCurrentInView"
+ onget="return (this.getAttribute('keepcurrentinview') == 'true');"
+ onset="if (val) this.setAttribute('keepcurrentinview', 'true');
+ else this.removeAttribute('keepcurrentinview'); return val;"/>
+
+ <property name="enableColumnDrag"
+ onget="return this.hasAttribute('enableColumnDrag');"
+ onset="if (val) this.setAttribute('enableColumnDrag', 'true');
+ else this.removeAttribute('enableColumnDrag'); return val;"/>
+
+ <field name="_inputField">null</field>
+
+ <property name="inputField" readonly="true">
+ <getter><![CDATA[
+ if (!this._inputField)
+ this._inputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
+ return this._inputField;
+ ]]></getter>
+ </property>
+
+ <property name="disableKeyNavigation"
+ onget="return this.hasAttribute('disableKeyNavigation');"
+ onset="if (val) this.setAttribute('disableKeyNavigation', 'true');
+ else this.removeAttribute('disableKeyNavigation'); return val;"/>
+
+ <field name="_editingRow">-1</field>
+ <field name="_editingColumn">null</field>
+
+ <property name="editingRow" readonly="true"
+ onget="return this._editingRow;"/>
+ <property name="editingColumn" readonly="true"
+ onget="return this._editingColumn;"/>
+
+ <property name="_selectDelay"
+ onset="this.setAttribute('_selectDelay', val);"
+ onget="return this.getAttribute('_selectDelay') || 50;"/>
+ <field name="_columnsDirty">true</field>
+ <field name="_lastKeyTime">0</field>
+ <field name="_incrementalString">""</field>
+
+ <field name="_touchY">-1</field>
+
+ <method name="_ensureColumnOrder">
+ <body><![CDATA[
+ if (!this._columnsDirty)
+ return;
+
+ if (this.columns) {
+ // update the ordinal position of each column to assure that it is
+ // an odd number and 2 positions above its next sibling
+ var cols = [];
+ var i;
+ for (var col = this.columns.getFirstColumn(); col; col = col.getNext())
+ cols.push(col.element);
+ for (i = 0; i < cols.length; ++i)
+ cols[i].setAttribute("ordinal", (i*2)+1);
+
+ // update the ordinal positions of splitters to even numbers, so that
+ // they are in between columns
+ var splitters = this.getElementsByTagName("splitter");
+ for (i = 0; i < splitters.length; ++i)
+ splitters[i].setAttribute("ordinal", (i+1)*2);
+ }
+ this._columnsDirty = false;
+ ]]></body>
+ </method>
+
+ <method name="_reorderColumn">
+ <parameter name="aColMove"/>
+ <parameter name="aColBefore"/>
+ <parameter name="aBefore"/>
+ <body><![CDATA[
+ this._ensureColumnOrder();
+
+ var i;
+ var cols = [];
+ var col = this.columns.getColumnFor(aColBefore);
+ if (parseInt(aColBefore.ordinal) < parseInt(aColMove.ordinal)) {
+ if (aBefore)
+ cols.push(aColBefore);
+ for (col = col.getNext(); col.element != aColMove;
+ col = col.getNext())
+ cols.push(col.element);
+
+ aColMove.ordinal = cols[0].ordinal;
+ for (i = 0; i < cols.length; ++i)
+ cols[i].ordinal = parseInt(cols[i].ordinal) + 2;
+ } else if (aColBefore.ordinal != aColMove.ordinal) {
+ if (!aBefore)
+ cols.push(aColBefore);
+ for (col = col.getPrevious(); col.element != aColMove;
+ col = col.getPrevious())
+ cols.push(col.element);
+
+ aColMove.ordinal = cols[0].ordinal;
+ for (i = 0; i < cols.length; ++i)
+ cols[i].ordinal = parseInt(cols[i].ordinal) - 2;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_getColumnAtX">
+ <parameter name="aX"/>
+ <parameter name="aThresh"/>
+ <parameter name="aPos"/>
+ <body><![CDATA[
+ var isRTL = document.defaultView.getComputedStyle(this, "")
+ .direction == "rtl";
+
+ if (aPos)
+ aPos.value = isRTL ? "after" : "before";
+
+ var columns = [];
+ var col = this.columns.getFirstColumn();
+ while (col) {
+ columns.push(col);
+ col = col.getNext();
+ }
+ if (isRTL)
+ columns.reverse();
+ var currentX = this.boxObject.x;
+ var adjustedX = aX + this.treeBoxObject.horizontalPosition;
+ for (var i = 0; i < columns.length; ++i) {
+ col = columns[i];
+ var cw = col.element.boxObject.width;
+ if (cw > 0) {
+ currentX += cw;
+ if (currentX - (cw * aThresh) > adjustedX)
+ return col.element;
+ }
+ }
+
+ if (aPos)
+ aPos.value = isRTL ? "before" : "after";
+ return columns.pop().element;
+ ]]></body>
+ </method>
+
+ <method name="changeOpenState">
+ <parameter name="row"/>
+ <!-- Optional parameter openState == true or false to set.
+ No openState param == toggle -->
+ <parameter name="openState"/>
+ <body><![CDATA[
+ if (row < 0 || !this.view.isContainer(row)) {
+ return false;
+ }
+
+ if (this.view.isContainerOpen(row) != openState) {
+ this.view.toggleOpenState(row);
+ if (row == this.currentIndex) {
+ // Only fire event when current row is expanded or collapsed
+ // because that's all the assistive technology really cares about.
+ var event = document.createEvent('Events');
+ event.initEvent('OpenStateChange', true, true);
+ this.dispatchEvent(event);
+ }
+ return true;
+ }
+ return false;
+ ]]></body>
+ </method>
+
+ <property name="_cellSelType">
+ <getter>
+ <![CDATA[
+ var seltype = this.selType;
+ if (seltype == "cell" || seltype == "text")
+ return seltype;
+ return null;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="_getNextColumn">
+ <parameter name="row"/>
+ <parameter name="left"/>
+ <body><![CDATA[
+ var col = this.view.selection.currentColumn;
+ if (col) {
+ col = left ? col.getPrevious() : col.getNext();
+ }
+ else {
+ col = this.columns.getKeyColumn();
+ }
+ while (col && (col.width == 0 || !col.selectable ||
+ !this.view.isSelectable(row, col)))
+ col = left ? col.getPrevious() : col.getNext();
+ return col;
+ ]]></body>
+ </method>
+
+ <method name="_keyNavigate">
+ <parameter name="event"/>
+ <body><![CDATA[
+ var key = String.fromCharCode(event.charCode).toLowerCase();
+ if (event.timeStamp - this._lastKeyTime > 1000)
+ this._incrementalString = key;
+ else
+ this._incrementalString += key;
+ this._lastKeyTime = event.timeStamp;
+
+ var length = this._incrementalString.length;
+ var incrementalString = this._incrementalString;
+ var charIndex = 1;
+ while (charIndex < length && incrementalString[charIndex] == incrementalString[charIndex - 1])
+ charIndex++;
+ // If all letters in incremental string are same, just try to match the first one
+ if (charIndex == length) {
+ length = 1;
+ incrementalString = incrementalString.substring(0, length);
+ }
+
+ var keyCol = this.columns.getKeyColumn();
+ var rowCount = this.view.rowCount;
+ var start = 1;
+
+ var c = this.currentIndex;
+ if (length > 1) {
+ start = 0;
+ if (c < 0)
+ c = 0;
+ }
+
+ for (var i = 0; i < rowCount; i++) {
+ var l = (i + start + c) % rowCount;
+ var cellText = this.view.getCellText(l, keyCol);
+ cellText = cellText.substring(0, length).toLowerCase();
+ if (cellText == incrementalString)
+ return l;
+ }
+ return -1;
+ ]]></body>
+ </method>
+
+ <method name="startEditing">
+ <parameter name="row"/>
+ <parameter name="column"/>
+ <body>
+ <![CDATA[
+ if (!this.editable)
+ return false;
+ if (row < 0 || row >= this.view.rowCount || !column)
+ return false;
+ if (column.type != Components.interfaces.nsITreeColumn.TYPE_TEXT &&
+ column.type != Components.interfaces.nsITreeColumn.TYPE_PASSWORD)
+ return false;
+ if (column.cycler || !this.view.isEditable(row, column))
+ return false;
+
+ // Beyond this point, we are going to edit the cell.
+ if (this._editingColumn)
+ this.stopEditing();
+
+ var input = this.inputField;
+
+ var box = this.treeBoxObject;
+ box.ensureCellIsVisible(row, column);
+
+ // Get the coordinates of the text inside the cell.
+ var textRect = box.getCoordsForCellItem(row, column, "text");
+
+ // Get the coordinates of the cell itself.
+ var cellRect = box.getCoordsForCellItem(row, column, "cell");
+
+ // Calculate the top offset of the textbox.
+ var style = window.getComputedStyle(input, "");
+ var topadj = parseInt(style.borderTopWidth) + parseInt(style.paddingTop);
+ input.top = textRect.y - topadj;
+
+ // The leftside of the textbox is aligned to the left side of the text
+ // in LTR mode, and left side of the cell in RTL mode.
+ var left, widthdiff;
+ if (style.direction == "rtl") {
+ left = cellRect.x;
+ widthdiff = cellRect.x - textRect.x;
+ } else {
+ left = textRect.x;
+ widthdiff = textRect.x - cellRect.x;
+ }
+
+ input.left = left;
+ input.height = textRect.height + topadj +
+ parseInt(style.borderBottomWidth) +
+ parseInt(style.paddingBottom);
+ input.width = cellRect.width - widthdiff;
+ input.hidden = false;
+
+ input.value = this.view.getCellText(row, column);
+ var selectText = function selectText() {
+ input.select();
+ input.inputField.focus();
+ }
+ setTimeout(selectText, 0);
+
+ this._editingRow = row;
+ this._editingColumn = column;
+ this.setAttribute("editing", "true");
+
+ box.invalidateCell(row, column);
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="stopEditing">
+ <parameter name="accept"/>
+ <body>
+ <![CDATA[
+ if (!this._editingColumn)
+ return;
+
+ var input = this.inputField;
+ var editingRow = this._editingRow;
+ var editingColumn = this._editingColumn;
+ this._editingRow = -1;
+ this._editingColumn = null;
+
+ if (accept) {
+ var value = input.value;
+ this.view.setCellText(editingRow, editingColumn, value);
+ }
+ input.hidden = true;
+ input.value = "";
+ this.removeAttribute("editing");
+ ]]>
+ </body>
+ </method>
+
+ <method name="_moveByOffset">
+ <parameter name="offset"/>
+ <parameter name="edge"/>
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ event.preventDefault();
+
+ if (this.view.rowCount == 0)
+ return;
+
+ if (this._isAccelPressed(event) && this.view.selection.single) {
+ this.treeBoxObject.scrollByLines(offset);
+ return;
+ }
+
+ var c = this.currentIndex + offset;
+ if (offset > 0 ? c > edge : c < edge) {
+ if (this.view.selection.isSelected(edge) && this.view.selection.count <= 1)
+ return;
+ c = edge;
+ }
+
+ var cellSelType = this._cellSelType;
+ if (cellSelType) {
+ var column = this.view.selection.currentColumn;
+ if (!column)
+ return;
+
+ while ((offset > 0 ? c <= edge : c >= edge) && !this.view.isSelectable(c, column))
+ c += offset;
+ if (offset > 0 ? c > edge : c < edge)
+ return;
+ }
+
+ if (!this._isAccelPressed(event))
+ this.view.selection.timedSelect(c, this._selectDelay);
+ else // Ctrl+Up/Down moves the anchor without selecting
+ this.currentIndex = c;
+ this.treeBoxObject.ensureRowIsVisible(c);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_moveByOffsetShift">
+ <parameter name="offset"/>
+ <parameter name="edge"/>
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ event.preventDefault();
+
+ if (this.view.rowCount == 0)
+ return;
+
+ if (this.view.selection.single) {
+ this.treeBoxObject.scrollByLines(offset);
+ return;
+ }
+
+ if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
+ this.view.selection.timedSelect(0, this._selectDelay);
+ return;
+ }
+
+ var c = this.currentIndex;
+ if (c == -1)
+ c = 0;
+
+ if (c == edge) {
+ if (this.view.selection.isSelected(c))
+ return;
+ }
+
+ // Extend the selection from the existing pivot, if any
+ this.view.selection.rangedSelect(-1, c + offset,
+ this._isAccelPressed(event));
+ this.treeBoxObject.ensureRowIsVisible(c + offset);
+
+ ]]>
+ </body>
+ </method>
+
+ <method name="_moveByPage">
+ <parameter name="offset"/>
+ <parameter name="edge"/>
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ event.preventDefault();
+
+ if (this.view.rowCount == 0)
+ return;
+
+ if (this.pageUpOrDownMovesSelection == this._isAccelPressed(event)) {
+ this.treeBoxObject.scrollByPages(offset);
+ return;
+ }
+
+ if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
+ this.view.selection.timedSelect(0, this._selectDelay);
+ return;
+ }
+
+ var c = this.currentIndex;
+ if (c == -1)
+ return;
+
+ if (c == edge && this.view.selection.isSelected(c)) {
+ this.treeBoxObject.ensureRowIsVisible(c);
+ return;
+ }
+ var i = this.treeBoxObject.getFirstVisibleRow();
+ var p = this.treeBoxObject.getPageLength();
+
+ if (offset > 0) {
+ i += p - 1;
+ if (c >= i) {
+ i = c + p;
+ this.treeBoxObject.ensureRowIsVisible(i > edge ? edge : i);
+ }
+ i = i > edge ? edge : i;
+
+ } else if (c <= i) {
+ i = c <= p ? 0 : c - p;
+ this.treeBoxObject.ensureRowIsVisible(i);
+ }
+ this.view.selection.timedSelect(i, this._selectDelay);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_moveByPageShift">
+ <parameter name="offset"/>
+ <parameter name="edge"/>
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ event.preventDefault();
+
+ if (this.view.rowCount == 0)
+ return;
+
+ if (this.view.rowCount == 1 && !this.view.selection.isSelected(0) &&
+ !(this.pageUpOrDownMovesSelection == this._isAccelPressed(event))) {
+ this.view.selection.timedSelect(0, this._selectDelay);
+ return;
+ }
+
+ if (this.view.selection.single)
+ return;
+
+ var c = this.currentIndex;
+ if (c == -1)
+ return;
+ if (c == edge && this.view.selection.isSelected(c)) {
+ this.treeBoxObject.ensureRowIsVisible(edge);
+ return;
+ }
+ var i = this.treeBoxObject.getFirstVisibleRow();
+ var p = this.treeBoxObject.getPageLength();
+
+ if (offset > 0) {
+ i += p - 1;
+ if (c >= i) {
+ i = c + p;
+ this.treeBoxObject.ensureRowIsVisible(i > edge ? edge : i);
+ }
+ // Extend the selection from the existing pivot, if any
+ this.view.selection.rangedSelect(-1, i > edge ? edge : i, this._isAccelPressed(event));
+
+ } else {
+
+ if (c <= i) {
+ i = c <= p ? 0 : c - p;
+ this.treeBoxObject.ensureRowIsVisible(i);
+ }
+ // Extend the selection from the existing pivot, if any
+ this.view.selection.rangedSelect(-1, i, this._isAccelPressed(event));
+ }
+
+ ]]>
+ </body>
+ </method>
+
+ <method name="_moveToEdge">
+ <parameter name="edge"/>
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ event.preventDefault();
+
+ if (this.view.rowCount == 0)
+ return;
+
+ if (this.view.selection.isSelected(edge) && this.view.selection.count == 1) {
+ this.currentIndex = edge;
+ return;
+ }
+
+ // Normal behaviour is to select the first/last row
+ if (!this._isAccelPressed(event))
+ this.view.selection.timedSelect(edge, this._selectDelay);
+
+ // In a multiselect tree Ctrl+Home/End moves the anchor
+ else if (!this.view.selection.single)
+ this.currentIndex = edge;
+
+ this.treeBoxObject.ensureRowIsVisible(edge);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_moveToEdgeShift">
+ <parameter name="edge"/>
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ event.preventDefault();
+
+ if (this.view.rowCount == 0)
+ return;
+
+ if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
+ this.view.selection.timedSelect(0, this._selectDelay);
+ return;
+ }
+
+ if (this.view.selection.single ||
+ (this.view.selection.isSelected(edge)) && this.view.selection.isSelected(this.currentIndex))
+ return;
+
+ // Extend the selection from the existing pivot, if any.
+ // -1 doesn't work here, so using currentIndex instead
+ this.view.selection.rangedSelect(this.currentIndex, edge, this._isAccelPressed(event));
+
+ this.treeBoxObject.ensureRowIsVisible(edge);
+ ]]>
+ </body>
+ </method>
+ <method name="_handleEnter">
+ <parameter name="event"/>
+ <body><![CDATA[
+ if (this._editingColumn) {
+ this.stopEditing(true);
+ this.focus();
+ return true;
+ }
+
+ if (/Mac/.test(navigator.platform)) {
+ // See if we can edit the cell.
+ var row = this.currentIndex;
+ if (this._cellSelType) {
+ var column = this.view.selection.currentColumn;
+ var startedEditing = this.startEditing(row, column);
+ if (startedEditing)
+ return true;
+ }
+ }
+ return this.changeOpenState(this.currentIndex);
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="touchstart">
+ <![CDATA[
+ if (event.touches.length > 1) {
+ // Multiple touch points detected, abort. In particular this aborts
+ // the panning gesture when the user puts a second finger down after
+ // already panning with one finger. Aborting at this point prevents
+ // the pan gesture from being resumed until all fingers are lifted
+ // (as opposed to when the user is back down to one finger).
+ this._touchY = -1;
+ } else {
+ this._touchY = event.touches[0].screenY;
+ }
+ ]]>
+ </handler>
+ <handler event="touchmove">
+ <![CDATA[
+ if (event.touches.length == 1 &&
+ this._touchY >= 0) {
+ var deltaY = this._touchY - event.touches[0].screenY;
+ var lines = Math.trunc(deltaY / this.treeBoxObject.rowHeight);
+ if (Math.abs(lines) > 0) {
+ this.treeBoxObject.scrollByLines(lines);
+ deltaY -= lines * this.treeBoxObject.rowHeight;
+ this._touchY = event.touches[0].screenY + deltaY;
+ }
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+ <handler event="touchend">
+ <![CDATA[
+ this._touchY = -1;
+ ]]>
+ </handler>
+ <handler event="MozMousePixelScroll" preventdefault="true"/>
+ <handler event="DOMMouseScroll" preventdefault="true">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ if (event.axis == event.HORIZONTAL_AXIS)
+ return;
+
+ var rows = event.detail;
+ if (rows == UIEvent.SCROLL_PAGE_UP)
+ this.treeBoxObject.scrollByPages(-1);
+ else if (rows == UIEvent.SCROLL_PAGE_DOWN)
+ this.treeBoxObject.scrollByPages(1);
+ else
+ this.treeBoxObject.scrollByLines(rows);
+ ]]>
+ </handler>
+ <handler event="MozSwipeGesture" preventdefault="true">
+ <![CDATA[
+ // Figure out which row to show
+ let targetRow = 0;
+
+ // Only handle swipe gestures up and down
+ switch (event.direction) {
+ case event.DIRECTION_DOWN:
+ targetRow = this.view.rowCount - 1;
+ // Fall through for actual action
+ case event.DIRECTION_UP:
+ this.treeBoxObject.ensureRowIsVisible(targetRow);
+ break;
+ }
+ ]]>
+ </handler>
+ <handler event="select" phase="target"
+ action="if (event.originalTarget == this) this.stopEditing(true);"/>
+ <handler event="focus">
+ <![CDATA[
+ this.treeBoxObject.focused = true;
+ if (this.currentIndex == -1 && this.view.rowCount > 0) {
+ this.currentIndex = this.treeBoxObject.getFirstVisibleRow();
+ }
+ if (this._cellSelType && !this.view.selection.currentColumn) {
+ var col = this._getNextColumn(this.currentIndex, false);
+ this.view.selection.currentColumn = col;
+ }
+ ]]>
+ </handler>
+ <handler event="blur" action="this.treeBoxObject.focused = false;"/>
+ <handler event="blur" phase="capturing"
+ action="if (event.originalTarget == this.inputField.inputField) this.stopEditing(true);"/>
+ <handler event="keydown" keycode="VK_RETURN">
+ if (this._handleEnter(event)) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ </handler>
+ <!-- Use F2 key to enter text editing. -->
+ <handler event="keydown" keycode="VK_F2">
+ <![CDATA[
+ if (!this._cellSelType)
+ return;
+ var row = this.currentIndex;
+ var column = this.view.selection.currentColumn;
+ if (this.startEditing(row, column))
+ event.preventDefault();
+ ]]>
+ </handler>
+
+ <handler event="keydown" keycode="VK_ESCAPE">
+ <![CDATA[
+ if (this._editingColumn) {
+ this.stopEditing(false);
+ this.focus();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_LEFT">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+
+ var row = this.currentIndex;
+ if (row < 0)
+ return;
+
+ var cellSelType = this._cellSelType;
+ var checkContainers = true;
+
+ var currentColumn;
+ if (cellSelType) {
+ currentColumn = this.view.selection.currentColumn;
+ if (currentColumn && !currentColumn.primary)
+ checkContainers = false;
+ }
+
+ if (checkContainers) {
+ if (this.changeOpenState(this.currentIndex, false)) {
+ event.preventDefault();
+ return;
+ }
+ var parentIndex = this.view.getParentIndex(this.currentIndex);
+ if (parentIndex >= 0) {
+ if (cellSelType && !this.view.isSelectable(parentIndex, currentColumn)) {
+ return;
+ }
+ this.view.selection.select(parentIndex);
+ this.treeBoxObject.ensureRowIsVisible(parentIndex);
+ event.preventDefault();
+ return;
+ }
+ }
+
+ if (cellSelType) {
+ var col = this._getNextColumn(row, true);
+ if (col) {
+ this.view.selection.currentColumn = col;
+ this.treeBoxObject.ensureCellIsVisible(row, col);
+ event.preventDefault();
+ }
+ }
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_RIGHT">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+
+ var row = this.currentIndex;
+ if (row < 0)
+ return;
+
+ var cellSelType = this._cellSelType;
+ var checkContainers = true;
+
+ var currentColumn;
+ if (cellSelType) {
+ currentColumn = this.view.selection.currentColumn;
+ if (currentColumn && !currentColumn.primary)
+ checkContainers = false;
+ }
+
+ if (checkContainers) {
+ if (this.changeOpenState(row, true)) {
+ event.preventDefault();
+ return;
+ }
+ var c = row + 1;
+ var view = this.view;
+ if (c < view.rowCount &&
+ view.getParentIndex(c) == row) {
+ // If already opened, select the first child.
+ // The getParentIndex test above ensures that the children
+ // are already populated and ready.
+ if (cellSelType && !this.view.isSelectable(c, currentColumn)) {
+ let col = this._getNextColumn(c, false);
+ if (col) {
+ this.view.selection.currentColumn = col;
+ }
+ }
+ this.view.selection.timedSelect(c, this._selectDelay);
+ this.treeBoxObject.ensureRowIsVisible(c);
+ event.preventDefault();
+ return;
+ }
+ }
+
+ if (cellSelType) {
+ let col = this._getNextColumn(row, false);
+ if (col) {
+ this.view.selection.currentColumn = col;
+ this.treeBoxObject.ensureCellIsVisible(row, col);
+ event.preventDefault();
+ }
+ }
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_UP" modifiers="accel any">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveByOffset(-1, 0, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_DOWN" modifiers="accel any">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveByOffset(1, this.view.rowCount - 1, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_UP" modifiers="accel any, shift">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveByOffsetShift(-1, 0, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_DOWN" modifiers="accel any, shift">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveByOffsetShift(1, this.view.rowCount - 1, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_PAGE_UP" modifiers="accel any">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveByPage(-1, 0, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_PAGE_DOWN" modifiers="accel any">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveByPage(1, this.view.rowCount - 1, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_PAGE_UP" modifiers="accel any, shift">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveByPageShift(-1, 0, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_PAGE_DOWN" modifiers="accel any, shift">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveByPageShift(1, this.view.rowCount - 1, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_HOME" modifiers="accel any">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveToEdge(0, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_END" modifiers="accel any">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveToEdge(this.view.rowCount - 1, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_HOME" modifiers="accel any, shift">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveToEdgeShift(0, event);
+ ]]>
+ </handler>
+ <handler event="keydown" keycode="VK_END" modifiers="accel any, shift">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+ _moveToEdgeShift(this.view.rowCount - 1, event);
+ ]]>
+ </handler>
+ <handler event="keypress">
+ <![CDATA[
+ if (this._editingColumn)
+ return;
+
+ if (event.charCode == ' '.charCodeAt(0)) {
+ var c = this.currentIndex;
+ if (!this.view.selection.isSelected(c) ||
+ (!this.view.selection.single && this._isAccelPressed(event))) {
+ this.view.selection.toggleSelect(c);
+ event.preventDefault();
+ }
+ }
+ else if (!this.disableKeyNavigation && event.charCode > 0 &&
+ !event.altKey && !this._isAccelPressed(event) &&
+ !event.metaKey && !event.ctrlKey) {
+ var l = this._keyNavigate(event);
+ if (l >= 0) {
+ this.view.selection.timedSelect(l, this._selectDelay);
+ this.treeBoxObject.ensureRowIsVisible(l);
+ }
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="treecols" role="xul:treecolumns">
+ <resources>
+ <stylesheet src="chrome://global/skin/tree.css"/>
+ </resources>
+ <content orient="horizontal">
+ <xul:hbox class="tree-scrollable-columns" flex="1">
+ <children includes="treecol|splitter"/>
+ </xul:hbox>
+ <xul:treecolpicker class="treecol-image" fixed="true" xbl:inherits="tooltiptext=pickertooltiptext"/>
+ </content>
+ <implementation>
+ <constructor><![CDATA[
+ // Set resizeafter="farthest" on the splitters if nothing else has been
+ // specified.
+ Array.forEach(this.getElementsByTagName("splitter"), function (splitter) {
+ if (!splitter.hasAttribute("resizeafter"))
+ splitter.setAttribute("resizeafter", "farthest");
+ });
+ ]]></constructor>
+ </implementation>
+ </binding>
+
+ <binding id="treerows" extends="chrome://global/content/bindings/tree.xml#tree-base">
+ <content>
+ <xul:hbox flex="1" class="tree-bodybox">
+ <children/>
+ </xul:hbox>
+ <xul:scrollbar height="0" minwidth="0" minheight="0" orient="vertical" xbl:inherits="collapsed=hidevscroll" style="position:relative; z-index:2147483647;"/>
+ </content>
+ <handlers>
+ <handler event="underflow">
+ <![CDATA[
+ // Scrollport event orientation
+ // 0: vertical
+ // 1: horizontal
+ // 2: both (not used)
+ var tree = document.getBindingParent(this);
+ if (event.detail == 1)
+ tree.setAttribute("hidehscroll", "true");
+ else if (event.detail == 0)
+ tree.setAttribute("hidevscroll", "true");
+ event.stopPropagation();
+ ]]>
+ </handler>
+ <handler event="overflow">
+ <![CDATA[
+ var tree = document.getBindingParent(this);
+ if (event.detail == 1)
+ tree.removeAttribute("hidehscroll");
+ else if (event.detail == 0)
+ tree.removeAttribute("hidevscroll");
+ event.stopPropagation();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="treebody" extends="chrome://global/content/bindings/tree.xml#tree-base">
+ <implementation>
+ <constructor>
+ if ("_ensureColumnOrder" in this.parentNode)
+ this.parentNode._ensureColumnOrder();
+ </constructor>
+
+ <field name="_lastSelectedRow">
+ -1
+ </field>
+ </implementation>
+ <handlers>
+ <!-- If there is no modifier key, we select on mousedown, not
+ click, so that drags work correctly. -->
+ <handler event="mousedown" clickcount="1">
+ <![CDATA[
+ if (this.parentNode.disabled)
+ return;
+ if (((!this._isAccelPressed(event) ||
+ !this.parentNode.pageUpOrDownMovesSelection) &&
+ !event.shiftKey && !event.metaKey) ||
+ this.parentNode.view.selection.single) {
+ var b = this.parentNode.treeBoxObject;
+ var cell = b.getCellAt(event.clientX, event.clientY);
+ var view = this.parentNode.view;
+
+ // save off the last selected row
+ this._lastSelectedRow = cell.row;
+
+ if (cell.row == -1)
+ return;
+
+ if (cell.childElt == "twisty")
+ return;
+
+ if (cell.col && event.button == 0) {
+ if (cell.col.cycler) {
+ view.cycleCell(cell.row, cell.col);
+ return;
+ } else if (cell.col.type == Components.interfaces.nsITreeColumn.TYPE_CHECKBOX) {
+ if (this.parentNode.editable && cell.col.editable &&
+ view.isEditable(cell.row, cell.col)) {
+ var value = view.getCellValue(cell.row, cell.col);
+ value = value == "true" ? "false" : "true";
+ view.setCellValue(cell.row, cell.col, value);
+ return;
+ }
+ }
+ }
+
+ var cellSelType = this.parentNode._cellSelType;
+ if (cellSelType == "text" && cell.childElt != "text" && cell.childElt != "image")
+ return;
+
+ if (cellSelType) {
+ if (!cell.col.selectable ||
+ !view.isSelectable(cell.row, cell.col)) {
+ return;
+ }
+ }
+
+ if (!view.selection.isSelected(cell.row)) {
+ view.selection.select(cell.row);
+ b.ensureRowIsVisible(cell.row);
+ }
+
+ if (cellSelType) {
+ view.selection.currentColumn = cell.col;
+ }
+ }
+ ]]>
+ </handler>
+
+ <!-- On a click (up+down on the same item), deselect everything
+ except this item. -->
+ <handler event="click" button="0" clickcount="1">
+ <![CDATA[
+ if (this.parentNode.disabled)
+ return;
+ var b = this.parentNode.treeBoxObject;
+ var cell = b.getCellAt(event.clientX, event.clientY);
+ var view = this.parentNode.view;
+
+ if (cell.row == -1)
+ return;
+
+ if (cell.childElt == "twisty") {
+ if (view.selection.currentIndex >= 0 &&
+ view.isContainerOpen(cell.row)) {
+ var parentIndex = view.getParentIndex(view.selection.currentIndex);
+ while (parentIndex >= 0 && parentIndex != cell.row)
+ parentIndex = view.getParentIndex(parentIndex);
+ if (parentIndex == cell.row) {
+ var parentSelectable = true;
+ if (this.parentNode._cellSelType) {
+ var currentColumn = view.selection.currentColumn;
+ if (!view.isSelectable(parentIndex, currentColumn))
+ parentSelectable = false;
+ }
+ if (parentSelectable)
+ view.selection.select(parentIndex);
+ }
+ }
+ this.parentNode.changeOpenState(cell.row);
+ return;
+ }
+
+ if (! view.selection.single) {
+ var augment = this._isAccelPressed(event);
+ if (event.shiftKey) {
+ view.selection.rangedSelect(-1, cell.row, augment);
+ b.ensureRowIsVisible(cell.row);
+ return;
+ }
+ if (augment) {
+ view.selection.toggleSelect(cell.row);
+ b.ensureRowIsVisible(cell.row);
+ view.selection.currentIndex = cell.row;
+ return;
+ }
+ }
+
+ /* We want to deselect all the selected items except what was
+ clicked, UNLESS it was a right-click. We have to do this
+ in click rather than mousedown so that you can drag a
+ selected group of items */
+
+ if (!cell.col) return;
+
+ // if the last row has changed in between the time we
+ // mousedown and the time we click, don't fire the select handler.
+ // see bug #92366
+ if (!cell.col.cycler && this._lastSelectedRow == cell.row &&
+ cell.col.type != Components.interfaces.nsITreeColumn.TYPE_CHECKBOX) {
+
+ var cellSelType = this.parentNode._cellSelType;
+ if (cellSelType == "text" && cell.childElt != "text" && cell.childElt != "image")
+ return;
+
+ if (cellSelType) {
+ if (!cell.col.selectable ||
+ !view.isSelectable(cell.row, cell.col)) {
+ return;
+ }
+ }
+
+ view.selection.select(cell.row);
+ b.ensureRowIsVisible(cell.row);
+
+ if (cellSelType) {
+ view.selection.currentColumn = cell.col;
+ }
+ }
+ ]]>
+ </handler>
+
+ <!-- double-click -->
+ <handler event="click" clickcount="2">
+ <![CDATA[
+ if (this.parentNode.disabled)
+ return;
+ var tbo = this.parentNode.treeBoxObject;
+ var view = this.parentNode.view;
+ var row = view.selection.currentIndex;
+
+ if (row == -1)
+ return;
+
+ var cell = tbo.getCellAt(event.clientX, event.clientY);
+
+ if (cell.childElt != "twisty") {
+ view.selection.currentColumn = cell.col;
+ this.parentNode.startEditing(row, cell.col);
+ }
+
+ if (this.parentNode._editingColumn || !view.isContainer(row))
+ return;
+
+ // Cyclers and twisties respond to single clicks, not double clicks
+ if (cell.col && !cell.col.cycler && cell.childElt != "twisty")
+ this.parentNode.changeOpenState(row);
+ ]]>
+ </handler>
+
+ </handlers>
+ </binding>
+
+ <binding id="treecol-base" role="xul:treecolumnitem"
+ extends="chrome://global/content/bindings/tree.xml#tree-base">
+ <implementation>
+ <constructor>
+ this.parentNode.parentNode._columnsDirty = true;
+ </constructor>
+
+ <property name="ordinal">
+ <getter><![CDATA[
+ var val = this.getAttribute("ordinal");
+ if (val == "")
+ return "1";
+
+ return "" + (val == "0" ? 0 : parseInt(val));
+ ]]></getter>
+ <setter><![CDATA[
+ this.setAttribute("ordinal", val);
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="_previousVisibleColumn">
+ <getter><![CDATA[
+ var sib = this.boxObject.previousSibling;
+ while (sib) {
+ if (sib.localName == "treecol" && sib.boxObject.width > 0 && sib.parentNode == this.parentNode)
+ return sib;
+ sib = sib.boxObject.previousSibling;
+ }
+ return null;
+ ]]></getter>
+ </property>
+
+ <method name="_onDragMouseMove">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var col = document.treecolDragging;
+ if (!col) return;
+
+ // determine if we have moved the mouse far enough
+ // to initiate a drag
+ if (col.mDragGesturing) {
+ if (Math.abs(aEvent.clientX - col.mStartDragX) < 5 &&
+ Math.abs(aEvent.clientY - col.mStartDragY) < 5) {
+ return;
+ }
+ col.mDragGesturing = false;
+ col.setAttribute("dragging", "true");
+ window.addEventListener("click", col._onDragMouseClick, true);
+ }
+
+ var pos = {};
+ var targetCol = col.parentNode.parentNode._getColumnAtX(aEvent.clientX, 0.5, pos);
+
+ // bail if we haven't mousemoved to a different column
+ if (col.mTargetCol == targetCol && col.mTargetDir == pos.value)
+ return;
+
+ var tree = col.parentNode.parentNode;
+ var sib;
+ var column;
+ if (col.mTargetCol) {
+ // remove previous insertbefore/after attributes
+ col.mTargetCol.removeAttribute("insertbefore");
+ col.mTargetCol.removeAttribute("insertafter");
+ column = tree.columns.getColumnFor(col.mTargetCol);
+ tree.treeBoxObject.invalidateColumn(column);
+ sib = col.mTargetCol._previousVisibleColumn;
+ if (sib) {
+ sib.removeAttribute("insertafter");
+ column = tree.columns.getColumnFor(sib);
+ tree.treeBoxObject.invalidateColumn(column);
+ }
+ col.mTargetCol = null;
+ col.mTargetDir = null;
+ }
+
+ if (targetCol) {
+ // set insertbefore/after attributes
+ if (pos.value == "after") {
+ targetCol.setAttribute("insertafter", "true");
+ } else {
+ targetCol.setAttribute("insertbefore", "true");
+ sib = targetCol._previousVisibleColumn;
+ if (sib) {
+ sib.setAttribute("insertafter", "true");
+ column = tree.columns.getColumnFor(sib);
+ tree.treeBoxObject.invalidateColumn(column);
+ }
+ }
+ column = tree.columns.getColumnFor(targetCol);
+ tree.treeBoxObject.invalidateColumn(column);
+ col.mTargetCol = targetCol;
+ col.mTargetDir = pos.value;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_onDragMouseUp">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var col = document.treecolDragging;
+ if (!col) return;
+
+ if (!col.mDragGesturing) {
+ if (col.mTargetCol) {
+ // remove insertbefore/after attributes
+ var before = col.mTargetCol.hasAttribute("insertbefore");
+ col.mTargetCol.removeAttribute(before ? "insertbefore" : "insertafter");
+
+ var sib = col.mTargetCol._previousVisibleColumn;
+ if (before && sib) {
+ sib.removeAttribute("insertafter");
+ }
+
+ // Move the column only if it will result in a different column
+ // ordering
+ var move = true;
+
+ // If this is a before move and the previous visible column is
+ // the same as the column we're moving, don't move
+ if (before && col == sib) {
+ move = false;
+ }
+ else if (!before && col == col.mTargetCol) {
+ // If this is an after move and the column we're moving is
+ // the same as the target column, don't move.
+ move = false;
+ }
+
+ if (move) {
+ col.parentNode.parentNode._reorderColumn(col, col.mTargetCol, before);
+ }
+
+ // repaint to remove lines
+ col.parentNode.parentNode.treeBoxObject.invalidate();
+
+ col.mTargetCol = null;
+ }
+ } else
+ col.mDragGesturing = false;
+
+ document.treecolDragging = null;
+ col.removeAttribute("dragging");
+
+ window.removeEventListener("mousemove", col._onDragMouseMove, true);
+ window.removeEventListener("mouseup", col._onDragMouseUp, true);
+ // we have to wait for the click event to fire before removing
+ // cancelling handler
+ var clickHandler = function(handler) {
+ window.removeEventListener("click", handler, true);
+ };
+ window.setTimeout(clickHandler, 0, col._onDragMouseClick);
+ ]]></body>
+ </method>
+
+ <method name="_onDragMouseClick">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ // prevent click event from firing after column drag and drop
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="mousedown" button="0"><![CDATA[
+ if (this.parentNode.parentNode.enableColumnDrag) {
+ var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var cols = this.parentNode.getElementsByTagNameNS(xulns, "treecol");
+
+ // only start column drag operation if there are at least 2 visible columns
+ var visible = 0;
+ for (var i = 0; i < cols.length; ++i)
+ if (cols[i].boxObject.width > 0) ++visible;
+
+ if (visible > 1) {
+ window.addEventListener("mousemove", this._onDragMouseMove, true);
+ window.addEventListener("mouseup", this._onDragMouseUp, true);
+ document.treecolDragging = this;
+ this.mDragGesturing = true;
+ this.mStartDragX = event.clientX;
+ this.mStartDragY = event.clientY;
+ }
+ }
+ ]]></handler>
+ <handler event="click" button="0" phase="target">
+ <![CDATA[
+ if (event.target != event.originalTarget)
+ return;
+
+ // On Windows multiple clicking on tree columns only cycles one time
+ // every 2 clicks.
+ if (/Win/.test(navigator.platform) && event.detail % 2 == 0)
+ return;
+
+ var tree = this.parentNode.parentNode;
+ if (tree.columns) {
+ tree.view.cycleHeader(tree.columns.getColumnFor(this));
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="treecol" extends="chrome://global/content/bindings/tree.xml#treecol-base">
+ <content>
+ <xul:label class="treecol-text" xbl:inherits="crop,value=label" flex="1" crop="right"/>
+ <xul:image class="treecol-sortdirection" xbl:inherits="sortDirection,hidden=hideheader"/>
+ </content>
+ </binding>
+
+ <binding id="treecol-image" extends="chrome://global/content/bindings/tree.xml#treecol-base">
+ <content>
+ <xul:image class="treecol-icon" xbl:inherits="src"/>
+ </content>
+ </binding>
+
+ <binding id="columnpicker" display="xul:button" role="xul:button"
+ extends="chrome://global/content/bindings/tree.xml#tree-base">
+ <content>
+ <xul:image class="tree-columnpicker-icon"/>
+ <xul:menupopup anonid="popup">
+ <xul:menuseparator anonid="menuseparator"/>
+ <xul:menuitem anonid="menuitem" label="&restoreColumnOrder.label;"/>
+ </xul:menupopup>
+ </content>
+
+ <implementation>
+ <method name="buildPopup">
+ <parameter name="aPopup"/>
+ <body>
+ <![CDATA[
+ // We no longer cache the picker content, remove the old content.
+ while (aPopup.childNodes.length > 2)
+ aPopup.removeChild(aPopup.firstChild);
+
+ var refChild = aPopup.firstChild;
+
+ var tree = this.parentNode.parentNode;
+ for (var currCol = tree.columns.getFirstColumn(); currCol;
+ currCol = currCol.getNext()) {
+ // Construct an entry for each column in the row, unless
+ // it is not being shown.
+ var currElement = currCol.element;
+ if (!currElement.hasAttribute("ignoreincolumnpicker")) {
+ var popupChild = document.createElement("menuitem");
+ popupChild.setAttribute("type", "checkbox");
+ var columnName = currElement.getAttribute("display") ||
+ currElement.getAttribute("label");
+ popupChild.setAttribute("label", columnName);
+ popupChild.setAttribute("colindex", currCol.index);
+ if (currElement.getAttribute("hidden") != "true")
+ popupChild.setAttribute("checked", "true");
+ if (currCol.primary)
+ popupChild.setAttribute("disabled", "true");
+ aPopup.insertBefore(popupChild, refChild);
+ }
+ }
+
+ var hidden = !tree.enableColumnDrag;
+ const anonids = ["menuseparator", "menuitem"];
+ for (var i = 0; i < anonids.length; i++) {
+ var element = document.getAnonymousElementByAttribute(this, "anonid", anonids[i]);
+ element.hidden = hidden;
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ if (event.originalTarget == this) {
+ var popup = document.getAnonymousElementByAttribute(this, "anonid", "popup");
+ this.buildPopup(popup);
+ popup.showPopup(this, -1, -1, "popup", "bottomright", "topright");
+ }
+ else {
+ var tree = this.parentNode.parentNode;
+ tree.stopEditing(true);
+ var menuitem = document.getAnonymousElementByAttribute(this, "anonid", "menuitem");
+ if (event.originalTarget == menuitem) {
+ tree.columns.restoreNaturalOrder();
+ tree._ensureColumnOrder();
+ }
+ else {
+ var colindex = event.originalTarget.getAttribute("colindex");
+ var column = tree.columns[colindex];
+ if (column) {
+ var element = column.element;
+ if (element.getAttribute("hidden") == "true")
+ element.setAttribute("hidden", "false");
+ else
+ element.setAttribute("hidden", "true");
+ }
+ }
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/components/bindings/content/videocontrols.css b/components/bindings/content/videocontrols.css
new file mode 100644
index 000000000..99dbf5a2f
--- /dev/null
+++ b/components/bindings/content/videocontrols.css
@@ -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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+.scrubber,
+.volumeControl {
+ -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#suppressChangeEvent");
+}
+
+.scrubber .scale-thumb {
+ -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#timeThumb");
+}
+
+.playButton,
+.muteButton,
+.scrubber .scale-slider,
+.volumeControl .scale-slider {
+ -moz-user-focus: none;
+}
+
+.controlBar[fullscreen-unavailable] > .fullscreenButton {
+ display: none;
+}
+
+.mediaControlsFrame {
+ direction: ltr;
+ /* Prevent unwanted style inheritance. See bug 554717. */
+ text-align: left;
+ list-style-image: none !important;
+ font: normal normal normal 100%/normal sans-serif !important;
+ text-decoration: none !important;
+}
+
+.controlsSpacer[hideCursor] {
+ cursor: none;
+}
+
+.controlsOverlay[scaled] {
+ -moz-box-align: center;
+}
+
+/* CSS Transitions
+ *
+ * These are overriden by the default theme; the rules here just
+ * provide a fallback to drive the required transitionend event
+ * (in case a 3rd party theme does not provide transitions).
+ */
+.controlBar:not([immediate]) {
+ transition-property: opacity;
+ transition-duration: 1ms;
+}
+.controlBar[fadeout] {
+ opacity: 0;
+}
+.volumeStack:not([immediate]) {
+ transition-property: opacity, margin-top;
+ transition-duration: 1ms, 1ms;
+}
+.volumeStack[fadeout] {
+ opacity: 0;
+ margin-top: 0;
+}
+.statusOverlay:not([immediate]) {
+ transition-property: opacity;
+ transition-duration: 1ms;
+ transition-delay: 750ms;
+}
+.statusOverlay[fadeout] {
+ opacity: 0;
+}
+
+/* Statistics formatting */
+html|td.statLabel {
+ font-weight: bold;
+ max-width: 20%;
+ white-space: nowrap;
+}
+html|td.statValue {
+ max-width: 30%;
+}
+html|td.filename {
+ max-width: 80%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+html|span.statActivity > html|span {
+ display: none;
+}
+html|span.statActivity[activity="paused"] > html|span.statActivityPaused,
+html|span.statActivity[activity="playing"] > html|span.statActivityPlaying,
+html|span.statActivity[activity="ended"] > html|span.statActivityEnded,
+html|span.statActivity[seeking] > html|span.statActivitySeeking {
+ display: inline;
+}
+
+.controlBar[size="hidden"],
+.controlBar[size="small"] .durationBox,
+.controlBar[size="small"] .durationLabel,
+.controlBar[size="small"] .positionLabel,
+.controlBar[size="small"] .volumeStack {
+ visibility: collapse;
+}
+
+.controlBar[size="small"] .scrubberStack,
+.controlBar[size="small"] .backgroundBar,
+.controlBar[size="small"] .bufferBar,
+.controlBar[size="small"] .progressBar,
+.controlBar[size="small"] .scrubber {
+ visibility: hidden;
+}
+
+/* Error description formatting */
+.errorLabel {
+ display: none;
+}
+
+[error="errorAborted"] > [anonid="errorAborted"],
+[error="errorNetwork"] > [anonid="errorNetwork"],
+[error="errorDecode"] > [anonid="errorDecode"],
+[error="errorSrcNotSupported"] > [anonid="errorSrcNotSupported"],
+[error="errorNoSource"] > [anonid="errorNoSource"],
+[error="errorGeneric"] > [anonid="errorGeneric"] {
+ display: inline;
+}
diff --git a/components/bindings/content/videocontrols.xml b/components/bindings/content/videocontrols.xml
new file mode 100644
index 000000000..630f5a022
--- /dev/null
+++ b/components/bindings/content/videocontrols.xml
@@ -0,0 +1,2027 @@
+<?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 % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
+ %videocontrolsDTD;
+]>
+
+<bindings id="videoControlBindings"
+ 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"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <binding id="timeThumb"
+ extends="chrome://global/content/bindings/scale.xml#scalethumb">
+ <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <xbl:children/>
+ <hbox class="timeThumb" xbl:inherits="showhours">
+ <label class="timeLabel"/>
+ </hbox>
+ </xbl:content>
+ <implementation>
+
+ <constructor>
+ <![CDATA[
+ this.timeLabel = document.getAnonymousElementByAttribute(this, "class", "timeLabel");
+ this.timeLabel.setAttribute("value", "0:00");
+ ]]>
+ </constructor>
+
+ <property name="showHours">
+ <getter>
+ <![CDATA[
+ return this.getAttribute("showhours") == "true";
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this.setAttribute("showhours", val);
+ // If the duration becomes known while we're still showing the value
+ // for time=0, immediately update the value to show or hide the hours.
+ // It's less intrusive to do it now than when the user clicks play and
+ // is looking right next to the thumb.
+ var displayedTime = this.timeLabel.getAttribute("value");
+ if (val && displayedTime == "0:00")
+ this.timeLabel.setAttribute("value", "0:00:00");
+ else if (!val && displayedTime == "0:00:00")
+ this.timeLabel.setAttribute("value", "0:00");
+ ]]>
+ </setter>
+ </property>
+
+ <method name="setTime">
+ <parameter name="time"/>
+ <body>
+ <![CDATA[
+ var timeString;
+ time = Math.round(time / 1000);
+ var hours = Math.floor(time / 3600);
+ var mins = Math.floor((time % 3600) / 60);
+ var secs = Math.floor(time % 60);
+ if (secs < 10)
+ secs = "0" + secs;
+ if (hours || this.showHours) {
+ if (mins < 10)
+ mins = "0" + mins;
+ timeString = hours + ":" + mins + ":" + secs;
+ } else {
+ timeString = mins + ":" + secs;
+ }
+
+ this.timeLabel.setAttribute("value", timeString);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="suppressChangeEvent"
+ extends="chrome://global/content/bindings/scale.xml#scale">
+ <implementation implements="nsIXBLAccessible">
+ <!-- nsIXBLAccessible -->
+ <property name="accessibleName" readonly="true">
+ <getter>
+ if (this.type != "scrubber")
+ return "";
+
+ var currTime = this.thumb.timeLabel.getAttribute("value");
+ var totalTime = this.durationValue;
+
+ return this.scrubberNameFormat.replace(/#1/, currTime).
+ replace(/#2/, totalTime);
+ </getter>
+ </property>
+
+ <constructor>
+ <![CDATA[
+ this.scrubberNameFormat = ]]>"&scrubberScale.nameFormat;"<![CDATA[;
+ this.durationValue = "";
+ this.valueBar = null;
+ this.isDragging = false;
+ this.isPausedByDragging = false;
+
+ this.thumb = document.getAnonymousElementByAttribute(this, "class", "scale-thumb");
+ this.type = this.getAttribute("class");
+ this.Utils = document.getBindingParent(this.parentNode).Utils;
+ if (this.type == "scrubber")
+ this.valueBar = this.Utils.progressBar;
+ ]]>
+ </constructor>
+
+ <method name="valueChanged">
+ <parameter name="which"/>
+ <parameter name="newValue"/>
+ <parameter name="userChanged"/>
+ <body>
+ <![CDATA[
+ // This method is a copy of the base binding's valueChanged(), except that it does
+ // not dispatch a |change| event (to avoid exposing the event to web content), and
+ // just calls the videocontrol's seekToPosition() method directly.
+ switch (which) {
+ case "curpos":
+ if (this.type == "scrubber") {
+ // Update the time shown in the thumb.
+ this.thumb.setTime(newValue);
+ this.Utils.positionLabel.setAttribute("value", this.thumb.timeLabel.value);
+ // Update the value bar to match the thumb position.
+ let percent = newValue / this.max;
+ this.valueBar.value = Math.round(percent * 10000); // has max=10000
+ }
+
+ // The value of userChanged is true when changing the position with the mouse,
+ // but not when pressing an arrow key. However, the base binding sets
+ // ._userChanged in its keypress handlers, so we just need to check both.
+ if (!userChanged && !this._userChanged)
+ return;
+ this.setAttribute("value", newValue);
+
+ if (this.type == "scrubber")
+ this.Utils.seekToPosition(newValue);
+ else if (this.type == "volumeControl")
+ this.Utils.setVolume(newValue / 100);
+ break;
+
+ case "minpos":
+ this.setAttribute("min", newValue);
+ break;
+
+ case "maxpos":
+ if (this.type == "scrubber") {
+ // Update the value bar to match the thumb position.
+ let percent = this.value / newValue;
+ this.valueBar.value = Math.round(percent * 10000); // has max=10000
+ }
+ this.setAttribute("max", newValue);
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="dragStateChanged">
+ <parameter name="isDragging"/>
+ <body>
+ <![CDATA[
+ if (this.type == "scrubber") {
+ this.Utils.log("--- dragStateChanged: " + isDragging + " ---");
+ this.isDragging = isDragging;
+ if (this.isPausedByDragging && !isDragging) {
+ // After the drag ends, resume playing.
+ this.Utils.video.play();
+ this.isPausedByDragging = false;
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="pauseVideoDuringDragging">
+ <body>
+ <![CDATA[
+ if (this.isDragging &&
+ !this.Utils.video.paused && !this.isPausedByDragging) {
+ this.isPausedByDragging = true;
+ this.Utils.video.pause();
+ }
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="videoControls">
+
+ <resources>
+ <stylesheet src="chrome://global/content/bindings/videocontrols.css"/>
+ <stylesheet src="chrome://global/skin/media/videocontrols.css"/>
+ </resources>
+
+ <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="mediaControlsFrame">
+ <stack flex="1">
+ <vbox flex="1" class="statusOverlay" hidden="true">
+ <box class="statusIcon"/>
+ <label class="errorLabel" anonid="errorAborted">&error.aborted;</label>
+ <label class="errorLabel" anonid="errorNetwork">&error.network;</label>
+ <label class="errorLabel" anonid="errorDecode">&error.decode;</label>
+ <label class="errorLabel" anonid="errorSrcNotSupported">&error.srcNotSupported;</label>
+ <label class="errorLabel" anonid="errorNoSource">&error.noSource2;</label>
+ <label class="errorLabel" anonid="errorGeneric">&error.generic;</label>
+ </vbox>
+
+ <vbox class="controlsOverlay">
+ <stack flex="1">
+ <spacer class="controlsSpacer" flex="1"/>
+ <box class="clickToPlay" hidden="true" flex="1"/>
+ <vbox class="textTrackList" hidden="true" offlabel="&closedCaption.off;"></vbox>
+ </stack>
+ <hbox class="controlBar" hidden="true">
+ <button class="playButton"
+ playlabel="&playButton.playLabel;"
+ pauselabel="&playButton.pauseLabel;"/>
+ <stack class="scrubberStack" flex="1">
+ <box class="backgroundBar"/>
+ <progressmeter class="bufferBar"/>
+ <progressmeter class="progressBar" max="10000"/>
+ <scale class="scrubber" movetoclick="true"/>
+ </stack>
+ <vbox class="durationBox">
+ <label class="positionLabel" role="presentation"/>
+ <label class="durationLabel" role="presentation"/>
+ </vbox>
+ <button class="muteButton"
+ mutelabel="&muteButton.muteLabel;"
+ unmutelabel="&muteButton.unmuteLabel;"/>
+ <stack class="volumeStack">
+ <box class="volumeBackground"/>
+ <box class="volumeForeground" anonid="volumeForeground"/>
+ <scale class="volumeControl" movetoclick="true"/>
+ </stack>
+ <button class="closedCaptionButton"/>
+ <button class="fullscreenButton"
+ enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
+ exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
+ </hbox>
+ </vbox>
+ </stack>
+ </xbl:content>
+
+ <implementation>
+
+ <constructor>
+ <![CDATA[
+ this.isTouchControl = false;
+ this.randomID = 0;
+
+ this.Utils = {
+ debug : false,
+ video : null,
+ videocontrols : null,
+ controlBar : null,
+ playButton : null,
+ muteButton : null,
+ volumeControl : null,
+ durationLabel : null,
+ positionLabel : null,
+ scrubberThumb : null,
+ scrubber : null,
+ progressBar : null,
+ bufferBar : null,
+ statusOverlay : null,
+ controlsSpacer : null,
+ clickToPlay : null,
+ controlsOverlay : null,
+ fullscreenButton : null,
+ currentTextTrackIndex: 0,
+
+ textTracksCount: 0,
+ randomID : 0,
+ videoEvents : ["play", "pause", "ended", "volumechange", "loadeddata",
+ "loadstart", "timeupdate", "progress",
+ "playing", "waiting", "canplay", "canplaythrough",
+ "seeking", "seeked", "emptied", "loadedmetadata",
+ "error", "suspend", "stalled",
+ "mozinterruptbegin", "mozinterruptend" ],
+
+ firstFrameShown : false,
+ timeUpdateCount : 0,
+ maxCurrentTimeSeen : 0,
+ _isAudioOnly : false,
+ get isAudioOnly() { return this._isAudioOnly; },
+ set isAudioOnly(val) {
+ this._isAudioOnly = val;
+ this.setFullscreenButtonState();
+
+ if (!this.isTopLevelSyntheticDocument)
+ return;
+ if (this._isAudioOnly) {
+ this.video.style.height = this._controlBarHeight + "px";
+ this.video.style.width = "66%";
+ } else {
+ this.video.style.removeProperty("height");
+ this.video.style.removeProperty("width");
+ }
+ },
+ suppressError : false,
+
+ setupStatusFader : function(immediate) {
+ // Since the play button will be showing, we don't want to
+ // show the throbber behind it. The throbber here will
+ // only show if needed after the play button has been pressed.
+ if (!this.clickToPlay.hidden) {
+ this.startFadeOut(this.statusOverlay, true);
+ return;
+ }
+
+ var show = false;
+ if (this.video.seeking ||
+ (this.video.error && !this.suppressError) ||
+ this.video.networkState == this.video.NETWORK_NO_SOURCE ||
+ (this.video.networkState == this.video.NETWORK_LOADING &&
+ (this.video.paused || this.video.ended
+ ? this.video.readyState < this.video.HAVE_CURRENT_DATA
+ : this.video.readyState < this.video.HAVE_FUTURE_DATA)) ||
+ (this.timeUpdateCount <= 1 && !this.video.ended &&
+ this.video.readyState < this.video.HAVE_FUTURE_DATA &&
+ this.video.networkState == this.video.NETWORK_LOADING))
+ show = true;
+
+ // Explicitly hide the status fader if this
+ // is audio only until bug 619421 is fixed.
+ if (this.isAudioOnly)
+ show = false;
+
+ this.log("Status overlay: seeking=" + this.video.seeking +
+ " error=" + this.video.error + " readyState=" + this.video.readyState +
+ " paused=" + this.video.paused + " ended=" + this.video.ended +
+ " networkState=" + this.video.networkState +
+ " timeUpdateCount=" + this.timeUpdateCount +
+ " --> " + (show ? "SHOW" : "HIDE"));
+ this.startFade(this.statusOverlay, show, immediate);
+ },
+
+ /*
+ * Set the initial state of the controls. The binding is normally created along
+ * with video element, but could be attached at any point (eg, if the video is
+ * removed from the document and then reinserted). Thus, some one-time events may
+ * have already fired, and so we'll need to explicitly check the initial state.
+ */
+ setupInitialState : function() {
+ this.randomID = Math.random();
+ this.videocontrols.randomID = this.randomID;
+
+ this.setPlayButtonState(this.video.paused);
+
+ this.setFullscreenButtonState();
+
+ var duration = Math.round(this.video.duration * 1000); // in ms
+ var currentTime = Math.round(this.video.currentTime * 1000); // in ms
+ this.log("Initial playback position is at " + currentTime + " of " + duration);
+ // It would be nice to retain maxCurrentTimeSeen, but it would be difficult
+ // to determine if the media source changed while we were detached.
+ this.maxCurrentTimeSeen = currentTime;
+ this.showPosition(currentTime, duration);
+
+ // If we have metadata, check if this is a <video> without
+ // video data, or a video with no audio track.
+ if (this.video.readyState >= this.video.HAVE_METADATA) {
+ if (this.video instanceof HTMLVideoElement &&
+ (this.video.videoWidth == 0 || this.video.videoHeight == 0))
+ this.isAudioOnly = true;
+
+ // We have to check again if the media has audio here,
+ // because of bug 718107: switching to fullscreen may
+ // cause the bindings to detach and reattach, hence
+ // unsetting the attribute.
+ if (!this.isAudioOnly && !this.video.mozHasAudio) {
+ this.muteButton.setAttribute("noAudio", "true");
+ this.muteButton.setAttribute("disabled", "true");
+ }
+ }
+
+ if (this.isAudioOnly)
+ this.clickToPlay.hidden = true;
+
+ // If the first frame hasn't loaded, kick off a throbber fade-in.
+ if (this.video.readyState >= this.video.HAVE_CURRENT_DATA)
+ this.firstFrameShown = true;
+
+ // We can't determine the exact buffering status, but do know if it's
+ // fully loaded. (If it's still loading, it will fire a progress event
+ // and we'll figure out the exact state then.)
+ this.bufferBar.setAttribute("max", 100);
+ if (this.video.readyState >= this.video.HAVE_METADATA)
+ this.showBuffered();
+ else
+ this.bufferBar.setAttribute("value", 0);
+
+ // Set the current status icon.
+ if (this.hasError()) {
+ this.clickToPlay.hidden = true;
+ this.statusIcon.setAttribute("type", "error");
+ this.updateErrorText();
+ this.setupStatusFader(true);
+ }
+
+ // An event handler for |onresize| should be added when bug 227495 is fixed.
+ this.controlBar.hidden = false;
+ this._playButtonWidth = this.playButton.clientWidth;
+ this._durationLabelWidth = this.durationLabel.clientWidth;
+ this._muteButtonWidth = this.muteButton.clientWidth;
+ this._volumeControlWidth = this.volumeControl.clientWidth;
+ this._closedCaptionButtonWidth = this.closedCaptionButton.clientWidth;
+ this._fullscreenButtonWidth = this.fullscreenButton.clientWidth;
+ this._controlBarHeight = this.controlBar.clientHeight;
+ this.controlBar.hidden = true;
+ this.adjustControlSize();
+
+ // Can only update the volume controls once we've computed
+ // _volumeControlWidth, since the volume slider implementation
+ // depends on it.
+ this.updateVolumeControls();
+ },
+
+ setupNewLoadState : function() {
+ // videocontrols.css hides the control bar by default, because if script
+ // is disabled our binding's script is disabled too (bug 449358). Thus,
+ // the controls are broken and we don't want them shown. But if script is
+ // enabled, the code here will run and can explicitly unhide the controls.
+ //
+ // For videos with |autoplay| set, we'll leave the controls initially hidden,
+ // so that they don't get in the way of the playing video. Otherwise we'll
+ // go ahead and reveal the controls now, so they're an obvious user cue.
+ //
+ // (Note: the |controls| attribute is already handled via layout/style/html.css)
+ var shouldShow = !this.dynamicControls ||
+ (this.video.paused &&
+ !(this.video.autoplay && this.video.mozAutoplayEnabled));
+ // Hide the overlay if the video time is non-zero or if an error occurred to workaround bug 718107.
+ this.startFade(this.clickToPlay, shouldShow && !this.isAudioOnly &&
+ this.video.currentTime == 0 && !this.hasError(), true);
+ this.startFade(this.controlBar, shouldShow, true);
+ },
+
+ get dynamicControls() {
+ // Don't fade controls for <audio> elements.
+ var enabled = !this.isAudioOnly;
+
+ // Allow tests to explicitly suppress the fading of controls.
+ if (this.video.hasAttribute("mozNoDynamicControls"))
+ enabled = false;
+
+ // If the video hits an error, suppress controls if it
+ // hasn't managed to do anything else yet.
+ if (!this.firstFrameShown && this.hasError())
+ enabled = false;
+
+ return enabled;
+ },
+
+ updateVolumeControls() {
+ var volume = this.video.muted ? 0 : this.video.volume;
+ var volumePercentage = Math.round(volume * 100);
+ this.updateMuteButtonState();
+ this.volumeControl.value = volumePercentage;
+ this.volumeForeground.style.paddingRight = (1 - volume) * this._volumeControlWidth + "px";
+ },
+
+ handleEvent : function (aEvent) {
+ this.log("Got media event ----> " + aEvent.type);
+
+ // If the binding is detached (or has been replaced by a
+ // newer instance of the binding), nuke our event-listeners.
+ if (this.videocontrols.randomID != this.randomID) {
+ this.terminateEventListeners();
+ return;
+ }
+
+ switch (aEvent.type) {
+ case "play":
+ this.setPlayButtonState(false);
+ this.setupStatusFader();
+ if (!this._triggeredByControls && this.dynamicControls && this.videocontrols.isTouchControl)
+ this.startFadeOut(this.controlBar);
+ if (!this._triggeredByControls)
+ this.clickToPlay.hidden = true;
+ this._triggeredByControls = false;
+ break;
+ case "pause":
+ // Little white lie: if we've internally paused the video
+ // while dragging the scrubber, don't change the button state.
+ if (!this.scrubber.isDragging)
+ this.setPlayButtonState(true);
+ this.setupStatusFader();
+ break;
+ case "ended":
+ this.setPlayButtonState(true);
+ // We throttle timechange events, so the thumb might not be
+ // exactly at the end when the video finishes.
+ this.showPosition(Math.round(this.video.currentTime * 1000),
+ Math.round(this.video.duration * 1000));
+ this.startFadeIn(this.controlBar);
+ this.setupStatusFader();
+ break;
+ case "volumechange":
+ this.updateVolumeControls();
+ // Show the controls to highlight the changing volume,
+ // but only if the click-to-play overlay has already
+ // been hidden (we don't hide controls when the overlay is visible).
+ if (this.clickToPlay.hidden && !this.isAudioOnly) {
+ this.startFadeIn(this.controlBar);
+ clearTimeout(this._hideControlsTimeout);
+ this._hideControlsTimeout = setTimeout(this._hideControlsFn, this.HIDE_CONTROLS_TIMEOUT_MS);
+ }
+ break;
+ case "loadedmetadata":
+ this.adjustControlSize();
+ // If a <video> doesn't have any video data, treat it as <audio>
+ // and show the controls (they won't fade back out)
+ if (this.video instanceof HTMLVideoElement &&
+ (this.video.videoWidth == 0 || this.video.videoHeight == 0)) {
+ this.isAudioOnly = true;
+ this.clickToPlay.hidden = true;
+ this.startFadeIn(this.controlBar);
+ this.setFullscreenButtonState();
+ }
+ this.showDuration(Math.round(this.video.duration * 1000));
+ if (!this.isAudioOnly && !this.video.mozHasAudio) {
+ this.muteButton.setAttribute("noAudio", "true");
+ this.muteButton.setAttribute("disabled", "true");
+ }
+ break;
+ case "loadeddata":
+ this.firstFrameShown = true;
+ this.setupStatusFader();
+ break;
+ case "loadstart":
+ this.maxCurrentTimeSeen = 0;
+ this.controlsSpacer.removeAttribute("aria-label");
+ this.statusOverlay.removeAttribute("error");
+ this.statusIcon.setAttribute("type", "throbber");
+ this.isAudioOnly = (this.video instanceof HTMLAudioElement);
+ this.setPlayButtonState(true);
+ this.setupNewLoadState();
+ this.setupStatusFader();
+ break;
+ case "progress":
+ this.statusIcon.removeAttribute("stalled");
+ this.showBuffered();
+ this.setupStatusFader();
+ break;
+ case "stalled":
+ this.statusIcon.setAttribute("stalled", "true");
+ this.statusIcon.setAttribute("type", "throbber");
+ this.setupStatusFader();
+ break;
+ case "suspend":
+ this.setupStatusFader();
+ break;
+ case "timeupdate":
+ var currentTime = Math.round(this.video.currentTime * 1000); // in ms
+ var duration = Math.round(this.video.duration * 1000); // in ms
+
+ // If playing/seeking after the video ended, we won't get a "play"
+ // event, so update the button state here.
+ if (!this.video.paused)
+ this.setPlayButtonState(false);
+
+ this.timeUpdateCount++;
+ // Whether we show the statusOverlay sometimes depends
+ // on whether we've seen more than one timeupdate
+ // event (if we haven't, there hasn't been any
+ // "playback activity" and we may wish to show the
+ // statusOverlay while we wait for HAVE_ENOUGH_DATA).
+ // If we've seen more than 2 timeupdate events,
+ // the count is no longer relevant to setupStatusFader.
+ if (this.timeUpdateCount <= 2)
+ this.setupStatusFader();
+
+ // If the user is dragging the scrubber ignore the delayed seek
+ // responses (don't yank the thumb away from the user)
+ if (this.scrubber.isDragging)
+ return;
+
+ this.showPosition(currentTime, duration);
+ break;
+ case "emptied":
+ this.bufferBar.value = 0;
+ this.showPosition(0, 0);
+ break;
+ case "seeking":
+ this.showBuffered();
+ this.statusIcon.setAttribute("type", "throbber");
+ this.setupStatusFader();
+ break;
+ case "waiting":
+ this.statusIcon.setAttribute("type", "throbber");
+ this.setupStatusFader();
+ break;
+ case "seeked":
+ case "playing":
+ case "canplay":
+ case "canplaythrough":
+ this.setupStatusFader();
+ break;
+ case "error":
+ // We'll show the error status icon when we receive an error event
+ // under either of the following conditions:
+ // 1. The video has its error attribute set; this means we're loading
+ // from our src attribute, and the load failed, or we we're loading
+ // from source children and the decode or playback failed after we
+ // determined our selected resource was playable.
+ // 2. The video's networkState is NETWORK_NO_SOURCE. This means we we're
+ // loading from child source elements, but we were unable to select
+ // any of the child elements for playback during resource selection.
+ if (this.hasError()) {
+ this.suppressError = false;
+ this.clickToPlay.hidden = true;
+ this.statusIcon.setAttribute("type", "error");
+ this.updateErrorText();
+ this.setupStatusFader(true);
+ // If video hasn't shown anything yet, disable the controls.
+ if (!this.firstFrameShown)
+ this.startFadeOut(this.controlBar);
+ this.controlsSpacer.removeAttribute("hideCursor");
+ }
+ break;
+ case "mozinterruptbegin":
+ case "mozinterruptend":
+ // Nothing to do...
+ break;
+ default:
+ this.log("!!! event " + aEvent.type + " not handled!");
+ }
+ },
+
+ terminateEventListeners : function () {
+ if (this.videoEvents) {
+ for (let event of this.videoEvents) {
+ this.video.removeEventListener(event, this, {
+ capture: true,
+ mozSystemGroup: true
+ });
+ }
+ }
+
+ if (this.controlListeners) {
+ for (let element of this.controlListeners) {
+ element.item.removeEventListener(element.event, element.func,
+ { mozSystemGroup: true });
+ }
+
+ delete this.controlListeners;
+ }
+
+ this.log("--- videocontrols terminated ---");
+ },
+
+ hasError : function () {
+ // We either have an explicit error, or the resource selection
+ // algorithm is running and we've tried to load something and failed.
+ // Note: we don't consider the case where we've tried to load but
+ // there's no sources to load as an error condition, as sites may
+ // do this intentionally to work around requires-user-interaction to
+ // play restrictions, and we don't want to display a debug message
+ // if that's the case.
+ return this.video.error != null ||
+ (this.video.networkState == this.video.NETWORK_NO_SOURCE &&
+ this.hasSources());
+ },
+
+ hasSources : function() {
+ if (this.video.hasAttribute('src') &&
+ this.video.getAttribute('src') !== "") {
+ return true;
+ }
+ for (var child = this.video.firstChild;
+ child !== null;
+ child = child.nextElementSibling) {
+ if (child instanceof HTMLSourceElement) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ updateErrorText : function () {
+ let error;
+ let v = this.video;
+ // It is possible to have both v.networkState == NETWORK_NO_SOURCE
+ // as well as v.error being non-null. In this case, we will show
+ // the v.error.code instead of the v.networkState error.
+ if (v.error) {
+ switch (v.error.code) {
+ case v.error.MEDIA_ERR_ABORTED:
+ error = "errorAborted";
+ break;
+ case v.error.MEDIA_ERR_NETWORK:
+ error = "errorNetwork";
+ break;
+ case v.error.MEDIA_ERR_DECODE:
+ error = "errorDecode";
+ break;
+ case v.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
+ error = "errorSrcNotSupported";
+ break;
+ default:
+ error = "errorGeneric";
+ break;
+ }
+ } else if (v.networkState == v.NETWORK_NO_SOURCE) {
+ error = "errorNoSource";
+ } else {
+ return; // No error found.
+ }
+
+ let label = document.getAnonymousElementByAttribute(this.videocontrols, "anonid", error);
+ this.controlsSpacer.setAttribute("aria-label", label.textContent);
+ this.statusOverlay.setAttribute("error", error);
+ },
+
+ formatTime : function(aTime) {
+ // Format the duration as "h:mm:ss" or "m:ss"
+ aTime = Math.round(aTime / 1000);
+ let hours = Math.floor(aTime / 3600);
+ let mins = Math.floor((aTime % 3600) / 60);
+ let secs = Math.floor(aTime % 60);
+ let timeString;
+ if (secs < 10)
+ secs = "0" + secs;
+ if (hours) {
+ if (mins < 10)
+ mins = "0" + mins;
+ timeString = hours + ":" + mins + ":" + secs;
+ } else {
+ timeString = mins + ":" + secs;
+ }
+ return timeString;
+ },
+
+ showDuration : function (duration) {
+ let isInfinite = (duration == Infinity);
+ this.log("Duration is " + duration + "ms.\n");
+
+ if (isNaN(duration) || isInfinite)
+ duration = this.maxCurrentTimeSeen;
+
+ // Format the duration as "h:mm:ss" or "m:ss"
+ let timeString = isInfinite ? "" : this.formatTime(duration);
+ this.durationLabel.setAttribute("value", timeString);
+
+ // "durationValue" property is used by scale binding to
+ // generate accessible name.
+ this.scrubber.durationValue = timeString;
+
+ // If the duration is over an hour, thumb should show h:mm:ss instead of mm:ss
+ this.scrubberThumb.showHours = (duration >= 3600000);
+
+ this.scrubber.max = duration;
+ // XXX Can't set increment here, due to bug 473103. Also, doing so causes
+ // snapping when dragging with the mouse, so we can't just set a value for
+ // the arrow-keys.
+ this.scrubber.pageIncrement = Math.round(duration / 10);
+ },
+
+ seekToPosition : function(newPosition) {
+ newPosition /= 1000; // convert from ms
+ this.log("+++ seeking to " + newPosition);
+ if (this.videocontrols.isGonk) {
+ // We use fastSeek() on B2G, and an accurate (but slower)
+ // seek on other platforms (that are likely to be higher
+ // perf).
+ this.video.fastSeek(newPosition);
+ } else {
+ this.video.currentTime = newPosition;
+ }
+ },
+
+ setVolume : function(newVolume) {
+ this.log("*** setting volume to " + newVolume);
+ this.video.volume = newVolume;
+ this.video.muted = false;
+ },
+
+ showPosition : function(currentTime, duration) {
+ // If the duration is unknown (because the server didn't provide
+ // it, or the video is a stream), then we want to fudge the duration
+ // by using the maximum playback position that's been seen.
+ if (currentTime > this.maxCurrentTimeSeen)
+ this.maxCurrentTimeSeen = currentTime;
+ this.showDuration(duration);
+
+ this.log("time update @ " + currentTime + "ms of " + duration + "ms");
+
+ this.positionLabel.setAttribute("value", this.formatTime(currentTime));
+ this.scrubber.value = currentTime;
+ },
+
+ showBuffered : function() {
+ function bsearch(haystack, needle, cmp) {
+ var length = haystack.length;
+ var low = 0;
+ var high = length;
+ while (low < high) {
+ var probe = low + ((high - low) >> 1);
+ var r = cmp(haystack, probe, needle);
+ if (r == 0) {
+ return probe;
+ } else if (r > 0) {
+ low = probe + 1;
+ } else {
+ high = probe;
+ }
+ }
+ return -1;
+ }
+
+ function bufferedCompare(buffered, i, time) {
+ if (time > buffered.end(i)) {
+ return 1;
+ } else if (time >= buffered.start(i)) {
+ return 0;
+ }
+ return -1;
+ }
+
+ var duration = Math.round(this.video.duration * 1000);
+ if (isNaN(duration))
+ duration = this.maxCurrentTimeSeen;
+
+ // Find the range that the current play position is in and use that
+ // range for bufferBar. At some point we may support multiple ranges
+ // displayed in the bar.
+ var currentTime = this.video.currentTime;
+ var buffered = this.video.buffered;
+ var index = bsearch(buffered, currentTime, bufferedCompare);
+ var endTime = 0;
+ if (index >= 0) {
+ endTime = Math.round(buffered.end(index) * 1000);
+ }
+ this.bufferBar.max = duration;
+ this.bufferBar.value = endTime;
+ },
+
+ _controlsHiddenByTimeout : false,
+ _showControlsTimeout : 0,
+ SHOW_CONTROLS_TIMEOUT_MS: 500,
+ _showControlsFn : function () {
+ if (Utils.video.matches("video:hover")) {
+ Utils.startFadeIn(Utils.controlBar, false);
+ Utils._showControlsTimeout = 0;
+ Utils._controlsHiddenByTimeout = false;
+ }
+ },
+
+ _hideControlsTimeout : 0,
+ _hideControlsFn : function () {
+ if (!Utils.scrubber.isDragging) {
+ Utils.startFade(Utils.controlBar, false);
+ Utils._hideControlsTimeout = 0;
+ Utils._controlsHiddenByTimeout = true;
+ }
+ },
+ HIDE_CONTROLS_TIMEOUT_MS : 2000,
+ onMouseMove : function (event) {
+ // Pause playing video when the mouse is dragging over the control bar.
+ if (this.scrubber.isDragging) {
+ this.scrubber.pauseVideoDuringDragging();
+ }
+
+ // If the controls are static, don't change anything.
+ if (!this.dynamicControls)
+ return;
+
+ clearTimeout(this._hideControlsTimeout);
+
+ // Suppress fading out the controls until the video has rendered
+ // its first frame. But since autoplay videos start off with no
+ // controls, let them fade-out so the controls don't get stuck on.
+ if (!this.firstFrameShown &&
+ !(this.video.autoplay && this.video.mozAutoplayEnabled))
+ return;
+
+ if (this._controlsHiddenByTimeout)
+ this._showControlsTimeout = setTimeout(this._showControlsFn, this.SHOW_CONTROLS_TIMEOUT_MS);
+ else
+ this.startFade(this.controlBar, true);
+
+ // Hide the controls if the mouse cursor is left on top of the video
+ // but above the control bar and if the click-to-play overlay is hidden.
+ if ((this._controlsHiddenByTimeout ||
+ event.clientY < this.controlBar.getBoundingClientRect().top) &&
+ this.clickToPlay.hidden) {
+ this._hideControlsTimeout = setTimeout(this._hideControlsFn, this.HIDE_CONTROLS_TIMEOUT_MS);
+ }
+ },
+
+ onMouseInOut : function (event) {
+ // If the controls are static, don't change anything.
+ if (!this.dynamicControls)
+ return;
+
+ clearTimeout(this._hideControlsTimeout);
+
+ // Ignore events caused by transitions between child nodes.
+ // Note that the videocontrols element is the same
+ // size as the *content area* of the video element,
+ // but this is not the same as the video element's
+ // border area if the video has border or padding.
+ if (this.isEventWithin(event, this.videocontrols))
+ return;
+
+ var isMouseOver = (event.type == "mouseover");
+
+ var controlRect = this.controlBar.getBoundingClientRect();
+ var isMouseInControls = event.clientY > controlRect.top &&
+ event.clientY < controlRect.bottom &&
+ event.clientX > controlRect.left &&
+ event.clientX < controlRect.right;
+
+ // Suppress fading out the controls until the video has rendered
+ // its first frame. But since autoplay videos start off with no
+ // controls, let them fade-out so the controls don't get stuck on.
+ if (!this.firstFrameShown && !isMouseOver &&
+ !(this.video.autoplay && this.video.mozAutoplayEnabled))
+ return;
+
+ if (!isMouseOver && !isMouseInControls) {
+ this.adjustControlSize();
+
+ // Keep the controls visible if the click-to-play is visible.
+ if (!this.clickToPlay.hidden)
+ return;
+
+ this.startFadeOut(this.controlBar, false);
+ this.textTrackList.setAttribute("hidden", "true");
+ clearTimeout(this._showControlsTimeout);
+ Utils._controlsHiddenByTimeout = false;
+ }
+ },
+
+ startFadeIn : function (element, immediate) {
+ this.startFade(element, true, immediate);
+ },
+
+ startFadeOut : function (element, immediate) {
+ this.startFade(element, false, immediate);
+ },
+
+ startFade : function (element, fadeIn, immediate) {
+ if (element.classList.contains("controlBar") && fadeIn) {
+ // Bug 493523, the scrubber doesn't call valueChanged while hidden,
+ // so our dependent state (eg, timestamp in the thumb) will be stale.
+ // As a workaround, update it manually when it first becomes unhidden.
+ if (element.hidden)
+ this.scrubber.valueChanged("curpos", this.video.currentTime * 1000, false);
+ }
+
+ if (immediate)
+ element.setAttribute("immediate", true);
+ else
+ element.removeAttribute("immediate");
+
+ if (fadeIn) {
+ element.hidden = false;
+ // force style resolution, so that transition begins
+ // when we remove the attribute.
+ element.clientTop;
+ element.removeAttribute("fadeout");
+ if (element.classList.contains("controlBar"))
+ this.controlsSpacer.removeAttribute("hideCursor");
+ } else {
+ element.setAttribute("fadeout", true);
+ if (element.classList.contains("controlBar") && !this.hasError() &&
+ document.mozFullScreenElement == this.video)
+ this.controlsSpacer.setAttribute("hideCursor", true);
+
+ }
+ },
+
+ onTransitionEnd : function (event) {
+ // Ignore events for things other than opacity changes.
+ if (event.propertyName != "opacity")
+ return;
+
+ var element = event.originalTarget;
+
+ // Nothing to do when a fade *in* finishes.
+ if (!element.hasAttribute("fadeout"))
+ return;
+
+ this.scrubber.dragStateChanged(false);
+ element.hidden = true;
+ },
+
+ _triggeredByControls: false,
+
+ startPlay : function () {
+ this._triggeredByControls = true;
+ this.hideClickToPlay();
+ this.video.play();
+ },
+
+ togglePause : function () {
+ if (this.video.paused || this.video.ended) {
+ this.startPlay();
+ } else {
+ this.video.pause();
+ }
+
+ // We'll handle style changes in the event listener for
+ // the "play" and "pause" events, same as if content
+ // script was controlling video playback.
+ },
+
+ isVideoWithoutAudioTrack : function() {
+ return this.video.readyState >= this.video.HAVE_METADATA &&
+ !this.isAudioOnly &&
+ !this.video.mozHasAudio;
+ },
+
+ toggleMute : function () {
+ if (this.isVideoWithoutAudioTrack()) {
+ return;
+ }
+ this.video.muted = !this.isEffectivelyMuted();
+ if (this.video.volume === 0) {
+ this.video.volume = 0.5;
+ }
+
+ // We'll handle style changes in the event listener for
+ // the "volumechange" event, same as if content script was
+ // controlling volume.
+ },
+
+ isVideoInFullScreen : function () {
+ return document.mozFullScreenElement == this.video;
+ },
+
+ toggleFullscreen : function () {
+ this.isVideoInFullScreen() ?
+ document.mozCancelFullScreen() :
+ this.video.mozRequestFullScreen();
+ },
+
+ setFullscreenButtonState : function () {
+ if (this.isAudioOnly || !document.mozFullScreenEnabled) {
+ this.controlBar.setAttribute("fullscreen-unavailable", true);
+ this.adjustControlSize();
+ return;
+ }
+ this.controlBar.removeAttribute("fullscreen-unavailable");
+ this.adjustControlSize();
+
+ var attrName = this.isVideoInFullScreen() ? "exitfullscreenlabel" : "enterfullscreenlabel";
+ var value = this.fullscreenButton.getAttribute(attrName);
+ this.fullscreenButton.setAttribute("aria-label", value);
+
+ if (this.isVideoInFullScreen())
+ this.fullscreenButton.setAttribute("fullscreened", "true");
+ else
+ this.fullscreenButton.removeAttribute("fullscreened");
+ },
+
+ onFullscreenChange: function () {
+ if (this.isVideoInFullScreen()) {
+ Utils._hideControlsTimeout = setTimeout(this._hideControlsFn, this.HIDE_CONTROLS_TIMEOUT_MS);
+ }
+ this.setFullscreenButtonState();
+ },
+
+ clickToPlayClickHandler : function(e) {
+ if (e.button != 0)
+ return;
+ if (this.hasError() && !this.suppressError) {
+ // Errors that can be dismissed should be placed here as we discover them.
+ if (this.video.error.code != this.video.error.MEDIA_ERR_ABORTED)
+ return;
+ this.statusOverlay.hidden = true;
+ this.suppressError = true;
+ return;
+ }
+ if (e.defaultPrevented)
+ return;
+ if (this.playButton.hasAttribute("paused")) {
+ this.startPlay();
+ } else {
+ this.video.pause();
+ }
+ },
+ hideClickToPlay : function () {
+ let videoHeight = this.video.clientHeight;
+ let videoWidth = this.video.clientWidth;
+
+ // The play button will animate to 3x its size. This
+ // shows the animation unless the video is too small
+ // to show 2/3 of the animation.
+ let animationScale = 2;
+ if (this._overlayPlayButtonHeight * animationScale > (videoHeight - this._controlBarHeight)||
+ this._overlayPlayButtonWidth * animationScale > videoWidth) {
+ this.clickToPlay.setAttribute("immediate", "true");
+ this.clickToPlay.hidden = true;
+ } else {
+ this.clickToPlay.removeAttribute("immediate");
+ }
+ this.clickToPlay.setAttribute("fadeout", "true");
+ },
+
+ setPlayButtonState : function(aPaused) {
+ if (aPaused)
+ this.playButton.setAttribute("paused", "true");
+ else
+ this.playButton.removeAttribute("paused");
+
+ var attrName = aPaused ? "playlabel" : "pauselabel";
+ var value = this.playButton.getAttribute(attrName);
+ this.playButton.setAttribute("aria-label", value);
+ },
+
+ isEffectivelyMuted : function() {
+ return this.video.muted || !this.video.volume;
+ },
+
+ updateMuteButtonState : function() {
+ var muted = this.isEffectivelyMuted();
+
+ if (muted)
+ this.muteButton.setAttribute("muted", "true");
+ else
+ this.muteButton.removeAttribute("muted");
+
+ var attrName = muted ? "unmutelabel" : "mutelabel";
+ var value = this.muteButton.getAttribute(attrName);
+ this.muteButton.setAttribute("aria-label", value);
+ },
+
+ _getComputedPropertyValueAsInt : function(element, property) {
+ let value = getComputedStyle(element, null).getPropertyValue(property);
+ return parseInt(value, 10);
+ },
+
+ keyHandler : function(event) {
+ // Ignore keys when content might be providing its own.
+ if (!this.video.hasAttribute("controls"))
+ return;
+
+ var keystroke = "";
+ if (event.altKey)
+ keystroke += "alt-";
+ if (event.shiftKey)
+ keystroke += "shift-";
+ if (navigator.platform.startsWith("Mac")) {
+ if (event.metaKey)
+ keystroke += "accel-";
+ if (event.ctrlKey)
+ keystroke += "control-";
+ } else {
+ if (event.metaKey)
+ keystroke += "meta-";
+ if (event.ctrlKey)
+ keystroke += "accel-";
+ }
+ switch (event.keyCode) {
+ case KeyEvent.DOM_VK_UP:
+ keystroke += "upArrow";
+ break;
+ case KeyEvent.DOM_VK_DOWN:
+ keystroke += "downArrow";
+ break;
+ case KeyEvent.DOM_VK_LEFT:
+ keystroke += "leftArrow";
+ break;
+ case KeyEvent.DOM_VK_RIGHT:
+ keystroke += "rightArrow";
+ break;
+ case KeyEvent.DOM_VK_HOME:
+ keystroke += "home";
+ break;
+ case KeyEvent.DOM_VK_END:
+ keystroke += "end";
+ break;
+ }
+
+ if (String.fromCharCode(event.charCode) == ' ')
+ keystroke += "space";
+
+ this.log("Got keystroke: " + keystroke);
+ var oldval, newval;
+
+ try {
+ switch (keystroke) {
+ case "space": /* Play */
+ this.togglePause();
+ break;
+ case "downArrow": /* Volume decrease */
+ oldval = this.video.volume;
+ this.video.volume = (oldval < 0.1 ? 0 : oldval - 0.1);
+ this.video.muted = false;
+ break;
+ case "upArrow": /* Volume increase */
+ oldval = this.video.volume;
+ this.video.volume = (oldval > 0.9 ? 1 : oldval + 0.1);
+ this.video.muted = false;
+ break;
+ case "accel-downArrow": /* Mute */
+ this.video.muted = true;
+ break;
+ case "accel-upArrow": /* Unmute */
+ this.video.muted = false;
+ break;
+ case "leftArrow": /* Seek back 15 seconds */
+ case "accel-leftArrow": /* Seek back 10% */
+ oldval = this.video.currentTime;
+ if (keystroke == "leftArrow")
+ newval = oldval - 15;
+ else
+ newval = oldval - (this.video.duration || this.maxCurrentTimeSeen / 1000) / 10;
+ this.video.currentTime = (newval >= 0 ? newval : 0);
+ break;
+ case "rightArrow": /* Seek forward 15 seconds */
+ case "accel-rightArrow": /* Seek forward 10% */
+ oldval = this.video.currentTime;
+ var maxtime = (this.video.duration || this.maxCurrentTimeSeen / 1000);
+ if (keystroke == "rightArrow")
+ newval = oldval + 15;
+ else
+ newval = oldval + maxtime / 10;
+ this.video.currentTime = (newval <= maxtime ? newval : maxtime);
+ break;
+ case "home": /* Seek to beginning */
+ this.video.currentTime = 0;
+ break;
+ case "end": /* Seek to end */
+ if (this.video.currentTime != this.video.duration)
+ this.video.currentTime = (this.video.duration || this.maxCurrentTimeSeen / 1000);
+ break;
+ default:
+ return;
+ }
+ } catch (e) { /* ignore any exception from setting .currentTime */ }
+
+ event.preventDefault(); // Prevent page scrolling
+ },
+
+ isSupportedTextTrack : function(textTrack) {
+ return textTrack.kind == "subtitles" ||
+ textTrack.kind == "captions";
+ },
+
+ get overlayableTextTracks() {
+ return Array.prototype.filter.call(this.video.textTracks, this.isSupportedTextTrack);
+ },
+
+ isClosedCaptionOn : function () {
+ for (let tt of this.overlayableTextTracks) {
+ if (tt.mode === "showing") {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ setClosedCaptionButtonState : function () {
+ if (!this.overlayableTextTracks.length || this.videocontrols.isTouchControl) {
+ this.closedCaptionButton.setAttribute("hidden", "true");
+ return;
+ }
+
+ this.closedCaptionButton.removeAttribute("hidden");
+
+ if (this.isClosedCaptionOn()) {
+ this.closedCaptionButton.setAttribute("enabled", "true");
+ } else {
+ this.closedCaptionButton.removeAttribute("enabled");
+ }
+
+ let ttItems = this.textTrackList.childNodes;
+
+ for (let tti of ttItems) {
+ const idx = +tti.getAttribute("index");
+
+ if (idx == this.currentTextTrackIndex) {
+ tti.setAttribute("on", "true");
+ } else {
+ tti.removeAttribute("on");
+ }
+ }
+ },
+
+ addNewTextTrack : function (tt) {
+ if (!this.isSupportedTextTrack(tt)) {
+ return;
+ }
+
+ if (tt.index && tt.index < this.textTracksCount) {
+ // Don't create items for initialized tracks. However, we
+ // still need to care about mode since TextTrackManager would
+ // turn on the first available track automatically.
+ if (tt.mode === "showing") {
+ this.changeTextTrack(tt.index);
+ }
+ return;
+ }
+
+ tt.index = this.textTracksCount++;
+
+ const label = tt.label || "";
+ const ttText = document.createTextNode(label);
+ const ttBtn = document.createElement("button");
+
+ ttBtn.classList.add("textTrackItem");
+ ttBtn.setAttribute("index", tt.index);
+
+ ttBtn.addEventListener("click", function (event) {
+ event.stopPropagation();
+
+ this.changeTextTrack(tt.index);
+ }.bind(this));
+
+ ttBtn.appendChild(ttText);
+
+ this.textTrackList.appendChild(ttBtn);
+
+ if (tt.mode === "showing" && tt.index) {
+ this.changeTextTrack(tt.index);
+ }
+ },
+
+ changeTextTrack : function (index) {
+ for (let tt of this.overlayableTextTracks) {
+ if (tt.index === index) {
+ tt.mode = "showing";
+
+ this.currentTextTrackIndex = tt.index;
+ } else {
+ tt.mode = "disabled";
+ }
+ }
+
+ // should fallback to off
+ if (this.currentTextTrackIndex !== index) {
+ this.currentTextTrackIndex = 0;
+ }
+
+ this.textTrackList.setAttribute("hidden", "true");
+ this.setClosedCaptionButtonState();
+ },
+
+ onControlBarTransitioned : function () {
+ this.textTrackList.setAttribute("hidden", "true");
+ this.video.dispatchEvent(new CustomEvent("controlbarchange"));
+ },
+
+ toggleClosedCaption : function () {
+ if (this.overlayableTextTracks.length === 1) {
+ const lastTTIdx = this.overlayableTextTracks[0].index;
+ this.changeTextTrack(this.isClosedCaptionOn() ? 0 : lastTTIdx);
+ return;
+ }
+
+ if (this.textTrackList.hasAttribute("hidden")) {
+ this.textTrackList.removeAttribute("hidden");
+ } else {
+ this.textTrackList.setAttribute("hidden", "true");
+ }
+
+ let maxButtonWidth = 0;
+
+ for (let tti of this.textTrackList.childNodes) {
+ if (tti.clientWidth > maxButtonWidth) {
+ maxButtonWidth = tti.clientWidth;
+ }
+ }
+
+ if (maxButtonWidth > this.video.clientWidth) {
+ maxButtonWidth = this.video.clientWidth;
+ }
+
+ for (let tti of this.textTrackList.childNodes) {
+ tti.style.width = maxButtonWidth + "px";
+ }
+ },
+
+ onTextTrackAdd : function (trackEvent) {
+ this.addNewTextTrack(trackEvent.track);
+ this.setClosedCaptionButtonState();
+ },
+
+ onTextTrackRemove : function (trackEvent) {
+ const toRemoveIndex = trackEvent.track.index;
+ const ttItems = this.textTrackList.childNodes;
+
+ if (!ttItems) {
+ return;
+ }
+
+ for (let tti of ttItems) {
+ const idx = +tti.getAttribute("index");
+
+ if (idx === toRemoveIndex) {
+ tti.remove();
+ this.textTracksCount--;
+ }
+
+ if (idx === this.currentTextTrackIndex) {
+ this.currentTextTrackIndex = 0;
+
+ this.video.dispatchEvent(new CustomEvent("texttrackchange"));
+ }
+ }
+
+ this.setClosedCaptionButtonState();
+ },
+
+ initTextTracks : function () {
+ // add 'off' button anyway as new text track might be
+ // dynamically added after initialization.
+ const offLabel = this.textTrackList.getAttribute("offlabel");
+
+ this.addNewTextTrack({
+ label: offLabel,
+ kind: "subtitles"
+ });
+
+ for (let tt of this.overlayableTextTracks) {
+ this.addNewTextTrack(tt);
+ }
+
+ this.setClosedCaptionButtonState();
+ },
+
+ isEventWithin : function (event, parent1, parent2) {
+ function isDescendant (node) {
+ while (node) {
+ if (node == parent1 || node == parent2)
+ return true;
+ node = node.parentNode;
+ }
+ return false;
+ }
+ return isDescendant(event.target) && isDescendant(event.relatedTarget);
+ },
+
+ log : function (msg) {
+ if (this.debug)
+ console.log("videoctl: " + msg + "\n");
+ },
+
+ get isTopLevelSyntheticDocument() {
+ let doc = this.video.ownerDocument;
+ let win = doc.defaultView;
+ return doc.mozSyntheticDocument && win === win.top;
+ },
+
+ _playButtonWidth : 0,
+ _durationLabelWidth : 0,
+ _muteButtonWidth : 0,
+ _volumeControlWidth : 0,
+ _closedCaptionButtonWidth : 0,
+ _fullscreenButtonWidth : 0,
+ _controlBarHeight : 0,
+ _overlayPlayButtonHeight : 64,
+ _overlayPlayButtonWidth : 64,
+ _controlBarPaddingEnd: 8,
+ adjustControlSize : function adjustControlSize() {
+ let doc = this.video.ownerDocument;
+
+ // The scrubber has |flex=1|, therefore |minScrubberWidth|
+ // was generated by empirical testing.
+ let minScrubberWidth = 25;
+ let minWidthAllControls = this._playButtonWidth +
+ minScrubberWidth +
+ this._durationLabelWidth +
+ this._muteButtonWidth +
+ this._volumeControlWidth +
+ this._closedCaptionButtonWidth +
+ this._fullscreenButtonWidth;
+
+ let isFullscreenUnavailable = this.controlBar.hasAttribute("fullscreen-unavailable");
+ if (isFullscreenUnavailable) {
+ // When the fullscreen button is hidden we add margin-end to the volume stack.
+ minWidthAllControls -= this._fullscreenButtonWidth - this._controlBarPaddingEnd;
+ }
+
+ let minHeightForControlBar = this._controlBarHeight;
+ let minWidthOnlyPlayPause = this._playButtonWidth + this._muteButtonWidth;
+
+ let isAudioOnly = this.isAudioOnly;
+ let videoHeight = isAudioOnly ? minHeightForControlBar : this.video.clientHeight;
+ let videoWidth = isAudioOnly ? minWidthAllControls : this.video.clientWidth;
+
+ // Adapt the size of the controls to the size of the video
+ if (this.video.readyState >= this.video.HAVE_METADATA) {
+ if (!this.isAudioOnly && this.video.videoWidth && this.video.videoHeight) {
+ var rect = this.video.getBoundingClientRect();
+ var widthRatio = rect.width / this.video.videoWidth;
+ var heightRatio = rect.height / this.video.videoHeight;
+ var width = this.video.videoWidth * Math.min(widthRatio, heightRatio);
+
+ this.controlsOverlay.setAttribute("scaled", true);
+ this.controlsOverlay.style.width = width + "px";
+ this.controlsSpacer.style.width = width + "px";
+ this.controlBar.style.width = width + "px";
+ } else {
+ this.controlsOverlay.removeAttribute("scaled");
+ this.controlsOverlay.style.width = "";
+ this.controlsSpacer.style.width = "";
+ this.controlBar.style.width = "";
+ }
+ }
+
+ if ((this._overlayPlayButtonHeight + this._controlBarHeight) > videoHeight ||
+ this._overlayPlayButtonWidth > videoWidth) {
+ this.clickToPlay.hidden = true;
+ } else if (this.clickToPlay.hidden &&
+ !this.video.played.length &&
+ this.video.paused) {
+ // Check this.video.paused to handle when a video is
+ // playing but hasn't processed any frames yet
+ this.clickToPlay.hidden = false;
+ }
+
+ let size = "normal";
+ if (videoHeight < minHeightForControlBar)
+ size = "hidden";
+ else if (videoWidth < minWidthOnlyPlayPause)
+ size = "hidden";
+ else if (videoWidth < minWidthAllControls)
+ size = "small";
+ this.controlBar.setAttribute("size", size);
+ },
+
+ init : function (binding) {
+ this.video = binding.parentNode;
+ this.videocontrols = binding;
+
+ this.statusIcon = document.getAnonymousElementByAttribute(binding, "class", "statusIcon");
+ this.controlBar = document.getAnonymousElementByAttribute(binding, "class", "controlBar");
+ this.playButton = document.getAnonymousElementByAttribute(binding, "class", "playButton");
+ this.muteButton = document.getAnonymousElementByAttribute(binding, "class", "muteButton");
+ this.volumeControl = document.getAnonymousElementByAttribute(binding, "class", "volumeControl");
+ this.progressBar = document.getAnonymousElementByAttribute(binding, "class", "progressBar");
+ this.bufferBar = document.getAnonymousElementByAttribute(binding, "class", "bufferBar");
+ this.scrubber = document.getAnonymousElementByAttribute(binding, "class", "scrubber");
+ this.scrubberThumb = document.getAnonymousElementByAttribute(this.scrubber, "class", "scale-thumb");
+ this.durationLabel = document.getAnonymousElementByAttribute(binding, "class", "durationLabel");
+ this.positionLabel = document.getAnonymousElementByAttribute(binding, "class", "positionLabel");
+ this.statusOverlay = document.getAnonymousElementByAttribute(binding, "class", "statusOverlay");
+ this.controlsOverlay = document.getAnonymousElementByAttribute(binding, "class", "controlsOverlay");
+ this.controlsSpacer = document.getAnonymousElementByAttribute(binding, "class", "controlsSpacer");
+ this.clickToPlay = document.getAnonymousElementByAttribute(binding, "class", "clickToPlay");
+ this.fullscreenButton = document.getAnonymousElementByAttribute(binding, "class", "fullscreenButton");
+ this.volumeForeground = document.getAnonymousElementByAttribute(binding, "anonid", "volumeForeground");
+ this.closedCaptionButton = document.getAnonymousElementByAttribute(binding, "class", "closedCaptionButton");
+ this.textTrackList = document.getAnonymousElementByAttribute(binding, "class", "textTrackList");
+
+ this.isAudioOnly = (this.video instanceof HTMLAudioElement);
+ this.setupInitialState();
+ this.setupNewLoadState();
+ this.initTextTracks();
+
+ // Use the handleEvent() callback for all media events.
+ // Only the "error" event listener must capture, so that it can trap error
+ // events from <source> children, which don't bubble. But we use capture
+ // for all events in order to simplify the event listener add/remove.
+ for (let event of this.videoEvents) {
+ this.video.addEventListener(event, this, {
+ capture: true,
+ mozSystemGroup: true
+ });
+ }
+
+ var self = this;
+ this.controlListeners = [];
+
+ // Helper function to add an event listener to the given element
+ function addListener(elem, eventName, func) {
+ let boundFunc = func.bind(self);
+ self.controlListeners.push({ item: elem, event: eventName, func: boundFunc });
+ elem.addEventListener(eventName, boundFunc, { mozSystemGroup: true });
+ }
+
+ addListener(this.muteButton, "command", this.toggleMute);
+ addListener(this.closedCaptionButton, "command", this.toggleClosedCaption);
+ addListener(this.playButton, "click", this.clickToPlayClickHandler);
+ addListener(this.fullscreenButton, "command", this.toggleFullscreen);
+ addListener(this.clickToPlay, "click", this.clickToPlayClickHandler);
+ addListener(this.controlsSpacer, "click", this.clickToPlayClickHandler);
+ addListener(this.controlsSpacer, "dblclick", this.toggleFullscreen);
+
+ addListener(this.videocontrols, "resizevideocontrols", this.adjustControlSize);
+ addListener(this.videocontrols, "transitionend", this.onTransitionEnd);
+ addListener(this.video.ownerDocument, "mozfullscreenchange", this.onFullscreenChange);
+ addListener(this.videocontrols, "transitionend", this.onControlBarTransitioned);
+ addListener(this.video.ownerDocument, "fullscreenchange", this.onFullscreenChange);
+ addListener(this.video, "keypress", this.keyHandler);
+ addListener(this.video.textTracks, "addtrack", this.onTextTrackAdd);
+ addListener(this.video.textTracks, "removetrack", this.onTextTrackRemove);
+
+ addListener(this.videocontrols, "dragstart", function(event) {
+ event.preventDefault(); // prevent dragging of controls image (bug 517114)
+ });
+
+ this.log("--- videocontrols initialized ---");
+ }
+ };
+ this.Utils.init(this);
+ ]]>
+ </constructor>
+ <destructor>
+ <![CDATA[
+ this.Utils.terminateEventListeners();
+ // randomID used to be a <field>, which meant that the XBL machinery
+ // undefined the property when the element was unbound. The code in
+ // this file actually depends on this, so now that randomID is an
+ // expando, we need to make sure to explicitly delete it.
+ delete this.randomID;
+ ]]>
+ </destructor>
+
+ </implementation>
+
+ <handlers>
+ <handler event="mouseover">
+ if (!this.isTouchControl)
+ this.Utils.onMouseInOut(event);
+ </handler>
+ <handler event="mouseout">
+ if (!this.isTouchControl)
+ this.Utils.onMouseInOut(event);
+ </handler>
+ <handler event="mousemove">
+ if (!this.isTouchControl)
+ this.Utils.onMouseMove(event);
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="touchControls" extends="chrome://global/content/bindings/videocontrols.xml#videoControls">
+
+ <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="mediaControlsFrame">
+ <stack flex="1">
+ <vbox flex="1" class="statusOverlay" hidden="true">
+ <box class="statusIcon"/>
+ <label class="errorLabel" anonid="errorAborted">&error.aborted;</label>
+ <label class="errorLabel" anonid="errorNetwork">&error.network;</label>
+ <label class="errorLabel" anonid="errorDecode">&error.decode;</label>
+ <label class="errorLabel" anonid="errorSrcNotSupported">&error.srcNotSupported;</label>
+ <label class="errorLabel" anonid="errorNoSource">&error.noSource2;</label>
+ <label class="errorLabel" anonid="errorGeneric">&error.generic;</label>
+ </vbox>
+
+ <vbox class="controlsOverlay">
+ <spacer class="controlsSpacer" flex="1"/>
+ <box flex="1" hidden="true">
+ <box class="clickToPlay" hidden="true" flex="1"/>
+ <vbox class="textTrackList" hidden="true" offlabel="&closedCaption.off;"></vbox>
+ </box>
+ <vbox class="controlBar" hidden="true">
+ <hbox class="buttonsBar">
+ <button class="playButton"
+ playlabel="&playButton.playLabel;"
+ pauselabel="&playButton.pauseLabel;"/>
+ <label class="positionLabel" role="presentation"/>
+ <stack class="scrubberStack">
+ <box class="backgroundBar"/>
+ <progressmeter class="flexibleBar" value="100"/>
+ <progressmeter class="bufferBar"/>
+ <progressmeter class="progressBar" max="10000"/>
+ <scale class="scrubber" movetoclick="true"/>
+ </stack>
+ <label class="durationLabel" role="presentation"/>
+ <button class="muteButton"
+ mutelabel="&muteButton.muteLabel;"
+ unmutelabel="&muteButton.unmuteLabel;"/>
+ <stack class="volumeStack">
+ <box class="volumeBackground"/>
+ <box class="volumeForeground" anonid="volumeForeground"/>
+ <scale class="volumeControl" movetoclick="true"/>
+ </stack>
+ <button class="castingButton" hidden="true"
+ aria-label="&castingButton.castingLabel;"/>
+ <button class="closedCaptionButton" hidden="true"/>
+ <button class="fullscreenButton"
+ enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
+ exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ </stack>
+ </xbl:content>
+
+ <implementation>
+
+ <constructor>
+ <![CDATA[
+ this.isTouchControl = true;
+ this.TouchUtils = {
+ videocontrols: null,
+ video: null,
+ controlsTimer: null,
+ controlsTimeout: 5000,
+ positionLabel: null,
+ castingButton: null,
+
+ get Utils() {
+ return this.videocontrols.Utils;
+ },
+
+ get visible() {
+ return !this.Utils.controlBar.hasAttribute("fadeout") &&
+ !(this.Utils.controlBar.getAttribute("hidden") == "true");
+ },
+
+ _firstShow: false,
+ get firstShow() { return this._firstShow; },
+ set firstShow(val) {
+ this._firstShow = val;
+ this.Utils.controlBar.setAttribute("firstshow", val);
+ },
+
+ toggleControls: function() {
+ if (!this.Utils.dynamicControls || !this.visible)
+ this.showControls();
+ else
+ this.delayHideControls(0);
+ },
+
+ showControls : function() {
+ if (this.Utils.dynamicControls) {
+ this.Utils.startFadeIn(this.Utils.controlBar);
+ this.delayHideControls(this.controlsTimeout);
+ }
+ },
+
+ clearTimer: function() {
+ if (this.controlsTimer) {
+ clearTimeout(this.controlsTimer);
+ this.controlsTimer = null;
+ }
+ },
+
+ delayHideControls : function(aTimeout) {
+ this.clearTimer();
+ let self = this;
+ this.controlsTimer = setTimeout(function() {
+ self.hideControls();
+ }, aTimeout);
+ },
+
+ hideControls : function() {
+ if (!this.Utils.dynamicControls)
+ return;
+ this.Utils.startFadeOut(this.Utils.controlBar);
+ if (this.firstShow)
+ this.videocontrols.addEventListener("transitionend", this, false);
+ },
+
+ handleEvent : function (aEvent) {
+ if (aEvent.type == "transitionend") {
+ this.firstShow = false;
+ this.videocontrols.removeEventListener("transitionend", this, false);
+ return;
+ }
+
+ if (this.videocontrols.randomID != this.Utils.randomID)
+ this.terminateEventListeners();
+
+ },
+
+ terminateEventListeners : function () {
+ for (var event of this.videoEvents)
+ this.Utils.video.removeEventListener(event, this, false);
+ },
+
+ isVideoCasting : function () {
+ if (this.video.mozIsCasting)
+ return true;
+ return false;
+ },
+
+ updateCasting : function (eventDetail) {
+ let castingData = JSON.parse(eventDetail);
+ if ("allow" in castingData) {
+ this.video.mozAllowCasting = !!castingData.allow;
+ }
+
+ if ("active" in castingData) {
+ this.video.mozIsCasting = !!castingData.active;
+ }
+ this.setCastButtonState();
+ },
+
+ startCasting : function () {
+ this.videocontrols.dispatchEvent(new CustomEvent("VideoBindingCast"));
+ },
+
+ setCastButtonState : function () {
+ if (this.isAudioOnly || !this.video.mozAllowCasting) {
+ this.castingButton.hidden = true;
+ return;
+ }
+
+ if (this.video.mozIsCasting) {
+ this.castingButton.setAttribute("active", "true");
+ } else {
+ this.castingButton.removeAttribute("active");
+ }
+
+ this.castingButton.hidden = false;
+ },
+
+ init : function (binding) {
+ this.videocontrols = binding;
+ this.video = binding.parentNode;
+
+ let self = this;
+ this.Utils.playButton.addEventListener("command", function() {
+ if (!self.video.paused)
+ self.delayHideControls(0);
+ else
+ self.showControls();
+ }, false);
+ this.Utils.scrubber.addEventListener("touchstart", function() {
+ self.clearTimer();
+ }, false);
+ this.Utils.scrubber.addEventListener("touchend", function() {
+ self.delayHideControls(self.controlsTimeout);
+ }, false);
+ this.Utils.muteButton.addEventListener("click", function() { self.delayHideControls(self.controlsTimeout); }, false);
+
+ this.castingButton = document.getAnonymousElementByAttribute(binding, "class", "castingButton");
+ this.castingButton.addEventListener("command", function() {
+ self.startCasting();
+ }, false);
+
+ this.video.addEventListener("media-videoCasting", function (e) {
+ if (!e.isTrusted)
+ return;
+ self.updateCasting(e.detail);
+ }, false, true);
+
+ // The first time the controls appear we want to just display
+ // a play button that does not fade away. The firstShow property
+ // makes that happen. But because of bug 718107 this init() method
+ // may be called again when we switch in or out of fullscreen
+ // mode. So we only set firstShow if we're not autoplaying and
+ // if we are at the beginning of the video and not already playing
+ if (!this.video.autoplay && this.Utils.dynamicControls && this.video.paused &&
+ this.video.currentTime === 0)
+ this.firstShow = true;
+
+ // If the video is not at the start, then we probably just
+ // transitioned into or out of fullscreen mode, and we don't want
+ // the controls to remain visible. this.controlsTimeout is a full
+ // 5s, which feels too long after the transition.
+ if (this.video.currentTime !== 0) {
+ this.delayHideControls(this.Utils.HIDE_CONTROLS_TIMEOUT_MS);
+ }
+ }
+ };
+ this.TouchUtils.init(this);
+ this.dispatchEvent(new CustomEvent("VideoBindingAttached"));
+ ]]>
+ </constructor>
+ <destructor>
+ <![CDATA[
+ // XBL destructors don't appear to be inherited properly, so we need
+ // to do this here in addition to the videoControls destructor. :-(
+ delete this.randomID;
+ ]]>
+ </destructor>
+
+ </implementation>
+
+ <handlers>
+ <handler event="mouseup">
+ if (event.originalTarget.nodeName == "vbox") {
+ if (this.TouchUtils.firstShow)
+ this.Utils.video.play();
+ this.TouchUtils.toggleControls();
+ }
+ </handler>
+ </handlers>
+
+ </binding>
+
+ <binding id="touchControlsGonk" extends="chrome://global/content/bindings/videoControls.xml#touchControls">
+ <implementation>
+ <constructor>
+ this.isGonk = true;
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="noControls">
+
+ <resources>
+ <stylesheet src="chrome://global/content/bindings/videocontrols.css"/>
+ <stylesheet src="chrome://global/skin/media/videocontrols.css"/>
+ </resources>
+
+ <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="mediaControlsFrame">
+ <vbox flex="1" class="statusOverlay" hidden="true">
+ <box flex="1">
+ <box class="clickToPlay" flex="1"/>
+ </box>
+ </vbox>
+ </xbl:content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ this.randomID = 0;
+ this.Utils = {
+ randomID : 0,
+ videoEvents : ["play",
+ "playing"],
+ controlListeners: [],
+ terminateEventListeners : function () {
+ for (let event of this.videoEvents)
+ this.video.removeEventListener(event, this, { mozSystemGroup: true });
+
+ for (let element of this.controlListeners) {
+ element.item.removeEventListener(element.event, element.func,
+ { mozSystemGroup: true });
+ }
+
+ delete this.controlListeners;
+ },
+
+ hasError : function () {
+ return (this.video.error != null || this.video.networkState == this.video.NETWORK_NO_SOURCE);
+ },
+
+ handleEvent : function (aEvent) {
+ // If the binding is detached (or has been replaced by a
+ // newer instance of the binding), nuke our event-listeners.
+ if (this.binding.randomID != this.randomID) {
+ this.terminateEventListeners();
+ return;
+ }
+
+ switch (aEvent.type) {
+ case "play":
+ this.noControlsOverlay.hidden = true;
+ break;
+ case "playing":
+ this.noControlsOverlay.hidden = true;
+ break;
+ }
+ },
+
+ blockedVideoHandler : function () {
+ if (this.binding.randomID != this.randomID) {
+ this.terminateEventListeners();
+ return;
+ } else if (this.hasError()) {
+ this.noControlsOverlay.hidden = true;
+ return;
+ }
+ this.noControlsOverlay.hidden = false;
+ },
+
+ clickToPlayClickHandler : function (e) {
+ if (this.binding.randomID != this.randomID) {
+ this.terminateEventListeners();
+ return;
+ } else if (e.button != 0) {
+ return;
+ }
+
+ this.noControlsOverlay.hidden = true;
+ this.video.play();
+ },
+
+ init : function (binding) {
+ this.binding = binding;
+ this.randomID = Math.random();
+ this.binding.randomID = this.randomID;
+ this.video = binding.parentNode;
+ this.clickToPlay = document.getAnonymousElementByAttribute(binding, "class", "clickToPlay");
+ this.noControlsOverlay = document.getAnonymousElementByAttribute(binding, "class", "statusOverlay");
+
+ let self = this;
+ function addListener(elem, eventName, func) {
+ let boundFunc = func.bind(self);
+ self.controlListeners.push({ item: elem, event: eventName, func: boundFunc });
+ elem.addEventListener(eventName, boundFunc, { mozSystemGroup: true });
+ }
+ addListener(this.clickToPlay, "click", this.clickToPlayClickHandler);
+ addListener(this.video, "MozNoControlsBlockedVideo", this.blockedVideoHandler);
+
+ for (let event of this.videoEvents) {
+ this.video.addEventListener(event, this, { mozSystemGroup: true });
+ }
+
+ if (this.video.autoplay && !this.video.mozAutoplayEnabled) {
+ this.blockedVideoHandler();
+ }
+ }
+ };
+ this.Utils.init(this);
+ this.Utils.video.dispatchEvent(new CustomEvent("MozNoControlsVideoBindingAttached"));
+ ]]>
+ </constructor>
+ <destructor>
+ <![CDATA[
+ this.Utils.terminateEventListeners();
+ // randomID used to be a <field>, which meant that the XBL machinery
+ // undefined the property when the element was unbound. The code in
+ // this file actually depends on this, so now that randomID is an
+ // expando, we need to make sure to explicitly delete it.
+ delete this.randomID;
+ ]]>
+ </destructor>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/content/wizard.xml b/components/bindings/content/wizard.xml
new file mode 100644
index 000000000..eb812f065
--- /dev/null
+++ b/components/bindings/content/wizard.xml
@@ -0,0 +1,558 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!DOCTYPE bindings [
+ <!ENTITY % wizardDTD SYSTEM "chrome://global/locale/wizard.dtd">
+ %wizardDTD;
+]>
+
+<bindings id="wizardBindings"
+ 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="wizard-base">
+ <resources>
+ <stylesheet src="chrome://global/skin/wizard.css"/>
+ </resources>
+ </binding>
+
+ <binding id="wizard" extends="chrome://global/content/bindings/general.xml#root-element">
+ <resources>
+ <stylesheet src="chrome://global/skin/wizard.css"/>
+ </resources>
+ <content>
+ <xul:hbox class="wizard-header" anonid="Header"/>
+
+ <xul:deck class="wizard-page-box" flex="1" anonid="Deck">
+ <children includes="wizardpage"/>
+ </xul:deck>
+ <children/>
+
+ <xul:hbox class="wizard-buttons" anonid="Buttons" xbl:inherits="pagestep,firstpage,lastpage"/>
+ </content>
+
+ <implementation>
+ <property name="title" onget="return document.title;"
+ onset="return document.title = val;"/>
+
+ <property name="canAdvance" onget="return this._canAdvance;"
+ onset="this._nextButton.disabled = !val; return this._canAdvance = val;"/>
+ <property name="canRewind" onget="return this._canRewind;"
+ onset="this._backButton.disabled = !val; return this._canRewind = val;"/>
+
+ <property name="pageStep" readonly="true" onget="return this._pageStack.length"/>
+
+ <field name="pageCount">0</field>
+
+ <field name="_accessMethod">null</field>
+ <field name="_pageStack">null</field>
+ <field name="_currentPage">null</field>
+
+ <property name="wizardPages">
+ <getter>
+ <![CDATA[
+ var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ return this.getElementsByTagNameNS(xulns, "wizardpage");
+ ]]>
+ </getter>
+ </property>
+
+ <property name="currentPage" onget="return this._currentPage">
+ <setter>
+ <![CDATA[
+ if (!val)
+ return val;
+
+ this._currentPage = val;
+
+ // Setting this attribute allows wizard's clients to dynamically
+ // change the styles of each page based on purpose of the page.
+ this.setAttribute("currentpageid", val.pageid);
+ if (this.onFirstPage) {
+ this.canRewind = false;
+ this.setAttribute("firstpage", "true");
+ if (/Linux/.test(navigator.platform)) {
+ this._backButton.setAttribute('hidden', 'true');
+ }
+ } else {
+ this.canRewind = true;
+ this.setAttribute("firstpage", "false");
+ if (/Linux/.test(navigator.platform)) {
+ this._backButton.setAttribute('hidden', 'false');
+ }
+ }
+
+ if (this.onLastPage) {
+ this.canAdvance = true;
+ this.setAttribute("lastpage", "true");
+ } else {
+ this.setAttribute("lastpage", "false");
+ }
+
+ this._deck.setAttribute("selectedIndex", val.pageIndex);
+ this._advanceFocusToPage(val);
+
+ this._adjustWizardHeader();
+ this._wizardButtons.onPageChange();
+
+ this._fireEvent(val, "pageshow");
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="pageIndex"
+ onget="return this._currentPage ? this._currentPage.pageIndex : -1;">
+ <setter>
+ <![CDATA[
+ if (val < 0 || val >= this.pageCount)
+ return val;
+
+ var page = this.wizardPages[val];
+ this._pageStack[this._pageStack.length-1] = page;
+ this.currentPage = page;
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="onFirstPage" readonly="true"
+ onget="return this._pageStack.length == 1;"/>
+
+ <property name="onLastPage" readonly="true">
+ <getter><![CDATA[
+ var cp = this.currentPage;
+ return cp && ((this._accessMethod == "sequential" && cp.pageIndex == this.pageCount-1) ||
+ (this._accessMethod == "random" && cp.next == ""));
+ ]]></getter>
+ </property>
+
+ <method name="getButton">
+ <parameter name="aDlgType"/>
+ <body>
+ <![CDATA[
+ var btns = this.getElementsByAttribute("dlgtype", aDlgType);
+ return btns.item(0) ? btns[0] : document.getAnonymousElementByAttribute(this._wizardButtons, "dlgtype", aDlgType);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_canAdvance"/>
+ <field name="_canRewind"/>
+ <field name="_wizardHeader"/>
+ <field name="_wizardButtons"/>
+ <field name="_deck"/>
+ <field name="_backButton"/>
+ <field name="_nextButton"/>
+ <field name="_cancelButton"/>
+
+ <!-- functions to be added as oncommand listeners to the wizard buttons -->
+ <field name="_backFunc">(function() { document.documentElement.rewind(); })</field>
+ <field name="_nextFunc">(function() { document.documentElement.advance(); })</field>
+ <field name="_finishFunc">(function() { document.documentElement.advance(); })</field>
+ <field name="_cancelFunc">(function() { document.documentElement.cancel(); })</field>
+ <field name="_extra1Func">(function() { document.documentElement.extra1(); })</field>
+ <field name="_extra2Func">(function() { document.documentElement.extra2(); })</field>
+
+ <field name="_closeHandler">(function(event) {
+ if (document.documentElement.cancel())
+ event.preventDefault();
+ })</field>
+
+ <constructor><![CDATA[
+ this._canAdvance = true;
+ this._canRewind = false;
+ this._hasLoaded = false;
+
+ this._pageStack = [];
+
+ try {
+ // need to create string bundle manually instead of using <xul:stringbundle/>
+ // see bug 63370 for details
+ this._bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
+ .getService(Components.interfaces.nsIStringBundleService)
+ .createBundle("chrome://global/locale/wizard.properties");
+ } catch (e) {
+ // This fails in remote XUL, which has to provide titles for all pages
+ // see bug 142502
+ }
+
+ // get anonymous content references
+ this._wizardHeader = document.getAnonymousElementByAttribute(this, "anonid", "Header");
+ this._wizardButtons = document.getAnonymousElementByAttribute(this, "anonid", "Buttons");
+ this._deck = document.getAnonymousElementByAttribute(this, "anonid", "Deck");
+
+ this._initWizardButton("back");
+ this._initWizardButton("next");
+ this._initWizardButton("finish");
+ this._initWizardButton("cancel");
+ this._initWizardButton("extra1");
+ this._initWizardButton("extra2");
+
+ this._initPages();
+
+ window.addEventListener("close", this._closeHandler, false);
+
+ // start off on the first page
+ this.pageCount = this.wizardPages.length;
+ this.advance();
+
+ // give focus to the first focusable element in the dialog
+ window.addEventListener("load", this._setInitialFocus, false);
+ ]]></constructor>
+
+ <method name="getPageById">
+ <parameter name="aPageId"/>
+ <body><![CDATA[
+ var els = this.getElementsByAttribute("pageid", aPageId);
+ return els.item(0);
+ ]]></body>
+ </method>
+
+ <method name="extra1">
+ <body><![CDATA[
+ if (this.currentPage)
+ this._fireEvent(this.currentPage, "extra1");
+ ]]></body>
+ </method>
+
+ <method name="extra2">
+ <body><![CDATA[
+ if (this.currentPage)
+ this._fireEvent(this.currentPage, "extra2");
+ ]]></body>
+ </method>
+
+ <method name="rewind">
+ <body><![CDATA[
+ if (!this.canRewind)
+ return;
+
+ if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide"))
+ return;
+
+ if (this.currentPage && !this._fireEvent(this.currentPage, "pagerewound"))
+ return;
+
+ if (!this._fireEvent(this, "wizardback"))
+ return;
+
+
+ this._pageStack.pop();
+ this.currentPage = this._pageStack[this._pageStack.length-1];
+ this.setAttribute("pagestep", this._pageStack.length);
+ ]]></body>
+ </method>
+
+ <method name="advance">
+ <parameter name="aPageId"/>
+ <body><![CDATA[
+ if (!this.canAdvance)
+ return;
+
+ if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide"))
+ return;
+
+ if (this.currentPage && !this._fireEvent(this.currentPage, "pageadvanced"))
+ return;
+
+ if (this.onLastPage && !aPageId) {
+ if (this._fireEvent(this, "wizardfinish"))
+ window.setTimeout(function() {window.close();}, 1);
+ } else {
+ if (!this._fireEvent(this, "wizardnext"))
+ return;
+
+ var page;
+ if (aPageId)
+ page = this.getPageById(aPageId);
+ else {
+ if (this.currentPage) {
+ if (this._accessMethod == "random")
+ page = this.getPageById(this.currentPage.next);
+ else
+ page = this.wizardPages[this.currentPage.pageIndex+1];
+ } else
+ page = this.wizardPages[0];
+ }
+
+ if (page) {
+ this._pageStack.push(page);
+ this.setAttribute("pagestep", this._pageStack.length);
+
+ this.currentPage = page;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="goTo">
+ <parameter name="aPageId"/>
+ <body><![CDATA[
+ var page = this.getPageById(aPageId);
+ if (page) {
+ this._pageStack[this._pageStack.length-1] = page;
+ this.currentPage = page;
+ }
+ ]]></body>
+ </method>
+
+ <method name="cancel">
+ <body><![CDATA[
+ if (!this._fireEvent(this, "wizardcancel"))
+ return true;
+
+ window.close();
+ window.setTimeout(function() {window.close();}, 1);
+ return false;
+ ]]></body>
+ </method>
+
+ <method name="_setInitialFocus">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ document.documentElement._hasLoaded = true;
+ var focusInit =
+ function() {
+ // give focus to the first focusable element in the dialog
+ if (!document.commandDispatcher.focusedElement)
+ document.commandDispatcher.advanceFocusIntoSubtree(document.documentElement);
+
+ try {
+ var button =
+ document.documentElement._wizardButtons.defaultButton;
+ if (button)
+ window.notifyDefaultButtonLoaded(button);
+ } catch (e) { }
+ };
+
+ // Give focus after onload completes, see bug 103197.
+ setTimeout(focusInit, 0);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_advanceFocusToPage">
+ <parameter name="aPage"/>
+ <body>
+ <![CDATA[
+ if (!this._hasLoaded)
+ return;
+
+ document.commandDispatcher.advanceFocusIntoSubtree(aPage);
+
+ // if advanceFocusIntoSubtree tries to focus one of our
+ // dialog buttons, then remove it and put it on the root
+ var focused = document.commandDispatcher.focusedElement;
+ if (focused && focused.hasAttribute("dlgtype"))
+ this.focus();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_initPages">
+ <body><![CDATA[
+ var meth = "sequential";
+ var pages = this.wizardPages;
+ for (var i = 0; i < pages.length; ++i) {
+ var page = pages[i];
+ page.pageIndex = i;
+ if (page.next != "")
+ meth = "random";
+ }
+ this._accessMethod = meth;
+ ]]></body>
+ </method>
+
+ <method name="_initWizardButton">
+ <parameter name="aName"/>
+ <body><![CDATA[
+ var btn = document.getAnonymousElementByAttribute(this._wizardButtons, "dlgtype", aName);
+ if (btn) {
+ btn.addEventListener("command", this["_"+aName+"Func"], false);
+ this["_"+aName+"Button"] = btn;
+ }
+ return btn;
+ ]]></body>
+ </method>
+
+ <method name="_adjustWizardHeader">
+ <body><![CDATA[
+ var label = this.currentPage.getAttribute("label");
+ if (!label && this.onFirstPage && this._bundle) {
+ if (/Mac/.test(navigator.platform)) {
+ label = this._bundle.GetStringFromName("default-first-title-mac");
+ } else {
+ label = this._bundle.formatStringFromName("default-first-title", [this.title], 1);
+ }
+ } else if (!label && this.onLastPage && this._bundle) {
+ if (/Mac/.test(navigator.platform)) {
+ label = this._bundle.GetStringFromName("default-last-title-mac");
+ } else {
+ label = this._bundle.formatStringFromName("default-last-title", [this.title], 1);
+ }
+ }
+ this._wizardHeader.setAttribute("label", label);
+ this._wizardHeader.setAttribute("description", this.currentPage.getAttribute("description"));
+ ]]></body>
+ </method>
+
+ <method name="_hitEnter">
+ <parameter name="evt"/>
+ <body>
+ <![CDATA[
+ if (!evt.defaultPrevented)
+ this.advance();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_fireEvent">
+ <parameter name="aTarget"/>
+ <parameter name="aType"/>
+ <body>
+ <![CDATA[
+ var event = document.createEvent("Events");
+ event.initEvent(aType, true, true);
+
+ // handle dom event handlers
+ var noCancel = aTarget.dispatchEvent(event);
+
+ // handle any xml attribute event handlers
+ var handler = aTarget.getAttribute("on"+aType);
+ if (handler != "") {
+ var fn = new Function("event", handler);
+ var returned = fn.apply(aTarget, [event]);
+ if (returned == false)
+ noCancel = false;
+ }
+
+ return noCancel;
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="keypress" keycode="VK_RETURN"
+ group="system" action="this._hitEnter(event)"/>
+ <handler event="keypress" keycode="VK_ESCAPE" group="system">
+ if (!event.defaultPrevented)
+ this.cancel();
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="wizardpage" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
+ <implementation>
+ <field name="pageIndex">-1</field>
+
+ <property name="pageid" onget="return this.getAttribute('pageid');"
+ onset="this.setAttribute('pageid', val);"/>
+
+ <property name="next" onget="return this.getAttribute('next');"
+ onset="this.setAttribute('next', val);
+ this.parentNode._accessMethod = 'random';
+ return val;"/>
+ </implementation>
+ </binding>
+
+ <binding id="wizard-header" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
+ <content>
+ <xul:hbox class="wizard-header-box-1" flex="1">
+ <xul:vbox class="wizard-header-box-text" flex="1">
+ <xul:label class="wizard-header-label" xbl:inherits="xbl:text=label"/>
+ <xul:label class="wizard-header-description" xbl:inherits="xbl:text=description"/>
+ </xul:vbox>
+ <xul:image class="wizard-header-icon" xbl:inherits="src=iconsrc"/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="wizard-buttons" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
+ <content>
+ <xul:vbox class="wizard-buttons-box-1" flex="1">
+ <xul:separator class="wizard-buttons-separator groove"/>
+ <xul:hbox class="wizard-buttons-box-2">
+ <xul:button class="wizard-button" dlgtype="extra1" hidden="true"/>
+ <xul:button class="wizard-button" dlgtype="extra2" hidden="true"/>
+ <xul:spacer flex="1" anonid="spacer"/>
+#ifdef XP_UNIX
+ <xul:button label="&button-cancel-unix.label;" class="wizard-button"
+ dlgtype="cancel" icon="cancel"/>
+ <xul:spacer style="width: 24px"/>
+ <xul:button label="&button-back-unix.label;" accesskey="&button-back-unix.accesskey;"
+ class="wizard-button" dlgtype="back" icon="go-back"/>
+ <xul:deck class="wizard-next-deck" anonid="WizardButtonDeck">
+ <xul:hbox>
+ <xul:button label="&button-finish-unix.label;" class="wizard-button"
+ dlgtype="finish" default="true" flex="1"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:button label="&button-next-unix.label;" accesskey="&button-next-unix.accesskey;"
+ class="wizard-button" dlgtype="next" icon="go-forward"
+ default="true" flex="1"/>
+ </xul:hbox>
+ </xul:deck>
+#else
+ <xul:button label="&button-back-win.label;" accesskey="&button-back-win.accesskey;"
+ class="wizard-button" dlgtype="back" icon="go-back"/>
+ <xul:deck class="wizard-next-deck" anonid="WizardButtonDeck">
+ <xul:hbox>
+ <xul:button label="&button-finish-win.label;" class="wizard-button"
+ dlgtype="finish" default="true" flex="1"/>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:button label="&button-next-win.label;" accesskey="&button-next-win.accesskey;"
+ class="wizard-button" dlgtype="next" icon="go-forward"
+ default="true" flex="1"/>
+ </xul:hbox>
+ </xul:deck>
+ <xul:button label="&button-cancel-win.label;" class="wizard-button"
+ dlgtype="cancel" icon="cancel"/>
+#endif
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+
+ <implementation>
+ <field name="_wizardButtonDeck" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "WizardButtonDeck");
+ </field>
+
+ <method name="onPageChange">
+ <body><![CDATA[
+ if (this.getAttribute("lastpage") == "true") {
+ this._wizardButtonDeck.setAttribute("selectedIndex", 0);
+ } else {
+ this._wizardButtonDeck.setAttribute("selectedIndex", 1);
+ }
+ ]]></body>
+ </method>
+
+ <property name="defaultButton" readonly="true">
+ <getter><![CDATA[
+ const kXULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var buttons = this._wizardButtonDeck.selectedPanel
+ .getElementsByTagNameNS(kXULNS, "button");
+ for (var i = 0; i < buttons.length; i++) {
+ if (buttons[i].getAttribute("default") == "true" &&
+ !buttons[i].hidden && !buttons[i].disabled)
+ return buttons[i];
+ }
+ return null;
+ ]]></getter>
+ </property>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/bindings/jar.mn b/components/bindings/jar.mn
new file mode 100644
index 000000000..be47fe924
--- /dev/null
+++ b/components/bindings/jar.mn
@@ -0,0 +1,52 @@
+toolkit.jar:
+% content global %content/global/ contentaccessible=yes
+ content/global/bindings/autocomplete.xml (content/autocomplete.xml)
+ content/global/bindings/browser.xml (content/browser.xml)
+ content/global/bindings/button.xml (content/button.xml)
+ content/global/bindings/calendar.js (content/calendar.js)
+ content/global/bindings/checkbox.xml (content/checkbox.xml)
+ content/global/bindings/colorpicker.xml (content/colorpicker.xml)
+ content/global/bindings/datekeeper.js (content/datekeeper.js)
+ content/global/bindings/datepicker.js (content/datepicker.js)
+ content/global/bindings/datetimepicker.xml (content/datetimepicker.xml)
+ content/global/bindings/datetimepopup.xml (content/datetimepopup.xml)
+ content/global/bindings/datetimebox.xml (content/datetimebox.xml)
+ content/global/bindings/datetimebox.css (content/datetimebox.css)
+* content/global/bindings/dialog.xml (content/dialog.xml)
+ content/global/bindings/editor.xml (content/editor.xml)
+ content/global/bindings/expander.xml (content/expander.xml)
+ content/global/bindings/filefield.xml (content/filefield.xml)
+ content/global/bindings/findbar.xml (content/findbar.xml)
+ content/global/bindings/general.xml (content/general.xml)
+ content/global/bindings/groupbox.xml (content/groupbox.xml)
+ content/global/bindings/listbox.xml (content/listbox.xml)
+ content/global/bindings/menu.xml (content/menu.xml)
+ content/global/bindings/menulist.xml (content/menulist.xml)
+ content/global/bindings/notification.xml (content/notification.xml)
+ content/global/bindings/numberbox.xml (content/numberbox.xml)
+* content/global/bindings/popup.xml (content/popup.xml)
+* content/global/bindings/preferences.xml (content/preferences.xml)
+ content/global/bindings/progressmeter.xml (content/progressmeter.xml)
+ content/global/bindings/radio.xml (content/radio.xml)
+ content/global/bindings/remote-browser.xml (content/remote-browser.xml)
+ content/global/bindings/resizer.xml (content/resizer.xml)
+ content/global/bindings/richlistbox.xml (content/richlistbox.xml)
+ content/global/bindings/scale.xml (content/scale.xml)
+ content/global/bindings/scrollbar.xml (content/scrollbar.xml)
+ content/global/bindings/scrollbox.xml (content/scrollbox.xml)
+ content/global/bindings/spinner.js (content/spinner.js)
+ content/global/bindings/splitter.xml (content/splitter.xml)
+ content/global/bindings/spinbuttons.xml (content/spinbuttons.xml)
+ content/global/bindings/stringbundle.xml (content/stringbundle.xml)
+* content/global/bindings/tabbox.xml (content/tabbox.xml)
+ content/global/bindings/text.xml (content/text.xml)
+* content/global/bindings/textbox.xml (content/textbox.xml)
+ content/global/bindings/timekeeper.js (content/timekeeper.js)
+ content/global/bindings/timepicker.js (content/timepicker.js)
+ content/global/bindings/toolbar.xml (content/toolbar.xml)
+ content/global/bindings/toolbarbutton.xml (content/toolbarbutton.xml)
+ content/global/bindings/tree.xml (content/tree.xml)
+ content/global/bindings/videocontrols.xml (content/videocontrols.xml)
+ content/global/bindings/videocontrols.css (content/videocontrols.css)
+* content/global/bindings/wizard.xml (content/wizard.xml)
+ content/global/svg/svgBindings.xml (/layout/svg/resources/content/svgBindings.xml) \ No newline at end of file
diff --git a/components/bindings/moz.build b/components/bindings/moz.build
new file mode 100644
index 000000000..aee4b90a5
--- /dev/null
+++ b/components/bindings/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/blocklist/blocklist.manifest b/components/blocklist/blocklist.manifest
new file mode 100644
index 000000000..6b63d6072
--- /dev/null
+++ b/components/blocklist/blocklist.manifest
@@ -0,0 +1,5 @@
+component {66354bc9-7ed1-4692-ae1d-8da97d6b205e} nsBlocklistService.js process=main
+contract @mozilla.org/extensions/blocklist;1 {66354bc9-7ed1-4692-ae1d-8da97d6b205e} process=main
+category profile-after-change nsBlocklistService @mozilla.org/extensions/blocklist;1 process=main
+
+category update-timer nsBlocklistService @mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400 \ No newline at end of file
diff --git a/components/blocklist/moz.build b/components/blocklist/moz.build
new file mode 100644
index 000000000..1cec7ab71
--- /dev/null
+++ b/components/blocklist/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'blocklist.manifest',
+]
+
+EXTRA_PP_COMPONENTS += [
+ 'nsBlocklistService.js',
+]
diff --git a/components/blocklist/nsBlocklistService.js b/components/blocklist/nsBlocklistService.js
new file mode 100644
index 000000000..50de11038
--- /dev/null
+++ b/components/blocklist/nsBlocklistService.js
@@ -0,0 +1,1611 @@
+/* -*- 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/. */
+
+#filter substitution
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+try {
+ // AddonManager.jsm doesn't allow itself to be imported in the child
+ // process. We're used in the child process (for now), so guard against
+ // this.
+ Components.utils.import("resource://gre/modules/AddonManager.jsm");
+ /* globals AddonManagerPrivate*/
+} catch (e) {
+}
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest",
+ "resource://gre/modules/ServiceRequest.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const TOOLKIT_ID = "toolkit@mozilla.org";
+const KEY_PROFILEDIR = "ProfD";
+const KEY_APPDIR = "XCurProcD";
+const FILE_BLOCKLIST = "blocklist.xml";
+const PREF_BLOCKLIST_LASTUPDATETIME = "app.update.lastUpdateTime.blocklist-background-update-timer";
+const PREF_BLOCKLIST_URL = "extensions.blocklist.url";
+const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL";
+const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
+const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval";
+const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level";
+const PREF_BLOCKLIST_PINGCOUNTTOTAL = "extensions.blocklist.pingCountTotal";
+const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
+const PREF_BLOCKLIST_SUPPRESSUI = "extensions.blocklist.suppressUI";
+const PREF_ONECRL_VIA_AMO = "security.onecrl.via.amo";
+const PREF_BLOCKLIST_UPDATE_ENABLED = "services.blocklist.update_enabled";
+const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale";
+const PREF_APP_DISTRIBUTION = "distribution.id";
+const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
+const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled";
+const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist";
+const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
+const UNKNOWN_XPCOM_ABI = "unknownABI";
+const URI_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul"
+const DEFAULT_SEVERITY = 3;
+const DEFAULT_LEVEL = 2;
+const MAX_BLOCK_LEVEL = 3;
+const SEVERITY_OUTDATED = 0;
+const VULNERABILITYSTATUS_NONE = 0;
+const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1;
+const VULNERABILITYSTATUS_NO_UPDATE = 2;
+
+const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"];
+
+var gLoggingEnabled = null;
+var gBlocklistEnabled = true;
+var gBlocklistLevel = DEFAULT_LEVEL;
+
+XPCOMUtils.defineLazyServiceGetter(this, "gConsole",
+ "@mozilla.org/consoleservice;1",
+ "nsIConsoleService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker",
+ "@mozilla.org/xpcom/version-comparator;1",
+ "nsIVersionComparator");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gCertBlocklistService",
+ "@mozilla.org/security/certblocklist;1",
+ "nsICertBlocklist");
+
+XPCOMUtils.defineLazyGetter(this, "gPref", function() {
+ return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
+ QueryInterface(Ci.nsIPrefBranch);
+});
+
+// From appinfo in Services.jsm. It is not possible to use the one in
+// Services.jsm since it will not successfully QueryInterface nsIXULAppInfo in
+// xpcshell tests due to other code calling Services.appinfo before the
+// nsIXULAppInfo is created by the tests.
+XPCOMUtils.defineLazyGetter(this, "gApp", function() {
+ let appinfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime);
+ try {
+ appinfo.QueryInterface(Ci.nsIXULAppInfo);
+ } catch (ex) {
+ // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
+ if (!(ex instanceof Components.Exception) ||
+ ex.result != Cr.NS_NOINTERFACE)
+ throw ex;
+ }
+ return appinfo;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gABI", function() {
+ let abi = null;
+ try {
+ abi = gApp.XPCOMABI;
+ }
+ catch (e) {
+ LOG("BlockList Global gABI: XPCOM ABI unknown.");
+ }
+
+ return abi;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gOSVersion", function() {
+ let osVersion;
+ let sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ try {
+ osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
+ }
+ catch (e) {
+ LOG("BlockList Global gOSVersion: OS Version unknown.");
+ }
+
+ if (osVersion) {
+ try {
+ osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
+ }
+ catch (e) {
+ // Not all platforms have a secondary widget library, so an error is nothing to worry about.
+ }
+ osVersion = encodeURIComponent(osVersion);
+ }
+ return osVersion;
+});
+
+// shared code for suppressing bad cert dialogs
+XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() {
+ let temp = { };
+ Components.utils.import("resource://gre/modules/CertUtils.jsm", temp);
+ return temp;
+});
+
+/**
+ * Logs a string to the error console.
+ * @param string
+ * The string to write to the error console..
+ */
+function LOG(string) {
+ if (gLoggingEnabled) {
+ dump("*** " + string + "\n");
+ gConsole.logStringMessage(string);
+ }
+}
+
+/**
+ * Constructs a URI to a spec.
+ * @param spec
+ * The spec to construct a URI to
+ * @returns The nsIURI constructed.
+ */
+function newURI(spec) {
+ var ioServ = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ioServ.newURI(spec, null, null);
+}
+
+// Restarts the application checking in with observers first
+function restartApp() {
+ // Notify all windows that an application quit has been requested.
+ var os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ os.notifyObservers(cancelQuit, "quit-application-requested", null);
+
+ // Something aborted the quit process.
+ if (cancelQuit.data)
+ return;
+
+ var as = Cc["@mozilla.org/toolkit/app-startup;1"].
+ getService(Ci.nsIAppStartup);
+ as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
+}
+
+/**
+ * Checks whether this blocklist element is valid for the current OS and ABI.
+ * If the element has an "os" attribute then the current OS must appear in
+ * its comma separated list for the element to be valid. Similarly for the
+ * xpcomabi attribute.
+ */
+function matchesOSABI(blocklistElement) {
+ if (blocklistElement.hasAttribute("os")) {
+ var choices = blocklistElement.getAttribute("os").split(",");
+ if (choices.length > 0 && choices.indexOf(gApp.OS) < 0)
+ return false;
+ }
+
+ if (blocklistElement.hasAttribute("xpcomabi")) {
+ choices = blocklistElement.getAttribute("xpcomabi").split(",");
+ if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Gets the current value of the locale. It's possible for this preference to
+ * be localized, so we have to do a little extra work here. Similar code
+ * exists in nsHttpHandler.cpp when building the UA string.
+ */
+function getLocale() {
+ try {
+ // Get the default branch
+ var defaultPrefs = gPref.getDefaultBranch(null);
+ return defaultPrefs.getComplexValue(PREF_GENERAL_USERAGENT_LOCALE,
+ Ci.nsIPrefLocalizedString).data;
+ } catch (e) {}
+
+ return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
+}
+
+/* Get the distribution pref values, from defaults only */
+function getDistributionPrefValue(aPrefName) {
+ return gPref.getDefaultBranch(null).getCharPref(aPrefName, "default");
+}
+
+/**
+ * Parse a string representation of a regular expression. Needed because we
+ * use the /pattern/flags form (because it's detectable), which is only
+ * supported as a literal in JS.
+ *
+ * @param aStr
+ * String representation of regexp
+ * @return RegExp instance
+ */
+function parseRegExp(aStr) {
+ let lastSlash = aStr.lastIndexOf("/");
+ let pattern = aStr.slice(1, lastSlash);
+ let flags = aStr.slice(lastSlash + 1);
+ return new RegExp(pattern, flags);
+}
+
+/**
+ * Manages the Blocklist. The Blocklist is a representation of the contents of
+ * blocklist.xml and allows us to remotely disable / re-enable blocklisted
+ * items managed by the Extension Manager with an item's appDisabled property.
+ * It also blocklists plugins with data from blocklist.xml.
+ */
+
+function Blocklist() {
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ Services.obs.addObserver(this, "sessionstore-windows-restored", false);
+ gLoggingEnabled = Services.prefs.getBoolPref(PREF_EM_LOGGING_ENABLED, false);
+ gBlocklistEnabled = Services.prefs.getBoolPref(PREF_BLOCKLIST_ENABLED, true);
+ gBlocklistLevel = Math.min(Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
+ MAX_BLOCK_LEVEL);
+ gPref.addObserver("extensions.blocklist.", this, false);
+ gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false);
+ this.wrappedJSObject = this;
+ // requests from child processes come in here, see receiveMessage.
+ Services.ppmm.addMessageListener("Blocklist:getPluginBlocklistState", this);
+ Services.ppmm.addMessageListener("Blocklist:content-blocklist-updated", this);
+}
+
+Blocklist.prototype = {
+ /**
+ * Extension ID -> array of Version Ranges
+ * Each value in the version range array is a JS Object that has the
+ * following properties:
+ * "minVersion" The minimum version in a version range (default = 0)
+ * "maxVersion" The maximum version in a version range (default = *)
+ * "targetApps" Application ID -> array of Version Ranges
+ * (default = current application ID)
+ * Each value in the version range array is a JS Object that
+ * has the following properties:
+ * "minVersion" The minimum version in a version range
+ * (default = 0)
+ * "maxVersion" The maximum version in a version range
+ * (default = *)
+ */
+ _addonEntries: null,
+ _gfxEntries: null,
+ _pluginEntries: null,
+
+ shutdown: function() {
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ Services.ppmm.removeMessageListener("Blocklist:getPluginBlocklistState", this);
+ Services.ppmm.removeMessageListener("Blocklist:content-blocklist-updated", this);
+ gPref.removeObserver("extensions.blocklist.", this);
+ gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this);
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "xpcom-shutdown":
+ this.shutdown();
+ break;
+ case "nsPref:changed":
+ switch (aData) {
+ case PREF_EM_LOGGING_ENABLED:
+ gLoggingEnabled = Services.prefs.getBoolPref(PREF_EM_LOGGING_ENABLED, false);
+ break;
+ case PREF_BLOCKLIST_ENABLED:
+ gBlocklistEnabled = Services.prefs.getBoolPref(PREF_BLOCKLIST_ENABLED, true);
+ this._loadBlocklist();
+ this._blocklistUpdated(null, null);
+ break;
+ case PREF_BLOCKLIST_LEVEL:
+ gBlocklistLevel = Math.min(Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
+ MAX_BLOCK_LEVEL);
+ this._blocklistUpdated(null, null);
+ break;
+ }
+ break;
+ case "sessionstore-windows-restored":
+ Services.obs.removeObserver(this, "sessionstore-windows-restored");
+ this._preloadBlocklist();
+ break;
+ }
+ },
+
+ // Message manager message handlers
+ receiveMessage: function(aMsg) {
+ switch (aMsg.name) {
+ case "Blocklist:getPluginBlocklistState":
+ return this.getPluginBlocklistState(aMsg.data.addonData,
+ aMsg.data.appVersion,
+ aMsg.data.toolkitVersion);
+ case "Blocklist:content-blocklist-updated":
+ Services.obs.notifyObservers(null, "content-blocklist-updated", null);
+ break;
+ default:
+ throw new Error("Unknown blocklist message received from content: " + aMsg.name);
+ }
+ return undefined;
+ },
+
+ /* See nsIBlocklistService */
+ isAddonBlocklisted: function(addon, appVersion, toolkitVersion) {
+ return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) ==
+ Ci.nsIBlocklistService.STATE_BLOCKED;
+ },
+
+ /* See nsIBlocklistService */
+ getAddonBlocklistState: function(addon, appVersion, toolkitVersion) {
+ if (!this._isBlocklistLoaded())
+ this._loadBlocklist();
+ return this._getAddonBlocklistState(addon, this._addonEntries,
+ appVersion, toolkitVersion);
+ },
+
+ /**
+ * Private version of getAddonBlocklistState that allows the caller to pass in
+ * the add-on blocklist entries to compare against.
+ *
+ * @param id
+ * The ID of the item to get the blocklist state for.
+ * @param version
+ * The version of the item to get the blocklist state for.
+ * @param addonEntries
+ * The add-on blocklist entries to compare against.
+ * @param appVersion
+ * The application version to compare to, will use the current
+ * version if null.
+ * @param toolkitVersion
+ * The toolkit version to compare to, will use the current version if
+ * null.
+ * @returns The blocklist state for the item, one of the STATE constants as
+ * defined in nsIBlocklistService.
+ */
+ _getAddonBlocklistState: function(addon, addonEntries, appVersion, toolkitVersion) {
+ if (!gBlocklistEnabled)
+ return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+
+ // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
+ if (!appVersion && !gApp.version)
+ return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+
+ if (!appVersion)
+ appVersion = gApp.version;
+ if (!toolkitVersion)
+ toolkitVersion = gApp.greVersion;
+
+ var blItem = this._findMatchingAddonEntry(addonEntries, addon);
+ if (!blItem)
+ return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+
+ for (let currentblItem of blItem.versions) {
+ if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion))
+ return currentblItem.severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED :
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
+ }
+ return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+ },
+
+ /**
+ * Returns the set of prefs of the add-on stored in the blocklist file
+ * (probably to revert them on disabling).
+ * @param addon
+ * The add-on whose to-be-reset prefs are to be found.
+ */
+ _getAddonPrefs: function(addon) {
+ let entry = this._findMatchingAddonEntry(this._addonEntries, addon);
+ return entry.prefs.slice(0);
+ },
+
+ _findMatchingAddonEntry: function(aAddonEntries, aAddon) {
+ if (!aAddon)
+ return null;
+ // Returns true if the params object passes the constraints set by entry.
+ // (For every non-null property in entry, the same key must exist in
+ // params and value must be the same)
+ function checkEntry(entry, params) {
+ for (let [key, value] of entry) {
+ if (value === null || value === undefined)
+ continue;
+ if (params[key]) {
+ if (value instanceof RegExp) {
+ if (!value.test(params[key])) {
+ return false;
+ }
+ } else if (value !== params[key]) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ let params = {};
+ for (let filter of EXTENSION_BLOCK_FILTERS) {
+ params[filter] = aAddon[filter];
+ }
+ if (params.creator)
+ params.creator = params.creator.name;
+ for (let entry of aAddonEntries) {
+ if (checkEntry(entry.attributes, params)) {
+ return entry;
+ }
+ }
+ return null;
+ },
+
+ /* See nsIBlocklistService */
+ getAddonBlocklistURL: function(addon, appVersion, toolkitVersion) {
+ if (!gBlocklistEnabled)
+ return "";
+
+ if (!this._isBlocklistLoaded())
+ this._loadBlocklist();
+
+ let blItem = this._findMatchingAddonEntry(this._addonEntries, addon);
+ if (!blItem || !blItem.blockID)
+ return null;
+
+ return this._createBlocklistURL(blItem.blockID);
+ },
+
+ _createBlocklistURL: function(id) {
+ let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
+ url = url.replace(/%blockID%/g, id);
+
+ return url;
+ },
+
+ notify: function(aTimer) {
+ if (!gBlocklistEnabled)
+ return;
+
+ try {
+ var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL);
+ }
+ catch (e) {
+ LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" +
+ " is missing!");
+ return;
+ }
+
+ var pingCountVersion = Services.prefs.getIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, 0);
+ var pingCountTotal = Services.prefs.getIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, 1);
+ var daysSinceLastPing = 0;
+ if (pingCountVersion == 0) {
+ daysSinceLastPing = "new";
+ }
+ else {
+ // Seconds in one day is used because nsIUpdateTimerManager stores the
+ // last update time in seconds.
+ let secondsInDay = 60 * 60 * 24;
+ let lastUpdateTime = Services.prefs.getIntPref(PREF_BLOCKLIST_LASTUPDATETIME, 0);
+ if (lastUpdateTime == 0) {
+ daysSinceLastPing = "invalid";
+ }
+ else {
+ let now = Math.round(Date.now() / 1000);
+ daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay);
+ }
+
+ if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") {
+ pingCountVersion = pingCountTotal = "invalid";
+ }
+ }
+
+ if (pingCountVersion < 1)
+ pingCountVersion = 1;
+ if (pingCountTotal < 1)
+ pingCountTotal = 1;
+
+ dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID);
+
+ // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
+ if (gApp.version) {
+ dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version);
+ dsURI = dsURI.replace(/%VERSION%/g, gApp.version);
+ }
+
+ dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name);
+ dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID);
+ dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI);
+ dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion);
+ dsURI = dsURI.replace(/%LOCALE%/g, getLocale());
+ dsURI = dsURI.replace(/%CHANNEL%/g, "@MOZ_UPDATE_CHANNEL@");
+ dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.greVersion);
+ dsURI = dsURI.replace(/%DISTRIBUTION%/g,
+ getDistributionPrefValue(PREF_APP_DISTRIBUTION));
+ dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g,
+ getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
+ dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion);
+ dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal);
+ dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing);
+ dsURI = dsURI.replace(/\+/g, "%2B");
+
+ // Under normal operations it will take around 5,883,516 years before the
+ // preferences used to store pingCountVersion and pingCountTotal will rollover
+ // so this code doesn't bother trying to do the "right thing" here.
+ if (pingCountVersion != "invalid") {
+ pingCountVersion++;
+ if (pingCountVersion > 2147483647) {
+ // Rollover to -1 if the value is greater than what is support by an
+ // integer preference. The -1 indicates that the counter has been reset.
+ pingCountVersion = -1;
+ }
+ gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion);
+ }
+
+ if (pingCountTotal != "invalid") {
+ pingCountTotal++;
+ if (pingCountTotal > 2147483647) {
+ // Rollover to 1 if the value is greater than what is support by an
+ // integer preference.
+ pingCountTotal = -1;
+ }
+ gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal);
+ }
+
+ // Verify that the URI is valid
+ try {
+ var uri = newURI(dsURI);
+ }
+ catch (e) {
+ LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" +
+ "for: " + dsURI + ", error: " + e);
+ return;
+ }
+
+ LOG("Blocklist::notify: Requesting " + uri.spec);
+ let request = new ServiceRequest();
+ request.open("GET", uri.spec, true);
+ request.channel.notificationCallbacks = new gCertUtils.BadCertHandler();
+ request.overrideMimeType("text/xml");
+ request.setRequestHeader("Cache-Control", "no-cache");
+ request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);
+
+ request.addEventListener("error", event => this.onXMLError(event), false);
+ request.addEventListener("load", event => this.onXMLLoad(event), false);
+ request.send(null);
+
+ // When the blocklist loads we need to compare it to the current copy so
+ // make sure we have loaded it.
+ if (!this._isBlocklistLoaded())
+ this._loadBlocklist();
+ },
+
+ onXMLLoad: Task.async(function*(aEvent) {
+ let request = aEvent.target;
+ try {
+ gCertUtils.checkCert(request.channel);
+ }
+ catch (e) {
+ LOG("Blocklist::onXMLLoad: " + e);
+ return;
+ }
+ let responseXML = request.responseXML;
+ if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
+ (request.status != 200 && request.status != 0)) {
+ LOG("Blocklist::onXMLLoad: there was an error during load");
+ return;
+ }
+
+ var oldAddonEntries = this._addonEntries;
+ var oldPluginEntries = this._pluginEntries;
+ this._addonEntries = [];
+ this._gfxEntries = [];
+ this._pluginEntries = [];
+
+ this._loadBlocklistFromString(request.responseText);
+ // We don't inform the users when the graphics blocklist changed at runtime.
+ // However addons and plugins blocking status is refreshed.
+ this._blocklistUpdated(oldAddonEntries, oldPluginEntries);
+
+ try {
+ let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
+ yield OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"});
+ } catch (e) {
+ LOG("Blocklist::onXMLLoad: " + e);
+ }
+ }),
+
+ onXMLError: function(aEvent) {
+ try {
+ var request = aEvent.target;
+ // the following may throw (e.g. a local file or timeout)
+ var status = request.status;
+ }
+ catch (e) {
+ request = aEvent.target.channel.QueryInterface(Ci.nsIRequest);
+ status = request.status;
+ }
+ var statusText = "nsIXMLHttpRequest channel unavailable";
+ // When status is 0 we don't have a valid channel.
+ if (status != 0) {
+ try {
+ statusText = request.statusText;
+ } catch (e) {
+ }
+ }
+ LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" +
+ statusText);
+ },
+
+ /**
+ * Finds the newest blocklist file from the application and the profile and
+ * load it or does nothing if neither exist.
+ */
+ _loadBlocklist: function() {
+ this._addonEntries = [];
+ this._gfxEntries = [];
+ this._pluginEntries = [];
+ var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
+ if (profFile.exists()) {
+ this._loadBlocklistFromFile(profFile);
+ return;
+ }
+ var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
+ if (appFile.exists()) {
+ this._loadBlocklistFromFile(appFile);
+ return;
+ }
+ LOG("Blocklist::_loadBlocklist: no XML File found");
+ },
+
+ /**
+# The blocklist XML file looks something like this:
+#
+# <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+# <emItems>
+# <emItem id="item_1@domain" blockID="i1">
+# <prefs>
+# <pref>accessibility.accesskeycausesactivation</pref>
+# <pref>accessibility.blockautorefresh</pref>
+# </prefs>
+# <versionRange minVersion="1.0" maxVersion="2.0.*">
+# <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+# <versionRange minVersion="1.5" maxVersion="1.5.*"/>
+# <versionRange minVersion="1.7" maxVersion="1.7.*"/>
+# </targetApplication>
+# <targetApplication id="toolkit@mozilla.org">
+# <versionRange minVersion="1.9" maxVersion="1.9.*"/>
+# </targetApplication>
+# </versionRange>
+# <versionRange minVersion="3.0" maxVersion="3.0.*">
+# <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+# <versionRange minVersion="1.5" maxVersion="1.5.*"/>
+# </targetApplication>
+# <targetApplication id="toolkit@mozilla.org">
+# <versionRange minVersion="1.9" maxVersion="1.9.*"/>
+# </targetApplication>
+# </versionRange>
+# </emItem>
+# <emItem id="item_2@domain" blockID="i2">
+# <versionRange minVersion="3.1" maxVersion="4.*"/>
+# </emItem>
+# <emItem id="item_3@domain">
+# <versionRange>
+# <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+# <versionRange minVersion="1.5" maxVersion="1.5.*"/>
+# </targetApplication>
+# </versionRange>
+# </emItem>
+# <emItem id="item_4@domain" blockID="i3">
+# <versionRange>
+# <targetApplication>
+# <versionRange minVersion="1.5" maxVersion="1.5.*"/>
+# </targetApplication>
+# </versionRange>
+# <emItem id="/@badperson\.com$/"/>
+# </emItems>
+# <pluginItems>
+# <pluginItem blockID="i4">
+# <!-- All match tags must match a plugin to blocklist a plugin -->
+# <match name="name" exp="some plugin"/>
+# <match name="description" exp="1[.]2[.]3"/>
+# </pluginItem>
+# </pluginItems>
+# <certItems>
+# <!-- issuerName is the DER issuer name data base64 encoded... -->
+# <certItem issuerName="MA0xCzAJBgNVBAMMAmNh">
+# <!-- ... as is the serial number DER data -->
+# <serialNumber>AkHVNA==</serialNumber>
+# </certItem>
+# <!-- subject is the DER subject name data base64 encoded... -->
+# <certItem subject="MA0xCzAJBgNVBAMMAmNh" pubKeyHash="/xeHA5s+i9/z9d8qy6JEuE1xGoRYIwgJuTE/lmaGJ7M=">
+# </certItem>
+# </certItems>
+# </blocklist>
+ */
+
+ _loadBlocklistFromFile: function(file) {
+ if (!gBlocklistEnabled) {
+ LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
+ return;
+ }
+
+ if (this._isBlocklistPreloaded()) {
+ this._loadBlocklistFromString(this._preloadedBlocklistContent);
+ delete this._preloadedBlocklistContent;
+ return;
+ }
+
+ if (!file.exists()) {
+ LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path);
+ return;
+ }
+
+ let text = "";
+ let fstream = null;
+ let cstream = null;
+
+ try {
+ fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Components.interfaces.nsIConverterInputStream);
+
+ fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+ cstream.init(fstream, "UTF-8", 0, 0);
+
+ let str = {};
+ let read = 0;
+
+ do {
+ read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
+ text += str.value;
+ } while (read != 0);
+ } catch (e) {
+ LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e);
+ } finally {
+ if (cstream)
+ cstream.close();
+ if (fstream)
+ fstream.close();
+ }
+
+ if (text)
+ this._loadBlocklistFromString(text);
+ },
+
+ _isBlocklistLoaded: function() {
+ return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
+ },
+
+ _isBlocklistPreloaded: function() {
+ return this._preloadedBlocklistContent != null;
+ },
+
+ /* Used for testing */
+ _clear: function() {
+ this._addonEntries = null;
+ this._gfxEntries = null;
+ this._pluginEntries = null;
+ this._preloadedBlocklistContent = null;
+ },
+
+ _preloadBlocklist: Task.async(function*() {
+ let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
+ try {
+ yield this._preloadBlocklistFile(profPath);
+ return;
+ } catch (e) {
+ LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e)
+ }
+
+ var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
+ try {
+ yield this._preloadBlocklistFile(appFile.path);
+ return;
+ } catch (e) {
+ LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e)
+ }
+
+ LOG("Blocklist::_preloadBlocklist: no XML File found");
+ }),
+
+ _preloadBlocklistFile: Task.async(function*(path) {
+ if (this._addonEntries) {
+ // The file has been already loaded.
+ return;
+ }
+
+ if (!gBlocklistEnabled) {
+ LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled");
+ return;
+ }
+
+ let text = yield OS.File.read(path, { encoding: "utf-8" });
+
+ if (!this._addonEntries) {
+ // Store the content only if a sync load has not been performed in the meantime.
+ this._preloadedBlocklistContent = text;
+ }
+ }),
+
+ _loadBlocklistFromString : function(text) {
+ try {
+ var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+ var doc = parser.parseFromString(text, "text/xml");
+ if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
+ LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " +
+ "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" +
+ "Received: " + doc.documentElement.namespaceURI);
+ return;
+ }
+
+ var populateCertBlocklist = Services.prefs.getBoolPref(PREF_ONECRL_VIA_AMO, true);
+
+ var childNodes = doc.documentElement.childNodes;
+ for (let element of childNodes) {
+ if (!(element instanceof Ci.nsIDOMElement))
+ continue;
+ switch (element.localName) {
+ case "emItems":
+ this._addonEntries = this._processItemNodes(element.childNodes, "emItem",
+ this._handleEmItemNode);
+ break;
+ case "pluginItems":
+ this._pluginEntries = this._processItemNodes(element.childNodes, "pluginItem",
+ this._handlePluginItemNode);
+ break;
+ case "certItems":
+ if (populateCertBlocklist) {
+ this._processItemNodes(element.childNodes, "certItem",
+ this._handleCertItemNode.bind(this));
+ }
+ break;
+ case "gfxItems":
+ // Parse as simple list of objects.
+ this._gfxEntries = this._processItemNodes(element.childNodes, "gfxBlacklistEntry",
+ this._handleGfxBlacklistNode);
+ break;
+ default:
+ LOG("Blocklist::_loadBlocklistFromString: ignored entries " + element.localName);
+ }
+ }
+ if (populateCertBlocklist) {
+ gCertBlocklistService.saveEntries();
+ }
+ if (this._gfxEntries.length > 0) {
+ this._notifyObserversBlocklistGFX();
+ }
+ }
+ catch (e) {
+ LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e);
+ return;
+ }
+ },
+
+ _processItemNodes: function(itemNodes, itemName, handler) {
+ var result = [];
+ for (var i = 0; i < itemNodes.length; ++i) {
+ var blocklistElement = itemNodes.item(i);
+ if (!(blocklistElement instanceof Ci.nsIDOMElement) ||
+ blocklistElement.localName != itemName)
+ continue;
+
+ handler(blocklistElement, result);
+ }
+ return result;
+ },
+
+ _handleCertItemNode: function(blocklistElement, result) {
+ let issuer = blocklistElement.getAttribute("issuerName");
+ if (issuer) {
+ for (let snElement of blocklistElement.children) {
+ try {
+ gCertBlocklistService.revokeCertByIssuerAndSerial(issuer, snElement.textContent);
+ } catch (e) {
+ // we want to keep trying other elements since missing all items
+ // is worse than missing one
+ LOG("Blocklist::_handleCertItemNode: Error adding revoked cert by Issuer and Serial" + e);
+ }
+ }
+ return;
+ }
+
+ let pubKeyHash = blocklistElement.getAttribute("pubKeyHash");
+ let subject = blocklistElement.getAttribute("subject");
+
+ if (pubKeyHash && subject) {
+ try {
+ gCertBlocklistService.revokeCertBySubjectAndPubKey(subject, pubKeyHash);
+ } catch (e) {
+ LOG("Blocklist::_handleCertItemNode: Error adding revoked cert by Subject and PubKey" + e);
+ }
+ }
+ },
+
+ _handleEmItemNode: function(blocklistElement, result) {
+ if (!matchesOSABI(blocklistElement))
+ return;
+
+ let blockEntry = {
+ versions: [],
+ prefs: [],
+ blockID: null,
+ attributes: new Map()
+ // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes
+ };
+
+ // Any filter starting with '/' is interpreted as a regex. So if an attribute
+ // starts with a '/' it must be checked via a regex.
+ function regExpCheck(attr) {
+ return attr.startsWith("/") ? parseRegExp(attr) : attr;
+ }
+
+ for (let filter of EXTENSION_BLOCK_FILTERS) {
+ let attr = blocklistElement.getAttribute(filter);
+ if (attr)
+ blockEntry.attributes.set(filter, regExpCheck(attr));
+ }
+
+ var childNodes = blocklistElement.childNodes;
+
+ for (let x = 0; x < childNodes.length; x++) {
+ var childElement = childNodes.item(x);
+ if (!(childElement instanceof Ci.nsIDOMElement))
+ continue;
+ if (childElement.localName === "prefs") {
+ let prefElements = childElement.childNodes;
+ for (let i = 0; i < prefElements.length; i++) {
+ let prefElement = prefElements.item(i);
+ if (!(prefElement instanceof Ci.nsIDOMElement) ||
+ prefElement.localName !== "pref")
+ continue;
+ blockEntry.prefs.push(prefElement.textContent);
+ }
+ }
+ else if (childElement.localName === "versionRange")
+ blockEntry.versions.push(new BlocklistItemData(childElement));
+ }
+ // if only the extension ID is specified block all versions of the
+ // extension for the current application.
+ if (blockEntry.versions.length == 0)
+ blockEntry.versions.push(new BlocklistItemData(null));
+
+ blockEntry.blockID = blocklistElement.getAttribute("blockID");
+
+ result.push(blockEntry);
+ },
+
+ _handlePluginItemNode: function(blocklistElement, result) {
+ if (!matchesOSABI(blocklistElement))
+ return;
+
+ var matchNodes = blocklistElement.childNodes;
+ var blockEntry = {
+ matches: {},
+ versions: [],
+ blockID: null,
+ infoURL: null,
+ };
+ var hasMatch = false;
+ for (var x = 0; x < matchNodes.length; ++x) {
+ var matchElement = matchNodes.item(x);
+ if (!(matchElement instanceof Ci.nsIDOMElement))
+ continue;
+ if (matchElement.localName == "match") {
+ var name = matchElement.getAttribute("name");
+ var exp = matchElement.getAttribute("exp");
+ try {
+ blockEntry.matches[name] = new RegExp(exp, "m");
+ hasMatch = true;
+ } catch (e) {
+ // Ignore invalid regular expressions
+ }
+ }
+ if (matchElement.localName == "versionRange") {
+ blockEntry.versions.push(new BlocklistItemData(matchElement));
+ }
+ else if (matchElement.localName == "infoURL") {
+ blockEntry.infoURL = matchElement.textContent;
+ }
+ }
+ // Plugin entries require *something* to match to an actual plugin
+ if (!hasMatch)
+ return;
+ // Add a default versionRange if there wasn't one specified
+ if (blockEntry.versions.length == 0)
+ blockEntry.versions.push(new BlocklistItemData(null));
+
+ blockEntry.blockID = blocklistElement.getAttribute("blockID");
+
+ result.push(blockEntry);
+ },
+
+ // <gfxBlacklistEntry blockID="g60">
+ // <os>WINNT 6.0</os>
+ // <osversion>14</osversion>
+ // <versionRange minVersion="42.0" maxVersion="13.0b2"/>
+ // <vendor>0x8086</vendor>
+ // <devices>
+ // <device>0x2582</device>
+ // <device>0x2782</device>
+ // </devices>
+ // <feature> DIRECT3D_10_LAYERS </feature>
+ // <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
+ // <driverVersion> 8.52.322.2202 </driverVersion>
+ // <driverVersionMax> 8.52.322.2202 </driverVersionMax>
+ // <driverVersionComparator> LESS_THAN_OR_EQUAL </driverVersionComparator>
+ // <model>foo</model>
+ // <product>foo</product>
+ // <manufacturer>foo</manufacturer>
+ // <hardware>foo</hardware>
+ // </gfxBlacklistEntry>
+ _handleGfxBlacklistNode: function (blocklistElement, result) {
+ const blockEntry = {};
+
+ // The blockID attribute is always present in the actual data produced on server
+ // (see https://github.com/mozilla/addons-server/blob/2016.05.05/src/olympia/blocklist/templates/blocklist/blocklist.xml#L74)
+ // But it is sometimes missing in test fixtures.
+ if (blocklistElement.hasAttribute("blockID")) {
+ blockEntry.blockID = blocklistElement.getAttribute("blockID");
+ }
+
+ // Trim helper (spaces, tabs, no-break spaces..)
+ const trim = (s) => (s || '').replace(/(^[\s\uFEFF\xA0]+)|([\s\uFEFF\xA0]+$)/g, "");
+
+ for (let i = 0; i < blocklistElement.childNodes.length; ++i) {
+ var matchElement = blocklistElement.childNodes.item(i);
+ if (!(matchElement instanceof Ci.nsIDOMElement))
+ continue;
+
+ let value;
+ if (matchElement.localName == "devices") {
+ value = [];
+ for (let j = 0; j < matchElement.childNodes.length; j++) {
+ const childElement = matchElement.childNodes.item(j);
+ const childValue = trim(childElement.textContent);
+ // Make sure no empty value is added.
+ if (childValue) {
+ if (/,/.test(childValue)) {
+ // Devices can't contain comma.
+ // (c.f serialization in _notifyObserversBlocklistGFX)
+ const e = new Error(`Unsupported device name ${childValue}`);
+ Components.utils.reportError(e);
+ }
+ else {
+ value.push(childValue);
+ }
+ }
+ }
+ } else if (matchElement.localName == "versionRange") {
+ value = {minVersion: trim(matchElement.getAttribute("minVersion")) || "0",
+ maxVersion: trim(matchElement.getAttribute("maxVersion")) || "*"};
+ } else {
+ value = trim(matchElement.textContent);
+ }
+ if (value) {
+ blockEntry[matchElement.localName] = value;
+ }
+ }
+ result.push(blockEntry);
+ },
+
+ /* See nsIBlocklistService */
+ getPluginBlocklistState: function(plugin, appVersion, toolkitVersion) {
+ if (!this._isBlocklistLoaded())
+ this._loadBlocklist();
+ return this._getPluginBlocklistState(plugin, this._pluginEntries,
+ appVersion, toolkitVersion);
+ },
+
+ /**
+ * Private helper to get the blocklist entry for a plugin given a set of
+ * blocklist entries and versions.
+ *
+ * @param plugin
+ * The nsIPluginTag to get the blocklist state for.
+ * @param pluginEntries
+ * The plugin blocklist entries to compare against.
+ * @param appVersion
+ * The application version to compare to, will use the current
+ * version if null.
+ * @param toolkitVersion
+ * The toolkit version to compare to, will use the current version if
+ * null.
+ * @returns {entry: blocklistEntry, version: blocklistEntryVersion},
+ * or null if there is no matching entry.
+ */
+ _getPluginBlocklistEntry: function(plugin, pluginEntries, appVersion, toolkitVersion) {
+ if (!gBlocklistEnabled)
+ return null;
+
+ // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
+ if (!appVersion && !gApp.version)
+ return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+
+ if (!appVersion)
+ appVersion = gApp.version;
+ if (!toolkitVersion)
+ toolkitVersion = gApp.greVersion;
+
+ for (var blockEntry of pluginEntries) {
+ var matchFailed = false;
+ for (var name in blockEntry.matches) {
+ if (!(name in plugin) ||
+ typeof(plugin[name]) != "string" ||
+ !blockEntry.matches[name].test(plugin[name])) {
+ matchFailed = true;
+ break;
+ }
+ }
+
+ if (matchFailed)
+ continue;
+
+ for (let blockEntryVersion of blockEntry.versions) {
+ if (blockEntryVersion.includesItem(plugin.version, appVersion,
+ toolkitVersion)) {
+ return {entry: blockEntry, version: blockEntryVersion};
+ }
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Private version of getPluginBlocklistState that allows the caller to pass in
+ * the plugin blocklist entries.
+ *
+ * @param plugin
+ * The nsIPluginTag to get the blocklist state for.
+ * @param pluginEntries
+ * The plugin blocklist entries to compare against.
+ * @param appVersion
+ * The application version to compare to, will use the current
+ * version if null.
+ * @param toolkitVersion
+ * The toolkit version to compare to, will use the current version if
+ * null.
+ * @returns The blocklist state for the item, one of the STATE constants as
+ * defined in nsIBlocklistService.
+ */
+ _getPluginBlocklistState: function(plugin, pluginEntries, appVersion, toolkitVersion) {
+
+ let r = this._getPluginBlocklistEntry(plugin, pluginEntries,
+ appVersion, toolkitVersion);
+ if (!r) {
+ return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+ }
+
+ let {entry: blockEntry, version: blockEntryVersion} = r;
+
+ if (blockEntryVersion.severity >= gBlocklistLevel)
+ return Ci.nsIBlocklistService.STATE_BLOCKED;
+ if (blockEntryVersion.severity == SEVERITY_OUTDATED) {
+ let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus;
+ if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE)
+ return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE;
+ if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE)
+ return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE;
+ return Ci.nsIBlocklistService.STATE_OUTDATED;
+ }
+ return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
+ },
+
+ /* See nsIBlocklistService */
+ getPluginBlocklistURL: function(plugin) {
+ if (!this._isBlocklistLoaded())
+ this._loadBlocklist();
+
+ let r = this._getPluginBlocklistEntry(plugin, this._pluginEntries);
+ if (!r) {
+ return null;
+ }
+ let {entry: blockEntry, version: blockEntryVersion} = r;
+ if (!blockEntry.blockID) {
+ return null;
+ }
+
+ return this._createBlocklistURL(blockEntry.blockID);
+ },
+
+ /* See nsIBlocklistService */
+ getPluginInfoURL: function(plugin) {
+ if (!this._isBlocklistLoaded())
+ this._loadBlocklist();
+
+ let r = this._getPluginBlocklistEntry(plugin, this._pluginEntries);
+ if (!r) {
+ return null;
+ }
+ let {entry: blockEntry, version: blockEntryVersion} = r;
+ if (!blockEntry.blockID) {
+ return null;
+ }
+
+ return blockEntry.infoURL;
+ },
+
+ _notifyObserversBlocklistGFX: function () {
+ // Notify `GfxInfoBase`, by passing a string serialization.
+ // This way we avoid spreading XML structure logics there.
+ const payload = this._gfxEntries.map((r) => {
+ return Object.keys(r).sort().filter((k) => !/id|last_modified/.test(k)).map((key) => {
+ let value = r[key];
+ if (Array.isArray(value)) {
+ value = value.join(",");
+ } else if (value.hasOwnProperty("minVersion")) {
+ // When XML is parsed, both minVersion and maxVersion are set.
+ value = `${value.minVersion},${value.maxVersion}`;
+ }
+ return `${key}:${value}`;
+ }).join("\t");
+ }).join("\n");
+ Services.obs.notifyObservers(null, "blocklist-data-gfxItems", payload);
+ },
+
+ _notifyObserversBlocklistUpdated: function() {
+ Services.obs.notifyObservers(this, "blocklist-updated", "");
+ Services.ppmm.broadcastAsyncMessage("Blocklist:blocklistInvalidated", {});
+ },
+
+ _blocklistUpdated: function(oldAddonEntries, oldPluginEntries) {
+ var addonList = [];
+
+ // A helper function that reverts the prefs passed to default values.
+ function resetPrefs(prefs) {
+ for (let pref of prefs)
+ gPref.clearUserPref(pref);
+ }
+ const types = ["extension", "theme", "locale", "dictionary", "service"];
+ AddonManager.getAddonsByTypes(types, addons => {
+ for (let addon of addons) {
+ let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED;
+ if (oldAddonEntries)
+ oldState = this._getAddonBlocklistState(addon, oldAddonEntries);
+ let state = this.getAddonBlocklistState(addon);
+
+ LOG("Blocklist state for " + addon.id + " changed from " +
+ oldState + " to " + state);
+
+ // We don't want to re-warn about add-ons
+ if (state == oldState)
+ continue;
+
+ if (state === Ci.nsIBlocklistService.STATE_BLOCKED) {
+ // It's a hard block. We must reset certain preferences.
+ let prefs = this._getAddonPrefs(addon);
+ resetPrefs(prefs);
+ }
+
+ // Ensure that softDisabled is false if the add-on is not soft blocked
+ if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+ addon.softDisabled = false;
+
+ // Don't warn about add-ons becoming unblocked.
+ if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
+ continue;
+
+ // If an add-on has dropped from hard to soft blocked just mark it as
+ // soft disabled and don't warn about it.
+ if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
+ oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ addon.softDisabled = true;
+ continue;
+ }
+
+ // If the add-on is already disabled for some reason then don't warn
+ // about it
+ if (!addon.isActive) {
+ // But mark it as softblocked if necessary. Note that we avoid setting
+ // softDisabled at the same time as userDisabled to make it clear
+ // which was the original cause of the add-on becoming disabled in a
+ // way that the user can change.
+ if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && !addon.userDisabled)
+ addon.softDisabled = true;
+ continue;
+ }
+
+ addonList.push({
+ name: addon.name,
+ version: addon.version,
+ icon: addon.iconURL,
+ disable: false,
+ blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
+ item: addon,
+ url: this.getAddonBlocklistURL(addon),
+ });
+ }
+
+ AddonManagerPrivate.updateAddonAppDisabledStates();
+
+ var phs = Cc["@mozilla.org/plugin/host;1"].
+ getService(Ci.nsIPluginHost);
+ var plugins = phs.getPluginTags();
+
+ for (let plugin of plugins) {
+ let oldState = -1;
+ if (oldPluginEntries)
+ oldState = this._getPluginBlocklistState(plugin, oldPluginEntries);
+ let state = this.getPluginBlocklistState(plugin);
+ LOG("Blocklist state for " + plugin.name + " changed from " +
+ oldState + " to " + state);
+ // We don't want to re-warn about items
+ if (state == oldState)
+ continue;
+
+ if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+ plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
+ }
+ else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+ if (state != Ci.nsIBlocklistService.STATE_OUTDATED &&
+ state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE &&
+ state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
+ addonList.push({
+ name: plugin.name,
+ version: plugin.version,
+ icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
+ disable: false,
+ blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
+ item: plugin,
+ url: this.getPluginBlocklistURL(plugin),
+ });
+ }
+ }
+ }
+
+ if (addonList.length == 0) {
+ this._notifyObserversBlocklistUpdated();
+ return;
+ }
+
+ if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) {
+ try {
+ let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"]
+ .getService(Ci.nsIBlocklistPrompt);
+ blockedPrompter.prompt(addonList);
+ } catch (e) {
+ LOG(e);
+ }
+ this._notifyObserversBlocklistUpdated();
+ return;
+ }
+
+ var args = {
+ restart: false,
+ list: addonList
+ };
+ // This lets the dialog get the raw js object
+ args.wrappedJSObject = args;
+
+ /*
+ Some tests run without UI, so the async code listens to a message
+ that can be sent programatically
+ */
+ let applyBlocklistChanges = () => {
+ for (let addon of addonList) {
+ if (!addon.disable)
+ continue;
+
+ if (addon.item instanceof Ci.nsIPluginTag)
+ addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
+ else {
+ // This add-on is softblocked.
+ addon.item.softDisabled = true;
+ // We must revert certain prefs.
+ let prefs = this._getAddonPrefs(addon.item);
+ resetPrefs(prefs);
+ }
+ }
+
+ if (args.restart)
+ restartApp();
+
+ this._notifyObserversBlocklistUpdated();
+ Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed");
+ }
+
+ Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed", false);
+
+ if (Services.prefs.getBoolPref(PREF_BLOCKLIST_SUPPRESSUI, false)) {
+ applyBlocklistChanges();
+ return;
+ }
+
+ function blocklistUnloadHandler(event) {
+ if (event.target.location == URI_BLOCKLIST_DIALOG) {
+ applyBlocklistChanges();
+ blocklistWindow.removeEventListener("unload", blocklistUnloadHandler);
+ }
+ }
+
+ let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
+ "chrome,centerscreen,dialog,titlebar", args);
+ if (blocklistWindow)
+ blocklistWindow.addEventListener("unload", blocklistUnloadHandler, false);
+ });
+ },
+
+ classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsIBlocklistService,
+ Ci.nsITimerCallback]),
+};
+
+/**
+ * Helper for constructing a blocklist.
+ */
+function BlocklistItemData(versionRangeElement) {
+ var versionRange = this.getBlocklistVersionRange(versionRangeElement);
+ this.minVersion = versionRange.minVersion;
+ this.maxVersion = versionRange.maxVersion;
+ if (versionRangeElement && versionRangeElement.hasAttribute("severity"))
+ this.severity = versionRangeElement.getAttribute("severity");
+ else
+ this.severity = DEFAULT_SEVERITY;
+ if (versionRangeElement && versionRangeElement.hasAttribute("vulnerabilitystatus")) {
+ this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus");
+ } else {
+ this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE;
+ }
+ this.targetApps = { };
+ var found = false;
+
+ if (versionRangeElement) {
+ for (var i = 0; i < versionRangeElement.childNodes.length; ++i) {
+ var targetAppElement = versionRangeElement.childNodes.item(i);
+ if (!(targetAppElement instanceof Ci.nsIDOMElement) ||
+ targetAppElement.localName != "targetApplication")
+ continue;
+ found = true;
+ // default to the current application if id is not provided.
+ var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID;
+ this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement);
+ }
+ }
+ // Default to all versions of the current application when no targetApplication
+ // elements were found
+ if (!found)
+ this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null);
+}
+
+BlocklistItemData.prototype = {
+ /**
+ * Tests if a version of an item is included in the version range and target
+ * application information represented by this BlocklistItemData using the
+ * provided application and toolkit versions.
+ * @param version
+ * The version of the item being tested.
+ * @param appVersion
+ * The application version to test with.
+ * @param toolkitVersion
+ * The toolkit version to test with.
+ * @returns True if the version range covers the item version and application
+ * or toolkit version.
+ */
+ includesItem: function(version, appVersion, toolkitVersion) {
+ // Some platforms have no version for plugins, these don't match if there
+ // was a min/maxVersion provided
+ if (!version && (this.minVersion || this.maxVersion))
+ return false;
+
+ // Check if the item version matches
+ if (!this.matchesRange(version, this.minVersion, this.maxVersion))
+ return false;
+
+ // Check if the application version matches
+ if (this.matchesTargetRange(gApp.ID, appVersion))
+ return true;
+
+ // Check if the toolkit version matches
+ return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion);
+ },
+
+ /**
+ * Checks if a version is higher than or equal to the minVersion (if provided)
+ * and lower than or equal to the maxVersion (if provided).
+ * @param version
+ * The version to test.
+ * @param minVersion
+ * The minimum version. If null it is assumed that version is always
+ * larger.
+ * @param maxVersion
+ * The maximum version. If null it is assumed that version is always
+ * smaller.
+ */
+ matchesRange: function(version, minVersion, maxVersion) {
+ if (minVersion && gVersionChecker.compare(version, minVersion) < 0)
+ return false;
+ if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0)
+ return false;
+ return true;
+ },
+
+ /**
+ * Tests if there is a matching range for the given target application id and
+ * version.
+ * @param appID
+ * The application ID to test for, may be for an application or toolkit
+ * @param appVersion
+ * The version of the application to test for.
+ * @returns True if this version range covers the application version given.
+ */
+ matchesTargetRange: function(appID, appVersion) {
+ var blTargetApp = this.targetApps[appID];
+ if (!blTargetApp)
+ return false;
+
+ for (let app of blTargetApp) {
+ if (this.matchesRange(appVersion, app.minVersion, app.maxVersion))
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Retrieves a version range (e.g. minVersion and maxVersion) for a
+ * blocklist item's targetApplication element.
+ * @param targetAppElement
+ * A targetApplication blocklist element.
+ * @returns An array of JS objects with the following properties:
+ * "minVersion" The minimum version in a version range (default = null).
+ * "maxVersion" The maximum version in a version range (default = null).
+ */
+ getBlocklistAppVersions: function(targetAppElement) {
+ var appVersions = [ ];
+
+ if (targetAppElement) {
+ for (var i = 0; i < targetAppElement.childNodes.length; ++i) {
+ var versionRangeElement = targetAppElement.childNodes.item(i);
+ if (!(versionRangeElement instanceof Ci.nsIDOMElement) ||
+ versionRangeElement.localName != "versionRange")
+ continue;
+ appVersions.push(this.getBlocklistVersionRange(versionRangeElement));
+ }
+ }
+ // return minVersion = null and maxVersion = null if no specific versionRange
+ // elements were found
+ if (appVersions.length == 0)
+ appVersions.push(this.getBlocklistVersionRange(null));
+ return appVersions;
+ },
+
+ /**
+ * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist
+ * versionRange element.
+ * @param versionRangeElement
+ * The versionRange blocklist element.
+ * @returns A JS object with the following properties:
+ * "minVersion" The minimum version in a version range (default = null).
+ * "maxVersion" The maximum version in a version range (default = null).
+ */
+ getBlocklistVersionRange: function(versionRangeElement) {
+ var minVersion = null;
+ var maxVersion = null;
+ if (!versionRangeElement)
+ return { minVersion: minVersion, maxVersion: maxVersion };
+
+ if (versionRangeElement.hasAttribute("minVersion"))
+ minVersion = versionRangeElement.getAttribute("minVersion");
+ if (versionRangeElement.hasAttribute("maxVersion"))
+ maxVersion = versionRangeElement.getAttribute("maxVersion");
+
+ return { minVersion: minVersion, maxVersion: maxVersion };
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]);
diff --git a/components/build/moz.build b/components/build/moz.build
new file mode 100644
index 000000000..53ad22232
--- /dev/null
+++ b/components/build/moz.build
@@ -0,0 +1,56 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+EXPORTS += [
+ 'nsEmbedCID.h',
+ 'nsRDFCID.h',
+ 'nsToolkitCompsCID.h'
+]
+
+SOURCES += [
+ 'nsEmbeddingModule.cpp',
+ 'nsRDFModule.cpp',
+ 'nsToolkitCompsModule.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '../alerts/src',
+ '../directory/src',
+ '../downloads/src',
+ '../feeds',
+ '../find/src',
+ '../jsdownloads/src',
+ '../perfmonitoring',
+ '../printing/src',
+ '../rdf/src',
+ '../startup/src',
+ '../statusfilter',
+ '../typeaheadfind',
+ '../windowwatcher/src',
+ '/dom/commandhandler',
+ '/dom/webbrowserpersist',
+ '/system/runtime',
+]
+
+if not CONFIG['MOZ_DISABLE_PARENTAL_CONTROLS']:
+ LOCAL_INCLUDES += [
+ '../parentalcontrols',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ DEFINES['PROXY_PRINTING'] = 1
+ LOCAL_INCLUDES += [
+ '../printing/src/win',
+ ]
+
+if CONFIG['MOZ_PDF_PRINTING']:
+ DEFINES['PROXY_PRINTING'] = 1
+ LOCAL_INCLUDES += [
+ '../printing/src/unix',
+ ]
+
+FINAL_LIBRARY = 'xul' \ No newline at end of file
diff --git a/components/build/nsEmbedCID.h b/components/build/nsEmbedCID.h
new file mode 100644
index 000000000..57dc4b453
--- /dev/null
+++ b/components/build/nsEmbedCID.h
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSEMBEDCID_H
+#define NSEMBEDCID_H
+
+/**
+ * @file
+ * @brief List of, and documentation for, frozen Gecko embedding contracts.
+ */
+
+/**
+ * Web Browser ContractID
+ * Creating an instance of this ContractID (via createInstanceByContractID)
+ * is the basic way to instantiate a Gecko browser.
+ *
+ * This contract implements the following interfaces:
+ * nsIWebBrowser
+ * nsIWebBrowserSetup
+ * nsIInterfaceRequestor
+ *
+ * @note This contract does not guarantee implementation of any other
+ * interfaces and does not guarantee ability to get any particular
+ * interfaces via the nsIInterfaceRequestor implementation.
+ */
+#define NS_WEBBROWSER_CONTRACTID \
+ "@mozilla.org/embedding/browser/nsWebBrowser;1"
+
+/**
+ * Prompt Service ContractID
+ * The prompt service (which can be gotten by calling getServiceByContractID
+ * on this ContractID) is the way to pose various prompts, alerts,
+ * and confirmation dialogs to the user.
+ *
+ * This contract implements the following interfaces:
+ * nsIPromptService
+ * nsIPromptService2 (optional)
+ *
+ * Embedders may override this ContractID with their own implementation if they
+ * want more control over the way prompts, alerts, and confirmation dialogs are
+ * presented to the user.
+ */
+#define NS_PROMPTSERVICE_CONTRACTID \
+ "@mozilla.org/embedcomp/prompt-service;1"
+
+/**
+ * This contract ID should be implemented by password managers to be able to
+ * override the standard implementation of nsIAuthPrompt2. It will be used as
+ * a service.
+ *
+ * This contract implements the following interfaces:
+ * nsIPromptFactory
+ */
+#define NS_PWMGR_AUTHPROMPTFACTORY \
+ "@mozilla.org/passwordmanager/authpromptfactory;1"
+
+#endif // NSEMBEDCID_H
diff --git a/components/build/nsEmbeddingModule.cpp b/components/build/nsEmbeddingModule.cpp
new file mode 100644
index 000000000..d1d3e5055
--- /dev/null
+++ b/components/build/nsEmbeddingModule.cpp
@@ -0,0 +1,107 @@
+/* -*- 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 "nsDialogParamBlock.h"
+#include "nsWindowWatcher.h"
+#include "nsFind.h"
+#include "nsWebBrowserFind.h"
+#include "nsWebBrowserPersist.h"
+#include "nsCommandManager.h"
+#include "nsControllerCommandTable.h"
+#include "nsCommandParams.h"
+#include "nsCommandGroup.h"
+#include "nsBaseCommandController.h"
+#include "nsNetCID.h"
+#include "nsEmbedCID.h"
+
+#ifdef NS_PRINTING
+#include "nsPrintingPromptService.h"
+#include "nsPrintingProxy.h"
+#endif
+
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowWatcher, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFind)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowserFind)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowserPersist)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsControllerCommandTable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandManager)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandParams)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsControllerCommandGroup)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBaseCommandController)
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDialogParamBlock)
+#ifdef NS_PRINTING
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintingPromptService, Init)
+#ifdef PROXY_PRINTING
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPrintingProxy,
+ nsPrintingProxy::GetInstance)
+#endif
+#endif
+
+NS_DEFINE_NAMED_CID(NS_DIALOGPARAMBLOCK_CID);
+#ifdef NS_PRINTING
+NS_DEFINE_NAMED_CID(NS_PRINTINGPROMPTSERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_WINDOWWATCHER_CID);
+NS_DEFINE_NAMED_CID(NS_FIND_CID);
+NS_DEFINE_NAMED_CID(NS_WEB_BROWSER_FIND_CID);
+NS_DEFINE_NAMED_CID(NS_WEBBROWSERPERSIST_CID);
+NS_DEFINE_NAMED_CID(NS_CONTROLLERCOMMANDTABLE_CID);
+NS_DEFINE_NAMED_CID(NS_COMMAND_MANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_COMMAND_PARAMS_CID);
+NS_DEFINE_NAMED_CID(NS_CONTROLLER_COMMAND_GROUP_CID);
+NS_DEFINE_NAMED_CID(NS_BASECOMMANDCONTROLLER_CID);
+
+static const mozilla::Module::CIDEntry kEmbeddingCIDs[] = {
+ { &kNS_DIALOGPARAMBLOCK_CID, false, nullptr, nsDialogParamBlockConstructor },
+#ifdef NS_PRINTING
+
+#ifdef PROXY_PRINTING
+ { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingPromptServiceConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingProxyConstructor,
+ mozilla::Module::CONTENT_PROCESS_ONLY },
+#else
+ { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingPromptServiceConstructor },
+#endif
+#endif
+ { &kNS_WINDOWWATCHER_CID, false, nullptr, nsWindowWatcherConstructor },
+ { &kNS_FIND_CID, false, nullptr, nsFindConstructor },
+ { &kNS_WEB_BROWSER_FIND_CID, false, nullptr, nsWebBrowserFindConstructor },
+ { &kNS_WEBBROWSERPERSIST_CID, false, nullptr, nsWebBrowserPersistConstructor },
+ { &kNS_CONTROLLERCOMMANDTABLE_CID, false, nullptr, nsControllerCommandTableConstructor },
+ { &kNS_COMMAND_MANAGER_CID, false, nullptr, nsCommandManagerConstructor },
+ { &kNS_COMMAND_PARAMS_CID, false, nullptr, nsCommandParamsConstructor },
+ { &kNS_CONTROLLER_COMMAND_GROUP_CID, false, nullptr, nsControllerCommandGroupConstructor },
+ { &kNS_BASECOMMANDCONTROLLER_CID, false, nullptr, nsBaseCommandControllerConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kEmbeddingContracts[] = {
+ { NS_DIALOGPARAMBLOCK_CONTRACTID, &kNS_DIALOGPARAMBLOCK_CID },
+#ifdef NS_PRINTING
+ { NS_PRINTINGPROMPTSERVICE_CONTRACTID, &kNS_PRINTINGPROMPTSERVICE_CID },
+#endif
+ { NS_WINDOWWATCHER_CONTRACTID, &kNS_WINDOWWATCHER_CID },
+ { NS_FIND_CONTRACTID, &kNS_FIND_CID },
+ { NS_WEB_BROWSER_FIND_CONTRACTID, &kNS_WEB_BROWSER_FIND_CID },
+ { NS_WEBBROWSERPERSIST_CONTRACTID, &kNS_WEBBROWSERPERSIST_CID },
+ { NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &kNS_CONTROLLERCOMMANDTABLE_CID },
+ { NS_COMMAND_MANAGER_CONTRACTID, &kNS_COMMAND_MANAGER_CID },
+ { NS_COMMAND_PARAMS_CONTRACTID, &kNS_COMMAND_PARAMS_CID },
+ { NS_CONTROLLER_COMMAND_GROUP_CONTRACTID, &kNS_CONTROLLER_COMMAND_GROUP_CID },
+ { NS_BASECOMMANDCONTROLLER_CONTRACTID, &kNS_BASECOMMANDCONTROLLER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kEmbeddingModule = {
+ mozilla::Module::kVersion,
+ kEmbeddingCIDs,
+ kEmbeddingContracts
+};
+
+NSMODULE_DEFN(embedcomponents) = &kEmbeddingModule;
diff --git a/components/build/nsRDFCID.h b/components/build/nsRDFCID.h
new file mode 100644
index 000000000..18d47aed5
--- /dev/null
+++ b/components/build/nsRDFCID.h
@@ -0,0 +1,76 @@
+/* -*- 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/. */
+
+/*
+
+ XPCOM Class IDs for RDF objects that can be constructed via the RDF
+ factory.
+
+ */
+
+#ifndef nsRDFCID_h__
+#define nsRDFCID_h__
+
+// {0F78DA56-8321-11d2-8EAC-00805F29F370}
+#define NS_RDFDEFAULTRESOURCE_CID \
+{ 0xf78da56, 0x8321, 0x11d2, { 0x8e, 0xac, 0x0, 0x80, 0x5f, 0x29, 0xf3, 0x70 } }
+
+// {BFD05264-834C-11d2-8EAC-00805F29F370}
+#define NS_RDFSERVICE_CID \
+{ 0xbfd05264, 0x834c, 0x11d2, { 0x8e, 0xac, 0x0, 0x80, 0x5f, 0x29, 0xf3, 0x70 } }
+
+// {BFD0526D-834C-11d2-8EAC-00805F29F370}
+#define NS_RDFINMEMORYDATASOURCE_CID \
+{ 0xbfd0526d, 0x834c, 0x11d2, { 0x8e, 0xac, 0x0, 0x80, 0x5f, 0x29, 0xf3, 0x70 } }
+
+// {E638D760-8687-11d2-B530-000000000001}
+#define NS_RDFFILESYSTEMDATASOURCE_CID \
+{ 0xe638d760, 0x8687, 0x11d2, { 0xb5, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x01 } }
+
+// {6bd1d807-1c67-11d3-9820-ed1b357eb3c4}
+#define NS_RDFSEARCHDATASOURCE_CID \
+{ 0x6bd1d807, 0x1c67, 0x11d3, { 0x98, 0x20, 0xed, 0x1b, 0x35, 0x7e, 0xb3, 0xc4 } }
+
+// {E638D760-8687-11d2-B530-000000000002}
+#define NS_RDFFINDDATASOURCE_CID \
+{ 0xe638d760, 0x8687, 0x11d2, { 0xb5, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x02 } }
+
+// {E638D761-8687-11d2-B530-000000000000}
+#define NS_RDFCOMPOSITEDATASOURCE_CID \
+{ 0xe638d761, 0x8687, 0x11d2, { 0xb5, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } }
+
+// {7BAF62E0-8E61-11d2-8EB1-00805F29F370}
+#define NS_RDFXMLDATASOURCE_CID \
+{ 0x7baf62e0, 0x8e61, 0x11d2, { 0x8e, 0xb1, 0x0, 0x80, 0x5f, 0x29, 0xf3, 0x70 } }
+
+// {0958B101-9ADA-11d2-8EBC-00805F29F370}
+#define NS_RDFCONTENTSINK_CID \
+{ 0x958b101, 0x9ada, 0x11d2, { 0x8e, 0xbc, 0x0, 0x80, 0x5f, 0x29, 0xf3, 0x70 } }
+
+// {1EAAFD60-D596-11d2-80BE-006097B76B8E}
+#define NS_RDFHISTORYDATASOURCE_CID \
+{ 0x1eaafd60, 0xd596, 0x11d2, { 0x80, 0xbe, 0x0, 0x60, 0x97, 0xb7, 0x6b, 0x8e } }
+
+// {D4214E92-FB94-11d2-BDD8-00104BDE6048}
+#define NS_RDFCONTAINERUTILS_CID \
+{ 0xd4214e92, 0xfb94, 0x11d2, { 0xbd, 0xd8, 0x0, 0x10, 0x4b, 0xde, 0x60, 0x48 } }
+
+// {D4214E93-FB94-11d2-BDD8-00104BDE6048}
+#define NS_RDFCONTAINER_CID \
+{ 0xd4214e93, 0xfb94, 0x11d2, { 0xbd, 0xd8, 0x0, 0x10, 0x4b, 0xde, 0x60, 0x48 } }
+
+// {0032d852-1dd2-11b2-95f7-e0a1910ed2da}
+#define NS_RDFXMLSERIALIZER_CID \
+{ 0x0032d852, 0x1dd2, 0x11b2, { 0x95, 0xf7, 0xe0, 0xa1, 0x91, 0x0e, 0xd2, 0xda } }
+
+// {a4048e94-1dd1-11b2-a676-8a06c086cc7d}
+#define NS_RDFXMLPARSER_CID \
+{ 0xa4048e94, 0x1dd1, 0x11b2, { 0xa6, 0x76, 0x8a, 0x06, 0xc0, 0x86, 0xcc, 0x7d } }
+
+// {0a5cd734-eb65-4d14-88a0-9f0bb2aba206}
+#define NS_RDFNTRIPLES_SERIALIZER_CID \
+{ 0x0a5cd734, 0xeb65, 0x4d14, { 0x88, 0xa0, 0x9f, 0x0b, 0xb2, 0xab, 0xa2, 0x06 } }
+
+#endif // nsRDFCID_h__
diff --git a/components/build/nsRDFModule.cpp b/components/build/nsRDFModule.cpp
new file mode 100644
index 000000000..d1af94bc0
--- /dev/null
+++ b/components/build/nsRDFModule.cpp
@@ -0,0 +1,159 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "mozilla/ModuleUtils.h"
+
+#include "nsIFactory.h"
+#include "nsRDFService.h"
+#include "nsIRDFContainer.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIRDFCompositeDataSource.h"
+#include "nsIRDFContentSink.h"
+#include "nsISupports.h"
+#include "nsRDFBaseDataSources.h"
+#include "nsRDFBuiltInDataSources.h"
+#include "nsFileSystemDataSource.h"
+#include "nsRDFCID.h"
+#include "nsIComponentManager.h"
+#include "rdf.h"
+#include "nsIServiceManager.h"
+#include "nsILocalStore.h"
+#include "nsRDFXMLParser.h"
+#include "nsRDFXMLSerializer.h"
+
+#include "rdfISerializer.h"
+
+//----------------------------------------------------------------------
+
+// Functions used to create new instances of a given object by the
+// generic factory.
+
+#define MAKE_CTOR(_func,_new,_ifname) \
+static nsresult \
+CreateNew##_func(nsISupports* aOuter, REFNSIID aIID, void **aResult) \
+{ \
+ if (!aResult) { \
+ return NS_ERROR_INVALID_POINTER; \
+ } \
+ if (aOuter) { \
+ *aResult = nullptr; \
+ return NS_ERROR_NO_AGGREGATION; \
+ } \
+ nsI##_ifname* inst; \
+ nsresult rv = NS_New##_new(&inst); \
+ if (NS_FAILED(rv)) { \
+ *aResult = nullptr; \
+ return rv; \
+ } \
+ rv = inst->QueryInterface(aIID, aResult); \
+ if (NS_FAILED(rv)) { \
+ *aResult = nullptr; \
+ } \
+ NS_RELEASE(inst); /* get rid of extra refcnt */ \
+ return rv; \
+}
+
+extern nsresult
+NS_NewDefaultResource(nsIRDFResource** aResult);
+
+MAKE_CTOR(RDFXMLDataSource,RDFXMLDataSource,RDFDataSource)
+MAKE_CTOR(RDFCompositeDataSource,RDFCompositeDataSource,RDFCompositeDataSource)
+MAKE_CTOR(RDFContainer,RDFContainer,RDFContainer)
+
+MAKE_CTOR(RDFContainerUtils,RDFContainerUtils,RDFContainerUtils)
+
+MAKE_CTOR(RDFContentSink,RDFContentSink,RDFContentSink)
+MAKE_CTOR(RDFDefaultResource,DefaultResource,RDFResource)
+
+#define MAKE_RDF_CTOR(_func,_new,_ifname) \
+static nsresult \
+CreateNew##_func(nsISupports* aOuter, REFNSIID aIID, void **aResult) \
+{ \
+ if (!aResult) { \
+ return NS_ERROR_INVALID_POINTER; \
+ } \
+ if (aOuter) { \
+ *aResult = nullptr; \
+ return NS_ERROR_NO_AGGREGATION; \
+ } \
+ rdfI##_ifname* inst; \
+ nsresult rv = NS_New##_new(&inst); \
+ if (NS_FAILED(rv)) { \
+ *aResult = nullptr; \
+ return rv; \
+ } \
+ rv = inst->QueryInterface(aIID, aResult); \
+ if (NS_FAILED(rv)) { \
+ *aResult = nullptr; \
+ } \
+ NS_RELEASE(inst); /* get rid of extra refcnt */ \
+ return rv; \
+}
+
+extern nsresult
+NS_NewTriplesSerializer(rdfISerializer** aResult);
+
+MAKE_RDF_CTOR(TriplesSerializer, TriplesSerializer, Serializer)
+
+NS_DEFINE_NAMED_CID(NS_RDFCOMPOSITEDATASOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_RDFFILESYSTEMDATASOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_RDFINMEMORYDATASOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_RDFXMLDATASOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_RDFDEFAULTRESOURCE_CID);
+NS_DEFINE_NAMED_CID(NS_RDFCONTENTSINK_CID);
+NS_DEFINE_NAMED_CID(NS_RDFCONTAINER_CID);
+NS_DEFINE_NAMED_CID(NS_RDFCONTAINERUTILS_CID);
+NS_DEFINE_NAMED_CID(NS_RDFSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_RDFXMLPARSER_CID);
+NS_DEFINE_NAMED_CID(NS_RDFXMLSERIALIZER_CID);
+NS_DEFINE_NAMED_CID(NS_RDFNTRIPLES_SERIALIZER_CID);
+NS_DEFINE_NAMED_CID(NS_LOCALSTORE_CID);
+
+
+static const mozilla::Module::CIDEntry kRDFCIDs[] = {
+ { &kNS_RDFCOMPOSITEDATASOURCE_CID, false, nullptr, CreateNewRDFCompositeDataSource },
+ { &kNS_RDFFILESYSTEMDATASOURCE_CID, false, nullptr, FileSystemDataSource::Create },
+ { &kNS_RDFINMEMORYDATASOURCE_CID, false, nullptr, NS_NewRDFInMemoryDataSource },
+ { &kNS_RDFXMLDATASOURCE_CID, false, nullptr, CreateNewRDFXMLDataSource },
+ { &kNS_RDFDEFAULTRESOURCE_CID, false, nullptr, CreateNewRDFDefaultResource },
+ { &kNS_RDFCONTENTSINK_CID, false, nullptr, CreateNewRDFContentSink },
+ { &kNS_RDFCONTAINER_CID, false, nullptr, CreateNewRDFContainer },
+ { &kNS_RDFCONTAINERUTILS_CID, false, nullptr, CreateNewRDFContainerUtils },
+ { &kNS_RDFSERVICE_CID, false, nullptr, RDFServiceImpl::CreateSingleton },
+ { &kNS_RDFXMLPARSER_CID, false, nullptr, nsRDFXMLParser::Create },
+ { &kNS_RDFXMLSERIALIZER_CID, false, nullptr, nsRDFXMLSerializer::Create },
+ { &kNS_RDFNTRIPLES_SERIALIZER_CID, false, nullptr, CreateNewTriplesSerializer },
+ { &kNS_LOCALSTORE_CID, false, nullptr, NS_NewLocalStore },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kRDFContracts[] = {
+ { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "composite-datasource", &kNS_RDFCOMPOSITEDATASOURCE_CID },
+ { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "files", &kNS_RDFFILESYSTEMDATASOURCE_CID },
+ { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "in-memory-datasource", &kNS_RDFINMEMORYDATASOURCE_CID },
+ { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "xml-datasource", &kNS_RDFXMLDATASOURCE_CID },
+ { NS_RDF_RESOURCE_FACTORY_CONTRACTID, &kNS_RDFDEFAULTRESOURCE_CID },
+ { NS_RDF_CONTRACTID "/content-sink;1", &kNS_RDFCONTENTSINK_CID },
+ { NS_RDF_CONTRACTID "/container;1", &kNS_RDFCONTAINER_CID },
+ { NS_RDF_CONTRACTID "/container-utils;1", &kNS_RDFCONTAINERUTILS_CID },
+ { NS_RDF_CONTRACTID "/rdf-service;1", &kNS_RDFSERVICE_CID },
+ { NS_RDF_CONTRACTID "/xml-parser;1", &kNS_RDFXMLPARSER_CID },
+ { NS_RDF_CONTRACTID "/xml-serializer;1", &kNS_RDFXMLSERIALIZER_CID },
+ { NS_RDF_SERIALIZER "ntriples", &kNS_RDFNTRIPLES_SERIALIZER_CID },
+ { NS_LOCALSTORE_CONTRACTID, &kNS_LOCALSTORE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kRDFModule = {
+ mozilla::Module::kVersion,
+ kRDFCIDs,
+ kRDFContracts,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr
+};
+
+NSMODULE_DEFN(nsRDFModule) = &kRDFModule;
diff --git a/components/build/nsToolkitCompsCID.h b/components/build/nsToolkitCompsCID.h
new file mode 100644
index 000000000..6f01a79f6
--- /dev/null
+++ b/components/build/nsToolkitCompsCID.h
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_ALERTSERVICE_CONTRACTID \
+ "@mozilla.org/alerts-service;1"
+
+// This separate service uses the same nsIAlertsService interface,
+// but instead sends a notification to a platform alerts API
+// if available. Using a separate CID allows us to overwrite the XUL
+// alerts service at runtime.
+#define NS_SYSTEMALERTSERVICE_CONTRACTID \
+ "@mozilla.org/system-alerts-service;1"
+
+#define NS_AUTOCOMPLETECONTROLLER_CONTRACTID \
+ "@mozilla.org/autocomplete/controller;1"
+
+#define NS_AUTOCOMPLETESIMPLERESULT_CONTRACTID \
+ "@mozilla.org/autocomplete/simple-result;1"
+
+#define NS_AUTOCOMPLETEMDBRESULT_CONTRACTID \
+ "@mozilla.org/autocomplete/mdb-result;1"
+
+#define NS_DOWNLOADMANAGER_CONTRACTID \
+ "@mozilla.org/download-manager;1"
+
+#define NS_DOWNLOADPLATFORM_CONTRACTID \
+ "@mozilla.org/toolkit/download-platform;1"
+
+#define NS_FORMHISTORY_CONTRACTID \
+ "@mozilla.org/satchel/form-history;1"
+
+#define NS_FORMFILLCONTROLLER_CONTRACTID \
+ "@mozilla.org/satchel/form-fill-controller;1"
+
+#define NS_FORMHISTORYAUTOCOMPLETE_CONTRACTID \
+ "@mozilla.org/autocomplete/search;1?name=form-history"
+
+#define NS_GLOBALHISTORY_DATASOURCE_CONTRACTID \
+ "@mozilla.org/rdf/datasource;1?name=history"
+
+#define NS_TYPEAHEADFIND_CONTRACTID \
+ "@mozilla.org/typeaheadfind;1"
+
+#define NS_PARENTALCONTROLSSERVICE_CONTRACTID \
+ "@mozilla.org/parental-controls-service;1"
+
+#define NS_URLCLASSIFIERPREFIXSET_CONTRACTID \
+ "@mozilla.org/url-classifier/prefixset;1"
+
+#define NS_URLCLASSIFIERDBSERVICE_CONTRACTID \
+ "@mozilla.org/url-classifier/dbservice;1"
+
+#define NS_URLCLASSIFIERSTREAMUPDATER_CONTRACTID \
+ "@mozilla.org/url-classifier/streamupdater;1"
+
+#define NS_URLCLASSIFIERUTILS_CONTRACTID \
+ "@mozilla.org/url-classifier/utils;1"
+
+#define NS_URLCLASSIFIERHASHCOMPLETER_CONTRACTID \
+ "@mozilla.org/url-classifier/hashcompleter;1"
+
+#define NS_NAVHISTORYSERVICE_CONTRACTID \
+ "@mozilla.org/browser/nav-history-service;1"
+
+#define NS_ANNOTATIONSERVICE_CONTRACTID \
+ "@mozilla.org/browser/annotation-service;1"
+
+#define NS_NAVBOOKMARKSSERVICE_CONTRACTID \
+ "@mozilla.org/browser/nav-bookmarks-service;1"
+
+#define NS_LIVEMARKSERVICE_CONTRACTID \
+ "@mozilla.org/browser/livemark-service;2"
+
+#define NS_TAGGINGSERVICE_CONTRACTID \
+"@mozilla.org/browser/tagging-service;1"
+
+#define NS_FAVICONSERVICE_CONTRACTID \
+ "@mozilla.org/browser/favicon-service;1"
+
+#define NS_APPSTARTUP_CONTRACTID \
+ "@mozilla.org/toolkit/app-startup;1"
+
+#ifdef MOZ_UPDATER
+#define NS_UPDATEPROCESSOR_CONTRACTID \
+ "@mozilla.org/updates/update-processor;1"
+#endif
+
+#define NS_ADDONCONTENTPOLICY_CONTRACTID \
+ "@mozilla.org/addons/content-policy;1"
+
+#define NS_ADDONPATHSERVICE_CONTRACTID \
+ "@mozilla.org/addon-path-service;1"
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define ALERT_NOTIFICATION_CID \
+{ 0x9a7b7a41, 0x0b47, 0x47f7, { 0xb6, 0x1b, 0x15, 0xa2, 0x10, 0xd6, 0xf0, 0x20 } }
+
+// {A0CCAAF8-09DA-44D8-B250-9AC3E93C8117}
+#define NS_ALERTSSERVICE_CID \
+{ 0xa0ccaaf8, 0x9da, 0x44d8, { 0xb2, 0x50, 0x9a, 0xc3, 0xe9, 0x3c, 0x81, 0x17 } }
+
+// {84E11F80-CA55-11DD-AD8B-0800200C9A66}
+#define NS_SYSTEMALERTSSERVICE_CID \
+{ 0x84e11f80, 0xca55, 0x11dd, { 0xad, 0x8b, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
+
+// {F6D5EBBD-34F4-487d-9D10-3D34123E3EB9}
+#define NS_AUTOCOMPLETECONTROLLER_CID \
+{ 0xf6d5ebbd, 0x34f4, 0x487d, { 0x9d, 0x10, 0x3d, 0x34, 0x12, 0x3e, 0x3e, 0xb9 } }
+
+// {2ee3039b-2de4-43d9-93b0-649beacff39a}
+#define NS_AUTOCOMPLETESIMPLERESULT_CID \
+{ 0x2ee3039b, 0x2de4, 0x43d9, { 0x93, 0xb0, 0x64, 0x9b, 0xea, 0xcf, 0xf3, 0x9a } }
+
+// {7A6F70B6-2BBD-44b5-9304-501352D44AB5}
+#define NS_AUTOCOMPLETEMDBRESULT_CID \
+{ 0x7a6f70b6, 0x2bbd, 0x44b5, { 0x93, 0x4, 0x50, 0x13, 0x52, 0xd4, 0x4a, 0xb5 } }
+
+#define NS_DOWNLOADMANAGER_CID \
+ { 0xedb0490e, 0x1dd1, 0x11b2, { 0x83, 0xb8, 0xdb, 0xf8, 0xd8, 0x59, 0x06, 0xa6 } }
+
+#define NS_DOWNLOADPLATFORM_CID \
+ { 0x649a14c9, 0xfe5c, 0x48ec, { 0x9c, 0x85, 0x00, 0xca, 0xd9, 0xcc, 0xf3, 0x2e } }
+
+// {895DB6C7-DBDF-40ea-9F64-B175033243DC}
+#define NS_FORMFILLCONTROLLER_CID \
+{ 0x895db6c7, 0xdbdf, 0x40ea, { 0x9f, 0x64, 0xb1, 0x75, 0x3, 0x32, 0x43, 0xdc } }
+
+// {59648a91-5a60-4122-8ff2-54b839c84aed}
+#define NS_GLOBALHISTORY_CID \
+{ 0x59648a91, 0x5a60, 0x4122, { 0x8f, 0xf2, 0x54, 0xb8, 0x39, 0xc8, 0x4a, 0xed} }
+
+// {59648a91-5a60-4122-8ff2-54b839c84aed}
+#define NS_PARENTALCONTROLSSERVICE_CID \
+{ 0x580530e5, 0x118c, 0x4bc7, { 0xab, 0x88, 0xbc, 0x2c, 0xd2, 0xb9, 0x72, 0x23 } }
+
+// {e7f70966-9a37-48d7-8aeb-35998f31090e}
+#define NS_TYPEAHEADFIND_CID \
+{ 0xe7f70966, 0x9a37, 0x48d7, { 0x8a, 0xeb, 0x35, 0x99, 0x8f, 0x31, 0x09, 0x0e} }
+
+// {3d8579f0-75fa-4e00-ba41-38661d5b5d17}
+ #define NS_URLCLASSIFIERPREFIXSET_CID \
+{ 0x3d8579f0, 0x75fa, 0x4e00, { 0xba, 0x41, 0x38, 0x66, 0x1d, 0x5b, 0x5d, 0x17} }
+
+// {7a258022-6765-11e5-b379-b37b1f2354be}
+#define NS_URLCLASSIFIERDBSERVICE_CID \
+{ 0x7a258022, 0x6765, 0x11e5, { 0xb3, 0x79, 0xb3, 0x7b, 0x1f, 0x23, 0x54, 0xbe} }
+
+// e1797597-f4d6-4dd3-a1e1-745ad352cd80
+#define NS_URLCLASSIFIERSTREAMUPDATER_CID \
+{ 0xe1797597, 0xf4d6, 0x4dd3, { 0xa1, 0xe1, 0x74, 0x5a, 0xd3, 0x52, 0xcd, 0x80 }}
+
+// {b7b2ccec-7912-4ea6-a548-b038447004bd}
+#define NS_URLCLASSIFIERUTILS_CID \
+{ 0xb7b2ccec, 0x7912, 0x4ea6, { 0xa5, 0x48, 0xb0, 0x38, 0x44, 0x70, 0x04, 0xbd} }
+
+#define NS_NAVHISTORYSERVICE_CID \
+{ 0x88cecbb7, 0x6c63, 0x4b3b, { 0x8c, 0xd4, 0x84, 0xf3, 0xb8, 0x22, 0x8c, 0x69 } }
+
+#define NS_NAVHISTORYRESULTTREEVIEWER_CID \
+{ 0x2ea8966f, 0x0671, 0x4c02, { 0x9c, 0x70, 0x94, 0x59, 0x56, 0xd4, 0x54, 0x34 } }
+
+#define NS_ANNOTATIONSERVICE_CID \
+{ 0x5e8d4751, 0x1852, 0x434b, { 0xa9, 0x92, 0x2c, 0x6d, 0x2a, 0x25, 0xfa, 0x46 } }
+
+#define NS_NAVBOOKMARKSSERVICE_CID \
+{ 0x9de95a0c, 0x39a4, 0x4d64, {0x9a, 0x53, 0x17, 0x94, 0x0d, 0xd7, 0xca, 0xbb}}
+
+#define NS_FAVICONSERVICE_CID \
+{ 0x984e3259, 0x9266, 0x49cf, { 0xb6, 0x05, 0x60, 0xb0, 0x22, 0xa0, 0x07, 0x56 } }
+
+#ifdef MOZ_UPDATER
+#define NS_UPDATEPROCESSOR_CID \
+{ 0xf3dcf644, 0x79e8, 0x4f59, { 0xa1, 0xbb, 0x87, 0x84, 0x54, 0x48, 0x8e, 0xf9 } }
+#endif
+
+#define NS_ADDONCONTENTPOLICY_CID \
+{ 0xc26a8241, 0xecf4, 0x4aed, { 0x9f, 0x3c, 0xf1, 0xf5, 0xc7, 0x13, 0xb9, 0xa5 } }
+
+#define NS_ADDON_PATH_SERVICE_CID \
+{ 0xa39f39d0, 0xdfb6, 0x11e3, { 0x8b, 0x68, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
diff --git a/components/build/nsToolkitCompsModule.cpp b/components/build/nsToolkitCompsModule.cpp
new file mode 100644
index 000000000..7e05e6ce7
--- /dev/null
+++ b/components/build/nsToolkitCompsModule.cpp
@@ -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/. */
+
+#include "mozilla/ModuleUtils.h"
+#include "nsAppStartup.h"
+#include "nsAppStartupNotifier.h"
+#include "nsNetCID.h"
+#ifdef MOZ_USERINFO
+#include "nsUserInfo.h"
+#endif
+#include "nsToolkitCompsCID.h"
+#include "nsFindService.h"
+#ifdef MOZ_UPDATER
+#include "nsUpdateDriver.h"
+#endif
+
+#if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
+#include "nsParentalControlsService.h"
+#endif
+
+#include "mozilla/AlertNotification.h"
+#include "nsAlertsService.h"
+
+#include "nsDownloadManager.h"
+#include "DownloadPlatform.h"
+#include "nsDownloadProxy.h"
+#include "rdf.h"
+
+#include "nsTypeAheadFind.h"
+
+#include "nsBrowserStatusFilter.h"
+#include "mozilla/FinalizationWitnessService.h"
+#include "mozilla/NativeOSFileInternals.h"
+#include "mozilla/AddonPathService.h"
+
+#if defined(XP_WIN)
+#include "NativeFileWatcherWin.h"
+#else
+#include "NativeFileWatcherNotSupported.h"
+#endif // (XP_WIN)
+
+#define MOZ_HAS_TERMINATOR
+#include "nsTerminator.h"
+
+#define MOZ_HAS_PERFSTATS
+
+#if defined(MOZ_HAS_PERFSTATS)
+#include "nsPerformanceStats.h"
+#endif // defined (MOZ_HAS_PERFSTATS)
+
+using namespace mozilla;
+
+/////////////////////////////////////////////////////////////////////////////
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppStartup, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAppStartupNotifier)
+
+#if defined(MOZ_HAS_PERFSTATS)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPerformanceStatsService, Init)
+#endif // defined (MOZ_HAS_PERFSTATS)
+
+#if defined(MOZ_HAS_TERMINATOR)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTerminator)
+#endif
+
+#if defined(MOZ_USERINFO)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsUserInfo)
+#endif // defined (MOZ_USERINFO)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFindService)
+
+#if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsParentalControlsService)
+#endif
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(AlertNotification)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAlertsService)
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsDownloadManager,
+ nsDownloadManager::GetSingleton)
+NS_GENERIC_FACTORY_CONSTRUCTOR(DownloadPlatform)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadProxy)
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTypeAheadFind)
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter)
+#ifdef MOZ_UPDATER
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor)
+#endif
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(FinalizationWitnessService, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(NativeOSFileInternalsService)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NativeFileWatcherService, Init)
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AddonPathService, AddonPathService::GetInstance)
+
+NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
+NS_DEFINE_NAMED_CID(NS_APPSTARTUPNOTIFIER_CID);
+#if defined(MOZ_HAS_PERFSTATS)
+NS_DEFINE_NAMED_CID(NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID);
+#endif // defined (MOZ_HAS_PERFSTATS)
+
+#if defined(MOZ_HAS_TERMINATOR)
+NS_DEFINE_NAMED_CID(NS_TOOLKIT_TERMINATOR_CID);
+#endif
+#if defined(MOZ_USERINFO)
+NS_DEFINE_NAMED_CID(NS_USERINFO_CID);
+#endif // defined (MOZ_USERINFO)
+NS_DEFINE_NAMED_CID(ALERT_NOTIFICATION_CID);
+NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID);
+#if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
+NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_DOWNLOADMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_DOWNLOADPLATFORM_CID);
+NS_DEFINE_NAMED_CID(NS_DOWNLOAD_CID);
+NS_DEFINE_NAMED_CID(NS_FIND_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_TYPEAHEADFIND_CID);
+NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILTER_CID);
+#ifdef MOZ_UPDATER
+NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
+#endif
+NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NATIVE_OSFILE_INTERNALS_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_ADDON_PATH_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NATIVE_FILEWATCHER_SERVICE_CID);
+
+static const Module::CIDEntry kToolkitCIDs[] = {
+ { &kNS_TOOLKIT_APPSTARTUP_CID, false, nullptr, nsAppStartupConstructor },
+ { &kNS_APPSTARTUPNOTIFIER_CID, false, nullptr, nsAppStartupNotifierConstructor },
+#if defined(MOZ_HAS_TERMINATOR)
+ { &kNS_TOOLKIT_TERMINATOR_CID, false, nullptr, nsTerminatorConstructor },
+#endif
+#if defined(MOZ_HAS_PERFSTATS)
+ { &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID, false, nullptr, nsPerformanceStatsServiceConstructor },
+#endif // defined (MOZ_HAS_PERFSTATS)
+#if defined(MOZ_USERINFO)
+ { &kNS_USERINFO_CID, false, nullptr, nsUserInfoConstructor },
+#endif // defined (MOZ_USERINFO)
+ { &kALERT_NOTIFICATION_CID, false, nullptr, AlertNotificationConstructor },
+ { &kNS_ALERTSSERVICE_CID, false, nullptr, nsAlertsServiceConstructor },
+#if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
+ { &kNS_PARENTALCONTROLSSERVICE_CID, false, nullptr, nsParentalControlsServiceConstructor },
+#endif
+ { &kNS_DOWNLOADMANAGER_CID, false, nullptr, nsDownloadManagerConstructor },
+ { &kNS_DOWNLOADPLATFORM_CID, false, nullptr, DownloadPlatformConstructor },
+ { &kNS_DOWNLOAD_CID, false, nullptr, nsDownloadProxyConstructor },
+ { &kNS_FIND_SERVICE_CID, false, nullptr, nsFindServiceConstructor },
+ { &kNS_TYPEAHEADFIND_CID, false, nullptr, nsTypeAheadFindConstructor },
+ { &kNS_BROWSERSTATUSFILTER_CID, false, nullptr, nsBrowserStatusFilterConstructor },
+#ifdef MOZ_UPDATER
+ { &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor },
+#endif
+ { &kFINALIZATIONWITNESSSERVICE_CID, false, nullptr, FinalizationWitnessServiceConstructor },
+ { &kNATIVE_OSFILE_INTERNALS_SERVICE_CID, false, nullptr, NativeOSFileInternalsServiceConstructor },
+ { &kNS_ADDON_PATH_SERVICE_CID, false, nullptr, AddonPathServiceConstructor },
+ { &kNATIVE_FILEWATCHER_SERVICE_CID, false, nullptr, NativeFileWatcherServiceConstructor },
+ { nullptr }
+};
+
+static const Module::ContractIDEntry kToolkitContracts[] = {
+ { NS_APPSTARTUP_CONTRACTID, &kNS_TOOLKIT_APPSTARTUP_CID },
+ { NS_APPSTARTUPNOTIFIER_CONTRACTID, &kNS_APPSTARTUPNOTIFIER_CID },
+#if defined(MOZ_HAS_TERMINATOR)
+ { NS_TOOLKIT_TERMINATOR_CONTRACTID, &kNS_TOOLKIT_TERMINATOR_CID },
+#endif
+#if defined(MOZ_HAS_PERFSTATS)
+ { NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID, &kNS_TOOLKIT_PERFORMANCESTATSSERVICE_CID },
+#endif // defined (MOZ_HAS_PERFSTATS)
+#if defined(MOZ_USERINFO)
+ { NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID },
+#endif // defined(MOZ_USERINFO)
+ { ALERT_NOTIFICATION_CONTRACTID, &kALERT_NOTIFICATION_CID },
+ { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID },
+#if !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
+ { NS_PARENTALCONTROLSSERVICE_CONTRACTID, &kNS_PARENTALCONTROLSSERVICE_CID },
+#endif
+ { NS_DOWNLOADMANAGER_CONTRACTID, &kNS_DOWNLOADMANAGER_CID },
+ { NS_DOWNLOADPLATFORM_CONTRACTID, &kNS_DOWNLOADPLATFORM_CID },
+ { NS_FIND_SERVICE_CONTRACTID, &kNS_FIND_SERVICE_CID },
+ { NS_TYPEAHEADFIND_CONTRACTID, &kNS_TYPEAHEADFIND_CID },
+ { NS_BROWSERSTATUSFILTER_CONTRACTID, &kNS_BROWSERSTATUSFILTER_CID },
+#ifdef MOZ_UPDATER
+ { NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
+#endif
+ { FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
+ { NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID, &kNATIVE_OSFILE_INTERNALS_SERVICE_CID },
+ { NS_ADDONPATHSERVICE_CONTRACTID, &kNS_ADDON_PATH_SERVICE_CID },
+ { NATIVE_FILEWATCHER_SERVICE_CONTRACTID, &kNATIVE_FILEWATCHER_SERVICE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kToolkitCategories[] = {
+ { nullptr }
+};
+
+static const Module kToolkitModule = {
+ Module::kVersion,
+ kToolkitCIDs,
+ kToolkitContracts,
+ kToolkitCategories
+};
+
+NSMODULE_DEFN(nsToolkitCompsModule) = &kToolkitModule;
diff --git a/components/captivedetect/CaptivePortalDetectComponents.manifest b/components/captivedetect/CaptivePortalDetectComponents.manifest
new file mode 100644
index 000000000..490545c82
--- /dev/null
+++ b/components/captivedetect/CaptivePortalDetectComponents.manifest
@@ -0,0 +1,2 @@
+component {d9cd00ba-aa4d-47b1-8792-b1fe0cd35060} captivedetect.js
+contract @mozilla.org/toolkit/captive-detector;1 {d9cd00ba-aa4d-47b1-8792-b1fe0cd35060}
diff --git a/components/captivedetect/captivedetect.js b/components/captivedetect/captivedetect.js
new file mode 100644
index 000000000..0e8c75981
--- /dev/null
+++ b/components/captivedetect/captivedetect.js
@@ -0,0 +1,476 @@
+/* -*- 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/. */
+
+'use strict';
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSysMsgr",
+ "@mozilla.org/system-message-internal;1",
+ "nsISystemMessagesInternal");
+
+const DEBUG = false; // set to true to show debug messages
+
+const kCAPTIVEPORTALDETECTOR_CONTRACTID = '@mozilla.org/toolkit/captive-detector;1';
+const kCAPTIVEPORTALDETECTOR_CID = Components.ID('{d9cd00ba-aa4d-47b1-8792-b1fe0cd35060}');
+
+const kOpenCaptivePortalLoginEvent = 'captive-portal-login';
+const kAbortCaptivePortalLoginEvent = 'captive-portal-login-abort';
+const kCaptivePortalLoginSuccessEvent = 'captive-portal-login-success';
+const kCaptivePortalCheckComplete = 'captive-portal-check-complete';
+
+const kCaptivePortalSystemMessage = 'captive-portal';
+
+function URLFetcher(url, timeout) {
+ let self = this;
+ let xhr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
+ .createInstance(Ci.nsIXMLHttpRequest);
+ xhr.open('GET', url, true);
+ // Prevent the request from reading from the cache.
+ xhr.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ // Prevent the request from writing to the cache.
+ xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
+ // Prevent privacy leaks
+ xhr.channel.loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS;
+ // The Cache-Control header is only interpreted by proxies and the
+ // final destination. It does not help if a resource is already
+ // cached locally.
+ xhr.setRequestHeader("Cache-Control", "no-cache");
+ // HTTP/1.0 servers might not implement Cache-Control and
+ // might only implement Pragma: no-cache
+ xhr.setRequestHeader("Pragma", "no-cache");
+
+ xhr.timeout = timeout;
+ xhr.ontimeout = function () { self.ontimeout(); };
+ xhr.onerror = function () { self.onerror(); };
+ xhr.onreadystatechange = function(oEvent) {
+ if (xhr.readyState === 4) {
+ if (self._isAborted) {
+ return;
+ }
+ if (xhr.status === 200) {
+ self.onsuccess(xhr.responseText);
+ } else if (xhr.status) {
+ self.onredirectorerror(xhr.status);
+ }
+ }
+ };
+ xhr.send();
+ this._xhr = xhr;
+}
+
+URLFetcher.prototype = {
+ _isAborted: false,
+ ontimeout: function() {},
+ onerror: function() {},
+ abort: function() {
+ if (!this._isAborted) {
+ this._isAborted = true;
+ this._xhr.abort();
+ }
+ },
+}
+
+function LoginObserver(captivePortalDetector) {
+ const LOGIN_OBSERVER_STATE_DETACHED = 0; /* Should not monitor network activity since no ongoing login procedure */
+ const LOGIN_OBSERVER_STATE_IDLE = 1; /* No network activity currently, waiting for a longer enough idle period */
+ const LOGIN_OBSERVER_STATE_BURST = 2; /* Network activity is detected, probably caused by a login procedure */
+ const LOGIN_OBSERVER_STATE_VERIFY_NEEDED = 3; /* Verifing network accessiblity is required after a long enough idle */
+ const LOGIN_OBSERVER_STATE_VERIFYING = 4; /* LoginObserver is probing if public network is available */
+
+ let state = LOGIN_OBSERVER_STATE_DETACHED;
+
+ let timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
+ let activityDistributor = Cc['@mozilla.org/network/http-activity-distributor;1']
+ .getService(Ci.nsIHttpActivityDistributor);
+ let urlFetcher = null;
+
+ let waitForNetworkActivity = false;
+
+ let pageCheckingDone = function pageCheckingDone() {
+ if (state === LOGIN_OBSERVER_STATE_VERIFYING) {
+ urlFetcher = null;
+ // Finish polling the canonical site, switch back to idle state and
+ // waiting for next burst
+ state = LOGIN_OBSERVER_STATE_IDLE;
+ timer.initWithCallback(observer,
+ captivePortalDetector._pollingTime,
+ timer.TYPE_ONE_SHOT);
+ }
+ };
+
+ let checkPageContent = function checkPageContent() {
+ debug("checking if public network is available after the login procedure");
+
+ urlFetcher = new URLFetcher(captivePortalDetector._canonicalSiteURL,
+ captivePortalDetector._maxWaitingTime);
+ urlFetcher.ontimeout = pageCheckingDone;
+ urlFetcher.onerror = pageCheckingDone;
+ urlFetcher.onsuccess = function (content) {
+ if (captivePortalDetector.validateContent(content)) {
+ urlFetcher = null;
+ captivePortalDetector.executeCallback(true);
+ } else {
+ pageCheckingDone();
+ }
+ };
+ urlFetcher.onredirectorerror = pageCheckingDone;
+ };
+
+ // Public interface of LoginObserver
+ let observer = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIHttpActivityObserver,
+ Ci.nsITimerCallback]),
+
+ attach: function attach() {
+ if (state === LOGIN_OBSERVER_STATE_DETACHED) {
+ activityDistributor.addObserver(this);
+ state = LOGIN_OBSERVER_STATE_IDLE;
+ timer.initWithCallback(this,
+ captivePortalDetector._pollingTime,
+ timer.TYPE_ONE_SHOT);
+ debug('attach HttpObserver for login activity');
+ }
+ },
+
+ detach: function detach() {
+ if (state !== LOGIN_OBSERVER_STATE_DETACHED) {
+ if (urlFetcher) {
+ urlFetcher.abort();
+ urlFetcher = null;
+ }
+ activityDistributor.removeObserver(this);
+ timer.cancel();
+ state = LOGIN_OBSERVER_STATE_DETACHED;
+ debug('detach HttpObserver for login activity');
+ }
+ },
+
+ /*
+ * Treat all HTTP transactions as captive portal login activities.
+ */
+ observeActivity: function observeActivity(aHttpChannel, aActivityType,
+ aActivitySubtype, aTimestamp,
+ aExtraSizeData, aExtraStringData) {
+ if (aActivityType === Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION
+ && aActivitySubtype === Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE) {
+ switch (state) {
+ case LOGIN_OBSERVER_STATE_IDLE:
+ case LOGIN_OBSERVER_STATE_VERIFY_NEEDED:
+ state = LOGIN_OBSERVER_STATE_BURST;
+ break;
+ default:
+ break;
+ }
+ }
+ },
+
+ /*
+ * Check if login activity is finished according to HTTP burst.
+ */
+ notify : function notify() {
+ switch (state) {
+ case LOGIN_OBSERVER_STATE_BURST:
+ // Wait while network stays idle for a short period
+ state = LOGIN_OBSERVER_STATE_VERIFY_NEEDED;
+ // Fall though to start polling timer
+ case LOGIN_OBSERVER_STATE_IDLE:
+ if (waitForNetworkActivity) {
+ timer.initWithCallback(this,
+ captivePortalDetector._pollingTime,
+ timer.TYPE_ONE_SHOT);
+ break;
+ }
+ // if we don't need to wait for network activity, just fall through
+ // to perform a captive portal check.
+ case LOGIN_OBSERVER_STATE_VERIFY_NEEDED:
+ // Polling the canonical website since network stays idle for a while
+ state = LOGIN_OBSERVER_STATE_VERIFYING;
+ checkPageContent();
+ break;
+
+ default:
+ break;
+ }
+ },
+ };
+
+ return observer;
+}
+
+function CaptivePortalDetector() {
+ // Load preference
+ this._canonicalSiteURL = null;
+ this._canonicalSiteExpectedContent = null;
+
+ try {
+ this._canonicalSiteURL =
+ Services.prefs.getCharPref('captivedetect.canonicalURL');
+ this._canonicalSiteExpectedContent =
+ Services.prefs.getCharPref('captivedetect.canonicalContent');
+ } catch (e) {
+ debug('canonicalURL or canonicalContent not set.')
+ }
+
+ this._maxWaitingTime =
+ Services.prefs.getIntPref('captivedetect.maxWaitingTime');
+ this._pollingTime =
+ Services.prefs.getIntPref('captivedetect.pollingTime');
+ this._maxRetryCount =
+ Services.prefs.getIntPref('captivedetect.maxRetryCount');
+ debug('Load Prefs {site=' + this._canonicalSiteURL + ',content='
+ + this._canonicalSiteExpectedContent + ',time=' + this._maxWaitingTime
+ + "max-retry=" + this._maxRetryCount + '}');
+
+ // Create HttpObserver for monitoring the login procedure
+ this._loginObserver = LoginObserver(this);
+
+ this._nextRequestId = 0;
+ this._runningRequest = null;
+ this._requestQueue = []; // Maintain a progress table, store callbacks and the ongoing XHR
+ this._interfaceNames = {}; // Maintain names of the requested network interfaces
+
+ debug('CaptiveProtalDetector initiated, waiting for network connection established');
+}
+
+CaptivePortalDetector.prototype = {
+ classID: kCAPTIVEPORTALDETECTOR_CID,
+ classInfo: XPCOMUtils.generateCI({classID: kCAPTIVEPORTALDETECTOR_CID,
+ contractID: kCAPTIVEPORTALDETECTOR_CONTRACTID,
+ classDescription: 'Captive Portal Detector',
+ interfaces: [Ci.nsICaptivePortalDetector]}),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICaptivePortalDetector]),
+
+ // nsICaptivePortalDetector
+ checkCaptivePortal: function checkCaptivePortal(aInterfaceName, aCallback) {
+ if (!this._canonicalSiteURL) {
+ throw Components.Exception('No canonical URL set up.');
+ }
+
+ // Prevent multiple requests on a single network interface
+ if (this._interfaceNames[aInterfaceName]) {
+ throw Components.Exception('Do not allow multiple request on one interface: ' + aInterfaceName);
+ }
+
+ let request = {interfaceName: aInterfaceName};
+ if (aCallback) {
+ let callback = aCallback.QueryInterface(Ci.nsICaptivePortalCallback);
+ request['callback'] = callback;
+ request['retryCount'] = 0;
+ }
+ this._addRequest(request);
+ },
+
+ abort: function abort(aInterfaceName) {
+ debug('abort for ' + aInterfaceName);
+ this._removeRequest(aInterfaceName);
+ },
+
+ finishPreparation: function finishPreparation(aInterfaceName) {
+ debug('finish preparation phase for interface "' + aInterfaceName + '"');
+ if (!this._runningRequest
+ || this._runningRequest.interfaceName !== aInterfaceName) {
+ debug('invalid finishPreparation for ' + aInterfaceName);
+ throw Components.Exception('only first request is allowed to invoke |finishPreparation|');
+ }
+
+ this._startDetection();
+ },
+
+ cancelLogin: function cancelLogin(eventId) {
+ debug('login canceled by user for request "' + eventId + '"');
+ // Captive portal login procedure is canceled by user
+ if (this._runningRequest && this._runningRequest.hasOwnProperty('eventId')) {
+ let id = this._runningRequest.eventId;
+ if (eventId === id) {
+ this.executeCallback(false);
+ }
+ }
+ },
+
+ _applyDetection: function _applyDetection() {
+ debug('enter applyDetection('+ this._runningRequest.interfaceName + ')');
+
+ // Execute network interface preparation
+ if (this._runningRequest.hasOwnProperty('callback')) {
+ this._runningRequest.callback.prepare();
+ } else {
+ this._startDetection();
+ }
+ },
+
+ _startDetection: function _startDetection() {
+ debug('startDetection {site=' + this._canonicalSiteURL + ',content='
+ + this._canonicalSiteExpectedContent + ',time=' + this._maxWaitingTime + '}');
+ let self = this;
+
+ let urlFetcher = new URLFetcher(this._canonicalSiteURL, this._maxWaitingTime);
+
+ let mayRetry = this._mayRetry.bind(this);
+
+ urlFetcher.ontimeout = mayRetry;
+ urlFetcher.onerror = mayRetry;
+ urlFetcher.onsuccess = function (content) {
+ if (self.validateContent(content)) {
+ self.executeCallback(true);
+ } else {
+ // Content of the canonical website has been overwrite
+ self._startLogin();
+ }
+ };
+ urlFetcher.onredirectorerror = function (status) {
+ if (status >= 300 && status <= 399) {
+ // The canonical website has been redirected to an unknown location
+ self._startLogin();
+ } else {
+ mayRetry();
+ }
+ };
+
+ this._runningRequest['urlFetcher'] = urlFetcher;
+ },
+
+ _startLogin: function _startLogin() {
+ let id = this._allocateRequestId();
+ let details = {
+ type: kOpenCaptivePortalLoginEvent,
+ id: id,
+ url: this._canonicalSiteURL,
+ };
+ this._loginObserver.attach();
+ this._runningRequest['eventId'] = id;
+ this._sendEvent(kOpenCaptivePortalLoginEvent, details);
+ gSysMsgr.broadcastMessage(kCaptivePortalSystemMessage, {});
+ },
+
+ _mayRetry: function _mayRetry() {
+ if (this._runningRequest.retryCount++ < this._maxRetryCount) {
+ debug('retry-Detection: ' + this._runningRequest.retryCount + '/' + this._maxRetryCount);
+ this._startDetection();
+ } else {
+ this.executeCallback(false);
+ }
+ },
+
+ executeCallback: function executeCallback(success) {
+ if (this._runningRequest) {
+ debug('callback executed');
+ if (this._runningRequest.hasOwnProperty('callback')) {
+ this._runningRequest.callback.complete(success);
+ }
+
+ // Only when the request has a event id and |success| is true
+ // do we need to notify the login-success event.
+ if (this._runningRequest.hasOwnProperty('eventId') && success) {
+ let details = {
+ type: kCaptivePortalLoginSuccessEvent,
+ id: this._runningRequest['eventId'],
+ };
+ this._sendEvent(kCaptivePortalLoginSuccessEvent, details);
+ }
+
+ // Continue the following request
+ this._runningRequest['complete'] = true;
+ this._removeRequest(this._runningRequest.interfaceName);
+ }
+ },
+
+ _sendEvent: function _sendEvent(topic, details) {
+ debug('sendEvent "' + JSON.stringify(details) + '"');
+ Services.obs.notifyObservers(this,
+ topic,
+ JSON.stringify(details));
+ },
+
+ validateContent: function validateContent(content) {
+ debug('received content: ' + content);
+ let valid = content === this._canonicalSiteExpectedContent;
+ // We need a way to indicate that a check has been performed, and if we are
+ // still in a captive portal.
+ this._sendEvent(kCaptivePortalCheckComplete, !valid);
+ return valid;
+ },
+
+ _allocateRequestId: function _allocateRequestId() {
+ let newId = this._nextRequestId++;
+ return newId.toString();
+ },
+
+ _runNextRequest: function _runNextRequest() {
+ let nextRequest = this._requestQueue.shift();
+ if (nextRequest) {
+ this._runningRequest = nextRequest;
+ this._applyDetection();
+ }
+ },
+
+ _addRequest: function _addRequest(request) {
+ this._interfaceNames[request.interfaceName] = true;
+ this._requestQueue.push(request);
+ if (!this._runningRequest) {
+ this._runNextRequest();
+ }
+ },
+
+ _removeRequest: function _removeRequest(aInterfaceName) {
+ if (!this._interfaceNames[aInterfaceName]) {
+ return;
+ }
+
+ delete this._interfaceNames[aInterfaceName];
+
+ if (this._runningRequest
+ && this._runningRequest.interfaceName === aInterfaceName) {
+ this._loginObserver.detach();
+
+ if (!this._runningRequest.complete) {
+ // Abort the user login procedure
+ if (this._runningRequest.hasOwnProperty('eventId')) {
+ let details = {
+ type: kAbortCaptivePortalLoginEvent,
+ id: this._runningRequest.eventId
+ };
+ this._sendEvent(kAbortCaptivePortalLoginEvent, details);
+ }
+
+ // Abort the ongoing HTTP request
+ if (this._runningRequest.hasOwnProperty('urlFetcher')) {
+ this._runningRequest.urlFetcher.abort();
+ }
+ }
+
+ debug('remove running request');
+ this._runningRequest = null;
+
+ // Continue next pending reqeust if the ongoing one has been aborted
+ this._runNextRequest();
+ return;
+ }
+
+ // Check if a pending request has been aborted
+ for (let i = 0; i < this._requestQueue.length; i++) {
+ if (this._requestQueue[i].interfaceName == aInterfaceName) {
+ this._requestQueue.splice(i, 1);
+
+ debug('remove pending request #' + i + ', remaining ' + this._requestQueue.length);
+ break;
+ }
+ }
+ },
+};
+
+var debug;
+if (DEBUG) {
+ debug = function (s) {
+ dump('-*- CaptivePortalDetector component: ' + s + '\n');
+ };
+} else {
+ debug = function (s) {};
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CaptivePortalDetector]);
diff --git a/components/captivedetect/moz.build b/components/captivedetect/moz.build
new file mode 100644
index 000000000..73ed5043e
--- /dev/null
+++ b/components/captivedetect/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['nsICaptivePortalDetector.idl']
+XPIDL_MODULE = 'captivedetect'
+
+EXTRA_COMPONENTS += [
+ 'captivedetect.js',
+ 'CaptivePortalDetectComponents.manifest',
+]
+
diff --git a/components/captivedetect/nsICaptivePortalDetector.idl b/components/captivedetect/nsICaptivePortalDetector.idl
new file mode 100644
index 000000000..631685451
--- /dev/null
+++ b/components/captivedetect/nsICaptivePortalDetector.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: IDL; 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"
+
+[scriptable, uuid(593fdeec-6284-4de8-b416-8e63cbdc695e)]
+interface nsICaptivePortalCallback : nsISupports
+{
+ /**
+ * Preparation for network interface before captive portal detection started.
+ */
+ void prepare();
+
+ /**
+ * Invoke callbacks after captive portal detection finished.
+ */
+ void complete(in bool success);
+};
+
+[scriptable, uuid(2f827c5a-f551-477f-af09-71adbfbd854a)]
+interface nsICaptivePortalDetector : nsISupports
+{
+ /**
+ * Perform captive portal detection on specific network interface.
+ * @param ifname The name of network interface, exception will be thrwon
+ * if the same interface has unfinished request.
+ * @param callback Callbacks when detection procedure starts and finishes.
+ */
+ void checkCaptivePortal(in wstring ifname,
+ in nsICaptivePortalCallback callback);
+
+ /**
+ * Abort captive portal detection for specific network interface
+ * due to system failure, callback will not be invoked.
+ * @param ifname The name of network interface.
+ */
+ void abort(in wstring ifname);
+
+ /**
+ * Cancel captive portal login procedure by user, callback will be invoked.
+ * @param eventId Login event id provided in |captive-portal-login| event.
+ */
+ void cancelLogin(in wstring eventId);
+
+ /**
+ * Notify prepare phase is finished, routing and dns must be ready for sending
+ * out XMLHttpRequest. this is callback for CaptivePortalDetector API user.
+ * @param ifname The name of network interface, must be unique.
+ */
+ void finishPreparation(in wstring ifname);
+};
diff --git a/components/commandlines/moz.build b/components/commandlines/moz.build
new file mode 100644
index 000000000..92a4092d4
--- /dev/null
+++ b/components/commandlines/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'nsICommandLine.idl',
+ 'nsICommandLineHandler.idl',
+ 'nsICommandLineRunner.idl',
+ 'nsICommandLineValidator.idl',
+]
+
+XPIDL_MODULE = 'commandlines'
+
+SOURCES += ['nsCommandLine.cpp']
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/commandlines/nsCommandLine.cpp b/components/commandlines/nsCommandLine.cpp
new file mode 100644
index 000000000..07eccfc80
--- /dev/null
+++ b/components/commandlines/nsCommandLine.cpp
@@ -0,0 +1,633 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsICommandLineRunner.h"
+
+#include "nsICategoryManager.h"
+#include "nsICommandLineHandler.h"
+#include "nsICommandLineValidator.h"
+#include "nsIConsoleService.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIDOMWindow.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIStringEnumerator.h"
+
+#include "nsCOMPtr.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsISupportsImpl.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsNetUtil.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsUnicharUtils.h"
+#include "nsTArray.h"
+#include "nsTextFormatter.h"
+#include "nsXPCOMCID.h"
+#include "plstr.h"
+#include "mozilla/Attributes.h"
+
+#if defined(XP_WIN)
+#include <windows.h>
+#include <shlobj.h>
+#elif defined(XP_UNIX)
+#include <unistd.h>
+#endif
+
+#ifdef DEBUG_bsmedberg
+#define DEBUG_COMMANDLINE
+#endif
+
+#define NS_COMMANDLINE_CID \
+ { 0x23bcc750, 0xdc20, 0x460b, { 0xb2, 0xd4, 0x74, 0xd8, 0xf5, 0x8d, 0x36, 0x15 } }
+
+class nsCommandLine final : public nsICommandLineRunner
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOMMANDLINE
+ NS_DECL_NSICOMMANDLINERUNNER
+
+ nsCommandLine();
+
+protected:
+ ~nsCommandLine() { }
+
+ typedef nsresult (*EnumerateHandlersCallback)(nsICommandLineHandler* aHandler,
+ nsICommandLine* aThis,
+ void *aClosure);
+ typedef nsresult (*EnumerateValidatorsCallback)(nsICommandLineValidator* aValidator,
+ nsICommandLine* aThis,
+ void *aClosure);
+
+ void appendArg(const char* arg);
+ MOZ_MUST_USE nsresult resolveShortcutURL(nsIFile* aFile, nsACString& outURL);
+ nsresult EnumerateHandlers(EnumerateHandlersCallback aCallback, void *aClosure);
+ nsresult EnumerateValidators(EnumerateValidatorsCallback aCallback, void *aClosure);
+
+ nsTArray<nsString> mArgs;
+ uint32_t mState;
+ nsCOMPtr<nsIFile> mWorkingDir;
+ nsCOMPtr<nsIDOMWindow> mWindowContext;
+ bool mPreventDefault;
+};
+
+nsCommandLine::nsCommandLine() :
+ mState(STATE_INITIAL_LAUNCH),
+ mPreventDefault(false)
+{
+
+}
+
+
+NS_IMPL_CLASSINFO(nsCommandLine, nullptr, 0, NS_COMMANDLINE_CID)
+NS_IMPL_ISUPPORTS_CI(nsCommandLine,
+ nsICommandLine,
+ nsICommandLineRunner)
+
+NS_IMETHODIMP
+nsCommandLine::GetLength(int32_t *aResult)
+{
+ *aResult = int32_t(mArgs.Length());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::GetArgument(int32_t aIndex, nsAString& aResult)
+{
+ NS_ENSURE_ARG_MIN(aIndex, 0);
+ NS_ENSURE_ARG_MAX(aIndex, int32_t(mArgs.Length() - 1));
+
+ aResult = mArgs[aIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::FindFlag(const nsAString& aFlag, bool aCaseSensitive, int32_t *aResult)
+{
+ NS_ENSURE_ARG(!aFlag.IsEmpty());
+
+ nsDefaultStringComparator caseCmp;
+ nsCaseInsensitiveStringComparator caseICmp;
+ nsStringComparator& c = aCaseSensitive ?
+ static_cast<nsStringComparator&>(caseCmp) :
+ static_cast<nsStringComparator&>(caseICmp);
+
+ for (uint32_t f = 0; f < mArgs.Length(); f++) {
+ const nsString &arg = mArgs[f];
+
+ if (arg.Length() >= 2 && arg.First() == char16_t('-')) {
+ if (aFlag.Equals(Substring(arg, 1), c)) {
+ *aResult = f;
+ return NS_OK;
+ }
+ }
+ }
+
+ *aResult = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::RemoveArguments(int32_t aStart, int32_t aEnd)
+{
+ NS_ENSURE_ARG_MIN(aStart, 0);
+ NS_ENSURE_ARG_MAX(uint32_t(aEnd) + 1, mArgs.Length());
+
+ for (int32_t i = aEnd; i >= aStart; --i) {
+ mArgs.RemoveElementAt(i);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::HandleFlag(const nsAString& aFlag, bool aCaseSensitive,
+ bool *aResult)
+{
+ nsresult rv;
+
+ int32_t found;
+ rv = FindFlag(aFlag, aCaseSensitive, &found);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (found == -1) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ *aResult = true;
+ RemoveArguments(found, found);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::HandleFlagWithParam(const nsAString& aFlag, bool aCaseSensitive,
+ nsAString& aResult)
+{
+ nsresult rv;
+
+ int32_t found;
+ rv = FindFlag(aFlag, aCaseSensitive, &found);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (found == -1) {
+ aResult.SetIsVoid(true);
+ return NS_OK;
+ }
+
+ if (found == int32_t(mArgs.Length()) - 1) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ++found;
+
+ if (mArgs[found].First() == '-') {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ aResult = mArgs[found];
+ RemoveArguments(found - 1, found);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::GetState(uint32_t *aResult)
+{
+ *aResult = mState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::GetPreventDefault(bool *aResult)
+{
+ *aResult = mPreventDefault;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::SetPreventDefault(bool aValue)
+{
+ mPreventDefault = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::GetWorkingDirectory(nsIFile* *aResult)
+{
+ NS_ENSURE_TRUE(mWorkingDir, NS_ERROR_NOT_INITIALIZED);
+
+ NS_ADDREF(*aResult = mWorkingDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::GetWindowContext(nsIDOMWindow* *aResult)
+{
+ NS_IF_ADDREF(*aResult = mWindowContext);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::SetWindowContext(nsIDOMWindow* aValue)
+{
+ mWindowContext = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::ResolveFile(const nsAString& aArgument, nsIFile* *aResult)
+{
+ NS_ENSURE_TRUE(mWorkingDir, NS_ERROR_NOT_INITIALIZED);
+
+ // This is some seriously screwed-up code. nsIFile.appendRelativeNativePath
+ // explicitly does not accept .. or . path parts, but that is exactly what we
+ // need here. So we hack around it.
+
+ nsresult rv;
+
+#if defined(XP_UNIX)
+ nsCOMPtr<nsIFile> lf (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+ NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY);
+
+ if (aArgument.First() == '/') {
+ // absolute path
+ rv = lf->InitWithPath(aArgument);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*aResult = lf);
+ return NS_OK;
+ }
+
+ nsAutoCString nativeArg;
+ NS_CopyUnicodeToNative(aArgument, nativeArg);
+
+ nsAutoCString newpath;
+#ifdef XP_WIN
+ mWorkingDir->GetPersistentDescriptor(newpath);
+#else
+ mWorkingDir->GetNativePath(newpath);
+#endif
+
+ newpath.Append('/');
+ newpath.Append(nativeArg);
+
+ rv = lf->InitWithNativePath(newpath);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = lf->Normalize();
+ if (NS_FAILED(rv)) return rv;
+
+ lf.forget(aResult);
+ return NS_OK;
+
+#elif defined(XP_WIN32)
+ nsCOMPtr<nsIFile> lf (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+ NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = lf->InitWithPath(aArgument);
+ if (NS_FAILED(rv)) {
+ // If it's a relative path, the Init is *going* to fail. We use string magic and
+ // win32 _fullpath. Note that paths of the form "\Relative\To\CurDrive" are
+ // going to fail, and I haven't figured out a way to work around this without
+ // the PathCombine() function, which is not available in plain win95/nt4
+
+ nsAutoString fullPath;
+ mWorkingDir->GetPath(fullPath);
+
+ fullPath.Append('\\');
+ fullPath.Append(aArgument);
+
+ WCHAR pathBuf[MAX_PATH];
+ if (!_wfullpath(pathBuf, fullPath.get(), MAX_PATH))
+ return NS_ERROR_FAILURE;
+
+ rv = lf->InitWithPath(nsDependentString(pathBuf));
+ if (NS_FAILED(rv)) return rv;
+ }
+ lf.forget(aResult);
+ return NS_OK;
+
+#else
+#error Need platform-specific logic here.
+#endif
+}
+
+NS_IMETHODIMP
+nsCommandLine::ResolveURI(const nsAString& aArgument, nsIURI* *aResult)
+{
+ nsresult rv;
+
+ // First, we try to init the argument as an absolute file path. If this doesn't
+ // work, it is an absolute or relative URI.
+
+ nsCOMPtr<nsIIOService> io = do_GetIOService();
+ NS_ENSURE_TRUE(io, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMPtr<nsIURI> workingDirURI;
+ if (mWorkingDir) {
+ io->NewFileURI(mWorkingDir, getter_AddRefs(workingDirURI));
+ }
+
+ nsCOMPtr<nsIFile> lf (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+ rv = lf->InitWithPath(aArgument);
+ if (NS_SUCCEEDED(rv)) {
+ lf->Normalize();
+ nsAutoCString url;
+ // Try to resolve the url for .url files.
+ rv = resolveShortcutURL(lf, url);
+ if (NS_SUCCEEDED(rv) && !url.IsEmpty()) {
+ return io->NewURI(url,
+ nullptr,
+ workingDirURI,
+ aResult);
+ }
+
+ return io->NewFileURI(lf, aResult);
+ }
+
+ return io->NewURI(NS_ConvertUTF16toUTF8(aArgument),
+ nullptr,
+ workingDirURI,
+ aResult);
+}
+
+void
+nsCommandLine::appendArg(const char* arg)
+{
+#ifdef DEBUG_COMMANDLINE
+ printf("Adding XP arg: %s\n", arg);
+#endif
+
+ nsAutoString warg;
+#ifdef XP_WIN
+ CopyUTF8toUTF16(nsDependentCString(arg), warg);
+#else
+ NS_CopyNativeToUnicode(nsDependentCString(arg), warg);
+#endif
+
+ mArgs.AppendElement(warg);
+}
+
+nsresult
+nsCommandLine::resolveShortcutURL(nsIFile* aFile, nsACString& outURL)
+{
+ nsCOMPtr<nsIFileProtocolHandler> fph;
+ nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return uri->GetSpec(outURL);
+}
+
+NS_IMETHODIMP
+nsCommandLine::Init(int32_t argc, const char* const* argv, nsIFile* aWorkingDir,
+ uint32_t aState)
+{
+ NS_ENSURE_ARG_MAX(aState, 2);
+
+ int32_t i;
+
+ mWorkingDir = aWorkingDir;
+
+ // skip argv[0], we don't want it
+ for (i = 1; i < argc; ++i) {
+ const char* curarg = argv[i];
+
+#ifdef DEBUG_COMMANDLINE
+ printf("Testing native arg %i: '%s'\n", i, curarg);
+#endif
+#if defined(XP_WIN)
+ if (*curarg == '/') {
+ char* dup = PL_strdup(curarg);
+ if (!dup) return NS_ERROR_OUT_OF_MEMORY;
+
+ *dup = '-';
+ char* colon = PL_strchr(dup, ':');
+ if (colon) {
+ *colon = '\0';
+ appendArg(dup);
+ appendArg(colon+1);
+ } else {
+ appendArg(dup);
+ }
+ PL_strfree(dup);
+ continue;
+ }
+#endif
+#ifdef XP_UNIX
+ if (*curarg == '-' &&
+ *(curarg+1) == '-') {
+ ++curarg;
+
+ char* dup = PL_strdup(curarg);
+ if (!dup) return NS_ERROR_OUT_OF_MEMORY;
+
+ char* eq = PL_strchr(dup, '=');
+ if (eq) {
+ *eq = '\0';
+ appendArg(dup);
+ appendArg(eq + 1);
+ } else {
+ appendArg(dup);
+ }
+ PL_strfree(dup);
+ continue;
+ }
+#endif
+
+ appendArg(curarg);
+ }
+
+ mState = aState;
+
+ return NS_OK;
+}
+
+static void
+LogConsoleMessage(const char16_t* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ char16_t* msg = nsTextFormatter::vsmprintf(fmt, args);
+ va_end(args);
+
+ nsCOMPtr<nsIConsoleService> cs = do_GetService("@mozilla.org/consoleservice;1");
+ if (cs)
+ cs->LogStringMessage(msg);
+
+ free(msg);
+}
+
+nsresult
+nsCommandLine::EnumerateHandlers(EnumerateHandlersCallback aCallback, void *aClosure)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICategoryManager> catman
+ (do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
+ NS_ENSURE_TRUE(catman, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsISimpleEnumerator> entenum;
+ rv = catman->EnumerateCategory("command-line-handler",
+ getter_AddRefs(entenum));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIUTF8StringEnumerator> strenum (do_QueryInterface(entenum));
+ NS_ENSURE_TRUE(strenum, NS_ERROR_UNEXPECTED);
+
+ nsAutoCString entry;
+ bool hasMore;
+ while (NS_SUCCEEDED(strenum->HasMore(&hasMore)) && hasMore) {
+ strenum->GetNext(entry);
+
+ nsCString contractID;
+ rv = catman->GetCategoryEntry("command-line-handler",
+ entry.get(),
+ getter_Copies(contractID));
+ if (NS_FAILED(rv))
+ continue;
+
+ nsCOMPtr<nsICommandLineHandler> clh(do_GetService(contractID.get()));
+ if (!clh) {
+ LogConsoleMessage(u"Contract ID '%s' was registered as a command line handler for entry '%s', but could not be created.",
+ contractID.get(), entry.get());
+ continue;
+ }
+
+ rv = (aCallback)(clh, this, aClosure);
+ if (rv == NS_ERROR_ABORT)
+ break;
+
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+nsresult
+nsCommandLine::EnumerateValidators(EnumerateValidatorsCallback aCallback, void *aClosure)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICategoryManager> catman
+ (do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
+ NS_ENSURE_TRUE(catman, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsISimpleEnumerator> entenum;
+ rv = catman->EnumerateCategory("command-line-validator",
+ getter_AddRefs(entenum));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIUTF8StringEnumerator> strenum (do_QueryInterface(entenum));
+ NS_ENSURE_TRUE(strenum, NS_ERROR_UNEXPECTED);
+
+ nsAutoCString entry;
+ bool hasMore;
+ while (NS_SUCCEEDED(strenum->HasMore(&hasMore)) && hasMore) {
+ strenum->GetNext(entry);
+
+ nsXPIDLCString contractID;
+ rv = catman->GetCategoryEntry("command-line-validator",
+ entry.get(),
+ getter_Copies(contractID));
+ if (!contractID)
+ continue;
+
+ nsCOMPtr<nsICommandLineValidator> clv(do_GetService(contractID.get()));
+ if (!clv)
+ continue;
+
+ rv = (aCallback)(clv, this, aClosure);
+ if (rv == NS_ERROR_ABORT)
+ break;
+
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+static nsresult
+EnumValidate(nsICommandLineValidator* aValidator, nsICommandLine* aThis, void*)
+{
+ return aValidator->Validate(aThis);
+}
+
+static nsresult
+EnumRun(nsICommandLineHandler* aHandler, nsICommandLine* aThis, void*)
+{
+ return aHandler->Handle(aThis);
+}
+
+NS_IMETHODIMP
+nsCommandLine::Run()
+{
+ nsresult rv;
+
+ rv = EnumerateValidators(EnumValidate, nullptr);
+ if (rv == NS_ERROR_ABORT)
+ return rv;
+
+ rv = EnumerateHandlers(EnumRun, nullptr);
+ if (rv == NS_ERROR_ABORT)
+ return rv;
+
+ return NS_OK;
+}
+
+static nsresult
+EnumHelp(nsICommandLineHandler* aHandler, nsICommandLine* aThis, void* aClosure)
+{
+ nsresult rv;
+
+ nsCString text;
+ rv = aHandler->GetHelpInfo(text);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(text.Length() == 0 || text.Last() == '\n',
+ "Help text from command line handlers should end in a newline.");
+
+ nsACString* totalText = reinterpret_cast<nsACString*>(aClosure);
+ totalText->Append(text);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandLine::GetHelpText(nsACString& aResult)
+{
+ EnumerateHandlers(EnumHelp, &aResult);
+
+ return NS_OK;
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandLine)
+
+NS_DEFINE_NAMED_CID(NS_COMMANDLINE_CID);
+
+static const mozilla::Module::CIDEntry kCommandLineCIDs[] = {
+ { &kNS_COMMANDLINE_CID, false, nullptr, nsCommandLineConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kCommandLineContracts[] = {
+ { "@mozilla.org/toolkit/command-line;1", &kNS_COMMANDLINE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kCommandLineModule = {
+ mozilla::Module::kVersion,
+ kCommandLineCIDs,
+ kCommandLineContracts
+};
+
+NSMODULE_DEFN(CommandLineModule) = &kCommandLineModule;
diff --git a/components/commandlines/nsICommandLine.idl b/components/commandlines/nsICommandLine.idl
new file mode 100644
index 000000000..e44b3de9a
--- /dev/null
+++ b/components/commandlines/nsICommandLine.idl
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIURI;
+interface nsIDOMWindow;
+
+/**
+ * Represents the command line used to invoke a XUL application. This may be the
+ * original command-line of this instance, or a command line remoted from another
+ * instance of the application.
+ *
+ * DEFINITIONS:
+ * "arguments" are any values found on the command line.
+ * "flags" are switches. In normalized form they are preceded by a single dash.
+ * Some flags may take "parameters", e.g. "--url <param>".
+ */
+
+[scriptable, uuid(bc3173bd-aa46-46a0-9d25-d9867a9659b6)]
+interface nsICommandLine : nsISupports
+{
+ /**
+ * Number of arguments in the command line. The application name is not
+ * part of the command line.
+ */
+ readonly attribute long length;
+
+ /**
+ * Get an argument from the array of command-line arguments.
+ *
+ * On windows, flags of the form /flag are normalized to -flag. /flag:param
+ * are normalized to -flag param.
+ *
+ * On *nix and mac flags of the form --flag are normalized to -flag. --flag=param
+ * are normalized to the form -flag param.
+ *
+ * @param aIndex The argument to retrieve. This index is 0-based, and does
+ * not include the application name.
+ * @return The indexth argument.
+ * @throws NS_ERROR_INVALID_ARG if aIndex is out of bounds.
+ */
+ AString getArgument(in long aIndex);
+
+ /**
+ * Find a command-line flag.
+ *
+ * @param aFlag The flag name to locate. Do not include the initial
+ * hyphen.
+ * @param aCaseSensitive Whether to do case-sensitive comparisons.
+ * @return The position of the flag in the command line.
+ */
+ long findFlag(in AString aFlag, in boolean aCaseSensitive);
+
+ /**
+ * Remove arguments from the command line. This normally occurs after
+ * a handler has processed the arguments.
+ *
+ * @param aStart Index to begin removing.
+ * @param aEnd Index to end removing, inclusive.
+ */
+ void removeArguments(in long aStart, in long aEnd);
+
+ /**
+ * A helper method which will find a flag and remove it in one step.
+ *
+ * @param aFlag The flag name to find and remove.
+ * @param aCaseSensitive Whether to do case-sensitive comparisons.
+ * @return Whether the flag was found.
+ */
+ boolean handleFlag(in AString aFlag, in boolean aCaseSensitive);
+
+ /**
+ * Find a flag with a parameter and remove both. This is a helper
+ * method that combines "findFlag" and "removeArguments" in one step.
+ *
+ * @return null (a void astring) if the flag is not found. The parameter value
+ * if found. Note that null and the empty string are not the same.
+ * @throws NS_ERROR_INVALID_ARG if the flag exists without a parameter
+ *
+ * @param aFlag The flag name to find and remove.
+ * @param aCaseSensitive Whether to do case-sensitive flag search.
+ */
+ AString handleFlagWithParam(in AString aFlag, in boolean aCaseSensitive);
+
+ /**
+ * The type of command line being processed.
+ *
+ * STATE_INITIAL_LAUNCH is the first launch of the application instance.
+ * STATE_REMOTE_AUTO is a remote command line automatically redirected to
+ * this instance.
+ * STATE_REMOTE_EXPLICIT is a remote command line explicitly redirected to
+ * this instance using xremote/windde/appleevents.
+ */
+ readonly attribute unsigned long state;
+
+ const unsigned long STATE_INITIAL_LAUNCH = 0;
+ const unsigned long STATE_REMOTE_AUTO = 1;
+ const unsigned long STATE_REMOTE_EXPLICIT = 2;
+
+ /**
+ * There may be a command-line handler which performs a default action if
+ * there was no explicit action on the command line (open a default browser
+ * window, for example). This flag allows the default action to be prevented.
+ */
+ attribute boolean preventDefault;
+
+ /**
+ * The working directory for this command line. Use this property instead
+ * of the working directory for the current process, since a redirected
+ * command line may have had a different working directory.
+ */
+ readonly attribute nsIFile workingDirectory;
+
+ /**
+ * A window to be targeted by this command line. In most cases, this will
+ * be null (xremote will sometimes set this attribute).
+ */
+ readonly attribute nsIDOMWindow windowContext;
+
+ /**
+ * Resolve a file-path argument into an nsIFile. This method gracefully
+ * handles relative or absolute file paths, according to the working
+ * directory of this command line.
+ *
+ * @param aArgument The command-line argument to resolve.
+ */
+ nsIFile resolveFile(in AString aArgument);
+
+ /**
+ * Resolves a URI argument into a URI. This method has platform-specific
+ * logic for converting an absolute URI or a relative file-path into the
+ * appropriate URI object; it gracefully handles win32 C:\ paths which would
+ * confuse the ioservice if passed directly.
+ *
+ * @param aArgument The command-line argument to resolve.
+ */
+ nsIURI resolveURI(in AString aArgument);
+};
diff --git a/components/commandlines/nsICommandLineHandler.idl b/components/commandlines/nsICommandLineHandler.idl
new file mode 100644
index 000000000..cd042d6a5
--- /dev/null
+++ b/components/commandlines/nsICommandLineHandler.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsICommandLine;
+
+/**
+ * Handles arguments on the command line of an XUL application.
+ *
+ * Each handler is registered in the category "command-line-handler".
+ * The entries in this category are read in alphabetical order, and each
+ * category value is treated as a service contractid implementing this
+ * interface.
+ *
+ * By convention, handler with ordinary priority should begin with "m".
+ *
+ * Example:
+ * Category Entry Value
+ * command-line-handler c-extensions @mozilla.org/extension-manager/clh;1
+ * command-line-handler m-edit @mozilla.org/composer/clh;1
+ * command-line-handler m-irc @mozilla.org/chatzilla/clh;1
+ * command-line-handler y-final @mozilla.org/browser/clh-final;1
+ *
+ * @note What do we do about localizing helpInfo? Do we make each handler do it,
+ * or provide a generic solution of some sort? Don't freeze this interface
+ * without thinking about this!
+ */
+
+[scriptable, uuid(d4b123df-51ee-48b1-a663-002180e60d3b)]
+interface nsICommandLineHandler : nsISupports
+{
+ /**
+ * Process a command line. If this handler finds arguments that it
+ * understands, it should perform the appropriate actions (such as opening
+ * a window), and remove the arguments from the command-line array.
+ *
+ * @throw NS_ERROR_ABORT to immediately cease command-line handling
+ * (if this is STATE_INITIAL_LAUNCH, quits the app).
+ * All other exceptions are silently ignored.
+ */
+ void handle(in nsICommandLine aCommandLine);
+
+ /**
+ * When the app is launched with the --help argument, this attribute
+ * is retrieved and displayed to the user (on stdout). The text should
+ * have embedded newlines which wrap at 76 columns, and should include
+ * a newline at the end. By convention, the right column which contains flag
+ * descriptions begins at the 24th character.
+ */
+ readonly attribute AUTF8String helpInfo;
+};
diff --git a/components/commandlines/nsICommandLineRunner.idl b/components/commandlines/nsICommandLineRunner.idl
new file mode 100644
index 000000000..0cd3f0c90
--- /dev/null
+++ b/components/commandlines/nsICommandLineRunner.idl
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsICommandLine.idl"
+
+[ptr] native nsArgvArray(const char* const);
+
+/**
+ * Extension of nsICommandLine that allows for initialization of new command lines
+ * and running the command line actions by processing the command line handlers.
+ *
+ * @status INTERNAL - This interface is not meant for use by embedders, and is
+ * not intended to be frozen. If you are an embedder and need
+ * functionality provided by this interface, talk to Benjamin
+ * Smedberg <benjamin@smedbergs.us>.
+ */
+
+[uuid(c9f2996c-b25a-4d3d-821f-4cd0c4bc8afb)]
+interface nsICommandLineRunner : nsICommandLine
+{
+ /**
+ * This method assumes a native character set, and is meant to be called
+ * with the argc/argv passed to main(). Talk to bsmedberg if you need to
+ * create a command line using other data. argv will not be altered in any
+ * way.
+ *
+ * On Windows, the "native" character set is UTF-8, not the native codepage.
+ *
+ * @param workingDir The working directory for resolving file and URI paths.
+ * @param state The nsICommandLine.state flag.
+ */
+ void init(in long argc, in nsArgvArray argv,
+ in nsIFile workingDir, in unsigned long state);
+
+ /**
+ * Set the windowContext parameter.
+ */
+ void setWindowContext(in nsIDOMWindow aWindow);
+
+ /**
+ * Process the command-line handlers in the proper order, calling "handle()" on
+ * each.
+ *
+ * @throws NS_ERROR_ABORT if any handler throws NS_ERROR_ABORT. All other errors
+ * thrown by handlers will be silently ignored.
+ */
+ void run();
+
+ /**
+ * Process and combine the help text provided by each command-line handler.
+ */
+ readonly attribute AUTF8String helpText;
+};
diff --git a/components/commandlines/nsICommandLineValidator.idl b/components/commandlines/nsICommandLineValidator.idl
new file mode 100644
index 000000000..eba949bf3
--- /dev/null
+++ b/components/commandlines/nsICommandLineValidator.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsICommandLine;
+
+/**
+ * Validates arguments on the command line of an XUL application.
+ *
+ * Each validator is registered in the category "command-line-validator".
+ * The entries in this category are read in alphabetical order, and each
+ * category value is treated as a service contractid implementing this
+ * interface.
+ *
+ * By convention, validator with ordinary priority should begin with "m".
+ *
+ * Example:
+ * Category Entry Value
+ * command-line-validator b-browser @mozilla.org/browser/clh;1
+ * command-line-validator m-edit @mozilla.org/composer/clh;1
+ * command-line-validator m-irc @mozilla.org/chatzilla/clh;1
+ *
+ */
+
+[scriptable, uuid(5ecaa593-7660-4a3a-957a-92d5770671c7)]
+interface nsICommandLineValidator : nsISupports
+{
+ /**
+ * Process the command-line validators in the proper order, calling
+ * "validate()" on each.
+ *
+ * @throws NS_ERROR_ABORT if any validator throws NS_ERROR_ABORT. All other
+ * errors thrown by validators will be silently ignored.
+ */
+ void validate(in nsICommandLine aCommandLine);
+};
diff --git a/components/console/content/console.css b/components/console/content/console.css
new file mode 100644
index 000000000..e05f13c3e
--- /dev/null
+++ b/components/console/content/console.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/. */
+
+
+.console-box {
+ -moz-binding: url("chrome://global/content/consoleBindings.xml#console-box");
+ overflow: auto;
+}
+
+.console-rows {
+ -moz-user-focus: normal;
+}
+
+.console-row[type="error"],
+.console-row[type="warning"] {
+ -moz-binding: url("chrome://global/content/consoleBindings.xml#error");
+}
+
+.console-row[type="message"] {
+ -moz-binding: url("chrome://global/content/consoleBindings.xml#message");
+}
+
+.console-msg-text,
+.console-error-msg {
+ white-space: pre-wrap;
+}
+
+.console-error-source {
+ -moz-binding: url("chrome://global/content/consoleBindings.xml#console-error-source");
+}
+
+.console-dots {
+ width: 1px;
+}
+
+/* :::::::::: hiding and showing of rows for each mode :::::::::: */
+
+.console-box[mode="Warnings"] > .console-box-internal > .console-rows
+ > .console-row[type="error"],
+.console-box[mode="Messages"] > .console-box-internal > .console-rows
+ > .console-row[type="error"]
+{
+ display: none;
+}
+
+.console-box[mode="Errors"] > .console-box-internal > .console-rows
+ > .console-row[type="warning"],
+.console-box[mode="Messages"] > .console-box-internal > .console-rows
+ > .console-row[type="warning"]
+{
+ display: none;
+}
+
+.console-box[mode="Errors"] > .console-box-internal > .console-rows
+ > .console-row[type="message"],
+.console-box[mode="Warnings"] > .console-box-internal > .console-rows
+ > .console-row[type="message"]
+{
+ display: none;
+}
+
+.filtered-by-string {
+ display: none;
+}
+
+/* If line number is 0, hide the line number section */
+.lineNumberRow[line="0"] {
+ display: none;
+}
+
+#TextboxEval {
+ direction: ltr;
+}
diff --git a/components/console/content/console.js b/components/console/content/console.js
new file mode 100644
index 000000000..ad6802607
--- /dev/null
+++ b/components/console/content/console.js
@@ -0,0 +1,111 @@
+// -*- 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/Services.jsm");
+
+var gConsole, gConsoleBundle, gTextBoxEval, gEvaluator, gCodeToEvaluate;
+var gFilter;
+
+/* :::::::: Console Initialization ::::::::::::::: */
+
+window.onload = function()
+{
+ gConsole = document.getElementById("ConsoleBox");
+ gConsoleBundle = document.getElementById("ConsoleBundle");
+ gTextBoxEval = document.getElementById("TextboxEval");
+ gEvaluator = document.getElementById("Evaluator");
+ gFilter = document.getElementById("Filter");
+
+ updateSortCommand(gConsole.sortOrder);
+ updateModeCommand(gConsole.mode);
+
+ gEvaluator.addEventListener("load", loadOrDisplayResult, true);
+}
+
+/* :::::::: Console UI Functions ::::::::::::::: */
+
+function changeFilter()
+{
+ gConsole.filter = gFilter.value;
+
+ document.persist("ConsoleBox", "filter");
+}
+
+function changeMode(aMode)
+{
+ switch (aMode) {
+ case "Errors":
+ case "Warnings":
+ case "Messages":
+ gConsole.mode = aMode;
+ break;
+ case "All":
+ gConsole.mode = null;
+ }
+
+ document.persist("ConsoleBox", "mode");
+}
+
+function clearConsole()
+{
+ gConsole.clear();
+}
+
+function changeSortOrder(aOrder)
+{
+ updateSortCommand(gConsole.sortOrder = aOrder);
+}
+
+function updateSortCommand(aOrder)
+{
+ var orderString = aOrder == 'reverse' ? "Descend" : "Ascend";
+ var bc = document.getElementById("Console:sort"+orderString);
+ bc.setAttribute("checked", true);
+
+ orderString = aOrder == 'reverse' ? "Ascend" : "Descend";
+ bc = document.getElementById("Console:sort"+orderString);
+ bc.setAttribute("checked", false);
+}
+
+function updateModeCommand(aMode)
+{
+ /* aMode can end up invalid if it set by an extension that replaces */
+ /* mode and then it is uninstalled or disabled */
+ var bc = document.getElementById("Console:mode" + aMode) ||
+ document.getElementById("Console:modeAll");
+ bc.setAttribute("checked", true);
+}
+
+function onEvalKeyPress(aEvent)
+{
+ if (aEvent.keyCode == 13)
+ evaluateTypein();
+}
+
+function evaluateTypein()
+{
+ gCodeToEvaluate = gTextBoxEval.value;
+ // reset the iframe first; the code will be evaluated in loadOrDisplayResult
+ // below, once about:blank has completed loading (see bug 385092)
+ gEvaluator.contentWindow.location = "about:blank";
+}
+
+function loadOrDisplayResult()
+{
+ if (gCodeToEvaluate) {
+ gEvaluator.contentWindow.location = "javascript: " +
+ gCodeToEvaluate.replace(/%/g, "%25");
+ gCodeToEvaluate = "";
+ return;
+ }
+
+ var resultRange = gEvaluator.contentDocument.createRange();
+ resultRange.selectNode(gEvaluator.contentDocument.documentElement);
+ var result = resultRange.toString();
+ if (result)
+ Services.console.logStringMessage(result);
+ // or could use appendMessage which doesn't persist
+}
diff --git a/components/console/content/console.xul b/components/console/content/console.xul
new file mode 100644
index 000000000..90b900cf9
--- /dev/null
+++ b/components/console/content/console.xul
@@ -0,0 +1,96 @@
+<?xml version="1.0"?> <!-- -*- tab-width: 4; 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://global/skin/console/console.css" type="text/css"?>
+<?xml-stylesheet href="chrome://global/content/console.css" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<!DOCTYPE window [
+ <!ENTITY % console SYSTEM "chrome://global/locale/console.dtd"> %console;
+]>
+
+<window id="JSConsoleWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&errorConsole.title;"
+ windowtype="global:console"
+ width="640" height="480"
+ screenX="10" screenY="10"
+ persist="screenX screenY width height sizemode"
+ onclose="return closeWindow(false);">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://global/content/console.js"/>
+ <script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
+
+ <stringbundle id="ConsoleBundle" src="chrome://global/locale/console.properties"/>
+
+ <commandset id="editMenuCommands"/>
+
+ <commandset id="consoleCommands">
+ <command id="cmd_close" oncommand="closeWindow(true)"/>
+ </commandset>
+
+ <keyset id="consoleKeys">
+ <key id="key_close" key="&closeCmd.commandkey;" modifiers="accel"
+ command="cmd_close"/>
+ <key id="key_close2" keycode="VK_ESCAPE" command="cmd_close"/>
+ <key id="key_focus1" key="&focus1.commandkey;" modifiers="accel"
+ oncommand="gTextBoxEval.focus()"/>
+ <key id="key_focus2" key="&focus2.commandkey;" modifiers="alt"
+ oncommand="gTextBoxEval.focus()"/>
+ </keyset>
+
+ <popupset id="ContextMenus">
+ <menupopup id="ConsoleContext">
+ <menuitem type="radio" id="Console:sortAscend"
+ label="&sortFirst.label;" accesskey="&sortFirst.accesskey;"
+ oncommand="changeSortOrder('forward');"/>
+ <menuitem type="radio" id="Console:sortDescend"
+ label="&sortLast.label;" accesskey="&sortLast.accesskey;"
+ oncommand="changeSortOrder('reverse');"/>
+ <menuseparator/>
+ <menuitem id="menu_copy_cm" command="cmd_copy"
+ label="&copyCmd.label;" accesskey="&copyCmd.accesskey;"/>
+ </menupopup>
+ </popupset>
+
+ <toolbox id="console-toolbox">
+ <toolbar class="chromeclass-toolbar" id="ToolbarMode">
+ <hbox id="viewGroup">
+ <toolbarbutton type="radio" group="mode" id="Console:modeAll"
+ label="&all.label;" accesskey="&all.accesskey;"
+ oncommand="changeMode('All');"/>
+ <toolbarbutton type="radio" group="mode" id="Console:modeErrors"
+ label="&errors.label;" accesskey="&errors.accesskey;"
+ oncommand="changeMode('Errors');"/>
+ <toolbarbutton type="radio" group="mode" id="Console:modeWarnings"
+ label="&warnings.label;" accesskey="&warnings.accesskey;"
+ oncommand="changeMode('Warnings');"/>
+ <toolbarbutton type="radio" group="mode" id="Console:modeMessages"
+ label="&messages.label;" accesskey="&messages.accesskey;"
+ oncommand="changeMode('Messages');"/>
+ </hbox>
+ <toolbarseparator/>
+ <toolbarbutton id="Console:clear" oncommand="clearConsole();"
+ label="&clear.label;" accesskey="&clear.accesskey;"/>
+ <toolbarspring/>
+ </toolbar>
+
+ <toolbar class="chromeclass-toolbar" id="ToolbarEval" align="center" nowindowdrag="true">
+ <label value="&filter2.label;" control="Filter"/>
+ <textbox accesskey="&filter2.accesskey;" type="search" id="Filter" oncommand="changeFilter();"/>
+ <label value="&codeEval.label;" accesskey="&codeEval.accesskey;" control="TextboxEval"/>
+ <textbox id="TextboxEval" class="toolbar" value="" onkeypress="onEvalKeyPress(event)" flex="1"/>
+ <toolbarbutton id="ButtonEval" label="&evaluate.label;"
+ accesskey="&evaluate.accesskey;" oncommand="evaluateTypein()"/>
+ </toolbar>
+ </toolbox>
+
+ <vbox id="ConsoleBox" class="console-box" flex="1" context="ConsoleContext" persist="sortOrder"/>
+
+ <iframe name="Evaluator" id="Evaluator" collapsed="true"/>
+</window>
diff --git a/components/console/content/consoleBindings.xml b/components/console/content/consoleBindings.xml
new file mode 100644
index 000000000..1b00326bf
--- /dev/null
+++ b/components/console/content/consoleBindings.xml
@@ -0,0 +1,547 @@
+<?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 SYSTEM "chrome://global/locale/console.dtd">
+
+<bindings id="consoleBindings"
+ 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="console-box" extends="xul:box">
+ <content>
+ <xul:stringbundle src="chrome://global/locale/console.properties" role="string-bundle"/>
+ <xul:vbox class="console-box-internal">
+ <xul:vbox class="console-rows" role="console-rows" xbl:inherits="dir=sortOrder"/>
+ </xul:vbox>
+ </content>
+
+ <implementation>
+ <field name="limit" readonly="true">
+ 250
+ </field>
+
+ <field name="fieldMaxLength" readonly="true">
+ <!-- Limit displayed string lengths to avoid performance issues. (Bug 796179 and 831020) -->
+ 200
+ </field>
+
+ <field name="showChromeErrors" readonly="true">
+ Services.prefs.getBoolPref("javascript.options.showInConsole");
+ </field>
+
+ <property name="count" readonly="true">
+ <getter>return this.mCount</getter>
+ </property>
+
+ <property name="mode">
+ <getter>return this.mMode;</getter>
+ <setter><![CDATA[
+ if (this.mode != val) {
+ this.mMode = val || "All";
+ this.setAttribute("mode", this.mMode);
+ this.selectedItem = null;
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="filter">
+ <getter>return this.mFilter;</getter>
+ <setter><![CDATA[
+ val = val.toLowerCase();
+ if (this.mFilter != val) {
+ this.mFilter = val;
+ for (let aRow of this.mConsoleRowBox.children) {
+ this.filterElement(aRow);
+ }
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="sortOrder">
+ <getter>return this.getAttribute("sortOrder");</getter>
+ <setter>this.setAttribute("sortOrder", val); return val;</setter>
+ </property>
+ <field name="mSelectedItem">null</field>
+ <property name="selectedItem">
+ <getter>return this.mSelectedItem</getter>
+ <setter><![CDATA[
+ if (this.mSelectedItem)
+ this.mSelectedItem.removeAttribute("selected");
+
+ this.mSelectedItem = val;
+ if (val)
+ val.setAttribute("selected", "true");
+
+ // Update edit commands
+ window.updateCommands("focus");
+ return val;
+ ]]></setter>
+ </property>
+
+ <method name="init">
+ <body><![CDATA[
+ this.mCount = 0;
+
+ this.mConsoleListener = {
+ console: this,
+ observe : function(aObject) {
+ // The message can arrive a little bit after the xbl binding has been
+ // unbind. So node.appendItem will not be available anymore.
+ if ('appendItem' in this.console)
+ this.console.appendItem(aObject);
+ }
+ };
+
+ this.mConsoleRowBox = document.getAnonymousElementByAttribute(this, "role", "console-rows");
+ this.mStrBundle = document.getAnonymousElementByAttribute(this, "role", "string-bundle");
+
+ try {
+ Services.console.registerListener(this.mConsoleListener);
+ } catch (ex) {
+ appendItem(
+ "Unable to display errors - couldn't get Console Service component. " +
+ "(Missing @mozilla.org/consoleservice;1)");
+ return;
+ }
+
+ this.mMode = this.getAttribute("mode") || "All";
+ this.mFilter = "";
+
+ this.appendInitialItems();
+ window.controllers.insertControllerAt(0, this._controller);
+ ]]></body>
+ </method>
+
+ <method name="destroy">
+ <body><![CDATA[
+ Services.console.unregisterListener(this.mConsoleListener);
+ window.controllers.removeController(this._controller);
+ ]]></body>
+ </method>
+
+ <method name="appendInitialItems">
+ <body><![CDATA[
+ var messages = Services.console.getMessageArray();
+
+ // In case getMessageArray returns 0-length array as null
+ if (!messages)
+ messages = [];
+
+ var limit = messages.length - this.limit;
+ if (limit < 0) limit = 0;
+
+ // Checks if console ever been cleared
+ for (var i = messages.length - 1; i >= limit; --i)
+ if (!messages[i].message)
+ break;
+
+ // Populate with messages after latest "clear"
+ while (++i < messages.length)
+ this.appendItem(messages[i]);
+ ]]></body>
+ </method>
+
+ <method name="appendItem">
+ <parameter name="aObject"/>
+ <body><![CDATA[
+ try {
+ // Try to QI it to a script error to get more info
+ var scriptError = aObject.QueryInterface(Components.interfaces.nsIScriptError);
+
+ // filter chrome urls
+ if (!this.showChromeErrors && scriptError.sourceName.substr(0, 9) == "chrome://")
+ return;
+
+ // filter private windows
+ if (scriptError.isFromPrivateWindow)
+ return;
+
+ this.appendError(scriptError);
+ } catch (ex) {
+ try {
+ // Try to QI it to a console message
+ var msg = aObject.QueryInterface(Components.interfaces.nsIConsoleMessage);
+ if (msg.message)
+ this.appendMessage(msg.message);
+ else // observed a null/"clear" message
+ this.clearConsole();
+ } catch (ex2) {
+ // Give up and append the object itself as a string
+ this.appendMessage(aObject);
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_truncateIfNecessary">
+ <parameter name="aString"/>
+ <parameter name="aMiddleCharacter"/>
+ <body><![CDATA[
+ if (!aString || aString.length <= this.fieldMaxLength)
+ return {string: aString, column: aMiddleCharacter};
+ let halfLimit = this.fieldMaxLength / 2;
+ if (!aMiddleCharacter || aMiddleCharacter < 0 || aMiddleCharacter > aString.length)
+ aMiddleCharacter = halfLimit;
+
+ let startPosition = 0;
+ let endPosition = aString.length;
+ if (aMiddleCharacter - halfLimit >= 0)
+ startPosition = aMiddleCharacter - halfLimit;
+ if (aMiddleCharacter + halfLimit <= aString.length)
+ endPosition = aMiddleCharacter + halfLimit;
+ if (endPosition - startPosition < this.fieldMaxLength)
+ endPosition += this.fieldMaxLength - (endPosition - startPosition);
+ let truncatedString = aString.substring(startPosition, endPosition);
+ let Ci = Components.interfaces;
+ let ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
+ Ci.nsIPrefLocalizedString).data;
+ if (startPosition > 0) {
+ truncatedString = ellipsis + truncatedString;
+ aMiddleCharacter += ellipsis.length;
+ }
+ if (endPosition < aString.length)
+ truncatedString = truncatedString + ellipsis;
+
+ return {
+ string: truncatedString,
+ column: aMiddleCharacter - startPosition
+ };
+ ]]></body>
+ </method>
+
+ <method name="appendError">
+ <parameter name="aObject"/>
+ <body><![CDATA[
+ var row = this.createConsoleRow();
+ var nsIScriptError = Components.interfaces.nsIScriptError;
+
+ // Is this error actually just a non-fatal warning?
+ var warning = aObject.flags & nsIScriptError.warningFlag != 0;
+
+ var typetext = warning ? "typeWarning" : "typeError";
+ row.setAttribute("typetext", this.mStrBundle.getString(typetext));
+ row.setAttribute("type", warning ? "warning" : "error");
+ row.setAttribute("msg", aObject.errorMessage);
+ row.setAttribute("category", aObject.category);
+ row.setAttribute("time", this.properFormatTime(aObject.timeStamp));
+ if (aObject.lineNumber || aObject.sourceName) {
+ row.setAttribute("href", this._truncateIfNecessary(aObject.sourceName).string);
+ row.mSourceName = aObject.sourceName;
+ row.setAttribute("line", aObject.lineNumber);
+ } else {
+ row.setAttribute("hideSource", "true");
+ }
+ if (aObject.sourceLine) {
+ let sourceLine = aObject.sourceLine.replace(/\s/g, " ");
+ let truncatedLineObj = this._truncateIfNecessary(sourceLine, aObject.columnNumber);
+ row.setAttribute("code", truncatedLineObj.string);
+ row.mSourceLine = sourceLine;
+ if (aObject.columnNumber) {
+ row.setAttribute("col", aObject.columnNumber);
+ row.setAttribute("errorDots", this.repeatChar(" ", truncatedLineObj.column));
+ row.setAttribute("errorCaret", " ");
+ } else {
+ row.setAttribute("hideCaret", "true");
+ }
+ } else {
+ row.setAttribute("hideCode", "true");
+ }
+
+ this.appendConsoleRow(row);
+ ]]></body>
+ </method>
+
+ <method name="appendMessage">
+ <parameter name="aMessage"/>
+ <parameter name="aType"/>
+ <body><![CDATA[
+ var row = this.createConsoleRow();
+ row.setAttribute("type", aType || "message");
+ row.setAttribute("msg", aMessage);
+ this.appendConsoleRow(row);
+ ]]></body>
+ </method>
+
+ <method name="clear">
+ <body><![CDATA[
+ // add a "clear" message (mainly for other listeners)
+ Services.console.logStringMessage(null);
+ Services.console.reset();
+ ]]></body>
+ </method>
+
+ <method name="properFormatTime">
+ <parameter name="aTime"/>
+ <body><![CDATA[
+ const dateServ = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Components.interfaces.nsIScriptableDateFormat);
+ let errorTime = new Date(aTime);
+ return dateServ.FormatDateTime("", dateServ.dateFormatShort, dateServ.timeFormatSeconds,
+ errorTime.getFullYear(), errorTime.getMonth() + 1, errorTime.getDate(),
+ errorTime.getHours(), errorTime.getMinutes(), errorTime.getSeconds());
+ ]]></body>
+ </method>
+
+ <method name="copySelectedItem">
+ <body><![CDATA[
+ if (this.mSelectedItem) try {
+ const clipURI = "@mozilla.org/widget/clipboardhelper;1";
+ const clipI = Components.interfaces.nsIClipboardHelper;
+ var clipboard = Components.classes[clipURI].getService(clipI);
+
+ clipboard.copyString(this.mSelectedItem.toString(), document);
+ } catch (ex) {
+ // Unable to copy anything, die quietly
+ }
+ ]]></body>
+ </method>
+
+ <method name="createConsoleRow">
+ <body><![CDATA[
+ var row = document.createElement("box");
+ row.setAttribute("class", "console-row");
+ row._IsConsoleRow = true;
+ row._ConsoleBox = this;
+ return row;
+ ]]></body>
+ </method>
+
+ <method name="appendConsoleRow">
+ <parameter name="aRow"/>
+ <body><![CDATA[
+ this.filterElement(aRow);
+ this.mConsoleRowBox.appendChild(aRow);
+ if (++this.mCount > this.limit) this.deleteFirst();
+ ]]></body>
+ </method>
+
+ <method name="deleteFirst">
+ <body><![CDATA[
+ var node = this.mConsoleRowBox.firstChild;
+ this.mConsoleRowBox.removeChild(node);
+ --this.mCount;
+ ]]></body>
+ </method>
+
+ <method name="clearConsole">
+ <body><![CDATA[
+ if (this.mCount == 0) // already clear
+ return;
+ this.mCount = 0;
+
+ var newRows = this.mConsoleRowBox.cloneNode(false);
+ this.mConsoleRowBox.parentNode.replaceChild(newRows, this.mConsoleRowBox);
+ this.mConsoleRowBox = newRows;
+ this.selectedItem = null;
+ ]]></body>
+ </method>
+
+ <method name="filterElement">
+ <parameter name="aRow" />
+ <body><![CDATA[
+ let anyMatch = ["msg", "line", "code"].some(function (key) {
+ return (aRow.hasAttribute(key) &&
+ this.stringMatchesFilters(aRow.getAttribute(key), this.mFilter));
+ }, this) || (aRow.mSourceName &&
+ this.stringMatchesFilters(aRow.mSourceName, this.mFilter));
+
+ if (anyMatch) {
+ aRow.classList.remove("filtered-by-string")
+ } else {
+ aRow.classList.add("filtered-by-string")
+ }
+ ]]></body>
+ </method>
+
+ <!-- UTILITY FUNCTIONS -->
+
+ <method name="repeatChar">
+ <parameter name="aChar"/>
+ <parameter name="aCol"/>
+ <body><![CDATA[
+ if (--aCol <= 0)
+ return "";
+
+ for (var i = 2; i < aCol; i += i)
+ aChar += aChar;
+
+ return aChar + aChar.slice(0, aCol - aChar.length);
+ ]]></body>
+ </method>
+
+ <method name="stringMatchesFilters">
+ <parameter name="aString"/>
+ <parameter name="aFilter"/>
+ <body><![CDATA[
+ if (!aString || !aFilter) {
+ return true;
+ }
+
+ let searchStr = aString.toLowerCase();
+ let filterStrings = aFilter.split(/\s+/);
+ return !filterStrings.some(function (f) {
+ return searchStr.indexOf(f) == -1;
+ });
+ ]]></body>
+ </method>
+
+ <constructor> this.init(); </constructor>
+ <destructor> this.destroy(); </destructor>
+
+ <!-- Command controller for the copy command -->
+ <field name="_controller"><![CDATA[({
+ _outer: this,
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIController) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ supportsCommand: function(aCommand) {
+ return aCommand == "cmd_copy";
+ },
+
+ isCommandEnabled: function(aCommand) {
+ return aCommand == "cmd_copy" && this._outer.selectedItem;
+ },
+
+ doCommand: function(aCommand) {
+ if (aCommand == "cmd_copy")
+ this._outer.copySelectedItem();
+ },
+
+ onEvent: function() { }
+ });]]></field>
+ </implementation>
+
+ <handlers>
+ <handler event="mousedown"><![CDATA[
+ if (event.button == 0 || event.button == 2) {
+ var target = event.originalTarget;
+
+ while (target && !("_IsConsoleRow" in target))
+ target = target.parentNode;
+
+ if (target)
+ this.selectedItem = target;
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="error" extends="xul:box">
+ <content>
+ <xul:box class="console-row-internal-box" flex="1">
+ <xul:box class="console-row-icon" align="center" xbl:inherits="selected">
+ <xul:image class="console-icon" xbl:inherits="src,type"/>
+ </xul:box>
+ <xul:vbox class="console-row-content" xbl:inherits="selected" flex="1">
+ <xul:box class="console-row-msg" align="start">
+ <xul:label class="label" xbl:inherits="value=typetext"/>
+ <xul:description class="console-error-msg" xbl:inherits="xbl:text=msg" flex="1"/>
+ <xul:label class="label console-time" xbl:inherits="value=time"/>
+ </xul:box>
+ <xul:box class="console-row-file" xbl:inherits="hidden=hideSource">
+ <xul:label class="label" value="&errFile.label;"/>
+ <xul:box class="console-error-source" xbl:inherits="href,line"/>
+ <xul:spacer flex="1"/>
+ <xul:hbox class="lineNumberRow" xbl:inherits="line">
+ <xul:label class="label" value="&errLine.label;"/>
+ <xul:label class="label" xbl:inherits="value=line"/>
+ </xul:hbox>
+ </xul:box>
+ <xul:vbox class="console-row-code" xbl:inherits="selected,hidden=hideCode">
+ <xul:label class="monospace console-code" xbl:inherits="value=code" crop="end"/>
+ <xul:box xbl:inherits="hidden=hideCaret">
+ <xul:label class="monospace console-dots" xbl:inherits="value=errorDots"/>
+ <xul:label class="monospace console-caret" xbl:inherits="value=errorCaret"/>
+ <xul:spacer flex="1"/>
+ </xul:box>
+ </xul:vbox>
+ </xul:vbox>
+ </xul:box>
+ </content>
+
+ <implementation>
+ <field name="mSourceName">null</field>
+ <field name="mSourceLine">null</field>
+
+ <method name="toString">
+ <body><![CDATA[
+ let msg = "";
+ let strBundle = this._ConsoleBox.mStrBundle;
+
+ if (this.hasAttribute("time"))
+ msg += strBundle.getFormattedString("errTime", [this.getAttribute("time")]) + "\n";
+
+ msg += this.getAttribute("typetext") + " " + this.getAttribute("msg");
+
+ if (this.hasAttribute("line") && this.mSourceName) {
+ msg += "\n" + strBundle.getFormattedString("errFile",
+ [this.mSourceName]) + "\n";
+ if (this.hasAttribute("col")) {
+ msg += strBundle.getFormattedString("errLineCol",
+ [this.getAttribute("line"), this.getAttribute("col")]);
+ } else
+ msg += strBundle.getFormattedString("errLine", [this.getAttribute("line")]);
+ }
+
+ if (this.hasAttribute("code"))
+ msg += "\n" + strBundle.getString("errCode") + "\n" + this.mSourceLine;
+
+ return msg;
+ ]]></body>
+ </method>
+ </implementation>
+
+ </binding>
+
+ <binding id="message" extends="xul:box">
+ <content>
+ <xul:box class="console-internal-box" flex="1">
+ <xul:box class="console-row-icon" align="center">
+ <xul:image class="console-icon" xbl:inherits="src,type"/>
+ </xul:box>
+ <xul:vbox class="console-row-content" xbl:inherits="selected" flex="1">
+ <xul:vbox class="console-row-msg" flex="1">
+ <xul:description class="console-msg-text" xbl:inherits="xbl:text=msg"/>
+ </xul:vbox>
+ </xul:vbox>
+ </xul:box>
+ </content>
+
+ <implementation>
+ <method name="toString">
+ <body><![CDATA[
+ return this.getAttribute("msg");
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="console-error-source" extends="xul:box">
+ <content>
+ <xul:label class="text-link" xbl:inherits="value=href" crop="right"/>
+ </content>
+
+ <handlers>
+ <handler event="click" phase="capturing" button="0" preventdefault="true">
+ <![CDATA[
+ var url = document.getBindingParent(this).mSourceName;
+ url = url.substring(url.lastIndexOf(" ") + 1);
+ var line = getAttribute("line");
+ gViewSourceUtils.viewSource(url, null, null, line);
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/components/console/jar.mn b/components/console/jar.mn
new file mode 100644
index 000000000..26e54c6fb
--- /dev/null
+++ b/components/console/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/.
+
+toolkit.jar:
+ content/global/console.js (content/console.js)
+ content/global/console.xul (content/console.xul)
+ content/global/console.css (content/console.css)
+ content/global/consoleBindings.xml (content/consoleBindings.xml)
diff --git a/components/console/jsconsole-clhandler.js b/components/console/jsconsole-clhandler.js
new file mode 100644
index 000000000..1fff88890
--- /dev/null
+++ b/components/console/jsconsole-clhandler.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* vim:sw=4:sr:sta:et:sts: */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function jsConsoleHandler() {}
+jsConsoleHandler.prototype = {
+ handle: function clh_handle(cmdLine) {
+ if (!cmdLine.handleFlag("jsconsole", false))
+ return;
+
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ var console = wm.getMostRecentWindow("global:console");
+ if (!console) {
+ var wwatch = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ wwatch.openWindow(null, "chrome://global/content/console.xul", "_blank",
+ "chrome,dialog=no,all", cmdLine);
+ } else {
+ console.focus(); // the Error console was already open
+ }
+
+ if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO)
+ cmdLine.preventDefault = true;
+ },
+
+ helpInfo : " --jsconsole Open the Error console.\n",
+
+ classID: Components.ID("{2cd0c310-e127-44d0-88fc-4435c9ab4d4b}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([jsConsoleHandler]);
diff --git a/components/console/jsconsole-clhandler.manifest b/components/console/jsconsole-clhandler.manifest
new file mode 100644
index 000000000..8e8c9b8bb
--- /dev/null
+++ b/components/console/jsconsole-clhandler.manifest
@@ -0,0 +1,3 @@
+component {2cd0c310-e127-44d0-88fc-4435c9ab4d4b} jsconsole-clhandler.js
+contract @mozilla.org/toolkit/console-clh;1 {2cd0c310-e127-44d0-88fc-4435c9ab4d4b}
+category command-line-handler b-jsconsole @mozilla.org/toolkit/console-clh;1
diff --git a/components/console/moz.build b/components/console/moz.build
new file mode 100644
index 000000000..43e3d8d02
--- /dev/null
+++ b/components/console/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'jsconsole-clhandler.js',
+ 'jsconsole-clhandler.manifest',
+]
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/contentprefs/ContentPrefInstance.jsm b/components/contentprefs/ContentPrefInstance.jsm
new file mode 100644
index 000000000..395569995
--- /dev/null
+++ b/components/contentprefs/ContentPrefInstance.jsm
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+
+this.EXPORTED_SYMBOLS = ['ContentPrefInstance'];
+
+// This is a wrapper for nsIContentPrefService that alleviates the need to pass
+// an nsILoadContext argument to every method. Pass the context to the constructor
+// instead and continue on your way in blissful ignorance.
+
+this.ContentPrefInstance = function ContentPrefInstance(aContext) {
+ this._contentPrefSvc = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService);
+ this._context = aContext;
+};
+
+ContentPrefInstance.prototype = {
+ getPref: function ContentPrefInstance_init(aName, aGroup, aCallback) {
+ return this._contentPrefSvc.getPref(aName, aGroup, this._context, aCallback);
+ },
+
+ setPref: function ContentPrefInstance_setPref(aGroup, aName, aValue, aContext) {
+ return this._contentPrefSvc.setPref(aGroup, aName, aValue,
+ aContext ? aContext : this._context);
+ },
+
+ hasPref: function ContentPrefInstance_hasPref(aGroup, aName) {
+ return this._contentPrefSvc.hasPref(aGroup, aName, this._context);
+ },
+
+ hasCachedPref: function ContentPrefInstance_hasCachedPref(aGroup, aName) {
+ return this._contentPrefSvc.hasCachedPref(aGroup, aName, this._context);
+ },
+
+ removePref: function ContentPrefInstance_removePref(aGroup, aName) {
+ return this._contentPrefSvc.removePref(aGroup, aName, this._context);
+ },
+
+ removeGroupedPrefs: function ContentPrefInstance_removeGroupedPrefs() {
+ return this._contentPrefSvc.removeGroupedPrefs(this._context);
+ },
+
+ removePrefsByName: function ContentPrefInstance_removePrefsByName(aName) {
+ return this._contentPrefSvc.removePrefsByName(aName, this._context);
+ },
+
+ getPrefs: function ContentPrefInstance_getPrefs(aGroup) {
+ return this._contentPrefSvc.getPrefs(aGroup, this._context);
+ },
+
+ getPrefsByName: function ContentPrefInstance_getPrefsByName(aName) {
+ return this._contentPrefSvc.getPrefsByName(aName, this._context);
+ },
+
+ addObserver: function ContentPrefInstance_addObserver(aName, aObserver) {
+ return this._contentPrefSvc.addObserver(aName, aObserver);
+ },
+
+ removeObserver: function ContentPrefInstance_removeObserver(aName, aObserver) {
+ return this._contentPrefSvc.removeObserver(aName, aObserver);
+ },
+
+ get grouper() {
+ return this._contentPrefSvc.grouper;
+ },
+
+ get DBConnection() {
+ return this._contentPrefSvc.DBConnection;
+ }
+};
diff --git a/components/contentprefs/ContentPrefService2.jsm b/components/contentprefs/ContentPrefService2.jsm
new file mode 100644
index 000000000..87063d170
--- /dev/null
+++ b/components/contentprefs/ContentPrefService2.jsm
@@ -0,0 +1,885 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 an XPCOM component that implements nsIContentPrefService2.
+// Although it's a JSM, it's not intended to be imported by consumers like JSMs
+// are usually imported. It's only a JSM so that nsContentPrefService.js can
+// easily use it. Consumers should access this component with the usual XPCOM
+// rigmarole:
+//
+// Cc["@mozilla.org/content-pref/service;1"].
+// getService(Ci.nsIContentPrefService2);
+//
+// That contract ID actually belongs to nsContentPrefService.js, which, when
+// QI'ed to nsIContentPrefService2, returns an instance of this component.
+//
+// The plan is to eventually remove nsIContentPrefService and its
+// implementation, nsContentPrefService.js. At such time this file can stop
+// being a JSM, and the "_cps" parts that ContentPrefService2 relies on and
+// NSGetFactory and all the other XPCOM initialization goop in
+// nsContentPrefService.js can be moved here.
+//
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=699859
+
+var EXPORTED_SYMBOLS = [
+ "ContentPrefService2",
+];
+
+const { interfaces: Ci, classes: Cc, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/ContentPrefUtils.jsm");
+Cu.import("resource://gre/modules/ContentPrefStore.jsm");
+
+const GROUP_CLAUSE = `
+ SELECT id
+ FROM groups
+ WHERE name = :group OR
+ (:includeSubdomains AND name LIKE :pattern ESCAPE '/')
+`;
+
+function ContentPrefService2(cps) {
+ this._cps = cps;
+ this._cache = cps._cache;
+ this._pbStore = cps._privModeStorage;
+}
+
+ContentPrefService2.prototype = {
+
+ getByName: function CPS2_getByName(name, context, callback) {
+ checkNameArg(name);
+ checkCallbackArg(callback, true);
+
+ // Some prefs may be in both the database and the private browsing store.
+ // Notify the caller of such prefs only once, using the values from private
+ // browsing.
+ let pbPrefs = new ContentPrefStore();
+ if (context && context.usePrivateBrowsing) {
+ for (let [sgroup, sname, val] of this._pbStore) {
+ if (sname == name) {
+ pbPrefs.set(sgroup, sname, val);
+ }
+ }
+ }
+
+ let stmt1 = this._stmt(`
+ SELECT groups.name AS grp, prefs.value AS value
+ FROM prefs
+ JOIN settings ON settings.id = prefs.settingID
+ JOIN groups ON groups.id = prefs.groupID
+ WHERE settings.name = :name
+ `);
+ stmt1.params.name = name;
+
+ let stmt2 = this._stmt(`
+ SELECT NULL AS grp, prefs.value AS value
+ FROM prefs
+ JOIN settings ON settings.id = prefs.settingID
+ WHERE settings.name = :name AND prefs.groupID ISNULL
+ `);
+ stmt2.params.name = name;
+
+ this._execStmts([stmt1, stmt2], {
+ onRow: function onRow(row) {
+ let grp = row.getResultByName("grp");
+ let val = row.getResultByName("value");
+ this._cache.set(grp, name, val);
+ if (!pbPrefs.has(grp, name))
+ cbHandleResult(callback, new ContentPref(grp, name, val));
+ },
+ onDone: function onDone(reason, ok, gotRow) {
+ if (ok) {
+ for (let [pbGroup, pbName, pbVal] of pbPrefs) {
+ cbHandleResult(callback, new ContentPref(pbGroup, pbName, pbVal));
+ }
+ }
+ cbHandleCompletion(callback, reason);
+ },
+ onError: function onError(nsresult) {
+ cbHandleError(callback, nsresult);
+ }
+ });
+ },
+
+ getByDomainAndName: function CPS2_getByDomainAndName(group, name, context,
+ callback) {
+ checkGroupArg(group);
+ this._get(group, name, false, context, callback);
+ },
+
+ getBySubdomainAndName: function CPS2_getBySubdomainAndName(group, name,
+ context,
+ callback) {
+ checkGroupArg(group);
+ this._get(group, name, true, context, callback);
+ },
+
+ getGlobal: function CPS2_getGlobal(name, context, callback) {
+ this._get(null, name, false, context, callback);
+ },
+
+ _get: function CPS2__get(group, name, includeSubdomains, context, callback) {
+ group = this._parseGroup(group);
+ checkNameArg(name);
+ checkCallbackArg(callback, true);
+
+ // Some prefs may be in both the database and the private browsing store.
+ // Notify the caller of such prefs only once, using the values from private
+ // browsing.
+ let pbPrefs = new ContentPrefStore();
+ if (context && context.usePrivateBrowsing) {
+ for (let [sgroup, val] of
+ this._pbStore.match(group, name, includeSubdomains)) {
+ pbPrefs.set(sgroup, name, val);
+ }
+ }
+
+ this._execStmts([this._commonGetStmt(group, name, includeSubdomains)], {
+ onRow: function onRow(row) {
+ let grp = row.getResultByName("grp");
+ let val = row.getResultByName("value");
+ this._cache.set(grp, name, val);
+ if (!pbPrefs.has(group, name))
+ cbHandleResult(callback, new ContentPref(grp, name, val));
+ },
+ onDone: function onDone(reason, ok, gotRow) {
+ if (ok) {
+ if (!gotRow)
+ this._cache.set(group, name, undefined);
+ for (let [pbGroup, pbName, pbVal] of pbPrefs) {
+ cbHandleResult(callback, new ContentPref(pbGroup, pbName, pbVal));
+ }
+ }
+ cbHandleCompletion(callback, reason);
+ },
+ onError: function onError(nsresult) {
+ cbHandleError(callback, nsresult);
+ }
+ });
+ },
+
+ _commonGetStmt: function CPS2__commonGetStmt(group, name, includeSubdomains) {
+ let stmt = group ?
+ this._stmtWithGroupClause(group, includeSubdomains, `
+ SELECT groups.name AS grp, prefs.value AS value
+ FROM prefs
+ JOIN settings ON settings.id = prefs.settingID
+ JOIN groups ON groups.id = prefs.groupID
+ WHERE settings.name = :name AND prefs.groupID IN (${GROUP_CLAUSE})
+ `) :
+ this._stmt(`
+ SELECT NULL AS grp, prefs.value AS value
+ FROM prefs
+ JOIN settings ON settings.id = prefs.settingID
+ WHERE settings.name = :name AND prefs.groupID ISNULL
+ `);
+ stmt.params.name = name;
+ return stmt;
+ },
+
+ _stmtWithGroupClause: function CPS2__stmtWithGroupClause(group,
+ includeSubdomains,
+ sql) {
+ let stmt = this._stmt(sql);
+ stmt.params.group = group;
+ stmt.params.includeSubdomains = includeSubdomains || false;
+ stmt.params.pattern = "%." + stmt.escapeStringForLIKE(group, "/");
+ return stmt;
+ },
+
+ getCachedByDomainAndName: function CPS2_getCachedByDomainAndName(group,
+ name,
+ context) {
+ checkGroupArg(group);
+ let prefs = this._getCached(group, name, false, context);
+ return prefs[0] || null;
+ },
+
+ getCachedBySubdomainAndName: function CPS2_getCachedBySubdomainAndName(group,
+ name,
+ context,
+ len) {
+ checkGroupArg(group);
+ let prefs = this._getCached(group, name, true, context);
+ if (len)
+ len.value = prefs.length;
+ return prefs;
+ },
+
+ getCachedGlobal: function CPS2_getCachedGlobal(name, context) {
+ let prefs = this._getCached(null, name, false, context);
+ return prefs[0] || null;
+ },
+
+ _getCached: function CPS2__getCached(group, name, includeSubdomains,
+ context) {
+ group = this._parseGroup(group);
+ checkNameArg(name);
+
+ let storesToCheck = [this._cache];
+ if (context && context.usePrivateBrowsing)
+ storesToCheck.push(this._pbStore);
+
+ let outStore = new ContentPrefStore();
+ storesToCheck.forEach(function (store) {
+ for (let [sgroup, val] of store.match(group, name, includeSubdomains)) {
+ outStore.set(sgroup, name, val);
+ }
+ });
+
+ let prefs = [];
+ for (let [sgroup, sname, val] of outStore) {
+ prefs.push(new ContentPref(sgroup, sname, val));
+ }
+ return prefs;
+ },
+
+ set: function CPS2_set(group, name, value, context, callback) {
+ checkGroupArg(group);
+ this._set(group, name, value, context, callback);
+ },
+
+ setGlobal: function CPS2_setGlobal(name, value, context, callback) {
+ this._set(null, name, value, context, callback);
+ },
+
+ _set: function CPS2__set(group, name, value, context, callback) {
+ group = this._parseGroup(group);
+ checkNameArg(name);
+ checkValueArg(value);
+ checkCallbackArg(callback, false);
+
+ if (context && context.usePrivateBrowsing) {
+ this._pbStore.set(group, name, value);
+ this._schedule(function () {
+ cbHandleCompletion(callback, Ci.nsIContentPrefCallback2.COMPLETE_OK);
+ this._cps._notifyPrefSet(group, name, value, context.usePrivateBrowsing);
+ });
+ return;
+ }
+
+ // Invalidate the cached value so consumers accessing the cache between now
+ // and when the operation finishes don't get old data.
+ this._cache.remove(group, name);
+
+ let stmts = [];
+
+ // Create the setting if it doesn't exist.
+ let stmt = this._stmt(`
+ INSERT OR IGNORE INTO settings (id, name)
+ VALUES((SELECT id FROM settings WHERE name = :name), :name)
+ `);
+ stmt.params.name = name;
+ stmts.push(stmt);
+
+ // Create the group if it doesn't exist.
+ if (group) {
+ stmt = this._stmt(`
+ INSERT OR IGNORE INTO groups (id, name)
+ VALUES((SELECT id FROM groups WHERE name = :group), :group)
+ `);
+ stmt.params.group = group;
+ stmts.push(stmt);
+ }
+
+ // Finally create or update the pref.
+ if (group) {
+ stmt = this._stmt(`
+ INSERT OR REPLACE INTO prefs (id, groupID, settingID, value, timestamp)
+ VALUES(
+ (SELECT prefs.id
+ FROM prefs
+ JOIN groups ON groups.id = prefs.groupID
+ JOIN settings ON settings.id = prefs.settingID
+ WHERE groups.name = :group AND settings.name = :name),
+ (SELECT id FROM groups WHERE name = :group),
+ (SELECT id FROM settings WHERE name = :name),
+ :value,
+ :now
+ )
+ `);
+ stmt.params.group = group;
+ }
+ else {
+ stmt = this._stmt(`
+ INSERT OR REPLACE INTO prefs (id, groupID, settingID, value, timestamp)
+ VALUES(
+ (SELECT prefs.id
+ FROM prefs
+ JOIN settings ON settings.id = prefs.settingID
+ WHERE prefs.groupID IS NULL AND settings.name = :name),
+ NULL,
+ (SELECT id FROM settings WHERE name = :name),
+ :value,
+ :now
+ )
+ `);
+ }
+ stmt.params.name = name;
+ stmt.params.value = value;
+ stmt.params.now = Date.now() / 1000;
+ stmts.push(stmt);
+
+ this._execStmts(stmts, {
+ onDone: function onDone(reason, ok) {
+ if (ok)
+ this._cache.setWithCast(group, name, value);
+ cbHandleCompletion(callback, reason);
+ if (ok)
+ this._cps._notifyPrefSet(group, name, value, context && context.usePrivateBrowsing);
+ },
+ onError: function onError(nsresult) {
+ cbHandleError(callback, nsresult);
+ }
+ });
+ },
+
+ removeByDomainAndName: function CPS2_removeByDomainAndName(group, name,
+ context,
+ callback) {
+ checkGroupArg(group);
+ this._remove(group, name, false, context, callback);
+ },
+
+ removeBySubdomainAndName: function CPS2_removeBySubdomainAndName(group, name,
+ context,
+ callback) {
+ checkGroupArg(group);
+ this._remove(group, name, true, context, callback);
+ },
+
+ removeGlobal: function CPS2_removeGlobal(name, context, callback) {
+ this._remove(null, name, false, context, callback);
+ },
+
+ _remove: function CPS2__remove(group, name, includeSubdomains, context,
+ callback) {
+ group = this._parseGroup(group);
+ checkNameArg(name);
+ checkCallbackArg(callback, false);
+
+ // Invalidate the cached values so consumers accessing the cache between now
+ // and when the operation finishes don't get old data.
+ for (let sgroup of this._cache.matchGroups(group, includeSubdomains)) {
+ this._cache.remove(sgroup, name);
+ }
+
+ let stmts = [];
+
+ // First get the matching prefs.
+ stmts.push(this._commonGetStmt(group, name, includeSubdomains));
+
+ // Delete the matching prefs.
+ let stmt = this._stmtWithGroupClause(group, includeSubdomains, `
+ DELETE FROM prefs
+ WHERE settingID = (SELECT id FROM settings WHERE name = :name) AND
+ CASE typeof(:group)
+ WHEN 'null' THEN prefs.groupID IS NULL
+ ELSE prefs.groupID IN (${GROUP_CLAUSE})
+ END
+ `);
+ stmt.params.name = name;
+ stmts.push(stmt);
+
+ stmts = stmts.concat(this._settingsAndGroupsCleanupStmts());
+
+ let prefs = new ContentPrefStore();
+
+ let isPrivate = context && context.usePrivateBrowsing;
+ this._execStmts(stmts, {
+ onRow: function onRow(row) {
+ let grp = row.getResultByName("grp");
+ prefs.set(grp, name, undefined);
+ this._cache.set(grp, name, undefined);
+ },
+ onDone: function onDone(reason, ok) {
+ if (ok) {
+ this._cache.set(group, name, undefined);
+ if (isPrivate) {
+ for (let [sgroup, ] of
+ this._pbStore.match(group, name, includeSubdomains)) {
+ prefs.set(sgroup, name, undefined);
+ this._pbStore.remove(sgroup, name);
+ }
+ }
+ }
+ cbHandleCompletion(callback, reason);
+ if (ok) {
+ for (let [sgroup, , ] of prefs) {
+ this._cps._notifyPrefRemoved(sgroup, name, isPrivate);
+ }
+ }
+ },
+ onError: function onError(nsresult) {
+ cbHandleError(callback, nsresult);
+ }
+ });
+ },
+
+ // Deletes settings and groups that are no longer used.
+ _settingsAndGroupsCleanupStmts: function() {
+ // The NOTNULL term in the subquery of the second statment is needed because of
+ // SQLite's weird IN behavior vis-a-vis NULLs. See http://sqlite.org/lang_expr.html.
+ return [
+ this._stmt(`
+ DELETE FROM settings
+ WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)
+ `),
+ this._stmt(`
+ DELETE FROM groups WHERE id NOT IN (
+ SELECT DISTINCT groupID FROM prefs WHERE groupID NOTNULL
+ )
+ `)
+ ];
+ },
+
+ removeByDomain: function CPS2_removeByDomain(group, context, callback) {
+ checkGroupArg(group);
+ this._removeByDomain(group, false, context, callback);
+ },
+
+ removeBySubdomain: function CPS2_removeBySubdomain(group, context, callback) {
+ checkGroupArg(group);
+ this._removeByDomain(group, true, context, callback);
+ },
+
+ removeAllGlobals: function CPS2_removeAllGlobals(context, callback) {
+ this._removeByDomain(null, false, context, callback);
+ },
+
+ _removeByDomain: function CPS2__removeByDomain(group, includeSubdomains,
+ context, callback) {
+ group = this._parseGroup(group);
+ checkCallbackArg(callback, false);
+
+ // Invalidate the cached values so consumers accessing the cache between now
+ // and when the operation finishes don't get old data.
+ for (let sgroup of this._cache.matchGroups(group, includeSubdomains)) {
+ this._cache.removeGroup(sgroup);
+ }
+
+ let stmts = [];
+
+ // First get the matching prefs, then delete groups and prefs that reference
+ // deleted groups.
+ if (group) {
+ stmts.push(this._stmtWithGroupClause(group, includeSubdomains, `
+ SELECT groups.name AS grp, settings.name AS name
+ FROM prefs
+ JOIN settings ON settings.id = prefs.settingID
+ JOIN groups ON groups.id = prefs.groupID
+ WHERE prefs.groupID IN (${GROUP_CLAUSE})
+ `));
+ stmts.push(this._stmtWithGroupClause(group, includeSubdomains,
+ `DELETE FROM groups WHERE id IN (${GROUP_CLAUSE})`
+ ));
+ stmts.push(this._stmt(`
+ DELETE FROM prefs
+ WHERE groupID NOTNULL AND groupID NOT IN (SELECT id FROM groups)
+ `));
+ }
+ else {
+ stmts.push(this._stmt(`
+ SELECT NULL AS grp, settings.name AS name
+ FROM prefs
+ JOIN settings ON settings.id = prefs.settingID
+ WHERE prefs.groupID IS NULL
+ `));
+ stmts.push(this._stmt(
+ "DELETE FROM prefs WHERE groupID IS NULL"
+ ));
+ }
+
+ // Finally delete settings that are no longer referenced.
+ stmts.push(this._stmt(`
+ DELETE FROM settings
+ WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)
+ `));
+
+ let prefs = new ContentPrefStore();
+
+ let isPrivate = context && context.usePrivateBrowsing;
+ this._execStmts(stmts, {
+ onRow: function onRow(row) {
+ let grp = row.getResultByName("grp");
+ let name = row.getResultByName("name");
+ prefs.set(grp, name, undefined);
+ this._cache.set(grp, name, undefined);
+ },
+ onDone: function onDone(reason, ok) {
+ if (ok && isPrivate) {
+ for (let [sgroup, sname, ] of this._pbStore) {
+ if (!group ||
+ (!includeSubdomains && group == sgroup) ||
+ (includeSubdomains && sgroup && this._pbStore.groupsMatchIncludingSubdomains(group, sgroup))) {
+ prefs.set(sgroup, sname, undefined);
+ this._pbStore.remove(sgroup, sname);
+ }
+ }
+ }
+ cbHandleCompletion(callback, reason);
+ if (ok) {
+ for (let [sgroup, sname, ] of prefs) {
+ this._cps._notifyPrefRemoved(sgroup, sname, isPrivate);
+ }
+ }
+ },
+ onError: function onError(nsresult) {
+ cbHandleError(callback, nsresult);
+ }
+ });
+ },
+
+ _removeAllDomainsSince: function CPS2__removeAllDomainsSince(since, context, callback) {
+ checkCallbackArg(callback, false);
+
+ since /= 1000;
+
+ // Invalidate the cached values so consumers accessing the cache between now
+ // and when the operation finishes don't get old data.
+ // Invalidate all the group cache because we don't know which groups will be removed.
+ this._cache.removeAllGroups();
+
+ let stmts = [];
+
+ // Get prefs that are about to be removed to notify about their removal.
+ let stmt = this._stmt(`
+ SELECT groups.name AS grp, settings.name AS name
+ FROM prefs
+ JOIN settings ON settings.id = prefs.settingID
+ JOIN groups ON groups.id = prefs.groupID
+ WHERE timestamp >= :since
+ `);
+ stmt.params.since = since;
+ stmts.push(stmt);
+
+ // Do the actual remove.
+ stmt = this._stmt(`
+ DELETE FROM prefs WHERE groupID NOTNULL AND timestamp >= :since
+ `);
+ stmt.params.since = since;
+ stmts.push(stmt);
+
+ // Cleanup no longer used values.
+ stmts = stmts.concat(this._settingsAndGroupsCleanupStmts());
+
+ let prefs = new ContentPrefStore();
+ let isPrivate = context && context.usePrivateBrowsing;
+ this._execStmts(stmts, {
+ onRow: function onRow(row) {
+ let grp = row.getResultByName("grp");
+ let name = row.getResultByName("name");
+ prefs.set(grp, name, undefined);
+ this._cache.set(grp, name, undefined);
+ },
+ onDone: function onDone(reason, ok) {
+ // This nukes all the groups in _pbStore since we don't have their timestamp
+ // information.
+ if (ok && isPrivate) {
+ for (let [sgroup, sname, ] of this._pbStore) {
+ if (sgroup) {
+ prefs.set(sgroup, sname, undefined);
+ }
+ }
+ this._pbStore.removeAllGroups();
+ }
+ cbHandleCompletion(callback, reason);
+ if (ok) {
+ for (let [sgroup, sname, ] of prefs) {
+ this._cps._notifyPrefRemoved(sgroup, sname, isPrivate);
+ }
+ }
+ },
+ onError: function onError(nsresult) {
+ cbHandleError(callback, nsresult);
+ }
+ });
+ },
+
+ removeAllDomainsSince: function CPS2_removeAllDomainsSince(since, context, callback) {
+ this._removeAllDomainsSince(since, context, callback);
+ },
+
+ removeAllDomains: function CPS2_removeAllDomains(context, callback) {
+ this._removeAllDomainsSince(0, context, callback);
+ },
+
+ removeByName: function CPS2_removeByName(name, context, callback) {
+ checkNameArg(name);
+ checkCallbackArg(callback, false);
+
+ // Invalidate the cached values so consumers accessing the cache between now
+ // and when the operation finishes don't get old data.
+ for (let [group, sname, ] of this._cache) {
+ if (sname == name)
+ this._cache.remove(group, name);
+ }
+
+ let stmts = [];
+
+ // First get the matching prefs. Include null if any of those prefs are
+ // global.
+ let stmt = this._stmt(`
+ SELECT groups.name AS grp
+ FROM prefs
+ JOIN settings ON settings.id = prefs.settingID
+ JOIN groups ON groups.id = prefs.groupID
+ WHERE settings.name = :name
+ UNION
+ SELECT NULL AS grp
+ WHERE EXISTS (
+ SELECT prefs.id
+ FROM prefs
+ JOIN settings ON settings.id = prefs.settingID
+ WHERE settings.name = :name AND prefs.groupID IS NULL
+ )
+ `);
+ stmt.params.name = name;
+ stmts.push(stmt);
+
+ // Delete the target settings.
+ stmt = this._stmt(
+ "DELETE FROM settings WHERE name = :name"
+ );
+ stmt.params.name = name;
+ stmts.push(stmt);
+
+ // Delete prefs and groups that are no longer used.
+ stmts.push(this._stmt(
+ "DELETE FROM prefs WHERE settingID NOT IN (SELECT id FROM settings)"
+ ));
+ stmts.push(this._stmt(`
+ DELETE FROM groups WHERE id NOT IN (
+ SELECT DISTINCT groupID FROM prefs WHERE groupID NOTNULL
+ )
+ `));
+
+ let prefs = new ContentPrefStore();
+ let isPrivate = context && context.usePrivateBrowsing;
+
+ this._execStmts(stmts, {
+ onRow: function onRow(row) {
+ let grp = row.getResultByName("grp");
+ prefs.set(grp, name, undefined);
+ this._cache.set(grp, name, undefined);
+ },
+ onDone: function onDone(reason, ok) {
+ if (ok && isPrivate) {
+ for (let [sgroup, sname, ] of this._pbStore) {
+ if (sname === name) {
+ prefs.set(sgroup, name, undefined);
+ this._pbStore.remove(sgroup, name);
+ }
+ }
+ }
+ cbHandleCompletion(callback, reason);
+ if (ok) {
+ for (let [sgroup, , ] of prefs) {
+ this._cps._notifyPrefRemoved(sgroup, name, isPrivate);
+ }
+ }
+ },
+ onError: function onError(nsresult) {
+ cbHandleError(callback, nsresult);
+ }
+ });
+ },
+
+ destroy: function CPS2_destroy() {
+ if (this._statements) {
+ for (let sql in this._statements) {
+ let stmt = this._statements[sql];
+ stmt.finalize();
+ }
+ }
+ },
+
+ /**
+ * Returns the cached mozIStorageAsyncStatement for the given SQL. If no such
+ * statement is cached, one is created and cached.
+ *
+ * @param sql The SQL query string.
+ * @return The cached, possibly new, statement.
+ */
+ _stmt: function CPS2__stmt(sql) {
+ if (!this._statements)
+ this._statements = {};
+ if (!this._statements[sql])
+ this._statements[sql] = this._cps._dbConnection.createAsyncStatement(sql);
+ return this._statements[sql];
+ },
+
+ /**
+ * Executes some async statements.
+ *
+ * @param stmts An array of mozIStorageAsyncStatements.
+ * @param callbacks An object with the following methods:
+ * onRow(row) (optional)
+ * Called once for each result row.
+ * row: A mozIStorageRow.
+ * onDone(reason, reasonOK, didGetRow) (required)
+ * Called when done.
+ * reason: A nsIContentPrefService2.COMPLETE_* value.
+ * reasonOK: reason == nsIContentPrefService2.COMPLETE_OK.
+ * didGetRow: True if onRow was ever called.
+ * onError(nsresult) (optional)
+ * Called on error.
+ * nsresult: The error code.
+ */
+ _execStmts: function CPS2__execStmts(stmts, callbacks) {
+ let self = this;
+ let gotRow = false;
+ this._cps._dbConnection.executeAsync(stmts, stmts.length, {
+ handleResult: function handleResult(results) {
+ try {
+ let row = null;
+ while ((row = results.getNextRow())) {
+ gotRow = true;
+ if (callbacks.onRow)
+ callbacks.onRow.call(self, row);
+ }
+ }
+ catch (err) {
+ Cu.reportError(err);
+ }
+ },
+ handleCompletion: function handleCompletion(reason) {
+ try {
+ let ok = reason == Ci.mozIStorageStatementCallback.REASON_FINISHED;
+ callbacks.onDone.call(self,
+ ok ? Ci.nsIContentPrefCallback2.COMPLETE_OK :
+ Ci.nsIContentPrefCallback2.COMPLETE_ERROR,
+ ok, gotRow);
+ }
+ catch (err) {
+ Cu.reportError(err);
+ }
+ },
+ handleError: function handleError(error) {
+ try {
+ if (callbacks.onError)
+ callbacks.onError.call(self, Cr.NS_ERROR_FAILURE);
+ }
+ catch (err) {
+ Cu.reportError(err);
+ }
+ }
+ });
+ },
+
+ /**
+ * Parses the domain (the "group", to use the database's term) from the given
+ * string.
+ *
+ * @param groupStr Assumed to be either a string or falsey.
+ * @return If groupStr is a valid URL string, returns the domain of
+ * that URL. If groupStr is some other nonempty string,
+ * returns groupStr itself. Otherwise returns null.
+ */
+ _parseGroup: function CPS2__parseGroup(groupStr) {
+ if (!groupStr)
+ return null;
+ try {
+ var groupURI = Services.io.newURI(groupStr, null, null);
+ }
+ catch (err) {
+ return groupStr;
+ }
+ return this._cps._grouper.group(groupURI);
+ },
+
+ _schedule: function CPS2__schedule(fn) {
+ Services.tm.mainThread.dispatch(fn.bind(this),
+ Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ addObserverForName: function CPS2_addObserverForName(name, observer) {
+ this._cps._addObserver(name, observer);
+ },
+
+ removeObserverForName: function CPS2_removeObserverForName(name, observer) {
+ this._cps._removeObserver(name, observer);
+ },
+
+ extractDomain: function CPS2_extractDomain(str) {
+ return this._parseGroup(str);
+ },
+
+ /**
+ * Tests use this as a backchannel by calling it directly.
+ *
+ * @param subj This value depends on topic.
+ * @param topic The backchannel "method" name.
+ * @param data This value depends on topic.
+ */
+ observe: function CPS2_observe(subj, topic, data) {
+ switch (topic) {
+ case "test:reset":
+ let fn = subj.QueryInterface(Ci.xpcIJSWeakReference).get();
+ this._reset(fn);
+ break;
+ case "test:db":
+ let obj = subj.QueryInterface(Ci.xpcIJSWeakReference).get();
+ obj.value = this._cps._dbConnection;
+ break;
+ }
+ },
+
+ /**
+ * Removes all state from the service. Used by tests.
+ *
+ * @param callback A function that will be called when done.
+ */
+ _reset: function CPS2__reset(callback) {
+ this._pbStore.removeAll();
+ this._cache.removeAll();
+
+ let cps = this._cps;
+ cps._observers = {};
+ cps._genericObservers = [];
+
+ let tables = ["prefs", "groups", "settings"];
+ let stmts = tables.map(t => this._stmt(`DELETE FROM ${t}`));
+ this._execStmts(stmts, { onDone: () => callback() });
+ },
+
+ QueryInterface: function CPS2_QueryInterface(iid) {
+ let supportedIIDs = [
+ Ci.nsIContentPrefService2,
+ Ci.nsIObserver,
+ Ci.nsISupports,
+ ];
+ if (supportedIIDs.some(i => iid.equals(i)))
+ return this;
+ if (iid.equals(Ci.nsIContentPrefService))
+ return this._cps;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+};
+
+function checkGroupArg(group) {
+ if (!group || typeof(group) != "string")
+ throw invalidArg("domain must be nonempty string.");
+}
+
+function checkNameArg(name) {
+ if (!name || typeof(name) != "string")
+ throw invalidArg("name must be nonempty string.");
+}
+
+function checkValueArg(value) {
+ if (value === undefined)
+ throw invalidArg("value must not be undefined.");
+}
+
+function checkCallbackArg(callback, required) {
+ if (callback && !(callback instanceof Ci.nsIContentPrefCallback2))
+ throw invalidArg("callback must be an nsIContentPrefCallback2.");
+ if (!callback && required)
+ throw invalidArg("callback must be given.");
+}
+
+function invalidArg(msg) {
+ return Components.Exception(msg, Cr.NS_ERROR_INVALID_ARG);
+}
diff --git a/components/contentprefs/ContentPrefServiceChild.jsm b/components/contentprefs/ContentPrefServiceChild.jsm
new file mode 100644
index 000000000..af2a3c14b
--- /dev/null
+++ b/components/contentprefs/ContentPrefServiceChild.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [ "ContentPrefServiceChild" ];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/ContentPrefUtils.jsm");
+Cu.import("resource://gre/modules/ContentPrefStore.jsm");
+
+// We only need one bit of information out of the context.
+function contextArg(context) {
+ return (context && context.usePrivateBrowsing) ?
+ { usePrivateBrowsing: true } :
+ null;
+}
+
+function NYI() {
+ throw new Error("Do not add any new users of these functions");
+}
+
+function CallbackCaller(callback) {
+ this._callback = callback;
+}
+
+CallbackCaller.prototype = {
+ handleResult: function(contentPref) {
+ cbHandleResult(this._callback,
+ new ContentPref(contentPref.domain,
+ contentPref.name,
+ contentPref.value));
+ },
+
+ handleError: function(result) {
+ cbHandleError(this._callback, result);
+ },
+
+ handleCompletion: function(reason) {
+ cbHandleCompletion(this._callback, reason);
+ },
+};
+
+var ContentPrefServiceChild = {
+ QueryInterface: XPCOMUtils.generateQI([ Ci.nsIContentPrefService2 ]),
+
+ // Map from pref name -> set of observers
+ _observers: new Map(),
+
+ _mm: Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageSender),
+
+ _getRandomId: function() {
+ return Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+ },
+
+ // Map from random ID string -> CallbackCaller, per request
+ _requests: new Map(),
+
+ init: function() {
+ this._mm.addMessageListener("ContentPrefs:HandleResult", this);
+ this._mm.addMessageListener("ContentPrefs:HandleError", this);
+ this._mm.addMessageListener("ContentPrefs:HandleCompletion", this);
+ },
+
+ receiveMessage: function(msg) {
+ let data = msg.data;
+ let callback;
+ switch (msg.name) {
+ case "ContentPrefs:HandleResult":
+ callback = this._requests.get(data.requestId);
+ callback.handleResult(data.contentPref);
+ break;
+
+ case "ContentPrefs:HandleError":
+ callback = this._requests.get(data.requestId);
+ callback.handleError(data.error);
+ break;
+
+ case "ContentPrefs:HandleCompletion":
+ callback = this._requests.get(data.requestId);
+ this._requests.delete(data.requestId);
+ callback.handleCompletion(data.reason);
+ break;
+
+ case "ContentPrefs:NotifyObservers": {
+ let observerList = this._observers.get(data.name);
+ if (!observerList)
+ break;
+
+ for (let observer of observerList) {
+ safeCallback(observer, data.callback, data.args);
+ }
+
+ break;
+ }
+ }
+ },
+
+ _callFunction: function(call, args, callback) {
+ let requestId = this._getRandomId();
+ let data = { call: call, args: args, requestId: requestId };
+
+ this._mm.sendAsyncMessage("ContentPrefs:FunctionCall", data);
+
+ this._requests.set(requestId, new CallbackCaller(callback));
+ },
+
+ getCachedByDomainAndName: NYI,
+ getCachedBySubdomainAndName: NYI,
+ getCachedGlobal: NYI,
+
+ addObserverForName: function(name, observer) {
+ let set = this._observers.get(name);
+ if (!set) {
+ set = new Set();
+ if (this._observers.size === 0) {
+ // This is the first observer of any kind. Start listening for changes.
+ this._mm.addMessageListener("ContentPrefs:NotifyObservers", this);
+ }
+
+ // This is the first observer for this name. Start listening for changes
+ // to it.
+ this._mm.sendAsyncMessage("ContentPrefs:AddObserverForName", { name: name });
+ this._observers.set(name, set);
+ }
+
+ set.add(observer);
+ },
+
+ removeObserverForName: function(name, observer) {
+ let set = this._observers.get(name);
+ if (!set)
+ return;
+
+ set.delete(observer);
+ if (set.size === 0) {
+ // This was the last observer for this name. Stop listening for changes.
+ this._mm.sendAsyncMessage("ContentPrefs:RemoveObserverForName", { name: name });
+
+ this._observers.delete(name);
+ if (this._observers.size === 0) {
+ // This was the last observer for this process. Stop listing for all
+ // changes.
+ this._mm.removeMessageListener("ContentPrefs:NotifyObservers", this);
+ }
+ }
+ },
+
+ extractDomain: NYI
+};
+
+function forwardMethodToParent(method, signature, ...args) {
+ // Ignore superfluous arguments
+ args = args.slice(0, signature.length);
+
+ // Process context argument for forwarding
+ let contextIndex = signature.indexOf("context");
+ if (contextIndex > -1) {
+ args[contextIndex] = contextArg(args[contextIndex]);
+ }
+ // Take out the callback argument, if present.
+ let callbackIndex = signature.indexOf("callback");
+ let callback = null;
+ if (callbackIndex > -1 && args.length > callbackIndex) {
+ callback = args.splice(callbackIndex, 1)[0];
+ }
+ this._callFunction(method, args, callback);
+}
+
+for (let [method, signature] of _methodsCallableFromChild) {
+ ContentPrefServiceChild[method] = forwardMethodToParent.bind(ContentPrefServiceChild, method, signature);
+}
+
+ContentPrefServiceChild.init();
diff --git a/components/contentprefs/ContentPrefServiceParent.jsm b/components/contentprefs/ContentPrefServiceParent.jsm
new file mode 100644
index 000000000..2b425c42f
--- /dev/null
+++ b/components/contentprefs/ContentPrefServiceParent.jsm
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [ "ContentPrefServiceParent" ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/ContentPrefUtils.jsm");
+
+var ContentPrefServiceParent = {
+ _cps2: null,
+
+ init: function() {
+ let globalMM = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ this._cps2 = Cc["@mozilla.org/content-pref/service;1"]
+ .getService(Ci.nsIContentPrefService2);
+
+ globalMM.addMessageListener("ContentPrefs:FunctionCall", this);
+
+ let observerChangeHandler = this.handleObserverChange.bind(this);
+ globalMM.addMessageListener("ContentPrefs:AddObserverForName", observerChangeHandler);
+ globalMM.addMessageListener("ContentPrefs:RemoveObserverForName", observerChangeHandler);
+ globalMM.addMessageListener("child-process-shutdown", observerChangeHandler);
+ },
+
+ // Map from message manager -> content pref observer.
+ _observers: new Map(),
+
+ handleObserverChange: function(msg) {
+ let observer = this._observers.get(msg.target);
+ if (msg.name === "child-process-shutdown") {
+ // If we didn't have any observers for this child process, don't do
+ // anything.
+ if (!observer)
+ return;
+
+ for (let i of observer._names) {
+ this._cps2.removeObserverForName(i, observer);
+ }
+
+ this._observers.delete(msg.target);
+ return;
+ }
+
+ let prefName = msg.data.name;
+ if (msg.name === "ContentPrefs:AddObserverForName") {
+ // The child process is responsible for not adding multiple parent
+ // observers for the same name.
+ if (!observer) {
+ observer = {
+ onContentPrefSet: function(group, name, value, isPrivate) {
+ msg.target.sendAsyncMessage("ContentPrefs:NotifyObservers",
+ { name: name, callback: "onContentPrefSet",
+ args: [ group, name, value, isPrivate ] });
+ },
+
+ onContentPrefRemoved: function(group, name, isPrivate) {
+ msg.target.sendAsyncMessage("ContentPrefs:NotifyObservers",
+ { name: name, callback: "onContentPrefRemoved",
+ args: [ group, name, isPrivate ] });
+ },
+
+ // The names we're using this observer object for, used to keep track
+ // of the number of names we care about as well as for removing this
+ // observer if its associated process goes away.
+ _names: new Set()
+ };
+
+ this._observers.set(msg.target, observer);
+ }
+
+ observer._names.add(prefName);
+
+ this._cps2.addObserverForName(prefName, observer);
+ } else {
+ // RemoveObserverForName
+
+ // We must have an observer.
+ this._cps2.removeObserverForName(prefName, observer);
+
+ observer._names.delete(prefName);
+ if (observer._names.size === 0) {
+ // This was the last use for this observer.
+ this._observers.delete(msg.target);
+ }
+ }
+ },
+
+ receiveMessage: function(msg) {
+ let data = msg.data;
+
+ if (!_methodsCallableFromChild.some(([method, args]) => method == data.call)) {
+ throw new Error(`Can't call ${data.call} from child!`);
+ }
+
+ let args = data.args;
+ let requestId = data.requestId;
+
+ let listener = {
+ handleResult: function(pref) {
+ msg.target.sendAsyncMessage("ContentPrefs:HandleResult",
+ { requestId: requestId,
+ contentPref: {
+ domain: pref.domain,
+ name: pref.name,
+ value: pref.value
+ }
+ });
+ },
+
+ handleError: function(error) {
+ msg.target.sendAsyncMessage("ContentPrefs:HandleError",
+ { requestId: requestId,
+ error: error });
+ },
+ handleCompletion: function(reason) {
+ msg.target.sendAsyncMessage("ContentPrefs:HandleCompletion",
+ { requestId: requestId,
+ reason: reason });
+ }
+ };
+
+ // Push our special listener.
+ args.push(listener);
+
+ // And call the function.
+ this._cps2[data.call](...args);
+ }
+};
diff --git a/components/contentprefs/ContentPrefStore.jsm b/components/contentprefs/ContentPrefStore.jsm
new file mode 100644
index 000000000..7a552662f
--- /dev/null
+++ b/components/contentprefs/ContentPrefStore.jsm
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 EXPORTED_SYMBOLS = [
+ "ContentPrefStore",
+];
+
+function ContentPrefStore() {
+ this._groups = new Map();
+ this._globalNames = new Map();
+}
+
+ContentPrefStore.prototype = {
+
+ set: function CPS_set(group, name, val) {
+ if (group) {
+ if (!this._groups.has(group))
+ this._groups.set(group, new Map());
+ this._groups.get(group).set(name, val);
+ }
+ else {
+ this._globalNames.set(name, val);
+ }
+ },
+
+ setWithCast: function CPS_setWithCast(group, name, val) {
+ if (typeof(val) == "boolean")
+ val = val ? 1 : 0;
+ else if (val === undefined)
+ val = null;
+ this.set(group, name, val);
+ },
+
+ has: function CPS_has(group, name) {
+ if (group) {
+ return this._groups.has(group) &&
+ this._groups.get(group).has(name);
+ }
+ return this._globalNames.has(name);
+ },
+
+ get: function CPS_get(group, name) {
+ if (group && this._groups.has(group))
+ return this._groups.get(group).get(name);
+ return this._globalNames.get(name);
+ },
+
+ remove: function CPS_remove(group, name) {
+ if (group) {
+ if (this._groups.has(group)) {
+ this._groups.get(group).delete(name);
+ if (this._groups.get(group).size == 0)
+ this._groups.delete(group);
+ }
+ }
+ else {
+ this._globalNames.delete(name);
+ }
+ },
+
+ removeGroup: function CPS_removeGroup(group) {
+ if (group) {
+ this._groups.delete(group);
+ }
+ else {
+ this._globalNames.clear();
+ }
+ },
+
+ removeAllGroups: function CPS_removeAllGroups() {
+ this._groups.clear();
+ },
+
+ removeAll: function CPS_removeAll() {
+ this.removeAllGroups();
+ this._globalNames.clear();
+ },
+
+ groupsMatchIncludingSubdomains: function CPS_groupsMatchIncludingSubdomains(group, group2) {
+ let idx = group2.indexOf(group);
+ return (idx == group2.length - group.length &&
+ (idx == 0 || group2[idx - 1] == "."));
+ },
+
+ * [Symbol.iterator]() {
+ for (let [group, names] of this._groups) {
+ for (let [name, val] of names) {
+ yield [group, name, val];
+ }
+ }
+ for (let [name, val] of this._globalNames) {
+ yield [null, name, val];
+ }
+ },
+
+ * match(group, name, includeSubdomains) {
+ for (let sgroup of this.matchGroups(group, includeSubdomains)) {
+ if (this.has(sgroup, name))
+ yield [sgroup, this.get(sgroup, name)];
+ }
+ },
+
+ * matchGroups(group, includeSubdomains) {
+ if (group) {
+ if (includeSubdomains) {
+ for (let [sgroup, , ] of this) {
+ if (sgroup) {
+ if (this.groupsMatchIncludingSubdomains(group, sgroup)) {
+ yield sgroup;
+ }
+ }
+ }
+ }
+ else if (this._groups.has(group)) {
+ yield group;
+ }
+ }
+ else if (this._globalNames.size) {
+ yield null;
+ }
+ },
+};
diff --git a/components/contentprefs/ContentPrefUtils.jsm b/components/contentprefs/ContentPrefUtils.jsm
new file mode 100644
index 000000000..72c12e558
--- /dev/null
+++ b/components/contentprefs/ContentPrefUtils.jsm
@@ -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";
+
+this.EXPORTED_SYMBOLS = [
+ "ContentPref",
+ "cbHandleResult",
+ "cbHandleError",
+ "cbHandleCompletion",
+ "safeCallback",
+ "_methodsCallableFromChild",
+];
+
+const { interfaces: Ci, classes: Cc, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function ContentPref(domain, name, value) {
+ this.domain = domain;
+ this.name = name;
+ this.value = value;
+}
+
+ContentPref.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPref]),
+};
+
+function cbHandleResult(callback, pref) {
+ safeCallback(callback, "handleResult", [pref]);
+}
+
+function cbHandleCompletion(callback, reason) {
+ safeCallback(callback, "handleCompletion", [reason]);
+}
+
+function cbHandleError(callback, nsresult) {
+ safeCallback(callback, "handleError", [nsresult]);
+}
+
+function safeCallback(callbackObj, methodName, args) {
+ if (!callbackObj || typeof(callbackObj[methodName]) != "function")
+ return;
+ try {
+ callbackObj[methodName].apply(callbackObj, args);
+ }
+ catch (err) {
+ Cu.reportError(err);
+ }
+}
+
+const _methodsCallableFromChild = Object.freeze([
+ ["getByName", ["name", "context", "callback"]],
+ ["getByDomainAndName", ["domain", "name", "context", "callback"]],
+ ["getBySubdomainAndName", ["domain", "name", "context", "callback"]],
+ ["getGlobal", ["name", "context", "callback"]],
+ ["set", ["domain", "name", "value", "context", "callback"]],
+ ["setGlobal", ["name", "value", "context", "callback"]],
+ ["removeByDomainAndName", ["domain", "name", "context", "callback"]],
+ ["removeBySubdomainAndName", ["domain", "name", "context", "callback"]],
+ ["removeGlobal", ["name", "context", "callback"]],
+ ["removeByDomain", ["domain", "context", "callback"]],
+ ["removeBySubdomain", ["domain", "context", "callback"]],
+ ["removeByName", ["name", "context", "callback"]],
+ ["removeAllDomains", ["context", "callback"]],
+ ["removeAllGlobals", ["context", "callback"]],
+]);
diff --git a/components/contentprefs/moz.build b/components/contentprefs/moz.build
new file mode 100644
index 000000000..0733f41b6
--- /dev/null
+++ b/components/contentprefs/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'nsContentPrefService.js',
+ 'nsContentPrefService.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'ContentPrefInstance.jsm',
+ 'ContentPrefService2.jsm',
+ 'ContentPrefServiceChild.jsm',
+ 'ContentPrefServiceParent.jsm',
+ 'ContentPrefStore.jsm',
+ 'ContentPrefUtils.jsm',
+]
diff --git a/components/contentprefs/nsContentPrefService.js b/components/contentprefs/nsContentPrefService.js
new file mode 100644
index 000000000..6360134a5
--- /dev/null
+++ b/components/contentprefs/nsContentPrefService.js
@@ -0,0 +1,1332 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 CACHE_MAX_GROUP_ENTRIES = 100;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function ContentPrefService() {
+ if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
+ return Cu.import("resource://gre/modules/ContentPrefServiceChild.jsm")
+ .ContentPrefServiceChild;
+ }
+
+ // If this throws an exception, it causes the getService call to fail,
+ // but the next time a consumer tries to retrieve the service, we'll try
+ // to initialize the database again, which might work if the failure
+ // was due to a temporary condition (like being out of disk space).
+ this._dbInit();
+
+ this._observerSvc.addObserver(this, "last-pb-context-exited", false);
+
+ // Observe shutdown so we can shut down the database connection.
+ this._observerSvc.addObserver(this, "xpcom-shutdown", false);
+}
+
+Cu.import("resource://gre/modules/ContentPrefStore.jsm");
+const cache = new ContentPrefStore();
+cache.set = function CPS_cache_set(group, name, val) {
+ Object.getPrototypeOf(this).set.apply(this, arguments);
+ let groupCount = this._groups.size;
+ if (groupCount >= CACHE_MAX_GROUP_ENTRIES) {
+ // Clean half of the entries
+ for (let [group, name, ] of this) {
+ this.remove(group, name);
+ groupCount--;
+ if (groupCount < CACHE_MAX_GROUP_ENTRIES / 2)
+ break;
+ }
+ }
+};
+
+const privModeStorage = new ContentPrefStore();
+
+ContentPrefService.prototype = {
+ // XPCOM Plumbing
+
+ classID: Components.ID("{e3f772f3-023f-4b32-b074-36cf0fd5d414}"),
+
+ QueryInterface: function CPS_QueryInterface(iid) {
+ let supportedIIDs = [
+ Ci.nsIContentPrefService,
+ Ci.nsISupports,
+ ];
+ if (supportedIIDs.some(i => iid.equals(i)))
+ return this;
+ if (iid.equals(Ci.nsIContentPrefService2)) {
+ if (!this._contentPrefService2) {
+ let s = {};
+ Cu.import("resource://gre/modules/ContentPrefService2.jsm", s);
+ this._contentPrefService2 = new s.ContentPrefService2(this);
+ }
+ return this._contentPrefService2;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ // Convenience Getters
+
+ // Observer Service
+ __observerSvc: null,
+ get _observerSvc() {
+ if (!this.__observerSvc)
+ this.__observerSvc = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ return this.__observerSvc;
+ },
+
+ // Console Service
+ __consoleSvc: null,
+ get _consoleSvc() {
+ if (!this.__consoleSvc)
+ this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService);
+ return this.__consoleSvc;
+ },
+
+ // Preferences Service
+ __prefSvc: null,
+ get _prefSvc() {
+ if (!this.__prefSvc)
+ this.__prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ return this.__prefSvc;
+ },
+
+
+ // Destruction
+
+ _destroy: function ContentPrefService__destroy() {
+ this._observerSvc.removeObserver(this, "xpcom-shutdown");
+ this._observerSvc.removeObserver(this, "last-pb-context-exited");
+
+ // Finalize statements which may have been used asynchronously.
+ // FIXME(696499): put them in an object cache like other components.
+ if (this.__stmtSelectPrefID) {
+ this.__stmtSelectPrefID.finalize();
+ this.__stmtSelectPrefID = null;
+ }
+ if (this.__stmtSelectGlobalPrefID) {
+ this.__stmtSelectGlobalPrefID.finalize();
+ this.__stmtSelectGlobalPrefID = null;
+ }
+ if (this.__stmtInsertPref) {
+ this.__stmtInsertPref.finalize();
+ this.__stmtInsertPref = null;
+ }
+ if (this.__stmtInsertGroup) {
+ this.__stmtInsertGroup.finalize();
+ this.__stmtInsertGroup = null;
+ }
+ if (this.__stmtInsertSetting) {
+ this.__stmtInsertSetting.finalize();
+ this.__stmtInsertSetting = null;
+ }
+ if (this.__stmtSelectGroupID) {
+ this.__stmtSelectGroupID.finalize();
+ this.__stmtSelectGroupID = null;
+ }
+ if (this.__stmtSelectSettingID) {
+ this.__stmtSelectSettingID.finalize();
+ this.__stmtSelectSettingID = null;
+ }
+ if (this.__stmtSelectPref) {
+ this.__stmtSelectPref.finalize();
+ this.__stmtSelectPref = null;
+ }
+ if (this.__stmtSelectGlobalPref) {
+ this.__stmtSelectGlobalPref.finalize();
+ this.__stmtSelectGlobalPref = null;
+ }
+ if (this.__stmtSelectPrefsByName) {
+ this.__stmtSelectPrefsByName.finalize();
+ this.__stmtSelectPrefsByName = null;
+ }
+ if (this.__stmtDeleteSettingIfUnused) {
+ this.__stmtDeleteSettingIfUnused.finalize();
+ this.__stmtDeleteSettingIfUnused = null;
+ }
+ if (this.__stmtSelectPrefs) {
+ this.__stmtSelectPrefs.finalize();
+ this.__stmtSelectPrefs = null;
+ }
+ if (this.__stmtDeleteGroupIfUnused) {
+ this.__stmtDeleteGroupIfUnused.finalize();
+ this.__stmtDeleteGroupIfUnused = null;
+ }
+ if (this.__stmtDeletePref) {
+ this.__stmtDeletePref.finalize();
+ this.__stmtDeletePref = null;
+ }
+ if (this.__stmtUpdatePref) {
+ this.__stmtUpdatePref.finalize();
+ this.__stmtUpdatePref = null;
+ }
+
+ if (this._contentPrefService2)
+ this._contentPrefService2.destroy();
+
+ this._dbConnection.asyncClose();
+
+ // Delete references to XPCOM components to make sure we don't leak them
+ // (although we haven't observed leakage in tests). Also delete references
+ // in _observers and _genericObservers to avoid cycles with those that
+ // refer to us and don't remove themselves from those observer pools.
+ delete this._observers;
+ delete this._genericObservers;
+ delete this.__consoleSvc;
+ delete this.__grouper;
+ delete this.__observerSvc;
+ delete this.__prefSvc;
+ },
+
+
+ // nsIObserver
+
+ observe: function ContentPrefService_observe(subject, topic, data) {
+ switch (topic) {
+ case "xpcom-shutdown":
+ this._destroy();
+ break;
+ case "last-pb-context-exited":
+ this._privModeStorage.removeAll();
+ break;
+ }
+ },
+
+
+ // in-memory cache and private-browsing stores
+
+ _cache: cache,
+ _privModeStorage: privModeStorage,
+
+ // nsIContentPrefService
+
+ getPref: function ContentPrefService_getPref(aGroup, aName, aContext, aCallback) {
+ warnDeprecated();
+
+ if (!aName)
+ throw Components.Exception("aName cannot be null or an empty string",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ var group = this._parseGroupParam(aGroup);
+
+ if (aContext && aContext.usePrivateBrowsing) {
+ if (this._privModeStorage.has(group, aName)) {
+ let value = this._privModeStorage.get(group, aName);
+ if (aCallback) {
+ this._scheduleCallback(function() { aCallback.onResult(value); });
+ return undefined;
+ }
+ return value;
+ }
+ // if we don't have a pref specific to this private mode browsing
+ // session, to try to get one from normal mode
+ }
+
+ if (group == null)
+ return this._selectGlobalPref(aName, aCallback);
+ return this._selectPref(group, aName, aCallback);
+ },
+
+ setPref: function ContentPrefService_setPref(aGroup, aName, aValue, aContext) {
+ warnDeprecated();
+
+ // If the pref is already set to the value, there's nothing more to do.
+ var currentValue = this.getPref(aGroup, aName, aContext);
+ if (typeof currentValue != "undefined") {
+ if (currentValue == aValue)
+ return;
+ }
+
+ var group = this._parseGroupParam(aGroup);
+
+ if (aContext && aContext.usePrivateBrowsing) {
+ this._privModeStorage.setWithCast(group, aName, aValue);
+ this._notifyPrefSet(group, aName, aValue, aContext.usePrivateBrowsing);
+ return;
+ }
+
+ var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
+ var groupID, prefID;
+ if (group == null) {
+ groupID = null;
+ prefID = this._selectGlobalPrefID(settingID);
+ }
+ else {
+ groupID = this._selectGroupID(group) || this._insertGroup(group);
+ prefID = this._selectPrefID(groupID, settingID);
+ }
+
+ // Update the existing record, if any, or create a new one.
+ if (prefID)
+ this._updatePref(prefID, aValue);
+ else
+ this._insertPref(groupID, settingID, aValue);
+
+ this._cache.setWithCast(group, aName, aValue);
+
+ this._notifyPrefSet(group, aName, aValue,
+ aContext ? aContext.usePrivateBrowsing : false);
+ },
+
+ hasPref: function ContentPrefService_hasPref(aGroup, aName, aContext) {
+ warnDeprecated();
+
+ // XXX If consumers end up calling this method regularly, then we should
+ // optimize this to query the database directly.
+ return (typeof this.getPref(aGroup, aName, aContext) != "undefined");
+ },
+
+ hasCachedPref: function ContentPrefService_hasCachedPref(aGroup, aName, aContext) {
+ warnDeprecated();
+
+ if (!aName)
+ throw Components.Exception("aName cannot be null or an empty string",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ let group = this._parseGroupParam(aGroup);
+ let storage = aContext && aContext.usePrivateBrowsing ? this._privModeStorage: this._cache;
+ return storage.has(group, aName);
+ },
+
+ removePref: function ContentPrefService_removePref(aGroup, aName, aContext) {
+ warnDeprecated();
+
+ // If there's no old value, then there's nothing to remove.
+ if (!this.hasPref(aGroup, aName, aContext))
+ return;
+
+ var group = this._parseGroupParam(aGroup);
+
+ if (aContext && aContext.usePrivateBrowsing) {
+ this._privModeStorage.remove(group, aName);
+ this._notifyPrefRemoved(group, aName, true);
+ return;
+ }
+
+ var settingID = this._selectSettingID(aName);
+ var groupID, prefID;
+ if (group == null) {
+ groupID = null;
+ prefID = this._selectGlobalPrefID(settingID);
+ }
+ else {
+ groupID = this._selectGroupID(group);
+ prefID = this._selectPrefID(groupID, settingID);
+ }
+
+ this._deletePref(prefID);
+
+ // Get rid of extraneous records that are no longer being used.
+ this._deleteSettingIfUnused(settingID);
+ if (groupID)
+ this._deleteGroupIfUnused(groupID);
+
+ this._cache.remove(group, aName);
+ this._notifyPrefRemoved(group, aName, false);
+ },
+
+ removeGroupedPrefs: function ContentPrefService_removeGroupedPrefs(aContext) {
+ warnDeprecated();
+
+ // will not delete global preferences
+ if (aContext && aContext.usePrivateBrowsing) {
+ // keep only global prefs
+ this._privModeStorage.removeAllGroups();
+ }
+ this._cache.removeAllGroups();
+ this._dbConnection.beginTransaction();
+ try {
+ this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE groupID IS NOT NULL");
+ this._dbConnection.executeSimpleSQL("DELETE FROM groups");
+ this._dbConnection.executeSimpleSQL(`
+ DELETE FROM settings
+ WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)
+ `);
+ this._dbConnection.commitTransaction();
+ }
+ catch (ex) {
+ this._dbConnection.rollbackTransaction();
+ throw ex;
+ }
+ },
+
+ removePrefsByName: function ContentPrefService_removePrefsByName(aName, aContext) {
+ warnDeprecated();
+
+ if (!aName)
+ throw Components.Exception("aName cannot be null or an empty string",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ if (aContext && aContext.usePrivateBrowsing) {
+ for (let [group, name, ] of this._privModeStorage) {
+ if (name === aName) {
+ this._privModeStorage.remove(group, aName);
+ this._notifyPrefRemoved(group, aName, true);
+ }
+ }
+ }
+
+ var settingID = this._selectSettingID(aName);
+ if (!settingID)
+ return;
+
+ var selectGroupsStmt = this._dbCreateStatement(`
+ SELECT groups.id AS groupID, groups.name AS groupName
+ FROM prefs
+ JOIN groups ON prefs.groupID = groups.id
+ WHERE prefs.settingID = :setting
+ `);
+
+ var groupNames = [];
+ var groupIDs = [];
+ try {
+ selectGroupsStmt.params.setting = settingID;
+
+ while (selectGroupsStmt.executeStep()) {
+ groupIDs.push(selectGroupsStmt.row["groupID"]);
+ groupNames.push(selectGroupsStmt.row["groupName"]);
+ }
+ }
+ finally {
+ selectGroupsStmt.reset();
+ }
+
+ if (this.hasPref(null, aName)) {
+ groupNames.push(null);
+ }
+
+ this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE settingID = " + settingID);
+ this._dbConnection.executeSimpleSQL("DELETE FROM settings WHERE id = " + settingID);
+
+ for (var i = 0; i < groupNames.length; i++) {
+ this._cache.remove(groupNames[i], aName);
+ if (groupNames[i]) // ie. not null, which will be last (and i == groupIDs.length)
+ this._deleteGroupIfUnused(groupIDs[i]);
+ if (!aContext || !aContext.usePrivateBrowsing) {
+ this._notifyPrefRemoved(groupNames[i], aName, false);
+ }
+ }
+ },
+
+ getPrefs: function ContentPrefService_getPrefs(aGroup, aContext) {
+ warnDeprecated();
+
+ var group = this._parseGroupParam(aGroup);
+ if (aContext && aContext.usePrivateBrowsing) {
+ let prefs = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ for (let [sgroup, sname, sval] of this._privModeStorage) {
+ if (sgroup === group)
+ prefs.setProperty(sname, sval);
+ }
+ return prefs;
+ }
+
+ if (group == null)
+ return this._selectGlobalPrefs();
+ return this._selectPrefs(group);
+ },
+
+ getPrefsByName: function ContentPrefService_getPrefsByName(aName, aContext) {
+ warnDeprecated();
+
+ if (!aName)
+ throw Components.Exception("aName cannot be null or an empty string",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ if (aContext && aContext.usePrivateBrowsing) {
+ let prefs = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ for (let [sgroup, sname, sval] of this._privModeStorage) {
+ if (sname === aName)
+ prefs.setProperty(sgroup, sval);
+ }
+ return prefs;
+ }
+
+ return this._selectPrefsByName(aName);
+ },
+
+ // A hash of arrays of observers, indexed by setting name.
+ _observers: {},
+
+ // An array of generic observers, which observe all settings.
+ _genericObservers: [],
+
+ addObserver: function ContentPrefService_addObserver(aName, aObserver) {
+ warnDeprecated();
+ this._addObserver.apply(this, arguments);
+ },
+
+ _addObserver: function ContentPrefService__addObserver(aName, aObserver) {
+ var observers;
+ if (aName) {
+ if (!this._observers[aName])
+ this._observers[aName] = [];
+ observers = this._observers[aName];
+ }
+ else
+ observers = this._genericObservers;
+
+ if (observers.indexOf(aObserver) == -1)
+ observers.push(aObserver);
+ },
+
+ removeObserver: function ContentPrefService_removeObserver(aName, aObserver) {
+ warnDeprecated();
+ this._removeObserver.apply(this, arguments);
+ },
+
+ _removeObserver: function ContentPrefService__removeObserver(aName, aObserver) {
+ var observers;
+ if (aName) {
+ if (!this._observers[aName])
+ return;
+ observers = this._observers[aName];
+ }
+ else
+ observers = this._genericObservers;
+
+ if (observers.indexOf(aObserver) != -1)
+ observers.splice(observers.indexOf(aObserver), 1);
+ },
+
+ /**
+ * Construct a list of observers to notify about a change to some setting,
+ * putting setting-specific observers before before generic ones, so observers
+ * that initialize individual settings (like the page style controller)
+ * execute before observers that display multiple settings and depend on them
+ * being initialized first (like the content prefs sidebar).
+ */
+ _getObservers: function ContentPrefService__getObservers(aName) {
+ var observers = [];
+
+ if (aName && this._observers[aName])
+ observers = observers.concat(this._observers[aName]);
+ observers = observers.concat(this._genericObservers);
+
+ return observers;
+ },
+
+ /**
+ * Notify all observers about the removal of a preference.
+ */
+ _notifyPrefRemoved: function ContentPrefService__notifyPrefRemoved(aGroup, aName, aIsPrivate) {
+ for (var observer of this._getObservers(aName)) {
+ try {
+ observer.onContentPrefRemoved(aGroup, aName, aIsPrivate);
+ }
+ catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ },
+
+ /**
+ * Notify all observers about a preference change.
+ */
+ _notifyPrefSet: function ContentPrefService__notifyPrefSet(aGroup, aName, aValue, aIsPrivate) {
+ for (var observer of this._getObservers(aName)) {
+ try {
+ observer.onContentPrefSet(aGroup, aName, aValue, aIsPrivate);
+ }
+ catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ },
+
+ get grouper() {
+ warnDeprecated();
+ return this._grouper;
+ },
+ __grouper: null,
+ get _grouper() {
+ if (!this.__grouper)
+ this.__grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
+ getService(Ci.nsIContentURIGrouper);
+ return this.__grouper;
+ },
+
+ get DBConnection() {
+ warnDeprecated();
+ return this._dbConnection;
+ },
+
+
+ // Data Retrieval & Modification
+
+ __stmtSelectPref: null,
+ get _stmtSelectPref() {
+ if (!this.__stmtSelectPref)
+ this.__stmtSelectPref = this._dbCreateStatement(`
+ SELECT prefs.value AS value
+ FROM prefs
+ JOIN groups ON prefs.groupID = groups.id
+ JOIN settings ON prefs.settingID = settings.id
+ WHERE groups.name = :group
+ AND settings.name = :setting
+ `);
+
+ return this.__stmtSelectPref;
+ },
+
+ _scheduleCallback: function(func) {
+ let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+ tm.mainThread.dispatch(func, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ _selectPref: function ContentPrefService__selectPref(aGroup, aSetting, aCallback) {
+ let value = undefined;
+ if (this._cache.has(aGroup, aSetting)) {
+ value = this._cache.get(aGroup, aSetting);
+ if (aCallback) {
+ this._scheduleCallback(function() { aCallback.onResult(value); });
+ return undefined;
+ }
+ return value;
+ }
+
+ try {
+ this._stmtSelectPref.params.group = aGroup;
+ this._stmtSelectPref.params.setting = aSetting;
+
+ if (aCallback) {
+ let cache = this._cache;
+ new AsyncStatement(this._stmtSelectPref).execute({onResult: function(aResult) {
+ cache.set(aGroup, aSetting, aResult);
+ aCallback.onResult(aResult);
+ }});
+ }
+ else {
+ if (this._stmtSelectPref.executeStep()) {
+ value = this._stmtSelectPref.row["value"];
+ }
+ this._cache.set(aGroup, aSetting, value);
+ }
+ }
+ finally {
+ this._stmtSelectPref.reset();
+ }
+
+ return value;
+ },
+
+ __stmtSelectGlobalPref: null,
+ get _stmtSelectGlobalPref() {
+ if (!this.__stmtSelectGlobalPref)
+ this.__stmtSelectGlobalPref = this._dbCreateStatement(`
+ SELECT prefs.value AS value
+ FROM prefs
+ JOIN settings ON prefs.settingID = settings.id
+ WHERE prefs.groupID IS NULL
+ AND settings.name = :name
+ `);
+
+ return this.__stmtSelectGlobalPref;
+ },
+
+ _selectGlobalPref: function ContentPrefService__selectGlobalPref(aName, aCallback) {
+ let value = undefined;
+ if (this._cache.has(null, aName)) {
+ value = this._cache.get(null, aName);
+ if (aCallback) {
+ this._scheduleCallback(function() { aCallback.onResult(value); });
+ return undefined;
+ }
+ return value;
+ }
+
+ try {
+ this._stmtSelectGlobalPref.params.name = aName;
+
+ if (aCallback) {
+ let cache = this._cache;
+ new AsyncStatement(this._stmtSelectGlobalPref).execute({onResult: function(aResult) {
+ cache.set(null, aName, aResult);
+ aCallback.onResult(aResult);
+ }});
+ }
+ else {
+ if (this._stmtSelectGlobalPref.executeStep()) {
+ value = this._stmtSelectGlobalPref.row["value"];
+ }
+ this._cache.set(null, aName, value);
+ }
+ }
+ finally {
+ this._stmtSelectGlobalPref.reset();
+ }
+
+ return value;
+ },
+
+ __stmtSelectGroupID: null,
+ get _stmtSelectGroupID() {
+ if (!this.__stmtSelectGroupID)
+ this.__stmtSelectGroupID = this._dbCreateStatement(`
+ SELECT groups.id AS id
+ FROM groups
+ WHERE groups.name = :name
+ `);
+
+ return this.__stmtSelectGroupID;
+ },
+
+ _selectGroupID: function ContentPrefService__selectGroupID(aName) {
+ var id;
+
+ try {
+ this._stmtSelectGroupID.params.name = aName;
+
+ if (this._stmtSelectGroupID.executeStep())
+ id = this._stmtSelectGroupID.row["id"];
+ }
+ finally {
+ this._stmtSelectGroupID.reset();
+ }
+
+ return id;
+ },
+
+ __stmtInsertGroup: null,
+ get _stmtInsertGroup() {
+ if (!this.__stmtInsertGroup)
+ this.__stmtInsertGroup = this._dbCreateStatement(
+ "INSERT INTO groups (name) VALUES (:name)"
+ );
+
+ return this.__stmtInsertGroup;
+ },
+
+ _insertGroup: function ContentPrefService__insertGroup(aName) {
+ this._stmtInsertGroup.params.name = aName;
+ this._stmtInsertGroup.execute();
+ return this._dbConnection.lastInsertRowID;
+ },
+
+ __stmtSelectSettingID: null,
+ get _stmtSelectSettingID() {
+ if (!this.__stmtSelectSettingID)
+ this.__stmtSelectSettingID = this._dbCreateStatement(
+ "SELECT id FROM settings WHERE name = :name"
+ );
+
+ return this.__stmtSelectSettingID;
+ },
+
+ _selectSettingID: function ContentPrefService__selectSettingID(aName) {
+ var id;
+
+ try {
+ this._stmtSelectSettingID.params.name = aName;
+
+ if (this._stmtSelectSettingID.executeStep())
+ id = this._stmtSelectSettingID.row["id"];
+ }
+ finally {
+ this._stmtSelectSettingID.reset();
+ }
+
+ return id;
+ },
+
+ __stmtInsertSetting: null,
+ get _stmtInsertSetting() {
+ if (!this.__stmtInsertSetting)
+ this.__stmtInsertSetting = this._dbCreateStatement(
+ "INSERT INTO settings (name) VALUES (:name)"
+ );
+
+ return this.__stmtInsertSetting;
+ },
+
+ _insertSetting: function ContentPrefService__insertSetting(aName) {
+ this._stmtInsertSetting.params.name = aName;
+ this._stmtInsertSetting.execute();
+ return this._dbConnection.lastInsertRowID;
+ },
+
+ __stmtSelectPrefID: null,
+ get _stmtSelectPrefID() {
+ if (!this.__stmtSelectPrefID)
+ this.__stmtSelectPrefID = this._dbCreateStatement(
+ "SELECT id FROM prefs WHERE groupID = :groupID AND settingID = :settingID"
+ );
+
+ return this.__stmtSelectPrefID;
+ },
+
+ _selectPrefID: function ContentPrefService__selectPrefID(aGroupID, aSettingID) {
+ var id;
+
+ try {
+ this._stmtSelectPrefID.params.groupID = aGroupID;
+ this._stmtSelectPrefID.params.settingID = aSettingID;
+
+ if (this._stmtSelectPrefID.executeStep())
+ id = this._stmtSelectPrefID.row["id"];
+ }
+ finally {
+ this._stmtSelectPrefID.reset();
+ }
+
+ return id;
+ },
+
+ __stmtSelectGlobalPrefID: null,
+ get _stmtSelectGlobalPrefID() {
+ if (!this.__stmtSelectGlobalPrefID)
+ this.__stmtSelectGlobalPrefID = this._dbCreateStatement(
+ "SELECT id FROM prefs WHERE groupID IS NULL AND settingID = :settingID"
+ );
+
+ return this.__stmtSelectGlobalPrefID;
+ },
+
+ _selectGlobalPrefID: function ContentPrefService__selectGlobalPrefID(aSettingID) {
+ var id;
+
+ try {
+ this._stmtSelectGlobalPrefID.params.settingID = aSettingID;
+
+ if (this._stmtSelectGlobalPrefID.executeStep())
+ id = this._stmtSelectGlobalPrefID.row["id"];
+ }
+ finally {
+ this._stmtSelectGlobalPrefID.reset();
+ }
+
+ return id;
+ },
+
+ __stmtInsertPref: null,
+ get _stmtInsertPref() {
+ if (!this.__stmtInsertPref)
+ this.__stmtInsertPref = this._dbCreateStatement(`
+ INSERT INTO prefs (groupID, settingID, value)
+ VALUES (:groupID, :settingID, :value)
+ `);
+
+ return this.__stmtInsertPref;
+ },
+
+ _insertPref: function ContentPrefService__insertPref(aGroupID, aSettingID, aValue) {
+ this._stmtInsertPref.params.groupID = aGroupID;
+ this._stmtInsertPref.params.settingID = aSettingID;
+ this._stmtInsertPref.params.value = aValue;
+ this._stmtInsertPref.execute();
+ return this._dbConnection.lastInsertRowID;
+ },
+
+ __stmtUpdatePref: null,
+ get _stmtUpdatePref() {
+ if (!this.__stmtUpdatePref)
+ this.__stmtUpdatePref = this._dbCreateStatement(
+ "UPDATE prefs SET value = :value WHERE id = :id"
+ );
+
+ return this.__stmtUpdatePref;
+ },
+
+ _updatePref: function ContentPrefService__updatePref(aPrefID, aValue) {
+ this._stmtUpdatePref.params.id = aPrefID;
+ this._stmtUpdatePref.params.value = aValue;
+ this._stmtUpdatePref.execute();
+ },
+
+ __stmtDeletePref: null,
+ get _stmtDeletePref() {
+ if (!this.__stmtDeletePref)
+ this.__stmtDeletePref = this._dbCreateStatement(
+ "DELETE FROM prefs WHERE id = :id"
+ );
+
+ return this.__stmtDeletePref;
+ },
+
+ _deletePref: function ContentPrefService__deletePref(aPrefID) {
+ this._stmtDeletePref.params.id = aPrefID;
+ this._stmtDeletePref.execute();
+ },
+
+ __stmtDeleteSettingIfUnused: null,
+ get _stmtDeleteSettingIfUnused() {
+ if (!this.__stmtDeleteSettingIfUnused)
+ this.__stmtDeleteSettingIfUnused = this._dbCreateStatement(`
+ DELETE FROM settings WHERE id = :id
+ AND id NOT IN (SELECT DISTINCT settingID FROM prefs)
+ `);
+
+ return this.__stmtDeleteSettingIfUnused;
+ },
+
+ _deleteSettingIfUnused: function ContentPrefService__deleteSettingIfUnused(aSettingID) {
+ this._stmtDeleteSettingIfUnused.params.id = aSettingID;
+ this._stmtDeleteSettingIfUnused.execute();
+ },
+
+ __stmtDeleteGroupIfUnused: null,
+ get _stmtDeleteGroupIfUnused() {
+ if (!this.__stmtDeleteGroupIfUnused)
+ this.__stmtDeleteGroupIfUnused = this._dbCreateStatement(`
+ DELETE FROM groups WHERE id = :id
+ AND id NOT IN (SELECT DISTINCT groupID FROM prefs)
+ `);
+
+ return this.__stmtDeleteGroupIfUnused;
+ },
+
+ _deleteGroupIfUnused: function ContentPrefService__deleteGroupIfUnused(aGroupID) {
+ this._stmtDeleteGroupIfUnused.params.id = aGroupID;
+ this._stmtDeleteGroupIfUnused.execute();
+ },
+
+ __stmtSelectPrefs: null,
+ get _stmtSelectPrefs() {
+ if (!this.__stmtSelectPrefs)
+ this.__stmtSelectPrefs = this._dbCreateStatement(`
+ SELECT settings.name AS name, prefs.value AS value
+ FROM prefs
+ JOIN groups ON prefs.groupID = groups.id
+ JOIN settings ON prefs.settingID = settings.id
+ WHERE groups.name = :group
+ `);
+
+ return this.__stmtSelectPrefs;
+ },
+
+ _selectPrefs: function ContentPrefService__selectPrefs(aGroup) {
+ var prefs = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+
+ try {
+ this._stmtSelectPrefs.params.group = aGroup;
+
+ while (this._stmtSelectPrefs.executeStep())
+ prefs.setProperty(this._stmtSelectPrefs.row["name"],
+ this._stmtSelectPrefs.row["value"]);
+ }
+ finally {
+ this._stmtSelectPrefs.reset();
+ }
+
+ return prefs;
+ },
+
+ __stmtSelectGlobalPrefs: null,
+ get _stmtSelectGlobalPrefs() {
+ if (!this.__stmtSelectGlobalPrefs)
+ this.__stmtSelectGlobalPrefs = this._dbCreateStatement(`
+ SELECT settings.name AS name, prefs.value AS value
+ FROM prefs
+ JOIN settings ON prefs.settingID = settings.id
+ WHERE prefs.groupID IS NULL
+ `);
+
+ return this.__stmtSelectGlobalPrefs;
+ },
+
+ _selectGlobalPrefs: function ContentPrefService__selectGlobalPrefs() {
+ var prefs = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+
+ try {
+ while (this._stmtSelectGlobalPrefs.executeStep())
+ prefs.setProperty(this._stmtSelectGlobalPrefs.row["name"],
+ this._stmtSelectGlobalPrefs.row["value"]);
+ }
+ finally {
+ this._stmtSelectGlobalPrefs.reset();
+ }
+
+ return prefs;
+ },
+
+ __stmtSelectPrefsByName: null,
+ get _stmtSelectPrefsByName() {
+ if (!this.__stmtSelectPrefsByName)
+ this.__stmtSelectPrefsByName = this._dbCreateStatement(`
+ SELECT groups.name AS groupName, prefs.value AS value
+ FROM prefs
+ JOIN groups ON prefs.groupID = groups.id
+ JOIN settings ON prefs.settingID = settings.id
+ WHERE settings.name = :setting
+ `);
+
+ return this.__stmtSelectPrefsByName;
+ },
+
+ _selectPrefsByName: function ContentPrefService__selectPrefsByName(aName) {
+ var prefs = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+
+ try {
+ this._stmtSelectPrefsByName.params.setting = aName;
+
+ while (this._stmtSelectPrefsByName.executeStep())
+ prefs.setProperty(this._stmtSelectPrefsByName.row["groupName"],
+ this._stmtSelectPrefsByName.row["value"]);
+ }
+ finally {
+ this._stmtSelectPrefsByName.reset();
+ }
+
+ var global = this._selectGlobalPref(aName);
+ if (typeof global != "undefined") {
+ prefs.setProperty(null, global);
+ }
+
+ return prefs;
+ },
+
+
+ // Database Creation & Access
+
+ _dbVersion: 4,
+
+ _dbSchema: {
+ tables: {
+ groups: "id INTEGER PRIMARY KEY, \
+ name TEXT NOT NULL",
+
+ settings: "id INTEGER PRIMARY KEY, \
+ name TEXT NOT NULL",
+
+ prefs: "id INTEGER PRIMARY KEY, \
+ groupID INTEGER REFERENCES groups(id), \
+ settingID INTEGER NOT NULL REFERENCES settings(id), \
+ value BLOB, \
+ timestamp INTEGER NOT NULL DEFAULT 0" // Storage in seconds, API in ms. 0 for migrated values.
+ },
+ indices: {
+ groups_idx: {
+ table: "groups",
+ columns: ["name"]
+ },
+ settings_idx: {
+ table: "settings",
+ columns: ["name"]
+ },
+ prefs_idx: {
+ table: "prefs",
+ columns: ["timestamp", "groupID", "settingID"]
+ }
+ }
+ },
+
+ _dbConnection: null,
+
+ _dbCreateStatement: function ContentPrefService__dbCreateStatement(aSQLString) {
+ try {
+ var statement = this._dbConnection.createStatement(aSQLString);
+ }
+ catch (ex) {
+ Cu.reportError("error creating statement " + aSQLString + ": " +
+ this._dbConnection.lastError + " - " +
+ this._dbConnection.lastErrorString);
+ throw ex;
+ }
+
+ return statement;
+ },
+
+ // _dbInit and the methods it calls (_dbCreate, _dbMigrate, and version-
+ // specific migration methods) must be careful not to call any method
+ // of the service that assumes the database connection has already been
+ // initialized, since it won't be initialized until at the end of _dbInit.
+
+ _dbInit: function ContentPrefService__dbInit() {
+ var dirService = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ var dbFile = dirService.get("ProfD", Ci.nsIFile);
+ dbFile.append("content-prefs.sqlite");
+
+ var dbService = Cc["@mozilla.org/storage/service;1"].
+ getService(Ci.mozIStorageService);
+
+ var dbConnection;
+
+ if (!dbFile.exists())
+ dbConnection = this._dbCreate(dbService, dbFile);
+ else {
+ try {
+ dbConnection = dbService.openDatabase(dbFile);
+ }
+ // If the connection isn't ready after we open the database, that means
+ // the database has been corrupted, so we back it up and then recreate it.
+ catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_CORRUPTED)
+ throw e;
+ dbConnection = this._dbBackUpAndRecreate(dbService, dbFile,
+ dbConnection);
+ }
+
+ // Get the version of the schema in the file.
+ var version = dbConnection.schemaVersion;
+
+ // Try to migrate the schema in the database to the current schema used by
+ // the service. If migration fails, back up the database and recreate it.
+ if (version != this._dbVersion) {
+ try {
+ this._dbMigrate(dbConnection, version, this._dbVersion);
+ }
+ catch (ex) {
+ Cu.reportError("error migrating DB: " + ex + "; backing up and recreating");
+ dbConnection = this._dbBackUpAndRecreate(dbService, dbFile, dbConnection);
+ }
+ }
+ }
+
+ // Turn off disk synchronization checking to reduce disk churn and speed up
+ // operations when prefs are changed rapidly (such as when a user repeatedly
+ // changes the value of the browser zoom setting for a site).
+ //
+ // Note: this could cause database corruption if the OS crashes or machine
+ // loses power before the data gets written to disk, but this is considered
+ // a reasonable risk for the not-so-critical data stored in this database.
+ //
+ // If you really don't want to take this risk, however, just set the
+ // toolkit.storage.synchronous pref to 1 (NORMAL synchronization) or 2
+ // (FULL synchronization), in which case mozStorageConnection::Initialize
+ // will use that value, and we won't override it here.
+ if (!this._prefSvc.prefHasUserValue("toolkit.storage.synchronous"))
+ dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF");
+
+ this._dbConnection = dbConnection;
+ },
+
+ _dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
+ var dbConnection = aDBService.openDatabase(aDBFile);
+
+ try {
+ this._dbCreateSchema(dbConnection);
+ dbConnection.schemaVersion = this._dbVersion;
+ }
+ catch (ex) {
+ // If we failed to create the database (perhaps because the disk ran out
+ // of space), then remove the database file so we don't leave it in some
+ // half-created state from which we won't know how to recover.
+ dbConnection.close();
+ aDBFile.remove(false);
+ throw ex;
+ }
+
+ return dbConnection;
+ },
+
+ _dbCreateSchema: function ContentPrefService__dbCreateSchema(aDBConnection) {
+ this._dbCreateTables(aDBConnection);
+ this._dbCreateIndices(aDBConnection);
+ },
+
+ _dbCreateTables: function ContentPrefService__dbCreateTables(aDBConnection) {
+ for (let name in this._dbSchema.tables)
+ aDBConnection.createTable(name, this._dbSchema.tables[name]);
+ },
+
+ _dbCreateIndices: function ContentPrefService__dbCreateIndices(aDBConnection) {
+ for (let name in this._dbSchema.indices) {
+ let index = this._dbSchema.indices[name];
+ let statement = `
+ CREATE INDEX IF NOT EXISTS ${name} ON ${index.table}
+ (${index.columns.join(", ")})
+ `;
+ aDBConnection.executeSimpleSQL(statement);
+ }
+ },
+
+ _dbBackUpAndRecreate: function ContentPrefService__dbBackUpAndRecreate(aDBService,
+ aDBFile,
+ aDBConnection) {
+ aDBService.backupDatabaseFile(aDBFile, "content-prefs.sqlite.corrupt");
+
+ // Close the database, ignoring the "already closed" exception, if any.
+ // It'll be open if we're here because of a migration failure but closed
+ // if we're here because of database corruption.
+ try { aDBConnection.close() } catch (ex) {}
+
+ aDBFile.remove(false);
+
+ let dbConnection = this._dbCreate(aDBService, aDBFile);
+
+ return dbConnection;
+ },
+
+ _dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
+ /**
+ * Migrations should follow the template rules in bug 1074817 comment 3 which are:
+ * 1. Migration should be incremental and non-breaking.
+ * 2. It should be idempotent because one can downgrade an upgrade again.
+ * On downgrade:
+ * 1. Decrement schema version so that upgrade runs the migrations again.
+ */
+ aDBConnection.beginTransaction();
+
+ try {
+ /**
+ * If the schema version is 0, that means it was never set, which means
+ * the database was somehow created without the schema being applied, perhaps
+ * because the system ran out of disk space (although we check for this
+ * in _createDB) or because some other code created the database file without
+ * applying the schema. In any case, recover by simply reapplying the schema.
+ */
+ if (aOldVersion == 0) {
+ this._dbCreateSchema(aDBConnection);
+ } else {
+ for (let i = aOldVersion; i < aNewVersion; i++) {
+ let migrationName = "_dbMigrate" + i + "To" + (i + 1);
+ if (typeof this[migrationName] != 'function') {
+ throw ("no migrator function from version " + aOldVersion + " to version " + aNewVersion);
+ }
+ this[migrationName](aDBConnection);
+ }
+ }
+ aDBConnection.schemaVersion = aNewVersion;
+ aDBConnection.commitTransaction();
+ } catch (ex) {
+ aDBConnection.rollbackTransaction();
+ throw ex;
+ }
+ },
+
+ _dbMigrate1To2: function ContentPrefService___dbMigrate1To2(aDBConnection) {
+ aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
+ aDBConnection.createTable("groups", this._dbSchema.tables.groups);
+ aDBConnection.executeSimpleSQL(`
+ INSERT INTO groups (id, name)
+ SELECT id, name FROM groupsOld
+ `);
+
+ aDBConnection.executeSimpleSQL("DROP TABLE groupers");
+ aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
+ },
+
+ _dbMigrate2To3: function ContentPrefService__dbMigrate2To3(aDBConnection) {
+ this._dbCreateIndices(aDBConnection);
+ },
+
+ _dbMigrate3To4: function ContentPrefService__dbMigrate3To4(aDBConnection) {
+ // Add timestamp column if it does not exist yet. This operation is idempotent.
+ try {
+ let stmt = aDBConnection.createStatement("SELECT timestamp FROM prefs");
+ stmt.finalize();
+ } catch (e) {
+ aDBConnection.executeSimpleSQL("ALTER TABLE prefs ADD COLUMN timestamp INTEGER NOT NULL DEFAULT 0");
+ }
+
+ // To modify prefs_idx drop it and create again.
+ aDBConnection.executeSimpleSQL("DROP INDEX IF EXISTS prefs_idx");
+ this._dbCreateIndices(aDBConnection);
+ },
+
+ _parseGroupParam: function ContentPrefService__parseGroupParam(aGroup) {
+ if (aGroup == null)
+ return null;
+ if (aGroup.constructor.name == "String")
+ return aGroup.toString();
+ if (aGroup instanceof Ci.nsIURI)
+ return this.grouper.group(aGroup);
+
+ throw Components.Exception("aGroup is not a string, nsIURI or null",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ },
+};
+
+function warnDeprecated() {
+ let Deprecated = Cu.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
+ Deprecated.warning("nsIContentPrefService is deprecated. Please use nsIContentPrefService2 instead.",
+ "https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIContentPrefService2",
+ Components.stack.caller);
+}
+
+
+function HostnameGrouper() {}
+
+HostnameGrouper.prototype = {
+ // XPCOM Plumbing
+
+ classID: Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentURIGrouper]),
+
+ // nsIContentURIGrouper
+
+ group: function HostnameGrouper_group(aURI) {
+ var group;
+
+ try {
+ // Accessing the host property of the URI will throw an exception
+ // if the URI is of a type that doesn't have a host property.
+ // Otherwise, we manually throw an exception if the host is empty,
+ // since the effect is the same (we can't derive a group from it).
+
+ group = aURI.host;
+ if (!group)
+ throw ("can't derive group from host; no host in URI");
+ }
+ catch (ex) {
+ // If we don't have a host, then use the entire URI (minus the query,
+ // reference, and hash, if possible) as the group. This means that URIs
+ // like about:mozilla and about:blank will be considered separate groups,
+ // but at least they'll be grouped somehow.
+
+ // This also means that each individual file: URL will be considered
+ // its own group. This seems suboptimal, but so does treating the entire
+ // file: URL space as a single group (especially if folks start setting
+ // group-specific capabilities prefs).
+
+ // XXX Is there something better we can do here?
+
+ try {
+ var url = aURI.QueryInterface(Ci.nsIURL);
+ group = aURI.prePath + url.filePath;
+ }
+ catch (ex) {
+ group = aURI.spec;
+ }
+ }
+
+ return group;
+ }
+};
+
+function AsyncStatement(aStatement) {
+ this.stmt = aStatement;
+}
+
+AsyncStatement.prototype = {
+ execute: function AsyncStmt_execute(aCallback) {
+ let stmt = this.stmt;
+ stmt.executeAsync({
+ _callback: aCallback,
+ _hadResult: false,
+ handleResult: function(aResult) {
+ this._hadResult = true;
+ if (this._callback) {
+ let row = aResult.getNextRow();
+ this._callback.onResult(row.getResultByName("value"));
+ }
+ },
+ handleCompletion: function(aReason) {
+ if (!this._hadResult && this._callback &&
+ aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED)
+ this._callback.onResult(undefined);
+ },
+ handleError: function(aError) {}
+ });
+ }
+};
+
+// XPCOM Plumbing
+
+var components = [ContentPrefService, HostnameGrouper];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/components/contentprefs/nsContentPrefService.manifest b/components/contentprefs/nsContentPrefService.manifest
new file mode 100644
index 000000000..b6bc15721
--- /dev/null
+++ b/components/contentprefs/nsContentPrefService.manifest
@@ -0,0 +1,5 @@
+component {e3f772f3-023f-4b32-b074-36cf0fd5d414} nsContentPrefService.js
+contract @mozilla.org/content-pref/service;1 {e3f772f3-023f-4b32-b074-36cf0fd5d414}
+component {8df290ae-dcaa-4c11-98a5-2429a4dc97bb} nsContentPrefService.js
+contract @mozilla.org/content-pref/hostname-grouper;1 {8df290ae-dcaa-4c11-98a5-2429a4dc97bb}
+
diff --git a/components/crashes/CrashManager.jsm b/components/crashes/CrashManager.jsm
new file mode 100644
index 000000000..199b9185a
--- /dev/null
+++ b/components/crashes/CrashManager.jsm
@@ -0,0 +1,1285 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const myScope = this;
+
+Cu.import("resource://gre/modules/Log.jsm", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+Cu.import("resource://gre/modules/Promise.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+Cu.import("resource://gre/modules/Timer.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/KeyValueParser.jsm");
+
+this.EXPORTED_SYMBOLS = [
+ "CrashManager",
+];
+
+/**
+ * How long to wait after application startup before crash event files are
+ * automatically aggregated.
+ *
+ * We defer aggregation for performance reasons, as we don't want too many
+ * services competing for I/O immediately after startup.
+ */
+const AGGREGATE_STARTUP_DELAY_MS = 57000;
+
+const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
+
+// Converts Date to days since UNIX epoch.
+// This was copied from /services/metrics.storage.jsm. The implementation
+// does not account for leap seconds.
+function dateToDays(date) {
+ return Math.floor(date.getTime() / MILLISECONDS_IN_DAY);
+}
+
+
+/**
+ * A gateway to crash-related data.
+ *
+ * This type is generic and can be instantiated any number of times.
+ * However, most applications will typically only have one instance
+ * instantiated and that instance will point to profile and user appdata
+ * directories.
+ *
+ * Instances are created by passing an object with properties.
+ * Recognized properties are:
+ *
+ * pendingDumpsDir (string) (required)
+ * Where dump files that haven't been uploaded are located.
+ *
+ * submittedDumpsDir (string) (required)
+ * Where records of uploaded dumps are located.
+ *
+ * eventsDirs (array)
+ * Directories (defined as strings) where events files are written. This
+ * instance will collects events from files in the directories specified.
+ *
+ * storeDir (string)
+ * Directory we will use for our data store. This instance will write
+ * data files into the directory specified.
+ */
+this.CrashManager = function (options) {
+ for (let k of ["pendingDumpsDir", "submittedDumpsDir", "eventsDirs",
+ "storeDir"]) {
+ if (!(k in options)) {
+ throw new Error("Required key not present in options: " + k);
+ }
+ }
+
+ this._log = Log.repository.getLogger("Crashes.CrashManager");
+
+ for (let k in options) {
+ let v = options[k];
+
+ switch (k) {
+ case "pendingDumpsDir":
+ this._pendingDumpsDir = v;
+ break;
+
+ case "submittedDumpsDir":
+ this._submittedDumpsDir = v;
+ break;
+
+ case "eventsDirs":
+ this._eventsDirs = v;
+ break;
+
+ case "storeDir":
+ this._storeDir = v;
+ break;
+
+ default:
+ throw new Error("Unknown property in options: " + k);
+ }
+ }
+
+ // Promise for in-progress aggregation operation. We store it on the
+ // object so it can be returned for in-progress operations.
+ this._aggregatePromise = null;
+
+ // The CrashStore currently attached to this object.
+ this._store = null;
+
+ // A Task to retrieve the store. This is needed to avoid races when
+ // _getStore() is called multiple times in a short interval.
+ this._getStoreTask = null;
+
+ // The timer controlling the expiration of the CrashStore instance.
+ this._storeTimer = null;
+
+ // This is a semaphore that prevents the store from being freed by our
+ // timer-based resource freeing mechanism.
+ this._storeProtectedCount = 0;
+};
+
+this.CrashManager.prototype = Object.freeze({
+ // A crash in the main process.
+ PROCESS_TYPE_MAIN: "main",
+
+ // A crash in a content process.
+ PROCESS_TYPE_CONTENT: "content",
+
+ // A crash in a plugin process.
+ PROCESS_TYPE_PLUGIN: "plugin",
+
+ // A crash in the GPU process.
+ PROCESS_TYPE_GPU: "gpu",
+
+ // A real crash.
+ CRASH_TYPE_CRASH: "crash",
+
+ // A hang.
+ CRASH_TYPE_HANG: "hang",
+
+ // Submission result values.
+ SUBMISSION_RESULT_OK: "ok",
+ SUBMISSION_RESULT_FAILED: "failed",
+
+ DUMP_REGEX: /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.dmp$/i,
+ SUBMITTED_REGEX: /^bp-(?:hr-)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.txt$/i,
+ ALL_REGEX: /^(.*)$/,
+
+ // How long the store object should persist in memory before being
+ // automatically garbage collected.
+ STORE_EXPIRATION_MS: 60 * 1000,
+
+ // Number of days after which a crash with no activity will get purged.
+ PURGE_OLDER_THAN_DAYS: 180,
+
+ // The following are return codes for individual event file processing.
+ // File processed OK.
+ EVENT_FILE_SUCCESS: "ok",
+ // The event appears to be malformed.
+ EVENT_FILE_ERROR_MALFORMED: "malformed",
+ // The type of event is unknown.
+ EVENT_FILE_ERROR_UNKNOWN_EVENT: "unknown-event",
+
+ /**
+ * Obtain a list of all dumps pending upload.
+ *
+ * The returned value is a promise that resolves to an array of objects
+ * on success. Each element in the array has the following properties:
+ *
+ * id (string)
+ * The ID of the crash (a UUID).
+ *
+ * path (string)
+ * The filename of the crash (<UUID.dmp>)
+ *
+ * date (Date)
+ * When this dump was created
+ *
+ * The returned arry is sorted by the modified time of the file backing
+ * the entry, oldest to newest.
+ *
+ * @return Promise<Array>
+ */
+ pendingDumps: function () {
+ return this._getDirectoryEntries(this._pendingDumpsDir, this.DUMP_REGEX);
+ },
+
+ /**
+ * Obtain a list of all dump files corresponding to submitted crashes.
+ *
+ * The returned value is a promise that resolves to an Array of
+ * objects. Each object has the following properties:
+ *
+ * path (string)
+ * The path of the file this entry comes from.
+ *
+ * id (string)
+ * The crash UUID.
+ *
+ * date (Date)
+ * The (estimated) date this crash was submitted.
+ *
+ * The returned array is sorted by the modified time of the file backing
+ * the entry, oldest to newest.
+ *
+ * @return Promise<Array>
+ */
+ submittedDumps: function () {
+ return this._getDirectoryEntries(this._submittedDumpsDir,
+ this.SUBMITTED_REGEX);
+ },
+
+ /**
+ * Aggregates "loose" events files into the unified "database."
+ *
+ * This function should be called periodically to collect metadata from
+ * all events files into the central data store maintained by this manager.
+ *
+ * Once events have been stored in the backing store the corresponding
+ * source files are deleted.
+ *
+ * Only one aggregation operation is allowed to occur at a time. If this
+ * is called when an existing aggregation is in progress, the promise for
+ * the original call will be returned.
+ *
+ * @return promise<int> The number of event files that were examined.
+ */
+ aggregateEventsFiles: function () {
+ if (this._aggregatePromise) {
+ return this._aggregatePromise;
+ }
+
+ return this._aggregatePromise = Task.spawn(function* () {
+ if (this._aggregatePromise) {
+ return this._aggregatePromise;
+ }
+
+ try {
+ let unprocessedFiles = yield this._getUnprocessedEventsFiles();
+
+ let deletePaths = [];
+ let needsSave = false;
+
+ this._storeProtectedCount++;
+ for (let entry of unprocessedFiles) {
+ try {
+ let result = yield this._processEventFile(entry);
+
+ switch (result) {
+ case this.EVENT_FILE_SUCCESS:
+ needsSave = true;
+ // Fall through.
+
+ case this.EVENT_FILE_ERROR_MALFORMED:
+ deletePaths.push(entry.path);
+ break;
+
+ case this.EVENT_FILE_ERROR_UNKNOWN_EVENT:
+ break;
+
+ default:
+ Cu.reportError("Unhandled crash event file return code. Please " +
+ "file a bug: " + result);
+ }
+ } catch (ex) {
+ if (ex instanceof OS.File.Error) {
+ this._log.warn("I/O error reading " + entry.path, ex);
+ } else {
+ // We should never encounter an exception. This likely represents
+ // a coding error because all errors should be detected and
+ // converted to return codes.
+ //
+ // If we get here, report the error and delete the source file
+ // so we don't see it again.
+ Cu.reportError("Exception when processing crash event file: " +
+ Log.exceptionStr(ex));
+ deletePaths.push(entry.path);
+ }
+ }
+ }
+
+ if (needsSave) {
+ let store = yield this._getStore();
+ yield store.save();
+ }
+
+ for (let path of deletePaths) {
+ try {
+ yield OS.File.remove(path);
+ } catch (ex) {
+ this._log.warn("Error removing event file (" + path + ")", ex);
+ }
+ }
+
+ return unprocessedFiles.length;
+
+ } finally {
+ this._aggregatePromise = false;
+ this._storeProtectedCount--;
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Prune old crash data.
+ *
+ * @param date
+ * (Date) The cutoff point for pruning. Crashes without data newer
+ * than this will be pruned.
+ */
+ pruneOldCrashes: function (date) {
+ return Task.spawn(function* () {
+ let store = yield this._getStore();
+ store.pruneOldCrashes(date);
+ yield store.save();
+ }.bind(this));
+ },
+
+ /**
+ * Run tasks that should be periodically performed.
+ */
+ runMaintenanceTasks: function () {
+ return Task.spawn(function* () {
+ yield this.aggregateEventsFiles();
+
+ let offset = this.PURGE_OLDER_THAN_DAYS * MILLISECONDS_IN_DAY;
+ yield this.pruneOldCrashes(new Date(Date.now() - offset));
+ }.bind(this));
+ },
+
+ /**
+ * Schedule maintenance tasks for some point in the future.
+ *
+ * @param delay
+ * (integer) Delay in milliseconds when maintenance should occur.
+ */
+ scheduleMaintenance: function (delay) {
+ let deferred = Promise.defer();
+
+ setTimeout(() => {
+ this.runMaintenanceTasks().then(deferred.resolve, deferred.reject);
+ }, delay);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Record the occurrence of a crash.
+ *
+ * This method skips event files altogether and writes directly and
+ * immediately to the manager's data store.
+ *
+ * @param processType (string) One of the PROCESS_TYPE constants.
+ * @param crashType (string) One of the CRASH_TYPE constants.
+ * @param id (string) Crash ID. Likely a UUID.
+ * @param date (Date) When the crash occurred.
+ * @param metadata (dictionary) Crash metadata, may be empty.
+ *
+ * @return promise<null> Resolved when the store has been saved.
+ */
+ addCrash: function (processType, crashType, id, date, metadata) {
+ return Task.spawn(function* () {
+ let store = yield this._getStore();
+ if (store.addCrash(processType, crashType, id, date, metadata)) {
+ yield store.save();
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Record the remote ID for a crash.
+ *
+ * @param crashID (string) Crash ID. Likely a UUID.
+ * @param remoteID (Date) Server/Breakpad ID.
+ *
+ * @return boolean True if the remote ID was recorded.
+ */
+ setRemoteCrashID: Task.async(function* (crashID, remoteID) {
+ let store = yield this._getStore();
+ if (store.setRemoteCrashID(crashID, remoteID)) {
+ yield store.save();
+ }
+ }),
+
+ /**
+ * Generate a submission ID for use with addSubmission{Attempt,Result}.
+ */
+ generateSubmissionID() {
+ return "sub-" + Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator)
+ .generateUUID().toString().slice(1, -1);
+ },
+
+ /**
+ * Record the occurrence of a submission attempt for a crash.
+ *
+ * @param crashID (string) Crash ID. Likely a UUID.
+ * @param submissionID (string) Submission ID. Likely a UUID.
+ * @param date (Date) When the attempt occurred.
+ *
+ * @return boolean True if the attempt was recorded and false if not.
+ */
+ addSubmissionAttempt: Task.async(function* (crashID, submissionID, date) {
+ let store = yield this._getStore();
+ if (store.addSubmissionAttempt(crashID, submissionID, date)) {
+ yield store.save();
+ }
+ }),
+
+ /**
+ * Record the occurrence of a submission result for a crash.
+ *
+ * @param crashID (string) Crash ID. Likely a UUID.
+ * @param submissionID (string) Submission ID. Likely a UUID.
+ * @param date (Date) When the submission result was obtained.
+ * @param result (string) One of the SUBMISSION_RESULT constants.
+ *
+ * @return boolean True if the result was recorded and false if not.
+ */
+ addSubmissionResult: Task.async(function* (crashID, submissionID, date, result) {
+ let store = yield this._getStore();
+ if (store.addSubmissionResult(crashID, submissionID, date, result)) {
+ yield store.save();
+ }
+ }),
+
+ /**
+ * Set the classification of a crash.
+ *
+ * @param crashID (string) Crash ID. Likely a UUID.
+ * @param classifications (array) Crash classifications.
+ *
+ * @return boolean True if the data was recorded and false if not.
+ */
+ setCrashClassifications: Task.async(function* (crashID, classifications) {
+ let store = yield this._getStore();
+ if (store.setCrashClassifications(crashID, classifications)) {
+ yield store.save();
+ }
+ }),
+
+ /**
+ * Obtain the paths of all unprocessed events files.
+ *
+ * The promise-resolved array is sorted by file mtime, oldest to newest.
+ */
+ _getUnprocessedEventsFiles: function () {
+ return Task.spawn(function* () {
+ let entries = [];
+
+ for (let dir of this._eventsDirs) {
+ for (let e of yield this._getDirectoryEntries(dir, this.ALL_REGEX)) {
+ entries.push(e);
+ }
+ }
+
+ entries.sort((a, b) => { return a.date - b.date; });
+
+ return entries;
+ }.bind(this));
+ },
+
+ // See docs/crash-events.rst for the file format specification.
+ _processEventFile: function (entry) {
+ return Task.spawn(function* () {
+ let data = yield OS.File.read(entry.path);
+ let store = yield this._getStore();
+
+ let decoder = new TextDecoder();
+ data = decoder.decode(data);
+
+ let type, time;
+ let start = 0;
+ for (let i = 0; i < 2; i++) {
+ let index = data.indexOf("\n", start);
+ if (index == -1) {
+ return this.EVENT_FILE_ERROR_MALFORMED;
+ }
+
+ let sub = data.substring(start, index);
+ switch (i) {
+ case 0:
+ type = sub;
+ break;
+ case 1:
+ time = sub;
+ try {
+ time = parseInt(time, 10);
+ } catch (ex) {
+ return this.EVENT_FILE_ERROR_MALFORMED;
+ }
+ }
+
+ start = index + 1;
+ }
+ let date = new Date(time * 1000);
+ let payload = data.substring(start);
+
+ return this._handleEventFilePayload(store, entry, type, date, payload);
+ }.bind(this));
+ },
+
+ _handleEventFilePayload: function (store, entry, type, date, payload) {
+ // The payload types and formats are documented in docs/crash-events.rst.
+ // Do not change the format of an existing type. Instead, invent a new
+ // type.
+ // DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
+ let lines = payload.split("\n");
+
+ switch (type) {
+ case "crash.main.1":
+ if (lines.length > 1) {
+ this._log.warn("Multiple lines unexpected in payload for " +
+ entry.path);
+ return this.EVENT_FILE_ERROR_MALFORMED;
+ }
+ // fall-through
+ case "crash.main.2":
+ let crashID = lines[0];
+ let metadata = parseKeyValuePairsFromLines(lines.slice(1));
+ store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
+ crashID, date, metadata);
+
+ break;
+
+ case "crash.submission.1":
+ if (lines.length == 3) {
+ let [crashID, result, remoteID] = lines;
+ store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
+ crashID, date);
+
+ let submissionID = this.generateSubmissionID();
+ let succeeded = result === "true";
+ store.addSubmissionAttempt(crashID, submissionID, date);
+ store.addSubmissionResult(crashID, submissionID, date,
+ succeeded ? this.SUBMISSION_RESULT_OK :
+ this.SUBMISSION_RESULT_FAILED);
+ if (succeeded) {
+ store.setRemoteCrashID(crashID, remoteID);
+ }
+ } else {
+ return this.EVENT_FILE_ERROR_MALFORMED;
+ }
+ break;
+
+ default:
+ return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
+ }
+
+ return this.EVENT_FILE_SUCCESS;
+ },
+
+ /**
+ * The resolved promise is an array of objects with the properties:
+ *
+ * path -- String filename
+ * id -- regexp.match()[1] (likely the crash ID)
+ * date -- Date mtime of the file
+ */
+ _getDirectoryEntries: function (path, re) {
+ return Task.spawn(function* () {
+ try {
+ yield OS.File.stat(path);
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+ throw ex;
+ }
+ return [];
+ }
+
+ let it = new OS.File.DirectoryIterator(path);
+ let entries = [];
+
+ try {
+ yield it.forEach((entry, index, it) => {
+ if (entry.isDir) {
+ return undefined;
+ }
+
+ let match = re.exec(entry.name);
+ if (!match) {
+ return undefined;
+ }
+
+ return OS.File.stat(entry.path).then((info) => {
+ entries.push({
+ path: entry.path,
+ id: match[1],
+ date: info.lastModificationDate,
+ });
+ });
+ });
+ } finally {
+ it.close();
+ }
+
+ entries.sort((a, b) => { return a.date - b.date; });
+
+ return entries;
+ }.bind(this));
+ },
+
+ _getStore: function () {
+ if (this._getStoreTask) {
+ return this._getStoreTask;
+ }
+
+ return this._getStoreTask = Task.spawn(function* () {
+ try {
+ if (!this._store) {
+ yield OS.File.makeDir(this._storeDir, {
+ ignoreExisting: true,
+ unixMode: OS.Constants.libc.S_IRWXU,
+ });
+
+ let store = new CrashStore(this._storeDir);
+ yield store.load();
+
+ this._store = store;
+ this._storeTimer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+ }
+
+ // The application can go long periods without interacting with the
+ // store. Since the store takes up resources, we automatically "free"
+ // the store after inactivity so resources can be returned to the
+ // system. We do this via a timer and a mechanism that tracks when the
+ // store is being accessed.
+ this._storeTimer.cancel();
+
+ // This callback frees resources from the store unless the store
+ // is protected from freeing by some other process.
+ let timerCB = function () {
+ if (this._storeProtectedCount) {
+ this._storeTimer.initWithCallback(timerCB, this.STORE_EXPIRATION_MS,
+ this._storeTimer.TYPE_ONE_SHOT);
+ return;
+ }
+
+ // We kill the reference that we hold. GC will kill it later. If
+ // someone else holds a reference, that will prevent GC until that
+ // reference is gone.
+ this._store = null;
+ this._storeTimer = null;
+ }.bind(this);
+
+ this._storeTimer.initWithCallback(timerCB, this.STORE_EXPIRATION_MS,
+ this._storeTimer.TYPE_ONE_SHOT);
+
+ return this._store;
+ } finally {
+ this._getStoreTask = null;
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Obtain information about all known crashes.
+ *
+ * Returns an array of CrashRecord instances. Instances are read-only.
+ */
+ getCrashes: function () {
+ return Task.spawn(function* () {
+ let store = yield this._getStore();
+
+ return store.crashes;
+ }.bind(this));
+ },
+
+ getCrashCountsByDay: function () {
+ return Task.spawn(function* () {
+ let store = yield this._getStore();
+
+ return store._countsByDay;
+ }.bind(this));
+ },
+});
+
+var gCrashManager;
+
+/**
+ * Interface to storage of crash data.
+ *
+ * This type handles storage of crash metadata. It exists as a separate type
+ * from the crash manager for performance reasons: since all crash metadata
+ * needs to be loaded into memory for access, we wish to easily dispose of all
+ * associated memory when this data is no longer needed. Having an isolated
+ * object whose references can easily be lost faciliates that simple disposal.
+ *
+ * When metadata is updated, the caller must explicitly persist the changes
+ * to disk. This prevents excessive I/O during updates.
+ *
+ * The store has a mechanism for ensuring it doesn't grow too large. A ceiling
+ * is placed on the number of daily events that can occur for events that can
+ * occur with relatively high frequency, notably plugin crashes and hangs
+ * (plugins can enter cycles where they repeatedly crash). If we've reached
+ * the high water mark and new data arrives, it's silently dropped.
+ * However, the count of actual events is always preserved. This allows
+ * us to report on the severity of problems beyond the storage threshold.
+ *
+ * Main process crashes are excluded from limits because they are both
+ * important and should be rare.
+ *
+ * @param storeDir (string)
+ * Directory the store should be located in.
+ */
+function CrashStore(storeDir) {
+ this._storeDir = storeDir;
+
+ this._storePath = OS.Path.join(storeDir, "store.json.mozlz4");
+
+ // Holds the read data from disk.
+ this._data = null;
+
+ // Maps days since UNIX epoch to a Map of event types to counts.
+ // This data structure is populated when the JSON file is loaded
+ // and is also updated when new events are added.
+ this._countsByDay = new Map();
+}
+
+CrashStore.prototype = Object.freeze({
+ // Maximum number of events to store per day. This establishes a
+ // ceiling on the per-type/per-day records that will be stored.
+ HIGH_WATER_DAILY_THRESHOLD: 100,
+
+ /**
+ * Reset all data.
+ */
+ reset() {
+ this._data = {
+ v: 1,
+ crashes: new Map(),
+ corruptDate: null,
+ };
+ this._countsByDay = new Map();
+ },
+
+ /**
+ * Load data from disk.
+ *
+ * @return Promise
+ */
+ load: function () {
+ return Task.spawn(function* () {
+ // Loading replaces data.
+ this.reset();
+
+ try {
+ let decoder = new TextDecoder();
+ let data = yield OS.File.read(this._storePath, {compression: "lz4"});
+ data = JSON.parse(decoder.decode(data));
+
+ if (data.corruptDate) {
+ this._data.corruptDate = new Date(data.corruptDate);
+ }
+
+ // actualCounts is used to validate that the derived counts by
+ // days stored in the payload matches up to actual data.
+ let actualCounts = new Map();
+
+ // In the past, submissions were stored as separate crash records
+ // with an id of e.g. "someID-submission". If we find IDs ending
+ // with "-submission", we will need to convert the data to be stored
+ // as actual submissions.
+ //
+ // The old way of storing submissions was used from FF33 - FF34. We
+ // drop this old data on the floor.
+ for (let id in data.crashes) {
+ if (id.endsWith("-submission")) {
+ continue;
+ }
+
+ let crash = data.crashes[id];
+ let denormalized = this._denormalize(crash);
+
+ denormalized.submissions = new Map();
+ if (crash.submissions) {
+ for (let submissionID in crash.submissions) {
+ let submission = crash.submissions[submissionID];
+ denormalized.submissions.set(submissionID,
+ this._denormalize(submission));
+ }
+ }
+
+ this._data.crashes.set(id, denormalized);
+
+ let key = dateToDays(denormalized.crashDate) + "-" + denormalized.type;
+ actualCounts.set(key, (actualCounts.get(key) || 0) + 1);
+
+ // If we have an OOM size, count the crash as an OOM in addition to
+ // being a main process crash.
+ if (denormalized.metadata &&
+ denormalized.metadata.OOMAllocationSize) {
+ let oomKey = key + "-oom";
+ actualCounts.set(oomKey, (actualCounts.get(oomKey) || 0) + 1);
+ }
+
+ }
+
+ // The validation in this loop is arguably not necessary. We perform
+ // it as a defense against unknown bugs.
+ for (let dayKey in data.countsByDay) {
+ let day = parseInt(dayKey, 10);
+ for (let type in data.countsByDay[day]) {
+ this._ensureCountsForDay(day);
+
+ let count = data.countsByDay[day][type];
+ let key = day + "-" + type;
+
+ // If the payload says we have data for a given day but we
+ // don't, the payload is wrong. Ignore it.
+ if (!actualCounts.has(key)) {
+ continue;
+ }
+
+ // If we encountered more data in the payload than what the
+ // data structure says, use the proper value.
+ count = Math.max(count, actualCounts.get(key));
+
+ this._countsByDay.get(day).set(type, count);
+ }
+ }
+ } catch (ex) {
+ // Missing files (first use) are allowed.
+ if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+ // If we can't load for any reason, mark a corrupt date in the instance
+ // and swallow the error.
+ //
+ // The marking of a corrupted file is intentionally not persisted to
+ // disk yet. Instead, we wait until the next save(). This is to give
+ // non-permanent failures the opportunity to recover on their own.
+ this._data.corruptDate = new Date();
+ }
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Save data to disk.
+ *
+ * @return Promise<null>
+ */
+ save: function () {
+ return Task.spawn(function* () {
+ if (!this._data) {
+ return;
+ }
+
+ let normalized = {
+ // The version should be incremented whenever the format
+ // changes.
+ v: 1,
+ // Maps crash IDs to objects defining the crash.
+ crashes: {},
+ // Maps days since UNIX epoch to objects mapping event types to
+ // counts. This is a mirror of this._countsByDay. e.g.
+ // {
+ // 15000: {
+ // "main-crash": 2,
+ // "plugin-crash": 1
+ // }
+ // }
+ countsByDay: {},
+
+ // When the store was last corrupted.
+ corruptDate: null,
+ };
+
+ if (this._data.corruptDate) {
+ normalized.corruptDate = this._data.corruptDate.getTime();
+ }
+
+ for (let [id, crash] of this._data.crashes) {
+ let c = this._normalize(crash);
+
+ c.submissions = {};
+ for (let [submissionID, submission] of crash.submissions) {
+ c.submissions[submissionID] = this._normalize(submission);
+ }
+
+ normalized.crashes[id] = c;
+ }
+
+ for (let [day, m] of this._countsByDay) {
+ normalized.countsByDay[day] = {};
+ for (let [type, count] of m) {
+ normalized.countsByDay[day][type] = count;
+ }
+ }
+
+ let encoder = new TextEncoder();
+ let data = encoder.encode(JSON.stringify(normalized));
+ let size = yield OS.File.writeAtomic(this._storePath, data, {
+ tmpPath: this._storePath + ".tmp",
+ compression: "lz4"});
+ }.bind(this));
+ },
+
+ /**
+ * Normalize an object into one fit for serialization.
+ *
+ * This function along with _denormalize() serve to hack around the
+ * default handling of Date JSON serialization because Date serialization
+ * is undefined by JSON.
+ *
+ * Fields ending with "Date" are assumed to contain Date instances.
+ * We convert these to milliseconds since epoch on output and back to
+ * Date on input.
+ */
+ _normalize: function (o) {
+ let normalized = {};
+
+ for (let k in o) {
+ let v = o[k];
+ if (v && k.endsWith("Date")) {
+ normalized[k] = v.getTime();
+ } else {
+ normalized[k] = v;
+ }
+ }
+
+ return normalized;
+ },
+
+ /**
+ * Convert a serialized object back to its native form.
+ */
+ _denormalize: function (o) {
+ let n = {};
+
+ for (let k in o) {
+ let v = o[k];
+ if (v && k.endsWith("Date")) {
+ n[k] = new Date(parseInt(v, 10));
+ } else {
+ n[k] = v;
+ }
+ }
+
+ return n;
+ },
+
+ /**
+ * Prune old crash data.
+ *
+ * Crashes without recent activity are pruned from the store so the
+ * size of the store is not unbounded. If there is activity on a crash,
+ * that activity will keep the crash and all its data around for longer.
+ *
+ * @param date
+ * (Date) The cutoff at which data will be pruned. If an entry
+ * doesn't have data newer than this, it will be pruned.
+ */
+ pruneOldCrashes: function (date) {
+ for (let crash of this.crashes) {
+ let newest = crash.newestDate;
+ if (!newest || newest.getTime() < date.getTime()) {
+ this._data.crashes.delete(crash.id);
+ }
+ }
+ },
+
+ /**
+ * Date the store was last corrupted and required a reset.
+ *
+ * May be null (no corruption has ever occurred) or a Date instance.
+ */
+ get corruptDate() {
+ return this._data.corruptDate;
+ },
+
+ /**
+ * The number of distinct crashes tracked.
+ */
+ get crashesCount() {
+ return this._data.crashes.size;
+ },
+
+ /**
+ * All crashes tracked.
+ *
+ * This is an array of CrashRecord.
+ */
+ get crashes() {
+ let crashes = [];
+ for (let [, crash] of this._data.crashes) {
+ crashes.push(new CrashRecord(crash));
+ }
+
+ return crashes;
+ },
+
+ /**
+ * Obtain a particular crash from its ID.
+ *
+ * A CrashRecord will be returned if the crash exists. null will be returned
+ * if the crash is unknown.
+ */
+ getCrash: function (id) {
+ for (let crash of this.crashes) {
+ if (crash.id == id) {
+ return crash;
+ }
+ }
+
+ return null;
+ },
+
+ _ensureCountsForDay: function (day) {
+ if (!this._countsByDay.has(day)) {
+ this._countsByDay.set(day, new Map());
+ }
+ },
+
+ /**
+ * Ensure the crash record is present in storage.
+ *
+ * Returns the crash record if we're allowed to store it or null
+ * if we've hit the high water mark.
+ *
+ * @param processType
+ * (string) One of the PROCESS_TYPE constants.
+ * @param crashType
+ * (string) One of the CRASH_TYPE constants.
+ * @param id
+ * (string) The crash ID.
+ * @param date
+ * (Date) When this crash occurred.
+ * @param metadata
+ * (dictionary) Crash metadata, may be empty.
+ *
+ * @return null | object crash record
+ */
+ _ensureCrashRecord: function (processType, crashType, id, date, metadata) {
+ if (!id) {
+ // Crashes are keyed on ID, so it's not really helpful to store crashes
+ // without IDs.
+ return null;
+ }
+
+ let type = processType + "-" + crashType;
+
+ if (!this._data.crashes.has(id)) {
+ let day = dateToDays(date);
+ this._ensureCountsForDay(day);
+
+ let count = (this._countsByDay.get(day).get(type) || 0) + 1;
+ this._countsByDay.get(day).set(type, count);
+
+ if (count > this.HIGH_WATER_DAILY_THRESHOLD &&
+ processType != CrashManager.prototype.PROCESS_TYPE_MAIN) {
+ return null;
+ }
+
+ // If we have an OOM size, count the crash as an OOM in addition to
+ // being a main process crash.
+ if (metadata && metadata.OOMAllocationSize) {
+ let oomType = type + "-oom";
+ let oomCount = (this._countsByDay.get(day).get(oomType) || 0) + 1;
+ this._countsByDay.get(day).set(oomType, oomCount);
+ }
+
+ this._data.crashes.set(id, {
+ id: id,
+ remoteID: null,
+ type: type,
+ crashDate: date,
+ submissions: new Map(),
+ classifications: [],
+ metadata: metadata,
+ });
+ }
+
+ let crash = this._data.crashes.get(id);
+ crash.type = type;
+ crash.crashDate = date;
+
+ return crash;
+ },
+
+ /**
+ * Record the occurrence of a crash.
+ *
+ * @param processType (string) One of the PROCESS_TYPE constants.
+ * @param crashType (string) One of the CRASH_TYPE constants.
+ * @param id (string) Crash ID. Likely a UUID.
+ * @param date (Date) When the crash occurred.
+ * @param metadata (dictionary) Crash metadata, may be empty.
+ *
+ * @return boolean True if the crash was recorded and false if not.
+ */
+ addCrash: function (processType, crashType, id, date, metadata) {
+ return !!this._ensureCrashRecord(processType, crashType, id, date, metadata);
+ },
+
+ /**
+ * @return boolean True if the remote ID was recorded and false if not.
+ */
+ setRemoteCrashID: function (crashID, remoteID) {
+ let crash = this._data.crashes.get(crashID);
+ if (!crash || !remoteID) {
+ return false;
+ }
+
+ crash.remoteID = remoteID;
+ return true;
+ },
+
+ getCrashesOfType: function (processType, crashType) {
+ let crashes = [];
+ for (let crash of this.crashes) {
+ if (crash.isOfType(processType, crashType)) {
+ crashes.push(crash);
+ }
+ }
+
+ return crashes;
+ },
+
+ /**
+ * Ensure the submission record is present in storage.
+ * @returns [submission, crash]
+ */
+ _ensureSubmissionRecord: function (crashID, submissionID) {
+ let crash = this._data.crashes.get(crashID);
+ if (!crash || !submissionID) {
+ return null;
+ }
+
+ if (!crash.submissions.has(submissionID)) {
+ crash.submissions.set(submissionID, {
+ requestDate: null,
+ responseDate: null,
+ result: null,
+ });
+ }
+
+ return [crash.submissions.get(submissionID), crash];
+ },
+
+ /**
+ * @return boolean True if the attempt was recorded.
+ */
+ addSubmissionAttempt: function (crashID, submissionID, date) {
+ let [submission, crash] =
+ this._ensureSubmissionRecord(crashID, submissionID);
+ if (!submission) {
+ return false;
+ }
+
+ submission.requestDate = date;
+ return true;
+ },
+
+ /**
+ * @return boolean True if the response was recorded.
+ */
+ addSubmissionResult: function (crashID, submissionID, date, result) {
+ let crash = this._data.crashes.get(crashID);
+ if (!crash || !submissionID) {
+ return false;
+ }
+ let submission = crash.submissions.get(submissionID);
+ if (!submission) {
+ return false;
+ }
+
+ submission.responseDate = date;
+ submission.result = result;
+ return true;
+ },
+
+ /**
+ * @return boolean True if the classifications were set.
+ */
+ setCrashClassifications: function (crashID, classifications) {
+ let crash = this._data.crashes.get(crashID);
+ if (!crash) {
+ return false;
+ }
+
+ crash.classifications = classifications;
+ return true;
+ },
+});
+
+/**
+ * Represents an individual crash with metadata.
+ *
+ * This is a wrapper around the low-level anonymous JS objects that define
+ * crashes. It exposes a consistent and helpful API.
+ *
+ * Instances of this type should only be constructured inside this module,
+ * not externally. The constructor is not considered a public API.
+ *
+ * @param o (object)
+ * The crash's entry from the CrashStore.
+ */
+function CrashRecord(o) {
+ this._o = o;
+}
+
+CrashRecord.prototype = Object.freeze({
+ get id() {
+ return this._o.id;
+ },
+
+ get remoteID() {
+ return this._o.remoteID;
+ },
+
+ get crashDate() {
+ return this._o.crashDate;
+ },
+
+ /**
+ * Obtain the newest date in this record.
+ *
+ * This is a convenience getter. The returned value is used to determine when
+ * to expire a record.
+ */
+ get newestDate() {
+ // We currently only have 1 date, so this is easy.
+ return this._o.crashDate;
+ },
+
+ get oldestDate() {
+ return this._o.crashDate;
+ },
+
+ get type() {
+ return this._o.type;
+ },
+
+ isOfType: function (processType, crashType) {
+ return processType + "-" + crashType == this.type;
+ },
+
+ get submissions() {
+ return this._o.submissions;
+ },
+
+ get classifications() {
+ return this._o.classifications;
+ },
+
+ get metadata() {
+ return this._o.metadata;
+ },
+});
+
+/**
+ * Obtain the global CrashManager instance used by the running application.
+ *
+ * CrashManager is likely only ever instantiated once per application lifetime.
+ * The main reason it's implemented as a reusable type is to facilitate testing.
+ */
+XPCOMUtils.defineLazyGetter(this.CrashManager, "Singleton", function () {
+ if (gCrashManager) {
+ return gCrashManager;
+ }
+
+ let crPath = OS.Path.join(OS.Constants.Path.userApplicationDataDir,
+ "Crash Reports");
+ let storePath = OS.Path.join(OS.Constants.Path.profileDir, "crashes");
+
+ gCrashManager = new CrashManager({
+ pendingDumpsDir: OS.Path.join(crPath, "pending"),
+ submittedDumpsDir: OS.Path.join(crPath, "submitted"),
+ eventsDirs: [OS.Path.join(crPath, "events"), OS.Path.join(storePath, "events")],
+ storeDir: storePath,
+ });
+
+ // Automatically aggregate event files shortly after startup. This
+ // ensures it happens with some frequency.
+ //
+ // There are performance considerations here. While this is doing
+ // work and could negatively impact performance, the amount of work
+ // is kept small per run by periodically aggregating event files.
+ // Furthermore, well-behaving installs should not have much work
+ // here to do. If there is a lot of work, that install has bigger
+ // issues beyond reduced performance near startup.
+ gCrashManager.scheduleMaintenance(AGGREGATE_STARTUP_DELAY_MS);
+
+ return gCrashManager;
+});
diff --git a/components/crashes/CrashManagerTest.jsm b/components/crashes/CrashManagerTest.jsm
new file mode 100644
index 000000000..2c6c4b1a0
--- /dev/null
+++ b/components/crashes/CrashManagerTest.jsm
@@ -0,0 +1,186 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 provides common and shared functionality to facilitate
+ * testing of the Crashes component (CrashManager.jsm).
+ */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+this.EXPORTED_SYMBOLS = [
+ "configureLogging",
+ "getManager",
+ "sleep",
+ "TestingCrashManager",
+];
+
+Cu.import("resource://gre/modules/CrashManager.jsm", this);
+Cu.import("resource://gre/modules/Log.jsm", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+Cu.import("resource://gre/modules/Promise.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+Cu.import("resource://gre/modules/Timer.jsm", this);
+
+var loggingConfigured = false;
+
+this.configureLogging = function () {
+ if (loggingConfigured) {
+ return;
+ }
+
+ let log = Log.repository.getLogger("Crashes.CrashManager");
+ log.level = Log.Level.All;
+ let appender = new Log.DumpAppender();
+ appender.level = Log.Level.All;
+ log.addAppender(appender);
+ loggingConfigured = true;
+};
+
+this.sleep = function (wait) {
+ let deferred = Promise.defer();
+
+ setTimeout(() => {
+ deferred.resolve();
+ }, wait);
+
+ return deferred.promise;
+};
+
+this.TestingCrashManager = function (options) {
+ CrashManager.call(this, options);
+}
+
+this.TestingCrashManager.prototype = {
+ __proto__: CrashManager.prototype,
+
+ createDummyDump: function (submitted=false, date=new Date(), hr=false) {
+ let uuid = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator)
+ .generateUUID()
+ .toString();
+ uuid = uuid.substring(1, uuid.length - 1);
+
+ let path;
+ let mode;
+ if (submitted) {
+ if (hr) {
+ path = OS.Path.join(this._submittedDumpsDir, "bp-hr-" + uuid + ".txt");
+ } else {
+ path = OS.Path.join(this._submittedDumpsDir, "bp-" + uuid + ".txt");
+ }
+ mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR |
+ OS.Constants.libc.S_IRGRP | OS.Constants.libc.S_IROTH;
+ } else {
+ path = OS.Path.join(this._pendingDumpsDir, uuid + ".dmp");
+ mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR;
+ }
+
+ return Task.spawn(function* () {
+ let f = yield OS.File.open(path, {create: true}, {unixMode: mode});
+ yield f.setDates(date, date);
+ yield f.close();
+ dump("Created fake crash: " + path + "\n");
+
+ return uuid;
+ });
+ },
+
+ createIgnoredDumpFile: function (filename, submitted=false) {
+ let path;
+ if (submitted) {
+ path = OS.Path.join(this._submittedDumpsDir, filename);
+ } else {
+ path = OS.Path.join(this._pendingDumpsDir, filename);
+ }
+
+ return Task.spawn(function* () {
+ let mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR;
+ yield OS.File.open(path, {create: true}, {unixMode: mode});
+ dump ("Create ignored dump file: " + path + "\n");
+ });
+ },
+
+ createEventsFile: function (filename, type, date, content, index=0) {
+ let path = OS.Path.join(this._eventsDirs[index], filename);
+
+ let data = type + "\n" +
+ Math.floor(date.getTime() / 1000) + "\n" +
+ content;
+ let encoder = new TextEncoder();
+ let array = encoder.encode(data);
+
+ return Task.spawn(function* () {
+ yield OS.File.writeAtomic(path, array);
+ yield OS.File.setDates(path, date, date);
+ });
+ },
+
+ /**
+ * Overwrite event file handling to process our test file type.
+ *
+ * We can probably delete this once we have actual events defined.
+ */
+ _handleEventFilePayload: function (store, entry, type, date, payload) {
+ if (type == "test.1") {
+ if (payload == "malformed") {
+ return this.EVENT_FILE_ERROR_MALFORMED;
+ } else if (payload == "success") {
+ return this.EVENT_FILE_SUCCESS;
+ }
+ return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
+ }
+
+ return CrashManager.prototype._handleEventFilePayload.call(this,
+ store,
+ entry,
+ type,
+ date,
+ payload);
+ },
+};
+
+var DUMMY_DIR_COUNT = 0;
+
+this.getManager = function () {
+ return Task.spawn(function* () {
+ const dirMode = OS.Constants.libc.S_IRWXU;
+ let baseFile = OS.Constants.Path.profileDir;
+
+ function makeDir(create=true) {
+ return Task.spawn(function* () {
+ let path = OS.Path.join(baseFile, "dummy-dir-" + DUMMY_DIR_COUNT++);
+
+ if (!create) {
+ return path;
+ }
+
+ dump("Creating directory: " + path + "\n");
+ yield OS.File.makeDir(path, {unixMode: dirMode});
+
+ return path;
+ });
+ }
+
+ let pendingD = yield makeDir();
+ let submittedD = yield makeDir();
+ let eventsD1 = yield makeDir();
+ let eventsD2 = yield makeDir();
+
+ // Store directory is created at run-time if needed. Ensure those code
+ // paths are triggered.
+ let storeD = yield makeDir(false);
+
+ let m = new TestingCrashManager({
+ pendingDumpsDir: pendingD,
+ submittedDumpsDir: submittedD,
+ eventsDirs: [eventsD1, eventsD2],
+ storeDir: storeD,
+ });
+
+ return m;
+ });
+};
diff --git a/components/crashes/CrashService.js b/components/crashes/CrashService.js
new file mode 100644
index 000000000..ef955676c
--- /dev/null
+++ b/components/crashes/CrashService.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+/**
+ * This component makes crash data available throughout the application.
+ *
+ * It is a service because some background activity will eventually occur.
+ */
+this.CrashService = function () {};
+
+CrashService.prototype = Object.freeze({
+ classID: Components.ID("{92668367-1b17-4190-86b2-1061b2179744}"),
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsICrashService,
+ Ci.nsIObserver,
+ ]),
+
+ addCrash: function (processType, crashType, id) {
+ switch (processType) {
+ case Ci.nsICrashService.PROCESS_TYPE_MAIN:
+ processType = Services.crashmanager.PROCESS_TYPE_MAIN;
+ break;
+ case Ci.nsICrashService.PROCESS_TYPE_CONTENT:
+ processType = Services.crashmanager.PROCESS_TYPE_CONTENT;
+ break;
+ case Ci.nsICrashService.PROCESS_TYPE_PLUGIN:
+ processType = Services.crashmanager.PROCESS_TYPE_PLUGIN;
+ break;
+ case Ci.nsICrashService.PROCESS_TYPE_GPU:
+ processType = Services.crashmanager.PROCESS_TYPE_GPU;
+ break;
+ default:
+ throw new Error("Unrecognized PROCESS_TYPE: " + processType);
+ }
+
+ switch (crashType) {
+ case Ci.nsICrashService.CRASH_TYPE_CRASH:
+ crashType = Services.crashmanager.CRASH_TYPE_CRASH;
+ break;
+ case Ci.nsICrashService.CRASH_TYPE_HANG:
+ crashType = Services.crashmanager.CRASH_TYPE_HANG;
+ break;
+ default:
+ throw new Error("Unrecognized CRASH_TYPE: " + crashType);
+ }
+
+ Services.crashmanager.addCrash(processType, crashType, id, new Date());
+ },
+
+ observe: function (subject, topic, data) {
+ switch (topic) {
+ case "profile-after-change":
+ // Side-effect is the singleton is instantiated.
+ Services.crashmanager;
+ break;
+ }
+ },
+});
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CrashService]);
diff --git a/components/crashes/CrashService.manifest b/components/crashes/CrashService.manifest
new file mode 100644
index 000000000..ed45109fe
--- /dev/null
+++ b/components/crashes/CrashService.manifest
@@ -0,0 +1,3 @@
+component {92668367-1b17-4190-86b2-1061b2179744} CrashService.js
+contract @mozilla.org/crashservice;1 {92668367-1b17-4190-86b2-1061b2179744}
+category profile-after-change CrashService @mozilla.org/crashservice;1
diff --git a/components/crashes/docs/crash-events.rst b/components/crashes/docs/crash-events.rst
new file mode 100644
index 000000000..b29b27989
--- /dev/null
+++ b/components/crashes/docs/crash-events.rst
@@ -0,0 +1,176 @@
+============
+Crash Events
+============
+
+**Crash Events** refers to a special subsystem of Gecko that aims to capture
+events of interest related to process crashing and hanging.
+
+When an event worthy of recording occurs, a file containing that event's
+information is written to a well-defined location on the filesystem. The Gecko
+process periodically scans for produced files and consolidates information
+into a more unified and efficient backend store.
+
+Crash Event Files
+=================
+
+When a crash-related event occurs, a file describing that event is written
+to a well-defined directory. That directory is likely in the directory of
+the currently-active profile. However, if a profile is not yet active in
+the Gecko process, that directory likely resides in the user's *app data*
+directory (*UAppData* from the directory service).
+
+The filename of the event file is not relevant. However, producers need
+to choose a filename intelligently to avoid name collisions and race
+conditions. Since file locking is potentially dangerous at crash time,
+the convention of generating a UUID and using it as a filename has been
+adopted.
+
+File Format
+-----------
+
+All crash event files share the same high-level file format. The format
+consists of the following fields delimited by a UNIX newline (*\n*)
+character:
+
+* String event name (valid UTF-8, but likely ASCII)
+* String representation of integer seconds since UNIX epoch
+* Payload
+
+The payload is event specific and may contain UNIX newline characters.
+The recommended method for parsing is to split at most 3 times on UNIX
+newline and then dispatch to an event-specific parsed based on the
+event name.
+
+If an unknown event type is encountered, the event can safely be ignored
+until later. This helps ensure that application downgrades (potentially
+due to elevated crash rate) don't result in data loss.
+
+The format and semantics of each event type are meant to be constant once
+that event type is committed to the main Firefox repository. If new metadata
+needs to be captured or the meaning of data captured in an event changes,
+that change should be expressed through the invention of a new event type.
+For this reason, event names are highly recommended to contain a version.
+e.g. instead of a *Gecko process crashed* event, we prefer a *Gecko process
+crashed v1* event.
+
+Event Types
+-----------
+
+Each subsection documents the different types of crash events that may be
+produced. Each section name corresponds to the first line of the crash
+event file.
+
+Currently only main process crashes produce event files. Because crashes and
+hangs in child processes can be easily recorded by the main process, we do not
+foresee the need for writing event files for child processes, design
+considerations below notwithstanding.
+
+crash.main.2
+^^^^^^^^^^^^
+
+This event is produced when the main process crashes.
+
+The payload of this event is delimited by UNIX newlines (*\n*) and contains the
+following fields:
+
+* The crash ID string, very likely a UUID
+* 0 or more lines of metadata, each containing one key=value pair of text
+
+crash.main.1
+^^^^^^^^^^^^
+
+This event is produced when the main process crashes.
+
+The payload of this event is the string crash ID, very likely a UUID.
+There should be ``UUID.dmp`` and ``UUID.extra`` files on disk, saved by
+Breakpad.
+
+crash.submission.1
+^^^^^^^^^^^^^^^^^^
+
+This event is produced when a crash is submitted.
+
+The payload of this event is delimited by UNIX newlines (*\n*) and contains the
+following fields:
+
+* The crash ID string
+* "true" if the submission succeeded or "false" otherwise
+* The remote crash ID string if the submission succeeded
+
+Aggregated Event Log
+====================
+
+Crash events are aggregated together into a unified event *log*. Currently,
+this *log* is really a JSON file. However, this is an implementation detail
+and it could change at any time. The interface to crash data provided by
+the JavaScript API is the only supported interface.
+
+Design Considerations
+=====================
+
+There are many considerations influencing the design of this subsystem.
+We attempt to document them in this section.
+
+Decoupling of Event Files from Final Data Structure
+---------------------------------------------------
+
+While it is certainly possible for the Gecko process to write directly to
+the final data structure on disk, there is an intentional decoupling between
+the production of events and their transition into final storage. Along the
+same vein, the choice to have events written to multiple files by producers
+is deliberate.
+
+Some recorded events are written immediately after a process crash. This is
+a very uncertain time for the host system. There is a high liklihood the
+system is in an exceptional state, such as memory exhaustion. Therefore, any
+action taken after crashing needs to be very deliberate about what it does.
+Excessive memory allocation and certain system calls may cause the system
+to crash again or the machine's condition to worsen. This means that the act
+of recording a crash event must be very light weight. Writing a new file from
+nothing is very light weight. This is one reason we write separate files.
+
+Another reason we write separate files is because if the main Gecko process
+itself crashes (as opposed to say a plugin process), the crash reporter (not
+Gecko) is running and the crash reporter needs to handle the writing of the
+event info. If this writing is involved (say loading, parsing, updating, and
+reserializing back to disk), this logic would need to be implemented in both
+Gecko and the crash reporter or would need to be implemented in such a way
+that both could use. Neither of these is very practical from a software
+lifecycle management perspective. It's much easier to have separate processes
+write a simple file and to let a single implementation do all the complex
+work.
+
+Idempotent Event Processing
+===========================
+
+Processing of event files has been designed such that the result is
+idempotent regardless of what order those files are processed in. This is
+not only a good design decision, but it is arguably necessary. While event
+files are processed in order by file mtime, filesystem times may not have
+the resolution required for proper sorting. Therefore, processing order is
+merely an optimistic assumption.
+
+Aggregated Storage Format
+=========================
+
+Crash events are aggregated into a unified data structure on disk. That data
+structure is currently LZ4-compressed JSON and is represented by a single file.
+
+The choice of a single JSON file was initially driven by time and complexity
+concerns. Before changing the format or adding significant amounts of new
+data, some considerations must be taken into account.
+
+First, in well-behaving installs, crash data should be minimal. Crashes and
+hangs will be rare and thus the size of the crash data should remain small
+over time.
+
+The choice of a single JSON file has larger implications as the amount of
+crash data grows. As new data is accumulated, we need to read and write
+an entire file to make small updates. LZ4 compression helps reduce I/O.
+But, there is a potential for unbounded file growth. We establish a
+limit for the max age of records. Anything older than that limit is
+pruned. We also establish a daily limit on the number of crashes we will
+store. All crashes beyond the first N in a day have no payload and are
+only recorded by the presence of a count. This count ensures we can
+distinguish between ``N`` and ``100 * N``, which are very different
+values!
diff --git a/components/crashes/docs/index.rst b/components/crashes/docs/index.rst
new file mode 100644
index 000000000..e2ab50ea4
--- /dev/null
+++ b/components/crashes/docs/index.rst
@@ -0,0 +1,24 @@
+.. _crashes_crashmanager:
+
+=============
+Crash Manager
+=============
+
+The **Crash Manager** is a service and interface for managing crash
+data within the Gecko application.
+
+From JavaScript, the service can be accessed via::
+
+ Cu.import("resource://gre/modules/Services.jsm");
+ let crashManager = Services.crashmanager;
+
+That will give you an instance of ``CrashManager`` from ``CrashManager.jsm``.
+From there, you can access and manipulate crash data.
+
+Other Documents
+===============
+
+.. toctree::
+ :maxdepth: 1
+
+ crash-events
diff --git a/components/crashes/moz.build b/components/crashes/moz.build
new file mode 100644
index 000000000..25b70c875
--- /dev/null
+++ b/components/crashes/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+SPHINX_TREES['crash-manager'] = 'docs'
+
+EXTRA_COMPONENTS += [
+ 'CrashService.js',
+ 'CrashService.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'CrashManager.jsm',
+]
+
+TESTING_JS_MODULES += [
+ 'CrashManagerTest.jsm',
+]
+
+XPIDL_MODULE = 'toolkit_crashservice'
+
+XPIDL_SOURCES += [
+ 'nsICrashService.idl',
+]
diff --git a/components/crashes/nsICrashService.idl b/components/crashes/nsICrashService.idl
new file mode 100644
index 000000000..d3ce8c19d
--- /dev/null
+++ b/components/crashes/nsICrashService.idl
@@ -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 "nsISupports.idl"
+
+[scriptable, uuid(f60d76e5-62c3-4f58-89f6-b726c2b7bc20)]
+interface nsICrashService : nsISupports
+{
+ /**
+ * Records the occurrence of a crash.
+ *
+ * @param processType
+ * One of the PROCESS_TYPE constants defined below.
+ * @param crashType
+ * One of the CRASH_TYPE constants defined below.
+ * @param id
+ * Crash ID. Likely a UUID.
+ */
+ void addCrash(in long processType, in long crashType, in AString id);
+
+ const long PROCESS_TYPE_MAIN = 0;
+ const long PROCESS_TYPE_CONTENT = 1;
+ const long PROCESS_TYPE_PLUGIN = 2;
+ const long PROCESS_TYPE_GPU = 4;
+
+ const long CRASH_TYPE_CRASH = 0;
+ const long CRASH_TYPE_HANG = 1;
+};
diff --git a/components/crashmonitor/CrashMonitor.jsm b/components/crashmonitor/CrashMonitor.jsm
new file mode 100644
index 000000000..34f4f26d1
--- /dev/null
+++ b/components/crashmonitor/CrashMonitor.jsm
@@ -0,0 +1,224 @@
+/* -*- 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/. */
+
+/**
+ * Crash Monitor
+ *
+ * Monitors execution of a program to detect possible crashes. After
+ * program termination, the monitor can be queried during the next run
+ * to determine whether the last run exited cleanly or not.
+ *
+ * The monitoring is done by registering and listening for special
+ * notifications, or checkpoints, known to be sent by the monitored
+ * program as different stages in the execution are reached. As they
+ * are observed, these notifications are written asynchronously to a
+ * checkpoint file.
+ *
+ * During next program startup the crash monitor reads the checkpoint
+ * file from the last session. If notifications are missing, a crash
+ * has likely happened. By inspecting the notifications present, it is
+ * possible to determine what stages were reached in the program
+ * before the crash.
+ *
+ * Note that since the file is written asynchronously it is possible
+ * that a received notification is lost if the program crashes right
+ * after a checkpoint, but before crash monitor has been able to write
+ * it to disk. Thus, while the presence of a notification in the
+ * checkpoint file tells us that the corresponding stage was reached
+ * during the last run, the absence of a notification after a crash
+ * does not necessarily tell us that the checkpoint wasn't reached.
+ */
+
+this.EXPORTED_SYMBOLS = [ "CrashMonitor" ];
+
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+
+const NOTIFICATIONS = [
+ "final-ui-startup",
+ "sessionstore-windows-restored",
+ "quit-application-granted",
+ "quit-application",
+ "profile-change-net-teardown",
+ "profile-change-teardown",
+ "profile-before-change",
+ "sessionstore-final-state-write-complete"
+];
+
+var CrashMonitorInternal = {
+
+ /**
+ * Notifications received during the current session.
+ *
+ * Object where a property with a value of |true| means that the
+ * notification of the same name has been received at least once by
+ * the CrashMonitor during this session. Notifications that have not
+ * yet been received are not present as properties. |NOTIFICATIONS|
+ * lists the notifications tracked by the CrashMonitor.
+ */
+ checkpoints: {},
+
+ /**
+ * Notifications received during previous session.
+ *
+ * Available after |loadPreviousCheckpoints|. Promise which resolves
+ * to an object containing a set of properties, where a property
+ * with a value of |true| means that the notification with the same
+ * name as the property name was received at least once last
+ * session.
+ */
+ previousCheckpoints: null,
+
+ /* Deferred for AsyncShutdown blocker */
+ profileBeforeChangeDeferred: Promise.defer(),
+
+ /**
+ * Path to checkpoint file.
+ *
+ * Each time a new notification is received, this file is written to
+ * disc to reflect the information in |checkpoints|.
+ */
+ path: OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"),
+
+ /**
+ * Load checkpoints from previous session asynchronously.
+ *
+ * @return {Promise} A promise that resolves/rejects once loading is complete
+ */
+ loadPreviousCheckpoints: function () {
+ this.previousCheckpoints = Task.spawn(function*() {
+ let data;
+ try {
+ data = yield OS.File.read(CrashMonitorInternal.path, { encoding: "utf-8" });
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error)) {
+ throw ex;
+ }
+ if (!ex.becauseNoSuchFile) {
+ Cu.reportError("Error while loading crash monitor data: " + ex.toString());
+ }
+
+ return null;
+ }
+
+ let notifications;
+ try {
+ notifications = JSON.parse(data);
+ } catch (ex) {
+ Cu.reportError("Error while parsing crash monitor data: " + ex);
+ return null;
+ }
+
+ // If `notifications` isn't an object, then the monitor data isn't valid.
+ if (Object(notifications) !== notifications) {
+ Cu.reportError("Error while parsing crash monitor data: invalid monitor data");
+ return null;
+ }
+
+ return Object.freeze(notifications);
+ });
+
+ return this.previousCheckpoints;
+ }
+};
+
+this.CrashMonitor = {
+
+ /**
+ * Notifications received during previous session.
+ *
+ * Return object containing the set of notifications received last
+ * session as keys with values set to |true|.
+ *
+ * @return {Promise} A promise resolving to previous checkpoints
+ */
+ get previousCheckpoints() {
+ if (!CrashMonitorInternal.initialized) {
+ throw new Error("CrashMonitor must be initialized before getting previous checkpoints");
+ }
+
+ return CrashMonitorInternal.previousCheckpoints
+ },
+
+ /**
+ * Initialize CrashMonitor.
+ *
+ * Should only be called from the CrashMonitor XPCOM component.
+ *
+ * @return {Promise}
+ */
+ init: function () {
+ if (CrashMonitorInternal.initialized) {
+ throw new Error("CrashMonitor.init() must only be called once!");
+ }
+
+ let promise = CrashMonitorInternal.loadPreviousCheckpoints();
+ // Add "profile-after-change" to checkpoint as this method is
+ // called after receiving it
+ CrashMonitorInternal.checkpoints["profile-after-change"] = true;
+
+ NOTIFICATIONS.forEach(function (aTopic) {
+ Services.obs.addObserver(this, aTopic, false);
+ }, this);
+
+ // Add shutdown blocker for profile-before-change
+ OS.File.profileBeforeChange.addBlocker(
+ "CrashMonitor: Writing notifications to file after receiving profile-before-change",
+ CrashMonitorInternal.profileBeforeChangeDeferred.promise,
+ () => this.checkpoints
+ );
+
+ CrashMonitorInternal.initialized = true;
+ return promise;
+ },
+
+ /**
+ * Handle registered notifications.
+ *
+ * Update checkpoint file for every new notification received.
+ */
+ observe: function (aSubject, aTopic, aData) {
+ if (!(aTopic in CrashMonitorInternal.checkpoints)) {
+ // If this is the first time this notification is received,
+ // remember it and write it to file
+ CrashMonitorInternal.checkpoints[aTopic] = true;
+ Task.spawn(function* () {
+ try {
+ let data = JSON.stringify(CrashMonitorInternal.checkpoints);
+
+ /* Write to the checkpoint file asynchronously, off the main
+ * thread, for performance reasons. Note that this means
+ * that there's not a 100% guarantee that the file will be
+ * written by the time the notification completes. The
+ * exception is profile-before-change which has a shutdown
+ * blocker. */
+ yield OS.File.writeAtomic(
+ CrashMonitorInternal.path,
+ data, {tmpPath: CrashMonitorInternal.path + ".tmp"});
+
+ } finally {
+ // Resolve promise for blocker
+ if (aTopic == "profile-before-change") {
+ CrashMonitorInternal.profileBeforeChangeDeferred.resolve();
+ }
+ }
+ });
+ }
+
+ if (NOTIFICATIONS.every(elem => elem in CrashMonitorInternal.checkpoints)) {
+ // All notifications received, unregister observers
+ NOTIFICATIONS.forEach(function (aTopic) {
+ Services.obs.removeObserver(this, aTopic);
+ }, this);
+ }
+ }
+};
+Object.freeze(this.CrashMonitor);
diff --git a/components/crashmonitor/crashmonitor.manifest b/components/crashmonitor/crashmonitor.manifest
new file mode 100644
index 000000000..59e336f82
--- /dev/null
+++ b/components/crashmonitor/crashmonitor.manifest
@@ -0,0 +1,3 @@
+component {d9d75e86-8f17-4c57-993e-f738f0d86d42} nsCrashMonitor.js
+contract @mozilla.org/toolkit/crashmonitor;1 {d9d75e86-8f17-4c57-993e-f738f0d86d42}
+category profile-after-change CrashMonitor @mozilla.org/toolkit/crashmonitor;1
diff --git a/components/crashmonitor/moz.build b/components/crashmonitor/moz.build
new file mode 100644
index 000000000..0f85aa85b
--- /dev/null
+++ b/components/crashmonitor/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_JS_MODULES += [
+ 'CrashMonitor.jsm',
+]
+
+EXTRA_COMPONENTS += [
+ 'crashmonitor.manifest',
+ 'nsCrashMonitor.js',
+]
diff --git a/components/crashmonitor/nsCrashMonitor.js b/components/crashmonitor/nsCrashMonitor.js
new file mode 100644
index 000000000..41e0dc901
--- /dev/null
+++ b/components/crashmonitor/nsCrashMonitor.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var Scope = {}
+Components.utils.import("resource://gre/modules/CrashMonitor.jsm", Scope);
+var MonitorAPI = Scope.CrashMonitor;
+
+function CrashMonitor() {}
+
+CrashMonitor.prototype = {
+
+ classID: Components.ID("{d9d75e86-8f17-4c57-993e-f738f0d86d42}"),
+ contractID: "@mozilla.org/toolkit/crashmonitor;1",
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "profile-after-change":
+ MonitorAPI.init();
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CrashMonitor]);
diff --git a/components/ctypes/ctypes.cpp b/components/ctypes/ctypes.cpp
new file mode 100644
index 000000000..249e26983
--- /dev/null
+++ b/components/ctypes/ctypes.cpp
@@ -0,0 +1,151 @@
+/* -*- 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 "ctypes.h"
+#include "jsapi.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsMemory.h"
+#include "nsString.h"
+#include "nsNativeCharsetUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozJSComponentLoader.h"
+#include "nsZipArchive.h"
+#include "xpc_make_class.h"
+
+#define JSCTYPES_CONTRACTID \
+ "@mozilla.org/jsctypes;1"
+
+
+#define JSCTYPES_CID \
+{ 0xc797702, 0x1c60, 0x4051, { 0x9d, 0xd7, 0x4d, 0x74, 0x5, 0x60, 0x56, 0x42 } }
+
+namespace mozilla {
+namespace ctypes {
+
+static char*
+UnicodeToNative(JSContext *cx, const char16_t *source, size_t slen)
+{
+ nsAutoCString native;
+ nsDependentString unicode(reinterpret_cast<const char16_t*>(source), slen);
+ nsresult rv = NS_CopyUnicodeToNative(unicode, native);
+ if (NS_FAILED(rv)) {
+ JS_ReportErrorASCII(cx, "could not convert string to native charset");
+ return nullptr;
+ }
+
+ char* result = static_cast<char*>(JS_malloc(cx, native.Length() + 1));
+ if (!result)
+ return nullptr;
+
+ memcpy(result, native.get(), native.Length() + 1);
+ return result;
+}
+
+static JSCTypesCallbacks sCallbacks = {
+ UnicodeToNative
+};
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(Module)
+
+NS_IMPL_ISUPPORTS(Module, nsIXPCScriptable)
+
+Module::Module()
+{
+}
+
+Module::~Module()
+{
+}
+
+#define XPC_MAP_CLASSNAME Module
+#define XPC_MAP_QUOTED_CLASSNAME "Module"
+#define XPC_MAP_WANT_CALL
+#define XPC_MAP_FLAGS nsIXPCScriptable::WANT_CALL
+#include "xpc_map_end.h"
+
+static bool
+SealObjectAndPrototype(JSContext* cx, JS::Handle<JSObject *> parent, const char* name)
+{
+ JS::Rooted<JS::Value> prop(cx);
+ if (!JS_GetProperty(cx, parent, name, &prop))
+ return false;
+
+ if (prop.isUndefined()) {
+ // Pretend we sealed the object.
+ return true;
+ }
+
+ JS::Rooted<JSObject*> obj(cx, prop.toObjectOrNull());
+ if (!JS_GetProperty(cx, obj, "prototype", &prop))
+ return false;
+
+ JS::Rooted<JSObject*> prototype(cx, prop.toObjectOrNull());
+ return JS_FreezeObject(cx, obj) && JS_FreezeObject(cx, prototype);
+}
+
+static bool
+InitAndSealCTypesClass(JSContext* cx, JS::Handle<JSObject*> global)
+{
+ // Init the ctypes object.
+ if (!JS_InitCTypesClass(cx, global))
+ return false;
+
+ // Set callbacks for charset conversion and such.
+ JS::Rooted<JS::Value> ctypes(cx);
+ if (!JS_GetProperty(cx, global, "ctypes", &ctypes))
+ return false;
+
+ JS_SetCTypesCallbacks(ctypes.toObjectOrNull(), &sCallbacks);
+
+ // Seal up Object, Function, Array and Error and their prototypes. (This
+ // single object instance is shared amongst everyone who imports the ctypes
+ // module.)
+ if (!SealObjectAndPrototype(cx, global, "Object") ||
+ !SealObjectAndPrototype(cx, global, "Function") ||
+ !SealObjectAndPrototype(cx, global, "Array") ||
+ !SealObjectAndPrototype(cx, global, "Error"))
+ return false;
+
+ return true;
+}
+
+NS_IMETHODIMP
+Module::Call(nsIXPConnectWrappedNative* wrapper,
+ JSContext* cx,
+ JSObject* obj,
+ const JS::CallArgs& args,
+ bool* _retval)
+{
+ mozJSComponentLoader* loader = mozJSComponentLoader::Get();
+ JS::Rooted<JSObject*> targetObj(cx);
+ nsresult rv = loader->FindTargetObject(cx, &targetObj);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = InitAndSealCTypesClass(cx, targetObj);
+ return NS_OK;
+}
+
+} // namespace ctypes
+} // namespace mozilla
+
+NS_DEFINE_NAMED_CID(JSCTYPES_CID);
+
+static const mozilla::Module::CIDEntry kCTypesCIDs[] = {
+ { &kJSCTYPES_CID, false, nullptr, mozilla::ctypes::ModuleConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kCTypesContracts[] = {
+ { JSCTYPES_CONTRACTID, &kJSCTYPES_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kCTypesModule = {
+ mozilla::Module::kVersion,
+ kCTypesCIDs,
+ kCTypesContracts
+};
+
+NSMODULE_DEFN(jsctypes) = &kCTypesModule;
diff --git a/components/ctypes/ctypes.h b/components/ctypes/ctypes.h
new file mode 100644
index 000000000..b72f22c1c
--- /dev/null
+++ b/components/ctypes/ctypes.h
@@ -0,0 +1,30 @@
+/* -*- 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 COMPONENTS_CTYPES_H
+#define COMPONENTS_CTYPES_H
+
+#include "nsIXPCScriptable.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace ctypes {
+
+class Module final : public nsIXPCScriptable
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIXPCSCRIPTABLE
+
+ Module();
+
+private:
+ ~Module();
+};
+
+} // namespace ctypes
+} // namespace mozilla
+
+#endif
diff --git a/components/ctypes/ctypes.jsm b/components/ctypes/ctypes.jsm
new file mode 100644
index 000000000..f22d01184
--- /dev/null
+++ b/components/ctypes/ctypes.jsm
@@ -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/. */
+
+this.EXPORTED_SYMBOLS = [ "ctypes" ];
+
+/*
+ * This is the js module for ctypes. Import it like so:
+ * Components.utils.import("resource://gre/modules/ctypes.jsm");
+ *
+ * This will create a 'ctypes' object, which provides an interface to describe
+ * and instantiate C types and call C functions from a dynamic library.
+ *
+ * For documentation on the API, see:
+ * https://developer.mozilla.org/en/js-ctypes/js-ctypes_reference
+ *
+ */
+
+// Initialize the ctypes object. You do not need to do this yourself.
+const init = Components.classes["@mozilla.org/jsctypes;1"].createInstance();
+init();
+
diff --git a/components/ctypes/moz.build b/components/ctypes/moz.build
new file mode 100644
index 000000000..9ee1e48c6
--- /dev/null
+++ b/components/ctypes/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+LOCAL_INCLUDES += ['/js/xpconnect/loader']
+
+SOURCES += ['ctypes.cpp']
+
+EXTRA_JS_MODULES += ['ctypes.jsm']
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/directory/moz.build b/components/directory/moz.build
new file mode 100644
index 000000000..ea3cf921e
--- /dev/null
+++ b/components/directory/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['public/nsIHTTPIndex.idl']
+
+SOURCES += [
+ 'src/nsDirectoryViewer.cpp',
+ 'src/nsDirectoryViewerFactory.cpp',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+XPIDL_MODULE = 'directory'
+FINAL_LIBRARY = 'xul'
+
+
diff --git a/components/directory/public/nsIHTTPIndex.idl b/components/directory/public/nsIHTTPIndex.idl
new file mode 100644
index 000000000..47697172b
--- /dev/null
+++ b/components/directory/public/nsIHTTPIndex.idl
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+/*
+
+ The interface to an HTTP index
+
+*/
+
+#include "nsISupports.idl"
+
+interface nsIStreamListener;
+interface nsIRDFDataSource;
+interface nsIRDFNode;
+interface nsIRDFResource;
+
+[scriptable, uuid(6F2BDBD0-58C3-11d3-BE36-00104BDE6048)]
+interface nsIHTTPIndex : nsISupports
+{
+ /**
+ * The base URL of the HTTP index
+ */
+ readonly attribute string BaseURL;
+
+ /**
+ * The RDF datasource that contains the HTTP index information.
+ */
+ readonly attribute nsIRDFDataSource DataSource;
+
+ /**
+ * The charset to use for decoding FTP filenames
+ */
+ attribute string encoding;
+};
+
+%{C++
+
+// {{2587e382-1324-11d4-a652-eadbb2be3484}
+#define NS_HTTPINDEX_SERVICE_CID \
+{ 0x2587e382, 0x1324, 0x11d4, { 0xa6, 0x52, 0xea, 0xdb, 0xb2, 0xbe, 0x34, 0x84 } }
+
+#define NS_HTTPINDEX_SERVICE_CONTRACTID \
+ "@mozilla.org/browser/httpindex-service;1"
+
+#define NS_HTTPINDEX_DATASOURCE_CONTRACTID \
+ "@mozilla.org/rdf/datasource;1?name=httpindex"
+
+%}
diff --git a/components/directory/src/nsDirectoryViewer.cpp b/components/directory/src/nsDirectoryViewer.cpp
new file mode 100644
index 000000000..9d23c5e74
--- /dev/null
+++ b/components/directory/src/nsDirectoryViewer.cpp
@@ -0,0 +1,1393 @@
+/* -*- 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/. */
+
+/*
+
+ A directory viewer object. Parses "application/http-index-format"
+ per Lou Montulli's original spec:
+
+ http://www.mozilla.org/projects/netlib/dirindexformat.html
+
+ One added change is for a description entry, for when the
+ target does not match the filename
+
+*/
+
+#include "nsDirectoryViewer.h"
+#include "nsArray.h"
+#include "nsArrayUtils.h"
+#include "nsIDirIndex.h"
+#include "nsIDocShell.h"
+#include "jsapi.h"
+#include "nsCOMPtr.h"
+#include "nsEnumeratorUtils.h"
+#include "nsEscape.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "rdf.h"
+#include "nsIServiceManager.h"
+#include "nsIXPConnect.h"
+#include "nsEnumeratorUtils.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsITextToSubURI.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIFTPChannel.h"
+#include "nsIWindowWatcher.h"
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsIProgressEventSink.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMElement.h"
+#include "nsIStreamConverterService.h"
+#include "nsICategoryManager.h"
+#include "nsXPCOMCID.h"
+#include "nsIDocument.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsContentUtils.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla;
+
+static const int FORMAT_XUL = 3;
+
+//----------------------------------------------------------------------
+//
+// Common CIDs
+//
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+// Various protocols we have to special case
+static const char kFTPProtocol[] = "ftp://";
+
+//----------------------------------------------------------------------
+//
+// nsHTTPIndex
+//
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsHTTPIndex)
+ NS_INTERFACE_MAP_ENTRY(nsIHTTPIndex)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDirIndexListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIFTPEventSink)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHTTPIndex)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(nsHTTPIndex, mInner)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHTTPIndex)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHTTPIndex)
+
+NS_IMETHODIMP
+nsHTTPIndex::GetInterface(const nsIID &anIID, void **aResult )
+{
+ if (anIID.Equals(NS_GET_IID(nsIFTPEventSink))) {
+ // If we don't have a container to store the logged data
+ // then don't report ourselves back to the caller
+
+ if (!mRequestor)
+ return NS_ERROR_NO_INTERFACE;
+ *aResult = static_cast<nsIFTPEventSink*>(this);
+ NS_ADDREF(this);
+ return NS_OK;
+ }
+
+ if (anIID.Equals(NS_GET_IID(nsIPrompt))) {
+
+ if (!mRequestor)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsCOMPtr<nsPIDOMWindowOuter> aDOMWindow = do_GetInterface(mRequestor);
+ if (!aDOMWindow)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+
+ return wwatch->GetNewPrompter(aDOMWindow, (nsIPrompt**)aResult);
+ }
+
+ if (anIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
+
+ if (!mRequestor)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsCOMPtr<nsPIDOMWindowOuter> aDOMWindow = do_GetInterface(mRequestor);
+ if (!aDOMWindow)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+
+ return wwatch->GetNewAuthPrompter(aDOMWindow, (nsIAuthPrompt**)aResult);
+ }
+
+ if (anIID.Equals(NS_GET_IID(nsIProgressEventSink))) {
+
+ if (!mRequestor)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsCOMPtr<nsIProgressEventSink> sink = do_GetInterface(mRequestor);
+ if (!sink)
+ return NS_ERROR_NO_INTERFACE;
+
+ *aResult = sink;
+ NS_ADDREF((nsISupports*)*aResult);
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::OnFTPControlLog(bool server, const char *msg)
+{
+ NS_ENSURE_TRUE(mRequestor, NS_OK);
+
+ nsCOMPtr<nsIGlobalObject> globalObject = do_GetInterface(mRequestor);
+ NS_ENSURE_TRUE(globalObject, NS_OK);
+
+ // We're going to run script via JS_CallFunctionName, so we need an
+ // AutoEntryScript. This is Gecko specific and not in any spec.
+ dom::AutoEntryScript aes(globalObject,
+ "nsHTTPIndex OnFTPControlLog");
+ JSContext* cx = aes.cx();
+
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ NS_ENSURE_TRUE(global, NS_OK);
+
+ nsString unicodeMsg;
+ unicodeMsg.AssignWithConversion(msg);
+ JSString* jsMsgStr = JS_NewUCStringCopyZ(cx, unicodeMsg.get());
+ NS_ENSURE_TRUE(jsMsgStr, NS_ERROR_OUT_OF_MEMORY);
+
+ JS::AutoValueArray<2> params(cx);
+ params[0].setBoolean(server);
+ params[1].setString(jsMsgStr);
+
+ JS::Rooted<JS::Value> val(cx);
+ JS_CallFunctionName(cx,
+ global,
+ "OnFTPControlLog",
+ params,
+ &val);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::SetEncoding(const char *encoding)
+{
+ mEncoding = encoding;
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetEncoding(char **encoding)
+{
+ NS_PRECONDITION(encoding, "null ptr");
+ if (! encoding)
+ return(NS_ERROR_NULL_POINTER);
+
+ *encoding = ToNewCString(mEncoding);
+ if (!*encoding)
+ return(NS_ERROR_OUT_OF_MEMORY);
+
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::OnStartRequest(nsIRequest *request, nsISupports* aContext)
+{
+ nsresult rv;
+
+ mParser = do_CreateInstance(NS_DIRINDEXPARSER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mParser->SetEncoding(mEncoding.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mParser->SetListener(this);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mParser->OnStartRequest(request,aContext);
+ if (NS_FAILED(rv)) return rv;
+
+ // This should only run once...
+ // Unless we don't have a container to start with
+ // (ie called from bookmarks as an rdf datasource)
+ if (mBindToGlobalObject && mRequestor) {
+ mBindToGlobalObject = false;
+
+ nsCOMPtr<nsIGlobalObject> globalObject = do_GetInterface(mRequestor);
+ NS_ENSURE_TRUE(globalObject, NS_ERROR_FAILURE);
+
+ // We might run script via JS_SetProperty, so we need an AutoEntryScript.
+ // This is Gecko specific and not in any spec.
+ dom::AutoEntryScript aes(globalObject,
+ "nsHTTPIndex set HTTPIndex property");
+ JSContext* cx = aes.cx();
+
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+
+ // Using XPConnect, wrap the HTTP index object...
+ static NS_DEFINE_CID(kXPConnectCID, NS_XPCONNECT_CID);
+ nsCOMPtr<nsIXPConnect> xpc(do_GetService(kXPConnectCID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ JS::Rooted<JSObject*> jsobj(cx);
+ rv = xpc->WrapNative(cx,
+ global,
+ static_cast<nsIHTTPIndex*>(this),
+ NS_GET_IID(nsIHTTPIndex),
+ jsobj.address());
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to xpconnect-wrap http-index");
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(jsobj,
+ "unable to get jsobj from xpconnect wrapper");
+ if (!jsobj) return NS_ERROR_UNEXPECTED;
+
+ JS::Rooted<JS::Value> jslistener(cx, JS::ObjectValue(*jsobj));
+
+ // ...and stuff it into the global context
+ bool ok = JS_SetProperty(cx, global, "HTTPIndex", jslistener);
+ NS_ASSERTION(ok, "unable to set Listener property");
+ if (!ok)
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aContext) {
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
+ NS_ASSERTION(channel, "request should be a channel");
+
+ // lets hijack the notifications:
+ channel->SetNotificationCallbacks(this);
+
+ // now create the top most resource
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+
+ nsAutoCString entryuriC;
+ rv = uri->GetSpec(entryuriC);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFResource> entry;
+ rv = mDirRDF->GetResource(entryuriC, getter_AddRefs(entry));
+
+ NS_ConvertUTF8toUTF16 uriUnicode(entryuriC);
+
+ nsCOMPtr<nsIRDFLiteral> URLVal;
+ rv = mDirRDF->GetLiteral(uriUnicode.get(), getter_AddRefs(URLVal));
+
+ Assert(entry, kNC_URL, URLVal, true);
+ mDirectory = do_QueryInterface(entry);
+ }
+ else
+ {
+ // Get the directory from the context
+ mDirectory = do_QueryInterface(aContext);
+ }
+
+ if (!mDirectory) {
+ request->Cancel(NS_BINDING_ABORTED);
+ return NS_BINDING_ABORTED;
+ }
+
+ // Mark the directory as "loading"
+ rv = Assert(mDirectory, kNC_Loading,
+ kTrueLiteral, true);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsHTTPIndex::OnStopRequest(nsIRequest *request,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ // If mDirectory isn't set, then we should just bail. Either an
+ // error occurred and OnStartRequest() never got called, or
+ // something exploded in OnStartRequest().
+ if (! mDirectory)
+ return NS_BINDING_ABORTED;
+
+ mParser->OnStopRequest(request,aContext,aStatus);
+
+ nsresult rv;
+
+ nsXPIDLCString commentStr;
+ mParser->GetComment(getter_Copies(commentStr));
+
+ nsCOMPtr<nsIRDFLiteral> comment;
+ rv = mDirRDF->GetLiteral(NS_ConvertASCIItoUTF16(commentStr).get(), getter_AddRefs(comment));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = Assert(mDirectory, kNC_Comment, comment, true);
+ if (NS_FAILED(rv)) return rv;
+
+ // hack: Remove the 'loading' annotation (ignore errors)
+ AddElement(mDirectory, kNC_Loading, kTrueLiteral);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsHTTPIndex::OnDataAvailable(nsIRequest *request,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aSourceOffset,
+ uint32_t aCount)
+{
+ // If mDirectory isn't set, then we should just bail. Either an
+ // error occurred and OnStartRequest() never got called, or
+ // something exploded in OnStartRequest().
+ if (! mDirectory)
+ return NS_BINDING_ABORTED;
+
+ return mParser->OnDataAvailable(request, mDirectory, aStream, aSourceOffset, aCount);
+}
+
+
+nsresult
+nsHTTPIndex::OnIndexAvailable(nsIRequest* aRequest, nsISupports *aContext,
+ nsIDirIndex* aIndex)
+{
+ nsCOMPtr<nsIRDFResource> parentRes = do_QueryInterface(aContext);
+ if (!parentRes) {
+ NS_ERROR("Could not obtain parent resource");
+ return(NS_ERROR_UNEXPECTED);
+ }
+
+ const char* baseStr;
+ parentRes->GetValueConst(&baseStr);
+ if (! baseStr) {
+ NS_ERROR("Could not reconstruct base uri");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // we found the filename; construct a resource for its entry
+ nsAutoCString entryuriC(baseStr);
+
+ nsXPIDLCString filename;
+ nsresult rv = aIndex->GetLocation(getter_Copies(filename));
+ if (NS_FAILED(rv)) return rv;
+ entryuriC.Append(filename);
+
+ // if its a directory, make sure it ends with a trailing slash.
+ uint32_t type;
+ rv = aIndex->GetType(&type);
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool isDirType = (type == nsIDirIndex::TYPE_DIRECTORY);
+ if (isDirType && entryuriC.Last() != '/') {
+ entryuriC.Append('/');
+ }
+
+ nsCOMPtr<nsIRDFResource> entry;
+ rv = mDirRDF->GetResource(entryuriC, getter_AddRefs(entry));
+
+ // At this point, we'll (hopefully) have found the filename and
+ // constructed a resource for it, stored in entry. So now take a
+ // second pass through the values and add as statements to the RDF
+ // datasource.
+
+ if (entry && NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIRDFLiteral> lit;
+ nsString str;
+
+ str.AssignWithConversion(entryuriC.get());
+
+ rv = mDirRDF->GetLiteral(str.get(), getter_AddRefs(lit));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = Assert(entry, kNC_URL, lit, true);
+ if (NS_FAILED(rv)) return rv;
+
+ nsXPIDLString xpstr;
+
+ // description
+ rv = aIndex->GetDescription(getter_Copies(xpstr));
+ if (NS_FAILED(rv)) return rv;
+ if (xpstr.Last() == '/')
+ xpstr.Truncate(xpstr.Length() - 1);
+
+ rv = mDirRDF->GetLiteral(xpstr.get(), getter_AddRefs(lit));
+ if (NS_FAILED(rv)) return rv;
+ rv = Assert(entry, kNC_Description, lit, true);
+ if (NS_FAILED(rv)) return rv;
+
+ // contentlength
+ int64_t size;
+ rv = aIndex->GetSize(&size);
+ if (NS_FAILED(rv)) return rv;
+ int64_t minus1 = UINT64_MAX;
+ if (size != minus1) {
+ int32_t intSize = int32_t(size);
+ // XXX RDF should support 64 bit integers (bug 240160)
+ nsCOMPtr<nsIRDFInt> val;
+ rv = mDirRDF->GetIntLiteral(intSize, getter_AddRefs(val));
+ if (NS_FAILED(rv)) return rv;
+ rv = Assert(entry, kNC_ContentLength, val, true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // lastmodified
+ PRTime tm;
+ rv = aIndex->GetLastModified(&tm);
+ if (NS_FAILED(rv)) return rv;
+ if (tm != -1) {
+ nsCOMPtr<nsIRDFDate> val;
+ rv = mDirRDF->GetDateLiteral(tm, getter_AddRefs(val));
+ if (NS_FAILED(rv)) return rv;
+ rv = Assert(entry, kNC_LastModified, val, true);
+ }
+
+ // filetype
+ uint32_t type;
+ rv = aIndex->GetType(&type);
+ switch (type) {
+ case nsIDirIndex::TYPE_UNKNOWN:
+ rv = mDirRDF->GetLiteral(u"UNKNOWN", getter_AddRefs(lit));
+ break;
+ case nsIDirIndex::TYPE_DIRECTORY:
+ rv = mDirRDF->GetLiteral(u"DIRECTORY", getter_AddRefs(lit));
+ break;
+ case nsIDirIndex::TYPE_FILE:
+ rv = mDirRDF->GetLiteral(u"FILE", getter_AddRefs(lit));
+ break;
+ case nsIDirIndex::TYPE_SYMLINK:
+ rv = mDirRDF->GetLiteral(u"SYMLINK", getter_AddRefs(lit));
+ break;
+ }
+
+ if (NS_FAILED(rv)) return rv;
+ rv = Assert(entry, kNC_FileType, lit, true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Since the definition of a directory depends on the protocol, we would have
+ // to do string comparisons all the time.
+ // But we're told if we're a container right here - so save that fact
+ if (isDirType)
+ Assert(entry, kNC_IsContainer, kTrueLiteral, true);
+ else
+ Assert(entry, kNC_IsContainer, kFalseLiteral, true);
+
+// instead of
+// rv = Assert(parentRes, kNC_Child, entry, true);
+// if (NS_FAILED(rv)) return rv;
+// defer insertion onto a timer so that the UI isn't starved
+ AddElement(parentRes, kNC_Child, entry);
+ }
+
+ return rv;
+}
+
+nsresult
+nsHTTPIndex::OnInformationAvailable(nsIRequest *aRequest,
+ nsISupports *aCtxt,
+ const nsAString& aInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//----------------------------------------------------------------------
+//
+// nsHTTPIndex implementation
+//
+
+nsHTTPIndex::nsHTTPIndex()
+ : mBindToGlobalObject(true),
+ mRequestor(nullptr)
+{
+}
+
+
+nsHTTPIndex::nsHTTPIndex(nsIInterfaceRequestor* aRequestor)
+ : mBindToGlobalObject(true),
+ mRequestor(aRequestor)
+{
+}
+
+
+nsHTTPIndex::~nsHTTPIndex()
+{
+ // note: these are NOT statics due to the native of nsHTTPIndex
+ // where it may or may not be treated as a singleton
+
+ if (mTimer)
+ {
+ // be sure to cancel the timer, as it holds a
+ // weak reference back to nsHTTPIndex
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ mConnectionList = nullptr;
+ mNodeList = nullptr;
+
+ if (mDirRDF)
+ {
+ // UnregisterDataSource() may fail; just ignore errors
+ mDirRDF->UnregisterDataSource(this);
+ }
+}
+
+
+
+nsresult
+nsHTTPIndex::CommonInit()
+{
+ nsresult rv = NS_OK;
+
+ // set initial/default encoding to ISO-8859-1 (not UTF-8)
+ mEncoding = "ISO-8859-1";
+
+ mDirRDF = do_GetService(kRDFServiceCID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
+ if (NS_FAILED(rv)) {
+ return(rv);
+ }
+
+ mInner = do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource", &rv);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"),
+ getter_AddRefs(kNC_Child));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "loading"),
+ getter_AddRefs(kNC_Loading));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Comment"),
+ getter_AddRefs(kNC_Comment));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "URL"),
+ getter_AddRefs(kNC_URL));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"),
+ getter_AddRefs(kNC_Description));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Content-Length"),
+ getter_AddRefs(kNC_ContentLength));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(WEB_NAMESPACE_URI "LastModifiedDate"),
+ getter_AddRefs(kNC_LastModified));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Content-Type"),
+ getter_AddRefs(kNC_ContentType));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "File-Type"),
+ getter_AddRefs(kNC_FileType));
+ mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "IsContainer"),
+ getter_AddRefs(kNC_IsContainer));
+
+ rv = mDirRDF->GetLiteral(u"true", getter_AddRefs(kTrueLiteral));
+ if (NS_FAILED(rv)) return(rv);
+ rv = mDirRDF->GetLiteral(u"false", getter_AddRefs(kFalseLiteral));
+ if (NS_FAILED(rv)) return(rv);
+
+ mConnectionList = nsArray::Create();
+
+ // note: don't register DS here
+ return rv;
+}
+
+
+nsresult
+nsHTTPIndex::Init()
+{
+ nsresult rv;
+
+ // set initial/default encoding to ISO-8859-1 (not UTF-8)
+ mEncoding = "ISO-8859-1";
+
+ rv = CommonInit();
+ if (NS_FAILED(rv)) return(rv);
+
+ // (do this last) register this as a named data source with the RDF service
+ rv = mDirRDF->RegisterDataSource(this, false);
+ if (NS_FAILED(rv)) return(rv);
+
+ return(NS_OK);
+}
+
+
+
+nsresult
+nsHTTPIndex::Init(nsIURI* aBaseURL)
+{
+ NS_PRECONDITION(aBaseURL != nullptr, "null ptr");
+ if (! aBaseURL)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ rv = CommonInit();
+ if (NS_FAILED(rv)) return(rv);
+
+ // note: don't register DS here (singleton case)
+
+ rv = aBaseURL->GetSpec(mBaseURL);
+ if (NS_FAILED(rv)) return rv;
+
+ // Mark the base url as a container
+ nsCOMPtr<nsIRDFResource> baseRes;
+ mDirRDF->GetResource(mBaseURL, getter_AddRefs(baseRes));
+ Assert(baseRes, kNC_IsContainer, kTrueLiteral, true);
+
+ return NS_OK;
+}
+
+
+
+nsresult
+nsHTTPIndex::Create(nsIURI* aBaseURL, nsIInterfaceRequestor* aRequestor,
+ nsIHTTPIndex** aResult)
+{
+ *aResult = nullptr;
+
+ nsHTTPIndex* result = new nsHTTPIndex(aRequestor);
+ nsresult rv = result->Init(aBaseURL);
+ if (NS_SUCCEEDED(rv))
+ {
+ NS_ADDREF(result);
+ *aResult = result;
+ }
+ else
+ {
+ delete result;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetBaseURL(char** _result)
+{
+ *_result = ToNewCString(mBaseURL);
+ if (! *_result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetDataSource(nsIRDFDataSource** _result)
+{
+ NS_ADDREF(*_result = this);
+ return NS_OK;
+}
+
+// This function finds the destination when following a given nsIRDFResource
+// If the resource has a URL attribute, we use that. If not, just use
+// the uri.
+//
+// Do NOT try to get the destination of a uri in any other way
+void nsHTTPIndex::GetDestination(nsIRDFResource* r, nsXPIDLCString& dest) {
+ // First try the URL attribute
+ nsCOMPtr<nsIRDFNode> node;
+
+ GetTarget(r, kNC_URL, true, getter_AddRefs(node));
+ nsCOMPtr<nsIRDFLiteral> url;
+
+ if (node)
+ url = do_QueryInterface(node);
+
+ if (!url) {
+ const char* temp;
+ r->GetValueConst(&temp);
+ dest.Adopt(temp ? strdup(temp) : 0);
+ } else {
+ const char16_t* uri;
+ url->GetValueConst(&uri);
+ dest.Adopt(ToNewUTF8String(nsDependentString(uri)));
+ }
+}
+
+// rjc: isWellknownContainerURI() decides whether a URI is a container for which,
+// when asked (say, by the template builder), we'll make a network connection
+// to get its contents. For the moment, all we speak is ftp:// URLs, even though
+// a) we can get "http-index" mimetypes for really anything
+// b) we could easily handle file:// URLs here
+// Q: Why don't we?
+// A: The file system datasource ("rdf:file"); at some point, the two
+// should be perhaps united. Until then, we can't aggregate both
+// "rdf:file" and "http-index" (such as with bookmarks) because we'd
+// get double the # of answers we really want... also, "rdf:file" is
+// less expensive in terms of both memory usage as well as speed
+
+
+
+// We use an rdf attribute to mark if this is a container or not.
+// Note that we still have to do string comparisons as a fallback
+// because stuff like the personal toolbar and bookmarks check whether
+// a URL is a container, and we have no attribute in that case.
+bool
+nsHTTPIndex::isWellknownContainerURI(nsIRDFResource *r)
+{
+ nsCOMPtr<nsIRDFNode> node;
+ GetTarget(r, kNC_IsContainer, true, getter_AddRefs(node));
+ if (node) {
+ bool isContainerFlag;
+ if (NS_SUCCEEDED(node->EqualsNode(kTrueLiteral, &isContainerFlag)))
+ return isContainerFlag;
+ }
+
+ nsXPIDLCString uri;
+ GetDestination(r, uri);
+ return uri.get() && !strncmp(uri, kFTPProtocol, sizeof(kFTPProtocol) - 1) &&
+ (uri.Last() == '/');
+}
+
+
+NS_IMETHODIMP
+nsHTTPIndex::GetURI(char * *uri)
+{
+ NS_PRECONDITION(uri != nullptr, "null ptr");
+ if (! uri)
+ return(NS_ERROR_NULL_POINTER);
+
+ if ((*uri = strdup("rdf:httpindex")) == nullptr)
+ return(NS_ERROR_OUT_OF_MEMORY);
+
+ return(NS_OK);
+}
+
+
+
+NS_IMETHODIMP
+nsHTTPIndex::GetSource(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue,
+ nsIRDFResource **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ *_retval = nullptr;
+
+ if (mInner)
+ {
+ rv = mInner->GetSource(aProperty, aTarget, aTruthValue, _retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue,
+ nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ if (mInner)
+ {
+ rv = mInner->GetSources(aProperty, aTarget, aTruthValue, _retval);
+ }
+ else
+ {
+ rv = NS_NewEmptyEnumerator(_retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetTarget(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue,
+ nsIRDFNode **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ *_retval = nullptr;
+
+ if ((aTruthValue) && (aProperty == kNC_Child) && isWellknownContainerURI(aSource))
+ {
+ // fake out the generic builder (i.e. return anything in this case)
+ // so that search containers never appear to be empty
+ NS_IF_ADDREF(aSource);
+ *_retval = aSource;
+ return(NS_OK);
+ }
+
+ if (mInner)
+ {
+ rv = mInner->GetTarget(aSource, aProperty, aTruthValue, _retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetTargets(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue,
+ nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ if (mInner)
+ {
+ rv = mInner->GetTargets(aSource, aProperty, aTruthValue, _retval);
+ }
+ else
+ {
+ rv = NS_NewEmptyEnumerator(_retval);
+ }
+
+ if ((aProperty == kNC_Child) && isWellknownContainerURI(aSource))
+ {
+ bool doNetworkRequest = true;
+ if (NS_SUCCEEDED(rv) && (_retval))
+ {
+ // check and see if we already have data for the search in question;
+ // if we do, don't bother doing the search again
+ bool hasResults;
+ if (NS_SUCCEEDED((*_retval)->HasMoreElements(&hasResults)) &&
+ hasResults)
+ doNetworkRequest = false;
+ }
+
+ // Note: if we need to do a network request, do it out-of-band
+ // (because the XUL template builder isn't re-entrant)
+ // by using a global connection list and an immediately-firing timer
+ if (doNetworkRequest && mConnectionList)
+ {
+ uint32_t connectionIndex;
+ nsresult idx_rv = mConnectionList->IndexOf(0, aSource, &connectionIndex);
+ if (NS_FAILED(idx_rv))
+ {
+ // add aSource into list of connections to make
+ mConnectionList->AppendElement(aSource, /*weak =*/ false);
+
+ // if we don't have a timer about to fire, create one
+ // which should fire as soon as possible (out-of-band)
+ if (!mTimer)
+ {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create a timer");
+ if (NS_SUCCEEDED(rv))
+ {
+ mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, this, 1,
+ nsITimer::TYPE_ONE_SHOT);
+ // Note: don't addref "this" as we'll cancel the
+ // timer in the httpIndex destructor
+ }
+ }
+ }
+ }
+ }
+
+ return(rv);
+}
+
+
+nsresult
+nsHTTPIndex::AddElement(nsIRDFResource *parent, nsIRDFResource *prop, nsIRDFNode *child)
+{
+ nsresult rv;
+
+ if (!mNodeList)
+ {
+ mNodeList = nsArray::Create();
+ }
+
+ // order required: parent, prop, then child
+ mNodeList->AppendElement(parent, /*weak =*/ false);
+ mNodeList->AppendElement(prop, /*weak =*/ false);
+ mNodeList->AppendElement(child, /*weak = */ false);
+
+ if (!mTimer)
+ {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create a timer");
+ if (NS_FAILED(rv)) return(rv);
+
+ mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, this, 1,
+ nsITimer::TYPE_ONE_SHOT);
+ // Note: don't addref "this" as we'll cancel the
+ // timer in the httpIndex destructor
+ }
+
+ return(NS_OK);
+}
+
+void
+nsHTTPIndex::FireTimer(nsITimer* aTimer, void* aClosure)
+{
+ nsHTTPIndex *httpIndex = static_cast<nsHTTPIndex *>(aClosure);
+ if (!httpIndex)
+ return;
+
+ // don't return out of this loop as mTimer may need to be cancelled afterwards
+ uint32_t numItems = 0;
+ if (httpIndex->mConnectionList)
+ {
+ httpIndex->mConnectionList->GetLength(&numItems);
+ if (numItems > 0)
+ {
+ nsCOMPtr<nsIRDFResource> source =
+ do_QueryElementAt(httpIndex->mConnectionList, 0);
+ httpIndex->mConnectionList->RemoveElementAt(0);
+
+ nsXPIDLCString uri;
+ if (source) {
+ httpIndex->GetDestination(source, uri);
+ }
+
+ if (!uri) {
+ NS_ERROR("Could not reconstruct uri");
+ return;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> url;
+
+ rv = NS_NewURI(getter_AddRefs(url), uri.get());
+ nsCOMPtr<nsIChannel> channel;
+ if (NS_SUCCEEDED(rv) && (url)) {
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ url,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ }
+ if (NS_SUCCEEDED(rv) && (channel)) {
+ channel->SetNotificationCallbacks(httpIndex);
+ rv = channel->AsyncOpen2(httpIndex);
+ }
+ }
+ }
+
+ if (httpIndex->mNodeList)
+ {
+ httpIndex->mNodeList->GetLength(&numItems);
+ if (numItems > 0)
+ {
+ // account for order required: src, prop, then target
+ numItems /=3;
+ if (numItems > 10)
+ numItems = 10;
+
+ int32_t loop;
+ for (loop=0; loop<(int32_t)numItems; loop++)
+ {
+ nsCOMPtr<nsIRDFResource> src = do_QueryElementAt(httpIndex->mNodeList, 0);
+ httpIndex->mNodeList->RemoveElementAt(0);
+
+ nsCOMPtr<nsIRDFResource> prop = do_QueryElementAt(httpIndex->mNodeList, 0);
+ httpIndex->mNodeList->RemoveElementAt(0);
+
+ nsCOMPtr<nsIRDFNode> target = do_QueryElementAt(httpIndex->mNodeList, 0);
+ httpIndex->mNodeList->RemoveElementAt(0);
+
+ if (src && prop && target)
+ {
+ if (prop.get() == httpIndex->kNC_Loading)
+ {
+ httpIndex->Unassert(src, prop, target);
+ }
+ else
+ {
+ httpIndex->Assert(src, prop, target, true);
+ }
+ }
+ }
+ }
+ }
+
+ bool refireTimer = false;
+ // check both lists to see if the timer needs to continue firing
+ if (httpIndex->mConnectionList)
+ {
+ httpIndex->mConnectionList->GetLength(&numItems);
+ if (numItems > 0)
+ {
+ refireTimer = true;
+ }
+ else
+ {
+ httpIndex->mConnectionList->Clear();
+ }
+ }
+
+ if (httpIndex->mNodeList)
+ {
+ httpIndex->mNodeList->GetLength(&numItems);
+ if (numItems > 0)
+ {
+ refireTimer = true;
+ }
+ else
+ {
+ httpIndex->mNodeList->Clear();
+ }
+ }
+
+ // be sure to cancel the timer, as it holds a
+ // weak reference back to nsHTTPIndex
+ httpIndex->mTimer->Cancel();
+ httpIndex->mTimer = nullptr;
+
+ // after firing off any/all of the connections be sure
+ // to cancel the timer if we don't need to refire it
+ if (refireTimer)
+ {
+ httpIndex->mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (httpIndex->mTimer)
+ {
+ httpIndex->mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, aClosure, 10,
+ nsITimer::TYPE_ONE_SHOT);
+ // Note: don't addref "this" as we'll cancel the
+ // timer in the httpIndex destructor
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::Assert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget,
+ bool aTruthValue)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::Unassert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->Unassert(aSource, aProperty, aTarget);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::Change(nsIRDFResource *aSource, nsIRDFResource *aProperty,
+ nsIRDFNode *aOldTarget, nsIRDFNode *aNewTarget)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->Change(aSource, aProperty, aOldTarget, aNewTarget);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::Move(nsIRDFResource *aOldSource, nsIRDFResource *aNewSource,
+ nsIRDFResource *aProperty, nsIRDFNode *aTarget)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->Move(aOldSource, aNewSource, aProperty, aTarget);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty,
+ nsIRDFNode *aTarget, bool aTruthValue, bool *_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->HasAssertion(aSource, aProperty, aTarget, aTruthValue, _retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::AddObserver(nsIRDFObserver *aObserver)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->AddObserver(aObserver);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::RemoveObserver(nsIRDFObserver *aObserver)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->RemoveObserver(aObserver);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result)
+{
+ if (!mInner) {
+ *result = false;
+ return NS_OK;
+ }
+ return mInner->HasArcIn(aNode, aArc, result);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result)
+{
+ if (aArc == kNC_Child && isWellknownContainerURI(aSource)) {
+ *result = true;
+ return NS_OK;
+ }
+
+ if (mInner) {
+ return mInner->HasArcOut(aSource, aArc, result);
+ }
+
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::ArcLabelsIn(nsIRDFNode *aNode, nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->ArcLabelsIn(aNode, _retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::ArcLabelsOut(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
+{
+ *_retval = nullptr;
+
+ nsCOMPtr<nsISimpleEnumerator> child, anonArcs;
+ if (isWellknownContainerURI(aSource))
+ {
+ NS_NewSingletonEnumerator(getter_AddRefs(child), kNC_Child);
+ }
+
+ if (mInner)
+ {
+ mInner->ArcLabelsOut(aSource, getter_AddRefs(anonArcs));
+ }
+
+ return NS_NewUnionEnumerator(_retval, child, anonArcs);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetAllResources(nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->GetAllResources(_retval);
+ }
+ return(rv);
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::IsCommandEnabled(nsISupports *aSources, nsIRDFResource *aCommand,
+ nsISupports *aArguments, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::DoCommand(nsISupports *aSources, nsIRDFResource *aCommand,
+ nsISupports *aArguments)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::BeginUpdateBatch()
+{
+ return mInner->BeginUpdateBatch();
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::EndUpdateBatch()
+{
+ return mInner->EndUpdateBatch();
+}
+
+NS_IMETHODIMP
+nsHTTPIndex::GetAllCmds(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
+{
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mInner)
+ {
+ rv = mInner->GetAllCmds(aSource, _retval);
+ }
+ return(rv);
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsDirectoryViewerFactory
+//
+nsDirectoryViewerFactory::nsDirectoryViewerFactory()
+{
+}
+
+
+
+nsDirectoryViewerFactory::~nsDirectoryViewerFactory()
+{
+}
+
+
+NS_IMPL_ISUPPORTS(nsDirectoryViewerFactory, nsIDocumentLoaderFactory)
+
+
+
+NS_IMETHODIMP
+nsDirectoryViewerFactory::CreateInstance(const char *aCommand,
+ nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ const nsACString& aContentType,
+ nsIDocShell* aContainer,
+ nsISupports* aExtraInfo,
+ nsIStreamListener** aDocListenerResult,
+ nsIContentViewer** aDocViewerResult)
+{
+ nsresult rv;
+
+ bool viewSource = FindInReadable(NS_LITERAL_CSTRING("view-source"),
+ aContentType);
+
+ if (!viewSource &&
+ Preferences::GetInt("network.dir.format", FORMAT_XUL) == FORMAT_XUL) {
+ // ... and setup the original channel's content type
+ (void)aChannel->SetContentType(NS_LITERAL_CSTRING("application/vnd.mozilla.xul+xml"));
+
+ // This is where we shunt the HTTP/Index stream into our datasource,
+ // and open the directory viewer XUL file as the content stream to
+ // load in its place.
+
+ // Create a dummy loader that will load a stub XUL document.
+ nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ nsXPIDLCString contractID;
+ rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "application/vnd.mozilla.xul+xml",
+ getter_Copies(contractID));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIDocumentLoaderFactory> factory(do_GetService(contractID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), "chrome://communicator/content/directory/directory.xul");
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ aLoadGroup);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = factory->CreateInstance(aCommand, channel, aLoadGroup,
+ NS_LITERAL_CSTRING("application/vnd.mozilla.xul+xml"),
+ aContainer, aExtraInfo, getter_AddRefs(listener),
+ aDocViewerResult);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = channel->AsyncOpen2(listener);
+ if (NS_FAILED(rv)) return rv;
+
+ // Create an HTTPIndex object so that we can stuff it into the script context
+ nsCOMPtr<nsIURI> baseuri;
+ rv = aChannel->GetURI(getter_AddRefs(baseuri));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInterfaceRequestor> requestor = do_QueryInterface(aContainer,&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIHTTPIndex> httpindex;
+ rv = nsHTTPIndex::Create(baseuri, requestor, getter_AddRefs(httpindex));
+ if (NS_FAILED(rv)) return rv;
+
+ // Now shanghai the stream into our http-index parsing datasource
+ // wrapper beastie.
+ listener = do_QueryInterface(httpindex,&rv);
+ *aDocListenerResult = listener.get();
+ NS_ADDREF(*aDocListenerResult);
+
+ return NS_OK;
+ }
+
+ // setup the original channel's content type
+ (void)aChannel->SetContentType(NS_LITERAL_CSTRING("text/html"));
+
+ // Otherwise, lets use the html listing
+ nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ nsXPIDLCString contractID;
+ rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "text/html",
+ getter_Copies(contractID));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIDocumentLoaderFactory> factory(do_GetService(contractID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamListener> listener;
+
+ if (viewSource) {
+ rv = factory->CreateInstance("view-source", aChannel, aLoadGroup,
+ NS_LITERAL_CSTRING("text/html; x-view-type=view-source"),
+ aContainer, aExtraInfo, getter_AddRefs(listener),
+ aDocViewerResult);
+ } else {
+ rv = factory->CreateInstance("view", aChannel, aLoadGroup,
+ NS_LITERAL_CSTRING("text/html"),
+ aContainer, aExtraInfo, getter_AddRefs(listener),
+ aDocViewerResult);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamConverterService> scs = do_GetService("@mozilla.org/streamConverters;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = scs->AsyncConvertData("application/http-index-format",
+ "text/html",
+ listener,
+ nullptr,
+ aDocListenerResult);
+
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+nsDirectoryViewerFactory::CreateInstanceForDocument(nsISupports* aContainer,
+ nsIDocument* aDocument,
+ const char *aCommand,
+ nsIContentViewer** aDocViewerResult)
+{
+ NS_NOTYETIMPLEMENTED("didn't expect to get here");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDirectoryViewerFactory::CreateBlankDocument(nsILoadGroup *aLoadGroup,
+ nsIPrincipal *aPrincipal,
+ nsIDocument **_retval) {
+
+ NS_NOTYETIMPLEMENTED("didn't expect to get here");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/components/directory/src/nsDirectoryViewer.h b/components/directory/src/nsDirectoryViewer.h
new file mode 100644
index 000000000..05b68f1b6
--- /dev/null
+++ b/components/directory/src/nsDirectoryViewer.h
@@ -0,0 +1,126 @@
+/* -*- 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/. */
+
+#ifndef nsdirectoryviewer__h____
+#define nsdirectoryviewer__h____
+
+#include "nsCOMPtr.h"
+#include "nsIStreamListener.h"
+#include "nsIContentViewer.h"
+#include "nsIHTTPIndex.h"
+#include "nsIRDFService.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFLiteral.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsITimer.h"
+#include "nsXPIDLString.h"
+#include "nsIDirIndexListener.h"
+#include "nsIFTPChannel.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIURI.h"
+
+class nsIMutableArray;
+
+class nsDirectoryViewerFactory : public nsIDocumentLoaderFactory
+{
+public:
+ nsDirectoryViewerFactory();
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOCUMENTLOADERFACTORY
+
+protected:
+ virtual ~nsDirectoryViewerFactory();
+};
+
+class nsHTTPIndex final : public nsIHTTPIndex,
+ public nsIRDFDataSource,
+ public nsIStreamListener,
+ public nsIDirIndexListener,
+ public nsIInterfaceRequestor,
+ public nsIFTPEventSink
+{
+private:
+
+ // note: these are NOT statics due to the native of nsHTTPIndex
+ // where it may or may not be treated as a singleton
+
+ nsCOMPtr<nsIRDFResource> kNC_Child;
+ nsCOMPtr<nsIRDFResource> kNC_Comment;
+ nsCOMPtr<nsIRDFResource> kNC_Loading;
+ nsCOMPtr<nsIRDFResource> kNC_URL;
+ nsCOMPtr<nsIRDFResource> kNC_Description;
+ nsCOMPtr<nsIRDFResource> kNC_ContentLength;
+ nsCOMPtr<nsIRDFResource> kNC_LastModified;
+ nsCOMPtr<nsIRDFResource> kNC_ContentType;
+ nsCOMPtr<nsIRDFResource> kNC_FileType;
+ nsCOMPtr<nsIRDFResource> kNC_IsContainer;
+ nsCOMPtr<nsIRDFLiteral> kTrueLiteral;
+ nsCOMPtr<nsIRDFLiteral> kFalseLiteral;
+
+ nsCOMPtr<nsIRDFService> mDirRDF;
+
+protected:
+ // We grab a reference to the content viewer container (which
+ // indirectly owns us) so that we can insert ourselves as a global
+ // in the script context _after_ the XUL doc has been embedded into
+ // content viewer. We'll know that this has happened once we receive
+ // an OnStartRequest() notification
+
+ nsCOMPtr<nsIRDFDataSource> mInner;
+ nsCOMPtr<nsIMutableArray> mConnectionList;
+ nsCOMPtr<nsIMutableArray> mNodeList;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIDirIndexParser> mParser;
+ nsCString mBaseURL;
+ nsCString mEncoding;
+ bool mBindToGlobalObject;
+ nsIInterfaceRequestor* mRequestor; // WEAK
+ nsCOMPtr<nsIRDFResource> mDirectory;
+
+ explicit nsHTTPIndex(nsIInterfaceRequestor* aRequestor);
+ nsresult CommonInit(void);
+ nsresult Init(nsIURI* aBaseURL);
+ void GetDestination(nsIRDFResource* r, nsXPIDLCString& dest);
+ bool isWellknownContainerURI(nsIRDFResource *r);
+ nsresult AddElement(nsIRDFResource *parent, nsIRDFResource *prop,
+ nsIRDFNode *child);
+
+ static void FireTimer(nsITimer* aTimer, void* aClosure);
+
+ virtual ~nsHTTPIndex();
+
+public:
+ nsHTTPIndex();
+ nsresult Init(void);
+
+ static nsresult Create(nsIURI* aBaseURI, nsIInterfaceRequestor* aContainer,
+ nsIHTTPIndex** aResult);
+
+ // nsIHTTPIndex interface
+ NS_DECL_NSIHTTPINDEX
+
+ // NSIRDFDataSource interface
+ NS_DECL_NSIRDFDATASOURCE
+
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ NS_DECL_NSIDIRINDEXLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIFTPEVENTSINK
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHTTPIndex, nsIHTTPIndex)
+};
+
+// {82776710-5690-11d3-BE36-00104BDE6048}
+#define NS_DIRECTORYVIEWERFACTORY_CID \
+{ 0x82776710, 0x5690, 0x11d3, { 0xbe, 0x36, 0x0, 0x10, 0x4b, 0xde, 0x60, 0x48 } }
+
+#endif // nsdirectoryviewer__h____
diff --git a/components/directory/src/nsDirectoryViewerFactory.cpp b/components/directory/src/nsDirectoryViewerFactory.cpp
new file mode 100644
index 000000000..e7f4d2eae
--- /dev/null
+++ b/components/directory/src/nsDirectoryViewerFactory.cpp
@@ -0,0 +1,44 @@
+/* -*- 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 "nsDirectoryViewer.h"
+#include "rdf.h"
+#include "nsRDFCID.h"
+#include "nsCURILoader.h"
+
+// Factory constructors
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHTTPIndex, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDirectoryViewerFactory)
+
+NS_DEFINE_NAMED_CID(NS_DIRECTORYVIEWERFACTORY_CID);
+NS_DEFINE_NAMED_CID(NS_HTTPINDEX_SERVICE_CID);
+
+static const mozilla::Module::CIDEntry kDirectoryViewerCIDs[] = {
+ { &kNS_DIRECTORYVIEWERFACTORY_CID, false, nullptr, nsDirectoryViewerFactoryConstructor },
+ { &kNS_HTTPINDEX_SERVICE_CID, false, nullptr, nsHTTPIndexConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kDirectoryViewerContracts[] = {
+ { "@mozilla.org/xpfe/http-index-format-factory-constructor", &kNS_DIRECTORYVIEWERFACTORY_CID },
+ { NS_HTTPINDEX_SERVICE_CONTRACTID, &kNS_HTTPINDEX_SERVICE_CID },
+ { NS_HTTPINDEX_DATASOURCE_CONTRACTID, &kNS_HTTPINDEX_SERVICE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kDirectoryViewerCategories[] = {
+ { "Gecko-Content-Viewers", "application/http-index-format", "@mozilla.org/xpfe/http-index-format-factory-constructor" },
+ { nullptr }
+};
+
+static const mozilla::Module kDirectoryViewerModule = {
+ mozilla::Module::kVersion,
+ kDirectoryViewerCIDs,
+ kDirectoryViewerContracts,
+ kDirectoryViewerCategories
+};
+
+NSMODULE_DEFN(application) = &kDirectoryViewerModule;
diff --git a/components/downloads/content/DownloadProgressListener.js b/components/downloads/content/DownloadProgressListener.js
new file mode 100644
index 000000000..ab349baf2
--- /dev/null
+++ b/components/downloads/content/DownloadProgressListener.js
@@ -0,0 +1,117 @@
+// -*- 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/. */
+
+/**
+ * DownloadProgressListener "class" is used to help update download items shown
+ * in the Download Manager UI such as displaying amount transferred, transfer
+ * rate, and time left for each download.
+ *
+ * This class implements the nsIDownloadProgressListener interface.
+ */
+function DownloadProgressListener() {}
+
+DownloadProgressListener.prototype = {
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadProgressListener]),
+
+ // nsIDownloadProgressListener
+
+ onDownloadStateChange: function dlPL_onDownloadStateChange(aState, aDownload)
+ {
+ // Update window title in-case we don't get all progress notifications
+ onUpdateProgress();
+
+ let dl;
+ let state = aDownload.state;
+ switch (state) {
+ case nsIDM.DOWNLOAD_QUEUED:
+ prependList(aDownload);
+ break;
+
+ case nsIDM.DOWNLOAD_BLOCKED_POLICY:
+ prependList(aDownload);
+ // Should fall through, this is a final state but DOWNLOAD_QUEUED
+ // is skipped. See nsDownloadManager::AddDownload.
+ case nsIDM.DOWNLOAD_FAILED:
+ case nsIDM.DOWNLOAD_CANCELED:
+ case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
+ case nsIDM.DOWNLOAD_DIRTY:
+ case nsIDM.DOWNLOAD_FINISHED:
+ downloadCompleted(aDownload);
+ if (state == nsIDM.DOWNLOAD_FINISHED)
+ autoRemoveAndClose(aDownload);
+ break;
+ case nsIDM.DOWNLOAD_DOWNLOADING: {
+ dl = getDownload(aDownload.id);
+
+ // At this point, we know if we are an indeterminate download or not
+ dl.setAttribute("progressmode", aDownload.percentComplete == -1 ?
+ "undetermined" : "normal");
+
+ // As well as knowing the referrer
+ let referrer = aDownload.referrer;
+ if (referrer)
+ dl.setAttribute("referrer", referrer.spec);
+
+ break;
+ }
+ }
+
+ // autoRemoveAndClose could have already closed our window...
+ try {
+ if (!dl)
+ dl = getDownload(aDownload.id);
+
+ // Update to the new state
+ dl.setAttribute("state", state);
+
+ // Update ui text values after switching states
+ updateTime(dl);
+ updateStatus(dl);
+ updateButtons(dl);
+ } catch (e) { }
+ },
+
+ onProgressChange: function dlPL_onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress, aDownload)
+ {
+ var download = getDownload(aDownload.id);
+
+ // Update this download's progressmeter
+ if (aDownload.percentComplete != -1) {
+ download.setAttribute("progress", aDownload.percentComplete);
+
+ // Dispatch ValueChange for a11y
+ let event = document.createEvent("Events");
+ event.initEvent("ValueChange", true, true);
+ document.getAnonymousElementByAttribute(download, "anonid", "progressmeter")
+ .dispatchEvent(event);
+ }
+
+ // Update the progress so the status can be correctly updated
+ download.setAttribute("currBytes", aDownload.amountTransferred);
+ download.setAttribute("maxBytes", aDownload.size);
+
+ // Update the rest of the UI (bytes transferred, bytes total, download rate,
+ // time remaining).
+ updateStatus(download, aDownload);
+
+ // Update window title
+ onUpdateProgress();
+ },
+
+ onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload)
+ {
+ },
+
+ onSecurityChange: function(aWebProgress, aRequest, aState, aDownload)
+ {
+ }
+};
diff --git a/components/downloads/content/download.xml b/components/downloads/content/download.xml
new file mode 100644
index 000000000..1d4b87270
--- /dev/null
+++ b/components/downloads/content/download.xml
@@ -0,0 +1,327 @@
+<?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 % downloadDTD SYSTEM "chrome://mozapps/locale/downloads/downloads.dtd" >
+ %downloadDTD;
+]>
+
+<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-base" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <resources>
+ <stylesheet src="chrome://mozapps/skin/downloads/downloads.css"/>
+ </resources>
+ <implementation>
+ <property name="paused">
+ <getter>
+ <![CDATA[
+ return parseInt(this.getAttribute("state")) == Components.interfaces.nsIDownloadManager.DOWNLOAD_PAUSED;
+ ]]>
+ </getter>
+ </property>
+ <property name="openable">
+ <getter>
+ <![CDATA[
+ return parseInt(this.getAttribute("state")) == Components.interfaces.nsIDownloadManager.DOWNLOAD_FINISHED;
+ ]]>
+ </getter>
+ </property>
+ <property name="inProgress">
+ <getter>
+ <![CDATA[
+ var state = parseInt(this.getAttribute("state"));
+ const dl = Components.interfaces.nsIDownloadManager;
+ return state == dl.DOWNLOAD_NOTSTARTED ||
+ state == dl.DOWNLOAD_QUEUED ||
+ state == dl.DOWNLOAD_DOWNLOADING ||
+ state == dl.DOWNLOAD_PAUSED ||
+ state == dl.DOWNLOAD_SCANNING;
+ ]]>
+ </getter>
+ </property>
+ <property name="removable">
+ <getter>
+ <![CDATA[
+ var state = parseInt(this.getAttribute("state"));
+ const dl = Components.interfaces.nsIDownloadManager;
+ return state == dl.DOWNLOAD_FINISHED ||
+ state == dl.DOWNLOAD_CANCELED ||
+ state == dl.DOWNLOAD_BLOCKED_PARENTAL ||
+ state == dl.DOWNLOAD_BLOCKED_POLICY ||
+ state == dl.DOWNLOAD_DIRTY ||
+ state == dl.DOWNLOAD_FAILED;
+ ]]>
+ </getter>
+ </property>
+ <property name="buttons">
+ <getter>
+ <![CDATA[
+ var startEl = document.getAnonymousNodes(this);
+ if (!startEl.length)
+ startEl = [this];
+
+ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ return startEl[0].getElementsByTagNameNS(XULNS, "button");
+ ]]>
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="download-starting" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" class="name"/>
+ <xul:progressmeter mode="normal" value="0" flex="1"
+ anonid="progressmeter"/>
+ <xul:label value="&starting.label;" class="status"/>
+ <xul:spacer flex="1"/>
+ </xul:vbox>
+ <xul:vbox pack="center">
+ <xul:button class="cancel mini-button" tooltiptext="&cmd.cancel.label;"
+ cmd="cmd_cancel" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_cancel', this);"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-downloading" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1" class="downloadContentBox">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="2" class="name"/>
+ <xul:hbox>
+ <xul:vbox flex="1">
+ <xul:progressmeter mode="normal" value="0" flex="1"
+ anonid="progressmeter"
+ xbl:inherits="value=progress,mode=progressmode"/>
+ </xul:vbox>
+ <xul:button class="pause mini-button" tooltiptext="&cmd.pause.label;"
+ cmd="cmd_pause" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_pause', this);"/>
+ <xul:button class="cancel mini-button" tooltiptext="&cmd.cancel.label;"
+ cmd="cmd_cancel" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_cancel', this);"/>
+ </xul:hbox>
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip" flex="1"
+ crop="right" class="status"/>
+ <xul:spacer flex="1"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-paused" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="2" class="name"/>
+ <xul:hbox>
+ <xul:vbox flex="1">
+ <xul:progressmeter mode="normal" value="0" flex="1"
+ anonid="progressmeter"
+ xbl:inherits="value=progress,mode=progressmode"/>
+ </xul:vbox>
+ <xul:button class="resume mini-button" tooltiptext="&cmd.resume.label;"
+ cmd="cmd_resume" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_resume', this);"/>
+ <xul:button class="cancel mini-button" tooltiptext="&cmd.cancel.label;"
+ cmd="cmd_cancel" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_cancel', this);"/>
+ </xul:hbox>
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip" flex="1"
+ crop="right" class="status"/>
+ <xul:spacer flex="1"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-done" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-canceled" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ <xul:button class="retry mini-button" tooltiptext="&cmd.retry.label;"
+ cmd="cmd_retry" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_retry', this);"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-failed" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ <xul:button class="retry mini-button" tooltiptext="&cmd.retry.label;"
+ cmd="cmd_retry" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_retry', this);"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-blocked-parental" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-blocked-policy" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-scanning" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="2" class="name"/>
+ <xul:hbox>
+ <xul:vbox flex="1">
+ <xul:progressmeter mode="undetermined" flex="1" />
+ </xul:vbox>
+ </xul:hbox>
+ <xul:label value="&scanning.label;" class="status"/>
+ <xul:spacer flex="1"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-dirty" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/components/downloads/content/downloads.css b/components/downloads/content/downloads.css
new file mode 100644
index 000000000..dcb648d62
--- /dev/null
+++ b/components/downloads/content/downloads.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/. */
+
+richlistitem[type="download"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-starting');
+ -moz-box-orient: vertical;
+}
+
+richlistitem[type="download"][state="0"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-downloading');
+}
+
+richlistitem[type="download"][state="1"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-done');
+}
+
+richlistitem[type="download"][state="2"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-failed');
+}
+
+richlistitem[type="download"][state="3"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-canceled');
+}
+
+richlistitem[type="download"][state="4"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-paused');
+}
+
+richlistitem[type="download"][state="6"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-blocked-parental');
+}
+
+richlistitem[type="download"][state="7"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-scanning');
+}
+
+richlistitem[type="download"][state="8"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-dirty');
+}
+
+richlistitem[type="download"][state="9"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-blocked-policy');
+}
+
+/* Only focus buttons in the selected item*/
+richlistitem[type="download"]:not([selected="true"]) button {
+ -moz-user-focus: none;
+}
+
diff --git a/components/downloads/content/downloads.js b/components/downloads/content/downloads.js
new file mode 100644
index 000000000..299717336
--- /dev/null
+++ b/components/downloads/content/downloads.js
@@ -0,0 +1,1314 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+// Globals
+
+const PREF_BDM_CLOSEWHENDONE = "browser.download.manager.closeWhenDone";
+const PREF_BDM_CONFIRMOPENEXE = "browser.download.confirmOpenExecutable";
+const PREF_BDM_SCANWHENDONE = "browser.download.manager.scanWhenDone";
+
+const nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
+ "nsILocalFile", "initWithPath");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+const nsIDM = Ci.nsIDownloadManager;
+
+var gDownloadManager = Cc["@mozilla.org/download-manager;1"].getService(nsIDM);
+var gDownloadManagerUI = Cc["@mozilla.org/download-manager-ui;1"].
+ getService(Ci.nsIDownloadManagerUI);
+
+var gDownloadListener = null;
+var gDownloadsView = null;
+var gSearchBox = null;
+var gSearchTerms = [];
+var gBuilder = 0;
+
+// This variable is used when performing commands on download items and gives
+// the command the ability to do something after all items have been operated
+// on. The following convention is used to handle the value of the variable:
+// whenever we aren't performing a command, the value is |undefined|; just
+// before executing commands, the value will be set to |null|; and when
+// commands want to create a callback, they set the value to be a callback
+// function to be executed after all download items have been visited.
+var gPerformAllCallback;
+
+// Control the performance of the incremental list building by setting how many
+// milliseconds to wait before building more of the list and how many items to
+// add between each delay.
+const gListBuildDelay = 300;
+const gListBuildChunk = 3;
+
+// Array of download richlistitem attributes to check when searching
+const gSearchAttributes = [
+ "target",
+ "status",
+ "dateTime",
+];
+
+// If the user has interacted with the window in a significant way, we should
+// not auto-close the window. Tough UI decisions about what is "significant."
+var gUserInteracted = false;
+
+// These strings will be converted to the corresponding ones from the string
+// bundle on startup.
+var gStr = {
+ paused: "paused",
+ cannotPause: "cannotPause",
+ doneStatus: "doneStatus",
+ doneSize: "doneSize",
+ doneSizeUnknown: "doneSizeUnknown",
+ stateFailed: "stateFailed",
+ stateCanceled: "stateCanceled",
+ stateBlockedParentalControls: "stateBlocked",
+ stateBlockedPolicy: "stateBlockedPolicy",
+ stateDirty: "stateDirty",
+ downloadsTitleFiles: "downloadsTitleFiles",
+ downloadsTitlePercent: "downloadsTitlePercent",
+ fileExecutableSecurityWarningTitle: "fileExecutableSecurityWarningTitle",
+};
+
+// The statement to query for downloads that are active or match the search
+var gStmt = null;
+
+// Start/Stop Observers
+
+function downloadCompleted(aDownload)
+{
+ // The download is changing state, so update the clear list button
+ updateClearListButton();
+
+ // Wrap this in try...catch since this can be called while shutting down...
+ // it doesn't really matter if it fails then since well.. we're shutting down
+ // and there's no UI to update!
+ try {
+ let dl = getDownload(aDownload.id);
+
+ // Update attributes now that we've finished
+ dl.setAttribute("startTime", Math.round(aDownload.startTime / 1000));
+ dl.setAttribute("endTime", Date.now());
+ dl.setAttribute("currBytes", aDownload.amountTransferred);
+ dl.setAttribute("maxBytes", aDownload.size);
+
+ // Move the download below active if it should stay in the list
+ if (downloadMatchesSearch(dl)) {
+ // Iterate down until we find a non-active download
+ let next = dl.nextSibling;
+ while (next && next.inProgress)
+ next = next.nextSibling;
+
+ // Move the item
+ gDownloadsView.insertBefore(dl, next);
+ } else {
+ removeFromView(dl);
+ }
+
+ // getTypeFromFile fails if it can't find a type for this file.
+ try {
+ // Refresh the icon, so that executable icons are shown.
+ var mimeService = Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService);
+ var contentType = mimeService.getTypeFromFile(aDownload.targetFile);
+
+ var listItem = getDownload(aDownload.id)
+ var oldImage = listItem.getAttribute("image");
+ // Tacking on contentType bypasses cache
+ listItem.setAttribute("image", oldImage + "&contentType=" + contentType);
+ } catch (e) { }
+
+ if (gDownloadManager.activeDownloadCount == 0)
+ document.title = document.documentElement.getAttribute("statictitle");
+
+ gDownloadManagerUI.getAttention();
+ }
+ catch (e) { }
+}
+
+function autoRemoveAndClose(aDownload)
+{
+ var pref = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+ if (gDownloadManager.activeDownloadCount == 0) {
+ // For the moment, just use the simple heuristic that if this window was
+ // opened by the download process, rather than by the user, it should
+ // auto-close if the pref is set that way. If the user opened it themselves,
+ // it should not close until they explicitly close it. Additionally, the
+ // preference to control the feature may not be set, so defaulting to
+ // keeping the window open.
+ let autoClose = false;
+ try {
+ autoClose = pref.getBoolPref(PREF_BDM_CLOSEWHENDONE);
+ } catch (e) { }
+ var autoOpened =
+ !window.opener || window.opener.location.href == window.location.href;
+ if (autoClose && autoOpened && !gUserInteracted) {
+ gCloseDownloadManager();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// This function can be overwritten by extensions that wish to place the
+// Download Window in another part of the UI.
+function gCloseDownloadManager()
+{
+ window.close();
+}
+
+// Download Event Handlers
+
+function cancelDownload(aDownload)
+{
+ gDownloadManager.cancelDownload(aDownload.getAttribute("dlid"));
+
+ // XXXben -
+ // If we got here because we resumed the download, we weren't using a temp file
+ // because we used saveURL instead. (this is because the proper download mechanism
+ // employed by the helper app service isn't fully accessible yet... should be fixed...
+ // talk to bz...)
+ // the upshot is we have to delete the file if it exists.
+ var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
+
+ if (f.exists())
+ f.remove(false);
+}
+
+function pauseDownload(aDownload)
+{
+ var id = aDownload.getAttribute("dlid");
+ gDownloadManager.pauseDownload(id);
+}
+
+function resumeDownload(aDownload)
+{
+ gDownloadManager.resumeDownload(aDownload.getAttribute("dlid"));
+}
+
+function removeDownload(aDownload)
+{
+ gDownloadManager.removeDownload(aDownload.getAttribute("dlid"));
+}
+
+function retryDownload(aDownload)
+{
+ removeFromView(aDownload);
+ gDownloadManager.retryDownload(aDownload.getAttribute("dlid"));
+}
+
+function showDownload(aDownload)
+{
+ var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
+
+ try {
+ // Show the directory containing the file and select the file
+ f.reveal();
+ } catch (e) {
+ // 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 = f.parent.QueryInterface(Ci.nsILocalFile);
+ if (!parent)
+ return;
+
+ try {
+ // "Double click" the parent directory to show where the file should be
+ parent.launch();
+ } catch (e) {
+ // If launch also fails (probably because it's not implemented), let the
+ // OS handler try to open the parent
+ openExternal(parent);
+ }
+ }
+}
+
+function onDownloadDblClick(aEvent)
+{
+ // Only do the default action for double primary clicks
+ if (aEvent.button == 0 && aEvent.target.selected)
+ doDefaultForSelected();
+}
+
+function openDownload(aDownload)
+{
+ var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
+ if (f.isExecutable()) {
+ var dontAsk = false;
+ var pref = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ try {
+ dontAsk = !pref.getBoolPref(PREF_BDM_CONFIRMOPENEXE);
+ } catch (e) { }
+
+#ifdef XP_WIN
+ // On Vista and above, we rely on native security prompting for
+ // downloaded content unless it's disabled.
+ try {
+ var sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ if (parseFloat(sysInfo.getProperty("version")) >= 6 &&
+ pref.getBoolPref(PREF_BDM_SCANWHENDONE)) {
+ dontAsk = true;
+ }
+ } catch (ex) { }
+#endif
+
+ if (!dontAsk) {
+ var strings = document.getElementById("downloadStrings");
+ var name = aDownload.getAttribute("target");
+ var message = strings.getFormattedString("fileExecutableSecurityWarning", [name, name]);
+
+ let title = gStr.fileExecutableSecurityWarningTitle;
+
+ var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ var open = promptSvc.confirm(window, title, message);
+
+ if (!open)
+ return;
+ }
+ }
+ try {
+ try {
+ let download = gDownloadManager.getDownload(aDownload.getAttribute("dlid"));
+ let mimeInfo = download.MIMEInfo;
+ if (mimeInfo.preferredAction == mimeInfo.useHelperApp) {
+ mimeInfo.launchWithFile(f);
+ return;
+ }
+ } catch (ex) {
+ }
+ f.launch();
+ } catch (ex) {
+ // if launch fails, try sending it through the system's external
+ // file: URL handler
+ openExternal(f);
+ }
+}
+
+function openReferrer(aDownload)
+{
+ openURL(getReferrerOrSource(aDownload));
+}
+
+function copySourceLocation(aDownload)
+{
+ var uri = aDownload.getAttribute("uri");
+ var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+
+ // Check if we should initialize a callback
+ if (gPerformAllCallback === null) {
+ let uris = [];
+ gPerformAllCallback = aURI => aURI ? uris.push(aURI) :
+ clipboard.copyString(uris.join("\n"));
+ }
+
+ // We have a callback to use, so use it to add a uri
+ if (typeof gPerformAllCallback == "function")
+ gPerformAllCallback(uri);
+ else {
+ // It's a plain copy source, so copy it
+ clipboard.copyString(uri);
+ }
+}
+
+/**
+ * Remove the currently shown downloads from the download list.
+ */
+function clearDownloadList() {
+ // Clear the whole list if there's no search
+ if (gSearchTerms == "") {
+ gDownloadManager.cleanUp();
+ return;
+ }
+
+ // Remove each download starting from the end until we hit a download
+ // that is in progress
+ let item;
+ while ((item = gDownloadsView.lastChild) && !item.inProgress)
+ removeDownload(item);
+
+ // Clear the input as if the user did it and move focus to the list
+ gSearchBox.value = "";
+ gSearchBox.doCommand();
+ gDownloadsView.focus();
+}
+
+// This is called by the progress listener.
+var gLastComputedMean = -1;
+var gLastActiveDownloads = 0;
+function onUpdateProgress()
+{
+ let numActiveDownloads = gDownloadManager.activeDownloadCount;
+
+ // Use the default title and reset "last" values if there's no downloads
+ if (numActiveDownloads == 0) {
+ document.title = document.documentElement.getAttribute("statictitle");
+ gLastComputedMean = -1;
+ gLastActiveDownloads = 0;
+
+ return;
+ }
+
+ // Establish the mean transfer speed and amount downloaded.
+ var mean = 0;
+ var base = 0;
+ var dls = gDownloadManager.activeDownloads;
+ while (dls.hasMoreElements()) {
+ let dl = dls.getNext();
+ if (dl.percentComplete < 100 && dl.size > 0) {
+ mean += dl.amountTransferred;
+ base += dl.size;
+ }
+ }
+
+ // Calculate the percent transferred, unless we don't have a total file size
+ let title = gStr.downloadsTitlePercent;
+ if (base == 0)
+ title = gStr.downloadsTitleFiles;
+ else
+ mean = Math.floor((mean / base) * 100);
+
+ // Update title of window
+ if (mean != gLastComputedMean || gLastActiveDownloads != numActiveDownloads) {
+ gLastComputedMean = mean;
+ gLastActiveDownloads = numActiveDownloads;
+
+ // Get the correct plural form and insert number of downloads and percent
+ title = PluralForm.get(numActiveDownloads, title);
+ title = replaceInsert(title, 1, numActiveDownloads);
+ title = replaceInsert(title, 2, mean);
+
+ document.title = title;
+ }
+}
+
+// Startup, Shutdown
+
+function Startup()
+{
+ gDownloadsView = document.getElementById("downloadView");
+ gSearchBox = document.getElementById("searchbox");
+
+ // convert strings to those in the string bundle
+ let sb = document.getElementById("downloadStrings");
+ let getStr = string => sb.getString(string);
+ for (let [name, value] of Object.entries(gStr))
+ gStr[name] = typeof value == "string" ? getStr(value) : value.map(getStr);
+
+ initStatement();
+ buildDownloadList(true);
+
+ // The DownloadProgressListener (DownloadProgressListener.js) handles progress
+ // notifications.
+ gDownloadListener = new DownloadProgressListener();
+ gDownloadManager.addListener(gDownloadListener);
+
+ // If the UI was displayed because the user interacted, we need to make sure
+ // we update gUserInteracted accordingly.
+ if (window.arguments[1] == Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED)
+ gUserInteracted = true;
+
+ // downloads can finish before Startup() does, so check if the window should
+ // close and act accordingly
+ if (!autoRemoveAndClose())
+ gDownloadsView.focus();
+
+ let obs = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ obs.addObserver(gDownloadObserver, "download-manager-remove-download", false);
+ obs.addObserver(gDownloadObserver, "browser-lastwindow-close-granted", false);
+
+ // Clear the search box and move focus to the list on escape from the box
+ gSearchBox.addEventListener("keypress", function(e) {
+ if (e.keyCode == e.DOM_VK_ESCAPE) {
+ // Move focus to the list instead of closing the window
+ gDownloadsView.focus();
+ e.preventDefault();
+ }
+ }, false);
+
+ let DownloadTaskbarProgress =
+ Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
+ DownloadTaskbarProgress.onDownloadWindowLoad(window);
+}
+
+function Shutdown()
+{
+ gDownloadManager.removeListener(gDownloadListener);
+
+ let obs = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ obs.removeObserver(gDownloadObserver, "download-manager-remove-download");
+ obs.removeObserver(gDownloadObserver, "browser-lastwindow-close-granted");
+
+ clearTimeout(gBuilder);
+ gStmt.reset();
+ gStmt.finalize();
+}
+
+var gDownloadObserver = {
+ observe: function gdo_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "download-manager-remove-download":
+ // A null subject here indicates "remove multiple", so we just rebuild.
+ if (!aSubject) {
+ // Rebuild the default view
+ buildDownloadList(true);
+ break;
+ }
+
+ // Otherwise, remove a single download
+ let id = aSubject.QueryInterface(Ci.nsISupportsPRUint32);
+ let dl = getDownload(id.data);
+ removeFromView(dl);
+ break;
+ case "browser-lastwindow-close-granted":
+ if (gDownloadManager.activeDownloadCount == 0) {
+ setTimeout(gCloseDownloadManager, 0);
+ }
+ break;
+ }
+ }
+};
+
+// View Context Menus
+
+var gContextMenus = [
+ // DOWNLOAD_DOWNLOADING
+ [
+ "menuitem_pause"
+ , "menuitem_cancel"
+ , "menuseparator"
+ , "menuitem_show"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ ],
+ // DOWNLOAD_FINISHED
+ [
+ "menuitem_open"
+ , "menuitem_show"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ],
+ // DOWNLOAD_FAILED
+ [
+ "menuitem_retry"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ],
+ // DOWNLOAD_CANCELED
+ [
+ "menuitem_retry"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ],
+ // DOWNLOAD_PAUSED
+ [
+ "menuitem_resume"
+ , "menuitem_cancel"
+ , "menuseparator"
+ , "menuitem_show"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ ],
+ // DOWNLOAD_QUEUED
+ [
+ "menuitem_cancel"
+ , "menuseparator"
+ , "menuitem_show"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ ],
+ // DOWNLOAD_BLOCKED_PARENTAL
+ [
+ "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ],
+ // DOWNLOAD_SCANNING
+ [
+ "menuitem_show"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ ],
+ // DOWNLOAD_DIRTY
+ [
+ "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ],
+ // DOWNLOAD_BLOCKED_POLICY
+ [
+ "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ]
+];
+
+function buildContextMenu(aEvent)
+{
+ if (aEvent.target.id != "downloadContextMenu")
+ return false;
+
+ var popup = document.getElementById("downloadContextMenu");
+ while (popup.hasChildNodes())
+ popup.removeChild(popup.firstChild);
+
+ if (gDownloadsView.selectedItem) {
+ let dl = gDownloadsView.selectedItem;
+ let idx = parseInt(dl.getAttribute("state"));
+ if (idx < 0)
+ idx = 0;
+
+ var menus = gContextMenus[idx];
+ for (let i = 0; i < menus.length; ++i) {
+ let menuitem = document.getElementById(menus[i]).cloneNode(true);
+ let cmd = menuitem.getAttribute("cmd");
+ if (cmd)
+ menuitem.disabled = !gDownloadViewController.isCommandEnabled(cmd, dl);
+
+ popup.appendChild(menuitem);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+// Drag and Drop
+var gDownloadDNDObserver =
+{
+ onDragStart: function (aEvent)
+ {
+ if (!gDownloadsView.selectedItem)
+ return;
+ var dl = gDownloadsView.selectedItem;
+ var f = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
+ if (!f.exists())
+ return;
+
+ var dt = aEvent.dataTransfer;
+ dt.mozSetDataAt("application/x-moz-file", f, 0);
+ var url = Services.io.newFileURI(f).spec;
+ dt.setData("text/uri-list", url);
+ dt.setData("text/plain", url);
+ dt.effectAllowed = "copyMove";
+ dt.addElement(dl);
+ },
+
+ onDragOver: function (aEvent)
+ {
+ var types = aEvent.dataTransfer.types;
+ if (types.includes("text/uri-list") ||
+ types.includes("text/x-moz-url") ||
+ types.includes("text/plain"))
+ aEvent.preventDefault();
+ },
+
+ onDrop: function(aEvent)
+ {
+ var 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;
+
+ var url = dt.getData("URL");
+ var name;
+ if (!url) {
+ url = dt.getData("text/x-moz-url") || dt.getData("text/plain");
+ [url, name] = url.split("\n");
+ }
+ if (url) {
+ let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
+ saveURL(url, name ? name : url, null, true, true, null, sourceDoc);
+ }
+ }
+}
+
+function pasteHandler() {
+ 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 = Services.io.newURI(url, null, null);
+
+ saveURL(uri.spec, name || uri.spec, null, true, true, null, document);
+ } catch (ex) {}
+}
+
+// Command Updating and Command Handlers
+
+var gDownloadViewController = {
+ isCommandEnabled: function(aCommand, aItem)
+ {
+ let dl = aItem;
+ let download = null; // used for getting an nsIDownload object
+
+ switch (aCommand) {
+ case "cmd_cancel":
+ return dl.inProgress;
+ case "cmd_open": {
+ let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
+ return dl.openable && file.exists();
+ }
+ case "cmd_show": {
+ let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
+ return file.exists();
+ }
+ case "cmd_pause":
+ download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
+ return dl.inProgress && !dl.paused && download.resumable;
+ case "cmd_pauseResume":
+ download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
+ return (dl.inProgress || dl.paused) && download.resumable;
+ case "cmd_resume":
+ download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
+ return dl.paused && download.resumable;
+ case "cmd_openReferrer":
+ return dl.hasAttribute("referrer");
+ case "cmd_removeFromList":
+ case "cmd_retry":
+ return dl.removable;
+ case "cmd_copyLocation":
+ return true;
+ }
+ return false;
+ },
+
+ doCommand: function(aCommand, aItem)
+ {
+ if (this.isCommandEnabled(aCommand, aItem))
+ this.commands[aCommand](aItem);
+ },
+
+ commands: {
+ cmd_cancel: function(aSelectedItem) {
+ cancelDownload(aSelectedItem);
+ },
+ cmd_open: function(aSelectedItem) {
+ openDownload(aSelectedItem);
+ },
+ cmd_openReferrer: function(aSelectedItem) {
+ openReferrer(aSelectedItem);
+ },
+ cmd_pause: function(aSelectedItem) {
+ pauseDownload(aSelectedItem);
+ },
+ cmd_pauseResume: function(aSelectedItem) {
+ if (aSelectedItem.paused)
+ this.cmd_resume(aSelectedItem);
+ else
+ this.cmd_pause(aSelectedItem);
+ },
+ cmd_removeFromList: function(aSelectedItem) {
+ removeDownload(aSelectedItem);
+ },
+ cmd_resume: function(aSelectedItem) {
+ resumeDownload(aSelectedItem);
+ },
+ cmd_retry: function(aSelectedItem) {
+ retryDownload(aSelectedItem);
+ },
+ cmd_show: function(aSelectedItem) {
+ showDownload(aSelectedItem);
+ },
+ cmd_copyLocation: function(aSelectedItem) {
+ copySourceLocation(aSelectedItem);
+ },
+ }
+};
+
+/**
+ * Helper function to do commands.
+ *
+ * @param aCmd
+ * The command to be performed.
+ * @param aItem
+ * The richlistitem that represents the download that will have the
+ * command performed on it. If this is null, the command is performed on
+ * all downloads. If the item passed in is not a richlistitem that
+ * represents a download, it will walk up the parent nodes until it finds
+ * a DOM node that is.
+ */
+function performCommand(aCmd, aItem)
+{
+ let elm = aItem;
+ if (!elm) {
+ // If we don't have a desired download item, do the command for all
+ // selected items. Initialize the callback to null so commands know to add
+ // a callback if they want. We will call the callback with empty arguments
+ // after performing the command on each selected download item.
+ gPerformAllCallback = null;
+
+ // Convert the nodelist into an array to keep a copy of the download items
+ let items = [];
+ for (let i = gDownloadsView.selectedItems.length; --i >= 0; )
+ items.unshift(gDownloadsView.selectedItems[i]);
+
+ // Do the command for each download item
+ for (let item of items)
+ performCommand(aCmd, item);
+
+ // Call the callback with no arguments and reset because we're done
+ if (typeof gPerformAllCallback == "function")
+ gPerformAllCallback();
+ gPerformAllCallback = undefined;
+
+ return;
+ }
+ while (elm.nodeName != "richlistitem" ||
+ elm.getAttribute("type") != "download") {
+ elm = elm.parentNode;
+ }
+
+ gDownloadViewController.doCommand(aCmd, elm);
+}
+
+function setSearchboxFocus()
+{
+ gSearchBox.focus();
+ gSearchBox.select();
+}
+
+function openExternal(aFile)
+{
+ var uri = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService).newFileURI(aFile);
+
+ var protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ protocolSvc.loadUrl(uri);
+
+ return;
+}
+
+// Utility Functions
+
+/**
+ * Create a download richlistitem with the provided attributes. Some attributes
+ * are *required* while optional ones will only be set on the item if provided.
+ *
+ * @param aAttrs
+ * An object that must have the following properties: dlid, file,
+ * target, uri, state, progress, startTime, endTime, currBytes,
+ * maxBytes; optional properties: referrer
+ * @return An initialized download richlistitem
+ */
+function createDownloadItem(aAttrs)
+{
+ let dl = document.createElement("richlistitem");
+
+ // Copy the attributes from the argument into the item
+ for (let attr in aAttrs)
+ dl.setAttribute(attr, aAttrs[attr]);
+
+ // Initialize other attributes
+ dl.setAttribute("type", "download");
+ dl.setAttribute("id", "dl" + aAttrs.dlid);
+ dl.setAttribute("image", "moz-icon://" + aAttrs.file + "?size=32");
+ dl.setAttribute("lastSeconds", Infinity);
+
+ // Initialize more complex attributes
+ updateTime(dl);
+ updateStatus(dl);
+
+ try {
+ let file = getLocalFileFromNativePathOrUrl(aAttrs.file);
+ dl.setAttribute("path", file.nativePath || file.path);
+ return dl;
+ } catch (e) {
+ // aFile might not be a file: url or a valid native path
+ // see bug #392386 for details
+ }
+ return null;
+}
+
+/**
+ * Updates the disabled state of the buttons of a downlaod.
+ *
+ * @param aItem
+ * The richlistitem representing the download.
+ */
+function updateButtons(aItem)
+{
+ let buttons = aItem.buttons;
+
+ for (let i = 0; i < buttons.length; ++i) {
+ let cmd = buttons[i].getAttribute("cmd");
+ let enabled = gDownloadViewController.isCommandEnabled(cmd, aItem);
+ buttons[i].disabled = !enabled;
+
+ if ("cmd_pause" == cmd && !enabled) {
+ // We need to add the tooltip indicating that the download cannot be
+ // paused now.
+ buttons[i].setAttribute("tooltiptext", gStr.cannotPause);
+ }
+ }
+}
+
+/**
+ * Updates the status for a download item depending on its state
+ *
+ * @param aItem
+ * The richlistitem that has various download attributes.
+ * @param aDownload
+ * The nsDownload from the backend. This is an optional parameter, but
+ * is useful for certain states such as DOWNLOADING.
+ */
+function updateStatus(aItem, aDownload) {
+ let status = "";
+ let statusTip = "";
+
+ let state = Number(aItem.getAttribute("state"));
+ switch (state) {
+ case nsIDM.DOWNLOAD_PAUSED:
+ {
+ let currBytes = Number(aItem.getAttribute("currBytes"));
+ let maxBytes = Number(aItem.getAttribute("maxBytes"));
+
+ let transfer = DownloadUtils.getTransferTotal(currBytes, maxBytes);
+ status = replaceInsert(gStr.paused, 1, transfer);
+
+ break;
+ }
+ case nsIDM.DOWNLOAD_DOWNLOADING:
+ {
+ let currBytes = Number(aItem.getAttribute("currBytes"));
+ let maxBytes = Number(aItem.getAttribute("maxBytes"));
+ // If we don't have an active download, assume 0 bytes/sec
+ let speed = aDownload ? aDownload.speed : 0;
+ let lastSec = Number(aItem.getAttribute("lastSeconds"));
+
+ let newLast;
+ [status, newLast] =
+ DownloadUtils.getDownloadStatus(currBytes, maxBytes, speed, lastSec);
+
+ // Update lastSeconds to be the new value
+ aItem.setAttribute("lastSeconds", newLast);
+
+ break;
+ }
+ case nsIDM.DOWNLOAD_FINISHED:
+ case nsIDM.DOWNLOAD_FAILED:
+ case nsIDM.DOWNLOAD_CANCELED:
+ case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
+ case nsIDM.DOWNLOAD_BLOCKED_POLICY:
+ case nsIDM.DOWNLOAD_DIRTY:
+ {
+ let stateSize = {};
+ stateSize[nsIDM.DOWNLOAD_FINISHED] = function() {
+ // Display the file size, but show "Unknown" for negative sizes
+ let fileSize = Number(aItem.getAttribute("maxBytes"));
+ let sizeText = gStr.doneSizeUnknown;
+ if (fileSize >= 0) {
+ let [size, unit] = DownloadUtils.convertByteUnits(fileSize);
+ sizeText = replaceInsert(gStr.doneSize, 1, size);
+ sizeText = replaceInsert(sizeText, 2, unit);
+ }
+ return sizeText;
+ };
+ stateSize[nsIDM.DOWNLOAD_FAILED] = () => gStr.stateFailed;
+ stateSize[nsIDM.DOWNLOAD_CANCELED] = () => gStr.stateCanceled;
+ stateSize[nsIDM.DOWNLOAD_BLOCKED_PARENTAL] = () => gStr.stateBlockedParentalControls;
+ stateSize[nsIDM.DOWNLOAD_BLOCKED_POLICY] = () => gStr.stateBlockedPolicy;
+ stateSize[nsIDM.DOWNLOAD_DIRTY] = () => gStr.stateDirty;
+
+ // Insert 1 is the download size or download state
+ status = replaceInsert(gStr.doneStatus, 1, stateSize[state]());
+
+ let [displayHost, fullHost] =
+ DownloadUtils.getURIHost(getReferrerOrSource(aItem));
+
+ // Insert 2 is the eTLD + 1 or other variations of the host
+ status = replaceInsert(status, 2, displayHost);
+ // Set the tooltip to be the full host
+ statusTip = fullHost;
+
+ break;
+ }
+ }
+
+ aItem.setAttribute("status", status);
+ aItem.setAttribute("statusTip", statusTip != "" ? statusTip : status);
+}
+
+/**
+ * Updates the time that gets shown for completed download items
+ *
+ * @param aItem
+ * The richlistitem representing a download in the UI
+ */
+function updateTime(aItem)
+{
+ // Don't bother updating for things that aren't finished
+ if (aItem.inProgress)
+ return;
+
+ let end = new Date(parseInt(aItem.getAttribute("endTime")));
+ let [dateCompact, dateComplete] = DownloadUtils.getReadableDates(end);
+ aItem.setAttribute("dateTime", dateCompact);
+ aItem.setAttribute("dateTimeTip", dateComplete);
+}
+
+/**
+ * Helper function to replace a placeholder string with a real string
+ *
+ * @param aText
+ * Source text containing placeholder (e.g., #1)
+ * @param aIndex
+ * Index number of placeholder to replace
+ * @param aValue
+ * New string to put in place of placeholder
+ * @return The string with placeholder replaced with the new string
+ */
+function replaceInsert(aText, aIndex, aValue)
+{
+ return aText.replace("#" + aIndex, aValue);
+}
+
+/**
+ * Perform the default action for the currently selected download item
+ */
+function doDefaultForSelected()
+{
+ // Make sure we have something selected
+ let item = gDownloadsView.selectedItem;
+ if (!item)
+ return;
+
+ // Get the default action (first item in the menu)
+ let state = Number(item.getAttribute("state"));
+ let menuitem = document.getElementById(gContextMenus[state][0]);
+
+ // Try to do the action if the command is enabled
+ gDownloadViewController.doCommand(menuitem.getAttribute("cmd"), item);
+}
+
+function removeFromView(aDownload)
+{
+ // Make sure we have an item to remove
+ if (!aDownload) return;
+
+ let index = gDownloadsView.selectedIndex;
+ gDownloadsView.removeChild(aDownload);
+ gDownloadsView.selectedIndex = Math.min(index, gDownloadsView.itemCount - 1);
+
+ // We might have removed the last item, so update the clear list button
+ updateClearListButton();
+}
+
+function getReferrerOrSource(aDownload)
+{
+ // Give the referrer if we have it set
+ if (aDownload.hasAttribute("referrer"))
+ return aDownload.getAttribute("referrer");
+
+ // Otherwise, provide the source
+ return aDownload.getAttribute("uri");
+}
+
+/**
+ * Initiate building the download list to have the active downloads followed by
+ * completed ones filtered by the search term if necessary.
+ *
+ * @param aForceBuild
+ * Force the list to be built even if the search terms don't change
+ */
+function buildDownloadList(aForceBuild)
+{
+ // Stringify the previous search
+ let prevSearch = gSearchTerms.join(" ");
+
+ // Array of space-separated lower-case search terms
+ gSearchTerms = gSearchBox.value.replace(/^\s+|\s+$/g, "").
+ toLowerCase().split(/\s+/);
+
+ // Unless forced, don't rebuild the download list if the search didn't change
+ if (!aForceBuild && gSearchTerms.join(" ") == prevSearch)
+ return;
+
+ // Clear out values before using them
+ clearTimeout(gBuilder);
+ gStmt.reset();
+
+ // Clear the list before adding items by replacing with a shallow copy
+ let empty = gDownloadsView.cloneNode(false);
+ gDownloadsView.parentNode.replaceChild(empty, gDownloadsView);
+ gDownloadsView = empty;
+
+ try {
+ gStmt.bindByIndex(0, nsIDM.DOWNLOAD_NOTSTARTED);
+ gStmt.bindByIndex(1, nsIDM.DOWNLOAD_DOWNLOADING);
+ gStmt.bindByIndex(2, nsIDM.DOWNLOAD_PAUSED);
+ gStmt.bindByIndex(3, nsIDM.DOWNLOAD_QUEUED);
+ gStmt.bindByIndex(4, nsIDM.DOWNLOAD_SCANNING);
+ } catch (e) {
+ // Something must have gone wrong when binding, so clear and quit
+ gStmt.reset();
+ return;
+ }
+
+ // Take a quick break before we actually start building the list
+ gBuilder = setTimeout(function() {
+ // Start building the list
+ stepListBuilder(1);
+
+ // We just tried to add a single item, so we probably need to enable
+ updateClearListButton();
+ }, 0);
+}
+
+/**
+ * Incrementally build the download list by adding at most the requested number
+ * of items if there are items to add. After doing that, it will schedule
+ * another chunk of items specified by gListBuildDelay and gListBuildChunk.
+ *
+ * @param aNumItems
+ * Number of items to add to the list before taking a break
+ */
+function stepListBuilder(aNumItems) {
+ try {
+ // If we're done adding all items, we can quit
+ if (!gStmt.executeStep()) {
+ // Send a notification that we finished, but wait for clear list to update
+ updateClearListButton();
+ setTimeout(() => Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService).
+ notifyObservers(window, "download-manager-ui-done", null), 0);
+
+ return;
+ }
+
+ // Try to get the attribute values from the statement
+ let attrs = {
+ dlid: gStmt.getInt64(0),
+ file: gStmt.getString(1),
+ target: gStmt.getString(2),
+ uri: gStmt.getString(3),
+ state: gStmt.getInt32(4),
+ startTime: Math.round(gStmt.getInt64(5) / 1000),
+ endTime: Math.round(gStmt.getInt64(6) / 1000),
+ currBytes: gStmt.getInt64(8),
+ maxBytes: gStmt.getInt64(9)
+ };
+
+ // Only add the referrer if it's not null
+ let referrer = gStmt.getString(7);
+ if (referrer)
+ attrs.referrer = referrer;
+
+ // If the download is active, grab the real progress, otherwise default 100
+ let isActive = gStmt.getInt32(10);
+ attrs.progress = isActive ? gDownloadManager.getDownload(attrs.dlid).
+ percentComplete : 100;
+
+ // Make the item and add it to the end if it's active or matches the search
+ let item = createDownloadItem(attrs);
+ if (item && (isActive || downloadMatchesSearch(item))) {
+ // Add item to the end
+ gDownloadsView.appendChild(item);
+
+ // Because of the joys of XBL, we can't update the buttons until the
+ // download object is in the document.
+ updateButtons(item);
+ } else {
+ // We didn't add an item, so bump up the number of items to process, but
+ // not a whole number so that we eventually do pause for a chunk break
+ aNumItems += .9;
+ }
+ } catch (e) {
+ // Something went wrong when stepping or getting values, so clear and quit
+ gStmt.reset();
+ return;
+ }
+
+ // Add another item to the list if we should; otherwise, let the UI update
+ // and continue later
+ if (aNumItems > 1) {
+ stepListBuilder(aNumItems - 1);
+ } else {
+ // Use a shorter delay for earlier downloads to display them faster
+ let delay = Math.min(gDownloadsView.itemCount * 10, gListBuildDelay);
+ gBuilder = setTimeout(stepListBuilder, delay, gListBuildChunk);
+ }
+}
+
+/**
+ * Add a download to the front of the download list
+ *
+ * @param aDownload
+ * The nsIDownload to make into a richlistitem
+ */
+function prependList(aDownload)
+{
+ let attrs = {
+ dlid: aDownload.id,
+ file: aDownload.target.spec,
+ target: aDownload.displayName,
+ uri: aDownload.source.spec,
+ state: aDownload.state,
+ progress: aDownload.percentComplete,
+ startTime: Math.round(aDownload.startTime / 1000),
+ endTime: Date.now(),
+ currBytes: aDownload.amountTransferred,
+ maxBytes: aDownload.size
+ };
+
+ // Make the item and add it to the beginning
+ let item = createDownloadItem(attrs);
+ if (item) {
+ // Add item to the beginning
+ gDownloadsView.insertBefore(item, gDownloadsView.firstChild);
+
+ // Because of the joys of XBL, we can't update the buttons until the
+ // download object is in the document.
+ updateButtons(item);
+
+ // We might have added an item to an empty list, so update button
+ updateClearListButton();
+ }
+}
+
+/**
+ * Check if the download matches the current search term based on the texts
+ * shown to the user. All search terms are checked to see if each matches any
+ * of the displayed texts.
+ *
+ * @param aItem
+ * Download richlistitem to check if it matches the current search
+ * @return Boolean true if it matches the search; false otherwise
+ */
+function downloadMatchesSearch(aItem)
+{
+ // Search through the download attributes that are shown to the user and
+ // make it into one big string for easy combined searching
+ let combinedSearch = "";
+ for (let attr of gSearchAttributes)
+ combinedSearch += aItem.getAttribute(attr).toLowerCase() + " ";
+
+ // Make sure each of the terms are found
+ for (let term of gSearchTerms)
+ if (combinedSearch.indexOf(term) == -1)
+ return false;
+
+ return true;
+}
+
+// we should be using real URLs all the time, but until
+// bug 239948 is fully fixed, this will do...
+//
+// note, this will thrown an exception if the native path
+// is not valid (for example a native Windows path on a Mac)
+// see bug #392386 for details
+function getLocalFileFromNativePathOrUrl(aPathOrUrl)
+{
+ if (aPathOrUrl.substring(0, 7) == "file://") {
+ // if this is a URL, get the file from that
+ let ioSvc = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ // XXX it's possible that using a null char-set here is bad
+ const fileUrl = ioSvc.newURI(aPathOrUrl, null, null).
+ QueryInterface(Ci.nsIFileURL);
+ return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
+ }
+ // if it's a pathname, create the nsILocalFile directly
+ var f = new nsLocalFile(aPathOrUrl);
+
+ return f;
+}
+
+/**
+ * Update the disabled state of the clear list button based on whether or not
+ * there are items in the list that can potentially be removed.
+ */
+function updateClearListButton()
+{
+ let button = document.getElementById("clearListButton");
+ // The button is enabled if we have items in the list and we can clean up
+ button.disabled = !(gDownloadsView.itemCount && gDownloadManager.canCleanUp);
+}
+
+function getDownload(aID)
+{
+ return document.getElementById("dl" + aID);
+}
+
+/**
+ * Initialize the statement which is used to retrieve the list of downloads.
+ */
+function initStatement()
+{
+ if (gStmt)
+ gStmt.finalize();
+
+ gStmt = gDownloadManager.DBConnection.createStatement(
+ "SELECT id, target, name, source, state, startTime, endTime, referrer, " +
+ "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
+ "FROM moz_downloads " +
+ "ORDER BY isActive DESC, endTime DESC, startTime DESC");
+}
diff --git a/components/downloads/content/downloads.xul b/components/downloads/content/downloads.xul
new file mode 100644
index 000000000..b5ca87a0c
--- /dev/null
+++ b/components/downloads/content/downloads.xul
@@ -0,0 +1,154 @@
+<?xml version="1.0"?>
+
+# -*- Mode: XML; 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
+#define XP_GNOME 1
+#endif
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/content/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/downloads/downloads.css"?>
+
+<!DOCTYPE window [
+<!ENTITY % downloadManagerDTD SYSTEM "chrome://mozapps/locale/downloads/downloads.dtd">
+%downloadManagerDTD;
+<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+%editMenuOverlayDTD;
+]>
+
+<window xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="downloadManager" windowtype="Download:Manager"
+ orient="vertical" title="&downloads.title;" statictitle="&downloads.title;"
+ width="&window.width2;" height="&window.height;" screenX="10" screenY="10"
+ persist="width height screenX screenY sizemode"
+ onload="Startup();" onunload="Shutdown();"
+ onclose="return closeWindow(false);">
+
+ <script type="application/javascript" src="chrome://mozapps/content/downloads/downloads.js"/>
+ <script type="application/javascript" src="chrome://mozapps/content/downloads/DownloadProgressListener.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+
+ <stringbundleset id="downloadSet">
+ <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="downloadStrings" src="chrome://mozapps/locale/downloads/downloads.properties"/>
+ </stringbundleset>
+
+ <!-- Use this commandset for command which do not depened on focus or selection -->
+ <commandset id="generalCommands">
+ <command id="cmd_findDownload" oncommand="setSearchboxFocus();"/>
+ <command id="cmd_selectAllDownloads" oncommand="gDownloadsView.selectAll();"/>
+ <command id="cmd_clearList" oncommand="clearDownloadList();"/>
+ </commandset>
+
+ <keyset id="downloadKeys">
+ <key keycode="VK_RETURN" oncommand="doDefaultForSelected();"/>
+ <key id="key_pauseResume" key=" " oncommand="performCommand('cmd_pauseResume');"/>
+ <key id="key_removeFromList" keycode="VK_DELETE" oncommand="performCommand('cmd_removeFromList');"/>
+ <key id="key_close" key="&cmd.close.commandKey;" oncommand="closeWindow(true);" modifiers="accel"/>
+#ifdef XP_GNOME
+ <key id="key_close2" key="&cmd.close2Unix.commandKey;" oncommand="closeWindow(true);" modifiers="accel,shift"/>
+#else
+ <key id="key_close2" key="&cmd.close2.commandKey;" oncommand="closeWindow(true);" modifiers="accel"/>
+#endif
+ <key keycode="VK_ESCAPE" oncommand="closeWindow(true);"/>
+
+ <key id="key_findDownload"
+ key="&cmd.find.commandKey;"
+ modifiers="accel"
+ command="cmd_findDownload"/>
+ <key id="key_findDownload2"
+ key="&cmd.search.commandKey;"
+ modifiers="accel"
+ command="cmd_findDownload"/>
+ <key id="key_selectAllDownloads"
+ key="&selectAllCmd.key;"
+ modifiers="accel"
+ command="cmd_selectAllDownloads"/>
+ <key id="pasteKey"
+ key="V"
+ modifiers="accel"
+ oncommand="pasteHandler();"/>
+ </keyset>
+
+ <vbox id="contextMenuPalette" hidden="true">
+ <menuitem id="menuitem_pause"
+ label="&cmd.pause.label;" accesskey="&cmd.pause.accesskey;"
+ oncommand="performCommand('cmd_pause');"
+ cmd="cmd_pause"/>
+ <menuitem id="menuitem_resume"
+ label="&cmd.resume.label;" accesskey="&cmd.resume.accesskey;"
+ oncommand="performCommand('cmd_resume');"
+ cmd="cmd_resume"/>
+ <menuitem id="menuitem_cancel"
+ label="&cmd.cancel.label;" accesskey="&cmd.cancel.accesskey;"
+ oncommand="performCommand('cmd_cancel');"
+ cmd="cmd_cancel"/>
+
+ <menuitem id="menuitem_open" default="true"
+ label="&cmd.open.label;" accesskey="&cmd.open.accesskey;"
+ oncommand="performCommand('cmd_open');"
+ cmd="cmd_open"/>
+ <menuitem id="menuitem_show"
+ label="&cmd.show.label;"
+ accesskey="&cmd.show.accesskey;"
+ oncommand="performCommand('cmd_show');"
+ cmd="cmd_show"/>
+
+ <menuitem id="menuitem_retry" default="true"
+ label="&cmd.retry.label;" accesskey="&cmd.retry.accesskey;"
+ oncommand="performCommand('cmd_retry');"
+ cmd="cmd_retry"/>
+
+ <menuitem id="menuitem_removeFromList"
+ label="&cmd.removeFromList.label;" accesskey="&cmd.removeFromList.accesskey;"
+ oncommand="performCommand('cmd_removeFromList');"
+ cmd="cmd_removeFromList"/>
+
+ <menuseparator id="menuseparator"/>
+
+ <menuitem id="menuitem_openReferrer"
+ label="&cmd.goToDownloadPage.label;"
+ accesskey="&cmd.goToDownloadPage.accesskey;"
+ oncommand="performCommand('cmd_openReferrer');"
+ cmd="cmd_openReferrer"/>
+
+ <menuitem id="menuitem_copyLocation"
+ label="&cmd.copyDownloadLink.label;"
+ accesskey="&cmd.copyDownloadLink.accesskey;"
+ oncommand="performCommand('cmd_copyLocation');"
+ cmd="cmd_copyLocation"/>
+
+ <menuitem id="menuitem_selectAll"
+ label="&selectAllCmd.label;"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAllDownloads"/>
+ </vbox>
+
+ <menupopup id="downloadContextMenu" onpopupshowing="return buildContextMenu(event);"/>
+
+ <richlistbox id="downloadView" seltype="multiple" flex="1"
+ context="downloadContextMenu"
+ ondblclick="onDownloadDblClick(event);"
+ ondragstart="gDownloadDNDObserver.onDragStart(event);"
+ ondragover="gDownloadDNDObserver.onDragOver(event);event.stopPropagation();"
+ ondrop="gDownloadDNDObserver.onDrop(event)">
+ </richlistbox>
+
+ <windowdragbox id="search" align="center">
+ <button id="clearListButton" command="cmd_clearList"
+ label="&cmd.clearList.label;"
+ accesskey="&cmd.clearList.accesskey;"
+ tooltiptext="&cmd.clearList.tooltip;"/>
+ <spacer flex="1"/>
+ <textbox type="search" id="searchbox" class="compact"
+ aria-controls="downloadView"
+ oncommand="buildDownloadList();" placeholder="&searchBox.label;"/>
+ </windowdragbox>
+
+</window>
diff --git a/components/downloads/content/unknownContentType.xul b/components/downloads/content/unknownContentType.xul
new file mode 100644
index 000000000..42d356e9f
--- /dev/null
+++ b/components/downloads/content/unknownContentType.xul
@@ -0,0 +1,103 @@
+<?xml version="1.0"?>
+# -*- Mode: XML; 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://mozapps/skin/downloads/unknownContentType.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+ %brandDTD;
+ <!ENTITY % uctDTD SYSTEM "chrome://mozapps/locale/downloads/unknownContentType.dtd" >
+ %uctDTD;
+ <!ENTITY % scDTD SYSTEM "chrome://mozapps/locale/downloads/settingsChange.dtd" >
+ %scDTD;
+]>
+
+<dialog id="unknownContentType"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="dialog.initDialog();" onunload="if (dialog) dialog.onCancel();"
+#ifdef XP_WIN
+ style="width: 36em;"
+#else
+ style="width: 34em;"
+#endif
+ screenX="" screenY=""
+ persist="screenX screenY"
+ aria-describedby="intro location whichIs type from source unknownPrompt"
+ ondialogaccept="return dialog.onOK()"
+ ondialogcancel="return dialog.onCancel()">
+
+
+ <stringbundle id="strings" src="chrome://mozapps/locale/downloads/unknownContentType.properties"/>
+
+ <vbox flex="1" id="container">
+ <description id="intro">&intro2.label;</description>
+ <separator class="thin"/>
+ <hbox align="start" class="small-indent">
+ <image id="contentTypeImage"/>
+ <vbox flex="1">
+ <description id="location" class="plain" crop="start" flex="1"/>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label id="whichIs" value="&whichIs.label;"/>
+ <textbox id="type" class="plain" readonly="true" flex="1" noinitialfocus="true"/>
+ </hbox>
+ <hbox align="center">
+ <label value="&from.label;" id="from"/>
+ <description id="source" class="plain" crop="start" flex="1"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <hbox align="center" id="basicBox" collapsed="true">
+ <label id="unknownPrompt" value="&unknownPromptText.label;" flex="1"/>
+ </hbox>
+
+ <groupbox flex="1" id="normalBox">
+ <caption label="&actionQuestion.label;"/>
+ <separator class="thin"/>
+ <radiogroup id="mode" class="small-indent">
+ <hbox>
+ <radio id="open" label="&openWith.label;" accesskey="&openWith.accesskey;"/>
+ <deck id="modeDeck" flex="1">
+ <hbox id="openHandlerBox" flex="1" align="center"/>
+ <hbox flex="1" align="center">
+ <button id="chooseButton" oncommand="dialog.chooseApp();"
+ label="&chooseHandler.label;" accesskey="&chooseHandler.accesskey;"/>
+ </hbox>
+ </deck>
+ </hbox>
+
+ <radio id="save" label="&saveFile.label;" accesskey="&saveFile.accesskey;"/>
+ </radiogroup>
+ <separator class="thin"/>
+ <hbox class="small-indent">
+ <checkbox id="rememberChoice" label="&rememberChoice.label;"
+ accesskey="&rememberChoice.accesskey;"
+ oncommand="dialog.toggleRememberChoice(event.target);"/>
+ </hbox>
+
+ <separator/>
+#ifdef XP_UNIX
+ <description id="settingsChange" hidden="true">&settingsChangePreferences.label;</description>
+#else
+ <description id="settingsChange" hidden="true">&settingsChangeOptions.label;</description>
+#endif
+ <separator class="thin"/>
+ </groupbox>
+ </vbox>
+
+ <menulist id="openHandler" flex="1">
+ <menupopup id="openHandlerPopup" oncommand="dialog.openHandlerCommand();">
+ <menuitem id="defaultHandler" default="true" crop="right"/>
+ <menuitem id="otherHandler" hidden="true" crop="left"/>
+ <menuseparator/>
+ <menuitem id="choose" label="&other.label;"/>
+ </menupopup>
+ </menulist>
+</dialog>
diff --git a/components/downloads/jar.mn b/components/downloads/jar.mn
new file mode 100644
index 000000000..29a3d0ee2
--- /dev/null
+++ b/components/downloads/jar.mn
@@ -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/.
+
+toolkit.jar:
+% content mozapps %content/mozapps/
+* content/mozapps/downloads/unknownContentType.xul (content/unknownContentType.xul)
+* content/mozapps/downloads/downloads.xul (content/downloads.xul)
+* content/mozapps/downloads/downloads.js (content/downloads.js)
+ content/mozapps/downloads/DownloadProgressListener.js (content/DownloadProgressListener.js)
+ content/mozapps/downloads/downloads.css (content/downloads.css)
+ content/mozapps/downloads/download.xml (content/download.xml)
diff --git a/components/downloads/locale/downloads.dtd b/components/downloads/locale/downloads.dtd
new file mode 100644
index 000000000..3c6373cfb
--- /dev/null
+++ b/components/downloads/locale/downloads.dtd
@@ -0,0 +1,52 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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 (window.width2, window.height): These values should be
+close to the golden ratio (1.618:1) while making sure it's wide enough for long
+file names and tall enough to hint that there are more downloads in the list -->
+<!ENTITY window.width2 "485">
+<!ENTITY window.height "300">
+
+<!ENTITY starting.label "Starting…">
+<!ENTITY scanning.label "Scanning for viruses…">
+
+<!ENTITY downloads.title "Downloads">
+
+<!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">
+<!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.open.label "Open">
+<!ENTITY cmd.open.accesskey "O">
+<!ENTITY cmd.openWith.label "Open With…">
+<!ENTITY cmd.openWith.accesskey "h">
+<!ENTITY cmd.retry.label "Retry">
+<!ENTITY cmd.retry.accesskey "R">
+<!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.removeFromList.label "Remove From List">
+<!ENTITY cmd.removeFromList.accesskey "e">
+
+<!ENTITY cmd.close.commandKey "w">
+<!ENTITY cmd.close2.commandKey "j">
+<!ENTITY cmd.close2Unix.commandKey "y">
+<!ENTITY cmd.clearList.label "Clear List">
+<!ENTITY cmd.clearList.tooltip "Removes completed, canceled, and failed downloads from the list">
+<!ENTITY cmd.clearList.accesskey "C">
+<!ENTITY cmd.find.commandKey "f">
+<!ENTITY cmd.search.commandKey "k">
+
+<!ENTITY closeWhenDone.label "Close when downloads complete">
+<!ENTITY closeWhenDone.tooltip "Closes the Downloads window when all files are done downloading">
+
+<!ENTITY showFolder.label "Show this Folder">
+<!ENTITY searchBox.label "Search…">
diff --git a/components/downloads/locale/downloads.properties b/components/downloads/locale/downloads.properties
new file mode 100644
index 000000000..af95022f1
--- /dev/null
+++ b/components/downloads/locale/downloads.properties
@@ -0,0 +1,141 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE (seconds, minutes, hours, days): Semi-colon list of plural
+# forms. See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+seconds=second;seconds
+minutes=minute;minutes
+hours=hour;hours
+days=day;days
+
+# LOCALIZATION NOTE (paused): — is the "em dash" (long dash)
+paused=Paused — #1
+downloading=Downloading
+notStarted=Not Started
+failed=Failed
+finished=Finished
+canceled=Canceled
+
+cannotPause=This download cannot be paused
+
+downloadErrorAlertTitle=Download Error
+downloadErrorGeneric=The download cannot be saved because an unknown error occurred.\n\nPlease try again.
+
+# LOCALIZATION NOTE: we don't have proper plural support in the CPP code; bug 463102
+quitCancelDownloadsAlertTitle=Cancel All Downloads?
+quitCancelDownloadsAlertMsg=If you exit now, 1 download will be canceled. Are you sure you want to exit?
+quitCancelDownloadsAlertMsgMultiple=If you exit now, %S downloads will be canceled. Are you sure you want to exit?
+quitCancelDownloadsAlertMsgMac=If you quit now, 1 download will be canceled. Are you sure you want to quit?
+quitCancelDownloadsAlertMsgMacMultiple=If you quit now, %S downloads will be canceled. Are you sure you want to quit?
+offlineCancelDownloadsAlertTitle=Cancel All Downloads?
+offlineCancelDownloadsAlertMsg=If you go offline now, 1 download will be canceled. Are you sure you want to go offline?
+offlineCancelDownloadsAlertMsgMultiple=If you go offline now, %S downloads will be canceled. Are you sure you want to go offline?
+leavePrivateBrowsingCancelDownloadsAlertTitle=Cancel All Downloads?
+leavePrivateBrowsingWindowsCancelDownloadsAlertMsg2=If you close all Private Browsing windows now, 1 download will be canceled. Are you sure you want to leave Private Browsing?
+leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2=If you close all Private Browsing windows now, %S downloads will be canceled. Are you sure you want to leave Private Browsing?
+cancelDownloadsOKText=Cancel 1 Download
+cancelDownloadsOKTextMultiple=Cancel %S Downloads
+dontQuitButtonWin=Don’t Exit
+dontQuitButtonMac=Don’t Quit
+dontGoOfflineButton=Stay Online
+dontLeavePrivateBrowsingButton2=Stay in Private Browsing
+downloadsCompleteTitle=Downloads Complete
+downloadsCompleteMsg=All files have finished downloading.
+
+# LOCALIZATION NOTE (infiniteRate):
+# If download speed is a JavaScript Infinity value, this phrase is used
+infiniteRate=Really fast
+
+# LOCALIZATION NOTE (statusFormat3): — is the "em dash" (long dash)
+# %1$S transfer progress; %2$S rate number; %3$S rate unit; %4$S time left
+# example: 4 minutes left — 1.1 of 11.1 GB (2.2 MB/sec)
+statusFormat3=%4$S — %1$S (%2$S %3$S/sec)
+
+# LOCALIZATION NOTE (statusFormatInfiniteRate): — is the "em dash" (long dash)
+# %1$S transfer progress; %2$S substitute phrase for Infinity speed; %3$S time left
+# example: 4 minutes left — 1.1 of 11.1 GB (Really fast)
+statusFormatInfiniteRate=%3$S — %1$S (%2$S)
+
+# LOCALIZATION NOTE (statusFormatNoRate): — is the "em dash" (long dash)
+# %1$S transfer progress; %2$S time left
+# example: 4 minutes left — 1.1 of 11.1 GB
+statusFormatNoRate=%2$S — %1$S
+
+bytes=bytes
+kilobyte=KB
+megabyte=MB
+gigabyte=GB
+
+# LOCALIZATION NOTE (transferSameUnits2):
+# %1$S progress number; %2$S total number; %3$S total unit
+# example: 1.1 of 333 MB
+transferSameUnits2=%1$S of %2$S %3$S
+# LOCALIZATION NOTE (transferDiffUnits2):
+# %1$S progress number; %2$S progress unit; %3$S total number; %4$S total unit
+# example: 11.1 MB of 3.3 GB
+transferDiffUnits2=%1$S %2$S of %3$S %4$S
+# LOCALIZATION NOTE (transferNoTotal2):
+# %1$S progress number; %2$S unit
+# example: 111 KB
+transferNoTotal2=%1$S %2$S
+
+# LOCALIZATION NOTE (timePair2): %1$S time number; %2$S time unit
+# example: 1 minute; 11 hours
+timePair2=%1$S %2$S
+# LOCALIZATION NOTE (timeLeftSingle2): %1$S time left
+# example: 1 minute remaining; 11 hours remaining
+timeLeftSingle2=%1$S remaining
+# LOCALIZATION NOTE (timeLeftDouble2): %1$S time left; %2$S time left sub units
+# example: 11 hours, 2 minutes remaining; 1 day, 22 hours remaining
+timeLeftDouble2=%1$S, %2$S remaining
+timeFewSeconds=A few seconds remaining
+timeUnknown=Unknown time remaining
+
+# LOCALIZATION NOTE (doneStatus): — is the "em dash" (long dash)
+# #1 download size for FINISHED or download state; #2 host (e.g., eTLD + 1, IP)
+# #2 can also be doneScheme or doneFileScheme for special URIs like file:
+# examples: 1.1 MB — website2.com; Canceled — 222.net
+doneStatus=#1 — #2
+# LOCALIZATION NOTE (doneSize): #1 size number; #2 size unit
+doneSize=#1 #2
+doneSizeUnknown=Unknown size
+# LOCALIZATION NOTE (doneScheme): #1 URI scheme like data: jar: about:
+doneScheme2=%1$S resource
+# LOCALIZATION NOTE (doneFileScheme): Special case of doneScheme for file:
+# This is used as an eTLD replacement for local files, so make it lower case
+doneFileScheme=local file
+
+stateFailed=Failed
+stateCanceled=Canceled
+# LOCALIZATION NOTE (stateBlocked): 'Parental Controls' should be capitalized
+stateBlocked=Blocked by Parental Controls
+stateDirty=Blocked: Download may contain a virus or spyware
+# LOCALIZATION NOTE (stateBlockedPolicy): 'Security Zone Policy' should be capitalized
+ stateBlockedPolicy=This download has been blocked by your Security Zone Policy
+
+# LOCALIZATION NOTE (yesterday): Displayed time for files finished yesterday
+yesterday=Yesterday
+# LOCALIZATION NOTE (monthDate): #1 month name; #2 date number; e.g., January 22
+monthDate2=%1$S %2$S
+
+fileDoesNotExistOpenTitle=Cannot Open %S
+fileDoesNotExistShowTitle=Cannot Show %S
+fileDoesNotExistError=%S does not exist. It may have been renamed, moved, or deleted since it was downloaded.
+
+chooseAppFilePickerTitle=Open With…
+
+# LOCALIZATION NOTE (downloadsTitleFiles, downloadsTitlePercent): Semi-colon list of
+# plural forms. See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 number of files; #2 overall download percent (only for downloadsTitlePercent)
+# examples: 2% of 1 file - Downloads; 22% of 11 files - Downloads
+downloadsTitleFiles=#1 file - Downloads;#1 files - Downloads
+downloadsTitlePercent=#2% of #1 file - Downloads;#2% of #1 files - Downloads
+
+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?
+
+displayNameDesktop=Desktop
+
+# Desktop folder name for downloaded files
+downloadsFolder=Downloads
diff --git a/components/downloads/locale/settingsChange.dtd b/components/downloads/locale/settingsChange.dtd
new file mode 100644
index 000000000..f28f7f341
--- /dev/null
+++ b/components/downloads/locale/settingsChange.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 settingsChangePreferences.label "Settings can be changed in &brandShortName;'s Preferences.">
+<!ENTITY settingsChangeOptions.label "Settings can be changed in &brandShortName;'s Options.">
diff --git a/components/downloads/locale/unknownContentType.dtd b/components/downloads/locale/unknownContentType.dtd
new file mode 100644
index 000000000..e0fbf7368
--- /dev/null
+++ b/components/downloads/locale/unknownContentType.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 intro2.label "You have chosen to open:">
+<!ENTITY from.label "from:">
+<!ENTITY actionQuestion.label "What should &brandShortName; do with this file?">
+
+<!ENTITY openWith.label "Open with">
+<!ENTITY openWith.accesskey "o">
+<!ENTITY other.label "Other…">
+
+<!ENTITY saveFile.label "Save File">
+<!ENTITY saveFile.accesskey "s">
+
+<!ENTITY rememberChoice.label "Do this automatically for files like this from now on.">
+<!ENTITY rememberChoice.accesskey "a">
+
+<!ENTITY whichIs.label "which is:">
+
+<!ENTITY chooseHandlerMac.label "Choose…">
+<!ENTITY chooseHandlerMac.accesskey "C">
+<!ENTITY chooseHandler.label "Browse…">
+<!ENTITY chooseHandler.accesskey "B">
+
+<!ENTITY unknownPromptText.label "Would you like to save this file?">
diff --git a/components/downloads/locale/unknownContentType.properties b/components/downloads/locale/unknownContentType.properties
new file mode 100644
index 000000000..e599133ce
--- /dev/null
+++ b/components/downloads/locale/unknownContentType.properties
@@ -0,0 +1,19 @@
+# -*- 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/.
+
+title=Opening %S
+saveDialogTitle=Enter name of file to save to…
+defaultApp=%S (default)
+chooseAppFilePickerTitle=Choose Helper Application
+badApp=The application you chose (“%Sâ€) could not be found. Check the file name or choose another application.
+badApp.title=Application not found
+badPermissions=The file could not be saved because you do not have the proper permissions. Choose another save directory.
+badPermissions.title=Invalid Save Permissions
+selectDownloadDir=Select Download Folder
+unknownAccept.label=Save File
+unknownCancel.label=Cancel
+fileType=%S file
+# LOCALIZATION NOTE (orderedFileSizeWithType): first %S is type, second %S is size, and third %S is unit
+orderedFileSizeWithType=%1$S (%2$S %3$S)
diff --git a/components/downloads/moz.build b/components/downloads/moz.build
new file mode 100644
index 000000000..ffd526570
--- /dev/null
+++ b/components/downloads/moz.build
@@ -0,0 +1,57 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
+DEFINES['GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER'] = True
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-shadow']
+
+XPIDL_SOURCES += [
+ 'public/nsIDownload.idl',
+ 'public/nsIDownloadManager.idl',
+ 'public/nsIDownloadManagerUI.idl',
+ 'public/nsIDownloadProgressListener.idl',
+]
+
+SOURCES += [
+ 'src/nsDownloadManager.cpp',
+ 'src/SQLFunctions.cpp',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += ['src/nsDownloadScanner.cpp']
+
+EXTRA_COMPONENTS += ['nsHelperAppDlg.manifest']
+
+EXTRA_PP_COMPONENTS += ['src/nsHelperAppDlg.js']
+
+# The Communicator Downloads Manager uses its own DownloadManagerUI
+# component and it can't be guaranteed that its implementation will override
+# toolkit's so don't include toolkit's
+if not CONFIG['BINOC_DOWNLOADS']:
+ EXTRA_COMPONENTS += [
+ 'nsDownloadManagerUI.manifest',
+ 'src/nsDownloadManagerUI.js',
+ ]
+
+EXTRA_JS_MODULES += [
+ 'src/DownloadLastDir.jsm',
+ 'src/DownloadPaths.jsm',
+ 'src/DownloadUtils.jsm',
+]
+
+EXTRA_PP_JS_MODULES += ['src/DownloadTaskbarProgress.jsm']
+
+LOCAL_INCLUDES += [
+ '/ipc/chromium/src',
+ '/libs/protobuf',
+]
+
+XPIDL_MODULE = 'downloads'
+FINAL_LIBRARY = 'xul'
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/downloads/nsDownloadManagerUI.manifest b/components/downloads/nsDownloadManagerUI.manifest
new file mode 100644
index 000000000..4073c23fb
--- /dev/null
+++ b/components/downloads/nsDownloadManagerUI.manifest
@@ -0,0 +1,2 @@
+component {7dfdf0d1-aff6-4a34-bad1-d0fe74601642} nsDownloadManagerUI.js
+contract @mozilla.org/download-manager-ui;1 {7dfdf0d1-aff6-4a34-bad1-d0fe74601642}
diff --git a/components/downloads/nsHelperAppDlg.manifest b/components/downloads/nsHelperAppDlg.manifest
new file mode 100644
index 000000000..8824b45a2
--- /dev/null
+++ b/components/downloads/nsHelperAppDlg.manifest
@@ -0,0 +1,2 @@
+component {F68578EB-6EC2-4169-AE19-8C6243F0ABE1} nsHelperAppDlg.js
+contract @mozilla.org/helperapplauncherdialog;1 {F68578EB-6EC2-4169-AE19-8C6243F0ABE1}
diff --git a/components/downloads/public/nsIDownload.idl b/components/downloads/public/nsIDownload.idl
new file mode 100644
index 000000000..47eb48780
--- /dev/null
+++ b/components/downloads/public/nsIDownload.idl
@@ -0,0 +1,175 @@
+/* -*- 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 "nsITransfer.idl"
+
+interface nsIURI;
+interface nsIFile;
+interface nsIObserver;
+interface nsICancelable;
+interface nsIWebProgressListener;
+interface nsIMIMEInfo;
+
+/**
+ * Represents a download object.
+ *
+ * @note This object is no longer updated once it enters a completed state.
+ * Completed states are the following:
+ * nsIDownloadManager::DOWNLOAD_FINISHED
+ * nsIDownloadManager::DOWNLOAD_FAILED
+ * nsIDownloadManager::DOWNLOAD_CANCELED
+ * nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL
+ * nsIDownloadManager::DOWNLOAD_DIRTY
+ * nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY
+ */
+[scriptable, uuid(2258f465-656e-4566-87cb-f791dbaf0322)]
+interface nsIDownload : nsITransfer {
+
+ /**
+ * The target of a download is always a file on the local file system.
+ */
+ readonly attribute nsIFile targetFile;
+
+ /**
+ * The percentage of transfer completed.
+ * If the file size is unknown it'll be -1 here.
+ */
+ readonly attribute long percentComplete;
+
+ /**
+ * The amount of bytes downloaded so far.
+ */
+ readonly attribute long long amountTransferred;
+
+ /**
+ * The size of file in bytes.
+ * Unknown size is represented by -1.
+ */
+ readonly attribute long long size;
+
+ /**
+ * The source of the transfer.
+ */
+ readonly attribute nsIURI source;
+
+ /**
+ * The target of the transfer.
+ */
+ readonly attribute nsIURI target;
+
+ /**
+ * Object that can be used to cancel the download.
+ * Will be null after the download is finished.
+ */
+ readonly attribute nsICancelable cancelable;
+
+ /**
+ * The user-readable description of the transfer.
+ */
+ readonly attribute AString displayName;
+
+ /**
+ * The time a transfer was started.
+ */
+ readonly attribute long long startTime;
+
+ /**
+ * The speed of the transfer in bytes/sec.
+ */
+ readonly attribute double speed;
+
+ /**
+ * Optional. If set, it will contain the target's relevant MIME information.
+ * This includes its MIME Type, helper app, and whether that helper should be
+ * executed.
+ */
+ readonly attribute nsIMIMEInfo MIMEInfo;
+
+ /**
+ * The id of the download that is stored in the database - not globally unique.
+ * For example, a private download and a public one might have identical ids.
+ * Can only be safely used for direct database manipulation in the database that
+ * contains this download. Use the guid property instead for safe, database-agnostic
+ * searching and manipulation.
+ *
+ * @deprecated
+ */
+ readonly attribute unsigned long id;
+
+ /**
+ * The guid of the download that is stored in the database.
+ * Has the form of twelve alphanumeric characters.
+ */
+ readonly attribute ACString guid;
+
+ /**
+ * The state of the download.
+ * @see nsIDownloadManager and nsIXPInstallManagerUI
+ */
+ readonly attribute short state;
+
+ /**
+ * The referrer uri of the download. This is only valid for HTTP downloads,
+ * and can be null.
+ */
+ readonly attribute nsIURI referrer;
+
+ /**
+ * Indicates if the download can be resumed after being paused or not. This
+ * is only the case if the download is over HTTP/1.1 or FTP and if the
+ * server supports it.
+ */
+ readonly attribute boolean resumable;
+
+ /**
+ * Indicates if the download was initiated from a context marked as private,
+ * controlling whether it should be stored in a permanent manner or not.
+ */
+ readonly attribute boolean isPrivate;
+
+ /**
+ * Cancel this download if it's currently in progress.
+ */
+ void cancel();
+
+ /**
+ * Pause this download if it is in progress.
+ *
+ * @throws NS_ERROR_UNEXPECTED if it cannot be paused.
+ */
+ void pause();
+
+ /**
+ * Resume this download if it is paused.
+ *
+ * @throws NS_ERROR_UNEXPECTED if it cannot be resumed or is not paused.
+ */
+ void resume();
+
+ /**
+ * Instruct the download manager to remove this download. Whereas
+ * cancel simply cancels the transfer, but retains information about it,
+ * remove removes all knowledge of it.
+ *
+ * @see nsIDownloadManager.removeDownload for more detail
+ * @throws NS_ERROR_FAILURE if the download is active.
+ */
+ void remove();
+
+ /**
+ * Instruct the download manager to retry this failed download
+ * @throws NS_ERROR_NOT_AVAILABLE if the download is not known.
+ * @throws NS_ERROR_FAILURE if the download is not in the following states:
+ * nsIDownloadManager::DOWNLOAD_CANCELED
+ * nsIDownloadManager::DOWNLOAD_FAILED
+ */
+ void retry();
+};
+
+%{C++
+// {b02be33b-d47c-4bd3-afd9-402a942426b0}
+#define NS_DOWNLOAD_CID \
+ { 0xb02be33b, 0xd47c, 0x4bd3, { 0xaf, 0xd9, 0x40, 0x2a, 0x94, 0x24, 0x26, 0xb0 } }
+%}
diff --git a/components/downloads/public/nsIDownloadManager.idl b/components/downloads/public/nsIDownloadManager.idl
new file mode 100644
index 000000000..d7eba8940
--- /dev/null
+++ b/components/downloads/public/nsIDownloadManager.idl
@@ -0,0 +1,358 @@
+/* -*- 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/. */
+
+// Keeps track of ongoing downloads, in the form of nsIDownload's.
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIFile;
+interface nsIDownload;
+interface nsICancelable;
+interface nsIMIMEInfo;
+interface nsIDownloadProgressListener;
+interface nsISimpleEnumerator;
+interface mozIStorageConnection;
+
+[scriptable, function, uuid(0c07ffeb-791b-49f3-ae38-2c331fd55a52)]
+interface nsIDownloadManagerResult : nsISupports {
+ /**
+ * Process an asynchronous result from getDownloadByGUID.
+ *
+ * @param aStatus The result code of the operation:
+ * * NS_OK: an item was found. No other success values are returned.
+ * * NS_ERROR_NOT_AVAILABLE: no such item was found.
+ * * Other error values are possible, but less well-defined.
+ */
+ void handleResult(in nsresult aStatus, in nsIDownload aDownload);
+};
+
+[scriptable, uuid(b29aac15-7ec4-4ab3-a53b-08f78aed3b34)]
+interface nsIDownloadManager : nsISupports {
+ /**
+ * Download type for generic file download.
+ */
+ const short DOWNLOAD_TYPE_DOWNLOAD = 0;
+
+ /**
+ * Download state for uninitialized download object.
+ */
+ const short DOWNLOAD_NOTSTARTED = -1;
+
+ /**
+ * Download is currently transferring data.
+ */
+ const short DOWNLOAD_DOWNLOADING = 0;
+
+ /**
+ * Download completed including any processing of the target
+ * file. (completed)
+ */
+ const short DOWNLOAD_FINISHED = 1;
+
+ /**
+ * Transfer failed due to error. (completed)
+ */
+ const short DOWNLOAD_FAILED = 2;
+
+ /**
+ * Download was canceled by the user. (completed)
+ */
+ const short DOWNLOAD_CANCELED = 3;
+
+ /**
+ * Transfer was paused by the user.
+ */
+ const short DOWNLOAD_PAUSED = 4;
+
+ /**
+ * Download is active but data has not yet been received.
+ */
+ const short DOWNLOAD_QUEUED = 5;
+
+ /**
+ * Transfer request was blocked by parental controls proxies. (completed)
+ */
+ const short DOWNLOAD_BLOCKED_PARENTAL = 6;
+
+ /**
+ * Transferred download is being scanned by virus scanners.
+ */
+ const short DOWNLOAD_SCANNING = 7;
+
+ /**
+ * A virus was detected in the download. The target will most likely
+ * no longer exist. (completed)
+ */
+ const short DOWNLOAD_DIRTY = 8;
+
+ /**
+ * Win specific: Request was blocked by zone policy settings.
+ * (see bug #416683) (completed)
+ */
+ const short DOWNLOAD_BLOCKED_POLICY = 9;
+
+
+ /**
+ * Creates an nsIDownload and adds it to be managed by the download manager.
+ *
+ * @param aSource The source URI of the transfer. Must not be null.
+ *
+ * @param aTarget The target URI of the transfer. Must not be null.
+ *
+ * @param aDisplayName The user-readable description of the transfer.
+ * Can be empty.
+ *
+ * @param aMIMEInfo The MIME info associated with the target,
+ * including MIME type and helper app when appropriate.
+ * This parameter is optional.
+ *
+ * @param startTime Time when the download started
+ *
+ * @param aTempFile The location of a temporary file; i.e. a file in which
+ * the received data will be stored, but which is not
+ * equal to the target file. (will be moved to the real
+ * target by the DownloadManager, when the download is
+ * finished). This will be null for all callers except for
+ * nsExternalHelperAppHandler. Addons should generally pass
+ * null for aTempFile. This will be moved to the real target
+ * by the download manager when the download is finished,
+ * and the action indicated by aMIMEInfo will be executed.
+ *
+ * @param aCancelable An object that can be used to abort the download.
+ * Must not be null.
+ *
+ * @param aIsPrivate Used to determine the privacy status of the new download.
+ * If true, the download is stored in a manner that leaves
+ * no permanent trace outside of the current private session.
+ *
+ * @return The newly created download item with the passed-in properties.
+ *
+ * @note This does not actually start a download. If you want to add and
+ * start a download, you need to create an nsIWebBrowserPersist, pass it
+ * as the aCancelable object, call this method, set the progressListener
+ * as the returned download object, then call saveURI.
+ */
+ nsIDownload addDownload(in short aDownloadType,
+ in nsIURI aSource,
+ in nsIURI aTarget,
+ in AString aDisplayName,
+ in nsIMIMEInfo aMIMEInfo,
+ in PRTime aStartTime,
+ in nsIFile aTempFile,
+ in nsICancelable aCancelable,
+ in boolean aIsPrivate);
+
+ /**
+ * Retrieves a download managed by the download manager. This can be one that
+ * is in progress, or one that has completed in the past and is stored in the
+ * database.
+ *
+ * @param aID The unique ID of the download.
+ * @return The download with the specified ID.
+ * @throws NS_ERROR_NOT_AVAILABLE if the download is not in the database.
+ */
+ nsIDownload getDownload(in unsigned long aID);
+
+ /**
+ * Retrieves a download managed by the download manager. This can be one that
+ * is in progress, or one that has completed in the past and is stored in the
+ * database. The result of this method is returned via an asynchronous callback,
+ * the parameter of which will be an nsIDownload object, or null if none exists
+ * with the provided GUID.
+ *
+ * @param aGUID The unique GUID of the download.
+ * @param aCallback The callback to invoke with the result of the search.
+ */
+ void getDownloadByGUID(in ACString aGUID, in nsIDownloadManagerResult aCallback);
+
+ /**
+ * Cancels the download with the specified ID if it's currently in-progress.
+ * This calls cancel(NS_BINDING_ABORTED) on the nsICancelable provided by the
+ * download.
+ *
+ * @param aID The unique ID of the download.
+ * @throws NS_ERROR_FAILURE if the download is not in-progress.
+ */
+ void cancelDownload(in unsigned long aID);
+
+ /**
+ * Removes the download with the specified id if it's not currently
+ * in-progress. Whereas cancelDownload simply cancels the transfer, but
+ * retains information about it, removeDownload removes all knowledge of it.
+ *
+ * Also notifies observers of the "download-manager-remove-download-guid"
+ * topic with the download guid as the subject to allow any DM consumers to
+ * react to the removal.
+ *
+ * Also may notify observers of the "download-manager-remove-download" topic
+ * with the download id as the subject, if the download removed is public
+ * or if global private browsing mode is in use. This notification is deprecated;
+ * the guid notification should be relied upon instead.
+ *
+ * @param aID The unique ID of the download.
+ * @throws NS_ERROR_FAILURE if the download is active.
+ */
+ void removeDownload(in unsigned long aID);
+
+ /**
+ * Removes all inactive downloads that were started inclusively within the
+ * specified time frame.
+ *
+ * @param aBeginTime
+ * The start time to remove downloads by in microseconds.
+ * @param aEndTime
+ * The end time to remove downloads by in microseconds.
+ */
+ void removeDownloadsByTimeframe(in long long aBeginTime,
+ in long long aEndTime);
+
+ /**
+ * Pause the specified download.
+ *
+ * @param aID The unique ID of the download.
+ * @throws NS_ERROR_FAILURE if the download is not in-progress.
+ */
+ void pauseDownload(in unsigned long aID);
+
+ /**
+ * Resume the specified download.
+ *
+ * @param aID The unique ID of the download.
+ * @throws NS_ERROR_FAILURE if the download is not in-progress.
+ */
+ void resumeDownload(in unsigned long aID);
+
+ /**
+ * Retries a failed download.
+ *
+ * @param aID The unique ID of the download.
+ * @throws NS_ERROR_NOT_AVAILALE if the download id is not known.
+ * @throws NS_ERROR_FAILURE if the download is not in the following states:
+ * nsIDownloadManager::DOWNLOAD_CANCELED
+ * nsIDownloadManager::DOWNLOAD_FAILED
+ */
+ void retryDownload(in unsigned long aID);
+
+ /**
+ * The database connection to the downloads database.
+ */
+ readonly attribute mozIStorageConnection DBConnection;
+ readonly attribute mozIStorageConnection privateDBConnection;
+
+ /**
+ * Whether or not there are downloads that can be cleaned up (removed)
+ * i.e. downloads that have completed, have failed or have been canceled.
+ * In global private browsing mode, this reports the status of the relevant
+ * private or public downloads. In per-window mode, it only reports for
+ * public ones.
+ */
+ readonly attribute boolean canCleanUp;
+
+ /**
+ * Whether or not there are private downloads that can be cleaned up (removed)
+ * i.e. downloads that have completed, have failed or have been canceled.
+ */
+readonly attribute boolean canCleanUpPrivate;
+
+ /**
+ * Removes completed, failed, and canceled downloads from the list.
+ * In global private browsing mode, this operates on the relevant
+ * private or public downloads. In per-window mode, it only operates
+ * on public ones.
+ *
+ * Also notifies observers of the "download-manager-remove-download-gui"
+ * and "download-manager-remove-download" topics with a null subject to
+ * allow any DM consumers to react to the removals.
+ */
+ void cleanUp();
+
+ /**
+ * Removes completed, failed, and canceled downloads from the list
+ * of private downloads.
+ *
+ * Also notifies observers of the "download-manager-remove-download-gui"
+ * and "download-manager-remove-download" topics with a null subject to
+ * allow any DM consumers to react to the removals.
+ */
+void cleanUpPrivate();
+
+ /**
+ * The number of files currently being downloaded.
+ *
+ * In global private browsing mode, this reports the status of the relevant
+ * private or public downloads. In per-window mode, it only reports public
+ * ones.
+ */
+ readonly attribute long activeDownloadCount;
+
+ /**
+ * The number of private files currently being downloaded.
+ */
+ readonly attribute long activePrivateDownloadCount;
+
+ /**
+ * An enumeration of active nsIDownloads
+ *
+ * In global private browsing mode, this reports the status of the relevant
+ * private or public downloads. In per-window mode, it only reports public
+ * ones.
+ */
+ readonly attribute nsISimpleEnumerator activeDownloads;
+
+ /**
+ * An enumeration of active private nsIDownloads
+ */
+ readonly attribute nsISimpleEnumerator activePrivateDownloads;
+
+ /**
+ * Adds a listener to the download manager. It is expected that this
+ * listener will only access downloads via their deprecated integer id attribute,
+ * and when global private browsing compatibility mode is disabled, this listener
+ * will receive no notifications for downloads marked private.
+ */
+ void addListener(in nsIDownloadProgressListener aListener);
+
+ /**
+ * Adds a listener to the download manager. This listener must be able to
+ * understand and use the guid attribute of downloads for all interactions
+ * with the download manager.
+ */
+ void addPrivacyAwareListener(in nsIDownloadProgressListener aListener);
+
+ /**
+ * Removes a listener from the download manager.
+ */
+ void removeListener(in nsIDownloadProgressListener aListener);
+
+ /**
+ * Returns the platform default downloads directory.
+ */
+ readonly attribute nsIFile defaultDownloadsDirectory;
+
+ /**
+ * Returns the user configured downloads directory.
+ * The path is dependent on two user configurable prefs
+ * set in preferences:
+ *
+ * browser.download.folderList
+ * Indicates the location users wish to save downloaded
+ * files too.
+ * 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. If invalid, userDownloadsDirectory
+ * will fallback on defaultDownloadsDirectory.
+ *
+ * browser.download.dir -
+ * A local path the user may have selected at some point
+ * where downloaded files are saved. The use of which is
+ * enabled when folderList equals 2.
+ */
+ readonly attribute nsIFile userDownloadsDirectory;
+};
+
+
diff --git a/components/downloads/public/nsIDownloadManagerUI.idl b/components/downloads/public/nsIDownloadManagerUI.idl
new file mode 100644
index 000000000..b5ceff5b0
--- /dev/null
+++ b/components/downloads/public/nsIDownloadManagerUI.idl
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIInterfaceRequestor;
+interface nsIDownload;
+
+[scriptable, uuid(0c76d4cf-0b06-4c1a-9bea-520c7bbdba99)]
+interface nsIDownloadManagerUI : nsISupports {
+ /**
+ * The reason that should be passed when the user requests to show the
+ * download manager's UI.
+ */
+ const short REASON_USER_INTERACTED = 0;
+
+ /**
+ * The reason that should be passed to the show method when we are displaying
+ * the UI because a new download is being added to it.
+ */
+ const short REASON_NEW_DOWNLOAD = 1;
+
+ /**
+ * Shows the Download Manager's UI to the user.
+ *
+ * @param [optional] aWindowContext
+ * The parent window context to show the UI.
+ * @param [optional] aDownload
+ * The download to be preselected upon opening.
+ * @param [optional] aReason
+ * The reason to show the download manager's UI. This defaults to
+ * REASON_USER_INTERACTED, and should be one of the previously listed
+ * constants.
+ * @param [optional] aUsePrivateUI
+ * Pass true as this argument to hint to the implementation that it
+ * should only display private downloads in the UI, if possible.
+ */
+ void show([optional] in nsIInterfaceRequestor aWindowContext,
+ [optional] in nsIDownload aDownload,
+ [optional] in short aReason,
+ [optional] in boolean aUsePrivateUI);
+
+ /**
+ * Indicates if the UI is visible or not.
+ */
+ readonly attribute boolean visible;
+
+ /**
+ * Brings attention to the UI if it is already visible
+ *
+ * @throws NS_ERROR_UNEXPECTED if the UI is not visible.
+ */
+ void getAttention();
+};
+
diff --git a/components/downloads/public/nsIDownloadProgressListener.idl b/components/downloads/public/nsIDownloadProgressListener.idl
new file mode 100644
index 000000000..e406f64d6
--- /dev/null
+++ b/components/downloads/public/nsIDownloadProgressListener.idl
@@ -0,0 +1,60 @@
+/* -*- 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/. */
+
+/* A minimally extended progress listener used by download manager
+ * to update its default UI. This is implemented in nsDownloadProgressListener.js.
+ * See nsIWebProgressListener for documentation, and use its constants. This isn't
+ * too pretty, but the alternative is having this extend nsIWebProgressListener and
+ * adding an |item| attribute, which would mean a separate nsIDownloadProgressListener
+ * for every nsIDownloadItem, which is a waste...
+ */
+
+#include "nsISupports.idl"
+
+interface nsIWebProgress;
+interface nsIRequest;
+interface nsIURI;
+interface nsIDownload;
+interface nsIDOMDocument;
+
+[scriptable, uuid(7acb07ea-cac2-4c15-a3ad-23aaa789ed51)]
+interface nsIDownloadProgressListener : nsISupports {
+
+ /**
+ * document
+ * The document of the download manager frontend.
+ */
+
+ attribute nsIDOMDocument document;
+
+ /**
+ * Dispatched whenever the state of the download changes.
+ *
+ * @param aState The previous download sate.
+ * @param aDownload The download object.
+ * @see nsIDownloadManager for download states.
+ */
+ void onDownloadStateChange(in short aState, in nsIDownload aDownload);
+
+ void onStateChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in unsigned long aStateFlags,
+ in nsresult aStatus,
+ in nsIDownload aDownload);
+
+ void onProgressChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in long long aCurSelfProgress,
+ in long long aMaxSelfProgress,
+ in long long aCurTotalProgress,
+ in long long aMaxTotalProgress,
+ in nsIDownload aDownload);
+
+ void onSecurityChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in unsigned long aState,
+ in nsIDownload aDownload);
+
+};
diff --git a/components/downloads/src/DownloadLastDir.jsm b/components/downloads/src/DownloadLastDir.jsm
new file mode 100644
index 000000000..552fd3ef1
--- /dev/null
+++ b/components/downloads/src/DownloadLastDir.jsm
@@ -0,0 +1,195 @@
+/* -*- 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/. */
+
+/*
+ * The behavior implemented by gDownloadLastDir is documented here.
+ *
+ * In normal browsing sessions, gDownloadLastDir uses the browser.download.lastDir
+ * preference to store the last used download directory. The first time the user
+ * switches into the private browsing mode, the last download directory is
+ * preserved to the pref value, but if the user switches to another directory
+ * during the private browsing mode, that directory is not stored in the pref,
+ * and will be merely kept in memory. When leaving the private browsing mode,
+ * this in-memory value will be discarded, and the last download directory
+ * will be reverted to the pref value.
+ *
+ * Both the pref and the in-memory value will be cleared when clearing the
+ * browsing history. This effectively changes the last download directory
+ * to the default download directory on each platform.
+ *
+ * If passed a URI, the last used directory is also stored with that URI in the
+ * content preferences database. This can be disabled by setting the pref
+ * browser.download.lastDir.savePerSite to false.
+ */
+
+const LAST_DIR_PREF = "browser.download.lastDir";
+const SAVE_PER_SITE_PREF = LAST_DIR_PREF + ".savePerSite";
+const nsIFile = Components.interfaces.nsIFile;
+
+this.EXPORTED_SYMBOLS = [ "DownloadLastDir" ];
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+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":
+ gDownloadLastDirFile = null;
+ break;
+ case "browser:purge-session-history":
+ gDownloadLastDirFile = null;
+ if (Services.prefs.prefHasUserValue(LAST_DIR_PREF))
+ Services.prefs.clearUserPref(LAST_DIR_PREF);
+ // Ensure that purging session history causes both the session-only PB cache
+ // and persistent prefs to be cleared.
+ let cps2 = Components.classes["@mozilla.org/content-pref/service;1"].
+ getService(Components.interfaces.nsIContentPrefService2);
+
+ cps2.removeByName(LAST_DIR_PREF, {usePrivateBrowsing: false});
+ cps2.removeByName(LAST_DIR_PREF, {usePrivateBrowsing: true});
+ 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);
+
+function readLastDirPref() {
+ try {
+ return Services.prefs.getComplexValue(LAST_DIR_PREF, nsIFile);
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+function isContentPrefEnabled() {
+ try {
+ return Services.prefs.getBoolPref(SAVE_PER_SITE_PREF);
+ }
+ catch (e) {
+ return true;
+ }
+}
+
+var gDownloadLastDirFile = readLastDirPref();
+
+this.DownloadLastDir = function DownloadLastDir(aWindow) {
+ let loadContext = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext);
+ // Need this in case the real thing has gone away by the time we need it.
+ // We only care about the private browsing state. All the rest of the
+ // load context isn't of interest to the content pref service.
+ this.fakeContext = {
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsILoadContext]),
+ usePrivateBrowsing: loadContext.usePrivateBrowsing,
+ originAttributes: {},
+ };
+}
+
+DownloadLastDir.prototype = {
+ isPrivate: function DownloadLastDir_isPrivate() {
+ return this.fakeContext.usePrivateBrowsing;
+ },
+ // compat shims
+ get file() { return this._getLastFile(); },
+ set file(val) { this.setFile(null, val); },
+ cleanupPrivateFile: function () {
+ gDownloadLastDirFile = null;
+ },
+ // This function is now deprecated as it uses the sync nsIContentPrefService
+ // interface. New consumers should use the getFileAsync function.
+ getFile: function (aURI) {
+ let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
+ Deprecated.warning("DownloadLastDir.getFile is deprecated. Please use getFileAsync instead.",
+ "https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/DownloadLastDir.jsm",
+ Components.stack.caller);
+
+ if (aURI && isContentPrefEnabled()) {
+ let lastDir = Services.contentPrefs.getPref(aURI, LAST_DIR_PREF, this.fakeContext);
+ if (lastDir) {
+ var lastDirFile = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsIFile);
+ lastDirFile.initWithPath(lastDir);
+ return lastDirFile;
+ }
+ }
+ return this._getLastFile();
+ },
+
+ _getLastFile: function () {
+ if (gDownloadLastDirFile && !gDownloadLastDirFile.exists())
+ gDownloadLastDirFile = null;
+
+ if (this.isPrivate()) {
+ if (!gDownloadLastDirFile)
+ gDownloadLastDirFile = readLastDirPref();
+ return gDownloadLastDirFile;
+ }
+ return readLastDirPref();
+ },
+
+ getFileAsync: function(aURI, aCallback) {
+ let plainPrefFile = this._getLastFile();
+ if (!aURI || !isContentPrefEnabled()) {
+ Services.tm.mainThread.dispatch(() => aCallback(plainPrefFile),
+ Components.interfaces.nsIThread.DISPATCH_NORMAL);
+ return;
+ }
+
+ let uri = aURI instanceof Components.interfaces.nsIURI ? aURI.spec : aURI;
+ let cps2 = Components.classes["@mozilla.org/content-pref/service;1"]
+ .getService(Components.interfaces.nsIContentPrefService2);
+ let result = null;
+ cps2.getByDomainAndName(uri, LAST_DIR_PREF, this.fakeContext, {
+ handleResult: aResult => result = aResult,
+ handleCompletion: function(aReason) {
+ let file = plainPrefFile;
+ if (aReason == Components.interfaces.nsIContentPrefCallback2.COMPLETE_OK &&
+ result instanceof Components.interfaces.nsIContentPref) {
+ file = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsIFile);
+ file.initWithPath(result.value);
+ }
+ aCallback(file);
+ }
+ });
+ },
+
+ setFile: function (aURI, aFile) {
+ if (aURI && isContentPrefEnabled()) {
+ let uri = aURI instanceof Components.interfaces.nsIURI ? aURI.spec : aURI;
+ let cps2 = Components.classes["@mozilla.org/content-pref/service;1"]
+ .getService(Components.interfaces.nsIContentPrefService2);
+ if (aFile instanceof Components.interfaces.nsIFile)
+ cps2.set(uri, LAST_DIR_PREF, aFile.path, this.fakeContext);
+ else
+ cps2.removeByDomainAndName(uri, LAST_DIR_PREF, this.fakeContext);
+ }
+ if (this.isPrivate()) {
+ if (aFile instanceof Components.interfaces.nsIFile)
+ gDownloadLastDirFile = aFile.clone();
+ else
+ gDownloadLastDirFile = null;
+ } else if (aFile instanceof Components.interfaces.nsIFile) {
+ Services.prefs.setComplexValue(LAST_DIR_PREF, nsIFile, aFile);
+ } else if (Services.prefs.prefHasUserValue(LAST_DIR_PREF)) {
+ Services.prefs.clearUserPref(LAST_DIR_PREF);
+ }
+ }
+};
diff --git a/components/downloads/src/DownloadPaths.jsm b/components/downloads/src/DownloadPaths.jsm
new file mode 100644
index 000000000..202e42487
--- /dev/null
+++ b/components/downloads/src/DownloadPaths.jsm
@@ -0,0 +1,88 @@
+/* -*- 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/. */
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadPaths",
+];
+
+/**
+ * This module provides the DownloadPaths object which contains methods for
+ * giving names and paths to files being downloaded.
+ *
+ * List of methods:
+ *
+ * nsILocalFile
+ * createNiceUniqueFile(nsILocalFile aLocalFile)
+ *
+ * [string base, string ext]
+ * splitBaseNameAndExtension(string aLeafName)
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+this.DownloadPaths = {
+ /**
+ * Creates a uniquely-named file starting from the name of the provided file.
+ * If a file with the provided name already exists, the function attempts to
+ * create nice alternatives, like "base(1).ext" (instead of "base-1.ext").
+ *
+ * If a unique name cannot be found, the function throws the XPCOM exception
+ * NS_ERROR_FILE_TOO_BIG. Other exceptions, like NS_ERROR_FILE_ACCESS_DENIED,
+ * can also be expected.
+ *
+ * @param aTemplateFile
+ * nsILocalFile whose leaf name is going to be used as a template. The
+ * provided object is not modified.
+ * @returns A new instance of an nsILocalFile object pointing to the newly
+ * created empty file. On platforms that support permission bits, the
+ * file is created with permissions 644.
+ */
+ createNiceUniqueFile: function DP_createNiceUniqueFile(aTemplateFile) {
+ // Work on a clone of the provided template file object.
+ var curFile = aTemplateFile.clone().QueryInterface(Ci.nsILocalFile);
+ var [base, ext] = DownloadPaths.splitBaseNameAndExtension(curFile.leafName);
+ // Try other file names, for example "base(1).txt" or "base(1).tar.gz",
+ // only if the file name initially set already exists.
+ for (let i = 1; i < 10000 && curFile.exists(); i++) {
+ curFile.leafName = base + "(" + i + ")" + ext;
+ }
+ // At this point we hand off control to createUnique, which will create the
+ // file with the name we chose, if it is valid. If not, createUnique will
+ // attempt to modify it again, for example it will shorten very long names
+ // that can't be created on some platforms, and for which a normal call to
+ // nsIFile.create would result in NS_ERROR_FILE_NOT_FOUND. This can result
+ // very rarely in strange names like "base(9999).tar-1.gz" or "ba-1.gz".
+ curFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ return curFile;
+ },
+
+ /**
+ * Separates the base name from the extension in a file name, recognizing some
+ * double extensions like ".tar.gz".
+ *
+ * @param aLeafName
+ * The full leaf name to be parsed. Be careful when processing names
+ * containing leading or trailing dots or spaces.
+ * @returns [base, ext]
+ * The base name of the file, which can be empty, and its extension,
+ * which always includes the leading dot unless it's an empty string.
+ * Concatenating the two items always results in the original name.
+ */
+ splitBaseNameAndExtension: function DP_splitBaseNameAndExtension(aLeafName) {
+ // The following regular expression is built from these key parts:
+ // .*? Matches the base name non-greedily.
+ // \.[A-Z0-9]{1,3} Up to three letters or numbers preceding a
+ // double extension.
+ // \.(?:gz|bz2|Z) The second part of common double extensions.
+ // \.[^.]* Matches any extension or a single trailing dot.
+ var [, base, ext] = /(.*?)(\.[A-Z0-9]{1,3}\.(?:gz|bz2|Z)|\.[^.]*)?$/i
+ .exec(aLeafName);
+ // Return an empty string instead of undefined if no extension is found.
+ return [base, ext || ""];
+ }
+};
diff --git a/components/downloads/src/DownloadTaskbarProgress.jsm b/components/downloads/src/DownloadTaskbarProgress.jsm
new file mode 100644
index 000000000..0264005e0
--- /dev/null
+++ b/components/downloads/src/DownloadTaskbarProgress.jsm
@@ -0,0 +1,400 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=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.EXPORTED_SYMBOLS = [
+ "DownloadTaskbarProgress",
+];
+
+// Constants
+
+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, "Services",
+ "resource://gre/modules/Services.jsm");
+
+const kTaskbarIDWin = "@mozilla.org/windows-taskbar;1";
+const kTaskbarIDMac = "@mozilla.org/widget/macdocksupport;1";
+
+// DownloadTaskbarProgress Object
+
+this.DownloadTaskbarProgress =
+{
+ init: function DTP_init()
+ {
+ if (DownloadTaskbarProgressUpdater) {
+ DownloadTaskbarProgressUpdater._init();
+ }
+ },
+
+ /**
+ * Called when a browser window appears. This has an effect only when we
+ * don't already have an active window.
+ *
+ * @param aWindow
+ * The browser window that we'll potentially use to display the
+ * progress.
+ */
+ onBrowserWindowLoad: function DTP_onBrowserWindowLoad(aWindow)
+ {
+ this.init();
+ if (!DownloadTaskbarProgressUpdater) {
+ return;
+ }
+ if (!DownloadTaskbarProgressUpdater._activeTaskbarProgress) {
+ DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, false);
+ }
+ },
+
+ /**
+ * Called when the download window appears. The download window will take
+ * over as the active window.
+ */
+ onDownloadWindowLoad: function DTP_onDownloadWindowLoad(aWindow)
+ {
+ this.init();
+ if (!DownloadTaskbarProgressUpdater) {
+ return;
+ }
+ DownloadTaskbarProgressUpdater._setActiveWindow(aWindow, true);
+ },
+
+ /**
+ * Getters for internal DownloadTaskbarProgressUpdater values
+ */
+
+ get activeTaskbarProgress() {
+ if (!DownloadTaskbarProgressUpdater) {
+ return null;
+ }
+ return DownloadTaskbarProgressUpdater._activeTaskbarProgress;
+ },
+
+ get activeWindowIsDownloadWindow() {
+ if (!DownloadTaskbarProgressUpdater) {
+ return null;
+ }
+ return DownloadTaskbarProgressUpdater._activeWindowIsDownloadWindow;
+ },
+
+ get taskbarState() {
+ if (!DownloadTaskbarProgressUpdater) {
+ return null;
+ }
+ return DownloadTaskbarProgressUpdater._taskbarState;
+ },
+
+};
+
+// DownloadTaskbarProgressUpdater Object
+
+var DownloadTaskbarProgressUpdater =
+{
+ // / Whether the taskbar is initialized.
+ _initialized: false,
+
+ // / Reference to the taskbar.
+ _taskbar: null,
+
+ // / Reference to the download manager.
+ _dm: null,
+
+ /**
+ * Initialize and register ourselves as a download progress listener.
+ */
+ _init: function DTPU_init()
+ {
+ if (this._initialized) {
+ return; // Already initialized
+ }
+ this._initialized = true;
+
+ if (kTaskbarIDWin in Cc) {
+ this._taskbar = Cc[kTaskbarIDWin].getService(Ci.nsIWinTaskbar);
+ if (!this._taskbar.available) {
+ // The Windows version is probably too old
+ DownloadTaskbarProgressUpdater = null;
+ return;
+ }
+ } else if (kTaskbarIDMac in Cc) {
+ this._activeTaskbarProgress = Cc[kTaskbarIDMac].
+ getService(Ci.nsITaskbarProgress);
+ } else {
+ DownloadTaskbarProgressUpdater = null;
+ return;
+ }
+
+ this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS;
+
+ this._dm = Cc["@mozilla.org/download-manager;1"].
+ getService(Ci.nsIDownloadManager);
+ this._dm.addPrivacyAwareListener(this);
+
+ this._os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ this._os.addObserver(this, "quit-application-granted", false);
+
+ this._updateStatus();
+ // onBrowserWindowLoad/onDownloadWindowLoad are going to set the active
+ // window, so don't do it here.
+ },
+
+ /**
+ * Unregisters ourselves as a download progress listener.
+ */
+ _uninit: function DTPU_uninit() {
+ this._dm.removeListener(this);
+ this._os.removeObserver(this, "quit-application-granted");
+ this._activeTaskbarProgress = null;
+ this._initialized = false;
+ },
+
+ /**
+ * This holds a reference to the taskbar progress for the window we're
+ * working with. This window would preferably be download window, but can be
+ * another window if it isn't open.
+ */
+ _activeTaskbarProgress: null,
+
+ // / Whether the active window is the download window
+ _activeWindowIsDownloadWindow: false,
+
+ /**
+ * Sets the active window, and whether it's the download window. This takes
+ * care of clearing out the previous active window's taskbar item, updating
+ * the taskbar, and setting an onunload listener.
+ *
+ * @param aWindow
+ * The window to set as active.
+ * @param aIsDownloadWindow
+ * Whether this window is a download window.
+ */
+ _setActiveWindow: function DTPU_setActiveWindow(aWindow, aIsDownloadWindow)
+ {
+#ifdef XP_WIN
+ // Clear out the taskbar for the old active window. (If there was no active
+ // window, this is a no-op.)
+ this._clearTaskbar();
+
+ this._activeWindowIsDownloadWindow = aIsDownloadWindow;
+ if (aWindow) {
+ // Get the taskbar progress for this window
+ let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShellTreeItem).treeOwner.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIXULWindow).docShell;
+ let taskbarProgress = this._taskbar.getTaskbarProgress(docShell);
+ this._activeTaskbarProgress = taskbarProgress;
+
+ this._updateTaskbar();
+ // _onActiveWindowUnload is idempotent, so we don't need to check whether
+ // we've already set this before or not.
+ aWindow.addEventListener("unload", function () {
+ DownloadTaskbarProgressUpdater._onActiveWindowUnload(taskbarProgress);
+ }, false);
+ }
+ else {
+ this._activeTaskbarProgress = null;
+ }
+#endif
+ },
+
+ // / Current state displayed on the active window's taskbar item
+ _taskbarState: null,
+ _totalSize: 0,
+ _totalTransferred: 0,
+
+ _shouldSetState: function DTPU_shouldSetState()
+ {
+#ifdef XP_WIN
+ // If the active window is not the download manager window, set the state
+ // only if it is normal or indeterminate.
+ return this._activeWindowIsDownloadWindow ||
+ (this._taskbarState == Ci.nsITaskbarProgress.STATE_NORMAL ||
+ this._taskbarState == Ci.nsITaskbarProgress.STATE_INDETERMINATE);
+#else
+ return true;
+#endif
+ },
+
+ /**
+ * Update the active window's taskbar indicator with the current state. There
+ * are two cases here:
+ * 1. If the active window is the download window, then we always update
+ * the taskbar indicator.
+ * 2. If the active window isn't the download window, then we update only if
+ * the status is normal or indeterminate. i.e. one or more downloads are
+ * currently progressing or in scan mode. If we aren't, then we clear the
+ * indicator.
+ */
+ _updateTaskbar: function DTPU_updateTaskbar()
+ {
+ if (!this._activeTaskbarProgress) {
+ return;
+ }
+
+ if (this._shouldSetState()) {
+ this._activeTaskbarProgress.setProgressState(this._taskbarState,
+ this._totalTransferred,
+ this._totalSize);
+ }
+ // Clear any state otherwise
+ else {
+ this._clearTaskbar();
+ }
+ },
+
+ /**
+ * Clear taskbar state. This is needed:
+ * - to transfer the indicator off a window before transferring it onto
+ * another one
+ * - whenever we don't want to show it for a non-download window.
+ */
+ _clearTaskbar: function DTPU_clearTaskbar()
+ {
+ if (this._activeTaskbarProgress) {
+ this._activeTaskbarProgress.setProgressState(
+ Ci.nsITaskbarProgress.STATE_NO_PROGRESS
+ );
+ }
+ },
+
+ /**
+ * Update this._taskbarState, this._totalSize and this._totalTransferred.
+ * This is called when the download manager is initialized or when the
+ * progress or state of a download changes.
+ * We compute the number of active and paused downloads, and the total size
+ * and total amount already transferred across whichever downloads we have
+ * the data for.
+ * - If there are no active downloads, then we don't want to show any
+ * progress.
+ * - If the number of active downloads is equal to the number of paused
+ * downloads, then we show a paused indicator if we know the size of at
+ * least one download, and no indicator if we don't.
+ * - If the number of active downloads is more than the number of paused
+ * downloads, then we show a "normal" indicator if we know the size of at
+ * least one download, and an indeterminate indicator if we don't.
+ */
+ _updateStatus: function DTPU_updateStatus()
+ {
+ let numActive = this._dm.activeDownloadCount + this._dm.activePrivateDownloadCount;
+ let totalSize = 0, totalTransferred = 0;
+
+ if (numActive == 0) {
+ this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS;
+ }
+ else {
+ let numPaused = 0, numScanning = 0;
+
+ // Enumerate all active downloads
+ [this._dm.activeDownloads, this._dm.activePrivateDownloads].forEach(function(downloads) {
+ while (downloads.hasMoreElements()) {
+ let download = downloads.getNext().QueryInterface(Ci.nsIDownload);
+ // Only set values if we actually know the download size
+ if (download.percentComplete != -1) {
+ totalSize += download.size;
+ totalTransferred += download.amountTransferred;
+ }
+ // We might need to display a paused state, so track this
+ if (download.state == this._dm.DOWNLOAD_PAUSED) {
+ numPaused++;
+ } else if (download.state == this._dm.DOWNLOAD_SCANNING) {
+ numScanning++;
+ }
+ }
+ }.bind(this));
+
+ // If all downloads are paused, show the progress as paused, unless we
+ // don't have any information about sizes, in which case we don't
+ // display anything
+ if (numActive == numPaused) {
+ if (totalSize == 0) {
+ this._taskbarState = Ci.nsITaskbarProgress.STATE_NO_PROGRESS;
+ totalTransferred = 0;
+ }
+ else {
+ this._taskbarState = Ci.nsITaskbarProgress.STATE_PAUSED;
+ }
+ }
+ // If at least one download is not paused, and we don't have any
+ // information about download sizes, display an indeterminate indicator
+ else if (totalSize == 0 || numActive == numScanning) {
+ this._taskbarState = Ci.nsITaskbarProgress.STATE_INDETERMINATE;
+ totalSize = 0;
+ totalTransferred = 0;
+ }
+ // Otherwise display a normal progress bar
+ else {
+ this._taskbarState = Ci.nsITaskbarProgress.STATE_NORMAL;
+ }
+ }
+
+ this._totalSize = totalSize;
+ this._totalTransferred = totalTransferred;
+ },
+
+ /**
+ * Called when a window that at one point has been an active window is
+ * closed. If this window is currently the active window, we need to look for
+ * another window and make that our active window.
+ *
+ * This function is idempotent, so multiple calls for the same window are not
+ * a problem.
+ *
+ * @param aTaskbarProgress
+ * The taskbar progress for the window that is being unloaded.
+ */
+ _onActiveWindowUnload: function DTPU_onActiveWindowUnload(aTaskbarProgress)
+ {
+ if (this._activeTaskbarProgress == aTaskbarProgress) {
+ let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ let windows = windowMediator.getEnumerator(null);
+ let newActiveWindow = null;
+ if (windows.hasMoreElements()) {
+ newActiveWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
+ }
+
+ // We aren't ever going to reach this point while the download manager is
+ // open, so it's safe to assume false for the second operand
+ this._setActiveWindow(newActiveWindow, false);
+ }
+ },
+
+ // nsIDownloadProgressListener
+
+ /**
+ * Update status if a download's progress has changed.
+ */
+ onProgressChange: function DTPU_onProgressChange()
+ {
+ this._updateStatus();
+ this._updateTaskbar();
+ },
+
+ /**
+ * Update status if a download's state has changed.
+ */
+ onDownloadStateChange: function DTPU_onDownloadStateChange()
+ {
+ this._updateStatus();
+ this._updateTaskbar();
+ },
+
+ onSecurityChange: function() { },
+
+ onStateChange: function() { },
+
+ observe: function DTPU_observe(aSubject, aTopic, aData) {
+ if (aTopic == "quit-application-granted") {
+ this._uninit();
+ }
+ }
+};
diff --git a/components/downloads/src/DownloadUtils.jsm b/components/downloads/src/DownloadUtils.jsm
new file mode 100644
index 000000000..3ebdd605e
--- /dev/null
+++ b/components/downloads/src/DownloadUtils.jsm
@@ -0,0 +1,600 @@
+/* vim: sw=2 ts=2 sts=2 expandtab 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 = [ "DownloadUtils" ];
+
+/**
+ * This module provides the DownloadUtils object which contains useful methods
+ * for downloads such as displaying file sizes, transfer times, and download
+ * locations.
+ *
+ * List of methods:
+ *
+ * [string status, double newLast]
+ * getDownloadStatus(int aCurrBytes, [optional] int aMaxBytes,
+ * [optional] double aSpeed, [optional] double aLastSec)
+ *
+ * string progress
+ * getTransferTotal(int aCurrBytes, [optional] int aMaxBytes)
+ *
+ * [string timeLeft, double newLast]
+ * getTimeLeft(double aSeconds, [optional] double aLastSec)
+ *
+ * [string dateCompact, string dateComplete]
+ * getReadableDates(Date aDate, [optional] Date aNow)
+ *
+ * [string displayHost, string fullHost]
+ * getURIHost(string aURIString)
+ *
+ * [string convertedBytes, string units]
+ * convertByteUnits(int aBytes)
+ *
+ * [int time, string units, int subTime, string subUnits]
+ * convertTimeUnits(double aSecs)
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+this.__defineGetter__("gDecimalSymbol", function() {
+ delete this.gDecimalSymbol;
+ return this.gDecimalSymbol = Number(5.4).toLocaleString().match(/\D/);
+});
+
+var localeNumberFormatCache = new Map();
+function getLocaleNumberFormat(fractionDigits) {
+ // Backward compatibility: don't use localized digits
+ let locale = Intl.NumberFormat().resolvedOptions().locale +
+ "-u-nu-latn";
+ let key = locale + "_" + fractionDigits;
+ if (!localeNumberFormatCache.has(key)) {
+ localeNumberFormatCache.set(key,
+ Intl.NumberFormat(locale,
+ { maximumFractionDigits: fractionDigits,
+ minimumFractionDigits: fractionDigits }));
+ }
+ return localeNumberFormatCache.get(key);
+}
+
+const kDownloadProperties =
+ "chrome://mozapps/locale/downloads/downloads.properties";
+
+var gStr = {
+ statusFormat: "statusFormat3",
+ statusFormatInfiniteRate: "statusFormatInfiniteRate",
+ statusFormatNoRate: "statusFormatNoRate",
+ transferSameUnits: "transferSameUnits2",
+ transferDiffUnits: "transferDiffUnits2",
+ transferNoTotal: "transferNoTotal2",
+ timePair: "timePair2",
+ timeLeftSingle: "timeLeftSingle2",
+ timeLeftDouble: "timeLeftDouble2",
+ timeFewSeconds: "timeFewSeconds",
+ timeUnknown: "timeUnknown",
+ monthDate: "monthDate2",
+ yesterday: "yesterday",
+ doneScheme: "doneScheme2",
+ doneFileScheme: "doneFileScheme",
+ units: ["bytes", "kilobyte", "megabyte", "gigabyte"],
+ // Update timeSize in convertTimeUnits if changing the length of this array
+ timeUnits: ["seconds", "minutes", "hours", "days"],
+ infiniteRate: "infiniteRate",
+};
+
+// This lazily initializes the string bundle upon first use.
+this.__defineGetter__("gBundle", function() {
+ delete this.gBundle;
+ return this.gBundle = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(kDownloadProperties);
+});
+
+// Keep track of at most this many second/lastSec pairs so that multiple calls
+// to getTimeLeft produce the same time left
+const kCachedLastMaxSize = 10;
+var gCachedLast = [];
+
+this.DownloadUtils = {
+ /**
+ * Generate a full status string for a download given its current progress,
+ * total size, speed, last time remaining
+ *
+ * @param aCurrBytes
+ * Number of bytes transferred so far
+ * @param [optional] aMaxBytes
+ * Total number of bytes or -1 for unknown
+ * @param [optional] aSpeed
+ * Current transfer rate in bytes/sec or -1 for unknown
+ * @param [optional] aLastSec
+ * Last time remaining in seconds or Infinity for unknown
+ * @return A pair: [download status text, new value of "last seconds"]
+ */
+ getDownloadStatus: function DU_getDownloadStatus(aCurrBytes, aMaxBytes,
+ aSpeed, aLastSec)
+ {
+ let [transfer, timeLeft, newLast, normalizedSpeed]
+ = this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec);
+
+ let [rate, unit] = DownloadUtils.convertByteUnits(normalizedSpeed);
+
+ let status;
+ if (rate === "Infinity") {
+ // Infinity download speed doesn't make sense. Show a localized phrase instead.
+ let params = [transfer, gBundle.GetStringFromName(gStr.infiniteRate), timeLeft];
+ status = gBundle.formatStringFromName(gStr.statusFormatInfiniteRate, params,
+ params.length);
+ }
+ else {
+ let params = [transfer, rate, unit, timeLeft];
+ status = gBundle.formatStringFromName(gStr.statusFormat, params,
+ params.length);
+ }
+ return [status, newLast];
+ },
+
+ /**
+ * Generate a status string for a download given its current progress,
+ * total size, speed, last time remaining. The status string contains the
+ * time remaining, as well as the total bytes downloaded. Unlike
+ * getDownloadStatus, it does not include the rate of download.
+ *
+ * @param aCurrBytes
+ * Number of bytes transferred so far
+ * @param [optional] aMaxBytes
+ * Total number of bytes or -1 for unknown
+ * @param [optional] aSpeed
+ * Current transfer rate in bytes/sec or -1 for unknown
+ * @param [optional] aLastSec
+ * Last time remaining in seconds or Infinity for unknown
+ * @return A pair: [download status text, new value of "last seconds"]
+ */
+ getDownloadStatusNoRate:
+ function DU_getDownloadStatusNoRate(aCurrBytes, aMaxBytes, aSpeed,
+ aLastSec)
+ {
+ let [transfer, timeLeft, newLast]
+ = this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec);
+
+ let params = [transfer, timeLeft];
+ let status = gBundle.formatStringFromName(gStr.statusFormatNoRate, params,
+ params.length);
+ return [status, newLast];
+ },
+
+ /**
+ * Helper function that returns a transfer string, a time remaining string,
+ * and a new value of "last seconds".
+ * @param aCurrBytes
+ * Number of bytes transferred so far
+ * @param [optional] aMaxBytes
+ * Total number of bytes or -1 for unknown
+ * @param [optional] aSpeed
+ * Current transfer rate in bytes/sec or -1 for unknown
+ * @param [optional] aLastSec
+ * Last time remaining in seconds or Infinity for unknown
+ * @return A triple: [amount transferred string, time remaining string,
+ * new value of "last seconds"]
+ */
+ _deriveTransferRate: function DU__deriveTransferRate(aCurrBytes,
+ aMaxBytes, aSpeed,
+ aLastSec)
+ {
+ if (aMaxBytes == null)
+ aMaxBytes = -1;
+ if (aSpeed == null)
+ aSpeed = -1;
+ if (aLastSec == null)
+ aLastSec = Infinity;
+
+ // Calculate the time remaining if we have valid values
+ let seconds = (aSpeed > 0) && (aMaxBytes > 0) ?
+ (aMaxBytes - aCurrBytes) / aSpeed : -1;
+
+ let transfer = DownloadUtils.getTransferTotal(aCurrBytes, aMaxBytes);
+ let [timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, aLastSec);
+ return [transfer, timeLeft, newLast, aSpeed];
+ },
+
+ /**
+ * Generate the transfer progress string to show the current and total byte
+ * size. Byte units will be as large as possible and the same units for
+ * current and max will be suppressed for the former.
+ *
+ * @param aCurrBytes
+ * Number of bytes transferred so far
+ * @param [optional] aMaxBytes
+ * Total number of bytes or -1 for unknown
+ * @return The transfer progress text
+ */
+ getTransferTotal: function DU_getTransferTotal(aCurrBytes, aMaxBytes)
+ {
+ if (aMaxBytes == null)
+ aMaxBytes = -1;
+
+ let [progress, progressUnits] = DownloadUtils.convertByteUnits(aCurrBytes);
+ let [total, totalUnits] = DownloadUtils.convertByteUnits(aMaxBytes);
+
+ // Figure out which byte progress string to display
+ let name, values;
+ if (aMaxBytes < 0) {
+ name = gStr.transferNoTotal;
+ values = [
+ progress,
+ progressUnits,
+ ];
+ } else if (progressUnits == totalUnits) {
+ name = gStr.transferSameUnits;
+ values = [
+ progress,
+ total,
+ totalUnits,
+ ];
+ } else {
+ name = gStr.transferDiffUnits;
+ values = [
+ progress,
+ progressUnits,
+ total,
+ totalUnits,
+ ];
+ }
+
+ return gBundle.formatStringFromName(name, values, values.length);
+ },
+
+ /**
+ * Generate a "time left" string given an estimate on the time left and the
+ * last time. The extra time is used to give a better estimate on the time to
+ * show. Both the time values are doubles instead of integers to help get
+ * sub-second accuracy for current and future estimates.
+ *
+ * @param aSeconds
+ * Current estimate on number of seconds left for the download
+ * @param [optional] aLastSec
+ * Last time remaining in seconds or Infinity for unknown
+ * @return A pair: [time left text, new value of "last seconds"]
+ */
+ getTimeLeft: function DU_getTimeLeft(aSeconds, aLastSec)
+ {
+ if (aLastSec == null)
+ aLastSec = Infinity;
+
+ if (aSeconds < 0)
+ return [gBundle.GetStringFromName(gStr.timeUnknown), aLastSec];
+
+ // Try to find a cached lastSec for the given second
+ aLastSec = gCachedLast.reduce((aResult, aItem) =>
+ aItem[0] == aSeconds ? aItem[1] : aResult, aLastSec);
+
+ // Add the current second/lastSec pair unless we have too many
+ gCachedLast.push([aSeconds, aLastSec]);
+ if (gCachedLast.length > kCachedLastMaxSize)
+ gCachedLast.shift();
+
+ // Apply smoothing only if the new time isn't a huge change -- e.g., if the
+ // new time is more than half the previous time; this is useful for
+ // downloads that start/resume slowly
+ if (aSeconds > aLastSec / 2) {
+ // Apply hysteresis to favor downward over upward swings
+ // 30% of down and 10% of up (exponential smoothing)
+ let diff = aSeconds - aLastSec;
+ aSeconds = aLastSec + (diff < 0 ? .3 : .1) * diff;
+
+ // If the new time is similar, reuse something close to the last seconds,
+ // but subtract a little to provide forward progress
+ let diffPct = diff / aLastSec * 100;
+ if (Math.abs(diff) < 5 || Math.abs(diffPct) < 5)
+ aSeconds = aLastSec - (diff < 0 ? .4 : .2);
+ }
+
+ // Decide what text to show for the time
+ let timeLeft;
+ if (aSeconds < 4) {
+ // Be friendly in the last few seconds
+ timeLeft = gBundle.GetStringFromName(gStr.timeFewSeconds);
+ } else {
+ // Convert the seconds into its two largest units to display
+ let [time1, unit1, time2, unit2] =
+ DownloadUtils.convertTimeUnits(aSeconds);
+
+ let pair1 =
+ gBundle.formatStringFromName(gStr.timePair, [time1, unit1], 2);
+ let pair2 =
+ gBundle.formatStringFromName(gStr.timePair, [time2, unit2], 2);
+
+ // Only show minutes for under 1 hour unless there's a few minutes left;
+ // or the second pair is 0.
+ if ((aSeconds < 3600 && time1 >= 4) || time2 == 0) {
+ timeLeft = gBundle.formatStringFromName(gStr.timeLeftSingle,
+ [pair1], 1);
+ } else {
+ // We've got 2 pairs of times to display
+ timeLeft = gBundle.formatStringFromName(gStr.timeLeftDouble,
+ [pair1, pair2], 2);
+ }
+ }
+
+ return [timeLeft, aSeconds];
+ },
+
+ /**
+ * Converts a Date object to two readable formats, one compact, one complete.
+ * The compact format is relative to the current date, and is not an accurate
+ * representation. For example, only the time is displayed for today. The
+ * complete format always includes both the date and the time, excluding the
+ * seconds, and is often shown when hovering the cursor over the compact
+ * representation.
+ *
+ * @param aDate
+ * Date object representing the date and time to format. It is assumed
+ * that this value represents a past date.
+ * @param [optional] aNow
+ * Date object representing the current date and time. The real date
+ * and time of invocation is used if this parameter is omitted.
+ * @return A pair: [compact text, complete text]
+ */
+ getReadableDates: function DU_getReadableDates(aDate, aNow)
+ {
+ if (!aNow) {
+ aNow = new Date();
+ }
+
+ let dts = Cc["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Ci.nsIScriptableDateFormat);
+
+ // Figure out when today begins
+ let today = new Date(aNow.getFullYear(), aNow.getMonth(), aNow.getDate());
+
+ // Get locale to use for date/time formatting
+ // TODO: Remove Intl fallback when bug 1215247 is fixed.
+ const locale = typeof Intl === "undefined"
+ ? undefined
+ : Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("global", true);
+
+ // Figure out if the time is from today, yesterday, this week, etc.
+ let dateTimeCompact;
+ if (aDate >= today) {
+ // After today started, show the time
+ dateTimeCompact = dts.FormatTime("",
+ dts.timeFormatNoSeconds,
+ aDate.getHours(),
+ aDate.getMinutes(),
+ 0);
+ } else if (today - aDate < (24 * 60 * 60 * 1000)) {
+ // After yesterday started, show yesterday
+ dateTimeCompact = gBundle.GetStringFromName(gStr.yesterday);
+ } else if (today - aDate < (6 * 24 * 60 * 60 * 1000)) {
+ // After last week started, show day of week
+ dateTimeCompact = typeof Intl === "undefined"
+ ? aDate.toLocaleFormat("%A")
+ : aDate.toLocaleDateString(locale, { weekday: "long" });
+ } else {
+ // Show month/day
+ let month = typeof Intl === "undefined"
+ ? aDate.toLocaleFormat("%B")
+ : aDate.toLocaleDateString(locale, { month: "long" });
+ let date = aDate.getDate();
+ dateTimeCompact = gBundle.formatStringFromName(gStr.monthDate, [month, date], 2);
+ }
+
+ let dateTimeFull = dts.FormatDateTime("",
+ dts.dateFormatLong,
+ dts.timeFormatNoSeconds,
+ aDate.getFullYear(),
+ aDate.getMonth() + 1,
+ aDate.getDate(),
+ aDate.getHours(),
+ aDate.getMinutes(),
+ 0);
+
+ return [dateTimeCompact, dateTimeFull];
+ },
+
+ /**
+ * Get the appropriate display host string for a URI string depending on if
+ * the URI has an eTLD + 1, is an IP address, a local file, or other protocol
+ *
+ * @param aURIString
+ * The URI string to try getting an eTLD + 1, etc.
+ * @return A pair: [display host for the URI string, full host name]
+ */
+ getURIHost: function DU_getURIHost(aURIString)
+ {
+ let ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ let eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].
+ getService(Ci.nsIEffectiveTLDService);
+ let idnService = Cc["@mozilla.org/network/idn-service;1"].
+ getService(Ci.nsIIDNService);
+
+ // Get a URI that knows about its components
+ let uri;
+ try {
+ uri = ioService.newURI(aURIString, null, null);
+ } catch (ex) {
+ return ["", ""];
+ }
+
+ // Get the inner-most uri for schemes like jar:
+ if (uri instanceof Ci.nsINestedURI)
+ uri = uri.innermostURI;
+
+ let fullHost;
+ try {
+ // Get the full host name; some special URIs fail (data: jar:)
+ fullHost = uri.host;
+ } catch (e) {
+ fullHost = "";
+ }
+
+ let displayHost;
+ try {
+ // This might fail if it's an IP address or doesn't have more than 1 part
+ let baseDomain = eTLDService.getBaseDomain(uri);
+
+ // Convert base domain for display; ignore the isAscii out param
+ displayHost = idnService.convertToDisplayIDN(baseDomain, {});
+ } catch (e) {
+ // Default to the host name
+ displayHost = fullHost;
+ }
+
+ // Check if we need to show something else for the host
+ if (uri.scheme == "file") {
+ // Display special text for file protocol
+ displayHost = gBundle.GetStringFromName(gStr.doneFileScheme);
+ fullHost = displayHost;
+ } else if (displayHost.length == 0) {
+ // Got nothing; show the scheme (data: about: moz-icon:)
+ displayHost =
+ gBundle.formatStringFromName(gStr.doneScheme, [uri.scheme], 1);
+ fullHost = displayHost;
+ } else if (uri.port != -1) {
+ // Tack on the port if it's not the default port
+ let port = ":" + uri.port;
+ displayHost += port;
+ fullHost += port;
+ }
+
+ return [displayHost, fullHost];
+ },
+
+ /**
+ * Converts a number of bytes to the appropriate unit that results in an
+ * internationalized number that needs fewer than 4 digits.
+ *
+ * @param aBytes
+ * Number of bytes to convert
+ * @return A pair: [new value with 3 sig. figs., its unit]
+ */
+ convertByteUnits: function DU_convertByteUnits(aBytes)
+ {
+ 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 < gStr.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
+ // added in bug 462064: (unitIndex != 0) makes sure that no decimal digit for bytes appears when aBytes < 100
+ let fractionDigits = (aBytes > 0) && (aBytes < 100) && (unitIndex != 0) ? 1 : 0;
+
+ // Don't try to format Infinity values using NumberFormat.
+ if (aBytes === Infinity) {
+ aBytes = "Infinity";
+ } else if (typeof Intl != "undefined") {
+ aBytes = getLocaleNumberFormat(fractionDigits)
+ .format(aBytes);
+ } else {
+ // FIXME: Fall back to the old hack, will be fixed in bug 1200494.
+ aBytes = aBytes.toFixed(fractionDigits);
+ if (gDecimalSymbol != ".") {
+ aBytes = aBytes.replace(".", gDecimalSymbol);
+ }
+ }
+
+ return [aBytes, gBundle.GetStringFromName(gStr.units[unitIndex])];
+ },
+
+ /**
+ * Converts a number of seconds to the two largest units. Time values are
+ * whole numbers, and units have the correct plural/singular form.
+ *
+ * @param aSecs
+ * Seconds to convert into the appropriate 2 units
+ * @return 4-item array [first value, its unit, second value, its unit]
+ */
+ convertTimeUnits: function DU_convertTimeUnits(aSecs)
+ {
+ // These are the maximum values for seconds, minutes, hours corresponding
+ // with gStr.timeUnits without the last item
+ let timeSize = [60, 60, 24];
+
+ let time = aSecs;
+ let scale = 1;
+ let unitIndex = 0;
+
+ // Keep converting to the next unit while we have units left and the
+ // current one isn't the largest unit possible
+ while ((unitIndex < timeSize.length) && (time >= timeSize[unitIndex])) {
+ time /= timeSize[unitIndex];
+ scale *= timeSize[unitIndex];
+ unitIndex++;
+ }
+
+ let value = convertTimeUnitsValue(time);
+ let units = convertTimeUnitsUnits(value, unitIndex);
+
+ let extra = aSecs - value * scale;
+ let nextIndex = unitIndex - 1;
+
+ // Convert the extra time to the next largest unit
+ for (let index = 0; index < nextIndex; index++)
+ extra /= timeSize[index];
+
+ let value2 = convertTimeUnitsValue(extra);
+ let units2 = convertTimeUnitsUnits(value2, nextIndex);
+
+ return [value, units, value2, units2];
+ },
+};
+
+/**
+ * Private helper for convertTimeUnits that gets the display value of a time
+ *
+ * @param aTime
+ * Time value for display
+ * @return An integer value for the time rounded down
+ */
+function convertTimeUnitsValue(aTime)
+{
+ return Math.floor(aTime);
+}
+
+/**
+ * Private helper for convertTimeUnits that gets the display units of a time
+ *
+ * @param aTime
+ * Time value for display
+ * @param aIndex
+ * Index into gStr.timeUnits for the appropriate unit
+ * @return The appropriate plural form of the unit for the time
+ */
+function convertTimeUnitsUnits(aTime, aIndex)
+{
+ // Negative index would be an invalid unit, so just give empty
+ if (aIndex < 0)
+ return "";
+
+ return PluralForm.get(aTime, gBundle.GetStringFromName(gStr.timeUnits[aIndex]));
+}
+
+/**
+ * Private helper function to log errors to the error console and command line
+ *
+ * @param aMsg
+ * Error message to log or an array of strings to concat
+ */
+function log(aMsg)
+{
+ let msg = "DownloadUtils.jsm: " + (aMsg.join ? aMsg.join("") : aMsg);
+ Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
+ logStringMessage(msg);
+ dump(msg + "\n");
+}
diff --git a/components/downloads/src/SQLFunctions.cpp b/components/downloads/src/SQLFunctions.cpp
new file mode 100644
index 000000000..8f2d3e77b
--- /dev/null
+++ b/components/downloads/src/SQLFunctions.cpp
@@ -0,0 +1,146 @@
+/* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/storage.h"
+#include "mozilla/storage/Variant.h"
+#include "mozilla/mozalloc.h"
+#include "nsString.h"
+#include "SQLFunctions.h"
+#include "nsUTF8Utils.h"
+#include "plbase64.h"
+#include "prio.h"
+
+#ifdef XP_WIN
+#include <windows.h>
+#include <wincrypt.h>
+#endif
+
+// The length of guids that are used by the download manager
+#define GUID_LENGTH 12
+
+namespace mozilla {
+namespace downloads {
+
+// Keep this file in sync with the GUID-related code in toolkit/places/SQLFunctions.cpp
+// and toolkit/places/Helpers.cpp!
+
+////////////////////////////////////////////////////////////////////////////////
+//// GUID Creation Function
+
+//////////////////////////////////////////////////////////////////////////////
+//// GenerateGUIDFunction
+
+/* static */
+nsresult
+GenerateGUIDFunction::create(mozIStorageConnection *aDBConn)
+{
+ RefPtr<GenerateGUIDFunction> function = new GenerateGUIDFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("generate_guid"), 0, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(
+ GenerateGUIDFunction,
+ mozIStorageFunction
+)
+
+static
+nsresult
+Base64urlEncode(const uint8_t* aBytes,
+ uint32_t aNumBytes,
+ nsCString& _result)
+{
+ // SetLength does not set aside space for null termination. PL_Base64Encode
+ // will not null terminate, however, nsCStrings must be null terminated. As a
+ // result, we set the capacity to be one greater than what we need, and the
+ // length to our desired length.
+ uint32_t length = (aNumBytes + 2) / 3 * 4; // +2 due to integer math.
+ NS_ENSURE_TRUE(_result.SetCapacity(length + 1, mozilla::fallible),
+ NS_ERROR_OUT_OF_MEMORY);
+ _result.SetLength(length);
+ (void)PL_Base64Encode(reinterpret_cast<const char*>(aBytes), aNumBytes,
+ _result.BeginWriting());
+
+ // base64url encoding is defined in RFC 4648. It replaces the last two
+ // alphabet characters of base64 encoding with '-' and '_' respectively.
+ _result.ReplaceChar('+', '-');
+ _result.ReplaceChar('/', '_');
+ return NS_OK;
+}
+
+static
+nsresult
+GenerateRandomBytes(uint32_t aSize,
+ uint8_t* _buffer)
+{
+ // On Windows, we'll use its built-in cryptographic API.
+#if defined(XP_WIN)
+ HCRYPTPROV cryptoProvider;
+ BOOL rc = CryptAcquireContext(&cryptoProvider, 0, 0, PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT | CRYPT_SILENT);
+ if (rc) {
+ rc = CryptGenRandom(cryptoProvider, aSize, _buffer);
+ (void)CryptReleaseContext(cryptoProvider, 0);
+ }
+ return rc ? NS_OK : NS_ERROR_FAILURE;
+
+ // On Unix, we'll just read in from /dev/urandom.
+#elif defined(XP_UNIX)
+ NS_ENSURE_ARG_MAX(aSize, INT32_MAX);
+ PRFileDesc* urandom = PR_Open("/dev/urandom", PR_RDONLY, 0);
+ nsresult rv = NS_ERROR_FAILURE;
+ if (urandom) {
+ int32_t bytesRead = PR_Read(urandom, _buffer, aSize);
+ if (bytesRead == static_cast<int32_t>(aSize)) {
+ rv = NS_OK;
+ }
+ (void)PR_Close(urandom);
+ }
+ return rv;
+#endif
+}
+
+nsresult
+GenerateGUID(nsCString& _guid)
+{
+ _guid.Truncate();
+
+ // Request raw random bytes and base64url encode them. For each set of three
+ // bytes, we get one character.
+ const uint32_t kRequiredBytesLength =
+ static_cast<uint32_t>(GUID_LENGTH / 4 * 3);
+
+ uint8_t buffer[kRequiredBytesLength];
+ nsresult rv = GenerateRandomBytes(kRequiredBytesLength, buffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = Base64urlEncode(buffer, kRequiredBytesLength, _guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(_guid.Length() == GUID_LENGTH, "GUID is not the right size!");
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//// mozIStorageFunction
+
+NS_IMETHODIMP
+GenerateGUIDFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+{
+ nsAutoCString guid;
+ nsresult rv = GenerateGUID(guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*_result = new mozilla::storage::UTF8TextVariant(guid));
+ return NS_OK;
+}
+
+} // namespace downloads
+} // namespace mozilla
diff --git a/components/downloads/src/SQLFunctions.h b/components/downloads/src/SQLFunctions.h
new file mode 100644
index 000000000..ae207788c
--- /dev/null
+++ b/components/downloads/src/SQLFunctions.h
@@ -0,0 +1,46 @@
+/* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_downloads_SQLFunctions_h
+#define mozilla_downloads_SQLFunctions_h
+
+#include "mozIStorageFunction.h"
+#include "mozilla/Attributes.h"
+
+class nsCString;
+class mozIStorageConnection;
+
+namespace mozilla {
+namespace downloads {
+
+/**
+ * SQL function to generate a GUID for a place or bookmark item. This is just
+ * a wrapper around GenerateGUID in SQLFunctions.cpp.
+ *
+ * @return a guid for the item.
+ * @see toolkit/components/places/SQLFunctions.h - keep this in sync
+ */
+class GenerateGUIDFunction final : public mozIStorageFunction
+{
+ ~GenerateGUIDFunction() {}
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ /**
+ * Registers the function with the specified database connection.
+ *
+ * @param aDBConn
+ * The database connection to register with.
+ */
+ static nsresult create(mozIStorageConnection *aDBConn);
+};
+
+nsresult GenerateGUID(nsCString& _guid);
+
+} // namespace downloads
+} // namespace mozilla
+
+#endif
diff --git a/components/downloads/src/nsDownloadManager.cpp b/components/downloads/src/nsDownloadManager.cpp
new file mode 100644
index 000000000..bc01b9ae5
--- /dev/null
+++ b/components/downloads/src/nsDownloadManager.cpp
@@ -0,0 +1,3711 @@
+/* -*- Mode: C++; 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/. */
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Unused.h"
+
+#include "mozIStorageService.h"
+#include "nsIAlertsService.h"
+#include "nsIArray.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIDOMWindow.h"
+#include "nsIDownloadHistory.h"
+#include "nsIDownloadManagerUI.h"
+#include "nsIFileURL.h"
+#include "nsIMIMEService.h"
+#include "nsIParentalControlsService.h"
+#include "nsIPrefService.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIPromptService.h"
+#include "nsIPropertyBag2.h"
+#include "nsIResumableChannel.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsIWindowMediator.h"
+#include "nsILocalFileWin.h"
+#include "nsILoadContext.h"
+#include "nsIXULAppInfo.h"
+#include "nsContentUtils.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsArrayEnumerator.h"
+#include "nsCExternalHandlerService.h"
+#include "nsCRTGlue.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDownloadManager.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "prtime.h"
+
+#include "mozStorageCID.h"
+#include "nsDocShellCID.h"
+#include "nsEmbedCID.h"
+#include "nsToolkitCompsCID.h"
+
+#include "mozilla/net/ReferrerPolicy.h"
+
+#include "SQLFunctions.h"
+
+#include "mozilla/Preferences.h"
+
+#ifdef XP_WIN
+#include <shlobj.h>
+#include "nsWindowsHelpers.h"
+#ifdef DOWNLOAD_SCANNER
+#include "nsDownloadScanner.h"
+#endif
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+#include <gtk/gtk.h>
+#endif
+
+using namespace mozilla;
+using mozilla::downloads::GenerateGUID;
+
+#define DOWNLOAD_MANAGER_BUNDLE "chrome://mozapps/locale/downloads/downloads.properties"
+#define DOWNLOAD_MANAGER_ALERT_ICON "chrome://mozapps/skin/downloads/downloadIcon.png"
+#define PREF_BD_USEJSTRANSFER "browser.download.useJSTransfer"
+#define PREF_BDM_SHOWALERTONCOMPLETE "browser.download.manager.showAlertOnComplete"
+#define PREF_BDM_SHOWALERTINTERVAL "browser.download.manager.showAlertInterval"
+#define PREF_BDM_RETENTION "browser.download.manager.retention"
+#define PREF_BDM_QUITBEHAVIOR "browser.download.manager.quitBehavior"
+#define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
+#define PREF_BDM_SCANWHENDONE "browser.download.manager.scanWhenDone"
+#define PREF_BDM_RESUMEONWAKEDELAY "browser.download.manager.resumeOnWakeDelay"
+#define PREF_BH_DELETETEMPFILEONEXIT "browser.helperApps.deleteTempFileOnExit"
+
+static const int64_t gUpdateInterval = 400 * PR_USEC_PER_MSEC;
+
+#define DM_SCHEMA_VERSION 9
+#define DM_DB_NAME NS_LITERAL_STRING("downloads.sqlite")
+#define DM_DB_CORRUPT_FILENAME NS_LITERAL_STRING("downloads.sqlite.corrupt")
+
+#define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsDownloadManager
+
+NS_IMPL_ISUPPORTS(
+ nsDownloadManager
+, nsIDownloadManager
+, nsINavHistoryObserver
+, nsIObserver
+, nsISupportsWeakReference
+)
+
+nsDownloadManager *nsDownloadManager::gDownloadManagerService = nullptr;
+
+nsDownloadManager *
+nsDownloadManager::GetSingleton()
+{
+ if (gDownloadManagerService) {
+ NS_ADDREF(gDownloadManagerService);
+ return gDownloadManagerService;
+ }
+
+ gDownloadManagerService = new nsDownloadManager();
+ if (gDownloadManagerService) {
+#if defined(MOZ_WIDGET_GTK)
+ g_type_init();
+#endif
+ NS_ADDREF(gDownloadManagerService);
+ if (NS_FAILED(gDownloadManagerService->Init()))
+ NS_RELEASE(gDownloadManagerService);
+ }
+
+ return gDownloadManagerService;
+}
+
+nsDownloadManager::~nsDownloadManager()
+{
+#ifdef DOWNLOAD_SCANNER
+ if (mScanner) {
+ delete mScanner;
+ mScanner = nullptr;
+ }
+#endif
+ gDownloadManagerService = nullptr;
+}
+
+nsresult
+nsDownloadManager::ResumeRetry(nsDownload *aDl)
+{
+ // Keep a reference in case we need to cancel the download
+ RefPtr<nsDownload> dl = aDl;
+
+ // Try to resume the active download
+ nsresult rv = dl->Resume();
+
+ // If not, try to retry the download
+ if (NS_FAILED(rv)) {
+ // First cancel the download so it's no longer active
+ rv = dl->Cancel();
+
+ // Then retry it
+ if (NS_SUCCEEDED(rv))
+ rv = dl->Retry();
+ }
+
+ return rv;
+}
+
+nsresult
+nsDownloadManager::PauseAllDownloads(bool aSetResume)
+{
+ nsresult rv = PauseAllDownloads(mCurrentDownloads, aSetResume);
+ nsresult rv2 = PauseAllDownloads(mCurrentPrivateDownloads, aSetResume);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::PauseAllDownloads(nsCOMArray<nsDownload>& aDownloads, bool aSetResume)
+{
+ nsresult retVal = NS_OK;
+ for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
+ RefPtr<nsDownload> dl = aDownloads[i];
+
+ // Only pause things that need to be paused
+ if (!dl->IsPaused()) {
+ // Set auto-resume before pausing so that it gets into the DB
+ dl->mAutoResume = aSetResume ? nsDownload::AUTO_RESUME :
+ nsDownload::DONT_RESUME;
+
+ // Try to pause the download but don't bail now if we fail
+ nsresult rv = dl->Pause();
+ if (NS_FAILED(rv))
+ retVal = rv;
+ }
+ }
+
+ return retVal;
+}
+
+nsresult
+nsDownloadManager::ResumeAllDownloads(bool aResumeAll)
+{
+ nsresult rv = ResumeAllDownloads(mCurrentDownloads, aResumeAll);
+ nsresult rv2 = ResumeAllDownloads(mCurrentPrivateDownloads, aResumeAll);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::ResumeAllDownloads(nsCOMArray<nsDownload>& aDownloads, bool aResumeAll)
+{
+ nsresult retVal = NS_OK;
+ for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
+ RefPtr<nsDownload> dl = aDownloads[i];
+
+ // If aResumeAll is true, then resume everything; otherwise, check if the
+ // download should auto-resume
+ if (aResumeAll || dl->ShouldAutoResume()) {
+ // Reset auto-resume before retrying so that it gets into the DB through
+ // ResumeRetry's eventual call to SetState. We clear the value now so we
+ // don't accidentally query completed downloads that were previously
+ // auto-resumed (and try to resume them).
+ dl->mAutoResume = nsDownload::DONT_RESUME;
+
+ // Try to resume/retry the download but don't bail now if we fail
+ nsresult rv = ResumeRetry(dl);
+ if (NS_FAILED(rv))
+ retVal = rv;
+ }
+ }
+
+ return retVal;
+}
+
+nsresult
+nsDownloadManager::RemoveAllDownloads()
+{
+ nsresult rv = RemoveAllDownloads(mCurrentDownloads);
+ nsresult rv2 = RemoveAllDownloads(mCurrentPrivateDownloads);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::RemoveAllDownloads(nsCOMArray<nsDownload>& aDownloads)
+{
+ nsresult rv = NS_OK;
+ for (int32_t i = aDownloads.Count() - 1; i >= 0; --i) {
+ RefPtr<nsDownload> dl = aDownloads[0];
+
+ nsresult result = NS_OK;
+ if (!dl->mPrivate && dl->IsPaused() && GetQuitBehavior() != QUIT_AND_CANCEL)
+ aDownloads.RemoveObject(dl);
+ else
+ result = dl->Cancel();
+
+ // Track the failure, but don't miss out on other downloads
+ if (NS_FAILED(result))
+ rv = result;
+ }
+
+ return rv;
+}
+
+nsresult
+nsDownloadManager::RemoveDownloadsForURI(mozIStorageStatement* aStatement, nsIURI *aURI)
+{
+ mozStorageStatementScoper scope(aStatement);
+
+ nsAutoCString source;
+ nsresult rv = aURI->GetSpec(source);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStatement->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("source"), source);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ AutoTArray<nsCString, 4> downloads;
+ // Get all the downloads that match the provided URI
+ while (NS_SUCCEEDED(aStatement->ExecuteStep(&hasMore)) &&
+ hasMore) {
+ nsAutoCString downloadGuid;
+ rv = aStatement->GetUTF8String(0, downloadGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ downloads.AppendElement(downloadGuid);
+ }
+
+ // Remove each download ignoring any failure so we reach other downloads
+ for (int32_t i = downloads.Length(); --i >= 0; )
+ (void)RemoveDownload(downloads[i]);
+
+ return NS_OK;
+}
+
+void // static
+nsDownloadManager::ResumeOnWakeCallback(nsITimer *aTimer, void *aClosure)
+{
+ // Resume the downloads that were set to autoResume
+ nsDownloadManager *dlMgr = static_cast<nsDownloadManager *>(aClosure);
+ (void)dlMgr->ResumeAllDownloads(false);
+}
+
+already_AddRefed<mozIStorageConnection>
+nsDownloadManager::GetFileDBConnection(nsIFile *dbFile) const
+{
+ NS_ASSERTION(dbFile, "GetFileDBConnection called with an invalid nsIFile");
+
+ nsCOMPtr<mozIStorageService> storage =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(storage, nullptr);
+
+ nsCOMPtr<mozIStorageConnection> conn;
+ nsresult rv = storage->OpenDatabase(dbFile, getter_AddRefs(conn));
+ if (rv == NS_ERROR_FILE_CORRUPTED) {
+ // delete and try again, since we don't care so much about losing a user's
+ // download history
+ rv = dbFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ rv = storage->OpenDatabase(dbFile, getter_AddRefs(conn));
+ }
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return conn.forget();
+}
+
+already_AddRefed<mozIStorageConnection>
+nsDownloadManager::GetPrivateDBConnection() const
+{
+ nsCOMPtr<mozIStorageService> storage =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(storage, nullptr);
+
+ nsCOMPtr<mozIStorageConnection> conn;
+ nsresult rv = storage->OpenSpecialDatabase("memory", getter_AddRefs(conn));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return conn.forget();
+}
+
+void
+nsDownloadManager::CloseAllDBs()
+{
+ CloseDB(mDBConn, mUpdateDownloadStatement, mGetIdsForURIStatement);
+ CloseDB(mPrivateDBConn, mUpdatePrivateDownloadStatement, mGetPrivateIdsForURIStatement);
+}
+
+void
+nsDownloadManager::CloseDB(mozIStorageConnection* aDBConn,
+ mozIStorageStatement* aUpdateStmt,
+ mozIStorageStatement* aGetIdsStmt)
+{
+ DebugOnly<nsresult> rv = aGetIdsStmt->Finalize();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = aUpdateStmt->Finalize();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = aDBConn->AsyncClose(nullptr);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+static nsresult
+InitSQLFunctions(mozIStorageConnection* aDBConn)
+{
+ nsresult rv = mozilla::downloads::GenerateGUIDFunction::create(aDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::InitPrivateDB()
+{
+ bool ready = false;
+ if (mPrivateDBConn && NS_SUCCEEDED(mPrivateDBConn->GetConnectionReady(&ready)) && ready)
+ CloseDB(mPrivateDBConn, mUpdatePrivateDownloadStatement, mGetPrivateIdsForURIStatement);
+ mPrivateDBConn = GetPrivateDBConnection();
+ if (!mPrivateDBConn)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = InitSQLFunctions(mPrivateDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CreateTable(mPrivateDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitStatements(mPrivateDBConn, getter_AddRefs(mUpdatePrivateDownloadStatement),
+ getter_AddRefs(mGetPrivateIdsForURIStatement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::InitFileDB()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> dbFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dbFile->Append(DM_DB_NAME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool ready = false;
+ if (mDBConn && NS_SUCCEEDED(mDBConn->GetConnectionReady(&ready)) && ready)
+ CloseDB(mDBConn, mUpdateDownloadStatement, mGetIdsForURIStatement);
+ mDBConn = GetFileDBConnection(dbFile);
+ NS_ENSURE_TRUE(mDBConn, NS_ERROR_NOT_AVAILABLE);
+
+ rv = InitSQLFunctions(mDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool tableExists;
+ rv = mDBConn->TableExists(NS_LITERAL_CSTRING("moz_downloads"), &tableExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!tableExists) {
+ rv = CreateTable(mDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We're done with the initialization now and can skip the remaining
+ // upgrading logic.
+ return NS_OK;
+ }
+
+ // Checking the database schema now
+ int32_t schemaVersion;
+ rv = mDBConn->GetSchemaVersion(&schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Changing the database? Be sure to do these two things!
+ // 1) Increment DM_SCHEMA_VERSION
+ // 2) Implement the proper downgrade/upgrade code for the current version
+
+ switch (schemaVersion) {
+ // Upgrading
+ // Every time you increment the database schema, you need to implement
+ // the upgrading code from the previous version to the new one.
+ // Also, don't forget to make a unit test to test your upgrading code!
+ case 1: // Drop a column (iconURL) from the database (bug 385875)
+ {
+ // Safely wrap this in a transaction so we don't hose the whole DB
+ mozStorageTransaction safeTransaction(mDBConn, true);
+
+ // Create a temporary table that will store the existing records
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMPORARY TABLE moz_downloads_backup ("
+ "id INTEGER PRIMARY KEY, "
+ "name TEXT, "
+ "source TEXT, "
+ "target TEXT, "
+ "startTime INTEGER, "
+ "endTime INTEGER, "
+ "state INTEGER"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Insert into a temporary table
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_downloads_backup "
+ "SELECT id, name, source, target, startTime, endTime, state "
+ "FROM moz_downloads"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Drop the old table
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE moz_downloads"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now recreate it with this schema version
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_downloads ("
+ "id INTEGER PRIMARY KEY, "
+ "name TEXT, "
+ "source TEXT, "
+ "target TEXT, "
+ "startTime INTEGER, "
+ "endTime INTEGER, "
+ "state INTEGER"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Insert the data back into it
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_downloads "
+ "SELECT id, name, source, target, startTime, endTime, state "
+ "FROM moz_downloads_backup"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // And drop our temporary table
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE moz_downloads_backup"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 2;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 2: // Add referrer column to the database
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN referrer TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 3;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 3: // This version adds a column to the database (entityID)
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN entityID TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 4;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 4: // This version adds a column to the database (tempPath)
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN tempPath TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 5;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 5: // This version adds two columns for tracking transfer progress
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN currBytes INTEGER NOT NULL DEFAULT 0"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN maxBytes INTEGER NOT NULL DEFAULT -1"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 6;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 6: // This version adds three columns to DB (MIME type related info)
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN mimeType TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN preferredApplication TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN preferredAction INTEGER NOT NULL DEFAULT 0"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 7;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to next upgrade
+ MOZ_FALLTHROUGH;
+
+ case 7: // This version adds a column to remember to auto-resume downloads
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads "
+ "ADD COLUMN autoResume INTEGER NOT NULL DEFAULT 0"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the schemaVersion variable and the database schema
+ schemaVersion = 8;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // Warning: schema versions >=8 must take into account that they can
+ // be operating on schemas from unknown, future versions that have
+ // been downgraded. Operations such as adding columns may fail,
+ // since the column may already exist.
+
+ case 8: // This version adds a column for GUIDs
+ {
+ bool exists;
+ rv = mDBConn->IndexExists(NS_LITERAL_CSTRING("moz_downloads_guid_uniqueindex"),
+ &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_downloads ADD COLUMN guid TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE UNIQUE INDEX moz_downloads_guid_uniqueindex ON moz_downloads (guid)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads SET guid = GENERATE_GUID() WHERE guid ISNULL"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, update the database schema
+ schemaVersion = 9;
+ rv = mDBConn->SetSchemaVersion(schemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to the next upgrade
+
+ // Extra sanity checking for developers
+#ifndef DEBUG
+ MOZ_FALLTHROUGH;
+ case DM_SCHEMA_VERSION:
+#endif
+ break;
+
+ case 0:
+ {
+ NS_WARNING("Could not get download database's schema version!");
+
+ // The table may still be usable - someone may have just messed with the
+ // schema version, so let's just treat this like a downgrade and verify
+ // that the needed columns are there. If they aren't there, we'll drop
+ // the table anyway.
+ rv = mDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Fallthrough to downgrade check
+ MOZ_FALLTHROUGH;
+
+ // Downgrading
+ // If columns have been added to the table, we can still use the ones we
+ // understand safely. If columns have been deleted or alterd, we just
+ // drop the table and start from scratch. If you change how a column
+ // should be interpreted, make sure you also change its name so this
+ // check will catch it.
+ default:
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id, name, source, target, tempPath, startTime, endTime, state, "
+ "referrer, entityID, currBytes, maxBytes, mimeType, "
+ "preferredApplication, preferredAction, autoResume, guid "
+ "FROM moz_downloads"), getter_AddRefs(stmt));
+ if (NS_SUCCEEDED(rv)) {
+ // We have a database that contains all of the elements that make up
+ // the latest known schema. Reset the version to force an upgrade
+ // path if this downgraded database is used in a later version.
+ mDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
+ break;
+ }
+
+ // if the statement fails, that means all the columns were not there.
+ // First we backup the database
+ nsCOMPtr<mozIStorageService> storage =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(storage, NS_ERROR_NOT_AVAILABLE);
+ nsCOMPtr<nsIFile> backup;
+ rv = storage->BackupDatabaseFile(dbFile, DM_DB_CORRUPT_FILENAME, nullptr,
+ getter_AddRefs(backup));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Then we dump it
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE moz_downloads"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CreateTable(mDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::CreateTable(mozIStorageConnection* aDBConn)
+{
+ nsresult rv = aDBConn->SetSchemaVersion(DM_SCHEMA_VERSION);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_downloads ("
+ "id INTEGER PRIMARY KEY, "
+ "name TEXT, "
+ "source TEXT, "
+ "target TEXT, "
+ "tempPath TEXT, "
+ "startTime INTEGER, "
+ "endTime INTEGER, "
+ "state INTEGER, "
+ "referrer TEXT, "
+ "entityID TEXT, "
+ "currBytes INTEGER NOT NULL DEFAULT 0, "
+ "maxBytes INTEGER NOT NULL DEFAULT -1, "
+ "mimeType TEXT, "
+ "preferredApplication TEXT, "
+ "preferredAction INTEGER NOT NULL DEFAULT 0, "
+ "autoResume INTEGER NOT NULL DEFAULT 0, "
+ "guid TEXT"
+ ")"));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE UNIQUE INDEX moz_downloads_guid_uniqueindex "
+ "ON moz_downloads(guid)"));
+ return rv;
+}
+
+nsresult
+nsDownloadManager::RestoreDatabaseState()
+{
+ // Restore downloads that were in a scanning state. We can assume that they
+ // have been dealt with by the virus scanner
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads "
+ "SET state = :state "
+ "WHERE state = :state_cond"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_FINISHED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state_cond"), nsIDownloadManager::DOWNLOAD_SCANNING);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Convert supposedly-active downloads into downloads that should auto-resume
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads "
+ "SET autoResume = :autoResume "
+ "WHERE state = :notStarted "
+ "OR state = :queued "
+ "OR state = :downloading"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::AUTO_RESUME);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("notStarted"), nsIDownloadManager::DOWNLOAD_NOTSTARTED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("queued"), nsIDownloadManager::DOWNLOAD_QUEUED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("downloading"), nsIDownloadManager::DOWNLOAD_DOWNLOADING);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Switch any download that is supposed to automatically resume and is in a
+ // finished state to *not* automatically resume. See Bug 409179 for details.
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads "
+ "SET autoResume = :autoResume "
+ "WHERE state = :state "
+ "AND autoResume = :autoResume_cond"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::DONT_RESUME);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_FINISHED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume_cond"), nsDownload::AUTO_RESUME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::RestoreActiveDownloads()
+{
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id "
+ "FROM moz_downloads "
+ "WHERE (state = :state AND LENGTH(entityID) > 0) "
+ "OR autoResume != :autoResume"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_PAUSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), nsDownload::DONT_RESUME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult retVal = NS_OK;
+ bool hasResults;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResults)) && hasResults) {
+ RefPtr<nsDownload> dl;
+ // Keep trying to add even if we fail one, but make sure to return failure.
+ // Additionally, be careful to not call anything that tries to change the
+ // database because we're iterating over a live statement.
+ if (NS_FAILED(GetDownloadFromDB(stmt->AsInt32(0), getter_AddRefs(dl))) ||
+ NS_FAILED(AddToCurrentDownloads(dl)))
+ retVal = NS_ERROR_FAILURE;
+ }
+
+ // Try to resume only the downloads that should auto-resume
+ rv = ResumeAllDownloads(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return retVal;
+}
+
+int64_t
+nsDownloadManager::AddDownloadToDB(const nsAString &aName,
+ const nsACString &aSource,
+ const nsACString &aTarget,
+ const nsAString &aTempPath,
+ int64_t aStartTime,
+ int64_t aEndTime,
+ const nsACString &aMimeType,
+ const nsACString &aPreferredApp,
+ nsHandlerInfoAction aPreferredAction,
+ bool aPrivate,
+ nsACString& aNewGUID)
+{
+ mozIStorageConnection* dbConn = aPrivate ? mPrivateDBConn : mDBConn;
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_downloads "
+ "(name, source, target, tempPath, startTime, endTime, state, "
+ "mimeType, preferredApplication, preferredAction, guid) VALUES "
+ "(:name, :source, :target, :tempPath, :startTime, :endTime, :state, "
+ ":mimeType, :preferredApplication, :preferredAction, :guid)"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("source"), aSource);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("target"), aTarget);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("tempPath"), aTempPath);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), aStartTime);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), aEndTime);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), nsIDownloadManager::DOWNLOAD_NOTSTARTED);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("mimeType"), aMimeType);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("preferredApplication"), aPreferredApp);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("preferredAction"), aPreferredAction);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ nsAutoCString guid;
+ rv = GenerateGUID(guid);
+ NS_ENSURE_SUCCESS(rv, 0);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ bool hasMore;
+ rv = stmt->ExecuteStep(&hasMore); // we want to keep our lock
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ int64_t id = 0;
+ rv = dbConn->GetLastInsertRowID(&id);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ aNewGUID = guid;
+
+ // lock on DB from statement will be released once we return
+ return id;
+}
+
+nsresult
+nsDownloadManager::InitDB()
+{
+ nsresult rv = InitPrivateDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitFileDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = InitStatements(mDBConn, getter_AddRefs(mUpdateDownloadStatement),
+ getter_AddRefs(mGetIdsForURIStatement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::InitStatements(mozIStorageConnection* aDBConn,
+ mozIStorageStatement** aUpdateStatement,
+ mozIStorageStatement** aGetIdsStatement)
+{
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads "
+ "SET tempPath = :tempPath, startTime = :startTime, endTime = :endTime, "
+ "state = :state, referrer = :referrer, entityID = :entityID, "
+ "currBytes = :currBytes, maxBytes = :maxBytes, autoResume = :autoResume "
+ "WHERE id = :id"), aUpdateStatement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT guid "
+ "FROM moz_downloads "
+ "WHERE source = :source"), aGetIdsStatement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::Init()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService)
+ return NS_ERROR_FAILURE;
+
+ rv = bundleService->CreateBundle(DOWNLOAD_MANAGER_BUNDLE,
+ getter_AddRefs(mBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#if !defined(MOZ_JSDOWNLOADS)
+ // When MOZ_JSDOWNLOADS is undefined, we still check the preference that can
+ // be used to enable the JavaScript API during the migration process.
+ mUseJSTransfer = Preferences::GetBool(PREF_BD_USEJSTRANSFER, false);
+#else
+ mUseJSTransfer = true;
+#endif
+
+ if (mUseJSTransfer)
+ return NS_OK;
+
+ // Clean up any old downloads.rdf files from before Firefox 3
+ {
+ nsCOMPtr<nsIFile> oldDownloadsFile;
+ bool fileExists;
+ if (NS_SUCCEEDED(NS_GetSpecialDirectory(NS_APP_DOWNLOADS_50_FILE,
+ getter_AddRefs(oldDownloadsFile))) &&
+ NS_SUCCEEDED(oldDownloadsFile->Exists(&fileExists)) &&
+ fileExists) {
+ (void)oldDownloadsFile->Remove(false);
+ }
+ }
+
+ mObserverService = mozilla::services::GetObserverService();
+ if (!mObserverService)
+ return NS_ERROR_FAILURE;
+
+ rv = InitDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef DOWNLOAD_SCANNER
+ mScanner = new nsDownloadScanner();
+ if (!mScanner)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = mScanner->Init();
+ if (NS_FAILED(rv)) {
+ delete mScanner;
+ mScanner = nullptr;
+ }
+#endif
+
+ // Do things *after* initializing various download manager properties such as
+ // restoring downloads to a consistent state
+ rv = RestoreDatabaseState();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = RestoreActiveDownloads();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to restore all active downloads");
+
+ nsCOMPtr<nsINavHistoryService> history =
+ do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
+
+ (void)mObserverService->NotifyObservers(
+ static_cast<nsIDownloadManager *>(this),
+ "download-manager-initialized",
+ nullptr);
+
+ // The following AddObserver calls must be the last lines in this function,
+ // because otherwise, this function may fail (and thus, this object would be not
+ // completely initialized), but the observerservice would still keep a reference
+ // to us and notify us about shutdown, which may cause crashes.
+ // failure to add an observer is not critical
+ (void)mObserverService->AddObserver(this, "quit-application", true);
+ (void)mObserverService->AddObserver(this, "quit-application-requested", true);
+ (void)mObserverService->AddObserver(this, "offline-requested", true);
+ (void)mObserverService->AddObserver(this, "sleep_notification", true);
+ (void)mObserverService->AddObserver(this, "wake_notification", true);
+ (void)mObserverService->AddObserver(this, "suspend_process_notification", true);
+ (void)mObserverService->AddObserver(this, "resume_process_notification", true);
+ (void)mObserverService->AddObserver(this, "profile-before-change", true);
+ (void)mObserverService->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, true);
+ (void)mObserverService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, true);
+ (void)mObserverService->AddObserver(this, "last-pb-context-exited", true);
+ (void)mObserverService->AddObserver(this, "last-pb-context-exiting", true);
+
+ if (history)
+ (void)history->AddObserver(this, true);
+
+ return NS_OK;
+}
+
+int32_t
+nsDownloadManager::GetRetentionBehavior()
+{
+ // We use 0 as the default, which is "remove when done"
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ int32_t val;
+ rv = pref->GetIntPref(PREF_BDM_RETENTION, &val);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ // Allow the Downloads Panel to change the retention behavior. We do this to
+ // allow proper migration to the new feature when using the same profile on
+ // multiple versions of the product (bug 697678). Implementation note: in
+ // order to allow observers to change the retention value, we have to pass an
+ // object in the aSubject parameter, we cannot use aData for that.
+ nsCOMPtr<nsISupportsPRInt32> retentionBehavior =
+ do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID);
+ retentionBehavior->SetData(val);
+ (void)mObserverService->NotifyObservers(retentionBehavior,
+ "download-manager-change-retention",
+ nullptr);
+ retentionBehavior->GetData(&val);
+
+ return val;
+}
+
+enum nsDownloadManager::QuitBehavior
+nsDownloadManager::GetQuitBehavior()
+{
+ // We use 0 as the default, which is "remember and resume the download"
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, QUIT_AND_RESUME);
+
+ int32_t val;
+ rv = pref->GetIntPref(PREF_BDM_QUITBEHAVIOR, &val);
+ NS_ENSURE_SUCCESS(rv, QUIT_AND_RESUME);
+
+ switch (val) {
+ case 1:
+ return QUIT_AND_PAUSE;
+ case 2:
+ return QUIT_AND_CANCEL;
+ default:
+ return QUIT_AND_RESUME;
+ }
+}
+
+// Using a globally-unique GUID, search all databases (both private and public).
+// A return value of NS_ERROR_NOT_AVAILABLE means no download with the given GUID
+// could be found, either private or public.
+
+nsresult
+nsDownloadManager::GetDownloadFromDB(const nsACString& aGUID, nsDownload **retVal)
+{
+ MOZ_ASSERT(!FindDownload(aGUID),
+ "If it is a current download, you should not call this method!");
+
+ NS_NAMED_LITERAL_CSTRING(query,
+ "SELECT id, state, startTime, source, target, tempPath, name, referrer, "
+ "entityID, currBytes, maxBytes, mimeType, preferredAction, "
+ "preferredApplication, autoResume, guid "
+ "FROM moz_downloads "
+ "WHERE guid = :guid");
+ // First, let's query the database and see if it even exists
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mDBConn->CreateStatement(query, getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetDownloadFromDB(mDBConn, stmt, retVal);
+
+ // If the download cannot be found in the public database, try again
+ // in the private one. Otherwise, return whatever successful result
+ // or failure obtained from the public database.
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ rv = mPrivateDBConn->CreateStatement(query, getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetDownloadFromDB(mPrivateDBConn, stmt, retVal);
+
+ // Only if it still cannot be found do we report the failure.
+ if (rv == NS_ERROR_NOT_AVAILABLE) {
+ *retVal = nullptr;
+ }
+ }
+ return rv;
+}
+
+nsresult
+nsDownloadManager::GetDownloadFromDB(uint32_t aID, nsDownload **retVal)
+{
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ MOZ_ASSERT(!FindDownload(aID),
+ "If it is a current download, you should not call this method!");
+
+ // First, let's query the database and see if it even exists
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id, state, startTime, source, target, tempPath, name, referrer, "
+ "entityID, currBytes, maxBytes, mimeType, preferredAction, "
+ "preferredApplication, autoResume, guid "
+ "FROM moz_downloads "
+ "WHERE id = :id"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetDownloadFromDB(mDBConn, stmt, retVal);
+}
+
+nsresult
+nsDownloadManager::GetDownloadFromDB(mozIStorageConnection* aDBConn,
+ mozIStorageStatement* stmt,
+ nsDownload **retVal)
+{
+ bool hasResults = false;
+ nsresult rv = stmt->ExecuteStep(&hasResults);
+ if (NS_FAILED(rv) || !hasResults)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // We have a download, so lets create it
+ RefPtr<nsDownload> dl = new nsDownload();
+ if (!dl)
+ return NS_ERROR_OUT_OF_MEMORY;
+ dl->mPrivate = aDBConn == mPrivateDBConn;
+
+ dl->mDownloadManager = this;
+
+ int32_t i = 0;
+ // Setting all properties of the download now
+ dl->mCancelable = nullptr;
+ dl->mID = stmt->AsInt64(i++);
+ dl->mDownloadState = stmt->AsInt32(i++);
+ dl->mStartTime = stmt->AsInt64(i++);
+
+ nsCString source;
+ stmt->GetUTF8String(i++, source);
+ rv = NS_NewURI(getter_AddRefs(dl->mSource), source);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString target;
+ stmt->GetUTF8String(i++, target);
+ rv = NS_NewURI(getter_AddRefs(dl->mTarget), target);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString tempPath;
+ stmt->GetString(i++, tempPath);
+ if (!tempPath.IsEmpty()) {
+ rv = NS_NewLocalFile(tempPath, true, getter_AddRefs(dl->mTempFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ stmt->GetString(i++, dl->mDisplayName);
+
+ nsCString referrer;
+ rv = stmt->GetUTF8String(i++, referrer);
+ if (NS_SUCCEEDED(rv) && !referrer.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(dl->mReferrer), referrer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = stmt->GetUTF8String(i++, dl->mEntityID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t currBytes = stmt->AsInt64(i++);
+ int64_t maxBytes = stmt->AsInt64(i++);
+ dl->SetProgressBytes(currBytes, maxBytes);
+
+ // Build mMIMEInfo only if the mimeType in DB is not empty
+ nsAutoCString mimeType;
+ rv = stmt->GetUTF8String(i++, mimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mimeType.IsEmpty()) {
+ nsCOMPtr<nsIMIMEService> mimeService =
+ do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
+ getter_AddRefs(dl->mMIMEInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsHandlerInfoAction action = stmt->AsInt32(i++);
+ rv = dl->mMIMEInfo->SetPreferredAction(action);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString persistentDescriptor;
+ rv = stmt->GetUTF8String(i++, persistentDescriptor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!persistentDescriptor.IsEmpty()) {
+ nsCOMPtr<nsILocalHandlerApp> handler =
+ do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> localExecutable;
+ rv = NS_NewNativeLocalFile(EmptyCString(), false,
+ getter_AddRefs(localExecutable));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localExecutable->SetPersistentDescriptor(persistentDescriptor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = handler->SetExecutable(localExecutable);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dl->mMIMEInfo->SetPreferredApplicationHandler(handler);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ // Compensate for the i++s skipped in the true block
+ i += 2;
+ }
+
+ dl->mAutoResume =
+ static_cast<enum nsDownload::AutoResume>(stmt->AsInt32(i++));
+
+ rv = stmt->GetUTF8String(i++, dl->mGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Handle situations where we load a download from a database that has been
+ // used in an older version and not gone through the upgrade path (ie. it
+ // contains empty GUID entries).
+ if (dl->mGUID.IsEmpty()) {
+ rv = GenerateGUID(dl->mGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIStorageStatement> updateStmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_downloads SET guid = :guid "
+ "WHERE id = :id"),
+ getter_AddRefs(updateStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updateStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), dl->mGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), dl->mID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = updateStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Addrefing and returning
+ dl.forget(retVal);
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::AddToCurrentDownloads(nsDownload *aDl)
+{
+ nsCOMArray<nsDownload>& currentDownloads =
+ aDl->mPrivate ? mCurrentPrivateDownloads : mCurrentDownloads;
+ if (!currentDownloads.AppendObject(aDl))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ aDl->mDownloadManager = this;
+ return NS_OK;
+}
+
+void
+nsDownloadManager::SendEvent(nsDownload *aDownload, const char *aTopic)
+{
+ (void)mObserverService->NotifyObservers(aDownload, aTopic, nullptr);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIDownloadManager
+
+NS_IMETHODIMP
+nsDownloadManager::GetActivePrivateDownloadCount(int32_t* aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ *aResult = mCurrentPrivateDownloads.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetActiveDownloadCount(int32_t *aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ *aResult = mCurrentDownloads.Count();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetActiveDownloads(nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return NS_NewArrayEnumerator(aResult, mCurrentDownloads);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetActivePrivateDownloads(nsISimpleEnumerator **aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return NS_NewArrayEnumerator(aResult, mCurrentPrivateDownloads);
+}
+
+/**
+ * For platforms where helper apps use the downloads directory (i.e. mobile),
+ * this should be kept in sync with nsExternalHelperAppService.cpp
+ */
+NS_IMETHODIMP
+nsDownloadManager::GetDefaultDownloadsDirectory(nsIFile **aResult)
+{
+ nsCOMPtr<nsIFile> downloadDir;
+
+ nsresult rv;
+ nsCOMPtr<nsIProperties> dirService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // OSX 10.4:
+ // Desktop
+ // OSX 10.5:
+ // User download directory
+ // Vista:
+ // Downloads
+ // XP/2K:
+ // My Documents/Downloads
+ // Linux:
+ // XDG user dir spec, with a fallback to Home/Downloads
+
+ nsXPIDLString folderName;
+ mBundle->GetStringFromName(u"downloadsFolder",
+ getter_Copies(folderName));
+
+#if defined(XP_WIN)
+ rv = dirService->Get(NS_WIN_DEFAULT_DOWNLOAD_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check the os version
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService(NS_SYSTEMINFO_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t version;
+ NS_NAMED_LITERAL_STRING(osVersion, "version");
+ rv = infoService->GetPropertyAsInt32(osVersion, &version);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (version < 6) { // XP/2K
+ // First get "My Documents"
+ rv = dirService->Get(NS_WIN_PERSONAL_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = downloadDir->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This could be the first time we are creating the downloads folder in My
+ // Documents, so make sure it exists.
+ bool exists;
+ rv = downloadDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) {
+ rv = downloadDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+#elif defined(XP_UNIX)
+ rv = dirService->Get(NS_UNIX_DEFAULT_DOWNLOAD_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ // fallback to Home/Downloads
+ if (NS_FAILED(rv)) {
+ rv = dirService->Get(NS_UNIX_HOME_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = downloadDir->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#else
+ rv = dirService->Get(NS_OS_HOME_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = downloadDir->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+
+ downloadDir.forget(aResult);
+
+ return NS_OK;
+}
+
+#define NS_BRANCH_DOWNLOAD "browser.download."
+#define NS_PREF_FOLDERLIST "folderList"
+#define NS_PREF_DIR "dir"
+
+NS_IMETHODIMP
+nsDownloadManager::GetUserDownloadsDirectory(nsIFile **aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProperties> dirService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefService> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefService->GetBranch(NS_BRANCH_DOWNLOAD,
+ getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t val;
+ rv = prefBranch->GetIntPref(NS_PREF_FOLDERLIST,
+ &val);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch(val) {
+ case 0: // Desktop
+ {
+ nsCOMPtr<nsIFile> downloadDir;
+ rv = dirService->Get(NS_OS_DESKTOP_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ downloadDir.forget(aResult);
+ return NS_OK;
+ }
+ break;
+ case 1: // Downloads
+ return GetDefaultDownloadsDirectory(aResult);
+ case 2: // Custom
+ {
+ nsCOMPtr<nsIFile> customDirectory;
+ prefBranch->GetComplexValue(NS_PREF_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(customDirectory));
+ if (customDirectory) {
+ bool exists = false;
+ (void)customDirectory->Exists(&exists);
+
+ if (!exists) {
+ rv = customDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_SUCCEEDED(rv)) {
+ customDirectory.forget(aResult);
+ return NS_OK;
+ }
+
+ // Create failed, so it still doesn't exist. Fall out and get the
+ // default downloads directory.
+ }
+
+ bool writable = false;
+ bool directory = false;
+ (void)customDirectory->IsWritable(&writable);
+ (void)customDirectory->IsDirectory(&directory);
+
+ if (exists && writable && directory) {
+ customDirectory.forget(aResult);
+ return NS_OK;
+ }
+ }
+ rv = GetDefaultDownloadsDirectory(aResult);
+ if (NS_SUCCEEDED(rv)) {
+ (void)prefBranch->SetComplexValue(NS_PREF_DIR,
+ NS_GET_IID(nsIFile),
+ *aResult);
+ }
+ return rv;
+ }
+ break;
+ }
+ return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::AddDownload(DownloadType aDownloadType,
+ nsIURI *aSource,
+ nsIURI *aTarget,
+ const nsAString& aDisplayName,
+ nsIMIMEInfo *aMIMEInfo,
+ PRTime aStartTime,
+ nsIFile *aTempFile,
+ nsICancelable *aCancelable,
+ bool aIsPrivate,
+ nsIDownload **aDownload)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_ENSURE_ARG_POINTER(aSource);
+ NS_ENSURE_ARG_POINTER(aTarget);
+ NS_ENSURE_ARG_POINTER(aDownload);
+
+ nsresult rv;
+
+ // target must be on the local filesystem
+ nsCOMPtr<nsIFileURL> targetFileURL = do_QueryInterface(aTarget, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> targetFile;
+ rv = targetFileURL->GetFile(getter_AddRefs(targetFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsDownload> dl = new nsDownload();
+ if (!dl)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // give our new nsIDownload some info so it's ready to go off into the world
+ dl->mTarget = aTarget;
+ dl->mSource = aSource;
+ dl->mTempFile = aTempFile;
+ dl->mPrivate = aIsPrivate;
+
+ dl->mDisplayName = aDisplayName;
+ if (dl->mDisplayName.IsEmpty())
+ targetFile->GetLeafName(dl->mDisplayName);
+
+ dl->mMIMEInfo = aMIMEInfo;
+ dl->SetStartTime(aStartTime == 0 ? PR_Now() : aStartTime);
+
+ // Creates a cycle that will be broken when the download finishes
+ dl->mCancelable = aCancelable;
+
+ // Adding to the DB
+ nsAutoCString source, target;
+ rv = aSource->GetSpec(source);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aTarget->GetSpec(target);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Track the temp file for exthandler downloads
+ nsAutoString tempPath;
+ if (aTempFile)
+ aTempFile->GetPath(tempPath);
+
+ // Break down MIMEInfo but don't panic if we can't get all the pieces - we
+ // can still download the file
+ nsAutoCString persistentDescriptor, mimeType;
+ nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
+ if (aMIMEInfo) {
+ (void)aMIMEInfo->GetType(mimeType);
+
+ nsCOMPtr<nsIHandlerApp> handlerApp;
+ (void)aMIMEInfo->GetPreferredApplicationHandler(getter_AddRefs(handlerApp));
+ nsCOMPtr<nsILocalHandlerApp> locHandlerApp = do_QueryInterface(handlerApp);
+
+ if (locHandlerApp) {
+ nsCOMPtr<nsIFile> executable;
+ (void)locHandlerApp->GetExecutable(getter_AddRefs(executable));
+ Unused << executable->GetPersistentDescriptor(persistentDescriptor);
+ }
+
+ (void)aMIMEInfo->GetPreferredAction(&action);
+ }
+
+ int64_t id = AddDownloadToDB(dl->mDisplayName, source, target, tempPath,
+ dl->mStartTime, dl->mLastUpdate,
+ mimeType, persistentDescriptor, action,
+ dl->mPrivate, dl->mGUID /* outparam */);
+ NS_ENSURE_TRUE(id, NS_ERROR_FAILURE);
+ dl->mID = id;
+
+ rv = AddToCurrentDownloads(dl);
+ (void)dl->SetState(nsIDownloadManager::DOWNLOAD_QUEUED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef DOWNLOAD_SCANNER
+ if (mScanner) {
+ bool scan = true;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void)prefs->GetBoolPref(PREF_BDM_SCANWHENDONE, &scan);
+ }
+ // We currently apply local security policy to downloads when we scan
+ // via windows all-in-one download security api. The CheckPolicy call
+ // below is a pre-emptive part of that process. So tie applying security
+ // zone policy settings when downloads are intiated to the same pref
+ // that triggers applying security zone policy settings after a download
+ // completes. (bug 504804)
+ if (scan) {
+ AVCheckPolicyState res = mScanner->CheckPolicy(aSource, aTarget);
+ if (res == AVPOLICY_BLOCKED) {
+ // This download will get deleted during a call to IAE's Save,
+ // so go ahead and mark it as blocked and avoid the download.
+ (void)CancelDownload(id);
+ (void)dl->SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY);
+ }
+ }
+ }
+#endif
+
+ // Check with parental controls to see if file downloads
+ // are allowed for this user. If not allowed, cancel the
+ // download and mark its state as being blocked.
+ nsCOMPtr<nsIParentalControlsService> pc =
+ do_CreateInstance(NS_PARENTALCONTROLSSERVICE_CONTRACTID);
+ if (pc) {
+ bool enabled = false;
+ (void)pc->GetBlockFileDownloadsEnabled(&enabled);
+ if (enabled) {
+ (void)CancelDownload(id);
+ (void)dl->SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL);
+ }
+
+ // Log the event if required by pc settings.
+ bool logEnabled = false;
+ (void)pc->GetLoggingEnabled(&logEnabled);
+ if (logEnabled) {
+ (void)pc->Log(nsIParentalControlsService::ePCLog_FileDownload,
+ enabled,
+ aSource,
+ nullptr);
+ }
+ }
+
+ dl.forget(aDownload);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetDownload(uint32_t aID, nsIDownload **aDownloadItem)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ nsDownload *itm = FindDownload(aID);
+
+ RefPtr<nsDownload> dl;
+ if (!itm) {
+ nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ itm = dl.get();
+ }
+
+ NS_ADDREF(*aDownloadItem = itm);
+
+ return NS_OK;
+}
+
+namespace {
+class AsyncResult : public Runnable
+{
+public:
+ AsyncResult(nsresult aStatus, nsIDownload* aResult,
+ nsIDownloadManagerResult* aCallback)
+ : mStatus(aStatus), mResult(aResult), mCallback(aCallback)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mCallback->HandleResult(mStatus, mResult);
+ return NS_OK;
+ }
+
+private:
+ nsresult mStatus;
+ nsCOMPtr<nsIDownload> mResult;
+ nsCOMPtr<nsIDownloadManagerResult> mCallback;
+};
+} // namespace
+
+NS_IMETHODIMP
+nsDownloadManager::GetDownloadByGUID(const nsACString& aGUID,
+ nsIDownloadManagerResult* aCallback)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ nsDownload *itm = FindDownload(aGUID);
+
+ nsresult rv = NS_OK;
+ RefPtr<nsDownload> dl;
+ if (!itm) {
+ rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
+ itm = dl.get();
+ }
+
+ RefPtr<AsyncResult> runnable = new AsyncResult(rv, itm, aCallback);
+ NS_DispatchToMainThread(runnable);
+ return NS_OK;
+}
+
+nsDownload *
+nsDownloadManager::FindDownload(uint32_t aID)
+{
+ // we shouldn't ever have many downloads, so we can loop over them
+ for (int32_t i = mCurrentDownloads.Count() - 1; i >= 0; --i) {
+ nsDownload *dl = mCurrentDownloads[i];
+ if (dl->mID == aID)
+ return dl;
+ }
+
+ return nullptr;
+}
+
+nsDownload *
+nsDownloadManager::FindDownload(const nsACString& aGUID)
+{
+ // we shouldn't ever have many downloads, so we can loop over them
+ for (int32_t i = mCurrentDownloads.Count() - 1; i >= 0; --i) {
+ nsDownload *dl = mCurrentDownloads[i];
+ if (dl->mGUID == aGUID)
+ return dl;
+ }
+
+ for (int32_t i = mCurrentPrivateDownloads.Count() - 1; i >= 0; --i) {
+ nsDownload *dl = mCurrentPrivateDownloads[i];
+ if (dl->mGUID == aGUID)
+ return dl;
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::CancelDownload(uint32_t aID)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ // We AddRef here so we don't lose access to member variables when we remove
+ RefPtr<nsDownload> dl = FindDownload(aID);
+
+ // if it's null, someone passed us a bad id.
+ if (!dl)
+ return NS_ERROR_FAILURE;
+
+ return dl->Cancel();
+}
+
+nsresult
+nsDownloadManager::RetryDownload(const nsACString& aGUID)
+{
+ RefPtr<nsDownload> dl;
+ nsresult rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RetryDownload(dl);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::RetryDownload(uint32_t aID)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ RefPtr<nsDownload> dl;
+ nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RetryDownload(dl);
+}
+
+nsresult
+nsDownloadManager::RetryDownload(nsDownload* dl)
+{
+ // if our download is not canceled or failed, we should fail
+ if (dl->mDownloadState != nsIDownloadManager::DOWNLOAD_FAILED &&
+ dl->mDownloadState != nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL &&
+ dl->mDownloadState != nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY &&
+ dl->mDownloadState != nsIDownloadManager::DOWNLOAD_DIRTY &&
+ dl->mDownloadState != nsIDownloadManager::DOWNLOAD_CANCELED)
+ return NS_ERROR_FAILURE;
+
+ // If the download has failed and is resumable then we first try resuming it
+ nsresult rv;
+ if (dl->mDownloadState == nsIDownloadManager::DOWNLOAD_FAILED && dl->IsResumable()) {
+ rv = dl->Resume();
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+
+ rv = NotifyDownloadRemoval(dl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // reset time and download progress
+ dl->SetStartTime(PR_Now());
+ dl->SetProgressBytes(0, -1);
+
+ nsCOMPtr<nsIWebBrowserPersist> wbp =
+ do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = wbp->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddToCurrentDownloads(dl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dl->SetState(nsIDownloadManager::DOWNLOAD_QUEUED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Creates a cycle that will be broken when the download finishes
+ dl->mCancelable = wbp;
+ (void)wbp->SetProgressListener(dl);
+
+ // referrer policy can be anything since referrer is nullptr
+ rv = wbp->SavePrivacyAwareURI(dl->mSource, nullptr,
+ nullptr, mozilla::net::RP_Default,
+ nullptr, nullptr,
+ dl->mTarget, dl->mPrivate);
+ if (NS_FAILED(rv)) {
+ dl->mCancelable = nullptr;
+ (void)wbp->SetProgressListener(nullptr);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+RemoveDownloadByGUID(const nsACString& aGUID, mozIStorageConnection* aDBConn)
+{
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_downloads "
+ "WHERE guid = :guid"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloadManager::RemoveDownload(const nsACString& aGUID)
+{
+ RefPtr<nsDownload> dl = FindDownload(aGUID);
+ MOZ_ASSERT(!dl, "Can't call RemoveDownload on a download in progress!");
+ if (dl)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = GetDownloadFromDB(aGUID, getter_AddRefs(dl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dl->mPrivate) {
+ RemoveDownloadByGUID(aGUID, mPrivateDBConn);
+ } else {
+ RemoveDownloadByGUID(aGUID, mDBConn);
+ }
+
+ return NotifyDownloadRemoval(dl);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::RemoveDownload(uint32_t aID)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ RefPtr<nsDownload> dl = FindDownload(aID);
+ MOZ_ASSERT(!dl, "Can't call RemoveDownload on a download in progress!");
+ if (dl)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = GetDownloadFromDB(aID, getter_AddRefs(dl));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_downloads "
+ "WHERE id = :id"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aID); // unsigned; 64-bit to prevent overflow
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Notify the UI with the topic and download id
+ return NotifyDownloadRemoval(dl);
+}
+
+nsresult
+nsDownloadManager::NotifyDownloadRemoval(nsDownload* aRemoved)
+{
+ nsCOMPtr<nsISupportsPRUint32> id;
+ nsCOMPtr<nsISupportsCString> guid;
+ nsresult rv;
+
+ // Only send an integer ID notification if the download is public.
+ bool sendDeprecatedNotification = !(aRemoved && aRemoved->mPrivate);
+
+ if (sendDeprecatedNotification && aRemoved) {
+ id = do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t dlID;
+ rv = aRemoved->GetId(&dlID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = id->SetData(dlID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (sendDeprecatedNotification) {
+ mObserverService->NotifyObservers(id,
+ "download-manager-remove-download",
+ nullptr);
+ }
+
+ if (aRemoved) {
+ guid = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString guidStr;
+ rv = aRemoved->GetGuid(guidStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = guid->SetData(guidStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mObserverService->NotifyObservers(guid,
+ "download-manager-remove-download-guid",
+ nullptr);
+ return NS_OK;
+}
+
+static nsresult
+DoRemoveDownloadsByTimeframe(mozIStorageConnection* aDBConn,
+ int64_t aStartTime,
+ int64_t aEndTime)
+{
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_downloads "
+ "WHERE startTime >= :startTime "
+ "AND startTime <= :endTime "
+ "AND state NOT IN (:downloading, :paused, :queued)"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bind the times
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), aStartTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), aEndTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bind the active states
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("downloading"), nsIDownloadManager::DOWNLOAD_DOWNLOADING);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("paused"), nsIDownloadManager::DOWNLOAD_PAUSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("queued"), nsIDownloadManager::DOWNLOAD_QUEUED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Execute
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::RemoveDownloadsByTimeframe(int64_t aStartTime,
+ int64_t aEndTime)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ nsresult rv = DoRemoveDownloadsByTimeframe(mDBConn, aStartTime, aEndTime);
+ nsresult rv2 = DoRemoveDownloadsByTimeframe(mPrivateDBConn, aStartTime, aEndTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+
+ // Notify the UI with the topic and null subject to indicate "remove multiple"
+ return NotifyDownloadRemoval(nullptr);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::CleanUp()
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return CleanUp(mDBConn);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::CleanUpPrivate()
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return CleanUp(mPrivateDBConn);
+}
+
+nsresult
+nsDownloadManager::CleanUp(mozIStorageConnection* aDBConn)
+{
+ DownloadState states[] = { nsIDownloadManager::DOWNLOAD_FINISHED,
+ nsIDownloadManager::DOWNLOAD_FAILED,
+ nsIDownloadManager::DOWNLOAD_CANCELED,
+ nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL,
+ nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY,
+ nsIDownloadManager::DOWNLOAD_DIRTY };
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_downloads "
+ "WHERE state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ?"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < ArrayLength(states); ++i) {
+ rv = stmt->BindInt32ByIndex(i, states[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Notify the UI with the topic and null subject to indicate "remove multiple"
+ return NotifyDownloadRemoval(nullptr);
+}
+
+static nsresult
+DoGetCanCleanUp(mozIStorageConnection* aDBConn, bool *aResult)
+{
+ // This method should never return anything but NS_OK for the benefit of
+ // unwitting consumers.
+
+ *aResult = false;
+
+ DownloadState states[] = { nsIDownloadManager::DOWNLOAD_FINISHED,
+ nsIDownloadManager::DOWNLOAD_FAILED,
+ nsIDownloadManager::DOWNLOAD_CANCELED,
+ nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL,
+ nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY,
+ nsIDownloadManager::DOWNLOAD_DIRTY };
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT COUNT(*) "
+ "FROM moz_downloads "
+ "WHERE state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ? "
+ "OR state = ?"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ for (uint32_t i = 0; i < ArrayLength(states); ++i) {
+ rv = stmt->BindInt32ByIndex(i, states[i]);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ }
+
+ bool moreResults; // We don't really care...
+ rv = stmt->ExecuteStep(&moreResults);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ int32_t count;
+ rv = stmt->GetInt32(0, &count);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ if (count > 0)
+ *aResult = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetCanCleanUp(bool *aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return DoGetCanCleanUp(mDBConn, aResult);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetCanCleanUpPrivate(bool *aResult)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ return DoGetCanCleanUp(mPrivateDBConn, aResult);
+}
+
+NS_IMETHODIMP
+nsDownloadManager::PauseDownload(uint32_t aID)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ nsDownload *dl = FindDownload(aID);
+ if (!dl)
+ return NS_ERROR_FAILURE;
+
+ return dl->Pause();
+}
+
+NS_IMETHODIMP
+nsDownloadManager::ResumeDownload(uint32_t aID)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_WARNING("Using integer IDs without compat mode enabled");
+
+ nsDownload *dl = FindDownload(aID);
+ if (!dl)
+ return NS_ERROR_FAILURE;
+
+ return dl->Resume();
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetDBConnection(mozIStorageConnection **aDBConn)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_ADDREF(*aDBConn = mDBConn);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::GetPrivateDBConnection(mozIStorageConnection **aDBConn)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ NS_ADDREF(*aDBConn = mPrivateDBConn);
+
+ return NS_OK;
+ }
+
+NS_IMETHODIMP
+nsDownloadManager::AddListener(nsIDownloadProgressListener *aListener)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ mListeners.AppendObject(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::AddPrivacyAwareListener(nsIDownloadProgressListener *aListener)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ mPrivacyAwareListeners.AppendObject(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::RemoveListener(nsIDownloadProgressListener *aListener)
+{
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ mListeners.RemoveObject(aListener);
+ mPrivacyAwareListeners.RemoveObject(aListener);
+ return NS_OK;
+}
+
+void
+nsDownloadManager::NotifyListenersOnDownloadStateChange(int16_t aOldState,
+ nsDownload *aDownload)
+{
+ for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
+ mPrivacyAwareListeners[i]->OnDownloadStateChange(aOldState, aDownload);
+ }
+
+ // Only privacy-aware listeners should receive notifications about private
+ // downloads, while non-privacy-aware listeners receive no sign they exist.
+ if (aDownload->mPrivate) {
+ return;
+ }
+
+ for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
+ mListeners[i]->OnDownloadStateChange(aOldState, aDownload);
+ }
+}
+
+void
+nsDownloadManager::NotifyListenersOnProgressChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ int64_t aCurSelfProgress,
+ int64_t aMaxSelfProgress,
+ int64_t aCurTotalProgress,
+ int64_t aMaxTotalProgress,
+ nsDownload *aDownload)
+{
+ for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
+ mPrivacyAwareListeners[i]->OnProgressChange(aProgress, aRequest, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress, aDownload);
+ }
+
+ // Only privacy-aware listeners should receive notifications about private
+ // downloads, while non-privacy-aware listeners receive no sign they exist.
+ if (aDownload->mPrivate) {
+ return;
+ }
+
+ for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
+ mListeners[i]->OnProgressChange(aProgress, aRequest, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress, aDownload);
+ }
+}
+
+void
+nsDownloadManager::NotifyListenersOnStateChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus,
+ nsDownload *aDownload)
+{
+ for (int32_t i = mPrivacyAwareListeners.Count() - 1; i >= 0; --i) {
+ mPrivacyAwareListeners[i]->OnStateChange(aProgress, aRequest, aStateFlags, aStatus,
+ aDownload);
+ }
+
+ // Only privacy-aware listeners should receive notifications about private
+ // downloads, while non-privacy-aware listeners receive no sign they exist.
+ if (aDownload->mPrivate) {
+ return;
+ }
+
+ for (int32_t i = mListeners.Count() - 1; i >= 0; --i) {
+ mListeners[i]->OnStateChange(aProgress, aRequest, aStateFlags, aStatus,
+ aDownload);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsINavHistoryObserver
+
+NS_IMETHODIMP
+nsDownloadManager::OnBeginUpdateBatch()
+{
+ // This method in not normally invoked when mUseJSTransfer is enabled, however
+ // we provide an extra check in case it is called manually by add-ons.
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ // We already have a transaction, so don't make another
+ if (mHistoryTransaction)
+ return NS_OK;
+
+ // Start a transaction that commits when deleted
+ mHistoryTransaction = new mozStorageTransaction(mDBConn, true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnEndUpdateBatch()
+{
+ // Get rid of the transaction and cause it to commit
+ mHistoryTransaction = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnVisit(nsIURI *aURI, int64_t aVisitID, PRTime aTime,
+ int64_t aSessionID, int64_t aReferringID,
+ uint32_t aTransitionType, const nsACString& aGUID,
+ bool aHidden, uint32_t aVisitCount, uint32_t aTyped)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnTitleChanged(nsIURI *aURI,
+ const nsAString &aPageTitle,
+ const nsACString &aGUID)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnFrecencyChanged(nsIURI* aURI,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnManyFrecenciesChanged()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnDeleteURI(nsIURI *aURI,
+ const nsACString& aGUID,
+ uint16_t aReason)
+{
+ // This method in not normally invoked when mUseJSTransfer is enabled, however
+ // we provide an extra check in case it is called manually by add-ons.
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ nsresult rv = RemoveDownloadsForURI(mGetIdsForURIStatement, aURI);
+ nsresult rv2 = RemoveDownloadsForURI(mGetPrivateIdsForURIStatement, aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnClearHistory()
+{
+ return CleanUp();
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnPageChanged(nsIURI *aURI,
+ uint32_t aChangedAttribute,
+ const nsAString& aNewValue,
+ const nsACString &aGUID)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownloadManager::OnDeleteVisits(nsIURI *aURI, PRTime aVisitTime,
+ const nsACString& aGUID,
+ uint16_t aReason, uint32_t aTransitionType)
+{
+ // Don't bother removing downloads until the page is removed.
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+nsDownloadManager::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ // This method in not normally invoked when mUseJSTransfer is enabled, however
+ // we provide an extra check in case it is called manually by add-ons.
+ NS_ENSURE_STATE(!mUseJSTransfer);
+
+ // We need to count the active public downloads that could be lost
+ // by quitting, and add any active private ones as well, since per-window
+ // private browsing may be active.
+ int32_t currDownloadCount = mCurrentDownloads.Count();
+
+ // If we don't need to cancel all the downloads on quit, only count the ones
+ // that aren't resumable.
+ if (GetQuitBehavior() != QUIT_AND_CANCEL) {
+ for (int32_t i = currDownloadCount - 1; i >= 0; --i) {
+ if (mCurrentDownloads[i]->IsResumable()) {
+ currDownloadCount--;
+ }
+ }
+
+ // We have a count of the public, non-resumable downloads. Now we need
+ // to add the total number of private downloads, since they are in danger
+ // of being lost.
+ currDownloadCount += mCurrentPrivateDownloads.Count();
+ }
+
+ nsresult rv;
+ if (strcmp(aTopic, "oncancel") == 0) {
+ nsCOMPtr<nsIDownload> dl = do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dl->Cancel();
+ } else if (strcmp(aTopic, "profile-before-change") == 0) {
+ CloseAllDBs();
+ } else if (strcmp(aTopic, "quit-application") == 0) {
+ // Try to pause all downloads and, if appropriate, mark them as auto-resume
+ // unless user has specified that downloads should be canceled
+ enum QuitBehavior behavior = GetQuitBehavior();
+ if (behavior != QUIT_AND_CANCEL)
+ (void)PauseAllDownloads(bool(behavior != QUIT_AND_PAUSE));
+
+ // Remove downloads to break cycles and cancel downloads
+ (void)RemoveAllDownloads();
+
+ // Now that active downloads have been canceled, remove all completed or
+ // aborted downloads if the user's retention policy specifies it.
+ if (GetRetentionBehavior() == 1)
+ CleanUp();
+ } else if (strcmp(aTopic, "quit-application-requested") == 0 &&
+ currDownloadCount) {
+ nsCOMPtr<nsISupportsPRBool> cancelDownloads =
+ do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
+ u"quitCancelDownloadsAlertTitle",
+ u"quitCancelDownloadsAlertMsgMultiple",
+ u"quitCancelDownloadsAlertMsg",
+ u"dontQuitButtonWin");
+ } else if (strcmp(aTopic, "offline-requested") == 0 && currDownloadCount) {
+ nsCOMPtr<nsISupportsPRBool> cancelDownloads =
+ do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
+ u"offlineCancelDownloadsAlertTitle",
+ u"offlineCancelDownloadsAlertMsgMultiple",
+ u"offlineCancelDownloadsAlertMsg",
+ u"dontGoOfflineButton");
+ }
+ else if (strcmp(aTopic, NS_IOSERVICE_GOING_OFFLINE_TOPIC) == 0) {
+ // Pause all downloads, and mark them to auto-resume.
+ (void)PauseAllDownloads(true);
+ }
+ else if (strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) == 0 &&
+ nsDependentString(aData).EqualsLiteral(NS_IOSERVICE_ONLINE)) {
+ // We can now resume all downloads that are supposed to auto-resume.
+ (void)ResumeAllDownloads(false);
+ }
+ else if (strcmp(aTopic, "alertclickcallback") == 0) {
+ nsCOMPtr<nsIDownloadManagerUI> dmui =
+ do_GetService("@mozilla.org/download-manager-ui;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return dmui->Show(nullptr, nullptr, nsIDownloadManagerUI::REASON_USER_INTERACTED,
+ aData && NS_strcmp(aData, u"private") == 0);
+ } else if (strcmp(aTopic, "sleep_notification") == 0 ||
+ strcmp(aTopic, "suspend_process_notification") == 0) {
+ // Pause downloads if we're sleeping, and mark the downloads as auto-resume
+ (void)PauseAllDownloads(true);
+ } else if (strcmp(aTopic, "wake_notification") == 0 ||
+ strcmp(aTopic, "resume_process_notification") == 0) {
+ int32_t resumeOnWakeDelay = 10000;
+ nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (pref)
+ (void)pref->GetIntPref(PREF_BDM_RESUMEONWAKEDELAY, &resumeOnWakeDelay);
+
+ // Wait a little bit before trying to resume to avoid resuming when network
+ // connections haven't restarted yet
+ mResumeOnWakeTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (resumeOnWakeDelay >= 0 && mResumeOnWakeTimer) {
+ (void)mResumeOnWakeTimer->InitWithFuncCallback(ResumeOnWakeCallback,
+ this, resumeOnWakeDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+ } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
+ // Upon leaving private browsing mode, cancel all private downloads,
+ // remove all trace of them, and then blow away the private database
+ // and recreate a blank one.
+ RemoveAllDownloads(mCurrentPrivateDownloads);
+ InitPrivateDB();
+ } else if (strcmp(aTopic, "last-pb-context-exiting") == 0) {
+ // If there are active private downloads, prompt the user to confirm leaving
+ // private browsing mode (thereby cancelling them). Otherwise, silently proceed.
+ if (!mCurrentPrivateDownloads.Count())
+ return NS_OK;
+
+ nsCOMPtr<nsISupportsPRBool> cancelDownloads = do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ConfirmCancelDownloads(mCurrentPrivateDownloads.Count(), cancelDownloads,
+ u"leavePrivateBrowsingCancelDownloadsAlertTitle",
+ u"leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2",
+ u"leavePrivateBrowsingWindowsCancelDownloadsAlertMsg2",
+ u"dontLeavePrivateBrowsingButton2");
+ }
+
+ return NS_OK;
+}
+
+void
+nsDownloadManager::ConfirmCancelDownloads(int32_t aCount,
+ nsISupportsPRBool *aCancelDownloads,
+ const char16_t *aTitle,
+ const char16_t *aCancelMessageMultiple,
+ const char16_t *aCancelMessageSingle,
+ const char16_t *aDontCancelButton)
+{
+ // If user has already dismissed quit request, then do nothing
+ bool quitRequestCancelled = false;
+ aCancelDownloads->GetData(&quitRequestCancelled);
+ if (quitRequestCancelled)
+ return;
+
+ nsXPIDLString title, message, quitButton, dontQuitButton;
+
+ mBundle->GetStringFromName(aTitle, getter_Copies(title));
+
+ nsAutoString countString;
+ countString.AppendInt(aCount);
+ const char16_t *strings[1] = { countString.get() };
+ if (aCount > 1) {
+ mBundle->FormatStringFromName(aCancelMessageMultiple, strings, 1,
+ getter_Copies(message));
+ mBundle->FormatStringFromName(u"cancelDownloadsOKTextMultiple",
+ strings, 1, getter_Copies(quitButton));
+ } else {
+ mBundle->GetStringFromName(aCancelMessageSingle, getter_Copies(message));
+ mBundle->GetStringFromName(u"cancelDownloadsOKText",
+ getter_Copies(quitButton));
+ }
+
+ mBundle->GetStringFromName(aDontCancelButton, getter_Copies(dontQuitButton));
+
+ // Get Download Manager window, to be parent of alert.
+ nsCOMPtr<nsIWindowMediator> wm = do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+ nsCOMPtr<mozIDOMWindowProxy> dmWindow;
+ if (wm) {
+ wm->GetMostRecentWindow(u"Download:Manager",
+ getter_AddRefs(dmWindow));
+ }
+
+ // Show alert.
+ nsCOMPtr<nsIPromptService> prompter(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
+ if (prompter) {
+ int32_t flags = (nsIPromptService::BUTTON_TITLE_IS_STRING * nsIPromptService::BUTTON_POS_0) + (nsIPromptService::BUTTON_TITLE_IS_STRING * nsIPromptService::BUTTON_POS_1);
+ bool nothing = false;
+ int32_t button;
+ prompter->ConfirmEx(dmWindow, title, message, flags, quitButton.get(), dontQuitButton.get(), nullptr, nullptr, &nothing, &button);
+
+ aCancelDownloads->SetData(button == 1);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsDownload
+
+NS_IMPL_CLASSINFO(nsDownload, nullptr, 0, NS_DOWNLOAD_CID)
+NS_IMPL_ISUPPORTS_CI(
+ nsDownload
+ , nsIDownload
+ , nsITransfer
+ , nsIWebProgressListener
+ , nsIWebProgressListener2
+)
+
+nsDownload::nsDownload() : mDownloadState(nsIDownloadManager::DOWNLOAD_NOTSTARTED),
+ mID(0),
+ mPercentComplete(0),
+ mCurrBytes(0),
+ mMaxBytes(-1),
+ mStartTime(0),
+ mLastUpdate(PR_Now() - (uint32_t)gUpdateInterval),
+ mResumedAt(-1),
+ mSpeed(0),
+ mHasMultipleFiles(false),
+ mPrivate(false),
+ mAutoResume(DONT_RESUME)
+{
+}
+
+nsDownload::~nsDownload()
+{
+}
+
+NS_IMETHODIMP nsDownload::SetSha256Hash(const nsACString& aHash) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must call SetSha256Hash on main thread");
+ // This will be used later to query the application reputation service.
+ mHash = aHash;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDownload::SetSignatureInfo(nsIArray* aSignatureInfo) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must call SetSignatureInfo on main thread");
+ // This will be used later to query the application reputation service.
+ mSignatureInfo = aSignatureInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDownload::SetRedirects(nsIArray* aRedirects) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must call SetRedirects on main thread");
+ // This will be used later to query the application reputation service.
+ mRedirects = aRedirects;
+ return NS_OK;
+}
+
+#ifdef MOZ_ENABLE_GIO
+static void gio_set_metadata_done(GObject *source_obj, GAsyncResult *res, gpointer user_data)
+{
+ GError *err = nullptr;
+ g_file_set_attributes_finish(G_FILE(source_obj), res, nullptr, &err);
+ if (err) {
+#ifdef DEBUG
+ NS_DebugBreak(NS_DEBUG_WARNING, "Set file metadata failed: ", err->message, __FILE__, __LINE__);
+#endif
+ g_error_free(err);
+ }
+}
+#endif
+
+nsresult
+nsDownload::SetState(DownloadState aState)
+{
+ NS_ASSERTION(mDownloadState != aState,
+ "Trying to set the download state to what it already is set to!");
+
+ int16_t oldState = mDownloadState;
+ mDownloadState = aState;
+
+ // We don't want to lose access to our member variables
+ RefPtr<nsDownload> kungFuDeathGrip = this;
+
+ // When the state changed listener is dispatched, queries to the database and
+ // the download manager api should reflect what the nsIDownload object would
+ // return. So, if a download is done (finished, canceled, etc.), it should
+ // first be removed from the current downloads. We will also have to update
+ // the database *before* notifying listeners. At this point, you can safely
+ // dispatch to the observers as well.
+ switch (aState) {
+ case nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL:
+ case nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY:
+ case nsIDownloadManager::DOWNLOAD_DIRTY:
+ case nsIDownloadManager::DOWNLOAD_CANCELED:
+ case nsIDownloadManager::DOWNLOAD_FAILED:
+
+ // Transfers are finished, so break the reference cycle
+ Finalize();
+ break;
+#ifdef DOWNLOAD_SCANNER
+ case nsIDownloadManager::DOWNLOAD_SCANNING:
+ {
+ nsresult rv = mDownloadManager->mScanner ? mDownloadManager->mScanner->ScanDownload(this) : NS_ERROR_NOT_INITIALIZED;
+ // If we failed, then fall through to 'download finished'
+ if (NS_SUCCEEDED(rv))
+ break;
+ mDownloadState = aState = nsIDownloadManager::DOWNLOAD_FINISHED;
+ }
+#endif
+ case nsIDownloadManager::DOWNLOAD_FINISHED:
+ {
+ nsresult rv = ExecuteDesiredAction();
+ if (NS_FAILED(rv)) {
+ // We've failed to execute the desired action. As a result, we should
+ // fail the download so the user can try again.
+ (void)FailDownload(rv, nullptr);
+ return rv;
+ }
+
+ // Now that we're done with handling the download, clean it up
+ Finalize();
+
+ nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ // Master pref to control this function.
+ bool showTaskbarAlert = true;
+ if (pref)
+ pref->GetBoolPref(PREF_BDM_SHOWALERTONCOMPLETE, &showTaskbarAlert);
+
+ if (showTaskbarAlert) {
+ int32_t alertInterval = 2000;
+ if (pref)
+ pref->GetIntPref(PREF_BDM_SHOWALERTINTERVAL, &alertInterval);
+
+ int64_t alertIntervalUSec = alertInterval * PR_USEC_PER_MSEC;
+ int64_t goat = PR_Now() - mStartTime;
+ showTaskbarAlert = goat > alertIntervalUSec;
+
+ int32_t size = mPrivate ?
+ mDownloadManager->mCurrentPrivateDownloads.Count() :
+ mDownloadManager->mCurrentDownloads.Count();
+ if (showTaskbarAlert && size == 0) {
+ nsCOMPtr<nsIAlertsService> alerts =
+ do_GetService("@mozilla.org/alerts-service;1");
+ if (alerts) {
+ nsXPIDLString title, message;
+
+ mDownloadManager->mBundle->GetStringFromName(
+ u"downloadsCompleteTitle",
+ getter_Copies(title));
+ mDownloadManager->mBundle->GetStringFromName(
+ u"downloadsCompleteMsg",
+ getter_Copies(message));
+
+ bool removeWhenDone =
+ mDownloadManager->GetRetentionBehavior() == 0;
+
+ // If downloads are automatically removed per the user's
+ // retention policy, there's no reason to make the text clickable
+ // because if it is, they'll click open the download manager and
+ // the items they downloaded will have been removed.
+ alerts->ShowAlertNotification(
+ NS_LITERAL_STRING(DOWNLOAD_MANAGER_ALERT_ICON), title,
+ message, !removeWhenDone,
+ mPrivate ? NS_LITERAL_STRING("private") : NS_LITERAL_STRING("non-private"),
+ mDownloadManager, EmptyString(), NS_LITERAL_STRING("auto"),
+ EmptyString(), EmptyString(), nullptr, mPrivate,
+ false /* requireInteraction */);
+ }
+ }
+ }
+
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mTarget);
+ nsCOMPtr<nsIFile> file;
+ nsAutoString path;
+
+ if (fileURL &&
+ NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
+ file &&
+ NS_SUCCEEDED(file->GetPath(path))) {
+
+ // On Windows and Gtk, add the download to the system's "recent documents"
+ // list, with a pref to disable.
+ {
+ bool addToRecentDocs = true;
+ if (pref)
+ pref->GetBoolPref(PREF_BDM_ADDTORECENTDOCS, &addToRecentDocs);
+ if (addToRecentDocs && !mPrivate) {
+#ifdef XP_WIN
+ ::SHAddToRecentDocs(SHARD_PATHW, path.get());
+#elif defined(MOZ_WIDGET_GTK)
+ GtkRecentManager* manager = gtk_recent_manager_get_default();
+
+ gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),
+ nullptr, nullptr);
+ if (uri) {
+ gtk_recent_manager_add_item(manager, uri);
+ g_free(uri);
+ }
+#endif
+ }
+#ifdef MOZ_ENABLE_GIO
+ // Use GIO to store the source URI for later display in the file manager.
+ GFile* gio_file = g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());
+ nsCString source_uri;
+ rv = mSource->GetSpec(source_uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ GFileInfo *file_info = g_file_info_new();
+ g_file_info_set_attribute_string(file_info, "metadata::download-uri", source_uri.get());
+ g_file_set_attributes_async(gio_file,
+ file_info,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ nullptr, gio_set_metadata_done, nullptr);
+ g_object_unref(file_info);
+ g_object_unref(gio_file);
+#endif
+ }
+ }
+
+#ifdef XP_WIN
+ // Adjust file attributes so that by default, new files are indexed
+ // by desktop search services. Skip off those that land in the temp
+ // folder.
+ nsCOMPtr<nsIFile> tempDir, fileDir;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ (void)file->GetParent(getter_AddRefs(fileDir));
+
+ bool isTemp = false;
+ if (fileDir)
+ (void)fileDir->Equals(tempDir, &isTemp);
+
+ nsCOMPtr<nsILocalFileWin> localFileWin(do_QueryInterface(file));
+ if (!isTemp && localFileWin)
+ (void)localFileWin->SetFileAttributesWin(nsILocalFileWin::WFA_SEARCH_INDEXED);
+#endif
+
+#endif
+ // Now remove the download if the user's retention policy is "Remove when Done"
+ if (mDownloadManager->GetRetentionBehavior() == 0)
+ mDownloadManager->RemoveDownload(mGUID);
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Before notifying the listener, we must update the database so that calls
+ // to it work out properly.
+ nsresult rv = UpdateDB();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDownloadManager->NotifyListenersOnDownloadStateChange(oldState, this);
+
+ switch (mDownloadState) {
+ case nsIDownloadManager::DOWNLOAD_DOWNLOADING:
+ // Only send the dl-start event to downloads that are actually starting.
+ if (oldState == nsIDownloadManager::DOWNLOAD_QUEUED) {
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-start");
+ }
+ break;
+ case nsIDownloadManager::DOWNLOAD_FAILED:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-failed");
+ break;
+ case nsIDownloadManager::DOWNLOAD_SCANNING:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-scanning");
+ break;
+ case nsIDownloadManager::DOWNLOAD_FINISHED:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-done");
+ break;
+ case nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL:
+ case nsIDownloadManager::DOWNLOAD_BLOCKED_POLICY:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-blocked");
+ break;
+ case nsIDownloadManager::DOWNLOAD_DIRTY:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-dirty");
+ break;
+ case nsIDownloadManager::DOWNLOAD_CANCELED:
+ if (!mPrivate)
+ mDownloadManager->SendEvent(this, "dl-cancel");
+ break;
+ default:
+ break;
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIWebProgressListener2
+
+NS_IMETHODIMP
+nsDownload::OnProgressChange64(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int64_t aCurSelfProgress,
+ int64_t aMaxSelfProgress,
+ int64_t aCurTotalProgress,
+ int64_t aMaxTotalProgress)
+{
+ if (!mRequest)
+ mRequest = aRequest; // used for pause/resume
+
+ if (mDownloadState == nsIDownloadManager::DOWNLOAD_QUEUED) {
+ // Obtain the referrer
+ nsresult rv;
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ nsCOMPtr<nsIURI> referrer = mReferrer;
+ if (channel)
+ (void)NS_GetReferrerFromChannel(channel, getter_AddRefs(mReferrer));
+
+ // Restore the original referrer if the new one isn't useful
+ if (!mReferrer)
+ mReferrer = referrer;
+
+ // If we have a MIME info, we know that exthandler has already added this to
+ // the history, but if we do not, we'll have to add it ourselves.
+ if (!mMIMEInfo && !mPrivate) {
+ nsCOMPtr<nsIDownloadHistory> dh =
+ do_GetService(NS_DOWNLOADHISTORY_CONTRACTID);
+ if (dh)
+ (void)dh->AddDownload(mSource, mReferrer, mStartTime, mTarget);
+ }
+
+ // Fetch the entityID, but if we can't get it, don't panic (non-resumable)
+ nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(aRequest));
+ if (resumableChannel)
+ (void)resumableChannel->GetEntityID(mEntityID);
+
+ // Before we update the state and dispatch state notifications, we want to
+ // ensure that we have the correct state for this download with regards to
+ // its percent completion and size.
+ SetProgressBytes(0, aMaxTotalProgress);
+
+ // Update the state and the database
+ rv = SetState(nsIDownloadManager::DOWNLOAD_DOWNLOADING);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // filter notifications since they come in so frequently
+ PRTime now = PR_Now();
+ PRIntervalTime delta = now - mLastUpdate;
+ if (delta < gUpdateInterval)
+ return NS_OK;
+
+ mLastUpdate = now;
+
+ // Calculate the speed using the elapsed delta time and bytes downloaded
+ // during that time for more accuracy.
+ double elapsedSecs = double(delta) / PR_USEC_PER_SEC;
+ if (elapsedSecs > 0) {
+ double speed = double(aCurTotalProgress - mCurrBytes) / elapsedSecs;
+ if (mCurrBytes == 0) {
+ mSpeed = speed;
+ } else {
+ // Calculate 'smoothed average' of 10 readings.
+ mSpeed = mSpeed * 0.9 + speed * 0.1;
+ }
+ }
+
+ SetProgressBytes(aCurTotalProgress, aMaxTotalProgress);
+
+ // Report to the listener our real sizes
+ int64_t currBytes, maxBytes;
+ (void)GetAmountTransferred(&currBytes);
+ (void)GetSize(&maxBytes);
+ mDownloadManager->NotifyListenersOnProgressChange(
+ aWebProgress, aRequest, currBytes, maxBytes, currBytes, maxBytes, this);
+
+ // If the maximums are different, then there must be more than one file
+ if (aMaxSelfProgress != aMaxTotalProgress)
+ mHasMultipleFiles = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::OnRefreshAttempted(nsIWebProgress *aWebProgress,
+ nsIURI *aUri,
+ int32_t aDelay,
+ bool aSameUri,
+ bool *allowRefresh)
+{
+ *allowRefresh = true;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIWebProgressListener
+
+NS_IMETHODIMP
+nsDownload::OnProgressChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ return OnProgressChange64(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress);
+}
+
+NS_IMETHODIMP
+nsDownload::OnLocationChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, nsIURI *aLocation,
+ uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::OnStatusChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, nsresult aStatus,
+ const char16_t *aMessage)
+{
+ if (NS_FAILED(aStatus))
+ return FailDownload(aStatus, aMessage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::OnStateChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Must call OnStateChange in main thread");
+
+ // We don't want to lose access to our member variables
+ RefPtr<nsDownload> kungFuDeathGrip = this;
+
+ // Check if we're starting a request; the NETWORK flag is necessary to not
+ // pick up the START of *each* file but only for the whole request
+ if ((aStateFlags & STATE_START) && (aStateFlags & STATE_IS_NETWORK)) {
+ nsresult rv;
+ nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t status;
+ rv = channel->GetResponseStatus(&status);
+ // HTTP 450 - Blocked by parental control proxies
+ if (NS_SUCCEEDED(rv) && status == 450) {
+ // Cancel using the provided object
+ (void)Cancel();
+
+ // Fail the download
+ (void)SetState(nsIDownloadManager::DOWNLOAD_BLOCKED_PARENTAL);
+ }
+ }
+ } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK) &&
+ IsFinishable()) {
+ // We got both STOP and NETWORK so that means the whole request is done
+ // (and not just a single file if there are multiple files)
+ if (NS_SUCCEEDED(aStatus)) {
+ // We can't completely trust the bytes we've added up because we might be
+ // missing on some/all of the progress updates (especially from cache).
+ // Our best bet is the file itself, but if for some reason it's gone or
+ // if we have multiple files, the next best is what we've calculated.
+ int64_t fileSize;
+ nsCOMPtr<nsIFile> file;
+ // We need a nsIFile clone to deal with file size caching issues. :(
+ nsCOMPtr<nsIFile> clone;
+ if (!mHasMultipleFiles &&
+ NS_SUCCEEDED(GetTargetFile(getter_AddRefs(file))) &&
+ NS_SUCCEEDED(file->Clone(getter_AddRefs(clone))) &&
+ NS_SUCCEEDED(clone->GetFileSize(&fileSize)) && fileSize > 0) {
+ mCurrBytes = mMaxBytes = fileSize;
+
+ // If we resumed, keep the fact that we did and fix size calculations
+ if (WasResumed())
+ mResumedAt = 0;
+ } else if (mMaxBytes == -1) {
+ mMaxBytes = mCurrBytes;
+ } else {
+ mCurrBytes = mMaxBytes;
+ }
+
+ mPercentComplete = 100;
+ mLastUpdate = PR_Now();
+
+#ifdef DOWNLOAD_SCANNER
+ bool scan = true;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ (void)prefs->GetBoolPref(PREF_BDM_SCANWHENDONE, &scan);
+
+ if (scan)
+ (void)SetState(nsIDownloadManager::DOWNLOAD_SCANNING);
+ else
+ (void)SetState(nsIDownloadManager::DOWNLOAD_FINISHED);
+#else
+ (void)SetState(nsIDownloadManager::DOWNLOAD_FINISHED);
+#endif
+ } else if (aStatus != NS_BINDING_ABORTED) {
+ // We failed for some unknown reason -- fail with a generic message
+ (void)FailDownload(aStatus, nullptr);
+ }
+ }
+
+ mDownloadManager->NotifyListenersOnStateChange(aWebProgress, aRequest,
+ aStateFlags, aStatus, this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, uint32_t aState)
+{
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIDownload
+
+NS_IMETHODIMP
+nsDownload::Init(nsIURI *aSource,
+ nsIURI *aTarget,
+ const nsAString& aDisplayName,
+ nsIMIMEInfo *aMIMEInfo,
+ PRTime aStartTime,
+ nsIFile *aTempFile,
+ nsICancelable *aCancelable,
+ bool aIsPrivate)
+{
+ NS_WARNING("Huh...how did we get here?!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetState(int16_t *aState)
+{
+ *aState = mDownloadState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetDisplayName(nsAString &aDisplayName)
+{
+ aDisplayName = mDisplayName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetCancelable(nsICancelable **aCancelable)
+{
+ *aCancelable = mCancelable;
+ NS_IF_ADDREF(*aCancelable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetTarget(nsIURI **aTarget)
+{
+ *aTarget = mTarget;
+ NS_IF_ADDREF(*aTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetSource(nsIURI **aSource)
+{
+ *aSource = mSource;
+ NS_IF_ADDREF(*aSource);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetStartTime(int64_t *aStartTime)
+{
+ *aStartTime = mStartTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetPercentComplete(int32_t *aPercentComplete)
+{
+ *aPercentComplete = mPercentComplete;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetAmountTransferred(int64_t *aAmountTransferred)
+{
+ *aAmountTransferred = mCurrBytes + (WasResumed() ? mResumedAt : 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetSize(int64_t *aSize)
+{
+ *aSize = mMaxBytes + (WasResumed() && mMaxBytes != -1 ? mResumedAt : 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetMIMEInfo(nsIMIMEInfo **aMIMEInfo)
+{
+ *aMIMEInfo = mMIMEInfo;
+ NS_IF_ADDREF(*aMIMEInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetTargetFile(nsIFile **aTargetFile)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mTarget, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ file.forget(aTargetFile);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDownload::GetSpeed(double *aSpeed)
+{
+ *aSpeed = mSpeed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetId(uint32_t *aId)
+{
+ if (mPrivate) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aId = mID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetGuid(nsACString &aGUID)
+{
+ aGUID = mGUID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetReferrer(nsIURI **referrer)
+{
+ NS_IF_ADDREF(*referrer = mReferrer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetResumable(bool *resumable)
+{
+ *resumable = IsResumable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::GetIsPrivate(bool *isPrivate)
+{
+ *isPrivate = mPrivate;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsDownload Helper Functions
+
+void
+nsDownload::Finalize()
+{
+ // We're stopping, so break the cycle we created at download start
+ mCancelable = nullptr;
+
+ // Reset values that aren't needed anymore, so the DB can be updated as well
+ mEntityID.Truncate();
+ mTempFile = nullptr;
+
+ // Remove ourself from the active downloads
+ nsCOMArray<nsDownload>& currentDownloads = mPrivate ?
+ mDownloadManager->mCurrentPrivateDownloads :
+ mDownloadManager->mCurrentDownloads;
+ (void)currentDownloads.RemoveObject(this);
+
+ // Make sure we do not automatically resume
+ mAutoResume = DONT_RESUME;
+}
+
+nsresult
+nsDownload::ExecuteDesiredAction()
+{
+ // nsExternalHelperAppHandler is the only caller of AddDownload that sets a
+ // tempfile parameter. In this case, execute the desired action according to
+ // the saved mime info.
+ if (!mTempFile) {
+ return NS_OK;
+ }
+
+ // We need to bail if for some reason the temp file got removed
+ bool fileExists;
+ if (NS_FAILED(mTempFile->Exists(&fileExists)) || !fileExists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ // Assume an unknown action is save to disk
+ nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
+ if (mMIMEInfo) {
+ nsresult rv = mMIMEInfo->GetPreferredAction(&action);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsresult rv = NS_OK;
+ switch (action) {
+ case nsIMIMEInfo::saveToDisk:
+ // Move the file to the proper location
+ rv = MoveTempToTarget();
+ if (NS_SUCCEEDED(rv)) {
+ rv = FixTargetPermissions();
+ }
+ break;
+ case nsIMIMEInfo::useHelperApp:
+ case nsIMIMEInfo::useSystemDefault:
+ // For these cases we have to move the file to the target location and
+ // open with the appropriate application
+ rv = OpenWithApplication();
+ break;
+ default:
+ break;
+ }
+
+ return rv;
+}
+
+nsresult
+nsDownload::FixTargetPermissions()
+{
+ nsCOMPtr<nsIFile> target;
+ nsresult rv = GetTargetFile(getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set perms according to umask.
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService("@mozilla.org/system-info;1");
+ uint32_t gUserUmask = 0;
+ rv = infoService->GetPropertyAsUint32(NS_LITERAL_STRING("umask"),
+ &gUserUmask);
+ if (NS_SUCCEEDED(rv)) {
+ (void)target->SetPermissions(0666 & ~gUserUmask);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsDownload::MoveTempToTarget()
+{
+ nsCOMPtr<nsIFile> target;
+ nsresult rv = GetTargetFile(getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // MoveTo will fail if the file already exists, but we've already obtained
+ // confirmation from the user that this is OK, so remove it if it exists.
+ bool fileExists;
+ if (NS_SUCCEEDED(target->Exists(&fileExists)) && fileExists) {
+ rv = target->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Extract the new leaf name from the file location
+ nsAutoString fileName;
+ rv = target->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> dir;
+ rv = target->GetParent(getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mTempFile->MoveTo(dir, fileName);
+ return rv;
+}
+
+nsresult
+nsDownload::OpenWithApplication()
+{
+ // First move the temporary file to the target location
+ nsCOMPtr<nsIFile> target;
+ nsresult rv = GetTargetFile(getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Move the temporary file to the target location
+ rv = MoveTempToTarget();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool deleteTempFileOnExit;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (!prefs || NS_FAILED(prefs->GetBoolPref(PREF_BH_DELETETEMPFILEONEXIT,
+ &deleteTempFileOnExit))) {
+ // No prefservice or no pref set; use default value
+ // Some users have been very verbal about temp files being deleted on
+ // app exit - they don't like it - but we'll continue to do this on
+ // all platforms for now.
+ deleteTempFileOnExit = true;
+ }
+
+ // Always schedule files to be deleted at the end of the private browsing
+ // mode, regardless of the value of the pref.
+ if (deleteTempFileOnExit || mPrivate) {
+
+ // Make the tmp file readonly so users won't lose changes.
+ target->SetPermissions(0400);
+
+ // Use the ExternalHelperAppService to push the temporary file to the list
+ // of files to be deleted on exit.
+ nsCOMPtr<nsPIExternalAppLauncher> appLauncher(do_GetService
+ (NS_EXTERNALHELPERAPPSERVICE_CONTRACTID));
+
+ // Even if we are unable to get this service we return the result
+ // of LaunchWithFile() which makes more sense.
+ if (appLauncher) {
+ if (mPrivate) {
+ (void)appLauncher->DeleteTemporaryPrivateFileWhenPossible(target);
+ } else {
+ (void)appLauncher->DeleteTemporaryFileOnExit(target);
+ }
+ }
+ }
+
+ return mMIMEInfo->LaunchWithFile(target);
+}
+
+void
+nsDownload::SetStartTime(int64_t aStartTime)
+{
+ mStartTime = aStartTime;
+ mLastUpdate = aStartTime;
+}
+
+void
+nsDownload::SetProgressBytes(int64_t aCurrBytes, int64_t aMaxBytes)
+{
+ mCurrBytes = aCurrBytes;
+ mMaxBytes = aMaxBytes;
+
+ // Get the real bytes that include resume position
+ int64_t currBytes, maxBytes;
+ (void)GetAmountTransferred(&currBytes);
+ (void)GetSize(&maxBytes);
+
+ if (currBytes == maxBytes)
+ mPercentComplete = 100;
+ else if (maxBytes <= 0)
+ mPercentComplete = -1;
+ else
+ mPercentComplete = (int32_t)((double)currBytes / maxBytes * 100 + .5);
+}
+
+NS_IMETHODIMP
+nsDownload::Pause()
+{
+ if (!IsResumable())
+ return NS_ERROR_UNEXPECTED;
+
+ nsresult rv = CancelTransfer();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return SetState(nsIDownloadManager::DOWNLOAD_PAUSED);
+}
+
+nsresult
+nsDownload::CancelTransfer()
+{
+ nsresult rv = NS_OK;
+ if (mCancelable) {
+ rv = mCancelable->Cancel(NS_BINDING_ABORTED);
+ // we're done with this, so break the cycle
+ mCancelable = nullptr;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDownload::Cancel()
+{
+ // Don't cancel if download is already finished
+ if (IsFinished())
+ return NS_OK;
+
+ // Have the download cancel its connection
+ (void)CancelTransfer();
+
+ // Dump the temp file because we know we don't need the file anymore. The
+ // underlying transfer creating the file doesn't delete the file because it
+ // can't distinguish between a pause that cancels the transfer or a real
+ // cancel.
+ if (mTempFile) {
+ bool exists;
+ mTempFile->Exists(&exists);
+ if (exists)
+ mTempFile->Remove(false);
+ }
+
+ nsCOMPtr<nsIFile> file;
+ if (NS_SUCCEEDED(GetTargetFile(getter_AddRefs(file))))
+ {
+ bool exists;
+ file->Exists(&exists);
+ if (exists)
+ file->Remove(false);
+ }
+
+ nsresult rv = SetState(nsIDownloadManager::DOWNLOAD_CANCELED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDownload::Resume()
+{
+ if (!IsPaused() || !IsResumable())
+ return NS_ERROR_UNEXPECTED;
+
+ nsresult rv;
+ nsCOMPtr<nsIWebBrowserPersist> wbp =
+ do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = wbp->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE |
+ nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a new channel for the source URI
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(wbp));
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ mSource,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aLoadGroup
+ ir);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
+ if (pbChannel) {
+ pbChannel->SetPrivate(mPrivate);
+ }
+
+ // Make sure we can get a file, either the temporary or the real target, for
+ // both purposes of file size and a target to write to
+ nsCOMPtr<nsIFile> targetLocalFile(mTempFile);
+ if (!targetLocalFile) {
+ rv = GetTargetFile(getter_AddRefs(targetLocalFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Get the file size to be used as an offset, but if anything goes wrong
+ // along the way, we'll silently restart at 0.
+ int64_t fileSize;
+ // We need a nsIFile clone to deal with file size caching issues. :(
+ nsCOMPtr<nsIFile> clone;
+ if (NS_FAILED(targetLocalFile->Clone(getter_AddRefs(clone))) ||
+ NS_FAILED(clone->GetFileSize(&fileSize)))
+ fileSize = 0;
+
+ // Set the channel to resume at the right position along with the entityID
+ nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(channel));
+ if (!resumableChannel)
+ return NS_ERROR_UNEXPECTED;
+ rv = resumableChannel->ResumeAt(fileSize, mEntityID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we know the max size, we know what it should be when resuming
+ int64_t maxBytes;
+ GetSize(&maxBytes);
+ SetProgressBytes(0, maxBytes != -1 ? maxBytes - fileSize : -1);
+ // Track where we resumed because progress notifications restart at 0
+ mResumedAt = fileSize;
+
+ // Set the referrer
+ if (mReferrer) {
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ rv = httpChannel->SetReferrer(mReferrer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Creates a cycle that will be broken when the download finishes
+ mCancelable = wbp;
+ (void)wbp->SetProgressListener(this);
+
+ // Save the channel using nsIWBP
+ rv = wbp->SaveChannel(channel, targetLocalFile);
+ if (NS_FAILED(rv)) {
+ mCancelable = nullptr;
+ (void)wbp->SetProgressListener(nullptr);
+ return rv;
+ }
+
+ return SetState(nsIDownloadManager::DOWNLOAD_DOWNLOADING);
+}
+
+NS_IMETHODIMP
+nsDownload::Remove()
+{
+ return mDownloadManager->RemoveDownload(mGUID);
+}
+
+NS_IMETHODIMP
+nsDownload::Retry()
+{
+ return mDownloadManager->RetryDownload(mGUID);
+}
+
+bool
+nsDownload::IsPaused()
+{
+ return mDownloadState == nsIDownloadManager::DOWNLOAD_PAUSED;
+}
+
+bool
+nsDownload::IsResumable()
+{
+ return !mEntityID.IsEmpty();
+}
+
+bool
+nsDownload::WasResumed()
+{
+ return mResumedAt != -1;
+}
+
+bool
+nsDownload::ShouldAutoResume()
+{
+ return mAutoResume == AUTO_RESUME;
+}
+
+bool
+nsDownload::IsFinishable()
+{
+ return mDownloadState == nsIDownloadManager::DOWNLOAD_NOTSTARTED ||
+ mDownloadState == nsIDownloadManager::DOWNLOAD_QUEUED ||
+ mDownloadState == nsIDownloadManager::DOWNLOAD_DOWNLOADING;
+}
+
+bool
+nsDownload::IsFinished()
+{
+ return mDownloadState == nsIDownloadManager::DOWNLOAD_FINISHED;
+}
+
+nsresult
+nsDownload::UpdateDB()
+{
+ NS_ASSERTION(mID, "Download ID is stored as zero. This is bad!");
+ NS_ASSERTION(mDownloadManager, "Egads! We have no download manager!");
+
+ mozIStorageStatement *stmt = mPrivate ?
+ mDownloadManager->mUpdatePrivateDownloadStatement : mDownloadManager->mUpdateDownloadStatement;
+
+ nsAutoString tempPath;
+ if (mTempFile)
+ (void)mTempFile->GetPath(tempPath);
+ nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("tempPath"), tempPath);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startTime"), mStartTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("endTime"), mLastUpdate);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("state"), mDownloadState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mReferrer) {
+ nsAutoCString referrer;
+ rv = mReferrer->GetSpec(referrer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("referrer"), referrer);
+ } else {
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("referrer"));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("entityID"), mEntityID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t currBytes;
+ (void)GetAmountTransferred(&currBytes);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("currBytes"), currBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t maxBytes;
+ (void)GetSize(&maxBytes);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("maxBytes"), maxBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("autoResume"), mAutoResume);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return stmt->Execute();
+}
+
+nsresult
+nsDownload::FailDownload(nsresult aStatus, const char16_t *aMessage)
+{
+ // Grab the bundle before potentially losing our member variables
+ nsCOMPtr<nsIStringBundle> bundle = mDownloadManager->mBundle;
+
+ (void)SetState(nsIDownloadManager::DOWNLOAD_FAILED);
+
+ // Get title for alert.
+ nsXPIDLString title;
+ nsresult rv = bundle->GetStringFromName(
+ u"downloadErrorAlertTitle", getter_Copies(title));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get a generic message if we weren't supplied one
+ nsXPIDLString message;
+ message = aMessage;
+ if (message.IsEmpty()) {
+ rv = bundle->GetStringFromName(
+ u"downloadErrorGeneric", getter_Copies(message));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Get Download Manager window to be parent of alert
+ nsCOMPtr<nsIWindowMediator> wm =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIDOMWindowProxy> dmWindow;
+ rv = wm->GetMostRecentWindow(u"Download:Manager",
+ getter_AddRefs(dmWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Show alert
+ nsCOMPtr<nsIPromptService> prompter =
+ do_GetService("@mozilla.org/embedcomp/prompt-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prompter->Alert(dmWindow, title, message);
+}
diff --git a/components/downloads/src/nsDownloadManager.h b/components/downloads/src/nsDownloadManager.h
new file mode 100644
index 000000000..566e3560a
--- /dev/null
+++ b/components/downloads/src/nsDownloadManager.h
@@ -0,0 +1,454 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef downloadmanager___h___
+#define downloadmanager___h___
+
+#if defined(XP_WIN)
+#define DOWNLOAD_SCANNER
+#endif
+
+#include "nsIDownload.h"
+#include "nsIDownloadManager.h"
+#include "nsIDownloadProgressListener.h"
+#include "nsIFile.h"
+#include "nsIMIMEInfo.h"
+#include "nsINavHistoryService.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIStringBundle.h"
+#include "nsISupportsPrimitives.h"
+#include "nsWeakReference.h"
+#include "nsITimer.h"
+#include "nsString.h"
+
+#include "mozIDOMWindow.h"
+#include "mozStorageHelper.h"
+#include "nsAutoPtr.h"
+#include "nsCOMArray.h"
+
+typedef int16_t DownloadState;
+typedef int16_t DownloadType;
+
+class nsIArray;
+class nsDownload;
+
+#ifdef DOWNLOAD_SCANNER
+#include "nsDownloadScanner.h"
+#endif
+
+class nsDownloadManager final : public nsIDownloadManager,
+ public nsINavHistoryObserver,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOWNLOADMANAGER
+ NS_DECL_NSINAVHISTORYOBSERVER
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+ static nsDownloadManager *GetSingleton();
+
+ nsDownloadManager()
+#ifdef DOWNLOAD_SCANNER
+ : mScanner(nullptr)
+#endif
+ {
+ }
+
+protected:
+ virtual ~nsDownloadManager();
+
+ nsresult InitDB();
+ nsresult InitFileDB();
+ void CloseAllDBs();
+ void CloseDB(mozIStorageConnection* aDBConn,
+ mozIStorageStatement* aUpdateStmt,
+ mozIStorageStatement* aGetIdsStmt);
+ nsresult InitPrivateDB();
+ already_AddRefed<mozIStorageConnection> GetFileDBConnection(nsIFile *dbFile) const;
+ already_AddRefed<mozIStorageConnection> GetPrivateDBConnection() const;
+ nsresult CreateTable(mozIStorageConnection* aDBConn);
+
+ /**
+ * Fix up the database after a crash such as dealing with previously-active
+ * downloads. Call this before RestoreActiveDownloads to get the downloads
+ * fixed here to be auto-resumed.
+ */
+ nsresult RestoreDatabaseState();
+
+ /**
+ * Paused downloads that survive across sessions are considered active, so
+ * rebuild the list of these downloads.
+ */
+ nsresult RestoreActiveDownloads();
+
+ nsresult GetDownloadFromDB(const nsACString& aGUID, nsDownload **retVal);
+ nsresult GetDownloadFromDB(uint32_t aID, nsDownload **retVal);
+ nsresult GetDownloadFromDB(mozIStorageConnection* aDBConn,
+ mozIStorageStatement* stmt,
+ nsDownload **retVal);
+
+ /**
+ * Specially track the active downloads so that we don't need to check
+ * every download to see if they're in progress.
+ */
+ nsresult AddToCurrentDownloads(nsDownload *aDl);
+
+ void SendEvent(nsDownload *aDownload, const char *aTopic);
+
+ /**
+ * Adds a download with the specified information to the DB.
+ *
+ * @return The id of the download, or 0 if there was an error.
+ */
+ int64_t AddDownloadToDB(const nsAString &aName,
+ const nsACString &aSource,
+ const nsACString &aTarget,
+ const nsAString &aTempPath,
+ int64_t aStartTime,
+ int64_t aEndTime,
+ const nsACString &aMimeType,
+ const nsACString &aPreferredApp,
+ nsHandlerInfoAction aPreferredAction,
+ bool aPrivate,
+ nsACString &aNewGUID);
+
+ void NotifyListenersOnDownloadStateChange(int16_t aOldState,
+ nsDownload *aDownload);
+ void NotifyListenersOnProgressChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ int64_t aCurSelfProgress,
+ int64_t aMaxSelfProgress,
+ int64_t aCurTotalProgress,
+ int64_t aMaxTotalProgress,
+ nsDownload *aDownload);
+ void NotifyListenersOnStateChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus,
+ nsDownload *aDownload);
+
+ nsDownload *FindDownload(const nsACString& aGUID);
+ nsDownload *FindDownload(uint32_t aID);
+
+ /**
+ * First try to resume the download, and if that fails, retry it.
+ *
+ * @param aDl The download to resume and/or retry.
+ */
+ nsresult ResumeRetry(nsDownload *aDl);
+
+ /**
+ * Pause all active downloads and remember if they should try to auto-resume
+ * when the download manager starts again.
+ *
+ * @param aSetResume Indicate if the downloads that get paused should be set
+ * as auto-resume.
+ */
+ nsresult PauseAllDownloads(bool aSetResume);
+
+ /**
+ * Resume all paused downloads unless we're only supposed to do the automatic
+ * ones; in that case, try to retry them as well if resuming doesn't work.
+ *
+ * @param aResumeAll If true, all downloads will be resumed; otherwise, only
+ * those that are marked as auto-resume will resume.
+ */
+ nsresult ResumeAllDownloads(bool aResumeAll);
+
+ /**
+ * Stop tracking the active downloads. Only use this when we're about to quit
+ * the download manager because we destroy our list of active downloads to
+ * break the dlmgr<->dl cycle. Active downloads that aren't real-paused will
+ * be canceled.
+ */
+ nsresult RemoveAllDownloads();
+
+ /**
+ * Find all downloads from a source URI and delete them.
+ *
+ * @param aURI
+ * The source URI to remove downloads
+ */
+ nsresult RemoveDownloadsForURI(nsIURI *aURI);
+
+ /**
+ * Callback used for resuming downloads after getting a wake notification.
+ *
+ * @param aTimer
+ * Timer object fired after some delay after a wake notification
+ * @param aClosure
+ * nsDownloadManager object used to resume downloads
+ */
+ static void ResumeOnWakeCallback(nsITimer *aTimer, void *aClosure);
+ nsCOMPtr<nsITimer> mResumeOnWakeTimer;
+
+ void ConfirmCancelDownloads(int32_t aCount,
+ nsISupportsPRBool *aCancelDownloads,
+ const char16_t *aTitle,
+ const char16_t *aCancelMessageMultiple,
+ const char16_t *aCancelMessageSingle,
+ const char16_t *aDontCancelButton);
+
+ int32_t GetRetentionBehavior();
+
+ /**
+ * Type to indicate possible behaviors for active downloads across sessions.
+ *
+ * Possible values are:
+ * QUIT_AND_RESUME - downloads should be auto-resumed
+ * QUIT_AND_PAUSE - downloads should be paused
+ * QUIT_AND_CANCEL - downloads should be cancelled
+ */
+ enum QuitBehavior {
+ QUIT_AND_RESUME = 0,
+ QUIT_AND_PAUSE = 1,
+ QUIT_AND_CANCEL = 2
+ };
+
+ /**
+ * Indicates user-set behavior for active downloads across sessions,
+ *
+ * @return value of user-set pref for active download behavior
+ */
+ enum QuitBehavior GetQuitBehavior();
+
+ void OnEnterPrivateBrowsingMode();
+ void OnLeavePrivateBrowsingMode();
+
+ nsresult RetryDownload(const nsACString& aGUID);
+ nsresult RetryDownload(nsDownload* dl);
+
+ nsresult RemoveDownload(const nsACString& aGUID);
+
+ nsresult NotifyDownloadRemoval(nsDownload* aRemoved);
+
+ // Virus scanner for windows
+#ifdef DOWNLOAD_SCANNER
+private:
+ nsDownloadScanner* mScanner;
+#endif
+
+private:
+ nsresult CleanUp(mozIStorageConnection* aDBConn);
+ nsresult InitStatements(mozIStorageConnection* aDBConn,
+ mozIStorageStatement** aUpdateStatement,
+ mozIStorageStatement** aGetIdsStatement);
+ nsresult RemoveAllDownloads(nsCOMArray<nsDownload>& aDownloads);
+ nsresult PauseAllDownloads(nsCOMArray<nsDownload>& aDownloads, bool aSetResume);
+ nsresult ResumeAllDownloads(nsCOMArray<nsDownload>& aDownloads, bool aResumeAll);
+ nsresult RemoveDownloadsForURI(mozIStorageStatement* aStatement, nsIURI *aURI);
+
+ bool mUseJSTransfer;
+ nsCOMArray<nsIDownloadProgressListener> mListeners;
+ nsCOMArray<nsIDownloadProgressListener> mPrivacyAwareListeners;
+ nsCOMPtr<nsIStringBundle> mBundle;
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ nsCOMPtr<mozIStorageConnection> mPrivateDBConn;
+ nsCOMArray<nsDownload> mCurrentDownloads;
+ nsCOMArray<nsDownload> mCurrentPrivateDownloads;
+ nsCOMPtr<nsIObserverService> mObserverService;
+ nsCOMPtr<mozIStorageStatement> mUpdateDownloadStatement;
+ nsCOMPtr<mozIStorageStatement> mUpdatePrivateDownloadStatement;
+ nsCOMPtr<mozIStorageStatement> mGetIdsForURIStatement;
+ nsCOMPtr<mozIStorageStatement> mGetPrivateIdsForURIStatement;
+ nsAutoPtr<mozStorageTransaction> mHistoryTransaction;
+
+ static nsDownloadManager *gDownloadManagerService;
+
+ friend class nsDownload;
+};
+
+class nsDownload final : public nsIDownload
+{
+public:
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIWEBPROGRESSLISTENER2
+ NS_DECL_NSITRANSFER
+ NS_DECL_NSIDOWNLOAD
+ NS_DECL_ISUPPORTS
+
+ nsDownload();
+
+ /**
+ * This method MUST be called when changing states on a download. It will
+ * notify the download listener when a change happens. This also updates the
+ * database, by calling UpdateDB().
+ */
+ nsresult SetState(DownloadState aState);
+
+protected:
+ virtual ~nsDownload();
+
+ /**
+ * Finish up the download by breaking reference cycles and clearing unneeded
+ * data. Additionally, the download removes itself from the download
+ * manager's list of current downloads.
+ *
+ * NOTE: This method removes the cycle created when starting the download, so
+ * make sure to use kungFuDeathGrip if you want to access member variables.
+ */
+ void Finalize();
+
+ /**
+ * For finished resumed downloads that came in from exthandler, perform the
+ * action that would have been done if the download wasn't resumed.
+ */
+ nsresult ExecuteDesiredAction();
+
+ /**
+ * Move the temporary file to the final destination by removing the existing
+ * dummy target and renaming the temporary.
+ */
+ nsresult MoveTempToTarget();
+
+ /**
+ * Set the target file permissions to be appropriate.
+ */
+ nsresult FixTargetPermissions();
+
+ /**
+ * Update the start time which also implies the last update time is the same.
+ */
+ void SetStartTime(int64_t aStartTime);
+
+ /**
+ * Update the amount of bytes transferred and max bytes; and recalculate the
+ * download percent.
+ */
+ void SetProgressBytes(int64_t aCurrBytes, int64_t aMaxBytes);
+
+ /**
+ * All this does is cancel the connection that the download is using. It does
+ * not remove it from the download manager.
+ */
+ nsresult CancelTransfer();
+
+ /**
+ * Download is not transferring?
+ */
+ bool IsPaused();
+
+ /**
+ * Download can continue from the middle of a transfer?
+ */
+ bool IsResumable();
+
+ /**
+ * Download was resumed?
+ */
+ bool WasResumed();
+
+ /**
+ * Indicates if the download should try to automatically resume or not.
+ */
+ bool ShouldAutoResume();
+
+ /**
+ * Download is in a state to stop and complete the download?
+ */
+ bool IsFinishable();
+
+ /**
+ * Download is totally done transferring and all?
+ */
+ bool IsFinished();
+
+ /**
+ * Update the DB with the current state of the download including time,
+ * download state and other values not known when first creating the
+ * download DB entry.
+ */
+ nsresult UpdateDB();
+
+ /**
+ * Fail a download because of a failure status and prompt the provided
+ * message or use a generic download failure message if nullptr.
+ */
+ nsresult FailDownload(nsresult aStatus, const char16_t *aMessage);
+
+ /**
+ * Opens the downloaded file with the appropriate application, which is
+ * either the OS default, MIME type default, or the one selected by the user.
+ *
+ * This also adds the temporary file to the "To be deleted on Exit" list, if
+ * the corresponding user preference is set (except on OS X).
+ *
+ * This function was adopted from nsExternalAppHandler::OpenWithApplication
+ * (uriloader/exthandler/nsExternalHelperAppService.cpp).
+ */
+ nsresult OpenWithApplication();
+
+ nsDownloadManager *mDownloadManager;
+ nsCOMPtr<nsIURI> mTarget;
+
+private:
+ nsString mDisplayName;
+ nsCString mEntityID;
+ nsCString mGUID;
+
+ nsCOMPtr<nsIURI> mSource;
+ nsCOMPtr<nsIURI> mReferrer;
+ nsCOMPtr<nsICancelable> mCancelable;
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIFile> mTempFile;
+ nsCOMPtr<nsIMIMEInfo> mMIMEInfo;
+
+ DownloadState mDownloadState;
+
+ uint32_t mID;
+ int32_t mPercentComplete;
+
+ /**
+ * These bytes are based on the position of where the request started, so 0
+ * doesn't necessarily mean we have nothing. Use GetAmountTransferred and
+ * GetSize for the real transferred amount and size.
+ */
+ int64_t mCurrBytes;
+ int64_t mMaxBytes;
+
+ PRTime mStartTime;
+ PRTime mLastUpdate;
+ int64_t mResumedAt;
+ double mSpeed;
+
+ bool mHasMultipleFiles;
+ bool mPrivate;
+
+ /**
+ * Track various states of the download trying to auto-resume when starting
+ * the download manager or restoring from a crash.
+ *
+ * DONT_RESUME: Don't automatically resume the download
+ * AUTO_RESUME: Automaically resume the download
+ */
+ enum AutoResume { DONT_RESUME, AUTO_RESUME };
+ AutoResume mAutoResume;
+
+ /**
+ * Stores the SHA-256 hash associated with the downloaded file.
+ */
+ nsCString mHash;
+
+ /**
+ * Stores the certificate chains in an nsIArray of nsIX509CertList of
+ * nsIX509Cert, if this binary is signed.
+ */
+ nsCOMPtr<nsIArray> mSignatureInfo;
+
+ /**
+ * Stores the redirects that led to this download in an nsIArray of
+ * nsIPrincipal.
+ */
+ nsCOMPtr<nsIArray> mRedirects;
+
+ friend class nsDownloadManager;
+};
+
+#endif
diff --git a/components/downloads/src/nsDownloadManagerUI.js b/components/downloads/src/nsDownloadManagerUI.js
new file mode 100644
index 000000000..11e241403
--- /dev/null
+++ b/components/downloads/src/nsDownloadManagerUI.js
@@ -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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Constants
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const DOWNLOAD_MANAGER_URL = "chrome://mozapps/content/downloads/downloads.xul";
+const PREF_FLASH_COUNT = "browser.download.manager.flashCount";
+
+// nsDownloadManagerUI class
+
+function nsDownloadManagerUI() {}
+
+nsDownloadManagerUI.prototype = {
+ classID: Components.ID("7dfdf0d1-aff6-4a34-bad1-d0fe74601642"),
+
+ // nsIDownloadManagerUI
+
+ show: function show(aWindowContext, aDownload, aReason, aUsePrivateUI)
+ {
+ if (!aReason)
+ aReason = Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED;
+
+ // First we see if it is already visible
+ let window = this.recentWindow;
+ if (window) {
+ window.focus();
+
+ // If we are being asked to show again, with a user interaction reason,
+ // set the appropriate variable.
+ if (aReason == Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED)
+ window.gUserInteracted = true;
+ return;
+ }
+
+ let parent = null;
+ // We try to get a window to use as the parent here. If we don't have one,
+ // the download manager will close immediately after opening if the pref
+ // browser.download.manager.closeWhenDone is set to true.
+ try {
+ if (aWindowContext)
+ parent = aWindowContext.getInterface(Ci.nsIDOMWindow);
+ } catch (e) { /* it's OK to not have a parent window */ }
+
+ // We pass the download manager and the nsIDownload we want selected (if any)
+ var params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ params.appendElement(aDownload, false);
+
+ // Pass in the reason as well
+ let reason = Cc["@mozilla.org/supports-PRInt16;1"].
+ createInstance(Ci.nsISupportsPRInt16);
+ reason.data = aReason;
+ params.appendElement(reason, false);
+
+ var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ ww.openWindow(parent,
+ DOWNLOAD_MANAGER_URL,
+ "Download:Manager",
+ "chrome,dialog=no,resizable",
+ params);
+ },
+
+ get visible() {
+ return (null != this.recentWindow);
+ },
+
+ getAttention: function getAttention()
+ {
+ if (!this.visible)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ // This preference may not be set, so defaulting to two.
+ let flashCount = 2;
+ try {
+ flashCount = prefs.getIntPref(PREF_FLASH_COUNT);
+ } catch (e) { }
+
+ var win = this.recentWindow.QueryInterface(Ci.nsIDOMChromeWindow);
+ win.getAttentionWithCycleCount(flashCount);
+ },
+
+ // nsDownloadManagerUI
+
+ get recentWindow() {
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ return wm.getMostRecentWindow("Download:Manager");
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI])
+};
+
+// Module
+
+var components = [nsDownloadManagerUI];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
+
diff --git a/components/downloads/src/nsDownloadProxy.h b/components/downloads/src/nsDownloadProxy.h
new file mode 100644
index 000000000..ca48c9dad
--- /dev/null
+++ b/components/downloads/src/nsDownloadProxy.h
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef downloadproxy___h___
+#define downloadproxy___h___
+
+#include "nsIDownloadManager.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIMIMEInfo.h"
+#include "nsIFileURL.h"
+#include "nsIDownloadManagerUI.h"
+
+#define PREF_BDM_SHOWWHENSTARTING "browser.download.manager.showWhenStarting"
+#define PREF_BDM_FOCUSWHENSTARTING "browser.download.manager.focusWhenStarting"
+
+// This class only exists because nsDownload cannot inherit from nsITransfer
+// directly. The reason for this is that nsDownloadManager (incorrectly) keeps
+// an nsCOMArray of nsDownloads, and nsCOMArray is only intended for use with
+// abstract classes. Using a concrete class that multiply inherits from classes
+// deriving from nsISupports will throw ambiguous base class errors.
+class nsDownloadProxy : public nsITransfer
+{
+protected:
+
+ virtual ~nsDownloadProxy() { }
+
+public:
+
+ nsDownloadProxy() { }
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init(nsIURI* aSource,
+ nsIURI* aTarget,
+ const nsAString& aDisplayName,
+ nsIMIMEInfo *aMIMEInfo,
+ PRTime aStartTime,
+ nsIFile* aTempFile,
+ nsICancelable* aCancelable,
+ bool aIsPrivate) override {
+ nsresult rv;
+ nsCOMPtr<nsIDownloadManager> dm = do_GetService("@mozilla.org/download-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dm->AddDownload(nsIDownloadManager::DOWNLOAD_TYPE_DOWNLOAD, aSource,
+ aTarget, aDisplayName, aMIMEInfo, aStartTime,
+ aTempFile, aCancelable, aIsPrivate,
+ getter_AddRefs(mInner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
+
+ bool showDM = true;
+ if (branch)
+ branch->GetBoolPref(PREF_BDM_SHOWWHENSTARTING, &showDM);
+
+ if (showDM) {
+ nsCOMPtr<nsIDownloadManagerUI> dmui =
+ do_GetService("@mozilla.org/download-manager-ui;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool visible;
+ rv = dmui->GetVisible(&visible);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool focusWhenStarting = true;
+ if (branch)
+ (void)branch->GetBoolPref(PREF_BDM_FOCUSWHENSTARTING, &focusWhenStarting);
+
+ if (visible && !focusWhenStarting)
+ return NS_OK;
+
+ return dmui->Show(nullptr, mInner, nsIDownloadManagerUI::REASON_NEW_DOWNLOAD, aIsPrivate);
+ }
+ return rv;
+ }
+
+ NS_IMETHOD OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aStateFlags,
+ nsresult aStatus) override
+ {
+ NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
+ return mInner->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
+ }
+
+ NS_IMETHOD OnStatusChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, nsresult aStatus,
+ const char16_t *aMessage) override
+ {
+ NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
+ return mInner->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ }
+
+ NS_IMETHOD OnLocationChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, nsIURI *aLocation,
+ uint32_t aFlags) override
+ {
+ NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
+ return mInner->OnLocationChange(aWebProgress, aRequest, aLocation, aFlags);
+ }
+
+ NS_IMETHOD OnProgressChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) override
+ {
+ NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
+ return mInner->OnProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress);
+ }
+
+ NS_IMETHOD OnProgressChange64(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int64_t aCurSelfProgress,
+ int64_t aMaxSelfProgress,
+ int64_t aCurTotalProgress,
+ int64_t aMaxTotalProgress) override
+ {
+ NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
+ return mInner->OnProgressChange64(aWebProgress, aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress);
+ }
+
+ NS_IMETHOD OnRefreshAttempted(nsIWebProgress *aWebProgress,
+ nsIURI *aUri,
+ int32_t aDelay,
+ bool aSameUri,
+ bool *allowRefresh) override
+ {
+ *allowRefresh = true;
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest, uint32_t aState) override
+ {
+ NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
+ return mInner->OnSecurityChange(aWebProgress, aRequest, aState);
+ }
+
+ NS_IMETHOD SetSha256Hash(const nsACString& aHash) override
+ {
+ NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
+ return mInner->SetSha256Hash(aHash);
+ }
+
+ NS_IMETHOD SetSignatureInfo(nsIArray* aSignatureInfo) override
+ {
+ NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
+ return mInner->SetSignatureInfo(aSignatureInfo);
+ }
+
+ NS_IMETHOD SetRedirects(nsIArray* aRedirects) override
+ {
+ NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
+ return mInner->SetRedirects(aRedirects);
+ }
+
+private:
+ nsCOMPtr<nsIDownload> mInner;
+};
+
+NS_IMPL_ISUPPORTS(nsDownloadProxy, nsITransfer,
+ nsIWebProgressListener, nsIWebProgressListener2)
+
+#endif
diff --git a/components/downloads/src/nsDownloadScanner.cpp b/components/downloads/src/nsDownloadScanner.cpp
new file mode 100644
index 000000000..1ef5b3660
--- /dev/null
+++ b/components/downloads/src/nsDownloadScanner.cpp
@@ -0,0 +1,728 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: se cin sw=2 ts=2 et : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDownloadScanner.h"
+#include <comcat.h>
+#include <process.h>
+#include "nsDownloadManager.h"
+#include "nsIXULAppInfo.h"
+#include "nsXULAppAPI.h"
+#include "nsIPrefService.h"
+#include "nsNetUtil.h"
+#include "prtime.h"
+#include "nsDeque.h"
+#include "nsIFileURL.h"
+#include "nsIPrefBranch.h"
+#include "nsXPCOMCIDInternal.h"
+
+/**
+ * Code overview
+ *
+ * Download scanner attempts to make use of one of two different virus
+ * scanning interfaces available on Windows - IOfficeAntiVirus (Windows
+ * 95/NT 4 and IE 5) and IAttachmentExecute (XPSP2 and up). The latter
+ * interface supports calling IOfficeAntiVirus internally, while also
+ * adding support for XPSP2+ ADS forks which define security related
+ * prompting on downloaded content.
+ *
+ * Both interfaces are synchronous and can take a while, so it is not a
+ * good idea to call either from the main thread. Some antivirus scanners can
+ * take a long time to scan or the call might block while the scanner shows
+ * its UI so if the user were to download many files that finished around the
+ * same time, they would have to wait a while if the scanning were done on
+ * exactly one other thread. Since the overhead of creating a thread is
+ * relatively small compared to the time it takes to download a file and scan
+ * it, a new thread is spawned for each download that is to be scanned. Since
+ * most of the mozilla codebase is not threadsafe, all the information needed
+ * for the scanner is gathered in the main thread in nsDownloadScanner::Scan::Start.
+ * The only function of nsDownloadScanner::Scan which is invoked on another
+ * thread is DoScan.
+ *
+ * Watchdog overview
+ *
+ * The watchdog is used internally by the scanner. It maintains a queue of
+ * current download scans. In a separate thread, it dequeues the oldest scan
+ * and waits on that scan's thread with a timeout given by WATCHDOG_TIMEOUT
+ * (default is 30 seconds). If the wait times out, then the watchdog notifies
+ * the Scan that it has timed out. If the scan really has timed out, then the
+ * Scan object will dispatch its run method to the main thread; this will
+ * release the watchdog thread's addref on the Scan. If it has not timed out
+ * (i.e. the Scan just finished in time), then the watchdog dispatches a
+ * ReleaseDispatcher to release its ref of the Scan on the main thread.
+ *
+ * In order to minimize execution time, there are two events used to notify the
+ * watchdog thread of a non-empty queue and a quit event. Every blocking wait
+ * that the watchdog thread does waits on the quit event; this lets the thread
+ * quickly exit when shutting down. Also, the download scan queue will be empty
+ * most of the time; rather than use a spin loop, a simple event is triggered
+ * by the main thread when a new scan is added to an empty queue. When the
+ * watchdog thread knows that it has run out of elements in the queue, it will
+ * wait on the new item event.
+ *
+ * Memory/resource leaks due to timeout:
+ * In the event of a timeout, the thread must remain alive; terminating it may
+ * very well cause the antivirus scanner to crash or be put into an
+ * inconsistent state; COM resources may also not be cleaned up. The downside
+ * is that we need to leave the thread running; suspending it may lead to a
+ * deadlock. Because the scan call may be ongoing, it may be dependent on the
+ * memory referenced by the MSOAVINFO structure, so we cannot free mName, mPath
+ * or mOrigin; this means that we cannot free the Scan object since doing so
+ * will deallocate that memory. Note that mDownload is set to null upon timeout
+ * or completion, so the download itself is never leaked. If the scan does
+ * eventually complete, then the all the memory and resources will be freed.
+ * It is possible, however extremely rare, that in the event of a timeout, the
+ * mStateSync critical section will leak its event; this will happen only if
+ * the scanning thread, watchdog thread or main thread try to enter the
+ * critical section when one of the others is already in it.
+ *
+ * Reasoning for CheckAndSetState - there exists a race condition between the time when
+ * either the timeout or normal scan sets the state and when Scan::Run is
+ * executed on the main thread. Ex: mStatus could be set by Scan::DoScan* which
+ * then queues a dispatch on the main thread. Before that dispatch is executed,
+ * the timeout code fires and sets mStatus to AVSCAN_TIMEDOUT which then queues
+ * its dispatch to the main thread (the same function as DoScan*). Both
+ * dispatches run and both try to set the download state to AVSCAN_TIMEDOUT
+ * which is incorrect.
+ *
+ * There are 5 possible outcomes of the virus scan:
+ * AVSCAN_GOOD => the file is clean
+ * AVSCAN_BAD => the file has a virus
+ * AVSCAN_UGLY => the file had a virus, but it was cleaned
+ * AVSCAN_FAILED => something else went wrong with the virus scanner.
+ * AVSCAN_TIMEDOUT => the scan (thread setup + execution) took too long
+ *
+ * Both the good and ugly states leave the user with a benign file, so they
+ * transition to the finished state. Bad files are sent to the blocked state.
+ * The failed and timedout states transition to finished downloads.
+ *
+ * Possible Future enhancements:
+ * * Create an interface for scanning files in general
+ * * Make this a service
+ * * Get antivirus scanner status via WMI/registry
+ */
+
+// IAttachementExecute supports user definable settings for certain
+// security related prompts. This defines a general GUID for use in
+// all projects. Individual projects can define an individual guid
+// if they want to.
+#ifndef MOZ_VIRUS_SCANNER_PROMPT_GUID
+#define MOZ_VIRUS_SCANNER_PROMPT_GUID \
+ { 0xb50563d1, 0x16b6, 0x43c2, { 0xa6, 0x6a, 0xfa, 0xe6, 0xd2, 0x11, 0xf2, \
+ 0xea } }
+#endif
+static const GUID GUID_MozillaVirusScannerPromptGeneric =
+ MOZ_VIRUS_SCANNER_PROMPT_GUID;
+
+// Initial timeout is 30 seconds
+#define WATCHDOG_TIMEOUT (30*PR_USEC_PER_SEC)
+
+// Maximum length for URI's passed into IAE
+#define MAX_IAEURILENGTH 1683
+
+class nsDownloadScannerWatchdog
+{
+ typedef nsDownloadScanner::Scan Scan;
+public:
+ nsDownloadScannerWatchdog();
+ ~nsDownloadScannerWatchdog();
+
+ nsresult Init();
+ nsresult Shutdown();
+
+ void Watch(Scan *scan);
+private:
+ static unsigned int __stdcall WatchdogThread(void *p);
+ CRITICAL_SECTION mQueueSync;
+ nsDeque mScanQueue;
+ HANDLE mThread;
+ HANDLE mNewItemEvent;
+ HANDLE mQuitEvent;
+};
+
+nsDownloadScanner::nsDownloadScanner() :
+ mAESExists(false)
+{
+}
+
+// This destructor appeases the compiler; it would otherwise complain about an
+// incomplete type for nsDownloadWatchdog in the instantiation of
+// nsAutoPtr::~nsAutoPtr
+// Plus, it's a handy location to call nsDownloadScannerWatchdog::Shutdown from
+nsDownloadScanner::~nsDownloadScanner() {
+ if (mWatchdog)
+ (void)mWatchdog->Shutdown();
+}
+
+nsresult
+nsDownloadScanner::Init()
+{
+ // This CoInitialize/CoUninitialize pattern seems to be common in the Mozilla
+ // codebase. All other COM calls/objects are made on different threads.
+ nsresult rv = NS_OK;
+ CoInitialize(nullptr);
+
+ if (!IsAESAvailable()) {
+ CoUninitialize();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mAESExists = true;
+
+ // Initialize scanning
+ mWatchdog = new nsDownloadScannerWatchdog();
+ if (mWatchdog) {
+ rv = mWatchdog->Init();
+ if (FAILED(rv))
+ mWatchdog = nullptr;
+ } else {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ return rv;
+}
+
+bool
+nsDownloadScanner::IsAESAvailable()
+{
+ // Try to instantiate IAE to see if it's available.
+ RefPtr<IAttachmentExecute> ae;
+ HRESULT hr;
+ hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_INPROC,
+ IID_IAttachmentExecute, getter_AddRefs(ae));
+ if (FAILED(hr)) {
+ NS_WARNING("Could not instantiate attachment execution service\n");
+ return false;
+ }
+ return true;
+}
+
+// If IAttachementExecute is available, use the CheckPolicy call to find out
+// if this download should be prevented due to Security Zone Policy settings.
+AVCheckPolicyState
+nsDownloadScanner::CheckPolicy(nsIURI *aSource, nsIURI *aTarget)
+{
+ nsresult rv;
+
+ if (!mAESExists || !aSource || !aTarget)
+ return AVPOLICY_DOWNLOAD;
+
+ nsAutoCString source;
+ rv = aSource->GetSpec(source);
+ if (NS_FAILED(rv))
+ return AVPOLICY_DOWNLOAD;
+
+ nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aTarget));
+ if (!fileUrl)
+ return AVPOLICY_DOWNLOAD;
+
+ nsCOMPtr<nsIFile> theFile;
+ nsAutoString aFileName;
+ if (NS_FAILED(fileUrl->GetFile(getter_AddRefs(theFile))) ||
+ NS_FAILED(theFile->GetLeafName(aFileName)))
+ return AVPOLICY_DOWNLOAD;
+
+ // IAttachementExecute prohibits src data: schemes by default but we
+ // support them. If this is a data src, skip off doing a policy check.
+ // (The file will still be scanned once it lands on the local system.)
+ bool isDataScheme(false);
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aSource);
+ if (innerURI)
+ (void)innerURI->SchemeIs("data", &isDataScheme);
+ if (isDataScheme)
+ return AVPOLICY_DOWNLOAD;
+
+ RefPtr<IAttachmentExecute> ae;
+ HRESULT hr;
+ hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_INPROC,
+ IID_IAttachmentExecute, getter_AddRefs(ae));
+ if (FAILED(hr))
+ return AVPOLICY_DOWNLOAD;
+
+ (void)ae->SetClientGuid(GUID_MozillaVirusScannerPromptGeneric);
+ (void)ae->SetSource(NS_ConvertUTF8toUTF16(source).get());
+ (void)ae->SetFileName(aFileName.get());
+
+ // Any failure means the file download/exec will be blocked by the system.
+ // S_OK or S_FALSE imply it's ok.
+ hr = ae->CheckPolicy();
+
+ if (hr == S_OK)
+ return AVPOLICY_DOWNLOAD;
+
+ if (hr == S_FALSE)
+ return AVPOLICY_PROMPT;
+
+ if (hr == E_INVALIDARG)
+ return AVPOLICY_PROMPT;
+
+ return AVPOLICY_BLOCKED;
+}
+
+#ifndef THREAD_MODE_BACKGROUND_BEGIN
+#define THREAD_MODE_BACKGROUND_BEGIN 0x00010000
+#endif
+
+#ifndef THREAD_MODE_BACKGROUND_END
+#define THREAD_MODE_BACKGROUND_END 0x00020000
+#endif
+
+unsigned int __stdcall
+nsDownloadScanner::ScannerThreadFunction(void *p)
+{
+ HANDLE currentThread = GetCurrentThread();
+ NS_ASSERTION(!NS_IsMainThread(), "Antivirus scan should not be run on the main thread");
+ nsDownloadScanner::Scan *scan = static_cast<nsDownloadScanner::Scan*>(p);
+ if (!SetThreadPriority(currentThread, THREAD_MODE_BACKGROUND_BEGIN))
+ (void)SetThreadPriority(currentThread, THREAD_PRIORITY_IDLE);
+ scan->DoScan();
+ (void)SetThreadPriority(currentThread, THREAD_MODE_BACKGROUND_END);
+ _endthreadex(0);
+ return 0;
+}
+
+// The sole purpose of this class is to release an object on the main thread
+// It assumes that its creator will addref it and it will release itself on
+// the main thread too
+class ReleaseDispatcher : public mozilla::Runnable {
+public:
+ ReleaseDispatcher(nsISupports *ptr)
+ : mPtr(ptr) {}
+ NS_IMETHOD Run();
+private:
+ nsISupports *mPtr;
+};
+
+nsresult ReleaseDispatcher::Run() {
+ NS_ASSERTION(NS_IsMainThread(), "Antivirus scan release dispatch should be run on the main thread");
+ NS_RELEASE(mPtr);
+ NS_RELEASE_THIS();
+ return NS_OK;
+}
+
+nsDownloadScanner::Scan::Scan(nsDownloadScanner *scanner, nsDownload *download)
+ : mDLScanner(scanner), mThread(nullptr),
+ mDownload(download), mStatus(AVSCAN_NOTSTARTED),
+ mSkipSource(false)
+{
+ InitializeCriticalSection(&mStateSync);
+}
+
+nsDownloadScanner::Scan::~Scan() {
+ DeleteCriticalSection(&mStateSync);
+}
+
+nsresult
+nsDownloadScanner::Scan::Start()
+{
+ mStartTime = PR_Now();
+
+ mThread = (HANDLE)_beginthreadex(nullptr, 0, ScannerThreadFunction,
+ this, CREATE_SUSPENDED, nullptr);
+ if (!mThread)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = NS_OK;
+
+ // Get the path to the file on disk
+ nsCOMPtr<nsIFile> file;
+ rv = mDownload->GetTargetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = file->GetPath(mPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Grab the app name
+ nsCOMPtr<nsIXULAppInfo> appinfo =
+ do_GetService(XULAPPINFO_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString name;
+ rv = appinfo->GetName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(name, mName);
+
+ // Get the origin
+ nsCOMPtr<nsIURI> uri;
+ rv = mDownload->GetSource(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString origin;
+ rv = uri->GetSpec(origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Certain virus interfaces do not like extremely long uris.
+ // Chop off the path and cgi data and just pass the base domain.
+ if (origin.Length() > MAX_IAEURILENGTH) {
+ rv = uri->GetPrePath(origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ CopyUTF8toUTF16(origin, mOrigin);
+
+ // We count https/ftp/http as an http download
+ bool isHttp(false), isFtp(false), isHttps(false);
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
+ if (!innerURI) innerURI = uri;
+ (void)innerURI->SchemeIs("http", &isHttp);
+ (void)innerURI->SchemeIs("ftp", &isFtp);
+ (void)innerURI->SchemeIs("https", &isHttps);
+ mIsHttpDownload = isHttp || isFtp || isHttps;
+
+ // IAttachementExecute prohibits src data: schemes by default but we
+ // support them. Mark the download if it's a data scheme, so we
+ // can skip off supplying the src to IAttachementExecute when we scan
+ // the resulting file.
+ (void)innerURI->SchemeIs("data", &mSkipSource);
+
+ // ResumeThread returns the previous suspend count
+ if (1 != ::ResumeThread(mThread)) {
+ CloseHandle(mThread);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsDownloadScanner::Scan::Run()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Antivirus scan dispatch should be run on the main thread");
+
+ // Cleanup our thread
+ if (mStatus != AVSCAN_TIMEDOUT)
+ WaitForSingleObject(mThread, INFINITE);
+ CloseHandle(mThread);
+
+ DownloadState downloadState = 0;
+ EnterCriticalSection(&mStateSync);
+ switch (mStatus) {
+ case AVSCAN_BAD:
+ downloadState = nsIDownloadManager::DOWNLOAD_DIRTY;
+ break;
+ default:
+ case AVSCAN_FAILED:
+ case AVSCAN_GOOD:
+ case AVSCAN_UGLY:
+ case AVSCAN_TIMEDOUT:
+ downloadState = nsIDownloadManager::DOWNLOAD_FINISHED;
+ break;
+ }
+ LeaveCriticalSection(&mStateSync);
+ // Download will be null if we already timed out
+ if (mDownload)
+ (void)mDownload->SetState(downloadState);
+
+ // Clean up some other variables
+ // In the event of a timeout, our destructor won't be called
+ mDownload = nullptr;
+
+ NS_RELEASE_THIS();
+ return NS_OK;
+}
+
+static DWORD
+ExceptionFilterFunction(DWORD exceptionCode) {
+ switch(exceptionCode) {
+ case EXCEPTION_ACCESS_VIOLATION:
+ case EXCEPTION_ILLEGAL_INSTRUCTION:
+ case EXCEPTION_IN_PAGE_ERROR:
+ case EXCEPTION_PRIV_INSTRUCTION:
+ case EXCEPTION_STACK_OVERFLOW:
+ return EXCEPTION_EXECUTE_HANDLER;
+ default:
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+}
+
+bool
+nsDownloadScanner::Scan::DoScanAES()
+{
+ // This warning is for the destructor of ae which will not be invoked in the
+ // event of a win32 exception
+#pragma warning(disable: 4509)
+ HRESULT hr;
+ RefPtr<IAttachmentExecute> ae;
+ MOZ_SEH_TRY {
+ hr = CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_ALL,
+ IID_IAttachmentExecute, getter_AddRefs(ae));
+ } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
+ return CheckAndSetState(AVSCAN_NOTSTARTED,AVSCAN_FAILED);
+ }
+
+ // If we (somehow) already timed out, then don't bother scanning
+ if (CheckAndSetState(AVSCAN_SCANNING, AVSCAN_NOTSTARTED)) {
+ AVScanState newState;
+ if (SUCCEEDED(hr)) {
+ bool gotException = false;
+ MOZ_SEH_TRY {
+ (void)ae->SetClientGuid(GUID_MozillaVirusScannerPromptGeneric);
+ (void)ae->SetLocalPath(mPath.get());
+ // Provide the src for everything but data: schemes.
+ if (!mSkipSource)
+ (void)ae->SetSource(mOrigin.get());
+
+ // Save() will invoke the scanner
+ hr = ae->Save();
+ } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
+ gotException = true;
+ }
+
+ MOZ_SEH_TRY {
+ ae = nullptr;
+ } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
+ gotException = true;
+ }
+
+ if(gotException) {
+ newState = AVSCAN_FAILED;
+ }
+ else if (SUCCEEDED(hr)) { // Passed the scan
+ newState = AVSCAN_GOOD;
+ }
+ else if (HRESULT_CODE(hr) == ERROR_FILE_NOT_FOUND) {
+ NS_WARNING("Downloaded file disappeared before it could be scanned");
+ newState = AVSCAN_FAILED;
+ }
+ else if (hr == E_INVALIDARG) {
+ NS_WARNING("IAttachementExecute returned invalid argument error");
+ newState = AVSCAN_FAILED;
+ }
+ else {
+ newState = AVSCAN_UGLY;
+ }
+ }
+ else {
+ newState = AVSCAN_FAILED;
+ }
+ return CheckAndSetState(newState, AVSCAN_SCANNING);
+ }
+ return false;
+}
+#pragma warning(default: 4509)
+
+void
+nsDownloadScanner::Scan::DoScan()
+{
+ CoInitialize(nullptr);
+
+ if (DoScanAES()) {
+ // We need to do a few more things on the main thread
+ NS_DispatchToMainThread(this);
+ } else {
+ // We timed out, so just release
+ ReleaseDispatcher* releaser = new ReleaseDispatcher(this);
+ if(releaser) {
+ NS_ADDREF(releaser);
+ NS_DispatchToMainThread(releaser);
+ }
+ }
+
+ MOZ_SEH_TRY {
+ CoUninitialize();
+ } MOZ_SEH_EXCEPT(ExceptionFilterFunction(GetExceptionCode())) {
+ // Not much we can do at this point...
+ }
+}
+
+HANDLE
+nsDownloadScanner::Scan::GetWaitableThreadHandle() const
+{
+ HANDLE targetHandle = INVALID_HANDLE_VALUE;
+ (void)DuplicateHandle(GetCurrentProcess(), mThread,
+ GetCurrentProcess(), &targetHandle,
+ SYNCHRONIZE, // Only allow clients to wait on this handle
+ FALSE, // cannot be inherited by child processes
+ 0);
+ return targetHandle;
+}
+
+bool
+nsDownloadScanner::Scan::NotifyTimeout()
+{
+ bool didTimeout = CheckAndSetState(AVSCAN_TIMEDOUT, AVSCAN_SCANNING) ||
+ CheckAndSetState(AVSCAN_TIMEDOUT, AVSCAN_NOTSTARTED);
+ if (didTimeout) {
+ // We need to do a few more things on the main thread
+ NS_DispatchToMainThread(this);
+ }
+ return didTimeout;
+}
+
+bool
+nsDownloadScanner::Scan::CheckAndSetState(AVScanState newState, AVScanState expectedState) {
+ bool gotExpectedState = false;
+ EnterCriticalSection(&mStateSync);
+ if((gotExpectedState = (mStatus == expectedState)))
+ mStatus = newState;
+ LeaveCriticalSection(&mStateSync);
+ return gotExpectedState;
+}
+
+nsresult
+nsDownloadScanner::ScanDownload(nsDownload *download)
+{
+ if (!mAESExists)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // No ref ptr, see comment below
+ Scan *scan = new Scan(this, download);
+ if (!scan)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(scan);
+
+ nsresult rv = scan->Start();
+
+ // Note that we only release upon error. On success, the scan is passed off
+ // to a new thread. It is eventually released in Scan::Run on the main thread.
+ if (NS_FAILED(rv))
+ NS_RELEASE(scan);
+ else
+ // Notify the watchdog
+ mWatchdog->Watch(scan);
+
+ return rv;
+}
+
+nsDownloadScannerWatchdog::nsDownloadScannerWatchdog()
+ : mNewItemEvent(nullptr), mQuitEvent(nullptr) {
+ InitializeCriticalSection(&mQueueSync);
+}
+nsDownloadScannerWatchdog::~nsDownloadScannerWatchdog() {
+ DeleteCriticalSection(&mQueueSync);
+}
+
+nsresult
+nsDownloadScannerWatchdog::Init() {
+ // Both events are auto-reset
+ mNewItemEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ if (INVALID_HANDLE_VALUE == mNewItemEvent)
+ return NS_ERROR_OUT_OF_MEMORY;
+ mQuitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ if (INVALID_HANDLE_VALUE == mQuitEvent) {
+ (void)CloseHandle(mNewItemEvent);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // This thread is always running, however it will be asleep
+ // for most of the dlmgr's lifetime
+ mThread = (HANDLE)_beginthreadex(nullptr, 0, WatchdogThread,
+ this, 0, nullptr);
+ if (!mThread) {
+ (void)CloseHandle(mNewItemEvent);
+ (void)CloseHandle(mQuitEvent);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsDownloadScannerWatchdog::Shutdown() {
+ // Tell the watchdog thread to quite
+ (void)SetEvent(mQuitEvent);
+ (void)WaitForSingleObject(mThread, INFINITE);
+ (void)CloseHandle(mThread);
+ // Manually clear and release the queued scans
+ while (mScanQueue.GetSize() != 0) {
+ Scan *scan = reinterpret_cast<Scan*>(mScanQueue.Pop());
+ NS_RELEASE(scan);
+ }
+ (void)CloseHandle(mNewItemEvent);
+ (void)CloseHandle(mQuitEvent);
+ return NS_OK;
+}
+
+void
+nsDownloadScannerWatchdog::Watch(Scan *scan) {
+ bool wasEmpty;
+ // Note that there is no release in this method
+ // The scan will be released by the watchdog ALWAYS on the main thread
+ // when either the watchdog thread processes the scan or the watchdog
+ // is shut down
+ NS_ADDREF(scan);
+ EnterCriticalSection(&mQueueSync);
+ wasEmpty = mScanQueue.GetSize()==0;
+ mScanQueue.Push(scan);
+ LeaveCriticalSection(&mQueueSync);
+ // If the queue was empty, then the watchdog thread is/will be asleep
+ if (wasEmpty)
+ (void)SetEvent(mNewItemEvent);
+}
+
+unsigned int
+__stdcall
+nsDownloadScannerWatchdog::WatchdogThread(void *p) {
+ NS_ASSERTION(!NS_IsMainThread(), "Antivirus scan watchdog should not be run on the main thread");
+ nsDownloadScannerWatchdog *watchdog = (nsDownloadScannerWatchdog*)p;
+ HANDLE waitHandles[3] = {watchdog->mNewItemEvent, watchdog->mQuitEvent, INVALID_HANDLE_VALUE};
+ DWORD waitStatus;
+ DWORD queueItemsLeft = 0;
+ // Loop until quit event or error
+ while (0 != queueItemsLeft ||
+ ((WAIT_OBJECT_0 + 1) !=
+ (waitStatus =
+ WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE)) &&
+ waitStatus != WAIT_FAILED)) {
+ Scan *scan = nullptr;
+ PRTime startTime, expectedEndTime, now;
+ DWORD waitTime;
+
+ // Pop scan from queue
+ EnterCriticalSection(&watchdog->mQueueSync);
+ scan = reinterpret_cast<Scan*>(watchdog->mScanQueue.Pop());
+ queueItemsLeft = watchdog->mScanQueue.GetSize();
+ LeaveCriticalSection(&watchdog->mQueueSync);
+
+ // Calculate expected end time
+ startTime = scan->GetStartTime();
+ expectedEndTime = WATCHDOG_TIMEOUT + startTime;
+ now = PR_Now();
+ // PRTime is not guaranteed to be a signed integral type (afaik), but
+ // currently it is
+ if (now > expectedEndTime) {
+ waitTime = 0;
+ } else {
+ // This is a positive value, and we know that it will not overflow
+ // (bounded by WATCHDOG_TIMEOUT)
+ // waitTime is in milliseconds, nspr uses microseconds
+ waitTime = static_cast<DWORD>((expectedEndTime - now)/PR_USEC_PER_MSEC);
+ }
+ HANDLE hThread = waitHandles[2] = scan->GetWaitableThreadHandle();
+
+ // Wait for the thread (obj 1) or quit event (obj 0)
+ waitStatus = WaitForMultipleObjects(2, (waitHandles+1), FALSE, waitTime);
+ CloseHandle(hThread);
+
+ ReleaseDispatcher* releaser = new ReleaseDispatcher(scan);
+ if(!releaser)
+ continue;
+ NS_ADDREF(releaser);
+ // Got quit event or error
+ if (waitStatus == WAIT_FAILED || waitStatus == WAIT_OBJECT_0) {
+ NS_DispatchToMainThread(releaser);
+ break;
+ // Thread exited normally
+ } else if (waitStatus == (WAIT_OBJECT_0+1)) {
+ NS_DispatchToMainThread(releaser);
+ continue;
+ // Timeout case
+ } else {
+ NS_ASSERTION(waitStatus == WAIT_TIMEOUT, "Unexpected wait status in dlmgr watchdog thread");
+ if (!scan->NotifyTimeout()) {
+ // If we didn't time out, then release the thread
+ NS_DispatchToMainThread(releaser);
+ } else {
+ // NotifyTimeout did a dispatch which will release the scan, so we
+ // don't need to release the scan
+ NS_RELEASE(releaser);
+ }
+ }
+ }
+ _endthreadex(0);
+ return 0;
+}
diff --git a/components/downloads/src/nsDownloadScanner.h b/components/downloads/src/nsDownloadScanner.h
new file mode 100644
index 000000000..3301489fe
--- /dev/null
+++ b/components/downloads/src/nsDownloadScanner.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; 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/. */
+
+/* vim: se cin sw=2 ts=2 et : */
+
+#ifndef nsDownloadScanner_h_
+#define nsDownloadScanner_h_
+
+#ifdef WIN32_LEAN_AND_MEAN
+#undef WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#define AVVENDOR
+#include <objidl.h>
+#include <msoav.h>
+#include <shlobj.h>
+
+#include "nsAutoPtr.h"
+#include "nsThreadUtils.h"
+#include "nsTArray.h"
+#include "nsIObserver.h"
+#include "nsIURI.h"
+
+enum AVScanState
+{
+ AVSCAN_NOTSTARTED = 0,
+ AVSCAN_SCANNING,
+ AVSCAN_GOOD,
+ AVSCAN_BAD,
+ AVSCAN_UGLY,
+ AVSCAN_FAILED,
+ AVSCAN_TIMEDOUT
+};
+
+enum AVCheckPolicyState
+{
+ AVPOLICY_DOWNLOAD,
+ AVPOLICY_PROMPT,
+ AVPOLICY_BLOCKED
+};
+
+// See nsDownloadScanner.cpp for declaration and definition
+class nsDownloadScannerWatchdog;
+class nsDownload;
+
+class nsDownloadScanner
+{
+public:
+ nsDownloadScanner();
+ ~nsDownloadScanner();
+ nsresult Init();
+ nsresult ScanDownload(nsDownload *download);
+ AVCheckPolicyState CheckPolicy(nsIURI *aSource, nsIURI *aTarget);
+
+private:
+ bool mAESExists;
+ nsTArray<CLSID> mScanCLSID;
+ bool IsAESAvailable();
+ bool EnumerateOAVProviders();
+
+ nsAutoPtr<nsDownloadScannerWatchdog> mWatchdog;
+
+ static unsigned int __stdcall ScannerThreadFunction(void *p);
+ class Scan : public mozilla::Runnable
+ {
+ public:
+ Scan(nsDownloadScanner *scanner, nsDownload *download);
+ ~Scan();
+ nsresult Start();
+
+ // Returns the time that Start was called
+ PRTime GetStartTime() const { return mStartTime; }
+ // Returns a copy of the thread handle that can be waited on, but not
+ // terminated
+ // The caller is responsible for closing the handle
+ // If the thread has terminated, then this will return the pseudo-handle
+ // INVALID_HANDLE_VALUE
+ HANDLE GetWaitableThreadHandle() const;
+
+ // Called on a secondary thread to notify the scan that it has timed out
+ // this is used only by the watchdog thread
+ bool NotifyTimeout();
+
+ private:
+ nsDownloadScanner *mDLScanner;
+ PRTime mStartTime;
+ HANDLE mThread;
+ RefPtr<nsDownload> mDownload;
+ // Guards mStatus
+ CRITICAL_SECTION mStateSync;
+ AVScanState mStatus;
+ nsString mPath;
+ nsString mName;
+ nsString mOrigin;
+ // Also true if it is an ftp download
+ bool mIsHttpDownload;
+ bool mSkipSource;
+
+ /* @summary Sets the Scan's state to newState if the current state is
+ expectedState
+ * @param newState The new state of the scan
+ * @param expectedState The state that the caller expects the scan to be in
+ * @return If the old state matched expectedState
+ */
+ bool CheckAndSetState(AVScanState newState, AVScanState expectedState);
+
+ NS_IMETHOD Run();
+
+ void DoScan();
+ bool DoScanAES();
+ bool DoScanOAV();
+
+ friend unsigned int __stdcall nsDownloadScanner::ScannerThreadFunction(void *);
+ };
+ // Used to give access to Scan
+ friend class nsDownloadScannerWatchdog;
+};
+#endif
+
diff --git a/components/downloads/src/nsHelperAppDlg.js b/components/downloads/src/nsHelperAppDlg.js
new file mode 100644
index 000000000..0e5cfdaf0
--- /dev/null
+++ b/components/downloads/src/nsHelperAppDlg.js
@@ -0,0 +1,1138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 {utils: Cu, interfaces: Ci, classes: Cc, results: Cr} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "EnableDelayHelper",
+ "resource://gre/modules/SharedPromptUtils.jsm");
+
+///////////////////////////////////////////////////////////////////////////////
+//// Helper Functions
+
+/**
+ * Determines if a given directory is able to be used to download to.
+ *
+ * @param aDirectory
+ * The directory to check.
+ * @return true if we can use the directory, false otherwise.
+ */
+function isUsableDirectory(aDirectory)
+{
+ return aDirectory.exists() && aDirectory.isDirectory() &&
+ aDirectory.isWritable();
+}
+
+// Web progress listener so we can detect errors while mLauncher is
+// streaming the data to a temporary file.
+function nsUnknownContentTypeDialogProgressListener(aHelperAppDialog) {
+ this.helperAppDlg = aHelperAppDialog;
+}
+
+nsUnknownContentTypeDialogProgressListener.prototype = {
+ // nsIWebProgressListener methods.
+ // Look for error notifications and display alert to user.
+ onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
+ if ( aStatus != Components.results.NS_OK ) {
+ // Display error alert (using text supplied by back-end).
+ // FIXME this.dialog is undefined?
+ Services.prompt.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
+ // Close the dialog.
+ this.helperAppDlg.onCancel();
+ if ( this.helperAppDlg.mDialog ) {
+ this.helperAppDlg.mDialog.close();
+ }
+ }
+ },
+
+ // Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, and onRefreshAttempted notifications.
+ onProgressChange: function( aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress ) {
+ },
+
+ onProgressChange64: function( aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress ) {
+ },
+
+
+
+ onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
+ },
+
+ onLocationChange: function( aWebProgress, aRequest, aLocation, aFlags ) {
+ },
+
+ onSecurityChange: function( aWebProgress, aRequest, state ) {
+ },
+
+ onRefreshAttempted: function( aWebProgress, aURI, aDelay, aSameURI ) {
+ return true;
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+//// nsUnknownContentTypeDialog
+
+/* This file implements the nsIHelperAppLauncherDialog interface.
+ *
+ * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog,
+ * comprised of:
+ * - a JS constructor function
+ * - a prototype providing all the interface methods and implementation stuff
+ *
+ * In addition, this file implements an nsIModule object that registers the
+ * nsUnknownContentTypeDialog component.
+ */
+
+const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
+const nsITimer = Components.interfaces.nsITimer;
+
+var downloadModule = {};
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/DownloadLastDir.jsm", downloadModule);
+Components.utils.import("resource://gre/modules/DownloadPaths.jsm");
+Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+Components.utils.import("resource://gre/modules/Downloads.jsm");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+/* ctor
+ */
+function nsUnknownContentTypeDialog() {
+ // Initialize data properties.
+ this.mLauncher = null;
+ this.mContext = null;
+ this.mReason = null;
+ this.chosenApp = null;
+ this.givenDefaultApp = false;
+ this.updateSelf = true;
+ this.mTitle = "";
+}
+
+nsUnknownContentTypeDialog.prototype = {
+ classID: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
+
+ nsIMIMEInfo : Components.interfaces.nsIMIMEInfo,
+
+ QueryInterface: function (iid) {
+ if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
+ !iid.equals(Components.interfaces.nsITimerCallback) &&
+ !iid.equals(Components.interfaces.nsISupports)) {
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ return this;
+ },
+
+ // ---------- nsIHelperAppLauncherDialog methods ----------
+
+ // show: Open XUL dialog using window watcher. Since the dialog is not
+ // modal, it needs to be a top level window and the way to open
+ // one of those is via that route).
+ show: function(aLauncher, aContext, aReason) {
+ this.mLauncher = aLauncher;
+ this.mContext = aContext;
+ this.mReason = aReason;
+
+ // Cache some information in case this context goes away:
+ try {
+ let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+ this._mDownloadDir = new downloadModule.DownloadLastDir(parent);
+ } catch (ex) {
+ Cu.reportError("Missing window information when showing nsIHelperAppLauncherDialog: " + ex);
+ }
+
+ const nsITimer = Components.interfaces.nsITimer;
+ this._showTimer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(nsITimer);
+ this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
+ },
+
+ // When opening from new tab, if tab closes while dialog is opening,
+ // (which is a race condition on the XUL file being cached and the timer
+ // in nsExternalHelperAppService), the dialog gets a blur and doesn't
+ // activate the OK button. So we wait a bit before doing opening it.
+ reallyShow: function() {
+ try {
+ let ir = this.mContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ let docShell = ir.getInterface(Components.interfaces.nsIDocShell);
+ let rootWin = docShell.QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ let ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Components.interfaces.nsIWindowWatcher);
+ this.mDialog = ww.openWindow(rootWin,
+ "chrome://mozapps/content/downloads/unknownContentType.xul",
+ null,
+ "chrome,centerscreen,titlebar,dialog=yes,dependent",
+ null);
+ } catch (ex) {
+ // The containing window may have gone away. Break reference
+ // cycles and stop doing the download.
+ this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED);
+ return;
+ }
+
+ // Hook this object to the dialog.
+ this.mDialog.dialog = this;
+
+ // Hook up utility functions.
+ this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey;
+
+ // Watch for error notifications.
+ var progressListener = new nsUnknownContentTypeDialogProgressListener(this);
+ this.mLauncher.setWebProgressListener(progressListener);
+ },
+
+ //
+ // displayBadPermissionAlert()
+ //
+ // Diplay an alert panel about the bad permission of folder/directory.
+ //
+ displayBadPermissionAlert: function () {
+ let bundle =
+ Services.strings.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
+
+ Services.prompt.alert(this.dialog,
+ bundle.GetStringFromName("badPermissions.title"),
+ bundle.GetStringFromName("badPermissions"));
+ },
+
+ promptForSaveToFileAsync: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
+ var result = null;
+
+ this.mLauncher = aLauncher;
+
+ let prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ let bundle =
+ Services.strings
+ .createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
+
+ let parent;
+ let gDownloadLastDir;
+ try {
+ parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+ } catch (ex) {}
+
+ if (parent) {
+ gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
+ } else {
+ // Use the cached download info, but pick an arbitrary parent window
+ // because the original one is definitely gone (and nsIFilePicker doesn't like
+ // a null parent):
+ gDownloadLastDir = this._mDownloadDir;
+ let windowsEnum = Services.wm.getEnumerator("");
+ while (windowsEnum.hasMoreElements()) {
+ let someWin = windowsEnum.getNext();
+ // We need to make sure we don't end up with this dialog, because otherwise
+ // that's going to go away when the user clicks "Save", and that breaks the
+ // windows file picker that's supposed to show up if we let the user choose
+ // where to save files...
+ if (someWin != this.mDialog) {
+ parent = someWin;
+ }
+ }
+ if (!parent) {
+ Cu.reportError("No candidate parent windows were found for the save filepicker." +
+ "This should never happen.");
+ }
+ }
+
+ Task.spawn(function() {
+ if (!aForcePrompt) {
+ // Check to see if the user wishes to auto save to the default download
+ // folder without prompting. Note that preference might not be set.
+ let autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR, false);
+
+ if (autodownload) {
+ // Retrieve the user's default download directory
+ let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
+ let defaultFolder = new FileUtils.File(preferredDir);
+
+ try {
+ result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension);
+ }
+ catch (ex) {
+ // When the default download directory is write-protected,
+ // prompt the user for a different target file.
+ }
+
+ // Check to make sure we have a valid directory, otherwise, prompt
+ if (result) {
+ // This path is taken when we have a writable default download directory.
+ aLauncher.saveDestinationAvailable(result);
+ return;
+ }
+ }
+ }
+
+ // Use file picker to show dialog.
+ var nsIFilePicker = Components.interfaces.nsIFilePicker;
+ var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ var windowTitle = bundle.GetStringFromName("saveDialogTitle");
+ picker.init(parent, windowTitle, nsIFilePicker.modeSave);
+ picker.defaultString = aDefaultFile;
+
+ if (aSuggestedFileExtension) {
+ // aSuggestedFileExtension includes the period, so strip it
+ picker.defaultExtension = aSuggestedFileExtension.substring(1);
+ }
+ else {
+ try {
+ picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
+ }
+ catch (ex) { }
+ }
+
+ var wildCardExtension = "*";
+ if (aSuggestedFileExtension) {
+ wildCardExtension += aSuggestedFileExtension;
+ picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension);
+ }
+
+ picker.appendFilters( nsIFilePicker.filterAll );
+
+ // Default to lastDir if it is valid, otherwise use the user's default
+ // downloads directory. getPreferredDownloadsDirectory should always
+ // return a valid directory path, so we can safely default to it.
+ let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
+ picker.displayDirectory = new FileUtils.File(preferredDir);
+
+ gDownloadLastDir.getFileAsync(aLauncher.source, function LastDirCallback(lastDir) {
+ if (lastDir && isUsableDirectory(lastDir))
+ picker.displayDirectory = lastDir;
+
+ if (picker.show() == nsIFilePicker.returnCancel) {
+ // null result means user cancelled.
+ aLauncher.saveDestinationAvailable(null);
+ return;
+ }
+
+ // Be sure to save the directory the user chose through the Save As...
+ // dialog as the new browser.download.dir since the old one
+ // didn't exist.
+ result = picker.file;
+
+ if (result) {
+ try {
+ // Remove the file so that it's not there when we ensure non-existence later;
+ // this is safe because for the file to exist, the user would have had to
+ // confirm that he wanted the file overwritten.
+ // Only remove file if final name exists
+ if (result.exists() && this.getFinalLeafName(result.leafName) == result.leafName)
+ result.remove(false);
+ }
+ catch (ex) {
+ // As it turns out, the failure to remove the file, for example due to
+ // permission error, will be handled below eventually somehow.
+ }
+
+ var newDir = result.parent.QueryInterface(Components.interfaces.nsILocalFile);
+
+ // Do not store the last save directory as a pref inside the private browsing mode
+ gDownloadLastDir.setFile(aLauncher.source, newDir);
+
+ try {
+ result = this.validateLeafName(newDir, result.leafName, null);
+ }
+ catch (ex) {
+ // When the chosen download directory is write-protected,
+ // display an informative error message.
+ // In all cases, download will be stopped.
+
+ if (ex.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED) {
+ this.displayBadPermissionAlert();
+ aLauncher.saveDestinationAvailable(null);
+ return;
+ }
+
+ }
+ }
+ aLauncher.saveDestinationAvailable(result);
+ }.bind(this));
+ }.bind(this)).then(null, Components.utils.reportError);
+ },
+
+ getFinalLeafName: function (aLeafName, aFileExt)
+ {
+ // Remove any leading periods, since we don't want to save hidden files
+ // automatically.
+ aLeafName = aLeafName.replace(/^\.+/, "");
+
+ if (aLeafName == "")
+ aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
+
+ return aLeafName;
+ },
+
+ /**
+ * Ensures that a local folder/file combination does not already exist in
+ * the file system (or finds such a combination with a reasonably similar
+ * leaf name), creates the corresponding file, and returns it.
+ *
+ * @param aLocalFolder
+ * the folder where the file resides
+ * @param aLeafName
+ * the string name of the file (may be empty if no name is known,
+ * in which case a name will be chosen)
+ * @param aFileExt
+ * the extension of the file, if one is known; this will be ignored
+ * if aLeafName is non-empty
+ * @return nsILocalFile
+ * the created file
+ * @throw an error such as permission doesn't allow creation of
+ * file, etc.
+ */
+ validateLeafName: function (aLocalFolder, aLeafName, aFileExt)
+ {
+ if (!(aLocalFolder && isUsableDirectory(aLocalFolder))) {
+ throw new Components.Exception("Destination directory non-existing or permission error",
+ Components.results.NS_ERROR_FILE_ACCESS_DENIED);
+ }
+
+ aLeafName = this.getFinalLeafName(aLeafName, aFileExt);
+ aLocalFolder.append(aLeafName);
+
+ // The following assignment can throw an exception, but
+ // is now caught properly in the caller of validateLeafName.
+ var createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder);
+
+#ifdef XP_WIN
+ let ext;
+ try {
+ // We can fail here if there's no primary extension set
+ ext = "." + this.mLauncher.MIMEInfo.primaryExtension;
+ } catch (e) { }
+
+ // Append a file extension if it's an executable that doesn't have one
+ // but make sure we actually have an extension to add
+ let leaf = createdFile.leafName;
+ if (ext && leaf.slice(-ext.length) != ext && createdFile.isExecutable()) {
+ createdFile.remove(false);
+ aLocalFolder.leafName = leaf + ext;
+ createdFile = DownloadPaths.createNiceUniqueFile(aLocalFolder);
+ }
+#endif
+
+ return createdFile;
+ },
+
+ // ---------- implementation methods ----------
+
+ // initDialog: Fill various dialog fields with initial content.
+ initDialog : function() {
+ // Put file name in window title.
+ var suggestedFileName = this.mLauncher.suggestedFileName;
+
+ // Some URIs do not implement nsIURL, so we can't just QI.
+ var url = this.mLauncher.source;
+ if (url instanceof Components.interfaces.nsINestedURI)
+ url = url.innermostURI;
+
+ var fname = "";
+ var iconPath = "goat";
+ this.mSourcePath = url.prePath;
+ if (url instanceof Components.interfaces.nsIURL) {
+ // A url, use file name from it.
+ fname = iconPath = url.fileName;
+ this.mSourcePath += url.directory;
+ } else {
+ // A generic uri, use path.
+ fname = url.path;
+ this.mSourcePath += url.path;
+ }
+
+ if (suggestedFileName)
+ fname = iconPath = suggestedFileName;
+
+ var displayName = fname.replace(/ +/g, " ");
+
+ this.mTitle = this.dialogElement("strings").getFormattedString("title", [displayName]);
+ this.mDialog.document.title = this.mTitle;
+
+ // Put content type, filename and location into intro.
+ this.initIntro(url, fname, displayName);
+
+ var iconString = "moz-icon://" + iconPath + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
+ this.dialogElement("contentTypeImage").setAttribute("src", iconString);
+
+ // if always-save and is-executable and no-handler
+ // then set up simple ui
+ var mimeType = this.mLauncher.MIMEInfo.MIMEType;
+ var shouldntRememberChoice = (mimeType == "application/octet-stream" ||
+ mimeType == "application/x-msdownload" ||
+ this.mLauncher.targetFileIsExecutable);
+ if ((shouldntRememberChoice && !this.openWithDefaultOK()) ||
+ Services.prefs.getBoolPref("browser.download.forbid_open_with")) {
+ // hide featured choice
+ this.dialogElement("normalBox").collapsed = true;
+ // show basic choice
+ this.dialogElement("basicBox").collapsed = false;
+ // change button labels and icons; use "save" icon for the accept
+ // button since it's the only action possible
+ let acceptButton = this.mDialog.document.documentElement
+ .getButton("accept");
+ acceptButton.label = this.dialogElement("strings")
+ .getString("unknownAccept.label");
+ acceptButton.setAttribute("icon", "save");
+ this.mDialog.document.documentElement.getButton("cancel").label = this.dialogElement("strings").getString("unknownCancel.label");
+ // hide other handler
+ this.dialogElement("openHandler").collapsed = true;
+ // set save as the selected option
+ this.dialogElement("mode").selectedItem = this.dialogElement("save");
+ }
+ else {
+ this.initAppAndSaveToDiskValues();
+
+ // Initialize "always ask me" box. This should always be disabled
+ // and set to true for the ambiguous type application/octet-stream.
+ // We don't also check for application/x-msdownload here since we
+ // want users to be able to autodownload .exe files.
+ var rememberChoice = this.dialogElement("rememberChoice");
+
+ // Just because we have a content-type of application/octet-stream
+ // here doesn't actually mean that the content is of that type. Many
+ // servers default to sending text/plain for file types they don't know
+ // about. To account for this, the uriloader does some checking to see
+ // if a file sent as text/plain contains binary characters, and if so (*)
+ // it morphs the content-type into application/octet-stream so that
+ // the file can be properly handled. Since this is not generic binary
+ // data, rather, a data format that the system probably knows about,
+ // we don't want to use the content-type provided by this dialog's
+ // opener, as that's the generic application/octet-stream that the
+ // uriloader has passed, rather we want to ask the MIME Service.
+ // This is so we don't needlessly disable the "autohandle" checkbox.
+
+ // commented out to close the opening brace in the if statement.
+ // var mimeService = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService);
+ // var type = mimeService.getTypeFromURI(this.mLauncher.source);
+ // this.realMIMEInfo = mimeService.getFromTypeAndExtension(type, "");
+
+ // if (type == "application/octet-stream") {
+ if (shouldntRememberChoice) {
+ rememberChoice.checked = false;
+ rememberChoice.disabled = true;
+ }
+ else {
+ rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling &&
+ this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.handleInternally;
+ }
+ this.toggleRememberChoice(rememberChoice);
+
+ // XXXben - menulist won't init properly, hack.
+ var openHandler = this.dialogElement("openHandler");
+ openHandler.parentNode.removeChild(openHandler);
+ var openHandlerBox = this.dialogElement("openHandlerBox");
+ openHandlerBox.appendChild(openHandler);
+ }
+
+ this.mDialog.setTimeout("dialog.postShowCallback()", 0);
+
+ this.delayHelper = new EnableDelayHelper({
+ disableDialog: () => {
+ this.mDialog.document.documentElement.getButton("accept").disabled = true;
+ },
+ enableDialog: () => {
+ this.mDialog.document.documentElement.getButton("accept").disabled = false;
+ },
+ focusTarget: this.mDialog
+ });
+ },
+
+ notify: function (aTimer) {
+ if (aTimer == this._showTimer) {
+ if (!this.mDialog) {
+ this.reallyShow();
+ }
+ // The timer won't release us, so we have to release it.
+ this._showTimer = null;
+ }
+ else if (aTimer == this._saveToDiskTimer) {
+ // Since saveToDisk may open a file picker and therefore block this routine,
+ // we should only call it once the dialog is closed.
+ this.mLauncher.saveToDisk(null, false);
+ this._saveToDiskTimer = null;
+ }
+ },
+
+ postShowCallback: function () {
+ this.mDialog.sizeToContent();
+
+ // Set initial focus
+ this.dialogElement("mode").focus();
+ },
+
+ // initIntro:
+ initIntro: function(url, filename, displayname) {
+ this.dialogElement( "location" ).value = displayname;
+ this.dialogElement( "location" ).setAttribute("realname", filename);
+ this.dialogElement( "location" ).setAttribute("tooltiptext", displayname);
+
+ // if mSourcePath is a local file, then let's use the pretty path name
+ // instead of an ugly url...
+ var pathString;
+ if (url instanceof Components.interfaces.nsIFileURL) {
+ try {
+ // Getting .file might throw, or .parent could be null
+ pathString = url.file.parent.path;
+ } catch (ex) {}
+ }
+
+ if (!pathString) {
+ // wasn't a fileURL
+ var tmpurl = url.clone(); // don't want to change the real url
+ try {
+ tmpurl.userPass = "";
+ } catch (ex) {}
+ pathString = tmpurl.prePath;
+ }
+
+ // Set the location text, which is separate from the intro text so it can be cropped
+ var location = this.dialogElement( "source" );
+ location.value = pathString;
+ location.setAttribute("tooltiptext", this.mSourcePath);
+
+ // Show the type of file.
+ var type = this.dialogElement("type");
+ var mimeInfo = this.mLauncher.MIMEInfo;
+
+ // 1. Try to use the pretty description of the type, if one is available.
+ var typeString = mimeInfo.description;
+
+ if (typeString == "") {
+ // 2. If there is none, use the extension to identify the file, e.g. "ZIP file"
+ var primaryExtension = "";
+ try {
+ primaryExtension = mimeInfo.primaryExtension;
+ }
+ catch (ex) {
+ }
+ if (primaryExtension != "")
+ typeString = this.dialogElement("strings").getFormattedString("fileType", [primaryExtension.toUpperCase()]);
+ // 3. If we can't even do that, just give up and show the MIME type.
+ else
+ typeString = mimeInfo.MIMEType;
+ }
+ // When the length is unknown, contentLength would be -1
+ if (this.mLauncher.contentLength >= 0) {
+ let [size, unit] = DownloadUtils.
+ convertByteUnits(this.mLauncher.contentLength);
+ type.value = this.dialogElement("strings")
+ .getFormattedString("orderedFileSizeWithType",
+ [typeString, size, unit]);
+ }
+ else {
+ type.value = typeString;
+ }
+ },
+
+ // Returns true if opening the default application makes sense.
+ openWithDefaultOK: function() {
+ // The checking is different on Windows...
+#ifdef XP_WIN
+ // Windows presents some special cases.
+ // We need to prevent use of "system default" when the file is
+ // executable (so the user doesn't launch nasty programs downloaded
+ // from the web), and, enable use of "system default" if it isn't
+ // executable (because we will prompt the user for the default app
+ // in that case).
+
+ // Default is Ok if the file isn't executable (and vice-versa).
+ return !this.mLauncher.targetFileIsExecutable;
+#else
+ // On other platforms, default is Ok if there is a default app.
+ // Note that nsIMIMEInfo providers need to ensure that this holds true
+ // on each platform.
+ return this.mLauncher.MIMEInfo.hasDefaultHandler;
+#endif
+ },
+
+ // Set "default" application description field.
+ initDefaultApp: function() {
+ // Use description, if we can get one.
+ var desc = this.mLauncher.MIMEInfo.defaultDescription;
+ if (desc) {
+ var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]);
+ this.dialogElement("defaultHandler").label = defaultApp;
+ }
+ else {
+ this.dialogElement("modeDeck").setAttribute("selectedIndex", "1");
+ // Hide the default handler item too, in case the user picks a
+ // custom handler at a later date which triggers the menulist to show.
+ this.dialogElement("defaultHandler").hidden = true;
+ }
+ },
+
+ // getPath:
+ getPath: function (aFile) {
+ return aFile.path;
+ },
+
+ // initAppAndSaveToDiskValues:
+ initAppAndSaveToDiskValues: function() {
+ var modeGroup = this.dialogElement("mode");
+
+ // We don't let users open .exe files or random binary data directly
+ // from the browser at the moment because of security concerns.
+ var openWithDefaultOK = this.openWithDefaultOK();
+ var mimeType = this.mLauncher.MIMEInfo.MIMEType;
+ if (this.mLauncher.targetFileIsExecutable || (
+ (mimeType == "application/octet-stream" ||
+ mimeType == "application/x-msdownload") &&
+ !openWithDefaultOK)) {
+ this.dialogElement("open").disabled = true;
+ var openHandler = this.dialogElement("openHandler");
+ openHandler.disabled = true;
+ openHandler.selectedItem = null;
+ modeGroup.selectedItem = this.dialogElement("save");
+ return;
+ }
+
+ // Fill in helper app info, if there is any.
+ try {
+ this.chosenApp =
+ this.mLauncher.MIMEInfo.preferredApplicationHandler
+ .QueryInterface(Components.interfaces.nsILocalHandlerApp);
+ } catch (e) {
+ this.chosenApp = null;
+ }
+ // Initialize "default application" field.
+ this.initDefaultApp();
+
+ var otherHandler = this.dialogElement("otherHandler");
+
+ // Fill application name textbox.
+ if (this.chosenApp && this.chosenApp.executable &&
+ this.chosenApp.executable.path) {
+ otherHandler.setAttribute("path",
+ this.getPath(this.chosenApp.executable));
+
+ otherHandler.label = this.getFileDisplayName(this.chosenApp.executable);
+ otherHandler.hidden = false;
+ }
+
+ var openHandler = this.dialogElement("openHandler");
+ openHandler.selectedIndex = 0;
+ var defaultOpenHandler = this.dialogElement("defaultHandler");
+
+ if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
+ // Open (using system default).
+ modeGroup.selectedItem = this.dialogElement("open");
+ } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
+ // Open with given helper app.
+ modeGroup.selectedItem = this.dialogElement("open");
+ openHandler.selectedItem = (otherHandler && !otherHandler.hidden) ?
+ otherHandler : defaultOpenHandler;
+ } else {
+ // Save to disk.
+ modeGroup.selectedItem = this.dialogElement("save");
+ }
+
+ // If we don't have a "default app" then disable that choice.
+ if (!openWithDefaultOK) {
+ var isSelected = defaultOpenHandler.selected;
+
+ // Disable that choice.
+ defaultOpenHandler.hidden = true;
+ // If that's the default, then switch to "save to disk."
+ if (isSelected) {
+ openHandler.selectedIndex = 1;
+ modeGroup.selectedItem = this.dialogElement("save");
+ }
+ }
+
+ otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = false;
+ this.updateOKButton();
+ },
+
+ // Returns the user-selected application
+ helperAppChoice: function() {
+ return this.chosenApp;
+ },
+
+ get saveToDisk() {
+ return this.dialogElement("save").selected;
+ },
+
+ get useOtherHandler() {
+ return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1;
+ },
+
+ get useSystemDefault() {
+ return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0;
+ },
+
+ toggleRememberChoice: function (aCheckbox) {
+ this.dialogElement("settingsChange").hidden = !aCheckbox.checked;
+ this.mDialog.sizeToContent();
+ },
+
+ openHandlerCommand: function () {
+ var openHandler = this.dialogElement("openHandler");
+ if (openHandler.selectedItem.id == "choose")
+ this.chooseApp();
+ else
+ openHandler.setAttribute("lastSelectedItemID", openHandler.selectedItem.id);
+ },
+
+ updateOKButton: function() {
+ var ok = false;
+ if (this.dialogElement("save").selected) {
+ // This is always OK.
+ ok = true;
+ }
+ else if (this.dialogElement("open").selected) {
+ switch (this.dialogElement("openHandler").selectedIndex) {
+ case 0:
+ // No app need be specified in this case.
+ ok = true;
+ break;
+ case 1:
+ // only enable the OK button if we have a default app to use or if
+ // the user chose an app....
+ ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path"));
+ break;
+ }
+ }
+
+ // Enable Ok button if ok to press.
+ this.mDialog.document.documentElement.getButton("accept").disabled = !ok;
+ },
+
+ // Returns true iff the user-specified helper app has been modified.
+ appChanged: function() {
+ return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
+ },
+
+ updateMIMEInfo: function() {
+ // Don't update mime type preferences when the preferred action is set to
+ // the internal handler -- this dialog is the result of the handler fallback
+ // (e.g. Content-Disposition was set as attachment)
+ var discardUpdate = this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.handleInternally &&
+ !this.dialogElement("rememberChoice").checked;
+
+ var needUpdate = false;
+ // If current selection differs from what's in the mime info object,
+ // then we need to update.
+ if (this.saveToDisk) {
+ needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
+ if (needUpdate)
+ this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
+ }
+ else if (this.useSystemDefault) {
+ needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
+ if (needUpdate)
+ this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
+ }
+ else {
+ // For "open with", we need to check both preferred action and whether the user chose
+ // a new app.
+ needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
+ if (needUpdate) {
+ this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
+ // App may have changed - Update application
+ var app = this.helperAppChoice();
+ this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
+ }
+ }
+ // We will also need to update if the "always ask" flag has changed.
+ needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked);
+
+ // One last special case: If the input "always ask" flag was false, then we always
+ // update. In that case we are displaying the helper app dialog for the first
+ // time for this mime type and we need to store the user's action in the mimeTypes.rdf
+ // data source (whether that action has changed or not; if it didn't change, then we need
+ // to store the "always ask" flag so the helper app dialog will or won't display
+ // next time, per the user's selection).
+ needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
+
+ // Make sure mime info has updated setting for the "always ask" flag.
+ this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked;
+
+ return needUpdate && !discardUpdate;
+ },
+
+ // See if the user changed things, and if so, update the
+ // mimeTypes.rdf entry for this mime type.
+ updateHelperAppPref: function() {
+ var handlerInfo = this.mLauncher.MIMEInfo;
+ var hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(Ci.nsIHandlerService);
+ hs.store(handlerInfo);
+ },
+
+ // onOK:
+ onOK: function() {
+ // Verify typed app path, if necessary.
+ if (this.useOtherHandler) {
+ var helperApp = this.helperAppChoice();
+ if (!helperApp || !helperApp.executable ||
+ !helperApp.executable.exists()) {
+ // Show alert and try again.
+ var bundle = this.dialogElement("strings");
+ var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").getAttribute("path")]);
+ Services.prompt.alert(this.mDialog, bundle.getString("badApp.title"), msg);
+
+ // Disable the OK button.
+ this.mDialog.document.documentElement.getButton("accept").disabled = true;
+ this.dialogElement("mode").focus();
+
+ // Clear chosen application.
+ this.chosenApp = null;
+
+ // Leave dialog up.
+ return false;
+ }
+ }
+
+ // Remove our web progress listener (a progress dialog will be
+ // taking over).
+ this.mLauncher.setWebProgressListener(null);
+
+ // saveToDisk and launchWithApplication can return errors in
+ // certain circumstances (e.g. The user clicks cancel in the
+ // "Save to Disk" dialog. In those cases, we don't want to
+ // update the helper application preferences in the RDF file.
+ try {
+ var needUpdate = this.updateMIMEInfo();
+
+ if (this.dialogElement("save").selected) {
+ // If we're using a default download location, create a path
+ // for the file to be saved to to pass to |saveToDisk| - otherwise
+ // we must ask the user to pick a save name.
+
+ /*
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
+ var targetFile = null;
+ try {
+ targetFile = prefs.getComplexValue("browser.download.defaultFolder",
+ Components.interfaces.nsILocalFile);
+ var leafName = this.dialogElement("location").getAttribute("realname");
+ // Ensure that we don't overwrite any existing files here.
+ targetFile = this.validateLeafName(targetFile, leafName, null);
+ }
+ catch(e) { }
+
+ this.mLauncher.saveToDisk(targetFile, false);
+ */
+
+ // see @notify
+ // we cannot use opener's setTimeout, see bug 420405
+ this._saveToDiskTimer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(nsITimer);
+ this._saveToDiskTimer.initWithCallback(this, 0,
+ nsITimer.TYPE_ONE_SHOT);
+ }
+ else
+ this.mLauncher.launchWithApplication(null, false);
+
+ // Update user pref for this mime type (if necessary). We do not
+ // store anything in the mime type preferences for the ambiguous
+ // type application/octet-stream. We do NOT do this for
+ // application/x-msdownload since we want users to be able to
+ // autodownload these to disk.
+ if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream")
+ this.updateHelperAppPref();
+ } catch(e) { }
+
+ // Unhook dialog from this object.
+ this.mDialog.dialog = null;
+
+ // Close up dialog by returning true.
+ return true;
+ },
+
+ // onCancel:
+ onCancel: function() {
+ // Remove our web progress listener.
+ this.mLauncher.setWebProgressListener(null);
+
+ // Cancel app launcher.
+ try {
+ this.mLauncher.cancel(Components.results.NS_BINDING_ABORTED);
+ } catch(exception) {
+ }
+
+ // Unhook dialog from this object.
+ this.mDialog.dialog = null;
+
+ // Close up dialog by returning true.
+ return true;
+ },
+
+ // dialogElement: Convenience.
+ dialogElement: function(id) {
+ return this.mDialog.document.getElementById(id);
+ },
+
+ // Retrieve the pretty description from the file
+ getFileDisplayName: function getFileDisplayName(file)
+ {
+#ifdef XP_WIN
+ if (file instanceof Components.interfaces.nsILocalFileWin) {
+ try {
+ return file.getVersionInfoField("FileDescription");
+ } catch (e) {}
+ }
+#endif
+
+ return file.leafName;
+ },
+
+ finishChooseApp: function() {
+ if (this.chosenApp) {
+ // Show the "handler" menulist since we have a (user-specified)
+ // application now.
+ this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");
+
+ // Update dialog.
+ var otherHandler = this.dialogElement("otherHandler");
+ otherHandler.removeAttribute("hidden");
+ otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable));
+
+#ifdef XP_WIN
+ otherHandler.label = this.getFileDisplayName(this.chosenApp.executable);
+#else
+ otherHandler.label = this.chosenApp.name;
+#endif
+
+ this.dialogElement("openHandler").selectedIndex = 1;
+ this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler");
+
+ this.dialogElement("mode").selectedItem = this.dialogElement("open");
+ }
+ else {
+ var openHandler = this.dialogElement("openHandler");
+ var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
+ if (!lastSelectedID)
+ lastSelectedID = "defaultHandler";
+ openHandler.selectedItem = this.dialogElement(lastSelectedID);
+ }
+ },
+ // chooseApp: Open file picker and prompt user for application.
+ chooseApp: function() {
+#ifdef XP_WIN
+ // Protect against the lack of an extension
+ var fileExtension = "";
+ try {
+ fileExtension = this.mLauncher.MIMEInfo.primaryExtension;
+ } catch(ex) {
+ }
+
+ // Try to use the pretty description of the type, if one is available.
+ var typeString = this.mLauncher.MIMEInfo.description;
+
+ if (!typeString) {
+ // If there is none, use the extension to
+ // identify the file, e.g. "ZIP file"
+ if (fileExtension) {
+ typeString =
+ this.dialogElement("strings").
+ getFormattedString("fileType", [fileExtension.toUpperCase()]);
+ } else {
+ // If we can't even do that, just give up and show the MIME type.
+ typeString = this.mLauncher.MIMEInfo.MIMEType;
+ }
+ }
+
+ var params = {};
+ params.title =
+ this.dialogElement("strings").getString("chooseAppFilePickerTitle");
+ params.description = typeString;
+ params.filename = this.mLauncher.suggestedFileName;
+ params.mimeInfo = this.mLauncher.MIMEInfo;
+ params.handlerApp = null;
+
+ this.mDialog.openDialog("chrome://global/content/appPicker.xul", null,
+ "chrome,modal,centerscreen,titlebar,dialog=yes",
+ params);
+
+ if (params.handlerApp &&
+ params.handlerApp.executable &&
+ params.handlerApp.executable.isFile()) {
+ // Remember the file they chose to run.
+ this.chosenApp = params.handlerApp;
+ }
+#else // XP_WIN
+#if MOZ_WIDGET_GTK == 3
+ var nsIApplicationChooser = Components.interfaces.nsIApplicationChooser;
+ var appChooser = Components.classes["@mozilla.org/applicationchooser;1"]
+ .createInstance(nsIApplicationChooser);
+ appChooser.init(this.mDialog, this.dialogElement("strings").getString("chooseAppFilePickerTitle"));
+ var contentTypeDialogObj = this;
+ let appChooserCallback = function appChooserCallback_done(aResult) {
+ if (aResult) {
+ contentTypeDialogObj.chosenApp = aResult.QueryInterface(Components.interfaces.nsILocalHandlerApp);
+ }
+ contentTypeDialogObj.finishChooseApp();
+ };
+ appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback);
+ // The finishChooseApp is called from appChooserCallback
+ return;
+#else // MOZ_WIDGET_GTK == 3
+ var nsIFilePicker = Components.interfaces.nsIFilePicker;
+ var fp = Components.classes["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ fp.init(this.mDialog,
+ this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
+ nsIFilePicker.modeOpen);
+
+ fp.appendFilters(nsIFilePicker.filterApps);
+
+ if (fp.show() == nsIFilePicker.returnOK && fp.file) {
+ // Remember the file they chose to run.
+ var localHandlerApp =
+ Components.classes["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Components.interfaces.nsILocalHandlerApp);
+ localHandlerApp.executable = fp.file;
+ this.chosenApp = localHandlerApp;
+ }
+#endif // MOZ_WIDGET_GTK == 3
+#endif // XP_WIN
+ this.finishChooseApp();
+ },
+
+ // Turn this on to get debugging messages.
+ debug: false,
+
+ // Dump text (if debug is on).
+ dump: function( text ) {
+ if ( this.debug ) {
+ dump( text );
+ }
+ },
+
+ // dumpObj:
+ dumpObj: function( spec ) {
+ var val = "<undefined>";
+ try {
+ val = eval( "this."+spec ).toString();
+ } catch( exception ) {
+ }
+ this.dump( spec + "=" + val + "\n" );
+ },
+
+ // dumpObjectProperties
+ dumpObjectProperties: function( desc, obj ) {
+ for( prop in obj ) {
+ this.dump( desc + "." + prop + "=" );
+ var val = "<undefined>";
+ try {
+ val = obj[ prop ];
+ } catch ( exception ) {
+ }
+ this.dump( val + "\n" );
+ }
+ }
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsUnknownContentTypeDialog]);
diff --git a/components/exthelper/extApplication.js b/components/exthelper/extApplication.js
new file mode 100644
index 000000000..a56a04c0e
--- /dev/null
+++ b/components/exthelper/extApplication.js
@@ -0,0 +1,719 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/AddonManager.jsm");
+
+// =================================================
+// Console constructor
+function Console() {
+ this._console = Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+}
+
+// =================================================
+// Console implementation
+Console.prototype = {
+ log: function cs_log(aMsg) {
+ this._console.logStringMessage(aMsg);
+ },
+
+ open: function cs_open() {
+ var wMediator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator);
+ var console = wMediator.getMostRecentWindow("global:console");
+ if (!console) {
+ var wWatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Ci.nsIWindowWatcher);
+ wWatch.openWindow(null, "chrome://global/content/console.xul", "_blank",
+ "chrome,dialog=no,all", null);
+ } else {
+ // console was already open
+ console.focus();
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIConsole])
+};
+
+
+// =================================================
+// EventItem constructor
+function EventItem(aType, aData) {
+ this._type = aType;
+ this._data = aData;
+}
+
+// =================================================
+// EventItem implementation
+EventItem.prototype = {
+ _cancel: false,
+
+ get type() {
+ return this._type;
+ },
+
+ get data() {
+ return this._data;
+ },
+
+ preventDefault: function ei_pd() {
+ this._cancel = true;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIEventItem])
+};
+
+
+// =================================================
+// Events constructor
+function Events(notifier) {
+ this._listeners = [];
+ this._notifier = notifier;
+}
+
+// =================================================
+// Events implementation
+Events.prototype = {
+ addListener: function evts_al(aEvent, aListener) {
+ function hasFilter(element) {
+ return element.event == aEvent && element.listener == aListener;
+ }
+
+ if (this._listeners.some(hasFilter))
+ return;
+
+ this._listeners.push({
+ event: aEvent,
+ listener: aListener
+ });
+
+ if (this._notifier) {
+ this._notifier(aEvent, aListener);
+ }
+ },
+
+ removeListener: function evts_rl(aEvent, aListener) {
+ function hasFilter(element) {
+ return (element.event != aEvent) || (element.listener != aListener);
+ }
+
+ this._listeners = this._listeners.filter(hasFilter);
+ },
+
+ dispatch: function evts_dispatch(aEvent, aEventItem) {
+ var eventItem = new EventItem(aEvent, aEventItem);
+
+ this._listeners.forEach(function(key) {
+ if (key.event == aEvent) {
+ key.listener.handleEvent ?
+ key.listener.handleEvent(eventItem) :
+ key.listener(eventItem);
+ }
+ });
+
+ return !eventItem._cancel;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
+};
+
+// =================================================
+// PreferenceObserver (internal class)
+//
+// PreferenceObserver is a global singleton which watches the browser's
+// preferences and sends you events when things change.
+
+function PreferenceObserver() {
+ this._observersDict = {};
+}
+
+PreferenceObserver.prototype = {
+ /**
+ * Add a preference observer.
+ *
+ * @param aPrefs the nsIPrefBranch onto which we'll install our listener.
+ * @param aDomain the domain our listener will watch (a string).
+ * @param aEvent the event to listen to (you probably want "change").
+ * @param aListener the function to call back when the event fires. This
+ * function will receive an EventData argument.
+ */
+ addListener: function po_al(aPrefs, aDomain, aEvent, aListener) {
+ var root = aPrefs.root;
+ if (!this._observersDict[root]) {
+ this._observersDict[root] = {};
+ }
+ var observer = this._observersDict[root][aDomain];
+
+ if (!observer) {
+ observer = {
+ events: new Events(),
+ observe: function po_observer_obs(aSubject, aTopic, aData) {
+ this.events.dispatch("change", aData);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference])
+ };
+ observer.prefBranch = aPrefs;
+ observer.prefBranch.addObserver(aDomain, observer, /* ownsWeak = */ true);
+
+ // Notice that the prefBranch keeps a weak reference to the observer;
+ // it's this._observersDict which keeps the observer alive.
+ this._observersDict[root][aDomain] = observer;
+ }
+ observer.events.addListener(aEvent, aListener);
+ },
+
+ /**
+ * Remove a preference observer.
+ *
+ * This function's parameters are identical to addListener's.
+ */
+ removeListener: function po_rl(aPrefs, aDomain, aEvent, aListener) {
+ var root = aPrefs.root;
+ if (!this._observersDict[root] ||
+ !this._observersDict[root][aDomain]) {
+ return;
+ }
+ var observer = this._observersDict[root][aDomain];
+ observer.events.removeListener(aEvent, aListener);
+
+ if (observer.events._listeners.length == 0) {
+ // nsIPrefBranch objects are not singletons -- we can have two
+ // nsIPrefBranch'es for the same branch. There's no guarantee that
+ // aPrefs is the same object as observer.prefBranch, so we have to call
+ // removeObserver on observer.prefBranch.
+ observer.prefBranch.removeObserver(aDomain, observer);
+ delete this._observersDict[root][aDomain];
+ if (Object.keys(this._observersDict[root]).length == 0) {
+ delete this._observersDict[root];
+ }
+ }
+ }
+};
+
+// =================================================
+// PreferenceBranch constructor
+function PreferenceBranch(aBranch) {
+ if (!aBranch)
+ aBranch = "";
+
+ this._root = aBranch;
+ this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService)
+ .QueryInterface(Ci.nsIPrefBranch);
+
+ if (aBranch)
+ this._prefs = this._prefs.getBranch(aBranch);
+
+ let prefs = this._prefs;
+ this._events = {
+ addListener: function pb_al(aEvent, aListener) {
+ gPreferenceObserver.addListener(prefs, "", aEvent, aListener);
+ },
+ removeListener: function pb_rl(aEvent, aListener) {
+ gPreferenceObserver.removeListener(prefs, "", aEvent, aListener);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
+ };
+}
+
+// =================================================
+// PreferenceBranch implementation
+PreferenceBranch.prototype = {
+ get root() {
+ return this._root;
+ },
+
+ get all() {
+ return this.find({});
+ },
+
+ get events() {
+ return this._events;
+ },
+
+ // XXX: Disabled until we can figure out the wrapped object issues
+ // name: "name" or /name/
+ // path: "foo.bar." or "" or /fo+\.bar/
+ // type: Boolean, Number, String (getPrefType)
+ // locked: true, false (prefIsLocked)
+ // modified: true, false (prefHasUserValue)
+ find: function prefs_find(aOptions) {
+ var retVal = [];
+ var items = this._prefs.getChildList("");
+
+ for (var i = 0; i < items.length; i++) {
+ retVal.push(new Preference(items[i], this));
+ }
+
+ return retVal;
+ },
+
+ has: function prefs_has(aName) {
+ return (this._prefs.getPrefType(aName) != Ci.nsIPrefBranch.PREF_INVALID);
+ },
+
+ get: function prefs_get(aName) {
+ return this.has(aName) ? new Preference(aName, this) : null;
+ },
+
+ getValue: function prefs_gv(aName, aValue) {
+ var type = this._prefs.getPrefType(aName);
+
+ switch (type) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ aValue = this._prefs.getComplexValue(aName, Ci.nsISupportsString).data;
+ break;
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ aValue = this._prefs.getBoolPref(aName);
+ break;
+ case Ci.nsIPrefBranch.PREF_INT:
+ aValue = this._prefs.getIntPref(aName);
+ break;
+ }
+
+ return aValue;
+ },
+
+ setValue: function prefs_sv(aName, aValue) {
+ var type = aValue != null ? aValue.constructor.name : "";
+
+ switch (type) {
+ case "String":
+ var str = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ str.data = aValue;
+ this._prefs.setComplexValue(aName, Ci.nsISupportsString, str);
+ break;
+ case "Boolean":
+ this._prefs.setBoolPref(aName, aValue);
+ break;
+ case "Number":
+ this._prefs.setIntPref(aName, aValue);
+ break;
+ default:
+ throw ("Unknown preference value specified.");
+ }
+ },
+
+ reset: function prefs_reset() {
+ this._prefs.resetBranch("");
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIPreferenceBranch])
+};
+
+
+// =================================================
+// Preference constructor
+function Preference(aName, aBranch) {
+ this._name = aName;
+ this._branch = aBranch;
+
+ var self = this;
+ this._events = {
+ addListener: function pref_al(aEvent, aListener) {
+ gPreferenceObserver.addListener(self._branch._prefs, self._name, aEvent, aListener);
+ },
+ removeListener: function pref_rl(aEvent, aListener) {
+ gPreferenceObserver.removeListener(self._branch._prefs, self._name, aEvent, aListener);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
+ };
+}
+
+// =================================================
+// Preference implementation
+Preference.prototype = {
+ get name() {
+ return this._name;
+ },
+
+ get type() {
+ var value = "";
+ var type = this.branch._prefs.getPrefType(this._name);
+
+ switch (type) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ value = "String";
+ break;
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ value = "Boolean";
+ break;
+ case Ci.nsIPrefBranch.PREF_INT:
+ value = "Number";
+ break;
+ }
+
+ return value;
+ },
+
+ get value() {
+ return this.branch.getValue(this._name, null);
+ },
+
+ set value(aValue) {
+ return this.branch.setValue(this._name, aValue);
+ },
+
+ get locked() {
+ return this.branch._prefs.prefIsLocked(this.name);
+ },
+
+ set locked(aValue) {
+ this.branch._prefs[aValue ? "lockPref" : "unlockPref"](this.name);
+ },
+
+ get modified() {
+ return this.branch._prefs.prefHasUserValue(this.name);
+ },
+
+ get branch() {
+ return this._branch;
+ },
+
+ get events() {
+ return this._events;
+ },
+
+ reset: function pref_reset() {
+ this.branch._prefs.clearUserPref(this.name);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIPreference])
+};
+
+
+// =================================================
+// SessionStorage constructor
+function SessionStorage() {
+ this._storage = {};
+ this._events = new Events();
+}
+
+// =================================================
+// SessionStorage implementation
+SessionStorage.prototype = {
+ get events() {
+ return this._events;
+ },
+
+ has: function ss_has(aName) {
+ return this._storage.hasOwnProperty(aName);
+ },
+
+ set: function ss_set(aName, aValue) {
+ this._storage[aName] = aValue;
+ this._events.dispatch("change", aName);
+ },
+
+ get: function ss_get(aName, aDefaultValue) {
+ return this.has(aName) ? this._storage[aName] : aDefaultValue;
+ },
+
+ QueryInterface : XPCOMUtils.generateQI([Ci.extISessionStorage])
+};
+
+// =================================================
+// ExtensionObserver constructor (internal class)
+//
+// ExtensionObserver is a global singleton which watches the browser's
+// extensions and sends you events when things change.
+
+function ExtensionObserver() {
+ this._eventsDict = {};
+
+ AddonManager.addAddonListener(this);
+ AddonManager.addInstallListener(this);
+}
+
+// =================================================
+// ExtensionObserver implementation (internal class)
+ExtensionObserver.prototype = {
+ onDisabling: function eo_onDisabling(addon, needsRestart) {
+ this._dispatchEvent(addon.id, "disable");
+ },
+
+ onEnabling: function eo_onEnabling(addon, needsRestart) {
+ this._dispatchEvent(addon.id, "enable");
+ },
+
+ onUninstalling: function eo_onUninstalling(addon, needsRestart) {
+ this._dispatchEvent(addon.id, "uninstall");
+ },
+
+ onOperationCancelled: function eo_onOperationCancelled(addon) {
+ this._dispatchEvent(addon.id, "cancel");
+ },
+
+ onInstallEnded: function eo_onInstallEnded(install, addon) {
+ this._dispatchEvent(addon.id, "upgrade");
+ },
+
+ addListener: function eo_al(aId, aEvent, aListener) {
+ var events = this._eventsDict[aId];
+ if (!events) {
+ events = new Events();
+ this._eventsDict[aId] = events;
+ }
+ events.addListener(aEvent, aListener);
+ },
+
+ removeListener: function eo_rl(aId, aEvent, aListener) {
+ var events = this._eventsDict[aId];
+ if (!events) {
+ return;
+ }
+ events.removeListener(aEvent, aListener);
+ if (events._listeners.length == 0) {
+ delete this._eventsDict[aId];
+ }
+ },
+
+ _dispatchEvent: function eo_dispatchEvent(aId, aEvent) {
+ var events = this._eventsDict[aId];
+ if (events) {
+ events.dispatch(aEvent, aId);
+ }
+ }
+};
+
+// =================================================
+// Extension constructor
+function Extension(aItem) {
+ this._item = aItem;
+ this._firstRun = false;
+ this._prefs = new PreferenceBranch("extensions." + this.id + ".");
+ this._storage = new SessionStorage();
+
+ let id = this.id;
+ this._events = {
+ addListener: function ext_events_al(aEvent, aListener) {
+ gExtensionObserver.addListener(id, aEvent, aListener);
+ },
+ removeListener: function ext_events_rl(aEvent, aListener) {
+ gExtensionObserver.addListener(id, aEvent, aListener);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
+ };
+
+ var installPref = "install-event-fired";
+ if (!this._prefs.has(installPref)) {
+ this._prefs.setValue(installPref, true);
+ this._firstRun = true;
+ }
+}
+
+// =================================================
+// Extension implementation
+Extension.prototype = {
+ get id() {
+ return this._item.id;
+ },
+
+ get name() {
+ return this._item.name;
+ },
+
+ get enabled() {
+ return this._item.isActive;
+ },
+
+ get version() {
+ return this._item.version;
+ },
+
+ get firstRun() {
+ return this._firstRun;
+ },
+
+ get storage() {
+ return this._storage;
+ },
+
+ get prefs() {
+ return this._prefs;
+ },
+
+ get events() {
+ return this._events;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIExtension])
+};
+
+
+// =================================================
+// Extensions constructor
+function Extensions(addons) {
+ this._cache = {};
+
+ addons.forEach(function (addon) {
+ this._cache[addon.id] = new Extension(addon);
+ }, this);
+}
+
+// =================================================
+// Extensions implementation
+Extensions.prototype = {
+ get all() {
+ return this.find({});
+ },
+
+ // XXX: Disabled until we can figure out the wrapped object issues
+ // id: "some@id" or /id/
+ // name: "name" or /name/
+ // version: "1.0.1"
+ // minVersion: "1.0"
+ // maxVersion: "2.0"
+ find: function exts_find(aOptions) {
+ return Object.keys(this._cache).map(id => this._cache[id]);
+ },
+
+ has: function exts_has(aId) {
+ return aId in this._cache;
+ },
+
+ get: function exts_get(aId) {
+ return this.has(aId) ? this._cache[aId] : null;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIExtensions])
+};
+
+// =================================================
+// Application globals
+
+var gExtensionObserver = new ExtensionObserver();
+var gPreferenceObserver = new PreferenceObserver();
+
+// =================================================
+// extApplication constructor
+function extApplication() {
+}
+
+// =================================================
+// extApplication implementation
+extApplication.prototype = {
+ initToolkitHelpers: function extApp_initToolkitHelpers() {
+ XPCOMUtils.defineLazyServiceGetter(this, "_info",
+ "@mozilla.org/xre/app-info;1",
+ "nsIXULAppInfo");
+
+ this._obs = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ this._obs.addObserver(this, "xpcom-shutdown", /* ownsWeak = */ true);
+ this._registered = {"unload": true};
+ },
+
+ classInfo: XPCOMUtils.generateCI({interfaces: [Ci.extIApplication,
+ Ci.nsIObserver],
+ flags: Ci.nsIClassInfo.SINGLETON}),
+
+ // extIApplication
+ get id() {
+ return this._info.ID;
+ },
+
+ get name() {
+ return this._info.name;
+ },
+
+ get version() {
+ return this._info.version;
+ },
+
+ // for nsIObserver
+ observe: function app_observe(aSubject, aTopic, aData) {
+ if (aTopic == "app-startup") {
+ this.events.dispatch("load", "application");
+ }
+ else if (aTopic == "final-ui-startup") {
+ this.events.dispatch("ready", "application");
+ }
+ else if (aTopic == "quit-application-requested") {
+ // we can stop the quit by checking the return value
+ if (this.events.dispatch("quit", "application") == false)
+ aSubject.data = true;
+ }
+ else if (aTopic == "xpcom-shutdown") {
+ this.events.dispatch("unload", "application");
+ gExtensionObserver = null;
+ gPreferenceObserver = null;
+ }
+ },
+
+ get console() {
+ let console = new Console();
+ this.__defineGetter__("console", () => console);
+ return this.console;
+ },
+
+ get storage() {
+ let storage = new SessionStorage();
+ this.__defineGetter__("storage", () => storage);
+ return this.storage;
+ },
+
+ get prefs() {
+ let prefs = new PreferenceBranch("");
+ this.__defineGetter__("prefs", () => prefs);
+ return this.prefs;
+ },
+
+ getExtensions: function(callback) {
+ AddonManager.getAddonsByTypes(["extension"], function (addons) {
+ callback.callback(new Extensions(addons));
+ });
+ },
+
+ get events() {
+
+ // This ensures that FUEL only registers for notifications as needed
+ // by callers. Note that the unload (xpcom-shutdown) event is listened
+ // for by default, as it's needed for cleanup purposes.
+ var self = this;
+ function registerCheck(aEvent) {
+ var rmap = { "load": "app-startup",
+ "ready": "final-ui-startup",
+ "quit": "quit-application-requested"};
+ if (!(aEvent in rmap) || aEvent in self._registered)
+ return;
+
+ self._obs.addObserver(self, rmap[aEvent], /* ownsWeak = */ true);
+ self._registered[aEvent] = true;
+ }
+
+ let events = new Events(registerCheck);
+ this.__defineGetter__("events", () => events);
+ return this.events;
+ },
+
+ // helper method for correct quitting/restarting
+ _quitWithFlags: function app__quitWithFlags(aFlags) {
+ let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Components.interfaces.nsISupportsPRBool);
+ let quitType = aFlags & Components.interfaces.nsIAppStartup.eRestart ? "restart" : null;
+ this._obs.notifyObservers(cancelQuit, "quit-application-requested", quitType);
+ if (cancelQuit.data)
+ return false; // somebody canceled our quit request
+
+ let appStartup = Components.classes['@mozilla.org/toolkit/app-startup;1']
+ .getService(Components.interfaces.nsIAppStartup);
+ appStartup.quit(aFlags);
+ return true;
+ },
+
+ quit: function app_quit() {
+ return this._quitWithFlags(Components.interfaces.nsIAppStartup.eAttemptQuit);
+ },
+
+ restart: function app_restart() {
+ return this._quitWithFlags(Components.interfaces.nsIAppStartup.eAttemptQuit |
+ Components.interfaces.nsIAppStartup.eRestart);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIApplication, Ci.nsISupportsWeakReference])
+};
diff --git a/components/exthelper/extIApplication.idl b/components/exthelper/extIApplication.idl
new file mode 100644
index 000000000..51c17b436
--- /dev/null
+++ b/components/exthelper/extIApplication.idl
@@ -0,0 +1,416 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIVariant;
+interface extIPreference;
+interface extISessionStorage;
+
+
+/**
+ * Interface that gives simplified access to the console
+ */
+[scriptable, uuid(ae8482e0-aa5a-11db-abbd-0800200c9a66)]
+interface extIConsole : nsISupports
+{
+ /**
+ * Sends a given string to the console.
+ * @param aMsg
+ * The text to send to the console
+ */
+ void log(in AString aMsg);
+
+ /**
+ * Opens the error console window. The console window
+ * is focused if already open.
+ */
+ void open();
+};
+
+
+/**
+ * Interface holds information about an event.
+ */
+[scriptable, function, uuid(05281820-ab62-11db-abbd-0800200c9a66)]
+interface extIEventItem : nsISupports
+{
+ /**
+ * The name of the event
+ */
+ readonly attribute AString type;
+
+ /**
+ * Can hold extra details and data associated with the event. This
+ * is optional and event specific. If the event does not send extra
+ * details, this is null.
+ */
+ readonly attribute nsIVariant data;
+
+ /**
+ * Cancels the event if it is cancelable.
+ */
+ void preventDefault();
+};
+
+
+/**
+ * Interface used as a callback for listening to events.
+ */
+[scriptable, function, uuid(2dfe3a50-ab2f-11db-abbd-0800200c9a66)]
+interface extIEventListener : nsISupports
+{
+ /**
+ * This method is called whenever an event occurs of the type for which
+ * the extIEventListener interface was registered.
+ *
+ * @param aEvent
+ * The extIEventItem associated with the event.
+ */
+ void handleEvent(in extIEventItem aEvent);
+};
+
+
+/**
+ * Interface for supporting custom events.
+ */
+[scriptable, uuid(3a8ec9d0-ab19-11db-abbd-0800200c9a66)]
+interface extIEvents : nsISupports
+{
+ /**
+ * Adds an event listener to the list. If multiple identical event listeners
+ * are registered on the same event target with the same parameters the
+ * duplicate instances are discarded. They do not cause the EventListener
+ * to be called twice and since they are discarded they do not need to be
+ * removed with the removeListener method.
+ *
+ * @param aEvent
+ * The name of an event
+ * @param aListener
+ * The reference to a listener
+ */
+ void addListener(in AString aEvent, in extIEventListener aListener);
+
+ /**
+ * Removes an event listener from the list. Calling remove
+ * with arguments which do not identify any currently registered
+ * event listener has no effect.
+ * @param aEvent
+ * The name of an event
+ * @param aListener
+ * The reference to a listener
+ */
+ void removeListener(in AString aEvent, in extIEventListener aListener);
+};
+
+
+/**
+ * Interface for simplified access to preferences. The interface has a
+ * predefined root preference branch. The root branch is set based on the
+ * context of the owner. For example, an extension's preferences have a root
+ * of "extensions.<extensionid>.", while the application level preferences
+ * have an empty root. All preference "aName" parameters used in this interface
+ * are relative to the root branch.
+ */
+[scriptable, uuid(ce697d40-aa5a-11db-abbd-0800200c9a66)]
+interface extIPreferenceBranch : nsISupports
+{
+ /**
+ * The name of the branch root.
+ */
+ readonly attribute AString root;
+
+ /**
+ * Array of extIPreference listing all preferences in this branch.
+ */
+ readonly attribute nsIVariant all;
+
+ /**
+ * The events object for the preferences
+ * supports: "change"
+ */
+ readonly attribute extIEvents events;
+
+ /**
+ * Check to see if a preference exists.
+ * @param aName
+ * The name of preference
+ * @returns true if the preference exists, false if not
+ */
+ boolean has(in AString aName);
+
+ /**
+ * Gets an object representing a preference
+ * @param aName
+ * The name of preference
+ * @returns a preference object, or null if the preference does not exist
+ */
+ extIPreference get(in AString aName);
+
+ /**
+ * Gets the value of a preference. Returns a default value if
+ * the preference does not exist.
+ * @param aName
+ * The name of preference
+ * @param aDefaultValue
+ * The value to return if preference does not exist
+ * @returns value of the preference or the given default value if preference
+ * does not exists.
+ */
+ nsIVariant getValue(in AString aName, in nsIVariant aDefaultValue);
+
+ /**
+ * Sets the value of a storage item with the given name.
+ * @param aName
+ * The name of an item
+ * @param aValue
+ * The value to assign to the item
+ */
+ void setValue(in AString aName, in nsIVariant aValue);
+
+ /**
+ * Resets all preferences in a branch back to their default values.
+ */
+ void reset();
+};
+
+/**
+ * Interface for accessing a single preference. The data is not cached.
+ * All reads access the current state of the preference.
+ */
+[scriptable, uuid(2C7462E2-72C2-4473-9007-0E6AE71E23CA)]
+interface extIPreference : nsISupports
+{
+ /**
+ * The name of the preference.
+ */
+ readonly attribute AString name;
+
+ /**
+ * A string representing the type of preference (String, Boolean, or Number).
+ */
+ readonly attribute AString type;
+
+ /**
+ * Get/Set the value of the preference.
+ */
+ attribute nsIVariant value;
+
+ /**
+ * Get the locked state of the preference. Set to a boolean value to (un)lock it.
+ */
+ attribute boolean locked;
+
+ /**
+ * Check if a preference has been modified by the user, or not.
+ */
+ readonly attribute boolean modified;
+
+ /**
+ * The preference branch that contains this preference.
+ */
+ readonly attribute extIPreferenceBranch branch;
+
+ /**
+ * The events object for this preference.
+ * supports: "change"
+ */
+ readonly attribute extIEvents events;
+
+ /**
+ * Resets a preference back to its default values.
+ */
+ void reset();
+};
+
+
+/**
+ * Interface representing an extension
+ */
+[scriptable, uuid(10cee02c-f6e0-4d61-ab27-c16572b18c46)]
+interface extIExtension : nsISupports
+{
+ /**
+ * The id of the extension.
+ */
+ readonly attribute AString id;
+
+ /**
+ * The name of the extension.
+ */
+ readonly attribute AString name;
+
+ /**
+ * Check if the extension is currently enabled, or not.
+ */
+ readonly attribute boolean enabled;
+
+ /**
+ * The version number of the extension.
+ */
+ readonly attribute AString version;
+
+ /**
+ * Indicates whether this is the extension's first run after install
+ */
+ readonly attribute boolean firstRun;
+
+ /**
+ * The preferences object for the extension. Defaults to the
+ * "extensions.<extensionid>." branch.
+ */
+ readonly attribute extIPreferenceBranch prefs;
+
+ /**
+ * The storage object for the extension.
+ */
+ readonly attribute extISessionStorage storage;
+
+ /**
+ * The events object for the extension.
+ * supports: "uninstall"
+ */
+ readonly attribute extIEvents events;
+};
+
+/**
+ * Interface representing a list of all installed extensions
+ */
+[scriptable, uuid(de281930-aa5a-11db-abbd-0800200c9a66)]
+interface extIExtensions : nsISupports
+{
+ /**
+ * Array of extIExtension listing all extensions in the application.
+ */
+ readonly attribute nsIVariant all;
+
+ /**
+ * Determines if an extension exists with the given id.
+ * @param aId
+ * The id of an extension
+ * @returns true if an extension exists with the given id,
+ * false otherwise.
+ */
+ boolean has(in AString aId);
+
+ /**
+ * Gets a extIExtension object for an extension.
+ * @param aId
+ * The id of an extension
+ * @returns An extension object or null if no extension exists
+ * with the given id.
+ */
+ extIExtension get(in AString aId);
+};
+
+/**
+ * Interface representing a callback that receives an array of extIExtensions
+ */
+[scriptable, function, uuid(2571cbb5-550d-4400-8038-75df9b553f98)]
+interface extIExtensionsCallback : nsISupports
+{
+ void callback(in nsIVariant extensions);
+};
+
+/**
+ * Interface representing a simple storage system
+ */
+[scriptable, uuid(0787ac44-29b9-4889-b97f-13573aec6971)]
+interface extISessionStorage : nsISupports
+{
+ /**
+ * The events object for the storage
+ * supports: "change"
+ */
+ readonly attribute extIEvents events;
+
+ /**
+ * Determines if a storage item exists with the given name.
+ * @param aName
+ * The name of an item
+ * @returns true if an item exists with the given name,
+ * false otherwise.
+ */
+ boolean has(in AString aName);
+
+ /**
+ * Sets the value of a storage item with the given name.
+ * @param aName
+ * The name of an item
+ * @param aValue
+ * The value to assign to the item
+ */
+ void set(in AString aName, in nsIVariant aValue);
+
+ /**
+ * Gets the value of a storage item with the given name. Returns a
+ * default value if the item does not exist.
+ * @param aName
+ * The name of an item
+ * @param aDefaultValue
+ * The value to return if no item exists with the given name
+ * @returns value of the item or the given default value if no item
+ * exists with the given name.
+ */
+ nsIVariant get(in AString aName, in nsIVariant aDefaultValue);
+};
+
+[scriptable, uuid(2be87909-0817-4292-acfa-fc39be53be3f)]
+interface extIApplication : nsISupports
+{
+ /**
+ * The id of the application.
+ */
+ readonly attribute AString id;
+
+ /**
+ * The name of the application.
+ */
+ readonly attribute AString name;
+
+ /**
+ * The version number of the application.
+ */
+ readonly attribute AString version;
+
+ /**
+ * The console object for the application.
+ */
+ readonly attribute extIConsole console;
+
+ /**
+ * The extensions object for the application. Contains a list
+ * of all installed extensions.
+ */
+ void getExtensions(in extIExtensionsCallback aCallback);
+
+ /**
+ * The preferences object for the application. Defaults to an empty
+ * root branch.
+ */
+ readonly attribute extIPreferenceBranch prefs;
+
+ /**
+ * The storage object for the application.
+ */
+ readonly attribute extISessionStorage storage;
+
+ /**
+ * The events object for the application.
+ * supports: "load", "ready", "quit", "unload"
+ */
+ readonly attribute extIEvents events;
+
+ /**
+ * Quits the application (if nobody objects to quit-application-requested).
+ * @returns whether quitting will proceed
+ */
+ boolean quit();
+
+ /**
+ * Restarts the application (if nobody objects to quit-application-requested).
+ * @returns whether restarting will proceed
+ */
+ boolean restart();
+};
diff --git a/components/exthelper/moz.build b/components/exthelper/moz.build
new file mode 100644
index 000000000..784d2761e
--- /dev/null
+++ b/components/exthelper/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['extIApplication.idl']
+
+XPIDL_MODULE = 'exthelper'
+
diff --git a/components/feeds/FeedProcessor.js b/components/feeds/FeedProcessor.js
new file mode 100644
index 000000000..51b6b13c9
--- /dev/null
+++ b/components/feeds/FeedProcessor.js
@@ -0,0 +1,1793 @@
+/* -*- 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/. */
+
+function LOG(str) {
+ dump("*** " + str + "\n");
+}
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const FP_CONTRACTID = "@mozilla.org/feed-processor;1";
+const FP_CLASSID = Components.ID("{26acb1f0-28fc-43bc-867a-a46aabc85dd4}");
+const FP_CLASSNAME = "Feed Processor";
+const FR_CONTRACTID = "@mozilla.org/feed-result;1";
+const FR_CLASSID = Components.ID("{072a5c3d-30c6-4f07-b87f-9f63d51403f2}");
+const FR_CLASSNAME = "Feed Result";
+const FEED_CONTRACTID = "@mozilla.org/feed;1";
+const FEED_CLASSID = Components.ID("{5d0cfa97-69dd-4e5e-ac84-f253162e8f9a}");
+const FEED_CLASSNAME = "Feed";
+const ENTRY_CONTRACTID = "@mozilla.org/feed-entry;1";
+const ENTRY_CLASSID = Components.ID("{8e4444ff-8e99-4bdd-aa7f-fb3c1c77319f}");
+const ENTRY_CLASSNAME = "Feed Entry";
+const TEXTCONSTRUCT_CONTRACTID = "@mozilla.org/feed-textconstruct;1";
+const TEXTCONSTRUCT_CLASSID =
+ Components.ID("{b992ddcd-3899-4320-9909-924b3e72c922}");
+const TEXTCONSTRUCT_CLASSNAME = "Feed Text Construct";
+const GENERATOR_CONTRACTID = "@mozilla.org/feed-generator;1";
+const GENERATOR_CLASSID =
+ Components.ID("{414af362-9ad8-4296-898e-62247f25a20e}");
+const GENERATOR_CLASSNAME = "Feed Generator";
+const PERSON_CONTRACTID = "@mozilla.org/feed-person;1";
+const PERSON_CLASSID = Components.ID("{95c963b7-20b2-11db-92f6-001422106990}");
+const PERSON_CLASSNAME = "Feed Person";
+
+const IO_CONTRACTID = "@mozilla.org/network/io-service;1"
+const BAG_CONTRACTID = "@mozilla.org/hash-property-bag;1"
+const ARRAY_CONTRACTID = "@mozilla.org/array;1";
+const SAX_CONTRACTID = "@mozilla.org/saxparser/xmlreader;1";
+const PARSERUTILS_CONTRACTID = "@mozilla.org/parserutils;1";
+
+const gMimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+var gIoService = null;
+
+const XMLNS = "http://www.w3.org/XML/1998/namespace";
+const RSS090NS = "http://my.netscape.com/rdf/simple/0.9/";
+
+/** *** Some general utils *****/
+function strToURI(link, base) {
+ base = base || null;
+ if (!gIoService)
+ gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
+ try {
+ return gIoService.newURI(link, null, base);
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+function isArray(a) {
+ return isObject(a) && a.constructor == Array;
+}
+
+function isObject(a) {
+ return (a && typeof a == "object") || isFunction(a);
+}
+
+function isFunction(a) {
+ return typeof a == "function";
+}
+
+function isIID(a, iid) {
+ var rv = false;
+ try {
+ a.QueryInterface(iid);
+ rv = true;
+ }
+ catch (e) {
+ }
+ return rv;
+}
+
+function isIArray(a) {
+ return isIID(a, Ci.nsIArray);
+}
+
+function isIFeedContainer(a) {
+ return isIID(a, Ci.nsIFeedContainer);
+}
+
+function stripTags(someHTML) {
+ return someHTML.replace(/<[^>]+>/g, "");
+}
+
+/**
+ * Searches through an array of links and returns a JS array
+ * of matching property bags.
+ */
+const IANA_URI = "http://www.iana.org/assignments/relation/";
+function findAtomLinks(rel, links) {
+ var rvLinks = [];
+ for (var i = 0; i < links.length; ++i) {
+ var linkElement = links.queryElementAt(i, Ci.nsIPropertyBag2);
+ // atom:link MUST have @href
+ if (bagHasKey(linkElement, "href")) {
+ var relAttribute = null;
+ if (bagHasKey(linkElement, "rel"))
+ relAttribute = linkElement.getPropertyAsAString("rel")
+ if ((!relAttribute && rel == "alternate") || relAttribute == rel) {
+ rvLinks.push(linkElement);
+ continue;
+ }
+ // catch relations specified by IANA URI
+ if (relAttribute == IANA_URI + rel) {
+ rvLinks.push(linkElement);
+ }
+ }
+ }
+ return rvLinks;
+}
+
+function xmlEscape(s) {
+ s = s.replace(/&/g, "&amp;");
+ s = s.replace(/>/g, "&gt;");
+ s = s.replace(/</g, "&lt;");
+ s = s.replace(/"/g, "&quot;");
+ s = s.replace(/'/g, "&apos;");
+ return s;
+}
+
+function arrayContains(array, element) {
+ for (var i = 0; i < array.length; ++i) {
+ if (array[i] == element) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// XXX add hasKey to nsIPropertyBag
+function bagHasKey(bag, key) {
+ try {
+ bag.getProperty(key);
+ return true;
+ }
+ catch (e) {
+ return false;
+ }
+}
+
+function makePropGetter(key) {
+ return function FeedPropGetter(bag) {
+ try {
+ return value = bag.getProperty(key);
+ }
+ catch (e) {
+ }
+ return null;
+ }
+}
+
+const RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+// namespace map
+var gNamespaces = {
+ "http://webns.net/mvcb/":"admin",
+ "http://backend.userland.com/rss":"",
+ "http://blogs.law.harvard.edu/tech/rss":"",
+ "http://www.w3.org/2005/Atom":"atom",
+ "http://purl.org/atom/ns#":"atom03",
+ "http://purl.org/rss/1.0/modules/content/":"content",
+ "http://purl.org/dc/elements/1.1/":"dc",
+ "http://purl.org/dc/terms/":"dcterms",
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#":"rdf",
+ "http://purl.org/rss/1.0/":"rss1",
+ "http://my.netscape.com/rdf/simple/0.9/":"rss1",
+ "http://wellformedweb.org/CommentAPI/":"wfw",
+ "http://purl.org/rss/1.0/modules/wiki/":"wiki",
+ "http://www.w3.org/XML/1998/namespace":"xml",
+ "http://search.yahoo.com/mrss/":"media",
+ "http://search.yahoo.com/mrss":"media"
+}
+
+// We allow a very small set of namespaces in XHTML content,
+// for attributes only
+var gAllowedXHTMLNamespaces = {
+ "http://www.w3.org/XML/1998/namespace":"xml",
+ // if someone ns qualifies XHTML, we have to prefix it to avoid an
+ // attribute collision.
+ "http://www.w3.org/1999/xhtml":"xhtml"
+}
+
+function FeedResult() {}
+FeedResult.prototype = {
+ bozo: false,
+ doc: null,
+ version: null,
+ headers: null,
+ uri: null,
+ stylesheet: null,
+
+ registerExtensionPrefix: function FR_registerExtensionPrefix(ns, prefix) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ // XPCOM stuff
+ classID: FR_CLASSID,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeedResult])
+}
+
+function Feed() {
+ this.subtitle = null;
+ this.title = null;
+ this.items = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
+ this.link = null;
+ this.id = null;
+ this.generator = null;
+ this.authors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
+ this.contributors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
+ this.baseURI = null;
+ this.enclosureCount = 0;
+ this.type = Ci.nsIFeed.TYPE_FEED;
+}
+
+Feed.prototype = {
+ searchLists: {
+ title: ["title", "rss1:title", "atom03:title", "atom:title"],
+ subtitle: ["description", "dc:description", "rss1:description",
+ "atom03:tagline", "atom:subtitle"],
+ items: ["items", "atom03_entries", "entries"],
+ id: ["atom:id", "rdf:about"],
+ generator: ["generator"],
+ authors : ["authors"],
+ contributors: ["contributors"],
+ link: [["link", strToURI], ["rss1:link", strToURI]],
+ categories: ["categories", "dc:subject"],
+ rights: ["atom03:rights", "atom:rights"],
+ cloud: ["cloud"],
+ image: ["image", "rss1:image", "atom:logo"],
+ textInput: ["textInput", "rss1:textinput"],
+ skipDays: ["skipDays"],
+ skipHours: ["skipHours"],
+ updated: ["pubDate", "lastBuildDate", "atom03:modified", "dc:date",
+ "dcterms:modified", "atom:updated"]
+ },
+
+ normalize: function Feed_normalize() {
+ fieldsToObj(this, this.searchLists);
+ if (this.skipDays)
+ this.skipDays = this.skipDays.getProperty("days");
+ if (this.skipHours)
+ this.skipHours = this.skipHours.getProperty("hours");
+
+ if (this.updated)
+ this.updated = dateParse(this.updated);
+
+ // Assign Atom link if needed
+ if (bagHasKey(this.fields, "links"))
+ this._atomLinksToURI();
+
+ this._calcEnclosureCountAndFeedType();
+
+ // Resolve relative image links
+ if (this.image && bagHasKey(this.image, "url"))
+ this._resolveImageLink();
+
+ this._resetBagMembersToRawText([this.searchLists.subtitle,
+ this.searchLists.title]);
+ },
+
+ _calcEnclosureCountAndFeedType: function Feed_calcEnclosureCountAndFeedType() {
+ var entries_with_enclosures = 0;
+ var audio_count = 0;
+ var image_count = 0;
+ var video_count = 0;
+ var other_count = 0;
+
+ for (var i = 0; i < this.items.length; ++i) {
+ var entry = this.items.queryElementAt(i, Ci.nsIFeedEntry);
+ entry.QueryInterface(Ci.nsIFeedContainer);
+
+ if (entry.enclosures && entry.enclosures.length > 0) {
+ ++entries_with_enclosures;
+
+ for (var e = 0; e < entry.enclosures.length; ++e) {
+ var enc = entry.enclosures.queryElementAt(e, Ci.nsIWritablePropertyBag2);
+ if (enc.hasKey("type")) {
+ var enctype = enc.get("type");
+
+ if (/^audio/.test(enctype)) {
+ ++audio_count;
+ } else if (/^image/.test(enctype)) {
+ ++image_count;
+ } else if (/^video/.test(enctype)) {
+ ++video_count;
+ } else {
+ ++other_count;
+ }
+ } else {
+ ++other_count;
+ }
+ }
+ }
+ }
+
+ var feedtype = Ci.nsIFeed.TYPE_FEED;
+
+ // For a feed to be marked as TYPE_VIDEO, TYPE_AUDIO and TYPE_IMAGE,
+ // we enforce two things:
+ //
+ // 1. all entries must have at least one enclosure
+ // 2. all enclosures must be video for TYPE_VIDEO, audio for TYPE_AUDIO or image
+ // for TYPE_IMAGE
+ //
+ // Otherwise it's a TYPE_FEED.
+ if (entries_with_enclosures == this.items.length && other_count == 0) {
+ if (audio_count > 0 && !video_count && !image_count) {
+ feedtype = Ci.nsIFeed.TYPE_AUDIO;
+
+ } else if (image_count > 0 && !audio_count && !video_count) {
+ feedtype = Ci.nsIFeed.TYPE_IMAGE;
+
+ } else if (video_count > 0 && !audio_count && !image_count) {
+ feedtype = Ci.nsIFeed.TYPE_VIDEO;
+ }
+ }
+
+ this.type = feedtype;
+ this.enclosureCount = other_count + video_count + audio_count + image_count;
+ },
+
+ _atomLinksToURI: function Feed_linkToURI() {
+ var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray);
+ var alternates = findAtomLinks("alternate", links);
+ if (alternates.length > 0) {
+ var href = alternates[0].getPropertyAsAString("href");
+ var base;
+ if (bagHasKey(alternates[0], "xml:base"))
+ base = alternates[0].getPropertyAsAString("xml:base");
+ this.link = this._resolveURI(href, base);
+ }
+ },
+
+ _resolveImageLink: function Feed_resolveImageLink() {
+ var base;
+ if (bagHasKey(this.image, "xml:base"))
+ base = this.image.getPropertyAsAString("xml:base");
+ var url = this._resolveURI(this.image.getPropertyAsAString("url"), base);
+ if (url)
+ this.image.setPropertyAsAString("url", url.spec);
+ },
+
+ _resolveURI: function Feed_resolveURI(linkSpec, baseSpec) {
+ var uri = null;
+ try {
+ var base = baseSpec ? strToURI(baseSpec, this.baseURI) : this.baseURI;
+ uri = strToURI(linkSpec, base);
+ }
+ catch (e) {
+ LOG(e);
+ }
+
+ return uri;
+ },
+
+ // reset the bag to raw contents, not text constructs
+ _resetBagMembersToRawText: function Feed_resetBagMembers(fieldLists) {
+ for (var i=0; i<fieldLists.length; i++) {
+ for (var j=0; j<fieldLists[i].length; j++) {
+ if (bagHasKey(this.fields, fieldLists[i][j])) {
+ var textConstruct = this.fields.getProperty(fieldLists[i][j]);
+ this.fields.setPropertyAsAString(fieldLists[i][j],
+ textConstruct.text);
+ }
+ }
+ }
+ },
+
+ // XPCOM stuff
+ classID: FEED_CLASSID,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeed, Ci.nsIFeedContainer])
+}
+
+function Entry() {
+ this.summary = null;
+ this.content = null;
+ this.title = null;
+ this.fields = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag2);
+ this.link = null;
+ this.id = null;
+ this.baseURI = null;
+ this.updated = null;
+ this.published = null;
+ this.authors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
+ this.contributors = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
+}
+
+Entry.prototype = {
+ fields: null,
+ enclosures: null,
+ mediaContent: null,
+
+ searchLists: {
+ title: ["title", "rss1:title", "atom03:title", "atom:title"],
+ link: [["link", strToURI], ["rss1:link", strToURI]],
+ id: [["guid", makePropGetter("guid")], "rdf:about",
+ "atom03:id", "atom:id"],
+ authors : ["authors"],
+ contributors: ["contributors"],
+ summary: ["description", "rss1:description", "dc:description",
+ "atom03:summary", "atom:summary"],
+ content: ["content:encoded", "atom03:content", "atom:content"],
+ rights: ["atom03:rights", "atom:rights"],
+ published: ["pubDate", "atom03:issued", "dcterms:issued", "atom:published"],
+ updated: ["pubDate", "atom03:modified", "dc:date", "dcterms:modified",
+ "atom:updated"]
+ },
+
+ normalize: function Entry_normalize() {
+ fieldsToObj(this, this.searchLists);
+
+ // Assign Atom link if needed
+ if (bagHasKey(this.fields, "links"))
+ this._atomLinksToURI();
+
+ // Populate enclosures array
+ this._populateEnclosures();
+
+ // The link might be a guid w/ permalink=true
+ if (!this.link && bagHasKey(this.fields, "guid")) {
+ var guid = this.fields.getProperty("guid");
+ var isPermaLink = true;
+
+ if (bagHasKey(guid, "isPermaLink"))
+ isPermaLink = guid.getProperty("isPermaLink").toLowerCase() != "false";
+
+ if (guid && isPermaLink)
+ this.link = strToURI(guid.getProperty("guid"));
+ }
+
+ if (this.updated)
+ this.updated = dateParse(this.updated);
+ if (this.published)
+ this.published = dateParse(this.published);
+
+ this._resetBagMembersToRawText([this.searchLists.content,
+ this.searchLists.summary,
+ this.searchLists.title]);
+ },
+
+ _populateEnclosures: function Entry_populateEnclosures() {
+ if (bagHasKey(this.fields, "links"))
+ this._atomLinksToEnclosures();
+
+ // Add RSS2 enclosure to enclosures
+ if (bagHasKey(this.fields, "enclosure"))
+ this._enclosureToEnclosures();
+
+ // Add media:content to enclosures
+ if (bagHasKey(this.fields, "mediacontent"))
+ this._mediaToEnclosures("mediacontent");
+
+ // Add media:thumbnail to enclosures
+ if (bagHasKey(this.fields, "mediathumbnail"))
+ this._mediaToEnclosures("mediathumbnail");
+
+ // Add media:content in media:group to enclosures
+ if (bagHasKey(this.fields, "mediagroup"))
+ this._mediaToEnclosures("mediagroup", "mediacontent");
+ },
+
+ __enclosure_map: null,
+
+ _addToEnclosures: function Entry_addToEnclosures(new_enc) {
+ // items we add to the enclosures array get displayed in the FeedWriter and
+ // they must have non-empty urls.
+ if (!bagHasKey(new_enc, "url") || new_enc.getPropertyAsAString("url") == "")
+ return;
+
+ if (this.__enclosure_map == null)
+ this.__enclosure_map = {};
+
+ var previous_enc = this.__enclosure_map[new_enc.getPropertyAsAString("url")];
+
+ if (previous_enc != undefined) {
+ previous_enc.QueryInterface(Ci.nsIWritablePropertyBag2);
+
+ if (!bagHasKey(previous_enc, "type") && bagHasKey(new_enc, "type")) {
+ previous_enc.setPropertyAsAString("type", new_enc.getPropertyAsAString("type"));
+ try {
+ let handlerInfoWrapper = gMimeService.getFromTypeAndExtension(new_enc.getPropertyAsAString("type"), null);
+ if (handlerInfoWrapper && handlerInfoWrapper.description) {
+ previous_enc.setPropertyAsAString("typeDesc", handlerInfoWrapper.description);
+ }
+ } catch (ext) {}
+ }
+
+ if (!bagHasKey(previous_enc, "length") && bagHasKey(new_enc, "length"))
+ previous_enc.setPropertyAsAString("length", new_enc.getPropertyAsAString("length"));
+
+ return;
+ }
+
+ if (this.enclosures == null) {
+ this.enclosures = Cc[ARRAY_CONTRACTID].createInstance(Ci.nsIMutableArray);
+ this.enclosures.QueryInterface(Ci.nsIMutableArray);
+ }
+
+ this.enclosures.appendElement(new_enc, false);
+ this.__enclosure_map[new_enc.getPropertyAsAString("url")] = new_enc;
+ },
+
+ _atomLinksToEnclosures: function Entry_linkToEnclosure() {
+ var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray);
+ var enc_links = findAtomLinks("enclosure", links);
+ if (enc_links.length == 0)
+ return;
+
+ for (var i = 0; i < enc_links.length; ++i) {
+ var link = enc_links[i];
+
+ // an enclosure must have an href
+ if (!(link.getProperty("href")))
+ return;
+
+ var enc = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
+
+ // copy Atom bits over to equivalent enclosure bits
+ enc.setPropertyAsAString("url", link.getPropertyAsAString("href"));
+ if (bagHasKey(link, "type"))
+ enc.setPropertyAsAString("type", link.getPropertyAsAString("type"));
+ if (bagHasKey(link, "length"))
+ enc.setPropertyAsAString("length", link.getPropertyAsAString("length"));
+
+ this._addToEnclosures(enc);
+ }
+ },
+
+ _enclosureToEnclosures: function Entry_enclosureToEnclosures() {
+ var enc = this.fields.getPropertyAsInterface("enclosure", Ci.nsIPropertyBag2);
+
+ if (!(enc.getProperty("url")))
+ return;
+
+ this._addToEnclosures(enc);
+ },
+
+ _mediaToEnclosures: function Entry_mediaToEnclosures(mediaType, contentType) {
+ var content;
+
+ // If a contentType is specified, the mediaType is a simple propertybag,
+ // and the contentType is an array inside it.
+ if (contentType) {
+ var group = this.fields.getPropertyAsInterface(mediaType, Ci.nsIPropertyBag2);
+ content = group.getPropertyAsInterface(contentType, Ci.nsIArray);
+ } else {
+ content = this.fields.getPropertyAsInterface(mediaType, Ci.nsIArray);
+ }
+
+ for (var i = 0; i < content.length; ++i) {
+ var contentElement = content.queryElementAt(i, Ci.nsIWritablePropertyBag2);
+
+ // media:content don't require url, but if it's not there, we should
+ // skip it.
+ if (!bagHasKey(contentElement, "url"))
+ continue;
+
+ var enc = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
+
+ // copy media:content bits over to equivalent enclosure bits
+ enc.setPropertyAsAString("url", contentElement.getPropertyAsAString("url"));
+ if (bagHasKey(contentElement, "type")) {
+ enc.setPropertyAsAString("type", contentElement.getPropertyAsAString("type"));
+ } else if (mediaType == "mediathumbnail") {
+ // thumbnails won't have a type, but default to image types
+ enc.setPropertyAsAString("type", "image/*");
+ enc.setPropertyAsBool("thumbnail", true);
+ }
+
+ if (bagHasKey(contentElement, "fileSize")) {
+ enc.setPropertyAsAString("length", contentElement.getPropertyAsAString("fileSize"));
+ }
+
+ this._addToEnclosures(enc);
+ }
+ },
+
+ // XPCOM stuff
+ classID: ENTRY_CLASSID,
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsIFeedEntry, Ci.nsIFeedContainer]
+ )
+}
+
+Entry.prototype._atomLinksToURI = Feed.prototype._atomLinksToURI;
+Entry.prototype._resolveURI = Feed.prototype._resolveURI;
+Entry.prototype._resetBagMembersToRawText =
+ Feed.prototype._resetBagMembersToRawText;
+
+// TextConstruct represents and element that could contain (X)HTML
+function TextConstruct() {
+ this.lang = null;
+ this.base = null;
+ this.type = "text";
+ this.text = null;
+ this.parserUtils = Cc[PARSERUTILS_CONTRACTID].getService(Ci.nsIParserUtils);
+}
+
+TextConstruct.prototype = {
+ plainText: function TC_plainText() {
+ if (this.type != "text") {
+ return this.parserUtils.convertToPlainText(stripTags(this.text),
+ Ci.nsIDocumentEncoder.OutputSelectionOnly |
+ Ci.nsIDocumentEncoder.OutputAbsoluteLinks,
+ 0);
+ }
+ return this.text;
+ },
+
+ createDocumentFragment: function TC_createDocumentFragment(element) {
+ if (this.type == "text") {
+ var doc = element.ownerDocument;
+ var docFragment = doc.createDocumentFragment();
+ var node = doc.createTextNode(this.text);
+ docFragment.appendChild(node);
+ return docFragment;
+ }
+ var isXML;
+ if (this.type == "xhtml")
+ isXML = true
+ else if (this.type == "html")
+ isXML = false;
+ else
+ return null;
+
+ let flags = Ci.nsIParserUtils.SanitizerDropForms;
+ return this.parserUtils.parseFragment(this.text, flags, isXML,
+ this.base, element);
+ },
+
+ // XPCOM stuff
+ classID: TEXTCONSTRUCT_CLASSID,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFeedTextConstruct])
+}
+
+// Generator represents the software that produced the feed
+function Generator() {
+ this.lang = null;
+ this.agent = null;
+ this.version = null;
+ this.uri = null;
+
+ // nsIFeedElementBase
+ this._attributes = null;
+ this.baseURI = null;
+}
+
+Generator.prototype = {
+
+ get attributes() {
+ return this._attributes;
+ },
+
+ set attributes(value) {
+ this._attributes = value;
+ this.version = this._attributes.getValueFromName("", "version");
+ var uriAttribute = this._attributes.getValueFromName("", "uri") ||
+ this._attributes.getValueFromName("", "url");
+ this.uri = strToURI(uriAttribute, this.baseURI);
+
+ // RSS1
+ uriAttribute = this._attributes.getValueFromName(RDF_NS, "resource");
+ if (uriAttribute) {
+ this.agent = uriAttribute;
+ this.uri = strToURI(uriAttribute, this.baseURI);
+ }
+ },
+
+ // XPCOM stuff
+ classID: GENERATOR_CLASSID,
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsIFeedGenerator, Ci.nsIFeedElementBase]
+ )
+}
+
+function Person() {
+ this.name = null;
+ this.uri = null;
+ this.email = null;
+
+ // nsIFeedElementBase
+ this.attributes = null;
+ this.baseURI = null;
+}
+
+Person.prototype = {
+ // XPCOM stuff
+ classID: PERSON_CLASSID,
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsIFeedPerson, Ci.nsIFeedElementBase]
+ )
+}
+
+/**
+ * Map a list of fields into properties on a container.
+ *
+ * @param container An nsIFeedContainer
+ * @param fields A list of fields to search for. List members can
+ * be a list, in which case the second member is
+ * transformation function (like parseInt).
+ */
+function fieldsToObj(container, fields) {
+ var props, prop, field, searchList;
+ for (var key in fields) {
+ searchList = fields[key];
+ for (var i=0; i < searchList.length; ++i) {
+ props = searchList[i];
+ prop = null;
+ field = isArray(props) ? props[0] : props;
+ try {
+ prop = container.fields.getProperty(field);
+ }
+ catch (e) {
+ }
+ if (prop) {
+ prop = isArray(props) ? props[1](prop) : prop;
+ container[key] = prop;
+ }
+ }
+ }
+}
+
+/**
+ * Lower cases an element's localName property
+ * @param element A DOM element.
+ *
+ * @returns The lower case localName property of the specified element
+ */
+function LC(element) {
+ return element.localName.toLowerCase();
+}
+
+// TODO move these post-processor functions
+// create a generator element
+function atomGenerator(s, generator) {
+ generator.QueryInterface(Ci.nsIFeedGenerator);
+ generator.agent = s.trim();
+ return generator;
+}
+
+// post-process atom:logo to create an RSS2-like structure
+function atomLogo(s, logo) {
+ logo.setPropertyAsAString("url", s.trim());
+}
+
+// post-process an RSS category, map it to the Atom fields.
+function rssCatTerm(s, cat) {
+ // add slash handling?
+ cat.setPropertyAsAString("term", s.trim());
+ return cat;
+}
+
+// post-process a GUID
+function rssGuid(s, guid) {
+ guid.setPropertyAsAString("guid", s.trim());
+ return guid;
+}
+
+// post-process an RSS author element
+//
+// It can contain a field like this:
+//
+// <author>lawyer@boyer.net (Lawyer Boyer)</author>
+//
+// or, delightfully, a field like this:
+//
+// <dc:creator>Simon St.Laurent (mailto:simonstl@simonstl.com)</dc:creator>
+//
+// We want to split this up and assign it to corresponding Atom
+// fields.
+//
+function rssAuthor(s, author) {
+ author.QueryInterface(Ci.nsIFeedPerson);
+ // check for RSS2 string format
+ var chars = s.trim();
+ var matches = chars.match(/(.*)\((.*)\)/);
+ var emailCheck =
+ /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
+ if (matches) {
+ var match1 = matches[1].trim();
+ var match2 = matches[2].trim();
+ if (match2.indexOf("mailto:") == 0)
+ match2 = match2.substring(7);
+ if (emailCheck.test(match1)) {
+ author.email = match1;
+ author.name = match2;
+ }
+ else if (emailCheck.test(match2)) {
+ author.email = match2;
+ author.name = match1;
+ }
+ else {
+ // put it back together
+ author.name = match1 + " (" + match2 + ")";
+ }
+ }
+ else {
+ author.name = chars;
+ if (chars.indexOf('@'))
+ author.email = chars;
+ }
+ return author;
+}
+
+//
+// skipHours and skipDays map to arrays, so we need to change the
+// string to an nsISupports in order to stick it in there.
+//
+function rssArrayElement(s) {
+ var str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ str.data = s;
+ str.QueryInterface(Ci.nsISupportsString);
+ return str;
+}
+
+/**
+ * Tries parsing a string through the JavaScript Date object.
+ * @param aDateString
+ * A string that is supposedly an RFC822 or RFC3339 date.
+ * @return A Date.toUTCString, or null if the string can't be parsed.
+ */
+function dateParse(aDateString) {
+ let dateString = aDateString.trim();
+ // Without bug 682781 fixed, JS won't parse an RFC822 date with a Z for the
+ // timezone, so convert to -00:00 which works for any date format.
+ dateString = dateString.replace(/z$/i, "-00:00");
+ let date = new Date(dateString);
+ if (!isNaN(date)) {
+ return date.toUTCString();
+ }
+ return null;
+}
+
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+
+// The XHTMLHandler handles inline XHTML found in things like atom:summary
+function XHTMLHandler(processor, isAtom) {
+ this._buf = "";
+ this._processor = processor;
+ this._depth = 0;
+ this._isAtom = isAtom;
+ // a stack of lists tracking in-scope namespaces
+ this._inScopeNS = [];
+}
+
+// The fidelity can be improved here, to allow handling of stuff like
+// SVG and MathML. XXX
+XHTMLHandler.prototype = {
+
+ // look back up at the declared namespaces
+ // we always use the same prefixes for our safe stuff
+ _isInScope: function XH__isInScope(ns) {
+ for (var i in this._inScopeNS) {
+ for (var uri in this._inScopeNS[i]) {
+ if (this._inScopeNS[i][uri] == ns)
+ return true;
+ }
+ }
+ return false;
+ },
+
+ startDocument: function XH_startDocument() {
+ },
+ endDocument: function XH_endDocument() {
+ },
+ startElement: function XH_startElement(namespace, localName, qName, attributes) {
+ ++this._depth;
+ this._inScopeNS.push([]);
+
+ // RFC4287 requires XHTML to be wrapped in a div that is *not* part of
+ // the content. This prevents people from screwing up namespaces, but
+ // we need to skip it here.
+ if (this._isAtom && this._depth == 1 && localName == "div")
+ return;
+
+ // If it's an XHTML element, record it. Otherwise, it's ignored.
+ if (namespace == XHTML_NS) {
+ this._buf += "<" + localName;
+ var uri;
+ for (var i=0; i < attributes.length; ++i) {
+ uri = attributes.getURI(i);
+ // XHTML attributes aren't in a namespace
+ if (uri == "") {
+ this._buf += (" " + attributes.getLocalName(i) + "='" +
+ xmlEscape(attributes.getValue(i)) + "'");
+ } else {
+ // write a small set of allowed attribute namespaces
+ var prefix = gAllowedXHTMLNamespaces[uri];
+ if (prefix != null) {
+ // The attribute value we'll attempt to write
+ var attributeValue = xmlEscape(attributes.getValue(i));
+
+ // it's an allowed attribute NS.
+ // write the attribute
+ this._buf += (" " + prefix + ":" +
+ attributes.getLocalName(i) +
+ "='" + attributeValue + "'");
+
+ // write an xmlns declaration if necessary
+ if (prefix != "xml" && !this._isInScope(uri)) {
+ this._inScopeNS[this._inScopeNS.length - 1].push(uri);
+ this._buf += " xmlns:" + prefix + "='" + uri + "'";
+ }
+ }
+ }
+ }
+ this._buf += ">";
+ }
+ },
+ endElement: function XH_endElement(uri, localName, qName) {
+ --this._depth;
+ this._inScopeNS.pop();
+
+ // We need to skip outer divs in Atom. See comment in startElement.
+ if (this._isAtom && this._depth == 0 && localName == "div")
+ return;
+
+ // When we peek too far, go back to the main processor
+ if (this._depth < 0) {
+ this._processor.returnFromXHTMLHandler(this._buf.trim(),
+ uri, localName, qName);
+ return;
+ }
+ // If it's an XHTML element, record it. Otherwise, it's ignored.
+ if (uri == XHTML_NS) {
+ this._buf += "</" + localName + ">";
+ }
+ },
+ characters: function XH_characters(data) {
+ this._buf += xmlEscape(data);
+ },
+ startPrefixMapping: function XH_startPrefixMapping(prefix, uri) {
+ },
+ endPrefixMapping: function FP_endPrefixMapping(prefix) {
+ },
+ processingInstruction: function XH_processingInstruction() {
+ },
+}
+
+/**
+ * The ExtensionHandler deals with elements we haven't explicitly
+ * added to our transition table in the FeedProcessor.
+ */
+function ExtensionHandler(processor) {
+ this._buf = "";
+ this._depth = 0;
+ this._hasChildElements = false;
+
+ // The FeedProcessor
+ this._processor = processor;
+
+ // Fields of the outermost extension element.
+ this._localName = null;
+ this._uri = null;
+ this._qName = null;
+ this._attrs = null;
+}
+
+ExtensionHandler.prototype = {
+ startDocument: function EH_startDocument() {
+ },
+ endDocument: function EH_endDocument() {
+ },
+ startElement: function EH_startElement(uri, localName, qName, attrs) {
+ ++this._depth;
+
+ if (this._depth == 1) {
+ this._uri = uri;
+ this._localName = localName;
+ this._qName = qName;
+ this._attrs = attrs;
+ }
+
+ // if we descend into another element, we won't send text
+ this._hasChildElements = (this._depth > 1);
+
+ },
+ endElement: function EH_endElement(uri, localName, qName) {
+ --this._depth;
+ if (this._depth == 0) {
+ var text = this._hasChildElements ? null : this._buf.trim();
+ this._processor.returnFromExtHandler(this._uri, this._localName,
+ text, this._attrs);
+ }
+ },
+ characters: function EH_characters(data) {
+ if (!this._hasChildElements)
+ this._buf += data;
+ },
+ startPrefixMapping: function EH_startPrefixMapping() {
+ },
+ endPrefixMapping: function EH_endPrefixMapping() {
+ },
+ processingInstruction: function EH_processingInstruction() {
+ },
+};
+
+
+/**
+ * ElementInfo is a simple container object that describes
+ * some characteristics of a feed element. For example, it
+ * says whether an element can be expected to appear more
+ * than once inside a given entry or feed.
+ */
+function ElementInfo(fieldName, containerClass, closeFunc, isArray) {
+ this.fieldName = fieldName;
+ this.containerClass = containerClass;
+ this.closeFunc = closeFunc;
+ this.isArray = isArray;
+ this.isWrapper = false;
+}
+
+/**
+ * FeedElementInfo represents a feed element, usually the root.
+ */
+function FeedElementInfo(fieldName, feedVersion) {
+ this.isWrapper = false;
+ this.fieldName = fieldName;
+ this.feedVersion = feedVersion;
+}
+
+/**
+ * Some feed formats include vestigial wrapper elements that we don't
+ * want to include in our object model, but we do need to keep track
+ * of during parsing.
+ */
+function WrapperElementInfo(fieldName) {
+ this.isWrapper = true;
+ this.fieldName = fieldName;
+}
+
+/** *** The Processor *****/
+function FeedProcessor() {
+ this._reader = Cc[SAX_CONTRACTID].createInstance(Ci.nsISAXXMLReader);
+ this._buf = "";
+ this._feed = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
+ this._handlerStack = [];
+ this._xmlBaseStack = []; // sparse array keyed to nesting depth
+ this._depth = 0;
+ this._state = "START";
+ this._result = null;
+ this._extensionHandler = null;
+ this._xhtmlHandler = null;
+ this._haveSentResult = false;
+
+ // The nsIFeedResultListener waiting for the parse results
+ this.listener = null;
+
+ // These elements can contain (X)HTML or plain text.
+ // We keep a table here that contains their default treatment
+ this._textConstructs = {"atom:title":"text",
+ "atom:summary":"text",
+ "atom:rights":"text",
+ "atom:content":"text",
+ "atom:subtitle":"text",
+ "description":"html",
+ "rss1:description":"html",
+ "dc:description":"html",
+ "content:encoded":"html",
+ "title":"text",
+ "rss1:title":"text",
+ "atom03:title":"text",
+ "atom03:tagline":"text",
+ "atom03:summary":"text",
+ "atom03:content":"text"};
+ this._stack = [];
+
+ this._trans = {
+ "START": {
+ // If we hit a root RSS element, treat as RSS2.
+ "rss": new FeedElementInfo("RSS2", "rss2"),
+
+ // If we hit an RDF element, if could be RSS1, but we can't
+ // verify that until we hit a rss1:channel element.
+ "rdf:RDF": new WrapperElementInfo("RDF"),
+
+ // If we hit a Atom 1.0 element, treat as Atom 1.0.
+ "atom:feed": new FeedElementInfo("Atom", "atom"),
+
+ // Treat as Atom 0.3
+ "atom03:feed": new FeedElementInfo("Atom03", "atom03"),
+ },
+
+ /** ******* RSS2 **********/
+ "IN_RSS2": {
+ "channel": new WrapperElementInfo("channel")
+ },
+
+ "IN_CHANNEL": {
+ "item": new ElementInfo("items", Cc[ENTRY_CONTRACTID], null, true),
+ "managingEditor": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ "category": new ElementInfo("categories", null, rssCatTerm, true),
+ "cloud": new ElementInfo("cloud", null, null, false),
+ "image": new ElementInfo("image", null, null, false),
+ "textInput": new ElementInfo("textInput", null, null, false),
+ "skipDays": new ElementInfo("skipDays", null, null, false),
+ "skipHours": new ElementInfo("skipHours", null, null, false),
+ "generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
+ atomGenerator, false),
+ },
+
+ "IN_ITEMS": {
+ "author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ "category": new ElementInfo("categories", null, rssCatTerm, true),
+ "enclosure": new ElementInfo("enclosure", null, null, false),
+ "media:content": new ElementInfo("mediacontent", null, null, true),
+ "media:group": new ElementInfo("mediagroup", null, null, false),
+ "media:thumbnail": new ElementInfo("mediathumbnail", null, null, true),
+ "guid": new ElementInfo("guid", null, rssGuid, false)
+ },
+
+ "IN_SKIPDAYS": {
+ "day": new ElementInfo("days", null, rssArrayElement, true)
+ },
+
+ "IN_SKIPHOURS":{
+ "hour": new ElementInfo("hours", null, rssArrayElement, true)
+ },
+
+ "IN_MEDIAGROUP": {
+ "media:content": new ElementInfo("mediacontent", null, null, true),
+ "media:thumbnail": new ElementInfo("mediathumbnail", null, null, true)
+ },
+
+ /** ******* RSS1 **********/
+ "IN_RDF": {
+ // If we hit a rss1:channel, we can verify that we have RSS1
+ "rss1:channel": new FeedElementInfo("rdf_channel", "rss1"),
+ "rss1:image": new ElementInfo("image", null, null, false),
+ "rss1:textinput": new ElementInfo("textInput", null, null, false),
+ "rss1:item": new ElementInfo("items", Cc[ENTRY_CONTRACTID], null, true),
+ },
+
+ "IN_RDF_CHANNEL": {
+ "admin:generatorAgent": new ElementInfo("generator",
+ Cc[GENERATOR_CONTRACTID],
+ null, false),
+ "dc:creator": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ "dc:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ "dc:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
+ rssAuthor, true),
+ },
+
+ /** ******* ATOM 1.0 **********/
+ "IN_ATOM": {
+ "atom:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ null, true),
+ "atom:generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
+ atomGenerator, false),
+ "atom:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
+ null, true),
+ "atom:link": new ElementInfo("links", null, null, true),
+ "atom:logo": new ElementInfo("atom:logo", null, atomLogo, false),
+ "atom:entry": new ElementInfo("entries", Cc[ENTRY_CONTRACTID],
+ null, true)
+ },
+
+ "IN_ENTRIES": {
+ "atom:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ null, true),
+ "atom:contributor": new ElementInfo("contributors", Cc[PERSON_CONTRACTID],
+ null, true),
+ "atom:link": new ElementInfo("links", null, null, true),
+ },
+
+ /** ******* ATOM 0.3 **********/
+ "IN_ATOM03": {
+ "atom03:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ null, true),
+ "atom03:contributor": new ElementInfo("contributors",
+ Cc[PERSON_CONTRACTID],
+ null, true),
+ "atom03:link": new ElementInfo("links", null, null, true),
+ "atom03:entry": new ElementInfo("atom03_entries", Cc[ENTRY_CONTRACTID],
+ null, true),
+ "atom03:generator": new ElementInfo("generator", Cc[GENERATOR_CONTRACTID],
+ atomGenerator, false),
+ },
+
+ "IN_ATOM03_ENTRIES": {
+ "atom03:author": new ElementInfo("authors", Cc[PERSON_CONTRACTID],
+ null, true),
+ "atom03:contributor": new ElementInfo("contributors",
+ Cc[PERSON_CONTRACTID],
+ null, true),
+ "atom03:link": new ElementInfo("links", null, null, true),
+ "atom03:entry": new ElementInfo("atom03_entries", Cc[ENTRY_CONTRACTID],
+ null, true)
+ }
+ }
+}
+
+// See startElement for a long description of how feeds are processed.
+FeedProcessor.prototype = {
+
+ // Set ourselves as the SAX handler, and set the base URI
+ _init: function FP_init(uri) {
+ this._reader.contentHandler = this;
+ this._reader.errorHandler = this;
+ this._result = Cc[FR_CONTRACTID].createInstance(Ci.nsIFeedResult);
+ if (uri) {
+ this._result.uri = uri;
+ this._reader.baseURI = uri;
+ this._xmlBaseStack[0] = uri;
+ }
+ },
+
+ // This function is called once we figure out what type of feed
+ // we're dealing with. Some feed types require digging a bit further
+ // than the root.
+ _docVerified: function FP_docVerified(version) {
+ this._result.doc = Cc[FEED_CONTRACTID].createInstance(Ci.nsIFeed);
+ this._result.doc.baseURI =
+ this._xmlBaseStack[this._xmlBaseStack.length - 1];
+ this._result.doc.fields = this._feed;
+ this._result.version = version;
+ },
+
+ // When we're done with the feed, let the listener know what
+ // happened.
+ _sendResult: function FP_sendResult() {
+ this._haveSentResult = true;
+ try {
+ // Can be null when a non-feed is fed to us
+ if (this._result.doc)
+ this._result.doc.normalize();
+ }
+ catch (e) {
+ LOG("FIXME: " + e);
+ }
+
+ try {
+ if (this.listener != null)
+ this.listener.handleResult(this._result);
+ }
+ finally {
+ this._result = null;
+ }
+ },
+
+ // Parsing functions
+ parseFromStream: function FP_parseFromStream(stream, uri) {
+ this._init(uri);
+ this._reader.parseFromStream(stream, null, stream.available(),
+ "application/xml");
+ this._reader = null;
+ },
+
+ parseFromString: function FP_parseFromString(inputString, uri) {
+ this._init(uri);
+ this._reader.parseFromString(inputString, "application/xml");
+ this._reader = null;
+ },
+
+ parseAsync: function FP_parseAsync(requestObserver, uri) {
+ this._init(uri);
+ this._reader.parseAsync(requestObserver);
+ },
+
+ // nsIStreamListener
+
+ // The XMLReader will throw sensible exceptions if these get called
+ // out of order.
+ onStartRequest: function FP_onStartRequest(request, context) {
+ // this will throw if the request is not a channel, but so will nsParser.
+ var channel = request.QueryInterface(Ci.nsIChannel);
+ channel.contentType = "application/vnd.mozilla.maybe.feed";
+ this._reader.onStartRequest(request, context);
+ },
+
+ onStopRequest: function FP_onStopRequest(request, context, statusCode) {
+ try {
+ this._reader.onStopRequest(request, context, statusCode);
+ }
+ finally {
+ this._reader = null;
+ }
+ },
+
+ onDataAvailable:
+ function FP_onDataAvailable(request, context, inputStream, offset, count) {
+ this._reader.onDataAvailable(request, context, inputStream, offset, count);
+ },
+
+ // nsISAXErrorHandler
+
+ // We only care about fatal errors. When this happens, we may have
+ // parsed through the feed metadata and some number of entries. The
+ // listener can still show some of that data if it wants, and we'll
+ // set the bozo bit to indicate we were unable to parse all the way
+ // through.
+ fatalError: function FP_reportError() {
+ this._result.bozo = true;
+ // XXX need to QI to FeedProgressListener
+ if (!this._haveSentResult)
+ this._sendResult();
+ },
+
+ // nsISAXContentHandler
+
+ startDocument: function FP_startDocument() {
+ // LOG("----------");
+ },
+
+ endDocument: function FP_endDocument() {
+ if (!this._haveSentResult)
+ this._sendResult();
+ },
+
+ // The transitions defined above identify elements that contain more
+ // than just text. For example RSS items contain many fields, and so
+ // do Atom authors. The only commonly used elements that contain
+ // mixed content are Atom Text Constructs of type="xhtml", which we
+ // delegate to another handler for cleaning. That leaves a couple
+ // different types of elements to deal with: those that should occur
+ // only once, such as title elements, and those that can occur
+ // multiple times, such as the RSS category element and the Atom
+ // link element. Most of the RSS1/DC elements can occur multiple
+ // times in theory, but in practice, the only ones that do have
+ // analogues in Atom.
+ //
+ // Some elements are also groups of attributes or sub-elements,
+ // while others are simple text fields. For the most part, we don't
+ // have to pay explicit attention to the simple text elements,
+ // unless we want to post-process the resulting string to transform
+ // it into some richer object like a Date or URI.
+ //
+ // Elements that have more sophisticated content models still end up
+ // being dictionaries, whether they are based on attributes like RSS
+ // cloud, sub-elements like Atom author, or even items and
+ // entries. These elements are treated as "containers". It's
+ // theoretically possible for a container to have an attribute with
+ // the same universal name as a sub-element, but none of the feed
+ // formats allow this by default, and I don't of any extension that
+ // works this way.
+ //
+ startElement: function FP_startElement(uri, localName, qName, attributes) {
+ this._buf = "";
+ ++this._depth;
+ var elementInfo;
+
+ // LOG("<" + localName + ">");
+
+ // Check for xml:base
+ var base = attributes.getValueFromName(XMLNS, "base");
+ if (base) {
+ this._xmlBaseStack[this._depth] =
+ strToURI(base, this._xmlBaseStack[this._xmlBaseStack.length - 1]);
+ }
+
+ // To identify the element we're dealing with, we look up the
+ // namespace URI in our gNamespaces dictionary, which will give us
+ // a "canonical" prefix for a namespace URI. For example, this
+ // allows Dublin Core "creator" elements to be consistently mapped
+ // to "dc:creator", for easy field access by consumer code. This
+ // strategy also happens to shorten up our state table.
+ var key = this._prefixForNS(uri) + localName;
+
+ // Check to see if we need to hand this off to our XHTML handler.
+ // The elements we're dealing with will look like this:
+ //
+ // <title type="xhtml">
+ // <div xmlns="http://www.w3.org/1999/xhtml">
+ // A title with <b>bold</b> and <i>italics</i>.
+ // </div>
+ // </title>
+ //
+ // When it returns in returnFromXHTMLHandler, the handler should
+ // give us back a string like this:
+ //
+ // "A title with <b>bold</b> and <i>italics</i>."
+ //
+ // The Atom spec explicitly says the div is not part of the content,
+ // and explicitly allows whitespace collapsing.
+ //
+ if ((this._result.version == "atom" || this._result.version == "atom03") &&
+ this._textConstructs[key] != null) {
+ var type = attributes.getValueFromName("", "type");
+ if (type != null && type.indexOf("xhtml") >= 0) {
+ this._xhtmlHandler =
+ new XHTMLHandler(this, (this._result.version == "atom"));
+ this._reader.contentHandler = this._xhtmlHandler;
+ return;
+ }
+ }
+
+ // Check our current state, and see if that state has a defined
+ // transition. For example, this._trans["atom:entry"]["atom:author"]
+ // will have one, and it tells us to add an item to our authors array.
+ if (this._trans[this._state] && this._trans[this._state][key]) {
+ elementInfo = this._trans[this._state][key];
+ }
+ else {
+ // If we don't have a transition, hand off to extension handler
+ this._extensionHandler = new ExtensionHandler(this);
+ this._reader.contentHandler = this._extensionHandler;
+ this._extensionHandler.startElement(uri, localName, qName, attributes);
+ return;
+ }
+
+ // This distinguishes wrappers like 'channel' from elements
+ // we'd actually like to do something with (which will test true).
+ this._handlerStack[this._depth] = elementInfo;
+ if (elementInfo.isWrapper) {
+ this._state = "IN_" + elementInfo.fieldName.toUpperCase();
+ this._stack.push([this._feed, this._state]);
+ }
+ else if (elementInfo.feedVersion) {
+ this._state = "IN_" + elementInfo.fieldName.toUpperCase();
+
+ // Check for the older RSS2 variants
+ if (elementInfo.feedVersion == "rss2")
+ elementInfo.feedVersion = this._findRSSVersion(attributes);
+ else if (uri == RSS090NS)
+ elementInfo.feedVersion = "rss090";
+
+ this._docVerified(elementInfo.feedVersion);
+ this._stack.push([this._feed, this._state]);
+ this._mapAttributes(this._feed, attributes);
+ }
+ else {
+ this._state = this._processComplexElement(elementInfo, attributes);
+ }
+ },
+
+ // In the endElement handler, we decrement the stack and look
+ // for cleanup/transition functions to execute. The second part
+ // of the state transition works as above in startElement, but
+ // the state we're looking for is prefixed with an underscore
+ // to distinguish endElement events from startElement events.
+ endElement: function FP_endElement(uri, localName, qName) {
+ var elementInfo = this._handlerStack[this._depth];
+ // LOG("</" + localName + ">");
+ if (elementInfo && !elementInfo.isWrapper)
+ this._closeComplexElement(elementInfo);
+
+ // cut down xml:base context
+ if (this._xmlBaseStack.length == this._depth + 1)
+ this._xmlBaseStack = this._xmlBaseStack.slice(0, this._depth);
+
+ // our new state is whatever is at the top of the stack now
+ if (this._stack.length > 0)
+ this._state = this._stack[this._stack.length - 1][1];
+ this._handlerStack = this._handlerStack.slice(0, this._depth);
+ --this._depth;
+ },
+
+ // Buffer up character data. The buffer is cleared with every
+ // opening element.
+ characters: function FP_characters(data) {
+ this._buf += data;
+ },
+ // TODO: It would be nice to check new prefixes here, and if they
+ // don't conflict with the ones we've defined, throw them in a
+ // dictionary to check.
+ startPrefixMapping: function FP_startPrefixMapping(prefix, uri) {
+ },
+
+ endPrefixMapping: function FP_endPrefixMapping(prefix) {
+ },
+
+ processingInstruction: function FP_processingInstruction(target, data) {
+ if (target == "xml-stylesheet") {
+ var hrefAttribute = data.match(/href=[\"\'](.*?)[\"\']/);
+ if (hrefAttribute && hrefAttribute.length == 2)
+ this._result.stylesheet = strToURI(hrefAttribute[1], this._result.uri);
+ }
+ },
+
+ // end of nsISAXContentHandler
+
+ // Handle our more complicated elements--those that contain
+ // attributes and child elements.
+ _processComplexElement:
+ function FP__processComplexElement(elementInfo, attributes) {
+ var obj;
+
+ // If the container is an entry/item, it'll need to have its
+ // more esoteric properties put in the 'fields' property bag.
+ if (elementInfo.containerClass == Cc[ENTRY_CONTRACTID]) {
+ obj = elementInfo.containerClass.createInstance(Ci.nsIFeedEntry);
+ obj.baseURI = this._xmlBaseStack[this._xmlBaseStack.length - 1];
+ this._mapAttributes(obj.fields, attributes);
+ }
+ else if (elementInfo.containerClass) {
+ obj = elementInfo.containerClass.createInstance(Ci.nsIFeedElementBase);
+ obj.baseURI = this._xmlBaseStack[this._xmlBaseStack.length - 1];
+ obj.attributes = attributes; // just set the SAX attributes
+ }
+ else {
+ obj = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2);
+ this._mapAttributes(obj, attributes);
+ }
+
+ // We should have a container/propertyBag that's had its
+ // attributes processed. Now we need to attach it to its
+ // container.
+ var newProp;
+
+ // First we'll see what's on top of the stack.
+ var container = this._stack[this._stack.length - 1][0];
+
+ // Check to see if it has the property
+ var prop;
+ try {
+ prop = container.getProperty(elementInfo.fieldName);
+ }
+ catch (e) {
+ }
+
+ if (elementInfo.isArray) {
+ if (!prop) {
+ container.setPropertyAsInterface(elementInfo.fieldName,
+ Cc[ARRAY_CONTRACTID].
+ createInstance(Ci.nsIMutableArray));
+ }
+
+ newProp = container.getProperty(elementInfo.fieldName);
+ // XXX This QI should not be necessary, but XPConnect seems to fly
+ // off the handle in the browser, and loses track of the interface
+ // on large files. Bug 335638.
+ newProp.QueryInterface(Ci.nsIMutableArray);
+ newProp.appendElement(obj, false);
+
+ // If new object is an nsIFeedContainer, we want to deal with
+ // its member nsIPropertyBag instead.
+ if (isIFeedContainer(obj))
+ newProp = obj.fields;
+
+ }
+ else {
+ // If it doesn't, set it.
+ if (!prop) {
+ container.setPropertyAsInterface(elementInfo.fieldName, obj);
+ }
+ newProp = container.getProperty(elementInfo.fieldName);
+ }
+
+ // make our new state name, and push the property onto the stack
+ var newState = "IN_" + elementInfo.fieldName.toUpperCase();
+ this._stack.push([newProp, newState, obj]);
+ return newState;
+ },
+
+ // Sometimes we need reconcile the element content with the object
+ // model for a given feed. We use helper functions to do the
+ // munging, but we need to identify array types here, so the munging
+ // happens only to the last element of an array.
+ _closeComplexElement: function FP__closeComplexElement(elementInfo) {
+ var stateTuple = this._stack.pop();
+ var container = stateTuple[0];
+ var containerParent = stateTuple[2];
+ var element = null;
+ var isArray = isIArray(container);
+
+ // If it's an array and we have to post-process,
+ // grab the last element
+ if (isArray)
+ element = container.queryElementAt(container.length - 1, Ci.nsISupports);
+ else
+ element = container;
+
+ // Run the post-processing function if there is one.
+ if (elementInfo.closeFunc)
+ element = elementInfo.closeFunc(this._buf, element);
+
+ // If an nsIFeedContainer was on top of the stack,
+ // we need to normalize it
+ if (elementInfo.containerClass == Cc[ENTRY_CONTRACTID])
+ containerParent.normalize();
+
+ // If it's an array, re-set the last element
+ if (isArray)
+ container.replaceElementAt(element, container.length - 1, false);
+ },
+
+ _prefixForNS: function FP_prefixForNS(uri) {
+ if (!uri)
+ return "";
+ var prefix = gNamespaces[uri];
+ if (prefix)
+ return prefix + ":";
+ if (uri.toLowerCase().indexOf("http://backend.userland.com") == 0)
+ return "";
+ return null;
+ },
+
+ _mapAttributes: function FP__mapAttributes(bag, attributes) {
+ // Cycle through the attributes, and set our properties using the
+ // prefix:localNames we find in our namespace dictionary.
+ for (var i = 0; i < attributes.length; ++i) {
+ var key = this._prefixForNS(attributes.getURI(i)) + attributes.getLocalName(i);
+ var val = attributes.getValue(i);
+ bag.setPropertyAsAString(key, val);
+ }
+ },
+
+ // Only for RSS2esque formats
+ _findRSSVersion: function FP__findRSSVersion(attributes) {
+ var versionAttr = attributes.getValueFromName("", "version").trim();
+ var versions = { "0.91":"rss091",
+ "0.92":"rss092",
+ "0.93":"rss093",
+ "0.94":"rss094" }
+ if (versions[versionAttr])
+ return versions[versionAttr];
+ if (versionAttr.substr(0, 2) != "2.")
+ return "rssUnknown";
+ return "rss2";
+ },
+
+ // unknown element values are returned here. See startElement above
+ // for how this works.
+ returnFromExtHandler:
+ function FP_returnExt(uri, localName, chars, attributes) {
+ --this._depth;
+
+ // take control of the SAX events
+ this._reader.contentHandler = this;
+ if (localName == null && chars == null)
+ return;
+
+ // we don't take random elements inside rdf:RDF
+ if (this._state == "IN_RDF")
+ return;
+
+ // Grab the top of the stack
+ var top = this._stack[this._stack.length - 1];
+ if (!top)
+ return;
+
+ var container = top[0];
+ // Grab the last element if it's an array
+ if (isIArray(container)) {
+ var contract = this._handlerStack[this._depth].containerClass;
+ // check if it's something specific, but not an entry
+ if (contract && contract != Cc[ENTRY_CONTRACTID]) {
+ var el = container.queryElementAt(container.length - 1,
+ Ci.nsIFeedElementBase);
+ // XXX there must be a way to flatten these interfaces
+ if (contract == Cc[PERSON_CONTRACTID])
+ el.QueryInterface(Ci.nsIFeedPerson);
+ else
+ return; // don't know about this interface
+
+ let propName = localName;
+ var prefix = gNamespaces[uri];
+
+ // synonyms
+ if ((uri == "" ||
+ prefix &&
+ ((prefix.indexOf("atom") > -1) ||
+ (prefix.indexOf("rss") > -1))) &&
+ (propName == "url" || propName == "href"))
+ propName = "uri";
+
+ try {
+ if (el[propName] !== "undefined") {
+ var propValue = chars;
+ // convert URI-bearing values to an nsIURI
+ if (propName == "uri") {
+ var base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
+ propValue = strToURI(chars, base);
+ }
+ el[propName] = propValue;
+ }
+ }
+ catch (e) {
+ // ignore XPConnect errors
+ }
+ // the rest of the function deals with entry- and feed-level stuff
+ return;
+ }
+ container = container.queryElementAt(container.length - 1,
+ Ci.nsIWritablePropertyBag2);
+ }
+
+ // Make the buffer our new property
+ var propName = this._prefixForNS(uri) + localName;
+
+ // But, it could be something containing HTML. If so,
+ // we need to know about that.
+ if (this._textConstructs[propName] != null &&
+ this._handlerStack[this._depth].containerClass !== null) {
+ var newProp = Cc[TEXTCONSTRUCT_CONTRACTID].
+ createInstance(Ci.nsIFeedTextConstruct);
+ newProp.text = chars;
+ // Look up the default type in our table
+ var type = this._textConstructs[propName];
+ var typeAttribute = attributes.getValueFromName("", "type");
+ if (this._result.version == "atom" && typeAttribute != null) {
+ type = typeAttribute;
+ }
+ else if (this._result.version == "atom03" && typeAttribute != null) {
+ if (typeAttribute.toLowerCase().indexOf("xhtml") >= 0) {
+ type = "xhtml";
+ }
+ else if (typeAttribute.toLowerCase().indexOf("html") >= 0) {
+ type = "html";
+ }
+ else if (typeAttribute.toLowerCase().indexOf("text") >= 0) {
+ type = "text";
+ }
+ }
+
+ // If it's rss feed-level description, it's not supposed to have html
+ if (this._result.version.indexOf("rss") >= 0 &&
+ this._handlerStack[this._depth].containerClass != ENTRY_CONTRACTID) {
+ type = "text";
+ }
+ newProp.type = type;
+ newProp.base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
+ container.setPropertyAsInterface(propName, newProp);
+ }
+ else {
+ container.setPropertyAsAString(propName, chars);
+ }
+ },
+
+ // Sometimes, we'll hand off SAX handling duties to an XHTMLHandler
+ // (see above) that will scrape out non-XHTML stuff, normalize
+ // namespaces, and remove the wrapper div from Atom 1.0. When the
+ // XHTMLHandler is done, it'll callback here.
+ returnFromXHTMLHandler:
+ function FP_returnFromXHTMLHandler(chars, uri, localName, qName) {
+ // retake control of the SAX content events
+ this._reader.contentHandler = this;
+
+ // Grab the top of the stack
+ var top = this._stack[this._stack.length - 1];
+ if (!top)
+ return;
+ var container = top[0];
+
+ // Assign the property
+ var newProp = newProp = Cc[TEXTCONSTRUCT_CONTRACTID].
+ createInstance(Ci.nsIFeedTextConstruct);
+ newProp.text = chars;
+ newProp.type = "xhtml";
+ newProp.base = this._xmlBaseStack[this._xmlBaseStack.length - 1];
+ container.setPropertyAsInterface(this._prefixForNS(uri) + localName,
+ newProp);
+
+ // XHTML will cause us to peek too far. The XHTML handler will
+ // send us an end element to call. RFC4287-valid feeds allow a
+ // more graceful way to handle this. Unfortunately, we can't count
+ // on compliance at this point.
+ this.endElement(uri, localName, qName);
+ },
+
+ // XPCOM stuff
+ classID: FP_CLASSID,
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsIFeedProcessor, Ci.nsISAXContentHandler, Ci.nsISAXErrorHandler,
+ Ci.nsIStreamListener, Ci.nsIRequestObserver]
+ )
+}
+
+var components = [FeedProcessor, FeedResult, Feed, Entry,
+ TextConstruct, Generator, Person];
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/components/feeds/FeedProcessor.manifest b/components/feeds/FeedProcessor.manifest
new file mode 100644
index 000000000..5081d70c5
--- /dev/null
+++ b/components/feeds/FeedProcessor.manifest
@@ -0,0 +1,14 @@
+component {072a5c3d-30c6-4f07-b87f-9f63d51403f2} FeedProcessor.js
+contract @mozilla.org/feed-result;1 {072a5c3d-30c6-4f07-b87f-9f63d51403f2}
+component {5d0cfa97-69dd-4e5e-ac84-f253162e8f9a} FeedProcessor.js
+contract @mozilla.org/feed;1 {5d0cfa97-69dd-4e5e-ac84-f253162e8f9a}
+component {8e4444ff-8e99-4bdd-aa7f-fb3c1c77319f} FeedProcessor.js
+contract @mozilla.org/feed-entry;1 {8e4444ff-8e99-4bdd-aa7f-fb3c1c77319f}
+component {b992ddcd-3899-4320-9909-924b3e72c922} FeedProcessor.js
+contract @mozilla.org/feed-textconstruct;1 {b992ddcd-3899-4320-9909-924b3e72c922}
+component {414af362-9ad8-4296-898e-62247f25a20e} FeedProcessor.js
+contract @mozilla.org/feed-generator;1 {414af362-9ad8-4296-898e-62247f25a20e}
+component {95c963b7-20b2-11db-92f6-001422106990} FeedProcessor.js
+contract @mozilla.org/feed-person;1 {95c963b7-20b2-11db-92f6-001422106990}
+component {26acb1f0-28fc-43bc-867a-a46aabc85dd4} FeedProcessor.js
+contract @mozilla.org/feed-processor;1 {26acb1f0-28fc-43bc-867a-a46aabc85dd4}
diff --git a/components/feeds/moz.build b/components/feeds/moz.build
new file mode 100644
index 000000000..7ae10d0a3
--- /dev/null
+++ b/components/feeds/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'nsIFeed.idl',
+ 'nsIFeedContainer.idl',
+ 'nsIFeedElementBase.idl',
+ 'nsIFeedEntry.idl',
+ 'nsIFeedGenerator.idl',
+ 'nsIFeedListener.idl',
+ 'nsIFeedPerson.idl',
+ 'nsIFeedProcessor.idl',
+ 'nsIFeedResult.idl',
+ 'nsIFeedTextConstruct.idl',
+]
+
+XPIDL_MODULE = 'feeds'
+
+EXTRA_COMPONENTS += [
+ 'FeedProcessor.js',
+ 'FeedProcessor.manifest',
+]
diff --git a/components/feeds/nsIFeed.idl b/components/feeds/nsIFeed.idl
new file mode 100644
index 000000000..ad87ad9d3
--- /dev/null
+++ b/components/feeds/nsIFeed.idl
@@ -0,0 +1,86 @@
+/* -*- 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 "nsIFeedContainer.idl"
+
+interface nsIArray;
+interface nsIFeedGenerator;
+
+/**
+ * An nsIFeed represents a single Atom or RSS feed.
+ */
+[scriptable, uuid(3b8aae33-80e2-4efa-99c8-a6c5b99f76ea)]
+interface nsIFeed : nsIFeedContainer
+{
+ /**
+ * Uses description, subtitle, and extensions
+ * to generate a summary.
+ */
+ attribute nsIFeedTextConstruct subtitle;
+
+ // All content classifies as a "feed" - it is the transport.
+ const unsigned long TYPE_FEED = 0;
+ const unsigned long TYPE_AUDIO = 1;
+ const unsigned long TYPE_IMAGE = 2;
+ const unsigned long TYPE_VIDEO = 4;
+
+ /**
+ * The type of feed. For example, a podcast would be TYPE_AUDIO.
+ */
+ readonly attribute unsigned long type;
+
+ /**
+ * The total number of enclosures found in the feed.
+ */
+ attribute long enclosureCount;
+
+ /**
+ * The items or entries in feed.
+ */
+ attribute nsIArray items;
+
+ /**
+ * No one really knows what cloud is for.
+ *
+ * It supposedly enables some sort of interaction with an XML-RPC or
+ * SOAP service.
+ */
+ attribute nsIWritablePropertyBag2 cloud;
+
+ /**
+ * Information about the software that produced the feed.
+ */
+ attribute nsIFeedGenerator generator;
+
+ /**
+ * An image url and some metadata (as defined by RSS2).
+ *
+ */
+ attribute nsIWritablePropertyBag2 image;
+
+ /**
+ * No one really knows what textInput is for.
+ *
+ * See
+ * <http://www.cadenhead.org/workbench/news/2894/rss-joy-textinput>
+ * for more details.
+ */
+ attribute nsIWritablePropertyBag2 textInput;
+
+ /**
+ * Days to skip fetching. This field was supposed to designate
+ * intervals for feed fetching. It's not generally implemented. For
+ * example, if this array contained "Monday", aggregators should not
+ * fetch the feed on Mondays.
+ */
+ attribute nsIArray skipDays;
+
+ /**
+ * Hours to skip fetching. This field was supposed to designate
+ * intervals for feed fetching. It's not generally implemented. See
+ * <http://blogs.law.harvard.edu/tech/rss> for more information.
+ */
+ attribute nsIArray skipHours;
+};
diff --git a/components/feeds/nsIFeedContainer.idl b/components/feeds/nsIFeedContainer.idl
new file mode 100644
index 000000000..58de494a5
--- /dev/null
+++ b/components/feeds/nsIFeedContainer.idl
@@ -0,0 +1,85 @@
+/* -*- 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 "nsIFeedElementBase.idl"
+
+interface nsIURI;
+interface nsIWritablePropertyBag2;
+interface nsIArray;
+interface nsIFeedTextConstruct;
+
+/**
+ * A shared base for feeds and items, which are pretty similar,
+ * but they have some divergent attributes and require
+ * different convenience methods.
+ */
+[scriptable, uuid(577a1b4c-b3d4-4c76-9cf8-753e6606114f)]
+interface nsIFeedContainer : nsIFeedElementBase
+{
+ /**
+ * Many feeds contain an ID distinct from their URI, and
+ * entries have standard fields for this in all major formats.
+ */
+ attribute AString id;
+
+ /**
+ * The fields found in the document. Common Atom
+ * and RSS fields are normalized. This includes some namespaced
+ * extensions such as dc:subject and content:encoded.
+ * Consumers can avoid normalization by checking the feed type
+ * and accessing specific fields.
+ *
+ * Common namespaces are accessed using prefixes, like get("dc:subject");.
+ * See nsIFeedResult::registerExtensionPrefix.
+ */
+ attribute nsIWritablePropertyBag2 fields;
+
+ /**
+ * Sometimes there's no title, or the title contains markup, so take
+ * care in decoding the attribute.
+ */
+ attribute nsIFeedTextConstruct title;
+
+ /**
+ * Returns the primary link for the feed or entry.
+ */
+ attribute nsIURI link;
+
+ /**
+ * Returns all links for a feed or entry.
+ */
+ attribute nsIArray links;
+
+ /**
+ * Returns the categories found in a feed or entry.
+ */
+ attribute nsIArray categories;
+
+ /**
+ * The rights or license associated with a feed or entry.
+ */
+ attribute nsIFeedTextConstruct rights;
+
+ /**
+ * A list of nsIFeedPersons that authored the feed.
+ */
+ attribute nsIArray authors;
+
+ /**
+ * A list of nsIFeedPersons that contributed to the feed.
+ */
+ attribute nsIArray contributors;
+
+ /**
+ * The date the feed was updated, in RFC822 form. Parsable by JS
+ * and mail code.
+ */
+ attribute AString updated;
+
+ /**
+ * Syncs a container's fields with its convenience attributes.
+ */
+ void normalize();
+};
diff --git a/components/feeds/nsIFeedElementBase.idl b/components/feeds/nsIFeedElementBase.idl
new file mode 100644
index 000000000..1b8975ae5
--- /dev/null
+++ b/components/feeds/nsIFeedElementBase.idl
@@ -0,0 +1,28 @@
+/* -*- 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 nsISAXAttributes;
+interface nsIURI;
+
+/**
+ * An nsIFeedGenerator represents the software used to create a feed.
+ */
+[scriptable, uuid(5215291e-fa0a-40c2-8ce7-e86cd1a1d3fa)]
+interface nsIFeedElementBase : nsISupports
+{
+ /**
+ * The attributes found on the element. Most interfaces provide convenience
+ * accessors for their standard fields, so this useful only when looking for
+ * an extension.
+ */
+ attribute nsISAXAttributes attributes;
+
+ /**
+ * The baseURI for the Entry or Feed.
+ */
+ attribute nsIURI baseURI;
+};
diff --git a/components/feeds/nsIFeedEntry.idl b/components/feeds/nsIFeedEntry.idl
new file mode 100644
index 000000000..83646aadb
--- /dev/null
+++ b/components/feeds/nsIFeedEntry.idl
@@ -0,0 +1,46 @@
+/* -*- 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 "nsIFeedContainer.idl"
+interface nsIArray;
+
+/**
+ * An nsIFeedEntry represents an Atom or RSS entry/item. Summary
+ * and/or full-text content may be available, but callers will have to
+ * check both.
+ */
+[scriptable, uuid(31bfd5b4-8ff5-4bfd-a8cb-b3dfbd4f0a5b)]
+interface nsIFeedEntry : nsIFeedContainer {
+
+ /**
+ * Uses description, subtitle, summary, content and extensions
+ * to generate a summary.
+ *
+ */
+ attribute nsIFeedTextConstruct summary;
+
+ /**
+ * The date the entry was published, in RFC822 form. Parsable by JS
+ * and mail code.
+ */
+ attribute AString published;
+
+ /**
+ * Uses atom:content and content:encoded to provide
+ * a 'full text' view of an entry.
+ *
+ */
+ attribute nsIFeedTextConstruct content;
+
+ /**
+ * Enclosures are podcasts, photocasts, etc.
+ */
+ attribute nsIArray enclosures;
+
+ /**
+ * Enclosures, etc. that might be displayed inline.
+ */
+ attribute nsIArray mediaContent;
+};
diff --git a/components/feeds/nsIFeedGenerator.idl b/components/feeds/nsIFeedGenerator.idl
new file mode 100644
index 000000000..3c23ca142
--- /dev/null
+++ b/components/feeds/nsIFeedGenerator.idl
@@ -0,0 +1,30 @@
+/* -*- 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 "nsIFeedElementBase.idl"
+
+interface nsIURI;
+
+/**
+ * An nsIFeedGenerator represents the software used to create a feed.
+ */
+[scriptable, uuid(0fecd56b-bd92-481b-a486-b8d489cdd385)]
+interface nsIFeedGenerator : nsIFeedElementBase
+{
+ /**
+ * The name of the software.
+ */
+ attribute AString agent;
+
+ /**
+ * The version of the software.
+ */
+ attribute AString version;
+
+ /**
+ * A URI associated with the software.
+ */
+ attribute nsIURI uri;
+};
diff --git a/components/feeds/nsIFeedListener.idl b/components/feeds/nsIFeedListener.idl
new file mode 100644
index 000000000..6826d04a4
--- /dev/null
+++ b/components/feeds/nsIFeedListener.idl
@@ -0,0 +1,87 @@
+/* -*- 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 nsIFeedResult;
+interface nsIFeedEntry;
+
+/**
+ * nsIFeedResultListener defines a callback used when feed processing
+ * completes.
+ */
+[scriptable, uuid(4d2ebe88-36eb-4e20-bcd1-997b3c1f24ce)]
+interface nsIFeedResultListener : nsISupports
+{
+ /**
+ * Always called, even after an error. There could be new feed-level
+ * data available at this point, if it followed or was interspersed
+ * with the items. Fire-and-Forget implementations only need this.
+ *
+ * @param result
+ * An object implementing nsIFeedResult representing the feed
+ * and its metadata.
+ */
+ void handleResult(in nsIFeedResult result);
+};
+
+
+/**
+ * nsIFeedProgressListener defines callbacks used during feed
+ * processing.
+ */
+[scriptable, uuid(ebfd5de5-713c-40c0-ad7c-f095117fa580)]
+interface nsIFeedProgressListener : nsIFeedResultListener {
+
+ /**
+ * ReportError will be called in the event of fatal
+ * XML errors, or if the document is not a feed. The bozo
+ * bit will be set if the error was due to a fatal error.
+ *
+ * @param errorText
+ * A short description of the error.
+ * @param lineNumber
+ * The line on which the error occurred.
+ */
+ void reportError(in AString errorText, in long lineNumber,
+ in boolean bozo);
+
+ /**
+ * StartFeed will be called as soon as a reasonable start to
+ * a feed is detected.
+ *
+ * @param result
+ * An object implementing nsIFeedResult representing the feed
+ * and its metadata. At this point, the result has version
+ * information.
+ */
+ void handleStartFeed(in nsIFeedResult result);
+
+ /**
+ * Called when the first entry/item is encountered. In Atom, all
+ * feed data is required to preceed the entries. In RSS, the data
+ * usually does. If the type is one of the entry/item-only types,
+ * this event will not be called.
+ *
+ * @param result
+ * An object implementing nsIFeedResult representing the feed
+ * and its metadata. At this point, the result will likely have
+ * most of its feed-level metadata.
+ */
+ void handleFeedAtFirstEntry(in nsIFeedResult result);
+
+ /**
+ * Called after each entry/item. If the document is a standalone
+ * item or entry, this HandleFeedAtFirstEntry will not have been
+ * called. Also, this entry's parent field will be null.
+ *
+ * @param entry
+ * An object implementing nsIFeedEntry that represents the latest
+ * entry encountered.
+ * @param result
+ * An object implementing nsIFeedResult representing the feed
+ * and its metadata.
+ */
+ void handleEntry(in nsIFeedEntry entry, in nsIFeedResult result);
+};
diff --git a/components/feeds/nsIFeedPerson.idl b/components/feeds/nsIFeedPerson.idl
new file mode 100644
index 000000000..d9d6eb77b
--- /dev/null
+++ b/components/feeds/nsIFeedPerson.idl
@@ -0,0 +1,30 @@
+/* -*- 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 "nsIFeedElementBase.idl"
+
+interface nsIURI;
+
+/**
+ * An nsIFeedPerson represents an author or contributor of a feed.
+ */
+[scriptable, uuid(29cbd45f-f2d3-4b28-b557-3ab7a61ecde4)]
+interface nsIFeedPerson : nsIFeedElementBase
+{
+ /**
+ * The name of the person.
+ */
+ attribute AString name;
+
+ /**
+ * An email address associated with the person.
+ */
+ attribute AString email;
+
+ /**
+ * A URI associated with the person (e.g. a homepage).
+ */
+ attribute nsIURI uri;
+};
diff --git a/components/feeds/nsIFeedProcessor.idl b/components/feeds/nsIFeedProcessor.idl
new file mode 100644
index 000000000..d7205600d
--- /dev/null
+++ b/components/feeds/nsIFeedProcessor.idl
@@ -0,0 +1,57 @@
+/* -*- 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 "nsIStreamListener.idl"
+
+interface nsIURI;
+interface nsIFeedResultListener;
+interface nsIInputStream;
+
+/**
+ * An nsIFeedProcessor parses feeds, triggering callbacks based on
+ * their contents.
+ */
+[scriptable, uuid(8a0b2908-21b0-45d7-b14d-30df0f92afc7)]
+interface nsIFeedProcessor : nsIStreamListener {
+
+ /**
+ * The listener that will respond to feed events.
+ */
+ attribute nsIFeedResultListener listener;
+
+ // Level is where to listen for the extension, a constant: FEED,
+ // ENTRY, BOTH.
+ //
+ // XXX todo void registerExtensionHandler(in
+ // nsIFeedExtensionHandler, in long level);
+
+ /**
+ * Parse a feed from an nsIInputStream.
+ *
+ * @param stream The input stream.
+ * @param uri The base URI.
+ */
+ void parseFromStream(in nsIInputStream stream, in nsIURI uri);
+
+ /**
+ * Parse a feed from a string.
+ *
+ * @param str The string to parse.
+ * @param uri The base URI.
+ */
+ void parseFromString(in AString str, in nsIURI uri);
+
+ /**
+ * Parse a feed asynchronously. The caller must then call the
+ * nsIFeedProcessor's nsIStreamListener methods to drive the
+ * parse. Do not call the other parse methods during an asynchronous
+ * parse.
+ *
+ * @param requestObserver The observer to notify on start/stop. This
+ * argument can be null.
+ * @param uri The base URI.
+ */
+ void parseAsync(in nsIRequestObserver requestObserver, in nsIURI uri);
+};
diff --git a/components/feeds/nsIFeedResult.idl b/components/feeds/nsIFeedResult.idl
new file mode 100644
index 000000000..4cfb0a13e
--- /dev/null
+++ b/components/feeds/nsIFeedResult.idl
@@ -0,0 +1,65 @@
+/* -*- 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 nsIFeedContainer;
+interface nsIProperties;
+interface nsIURI;
+
+/**
+ * The nsIFeedResult interface provides access to HTTP and parsing
+ * metadata for a feed or entry.
+ */
+[scriptable, uuid(7a180b78-0f46-4569-8c22-f3d720ea1c57)]
+interface nsIFeedResult : nsISupports {
+
+ /**
+ * The Feed parser will set the bozo bit when a feed triggers a fatal
+ * error during XML parsing. There may be entries and feed metadata
+ * that were parsed before the error. Thanks to Tim Bray for
+ * suggesting this terminology.
+ * <http://www.tbray.org/ongoing/When/200x/2004/01/11/PostelPilgrim>
+ */
+ attribute boolean bozo;
+
+ /**
+ * The parsed feed or entry.
+ *
+ * Will be null if a non-feed is processed.
+ */
+ attribute nsIFeedContainer doc;
+
+ /**
+ * The address from which the feed was fetched.
+ */
+ attribute nsIURI uri;
+
+ /**
+ * Feed Version:
+ * atom, rss2, rss09, rss091, rss091userland, rss092, rss1, atom03,
+ * atomEntry, rssItem
+ *
+ * Will be null if a non-feed is processed.
+ */
+ attribute AString version;
+
+ /**
+ * An XSLT stylesheet available to transform the source of the
+ * feed. Some feeds include this information in a processing
+ * instruction. It's generally intended for clients with specific
+ * feed capabilities.
+ */
+ attribute nsIURI stylesheet;
+
+ /**
+ * HTTP response headers that accompanied the feed.
+ */
+ attribute nsIProperties headers;
+
+ /**
+ * Registers a prefix used to access an extension in the feed/entry
+ */
+ void registerExtensionPrefix(in AString aNamespace, in AString aPrefix);
+};
diff --git a/components/feeds/nsIFeedTextConstruct.idl b/components/feeds/nsIFeedTextConstruct.idl
new file mode 100644
index 000000000..070acb20a
--- /dev/null
+++ b/components/feeds/nsIFeedTextConstruct.idl
@@ -0,0 +1,57 @@
+/* -*- 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 nsIDOMElement;
+interface nsIDOMDocumentFragment;
+
+/**
+ * nsIFeedTextConstructs represent feed text fields that can contain
+ * one of text, HTML, or XHTML. Some extension elements also have "type"
+ * parameters, and this interface could be used there as well.
+ */
+[scriptable, uuid(fc97a2a9-d649-4494-931e-db81a156c873)]
+interface nsIFeedTextConstruct : nsISupports
+{
+ /**
+ * If the text construct contains (X)HTML, relative references in
+ * the content should be resolved against this base URI.
+ */
+ attribute nsIURI base;
+
+ /**
+ * The language of the text. For example, "en-US" for US English.
+ */
+ attribute AString lang;
+
+ /**
+ * One of "text", "html", or "xhtml". If the type is (x)html, a '<'
+ * character represents markup. To display that character, an escape
+ * such as &lt; must be used. If the type is "text", the '<'
+ * character represents the character itself, and such text should
+ * not be embedded in markup without escaping it first.
+ */
+ attribute AString type;
+
+ /**
+ * The content of the text construct.
+ */
+ attribute AString text;
+
+ /**
+ * Returns the text of the text construct, with all markup stripped
+ * and all entities decoded. If the type attribute's value is "text",
+ * this function returns the value of the text attribute unchanged.
+ */
+ AString plainText();
+
+ /**
+ * Return an nsIDocumentFragment containing the text and markup.
+ */
+ nsIDOMDocumentFragment createDocumentFragment(in nsIDOMElement element);
+};
+
diff --git a/components/filepicker/content/filepicker.js b/components/filepicker/content/filepicker.js
new file mode 100644
index 000000000..6f91066ba
--- /dev/null
+++ b/components/filepicker/content/filepicker.js
@@ -0,0 +1,833 @@
+// -*- 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 nsIFilePicker = Components.interfaces.nsIFilePicker;
+const nsIProperties = Components.interfaces.nsIProperties;
+const NS_DIRECTORYSERVICE_CONTRACTID = "@mozilla.org/file/directory_service;1";
+const NS_IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
+const nsIFileView = Components.interfaces.nsIFileView;
+const NS_FILEVIEW_CONTRACTID = "@mozilla.org/filepicker/fileview;1";
+const nsITreeView = Components.interfaces.nsITreeView;
+const nsILocalFile = Components.interfaces.nsILocalFile;
+const nsIFile = Components.interfaces.nsIFile;
+const NS_LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1";
+const NS_PROMPTSERVICE_CONTRACTID = "@mozilla.org/embedcomp/prompt-service;1";
+
+var sfile = Components.classes[NS_LOCAL_FILE_CONTRACTID].createInstance(nsILocalFile);
+var retvals;
+var filePickerMode;
+var homeDir;
+var treeView;
+var allowURLs;
+
+var textInput;
+var okButton;
+
+var gFilePickerBundle;
+
+// name of new directory entered by the user to be remembered
+// for next call of newDir() in case something goes wrong with creation
+var gNewDirName = { value: "" };
+
+function filepickerLoad() {
+ gFilePickerBundle = document.getElementById("bundle_filepicker");
+
+ textInput = document.getElementById("textInput");
+ okButton = document.documentElement.getButton("accept");
+ treeView = Components.classes[NS_FILEVIEW_CONTRACTID].createInstance(nsIFileView);
+
+ if (window.arguments) {
+ var o = window.arguments[0];
+ retvals = o.retvals; /* set this to a global var so we can set return values */
+ const title = o.title;
+ filePickerMode = o.mode;
+ if (o.displayDirectory) {
+ var directory = o.displayDirectory.path;
+ }
+
+ const initialText = o.defaultString;
+ var filterTitles = o.filters.titles;
+ var filterTypes = o.filters.types;
+ var numFilters = filterTitles.length;
+
+ document.title = title;
+ allowURLs = o.allowURLs;
+
+ if (initialText) {
+ textInput.value = initialText;
+ }
+ }
+
+ if (filePickerMode != nsIFilePicker.modeOpen && filePickerMode != nsIFilePicker.modeOpenMultiple) {
+ var newDirButton = document.getElementById("newDirButton");
+ newDirButton.removeAttribute("hidden");
+ }
+
+ if (filePickerMode == nsIFilePicker.modeGetFolder) {
+ var textInputLabel = document.getElementById("textInputLabel");
+ textInputLabel.value = gFilePickerBundle.getString("dirTextInputLabel");
+ textInputLabel.accessKey = gFilePickerBundle.getString("dirTextInputAccesskey");
+ }
+
+ if ((filePickerMode == nsIFilePicker.modeOpen) ||
+ (filePickerMode == nsIFilePicker.modeOpenMultiple) ||
+ (filePickerMode == nsIFilePicker.modeSave)) {
+
+ /* build filter popup */
+ var filterPopup = document.createElement("menupopup");
+
+ for (var i = 0; i < numFilters; i++) {
+ var menuItem = document.createElement("menuitem");
+ if (filterTypes[i] == "..apps")
+ menuItem.setAttribute("label", filterTitles[i]);
+ else
+ menuItem.setAttribute("label", filterTitles[i] + " (" + filterTypes[i] + ")");
+ menuItem.setAttribute("filters", filterTypes[i]);
+ filterPopup.appendChild(menuItem);
+ }
+
+ var filterMenuList = document.getElementById("filterMenuList");
+ filterMenuList.appendChild(filterPopup);
+ if (numFilters > 0)
+ filterMenuList.selectedIndex = 0;
+ var filterBox = document.getElementById("filterBox");
+ filterBox.removeAttribute("hidden");
+
+ filterMenuList.selectedIndex = o.filterIndex;
+
+ treeView.setFilter(filterTypes[o.filterIndex]);
+
+ } else if (filePickerMode == nsIFilePicker.modeGetFolder) {
+ treeView.showOnlyDirectories = true;
+ }
+
+ // The dialog defaults to an "open" icon, change it to "save" if applicable
+ if (filePickerMode == nsIFilePicker.modeSave)
+ okButton.setAttribute("icon", "save");
+
+ // start out with a filename sort
+ handleColumnClick("FilenameColumn");
+
+ try {
+ setOKAction();
+ } catch (exception) {
+ // keep it set to "OK"
+ }
+
+ // setup the dialogOverlay.xul button handlers
+ retvals.buttonStatus = nsIFilePicker.returnCancel;
+
+ var tree = document.getElementById("directoryTree");
+ if (filePickerMode == nsIFilePicker.modeOpenMultiple)
+ tree.removeAttribute("seltype");
+
+ tree.view = treeView;
+
+ // Start out with the ok button disabled since nothing will be
+ // selected and nothing will be in the text field.
+ okButton.disabled = filePickerMode != nsIFilePicker.modeGetFolder;
+
+ // This allows the window to show onscreen before we begin
+ // loading the file list
+
+ setTimeout(setInitialDirectory, 0, directory);
+}
+
+function setInitialDirectory(directory)
+{
+ // Start in the user's home directory
+ var dirService = Components.classes[NS_DIRECTORYSERVICE_CONTRACTID]
+ .getService(nsIProperties);
+ homeDir = dirService.get("Home", Components.interfaces.nsIFile);
+
+ if (directory) {
+ sfile.initWithPath(directory);
+ if (!sfile.exists() || !sfile.isDirectory())
+ directory = false;
+ }
+ if (!directory) {
+ sfile.initWithPath(homeDir.path);
+ }
+
+ gotoDirectory(sfile);
+}
+
+function onFilterChanged(target)
+{
+ // Do this on a timeout callback so the filter list can roll up
+ // and we don't keep the mouse grabbed while we are refiltering.
+
+ setTimeout(changeFilter, 0, target.getAttribute("filters"));
+}
+
+function changeFilter(filterTypes)
+{
+ window.setCursor("wait");
+ treeView.setFilter(filterTypes);
+ window.setCursor("auto");
+}
+
+function showErrorDialog(titleStrName, messageStrName, file)
+{
+ var errorTitle =
+ gFilePickerBundle.getFormattedString(titleStrName, [file.path]);
+ var errorMessage =
+ gFilePickerBundle.getFormattedString(messageStrName, [file.path]);
+ var promptService =
+ Components.classes[NS_PROMPTSERVICE_CONTRACTID].getService(Components.interfaces.nsIPromptService);
+
+ promptService.alert(window, errorTitle, errorMessage);
+}
+
+function openOnOK()
+{
+ var dir = treeView.selectedFiles.queryElementAt(0, nsIFile);
+ if (dir)
+ gotoDirectory(dir);
+
+ return false;
+}
+
+function selectOnOK()
+{
+ var errorTitle, errorMessage, promptService;
+ var ret = nsIFilePicker.returnOK;
+
+ var isDir = false;
+ var isFile = false;
+
+ retvals.filterIndex = document.getElementById("filterMenuList").selectedIndex;
+ retvals.fileURL = null;
+
+ if (allowURLs) {
+ try {
+ var ios = Components.classes[NS_IOSERVICE_CONTRACTID].getService(Components.interfaces.nsIIOService);
+ retvals.fileURL = ios.newURI(textInput.value, null, null);
+ let fileList = [];
+ if (retvals.fileURL instanceof Components.interfaces.nsIFileURL)
+ fileList.push(retvals.fileURL.file);
+ gFilesEnumerator.mFiles = fileList;
+ retvals.files = gFilesEnumerator;
+ retvals.buttonStatus = ret;
+
+ return true;
+ } catch (e) {
+ }
+ }
+
+ var fileList = processPath(textInput.value);
+ if (!fileList) {
+ // generic error message, should probably never happen
+ showErrorDialog("errorPathProblemTitle",
+ "errorPathProblemMessage",
+ textInput.value);
+ return false;
+ }
+
+ var curFileIndex;
+ for (curFileIndex = 0; curFileIndex < fileList.length &&
+ ret != nsIFilePicker.returnCancel; ++curFileIndex) {
+ var file = fileList[curFileIndex].QueryInterface(nsIFile);
+
+ // try to normalize - if this fails we will ignore the error
+ // because we will notice the
+ // error later and show a fitting error alert.
+ try {
+ file.normalize();
+ } catch (e) {
+ // promptService.alert(window, "Problem", "normalize failed, continuing");
+ }
+
+ var fileExists = file.exists();
+
+ if (!fileExists && (filePickerMode == nsIFilePicker.modeOpen ||
+ filePickerMode == nsIFilePicker.modeOpenMultiple)) {
+ showErrorDialog("errorOpenFileDoesntExistTitle",
+ "errorOpenFileDoesntExistMessage",
+ file);
+ return false;
+ }
+
+ if (!fileExists && filePickerMode == nsIFilePicker.modeGetFolder) {
+ showErrorDialog("errorDirDoesntExistTitle",
+ "errorDirDoesntExistMessage",
+ file);
+ return false;
+ }
+
+ if (fileExists) {
+ isDir = file.isDirectory();
+ isFile = file.isFile();
+ }
+
+ switch (filePickerMode) {
+ case nsIFilePicker.modeOpen:
+ case nsIFilePicker.modeOpenMultiple:
+ if (isFile) {
+ if (file.isReadable()) {
+ retvals.directory = file.parent.path;
+ } else {
+ showErrorDialog("errorOpeningFileTitle",
+ "openWithoutPermissionMessage_file",
+ file);
+ ret = nsIFilePicker.returnCancel;
+ }
+ } else if (isDir) {
+ if (!sfile.equals(file)) {
+ gotoDirectory(file);
+ }
+ textInput.value = "";
+ doEnabling();
+ ret = nsIFilePicker.returnCancel;
+ }
+ break;
+ case nsIFilePicker.modeSave:
+ if (isFile) { // can only be true if file.exists()
+ if (!file.isWritable()) {
+ showErrorDialog("errorSavingFileTitle",
+ "saveWithoutPermissionMessage_file",
+ file);
+ ret = nsIFilePicker.returnCancel;
+ } else {
+ // we need to pop up a dialog asking if you want to save
+ var confirmTitle = gFilePickerBundle.getString("confirmTitle");
+ var message =
+ gFilePickerBundle.getFormattedString("confirmFileReplacing",
+ [file.path]);
+
+ promptService = Components.classes[NS_PROMPTSERVICE_CONTRACTID].getService(Components.interfaces.nsIPromptService);
+ var rv = promptService.confirm(window, confirmTitle, message);
+ if (rv) {
+ ret = nsIFilePicker.returnReplace;
+ retvals.directory = file.parent.path;
+ } else {
+ ret = nsIFilePicker.returnCancel;
+ }
+ }
+ } else if (isDir) {
+ if (!sfile.equals(file)) {
+ gotoDirectory(file);
+ }
+ textInput.value = "";
+ doEnabling();
+ ret = nsIFilePicker.returnCancel;
+ } else {
+ var parent = file.parent;
+ if (parent.exists() && parent.isDirectory() && parent.isWritable()) {
+ retvals.directory = parent.path;
+ } else {
+ var oldParent = parent;
+ while (!parent.exists()) {
+ oldParent = parent;
+ parent = parent.parent;
+ }
+ errorTitle =
+ gFilePickerBundle.getFormattedString("errorSavingFileTitle",
+ [file.path]);
+ if (parent.isFile()) {
+ errorMessage =
+ gFilePickerBundle.getFormattedString("saveParentIsFileMessage",
+ [parent.path, file.path]);
+ } else {
+ errorMessage =
+ gFilePickerBundle.getFormattedString("saveParentDoesntExistMessage",
+ [oldParent.path, file.path]);
+ }
+ if (!parent.isWritable()) {
+ errorMessage =
+ gFilePickerBundle.getFormattedString("saveWithoutPermissionMessage_dir", [parent.path]);
+ }
+ promptService = Components.classes[NS_PROMPTSERVICE_CONTRACTID].getService(Components.interfaces.nsIPromptService);
+ promptService.alert(window, errorTitle, errorMessage);
+ ret = nsIFilePicker.returnCancel;
+ }
+ }
+ break;
+ case nsIFilePicker.modeGetFolder:
+ if (isDir) {
+ retvals.directory = file.parent.path;
+ } else { // if nothing selected, the current directory will be fine
+ retvals.directory = sfile.path;
+ }
+ break;
+ }
+ }
+
+ gFilesEnumerator.mFiles = fileList;
+
+ retvals.files = gFilesEnumerator;
+ retvals.buttonStatus = ret;
+
+ return (ret != nsIFilePicker.returnCancel);
+}
+
+var gFilesEnumerator = {
+ mFiles: null,
+ mIndex: 0,
+
+ hasMoreElements: function()
+ {
+ return (this.mIndex < this.mFiles.length);
+ },
+ getNext: function()
+ {
+ if (this.mIndex >= this.mFiles.length)
+ throw Components.results.NS_ERROR_FAILURE;
+ return this.mFiles[this.mIndex++];
+ }
+};
+
+function onCancel()
+{
+ // Close the window.
+ retvals.buttonStatus = nsIFilePicker.returnCancel;
+ retvals.file = null;
+ retvals.files = null;
+ return true;
+}
+
+function onDblClick(e) {
+ // we only care about button 0 (left click) events
+ if (e.button != 0) return;
+
+ var t = e.originalTarget;
+ if (t.localName != "treechildren")
+ return;
+
+ openSelectedFile();
+}
+
+function openSelectedFile() {
+ var fileList = treeView.selectedFiles;
+ if (fileList.length == 0)
+ return;
+
+ var file = fileList.queryElementAt(0, nsIFile);
+ if (file.isDirectory())
+ gotoDirectory(file);
+ else if (file.isFile())
+ document.documentElement.acceptDialog();
+}
+
+function onClick(e) {
+ var t = e.originalTarget;
+ if (t.localName == "treecol")
+ handleColumnClick(t.id);
+}
+
+function convertColumnIDtoSortType(columnID) {
+ var sortKey;
+
+ switch (columnID) {
+ case "FilenameColumn":
+ sortKey = nsIFileView.sortName;
+ break;
+ case "FileSizeColumn":
+ sortKey = nsIFileView.sortSize;
+ break;
+ case "LastModifiedColumn":
+ sortKey = nsIFileView.sortDate;
+ break;
+ default:
+ dump("unsupported sort column: " + columnID + "\n");
+ sortKey = 0;
+ break;
+ }
+
+ return sortKey;
+}
+
+function handleColumnClick(columnID) {
+ var sortType = convertColumnIDtoSortType(columnID);
+ var sortOrder = (treeView.sortType == sortType) ? !treeView.reverseSort : false;
+ treeView.sort(sortType, sortOrder);
+
+ // set the sort indicator on the column we are sorted by
+ var sortedColumn = document.getElementById(columnID);
+ if (treeView.reverseSort) {
+ sortedColumn.setAttribute("sortDirection", "descending");
+ } else {
+ sortedColumn.setAttribute("sortDirection", "ascending");
+ }
+
+ // remove the sort indicator from the rest of the columns
+ var currCol = sortedColumn.parentNode.firstChild;
+ while (currCol) {
+ if (currCol != sortedColumn && currCol.localName == "treecol")
+ currCol.removeAttribute("sortDirection");
+ currCol = currCol.nextSibling;
+ }
+}
+
+function onKeypress(e) {
+ if (e.keyCode == 8) /* backspace */
+ goUp();
+
+ /* enter is handled by the ondialogaccept handler */
+}
+
+function doEnabling() {
+ if (filePickerMode != nsIFilePicker.modeGetFolder)
+ // Maybe add check if textInput.value would resolve to an existing
+ // file or directory in .modeOpen. Too costly I think.
+ okButton.disabled = (textInput.value == "")
+}
+
+function onTreeFocus(event) {
+ // Reset the button label and enabled/disabled state.
+ onFileSelected(treeView.selectedFiles);
+}
+
+function setOKAction(file) {
+ var buttonLabel;
+ var buttonIcon = "open"; // used in all but one case
+
+ if (file && file.isDirectory()) {
+ document.documentElement.setAttribute("ondialogaccept", "return openOnOK();");
+ buttonLabel = gFilePickerBundle.getString("openButtonLabel");
+ }
+ else {
+ document.documentElement.setAttribute("ondialogaccept", "return selectOnOK();");
+ switch (filePickerMode) {
+ case nsIFilePicker.modeGetFolder:
+ buttonLabel = gFilePickerBundle.getString("selectFolderButtonLabel");
+ break;
+ case nsIFilePicker.modeOpen:
+ case nsIFilePicker.modeOpenMultiple:
+ buttonLabel = gFilePickerBundle.getString("openButtonLabel");
+ break;
+ case nsIFilePicker.modeSave:
+ buttonLabel = gFilePickerBundle.getString("saveButtonLabel");
+ buttonIcon = "save";
+ break;
+ }
+ }
+ okButton.setAttribute("label", buttonLabel);
+ okButton.setAttribute("icon", buttonIcon);
+}
+
+function onSelect(event) {
+ onFileSelected(treeView.selectedFiles);
+}
+
+function onFileSelected(/* nsIArray */ selectedFileList) {
+ var validFileSelected = false;
+ var invalidSelection = false;
+ var file;
+ var fileCount = selectedFileList.length;
+
+ for (var index = 0; index < fileCount; ++index) {
+ file = selectedFileList.queryElementAt(index, nsIFile);
+ if (file) {
+ var path = file.leafName;
+
+ if (path) {
+ var isDir = file.isDirectory();
+ if ((filePickerMode == nsIFilePicker.modeGetFolder) || !isDir) {
+ if (!validFileSelected)
+ textInput.value = "";
+ addToTextFieldValue(path);
+ }
+
+ if (isDir && fileCount > 1) {
+ // The user has selected multiple items, and one of them is
+ // a directory. This is not a valid state, so we'll disable
+ // the ok button.
+ invalidSelection = true;
+ }
+
+ validFileSelected = true;
+ }
+ }
+ }
+
+ if (validFileSelected) {
+ setOKAction(file);
+ okButton.disabled = invalidSelection;
+ } else if (filePickerMode != nsIFilePicker.modeGetFolder)
+ okButton.disabled = (textInput.value == "");
+}
+
+function addToTextFieldValue(path)
+{
+ var newValue = "";
+
+ if (textInput.value == "")
+ newValue = path.replace(/\"/g, "\\\"");
+ else {
+ // Quote the existing text if needed,
+ // then append the new filename (quoted and escaped)
+ if (textInput.value[0] != '"')
+ newValue = '"' + textInput.value.replace(/\"/g, "\\\"") + '"';
+ else
+ newValue = textInput.value;
+
+ newValue = newValue + ' "' + path.replace(/\"/g, "\\\"") + '"';
+ }
+
+ textInput.value = newValue;
+}
+
+function onTextFieldFocus() {
+ setOKAction(null);
+ doEnabling();
+}
+
+function onDirectoryChanged(target)
+{
+ var path = target.getAttribute("label");
+
+ var file = Components.classes[NS_LOCAL_FILE_CONTRACTID].createInstance(nsILocalFile);
+ file.initWithPath(path);
+
+ if (!sfile.equals(file)) {
+ // Do this on a timeout callback so the directory list can roll up
+ // and we don't keep the mouse grabbed while we are loading.
+
+ setTimeout(gotoDirectory, 0, file);
+ }
+}
+
+function populateAncestorList(directory) {
+ var menu = document.getElementById("lookInMenu");
+
+ while (menu.hasChildNodes()) {
+ menu.removeChild(menu.firstChild);
+ }
+
+ var menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("label", directory.path);
+ menuItem.setAttribute("crop", "start");
+ menu.appendChild(menuItem);
+
+ // .parent is _sometimes_ null, see bug 121489. Do a dance around that.
+ var parent = directory.parent;
+ while (parent && !parent.equals(directory)) {
+ menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("label", parent.path);
+ menuItem.setAttribute("crop", "start");
+ menu.appendChild(menuItem);
+ directory = parent;
+ parent = directory.parent;
+ }
+
+ var menuList = document.getElementById("lookInMenuList");
+ menuList.selectedIndex = 0;
+}
+
+function goUp() {
+ try {
+ var parent = sfile.parent;
+ } catch (ex) { dump("can't get parent directory\n"); }
+
+ if (parent) {
+ gotoDirectory(parent);
+ }
+}
+
+function goHome() {
+ gotoDirectory(homeDir);
+}
+
+function newDir() {
+ var file;
+ var promptService =
+ Components.classes[NS_PROMPTSERVICE_CONTRACTID].getService(Components.interfaces.nsIPromptService);
+ var dialogTitle =
+ gFilePickerBundle.getString("promptNewDirTitle");
+ var dialogMsg =
+ gFilePickerBundle.getString("promptNewDirMessage");
+ var ret = promptService.prompt(window, dialogTitle, dialogMsg, gNewDirName, null, {value:0});
+
+ if (ret) {
+ file = processPath(gNewDirName.value);
+ if (!file) {
+ showErrorDialog("errorCreateNewDirTitle",
+ "errorCreateNewDirMessage",
+ file);
+ return false;
+ }
+
+ file = file[0].QueryInterface(nsIFile);
+ if (file.exists()) {
+ showErrorDialog("errorNewDirDoesExistTitle",
+ "errorNewDirDoesExistMessage",
+ file);
+ return false;
+ }
+
+ var parent = file.parent;
+ if (!(parent.exists() && parent.isDirectory() && parent.isWritable())) {
+ while (!parent.exists()) {
+ parent = parent.parent;
+ }
+ if (parent.isFile()) {
+ showErrorDialog("errorCreateNewDirTitle",
+ "errorCreateNewDirIsFileMessage",
+ parent);
+ return false;
+ }
+ if (!parent.isWritable()) {
+ showErrorDialog("errorCreateNewDirTitle",
+ "errorCreateNewDirPermissionMessage",
+ parent);
+ return false;
+ }
+ }
+
+ try {
+ file.create(nsIFile.DIRECTORY_TYPE, 0o755);
+ } catch (e) {
+ showErrorDialog("errorCreateNewDirTitle",
+ "errorCreateNewDirMessage",
+ file);
+ return false;
+ }
+ file.normalize(); // ... in case ".." was used in the path
+ gotoDirectory(file);
+ // we remember and reshow a dirname if something goes wrong
+ // so that errors can be corrected more easily. If all went well,
+ // reset the default value to blank
+ gNewDirName = { value: "" };
+ }
+ return true;
+}
+
+function gotoDirectory(directory) {
+ window.setCursor("wait");
+ try {
+ populateAncestorList(directory);
+ treeView.setDirectory(directory);
+ document.getElementById("errorShower").selectedIndex = 0;
+ } catch (ex) {
+ document.getElementById("errorShower").selectedIndex = 1;
+ }
+
+ window.setCursor("auto");
+
+ if (filePickerMode == nsIFilePicker.modeGetFolder) {
+ textInput.value = "";
+ }
+ textInput.focus();
+ textInput.setAttribute("autocompletesearchparam", directory.path);
+ sfile = directory;
+}
+
+function toggleShowHidden(event) {
+ treeView.showHiddenFiles = !treeView.showHiddenFiles;
+}
+
+// from the current directory and whatever was entered
+// in the entry field, try to make a new path. This
+// uses "/" as the directory separator, "~" as a shortcut
+// for the home directory (but only when seen at the start
+// of a path), and ".." to denote the parent directory.
+// returns an array of the files listed,
+// or false if an error occurred.
+function processPath(path)
+{
+ var fileArray = new Array();
+ var strLength = path.length;
+
+ if (path[0] == '"' && filePickerMode == nsIFilePicker.modeOpenMultiple &&
+ strLength > 1) {
+ // we have a quoted list of filenames, separated by spaces.
+ // iterate the list and process each file.
+
+ var curFileStart = 1;
+
+ while (1) {
+ var nextQuote;
+
+ // Look for an unescaped quote
+ var quoteSearchStart = curFileStart + 1;
+ do {
+ nextQuote = path.indexOf('"', quoteSearchStart);
+ quoteSearchStart = nextQuote + 1;
+ } while (nextQuote != -1 && path[nextQuote - 1] == '\\');
+
+ if (nextQuote == -1) {
+ // we have a filename with no trailing quote.
+ // just assume that the filename ends at the end of the string.
+
+ if (!processPathEntry(path.substring(curFileStart), fileArray))
+ return false;
+ break;
+ }
+
+ if (!processPathEntry(path.substring(curFileStart, nextQuote), fileArray))
+ return false;
+
+ curFileStart = path.indexOf('"', nextQuote + 1);
+ if (curFileStart == -1) {
+ // no more quotes, but if we're not at the end of the string,
+ // go ahead and process the remaining text.
+
+ if (nextQuote < strLength - 1)
+ if (!processPathEntry(path.substring(nextQuote + 1), fileArray))
+ return false;
+ break;
+ }
+ ++curFileStart;
+ }
+ } else if (!processPathEntry(path, fileArray)) {
+ // If we didn't start with a quote, assume we just have a single file.
+ return false;
+ }
+
+ return fileArray;
+}
+
+function processPathEntry(path, fileArray)
+{
+ var filePath;
+ var file;
+
+ try {
+ file = sfile.clone().QueryInterface(nsILocalFile);
+ } catch (e) {
+ dump("Couldn't clone\n"+e);
+ return false;
+ }
+
+ var tilde_file = file.clone();
+ tilde_file.append("~");
+ if (path[0] == '~' && // Expand ~ to $HOME, except:
+ !(path == "~" && tilde_file.exists()) && // If ~ was entered and such a file exists, don't expand
+ (path.length == 1 || path[1] == "/")) // We don't want to expand ~file to ${HOME}file
+ filePath = homeDir.path + path.substring(1);
+ else
+ filePath = path;
+
+ // Unescape quotes
+ filePath = filePath.replace(/\\\"/g, "\"");
+
+ if (filePath[0] == '/') /* an absolute path was entered */
+ file.initWithPath(filePath);
+ else if ((filePath.indexOf("/../") > 0) ||
+ (filePath.substr(-3) == "/..") ||
+ (filePath.substr(0, 3) == "../") ||
+ (filePath == "..")) {
+ /* appendRelativePath doesn't allow .. */
+ try {
+ file.initWithPath(file.path + "/" + filePath);
+ } catch (e) {
+ dump("Couldn't init path\n"+e);
+ return false;
+ }
+ }
+ else {
+ try {
+ file.appendRelativePath(filePath);
+ } catch (e) {
+ dump("Couldn't append path\n"+e);
+ return false;
+ }
+ }
+
+ fileArray[fileArray.length] = file;
+ return true;
+}
diff --git a/components/filepicker/content/filepicker.xul b/components/filepicker/content/filepicker.xul
new file mode 100644
index 000000000..4bf311230
--- /dev/null
+++ b/components/filepicker/content/filepicker.xul
@@ -0,0 +1,80 @@
+<?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/filepicker.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://global/locale/filepicker.dtd" >
+
+<dialog id="main-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="filepickerLoad();"
+ width="426" height="300"
+ ondialogaccept="return selectOnOK();"
+ ondialogcancel="return onCancel();"
+ persist="screenX screenY width height">
+
+<stringbundle id="bundle_filepicker" src="chrome://global/locale/filepicker.properties"/>
+<script type="application/javascript" src="chrome://global/content/filepicker.js"/>
+
+<hbox align="center">
+ <label value="&lookInMenuList.label;" control="lookInMenuList" accesskey="&lookInMenuList.accesskey;"/>
+ <menulist id="lookInMenuList" flex="1" oncommand="onDirectoryChanged(event.target);" crop="start">
+ <menupopup id="lookInMenu"/>
+ </menulist>
+ <button id="folderUpButton" class="up-button" tooltiptext="&folderUp.tooltiptext;" oncommand="goUp();"/>
+ <button id="homeButton" class="home-button" tooltiptext="&folderHome.tooltiptext;" oncommand="goHome();"/>
+ <button id="newDirButton" hidden="true" class="new-dir-button" tooltiptext="&folderNew.tooltiptext;" oncommand="newDir();"/>
+</hbox>
+
+<hbox flex="1">
+ <deck id="errorShower" flex="1">
+ <tree id="directoryTree" flex="1" class="focusring" seltype="single"
+ onclick="onClick(event);"
+ ondblclick="onDblClick(event);"
+ onkeypress="onKeypress(event);"
+ onfocus="onTreeFocus(event);"
+ onselect="onSelect(event);">
+ <treecols>
+ <treecol id="FilenameColumn" label="&name.label;" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="FileSizeColumn" label="&size.label;" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="LastModifiedColumn" label="&lastModified.label;" flex="1"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ <label>&noPermissionError.label;</label>
+ </deck>
+</hbox>
+
+<grid style="margin-top: 5px">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <label value="&textInput.label;" id="textInputLabel" control="textInput" accesskey="&textInput.accesskey;"/>
+ <textbox id="textInput" flex="1" oninput="doEnabling()"
+ type="autocomplete" autocompletesearch="file"
+ onfocus="onTextFieldFocus();"/>
+ </row>
+ <row id="filterBox" hidden="true" align="center">
+ <label value="&filterMenuList.label;" control="filterMenuList" accesskey="&filterMenuList.accesskey;"/>
+ <menulist id="filterMenuList" flex="1" oncommand="onFilterChanged(event.target);"/>
+ </row>
+ </rows>
+</grid>
+<hbox class="dialog-button-box" align="center">
+ <checkbox label="&showHiddenFiles.label;" oncommand="toggleShowHidden();"
+ flex="1" accesskey="&showHiddenFiles.accesskey;"/>
+ <button dlgtype="cancel" icon="cancel" class="dialog-button"/>
+ <button dlgtype="accept" icon="open" class="dialog-button"/>
+</hbox>
+</dialog>
diff --git a/components/filepicker/jar.mn b/components/filepicker/jar.mn
new file mode 100644
index 000000000..ba585f3a4
--- /dev/null
+++ b/components/filepicker/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/.
+
+toolkit.jar:
+ content/global/filepicker.xul (content/filepicker.xul)
+ content/global/filepicker.js (content/filepicker.js)
+
diff --git a/components/filepicker/moz.build b/components/filepicker/moz.build
new file mode 100644
index 000000000..50c344dc5
--- /dev/null
+++ b/components/filepicker/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['nsIFileView.idl']
+
+XPIDL_MODULE = 'filepicker'
+
+SOURCES += ['nsFileView.cpp']
+
+EXTRA_COMPONENTS += ['nsFilePicker.js']
+
+EXTRA_PP_COMPONENTS += ['nsFilePicker.manifest']
+
+FINAL_LIBRARY = 'xul'
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/filepicker/nsFilePicker.js b/components/filepicker/nsFilePicker.js
new file mode 100644
index 000000000..8c92ff821
--- /dev/null
+++ b/components/filepicker/nsFilePicker.js
@@ -0,0 +1,319 @@
+/* -*- tab-width: 2; 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/. */
+
+/*
+ * No magic constructor behaviour, as is de rigeur for XPCOM.
+ * If you must perform some initialization, and it could possibly fail (even
+ * due to an out-of-memory condition), you should use an Init method, which
+ * can convey failure appropriately (thrown exception in JS,
+ * NS_FAILED(nsresult) return in C++).
+ *
+ * In JS, you can actually cheat, because a thrown exception will cause the
+ * CreateInstance call to fail in turn, but not all languages are so lucky.
+ * (Though ANSI C++ provides exceptions, they are verboten in Mozilla code
+ * for portability reasons -- and even when you're building completely
+ * platform-specific code, you can't throw across an XPCOM method boundary.)
+ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const DEBUG = false; /* set to true to enable debug messages */
+
+const LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1";
+const APPSHELL_SERV_CONTRACTID = "@mozilla.org/appshell/appShellService;1";
+const STRBUNDLE_SERV_CONTRACTID = "@mozilla.org/intl/stringbundle;1";
+
+const nsIAppShellService = Components.interfaces.nsIAppShellService;
+const nsILocalFile = Components.interfaces.nsILocalFile;
+const nsIFileURL = Components.interfaces.nsIFileURL;
+const nsISupports = Components.interfaces.nsISupports;
+const nsIFactory = Components.interfaces.nsIFactory;
+const nsIFilePicker = Components.interfaces.nsIFilePicker;
+const nsIInterfaceRequestor = Components.interfaces.nsIInterfaceRequestor;
+const nsIDOMWindow = Components.interfaces.nsIDOMWindow;
+const nsIStringBundleService = Components.interfaces.nsIStringBundleService;
+const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
+const nsIDocShellTreeItem = Components.interfaces.nsIDocShellTreeItem;
+const nsIBaseWindow = Components.interfaces.nsIBaseWindow;
+
+var titleBundle = null;
+var filterBundle = null;
+var lastDirectory = null;
+
+function nsFilePicker()
+{
+ if (!titleBundle)
+ titleBundle = srGetStrBundle("chrome://global/locale/filepicker.properties");
+ if (!filterBundle)
+ filterBundle = srGetStrBundle("chrome://global/content/filepicker.properties");
+
+ /* attributes */
+ this.mDefaultString = "";
+ this.mFilterIndex = 0;
+ this.mFilterTitles = new Array();
+ this.mFilters = new Array();
+ this.mDisplayDirectory = null;
+ if (lastDirectory) {
+ try {
+ var dir = Components.classes[LOCAL_FILE_CONTRACTID].createInstance(nsILocalFile);
+ dir.initWithPath(lastDirectory);
+ this.mDisplayDirectory = dir;
+ } catch (e) {}
+ }
+}
+
+nsFilePicker.prototype = {
+ classID: Components.ID("{54ae32f8-1dd2-11b2-a209-df7c505370f8}"),
+
+ QueryInterface: function(iid) {
+ if (iid.equals(nsIFilePicker) ||
+ iid.equals(nsISupports))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ /* attribute nsILocalFile displayDirectory; */
+ set displayDirectory(a) {
+ this.mDisplayDirectory = a &&
+ a.clone().QueryInterface(nsILocalFile);
+ },
+ get displayDirectory() {
+ return this.mDisplayDirectory &&
+ this.mDisplayDirectory.clone()
+ .QueryInterface(nsILocalFile);
+ },
+
+ /* readonly attribute nsILocalFile file; */
+ get file() { return this.mFilesEnumerator.mFiles[0]; },
+
+ /* readonly attribute nsISimpleEnumerator files; */
+ get files() { return this.mFilesEnumerator; },
+
+ /* we don't support directories, yet */
+ get domFileOrDirectory() {
+ let enumerator = this.domFileOrDirectoryEnumerator;
+ return enumerator ? enumerator.mFiles[0] : null;
+ },
+
+ /* readonly attribute nsISimpleEnumerator domFileOrDirectoryEnumerator; */
+ get domFileOrDirectoryEnumerator() {
+ if (!this.mFilesEnumerator) {
+ return null;
+ }
+
+ if (!this.mDOMFilesEnumerator) {
+ this.mDOMFilesEnumerator = {
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISimpleEnumerator]),
+
+ mFiles: [],
+ mIndex: 0,
+
+ hasMoreElements: function() {
+ return (this.mIndex < this.mFiles.length);
+ },
+
+ getNext: function() {
+ if (this.mIndex >= this.mFiles.length) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+ return this.mFiles[this.mIndex++];
+ }
+ };
+
+ var utils = this.mParentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ for (var i = 0; i < this.mFilesEnumerator.mFiles.length; ++i) {
+ var file = utils.wrapDOMFile(this.mFilesEnumerator.mFiles[i]);
+ this.mDOMFilesEnumerator.mFiles.push(file);
+ }
+ }
+
+ return this.mDOMFilesEnumerator;
+ },
+
+ /* readonly attribute nsIURI fileURL; */
+ get fileURL() {
+ if (this.mFileURL)
+ return this.mFileURL;
+
+ if (!this.mFilesEnumerator)
+ return null;
+
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+ return this.mFileURL = ioService.newFileURI(this.file);
+ },
+
+ /* attribute wstring defaultString; */
+ set defaultString(a) { this.mDefaultString = a; },
+ get defaultString() { return this.mDefaultString; },
+
+ /* attribute wstring defaultExtension */
+ set defaultExtension(ext) { },
+ get defaultExtension() { return ""; },
+
+ /* attribute long filterIndex; */
+ set filterIndex(a) { this.mFilterIndex = a; },
+ get filterIndex() { return this.mFilterIndex; },
+
+ /* attribute boolean addToRecentDocs; */
+ set addToRecentDocs(a) {},
+ get addToRecentDocs() { return false; },
+
+ /* readonly attribute short mode; */
+ get mode() { return this.mMode; },
+
+ /* members */
+ mFilesEnumerator: undefined,
+ mDOMFilesEnumerator: undefined,
+ mParentWindow: null,
+
+ /* methods */
+ init: function(parent, title, mode) {
+ this.mParentWindow = parent;
+ this.mTitle = title;
+ this.mMode = mode;
+ },
+
+ appendFilters: function(filterMask) {
+ if (filterMask & nsIFilePicker.filterHTML) {
+ this.appendFilter(titleBundle.GetStringFromName("htmlTitle"),
+ filterBundle.GetStringFromName("htmlFilter"));
+ }
+ if (filterMask & nsIFilePicker.filterText) {
+ this.appendFilter(titleBundle.GetStringFromName("textTitle"),
+ filterBundle.GetStringFromName("textFilter"));
+ }
+ if (filterMask & nsIFilePicker.filterImages) {
+ this.appendFilter(titleBundle.GetStringFromName("imageTitle"),
+ filterBundle.GetStringFromName("imageFilter"));
+ }
+ if (filterMask & nsIFilePicker.filterXML) {
+ this.appendFilter(titleBundle.GetStringFromName("xmlTitle"),
+ filterBundle.GetStringFromName("xmlFilter"));
+ }
+ if (filterMask & nsIFilePicker.filterXUL) {
+ this.appendFilter(titleBundle.GetStringFromName("xulTitle"),
+ filterBundle.GetStringFromName("xulFilter"));
+ }
+ this.mAllowURLs = !!(filterMask & nsIFilePicker.filterAllowURLs);
+ if (filterMask & nsIFilePicker.filterApps) {
+ // We use "..apps" as a special filter for executable files
+ this.appendFilter(titleBundle.GetStringFromName("appsTitle"),
+ "..apps");
+ }
+ if (filterMask & nsIFilePicker.filterAudio) {
+ this.appendFilter(titleBundle.GetStringFromName("audioTitle"),
+ filterBundle.GetStringFromName("audioFilter"));
+ }
+ if (filterMask & nsIFilePicker.filterVideo) {
+ this.appendFilter(titleBundle.GetStringFromName("videoTitle"),
+ filterBundle.GetStringFromName("videoFilter"));
+ }
+ if (filterMask & nsIFilePicker.filterAll) {
+ this.appendFilter(titleBundle.GetStringFromName("allTitle"),
+ filterBundle.GetStringFromName("allFilter"));
+ }
+ },
+
+ appendFilter: function(title, extensions) {
+ this.mFilterTitles.push(title);
+ this.mFilters.push(extensions);
+ },
+
+ open: function(aFilePickerShownCallback) {
+ var tm = Components.classes["@mozilla.org/thread-manager;1"]
+ .getService(Components.interfaces.nsIThreadManager);
+ tm.mainThread.dispatch(function() {
+ let result = Components.interfaces.nsIFilePicker.returnCancel;
+ try {
+ result = this.show();
+ } catch (ex) {
+ }
+ if (aFilePickerShownCallback) {
+ aFilePickerShownCallback.done(result);
+ }
+ }.bind(this), Components.interfaces.nsIThread.DISPATCH_NORMAL);
+ },
+
+ show: function() {
+ var o = {};
+ o.title = this.mTitle;
+ o.mode = this.mMode;
+ o.displayDirectory = this.mDisplayDirectory;
+ o.defaultString = this.mDefaultString;
+ o.filterIndex = this.mFilterIndex;
+ o.filters = {};
+ o.filters.titles = this.mFilterTitles;
+ o.filters.types = this.mFilters;
+ o.allowURLs = this.mAllowURLs;
+ o.retvals = {};
+
+ var parent;
+ if (this.mParentWindow) {
+ parent = this.mParentWindow;
+ } else if (typeof(window) == "object" && window != null) {
+ parent = window;
+ } else {
+ try {
+ var appShellService = Components.classes[APPSHELL_SERV_CONTRACTID].getService(nsIAppShellService);
+ parent = appShellService.hiddenDOMWindow;
+ } catch (ex) {
+ debug("Can't get parent. xpconnect hates me so we can't get one from the appShellService.\n");
+ debug(ex + "\n");
+ }
+ }
+
+ try {
+ parent.openDialog("chrome://global/content/filepicker.xul",
+ "",
+ "chrome,modal,titlebar,resizable=yes,dependent=yes",
+ o);
+
+ this.mFilterIndex = o.retvals.filterIndex;
+ this.mFilesEnumerator = o.retvals.files;
+ this.mFileURL = o.retvals.fileURL;
+ lastDirectory = o.retvals.directory;
+ return o.retvals.buttonStatus;
+ } catch (ex) { dump("unable to open file picker\n" + ex + "\n"); }
+
+ return null;
+ }
+}
+
+if (DEBUG)
+ debug = function (s) { dump("-*- filepicker: " + s + "\n"); };
+else
+ debug = function (s) {};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsFilePicker]);
+
+/* crap from strres.js that I want to use for string bundles since I can't include another .js file.... */
+
+var strBundleService = null;
+
+function srGetStrBundle(path)
+{
+ var strBundle = null;
+
+ if (!strBundleService) {
+ try {
+ strBundleService = Components.classes[STRBUNDLE_SERV_CONTRACTID].getService(nsIStringBundleService);
+ } catch (ex) {
+ dump("\n--** strBundleService createInstance failed **--\n");
+ return null;
+ }
+ }
+
+ strBundle = strBundleService.createBundle(path);
+ if (!strBundle) {
+ dump("\n--** strBundle createInstance failed **--\n");
+ }
+ return strBundle;
+}
diff --git a/components/filepicker/nsFilePicker.manifest b/components/filepicker/nsFilePicker.manifest
new file mode 100644
index 000000000..06c2e8956
--- /dev/null
+++ b/components/filepicker/nsFilePicker.manifest
@@ -0,0 +1,4 @@
+component {54ae32f8-1dd2-11b2-a209-df7c505370f8} nsFilePicker.js
+#ifndef MOZ_WIDGET_GTK
+contract @mozilla.org/filepicker;1 {54ae32f8-1dd2-11b2-a209-df7c505370f8}
+#endif
diff --git a/components/filepicker/nsFileView.cpp b/components/filepicker/nsFileView.cpp
new file mode 100644
index 000000000..9a8278496
--- /dev/null
+++ b/components/filepicker/nsFileView.cpp
@@ -0,0 +1,989 @@
+/* -*- 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 "nsIFileView.h"
+#include "nsITreeView.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsITreeSelection.h"
+#include "nsITreeColumns.h"
+#include "nsITreeBoxObject.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "nsPrintfCString.h"
+#include "nsIDateTimeFormat.h"
+#include "nsQuickSort.h"
+#include "nsIAtom.h"
+#include "nsIAutoCompleteResult.h"
+#include "nsIAutoCompleteSearch.h"
+#include "nsISimpleEnumerator.h"
+#include "nsAutoPtr.h"
+#include "nsIMutableArray.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+#include "nsWildCard.h"
+
+class nsIDOMDataTransfer;
+
+#define NS_FILECOMPLETE_CID { 0xcb60980e, 0x18a5, 0x4a77, \
+ { 0x91, 0x10, 0x81, 0x46, 0x61, 0x4c, 0xa7, 0xf0 } }
+#define NS_FILECOMPLETE_CONTRACTID "@mozilla.org/autocomplete/search;1?name=file"
+
+class nsFileResult final : public nsIAutoCompleteResult
+{
+public:
+ // aSearchString is the text typed into the autocomplete widget
+ // aSearchParam is the picker's currently displayed directory
+ nsFileResult(const nsAString& aSearchString, const nsAString& aSearchParam);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTOCOMPLETERESULT
+
+ nsTArray<nsString> mValues;
+ nsString mSearchString;
+ uint16_t mSearchResult;
+private:
+ ~nsFileResult() {}
+};
+
+NS_IMPL_ISUPPORTS(nsFileResult, nsIAutoCompleteResult)
+
+nsFileResult::nsFileResult(const nsAString& aSearchString,
+ const nsAString& aSearchParam):
+ mSearchString(aSearchString)
+{
+ if (aSearchString.IsEmpty())
+ mSearchResult = RESULT_IGNORED;
+ else {
+ int32_t slashPos = mSearchString.RFindChar('/');
+ mSearchResult = RESULT_FAILURE;
+ nsCOMPtr<nsIFile> directory;
+ nsDependentSubstring parent(Substring(mSearchString, 0, slashPos + 1));
+ if (!parent.IsEmpty() && parent.First() == '/')
+ NS_NewLocalFile(parent, true, getter_AddRefs(directory));
+ if (!directory) {
+ if (NS_FAILED(NS_NewLocalFile(aSearchParam, true, getter_AddRefs(directory))))
+ return;
+ if (slashPos > 0)
+ directory->AppendRelativePath(Substring(mSearchString, 0, slashPos));
+ }
+ nsCOMPtr<nsISimpleEnumerator> dirEntries;
+ if (NS_FAILED(directory->GetDirectoryEntries(getter_AddRefs(dirEntries))))
+ return;
+ mSearchResult = RESULT_NOMATCH;
+ bool hasMore = false;
+ nsDependentSubstring prefix(Substring(mSearchString, slashPos + 1));
+ while (NS_SUCCEEDED(dirEntries->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> nextItem;
+ dirEntries->GetNext(getter_AddRefs(nextItem));
+ nsCOMPtr<nsIFile> nextFile(do_QueryInterface(nextItem));
+ nsAutoString fileName;
+ nextFile->GetLeafName(fileName);
+ if (StringBeginsWith(fileName, prefix)) {
+ fileName.Insert(parent, 0);
+ if (mSearchResult == RESULT_NOMATCH && fileName.Equals(mSearchString))
+ mSearchResult = RESULT_IGNORED;
+ else
+ mSearchResult = RESULT_SUCCESS;
+ bool isDirectory = false;
+ nextFile->IsDirectory(&isDirectory);
+ if (isDirectory)
+ fileName.Append('/');
+ mValues.AppendElement(fileName);
+ }
+ }
+ mValues.Sort();
+ }
+}
+
+NS_IMETHODIMP nsFileResult::GetSearchString(nsAString & aSearchString)
+{
+ aSearchString.Assign(mSearchString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFileResult::GetSearchResult(uint16_t *aSearchResult)
+{
+ NS_ENSURE_ARG_POINTER(aSearchResult);
+ *aSearchResult = mSearchResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFileResult::GetDefaultIndex(int32_t *aDefaultIndex)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultIndex);
+ *aDefaultIndex = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFileResult::GetErrorDescription(nsAString & aErrorDescription)
+{
+ aErrorDescription.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFileResult::GetMatchCount(uint32_t *aMatchCount)
+{
+ NS_ENSURE_ARG_POINTER(aMatchCount);
+ *aMatchCount = mValues.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFileResult::GetTypeAheadResult(bool *aTypeAheadResult)
+{
+ NS_ENSURE_ARG_POINTER(aTypeAheadResult);
+ *aTypeAheadResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFileResult::GetValueAt(int32_t index, nsAString & aValue)
+{
+ aValue = mValues[index];
+ if (aValue.Last() == '/')
+ aValue.Truncate(aValue.Length() - 1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFileResult::GetLabelAt(int32_t index, nsAString & aValue)
+{
+ return GetValueAt(index, aValue);
+}
+
+NS_IMETHODIMP nsFileResult::GetCommentAt(int32_t index, nsAString & aComment)
+{
+ aComment.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFileResult::GetStyleAt(int32_t index, nsAString & aStyle)
+{
+ if (mValues[index].Last() == '/')
+ aStyle.AssignLiteral("directory");
+ else
+ aStyle.AssignLiteral("file");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFileResult::GetImageAt(int32_t index, nsAString & aImage)
+{
+ aImage.Truncate();
+ return NS_OK;
+}
+NS_IMETHODIMP nsFileResult::GetFinalCompleteValueAt(int32_t index,
+ nsAString & aValue)
+{
+ return GetValueAt(index, aValue);
+}
+
+NS_IMETHODIMP nsFileResult::RemoveValueAt(int32_t rowIndex, bool removeFromDb)
+{
+ return NS_OK;
+}
+
+class nsFileComplete final : public nsIAutoCompleteSearch
+{
+ ~nsFileComplete() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTOCOMPLETESEARCH
+};
+
+NS_IMPL_ISUPPORTS(nsFileComplete, nsIAutoCompleteSearch)
+
+NS_IMETHODIMP
+nsFileComplete::StartSearch(const nsAString& aSearchString,
+ const nsAString& aSearchParam,
+ nsIAutoCompleteResult *aPreviousResult,
+ nsIAutoCompleteObserver *aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ RefPtr<nsFileResult> result = new nsFileResult(aSearchString, aSearchParam);
+ NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY);
+ return aListener->OnSearchResult(this, result);
+}
+
+NS_IMETHODIMP
+nsFileComplete::StopSearch()
+{
+ return NS_OK;
+}
+
+#define NS_FILEVIEW_CID { 0xa5570462, 0x1dd1, 0x11b2, \
+ { 0x9d, 0x19, 0xdf, 0x30, 0xa2, 0x7f, 0xbd, 0xc4 } }
+
+class nsFileView : public nsIFileView,
+ public nsITreeView
+{
+public:
+ nsFileView();
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFILEVIEW
+ NS_DECL_NSITREEVIEW
+
+protected:
+ virtual ~nsFileView();
+
+ void FilterFiles();
+ void ReverseArray(nsTArray<nsCOMPtr<nsIFile> >& aArray);
+ void SortArray(nsTArray<nsCOMPtr<nsIFile> >& aArray);
+ void SortInternal();
+
+ nsTArray<nsCOMPtr<nsIFile> > mFileList;
+ nsTArray<nsCOMPtr<nsIFile> > mDirList;
+ nsTArray<nsCOMPtr<nsIFile> > mFilteredFiles;
+
+ nsCOMPtr<nsIFile> mDirectoryPath;
+ nsCOMPtr<nsITreeBoxObject> mTree;
+ nsCOMPtr<nsITreeSelection> mSelection;
+ nsCOMPtr<nsIDateTimeFormat> mDateFormatter;
+
+ int16_t mSortType;
+ int32_t mTotalRows;
+
+ nsTArray<char16_t*> mCurrentFilters;
+
+ bool mShowHiddenFiles;
+ bool mDirectoryFilter;
+ bool mReverseSort;
+};
+
+// Factory constructor
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFileComplete)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsFileView, Init)
+NS_DEFINE_NAMED_CID(NS_FILECOMPLETE_CID);
+NS_DEFINE_NAMED_CID(NS_FILEVIEW_CID);
+
+static const mozilla::Module::CIDEntry kFileViewCIDs[] = {
+ { &kNS_FILECOMPLETE_CID, false, nullptr, nsFileCompleteConstructor },
+ { &kNS_FILEVIEW_CID, false, nullptr, nsFileViewConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kFileViewContracts[] = {
+ { NS_FILECOMPLETE_CONTRACTID, &kNS_FILECOMPLETE_CID },
+ { NS_FILEVIEW_CONTRACTID, &kNS_FILEVIEW_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kFileViewModule = {
+ mozilla::Module::kVersion,
+ kFileViewCIDs,
+ kFileViewContracts
+};
+
+NSMODULE_DEFN(nsFileViewModule) = &kFileViewModule;
+
+nsFileView::nsFileView() :
+ mSortType(-1),
+ mTotalRows(0),
+ mShowHiddenFiles(false),
+ mDirectoryFilter(false),
+ mReverseSort(false)
+{
+}
+
+nsFileView::~nsFileView()
+{
+ uint32_t count = mCurrentFilters.Length();
+ for (uint32_t i = 0; i < count; ++i)
+ free(mCurrentFilters[i]);
+}
+
+nsresult
+nsFileView::Init()
+{
+ mDateFormatter = nsIDateTimeFormat::Create();
+ if (!mDateFormatter)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+// nsISupports implementation
+
+NS_IMPL_ISUPPORTS(nsFileView, nsITreeView, nsIFileView)
+
+// nsIFileView implementation
+
+NS_IMETHODIMP
+nsFileView::SetShowHiddenFiles(bool aShowHidden)
+{
+ if (aShowHidden != mShowHiddenFiles) {
+ mShowHiddenFiles = aShowHidden;
+
+ // This could be better optimized, but since the hidden
+ // file functionality is not currently used, this will be fine.
+ SetDirectory(mDirectoryPath);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetShowHiddenFiles(bool* aShowHidden)
+{
+ *aShowHidden = mShowHiddenFiles;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::SetShowOnlyDirectories(bool aOnlyDirs)
+{
+ if (aOnlyDirs == mDirectoryFilter)
+ return NS_OK;
+
+ mDirectoryFilter = aOnlyDirs;
+ uint32_t dirCount = mDirList.Length();
+ if (mDirectoryFilter) {
+ int32_t rowDiff = mTotalRows - dirCount;
+
+ mFilteredFiles.Clear();
+ mTotalRows = dirCount;
+ if (mTree)
+ mTree->RowCountChanged(mTotalRows, -rowDiff);
+ } else {
+ // Run the filter again to get the file list back
+ FilterFiles();
+
+ SortArray(mFilteredFiles);
+ if (mReverseSort)
+ ReverseArray(mFilteredFiles);
+
+ if (mTree)
+ mTree->RowCountChanged(dirCount, mTotalRows - dirCount);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetShowOnlyDirectories(bool* aOnlyDirs)
+{
+ *aOnlyDirs = mDirectoryFilter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetSortType(int16_t* aSortType)
+{
+ *aSortType = mSortType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetReverseSort(bool* aReverseSort)
+{
+ *aReverseSort = mReverseSort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::Sort(int16_t aSortType, bool aReverseSort)
+{
+ if (aSortType == mSortType) {
+ if (aReverseSort == mReverseSort)
+ return NS_OK;
+
+ mReverseSort = aReverseSort;
+ ReverseArray(mDirList);
+ ReverseArray(mFilteredFiles);
+ } else {
+ mSortType = aSortType;
+ mReverseSort = aReverseSort;
+ SortInternal();
+ }
+
+ if (mTree)
+ mTree->Invalidate();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::SetDirectory(nsIFile* aDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aDirectory);
+
+ nsCOMPtr<nsISimpleEnumerator> dirEntries;
+ aDirectory->GetDirectoryEntries(getter_AddRefs(dirEntries));
+
+ if (!dirEntries) {
+ // Couldn't read in the directory, this can happen if the user does not
+ // have permission to list it.
+ return NS_ERROR_FAILURE;
+ }
+
+ mDirectoryPath = aDirectory;
+ mFileList.Clear();
+ mDirList.Clear();
+
+ bool hasMore = false;
+
+ while (NS_SUCCEEDED(dirEntries->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> nextItem;
+ dirEntries->GetNext(getter_AddRefs(nextItem));
+ nsCOMPtr<nsIFile> theFile = do_QueryInterface(nextItem);
+
+ bool isDirectory = false;
+ if (theFile) {
+ theFile->IsDirectory(&isDirectory);
+
+ if (isDirectory) {
+ bool isHidden;
+ theFile->IsHidden(&isHidden);
+ if (mShowHiddenFiles || !isHidden) {
+ mDirList.AppendElement(theFile);
+ }
+ }
+ else {
+ mFileList.AppendElement(theFile);
+ }
+ }
+ }
+
+ if (mTree) {
+ mTree->BeginUpdateBatch();
+ mTree->RowCountChanged(0, -mTotalRows);
+ }
+
+ FilterFiles();
+ SortInternal();
+
+ if (mTree) {
+ mTree->EndUpdateBatch();
+ mTree->ScrollToRow(0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::SetFilter(const nsAString& aFilterString)
+{
+ uint32_t filterCount = mCurrentFilters.Length();
+ for (uint32_t i = 0; i < filterCount; ++i)
+ free(mCurrentFilters[i]);
+ mCurrentFilters.Clear();
+
+ nsAString::const_iterator start, iter, end;
+ aFilterString.BeginReading(iter);
+ aFilterString.EndReading(end);
+
+ while (true) {
+ // skip over delimiters
+ while (iter != end && (*iter == ';' || *iter == ' '))
+ ++iter;
+
+ if (iter == end)
+ break;
+
+ start = iter; // start of a filter
+
+ // we know this is neither ';' nor ' ', skip to next char
+ ++iter;
+
+ // find next delimiter or end of string
+ while (iter != end && (*iter != ';' && *iter != ' '))
+ ++iter;
+
+ char16_t* filter = ToNewUnicode(Substring(start, iter));
+ if (!filter)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (!mCurrentFilters.AppendElement(filter)) {
+ free(filter);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (iter == end)
+ break;
+
+ ++iter; // we know this is either ';' or ' ', skip to next char
+ }
+
+ if (mTree) {
+ mTree->BeginUpdateBatch();
+ uint32_t count = mDirList.Length();
+ mTree->RowCountChanged(count, count - mTotalRows);
+ }
+
+ mFilteredFiles.Clear();
+
+ FilterFiles();
+
+ SortArray(mFilteredFiles);
+ if (mReverseSort)
+ ReverseArray(mFilteredFiles);
+
+ if (mTree)
+ mTree->EndUpdateBatch();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetSelectedFiles(nsIArray** aFiles)
+{
+ *aFiles = nullptr;
+ if (!mSelection)
+ return NS_OK;
+
+ int32_t numRanges;
+ mSelection->GetRangeCount(&numRanges);
+
+ uint32_t dirCount = mDirList.Length();
+ nsCOMPtr<nsIMutableArray> fileArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+ NS_ENSURE_STATE(fileArray);
+
+ for (int32_t range = 0; range < numRanges; ++range) {
+ int32_t rangeBegin, rangeEnd;
+ mSelection->GetRangeAt(range, &rangeBegin, &rangeEnd);
+
+ for (int32_t itemIndex = rangeBegin; itemIndex <= rangeEnd; ++itemIndex) {
+ nsIFile* curFile = nullptr;
+
+ if (itemIndex < (int32_t) dirCount)
+ curFile = mDirList[itemIndex];
+ else {
+ if (itemIndex < mTotalRows)
+ curFile = mFilteredFiles[itemIndex - dirCount];
+ }
+
+ if (curFile)
+ fileArray->AppendElement(curFile, false);
+ }
+ }
+
+ fileArray.forget(aFiles);
+ return NS_OK;
+}
+
+
+// nsITreeView implementation
+
+NS_IMETHODIMP
+nsFileView::GetRowCount(int32_t* aRowCount)
+{
+ *aRowCount = mTotalRows;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetSelection(nsITreeSelection** aSelection)
+{
+ *aSelection = mSelection;
+ NS_IF_ADDREF(*aSelection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::SetSelection(nsITreeSelection* aSelection)
+{
+ mSelection = aSelection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetRowProperties(int32_t aIndex, nsAString& aProps)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetCellProperties(int32_t aRow, nsITreeColumn* aCol,
+ nsAString& aProps)
+{
+ uint32_t dirCount = mDirList.Length();
+
+ if (aRow < (int32_t) dirCount)
+ aProps.AppendLiteral("directory");
+ else if (aRow < mTotalRows)
+ aProps.AppendLiteral("file");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetColumnProperties(nsITreeColumn* aCol, nsAString& aProps)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::IsContainer(int32_t aIndex, bool* aIsContainer)
+{
+ *aIsContainer = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::IsContainerOpen(int32_t aIndex, bool* aIsOpen)
+{
+ *aIsOpen = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::IsContainerEmpty(int32_t aIndex, bool* aIsEmpty)
+{
+ *aIsEmpty = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::IsSeparator(int32_t aIndex, bool* aIsSeparator)
+{
+ *aIsSeparator = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::IsSorted(bool* aIsSorted)
+{
+ *aIsSorted = (mSortType >= 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::CanDrop(int32_t aIndex, int32_t aOrientation,
+ nsIDOMDataTransfer* dataTransfer, bool* aCanDrop)
+{
+ *aCanDrop = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::Drop(int32_t aRow, int32_t aOrientation, nsIDOMDataTransfer* dataTransfer)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetParentIndex(int32_t aRowIndex, int32_t* aParentIndex)
+{
+ *aParentIndex = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex,
+ bool* aHasSibling)
+{
+ *aHasSibling = (aAfterIndex < (mTotalRows - 1));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetLevel(int32_t aIndex, int32_t* aLevel)
+{
+ *aLevel = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetImageSrc(int32_t aRow, nsITreeColumn* aCol,
+ nsAString& aImageSrc)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetProgressMode(int32_t aRow, nsITreeColumn* aCol,
+ int32_t* aProgressMode)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetCellValue(int32_t aRow, nsITreeColumn* aCol,
+ nsAString& aCellValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::GetCellText(int32_t aRow, nsITreeColumn* aCol,
+ nsAString& aCellText)
+{
+ uint32_t dirCount = mDirList.Length();
+ bool isDirectory;
+ nsIFile* curFile = nullptr;
+
+ if (aRow < (int32_t) dirCount) {
+ isDirectory = true;
+ curFile = mDirList[aRow];
+ } else if (aRow < mTotalRows) {
+ isDirectory = false;
+ curFile = mFilteredFiles[aRow - dirCount];
+ } else {
+ // invalid row
+ aCellText.SetCapacity(0);
+ return NS_OK;
+ }
+
+ const char16_t* colID;
+ aCol->GetIdConst(&colID);
+ if (NS_LITERAL_STRING("FilenameColumn").Equals(colID)) {
+ curFile->GetLeafName(aCellText);
+ } else if (NS_LITERAL_STRING("LastModifiedColumn").Equals(colID)) {
+ PRTime lastModTime;
+ curFile->GetLastModifiedTime(&lastModTime);
+ // XXX FormatPRTime could take an nsAString&
+ nsAutoString temp;
+ mDateFormatter->FormatPRTime(nullptr, kDateFormatShort, kTimeFormatSeconds,
+ lastModTime * 1000, temp);
+ aCellText = temp;
+ } else {
+ // file size
+ if (isDirectory)
+ aCellText.SetCapacity(0);
+ else {
+ int64_t fileSize;
+ curFile->GetFileSize(&fileSize);
+ CopyUTF8toUTF16(nsPrintfCString("%lld", fileSize), aCellText);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::SetTree(nsITreeBoxObject* aTree)
+{
+ mTree = aTree;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::ToggleOpenState(int32_t aIndex)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::CycleHeader(nsITreeColumn* aCol)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::SelectionChanged()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::CycleCell(int32_t aRow, nsITreeColumn* aCol)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::IsEditable(int32_t aRow, nsITreeColumn* aCol,
+ bool* aIsEditable)
+{
+ *aIsEditable = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::IsSelectable(int32_t aRow, nsITreeColumn* aCol,
+ bool* aIsSelectable)
+{
+ *aIsSelectable = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::SetCellValue(int32_t aRow, nsITreeColumn* aCol,
+ const nsAString& aValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::SetCellText(int32_t aRow, nsITreeColumn* aCol,
+ const nsAString& aValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::PerformAction(const char16_t* aAction)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::PerformActionOnRow(const char16_t* aAction, int32_t aRow)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileView::PerformActionOnCell(const char16_t* aAction, int32_t aRow,
+ nsITreeColumn* aCol)
+{
+ return NS_OK;
+}
+
+// Private methods
+
+void
+nsFileView::FilterFiles()
+{
+ uint32_t count = mDirList.Length();
+ mTotalRows = count;
+ count = mFileList.Length();
+ mFilteredFiles.Clear();
+ uint32_t filterCount = mCurrentFilters.Length();
+
+ for (uint32_t i = 0; i < count; ++i) {
+ nsIFile* file = mFileList[i];
+ bool isHidden = false;
+ if (!mShowHiddenFiles)
+ file->IsHidden(&isHidden);
+
+ nsAutoString ucsLeafName;
+ if(NS_FAILED(file->GetLeafName(ucsLeafName))) {
+ // need to check return value for GetLeafName()
+ continue;
+ }
+
+ if (!isHidden) {
+ for (uint32_t j = 0; j < filterCount; ++j) {
+ bool matched = false;
+ if (!nsCRT::strcmp(mCurrentFilters.ElementAt(j),
+ u"..apps"))
+ {
+ file->IsExecutable(&matched);
+ } else
+ matched = (NS_WildCardMatch(ucsLeafName.get(),
+ mCurrentFilters.ElementAt(j),
+ true) == MATCH);
+
+ if (matched) {
+ mFilteredFiles.AppendElement(file);
+ ++mTotalRows;
+ break;
+ }
+ }
+ }
+ }
+}
+
+void
+nsFileView::ReverseArray(nsTArray<nsCOMPtr<nsIFile> >& aArray)
+{
+ uint32_t count = aArray.Length();
+ for (uint32_t i = 0; i < count/2; ++i) {
+ // If we get references to the COMPtrs in the array, and then .swap() them
+ // we avoid AdRef() / Release() calls.
+ nsCOMPtr<nsIFile>& element = aArray[i];
+ nsCOMPtr<nsIFile>& element2 = aArray[count - i - 1];
+ element.swap(element2);
+ }
+}
+
+static int
+SortNameCallback(const void* aElement1, const void* aElement2, void* aContext)
+{
+ nsIFile* file1 = *static_cast<nsIFile* const *>(aElement1);
+ nsIFile* file2 = *static_cast<nsIFile* const *>(aElement2);
+
+ nsAutoString leafName1, leafName2;
+ file1->GetLeafName(leafName1);
+ file2->GetLeafName(leafName2);
+
+ return Compare(leafName1, leafName2);
+}
+
+static int
+SortSizeCallback(const void* aElement1, const void* aElement2, void* aContext)
+{
+ nsIFile* file1 = *static_cast<nsIFile* const *>(aElement1);
+ nsIFile* file2 = *static_cast<nsIFile* const *>(aElement2);
+
+ int64_t size1, size2;
+ file1->GetFileSize(&size1);
+ file2->GetFileSize(&size2);
+
+ if (size1 == size2)
+ return 0;
+
+ return size1 < size2 ? -1 : 1;
+}
+
+static int
+SortDateCallback(const void* aElement1, const void* aElement2, void* aContext)
+{
+ nsIFile* file1 = *static_cast<nsIFile* const *>(aElement1);
+ nsIFile* file2 = *static_cast<nsIFile* const *>(aElement2);
+
+ PRTime time1, time2;
+ file1->GetLastModifiedTime(&time1);
+ file2->GetLastModifiedTime(&time2);
+
+ if (time1 == time2)
+ return 0;
+
+ return time1 < time2 ? -1 : 1;
+}
+
+void
+nsFileView::SortArray(nsTArray<nsCOMPtr<nsIFile> >& aArray)
+{
+ // We assume the array to be in filesystem order, which
+ // for our purposes, is completely unordered.
+
+ int (*compareFunc)(const void*, const void*, void*);
+
+ switch (mSortType) {
+ case sortName:
+ compareFunc = SortNameCallback;
+ break;
+ case sortSize:
+ compareFunc = SortSizeCallback;
+ break;
+ case sortDate:
+ compareFunc = SortDateCallback;
+ break;
+ default:
+ return;
+ }
+
+ uint32_t count = aArray.Length();
+
+ nsIFile** array = new nsIFile*[count];
+ for (uint32_t i = 0; i < count; ++i) {
+ array[i] = aArray[i];
+ }
+
+ NS_QuickSort(array, count, sizeof(nsIFile*), compareFunc, nullptr);
+
+ for (uint32_t i = 0; i < count; ++i) {
+ // Use swap() to avoid refcounting.
+ aArray[i].swap(array[i]);
+ }
+
+ delete[] array;
+}
+
+void
+nsFileView::SortInternal()
+{
+ SortArray(mDirList);
+ SortArray(mFilteredFiles);
+
+ if (mReverseSort) {
+ ReverseArray(mDirList);
+ ReverseArray(mFilteredFiles);
+ }
+}
diff --git a/components/filepicker/nsIFileView.idl b/components/filepicker/nsIFileView.idl
new file mode 100644
index 000000000..4862a594e
--- /dev/null
+++ b/components/filepicker/nsIFileView.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: IDL; 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 nsIArray;
+interface nsIFile;
+
+[scriptable, uuid(60b320d2-1dd2-11b2-bd73-dc3575f78ddd)]
+interface nsIFileView : nsISupports
+{
+ const short sortName = 0;
+ const short sortSize = 1;
+ const short sortDate = 2;
+
+ attribute boolean showHiddenFiles;
+ attribute boolean showOnlyDirectories;
+ readonly attribute short sortType;
+ readonly attribute boolean reverseSort;
+
+ void sort(in short sortType, in boolean reverseSort);
+ void setDirectory(in nsIFile directory);
+ void setFilter(in AString filterString);
+
+ readonly attribute nsIArray selectedFiles;
+};
+
+%{C++
+
+#define NS_FILEVIEW_CONTRACTID "@mozilla.org/filepicker/fileview;1"
+
+%}
diff --git a/components/filewatcher/NativeFileWatcherNotSupported.h b/components/filewatcher/NativeFileWatcherNotSupported.h
new file mode 100644
index 000000000..6b1aa97ba
--- /dev/null
+++ b/components/filewatcher/NativeFileWatcherNotSupported.h
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 mozilla_nativefilewatcher_h__
+#define mozilla_nativefilewatcher_h__
+
+#include "nsINativeFileWatcher.h"
+
+namespace mozilla {
+
+class NativeFileWatcherService final : public nsINativeFileWatcherService
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ NativeFileWatcherService()
+ {
+ };
+
+ nsresult Init()
+ {
+ return NS_OK;
+ };
+
+ NS_IMETHOD AddPath(const nsAString& aPathToWatch,
+ nsINativeFileWatcherCallback* aOnChange,
+ nsINativeFileWatcherErrorCallback* aOnError,
+ nsINativeFileWatcherSuccessCallback* aOnSuccess) override
+ {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ };
+
+ NS_IMETHOD RemovePath(const nsAString& aPathToRemove,
+ nsINativeFileWatcherCallback* aOnChange,
+ nsINativeFileWatcherErrorCallback* aOnError,
+ nsINativeFileWatcherSuccessCallback* aOnSuccess) override
+ {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ };
+
+private:
+ ~NativeFileWatcherService() { };
+ NativeFileWatcherService(const NativeFileWatcherService& other) = delete;
+ void operator=(const NativeFileWatcherService& other) = delete;
+};
+
+NS_IMPL_ISUPPORTS(NativeFileWatcherService, nsINativeFileWatcherService);
+
+} // namespace mozilla
+
+#endif // mozilla_nativefilewatcher_h__
diff --git a/components/filewatcher/NativeFileWatcherWin.cpp b/components/filewatcher/NativeFileWatcherWin.cpp
new file mode 100644
index 000000000..1e5bcf9e7
--- /dev/null
+++ b/components/filewatcher/NativeFileWatcherWin.cpp
@@ -0,0 +1,1493 @@
+/* -*- 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/. */
+
+/**
+ * Native implementation of Watcher operations.
+ */
+#include "NativeFileWatcherWin.h"
+
+#include "mozilla/Services.h"
+#include "mozilla/UniquePtr.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsILocalFile.h"
+#include "nsIObserverService.h"
+#include "nsProxyRelease.h"
+#include "nsTArray.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+
+// Enclose everything which is not exported in an anonymous namespace.
+namespace {
+
+/**
+ * An event used to notify the main thread when an error happens.
+ */
+class WatchedErrorEvent final : public Runnable
+{
+public:
+ /**
+ * @param aOnError The passed error callback.
+ * @param aError The |nsresult| error value.
+ * @param osError The error returned by GetLastError().
+ */
+ WatchedErrorEvent(const nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback>& aOnError,
+ const nsresult& anError, const DWORD& osError)
+ : mOnError(aOnError)
+ , mError(anError)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Make sure we wrap a valid callback since it's not mandatory to provide
+ // one when watching a resource.
+ if (mOnError) {
+ (void)mOnError->Complete(mError, mOsError);
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback> mOnError;
+ nsresult mError;
+ DWORD mOsError;
+};
+
+/**
+ * An event used to notify the main thread when an operation is successful.
+ */
+class WatchedSuccessEvent final : public Runnable
+{
+public:
+ /**
+ * @param aOnSuccess The passed success callback.
+ * @param aResourcePath
+ * The path of the resource for which this event was generated.
+ */
+ WatchedSuccessEvent(const nsMainThreadPtrHandle<nsINativeFileWatcherSuccessCallback>& aOnSuccess,
+ const nsAString& aResourcePath)
+ : mOnSuccess(aOnSuccess)
+ , mResourcePath(aResourcePath)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Make sure we wrap a valid callback since it's not mandatory to provide
+ // one when watching a resource.
+ if (mOnSuccess) {
+ (void)mOnSuccess->Complete(mResourcePath);
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ nsMainThreadPtrHandle<nsINativeFileWatcherSuccessCallback> mOnSuccess;
+ nsString mResourcePath;
+};
+
+/**
+ * An event used to notify the main thread of a change in a watched
+ * resource.
+ */
+class WatchedChangeEvent final : public Runnable
+{
+public:
+ /**
+ * @param aOnChange The passed change callback.
+ * @param aChangedResource The name of the changed resource.
+ */
+ WatchedChangeEvent(const nsMainThreadPtrHandle<nsINativeFileWatcherCallback>& aOnChange,
+ const nsAString& aChangedResource)
+ : mOnChange(aOnChange)
+ , mChangedResource(aChangedResource)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // The second parameter is reserved for future uses: we use 0 as a placeholder.
+ (void)mOnChange->Changed(mChangedResource, 0);
+ return NS_OK;
+ }
+
+private:
+ nsMainThreadPtrHandle<nsINativeFileWatcherCallback> mOnChange;
+ nsString mChangedResource;
+};
+
+static mozilla::LazyLogModule gNativeWatcherPRLog("NativeFileWatcherService");
+#define FILEWATCHERLOG(...) MOZ_LOG(gNativeWatcherPRLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+// The number of notifications to store within WatchedResourceDescriptor:mNotificationBuffer.
+// If the buffer overflows, its contents are discarded and a change callback is dispatched
+// with "*" as changed path.
+const unsigned int WATCHED_RES_MAXIMUM_NOTIFICATIONS = 100;
+
+// The size, in bytes, of the notification buffer used to store the changes notifications
+// for each watched resource.
+const size_t NOTIFICATION_BUFFER_SIZE =
+ WATCHED_RES_MAXIMUM_NOTIFICATIONS * sizeof(FILE_NOTIFY_INFORMATION);
+
+/**
+ * AutoCloseHandle is a RAII wrapper for Windows |HANDLE|s
+ */
+struct AutoCloseHandleTraits
+{
+ typedef HANDLE type;
+ static type empty() { return INVALID_HANDLE_VALUE; }
+ static void release(type anHandle)
+ {
+ if (anHandle != INVALID_HANDLE_VALUE) {
+ // If CancelIo is called on an |HANDLE| not yet associated to a Completion I/O
+ // it simply does nothing.
+ (void)CancelIo(anHandle);
+ (void)CloseHandle(anHandle);
+ }
+ }
+};
+typedef Scoped<AutoCloseHandleTraits> AutoCloseHandle;
+
+// Define these callback array types to make the code easier to read.
+typedef nsTArray<nsMainThreadPtrHandle<nsINativeFileWatcherCallback>> ChangeCallbackArray;
+typedef nsTArray<nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback>> ErrorCallbackArray;
+
+/**
+ * A structure to keep track of the information related to a
+ * watched resource.
+ */
+struct WatchedResourceDescriptor {
+ // The path on the file system of the watched resource.
+ nsString mPath;
+
+ // A buffer containing the latest notifications received for the resource.
+ // UniquePtr<FILE_NOTIFY_INFORMATION> cannot be used as the structure
+ // contains a variable length field (FileName).
+ UniquePtr<unsigned char> mNotificationBuffer;
+
+ // Used to hold information for the asynchronous ReadDirectoryChangesW call
+ // (does not need to be closed as it is not an |HANDLE|).
+ OVERLAPPED mOverlappedInfo;
+
+ // The OS handle to the watched resource.
+ AutoCloseHandle mResourceHandle;
+
+ WatchedResourceDescriptor(const nsAString& aPath, const HANDLE anHandle)
+ : mPath(aPath)
+ , mResourceHandle(anHandle)
+ {
+ memset(&mOverlappedInfo, 0, sizeof(OVERLAPPED));
+ mNotificationBuffer.reset(new unsigned char[NOTIFICATION_BUFFER_SIZE]);
+ }
+};
+
+/**
+ * A structure used to pass the callbacks to the AddPathRunnableMethod() and
+ * RemovePathRunnableMethod().
+ */
+struct PathRunnablesParametersWrapper {
+ nsString mPath;
+ nsMainThreadPtrHandle<nsINativeFileWatcherCallback> mChangeCallbackHandle;
+ nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback> mErrorCallbackHandle;
+ nsMainThreadPtrHandle<nsINativeFileWatcherSuccessCallback> mSuccessCallbackHandle;
+
+ PathRunnablesParametersWrapper(
+ const nsAString& aPath,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherCallback>& aOnChange,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback>& aOnError,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherSuccessCallback>& aOnSuccess)
+ : mPath(aPath)
+ , mChangeCallbackHandle(aOnChange)
+ , mErrorCallbackHandle(aOnError)
+ , mSuccessCallbackHandle(aOnSuccess)
+ {
+ }
+};
+
+/**
+ * This runnable is dispatched to the main thread in order to safely
+ * shutdown the worker thread.
+ */
+class NativeWatcherIOShutdownTask : public Runnable
+{
+public:
+ NativeWatcherIOShutdownTask()
+ : mWorkerThread(do_GetCurrentThread())
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mWorkerThread->Shutdown();
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIThread> mWorkerThread;
+};
+
+/**
+ * This runnable is dispatched from the main thread to get the notifications of the
+ * changes in the watched resources by continuously calling the blocking function
+ * GetQueuedCompletionStatus. This function queries the status of the Completion I/O
+ * port initialized in the main thread. The watched resources are registered to the
+ * completion I/O port when calling |addPath|.
+ *
+ * Instead of using a loop within the Run() method, the Runnable reschedules itself
+ * by issuing a NS_DispatchToCurrentThread(this) before exiting. This is done to allow
+ * the execution of other runnables enqueued within the thread task queue.
+ */
+class NativeFileWatcherIOTask : public Runnable
+{
+public:
+ NativeFileWatcherIOTask(HANDLE aIOCompletionPort)
+ : mIOCompletionPort(aIOCompletionPort)
+ , mShuttingDown(false)
+ {
+ }
+
+ NS_IMETHOD Run();
+ nsresult AddPathRunnableMethod(PathRunnablesParametersWrapper* aWrappedParameters);
+ nsresult RemovePathRunnableMethod(PathRunnablesParametersWrapper* aWrappedParameters);
+ nsresult DeactivateRunnableMethod();
+
+private:
+ // Maintain 2 indexes - one by resource path, one by resource |HANDLE|.
+ // Since |HANDLE| is basically a typedef to void*, we use nsVoidPtrHashKey to
+ // compute the hashing key. We need 2 indexes in order to quickly look up the
+ // changed resource in the Worker Thread.
+ // The objects are not ref counted and get destroyed by mWatchedResourcesByPath
+ // on NativeFileWatcherService::Destroy or in NativeFileWatcherService::RemovePath.
+ nsClassHashtable<nsStringHashKey, WatchedResourceDescriptor> mWatchedResourcesByPath;
+ nsDataHashtable<nsVoidPtrHashKey, WatchedResourceDescriptor*> mWatchedResourcesByHandle;
+
+ // The same callback can be associated to multiple watches so we need to keep
+ // them alive as long as there is a watch using them. We create two hashtables
+ // to map directory names to lists of nsMainThreadPtr<callbacks>.
+ nsClassHashtable<nsStringHashKey, ChangeCallbackArray> mChangeCallbacksTable;
+ nsClassHashtable<nsStringHashKey, ErrorCallbackArray> mErrorCallbacksTable;
+
+ // We hold a copy of the completion port |HANDLE|, which is owned by the main thread.
+ HANDLE mIOCompletionPort;
+
+ // Other methods need to know that a shutdown is in progress.
+ bool mShuttingDown;
+
+ nsresult RunInternal();
+
+ nsresult DispatchChangeCallbacks(WatchedResourceDescriptor* aResourceDescriptor,
+ const nsAString& aChangedResource);
+
+ nsresult ReportChange(
+ const nsMainThreadPtrHandle<nsINativeFileWatcherCallback>& aOnChange,
+ const nsAString& aChangedResource);
+
+ nsresult DispatchErrorCallbacks(WatchedResourceDescriptor* aResourceDescriptor,
+ nsresult anError, DWORD anOSError);
+
+ nsresult ReportError(
+ const nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback>& aOnError,
+ nsresult anError, DWORD anOSError);
+
+ nsresult ReportSuccess(
+ const nsMainThreadPtrHandle<nsINativeFileWatcherSuccessCallback>& aOnSuccess,
+ const nsAString& aResourcePath);
+
+ nsresult AddDirectoryToWatchList(WatchedResourceDescriptor* aDirectoryDescriptor);
+
+ void AppendCallbacksToHashtables(
+ const nsAString& aPath,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherCallback>& aOnChange,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback>& aOnError);
+
+ void RemoveCallbacksFromHashtables(
+ const nsAString& aPath,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherCallback>& aOnChange,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback>& aOnError);
+
+ nsresult MakeResourcePath(
+ WatchedResourceDescriptor* changedDescriptor,
+ const nsAString& resourceName,
+ nsAString& nativeResourcePath);
+};
+
+/**
+ * The watching thread logic.
+ *
+ * @return NS_OK if the watcher loop must be rescheduled, a failure code
+ * if it must not.
+ */
+nsresult
+NativeFileWatcherIOTask::RunInternal()
+{
+ // Contains the address of the |OVERLAPPED| structure passed
+ // to ReadDirectoryChangesW (used to check for |HANDLE| closing).
+ OVERLAPPED* overlappedStructure;
+
+ // The number of bytes transferred by GetQueuedCompletionStatus
+ // (used to check for |HANDLE| closing).
+ DWORD transferredBytes = 0;
+
+ // Will hold the |HANDLE| to the watched resource returned by GetQueuedCompletionStatus
+ // which generated the change events.
+ ULONG_PTR changedResourceHandle = 0;
+
+ // Check for changes in the resource status by querying the |mIOCompletionPort|
+ // (blocking). GetQueuedCompletionStatus is always called before the first call
+ // to ReadDirectoryChangesW. This isn't a problem, since mIOCompletionPort is
+ // already a valid |HANDLE| even though it doesn't have any associated notification
+ // handles (through ReadDirectoryChangesW).
+ if (!GetQueuedCompletionStatus(mIOCompletionPort, &transferredBytes,
+ &changedResourceHandle, &overlappedStructure,
+ INFINITE)) {
+ // Ok, there was some error.
+ DWORD errCode = GetLastError();
+ switch (errCode) {
+ case ERROR_NOTIFY_ENUM_DIR: {
+ // There were too many changes and the notification buffer has overflowed.
+ // We dispatch the special value "*" and reschedule.
+ FILEWATCHERLOG("NativeFileWatcherIOTask::Run - Notification buffer has overflowed");
+
+ WatchedResourceDescriptor* changedRes =
+ mWatchedResourcesByHandle.Get((HANDLE)changedResourceHandle);
+
+ nsresult rv = DispatchChangeCallbacks(changedRes, NS_LITERAL_STRING("*"));
+ if (NS_FAILED(rv)) {
+ // We failed to dispatch the error callbacks. Something very
+ // bad happened to the main thread, so we bail out from the watcher thread.
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::Run - Failed to dispatch change callbacks (%x).",
+ rv);
+ return rv;
+ }
+
+ return NS_OK;
+ }
+ case ERROR_ABANDONED_WAIT_0:
+ case ERROR_INVALID_HANDLE: {
+ // If we reach this point, mIOCompletionPort was probably closed
+ // and we need to close this thread. This condition is identified
+ // by catching the ERROR_INVALID_HANDLE error.
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::Run - The completion port was closed (%x).",
+ errCode);
+ return NS_ERROR_ABORT;
+ }
+ case ERROR_OPERATION_ABORTED: {
+ // Some path was unwatched! That's not really an error, now it is safe
+ // to free the memory for the resource and call GetQueuedCompletionStatus
+ // again.
+ FILEWATCHERLOG("NativeFileWatcherIOTask::Run - Path unwatched (%x).", errCode);
+
+ WatchedResourceDescriptor* toFree =
+ mWatchedResourcesByHandle.Get((HANDLE)changedResourceHandle);
+
+ if (toFree) {
+ // Take care of removing the resource and freeing the memory
+
+ mWatchedResourcesByHandle.Remove((HANDLE)changedResourceHandle);
+
+ // This last call eventually frees the memory
+ mWatchedResourcesByPath.Remove(toFree->mPath);
+ }
+
+ return NS_OK;
+ }
+ default: {
+ // It should probably never get here, but it's better to be safe.
+ FILEWATCHERLOG("NativeFileWatcherIOTask::Run - Unknown error (%x).", errCode);
+
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+
+ // When an |HANDLE| associated to the completion I/O port is gracefully
+ // closed, GetQueuedCompletionStatus still may return a status update. Moreover,
+ // this can also be triggered when watching files on a network folder and losing
+ // the connection.
+ // That's an edge case we need to take care of for consistency by checking
+ // for (!transferredBytes && overlappedStructure). See http://xania.org/200807/iocp
+ if (!transferredBytes &&
+ (overlappedStructure ||
+ (!overlappedStructure && !changedResourceHandle))) {
+ // Note: if changedResourceHandle is nullptr as well, the wait on the Completion
+ // I/O was interrupted by a call to PostQueuedCompletionStatus with 0 transferred
+ // bytes and nullptr as |OVERLAPPED| and |HANDLE|. This is done to allow addPath
+ // and removePath to work on this thread.
+ return NS_OK;
+ }
+
+ // Check to see which resource is changedResourceHandle.
+ WatchedResourceDescriptor* changedRes =
+ mWatchedResourcesByHandle.Get((HANDLE)changedResourceHandle);
+ MOZ_ASSERT(changedRes, "Could not find the changed resource in the list of watched ones.");
+
+ // Parse the changes and notify the main thread.
+ const unsigned char* rawNotificationBuffer = changedRes->mNotificationBuffer.get();
+
+ while (true) {
+ FILE_NOTIFY_INFORMATION* notificationInfo =
+ (FILE_NOTIFY_INFORMATION*)rawNotificationBuffer;
+
+ // FileNameLength is in bytes, but we need FileName length
+ // in characters, so divide it by sizeof(WCHAR).
+ nsAutoString resourceName(notificationInfo->FileName,
+ notificationInfo->FileNameLength / sizeof(WCHAR));
+
+ // Handle path normalisation using nsILocalFile.
+ nsString resourcePath;
+ nsresult rv = MakeResourcePath(changedRes, resourceName, resourcePath);
+ if (NS_SUCCEEDED(rv)) {
+ rv = DispatchChangeCallbacks(changedRes, resourcePath);
+ if (NS_FAILED(rv)) {
+ // Log that we failed to dispatch the change callbacks.
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::Run - Failed to dispatch change callbacks (%x).",
+ rv);
+ return rv;
+ }
+ }
+
+ if (!notificationInfo->NextEntryOffset) {
+ break;
+ }
+
+ rawNotificationBuffer += notificationInfo->NextEntryOffset;
+ }
+
+ // We need to keep watching for further changes.
+ nsresult rv = AddDirectoryToWatchList(changedRes);
+ if (NS_FAILED(rv)) {
+ // We failed to watch the folder.
+ if (rv == NS_ERROR_ABORT) {
+ // Log that we also failed to dispatch the error callbacks.
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::Run - Failed to watch %s and"
+ " to dispatch the related error callbacks", changedRes->mPath.get());
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Wraps the watcher logic and takes care of rescheduling
+ * the watcher loop based on the return code of |RunInternal|
+ * in order to help with code readability.
+ *
+ * @return NS_OK or a failure error code from |NS_DispatchToCurrentThread|.
+ */
+NS_IMETHODIMP
+NativeFileWatcherIOTask::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // We return immediately if |mShuttingDown| is true (see below for
+ // details about the shutdown protocol being followed).
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+
+ nsresult rv = RunInternal();
+ if (NS_FAILED(rv)) {
+ // A critical error occurred in the watcher loop, don't reschedule.
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::Run - Stopping the watcher loop (error %S)", rv);
+
+ // We log the error but return NS_OK instead: we don't want to
+ // propagate an exception through XPCOM.
+ return NS_OK;
+ }
+
+ // No error occurred, reschedule.
+ return NS_DispatchToCurrentThread(this);
+}
+
+/**
+ * Adds the resource to the watched list. This function is enqueued on the worker
+ * thread by NativeFileWatcherService::AddPath. All the errors are reported to the main
+ * thread using the error callback function mErrorCallback.
+ *
+ * @param pathToWatch
+ * The path of the resource to watch for changes.
+ *
+ * @return NS_ERROR_FILE_NOT_FOUND if the path is invalid or does not exist.
+ * Returns NS_ERROR_UNEXPECTED if OS |HANDLE|s are unexpectedly closed.
+ * If the ReadDirectoryChangesW call fails, returns NS_ERROR_FAILURE,
+ * otherwise NS_OK.
+ */
+nsresult
+NativeFileWatcherIOTask::AddPathRunnableMethod(
+ PathRunnablesParametersWrapper* aWrappedParameters)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsAutoPtr<PathRunnablesParametersWrapper> wrappedParameters(aWrappedParameters);
+
+ // We return immediately if |mShuttingDown| is true (see below for
+ // details about the shutdown protocol being followed).
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+
+ if (!wrappedParameters ||
+ !wrappedParameters->mChangeCallbackHandle) {
+ FILEWATCHERLOG("NativeFileWatcherIOTask::AddPathRunnableMethod - Invalid arguments.");
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // Is aPathToWatch already being watched?
+ WatchedResourceDescriptor* watchedResource =
+ mWatchedResourcesByPath.Get(wrappedParameters->mPath);
+ if (watchedResource) {
+ // Append it to the hash tables.
+ AppendCallbacksToHashtables(
+ watchedResource->mPath,
+ wrappedParameters->mChangeCallbackHandle,
+ wrappedParameters->mErrorCallbackHandle);
+
+ return NS_OK;
+ }
+
+ // Retrieve a file handle to associate with the completion port. Makes
+ // sure to request the appropriate rights (i.e. read files and list
+ // files contained in a folder). Note: the nullptr security flag prevents
+ // the |HANDLE| to be passed to child processes.
+ HANDLE resHandle = CreateFileW(wrappedParameters->mPath.get(),
+ FILE_LIST_DIRECTORY, // Access rights
+ FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, // Share
+ nullptr, // Security flags
+ OPEN_EXISTING, // Returns an handle only if the resource exists
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
+ nullptr); // Template file (only used when creating files)
+ if (resHandle == INVALID_HANDLE_VALUE) {
+ DWORD dwError = GetLastError();
+ nsresult rv;
+ if (dwError == ERROR_FILE_NOT_FOUND) {
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ } else if (dwError == ERROR_ACCESS_DENIED) {
+ rv = NS_ERROR_FILE_ACCESS_DENIED;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::AddPathRunnableMethod - CreateFileW failed (error %x) for %S.",
+ dwError, wrappedParameters->mPath.get());
+
+ rv = ReportError(wrappedParameters->mErrorCallbackHandle, rv, dwError);
+ if (NS_FAILED(rv)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::AddPathRunnableMethod - "
+ "Failed to dispatch the error callback (%x).",
+ rv);
+ return rv;
+ }
+
+ // Error has already been reported through mErrorCallback.
+ return NS_OK;
+ }
+
+ // Initialise the resource descriptor.
+ UniquePtr<WatchedResourceDescriptor> resourceDesc(
+ new WatchedResourceDescriptor(wrappedParameters->mPath, resHandle));
+
+ // Associate the file with the previously opened completion port.
+ if (!CreateIoCompletionPort(resourceDesc->mResourceHandle, mIOCompletionPort,
+ (ULONG_PTR)resourceDesc->mResourceHandle.get(), 0)) {
+ DWORD dwError = GetLastError();
+
+ FILEWATCHERLOG("NativeFileWatcherIOTask::AddPathRunnableMethod"
+ " - CreateIoCompletionPort failed (error %x) for %S.",
+ dwError, wrappedParameters->mPath.get());
+
+ // This could fail because passed parameters could be invalid |HANDLE|s
+ // i.e. mIOCompletionPort was unexpectedly closed or failed.
+ nsresult rv =
+ ReportError(wrappedParameters->mErrorCallbackHandle, NS_ERROR_UNEXPECTED, dwError);
+ if (NS_FAILED(rv)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::AddPathRunnableMethod - "
+ "Failed to dispatch the error callback (%x).",
+ rv);
+ return rv;
+ }
+
+ // Error has already been reported through mErrorCallback.
+ return NS_OK;
+ }
+
+ // Append the callbacks to the hash tables. We do this now since
+ // AddDirectoryToWatchList could use the error callback, but we
+ // need to make sure to remove them if AddDirectoryToWatchList fails.
+ AppendCallbacksToHashtables(
+ wrappedParameters->mPath,
+ wrappedParameters->mChangeCallbackHandle,
+ wrappedParameters->mErrorCallbackHandle);
+
+ // We finally watch the resource for changes.
+ nsresult rv = AddDirectoryToWatchList(resourceDesc.get());
+ if (NS_SUCCEEDED(rv)) {
+ // Add the resource pointer to both indexes.
+ WatchedResourceDescriptor* resource = resourceDesc.release();
+ mWatchedResourcesByPath.Put(wrappedParameters->mPath, resource);
+ mWatchedResourcesByHandle.Put(resHandle, resource);
+
+ // Dispatch the success callback.
+ nsresult rv =
+ ReportSuccess(wrappedParameters->mSuccessCallbackHandle, wrappedParameters->mPath);
+ if (NS_FAILED(rv)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::AddPathRunnableMethod - "
+ "Failed to dispatch the success callback (%x).",
+ rv);
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ // We failed to watch the folder. Remove the callbacks
+ // from the hash tables.
+ RemoveCallbacksFromHashtables(
+ wrappedParameters->mPath,
+ wrappedParameters->mChangeCallbackHandle,
+ wrappedParameters->mErrorCallbackHandle);
+
+ if (rv != NS_ERROR_ABORT) {
+ // Just don't add the descriptor to the watch list.
+ return NS_OK;
+ }
+
+ // We failed to dispatch the error callbacks as well.
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::AddPathRunnableMethod - Failed to watch %s and"
+ " to dispatch the related error callbacks", resourceDesc->mPath.get());
+
+ return rv;
+}
+
+/**
+ * Removes the path from the list of watched resources. Silently ignores the request
+ * if the path was not being watched.
+ *
+ * Remove Protocol:
+ *
+ * 1. Find the resource to unwatch through the provided path.
+ * 2. Remove the error and change callbacks from the list of callbacks
+ * associated with the resource.
+ * 3. Remove the error and change callbacks from the callback hash maps.
+ * 4. If there are no more change callbacks for the resource, close
+ * its file |HANDLE|. We do not free the buffer memory just yet, it's
+ * still needed for the next call to GetQueuedCompletionStatus. That
+ * memory will be freed in NativeFileWatcherIOTask::Run.
+ *
+ * @param aWrappedParameters
+ * The structure containing the resource path, the error and change callback
+ * handles.
+ */
+nsresult
+NativeFileWatcherIOTask::RemovePathRunnableMethod(
+ PathRunnablesParametersWrapper* aWrappedParameters)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsAutoPtr<PathRunnablesParametersWrapper> wrappedParameters(aWrappedParameters);
+
+ // We return immediately if |mShuttingDown| is true (see below for
+ // details about the shutdown protocol being followed).
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+
+ if (!wrappedParameters ||
+ !wrappedParameters->mChangeCallbackHandle) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ WatchedResourceDescriptor* toRemove =
+ mWatchedResourcesByPath.Get(wrappedParameters->mPath);
+ if (!toRemove) {
+ // We are trying to remove a path which wasn't being watched. Silently ignore
+ // and dispatch the success callback.
+ nsresult rv =
+ ReportSuccess(wrappedParameters->mSuccessCallbackHandle, wrappedParameters->mPath);
+ if (NS_FAILED(rv)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::RemovePathRunnableMethod - "
+ "Failed to dispatch the success callback (%x).",
+ rv);
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ ChangeCallbackArray* changeCallbackArray =
+ mChangeCallbacksTable.Get(toRemove->mPath);
+
+ // This should always be valid.
+ MOZ_ASSERT(changeCallbackArray);
+
+ bool removed =
+ changeCallbackArray->RemoveElement(wrappedParameters->mChangeCallbackHandle);
+ if (!removed) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::RemovePathRunnableMethod - Unable to remove the change "
+ "callback from the change callback hash map for %S.",
+ wrappedParameters->mPath.get());
+ MOZ_CRASH();
+ }
+
+ ErrorCallbackArray* errorCallbackArray =
+ mErrorCallbacksTable.Get(toRemove->mPath);
+
+ MOZ_ASSERT(errorCallbackArray);
+
+ removed =
+ errorCallbackArray->RemoveElement(wrappedParameters->mErrorCallbackHandle);
+ if (!removed) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::RemovePathRunnableMethod - Unable to remove the error "
+ "callback from the error callback hash map for %S.",
+ wrappedParameters->mPath.get());
+ MOZ_CRASH();
+ }
+
+ // If there are still callbacks left, keep the descriptor.
+ // We don't check for error callbacks since there's no point in keeping
+ // the descriptor if there are no change callbacks but some error callbacks.
+ if (changeCallbackArray->Length()) {
+ // Dispatch the success callback.
+ nsresult rv =
+ ReportSuccess(wrappedParameters->mSuccessCallbackHandle, wrappedParameters->mPath);
+ if (NS_FAILED(rv)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::RemovePathRunnableMethod - "
+ "Failed to dispatch the success callback (%x).",
+ rv);
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ // In this runnable, we just cancel callbacks (see above) and I/O (see below).
+ // Resources are freed by the worker thread when GetQueuedCompletionStatus
+ // detects that a resource was removed from the watch list.
+ // Since when closing |HANDLE|s relative to watched resources
+ // GetQueuedCompletionStatus is notified one last time, it would end
+ // up referring to deallocated memory if we were to free the memory here.
+ // This happens because the worker IO is scheduled to watch the resources
+ // again once we complete executing this function.
+
+ // Enforce CloseHandle/CancelIO by disposing the AutoCloseHandle. We don't
+ // remove the entry from mWatchedResourceBy* since the completion port might
+ // still be using the notification buffer. Entry remove is performed when
+ // handling ERROR_OPERATION_ABORTED in NativeFileWatcherIOTask::Run.
+ toRemove->mResourceHandle.dispose();
+
+ // Dispatch the success callback.
+ nsresult rv =
+ ReportSuccess(wrappedParameters->mSuccessCallbackHandle, wrappedParameters->mPath);
+ if (NS_FAILED(rv)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::RemovePathRunnableMethod - "
+ "Failed to dispatch the success callback (%x).",
+ rv);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Removes all the watched resources from the watch list and stops the
+ * watcher thread. Frees all the used resources.
+ */
+nsresult
+NativeFileWatcherIOTask::DeactivateRunnableMethod()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Remind users to manually remove the watches before quitting.
+ MOZ_ASSERT(!mWatchedResourcesByHandle.Count(),
+ "Clients of the nsINativeFileWatcher must remove "
+ "watches manually before quitting.");
+
+ // Log any pending watch.
+ for (auto it = mWatchedResourcesByHandle.Iter(); !it.Done(); it.Next()) {
+ FILEWATCHERLOG("NativeFileWatcherIOTask::DeactivateRunnableMethod - "
+ "%S is still being watched.", it.UserData()->mPath.get());
+
+ }
+
+ // We return immediately if |mShuttingDown| is true (see below for
+ // details about the shutdown protocol being followed).
+ if (mShuttingDown) {
+ // If this happens, we are in a strange situation.
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::DeactivateRunnableMethod - We are already shutting down.");
+ MOZ_CRASH();
+ return NS_OK;
+ }
+
+ // Deactivate all the non-shutdown methods of this object.
+ mShuttingDown = true;
+
+ // Remove all the elements from the index. Memory will be freed by
+ // calling Clear() on mWatchedResourcesByPath.
+ mWatchedResourcesByHandle.Clear();
+
+ // Clear frees the memory associated with each element and clears the table.
+ // Since we are using Scoped |HANDLE|s, they get automatically closed as well.
+ mWatchedResourcesByPath.Clear();
+
+ // Now that all the descriptors are closed, release the callback hahstables.
+ mChangeCallbacksTable.Clear();
+ mErrorCallbacksTable.Clear();
+
+ // Close the IO completion port, eventually making
+ // the watcher thread exit from the watching loop.
+ if (mIOCompletionPort) {
+ if (!CloseHandle(mIOCompletionPort)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::DeactivateRunnableMethod - "
+ "Failed to close the IO completion port HANDLE.");
+ }
+ }
+
+ // Now we just need to reschedule a final call to Shutdown() back to the main thread.
+ RefPtr<NativeWatcherIOShutdownTask> shutdownRunnable =
+ new NativeWatcherIOShutdownTask();
+
+ return NS_DispatchToMainThread(shutdownRunnable);
+}
+
+/**
+ * Helper function to dispatch a change notification to all the registered callbacks.
+ * @param aResourceDescriptor
+ * The resource descriptor.
+ * @param aChangedResource
+ * The path of the changed resource.
+ * @return NS_OK if all the callbacks are dispatched correctly, a |nsresult| error code
+ * otherwise.
+ */
+nsresult
+NativeFileWatcherIOTask::DispatchChangeCallbacks(
+ WatchedResourceDescriptor* aResourceDescriptor,
+ const nsAString& aChangedResource)
+{
+ MOZ_ASSERT(aResourceDescriptor);
+
+ // Retrieve the change callbacks array.
+ ChangeCallbackArray* changeCallbackArray =
+ mChangeCallbacksTable.Get(aResourceDescriptor->mPath);
+
+ // This should always be valid.
+ MOZ_ASSERT(changeCallbackArray);
+
+ for (size_t i = 0; i < changeCallbackArray->Length(); i++) {
+ nsresult rv =
+ ReportChange((*changeCallbackArray)[i], aChangedResource);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Helper function to post a change runnable to the main thread.
+ *
+ * @param aOnChange
+ * The change callback handle.
+ * @param aChangedResource
+ * The resource name to dispatch thorough the change callback.
+ *
+ * @return NS_OK if the callback is dispatched correctly.
+ */
+nsresult
+NativeFileWatcherIOTask::ReportChange(
+ const nsMainThreadPtrHandle<nsINativeFileWatcherCallback>& aOnChange,
+ const nsAString& aChangedResource)
+{
+ RefPtr<WatchedChangeEvent> changeRunnable =
+ new WatchedChangeEvent(aOnChange, aChangedResource);
+ return NS_DispatchToMainThread(changeRunnable);
+}
+
+/**
+ * Helper function to dispatch a error notification to all the registered callbacks.
+ * @param aResourceDescriptor
+ * The resource descriptor.
+ * @param anError
+ * The error to dispatch thorough the error callback.
+ * @param anOSError
+ * An OS specific error code to send with the callback.
+ * @return NS_OK if all the callbacks are dispatched correctly, a |nsresult| error code
+ * otherwise.
+ */
+nsresult
+NativeFileWatcherIOTask::DispatchErrorCallbacks(
+ WatchedResourceDescriptor* aResourceDescriptor,
+ nsresult anError, DWORD anOSError)
+{
+ MOZ_ASSERT(aResourceDescriptor);
+
+ // Retrieve the error callbacks array.
+ ErrorCallbackArray* errorCallbackArray =
+ mErrorCallbacksTable.Get(aResourceDescriptor->mPath);
+
+ // This must be valid.
+ MOZ_ASSERT(errorCallbackArray);
+
+ for (size_t i = 0; i < errorCallbackArray->Length(); i++) {
+ nsresult rv =
+ ReportError((*errorCallbackArray)[i], anError, anOSError);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Helper function to post an error runnable to the main thread.
+ *
+ * @param aOnError
+ * The error callback handle.
+ * @param anError
+ * The error to dispatch thorough the error callback.
+ * @param anOSError
+ * An OS specific error code to send with the callback.
+ *
+ * @return NS_OK if the callback is dispatched correctly.
+ */
+nsresult
+NativeFileWatcherIOTask::ReportError(
+ const nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback>& aOnError,
+ nsresult anError, DWORD anOSError)
+{
+ RefPtr<WatchedErrorEvent> errorRunnable =
+ new WatchedErrorEvent(aOnError, anError, anOSError);
+ return NS_DispatchToMainThread(errorRunnable);
+}
+
+/**
+ * Helper function to post a success runnable to the main thread.
+ *
+ * @param aOnSuccess
+ * The success callback handle.
+ * @param aResource
+ * The resource name to dispatch thorough the success callback.
+ *
+ * @return NS_OK if the callback is dispatched correctly.
+ */
+nsresult
+NativeFileWatcherIOTask::ReportSuccess(
+ const nsMainThreadPtrHandle<nsINativeFileWatcherSuccessCallback>& aOnSuccess,
+ const nsAString& aResource)
+{
+ RefPtr<WatchedSuccessEvent> successRunnable =
+ new WatchedSuccessEvent(aOnSuccess, aResource);
+ return NS_DispatchToMainThread(successRunnable);
+}
+
+
+/**
+ * Instructs the OS to report the changes concerning the directory of interest.
+ *
+ * @param aDirectoryDescriptor
+ * A |WatchedResourceDescriptor| instance describing the directory to watch.
+ * @param aDispatchErrorCode
+ * If |ReadDirectoryChangesW| fails and dispatching an error callback to the
+ * main thread fails as well, the error code is stored here. If the OS API call
+ * does not fail, it gets set to NS_OK.
+ * @return |true| if |ReadDirectoryChangesW| returned no error, |false| otherwise.
+ */
+nsresult
+NativeFileWatcherIOTask::AddDirectoryToWatchList(
+ WatchedResourceDescriptor* aDirectoryDescriptor)
+{
+ MOZ_ASSERT(!mShuttingDown);
+
+ DWORD dwPlaceholder;
+ // Tells the OS to watch out on mResourceHandle for the changes specified
+ // with the FILE_NOTIFY_* flags. We monitor the creation, renaming and
+ // deletion of a file (FILE_NOTIFY_CHANGE_FILE_NAME), changes to the last
+ // modification time (FILE_NOTIFY_CHANGE_LAST_WRITE) and the creation and
+ // deletion of a folder (FILE_NOTIFY_CHANGE_DIR_NAME). Moreover, when you
+ // first call this function, the system allocates a buffer to store change
+ // information for the watched directory.
+ if (!ReadDirectoryChangesW(aDirectoryDescriptor->mResourceHandle,
+ aDirectoryDescriptor->mNotificationBuffer.get(),
+ NOTIFICATION_BUFFER_SIZE,
+ true, // watch subtree (recurse)
+ FILE_NOTIFY_CHANGE_LAST_WRITE
+ | FILE_NOTIFY_CHANGE_FILE_NAME
+ | FILE_NOTIFY_CHANGE_DIR_NAME,
+ &dwPlaceholder,
+ &aDirectoryDescriptor->mOverlappedInfo,
+ nullptr)) {
+ // NOTE: GetLastError() could return ERROR_INVALID_PARAMETER if the buffer length
+ // is greater than 64 KB and the application is monitoring a directory over the
+ // network. The same error could be returned when trying to watch a file instead
+ // of a directory.
+ // It could return ERROR_NOACCESS if the buffer is not aligned on a DWORD boundary.
+ DWORD dwError = GetLastError();
+
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::AddDirectoryToWatchList "
+ " - ReadDirectoryChangesW failed (error %x) for %S.",
+ dwError, aDirectoryDescriptor->mPath.get());
+
+ nsresult rv =
+ DispatchErrorCallbacks(aDirectoryDescriptor, NS_ERROR_FAILURE, dwError);
+ if (NS_FAILED(rv)) {
+ // That's really bad. We failed to watch the directory and failed to
+ // dispatch the error callbacks.
+ return NS_ERROR_ABORT;
+ }
+
+ // We failed to watch the directory, but we correctly dispatched the error callbacks.
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Appends the change and error callbacks to their respective hash tables.
+ * It also checks if the callbacks are already attached to them.
+ * @param aPath
+ * The watched directory path.
+ * @param aOnChangeHandle
+ * The callback to invoke when a change is detected.
+ * @param aOnErrorHandle
+ * The callback to invoke when an error is detected.
+ */
+void
+NativeFileWatcherIOTask::AppendCallbacksToHashtables(
+ const nsAString& aPath,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherCallback>& aOnChangeHandle,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback>& aOnErrorHandle)
+{
+ // First check to see if we've got an entry already.
+ ChangeCallbackArray* callbacksArray = mChangeCallbacksTable.Get(aPath);
+ if (!callbacksArray) {
+ // We don't have an entry. Create an array and put it into the hash table.
+ callbacksArray = new ChangeCallbackArray();
+ mChangeCallbacksTable.Put(aPath, callbacksArray);
+ }
+
+ // We do have an entry for that path. Check to see if the callback is
+ // already there.
+ ChangeCallbackArray::index_type changeCallbackIndex =
+ callbacksArray->IndexOf(aOnChangeHandle);
+
+ // If the callback is not attached to the descriptor, append it.
+ if (changeCallbackIndex == ChangeCallbackArray::NoIndex) {
+ callbacksArray->AppendElement(aOnChangeHandle);
+ }
+
+ // Same thing for the error callback.
+ ErrorCallbackArray* errorCallbacksArray = mErrorCallbacksTable.Get(aPath);
+ if (!errorCallbacksArray) {
+ // We don't have an entry. Create an array and put it into the hash table.
+ errorCallbacksArray = new ErrorCallbackArray();
+ mErrorCallbacksTable.Put(aPath, errorCallbacksArray);
+ }
+
+ ErrorCallbackArray::index_type errorCallbackIndex =
+ errorCallbacksArray->IndexOf(aOnErrorHandle);
+
+ if (errorCallbackIndex == ErrorCallbackArray::NoIndex) {
+ errorCallbacksArray->AppendElement(aOnErrorHandle);
+ }
+}
+
+/**
+ * Removes the change and error callbacks from their respective hash tables.
+ * @param aPath
+ * The watched directory path.
+ * @param aOnChangeHandle
+ * The change callback to remove.
+ * @param aOnErrorHandle
+ * The error callback to remove.
+ */
+void
+NativeFileWatcherIOTask::RemoveCallbacksFromHashtables(
+ const nsAString& aPath,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherCallback>& aOnChangeHandle,
+ const nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback>& aOnErrorHandle)
+{
+ // Find the change callback array for |aPath|.
+ ChangeCallbackArray* callbacksArray = mChangeCallbacksTable.Get(aPath);
+ if (callbacksArray) {
+ // Remove the change callback.
+ callbacksArray->RemoveElement(aOnChangeHandle);
+ }
+
+ // Find the error callback array for |aPath|.
+ ErrorCallbackArray* errorCallbacksArray = mErrorCallbacksTable.Get(aPath);
+ if (errorCallbacksArray) {
+ // Remove the error callback.
+ errorCallbacksArray->RemoveElement(aOnErrorHandle);
+ }
+}
+
+/**
+ * Creates a string representing the native path for the changed resource.
+ * It appends the resource name to the path of the changed descriptor by
+ * using nsILocalFile.
+ * @param changedDescriptor
+ * The descriptor of the watched resource.
+ * @param resourceName
+ * The resource which triggered the change.
+ * @param nativeResourcePath
+ * The full path to the changed resource.
+ * @return NS_OK if nsILocalFile succeeded in building the path.
+ */
+nsresult
+NativeFileWatcherIOTask::MakeResourcePath(
+ WatchedResourceDescriptor* changedDescriptor,
+ const nsAString& resourceName,
+ nsAString& nativeResourcePath)
+{
+ nsCOMPtr<nsILocalFile>
+ localPath(do_CreateInstance("@mozilla.org/file/local;1"));
+ if (!localPath) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::MakeResourcePath - Failed to create a nsILocalFile instance.");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = localPath->InitWithPath(changedDescriptor->mPath);
+ if (NS_FAILED(rv)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::MakeResourcePath - Failed to init nsILocalFile with %S (%x).",
+ changedDescriptor->mPath.get(), rv);
+ return rv;
+ }
+
+ rv = localPath->AppendRelativePath(resourceName);
+ if (NS_FAILED(rv)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::MakeResourcePath - Failed to append to %S (%x).",
+ changedDescriptor->mPath.get(), rv);
+ return rv;
+ }
+
+ rv = localPath->GetPath(nativeResourcePath);
+ if (NS_FAILED(rv)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::MakeResourcePath - Failed to get native path from nsILocalFile (%x).",
+ rv);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // namespace
+
+// The NativeFileWatcherService component
+
+NS_IMPL_ISUPPORTS(NativeFileWatcherService, nsINativeFileWatcherService, nsIObserver);
+
+NativeFileWatcherService::NativeFileWatcherService()
+{
+}
+
+NativeFileWatcherService::~NativeFileWatcherService()
+{
+}
+
+/**
+ * Sets the required resources and starts the watching IO thread.
+ *
+ * @return NS_OK if there was no error with thread creation and execution.
+ */
+nsresult
+NativeFileWatcherService::Init()
+{
+ // Creates an IO completion port and allows at most 2 thread to access it concurrently.
+ AutoCloseHandle completionPort(
+ CreateIoCompletionPort(INVALID_HANDLE_VALUE, // FileHandle
+ nullptr, // ExistingCompletionPort
+ 0, // CompletionKey
+ 2)); // NumberOfConcurrentThreads
+ if (!completionPort) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Add an observer for the shutdown.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ observerService->AddObserver(this, "xpcom-shutdown-threads", false);
+
+ // Start the IO worker thread.
+ mWorkerIORunnable = new NativeFileWatcherIOTask(completionPort);
+ nsresult rv = NS_NewThread(getter_AddRefs(mIOThread), mWorkerIORunnable);
+ if (NS_FAILED(rv)) {
+ FILEWATCHERLOG(
+ "NativeFileWatcherIOTask::Init - Unable to create and dispatch the worker thread (%x).",
+ rv);
+ return rv;
+ }
+
+ // Set the name for the worker thread.
+ NS_SetThreadName(mIOThread, "FileWatcher IO");
+
+ mIOCompletionPort = completionPort.forget();
+
+ return NS_OK;
+}
+
+/**
+ * Watches a path for changes: monitors the creations, name changes and
+ * content changes to the files contained in the watched path.
+ *
+ * @param aPathToWatch
+ * The path of the resource to watch for changes.
+ * @param aOnChange
+ * The callback to invoke when a change is detected.
+ * @param aOnError
+ * The optional callback to invoke when there's an error.
+ * @param aOnSuccess
+ * The optional callback to invoke when the file watcher starts
+ * watching the resource for changes.
+ *
+ * @return NS_OK or NS_ERROR_NOT_INITIALIZED if the instance was not initialized.
+ * Other errors are reported by the error callback function.
+ */
+NS_IMETHODIMP
+NativeFileWatcherService::AddPath(const nsAString& aPathToWatch,
+ nsINativeFileWatcherCallback* aOnChange,
+ nsINativeFileWatcherErrorCallback* aOnError,
+ nsINativeFileWatcherSuccessCallback* aOnSuccess)
+{
+ // Make sure the instance was initialized.
+ if (!mIOThread) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Be sure a valid change callback was passed.
+ if (!aOnChange) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsMainThreadPtrHandle<nsINativeFileWatcherCallback> changeCallbackHandle(
+ new nsMainThreadPtrHolder<nsINativeFileWatcherCallback>(aOnChange));
+
+ nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback> errorCallbackHandle(
+ new nsMainThreadPtrHolder<nsINativeFileWatcherErrorCallback>(aOnError));
+
+ nsMainThreadPtrHandle<nsINativeFileWatcherSuccessCallback> successCallbackHandle(
+ new nsMainThreadPtrHolder<nsINativeFileWatcherSuccessCallback>(aOnSuccess));
+
+ // Wrap the path and the callbacks in order to pass them using NewRunnableMethod.
+ UniquePtr<PathRunnablesParametersWrapper> wrappedCallbacks(
+ new PathRunnablesParametersWrapper(
+ aPathToWatch,
+ changeCallbackHandle,
+ errorCallbackHandle,
+ successCallbackHandle));
+
+ // Since this function does a bit of I/O stuff , run it in the IO thread.
+ nsresult rv =
+ mIOThread->Dispatch(
+ NewRunnableMethod<PathRunnablesParametersWrapper*>(
+ static_cast<NativeFileWatcherIOTask*>(mWorkerIORunnable.get()),
+ &NativeFileWatcherIOTask::AddPathRunnableMethod,
+ wrappedCallbacks.get()),
+ nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Since the dispatch succeeded, we let the runnable own the pointer.
+ wrappedCallbacks.release();
+
+ WakeUpWorkerThread();
+
+ return NS_OK;
+}
+
+/**
+ * Removes the path from the list of watched resources. Silently ignores the request
+ * if the path was not being watched or the callbacks were not registered.
+ *
+ * @param aPathToRemove
+ * The path of the resource to remove from the watch list.
+ * @param aOnChange
+ * The callback to invoke when a change is detected.
+ * @param aOnError
+ * The optionally registered callback invoked when there's an error.
+ * @param aOnSuccess
+ * The optional callback to invoke when the file watcher stops
+ * watching the resource for changes.
+ *
+ * @return NS_OK or NS_ERROR_NOT_INITIALIZED if the instance was not initialized.
+ * Other errors are reported by the error callback function.
+ */
+NS_IMETHODIMP
+NativeFileWatcherService::RemovePath(const nsAString& aPathToRemove,
+ nsINativeFileWatcherCallback* aOnChange,
+ nsINativeFileWatcherErrorCallback* aOnError,
+ nsINativeFileWatcherSuccessCallback* aOnSuccess)
+{
+ // Make sure the instance was initialized.
+ if (!mIOThread) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // Be sure a valid change callback was passed.
+ if (!aOnChange) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsMainThreadPtrHandle<nsINativeFileWatcherCallback> changeCallbackHandle(
+ new nsMainThreadPtrHolder<nsINativeFileWatcherCallback>(aOnChange));
+
+ nsMainThreadPtrHandle<nsINativeFileWatcherErrorCallback> errorCallbackHandle(
+ new nsMainThreadPtrHolder<nsINativeFileWatcherErrorCallback>(aOnError));
+
+ nsMainThreadPtrHandle<nsINativeFileWatcherSuccessCallback> successCallbackHandle(
+ new nsMainThreadPtrHolder<nsINativeFileWatcherSuccessCallback>(aOnSuccess));
+
+ // Wrap the path and the callbacks in order to pass them using NewRunnableMethod.
+ UniquePtr<PathRunnablesParametersWrapper> wrappedCallbacks(
+ new PathRunnablesParametersWrapper(
+ aPathToRemove,
+ changeCallbackHandle,
+ errorCallbackHandle,
+ successCallbackHandle));
+
+ // Since this function does a bit of I/O stuff, run it in the IO thread.
+ nsresult rv =
+ mIOThread->Dispatch(
+ NewRunnableMethod<PathRunnablesParametersWrapper*>(
+ static_cast<NativeFileWatcherIOTask*>(mWorkerIORunnable.get()),
+ &NativeFileWatcherIOTask::RemovePathRunnableMethod,
+ wrappedCallbacks.get()),
+ nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Since the dispatch succeeded, we let the runnable own the pointer.
+ wrappedCallbacks.release();
+
+ WakeUpWorkerThread();
+
+ return NS_OK;
+}
+
+/**
+ * Removes all the watched resources from the watch list and stops the
+ * watcher thread. Frees all the used resources.
+ *
+ * To avoid race conditions, we need a Shutdown Protocol:
+ *
+ * 1. [MainThread]
+ * When the "xpcom-shutdown-threads" event is detected, Uninit() gets called.
+ * 2. [MainThread]
+ * Uninit sends DeactivateRunnableMethod() to the WorkerThread.
+ * 3. [WorkerThread]
+ * DeactivateRunnableMethod makes it clear to other methods that shutdown is
+ * in progress, stops the IO completion port wait and schedules the rest of the
+ * deactivation for after every currently pending method call is complete.
+ */
+nsresult
+NativeFileWatcherService::Uninit()
+{
+ // Make sure the instance was initialized (and not de-initialized yet).
+ if (!mIOThread) {
+ return NS_OK;
+ }
+
+ // We need to be sure that there will be no calls to 'mIOThread' once we have entered
+ // 'Uninit()', even if we exit due to an error.
+ nsCOMPtr<nsIThread> ioThread;
+ ioThread.swap(mIOThread);
+
+ // Since this function does a bit of I/O stuff (close file handle), run it
+ // in the IO thread.
+ nsresult rv =
+ ioThread->Dispatch(
+ NewRunnableMethod(
+ static_cast<NativeFileWatcherIOTask*>(mWorkerIORunnable.get()),
+ &NativeFileWatcherIOTask::DeactivateRunnableMethod),
+ nsIEventTarget::DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ WakeUpWorkerThread();
+
+ return NS_OK;
+}
+
+/**
+ * Tells |NativeFileWatcherIOTask| to quit and to reschedule itself in order to
+ * execute the other runnables enqueued in the worker tread.
+ * This works by posting a bogus event to the blocking |GetQueuedCompletionStatus|
+ * call in |NativeFileWatcherIOTask::Run()|.
+ */
+void
+NativeFileWatcherService::WakeUpWorkerThread()
+{
+ // The last 3 parameters represent the number of transferred bytes, the changed
+ // resource |HANDLE| and the address of the |OVERLAPPED| structure passed to
+ // GetQueuedCompletionStatus: we set them to nullptr so that we can recognize
+ // that we requested an interruption from the Worker thread.
+ PostQueuedCompletionStatus(mIOCompletionPort, 0, 0, nullptr);
+}
+
+/**
+ * This method is used to catch the "xpcom-shutdown-threads" event in order
+ * to shutdown this service when closing the application.
+ */
+NS_IMETHODIMP
+NativeFileWatcherService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp("xpcom-shutdown-threads", aTopic)) {
+ nsresult rv = Uninit();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(false, "NativeFileWatcherService got an unexpected topic!");
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+} // namespace mozilla
diff --git a/components/filewatcher/NativeFileWatcherWin.h b/components/filewatcher/NativeFileWatcherWin.h
new file mode 100644
index 000000000..37dd97f84
--- /dev/null
+++ b/components/filewatcher/NativeFileWatcherWin.h
@@ -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/. */
+
+#ifndef mozilla_nativefilewatcher_h__
+#define mozilla_nativefilewatcher_h__
+
+#include "nsINativeFileWatcher.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsThreadUtils.h"
+
+// We need to include this header here for HANDLE definition.
+#include <windows.h>
+
+namespace mozilla {
+
+class NativeFileWatcherService final : public nsINativeFileWatcherService,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINATIVEFILEWATCHERSERVICE
+ NS_DECL_NSIOBSERVER
+
+ NativeFileWatcherService();
+
+ nsresult Init();
+
+private:
+ // The |HANDLE| to the I/O Completion Port, owned by the main thread.
+ HANDLE mIOCompletionPort;
+ nsCOMPtr<nsIThread> mIOThread;
+
+ // The instance of the runnable dealing with the I/O.
+ nsCOMPtr<nsIRunnable> mWorkerIORunnable;
+
+ nsresult Uninit();
+ void WakeUpWorkerThread();
+
+ // Make the dtor private to make this object only deleted via its ::Release() method.
+ ~NativeFileWatcherService();
+ NativeFileWatcherService(const NativeFileWatcherService& other) = delete;
+ void operator=(const NativeFileWatcherService& other) = delete;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_nativefilewatcher_h__
diff --git a/components/filewatcher/moz.build b/components/filewatcher/moz.build
new file mode 100644
index 000000000..5fb1c4570
--- /dev/null
+++ b/components/filewatcher/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_MODULE = 'toolkit_filewatcher'
+
+XPIDL_SOURCES += ['nsINativeFileWatcher.idl']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ EXPORTS += ['NativeFileWatcherWin.h']
+ UNIFIED_SOURCES += ['NativeFileWatcherWin.cpp']
+else:
+ EXPORTS += ['NativeFileWatcherNotSupported.h']
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/filewatcher/nsINativeFileWatcher.idl b/components/filewatcher/nsINativeFileWatcher.idl
new file mode 100644
index 000000000..e2e5d2fe1
--- /dev/null
+++ b/components/filewatcher/nsINativeFileWatcher.idl
@@ -0,0 +1,110 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * The interface for the callback invoked when there is an error.
+ */
+[scriptable, function, uuid(5DAEDDC3-FC94-4880-8A4F-26D910B92662)]
+interface nsINativeFileWatcherErrorCallback: nsISupports
+{
+ /**
+ * @param xpcomError The XPCOM error code.
+ * @param osError The native OS error (errno under Unix, GetLastError under Windows).
+ */
+ void complete(in nsresult xpcomError, in long osError);
+};
+
+/**
+ * The interface for the callback invoked when a change on a watched
+ * resource is detected.
+ */
+[scriptable, function, uuid(FE4D86C9-243F-4195-B544-AECE3DF4B86A)]
+interface nsINativeFileWatcherCallback: nsISupports
+{
+ /**
+ * @param resourcePath
+ * The path of the changed resource. If there were too many changes,
+ * the string "*" is passed.
+ * @param flags Reserved for future uses, not currently used.
+ */
+ void changed(in AString resourcePath, in int32_t flags);
+};
+
+/**
+ * The interface for the callback invoked when a file watcher operation
+ * successfully completes.
+ */
+[scriptable, function, uuid(C3D7F542-681B-4ABD-9D65-9D799B29A42B)]
+interface nsINativeFileWatcherSuccessCallback: nsISupports
+{
+ /**
+ * @param resourcePath
+ * The path of the resource for which the operation completes.
+ */
+ void complete(in AString resourcePath);
+};
+
+/**
+ * A service providing native implementations of path changes notification.
+ */
+[scriptable, builtinclass, uuid(B3A4E8D8-7DC8-47DB-A8B4-83736D7AC1AA)]
+interface nsINativeFileWatcherService: nsISupports
+{
+ /**
+ * Watches the passed path for changes. If it's a directory, every file
+ * it contains is watched. Recursively watches subdirectories. If the
+ * resource is already being watched, does nothing. If the passed path
+ * is a file, the behaviour is not specified.
+ *
+ * @param pathToWatch The path to watch for changes.
+ * @param onChange
+ * The callback invoked whenever a change on a watched
+ * resource is detected.
+ * @param onError
+ * The optional callback invoked whenever an error occurs.
+ * @param onSuccess
+ * The optional callback invoked when the file watcher starts
+ * watching the resource for changes.
+ */
+ void addPath(in AString pathToWatch,
+ in nsINativeFileWatcherCallback onChange,
+ [optional] in nsINativeFileWatcherErrorCallback onError,
+ [optional] in nsINativeFileWatcherSuccessCallback onSuccess);
+
+ /**
+ * Removes the provided path from the watched resources. If the path
+ * was not being watched or the callbacks were not registered, silently
+ * ignores the request.
+ * Please note that the file watcher only considers the onChange callbacks
+ * when deciding to close a watch on a resource. If there are no more onChange
+ * callbacks associated to the watch, it gets closed (even though it still has
+ * some error callbacks associated).
+ *
+ * @param pathToUnwatch The path to un-watch.
+ * @param onChange
+ * The registered callback invoked whenever a change on a watched
+ * resource is detected.
+ * @param onError
+ * The optionally registered callback invoked whenever an error
+ * occurs.
+ * @param onSuccess
+ * The optional callback invoked when the file watcher stops
+ * watching the resource for changes.
+ */
+ void removePath(in AString pathToUnwatch,
+ in nsINativeFileWatcherCallback onChange,
+ [optional] in nsINativeFileWatcherErrorCallback onError,
+ [optional] in nsINativeFileWatcherSuccessCallback onSuccess);
+};
+
+
+%{ C++
+
+#define NATIVE_FILEWATCHER_SERVICE_CID {0x6F488507, 0x469D, 0x4350, {0xA6, 0x8D, 0x99, 0xC8, 0x7, 0xBE, 0xA, 0x78}}
+#define NATIVE_FILEWATCHER_SERVICE_CONTRACTID "@mozilla.org/toolkit/filewatcher/native-file-watcher;1"
+
+%}
diff --git a/components/finalizationwitness/FinalizationWitnessService.cpp b/components/finalizationwitness/FinalizationWitnessService.cpp
new file mode 100644
index 000000000..0cf4ae52e
--- /dev/null
+++ b/components/finalizationwitness/FinalizationWitnessService.cpp
@@ -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/. */
+
+#include "FinalizationWitnessService.h"
+
+#include "nsString.h"
+#include "jsapi.h"
+#include "js/CallNonGenericMethod.h"
+#include "mozJSComponentLoader.h"
+#include "nsZipArchive.h"
+
+#include "mozilla/Scoped.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+
+
+// Implementation of nsIFinalizationWitnessService
+
+static bool gShuttingDown = false;
+
+namespace mozilla {
+
+namespace {
+
+/**
+ * An event meant to be dispatched to the main thread upon finalization
+ * of a FinalizationWitness, unless method |forget()| has been called.
+ *
+ * Held as private data by each instance of FinalizationWitness.
+ * Important note: we maintain the invariant that these private data
+ * slots are already addrefed.
+ */
+class FinalizationEvent final: public Runnable
+{
+public:
+ FinalizationEvent(const char* aTopic,
+ const char16_t* aValue)
+ : mTopic(aTopic)
+ , mValue(aValue)
+ { }
+
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ // This is either too early or, more likely, too late for notifications.
+ // Bail out.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ (void)observerService->
+ NotifyObservers(nullptr, mTopic.get(), mValue.get());
+ return NS_OK;
+ }
+private:
+ /**
+ * The topic on which to broadcast the notification of finalization.
+ *
+ * Deallocated on the main thread.
+ */
+ const nsCString mTopic;
+
+ /**
+ * The result of converting the exception to a string.
+ *
+ * Deallocated on the main thread.
+ */
+ const nsString mValue;
+};
+
+enum {
+ WITNESS_SLOT_EVENT,
+ WITNESS_INSTANCES_SLOTS
+};
+
+/**
+ * Extract the FinalizationEvent from an instance of FinalizationWitness
+ * and clear the slot containing the FinalizationEvent.
+ */
+already_AddRefed<FinalizationEvent>
+ExtractFinalizationEvent(JSObject *objSelf)
+{
+ JS::Value slotEvent = JS_GetReservedSlot(objSelf, WITNESS_SLOT_EVENT);
+ if (slotEvent.isUndefined()) {
+ // Forget() has been called
+ return nullptr;
+ }
+
+ JS_SetReservedSlot(objSelf, WITNESS_SLOT_EVENT, JS::UndefinedValue());
+
+ return dont_AddRef(static_cast<FinalizationEvent*>(slotEvent.toPrivate()));
+}
+
+/**
+ * Finalizer for instances of FinalizationWitness.
+ *
+ * Unless method Forget() has been called, the finalizer displays an error
+ * message.
+ */
+void Finalize(JSFreeOp *fop, JSObject *objSelf)
+{
+ RefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf);
+ if (event == nullptr || gShuttingDown) {
+ // NB: event will be null if Forget() has been called
+ return;
+ }
+
+ // Notify observers. Since we are executed during garbage-collection,
+ // we need to dispatch the notification to the main thread.
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ if (mainThread) {
+ mainThread->Dispatch(event.forget(), NS_DISPATCH_NORMAL);
+ }
+ // We may fail at dispatching to the main thread if we arrive too late
+ // during shutdown. In that case, there is not much we can do.
+}
+
+static const JSClassOps sWitnessClassOps = {
+ nullptr /* addProperty */,
+ nullptr /* delProperty */,
+ nullptr /* getProperty */,
+ nullptr /* setProperty */,
+ nullptr /* enumerate */,
+ nullptr /* resolve */,
+ nullptr /* mayResolve */,
+ Finalize /* finalize */
+};
+
+static const JSClass sWitnessClass = {
+ "FinalizationWitness",
+ JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &sWitnessClassOps
+};
+
+bool IsWitness(JS::Handle<JS::Value> v)
+{
+ return v.isObject() && JS_GetClass(&v.toObject()) == &sWitnessClass;
+}
+
+
+/**
+ * JS method |forget()|
+ *
+ * === JS documentation
+ *
+ * Neutralize the witness. Once this method is called, the witness will
+ * never report any error.
+ */
+bool ForgetImpl(JSContext* cx, const JS::CallArgs& args)
+{
+ if (args.length() != 0) {
+ JS_ReportErrorASCII(cx, "forget() takes no arguments");
+ return false;
+ }
+ JS::Rooted<JS::Value> valSelf(cx, args.thisv());
+ JS::Rooted<JSObject*> objSelf(cx, &valSelf.toObject());
+
+ RefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf);
+ if (event == nullptr) {
+ JS_ReportErrorASCII(cx, "forget() called twice");
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+bool Forget(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+ return JS::CallNonGenericMethod<IsWitness, ForgetImpl>(cx, args);
+}
+
+static const JSFunctionSpec sWitnessClassFunctions[] = {
+ JS_FN("forget", Forget, 0, JSPROP_READONLY | JSPROP_PERMANENT),
+ JS_FS_END
+};
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(FinalizationWitnessService, nsIFinalizationWitnessService, nsIObserver)
+
+/**
+ * Create a new Finalization Witness.
+ *
+ * A finalization witness is an object whose sole role is to notify
+ * observers when it is gc-ed. Once the witness is created, call its
+ * method |forget()| to prevent the observers from being notified.
+ *
+ * @param aTopic The notification topic.
+ * @param aValue The notification value. Converted to a string.
+ *
+ * @constructor
+ */
+NS_IMETHODIMP
+FinalizationWitnessService::Make(const char* aTopic,
+ const char16_t* aValue,
+ JSContext* aCx,
+ JS::MutableHandle<JS::Value> aRetval)
+{
+ JS::Rooted<JSObject*> objResult(aCx, JS_NewObject(aCx, &sWitnessClass));
+ if (!objResult) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (!JS_DefineFunctions(aCx, objResult, sWitnessClassFunctions)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<FinalizationEvent> event = new FinalizationEvent(aTopic, aValue);
+
+ // Transfer ownership of the addrefed |event| to |objResult|.
+ JS_SetReservedSlot(objResult, WITNESS_SLOT_EVENT,
+ JS::PrivateValue(event.forget().take()));
+
+ aRetval.setObject(*objResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FinalizationWitnessService::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aValue)
+{
+ MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
+ gShuttingDown = true;
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FinalizationWitnessService::Init()
+{
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+}
+
+} // namespace mozilla
diff --git a/components/finalizationwitness/FinalizationWitnessService.h b/components/finalizationwitness/FinalizationWitnessService.h
new file mode 100644
index 000000000..087cff239
--- /dev/null
+++ b/components/finalizationwitness/FinalizationWitnessService.h
@@ -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/. */
+
+#ifndef mozilla_finalizationwitnessservice_h__
+#define mozilla_finalizationwitnessservice_h__
+
+#include "nsIFinalizationWitnessService.h"
+#include "nsIObserver.h"
+
+namespace mozilla {
+
+/**
+ * XPConnect initializer, for use in the main thread.
+ */
+class FinalizationWitnessService final : public nsIFinalizationWitnessService,
+ public nsIObserver
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFINALIZATIONWITNESSSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+ private:
+ ~FinalizationWitnessService() {}
+ void operator=(const FinalizationWitnessService* other) = delete;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_finalizationwitnessservice_h__
diff --git a/components/finalizationwitness/moz.build b/components/finalizationwitness/moz.build
new file mode 100644
index 000000000..4e734992f
--- /dev/null
+++ b/components/finalizationwitness/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+SOURCES += [
+ 'FinalizationWitnessService.cpp',
+]
+
+XPIDL_SOURCES += [
+ 'nsIFinalizationWitnessService.idl',
+]
+
+XPIDL_MODULE = 'toolkit_finalizationwitness'
+
+EXPORTS.mozilla += [
+ 'FinalizationWitnessService.h',
+]
+
+LOCAL_INCLUDES += [
+ '/js/xpconnect/loader',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/finalizationwitness/nsIFinalizationWitnessService.idl b/components/finalizationwitness/nsIFinalizationWitnessService.idl
new file mode 100644
index 000000000..6fc071e02
--- /dev/null
+++ b/components/finalizationwitness/nsIFinalizationWitnessService.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 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/. */
+
+#include "nsISupports.idl"
+
+
+[scriptable, uuid(15686f9d-483e-4361-98cd-37f1e8f1e61d)]
+interface nsIFinalizationWitnessService: nsISupports
+{
+ /**
+ * Create a new Finalization Witness.
+ *
+ * A finalization witness is an object whose sole role is to
+ * broadcast when it is garbage-collected. Once the witness is
+ * created, call method its method |forget()| to prevent the
+ * broadcast.
+ *
+ * @param aTopic The topic that the witness will broadcast using
+ * Services.obs.
+ * @param aString The string that the witness will broadcast.
+ * @return An object with a single method |forget()|.
+ */
+ [implicit_jscontext]
+ jsval make(in string aTopic, in wstring aString);
+};
+
+%{ C++
+
+#define FINALIZATIONWITNESSSERVICE_CID {0x15686f9d,0x483e,0x4361,{0x98,0xcd,0x37,0xf1,0xe8,0xf1,0xe6,0x1d}}
+#define FINALIZATIONWITNESSSERVICE_CONTRACTID "@mozilla.org/toolkit/finalizationwitness;1"
+
+%}
diff --git a/components/find/moz.build b/components/find/moz.build
new file mode 100644
index 000000000..d2ea06ab9
--- /dev/null
+++ b/components/find/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsIFind.idl',
+ 'public/nsIFindService.idl',
+ 'public/nsIWebBrowserFind.idl',
+]
+
+
+SOURCES += [
+ 'src/nsFind.cpp',
+ 'src/nsFindService.cpp',
+ 'src/nsWebBrowserFind.cpp',
+]
+
+XPIDL_MODULE = 'mozfind'
+FINAL_LIBRARY = 'xul'
diff --git a/components/find/public/nsIFind.idl b/components/find/public/nsIFind.idl
new file mode 100644
index 000000000..ce02c9b7d
--- /dev/null
+++ b/components/find/public/nsIFind.idl
@@ -0,0 +1,34 @@
+/* -*- 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 nsIDOMRange;
+interface nsIWordBreaker;
+
+[scriptable, uuid(40aba110-2a56-4678-be90-e2c17a9ae7d7)]
+interface nsIFind : nsISupports
+{
+ attribute boolean findBackwards;
+ attribute boolean caseSensitive;
+ attribute boolean entireWord;
+
+ /**
+ * Find some text in the current context. The implementation is
+ * responsible for performing the find and highlighting the text.
+ *
+ * @param aPatText The text to search for.
+ * @param aSearchRange A Range specifying domain of search.
+ * @param aStartPoint A Range specifying search start point.
+ * If not collapsed, we'll start from
+ * end (forward) or start (backward).
+ * @param aEndPoint A Range specifying search end point.
+ * If not collapsed, we'll end at
+ * end (forward) or start (backward).
+ * @retval A range spanning the match that was found (or null).
+ */
+ nsIDOMRange Find(in AString aPatText, in nsIDOMRange aSearchRange,
+ in nsIDOMRange aStartPoint, in nsIDOMRange aEndPoint);
+};
diff --git a/components/find/public/nsIFindService.idl b/components/find/public/nsIFindService.idl
new file mode 100644
index 000000000..c5fb96f76
--- /dev/null
+++ b/components/find/public/nsIFindService.idl
@@ -0,0 +1,26 @@
+/* -*- 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"
+
+[scriptable, uuid(5060b801-340e-11d5-be5b-b3e063ec6a3c)]
+interface nsIFindService : nsISupports
+{
+
+ /*
+ * The sole purpose of the Find service is to store globally the
+ * last used Find settings
+ *
+ */
+
+ attribute AString searchString;
+ attribute AString replaceString;
+
+ attribute boolean findBackwards;
+ attribute boolean wrapFind;
+ attribute boolean entireWord;
+ attribute boolean matchCase;
+
+};
diff --git a/components/find/public/nsIWebBrowserFind.idl b/components/find/public/nsIWebBrowserFind.idl
new file mode 100644
index 000000000..a00763f78
--- /dev/null
+++ b/components/find/public/nsIWebBrowserFind.idl
@@ -0,0 +1,145 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+#include "domstubs.idl"
+
+interface mozIDOMWindowProxy;
+
+/* THIS IS A PUBLIC EMBEDDING API */
+
+
+/**
+ * nsIWebBrowserFind
+ *
+ * Searches for text in a web browser.
+ *
+ * Get one by doing a GetInterface on an nsIWebBrowser.
+ *
+ * By default, the implementation will search the focussed frame, or
+ * if there is no focussed frame, the web browser content area. It
+ * does not by default search subframes or iframes. To change this
+ * behaviour, and to explicitly set the frame to search,
+ * QueryInterface to nsIWebBrowserFindInFrames.
+ */
+
+[scriptable, uuid(e4920136-b3e0-49e0-b1cd-6c783d2591a8)]
+interface nsIWebBrowserFind : nsISupports
+{
+ /**
+ * findNext
+ *
+ * Finds, highlights, and scrolls into view the next occurrence of the
+ * search string, using the current search settings. Fails if the
+ * search string is empty.
+ *
+ * @return Whether an occurrence was found
+ */
+ boolean findNext();
+
+ /**
+ * searchString
+ *
+ * The string to search for. This must be non-empty to search.
+ */
+ attribute wstring searchString;
+
+ /**
+ * findBackwards
+ *
+ * Whether to find backwards (towards the beginning of the document).
+ * Default is false (search forward).
+ */
+ attribute boolean findBackwards;
+
+ /**
+ * wrapFind
+ *
+ * Whether the search wraps around to the start (or end) of the document
+ * if no match was found between the current position and the end (or
+ * beginning). Works correctly when searching backwards. Default is
+ * false.
+ */
+ attribute boolean wrapFind;
+
+ /**
+ * entireWord
+ *
+ * Whether to match entire words only. Default is false.
+ */
+ attribute boolean entireWord;
+
+ /**
+ * matchCase
+ *
+ * Whether to match case (case sensitive) when searching. Default is false.
+ */
+ attribute boolean matchCase;
+
+ /**
+ * searchFrames
+ *
+ * Whether to search through all frames in the content area. Default is true.
+ *
+ * Note that you can control whether the search propagates into child or
+ * parent frames explicitly using nsIWebBrowserFindInFrames, but if one,
+ * but not both, of searchSubframes and searchParentFrames are set, this
+ * returns false.
+ */
+ attribute boolean searchFrames;
+};
+
+
+
+/**
+ * nsIWebBrowserFindInFrames
+ *
+ * Controls how find behaves when multiple frames or iframes are present.
+ *
+ * Get by doing a QueryInterface from nsIWebBrowserFind.
+ */
+
+[scriptable, uuid(e0f5d182-34bc-11d5-be5b-b760676c6ebc)]
+interface nsIWebBrowserFindInFrames : nsISupports
+{
+ /**
+ * currentSearchFrame
+ *
+ * Frame at which to start the search. Once the search is done, this will
+ * be set to be the last frame searched, whether or not a result was found.
+ * Has to be equal to or contained within the rootSearchFrame.
+ */
+ attribute mozIDOMWindowProxy currentSearchFrame;
+
+ /**
+ * rootSearchFrame
+ *
+ * Frame within which to confine the search (normally the content area frame).
+ * Set this to only search a subtree of the frame hierarchy.
+ */
+ attribute mozIDOMWindowProxy rootSearchFrame;
+
+ /**
+ * searchSubframes
+ *
+ * Whether to recurse down into subframes while searching. Default is true.
+ *
+ * Setting nsIWebBrowserfind.searchFrames to true sets this to true.
+ */
+ attribute boolean searchSubframes;
+
+ /**
+ * searchParentFrames
+ *
+ * Whether to allow the search to propagate out of the currentSearchFrame into its
+ * parent frame(s). Search is always confined within the rootSearchFrame. Default
+ * is true.
+ *
+ * Setting nsIWebBrowserfind.searchFrames to true sets this to true.
+ */
+ attribute boolean searchParentFrames;
+
+};
diff --git a/components/find/src/nsFind.cpp b/components/find/src/nsFind.cpp
new file mode 100644
index 000000000..82a928f49
--- /dev/null
+++ b/components/find/src/nsFind.cpp
@@ -0,0 +1,1388 @@
+/* -*- 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/. */
+
+//#define DEBUG_FIND 1
+
+#include "nsFind.h"
+#include "nsContentCID.h"
+#include "nsIContent.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMNodeList.h"
+#include "nsISelection.h"
+#include "nsISelectionController.h"
+#include "nsIFrame.h"
+#include "nsITextControlFrame.h"
+#include "nsIFormControl.h"
+#include "nsIEditor.h"
+#include "nsIPlaintextEditor.h"
+#include "nsTextFragment.h"
+#include "nsString.h"
+#include "nsIAtom.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIDOMElement.h"
+#include "nsIWordBreaker.h"
+#include "nsCRT.h"
+#include "nsRange.h"
+#include "nsContentUtils.h"
+#include "mozilla/DebugOnly.h"
+
+using namespace mozilla;
+
+// Yikes! Casting a char to unichar can fill with ones!
+#define CHAR_TO_UNICHAR(c) ((char16_t)(unsigned char)c)
+
+static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
+static NS_DEFINE_CID(kCPreContentIteratorCID, NS_PRECONTENTITERATOR_CID);
+
+#define CH_QUOTE ((char16_t)0x22)
+#define CH_APOSTROPHE ((char16_t)0x27)
+#define CH_LEFT_SINGLE_QUOTE ((char16_t)0x2018)
+#define CH_RIGHT_SINGLE_QUOTE ((char16_t)0x2019)
+#define CH_LEFT_DOUBLE_QUOTE ((char16_t)0x201C)
+#define CH_RIGHT_DOUBLE_QUOTE ((char16_t)0x201D)
+
+#define CH_SHY ((char16_t)0xAD)
+
+// nsFind::Find casts CH_SHY to char before calling StripChars
+// This works correctly if and only if CH_SHY <= 255
+static_assert(CH_SHY <= 255, "CH_SHY is not an ascii character");
+
+// nsFindContentIterator is a special iterator that also goes through any
+// existing <textarea>'s or text <input>'s editor to lookup the anonymous DOM
+// content there.
+//
+// Details:
+// 1) We use two iterators: The "outer-iterator" goes through the normal DOM.
+// The "inner-iterator" goes through the anonymous DOM inside the editor.
+//
+// 2) [MaybeSetupInnerIterator] As soon as the outer-iterator's current node is
+// changed, a check is made to see if the node is a <textarea> or a text <input>
+// node. If so, an inner-iterator is created to lookup the anynomous contents of
+// the editor underneath the text control.
+//
+// 3) When the inner-iterator is created, we position the outer-iterator 'after'
+// (or 'before' in backward search) the text control to avoid revisiting that
+// control.
+//
+// 4) As a consequence of searching through text controls, we can be called via
+// FindNext with the current selection inside a <textarea> or a text <input>.
+// This means that we can be given an initial search range that stretches across
+// the anonymous DOM and the normal DOM. To cater for this situation, we split
+// the anonymous part into the inner-iterator and then reposition the outer-
+// iterator outside.
+//
+// 5) The implementation assumes that First() and Next() are only called in
+// find-forward mode, while Last() and Prev() are used in find-backward.
+
+class nsFindContentIterator final : public nsIContentIterator
+{
+public:
+ explicit nsFindContentIterator(bool aFindBackward)
+ : mStartOffset(0)
+ , mEndOffset(0)
+ , mFindBackward(aFindBackward)
+ {
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsFindContentIterator)
+
+ // nsIContentIterator
+ virtual nsresult Init(nsINode* aRoot) override
+ {
+ NS_NOTREACHED("internal error");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ virtual nsresult Init(nsIDOMRange* aRange) override
+ {
+ NS_NOTREACHED("internal error");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ // Not a range because one of the endpoints may be anonymous.
+ nsresult Init(nsIDOMNode* aStartNode, int32_t aStartOffset,
+ nsIDOMNode* aEndNode, int32_t aEndOffset);
+ virtual void First() override;
+ virtual void Last() override;
+ virtual void Next() override;
+ virtual void Prev() override;
+ virtual nsINode* GetCurrentNode() override;
+ virtual bool IsDone() override;
+ virtual nsresult PositionAt(nsINode* aCurNode) override;
+
+protected:
+ virtual ~nsFindContentIterator() {}
+
+private:
+ static already_AddRefed<nsIDOMRange> CreateRange(nsINode* aNode)
+ {
+ RefPtr<nsRange> range = new nsRange(aNode);
+ range->SetMaySpanAnonymousSubtrees(true);
+ return range.forget();
+ }
+
+ nsCOMPtr<nsIContentIterator> mOuterIterator;
+ nsCOMPtr<nsIContentIterator> mInnerIterator;
+ // Can't use a range here, since we want to represent part of the flattened
+ // tree, including native anonymous content.
+ nsCOMPtr<nsIDOMNode> mStartNode;
+ int32_t mStartOffset;
+ nsCOMPtr<nsIDOMNode> mEndNode;
+ int32_t mEndOffset;
+
+ nsCOMPtr<nsIContent> mStartOuterContent;
+ nsCOMPtr<nsIContent> mEndOuterContent;
+ bool mFindBackward;
+
+ void Reset();
+ void MaybeSetupInnerIterator();
+ void SetupInnerIterator(nsIContent* aContent);
+};
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFindContentIterator)
+ NS_INTERFACE_MAP_ENTRY(nsIContentIterator)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFindContentIterator)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFindContentIterator)
+
+NS_IMPL_CYCLE_COLLECTION(nsFindContentIterator, mOuterIterator, mInnerIterator,
+ mStartOuterContent, mEndOuterContent, mEndNode,
+ mStartNode)
+
+nsresult
+nsFindContentIterator::Init(nsIDOMNode* aStartNode, int32_t aStartOffset,
+ nsIDOMNode* aEndNode, int32_t aEndOffset)
+{
+ NS_ENSURE_ARG_POINTER(aStartNode);
+ NS_ENSURE_ARG_POINTER(aEndNode);
+ if (!mOuterIterator) {
+ if (mFindBackward) {
+ // Use post-order in the reverse case, so we get parents before children
+ // in case we want to prevent descending into a node.
+ mOuterIterator = do_CreateInstance(kCContentIteratorCID);
+ } else {
+ // Use pre-order in the forward case, so we get parents before children in
+ // case we want to prevent descending into a node.
+ mOuterIterator = do_CreateInstance(kCPreContentIteratorCID);
+ }
+ NS_ENSURE_ARG_POINTER(mOuterIterator);
+ }
+
+ // Set up the search "range" that we will examine
+ mStartNode = aStartNode;
+ mStartOffset = aStartOffset;
+ mEndNode = aEndNode;
+ mEndOffset = aEndOffset;
+
+ return NS_OK;
+}
+
+void
+nsFindContentIterator::First()
+{
+ Reset();
+}
+
+void
+nsFindContentIterator::Last()
+{
+ Reset();
+}
+
+void
+nsFindContentIterator::Next()
+{
+ if (mInnerIterator) {
+ mInnerIterator->Next();
+ if (!mInnerIterator->IsDone()) {
+ return;
+ }
+
+ // by construction, mOuterIterator is already on the next node
+ } else {
+ mOuterIterator->Next();
+ }
+ MaybeSetupInnerIterator();
+}
+
+void
+nsFindContentIterator::Prev()
+{
+ if (mInnerIterator) {
+ mInnerIterator->Prev();
+ if (!mInnerIterator->IsDone()) {
+ return;
+ }
+
+ // by construction, mOuterIterator is already on the previous node
+ } else {
+ mOuterIterator->Prev();
+ }
+ MaybeSetupInnerIterator();
+}
+
+nsINode*
+nsFindContentIterator::GetCurrentNode()
+{
+ if (mInnerIterator && !mInnerIterator->IsDone()) {
+ return mInnerIterator->GetCurrentNode();
+ }
+ return mOuterIterator->GetCurrentNode();
+}
+
+bool
+nsFindContentIterator::IsDone()
+{
+ if (mInnerIterator && !mInnerIterator->IsDone()) {
+ return false;
+ }
+ return mOuterIterator->IsDone();
+}
+
+nsresult
+nsFindContentIterator::PositionAt(nsINode* aCurNode)
+{
+ nsINode* oldNode = mOuterIterator->GetCurrentNode();
+ nsresult rv = mOuterIterator->PositionAt(aCurNode);
+ if (NS_SUCCEEDED(rv)) {
+ MaybeSetupInnerIterator();
+ } else {
+ mOuterIterator->PositionAt(oldNode);
+ if (mInnerIterator) {
+ rv = mInnerIterator->PositionAt(aCurNode);
+ }
+ }
+ return rv;
+}
+
+void
+nsFindContentIterator::Reset()
+{
+ mInnerIterator = nullptr;
+ mStartOuterContent = nullptr;
+ mEndOuterContent = nullptr;
+
+ // As a consequence of searching through text controls, we may have been
+ // initialized with a selection inside a <textarea> or a text <input>.
+
+ // see if the start node is an anonymous text node inside a text control
+ nsCOMPtr<nsIContent> startContent(do_QueryInterface(mStartNode));
+ if (startContent) {
+ mStartOuterContent = startContent->FindFirstNonChromeOnlyAccessContent();
+ }
+
+ // see if the end node is an anonymous text node inside a text control
+ nsCOMPtr<nsIContent> endContent(do_QueryInterface(mEndNode));
+ if (endContent) {
+ mEndOuterContent = endContent->FindFirstNonChromeOnlyAccessContent();
+ }
+
+ // Note: OK to just set up the outer iterator here; if our range has a native
+ // anonymous endpoint we'll end up setting up an inner iterator, and reset the
+ // outer one in the process.
+ nsCOMPtr<nsINode> node = do_QueryInterface(mStartNode);
+ NS_ENSURE_TRUE_VOID(node);
+
+ nsCOMPtr<nsIDOMRange> range = CreateRange(node);
+ range->SetStart(mStartNode, mStartOffset);
+ range->SetEnd(mEndNode, mEndOffset);
+ mOuterIterator->Init(range);
+
+ if (!mFindBackward) {
+ if (mStartOuterContent != startContent) {
+ // the start node was an anonymous text node
+ SetupInnerIterator(mStartOuterContent);
+ if (mInnerIterator) {
+ mInnerIterator->First();
+ }
+ }
+ if (!mOuterIterator->IsDone()) {
+ mOuterIterator->First();
+ }
+ } else {
+ if (mEndOuterContent != endContent) {
+ // the end node was an anonymous text node
+ SetupInnerIterator(mEndOuterContent);
+ if (mInnerIterator) {
+ mInnerIterator->Last();
+ }
+ }
+ if (!mOuterIterator->IsDone()) {
+ mOuterIterator->Last();
+ }
+ }
+
+ // if we didn't create an inner-iterator, the boundary node could still be
+ // a text control, in which case we also need an inner-iterator straightaway
+ if (!mInnerIterator) {
+ MaybeSetupInnerIterator();
+ }
+}
+
+void
+nsFindContentIterator::MaybeSetupInnerIterator()
+{
+ mInnerIterator = nullptr;
+
+ nsCOMPtr<nsIContent> content =
+ do_QueryInterface(mOuterIterator->GetCurrentNode());
+ if (!content || !content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
+ return;
+ }
+
+ nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content));
+ if (!formControl->IsTextControl(true)) {
+ return;
+ }
+
+ SetupInnerIterator(content);
+ if (mInnerIterator) {
+ if (!mFindBackward) {
+ mInnerIterator->First();
+ // finish setup: position mOuterIterator on the actual "next" node (this
+ // completes its re-init, @see SetupInnerIterator)
+ if (!mOuterIterator->IsDone()) {
+ mOuterIterator->First();
+ }
+ } else {
+ mInnerIterator->Last();
+ // finish setup: position mOuterIterator on the actual "previous" node
+ // (this completes its re-init, @see SetupInnerIterator)
+ if (!mOuterIterator->IsDone()) {
+ mOuterIterator->Last();
+ }
+ }
+ }
+}
+
+void
+nsFindContentIterator::SetupInnerIterator(nsIContent* aContent)
+{
+ if (!aContent) {
+ return;
+ }
+ NS_ASSERTION(!aContent->IsRootOfNativeAnonymousSubtree(), "invalid call");
+
+ nsITextControlFrame* tcFrame = do_QueryFrame(aContent->GetPrimaryFrame());
+ if (!tcFrame) {
+ return;
+ }
+
+ nsCOMPtr<nsIEditor> editor;
+ tcFrame->GetEditor(getter_AddRefs(editor));
+ if (!editor) {
+ return;
+ }
+
+ // don't mess with disabled input fields
+ uint32_t editorFlags = 0;
+ editor->GetFlags(&editorFlags);
+ if (editorFlags & nsIPlaintextEditor::eEditorDisabledMask) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMElement> rootElement;
+ editor->GetRootElement(getter_AddRefs(rootElement));
+
+ nsCOMPtr<nsIDOMRange> innerRange = CreateRange(aContent);
+ nsCOMPtr<nsIDOMRange> outerRange = CreateRange(aContent);
+ if (!innerRange || !outerRange) {
+ return;
+ }
+
+ // now create the inner-iterator
+ mInnerIterator = do_CreateInstance(kCPreContentIteratorCID);
+
+ if (mInnerIterator) {
+ innerRange->SelectNodeContents(rootElement);
+
+ // fix up the inner bounds, we may have to only lookup a portion
+ // of the text control if the current node is a boundary point
+ if (aContent == mStartOuterContent) {
+ innerRange->SetStart(mStartNode, mStartOffset);
+ }
+ if (aContent == mEndOuterContent) {
+ innerRange->SetEnd(mEndNode, mEndOffset);
+ }
+ // Note: we just init here. We do First() or Last() later.
+ mInnerIterator->Init(innerRange);
+
+ // make sure to place the outer-iterator outside the text control so that we
+ // don't go there again.
+ nsresult res1, res2;
+ nsCOMPtr<nsIDOMNode> outerNode(do_QueryInterface(aContent));
+ if (!mFindBackward) { // find forward
+ // cut the outer-iterator after the current node
+ res1 = outerRange->SetEnd(mEndNode, mEndOffset);
+ res2 = outerRange->SetStartAfter(outerNode);
+ } else { // find backward
+ // cut the outer-iterator before the current node
+ res1 = outerRange->SetStart(mStartNode, mStartOffset);
+ res2 = outerRange->SetEndBefore(outerNode);
+ }
+ if (NS_FAILED(res1) || NS_FAILED(res2)) {
+ // we are done with the outer-iterator, the inner-iterator will traverse
+ // what we want
+ outerRange->Collapse(true);
+ }
+
+ // Note: we just re-init here, using the segment of our search range that
+ // is yet to be visited. Thus when we later do mOuterIterator->First() [or
+ // mOuterIterator->Last()], we will effectively be on the next node [or
+ // the previous node] _with respect to_ the search range.
+ mOuterIterator->Init(outerRange);
+ }
+}
+
+nsresult
+NS_NewFindContentIterator(bool aFindBackward, nsIContentIterator** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsFindContentIterator* it = new nsFindContentIterator(aFindBackward);
+ if (!it) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return it->QueryInterface(NS_GET_IID(nsIContentIterator), (void**)aResult);
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFind)
+ NS_INTERFACE_MAP_ENTRY(nsIFind)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFind)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFind)
+
+NS_IMPL_CYCLE_COLLECTION(nsFind, mLastBlockParent, mIterNode, mIterator)
+
+nsFind::nsFind()
+ : mFindBackward(false)
+ , mCaseSensitive(false)
+ , mIterOffset(0)
+{
+}
+
+nsFind::~nsFind()
+{
+}
+
+#ifdef DEBUG_FIND
+static void
+DumpNode(nsIDOMNode* aNode)
+{
+ if (!aNode) {
+ printf(">>>> Node: NULL\n");
+ return;
+ }
+ nsAutoString nodeName;
+ aNode->GetNodeName(nodeName);
+ nsCOMPtr<nsIContent> textContent(do_QueryInterface(aNode));
+ if (textContent && textContent->IsNodeOfType(nsINode::eTEXT)) {
+ nsAutoString newText;
+ textContent->AppendTextTo(newText);
+ printf(">>>> Text node (node name %s): '%s'\n",
+ NS_LossyConvertUTF16toASCII(nodeName).get(),
+ NS_LossyConvertUTF16toASCII(newText).get());
+ } else {
+ printf(">>>> Node: %s\n", NS_LossyConvertUTF16toASCII(nodeName).get());
+ }
+}
+#endif
+
+nsresult
+nsFind::InitIterator(nsIDOMNode* aStartNode, int32_t aStartOffset,
+ nsIDOMNode* aEndNode, int32_t aEndOffset)
+{
+ if (!mIterator) {
+ mIterator = new nsFindContentIterator(mFindBackward);
+ NS_ENSURE_TRUE(mIterator, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ NS_ENSURE_ARG_POINTER(aStartNode);
+ NS_ENSURE_ARG_POINTER(aEndNode);
+
+#ifdef DEBUG_FIND
+ printf("InitIterator search range:\n");
+ printf(" -- start %d, ", aStartOffset);
+ DumpNode(aStartNode);
+ printf(" -- end %d, ", aEndOffset);
+ DumpNode(aEndNode);
+#endif
+
+ nsresult rv = mIterator->Init(aStartNode, aStartOffset, aEndNode, aEndOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mFindBackward) {
+ mIterator->Last();
+ } else {
+ mIterator->First();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFind::GetFindBackwards(bool* aFindBackward)
+{
+ if (!aFindBackward) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ *aFindBackward = mFindBackward;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFind::SetFindBackwards(bool aFindBackward)
+{
+ mFindBackward = aFindBackward;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFind::GetCaseSensitive(bool* aCaseSensitive)
+{
+ if (!aCaseSensitive) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ *aCaseSensitive = mCaseSensitive;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFind::SetCaseSensitive(bool aCaseSensitive)
+{
+ mCaseSensitive = aCaseSensitive;
+ return NS_OK;
+}
+
+/* attribute boolean entireWord; */
+NS_IMETHODIMP
+nsFind::GetEntireWord(bool *aEntireWord)
+{
+ if (!aEntireWord)
+ return NS_ERROR_NULL_POINTER;
+
+ *aEntireWord = !!mWordBreaker;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFind::SetEntireWord(bool aEntireWord)
+{
+ mWordBreaker = aEntireWord ? nsContentUtils::WordBreaker() : nullptr;
+ return NS_OK;
+}
+
+// Here begins the find code. A ten-thousand-foot view of how it works: Find
+// needs to be able to compare across inline (but not block) nodes, e.g. find
+// for "abc" should match a<b>b</b>c. So after we've searched a node, we're not
+// done with it; in the case of a partial match we may need to reset the
+// iterator to go back to a previously visited node, so we always save the
+// "match anchor" node and offset.
+//
+// Text nodes store their text in an nsTextFragment, which is effectively a
+// union of a one-byte string or a two-byte string. Single and double strings
+// are intermixed in the dom. We don't have string classes which can deal with
+// intermixed strings, so all the handling is done explicitly here.
+
+nsresult
+nsFind::NextNode(nsIDOMRange* aSearchRange,
+ nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint,
+ bool aContinueOk)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIContent> content;
+
+ if (!mIterator || aContinueOk) {
+ // If we are continuing, that means we have a match in progress. In that
+ // case, we want to continue from the end point (where we are now) to the
+ // beginning/end of the search range.
+ nsCOMPtr<nsIDOMNode> startNode;
+ nsCOMPtr<nsIDOMNode> endNode;
+ uint32_t startOffset, endOffset;
+ if (aContinueOk) {
+#ifdef DEBUG_FIND
+ printf("Match in progress: continuing past endpoint\n");
+#endif
+ if (mFindBackward) {
+ aSearchRange->GetStartContainer(getter_AddRefs(startNode));
+ aSearchRange->GetStartOffset(&startOffset);
+ aEndPoint->GetStartContainer(getter_AddRefs(endNode));
+ aEndPoint->GetStartOffset(&endOffset);
+ } else { // forward
+ aEndPoint->GetEndContainer(getter_AddRefs(startNode));
+ aEndPoint->GetEndOffset(&startOffset);
+ aSearchRange->GetEndContainer(getter_AddRefs(endNode));
+ aSearchRange->GetEndOffset(&endOffset);
+ }
+ } else { // Normal, not continuing
+ if (mFindBackward) {
+ aSearchRange->GetStartContainer(getter_AddRefs(startNode));
+ aSearchRange->GetStartOffset(&startOffset);
+ aStartPoint->GetEndContainer(getter_AddRefs(endNode));
+ aStartPoint->GetEndOffset(&endOffset);
+ // XXX Needs work: Problem with this approach: if there is a match which
+ // starts just before the current selection and continues into the
+ // selection, we will miss it, because our search algorithm only starts
+ // searching from the end of the word, so we would have to search the
+ // current selection but discount any matches that fall entirely inside
+ // it.
+ } else { // forward
+ aStartPoint->GetStartContainer(getter_AddRefs(startNode));
+ aStartPoint->GetStartOffset(&startOffset);
+ aEndPoint->GetEndContainer(getter_AddRefs(endNode));
+ aEndPoint->GetEndOffset(&endOffset);
+ }
+ }
+
+ rv = InitIterator(startNode, static_cast<int32_t>(startOffset),
+ endNode, static_cast<int32_t>(endOffset));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!aStartPoint) {
+ aStartPoint = aSearchRange;
+ }
+
+ content = do_QueryInterface(mIterator->GetCurrentNode());
+#ifdef DEBUG_FIND
+ nsCOMPtr<nsIDOMNode> dnode(do_QueryInterface(content));
+ printf(":::::: Got the first node ");
+ DumpNode(dnode);
+#endif
+ if (content && content->IsNodeOfType(nsINode::eTEXT) &&
+ !SkipNode(content)) {
+ mIterNode = do_QueryInterface(content);
+ // Also set mIterOffset if appropriate:
+ nsCOMPtr<nsIDOMNode> node;
+ if (mFindBackward) {
+ aStartPoint->GetEndContainer(getter_AddRefs(node));
+ if (mIterNode.get() == node.get()) {
+ uint32_t endOffset;
+ aStartPoint->GetEndOffset(&endOffset);
+ mIterOffset = static_cast<int32_t>(endOffset);
+ } else {
+ mIterOffset = -1; // sign to start from end
+ }
+ } else {
+ aStartPoint->GetStartContainer(getter_AddRefs(node));
+ if (mIterNode.get() == node.get()) {
+ uint32_t startOffset;
+ aStartPoint->GetStartOffset(&startOffset);
+ mIterOffset = static_cast<int32_t>(startOffset);
+ } else {
+ mIterOffset = 0;
+ }
+ }
+#ifdef DEBUG_FIND
+ printf("Setting initial offset to %d\n", mIterOffset);
+#endif
+ return NS_OK;
+ }
+ }
+
+ while (true) {
+ if (mFindBackward) {
+ mIterator->Prev();
+ } else {
+ mIterator->Next();
+ }
+
+ content = do_QueryInterface(mIterator->GetCurrentNode());
+ if (!content) {
+ break;
+ }
+
+#ifdef DEBUG_FIND
+ nsCOMPtr<nsIDOMNode> dnode(do_QueryInterface(content));
+ printf(":::::: Got another node ");
+ DumpNode(dnode);
+#endif
+
+ // If we ever cross a block node, we might want to reset the match anchor:
+ // we don't match patterns extending across block boundaries. But we can't
+ // depend on this test here now, because the iterator doesn't give us the
+ // parent going in and going out, and we need it both times to depend on
+ // this.
+ //if (IsBlockNode(content))
+
+ // Now see if we need to skip this node -- e.g. is it part of a script or
+ // other invisible node? Note that we don't ask for CSS information; a node
+ // can be invisible due to CSS, and we'd still find it.
+ if (SkipNode(content)) {
+ continue;
+ }
+
+ if (content->IsNodeOfType(nsINode::eTEXT)) {
+ break;
+ }
+#ifdef DEBUG_FIND
+ dnode = do_QueryInterface(content);
+ printf("Not a text node: ");
+ DumpNode(dnode);
+#endif
+ }
+
+ if (content) {
+ mIterNode = do_QueryInterface(content);
+ } else {
+ mIterNode = nullptr;
+ }
+ mIterOffset = -1;
+
+#ifdef DEBUG_FIND
+ printf("Iterator gave: ");
+ DumpNode(mIterNode);
+#endif
+ return NS_OK;
+}
+
+class MOZ_STACK_CLASS PeekNextCharRestoreState final
+{
+public:
+ explicit PeekNextCharRestoreState(nsFind* aFind)
+ : mIterOffset(aFind->mIterOffset),
+ mIterNode(aFind->mIterNode),
+ mCurrNode(aFind->mIterator->GetCurrentNode()),
+ mFind(aFind)
+ {
+ }
+
+ ~PeekNextCharRestoreState()
+ {
+ mFind->mIterOffset = mIterOffset;
+ mFind->mIterNode = mIterNode;
+ mFind->mIterator->PositionAt(mCurrNode);
+ }
+
+private:
+ int32_t mIterOffset;
+ nsCOMPtr<nsIDOMNode> mIterNode;
+ nsCOMPtr<nsINode> mCurrNode;
+ RefPtr<nsFind> mFind;
+};
+
+char16_t
+nsFind::PeekNextChar(nsIDOMRange* aSearchRange,
+ nsIDOMRange* aStartPoint,
+ nsIDOMRange* aEndPoint)
+{
+ // We need to restore the necessary member variables before this function
+ // returns.
+ PeekNextCharRestoreState restoreState(this);
+
+ nsCOMPtr<nsIContent> tc;
+ nsresult rv;
+ const nsTextFragment *frag;
+ int32_t fragLen;
+
+ // Loop through non-block nodes until we find one that's not empty.
+ do {
+ tc = nullptr;
+ NextNode(aSearchRange, aStartPoint, aEndPoint, false);
+
+ // Get the text content:
+ tc = do_QueryInterface(mIterNode);
+
+ // Get the block parent.
+ nsCOMPtr<nsIDOMNode> blockParent;
+ rv = GetBlockParent(mIterNode, getter_AddRefs(blockParent));
+ if (NS_FAILED(rv))
+ return L'\0';
+
+ // If out of nodes or in new parent.
+ if (!mIterNode || !tc || (blockParent != mLastBlockParent))
+ return L'\0';
+
+ frag = tc->GetText();
+ fragLen = frag->GetLength();
+ } while (fragLen <= 0);
+
+ const char16_t *t2b = nullptr;
+ const char *t1b = nullptr;
+
+ if (frag->Is2b()) {
+ t2b = frag->Get2b();
+ } else {
+ t1b = frag->Get1b();
+ }
+
+ // Index of char to return.
+ int32_t index = mFindBackward ? fragLen - 1 : 0;
+
+ return t1b ? CHAR_TO_UNICHAR(t1b[index]) : t2b[index];
+}
+
+bool
+nsFind::IsBlockNode(nsIContent* aContent)
+{
+ if (aContent->IsAnyOfHTMLElements(nsGkAtoms::img,
+ nsGkAtoms::hr,
+ nsGkAtoms::th,
+ nsGkAtoms::td)) {
+ return true;
+ }
+
+ return nsContentUtils::IsHTMLBlock(aContent);
+}
+
+bool
+nsFind::IsTextNode(nsIDOMNode* aNode)
+{
+ uint16_t nodeType;
+ aNode->GetNodeType(&nodeType);
+
+ return nodeType == nsIDOMNode::TEXT_NODE ||
+ nodeType == nsIDOMNode::CDATA_SECTION_NODE;
+}
+
+bool
+nsFind::IsVisibleNode(nsIDOMNode* aDOMNode)
+{
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aDOMNode));
+ if (!content) {
+ return false;
+ }
+
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame) {
+ // No frame! Not visible then.
+ return false;
+ }
+
+ return frame->StyleVisibility()->IsVisible();
+}
+
+bool
+nsFind::SkipNode(nsIContent* aContent)
+{
+#ifdef HAVE_BIDI_ITERATOR
+ // We may not need to skip comment nodes, now that IsTextNode distinguishes
+ // them from real text nodes.
+ return aContent->IsNodeOfType(nsINode::eCOMMENT) ||
+ aContent->IsAnyOfHTMLElements(sScriptAtom, sNoframesAtom, sSelectAtom);
+
+#else /* HAVE_BIDI_ITERATOR */
+ // Temporary: eventually we will have an iterator to do this, but for now, we
+ // have to climb up the tree for each node and see whether any parent is a
+ // skipped node, and take the performance hit.
+
+ nsIContent* content = aContent;
+ while (content) {
+ if (aContent->IsNodeOfType(nsINode::eCOMMENT) ||
+ content->IsAnyOfHTMLElements(nsGkAtoms::script,
+ nsGkAtoms::noframes,
+ nsGkAtoms::select)) {
+#ifdef DEBUG_FIND
+ printf("Skipping node: ");
+ nsCOMPtr<nsIDOMNode> node(do_QueryInterface(content));
+ DumpNode(node);
+#endif
+
+ return true;
+ }
+
+ // Only climb to the nearest block node
+ if (IsBlockNode(content)) {
+ return false;
+ }
+
+ content = content->GetParent();
+ }
+
+ return false;
+#endif /* HAVE_BIDI_ITERATOR */
+}
+
+nsresult
+nsFind::GetBlockParent(nsIDOMNode* aNode, nsIDOMNode** aParent)
+{
+ while (aNode) {
+ nsCOMPtr<nsIDOMNode> parent;
+ nsresult rv = aNode->GetParentNode(getter_AddRefs(parent));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIContent> content(do_QueryInterface(parent));
+ if (content && IsBlockNode(content)) {
+ *aParent = parent;
+ NS_ADDREF(*aParent);
+ return NS_OK;
+ }
+ aNode = parent;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+// Call ResetAll before returning, to remove all references to external objects.
+void
+nsFind::ResetAll()
+{
+ mIterator = nullptr;
+ mLastBlockParent = nullptr;
+}
+
+#define NBSP_CHARCODE (CHAR_TO_UNICHAR(160))
+#define IsSpace(c) (nsCRT::IsAsciiSpace(c) || (c) == NBSP_CHARCODE)
+#define OVERFLOW_PINDEX (mFindBackward ? pindex < 0 : pindex > patLen)
+#define DONE_WITH_PINDEX (mFindBackward ? pindex <= 0 : pindex >= patLen)
+#define ALMOST_DONE_WITH_PINDEX (mFindBackward ? pindex <= 0 : pindex >= patLen - 1)
+
+// Take nodes out of the tree with NextNode, until null (NextNode will return 0
+// at the end of our range).
+NS_IMETHODIMP
+nsFind::Find(const nsAString& aPatText, nsIDOMRange* aSearchRange,
+ nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint,
+ nsIDOMRange** aRangeRet)
+{
+#ifdef DEBUG_FIND
+ printf("============== nsFind::Find('%s'%s, %p, %p, %p)\n",
+ NS_LossyConvertUTF16toASCII(aPatText).get(),
+ mFindBackward ? " (backward)" : " (forward)",
+ (void*)aSearchRange, (void*)aStartPoint, (void*)aEndPoint);
+#endif
+
+ NS_ENSURE_ARG(aSearchRange);
+ NS_ENSURE_ARG(aStartPoint);
+ NS_ENSURE_ARG(aEndPoint);
+ NS_ENSURE_ARG_POINTER(aRangeRet);
+ *aRangeRet = 0;
+
+ ResetAll();
+
+ nsAutoString patAutoStr(aPatText);
+ if (!mCaseSensitive) {
+ ToLowerCase(patAutoStr);
+ }
+
+ // Ignore soft hyphens in the pattern
+ static const char kShy[] = { char(CH_SHY), 0 };
+ patAutoStr.StripChars(kShy);
+
+ const char16_t* patStr = patAutoStr.get();
+ int32_t patLen = patAutoStr.Length() - 1;
+
+ // If this function is called with an empty string, we should early exit.
+ if (patLen < 0) {
+ return NS_OK;
+ }
+
+ // current offset into the pattern -- reset to beginning/end:
+ int32_t pindex = (mFindBackward ? patLen : 0);
+
+ // Current offset into the fragment
+ int32_t findex = 0;
+
+ // Direction to move pindex and ptr*
+ int incr = (mFindBackward ? -1 : 1);
+
+ nsCOMPtr<nsIContent> tc;
+ const nsTextFragment* frag = nullptr;
+ int32_t fragLen = 0;
+
+ // Pointers into the current fragment:
+ const char16_t* t2b = nullptr;
+ const char* t1b = nullptr;
+
+ // Keep track of when we're in whitespace:
+ // (only matters when we're matching)
+ bool inWhitespace = false;
+ // Keep track of whether the previous char was a word-breaking one.
+ bool wordBreakPrev = false;
+
+ // Place to save the range start point in case we find a match:
+ nsCOMPtr<nsIDOMNode> matchAnchorNode;
+ int32_t matchAnchorOffset = 0;
+
+ // Get the end point, so we know when to end searches:
+ nsCOMPtr<nsIDOMNode> endNode;
+ uint32_t endOffset;
+ aEndPoint->GetEndContainer(getter_AddRefs(endNode));
+ aEndPoint->GetEndOffset(&endOffset);
+
+ char16_t c = 0;
+ char16_t patc = 0;
+ char16_t prevChar = 0;
+ char16_t prevCharInMatch = 0;
+ while (1) {
+#ifdef DEBUG_FIND
+ printf("Loop ...\n");
+#endif
+
+ // If this is our first time on a new node, reset the pointers:
+ if (!frag) {
+
+ tc = nullptr;
+ NextNode(aSearchRange, aStartPoint, aEndPoint, false);
+ if (!mIterNode) { // Out of nodes
+ // Are we in the middle of a match? If so, try again with continuation.
+ if (matchAnchorNode) {
+ NextNode(aSearchRange, aStartPoint, aEndPoint, true);
+ }
+
+ // Reset the iterator, so this nsFind will be usable if the user wants
+ // to search again (from beginning/end).
+ ResetAll();
+ return NS_OK;
+ }
+
+ // We have a new text content. If its block parent is different from the
+ // block parent of the last text content, then we need to clear the match
+ // since we don't want to find across block boundaries.
+ nsCOMPtr<nsIDOMNode> blockParent;
+ GetBlockParent(mIterNode, getter_AddRefs(blockParent));
+#ifdef DEBUG_FIND
+ printf("New node: old blockparent = %p, new = %p\n",
+ (void*)mLastBlockParent.get(), (void*)blockParent.get());
+#endif
+ if (blockParent != mLastBlockParent) {
+#ifdef DEBUG_FIND
+ printf("Different block parent!\n");
+#endif
+ mLastBlockParent = blockParent;
+ // End any pending match:
+ matchAnchorNode = nullptr;
+ matchAnchorOffset = 0;
+ pindex = (mFindBackward ? patLen : 0);
+ inWhitespace = false;
+ }
+
+ // Get the text content:
+ tc = do_QueryInterface(mIterNode);
+ if (!tc || !(frag = tc->GetText())) { // Out of nodes
+ mIterator = nullptr;
+ mLastBlockParent = nullptr;
+ ResetAll();
+ return NS_OK;
+ }
+
+ fragLen = frag->GetLength();
+
+ // Set our starting point in this node. If we're going back to the anchor
+ // node, which means that we just ended a partial match, use the saved
+ // offset:
+ if (mIterNode == matchAnchorNode) {
+ findex = matchAnchorOffset + (mFindBackward ? 1 : 0);
+ }
+
+ // mIterOffset, if set, is the range's idea of an offset, and points
+ // between characters. But when translated to a string index, it points to
+ // a character. If we're going backward, this is one character too late
+ // and we'll match part of our previous pattern.
+ else if (mIterOffset >= 0) {
+ findex = mIterOffset - (mFindBackward ? 1 : 0);
+ }
+
+ // Otherwise, just start at the appropriate end of the fragment:
+ else if (mFindBackward) {
+ findex = fragLen - 1;
+ } else {
+ findex = 0;
+ }
+
+ // Offset can only apply to the first node:
+ mIterOffset = -1;
+
+ // If this is outside the bounds of the string, then skip this node:
+ if (findex < 0 || findex > fragLen - 1) {
+#ifdef DEBUG_FIND
+ printf("At the end of a text node -- skipping to the next\n");
+#endif
+ frag = 0;
+ continue;
+ }
+
+#ifdef DEBUG_FIND
+ printf("Starting from offset %d\n", findex);
+#endif
+ if (frag->Is2b()) {
+ t2b = frag->Get2b();
+ t1b = nullptr;
+#ifdef DEBUG_FIND
+ nsAutoString str2(t2b, fragLen);
+ printf("2 byte, '%s'\n", NS_LossyConvertUTF16toASCII(str2).get());
+#endif
+ } else {
+ t1b = frag->Get1b();
+ t2b = nullptr;
+#ifdef DEBUG_FIND
+ nsAutoCString str1(t1b, fragLen);
+ printf("1 byte, '%s'\n", str1.get());
+#endif
+ }
+ } else {
+ // Still on the old node. Advance the pointers, then see if we need to
+ // pull a new node.
+ findex += incr;
+#ifdef DEBUG_FIND
+ printf("Same node -- (%d, %d)\n", pindex, findex);
+#endif
+ if (mFindBackward ? (findex < 0) : (findex >= fragLen)) {
+#ifdef DEBUG_FIND
+ printf("Will need to pull a new node: mAO = %d, frag len=%d\n",
+ matchAnchorOffset, fragLen);
+#endif
+ // Done with this node. Pull a new one.
+ frag = nullptr;
+ continue;
+ }
+ }
+
+ // Have we gone past the endpoint yet? If we have, and we're not in the
+ // middle of a match, return.
+ if (mIterNode == endNode &&
+ ((mFindBackward && findex < static_cast<int32_t>(endOffset)) ||
+ (!mFindBackward && findex > static_cast<int32_t>(endOffset)))) {
+ ResetAll();
+ return NS_OK;
+ }
+
+ // Save the previous character for word boundary detection
+ prevChar = c;
+ // The two characters we'll be comparing:
+ c = (t2b ? t2b[findex] : CHAR_TO_UNICHAR(t1b[findex]));
+ patc = patStr[pindex];
+
+#ifdef DEBUG_FIND
+ printf("Comparing '%c'=%x to '%c' (%d of %d), findex=%d%s\n",
+ (char)c, (int)c, patc, pindex, patLen, findex,
+ inWhitespace ? " (inWhitespace)" : "");
+#endif
+
+ // Do we need to go back to non-whitespace mode? If inWhitespace, then this
+ // space in the pat str has already matched at least one space in the
+ // document.
+ if (inWhitespace && !IsSpace(c)) {
+ inWhitespace = false;
+ pindex += incr;
+#ifdef DEBUG
+ // This shouldn't happen -- if we were still matching, and we were at the
+ // end of the pat string, then we should have caught it in the last
+ // iteration and returned success.
+ if (OVERFLOW_PINDEX) {
+ NS_ASSERTION(false, "Missed a whitespace match");
+ }
+#endif
+ patc = patStr[pindex];
+ }
+ if (!inWhitespace && IsSpace(patc)) {
+ inWhitespace = true;
+ } else if (!inWhitespace && !mCaseSensitive && IsUpperCase(c)) {
+ c = ToLowerCase(c);
+ }
+
+ if (c == CH_SHY) {
+ // ignore soft hyphens in the document
+ continue;
+ }
+
+ if (!mCaseSensitive) {
+ switch (c) {
+ // treat curly and straight quotes as identical
+ case CH_LEFT_SINGLE_QUOTE:
+ case CH_RIGHT_SINGLE_QUOTE:
+ c = CH_APOSTROPHE;
+ break;
+ case CH_LEFT_DOUBLE_QUOTE:
+ case CH_RIGHT_DOUBLE_QUOTE:
+ c = CH_QUOTE;
+ break;
+ }
+
+ switch (patc) {
+ // treat curly and straight quotes as identical
+ case CH_LEFT_SINGLE_QUOTE:
+ case CH_RIGHT_SINGLE_QUOTE:
+ patc = CH_APOSTROPHE;
+ break;
+ case CH_LEFT_DOUBLE_QUOTE:
+ case CH_RIGHT_DOUBLE_QUOTE:
+ patc = CH_QUOTE;
+ break;
+ }
+ }
+
+ // a '\n' between CJ characters is ignored
+ if (pindex != (mFindBackward ? patLen : 0) && c != patc && !inWhitespace) {
+ if (c == '\n' && t2b && IS_CJ_CHAR(prevCharInMatch)) {
+ int32_t nindex = findex + incr;
+ if (mFindBackward ? (nindex >= 0) : (nindex < fragLen)) {
+ if (IS_CJ_CHAR(t2b[nindex])) {
+ continue;
+ }
+ }
+ }
+ }
+
+ wordBreakPrev = false;
+ if (mWordBreaker) {
+ if (prevChar == NBSP_CHARCODE)
+ prevChar = CHAR_TO_UNICHAR(' ');
+ wordBreakPrev = mWordBreaker->BreakInBetween(&prevChar, 1, &c, 1);
+ }
+
+ // Compare. Match if we're in whitespace and c is whitespace, or if the
+ // characters match and at least one of the following is true:
+ // a) we're not matching the entire word
+ // b) a match has already been stored
+ // c) the previous character is a different "class" than the current character.
+ if ((c == patc && (!mWordBreaker || matchAnchorNode || wordBreakPrev)) ||
+ (inWhitespace && IsSpace(c)))
+ {
+ prevCharInMatch = c;
+#ifdef DEBUG_FIND
+ if (inWhitespace) {
+ printf("YES (whitespace)(%d of %d)\n", pindex, patLen);
+ } else {
+ printf("YES! '%c' == '%c' (%d of %d)\n", c, patc, pindex, patLen);
+ }
+#endif
+
+ // Save the range anchors if we haven't already:
+ if (!matchAnchorNode) {
+ matchAnchorNode = mIterNode;
+ matchAnchorOffset = findex;
+ }
+
+ // Are we done?
+ if (DONE_WITH_PINDEX) {
+ // Matched the whole string!
+#ifdef DEBUG_FIND
+ printf("Found a match!\n");
+#endif
+
+ // Make the range:
+ nsCOMPtr<nsIDOMNode> startParent;
+ nsCOMPtr<nsIDOMNode> endParent;
+
+ // Check for word break (if necessary)
+ if (mWordBreaker) {
+ int32_t nextfindex = findex + incr;
+
+ char16_t nextChar;
+ // If still in array boundaries, get nextChar.
+ if (mFindBackward ? (nextfindex >= 0) : (nextfindex < fragLen))
+ nextChar = (t2b ? t2b[nextfindex] : CHAR_TO_UNICHAR(t1b[nextfindex]));
+ // Get next character from the next node.
+ else
+ nextChar = PeekNextChar(aSearchRange, aStartPoint, aEndPoint);
+
+ if (nextChar == NBSP_CHARCODE)
+ nextChar = CHAR_TO_UNICHAR(' ');
+
+ // If a word break isn't there when it needs to be, reset search.
+ if (!mWordBreaker->BreakInBetween(&c, 1, &nextChar, 1)) {
+ matchAnchorNode = nullptr;
+ continue;
+ }
+ }
+
+ nsCOMPtr<nsIDOMRange> range = new nsRange(tc);
+ if (range) {
+ int32_t matchStartOffset, matchEndOffset;
+ // convert char index to range point:
+ int32_t mao = matchAnchorOffset + (mFindBackward ? 1 : 0);
+ if (mFindBackward) {
+ startParent = do_QueryInterface(tc);
+ endParent = matchAnchorNode;
+ matchStartOffset = findex;
+ matchEndOffset = mao;
+ } else {
+ startParent = matchAnchorNode;
+ endParent = do_QueryInterface(tc);
+ matchStartOffset = mao;
+ matchEndOffset = findex + 1;
+ }
+ if (startParent && endParent &&
+ IsVisibleNode(startParent) && IsVisibleNode(endParent)) {
+ range->SetStart(startParent, matchStartOffset);
+ range->SetEnd(endParent, matchEndOffset);
+ *aRangeRet = range.get();
+ NS_ADDREF(*aRangeRet);
+ } else {
+ // This match is no good -- invisible or bad range
+ startParent = nullptr;
+ }
+ }
+
+ if (startParent) {
+ // If startParent == nullptr, we didn't successfully make range
+ // or, we didn't make a range because the start or end node were
+ // invisible. Reset the offset to the other end of the found string:
+ mIterOffset = findex + (mFindBackward ? 1 : 0);
+#ifdef DEBUG_FIND
+ printf("mIterOffset = %d, mIterNode = ", mIterOffset);
+ DumpNode(mIterNode);
+#endif
+
+ ResetAll();
+ return NS_OK;
+ }
+ // This match is no good, continue on in document
+ matchAnchorNode = nullptr;
+ }
+
+ if (matchAnchorNode) {
+ // Not done, but still matching. Advance and loop around for the next
+ // characters. But don't advance from a space to a non-space:
+ if (!inWhitespace || DONE_WITH_PINDEX ||
+ IsSpace(patStr[pindex + incr])) {
+ pindex += incr;
+ inWhitespace = false;
+#ifdef DEBUG_FIND
+ printf("Advancing pindex to %d\n", pindex);
+#endif
+ }
+
+ continue;
+ }
+ }
+
+#ifdef DEBUG_FIND
+ printf("NOT: %c == %c\n", c, patc);
+#endif
+
+ // If we didn't match, go back to the beginning of patStr, and set findex
+ // back to the next char after we started the current match.
+ if (matchAnchorNode) { // we're ending a partial match
+ findex = matchAnchorOffset;
+ mIterOffset = matchAnchorOffset;
+ // +incr will be added to findex when we continue
+
+ // Are we going back to a previous node?
+ if (matchAnchorNode != mIterNode) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(matchAnchorNode));
+ DebugOnly<nsresult> rv = NS_ERROR_UNEXPECTED;
+ if (content) {
+ rv = mIterator->PositionAt(content);
+ }
+ frag = 0;
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Text content wasn't nsIContent!");
+#ifdef DEBUG_FIND
+ printf("Repositioned anchor node\n");
+#endif
+ }
+#ifdef DEBUG_FIND
+ printf("Ending a partial match; findex -> %d, mIterOffset -> %d\n",
+ findex, mIterOffset);
+#endif
+ }
+ matchAnchorNode = nullptr;
+ matchAnchorOffset = 0;
+ inWhitespace = false;
+ pindex = (mFindBackward ? patLen : 0);
+#ifdef DEBUG_FIND
+ printf("Setting findex back to %d, pindex to %d\n", findex, pindex);
+
+#endif
+ }
+
+ // Out of nodes, and didn't match.
+ ResetAll();
+ return NS_OK;
+}
diff --git a/components/find/src/nsFind.h b/components/find/src/nsFind.h
new file mode 100644
index 000000000..9ed447466
--- /dev/null
+++ b/components/find/src/nsFind.h
@@ -0,0 +1,82 @@
+/* -*- 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/. */
+
+#ifndef nsFind_h__
+#define nsFind_h__
+
+#include "nsIFind.h"
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMRange.h"
+#include "nsIContentIterator.h"
+#include "nsIWordBreaker.h"
+
+class nsIContent;
+
+#define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1"
+
+#define NS_FIND_CID \
+ {0x471f4944, 0x1dd2, 0x11b2, {0x87, 0xac, 0x90, 0xbe, 0x0a, 0x51, 0xd6, 0x09}}
+
+class nsFindContentIterator;
+
+class nsFind : public nsIFind
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIFIND
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsFind)
+
+ nsFind();
+
+protected:
+ virtual ~nsFind();
+
+ // Parameters set from the interface:
+ //nsCOMPtr<nsIDOMRange> mRange; // search only in this range
+ bool mFindBackward;
+ bool mCaseSensitive;
+
+ // Use "find entire words" mode by setting to a word breaker or null, to
+ // disable "entire words" mode.
+ nsCOMPtr<nsIWordBreaker> mWordBreaker;
+
+ int32_t mIterOffset;
+ nsCOMPtr<nsIDOMNode> mIterNode;
+
+ // Last block parent, so that we will notice crossing block boundaries:
+ nsCOMPtr<nsIDOMNode> mLastBlockParent;
+ nsresult GetBlockParent(nsIDOMNode* aNode, nsIDOMNode** aParent);
+
+ // Utility routines:
+ bool IsTextNode(nsIDOMNode* aNode);
+ bool IsBlockNode(nsIContent* aNode);
+ bool SkipNode(nsIContent* aNode);
+ bool IsVisibleNode(nsIDOMNode* aNode);
+
+ // Move in the right direction for our search:
+ nsresult NextNode(nsIDOMRange* aSearchRange,
+ nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint,
+ bool aContinueOk);
+
+ // Get the first character from the next node (last if mFindBackward).
+ char16_t PeekNextChar(nsIDOMRange* aSearchRange,
+ nsIDOMRange* aStartPoint,
+ nsIDOMRange* aEndPoint);
+
+ // Reset variables before returning -- don't hold any references.
+ void ResetAll();
+
+ // The iterator we use to move through the document:
+ nsresult InitIterator(nsIDOMNode* aStartNode, int32_t aStartOffset,
+ nsIDOMNode* aEndNode, int32_t aEndOffset);
+ RefPtr<nsFindContentIterator> mIterator;
+
+ friend class PeekNextCharRestoreState;
+};
+
+#endif // nsFind_h__
diff --git a/components/find/src/nsFindService.cpp b/components/find/src/nsFindService.cpp
new file mode 100644
index 000000000..3e80823ce
--- /dev/null
+++ b/components/find/src/nsFindService.cpp
@@ -0,0 +1,101 @@
+/* -*- 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/. */
+
+/*
+ * The sole purpose of the Find service is to store globally the
+ * last used Find settings
+ *
+ */
+
+
+#include "nsFindService.h"
+
+
+nsFindService::nsFindService()
+: mFindBackwards(false)
+, mWrapFind(true)
+, mEntireWord(false)
+, mMatchCase(false)
+{
+}
+
+
+nsFindService::~nsFindService()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsFindService, nsIFindService)
+
+NS_IMETHODIMP nsFindService::GetSearchString(nsAString & aSearchString)
+{
+ aSearchString = mSearchString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFindService::SetSearchString(const nsAString & aSearchString)
+{
+ mSearchString = aSearchString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFindService::GetReplaceString(nsAString & aReplaceString)
+{
+ aReplaceString = mReplaceString;
+ return NS_OK;
+}
+NS_IMETHODIMP nsFindService::SetReplaceString(const nsAString & aReplaceString)
+{
+ mReplaceString = aReplaceString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFindService::GetFindBackwards(bool *aFindBackwards)
+{
+ NS_ENSURE_ARG_POINTER(aFindBackwards);
+ *aFindBackwards = mFindBackwards;
+ return NS_OK;
+}
+NS_IMETHODIMP nsFindService::SetFindBackwards(bool aFindBackwards)
+{
+ mFindBackwards = aFindBackwards;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFindService::GetWrapFind(bool *aWrapFind)
+{
+ NS_ENSURE_ARG_POINTER(aWrapFind);
+ *aWrapFind = mWrapFind;
+ return NS_OK;
+}
+NS_IMETHODIMP nsFindService::SetWrapFind(bool aWrapFind)
+{
+ mWrapFind = aWrapFind;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFindService::GetEntireWord(bool *aEntireWord)
+{
+ NS_ENSURE_ARG_POINTER(aEntireWord);
+ *aEntireWord = mEntireWord;
+ return NS_OK;
+}
+NS_IMETHODIMP nsFindService::SetEntireWord(bool aEntireWord)
+{
+ mEntireWord = aEntireWord;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFindService::GetMatchCase(bool *aMatchCase)
+{
+ NS_ENSURE_ARG_POINTER(aMatchCase);
+ *aMatchCase = mMatchCase;
+ return NS_OK;
+}
+NS_IMETHODIMP nsFindService::SetMatchCase(bool aMatchCase)
+{
+ mMatchCase = aMatchCase;
+ return NS_OK;
+}
+
diff --git a/components/find/src/nsFindService.h b/components/find/src/nsFindService.h
new file mode 100644
index 000000000..6b391d5bf
--- /dev/null
+++ b/components/find/src/nsFindService.h
@@ -0,0 +1,46 @@
+/* -*- 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/. */
+
+/*
+ * The sole purpose of the Find service is to store globally the
+ * last used Find settings
+ *
+ */
+
+#include "nsString.h"
+
+#include "nsIFindService.h"
+
+
+// {5060b803-340e-11d5-be5b-b3e063ec6a3c}
+#define NS_FIND_SERVICE_CID \
+ {0x5060b803, 0x340e, 0x11d5, {0xbe, 0x5b, 0xb3, 0xe0, 0x63, 0xec, 0x6a, 0x3c}}
+
+
+#define NS_FIND_SERVICE_CONTRACTID \
+ "@mozilla.org/find/find_service;1"
+
+
+class nsFindService : public nsIFindService
+{
+public:
+
+ nsFindService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFINDSERVICE
+
+protected:
+
+ virtual ~nsFindService();
+
+ nsString mSearchString;
+ nsString mReplaceString;
+
+ bool mFindBackwards;
+ bool mWrapFind;
+ bool mEntireWord;
+ bool mMatchCase;
+};
diff --git a/components/find/src/nsWebBrowserFind.cpp b/components/find/src/nsWebBrowserFind.cpp
new file mode 100644
index 000000000..b5c7eebfa
--- /dev/null
+++ b/components/find/src/nsWebBrowserFind.cpp
@@ -0,0 +1,866 @@
+/* -*- 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 "nsWebBrowserFind.h"
+
+// Only need this for NS_FIND_CONTRACTID,
+// else we could use nsIDOMRange.h and nsIFind.h.
+#include "nsFind.h"
+#include "nsRange.h"
+#include "nsIComponentManager.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIURI.h"
+#include "nsIDocShell.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsISelectionController.h"
+#include "nsISelection.h"
+#include "nsIFrame.h"
+#include "nsITextControlFrame.h"
+#include "nsReadableUtils.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIContent.h"
+#include "nsContentCID.h"
+#include "nsIServiceManager.h"
+#include "nsIObserverService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsFind.h"
+#include "nsError.h"
+#include "nsFocusManager.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/Element.h"
+#include "nsISimpleEnumerator.h"
+#include "nsContentUtils.h"
+
+#if DEBUG
+#include "nsIWebNavigation.h"
+#include "nsXPIDLString.h"
+#endif
+
+nsWebBrowserFind::nsWebBrowserFind()
+ : mFindBackwards(false)
+ , mWrapFind(false)
+ , mEntireWord(false)
+ , mMatchCase(false)
+ , mSearchSubFrames(true)
+ , mSearchParentFrames(true)
+{
+}
+
+nsWebBrowserFind::~nsWebBrowserFind()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsWebBrowserFind, nsIWebBrowserFind,
+ nsIWebBrowserFindInFrames)
+
+NS_IMETHODIMP
+nsWebBrowserFind::FindNext(bool* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ NS_ENSURE_TRUE(CanFindNext(), NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsPIDOMWindowOuter> searchFrame = do_QueryReferent(mCurrentSearchFrame);
+ NS_ENSURE_TRUE(searchFrame, NS_ERROR_NOT_INITIALIZED);
+
+ nsCOMPtr<nsPIDOMWindowOuter> rootFrame = do_QueryReferent(mRootSearchFrame);
+ NS_ENSURE_TRUE(rootFrame, NS_ERROR_NOT_INITIALIZED);
+
+ // first, if there's a "cmd_findagain" observer around, check to see if it
+ // wants to perform the find again command . If it performs the find again
+ // it will return true, in which case we exit ::FindNext() early.
+ // Otherwise, nsWebBrowserFind needs to perform the find again command itself
+ // this is used by nsTypeAheadFind, which controls find again when it was
+ // the last executed find in the current window.
+ nsCOMPtr<nsIObserverService> observerSvc =
+ mozilla::services::GetObserverService();
+ if (observerSvc) {
+ nsCOMPtr<nsISupportsInterfacePointer> windowSupportsData =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISupports> searchWindowSupports = do_QueryInterface(rootFrame);
+ windowSupportsData->SetData(searchWindowSupports);
+ NS_NAMED_LITERAL_STRING(dnStr, "down");
+ NS_NAMED_LITERAL_STRING(upStr, "up");
+ observerSvc->NotifyObservers(windowSupportsData,
+ "nsWebBrowserFind_FindAgain",
+ mFindBackwards ? upStr.get() : dnStr.get());
+ windowSupportsData->GetData(getter_AddRefs(searchWindowSupports));
+ // findnext performed if search window data cleared out
+ *aResult = searchWindowSupports == nullptr;
+ if (*aResult) {
+ return NS_OK;
+ }
+ }
+
+ // next, look in the current frame. If found, return.
+
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ rv = SearchInFrame(searchFrame, false, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (*aResult) {
+ return OnFind(searchFrame); // we are done
+ }
+
+ // if we are not searching other frames, return
+ if (!mSearchSubFrames && !mSearchParentFrames) {
+ return NS_OK;
+ }
+
+ nsIDocShell* rootDocShell = rootFrame->GetDocShell();
+ if (!rootDocShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t enumDirection = mFindBackwards ? nsIDocShell::ENUMERATE_BACKWARDS :
+ nsIDocShell::ENUMERATE_FORWARDS;
+
+ nsCOMPtr<nsISimpleEnumerator> docShellEnumerator;
+ rv = rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll,
+ enumDirection,
+ getter_AddRefs(docShellEnumerator));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // remember where we started
+ nsCOMPtr<nsIDocShellTreeItem> startingItem =
+ do_QueryInterface(searchFrame->GetDocShell(), &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> curItem;
+
+ // XXX We should avoid searching in frameset documents here.
+ // We also need to honour mSearchSubFrames and mSearchParentFrames.
+ bool hasMore, doFind = false;
+ while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ nsCOMPtr<nsISupports> curSupports;
+ rv = docShellEnumerator->GetNext(getter_AddRefs(curSupports));
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ curItem = do_QueryInterface(curSupports, &rv);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ if (doFind) {
+ searchFrame = curItem->GetWindow();
+ if (!searchFrame) {
+ break;
+ }
+
+ OnStartSearchFrame(searchFrame);
+
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ rv = SearchInFrame(searchFrame, false, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (*aResult) {
+ return OnFind(searchFrame); // we are done
+ }
+
+ OnEndSearchFrame(searchFrame);
+ }
+
+ if (curItem.get() == startingItem.get()) {
+ doFind = true; // start looking in frames after this one
+ }
+ }
+
+ if (!mWrapFind) {
+ // remember where we left off
+ SetCurrentSearchFrame(searchFrame);
+ return NS_OK;
+ }
+
+ // From here on, we're wrapping, first through the other frames, then finally
+ // from the beginning of the starting frame back to the starting point.
+
+ // because nsISimpleEnumerator is totally lame and isn't resettable, I have to
+ // make a new one
+ docShellEnumerator = nullptr;
+ rv = rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll,
+ enumDirection,
+ getter_AddRefs(docShellEnumerator));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ nsCOMPtr<nsISupports> curSupports;
+ rv = docShellEnumerator->GetNext(getter_AddRefs(curSupports));
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ curItem = do_QueryInterface(curSupports, &rv);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ searchFrame = curItem->GetWindow();
+ if (!searchFrame) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ if (curItem.get() == startingItem.get()) {
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ rv = SearchInFrame(searchFrame, true, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (*aResult) {
+ return OnFind(searchFrame); // we are done
+ }
+ break;
+ }
+
+ OnStartSearchFrame(searchFrame);
+
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ rv = SearchInFrame(searchFrame, false, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (*aResult) {
+ return OnFind(searchFrame); // we are done
+ }
+
+ OnEndSearchFrame(searchFrame);
+ }
+
+ // remember where we left off
+ SetCurrentSearchFrame(searchFrame);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Something failed");
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::GetSearchString(char16_t** aSearchString)
+{
+ NS_ENSURE_ARG_POINTER(aSearchString);
+ *aSearchString = ToNewUnicode(mSearchString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::SetSearchString(const char16_t* aSearchString)
+{
+ mSearchString.Assign(aSearchString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::GetFindBackwards(bool* aFindBackwards)
+{
+ NS_ENSURE_ARG_POINTER(aFindBackwards);
+ *aFindBackwards = mFindBackwards;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::SetFindBackwards(bool aFindBackwards)
+{
+ mFindBackwards = aFindBackwards;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::GetWrapFind(bool* aWrapFind)
+{
+ NS_ENSURE_ARG_POINTER(aWrapFind);
+ *aWrapFind = mWrapFind;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::SetWrapFind(bool aWrapFind)
+{
+ mWrapFind = aWrapFind;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::GetEntireWord(bool* aEntireWord)
+{
+ NS_ENSURE_ARG_POINTER(aEntireWord);
+ *aEntireWord = mEntireWord;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::SetEntireWord(bool aEntireWord)
+{
+ mEntireWord = aEntireWord;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::GetMatchCase(bool* aMatchCase)
+{
+ NS_ENSURE_ARG_POINTER(aMatchCase);
+ *aMatchCase = mMatchCase;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::SetMatchCase(bool aMatchCase)
+{
+ mMatchCase = aMatchCase;
+ return NS_OK;
+}
+
+static bool
+IsInNativeAnonymousSubtree(nsIContent* aContent)
+{
+ while (aContent) {
+ nsIContent* bindingParent = aContent->GetBindingParent();
+ if (bindingParent == aContent) {
+ return true;
+ }
+
+ aContent = bindingParent;
+ }
+
+ return false;
+}
+
+void
+nsWebBrowserFind::SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow,
+ nsIDOMRange* aRange)
+{
+ nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
+ if (!doc) {
+ return;
+ }
+
+ nsIPresShell* presShell = doc->GetShell();
+ if (!presShell) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMNode> node;
+ aRange->GetStartContainer(getter_AddRefs(node));
+ nsCOMPtr<nsIContent> content(do_QueryInterface(node));
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame) {
+ return;
+ }
+ nsCOMPtr<nsISelectionController> selCon;
+ frame->GetSelectionController(presShell->GetPresContext(),
+ getter_AddRefs(selCon));
+
+ // since the match could be an anonymous textnode inside a
+ // <textarea> or text <input>, we need to get the outer frame
+ nsITextControlFrame* tcFrame = nullptr;
+ for (; content; content = content->GetParent()) {
+ if (!IsInNativeAnonymousSubtree(content)) {
+ nsIFrame* f = content->GetPrimaryFrame();
+ if (!f) {
+ return;
+ }
+ tcFrame = do_QueryFrame(f);
+ break;
+ }
+ }
+
+ nsCOMPtr<nsISelection> selection;
+
+ selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
+ selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(selection));
+ if (selection) {
+ selection->RemoveAllRanges();
+ selection->AddRange(aRange);
+
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ if (fm) {
+ if (tcFrame) {
+ nsCOMPtr<nsIDOMElement> newFocusedElement(do_QueryInterface(content));
+ fm->SetFocus(newFocusedElement, nsIFocusManager::FLAG_NOSCROLL);
+ } else {
+ nsCOMPtr<nsIDOMElement> result;
+ fm->MoveFocus(aWindow, nullptr, nsIFocusManager::MOVEFOCUS_CARET,
+ nsIFocusManager::FLAG_NOSCROLL, getter_AddRefs(result));
+ }
+ }
+
+ // Scroll if necessary to make the selection visible:
+ // Must be the last thing to do - bug 242056
+
+ // After ScrollSelectionIntoView(), the pending notifications might be
+ // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+ selCon->ScrollSelectionIntoView(
+ nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_WHOLE_SELECTION,
+ nsISelectionController::SCROLL_CENTER_VERTICALLY |
+ nsISelectionController::SCROLL_SYNCHRONOUS);
+ }
+}
+
+// Adapted from nsTextServicesDocument::GetDocumentContentRootNode
+nsresult
+nsWebBrowserFind::GetRootNode(nsIDOMDocument* aDomDoc, nsIDOMNode** aNode)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aNode);
+ *aNode = 0;
+
+ nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(aDomDoc);
+ if (htmlDoc) {
+ // For HTML documents, the content root node is the body.
+ nsCOMPtr<nsIDOMHTMLElement> bodyElement;
+ rv = htmlDoc->GetBody(getter_AddRefs(bodyElement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_ARG_POINTER(bodyElement);
+ bodyElement.forget(aNode);
+ return NS_OK;
+ }
+
+ // For non-HTML documents, the content root node will be the doc element.
+ nsCOMPtr<nsIDOMElement> docElement;
+ rv = aDomDoc->GetDocumentElement(getter_AddRefs(docElement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_ARG_POINTER(docElement);
+ docElement.forget(aNode);
+ return NS_OK;
+}
+
+nsresult
+nsWebBrowserFind::SetRangeAroundDocument(nsIDOMRange* aSearchRange,
+ nsIDOMRange* aStartPt,
+ nsIDOMRange* aEndPt,
+ nsIDOMDocument* aDoc)
+{
+ nsCOMPtr<nsIDOMNode> bodyNode;
+ nsresult rv = GetRootNode(aDoc, getter_AddRefs(bodyNode));
+ nsCOMPtr<nsIContent> bodyContent(do_QueryInterface(bodyNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_ARG_POINTER(bodyContent);
+
+ uint32_t childCount = bodyContent->GetChildCount();
+
+ aSearchRange->SetStart(bodyNode, 0);
+ aSearchRange->SetEnd(bodyNode, childCount);
+
+ if (mFindBackwards) {
+ aStartPt->SetStart(bodyNode, childCount);
+ aStartPt->SetEnd(bodyNode, childCount);
+ aEndPt->SetStart(bodyNode, 0);
+ aEndPt->SetEnd(bodyNode, 0);
+ } else {
+ aStartPt->SetStart(bodyNode, 0);
+ aStartPt->SetEnd(bodyNode, 0);
+ aEndPt->SetStart(bodyNode, childCount);
+ aEndPt->SetEnd(bodyNode, childCount);
+ }
+
+ return NS_OK;
+}
+
+// Set the range to go from the end of the current selection to the end of the
+// document (forward), or beginning to beginning (reverse). or around the whole
+// document if there's no selection.
+nsresult
+nsWebBrowserFind::GetSearchLimits(nsIDOMRange* aSearchRange,
+ nsIDOMRange* aStartPt, nsIDOMRange* aEndPt,
+ nsIDOMDocument* aDoc, nsISelection* aSel,
+ bool aWrap)
+{
+ NS_ENSURE_ARG_POINTER(aSel);
+
+ // There is a selection.
+ int32_t count = -1;
+ nsresult rv = aSel->GetRangeCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (count < 1) {
+ return SetRangeAroundDocument(aSearchRange, aStartPt, aEndPt, aDoc);
+ }
+
+ // Need bodyNode, for the start/end of the document
+ nsCOMPtr<nsIDOMNode> bodyNode;
+ rv = GetRootNode(aDoc, getter_AddRefs(bodyNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIContent> bodyContent(do_QueryInterface(bodyNode));
+ NS_ENSURE_ARG_POINTER(bodyContent);
+
+ uint32_t childCount = bodyContent->GetChildCount();
+
+ // There are four possible range endpoints we might use:
+ // DocumentStart, SelectionStart, SelectionEnd, DocumentEnd.
+
+ nsCOMPtr<nsIDOMRange> range;
+ nsCOMPtr<nsIDOMNode> node;
+ uint32_t offset;
+
+ // Forward, not wrapping: SelEnd to DocEnd
+ if (!mFindBackwards && !aWrap) {
+ // This isn't quite right, since the selection's ranges aren't
+ // necessarily in order; but they usually will be.
+ aSel->GetRangeAt(count - 1, getter_AddRefs(range));
+ if (!range) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ range->GetEndContainer(getter_AddRefs(node));
+ if (!node) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ range->GetEndOffset(&offset);
+
+ aSearchRange->SetStart(node, offset);
+ aSearchRange->SetEnd(bodyNode, childCount);
+ aStartPt->SetStart(node, offset);
+ aStartPt->SetEnd(node, offset);
+ aEndPt->SetStart(bodyNode, childCount);
+ aEndPt->SetEnd(bodyNode, childCount);
+ }
+ // Backward, not wrapping: DocStart to SelStart
+ else if (mFindBackwards && !aWrap) {
+ aSel->GetRangeAt(0, getter_AddRefs(range));
+ if (!range) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ range->GetStartContainer(getter_AddRefs(node));
+ if (!node) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ range->GetStartOffset(&offset);
+
+ aSearchRange->SetStart(bodyNode, 0);
+ aSearchRange->SetEnd(bodyNode, childCount);
+ aStartPt->SetStart(node, offset);
+ aStartPt->SetEnd(node, offset);
+ aEndPt->SetStart(bodyNode, 0);
+ aEndPt->SetEnd(bodyNode, 0);
+ }
+ // Forward, wrapping: DocStart to SelEnd
+ else if (!mFindBackwards && aWrap) {
+ aSel->GetRangeAt(count - 1, getter_AddRefs(range));
+ if (!range) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ range->GetEndContainer(getter_AddRefs(node));
+ if (!node) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ range->GetEndOffset(&offset);
+
+ aSearchRange->SetStart(bodyNode, 0);
+ aSearchRange->SetEnd(bodyNode, childCount);
+ aStartPt->SetStart(bodyNode, 0);
+ aStartPt->SetEnd(bodyNode, 0);
+ aEndPt->SetStart(node, offset);
+ aEndPt->SetEnd(node, offset);
+ }
+ // Backward, wrapping: SelStart to DocEnd
+ else if (mFindBackwards && aWrap) {
+ aSel->GetRangeAt(0, getter_AddRefs(range));
+ if (!range) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ range->GetStartContainer(getter_AddRefs(node));
+ if (!node) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ range->GetStartOffset(&offset);
+
+ aSearchRange->SetStart(bodyNode, 0);
+ aSearchRange->SetEnd(bodyNode, childCount);
+ aStartPt->SetStart(bodyNode, childCount);
+ aStartPt->SetEnd(bodyNode, childCount);
+ aEndPt->SetStart(node, offset);
+ aEndPt->SetEnd(node, offset);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::GetSearchFrames(bool* aSearchFrames)
+{
+ NS_ENSURE_ARG_POINTER(aSearchFrames);
+ // this only returns true if we are searching both sub and parent frames.
+ // There is ambiguity if the caller has previously set one, but not both of
+ // these.
+ *aSearchFrames = mSearchSubFrames && mSearchParentFrames;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::SetSearchFrames(bool aSearchFrames)
+{
+ mSearchSubFrames = aSearchFrames;
+ mSearchParentFrames = aSearchFrames;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::GetCurrentSearchFrame(mozIDOMWindowProxy** aCurrentSearchFrame)
+{
+ NS_ENSURE_ARG_POINTER(aCurrentSearchFrame);
+ nsCOMPtr<mozIDOMWindowProxy> searchFrame = do_QueryReferent(mCurrentSearchFrame);
+ searchFrame.forget(aCurrentSearchFrame);
+ return (*aCurrentSearchFrame) ? NS_OK : NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::SetCurrentSearchFrame(mozIDOMWindowProxy* aCurrentSearchFrame)
+{
+ // is it ever valid to set this to null?
+ NS_ENSURE_ARG(aCurrentSearchFrame);
+ mCurrentSearchFrame = do_GetWeakReference(aCurrentSearchFrame);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::GetRootSearchFrame(mozIDOMWindowProxy** aRootSearchFrame)
+{
+ NS_ENSURE_ARG_POINTER(aRootSearchFrame);
+ nsCOMPtr<mozIDOMWindowProxy> searchFrame = do_QueryReferent(mRootSearchFrame);
+ searchFrame.forget(aRootSearchFrame);
+ return (*aRootSearchFrame) ? NS_OK : NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::SetRootSearchFrame(mozIDOMWindowProxy* aRootSearchFrame)
+{
+ // is it ever valid to set this to null?
+ NS_ENSURE_ARG(aRootSearchFrame);
+ mRootSearchFrame = do_GetWeakReference(aRootSearchFrame);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::GetSearchSubframes(bool* aSearchSubframes)
+{
+ NS_ENSURE_ARG_POINTER(aSearchSubframes);
+ *aSearchSubframes = mSearchSubFrames;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::SetSearchSubframes(bool aSearchSubframes)
+{
+ mSearchSubFrames = aSearchSubframes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::GetSearchParentFrames(bool* aSearchParentFrames)
+{
+ NS_ENSURE_ARG_POINTER(aSearchParentFrames);
+ *aSearchParentFrames = mSearchParentFrames;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowserFind::SetSearchParentFrames(bool aSearchParentFrames)
+{
+ mSearchParentFrames = aSearchParentFrames;
+ return NS_OK;
+}
+
+/*
+ This method handles finding in a single window (aka frame).
+
+*/
+nsresult
+nsWebBrowserFind::SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping,
+ bool* aDidFind)
+{
+ NS_ENSURE_ARG(aWindow);
+ NS_ENSURE_ARG_POINTER(aDidFind);
+
+ *aDidFind = false;
+
+ // Do security check, to ensure that the frame we're searching is
+ // acccessible from the frame where the Find is being run.
+
+ // get a uri for the window
+ nsCOMPtr<nsIDocument> theDoc = aWindow->GetDoc();
+ if (!theDoc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!nsContentUtils::SubjectPrincipal()->Subsumes(theDoc->NodePrincipal())) {
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIFind> find = do_CreateInstance(NS_FIND_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ (void)find->SetCaseSensitive(mMatchCase);
+ (void)find->SetFindBackwards(mFindBackwards);
+
+ (void)find->SetEntireWord(mEntireWord);
+
+ // Now make sure the content (for actual finding) and frame (for
+ // selection) models are up to date.
+ theDoc->FlushPendingNotifications(Flush_Frames);
+
+ nsCOMPtr<nsISelection> sel = GetFrameSelection(aWindow);
+ NS_ENSURE_ARG_POINTER(sel);
+
+ nsCOMPtr<nsIDOMRange> searchRange = new nsRange(theDoc);
+ NS_ENSURE_ARG_POINTER(searchRange);
+ nsCOMPtr<nsIDOMRange> startPt = new nsRange(theDoc);
+ NS_ENSURE_ARG_POINTER(startPt);
+ nsCOMPtr<nsIDOMRange> endPt = new nsRange(theDoc);
+ NS_ENSURE_ARG_POINTER(endPt);
+
+ nsCOMPtr<nsIDOMRange> foundRange;
+
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(theDoc);
+ MOZ_ASSERT(domDoc);
+
+ // If !aWrapping, search from selection to end
+ if (!aWrapping)
+ rv = GetSearchLimits(searchRange, startPt, endPt, domDoc, sel, false);
+
+ // If aWrapping, search the part of the starting frame
+ // up to the point where we left off.
+ else
+ rv = GetSearchLimits(searchRange, startPt, endPt, domDoc, sel, true);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = find->Find(mSearchString, searchRange, startPt, endPt,
+ getter_AddRefs(foundRange));
+
+ if (NS_SUCCEEDED(rv) && foundRange) {
+ *aDidFind = true;
+ sel->RemoveAllRanges();
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ SetSelectionAndScroll(aWindow, foundRange);
+ }
+
+ return rv;
+}
+
+// called when we start searching a frame that is not the initial focussed
+// frame. Prepare the frame to be searched. we clear the selection, so that the
+// search starts from the top of the frame.
+nsresult
+nsWebBrowserFind::OnStartSearchFrame(nsPIDOMWindowOuter* aWindow)
+{
+ return ClearFrameSelection(aWindow);
+}
+
+// called when we are done searching a frame and didn't find anything, and about
+// about to start searching the next frame.
+nsresult
+nsWebBrowserFind::OnEndSearchFrame(nsPIDOMWindowOuter* aWindow)
+{
+ return NS_OK;
+}
+
+already_AddRefed<nsISelection>
+nsWebBrowserFind::GetFrameSelection(nsPIDOMWindowOuter* aWindow)
+{
+ nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ nsIPresShell* presShell = doc->GetShell();
+ if (!presShell) {
+ return nullptr;
+ }
+
+ // text input controls have their independent selection controllers that we
+ // must use when they have focus.
+ nsPresContext* presContext = presShell->GetPresContext();
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsCOMPtr<nsIContent> focusedContent = nsFocusManager::GetFocusedDescendant(
+ aWindow, false, getter_AddRefs(focusedWindow));
+
+ nsIFrame* frame =
+ focusedContent ? focusedContent->GetPrimaryFrame() : nullptr;
+
+ nsCOMPtr<nsISelectionController> selCon;
+ nsCOMPtr<nsISelection> sel;
+ if (frame) {
+ frame->GetSelectionController(presContext, getter_AddRefs(selCon));
+ selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(sel));
+ if (sel) {
+ int32_t count = -1;
+ sel->GetRangeCount(&count);
+ if (count > 0) {
+ return sel.forget();
+ }
+ }
+ }
+
+ selCon = do_QueryInterface(presShell);
+ selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(sel));
+ return sel.forget();
+}
+
+nsresult
+nsWebBrowserFind::ClearFrameSelection(nsPIDOMWindowOuter* aWindow)
+{
+ NS_ENSURE_ARG(aWindow);
+ nsCOMPtr<nsISelection> selection = GetFrameSelection(aWindow);
+ if (selection) {
+ selection->RemoveAllRanges();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsWebBrowserFind::OnFind(nsPIDOMWindowOuter* aFoundWindow)
+{
+ SetCurrentSearchFrame(aFoundWindow);
+
+ // We don't want a selection to appear in two frames simultaneously
+ nsCOMPtr<nsPIDOMWindowOuter> lastFocusedWindow =
+ do_QueryReferent(mLastFocusedWindow);
+ if (lastFocusedWindow && lastFocusedWindow != aFoundWindow) {
+ ClearFrameSelection(lastFocusedWindow);
+ }
+
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ if (fm) {
+ // get the containing frame and focus it. For top-level windows, the right
+ // window should already be focused.
+ nsCOMPtr<nsIDOMElement> frameElement =
+ do_QueryInterface(aFoundWindow->GetFrameElementInternal());
+ if (frameElement) {
+ fm->SetFocus(frameElement, 0);
+ }
+
+ mLastFocusedWindow = do_GetWeakReference(aFoundWindow);
+ }
+
+ return NS_OK;
+}
diff --git a/components/find/src/nsWebBrowserFind.h b/components/find/src/nsWebBrowserFind.h
new file mode 100644
index 000000000..c36414b53
--- /dev/null
+++ b/components/find/src/nsWebBrowserFind.h
@@ -0,0 +1,94 @@
+/* -*- 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/. */
+
+#ifndef nsWebBrowserFindImpl_h__
+#define nsWebBrowserFindImpl_h__
+
+#include "nsIWebBrowserFind.h"
+#include "nsPIDOMWindow.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+
+#include "nsIFind.h"
+
+#include "nsString.h"
+
+#define NS_WEB_BROWSER_FIND_CONTRACTID "@mozilla.org/embedcomp/find;1"
+
+#define NS_WEB_BROWSER_FIND_CID \
+ {0x57cf9383, 0x3405, 0x11d5, {0xbe, 0x5b, 0xaa, 0x20, 0xfa, 0x2c, 0xf3, 0x7c}}
+
+class nsISelection;
+class nsIDOMWindow;
+
+class nsIDocShell;
+
+//*****************************************************************************
+// class nsWebBrowserFind
+//*****************************************************************************
+
+class nsWebBrowserFind
+ : public nsIWebBrowserFind
+ , public nsIWebBrowserFindInFrames
+{
+public:
+ nsWebBrowserFind();
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIWebBrowserFind
+ NS_DECL_NSIWEBBROWSERFIND
+
+ // nsIWebBrowserFindInFrames
+ NS_DECL_NSIWEBBROWSERFINDINFRAMES
+
+protected:
+ virtual ~nsWebBrowserFind();
+
+ bool CanFindNext() { return mSearchString.Length() != 0; }
+
+ nsresult SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping,
+ bool* aDidFind);
+
+ nsresult OnStartSearchFrame(nsPIDOMWindowOuter* aWindow);
+ nsresult OnEndSearchFrame(nsPIDOMWindowOuter* aWindow);
+
+ already_AddRefed<nsISelection> GetFrameSelection(nsPIDOMWindowOuter* aWindow);
+ nsresult ClearFrameSelection(nsPIDOMWindowOuter* aWindow);
+
+ nsresult OnFind(nsPIDOMWindowOuter* aFoundWindow);
+
+ void SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow, nsIDOMRange* aRange);
+
+ nsresult GetRootNode(nsIDOMDocument* aDomDoc, nsIDOMNode** aNode);
+ nsresult GetSearchLimits(nsIDOMRange* aRange,
+ nsIDOMRange* aStartPt, nsIDOMRange* aEndPt,
+ nsIDOMDocument* aDoc, nsISelection* aSel,
+ bool aWrap);
+ nsresult SetRangeAroundDocument(nsIDOMRange* aSearchRange,
+ nsIDOMRange* aStartPoint,
+ nsIDOMRange* aEndPoint,
+ nsIDOMDocument* aDoc);
+
+protected:
+ nsString mSearchString;
+
+ bool mFindBackwards;
+ bool mWrapFind;
+ bool mEntireWord;
+ bool mMatchCase;
+
+ bool mSearchSubFrames;
+ bool mSearchParentFrames;
+
+ // These are all weak because who knows if windows can go away during our
+ // lifetime.
+ nsWeakPtr mCurrentSearchFrame;
+ nsWeakPtr mRootSearchFrame;
+ nsWeakPtr mLastFocusedWindow;
+};
+
+#endif
diff --git a/components/formautofill/content/requestAutocomplete.js b/components/formautofill/content/requestAutocomplete.js
new file mode 100644
index 000000000..47d500964
--- /dev/null
+++ b/components/formautofill/content/requestAutocomplete.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of "requestAutocomplete.xhtml".
+ */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const RequestAutocompleteDialog = {
+ resolveFn: null,
+ autofillData: null,
+
+ onLoad: function () {
+ Task.spawn(function* () {
+ let args = window.arguments[0].wrappedJSObject;
+ this.resolveFn = args.resolveFn;
+ this.autofillData = args.autofillData;
+
+ window.sizeToContent();
+
+ Services.obs.notifyObservers(window,
+ "formautofill-window-initialized", "");
+ }.bind(this)).catch(Cu.reportError);
+ },
+
+ onAccept: function () {
+ // TODO: Replace with autofill storage module (bug 1018304).
+ const dummyDB = {
+ "": {
+ "name": "Mozzy La",
+ "street-address": "331 E Evelyn Ave",
+ "address-level2": "Mountain View",
+ "address-level1": "CA",
+ "country": "US",
+ "postal-code": "94041",
+ "email": "email@example.org",
+ }
+ };
+
+ let result = { fields: [] };
+ for (let section of this.autofillData.sections) {
+ for (let addressSection of section.addressSections) {
+ let addressType = addressSection.addressType;
+ if (!(addressType in dummyDB)) {
+ continue;
+ }
+
+ for (let field of addressSection.fields) {
+ let fieldName = field.fieldName;
+ if (!(fieldName in dummyDB[addressType])) {
+ continue;
+ }
+
+ result.fields.push({
+ section: section.name,
+ addressType: addressType,
+ contactType: field.contactType,
+ fieldName: field.fieldName,
+ value: dummyDB[addressType][fieldName],
+ });
+ }
+ }
+ }
+
+ window.close();
+ this.resolveFn(result);
+ },
+
+ onCancel: function () {
+ window.close();
+ this.resolveFn({ canceled: true });
+ },
+};
diff --git a/components/formautofill/content/requestAutocomplete.xhtml b/components/formautofill/content/requestAutocomplete.xhtml
new file mode 100644
index 000000000..269e55bd6
--- /dev/null
+++ b/components/formautofill/content/requestAutocomplete.xhtml
@@ -0,0 +1,31 @@
+<?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 % requestAutocompleteDTD SYSTEM "chrome://formautofill/locale/requestAutocomplete.dtd">
+ %requestAutocompleteDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
+ %globalDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>requestAutocomplete demo window</title>
+ <link rel="stylesheet"
+ href="chrome://mozapps/skin/formautofill/requestAutocomplete.css" />
+ <script type="application/javascript;version=1.7"
+ src="chrome://formautofill/content/requestAutocomplete.js" />
+ </head>
+ <body dir="&locale.dir;" onload="RequestAutocompleteDialog.onLoad();">
+ <h1>requestAutocomplete</h1>
+ <p>This is a demo window.</p>
+ <input id="accept" type="button" value="(OK)"
+ onclick="RequestAutocompleteDialog.onAccept();" />
+ <input id="cancel" type="button" value="(Cancel)"
+ onclick="RequestAutocompleteDialog.onCancel();" />
+ </body>
+</html>
diff --git a/components/formautofill/formautofill.manifest b/components/formautofill/formautofill.manifest
new file mode 100644
index 000000000..880972edc
--- /dev/null
+++ b/components/formautofill/formautofill.manifest
@@ -0,0 +1,7 @@
+component {ed9c2c3c-3f86-4ae5-8e31-10f71b0f19e6} FormAutofillContentService.js
+contract @mozilla.org/formautofill/content-service;1 {ed9c2c3c-3f86-4ae5-8e31-10f71b0f19e6}
+component {51c95b3d-7431-467b-8d50-383f158ce9e5} FormAutofillStartup.js
+contract @mozilla.org/formautofill/startup;1 {51c95b3d-7431-467b-8d50-383f158ce9e5}
+#ifdef NIGHTLY_BUILD
+category profile-after-change FormAutofillStartup @mozilla.org/formautofill/startup;1
+#endif
diff --git a/components/formautofill/jar.mn b/components/formautofill/jar.mn
new file mode 100644
index 000000000..ebe869b58
--- /dev/null
+++ b/components/formautofill/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/.
+
+toolkit.jar:
+% content formautofill %content/formautofill/
+ content/formautofill/requestAutocomplete.js (content/requestAutocomplete.js)
+ content/formautofill/requestAutocomplete.xhtml (content/requestAutocomplete.xhtml)
diff --git a/components/formautofill/locale/requestAutocomplete.dtd b/components/formautofill/locale/requestAutocomplete.dtd
new file mode 100644
index 000000000..53a1d0d23
--- /dev/null
+++ b/components/formautofill/locale/requestAutocomplete.dtd
@@ -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/. -->
+
+<!-- This file has no entities because the feature is still experimental. -->
diff --git a/components/formautofill/moz.build b/components/formautofill/moz.build
new file mode 100644
index 000000000..ef83ef163
--- /dev/null
+++ b/components/formautofill/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['public/nsIFormAutofillContentService.idl']
+
+EXTRA_COMPONENTS += [
+ 'src/FormAutofillContentService.js',
+ 'src/FormAutofillStartup.js',
+]
+
+EXTRA_PP_COMPONENTS += ['formautofill.manifest']
+
+EXTRA_JS_MODULES += [
+ 'src/FormAutofill.jsm',
+ 'src/FormAutofillIntegration.jsm',
+ 'src/RequestAutocompleteUI.jsm',
+]
+
+XPIDL_MODULE = 'toolkit_formautofill'
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/formautofill/public/nsIFormAutofillContentService.idl b/components/formautofill/public/nsIFormAutofillContentService.idl
new file mode 100644
index 000000000..300645e74
--- /dev/null
+++ b/components/formautofill/public/nsIFormAutofillContentService.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMHTMLFormElement;
+interface nsIDOMWindow;
+
+/**
+ * Defines a service used by DOM content to request Form Autofill, in particular
+ * when the requestAutocomplete method of Form objects is invoked.
+ *
+ * This service lives in the process that hosts the requesting DOM content.
+ * This means that, in a multi-process (e10s) environment, there can be an
+ * instance of the service for each content process, in addition to an instance
+ * for the chrome process.
+ *
+ * @remarks The service implementation uses a child-side message manager to
+ * communicate with a parent-side message manager living in the chrome
+ * process, where most of the processing is located.
+ */
+[scriptable, uuid(1db29340-99df-4845-9102-0c5d281b2fe8)]
+interface nsIFormAutofillContentService : nsISupports
+{
+ /**
+ * Invoked by the requestAutocomplete method of the DOM Form object.
+ *
+ * The application is expected to display a user interface asking for the
+ * details that are relevant to the form being filled in. The application
+ * should use the "autocomplete" attributes on the input elements as hints
+ * about which type of information is being requested.
+ *
+ * The processing will result in either an "autocomplete" simple DOM Event or
+ * an AutocompleteErrorEvent being fired on the form.
+ *
+ * @param aForm
+ * The form on which the requestAutocomplete method was invoked.
+ * @param aWindow
+ * The window where the form is located. This must be specified even
+ * for elements that are not in a document, and is used to generate the
+ * DOM events resulting from the operation.
+ */
+ void requestAutocomplete(in nsIDOMHTMLFormElement aForm,
+ in nsIDOMWindow aWindow);
+};
diff --git a/components/formautofill/src/FormAutofill.jsm b/components/formautofill/src/FormAutofill.jsm
new file mode 100644
index 000000000..aae3a956c
--- /dev/null
+++ b/components/formautofill/src/FormAutofill.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/. */
+
+/*
+ * Main module handling references to objects living in the main process.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "FormAutofill",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Integration.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+/**
+ * Main module handling references to objects living in the main process.
+ */
+this.FormAutofill = {
+ /**
+ * Registers new overrides for the FormAutofillIntegration methods. Example:
+ *
+ * FormAutofill.registerIntegration(base => ({
+ * createRequestAutocompleteUI: Task.async(function* () {
+ * yield base.createRequestAutocompleteUI.apply(this, arguments);
+ * }),
+ * }));
+ *
+ * @param aIntegrationFn
+ * Function returning an object defining the methods that should be
+ * overridden. Its only parameter is an object that contains the base
+ * implementation of all the available methods.
+ *
+ * @note The integration function is called every time the list of registered
+ * integration functions changes. Thus, it should not have any side
+ * effects or do any other initialization.
+ */
+ registerIntegration(aIntegrationFn) {
+ Integration.formAutofill.register(aIntegrationFn);
+ },
+
+ /**
+ * Removes a previously registered FormAutofillIntegration override.
+ *
+ * Overrides don't usually need to be unregistered, unless they are added by a
+ * restartless add-on, in which case they should be unregistered when the
+ * add-on is disabled or uninstalled.
+ *
+ * @param aIntegrationFn
+ * This must be the same function object passed to registerIntegration.
+ */
+ unregisterIntegration(aIntegrationFn) {
+ Integration.formAutofill.unregister(aIntegrationFn);
+ },
+
+ /**
+ * Processes a requestAutocomplete message asynchronously.
+ *
+ * @param aData
+ * Provided to FormAutofillIntegration.createRequestAutocompleteUI.
+ *
+ * @return {Promise}
+ * @resolves Structured data received from the requestAutocomplete UI.
+ */
+ processRequestAutocomplete: Task.async(function* (aData) {
+ let ui = yield FormAutofill.integration.createRequestAutocompleteUI(aData);
+ return yield ui.show();
+ }),
+};
+
+/**
+ * Dynamically generated object implementing the FormAutofillIntegration
+ * methods. Platform-specific code and add-ons can override methods of this
+ * object using the registerIntegration method.
+ */
+Integration.formAutofill.defineModuleGetter(
+ this.FormAutofill,
+ "integration",
+ "resource://gre/modules/FormAutofillIntegration.jsm",
+ "FormAutofillIntegration"
+);
diff --git a/components/formautofill/src/FormAutofillContentService.js b/components/formautofill/src/FormAutofillContentService.js
new file mode 100644
index 000000000..ee8e978ad
--- /dev/null
+++ b/components/formautofill/src/FormAutofillContentService.js
@@ -0,0 +1,272 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implements a service used by DOM content to request Form Autofill, in
+ * particular when the requestAutocomplete method of Form objects is invoked.
+ *
+ * See the nsIFormAutofillContentService documentation for details.
+ */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormAutofill",
+ "resource://gre/modules/FormAutofill.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+/**
+ * Handles requestAutocomplete for a DOM Form element.
+ */
+function FormHandler(aForm, aWindow) {
+ this.form = aForm;
+ this.window = aWindow;
+
+ this.fieldDetails = [];
+}
+
+FormHandler.prototype = {
+ /**
+ * DOM Form element to which this object is attached.
+ */
+ form: null,
+
+ /**
+ * nsIDOMWindow to which this object is attached.
+ */
+ window: null,
+
+ /**
+ * Array of collected data about relevant form fields. Each item is an object
+ * storing the identifying details of the field and a reference to the
+ * originally associated element from the form.
+ *
+ * The "section", "addressType", "contactType", and "fieldName" values are
+ * used to identify the exact field when the serializable data is received
+ * from the requestAutocomplete user interface. There cannot be multiple
+ * fields which have the same exact combination of these values.
+ *
+ * A direct reference to the associated element cannot be sent to the user
+ * interface because processing may be done in the parent process.
+ */
+ fieldDetails: null,
+
+ /**
+ * Handles requestAutocomplete and generates the DOM events when finished.
+ */
+ handleRequestAutocomplete: Task.async(function* () {
+ // Start processing the request asynchronously. At the end, the "reason"
+ // variable will contain the outcome of the operation, where an empty
+ // string indicates that an unexpected exception occurred.
+ let reason = "";
+ try {
+ reason = yield this.promiseRequestAutocomplete();
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ // The type of event depends on whether this is a success condition.
+ let event = (reason == "success")
+ ? new this.window.Event("autocomplete", { bubbles: true })
+ : new this.window.AutocompleteErrorEvent("autocompleteerror",
+ { bubbles: true,
+ reason: reason });
+ yield this.waitForTick();
+ this.form.dispatchEvent(event);
+ }),
+
+ /**
+ * Handles requestAutocomplete and returns the outcome when finished.
+ *
+ * @return {Promise}
+ * @resolves The "reason" value indicating the outcome of the
+ * requestAutocomplete operation, including "success" if the
+ * operation completed successfully.
+ */
+ promiseRequestAutocomplete: Task.async(function* () {
+ let data = this.collectFormFields();
+ if (!data) {
+ return "disabled";
+ }
+
+ // Access the frame message manager of the window starting the request.
+ let rootDocShell = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .sameTypeRootTreeItem
+ .QueryInterface(Ci.nsIDocShell);
+ let frameMM = rootDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+
+ // We need to set up a temporary message listener for our result before we
+ // send the request to the parent process. At present, there is no check
+ // for reentrancy (bug 1020459), thus it is possible that we'll receive a
+ // message for a different request, but this will not be normally allowed.
+ let promiseRequestAutocompleteResult = new Promise((resolve, reject) => {
+ frameMM.addMessageListener("FormAutofill:RequestAutocompleteResult",
+ function onResult(aMessage) {
+ frameMM.removeMessageListener(
+ "FormAutofill:RequestAutocompleteResult", onResult);
+ // Exceptions in the parent process are serialized and propagated in
+ // the response message that we received.
+ if ("exception" in aMessage.data) {
+ reject(aMessage.data.exception);
+ } else {
+ resolve(aMessage.data);
+ }
+ });
+ });
+
+ // Send the message to the parent process, and wait for the result. This
+ // will throw an exception if one occurred in the parent process.
+ frameMM.sendAsyncMessage("FormAutofill:RequestAutocomplete", data);
+ let result = yield promiseRequestAutocompleteResult;
+ if (result.canceled) {
+ return "cancel";
+ }
+
+ this.autofillFormFields(result);
+
+ return "success";
+ }),
+
+ /**
+ * Returns information from the form about fields that can be autofilled, and
+ * populates the fieldDetails array on this object accordingly.
+ *
+ * @returns Serializable data structure that can be sent to the user
+ * interface, or null if the operation failed because the constraints
+ * on the allowed fields were not honored.
+ */
+ collectFormFields: function () {
+ let autofillData = {
+ sections: [],
+ };
+
+ for (let element of this.form.elements) {
+ // Query the interface and exclude elements that cannot be autocompleted.
+ if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
+ continue;
+ }
+
+ // Exclude elements to which no autocomplete field has been assigned.
+ let info = element.getAutocompleteInfo();
+ if (!info.fieldName || ["on", "off"].indexOf(info.fieldName) != -1) {
+ continue;
+ }
+
+ // Store the association between the field metadata and the element.
+ if (this.fieldDetails.some(f => f.section == info.section &&
+ f.addressType == info.addressType &&
+ f.contactType == info.contactType &&
+ f.fieldName == info.fieldName)) {
+ // A field with the same identifier already exists.
+ return null;
+ }
+ this.fieldDetails.push({
+ section: info.section,
+ addressType: info.addressType,
+ contactType: info.contactType,
+ fieldName: info.fieldName,
+ element: element,
+ });
+
+ // The first level is the custom section.
+ let section = autofillData.sections
+ .find(s => s.name == info.section);
+ if (!section) {
+ section = {
+ name: info.section,
+ addressSections: [],
+ };
+ autofillData.sections.push(section);
+ }
+
+ // The second level is the address section.
+ let addressSection = section.addressSections
+ .find(s => s.addressType == info.addressType);
+ if (!addressSection) {
+ addressSection = {
+ addressType: info.addressType,
+ fields: [],
+ };
+ section.addressSections.push(addressSection);
+ }
+
+ // The third level contains all the fields within the section.
+ let field = {
+ fieldName: info.fieldName,
+ contactType: info.contactType,
+ };
+ addressSection.fields.push(field);
+ }
+
+ return autofillData;
+ },
+
+ /**
+ * Processes form fields that can be autofilled, and populates them with the
+ * data provided by RequestAutocompleteUI.
+ *
+ * @param aAutofillResult
+ * Data returned by the user interface.
+ * {
+ * fields: [
+ * section: Value originally provided to the user interface.
+ * addressType: Value originally provided to the user interface.
+ * contactType: Value originally provided to the user interface.
+ * fieldName: Value originally provided to the user interface.
+ * value: String with which the field should be updated.
+ * ],
+ * }
+ */
+ autofillFormFields: function (aAutofillResult) {
+ for (let field of aAutofillResult.fields) {
+ // Get the field details, if it was processed by the user interface.
+ let fieldDetail = this.fieldDetails
+ .find(f => f.section == field.section &&
+ f.addressType == field.addressType &&
+ f.contactType == field.contactType &&
+ f.fieldName == field.fieldName);
+ if (!fieldDetail) {
+ continue;
+ }
+
+ fieldDetail.element.value = field.value;
+ }
+ },
+
+ /**
+ * Waits for one tick of the event loop before resolving the returned promise.
+ */
+ waitForTick: function () {
+ return new Promise(function (resolve) {
+ Services.tm.currentThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ },
+};
+
+/**
+ * Implements a service used by DOM content to request Form Autofill, in
+ * particular when the requestAutocomplete method of Form objects is invoked.
+ */
+function FormAutofillContentService() {
+}
+
+FormAutofillContentService.prototype = {
+ classID: Components.ID("{ed9c2c3c-3f86-4ae5-8e31-10f71b0f19e6}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFormAutofillContentService]),
+
+ // nsIFormAutofillContentService
+ requestAutocomplete: function (aForm, aWindow) {
+ new FormHandler(aForm, aWindow).handleRequestAutocomplete()
+ .catch(Cu.reportError);
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FormAutofillContentService]);
diff --git a/components/formautofill/src/FormAutofillIntegration.jsm b/components/formautofill/src/FormAutofillIntegration.jsm
new file mode 100644
index 000000000..4b838a6ab
--- /dev/null
+++ b/components/formautofill/src/FormAutofillIntegration.jsm
@@ -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/. */
+
+/*
+ * This module defines the default implementation of platform-specific functions
+ * that can be overridden by the host application and by add-ons.
+ *
+ * This module should not be imported directly, but the "integration" getter of
+ * the FormAutofill module should be used to get a reference to the currently
+ * defined implementations of the methods.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "FormAutofillIntegration",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RequestAutocompleteUI",
+ "resource://gre/modules/RequestAutocompleteUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+/**
+ * This module defines the default implementation of platform-specific functions
+ * that can be overridden by the host application and by add-ons.
+ */
+this.FormAutofillIntegration = {
+ /**
+ * Creates a new RequestAutocompleteUI object.
+ *
+ * @param aAutofillData
+ * Provides the initial data required to display the user interface.
+ * {
+ * sections: [{
+ * name: User-specified section name, or empty string.
+ * addressSections: [{
+ * addressType: "shipping", "billing", or empty string.
+ * fields: [{
+ * fieldName: Type of information requested, like "email".
+ * contactType: For example "work", "home", or empty string.
+ * }],
+ * }],
+ * }],
+ * }
+ *
+ * @return {Promise}
+ * @resolves The newly created RequestAutocompleteUI object.
+ * @rejects JavaScript exception.
+ */
+ createRequestAutocompleteUI: Task.async(function* (aAutofillData) {
+ return new RequestAutocompleteUI(aAutofillData);
+ }),
+};
diff --git a/components/formautofill/src/FormAutofillStartup.js b/components/formautofill/src/FormAutofillStartup.js
new file mode 100644
index 000000000..92887f872
--- /dev/null
+++ b/components/formautofill/src/FormAutofillStartup.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Handles startup in the parent process.
+ */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormAutofill",
+ "resource://gre/modules/FormAutofill.jsm");
+
+/**
+ * Handles startup in the parent process.
+ */
+function FormAutofillStartup() {
+}
+
+FormAutofillStartup.prototype = {
+ classID: Components.ID("{51c95b3d-7431-467b-8d50-383f158ce9e5}"),
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIFrameMessageListener,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ ]),
+
+ // nsIObserver
+ observe: function (aSubject, aTopic, aData) {
+ // This method is called by the "profile-after-change" category on startup,
+ // which is called before any web page loads. At this time, we need to
+ // register a global message listener in the parent process preemptively,
+ // because we can receive requests from child processes at any time. For
+ // performance reasons, we use this object as a message listener, so that we
+ // don't have to load the FormAutoFill module at startup.
+ let globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ globalMM.addMessageListener("FormAutofill:RequestAutocomplete", this);
+ },
+
+ // nsIFrameMessageListener
+ receiveMessage: function (aMessage) {
+ // Process the "FormAutofill:RequestAutocomplete" message. Any exception
+ // raised in the parent process is caught and serialized into the reply
+ // message that is sent to the requesting child process.
+ FormAutofill.processRequestAutocomplete(aMessage.data)
+ .catch(ex => { return { exception: ex } })
+ .then(result => {
+ // The browser message manager in the parent will send the reply to the
+ // associated frame message manager in the child.
+ let browserMM = aMessage.target.messageManager;
+ browserMM.sendAsyncMessage("FormAutofill:RequestAutocompleteResult",
+ result);
+ })
+ .catch(Cu.reportError);
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FormAutofillStartup]);
diff --git a/components/formautofill/src/RequestAutocompleteUI.jsm b/components/formautofill/src/RequestAutocompleteUI.jsm
new file mode 100644
index 000000000..74c4834ba
--- /dev/null
+++ b/components/formautofill/src/RequestAutocompleteUI.jsm
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Handles the requestAutocomplete user interface.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "RequestAutocompleteUI",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+/**
+ * Handles the requestAutocomplete user interface.
+ */
+this.RequestAutocompleteUI = function (aAutofillData) {
+ this._autofillData = aAutofillData;
+}
+
+this.RequestAutocompleteUI.prototype = {
+ _autofillData: null,
+
+ show: Task.async(function* () {
+ // Create a new promise and store the function that will resolve it. This
+ // will be called by the UI once the selection has been made.
+ let resolveFn;
+ let uiPromise = new Promise(resolve => resolveFn = resolve);
+
+ // Wrap the callback function so that it survives XPCOM.
+ let args = {
+ resolveFn: resolveFn,
+ autofillData: this._autofillData,
+ };
+ args.wrappedJSObject = args;
+
+ // Open the window providing the function to call when it closes.
+ Services.ww.openWindow(null,
+ "chrome://formautofill/content/requestAutocomplete.xhtml",
+ "Toolkit:RequestAutocomplete",
+ "chrome,dialog=no,resizable",
+ args);
+
+ // Wait for the window to be closed and the operation confirmed.
+ return yield uiPromise;
+ }),
+};
diff --git a/components/gfx/GfxSanityTest.manifest b/components/gfx/GfxSanityTest.manifest
new file mode 100644
index 000000000..b9febc498
--- /dev/null
+++ b/components/gfx/GfxSanityTest.manifest
@@ -0,0 +1,3 @@
+component {f3a8ca4d-4c83-456b-aee2-6a2cbf11e9bd} SanityTest.js process=main
+contract @mozilla.org/sanity-test;1 {f3a8ca4d-4c83-456b-aee2-6a2cbf11e9bd} process=main
+category profile-after-change SanityTest @mozilla.org/sanity-test;1 process=main
diff --git a/components/gfx/SanityTest.js b/components/gfx/SanityTest.js
new file mode 100644
index 000000000..03b9067ef
--- /dev/null
+++ b/components/gfx/SanityTest.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/. */
+
+"use strict";
+
+const { utils: Cu, interfaces: Ci, classes: Cc, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import('resource://gre/modules/Preferences.jsm');
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const FRAME_SCRIPT_URL = "chrome://gfxsanity/content/gfxFrameScript.js";
+
+const PAGE_WIDTH=92;
+const PAGE_HEIGHT=166;
+const DRIVER_PREF="sanity-test.driver-version";
+const DEVICE_PREF="sanity-test.device-id";
+const VERSION_PREF="sanity-test.version";
+const DISABLE_VIDEO_PREF="media.hardware-video-decoding.failed";
+const RUNNING_PREF="sanity-test.running";
+const TIMEOUT_SEC=20;
+
+// GRAPHICS_SANITY_TEST histogram enumeration values
+const TEST_PASSED=0;
+const TEST_FAILED_RENDER=1;
+const TEST_FAILED_VIDEO=2;
+const TEST_CRASHED=3;
+const TEST_TIMEOUT=4;
+
+// GRAPHICS_SANITY_TEST_REASON enumeration values.
+const REASON_FIRST_RUN=0;
+const REASON_FIREFOX_CHANGED=1;
+const REASON_DEVICE_CHANGED=2;
+const REASON_DRIVER_CHANGED=3;
+
+// GRAPHICS_SANITY_TEST_OS_SNAPSHOT histogram enumeration values
+const SNAPSHOT_VIDEO_OK=0;
+const SNAPSHOT_VIDEO_FAIL=1;
+const SNAPSHOT_ERROR=2;
+const SNAPSHOT_TIMEOUT=3;
+const SNAPSHOT_LAYERS_OK=4;
+const SNAPSHOT_LAYERS_FAIL=5;
+
+function testPixel(ctx, x, y, r, g, b, a, fuzz) {
+ var data = ctx.getImageData(x, y, 1, 1);
+
+ if (Math.abs(data.data[0] - r) <= fuzz &&
+ Math.abs(data.data[1] - g) <= fuzz &&
+ Math.abs(data.data[2] - b) <= fuzz &&
+ Math.abs(data.data[3] - a) <= fuzz) {
+ return true;
+ }
+ return false;
+}
+
+function setTimeout(aMs, aCallback) {
+ var timer = Cc['@mozilla.org/timer;1'].
+ createInstance(Ci.nsITimer);
+ timer.initWithCallback(aCallback, aMs, Ci.nsITimer.TYPE_ONE_SHOT);
+}
+
+function takeWindowSnapshot(win, ctx) {
+ // TODO: drawWindow reads back from the gpu's backbuffer, which won't catch issues with presenting
+ // the front buffer via the window manager. Ideally we'd use an OS level API for reading back
+ // from the desktop itself to get a more accurate test.
+ var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
+ ctx.drawWindow(win.ownerGlobal, 0, 0, PAGE_WIDTH, PAGE_HEIGHT, "rgb(255,255,255)", flags);
+}
+
+// Verify that all the 4 coloured squares of the video
+// render as expected (with a tolerance of 64 to allow for
+// yuv->rgb differences between platforms).
+//
+// The video is 64x64, and is split into quadrants of
+// different colours. The top left of the video is 8,72
+// and we test a pixel 10,10 into each quadrant to avoid
+// blending differences at the edges.
+//
+// We allow massive amounts of fuzz for the colours since
+// it can depend hugely on the yuv -> rgb conversion, and
+// we don't want to fail unnecessarily.
+function verifyVideoRendering(ctx) {
+ return testPixel(ctx, 18, 82, 255, 255, 255, 255, 64) &&
+ testPixel(ctx, 50, 82, 0, 255, 0, 255, 64) &&
+ testPixel(ctx, 18, 114, 0, 0, 255, 255, 64) &&
+ testPixel(ctx, 50, 114, 255, 0, 0, 255, 64);
+}
+
+// Verify that the middle of the layers test is the color we expect.
+// It's a red 64x64 square, test a pixel deep into the 64x64 square
+// to prevent fuzzing.
+function verifyLayersRendering(ctx) {
+ return testPixel(ctx, 18, 18, 255, 0, 0, 255, 64);
+}
+
+function testCompositor(win, ctx) {
+ takeWindowSnapshot(win, ctx);
+ var testPassed = true;
+
+ if (!verifyVideoRendering(ctx)) {
+ Preferences.set(DISABLE_VIDEO_PREF, true);
+ testPassed = false;
+ }
+
+ if (!verifyLayersRendering(ctx)) {
+ testPassed = false;
+ }
+
+ if (testPassed) {
+ }
+
+ return testPassed;
+}
+
+var listener = {
+ win: null,
+ utils: null,
+ canvas: null,
+ ctx: null,
+ mm: null,
+
+ messages: [
+ "gfxSanity:ContentLoaded",
+ ],
+
+ scheduleTest: function(win) {
+ this.win = win;
+ this.win.onload = this.onWindowLoaded.bind(this);
+ this.utils = this.win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ setTimeout(TIMEOUT_SEC * 1000, () => {
+ if (this.win) {
+ this.endTest();
+ }
+ });
+ },
+
+ runSanityTest: function() {
+ this.canvas = this.win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ this.canvas.setAttribute("width", PAGE_WIDTH);
+ this.canvas.setAttribute("height", PAGE_HEIGHT);
+ this.ctx = this.canvas.getContext("2d");
+
+ // Perform the compositor backbuffer test, which currently we use for
+ // actually deciding whether to enable hardware media decoding.
+ testCompositor(this.win, this.ctx);
+
+ this.endTest();
+ },
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "gfxSanity:ContentLoaded":
+ this.runSanityTest();
+ break;
+ }
+ },
+
+ onWindowLoaded: function() {
+ let browser = this.win.document.createElementNS(XUL_NS, "browser");
+ browser.setAttribute("type", "content");
+
+ let remoteBrowser = Services.appinfo.browserTabsRemoteAutostart;
+ browser.setAttribute("remote", remoteBrowser);
+
+ browser.style.width = PAGE_WIDTH + "px";
+ browser.style.height = PAGE_HEIGHT + "px";
+
+ this.win.document.documentElement.appendChild(browser);
+ // Have to set the mm after we append the child
+ this.mm = browser.messageManager;
+
+ this.messages.forEach((msgName) => {
+ this.mm.addMessageListener(msgName, this);
+ });
+
+ this.mm.loadFrameScript(FRAME_SCRIPT_URL, false);
+ },
+
+ endTest: function() {
+ if (!this.win) {
+ return;
+ }
+
+ this.win.ownerGlobal.close();
+ this.win = null;
+ this.utils = null;
+ this.canvas = null;
+ this.ctx = null;
+
+ if (this.mm) {
+ // We don't have a MessageManager if onWindowLoaded never fired.
+ this.messages.forEach((msgName) => {
+ this.mm.removeMessageListener(msgName, this);
+ });
+
+ this.mm = null;
+ }
+ }
+};
+
+function SanityTest() {}
+SanityTest.prototype = {
+ classID: Components.ID("{f3a8ca4d-4c83-456b-aee2-6a2cbf11e9bd}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ shouldRunTest: function() {
+ // Only test gfx features if firefox has updated, or if the user has a new
+ // gpu or drivers.
+ var buildId = Services.appinfo.platformBuildID;
+ var gfxinfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ if (Preferences.get(RUNNING_PREF, false)) {
+ Preferences.set(DISABLE_VIDEO_PREF, true);
+ return false;
+ }
+
+ function checkPref(pref, value, reason) {
+ var prefValue = Preferences.get(pref, undefined);
+ if (prefValue == value) {
+ return true;
+ }
+ return false;
+ }
+
+ // TODO: Handle dual GPU setups
+ if (checkPref(DRIVER_PREF, gfxinfo.adapterDriverVersion, REASON_DRIVER_CHANGED) &&
+ checkPref(DEVICE_PREF, gfxinfo.adapterDeviceID, REASON_DEVICE_CHANGED) &&
+ checkPref(VERSION_PREF, buildId, REASON_FIREFOX_CHANGED))
+ {
+ return false;
+ }
+
+ // Enable hardware decoding so we can test again
+ // and record the driver version to detect if the driver changes.
+ Preferences.set(DISABLE_VIDEO_PREF, false);
+ Preferences.set(DRIVER_PREF, gfxinfo.adapterDriverVersion);
+ Preferences.set(DEVICE_PREF, gfxinfo.adapterDeviceID);
+ Preferences.set(VERSION_PREF, buildId);
+
+ // Update the prefs so that this test doesn't run again until the next update.
+ Preferences.set(RUNNING_PREF, true);
+ Services.prefs.savePrefFile(null);
+ return true;
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic != "profile-after-change") return;
+
+ // profile-after-change fires only at startup, so we won't need
+ // to use the listener again.
+ let tester = listener;
+ listener = null;
+
+ if (!this.shouldRunTest()) return;
+
+ // Open a tiny window to render our test page, and notify us when it's loaded
+ var sanityTest = Services.ww.openWindow(null,
+ "chrome://gfxsanity/content/sanityparent.html",
+ "Test Page",
+ "width=" + PAGE_WIDTH + ",height=" + PAGE_HEIGHT + ",chrome,titlebar=0,scrollbars=0",
+ null);
+
+ // There's no clean way to have an invisible window and ensure it's always painted.
+ // Instead, move the window far offscreen so it doesn't show up during launch.
+ sanityTest.moveTo(100000000, 1000000000);
+ tester.scheduleTest(sanityTest);
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SanityTest]);
diff --git a/components/gfx/content/gfxFrameScript.js b/components/gfx/content/gfxFrameScript.js
new file mode 100644
index 000000000..d7f25d2ef
--- /dev/null
+++ b/components/gfx/content/gfxFrameScript.js
@@ -0,0 +1,62 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const gfxFrameScript = {
+ domUtils: null,
+
+ init: function() {
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
+
+ this.domUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ webNav.loadURI("chrome://gfxsanity/content/sanitytest.html",
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
+ null, null, null);
+
+ },
+
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "MozAfterPaint":
+ sendAsyncMessage('gfxSanity:ContentLoaded');
+ removeEventListener("MozAfterPaint", this);
+ break;
+ }
+ },
+
+ isSanityTest: function(aUri) {
+ if (!aUri) {
+ return false;
+ }
+
+ return aUri.endsWith("/sanitytest.html");
+ },
+
+ onStateChange: function (webProgress, req, flags, status) {
+ if (webProgress.isTopLevel &&
+ (flags & Ci.nsIWebProgressListener.STATE_STOP) &&
+ this.isSanityTest(req.name)) {
+
+ webProgress.removeProgressListener(this);
+
+ // If no paint is pending, then the test already painted
+ if (this.domUtils.isMozAfterPaintPending) {
+ addEventListener("MozAfterPaint", this);
+ } else {
+ sendAsyncMessage('gfxSanity:ContentLoaded');
+ }
+ }
+ },
+
+ // Needed to support web progress listener
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver,
+ ]),
+};
+
+gfxFrameScript.init();
diff --git a/components/gfx/content/sanityparent.html b/components/gfx/content/sanityparent.html
new file mode 100644
index 000000000..151214e68
--- /dev/null
+++ b/components/gfx/content/sanityparent.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ </head>
+ <body>
+
+ </body>
+</html>
diff --git a/components/gfx/content/sanitytest.html b/components/gfx/content/sanitytest.html
new file mode 100644
index 000000000..d60c47923
--- /dev/null
+++ b/components/gfx/content/sanitytest.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ <div style="width:64px; height:64px; background-color:red;"></div>
+ <video src="videotest.mp4"></video>
+ </body>
+</html>
diff --git a/components/gfx/content/videotest.mp4 b/components/gfx/content/videotest.mp4
new file mode 100644
index 000000000..42cf6e1aa
--- /dev/null
+++ b/components/gfx/content/videotest.mp4
Binary files differ
diff --git a/components/gfx/jar.mn b/components/gfx/jar.mn
new file mode 100644
index 000000000..4794e7d3d
--- /dev/null
+++ b/components/gfx/jar.mn
@@ -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/.
+
+toolkit.jar:
+% content gfxsanity %content/gfxsanity/
+ content/gfxsanity/gfxFrameScript.js (content/gfxFrameScript.js)
+ content/gfxsanity/sanityparent.html (content/sanityparent.html)
+ content/gfxsanity/sanitytest.html (content/sanitytest.html)
+ content/gfxsanity/videotest.mp4 (content/videotest.mp4)
diff --git a/components/gfx/moz.build b/components/gfx/moz.build
new file mode 100644
index 000000000..68b8580df
--- /dev/null
+++ b/components/gfx/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
+
+if toolkit == 'windows':
+ EXTRA_COMPONENTS += [
+ 'GfxSanityTest.manifest',
+ 'SanityTest.js',
+ ]
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/global/Makefile.in b/components/global/Makefile.in
new file mode 100644
index 000000000..3567e0b48
--- /dev/null
+++ b/components/global/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/.
+
+DEFINES += \
+ -DCXXFLAGS='$(CXXFLAGS)' \
+ -DCPPFLAGS='$(CPPFLAGS)' \
+ $(NULL)
diff --git a/components/global/content/TopLevelVideoDocument.js b/components/global/content/TopLevelVideoDocument.js
new file mode 100644
index 000000000..5a2b8a857
--- /dev/null
+++ b/components/global/content/TopLevelVideoDocument.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// <video> is used for top-level audio documents as well
+let videoElement = document.getElementsByTagName("video")[0];
+
+// 1. Handle fullscreen mode;
+// 2. Send keystrokes to the video element if the body element is focused,
+// to be received by the event listener in videocontrols.xml.
+document.addEventListener("keypress", ev => {
+ if (ev.synthetic) // prevent recursion
+ return;
+
+ // Maximize the standalone video when pressing F11,
+ // but ignore audio elements
+ if (ev.key == "F11" && videoElement.videoWidth != 0 && videoElement.videoHeight != 0) {
+ // If we're in browser fullscreen mode, it means the user pressed F11
+ // while browser chrome or another tab had focus.
+ // Don't break leaving that mode, so do nothing here.
+ if (window.fullScreen) {
+ return;
+ }
+
+ // If we're not in broser fullscreen mode, prevent entering into that,
+ // so we don't end up there after pressing Esc.
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ if (!document.mozFullScreenElement) {
+ videoElement.mozRequestFullScreen();
+ } else {
+ document.mozCancelFullScreen();
+ }
+ return;
+ }
+
+ // Check if the video element is focused, so it already receives
+ // keystrokes, and don't send it another one from here.
+ if (document.activeElement == videoElement)
+ return;
+
+ let newEvent = new KeyboardEvent("keypress", ev);
+ newEvent.synthetic = true;
+ videoElement.dispatchEvent(newEvent);
+});
diff --git a/components/global/content/XPCNativeWrapper.js b/components/global/content/XPCNativeWrapper.js
new file mode 100644
index 000000000..7010a28ab
--- /dev/null
+++ b/components/global/content/XPCNativeWrapper.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/. */
+
+/*
+ * Moved to C++ implementation in XPConnect. See bug 281988.
+ */
diff --git a/components/global/content/about.js b/components/global/content/about.js
new file mode 100644
index 000000000..c402ea685
--- /dev/null
+++ b/components/global/content/about.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// get release notes and vendor URL from prefs
+var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter);
+var releaseNotesURL;
+try {
+ releaseNotesURL = formatter.formatURLPref("app.releaseNotesURL");
+} catch(e) {
+ releaseNotesURL = "about:blank";
+}
+if (releaseNotesURL != "about:blank") {
+ var relnotes = document.getElementById("releaseNotesURL");
+ relnotes.setAttribute("href", releaseNotesURL);
+ relnotes.parentNode.removeAttribute("hidden");
+}
+
+var vendorURL;
+try {
+ vendorURL = formatter.formatURLPref("app.vendorURL");
+} catch(e) {
+ vendorURL = "about:blank";
+}
+if (vendorURL != "about:blank") {
+ var vendor = document.getElementById("vendorURL");
+ vendor.setAttribute("href", vendorURL);
+}
+
+// insert the version of the XUL application (!= XULRunner platform version)
+var versionNum = Components.classes["@mozilla.org/xre/app-info;1"]
+ .getService(Components.interfaces.nsIXULAppInfo)
+ .version;
+var version = document.getElementById("version");
+#ifdef HAVE_64BIT_BUILD
+var versionStr = versionNum + " (64-bit)";
+#else
+var versionStr = versionNum + " (32-bit)";
+#endif
+version.textContent += " " + versionStr;
+
+// insert the buildid of the XUL application
+var BuildIDVal = Components.classes["@mozilla.org/xre/app-info;1"]
+ .getService(Components.interfaces.nsIXULAppInfo)
+ .appBuildID;
+var buildID = document.getElementById("buildID");
+buildID.textContent += " " + BuildIDVal.slice(0,-6);
+
+// append user agent
+var ua = navigator.userAgent;
+if (ua) {
+ document.getElementById("userAgent").textContent += " " + ua;
+}
diff --git a/components/global/content/about.xhtml b/components/global/content/about.xhtml
new file mode 100644
index 000000000..1f57ddcc3
--- /dev/null
+++ b/components/global/content/about.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % aboutDTD SYSTEM "chrome://global/locale/about.dtd" >
+%aboutDTD;
+<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+%globalDTD;
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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>About:</title>
+ <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
+</head>
+
+<body dir="&locale.dir;">
+ <div id="aboutLogoContainer">
+ <a id="vendorURL">
+ <img src="about:logo" alt="&brandShortName;"/>
+ </a>
+ </div>
+
+ <ul id="aboutPageList">
+ <li>&about.credits.beforeLink;<a href="about:credits">&about.credits.linkTitle;</a>&about.credits.afterLink;</li>
+ <li>&about.license.beforeTheLink;<a href="about:license">&about.license.linkTitle;</a>&about.license.afterTheLink;</li>
+ <li hidden="true">&about.relnotes.beforeTheLink;<a id="releaseNotesURL">&about.relnotes.linkTitle;</a>&about.relnotes.afterTheLink;</li>
+ <li>&about.buildconfig.beforeTheLink;<a href="about:buildconfig">&about.buildconfig.linkTitle;</a>&about.buildconfig.afterTheLink;</li>
+ <li id="version">&about.version;</li>
+ <li id="buildID">&about.buildIdentifier;</li>
+ <li id="userAgent">&about.userAgent;</li>
+ <script type="application/javascript" src="chrome://global/content/about.js"/>
+ </ul>
+
+</body>
+</html>
diff --git a/components/global/content/aboutAbout.js b/components/global/content/aboutAbout.js
new file mode 100644
index 000000000..13cb6bd6c
--- /dev/null
+++ b/components/global/content/aboutAbout.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var gProtocols = [];
+var gContainer;
+window.onload = function () {
+ gContainer = document.getElementById("abouts");
+ findAbouts();
+}
+
+function findAbouts() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ for (var cid in Cc) {
+ var result = cid.match(/@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/);
+ if (result) {
+ var aboutType = result[1];
+ var contract = "@mozilla.org/network/protocol/about;1?what=" + aboutType;
+ try {
+ var am = Cc[contract].getService(Ci.nsIAboutModule);
+ var uri = ios.newURI("about:"+aboutType, null, null);
+ var flags = am.getURIFlags(uri);
+ if (!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT)) {
+ gProtocols.push(aboutType);
+ }
+ } catch (e) {
+ // getService might have thrown if the component doesn't actually
+ // implement nsIAboutModule
+ }
+ }
+ }
+ gProtocols.sort().forEach(createProtocolListing);
+}
+
+function createProtocolListing(aProtocol) {
+ var uri = "about:" + aProtocol;
+ var li = document.createElement("li");
+ var link = document.createElement("a");
+ var text = document.createTextNode(uri);
+
+ link.href = uri;
+ link.appendChild(text);
+ li.appendChild(link);
+ gContainer.appendChild(li);
+}
diff --git a/components/global/content/aboutAbout.xhtml b/components/global/content/aboutAbout.xhtml
new file mode 100644
index 000000000..5ec038638
--- /dev/null
+++ b/components/global/content/aboutAbout.xhtml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
+<!ENTITY % aboutAboutDTD SYSTEM "chrome://global/locale/aboutAbout.dtd" >
+%aboutAboutDTD;
+<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+%globalDTD;
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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>&aboutAbout.title;</title>
+ <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
+ <script type="application/javascript" src="chrome://global/content/aboutAbout.js"></script>
+</head>
+
+<body dir="&locale.dir;">
+ <h1>&aboutAbout.title;</h1>
+ <p><em>&aboutAbout.note;</em></p>
+ <ul id="abouts" class="columns"></ul>
+</body>
+</html>
diff --git a/components/global/content/aboutNetworking.js b/components/global/content/aboutNetworking.js
new file mode 100644
index 000000000..9400ae9d7
--- /dev/null
+++ b/components/global/content/aboutNetworking.js
@@ -0,0 +1,414 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Services.jsm");
+const FileUtils = Cu.import("resource://gre/modules/FileUtils.jsm").FileUtils
+const gEnv = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+const gDashboard = Cc['@mozilla.org/network/dashboard;1']
+ .getService(Ci.nsIDashboard);
+const gDirServ = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIDirectoryServiceProvider);
+
+const gRequestNetworkingData = {
+ "http": gDashboard.requestHttpConnections,
+ "sockets": gDashboard.requestSockets,
+ "dns": gDashboard.requestDNSInfo,
+ "websockets": gDashboard.requestWebsocketConnections
+};
+const gDashboardCallbacks = {
+ "http": displayHttp,
+ "sockets": displaySockets,
+ "dns": displayDns,
+ "websockets": displayWebsockets
+};
+
+const REFRESH_INTERVAL_MS = 3000;
+
+function col(element) {
+ let col = document.createElement('td');
+ let content = document.createTextNode(element);
+ col.appendChild(content);
+ return col;
+}
+
+function displayHttp(data) {
+ let cont = document.getElementById('http_content');
+ let parent = cont.parentNode;
+ let new_cont = document.createElement('tbody');
+ new_cont.setAttribute('id', 'http_content');
+
+ for (let i = 0; i < data.connections.length; i++) {
+ let row = document.createElement('tr');
+ row.appendChild(col(data.connections[i].host));
+ row.appendChild(col(data.connections[i].port));
+ row.appendChild(col(data.connections[i].spdy));
+ row.appendChild(col(data.connections[i].ssl));
+ row.appendChild(col(data.connections[i].active.length));
+ row.appendChild(col(data.connections[i].idle.length));
+ new_cont.appendChild(row);
+ }
+
+ parent.replaceChild(new_cont, cont);
+}
+
+function displaySockets(data) {
+ let cont = document.getElementById('sockets_content');
+ let parent = cont.parentNode;
+ let new_cont = document.createElement('tbody');
+ new_cont.setAttribute('id', 'sockets_content');
+
+ for (let i = 0; i < data.sockets.length; i++) {
+ let row = document.createElement('tr');
+ row.appendChild(col(data.sockets[i].host));
+ row.appendChild(col(data.sockets[i].port));
+ row.appendChild(col(data.sockets[i].tcp));
+ row.appendChild(col(data.sockets[i].active));
+ row.appendChild(col(data.sockets[i].sent));
+ row.appendChild(col(data.sockets[i].received));
+ new_cont.appendChild(row);
+ }
+
+ parent.replaceChild(new_cont, cont);
+}
+
+function displayDns(data) {
+ let cont = document.getElementById('dns_content');
+ let parent = cont.parentNode;
+ let new_cont = document.createElement('tbody');
+ new_cont.setAttribute('id', 'dns_content');
+
+ for (let i = 0; i < data.entries.length; i++) {
+ let row = document.createElement('tr');
+ row.appendChild(col(data.entries[i].hostname));
+ row.appendChild(col(data.entries[i].family));
+ let column = document.createElement('td');
+
+ for (let j = 0; j < data.entries[i].hostaddr.length; j++) {
+ column.appendChild(document.createTextNode(data.entries[i].hostaddr[j]));
+ column.appendChild(document.createElement('br'));
+ }
+
+ row.appendChild(column);
+ row.appendChild(col(data.entries[i].expiration));
+ new_cont.appendChild(row);
+ }
+
+ parent.replaceChild(new_cont, cont);
+}
+
+function displayWebsockets(data) {
+ let cont = document.getElementById('websockets_content');
+ let parent = cont.parentNode;
+ let new_cont = document.createElement('tbody');
+ new_cont.setAttribute('id', 'websockets_content');
+
+ for (let i = 0; i < data.websockets.length; i++) {
+ let row = document.createElement('tr');
+ row.appendChild(col(data.websockets[i].hostport));
+ row.appendChild(col(data.websockets[i].encrypted));
+ row.appendChild(col(data.websockets[i].msgsent));
+ row.appendChild(col(data.websockets[i].msgreceived));
+ row.appendChild(col(data.websockets[i].sentsize));
+ row.appendChild(col(data.websockets[i].receivedsize));
+ new_cont.appendChild(row);
+ }
+
+ parent.replaceChild(new_cont, cont);
+}
+
+function requestAllNetworkingData() {
+ for (let id in gRequestNetworkingData)
+ requestNetworkingDataForTab(id);
+}
+
+function requestNetworkingDataForTab(id) {
+ gRequestNetworkingData[id](gDashboardCallbacks[id]);
+}
+
+function init() {
+ gDashboard.enableLogging = true;
+ if (Services.prefs.getBoolPref("network.warnOnAboutNetworking")) {
+ let div = document.getElementById("warning_message");
+ div.classList.add("active");
+ div.hidden = false;
+ document.getElementById("confpref").addEventListener("click", confirm);
+ }
+
+ requestAllNetworkingData();
+
+ let autoRefresh = document.getElementById("autorefcheck");
+ if (autoRefresh.checked)
+ setAutoRefreshInterval(autoRefresh);
+
+ autoRefresh.addEventListener("click", function() {
+ let refrButton = document.getElementById("refreshButton");
+ if (this.checked) {
+ setAutoRefreshInterval(this);
+ refrButton.disabled = "disabled";
+ } else {
+ clearInterval(this.interval);
+ refrButton.disabled = null;
+ }
+ });
+
+ let refr = document.getElementById("refreshButton");
+ refr.addEventListener("click", requestAllNetworkingData);
+ if (document.getElementById("autorefcheck").checked)
+ refr.disabled = "disabled";
+
+ // Event delegation on #categories element
+ let menu = document.getElementById("categories");
+ menu.addEventListener("click", function click(e) {
+ if (e.target && e.target.parentNode == menu)
+ show(e.target);
+ });
+
+ let dnsLookupButton = document.getElementById("dnsLookupButton");
+ dnsLookupButton.addEventListener("click", function() {
+ doLookup();
+ });
+
+ let setLogButton = document.getElementById("set-log-file-button");
+ setLogButton.addEventListener("click", setLogFile);
+
+ let setModulesButton = document.getElementById("set-log-modules-button");
+ setModulesButton.addEventListener("click", setLogModules);
+
+ let startLoggingButton = document.getElementById("start-logging-button");
+ startLoggingButton.addEventListener("click", startLogging);
+
+ let stopLoggingButton = document.getElementById("stop-logging-button");
+ stopLoggingButton.addEventListener("click", stopLogging);
+
+ try {
+ let file = gDirServ.getFile("TmpD", {});
+ file.append("log.txt");
+ document.getElementById("log-file").value = file.path;
+ } catch (e) {
+ console.error(e);
+ }
+
+ // Update the value of the log file.
+ updateLogFile();
+
+ // Update the active log modules
+ updateLogModules();
+
+ // If we can't set the file and the modules at runtime,
+ // the start and stop buttons wouldn't really do anything.
+ if (setLogButton.disabled && setModulesButton.disabled) {
+ startLoggingButton.disabled = true;
+ stopLoggingButton.disabled = true;
+ }
+}
+
+function updateLogFile() {
+ let logPath = "";
+
+ // Try to get the environment variable for the log file
+ logPath = gEnv.get("MOZ_LOG_FILE") || gEnv.get("NSPR_LOG_FILE");
+ let currentLogFile = document.getElementById("current-log-file");
+ let setLogFileButton = document.getElementById("set-log-file-button");
+
+ // If the log file was set from an env var, we disable the ability to set it
+ // at runtime.
+ if (logPath.length > 0) {
+ currentLogFile.innerText = logPath;
+ setLogFileButton.disabled = true;
+ } else {
+ // There may be a value set by a pref.
+ currentLogFile.innerText = gDashboard.getLogPath();
+ }
+}
+
+function updateLogModules() {
+ // Try to get the environment variable for the log file
+ let logModules = gEnv.get("MOZ_LOG") ||
+ gEnv.get("MOZ_LOG_MODULES") ||
+ gEnv.get("NSPR_LOG_MODULES");
+ let currentLogModules = document.getElementById("current-log-modules");
+ let setLogModulesButton = document.getElementById("set-log-modules-button");
+ if (logModules.length > 0) {
+ currentLogModules.innerText = logModules;
+ // If the log modules are set by an environment variable at startup, do not
+ // allow changing them throught a pref. It would be difficult to figure out
+ // which ones are enabled and which ones are not. The user probably knows
+ // what he they are doing.
+ setLogModulesButton.disabled = true;
+ } else {
+ let activeLogModules = [];
+ try {
+ if (Services.prefs.getBoolPref("logging.config.add_timestamp")) {
+ activeLogModules.push("timestamp");
+ }
+ } catch (e) {}
+ try {
+ if (Services.prefs.getBoolPref("logging.config.sync")) {
+ activeLogModules.push("sync");
+ }
+ } catch (e) {}
+
+ let children = Services.prefs.getBranch("logging.").getChildList("", {});
+
+ for (let pref of children) {
+ if (pref.startsWith("config.")) {
+ continue;
+ }
+
+ try {
+ let value = Services.prefs.getIntPref(`logging.${pref}`);
+ activeLogModules.push(`${pref}:${value}`);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ currentLogModules.innerText = activeLogModules.join(",");
+ }
+}
+
+function setLogFile() {
+ let setLogButton = document.getElementById("set-log-file-button");
+ if (setLogButton.disabled) {
+ // There's no point trying since it wouldn't work anyway.
+ return;
+ }
+ let logFile = document.getElementById("log-file").value.trim();
+ Services.prefs.setCharPref("logging.config.LOG_FILE", logFile);
+ updateLogFile();
+}
+
+function clearLogModules() {
+ // Turn off all the modules.
+ let children = Services.prefs.getBranch("logging.").getChildList("", {});
+ for (let pref of children) {
+ if (!pref.startsWith("config.")) {
+ Services.prefs.clearUserPref(`logging.${pref}`);
+ }
+ }
+ Services.prefs.clearUserPref("logging.config.add_timestamp");
+ Services.prefs.clearUserPref("logging.config.sync");
+ updateLogModules();
+}
+
+function setLogModules() {
+ let setLogModulesButton = document.getElementById("set-log-modules-button");
+ if (setLogModulesButton.disabled) {
+ // The modules were set via env var, so we shouldn't try to change them.
+ return;
+ }
+
+ let modules = document.getElementById("log-modules").value.trim();
+
+ // Clear previously set log modules.
+ clearLogModules();
+
+ let logModules = modules.split(",");
+ for (let module of logModules) {
+ if (module == "timestamp") {
+ Services.prefs.setBoolPref("logging.config.add_timestamp", true);
+ } else if (module == "rotate") {
+ // XXX: rotate is not yet supported.
+ } else if (module == "append") {
+ // XXX: append is not yet supported.
+ } else if (module == "sync") {
+ Services.prefs.setBoolPref("logging.config.sync", true);
+ } else {
+ let [key, value] = module.split(":");
+ Services.prefs.setIntPref(`logging.${key}`, parseInt(value, 10));
+ }
+ }
+
+ updateLogModules();
+}
+
+function startLogging() {
+ setLogFile();
+ setLogModules();
+}
+
+function stopLogging() {
+ clearLogModules();
+ // clear the log file as well
+ Services.prefs.clearUserPref("logging.config.LOG_FILE");
+ updateLogFile();
+}
+
+function confirm () {
+ let div = document.getElementById("warning_message");
+ div.classList.remove("active");
+ div.hidden = true;
+ let warnBox = document.getElementById("warncheck");
+ Services.prefs.setBoolPref("network.warnOnAboutNetworking", warnBox.checked);
+}
+
+function show(button) {
+ let current_tab = document.querySelector(".active");
+ let content = document.getElementById(button.getAttribute("value"));
+ if (current_tab == content)
+ return;
+ current_tab.classList.remove("active");
+ current_tab.hidden = true;
+ content.classList.add("active");
+ content.hidden = false;
+
+ let current_button = document.querySelector("[selected=true]");
+ current_button.removeAttribute("selected");
+ button.setAttribute("selected", "true");
+
+ let autoRefresh = document.getElementById("autorefcheck");
+ if (autoRefresh.checked) {
+ clearInterval(autoRefresh.interval);
+ setAutoRefreshInterval(autoRefresh);
+ }
+
+ let title = document.getElementById("sectionTitle");
+ title.textContent = button.children[0].textContent;
+}
+
+function setAutoRefreshInterval(checkBox) {
+ let active_tab = document.querySelector(".active");
+ checkBox.interval = setInterval(function() {
+ requestNetworkingDataForTab(active_tab.id);
+ }, REFRESH_INTERVAL_MS);
+}
+
+window.addEventListener("DOMContentLoaded", function load() {
+ window.removeEventListener("DOMContentLoaded", load);
+ init();
+});
+
+function doLookup() {
+ let host = document.getElementById("host").value;
+ if (host) {
+ gDashboard.requestDNSLookup(host, displayDNSLookup);
+ }
+}
+
+function displayDNSLookup(data) {
+ let cont = document.getElementById("dnslookuptool_content");
+ let parent = cont.parentNode;
+ let new_cont = document.createElement("tbody");
+ new_cont.setAttribute("id", "dnslookuptool_content");
+
+ if (data.answer) {
+ for (let address of data.address) {
+ let row = document.createElement("tr");
+ row.appendChild(col(address));
+ new_cont.appendChild(row);
+ }
+ }
+ else {
+ new_cont.appendChild(col(data.error));
+ }
+
+ parent.replaceChild(new_cont, cont);
+}
diff --git a/components/global/content/aboutNetworking.xhtml b/components/global/content/aboutNetworking.xhtml
new file mode 100644
index 000000000..440ee7838
--- /dev/null
+++ b/components/global/content/aboutNetworking.xhtml
@@ -0,0 +1,168 @@
+<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
+<!ENTITY % networkingDTD SYSTEM "chrome://global/locale/aboutNetworking.dtd"> %networkingDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&aboutNetworking.title;</title>
+ <link rel="stylesheet" href="chrome://mozapps/skin/aboutNetworking.css" type="text/css" />
+ <script type="application/javascript;version=1.7" src="chrome://global/content/aboutNetworking.js" />
+ </head>
+ <body id="body">
+ <div id="warning_message" class="warningBackground" hidden="true">
+ <div class="container">
+ <h1 class="title">&aboutNetworking.warning;</h1>
+ <div class="toggle-container-with-text">
+ <input id="warncheck" type="checkbox" checked="yes" />
+ <label for="warncheck">&aboutNetworking.showNextTime;</label>
+ </div>
+ <div>
+ <button id="confpref" class="primary">&aboutNetworking.ok;</button>
+ </div>
+ </div>
+ </div>
+ <div id="categories">
+ <div class="category" selected="true" value="http">
+ <span class="category-name">&aboutNetworking.HTTP;</span>
+ </div>
+ <div class="category" value="sockets">
+ <span class="category-name">&aboutNetworking.sockets;</span>
+ </div>
+ <div class="category" value="dns">
+ <span class="category-name">&aboutNetworking.dns;</span>
+ </div>
+ <div class="category" value="websockets">
+ <span class="category-name">&aboutNetworking.websockets;</span>
+ </div>
+ <hr></hr>
+ <div class="category" value="dnslookuptool">
+ <span class="category-name">&aboutNetworking.dnsLookup;</span>
+ </div>
+ <div class="category" value="logging">
+ <span class="category-name">&aboutNetworking.logging;</span>
+ </div>
+ </div>
+ <div class="main-content">
+ <div class="header">
+ <div id="sectionTitle" class="header-name">
+ &aboutNetworking.HTTP;
+ </div>
+ <div id="refreshDiv" class="toggle-container-with-text">
+ <button id="refreshButton">&aboutNetworking.refresh;</button>
+ <input id="autorefcheck" type="checkbox" name="Autorefresh" />
+ <label for="autorefcheck">&aboutNetworking.autoRefresh;</label>
+ </div>
+ </div>
+
+ <div id="http" class="tab active">
+ <table>
+ <thead>
+ <tr>
+ <th>&aboutNetworking.hostname;</th>
+ <th>&aboutNetworking.port;</th>
+ <th>&aboutNetworking.spdy;</th>
+ <th>&aboutNetworking.ssl;</th>
+ <th>&aboutNetworking.active;</th>
+ <th>&aboutNetworking.idle;</th>
+ </tr>
+ </thead>
+ <tbody id="http_content" />
+ </table>
+ </div>
+
+ <div id="sockets" class="tab" hidden="true">
+ <table>
+ <thead>
+ <tr>
+ <th>&aboutNetworking.host;</th>
+ <th>&aboutNetworking.port;</th>
+ <th>&aboutNetworking.tcp;</th>
+ <th>&aboutNetworking.active;</th>
+ <th>&aboutNetworking.sent;</th>
+ <th>&aboutNetworking.received;</th>
+ </tr>
+ </thead>
+ <tbody id="sockets_content" />
+ </table>
+ </div>
+
+ <div id="dns" class="tab" hidden="true">
+ <table>
+ <thead>
+ <tr>
+ <th>&aboutNetworking.hostname;</th>
+ <th>&aboutNetworking.family;</th>
+ <th>&aboutNetworking.addresses;</th>
+ <th>&aboutNetworking.expires;</th>
+ </tr>
+ </thead>
+ <tbody id="dns_content" />
+ </table>
+ </div>
+
+ <div id="websockets" class="tab" hidden="true">
+ <table>
+ <thead>
+ <tr>
+ <th>&aboutNetworking.hostname;</th>
+ <th>&aboutNetworking.ssl;</th>
+ <th>&aboutNetworking.messagesSent;</th>
+ <th>&aboutNetworking.messagesReceived;</th>
+ <th>&aboutNetworking.bytesSent;</th>
+ <th>&aboutNetworking.bytesReceived;</th>
+ </tr>
+ </thead>
+ <tbody id="websockets_content" />
+ </table>
+ </div>
+
+ <div id="dnslookuptool" class="tab" hidden="true">
+ &aboutNetworking.dnsDomain;: <input type="text" name="host" id="host"></input>
+ <button id="dnsLookupButton">&aboutNetworking.dnsLookupButton;</button>
+ <hr/>
+ <table>
+ <thead>
+ <tr>
+ <th>&aboutNetworking.dnsLookupTableColumn;</th>
+ </tr>
+ </thead>
+ <tbody id="dnslookuptool_content" />
+ </table>
+ </div>
+
+ <div id="logging" class="tab" hidden="true">
+ <div>
+ &aboutNetworking.logTutorial;
+ </div>
+ <br/>
+ <div>
+ <button id="start-logging-button"> &aboutNetworking.startLogging; </button>
+ <button id="stop-logging-button"> &aboutNetworking.stopLogging; </button>
+ </div>
+ <br/>
+ <br/>
+ <div>
+ &aboutNetworking.currentLogFile; <div id="current-log-file"></div><br/>
+ <input type="text" name="log-file" id="log-file"></input>
+ <button id="set-log-file-button"> &aboutNetworking.setLogFile; </button>
+ </div>
+ <div>
+ &aboutNetworking.currentLogModules; <div id="current-log-modules"></div><br/>
+ <input type="text" name="log-modules" id="log-modules" value="timestamp,sync,nsHttp:5,nsSocketTransport:5,nsStreamPump:5,nsHostResolver:5"></input>
+ <button id="set-log-modules-button"> &aboutNetworking.setLogModules; </button>
+ </div>
+ </div>
+
+ </div>
+ </body>
+</html>
+
diff --git a/components/global/content/aboutProfiles.js b/components/global/content/aboutProfiles.js
new file mode 100644
index 000000000..0e548fd0f
--- /dev/null
+++ b/components/global/content/aboutProfiles.js
@@ -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/. */
+
+'use strict';
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ 'ProfileService',
+ '@mozilla.org/toolkit/profile-service;1',
+ 'nsIToolkitProfileService'
+);
+
+const bundle = Services.strings.createBundle(
+ 'chrome://global/locale/aboutProfiles.properties');
+
+// nsIToolkitProfileService.selectProfile can be used only during the selection
+// of the profile in the ProfileManager. If we are showing about:profiles in a
+// tab, the selectedProfile returns the default profile.
+// In this function we use the ProfD to find the current profile.
+function findCurrentProfile() {
+ let cpd;
+ try {
+ cpd = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ } catch (e) {}
+
+ if (cpd) {
+ let itr = ProfileService.profiles;
+ while (itr.hasMoreElements()) {
+ let profile = itr.getNext().QueryInterface(Ci.nsIToolkitProfile);
+ if (profile.rootDir.path == cpd.path) {
+ return profile;
+ }
+ }
+ }
+
+ // selectedProfile can trow if nothing is selected or if the selected profile
+ // has been deleted.
+ try {
+ return ProfileService.selectedProfile;
+ } catch (e) {
+ return null;
+ }
+}
+
+function refreshUI() {
+ let parent = document.getElementById('profiles');
+ while (parent.firstChild) {
+ parent.removeChild(parent.firstChild);
+ }
+
+ let defaultProfile;
+ try {
+ defaultProfile = ProfileService.defaultProfile;
+ } catch (e) {}
+
+ let currentProfile = findCurrentProfile() || defaultProfile;
+
+ let iter = ProfileService.profiles;
+ while (iter.hasMoreElements()) {
+ let profile = iter.getNext().QueryInterface(Ci.nsIToolkitProfile);
+ display({ profile: profile,
+ isDefault: profile == defaultProfile,
+ isCurrentProfile: profile == currentProfile });
+ }
+
+ let createButton = document.getElementById('create-button');
+ createButton.onclick = createProfileWizard;
+
+ let restartSafeModeButton = document.getElementById('restart-in-safe-mode-button');
+ restartSafeModeButton.onclick = function() { restart(true); }
+
+ let restartNormalModeButton = document.getElementById('restart-button');
+ restartNormalModeButton.onclick = function() { restart(false); }
+}
+
+function openDirectory(dir) {
+ let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
+ "nsILocalFile", "initWithPath");
+ new nsLocalFile(dir).reveal();
+}
+
+function display(profileData) {
+ let parent = document.getElementById('profiles');
+
+ let div = document.createElement('div');
+ parent.appendChild(div);
+
+ let nameStr = bundle.formatStringFromName('name', [profileData.profile.name], 1);
+
+ let name = document.createElement('h2');
+ name.appendChild(document.createTextNode(nameStr));
+
+ div.appendChild(name);
+
+ if (profileData.isCurrentProfile) {
+ let currentProfile = document.createElement('h3');
+ let currentProfileStr = bundle.GetStringFromName('currentProfile');
+ currentProfile.appendChild(document.createTextNode(currentProfileStr));
+ div.appendChild(currentProfile);
+ }
+
+ let table = document.createElement('table');
+ div.appendChild(table);
+
+ let tbody = document.createElement('tbody');
+ table.appendChild(tbody);
+
+ function createItem(title, value, dir = false) {
+ let tr = document.createElement('tr');
+ tbody.appendChild(tr);
+
+ let th = document.createElement('th');
+ th.setAttribute('class', 'column');
+ th.appendChild(document.createTextNode(title));
+ tr.appendChild(th);
+
+ let td = document.createElement('td');
+ td.appendChild(document.createTextNode(value));
+ tr.appendChild(td);
+
+ if (dir) {
+ td.appendChild(document.createTextNode(' '));
+ let button = document.createElement('button');
+#ifdef XP_WIN
+ let string = 'winOpenDir2';
+#else
+ let string = 'openDir';
+#endif
+ let buttonText = document.createTextNode(bundle.GetStringFromName(string));
+ button.appendChild(buttonText);
+ td.appendChild(button);
+
+ button.addEventListener('click', function(e) {
+ openDirectory(value);
+ });
+ }
+ }
+
+ createItem(bundle.GetStringFromName('isDefault'),
+ profileData.isDefault ? bundle.GetStringFromName('yes') : bundle.GetStringFromName('no'));
+
+ createItem(bundle.GetStringFromName('rootDir'), profileData.profile.rootDir.path, true);
+
+ if (profileData.profile.localDir.path != profileData.profile.rootDir.path) {
+ createItem(bundle.GetStringFromName('localDir'), profileData.profile.localDir.path, true);
+ }
+
+ let renameButton = document.createElement('button');
+ renameButton.appendChild(document.createTextNode(bundle.GetStringFromName('rename')));
+ renameButton.onclick = function() {
+ renameProfile(profileData.profile);
+ };
+ div.appendChild(renameButton);
+
+ if (!profileData.isCurrentProfile) {
+ let removeButton = document.createElement('button');
+ removeButton.appendChild(document.createTextNode(bundle.GetStringFromName('remove')));
+ removeButton.onclick = function() {
+ removeProfile(profileData.profile);
+ };
+
+ div.appendChild(removeButton);
+ }
+
+ if (!profileData.isDefault) {
+ let defaultButton = document.createElement('button');
+ defaultButton.appendChild(document.createTextNode(bundle.GetStringFromName('setAsDefault')));
+ defaultButton.onclick = function() {
+ defaultProfile(profileData.profile);
+ };
+ div.appendChild(defaultButton);
+ }
+
+ if (!profileData.isCurrentProfile) {
+ let runButton = document.createElement('button');
+ runButton.appendChild(document.createTextNode(bundle.GetStringFromName('launchProfile')));
+ runButton.onclick = function() {
+ openProfile(profileData.profile);
+ };
+ div.appendChild(runButton);
+ }
+
+ let sep = document.createElement('hr');
+ div.appendChild(sep);
+}
+
+function CreateProfile(profile) {
+ ProfileService.selectedProfile = profile;
+ ProfileService.flush();
+ refreshUI();
+}
+
+function createProfileWizard() {
+ // This should be rewritten in HTML eventually.
+ window.openDialog('chrome://mozapps/content/profile/createProfileWizard.xul',
+ '', 'centerscreen,chrome,modal,titlebar',
+ ProfileService);
+}
+
+function renameProfile(profile) {
+ let title = bundle.GetStringFromName('renameProfileTitle');
+ let msg = bundle.formatStringFromName('renameProfile', [profile.name], 1);
+ let newName = { value: profile.name };
+
+ if (Services.prompt.prompt(window, title, msg, newName, null,
+ { value: 0 })) {
+ newName = newName.value;
+
+ if (newName == profile.name) {
+ return;
+ }
+
+ try {
+ profile.name = newName;
+ } catch (e) {
+ let title = bundle.GetStringFromName('invalidProfileNameTitle');
+ let msg = bundle.formatStringFromName('invalidProfileName', [newName], 1);
+ Services.prompt.alert(window, title, msg);
+ return;
+ }
+
+ ProfileService.flush();
+ refreshUI();
+ }
+}
+
+function removeProfile(profile) {
+ let deleteFiles = false;
+
+ if (profile.rootDir.exists()) {
+ let title = bundle.GetStringFromName('deleteProfileTitle');
+ let msg = bundle.formatStringFromName('deleteProfileConfirm',
+ [profile.rootDir.path], 1);
+
+ let buttonPressed = Services.prompt.confirmEx(window, title, msg,
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1) +
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2),
+ bundle.GetStringFromName('dontDeleteFiles'),
+ null,
+ bundle.GetStringFromName('deleteFiles'),
+ null, {value:0});
+ if (buttonPressed == 1) {
+ return;
+ }
+
+ if (buttonPressed == 2) {
+ deleteFiles = true;
+ }
+ }
+
+ // If we are deleting the selected or the default profile we must choose a
+ // different one.
+ let isSelected = false;
+ try {
+ isSelected = ProfileService.selectedProfile == profile;
+ } catch (e) {}
+
+ let isDefault = false;
+ try {
+ isDefault = ProfileService.defaultProfile == profile;
+ } catch (e) {}
+
+ if (isSelected || isDefault) {
+ let itr = ProfileService.profiles;
+ while (itr.hasMoreElements()) {
+ let p = itr.getNext().QueryInterface(Ci.nsIToolkitProfile);
+ if (profile == p) {
+ continue;
+ }
+
+ if (isSelected) {
+ ProfileService.selectedProfile = p;
+ }
+
+ if (isDefault) {
+ ProfileService.defaultProfile = p;
+ }
+
+ break;
+ }
+ }
+
+ profile.remove(deleteFiles);
+ ProfileService.flush();
+ refreshUI();
+}
+
+function defaultProfile(profile) {
+ ProfileService.defaultProfile = profile;
+ ProfileService.selectedProfile = profile;
+ ProfileService.flush();
+ refreshUI();
+}
+
+function openProfile(profile) {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ if (cancelQuit.data) {
+ return;
+ }
+
+ Services.startup.createInstanceWithProfile(profile);
+}
+
+function restart(safeMode) {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ if (cancelQuit.data) {
+ return;
+ }
+
+ let flags = Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestartNotSameProfile;
+
+ if (safeMode) {
+ Services.startup.restartInSafeMode(flags);
+ } else {
+ Services.startup.quit(flags);
+ }
+}
+
+window.addEventListener('DOMContentLoaded', function load() {
+ window.removeEventListener('DOMContentLoaded', load);
+ refreshUI();
+});
diff --git a/components/global/content/aboutProfiles.xhtml b/components/global/content/aboutProfiles.xhtml
new file mode 100644
index 000000000..ae2964932
--- /dev/null
+++ b/components/global/content/aboutProfiles.xhtml
@@ -0,0 +1,38 @@
+<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
+<!ENTITY % profilesDTD SYSTEM "chrome://global/locale/aboutProfiles.dtd"> %profilesDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&aboutProfiles.title;</title>
+ <link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://mozapps/skin/aboutProfiles.css" type="text/css" />
+ <script type="application/javascript;version=1.7" src="chrome://global/content/aboutProfiles.js" />
+ </head>
+ <body id="body">
+ <div id="action-box">
+ <h3>&aboutProfiles.restart.title;</h3>
+ <button id="restart-in-safe-mode-button">&aboutProfiles.restart.inSafeMode;</button>
+ <button id="restart-button">&aboutProfiles.restart.normal;</button>
+ </div>
+
+ <h1>&aboutProfiles.title;</h1>
+ <div class="page-subtitle">&aboutProfiles.subtitle;</div>
+
+ <div>
+ <button id="create-button">&aboutProfiles.create;</button>
+ </div>
+
+ <div id="profiles" class="tab"></div>
+ </body>
+</html>
diff --git a/components/global/content/aboutRights-unbranded.xhtml b/components/global/content/aboutRights-unbranded.xhtml
new file mode 100644
index 000000000..dfa12532d
--- /dev/null
+++ b/components/global/content/aboutRights-unbranded.xhtml
@@ -0,0 +1,59 @@
+<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % aboutRightsDTD SYSTEM "chrome://global/locale/aboutRights.dtd">
+ %aboutRightsDTD;
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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>&rights.pagetitle;</title>
+ <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
+</head>
+
+<body id="your-rights" dir="&rights.locale-direction;" class="aboutPageWideContainer">
+
+<h1>&rights.intro-header;</h1>
+
+<p>&rights.intro;</p>
+
+<ul>
+ <li>&rights.intro-point1a;<a href="http://www.mozilla.org/MPL/">&rights.intro-point1b;</a>&rights.intro-point1c;</li>
+<!-- Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded.
+ - Point 3 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace)
+ - Point 4 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) -->
+ <li>&rights.intro-point3-unbranded;</li>
+ <li>&rights.intro-point4a-unbranded;<a href="about:rights#webservices" onclick="showServices();">&rights.intro-point4b-unbranded;</a>&rights.intro-point4c-unbranded;</li>
+</ul>
+
+<div id="webservices-container">
+ <a name="webservices"/>
+ <h3>&rights2.webservices-header;</h3>
+
+ <p>&rights.webservices-unbranded;</p>
+
+ <ol>
+<!-- Terms only apply to official builds, unbranded builds get a placeholder. -->
+ <li>&rights.webservices-term1-unbranded;</li>
+ </ol>
+</div>
+
+<script type="application/javascript"><![CDATA[
+ var servicesDiv = document.getElementById("webservices-container");
+ servicesDiv.style.display = "none";
+
+ function showServices() {
+ servicesDiv.style.display = "";
+ }
+]]></script>
+
+</body>
+</html>
diff --git a/components/global/content/aboutRights.xhtml b/components/global/content/aboutRights.xhtml
new file mode 100644
index 000000000..e7f50f2e6
--- /dev/null
+++ b/components/global/content/aboutRights.xhtml
@@ -0,0 +1,88 @@
+<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % securityPrefsDTD SYSTEM "chrome://browser/locale/preferences/security.dtd">
+ %securityPrefsDTD;
+ <!ENTITY % aboutRightsDTD SYSTEM "chrome://global/locale/aboutRights.dtd">
+ %aboutRightsDTD;
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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>&rights.pagetitle;</title>
+ <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
+</head>
+
+<body id="your-rights" dir="&rights.locale-direction;" class="aboutPageWideContainer">
+
+<h1>&rights.intro-header;</h1>
+
+<p>&rights.intro;</p>
+
+<ul>
+ <li>&rights.intro-point1a;<a href="http://www.mozilla.org/MPL/">&rights.intro-point1b;</a>&rights.intro-point1c;</li>
+<!-- Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded.
+ - Point 3 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace)
+ - Point 4 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) -->
+ <li>&rights.intro-point2-a;<a href="http://www.palemoon.org/branding.shtml">&rights.intro-point2-b;</a>&rights.intro-point2-c;</li>
+ <li>&rights.intro-point2.5;</li>
+ <li>&rights2.intro-point3a;<a href="http://www.palemoon.org/privacy.shtml">&rights2.intro-point3b;</a>&rights.intro-point3c;</li>
+ <li>&rights2.intro-point4a;<a href="about:rights#webservices" onclick="showServices();">&rights.intro-point4b;</a>&rights.intro-point4c;</li>
+ <li>&rights.intro-point5;</li>
+</ul>
+
+<div id="webservices-container">
+ <a name="webservices"/>
+ <h3>&rights2.webservices-header;</h3>
+
+ <p>&rights2.webservices-a;<a href="about:rights#disabling-webservices" onclick="showDisablingServices();">&rights2.webservices-b;</a>&rights3.webservices-c;</p>
+
+ <div id="disabling-webservices-container" style="margin-left:40px;">
+ <a name="disabling-webservices"/>
+ <p><strong>&rights.locationawarebrowsing-a;</strong>&rights.locationawarebrowsing-b;</p>
+ <ul>
+ <li>&rights.locationawarebrowsing-term1a;<code>&rights.locationawarebrowsing-term1b;</code></li>
+ <li>&rights.locationawarebrowsing-term2;</li>
+ <li>&rights.locationawarebrowsing-term3;</li>
+ <li>&rights.locationawarebrowsing-term4;</li>
+ </ul>
+ </div>
+
+ <ol>
+<!-- Terms only apply to official builds, unbranded builds get a placeholder. -->
+ <li>&rights2.webservices-term1;</li>
+ <li>&rights.webservices-term2;</li>
+ <li>&rights2.webservices-term3;</li>
+ <li><strong>&rights.webservices-term4;</strong></li>
+ <li><strong>&rights.webservices-term5;</strong></li>
+ <li>&rights.webservices-term6;</li>
+ <li>&rights.webservices-term7;</li>
+ </ol>
+</div>
+
+<script type="application/javascript"><![CDATA[
+ var servicesDiv = document.getElementById("webservices-container");
+ servicesDiv.style.display = "none";
+
+ function showServices() {
+ servicesDiv.style.display = "";
+ }
+
+ var disablingServicesDiv = document.getElementById("disabling-webservices-container");
+ disablingServicesDiv.style.display = "none";
+
+ function showDisablingServices() {
+ disablingServicesDiv.style.display = "";
+ }
+]]></script>
+
+</body>
+</html>
diff --git a/components/global/content/aboutServiceWorkers.js b/components/global/content/aboutServiceWorkers.js
new file mode 100644
index 000000000..1f0b67c17
--- /dev/null
+++ b/components/global/content/aboutServiceWorkers.js
@@ -0,0 +1,184 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+const bundle = Services.strings.createBundle(
+ "chrome://global/locale/aboutServiceWorkers.properties");
+
+const brandBundle = Services.strings.createBundle(
+ "chrome://branding/locale/brand.properties");
+
+var gSWM;
+var gSWCount = 0;
+
+function init() {
+ let enabled = Services.prefs.getBoolPref("dom.serviceWorkers.enabled");
+ if (!enabled) {
+ let div = document.getElementById("warning_not_enabled");
+ div.classList.add("active");
+ return;
+ }
+
+ gSWM = Cc["@mozilla.org/serviceworkers/manager;1"]
+ .getService(Ci.nsIServiceWorkerManager);
+ if (!gSWM) {
+ dump("AboutServiceWorkers: Failed to get the ServiceWorkerManager service!\n");
+ return;
+ }
+
+ let data = gSWM.getAllRegistrations();
+ if (!data) {
+ dump("AboutServiceWorkers: Failed to retrieve the registrations.\n");
+ return;
+ }
+
+ let length = data.length;
+ if (!length) {
+ let div = document.getElementById("warning_no_serviceworkers");
+ div.classList.add("active");
+ return;
+ }
+
+ let ps = undefined;
+ try {
+ ps = Cc["@mozilla.org/push/Service;1"]
+ .getService(Ci.nsIPushService);
+ } catch (e) {
+ dump("Could not acquire PushService\n");
+ }
+
+ for (let i = 0; i < length; ++i) {
+ let info = data.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
+ if (!info) {
+ dump("AboutServiceWorkers: Invalid nsIServiceWorkerRegistrationInfo interface.\n");
+ continue;
+ }
+
+ display(info, ps);
+ }
+}
+
+function display(info, pushService) {
+ let parent = document.getElementById("serviceworkers");
+
+ let div = document.createElement('div');
+ parent.appendChild(div);
+
+ let title = document.createElement('h2');
+ let titleStr = bundle.formatStringFromName('title', [info.principal.origin], 1);
+ title.appendChild(document.createTextNode(titleStr));
+ div.appendChild(title);
+
+ if (info.principal.appId) {
+ let b2gtitle = document.createElement('h3');
+ let trueFalse = bundle.GetStringFromName(info.principal.isInIsolatedMozBrowserElement ? 'true' : 'false');
+
+ let b2gtitleStr =
+ bundle.formatStringFromName('b2gtitle', [ brandBundle.getString("brandShortName"),
+ info.principal.appId,
+ trueFalse], 2);
+ b2gtitle.appendChild(document.createTextNode(b2gtitleStr));
+ div.appendChild(b2gtitle);
+ }
+
+ let list = document.createElement('ul');
+ div.appendChild(list);
+
+ function createItem(title, value, makeLink) {
+ let item = document.createElement('li');
+ list.appendChild(item);
+
+ let bold = document.createElement('strong');
+ bold.appendChild(document.createTextNode(title + " "));
+ item.appendChild(bold);
+
+ let textNode = document.createTextNode(value);
+
+ if (makeLink) {
+ let link = document.createElement("a");
+ link.href = value;
+ link.target = "_blank";
+ link.appendChild(textNode);
+ item.appendChild(link);
+ } else {
+ item.appendChild(textNode);
+ }
+
+ return textNode;
+ }
+
+ createItem(bundle.GetStringFromName('scope'), info.scope);
+ createItem(bundle.GetStringFromName('scriptSpec'), info.scriptSpec, true);
+ let currentWorkerURL = info.activeWorker ? info.activeWorker.scriptSpec : "";
+ createItem(bundle.GetStringFromName('currentWorkerURL'), currentWorkerURL, true);
+ let activeCacheName = info.activeWorker ? info.activeWorker.cacheName : "";
+ createItem(bundle.GetStringFromName('activeCacheName'), activeCacheName);
+ let waitingCacheName = info.waitingWorker ? info.waitingWorker.cacheName : "";
+ createItem(bundle.GetStringFromName('waitingCacheName'), waitingCacheName);
+
+ let pushItem = createItem(bundle.GetStringFromName('pushEndpoint'), bundle.GetStringFromName('waiting'));
+ if (pushService) {
+ pushService.getSubscription(info.scope, info.principal, (status, pushRecord) => {
+ if (Components.isSuccessCode(status)) {
+ pushItem.data = JSON.stringify(pushRecord);
+ } else {
+ dump("about:serviceworkers - retrieving push registration failed\n");
+ }
+ });
+ }
+
+ let updateButton = document.createElement("button");
+ updateButton.appendChild(document.createTextNode(bundle.GetStringFromName('update')));
+ updateButton.onclick = function() {
+ gSWM.propagateSoftUpdate(info.principal.originAttributes, info.scope);
+ };
+ div.appendChild(updateButton);
+
+ let unregisterButton = document.createElement("button");
+ unregisterButton.appendChild(document.createTextNode(bundle.GetStringFromName('unregister')));
+ div.appendChild(unregisterButton);
+
+ let loadingMessage = document.createElement('span');
+ loadingMessage.appendChild(document.createTextNode(bundle.GetStringFromName('waiting')));
+ loadingMessage.classList.add('inactive');
+ div.appendChild(loadingMessage);
+
+ unregisterButton.onclick = function() {
+ let cb = {
+ unregisterSucceeded: function() {
+ parent.removeChild(div);
+
+ if (!--gSWCount) {
+ let div = document.getElementById("warning_no_serviceworkers");
+ div.classList.add("active");
+ }
+ },
+
+ unregisterFailed: function() {
+ alert(bundle.GetStringFromName('unregisterError'));
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIServiceWorkerUnregisterCallback])
+ };
+
+ loadingMessage.classList.remove('inactive');
+ gSWM.propagateUnregister(info.principal, cb, info.scope);
+ };
+
+ let sep = document.createElement('hr');
+ div.appendChild(sep);
+
+ ++gSWCount;
+}
+
+window.addEventListener("DOMContentLoaded", function load() {
+ window.removeEventListener("DOMContentLoaded", load);
+ init();
+});
diff --git a/components/global/content/aboutServiceWorkers.xhtml b/components/global/content/aboutServiceWorkers.xhtml
new file mode 100644
index 000000000..8b7c86ba1
--- /dev/null
+++ b/components/global/content/aboutServiceWorkers.xhtml
@@ -0,0 +1,34 @@
+<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
+<!ENTITY % serviceworkersDTD SYSTEM "chrome://global/locale/aboutServiceWorkers.dtd"> %serviceworkersDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&aboutServiceWorkers.title;</title>
+ <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css" />
+ <link rel="stylesheet" href="chrome://mozapps/skin/aboutServiceWorkers.css" type="text/css" />
+ <script type="application/javascript;version=1.7" src="chrome://global/content/aboutServiceWorkers.js" />
+ </head>
+ <body id="body">
+ <div id="warning_not_enabled" class="warningBackground">
+ <div class="warningMessage">&aboutServiceWorkers.warning_not_enabled;</div>
+ </div>
+
+ <div id="warning_no_serviceworkers" class="warningBackground">
+ <div class="warningMessage">&aboutServiceWorkers.warning_no_serviceworkers;</div>
+ </div>
+
+ <div id="serviceworkers" class="tab active">
+ <h1>&aboutServiceWorkers.maintitle;</h1>
+ </div>
+ </body>
+</html>
diff --git a/components/global/content/aboutSupport.js b/components/global/content/aboutSupport.js
new file mode 100644
index 000000000..86f1fa35b
--- /dev/null
+++ b/components/global/content/aboutSupport.js
@@ -0,0 +1,997 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Troubleshoot.jsm");
+Cu.import("resource://gre/modules/ResetProfile.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils",
+ "resource://gre/modules/PlacesDBUtils.jsm");
+
+window.addEventListener("load", function onload(event) {
+ try {
+ window.removeEventListener("load", onload, false);
+ Troubleshoot.snapshot(function (snapshot) {
+ for (let prop in snapshotFormatters)
+ snapshotFormatters[prop](snapshot[prop]);
+ });
+ populateActionBox();
+ setupEventListeners();
+ } catch (e) {
+ Cu.reportError("stack of load error for about:support: " + e + ": " + e.stack);
+ }
+}, false);
+
+// Each property in this object corresponds to a property in Troubleshoot.jsm's
+// snapshot data. Each function is passed its property's corresponding data,
+// and it's the function's job to update the page with it.
+var snapshotFormatters = {
+
+ application: function application(data) {
+ $("application-box").textContent = data.name;
+ $("useragent-box").textContent = data.userAgent;
+ $("os-box").textContent = data.osVersion;
+ $("binary-box").textContent = Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
+ $("supportLink").href = data.supportURL;
+ let version = Services.appinfo.version;
+ if (data.versionArch) {
+ version += " (" + data.versionArch + ")";
+ }
+ if (data.vendor)
+ version += " (" + data.vendor + ")";
+ $("version-box").textContent = version;
+ $("buildid-box").textContent = data.buildID;
+ if (data.updateChannel)
+ $("updatechannel-box").textContent = data.updateChannel;
+
+ $("safemode-box").textContent = data.safeMode;
+ },
+
+ crashes: function crashes(data) {
+ return;
+ },
+
+ extensions: function extensions(data) {
+ $.append($("extensions-tbody"), data.map(function (extension) {
+ return $.new("tr", [
+ $.new("td", extension.name),
+ $.new("td", extension.version),
+ $.new("td", extension.isActive),
+ $.new("td", extension.id),
+ ]);
+ }));
+ },
+
+ modifiedPreferences: function modifiedPreferences(data) {
+ $.append($("prefs-tbody"), sortedArrayFromObject(data).map(
+ function ([name, value]) {
+ return $.new("tr", [
+ $.new("td", name, "pref-name"),
+ // Very long preference values can cause users problems when they
+ // copy and paste them into some text editors. Long values generally
+ // aren't useful anyway, so truncate them to a reasonable length.
+ $.new("td", String(value).substr(0, 120), "pref-value"),
+ ]);
+ }
+ ));
+ },
+
+ lockedPreferences: function lockedPreferences(data) {
+ $.append($("locked-prefs-tbody"), sortedArrayFromObject(data).map(
+ function ([name, value]) {
+ return $.new("tr", [
+ $.new("td", name, "pref-name"),
+ $.new("td", String(value).substr(0, 120), "pref-value"),
+ ]);
+ }
+ ));
+ },
+
+ graphics: function graphics(data) {
+ let strings = stringBundle();
+
+ function localizedMsg(msgArray) {
+ let nameOrMsg = msgArray.shift();
+ if (msgArray.length) {
+ // formatStringFromName logs an NS_ASSERTION failure otherwise that says
+ // "use GetStringFromName". Lame.
+ try {
+ return strings.formatStringFromName(nameOrMsg, msgArray,
+ msgArray.length);
+ }
+ catch (err) {
+ // Throws if nameOrMsg is not a name in the bundle. This shouldn't
+ // actually happen though, since msgArray.length > 1 => nameOrMsg is a
+ // name in the bundle, not a message, and the remaining msgArray
+ // elements are parameters.
+ return nameOrMsg;
+ }
+ }
+ try {
+ return strings.GetStringFromName(nameOrMsg);
+ }
+ catch (err) {
+ // Throws if nameOrMsg is not a name in the bundle.
+ }
+ return nameOrMsg;
+ }
+
+ // Read APZ info out of data.info, stripping it out in the process.
+ let apzInfo = [];
+ let formatApzInfo = function (info) {
+ let out = [];
+ for (let type of ['Wheel', 'Touch', 'Drag']) {
+ let key = 'Apz' + type + 'Input';
+
+ if (!(key in info))
+ continue;
+
+ delete info[key];
+
+ let message = localizedMsg([type.toLowerCase() + 'Enabled']);
+ out.push(message);
+ }
+
+ return out;
+ };
+
+ // Create a <tr> element with key and value columns.
+ //
+ // @key Text in the key column. Localized automatically, unless starts with "#".
+ // @value Text in the value column. Not localized.
+ function buildRow(key, value) {
+ let title;
+ if (key[0] == "#") {
+ title = key.substr(1);
+ } else {
+ try {
+ title = strings.GetStringFromName(key);
+ } catch (e) {
+ title = key;
+ }
+ }
+ let td = $.new("td", value);
+ td.style["white-space"] = "pre-wrap";
+
+ return $.new("tr", [
+ $.new("th", title, "column"),
+ td,
+ ]);
+ }
+
+ // @where The name in "graphics-<name>-tbody", of the element to append to.
+ // @trs Array of row elements.
+ function addRows(where, trs) {
+ $.append($("graphics-" + where + "-tbody"), trs);
+ }
+
+ // Build and append a row.
+ //
+ // @where The name in "graphics-<name>-tbody", of the element to append to.
+ function addRow(where, key, value) {
+ addRows(where, [buildRow(key, value)]);
+ }
+ if (data.clearTypeParameters !== undefined) {
+ addRow("diagnostics", "clearTypeParameters", data.clearTypeParameters);
+ }
+ if ("info" in data) {
+ apzInfo = formatApzInfo(data.info);
+
+ let trs = sortedArrayFromObject(data.info).map(function ([prop, val]) {
+ return $.new("tr", [
+ $.new("th", prop, "column"),
+ $.new("td", String(val)),
+ ]);
+ });
+ addRows("diagnostics", trs);
+
+ delete data.info;
+ }
+
+#ifdef NIGHTLY_BUILD
+ let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let gpuProcessPid = windowUtils.gpuProcessPid;
+
+ if (gpuProcessPid != -1) {
+ let gpuProcessKillButton = $.new("button");
+
+ gpuProcessKillButton.addEventListener("click", function() {
+ windowUtils.terminateGPUProcess();
+ });
+
+ gpuProcessKillButton.textContent = strings.GetStringFromName("gpuProcessKillButton");
+ addRow("diagnostics", "GPUProcessPid", gpuProcessPid);
+ addRow("diagnostics", "GPUProcess", [gpuProcessKillButton]);
+ }
+#endif
+
+ // graphics-failures-tbody tbody
+ if ("failures" in data) {
+ // If indices is there, it should be the same length as failures,
+ // (see Troubleshoot.jsm) but we check anyway:
+ if ("indices" in data && data.failures.length == data.indices.length) {
+ let combined = [];
+ for (let i = 0; i < data.failures.length; i++) {
+ let assembled = assembleFromGraphicsFailure(i, data);
+ combined.push(assembled);
+ }
+ combined.sort(function(a, b) {
+ if (a.index < b.index) return -1;
+ if (a.index > b.index) return 1;
+ return 0;
+ });
+ $.append($("graphics-failures-tbody"),
+ combined.map(function(val) {
+ return $.new("tr", [$.new("th", val.header, "column"),
+ $.new("td", val.message)]);
+ }));
+ delete data.indices;
+ } else {
+ $.append($("graphics-failures-tbody"),
+ [$.new("tr", [$.new("th", "LogFailure", "column"),
+ $.new("td", data.failures.map(function (val) {
+ return $.new("p", val);
+ }))])]);
+ }
+ } else {
+ $("graphics-failures-tbody").style.display = "none";
+ }
+
+ // Add a new row to the table, and take the key (or keys) out of data.
+ //
+ // @where Table section to add to.
+ // @key Data key to use.
+ // @colKey The localization key to use, if different from key.
+ function addRowFromKey(where, key, colKey) {
+ if (!(key in data))
+ return;
+ colKey = colKey || key;
+
+ let value;
+ let messageKey = key + "Message";
+ if (messageKey in data) {
+ value = localizedMsg(data[messageKey]);
+ delete data[messageKey];
+ } else {
+ value = data[key];
+ }
+ delete data[key];
+
+ if (value) {
+ addRow(where, colKey, value);
+ }
+ }
+
+ // graphics-features-tbody
+
+ let compositor = data.windowLayerManagerRemote
+ ? data.windowLayerManagerType
+ : "BasicLayers (" + strings.GetStringFromName("mainThreadNoOMTC") + ")";
+ addRow("features", "compositing", compositor);
+
+ let acceleratedWindows = data.numAcceleratedWindows + "/" + data.numTotalWindows;
+ if (data.windowLayerManagerType) {
+ acceleratedWindows += " " + data.windowLayerManagerType;
+ }
+ if (data.windowLayerManagerRemote) {
+ acceleratedWindows += " (OMTC)";
+ }
+ if (data.numAcceleratedWindowsMessage) {
+ acceleratedWindows += " " + localizedMsg(data.numAcceleratedWindowsMessage);
+ }
+ addRow("features", "acceleratedWindows", acceleratedWindows);
+ delete data.windowLayerManagerRemote;
+ delete data.windowLayerManagerType;
+ delete data.numTotalWindows;
+ delete data.numAcceleratedWindows;
+ delete data.numAcceleratedWindowsMessage;
+
+ addRow("features", "asyncPanZoom",
+ apzInfo.length
+ ? apzInfo.join("; ")
+ : localizedMsg(["apzNone"]));
+ addRowFromKey("features", "webgl1WSIInfo");
+ addRowFromKey("features", "webgl1Renderer");
+ addRowFromKey("features", "webgl1Version");
+ addRowFromKey("features", "webgl1DriverExtensions");
+ addRowFromKey("features", "webgl1Extensions");
+ addRowFromKey("features", "webgl2WSIInfo");
+ addRowFromKey("features", "webgl2Renderer");
+ addRowFromKey("features", "webgl2Version");
+ addRowFromKey("features", "webgl2DriverExtensions");
+ addRowFromKey("features", "webgl2Extensions");
+ addRowFromKey("features", "supportsHardwareH264", "hardwareH264");
+ addRowFromKey("features", "direct2DEnabled", "#Direct2D");
+
+ if ("directWriteEnabled" in data) {
+ let message = data.directWriteEnabled;
+ if ("directWriteVersion" in data)
+ message += " (" + data.directWriteVersion + ")";
+ addRow("features", "#DirectWrite", message);
+ delete data.directWriteEnabled;
+ delete data.directWriteVersion;
+ }
+
+ // Adapter tbodies.
+ let adapterKeys = [
+ ["adapterDescription", "gpuDescription"],
+ ["adapterVendorID", "gpuVendorID"],
+ ["adapterDeviceID", "gpuDeviceID"],
+ ["driverVersion", "gpuDriverVersion"],
+ ["driverDate", "gpuDriverDate"],
+ ["adapterDrivers", "gpuDrivers"],
+ ["adapterSubsysID", "gpuSubsysID"],
+ ["adapterRAM", "gpuRAM"],
+ ];
+
+ function showGpu(id, suffix) {
+ function get(prop) {
+ return data[prop + suffix];
+ }
+
+ let trs = [];
+ for (let [prop, key] of adapterKeys) {
+ let value = get(prop);
+ if (value === undefined || value === "")
+ continue;
+ trs.push(buildRow(key, value));
+ }
+
+ if (trs.length == 0) {
+ $("graphics-" + id + "-tbody").style.display = "none";
+ return;
+ }
+
+ let active = "yes";
+ if ("isGPU2Active" in data && ((suffix == "2") != data.isGPU2Active)) {
+ active = "no";
+ }
+ addRow(id, "gpuActive", strings.GetStringFromName(active));
+ addRows(id, trs);
+ }
+ showGpu("gpu-1", "");
+ showGpu("gpu-2", "2");
+
+ // Remove adapter keys.
+ for (let [prop, key] of adapterKeys) {
+ delete data[prop];
+ delete data[prop + "2"];
+ }
+ delete data.isGPU2Active;
+
+ let featureLog = data.featureLog;
+ delete data.featureLog;
+
+ let features = [];
+ for (let feature of featureLog.features) {
+ // Only add interesting decisions - ones that were not automatic based on
+ // all.js/gfxPrefs defaults.
+ if (feature.log.length > 1 || feature.log[0].status != "available") {
+ features.push(feature);
+ }
+ }
+
+ if (features.length) {
+ for (let feature of features) {
+ let trs = [];
+ for (let entry of feature.log) {
+ if (entry.type == "default" && entry.status == "available")
+ continue;
+
+ let contents;
+ if (entry.message.length > 0 && entry.message[0] == "#") {
+ // This is a failure ID. See nsIGfxInfo.idl.
+ let m;
+ if (m = /#BLOCKLIST_FEATURE_FAILURE_BUG_(\d+)/.exec(entry.message)) {
+ let bugSpan = $.new("span");
+ bugSpan.textContent = strings.GetStringFromName("blocklistedBug") + "; ";
+
+ let bugHref = $.new("a");
+ bugHref.href = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + m[1];
+ bugHref.textContent = strings.formatStringFromName("bugLink", [m[1]], 1);
+
+ contents = [bugSpan, bugHref];
+ } else {
+ contents = strings.formatStringFromName(
+ "unknownFailure", [entry.message.substr(1)], 1);
+ }
+ } else {
+ contents = entry.status + " by " + entry.type + ": " + entry.message;
+ }
+
+ trs.push($.new("tr", [
+ $.new("td", contents),
+ ]));
+ }
+ addRow("decisions", feature.name, [$.new("table", trs)]);
+ }
+ } else {
+ $("graphics-decisions-tbody").style.display = "none";
+ }
+
+ if (featureLog.fallbacks.length) {
+ for (let fallback of featureLog.fallbacks) {
+ addRow("workarounds", fallback.name, fallback.message);
+ }
+ } else {
+ $("graphics-workarounds-tbody").style.display = "none";
+ }
+
+ let crashGuards = data.crashGuards;
+ delete data.crashGuards;
+
+ if (crashGuards.length) {
+ for (let guard of crashGuards) {
+ let resetButton = $.new("button");
+ let onClickReset = (function (guard) {
+ // Note - need this wrapper until bug 449811 fixes |guard| scoping.
+ return function () {
+ Services.prefs.setIntPref(guard.prefName, 0);
+ resetButton.removeEventListener("click", onClickReset);
+ resetButton.disabled = true;
+ };
+ })(guard);
+
+ resetButton.textContent = strings.GetStringFromName("resetOnNextRestart");
+ resetButton.addEventListener("click", onClickReset);
+
+ addRow("crashguards", guard.type + "CrashGuard", [resetButton]);
+ }
+ } else {
+ $("graphics-crashguards-tbody").style.display = "none";
+ }
+
+ // Now that we're done, grab any remaining keys in data and drop them into
+ // the diagnostics section.
+ for (let key in data) {
+ let value = data[key];
+ if (Array.isArray(value)) {
+ value = localizedMsg(value);
+ }
+ addRow("diagnostics", key, value);
+ }
+ },
+
+ media: function media(data) {
+ let strings = stringBundle();
+
+ function insertBasicInfo(key, value) {
+ function createRow(key, value) {
+ let th = $.new("th", strings.GetStringFromName(key), "column");
+ let td = $.new("td", value);
+ td.style["white-space"] = "pre-wrap";
+ return $.new("tr", [th, td]);
+ }
+ $.append($("media-info-tbody"), [createRow(key, value)]);
+ }
+
+ function createDeviceInfoRow(device) {
+ let deviceInfo = Ci.nsIAudioDeviceInfo;
+
+ let states = {};
+ states[deviceInfo.STATE_DISABLED] = "Disabled";
+ states[deviceInfo.STATE_UNPLUGGED] = "Unplugged";
+ states[deviceInfo.STATE_ENABLED] = "Enabled";
+
+ let preferreds = {};
+ preferreds[deviceInfo.PREF_NONE] = "None";
+ preferreds[deviceInfo.PREF_MULTIMEDIA] = "Multimedia";
+ preferreds[deviceInfo.PREF_VOICE] = "Voice";
+ preferreds[deviceInfo.PREF_NOTIFICATION] = "Notification";
+ preferreds[deviceInfo.PREF_ALL] = "All";
+
+ let formats = {};
+ formats[deviceInfo.FMT_S16LE] = "S16LE";
+ formats[deviceInfo.FMT_S16BE] = "S16BE";
+ formats[deviceInfo.FMT_F32LE] = "F32LE";
+ formats[deviceInfo.FMT_F32BE] = "F32BE";
+
+ function toPreferredString(preferred) {
+ if (preferred == deviceInfo.PREF_NONE) {
+ return preferreds[deviceInfo.PREF_NONE];
+ } else if (preferred & deviceInfo.PREF_ALL) {
+ return preferreds[deviceInfo.PREF_ALL];
+ }
+ let str = "";
+ for (let pref of [deviceInfo.PREF_MULTIMEDIA,
+ deviceInfo.PREF_VOICE,
+ deviceInfo.PREF_NOTIFICATION]) {
+ if (preferred & pref) {
+ str += " " + preferreds[pref];
+ }
+ }
+ return str;
+ }
+
+ function toFromatString(dev) {
+ let str = "default: " + formats[dev.defaultFormat] + ", support:";
+ for (let fmt of [deviceInfo.FMT_S16LE,
+ deviceInfo.FMT_S16BE,
+ deviceInfo.FMT_F32LE,
+ deviceInfo.FMT_F32BE]) {
+ if (dev.supportedFormat & fmt) {
+ str += " " + formats[fmt];
+ }
+ }
+ return str;
+ }
+
+ function toRateString(dev) {
+ return "default: " + dev.defaultRate +
+ ", support: " + dev.minRate + " - " + dev.maxRate;
+ }
+
+ function toLatencyString(dev) {
+ return dev.minLatency + " - " + dev.maxLatency;
+ }
+
+ return $.new("tr", [$.new("td", device.name),
+ $.new("td", device.groupId),
+ $.new("td", device.vendor),
+ $.new("td", states[device.state]),
+ $.new("td", toPreferredString(device.preferred)),
+ $.new("td", toFromatString(device)),
+ $.new("td", device.maxChannels),
+ $.new("td", toRateString(device)),
+ $.new("td", toLatencyString(device))]);
+ }
+
+ function insertDeviceInfo(side, devices) {
+ let rows = [];
+ for (let dev of devices) {
+ rows.push(createDeviceInfoRow(dev));
+ }
+ $.append($("media-" + side + "-devices-tbody"), rows);
+ }
+
+ // Basic information
+ insertBasicInfo("audioBackend", data.currentAudioBackend);
+ insertBasicInfo("maxAudioChannels", data.currentMaxAudioChannels);
+ insertBasicInfo("sampleRate", data.currentPreferredSampleRate);
+
+ // Output devices information
+ insertDeviceInfo("output", data.audioOutputDevices);
+
+ // Input devices information
+ insertDeviceInfo("input", data.audioInputDevices);
+ },
+
+
+ javaScript: function javaScript(data) {
+ $("javascript-incremental-gc").textContent = data.incrementalGCEnabled;
+ },
+
+ accessibility: function accessibility(data) {
+ $("a11y-activated").textContent = data.isActive;
+ $("a11y-force-disabled").textContent = data.forceDisabled || 0;
+ },
+
+ libraryVersions: function libraryVersions(data) {
+ let strings = stringBundle();
+ let trs = [
+ $.new("tr", [
+ $.new("th", ""),
+ $.new("th", strings.GetStringFromName("minLibVersions")),
+ $.new("th", strings.GetStringFromName("loadedLibVersions")),
+ ])
+ ];
+ sortedArrayFromObject(data).forEach(
+ function ([name, val]) {
+ trs.push($.new("tr", [
+ $.new("td", name),
+ $.new("td", val.minVersion),
+ $.new("td", val.version),
+ ]));
+ }
+ );
+ $.append($("libversions-tbody"), trs);
+ },
+
+ userJS: function userJS(data) {
+ if (!data.exists)
+ return;
+ let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
+ userJSFile.append("user.js");
+ $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec;
+ $("prefs-user-js-section").style.display = "";
+ // Clear the no-copy class
+ $("prefs-user-js-section").className = "";
+ }
+};
+
+var $ = document.getElementById.bind(document);
+
+$.new = function $_new(tag, textContentOrChildren, className, attributes) {
+ let elt = document.createElement(tag);
+ if (className)
+ elt.className = className;
+ if (attributes) {
+ for (let attrName in attributes)
+ elt.setAttribute(attrName, attributes[attrName]);
+ }
+ if (Array.isArray(textContentOrChildren))
+ this.append(elt, textContentOrChildren);
+ else
+ elt.textContent = String(textContentOrChildren);
+ return elt;
+};
+
+$.append = function $_append(parent, children) {
+ children.forEach(c => parent.appendChild(c));
+};
+
+function stringBundle() {
+ return Services.strings.createBundle(
+ "chrome://global/locale/aboutSupport.properties");
+}
+
+function assembleFromGraphicsFailure(i, data)
+{
+ // Only cover the cases we have today; for example, we do not have
+ // log failures that assert and we assume the log level is 1/error.
+ let message = data.failures[i];
+ let index = data.indices[i];
+ let what = "";
+ if (message.search(/\[GFX1-\]: \(LF\)/) == 0) {
+ // Non-asserting log failure - the message is substring(14)
+ what = "LogFailure";
+ message = message.substring(14);
+ } else if (message.search(/\[GFX1-\]: /) == 0) {
+ // Non-asserting - the message is substring(9)
+ what = "Error";
+ message = message.substring(9);
+ } else if (message.search(/\[GFX1\]: /) == 0) {
+ // Asserting - the message is substring(8)
+ what = "Assert";
+ message = message.substring(8);
+ }
+ let assembled = {"index" : index,
+ "header" : ("(#" + index + ") " + what),
+ "message" : message};
+ return assembled;
+}
+
+function sortedArrayFromObject(obj) {
+ let tuples = [];
+ for (let prop in obj)
+ tuples.push([prop, obj[prop]]);
+ tuples.sort(([prop1, v1], [prop2, v2]) => prop1.localeCompare(prop2));
+ return tuples;
+}
+
+function getLoadContext() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function copyContentsToClipboard() {
+ // Get the HTML and text representations for the important part of the page.
+ let contentsDiv = $("contents");
+ let dataHtml = contentsDiv.innerHTML;
+ let dataText = createTextForElement(contentsDiv);
+
+ // We can't use plain strings, we have to use nsSupportsString.
+ let supportsStringClass = Cc["@mozilla.org/supports-string;1"];
+ let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString);
+ let ssText = supportsStringClass.createInstance(Ci.nsISupportsString);
+
+ let transferable = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ transferable.init(getLoadContext());
+
+ // Add the HTML flavor.
+ transferable.addDataFlavor("text/html");
+ ssHtml.data = dataHtml;
+ transferable.setTransferData("text/html", ssHtml, dataHtml.length * 2);
+
+ // Add the plain text flavor.
+ transferable.addDataFlavor("text/unicode");
+ ssText.data = dataText;
+ transferable.setTransferData("text/unicode", ssText, dataText.length * 2);
+
+ // Store the data into the clipboard.
+ let clipboard = Cc["@mozilla.org/widget/clipboard;1"]
+ .getService(Ci.nsIClipboard);
+ clipboard.setData(transferable, null, clipboard.kGlobalClipboard);
+}
+
+// Return the plain text representation of an element. Do a little bit
+// of pretty-printing to make it human-readable.
+function createTextForElement(elem) {
+ let serializer = new Serializer();
+ let text = serializer.serialize(elem);
+
+#ifdef XP_WIN
+ // Actual CR/LF pairs are needed for some Windows text editors.
+ text = text.replace(/\n/g, "\r\n");
+#endif
+
+ return text;
+}
+
+function Serializer() {
+}
+
+Serializer.prototype = {
+
+ serialize: function (rootElem) {
+ this._lines = [];
+ this._startNewLine();
+ this._serializeElement(rootElem);
+ this._startNewLine();
+ return this._lines.join("\n").trim() + "\n";
+ },
+
+ // The current line is always the line that writing will start at next. When
+ // an element is serialized, the current line is updated to be the line at
+ // which the next element should be written.
+ get _currentLine() {
+ return this._lines.length ? this._lines[this._lines.length - 1] : null;
+ },
+
+ set _currentLine(val) {
+ return this._lines[this._lines.length - 1] = val;
+ },
+
+ _serializeElement: function (elem) {
+ if (this._ignoreElement(elem))
+ return;
+
+ // table
+ if (elem.localName == "table") {
+ this._serializeTable(elem);
+ return;
+ }
+
+ // all other elements
+
+ let hasText = false;
+ for (let child of elem.childNodes) {
+ if (child.nodeType == Node.TEXT_NODE) {
+ let text = this._nodeText(
+ child, (child.classList && child.classList.contains("endline")));
+ this._appendText(text);
+ hasText = hasText || !!text.trim();
+ }
+ else if (child.nodeType == Node.ELEMENT_NODE)
+ this._serializeElement(child);
+ }
+
+ // For headings, draw a "line" underneath them so they stand out.
+ if (/^h[0-9]+$/.test(elem.localName)) {
+ let headerText = (this._currentLine || "").trim();
+ if (headerText) {
+ this._startNewLine();
+ this._appendText("-".repeat(headerText.length));
+ }
+ }
+
+ // Add a blank line underneath block elements but only if they contain text.
+ if (hasText) {
+ let display = window.getComputedStyle(elem).getPropertyValue("display");
+ if (display == "block") {
+ this._startNewLine();
+ this._startNewLine();
+ }
+ }
+ },
+
+ _startNewLine: function () {
+ let currLine = this._currentLine;
+ if (currLine) {
+ // The current line is not empty. Trim it.
+ this._currentLine = currLine.trim();
+ if (!this._currentLine)
+ // The current line became empty. Discard it.
+ this._lines.pop();
+ }
+ this._lines.push("");
+ },
+
+ _appendText: function (text) {
+ this._currentLine += text;
+ },
+
+ _isHiddenSubHeading: function (th) {
+ return th.parentNode.parentNode.style.display == "none";
+ },
+
+ _serializeTable: function (table) {
+ // Collect the table's column headings if in fact there are any. First
+ // check thead. If there's no thead, check the first tr.
+ let colHeadings = {};
+ let tableHeadingElem = table.querySelector("thead");
+ if (!tableHeadingElem)
+ tableHeadingElem = table.querySelector("tr");
+ if (tableHeadingElem) {
+ let tableHeadingCols = tableHeadingElem.querySelectorAll("th,td");
+ // If there's a contiguous run of th's in the children starting from the
+ // rightmost child, then consider them to be column headings.
+ for (let i = tableHeadingCols.length - 1; i >= 0; i--) {
+ let col = tableHeadingCols[i];
+ if (col.localName != "th" || col.classList.contains("title-column"))
+ break;
+ colHeadings[i] = this._nodeText(
+ col, (col.classList && col.classList.contains("endline"))).trim();
+ }
+ }
+ let hasColHeadings = Object.keys(colHeadings).length > 0;
+ if (!hasColHeadings)
+ tableHeadingElem = null;
+
+ let trs = table.querySelectorAll("table > tr, tbody > tr");
+ let startRow =
+ tableHeadingElem && tableHeadingElem.localName == "tr" ? 1 : 0;
+
+ if (startRow >= trs.length)
+ // The table's empty.
+ return;
+
+ if (hasColHeadings && !this._ignoreElement(tableHeadingElem)) {
+ // Use column headings. Print each tr as a multi-line chunk like:
+ // Heading 1: Column 1 value
+ // Heading 2: Column 2 value
+ for (let i = startRow; i < trs.length; i++) {
+ if (this._ignoreElement(trs[i]))
+ continue;
+ let children = trs[i].querySelectorAll("td");
+ for (let j = 0; j < children.length; j++) {
+ let text = "";
+ if (colHeadings[j])
+ text += colHeadings[j] + ": ";
+ text += this._nodeText(
+ children[j],
+ (children[j].classList &&
+ children[j].classList.contains("endline"))).trim();
+ this._appendText(text);
+ this._startNewLine();
+ }
+ this._startNewLine();
+ }
+ return;
+ }
+
+ // Don't use column headings. Assume the table has only two columns and
+ // print each tr in a single line like:
+ // Column 1 value: Column 2 value
+ for (let i = startRow; i < trs.length; i++) {
+ if (this._ignoreElement(trs[i]))
+ continue;
+ let children = trs[i].querySelectorAll("th,td");
+ let rowHeading = this._nodeText(
+ children[0],
+ (children[0].classList &&
+ children[0].classList.contains("endline"))).trim();
+ if (children[0].classList.contains("title-column")) {
+ if (!this._isHiddenSubHeading(children[0]))
+ this._appendText(rowHeading);
+ } else if (children.length == 1) {
+ // This is a single-cell row.
+ this._appendText(rowHeading);
+ } else {
+ let childTables = trs[i].querySelectorAll("table");
+ if (childTables.length) {
+ // If we have child tables, don't use nodeText - its trs are already
+ // queued up from querySelectorAll earlier.
+ this._appendText(rowHeading + ": ");
+ } else {
+ this._appendText(rowHeading + ": " + this._nodeText(
+ children[1],
+ (children[1].classList &&
+ children[1].classList.contains("endline"))).trim());
+ }
+ }
+ this._startNewLine();
+ }
+ this._startNewLine();
+ },
+
+ _ignoreElement: function (elem) {
+ return elem.classList.contains("no-copy");
+ },
+
+ _nodeText: function (node, endline) {
+ let whiteChars = /\s+/g
+ let whiteCharsButNoEndline = /(?!\n)[\s]+/g;
+ let _node = node.cloneNode(true);
+ if (_node.firstElementChild &&
+ (_node.firstElementChild.nodeName.toLowerCase() == "button")) {
+ _node.removeChild(_node.firstElementChild);
+ }
+ return _node.textContent.replace(
+ endline ? whiteCharsButNoEndline : whiteChars, " ");
+ },
+};
+
+function openProfileDirectory() {
+ // Get the profile directory.
+ let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let profileDir = currProfD.path;
+
+ // Show the profile directory.
+ let nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
+ "nsILocalFile", "initWithPath");
+ new nsLocalFile(profileDir).reveal();
+}
+
+/**
+ * Profile reset is only supported for the default profile if the appropriate migrator exists.
+ */
+function populateActionBox() {
+ if (ResetProfile.resetSupported()) {
+ $("reset-box").style.display = "block";
+ $("action-box").style.display = "block";
+ }
+ if (!Services.appinfo.inSafeMode) {
+ $("safe-mode-box").style.display = "block";
+ $("action-box").style.display = "block";
+ }
+}
+
+// Prompt user to restart the browser
+function restart(safeMode) {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ if (cancelQuit.data) {
+ return;
+ }
+
+ let flags = Ci.nsIAppStartup.eAttemptQuit;
+
+ if (safeMode) {
+ Services.startup.restartInSafeMode(flags);
+ } else {
+ Services.startup.quit(flags | Ci.nsIAppStartup.eRestart);
+ }
+}
+
+/**
+ * Set up event listeners for buttons.
+ */
+function setupEventListeners() {
+#ifdef MOZ_UPDATER
+ $("show-update-history-button").addEventListener("click", function(event) {
+ var prompter = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateHistory(window);
+ });
+#endif
+ $("reset-box-button").addEventListener("click", function(event) {
+ ResetProfile.openConfirmationDialog(window);
+ });
+ $("copy-to-clipboard").addEventListener("click", function(event) {
+ copyContentsToClipboard();
+ });
+ $("profile-dir-button").addEventListener("click", function(event) {
+ openProfileDirectory();
+ });
+ $("restart-in-safe-mode-button").addEventListener("click", function(event) {
+ if (Services.obs.enumerateObservers("restart-in-safe-mode").hasMoreElements()) {
+ Services.obs.notifyObservers(null, "restart-in-safe-mode", "");
+ } else {
+ restart(true);
+ }
+ });
+ $("restart-button").addEventListener("click", function(event) {
+ restart(false);
+ });
+ $("verify-place-integrity-button").addEventListener("click", function(event) {
+ PlacesDBUtils.checkAndFixDatabase(function(aLog) {
+ let msg = aLog.join("\n");
+ $("verify-place-result").style.display = "block";
+ $("verify-place-result-parent").classList.remove("no-copy");
+ $("verify-place-result").textContent = msg;
+ });
+ });
+}
diff --git a/components/global/content/aboutSupport.xhtml b/components/global/content/aboutSupport.xhtml
new file mode 100644
index 000000000..7772f6497
--- /dev/null
+++ b/components/global/content/aboutSupport.xhtml
@@ -0,0 +1,557 @@
+<?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 % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> %brandDTD;
+ <!ENTITY % aboutSupportDTD SYSTEM "chrome://global/locale/aboutSupport.dtd"> %aboutSupportDTD;
+ <!ENTITY % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd"> %resetProfileDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&aboutSupport.pageTitle;</title>
+
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet" href="chrome://global/skin/in-content/common.css"
+ type="text/css"/>
+ <link rel="stylesheet" href="chrome://global/skin/aboutSupport.css"
+ type="text/css"/>
+
+ <script type="application/javascript;version=1.7"
+ src="chrome://global/content/aboutSupport.js"/>
+ <script type="application/javascript;version=1.7"
+ src="chrome://global/content/resetProfile.js"/>
+ </head>
+
+ <body dir="&locale.dir;">
+
+ <div id="action-box">
+ <div id="reset-box">
+ <h3>&refreshProfile.title;</h3>
+ <button id="reset-box-button">
+ &refreshProfile.button.label;
+ </button>
+ </div>
+ <div id="safe-mode-box">
+ <h3>&aboutSupport.safeModeTitle;</h3>
+ <button id="restart-in-safe-mode-button">
+ &aboutSupport.restartInSafeMode.label;
+ </button>
+ </div>
+ <div id="restart-box">
+ <h3>&aboutSupport.restartTitle;</h3>
+ <button id="restart-button">
+ &aboutSupport.restartNormal.label;
+ </button>
+ </div>
+
+ </div>
+ <h1>
+ &aboutSupport.pageTitle;
+ </h1>
+
+ <div class="page-subtitle">
+ &aboutSupport.pageSubtitle;
+ </div>
+
+ <div>
+ <button id="copy-to-clipboard">
+ &aboutSupport.copyTextToClipboard.label;
+ </button>
+ </div>
+
+ <div id="contents">
+
+ <!-- - - - - - - - - - - - - - - - - - - - - -->
+
+ <h2 class="major-section">
+ &aboutSupport.appBasicsTitle;
+ </h2>
+
+ <table>
+ <tbody>
+ <tr>
+ <th class="column">
+ &aboutSupport.appBasicsName;
+ </th>
+
+ <td id="application-box">
+ </td>
+ </tr>
+
+ <tr>
+ <th class="column">
+ &aboutSupport.appBasicsVersion;
+ </th>
+
+ <td id="version-box">
+ </td>
+ </tr>
+
+ <tr>
+ <th class="column">
+ &aboutSupport.appBasicsBuildID;
+ </th>
+ <td id="buildid-box"></td>
+ </tr>
+
+#ifdef MOZ_UPDATER
+ <tr class="no-copy">
+ <th class="column">
+ &aboutSupport.appBasicsUpdateHistory;
+ </th>
+
+ <td>
+ <button id="show-update-history-button">
+ &aboutSupport.appBasicsShowUpdateHistory;
+ </button>
+ </td>
+ </tr>
+#endif
+
+#ifdef MOZ_UPDATER
+ <tr>
+ <th class="column">
+ &aboutSupport.appBasicsUpdateChannel;
+ </th>
+ <td id="updatechannel-box"></td>
+ </tr>
+#endif
+
+ <tr>
+ <th class="column">
+ &aboutSupport.appBasicsUserAgent;
+ </th>
+
+ <td id="useragent-box">
+ </td>
+ </tr>
+
+ <tr>
+ <th class="column">
+ &aboutSupport.appBasicsOS;
+ </th>
+
+ <td id="os-box">
+ </td>
+ </tr>
+
+ <tr class="no-copy">
+ <th class="column">
+ &aboutSupport.appBasicsBinary;
+ </th>
+
+ <td id="binary-box">
+ </td>
+ </tr>
+
+ <tr id="profile-row" class="no-copy">
+ <th class="column">
+#ifdef XP_WIN
+ &aboutSupport.appBasicsProfileDirWinMac;
+#else
+ &aboutSupport.appBasicsProfileDir;
+#endif
+ </th>
+
+ <td>
+ <button id="profile-dir-button">
+#ifdef XP_WIN
+ &aboutSupport.showWin2.label;
+#else
+ &aboutSupport.showDir.label;
+#endif
+ </button>
+ </td>
+ </tr>
+
+ <tr class="no-copy">
+ <th class="column">
+ &aboutSupport.appBasicsEnabledPlugins;
+ </th>
+
+ <td>
+ <a href="about:plugins">about:plugins</a>
+ </td>
+ </tr>
+
+ <tr class="no-copy">
+ <th class="column">
+ &aboutSupport.appBasicsBuildConfig;
+ </th>
+
+ <td>
+ <a href="about:buildconfig">about:buildconfig</a>
+ </td>
+ </tr>
+
+ <tr class="no-copy">
+ <th class="column">
+ &aboutSupport.appBasicsMemoryUse;
+ </th>
+
+ <td>
+ <a href="about:memory">about:memory</a>
+ </td>
+ </tr>
+
+ <tr class="no-copy">
+ <th class="column">
+ &aboutSupport.appBasicsPerformance;
+ </th>
+
+ <td>
+ <a href="about:performance">about:performance</a>
+ </td>
+ </tr>
+
+ <tr class="no-copy">
+ <th class="column">
+ &aboutSupport.appBasicsServiceWorkers;
+ </th>
+
+ <td>
+ <a href="about:serviceworkers">about:serviceworkers</a>
+ </td>
+ </tr>
+
+ <tr>
+ <th class="column">
+ &aboutSupport.appBasicsSafeMode;
+ </th>
+
+ <td id="safemode-box">
+ </td>
+ </tr>
+
+ <tr class="no-copy">
+ <th class="column">
+ &aboutSupport.appBasicsProfiles;
+ </th>
+
+ <td>
+ <a href="about:profiles">about:profiles</a>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - -->
+ <h2 class="major-section">
+ &aboutSupport.extensionsTitle;
+ </h2>
+
+ <table>
+ <thead>
+ <tr>
+ <th>
+ &aboutSupport.extensionName;
+ </th>
+ <th>
+ &aboutSupport.extensionVersion;
+ </th>
+ <th>
+ &aboutSupport.extensionEnabled;
+ </th>
+ <th>
+ &aboutSupport.extensionId;
+ </th>
+ </tr>
+ </thead>
+ <tbody id="extensions-tbody">
+ </tbody>
+ </table>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - -->
+
+ <h2 class="major-section">
+ &aboutSupport.graphicsTitle;
+ </h2>
+
+ <table>
+ <tbody id="graphics-features-tbody">
+ <tr>
+ <th colspan="2" class="title-column">
+ &aboutSupport.graphicsFeaturesTitle;
+ </th>
+ </tr>
+ </tbody>
+
+ <tbody id="graphics-tbody">
+ </tbody>
+
+ <tbody id="graphics-gpu-1-tbody">
+ <tr>
+ <th colspan="2" class="title-column">
+ &aboutSupport.graphicsGPU1Title;
+ </th>
+ </tr>
+ </tbody>
+
+ <tbody id="graphics-gpu-2-tbody">
+ <tr>
+ <th colspan="2" class="title-column">
+ &aboutSupport.graphicsGPU2Title;
+ </th>
+ </tr>
+ </tbody>
+
+ <tbody id="graphics-diagnostics-tbody">
+ <tr>
+ <th colspan="2" class="title-column">
+ &aboutSupport.graphicsDiagnosticsTitle;
+ </th>
+ </tr>
+ </tbody>
+
+ <tbody id="graphics-decisions-tbody">
+ <tr>
+ <th colspan="2" class="title-column">
+ &aboutSupport.graphicsDecisionLogTitle;
+ </th>
+ </tr>
+ </tbody>
+
+ <tbody id="graphics-crashguards-tbody">
+ <tr>
+ <th colspan="2" class="title-column">
+ &aboutSupport.graphicsCrashGuardsTitle;
+ </th>
+ </tr>
+ </tbody>
+
+ <tbody id="graphics-workarounds-tbody">
+ <tr>
+ <th colspan="2" class="title-column">
+ &aboutSupport.graphicsWorkaroundsTitle;
+ </th>
+ </tr>
+ </tbody>
+
+ <tbody id="graphics-failures-tbody">
+ <tr>
+ <th colspan="2" class="title-column">
+ &aboutSupport.graphicsFailureLogTitle;
+ </th>
+ </tr>
+ </tbody>
+ </table>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - -->
+
+ <h2 class="major-section">
+ &aboutSupport.mediaTitle;
+ </h2>
+ <table>
+ <tbody id="media-info-tbody">
+ </tbody>
+
+ <tbody id="media-output-devices-tbody">
+ <tr>
+ <th colspan="10" class="title-column">
+ &aboutSupport.mediaOutputDevicesTitle;
+ </th>
+ </tr>
+ <tr>
+ <th>
+ &aboutSupport.mediaDeviceName;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceGroup;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceVendor;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceState;
+ </th>
+ <th>
+ &aboutSupport.mediaDevicePreferred;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceFormat;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceChannels;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceRate;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceLatency;
+ </th>
+ </tr>
+ </tbody>
+
+ <tbody id="media-input-devices-tbody">
+ <tr>
+ <th colspan="10" class="title-column">
+ &aboutSupport.mediaInputDevicesTitle;
+ </th>
+ </tr>
+ <tr>
+ <th>
+ &aboutSupport.mediaDeviceName;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceGroup;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceVendor;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceState;
+ </th>
+ <th>
+ &aboutSupport.mediaDevicePreferred;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceFormat;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceChannels;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceRate;
+ </th>
+ <th>
+ &aboutSupport.mediaDeviceLatency;
+ </th>
+ </tr>
+ </tbody>
+
+ </table>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - -->
+
+ <h2 class="major-section">
+ &aboutSupport.modifiedKeyPrefsTitle;
+ </h2>
+
+ <table class="prefs-table">
+ <thead class="no-copy">
+ <th class="name">
+ &aboutSupport.modifiedPrefsName;
+ </th>
+
+ <th class="value">
+ &aboutSupport.modifiedPrefsValue;
+ </th>
+ </thead>
+
+ <tbody id="prefs-tbody">
+ </tbody>
+ </table>
+
+ <section id="prefs-user-js-section" class="hidden no-copy">
+ <h3>&aboutSupport.userJSTitle;</h3>
+ <p>&aboutSupport.userJSDescription;</p>
+ </section>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - -->
+
+ <h2 class="major-section">
+ &aboutSupport.lockedKeyPrefsTitle;
+ </h2>
+
+ <table class="prefs-table">
+ <thead class="no-copy">
+ <th class="name">
+ &aboutSupport.lockedPrefsName;
+ </th>
+
+ <th class="value">
+ &aboutSupport.lockedPrefsValue;
+ </th>
+ </thead>
+
+ <tbody id="locked-prefs-tbody">
+ </tbody>
+ </table>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - -->
+
+ <h2 class="major-section">
+ &aboutSupport.placeDatabaseTitle;
+ </h2>
+
+ <table>
+ <tr id="verify-place-result-parent" class="no-copy">
+ <th class="column">
+ &aboutSupport.placeDatabaseIntegrity;
+ </th>
+
+ <td class="endline">
+ <button id="verify-place-integrity-button">
+ &aboutSupport.placeDatabaseVerifyIntegrity;
+ </button>
+ <pre id="verify-place-result" class="hidden"></pre>
+ </td>
+ </tr>
+ </table>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - -->
+ <h2 class="major-section">
+ &aboutSupport.jsTitle;
+ </h2>
+
+ <table>
+ <tbody>
+ <tr>
+ <th class="column">
+ &aboutSupport.jsIncrementalGC;
+ </th>
+
+ <td id="javascript-incremental-gc">
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - -->
+ <h2 class="major-section">
+ &aboutSupport.a11yTitle;
+ </h2>
+
+ <table>
+ <tbody>
+ <tr>
+ <th class="column">
+ &aboutSupport.a11yActivated;
+ </th>
+
+ <td id="a11y-activated">
+ </td>
+ </tr>
+ <tr>
+ <th class="column">
+ &aboutSupport.a11yForceDisabled;
+ </th>
+
+ <td id="a11y-force-disabled">
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - -->
+ <h2 class="major-section">
+ &aboutSupport.libraryVersionsTitle;
+ </h2>
+
+ <table>
+ <tbody id="libversions-tbody">
+ </tbody>
+ </table>
+
+ </div>
+
+ </body>
+
+</html>
diff --git a/components/global/content/autocomplete.css b/components/global/content/autocomplete.css
new file mode 100644
index 000000000..11b36ddac
--- /dev/null
+++ b/components/global/content/autocomplete.css
@@ -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/. */
+
+@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 {
+ -moz-box-orient: horizontal;
+ overflow: hidden;
+}
+
+.ac-title-text,
+.ac-tags-text,
+.ac-url-text,
+.ac-action-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.ac-tags[empty] {
+ display: none;
+}
+
+.ac-action[actiontype=searchengine]:not([selected]),
+.ac-separator[actiontype=searchengine]:not([selected]) {
+ display: none;
+}
+
+.ac-separator[type=keyword] {
+ display: none;
+}
diff --git a/components/global/content/browser-child.js b/components/global/content/browser-child.js
new file mode 100644
index 000000000..ffb07dde2
--- /dev/null
+++ b/components/global/content/browser-child.js
@@ -0,0 +1,598 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cr = Components.results;
+
+Cu.import("resource://gre/modules/BrowserUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import("resource://gre/modules/RemoteAddonsChild.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils",
+ "resource://gre/modules/PageThumbUtils.jsm");
+
+function makeInputStream(aString) {
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsISupportsCString);
+ stream.data = aString;
+ return stream; // XPConnect will QI this to nsIInputStream for us.
+}
+
+var WebProgressListener = {
+ init: function() {
+ this._filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Ci.nsIWebProgress);
+ this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL);
+
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.addProgressListener(this._filter, Ci.nsIWebProgress.NOTIFY_ALL);
+ },
+
+ uninit() {
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.removeProgressListener(this._filter);
+
+ this._filter.removeProgressListener(this);
+ this._filter = null;
+ },
+
+ _requestSpec: function (aRequest, aPropertyName) {
+ if (!aRequest || !(aRequest instanceof Ci.nsIChannel))
+ return null;
+ return aRequest.QueryInterface(Ci.nsIChannel)[aPropertyName].spec;
+ },
+
+ _setupJSON: function setupJSON(aWebProgress, aRequest) {
+ let innerWindowID = null;
+ if (aWebProgress) {
+ let domWindowID = null;
+ try {
+ let utils = aWebProgress.DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ domWindowID = utils.outerWindowID;
+ innerWindowID = utils.currentInnerWindowID;
+ } catch (e) {
+ // If nsDocShell::Destroy has already been called, then we'll
+ // get NS_NOINTERFACE when trying to get the DOM window.
+ // If there is no current inner window, we'll get
+ // NS_ERROR_NOT_AVAILABLE.
+ }
+
+ aWebProgress = {
+ isTopLevel: aWebProgress.isTopLevel,
+ isLoadingDocument: aWebProgress.isLoadingDocument,
+ loadType: aWebProgress.loadType,
+ DOMWindowID: domWindowID
+ };
+ }
+
+ return {
+ webProgress: aWebProgress || null,
+ requestURI: this._requestSpec(aRequest, "URI"),
+ originalRequestURI: this._requestSpec(aRequest, "originalURI"),
+ documentContentType: content.document && content.document.contentType,
+ innerWindowID,
+ };
+ },
+
+ _setupObjects: function setupObjects(aWebProgress, aRequest) {
+ let domWindow;
+ try {
+ domWindow = aWebProgress && aWebProgress.DOMWindow;
+ } catch (e) {
+ // If nsDocShell::Destroy has already been called, then we'll
+ // get NS_NOINTERFACE when trying to get the DOM window. Ignore
+ // that here.
+ domWindow = null;
+ }
+
+ return {
+ contentWindow: content,
+ // DOMWindow is not necessarily the content-window with subframes.
+ DOMWindow: domWindow,
+ webProgress: aWebProgress,
+ request: aRequest,
+ };
+ },
+
+ _send(name, data, objects) {
+ if (RemoteAddonsChild.useSyncWebProgress) {
+ sendRpcMessage(name, data, objects);
+ } else {
+ sendAsyncMessage(name, data, objects);
+ }
+ },
+
+ onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ let json = this._setupJSON(aWebProgress, aRequest);
+ let objects = this._setupObjects(aWebProgress, aRequest);
+
+ json.stateFlags = aStateFlags;
+ json.status = aStatus;
+
+ // It's possible that this state change was triggered by
+ // loading an internal error page, for which the parent
+ // will want to know some details, so we'll update it with
+ // the documentURI.
+ if (aWebProgress && aWebProgress.isTopLevel) {
+ json.documentURI = content.document.documentURIObject.spec;
+ json.charset = content.document.characterSet;
+ json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu;
+ json.inLoadURI = WebNavigation.inLoadURI;
+ }
+
+ this._send("Content:StateChange", json, objects);
+ },
+
+ onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
+ let json = this._setupJSON(aWebProgress, aRequest);
+ let objects = this._setupObjects(aWebProgress, aRequest);
+
+ json.curSelf = aCurSelf;
+ json.maxSelf = aMaxSelf;
+ json.curTotal = aCurTotal;
+ json.maxTotal = aMaxTotal;
+
+ this._send("Content:ProgressChange", json, objects);
+ },
+
+ onProgressChange64: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
+ this.onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal);
+ },
+
+ onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
+ let json = this._setupJSON(aWebProgress, aRequest);
+ let objects = this._setupObjects(aWebProgress, aRequest);
+
+ json.location = aLocationURI ? aLocationURI.spec : "";
+ json.flags = aFlags;
+
+ // These properties can change even for a sub-frame navigation.
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ json.canGoBack = webNav.canGoBack;
+ json.canGoForward = webNav.canGoForward;
+
+ if (aWebProgress && aWebProgress.isTopLevel) {
+ json.documentURI = content.document.documentURIObject.spec;
+ json.title = content.document.title;
+ json.charset = content.document.characterSet;
+ json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu;
+ json.principal = content.document.nodePrincipal;
+ json.synthetic = content.document.mozSyntheticDocument;
+ json.inLoadURI = WebNavigation.inLoadURI;
+ }
+
+ this._send("Content:LocationChange", json, objects);
+ },
+
+ onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
+ let json = this._setupJSON(aWebProgress, aRequest);
+ let objects = this._setupObjects(aWebProgress, aRequest);
+
+ json.status = aStatus;
+ json.message = aMessage;
+
+ this._send("Content:StatusChange", json, objects);
+ },
+
+ onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) {
+ let json = this._setupJSON(aWebProgress, aRequest);
+ let objects = this._setupObjects(aWebProgress, aRequest);
+
+ json.state = aState;
+ json.status = SecurityUI.getSSLStatusAsString();
+
+ this._send("Content:SecurityChange", json, objects);
+ },
+
+ onRefreshAttempted: function onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
+ return true;
+ },
+
+ sendLoadCallResult() {
+ sendAsyncMessage("Content:LoadURIResult");
+ },
+
+ QueryInterface: function QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsIWebProgressListener2) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports)) {
+ return this;
+ }
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+WebProgressListener.init();
+addEventListener("unload", () => {
+ WebProgressListener.uninit();
+});
+
+var WebNavigation = {
+ init: function() {
+ addMessageListener("WebNavigation:GoBack", this);
+ addMessageListener("WebNavigation:GoForward", this);
+ addMessageListener("WebNavigation:GotoIndex", this);
+ addMessageListener("WebNavigation:LoadURI", this);
+ addMessageListener("WebNavigation:SetOriginAttributes", this);
+ addMessageListener("WebNavigation:Reload", this);
+ addMessageListener("WebNavigation:Stop", this);
+ },
+
+ get webNavigation() {
+ return docShell.QueryInterface(Ci.nsIWebNavigation);
+ },
+
+ _inLoadURI: false,
+
+ get inLoadURI() {
+ return this._inLoadURI;
+ },
+
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "WebNavigation:GoBack":
+ this.goBack();
+ break;
+ case "WebNavigation:GoForward":
+ this.goForward();
+ break;
+ case "WebNavigation:GotoIndex":
+ this.gotoIndex(message.data.index);
+ break;
+ case "WebNavigation:LoadURI":
+ this.loadURI(message.data.uri, message.data.flags,
+ message.data.referrer, message.data.referrerPolicy,
+ message.data.postData, message.data.headers,
+ message.data.baseURI);
+ break;
+ case "WebNavigation:SetOriginAttributes":
+ this.setOriginAttributes(message.data.originAttributes);
+ break;
+ case "WebNavigation:Reload":
+ this.reload(message.data.flags);
+ break;
+ case "WebNavigation:Stop":
+ this.stop(message.data.flags);
+ break;
+ }
+ },
+
+ _wrapURIChangeCall(fn) {
+ this._inLoadURI = true;
+ try {
+ fn();
+ } finally {
+ this._inLoadURI = false;
+ WebProgressListener.sendLoadCallResult();
+ }
+ },
+
+ goBack: function() {
+ if (this.webNavigation.canGoBack) {
+ this._wrapURIChangeCall(() => this.webNavigation.goBack());
+ }
+ },
+
+ goForward: function() {
+ if (this.webNavigation.canGoForward) {
+ this._wrapURIChangeCall(() => this.webNavigation.goForward());
+ }
+ },
+
+ gotoIndex: function(index) {
+ this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(index));
+ },
+
+ loadURI: function(uri, flags, referrer, referrerPolicy, postData, headers, baseURI) {
+ if (referrer)
+ referrer = Services.io.newURI(referrer, null, null);
+ if (postData)
+ postData = makeInputStream(postData);
+ if (headers)
+ headers = makeInputStream(headers);
+ if (baseURI)
+ baseURI = Services.io.newURI(baseURI, null, null);
+ this._wrapURIChangeCall(() => {
+ return this.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy,
+ postData, headers, baseURI);
+ });
+ },
+
+ setOriginAttributes: function(originAttributes) {
+ if (originAttributes) {
+ this.webNavigation.setOriginAttributesBeforeLoading(originAttributes);
+ }
+ },
+
+ reload: function(flags) {
+ this.webNavigation.reload(flags);
+ },
+
+ stop: function(flags) {
+ this.webNavigation.stop(flags);
+ }
+};
+
+WebNavigation.init();
+
+var SecurityUI = {
+ getSSLStatusAsString: function() {
+ let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
+
+ if (status) {
+ let helper = Cc["@mozilla.org/network/serialization-helper;1"]
+ .getService(Ci.nsISerializationHelper);
+
+ status.QueryInterface(Ci.nsISerializable);
+ return helper.serializeToString(status);
+ }
+
+ return null;
+ }
+};
+
+var ControllerCommands = {
+ init: function () {
+ addMessageListener("ControllerCommands:Do", this);
+ addMessageListener("ControllerCommands:DoWithParams", this);
+ },
+
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "ControllerCommands:Do":
+ if (docShell.isCommandEnabled(message.data))
+ docShell.doCommand(message.data);
+ break;
+
+ case "ControllerCommands:DoWithParams":
+ var data = message.data;
+ if (docShell.isCommandEnabled(data.cmd)) {
+ var params = Cc["@mozilla.org/embedcomp/command-params;1"].
+ createInstance(Ci.nsICommandParams);
+ for (var name in data.params) {
+ var value = data.params[name];
+ if (value.type == "long") {
+ params.setLongValue(name, parseInt(value.value));
+ } else {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ }
+ docShell.doCommandWithParams(data.cmd, params);
+ }
+ break;
+ }
+ }
+}
+
+ControllerCommands.init()
+
+addEventListener("DOMTitleChanged", function (aEvent) {
+ let document = content.document;
+ switch (aEvent.type) {
+ case "DOMTitleChanged":
+ if (!aEvent.isTrusted || aEvent.target.defaultView != content)
+ return;
+
+ sendAsyncMessage("DOMTitleChanged", { title: document.title });
+ break;
+ }
+}, false);
+
+addEventListener("DOMWindowClose", function (aEvent) {
+ if (!aEvent.isTrusted)
+ return;
+ sendAsyncMessage("DOMWindowClose");
+}, false);
+
+addEventListener("ImageContentLoaded", function (aEvent) {
+ if (content.document instanceof Ci.nsIImageDocument) {
+ let req = content.document.imageRequest;
+ if (!req.image)
+ return;
+ sendAsyncMessage("ImageDocumentLoaded", { width: req.image.width,
+ height: req.image.height });
+ }
+}, false);
+
+const ZoomManager = {
+ get fullZoom() {
+ return this._cache.fullZoom;
+ },
+
+ get textZoom() {
+ return this._cache.textZoom;
+ },
+
+ set fullZoom(value) {
+ this._cache.fullZoom = value;
+ this._markupViewer.fullZoom = value;
+ },
+
+ set textZoom(value) {
+ this._cache.textZoom = value;
+ this._markupViewer.textZoom = value;
+ },
+
+ refreshFullZoom: function() {
+ return this._refreshZoomValue('fullZoom');
+ },
+
+ refreshTextZoom: function() {
+ return this._refreshZoomValue('textZoom');
+ },
+
+ /**
+ * Retrieves specified zoom property value from markupViewer and refreshes
+ * cache if needed.
+ * @param valueName Either 'fullZoom' or 'textZoom'.
+ * @returns Returns true if cached value was actually refreshed.
+ * @private
+ */
+ _refreshZoomValue: function(valueName) {
+ let actualZoomValue = this._markupViewer[valueName];
+ // Round to remove any floating-point error.
+ actualZoomValue = Number(actualZoomValue.toFixed(2));
+ if (actualZoomValue != this._cache[valueName]) {
+ this._cache[valueName] = actualZoomValue;
+ return true;
+ }
+ return false;
+ },
+
+ get _markupViewer() {
+ return docShell.contentViewer;
+ },
+
+ _cache: {
+ fullZoom: NaN,
+ textZoom: NaN
+ }
+};
+
+addMessageListener("FullZoom", function (aMessage) {
+ ZoomManager.fullZoom = aMessage.data.value;
+});
+
+addMessageListener("TextZoom", function (aMessage) {
+ ZoomManager.textZoom = aMessage.data.value;
+});
+
+addEventListener("FullZoomChange", function () {
+ if (ZoomManager.refreshFullZoom()) {
+ sendAsyncMessage("FullZoomChange", { value: ZoomManager.fullZoom });
+ }
+}, false);
+
+addEventListener("TextZoomChange", function (aEvent) {
+ if (ZoomManager.refreshTextZoom()) {
+ sendAsyncMessage("TextZoomChange", { value: ZoomManager.textZoom });
+ }
+}, false);
+
+addEventListener("ZoomChangeUsingMouseWheel", function () {
+ sendAsyncMessage("ZoomChangeUsingMouseWheel", {});
+}, false);
+
+addMessageListener("UpdateCharacterSet", function (aMessage) {
+ docShell.charset = aMessage.data.value;
+ docShell.gatherCharsetMenuTelemetry();
+});
+
+/**
+ * Remote thumbnail request handler for PageThumbs thumbnails.
+ */
+addMessageListener("Browser:Thumbnail:Request", function (aMessage) {
+ let snapshot;
+ let args = aMessage.data.additionalArgs;
+ let fullScale = args ? args.fullScale : false;
+ if (fullScale) {
+ snapshot = PageThumbUtils.createSnapshotThumbnail(content, null, args);
+ } else {
+ let snapshotWidth = aMessage.data.canvasWidth;
+ let snapshotHeight = aMessage.data.canvasHeight;
+ snapshot =
+ PageThumbUtils.createCanvas(content, snapshotWidth, snapshotHeight);
+ PageThumbUtils.createSnapshotThumbnail(content, snapshot, args);
+ }
+
+ snapshot.toBlob(function (aBlob) {
+ sendAsyncMessage("Browser:Thumbnail:Response", {
+ thumbnail: aBlob,
+ id: aMessage.data.id
+ });
+ });
+});
+
+/**
+ * Remote isSafeForCapture request handler for PageThumbs.
+ */
+addMessageListener("Browser:Thumbnail:CheckState", function (aMessage) {
+ let result = PageThumbUtils.shouldStoreContentThumbnail(content, docShell);
+ sendAsyncMessage("Browser:Thumbnail:CheckState:Response", {
+ result: result
+ });
+});
+
+/**
+ * Remote GetOriginalURL request handler for PageThumbs.
+ */
+addMessageListener("Browser:Thumbnail:GetOriginalURL", function (aMessage) {
+ let channel = docShell.currentDocumentChannel;
+ let channelError = PageThumbUtils.isChannelErrorResponse(channel);
+ let originalURL;
+ try {
+ originalURL = channel.originalURI.spec;
+ } catch (ex) {}
+ sendAsyncMessage("Browser:Thumbnail:GetOriginalURL:Response", {
+ channelError: channelError,
+ originalURL: originalURL,
+ });
+});
+
+/**
+ * Remote createAboutBlankContentViewer request handler.
+ */
+addMessageListener("Browser:CreateAboutBlank", function(aMessage) {
+ if (!content.document || content.document.documentURI != "about:blank") {
+ throw new Error("Can't create a content viewer unless on about:blank");
+ }
+ let principal = aMessage.data;
+ principal = BrowserUtils.principalWithMatchingOA(principal, content.document.nodePrincipal);
+ docShell.createAboutBlankContentViewer(principal);
+});
+
+// The AddonsChild needs to be rooted so that it stays alive as long as
+// the tab.
+var AddonsChild = RemoteAddonsChild.init(this);
+if (AddonsChild) {
+ addEventListener("unload", () => {
+ RemoteAddonsChild.uninit(AddonsChild);
+ });
+}
+
+addMessageListener("NetworkPrioritizer:AdjustPriority", (msg) => {
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let loadGroup = webNav.QueryInterface(Ci.nsIDocumentLoader)
+ .loadGroup.QueryInterface(Ci.nsISupportsPriority);
+ loadGroup.adjustPriority(msg.data.adjustment);
+});
+
+addMessageListener("NetworkPrioritizer:SetPriority", (msg) => {
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let loadGroup = webNav.QueryInterface(Ci.nsIDocumentLoader)
+ .loadGroup.QueryInterface(Ci.nsISupportsPriority);
+ loadGroup.priority = msg.data.priority;
+});
+
+addMessageListener("InPermitUnload", msg => {
+ let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload;
+ sendAsyncMessage("InPermitUnload", {id: msg.data.id, inPermitUnload});
+});
+
+addMessageListener("PermitUnload", msg => {
+ sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "start"});
+
+ let permitUnload = true;
+ if (docShell && docShell.contentViewer) {
+ permitUnload = docShell.contentViewer.permitUnload();
+ }
+
+ sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "end", permitUnload});
+});
+
+// We may not get any responses to Browser:Init if the browser element
+// is torn down too quickly.
+var outerWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+sendAsyncMessage("Browser:Init", {outerWindowID: outerWindowID});
diff --git a/components/global/content/browser-content.js b/components/global/content/browser-content.js
new file mode 100644
index 000000000..954848cb8
--- /dev/null
+++ b/components/global/content/browser-content.js
@@ -0,0 +1,1756 @@
+/* -*- 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 Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
+ "resource://gre/modules/ReaderMode.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+
+var global = this;
+
+
+// Lazily load the finder code
+addMessageListener("Finder:Initialize", function () {
+ let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
+ new RemoteFinderListener(global);
+});
+
+var ClickEventHandler = {
+ init: function init() {
+ this._scrollable = null;
+ this._scrolldir = "";
+ this._startX = null;
+ this._startY = null;
+ this._screenX = null;
+ this._screenY = null;
+ this._lastFrame = null;
+ this.autoscrollLoop = this.autoscrollLoop.bind(this);
+
+ Services.els.addSystemEventListener(global, "mousedown", this, true);
+
+ addMessageListener("Autoscroll:Stop", this);
+ },
+
+ isAutoscrollBlocker: function(node) {
+ let mmPaste = Services.prefs.getBoolPref("middlemouse.paste");
+ let mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition");
+
+ while (node) {
+ if ((node instanceof content.HTMLAnchorElement || node instanceof content.HTMLAreaElement) &&
+ node.hasAttribute("href")) {
+ return true;
+ }
+
+ if (mmPaste && (node instanceof content.HTMLInputElement ||
+ node instanceof content.HTMLTextAreaElement)) {
+ return true;
+ }
+
+ if (node instanceof content.XULElement && mmScrollbarPosition
+ && (node.localName == "scrollbar" || node.localName == "scrollcorner")) {
+ return true;
+ }
+
+ node = node.parentNode;
+ }
+ return false;
+ },
+
+ findNearestScrollableElement: function(aNode) {
+ // this is a list of overflow property values that allow scrolling
+ const scrollingAllowed = ['scroll', 'auto'];
+
+ // go upward in the DOM and find any parent element that has a overflow
+ // area and can therefore be scrolled
+ for (this._scrollable = aNode; this._scrollable;
+ this._scrollable = this._scrollable.parentNode) {
+ // do not use overflow based autoscroll for <html> and <body>
+ // Elements or non-html elements such as svg or Document nodes
+ // also make sure to skip select elements that are not multiline
+ if (!(this._scrollable instanceof content.HTMLElement) ||
+ ((this._scrollable instanceof content.HTMLSelectElement) && !this._scrollable.multiple)) {
+ continue;
+ }
+
+ var overflowx = this._scrollable.ownerDocument.defaultView
+ .getComputedStyle(this._scrollable, '')
+ .getPropertyValue('overflow-x');
+ var overflowy = this._scrollable.ownerDocument.defaultView
+ .getComputedStyle(this._scrollable, '')
+ .getPropertyValue('overflow-y');
+ // we already discarded non-multiline selects so allow vertical
+ // scroll for multiline ones directly without checking for a
+ // overflow property
+ var scrollVert = this._scrollable.scrollTopMax &&
+ (this._scrollable instanceof content.HTMLSelectElement ||
+ scrollingAllowed.indexOf(overflowy) >= 0);
+
+ // do not allow horizontal scrolling for select elements, it leads
+ // to visual artifacts and is not the expected behavior anyway
+ if (!(this._scrollable instanceof content.HTMLSelectElement) &&
+ this._scrollable.scrollLeftMin != this._scrollable.scrollLeftMax &&
+ scrollingAllowed.indexOf(overflowx) >= 0) {
+ this._scrolldir = scrollVert ? "NSEW" : "EW";
+ break;
+ } else if (scrollVert) {
+ this._scrolldir = "NS";
+ break;
+ }
+ }
+
+ if (!this._scrollable) {
+ this._scrollable = aNode.ownerDocument.defaultView;
+ if (this._scrollable.scrollMaxX != this._scrollable.scrollMinX) {
+ this._scrolldir = this._scrollable.scrollMaxY !=
+ this._scrollable.scrollMinY ? "NSEW" : "EW";
+ } else if (this._scrollable.scrollMaxY != this._scrollable.scrollMinY) {
+ this._scrolldir = "NS";
+ } else if (this._scrollable.frameElement) {
+ this.findNearestScrollableElement(this._scrollable.frameElement);
+ } else {
+ this._scrollable = null; // abort scrolling
+ }
+ }
+ },
+
+ startScroll: function(event) {
+
+ this.findNearestScrollableElement(event.originalTarget);
+
+ if (!this._scrollable)
+ return;
+
+ let [enabled] = sendSyncMessage("Autoscroll:Start",
+ {scrolldir: this._scrolldir,
+ screenX: event.screenX,
+ screenY: event.screenY});
+ if (!enabled) {
+ this._scrollable = null;
+ return;
+ }
+
+ Services.els.addSystemEventListener(global, "mousemove", this, true);
+ addEventListener("pagehide", this, true);
+
+ this._ignoreMouseEvents = true;
+ this._startX = event.screenX;
+ this._startY = event.screenY;
+ this._screenX = event.screenX;
+ this._screenY = event.screenY;
+ this._scrollErrorX = 0;
+ this._scrollErrorY = 0;
+ this._lastFrame = content.performance.now();
+
+ content.requestAnimationFrame(this.autoscrollLoop);
+ },
+
+ stopScroll: function() {
+ if (this._scrollable) {
+ this._scrollable.mozScrollSnap();
+ this._scrollable = null;
+
+ Services.els.removeSystemEventListener(global, "mousemove", this, true);
+ removeEventListener("pagehide", this, true);
+ }
+ },
+
+ accelerate: function(curr, start) {
+ const speed = 12;
+ var val = (curr - start) / speed;
+
+ if (val > 1)
+ return val * Math.sqrt(val) - 1;
+ if (val < -1)
+ return val * Math.sqrt(-val) + 1;
+ return 0;
+ },
+
+ roundToZero: function(num) {
+ if (num > 0)
+ return Math.floor(num);
+ return Math.ceil(num);
+ },
+
+ autoscrollLoop: function(timestamp) {
+ if (!this._scrollable) {
+ // Scrolling has been canceled
+ return;
+ }
+
+ // avoid long jumps when the browser hangs for more than
+ // |maxTimeDelta| ms
+ const maxTimeDelta = 100;
+ var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame);
+ // we used to scroll |accelerate()| pixels every 20ms (50fps)
+ var timeCompensation = timeDelta / 20;
+ this._lastFrame = timestamp;
+
+ var actualScrollX = 0;
+ var actualScrollY = 0;
+ // don't bother scrolling vertically when the scrolldir is only horizontal
+ // and the other way around
+ if (this._scrolldir != 'EW') {
+ var y = this.accelerate(this._screenY, this._startY) * timeCompensation;
+ var desiredScrollY = this._scrollErrorY + y;
+ actualScrollY = this.roundToZero(desiredScrollY);
+ this._scrollErrorY = (desiredScrollY - actualScrollY);
+ }
+ if (this._scrolldir != 'NS') {
+ var x = this.accelerate(this._screenX, this._startX) * timeCompensation;
+ var desiredScrollX = this._scrollErrorX + x;
+ actualScrollX = this.roundToZero(desiredScrollX);
+ this._scrollErrorX = (desiredScrollX - actualScrollX);
+ }
+
+ const kAutoscroll = 15; // defined in mozilla/layers/ScrollInputMethods.h
+
+ this._scrollable.scrollBy({
+ left: actualScrollX,
+ top: actualScrollY,
+ behavior: "instant"
+ });
+ content.requestAnimationFrame(this.autoscrollLoop);
+ },
+
+ handleEvent: function(event) {
+ if (event.type == "mousemove") {
+ this._screenX = event.screenX;
+ this._screenY = event.screenY;
+ } else if (event.type == "mousedown") {
+ if (event.isTrusted &
+ !event.defaultPrevented &&
+ event.button == 1 &&
+ !this._scrollable &&
+ !this.isAutoscrollBlocker(event.originalTarget)) {
+ this.startScroll(event);
+ }
+ } else if (event.type == "pagehide") {
+ if (this._scrollable) {
+ var doc =
+ this._scrollable.ownerDocument || this._scrollable.document;
+ if (doc == event.target) {
+ sendAsyncMessage("Autoscroll:Cancel");
+ }
+ }
+ }
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "Autoscroll:Stop": {
+ this.stopScroll();
+ break;
+ }
+ }
+ },
+};
+ClickEventHandler.init();
+
+var PopupBlocking = {
+ popupData: null,
+ popupDataInternal: null,
+
+ init: function() {
+ addEventListener("DOMPopupBlocked", this, true);
+ addEventListener("pageshow", this, true);
+ addEventListener("pagehide", this, true);
+
+ addMessageListener("PopupBlocking:UnblockPopup", this);
+ addMessageListener("PopupBlocking:GetBlockedPopupList", this);
+ },
+
+ receiveMessage: function(msg) {
+ switch (msg.name) {
+ case "PopupBlocking:UnblockPopup": {
+ let i = msg.data.index;
+ if (this.popupData && this.popupData[i]) {
+ let data = this.popupData[i];
+ let internals = this.popupDataInternal[i];
+ let dwi = internals.requestingWindow;
+
+ // If we have a requesting window and the requesting document is
+ // still the current document, open the popup.
+ if (dwi && dwi.document == internals.requestingDocument) {
+ dwi.open(data.popupWindowURIspec, data.popupWindowName, data.popupWindowFeatures);
+ }
+ }
+ break;
+ }
+
+ case "PopupBlocking:GetBlockedPopupList": {
+ let popupData = [];
+ let length = this.popupData ? this.popupData.length : 0;
+
+ // Limit 15 popup URLs to be reported through the UI
+ length = Math.min(length, 15);
+
+ for (let i = 0; i < length; i++) {
+ let popupWindowURIspec = this.popupData[i].popupWindowURIspec;
+
+ if (popupWindowURIspec == global.content.location.href) {
+ popupWindowURIspec = "<self>";
+ } else {
+ // Limit 500 chars to be sent because the URI will be cropped
+ // by the UI anyway, and data: URIs can be significantly larger.
+ popupWindowURIspec = popupWindowURIspec.substring(0, 500)
+ }
+
+ popupData.push({popupWindowURIspec});
+ }
+
+ sendAsyncMessage("PopupBlocking:ReplyGetBlockedPopupList", {popupData});
+ break;
+ }
+ }
+ },
+
+ handleEvent: function(ev) {
+ switch (ev.type) {
+ case "DOMPopupBlocked":
+ return this.onPopupBlocked(ev);
+ case "pageshow":
+ return this.onPageShow(ev);
+ case "pagehide":
+ return this.onPageHide(ev);
+ }
+ return undefined;
+ },
+
+ onPopupBlocked: function(ev) {
+ if (!this.popupData) {
+ this.popupData = new Array();
+ this.popupDataInternal = new Array();
+ }
+
+ let obj = {
+ popupWindowURIspec: ev.popupWindowURI ? ev.popupWindowURI.spec : "about:blank",
+ popupWindowFeatures: ev.popupWindowFeatures,
+ popupWindowName: ev.popupWindowName
+ };
+
+ let internals = {
+ requestingWindow: ev.requestingWindow,
+ requestingDocument: ev.requestingWindow.document,
+ };
+
+ this.popupData.push(obj);
+ this.popupDataInternal.push(internals);
+ this.updateBlockedPopups(true);
+ },
+
+ onPageShow: function(ev) {
+ if (this.popupData) {
+ let i = 0;
+ while (i < this.popupData.length) {
+ // Filter out irrelevant reports.
+ if (this.popupDataInternal[i].requestingWindow &&
+ (this.popupDataInternal[i].requestingWindow.document ==
+ this.popupDataInternal[i].requestingDocument)) {
+ i++;
+ } else {
+ this.popupData.splice(i, 1);
+ this.popupDataInternal.splice(i, 1);
+ }
+ }
+ if (this.popupData.length == 0) {
+ this.popupData = null;
+ this.popupDataInternal = null;
+ }
+ this.updateBlockedPopups(false);
+ }
+ },
+
+ onPageHide: function(ev) {
+ if (this.popupData) {
+ this.popupData = null;
+ this.popupDataInternal = null;
+ this.updateBlockedPopups(false);
+ }
+ },
+
+ updateBlockedPopups: function(freshPopup) {
+ sendAsyncMessage("PopupBlocking:UpdateBlockedPopups",
+ {
+ count: this.popupData ? this.popupData.length : 0,
+ freshPopup
+ });
+ },
+};
+PopupBlocking.init();
+
+XPCOMUtils.defineLazyGetter(this, "console", () => {
+ // Set up console.* for frame scripts.
+ let Console = Components.utils.import("resource://gre/modules/Console.jsm", {});
+ return new Console.ConsoleAPI();
+});
+
+var Printing = {
+ // Bug 1088061: nsPrintEngine's DoCommonPrint currently expects the
+ // progress listener passed to it to QI to an nsIPrintingPromptService
+ // in order to know that a printing progress dialog has been shown. That's
+ // really all the interface is used for, hence the fact that I don't actually
+ // implement the interface here. Bug 1088061 has been filed to remove
+ // this hackery.
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsIPrintingPromptService]),
+
+ MESSAGES: [
+ "Printing:Preview:Enter",
+ "Printing:Preview:Exit",
+ "Printing:Preview:Navigate",
+ "Printing:Preview:ParseDocument",
+ "Printing:Preview:UpdatePageCount",
+ "Printing:Print",
+ ],
+
+ init() {
+ this.MESSAGES.forEach(msgName => addMessageListener(msgName, this));
+ addEventListener("PrintingError", this, true);
+ },
+
+ get shouldSavePrintSettings() {
+ return Services.prefs.getBoolPref("print.use_global_printsettings", false) &&
+ Services.prefs.getBoolPref("print.save_print_settings", false);
+ },
+
+ handleEvent(event) {
+ if (event.type == "PrintingError") {
+ let win = event.target.defaultView;
+ let wbp = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebBrowserPrint);
+ let nsresult = event.detail;
+ sendAsyncMessage("Printing:Error", {
+ isPrinting: wbp.doingPrint,
+ nsresult: nsresult,
+ });
+ }
+ },
+
+ receiveMessage(message) {
+ let objects = message.objects;
+ let data = message.data;
+ switch (message.name) {
+ case "Printing:Preview:Enter": {
+ this.enterPrintPreview(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode);
+ break;
+ }
+
+ case "Printing:Preview:Exit": {
+ this.exitPrintPreview();
+ break;
+ }
+
+ case "Printing:Preview:Navigate": {
+ this.navigate(data.navType, data.pageNum);
+ break;
+ }
+
+ case "Printing:Preview:ParseDocument": {
+ this.parseDocument(data.URL, Services.wm.getOuterWindowWithId(data.windowID));
+ break;
+ }
+
+ case "Printing:Preview:UpdatePageCount": {
+ this.updatePageCount();
+ break;
+ }
+
+ case "Printing:Print": {
+ this.print(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode);
+ break;
+ }
+ }
+ },
+
+ getPrintSettings() {
+ try {
+ let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Ci.nsIPrintSettingsService);
+
+ let printSettings = PSSVC.globalPrintSettings;
+ if (!printSettings.printerName) {
+ printSettings.printerName = PSSVC.defaultPrinterName;
+ }
+ // First get any defaults from the printer
+ try {
+ PSSVC.initPrintSettingsFromPrinter(printSettings.printerName,
+ printSettings);
+ } catch(e) {
+ // The printer name specified was invalid or there was an O.S. error.
+ Components.utils.reportError("Invalid printer: " + printSettings.printerName);
+ Services.prefs.clearUserPref("print.print_printer");
+ // Try again with default
+ printSettings.printerName = PSSVC.defaultPrinterName;
+ PSSVC.initPrintSettingsFromPrinter(printSettings.printerName,
+ printSettings);
+ }
+ // now augment them with any values from last time
+ PSSVC.initPrintSettingsFromPrefs(printSettings, true,
+ printSettings.kInitSaveAll);
+
+ return printSettings;
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ return null;
+ },
+
+ parseDocument(URL, contentWindow) {
+ // By using ReaderMode primitives, we parse given document and place the
+ // resulting JS object into the DOM of current browser.
+ let articlePromise = ReaderMode.parseDocument(contentWindow.document).catch(Cu.reportError);
+ articlePromise.then(function (article) {
+ // We make use of a web progress listener in order to know when the content we inject
+ // into the DOM has finished rendering. If our layout engine is still painting, we
+ // will wait for MozAfterPaint event to be fired.
+ let webProgressListener = {
+ onStateChange: function (webProgress, req, flags, status) {
+ if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
+ webProgress.removeProgressListener(webProgressListener);
+ let domUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ // Here we tell the parent that we have parsed the document successfully
+ // using ReaderMode primitives and we are able to enter on preview mode.
+ if (domUtils.isMozAfterPaintPending) {
+ addEventListener("MozAfterPaint", function onPaint() {
+ removeEventListener("MozAfterPaint", onPaint);
+ sendAsyncMessage("Printing:Preview:ReaderModeReady");
+ });
+ } else {
+ sendAsyncMessage("Printing:Preview:ReaderModeReady");
+ }
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver,
+ ]),
+ };
+
+ // Here we QI the docShell into a nsIWebProgress passing our web progress listener in.
+ let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ webProgress.addProgressListener(webProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
+
+ content.document.head.innerHTML = "";
+
+ // Set title of document
+ content.document.title = article.title;
+
+ // Set base URI of document. Print preview code will read this value to
+ // populate the URL field in print settings so that it doesn't show
+ // "about:blank" as its URI.
+ let headBaseElement = content.document.createElement("base");
+ headBaseElement.setAttribute("href", URL);
+ content.document.head.appendChild(headBaseElement);
+
+ // Create link element referencing aboutReader.css and append it to head
+ let headStyleElement = content.document.createElement("link");
+ headStyleElement.setAttribute("rel", "stylesheet");
+ headStyleElement.setAttribute("href", "chrome://global/skin/aboutReader.css");
+ headStyleElement.setAttribute("type", "text/css");
+ content.document.head.appendChild(headStyleElement);
+
+ content.document.body.innerHTML = "";
+
+ // Create container div (main element) and append it to body
+ let containerElement = content.document.createElement("div");
+ containerElement.setAttribute("id", "container");
+ content.document.body.appendChild(containerElement);
+
+ // Create header div and append it to container
+ let headerElement = content.document.createElement("div");
+ headerElement.setAttribute("id", "reader-header");
+ headerElement.setAttribute("class", "header");
+ containerElement.appendChild(headerElement);
+
+ // Create style element for header div and import simplifyMode.css
+ let controlHeaderStyle = content.document.createElement("style");
+ controlHeaderStyle.setAttribute("scoped", "");
+ controlHeaderStyle.textContent = "@import url(\"chrome://global/content/simplifyMode.css\");";
+ headerElement.appendChild(controlHeaderStyle);
+
+ // Jam the article's title and byline into header div
+ let titleElement = content.document.createElement("h1");
+ titleElement.setAttribute("id", "reader-title");
+ titleElement.textContent = article.title;
+ headerElement.appendChild(titleElement);
+
+ let bylineElement = content.document.createElement("div");
+ bylineElement.setAttribute("id", "reader-credits");
+ bylineElement.setAttribute("class", "credits");
+ bylineElement.textContent = article.byline;
+ headerElement.appendChild(bylineElement);
+
+ // Display header element
+ headerElement.style.display = "block";
+
+ // Create content div and append it to container
+ let contentElement = content.document.createElement("div");
+ contentElement.setAttribute("class", "content");
+ containerElement.appendChild(contentElement);
+
+ // Jam the article's content into content div
+ let readerContent = content.document.createElement("div");
+ readerContent.setAttribute("id", "moz-reader-content");
+ contentElement.appendChild(readerContent);
+
+ let articleUri = Services.io.newURI(article.url, null, null);
+ let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
+ let contentFragment = parserUtils.parseFragment(article.content,
+ Ci.nsIParserUtils.SanitizerDropForms | Ci.nsIParserUtils.SanitizerAllowStyle,
+ false, articleUri, readerContent);
+
+ readerContent.appendChild(contentFragment);
+
+ // Display reader content element
+ readerContent.style.display = "block";
+ });
+ },
+
+ enterPrintPreview(contentWindow, simplifiedMode) {
+ // We'll call this whenever we've finished reflowing the document, or if
+ // we errored out while attempting to print preview (in which case, we'll
+ // notify the parent that we've failed).
+ let notifyEntered = (error) => {
+ removeEventListener("printPreviewUpdate", onPrintPreviewReady);
+ sendAsyncMessage("Printing:Preview:Entered", {
+ failed: !!error,
+ });
+ };
+
+ let onPrintPreviewReady = () => {
+ notifyEntered();
+ };
+
+ // We have to wait for the print engine to finish reflowing all of the
+ // documents and subdocuments before we can tell the parent to flip to
+ // the print preview UI - otherwise, the print preview UI might ask for
+ // information (like the number of pages in the document) before we have
+ // our PresShells set up.
+ addEventListener("printPreviewUpdate", onPrintPreviewReady);
+
+ try {
+ let printSettings = this.getPrintSettings();
+
+ // If we happen to be on simplified mode, we need to set docURL in order
+ // to generate header/footer content correctly, since simplified tab has
+ // "about:blank" as its URI.
+ if (printSettings && simplifiedMode)
+ printSettings.docURL = contentWindow.document.baseURI;
+
+ docShell.printPreview.printPreview(printSettings, contentWindow, this);
+ } catch (error) {
+ // This might fail if we, for example, attempt to print a XUL document.
+ // In that case, we inform the parent to bail out of print preview.
+ Components.utils.reportError(error);
+ notifyEntered(error);
+ }
+ },
+
+ exitPrintPreview() {
+ docShell.printPreview.exitPrintPreview();
+ },
+
+ print(contentWindow, simplifiedMode) {
+ let printSettings = this.getPrintSettings();
+ let rv = Cr.NS_OK;
+
+ // If we happen to be on simplified mode, we need to set docURL in order
+ // to generate header/footer content correctly, since simplified tab has
+ // "about:blank" as its URI.
+ if (printSettings && simplifiedMode) {
+ printSettings.docURL = contentWindow.document.baseURI;
+ }
+
+ try {
+ let print = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebBrowserPrint);
+
+ print.print(printSettings, null);
+
+ } catch (e) {
+ // Pressing cancel is expressed as an NS_ERROR_ABORT return value,
+ // causing an exception to be thrown which we catch here.
+ if (e.result != Cr.NS_ERROR_ABORT) {
+ Cu.reportError(`In Printing:Print:Done handler, got unexpected rv
+ ${e.result}.`);
+ sendAsyncMessage("Printing:Error", {
+ isPrinting: true,
+ nsresult: e.result,
+ });
+ }
+ }
+
+ if (this.shouldSavePrintSettings) {
+ let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Ci.nsIPrintSettingsService);
+
+ PSSVC.savePrintSettingsToPrefs(printSettings, true,
+ printSettings.kInitSaveAll);
+ PSSVC.savePrintSettingsToPrefs(printSettings, false,
+ printSettings.kInitSavePrinterName);
+ }
+ },
+
+ updatePageCount() {
+ let numPages = docShell.printPreview.printPreviewNumPages;
+ sendAsyncMessage("Printing:Preview:UpdatePageCount", {
+ numPages: numPages,
+ });
+ },
+
+ navigate(navType, pageNum) {
+ docShell.printPreview.printPreviewNavigate(navType, pageNum);
+ },
+
+ /* nsIWebProgressListener for print preview */
+
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ sendAsyncMessage("Printing:Preview:StateChange", {
+ stateFlags: aStateFlags,
+ status: aStatus,
+ });
+ },
+
+ onProgressChange(aWebProgress, aRequest, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress) {
+ sendAsyncMessage("Printing:Preview:ProgressChange", {
+ curSelfProgress: aCurSelfProgress,
+ maxSelfProgress: aMaxSelfProgress,
+ curTotalProgress: aCurTotalProgress,
+ maxTotalProgress: aMaxTotalProgress,
+ });
+ },
+
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
+ onSecurityChange(aWebProgress, aRequest, aState) {},
+}
+Printing.init();
+
+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 (let run = 0; run < aWindow.frames.length; run++) {
+ SwitchDocumentDirection(aWindow.frames[run]);
+ }
+}
+
+addMessageListener("SwitchDocumentDirection", () => {
+ SwitchDocumentDirection(content.window);
+});
+
+var FindBar = {
+ /* Please keep in sync with toolkit/content/widgets/findbar.xml */
+ FIND_NORMAL: 0,
+ FIND_TYPEAHEAD: 1,
+ FIND_LINKS: 2,
+
+ _findMode: 0,
+
+ init() {
+ addMessageListener("Findbar:UpdateState", this);
+ Services.els.addSystemEventListener(global, "keypress", this, false);
+ Services.els.addSystemEventListener(global, "mouseup", this, false);
+ },
+
+ receiveMessage(msg) {
+ switch (msg.name) {
+ case "Findbar:UpdateState":
+ this._findMode = msg.data.findMode;
+ break;
+ }
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "keypress":
+ this._onKeypress(event);
+ break;
+ case "mouseup":
+ this._onMouseup(event);
+ break;
+ }
+ },
+
+ /**
+ * Returns whether FAYT can be used for the given event in
+ * the current content state.
+ */
+ _canAndShouldFastFind() {
+ let should = false;
+ let can = BrowserUtils.canFastFind(content);
+ if (can) {
+ // XXXgijs: why all these shenanigans? Why not use the event's target?
+ let focusedWindow = {};
+ let elt = Services.focus.getFocusedElementForWindow(content, true, focusedWindow);
+ let win = focusedWindow.value;
+ should = BrowserUtils.shouldFastFind(elt, win);
+ }
+ return { can, should }
+ },
+
+ _onKeypress(event) {
+ // Useless keys:
+ if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented) {
+ return undefined;
+ }
+
+ // Check the focused element etc.
+ let fastFind = this._canAndShouldFastFind();
+
+ // Can we even use find in this page at all?
+ if (!fastFind.can) {
+ return undefined;
+ }
+
+ let fakeEvent = {};
+ for (let k in event) {
+ if (typeof event[k] != "object" && typeof event[k] != "function" &&
+ !(k in content.KeyboardEvent)) {
+ fakeEvent[k] = event[k];
+ }
+ }
+
+ // sendSyncMessage returns an array of the responses from all listeners
+ let rv = sendSyncMessage("Findbar:Keypress", {
+ fakeEvent: fakeEvent,
+ shouldFastFind: fastFind.should
+ });
+ if (rv.indexOf(false) !== -1) {
+ event.preventDefault();
+ return false;
+ }
+ return undefined;
+ },
+
+ _onMouseup(event) {
+ if (this._findMode != this.FIND_NORMAL)
+ sendAsyncMessage("Findbar:Mouseup");
+ },
+};
+FindBar.init();
+
+let WebChannelMessageToChromeListener = {
+ // Preference containing the list (space separated) of origins that are
+ // allowed to send non-string values through a WebChannel, mainly for
+ // backwards compatability. See bug 1238128 for more information.
+ URL_WHITELIST_PREF: "webchannel.allowObject.urlWhitelist",
+
+ // Cached list of whitelisted principals, we avoid constructing this if the
+ // value in `_lastWhitelistValue` hasn't changed since we constructed it last.
+ _cachedWhitelist: [],
+ _lastWhitelistValue: "",
+
+ init() {
+ addEventListener("WebChannelMessageToChrome", e => {
+ this._onMessageToChrome(e);
+ }, true, true);
+ },
+
+ _getWhitelistedPrincipals() {
+ let whitelist = Services.prefs.getCharPref(this.URL_WHITELIST_PREF);
+ if (whitelist != this._lastWhitelistValue) {
+ let urls = whitelist.split(/\s+/);
+ this._cachedWhitelist = urls.map(origin =>
+ Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin));
+ }
+ return this._cachedWhitelist;
+ },
+
+ _onMessageToChrome(e) {
+ // If target is window then we want the document principal, otherwise fallback to target itself.
+ let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;
+
+ if (e.detail) {
+ if (typeof e.detail != 'string') {
+ // Check if the principal is one of the ones that's allowed to send
+ // non-string values for e.detail.
+ let objectsAllowed = this._getWhitelistedPrincipals().some(whitelisted =>
+ principal.originNoSuffix == whitelisted.originNoSuffix);
+ if (!objectsAllowed) {
+ Cu.reportError("WebChannelMessageToChrome sent with an object from a non-whitelisted principal");
+ return;
+ }
+ }
+ sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
+ } else {
+ Cu.reportError("WebChannel message failed. No message detail.");
+ }
+ }
+};
+
+WebChannelMessageToChromeListener.init();
+
+// This should be kept in sync with /browser/base/content.js.
+// Add message listener for "WebChannelMessageToContent" messages from chrome scripts.
+addMessageListener("WebChannelMessageToContent", function (e) {
+ if (e.data) {
+ // e.objects.eventTarget will be defined if sending a response to
+ // a WebChannelMessageToChrome event. An unsolicited send
+ // may not have an eventTarget defined, in this case send to the
+ // main content window.
+ let eventTarget = e.objects.eventTarget || content;
+
+ // Use nodePrincipal if available, otherwise fallback to document principal.
+ let targetPrincipal = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget.document.nodePrincipal : eventTarget.nodePrincipal;
+
+ if (e.principal.subsumes(targetPrincipal)) {
+ // If eventTarget is a window, use it as the targetWindow, otherwise
+ // find the window that owns the eventTarget.
+ let targetWindow = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget : eventTarget.ownerDocument.defaultView;
+
+ eventTarget.dispatchEvent(new targetWindow.CustomEvent("WebChannelMessageToContent", {
+ detail: Cu.cloneInto({
+ id: e.data.id,
+ message: e.data.message,
+ }, targetWindow),
+ }));
+ } else {
+ Cu.reportError("WebChannel message failed. Principal mismatch.");
+ }
+ } else {
+ Cu.reportError("WebChannel message failed. No message data.");
+ }
+});
+
+var AudioPlaybackListener = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+ init() {
+ Services.obs.addObserver(this, "audio-playback", false);
+ Services.obs.addObserver(this, "AudioFocusChanged", false);
+ Services.obs.addObserver(this, "MediaControl", false);
+
+ addMessageListener("AudioPlayback", this);
+ addEventListener("unload", () => {
+ AudioPlaybackListener.uninit();
+ });
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, "audio-playback");
+ Services.obs.removeObserver(this, "AudioFocusChanged");
+ Services.obs.removeObserver(this, "MediaControl");
+
+ removeMessageListener("AudioPlayback", this);
+ },
+
+ handleMediaControlMessage(msg) {
+ let utils = global.content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let suspendTypes = Ci.nsISuspendedTypes;
+ switch (msg) {
+ case "mute":
+ utils.audioMuted = true;
+ break;
+ case "unmute":
+ utils.audioMuted = false;
+ break;
+ case "lostAudioFocus":
+ utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE;
+ break;
+ case "lostAudioFocusTransiently":
+ utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE;
+ break;
+ case "gainAudioFocus":
+ utils.mediaSuspend = suspendTypes.NONE_SUSPENDED;
+ break;
+ case "mediaControlPaused":
+ utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE;
+ break;
+ case "mediaControlStopped":
+ utils.mediaSuspend = suspendTypes.SUSPENDED_STOP_DISPOSABLE;
+ break;
+ case "blockInactivePageMedia":
+ utils.mediaSuspend = suspendTypes.SUSPENDED_BLOCK;
+ break;
+ case "resumeMedia":
+ utils.mediaSuspend = suspendTypes.NONE_SUSPENDED;
+ break;
+ default:
+ dump("Error : wrong media control msg!\n");
+ break;
+ }
+ },
+
+ observe(subject, topic, data) {
+ if (topic === "audio-playback") {
+ if (subject && subject.top == global.content) {
+ let name = "AudioPlayback:";
+ if (data === "block") {
+ name += "Block";
+ } else {
+ name += (data === "active") ? "Start" : "Stop";
+ }
+ sendAsyncMessage(name);
+ }
+ } else if (topic == "AudioFocusChanged" || topic == "MediaControl") {
+ this.handleMediaControlMessage(data);
+ }
+ },
+
+ receiveMessage(msg) {
+ if (msg.name == "AudioPlayback") {
+ this.handleMediaControlMessage(msg.data.type);
+ }
+ },
+};
+AudioPlaybackListener.init();
+
+addMessageListener("Browser:PurgeSessionHistory", function BrowserPurgeHistory() {
+ let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
+ if (!sessionHistory) {
+ return;
+ }
+
+ // place the entry at current index at the end of the history list, so it won't get removed
+ if (sessionHistory.index < sessionHistory.count - 1) {
+ let indexEntry = sessionHistory.getEntryAtIndex(sessionHistory.index, false);
+ sessionHistory.QueryInterface(Components.interfaces.nsISHistoryInternal);
+ indexEntry.QueryInterface(Components.interfaces.nsISHEntry);
+ sessionHistory.addEntry(indexEntry, true);
+ }
+
+ let purge = sessionHistory.count;
+ if (global.content.location.href != "about:blank") {
+ --purge; // Don't remove the page the user's staring at from shistory
+ }
+
+ if (purge > 0) {
+ sessionHistory.PurgeHistory(purge);
+ }
+});
+
+var ViewSelectionSource = {
+ init: function () {
+ addMessageListener("ViewSource:GetSelection", this);
+ },
+
+ receiveMessage: function(message) {
+ if (message.name == "ViewSource:GetSelection") {
+ let selectionDetails;
+ try {
+ selectionDetails = message.objects.target ? this.getMathMLSelection(message.objects.target)
+ : this.getSelection();
+ } finally {
+ sendAsyncMessage("ViewSource:GetSelectionDone", selectionDetails);
+ }
+ }
+ },
+
+ /**
+ * A helper to get a path like FIXptr, but with an array instead of the
+ * "tumbler" notation.
+ * See FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm
+ */
+ getPath: function(ancestor, node) {
+ var n = node;
+ var p = n.parentNode;
+ if (n == ancestor || !p)
+ return null;
+ var path = new Array();
+ if (!path)
+ return null;
+ do {
+ for (var i = 0; i < p.childNodes.length; i++) {
+ if (p.childNodes.item(i) == n) {
+ path.push(i);
+ break;
+ }
+ }
+ n = p;
+ p = n.parentNode;
+ } while (n != ancestor && p);
+ return path;
+ },
+
+ getSelection: function () {
+ // These are markers used to delimit the selection during processing. They
+ // are removed from the final rendering.
+ // We use noncharacter Unicode codepoints to minimize the risk of clashing
+ // with anything that might legitimately be present in the document.
+ // U+FDD0..FDEF <noncharacters>
+ const MARK_SELECTION_START = "\uFDD0";
+ const MARK_SELECTION_END = "\uFDEF";
+
+ var focusedWindow = Services.focus.focusedWindow || content;
+ var selection = focusedWindow.getSelection();
+
+ var range = selection.getRangeAt(0);
+ var ancestorContainer = range.commonAncestorContainer;
+ var doc = ancestorContainer.ownerDocument;
+
+ var startContainer = range.startContainer;
+ var endContainer = range.endContainer;
+ var startOffset = range.startOffset;
+ var endOffset = range.endOffset;
+
+ // let the ancestor be an element
+ var Node = doc.defaultView.Node;
+ if (ancestorContainer.nodeType == Node.TEXT_NODE ||
+ ancestorContainer.nodeType == Node.CDATA_SECTION_NODE)
+ ancestorContainer = ancestorContainer.parentNode;
+
+ // for selectAll, let's use the entire document, including <html>...</html>
+ // @see nsDocumentViewer::SelectAll() for how selectAll is implemented
+ try {
+ if (ancestorContainer == doc.body)
+ ancestorContainer = doc.documentElement;
+ } catch (e) { }
+
+ // each path is a "child sequence" (a.k.a. "tumbler") that
+ // descends from the ancestor down to the boundary point
+ var startPath = this.getPath(ancestorContainer, startContainer);
+ var endPath = this.getPath(ancestorContainer, endContainer);
+
+ // clone the fragment of interest and reset everything to be relative to it
+ // note: it is with the clone that we operate/munge from now on. Also note
+ // that we clone into a data document to prevent images in the fragment from
+ // loading and the like. The use of importNode here, as opposed to adoptNode,
+ // is _very_ important.
+ // XXXbz wish there were a less hacky way to create an untrusted document here
+ var isHTML = (doc.createElement("div").tagName == "DIV");
+ var dataDoc = isHTML ?
+ ancestorContainer.ownerDocument.implementation.createHTMLDocument("") :
+ ancestorContainer.ownerDocument.implementation.createDocument("", "", null);
+ ancestorContainer = dataDoc.importNode(ancestorContainer, true);
+ startContainer = ancestorContainer;
+ endContainer = ancestorContainer;
+
+ // Only bother with the selection if it can be remapped. Don't mess with
+ // leaf elements (such as <isindex>) that secretly use anynomous content
+ // for their display appearance.
+ var canDrawSelection = ancestorContainer.hasChildNodes();
+ var tmpNode;
+ if (canDrawSelection) {
+ var i;
+ for (i = startPath ? startPath.length-1 : -1; i >= 0; i--) {
+ startContainer = startContainer.childNodes.item(startPath[i]);
+ }
+ for (i = endPath ? endPath.length-1 : -1; i >= 0; i--) {
+ endContainer = endContainer.childNodes.item(endPath[i]);
+ }
+
+ // add special markers to record the extent of the selection
+ // note: |startOffset| and |endOffset| are interpreted either as
+ // offsets in the text data or as child indices (see the Range spec)
+ // (here, munging the end point first to keep the start point safe...)
+ if (endContainer.nodeType == Node.TEXT_NODE ||
+ endContainer.nodeType == Node.CDATA_SECTION_NODE) {
+ // do some extra tweaks to try to avoid the view-source output to look like
+ // ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
+ // To get a neat output, the idea here is to remap the end point from:
+ // 1. ...<tag>]... to ...]<tag>...
+ // 2. ...]</tag>... to ...</tag>]...
+ if ((endOffset > 0 && endOffset < endContainer.data.length) ||
+ !endContainer.parentNode || !endContainer.parentNode.parentNode)
+ endContainer.insertData(endOffset, MARK_SELECTION_END);
+ else {
+ tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
+ endContainer = endContainer.parentNode;
+ if (endOffset === 0)
+ endContainer.parentNode.insertBefore(tmpNode, endContainer);
+ else
+ endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling);
+ }
+ }
+ else {
+ tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
+ endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset));
+ }
+
+ if (startContainer.nodeType == Node.TEXT_NODE ||
+ startContainer.nodeType == Node.CDATA_SECTION_NODE) {
+ // do some extra tweaks to try to avoid the view-source output to look like
+ // ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
+ // To get a neat output, the idea here is to remap the start point from:
+ // 1. ...<tag>[... to ...[<tag>...
+ // 2. ...[</tag>... to ...</tag>[...
+ if ((startOffset > 0 && startOffset < startContainer.data.length) ||
+ !startContainer.parentNode || !startContainer.parentNode.parentNode ||
+ startContainer != startContainer.parentNode.lastChild)
+ startContainer.insertData(startOffset, MARK_SELECTION_START);
+ else {
+ tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
+ startContainer = startContainer.parentNode;
+ if (startOffset === 0)
+ startContainer.parentNode.insertBefore(tmpNode, startContainer);
+ else
+ startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling);
+ }
+ }
+ else {
+ tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
+ startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
+ }
+ }
+
+ // now extract and display the syntax highlighted source
+ tmpNode = dataDoc.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ tmpNode.appendChild(ancestorContainer);
+
+ return { uri: (isHTML ? "view-source:data:text/html;charset=utf-8," :
+ "view-source:data:application/xml;charset=utf-8,")
+ + encodeURIComponent(tmpNode.innerHTML),
+ drawSelection: canDrawSelection,
+ baseURI: doc.baseURI };
+ },
+
+ /**
+ * Reformat the source of a MathML node to highlight the node that was targetted.
+ *
+ * @param node
+ * Some element within the fragment of interest.
+ */
+ getMathMLSelection: function(node) {
+ var Node = node.ownerDocument.defaultView.Node;
+ this._lineCount = 0;
+ this._startTargetLine = 0;
+ this._endTargetLine = 0;
+ this._targetNode = node;
+ if (this._targetNode && this._targetNode.nodeType == Node.TEXT_NODE)
+ this._targetNode = this._targetNode.parentNode;
+
+ // walk up the tree to the top-level element (e.g., <math>, <svg>)
+ var topTag = "math";
+ var topNode = this._targetNode;
+ while (topNode && topNode.localName != topTag) {
+ topNode = topNode.parentNode;
+ }
+ if (!topNode)
+ return undefined;
+
+ // serialize
+ const VIEW_SOURCE_CSS = "resource://gre-resources/viewsource.css";
+ const BUNDLE_URL = "chrome://global/locale/viewSource.properties";
+
+ let bundle = Services.strings.createBundle(BUNDLE_URL);
+ var title = bundle.GetStringFromName("viewMathMLSourceTitle");
+ var wrapClass = this.wrapLongLines ? ' class="wrap"' : '';
+ var source =
+ '<!DOCTYPE html>'
+ + '<html>'
+ + '<head><title>' + title + '</title>'
+ + '<link rel="stylesheet" type="text/css" href="' + VIEW_SOURCE_CSS + '">'
+ + '<style type="text/css">'
+ + '#target { border: dashed 1px; background-color: lightyellow; }'
+ + '</style>'
+ + '</head>'
+ + '<body id="viewsource"' + wrapClass
+ + ' onload="document.title=\''+title+'\'; document.getElementById(\'target\').scrollIntoView(true)">'
+ + '<pre>'
+ + this.getOuterMarkup(topNode, 0)
+ + '</pre></body></html>'
+ ; // end
+
+ return { uri: "data:text/html;charset=utf-8," + encodeURIComponent(source),
+ drawSelection: false, baseURI: node.ownerDocument.baseURI };
+ },
+
+ get wrapLongLines() {
+ return Services.prefs.getBoolPref("view_source.wrap_long_lines");
+ },
+
+ getInnerMarkup: function(node, indent) {
+ var str = '';
+ for (var i = 0; i < node.childNodes.length; i++) {
+ str += this.getOuterMarkup(node.childNodes.item(i), indent);
+ }
+ return str;
+ },
+
+ getOuterMarkup: function(node, indent) {
+ var Node = node.ownerDocument.defaultView.Node;
+ var newline = "";
+ var padding = "";
+ var str = "";
+ if (node == this._targetNode) {
+ this._startTargetLine = this._lineCount;
+ str += '</pre><pre id="target">';
+ }
+
+ switch (node.nodeType) {
+ case Node.ELEMENT_NODE: // Element
+ // to avoid the wide gap problem, '\n' is not emitted on the first
+ // line and the lines before & after the <pre id="target">...</pre>
+ if (this._lineCount > 0 &&
+ this._lineCount != this._startTargetLine &&
+ this._lineCount != this._endTargetLine) {
+ newline = "\n";
+ }
+ this._lineCount++;
+ for (var k = 0; k < indent; k++) {
+ padding += " ";
+ }
+ str += newline + padding
+ + '&lt;<span class="start-tag">' + node.nodeName + '</span>';
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes.item(i);
+ if (attr.nodeName.match(/^[-_]moz/)) {
+ continue;
+ }
+ str += ' <span class="attribute-name">'
+ + attr.nodeName
+ + '</span>=<span class="attribute-value">"'
+ + this.unicodeToEntity(attr.nodeValue)
+ + '"</span>';
+ }
+ if (!node.hasChildNodes()) {
+ str += "/&gt;";
+ }
+ else {
+ str += "&gt;";
+ var oldLine = this._lineCount;
+ str += this.getInnerMarkup(node, indent + 2);
+ if (oldLine == this._lineCount) {
+ newline = "";
+ padding = "";
+ }
+ else {
+ newline = (this._lineCount == this._endTargetLine) ? "" : "\n";
+ this._lineCount++;
+ }
+ str += newline + padding
+ + '&lt;/<span class="end-tag">' + node.nodeName + '</span>&gt;';
+ }
+ break;
+ case Node.TEXT_NODE: // Text
+ var tmp = node.nodeValue;
+ tmp = tmp.replace(/(\n|\r|\t)+/g, " ");
+ tmp = tmp.replace(/^ +/, "");
+ tmp = tmp.replace(/ +$/, "");
+ if (tmp.length != 0) {
+ str += '<span class="text">' + this.unicodeToEntity(tmp) + '</span>';
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (node == this._targetNode) {
+ this._endTargetLine = this._lineCount;
+ str += '</pre><pre>';
+ }
+ return str;
+ },
+
+ unicodeToEntity: function(text) {
+ const charTable = {
+ '&': '&amp;<span class="entity">amp;</span>',
+ '<': '&amp;<span class="entity">lt;</span>',
+ '>': '&amp;<span class="entity">gt;</span>',
+ '"': '&amp;<span class="entity">quot;</span>'
+ };
+
+ function charTableLookup(letter) {
+ return charTable[letter];
+ }
+
+ function convertEntity(letter) {
+ try {
+ var unichar = this._entityConverter
+ .ConvertToEntity(letter, entityVersion);
+ var entity = unichar.substring(1); // extract '&'
+ return '&amp;<span class="entity">' + entity + '</span>';
+ } catch (ex) {
+ return letter;
+ }
+ }
+
+ if (!this._entityConverter) {
+ try {
+ this._entityConverter = Cc["@mozilla.org/intl/entityconverter;1"]
+ .createInstance(Ci.nsIEntityConverter);
+ } catch (e) { }
+ }
+
+ const entityVersion = Ci.nsIEntityConverter.entityW3C;
+
+ var str = text;
+
+ // replace chars in our charTable
+ str = str.replace(/[<>&"]/g, charTableLookup);
+
+ // replace chars > 0x7f via nsIEntityConverter
+ str = str.replace(/[^\0-\u007f]/g, convertEntity);
+
+ return str;
+ }
+};
+
+ViewSelectionSource.init();
+
+addEventListener("MozApplicationManifest", function(e) {
+ let doc = e.target;
+ let info = {
+ uri: doc.documentURI,
+ characterSet: doc.characterSet,
+ manifest: doc.documentElement.getAttribute("manifest"),
+ principal: doc.nodePrincipal,
+ };
+ sendAsyncMessage("MozApplicationManifest", info);
+}, false);
+
+let AutoCompletePopup = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup]),
+
+ _connected: false,
+
+ MESSAGES: [
+ "FormAutoComplete:HandleEnter",
+ "FormAutoComplete:PopupClosed",
+ "FormAutoComplete:PopupOpened",
+ "FormAutoComplete:RequestFocus",
+ ],
+
+ init: function() {
+ addEventListener("unload", this);
+ addEventListener("DOMContentLoaded", this);
+
+ for (let messageName of this.MESSAGES) {
+ addMessageListener(messageName, this);
+ }
+
+ this._input = null;
+ this._popupOpen = false;
+ },
+
+ destroy: function() {
+ if (this._connected) {
+ let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
+ .getService(Ci.nsIFormFillController);
+ controller.detachFromBrowser(docShell);
+ this._connected = false;
+ }
+
+ removeEventListener("unload", this);
+ removeEventListener("DOMContentLoaded", this);
+
+ for (let messageName of this.MESSAGES) {
+ removeMessageListener(messageName, this);
+ }
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "DOMContentLoaded": {
+ removeEventListener("DOMContentLoaded", this);
+
+ // We need to wait for a content viewer to be available
+ // before we can attach our AutoCompletePopup handler,
+ // since nsFormFillController assumes one will exist
+ // when we call attachToBrowser.
+
+ // Hook up the form fill autocomplete controller.
+ let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
+ .getService(Ci.nsIFormFillController);
+ controller.attachToBrowser(docShell,
+ this.QueryInterface(Ci.nsIAutoCompletePopup));
+ this._connected = true;
+ break;
+ }
+
+ case "unload": {
+ this.destroy();
+ break;
+ }
+ }
+ },
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "FormAutoComplete:HandleEnter": {
+ this.selectedIndex = message.data.selectedIndex;
+
+ let controller = Cc["@mozilla.org/autocomplete/controller;1"]
+ .getService(Ci.nsIAutoCompleteController);
+ controller.handleEnter(message.data.isPopupSelection);
+ break;
+ }
+
+ case "FormAutoComplete:PopupClosed": {
+ this._popupOpen = false;
+ break;
+ }
+
+ case "FormAutoComplete:PopupOpened": {
+ this._popupOpen = true;
+ break;
+ }
+
+ case "FormAutoComplete:RequestFocus": {
+ if (this._input) {
+ this._input.focus();
+ }
+ break;
+ }
+ }
+ },
+
+ get input () { return this._input; },
+ get overrideValue () { return null; },
+ set selectedIndex (index) {
+ sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
+ },
+ get selectedIndex () {
+ // selectedIndex getter must be synchronous because we need the
+ // correct value when the controller is in controller::HandleEnter.
+ // We can't easily just let the parent inform us the new value every
+ // time it changes because not every action that can change the
+ // selectedIndex is trivial to catch (e.g. moving the mouse over the
+ // list).
+ return sendSyncMessage("FormAutoComplete:GetSelectedIndex", {});
+ },
+ get popupOpen () {
+ return this._popupOpen;
+ },
+
+ openAutocompletePopup: function (input, element) {
+ if (this._popupOpen || !input) {
+ return;
+ }
+
+ let rect = BrowserUtils.getElementBoundingScreenRect(element);
+ let window = element.ownerDocument.defaultView;
+ let dir = window.getComputedStyle(element).direction;
+ let results = this.getResultsFromController(input);
+
+ sendAsyncMessage("FormAutoComplete:MaybeOpenPopup",
+ { results, rect, dir });
+ this._input = input;
+ },
+
+ closePopup: function () {
+ // We set this here instead of just waiting for the
+ // PopupClosed message to do it so that we don't end
+ // up in a state where the content thinks that a popup
+ // is open when it isn't (or soon won't be).
+ this._popupOpen = false;
+ sendAsyncMessage("FormAutoComplete:ClosePopup", {});
+ },
+
+ invalidate: function () {
+ if (this._popupOpen) {
+ let results = this.getResultsFromController(this._input);
+ sendAsyncMessage("FormAutoComplete:Invalidate", { results });
+ }
+ },
+
+ selectBy: function(reverse, page) {
+ this._index = sendSyncMessage("FormAutoComplete:SelectBy", {
+ reverse: reverse,
+ page: page
+ });
+ },
+
+ getResultsFromController(inputField) {
+ let results = [];
+
+ if (!inputField) {
+ return results;
+ }
+
+ let controller = inputField.controller;
+ if (!(controller instanceof Ci.nsIAutoCompleteController)) {
+ return results;
+ }
+
+ for (let i = 0; i < controller.matchCount; ++i) {
+ let result = {};
+ result.value = controller.getValueAt(i);
+ result.label = controller.getLabelAt(i);
+ result.comment = controller.getCommentAt(i);
+ result.style = controller.getStyleAt(i);
+ result.image = controller.getImageAt(i);
+ results.push(result);
+ }
+
+ return results;
+ },
+}
+
+AutoCompletePopup.init();
+
+/**
+ * DateTimePickerListener is the communication channel between the input box
+ * (content) for date/time input types and its picker (chrome).
+ */
+let DateTimePickerListener = {
+ /**
+ * On init, just listen for the event to open the picker, once the picker is
+ * opened, we'll listen for update and close events.
+ */
+ init: function() {
+ addEventListener("MozOpenDateTimePicker", this);
+ this._inputElement = null;
+
+ addEventListener("unload", () => {
+ this.uninit();
+ });
+ },
+
+ uninit: function() {
+ removeEventListener("MozOpenDateTimePicker", this);
+ this._inputElement = null;
+ },
+
+ /**
+ * Cleanup function called when picker is closed.
+ */
+ close: function() {
+ this.removeListeners();
+ this._inputElement.setDateTimePickerState(false);
+ this._inputElement = null;
+ },
+
+ /**
+ * Called after picker is opened to start listening for input box update
+ * events.
+ */
+ addListeners: function() {
+ addEventListener("MozUpdateDateTimePicker", this);
+ addEventListener("MozCloseDateTimePicker", this);
+ addEventListener("pagehide", this);
+
+ addMessageListener("FormDateTime:PickerValueChanged", this);
+ addMessageListener("FormDateTime:PickerClosed", this);
+ },
+
+ /**
+ * Stop listeneing for events when picker is closed.
+ */
+ removeListeners: function() {
+ removeEventListener("MozUpdateDateTimePicker", this);
+ removeEventListener("MozCloseDateTimePicker", this);
+ removeEventListener("pagehide", this);
+
+ removeMessageListener("FormDateTime:PickerValueChanged", this);
+ removeMessageListener("FormDateTime:PickerClosed", this);
+ },
+
+ /**
+ * Helper function that returns the CSS direction property of the element.
+ */
+ getComputedDirection: function(aElement) {
+ return aElement.ownerDocument.defaultView.getComputedStyle(aElement)
+ .getPropertyValue("direction");
+ },
+
+ /**
+ * Helper function that returns the rect of the element, which is the position
+ * relative to the left/top of the content area.
+ */
+ getBoundingContentRect: function(aElement) {
+ return BrowserUtils.getElementBoundingRect(aElement);
+ },
+
+ getTimePickerPref: function() {
+ return Services.prefs.getBoolPref("dom.forms.datetime.timepicker");
+ },
+
+ /**
+ * nsIMessageListener.
+ */
+ receiveMessage: function(aMessage) {
+ switch (aMessage.name) {
+ case "FormDateTime:PickerClosed": {
+ this.close();
+ break;
+ }
+ case "FormDateTime:PickerValueChanged": {
+ this._inputElement.updateDateTimeInputBox(aMessage.data);
+ break;
+ }
+ default:
+ break;
+ }
+ },
+
+ /**
+ * nsIDOMEventListener, for chrome events sent by the input element and other
+ * DOM events.
+ */
+ handleEvent: function(aEvent) {
+ switch (aEvent.type) {
+ case "MozOpenDateTimePicker": {
+ // Time picker is disabled when preffed off
+ if (!(aEvent.originalTarget instanceof content.HTMLInputElement) ||
+ (aEvent.originalTarget.type == "time" && !this.getTimePickerPref())) {
+ return;
+ }
+
+ if (this._inputElement) {
+ // This happens when we're trying to open a picker when another picker
+ // is still open. We ignore this request to let the first picker
+ // close gracefully.
+ return;
+ }
+
+ this._inputElement = aEvent.originalTarget;
+ this._inputElement.setDateTimePickerState(true);
+ this.addListeners();
+
+ let value = this._inputElement.getDateTimeInputBoxValue();
+ sendAsyncMessage("FormDateTime:OpenPicker", {
+ rect: this.getBoundingContentRect(this._inputElement),
+ dir: this.getComputedDirection(this._inputElement),
+ type: this._inputElement.type,
+ detail: {
+ // Pass partial value if it's available, otherwise pass input
+ // element's value.
+ value: Object.keys(value).length > 0 ? value
+ : this._inputElement.value,
+ min: this._inputElement.getMinimum(),
+ max: this._inputElement.getMaximum(),
+ step: this._inputElement.getStep(),
+ stepBase: this._inputElement.getStepBase(),
+ },
+ });
+ break;
+ }
+ case "MozUpdateDateTimePicker": {
+ let value = this._inputElement.getDateTimeInputBoxValue();
+ value.type = this._inputElement.type;
+ sendAsyncMessage("FormDateTime:UpdatePicker", { value });
+ break;
+ }
+ case "MozCloseDateTimePicker": {
+ sendAsyncMessage("FormDateTime:ClosePicker");
+ this.close();
+ break;
+ }
+ case "pagehide": {
+ if (this._inputElement &&
+ this._inputElement.ownerDocument == aEvent.target) {
+ sendAsyncMessage("FormDateTime:ClosePicker");
+ this.close();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ },
+}
+
+DateTimePickerListener.init();
diff --git a/components/global/content/buildconfig.html b/components/global/content/buildconfig.html
new file mode 100644
index 000000000..322208189
--- /dev/null
+++ b/components/global/content/buildconfig.html
@@ -0,0 +1,62 @@
+<!DOCTYPE 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/.
+#
+#filter substitution
+#include @TOPOBJDIR@/source-repo.h
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width; user-scalable=false;">
+ <title>about:buildconfig</title>
+ <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css">
+ <style type="text/css">
+ th { text-align: start; }
+ h2 { margin-top: 1.5em; }
+ th, td { vertical-align: top; }
+ </style>
+</head>
+<body class="aboutPageWideContainer">
+<h1>about:buildconfig</h1>
+#ifdef MOZ_SOURCE_URL
+<h2>Source</h2>
+<p>Built from <a href="@MOZ_SOURCE_URL@">@MOZ_SOURCE_URL@</a></p>
+#endif
+<h2>Build platform</h2>
+<table>
+ <tbody>
+ <tr>
+ <th>target</th>
+ </tr>
+ <tr>
+ <td>@target@</td>
+ </tr>
+ </tbody>
+</table>
+<h2>Build tools</h2>
+<table>
+ <tbody>
+ <tr>
+ <th>Compiler</th>
+ <th>Version</th>
+ <th>Compiler flags</th>
+ </tr>
+ <tr>
+ <td>@CC@</td>
+ <td>@CC_VERSION@</td>
+ <td>@CFLAGS@</td>
+ </tr>
+ <tr>
+ <td>@CXX@</td>
+ <td>@CC_VERSION@</td>
+#ifndef BUILD_FASTER
+ <td>@CXXFLAGS@ @CPPFLAGS@</td>
+#endif
+ </tr>
+ </tbody>
+</table>
+<h2>Configure options</h2>
+<p>@MOZ_CONFIGURE_OPTIONS@</p>
+</body>
+</html>
diff --git a/components/global/content/contentAreaUtils.js b/components/global/content/contentAreaUtils.js
new file mode 100644
index 000000000..17e463325
--- /dev/null
+++ b/components/global/content/contentAreaUtils.js
@@ -0,0 +1,1297 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadLastDir",
+ "resource://gre/modules/DownloadLastDir.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+
+var ContentAreaUtils = {
+
+ // this is for backwards compatibility.
+ get ioService() {
+ return Services.io;
+ },
+
+ get stringBundle() {
+ delete this.stringBundle;
+ return this.stringBundle =
+ Services.strings.createBundle("chrome://global/locale/contentAreaCommands.properties");
+ }
+}
+
+function urlSecurityCheck(aURL, aPrincipal, aFlags)
+{
+ return BrowserUtils.urlSecurityCheck(aURL, aPrincipal, aFlags);
+}
+
+/**
+ * Determine whether or not a given focused DOMWindow is in the content area.
+ **/
+function isContentFrame(aFocusedWindow)
+{
+ if (!aFocusedWindow)
+ return false;
+
+ return (aFocusedWindow.top == window.content);
+}
+
+function forbidCPOW(arg, func, argname)
+{
+ if (arg && (typeof(arg) == "object" || typeof(arg) == "function") &&
+ Components.utils.isCrossProcessWrapper(arg)) {
+ throw new Error(`no CPOWs allowed for argument ${argname} to ${func}`);
+ }
+}
+
+// Clientele: (Make sure you don't break any of these)
+// - File -> Save Page/Frame As...
+// - Context -> Save Page/Frame As...
+// - Context -> Save Link As...
+// - Alt-Click links in web pages
+// - Alt-Click links in the UI
+//
+// Try saving each of these types:
+// - A complete webpage using File->Save Page As, and Context->Save Page As
+// - A webpage as HTML only using the above methods
+// - A webpage as Text only using the above methods
+// - An image with an extension (e.g. .jpg) in its file name, using
+// Context->Save Image As...
+// - An image without an extension (e.g. a banner ad on cnn.com) using
+// the above method.
+// - A linked document using Save Link As...
+// - A linked document using Alt-click Save Link As...
+//
+function saveURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
+ aSkipPrompt, aReferrer, aSourceDocument, aIsContentWindowPrivate)
+{
+ forbidCPOW(aURL, "saveURL", "aURL");
+ forbidCPOW(aReferrer, "saveURL", "aReferrer");
+ // Allow aSourceDocument to be a CPOW.
+
+ internalSave(aURL, null, aFileName, null, null, aShouldBypassCache,
+ aFilePickerTitleKey, null, aReferrer, aSourceDocument,
+ aSkipPrompt, null, aIsContentWindowPrivate);
+}
+
+// Just like saveURL, but will get some info off the image before
+// calling internalSave
+// Clientele: (Make sure you don't break any of these)
+// - Context -> Save Image As...
+const imgICache = Components.interfaces.imgICache;
+const nsISupportsCString = Components.interfaces.nsISupportsCString;
+
+/**
+ * Offers to save an image URL to the file system.
+ *
+ * @param aURL (string)
+ * The URL of the image to be saved.
+ * @param aFileName (string)
+ * The suggested filename for the saved file.
+ * @param aFilePickerTitleKey (string, optional)
+ * Localized string key for an alternate title for the file
+ * picker. If set to null, this will default to something sensible.
+ * @param aShouldBypassCache (bool)
+ * If true, the image will always be retrieved from the server instead
+ * of the network or image caches.
+ * @param aSkipPrompt (bool)
+ * If true, we will attempt to save the file with the suggested
+ * filename to the default downloads folder without showing the
+ * file picker.
+ * @param aReferrer (nsIURI, optional)
+ * The referrer URI object (not a URL string) to use, or null
+ * if no referrer should be sent.
+ * @param aDoc (nsIDocument, deprecated, optional)
+ * The content document that the save is being initiated from. If this
+ * is omitted, then aIsContentWindowPrivate must be provided.
+ * @param aContentType (string, optional)
+ * The content type of the image.
+ * @param aContentDisp (string, optional)
+ * The content disposition of the image.
+ * @param aIsContentWindowPrivate (bool)
+ * Whether or not the containing window is in private browsing mode.
+ * Does not need to be provided is aDoc is passed.
+ */
+function saveImageURL(aURL, aFileName, aFilePickerTitleKey, aShouldBypassCache,
+ aSkipPrompt, aReferrer, aDoc, aContentType, aContentDisp,
+ aIsContentWindowPrivate)
+{
+ forbidCPOW(aURL, "saveImageURL", "aURL");
+ forbidCPOW(aReferrer, "saveImageURL", "aReferrer");
+
+ if (aDoc && aIsContentWindowPrivate == undefined) {
+ if (Components.utils.isCrossProcessWrapper(aDoc)) {
+ Deprecated.warning("saveImageURL should not be passed document CPOWs. " +
+ "The caller should pass in the content type and " +
+ "disposition themselves",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1243643");
+ }
+ // This will definitely not work for in-browser code or multi-process compatible
+ // add-ons due to bug 1233497, which makes unsafe CPOW usage throw by default.
+ Deprecated.warning("saveImageURL should be passed the private state of " +
+ "the containing window.",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1243643");
+ aIsContentWindowPrivate =
+ PrivateBrowsingUtils.isContentWindowPrivate(aDoc.defaultView);
+ }
+
+ // We'd better have the private state by now.
+ if (aIsContentWindowPrivate == undefined) {
+ throw new Error("saveImageURL couldn't compute private state of content window");
+ }
+
+ if (!aShouldBypassCache && (aDoc && !Components.utils.isCrossProcessWrapper(aDoc)) &&
+ (!aContentType && !aContentDisp)) {
+ try {
+ var imageCache = Components.classes["@mozilla.org/image/tools;1"]
+ .getService(Components.interfaces.imgITools)
+ .getImgCacheForDocument(aDoc);
+ var props =
+ imageCache.findEntryProperties(makeURI(aURL, getCharsetforSave(null)), aDoc);
+ if (props) {
+ aContentType = props.get("type", nsISupportsCString);
+ aContentDisp = props.get("content-disposition", nsISupportsCString);
+ }
+ } catch (e) {
+ // Failure to get type and content-disposition off the image is non-fatal
+ }
+ }
+
+ internalSave(aURL, null, aFileName, aContentDisp, aContentType,
+ aShouldBypassCache, aFilePickerTitleKey, null, aReferrer,
+ null, aSkipPrompt, null, aIsContentWindowPrivate);
+}
+
+// This is like saveDocument, but takes any browser/frame-like element
+// (nsIFrameLoaderOwner) and saves the current document inside it,
+// whether in-process or out-of-process.
+function saveBrowser(aBrowser, aSkipPrompt, aOuterWindowID=0)
+{
+ if (!aBrowser) {
+ throw "Must have a browser when calling saveBrowser";
+ }
+ let persistable = aBrowser.QueryInterface(Ci.nsIFrameLoaderOwner)
+ .frameLoader
+ .QueryInterface(Ci.nsIWebBrowserPersistable);
+ let stack = Components.stack.caller;
+ persistable.startPersistence(aOuterWindowID, {
+ onDocumentReady: function (document) {
+ saveDocument(document, aSkipPrompt);
+ },
+ onError: function (status) {
+ throw new Components.Exception("saveBrowser failed asynchronously in startPersistence",
+ status, stack);
+ }
+ });
+}
+
+// Saves a document; aDocument can be an nsIWebBrowserPersistDocument
+// (see saveBrowser, above) or an nsIDOMDocument.
+//
+// aDocument can also be a CPOW for a remote nsIDOMDocument, in which
+// case "save as" modes that serialize the document's DOM are
+// unavailable. This is a temporary measure for the "Save Frame As"
+// command (bug 1141337) and pre-e10s add-ons.
+function saveDocument(aDocument, aSkipPrompt)
+{
+ const Ci = Components.interfaces;
+
+ if (!aDocument)
+ throw "Must have a document when calling saveDocument";
+
+ let contentDisposition = null;
+ let cacheKeyInt = null;
+
+ if (aDocument instanceof Ci.nsIWebBrowserPersistDocument) {
+ // nsIWebBrowserPersistDocument exposes these directly.
+ contentDisposition = aDocument.contentDisposition;
+ cacheKeyInt = aDocument.cacheKey;
+ } else if (aDocument instanceof Ci.nsIDOMDocument) {
+ // Otherwise it's an actual nsDocument (and possibly a CPOW).
+ // We want to use cached data because the document is currently visible.
+ let ifreq =
+ aDocument.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor);
+
+ try {
+ contentDisposition =
+ ifreq.getInterface(Ci.nsIDOMWindowUtils)
+ .getDocumentMetadata("content-disposition");
+ } catch (ex) {
+ // Failure to get a content-disposition is ok
+ }
+
+ try {
+ let shEntry =
+ ifreq.getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIWebPageDescriptor)
+ .currentDescriptor
+ .QueryInterface(Ci.nsISHEntry);
+
+ let cacheKey = shEntry.cacheKey
+ .QueryInterface(Ci.nsISupportsPRUint32)
+ .data;
+ // cacheKey might be a CPOW, which can't be passed to native
+ // code, but the data attribute is just a number.
+ cacheKeyInt = cacheKey.data;
+ } catch (ex) {
+ // We might not find it in the cache. Oh, well.
+ }
+ }
+
+ // Convert the cacheKey back into an XPCOM object.
+ let cacheKey = null;
+ if (cacheKeyInt) {
+ cacheKey = Cc["@mozilla.org/supports-PRUint32;1"]
+ .createInstance(Ci.nsISupportsPRUint32);
+ cacheKey.data = cacheKeyInt;
+ }
+
+ internalSave(aDocument.documentURI, aDocument, null, contentDisposition,
+ aDocument.contentType, false, null, null,
+ aDocument.referrer ? makeURI(aDocument.referrer) : null,
+ aDocument, aSkipPrompt, cacheKey);
+}
+
+function DownloadListener(win, transfer) {
+ function makeClosure(name) {
+ return function() {
+ transfer[name].apply(transfer, arguments);
+ }
+ }
+
+ this.window = win;
+
+ // Now... we need to forward all calls to our transfer
+ for (var i in transfer) {
+ if (i != "QueryInterface")
+ this[i] = makeClosure(i);
+ }
+}
+
+DownloadListener.prototype = {
+ QueryInterface: function dl_qi(aIID)
+ {
+ if (aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
+ aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
+ aIID.equals(Components.interfaces.nsISupports)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function dl_gi(aIID)
+ {
+ if (aIID.equals(Components.interfaces.nsIAuthPrompt) ||
+ aIID.equals(Components.interfaces.nsIAuthPrompt2)) {
+ var ww =
+ Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Components.interfaces.nsIPromptFactory);
+ return ww.getPrompt(this.window, aIID);
+ }
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+}
+
+const kSaveAsType_Complete = 0; // Save document with attached objects.
+XPCOMUtils.defineConstant(this, "kSaveAsType_Complete", 0);
+// const kSaveAsType_URL = 1; // Save document or URL by itself.
+const kSaveAsType_Text = 2; // Save document, converting to plain text.
+XPCOMUtils.defineConstant(this, "kSaveAsType_Text", kSaveAsType_Text);
+
+/**
+ * internalSave: Used when saving a document or URL.
+ *
+ * If aChosenData is null, this method:
+ * - Determines a local target filename to use
+ * - Prompts the user to confirm the destination filename and save mode
+ * (aContentType affects this)
+ * - [Note] This process involves the parameters aURL, aReferrer (to determine
+ * how aURL was encoded), aDocument, aDefaultFileName, aFilePickerTitleKey,
+ * and aSkipPrompt.
+ *
+ * If aChosenData is non-null, this method:
+ * - Uses the provided source URI and save file name
+ * - Saves the document as complete DOM if possible (aDocument present and
+ * right aContentType)
+ * - [Note] The parameters aURL, aDefaultFileName, aFilePickerTitleKey, and
+ * aSkipPrompt are ignored.
+ *
+ * In any case, this method:
+ * - Creates a 'Persist' object (which will perform the saving in the
+ * background) and then starts it.
+ * - [Note] This part of the process only involves the parameters aDocument,
+ * aShouldBypassCache and aReferrer. The source, the save name and the save
+ * mode are the ones determined previously.
+ *
+ * @param aURL
+ * The String representation of the URL of the document being saved
+ * @param aDocument
+ * The document to be saved
+ * @param aDefaultFileName
+ * The caller-provided suggested filename if we don't
+ * find a better one
+ * @param aContentDisposition
+ * The caller-provided content-disposition header to use.
+ * @param aContentType
+ * The caller-provided content-type to use
+ * @param aShouldBypassCache
+ * If true, the document will always be refetched from the server
+ * @param aFilePickerTitleKey
+ * Alternate title for the file picker
+ * @param aChosenData
+ * If non-null this contains an instance of object AutoChosen (see below)
+ * which holds pre-determined data so that the user does not need to be
+ * prompted for a target filename.
+ * @param aReferrer
+ * the referrer URI object (not URL string) to use, or null
+ * if no referrer should be sent.
+ * @param aInitiatingDocument [optional]
+ * The document from which the save was initiated.
+ * If this is omitted then aIsContentWindowPrivate has to be provided.
+ * @param aSkipPrompt [optional]
+ * If set to true, we will attempt to save the file to the
+ * default downloads folder without prompting.
+ * @param aCacheKey [optional]
+ * If set will be passed to saveURI. See nsIWebBrowserPersist for
+ * allowed values.
+ * @param aIsContentWindowPrivate [optional]
+ * This parameter is provided when the aInitiatingDocument is not a
+ * real document object. Stores whether aInitiatingDocument.defaultView
+ * was private or not.
+ */
+function internalSave(aURL, aDocument, aDefaultFileName, aContentDisposition,
+ aContentType, aShouldBypassCache, aFilePickerTitleKey,
+ aChosenData, aReferrer, aInitiatingDocument, aSkipPrompt,
+ aCacheKey, aIsContentWindowPrivate)
+{
+ forbidCPOW(aURL, "internalSave", "aURL");
+ forbidCPOW(aReferrer, "internalSave", "aReferrer");
+ forbidCPOW(aCacheKey, "internalSave", "aCacheKey");
+ // Allow aInitiatingDocument to be a CPOW.
+
+ if (aSkipPrompt == undefined)
+ aSkipPrompt = false;
+
+ if (aCacheKey == undefined)
+ aCacheKey = null;
+
+ // Note: aDocument == null when this code is used by save-link-as...
+ var saveMode = GetSaveModeForContentType(aContentType, aDocument);
+
+ var file, sourceURI, saveAsType;
+ // Find the URI object for aURL and the FileName/Extension to use when saving.
+ // FileName/Extension will be ignored if aChosenData supplied.
+ if (aChosenData) {
+ file = aChosenData.file;
+ sourceURI = aChosenData.uri;
+ saveAsType = kSaveAsType_Complete;
+
+ continueSave();
+ } else {
+ var charset = null;
+ if (aDocument)
+ charset = aDocument.characterSet;
+ else if (aReferrer)
+ charset = aReferrer.originCharset;
+ var fileInfo = new FileInfo(aDefaultFileName);
+ initFileInfo(fileInfo, aURL, charset, aDocument,
+ aContentType, aContentDisposition);
+ sourceURI = fileInfo.uri;
+
+ var fpParams = {
+ fpTitleKey: aFilePickerTitleKey,
+ fileInfo: fileInfo,
+ contentType: aContentType,
+ saveMode: saveMode,
+ saveAsType: kSaveAsType_Complete,
+ file: file
+ };
+
+ // Find a URI to use for determining last-downloaded-to directory
+ let relatedURI = aReferrer || sourceURI;
+
+ promiseTargetFile(fpParams, aSkipPrompt, relatedURI).then(aDialogAccepted => {
+ if (!aDialogAccepted)
+ return;
+
+ saveAsType = fpParams.saveAsType;
+ file = fpParams.file;
+
+ continueSave();
+ }).then(null, Components.utils.reportError);
+ }
+
+ function continueSave() {
+ // XXX We depend on the following holding true in appendFiltersForContentType():
+ // If we should save as a complete page, the saveAsType is kSaveAsType_Complete.
+ // If we should save as text, the saveAsType is kSaveAsType_Text.
+ var useSaveDocument = aDocument &&
+ (((saveMode & SAVEMODE_COMPLETE_DOM) && (saveAsType == kSaveAsType_Complete)) ||
+ ((saveMode & SAVEMODE_COMPLETE_TEXT) && (saveAsType == kSaveAsType_Text)));
+ // If we're saving a document, and are saving either in complete mode or
+ // as converted text, pass the document to the web browser persist component.
+ // If we're just saving the HTML (second option in the list), send only the URI.
+ let nonCPOWDocument =
+ aDocument && !Components.utils.isCrossProcessWrapper(aDocument);
+
+ let isPrivate = aIsContentWindowPrivate;
+ if (isPrivate === undefined) {
+ isPrivate = aInitiatingDocument instanceof Components.interfaces.nsIDOMDocument
+ ? PrivateBrowsingUtils.isContentWindowPrivate(aInitiatingDocument.defaultView)
+ : aInitiatingDocument.isPrivate;
+ }
+
+ var persistArgs = {
+ sourceURI : sourceURI,
+ sourceReferrer : aReferrer,
+ sourceDocument : useSaveDocument ? aDocument : null,
+ targetContentType : (saveAsType == kSaveAsType_Text) ? "text/plain" : null,
+ targetFile : file,
+ sourceCacheKey : aCacheKey,
+ sourcePostData : nonCPOWDocument ? getPostData(aDocument) : null,
+ bypassCache : aShouldBypassCache,
+ isPrivate : isPrivate,
+ };
+
+ // Start the actual save process
+ internalPersist(persistArgs);
+ }
+}
+
+/**
+ * internalPersist: Creates a 'Persist' object (which will perform the saving
+ * in the background) and then starts it.
+ *
+ * @param persistArgs.sourceURI
+ * The nsIURI of the document being saved
+ * @param persistArgs.sourceCacheKey [optional]
+ * If set will be passed to saveURI
+ * @param persistArgs.sourceDocument [optional]
+ * The document to be saved, or null if not saving a complete document
+ * @param persistArgs.sourceReferrer
+ * Required and used only when persistArgs.sourceDocument is NOT present,
+ * the nsIURI of the referrer to use, or null if no referrer should be
+ * sent.
+ * @param persistArgs.sourcePostData
+ * Required and used only when persistArgs.sourceDocument is NOT present,
+ * represents the POST data to be sent along with the HTTP request, and
+ * must be null if no POST data should be sent.
+ * @param persistArgs.targetFile
+ * The nsIFile of the file to create
+ * @param persistArgs.targetContentType
+ * Required and used only when persistArgs.sourceDocument is present,
+ * determines the final content type of the saved file, or null to use
+ * the same content type as the source document. Currently only
+ * "text/plain" is meaningful.
+ * @param persistArgs.bypassCache
+ * If true, the document will always be refetched from the server
+ * @param persistArgs.isPrivate
+ * Indicates whether this is taking place in a private browsing context.
+ */
+function internalPersist(persistArgs)
+{
+ var persist = makeWebBrowserPersist();
+
+ // Calculate persist flags.
+ const nsIWBP = Components.interfaces.nsIWebBrowserPersist;
+ const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ nsIWBP.PERSIST_FLAGS_FORCE_ALLOW_COOKIES;
+ if (persistArgs.bypassCache)
+ persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_BYPASS_CACHE;
+ else
+ persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
+
+ // Leave it to WebBrowserPersist to discover the encoding type (or lack thereof):
+ persist.persistFlags |= nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
+
+ // Find the URI associated with the target file
+ var targetFileURL = makeFileURI(persistArgs.targetFile);
+
+ // Create download and initiate it (below)
+ var tr = Components.classes["@mozilla.org/transfer;1"].createInstance(Components.interfaces.nsITransfer);
+ tr.init(persistArgs.sourceURI,
+ targetFileURL, "", null, null, null, persist, persistArgs.isPrivate);
+ persist.progressListener = new DownloadListener(window, tr);
+
+ if (persistArgs.sourceDocument) {
+ // Saving a Document, not a URI:
+ var filesFolder = null;
+ if (persistArgs.targetContentType != "text/plain") {
+ // Create the local directory into which to save associated files.
+ filesFolder = persistArgs.targetFile.clone();
+
+ var nameWithoutExtension = getFileBaseName(filesFolder.leafName);
+ var filesFolderLeafName =
+ ContentAreaUtils.stringBundle
+ .formatStringFromName("filesFolder", [nameWithoutExtension], 1);
+
+ filesFolder.leafName = filesFolderLeafName;
+ }
+
+ var encodingFlags = 0;
+ if (persistArgs.targetContentType == "text/plain") {
+ encodingFlags |= nsIWBP.ENCODE_FLAGS_FORMATTED;
+ encodingFlags |= nsIWBP.ENCODE_FLAGS_ABSOLUTE_LINKS;
+ encodingFlags |= nsIWBP.ENCODE_FLAGS_NOFRAMES_CONTENT;
+ }
+ else {
+ encodingFlags |= nsIWBP.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES;
+ }
+
+ const kWrapColumn = 80;
+ persist.saveDocument(persistArgs.sourceDocument, targetFileURL, filesFolder,
+ persistArgs.targetContentType, encodingFlags, kWrapColumn);
+ } else {
+ persist.savePrivacyAwareURI(persistArgs.sourceURI,
+ persistArgs.sourceCacheKey,
+ persistArgs.sourceReferrer,
+ Components.interfaces.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE,
+ persistArgs.sourcePostData,
+ null,
+ targetFileURL,
+ persistArgs.isPrivate);
+ }
+}
+
+/**
+ * Structure for holding info about automatically supplied parameters for
+ * internalSave(...). This allows parameters to be supplied so the user does not
+ * need to be prompted for file info.
+ * @param aFileAutoChosen This is an nsIFile object that has been
+ * pre-determined as the filename for the target to save to
+ * @param aUriAutoChosen This is the nsIURI object for the target
+ */
+function AutoChosen(aFileAutoChosen, aUriAutoChosen) {
+ this.file = aFileAutoChosen;
+ this.uri = aUriAutoChosen;
+}
+
+/**
+ * Structure for holding info about a URL and the target filename it should be
+ * saved to. This structure is populated by initFileInfo(...).
+ * @param aSuggestedFileName This is used by initFileInfo(...) when it
+ * cannot 'discover' the filename from the url
+ * @param aFileName The target filename
+ * @param aFileBaseName The filename without the file extension
+ * @param aFileExt The extension of the filename
+ * @param aUri An nsIURI object for the url that is being saved
+ */
+function FileInfo(aSuggestedFileName, aFileName, aFileBaseName, aFileExt, aUri) {
+ this.suggestedFileName = aSuggestedFileName;
+ this.fileName = aFileName;
+ this.fileBaseName = aFileBaseName;
+ this.fileExt = aFileExt;
+ this.uri = aUri;
+}
+
+/**
+ * Determine what the 'default' filename string is, its file extension and the
+ * filename without the extension. This filename is used when prompting the user
+ * for confirmation in the file picker dialog.
+ * @param aFI A FileInfo structure into which we'll put the results of this method.
+ * @param aURL The String representation of the URL of the document being saved
+ * @param aURLCharset The charset of aURL.
+ * @param aDocument The document to be saved
+ * @param aContentType The content type we're saving, if it could be
+ * determined by the caller.
+ * @param aContentDisposition The content-disposition header for the object
+ * we're saving, if it could be determined by the caller.
+ */
+function initFileInfo(aFI, aURL, aURLCharset, aDocument,
+ aContentType, aContentDisposition)
+{
+ try {
+ // Get an nsIURI object from aURL if possible:
+ try {
+ aFI.uri = makeURI(aURL, aURLCharset);
+ // Assuming nsiUri is valid, calling QueryInterface(...) on it will
+ // populate extra object fields (eg filename and file extension).
+ var url = aFI.uri.QueryInterface(Components.interfaces.nsIURL);
+ aFI.fileExt = url.fileExtension;
+ } catch (e) {
+ }
+
+ // Get the default filename:
+ aFI.fileName = getDefaultFileName((aFI.suggestedFileName || aFI.fileName),
+ aFI.uri, aDocument, aContentDisposition);
+ // If aFI.fileExt is still blank, consider: aFI.suggestedFileName is supplied
+ // if saveURL(...) was the original caller (hence both aContentType and
+ // aDocument are blank). If they were saving a link to a website then make
+ // the extension .htm .
+ if (!aFI.fileExt && !aDocument && !aContentType && (/^http(s?):\/\//i.test(aURL))) {
+ aFI.fileExt = "htm";
+ aFI.fileBaseName = aFI.fileName;
+ } else {
+ aFI.fileExt = getDefaultExtension(aFI.fileName, aFI.uri, aContentType);
+ aFI.fileBaseName = getFileBaseName(aFI.fileName);
+ }
+ } catch (e) {
+ }
+}
+
+/**
+ * Given the Filepicker Parameters (aFpP), show the file picker dialog,
+ * prompting the user to confirm (or change) the fileName.
+ * @param aFpP
+ * A structure (see definition in internalSave(...) method)
+ * containing all the data used within this method.
+ * @param aSkipPrompt
+ * If true, attempt to save the file automatically to the user's default
+ * download directory, thus skipping the explicit prompt for a file name,
+ * but only if the associated preference is set.
+ * If false, don't save the file automatically to the user's
+ * default download directory, even if the associated preference
+ * is set, but ask for the target explicitly.
+ * @param aRelatedURI
+ * An nsIURI associated with the download. The last used
+ * directory of the picker is retrieved from/stored in the
+ * Content Pref Service using this URI.
+ * @return Promise
+ * @resolve a boolean. When true, it indicates that the file picker dialog
+ * is accepted.
+ */
+function promiseTargetFile(aFpP, /* optional */ aSkipPrompt, /* optional */ aRelatedURI)
+{
+ return Task.spawn(function*() {
+ let downloadLastDir = new DownloadLastDir(window);
+ let prefBranch = Services.prefs.getBranch("browser.download.");
+ let useDownloadDir = prefBranch.getBoolPref("useDownloadDir");
+
+ if (!aSkipPrompt)
+ useDownloadDir = false;
+
+ // Default to the user's default downloads directory configured
+ // through download prefs.
+ let dirPath = yield Downloads.getPreferredDownloadsDirectory();
+ let dirExists = yield OS.File.exists(dirPath);
+ let dir = new FileUtils.File(dirPath);
+
+ if (useDownloadDir && dirExists) {
+ dir.append(getNormalizedLeafName(aFpP.fileInfo.fileName,
+ aFpP.fileInfo.fileExt));
+ aFpP.file = uniqueFile(dir);
+ return true;
+ }
+
+ // We must prompt for the file name explicitly.
+ // If we must prompt because we were asked to...
+ let deferred = Promise.defer();
+ if (useDownloadDir) {
+ // Keep async behavior in both branches
+ Services.tm.mainThread.dispatch(function() {
+ deferred.resolve(null);
+ }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+ } else {
+ downloadLastDir.getFileAsync(aRelatedURI, function getFileAsyncCB(aFile) {
+ deferred.resolve(aFile);
+ });
+ }
+ let file = yield deferred.promise;
+ if (file && (yield OS.File.exists(file.path))) {
+ dir = file;
+ dirExists = true;
+ }
+
+ if (!dirExists) {
+ // Default to desktop.
+ dir = Services.dirsvc.get("Desk", Components.interfaces.nsIFile);
+ }
+
+ let fp = makeFilePicker();
+ let titleKey = aFpP.fpTitleKey || "SaveLinkTitle";
+ fp.init(window, ContentAreaUtils.stringBundle.GetStringFromName(titleKey),
+ Components.interfaces.nsIFilePicker.modeSave);
+
+ fp.displayDirectory = dir;
+ fp.defaultExtension = aFpP.fileInfo.fileExt;
+ fp.defaultString = getNormalizedLeafName(aFpP.fileInfo.fileName,
+ aFpP.fileInfo.fileExt);
+ appendFiltersForContentType(fp, aFpP.contentType, aFpP.fileInfo.fileExt,
+ aFpP.saveMode);
+
+ // The index of the selected filter is only preserved and restored if there's
+ // more than one filter in addition to "All Files".
+ if (aFpP.saveMode != SAVEMODE_FILEONLY) {
+ try {
+ fp.filterIndex = prefBranch.getIntPref("save_converter_index");
+ }
+ catch (e) {
+ }
+ }
+
+ let deferComplete = Promise.defer();
+ fp.open(function(aResult) {
+ deferComplete.resolve(aResult);
+ });
+ let result = yield deferComplete.promise;
+ if (result == Components.interfaces.nsIFilePicker.returnCancel || !fp.file) {
+ return false;
+ }
+
+ if (aFpP.saveMode != SAVEMODE_FILEONLY)
+ prefBranch.setIntPref("save_converter_index", fp.filterIndex);
+
+ // Do not store the last save directory as a pref inside the private browsing mode
+ downloadLastDir.setFile(aRelatedURI, fp.file.parent);
+
+ fp.file.leafName = validateFileName(fp.file.leafName);
+
+ aFpP.saveAsType = fp.filterIndex;
+ aFpP.file = fp.file;
+ aFpP.fileURL = fp.fileURL;
+
+ return true;
+ });
+}
+
+// Since we're automatically downloading, we don't get the file picker's
+// logic to check for existing files, so we need to do that here.
+//
+// Note - this code is identical to that in
+// mozilla/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
+// If you are updating this code, update that code too! We can't share code
+// here since that code is called in a js component.
+function uniqueFile(aLocalFile)
+{
+ var collisionCount = 0;
+ while (aLocalFile.exists()) {
+ collisionCount++;
+ if (collisionCount == 1) {
+ // Append "(2)" before the last dot in (or at the end of) the filename
+ // special case .ext.gz etc files so we don't wind up with .tar(2).gz
+ if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i))
+ aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
+ else
+ aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&");
+ }
+ else {
+ // replace the last (n) in the filename with (n+1)
+ aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount + 1) + ")");
+ }
+ }
+ return aLocalFile;
+}
+
+/**
+ * Download a URL using the new jsdownloads API.
+ *
+ * @param aURL
+ * the url to download
+ * @param [optional] aFileName
+ * the destination file name, if omitted will be obtained from the url.
+ * @param aInitiatingDocument
+ * The document from which the download was initiated.
+ */
+function DownloadURL(aURL, aFileName, aInitiatingDocument) {
+ // For private browsing, try to get document out of the most recent browser
+ // window, or provide our own if there's no browser window.
+ let isPrivate = aInitiatingDocument.defaultView
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext)
+ .usePrivateBrowsing;
+
+ let fileInfo = new FileInfo(aFileName);
+ initFileInfo(fileInfo, aURL, null, null, null, null);
+
+ let filepickerParams = {
+ fileInfo: fileInfo,
+ saveMode: SAVEMODE_FILEONLY
+ };
+
+ Task.spawn(function* () {
+ let accepted = yield promiseTargetFile(filepickerParams, true, fileInfo.uri);
+ if (!accepted)
+ return;
+
+ let file = filepickerParams.file;
+ let download = yield Downloads.createDownload({
+ source: { url: aURL, isPrivate: isPrivate },
+ target: { path: file.path, partFilePath: file.path + ".part" }
+ });
+ download.tryToKeepPartialData = true;
+
+ // Ignore errors because failures are reported through the download list.
+ download.start().catch(() => {});
+
+ // Add the download to the list, allowing it to be managed.
+ let list = yield Downloads.getList(Downloads.ALL);
+ list.add(download);
+ }).then(null, Components.utils.reportError);
+}
+
+// We have no DOM, and can only save the URL as is.
+const SAVEMODE_FILEONLY = 0x00;
+XPCOMUtils.defineConstant(this, "SAVEMODE_FILEONLY", SAVEMODE_FILEONLY);
+// We have a DOM and can save as complete.
+const SAVEMODE_COMPLETE_DOM = 0x01;
+XPCOMUtils.defineConstant(this, "SAVEMODE_COMPLETE_DOM", SAVEMODE_COMPLETE_DOM);
+// We have a DOM which we can serialize as text.
+const SAVEMODE_COMPLETE_TEXT = 0x02;
+XPCOMUtils.defineConstant(this, "SAVEMODE_COMPLETE_TEXT", SAVEMODE_COMPLETE_TEXT);
+
+// If we are able to save a complete DOM, the 'save as complete' filter
+// must be the first filter appended. The 'save page only' counterpart
+// must be the second filter appended. And the 'save as complete text'
+// filter must be the third filter appended.
+function appendFiltersForContentType(aFilePicker, aContentType, aFileExtension, aSaveMode)
+{
+ // The bundle name for saving only a specific content type.
+ var bundleName;
+ // The corresponding filter string for a specific content type.
+ var filterString;
+
+ // Every case where GetSaveModeForContentType can return non-FILEONLY
+ // modes must be handled here.
+ if (aSaveMode != SAVEMODE_FILEONLY) {
+ switch (aContentType) {
+ case "text/html":
+ bundleName = "WebPageHTMLOnlyFilter";
+ filterString = "*.htm; *.html";
+ break;
+
+ case "application/xhtml+xml":
+ bundleName = "WebPageXHTMLOnlyFilter";
+ filterString = "*.xht; *.xhtml";
+ break;
+
+ case "image/svg+xml":
+ bundleName = "WebPageSVGOnlyFilter";
+ filterString = "*.svg; *.svgz";
+ break;
+
+ case "text/xml":
+ case "application/xml":
+ bundleName = "WebPageXMLOnlyFilter";
+ filterString = "*.xml";
+ break;
+ }
+ }
+
+ if (!bundleName) {
+ if (aSaveMode != SAVEMODE_FILEONLY)
+ throw "Invalid save mode for type '" + aContentType + "'";
+
+ var mimeInfo = getMIMEInfoForType(aContentType, aFileExtension);
+ if (mimeInfo) {
+
+ var extEnumerator = mimeInfo.getFileExtensions();
+
+ var extString = "";
+ while (extEnumerator.hasMore()) {
+ var extension = extEnumerator.getNext();
+ if (extString)
+ extString += "; "; // If adding more than one extension,
+ // separate by semi-colon
+ extString += "*." + extension;
+ }
+
+ if (extString)
+ aFilePicker.appendFilter(mimeInfo.description, extString);
+ }
+ }
+
+ if (aSaveMode & SAVEMODE_COMPLETE_DOM) {
+ aFilePicker.appendFilter(ContentAreaUtils.stringBundle.GetStringFromName("WebPageCompleteFilter"),
+ filterString);
+ // We should always offer a choice to save document only if
+ // we allow saving as complete.
+ aFilePicker.appendFilter(ContentAreaUtils.stringBundle.GetStringFromName(bundleName),
+ filterString);
+ }
+
+ if (aSaveMode & SAVEMODE_COMPLETE_TEXT)
+ aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterText);
+
+ // Always append the all files (*) filter
+ aFilePicker.appendFilters(Components.interfaces.nsIFilePicker.filterAll);
+}
+
+function getPostData(aDocument)
+{
+ const Ci = Components.interfaces;
+
+ if (aDocument instanceof Ci.nsIWebBrowserPersistDocument) {
+ return aDocument.postData;
+ }
+ try {
+ // Find the session history entry corresponding to the given document. In
+ // the current implementation, nsIWebPageDescriptor.currentDescriptor always
+ // returns a session history entry.
+ let sessionHistoryEntry =
+ aDocument.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIWebPageDescriptor)
+ .currentDescriptor
+ .QueryInterface(Ci.nsISHEntry);
+ return sessionHistoryEntry.postData;
+ }
+ catch (e) {
+ }
+ return null;
+}
+
+function makeWebBrowserPersist()
+{
+ const persistContractID = "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
+ const persistIID = Components.interfaces.nsIWebBrowserPersist;
+ return Components.classes[persistContractID].createInstance(persistIID);
+}
+
+function makeURI(aURL, aOriginCharset, aBaseURI)
+{
+ return BrowserUtils.makeURI(aURL, aOriginCharset, aBaseURI);
+}
+
+function makeFileURI(aFile)
+{
+ return BrowserUtils.makeFileURI(aFile);
+}
+
+function makeFilePicker()
+{
+ const fpContractID = "@mozilla.org/filepicker;1";
+ const fpIID = Components.interfaces.nsIFilePicker;
+ return Components.classes[fpContractID].createInstance(fpIID);
+}
+
+function getMIMEService()
+{
+ const mimeSvcContractID = "@mozilla.org/mime;1";
+ const mimeSvcIID = Components.interfaces.nsIMIMEService;
+ const mimeSvc = Components.classes[mimeSvcContractID].getService(mimeSvcIID);
+ return mimeSvc;
+}
+
+// Given aFileName, find the fileName without the extension on the end.
+function getFileBaseName(aFileName)
+{
+ // Remove the file extension from aFileName:
+ return aFileName.replace(/\.[^.]*$/, "");
+}
+
+function getMIMETypeForURI(aURI)
+{
+ try {
+ return getMIMEService().getTypeFromURI(aURI);
+ }
+ catch (e) {
+ }
+ return null;
+}
+
+function getMIMEInfoForType(aMIMEType, aExtension)
+{
+ if (aMIMEType || aExtension) {
+ try {
+ return getMIMEService().getFromTypeAndExtension(aMIMEType, aExtension);
+ }
+ catch (e) {
+ }
+ }
+ return null;
+}
+
+function getDefaultFileName(aDefaultFileName, aURI, aDocument,
+ aContentDisposition)
+{
+ // 1) look for a filename in the content-disposition header, if any
+ if (aContentDisposition) {
+ const mhpContractID = "@mozilla.org/network/mime-hdrparam;1";
+ const mhpIID = Components.interfaces.nsIMIMEHeaderParam;
+ const mhp = Components.classes[mhpContractID].getService(mhpIID);
+ var dummy = { value: null }; // Need an out param...
+ var charset = getCharsetforSave(aDocument);
+
+ var fileName = null;
+ try {
+ fileName = mhp.getParameter(aContentDisposition, "filename", charset,
+ true, dummy);
+ }
+ catch (e) {
+ try {
+ fileName = mhp.getParameter(aContentDisposition, "name", charset, true,
+ dummy);
+ }
+ catch (e) {
+ }
+ }
+ if (fileName)
+ return fileName;
+ }
+
+ let docTitle;
+ if (aDocument) {
+ // If the document looks like HTML or XML, try to use its original title.
+ docTitle = validateFileName(aDocument.title).trim();
+ if (docTitle) {
+ let contentType = aDocument.contentType;
+ if (contentType == "application/xhtml+xml" ||
+ contentType == "application/xml" ||
+ contentType == "image/svg+xml" ||
+ contentType == "text/html" ||
+ contentType == "text/xml") {
+ // 2) Use the document title
+ return docTitle;
+ }
+ }
+ }
+
+ try {
+ var url = aURI.QueryInterface(Components.interfaces.nsIURL);
+ if (url.fileName != "") {
+ // 3) Use the actual file name, if present
+ var textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
+ .getService(Components.interfaces.nsITextToSubURI);
+ return validateFileName(textToSubURI.unEscapeURIForUI(url.originCharset || "UTF-8", url.fileName));
+ }
+ } catch (e) {
+ // This is something like a data: and so forth URI... no filename here.
+ }
+
+ if (docTitle)
+ // 4) Use the document title
+ return docTitle;
+
+ if (aDefaultFileName)
+ // 5) Use the caller-provided name, if any
+ return validateFileName(aDefaultFileName);
+
+ // 6) If this is a directory, use the last directory name
+ var path = aURI.path.match(/\/([^\/]+)\/$/);
+ if (path && path.length > 1)
+ return validateFileName(path[1]);
+
+ try {
+ if (aURI.host)
+ // 7) Use the host.
+ return aURI.host;
+ } catch (e) {
+ // Some files have no information at all, like Javascript generated pages
+ }
+ try {
+ // 8) Use the default file name
+ return ContentAreaUtils.stringBundle.GetStringFromName("DefaultSaveFileName");
+ } catch (e) {
+ // in case localized string cannot be found
+ }
+ // 9) If all else fails, use "index"
+ return "index";
+}
+
+function validateFileName(aFileName)
+{
+ aFileName = aFileName.replace(/[\u200e\u200f\u202a-\u202e]/g, "");
+ var re = /[\/]+/g;
+ if (navigator.appVersion.indexOf("Windows") != -1) {
+ re = /[\\\/\|]+/g;
+ aFileName = aFileName.replace(/[\"]+/g, "'");
+ aFileName = aFileName.replace(/[\*\:\?]+/g, " ");
+ aFileName = aFileName.replace(/[\<]+/g, "(");
+ aFileName = aFileName.replace(/[\>]+/g, ")");
+ }
+ else if (navigator.appVersion.indexOf("Macintosh") != -1) {
+ re = /[\:\/]+/g;
+ }
+
+ return aFileName.replace(re, "_");
+}
+
+function getNormalizedLeafName(aFile, aDefaultExtension)
+{
+ if (!aDefaultExtension)
+ return aFile;
+
+#ifdef XP_WIN
+ // Remove trailing dots and spaces on windows
+ aFile = aFile.replace(/[\s.]+$/, "");
+#endif
+
+ // Remove leading dots
+ aFile = aFile.replace(/^\.+/, "");
+
+ // Fix up the file name we're saving to to include the default extension
+ var i = aFile.lastIndexOf(".");
+ if (aFile.substr(i + 1) != aDefaultExtension)
+ return aFile + "." + aDefaultExtension;
+
+ return aFile;
+}
+
+function getDefaultExtension(aFilename, aURI, aContentType)
+{
+ if (aContentType == "text/plain" || aContentType == "application/octet-stream" || aURI.scheme == "ftp")
+ return ""; // temporary fix for bug 120327
+
+ // First try the extension from the filename
+ const stdURLContractID = "@mozilla.org/network/standard-url;1";
+ const stdURLIID = Components.interfaces.nsIURL;
+ var url = Components.classes[stdURLContractID].createInstance(stdURLIID);
+ url.filePath = aFilename;
+
+ var ext = url.fileExtension;
+
+ // This mirrors some code in nsExternalHelperAppService::DoContent
+ // Use the filename first and then the URI if that fails
+
+ var mimeInfo = getMIMEInfoForType(aContentType, ext);
+
+ if (ext && mimeInfo && mimeInfo.extensionExists(ext))
+ return ext;
+
+ // Well, that failed. Now try the extension from the URI
+ var urlext;
+ try {
+ url = aURI.QueryInterface(Components.interfaces.nsIURL);
+ urlext = url.fileExtension;
+ } catch (e) {
+ }
+
+ if (urlext && mimeInfo && mimeInfo.extensionExists(urlext)) {
+ return urlext;
+ }
+ try {
+ if (mimeInfo)
+ return mimeInfo.primaryExtension;
+ }
+ catch (e) {
+ }
+ // Fall back on the extensions in the filename and URI for lack
+ // of anything better.
+ return ext || urlext;
+}
+
+function GetSaveModeForContentType(aContentType, aDocument)
+{
+ // We can only save a complete page if we have a loaded document,
+ // and it's not a CPOW -- nsWebBrowserPersist needs a real document.
+ if (!aDocument || Components.utils.isCrossProcessWrapper(aDocument))
+ return SAVEMODE_FILEONLY;
+
+ // Find the possible save modes using the provided content type
+ var saveMode = SAVEMODE_FILEONLY;
+ switch (aContentType) {
+ case "text/html":
+ case "application/xhtml+xml":
+ case "image/svg+xml":
+ saveMode |= SAVEMODE_COMPLETE_TEXT;
+ // Fall through
+ case "text/xml":
+ case "application/xml":
+ saveMode |= SAVEMODE_COMPLETE_DOM;
+ break;
+ }
+
+ return saveMode;
+}
+
+function getCharsetforSave(aDocument)
+{
+ if (aDocument)
+ return aDocument.characterSet;
+
+ if (document.commandDispatcher.focusedWindow)
+ return document.commandDispatcher.focusedWindow.document.characterSet;
+
+ return window.content.document.characterSet;
+}
+
+/**
+ * Open a URL from chrome, determining if we can handle it internally or need to
+ * launch an external application to handle it.
+ * @param aURL The URL to be opened
+ *
+ * WARNING: Please note that openURL() does not perform any content security checks!!!
+ */
+function openURL(aURL)
+{
+ var uri = makeURI(aURL);
+
+ var protocolSvc = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Components.interfaces.nsIExternalProtocolService);
+
+ if (!protocolSvc.isExposedProtocol(uri.scheme)) {
+ // If we're not a browser, use the external protocol service to load the URI.
+ protocolSvc.loadUrl(uri);
+ }
+ else {
+ var recentWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (recentWindow) {
+ recentWindow.openUILinkIn(uri.spec, "tab");
+ return;
+ }
+
+ var loadgroup = Components.classes["@mozilla.org/network/load-group;1"]
+ .createInstance(Components.interfaces.nsILoadGroup);
+ var appstartup = Services.startup;
+
+ var loadListener = {
+ onStartRequest: function ll_start(aRequest, aContext) {
+ appstartup.enterLastWindowClosingSurvivalArea();
+ },
+ onStopRequest: function ll_stop(aRequest, aContext, aStatusCode) {
+ appstartup.exitLastWindowClosingSurvivalArea();
+ },
+ QueryInterface: function ll_QI(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ }
+ loadgroup.groupObserver = loadListener;
+
+ var uriListener = {
+ onStartURIOpen: function(uri) { return false; },
+ doContent: function(ctype, preferred, request, handler) { return false; },
+ isPreferred: function(ctype, desired) { return false; },
+ canHandleContent: function(ctype, preferred, desired) { return false; },
+ loadCookie: null,
+ parentContentListener: null,
+ getInterface: function(iid) {
+ if (iid.equals(Components.interfaces.nsIURIContentListener))
+ return this;
+ if (iid.equals(Components.interfaces.nsILoadGroup))
+ return loadgroup;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ }
+
+ var channel = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ });
+
+ var uriLoader = Components.classes["@mozilla.org/uriloader;1"]
+ .getService(Components.interfaces.nsIURILoader);
+ uriLoader.openURI(channel,
+ Components.interfaces.nsIURILoader.IS_CONTENT_PREFERRED,
+ uriListener);
+ }
+}
diff --git a/components/global/content/customizeToolbar.css b/components/global/content/customizeToolbar.css
new file mode 100644
index 000000000..ae90a2f28
--- /dev/null
+++ b/components/global/content/customizeToolbar.css
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"); /* set default namespace to XUL */
+@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */
+
+#palette-box {
+ overflow: auto;
+ display: block;
+ min-height: 3em;
+}
+
+#palette-box > toolbarpaletteitem {
+ width: 110px;
+ height: 94px;
+ overflow: hidden;
+ display: inline-block;
+}
+
+.toolbarpaletteitem-box {
+ -moz-box-pack: center;
+ -moz-box-flex: 1;
+ width: 110px;
+ max-width: 110px;
+}
+
+toolbarpaletteitem > label {
+ text-align: center;
+}
+
+#main-box > box {
+ overflow: hidden;
+}
+
+/* Hide the toolbarbutton label because we replicate it on the wrapper */
+.toolbarbutton-text {
+ display: none;
+}
diff --git a/components/global/content/customizeToolbar.js b/components/global/content/customizeToolbar.js
new file mode 100644
index 000000000..1775d9f52
--- /dev/null
+++ b/components/global/content/customizeToolbar.js
@@ -0,0 +1,852 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 gToolbarInfoSeparators = ["|", "-"];
+
+var gToolboxDocument = null;
+var gToolbox = null;
+var gCurrentDragOverItem = null;
+var gToolboxChanged = false;
+var gToolboxSheet = false;
+var gPaletteBox = null;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function onLoad()
+{
+ if ("arguments" in window && window.arguments[0]) {
+ InitWithToolbox(window.arguments[0]);
+ repositionDialog(window);
+ }
+ else if (window.frameElement &&
+ "toolbox" in window.frameElement) {
+ gToolboxSheet = true;
+ InitWithToolbox(window.frameElement.toolbox);
+ repositionDialog(window.frameElement.panel);
+ }
+}
+
+function InitWithToolbox(aToolbox)
+{
+ gToolbox = aToolbox;
+ dispatchCustomizationEvent("beforecustomization");
+ gToolboxDocument = gToolbox.ownerDocument;
+ gToolbox.customizing = true;
+ forEachCustomizableToolbar(function (toolbar) {
+ toolbar.setAttribute("customizing", "true");
+ });
+ gPaletteBox = document.getElementById("palette-box");
+
+ var elts = getRootElements();
+ for (let i=0; i < elts.length; i++) {
+ elts[i].addEventListener("dragstart", onToolbarDragStart, true);
+ elts[i].addEventListener("dragover", onToolbarDragOver, true);
+ elts[i].addEventListener("dragexit", onToolbarDragExit, true);
+ elts[i].addEventListener("drop", onToolbarDrop, true);
+ }
+
+ initDialog();
+}
+
+function onClose()
+{
+ if (!gToolboxSheet)
+ window.close();
+ else
+ finishToolbarCustomization();
+}
+
+function onUnload()
+{
+ if (!gToolboxSheet)
+ finishToolbarCustomization();
+}
+
+function finishToolbarCustomization()
+{
+ removeToolboxListeners();
+ unwrapToolbarItems();
+ persistCurrentSets();
+ gToolbox.customizing = false;
+ forEachCustomizableToolbar(function (toolbar) {
+ toolbar.removeAttribute("customizing");
+ });
+
+ notifyParentComplete();
+}
+
+function initDialog()
+{
+ if (!gToolbox.toolbarset) {
+ document.getElementById("newtoolbar").hidden = true;
+ }
+
+ var mode = gToolbox.getAttribute("mode");
+ document.getElementById("modelist").value = mode;
+ var smallIconsCheckbox = document.getElementById("smallicons");
+ smallIconsCheckbox.checked = gToolbox.getAttribute("iconsize") == "small";
+ if (mode == "text")
+ smallIconsCheckbox.disabled = true;
+
+ // Build up the palette of other items.
+ buildPalette();
+
+ // Wrap all the items on the toolbar in toolbarpaletteitems.
+ wrapToolbarItems();
+}
+
+function repositionDialog(aWindow)
+{
+ // Position the dialog touching the bottom of the toolbox and centered with
+ // it.
+ if (!aWindow)
+ return;
+
+ var width;
+ if (aWindow != window)
+ width = aWindow.getBoundingClientRect().width;
+ else if (document.documentElement.hasAttribute("width"))
+ width = document.documentElement.getAttribute("width");
+ else
+ width = parseInt(document.documentElement.style.width);
+ var screenX = gToolbox.boxObject.screenX
+ + ((gToolbox.boxObject.width - width) / 2);
+ var screenY = gToolbox.boxObject.screenY + gToolbox.boxObject.height;
+
+ aWindow.moveTo(screenX, screenY);
+}
+
+function removeToolboxListeners()
+{
+ var elts = getRootElements();
+ for (let i=0; i < elts.length; i++) {
+ elts[i].removeEventListener("dragstart", onToolbarDragStart, true);
+ elts[i].removeEventListener("dragover", onToolbarDragOver, true);
+ elts[i].removeEventListener("dragexit", onToolbarDragExit, true);
+ elts[i].removeEventListener("drop", onToolbarDrop, true);
+ }
+}
+
+/**
+ * Invoke a callback on the toolbox to notify it that the dialog is done
+ * and going away.
+ */
+function notifyParentComplete()
+{
+ if ("customizeDone" in gToolbox)
+ gToolbox.customizeDone(gToolboxChanged);
+ dispatchCustomizationEvent("aftercustomization");
+}
+
+function toolboxChanged(aType)
+{
+ gToolboxChanged = true;
+ if ("customizeChange" in gToolbox)
+ gToolbox.customizeChange(aType);
+ dispatchCustomizationEvent("customizationchange");
+}
+
+function dispatchCustomizationEvent(aEventName) {
+ var evt = document.createEvent("Events");
+ evt.initEvent(aEventName, true, true);
+ gToolbox.dispatchEvent(evt);
+}
+
+/**
+ * Persist the current set of buttons in all customizable toolbars to
+ * localstore.
+ */
+function persistCurrentSets()
+{
+ if (!gToolboxChanged || gToolboxDocument.defaultView.closed)
+ return;
+
+ var customCount = 0;
+ forEachCustomizableToolbar(function (toolbar) {
+ // Calculate currentset and store it in the attribute.
+ var currentSet = toolbar.currentSet;
+ toolbar.setAttribute("currentset", currentSet);
+
+ var customIndex = toolbar.hasAttribute("customindex");
+ if (customIndex) {
+ if (!toolbar.hasChildNodes()) {
+ // Remove custom toolbars whose contents have been removed.
+ gToolbox.removeChild(toolbar);
+ } else if (gToolbox.toolbarset) {
+ var hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
+ "autohide" : "collapsed";
+ // Persist custom toolbar info on the <toolbarset/>
+ // Attributes:
+ // Names: "toolbarX" (X - the number of the toolbar)
+ // Values: "Name|HidingAttributeName-HidingAttributeValue|CurrentSet"
+ gToolbox.toolbarset.setAttribute("toolbar" + (++customCount),
+ toolbar.toolbarName
+ + gToolbarInfoSeparators[0]
+ + hidingAttribute
+ + gToolbarInfoSeparators[1]
+ + toolbar.getAttribute(hidingAttribute)
+ + gToolbarInfoSeparators[0]
+ + currentSet);
+ gToolboxDocument.persist(gToolbox.toolbarset.id, "toolbar"+customCount);
+ }
+ }
+
+ if (!customIndex) {
+ // Persist the currentset attribute directly on hardcoded toolbars.
+ gToolboxDocument.persist(toolbar.id, "currentset");
+ }
+ });
+
+ // Remove toolbarX attributes for removed toolbars.
+ while (gToolbox.toolbarset && gToolbox.toolbarset.hasAttribute("toolbar"+(++customCount))) {
+ gToolbox.toolbarset.removeAttribute("toolbar"+customCount);
+ gToolboxDocument.persist(gToolbox.toolbarset.id, "toolbar"+customCount);
+ }
+}
+
+/**
+ * Wraps all items in all customizable toolbars in a toolbox.
+ */
+function wrapToolbarItems()
+{
+ forEachCustomizableToolbar(function (toolbar) {
+ Array.forEach(toolbar.childNodes, function (item) {
+ if (isToolbarItem(item)) {
+ let wrapper = wrapToolbarItem(item);
+ cleanupItemForToolbar(item, wrapper);
+ }
+ });
+ });
+}
+
+function getRootElements()
+{
+ return [gToolbox].concat(gToolbox.externalToolbars);
+}
+
+/**
+ * Unwraps all items in all customizable toolbars in a toolbox.
+ */
+function unwrapToolbarItems()
+{
+ let elts = getRootElements();
+ for (let i=0; i < elts.length; i++) {
+ let paletteItems = elts[i].getElementsByTagName("toolbarpaletteitem");
+ let paletteItem;
+ while ((paletteItem = paletteItems.item(0)) != null) {
+ let toolbarItem = paletteItem.firstChild;
+ restoreItemForToolbar(toolbarItem, paletteItem);
+ paletteItem.parentNode.replaceChild(toolbarItem, paletteItem);
+ }
+ }
+}
+
+/**
+ * Creates a wrapper that can be used to contain a toolbaritem and prevent
+ * it from receiving UI events.
+ */
+function createWrapper(aId, aDocument)
+{
+ var wrapper = aDocument.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "toolbarpaletteitem");
+
+ wrapper.id = "wrapper-"+aId;
+ return wrapper;
+}
+
+/**
+ * Wraps an item that has been cloned from a template and adds
+ * it to the end of the palette.
+ */
+function wrapPaletteItem(aPaletteItem)
+{
+ var wrapper = createWrapper(aPaletteItem.id, document);
+
+ wrapper.appendChild(aPaletteItem);
+
+ // XXX We need to call this AFTER the palette item has been appended
+ // to the wrapper or else we crash dropping certain buttons on the
+ // palette due to removal of the command and disabled attributes - JRH
+ cleanUpItemForPalette(aPaletteItem, wrapper);
+
+ gPaletteBox.appendChild(wrapper);
+}
+
+/**
+ * Wraps an item that is currently on a toolbar and replaces the item
+ * with the wrapper. This is not used when dropping items from the palette,
+ * only when first starting the dialog and wrapping everything on the toolbars.
+ */
+function wrapToolbarItem(aToolbarItem)
+{
+ var wrapper = createWrapper(aToolbarItem.id, gToolboxDocument);
+
+ wrapper.flex = aToolbarItem.flex;
+
+ aToolbarItem.parentNode.replaceChild(wrapper, aToolbarItem);
+
+ wrapper.appendChild(aToolbarItem);
+
+ return wrapper;
+}
+
+/**
+ * Get the list of ids for the current set of items on each toolbar.
+ */
+function getCurrentItemIds()
+{
+ var currentItems = {};
+ forEachCustomizableToolbar(function (toolbar) {
+ var child = toolbar.firstChild;
+ while (child) {
+ if (isToolbarItem(child))
+ currentItems[child.id] = 1;
+ child = child.nextSibling;
+ }
+ });
+ return currentItems;
+}
+
+/**
+ * Builds the palette of draggable items that are not yet in a toolbar.
+ */
+function buildPalette()
+{
+ // Empty the palette first.
+ while (gPaletteBox.lastChild)
+ gPaletteBox.removeChild(gPaletteBox.lastChild);
+
+ // Add the toolbar separator item.
+ var templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "toolbarseparator");
+ templateNode.id = "separator";
+ wrapPaletteItem(templateNode);
+
+ // Add the toolbar spring item.
+ templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "toolbarspring");
+ templateNode.id = "spring";
+ templateNode.flex = 1;
+ wrapPaletteItem(templateNode);
+
+ // Add the toolbar spacer item.
+ templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "toolbarspacer");
+ templateNode.id = "spacer";
+ templateNode.flex = 1;
+ wrapPaletteItem(templateNode);
+
+ var currentItems = getCurrentItemIds();
+ templateNode = gToolbox.palette.firstChild;
+ while (templateNode) {
+ // Check if the item is already in a toolbar before adding it to the palette.
+ if (!(templateNode.id in currentItems)) {
+ var paletteItem = document.importNode(templateNode, true);
+ wrapPaletteItem(paletteItem);
+ }
+
+ templateNode = templateNode.nextSibling;
+ }
+}
+
+/**
+ * Makes sure that an item that has been cloned from a template
+ * is stripped of any attributes that may adversely affect its
+ * appearance in the palette.
+ */
+function cleanUpItemForPalette(aItem, aWrapper)
+{
+ aWrapper.setAttribute("place", "palette");
+ setWrapperType(aItem, aWrapper);
+
+ if (aItem.hasAttribute("title"))
+ aWrapper.setAttribute("title", aItem.getAttribute("title"));
+ else if (aItem.hasAttribute("label"))
+ aWrapper.setAttribute("title", aItem.getAttribute("label"));
+ else if (isSpecialItem(aItem)) {
+ var stringBundle = document.getElementById("stringBundle");
+ // Remove the common "toolbar" prefix to generate the string name.
+ var title = stringBundle.getString(aItem.localName.slice(7) + "Title");
+ aWrapper.setAttribute("title", title);
+ }
+ aWrapper.setAttribute("tooltiptext", aWrapper.getAttribute("title"));
+
+ // Remove attributes that screw up our appearance.
+ aItem.removeAttribute("command");
+ aItem.removeAttribute("observes");
+ aItem.removeAttribute("type");
+ aItem.removeAttribute("width");
+
+ Array.forEach(aWrapper.querySelectorAll("[disabled]"), function(aNode) {
+ aNode.removeAttribute("disabled");
+ });
+}
+
+/**
+ * Makes sure that an item that has been cloned from a template
+ * is stripped of all properties that may adversely affect its
+ * appearance in the toolbar. Store critical properties on the
+ * wrapper so they can be put back on the item when we're done.
+ */
+function cleanupItemForToolbar(aItem, aWrapper)
+{
+ setWrapperType(aItem, aWrapper);
+ aWrapper.setAttribute("place", "toolbar");
+
+ if (aItem.hasAttribute("command")) {
+ aWrapper.setAttribute("itemcommand", aItem.getAttribute("command"));
+ aItem.removeAttribute("command");
+ }
+
+ if (aItem.checked) {
+ aWrapper.setAttribute("itemchecked", "true");
+ aItem.checked = false;
+ }
+
+ if (aItem.disabled) {
+ aWrapper.setAttribute("itemdisabled", "true");
+ aItem.disabled = false;
+ }
+}
+
+/**
+ * Restore all the properties that we stripped off above.
+ */
+function restoreItemForToolbar(aItem, aWrapper)
+{
+ if (aWrapper.hasAttribute("itemdisabled"))
+ aItem.disabled = true;
+
+ if (aWrapper.hasAttribute("itemchecked"))
+ aItem.checked = true;
+
+ if (aWrapper.hasAttribute("itemcommand")) {
+ let commandID = aWrapper.getAttribute("itemcommand");
+ aItem.setAttribute("command", commandID);
+
+ // XXX Bug 309953 - toolbarbuttons aren't in sync with their commands after customizing
+ let command = gToolboxDocument.getElementById(commandID);
+ if (command && command.hasAttribute("disabled"))
+ aItem.setAttribute("disabled", command.getAttribute("disabled"));
+ }
+}
+
+function setWrapperType(aItem, aWrapper)
+{
+ if (aItem.localName == "toolbarseparator") {
+ aWrapper.setAttribute("type", "separator");
+ } else if (aItem.localName == "toolbarspring") {
+ aWrapper.setAttribute("type", "spring");
+ } else if (aItem.localName == "toolbarspacer") {
+ aWrapper.setAttribute("type", "spacer");
+ } else if (aItem.localName == "toolbaritem" && aItem.firstChild) {
+ aWrapper.setAttribute("type", aItem.firstChild.localName);
+ }
+}
+
+function setDragActive(aItem, aValue)
+{
+ var node = aItem;
+ var direction = window.getComputedStyle(aItem, null).direction;
+ var value = direction == "ltr"? "left" : "right";
+ if (aItem.localName == "toolbar") {
+ node = aItem.lastChild;
+ value = direction == "ltr"? "right" : "left";
+ }
+
+ if (!node)
+ return;
+
+ if (aValue) {
+ if (!node.hasAttribute("dragover"))
+ node.setAttribute("dragover", value);
+ } else {
+ node.removeAttribute("dragover");
+ }
+}
+
+function addNewToolbar()
+{
+ var promptService = Services.prompt;
+ var stringBundle = document.getElementById("stringBundle");
+ var message = stringBundle.getString("enterToolbarName");
+ var title = stringBundle.getString("enterToolbarTitle");
+
+ var name = {};
+
+ // Quitting from the toolbar dialog while the new toolbar prompt is up
+ // can cause things to become unresponsive on the Mac. Until dialog modality
+ // is fixed (395465), disable the "Done" button explicitly.
+ var doneButton = document.getElementById("donebutton");
+ doneButton.disabled = true;
+
+ while (true) {
+
+ if (!promptService.prompt(window, title, message, name, null, {})) {
+ doneButton.disabled = false;
+ return;
+ }
+
+ if (!name.value) {
+ message = stringBundle.getFormattedString("enterToolbarBlank", [name.value]);
+ continue;
+ }
+
+ if (name.value.includes(gToolbarInfoSeparators[0])) {
+ message = stringBundle.getFormattedString("enterToolbarIllegalChars", [name.value]);
+ continue;
+ }
+
+ var dupeFound = false;
+
+ // Check for an existing toolbar with the same display name
+ for (let i = 0; i < gToolbox.childNodes.length; ++i) {
+ var toolbar = gToolbox.childNodes[i];
+ var toolbarName = toolbar.getAttribute("toolbarname");
+
+ if (toolbarName == name.value &&
+ toolbar.getAttribute("type") != "menubar" &&
+ toolbar.nodeName == 'toolbar') {
+ dupeFound = true;
+ break;
+ }
+ }
+
+ if (!dupeFound)
+ break;
+
+ message = stringBundle.getFormattedString("enterToolbarDup", [name.value]);
+ }
+
+ gToolbox.appendCustomToolbar(name.value, "", [null, null]);
+
+ toolboxChanged();
+
+ doneButton.disabled = false;
+}
+
+/**
+ * Restore the default set of buttons to fixed toolbars,
+ * remove all custom toolbars, and rebuild the palette.
+ */
+function restoreDefaultSet()
+{
+ // Unwrap the items on the toolbar.
+ unwrapToolbarItems();
+
+ // Remove all of the customized toolbars.
+ var child = gToolbox.lastChild;
+ while (child) {
+ if (child.hasAttribute("customindex")) {
+ var thisChild = child;
+ child = child.previousSibling;
+ thisChild.currentSet = "__empty";
+ gToolbox.removeChild(thisChild);
+ } else {
+ child = child.previousSibling;
+ }
+ }
+
+ // Restore the defaultset for fixed toolbars.
+ forEachCustomizableToolbar(function (toolbar) {
+ var defaultSet = toolbar.getAttribute("defaultset");
+ if (defaultSet)
+ toolbar.currentSet = defaultSet;
+ });
+
+ // Restore the default icon size and mode.
+ document.getElementById("smallicons").checked = (updateIconSize() == "small");
+ document.getElementById("modelist").value = updateToolbarMode();
+
+ // Now rebuild the palette.
+ buildPalette();
+
+ // Now re-wrap the items on the toolbar.
+ wrapToolbarItems();
+
+ toolboxChanged("reset");
+}
+
+function updateIconSize(aSize) {
+ return updateToolboxProperty("iconsize", aSize, "large");
+}
+
+function updateToolbarMode(aModeValue) {
+ var mode = updateToolboxProperty("mode", aModeValue, "icons");
+
+ var iconSizeCheckbox = document.getElementById("smallicons");
+ iconSizeCheckbox.disabled = mode == "text";
+
+ return mode;
+}
+
+function updateToolboxProperty(aProp, aValue, aToolkitDefault) {
+ var toolboxDefault = gToolbox.getAttribute("default" + aProp) ||
+ aToolkitDefault;
+
+ gToolbox.setAttribute(aProp, aValue || toolboxDefault);
+ gToolboxDocument.persist(gToolbox.id, aProp);
+
+ forEachCustomizableToolbar(function (toolbar) {
+ var toolbarDefault = toolbar.getAttribute("default" + aProp) ||
+ toolboxDefault;
+ if (toolbar.getAttribute("lock" + aProp) == "true" &&
+ toolbar.getAttribute(aProp) == toolbarDefault)
+ return;
+
+ toolbar.setAttribute(aProp, aValue || toolbarDefault);
+ gToolboxDocument.persist(toolbar.id, aProp);
+ });
+
+ toolboxChanged(aProp);
+
+ return aValue || toolboxDefault;
+}
+
+function forEachCustomizableToolbar(callback) {
+ Array.filter(gToolbox.childNodes, isCustomizableToolbar).forEach(callback);
+ Array.filter(gToolbox.externalToolbars, isCustomizableToolbar).forEach(callback);
+}
+
+function isCustomizableToolbar(aElt)
+{
+ return aElt.localName == "toolbar" &&
+ aElt.getAttribute("customizable") == "true";
+}
+
+function isSpecialItem(aElt)
+{
+ return aElt.localName == "toolbarseparator" ||
+ aElt.localName == "toolbarspring" ||
+ aElt.localName == "toolbarspacer";
+}
+
+function isToolbarItem(aElt)
+{
+ return aElt.localName == "toolbarbutton" ||
+ aElt.localName == "toolbaritem" ||
+ aElt.localName == "toolbarseparator" ||
+ aElt.localName == "toolbarspring" ||
+ aElt.localName == "toolbarspacer";
+}
+
+// Drag and Drop observers
+
+function onToolbarDragExit(aEvent)
+{
+ if (isUnwantedDragEvent(aEvent)) {
+ return;
+ }
+
+ if (gCurrentDragOverItem)
+ setDragActive(gCurrentDragOverItem, false);
+}
+
+function onToolbarDragStart(aEvent)
+{
+ var item = aEvent.target;
+ while (item && item.localName != "toolbarpaletteitem") {
+ if (item.localName == "toolbar")
+ return;
+ item = item.parentNode;
+ }
+
+ item.setAttribute("dragactive", "true");
+
+ var dt = aEvent.dataTransfer;
+ var documentId = gToolboxDocument.documentElement.id;
+ dt.setData("text/toolbarwrapper-id/" + documentId, item.firstChild.id);
+ dt.effectAllowed = "move";
+}
+
+function onToolbarDragOver(aEvent)
+{
+ if (isUnwantedDragEvent(aEvent)) {
+ return;
+ }
+
+ var documentId = gToolboxDocument.documentElement.id;
+ if (!aEvent.dataTransfer.types.includes("text/toolbarwrapper-id/" + documentId.toLowerCase()))
+ return;
+
+ var toolbar = aEvent.target;
+ var dropTarget = aEvent.target;
+ while (toolbar && toolbar.localName != "toolbar") {
+ dropTarget = toolbar;
+ toolbar = toolbar.parentNode;
+ }
+
+ // Make sure we are dragging over a customizable toolbar.
+ if (!toolbar || !isCustomizableToolbar(toolbar)) {
+ gCurrentDragOverItem = null;
+ return;
+ }
+
+ var previousDragItem = gCurrentDragOverItem;
+
+ if (dropTarget.localName == "toolbar") {
+ gCurrentDragOverItem = dropTarget;
+ } else {
+ gCurrentDragOverItem = null;
+
+ var direction = window.getComputedStyle(dropTarget.parentNode, null).direction;
+ var dropTargetCenter = dropTarget.boxObject.x + (dropTarget.boxObject.width / 2);
+ var dragAfter;
+ if (direction == "ltr")
+ dragAfter = aEvent.clientX > dropTargetCenter;
+ else
+ dragAfter = aEvent.clientX < dropTargetCenter;
+
+ if (dragAfter) {
+ gCurrentDragOverItem = dropTarget.nextSibling;
+ if (!gCurrentDragOverItem)
+ gCurrentDragOverItem = toolbar;
+ } else
+ gCurrentDragOverItem = dropTarget;
+ }
+
+ if (previousDragItem && gCurrentDragOverItem != previousDragItem) {
+ setDragActive(previousDragItem, false);
+ }
+
+ setDragActive(gCurrentDragOverItem, true);
+
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+}
+
+function onToolbarDrop(aEvent)
+{
+ if (isUnwantedDragEvent(aEvent)) {
+ return;
+ }
+
+ if (!gCurrentDragOverItem)
+ return;
+
+ setDragActive(gCurrentDragOverItem, false);
+
+ var documentId = gToolboxDocument.documentElement.id;
+ var draggedItemId = aEvent.dataTransfer.getData("text/toolbarwrapper-id/" + documentId);
+ if (gCurrentDragOverItem.id == draggedItemId)
+ return;
+
+ var toolbar = aEvent.target;
+ while (toolbar.localName != "toolbar")
+ toolbar = toolbar.parentNode;
+
+ var draggedPaletteWrapper = document.getElementById("wrapper-"+draggedItemId);
+ if (!draggedPaletteWrapper) {
+ // The wrapper has been dragged from the toolbar.
+ // Get the wrapper from the toolbar document and make sure that
+ // it isn't being dropped on itself.
+ let wrapper = gToolboxDocument.getElementById("wrapper-"+draggedItemId);
+ if (wrapper == gCurrentDragOverItem)
+ return;
+
+ // Don't allow non-removable kids (e.g., the menubar) to move.
+ if (wrapper.firstChild.getAttribute("removable") != "true")
+ return;
+
+ // Remove the item from its place in the toolbar.
+ wrapper.parentNode.removeChild(wrapper);
+
+ // Determine which toolbar we are dropping on.
+ var dropToolbar = null;
+ if (gCurrentDragOverItem.localName == "toolbar")
+ dropToolbar = gCurrentDragOverItem;
+ else
+ dropToolbar = gCurrentDragOverItem.parentNode;
+
+ // Insert the item into the toolbar.
+ if (gCurrentDragOverItem != dropToolbar)
+ dropToolbar.insertBefore(wrapper, gCurrentDragOverItem);
+ else
+ dropToolbar.appendChild(wrapper);
+ } else {
+ // The item has been dragged from the palette
+
+ // Create a new wrapper for the item. We don't know the id yet.
+ let wrapper = createWrapper("", gToolboxDocument);
+
+ // Ask the toolbar to clone the item's template, place it inside the wrapper, and insert it in the toolbar.
+ var newItem = toolbar.insertItem(draggedItemId, gCurrentDragOverItem == toolbar ? null : gCurrentDragOverItem, wrapper);
+
+ // Prepare the item and wrapper to look good on the toolbar.
+ cleanupItemForToolbar(newItem, wrapper);
+ wrapper.id = "wrapper-"+newItem.id;
+ wrapper.flex = newItem.flex;
+
+ // Remove the wrapper from the palette.
+ if (draggedItemId != "separator" &&
+ draggedItemId != "spring" &&
+ draggedItemId != "spacer")
+ gPaletteBox.removeChild(draggedPaletteWrapper);
+ }
+
+ gCurrentDragOverItem = null;
+
+ toolboxChanged();
+}
+
+function onPaletteDragOver(aEvent)
+{
+ if (isUnwantedDragEvent(aEvent)) {
+ return;
+ }
+ var documentId = gToolboxDocument.documentElement.id;
+ if (aEvent.dataTransfer.types.includes("text/toolbarwrapper-id/" + documentId.toLowerCase()))
+ aEvent.preventDefault();
+}
+
+function onPaletteDrop(aEvent)
+{
+ if (isUnwantedDragEvent(aEvent)) {
+ return;
+ }
+ var documentId = gToolboxDocument.documentElement.id;
+ var itemId = aEvent.dataTransfer.getData("text/toolbarwrapper-id/" + documentId);
+
+ var wrapper = gToolboxDocument.getElementById("wrapper-"+itemId);
+ if (wrapper) {
+ // Don't allow non-removable kids (e.g., the menubar) to move.
+ if (wrapper.firstChild.getAttribute("removable") != "true")
+ return;
+
+ var wrapperType = wrapper.getAttribute("type");
+ if (wrapperType != "separator" &&
+ wrapperType != "spacer" &&
+ wrapperType != "spring") {
+ restoreItemForToolbar(wrapper.firstChild, wrapper);
+ wrapPaletteItem(document.importNode(wrapper.firstChild, true));
+ gToolbox.palette.appendChild(wrapper.firstChild);
+ }
+
+ // The item was dragged out of the toolbar.
+ wrapper.parentNode.removeChild(wrapper);
+ }
+
+ toolboxChanged();
+}
+
+
+function isUnwantedDragEvent(aEvent) {
+ try {
+ if (Services.prefs.getBoolPref("toolkit.customization.unsafe_drag_events")) {
+ return false;
+ }
+ } catch (ex) {}
+
+ /* Discard drag events that originated from a separate window to
+ prevent content->chrome privilege escalations. */
+ let mozSourceNode = aEvent.dataTransfer.mozSourceNode;
+ // mozSourceNode is null in the dragStart event handler or if
+ // the drag event originated in an external application.
+ if (!mozSourceNode) {
+ return true;
+ }
+ let sourceWindow = mozSourceNode.ownerDocument.defaultView;
+ return sourceWindow != window && sourceWindow != gToolboxDocument.defaultView;
+}
+
diff --git a/components/global/content/customizeToolbar.xul b/components/global/content/customizeToolbar.xul
new file mode 100644
index 000000000..09c45e834
--- /dev/null
+++ b/components/global/content/customizeToolbar.xul
@@ -0,0 +1,67 @@
+<?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 dialog [
+<!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
+ %customizeToolbarDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://global/content/customizeToolbar.css" type="text/css"?>
+<?xml-stylesheet href="chrome://global/skin/customizeToolbar.css" type="text/css"?>
+
+<window id="CustomizeToolbarWindow"
+ title="&dialog.title;"
+ onload="onLoad();"
+ onunload="onUnload();"
+ style="&dialog.dimensions;"
+ persist="width height"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/javascript" src="chrome://global/content/customizeToolbar.js"/>
+
+<stringbundle id="stringBundle" src="chrome://global/locale/customizeToolbar.properties"/>
+
+<keyset id="CustomizeToolbarKeyset">
+ <key id="cmd_close1" keycode="VK_ESCAPE" oncommand="onClose();"/>
+ <key id="cmd_close2" keycode="VK_RETURN" oncommand="onClose();"/>
+</keyset>
+
+<vbox id="main-box" flex="1">
+ <description id="instructions">
+ &instructions.description;
+ </description>
+
+ <vbox flex="1" id="palette-box"
+ ondragstart="onToolbarDragStart(event)"
+ ondragover="onPaletteDragOver(event)"
+ ondrop="onPaletteDrop(event)"/>
+
+ <box align="center">
+ <label value="&show.label;"/>
+ <menulist id="modelist" value="icons" oncommand="updateToolbarMode(this.value);">
+ <menupopup id="modelistpopup">
+ <menuitem id="modefull" value="full" label="&iconsAndText.label;"/>
+ <menuitem id="modeicons" value="icons" label="&icons.label;"/>
+ <menuitem id="modetext" value="text" label="&text.label;"/>
+ </menupopup>
+ </menulist>
+
+ <checkbox id="smallicons" oncommand="updateIconSize(this.checked ? 'small' : 'large');" label="&useSmallIcons.label;"/>
+
+ <button id="newtoolbar" label="&addNewToolbar.label;" oncommand="addNewToolbar();" icon="add"/>
+ <button id="restoreDefault" label="&restoreDefaultSet.label;" oncommand="restoreDefaultSet();" icon="revert"/>
+ </box>
+
+ <separator class="groove"/>
+
+ <hbox align="center" pack="end">
+ <button id="donebutton" label="&saveChanges.label;" oncommand="onClose();"
+ default="true" icon="close"/>
+ </hbox>
+</vbox>
+
+</window>
diff --git a/components/global/content/datepicker.xhtml b/components/global/content/datepicker.xhtml
new file mode 100644
index 000000000..4da6e398f
--- /dev/null
+++ b/components/global/content/datepicker.xhtml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+ <title>Date Picker</title>
+ <link rel="stylesheet" href="chrome://global/skin/datetimeinputpickers.css"/>
+ <script type="application/javascript" src="chrome://global/content/bindings/datekeeper.js"></script>
+ <script type="application/javascript" src="chrome://global/content/bindings/spinner.js"></script>
+ <script type="application/javascript" src="chrome://global/content/bindings/calendar.js"></script>
+ <script type="application/javascript" src="chrome://global/content/bindings/datepicker.js"></script>
+</head>
+<body>
+ <div id="date-picker">
+ <div class="calendar-container">
+ <div class="nav">
+ <button class="left"/>
+ <button class="right"/>
+ </div>
+ <div class="week-header"></div>
+ <div class="days-viewport">
+ <div class="days-view"></div>
+ </div>
+ </div>
+ <div class="month-year-container">
+ <button class="month-year"/>
+ </div>
+ <div class="month-year-view"></div>
+ </div>
+ <template id="spinner-template">
+ <div class="spinner-container">
+ <button class="up"/>
+ <div class="spinner"></div>
+ <button class="down"/>
+ </div>
+ </template>
+ <script type="application/javascript">
+ // We need to hide the scroll bar but maintain its scrolling
+ // capability, so using |overflow: hidden| is not an option.
+ // Instead, we are inserting a user agent stylesheet that is
+ // capable of selecting scrollbars, and do |display: none|.
+ var domWinUtls = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+ domWinUtls.loadSheetUsingURIString('data:text/css,@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); scrollbar { display: none; }', domWinUtls.AGENT_SHEET);
+ // Create a DatePicker instance and prepare to be
+ // initialized by the "DatePickerInit" event from datetimepopup.xml
+ const root = document.getElementById("date-picker");
+ new DatePicker({
+ monthYear: root.querySelector(".month-year"),
+ monthYearView: root.querySelector(".month-year-view"),
+ buttonLeft: root.querySelector(".left"),
+ buttonRight: root.querySelector(".right"),
+ weekHeader: root.querySelector(".week-header"),
+ daysView: root.querySelector(".days-view")
+ });
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/components/global/content/dialogOverlay.js b/components/global/content/dialogOverlay.js
new file mode 100644
index 000000000..4b03f268f
--- /dev/null
+++ b/components/global/content/dialogOverlay.js
@@ -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/. */
+
+var doOKFunction = 0;
+var doCancelFunction = 0;
+var doButton2Function = 0;
+var doButton3Function = 0;
+
+// call this from dialog onload() to allow ok and cancel to call your code
+// functions should return true if they want the dialog to close
+function doSetOKCancel(okFunc, cancelFunc, button2Func, button3Func )
+{
+ //dump("top.window.navigator.platform: " + top.window.navigator.platform + "\n");
+
+ doOKFunction = okFunc;
+ doCancelFunction = cancelFunc;
+ doButton2Function = button2Func;
+ doButton3Function = button3Func;
+}
+
+function doOKButton()
+{
+ var close = true;
+
+ if ( doOKFunction )
+ close = doOKFunction();
+
+ if (close && top)
+ top.window.close();
+}
+
+function doCancelButton()
+{
+ var close = true;
+
+ if ( doCancelFunction )
+ close = doCancelFunction();
+
+ if (close && top)
+ top.window.close();
+}
+
+function doButton2()
+{
+ var close = true;
+
+ if ( doButton2Function )
+ close = doButton2Function();
+
+ if (close && top)
+ top.window.close();
+}
+
+function doButton3()
+{
+ var close = true;
+
+ if ( doButton3Function )
+ close = doButton3Function();
+
+ if (close && top)
+ top.window.close();
+}
+
+function moveToAlertPosition()
+{
+ // hack. we need this so the window has something like its final size
+ if (window.outerWidth == 1) {
+ dump("Trying to position a sizeless window; caller should have called sizeToContent() or sizeTo(). See bug 75649.\n");
+ sizeToContent();
+ }
+
+ if (opener) {
+ var xOffset = (opener.outerWidth - window.outerWidth) / 2;
+ var yOffset = opener.outerHeight / 5;
+
+ var newX = opener.screenX + xOffset;
+ var newY = opener.screenY + yOffset;
+ } else {
+ newX = (screen.availWidth - window.outerWidth) / 2;
+ newY = (screen.availHeight - window.outerHeight) / 2;
+ }
+
+ // ensure the window is fully onscreen (if smaller than the screen)
+ if (newX < screen.availLeft)
+ newX = screen.availLeft + 20;
+ if ((newX + window.outerWidth) > (screen.availLeft + screen.availWidth))
+ newX = (screen.availLeft + screen.availWidth) - window.outerWidth - 20;
+
+ if (newY < screen.availTop)
+ newY = screen.availTop + 20;
+ if ((newY + window.outerHeight) > (screen.availTop + screen.availHeight))
+ newY = (screen.availTop + screen.availHeight) - window.outerHeight - 60;
+
+ window.moveTo( newX, newY );
+}
+
+function centerWindowOnScreen()
+{
+ var xOffset = screen.availWidth/2 - window.outerWidth/2;
+ var yOffset = screen.availHeight/2 - window.outerHeight/2; //(opener.outerHeight *2)/10;
+
+ xOffset = ( xOffset > 0 ) ? xOffset : 0;
+ yOffset = ( yOffset > 0 ) ? yOffset : 0;
+ window.moveTo( xOffset, yOffset);
+}
diff --git a/components/global/content/dialogOverlay.xul b/components/global/content/dialogOverlay.xul
new file mode 100644
index 000000000..9d4d8b613
--- /dev/null
+++ b/components/global/content/dialogOverlay.xul
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<!-- -*- Mode: XML -*-
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ WARNING!!! This file is obsoleted by the dialog.xml widget
+-->
+
+<!DOCTYPE overlay SYSTEM "chrome://global/locale/dialogOverlay.dtd">
+
+<overlay id="dialogOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://global/content/dialogOverlay.js"/>
+
+ <hbox id="okCancelButtons">
+ <spacer flex="1"/>
+ <button class="right exit-dialog" id="ok" label="&okButton.label;" default="true" oncommand="doOKButton();"/>
+ <button class="exit-dialog" id="Button2" label="&cancelButton.label;" collapsed="true" oncommand="doButton2();"/>
+ <button class="exit-dialog" id="Button3" label="&cancelButton.label;" collapsed="true" oncommand="doButton3();"/>
+ <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/>
+ <spacer flex="1"/>
+ </hbox>
+
+ <hbox id="okCancelHelpButtons">
+ <spacer flex="1"/>
+ <button class="right exit-dialog" id="ok" label="&okButton.label;" default="true" oncommand="doOKButton();"/>
+ <button class="exit-dialog" id="Button2" label="&cancelButton.label;" collapsed="true" oncommand="doButton2();"/>
+ <button class="exit-dialog" id="Button3" label="&cancelButton.label;" collapsed="true" oncommand="doButton3();"/>
+ <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/>
+ <button class="exit-dialog" id="help" label="&helpButton.label;" oncommand="doHelpButton();"/>
+ <spacer flex="1"/>
+ </hbox>
+
+ <hbox id="okCancelButtonsRight">
+ <spacer flex="1"/>
+ <button class="right exit-dialog" id="ok" label="&okButton.label;" default="true" oncommand="doOKButton();"/>
+ <button class="exit-dialog" id="Button2" label="&cancelButton.label;" collapsed="true" oncommand="doButton2();"/>
+ <button class="exit-dialog" id="Button3" label="&cancelButton.label;" collapsed="true" oncommand="doButton3();"/>
+ <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/>
+ </hbox>
+
+ <hbox id="okCancelHelpButtonsRight">
+ <spacer flex="1"/>
+ <button class="right exit-dialog" id="ok" label="&okButton.label;" default="true" oncommand="doOKButton();"/>
+ <button class="exit-dialog" id="Button2" label="&cancelButton.label;" collapsed="true" oncommand="doButton2();"/>
+ <button class="exit-dialog" id="Button3" label="&cancelButton.label;" collapsed="true" oncommand="doButton3();"/>
+ <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/>
+ <button class="exit-dialog" id="help" label="&helpButton.label;" oncommand="doHelpButton();"/>
+ </hbox>
+
+ <keyset id="dialogKeys">
+ <key keycode="VK_RETURN" oncommand="if (!document.getElementById('ok').disabled) doOKButton();"/>
+ <key keycode="VK_ESCAPE" oncommand="doCancelButton();"/>
+ </keyset>
+
+</overlay>
diff --git a/components/global/content/directionDetector.html b/components/global/content/directionDetector.html
new file mode 100644
index 000000000..5e91ba7f5
--- /dev/null
+++ b/components/global/content/directionDetector.html
@@ -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/. -->
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="chrome://global/locale/intl.css">
+ </head>
+ <body>
+ <window id="target" style="display: none;"></window>
+ </body>
+</html>
diff --git a/components/global/content/editMenuOverlay.js b/components/global/content/editMenuOverlay.js
new file mode 100644
index 000000000..a610b641a
--- /dev/null
+++ b/components/global/content/editMenuOverlay.js
@@ -0,0 +1,39 @@
+// -*- 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/. */
+
+// update menu items that rely on focus or on the current selection
+function goUpdateGlobalEditMenuItems()
+{
+ // Don't bother updating the edit commands if they aren't visible in any way
+ // (i.e. the Edit menu isn't open, nor is the context menu open, nor have the
+ // cut, copy, and paste buttons been added to the toolbars) for performance.
+ // This only works in applications/on platforms that set the gEditUIVisible
+ // flag, so we check to see if that flag is defined before using it.
+ if (typeof gEditUIVisible != "undefined" && !gEditUIVisible)
+ return;
+
+ goUpdateCommand("cmd_undo");
+ goUpdateCommand("cmd_redo");
+ goUpdateCommand("cmd_cut");
+ goUpdateCommand("cmd_copy");
+ goUpdateCommand("cmd_paste");
+ goUpdateCommand("cmd_selectAll");
+ goUpdateCommand("cmd_delete");
+ goUpdateCommand("cmd_switchTextDirection");
+}
+
+// update menu items that relate to undo/redo
+function goUpdateUndoEditMenuItems()
+{
+ goUpdateCommand("cmd_undo");
+ goUpdateCommand("cmd_redo");
+}
+
+// update menu items that depend on clipboard contents
+function goUpdatePasteMenuItems()
+{
+ goUpdateCommand("cmd_paste");
+}
diff --git a/components/global/content/editMenuOverlay.xul b/components/global/content/editMenuOverlay.xul
new file mode 100644
index 000000000..909701990
--- /dev/null
+++ b/components/global/content/editMenuOverlay.xul
@@ -0,0 +1,108 @@
+<?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 overlay SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+
+<overlay id="editMenuOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://global/content/editMenuOverlay.js"/>
+
+ <commandset id="editMenuCommands">
+ <commandset id="editMenuCommandSetAll" commandupdater="true" events="focus,select"
+ oncommandupdate="goUpdateGlobalEditMenuItems()"/>
+ <commandset id="editMenuCommandSetUndo" commandupdater="true" events="undo"
+ oncommandupdate="goUpdateUndoEditMenuItems()"/>
+ <commandset id="editMenuCommandSetPaste" commandupdater="true" events="clipboard"
+ oncommandupdate="goUpdatePasteMenuItems()"/>
+ <command id="cmd_undo" oncommand="goDoCommand('cmd_undo')"/>
+ <command id="cmd_redo" oncommand="goDoCommand('cmd_redo')"/>
+ <command id="cmd_cut" oncommand="goDoCommand('cmd_cut')"/>
+ <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')"/>
+ <command id="cmd_paste" oncommand="goDoCommand('cmd_paste')"/>
+ <command id="cmd_delete" oncommand="goDoCommand('cmd_delete')"/>
+ <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/>
+ <command id="cmd_switchTextDirection" oncommand="goDoCommand('cmd_switchTextDirection');"/>
+ </commandset>
+
+ <!-- These key nodes are here only for show. The real bindings come from
+ XBL, in platformHTMLBindings.xml. See bugs 57078 and 71779. -->
+
+ <keyset id="editMenuKeys">
+ <key id="key_undo" key="&undoCmd.key;" modifiers="accel" command="cmd_undo"/>
+#ifdef XP_UNIX
+ <key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift" command="cmd_redo"/>
+#else
+ <key id="key_redo" key="&redoCmd.key;" modifiers="accel" command="cmd_redo"/>
+#endif
+ <key id="key_cut" key="&cutCmd.key;" modifiers="accel" command="cmd_cut"/>
+ <key id="key_copy" key="&copyCmd.key;" modifiers="accel" command="cmd_copy"/>
+ <key id="key_paste" key="&pasteCmd.key;" modifiers="accel" command="cmd_paste"/>
+ <key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/>
+ <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel" command="cmd_selectAll"/>
+ <key id="key_find" key="&findCmd.key;" modifiers="accel" command="cmd_find"/>
+ <key id="key_findAgain" key="&findAgainCmd.key;" modifiers="accel" command="cmd_findAgain"/>
+ <key id="key_findPrevious" key="&findAgainCmd.key;" modifiers="shift,accel" command="cmd_findPrevious"/>
+ <key id="key_findAgain2" keycode="&findAgainCmd.key2;" command="cmd_findAgain"/>
+ <key id="key_findPrevious2" keycode="&findAgainCmd.key2;" modifiers="shift" command="cmd_findPrevious"/>
+ </keyset>
+
+ <!-- Edit Menu -->
+ <menu id="menu_edit" label="&editMenu.label;"
+ accesskey="&editMenu.accesskey;"/>
+
+ <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"/>
+ <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"/>
+ <menuitem id="menu_selectAll" label="&selectAllCmd.label;"
+ key="key_selectAll" accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+ <menuitem id="menu_find" label="&findCmd.label;"
+ key="key_find" accesskey="&findCmd.accesskey;"
+ command="cmd_find"/>
+ <menuitem id="menu_findAgain" label="&findAgainCmd.label;"
+ key="key_findAgain" accesskey="&findAgainCmd.accesskey;"
+ command="cmd_findAgain"/>
+ <menuitem id="menu_findPrevious" label="&findPreviousCmd.label;"
+ key="key_findPrevious" accesskey="&findPreviousCmd.accesskey;"
+ command="cmd_findPrevious"/>
+
+ <menuitem id="cMenu_undo" label="&undoCmd.label;"
+ accesskey="&undoCmd.accesskey;" command="cmd_undo"/>
+ <menuitem id="cMenu_redo" label="&redoCmd.label;"
+ accesskey="&redoCmd.accesskey;" command="cmd_redo"/>
+ <menuitem id="cMenu_cut" label="&cutCmd.label;"
+ accesskey="&cutCmd.accesskey;" command="cmd_cut"/>
+ <menuitem id="cMenu_copy" label="&copyCmd.label;"
+ accesskey="&copyCmd.accesskey;" command="cmd_copy"/>
+ <menuitem id="cMenu_paste" label="&pasteCmd.label;"
+ accesskey="&pasteCmd.accesskey;" command="cmd_paste"/>
+ <menuitem id="cMenu_delete" label="&deleteCmd.label;"
+ accesskey="&deleteCmd.accesskey;" command="cmd_delete"/>
+ <menuitem id="cMenu_selectAll" label="&selectAllCmd.label;"
+ accesskey="&selectAllCmd.accesskey;" command="cmd_selectAll"/>
+ <menuitem id="cMenu_find" label="&findCmd.label;"
+ accesskey="&findCmd.accesskey;" command="cmd_find"/>
+ <menuitem id="cMenu_findAgain" label="&findAgainCmd.label;"
+ accesskey="&findAgainCmd.accesskey;" command="cmd_findAgain"/>
+ <menuitem id="cMenu_findPrevious" label="&findPreviousCmd.label;"
+ accesskey="&findPreviousCmd.accesskey;" command="cmd_findPrevious"/>
+</overlay>
diff --git a/components/global/content/filepicker.properties b/components/global/content/filepicker.properties
new file mode 100644
index 000000000..5be84e3a0
--- /dev/null
+++ b/components/global/content/filepicker.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/.
+
+allFilter=*
+htmlFilter=*.html; *.htm; *.shtml; *.xhtml
+textFilter=*.txt; *.text
+imageFilter=*.jpe; *.jpg; *.jpeg; *.gif; *.png; *.bmp; *.ico; *.svg; *.svgz; *.tif; *.tiff; *.ai; *.drw; *.pct; *.psp; *.xcf; *.psd; *.raw
+xmlFilter=*.xml
+xulFilter=*.xul
+audioFilter=*.aac; *.aif; *.flac; *.iff; *.m4a; *.m4b; *.mid; *.midi; *.mp3; *.mpa; *.mpc; *.oga; *.ogg; *.ra; *.ram; *.snd; *.wav; *.wma
+videoFilter=*.avi; *.divx; *.flv; *.m4v; *.mkv; *.mov; *.mp4; *.mpeg; *.mpg; *.ogm; *.ogv; *.ogx; *.rm; *.rmvb; *.smil; *.webm; *.wmv; *.xvid
diff --git a/components/global/content/findUtils.js b/components/global/content/findUtils.js
new file mode 100644
index 000000000..397a98f6c
--- /dev/null
+++ b/components/global/content/findUtils.js
@@ -0,0 +1,111 @@
+// -*- 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/Services.jsm");
+
+var gFindBundle;
+
+function nsFindInstData() {}
+nsFindInstData.prototype =
+{
+ // set the next three attributes on your object to override the defaults
+ browser : null,
+
+ get rootSearchWindow() { return this._root || this.window.content; },
+ set rootSearchWindow(val) { this._root = val; },
+
+ get currentSearchWindow() {
+ if (this._current)
+ return this._current;
+
+ var focusedWindow = this.window.document.commandDispatcher.focusedWindow;
+ if (!focusedWindow || focusedWindow == this.window)
+ focusedWindow = this.window.content;
+
+ return focusedWindow;
+ },
+ set currentSearchWindow(val) { this._current = val; },
+
+ get webBrowserFind() { return this.browser.webBrowserFind; },
+
+ init : function() {
+ var findInst = this.webBrowserFind;
+ // set up the find to search the focussedWindow, bounded by the content window.
+ var findInFrames = findInst.QueryInterface(Components.interfaces.nsIWebBrowserFindInFrames);
+ findInFrames.rootSearchFrame = this.rootSearchWindow;
+ findInFrames.currentSearchFrame = this.currentSearchWindow;
+
+ // always search in frames for now. We could add a checkbox to the dialog for this.
+ findInst.searchFrames = true;
+ },
+
+ window : window,
+ _root : null,
+ _current : null
+}
+
+// browser is the <browser> element
+// rootSearchWindow is the window to constrain the search to (normally window.content)
+// currentSearchWindow is the frame to start searching (can be, and normally, rootSearchWindow)
+function findInPage(findInstData)
+{
+ // is the dialog up already?
+ if ("findDialog" in window && window.findDialog)
+ window.findDialog.focus();
+ else
+ {
+ findInstData.init();
+ window.findDialog = window.openDialog("chrome://global/content/finddialog.xul", "_blank", "chrome,resizable=no,dependent=yes", findInstData);
+ }
+}
+
+function findAgainInPage(findInstData, reverse)
+{
+ if ("findDialog" in window && window.findDialog)
+ window.findDialog.focus();
+ else
+ {
+ // get the find service, which stores global find state, and init the
+ // nsIWebBrowser find with it. We don't assume that there was a previous
+ // Find that set this up.
+ var findService = Components.classes["@mozilla.org/find/find_service;1"]
+ .getService(Components.interfaces.nsIFindService);
+
+ var searchString = findService.searchString;
+ if (searchString.length == 0) {
+ // no previous find text
+ findInPage(findInstData);
+ return;
+ }
+
+ findInstData.init();
+ var findInst = findInstData.webBrowserFind;
+ findInst.searchString = searchString;
+ findInst.matchCase = findService.matchCase;
+ findInst.wrapFind = findService.wrapFind;
+ findInst.entireWord = findService.entireWord;
+ findInst.findBackwards = findService.findBackwards ^ reverse;
+
+ var found = findInst.findNext();
+ if (!found) {
+ if (!gFindBundle)
+ gFindBundle = document.getElementById("findBundle");
+
+ Services.prompt.alert(window, gFindBundle.getString("notFoundTitle"), gFindBundle.getString("notFoundWarning"));
+ }
+
+ // Reset to normal value, otherwise setting can get changed in find dialog
+ findInst.findBackwards = findService.findBackwards;
+ }
+}
+
+function canFindAgainInPage()
+{
+ var findService = Components.classes["@mozilla.org/find/find_service;1"]
+ .getService(Components.interfaces.nsIFindService);
+ return (findService.searchString.length > 0);
+}
+
diff --git a/components/global/content/finddialog.js b/components/global/content/finddialog.js
new file mode 100644
index 000000000..fc5875321
--- /dev/null
+++ b/components/global/content/finddialog.js
@@ -0,0 +1,151 @@
+// -*- 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/Services.jsm");
+Components.utils.import("resource://gre/modules/FormHistory.jsm");
+
+var dialog; // Quick access to document/form elements.
+var gFindInst; // nsIWebBrowserFind that we're going to use
+var gFindInstData; // use this to update the find inst data
+
+function initDialogObject()
+{
+ // Create dialog object and initialize.
+ dialog = {};
+ dialog.findKey = document.getElementById("dialog.findKey");
+ dialog.caseSensitive = document.getElementById("dialog.caseSensitive");
+ dialog.wrap = document.getElementById("dialog.wrap");
+ dialog.find = document.getElementById("btnFind");
+ dialog.up = document.getElementById("radioUp");
+ dialog.down = document.getElementById("radioDown");
+ dialog.rg = dialog.up.radioGroup;
+ dialog.bundle = null;
+
+ // Move dialog to center, if it not been shown before
+ var windowElement = document.getElementById("findDialog");
+ if (!windowElement.hasAttribute("screenX") || !windowElement.hasAttribute("screenY"))
+ {
+ sizeToContent();
+ moveToAlertPosition();
+ }
+}
+
+function fillDialog()
+{
+ // get the find service, which stores global find state
+ var findService = Components.classes["@mozilla.org/find/find_service;1"]
+ .getService(Components.interfaces.nsIFindService);
+
+ // Set initial dialog field contents. Use the gFindInst attributes first,
+ // this is necessary for window.find()
+ dialog.findKey.value = gFindInst.searchString ? gFindInst.searchString : findService.searchString;
+ dialog.caseSensitive.checked = gFindInst.matchCase ? gFindInst.matchCase : findService.matchCase;
+ dialog.wrap.checked = gFindInst.wrapFind ? gFindInst.wrapFind : findService.wrapFind;
+ var findBackwards = gFindInst.findBackwards ? gFindInst.findBackwards : findService.findBackwards;
+ if (findBackwards)
+ dialog.rg.selectedItem = dialog.up;
+ else
+ dialog.rg.selectedItem = dialog.down;
+}
+
+function saveFindData()
+{
+ // get the find service, which stores global find state
+ var findService = Components.classes["@mozilla.org/find/find_service;1"]
+ .getService(Components.interfaces.nsIFindService);
+
+ // Set data attributes per user input.
+ findService.searchString = dialog.findKey.value;
+ findService.matchCase = dialog.caseSensitive.checked;
+ findService.wrapFind = dialog.wrap.checked;
+ findService.findBackwards = dialog.up.selected;
+}
+
+function onLoad()
+{
+ initDialogObject();
+
+ // get the find instance
+ var arg0 = window.arguments[0];
+ // If the dialog was opened from window.find(),
+ // arg0 will be an instance of nsIWebBrowserFind
+ if (arg0 instanceof Components.interfaces.nsIWebBrowserFind) {
+ gFindInst = arg0;
+ } else {
+ gFindInstData = arg0;
+ gFindInst = gFindInstData.webBrowserFind;
+ }
+
+ fillDialog();
+ doEnabling();
+
+ if (dialog.findKey.value)
+ dialog.findKey.select();
+ dialog.findKey.focus();
+}
+
+function onUnload()
+{
+ window.opener.findDialog = 0;
+}
+
+function onAccept()
+{
+ if (gFindInstData && gFindInst != gFindInstData.webBrowserFind) {
+ gFindInstData.init();
+ gFindInst = gFindInstData.webBrowserFind;
+ }
+
+ // Transfer dialog contents to the find service.
+ saveFindData();
+ updateFormHistory();
+
+ // set up the find instance
+ gFindInst.searchString = dialog.findKey.value;
+ gFindInst.matchCase = dialog.caseSensitive.checked;
+ gFindInst.wrapFind = dialog.wrap.checked;
+ gFindInst.findBackwards = dialog.up.selected;
+
+ // Search.
+ var result = gFindInst.findNext();
+
+ if (!result)
+ {
+ if (!dialog.bundle)
+ dialog.bundle = document.getElementById("findBundle");
+ Services.prompt.alert(window, dialog.bundle.getString("notFoundTitle"),
+ dialog.bundle.getString("notFoundWarning"));
+ dialog.findKey.select();
+ dialog.findKey.focus();
+ }
+ return false;
+}
+
+function doEnabling()
+{
+ dialog.find.disabled = !dialog.findKey.value;
+}
+
+function updateFormHistory()
+{
+ if (window.opener.PrivateBrowsingUtils &&
+ window.opener.PrivateBrowsingUtils.isWindowPrivate(window.opener) ||
+ !dialog.findKey.value)
+ return;
+
+ if (FormHistory.enabled) {
+ FormHistory.update({
+ op: "bump",
+ fieldname: "find-dialog",
+ value: dialog.findKey.value
+ }, {
+ handleError: function(aError) {
+ Components.utils.reportError("Saving find to form history failed: " +
+ aError.message);
+ }
+ });
+ }
+}
diff --git a/components/global/content/finddialog.xul b/components/global/content/finddialog.xul
new file mode 100644
index 000000000..c49092e6f
--- /dev/null
+++ b/components/global/content/finddialog.xul
@@ -0,0 +1,58 @@
+<?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"?>
+
+<!DOCTYPE window SYSTEM "chrome://global/locale/finddialog.dtd">
+
+<dialog id="findDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ orient="horizontal"
+ windowtype="findInPage"
+ onload="onLoad();"
+ onunload="onUnload();"
+ ondialogaccept="return onAccept();"
+ buttons="accept,cancel"
+ title="&findDialog.title;"
+ persist="screenX screenY">
+
+ <script type="application/javascript" src="chrome://global/content/finddialog.js"/>
+ <stringbundle id="findBundle" src="chrome://global/locale/finddialog.properties"/>
+
+ <hbox>
+ <vbox>
+ <hbox align="center">
+ <label value="&findField.label;" accesskey="&findField.accesskey;" control="dialog.findKey"/>
+ <textbox id="dialog.findKey" flex="1"
+ type="autocomplete"
+ autocompletesearch="form-history"
+ autocompletesearchparam="find-dialog"
+ oninput="doEnabling();"/>
+ </hbox>
+ <hbox align="center">
+ <vbox>
+ <checkbox id="dialog.caseSensitive" label="&caseSensitiveCheckbox.label;" accesskey="&caseSensitiveCheckbox.accesskey;"/>
+ <checkbox id="dialog.wrap" label="&wrapCheckbox.label;" accesskey="&wrapCheckbox.accesskey;" checked="true"/>
+ </vbox>
+ <groupbox orient="horizontal">
+ <caption label="&direction.label;"/>
+ <radiogroup orient="horizontal">
+ <radio id="radioUp" label="&up.label;" accesskey="&up.accesskey;"/>
+ <radio id="radioDown" label="&down.label;" accesskey="&down.accesskey;" selected="true"/>
+ </radiogroup>
+ </groupbox>
+ </hbox>
+ </vbox>
+ <vbox>
+ <button id="btnFind" label="&findButton.label;" accesskey="&findButton.accesskey;"
+ dlgtype="accept" icon="find"/>
+#ifdef XP_UNIX
+ <button label="&closeButton.label;" icon="close" dlgtype="cancel"/>
+#else
+ <button label="&cancelButton.label;" icon="cancel" dlgtype="cancel"/>
+#endif
+ </vbox>
+ </hbox>
+</dialog>
diff --git a/components/global/content/globalOverlay.js b/components/global/content/globalOverlay.js
new file mode 100644
index 000000000..d5ee13191
--- /dev/null
+++ b/components/global/content/globalOverlay.js
@@ -0,0 +1,161 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 closeWindow(aClose, aPromptFunction)
+{
+ var windowCount = 0;
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var e = wm.getEnumerator(null);
+
+ while (e.hasMoreElements()) {
+ var w = e.getNext();
+ if (w.closed) {
+ continue;
+ }
+ if (++windowCount == 2)
+ break;
+ }
+
+ // If we're down to the last window and someone tries to shut down, check to make sure we can!
+ if (windowCount == 1 && !canQuitApplication("lastwindow"))
+ return false;
+ if (windowCount != 1 && typeof(aPromptFunction) == "function" && !aPromptFunction())
+ return false;
+
+ if (aClose) {
+ window.close();
+ return window.closed;
+ }
+
+ return true;
+}
+
+function canQuitApplication(aData)
+{
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ if (!os) return true;
+
+ try {
+ var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Components.interfaces.nsISupportsPRBool);
+ os.notifyObservers(cancelQuit, "quit-application-requested", aData || null);
+
+ // Something aborted the quit process.
+ if (cancelQuit.data)
+ return false;
+ }
+ catch (ex) { }
+ return true;
+}
+
+function goQuitApplication()
+{
+ if (!canQuitApplication())
+ return false;
+
+ var appStartup = Components.classes['@mozilla.org/toolkit/app-startup;1'].
+ getService(Components.interfaces.nsIAppStartup);
+
+ appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
+ return true;
+}
+
+//
+// Command Updater functions
+//
+function goUpdateCommand(aCommand)
+{
+ try {
+ var controller = top.document.commandDispatcher
+ .getControllerForCommand(aCommand);
+
+ var enabled = false;
+ if (controller)
+ enabled = controller.isCommandEnabled(aCommand);
+
+ goSetCommandEnabled(aCommand, enabled);
+ }
+ catch (e) {
+ Components.utils.reportError("An error occurred updating the " +
+ aCommand + " command: " + e);
+ }
+}
+
+function goDoCommand(aCommand)
+{
+ try {
+ var controller = top.document.commandDispatcher
+ .getControllerForCommand(aCommand);
+ if (controller && controller.isCommandEnabled(aCommand))
+ controller.doCommand(aCommand);
+ }
+ catch (e) {
+ Components.utils.reportError("An error occurred executing the " +
+ aCommand + " command: " + e);
+ }
+}
+
+
+function goSetCommandEnabled(aID, aEnabled)
+{
+ var node = document.getElementById(aID);
+
+ if (node) {
+ if (aEnabled)
+ node.removeAttribute("disabled");
+ else
+ node.setAttribute("disabled", "true");
+ }
+}
+
+function goSetMenuValue(aCommand, aLabelAttribute)
+{
+ var commandNode = top.document.getElementById(aCommand);
+ if (commandNode) {
+ var label = commandNode.getAttribute(aLabelAttribute);
+ if (label)
+ commandNode.setAttribute("label", label);
+ }
+}
+
+function goSetAccessKey(aCommand, aValueAttribute)
+{
+ var commandNode = top.document.getElementById(aCommand);
+ if (commandNode) {
+ var value = commandNode.getAttribute(aValueAttribute);
+ if (value)
+ commandNode.setAttribute("accesskey", value);
+ }
+}
+
+// this function is used to inform all the controllers attached to a node that an event has occurred
+// (e.g. the tree controllers need to be informed of blur events so that they can change some of the
+// menu items back to their default values)
+function goOnEvent(aNode, aEvent)
+{
+ var numControllers = aNode.controllers.getControllerCount();
+ var controller;
+
+ for (var controllerIndex = 0; controllerIndex < numControllers; controllerIndex++) {
+ controller = aNode.controllers.getControllerAt(controllerIndex);
+ if (controller)
+ controller.onEvent(aEvent);
+ }
+}
+
+function setTooltipText(aID, aTooltipText)
+{
+ var element = document.getElementById(aID);
+ if (element)
+ element.setAttribute("tooltiptext", aTooltipText);
+}
+
+this.__defineGetter__("NS_ASSERT", function() {
+ delete this.NS_ASSERT;
+ var tmpScope = {};
+ Components.utils.import("resource://gre/modules/debug.js", tmpScope);
+ return this.NS_ASSERT = tmpScope.NS_ASSERT;
+});
diff --git a/components/global/content/globalOverlay.xul b/components/global/content/globalOverlay.xul
new file mode 100644
index 000000000..90268a8e4
--- /dev/null
+++ b/components/global/content/globalOverlay.xul
@@ -0,0 +1,38 @@
+<?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/. -->
+
+
+<overlay id="globalOverlay"
+ 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"><![CDATA[
+
+ function FillInTooltip ( tipElement )
+ {
+ var retVal = false;
+ var textNode = document.getElementById("TOOLTIP-tooltipText");
+ if (textNode) {
+ while (textNode.hasChildNodes())
+ textNode.removeChild(textNode.firstChild);
+ var tipText = tipElement.getAttribute("tooltiptext");
+ if (tipText) {
+ var node = document.createTextNode(tipText);
+ textNode.appendChild(node);
+ retVal = true;
+ }
+ }
+ return retVal;
+ }
+
+ ]]></script>
+
+ <popupset id="aTooltipSet">
+ <tooltip id="aTooltip" class="tooltip" onpopupshowing="return FillInTooltip(document.tooltipNode);">
+ <label id="TOOLTIP-tooltipText" class="tooltip-label" flex="1"/>
+ </tooltip>
+ </popupset>
+
+</overlay>
diff --git a/components/global/content/inlineSpellCheckUI.js b/components/global/content/inlineSpellCheckUI.js
new file mode 100644
index 000000000..177ce90cd
--- /dev/null
+++ b/components/global/content/inlineSpellCheckUI.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/. */
+
+Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
+
+var InlineSpellCheckerUI = new InlineSpellChecker();
diff --git a/components/global/content/license.html b/components/global/content/license.html
new file mode 100644
index 000000000..0d3134fe4
--- /dev/null
+++ b/components/global/content/license.html
@@ -0,0 +1,4186 @@
+<!DOCTYPE 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/. -->
+
+<html lang="en">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+ <title>about:license</title>
+
+ <style type="text/css">
+ .path {
+ font-family: monospace;
+ }
+
+ dt {
+ font-weight: bold;
+ }
+ </style>
+ <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css">
+ </head>
+
+ <body id="lic-info" class="aboutPageWideContainer">
+ <h1><a id="top"></a>about:license</h1>
+
+ <div>
+
+#ifdef APP_LICENSE_BLOCK
+#includesubst @APP_LICENSE_BLOCK@
+#endif
+
+ <p>All of the <b>source code</b> to this product is
+ available under licenses which are both
+ <a href="http://www.gnu.org/philosophy/free-sw.html">free</a> and
+ <a href="http://www.opensource.org/docs/definition.php">open source</a>.
+ You can read <a href="https://developer.palemoon.org/">instructions
+ on how to download and build the code for yourself</a>.
+ </p>
+
+ <p>More specifically, most of the source code is available under the
+ <a href="about:license#mpl">Mozilla Public License 2.0</a> (MPL).
+ The MPL has a
+ <a href="http://www.mozilla.org/MPL/2.0/FAQ.html">FAQ</a> to help
+ you understand it. The remainder of the software which is not
+ under the MPL is available under one of a variety of other
+ free and open source licenses. Those that require reproduction
+ of the license text in the distribution are given below.
+ (Note: your copy of this product may not contain code covered by one
+ or more of the licenses listed here, depending on the exact product
+ and version you choose.)
+ </p>
+
+ <ul>
+ <li><a href="about:license#mpl">Mozilla Public License 2.0</a>
+ <br><br>
+ </li>
+ <li><a href="about:license#lgpl">GNU Lesser General Public License 2.1</a>
+ <br><br>
+ </li>
+#ifdef MOZ_DEVTOOLS_SERVER
+ <li><a href="about:license#acorn">acorn License</a></li>
+#endif
+#ifdef MOZ_ANGLE_RENDERER
+ <li><a href="about:license#angle">ANGLE License</a></li>
+#endif
+ <li><a href="about:license#apache">Apache License 2.0</a></li>
+ <li><a href="about:license#apple">Apple License</a></li>
+ <li><a href="about:license#apple-mozilla">Apple/Mozilla NPRuntime License</a></li>
+ <li><a href="about:license#arm">ARM License</a></li>
+#ifdef MOZ_UPDATER
+ <li><a href="about:license#bspatch">bspatch License</a></li>
+#endif
+ <li><a href="about:license#cairo">Cairo Component Licenses</a></li>
+ <li><a href="about:license#chromium">Chromium License</a></li>
+#ifdef MOZ_DEVTOOLS <li><a href="about:license#codemirror">CodeMirror License</a></li>
+ <li><a href="about:license#cubic-bezier">cubic-bezier License</a></li>
+ <li><a href="about:license#d3">D3 License</a></li>
+ <li><a href="about:license#dagre-d3">Dagre-D3 License</a></li>
+#endif
+ <li><a href="about:license#dtoa">dtoa License</a></li>
+ <li><a href="about:license#twemoji">Twemoji License</a></li>
+ <li><a href="about:license#expat">Expat License</a></li>
+ <li><a href="about:license#firebug">Firebug License</a></li>
+ <li><a href="about:license#gfx-font-list">gfxFontList License</a></li>
+ <li><a href="about:license#google-bsd">Google BSD License</a></li>
+ <li><a href="about:license#vp8">Google VP8 License</a></li>
+ <li><a href="about:license#gsl">GSL License</a></li>
+ <li><a href="about:license#gyp">gyp License</a></li>
+ <li><a href="about:license#halloc">halloc License</a></li>
+ <li><a href="about:license#harfbuzz">HarfBuzz License</a></li>
+ <li><a href="about:license#icu">ICU License</a></li>
+#ifdef MOZ_DEVTOOLS
+ <li><a href="about:license#immutable">Immutable.js License</a></li>
+#endif
+ <li><a href="about:license#jpnic">Japan Network Information Center License</a></li>
+ <li><a href="about:license#jquery">jQuery License</a></li>
+ <li><a href="about:license#k_exp">k_exp License</a></li>
+ <li><a href="about:license#khronos">Khronos group License</a></li>
+ <li><a href="about:license#kiss_fft">Kiss FFT License</a></li>
+#ifdef MOZ_USE_LIBCXX
+ <li><a href="about:license#libc++">libc++ License</a></li>
+#endif
+ <li><a href="about:license#libcubeb">libcubeb License</a></li>
+ <li><a href="about:license#libevent">libevent License</a></li>
+ <li><a href="about:license#libffi">libffi License</a></li>
+ <li><a href="about:license#libnestegg">libnestegg License</a></li>
+ <li><a href="about:license#libsoundtouch">libsoundtouch License</a></li>
+ <li><a href="about:license#libyuv">libyuv License</a></li>
+ <li><a href="about:license#microformatsshiv">MIT license — microformat-shiv</a></li>
+ <li><a href="about:license#myspell">MySpell License</a></li>
+#ifdef MOZ_DEVTOOLS
+ <li><a href="about:license#naturalSort">naturalSort License</a></li>
+#endif
+#ifdef MOZ_DEVTOOLS_SERVER
+ <li><a href="about:license#node-properties">node-properties License</a></li>
+#endif
+ <li><a href="about:license#openvision">OpenVision License</a></li>
+ <li><a href="about:license#praton">praton License</a></li>
+ <li><a href="about:license#qcms">qcms License</a></li>
+#ifdef MOZ_DEVTOOLS_SERVER
+ <li><a href="about:license#qrcode-generator">QR Code Generator License</a></li>
+#endif
+ <li><a href="about:license#react">React License</a></li>
+#ifdef MOZ_DEVTOOLS
+ <li><a href="about:license#react-redux">React-Redux License</a></li>
+ <li><a href="about:license#react-virtualized">React Virtualized License</a></li>
+#endif
+ <li><a href="about:license#xdg">Red Hat xdg_user_dir_lookup License</a></li>
+#ifdef MOZ_DEVTOOLS
+ <li><a href="about:license#redux">Redux License</a></li>
+ <li><a href="about:license#reselect">Reselect License</a></li>
+#endif
+ <li><a href="about:license#sctp">SCTP Licenses</a></li>
+#ifdef MOZ_ENABLE_SKIA
+ <li><a href="about:license#skia">Skia License</a></li>
+#endif
+ <li><a href="about:license#snappy">Snappy License</a></li>
+#ifdef MOZ_DEVTOOLS_SERVER
+ <li><a href="about:license#sprintf.js">sprintf.js License</a></li>
+#endif
+ <li><a href="about:license#sunsoft">SunSoft License</a></li>
+ <li><a href="about:license#unicode">Unicode License</a></li>
+ <li><a href="about:license#ucal">University of California License</a></li>
+ <li><a href="about:license#hunspell-en-US">US English Spellchecking Dictionary Licenses</a></li>
+ <li><a href="about:license#v8">V8 License</a></li>
+ <li><a href="about:license#vtune">VTune License</a></li>
+ <li><a href="about:license#xiph">Xiph.org Foundation License</a></li>
+ </ul>
+
+<br>
+
+ <ul>
+ <li><a href="about:license#other-notices">Other Required Notices</a>
+ <li><a href="about:license#optional-notices">Optional Notices</a>
+#ifdef XP_WIN
+ <li><a href="about:license#proprietary-notices">Proprietary Operating System Components</a>
+#endif
+ </ul>
+
+ </div>
+
+ <hr>
+
+ <h1 id="mpl">Mozilla Public License 2.0</h1>
+
+ <h2 id="definitions">1. Definitions</h2>
+
+ <dl>
+ <dt>1.1. "Contributor"</dt>
+
+ <dd>
+ <p>means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.</p>
+ </dd>
+
+ <dt>1.2. "Contributor Version"</dt>
+
+ <dd>
+ <p>means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.</p>
+ </dd>
+
+ <dt>1.3. "Contribution"</dt>
+
+ <dd>
+ <p>means Covered Software of a particular Contributor.</p>
+ </dd>
+
+ <dt>1.4. "Covered Software"</dt>
+
+ <dd>
+ <p>means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code Form,
+ and Modifications of such Source Code Form, in each case including
+ portions thereof.</p>
+ </dd>
+
+ <dt>1.5. "Incompatible With Secondary Licenses"</dt>
+
+ <dd>
+ <p>means</p>
+
+ <ol type="a">
+ <li>
+ <p>that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or</p>
+ </li>
+
+ <li>
+ <p>that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the terms
+ of a Secondary License.</p>
+ </li>
+ </ol>
+ </dd>
+
+ <dt>1.6. "Executable Form"</dt>
+
+ <dd>
+ <p>means any form of the work other than Source Code Form.</p>
+ </dd>
+
+ <dt>1.7. "Larger Work"</dt>
+
+ <dd>
+ <p>means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.</p>
+ </dd>
+
+ <dt>1.8. "License"</dt>
+
+ <dd>
+ <p>means this document.</p>
+ </dd>
+
+ <dt>1.9. "Licensable"</dt>
+
+ <dd>
+ <p>means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and all
+ of the rights conveyed by this License.</p>
+ </dd>
+
+ <dt>1.10. "Modifications"</dt>
+
+ <dd>
+ <p>means any of the following:</p>
+
+ <ol type="a">
+ <li>
+ <p>any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered Software;
+ or</p>
+ </li>
+
+ <li>
+ <p>any new file in Source Code Form that contains any Covered
+ Software.</p>
+ </li>
+ </ol>
+ </dd>
+
+ <dt>1.11. "Patent Claims" of a Contributor</dt>
+
+ <dd>
+ <p>means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the License,
+ by the making, using, selling, offering for sale, having made, import,
+ or transfer of either its Contributions or its Contributor Version.</p>
+ </dd>
+
+ <dt>1.12. "Secondary License"</dt>
+
+ <dd>
+ <p>means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.</p>
+ </dd>
+
+ <dt>1.13. "Source Code Form"</dt>
+
+ <dd>
+ <p>means the form of the work preferred for making modifications.</p>
+ </dd>
+
+ <dt>1.14. "You" (or "Your")</dt>
+
+ <dd>
+ <p>means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that controls,
+ is controlled by, or is under common control with You. For purposes of
+ this definition, "control" means (a) the power, direct or indirect, to
+ cause the direction or management of such entity, whether by contract
+ or otherwise, or (b) ownership of more than fifty percent (50%) of the
+ outstanding shares or beneficial ownership of such entity.</p>
+ </dd>
+ </dl>
+
+ <h2 id="license-grants-and-conditions">2. License Grants and
+ Conditions</h2>
+
+ <h3 id="grants">2.1. Grants</h3>
+
+ <p>Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:</p>
+
+ <ol type="a">
+ <li>
+ <p>under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or as
+ part of a Larger Work; and</p>
+ </li>
+
+ <li>
+ <p>under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.</p>
+ </li>
+ </ol>
+
+ <h3 id="effective-date">2.2. Effective Date</h3>
+
+ <p>The licenses granted in Section 2.1 with respect to any Contribution
+ become effective for each Contribution on the date the Contributor first
+ distributes such Contribution.</p>
+
+ <h3 id="limitations-on-grant-scope">2.3. Limitations on Grant Scope</h3>
+
+ <p>The licenses granted in this Section 2 are the only rights granted under
+ this License. No additional rights or licenses will be implied from the
+ distribution or licensing of Covered Software under this License.
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
+ Contributor:</p>
+
+ <ol type="a">
+ <li>
+ <p>for any code that a Contributor has removed from Covered Software;
+ or</p>
+ </li>
+
+ <li>
+ <p>for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or</p>
+ </li>
+
+ <li>
+ <p>under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.</p>
+ </li>
+ </ol>
+
+ <p>This License does not grant any rights in the trademarks, service marks,
+ or logos of any Contributor (except as may be necessary to comply with the
+ notice requirements in Section 3.4).</p>
+
+ <h3 id="subsequent-licenses">2.4. Subsequent Licenses</h3>
+
+ <p>No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this License
+ (see Section 10.2) or under the terms of a Secondary License (if permitted
+ under the terms of Section 3.3).</p>
+
+ <h3 id="representation">2.5. Representation</h3>
+
+ <p>Each Contributor represents that the Contributor believes its
+ Contributions are its original creation(s) or it has sufficient rights to
+ grant the rights to its Contributions conveyed by this License.</p>
+
+ <h3 id="fair-use">2.6. Fair Use</h3>
+
+ <p>This License is not intended to limit any rights You have under
+ applicable copyright doctrines of fair use, fair dealing, or other
+ equivalents.</p>
+
+ <h3 id="conditions">2.7. Conditions</h3>
+
+ <p>Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+ in Section 2.1.</p>
+
+ <h2 id="responsibilities">3. Responsibilities</h2>
+
+ <h3 id="distribution-of-source-form">3.1. Distribution of Source Form</h3>
+
+ <p>All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under the
+ terms of this License. You must inform recipients that the Source Code Form
+ of the Covered Software is governed by the terms of this License, and how
+ they can obtain a copy of this License. You may not attempt to alter or
+ restrict the recipients' rights in the Source Code Form.</p>
+
+ <h3 id="distribution-of-executable-form">3.2. Distribution of Executable
+ Form</h3>
+
+ <p>If You distribute Covered Software in Executable Form then:</p>
+
+ <ol type="a">
+ <li>
+ <p>such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code Form
+ by reasonable means in a timely manner, at a charge no more than the
+ cost of distribution to the recipient; and</p>
+ </li>
+
+ <li>
+ <p>You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter the
+ recipients' rights in the Source Code Form under this License.</p>
+ </li>
+ </ol>
+
+ <h3 id="distribution-of-a-larger-work">3.3. Distribution of a Larger
+ Work</h3>
+
+ <p>You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for the
+ Covered Software. If the Larger Work is a combination of Covered Software
+ with a work governed by one or more Secondary Licenses, and the Covered
+ Software is not Incompatible With Secondary Licenses, this License permits
+ You to additionally distribute such Covered Software under the terms of
+ such Secondary License(s), so that the recipient of the Larger Work may, at
+ their option, further distribute the Covered Software under the terms of
+ either this License or such Secondary License(s).</p>
+
+ <h3 id="notices">3.4. Notices</h3>
+
+ <p>You may not remove or alter the substance of any license notices
+ (including copyright notices, patent notices, disclaimers of warranty, or
+ limitations of liability) contained within the Source Code Form of the
+ Covered Software, except that You may alter any license notices to the
+ extent required to remedy known factual inaccuracies.</p>
+
+ <h3 id="application-of-additional-terms">3.5. Application of Additional
+ Terms</h3>
+
+ <p>You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on behalf
+ of any Contributor. You must make it absolutely clear that any such
+ warranty, support, indemnity, or liability obligation is offered by You
+ alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.</p>
+
+ <h2 id="inability-to-comply-due-to-statute-or-regulation">4. Inability to
+ Comply Due to Statute or Regulation</h2>
+
+ <p>If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Software due to statute,
+ judicial order, or regulation then You must: (a) comply with the terms of
+ this License to the maximum extent possible; and (b) describe the
+ limitations and the code they affect. Such description must be placed in a
+ text file included with all distributions of the Covered Software under
+ this License. Except to the extent prohibited by statute or regulation,
+ such description must be sufficiently detailed for a recipient of ordinary
+ skill to be able to understand it.</p>
+
+ <h2 id="termination">5. Termination</h2>
+
+ <h3>5.1.</h3>
+
+ <p>The rights granted under this License will terminate automatically
+ if You fail to comply with any of its terms. However, if You become
+ compliant, then the rights granted under this License from a particular
+ Contributor are reinstated (a) provisionally, unless and until such
+ Contributor explicitly and finally terminates Your grants, and (b) on an
+ ongoing basis, if such Contributor fails to notify You of the
+ non-compliance by some reasonable means prior to 60 days after You have
+ come back into compliance. Moreover, Your grants from a particular
+ Contributor are reinstated on an ongoing basis if such Contributor notifies
+ You of the non-compliance by some reasonable means, this is the first time
+ You have received notice of non-compliance with this License from such
+ Contributor, and You become compliant prior to 30 days after Your receipt
+ of the notice.</p>
+
+ <h3>5.2.</h3>
+
+ <p>If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions, counter-claims,
+ and cross-claims) alleging that a Contributor Version directly or
+ indirectly infringes any patent, then the rights granted to You by any and
+ all Contributors for the Covered Software under Section 2.1 of this License
+ shall terminate.</p>
+
+ <h3>5.3.</h3>
+
+ <p>In the event of termination under Sections 5.1 or 5.2 above, all
+ end user license agreements (excluding distributors and resellers) which
+ have been validly granted by You or Your distributors under this License
+ prior to termination shall survive termination.</p>
+
+ <h2 id="disclaimer-of-warranty">6. Disclaimer of Warranty</h2>
+
+ <p><em>Covered Software is provided under this License on an "as is" basis,
+ without warranty of any kind, either expressed, implied, or statutory,
+ including, without limitation, warranties that the Covered Software is free
+ of defects, merchantable, fit for a particular purpose or non-infringing.
+ The entire risk as to the quality and performance of the Covered Software
+ is with You. Should any Covered Software prove defective in any respect,
+ You (not any Contributor) assume the cost of any necessary servicing,
+ repair, or correction. This disclaimer of warranty constitutes an essential
+ part of this License. No use of any Covered Software is authorized under
+ this License except under this disclaimer.</em></p>
+
+ <h2 id="limitation-of-liability">7. Limitation of Liability</h2>
+
+ <p><em>Under no circumstances and under no legal theory, whether tort
+ (including negligence), contract, or otherwise, shall any Contributor, or
+ anyone who distributes Covered Software as permitted above, be liable to
+ You for any direct, indirect, special, incidental, or consequential damages
+ of any character including, without limitation, damages for lost profits,
+ loss of goodwill, work stoppage, computer failure or malfunction, or any
+ and all other commercial damages or losses, even if such party shall have
+ been informed of the possibility of such damages. This limitation of
+ liability shall not apply to liability for death or personal injury
+ resulting from such party's negligence to the extent applicable law
+ prohibits such limitation. Some jurisdictions do not allow the exclusion or
+ limitation of incidental or consequential damages, so this exclusion and
+ limitation may not apply to You.</em></p>
+
+ <h2 id="litigation">8. Litigation</h2>
+
+ <p>Any litigation relating to this License may be brought only in the
+ courts of a jurisdiction where the defendant maintains its principal place
+ of business and such litigation shall be governed by laws of that
+ jurisdiction, without reference to its conflict-of-law provisions. Nothing
+ in this Section shall prevent a party's ability to bring cross-claims or
+ counter-claims.</p>
+
+ <h2 id="miscellaneous">9. Miscellaneous</h2>
+
+ <p>This License represents the complete agreement concerning the subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. Any law or regulation which provides that
+ the language of a contract shall be construed against the drafter shall not
+ be used to construe this License against a Contributor.</p>
+
+ <h2 id="versions-of-the-license">10. Versions of the License</h2>
+
+ <h3 id="new-versions">10.1. New Versions</h3>
+
+ <p>Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.</p>
+
+ <h3 id="effect-of-new-versions">10.2. Effect of New Versions</h3>
+
+ <p>You may distribute the Covered Software under the terms of the version
+ of the License under which You originally received the Covered Software, or
+ under the terms of any subsequent version published by the license
+ steward.</p>
+
+ <h3 id="modified-versions">10.3. Modified Versions</h3>
+
+ <p>If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a modified
+ version of this License if you rename the license and remove any references
+ to the name of the license steward (except to note that such modified
+ license differs from this License).</p>
+
+ <h3 id=
+ "distributing-source-code-form-that-is-incompatible-with-secondary-licenses">
+ 10.4. Distributing Source Code Form that is Incompatible With Secondary
+ Licenses</h3>
+
+ <p>If You choose to distribute Source Code Form that is Incompatible With
+ Secondary Licenses under the terms of this version of the License, the
+ notice described in Exhibit B of this License must be attached.</p>
+
+ <h2 id="exhibit-a---source-code-form-license-notice">Exhibit A - Source
+ Code Form License Notice</h2>
+
+ <blockquote>
+ <p>This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.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>
+ </blockquote>
+
+ <p>If it is not possible or desirable to put the notice in a particular
+ file, then You may include the notice in a location (such as a LICENSE file
+ in a relevant directory) where a recipient would be likely to look for such
+ a notice.</p>
+
+ <p>You may add additional accurate notices of copyright ownership.</p>
+
+ <h2 id="exhibit-b---incompatible-with-secondary-licenses-notice">Exhibit B
+ - "Incompatible With Secondary Licenses" Notice</h2>
+
+ <blockquote>
+ <p>This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.</p>
+ </blockquote>
+
+
+ <hr>
+
+ <h1 id="lgpl">GNU Lesser General Public License 2.1</h1>
+
+<p>This product contains code from the following LGPLed libraries:</p>
+
+<ul>
+<li><a href="http://www.surina.net/soundtouch/">libsoundtouch</a>
+<li><a href="http://libav.org/">libav</a>
+<li><a href="http://ffmpeg.org/">FFmpeg</a>
+</ul>
+
+<pre>
+Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+</pre>
+
+<h3><a id="SEC2">Preamble</a></h3>
+
+<p>
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+</p>
+<p>
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+</p>
+<p>
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+</p>
+<p>
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+</p>
+<p>
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+</p>
+<p>
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+</p>
+<p>
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+</p>
+<p>
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+</p>
+<p>
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+</p>
+<p>
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+</p>
+<p>
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+</p>
+<p>
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+</p>
+<p>
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+</p>
+<p>
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+</p>
+<p>
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+</p>
+
+<h3><a id="SEC3">TERMS AND CONDITIONS FOR COPYING,
+DISTRIBUTION AND MODIFICATION</a></h3>
+
+
+<p>
+<strong>0.</strong>
+This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+</p>
+<p>
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+</p>
+<p>
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+</p>
+<p>
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+</p>
+<p>
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+</p>
+<p>
+<strong>1.</strong>
+You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+</p>
+<p>
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+</p>
+<p>
+<strong>2.</strong>
+You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+</p>
+
+<ul>
+ <li><strong>a)</strong>
+ The modified work must itself be a software library.</li>
+ <li><strong>b)</strong>
+ You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.</li>
+
+ <li><strong>c)</strong>
+ You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.</li>
+
+ <li><strong>d)</strong>
+ If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+ <p>
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)</p></li>
+</ul>
+
+<p>
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Library, and can be
+reasonably considered independent and separate works in themselves, then
+this License, and its terms, do not apply to those sections when you
+distribute them as separate works. But when you distribute the same
+sections as part of a whole which is a work based on the Library, the
+distribution of the whole must be on the terms of this License, whose
+permissions for other licensees extend to the entire whole, and thus to
+each and every part regardless of who wrote it.
+</p>
+<p>
+Thus, it is not the intent of this section to claim rights or contest your
+rights to work written entirely by you; rather, the intent is to exercise
+the right to control the distribution of derivative or collective works
+based on the Library.
+</p>
+<p>
+In addition, mere aggregation of another work not based on the Library with
+the Library (or with a work based on the Library) on a volume of a storage
+or distribution medium does not bring the other work under the scope of
+this License.
+</p>
+<p>
+<strong>3.</strong>
+You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+</p>
+<p>
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+</p>
+<p>
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+</p>
+<p>
+<strong>4.</strong>
+You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+</p>
+<p>
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+</p>
+<p>
+<strong>5.</strong>
+A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+</p>
+<p>
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+</p>
+<p>
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+</p>
+<p>
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+</p>
+<p>
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+</p>
+<p>
+<strong>6.</strong>
+As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+</p>
+<p>
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+</p>
+
+<ul>
+ <li><strong>a)</strong> Accompany the work with the complete
+ corresponding machine-readable source code for the Library
+ including whatever changes were used in the work (which must be
+ distributed under Sections 1 and 2 above); and, if the work is an
+ executable linked with the Library, with the complete
+ machine-readable "work that uses the Library", as object code
+ and/or source code, so that the user can modify the Library and
+ then relink to produce a modified executable containing the
+ modified Library. (It is understood that the user who changes the
+ contents of definitions files in the Library will not necessarily
+ be able to recompile the application to use the modified
+ definitions.)</li>
+
+ <li><strong>b)</strong> Use a suitable shared library mechanism
+ for linking with the Library. A suitable mechanism is one that
+ (1) uses at run time a copy of the library already present on the
+ user's computer system, rather than copying library functions into
+ the executable, and (2) will operate properly with a modified
+ version of the library, if the user installs one, as long as the
+ modified version is interface-compatible with the version that the
+ work was made with.</li>
+
+ <li><strong>c)</strong> Accompany the work with a written offer,
+ valid for at least three years, to give the same user the
+ materials specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.</li>
+
+ <li><strong>d)</strong> If distribution of the work is made by
+ offering access to copy from a designated place, offer equivalent
+ access to copy the above specified materials from the same
+ place.</li>
+
+ <li><strong>e)</strong> Verify that the user has already received
+ a copy of these materials or that you have already sent this user
+ a copy.</li>
+</ul>
+
+<p>
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+</p>
+<p>
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+</p>
+<p>
+<strong>7.</strong> You may place library facilities that are a work
+based on the Library side-by-side in a single library together with
+other library facilities not covered by this License, and distribute
+such a combined library, provided that the separate distribution of
+the work based on the Library and of the other library facilities is
+otherwise permitted, and provided that you do these two things:
+</p>
+
+<ul>
+ <li><strong>a)</strong> Accompany the combined library with a copy
+ of the same work based on the Library, uncombined with any other
+ library facilities. This must be distributed under the terms of
+ the Sections above.</li>
+
+ <li><strong>b)</strong> Give prominent notice with the combined
+ library of the fact that part of it is a work based on the
+ Library, and explaining where to find the accompanying uncombined
+ form of the same work.</li>
+</ul>
+
+<p>
+<strong>8.</strong> You may not copy, modify, sublicense, link with,
+or distribute the Library except as expressly provided under this
+License. Any attempt otherwise to copy, modify, sublicense, link
+with, or distribute the Library is void, and will automatically
+terminate your rights under this License. However, parties who have
+received copies, or rights, from you under this License will not have
+their licenses terminated so long as such parties remain in full
+compliance.
+</p>
+<p>
+<strong>9.</strong>
+You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+</p>
+<p>
+<strong>10.</strong>
+Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+</p>
+<p>
+<strong>11.</strong>
+If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+</p>
+<p>
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+</p>
+<p>
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+</p>
+<p>
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+</p>
+<p>
+<strong>12.</strong>
+If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+</p>
+<p>
+<strong>13.</strong>
+The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+</p>
+<p>
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+</p>
+<p>
+<strong>14.</strong>
+If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+</p>
+<p>
+<strong>NO WARRANTY</strong>
+</p>
+<p>
+<strong>15.</strong>
+BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+</p>
+<p>
+<strong>16.</strong>
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+</p>
+
+ <hr>
+#ifdef MOZ_ANGLE_RENDERER
+ <h1><a id="angle"></a>ANGLE License</h1>
+
+ <p>This license applies to files in the directory <span class="path">system/graphics/angle/</span>.</p>
+
+<pre>
+Copyright (C) 2002-2010 The ANGLE Project Authors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc.
+ Ltd., nor the names of their contributors may be used to endorse
+ or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+#endif
+#ifdef MOZ_DEVTOOLS_SERVER
+ <h1><a id="acorn"></a>acorn License</h1>
+
+ <p>This license applies to all files in
+ <span class="path">devtools/shared/acorn</span>.
+ </p>
+<pre>
+Copyright (C) 2012 by Marijn Haverbeke &lt;marijnh@gmail.com&gt;
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Please note that some subdirectories of the CodeMirror distribution
+include their own LICENSE files, and are released under different
+licences.
+</pre>
+
+
+ <hr>
+#endif
+
+ <h1><a id="apache"></a>Apache License 2.0</h1>
+
+ <p>This license applies to various files in the Mozilla codebase.</p>
+
+<pre>
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+</pre>
+
+
+
+ <hr>
+
+ <h1><a id="apple"></a>Apple License</h1>
+
+ <p>This license applies to certain files in the directories <span class="path">dom/media/webaudio/blink</span>.</p>
+
+<pre>
+Copyright (C) 2008, 2009 Apple Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="apple-mozilla"></a>Apple/Mozilla NPRuntime License</h1>
+
+ <p>This license applies to the file
+ <span class="path">dom/plugins/base/npruntime.h</span>.</p>
+
+<pre>
+Copyright &copy; 2004, Apple Computer, Inc. and The Mozilla Foundation.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+3. Neither the names of Apple Computer, Inc. ("Apple") or The Mozilla
+Foundation ("Mozilla") nor the names of their contributors may be used
+to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY APPLE, MOZILLA AND THEIR CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE, MOZILLA OR
+THEIR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="arm"></a>ARM License</h1>
+
+ <p>This license applies to files in the directory <span class="path">js/src/jit/arm64/vixl/</span>.</p>
+
+<pre>
+Copyright 2013, ARM Limited
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of ARM Limited nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+
+#ifdef MOZ_UPDATER
+ <h1><a id="bspatch"></a>bspatch License</h1>
+
+ <p>This license applies to the files
+ <span class="path">system/updater/app/bspatch.cpp</span> and
+ <span class="path">system/updater/app/bspatch.h</span>.
+ </p>
+
+<pre>
+Copyright 2003,2004 Colin Percival
+All rights reserved
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted providing that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+#endif
+
+ <h1><a id="cairo"></a>Cairo Component Licenses</h1>
+
+ <p>This license, with different copyright holders, applies to certain files
+ in the directory <span class="path">system/graphics/cairo/</span>. The copyright
+ holders and the applicable ranges of dates are as follows:
+
+ <ul>
+<li>2004 Richard D. Worth
+<li>2004, 2005 Red Hat, Inc.
+<li>2003 USC, Information Sciences Institute
+<li>2004 David Reveman
+<li>2005 Novell, Inc.
+<li>2004 David Reveman, Peter Nilsson
+<li>2000 Keith Packard, member of The XFree86 Project, Inc.
+<li>2005 Lars Knoll &amp; Zack Rusin, Trolltech
+<li>1998, 2000, 2002, 2004 Keith Packard
+<li>2004 Nicholas Miell
+<li>2005 Trolltech AS
+<li>2000 SuSE, Inc.
+<li>2003 Carl Worth
+<li>1987, 1988, 1989, 1998 The Open Group
+<li>1987, 1988, 1989 Digital Equipment Corporation, Maynard, Massachusetts.
+<li>1998 Keith Packard
+<li>2003 Richard Henderson
+ </ul>
+
+<pre>
+Copyright &copy; &lt;date&gt; &lt;copyright holder&gt;
+
+Permission to use, copy, modify, distribute, and sell this software
+and its documentation for any purpose is hereby granted without
+fee, provided that the above copyright notice appear in all copies
+and that both that copyright notice and this permission notice
+appear in supporting documentation, and that the name of
+&lt;copyright holder&gt; not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior permission.
+&lt;copyright holder&gt; makes no representations about the suitability of this
+software for any purpose. It is provided "as is" without express or
+implied warranty.
+
+&lt;COPYRIGHT HOLDER&gt; DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+NO EVENT SHALL &lt;COPYRIGHT HOLDER&gt; BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="chromium"></a>Chromium License</h1>
+
+ <p>This license applies to parts of the code in:</p>
+ <ul>
+ <li><span class="path">editor/libeditor/EditorEventListener.cpp</span></li>
+ </ul>
+ <p>and also some files in these directories:</p>
+ <ul>
+ <li><span class="path">dom/media/webspeech/recognition/</span></li>
+ <li><span class="path">dom/plugins/</span></li>
+ <li><span class="path">system/graphics/ots/</span></li>
+ <li><span class="path">system/graphics/ycbcr/</span></li>
+ <li><span class="path">ipc/chromium/</span></li>
+ <li><span class="path">libs/openmax_dl/</span></li>
+ <li><span class="path">tools/profiler/</span></li>
+ </ul>
+ <p>((year)) is a placeholder for the applicable copyright years stated in the code.
+ See LICENSE files in various directories for details.</p>
+
+<pre>
+Copyright (c) ((year)) The Chromium Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+#ifdef MOZ_DEVTOOLS
+ <h1><a id="codemirror"></a>CodeMirror License</h1>
+
+ <p>This license applies to all files in
+ <span class="path">devtools/client/sourceeditor/codemirror</span> and
+ to specified files in the <span class="path">devtools/client/sourceeditor/test/</span>:
+ </p>
+ <ul>
+ <li><span class="path">cm_comment_test.js</span></li>
+ <li><span class="path">cm_driver.js</span></li>
+ <li><span class="path">cm_mode_javascript_test.js</span></li>
+ <li><span class="path">cm_mode_test.css</span></li>
+ <li><span class="path">cm_mode_test.js</span></li>
+ <li><span class="path">cm_test.js</span></li>
+ </ul>
+<pre>
+Copyright (C) 2013 by Marijn Haverbeke <marijnh@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Please note that some subdirectories of the CodeMirror distribution
+include their own LICENSE files, and are released under different
+licences.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="cubic-bezier"></a>cubic-bezier License</h1>
+
+ <p>This license applies to the file
+ <span class="path">devtools/client/shared/widgets/CubicBezierWidget.js
+ </span>.</p>
+<pre>
+Copyright (c) 2013 Lea Verou. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="d3"></a>D3 License</h1>
+
+ <p>This license applies to the file
+ <span class="path">devtools/client/shared/vendor/d3.js</span>.
+ </p>
+<pre>
+Copyright (c) 2014, Michael Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* The name Michael Bostock may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="dagre-d3"></a>Dagre-D3 License</h1>
+
+ <p>This license applies to the file
+ <span class="path">devtools/client/webaudioeditor/lib/dagre-d3.js</span>.
+ </p>
+<pre>
+Copyright (c) 2013 Chris Pettitt
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+</pre>
+
+
+ <hr>
+#endif
+
+ <h1><a id="dtoa"></a>dtoa License</h1>
+
+ <p>This license applies to the file
+ <span class="path">libs/nspr/pr/src/misc/dtoa.c</span>.</p>
+
+<pre>
+The author of this software is David M. Gay.
+
+Copyright (c) 1991, 2000, 2001 by Lucent Technologies.
+
+Permission to use, copy, modify, and distribute this software for any
+purpose without fee is hereby granted, provided that this entire notice
+is included in all copies of any software which is or includes a copy
+or modification of this software and in all copies of the supporting
+documentation for such software.
+
+THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
+REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="twemoji"></a>Twemoji License</h1>
+
+ <p>This license applies to the emoji art contained within the bundled
+emoji font file.</p>
+
+<pre>
+Copyright (c) 2018 Twitter, Inc and other contributors.
+
+Creative Commons Attribution 4.0 International (CC BY 4.0)
+
+See https://creativecommons.org/licenses/by/4.0/legalcode or
+for the human readable summary: https://creativecommons.org/licenses/by/4.0/
+
+You are free to:
+
+Share — copy and redistribute the material in any medium or format
+
+Adapt — remix, transform, and build upon the material for any purpose, even commercially.
+
+The licensor cannot revoke these freedoms as long as you follow the license terms.
+
+Under the following terms:
+
+Attribution — You must give appropriate credit, provide a link to the license,
+and indicate if changes were made. You may do so in any reasonable manner,
+but not in any way that suggests the licensor endorses you or your use.
+
+No additional restrictions — You may not apply legal terms or technological
+measures that legally restrict others from doing anything the license permits.
+
+Notices:
+
+You do not have to comply with the license for elements of the material in
+the public domain or where your use is permitted by an applicable exception or
+limitation. No warranties are given. The license may not give you all of the
+permissions necessary for your intended use. For example, other rights such as
+publicity, privacy, or moral rights may limit how you use the material.
+</pre>
+
+
+ <hr>
+
+
+ <h1><a id="expat"></a>Expat License</h1>
+
+ <p>This license applies to certain files in the directory
+ <span class="path">libs/expat/</span>.</p>
+
+<pre>
+Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd
+ and Clark Cooper
+Copyright (c) 2001, 2002, 2003 Expat maintainers.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+</pre>
+
+
+
+ <hr>
+
+#ifdef MOZ_DEVTOOLS_SERVER <h1><a id="firebug"></a>Firebug License</h1>
+
+ <p>This license applies to the code
+ <span class="path">devtools/shared/webconsole/network-helper.js</span>.</p>
+
+<pre>
+Copyright (c) 2007, Parakey Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, with or
+without modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of Parakey Inc. nor the names of its
+ contributors may be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of Parakey Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+
+ <hr>
+#endif
+
+ <h1><a id="gfx-font-list"></a>gfxFontList License</h1>
+
+ <p>This license applies to the files
+ <span class="path">system/graphics/thebes/gfxMacPlatformFontList.mm</span> and
+ <span class="path">system/graphics/thebes/gfxPlatformFontList.cpp</span>.
+ </p>
+
+<pre>
+Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ its contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+
+ <hr>
+
+ <h1><a id="google-bsd"></a>Google BSD License</h1>
+
+ <p>This license applies to files in the directories
+ <span class="path">components/protobuf/</span>.</p>
+
+<pre>
+Copyright (c) 2006, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="vpx"></a>VPX License</h1>
+
+ <p>This license applies to the files in the directory
+ <span class="path">libs/libvpx</span>.</p>
+<pre>
+Copyright (c) 2010, The WebM Project authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Google, nor the WebM Project, nor the names
+ of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Additional IP Rights Grant (Patents)
+------------------------------------
+
+"These implementations" means the copyrightable works that implement the WebM
+codecs distributed by Google as part of the WebM Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge,
+royalty-free, irrevocable (except as stated in this section) patent license to
+make, have made, use, offer to sell, sell, import, transfer, and otherwise
+run, modify and propagate the contents of these implementations of WebM, where
+such license applies only to those patent claims, both currently owned by
+Google and acquired in the future, licensable by Google that are necessarily
+infringed by these implementations of WebM. This grant does not include claims
+that would be infringed only as a consequence of further modification of these
+implementations. If you or your agent or exclusive licensee institute or order
+or agree to the institution of patent litigation or any other patent
+enforcement activity against any entity (including a cross-claim or
+counterclaim in a lawsuit) alleging that any of these implementations of WebM
+or any code incorporated within any of these implementations of WebM
+constitute direct or contributory patent infringement, or inducement of
+patent infringement, then any patent rights granted to you under this License
+for these implementations of WebM shall terminate as of the date such
+litigation is filed.
+</pre>
+
+ <hr>
+
+ <h1><a id="gsl"></a>GSL License</h1>
+
+ <p>This license applies to <span class="path">mfbt/Span.h</span> and
+ <span class="path">mfbt/tests/gtest/TestSpan.cpp</span>.</p>
+ <!-- https://github.com/Microsoft/GSL/blob/3819df6e378ffccf0e29465afe99c3b324c2aa70/LICENSE -->
+<pre>
+Copyright (c) 2015 Microsoft Corporation. All rights reserved.
+Copyright (c) 2020 Moonchild Productions. All rights reserved.
+
+This code is licensed under the MIT License (MIT).
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+</pre>
+
+
+ <hr>
+ <h1><a id="gyp"></a>gyp License</h1>
+
+ <p>This license applies to certain files in the directory
+ <span class="path">gyp-deps/tools/gyp</span>.</p>
+<pre>
+Copyright (c) 2009 Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="halloc"></a>halloc License</h1>
+
+ <p>This license applies to certain files in the directory
+ <span class="path">libs/libnestegg/src</span>.</p>
+<pre>
+Copyright (c) 2004-2010 Alex Pankratov. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the project nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+ <hr>
+
+ <h1><a id="harfbuzz"></a>HarfBuzz License</h1>
+
+ <p>This license, with different copyright holders, applies to the files in
+ the directory <span class="path">gfx/harfbuzz/</span>.
+ The copyright holders and the applicable ranges of dates are as follows:</p>
+
+ <ul>
+ <li>1998-2004 David Turner and Werner Lemberg</li>
+ <li>2004, 2007, 2008, 2009, 2010 Red Hat, Inc.</li>
+ <li>2006 Behdad Esfahbod</li>
+ <li>2007 Chris Wilson</li>
+ <li>2009 Keith Stribley &lt;devel@thanlwinsoft.org&gt;</li>
+ <li>2010 Mozilla Foundation</li>
+ </ul>
+
+<pre>
+Copyright (C) &lt;date&gt; &lt;copyright holder&gt;
+
+ This is part of HarfBuzz, an OpenType Layout engine library.
+
+Permission is hereby granted, without written agreement and without
+license or royalty fees, to use, copy, modify, and distribute this
+software and its documentation for any purpose, provided that the
+above copyright notice and the following two paragraphs appear in
+all copies of this software.
+
+IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
+ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="icu"></a>ICU License</h1>
+
+ <p>This license applies to some code in the
+ <span class="path">gfx/thebes</span> directory.</p>
+
+<pre>
+ICU License - ICU 1.8.1 and later
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright (c) 1995-2012 International Business Machines Corporation and
+others
+
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, provided that the above copyright notice(s) and this
+permission notice appear in all copies of the Software and that both the
+above copyright notice(s) and this permission notice appear in supporting
+documentation.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
+BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
+OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall
+not be used in advertising or otherwise to promote the sale, use or other
+dealings in this Software without prior written authorization of the
+copyright holder.
+All trademarks and registered trademarks mentioned herein are the property
+of their respective owners.
+</pre>
+ <hr>
+
+#ifdef MOZ_DEVTOOLS
+ <h1><a id="immutable"></a>Immutable.js License</h1>
+
+<pre>
+BSD License
+
+For Immutable JS software
+
+Copyright (c) 2014-2015, Facebook, Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Facebook nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+ <hr>
+#endif
+
+ <h1><a id="jpnic"></a>Japan Network Information Center License</h1>
+ <p>This license applies to certain files in the
+ directory <span class="path">system/network/dns/</span>.</p>
+<pre>
+Copyright (c) 2001,2002 Japan Network Information Center.
+All rights reserved.
+
+By using this file, you agree to the terms and conditions set forth below.
+
+ LICENSE TERMS AND CONDITIONS
+
+The following License Terms and Conditions apply, unless a different
+license is obtained from Japan Network Information Center ("JPNIC"),
+a Japanese association, Kokusai-Kougyou-Kanda Bldg 6F, 2-3-4 Uchi-Kanda,
+Chiyoda-ku, Tokyo 101-0047, Japan.
+
+1. Use, Modification and Redistribution (including distribution of any
+ modified or derived work) in source and/or binary forms is permitted
+ under this License Terms and Conditions.
+
+2. Redistribution of source code must retain the copyright notices as they
+ appear in each source code file, this License Terms and Conditions.
+
+3. Redistribution in binary form must reproduce the Copyright Notice,
+ this License Terms and Conditions, in the documentation and/or other
+ materials provided with the distribution. For the purposes of binary
+ distribution the "Copyright Notice" refers to the following language:
+ "Copyright (c) 2000-2002 Japan Network Information Center. All rights
+ reserved."
+
+4. The name of JPNIC may not be used to endorse or promote products
+ derived from this Software without specific prior written approval of
+ JPNIC.
+
+5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JPNIC BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+</pre>
+
+ <hr>
+
+ <h1><a id="jquery"></a>jQuery License</h1>
+
+ <p>This license applies to all copies of jQuery in the code.</p>
+
+<pre>
+Copyright (c) 2010 John Resig, http://jquery.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+</pre>
+
+ <hr>
+
+ <h1><a id="k_exp"></a>k_exp License</h1>
+
+ <p>This license applies to the file
+ <span class="path">libs/fdlibm/src/k_exp.cpp</span>.
+ </p>
+
+<pre>
+Copyright (c) 2011 David Schultz &lt;das@FreeBSD.ORG&gt;
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+</pre>
+
+ <hr>
+
+ <h1><a id="khronos"></a>Khronos group License</h1>
+
+ <p>This license applies to the following files:</p>
+
+ <ul>
+ <li class="path">libs/openmax_dl/dl/api/omxtypes.h</li>
+ <li class="path">libs/openmax_dl/dl/sp/api/omxSP.h</li>
+ </ul>
+
+<pre>
+Copyright 2005-2008 The Khronos Group Inc. All Rights Reserved.
+
+These materials are protected by copyright laws and contain material
+proprietary to the Khronos Group, Inc. You may use these materials
+for implementing Khronos specifications, without altering or removing
+any trademark, copyright or other notice from the specification.
+
+Khronos Group makes no, and expressly disclaims any, representations
+or warranties, express or implied, regarding these materials, including,
+without limitation, any implied warranties of merchantability or fitness
+for a particular purpose or non-infringement of any intellectual property.
+Khronos Group makes no, and expressly disclaims any, warranties, express
+or implied, regarding the correctness, accuracy, completeness, timeliness,
+and reliability of these materials.
+
+Under no circumstances will the Khronos Group, or any of its Promoters,
+Contributors or Members or their respective partners, officers, directors,
+employees, agents or representatives be liable for any damages, whether
+direct, indirect, special or consequential damages for lost revenues,
+lost profits, or otherwise, arising from or in connection with these
+materials.
+
+Khronos and OpenMAX are trademarks of the Khronos Group Inc.
+</pre>
+
+ <hr>
+
+ <h1><a id="kiss_fft"></a>Kiss FFT License</h1>
+
+ <p>This license applies to files in the directory
+ <span class="path">libs/kiss_fft/</span>.</p>
+
+<pre>
+Copyright (c) 2003-2010 Mark Borgerding
+Copyright (c) 2017 Mark Straver BASc
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the author nor the names of any contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+ <hr>
+
+#ifdef MOZ_USE_LIBCXX
+ <h1><a id="libc++"></a>libc++ License</h1>
+
+ <p class="correctme">This license applies to the copy of libc++ obtained
+ from the Android NDK.</p>
+
+<pre>
+Copyright (c) 2009-2014 by the contributors listed in the libc++ CREDITS.TXT
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+</pre>
+
+ <hr>
+
+#endif
+
+ <h1><a id="libcubeb"></a>libcubeb License</h1>
+
+ <p class="correctme">This license applies to files in the directory
+ <span class="path">libs/libcubeb</span>.
+ </p>
+
+<pre>
+Copyright &copy; 2011 Mozilla Foundation
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+</pre>
+
+ <hr>
+
+ <h1><a id="libevent"></a>libevent License</h1>
+
+ <p>This license applies to files in the directory
+ <span class="path">ipc/chromium/src/third_party/libevent/</span>.
+ </p>
+
+<pre>
+Copyright 2000-2002 Niels Provos &lt;provos@citi.umich.edu&gt;
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="libffi"></a>libffi License</h1>
+
+ <p>This license applies to files in the directory
+ <span class="path">js/src/ctypes/libffi/</span>.
+ </p>
+
+<pre>
+libffi - Copyright (c) 1996-2008 Red Hat, Inc and others.
+See source files for details.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+``Software''), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="libnestegg"></a>libnestegg License</h1>
+
+ <p>This license applies to certain files in the directory
+ <span class="path">libs/libnestegg</span>.
+ </p>
+
+<pre>
+Copyright &copy; 2010 Mozilla Foundation
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+</pre>
+
+ <hr>
+
+ <h1><a id="libsoundtouch"></a>libsoundtouch License</h1>
+
+ <p>This license applies to certain files in the directory
+ <span class="path">libs/libsoundtouch/src/</span>.
+ </p>
+
+<pre>
+The SoundTouch Library Copyright &copy; Olli Parviainen 2001-2012
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+</pre>
+
+ <hr>
+
+ <h1><a id="libyuv"></a>libyuv License</h1>
+
+ <p>This license applies to files in the directory
+ <span class="path">libs/libyuv</span> except
+ for the file <span class="path">libs/libyuv/source/x86inc.asm</span>.
+ </p>
+
+<pre>
+Copyright (c) 2011, The LibYuv project authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Google nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+ <hr>
+
+ <h1><a id="microformatsshiv"></a>MIT license — microformat-shiv</h1>
+
+ <p>This license applies to some files in the directory
+ <span class="path">components/microformats</span>.</p>
+
+<pre>
+MIT license — microformat-shiv
+
+Copyright (c) 2012-2013 Glenn Jones
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="myspell"></a>MySpell License</h1>
+
+ <p>This license applies to some files in the directory
+ <span class="path">libs/hunspell</span>.</p>
+
+<pre>
+Copyright 2002 Kevin B. Hendricks, Stratford, Ontario, Canada
+And Contributors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. All modifications to the source code must be clearly marked as
+ such. Binary redistributions based on modified source code
+ must be clearly marked as modified versions in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY KEVIN B. HENDRICKS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+KEVIN B. HENDRICKS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+</pre>
+
+
+<hr>
+#ifdef MOZ_DEVTOOLS
+<h1><a id="naturalSort"></a>naturalSort License</h1>
+
+<p>This license applies to <span class="path">devtools/client/shared/natural-sort.js</span>.</p>
+
+<pre>
+The MIT License (MIT)
+
+Copyright (c) 2014 Gabriel Llamas
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+</pre>
+
+ <hr>
+#endif
+ <h1><a id="openvision"></a>OpenVision License</h1>
+
+ <p>This license applies to the file
+ <span class="path">extensions/auth/gssapi.h</span>.</p>
+
+<pre>
+Copyright 1993 by OpenVision Technologies, Inc.
+
+Permission to use, copy, modify, distribute, and sell this software
+and its documentation for any purpose is hereby granted without fee,
+provided that the above copyright notice appears in all copies and
+that both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of OpenVision not be used
+in advertising or publicity pertaining to distribution of the software
+without specific, written prior permission. OpenVision makes no
+representations about the suitability of this software for any
+purpose. It is provided "as is" without express or implied warranty.
+
+OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+</pre>
+
+ <hr>
+#ifdef MOZ_DEVTOOLS_SERVER
+ <h1><a id="node-properties"></a>node-properties License</h1>
+
+ <p>This license applies to
+ <span class="path">devtools/shared/node-properties/node-properties.js</span>.</p>
+
+<pre>
+The MIT License (MIT)
+
+Copyright (c) 2014 Gabriel Llamas
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+</pre>
+
+ <hr>
+#endif
+ <h1><a id="praton"></a>praton License</h1>
+
+ <p>This license applies to the file
+ <span class="path">libs/nspr/pr/src/misc/praton.c</span>.</p>
+
+<pre>
+Copyright (c) 1983, 1990, 1993
+ The Regents of the University of California. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+4. Neither the name of the University nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+
+Portions Copyright (c) 1993 by Digital Equipment Corporation.
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies, and that
+the name of Digital Equipment Corporation not be used in advertising or
+publicity pertaining to distribution of the document or software without
+specific, written prior permission.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT
+CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+SOFTWARE.
+
+
+Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+Portions Copyright (c) 1996-1999 by Internet Software Consortium.
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="qcms"></a>qcms License</h1>
+
+ <p>This license applies to certain files in the directory
+ <span class="path">system/graphics/qcms/</span>.</p>
+<pre>
+Copyright (C) 2009 Mozilla Corporation
+Copyright (C) 1998-2007 Marti Maria
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so, subject
+to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+</pre>
+
+
+ <hr>
+
+#ifdef MOZ_DEVTOOLS_SERVER
+ <h1><a id="qrcode-generator"></a>QR Code Generator License</h1>
+
+ <p>This license applies to certain files in the directory
+ <span class="path">devtools/shared/qrcode/encoder/</span>.</p>
+<pre>
+Copyright (c) 2009 Kazuhiko Arase
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+</pre>
+
+
+ <hr>
+#endif
+
+ <h1><a id="react"></a>React License</h1>
+
+ <p>This license applies to various files in the Mozilla codebase.</p>
+
+<pre>
+Copyright (c) 2013-2015, Facebook, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Facebook nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+ <hr>
+
+#ifdef MOZ_DEVTOOLS
+ <h1><a id="react-redux"></a>React-Redux License</h1>
+
+ <p>This license applies to the file
+ <span class="path">devtools/client/shared/vendor/react-redux.js</span>.</p>
+<pre>
+Copyright (c) 2015 Dan Abramov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+</pre>
+
+ <hr>
+
+ <h1><a id="react-virtualized"></a>React Virtualized License</h1>
+
+ <p>This license applies to the file
+ <span class="path">devtools/client/shared/vendor/react-virtualized.js</span>.</p>
+<pre>
+Copyright (c) 2015 Brian Vaughn
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+</pre>
+
+ <hr>
+#endif
+
+ <h1><a id="xdg"></a>Red Hat xdg_user_dir_lookup License</h1>
+
+ <p>This license applies to the
+ <span class="path">xdg_user_dir_lookup</span> function in
+ <span class="path">xpcom/io/SpecialSystemDirectory.cpp</span>.</p>
+
+<pre>
+Copyright (c) 2007 Red Hat, Inc.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+</pre>
+
+ <hr>
+#ifdef MOZ_DEVTOOLS
+ <h1><a id="redux"></a>Redux License</h1>
+
+ <p>This license applies to the file
+ <span class="path">devtools/client/shared/vendor/redux.js</span>.</p>
+<pre>
+Copyright (c) 2015 Dan Abramov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+</pre>
+
+<hr>
+
+<h1><a id="reselect"></a>Reselect License</h1>
+
+<p>This license applies to the file
+<span class="path">devtools/client/shared/vendor/reselect.js</span>.</p>
+<pre>
+The MIT License (MIT)
+
+Copyright (c) 2015-2016 Reselect Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+</pre>
+
+ <hr>
+#endif
+
+ <h1><a id="sctp"></a>SCTP Licenses</h1>
+
+ <p>These licenses apply to certain files in the directory
+ <span class="path">libs/sctp/src/</span>.</p>
+
+<pre>
+Copyright (c) 2009-2010 Brad Penoff
+Copyright (c) 2009-2010 Humaira Kamal
+Copyright (c) 2011-2012 Irene Ruengeler
+Copyright (c) 2010-2012, by Michael Tuexen. All rights reserved.
+Copyright (c) 2010-2012, by Randall Stewart. All rights reserved.
+Copyright (c) 2010-2012, by Robin Seggelmann. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
+Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
+Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
+Copyright (c) 2008-2012, by Brad Penoff. All rights reserved.
+Copyright (c) 1980, 1982, 1986, 1987, 1988, 1990, 1993
+ The Regents of the University of California.
+Copyright (c) 2005 Robert N. M. Watson All rights reserved.
+Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+a) Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+b) Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the distribution.
+c) Neither the name of Cisco Systems, Inc, the name of the university,
+ the WIDE project, nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+
+#ifdef MOZ_ENABLE_SKIA
+ <h1><a id="skia"></a>Skia License</h1>
+
+ <p>This license applies to certain files in the directory
+ <span class="path">system/graphics/skia/</span>.</p>
+
+<pre>
+Copyright (c) 2011 Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+* Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+ <hr>
+#endif
+
+ <h1><a id="snappy"></a>Snappy License</h1>
+
+ <p>This license applies to certain files in the directory
+ <span class="path">libs/snappy/</span>.</p>
+
+<pre>
+Copyright 2011, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+ <hr>
+#ifdef MOZ_DEVTOOLS_SERVER
+ <h1><a id="sprintf.js"></a>sprintf.js License</h1>
+
+ <p>This license applies to
+ <span class="path">devtools/shared/sprintfjs/sprintf.js</span>.</p>
+
+<pre>
+Copyright (c) 2007-2016, Alexandru Marasteanu &lt;hello [at) alexei (dot] ro&gt;
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+* Neither the name of this software nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+ <hr>
+#endif
+
+ <h1><a id="sunsoft"></a>SunSoft License</h1>
+
+ <p>This license applies to the
+ <span class="path">ICC_H</span> block in
+ <span class="path">system/graphics/qcms/qcms.h</span>.</p>
+
+<pre>
+Copyright (c) 1994-1996 SunSoft, Inc.
+
+ Rights Reserved
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without restrict-
+ion, including without limitation the rights to use, copy, modify,
+merge, publish distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-
+INFRINGEMENT. IN NO EVENT SHALL SUNSOFT, INC. OR ITS PARENT
+COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of SunSoft, Inc.
+shall not be used in advertising or otherwise to promote the
+sale, use or other dealings in this Software without written
+authorization from SunSoft Inc.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="unicode"></a>Unicode License</h1>
+
+ <p>This license applies to files in the <span class="path">intl/icu</span>
+ and <span class="path">system/intl/tzdata</span> directories and certain files in
+ the <span class="path">js/src/vm</span> directory.</p>
+ </p>
+
+<pre>
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright © 1991-2016 Unicode, Inc. All rights reserved.
+Distributed under the Terms of Use in http://www.unicode.org/copyright.html.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Unicode data files and any associated documentation
+(the "Data Files") or Unicode software and any associated documentation
+(the "Software") to deal in the Data Files or Software
+without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, and/or sell copies of
+the Data Files or Software, and to permit persons to whom the Data Files
+or Software are furnished to do so, provided that either
+(a) this copyright and permission notice appear with all copies
+of the Data Files or Software, or
+(b) this copyright and permission notice appear in associated
+Documentation.
+
+THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
+NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
+DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in these Data Files or Software without prior
+written authorization of the copyright holder.
+
+---------------------
+
+Third-Party Software Licenses
+
+This section contains third-party software notices and/or additional
+terms for licensed third-party software components included within ICU
+libraries.
+
+1. ICU License - ICU 1.8.1 to ICU 57.1
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright (c) 1995-2016 International Business Machines Corporation and others
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, and/or sell copies of the Software, and to permit persons
+to whom the Software is furnished to do so, provided that the above
+copyright notice(s) and this permission notice appear in all copies of
+the Software and that both the above copyright notice(s) and this
+permission notice appear in supporting documentation.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
+SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder
+shall not be used in advertising or otherwise to promote the sale, use
+or other dealings in this Software without prior written authorization
+of the copyright holder.
+
+All trademarks and registered trademarks mentioned herein are the
+property of their respective owners.
+
+2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt)
+
+ # The Google Chrome software developed by Google is licensed under
+ # the BSD license. Other software included in this distribution is
+ # provided under other licenses, as set forth below.
+ #
+ # The BSD License
+ # http://opensource.org/licenses/bsd-license.php
+ # Copyright (C) 2006-2008, Google Inc.
+ #
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification, are permitted provided that the following conditions are met:
+ #
+ # Redistributions of source code must retain the above copyright notice,
+ # this list of conditions and the following disclaimer.
+ # Redistributions in binary form must reproduce the above
+ # copyright notice, this list of conditions and the following
+ # disclaimer in the documentation and/or other materials provided with
+ # the distribution.
+ # Neither the name of Google Inc. nor the names of its
+ # contributors may be used to endorse or promote products derived from
+ # this software without specific prior written permission.
+ #
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ #
+ #
+ # The word list in cjdict.txt are generated by combining three word lists
+ # listed below with further processing for compound word breaking. The
+ # frequency is generated with an iterative training against Google web
+ # corpora.
+ #
+ # * Libtabe (Chinese)
+ # - https://sourceforge.net/project/?group_id=1519
+ # - Its license terms and conditions are shown below.
+ #
+ # * IPADIC (Japanese)
+ # - http://chasen.aist-nara.ac.jp/chasen/distribution.html
+ # - Its license terms and conditions are shown below.
+ #
+ # ---------COPYING.libtabe ---- BEGIN--------------------
+ #
+ # /*
+ # * Copyrighy (c) 1999 TaBE Project.
+ # * Copyright (c) 1999 Pai-Hsiang Hsiao.
+ # * All rights reserved.
+ # *
+ # * Redistribution and use in source and binary forms, with or without
+ # * modification, are permitted provided that the following conditions
+ # * are met:
+ # *
+ # * . Redistributions of source code must retain the above copyright
+ # * notice, this list of conditions and the following disclaimer.
+ # * . Redistributions in binary form must reproduce the above copyright
+ # * notice, this list of conditions and the following disclaimer in
+ # * the documentation and/or other materials provided with the
+ # * distribution.
+ # * . Neither the name of the TaBE Project nor the names of its
+ # * contributors may be used to endorse or promote products derived
+ # * from this software without specific prior written permission.
+ # *
+ # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ # * OF THE POSSIBILITY OF SUCH DAMAGE.
+ # */
+ #
+ # /*
+ # * Copyright (c) 1999 Computer Systems and Communication Lab,
+ # * Institute of Information Science, Academia
+ # * Sinica. All rights reserved.
+ # *
+ # * Redistribution and use in source and binary forms, with or without
+ # * modification, are permitted provided that the following conditions
+ # * are met:
+ # *
+ # * . Redistributions of source code must retain the above copyright
+ # * notice, this list of conditions and the following disclaimer.
+ # * . Redistributions in binary form must reproduce the above copyright
+ # * notice, this list of conditions and the following disclaimer in
+ # * the documentation and/or other materials provided with the
+ # * distribution.
+ # * . Neither the name of the Computer Systems and Communication Lab
+ # * nor the names of its contributors may be used to endorse or
+ # * promote products derived from this software without specific
+ # * prior written permission.
+ # *
+ # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ # * OF THE POSSIBILITY OF SUCH DAMAGE.
+ # */
+ #
+ # Copyright 1996 Chih-Hao Tsai @ Beckman Institute,
+ # University of Illinois
+ # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4
+ #
+ # ---------------COPYING.libtabe-----END--------------------------------
+ #
+ #
+ # ---------------COPYING.ipadic-----BEGIN-------------------------------
+ #
+ # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science
+ # and Technology. All Rights Reserved.
+ #
+ # Use, reproduction, and distribution of this software is permitted.
+ # Any copy of this software, whether in its original form or modified,
+ # must include both the above copyright notice and the following
+ # paragraphs.
+ #
+ # Nara Institute of Science and Technology (NAIST),
+ # the copyright holders, disclaims all warranties with regard to this
+ # software, including all implied warranties of merchantability and
+ # fitness, in no event shall NAIST be liable for
+ # any special, indirect or consequential damages or any damages
+ # whatsoever resulting from loss of use, data or profits, whether in an
+ # action of contract, negligence or other tortuous action, arising out
+ # of or in connection with the use or performance of this software.
+ #
+ # A large portion of the dictionary entries
+ # originate from ICOT Free Software. The following conditions for ICOT
+ # Free Software applies to the current dictionary as well.
+ #
+ # Each User may also freely distribute the Program, whether in its
+ # original form or modified, to any third party or parties, PROVIDED
+ # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear
+ # on, or be attached to, the Program, which is distributed substantially
+ # in the same form as set out herein and that such intended
+ # distribution, if actually made, will neither violate or otherwise
+ # contravene any of the laws and regulations of the countries having
+ # jurisdiction over the User or the intended distribution itself.
+ #
+ # NO WARRANTY
+ #
+ # The program was produced on an experimental basis in the course of the
+ # research and development conducted during the project and is provided
+ # to users as so produced on an experimental basis. Accordingly, the
+ # program is provided without any warranty whatsoever, whether express,
+ # implied, statutory or otherwise. The term "warranty" used herein
+ # includes, but is not limited to, any warranty of the quality,
+ # performance, merchantability and fitness for a particular purpose of
+ # the program and the nonexistence of any infringement or violation of
+ # any right of any third party.
+ #
+ # Each user of the program will agree and understand, and be deemed to
+ # have agreed and understood, that there is no warranty whatsoever for
+ # the program and, accordingly, the entire risk arising from or
+ # otherwise connected with the program is assumed by the user.
+ #
+ # Therefore, neither ICOT, the copyright holder, or any other
+ # organization that participated in or was otherwise related to the
+ # development of the program and their respective officials, directors,
+ # officers and other employees shall be held liable for any and all
+ # damages, including, without limitation, general, special, incidental
+ # and consequential damages, arising out of or otherwise in connection
+ # with the use or inability to use the program or any product, material
+ # or result produced or otherwise obtained by using the program,
+ # regardless of whether they have been advised of, or otherwise had
+ # knowledge of, the possibility of such damages at any time during the
+ # project or thereafter. Each user will be deemed to have agreed to the
+ # foregoing by his or her commencement of use of the program. The term
+ # "use" as used herein includes, but is not limited to, the use,
+ # modification, copying and distribution of the program and the
+ # production of secondary products from the program.
+ #
+ # In the case where the program, whether in its original form or
+ # modified, was distributed or delivered to or received by a user from
+ # any person, organization or entity other than ICOT, unless it makes or
+ # grants independently of ICOT any specific warranty to the user in
+ # writing, such person, organization or entity, will also be exempted
+ # from and not be held liable to the user for any such damages as noted
+ # above as far as the program is concerned.
+ #
+ # ---------------COPYING.ipadic-----END----------------------------------
+
+3. Lao Word Break Dictionary Data (laodict.txt)
+
+ # Copyright (c) 2013 International Business Machines Corporation
+ # and others. All Rights Reserved.
+ #
+ # Project: http://code.google.com/p/lao-dictionary/
+ # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt
+ # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt
+ # (copied below)
+ #
+ # This file is derived from the above dictionary, with slight
+ # modifications.
+ # ----------------------------------------------------------------------
+ # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell.
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification,
+ # are permitted provided that the following conditions are met:
+ #
+ #
+ # Redistributions of source code must retain the above copyright notice, this
+ # list of conditions and the following disclaimer. Redistributions in
+ # binary form must reproduce the above copyright notice, this list of
+ # conditions and the following disclaimer in the documentation and/or
+ # other materials provided with the distribution.
+ #
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ # OF THE POSSIBILITY OF SUCH DAMAGE.
+ # --------------------------------------------------------------------------
+
+4. Burmese Word Break Dictionary Data (burmesedict.txt)
+
+ # Copyright (c) 2014 International Business Machines Corporation
+ # and others. All Rights Reserved.
+ #
+ # This list is part of a project hosted at:
+ # github.com/kanyawtech/myanmar-karen-word-lists
+ #
+ # --------------------------------------------------------------------------
+ # Copyright (c) 2013, LeRoy Benjamin Sharon
+ # All rights reserved.
+ #
+ # Redistribution and use in source and binary forms, with or without
+ # modification, are permitted provided that the following conditions
+ # are met: Redistributions of source code must retain the above
+ # copyright notice, this list of conditions and the following
+ # disclaimer. Redistributions in binary form must reproduce the
+ # above copyright notice, this list of conditions and the following
+ # disclaimer in the documentation and/or other materials provided
+ # with the distribution.
+ #
+ # Neither the name Myanmar Karen Word Lists, nor the names of its
+ # contributors may be used to endorse or promote products derived
+ # from this software without specific prior written permission.
+ #
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ # SUCH DAMAGE.
+ # --------------------------------------------------------------------------
+
+5. Time Zone Database
+
+ ICU uses the public domain data and code derived from Time Zone
+Database for its time zone support. The ownership of the TZ database
+is explained in BCP 175: Procedure for Maintaining the Time Zone
+Database section 7.
+
+ # 7. Database Ownership
+ #
+ # The TZ database itself is not an IETF Contribution or an IETF
+ # document. Rather it is a pre-existing and regularly updated work
+ # that is in the public domain, and is intended to remain in the
+ # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do
+ # not apply to the TZ Database or contributions that individuals make
+ # to it. Should any claims be made and substantiated against the TZ
+ # Database, the organization that is providing the IANA
+ # Considerations defined in this RFC, under the memorandum of
+ # understanding with the IETF, currently ICANN, may act in accordance
+ # with all competent court orders. No ownership claims will be made
+ # by ICANN or the IETF Trust on the database or the code. Any person
+ # making a contribution to the database or code waives all rights to
+ # future claims in that contribution or in the TZ Database.</pre>
+
+
+ <hr>
+
+ <h1><a id="ucal"></a>University of California License</h1>
+
+ <p>This license applies to the following files or, in the case of
+ directories, certain files in those directories:</p>
+
+ <ul>
+ <li class="path">libs/nss/lib/dbm/</li>
+ <li class="path">components/mork/src/morkQuickSort.cpp</li>
+ <li class="path">xpcom/glue/nsQuickSort.cpp</li>
+ <li class="path">libs/nspr/pr/src/misc/praton.c</li>
+ </ul>
+
+<pre>
+Copyright (c) 1990, 1993
+ The Regents of the University of California. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+[3 Deleted as of 22nd July 1999; see
+ <a href="ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change">ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change</a>
+ for details]
+4. Neither the name of the University nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="hunspell-en-US"></a>US English Spellchecking Dictionary Licenses</h1>
+
+ <p>These licenses apply to certain files in the directory
+ <span class="path">system/intl/spellcheck/locales/en-US/hunspell/</span>.</p>
+
+<pre>
+Different parts of the US English dictionary (SCOWL) are subject to the
+following licenses as shown below. For additional details, sources, credits,
+and public domain references, see <a href="https://dxr.mozilla.org/mozilla-central/source/extensions/spellcheck/locales/en-US/hunspell/README_en_US.txt">README.txt</a>.
+
+The collective work of the Spell Checking Oriented Word Lists (SCOWL) is under
+the following copyright:
+
+Copyright 2000-2007 by Kevin Atkinson
+Permission to use, copy, modify, distribute and sell these word lists, the
+associated scripts, the output created from the scripts, and its documentation
+for any purpose is hereby granted without fee, provided that the above
+copyright notice appears in all copies and that both that copyright notice and
+this permission notice appear in supporting documentation. Kevin Atkinson makes
+no representations about the suitability of this array for any purpose. It is
+provided "as is" without express or implied warranty.
+
+The WordNet database is under the following copyright:
+
+This software and database is being provided to you, the LICENSEE, by Princeton
+University under the following license. By obtaining, using and/or copying
+this software and database, you agree that you have read, understood, and will
+comply with these terms and conditions:
+Permission to use, copy, modify and distribute this software and database and
+its documentation for any purpose and without fee or royalty is hereby granted,
+provided that you agree to comply with the following copyright notice and
+statements, including the disclaimer, and that the same appear on ALL copies of
+the software, database and documentation, including modifications that you make
+for internal use or for distribution.
+WordNet 1.6 Copyright 1997 by Princeton University. All rights reserved.
+THIS SOFTWARE AND DATABASE IS PROVIDED "AS IS" AND PRINCETON UNIVERSITY
+MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF
+EXAMPLE, BUT NOT LIMITATION, PRINCETON UNIVERSITY MAKES NO
+REPRESENTATIONS OR WARRANTIES OF MERCHANT- ABILITY OR FITNESS FOR ANY
+PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE, DATABASE OR
+DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS,
+TRADEMARKS OR OTHER RIGHTS.
+The name of Princeton University or Princeton may not be used in advertising or
+publicity pertaining to distribution of the software and/or database. Title to
+copyright in this software, database and any associated documentation shall at
+all times remain with Princeton University and LICENSEE agrees to preserve same.
+
+The "UK Advanced Cryptics Dictionary" is under the following copyright:
+
+Copyright (c) J Ross Beresford 1993-1999. All Rights Reserved.
+The following restriction is placed on the use of this publication: if The UK
+Advanced Cryptics Dictionary is used in a software package or redistributed in
+any form, the copyright notice must be prominently displayed and the text of
+this document must be included verbatim. There are no other restrictions: I
+would like to see the list distributed as widely as possible.
+
+Various parts are under the Ispell copyright:
+
+Copyright 1993, Geoff Kuenning, Granada Hills, CA
+All rights reserved. Redistribution and use in source and binary forms, with
+or without modification, are permitted provided that the following conditions
+are met:
+ 1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+ 3. All modifications to the source code must be clearly marked as such.
+Binary redistributions based on modified source code must be clearly marked as
+modified versions in the documentation and/or other materials provided with
+the distribution.
+ (clause 4 removed with permission from Geoff Kuenning)
+ 5. The name of Geoff Kuenning may not be used to endorse or promote products
+derived from this software without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED BY GEOFF KUENNING AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GEOFF KUENNING OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+Additional Contributors:
+
+ Alan Beale &lt;biljir@pobox.com&gt;
+ M Cooper &lt;thegrendel@theriver.com&gt;
+</pre>
+
+
+
+ <hr>
+
+ <h1><a id="v8"></a>V8 License</h1>
+
+ <p>This license applies to certain files in the directories
+ <span class="path">js/src/irregexp</span>,
+ <span class="path">js/src/builtin</span>,
+ <span class="path">js/src/jit/arm</span> and
+ <span class="path">js/src/jit/mips</span>.
+ </p>
+<pre>
+Copyright 2006-2012 the V8 project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+#if defined(XP_WIN) || defined(XP_LINUX)
+
+ <hr>
+
+ <h1><a id="vtune"></a>VTune License</h1>
+
+ <p>This license applies to certain files in the directory
+ <span class="path">js/src/vtune</span>.</p>
+<pre>
+Copyright (c) 2005-2012 Intel Corporation. All rights reserved.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of Intel Corporation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+ <h1><a id="xiph"></a>Xiph.org Foundation License</h1>
+
+ <p>This license applies to files in the following directories
+ with the specified copyright year ranges:</p>
+ <ul>
+ <li><span class="path">libs/libogg/</span>, 2002</li>
+ <li><span class="path">libs/libtheora/</span>, 2002-2007</li>
+ <li><span class="path">libs/libvorbis/</span>, 2002-2004</li>
+ <li><span class="path">libs/libtremor/</span>, 2002-2010</li>
+ <li><span class="path">libs/libspeex_resampler/</span>, 2002-2008</li>
+ </ul>
+
+<pre>
+Copyright (c) &lt;year&gt;, Xiph.org Foundation
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the Xiph.org Foundation nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="other-notices"></a>Other Required Notices</h1>
+
+ <ul>
+ <li>This software is based in part on the work of the Independent
+ JPEG Group.</li>
+ </ul>
+
+
+ <hr>
+
+ <h1><a id="optional-notices"></a>Optional Notices</h1>
+
+ <p>Some permissive software licenses request but do not require an
+ acknowledgement of the use of their software. We are very grateful
+ to the following people and projects for their contributions to
+ this product:</p>
+
+ <ul>
+ <li>The <a href="http://www.zlib.net/">zlib</a> compression library
+ (Jean-loup Gailly, Mark Adler and team)</li>
+ <li>The <a href="http://www.bzip.org/">bzip2</a> compression library
+ (Julian Seward)</li>
+ <li>The <a href="http://www.libpng.org/pub/png/">libpng</a> graphics library
+ (Glenn Randers-Pehrson and team)</li>
+ <li>The <a href="http://www.sqlite.org/">sqlite</a> database engine
+ (D. Richard Hipp and team)</li>
+ <li>The <a href="http://nsis.sourceforge.net/">Nullsoft Scriptable Install System</a>
+ (Amir Szekely and team)</li>
+ </ul>
+
+
+ <hr>
+#ifdef XP_WIN
+
+ <h1><a id="proprietary-notices"></a>Proprietary Operating System Components</h1>
+
+ <p>Under some circumstances, under our
+ <a href="http://www.palemoon.org/policies/binarycomponents.html">Binary Component Policy</a>,
+ we may decide to include additional
+ operating system vendor code with the installer of our products designed
+ for that vendor's proprietary platform, to make our products work well on
+ that specific operating system. The following license statements
+ apply to such inclusions.</p>
+
+ <h2><a id="directx"></a>Microsoft Windows: Terms for 'Microsoft Distributable Code'</h2>
+
+ <p>These terms apply to the following files;
+ they are referred to below as "Distributable Code":
+ <ul>
+ <li><span class="path">d3d*.dll</span> (Direct3D libraries)</li>
+ <li><span class="path">msvc*.dll</span> and <span class="path">vcruntime*.dll</span> (C and C++ runtime libraries)</li>
+ <li><span class="path">api-ms-win*.dll</span> and <span class="path">ucrtbase.dll</span> (Universal CRT runtime libraries)</li>
+ </ul>
+ </p>
+
+<pre>
+Copyright (c) Microsoft Corporation.
+
+The Distributable Code may be used and distributed only if you comply with the
+following terms:
+
+(i) You may use, copy, and distribute the Distributable Code only as part of
+ this product;
+(ii) You may not use the Distributable Code on a platform other than Windows;
+(iii) You may not alter any copyright, trademark or patent notice in the
+ Distributable Code;
+(iv) You may not modify or distribute the source code of any Distributable
+ Code so that any part of the source code becomes subject to the MPL or
+ any other copyleft license;
+(v) You must comply with any technical limitations in the Distributable Code
+ that only allow you to use it in certain ways; and
+(vi) You must comply with all domestic and international export laws and
+ regulations that apply to the Distributable Code.
+</pre>
+
+ <hr>
+#endif
+
+ <p><a href="about:license#top">Return to top</a>.</p>
+
+ </body>
+</html>
diff --git a/components/global/content/logopage.xhtml b/components/global/content/logopage.xhtml
new file mode 100644
index 000000000..80de8ed58
--- /dev/null
+++ b/components/global/content/logopage.xhtml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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></title>
+ <style type="text/css">
+ body {
+ background: #eee;
+ color: black;
+ }
+
+ img {
+ text-align: center;
+ position: absolute;
+ margin: auto;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ opacity: 0.2;
+ }
+
+ @media(-moz-dark-theme) {
+ body {
+ background: #2E3B41;
+ color: #eee;
+ }
+ }
+ </style>
+</head>
+
+<body>
+ <img src="about:logo" alt=""/>
+</body>
+</html>
diff --git a/components/global/content/menulist.css b/components/global/content/menulist.css
new file mode 100644
index 000000000..ae6166d1b
--- /dev/null
+++ b/components/global/content/menulist.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/. */
+
+@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */
+
+html|*.menulist-editable-input {
+ -moz-appearance: none !important;
+ background: transparent ! important;
+ -moz-box-flex: 1;
+}
diff --git a/components/global/content/minimal-xul.css b/components/global/content/minimal-xul.css
new file mode 100644
index 000000000..0cd41922d
--- /dev/null
+++ b/components/global/content/minimal-xul.css
@@ -0,0 +1,133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 should only contain a minimal set of rules for the XUL elements
+ * that may be implicitly created as part of HTML/SVG documents (e.g.
+ * scrollbars). Rules for everything else related to XUL can be found in
+ * xul.css. (This split of the XUL rules is to minimize memory use and improve
+ * performance in HTML/SVG documents.)
+ *
+ * This file should also not contain any app specific styling. Defaults for
+ * widgets of a particular application should be in that application's style
+ * sheet. For example style definitions for navigator can be found in
+ * navigator.css.
+ *
+ * THIS FILE IS LOCKED DOWN. YOU ARE NOT ALLOWED TO MODIFY IT WITHOUT FIRST
+ * HAVING YOUR CHANGES REVIEWED BY enndeakin@gmail.com
+ */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
+@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */
+
+* {
+ -moz-user-focus: ignore;
+ -moz-user-select: none;
+ display: -moz-box;
+ box-sizing: border-box;
+}
+
+:root {
+ text-rendering: optimizeLegibility;
+ -moz-binding: url("chrome://global/content/bindings/general.xml#root-element");
+ -moz-control-character-visibility: visible;
+}
+
+:root:-moz-locale-dir(rtl) {
+ direction: rtl;
+}
+
+/* hide the content and destroy the frame */
+[hidden="true"] {
+ display: none;
+}
+
+/* hide the content, but don't destroy the frames */
+[collapsed="true"],
+[moz-collapsed="true"] {
+ visibility: collapse;
+}
+
+/********** label **********/
+
+description {
+ -moz-binding: url("chrome://global/content/bindings/text.xml#text-base");
+}
+
+label {
+ -moz-binding: url("chrome://global/content/bindings/text.xml#text-label");
+}
+
+label.text-link, label[onclick] {
+ -moz-binding: url("chrome://global/content/bindings/text.xml#text-link");
+ -moz-user-focus: normal;
+}
+
+label[control], label.radio-label, label.checkbox-label, label.toolbarbutton-multiline-text {
+ -moz-binding: url("chrome://global/content/bindings/text.xml#label-control");
+}
+
+html|span.accesskey {
+ text-decoration: underline;
+}
+
+/********** resizer **********/
+
+resizer {
+ -moz-binding: url("chrome://global/content/bindings/resizer.xml#resizer");
+ position: relative;
+ z-index: 2147483647;
+}
+
+/********** scrollbar **********/
+
+/* Scrollbars are never flipped even if BiDI kicks in. */
+scrollbar[orient="horizontal"] {
+ direction: ltr;
+}
+
+thumb {
+ -moz-binding: url(chrome://global/content/bindings/scrollbar.xml#thumb);
+ display: -moz-box !important;
+}
+
+.scale-thumb {
+ -moz-binding: url(chrome://global/content/bindings/scale.xml#scalethumb);
+}
+
+scrollbar, scrollbarbutton, scrollcorner, slider, thumb, scale {
+ -moz-user-select: none;
+}
+
+scrollcorner {
+ display: -moz-box !important;
+}
+
+scrollcorner[hidden="true"] {
+ display: none !important;
+}
+
+scrollbar[value="hidden"] {
+ visibility: hidden;
+}
+
+scale {
+ -moz-binding: url(chrome://global/content/bindings/scale.xml#scale);
+}
+
+.scale-slider {
+ -moz-binding: url(chrome://global/content/bindings/scale.xml#scaleslider);
+ -moz-user-focus: normal;
+}
+
+scrollbarbutton[sbattr="scrollbar-up-top"]:not(:-moz-system-metric(scrollbar-start-backward)),
+scrollbarbutton[sbattr="scrollbar-down-top"]:not(:-moz-system-metric(scrollbar-start-forward)),
+scrollbarbutton[sbattr="scrollbar-up-bottom"]:not(:-moz-system-metric(scrollbar-end-backward)),
+scrollbarbutton[sbattr="scrollbar-down-bottom"]:not(:-moz-system-metric(scrollbar-end-forward)) {
+ display: none;
+}
+
+thumb[sbattr="scrollbar-thumb"]:-moz-system-metric(scrollbar-thumb-proportional) {
+ -moz-box-flex: 1;
+}
diff --git a/components/global/content/mozilla.xhtml b/components/global/content/mozilla.xhtml
new file mode 100644
index 000000000..f1a1b474d
--- /dev/null
+++ b/components/global/content/mozilla.xhtml
@@ -0,0 +1,76 @@
+<!DOCTYPE html
+[
+ <!ENTITY % directionDTD SYSTEM "chrome://global/locale/global.dtd" >
+ %directionDTD;
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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>Mozilla: In Memoriam</title>
+
+<style>
+html {
+ background: maroon radial-gradient( circle, #a01010 0%, #800000 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.0em;
+ 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">
+ <h1>Mozilla: In Memoriam</h1>
+ <br/>
+ Dedicated to the tireless developers who have come and gone.<br/>
+ To those who have put their heart and soul into Mozilla products.<br/>
+ To those who have seen their good intentions and hard work squandered.<br/>
+ To those who really cared about the user, and cared about usability.<br/>
+ To those who truly understood us and desired freedom, but were unheard.<br/>
+ To those who knew that change is inevitable, but loss of vision is not.<br/>
+ To those who were forced to give up the good fight.<br/>
+ <br/>
+ <em>Thank you.</em> &brandFullName; would not have been possible without you.<br/>
+ <br/>
+ </p>
+
+ <p id="from">
+ </p>
+</section>
+
+</body>
+</html> \ No newline at end of file
diff --git a/components/global/content/nsClipboard.js b/components/global/content/nsClipboard.js
new file mode 100644
index 000000000..d9f7c4589
--- /dev/null
+++ b/components/global/content/nsClipboard.js
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+/**
+ * nsClipboard - wrapper around nsIClipboard and nsITransferable
+ * that simplifies access to the clipboard.
+ **/
+var nsClipboard = {
+ _CB: null,
+ get mClipboard()
+ {
+ if (!this._CB)
+ {
+ const kCBContractID = "@mozilla.org/widget/clipboard;1";
+ const kCBIID = Components.interfaces.nsIClipboard;
+ this._CB = Components.classes[kCBContractID].getService(kCBIID);
+ }
+ return this._CB;
+ },
+
+ currentClipboard: null,
+ /**
+ * Array/Object read (Object aFlavourList, long aClipboard, Bool aAnyFlag) ;
+ *
+ * returns the data in the clipboard
+ *
+ * @param FlavourSet aFlavourSet
+ * formatted list of desired flavours
+ * @param long aClipboard
+ * the clipboard to read data from (kSelectionClipboard/kGlobalClipboard)
+ * @param Bool aAnyFlag
+ * should be false.
+ **/
+ read: function (aFlavourList, aClipboard, aAnyFlag)
+ {
+ this.currentClipboard = aClipboard;
+ var data = nsTransferable.get(aFlavourList, this.getClipboardTransferable, aAnyFlag);
+ return data.first.first; // only support one item
+ },
+
+ /**
+ * nsISupportsArray getClipboardTransferable (Object aFlavourList) ;
+ *
+ * returns a nsISupportsArray of the item on the clipboard
+ *
+ * @param Object aFlavourList
+ * formatted list of desired flavours.
+ **/
+ getClipboardTransferable: function (aFlavourList)
+ {
+ const supportsContractID = "@mozilla.org/supports-array;1";
+ const supportsIID = Components.interfaces.nsISupportsArray;
+ var supportsArray = Components.classes[supportsContractID].createInstance(supportsIID);
+ var trans = nsTransferable.createTransferable();
+ for (var flavour in aFlavourList)
+ trans.addDataFlavor(flavour);
+ nsClipboard.mClipboard.getData(trans, nsClipboard.currentClipboard)
+ supportsArray.AppendElement(trans);
+ return supportsArray;
+ }
+};
+
diff --git a/components/global/content/nsUserSettings.js b/components/global/content/nsUserSettings.js
new file mode 100644
index 000000000..e0c378caf
--- /dev/null
+++ b/components/global/content/nsUserSettings.js
@@ -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/. */
+
+
+/**
+ * nsPreferences - a wrapper around nsIPrefService. Provides built in
+ * exception handling to make preferences access simpler.
+ **/
+var nsPreferences = {
+ get mPrefService()
+ {
+ return Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ },
+
+ setBoolPref: function (aPrefName, aPrefValue)
+ {
+ try
+ {
+ this.mPrefService.setBoolPref(aPrefName, aPrefValue);
+ }
+ catch (e)
+ {
+ }
+ },
+
+ getBoolPref: function (aPrefName, aDefVal)
+ {
+ try
+ {
+ return this.mPrefService.getBoolPref(aPrefName);
+ }
+ catch (e)
+ {
+ return aDefVal != undefined ? aDefVal : null;
+ }
+ return null; // quiet warnings
+ },
+
+ setUnicharPref: function (aPrefName, aPrefValue)
+ {
+ try
+ {
+ var str = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ str.data = aPrefValue;
+ this.mPrefService.setComplexValue(aPrefName,
+ Components.interfaces.nsISupportsString, str);
+ }
+ catch (e)
+ {
+ }
+ },
+
+ copyUnicharPref: function (aPrefName, aDefVal)
+ {
+ try
+ {
+ return this.mPrefService.getComplexValue(aPrefName,
+ Components.interfaces.nsISupportsString).data;
+ }
+ catch (e)
+ {
+ return aDefVal != undefined ? aDefVal : null;
+ }
+ return null; // quiet warnings
+ },
+
+ setIntPref: function (aPrefName, aPrefValue)
+ {
+ try
+ {
+ this.mPrefService.setIntPref(aPrefName, aPrefValue);
+ }
+ catch (e)
+ {
+ }
+ },
+
+ getIntPref: function (aPrefName, aDefVal)
+ {
+ try
+ {
+ return this.mPrefService.getIntPref(aPrefName);
+ }
+ catch (e)
+ {
+ return aDefVal != undefined ? aDefVal : null;
+ }
+ return null; // quiet warnings
+ },
+
+ getLocalizedUnicharPref: function (aPrefName, aDefVal)
+ {
+ try
+ {
+ return this.mPrefService.getComplexValue(aPrefName,
+ Components.interfaces.nsIPrefLocalizedString).data;
+ }
+ catch (e)
+ {
+ return aDefVal != undefined ? aDefVal : null;
+ }
+ return null; // quiet warnings
+ }
+};
+
diff --git a/components/global/content/plugins.css b/components/global/content/plugins.css
new file mode 100644
index 000000000..a599b5be4
--- /dev/null
+++ b/components/global/content/plugins.css
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* ===== plugins.css =====================================================
+ == Styles used by the about:plugins page.
+ ======================================================================= */
+
+body {
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+ font: message-box;
+}
+
+div#outside {
+ text-align: justify;
+ width: 90%;
+ margin-left: 5%;
+ margin-right: 5%;
+}
+
+#plugs {
+ text-align: center;
+ font-size: xx-large;
+ font-weight: bold;
+}
+
+#noplugs {
+ font-size: x-large;
+ font-weight: bold;
+}
+
+.plugname {
+ margin-top: 2em;
+ margin-bottom: 1em;
+ font-size: large;
+ text-align: start;
+ font-weight: bold;
+}
+
+dl {
+ margin: 0px 0px 3px 0px;
+}
+
+table {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+ font: message-box;
+ text-align: start;
+ width: 100%;
+ border: 1px solid ThreeDShadow;
+ border-spacing: 0px;
+}
+
+th, td {
+ border: none;
+ padding: 3px;
+}
+
+th {
+ text-align: center;
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+th + th,
+td + td {
+ border-inline-start: 1px dotted ThreeDShadow;
+}
+
+td {
+ text-align: start;
+ border-top: 1px dotted ThreeDShadow;
+}
+
+th.type, th.suff {
+ width: 25%;
+}
+
+th.desc {
+ width: 50%;
+}
+
+.notice {
+ background: -moz-cellhighlight;
+ border: 1px solid ThreeDShadow;
+ padding: 10px;
+}
diff --git a/components/global/content/plugins.html b/components/global/content/plugins.html
new file mode 100644
index 000000000..15bbed9bb
--- /dev/null
+++ b/components/global/content/plugins.html
@@ -0,0 +1,207 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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>
+<head>
+<script type="application/javascript">
+ "use strict";
+
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Components.utils.import("resource://gre/modules/AddonManager.jsm");
+
+ var Ci = Components.interfaces;
+ var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
+ var pluginsbundle = strBundleService.createBundle("chrome://global/locale/plugins.properties");
+
+ document.writeln("<title>" + pluginsbundle.GetStringFromName("title_label") + "<\/title>");
+</script>
+<link rel="stylesheet" type="text/css" href="chrome://global/content/plugins.css">
+<link rel="stylesheet" type="text/css" href="chrome://global/skin/plugins.css">
+</head>
+<body>
+<div id="outside">
+<script type="application/javascript">
+ "use strict";
+
+ function setDirection() {
+ var frame = document.getElementById("directionDetector");
+ var direction = frame.contentDocument
+ .defaultView
+ .window
+ .getComputedStyle(frame.contentDocument.getElementById("target"), "")
+ .getPropertyValue("direction");
+ document.body.removeChild(frame);
+ document.dir = direction;
+ }
+
+ function setupDirection() {
+ var frame = document.createElement("iframe");
+ frame.setAttribute("id", "directionDetector");
+ frame.setAttribute("src", "chrome://global/content/directionDetector.html");
+ frame.setAttribute("width", "0");
+ frame.setAttribute("height", "0");
+ frame.setAttribute("style", "visibility: hidden;");
+ frame.setAttribute("onload", "setDirection();");
+ document.body.appendChild(frame);
+ }
+ setupDirection();
+
+ /* JavaScript to enumerate and display all installed plug-ins
+
+ * First, refresh plugins in case anything has been changed recently in
+ * prefs: (The "false" argument tells refresh not to reload or activate
+ * any plug-ins that would be active otherwise. In contrast, one would
+ * use "true" in the case of ASD instead of restarting)
+ */
+ navigator.plugins.refresh(false);
+
+ AddonManager.getAddonsByTypes(["plugin"], function (aPlugins) {
+ var fragment = document.createDocumentFragment();
+
+ // "Installed plugins"
+ var id, label;
+ if (aPlugins.length > 0) {
+ id = "plugs";
+ label = "installedplugins_label";
+ } else {
+ id = "noplugs";
+ label = "nopluginsareinstalled_label";
+ }
+ var enabledplugins = document.createElement("h1");
+ enabledplugins.setAttribute("id", id);
+ enabledplugins.appendChild(document.createTextNode(pluginsbundle.GetStringFromName(label)));
+ fragment.appendChild(enabledplugins);
+
+ var stateNames = {};
+ ["STATE_SOFTBLOCKED",
+ "STATE_BLOCKED",
+ "STATE_OUTDATED",
+ "STATE_VULNERABLE_UPDATE_AVAILABLE",
+ "STATE_VULNERABLE_NO_UPDATE"].forEach(function(label) {
+ stateNames[Ci.nsIBlocklistService[label]] = label;
+ });
+
+ for (var i = 0; i < aPlugins.length; i++) {
+ var plugin = aPlugins[i];
+ if (plugin) {
+ // "Shockwave Flash"
+ var plugname = document.createElement("h2");
+ plugname.setAttribute("class", "plugname");
+ plugname.appendChild(document.createTextNode(plugin.name));
+ fragment.appendChild(plugname);
+
+ var dl = document.createElement("dl");
+ fragment.appendChild(dl);
+
+ // "File: Flash Player.plugin"
+ var fileDd = document.createElement("dd");
+ var file = document.createElement("span");
+ file.setAttribute("class", "label");
+ file.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("file_label") + " "));
+ fileDd.appendChild(file);
+ fileDd.appendChild(document.createTextNode(plugin.pluginLibraries));
+ dl.appendChild(fileDd);
+
+ // "Path: /usr/lib/mozilla/plugins/libtotem-cone-plugin.so"
+ var pathDd = document.createElement("dd");
+ var path = document.createElement("span");
+ path.setAttribute("class", "label");
+ path.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("path_label") + " "));
+ pathDd.appendChild(path);
+ pathDd.appendChild(document.createTextNode(plugin.pluginFullpath));
+ dl.appendChild(pathDd);
+
+ // "Version: "
+ var versionDd = document.createElement("dd");
+ var version = document.createElement("span");
+ version.setAttribute("class", "label");
+ version.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("version_label") + " "));
+ versionDd.appendChild(version);
+ versionDd.appendChild(document.createTextNode(plugin.version));
+ dl.appendChild(versionDd);
+
+ // "State: "
+ var stateDd = document.createElement("dd");
+ var state = document.createElement("span");
+ state.setAttribute("label", "state");
+ state.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("state_label") + " "));
+ stateDd.appendChild(state);
+ var status = plugin.isActive ? pluginsbundle.GetStringFromName("state_enabled") : pluginsbundle.GetStringFromName("state_disabled");
+ if (plugin.blocklistState in stateNames) {
+ status += " (" + stateNames[plugin.blocklistState] + ")";
+ }
+ stateDd.appendChild(document.createTextNode(status));
+ dl.appendChild(stateDd);
+
+ // Plugin Description
+ var descDd = document.createElement("dd");
+ descDd.appendChild(document.createTextNode(plugin.description));
+ dl.appendChild(descDd);
+
+ // MIME Type table
+ var mimetypeTable = document.createElement("table");
+ mimetypeTable.setAttribute("border", "1");
+ mimetypeTable.setAttribute("class", "contenttable");
+ fragment.appendChild(mimetypeTable);
+
+ var thead = document.createElement("thead");
+ mimetypeTable.appendChild(thead);
+ var tr = document.createElement("tr");
+ thead.appendChild(tr);
+
+ // "MIME Type" column header
+ var typeTh = document.createElement("th");
+ typeTh.setAttribute("class", "type");
+ typeTh.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("mimetype_label")));
+ tr.appendChild(typeTh);
+
+ // "Description" column header
+ var descTh = document.createElement("th");
+ descTh.setAttribute("class", "desc");
+ descTh.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("description_label")));
+ tr.appendChild(descTh);
+
+ // "Suffixes" column header
+ var suffixesTh = document.createElement("th");
+ suffixesTh.setAttribute("class", "suff");
+ suffixesTh.appendChild(document.createTextNode(pluginsbundle.GetStringFromName("suffixes_label")));
+ tr.appendChild(suffixesTh);
+
+ var tbody = document.createElement("tbody");
+ mimetypeTable.appendChild(tbody);
+
+ var mimeTypes = plugin.pluginMimeTypes;
+ for (var j = 0; j < mimeTypes.length; j++) {
+ var mimetype = mimeTypes[j];
+ if (mimetype) {
+ var mimetypeRow = document.createElement("tr");
+ tbody.appendChild(mimetypeRow);
+
+ // "application/x-shockwave-flash"
+ var typename = document.createElement("td");
+ typename.appendChild(document.createTextNode(mimetype.type));
+ mimetypeRow.appendChild(typename);
+
+ // "Shockwave Flash"
+ var description = document.createElement("td");
+ description.appendChild(document.createTextNode(mimetype.description));
+ mimetypeRow.appendChild(description);
+
+ // "swf"
+ var suffixes = document.createElement("td");
+ suffixes.appendChild(document.createTextNode(mimetype.suffixes));
+ mimetypeRow.appendChild(suffixes);
+ }
+ }
+ }
+ }
+
+ document.getElementById("outside").appendChild(fragment);
+ });
+</script>
+</div>
+</body>
+</html>
diff --git a/components/global/content/process-content.js b/components/global/content/process-content.js
new file mode 100644
index 000000000..9d7e37100
--- /dev/null
+++ b/components/global/content/process-content.js
@@ -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/. */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+// Creates a new PageListener for this process. This will listen for page loads
+// and for those that match URLs provided by the parent process will set up
+// a dedicated message port and notify the parent process.
+Cu.import("resource://gre/modules/RemotePageManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+
+if (gInContentProcess) {
+ let ProcessObserver = {
+ TOPICS: [
+ "inner-window-destroyed",
+ "xpcom-shutdown",
+ ],
+
+ init() {
+ for (let topic of this.TOPICS) {
+ Services.obs.addObserver(this, topic, false);
+ Services.cpmm.addMessageListener("Memory:GetSummary", this);
+ }
+ },
+
+ uninit() {
+ for (let topic of this.TOPICS) {
+ Services.obs.removeObserver(this, topic);
+ Services.cpmm.removeMessageListener("Memory:GetSummary", this);
+ }
+ },
+
+ receiveMessage(msg) {
+ if (msg.name != "Memory:GetSummary") {
+ return;
+ }
+ let pid = Services.appinfo.processID;
+ let memMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
+ .getService(Ci.nsIMemoryReporterManager);
+ let rss = memMgr.resident;
+ let uss = memMgr.residentUnique;
+ Services.cpmm.sendAsyncMessage("Memory:Summary", {
+ pid,
+ summary: {
+ uss,
+ rss,
+ }
+ });
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "inner-window-destroyed": {
+ // Forward inner-window-destroyed notifications with the
+ // inner window ID, so that code in the parent that should
+ // do something when content windows go away can do it
+ let innerWindowID =
+ subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ Services.cpmm.sendAsyncMessage("Toolkit:inner-window-destroyed",
+ innerWindowID);
+ break;
+ }
+ case "xpcom-shutdown": {
+ this.uninit();
+ break;
+ }
+ }
+ },
+ };
+
+ ProcessObserver.init();
+}
diff --git a/components/global/content/resetProfile.css b/components/global/content/resetProfile.css
new file mode 100644
index 000000000..a83171ff5
--- /dev/null
+++ b/components/global/content/resetProfile.css
@@ -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/. */
+
+#migratedItems {
+ margin-inline-start: 1.5em;
+}
+
+#resetProfileFooter {
+ font-weight: bold;
+}
+
+#resetProfileProgressDialog {
+ padding: 10px;
+}
diff --git a/components/global/content/resetProfile.js b/components/global/content/resetProfile.js
new file mode 100644
index 000000000..9a46a09a5
--- /dev/null
+++ b/components/global/content/resetProfile.js
@@ -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/. */
+
+"use strict";
+
+// NB: this file can be loaded from aboutSupport.xhtml or from the
+// resetProfile.xul dialog, and so Cu may or may not exist already.
+// Proceed with caution:
+if (!("Cu" in window)) {
+ window.Cu = Components.utils;
+}
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/ResetProfile.jsm");
+
+function onResetProfileAccepted() {
+ let retVals = window.arguments[0];
+ retVals.reset = true;
+}
diff --git a/components/global/content/resetProfile.xul b/components/global/content/resetProfile.xul
new file mode 100644
index 000000000..845d473a4
--- /dev/null
+++ b/components/global/content/resetProfile.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE prefwindow [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd" >
+%resetProfileDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://global/content/resetProfile.css"?>
+
+<dialog id="resetProfileDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&refreshProfile.dialog.title;"
+ buttons="accept,cancel"
+ defaultButton="cancel"
+ buttonlabelaccept="&refreshProfile.dialog.button.label;"
+ ondialogaccept="return onResetProfileAccepted();"
+ ondialogcancel="window.close();">
+
+ <script type="application/javascript" src="chrome://global/content/resetProfile.js"/>
+
+ <description value="&refreshProfile.dialog.description1;"></description>
+ <label value="&refreshProfile.dialog.description2;"/>
+
+ <vbox id="migratedItems">
+ <label class="migratedLabel" value="&refreshProfile.dialog.items.label1;"/>
+ <label class="migratedLabel" value="&refreshProfile.dialog.items.label2;"/>
+ </vbox>
+</dialog>
diff --git a/components/global/content/resetProfileProgress.xul b/components/global/content/resetProfileProgress.xul
new file mode 100644
index 000000000..66585e224
--- /dev/null
+++ b/components/global/content/resetProfileProgress.xul
@@ -0,0 +1,25 @@
+<?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 window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % resetProfileDTD SYSTEM "chrome://global/locale/resetProfile.dtd" >
+%resetProfileDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/content/resetProfile.css"?>
+<?xml-stylesheet href="chrome://global/skin/"?>
+
+<window id="resetProfileProgressDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&refreshProfile.dialog.title;"
+ style="min-width: 300px;">
+ <vbox>
+ <description>&refreshProfile.cleaning.description;</description>
+ <progressmeter mode="undetermined"/>
+ </vbox>
+</window>
diff --git a/components/global/content/select-child.js b/components/global/content/select-child.js
new file mode 100644
index 000000000..d3690b7b8
--- /dev/null
+++ b/components/global/content/select-child.js
@@ -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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SelectContentHelper",
+ "resource://gre/modules/SelectContentHelper.jsm");
+
+addEventListener("mozshowdropdown", event => {
+ if (!event.isTrusted)
+ return;
+
+ if (!SelectContentHelper.open) {
+ new SelectContentHelper(event.target, {isOpenedViaTouch: false}, this);
+ }
+});
+
+addEventListener("mozshowdropdown-sourcetouch", event => {
+ if (!event.isTrusted)
+ return;
+
+ if (!SelectContentHelper.open) {
+ new SelectContentHelper(event.target, {isOpenedViaTouch: true}, this);
+ }
+});
diff --git a/components/global/content/strres.js b/components/global/content/strres.js
new file mode 100644
index 000000000..928c8f75f
--- /dev/null
+++ b/components/global/content/strres.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var strBundleService = null;
+
+function srGetStrBundle(path)
+{
+ var strBundle = null;
+
+ if (!strBundleService) {
+ try {
+ strBundleService =
+ Components.classes["@mozilla.org/intl/stringbundle;1"].getService();
+ strBundleService =
+ strBundleService.QueryInterface(Components.interfaces.nsIStringBundleService);
+ } catch (ex) {
+ dump("\n--** strBundleService failed: " + ex + "\n");
+ return null;
+ }
+ }
+
+ strBundle = strBundleService.createBundle(path);
+ if (!strBundle) {
+ dump("\n--** strBundle createInstance failed **--\n");
+ }
+ return strBundle;
+}
diff --git a/components/global/content/tests/reftests/reftest-stylo.list b/components/global/content/tests/reftests/reftest-stylo.list
new file mode 100644
index 000000000..77caaabfc
--- /dev/null
+++ b/components/global/content/tests/reftests/reftest-stylo.list
@@ -0,0 +1,6 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+skip-if(B2G&&browserIsRemote) random-if(cocoaWidget) == bug-442419-progressmeter-max.xul bug-442419-progressmeter-max.xul
+# fails most of the time on Mac because progress meter animates
+# Bug 974780
+skip-if(B2G&&browserIsRemote) == textbox-multiline-default-value.xul textbox-multiline-default-value.xul
+# Bug 974780
diff --git a/components/global/content/tests/reftests/reftest.list b/components/global/content/tests/reftests/reftest.list
new file mode 100644
index 000000000..a37a9722a
--- /dev/null
+++ b/components/global/content/tests/reftests/reftest.list
@@ -0,0 +1,2 @@
+random-if(cocoaWidget) == bug-442419-progressmeter-max.xul bug-442419-progressmeter-max-ref.xul # fails most of the time on Mac because progress meter animates
+!= textbox-multiline-default-value.xul textbox-multiline-empty.xul
diff --git a/components/global/content/textbox.css b/components/global/content/textbox.css
new file mode 100644
index 000000000..a16b4fd43
--- /dev/null
+++ b/components/global/content/textbox.css
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
+@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */
+
+html|*.textbox-input {
+ -moz-appearance: none !important;
+ text-align: inherit;
+ text-shadow: inherit;
+ box-sizing: border-box;
+ -moz-box-flex: 1;
+}
+
+html|*.textbox-textarea {
+ -moz-appearance: none !important;
+ text-shadow: inherit;
+ box-sizing: border-box;
+ -moz-box-flex: 1;
+}
+
+/*
+html|*.textbox-input::placeholder,
+html|*.textbox-textarea::placeholder {
+ text-align: left;
+ direction: ltr;
+}
+
+html|*.textbox-input::placeholder:-moz-locale-dir(rtl),
+html|*.textbox-textarea::placeholder:-moz-locale-dir(rtl) {
+ text-align: right;
+ direction: rtl;
+}
+*/
diff --git a/components/global/content/timepicker.xhtml b/components/global/content/timepicker.xhtml
new file mode 100644
index 000000000..77b9fba41
--- /dev/null
+++ b/components/global/content/timepicker.xhtml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+ <title>Time Picker</title>
+ <link rel="stylesheet" href="chrome://global/skin/datetimeinputpickers.css"/>
+ <script type="application/javascript" src="chrome://global/content/bindings/timekeeper.js"></script>
+ <script type="application/javascript" src="chrome://global/content/bindings/spinner.js"></script>
+ <script type="application/javascript" src="chrome://global/content/bindings/timepicker.js"></script>
+</head>
+<body>
+ <div id="time-picker"></div>
+ <template id="spinner-template">
+ <div class="spinner-container">
+ <button class="up"/>
+ <div class="spinner"></div>
+ <button class="down"/>
+ </div>
+ </template>
+ <script type="application/javascript">
+ // We need to hide the scroll bar but maintain its scrolling
+ // capability, so using |overflow: hidden| is not an option.
+ // Instead, we are inserting a user agent stylesheet that is
+ // capable of selecting scrollbars, and do |display: none|.
+ var domWinUtls = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+ domWinUtls.loadSheetUsingURIString('data:text/css,@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); scrollbar { display: none; }', domWinUtls.AGENT_SHEET);
+ // Create a TimePicker instance and prepare to be
+ // initialized by the "TimePickerInit" event from timepicker.xml
+ new TimePicker(document.getElementById("time-picker"));
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/components/global/content/treeUtils.js b/components/global/content/treeUtils.js
new file mode 100644
index 000000000..b4d1fb17d
--- /dev/null
+++ b/components/global/content/treeUtils.js
@@ -0,0 +1,78 @@
+// -*- 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 gTreeUtils = {
+ deleteAll: function (aTree, aView, aItems, aDeletedItems)
+ {
+ for (var i = 0; i < aItems.length; ++i)
+ aDeletedItems.push(aItems[i]);
+ aItems.splice(0, aItems.length);
+ var oldCount = aView.rowCount;
+ aView._rowCount = 0;
+ aTree.treeBoxObject.rowCountChanged(0, -oldCount);
+ },
+
+ deleteSelectedItems: function (aTree, aView, aItems, aDeletedItems)
+ {
+ var selection = aTree.view.selection;
+ selection.selectEventsSuppressed = true;
+
+ var rc = selection.getRangeCount();
+ for (var i = 0; i < rc; ++i) {
+ var min = { }; var max = { };
+ selection.getRangeAt(i, min, max);
+ for (let j = min.value; j <= max.value; ++j) {
+ aDeletedItems.push(aItems[j]);
+ aItems[j] = null;
+ }
+ }
+
+ var nextSelection = 0;
+ for (i = 0; i < aItems.length; ++i) {
+ if (!aItems[i]) {
+ let j = i;
+ while (j < aItems.length && !aItems[j])
+ ++j;
+ aItems.splice(i, j - i);
+ nextSelection = j < aView.rowCount ? j - 1 : j - 2;
+ aView._rowCount -= j - i;
+ aTree.treeBoxObject.rowCountChanged(i, i - j);
+ }
+ }
+
+ if (aItems.length) {
+ selection.select(nextSelection);
+ aTree.treeBoxObject.ensureRowIsVisible(nextSelection);
+ aTree.focus();
+ }
+ selection.selectEventsSuppressed = false;
+ },
+
+ sort: function (aTree, aView, aDataSet, aColumn, aComparator,
+ aLastSortColumn, aLastSortAscending)
+ {
+ var ascending = (aColumn == aLastSortColumn) ? !aLastSortAscending : true;
+ if (aDataSet.length == 0)
+ return ascending;
+
+ var numericSort = !isNaN(aDataSet[0][aColumn]);
+ var sortFunction = null;
+ if (aComparator) {
+ sortFunction = function (a, b) { return aComparator(a[aColumn], b[aColumn]); };
+ }
+ aDataSet.sort(sortFunction);
+ if (!ascending)
+ aDataSet.reverse();
+
+ aTree.view.selection.clearSelection();
+ aTree.view.selection.select(0);
+ aTree.treeBoxObject.invalidate();
+ aTree.treeBoxObject.ensureRowIsVisible(0);
+
+ return ascending;
+ }
+};
+
diff --git a/components/global/content/viewZoomOverlay.js b/components/global/content/viewZoomOverlay.js
new file mode 100644
index 000000000..66e054437
--- /dev/null
+++ b/components/global/content/viewZoomOverlay.js
@@ -0,0 +1,117 @@
+// -*- 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/. */
+
+/** Document Zoom Management Code
+ *
+ * To use this, you'll need to have a getBrowser() function or use the methods
+ * that accept a browser to be modified.
+ **/
+
+var ZoomManager = {
+ get _prefBranch() {
+ delete this._prefBranch;
+ return this._prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ },
+
+ get MIN() {
+ delete this.MIN;
+ return this.MIN = this._prefBranch.getIntPref("zoom.minPercent") / 100;
+ },
+
+ get MAX() {
+ delete this.MAX;
+ return this.MAX = this._prefBranch.getIntPref("zoom.maxPercent") / 100;
+ },
+
+ get useFullZoom() {
+ return this._prefBranch.getBoolPref("browser.zoom.full");
+ },
+
+ set useFullZoom(aVal) {
+ this._prefBranch.setBoolPref("browser.zoom.full", aVal);
+ return aVal;
+ },
+
+ get zoom() {
+ return this.getZoomForBrowser(getBrowser());
+ },
+
+ getZoomForBrowser: function ZoomManager_getZoomForBrowser(aBrowser) {
+ let zoom = (this.useFullZoom || aBrowser.isSyntheticDocument)
+ ? aBrowser.fullZoom : aBrowser.textZoom;
+ // Round to remove any floating-point error.
+ return Number(zoom.toFixed(2));
+ },
+
+ set zoom(aVal) {
+ this.setZoomForBrowser(getBrowser(), aVal);
+ return aVal;
+ },
+
+ setZoomForBrowser: function ZoomManager_setZoomForBrowser(aBrowser, aVal) {
+ if (aVal < this.MIN || aVal > this.MAX)
+ throw Components.results.NS_ERROR_INVALID_ARG;
+
+ if (this.useFullZoom || aBrowser.isSyntheticDocument) {
+ aBrowser.textZoom = 1;
+ aBrowser.fullZoom = aVal;
+ } else {
+ aBrowser.textZoom = aVal;
+ aBrowser.fullZoom = 1;
+ }
+ },
+
+ get zoomValues() {
+ var zoomValues = this._prefBranch.getCharPref("toolkit.zoomManager.zoomValues")
+ .split(",").map(parseFloat);
+ zoomValues.sort((a, b) => a - b);
+
+ while (zoomValues[0] < this.MIN)
+ zoomValues.shift();
+
+ while (zoomValues[zoomValues.length - 1] > this.MAX)
+ zoomValues.pop();
+
+ delete this.zoomValues;
+ return this.zoomValues = zoomValues;
+ },
+
+ enlarge: function ZoomManager_enlarge() {
+ var i = this.zoomValues.indexOf(this.snap(this.zoom)) + 1;
+ if (i < this.zoomValues.length)
+ this.zoom = this.zoomValues[i];
+ },
+
+ reduce: function ZoomManager_reduce() {
+ var i = this.zoomValues.indexOf(this.snap(this.zoom)) - 1;
+ if (i >= 0)
+ this.zoom = this.zoomValues[i];
+ },
+
+ reset: function ZoomManager_reset() {
+ this.zoom = 1;
+ },
+
+ toggleZoom: function ZoomManager_toggleZoom() {
+ var zoomLevel = this.zoom;
+
+ this.useFullZoom = !this.useFullZoom;
+ this.zoom = zoomLevel;
+ },
+
+ snap: function ZoomManager_snap(aVal) {
+ var values = this.zoomValues;
+ for (var i = 0; i < values.length; i++) {
+ if (values[i] >= aVal) {
+ if (i > 0 && aVal - values[i - 1] < values[i] - aVal)
+ i--;
+ return values[i];
+ }
+ }
+ return values[i - 1];
+ }
+};
diff --git a/components/global/content/xul.css b/components/global/content/xul.css
new file mode 100644
index 000000000..f51ec77e4
--- /dev/null
+++ b/components/global/content/xul.css
@@ -0,0 +1,1198 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A minimal set of rules for the XUL elements that may be implicitly created
+ * as part of HTML/SVG documents (e.g. scrollbars) can be found over in
+ * minimal-xul.css. Rules for everything else related to XUL can be found in
+ * this file. Make sure you choose the correct style sheet when adding new
+ * rules. (This split of the XUL rules is to minimize memory use and improve
+ * performance in HTML/SVG documents.)
+ *
+ * This file should also not contain any app specific styling. Defaults for
+ * widgets of a particular application should be in that application's style
+ * sheet. For example, style definitions for navigator can be found in
+ * navigator.css.
+ *
+ * THIS FILE IS LOCKED DOWN. YOU ARE NOT ALLOWED TO MODIFY IT WITHOUT FIRST
+ * HAVING YOUR CHANGES REVIEWED BY enndeakin@gmail.com
+ */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
+@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */
+@namespace xbl url("http://www.mozilla.org/xbl"); /* namespace for XBL elements */
+
+/* ::::::::::
+ :: Rules for 'hiding' portions of the chrome for special
+ :: kinds of windows (not JUST browser windows) with toolbars
+ ::::: */
+
+window[chromehidden~="menubar"] .chromeclass-menubar,
+window[chromehidden~="directories"] .chromeclass-directories,
+window[chromehidden~="status"] .chromeclass-status,
+window[chromehidden~="extrachrome"] .chromeclass-extrachrome,
+window[chromehidden~="location"] .chromeclass-location,
+window[chromehidden~="location"][chromehidden~="toolbar"] .chromeclass-toolbar,
+window[chromehidden~="toolbar"] .chromeclass-toolbar-additional {
+ display: none;
+}
+
+/* ::::::::::
+ :: Rules for forcing direction for entry and display of URIs
+ :: or URI elements
+ ::::: */
+
+.uri-element {
+ direction: ltr !important;
+}
+
+/****** elements that have no visual representation ******/
+
+script, data,
+xbl|children,
+commands, commandset, command,
+broadcasterset, broadcaster, observes,
+keyset, key, toolbarpalette, toolbarset,
+template, rule, conditions, action,
+bindings, binding, content, member, triple,
+treechildren, treeitem, treeseparator, treerow, treecell {
+ display: none;
+}
+
+/********** focus rules **********/
+
+button,
+checkbox,
+colorpicker[type="button"],
+datepicker[type="grid"],
+menulist,
+radiogroup,
+tree,
+browser,
+editor,
+iframe {
+ -moz-user-focus: normal;
+}
+
+menulist[editable="true"] {
+ -moz-user-focus: ignore;
+}
+
+/******** window & page ******/
+
+window,
+page {
+ overflow: -moz-hidden-unscrollable;
+ -moz-box-orient: vertical;
+}
+
+/******** box *******/
+
+vbox {
+ -moz-box-orient: vertical;
+}
+
+bbox {
+ -moz-box-align: baseline;
+}
+
+/********** button **********/
+
+button {
+ -moz-binding: url("chrome://global/content/bindings/button.xml#button");
+}
+
+button[type="repeat"] {
+ -moz-binding: url("chrome://global/content/bindings/button.xml#button-repeat");
+}
+
+button[type="menu"], button[type="panel"] {
+ -moz-binding: url("chrome://global/content/bindings/button.xml#menu");
+}
+
+button[type="menu-button"] {
+ -moz-binding: url("chrome://global/content/bindings/button.xml#menu-button");
+}
+
+/********** toolbarbutton **********/
+
+toolbarbutton {
+ -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton");
+}
+
+toolbarbutton.badged-button > toolbarbutton,
+toolbarbutton.badged-button {
+ -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged");
+}
+
+.toolbarbutton-badge:not([value]),
+.toolbarbutton-badge[value=""] {
+ display: none;
+}
+
+toolbarbutton[type="menu"],
+toolbarbutton[type="panel"] {
+ -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu");
+}
+
+toolbarbutton.badged-button[type="menu"],
+toolbarbutton.badged-button[type="panel"] {
+ -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged-menu");
+}
+
+toolbarbutton[type="menu-button"] {
+ -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu-button");
+}
+
+toolbar[mode="icons"] .toolbarbutton-text,
+toolbar[mode="icons"] .toolbarbutton-multiline-text,
+toolbar[mode="text"] .toolbarbutton-icon {
+ display: none;
+}
+
+.toolbarbutton-multiline-text:not([wrap="true"]),
+.toolbarbutton-text[wrap="true"] {
+ display: none;
+}
+
+/******** browser, editor, iframe ********/
+
+browser,
+editor,
+iframe {
+ display: inline;
+}
+
+browser {
+ -moz-binding: url("chrome://global/content/bindings/browser.xml#browser");
+}
+
+editor {
+ -moz-binding: url("chrome://global/content/bindings/editor.xml#editor");
+}
+
+iframe {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#iframe");
+}
+
+/********** notifications **********/
+
+notificationbox {
+ -moz-binding: url("chrome://global/content/bindings/notification.xml#notificationbox");
+ -moz-box-orient: vertical;
+}
+
+.notificationbox-stack {
+ overflow: -moz-hidden-unscrollable;
+}
+
+notification {
+ -moz-binding: url("chrome://global/content/bindings/notification.xml#notification");
+ transition: margin-top 300ms, opacity 300ms;
+}
+
+/*********** popup notification ************/
+popupnotification {
+ -moz-binding: url("chrome://global/content/bindings/notification.xml#popup-notification");
+}
+
+.popup-notification-menubutton:not([label]) {
+ display: none;
+}
+
+/********** image **********/
+
+image {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#image");
+}
+
+/********** checkbox **********/
+
+checkbox {
+ -moz-binding: url("chrome://global/content/bindings/checkbox.xml#checkbox");
+}
+
+/********** radio **********/
+
+radiogroup {
+ -moz-binding: url("chrome://global/content/bindings/radio.xml#radiogroup");
+ -moz-box-orient: vertical;
+}
+
+radio {
+ -moz-binding: url("chrome://global/content/bindings/radio.xml#radio");
+}
+
+/******** groupbox *********/
+
+groupbox {
+ -moz-binding: url("chrome://global/content/bindings/groupbox.xml#groupbox");
+ display: -moz-groupbox;
+}
+
+caption {
+ -moz-binding: url("chrome://global/content/bindings/groupbox.xml#caption");
+}
+
+.groupbox-body {
+ -moz-box-pack: inherit;
+ -moz-box-align: inherit;
+ -moz-box-orient: vertical;
+}
+
+/******** draggable elements *********/
+
+windowdragbox {
+ -moz-window-dragging: drag;
+}
+
+/* The list below is non-comprehensive and will probably need some tweaking. */
+toolbaritem,
+toolbarbutton,
+button,
+textbox,
+tab,
+radio,
+splitter,
+scale,
+menulist {
+ -moz-window-dragging: no-drag;
+}
+
+/******* toolbar *******/
+
+toolbox {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbox");
+ -moz-box-orient: vertical;
+}
+
+toolbar {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar");
+}
+
+toolbar[customizing="true"][collapsed="true"] {
+ /* Some apps, e.g. Firefox, use 'collapsed' to hide toolbars.
+ Override it while customizing. */
+ visibility: visible;
+}
+
+toolbar[customizing="true"][hidden="true"] {
+ /* Some apps, e.g. SeaMonkey, use 'hidden' to hide toolbars.
+ Override it while customizing. */
+ display: -moz-box;
+}
+
+toolbar[type="menubar"][autohide="true"] {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-menubar-autohide");
+ overflow: hidden;
+}
+
+toolbar[type="menubar"][autohide="true"][inactive="true"]:not([customizing="true"]) {
+ min-height: 0 !important;
+ height: 0 !important;
+ -moz-appearance: none !important;
+ border-style: none !important;
+}
+
+%ifdef MOZ_WIDGET_GTK
+window[shellshowingmenubar="true"] menubar,
+window[shellshowingmenubar="true"]
+toolbar[type="menubar"]:not([customizing="true"]) {
+ /* If a system-wide global menubar is in use, hide the XUL menubar. */
+ display: none !important;
+}
+%endif
+
+toolbarseparator {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration");
+}
+
+toolbarspacer {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration");
+}
+
+toolbarspring {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbardecoration");
+ -moz-box-flex: 1000;
+}
+
+toolbarpaletteitem {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem");
+}
+
+toolbarpaletteitem[place="palette"] {
+ -moz-box-orient: vertical;
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbarpaletteitem-palette");
+}
+
+/********* menubar ***********/
+
+menubar {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#menubar");
+}
+
+/********* menu ***********/
+
+menubar > menu {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menu-menubar");
+}
+
+menubar > menu.menu-iconic {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menu-menubar-iconic");
+}
+
+menu {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menu");
+}
+
+menu.menu-iconic {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menu-iconic");
+}
+
+menubar > menu:empty {
+ visibility: collapse;
+}
+
+/********* menuitem ***********/
+
+menuitem {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem");
+}
+
+menuitem.menuitem-iconic {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
+}
+
+menuitem[description] {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic-desc-noaccel");
+}
+
+menuitem[type="checkbox"],
+menuitem[type="radio"] {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
+}
+
+menuitem.menuitem-non-iconic {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menubutton-item");
+}
+
+menucaption {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menucaption");
+}
+
+.menu-text {
+ -moz-box-flex: 1;
+}
+
+/********* menuseparator ***********/
+
+menuseparator {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuseparator");
+}
+
+/********* popup & menupopup ***********/
+
+/* <popup> is deprecated. Only <menupopup> and <tooltip> are still valid. */
+
+popup,
+menupopup {
+ -moz-binding: url("chrome://global/content/bindings/popup.xml#popup");
+ -moz-box-orient: vertical;
+}
+
+panel {
+ -moz-binding: url("chrome://global/content/bindings/popup.xml#panel");
+ -moz-box-orient: vertical;
+}
+
+popup,
+menupopup,
+panel,
+tooltip {
+ display: -moz-popup;
+ z-index: 2147483647;
+ text-shadow: none;
+}
+
+tooltip {
+ -moz-binding: url("chrome://global/content/bindings/popup.xml#tooltip");
+ -moz-box-orient: vertical;
+ white-space: pre-wrap;
+ margin-top: 21px;
+}
+
+panel[type="arrow"] {
+ -moz-binding: url("chrome://global/content/bindings/popup.xml#arrowpanel");
+}
+
+%ifndef MOZ_WIDGET_GTK
+
+panel[type="arrow"]:not([animate="false"]) {
+ transform: scale(.4);
+ opacity: 0;
+ transition-property: transform, opacity;
+ transition-duration: 0.15s;
+ transition-timing-function: ease-out;
+}
+
+panel[type="arrow"][animate="open"] {
+ transform: none;
+ opacity: 1.0;
+}
+
+panel[type="arrow"][animate="cancel"] {
+ transform: none;
+}
+
+panel[arrowposition="after_start"]:-moz-locale-dir(ltr),
+panel[arrowposition="after_end"]:-moz-locale-dir(rtl) {
+ transform-origin: 20px top;
+}
+
+panel[arrowposition="after_end"]:-moz-locale-dir(ltr),
+panel[arrowposition="after_start"]:-moz-locale-dir(rtl) {
+ transform-origin: calc(100% - 20px) top;
+}
+
+panel[arrowposition="before_start"]:-moz-locale-dir(ltr),
+panel[arrowposition="before_end"]:-moz-locale-dir(rtl) {
+ transform-origin: 20px bottom;
+}
+
+panel[arrowposition="before_end"]:-moz-locale-dir(ltr),
+panel[arrowposition="before_start"]:-moz-locale-dir(rtl) {
+ transform-origin: calc(100% - 20px) bottom;
+}
+
+panel[arrowposition="start_before"]:-moz-locale-dir(ltr),
+panel[arrowposition="end_before"]:-moz-locale-dir(rtl) {
+ transform-origin: right 20px;
+}
+
+panel[arrowposition="start_after"]:-moz-locale-dir(ltr),
+panel[arrowposition="end_after"]:-moz-locale-dir(rtl) {
+ transform-origin: right calc(100% - 20px);
+}
+
+panel[arrowposition="end_before"]:-moz-locale-dir(ltr),
+panel[arrowposition="start_before"]:-moz-locale-dir(rtl) {
+ transform-origin: left 20px;
+}
+
+panel[arrowposition="end_after"]:-moz-locale-dir(ltr),
+panel[arrowposition="start_after"]:-moz-locale-dir(rtl) {
+ transform-origin: left calc(100% - 20px);
+}
+
+%endif
+
+window[sizemode="maximized"] statusbarpanel.statusbar-resizerpanel {
+ visibility: collapse;
+}
+
+/******** grid **********/
+
+grid {
+ display: -moz-grid;
+}
+
+rows,
+columns {
+ display: -moz-grid-group;
+}
+
+row,
+column {
+ display: -moz-grid-line;
+}
+
+rows {
+ -moz-box-orient: vertical;
+}
+
+column {
+ -moz-box-orient: vertical;
+}
+
+/******** listbox **********/
+
+listbox {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listbox");
+}
+
+listhead {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listhead");
+}
+
+listrows {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listrows");
+}
+
+listitem {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem");
+}
+
+listitem[type="checkbox"] {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem-checkbox");
+}
+
+listheader {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listheader");
+ -moz-box-ordinal-group: 2147483646;
+}
+
+listcell {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell");
+}
+
+listcell[type="checkbox"] {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell-checkbox");
+}
+
+.listitem-iconic {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem-iconic");
+}
+
+listitem[type="checkbox"].listitem-iconic {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listitem-checkbox-iconic");
+}
+
+.listcell-iconic {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell-iconic");
+}
+
+listcell[type="checkbox"].listcell-iconic {
+ -moz-binding: url("chrome://global/content/bindings/listbox.xml#listcell-checkbox-iconic");
+}
+
+listbox {
+ display: -moz-grid;
+}
+
+listbox[rows] {
+ height: auto;
+}
+
+listcols, listhead, listrows, listboxbody {
+ display: -moz-grid-group;
+}
+
+listcol, listitem, listheaditem {
+ display: -moz-grid-line;
+}
+
+listbox {
+ -moz-user-focus: normal;
+ -moz-box-orient: vertical;
+ min-width: 0px;
+ min-height: 0px;
+ width: 200px;
+ height: 200px;
+}
+
+listhead {
+ -moz-box-orient: vertical;
+}
+
+listrows {
+ -moz-box-orient: vertical;
+ -moz-box-flex: 1;
+}
+
+listboxbody {
+ -moz-box-orient: vertical;
+ -moz-box-flex: 1;
+ /* Don't permit a horizontal scrollbar. See bug 285449 */
+ overflow-x: hidden !important;
+ overflow-y: auto;
+ min-height: 0px;
+}
+
+listcol {
+ -moz-box-orient: vertical;
+ min-width: 16px;
+}
+
+listcell {
+ -moz-box-align: center;
+}
+
+/******** tree ******/
+
+tree {
+ -moz-binding: url("chrome://global/content/bindings/tree.xml#tree");
+}
+
+treecols {
+ -moz-binding: url("chrome://global/content/bindings/tree.xml#treecols");
+}
+
+treecol {
+ -moz-binding: url("chrome://global/content/bindings/tree.xml#treecol");
+ -moz-box-ordinal-group: 2147483646;
+}
+
+treecol.treecol-image {
+ -moz-binding: url("chrome://global/content/bindings/tree.xml#treecol-image");
+}
+
+tree > treechildren {
+ display: -moz-box;
+ -moz-binding: url("chrome://global/content/bindings/tree.xml#treebody");
+ -moz-user-select: none;
+ -moz-box-flex: 1;
+}
+
+treerows {
+ -moz-binding: url("chrome://global/content/bindings/tree.xml#treerows");
+}
+
+treecolpicker {
+ -moz-binding: url("chrome://global/content/bindings/tree.xml#columnpicker");
+}
+
+tree {
+ -moz-box-orient: vertical;
+ min-width: 0px;
+ min-height: 0px;
+ width: 10px;
+ height: 10px;
+}
+
+tree[hidecolumnpicker="true"] > treecols > treecolpicker {
+ display: none;
+}
+
+treecol {
+ min-width: 16px;
+}
+
+treecol[hidden="true"] {
+ visibility: collapse;
+ display: -moz-box;
+}
+
+.tree-scrollable-columns {
+ /* Yes, Virginia, this makes it scrollable */
+ overflow: hidden;
+}
+
+/* ::::: lines connecting cells ::::: */
+tree:not([treelines="true"]) > treechildren::-moz-tree-line {
+ visibility: hidden;
+}
+
+treechildren::-moz-tree-cell(ltr) {
+ direction: ltr !important;
+}
+
+/********** deck & stack *********/
+
+deck {
+ display: -moz-deck;
+ -moz-binding: url("chrome://global/content/bindings/general.xml#deck");
+}
+
+stack, bulletinboard {
+ display: -moz-stack;
+}
+
+/********** tabbox *********/
+
+tabbox {
+ -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabbox");
+ -moz-box-orient: vertical;
+}
+
+tabs {
+ -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabs");
+ -moz-box-orient: horizontal;
+}
+
+tab {
+ -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tab");
+ -moz-box-align: center;
+ -moz-box-pack: center;
+}
+
+tab[selected="true"]:not([ignorefocus="true"]) {
+ -moz-user-focus: normal;
+}
+
+tabpanels {
+ -moz-binding: url("chrome://global/content/bindings/tabbox.xml#tabpanels");
+ display: -moz-deck;
+}
+
+/********** progressmeter **********/
+
+progressmeter {
+ -moz-binding: url("chrome://global/content/bindings/progressmeter.xml#progressmeter");
+}
+
+/********** basic rule for anonymous content that needs to pass box properties through
+ ********** to an insertion point parent that holds the real kids **************/
+
+.box-inherit {
+ -moz-box-orient: inherit;
+ -moz-box-pack: inherit;
+ -moz-box-align: inherit;
+ -moz-box-direction: inherit;
+}
+
+/********** textbox **********/
+
+textbox {
+ -moz-binding: url("chrome://global/content/bindings/textbox.xml#textbox");
+ -moz-user-select: text;
+ text-shadow: none;
+}
+
+textbox[multiline="true"] {
+ -moz-binding: url("chrome://global/content/bindings/textbox.xml#textarea");
+}
+
+.textbox-input-box {
+ -moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box");
+}
+
+html|textarea.textbox-textarea {
+ resize: none;
+}
+
+textbox[resizable="true"] > .textbox-input-box > html|textarea.textbox-textarea {
+ resize: both;
+}
+
+.textbox-input-box[spellcheck="true"] {
+ -moz-binding: url("chrome://global/content/bindings/textbox.xml#input-box-spell");
+}
+
+textbox[type="timed"] {
+ -moz-binding: url("chrome://global/content/bindings/textbox.xml#timed-textbox");
+}
+
+textbox[type="search"] {
+ -moz-binding: url("chrome://global/content/bindings/textbox.xml#search-textbox");
+}
+
+textbox[type="number"] {
+ -moz-binding: url("chrome://global/content/bindings/numberbox.xml#numberbox");
+}
+
+.textbox-contextmenu:-moz-locale-dir(rtl) {
+ direction: rtl;
+}
+
+/********** autocomplete textbox **********/
+
+/* SeaMonkey does not use the new toolkit's autocomplete widget */
+%ifdef BINOC_NAVIGATOR
+
+textbox[type="autocomplete"] {
+ -moz-binding: url("chrome://global/content/autocomplete.xml#autocomplete");
+}
+
+panel[type="autocomplete"] {
+ -moz-binding: url("chrome://global/content/autocomplete.xml#autocomplete-result-popup");
+}
+
+.autocomplete-history-popup {
+ -moz-binding: url("chrome://global/content/autocomplete.xml#autocomplete-history-popup");
+}
+
+.autocomplete-treebody {
+ -moz-binding: url("chrome://global/content/autocomplete.xml#autocomplete-treebody");
+}
+
+.autocomplete-history-dropmarker {
+ -moz-binding: url("chrome://global/content/autocomplete.xml#history-dropmarker");
+}
+
+panel[type="autocomplete-richlistbox"] {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup");
+}
+
+.autocomplete-richlistbox {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistbox");
+ -moz-user-focus: ignore;
+}
+
+.autocomplete-richlistbox > scrollbox {
+ overflow-x: hidden !important;
+}
+
+.autocomplete-richlistitem {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem");
+ -moz-box-orient: vertical;
+ overflow: -moz-hidden-unscrollable;
+}
+
+%else
+
+textbox[type="autocomplete"] {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete");
+}
+
+panel[type="autocomplete"] {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-result-popup");
+}
+
+panel[type="autocomplete-richlistbox"] {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup");
+}
+
+/* FIXME: bug 616258 */
+
+.autocomplete-tree {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-tree");
+ -moz-user-focus: ignore;
+}
+
+.autocomplete-treebody {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-treebody");
+}
+
+.autocomplete-richlistbox {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistbox");
+ -moz-user-focus: ignore;
+}
+
+.autocomplete-richlistbox > scrollbox {
+ overflow-x: hidden !important;
+}
+
+.autocomplete-richlistitem {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem");
+ -moz-box-orient: vertical;
+ overflow: -moz-hidden-unscrollable;
+}
+
+.autocomplete-treerows {
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#autocomplete-treerows");
+}
+
+.autocomplete-history-dropmarker {
+ display: none;
+}
+
+.autocomplete-history-dropmarker[enablehistory="true"] {
+ display: -moz-box;
+ -moz-binding: url("chrome://global/content/bindings/autocomplete.xml#history-dropmarker");
+}
+
+%endif
+
+/* the C++ implementation of widgets is too eager to make popups visible.
+ this causes problems (bug 120155 and others), thus this workaround: */
+popup[type="autocomplete"][hidden="true"] {
+ visibility: hidden;
+}
+
+/* The following rule is here to fix bug 96899 (and now 117952).
+ Somehow trees create a situation
+ in which a popupset flows itself as if its popup child is directly within it
+ instead of the placeholder child that should actually be inside the popupset.
+ This is a stopgap measure, and it does not address the real bug. */
+.autocomplete-result-popupset {
+ max-width: 0px;
+ width: 0 !important;
+ min-width: 0%;
+ min-height: 0%;
+}
+
+/********** colorpicker **********/
+
+colorpicker {
+ -moz-binding: url("chrome://global/content/bindings/colorpicker.xml#colorpicker");
+}
+
+colorpicker[type="button"] {
+ -moz-binding: url("chrome://global/content/bindings/colorpicker.xml#colorpicker-button");
+}
+
+.colorpickertile {
+ -moz-binding: url("chrome://global/content/bindings/colorpicker.xml#colorpickertile");
+}
+
+/********** menulist **********/
+
+menulist {
+ -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist");
+}
+
+menulist[popuponly="true"] {
+ -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-popuponly");
+ -moz-appearance: none !important;
+ margin: 0 !important;
+ height: 0 !important;
+ border: 0 !important;
+}
+
+menulist[editable="true"] {
+ -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-editable");
+}
+
+menulist[type="description"] {
+ -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-description");
+}
+
+menulist > menupopup > menuitem {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic-noaccel");
+}
+
+menulist > menupopup > menucaption {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menucaption-inmenulist");
+}
+
+dropmarker {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#dropmarker");
+}
+
+/********** splitter **********/
+
+splitter {
+ -moz-binding: url("chrome://global/content/bindings/splitter.xml#splitter");
+}
+
+grippy {
+ -moz-binding: url("chrome://global/content/bindings/splitter.xml#grippy");
+}
+
+.tree-splitter {
+ width: 0px;
+ max-width: 0px;
+ min-width: 0% ! important;
+ min-height: 0% ! important;
+ -moz-box-ordinal-group: 2147483646;
+}
+
+/******** scrollbar ********/
+
+slider {
+ /* This is a hint to layerization that the scrollbar thumb can never leave
+ the scrollbar track. */
+ overflow: hidden;
+}
+
+/******** scrollbox ********/
+
+scrollbox {
+ -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#scrollbox");
+ /* This makes it scrollable! */
+ overflow: hidden;
+}
+
+arrowscrollbox {
+ -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#arrowscrollbox");
+}
+
+arrowscrollbox[clicktoscroll="true"] {
+ -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll");
+}
+
+autorepeatbutton {
+ -moz-binding: url("chrome://global/content/bindings/scrollbox.xml#autorepeatbutton");
+}
+
+/********** statusbar **********/
+
+statusbar {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#statusbar");
+}
+
+statusbarpanel {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel");
+}
+
+.statusbarpanel-iconic {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel-iconic");
+}
+
+.statusbarpanel-iconic-text {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel-iconic-text");
+}
+
+.statusbarpanel-menu-iconic {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#statusbarpanel-menu-iconic");
+}
+
+/********** spinbuttons ***********/
+
+spinbuttons {
+ -moz-binding: url("chrome://global/content/bindings/spinbuttons.xml#spinbuttons");
+}
+
+.spinbuttons-button {
+ -moz-user-focus: ignore;
+}
+
+/********** stringbundle **********/
+
+stringbundleset {
+ -moz-binding: url("chrome://global/content/bindings/stringbundle.xml#stringbundleset");
+ visibility: collapse;
+}
+
+stringbundle {
+ -moz-binding: url("chrome://global/content/bindings/stringbundle.xml#stringbundle");
+ visibility: collapse;
+}
+
+/********** dialog **********/
+
+dialog,
+dialog:root /* override :root from above */ {
+ -moz-binding: url("chrome://global/content/bindings/dialog.xml#dialog");
+ -moz-box-orient: vertical;
+}
+
+dialogheader {
+ -moz-binding: url("chrome://global/content/bindings/dialog.xml#dialogheader");
+}
+
+/********* page ************/
+
+page {
+ -moz-box-orient: vertical;
+}
+
+/********** wizard **********/
+
+wizard,
+wizard:root /* override :root from above */ {
+ -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard");
+ -moz-box-orient: vertical;
+ width: 40em;
+ height: 30em;
+}
+
+wizardpage {
+ -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizardpage");
+ -moz-box-orient: vertical;
+ overflow: auto;
+}
+
+.wizard-header {
+ -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard-header");
+}
+
+.wizard-buttons {
+ -moz-binding: url("chrome://global/content/bindings/wizard.xml#wizard-buttons");
+}
+
+/********** preferences ********/
+
+prefwindow,
+prefwindow:root /* override :root from above */ {
+ -moz-binding: url("chrome://global/content/bindings/preferences.xml#prefwindow");
+ -moz-box-orient: vertical;
+}
+
+prefpane {
+ -moz-binding: url("chrome://global/content/bindings/preferences.xml#prefpane");
+ -moz-box-orient: vertical;
+}
+
+prefwindow > .paneDeckContainer {
+ overflow: hidden;
+}
+
+prefpane > .content-box {
+ overflow: hidden;
+}
+
+prefwindow[type="child"] > .paneDeckContainer {
+ overflow: -moz-hidden-unscrollable;
+}
+
+prefwindow[type="child"] > prefpane > .content-box {
+ -moz-box-flex: 1;
+ overflow: -moz-hidden-unscrollable;
+}
+
+preferences {
+ -moz-binding: url("chrome://global/content/bindings/preferences.xml#preferences");
+ visibility: collapse;
+}
+
+preference {
+ -moz-binding: url("chrome://global/content/bindings/preferences.xml#preference");
+ visibility: collapse;
+}
+
+radio[pane] {
+ -moz-binding: url("chrome://global/content/bindings/preferences.xml#panebutton") !important;
+ -moz-box-orient: vertical;
+ -moz-box-align: center;
+}
+
+prefwindow[chromehidden~="toolbar"] .chromeclass-toolbar {
+ display: none;
+}
+
+/********** expander ********/
+
+expander {
+ -moz-binding: url("chrome://global/content/bindings/expander.xml#expander");
+ -moz-box-orient: vertical;
+}
+
+
+/********** Rich Listbox ********/
+
+richlistbox {
+ -moz-binding: url('chrome://global/content/bindings/richlistbox.xml#richlistbox');
+ -moz-user-focus: normal;
+ -moz-box-orient: vertical;
+}
+
+richlistitem {
+ -moz-binding: url('chrome://global/content/bindings/richlistbox.xml#richlistitem');
+}
+
+richlistbox > listheader {
+ -moz-box-ordinal-group: 1;
+}
+
+/********** datepicker and timepicker ********/
+
+datepicker {
+ -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#datepicker');
+}
+
+datepicker[type="popup"] {
+ -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#datepicker-popup');
+}
+
+datepicker[type="grid"] {
+ -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#datepicker-grid');
+}
+
+timepicker {
+ -moz-binding: url('chrome://global/content/bindings/datetimepicker.xml#timepicker');
+}
+
+
+/*********** findbar ************/
+findbar {
+ -moz-binding: url('chrome://global/content/bindings/findbar.xml#findbar');
+}
+
+.findbar-textbox {
+ -moz-binding: url("chrome://global/content/bindings/findbar.xml#findbar-textbox");
+}
+
+
+/*********** filefield ************/
+filefield {
+ -moz-binding: url("chrome://global/content/bindings/filefield.xml#filefield");
+}
+
+/*********** tabmodalprompt ************/
+tabmodalprompt {
+ -moz-binding: url("chrome://global/content/tabprompts.xml#tabmodalprompt");
+ overflow: hidden;
+ text-shadow: none;
+}
diff --git a/components/global/jar.mn b/components/global/jar.mn
new file mode 100644
index 000000000..b02587ec4
--- /dev/null
+++ b/components/global/jar.mn
@@ -0,0 +1,66 @@
+toolkit.jar:
+% content global %content/global/ contentaccessible=yes
+% content global-platform %content/global-platform/ platform
+ content/global/XPCNativeWrapper.js (content/XPCNativeWrapper.js)
+ content/global/minimal-xul.css (content/minimal-xul.css)
+* content/global/xul.css (content/xul.css)
+ content/global/textbox.css (content/textbox.css)
+ content/global/menulist.css (content/menulist.css)
+ content/global/autocomplete.css (content/autocomplete.css)
+* content/global/about.js (content/about.js)
+ content/global/about.xhtml (content/about.xhtml)
+ content/global/aboutAbout.js (content/aboutAbout.js)
+ content/global/aboutAbout.xhtml (content/aboutAbout.xhtml)
+#ifdef MOZ_PHOENIX
+ content/global/logopage.xhtml (content/logopage.xhtml)
+#endif
+ content/global/aboutNetworking.js (content/aboutNetworking.js)
+ content/global/aboutNetworking.xhtml (content/aboutNetworking.xhtml)
+* content/global/aboutProfiles.js (content/aboutProfiles.js)
+ content/global/aboutProfiles.xhtml (content/aboutProfiles.xhtml)
+ content/global/aboutServiceWorkers.js (content/aboutServiceWorkers.js)
+ content/global/aboutServiceWorkers.xhtml (content/aboutServiceWorkers.xhtml)
+#ifdef MOZ_OFFICIAL_BRANDING
+* content/global/aboutRights.xhtml (content/aboutRights.xhtml)
+#else
+ content/global/aboutRights.xhtml (content/aboutRights-unbranded.xhtml)
+#endif
+* content/global/aboutSupport.js (content/aboutSupport.js)
+* content/global/aboutSupport.xhtml (content/aboutSupport.xhtml)
+ content/global/directionDetector.html (content/directionDetector.html)
+ content/global/plugins.html (content/plugins.html)
+ content/global/plugins.css (content/plugins.css)
+ content/global/browser-child.js (content/browser-child.js)
+ content/global/browser-content.js (content/browser-content.js)
+* content/global/buildconfig.html (content/buildconfig.html)
+* content/global/contentAreaUtils.js (content/contentAreaUtils.js)
+ content/global/customizeToolbar.css (content/customizeToolbar.css)
+ content/global/customizeToolbar.js (content/customizeToolbar.js)
+ content/global/customizeToolbar.xul (content/customizeToolbar.xul)
+ content/global/datepicker.xhtml (content/datepicker.xhtml)
+ content/global/editMenuOverlay.js (content/editMenuOverlay.js)
+* content/global/editMenuOverlay.xul (content/editMenuOverlay.xul)
+ content/global/finddialog.js (content/finddialog.js)
+* content/global/finddialog.xul (content/finddialog.xul)
+ content/global/findUtils.js (content/findUtils.js)
+ content/global/filepicker.properties (content/filepicker.properties)
+ content/global/globalOverlay.js (content/globalOverlay.js)
+* content/global/license.html (content/license.html)
+ content/global/mozilla.xhtml (content/mozilla.xhtml)
+ content/global/process-content.js (content/process-content.js)
+ content/global/resetProfile.css (content/resetProfile.css)
+ content/global/resetProfile.js (content/resetProfile.js)
+ content/global/resetProfile.xul (content/resetProfile.xul)
+ content/global/resetProfileProgress.xul (content/resetProfileProgress.xul)
+ content/global/select-child.js (content/select-child.js)
+ content/global/TopLevelVideoDocument.js (content/TopLevelVideoDocument.js)
+ content/global/timepicker.xhtml (content/timepicker.xhtml)
+ content/global/treeUtils.js (content/treeUtils.js)
+ content/global/viewZoomOverlay.js (content/viewZoomOverlay.js)
+ content/global/globalOverlay.xul (content/globalOverlay.xul)
+ content/global/dialogOverlay.xul (content/dialogOverlay.xul)
+ content/global/dialogOverlay.js (content/dialogOverlay.js)
+ content/global/inlineSpellCheckUI.js (content/inlineSpellCheckUI.js)
+ content/global/nsClipboard.js (content/nsClipboard.js)
+ content/global/nsUserSettings.js (content/nsUserSettings.js)
+ content/global/strres.js (content/strres.js)
diff --git a/components/global/locale/about.dtd b/components/global/locale/about.dtd
new file mode 100644
index 000000000..5de3837ad
--- /dev/null
+++ b/components/global/locale/about.dtd
@@ -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/. -->
+<!ENTITY about.version "Version:">
+
+<!-- LOCALIZATION NOTE (about.credits.beforeLink): note that there is no space between this phrase and the linked about.credits.linkTitle phrase, so if your locale needs a space between words, add it at the end of this entity. -->
+<!ENTITY about.credits.beforeLink "See a list of ">
+<!ENTITY about.credits.linkTitle "contributors">
+<!-- LOCALIZATION NOTE (about.credits.afterLink): note that there is no space between the linked about.credits.linkTitle phrase and this phrase, so if your locale needs a space between words, add it at the start of this entity. -->
+<!ENTITY about.credits.afterLink " to the project.">
+
+<!-- LOCALIZATION NOTE (about.license.beforeTheLink): note that there is no space between this phrase and the linked about.license.linkTitle phrase, so if your locale needs a space between words, add it at the end of this entity. -->
+<!ENTITY about.license.beforeTheLink "Read the ">
+<!ENTITY about.license.linkTitle "licensing information">
+<!-- LOCALIZATION NOTE (about.license.afterTheLink): note that there is no space between the linked about.license.linkTitle phrase and this phrase, so if your locale needs a space between words, add it at the start of this entity. -->
+<!ENTITY about.license.afterTheLink " for this product.">
+
+<!-- LOCALIZATION NOTE (about.relnotes.beforeTheLink): note that there is no space between this phrase and the linked about.relnotes.linkTitle phrase, so if your locale needs a space between words, add it at the end of this entity. -->
+<!ENTITY about.relnotes.beforeTheLink "Read the ">
+<!ENTITY about.relnotes.linkTitle "release notes">
+<!-- LOCALIZATION NOTE (about.relnotes.afterTheLink): note that there is no space between the linked about.relnotes.linkTitle phrase and this phrase, so if your locale needs a space between words, add it at the start of this entity. -->
+<!ENTITY about.relnotes.afterTheLink " for this version.">
+
+<!-- LOCALIZATION NOTE (about.buildconfig.beforeTheLink): note that there is no space between this phrase and the linked about.buildconfig.linkTitle phrase, so if your locale needs a space between words, add it at the end of this entity. -->
+<!ENTITY about.buildconfig.beforeTheLink "See the ">
+<!ENTITY about.buildconfig.linkTitle "build configuration">
+<!-- LOCALIZATION NOTE (about.buildconfig.afterTheLink): note that there is no space between the linked about.buildconfig.linkTitle phrase and this phrase, so if your locale needs a space between words, add it at the start of this entity. -->
+<!ENTITY about.buildconfig.afterTheLink " used for this version.">
+
+<!ENTITY about.buildIdentifier "Build identifier:">
+<!ENTITY about.userAgent "User-agent:">
diff --git a/components/global/locale/aboutAbout.dtd b/components/global/locale/aboutAbout.dtd
new file mode 100644
index 000000000..7d858fe31
--- /dev/null
+++ b/components/global/locale/aboutAbout.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 aboutAbout.title "About About">
+<!ENTITY aboutAbout.note "This is a list of “about†pages for your convenience.<br/>
+ Some of them might be confusing. Some are for diagnostic purposes only.<br/>
+ And some are omitted because they require query strings.">
diff --git a/components/global/locale/aboutNetworking.dtd b/components/global/locale/aboutNetworking.dtd
new file mode 100644
index 000000000..4069e21c0
--- /dev/null
+++ b/components/global/locale/aboutNetworking.dtd
@@ -0,0 +1,43 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY aboutNetworking.title "About Networking">
+<!ENTITY aboutNetworking.warning "This is very experimental. Do not use without adult supervision.">
+<!ENTITY aboutNetworking.showNextTime "Show this warning next time">
+<!ENTITY aboutNetworking.ok "OK">
+<!ENTITY aboutNetworking.HTTP "HTTP">
+<!ENTITY aboutNetworking.sockets "Sockets">
+<!ENTITY aboutNetworking.dns "DNS">
+<!ENTITY aboutNetworking.websockets "WebSockets">
+<!ENTITY aboutNetworking.refresh "Refresh">
+<!ENTITY aboutNetworking.autoRefresh "Autorefresh every 3 seconds">
+<!ENTITY aboutNetworking.hostname "Hostname">
+<!ENTITY aboutNetworking.port "Port">
+<!ENTITY aboutNetworking.spdy "SPDY">
+<!ENTITY aboutNetworking.ssl "SSL">
+<!ENTITY aboutNetworking.active "Active">
+<!ENTITY aboutNetworking.idle "Idle">
+<!ENTITY aboutNetworking.host "Host">
+<!ENTITY aboutNetworking.tcp "TCP">
+<!ENTITY aboutNetworking.sent "Sent">
+<!ENTITY aboutNetworking.received "Received">
+<!ENTITY aboutNetworking.family "Family">
+<!ENTITY aboutNetworking.addresses "Addresses">
+<!ENTITY aboutNetworking.expires "Expires (Seconds)">
+<!ENTITY aboutNetworking.messagesSent "Messages Sent">
+<!ENTITY aboutNetworking.messagesReceived "Messages Received">
+<!ENTITY aboutNetworking.bytesSent "Bytes Sent">
+<!ENTITY aboutNetworking.bytesReceived "Bytes Received">
+<!ENTITY aboutNetworking.logging "Logging">
+<!ENTITY aboutNetworking.logTutorial "See <a href='https://developer.mozilla.org/docs/Mozilla/Debugging/HTTP_logging'>HTTP Logging</a> for instructions on how to use this tool.">
+<!ENTITY aboutNetworking.currentLogFile "Current Log File:">
+<!ENTITY aboutNetworking.currentLogModules "Current Log Modules:">
+<!ENTITY aboutNetworking.setLogFile "Set Log File">
+<!ENTITY aboutNetworking.setLogModules "Set Log Modules">
+<!ENTITY aboutNetworking.startLogging "Start Logging">
+<!ENTITY aboutNetworking.stopLogging "Stop Logging">
+<!ENTITY aboutNetworking.dnsLookup "DNS Lookup">
+<!ENTITY aboutNetworking.dnsLookupButton "Resolve">
+<!ENTITY aboutNetworking.dnsDomain "Domain">
+<!ENTITY aboutNetworking.dnsLookupTableColumn "IPs">
diff --git a/components/global/locale/aboutProfiles.dtd b/components/global/locale/aboutProfiles.dtd
new file mode 100644
index 000000000..5091517b2
--- /dev/null
+++ b/components/global/locale/aboutProfiles.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 aboutProfiles.title "About Profiles">
+<!ENTITY aboutProfiles.subtitle "This page helps you to manage your profiles. Each profile is a separate world which contains separate history, bookmarks, settings and add-ons.">
+<!ENTITY aboutProfiles.create "Create a New Profile">
+<!ENTITY aboutProfiles.restart.title "Restart">
+<!ENTITY aboutProfiles.restart.inSafeMode "Restart in Safe Mode…">
+<!ENTITY aboutProfiles.restart.normal "Restart normally…">
diff --git a/components/global/locale/aboutProfiles.properties b/components/global/locale/aboutProfiles.properties
new file mode 100644
index 000000000..afe141766
--- /dev/null
+++ b/components/global/locale/aboutProfiles.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/.
+
+name = Profile: %S
+isDefault = Default Profile
+rootDir = Root Directory
+# LOCALIZATION NOTE: localDir is used to show the directory corresponding to
+# the main profile directory that exists for the purpose of storing data on the
+# local filesystem, including cache files or other data files that may not
+# represent critical user data. (e.g., this directory may not be included as
+# part of a backup scheme.)
+# In case localDIr and rootDir are equal, localDir is not shown.
+localDir = Local Directory
+currentProfile = This is the profile in use and it cannot be deleted.
+
+rename = Rename
+remove = Remove
+setAsDefault = Set as default profile
+launchProfile = Launch profile in new browser
+
+yes = yes
+no = no
+
+renameProfileTitle = Rename Profile
+renameProfile = Rename profile %S
+
+invalidProfileNameTitle = Invalid profile name
+invalidProfileName = The profile name “%S†is not allowed.
+
+deleteProfileTitle = Delete Profile
+deleteProfileConfirm = Deleting a profile will remove the profile from the list of available profiles and cannot be undone.\nYou may also choose to delete the profile data files, including your settings, certificates and other user-related data. This option will delete the folder “%S†and cannot be undone.\nWould you like to delete the profile data files?
+deleteFiles = Delete Files
+dontDeleteFiles = Don’t Delete Files
+
+openDir = Open Directory
+# LOCALIZATION NOTE (macOpenDir): This is the Mac-specific variant of openDir.
+# This allows us to use the preferred"Finder" terminology on Mac.
+macOpenDir = Show in Finder
+# LOCALIZATION NOTE (winOpenDir2): This is the Windows-specific variant of
+# openDir.
+winOpenDir2 = Open Folder
diff --git a/components/global/locale/aboutReader.properties b/components/global/locale/aboutReader.properties
new file mode 100644
index 000000000..10d145686
--- /dev/null
+++ b/components/global/locale/aboutReader.properties
@@ -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/.
+
+#LOCALIZATION NOTE (aboutReader.loading2):
+# Use the unicode ellipsis char, \u2026,
+# or use "..." if \u2026 doesn't suit traditions in your locale.
+aboutReader.loading2=Loading…
+aboutReader.loadError=Failed to load article from page
+
+aboutReader.colorScheme.light=Light
+aboutReader.colorScheme.dark=Dark
+aboutReader.colorScheme.sepia=Sepia
+aboutReader.colorScheme.auto=Auto
+
+# LOCALIZATION NOTE (aboutReader.estimatedReadTimeValue1): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of minutes it is estimated to take to read the article
+# example: `3 minutes`
+aboutReader.estimatedReadTimeValue1=#1 minute;#1 minutes
+
+#LOCALIZATION NOTE (aboutReader.estimatedReadingTimeRange1): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# When there is some uncertainty in how long the article will take to read show a range of
+# minutes it is expected to take.
+# #1 is the number of minutes it is estimated to take to read the article for a fast reader
+# #2 is the number of minutes it is estimated to take to read the article for a slow reader
+# #2 is the variable used to determine the plural form to use.
+# example: `5-8 minutes`
+aboutReader.estimatedReadTimeRange1=#1-#2 minute;#1-#2 minutes
+
+# LOCALIZATION NOTE (aboutReader.fontType.serif, aboutReader.fontType.sans-serif):
+# These are the styles of typeface that are options in the reader view controls.
+aboutReader.fontType.serif=Serif
+aboutReader.fontType.sans-serif=Sans-serif
+
+# LOCALIZATION NOTE (aboutReader.fontTypeSample): String used to sample font types.
+aboutReader.fontTypeSample=Aa
+
+aboutReader.toolbar.close=Close Reader View
+aboutReader.toolbar.typeControls=Type controls
+
+# These are used for the Reader View toolbar button and the menuitem within the
+# View menu.
+readerView.enter=Enter Reader View
+readerView.enter.accesskey=R
+readerView.close=Close Reader View
+readerView.close.accesskey=R
+
+# These are used as tooltips in Type Control
+aboutReader.toolbar.minus = Decrease Font Size
+aboutReader.toolbar.plus = Increase Font Size
+aboutReader.toolbar.contentwidthminus = Decrease Content Width
+aboutReader.toolbar.contentwidthplus = Increase Content Width
+aboutReader.toolbar.lineheightminus = Decrease Line Height
+aboutReader.toolbar.lineheightplus = Increase Line Height
+aboutReader.toolbar.colorschemelight = Color Scheme Light
+aboutReader.toolbar.colorschemedark = Color Scheme Dark
+aboutReader.toolbar.colorschemesepia = Color Scheme Sepia
diff --git a/components/global/locale/aboutRights.dtd b/components/global/locale/aboutRights.dtd
new file mode 100644
index 000000000..eea75bd49
--- /dev/null
+++ b/components/global/locale/aboutRights.dtd
@@ -0,0 +1,84 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!-- rights.locale-direction instead of the usual local.dir entity, so RTL can skip translating page. -->
+<!ENTITY rights.locale-direction "ltr">
+<!ENTITY rights.pagetitle "about:rights">
+<!ENTITY rights.intro-header "About Your Rights">
+<!ENTITY rights.intro "&brandFullName; is free and open source software, built by a community of thousands from all over the world. There are a few things you should know:">
+
+<!-- Note on pointa / pointb / pointc form:
+ These points each have an embedded link in the HTML, so each point is
+ split into chunks for text before the link, the link text, and the text
+ after the link. If a localized grammar doesn't need the before or after
+ chunk, it can be left blank.
+
+ Also note the leading/trailing whitespace in strings here, which is
+ deliberate for formatting around the embedded links. -->
+<!ENTITY rights.intro-point1a "&brandShortName; is made available to you under the terms of the ">
+<!ENTITY rights.intro-point1b "Mozilla Public License">
+<!ENTITY rights.intro-point1c ". This means you may use, copy and distribute &brandShortName; to others. You are also welcome to modify the source code of &brandShortName; as you want to meet your needs. The Mozilla Public License also gives you the right to distribute your modified versions.">
+
+<!ENTITY rights.intro-point2-a "You are not granted any trademark rights or licenses to the trademarks of Moonchild Productions or any other party, including without limitation the Basilisk and Pale Moon names or logos. Additional information on trademarks may be found ">
+<!ENTITY rights.intro-point2-b "here">
+<!ENTITY rights.intro-point2-c ".">
+
+<!-- point 2.5 text for official branded builds -->
+<!ENTITY rights.intro-point2.5 "Some features in &brandShortName; give you the option to provide feedback to the publishers of this software. By choosing to submit feedback, you give the publishers in question permission to use the feedback to improve their products, to publish the feedback on their websites, and to distribute the feedback.">
+
+<!-- point 3 text for official branded builds -->
+<!ENTITY rights2.intro-point3a "How we use your personal information and feedback submitted to &vendorShortName; through &brandShortName; is described in the ">
+<!ENTITY rights2.intro-point3b "&brandShortName; Privacy Policy">
+<!ENTITY rights.intro-point3c ".">
+
+<!-- point 3 text for unbranded builds -->
+<!ENTITY rights.intro-point3-unbranded "Any applicable privacy policies for this product should be listed here.">
+
+<!-- point 4 text for official branded builds -->
+<!ENTITY rights2.intro-point4a "Some &brandShortName; features make use of web-based information services, however, we cannot guarantee they are 100&#37; accurate or error-free. More details, including information on how to disable the features that use these services, can be found in the ">
+<!ENTITY rights.intro-point4b "service terms">
+<!ENTITY rights.intro-point4c ".">
+
+<!-- point 4 text for unbranded builds -->
+<!ENTITY rights.intro-point4a-unbranded "If this product incorporates web services, any applicable service terms for the service(s) should be linked to the ">
+<!ENTITY rights.intro-point4b-unbranded "Website Services">
+<!ENTITY rights.intro-point4c-unbranded " section.">
+
+<!ENTITY rights2.webservices-header "&brandFullName; Web-Based Information Services">
+
+<!-- point 5 -->
+<!ENTITY rights.intro-point5 "In order to play back certain types of video content, &brandShortName; may download certain content decryption modules from third parties, if playback of DRM content is enabled or available.">
+
+<!-- Note that this paragraph references a couple of entities from
+ preferences/security.dtd, so that we can refer to text the user sees in
+ the UI, without this page being forgotten every time those strings are
+ updated. -->
+<!-- intro paragraph for branded builds -->
+<!ENTITY rights2.webservices-a "&brandFullName; uses web-based information services (&quot;Services&quot;) to provide some of the features provided for your use with this binary version of &brandShortName; under the terms described below. If you do not want to use one or more of the Services or the terms below are unacceptable, you may disable the feature or Service(s). Instructions on how to disable a particular feature or Service may be found ">
+<!ENTITY rights2.webservices-b "here">
+<!ENTITY rights3.webservices-c ". Other features and Services can be disabled in the application preferences.">
+
+<!-- location aware browsing points for branded builds -->
+<!-- Official builds use IP-API.com -->
+<!ENTITY rights.locationawarebrowsing-a "Location Aware Browsing: ">
+<!ENTITY rights.locationawarebrowsing-b "is always opt-in. No location information is ever sent without your permission. If you wish to disable the feature completely, follow these steps:">
+<!ENTITY rights.locationawarebrowsing-term1a "In the URL bar, type ">
+<!ENTITY rights.locationawarebrowsing-term1b "about:config">
+<!ENTITY rights.locationawarebrowsing-term2 "Type geo.enabled">
+<!ENTITY rights.locationawarebrowsing-term3 "Double click on the geo.enabled preference">
+<!ENTITY rights.locationawarebrowsing-term4 "Location-Aware Browsing is now disabled">
+
+<!-- intro paragraph for unbranded builds -->
+<!ENTITY rights.webservices-unbranded "An overview of the website services the product incorporates, along with instructions on how to disable them, if applicable, should be included here.">
+
+<!-- point 1 text for unbranded builds -->
+<!ENTITY rights.webservices-term1-unbranded "Any applicable service terms for this product should be listed here.">
+
+<!-- points 1-7 text for branded builds -->
+<!ENTITY rights2.webservices-term1 "&vendorShortName; and its contributors, licensors and partners work to provide the most accurate and up-to-date Services. However, we cannot guarantee that this information is comprehensive and error-free. For example, for the Location Aware Service all locations returned by our service provider are estimates only and neither we nor our service provider guarantee the accuracy of the locations provided.">
+<!ENTITY rights.webservices-term2 "&vendorShortName; may discontinue or change the Services at its discretion.">
+<!ENTITY rights2.webservices-term3 "You are welcome to use these Services with the accompanying version of &brandShortName;, and &vendorShortName; grants you its rights to do so. &vendorShortName; and its licensors reserve all other rights in the Services. These terms are not intended to limit any rights granted under open source licenses applicable to &brandShortName; and to corresponding source code versions of &brandShortName;, however these (optional) services are provided as a convenience to you, and in no way extend your software rights to the Services.">
+<!ENTITY rights.webservices-term4 "The Services are provided &quot;as-is&quot; and &quot;as-available&quot;. &vendorShortName;, its contributors, licensors and distributors disclaim all warranties, whether express or implied, including without limitation warranties that the Services are merchantable and fit for your particular purposes. You bear the entire risk as to selecting the Services for your purposes and as to the quality and performance of the Services. If your jurisdiction does not allow disclaiming of warranties, then you should not use &brandShortName; or Services.">
+<!ENTITY rights.webservices-term5 "Except as required by law, &vendorShortName;, its contributors, licensors, and distributors will not be liable for any indirect, special, incidental, consequential, punitive, or exemplary damages arising out of or in any way relating to the use of &brandShortName; and the Services. The collective liability under these terms will not exceed $500 (five hundred dollars). If your jurisdiction does not allow the exclusion or limitation of damages, then you should not use &brandShortName; or Services.">
+<!ENTITY rights.webservices-term6 "&vendorShortName; may update these terms as necessary from time to time. These terms may not be modified or canceled without &vendorShortName;'s written agreement.">
+<!ENTITY rights.webservices-term7 "These terms are governed by the laws of Sweden, excluding any conflict of law provisions. If any portion of these terms is held to be invalid or unenforceable, the remaining portions will remain in full force and effect. In the event of a conflict between a translated version of these terms and the English language version, the English language version shall take precedence.">
diff --git a/components/global/locale/aboutServiceWorkers.dtd b/components/global/locale/aboutServiceWorkers.dtd
new file mode 100644
index 000000000..d7d0298dd
--- /dev/null
+++ b/components/global/locale/aboutServiceWorkers.dtd
@@ -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/. -->
+
+<!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
+<!ENTITY aboutServiceWorkers.title "About Service Workers">
+<!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
+<!ENTITY aboutServiceWorkers.maintitle "Registered Service Workers">
+<!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
+<!ENTITY aboutServiceWorkers.warning_not_enabled "Service Workers are not enabled.">
+<!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
+<!ENTITY aboutServiceWorkers.warning_no_serviceworkers "No Service Workers registered.">
diff --git a/components/global/locale/aboutServiceWorkers.properties b/components/global/locale/aboutServiceWorkers.properties
new file mode 100644
index 000000000..34609f75b
--- /dev/null
+++ b/components/global/locale/aboutServiceWorkers.properties
@@ -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/.
+
+title = Origin: %S
+
+# LOCALIZATION NOTE: %1$S is brandShortName, %2$2 is the application ID, and $%$3 is true/false value.
+# LOCALIZATION NOTE: the term "InBrowserElement" should not be translated
+b2gtitle = %1$S Application ID %2$S - InBrowserElement %3$S
+
+scope = Scope:
+
+scriptSpec = Script Spec:
+
+# LOCALIZATION NOTE: the term "Worker" should not be translated.
+currentWorkerURL = Current Worker URL:
+
+activeCacheName = Active Cache Name:
+
+waitingCacheName = Waiting Cache Name:
+
+true = true
+
+false = false
+
+# LOCALIZATION NOTE this term is used as a button label (verb, not noun).
+update = Update
+
+unregister = Unregister
+
+waiting = Waiting…
+
+# LOCALIZATION NOTE: the term "Service Worker" should not translated.
+unregisterError = Failed to unregister this Service Worker.
+
+pushEndpoint = Push Endpoint:
diff --git a/components/global/locale/aboutSupport.dtd b/components/global/locale/aboutSupport.dtd
new file mode 100644
index 000000000..b4da6bc77
--- /dev/null
+++ b/components/global/locale/aboutSupport.dtd
@@ -0,0 +1,136 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY aboutSupport.pageTitle "Troubleshooting Information">
+
+<!-- LOCALIZATION NOTE (aboutSupport.pageSubtitle): don't change the 'supportLink' id. -->
+<!ENTITY aboutSupport.pageSubtitle "
+ This page contains technical information that might be useful when you’re
+ trying to solve a problem. If you are looking for answers to common questions
+ about &brandShortName;, check out our <a id='supportLink'>support website</a>.
+">
+
+<!ENTITY aboutSupport.crashes.title "Crash Reports">
+<!-- LOCALIZATION NOTE (aboutSupport.crashes.id):
+This is likely the same like id.heading in crashes.dtd. -->
+<!ENTITY aboutSupport.crashes.id "Report ID">
+<!ENTITY aboutSupport.crashes.sendDate "Submitted">
+<!ENTITY aboutSupport.crashes.allReports "All Crash Reports">
+<!ENTITY aboutSupport.crashes.noConfig "This application has not been configured to display crash reports.">
+
+<!ENTITY aboutSupport.extensionsTitle "Extensions">
+<!ENTITY aboutSupport.extensionName "Name">
+<!ENTITY aboutSupport.extensionEnabled "Enabled">
+<!ENTITY aboutSupport.extensionVersion "Version">
+<!ENTITY aboutSupport.extensionId "ID">
+
+<!ENTITY aboutSupport.experimentsTitle "Experimental Features">
+<!ENTITY aboutSupport.experimentName "Name">
+<!ENTITY aboutSupport.experimentId "ID">
+<!ENTITY aboutSupport.experimentDescription "Description">
+<!ENTITY aboutSupport.experimentActive "Active">
+<!ENTITY aboutSupport.experimentEndDate "End Date">
+<!ENTITY aboutSupport.experimentHomepage "Homepage">
+<!ENTITY aboutSupport.experimentBranch "Branch">
+
+<!ENTITY aboutSupport.appBasicsTitle "Application Basics">
+<!ENTITY aboutSupport.appBasicsName "Name">
+<!ENTITY aboutSupport.appBasicsVersion "Version">
+<!ENTITY aboutSupport.appBasicsBuildID "Build ID">
+
+<!-- LOCALIZATION NOTE (aboutSupport.appBasicsUpdateChannel, aboutSupport.appBasicsUpdateHistory, aboutSupport.appBasicsShowUpdateHistory):
+"Update" is a noun here, not a verb. -->
+<!ENTITY aboutSupport.appBasicsUpdateChannel "Update Channel">
+<!ENTITY aboutSupport.appBasicsUpdateHistory "Update History">
+<!ENTITY aboutSupport.appBasicsShowUpdateHistory "Show Update History">
+
+<!ENTITY aboutSupport.appBasicsBinary "Application Binary">
+<!ENTITY aboutSupport.appBasicsProfileDir "Profile Directory">
+<!-- LOCALIZATION NOTE (aboutSupport.appBasicsProfileDirWinMac):
+This is the Windows- and Mac-specific variant of aboutSupport.appBasicsProfileDir.
+Windows/Mac use the term "Folder" instead of "Directory" -->
+<!ENTITY aboutSupport.appBasicsProfileDirWinMac "Profile Folder">
+
+<!ENTITY aboutSupport.appBasicsEnabledPlugins "Enabled Plugins">
+<!ENTITY aboutSupport.appBasicsBuildConfig "Build Configuration">
+<!ENTITY aboutSupport.appBasicsUserAgent "User Agent">
+<!ENTITY aboutSupport.appBasicsOS "OS">
+<!ENTITY aboutSupport.appBasicsMemoryUse "Memory Use">
+<!ENTITY aboutSupport.appBasicsPerformance "Performance">
+
+<!-- LOCALIZATION NOTE the term "Service Workers" should not be translated. -->
+<!ENTITY aboutSupport.appBasicsServiceWorkers "Registered Service Workers">
+
+<!ENTITY aboutSupport.appBasicsProfiles "Profiles">
+
+<!ENTITY aboutSupport.appBasicsSafeMode "Safe Mode">
+
+<!ENTITY aboutSupport.showDir.label "Open Directory">
+<!-- LOCALIZATION NOTE (aboutSupport.showMac.label): This is the Mac-specific
+variant of aboutSupport.showDir.label. This allows us to use the preferred
+"Finder" terminology on Mac. -->
+<!ENTITY aboutSupport.showMac.label "Show in Finder">
+<!-- LOCALIZATION NOTE (aboutSupport.showWin2.label): This is the Windows-specific
+variant of aboutSupport.showDir.label. -->
+<!ENTITY aboutSupport.showWin2.label "Open Folder">
+
+<!ENTITY aboutSupport.modifiedKeyPrefsTitle "Important Modified Preferences">
+<!ENTITY aboutSupport.modifiedPrefsName "Name">
+<!ENTITY aboutSupport.modifiedPrefsValue "Value">
+
+<!-- LOCALIZATION NOTE (aboutSupport.userJSTitle, aboutSupport.userJSDescription): user.js is the name of the preference override file being checked. -->
+<!ENTITY aboutSupport.userJSTitle "user.js Preferences">
+<!ENTITY aboutSupport.userJSDescription "Your profile folder contains a <a id='prefs-user-js-link'>user.js file</a>, which includes preferences that were not created by &brandShortName;.">
+
+<!ENTITY aboutSupport.lockedKeyPrefsTitle "Important Locked Preferences">
+<!ENTITY aboutSupport.lockedPrefsName "Name">
+<!ENTITY aboutSupport.lockedPrefsValue "Value">
+
+<!ENTITY aboutSupport.graphicsTitle "Graphics">
+
+<!ENTITY aboutSupport.placeDatabaseTitle "Places Database">
+<!ENTITY aboutSupport.placeDatabaseIntegrity "Integrity">
+<!ENTITY aboutSupport.placeDatabaseVerifyIntegrity "Verify Integrity">
+
+<!ENTITY aboutSupport.jsTitle "JavaScript">
+<!ENTITY aboutSupport.jsIncrementalGC "Incremental GC">
+
+<!ENTITY aboutSupport.a11yTitle "Accessibility">
+<!ENTITY aboutSupport.a11yActivated "Activated">
+<!ENTITY aboutSupport.a11yForceDisabled "Prevent Accessibility">
+
+<!ENTITY aboutSupport.libraryVersionsTitle "Library Versions">
+
+<!ENTITY aboutSupport.installationHistoryTitle "Installation History">
+<!ENTITY aboutSupport.updateHistoryTitle "Update History">
+
+<!ENTITY aboutSupport.copyTextToClipboard.label "Copy text to clipboard">
+
+<!ENTITY aboutSupport.safeModeTitle "Try Safe Mode">
+<!ENTITY aboutSupport.restartInSafeMode.label "Restart in Safe Mode…">
+
+<!ENTITY aboutSupport.restartTitle "Try Restart">
+<!ENTITY aboutSupport.restartNormal.label "Restart normally…">
+
+<!ENTITY aboutSupport.graphicsFeaturesTitle "Features">
+<!ENTITY aboutSupport.graphicsDiagnosticsTitle "Diagnostics">
+<!ENTITY aboutSupport.graphicsFailureLogTitle "Failure Log">
+<!ENTITY aboutSupport.graphicsGPU1Title "GPU #1">
+<!ENTITY aboutSupport.graphicsGPU2Title "GPU #2">
+<!ENTITY aboutSupport.graphicsDecisionLogTitle "Decision Log">
+<!ENTITY aboutSupport.graphicsCrashGuardsTitle "Crash Guard Disabled Features">
+<!ENTITY aboutSupport.graphicsWorkaroundsTitle "Workarounds">
+
+<!ENTITY aboutSupport.mediaTitle "Media">
+<!ENTITY aboutSupport.mediaOutputDevicesTitle "Output Devices">
+<!ENTITY aboutSupport.mediaInputDevicesTitle "Input Devices">
+<!ENTITY aboutSupport.mediaDeviceName "Name">
+<!ENTITY aboutSupport.mediaDeviceGroup "Group">
+<!ENTITY aboutSupport.mediaDeviceVendor "Vendor">
+<!ENTITY aboutSupport.mediaDeviceState "State">
+<!ENTITY aboutSupport.mediaDevicePreferred "Preferred">
+<!ENTITY aboutSupport.mediaDeviceFormat "Format">
+<!ENTITY aboutSupport.mediaDeviceChannels "Channels">
+<!ENTITY aboutSupport.mediaDeviceRate "Rate">
+<!ENTITY aboutSupport.mediaDeviceLatency "Latency">
diff --git a/components/global/locale/aboutSupport.properties b/components/global/locale/aboutSupport.properties
new file mode 100644
index 000000000..751a3f096
--- /dev/null
+++ b/components/global/locale/aboutSupport.properties
@@ -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/.
+
+# LOCALIZATION NOTE (downloadsTitleFiles): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 number of relevant days with crash reports
+crashesTitle=Crash Reports for the Last #1 Day;Crash Reports for the Last #1 Days
+
+# LOCALIZATION NOTE (crashesTimeMinutes): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 number of minutes (between 1 and 59) which have passed since the crash
+crashesTimeMinutes=#1 minute ago;#1 minutes ago
+
+# LOCALIZATION NOTE (crashesTimeHours): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 number of hours (between 1 and 23) which have passed since the crash
+crashesTimeHours=#1 hour ago;#1 hours ago
+
+# LOCALIZATION NOTE (crashesTimeDays): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 number of days (1 or more) which have passed since the crash
+crashesTimeDays=#1 day ago;#1 days ago
+
+# LOCALIZATION NOTE (downloadsTitleFiles): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 number of pending crash reports
+pendingReports=All Crash Reports (including #1 pending crash in the given time range);All Crash Reports (including #1 pending crashes in the given time range)
+
+# LOCALIZATION NOTE: This can be localized with a more generic term, like
+# "Graphics-accelerated Windows". It describes a number of windows, e.g.:
+# "GPU Accelerated Windows: 2/2 (Direct3D 9)"
+# "GPU Accelerated Windows: 0/2"
+acceleratedWindows = GPU Accelerated Windows
+
+# LOCALIZATION NOTE (textCopied) Text displayed in a mobile "Toast" to user when the
+# text is successfully copied to the clipboard via button press.
+textCopied=Text copied to clipboard
+
+# LOCALIZATION NOTE The verb "blocked" here refers to a graphics feature such as "Direct2D" or "OpenGL layers".
+blockedDriver = Blocked for your graphics driver version.
+
+# LOCALIZATION NOTE The %S here is a placeholder, leave unchanged, it will get replaced by the driver version string.
+tryNewerDriver = Blocked for your graphics driver version. Try updating your graphics driver to version %S or newer.
+
+# LOCALIZATION NOTE The verb "blocked" here refers to a graphics feature such as "Direct2D" or "OpenGL layers".
+blockedGfxCard = Blocked for your graphics card because of unresolved driver issues.
+
+# LOCALIZATION NOTE The verb "blocked" here refers to a graphics feature such as "Direct2D" or "OpenGL layers".
+blockedOSVersion = Blocked for your operating system version.
+
+# LOCALIZATION NOTE The verb "blocked" here refers to a graphics feature such as "Direct2D" or "OpenGL layers".
+blockedMismatchedVersion = Blocked for your graphics driver version mismatch between registry and DLL.
+
+# LOCALIZATION NOTE In the following strings, "Direct2D", "DirectWrite" and "ClearType"
+# are proper nouns and should not be translated. Feel free to leave english strings if
+# there are no good translations, these are only used in about:support
+clearTypeParameters = ClearType Parameters
+
+compositing = Compositing
+hardwareH264 = Hardware H264 Decoding
+mainThreadNoOMTC = main thread, no OMTC
+yes = Yes
+no = No
+
+gpuDescription = Description
+gpuVendorID = Vendor ID
+gpuDeviceID = Device ID
+gpuSubsysID = Subsys ID
+gpuDrivers = Drivers
+gpuRAM = RAM
+gpuDriverVersion = Driver Version
+gpuDriverDate = Driver Date
+gpuActive = Active
+webgl1WSIInfo = WebGL 1 Driver WSI Info
+webgl1Renderer = WebGL 1 Driver Renderer
+webgl1Version = WebGL 1 Driver Version
+webgl1DriverExtensions = WebGL 1 Driver Extensions
+webgl1Extensions = WebGL 1 Extensions
+webgl2WSIInfo = WebGL 2 Driver WSI Info
+webgl2Renderer = WebGL 2 Driver Renderer
+webgl2Version = WebGL 2 Driver Version
+webgl2DriverExtensions = WebGL 2 Driver Extensions
+webgl2Extensions = WebGL 2 Extensions
+GPU1 = GPU #1
+GPU2 = GPU #2
+blocklistedBug = Blocklisted due to known issues
+# LOCALIZATION NOTE %1$S will be replaced with a bug number string.
+bugLink = bug %1$S
+# LOCALIZATION NOTE %1$S will be replaced with an arbitrary identifier
+# string that can be searched on DXR/MXR or grepped in the source tree.
+unknownFailure = Blocklisted; failure code %1$S
+d3d11layersCrashGuard = D3D11 Compositor
+d3d11videoCrashGuard = D3D11 Video Decoder
+d3d9videoCrashGuard = D3D9 Video Decoder
+glcontextCrashGuard = OpenGL
+resetOnNextRestart = Reset on Next Restart
+gpuProcessKillButton = Terminate GPU Process
+
+audioBackend = Audio Backend
+maxAudioChannels = Max Channels
+channelLayout = Preferred Channel Layout
+sampleRate = Preferred Sample Rate
+
+minLibVersions = Expected minimum version
+loadedLibVersions = Version in use
+
+asyncPanZoom = Asynchronous Pan/Zoom
+apzNone = none
+wheelEnabled = wheel input enabled
+touchEnabled = touch input enabled
+dragEnabled = scrollbar drag enabled
+
+# LOCALIZATION NOTE %1 will be replaced with the key of a preference.
+wheelWarning = async wheel input disabled due to unsupported pref: %S
+touchWarning = async touch input disabled due to unsupported pref: %S
diff --git a/components/global/locale/aboutTelemetry.dtd b/components/global/locale/aboutTelemetry.dtd
new file mode 100644
index 000000000..4a08de8ca
--- /dev/null
+++ b/components/global/locale/aboutTelemetry.dtd
@@ -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/. -->
+
+<!ENTITY aboutTelemetry.pageTitle "Telemetry Data">
+
+<!ENTITY aboutTelemetry.changeDataChoices "
+ Change
+">
+
+<!ENTITY aboutTelemetry.uploadEnabled "
+ FHR data upload is <span>enabled</span>.
+">
+
+<!ENTITY aboutTelemetry.uploadDisabled "
+ FHR data upload is <span>disabled</span>.
+">
+
+<!ENTITY aboutTelemetry.extendedRecordingEnabled "
+ Extended Telemetry recording is <span>enabled</span>.
+">
+
+<!ENTITY aboutTelemetry.extendedRecordingDisabled "
+ Extended Telemetry recording is <span>disabled</span>.
+">
+
+<!ENTITY aboutTelemetry.pingDataSource "
+Ping data source:
+">
+
+<!ENTITY aboutTelemetry.showCurrentPingData "
+Current ping data
+">
+
+<!ENTITY aboutTelemetry.showArchivedPingData "
+Archived ping data
+">
+
+<!ENTITY aboutTelemetry.pingDataDisplay "
+Ping data display:
+">
+
+<!ENTITY aboutTelemetry.structured "
+Structured
+">
+
+<!ENTITY aboutTelemetry.raw "
+Raw JSON
+">
+
+<!ENTITY aboutTelemetry.showSubsessionData "
+Show subsession data
+">
+
+<!ENTITY aboutTelemetry.choosePing "
+Choose ping:
+">
+
+<!ENTITY aboutTelemetry.showNewerPing "
+&lt;&lt; Newer ping
+">
+
+<!ENTITY aboutTelemetry.showOlderPing "
+Older ping &gt;&gt;
+">
+
+<!ENTITY aboutTelemetry.rawPingData "
+Raw ping data…
+">
+
+<!ENTITY aboutTelemetry.archiveWeekHeader "
+Week
+">
+
+<!ENTITY aboutTelemetry.archivePingHeader "
+Ping
+">
+
+<!ENTITY aboutTelemetry.generalDataSection "
+ General Data
+">
+
+<!ENTITY aboutTelemetry.environmentDataSection "
+ Environment Data
+">
+
+<!ENTITY aboutTelemetry.telemetryLogSection "
+ Telemetry Log
+">
+
+<!ENTITY aboutTelemetry.slowSqlSection "
+ Slow SQL Statements
+">
+
+<!ENTITY aboutTelemetry.chromeHangsSection "
+ Browser Hangs
+">
+
+<!ENTITY aboutTelemetry.threadHangStatsSection "
+ Thread Hangs
+">
+
+<!ENTITY aboutTelemetry.scalarsSection "
+ Scalars
+">
+
+<!ENTITY aboutTelemetry.keyedScalarsSection "
+ Keyed Scalars
+">
+
+<!ENTITY aboutTelemetry.histogramsSection "
+ Histograms
+">
+
+<!ENTITY aboutTelemetry.keyedHistogramsSection "
+ Keyed Histograms
+">
+
+<!ENTITY aboutTelemetry.simpleMeasurementsSection "
+ Simple Measurements
+">
+
+<!ENTITY aboutTelemetry.addonDetailsSection "
+ Add-on Details
+">
+
+<!ENTITY aboutTelemetry.lateWritesSection "
+ Late Writes
+">
+
+<!ENTITY aboutTelemetry.sessionInfoSection "
+ Session Information
+">
+
+<!ENTITY aboutTelemetry.addonHistogramsSection "
+ Histograms Collected by Add-ons
+">
+
+<!ENTITY aboutTelemetry.toggle "
+ Click to toggle section
+">
+
+<!ENTITY aboutTelemetry.emptySection "
+ (No data collected)
+">
+
+<!ENTITY aboutTelemetry.fullSqlWarning "
+ NOTE: Slow SQL debugging is enabled. Full SQL strings may be displayed below but they will not be submitted to Telemetry.
+">
+
+<!ENTITY aboutTelemetry.fetchSymbols "
+ Fetch function names for hang stacks
+">
+
+<!ENTITY aboutTelemetry.hideSymbols "
+ Show raw data from hangs
+">
+
+<!ENTITY aboutTelemetry.filterText "
+ Filter (strings or /regexp/)
+">
+
+<!ENTITY aboutTelemetry.payloadChoiceHeader "
+ Payload
+">
+
+<!ENTITY aboutTelemetry.rawPayload "
+ Raw Payload
+"> \ No newline at end of file
diff --git a/components/global/locale/aboutTelemetry.properties b/components/global/locale/aboutTelemetry.properties
new file mode 100644
index 000000000..c649c8082
--- /dev/null
+++ b/components/global/locale/aboutTelemetry.properties
@@ -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/.
+
+# Note to translators:
+# - %1$S will be replaced by brandFullName
+# - %2$S will be replaced with the value of the toolkit.telemetry.server_owner preference
+pageSubtitle = This page shows the information about performance, hardware, usage and customizations collected by Telemetry. This information is submitted to %1$S to help improve %2$S.
+
+generalDataTitle = General Data
+
+generalDataHeadingName = Name
+
+generalDataHeadingValue = Value
+
+environmentDataHeadingName = Name
+
+environmentDataHeadingValue = Value
+
+environmentDataSubsectionToggle = Click to toggle section
+
+environmentDataSubsectionEmpty = (No data collected)
+
+telemetryLogTitle = Telemetry Log
+
+telemetryLogHeadingId = Id
+
+telemetryLogHeadingTimestamp = Timestamp
+
+telemetryLogHeadingData = Data
+
+slowSqlMain = Slow SQL Statements on Main Thread
+
+slowSqlOther = Slow SQL Statements on Helper Threads
+
+slowSqlHits = Hits
+
+slowSqlAverage = Avg. Time (ms)
+
+slowSqlStatement = Statement
+
+# Note to translators:
+# - The %1$S will be replaced with the number of the hang
+# - The %2$S will be replaced with the duration of the hang
+chrome-hangs-title = Hang Report #%1$S (%2$S seconds)
+
+# Note to translators:
+# - The %1$S will be replaced with the number of the late write
+late-writes-title = Late Write #%1$S
+
+stackTitle = Stack:
+
+memoryMapTitle = Memory map:
+
+errorFetchingSymbols = An error occurred while fetching symbols. Check that you are connected to the Internet and try again.
+
+histogramSamples = samples
+
+histogramAverage = average
+
+histogramSum = sum
+
+histogramCopy = Copy
+
+keysHeader = Property
+
+namesHeader = Name
+
+valuesHeader = Value
+
+addonTableID = Add-on ID
+
+addonTableDetails = Details
+
+# Note to translators:
+# - The %1$S will be replaced with the name of an Add-on Provider (e.g. "XPI", "Plugin")
+addonProvider = %1$S Provider
+
+parentPayload = Parent Payload
+
+# Note to translators:
+# - The %1$S will be replaced with the number of the child payload (e.g. "1", "2")
+childPayloadN = Child Payload %1$S
diff --git a/components/global/locale/aboutWebrtc.properties b/components/global/locale/aboutWebrtc.properties
new file mode 100644
index 000000000..e1b270f7c
--- /dev/null
+++ b/components/global/locale/aboutWebrtc.properties
@@ -0,0 +1,120 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 (document_title, cannot_retrieve_log):
+# The text "WebRTC" is a proper noun and should not be translated.
+# It is the general label for the standards based technology. see http://www.webrtc.org
+document_title = WebRTC Internals
+cannot_retrieve_log = Cannot retrieve WebRTC log data
+
+# LOCALIZATION NOTE (save_page_msg):
+# %1$S will be replaced by a full path file name: the target of the SavePage operation.
+save_page_msg = page saved to: %1$S
+
+# LOCALIZATION NOTE (save_page_dialog_title): "about:webrtc" is a internal browser URL and should not be
+# translated. This string is used as a title for a file save dialog box.
+save_page_dialog_title = save about:webrtc as
+
+# LOCALIZATION NOTE (debug_mode_off_state_msg):
+# %1$S will be replaced by the full path file name of the debug log.
+debug_mode_off_state_msg = trace log can be found at: %1$S
+
+# LOCALIZATION NOTE (debug_mode_on_state_msg):
+# %1$S will be replaced by the full path file name of the debug log.
+debug_mode_on_state_msg = debug mode active, trace log at: %1$S
+
+# LOCALIZATION NOTE (aec_logging_msg_label, aec_logging_off_state_label,
+# aec_logging_on_state_label, aec_logging_on_state_msg):
+# AEC is an abbreviation for Acoustic Echo Cancellation.
+aec_logging_msg_label = AEC Logging
+aec_logging_off_state_label = Start AEC Logging
+aec_logging_on_state_label = Stop AEC Logging
+aec_logging_on_state_msg = AEC logging active (speak with the caller for a few minutes and then stop the capture)
+
+# LOCALIZATION NOTE (aec_logging_off_state_msg):
+# %1$S will be replaced by the full path to the directory containing the captured log files.
+# AEC is an abbreviation for Acoustic Echo Cancellation.
+aec_logging_off_state_msg = captured log files can be found in: %1$S
+
+# LOCALIZATION NOTE (peer_connection_id_label): "PeerConnection" is a proper noun
+# associated with the WebRTC module. "ID" is an abbreviation for Identifier. This string
+# should not normally be translated and is used as a data label.
+peer_connection_id_label = PeerConnection ID
+
+# LOCALIZATION NOTE (sdp_heading, local_sdp_heading, remote_sdp_heading):
+# "SDP" is an abbreviation for Session Description Protocol, an IETF standard.
+# See http://wikipedia.org/wiki/Session_Description_Protocol
+sdp_heading = SDP
+local_sdp_heading = Local SDP
+remote_sdp_heading = Remote SDP
+
+# LOCALIZATION NOTE (rtp_stats_heading): "RTP" is an abbreviation for the
+# Real-time Transport Protocol, an IETF specification, and should not
+# normally be translated. "Stats" is an abbreviation for Statistics.
+rtp_stats_heading = RTP Stats
+
+# LOCALIZATION NOTE (ice_state, ice_stats_heading): "ICE" is an abbreviation
+# for Interactive Connectivity Establishment, which is an IETF protocol,
+# and should not normally be translated. "Stats" is an abbreviation for
+# Statistics.
+ice_state = ICE State
+ice_stats_heading = ICE Stats
+
+# LOCALIZATION NOTE (av_sync_label): "A/V" stands for Audio/Video.
+# "sync" is an abbreviation for sychronization. This is used as
+# a data label.
+av_sync_label = A/V sync
+
+# LOCALIZATION NOTE (jitter_buffer_delay_label): A jitter buffer is an
+# element in the processing chain, see http://wikipedia.org/wiki/Jitter
+# This is used as a data label.
+jitter_buffer_delay_label = Jitter-buffer delay
+
+# LOCALIZATION NOTE (avg_bitrate_label, avg_framerate_label): "Avg." is an abbreviation
+# for Average. These are used as data labels.
+avg_bitrate_label = Avg. bitrate
+avg_framerate_label = Avg. framerate
+
+# LOCALIZATION NOTE (typeLocal, typeRemote): These adjectives are used to label a
+# line of statistics collected for a peer connection. The data represents
+# either the local or remote end of the connection.
+typeLocal = Local
+typeRemote = Remote
+
+# LOCALIZATION NOTE (nominated): This adjective is used to label a table column.
+# Cells in this column contain the localized javascript string representation of "true"
+# or are left blank.
+nominated = Nominated
+
+# LOCALIZATION NOTE (selected): This adjective is used to label a table column.
+# Cells in this column contain the localized javascript string representation of "true"
+# or are left blank. This represents an attribute of an ICE candidate.
+selected = Selected
+
+save_page_label = Save Page
+debug_mode_msg_label = Debug Mode
+debug_mode_off_state_label = Start Debug Mode
+debug_mode_on_state_label = Stop Debug Mode
+stats_heading = Session Statistics
+log_heading = Connection Log
+log_show_msg = show log
+log_hide_msg = hide log
+connection_closed = closed
+local_candidate = Local Candidate
+remote_candidate = Remote Candidate
+priority = Priority
+fold_show_msg = show details
+fold_show_hint = click to expand this section
+fold_hide_msg = hide details
+fold_hide_hint = click to collapse this section
+dropped_frames_label = Dropped frames
+discarded_packets_label = Discarded packets
+decoder_label = Decoder
+encoder_label = Encoder
+received_label = Received
+packets = packets
+lost_label = Lost
+jitter_label = Jitter
+sent_label = Sent
+
diff --git a/components/global/locale/appPicker.dtd b/components/global/locale/appPicker.dtd
new file mode 100644
index 000000000..04d9bb8fe
--- /dev/null
+++ b/components/global/locale/appPicker.dtd
@@ -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/. -->
+
+<!ENTITY NoAppFound.label "No applications were found for this file type.">
+<!ENTITY BrowseButton.label "Browse…">
+<!ENTITY SendMsg.label "Send this item to:">
diff --git a/components/global/locale/autocomplete.properties b/components/global/locale/autocomplete.properties
new file mode 100644
index 000000000..44da643ac
--- /dev/null
+++ b/components/global/locale/autocomplete.properties
@@ -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/.
+switchToTab = Switch to tab
+
+# LOCALIZATION NOTE (visitURL):
+# %S is the URL to visit.
+visitURL = Visit %S
+
+# LOCALIZATION NOTE (searchWithEngine): %S will be replaced with
+# the search engine provider's name. This format was chosen because
+# the provider can also end with "Search" (e.g.: MSN Search).
+searchWithEngine = Search with %S
+
+# LOCALIZATION NOTE (switchToTab2): This is the same as the older switchToTab
+# string that it's replacing, except it uses title case, so "Switch" and "Tab"
+# are capitalized.
+switchToTab2 = Switch to Tab
+
+# LOCALIZATION NOTE (visit): This is shown next to autocomplete entries that are
+# simple URLs or sites, which will be visited when the user selects them.
+visit = Visit
+
+# LOCALIZATION NOTE (bookmarkKeywordSearch): This is the title of autocomplete
+# entries that are bookmark keyword searches. %1$S will be replaced with the
+# domain name of the bookmark, and %2$S will be replaced with the keyword
+# search text that the user is typing. %2$S will not be empty.
+bookmarkKeywordSearch = %1$S: %2$S
diff --git a/components/global/locale/autoconfig.properties b/components/global/locale/autoconfig.properties
new file mode 100644
index 000000000..7c5e15ea1
--- /dev/null
+++ b/components/global/locale/autoconfig.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/.
+
+readConfigTitle = Configuration Error
+readConfigMsg = Failed to read the configuration file. Please contact your system administrator.
+
+autoConfigTitle = AutoConfig Alert
+autoConfigMsg = Netscape.cfg/AutoConfig failed. Please contact your system administrator. \n Error: %S failed:
+
+emailPromptTitle = Email Address
+emailPromptMsg = Enter your email address
diff --git a/components/global/locale/browser.properties b/components/global/locale/browser.properties
new file mode 100644
index 000000000..2b3150629
--- /dev/null
+++ b/components/global/locale/browser.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/.
+
+browsewithcaret.checkMsg=Do not show me this dialog box again.
+browsewithcaret.checkWindowTitle=Caret Browsing
+browsewithcaret.checkLabel=Pressing F7 turns Caret Browsing on or off. This feature places a moveable cursor in web pages, allowing you to select text with the keyboard. Do you want to turn Caret Browsing on?
+browsewithcaret.checkButtonLabel=Yes
+
+plainText.wordWrap=Wrap Long Lines
+
+formPostSecureToInsecureWarning.title = Security Warning
+formPostSecureToInsecureWarning.message = The information you have entered on this page will be sent over an insecure connection and could be read by a third party.\n\nAre you sure you want to send this information?
+formPostSecureToInsecureWarning.continue = Continue
diff --git a/components/global/locale/charsetMenu.dtd b/components/global/locale/charsetMenu.dtd
new file mode 100644
index 000000000..140627251
--- /dev/null
+++ b/components/global/locale/charsetMenu.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 charsetMenu2.label "Text Encoding">
+<!ENTITY charsetMenu2.accesskey "c">
diff --git a/components/global/locale/charsetMenu.properties b/components/global/locale/charsetMenu.properties
new file mode 100644
index 000000000..a86a58320
--- /dev/null
+++ b/components/global/locale/charsetMenu.properties
@@ -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/.
+
+# 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.
+#
+# For gbk, gbk.bis and gbk.bis.key are used to trigger string changes in
+# localizations.
+#
+# 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.)
+
+# Auto-Detect (sub)menu
+charsetMenuCharsets = Character Encoding
+charsetMenuAutodet = Auto-Detect
+# 'A' is reserved for Arabic:
+charsetMenuAutodet.key = D
+charsetMenuAutodet.off = (off)
+charsetMenuAutodet.off.key = o
+charsetMenuAutodet.ja = Japanese
+charsetMenuAutodet.ja.key = J
+charsetMenuAutodet.ru = Russian
+charsetMenuAutodet.ru.key = R
+charsetMenuAutodet.uk = Ukrainian
+charsetMenuAutodet.uk.key = U
+
+# 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.bis.key = S
+gbk.bis = Chinese, Simplified
+
+# 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/components/global/locale/commonDialog.dtd b/components/global/locale/commonDialog.dtd
new file mode 100644
index 000000000..a2273a61b
--- /dev/null
+++ b/components/global/locale/commonDialog.dtd
@@ -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/. -->
+
+<!ENTITY header.label "Brief Title">
+<!ENTITY message.label "Some sample Text goes here.">
+<!ENTITY editfield0.label "User Name:">
+<!ENTITY editfield1.label "Password:">
+<!ENTITY checkbox.label "check">
+<!ENTITY copyCmd.label "Copy">
+<!ENTITY copyCmd.accesskey "C">
+<!ENTITY selectAllCmd.label "Select All">
+<!ENTITY selectAllCmd.accesskey "A">
diff --git a/components/global/locale/commonDialogs.properties b/components/global/locale/commonDialogs.properties
new file mode 100644
index 000000000..001705b18
--- /dev/null
+++ b/components/global/locale/commonDialogs.properties
@@ -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/.
+
+Alert=Alert
+Confirm=Confirm
+ConfirmCheck=Confirm
+Prompt=Prompt
+PromptUsernameAndPassword2=Authentication Required
+PromptPassword2=Password Required
+Select=Select
+OK=OK
+Cancel=Cancel
+Yes=&Yes
+No=&No
+Save=&Save
+Revert=&Revert
+DontSave=Do&n’t Save
+ScriptDlgGenericHeading=[JavaScript Application]
+ScriptDlgHeading=The page at %S says:
+ScriptDialogLabel=Prevent this page from creating additional dialogs
+ScriptDialogPreventTitle=Confirm Dialog Preference
+# LOCALIZATION NOTE (EnterLoginForRealm3, EnterLoginForProxy3):
+# %1 is an untrusted string provided by a remote server. It could try to
+# take advantage of sentence structure in order to mislead the user (see
+# bug 244273). %1 should be integrated into the translated sentences as
+# little as possible. %2 is the url of the site being accessed.
+EnterLoginForRealm3=%2$S is requesting your username and password. The site says: “%1$Sâ€
+EnterLoginForProxy3=The proxy %2$S is requesting a username and password. The site says: “%1$Sâ€
+EnterUserPasswordFor2=%1$S is requesting your username and password.
+EnterUserPasswordForCrossOrigin2=%1$S is requesting your username and password. WARNING: Your password will not be sent to the website you are currently visiting!
+EnterPasswordFor=Enter password for %1$S on %2$S
diff --git a/components/global/locale/config.dtd b/components/global/locale/config.dtd
new file mode 100644
index 000000000..a9bf8ab48
--- /dev/null
+++ b/components/global/locale/config.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 window.title "about:config">
+
+<!-- about:config warning page -->
+<!-- LOCALIZATION NOTE: aboutWarningTitle.label should be attention grabbing and playful -->
+<!ENTITY aboutWarningTitle.label "There be dragons here!">
+<!ENTITY aboutWarningText.label "Changing these advanced settings can be (severely) harmful to the stability, security, operation and performance of this application. You should only continue if you are sure of what you are doing; we will not be responsible for any cases of users being mauled, set on fire, or eaten.">
+<!ENTITY aboutWarningButton2.label "I promise to be careful!">
+<!ENTITY aboutWarningCheckbox.label "Keep reminding me that this is dangerous">
+
+<!ENTITY searchPrefs.label "Search:">
+<!ENTITY searchPrefs.accesskey "r">
+<!ENTITY focusSearch.key "r">
+<!ENTITY focusSearch2.key "f">
+
+<!-- Columns -->
+<!ENTITY prefColumn.label "Preference Name">
+<!ENTITY lockColumn.label "Status">
+<!ENTITY typeColumn.label "Type">
+<!ENTITY valueColumn.label "Value">
+
+<!-- Tooltips -->
+<!ENTITY prefColumnHeader.tooltip "Click to sort">
+<!ENTITY columnChooser.tooltip "Click to select columns to display">
+
+<!-- Context Menu -->
+<!ENTITY copyPref.key "C">
+<!ENTITY copyPref.label "Copy">
+<!ENTITY copyPref.accesskey "C">
+<!ENTITY copyName.label "Copy Name">
+<!ENTITY copyName.accesskey "N">
+<!ENTITY copyValue.label "Copy Value">
+<!ENTITY copyValue.accesskey "V">
+<!ENTITY modify.label "Modify">
+<!ENTITY modify.accesskey "M">
+<!ENTITY toggle.label "Toggle">
+<!ENTITY toggle.accesskey "T">
+<!ENTITY reset.label "Reset">
+<!ENTITY reset.accesskey "R">
+<!ENTITY new.label "New">
+<!ENTITY new.accesskey "w">
+<!ENTITY string.label "String">
+<!ENTITY string.accesskey "S">
+<!ENTITY integer.label "Integer">
+<!ENTITY integer.accesskey "I">
+<!ENTITY boolean.label "Boolean">
+<!ENTITY boolean.accesskey "B">
diff --git a/components/global/locale/config.properties b/components/global/locale/config.properties
new file mode 100644
index 000000000..31b832cfa
--- /dev/null
+++ b/components/global/locale/config.properties
@@ -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/.
+
+# Lock column values
+default=default
+user=user set
+locked=locked
+
+# Type column values
+string=string
+int=integer
+bool=boolean
+
+# Preference prompts
+# %S is replaced by one of the type column values above
+new_title=New %S value
+new_prompt=Enter the preference name
+modify_title=Enter %S value
+
+nan_title=Invalid value
+nan_text=The text you entered is not a number.
diff --git a/components/global/locale/console.dtd b/components/global/locale/console.dtd
new file mode 100644
index 000000000..b5611fad4
--- /dev/null
+++ b/components/global/locale/console.dtd
@@ -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/. -->
+
+<!ENTITY errorConsole.title "Error Console">
+
+<!ENTITY errFile.label "Source File:">
+<!ENTITY errLine.label "Line:">
+<!ENTITY errColumn.label "Column:">
+
+<!ENTITY all.label "All">
+<!ENTITY all.accesskey "A">
+<!ENTITY errors.label "Errors">
+<!ENTITY errors.accesskey "E">
+<!ENTITY warnings.label "Warnings">
+<!ENTITY warnings.accesskey "W">
+<!ENTITY messages.label "Messages">
+<!ENTITY messages.accesskey "M">
+<!ENTITY clear.label "Clear">
+<!ENTITY clear.accesskey "C">
+<!ENTITY codeEval.label "Code:">
+<!ENTITY codeEval.accesskey "o">
+<!ENTITY evaluate.label "Evaluate">
+<!ENTITY evaluate.accesskey "v">
+<!ENTITY filter2.label "Filter:">
+<!ENTITY filter2.accesskey "F">
+
+<!ENTITY copyCmd.label "Copy">
+<!ENTITY copyCmd.accesskey "C">
+<!ENTITY copyCmd.commandkey "C">
+<!ENTITY sortFirst.label "First > Last Sort Order">
+<!ENTITY sortFirst.accesskey "f">
+<!ENTITY sortLast.label "Last > First Sort Order">
+<!ENTITY sortLast.accesskey "l">
+<!ENTITY closeCmd.commandkey "w">
+<!ENTITY focus1.commandkey "l">
+<!ENTITY focus2.commandkey "d">
diff --git a/components/global/locale/console.properties b/components/global/locale/console.properties
new file mode 100644
index 000000000..a17a250b3
--- /dev/null
+++ b/components/global/locale/console.properties
@@ -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/.
+
+typeError=Error:
+typeWarning=Warning:
+typeMessage=Message:
+errFile=Source File: %S
+errLine=Line: %S
+errLineCol=Line: %S, Column: %S
+errCode=Source Code:
+errTime=Timestamp: %S
+
+# LOCALIZATION NOTE (evaluationContextChanged): The message displayed when the
+# browser console's evaluation context (window against which input is evaluated)
+# changes.
+evaluationContextChanged=The console’s evaluation context changed, probably because the target window was closed or because you opened a main window from the browser console’s window.
diff --git a/components/global/locale/contentAreaCommands.properties b/components/global/locale/contentAreaCommands.properties
new file mode 100644
index 000000000..1941c5e6b
--- /dev/null
+++ b/components/global/locale/contentAreaCommands.properties
@@ -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/.
+
+# context menu strings
+
+SaveImageTitle=Save Image
+SaveVideoTitle=Save Video
+SaveAudioTitle=Save Audio
+SaveLinkTitle=Save As
+DefaultSaveFileName=index
+WebPageCompleteFilter=Web Page, complete
+WebPageHTMLOnlyFilter=Web Page, HTML only
+WebPageXHTMLOnlyFilter=Web Page, XHTML only
+WebPageSVGOnlyFilter=Web Page, SVG only
+WebPageXMLOnlyFilter=Web Page, XML only
+
+# LOCALIZATION NOTE (filesFolder):
+# This is the name of the folder that is created parallel to a HTML file
+# when it is saved "With Images". The %S section is replaced with the
+# leaf name of the file being saved (minus extension).
+filesFolder=%S_files
diff --git a/components/global/locale/customizeToolbar.dtd b/components/global/locale/customizeToolbar.dtd
new file mode 100644
index 000000000..01448273d
--- /dev/null
+++ b/components/global/locale/customizeToolbar.dtd
@@ -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/. -->
+
+<!ENTITY dialog.title "Customize Toolbar">
+<!ENTITY dialog.dimensions "width: 92ch; height: 36em;">
+<!ENTITY instructions.description "You can add or remove items by dragging to or from the toolbars.">
+<!ENTITY show.label "Show:">
+<!ENTITY iconsAndText.label "Icons and Text">
+<!ENTITY icons.label "Icons">
+<!ENTITY text.label "Text">
+<!ENTITY useSmallIcons.label "Use Small Icons">
+<!ENTITY restoreDefaultSet.label "Restore Default Set">
+<!ENTITY addNewToolbar.label "Add New Toolbar">
+<!ENTITY saveChanges.label "Done">
+<!ENTITY undoChanges.label "Undo Changes">
diff --git a/components/global/locale/customizeToolbar.properties b/components/global/locale/customizeToolbar.properties
new file mode 100644
index 000000000..b19152fab
--- /dev/null
+++ b/components/global/locale/customizeToolbar.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/.
+
+enterToolbarTitle=New Toolbar
+enterToolbarName=Enter a name for this toolbar:
+enterToolbarDup=There is already a toolbar with the name “%Sâ€. Please enter a different name.
+enterToolbarIllegalChars=The name contains illegal character "|". Please enter a different name.
+enterToolbarBlank=You must enter a name to create a new toolbar.
+separatorTitle=Separator
+springTitle=Flexible Space
+spacerTitle=Space
diff --git a/components/global/locale/dateFormat.properties b/components/global/locale/dateFormat.properties
new file mode 100644
index 000000000..a564c0d4d
--- /dev/null
+++ b/components/global/locale/dateFormat.properties
@@ -0,0 +1,58 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+month.1.Mmm=Jan
+month.2.Mmm=Feb
+month.3.Mmm=Mar
+month.4.Mmm=Apr
+month.5.Mmm=May
+month.6.Mmm=Jun
+month.7.Mmm=Jul
+month.8.Mmm=Aug
+month.9.Mmm=Sep
+month.10.Mmm=Oct
+month.11.Mmm=Nov
+month.12.Mmm=Dec
+
+month.1.name=January
+month.2.name=February
+month.3.name=March
+month.4.name=April
+month.5.name=May
+month.6.name=June
+month.7.name=July
+month.8.name=August
+month.9.name=September
+month.10.name=October
+month.11.name=November
+month.12.name=December
+
+day.1.name=Sunday
+day.2.name=Monday
+day.3.name=Tuesday
+day.4.name=Wednesday
+day.5.name=Thursday
+day.6.name=Friday
+day.7.name=Saturday
+
+day.1.Mmm=Sun
+day.2.Mmm=Mon
+day.3.Mmm=Tue
+day.4.Mmm=Wed
+day.5.Mmm=Thu
+day.6.Mmm=Fri
+day.7.Mmm=Sat
+
+day.1.short=Su
+day.2.short=Mo
+day.3.short=Tu
+day.4.short=We
+day.5.short=Th
+day.6.short=Fr
+day.7.short=Sa
+
+noon=Noon
+midnight=Midnight
+
+AllDay=All Day
diff --git a/components/global/locale/datetimebox.dtd b/components/global/locale/datetimebox.dtd
new file mode 100644
index 000000000..0deffa6b3
--- /dev/null
+++ b/components/global/locale/datetimebox.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/. -->
+
+<!-- Placeholders for input type=date -->
+
+<!ENTITY date.year.placeholder "yyyy">
+<!ENTITY date.month.placeholder "mm">
+<!ENTITY date.day.placeholder "dd">
diff --git a/components/global/locale/datetimepicker.dtd b/components/global/locale/datetimepicker.dtd
new file mode 100644
index 000000000..b010c4831
--- /dev/null
+++ b/components/global/locale/datetimepicker.dtd
@@ -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/. -->
+
+<!-- first day of week to display in datepicker, a value from 0 to 6,
+ 0 = Sunday, 1 = Monday, etc. -->
+<!ENTITY firstdayofweek.default "0">
diff --git a/components/global/locale/dialog.properties b/components/global/locale/dialog.properties
new file mode 100644
index 000000000..ce6acb5ef
--- /dev/null
+++ b/components/global/locale/dialog.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/.
+
+button-accept=OK
+button-cancel=Cancel
+button-help=Help
+button-disclosure=More Info
+accesskey-accept=
+accesskey-cancel=
+accesskey-help=H
+accesskey-disclosure=I
diff --git a/components/global/locale/dialogOverlay.dtd b/components/global/locale/dialogOverlay.dtd
new file mode 100644
index 000000000..217b8c9e9
--- /dev/null
+++ b/components/global/locale/dialogOverlay.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/. -->
+
+<!-- WARNING!!! This file is obsoleted by the dialog.xml widget -->
+
+<!-- OK Cancel Buttons -->
+<!ENTITY okButton.label "OK">
+<!ENTITY cancelButton.label "Cancel">
+<!ENTITY helpButton.label "Help">
diff --git a/components/global/locale/editMenuOverlay.dtd b/components/global/locale/editMenuOverlay.dtd
new file mode 100644
index 000000000..44185616b
--- /dev/null
+++ b/components/global/locale/editMenuOverlay.dtd
@@ -0,0 +1,35 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!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.accesskey "d">
+<!ENTITY selectAllCmd.label "Select All">
+<!ENTITY selectAllCmd.key "A">
+<!ENTITY selectAllCmd.accesskey "a">
+<!ENTITY findCmd.label "Find">
+<!ENTITY findCmd.key "F">
+<!ENTITY findCmd.accesskey "F">
+<!ENTITY findAgainCmd.label "Find Again">
+<!ENTITY findAgainCmd.key "G">
+<!ENTITY findAgainCmd.key2 "VK_F3">
+<!ENTITY findAgainCmd.accesskey "g">
+<!ENTITY findPreviousCmd.label "Find Previous">
+<!ENTITY findPreviousCmd.accesskey "v">
diff --git a/components/global/locale/extensions.properties b/components/global/locale/extensions.properties
new file mode 100644
index 000000000..20ee688ba
--- /dev/null
+++ b/components/global/locale/extensions.properties
@@ -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/.
+
+csp.error.missing-directive = Policy is missing a required ‘%S’ directive
+
+#LOCALIZATION NOTE (csp.error.illegal-keyword) %1$S is the name of a CSP directive, such as "script-src". %2$S is the name of a CSP keyword, usually 'unsafe-inline'.
+csp.error.illegal-keyword = ‘%1$S’ directive contains a forbidden %2$S keyword
+
+#LOCALIZATION NOTE (csp.error.illegal-protocol) %2$S a protocol name, such as "http", which appears as "http:", as it would in a URL.
+csp.error.illegal-protocol = ‘%1$S’ directive contains a forbidden %2$S: protocol source
+
+#LOCALIZATION NOTE (csp.error.missing-host) %2$S a protocol name, such as "http", which appears as "http:", as it would in a URL.
+csp.error.missing-host = %2$S: protocol requires a host in ‘%1$S’ directives
+
+#LOCALIZATION NOTE (csp.error.missing-source) %1$S is the name of a CSP directive, such as "script-src". %2$S is the name of a CSP source, usually 'self'.
+csp.error.missing-source = ‘%1$S’ must include the source %2$S
+
+#LOCALIZATION NOTE (csp.error.illegal-host-wildcard) %2$S a protocol name, such as "http", which appears as "http:", as it would in a URL.
+csp.error.illegal-host-wildcard = %2$S: wildcard sources in ‘%1$S’ directives must include at least one non-generic sub-domain (e.g., *.example.com rather than *.com)
+
+#LOCALIZATION NOTE (uninstall.confirmation.title) %S is the name of the extension which is about to be uninstalled.
+uninstall.confirmation.title = Uninstall %S
+
+#LOCALIZATION NOTE (uninstall.confirmation.message) %S is the name of the extension which is about to be uninstalled.
+uninstall.confirmation.message = The extension “%S†is requesting to be uninstalled. What would you like to do?
+
+uninstall.confirmation.button-0.label = Uninstall
+uninstall.confirmation.button-1.label = Keep Installed
diff --git a/components/global/locale/fallbackMenubar.properties b/components/global/locale/fallbackMenubar.properties
new file mode 100644
index 000000000..9765689ec
--- /dev/null
+++ b/components/global/locale/fallbackMenubar.properties
@@ -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/.
+
+# OSX only. Default menu label when there is no xul menubar.
+
+quitMenuitem.label=Quit
+quitMenuitem.key=q
diff --git a/components/global/locale/filefield.properties b/components/global/locale/filefield.properties
new file mode 100644
index 000000000..affe3e89f
--- /dev/null
+++ b/components/global/locale/filefield.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/.
+
+#### Change Action
+
+downloadHelperNoneSelected=None Selected
diff --git a/components/global/locale/filepicker.dtd b/components/global/locale/filepicker.dtd
new file mode 100644
index 000000000..3367722ee
--- /dev/null
+++ b/components/global/locale/filepicker.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 lookInMenuList.label "Look in:">
+<!ENTITY lookInMenuList.accesskey "L">
+<!ENTITY textInput.label "File name:">
+<!ENTITY textInput.accesskey "n">
+<!ENTITY filterMenuList.label "Files of type:">
+<!ENTITY filterMenuList.accesskey "t">
+<!ENTITY name.label "Name">
+<!ENTITY size.label "Size">
+<!ENTITY lastModified.label "Last Modified">
+<!ENTITY showHiddenFiles.label "Show hidden files and directories">
+<!ENTITY showHiddenFiles.accesskey "S">
+
+<!ENTITY noPermissionError.label "You do not have the permissions necessary to view this directory.">
+
+<!ENTITY folderUp.tooltiptext "Go up a level">
+<!ENTITY folderHome.tooltiptext "Go to home">
+<!ENTITY folderNew.tooltiptext "Create new directory">
diff --git a/components/global/locale/filepicker.properties b/components/global/locale/filepicker.properties
new file mode 100644
index 000000000..9c9be1f78
--- /dev/null
+++ b/components/global/locale/filepicker.properties
@@ -0,0 +1,55 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE: The extensions to which these descriptions refer
+# now live in toolkit/content/filepicker.properties
+allTitle=All Files
+htmlTitle=HTML Files
+textTitle=Text Files
+imageTitle=Image Files
+xmlTitle=XML Files
+xulTitle=XUL Files
+appsTitle=Applications
+audioTitle=Audio Files
+videoTitle=Video Files
+
+dirTextInputLabel=Directory name:
+dirTextInputAccesskey=n
+
+confirmTitle=Confirm
+confirmFileReplacing=%S already exists.\nDo you want to replace it?
+openButtonLabel=Open
+saveButtonLabel=Save
+selectFolderButtonLabel=Select
+noButtonLabel=No
+formatLabel=Format:
+
+errorOpenFileDoesntExistTitle=Error opening %S
+errorOpenFileDoesntExistMessage=File %S doesn’t exist
+errorDirDoesntExistTitle=Error accessing %S
+errorDirDoesntExistMessage=Directory %S doesn’t exist
+
+errorOpeningFileTitle=Error opening %S
+openWithoutPermissionMessage_file=File %S is not readable
+
+errorSavingFileTitle=Error saving %S
+saveParentIsFileMessage=%S is a file, can’t save %S
+saveParentDoesntExistMessage=Path %S doesn’t exist, can’t save %S
+
+saveWithoutPermissionMessage_file=File %S is not writable.
+saveWithoutPermissionMessage_dir=Cannot create file. Directory %S is not writable.
+
+errorNewDirDoesExistTitle=Error creating %S
+errorNewDirDoesExistMessage=A file named %S already exists, directory cannot be created.
+
+errorCreateNewDirTitle=Error creating %S
+errorCreateNewDirMessage=Directory %S could not be created
+errorCreateNewDirIsFileMessage=Directory cannot be created, %S is a file
+errorCreateNewDirPermissionMessage=Directory cannot be created, %S not writable
+
+promptNewDirTitle=Create new directory
+promptNewDirMessage=Directory name:
+
+errorPathProblemTitle=Unknown Error
+errorPathProblemMessage=An unknown error occurred (path %S)
diff --git a/components/global/locale/findbar.dtd b/components/global/locale/findbar.dtd
new file mode 100644
index 000000000..a90c77c7e
--- /dev/null
+++ b/components/global/locale/findbar.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/. -->
+
+<!-- LOCALIZATION NOTE : FILE This file contains the entities needed to -->
+<!-- LOCALIZATION NOTE : FILE use the Find Bar. -->
+
+<!ENTITY next.label "Next">
+<!ENTITY next.accesskey "N">
+<!ENTITY next.tooltip "Find the next occurrence of the phrase">
+<!ENTITY previous.label "Previous">
+<!ENTITY previous.accesskey "P">
+<!ENTITY previous.tooltip "Find the previous occurrence of the phrase">
+<!ENTITY findCloseButton.tooltip "Close find bar">
+<!ENTITY highlightAll.label "Highlight All">
+<!ENTITY highlightAll.accesskey "l">
+<!ENTITY highlightAll.tooltiptext "Highlight all occurrences of the phrase">
+<!ENTITY caseSensitive.label "Match Case">
+<!ENTITY caseSensitive.accesskey "c">
+<!ENTITY caseSensitive.tooltiptext "Search with case sensitivity">
+<!ENTITY entireWord.label "Whole Words">
+<!ENTITY entireWord.accesskey "w">
+<!ENTITY entireWord.tooltiptext "Search whole words only">
diff --git a/components/global/locale/findbar.properties b/components/global/locale/findbar.properties
new file mode 100644
index 000000000..abe037ba7
--- /dev/null
+++ b/components/global/locale/findbar.properties
@@ -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/.
+
+# strings used by the Find bar, split from browser.properties
+NotFound=Phrase not found
+WrappedToTop=Reached end of page, continued from top
+WrappedToBottom=Reached top of page, continued from bottom
+NormalFind=Find in page
+FastFind=Quick find
+FastFindLinks=Quick find (links only)
+CaseSensitive=(Case sensitive)
+EntireWord=(Whole words only)
+# LOCALIZATION NOTE (FoundMatches): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is currently selected match and #2 the total amount of matches.
+FoundMatches=#1 of #2 match;#1 of #2 matches
+# LOCALIZATION NOTE (FoundMatchesCountLimit): Semicolon-separated list of plural
+# forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the total amount of matches allowed before counting stops.
+FoundMatchesCountLimit=More than #1 match;More than #1 matches
diff --git a/components/global/locale/finddialog.dtd b/components/global/locale/finddialog.dtd
new file mode 100644
index 000000000..d3ca7e5dc
--- /dev/null
+++ b/components/global/locale/finddialog.dtd
@@ -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/. -->
+
+<!-- extracted from finddialog.xul -->
+
+<!ENTITY findDialog.title "Find in This Page">
+<!ENTITY findField.label "Find what:">
+<!ENTITY findField.accesskey "n">
+<!ENTITY caseSensitiveCheckbox.label "Match case">
+<!ENTITY caseSensitiveCheckbox.accesskey "c">
+<!ENTITY wrapCheckbox.label "Wrap">
+<!ENTITY wrapCheckbox.accesskey "W">
+<!ENTITY findButton.label "Find Next">
+<!ENTITY findButton.accesskey "F">
+<!ENTITY cancelButton.label "Cancel">
+<!ENTITY closeButton.label "Close">
+<!ENTITY up.label "Up">
+<!ENTITY up.accesskey "U">
+<!ENTITY down.label "Down">
+<!ENTITY down.accesskey "D">
+<!ENTITY direction.label "Direction">
diff --git a/components/global/locale/finddialog.properties b/components/global/locale/finddialog.properties
new file mode 100644
index 000000000..67b0b3c41
--- /dev/null
+++ b/components/global/locale/finddialog.properties
@@ -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/.
+
+notFoundWarning=The text you entered was not found.
+notFoundTitle=Find \ No newline at end of file
diff --git a/components/global/locale/globalKeys.dtd b/components/global/locale/globalKeys.dtd
new file mode 100644
index 000000000..76b923571
--- /dev/null
+++ b/components/global/locale/globalKeys.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 openHelp.commandkey "VK_F1">
+<!ENTITY openHelpMac.commandkey "?">
diff --git a/components/global/locale/headsUpDisplay.properties b/components/global/locale/headsUpDisplay.properties
new file mode 100644
index 000000000..6a95bb036
--- /dev/null
+++ b/components/global/locale/headsUpDisplay.properties
@@ -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/.
+
+
+# LOCALIZATION NOTE (stacktrace.anonymousFunction):
+# This string is used to display JavaScript functions that have no given name -
+# they are said to be anonymous. See stacktrace.outputMessage.
+stacktrace.anonymousFunction=<anonymous>
+
+# LOCALIZATION NOTE (stacktrace.outputMessage):
+# This string is used in the Web Console output to identify a web developer call
+# to console.trace(). The stack trace of JavaScript function calls is displayed.
+# In this minimal message we only show the last call.
+stacktrace.outputMessage=Stack trace from %S, function %S, line %S.
diff --git a/components/global/locale/intl.css b/components/global/locale/intl.css
new file mode 100644
index 000000000..2f54eb367
--- /dev/null
+++ b/components/global/locale/intl.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/. */
+
+/*
+ * This file contains all localizable skin settings such as
+ * font, layout, and geometry
+ */
+window {
+ font: 3mm tahoma,arial,helvetica,sans-serif;
+}
diff --git a/components/global/locale/intl.properties b/components/global/locale/intl.properties
new file mode 100644
index 000000000..0fd8a493f
--- /dev/null
+++ b/components/global/locale/intl.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/.
+
+# LOCALIZATION NOTE (general.useragent.locale):
+# This is the valid BCP 47 language tag representing your locale.
+#
+# In most cases, this will simply be your locale code. However, in rare cases
+# (such as 'jp-JP-mac'), you may need to modify your locale code in order to
+# make it a valid BCP 47 language tag. (If your locale code does not include a
+# region subtag, do not include one in the language tag representing your
+# locale.)
+general.useragent.locale=en-US
+
+# LOCALIZATION NOTE (intl.accept_languages):
+# This is a comma-separated list of valid BCP 47 language tags.
+#
+# Begin with the value of 'general.useragent.locale'. Next, include language
+# tags for other languages that you expect most users of your locale to be
+# able to speak, so that their browsing experience degrades gracefully if
+# content is not available in their primary language.
+#
+# It is recommended that you include "en-US, en" at the end of the list as a
+# last resort. However, if you know that users of your locale would prefer a
+# different variety of English, or if they are not likely to understand
+# English at all, you may opt to include a different English language tag, or
+# to exclude English altogether.
+#
+# For example, the Breton [br] locale might consider including French and
+# British English in their list, since those languages are commonly spoken in
+# the same area as Breton:
+# intl.accept_languages=br, fr-FR, fr, en-GB, en
+intl.accept_languages=en-US, en
+
+# LOCALIZATION NOTE (font.language.group):
+# This preference controls the initial setting of the language drop-down menu
+# in the Content > Fonts & Colors > Advanced preference panel.
+#
+# Set it to the value of one of the menuitems in the "selectLangs" menulist in
+# http://dxr.mozilla.org/mozilla-central/source/browser/components/preferences/fonts.xul
+font.language.group=x-western
+
+# LOCALIZATION NOTE (intl.charset.detector):
+# This preference controls the initial setting for the character encoding
+# detector. Valid values are ja_parallel_state_machine for Japanese, ruprob
+# for Russian and ukprob for Ukrainian and the empty string to turn detection
+# off. The value must be empty for locales other than Japanese, Russian and
+# Ukrainian.
+intl.charset.detector=
+
+# LOCALIZATION NOTE (pluralRule): Pick the appropriate plural rule for your
+# language. This will determine how many plural forms of a word you will need
+# to provide and in what order.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+pluralRule=1
+
+# LOCALIZATION NOTE (intl.menuitems.alwaysappendaccesskeys, intl.menuitems.insertseparatorbeforeaccesskeys):
+# Valid values are: true, false, <empty string>
+# Missing preference or empty value equals false.
+intl.menuitems.alwaysappendaccesskeys=
+intl.menuitems.insertseparatorbeforeaccesskeys=true
diff --git a/components/global/locale/keys.properties b/components/global/locale/keys.properties
new file mode 100644
index 000000000..1137bf7c6
--- /dev/null
+++ b/components/global/locale/keys.properties
@@ -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/.
+
+# LOCALIZATION NOTE : FILE This file contains the application's labels for keys on the keyboard.
+# If you decide to translate this file, you should translate it based on
+# the prevelant kind of keyboard for your target user.
+# LOCALIZATION NOTE : There are two types of keys, those w/ text on their labels
+# and those w/ glyphs.
+# LOCALIZATION NOTE : VK_<…> represents a key on the keyboard.
+#
+# For more information please see bugzilla bug 90888.
+
+# F1..F10 should probably not be translated unless there are keyboards that actually have other labels
+# F11..F20 might be something else, but are really keyboard specific and not region/language specific
+# there are actually two different F11/F12 keys, I don't know which one these labels represent.
+# eg, F13..F20 on a sparc keyboard are labeled Props, Again .. Find, Cut
+# sparc also has Stop, Again and F11/F12. VK_F11/VK_F12 probably map to Stop/Again
+# LOCALIZATION NOTE : BLOCK Do not translate the next block
+VK_F1=F1
+VK_F2=F2
+VK_F3=F3
+VK_F4=F4
+VK_F5=F5
+VK_F6=F6
+VK_F7=F7
+VK_F8=F8
+VK_F9=F9
+VK_F10=F10
+
+VK_F11=F11
+VK_F12=F12
+VK_F13=F13
+VK_F14=F14
+VK_F15=F15
+VK_F16=F16
+VK_F17=F17
+VK_F18=F18
+VK_F19=F19
+VK_F20=F20
+# LOCALIZATION NOTE : BLOCK end do not translate block
+
+# LOCALIZATION NOTE : BLOCK GLYPHS, DO translate this block
+VK_UP=Up Arrow
+VK_DOWN=Down Arrow
+VK_LEFT=Left Arrow
+VK_RIGHT=Right Arrow
+VK_PAGE_UP=Page Up
+VK_PAGE_DOWN=Page Down
+# LOCALIZATION NOTE : BLOCK end GLYPHS
+
+# Enter, backspace, and Tab might have both glyphs and text
+# if the keyboards usually have a glyph,
+# if there is a meaningful translation,
+# or if keyboards are localized
+# then translate them or insert the appropriate glyph
+# otherwise you should probably just translate the glyph regions
+
+# LOCALIZATION NOTE : BLOCK maybe GLYPHS
+VK_RETURN=Return
+VK_TAB=Tab
+VK_BACK=Backspace
+VK_DELETE=Del
+# LOCALIZATION NOTE : BLOCK end maybe GLYPHS
+# LOCALIZATION NOTE : BLOCK typing state keys
+VK_HOME=Home
+VK_END=End
+
+VK_ESCAPE=Esc
+VK_INSERT=Ins
+# LOCALIZATION NOTE : BLOCK end
diff --git a/components/global/locale/languageNames.properties b/components/global/locale/languageNames.properties
new file mode 100644
index 000000000..234000d11
--- /dev/null
+++ b/components/global/locale/languageNames.properties
@@ -0,0 +1,201 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+aa = Afar
+ab = Abkhazian
+ae = Avestan
+af = Afrikaans
+ak = Akan
+am = Amharic
+an = Aragonese
+ar = Arabic
+as = Assamese
+ast = Asturian
+av = Avaric
+ay = Aymara
+az = Azerbaijani
+ba = Bashkir
+be = Belarusian
+bg = Bulgarian
+bh = Bihari
+bi = Bislama
+bm = Bambara
+bn = Bengali
+bo = Tibetan
+br = Breton
+bs = Bosnian
+ca = Catalan
+ce = Chechen
+ch = Chamorro
+co = Corsican
+cr = Cree
+cs = Czech
+csb = Kashubian
+cu = Church Slavic
+cv = Chuvash
+cy = Welsh
+da = Danish
+de = German
+dsb = Lower Sorbian
+dv = Divehi
+dz = Dzongkha
+ee = Ewe
+el = Greek
+en = English
+eo = Esperanto
+es = Spanish
+et = Estonian
+eu = Basque
+fa = Persian
+ff = Fulah
+fi = Finnish
+fj = Fijian
+fo = Faroese
+fr = French
+fur = Friulian
+fy = Frisian
+ga = Irish
+gd = Scottish Gaelic
+gl = Galician
+gn = Guarani
+gu = Gujarati
+gv = Manx
+ha = Hausa
+haw = Hawaiian
+he = Hebrew
+hi = Hindi
+hil = Hiligaynon
+ho = Hiri Motu
+hr = Croatian
+hsb = Upper Sorbian
+ht = Haitian
+hu = Hungarian
+hy = Armenian
+hz = Herero
+ia = Interlingua
+id = Indonesian
+ie = Interlingue
+ig = Igbo
+ii = Sichuan Yi
+ik = Inupiaq
+io = Ido
+is = Icelandic
+it = Italian
+iu = Inuktitut
+ja = Japanese
+jv = Javanese
+ka = Georgian
+kg = Kongo
+ki = Kikuyu
+kj = Kuanyama
+kk = Kazakh
+kl = Greenlandic
+km = Khmer
+kn = Kannada
+ko = Korean
+kok = Konkani
+kr = Kanuri
+ks = Kashmiri
+ku = Kurdish
+kv = Komi
+kw = Cornish
+ky = Kirghiz
+la = Latin
+lb = Luxembourgish
+lg = Ganda
+li = Limburgan
+ln = Lingala
+lo = Lao
+lt = Lithuanian
+lu = Luba-Katanga
+lv = Latvian
+mg = Malagasy
+mh = Marshallese
+mi = Maori
+mk = Macedonian
+ml = Malayalam
+mn = Mongolian
+mr = Marathi
+ms = Malay
+mt = Maltese
+my = Burmese
+na = Nauru
+nb = Norwegian Bokm\u00e5l
+nd = Ndebele, North
+ne = Nepali
+ng = Ndonga
+nl = Dutch
+nn = Norwegian Nynorsk
+no = Norwegian
+nr = Ndebele, South
+nso = Sotho, Northern
+nv = Navajo
+ny = Chichewa
+oc = Occitan
+oj = Ojibwa
+om = Oromo
+or = Odia
+os = Ossetian
+pa = Punjabi
+pi = Pali
+pl = Polish
+ps = Pashto
+pt = Portuguese
+qu = Quechua
+rm = Rhaeto-Romanic
+rn = Kirundi
+ro = Romanian
+ru = Russian
+rw = Kinyarwanda
+sa = Sanskrit
+sc = Sardinian
+sd = Sindhi
+se = Northern Sami
+sg = Sango
+si = Singhalese
+sk = Slovak
+sl = Slovenian
+sm = Samoan
+sn = Shona
+so = Somali
+son = Songhay
+sq = Albanian
+sr = Serbian
+ss = Siswati
+st = Sotho, Southern
+su = Sundanese
+sv = Swedish
+sw = Swahili
+ta = Tamil
+te = Telugu
+tg = Tajik
+th = Thai
+ti = Tigrinya
+tig = Tigre
+tk = Turkmen
+tl = Tagalog
+tlh = Klingon
+tn = Tswana
+to = Tonga
+tr = Turkish
+ts = Tsonga
+tt = Tatar
+tw = Twi
+ty = Tahitian
+ug = Uighur
+uk = Ukrainian
+ur = Urdu
+uz = Uzbek
+ve = Venda
+vi = Vietnamese
+vo = Volap\u00fck
+wa = Walloon
+wen = Sorbian
+wo = Wolof
+xh = Xhosa
+yi = Yiddish
+yo = Yoruba
+za = Zhuang
+zh = Chinese
+zu = Zulu
diff --git a/components/global/locale/narrate.properties b/components/global/locale/narrate.properties
new file mode 100644
index 000000000..9ac839427
--- /dev/null
+++ b/components/global/locale/narrate.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/.
+
+# Narrate, meaning "read the page out loud". This is the name of the feature
+# and it is the label for the popup button.
+narrate = Narrate
+back = Back
+start = Start
+stop = Stop
+forward = Forward
+speed = Speed
+selectvoicelabel = Voice:
+# Default voice is determined by the language of the document.
+defaultvoice = Default
+
+# Voice name and language.
+# eg. David (English)
+voiceLabel = %S (%S) \ No newline at end of file
diff --git a/components/global/locale/notification.dtd b/components/global/locale/notification.dtd
new file mode 100644
index 000000000..75aea0b98
--- /dev/null
+++ b/components/global/locale/notification.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/. -->
+
+<!ENTITY closeNotification.tooltip "Close this message">
+
+<!ENTITY closeNotificationItem.label "Not Now">
+
+<!ENTITY checkForUpdates "Check for updates…">
+
+<!ENTITY learnMore "Learn more…">
diff --git a/components/global/locale/nsTreeSorting.properties b/components/global/locale/nsTreeSorting.properties
new file mode 100644
index 000000000..49629a838
--- /dev/null
+++ b/components/global/locale/nsTreeSorting.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/.
+
+SortMenuItems=Sorted by %COLNAME%
diff --git a/components/global/locale/preferences.dtd b/components/global/locale/preferences.dtd
new file mode 100644
index 000000000..4824e5c06
--- /dev/null
+++ b/components/global/locale/preferences.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 windowClose.key "w">
+<!ENTITY preferencesDefaultTitleMac.title "Preferences">
+<!ENTITY preferencesDefaultTitleWin.title "Preferences">
+<!ENTITY preferencesCloseButton.label "Close">
+<!ENTITY preferencesCloseButton.accesskey "C">
diff --git a/components/global/locale/printPageSetup.dtd b/components/global/locale/printPageSetup.dtd
new file mode 100644
index 000000000..601d9a602
--- /dev/null
+++ b/components/global/locale/printPageSetup.dtd
@@ -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/. -->
+
+<!-- extracted from printjoboptions.xul -->
+
+<!ENTITY printSetup.title "Page Setup">
+
+<!ENTITY basic.tab "Format &amp; Options">
+
+<!ENTITY formatGroup.label "Format">
+
+<!ENTITY orientation.label "Orientation:">
+<!ENTITY portrait.label "Portrait">
+<!ENTITY portrait.accesskey "P">
+<!ENTITY landscape.label "Landscape">
+<!ENTITY landscape.accesskey "L">
+
+<!ENTITY scale.label "Scale:">
+<!ENTITY scale.accesskey "S">
+<!ENTITY scalePercent "&#037;">
+
+<!ENTITY shrinkToFit.label "Shrink to fit Page Width">
+<!ENTITY shrinkToFit.accesskey "W">
+
+<!ENTITY optionsGroup.label "Options">
+
+<!ENTITY printBG.label "Print Background (colors &amp; images)">
+<!ENTITY printBG.accesskey "B">
+
+<!ENTITY advanced.tab "Margins &amp; Header/Footer">
+
+<!ENTITY marginGroup.label "Margins (#1)">
+<!ENTITY marginUnits.inches "inches">
+<!ENTITY marginUnits.metric "millimeters">
+<!ENTITY marginTop.label "Top:">
+<!ENTITY marginTop.accesskey "T">
+<!ENTITY marginBottom.label "Bottom:">
+<!ENTITY marginBottom.accesskey "B">
+<!ENTITY marginLeft.label "Left:">
+<!ENTITY marginLeft.accesskey "L">
+<!ENTITY marginRight.label "Right:">
+<!ENTITY marginRight.accesskey "R">
+
+<!ENTITY headerFooter.label "Headers &amp; Footers">
+
+<!ENTITY hfLeft.label "Left:">
+<!ENTITY hfCenter.label "Center:">
+<!ENTITY hfRight.label "Right:">
+<!ENTITY headerLeft.tip "Left header">
+<!ENTITY headerCenter.tip "Center header">
+<!ENTITY headerRight.tip "Right header">
+<!ENTITY footerLeft.tip "Left footer">
+<!ENTITY footerCenter.tip "Center footer">
+<!ENTITY footerRight.tip "Right footer">
+
+<!ENTITY hfTitle "Title">
+<!ENTITY hfURL "URL">
+<!ENTITY hfDateAndTime "Date/Time">
+<!ENTITY hfPage "Page #">
+<!ENTITY hfPageAndTotal "Page # of #">
+<!ENTITY hfBlank "--blank--">
+<!ENTITY hfCustom "Custom…">
+
+<!ENTITY customPrompt.title "Custom…">
+<!ENTITY customPrompt.prompt "Enter your custom header/footer text">
diff --git a/components/global/locale/printPreview.dtd b/components/global/locale/printPreview.dtd
new file mode 100644
index 000000000..8e68fde25
--- /dev/null
+++ b/components/global/locale/printPreview.dtd
@@ -0,0 +1,43 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY print.label "Print…">
+<!ENTITY print.accesskey "P">
+<!ENTITY pageSetup.label "Page Setup…">
+<!ENTITY pageSetup.accesskey "u">
+<!ENTITY page.label "Page:">
+<!ENTITY page.accesskey "a">
+<!ENTITY of.label "of">
+<!ENTITY scale.label "Scale:">
+<!ENTITY scale.accesskey "S">
+<!ENTITY portrait.label "Portrait">
+<!ENTITY portrait.accesskey "o">
+<!ENTITY landscape.label "Landscape">
+<!ENTITY landscape.accesskey "L">
+<!ENTITY close.label "Close">
+<!ENTITY close.accesskey "C">
+<!ENTITY p30.label "30&#037;">
+<!ENTITY p40.label "40&#037;">
+<!ENTITY p50.label "50&#037;">
+<!ENTITY p60.label "60&#037;">
+<!ENTITY p70.label "70&#037;">
+<!ENTITY p80.label "80&#037;">
+<!ENTITY p90.label "90&#037;">
+<!ENTITY p100.label "100&#037;">
+<!ENTITY p125.label "125&#037;">
+<!ENTITY p150.label "150&#037;">
+<!ENTITY p175.label "175&#037;">
+<!ENTITY p200.label "200&#037;">
+<!ENTITY Custom.label "Custom…">
+<!ENTITY ShrinkToFit.label "Shrink To Fit">
+<!ENTITY customPrompt.title "Custom Scale…">
+<!ENTITY simplifyPage.label "Simplify Page">
+<!ENTITY simplifyPage.accesskey "i">
+<!ENTITY simplifyPage.enabled.tooltip "Change layout for easier reading">
+<!ENTITY simplifyPage.disabled.tooltip "This page cannot be automatically simplified">
+
+<!ENTITY homearrow.tooltip "First page">
+<!ENTITY endarrow.tooltip "Last page">
+<!ENTITY nextarrow.tooltip "Next page">
+<!ENTITY previousarrow.tooltip "Previous page">
diff --git a/components/global/locale/printPreviewProgress.dtd b/components/global/locale/printPreviewProgress.dtd
new file mode 100644
index 000000000..344674c16
--- /dev/null
+++ b/components/global/locale/printPreviewProgress.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/. -->
+
+<!--LOCALIZATION NOTE printPreviewProgress.dtd Main UI for Print Preview Progress Dialog -->
+<!ENTITY printWindow.title "Print Preview">
+<!ENTITY title "Title:">
+<!ENTITY preparing "Preparing…">
+<!ENTITY progress "Progress:">
diff --git a/components/global/locale/printProgress.dtd b/components/global/locale/printProgress.dtd
new file mode 100644
index 000000000..30bfe0292
--- /dev/null
+++ b/components/global/locale/printProgress.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 printProgress.dtd Main UI for Print Progress Dialog -->
+<!ENTITY printWindow.title "Printing">
+<!ENTITY title "Title:">
+<!ENTITY progress "Progress:">
+<!ENTITY preparing "Preparing…">
+<!ENTITY printComplete "Printing is Completed.">
+
+<!ENTITY dialogCancel.label "Cancel">
+<!ENTITY dialogClose.label "Close">
+
+<!-- LOCALIZATION NOTE (percentPrint):
+
+ This string is used to format the text to the right of the progress
+ meter.
+
+ #1 will be replaced by the percentage of the file that has been saved -->
+<!ENTITY percentPrint "#1&#037;">
diff --git a/components/global/locale/printdialog.dtd b/components/global/locale/printdialog.dtd
new file mode 100644
index 000000000..847999795
--- /dev/null
+++ b/components/global/locale/printdialog.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/. -->
+
+<!-- extracted from printdialog.xul -->
+
+<!ENTITY printButton.label "Print">
+
+<!ENTITY printDialog.title "Print">
+
+<!ENTITY fpDialog.title "Save File">
+
+<!ENTITY fileCheck.label "Print to File">
+<!ENTITY fileCheck.accesskey "F">
+<!ENTITY propertiesButton.label "Properties…">
+<!ENTITY propertiesButton.accesskey "o">
+<!ENTITY descText.label "Printer Description:">
+<!ENTITY printer.label "Printer">
+<!ENTITY printerInput.label "Printer Name:">
+<!ENTITY printerInput.accesskey "N">
+
+<!ENTITY printrangeGroup.label "Print Range">
+<!ENTITY allpagesRadio.label "All Pages">
+<!ENTITY allpagesRadio.accesskey "A">
+<!ENTITY rangeRadio.label "Pages">
+<!ENTITY rangeRadio.accesskey "P">
+<!ENTITY frompageInput.label "from">
+<!ENTITY frompageInput.accesskey "r">
+<!ENTITY topageInput.label "to">
+<!ENTITY topageInput.accesskey "t">
+<!ENTITY selectionRadio.label "Selection">
+<!ENTITY selectionRadio.accesskey "S">
+
+<!ENTITY copies.label "Copies">
+<!ENTITY numCopies.label "Number of copies:">
+<!ENTITY numCopies.accesskey "c">
+
+<!ENTITY printframeGroup.label "Print Frames">
+<!ENTITY aslaidoutRadio.label "As laid out on the screen">
+<!ENTITY aslaidoutRadio.accesskey "u">
+<!ENTITY selectedframeRadio.label "The selected frame">
+<!ENTITY selectedframeRadio.accesskey "m">
+<!ENTITY eachframesepRadio.label "Each frame separately">
+<!ENTITY eachframesepRadio.accesskey "E">
diff --git a/components/global/locale/printdialog.properties b/components/global/locale/printdialog.properties
new file mode 100644
index 000000000..ae583eda5
--- /dev/null
+++ b/components/global/locale/printdialog.properties
@@ -0,0 +1,63 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# These strings are used in the native GTK, Mac and Windows print dialogs.
+
+# GTK titles:
+printTitleGTK=Print
+optionsTabLabelGTK=Options
+printFramesTitleGTK=Print Frames
+
+# Mac titles:
+optionsTitleMac=Options:
+appearanceTitleMac=Appearance:
+framesTitleMac=Frames:
+pageHeadersTitleMac=Page Headers:
+pageFootersTitleMac=Page Footers:
+
+# Windows titles:
+optionsTitleWindows=Options
+printFramesTitleWindows=Print Frames
+
+# TRANSLATOR NOTE: For radio button labels and check button labels, an underscore _
+# before a character will turn that character into an accesskey in the GTK dialog.
+# e.g. "_As laid out" will make A the accesskey.
+# In the Windows labels, use an ampersand (&).
+# On Mac, underscores will be stripped.
+
+asLaidOut=_As Laid Out on the Screen
+asLaidOutWindows=As &laid out on the screen
+selectedFrame=The _Selected Frame
+selectedFrameWindows=The selected &frame
+separateFrames=Each Frame on Separate _Pages
+separateFramesWindows=&Each frame separately
+shrinkToFit=Ignore Scaling and S_hrink To Fit Page Width
+selectionOnly=Print Selection _Only
+printBGOptions=Print Backgrounds
+printBGColors=Print Background _Colors
+printBGImages=Print Background I_mages
+headerFooter=Header and Footer
+left=Left
+center=Center
+right=Right
+headerFooterBlank=--blank--
+headerFooterTitle=Title
+headerFooterURL=URL
+headerFooterDate=Date/Time
+headerFooterPage=Page #
+headerFooterPageTotal=Page # of #
+headerFooterCustom=Custom…
+customHeaderFooterPrompt=Please enter your custom header/footer text
+
+# These are for the summary view in the Mac dialog:
+summaryFramesTitle=Print Frames
+summarySelectionOnlyTitle=Print Selection
+summaryShrinkToFitTitle=Shrink To Fit
+summaryPrintBGColorsTitle=Print BG Colors
+summaryPrintBGImagesTitle=Print BG Images
+summaryHeaderTitle=Page Headers
+summaryFooterTitle=Page Footers
+summaryNAValue=N/A
+summaryOnValue=On
+summaryOffValue=Off
diff --git a/components/global/locale/printjoboptions.dtd b/components/global/locale/printjoboptions.dtd
new file mode 100644
index 000000000..08b579416
--- /dev/null
+++ b/components/global/locale/printjoboptions.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/. -->
+
+<!-- extracted from printjoboptions.xul -->
+
+<!ENTITY printJobOptions.title "Printer Properties">
+
+<!ENTITY paperInput.label "Paper Size:">
+<!ENTITY paperInput.accesskey "P">
+
+<!ENTITY jobTitleInput.label "Job Title:">
+<!ENTITY jobTitleInput.accesskey "J">
+
+<!ENTITY colorGroup.label "Color:">
+<!ENTITY grayRadio.label "Grayscale">
+<!ENTITY grayRadio.accesskey "G">
+<!ENTITY colorRadio.label "Color">
+<!ENTITY colorRadio.accesskey "C">
+
+<!ENTITY edgeMarginInput.label "Gap from edge of paper to Margin">
+<!ENTITY topInput.label "Top:">
+<!ENTITY topInput.accesskey "T">
+<!ENTITY bottomInput.label "Bottom:">
+<!ENTITY bottomInput.accesskey "B">
+<!ENTITY leftInput.label "Left:">
+<!ENTITY leftInput.accesskey "L">
+<!ENTITY rightInput.label "Right:">
+<!ENTITY rightInput.accesskey "R">
diff --git a/components/global/locale/regionNames.properties b/components/global/locale/regionNames.properties
new file mode 100644
index 000000000..29287a65f
--- /dev/null
+++ b/components/global/locale/regionNames.properties
@@ -0,0 +1,276 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ad = Andorra
+ae = United Arab Emirates
+af = Afghanistan
+ag = Antigua and Barbuda
+ai = Anguilla
+al = Albania
+am = Armenia
+ao = Angola
+aq = Antarctica
+ar = Argentina
+as = American Samoa
+at = Austria
+au = Australia
+aw = Aruba
+az = Azerbaijan
+ba = Bosnia and Herzegovina
+bb = Barbados
+bd = Bangladesh
+be = Belgium
+bf = Burkina Faso
+bg = Bulgaria
+bh = Bahrain
+bi = Burundi
+bj = Benin
+bl = Saint Barthelemy
+bm = Bermuda
+bn = Brunei
+bo = Bolivia
+bq = Bonaire, Sint Eustatius, and Saba
+br = Brazil
+bs = Bahamas, The
+bt = Bhutan
+bv = Bouvet Island
+bw = Botswana
+by = Belarus
+bz = Belize
+ca = Canada
+cc = Cocos (Keeling) Islands
+cd = Congo (Kinshasa)
+cf = Central African Republic
+cg = Congo (Brazzaville)
+ch = Switzerland
+ci = Côte d’Ivoire
+ck = Cook Islands
+cl = Chile
+cm = Cameroon
+cn = China
+co = Colombia
+cp = Clipperton Island
+cr = Costa Rica
+cu = Cuba
+cv = Cabo Verde
+cw = Curaçao
+cx = Christmas Island
+cy = Cyprus
+cz = Czech Republic
+de = Germany
+dg = Diego Garcia
+dj = Djibouti
+dk = Denmark
+dm = Dominica
+do = Dominican Republic
+dz = Algeria
+ec = Ecuador
+ee = Estonia
+eg = Egypt
+eh = Western Sahara
+er = Eritrea
+es = Spain
+et = Ethiopia
+fi = Finland
+fj = Fiji
+fk = Falkland Islands (Islas Malvinas)
+fm = Micronesia, Federated States of
+fo = Faroe Islands
+fr = France
+ga = Gabon
+gb = United Kingdom
+gd = Grenada
+ge = Georgia
+gf = French Guiana
+gg = Guernsey
+gh = Ghana
+gi = Gibraltar
+gl = Greenland
+gm = Gambia, The
+gn = Guinea
+gp = Guadeloupe
+gq = Equatorial Guinea
+gr = Greece
+gs = South Georgia and South Sandwich Islands
+gt = Guatemala
+gu = Guam
+gw = Guinea-Bissau
+gy = Guyana
+hk = Hong Kong
+hm = Heard Island and McDonald Islands
+hn = Honduras
+hr = Croatia
+ht = Haiti
+hu = Hungary
+id = Indonesia
+ie = Ireland
+il = Israel
+im = Isle of Man
+in = India
+io = British Indian Ocean Territory
+iq = Iraq
+ir = Iran
+is = Iceland
+it = Italy
+je = Jersey
+jm = Jamaica
+jo = Jordan
+jp = Japan
+ke = Kenya
+kg = Kyrgyzstan
+kh = Cambodia
+ki = Kiribati
+km = Comoros
+kn = Saint Kitts and Nevis
+kp = Korea, North
+kr = Korea, South
+kw = Kuwait
+ky = Cayman Islands
+kz = Kazakhstan
+la = Laos
+lb = Lebanon
+lc = Saint Lucia
+li = Liechtenstein
+lk = Sri Lanka
+lr = Liberia
+ls = Lesotho
+lt = Lithuania
+lu = Luxembourg
+lv = Latvia
+ly = Libya
+ma = Morocco
+mc = Monaco
+md = Moldova
+me = Montenegro
+mf = Saint Martin
+mg = Madagascar
+mh = Marshall Islands
+mk = Macedonia
+ml = Mali
+mm = Burma
+mn = Mongolia
+mo = Macau
+mp = Northern Mariana Islands
+mq = Martinique
+mr = Mauritania
+ms = Montserrat
+mt = Malta
+mu = Mauritius
+mv = Maldives
+mw = Malawi
+mx = Mexico
+my = Malaysia
+mz = Mozambique
+na = Namibia
+nc = New Caledonia
+ne = Niger
+nf = Norfolk Island
+ng = Nigeria
+ni = Nicaragua
+nl = Netherlands
+no = Norway
+np = Nepal
+nr = Nauru
+nu = Niue
+nz = New Zealand
+om = Oman
+pa = Panama
+pe = Peru
+pf = French Polynesia
+pg = Papua New Guinea
+ph = Philippines
+pk = Pakistan
+pl = Poland
+pm = Saint Pierre and Miquelon
+pn = Pitcairn Islands
+pr = Puerto Rico
+pt = Portugal
+pw = Palau
+py = Paraguay
+qa = Qatar
+qm = Midway Islands
+qs = Bassas da India
+qu = Juan de Nova Island
+qw = Wake Island
+qx = Glorioso Islands
+qz = Akrotiri
+re = Reunion
+ro = Romania
+rs = Serbia
+ru = Russia
+rw = Rwanda
+sa = Saudi Arabia
+sb = Solomon Islands
+sc = Seychelles
+sd = Sudan
+se = Sweden
+sg = Singapore
+sh = Saint Helena, Ascension, and Tristan da Cunha
+si = Slovenia
+sk = Slovakia
+sl = Sierra Leone
+sm = San Marino
+sn = Senegal
+so = Somalia
+sr = Suriname
+ss = South Sudan
+st = Sao Tome and Principe
+sv = El Salvador
+sx = Sint Maarten
+sy = Syria
+sz = Swaziland
+tc = Turks and Caicos Islands
+td = Chad
+tf = French Southern and Antarctic Lands
+tg = Togo
+th = Thailand
+tj = Tajikistan
+tk = Tokelau
+tl = Timor-Leste
+tm = Turkmenistan
+tn = Tunisia
+to = Tonga
+tr = Turkey
+tt = Trinidad and Tobago
+tv = Tuvalu
+tw = Taiwan
+tz = Tanzania
+ua = Ukraine
+ug = Uganda
+us = United States
+uy = Uruguay
+uz = Uzbekistan
+va = Vatican City
+vc = Saint Vincent and the Grenadines
+ve = Venezuela
+vg = Virgin Islands, British
+vi = Virgin Islands, U.S.
+vn = Vietnam
+vu = Vanuatu
+wf = Wallis and Futuna
+ws = Samoa
+xa = Ashmore and Cartier Islands
+xb = Baker Island
+xc = Coral Sea Islands
+xd = Dhekelia
+xe = Europa Island
+xg = Gaza Strip
+xh = Howland Island
+xj = Jan Mayen
+xk = Kosovo
+xl = Palmyra Atoll
+xm = Kingman Reef
+xp = Paracel Islands
+xq = Jarvis Island
+xr = Svalbard
+xs = Spratly Islands
+xt = Tromelin Island
+xu = Johnston Atoll
+xv = Navassa Island
+xw = West Bank
+ye = Yemen
+yt = Mayotte
+za = South Africa
+zm = Zambia
+zw = Zimbabwe
diff --git a/components/global/locale/resetProfile.dtd b/components/global/locale/resetProfile.dtd
new file mode 100644
index 000000000..831b901c5
--- /dev/null
+++ b/components/global/locale/resetProfile.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 refreshProfile.dialog.title "Refresh &brandShortName;">
+<!ENTITY refreshProfile.dialog.description1 "Start fresh to fix problems and restore performance.">
+<!ENTITY refreshProfile.dialog.description2 "This will:">
+<!ENTITY refreshProfile.dialog.items.label1 "Remove your add-ons and customizations">
+<!ENTITY refreshProfile.dialog.items.label2 "Restore your browser settings to their defaults">
+<!ENTITY refreshProfile.dialog.button.label "Refresh &brandShortName;">
+
+<!ENTITY refreshProfile.title "Give &brandShortName; a tune up">
+<!ENTITY refreshProfile.button.label "Refresh &brandShortName;…">
+
+<!ENTITY refreshProfile.cleaning.description "Almost done…">
diff --git a/components/global/locale/resetProfile.properties b/components/global/locale/resetProfile.properties
new file mode 100644
index 000000000..c06007299
--- /dev/null
+++ b/components/global/locale/resetProfile.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: These strings are used for profile reset.
+
+# LOCALIZATION NOTE (resetUnusedProfile.message): %S is brandShortName.
+resetUnusedProfile.message=It looks like you haven’t started %S in a while. Do you want to clean it up for a fresh, like-new experience? And by the way, welcome back!
+# LOCALIZATION NOTE (resetUninstalled.message): %S is brandShortName.
+resetUninstalled.message=Looks like you’ve reinstalled %S. Want us to clean it up for a fresh, like-new experience?
+
+# LOCALIZATION NOTE (refreshProfile.resetButton.label): %S is brandShortName.
+refreshProfile.resetButton.label=Refresh %S…
+refreshProfile.resetButton.accesskey=e
diff --git a/components/global/locale/textcontext.dtd b/components/global/locale/textcontext.dtd
new file mode 100644
index 000000000..4ab18cbd8
--- /dev/null
+++ b/components/global/locale/textcontext.dtd
@@ -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/. -->
+
+<!ENTITY cutCmd.label "Cut">
+<!ENTITY cutCmd.accesskey "t">
+<!ENTITY copyCmd.label "Copy">
+<!ENTITY copyCmd.accesskey "c">
+<!ENTITY pasteCmd.label "Paste">
+<!ENTITY pasteCmd.accesskey "p">
+<!ENTITY undoCmd.label "Undo">
+<!ENTITY undoCmd.accesskey "u">
+<!ENTITY selectAllCmd.label "Select All">
+<!ENTITY selectAllCmd.accesskey "a">
+<!ENTITY deleteCmd.label "Delete">
+<!ENTITY deleteCmd.accesskey "d">
+
+<!ENTITY spellAddToDictionary.label "Add to Dictionary">
+<!ENTITY spellAddToDictionary.accesskey "o">
+<!ENTITY spellUndoAddToDictionary.label "Undo Add To Dictionary">
+<!ENTITY spellUndoAddToDictionary.accesskey "n">
+<!ENTITY spellCheckToggle.label "Check Spelling">
+<!ENTITY spellCheckToggle.accesskey "g">
+<!ENTITY spellNoSuggestions.label "(No Spelling Suggestions)">
+<!ENTITY spellDictionaries.label "Languages">
+<!ENTITY spellDictionaries.accesskey "l">
+
+<!ENTITY searchTextBox.clear.label "Clear">
+
+<!ENTITY fillLoginMenu.label "Fill Login">
+<!ENTITY fillLoginMenu.accesskey "F">
+<!ENTITY fillPasswordMenu.label "Fill Password">
+<!ENTITY fillPasswordMenu.accesskey "F">
+<!ENTITY fillUsernameMenu.label "Fill Username">
+<!ENTITY fillUsernameMenu.accesskey "F">
+<!ENTITY noLoginSuggestions.label "(No Login Suggestions)">
+<!ENTITY viewSavedLogins.label "View Saved Logins">
diff --git a/components/global/locale/tree.dtd b/components/global/locale/tree.dtd
new file mode 100644
index 000000000..7922852f5
--- /dev/null
+++ b/components/global/locale/tree.dtd
@@ -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/. -->
+
+<!ENTITY restoreColumnOrder.label "Restore Column Order">
diff --git a/components/global/locale/unix/intl.properties b/components/global/locale/unix/intl.properties
new file mode 100644
index 000000000..71265a9ef
--- /dev/null
+++ b/components/global/locale/unix/intl.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/.
+
+# LOCALIZATION NOTE (intl.ellipsis): Use the unicode ellipsis char, \u2026,
+# or use "..." if \u2026 doesn't suit traditions in your locale.
+intl.ellipsis=…
diff --git a/components/global/locale/unix/platformKeys.properties b/components/global/locale/unix/platformKeys.properties
new file mode 100644
index 000000000..53321356e
--- /dev/null
+++ b/components/global/locale/unix/platformKeys.properties
@@ -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/.
+
+#default
+#this file defines the on screen display names for the various modifier keys
+#these are used in XP menus to show keyboard shortcuts
+
+#the shift key
+VK_SHIFT=Shift
+
+#the command key
+VK_META=Meta
+
+#the win key (Super key and Hyper keys are mapped to DOM Win key)
+VK_WIN=Win
+
+#the alt key
+VK_ALT=Alt
+
+#the control key
+VK_CONTROL=Ctrl
+
+#the separator character used between modifiers
+MODIFIER_SEPARATOR=+
diff --git a/components/global/locale/videocontrols.dtd b/components/global/locale/videocontrols.dtd
new file mode 100644
index 000000000..7f3b6eecb
--- /dev/null
+++ b/components/global/locale/videocontrols.dtd
@@ -0,0 +1,39 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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 playButton.playLabel "Play">
+<!ENTITY playButton.pauseLabel "Pause">
+<!ENTITY muteButton.muteLabel "Mute">
+<!ENTITY muteButton.unmuteLabel "Unmute">
+<!ENTITY fullscreenButton.enterfullscreenlabel "Full Screen">
+<!ENTITY fullscreenButton.exitfullscreenlabel "Exit Full Screen">
+<!ENTITY castingButton.castingLabel "Cast to Screen">
+<!ENTITY closedCaption.off "Off">
+
+<!ENTITY stats.media "Media">
+<!ENTITY stats.size "Size">
+<!ENTITY stats.activity "Activity">
+<!ENTITY stats.activityPaused "Paused">
+<!ENTITY stats.activityPlaying "Playing">
+<!ENTITY stats.activityEnded "Ended">
+<!ENTITY stats.activitySeeking "(seeking)">
+<!ENTITY stats.volume "Volume">
+<!ENTITY stats.framesParsed "Frames parsed">
+<!ENTITY stats.framesDecoded "Frames decoded">
+<!ENTITY stats.framesPresented "Frames presented">
+<!ENTITY stats.framesPainted "Frames painted">
+
+<!ENTITY error.aborted "Video loading stopped.">
+<!ENTITY error.network "Video playback aborted due to a network error.">
+<!ENTITY error.decode "Video can’t be played because the file is corrupt.">
+<!ENTITY error.srcNotSupported "Video format or MIME type is not supported.">
+<!ENTITY error.noSource2 "No video with supported format and MIME type found.">
+<!ENTITY error.generic "Video playback aborted due to an unknown error.">
+
+<!-- LOCALIZATION NOTE (scrubberScale.nameFormat): the #1 string is the current
+media position, and the #2 string is the total duration. For example, when at
+the 5 minute mark in a 6 hour long video, #1 would be "5:00" and #2 would be
+"6:00:00", result string would be "5:00 of 6:00:00 elapsed".
+-->
+<!ENTITY scrubberScale.nameFormat "#1 of #2 elapsed">
diff --git a/components/global/locale/viewSource.dtd b/components/global/locale/viewSource.dtd
new file mode 100644
index 000000000..1a294b877
--- /dev/null
+++ b/components/global/locale/viewSource.dtd
@@ -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/. -->
+
+<!-- extracted from content/viewSource.xul -->
+
+<!-- LOCALIZATION NOTE (mainWindow.title) : DONT_TRANSLATE -->
+<!ENTITY mainWindow.title "&brandFullName;">
+<!-- LOCALIZATION NOTE (mainWindow.titlemodifier) : DONT_TRANSLATE -->
+<!ENTITY mainWindow.titlemodifier "&brandFullName;">
+<!-- LOCALIZATION NOTE (mainWindow.titlemodifierseparator) : DONT_TRANSLATE -->
+<!ENTITY mainWindow.titlemodifierseparator " - ">
+<!ENTITY mainWindow.preface "Source of: ">
+
+<!ENTITY fileMenu.label "File">
+<!ENTITY fileMenu.accesskey "F">
+<!ENTITY savePageCmd.label "Save Page As…">
+<!ENTITY savePageCmd.accesskey "A">
+<!ENTITY savePageCmd.commandkey "S">
+<!ENTITY pageSetupCmd.label "Page Setup…">
+<!ENTITY pageSetupCmd.accesskey "u">
+<!ENTITY printPreviewCmd.label "Print Preview">
+<!ENTITY printPreviewCmd.accesskey "v">
+<!ENTITY printCmd.label "Print…">
+<!ENTITY printCmd.accesskey "P">
+<!ENTITY printCmd.commandkey "P">
+<!ENTITY closeCmd.label "Close">
+<!ENTITY closeCmd.accesskey "C">
+<!ENTITY closeCmd.commandkey "W">
+
+<!-- LOCALIZATION NOTE :
+textEnlarge.commandkey3, textReduce.commandkey2 and
+textReset.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 textEnlarge.commandkey "+">
+<!ENTITY textEnlarge.commandkey2 "=">
+<!ENTITY textEnlarge.commandkey3 "">
+<!ENTITY textReduce.commandkey "-">
+<!ENTITY textReduce.commandkey2 "">
+<!ENTITY textReset.commandkey "0">
+<!ENTITY textReset.commandkey2 "">
+
+<!ENTITY goToLineCmd.label "Go to Line…">
+<!ENTITY goToLineCmd.accesskey "G">
+<!ENTITY goToLineCmd.commandkey "l">
+
+<!ENTITY viewMenu.label "View">
+<!ENTITY viewMenu.accesskey "V">
+<!ENTITY reloadCmd.label "Reload">
+<!ENTITY reloadCmd.accesskey "R">
+<!ENTITY reloadCmd.commandkey "r">
+<!ENTITY menu_wrapLongLines.title "Wrap Long Lines">
+<!ENTITY menu_wrapLongLines.accesskey "W">
+<!ENTITY menu_highlightSyntax.label "Syntax Highlighting">
+<!ENTITY menu_highlightSyntax.accesskey "H">
+<!ENTITY menu_textSize.label "Text Size">
+<!ENTITY menu_textSize.accesskey "Z">
+<!ENTITY menu_textEnlarge.label "Increase">
+<!ENTITY menu_textEnlarge.accesskey "I">
+<!ENTITY menu_textReduce.label "Decrease">
+<!ENTITY menu_textReduce.accesskey "D">
+<!ENTITY menu_textReset.label "Normal">
+<!ENTITY menu_textReset.accesskey "N">
+
+<!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 findSelectionCmd.commandkey "e">
+
+<!ENTITY backCmd.label "Back">
+<!ENTITY backCmd.accesskey "B">
+<!ENTITY forwardCmd.label "Forward">
+<!ENTITY forwardCmd.accesskey "F">
+<!ENTITY goBackCmd.commandKey "[">
+<!ENTITY goForwardCmd.commandKey "]">
+
+<!ENTITY copyLinkCmd.label "Copy Link Location">
+<!ENTITY copyLinkCmd.accesskey "L">
+<!ENTITY copyEmailCmd.label "Copy Email Address">
+<!ENTITY copyEmailCmd.accesskey "E">
diff --git a/components/global/locale/viewSource.properties b/components/global/locale/viewSource.properties
new file mode 100644
index 000000000..9137b383a
--- /dev/null
+++ b/components/global/locale/viewSource.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/.
+
+goToLineTitle = Go to line
+goToLineText = Enter line number
+invalidInputTitle = Invalid input
+invalidInputText = The line number entered is invalid.
+outOfRangeTitle = Line not found
+outOfRangeText = The specified line was not found.
+statusBarLineCol = Line %1$S, Col %2$S
+viewSelectionSourceTitle = DOM Source of Selection
+viewMathMLSourceTitle = DOM Source of MathML
+
+context_goToLine_label = Go to Line…
+context_goToLine_accesskey = L
+context_wrapLongLines_label = Wrap Long Lines
+context_highlightSyntax_label = Syntax Highlighting
diff --git a/components/global/locale/win/intl.properties b/components/global/locale/win/intl.properties
new file mode 100644
index 000000000..71265a9ef
--- /dev/null
+++ b/components/global/locale/win/intl.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/.
+
+# LOCALIZATION NOTE (intl.ellipsis): Use the unicode ellipsis char, \u2026,
+# or use "..." if \u2026 doesn't suit traditions in your locale.
+intl.ellipsis=…
diff --git a/components/global/locale/win/platformKeys.properties b/components/global/locale/win/platformKeys.properties
new file mode 100644
index 000000000..307eeaf19
--- /dev/null
+++ b/components/global/locale/win/platformKeys.properties
@@ -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/.
+
+#default
+#this file defines the on screen display names for the various modifier keys
+#these are used in XP menus to show keyboard shortcuts
+
+#the shift key
+VK_SHIFT=Shift
+
+#the command key
+VK_META=Meta
+
+#the win key
+VK_WIN=Win
+
+#the alt key
+VK_ALT=Alt
+
+#the control key
+VK_CONTROL=Ctrl
+
+#the separator character used between modifiers
+MODIFIER_SEPARATOR=+
diff --git a/components/global/locale/wizard.dtd b/components/global/locale/wizard.dtd
new file mode 100644
index 000000000..e8a7f90dc
--- /dev/null
+++ b/components/global/locale/wizard.dtd
@@ -0,0 +1,24 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY button-back-mac.label "Go Back">
+<!ENTITY button-back-mac.accesskey "B">
+<!ENTITY button-next-mac.label "Continue">
+<!ENTITY button-next-mac.accesskey "C">
+<!ENTITY button-finish-mac.label "Done">
+<!ENTITY button-cancel-mac.label "Cancel">
+
+<!ENTITY button-back-unix.label "Back">
+<!ENTITY button-back-unix.accesskey "B">
+<!ENTITY button-next-unix.label "Next">
+<!ENTITY button-next-unix.accesskey "N">
+<!ENTITY button-finish-unix.label "Finish">
+<!ENTITY button-cancel-unix.label "Cancel">
+
+<!ENTITY button-back-win.label "&lt; Back">
+<!ENTITY button-back-win.accesskey "B">
+<!ENTITY button-next-win.label "Next &gt;">
+<!ENTITY button-next-win.accesskey "N">
+<!ENTITY button-finish-win.label "Finish">
+<!ENTITY button-cancel-win.label "Cancel">
diff --git a/components/global/locale/wizard.properties b/components/global/locale/wizard.properties
new file mode 100644
index 000000000..99cc332e7
--- /dev/null
+++ b/components/global/locale/wizard.properties
@@ -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/.
+
+default-first-title=Welcome to the %S
+default-last-title=Completing the %S
+default-first-title-mac=Introduction
+default-last-title-mac=Conclusion
diff --git a/components/global/moz.build b/components/global/moz.build
new file mode 100644
index 000000000..f39d55a9e
--- /dev/null
+++ b/components/global/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+for var in ('target',
+ 'MOZ_CONFIGURE_OPTIONS',
+ 'CC',
+ 'CC_VERSION',
+ 'CXX'):
+ DEFINES[var] = CONFIG[var]
+
+DEFINES['CFLAGS'] = CONFIG['OS_CFLAGS']
+DEFINES['TOPOBJDIR'] = TOPOBJDIR
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/gservice/moz.build b/components/gservice/moz.build
new file mode 100644
index 000000000..8371b94b8
--- /dev/null
+++ b/components/gservice/moz.build
@@ -0,0 +1,35 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+SOURCES += [
+ 'src/nsAlertsIconListener.cpp',
+ 'src/nsGnomeModule.cpp',
+ 'src/nsSystemAlertsService.cpp',
+]
+
+if CONFIG['MOZ_ENABLE_GCONF']:
+ SOURCES += [
+ 'src/nsGConfService.cpp',
+ ]
+
+if CONFIG['MOZ_ENABLE_GIO']:
+ SOURCES += [
+ 'src/nsGIOProtocolHandler.cpp',
+ 'src/nsGIOService.cpp',
+ 'src/nsGSettingsService.cpp',
+ 'src/nsPackageKitService.cpp'
+ ]
+
+LOCAL_INCLUDES += [
+ '../build/',
+]
+
+CXXFLAGS += CONFIG['MOZ_GCONF_CFLAGS']
+CXXFLAGS += CONFIG['MOZ_GIO_CFLAGS']
+CXXFLAGS += CONFIG['GLIB_CFLAGS']
+CXXFLAGS += CONFIG['MOZ_DBUS_GLIB_CFLAGS']
+CXXFLAGS += CONFIG['TK_CFLAGS']
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/gservice/src/nsAlertsIconListener.cpp b/components/gservice/src/nsAlertsIconListener.cpp
new file mode 100644
index 000000000..b11672d30
--- /dev/null
+++ b/components/gservice/src/nsAlertsIconListener.cpp
@@ -0,0 +1,327 @@
+/* -*- 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 "nsAlertsIconListener.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSystemAlertsService.h"
+#include "nsIAlertsService.h"
+#include "nsICancelable.h"
+#include "nsIImageToPixbuf.h"
+#include "nsIStringBundle.h"
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+#include "nsCRT.h"
+
+#include <dlfcn.h>
+#include <gdk/gdk.h>
+
+static bool gHasActions = false;
+static bool gHasCaps = false;
+
+void* nsAlertsIconListener::libNotifyHandle = nullptr;
+bool nsAlertsIconListener::libNotifyNotAvail = false;
+nsAlertsIconListener::notify_is_initted_t nsAlertsIconListener::notify_is_initted = nullptr;
+nsAlertsIconListener::notify_init_t nsAlertsIconListener::notify_init = nullptr;
+nsAlertsIconListener::notify_get_server_caps_t nsAlertsIconListener::notify_get_server_caps = nullptr;
+nsAlertsIconListener::notify_notification_new_t nsAlertsIconListener::notify_notification_new = nullptr;
+nsAlertsIconListener::notify_notification_show_t nsAlertsIconListener::notify_notification_show = nullptr;
+nsAlertsIconListener::notify_notification_set_icon_from_pixbuf_t nsAlertsIconListener::notify_notification_set_icon_from_pixbuf = nullptr;
+nsAlertsIconListener::notify_notification_add_action_t nsAlertsIconListener::notify_notification_add_action = nullptr;
+nsAlertsIconListener::notify_notification_close_t nsAlertsIconListener::notify_notification_close = nullptr;
+
+static void notify_action_cb(NotifyNotification *notification,
+ gchar *action, gpointer user_data)
+{
+ nsAlertsIconListener* alert = static_cast<nsAlertsIconListener*> (user_data);
+ alert->SendCallback();
+}
+
+static void notify_closed_marshal(GClosure* closure,
+ GValue* return_value,
+ guint n_param_values,
+ const GValue* param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data)
+{
+ MOZ_ASSERT(n_param_values >= 1, "No object in params");
+
+ nsAlertsIconListener* alert =
+ static_cast<nsAlertsIconListener*>(closure->data);
+ alert->SendClosed();
+ NS_RELEASE(alert);
+}
+
+static GdkPixbuf*
+GetPixbufFromImgRequest(imgIRequest* aRequest)
+{
+ nsCOMPtr<imgIContainer> image;
+ nsresult rv = aRequest->GetImage(getter_AddRefs(image));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIImageToPixbuf> imgToPixbuf =
+ do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
+
+ return imgToPixbuf->ConvertImageToPixbuf(image);
+}
+
+NS_IMPL_ISUPPORTS(nsAlertsIconListener, nsIAlertNotificationImageListener,
+ nsIObserver, nsISupportsWeakReference)
+
+nsAlertsIconListener::nsAlertsIconListener(nsSystemAlertsService* aBackend,
+ const nsAString& aAlertName)
+: mAlertName(aAlertName),
+ mBackend(aBackend),
+ mNotification(nullptr)
+{
+ if (!libNotifyHandle && !libNotifyNotAvail) {
+ libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY);
+ if (!libNotifyHandle) {
+ libNotifyHandle = dlopen("libnotify.so.1", RTLD_LAZY);
+ if (!libNotifyHandle) {
+ libNotifyNotAvail = true;
+ return;
+ }
+ }
+
+ notify_is_initted = (notify_is_initted_t)dlsym(libNotifyHandle, "notify_is_initted");
+ notify_init = (notify_init_t)dlsym(libNotifyHandle, "notify_init");
+ notify_get_server_caps = (notify_get_server_caps_t)dlsym(libNotifyHandle, "notify_get_server_caps");
+ notify_notification_new = (notify_notification_new_t)dlsym(libNotifyHandle, "notify_notification_new");
+ notify_notification_show = (notify_notification_show_t)dlsym(libNotifyHandle, "notify_notification_show");
+ notify_notification_set_icon_from_pixbuf = (notify_notification_set_icon_from_pixbuf_t)dlsym(libNotifyHandle, "notify_notification_set_icon_from_pixbuf");
+ notify_notification_add_action = (notify_notification_add_action_t)dlsym(libNotifyHandle, "notify_notification_add_action");
+ notify_notification_close = (notify_notification_close_t)dlsym(libNotifyHandle, "notify_notification_close");
+ if (!notify_is_initted || !notify_init || !notify_get_server_caps || !notify_notification_new || !notify_notification_show || !notify_notification_set_icon_from_pixbuf || !notify_notification_add_action || !notify_notification_close) {
+ dlclose(libNotifyHandle);
+ libNotifyHandle = nullptr;
+ }
+ }
+}
+
+nsAlertsIconListener::~nsAlertsIconListener()
+{
+ mBackend->RemoveListener(mAlertName, this);
+ // Don't dlclose libnotify as it uses atexit().
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnImageMissing(nsISupports*)
+{
+ // This notification doesn't have an image, or there was an error getting
+ // the image. Show the notification without an icon.
+ return ShowAlert(nullptr);
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnImageReady(nsISupports*, imgIRequest* aRequest)
+{
+ GdkPixbuf* imagePixbuf = GetPixbufFromImgRequest(aRequest);
+ if (!imagePixbuf) {
+ ShowAlert(nullptr);
+ } else {
+ ShowAlert(imagePixbuf);
+ g_object_unref(imagePixbuf);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf)
+{
+ if (!mBackend->IsActiveListener(mAlertName, this))
+ return NS_OK;
+
+ mNotification = notify_notification_new(mAlertTitle.get(), mAlertText.get(),
+ nullptr, nullptr);
+
+ if (!mNotification)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIObserverService> obsServ =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (obsServ)
+ obsServ->AddObserver(this, "quit-application", true);
+
+ if (aPixbuf)
+ notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf);
+
+ NS_ADDREF(this);
+ if (mAlertHasAction) {
+ // What we put as the label doesn't matter here, if the action
+ // string is "default" then that makes the entire bubble clickable
+ // rather than creating a button.
+ notify_notification_add_action(mNotification, "default", "Activate",
+ notify_action_cb, this, nullptr);
+ }
+
+ // Fedora 10 calls NotifyNotification "closed" signal handlers with a
+ // different signature, so a marshaller is used instead of a C callback to
+ // get the user_data (this) in a parseable format. |closure| is created
+ // with a floating reference, which gets sunk by g_signal_connect_closure().
+ GClosure* closure = g_closure_new_simple(sizeof(GClosure), this);
+ g_closure_set_marshal(closure, notify_closed_marshal);
+ mClosureHandler = g_signal_connect_closure(mNotification, "closed", closure, FALSE);
+ GError* error = nullptr;
+ if (!notify_notification_show(mNotification, &error)) {
+ NS_WARNING(error->message);
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mAlertListener)
+ mAlertListener->Observe(nullptr, "alertshow", mAlertCookie.get());
+
+ return NS_OK;
+}
+
+void
+nsAlertsIconListener::SendCallback()
+{
+ if (mAlertListener)
+ mAlertListener->Observe(nullptr, "alertclickcallback", mAlertCookie.get());
+}
+
+void
+nsAlertsIconListener::SendClosed()
+{
+ if (mNotification) {
+ g_object_unref(mNotification);
+ mNotification = nullptr;
+ }
+ NotifyFinished();
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData) {
+ // We need to close any open notifications upon application exit, otherwise
+ // we will leak since libnotify holds a ref for us.
+ if (!nsCRT::strcmp(aTopic, "quit-application") && mNotification) {
+ g_signal_handler_disconnect(mNotification, mClosureHandler);
+ g_object_unref(mNotification);
+ mNotification = nullptr;
+ Release(); // equivalent to NS_RELEASE(this)
+ }
+ return NS_OK;
+}
+
+nsresult
+nsAlertsIconListener::Close()
+{
+ if (mIconRequest) {
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+
+ if (!mNotification) {
+ NotifyFinished();
+ return NS_OK;
+ }
+
+ GError* error = nullptr;
+ if (!notify_notification_close(mNotification, &error)) {
+ NS_WARNING(error->message);
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAlertsIconListener::InitAlertAsync(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ if (!libNotifyHandle)
+ return NS_ERROR_FAILURE;
+
+ if (!notify_is_initted()) {
+ // Give the name of this application to libnotify
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ nsAutoCString appShortName;
+ if (bundleService) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ nsAutoString appName;
+
+ if (bundle) {
+ bundle->GetStringFromName(u"brandShortName",
+ getter_Copies(appName));
+ appShortName = NS_ConvertUTF16toUTF8(appName);
+ } else {
+ NS_WARNING("brand.properties not present, using default application name");
+ appShortName.AssignLiteral("Mozilla");
+ }
+ } else {
+ appShortName.AssignLiteral("Mozilla");
+ }
+
+ if (!notify_init(appShortName.get()))
+ return NS_ERROR_FAILURE;
+
+ GList *server_caps = notify_get_server_caps();
+ if (server_caps) {
+ gHasCaps = true;
+ for (GList* cap = server_caps; cap != nullptr; cap = cap->next) {
+ if (!strcmp((char*) cap->data, "actions")) {
+ gHasActions = true;
+ break;
+ }
+ }
+ g_list_foreach(server_caps, (GFunc)g_free, nullptr);
+ g_list_free(server_caps);
+ }
+ }
+
+ if (!gHasCaps) {
+ // if notify_get_server_caps() failed above we need to assume
+ // there is no notification-server to display anything
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = aAlert->GetTextClickable(&mAlertHasAction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!gHasActions && mAlertHasAction)
+ return NS_ERROR_FAILURE; // No good, fallback to XUL
+
+ nsAutoString title;
+ rv = aAlert->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Workaround for a libnotify bug - blank titles aren't dealt with
+ // properly so we use a space
+ if (title.IsEmpty()) {
+ mAlertTitle = NS_LITERAL_CSTRING(" ");
+ } else {
+ mAlertTitle = NS_ConvertUTF16toUTF8(title);
+ }
+
+ nsAutoString text;
+ rv = aAlert->GetText(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mAlertText = NS_ConvertUTF16toUTF8(text);
+
+ mAlertListener = aAlertListener;
+
+ rv = aAlert->GetCookie(mAlertCookie);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aAlert->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr,
+ getter_AddRefs(mIconRequest));
+}
+
+void nsAlertsIconListener::NotifyFinished()
+{
+ if (mAlertListener)
+ mAlertListener->Observe(nullptr, "alertfinished", mAlertCookie.get());
+}
diff --git a/components/gservice/src/nsAlertsIconListener.h b/components/gservice/src/nsAlertsIconListener.h
new file mode 100644
index 000000000..f3a745d33
--- /dev/null
+++ b/components/gservice/src/nsAlertsIconListener.h
@@ -0,0 +1,91 @@
+/* -*- 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 nsAlertsIconListener_h__
+#define nsAlertsIconListener_h__
+
+#include "nsCOMPtr.h"
+#include "nsIAlertsService.h"
+#include "nsString.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+class nsIAlertNotification;
+class nsICancelable;
+class nsSystemAlertsService;
+
+struct NotifyNotification;
+
+class nsAlertsIconListener : public nsIAlertNotificationImageListener,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER
+ NS_DECL_NSIOBSERVER
+
+ nsAlertsIconListener(nsSystemAlertsService* aBackend,
+ const nsAString& aAlertName);
+
+ nsresult InitAlertAsync(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener);
+ nsresult Close();
+
+ void SendCallback();
+ void SendClosed();
+
+protected:
+ virtual ~nsAlertsIconListener();
+
+ /**
+ * The only difference between libnotify.so.4 and libnotify.so.1 for these symbols
+ * is that notify_notification_new takes three arguments in libnotify.so.4 and
+ * four in libnotify.so.1.
+ * Passing the fourth argument as NULL is binary compatible.
+ */
+ typedef void (*NotifyActionCallback)(NotifyNotification*, char*, gpointer);
+ typedef bool (*notify_is_initted_t)(void);
+ typedef bool (*notify_init_t)(const char*);
+ typedef GList* (*notify_get_server_caps_t)(void);
+ typedef NotifyNotification* (*notify_notification_new_t)(const char*, const char*, const char*, const char*);
+ typedef bool (*notify_notification_show_t)(void*, GError**);
+ typedef void (*notify_notification_set_icon_from_pixbuf_t)(void*, GdkPixbuf*);
+ typedef void (*notify_notification_add_action_t)(void*, const char*, const char*, NotifyActionCallback, gpointer, GFreeFunc);
+ typedef bool (*notify_notification_close_t)(void*, GError**);
+
+ nsCOMPtr<nsICancelable> mIconRequest;
+ nsCString mAlertTitle;
+ nsCString mAlertText;
+
+ nsCOMPtr<nsIObserver> mAlertListener;
+ nsString mAlertCookie;
+ nsString mAlertName;
+
+ RefPtr<nsSystemAlertsService> mBackend;
+
+ bool mAlertHasAction;
+
+ static void* libNotifyHandle;
+ static bool libNotifyNotAvail;
+ static notify_is_initted_t notify_is_initted;
+ static notify_init_t notify_init;
+ static notify_get_server_caps_t notify_get_server_caps;
+ static notify_notification_new_t notify_notification_new;
+ static notify_notification_show_t notify_notification_show;
+ static notify_notification_set_icon_from_pixbuf_t notify_notification_set_icon_from_pixbuf;
+ static notify_notification_add_action_t notify_notification_add_action;
+ static notify_notification_close_t notify_notification_close;
+ NotifyNotification* mNotification;
+ gulong mClosureHandler;
+
+ nsresult ShowAlert(GdkPixbuf* aPixbuf);
+
+ void NotifyFinished();
+};
+
+#endif
diff --git a/components/gservice/src/nsGConfService.cpp b/components/gservice/src/nsGConfService.cpp
new file mode 100644
index 000000000..ede71e588
--- /dev/null
+++ b/components/gservice/src/nsGConfService.cpp
@@ -0,0 +1,305 @@
+/* -*- 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 "nsGConfService.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIMutableArray.h"
+#include "prlink.h"
+
+#include <gconf/gconf-client.h>
+
+using namespace mozilla;
+
+#define GCONF_FUNCTIONS \
+ FUNC(gconf_client_get_default, GConfClient*, (void)) \
+ FUNC(gconf_client_get_bool, gboolean, (GConfClient*, const gchar*, GError**)) \
+ FUNC(gconf_client_get_string, gchar*, (GConfClient*, const gchar*, GError**)) \
+ FUNC(gconf_client_get_int, gint, (GConfClient*, const gchar*, GError**)) \
+ FUNC(gconf_client_get_float, gdouble, (GConfClient*, const gchar*, GError**)) \
+ FUNC(gconf_client_get_list, GSList*, (GConfClient*, const gchar*, GConfValueType, GError**)) \
+ FUNC(gconf_client_set_bool, gboolean, (GConfClient*, const gchar*, gboolean, GError**)) \
+ FUNC(gconf_client_set_string, gboolean, (GConfClient*, const gchar*, const gchar*, GError**)) \
+ FUNC(gconf_client_set_int, gboolean, (GConfClient*, const gchar*, gint, GError**)) \
+ FUNC(gconf_client_set_float, gboolean, (GConfClient*, const gchar*, gdouble, GError**)) \
+ FUNC(gconf_client_unset, gboolean, (GConfClient*, const gchar*, GError**))
+
+#define FUNC(name, type, params) \
+ typedef type (*_##name##_fn) params; \
+ static _##name##_fn _##name;
+
+GCONF_FUNCTIONS
+
+#undef FUNC
+
+#define gconf_client_get_default _gconf_client_get_default
+#define gconf_client_get_bool _gconf_client_get_bool
+#define gconf_client_get_string _gconf_client_get_string
+#define gconf_client_get_int _gconf_client_get_int
+#define gconf_client_get_float _gconf_client_get_float
+#define gconf_client_get_list _gconf_client_get_list
+#define gconf_client_set_bool _gconf_client_set_bool
+#define gconf_client_set_string _gconf_client_set_string
+#define gconf_client_set_int _gconf_client_set_int
+#define gconf_client_set_float _gconf_client_set_float
+#define gconf_client_unset _gconf_client_unset
+
+static PRLibrary *gconfLib = nullptr;
+
+typedef void (*nsGConfFunc)();
+struct nsGConfDynamicFunction {
+ const char *functionName;
+ nsGConfFunc *function;
+};
+
+nsGConfService::~nsGConfService()
+{
+ if (mClient)
+ g_object_unref(mClient);
+
+ // We don't unload gconf here because liborbit uses atexit(). In addition to
+ // this, it's not a good idea to unload any gobject based library, as it
+ // leaves types registered in glib's type system
+}
+
+nsresult
+nsGConfService::Init()
+{
+#define FUNC(name, type, params) { #name, (nsGConfFunc *)&_##name },
+ static const nsGConfDynamicFunction kGConfSymbols[] = {
+ GCONF_FUNCTIONS
+ };
+#undef FUNC
+
+ if (!gconfLib) {
+ gconfLib = PR_LoadLibrary("libgconf-2.so.4");
+ if (!gconfLib)
+ return NS_ERROR_FAILURE;
+ }
+
+ for (uint32_t i = 0; i < ArrayLength(kGConfSymbols); i++) {
+ *kGConfSymbols[i].function =
+ PR_FindFunctionSymbol(gconfLib, kGConfSymbols[i].functionName);
+ if (!*kGConfSymbols[i].function) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ mClient = gconf_client_get_default();
+ return mClient ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMPL_ISUPPORTS(nsGConfService, nsIGConfService)
+
+NS_IMETHODIMP
+nsGConfService::GetBool(const nsACString &aKey, bool *aResult)
+{
+ GError* error = nullptr;
+ *aResult = gconf_client_get_bool(mClient, PromiseFlatCString(aKey).get(),
+ &error);
+
+ if (error) {
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGConfService::GetString(const nsACString &aKey, nsACString &aResult)
+{
+ GError* error = nullptr;
+ gchar *result = gconf_client_get_string(mClient,
+ PromiseFlatCString(aKey).get(),
+ &error);
+
+ if (error) {
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ // We do a string copy here so that the caller doesn't need to worry about
+ // freeing the string with g_free().
+
+ aResult.Assign(result);
+ g_free(result);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGConfService::GetInt(const nsACString &aKey, int32_t* aResult)
+{
+ GError* error = nullptr;
+ *aResult = gconf_client_get_int(mClient, PromiseFlatCString(aKey).get(),
+ &error);
+
+ if (error) {
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGConfService::GetFloat(const nsACString &aKey, float* aResult)
+{
+ GError* error = nullptr;
+ *aResult = gconf_client_get_float(mClient, PromiseFlatCString(aKey).get(),
+ &error);
+
+ if (error) {
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGConfService::GetStringList(const nsACString &aKey, nsIArray** aResult)
+{
+ nsCOMPtr<nsIMutableArray> items(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ if (!items)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ GError* error = nullptr;
+ GSList* list = gconf_client_get_list(mClient, PromiseFlatCString(aKey).get(),
+ GCONF_VALUE_STRING, &error);
+ if (error) {
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ for (GSList* l = list; l; l = l->next) {
+ nsCOMPtr<nsISupportsString> obj(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ if (!obj) {
+ g_slist_free(list);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ obj->SetData(NS_ConvertUTF8toUTF16((const char*)l->data));
+ items->AppendElement(obj, false);
+ g_free(l->data);
+ }
+
+ g_slist_free(list);
+ items.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGConfService::SetBool(const nsACString &aKey, bool aValue)
+{
+ bool res = gconf_client_set_bool(mClient, PromiseFlatCString(aKey).get(),
+ aValue, nullptr);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGConfService::SetString(const nsACString &aKey, const nsACString &aValue)
+{
+ bool res = gconf_client_set_string(mClient, PromiseFlatCString(aKey).get(),
+ PromiseFlatCString(aValue).get(),
+ nullptr);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGConfService::SetInt(const nsACString &aKey, int32_t aValue)
+{
+ bool res = gconf_client_set_int(mClient, PromiseFlatCString(aKey).get(),
+ aValue, nullptr);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGConfService::SetFloat(const nsACString &aKey, float aValue)
+{
+ bool res = gconf_client_set_float(mClient, PromiseFlatCString(aKey).get(),
+ aValue, nullptr);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGConfService::GetAppForProtocol(const nsACString &aScheme, bool *aEnabled,
+ nsACString &aHandler)
+{
+ nsAutoCString key("/desktop/gnome/url-handlers/");
+ key.Append(aScheme);
+ key.AppendLiteral("/command");
+
+ GError *err = nullptr;
+ gchar *command = gconf_client_get_string(mClient, key.get(), &err);
+ if (!err && command) {
+ key.Replace(key.Length() - 7, 7, NS_LITERAL_CSTRING("enabled"));
+ *aEnabled = gconf_client_get_bool(mClient, key.get(), &err);
+ } else {
+ *aEnabled = false;
+ }
+
+ aHandler.Assign(command);
+ g_free(command);
+
+ if (err) {
+ g_error_free(err);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGConfService::HandlerRequiresTerminal(const nsACString &aScheme,
+ bool *aResult)
+{
+ nsAutoCString key("/desktop/gnome/url-handlers/");
+ key.Append(aScheme);
+ key.AppendLiteral("/requires_terminal");
+
+ GError *err = nullptr;
+ *aResult = gconf_client_get_bool(mClient, key.get(), &err);
+ if (err) {
+ g_error_free(err);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGConfService::SetAppForProtocol(const nsACString &aScheme,
+ const nsACString &aCommand)
+{
+ nsAutoCString key("/desktop/gnome/url-handlers/");
+ key.Append(aScheme);
+ key.AppendLiteral("/command");
+
+ bool res = gconf_client_set_string(mClient, key.get(),
+ PromiseFlatCString(aCommand).get(),
+ nullptr);
+ if (res) {
+ key.Replace(key.Length() - 7, 7, NS_LITERAL_CSTRING("enabled"));
+ res = gconf_client_set_bool(mClient, key.get(), true, nullptr);
+ if (res) {
+ key.Replace(key.Length() - 7, 7, NS_LITERAL_CSTRING("needs_terminal"));
+ res = gconf_client_set_bool(mClient, key.get(), false, nullptr);
+ if (res) {
+ key.Replace(key.Length() - 14, 14, NS_LITERAL_CSTRING("command-id"));
+ res = gconf_client_unset(mClient, key.get(), nullptr);
+ }
+ }
+ }
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
diff --git a/components/gservice/src/nsGConfService.h b/components/gservice/src/nsGConfService.h
new file mode 100644
index 000000000..4e500a40d
--- /dev/null
+++ b/components/gservice/src/nsGConfService.h
@@ -0,0 +1,31 @@
+/* -*- 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 nsGConfService_h_
+#define nsGConfService_h_
+
+#include "nsIGConfService.h"
+#include "gconf/gconf-client.h"
+#include "mozilla/Attributes.h"
+
+#define NS_GCONFSERVICE_CID \
+{0xd96d5985, 0xa13a, 0x4bdc, {0x93, 0x86, 0xef, 0x34, 0x8d, 0x7a, 0x97, 0xa1}}
+
+class nsGConfService final : public nsIGConfService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGCONFSERVICE
+
+ nsGConfService() : mClient(nullptr) {}
+ nsresult Init();
+
+private:
+ ~nsGConfService();
+
+ GConfClient *mClient;
+};
+
+#endif
diff --git a/components/gservice/src/nsGIOProtocolHandler.cpp b/components/gservice/src/nsGIOProtocolHandler.cpp
new file mode 100644
index 000000000..a378e8700
--- /dev/null
+++ b/components/gservice/src/nsGIOProtocolHandler.cpp
@@ -0,0 +1,1131 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 code is based on original Mozilla gnome-vfs extension. It implements
+ * input stream provided by GVFS/GIO.
+*/
+#include "mozilla/ModuleUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIObserver.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "nsIStringBundle.h"
+#include "nsIStandardURL.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIURI.h"
+#include "nsIAuthPrompt.h"
+#include "nsIChannel.h"
+#include "nsIInputStream.h"
+#include "nsIProtocolHandler.h"
+#include "nsNullPrincipal.h"
+#include "mozilla/Monitor.h"
+#include "plstr.h"
+#include "prtime.h"
+#include <gio/gio.h>
+#include <algorithm>
+
+#define MOZ_GIO_SCHEME "moz-gio"
+#define MOZ_GIO_SUPPORTED_PROTOCOLS "network.gio.supported-protocols"
+
+//-----------------------------------------------------------------------------
+
+// NSPR_LOG_MODULES=gio:5
+static mozilla::LazyLogModule sGIOLog("gio");
+#define LOG(args) MOZ_LOG(sGIOLog, mozilla::LogLevel::Debug, args)
+
+
+//-----------------------------------------------------------------------------
+static nsresult
+MapGIOResult(gint code)
+{
+ switch (code)
+ {
+ case G_IO_ERROR_NOT_FOUND: return NS_ERROR_FILE_NOT_FOUND; // shows error
+ case G_IO_ERROR_INVALID_ARGUMENT: return NS_ERROR_INVALID_ARG;
+ case G_IO_ERROR_NOT_SUPPORTED: return NS_ERROR_NOT_AVAILABLE;
+ case G_IO_ERROR_NO_SPACE: return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ case G_IO_ERROR_READ_ONLY: return NS_ERROR_FILE_READ_ONLY;
+ case G_IO_ERROR_PERMISSION_DENIED: return NS_ERROR_FILE_ACCESS_DENIED; // wrong password/login
+ case G_IO_ERROR_CLOSED: return NS_BASE_STREAM_CLOSED; // was EOF
+ case G_IO_ERROR_NOT_DIRECTORY: return NS_ERROR_FILE_NOT_DIRECTORY;
+ case G_IO_ERROR_PENDING: return NS_ERROR_IN_PROGRESS;
+ case G_IO_ERROR_EXISTS: return NS_ERROR_FILE_ALREADY_EXISTS;
+ case G_IO_ERROR_IS_DIRECTORY: return NS_ERROR_FILE_IS_DIRECTORY;
+ case G_IO_ERROR_NOT_MOUNTED: return NS_ERROR_NOT_CONNECTED; // shows error
+ case G_IO_ERROR_HOST_NOT_FOUND: return NS_ERROR_UNKNOWN_HOST; // shows error
+ case G_IO_ERROR_CANCELLED: return NS_ERROR_ABORT;
+ case G_IO_ERROR_NOT_EMPTY: return NS_ERROR_FILE_DIR_NOT_EMPTY;
+ case G_IO_ERROR_FILENAME_TOO_LONG: return NS_ERROR_FILE_NAME_TOO_LONG;
+ case G_IO_ERROR_INVALID_FILENAME: return NS_ERROR_FILE_INVALID_PATH;
+ case G_IO_ERROR_TIMED_OUT: return NS_ERROR_NET_TIMEOUT; // shows error
+ case G_IO_ERROR_WOULD_BLOCK: return NS_BASE_STREAM_WOULD_BLOCK;
+ case G_IO_ERROR_FAILED_HANDLED: return NS_ERROR_ABORT; // Cancel on login dialog
+
+/* unhandled:
+ G_IO_ERROR_NOT_REGULAR_FILE,
+ G_IO_ERROR_NOT_SYMBOLIC_LINK,
+ G_IO_ERROR_NOT_MOUNTABLE_FILE,
+ G_IO_ERROR_TOO_MANY_LINKS,
+ G_IO_ERROR_ALREADY_MOUNTED,
+ G_IO_ERROR_CANT_CREATE_BACKUP,
+ G_IO_ERROR_WRONG_ETAG,
+ G_IO_ERROR_WOULD_RECURSE,
+ G_IO_ERROR_BUSY,
+ G_IO_ERROR_WOULD_MERGE,
+ G_IO_ERROR_TOO_MANY_OPEN_FILES
+*/
+ // Make GCC happy
+ default:
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+static nsresult
+MapGIOResult(GError *result)
+{
+ if (!result)
+ return NS_OK;
+ else
+ return MapGIOResult(result->code);
+}
+/** Return values for mount operation.
+ * These enums are used as mount operation return values.
+ */
+typedef enum {
+ MOUNT_OPERATION_IN_PROGRESS, /** \enum operation in progress */
+ MOUNT_OPERATION_SUCCESS, /** \enum operation successful */
+ MOUNT_OPERATION_FAILED /** \enum operation not successful */
+} MountOperationResult;
+//-----------------------------------------------------------------------------
+/**
+ * Sort function compares according to file type (directory/file)
+ * and alphabethical order
+ * @param a pointer to GFileInfo object to compare
+ * @param b pointer to GFileInfo object to compare
+ * @return -1 when first object should be before the second, 0 when equal,
+ * +1 when second object should be before the first
+ */
+static gint
+FileInfoComparator(gconstpointer a, gconstpointer b)
+{
+ GFileInfo *ia = ( GFileInfo *) a;
+ GFileInfo *ib = ( GFileInfo *) b;
+ if (g_file_info_get_file_type(ia) == G_FILE_TYPE_DIRECTORY
+ && g_file_info_get_file_type(ib) != G_FILE_TYPE_DIRECTORY)
+ return -1;
+ if (g_file_info_get_file_type(ib) == G_FILE_TYPE_DIRECTORY
+ && g_file_info_get_file_type(ia) != G_FILE_TYPE_DIRECTORY)
+ return 1;
+
+ return strcasecmp(g_file_info_get_name(ia), g_file_info_get_name(ib));
+}
+
+/* Declaration of mount callback functions */
+static void mount_enclosing_volume_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data);
+static void mount_operation_ask_password (GMountOperation *mount_op,
+ const char *message,
+ const char *default_user,
+ const char *default_domain,
+ GAskPasswordFlags flags,
+ gpointer user_data);
+//-----------------------------------------------------------------------------
+
+class nsGIOInputStream final : public nsIInputStream
+{
+ ~nsGIOInputStream() { Close(); }
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ explicit nsGIOInputStream(const nsCString &uriSpec)
+ : mSpec(uriSpec)
+ , mChannel(nullptr)
+ , mHandle(nullptr)
+ , mStream(nullptr)
+ , mBytesRemaining(UINT64_MAX)
+ , mStatus(NS_OK)
+ , mDirList(nullptr)
+ , mDirListPtr(nullptr)
+ , mDirBufCursor(0)
+ , mDirOpen(false)
+ , mMonitorMountInProgress("GIOInputStream::MountFinished") { }
+
+ void SetChannel(nsIChannel *channel)
+ {
+ // We need to hold an owning reference to our channel. This is done
+ // so we can access the channel's notification callbacks to acquire
+ // a reference to a nsIAuthPrompt if we need to handle an interactive
+ // mount operation.
+ //
+ // However, the channel can only be accessed on the main thread, so
+ // we have to be very careful with ownership. Moreover, it doesn't
+ // support threadsafe addref/release, so proxying is the answer.
+ //
+ // Also, it's important to note that this likely creates a reference
+ // cycle since the channel likely owns this stream. This reference
+ // cycle is broken in our Close method.
+
+ NS_ADDREF(mChannel = channel);
+ }
+ void SetMountResult(MountOperationResult result, gint error_code);
+ private:
+ nsresult DoOpen();
+ nsresult DoRead(char *aBuf, uint32_t aCount, uint32_t *aCountRead);
+ nsresult SetContentTypeOfChannel(const char *contentType);
+ nsresult MountVolume();
+ nsresult DoOpenDirectory();
+ nsresult DoOpenFile(GFileInfo *info);
+ nsCString mSpec;
+ nsIChannel *mChannel; // manually refcounted
+ GFile *mHandle;
+ GFileInputStream *mStream;
+ uint64_t mBytesRemaining;
+ nsresult mStatus;
+ GList *mDirList;
+ GList *mDirListPtr;
+ nsCString mDirBuf;
+ uint32_t mDirBufCursor;
+ bool mDirOpen;
+ MountOperationResult mMountRes;
+ mozilla::Monitor mMonitorMountInProgress;
+ gint mMountErrorCode;
+};
+/**
+ * Set result of mount operation and notify monitor waiting for results.
+ * This method is called in main thread as long as it is used only
+ * in mount_enclosing_volume_finished function.
+ * @param result Result of mount operation
+ */
+void
+nsGIOInputStream::SetMountResult(MountOperationResult result, gint error_code)
+{
+ mozilla::MonitorAutoLock mon(mMonitorMountInProgress);
+ mMountRes = result;
+ mMountErrorCode = error_code;
+ mon.Notify();
+}
+
+/**
+ * Start mount operation and wait in loop until it is finished. This method is
+ * called from thread which is trying to read from location.
+ */
+nsresult
+nsGIOInputStream::MountVolume() {
+ GMountOperation* mount_op = g_mount_operation_new();
+ g_signal_connect (mount_op, "ask-password",
+ G_CALLBACK (mount_operation_ask_password), mChannel);
+ mMountRes = MOUNT_OPERATION_IN_PROGRESS;
+ /* g_file_mount_enclosing_volume uses a dbus request to mount the volume.
+ Callback mount_enclosing_volume_finished is called in main thread
+ (not this thread on which this method is called). */
+ g_file_mount_enclosing_volume(mHandle,
+ G_MOUNT_MOUNT_NONE,
+ mount_op,
+ nullptr,
+ mount_enclosing_volume_finished,
+ this);
+ mozilla::MonitorAutoLock mon(mMonitorMountInProgress);
+ /* Waiting for finish of mount operation thread */
+ while (mMountRes == MOUNT_OPERATION_IN_PROGRESS)
+ mon.Wait();
+
+ g_object_unref(mount_op);
+
+ if (mMountRes == MOUNT_OPERATION_FAILED) {
+ return MapGIOResult(mMountErrorCode);
+ } else {
+ return NS_OK;
+ }
+}
+
+/**
+ * Create list of infos about objects in opened directory
+ * Return: NS_OK when list obtained, otherwise error code according
+ * to failed operation.
+ */
+nsresult
+nsGIOInputStream::DoOpenDirectory()
+{
+ GError *error = nullptr;
+
+ GFileEnumerator *f_enum = g_file_enumerate_children(mHandle,
+ "standard::*,time::*",
+ G_FILE_QUERY_INFO_NONE,
+ nullptr,
+ &error);
+ if (!f_enum) {
+ nsresult rv = MapGIOResult(error);
+ g_warning("Cannot read from directory: %s", error->message);
+ g_error_free(error);
+ return rv;
+ }
+ // fill list of file infos
+ GFileInfo *info = g_file_enumerator_next_file(f_enum, nullptr, &error);
+ while (info) {
+ mDirList = g_list_append(mDirList, info);
+ info = g_file_enumerator_next_file(f_enum, nullptr, &error);
+ }
+ g_object_unref(f_enum);
+ if (error) {
+ g_warning("Error reading directory content: %s", error->message);
+ nsresult rv = MapGIOResult(error);
+ g_error_free(error);
+ return rv;
+ }
+ mDirOpen = true;
+
+ // Sort list of file infos by using FileInfoComparator function
+ mDirList = g_list_sort(mDirList, FileInfoComparator);
+ mDirListPtr = mDirList;
+
+ // Write base URL (make sure it ends with a '/')
+ mDirBuf.AppendLiteral("300: ");
+ mDirBuf.Append(mSpec);
+ if (mSpec.get()[mSpec.Length() - 1] != '/')
+ mDirBuf.Append('/');
+ mDirBuf.Append('\n');
+
+ // Write column names
+ mDirBuf.AppendLiteral("200: filename content-length last-modified file-type\n");
+
+ // Write charset (assume UTF-8)
+ // XXX is this correct?
+ mDirBuf.AppendLiteral("301: UTF-8\n");
+ SetContentTypeOfChannel(APPLICATION_HTTP_INDEX_FORMAT);
+ return NS_OK;
+}
+
+/**
+ * Create file stream and set mime type for channel
+ * @param info file info used to determine mime type
+ * @return NS_OK when file stream created successfuly, error code otherwise
+ */
+nsresult
+nsGIOInputStream::DoOpenFile(GFileInfo *info)
+{
+ GError *error = nullptr;
+
+ mStream = g_file_read(mHandle, nullptr, &error);
+ if (!mStream) {
+ nsresult rv = MapGIOResult(error);
+ g_warning("Cannot read from file: %s", error->message);
+ g_error_free(error);
+ return rv;
+ }
+
+ const char * content_type = g_file_info_get_content_type(info);
+ if (content_type) {
+ char *mime_type = g_content_type_get_mime_type(content_type);
+ if (mime_type) {
+ if (strcmp(mime_type, APPLICATION_OCTET_STREAM) != 0) {
+ SetContentTypeOfChannel(mime_type);
+ }
+ g_free(mime_type);
+ }
+ } else {
+ g_warning("Missing content type.");
+ }
+
+ mBytesRemaining = g_file_info_get_size(info);
+ // Update the content length attribute on the channel. We do this
+ // synchronously without proxying. This hack is not as bad as it looks!
+ mChannel->SetContentLength(mBytesRemaining);
+
+ return NS_OK;
+}
+
+/**
+ * Start file open operation, mount volume when needed and according to file type
+ * create file output stream or read directory content.
+ * @return NS_OK when file or directory opened successfully, error code otherwise
+ */
+nsresult
+nsGIOInputStream::DoOpen()
+{
+ nsresult rv;
+ GError *error = nullptr;
+
+ NS_ASSERTION(mHandle == nullptr, "already open");
+
+ mHandle = g_file_new_for_uri( mSpec.get() );
+
+ GFileInfo *info = g_file_query_info(mHandle,
+ "standard::*",
+ G_FILE_QUERY_INFO_NONE,
+ nullptr,
+ &error);
+
+ if (error) {
+ if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_MOUNTED) {
+ // location is not yet mounted, try to mount
+ g_error_free(error);
+ if (NS_IsMainThread())
+ return NS_ERROR_NOT_CONNECTED;
+ error = nullptr;
+ rv = MountVolume();
+ if (rv != NS_OK) {
+ return rv;
+ }
+ // get info again
+ info = g_file_query_info(mHandle,
+ "standard::*",
+ G_FILE_QUERY_INFO_NONE,
+ nullptr,
+ &error);
+ // second try to get file info from remote files after media mount
+ if (!info) {
+ g_warning("Unable to get file info: %s", error->message);
+ rv = MapGIOResult(error);
+ g_error_free(error);
+ return rv;
+ }
+ } else {
+ g_warning("Unable to get file info: %s", error->message);
+ rv = MapGIOResult(error);
+ g_error_free(error);
+ return rv;
+ }
+ }
+ // Get file type to handle directories and file differently
+ GFileType f_type = g_file_info_get_file_type(info);
+ if (f_type == G_FILE_TYPE_DIRECTORY) {
+ // directory
+ rv = DoOpenDirectory();
+ } else if (f_type != G_FILE_TYPE_UNKNOWN) {
+ // file
+ rv = DoOpenFile(info);
+ } else {
+ g_warning("Unable to get file type.");
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ }
+ if (info)
+ g_object_unref(info);
+ return rv;
+}
+
+/**
+ * Read content of file or create file list from directory
+ * @param aBuf read destination buffer
+ * @param aCount length of destination buffer
+ * @param aCountRead number of read characters
+ * @return NS_OK when read successfully, NS_BASE_STREAM_CLOSED when end of file,
+ * error code otherwise
+ */
+nsresult
+nsGIOInputStream::DoRead(char *aBuf, uint32_t aCount, uint32_t *aCountRead)
+{
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ if (mStream) {
+ // file read
+ GError *error = nullptr;
+ uint32_t bytes_read = g_input_stream_read(G_INPUT_STREAM(mStream),
+ aBuf,
+ aCount,
+ nullptr,
+ &error);
+ if (error) {
+ rv = MapGIOResult(error);
+ *aCountRead = 0;
+ g_warning("Cannot read from file: %s", error->message);
+ g_error_free(error);
+ return rv;
+ }
+ *aCountRead = bytes_read;
+ mBytesRemaining -= *aCountRead;
+ return NS_OK;
+ }
+ else if (mDirOpen) {
+ // directory read
+ while (aCount && rv != NS_BASE_STREAM_CLOSED)
+ {
+ // Copy data out of our buffer
+ uint32_t bufLen = mDirBuf.Length() - mDirBufCursor;
+ if (bufLen)
+ {
+ uint32_t n = std::min(bufLen, aCount);
+ memcpy(aBuf, mDirBuf.get() + mDirBufCursor, n);
+ *aCountRead += n;
+ aBuf += n;
+ aCount -= n;
+ mDirBufCursor += n;
+ }
+
+ if (!mDirListPtr) // Are we at the end of the directory list?
+ {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+ else if (aCount) // Do we need more data?
+ {
+ GFileInfo *info = (GFileInfo *) mDirListPtr->data;
+
+ // Prune '.' and '..' from directory listing.
+ const char * fname = g_file_info_get_name(info);
+ if (fname && fname[0] == '.' &&
+ (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0')))
+ {
+ mDirListPtr = mDirListPtr->next;
+ continue;
+ }
+
+ mDirBuf.AssignLiteral("201: ");
+
+ // The "filename" field
+ nsCString escName;
+ nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID);
+ if (nu && fname) {
+ nu->EscapeString(nsDependentCString(fname),
+ nsINetUtil::ESCAPE_URL_PATH, escName);
+
+ mDirBuf.Append(escName);
+ mDirBuf.Append(' ');
+ }
+
+ // The "content-length" field
+ // XXX truncates size from 64-bit to 32-bit
+ mDirBuf.AppendInt(int32_t(g_file_info_get_size(info)));
+ mDirBuf.Append(' ');
+
+ // The "last-modified" field
+ //
+ // NSPR promises: PRTime is compatible with time_t
+ // we just need to convert from seconds to microseconds
+ GTimeVal gtime;
+ g_file_info_get_modification_time(info, &gtime);
+
+ PRExplodedTime tm;
+ PRTime pt = ((PRTime) gtime.tv_sec) * 1000000;
+ PR_ExplodeTime(pt, PR_GMTParameters, &tm);
+ {
+ char buf[64];
+ PR_FormatTimeUSEnglish(buf, sizeof(buf),
+ "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
+ mDirBuf.Append(buf);
+ }
+
+ // The "file-type" field
+ switch (g_file_info_get_file_type(info))
+ {
+ case G_FILE_TYPE_REGULAR:
+ mDirBuf.AppendLiteral("FILE ");
+ break;
+ case G_FILE_TYPE_DIRECTORY:
+ mDirBuf.AppendLiteral("DIRECTORY ");
+ break;
+ case G_FILE_TYPE_SYMBOLIC_LINK:
+ mDirBuf.AppendLiteral("SYMBOLIC-LINK ");
+ break;
+ default:
+ break;
+ }
+ mDirBuf.Append('\n');
+
+ mDirBufCursor = 0;
+ mDirListPtr = mDirListPtr->next;
+ }
+ }
+ }
+ return rv;
+}
+
+/**
+ * This class is used to implement SetContentTypeOfChannel.
+ */
+class nsGIOSetContentTypeEvent : public mozilla::Runnable
+{
+ public:
+ nsGIOSetContentTypeEvent(nsIChannel *channel, const char *contentType)
+ : mChannel(channel), mContentType(contentType)
+ {
+ // stash channel reference in mChannel. no AddRef here! see note
+ // in SetContentTypeOfchannel.
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mChannel->SetContentType(mContentType);
+ return NS_OK;
+ }
+
+ private:
+ nsIChannel *mChannel;
+ nsCString mContentType;
+};
+
+nsresult
+nsGIOInputStream::SetContentTypeOfChannel(const char *contentType)
+{
+ // We need to proxy this call over to the main thread. We post an
+ // asynchronous event in this case so that we don't delay reading data, and
+ // we know that this is safe to do since the channel's reference will be
+ // released asynchronously as well. We trust the ordering of the main
+ // thread's event queue to protect us against memory corruption.
+
+ nsresult rv;
+ nsCOMPtr<nsIRunnable> ev =
+ new nsGIOSetContentTypeEvent(mChannel, contentType);
+ if (!ev)
+ {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ else
+ {
+ rv = NS_DispatchToMainThread(ev);
+ }
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsGIOInputStream, nsIInputStream)
+
+/**
+ * Free all used memory and close stream.
+ */
+NS_IMETHODIMP
+nsGIOInputStream::Close()
+{
+ if (mStream)
+ {
+ g_object_unref(mStream);
+ mStream = nullptr;
+ }
+
+ if (mHandle)
+ {
+ g_object_unref(mHandle);
+ mHandle = nullptr;
+ }
+
+ if (mDirList)
+ {
+ // Destroy the list of GIOFileInfo objects...
+ g_list_foreach(mDirList, (GFunc) g_object_unref, nullptr);
+ g_list_free(mDirList);
+ mDirList = nullptr;
+ mDirListPtr = nullptr;
+ }
+
+ if (mChannel) {
+ NS_ReleaseOnMainThread(dont_AddRef(mChannel));
+
+ mChannel = nullptr;
+ }
+
+ mSpec.Truncate(); // free memory
+
+ // Prevent future reads from re-opening the handle.
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = NS_BASE_STREAM_CLOSED;
+
+ return NS_OK;
+}
+
+/**
+ * Return number of remaining bytes available on input
+ * @param aResult remaining bytes
+ */
+NS_IMETHODIMP
+nsGIOInputStream::Available(uint64_t *aResult)
+{
+ if (NS_FAILED(mStatus))
+ return mStatus;
+
+ *aResult = mBytesRemaining;
+
+ return NS_OK;
+}
+
+/**
+ * Trying to read from stream. When location is not available it tries to mount it.
+ * @param aBuf buffer to put read data
+ * @param aCount length of aBuf
+ * @param aCountRead number of bytes actually read
+ */
+NS_IMETHODIMP
+nsGIOInputStream::Read(char *aBuf,
+ uint32_t aCount,
+ uint32_t *aCountRead)
+{
+ *aCountRead = 0;
+ // Check if file is already opened, otherwise open it
+ if (!mStream && !mDirOpen && mStatus == NS_OK) {
+ mStatus = DoOpen();
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+ }
+
+ mStatus = DoRead(aBuf, aCount, aCountRead);
+ // Check if all data has been read
+ if (mStatus == NS_BASE_STREAM_CLOSED)
+ return NS_OK;
+
+ // Check whenever any error appears while reading
+ return mStatus;
+}
+
+NS_IMETHODIMP
+nsGIOInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void *aClosure,
+ uint32_t aCount,
+ uint32_t *aResult)
+{
+ // There is no way to implement this using GnomeVFS, but fortunately
+ // that doesn't matter. Because we are a blocking input stream, Necko
+ // isn't going to call our ReadSegments method.
+ NS_NOTREACHED("nsGIOInputStream::ReadSegments");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsGIOInputStream::IsNonBlocking(bool *aResult)
+{
+ *aResult = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+/**
+ * Called when finishing mount operation. Result of operation is set in
+ * nsGIOInputStream. This function is called in main thread as an async request
+ * typically from dbus.
+ * @param source_object GFile object which requested the mount
+ * @param res result object
+ * @param user_data pointer to nsGIOInputStream
+ */
+static void
+mount_enclosing_volume_finished (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = nullptr;
+
+ nsGIOInputStream* istream = static_cast<nsGIOInputStream*>(user_data);
+
+ g_file_mount_enclosing_volume_finish(G_FILE (source_object), res, &error);
+
+ if (error) {
+ g_warning("Mount failed: %s %d", error->message, error->code);
+ istream->SetMountResult(MOUNT_OPERATION_FAILED, error->code);
+ g_error_free(error);
+ } else {
+ istream->SetMountResult(MOUNT_OPERATION_SUCCESS, 0);
+ }
+}
+
+/**
+ * This function is called when username or password are requested from user.
+ * This function is called in main thread as async request from dbus.
+ * @param mount_op mount operation
+ * @param message message to show to user
+ * @param default_user preffered user
+ * @param default_domain domain name
+ * @param flags what type of information is required
+ * @param user_data nsIChannel
+ */
+static void
+mount_operation_ask_password (GMountOperation *mount_op,
+ const char *message,
+ const char *default_user,
+ const char *default_domain,
+ GAskPasswordFlags flags,
+ gpointer user_data)
+{
+ nsIChannel *channel = (nsIChannel *) user_data;
+ if (!channel) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // We can't handle request for domain
+ if (flags & G_ASK_PASSWORD_NEED_DOMAIN) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+
+ nsCOMPtr<nsIAuthPrompt> prompt;
+ NS_QueryNotificationCallbacks(channel, prompt);
+
+ // If no auth prompt, then give up. We could failover to using the
+ // WindowWatcher service, but that might defeat a consumer's purposeful
+ // attempt to disable authentication (for whatever reason).
+ if (!prompt) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // Parse out the host and port...
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (!uri) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+
+ nsAutoCString scheme, hostPort;
+ uri->GetScheme(scheme);
+ uri->GetHostPort(hostPort);
+
+ // It doesn't make sense for either of these strings to be empty. What kind
+ // of funky URI is this?
+ if (scheme.IsEmpty() || hostPort.IsEmpty()) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // Construct the single signon key. Altering the value of this key will
+ // cause people's remembered passwords to be forgotten. Think carefully
+ // before changing the way this key is constructed.
+ nsAutoString key, realm;
+
+ NS_ConvertUTF8toUTF16 dispHost(scheme);
+ dispHost.AppendLiteral("://");
+ dispHost.Append(NS_ConvertUTF8toUTF16(hostPort));
+
+ key = dispHost;
+ if (*default_domain != '\0')
+ {
+ // We assume the realm string is ASCII. That might be a bogus assumption,
+ // but we have no idea what encoding GnomeVFS is using, so for now we'll
+ // limit ourselves to ISO-Latin-1. XXX What is a better solution?
+ realm.Append('"');
+ realm.Append(NS_ConvertASCIItoUTF16(default_domain));
+ realm.Append('"');
+ key.Append(' ');
+ key.Append(realm);
+ }
+ // Construct the message string...
+ //
+ // We use Necko's string bundle here. This code really should be encapsulated
+ // behind some Necko API, after all this code is based closely on the code in
+ // nsHttpChannel.cpp.
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleSvc) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleSvc->CreateBundle("chrome://global/locale/commonDialogs.properties",
+ getter_AddRefs(bundle));
+ if (!bundle) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ nsAutoString nsmessage;
+
+ if (flags & G_ASK_PASSWORD_NEED_PASSWORD) {
+ if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
+ if (!realm.IsEmpty()) {
+ const char16_t *strings[] = { realm.get(), dispHost.get() };
+ bundle->FormatStringFromName(u"EnterLoginForRealm3",
+ strings, 2, getter_Copies(nsmessage));
+ } else {
+ const char16_t *strings[] = { dispHost.get() };
+ bundle->FormatStringFromName(u"EnterUserPasswordFor2",
+ strings, 1, getter_Copies(nsmessage));
+ }
+ } else {
+ NS_ConvertUTF8toUTF16 userName(default_user);
+ const char16_t *strings[] = { userName.get(), dispHost.get() };
+ bundle->FormatStringFromName(u"EnterPasswordFor",
+ strings, 2, getter_Copies(nsmessage));
+ }
+ } else {
+ g_warning("Unknown mount operation request (flags: %x)", flags);
+ }
+
+ if (nsmessage.IsEmpty()) {
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ return;
+ }
+ // Prompt the user...
+ nsresult rv;
+ bool retval = false;
+ char16_t *user = nullptr, *pass = nullptr;
+ if (default_user) {
+ // user will be freed by PromptUsernameAndPassword
+ user = ToNewUnicode(NS_ConvertUTF8toUTF16(default_user));
+ }
+ if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
+ rv = prompt->PromptUsernameAndPassword(nullptr, nsmessage.get(),
+ key.get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ &user, &pass, &retval);
+ } else {
+ rv = prompt->PromptPassword(nullptr, nsmessage.get(),
+ key.get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ &pass, &retval);
+ }
+ if (NS_FAILED(rv) || !retval) { // was || user == '\0' || pass == '\0'
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
+ free(user);
+ free(pass);
+ return;
+ }
+ /* GIO should accept UTF8 */
+ g_mount_operation_set_username(mount_op, NS_ConvertUTF16toUTF8(user).get());
+ g_mount_operation_set_password(mount_op, NS_ConvertUTF16toUTF8(pass).get());
+ free(user);
+ free(pass);
+ g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_HANDLED);
+}
+
+//-----------------------------------------------------------------------------
+
+class nsGIOProtocolHandler final : public nsIProtocolHandler
+ , public nsIObserver
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+ private:
+ ~nsGIOProtocolHandler() {}
+
+ void InitSupportedProtocolsPref(nsIPrefBranch *prefs);
+ bool IsSupportedProtocol(const nsCString &spec);
+
+ nsCString mSupportedProtocols;
+};
+
+NS_IMPL_ISUPPORTS(nsGIOProtocolHandler, nsIProtocolHandler, nsIObserver)
+
+nsresult
+nsGIOProtocolHandler::Init()
+{
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs)
+ {
+ InitSupportedProtocolsPref(prefs);
+ prefs->AddObserver(MOZ_GIO_SUPPORTED_PROTOCOLS, this, false);
+ }
+
+ return NS_OK;
+}
+
+void
+nsGIOProtocolHandler::InitSupportedProtocolsPref(nsIPrefBranch *prefs)
+{
+ // Get user preferences to determine which protocol is supported.
+ // Gvfs/GIO has a set of supported protocols like obex, network, archive,
+ // computer, dav, cdda, gphoto2, trash, etc. Some of these seems to be
+ // irrelevant to process by browser. By default accept only smb and sftp
+ // protocols so far.
+ nsresult rv = prefs->GetCharPref(MOZ_GIO_SUPPORTED_PROTOCOLS,
+ getter_Copies(mSupportedProtocols));
+ if (NS_SUCCEEDED(rv)) {
+ mSupportedProtocols.StripWhitespace();
+ ToLowerCase(mSupportedProtocols);
+ }
+ else
+ mSupportedProtocols.AssignLiteral("smb:,sftp:"); // use defaults
+
+ LOG(("gio: supported protocols \"%s\"\n", mSupportedProtocols.get()));
+}
+
+bool
+nsGIOProtocolHandler::IsSupportedProtocol(const nsCString &aSpec)
+{
+ const char *specString = aSpec.get();
+ const char *colon = strchr(specString, ':');
+ if (!colon)
+ return false;
+
+ uint32_t length = colon - specString + 1;
+
+ // <scheme> + ':'
+ nsCString scheme(specString, length);
+
+ char *found = PL_strcasestr(mSupportedProtocols.get(), scheme.get());
+ if (!found)
+ return false;
+
+ if (found[length] != ',' && found[length] != '\0')
+ return false;
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::GetScheme(nsACString &aScheme)
+{
+ aScheme.Assign(MOZ_GIO_SCHEME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::GetDefaultPort(int32_t *aDefaultPort)
+{
+ *aDefaultPort = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::GetProtocolFlags(uint32_t *aProtocolFlags)
+{
+ // Is URI_STD true of all GnomeVFS URI types?
+ *aProtocolFlags = URI_STD | URI_DANGEROUS_TO_LOAD;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aOriginCharset,
+ nsIURI *aBaseURI,
+ nsIURI **aResult)
+{
+ const nsCString flatSpec(aSpec);
+ LOG(("gio: NewURI [spec=%s]\n", flatSpec.get()));
+
+ if (!aBaseURI)
+ {
+ // XXX Is it good to support all GIO protocols?
+ if (!IsSupportedProtocol(flatSpec))
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+
+ int32_t colon_location = flatSpec.FindChar(':');
+ if (colon_location <= 0)
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+
+ // Verify that GIO supports this URI scheme.
+ bool uri_scheme_supported = false;
+
+ GVfs *gvfs = g_vfs_get_default();
+
+ if (!gvfs) {
+ g_warning("Cannot get GVfs object.");
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ const gchar* const * uri_schemes = g_vfs_get_supported_uri_schemes(gvfs);
+
+ while (*uri_schemes != nullptr) {
+ // While flatSpec ends with ':' the uri_scheme does not. Therefore do not
+ // compare last character.
+ if (StringHead(flatSpec, colon_location).Equals(*uri_schemes)) {
+ uri_scheme_supported = true;
+ break;
+ }
+ uri_schemes++;
+ }
+
+ if (!uri_scheme_supported) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIStandardURL> url =
+ do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = url->Init(nsIStandardURL::URLTYPE_STANDARD, -1, flatSpec,
+ aOriginCharset, aBaseURI);
+ if (NS_SUCCEEDED(rv))
+ rv = CallQueryInterface(url, aResult);
+ return rv;
+
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ nsresult rv;
+
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RefPtr<nsGIOInputStream> stream = new nsGIOInputStream(spec);
+ if (!stream) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = NS_NewInputStreamChannelInternal(aResult,
+ aURI,
+ stream,
+ NS_LITERAL_CSTRING(UNKNOWN_CONTENT_TYPE),
+ EmptyCString(), // aContentCharset
+ aLoadInfo);
+ if (NS_SUCCEEDED(rv)) {
+ stream->SetChannel(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **aResult)
+{
+ return NewChannel2(aURI, nullptr, aResult);
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::AllowPort(int32_t aPort,
+ const char *aScheme,
+ bool *aResult)
+{
+ // Don't override anything.
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
+ nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
+ InitSupportedProtocolsPref(prefs);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+#define NS_GIOPROTOCOLHANDLER_CID \
+{ /* ee706783-3af8-4d19-9e84-e2ebfe213480 */ \
+ 0xee706783, \
+ 0x3af8, \
+ 0x4d19, \
+ {0x9e, 0x84, 0xe2, 0xeb, 0xfe, 0x21, 0x34, 0x80} \
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGIOProtocolHandler, Init)
+NS_DEFINE_NAMED_CID(NS_GIOPROTOCOLHANDLER_CID);
+
+static const mozilla::Module::CIDEntry kVFSCIDs[] = {
+ { &kNS_GIOPROTOCOLHANDLER_CID, false, nullptr, nsGIOProtocolHandlerConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kVFSContracts[] = {
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX MOZ_GIO_SCHEME, &kNS_GIOPROTOCOLHANDLER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kVFSModule = {
+ mozilla::Module::kVersion,
+ kVFSCIDs,
+ kVFSContracts
+};
+
+NSMODULE_DEFN(nsGIOModule) = &kVFSModule;
diff --git a/components/gservice/src/nsGIOService.cpp b/components/gservice/src/nsGIOService.cpp
new file mode 100644
index 000000000..9196c7d76
--- /dev/null
+++ b/components/gservice/src/nsGIOService.cpp
@@ -0,0 +1,475 @@
+/* -*- 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 "nsGIOService.h"
+#include "nsString.h"
+#include "nsIURI.h"
+#include "nsTArray.h"
+#include "nsIStringEnumerator.h"
+#include "nsAutoPtr.h"
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#ifdef MOZ_ENABLE_DBUS
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#endif
+
+
+class nsGIOMimeApp final : public nsIGIOMimeApp
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGIOMIMEAPP
+
+ explicit nsGIOMimeApp(GAppInfo* aApp) : mApp(aApp) {}
+
+private:
+ ~nsGIOMimeApp() { g_object_unref(mApp); }
+
+ GAppInfo *mApp;
+};
+
+NS_IMPL_ISUPPORTS(nsGIOMimeApp, nsIGIOMimeApp)
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetId(nsACString& aId)
+{
+ aId.Assign(g_app_info_get_id(mApp));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetName(nsACString& aName)
+{
+ aName.Assign(g_app_info_get_name(mApp));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetCommand(nsACString& aCommand)
+{
+ const char *cmd = g_app_info_get_commandline(mApp);
+ if (!cmd)
+ return NS_ERROR_FAILURE;
+ aCommand.Assign(cmd);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetExpectsURIs(int32_t* aExpects)
+{
+ *aExpects = g_app_info_supports_uris(mApp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::Launch(const nsACString& aUri)
+{
+ GList uris = { 0 };
+ nsPromiseFlatCString flatUri(aUri);
+ uris.data = const_cast<char*>(flatUri.get());
+
+ GError *error = nullptr;
+ gboolean result = g_app_info_launch_uris(mApp, &uris, nullptr, &error);
+
+ if (!result) {
+ g_warning("Cannot launch application: %s", error->message);
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+class GIOUTF8StringEnumerator final : public nsIUTF8StringEnumerator
+{
+ ~GIOUTF8StringEnumerator() { }
+
+public:
+ GIOUTF8StringEnumerator() : mIndex(0) { }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ nsTArray<nsCString> mStrings;
+ uint32_t mIndex;
+};
+
+NS_IMPL_ISUPPORTS(GIOUTF8StringEnumerator, nsIUTF8StringEnumerator)
+
+NS_IMETHODIMP
+GIOUTF8StringEnumerator::HasMore(bool* aResult)
+{
+ *aResult = mIndex < mStrings.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOUTF8StringEnumerator::GetNext(nsACString& aResult)
+{
+ if (mIndex >= mStrings.Length())
+ return NS_ERROR_UNEXPECTED;
+
+ aResult.Assign(mStrings[mIndex]);
+ ++mIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetSupportedURISchemes(nsIUTF8StringEnumerator** aSchemes)
+{
+ *aSchemes = nullptr;
+
+ RefPtr<GIOUTF8StringEnumerator> array = new GIOUTF8StringEnumerator();
+ NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+ GVfs *gvfs = g_vfs_get_default();
+
+ if (!gvfs) {
+ g_warning("Cannot get GVfs object.");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ const gchar* const * uri_schemes = g_vfs_get_supported_uri_schemes(gvfs);
+
+ while (*uri_schemes != nullptr) {
+ if (!array->mStrings.AppendElement(*uri_schemes)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ uri_schemes++;
+ }
+
+ array.forget(aSchemes);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::SetAsDefaultForMimeType(nsACString const& aMimeType)
+{
+ char *content_type =
+ g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get());
+ if (!content_type)
+ return NS_ERROR_FAILURE;
+ GError *error = nullptr;
+ g_app_info_set_as_default_for_type(mApp,
+ content_type,
+ &error);
+ if (error) {
+ g_warning("Cannot set application as default for MIME type (%s): %s",
+ PromiseFlatCString(aMimeType).get(),
+ error->message);
+ g_error_free(error);
+ g_free(content_type);
+ return NS_ERROR_FAILURE;
+ }
+
+ g_free(content_type);
+ return NS_OK;
+}
+/**
+ * Set default application for files with given extensions
+ * @param fileExts string of space separated extensions
+ * @return NS_OK when application was set as default for given extensions,
+ * NS_ERROR_FAILURE otherwise
+ */
+NS_IMETHODIMP
+nsGIOMimeApp::SetAsDefaultForFileExtensions(nsACString const& fileExts)
+{
+ GError *error = nullptr;
+ char *extensions = g_strdup(PromiseFlatCString(fileExts).get());
+ char *ext_pos = extensions;
+ char *space_pos;
+
+ while ( (space_pos = strchr(ext_pos, ' ')) || (*ext_pos != '\0') ) {
+ if (space_pos) {
+ *space_pos = '\0';
+ }
+ g_app_info_set_as_default_for_extension(mApp, ext_pos, &error);
+ if (error) {
+ g_warning("Cannot set application as default for extension (%s): %s",
+ ext_pos,
+ error->message);
+ g_error_free(error);
+ g_free(extensions);
+ return NS_ERROR_FAILURE;
+ }
+ if (space_pos) {
+ ext_pos = space_pos + 1;
+ } else {
+ *ext_pos = '\0';
+ }
+ }
+ g_free(extensions);
+ return NS_OK;
+}
+
+/**
+ * Set default application for URI's of a particular scheme
+ * @param aURIScheme string containing the URI scheme
+ * @return NS_OK when application was set as default for URI scheme,
+ * NS_ERROR_FAILURE otherwise
+ */
+NS_IMETHODIMP
+nsGIOMimeApp::SetAsDefaultForURIScheme(nsACString const& aURIScheme)
+{
+ GError *error = nullptr;
+ nsAutoCString contentType("x-scheme-handler/");
+ contentType.Append(aURIScheme);
+
+ g_app_info_set_as_default_for_type(mApp,
+ contentType.get(),
+ &error);
+ if (error) {
+ g_warning("Cannot set application as default for URI scheme (%s): %s",
+ PromiseFlatCString(aURIScheme).get(),
+ error->message);
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsGIOService, nsIGIOService)
+
+NS_IMETHODIMP
+nsGIOService::GetMimeTypeFromExtension(const nsACString& aExtension,
+ nsACString& aMimeType)
+{
+ nsAutoCString fileExtToUse("file.");
+ fileExtToUse.Append(aExtension);
+
+ gboolean result_uncertain;
+ char *content_type = g_content_type_guess(fileExtToUse.get(),
+ nullptr,
+ 0,
+ &result_uncertain);
+ if (!content_type)
+ return NS_ERROR_FAILURE;
+
+ char *mime_type = g_content_type_get_mime_type(content_type);
+ if (!mime_type) {
+ g_free(content_type);
+ return NS_ERROR_FAILURE;
+ }
+
+ aMimeType.Assign(mime_type);
+
+ g_free(mime_type);
+ g_free(content_type);
+
+ return NS_OK;
+}
+// used in nsGNOMERegistry
+// -----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsGIOService::GetAppForURIScheme(const nsACString& aURIScheme,
+ nsIGIOMimeApp** aApp)
+{
+ *aApp = nullptr;
+
+ GAppInfo *app_info = g_app_info_get_default_for_uri_scheme(
+ PromiseFlatCString(aURIScheme).get());
+ if (app_info) {
+ nsGIOMimeApp *mozApp = new nsGIOMimeApp(app_info);
+ NS_ADDREF(*aApp = mozApp);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOService::GetAppForMimeType(const nsACString& aMimeType,
+ nsIGIOMimeApp** aApp)
+{
+ *aApp = nullptr;
+ char *content_type =
+ g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get());
+ if (!content_type)
+ return NS_ERROR_FAILURE;
+
+ GAppInfo *app_info = g_app_info_get_default_for_type(content_type, false);
+ if (app_info) {
+ nsGIOMimeApp *mozApp = new nsGIOMimeApp(app_info);
+ NS_ENSURE_TRUE(mozApp, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*aApp = mozApp);
+ } else {
+ g_free(content_type);
+ return NS_ERROR_FAILURE;
+ }
+ g_free(content_type);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOService::GetDescriptionForMimeType(const nsACString& aMimeType,
+ nsACString& aDescription)
+{
+ char *content_type =
+ g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get());
+ if (!content_type)
+ return NS_ERROR_FAILURE;
+
+ char *desc = g_content_type_get_description(content_type);
+ if (!desc) {
+ g_free(content_type);
+ return NS_ERROR_FAILURE;
+ }
+
+ aDescription.Assign(desc);
+ g_free(content_type);
+ g_free(desc);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOService::ShowURI(nsIURI* aURI)
+{
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ GError *error = nullptr;
+ if (!g_app_info_launch_default_for_uri(spec.get(), nullptr, &error)) {
+ g_warning("Could not launch default application for URI: %s", error->message);
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOService::ShowURIForInput(const nsACString& aUri)
+{
+ GFile *file = g_file_new_for_commandline_arg(PromiseFlatCString(aUri).get());
+ char* spec = g_file_get_uri(file);
+ nsresult rv = NS_ERROR_FAILURE;
+ GError *error = nullptr;
+
+ g_app_info_launch_default_for_uri(spec, nullptr, &error);
+ if (error) {
+ g_warning("Cannot launch default application: %s", error->message);
+ g_error_free(error);
+ } else {
+ rv = NS_OK;
+ }
+ g_object_unref(file);
+ g_free(spec);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsGIOService::OrgFreedesktopFileManager1ShowItems(const nsACString& aPath)
+{
+#ifndef MOZ_ENABLE_DBUS
+ return NS_ERROR_FAILURE;
+#else
+ GError* error = nullptr;
+ static bool org_freedesktop_FileManager1_exists = true;
+
+ if (!org_freedesktop_FileManager1_exists) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ DBusGConnection* dbusGConnection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+
+ if (!dbusGConnection) {
+ if (error) {
+ g_printerr("Failed to open connection to session bus: %s\n", error->message);
+ g_error_free(error);
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ char *uri = g_filename_to_uri(PromiseFlatCString(aPath).get(), nullptr, nullptr);
+ if (uri == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusConnection* dbusConnection = dbus_g_connection_get_connection(dbusGConnection);
+ // Make sure we do not exit the entire program if DBus connection get lost.
+ dbus_connection_set_exit_on_disconnect(dbusConnection, false);
+
+ DBusGProxy* dbusGProxy = dbus_g_proxy_new_for_name(dbusGConnection,
+ "org.freedesktop.FileManager1",
+ "/org/freedesktop/FileManager1",
+ "org.freedesktop.FileManager1");
+
+ const char *uris[2] = { uri, nullptr };
+ gboolean rv_dbus_call = dbus_g_proxy_call (dbusGProxy, "ShowItems", nullptr, G_TYPE_STRV, uris,
+ G_TYPE_STRING, "", G_TYPE_INVALID, G_TYPE_INVALID);
+
+ g_object_unref(dbusGProxy);
+ dbus_g_connection_unref(dbusGConnection);
+ g_free(uri);
+
+ if (!rv_dbus_call) {
+ org_freedesktop_FileManager1_exists = false;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+#endif
+}
+
+/**
+ * Create or find already existing application info for specified command
+ * and application name.
+ * @param cmd command to execute
+ * @param appName application name
+ * @param appInfo location where created GAppInfo is stored
+ * @return NS_OK when object is created, NS_ERROR_FAILURE otherwise.
+ */
+NS_IMETHODIMP
+nsGIOService::CreateAppFromCommand(nsACString const& cmd,
+ nsACString const& appName,
+ nsIGIOMimeApp** appInfo)
+{
+ GError *error = nullptr;
+ *appInfo = nullptr;
+
+ GAppInfo *app_info = nullptr, *app_info_from_list = nullptr;
+ GList *apps = g_app_info_get_all();
+ GList *apps_p = apps;
+
+ // Try to find relevant and existing GAppInfo in all installed application
+ // We do this by comparing each GAppInfo's executable with out own
+ while (apps_p) {
+ app_info_from_list = (GAppInfo*) apps_p->data;
+ if (!app_info) {
+ // If the executable is not absolute, get it's full path
+ char *executable = g_find_program_in_path(g_app_info_get_executable(app_info_from_list));
+
+ if (executable && strcmp(executable, PromiseFlatCString(cmd).get()) == 0) {
+ g_object_ref (app_info_from_list);
+ app_info = app_info_from_list;
+ }
+ g_free(executable);
+ }
+
+ g_object_unref(app_info_from_list);
+ apps_p = apps_p->next;
+ }
+ g_list_free(apps);
+
+ if (!app_info) {
+ app_info = g_app_info_create_from_commandline(PromiseFlatCString(cmd).get(),
+ PromiseFlatCString(appName).get(),
+ G_APP_INFO_CREATE_SUPPORTS_URIS,
+ &error);
+ }
+
+ if (!app_info) {
+ g_warning("Cannot create application info from command: %s", error->message);
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+ nsGIOMimeApp *mozApp = new nsGIOMimeApp(app_info);
+ NS_ENSURE_TRUE(mozApp, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*appInfo = mozApp);
+ return NS_OK;
+}
diff --git a/components/gservice/src/nsGIOService.h b/components/gservice/src/nsGIOService.h
new file mode 100644
index 000000000..2ae7a7e1a
--- /dev/null
+++ b/components/gservice/src/nsGIOService.h
@@ -0,0 +1,24 @@
+/* -*- 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 nsGIOService_h_
+#define nsGIOService_h_
+
+#include "nsIGIOService.h"
+
+#define NS_GIOSERVICE_CID \
+{0xe3a1f3c9, 0x3ae1, 0x4b40, {0xa5, 0xe0, 0x7b, 0x45, 0x7f, 0xc9, 0xa9, 0xad}}
+
+class nsGIOService final : public nsIGIOService
+{
+ ~nsGIOService() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGIOSERVICE
+};
+
+#endif
+
diff --git a/components/gservice/src/nsGSettingsService.cpp b/components/gservice/src/nsGSettingsService.cpp
new file mode 100644
index 000000000..d8c46b4fe
--- /dev/null
+++ b/components/gservice/src/nsGSettingsService.cpp
@@ -0,0 +1,350 @@
+/* -*- 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 "nsGSettingsService.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsMemory.h"
+#include "prlink.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsISupportsPrimitives.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+using namespace mozilla;
+
+typedef struct _GSettings GSettings;
+typedef struct _GVariantType GVariantType;
+typedef struct _GVariant GVariant;
+
+#ifndef G_VARIANT_TYPE_INT32
+# define G_VARIANT_TYPE_INT32 ((const GVariantType *) "i")
+# define G_VARIANT_TYPE_BOOLEAN ((const GVariantType *) "b")
+# define G_VARIANT_TYPE_STRING ((const GVariantType *) "s")
+# define G_VARIANT_TYPE_OBJECT_PATH ((const GVariantType *) "o")
+# define G_VARIANT_TYPE_SIGNATURE ((const GVariantType *) "g")
+#endif
+#ifndef G_VARIANT_TYPE_STRING_ARRAY
+# define G_VARIANT_TYPE_STRING_ARRAY ((const GVariantType *) "as")
+#endif
+
+#define GSETTINGS_FUNCTIONS \
+ FUNC(g_settings_new, GSettings *, (const char* schema)) \
+ FUNC(g_settings_list_schemas, const char * const *, (void)) \
+ FUNC(g_settings_list_keys, char **, (GSettings* settings)) \
+ FUNC(g_settings_get_value, GVariant *, (GSettings* settings, const char* key)) \
+ FUNC(g_settings_set_value, gboolean, (GSettings* settings, const char* key, GVariant* value)) \
+ FUNC(g_settings_range_check, gboolean, (GSettings* settings, const char* key, GVariant* value)) \
+ FUNC(g_variant_get_int32, gint32, (GVariant* variant)) \
+ FUNC(g_variant_get_boolean, gboolean, (GVariant* variant)) \
+ FUNC(g_variant_get_string, const char *, (GVariant* value, gsize* length)) \
+ FUNC(g_variant_get_strv, const char **, (GVariant* value, gsize* length)) \
+ FUNC(g_variant_is_of_type, gboolean, (GVariant* value, const GVariantType* type)) \
+ FUNC(g_variant_new_int32, GVariant *, (gint32 value)) \
+ FUNC(g_variant_new_boolean, GVariant *, (gboolean value)) \
+ FUNC(g_variant_new_string, GVariant *, (const char* string)) \
+ FUNC(g_variant_unref, void, (GVariant* value))
+
+#define FUNC(name, type, params) \
+ typedef type (*_##name##_fn) params; \
+ static _##name##_fn _##name;
+
+GSETTINGS_FUNCTIONS
+
+#undef FUNC
+
+#define g_settings_new _g_settings_new
+#define g_settings_list_schemas _g_settings_list_schemas
+#define g_settings_list_keys _g_settings_list_keys
+#define g_settings_get_value _g_settings_get_value
+#define g_settings_set_value _g_settings_set_value
+#define g_settings_range_check _g_settings_range_check
+#define g_variant_get_int32 _g_variant_get_int32
+#define g_variant_get_boolean _g_variant_get_boolean
+#define g_variant_get_string _g_variant_get_string
+#define g_variant_get_strv _g_variant_get_strv
+#define g_variant_is_of_type _g_variant_is_of_type
+#define g_variant_new_int32 _g_variant_new_int32
+#define g_variant_new_boolean _g_variant_new_boolean
+#define g_variant_new_string _g_variant_new_string
+#define g_variant_unref _g_variant_unref
+
+static PRLibrary *gioLib = nullptr;
+
+class nsGSettingsCollection final : public nsIGSettingsCollection
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGSETTINGSCOLLECTION
+
+ explicit nsGSettingsCollection(GSettings* aSettings) : mSettings(aSettings),
+ mKeys(nullptr) {}
+private:
+ ~nsGSettingsCollection();
+
+ bool KeyExists(const nsACString& aKey);
+ bool SetValue(const nsACString& aKey,
+ GVariant *aValue);
+
+ GSettings *mSettings;
+ char **mKeys;
+};
+
+nsGSettingsCollection::~nsGSettingsCollection()
+{
+ g_strfreev(mKeys);
+ g_object_unref(mSettings);
+}
+
+bool
+nsGSettingsCollection::KeyExists(const nsACString& aKey)
+{
+ if (!mKeys)
+ mKeys = g_settings_list_keys(mSettings);
+
+ for (uint32_t i = 0; mKeys[i] != nullptr; i++) {
+ if (aKey.Equals(mKeys[i]))
+ return true;
+ }
+
+ return false;
+}
+
+bool
+nsGSettingsCollection::SetValue(const nsACString& aKey,
+ GVariant *aValue)
+{
+ if (!KeyExists(aKey) ||
+ !g_settings_range_check(mSettings,
+ PromiseFlatCString(aKey).get(),
+ aValue)) {
+ g_variant_unref(aValue);
+ return false;
+ }
+
+ return g_settings_set_value(mSettings,
+ PromiseFlatCString(aKey).get(),
+ aValue);
+}
+
+NS_IMPL_ISUPPORTS(nsGSettingsCollection, nsIGSettingsCollection)
+
+NS_IMETHODIMP
+nsGSettingsCollection::SetString(const nsACString& aKey,
+ const nsACString& aValue)
+{
+ GVariant *value = g_variant_new_string(PromiseFlatCString(aValue).get());
+ if (!value)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ bool res = SetValue(aKey, value);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::SetBoolean(const nsACString& aKey,
+ bool aValue)
+{
+ GVariant *value = g_variant_new_boolean(aValue);
+ if (!value)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ bool res = SetValue(aKey, value);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::SetInt(const nsACString& aKey,
+ int32_t aValue)
+{
+ GVariant *value = g_variant_new_int32(aValue);
+ if (!value)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ bool res = SetValue(aKey, value);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetString(const nsACString& aKey,
+ nsACString& aResult)
+{
+ if (!KeyExists(aKey))
+ return NS_ERROR_INVALID_ARG;
+
+ GVariant *value = g_settings_get_value(mSettings,
+ PromiseFlatCString(aKey).get());
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) &&
+ !g_variant_is_of_type(value, G_VARIANT_TYPE_OBJECT_PATH) &&
+ !g_variant_is_of_type(value, G_VARIANT_TYPE_SIGNATURE)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ aResult.Assign(g_variant_get_string(value, nullptr));
+ g_variant_unref(value);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetBoolean(const nsACString& aKey,
+ bool* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!KeyExists(aKey))
+ return NS_ERROR_INVALID_ARG;
+
+ GVariant *value = g_settings_get_value(mSettings,
+ PromiseFlatCString(aKey).get());
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ gboolean res = g_variant_get_boolean(value);
+ *aResult = res ? true : false;
+ g_variant_unref(value);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetInt(const nsACString& aKey,
+ int32_t* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!KeyExists(aKey))
+ return NS_ERROR_INVALID_ARG;
+
+ GVariant *value = g_settings_get_value(mSettings,
+ PromiseFlatCString(aKey).get());
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_INT32)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = g_variant_get_int32(value);
+ g_variant_unref(value);
+
+ return NS_OK;
+}
+
+// These types are local to nsGSettingsService::Init, but ISO C++98 doesn't
+// allow a template (ArrayLength) to be instantiated based on a local type.
+// Boo-urns!
+typedef void (*nsGSettingsFunc)();
+struct nsGSettingsDynamicFunction {
+ const char *functionName;
+ nsGSettingsFunc *function;
+};
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetStringList(const nsACString& aKey, nsIArray** aResult)
+{
+ if (!KeyExists(aKey))
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIMutableArray> items(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ if (!items) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ GVariant *value = g_settings_get_value(mSettings,
+ PromiseFlatCString(aKey).get());
+
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING_ARRAY)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ const gchar ** gs_strings = g_variant_get_strv(value, nullptr);
+ if (!gs_strings) {
+ // empty array
+ items.forget(aResult);
+ g_variant_unref(value);
+ return NS_OK;
+ }
+
+ const gchar** p_gs_strings = gs_strings;
+ while (*p_gs_strings != nullptr)
+ {
+ nsCOMPtr<nsISupportsCString> obj(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ if (obj) {
+ obj->SetData(nsDependentCString(*p_gs_strings));
+ items->AppendElement(obj, false);
+ }
+ p_gs_strings++;
+ }
+ g_free(gs_strings);
+ items.forget(aResult);
+ g_variant_unref(value);
+ return NS_OK;
+}
+
+nsresult
+nsGSettingsService::Init()
+{
+#define FUNC(name, type, params) { #name, (nsGSettingsFunc *)&_##name },
+ static const nsGSettingsDynamicFunction kGSettingsSymbols[] = {
+ GSETTINGS_FUNCTIONS
+ };
+#undef FUNC
+
+ if (!gioLib) {
+ gioLib = PR_LoadLibrary("libgio-2.0.so.0");
+ if (!gioLib)
+ return NS_ERROR_FAILURE;
+ }
+
+ for (uint32_t i = 0; i < ArrayLength(kGSettingsSymbols); i++) {
+ *kGSettingsSymbols[i].function =
+ PR_FindFunctionSymbol(gioLib, kGSettingsSymbols[i].functionName);
+ if (!*kGSettingsSymbols[i].function) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsGSettingsService, nsIGSettingsService)
+
+nsGSettingsService::~nsGSettingsService()
+{
+ if (gioLib) {
+ PR_UnloadLibrary(gioLib);
+ gioLib = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsGSettingsService::GetCollectionForSchema(const nsACString& schema,
+ nsIGSettingsCollection** collection)
+{
+ NS_ENSURE_ARG_POINTER(collection);
+
+ const char * const *schemas = g_settings_list_schemas();
+
+ for (uint32_t i = 0; schemas[i] != nullptr; i++) {
+ if (schema.Equals(schemas[i])) {
+ GSettings *settings = g_settings_new(PromiseFlatCString(schema).get());
+ nsGSettingsCollection *mozGSettings = new nsGSettingsCollection(settings);
+ NS_ADDREF(*collection = mozGSettings);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
diff --git a/components/gservice/src/nsGSettingsService.h b/components/gservice/src/nsGSettingsService.h
new file mode 100644
index 000000000..6ffcdedd1
--- /dev/null
+++ b/components/gservice/src/nsGSettingsService.h
@@ -0,0 +1,27 @@
+/* -*- 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 nsGSettingsService_h_
+#define nsGSettingsService_h_
+
+#include "nsIGSettingsService.h"
+
+#define NS_GSETTINGSSERVICE_CID \
+{0xbfd4a9d8, 0xd886, 0x4161, {0x81, 0xef, 0x88, 0x68, 0xda, 0x11, 0x41, 0x70}}
+
+class nsGSettingsService final : public nsIGSettingsService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGSETTINGSSERVICE
+
+ nsresult Init();
+
+private:
+ ~nsGSettingsService();
+};
+
+#endif
+
diff --git a/components/gservice/src/nsGnomeModule.cpp b/components/gservice/src/nsGnomeModule.cpp
new file mode 100644
index 000000000..6ecebcc1f
--- /dev/null
+++ b/components/gservice/src/nsGnomeModule.cpp
@@ -0,0 +1,78 @@
+/* -*- 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 "nsToolkitCompsCID.h"
+#include "mozilla/ModuleUtils.h"
+
+#include <glib-object.h>
+
+#ifdef MOZ_ENABLE_GCONF
+#include "nsGConfService.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGConfService, Init)
+#endif
+#ifdef MOZ_ENABLE_GIO
+#include "nsGIOService.h"
+#include "nsGSettingsService.h"
+#include "nsPackageKitService.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsGIOService)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGSettingsService, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPackageKitService, Init)
+#endif
+#include "nsSystemAlertsService.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSystemAlertsService, Init)
+
+#ifdef MOZ_ENABLE_GCONF
+NS_DEFINE_NAMED_CID(NS_GCONFSERVICE_CID);
+#endif
+#ifdef MOZ_ENABLE_GIO
+NS_DEFINE_NAMED_CID(NS_GIOSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_GSETTINGSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_PACKAGEKITSERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kGnomeCIDs[] = {
+#ifdef MOZ_ENABLE_GCONF
+ { &kNS_GCONFSERVICE_CID, false, nullptr, nsGConfServiceConstructor },
+#endif
+#ifdef MOZ_ENABLE_GIO
+ { &kNS_GIOSERVICE_CID, false, nullptr, nsGIOServiceConstructor },
+ { &kNS_GSETTINGSSERVICE_CID, false, nullptr, nsGSettingsServiceConstructor },
+ { &kNS_PACKAGEKITSERVICE_CID, false, nullptr, nsPackageKitServiceConstructor },
+#endif
+ { &kNS_SYSTEMALERTSSERVICE_CID, false, nullptr, nsSystemAlertsServiceConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kGnomeContracts[] = {
+#ifdef MOZ_ENABLE_GCONF
+ { NS_GCONFSERVICE_CONTRACTID, &kNS_GCONFSERVICE_CID },
+#endif
+#ifdef MOZ_ENABLE_GIO
+ { NS_GIOSERVICE_CONTRACTID, &kNS_GIOSERVICE_CID },
+ { NS_GSETTINGSSERVICE_CONTRACTID, &kNS_GSETTINGSSERVICE_CID },
+ { NS_PACKAGEKITSERVICE_CONTRACTID, &kNS_PACKAGEKITSERVICE_CID },
+#endif
+ { NS_SYSTEMALERTSERVICE_CONTRACTID, &kNS_SYSTEMALERTSSERVICE_CID },
+ { nullptr }
+};
+
+static nsresult
+InitGType ()
+{
+ g_type_init();
+ return NS_OK;
+}
+
+static const mozilla::Module kGnomeModule = {
+ mozilla::Module::kVersion,
+ kGnomeCIDs,
+ kGnomeContracts,
+ nullptr,
+ nullptr,
+ InitGType
+};
+
+NSMODULE_DEFN(mozgnome) = &kGnomeModule;
diff --git a/components/gservice/src/nsPackageKitService.cpp b/components/gservice/src/nsPackageKitService.cpp
new file mode 100644
index 000000000..c32a91367
--- /dev/null
+++ b/components/gservice/src/nsPackageKitService.cpp
@@ -0,0 +1,267 @@
+/* -*- 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 "nsArrayUtils.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsPackageKitService.h"
+#include "nsString.h"
+#include "prlink.h"
+#include "mozilla/Unused.h"
+#include "mozilla/UniquePtr.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <limits>
+
+using namespace mozilla;
+
+typedef struct _GAsyncResult GAsyncResult;
+typedef enum {
+ G_BUS_TYPE_STARTER = -1,
+ G_BUS_TYPE_NONE = 0,
+ G_BUS_TYPE_SYSTEM = 1,
+ G_BUS_TYPE_SESSION = 2
+} GBusType;
+typedef struct _GCancellable GCancellable;
+typedef enum {
+ G_DBUS_CALL_FLAGS_NONE = 0,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START = (1<<0)
+} GDBusCallFlags;
+typedef struct _GDBusInterfaceInfo GDBusInterfaceInfo;
+typedef struct _GDBusProxy GDBusProxy;
+typedef enum {
+ G_DBUS_PROXY_FLAGS_NONE = 0,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = (1<<0),
+ G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = (1<<1),
+ G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START = (1<<2),
+ G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES = (1<<3)
+} GDBusProxyFlags;
+typedef struct _GVariant GVariant;
+typedef void (*GAsyncReadyCallback) (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+#define GDBUS_FUNCTIONS \
+ FUNC(g_dbus_proxy_call, void, (GDBusProxy *proxy, const gchar *method_name, GVariant *parameters, GDBusCallFlags flags, gint timeout_msec, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)) \
+ FUNC(g_dbus_proxy_call_finish, GVariant*, (GDBusProxy *proxy, GAsyncResult *res, GError **error)) \
+ FUNC(g_dbus_proxy_new_finish, GDBusProxy*, (GAsyncResult *res, GError **error)) \
+ FUNC(g_dbus_proxy_new_for_bus, void, (GBusType bus_type, GDBusProxyFlags flags, GDBusInterfaceInfo *info, const gchar *name, const gchar *object_path, const gchar *interface_name, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)) \
+ FUNC(g_variant_is_floating, gboolean, (GVariant *value)) \
+ FUNC(g_variant_new, GVariant*, (const gchar *format_string, ...)) \
+ FUNC(g_variant_unref, void, (GVariant* value))
+
+#define FUNC(name, type, params) \
+ typedef type (*_##name##_fn) params; \
+ static _##name##_fn _##name;
+
+GDBUS_FUNCTIONS
+
+#undef FUNC
+
+#define g_dbus_proxy_call _g_dbus_proxy_call
+#define g_dbus_proxy_call_finish _g_dbus_proxy_call_finish
+#define g_dbus_proxy_new_finish _g_dbus_proxy_new_finish
+#define g_dbus_proxy_new_for_bus _g_dbus_proxy_new_for_bus
+#define g_variant_is_floating _g_variant_is_floating
+#define g_variant_new _g_variant_new
+#define g_variant_unref _g_variant_unref
+
+static PRLibrary *gioLib = nullptr;
+
+typedef void (*nsGDBusFunc)();
+struct nsGDBusDynamicFunction {
+ const char *functionName;
+ nsGDBusFunc *function;
+};
+
+nsresult
+nsPackageKitService::Init()
+{
+#define FUNC(name, type, params) { #name, (nsGDBusFunc *)&_##name },
+ const nsGDBusDynamicFunction kGDBusSymbols[] = {
+ GDBUS_FUNCTIONS
+ };
+#undef FUNC
+
+ if (!gioLib) {
+ gioLib = PR_LoadLibrary("libgio-2.0.so.0");
+ if (!gioLib)
+ return NS_ERROR_FAILURE;
+ }
+
+ for (uint32_t i = 0; i < ArrayLength(kGDBusSymbols); i++) {
+ *kGDBusSymbols[i].function =
+ PR_FindFunctionSymbol(gioLib, kGDBusSymbols[i].functionName);
+ if (!*kGDBusSymbols[i].function) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsPackageKitService, nsIPackageKitService)
+
+nsPackageKitService::~nsPackageKitService()
+{
+ if (gioLib) {
+ PR_UnloadLibrary(gioLib);
+ gioLib = nullptr;
+ }
+}
+
+static const char* InstallPackagesMethods[] = {
+ "InstallPackageNames",
+ "InstallMimeTypes",
+ "InstallFontconfigResources",
+ "InstallGStreamerResources"
+};
+
+struct InstallPackagesProxyNewData {
+ nsCOMPtr<nsIObserver> observer;
+ uint32_t method;
+ GVariant* parameters;
+};
+
+static void
+InstallPackagesNotifyObserver(nsIObserver* aObserver,
+ gchar* aErrorMessage)
+{
+ if (aObserver) {
+ aObserver->Observe(nullptr, "packagekit-install",
+ aErrorMessage ?
+ NS_ConvertUTF8toUTF16(aErrorMessage).get() :
+ nullptr);
+ }
+}
+
+static void
+InstallPackagesProxyCallCallback(GObject *aSourceObject,
+ GAsyncResult *aResult,
+ gpointer aUserData)
+{
+ nsCOMPtr<nsIObserver> observer = static_cast<nsIObserver*>(aUserData);
+ GDBusProxy* proxy = reinterpret_cast<GDBusProxy*>(aSourceObject);
+
+ GError* error = nullptr;
+ GVariant* result = g_dbus_proxy_call_finish(proxy, aResult, &error);
+ if (result) {
+ InstallPackagesNotifyObserver(observer, nullptr);
+ g_variant_unref(result);
+ } else {
+ NS_ASSERTION(error, "g_dbus_proxy_call_finish should set error when it returns NULL");
+ InstallPackagesNotifyObserver(observer, error->message);
+ g_error_free(error);
+ }
+
+ g_object_unref(proxy);
+ Unused << observer.forget().take();
+}
+
+static void
+InstallPackagesProxyNewCallback(GObject *aSourceObject,
+ GAsyncResult *aResult,
+ gpointer aUserData)
+{
+ InstallPackagesProxyNewData* userData =
+ static_cast<InstallPackagesProxyNewData*>(aUserData);
+
+ NS_ASSERTION(g_variant_is_floating(userData->parameters),
+ "userData->parameters should be a floating reference.");
+
+ GError* error = nullptr;
+ GDBusProxy* proxy = g_dbus_proxy_new_finish(aResult, &error);
+
+ if (proxy) {
+ // Send the asynchronous request to install the packages
+ // This call will consume the floating reference userData->parameters so we
+ // don't need to release it explicitly.
+ nsIObserver* observer;
+ userData->observer.forget(&observer);
+ g_dbus_proxy_call(proxy,
+ InstallPackagesMethods[userData->method],
+ userData->parameters,
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT,
+ nullptr,
+ &InstallPackagesProxyCallCallback,
+ static_cast<gpointer>(observer));
+ } else {
+ NS_ASSERTION(error, "g_dbus_proxy_new_finish should set error when it returns NULL");
+ InstallPackagesNotifyObserver(userData->observer, error->message);
+ g_error_free(error);
+ g_variant_unref(userData->parameters);
+ }
+ delete userData;
+}
+
+NS_IMETHODIMP
+nsPackageKitService::InstallPackages(uint32_t aInstallMethod,
+ nsIArray* aPackageArray,
+ nsIObserver* aObserver)
+{
+ NS_ENSURE_ARG(aPackageArray);
+
+ uint32_t arrayLength;
+ aPackageArray->GetLength(&arrayLength);
+ if (arrayLength == 0 ||
+ arrayLength == std::numeric_limits<uint32_t>::max() ||
+ aInstallMethod >= PK_INSTALL_METHOD_COUNT) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Create the GVariant* parameter from the list of packages.
+ GVariant* parameters = nullptr;
+ auto packages = MakeUnique<gchar*[]>(arrayLength + 1);
+
+ nsresult rv = NS_OK;
+ for (uint32_t i = 0; i < arrayLength; i++) {
+ nsCOMPtr<nsISupportsString> package =
+ do_QueryElementAt(aPackageArray, i);
+ if (!package) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ nsString data;
+ package->GetData(data);
+ packages[i] = g_strdup(NS_ConvertUTF16toUTF8(data).get());
+ if (!packages[i]) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+ }
+ packages[arrayLength] = nullptr;
+
+ if (NS_SUCCEEDED(rv)) {
+ // We create a new GVariant object to send parameters to PackageKit.
+ parameters = g_variant_new("(u^ass)", static_cast<guint32>(0),
+ packages.get(), "hide-finished");
+ if (!parameters) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ for (uint32_t i = 0; i < arrayLength; i++) {
+ g_free(packages[i]);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Send the asynchronous request to load the bus proxy
+ InstallPackagesProxyNewData* data = new InstallPackagesProxyNewData;
+ data->observer = aObserver;
+ data->method = aInstallMethod;
+ data->parameters = parameters;
+ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ nullptr,
+ "org.freedesktop.PackageKit",
+ "/org/freedesktop/PackageKit",
+ "org.freedesktop.PackageKit.Modify",
+ nullptr,
+ &InstallPackagesProxyNewCallback,
+ static_cast<gpointer>(data));
+ return NS_OK;
+}
diff --git a/components/gservice/src/nsPackageKitService.h b/components/gservice/src/nsPackageKitService.h
new file mode 100644
index 000000000..b14c7c51f
--- /dev/null
+++ b/components/gservice/src/nsPackageKitService.h
@@ -0,0 +1,26 @@
+/* -*- 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 nsPackageKitService_h_
+#define nsPackageKitService_h_
+
+#include "nsIPackageKitService.h"
+
+#define NS_PACKAGEKITSERVICE_CID \
+{0x9c95515e, 0x611d, 0x11e4, {0xb9, 0x7e, 0x60, 0xa4, 0x4c, 0x71, 0x70, 0x42}}
+
+class nsPackageKitService final : public nsIPackageKitService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPACKAGEKITSERVICE
+
+ nsresult Init();
+
+private:
+ ~nsPackageKitService();
+};
+
+#endif
diff --git a/components/gservice/src/nsSystemAlertsService.cpp b/components/gservice/src/nsSystemAlertsService.cpp
new file mode 100644
index 000000000..2af64383c
--- /dev/null
+++ b/components/gservice/src/nsSystemAlertsService.cpp
@@ -0,0 +1,118 @@
+/* -*- 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 "nsComponentManagerUtils.h"
+#include "nsXULAppAPI.h"
+#include "nsSystemAlertsService.h"
+#include "nsAlertsIconListener.h"
+#include "nsAutoPtr.h"
+
+NS_IMPL_ADDREF(nsSystemAlertsService)
+NS_IMPL_RELEASE(nsSystemAlertsService)
+
+NS_INTERFACE_MAP_BEGIN(nsSystemAlertsService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAlertsService)
+ NS_INTERFACE_MAP_ENTRY(nsIAlertsService)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsSystemAlertsService::nsSystemAlertsService()
+{
+}
+
+nsSystemAlertsService::~nsSystemAlertsService()
+{}
+
+nsresult
+nsSystemAlertsService::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSystemAlertsService::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle,
+ const nsAString & aAlertText, bool aAlertTextClickable,
+ const nsAString & aAlertCookie,
+ nsIObserver * aAlertListener,
+ const nsAString & aAlertName,
+ const nsAString & aBidi,
+ const nsAString & aLang,
+ const nsAString & aData,
+ nsIPrincipal * aPrincipal,
+ bool aInPrivateBrowsing,
+ bool aRequireInteraction)
+{
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
+ nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle,
+ aAlertText, aAlertTextClickable,
+ aAlertCookie, aBidi, aLang, aData,
+ aPrincipal, aInPrivateBrowsing,
+ aRequireInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP nsSystemAlertsService::ShowPersistentNotification(const nsAString& aPersistentData,
+ nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP nsSystemAlertsService::ShowAlert(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ NS_ENSURE_ARG(aAlert);
+
+ nsAutoString alertName;
+ nsresult rv = aAlert->GetName(alertName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsAlertsIconListener> alertListener =
+ new nsAlertsIconListener(this, alertName);
+ if (!alertListener)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ AddListener(alertName, alertListener);
+ return alertListener->InitAlertAsync(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP nsSystemAlertsService::CloseAlert(const nsAString& aAlertName,
+ nsIPrincipal* aPrincipal)
+{
+ RefPtr<nsAlertsIconListener> listener = mActiveListeners.Get(aAlertName);
+ if (!listener) {
+ return NS_OK;
+ }
+ mActiveListeners.Remove(aAlertName);
+ return listener->Close();
+}
+
+bool nsSystemAlertsService::IsActiveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener)
+{
+ return mActiveListeners.Get(aAlertName) == aListener;
+}
+
+void nsSystemAlertsService::AddListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener)
+{
+ RefPtr<nsAlertsIconListener> oldListener = mActiveListeners.Get(aAlertName);
+ mActiveListeners.Put(aAlertName, aListener);
+ if (oldListener) {
+ // If an alert with this name already exists, close it.
+ oldListener->Close();
+ }
+}
+
+void nsSystemAlertsService::RemoveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener)
+{
+ if (IsActiveListener(aAlertName, aListener)) {
+ // The alert may have been replaced; only remove it from the active
+ // listeners map if it's the same.
+ mActiveListeners.Remove(aAlertName);
+ }
+}
diff --git a/components/gservice/src/nsSystemAlertsService.h b/components/gservice/src/nsSystemAlertsService.h
new file mode 100644
index 000000000..66ae2ea27
--- /dev/null
+++ b/components/gservice/src/nsSystemAlertsService.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsSystemAlertsService_h__
+#define nsSystemAlertsService_h__
+
+#include "nsIAlertsService.h"
+#include "nsDataHashtable.h"
+#include "nsCOMPtr.h"
+
+class nsAlertsIconListener;
+
+class nsSystemAlertsService : public nsIAlertsService
+{
+public:
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_ISUPPORTS
+
+ nsSystemAlertsService();
+
+ nsresult Init();
+
+ bool IsActiveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener);
+ void RemoveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener);
+
+protected:
+ virtual ~nsSystemAlertsService();
+
+ void AddListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener);
+
+ nsDataHashtable<nsStringHashKey, nsAlertsIconListener*> mActiveListeners;
+};
+
+#endif /* nsSystemAlertsService_h__ */
diff --git a/components/handling/content/dialog.js b/components/handling/content/dialog.js
new file mode 100644
index 000000000..98a788201
--- /dev/null
+++ b/components/handling/content/dialog.js
@@ -0,0 +1,278 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 dialog builds its content based on arguments passed into it.
+ * window.arguments[0]:
+ * The title of the dialog.
+ * window.arguments[1]:
+ * The url of the image that appears to the left of the description text
+ * window.arguments[2]:
+ * The text of the description that will appear above the choices the user
+ * can choose from.
+ * window.arguments[3]:
+ * The text of the label directly above the choices the user can choose from.
+ * window.arguments[4]:
+ * This is the text to be placed in the label for the checkbox. If no text is
+ * passed (ie, it's an empty string), the checkbox will be hidden.
+ * window.arguments[5]:
+ * The accesskey for the checkbox
+ * window.arguments[6]:
+ * This is the text that is displayed below the checkbox when it is checked.
+ * window.arguments[7]:
+ * This is the nsIHandlerInfo that gives us all our precious information.
+ * window.arguments[8]:
+ * This is the nsIURI that we are being brought up for in the first place.
+ * window.arguments[9]:
+ * The nsIInterfaceRequestor of the parent window; may be null
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/SharedPromptUtils.jsm");
+
+
+var dialog = {
+ // Member Variables
+
+ _handlerInfo: null,
+ _URI: null,
+ _itemChoose: null,
+ _okButton: null,
+ _windowCtxt: null,
+ _buttonDisabled: true,
+
+ // Methods
+
+ /**
+ * This function initializes the content of the dialog.
+ */
+ initialize: function initialize()
+ {
+ this._handlerInfo = window.arguments[7].QueryInterface(Ci.nsIHandlerInfo);
+ this._URI = window.arguments[8].QueryInterface(Ci.nsIURI);
+ this._windowCtxt = window.arguments[9];
+ if (this._windowCtxt)
+ this._windowCtxt.QueryInterface(Ci.nsIInterfaceRequestor);
+ this._itemChoose = document.getElementById("item-choose");
+ this._okButton = document.documentElement.getButton("accept");
+
+ var description = {
+ image: document.getElementById("description-image"),
+ text: document.getElementById("description-text")
+ };
+ var options = document.getElementById("item-action-text");
+ var checkbox = {
+ desc: document.getElementById("remember"),
+ text: document.getElementById("remember-text")
+ };
+
+ // Setting values
+ document.title = window.arguments[0];
+ description.image.src = window.arguments[1];
+ description.text.textContent = window.arguments[2];
+ options.value = window.arguments[3];
+ checkbox.desc.label = window.arguments[4];
+ checkbox.desc.accessKey = window.arguments[5];
+ checkbox.text.textContent = window.arguments[6];
+
+ // Hide stuff that needs to be hidden
+ if (!checkbox.desc.label)
+ checkbox.desc.hidden = true;
+
+ // UI is ready, lets populate our list
+ this.populateList();
+
+ this._delayHelper = new EnableDelayHelper({
+ disableDialog: () => {
+ this._buttonDisabled = true;
+ this.updateOKButton();
+ },
+ enableDialog: () => {
+ this._buttonDisabled = false;
+ this.updateOKButton();
+ },
+ focusTarget: window
+ });
+ },
+
+ /**
+ * Populates the list that a user can choose from.
+ */
+ populateList: function populateList()
+ {
+ var items = document.getElementById("items");
+ var possibleHandlers = this._handlerInfo.possibleApplicationHandlers;
+ var preferredHandler = this._handlerInfo.preferredApplicationHandler;
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ for (let i = possibleHandlers.length - 1; i >= 0; --i) {
+ let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp);
+ let elm = document.createElement("richlistitem");
+ elm.setAttribute("type", "handler");
+ elm.setAttribute("name", app.name);
+ elm.obj = app;
+
+ if (app instanceof Ci.nsILocalHandlerApp) {
+ // See if we have an nsILocalHandlerApp and set the icon
+ let uri = ios.newFileURI(app.executable);
+ elm.setAttribute("image", "moz-icon://" + uri.spec + "?size=32");
+ }
+ else if (app instanceof Ci.nsIWebHandlerApp) {
+ let uri = ios.newURI(app.uriTemplate, null, null);
+ if (/^https?/.test(uri.scheme)) {
+ // 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).
+ elm.setAttribute("image", uri.prePath + "/favicon.ico");
+ }
+ elm.setAttribute("description", uri.prePath);
+ }
+ else if (app instanceof Ci.nsIDBusHandlerApp) {
+ elm.setAttribute("description", app.method);
+ }
+ else
+ throw "unknown handler type";
+
+ items.insertBefore(elm, this._itemChoose);
+ if (preferredHandler && app == preferredHandler)
+ this.selectedItem = elm;
+ }
+
+ if (this._handlerInfo.hasDefaultHandler) {
+ let elm = document.createElement("richlistitem");
+ elm.setAttribute("type", "handler");
+ elm.id = "os-default-handler";
+ elm.setAttribute("name", this._handlerInfo.defaultDescription);
+
+ items.insertBefore(elm, items.firstChild);
+ if (this._handlerInfo.preferredAction ==
+ Ci.nsIHandlerInfo.useSystemDefault)
+ this.selectedItem = elm;
+ }
+ items.ensureSelectedElementIsVisible();
+ },
+
+ /**
+ * Brings up a filepicker and allows a user to choose an application.
+ */
+ chooseApplication: function chooseApplication()
+ {
+ var bundle = document.getElementById("base-strings");
+ var title = bundle.getString("choose.application.title");
+
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ fp.init(window, title, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilters(Ci.nsIFilePicker.filterApps);
+
+ if (fp.show() == Ci.nsIFilePicker.returnOK && fp.file) {
+ let uri = Cc["@mozilla.org/network/util;1"].
+ getService(Ci.nsIIOService).
+ newFileURI(fp.file);
+
+ let handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ handlerApp.executable = fp.file;
+
+ // if this application is already in the list, select it and don't add it again
+ let parent = document.getElementById("items");
+ for (let i = 0; i < parent.childNodes.length; ++i) {
+ let elm = parent.childNodes[i];
+ if (elm.obj instanceof Ci.nsILocalHandlerApp && elm.obj.equals(handlerApp)) {
+ parent.selectedItem = elm;
+ parent.ensureSelectedElementIsVisible();
+ return;
+ }
+ }
+
+ let elm = document.createElement("richlistitem");
+ elm.setAttribute("type", "handler");
+ elm.setAttribute("name", fp.file.leafName);
+ elm.setAttribute("image", "moz-icon://" + uri.spec + "?size=32");
+ elm.obj = handlerApp;
+
+ parent.selectedItem = parent.insertBefore(elm, parent.firstChild);
+ parent.ensureSelectedElementIsVisible();
+ }
+ },
+
+ /**
+ * Function called when the OK button is pressed.
+ */
+ onAccept: function onAccept()
+ {
+ var checkbox = document.getElementById("remember");
+ if (!checkbox.hidden) {
+ // We need to make sure that the default is properly set now
+ if (this.selectedItem.obj) {
+ // default OS handler doesn't have this property
+ this._handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ this._handlerInfo.preferredApplicationHandler = this.selectedItem.obj;
+ }
+ else
+ this._handlerInfo.preferredAction = Ci.nsIHandlerInfo.useSystemDefault;
+ }
+ this._handlerInfo.alwaysAskBeforeHandling = !checkbox.checked;
+
+ var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+ hs.store(this._handlerInfo);
+
+ this._handlerInfo.launchWithURI(this._URI, this._windowCtxt);
+
+ return true;
+ },
+
+ /**
+ * Determines if the OK button should be disabled or not
+ */
+ updateOKButton: function updateOKButton()
+ {
+ this._okButton.disabled = this._itemChoose.selected ||
+ this._buttonDisabled;
+ },
+
+ /**
+ * Updates the UI based on the checkbox being checked or not.
+ */
+ onCheck: function onCheck()
+ {
+ if (document.getElementById("remember").checked)
+ document.getElementById("remember-text").setAttribute("visible", "true");
+ else
+ document.getElementById("remember-text").removeAttribute("visible");
+ },
+
+ /**
+ * Function called when the user double clicks on an item of the list
+ */
+ onDblClick: function onDblClick()
+ {
+ if (this.selectedItem == this._itemChoose)
+ this.chooseApplication();
+ else
+ document.documentElement.acceptDialog();
+ },
+
+ // Getters / Setters
+
+ /**
+ * Returns/sets the selected element in the richlistbox
+ */
+ get selectedItem()
+ {
+ return document.getElementById("items").selectedItem;
+ },
+ set selectedItem(aItem)
+ {
+ return document.getElementById("items").selectedItem = aItem;
+ }
+
+};
diff --git a/components/handling/content/dialog.xul b/components/handling/content/dialog.xul
new file mode 100644
index 000000000..2e2dab4e4
--- /dev/null
+++ b/components/handling/content/dialog.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<?xml-stylesheet href="chrome://mozapps/content/handling/handler.css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/handling/handling.css"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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 dialog SYSTEM "chrome://mozapps/locale/handling/handling.dtd">
+
+<dialog id="handling"
+ ondialogaccept="return dialog.onAccept();"
+ onload="dialog.initialize();"
+ style="min-width: &window.emWidth;; min-height: &window.emHeight;;"
+ persist="width height screenX screenY"
+ aria-describedby="description-text"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mozapps/content/handling/dialog.js" type="application/javascript"/>
+
+ <stringbundleset id="strings">
+ <stringbundle id="base-strings"
+ src="chrome://mozapps/locale/handling/handling.properties"/>
+ </stringbundleset>
+
+ <hbox>
+ <image id="description-image"/>
+ <description id="description-text"/>
+ </hbox>
+
+ <vbox flex="1">
+ <label id="item-action-text" control="items"/>
+ <richlistbox id="items" flex="1"
+ ondblclick="dialog.onDblClick();"
+ onselect="dialog.updateOKButton();">
+ <richlistitem id="item-choose" orient="horizontal" selected="true">
+ <label value="&ChooseOtherApp.description;" flex="1"/>
+ <button oncommand="dialog.chooseApplication();"
+ label="&ChooseApp.label;" accesskey="&ChooseApp.accessKey;"/>
+ </richlistitem>
+ </richlistbox>
+ </vbox>
+
+ <checkbox id="remember" aria-describedby="remember-text" oncommand="dialog.onCheck();"/>
+ <description id="remember-text"/>
+
+ <hbox class="dialog-button-box" pack="end">
+#ifdef XP_WIN
+ <button dlgtype="accept" label="&accept;" icon="open" class="dialog-button"/>
+ <button dlgtype="cancel" icon="cancel" class="dialog-button"/>
+#else
+ <button dlgtype="cancel" icon="cancel" class="dialog-button"/>
+ <button dlgtype="accept" label="&accept;" icon="open" class="dialog-button"/>
+#endif
+ </hbox>
+
+</dialog>
diff --git a/components/handling/content/handler.css b/components/handling/content/handler.css
new file mode 100644
index 000000000..438ab296d
--- /dev/null
+++ b/components/handling/content/handler.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="handler"] {
+ -moz-binding: url('chrome://mozapps/content/handling/handler.xml#handler');
+}
+
+#remember-text:not([visible]) {
+ visibility: hidden;
+}
diff --git a/components/handling/content/handler.xml b/components/handling/content/handler.xml
new file mode 100644
index 000000000..3fd907b45
--- /dev/null
+++ b/components/handling/content/handler.xml
@@ -0,0 +1,28 @@
+<?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="hanlder-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="handler"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+
+ <content>
+ <xul:vbox pack="center">
+ <xul:image xbl:inherits="src=image" height="32" width="32"/>
+ </xul:vbox>
+ <xul:vbox flex="1">
+ <xul:label class="name" xbl:inherits="value=name"/>
+ <xul:label class="description" xbl:inherits="value=description"/>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <property name="label" onget="return this.getAttribute('name') + ' ' + this.getAttribute('description');"/>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/handling/jar.mn b/components/handling/jar.mn
new file mode 100644
index 000000000..8c338a0db
--- /dev/null
+++ b/components/handling/jar.mn
@@ -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/.
+
+toolkit.jar:
+% content mozapps %content/mozapps/
+ content/mozapps/handling/handler.css (content/handler.css)
+ content/mozapps/handling/handler.xml (content/handler.xml)
+* content/mozapps/handling/dialog.xul (content/dialog.xul)
+ content/mozapps/handling/dialog.js (content/dialog.js)
diff --git a/components/handling/locale/handling.dtd b/components/handling/locale/handling.dtd
new file mode 100644
index 000000000..fe7a52566
--- /dev/null
+++ b/components/handling/locale/handling.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 window.emWidth "26em">
+<!ENTITY window.emHeight "26em">
+<!ENTITY ChooseOtherApp.description "Choose other Application">
+<!ENTITY ChooseApp.label "Choose…">
+<!ENTITY ChooseApp.accessKey "C">
+<!ENTITY accept "Open link">
diff --git a/components/handling/locale/handling.properties b/components/handling/locale/handling.properties
new file mode 100644
index 000000000..46d347ad0
--- /dev/null
+++ b/components/handling/locale/handling.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/.
+
+protocol.title=Launch Application
+protocol.description=This link needs to be opened with an application.
+protocol.choices.label=Send to:
+protocol.checkbox.label=Remember my choice for %S links.
+protocol.checkbox.accesskey=R
+protocol.checkbox.extra=This can be changed in %S’s preferences.
+
+choose.application.title=Another Application…
diff --git a/components/handling/moz.build b/components/handling/moz.build
new file mode 100644
index 000000000..038ad2c09
--- /dev/null
+++ b/components/handling/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'nsContentDispatchChooser.manifest',
+ 'src/nsContentDispatchChooser.js',
+]
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/handling/nsContentDispatchChooser.manifest b/components/handling/nsContentDispatchChooser.manifest
new file mode 100644
index 000000000..fcc76a410
--- /dev/null
+++ b/components/handling/nsContentDispatchChooser.manifest
@@ -0,0 +1,2 @@
+component {e35d5067-95bc-4029-8432-e8f1e431148d} nsContentDispatchChooser.js
+contract @mozilla.org/content-dispatch-chooser;1 {e35d5067-95bc-4029-8432-e8f1e431148d}
diff --git a/components/handling/src/nsContentDispatchChooser.js b/components/handling/src/nsContentDispatchChooser.js
new file mode 100644
index 000000000..e02545594
--- /dev/null
+++ b/components/handling/src/nsContentDispatchChooser.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Constants
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+const CONTENT_HANDLING_URL = "chrome://mozapps/content/handling/dialog.xul";
+const STRINGBUNDLE_URL = "chrome://mozapps/locale/handling/handling.properties";
+
+// nsContentDispatchChooser class
+
+function nsContentDispatchChooser()
+{
+}
+
+nsContentDispatchChooser.prototype =
+{
+ classID: Components.ID("e35d5067-95bc-4029-8432-e8f1e431148d"),
+
+ // nsIContentDispatchChooser
+
+ ask: function ask(aHandler, aWindowContext, aURI, aReason)
+ {
+ var window = null;
+ try {
+ if (aWindowContext)
+ window = aWindowContext.getInterface(Ci.nsIDOMWindow);
+ } catch (e) { /* it's OK to not have a window */ }
+
+ var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService);
+ var bundle = sbs.createBundle(STRINGBUNDLE_URL);
+
+ var xai = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo);
+ // TODO when this is hooked up for content, we will need different strings
+ // for most of these
+ var arr = [bundle.GetStringFromName("protocol.title"),
+ "",
+ bundle.GetStringFromName("protocol.description"),
+ bundle.GetStringFromName("protocol.choices.label"),
+ bundle.formatStringFromName("protocol.checkbox.label",
+ [aURI.scheme], 1),
+ bundle.GetStringFromName("protocol.checkbox.accesskey"),
+ bundle.formatStringFromName("protocol.checkbox.extra",
+ [xai.name], 1)];
+
+ var params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ let SupportsString = Components.Constructor(
+ "@mozilla.org/supports-string;1",
+ "nsISupportsString");
+ for (let text of arr) {
+ let string = new SupportsString;
+ string.data = text;
+ params.appendElement(string, false);
+ }
+ params.appendElement(aHandler, false);
+ params.appendElement(aURI, false);
+ params.appendElement(aWindowContext, false);
+
+ var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+ getService(Ci.nsIWindowWatcher);
+ ww.openWindow(window,
+ CONTENT_HANDLING_URL,
+ null,
+ "chrome,dialog=yes,resizable,centerscreen",
+ params);
+ },
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentDispatchChooser])
+};
+
+// Module
+
+var components = [nsContentDispatchChooser];
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/components/htmlfive/jArray.h b/components/htmlfive/jArray.h
new file mode 100644
index 000000000..98889059c
--- /dev/null
+++ b/components/htmlfive/jArray.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2008-2015 Mozilla Foundation
+ * Copyright (c) 2019 Moonchild Productions
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef jArray_h
+#define jArray_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/BinarySearch.h"
+#include "nsDebug.h"
+
+template<class T, class L>
+struct staticJArray {
+ const T* arr;
+ const L length;
+ operator T*() { return arr; }
+ T& operator[] (L const index) {
+ MOZ_ASSERT(index >= 0, "Array access with negative index.");
+ MOZ_ASSERT(index < length, "Array index out of bounds.");
+ return ((T*)arr)[index];
+ }
+ L binarySearch(T const elem) {
+ size_t idx;
+ bool found = mozilla::BinarySearch(arr, 0, length, elem, &idx);
+ return found ? idx : -1;
+ }
+};
+
+template <class T, class L>
+class autoJArray;
+
+template <class T, class L>
+class jArray {
+ friend class autoJArray<T, L>;
+
+private:
+ T* arr;
+
+public:
+ L length;
+
+ static jArray<T,L> newJArray(L const len) {
+ MOZ_ASSERT(len >= 0, "Negative length.");
+ jArray<T,L> newArray = { new T[size_t(len)], len };
+ return newArray;
+ }
+
+ static jArray<T,L> newFallibleJArray(L const len) {
+ MOZ_ASSERT(len >= 0, "Negative length.");
+ T* a = new (mozilla::fallible) T[size_t(len)];
+ jArray<T,L> newArray = { a, a ? len : 0 };
+ return newArray;
+ }
+
+ operator T*() {
+ return arr;
+ }
+
+ T& operator[] (L const index) {
+ MOZ_ASSERT(index >= 0, "Array access with negative index.");
+ MOZ_ASSERT(index < length, "Array index out of bounds.");
+ return arr[index];
+ }
+
+ void operator=(staticJArray<T,L>& other) {
+ arr = (T*)other.arr;
+ length = other.length;
+ }
+
+ MOZ_IMPLICIT jArray(decltype(nullptr))
+ : arr(nullptr)
+ , length(0)
+ {
+ }
+
+ jArray()
+ : arr(nullptr)
+ , length(0)
+ {
+ }
+
+private:
+ jArray(T* aArr, L aLength)
+ : arr(aArr)
+ , length(aLength)
+ {
+ }
+}; // class jArray
+
+template<class T, class L>
+class autoJArray {
+ private:
+ T* arr;
+ public:
+ L length;
+ autoJArray()
+ : arr(0)
+ , length(0)
+ {
+ }
+ MOZ_IMPLICIT autoJArray(const jArray<T,L>& other)
+ : arr(other.arr)
+ , length(other.length)
+ {
+ }
+ ~autoJArray()
+ {
+ delete[] arr;
+ }
+ operator T*() { return arr; }
+ T& operator[] (L const index) {
+ MOZ_ASSERT(index >= 0, "Array access with negative index.");
+ MOZ_ASSERT(index < length, "Array index out of bounds.");
+ return arr[index];
+ }
+ operator jArray<T,L>() {
+ // WARNING! This makes it possible to goof with buffer ownership!
+ // This is needed for the getStack and getListOfActiveFormattingElements
+ // methods to work sensibly.
+ jArray<T,L> newArray = { arr, length };
+ return newArray;
+ }
+ void operator=(const jArray<T,L>& other) {
+ delete[] arr;
+ arr = other.arr;
+ length = other.length;
+ }
+ void operator=(decltype(nullptr)) {
+ // Make assigning null to an array in Java delete the buffer in C++
+ delete[] arr;
+ arr = nullptr;
+ length = 0;
+ }
+};
+
+#endif // jArray_h
diff --git a/components/htmlfive/java/Makefile b/components/htmlfive/java/Makefile
new file mode 100644
index 000000000..8c3fe0140
--- /dev/null
+++ b/components/htmlfive/java/Makefile
@@ -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/.
+
+libs:: translator
+
+translator:: javaparser \
+; mkdir -p htmlparser/bin && \
+ find htmlparser/translator-src/nu/validator/htmlparser -name "*.java" | \
+ xargs javac -cp javaparser.jar -g -d htmlparser/bin && \
+ jar cfm translator.jar manifest.txt -C htmlparser/bin .
+
+javaparser:: \
+; mkdir -p javaparser/bin && find javaparser/src -name "*.java" | \
+ xargs javac -encoding ISO-8859-1 -g -d javaparser/bin && \
+ jar cf javaparser.jar -C javaparser/bin .
+
+translate:: translator \
+; mkdir -p ../javasrc ; \
+ java -jar translator.jar \
+ htmlparser/src/nu/validator/htmlparser/impl \
+ .. ../nsHtml5AtomList.h
+
+translate-javasrc:: translator \
+; mkdir -p ../javasrc ; \
+ java -jar translator.jar \
+ ../javasrc \
+ .. ../nsHtml5AtomList.h
+
+named-characters:: translator \
+; java -cp translator.jar \
+ nu.validator.htmlparser.generator.GenerateNamedCharactersCpp \
+ named-character-references.html ../
+
+clean-javaparser:: \
+; rm -rf javaparser/bin javaparser.jar
+
+clean-htmlparser:: \
+; rm -rf htmlparser/bin translator.jar
+
+clean-javasrc:: \
+; rm -rf ../javasrc
+
+clean:: clean-javaparser clean-htmlparser clean-javasrc
diff --git a/components/htmlfive/java/README.txt b/components/htmlfive/java/README.txt
new file mode 100644
index 000000000..569a5c842
--- /dev/null
+++ b/components/htmlfive/java/README.txt
@@ -0,0 +1,78 @@
+If this is your first time building the HTML5 parser, you need to execute the
+following commands (from this directory) to accomplish the translation:
+
+ make translate # perform the Java-to-C++ translation from the remote
+ # sources
+ make named_characters # Generate tables for named character tokenization
+
+If you make changes to the translator or the javaparser, you can rebuild by
+retyping 'make' in this directory. If you make changes to the HTML5 Java
+implementation, you can retranslate the Java sources from the htmlparser
+repository by retyping 'make translate' in this directory.
+
+The makefile supports the following targets:
+
+javaparser:
+ Builds the javaparser library retrieved earlier by sync_javaparser.
+translator:
+ Runs the javaparser target and then builds the Java to C++ translator from
+ sources.
+libs:
+ The default target. Alias for translator
+translate:
+ Runs the translator target and then translates the HTML parser sources and
+ copys the parser impl java sources to ../javasrc.
+translate-javasrc:
+ Runs the translator target and then translates the HTML parser sources
+ stored in ../javasrc. (Depercated)
+named-characters:
+ Generates data tables for named character tokenization.
+clean_-avaparser:
+ Removes the build products of the javaparser target.
+clean-htmlparser:
+ Removes the build products of the translator target.
+clean-javasrc:
+ Removes the javasrc snapshot code in ../javasrc
+clean:
+ Runs clean-javaparser, clean-htmlparser, and clean-javasrc.
+
+## How to add an attribute
+
+# starting from the root of a UXP checkout
+cd parser/html/java/htmlparser/src/
+$EDITOR nu/validator/htmlparser/impl/AttributeName.java
+# Search for the word "uncomment" and uncomment stuff according to the comments that talk about uncommenting
+# Duplicate the declaration a normal attribute (nothings special in SVG mode, etc.). Let's use "alt", since it's the first one.
+# In the duplicate, replace ALT with the new name in all caps and "alt" with the new name in quotes in lower case.
+# Search for "ALT,", duplicate that line and change the duplicate to say the new name in all caps followed by comma.
+# Save.
+javac nu/validator/htmlparser/impl/AttributeName.java
+java nu.validator.htmlparser.impl.AttributeName
+# Copy and paste the output into nu/validator/htmlparser/impl/AttributeName.java replacing the text below the comment "START GENERATED CODE" and above the very last "}".
+# Recomment the bits that you uncommented earlier.
+# Save.
+cd ../.. # Back to parser/html/java/
+make translate
+
+## How to add an element
+
+# First, add an entry to parser/htmlparser/nsHTMLTagList.h or dom/svg/SVGTagList.h!
+# Then, starting from the root of a UXP checkout
+cd parser/html/java/htmlparser/src/
+$EDITOR nu/validator/htmlparser/impl/ElementName.java
+# Search for the word "uncomment" and uncomment stuff according to the comments that talk about uncommenting
+# Duplicate the declaration a normal element. Let's use "bdo", since it's the first normal one.
+# In the duplicate, replace BDO with the new name in all caps and "bdo" with the new name in quotes in lower case (twice).
+# Search for "BDO,", duplicate that line and change the duplicate to say the new name in all caps followed by comma.
+# Save.
+javac nu/validator/htmlparser/impl/ElementName.java
+java nu.validator.htmlparser.impl.ElementName ../../../../../parser/htmlparser/nsHTMLTagList.h ../../../../../dom/svg/SVGTagList.h
+# Copy and paste the output into nu/validator/htmlparser/impl/ElementName.java replacing the text below the comment "START GENERATED CODE" and above the very last "}".
+# Recomment the bits that you uncommented earlier.
+# Save.
+cd ../.. # Back to parser/html/java/
+make translate
+
+Ben Newman (23 September 2009)
+Henri Sivonen (11 August 2016)
+Matt A. Tobin (16 January 2020)
diff --git a/components/htmlfive/java/htmlparser/HtmlParser-compile b/components/htmlfive/java/htmlparser/HtmlParser-compile
new file mode 100644
index 000000000..3e867827f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/HtmlParser-compile
@@ -0,0 +1,3 @@
+#!/bin/sh
+APPDIR=`dirname $0`;
+java -XstartOnFirstThread -Xmx256M -cp "$APPDIR/src:$APPDIR/gwt-src:$APPDIR/super:/Developer/gwt-mac-1.5.1/gwt-user.jar:/Developer/gwt-mac-1.5.1/gwt-dev-mac.jar" com.google.gwt.dev.GWTCompiler -out "$APPDIR/www" "$@" nu.validator.htmlparser.HtmlParser;
diff --git a/components/htmlfive/java/htmlparser/HtmlParser-compile-detailed b/components/htmlfive/java/htmlparser/HtmlParser-compile-detailed
new file mode 100644
index 000000000..a4102d642
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/HtmlParser-compile-detailed
@@ -0,0 +1,3 @@
+#!/bin/sh
+APPDIR=`dirname $0`;
+java -XstartOnFirstThread -Xmx256M -cp "$APPDIR/src:$APPDIR/gwt-src:$APPDIR/super:/Developer/gwt-mac-1.5.1/gwt-user.jar:/Developer/gwt-mac-1.5.1/gwt-dev-mac.jar" com.google.gwt.dev.GWTCompiler -style DETAILED -out "$APPDIR/www" "$@" nu.validator.htmlparser.HtmlParser;
diff --git a/components/htmlfive/java/htmlparser/HtmlParser-compile-detailed.launch b/components/htmlfive/java/htmlparser/HtmlParser-compile-detailed.launch
new file mode 100644
index 000000000..0347fd6cf
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/HtmlParser-compile-detailed.launch
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/htmlparser"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;htmlparser&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/htmlparser/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/htmlparser/gwt-src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/htmlparser/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;htmlparser&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry externalArchive=&quot;/Developer/gwt-mac-1.5.1/gwt-dev-mac.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry externalArchive=&quot;/Developer/gwt-mac-1.5.1/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.GWTCompiler"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-style DETAILED -out /Users/hsivonen/Projects/whattf/htmlparser/www nu.validator.htmlparser.HtmlParser"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="htmlparser"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XstartOnFirstThread -Xmx256M"/>
+</launchConfiguration>
diff --git a/components/htmlfive/java/htmlparser/HtmlParser-compile.launch b/components/htmlfive/java/htmlparser/HtmlParser-compile.launch
new file mode 100644
index 000000000..54e7bc337
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/HtmlParser-compile.launch
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/htmlparser"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;htmlparser&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/htmlparser/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/htmlparser/gwt-src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/htmlparser/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#10;&lt;memento exportedEntriesOnly=&quot;false&quot; project=&quot;htmlparser&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.GWTCompiler"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-out /home/hsivonen/Projects/whattf/htmlparser/www nu.validator.htmlparser.HtmlParser"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="htmlparser"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx256M"/>
+</launchConfiguration>
diff --git a/components/htmlfive/java/htmlparser/HtmlParser-linux b/components/htmlfive/java/htmlparser/HtmlParser-linux
new file mode 100644
index 000000000..0a9e9deff
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/HtmlParser-linux
@@ -0,0 +1,3 @@
+#!/bin/sh
+APPDIR=`dirname $0`;
+java -Xmx256M -cp "$APPDIR/src:$APPDIR/gwt-src:$APPDIR/super:$APPDIR/bin:/home/hsivonen/gwt-linux-1.5.1/gwt-user.jar:/home/hsivonen/gwt-linux-1.5.1/gwt-dev-linux.jar" com.google.gwt.dev.GWTShell -out "$APPDIR/www" "$@" nu.validator.htmlparser.HtmlParser/HtmlParser.html;
diff --git a/components/htmlfive/java/htmlparser/HtmlParser-shell b/components/htmlfive/java/htmlparser/HtmlParser-shell
new file mode 100644
index 000000000..ffcf2e297
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/HtmlParser-shell
@@ -0,0 +1,3 @@
+#!/bin/sh
+APPDIR=`dirname $0`;
+java -XstartOnFirstThread -Xmx256M -cp "$APPDIR/src:$APPDIR/gwt-src:$APPDIR/super:$APPDIR/bin:/Developer/gwt-mac-1.5.1/gwt-user.jar:/Developer/gwt-mac-1.5.1/gwt-dev-mac.jar" com.google.gwt.dev.GWTShell -out "$APPDIR/www" "$@" nu.validator.htmlparser.HtmlParser/HtmlParser.html;
diff --git a/components/htmlfive/java/htmlparser/HtmlParser.launch b/components/htmlfive/java/htmlparser/HtmlParser.launch
new file mode 100644
index 000000000..9335abf60
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/HtmlParser.launch
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/htmlparser"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
+<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER&quot; javaProject=&quot;htmlparser&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/htmlparser/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/htmlparser/gwt-src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/htmlparser/super&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.jdt.launching.classpathentry.defaultClasspath&quot;&gt;&#13;&#10;&lt;memento project=&quot;htmlparser&quot;/&gt;&#13;&#10;&lt;/runtimeClasspathEntry&gt;&#13;&#10;"/>
+<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#13;&#10;&lt;runtimeClasspathEntry externalArchive=&quot;/Developer/gwt-mac-1.5.1/gwt-dev-mac.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#13;&#10;"/>
+</listAttribute>
+<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.GWTShell"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-out www nu.validator.htmlparser.HtmlParser/HtmlParser.html"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="htmlparser"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XstartOnFirstThread -Xmx256M"/>
+</launchConfiguration>
diff --git a/components/htmlfive/java/htmlparser/LICENSE.txt b/components/htmlfive/java/htmlparser/LICENSE.txt
new file mode 100644
index 000000000..4bfe5d331
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/LICENSE.txt
@@ -0,0 +1,96 @@
+This is for the HTML parser as a whole except the rewindable input stream,
+the named character classes and the Live DOM Viewer.
+For the copyright notices for individual files, please see individual files.
+
+/*
+ * Copyright (c) 2005, 2006, 2007 Henri Sivonen
+ * Copyright (c) 2007-2012 Mozilla Foundation
+ * Portions of comments Copyright 2004-2007 Apple Computer, Inc., Mozilla
+ * Foundation, and Opera Software ASA.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+The following license is for the WHATWG spec from which the named character
+data was extracted.
+
+/*
+ * Copyright 2004-2010 Apple Computer, Inc., Mozilla Foundation, and Opera
+ * Software ASA.
+ *
+ * You are granted a license to use, reproduce and create derivative works of
+ * this document.
+ */
+
+The following license is for the rewindable input stream.
+
+/*
+ * Copyright (c) 2001-2003 Thai Open Source Software Center Ltd
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of the Thai Open Source Software Center Ltd nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+The following license applies to the Live DOM Viewer:
+
+Copyright (c) 2000, 2006, 2008 Ian Hickson and various contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/components/htmlfive/java/htmlparser/README.txt b/components/htmlfive/java/htmlparser/README.txt
new file mode 100644
index 000000000..713b404e8
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/README.txt
@@ -0,0 +1,5 @@
+An HTML5 parser.
+
+Please see http://about.validator.nu/htmlparser/
+
+-- Henri Sivonen (hsivonen@iki.fi).
diff --git a/components/htmlfive/java/htmlparser/doc/README b/components/htmlfive/java/htmlparser/doc/README
new file mode 100644
index 000000000..e0132a41e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/doc/README
@@ -0,0 +1,15 @@
+tokenization.txt represents the state of the spec implemented in Tokenizer.java.
+
+To get a diffable version corresponding to the current spec:
+lynx -display_charset=utf-8 -dump -nolist http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html > current.txt
+
+tree-construction.txt represents the state of the spec implemented in TreeBuilder.java.
+
+To get a diffable version corresponding to the current spec:
+lynx -display_charset=utf-8 -dump -nolist http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html > current.txt
+
+
+The text of the files in this directory comes from the WHATWG HTML 5 spec
+which carries the following notice:
+© Copyright 2004-2010 Apple Computer, Inc., Mozilla Foundation, and Opera Software ASA.
+You are granted a license to use, reproduce and create derivative works of this document.
diff --git a/components/htmlfive/java/htmlparser/doc/named-character-references.html b/components/htmlfive/java/htmlparser/doc/named-character-references.html
new file mode 100644
index 000000000..5f05a991f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/doc/named-character-references.html
@@ -0,0 +1,4 @@
+<!-- spec rev 5682 -->
+
+ <table><thead><tr><th> Name </th> <th> Character(s) </th> <th> Glyph </th> <tbody><tr id="entity-AElig"><td> <code title="">AElig;</code> </td> <td> U+000C6 </td> <td> <span class="glyph" title="">&AElig;</span> </td> <tr id="entity-AMP"><td> <code title="">AMP;</code> </td> <td> U+00026 </td> <td> <span class="glyph" title="">&amp;</span> </td> <tr id="entity-Aacute"><td> <code title="">Aacute;</code> </td> <td> U+000C1 </td> <td> <span class="glyph" title="">&Aacute;</span> </td> <tr id="entity-Abreve"><td> <code title="">Abreve;</code> </td> <td> U+00102 </td> <td> <span class="glyph" title="">&#258;</span> </td> <tr id="entity-Acirc"><td> <code title="">Acirc;</code> </td> <td> U+000C2 </td> <td> <span class="glyph" title="">&Acirc;</span> </td> <tr id="entity-Acy"><td> <code title="">Acy;</code> </td> <td> U+00410 </td> <td> <span class="glyph" title="">&#1040;</span> </td> <tr id="entity-Afr"><td> <code title="">Afr;</code> </td> <td> U+1D504 </td> <td> <span class="glyph" title="">&#120068;</span> </td> <tr id="entity-Agrave"><td> <code title="">Agrave;</code> </td> <td> U+000C0 </td> <td> <span class="glyph" title="">&Agrave;</span> </td> <tr id="entity-Alpha"><td> <code title="">Alpha;</code> </td> <td> U+00391 </td> <td> <span class="glyph" title="">&Alpha;</span> </td> <tr id="entity-Amacr"><td> <code title="">Amacr;</code> </td> <td> U+00100 </td> <td> <span class="glyph" title="">&#256;</span> </td> <tr id="entity-And"><td> <code title="">And;</code> </td> <td> U+02A53 </td> <td> <span class="glyph" title="">&#10835;</span> </td> <tr id="entity-Aogon"><td> <code title="">Aogon;</code> </td> <td> U+00104 </td> <td> <span class="glyph" title="">&#260;</span> </td> <tr id="entity-Aopf"><td> <code title="">Aopf;</code> </td> <td> U+1D538 </td> <td> <span class="glyph" title="">&#120120;</span> </td> <tr id="entity-ApplyFunction"><td> <code title="">ApplyFunction;</code> </td> <td> U+02061 </td> <td> <span class="glyph" title="">&#8289;</span> </td> <tr id="entity-Aring"><td> <code title="">Aring;</code> </td> <td> U+000C5 </td> <td> <span class="glyph" title="">&Aring;</span> </td> <tr id="entity-Ascr"><td> <code title="">Ascr;</code> </td> <td> U+1D49C </td> <td> <span class="glyph" title="">&#119964;</span> </td> <tr id="entity-Assign"><td> <code title="">Assign;</code> </td> <td> U+02254 </td> <td> <span class="glyph" title="">&#8788;</span> </td> <tr id="entity-Atilde"><td> <code title="">Atilde;</code> </td> <td> U+000C3 </td> <td> <span class="glyph" title="">&Atilde;</span> </td> <tr id="entity-Auml"><td> <code title="">Auml;</code> </td> <td> U+000C4 </td> <td> <span class="glyph" title="">&Auml;</span> </td> <tr id="entity-Backslash"><td> <code title="">Backslash;</code> </td> <td> U+02216 </td> <td> <span class="glyph" title="">&#8726;</span> </td> <tr id="entity-Barv"><td> <code title="">Barv;</code> </td> <td> U+02AE7 </td> <td> <span class="glyph" title="">&#10983;</span> </td> <tr id="entity-Barwed"><td> <code title="">Barwed;</code> </td> <td> U+02306 </td> <td> <span class="glyph" title="">&#8966;</span> </td> <tr id="entity-Bcy"><td> <code title="">Bcy;</code> </td> <td> U+00411 </td> <td> <span class="glyph" title="">&#1041;</span> </td> <tr id="entity-Because"><td> <code title="">Because;</code> </td> <td> U+02235 </td> <td> <span class="glyph" title="">&#8757;</span> </td> <tr id="entity-Bernoullis"><td> <code title="">Bernoullis;</code> </td> <td> U+0212C </td> <td> <span class="glyph" title="">&#8492;</span> </td> <tr id="entity-Beta"><td> <code title="">Beta;</code> </td> <td> U+00392 </td> <td> <span class="glyph" title="">&Beta;</span> </td> <tr id="entity-Bfr"><td> <code title="">Bfr;</code> </td> <td> U+1D505 </td> <td> <span class="glyph" title="">&#120069;</span> </td> <tr id="entity-Bopf"><td> <code title="">Bopf;</code> </td> <td> U+1D539 </td> <td> <span class="glyph" title="">&#120121;</span> </td> <tr id="entity-Breve"><td> <code title="">Breve;</code> </td> <td> U+002D8 </td> <td> <span class="glyph" title="">&#728;</span> </td> <tr id="entity-Bscr"><td> <code title="">Bscr;</code> </td> <td> U+0212C </td> <td> <span class="glyph" title="">&#8492;</span> </td> <tr id="entity-Bumpeq"><td> <code title="">Bumpeq;</code> </td> <td> U+0224E </td> <td> <span class="glyph" title="">&#8782;</span> </td> <tr id="entity-CHcy"><td> <code title="">CHcy;</code> </td> <td> U+00427 </td> <td> <span class="glyph" title="">&#1063;</span> </td> <tr id="entity-COPY"><td> <code title="">COPY;</code> </td> <td> U+000A9 </td> <td> <span class="glyph" title="">&copy;</span> </td> <tr id="entity-Cacute"><td> <code title="">Cacute;</code> </td> <td> U+00106 </td> <td> <span class="glyph" title="">&#262;</span> </td> <tr id="entity-Cap"><td> <code title="">Cap;</code> </td> <td> U+022D2 </td> <td> <span class="glyph" title="">&#8914;</span> </td> <tr id="entity-CapitalDifferentialD"><td> <code title="">CapitalDifferentialD;</code> </td> <td> U+02145 </td> <td> <span class="glyph" title="">&#8517;</span> </td> <tr id="entity-Cayleys"><td> <code title="">Cayleys;</code> </td> <td> U+0212D </td> <td> <span class="glyph" title="">&#8493;</span> </td> <tr id="entity-Ccaron"><td> <code title="">Ccaron;</code> </td> <td> U+0010C </td> <td> <span class="glyph" title="">&#268;</span> </td> <tr id="entity-Ccedil"><td> <code title="">Ccedil;</code> </td> <td> U+000C7 </td> <td> <span class="glyph" title="">&Ccedil;</span> </td> <tr id="entity-Ccirc"><td> <code title="">Ccirc;</code> </td> <td> U+00108 </td> <td> <span class="glyph" title="">&#264;</span> </td> <tr id="entity-Cconint"><td> <code title="">Cconint;</code> </td> <td> U+02230 </td> <td> <span class="glyph" title="">&#8752;</span> </td> <tr id="entity-Cdot"><td> <code title="">Cdot;</code> </td> <td> U+0010A </td> <td> <span class="glyph" title="">&#266;</span> </td> <tr id="entity-Cedilla"><td> <code title="">Cedilla;</code> </td> <td> U+000B8 </td> <td> <span class="glyph" title="">&cedil;</span> </td> <tr id="entity-CenterDot"><td> <code title="">CenterDot;</code> </td> <td> U+000B7 </td> <td> <span class="glyph" title="">&middot;</span> </td> <tr id="entity-Cfr"><td> <code title="">Cfr;</code> </td> <td> U+0212D </td> <td> <span class="glyph" title="">&#8493;</span> </td> <tr id="entity-Chi"><td> <code title="">Chi;</code> </td> <td> U+003A7 </td> <td> <span class="glyph" title="">&Chi;</span> </td> <tr id="entity-CircleDot"><td> <code title="">CircleDot;</code> </td> <td> U+02299 </td> <td> <span class="glyph" title="">&#8857;</span> </td> <tr id="entity-CircleMinus"><td> <code title="">CircleMinus;</code> </td> <td> U+02296 </td> <td> <span class="glyph" title="">&#8854;</span> </td> <tr id="entity-CirclePlus"><td> <code title="">CirclePlus;</code> </td> <td> U+02295 </td> <td> <span class="glyph" title="">&oplus;</span> </td> <tr id="entity-CircleTimes"><td> <code title="">CircleTimes;</code> </td> <td> U+02297 </td> <td> <span class="glyph" title="">&otimes;</span> </td> <tr id="entity-ClockwiseContourIntegral"><td> <code title="">ClockwiseContourIntegral;</code> </td> <td> U+02232 </td> <td> <span class="glyph" title="">&#8754;</span> </td> <tr id="entity-CloseCurlyDoubleQuote"><td> <code title="">CloseCurlyDoubleQuote;</code> </td> <td> U+0201D </td> <td> <span class="glyph" title="">&rdquo;</span> </td> <tr id="entity-CloseCurlyQuote"><td> <code title="">CloseCurlyQuote;</code> </td> <td> U+02019 </td> <td> <span class="glyph" title="">&rsquo;</span> </td> <tr id="entity-Colon"><td> <code title="">Colon;</code> </td> <td> U+02237 </td> <td> <span class="glyph" title="">&#8759;</span> </td> <tr id="entity-Colone"><td> <code title="">Colone;</code> </td> <td> U+02A74 </td> <td> <span class="glyph" title="">&#10868;</span> </td> <tr id="entity-Congruent"><td> <code title="">Congruent;</code> </td> <td> U+02261 </td> <td> <span class="glyph" title="">&equiv;</span> </td> <tr id="entity-Conint"><td> <code title="">Conint;</code> </td> <td> U+0222F </td> <td> <span class="glyph" title="">&#8751;</span> </td> <tr id="entity-ContourIntegral"><td> <code title="">ContourIntegral;</code> </td> <td> U+0222E </td> <td> <span class="glyph" title="">&#8750;</span> </td> <tr id="entity-Copf"><td> <code title="">Copf;</code> </td> <td> U+02102 </td> <td> <span class="glyph" title="">&#8450;</span> </td> <tr id="entity-Coproduct"><td> <code title="">Coproduct;</code> </td> <td> U+02210 </td> <td> <span class="glyph" title="">&#8720;</span> </td> <tr id="entity-CounterClockwiseContourIntegral"><td> <code title="">CounterClockwiseContourIntegral;</code> </td> <td> U+02233 </td> <td> <span class="glyph" title="">&#8755;</span> </td> <tr id="entity-Cross"><td> <code title="">Cross;</code> </td> <td> U+02A2F </td> <td> <span class="glyph" title="">&#10799;</span> </td> <tr id="entity-Cscr"><td> <code title="">Cscr;</code> </td> <td> U+1D49E </td> <td> <span class="glyph" title="">&#119966;</span> </td> <tr id="entity-Cup"><td> <code title="">Cup;</code> </td> <td> U+022D3 </td> <td> <span class="glyph" title="">&#8915;</span> </td> <tr id="entity-CupCap"><td> <code title="">CupCap;</code> </td> <td> U+0224D </td> <td> <span class="glyph" title="">&#8781;</span> </td> <tr id="entity-DD"><td> <code title="">DD;</code> </td> <td> U+02145 </td> <td> <span class="glyph" title="">&#8517;</span> </td> <tr id="entity-DDotrahd"><td> <code title="">DDotrahd;</code> </td> <td> U+02911 </td> <td> <span class="glyph" title="">&#10513;</span> </td> <tr id="entity-DJcy"><td> <code title="">DJcy;</code> </td> <td> U+00402 </td> <td> <span class="glyph" title="">&#1026;</span> </td> <tr id="entity-DScy"><td> <code title="">DScy;</code> </td> <td> U+00405 </td> <td> <span class="glyph" title="">&#1029;</span> </td> <tr id="entity-DZcy"><td> <code title="">DZcy;</code> </td> <td> U+0040F </td> <td> <span class="glyph" title="">&#1039;</span> </td> <tr id="entity-Dagger"><td> <code title="">Dagger;</code> </td> <td> U+02021 </td> <td> <span class="glyph" title="">&Dagger;</span> </td> <tr id="entity-Darr"><td> <code title="">Darr;</code> </td> <td> U+021A1 </td> <td> <span class="glyph" title="">&#8609;</span> </td> <tr id="entity-Dashv"><td> <code title="">Dashv;</code> </td> <td> U+02AE4 </td> <td> <span class="glyph" title="">&#10980;</span> </td> <tr id="entity-Dcaron"><td> <code title="">Dcaron;</code> </td> <td> U+0010E </td> <td> <span class="glyph" title="">&#270;</span> </td> <tr id="entity-Dcy"><td> <code title="">Dcy;</code> </td> <td> U+00414 </td> <td> <span class="glyph" title="">&#1044;</span> </td> <tr id="entity-Del"><td> <code title="">Del;</code> </td> <td> U+02207 </td> <td> <span class="glyph" title="">&nabla;</span> </td> <tr id="entity-Delta"><td> <code title="">Delta;</code> </td> <td> U+00394 </td> <td> <span class="glyph" title="">&Delta;</span> </td> <tr id="entity-Dfr"><td> <code title="">Dfr;</code> </td> <td> U+1D507 </td> <td> <span class="glyph" title="">&#120071;</span> </td> <tr id="entity-DiacriticalAcute"><td> <code title="">DiacriticalAcute;</code> </td> <td> U+000B4 </td> <td> <span class="glyph" title="">&acute;</span> </td> <tr id="entity-DiacriticalDot"><td> <code title="">DiacriticalDot;</code> </td> <td> U+002D9 </td> <td> <span class="glyph" title="">&#729;</span> </td> <tr id="entity-DiacriticalDoubleAcute"><td> <code title="">DiacriticalDoubleAcute;</code> </td> <td> U+002DD </td> <td> <span class="glyph" title="">&#733;</span> </td> <tr id="entity-DiacriticalGrave"><td> <code title="">DiacriticalGrave;</code> </td> <td> U+00060 </td> <td> <span class="glyph" title="">`</span> </td> <tr id="entity-DiacriticalTilde"><td> <code title="">DiacriticalTilde;</code> </td> <td> U+002DC </td> <td> <span class="glyph" title="">&tilde;</span> </td> <tr id="entity-Diamond"><td> <code title="">Diamond;</code> </td> <td> U+022C4 </td> <td> <span class="glyph" title="">&#8900;</span> </td> <tr id="entity-DifferentialD"><td> <code title="">DifferentialD;</code> </td> <td> U+02146 </td> <td> <span class="glyph" title="">&#8518;</span> </td> <tr id="entity-Dopf"><td> <code title="">Dopf;</code> </td> <td> U+1D53B </td> <td> <span class="glyph" title="">&#120123;</span> </td> <tr id="entity-Dot"><td> <code title="">Dot;</code> </td> <td> U+000A8 </td> <td> <span class="glyph" title="">&uml;</span> </td> <tr id="entity-DotDot"><td> <code title="">DotDot;</code> </td> <td> U+020DC </td> <td> <span class="glyph composition" title="">&#9676;&#8412;</span> </td> <tr id="entity-DotEqual"><td> <code title="">DotEqual;</code> </td> <td> U+02250 </td> <td> <span class="glyph" title="">&#8784;</span> </td> <tr id="entity-DoubleContourIntegral"><td> <code title="">DoubleContourIntegral;</code> </td> <td> U+0222F </td> <td> <span class="glyph" title="">&#8751;</span> </td> <tr id="entity-DoubleDot"><td> <code title="">DoubleDot;</code> </td> <td> U+000A8 </td> <td> <span class="glyph" title="">&uml;</span> </td> <tr id="entity-DoubleDownArrow"><td> <code title="">DoubleDownArrow;</code> </td> <td> U+021D3 </td> <td> <span class="glyph" title="">&dArr;</span> </td> <tr id="entity-DoubleLeftArrow"><td> <code title="">DoubleLeftArrow;</code> </td> <td> U+021D0 </td> <td> <span class="glyph" title="">&lArr;</span> </td> <tr id="entity-DoubleLeftRightArrow"><td> <code title="">DoubleLeftRightArrow;</code> </td> <td> U+021D4 </td> <td> <span class="glyph" title="">&hArr;</span> </td> <tr id="entity-DoubleLeftTee"><td> <code title="">DoubleLeftTee;</code> </td> <td> U+02AE4 </td> <td> <span class="glyph" title="">&#10980;</span> </td> <tr id="entity-DoubleLongLeftArrow"><td> <code title="">DoubleLongLeftArrow;</code> </td> <td> U+027F8 </td> <td> <span class="glyph" title="">&#10232;</span> </td> <tr id="entity-DoubleLongLeftRightArrow"><td> <code title="">DoubleLongLeftRightArrow;</code> </td> <td> U+027FA </td> <td> <span class="glyph" title="">&#10234;</span> </td> <tr id="entity-DoubleLongRightArrow"><td> <code title="">DoubleLongRightArrow;</code> </td> <td> U+027F9 </td> <td> <span class="glyph" title="">&#10233;</span> </td> <tr id="entity-DoubleRightArrow"><td> <code title="">DoubleRightArrow;</code> </td> <td> U+021D2 </td> <td> <span class="glyph" title="">&rArr;</span> </td> <tr id="entity-DoubleRightTee"><td> <code title="">DoubleRightTee;</code> </td> <td> U+022A8 </td> <td> <span class="glyph" title="">&#8872;</span> </td> <tr id="entity-DoubleUpArrow"><td> <code title="">DoubleUpArrow;</code> </td> <td> U+021D1 </td> <td> <span class="glyph" title="">&uArr;</span> </td> <tr id="entity-DoubleUpDownArrow"><td> <code title="">DoubleUpDownArrow;</code> </td> <td> U+021D5 </td> <td> <span class="glyph" title="">&#8661;</span> </td> <tr id="entity-DoubleVerticalBar"><td> <code title="">DoubleVerticalBar;</code> </td> <td> U+02225 </td> <td> <span class="glyph" title="">&#8741;</span> </td> <tr id="entity-DownArrow"><td> <code title="">DownArrow;</code> </td> <td> U+02193 </td> <td> <span class="glyph" title="">&darr;</span> </td> <tr id="entity-DownArrowBar"><td> <code title="">DownArrowBar;</code> </td> <td> U+02913 </td> <td> <span class="glyph" title="">&#10515;</span> </td> <tr id="entity-DownArrowUpArrow"><td> <code title="">DownArrowUpArrow;</code> </td> <td> U+021F5 </td> <td> <span class="glyph" title="">&#8693;</span> </td> <tr id="entity-DownBreve"><td> <code title="">DownBreve;</code> </td> <td> U+00311 </td> <td> <span class="glyph composition" title="">&#9676;&#785;</span> </td> <tr id="entity-DownLeftRightVector"><td> <code title="">DownLeftRightVector;</code> </td> <td> U+02950 </td> <td> <span class="glyph" title="">&#10576;</span> </td> <tr id="entity-DownLeftTeeVector"><td> <code title="">DownLeftTeeVector;</code> </td> <td> U+0295E </td> <td> <span class="glyph" title="">&#10590;</span> </td> <tr id="entity-DownLeftVector"><td> <code title="">DownLeftVector;</code> </td> <td> U+021BD </td> <td> <span class="glyph" title="">&#8637;</span> </td> <tr id="entity-DownLeftVectorBar"><td> <code title="">DownLeftVectorBar;</code> </td> <td> U+02956 </td> <td> <span class="glyph" title="">&#10582;</span> </td> <tr id="entity-DownRightTeeVector"><td> <code title="">DownRightTeeVector;</code> </td> <td> U+0295F </td> <td> <span class="glyph" title="">&#10591;</span> </td> <tr id="entity-DownRightVector"><td> <code title="">DownRightVector;</code> </td> <td> U+021C1 </td> <td> <span class="glyph" title="">&#8641;</span> </td> <tr id="entity-DownRightVectorBar"><td> <code title="">DownRightVectorBar;</code> </td> <td> U+02957 </td> <td> <span class="glyph" title="">&#10583;</span> </td> <tr id="entity-DownTee"><td> <code title="">DownTee;</code> </td> <td> U+022A4 </td> <td> <span class="glyph" title="">&#8868;</span> </td> <tr id="entity-DownTeeArrow"><td> <code title="">DownTeeArrow;</code> </td> <td> U+021A7 </td> <td> <span class="glyph" title="">&#8615;</span> </td> <tr id="entity-Downarrow"><td> <code title="">Downarrow;</code> </td> <td> U+021D3 </td> <td> <span class="glyph" title="">&dArr;</span> </td> <tr id="entity-Dscr"><td> <code title="">Dscr;</code> </td> <td> U+1D49F </td> <td> <span class="glyph" title="">&#119967;</span> </td> <tr id="entity-Dstrok"><td> <code title="">Dstrok;</code> </td> <td> U+00110 </td> <td> <span class="glyph" title="">&#272;</span> </td> <tr id="entity-ENG"><td> <code title="">ENG;</code> </td> <td> U+0014A </td> <td> <span class="glyph" title="">&#330;</span> </td> <tr id="entity-ETH"><td> <code title="">ETH;</code> </td> <td> U+000D0 </td> <td> <span class="glyph" title="">&ETH;</span> </td> <tr id="entity-Eacute"><td> <code title="">Eacute;</code> </td> <td> U+000C9 </td> <td> <span class="glyph" title="">&Eacute;</span> </td> <tr id="entity-Ecaron"><td> <code title="">Ecaron;</code> </td> <td> U+0011A </td> <td> <span class="glyph" title="">&#282;</span> </td> <tr id="entity-Ecirc"><td> <code title="">Ecirc;</code> </td> <td> U+000CA </td> <td> <span class="glyph" title="">&Ecirc;</span> </td> <tr id="entity-Ecy"><td> <code title="">Ecy;</code> </td> <td> U+0042D </td> <td> <span class="glyph" title="">&#1069;</span> </td> <tr id="entity-Edot"><td> <code title="">Edot;</code> </td> <td> U+00116 </td> <td> <span class="glyph" title="">&#278;</span> </td> <tr id="entity-Efr"><td> <code title="">Efr;</code> </td> <td> U+1D508 </td> <td> <span class="glyph" title="">&#120072;</span> </td> <tr id="entity-Egrave"><td> <code title="">Egrave;</code> </td> <td> U+000C8 </td> <td> <span class="glyph" title="">&Egrave;</span> </td> <tr id="entity-Element"><td> <code title="">Element;</code> </td> <td> U+02208 </td> <td> <span class="glyph" title="">&isin;</span> </td> <tr id="entity-Emacr"><td> <code title="">Emacr;</code> </td> <td> U+00112 </td> <td> <span class="glyph" title="">&#274;</span> </td> <tr id="entity-EmptySmallSquare"><td> <code title="">EmptySmallSquare;</code> </td> <td> U+025FB </td> <td> <span class="glyph" title="">&#9723;</span> </td> <tr id="entity-EmptyVerySmallSquare"><td> <code title="">EmptyVerySmallSquare;</code> </td> <td> U+025AB </td> <td> <span class="glyph" title="">&#9643;</span> </td> <tr id="entity-Eogon"><td> <code title="">Eogon;</code> </td> <td> U+00118 </td> <td> <span class="glyph" title="">&#280;</span> </td> <tr id="entity-Eopf"><td> <code title="">Eopf;</code> </td> <td> U+1D53C </td> <td> <span class="glyph" title="">&#120124;</span> </td> <tr id="entity-Epsilon"><td> <code title="">Epsilon;</code> </td> <td> U+00395 </td> <td> <span class="glyph" title="">&Epsilon;</span> </td> <tr id="entity-Equal"><td> <code title="">Equal;</code> </td> <td> U+02A75 </td> <td> <span class="glyph" title="">&#10869;</span> </td> <tr id="entity-EqualTilde"><td> <code title="">EqualTilde;</code> </td> <td> U+02242 </td> <td> <span class="glyph" title="">&#8770;</span> </td> <tr id="entity-Equilibrium"><td> <code title="">Equilibrium;</code> </td> <td> U+021CC </td> <td> <span class="glyph" title="">&#8652;</span> </td> <tr id="entity-Escr"><td> <code title="">Escr;</code> </td> <td> U+02130 </td> <td> <span class="glyph" title="">&#8496;</span> </td> <tr id="entity-Esim"><td> <code title="">Esim;</code> </td> <td> U+02A73 </td> <td> <span class="glyph" title="">&#10867;</span> </td> <tr id="entity-Eta"><td> <code title="">Eta;</code> </td> <td> U+00397 </td> <td> <span class="glyph" title="">&Eta;</span> </td> <tr id="entity-Euml"><td> <code title="">Euml;</code> </td> <td> U+000CB </td> <td> <span class="glyph" title="">&Euml;</span> </td> <tr id="entity-Exists"><td> <code title="">Exists;</code> </td> <td> U+02203 </td> <td> <span class="glyph" title="">&exist;</span> </td> <tr id="entity-ExponentialE"><td> <code title="">ExponentialE;</code> </td> <td> U+02147 </td> <td> <span class="glyph" title="">&#8519;</span> </td> <tr id="entity-Fcy"><td> <code title="">Fcy;</code> </td> <td> U+00424 </td> <td> <span class="glyph" title="">&#1060;</span> </td> <tr id="entity-Ffr"><td> <code title="">Ffr;</code> </td> <td> U+1D509 </td> <td> <span class="glyph" title="">&#120073;</span> </td> <tr id="entity-FilledSmallSquare"><td> <code title="">FilledSmallSquare;</code> </td> <td> U+025FC </td> <td> <span class="glyph" title="">&#9724;</span> </td> <tr id="entity-FilledVerySmallSquare"><td> <code title="">FilledVerySmallSquare;</code> </td> <td> U+025AA </td> <td> <span class="glyph" title="">&#9642;</span> </td> <tr id="entity-Fopf"><td> <code title="">Fopf;</code> </td> <td> U+1D53D </td> <td> <span class="glyph" title="">&#120125;</span> </td> <tr id="entity-ForAll"><td> <code title="">ForAll;</code> </td> <td> U+02200 </td> <td> <span class="glyph" title="">&forall;</span> </td> <tr id="entity-Fouriertrf"><td> <code title="">Fouriertrf;</code> </td> <td> U+02131 </td> <td> <span class="glyph" title="">&#8497;</span> </td> <tr id="entity-Fscr"><td> <code title="">Fscr;</code> </td> <td> U+02131 </td> <td> <span class="glyph" title="">&#8497;</span> </td> <tr id="entity-GJcy"><td> <code title="">GJcy;</code> </td> <td> U+00403 </td> <td> <span class="glyph" title="">&#1027;</span> </td> <tr id="entity-GT"><td> <code title="">GT;</code> </td> <td> U+0003E </td> <td> <span class="glyph" title="">&gt;</span> </td> <tr id="entity-Gamma"><td> <code title="">Gamma;</code> </td> <td> U+00393 </td> <td> <span class="glyph" title="">&Gamma;</span> </td> <tr id="entity-Gammad"><td> <code title="">Gammad;</code> </td> <td> U+003DC </td> <td> <span class="glyph" title="">&#988;</span> </td> <tr id="entity-Gbreve"><td> <code title="">Gbreve;</code> </td> <td> U+0011E </td> <td> <span class="glyph" title="">&#286;</span> </td> <tr id="entity-Gcedil"><td> <code title="">Gcedil;</code> </td> <td> U+00122 </td> <td> <span class="glyph" title="">&#290;</span> </td> <tr id="entity-Gcirc"><td> <code title="">Gcirc;</code> </td> <td> U+0011C </td> <td> <span class="glyph" title="">&#284;</span> </td> <tr id="entity-Gcy"><td> <code title="">Gcy;</code> </td> <td> U+00413 </td> <td> <span class="glyph" title="">&#1043;</span> </td> <tr id="entity-Gdot"><td> <code title="">Gdot;</code> </td> <td> U+00120 </td> <td> <span class="glyph" title="">&#288;</span> </td> <tr id="entity-Gfr"><td> <code title="">Gfr;</code> </td> <td> U+1D50A </td> <td> <span class="glyph" title="">&#120074;</span> </td> <tr id="entity-Gg"><td> <code title="">Gg;</code> </td> <td> U+022D9 </td> <td> <span class="glyph" title="">&#8921;</span> </td> <tr id="entity-Gopf"><td> <code title="">Gopf;</code> </td> <td> U+1D53E </td> <td> <span class="glyph" title="">&#120126;</span> </td> <tr id="entity-GreaterEqual"><td> <code title="">GreaterEqual;</code> </td> <td> U+02265 </td> <td> <span class="glyph" title="">&ge;</span> </td> <tr id="entity-GreaterEqualLess"><td> <code title="">GreaterEqualLess;</code> </td> <td> U+022DB </td> <td> <span class="glyph" title="">&#8923;</span> </td> <tr id="entity-GreaterFullEqual"><td> <code title="">GreaterFullEqual;</code> </td> <td> U+02267 </td> <td> <span class="glyph" title="">&#8807;</span> </td> <tr id="entity-GreaterGreater"><td> <code title="">GreaterGreater;</code> </td> <td> U+02AA2 </td> <td> <span class="glyph" title="">&#10914;</span> </td> <tr id="entity-GreaterLess"><td> <code title="">GreaterLess;</code> </td> <td> U+02277 </td> <td> <span class="glyph" title="">&#8823;</span> </td> <tr id="entity-GreaterSlantEqual"><td> <code title="">GreaterSlantEqual;</code> </td> <td> U+02A7E </td> <td> <span class="glyph" title="">&#10878;</span> </td> <tr id="entity-GreaterTilde"><td> <code title="">GreaterTilde;</code> </td> <td> U+02273 </td> <td> <span class="glyph" title="">&#8819;</span> </td> <tr id="entity-Gscr"><td> <code title="">Gscr;</code> </td> <td> U+1D4A2 </td> <td> <span class="glyph" title="">&#119970;</span> </td> <tr id="entity-Gt"><td> <code title="">Gt;</code> </td> <td> U+0226B </td> <td> <span class="glyph" title="">&#8811;</span> </td> <tr id="entity-HARDcy"><td> <code title="">HARDcy;</code> </td> <td> U+0042A </td> <td> <span class="glyph" title="">&#1066;</span> </td> <tr id="entity-Hacek"><td> <code title="">Hacek;</code> </td> <td> U+002C7 </td> <td> <span class="glyph" title="">&#711;</span> </td> <tr id="entity-Hat"><td> <code title="">Hat;</code> </td> <td> U+0005E </td> <td> <span class="glyph" title="">^</span> </td> <tr id="entity-Hcirc"><td> <code title="">Hcirc;</code> </td> <td> U+00124 </td> <td> <span class="glyph" title="">&#292;</span> </td> <tr id="entity-Hfr"><td> <code title="">Hfr;</code> </td> <td> U+0210C </td> <td> <span class="glyph" title="">&#8460;</span> </td> <tr id="entity-HilbertSpace"><td> <code title="">HilbertSpace;</code> </td> <td> U+0210B </td> <td> <span class="glyph" title="">&#8459;</span> </td> <tr id="entity-Hopf"><td> <code title="">Hopf;</code> </td> <td> U+0210D </td> <td> <span class="glyph" title="">&#8461;</span> </td> <tr id="entity-HorizontalLine"><td> <code title="">HorizontalLine;</code> </td> <td> U+02500 </td> <td> <span class="glyph" title="">&#9472;</span> </td> <tr id="entity-Hscr"><td> <code title="">Hscr;</code> </td> <td> U+0210B </td> <td> <span class="glyph" title="">&#8459;</span> </td> <tr id="entity-Hstrok"><td> <code title="">Hstrok;</code> </td> <td> U+00126 </td> <td> <span class="glyph" title="">&#294;</span> </td> <tr id="entity-HumpDownHump"><td> <code title="">HumpDownHump;</code> </td> <td> U+0224E </td> <td> <span class="glyph" title="">&#8782;</span> </td> <tr id="entity-HumpEqual"><td> <code title="">HumpEqual;</code> </td> <td> U+0224F </td> <td> <span class="glyph" title="">&#8783;</span> </td> <tr id="entity-IEcy"><td> <code title="">IEcy;</code> </td> <td> U+00415 </td> <td> <span class="glyph" title="">&#1045;</span> </td> <tr id="entity-IJlig"><td> <code title="">IJlig;</code> </td> <td> U+00132 </td> <td> <span class="glyph" title="">&#306;</span> </td> <tr id="entity-IOcy"><td> <code title="">IOcy;</code> </td> <td> U+00401 </td> <td> <span class="glyph" title="">&#1025;</span> </td> <tr id="entity-Iacute"><td> <code title="">Iacute;</code> </td> <td> U+000CD </td> <td> <span class="glyph" title="">&Iacute;</span> </td> <tr id="entity-Icirc"><td> <code title="">Icirc;</code> </td> <td> U+000CE </td> <td> <span class="glyph" title="">&Icirc;</span> </td> <tr id="entity-Icy"><td> <code title="">Icy;</code> </td> <td> U+00418 </td> <td> <span class="glyph" title="">&#1048;</span> </td> <tr id="entity-Idot"><td> <code title="">Idot;</code> </td> <td> U+00130 </td> <td> <span class="glyph" title="">&#304;</span> </td> <tr id="entity-Ifr"><td> <code title="">Ifr;</code> </td> <td> U+02111 </td> <td> <span class="glyph" title="">&image;</span> </td> <tr id="entity-Igrave"><td> <code title="">Igrave;</code> </td> <td> U+000CC </td> <td> <span class="glyph" title="">&Igrave;</span> </td> <tr id="entity-Im"><td> <code title="">Im;</code> </td> <td> U+02111 </td> <td> <span class="glyph" title="">&image;</span> </td> <tr id="entity-Imacr"><td> <code title="">Imacr;</code> </td> <td> U+0012A </td> <td> <span class="glyph" title="">&#298;</span> </td> <tr id="entity-ImaginaryI"><td> <code title="">ImaginaryI;</code> </td> <td> U+02148 </td> <td> <span class="glyph" title="">&#8520;</span> </td> <tr id="entity-Implies"><td> <code title="">Implies;</code> </td> <td> U+021D2 </td> <td> <span class="glyph" title="">&rArr;</span> </td> <tr id="entity-Int"><td> <code title="">Int;</code> </td> <td> U+0222C </td> <td> <span class="glyph" title="">&#8748;</span> </td> <tr id="entity-Integral"><td> <code title="">Integral;</code> </td> <td> U+0222B </td> <td> <span class="glyph" title="">&int;</span> </td> <tr id="entity-Intersection"><td> <code title="">Intersection;</code> </td> <td> U+022C2 </td> <td> <span class="glyph" title="">&#8898;</span> </td> <tr id="entity-InvisibleComma"><td> <code title="">InvisibleComma;</code> </td> <td> U+02063 </td> <td> <span class="glyph" title="">&#8291;</span> </td> <tr id="entity-InvisibleTimes"><td> <code title="">InvisibleTimes;</code> </td> <td> U+02062 </td> <td> <span class="glyph" title="">&#8290;</span> </td> <tr id="entity-Iogon"><td> <code title="">Iogon;</code> </td> <td> U+0012E </td> <td> <span class="glyph" title="">&#302;</span> </td> <tr id="entity-Iopf"><td> <code title="">Iopf;</code> </td> <td> U+1D540 </td> <td> <span class="glyph" title="">&#120128;</span> </td> <tr id="entity-Iota"><td> <code title="">Iota;</code> </td> <td> U+00399 </td> <td> <span class="glyph" title="">&Iota;</span> </td> <tr id="entity-Iscr"><td> <code title="">Iscr;</code> </td> <td> U+02110 </td> <td> <span class="glyph" title="">&#8464;</span> </td> <tr id="entity-Itilde"><td> <code title="">Itilde;</code> </td> <td> U+00128 </td> <td> <span class="glyph" title="">&#296;</span> </td> <tr id="entity-Iukcy"><td> <code title="">Iukcy;</code> </td> <td> U+00406 </td> <td> <span class="glyph" title="">&#1030;</span> </td> <tr id="entity-Iuml"><td> <code title="">Iuml;</code> </td> <td> U+000CF </td> <td> <span class="glyph" title="">&Iuml;</span> </td> <tr id="entity-Jcirc"><td> <code title="">Jcirc;</code> </td> <td> U+00134 </td> <td> <span class="glyph" title="">&#308;</span> </td> <tr id="entity-Jcy"><td> <code title="">Jcy;</code> </td> <td> U+00419 </td> <td> <span class="glyph" title="">&#1049;</span> </td> <tr id="entity-Jfr"><td> <code title="">Jfr;</code> </td> <td> U+1D50D </td> <td> <span class="glyph" title="">&#120077;</span> </td> <tr id="entity-Jopf"><td> <code title="">Jopf;</code> </td> <td> U+1D541 </td> <td> <span class="glyph" title="">&#120129;</span> </td> <tr id="entity-Jscr"><td> <code title="">Jscr;</code> </td> <td> U+1D4A5 </td> <td> <span class="glyph" title="">&#119973;</span> </td> <tr id="entity-Jsercy"><td> <code title="">Jsercy;</code> </td> <td> U+00408 </td> <td> <span class="glyph" title="">&#1032;</span> </td> <tr id="entity-Jukcy"><td> <code title="">Jukcy;</code> </td> <td> U+00404 </td> <td> <span class="glyph" title="">&#1028;</span> </td> <tr id="entity-KHcy"><td> <code title="">KHcy;</code> </td> <td> U+00425 </td> <td> <span class="glyph" title="">&#1061;</span> </td> <tr id="entity-KJcy"><td> <code title="">KJcy;</code> </td> <td> U+0040C </td> <td> <span class="glyph" title="">&#1036;</span> </td> <tr id="entity-Kappa"><td> <code title="">Kappa;</code> </td> <td> U+0039A </td> <td> <span class="glyph" title="">&Kappa;</span> </td> <tr id="entity-Kcedil"><td> <code title="">Kcedil;</code> </td> <td> U+00136 </td> <td> <span class="glyph" title="">&#310;</span> </td> <tr id="entity-Kcy"><td> <code title="">Kcy;</code> </td> <td> U+0041A </td> <td> <span class="glyph" title="">&#1050;</span> </td> <tr id="entity-Kfr"><td> <code title="">Kfr;</code> </td> <td> U+1D50E </td> <td> <span class="glyph" title="">&#120078;</span> </td> <tr id="entity-Kopf"><td> <code title="">Kopf;</code> </td> <td> U+1D542 </td> <td> <span class="glyph" title="">&#120130;</span> </td> <tr id="entity-Kscr"><td> <code title="">Kscr;</code> </td> <td> U+1D4A6 </td> <td> <span class="glyph" title="">&#119974;</span> </td> <tr id="entity-LJcy"><td> <code title="">LJcy;</code> </td> <td> U+00409 </td> <td> <span class="glyph" title="">&#1033;</span> </td> <tr id="entity-LT"><td> <code title="">LT;</code> </td> <td> U+0003C </td> <td> <span class="glyph" title="">&lt;</span> </td> <tr id="entity-Lacute"><td> <code title="">Lacute;</code> </td> <td> U+00139 </td> <td> <span class="glyph" title="">&#313;</span> </td> <tr id="entity-Lambda"><td> <code title="">Lambda;</code> </td> <td> U+0039B </td> <td> <span class="glyph" title="">&Lambda;</span> </td> <tr id="entity-Lang"><td> <code title="">Lang;</code> </td> <td> U+027EA </td> <td> <span class="glyph" title="">&#10218;</span> </td> <tr id="entity-Laplacetrf"><td> <code title="">Laplacetrf;</code> </td> <td> U+02112 </td> <td> <span class="glyph" title="">&#8466;</span> </td> <tr id="entity-Larr"><td> <code title="">Larr;</code> </td> <td> U+0219E </td> <td> <span class="glyph" title="">&#8606;</span> </td> <tr id="entity-Lcaron"><td> <code title="">Lcaron;</code> </td> <td> U+0013D </td> <td> <span class="glyph" title="">&#317;</span> </td> <tr id="entity-Lcedil"><td> <code title="">Lcedil;</code> </td> <td> U+0013B </td> <td> <span class="glyph" title="">&#315;</span> </td> <tr id="entity-Lcy"><td> <code title="">Lcy;</code> </td> <td> U+0041B </td> <td> <span class="glyph" title="">&#1051;</span> </td> <tr id="entity-LeftAngleBracket"><td> <code title="">LeftAngleBracket;</code> </td> <td> U+027E8 </td> <td> <span class="glyph" title="">&#9001;</span> </td> <tr id="entity-LeftArrow"><td> <code title="">LeftArrow;</code> </td> <td> U+02190 </td> <td> <span class="glyph" title="">&larr;</span> </td> <tr id="entity-LeftArrowBar"><td> <code title="">LeftArrowBar;</code> </td> <td> U+021E4 </td> <td> <span class="glyph" title="">&#8676;</span> </td> <tr id="entity-LeftArrowRightArrow"><td> <code title="">LeftArrowRightArrow;</code> </td> <td> U+021C6 </td> <td> <span class="glyph" title="">&#8646;</span> </td> <tr id="entity-LeftCeiling"><td> <code title="">LeftCeiling;</code> </td> <td> U+02308 </td> <td> <span class="glyph" title="">&lceil;</span> </td> <tr id="entity-LeftDoubleBracket"><td> <code title="">LeftDoubleBracket;</code> </td> <td> U+027E6 </td> <td> <span class="glyph" title="">&#10214;</span> </td> <tr id="entity-LeftDownTeeVector"><td> <code title="">LeftDownTeeVector;</code> </td> <td> U+02961 </td> <td> <span class="glyph" title="">&#10593;</span> </td> <tr id="entity-LeftDownVector"><td> <code title="">LeftDownVector;</code> </td> <td> U+021C3 </td> <td> <span class="glyph" title="">&#8643;</span> </td> <tr id="entity-LeftDownVectorBar"><td> <code title="">LeftDownVectorBar;</code> </td> <td> U+02959 </td> <td> <span class="glyph" title="">&#10585;</span> </td> <tr id="entity-LeftFloor"><td> <code title="">LeftFloor;</code> </td> <td> U+0230A </td> <td> <span class="glyph" title="">&lfloor;</span> </td> <tr id="entity-LeftRightArrow"><td> <code title="">LeftRightArrow;</code> </td> <td> U+02194 </td> <td> <span class="glyph" title="">&harr;</span> </td> <tr id="entity-LeftRightVector"><td> <code title="">LeftRightVector;</code> </td> <td> U+0294E </td> <td> <span class="glyph" title="">&#10574;</span> </td> <tr id="entity-LeftTee"><td> <code title="">LeftTee;</code> </td> <td> U+022A3 </td> <td> <span class="glyph" title="">&#8867;</span> </td> <tr id="entity-LeftTeeArrow"><td> <code title="">LeftTeeArrow;</code> </td> <td> U+021A4 </td> <td> <span class="glyph" title="">&#8612;</span> </td> <tr id="entity-LeftTeeVector"><td> <code title="">LeftTeeVector;</code> </td> <td> U+0295A </td> <td> <span class="glyph" title="">&#10586;</span> </td> <tr id="entity-LeftTriangle"><td> <code title="">LeftTriangle;</code> </td> <td> U+022B2 </td> <td> <span class="glyph" title="">&#8882;</span> </td> <tr id="entity-LeftTriangleBar"><td> <code title="">LeftTriangleBar;</code> </td> <td> U+029CF </td> <td> <span class="glyph" title="">&#10703;</span> </td> <tr id="entity-LeftTriangleEqual"><td> <code title="">LeftTriangleEqual;</code> </td> <td> U+022B4 </td> <td> <span class="glyph" title="">&#8884;</span> </td> <tr id="entity-LeftUpDownVector"><td> <code title="">LeftUpDownVector;</code> </td> <td> U+02951 </td> <td> <span class="glyph" title="">&#10577;</span> </td> <tr id="entity-LeftUpTeeVector"><td> <code title="">LeftUpTeeVector;</code> </td> <td> U+02960 </td> <td> <span class="glyph" title="">&#10592;</span> </td> <tr id="entity-LeftUpVector"><td> <code title="">LeftUpVector;</code> </td> <td> U+021BF </td> <td> <span class="glyph" title="">&#8639;</span> </td> <tr id="entity-LeftUpVectorBar"><td> <code title="">LeftUpVectorBar;</code> </td> <td> U+02958 </td> <td> <span class="glyph" title="">&#10584;</span> </td> <tr id="entity-LeftVector"><td> <code title="">LeftVector;</code> </td> <td> U+021BC </td> <td> <span class="glyph" title="">&#8636;</span> </td> <tr id="entity-LeftVectorBar"><td> <code title="">LeftVectorBar;</code> </td> <td> U+02952 </td> <td> <span class="glyph" title="">&#10578;</span> </td> <tr id="entity-Leftarrow"><td> <code title="">Leftarrow;</code> </td> <td> U+021D0 </td> <td> <span class="glyph" title="">&lArr;</span> </td> <tr id="entity-Leftrightarrow"><td> <code title="">Leftrightarrow;</code> </td> <td> U+021D4 </td> <td> <span class="glyph" title="">&hArr;</span> </td> <tr id="entity-LessEqualGreater"><td> <code title="">LessEqualGreater;</code> </td> <td> U+022DA </td> <td> <span class="glyph" title="">&#8922;</span> </td> <tr id="entity-LessFullEqual"><td> <code title="">LessFullEqual;</code> </td> <td> U+02266 </td> <td> <span class="glyph" title="">&#8806;</span> </td> <tr id="entity-LessGreater"><td> <code title="">LessGreater;</code> </td> <td> U+02276 </td> <td> <span class="glyph" title="">&#8822;</span> </td> <tr id="entity-LessLess"><td> <code title="">LessLess;</code> </td> <td> U+02AA1 </td> <td> <span class="glyph" title="">&#10913;</span> </td> <tr id="entity-LessSlantEqual"><td> <code title="">LessSlantEqual;</code> </td> <td> U+02A7D </td> <td> <span class="glyph" title="">&#10877;</span> </td> <tr id="entity-LessTilde"><td> <code title="">LessTilde;</code> </td> <td> U+02272 </td> <td> <span class="glyph" title="">&#8818;</span> </td> <tr id="entity-Lfr"><td> <code title="">Lfr;</code> </td> <td> U+1D50F </td> <td> <span class="glyph" title="">&#120079;</span> </td> <tr id="entity-Ll"><td> <code title="">Ll;</code> </td> <td> U+022D8 </td> <td> <span class="glyph" title="">&#8920;</span> </td> <tr id="entity-Lleftarrow"><td> <code title="">Lleftarrow;</code> </td> <td> U+021DA </td> <td> <span class="glyph" title="">&#8666;</span> </td> <tr id="entity-Lmidot"><td> <code title="">Lmidot;</code> </td> <td> U+0013F </td> <td> <span class="glyph" title="">&#319;</span> </td> <tr id="entity-LongLeftArrow"><td> <code title="">LongLeftArrow;</code> </td> <td> U+027F5 </td> <td> <span class="glyph" title="">&#10229;</span> </td> <tr id="entity-LongLeftRightArrow"><td> <code title="">LongLeftRightArrow;</code> </td> <td> U+027F7 </td> <td> <span class="glyph" title="">&#10231;</span> </td> <tr id="entity-LongRightArrow"><td> <code title="">LongRightArrow;</code> </td> <td> U+027F6 </td> <td> <span class="glyph" title="">&#10230;</span> </td> <tr id="entity-Longleftarrow"><td> <code title="">Longleftarrow;</code> </td> <td> U+027F8 </td> <td> <span class="glyph" title="">&#10232;</span> </td> <tr id="entity-Longleftrightarrow"><td> <code title="">Longleftrightarrow;</code> </td> <td> U+027FA </td> <td> <span class="glyph" title="">&#10234;</span> </td> <tr id="entity-Longrightarrow"><td> <code title="">Longrightarrow;</code> </td> <td> U+027F9 </td> <td> <span class="glyph" title="">&#10233;</span> </td> <tr id="entity-Lopf"><td> <code title="">Lopf;</code> </td> <td> U+1D543 </td> <td> <span class="glyph" title="">&#120131;</span> </td> <tr id="entity-LowerLeftArrow"><td> <code title="">LowerLeftArrow;</code> </td> <td> U+02199 </td> <td> <span class="glyph" title="">&#8601;</span> </td> <tr id="entity-LowerRightArrow"><td> <code title="">LowerRightArrow;</code> </td> <td> U+02198 </td> <td> <span class="glyph" title="">&#8600;</span> </td> <tr id="entity-Lscr"><td> <code title="">Lscr;</code> </td> <td> U+02112 </td> <td> <span class="glyph" title="">&#8466;</span> </td> <tr id="entity-Lsh"><td> <code title="">Lsh;</code> </td> <td> U+021B0 </td> <td> <span class="glyph" title="">&#8624;</span> </td> <tr id="entity-Lstrok"><td> <code title="">Lstrok;</code> </td> <td> U+00141 </td> <td> <span class="glyph" title="">&#321;</span> </td> <tr id="entity-Lt"><td> <code title="">Lt;</code> </td> <td> U+0226A </td> <td> <span class="glyph" title="">&#8810;</span> </td> <tr id="entity-Map"><td> <code title="">Map;</code> </td> <td> U+02905 </td> <td> <span class="glyph" title="">&#10501;</span> </td> <tr id="entity-Mcy"><td> <code title="">Mcy;</code> </td> <td> U+0041C </td> <td> <span class="glyph" title="">&#1052;</span> </td> <tr id="entity-MediumSpace"><td> <code title="">MediumSpace;</code> </td> <td> U+0205F </td> <td> <span class="glyph" title="">&#8287;</span> </td> <tr id="entity-Mellintrf"><td> <code title="">Mellintrf;</code> </td> <td> U+02133 </td> <td> <span class="glyph" title="">&#8499;</span> </td> <tr id="entity-Mfr"><td> <code title="">Mfr;</code> </td> <td> U+1D510 </td> <td> <span class="glyph" title="">&#120080;</span> </td> <tr id="entity-MinusPlus"><td> <code title="">MinusPlus;</code> </td> <td> U+02213 </td> <td> <span class="glyph" title="">&#8723;</span> </td> <tr id="entity-Mopf"><td> <code title="">Mopf;</code> </td> <td> U+1D544 </td> <td> <span class="glyph" title="">&#120132;</span> </td> <tr id="entity-Mscr"><td> <code title="">Mscr;</code> </td> <td> U+02133 </td> <td> <span class="glyph" title="">&#8499;</span> </td> <tr id="entity-Mu"><td> <code title="">Mu;</code> </td> <td> U+0039C </td> <td> <span class="glyph" title="">&Mu;</span> </td> <tr id="entity-NJcy"><td> <code title="">NJcy;</code> </td> <td> U+0040A </td> <td> <span class="glyph" title="">&#1034;</span> </td> <tr id="entity-Nacute"><td> <code title="">Nacute;</code> </td> <td> U+00143 </td> <td> <span class="glyph" title="">&#323;</span> </td> <tr id="entity-Ncaron"><td> <code title="">Ncaron;</code> </td> <td> U+00147 </td> <td> <span class="glyph" title="">&#327;</span> </td> <tr id="entity-Ncedil"><td> <code title="">Ncedil;</code> </td> <td> U+00145 </td> <td> <span class="glyph" title="">&#325;</span> </td> <tr id="entity-Ncy"><td> <code title="">Ncy;</code> </td> <td> U+0041D </td> <td> <span class="glyph" title="">&#1053;</span> </td> <tr id="entity-NegativeMediumSpace"><td> <code title="">NegativeMediumSpace;</code> </td> <td> U+0200B </td> <td> <span class="glyph" title="">&#8203;</span> </td> <tr id="entity-NegativeThickSpace"><td> <code title="">NegativeThickSpace;</code> </td> <td> U+0200B </td> <td> <span class="glyph" title="">&#8203;</span> </td> <tr id="entity-NegativeThinSpace"><td> <code title="">NegativeThinSpace;</code> </td> <td> U+0200B </td> <td> <span class="glyph" title="">&#8203;</span> </td> <tr id="entity-NegativeVeryThinSpace"><td> <code title="">NegativeVeryThinSpace;</code> </td> <td> U+0200B </td> <td> <span class="glyph" title="">&#8203;</span> </td> <tr id="entity-NestedGreaterGreater"><td> <code title="">NestedGreaterGreater;</code> </td> <td> U+0226B </td> <td> <span class="glyph" title="">&#8811;</span> </td> <tr id="entity-NestedLessLess"><td> <code title="">NestedLessLess;</code> </td> <td> U+0226A </td> <td> <span class="glyph" title="">&#8810;</span> </td> <tr id="entity-NewLine"><td> <code title="">NewLine;</code> </td> <td> U+0000A </td> <td> <span class="glyph control" title="">&#9226;</span> </td> <tr id="entity-Nfr"><td> <code title="">Nfr;</code> </td> <td> U+1D511 </td> <td> <span class="glyph" title="">&#120081;</span> </td> <tr id="entity-NoBreak"><td> <code title="">NoBreak;</code> </td> <td> U+02060 </td> <td> <span class="glyph" title="">&#8288;</span> </td> <tr id="entity-NonBreakingSpace"><td> <code title="">NonBreakingSpace;</code> </td> <td> U+000A0 </td> <td> <span class="glyph" title="">&nbsp;</span> </td> <tr id="entity-Nopf"><td> <code title="">Nopf;</code> </td> <td> U+02115 </td> <td> <span class="glyph" title="">&#8469;</span> </td> <tr id="entity-Not"><td> <code title="">Not;</code> </td> <td> U+02AEC </td> <td> <span class="glyph" title="">&#10988;</span> </td> <tr id="entity-NotCongruent"><td> <code title="">NotCongruent;</code> </td> <td> U+02262 </td> <td> <span class="glyph" title="">&#8802;</span> </td> <tr id="entity-NotCupCap"><td> <code title="">NotCupCap;</code> </td> <td> U+0226D </td> <td> <span class="glyph" title="">&#8813;</span> </td> <tr id="entity-NotDoubleVerticalBar"><td> <code title="">NotDoubleVerticalBar;</code> </td> <td> U+02226 </td> <td> <span class="glyph" title="">&#8742;</span> </td> <tr id="entity-NotElement"><td> <code title="">NotElement;</code> </td> <td> U+02209 </td> <td> <span class="glyph" title="">&notin;</span> </td> <tr id="entity-NotEqual"><td> <code title="">NotEqual;</code> </td> <td> U+02260 </td> <td> <span class="glyph" title="">&ne;</span> </td> <tr id="entity-NotEqualTilde"><td> <code title="">NotEqualTilde;</code> </td> <td> U+02242 U+00338 </td> <td> <span class="glyph compound" title="">&#8770;&#824;</span> </td> <tr id="entity-NotExists"><td> <code title="">NotExists;</code> </td> <td> U+02204 </td> <td> <span class="glyph" title="">&#8708;</span> </td> <tr id="entity-NotGreater"><td> <code title="">NotGreater;</code> </td> <td> U+0226F </td> <td> <span class="glyph" title="">&#8815;</span> </td> <tr id="entity-NotGreaterEqual"><td> <code title="">NotGreaterEqual;</code> </td> <td> U+02271 </td> <td> <span class="glyph" title="">&#8817;</span> </td> <tr id="entity-NotGreaterFullEqual"><td> <code title="">NotGreaterFullEqual;</code> </td> <td> U+02267 U+00338 </td> <td> <span class="glyph compound" title="">&#8807;&#824;</span> </td> <tr id="entity-NotGreaterGreater"><td> <code title="">NotGreaterGreater;</code> </td> <td> U+0226B U+00338 </td> <td> <span class="glyph compound" title="">&#8811;&#824;</span> </td> <tr id="entity-NotGreaterLess"><td> <code title="">NotGreaterLess;</code> </td> <td> U+02279 </td> <td> <span class="glyph" title="">&#8825;</span> </td> <tr id="entity-NotGreaterSlantEqual"><td> <code title="">NotGreaterSlantEqual;</code> </td> <td> U+02A7E U+00338 </td> <td> <span class="glyph compound" title="">&#10878;&#824;</span> </td> <tr id="entity-NotGreaterTilde"><td> <code title="">NotGreaterTilde;</code> </td> <td> U+02275 </td> <td> <span class="glyph" title="">&#8821;</span> </td> <tr id="entity-NotHumpDownHump"><td> <code title="">NotHumpDownHump;</code> </td> <td> U+0224E U+00338 </td> <td> <span class="glyph compound" title="">&#8782;&#824;</span> </td> <tr id="entity-NotHumpEqual"><td> <code title="">NotHumpEqual;</code> </td> <td> U+0224F U+00338 </td> <td> <span class="glyph compound" title="">&#8783;&#824;</span> </td> <tr id="entity-NotLeftTriangle"><td> <code title="">NotLeftTriangle;</code> </td> <td> U+022EA </td> <td> <span class="glyph" title="">&#8938;</span> </td> <tr id="entity-NotLeftTriangleBar"><td> <code title="">NotLeftTriangleBar;</code> </td> <td> U+029CF U+00338 </td> <td> <span class="glyph compound" title="">&#10703;&#824;</span> </td> <tr id="entity-NotLeftTriangleEqual"><td> <code title="">NotLeftTriangleEqual;</code> </td> <td> U+022EC </td> <td> <span class="glyph" title="">&#8940;</span> </td> <tr id="entity-NotLess"><td> <code title="">NotLess;</code> </td> <td> U+0226E </td> <td> <span class="glyph" title="">&#8814;</span> </td> <tr id="entity-NotLessEqual"><td> <code title="">NotLessEqual;</code> </td> <td> U+02270 </td> <td> <span class="glyph" title="">&#8816;</span> </td> <tr id="entity-NotLessGreater"><td> <code title="">NotLessGreater;</code> </td> <td> U+02278 </td> <td> <span class="glyph" title="">&#8824;</span> </td> <tr id="entity-NotLessLess"><td> <code title="">NotLessLess;</code> </td> <td> U+0226A U+00338 </td> <td> <span class="glyph compound" title="">&#8810;&#824;</span> </td> <tr id="entity-NotLessSlantEqual"><td> <code title="">NotLessSlantEqual;</code> </td> <td> U+02A7D U+00338 </td> <td> <span class="glyph compound" title="">&#10877;&#824;</span> </td> <tr id="entity-NotLessTilde"><td> <code title="">NotLessTilde;</code> </td> <td> U+02274 </td> <td> <span class="glyph" title="">&#8820;</span> </td> <tr id="entity-NotNestedGreaterGreater"><td> <code title="">NotNestedGreaterGreater;</code> </td> <td> U+02AA2 U+00338 </td> <td> <span class="glyph compound" title="">&#10914;&#824;</span> </td> <tr id="entity-NotNestedLessLess"><td> <code title="">NotNestedLessLess;</code> </td> <td> U+02AA1 U+00338 </td> <td> <span class="glyph compound" title="">&#10913;&#824;</span> </td> <tr id="entity-NotPrecedes"><td> <code title="">NotPrecedes;</code> </td> <td> U+02280 </td> <td> <span class="glyph" title="">&#8832;</span> </td> <tr id="entity-NotPrecedesEqual"><td> <code title="">NotPrecedesEqual;</code> </td> <td> U+02AAF U+00338 </td> <td> <span class="glyph compound" title="">&#10927;&#824;</span> </td> <tr id="entity-NotPrecedesSlantEqual"><td> <code title="">NotPrecedesSlantEqual;</code> </td> <td> U+022E0 </td> <td> <span class="glyph" title="">&#8928;</span> </td> <tr id="entity-NotReverseElement"><td> <code title="">NotReverseElement;</code> </td> <td> U+0220C </td> <td> <span class="glyph" title="">&#8716;</span> </td> <tr id="entity-NotRightTriangle"><td> <code title="">NotRightTriangle;</code> </td> <td> U+022EB </td> <td> <span class="glyph" title="">&#8939;</span> </td> <tr id="entity-NotRightTriangleBar"><td> <code title="">NotRightTriangleBar;</code> </td> <td> U+029D0 U+00338 </td> <td> <span class="glyph compound" title="">&#10704;&#824;</span> </td> <tr id="entity-NotRightTriangleEqual"><td> <code title="">NotRightTriangleEqual;</code> </td> <td> U+022ED </td> <td> <span class="glyph" title="">&#8941;</span> </td> <tr id="entity-NotSquareSubset"><td> <code title="">NotSquareSubset;</code> </td> <td> U+0228F U+00338 </td> <td> <span class="glyph compound" title="">&#8847;&#824;</span> </td> <tr id="entity-NotSquareSubsetEqual"><td> <code title="">NotSquareSubsetEqual;</code> </td> <td> U+022E2 </td> <td> <span class="glyph" title="">&#8930;</span> </td> <tr id="entity-NotSquareSuperset"><td> <code title="">NotSquareSuperset;</code> </td> <td> U+02290 U+00338 </td> <td> <span class="glyph compound" title="">&#8848;&#824;</span> </td> <tr id="entity-NotSquareSupersetEqual"><td> <code title="">NotSquareSupersetEqual;</code> </td> <td> U+022E3 </td> <td> <span class="glyph" title="">&#8931;</span> </td> <tr id="entity-NotSubset"><td> <code title="">NotSubset;</code> </td> <td> U+02282 U+020D2 </td> <td> <span class="glyph compound" title="">&sub;&#8402;</span> </td> <tr id="entity-NotSubsetEqual"><td> <code title="">NotSubsetEqual;</code> </td> <td> U+02288 </td> <td> <span class="glyph" title="">&#8840;</span> </td> <tr id="entity-NotSucceeds"><td> <code title="">NotSucceeds;</code> </td> <td> U+02281 </td> <td> <span class="glyph" title="">&#8833;</span> </td> <tr id="entity-NotSucceedsEqual"><td> <code title="">NotSucceedsEqual;</code> </td> <td> U+02AB0 U+00338 </td> <td> <span class="glyph compound" title="">&#10928;&#824;</span> </td> <tr id="entity-NotSucceedsSlantEqual"><td> <code title="">NotSucceedsSlantEqual;</code> </td> <td> U+022E1 </td> <td> <span class="glyph" title="">&#8929;</span> </td> <tr id="entity-NotSucceedsTilde"><td> <code title="">NotSucceedsTilde;</code> </td> <td> U+0227F U+00338 </td> <td> <span class="glyph compound" title="">&#8831;&#824;</span> </td> <tr id="entity-NotSuperset"><td> <code title="">NotSuperset;</code> </td> <td> U+02283 U+020D2 </td> <td> <span class="glyph compound" title="">&sup;&#8402;</span> </td> <tr id="entity-NotSupersetEqual"><td> <code title="">NotSupersetEqual;</code> </td> <td> U+02289 </td> <td> <span class="glyph" title="">&#8841;</span> </td> <tr id="entity-NotTilde"><td> <code title="">NotTilde;</code> </td> <td> U+02241 </td> <td> <span class="glyph" title="">&#8769;</span> </td> <tr id="entity-NotTildeEqual"><td> <code title="">NotTildeEqual;</code> </td> <td> U+02244 </td> <td> <span class="glyph" title="">&#8772;</span> </td> <tr id="entity-NotTildeFullEqual"><td> <code title="">NotTildeFullEqual;</code> </td> <td> U+02247 </td> <td> <span class="glyph" title="">&#8775;</span> </td> <tr id="entity-NotTildeTilde"><td> <code title="">NotTildeTilde;</code> </td> <td> U+02249 </td> <td> <span class="glyph" title="">&#8777;</span> </td> <tr id="entity-NotVerticalBar"><td> <code title="">NotVerticalBar;</code> </td> <td> U+02224 </td> <td> <span class="glyph" title="">&#8740;</span> </td> <tr id="entity-Nscr"><td> <code title="">Nscr;</code> </td> <td> U+1D4A9 </td> <td> <span class="glyph" title="">&#119977;</span> </td> <tr id="entity-Ntilde"><td> <code title="">Ntilde;</code> </td> <td> U+000D1 </td> <td> <span class="glyph" title="">&Ntilde;</span> </td> <tr id="entity-Nu"><td> <code title="">Nu;</code> </td> <td> U+0039D </td> <td> <span class="glyph" title="">&Nu;</span> </td> <tr id="entity-OElig"><td> <code title="">OElig;</code> </td> <td> U+00152 </td> <td> <span class="glyph" title="">&OElig;</span> </td> <tr id="entity-Oacute"><td> <code title="">Oacute;</code> </td> <td> U+000D3 </td> <td> <span class="glyph" title="">&Oacute;</span> </td> <tr id="entity-Ocirc"><td> <code title="">Ocirc;</code> </td> <td> U+000D4 </td> <td> <span class="glyph" title="">&Ocirc;</span> </td> <tr id="entity-Ocy"><td> <code title="">Ocy;</code> </td> <td> U+0041E </td> <td> <span class="glyph" title="">&#1054;</span> </td> <tr id="entity-Odblac"><td> <code title="">Odblac;</code> </td> <td> U+00150 </td> <td> <span class="glyph" title="">&#336;</span> </td> <tr id="entity-Ofr"><td> <code title="">Ofr;</code> </td> <td> U+1D512 </td> <td> <span class="glyph" title="">&#120082;</span> </td> <tr id="entity-Ograve"><td> <code title="">Ograve;</code> </td> <td> U+000D2 </td> <td> <span class="glyph" title="">&Ograve;</span> </td> <tr id="entity-Omacr"><td> <code title="">Omacr;</code> </td> <td> U+0014C </td> <td> <span class="glyph" title="">&#332;</span> </td> <tr id="entity-Omega"><td> <code title="">Omega;</code> </td> <td> U+003A9 </td> <td> <span class="glyph" title="">&Omega;</span> </td> <tr id="entity-Omicron"><td> <code title="">Omicron;</code> </td> <td> U+0039F </td> <td> <span class="glyph" title="">&Omicron;</span> </td> <tr id="entity-Oopf"><td> <code title="">Oopf;</code> </td> <td> U+1D546 </td> <td> <span class="glyph" title="">&#120134;</span> </td> <tr id="entity-OpenCurlyDoubleQuote"><td> <code title="">OpenCurlyDoubleQuote;</code> </td> <td> U+0201C </td> <td> <span class="glyph" title="">&ldquo;</span> </td> <tr id="entity-OpenCurlyQuote"><td> <code title="">OpenCurlyQuote;</code> </td> <td> U+02018 </td> <td> <span class="glyph" title="">&lsquo;</span> </td> <tr id="entity-Or"><td> <code title="">Or;</code> </td> <td> U+02A54 </td> <td> <span class="glyph" title="">&#10836;</span> </td> <tr id="entity-Oscr"><td> <code title="">Oscr;</code> </td> <td> U+1D4AA </td> <td> <span class="glyph" title="">&#119978;</span> </td> <tr id="entity-Oslash"><td> <code title="">Oslash;</code> </td> <td> U+000D8 </td> <td> <span class="glyph" title="">&Oslash;</span> </td> <tr id="entity-Otilde"><td> <code title="">Otilde;</code> </td> <td> U+000D5 </td> <td> <span class="glyph" title="">&Otilde;</span> </td> <tr id="entity-Otimes"><td> <code title="">Otimes;</code> </td> <td> U+02A37 </td> <td> <span class="glyph" title="">&#10807;</span> </td> <tr id="entity-Ouml"><td> <code title="">Ouml;</code> </td> <td> U+000D6 </td> <td> <span class="glyph" title="">&Ouml;</span> </td> <tr id="entity-OverBar"><td> <code title="">OverBar;</code> </td> <td> U+0203E </td> <td> <span class="glyph" title="">&oline;</span> </td> <tr id="entity-OverBrace"><td> <code title="">OverBrace;</code> </td> <td> U+023DE </td> <td> <span class="glyph" title="">&#9182;</span> </td> <tr id="entity-OverBracket"><td> <code title="">OverBracket;</code> </td> <td> U+023B4 </td> <td> <span class="glyph" title="">&#9140;</span> </td> <tr id="entity-OverParenthesis"><td> <code title="">OverParenthesis;</code> </td> <td> U+023DC </td> <td> <span class="glyph" title="">&#9180;</span> </td> <tr id="entity-PartialD"><td> <code title="">PartialD;</code> </td> <td> U+02202 </td> <td> <span class="glyph" title="">&part;</span> </td> <tr id="entity-Pcy"><td> <code title="">Pcy;</code> </td> <td> U+0041F </td> <td> <span class="glyph" title="">&#1055;</span> </td> <tr id="entity-Pfr"><td> <code title="">Pfr;</code> </td> <td> U+1D513 </td> <td> <span class="glyph" title="">&#120083;</span> </td> <tr id="entity-Phi"><td> <code title="">Phi;</code> </td> <td> U+003A6 </td> <td> <span class="glyph" title="">&Phi;</span> </td> <tr id="entity-Pi"><td> <code title="">Pi;</code> </td> <td> U+003A0 </td> <td> <span class="glyph" title="">&Pi;</span> </td> <tr id="entity-PlusMinus"><td> <code title="">PlusMinus;</code> </td> <td> U+000B1 </td> <td> <span class="glyph" title="">&plusmn;</span> </td> <tr id="entity-Poincareplane"><td> <code title="">Poincareplane;</code> </td> <td> U+0210C </td> <td> <span class="glyph" title="">&#8460;</span> </td> <tr id="entity-Popf"><td> <code title="">Popf;</code> </td> <td> U+02119 </td> <td> <span class="glyph" title="">&#8473;</span> </td> <tr id="entity-Pr"><td> <code title="">Pr;</code> </td> <td> U+02ABB </td> <td> <span class="glyph" title="">&#10939;</span> </td> <tr id="entity-Precedes"><td> <code title="">Precedes;</code> </td> <td> U+0227A </td> <td> <span class="glyph" title="">&#8826;</span> </td> <tr id="entity-PrecedesEqual"><td> <code title="">PrecedesEqual;</code> </td> <td> U+02AAF </td> <td> <span class="glyph" title="">&#10927;</span> </td> <tr id="entity-PrecedesSlantEqual"><td> <code title="">PrecedesSlantEqual;</code> </td> <td> U+0227C </td> <td> <span class="glyph" title="">&#8828;</span> </td> <tr id="entity-PrecedesTilde"><td> <code title="">PrecedesTilde;</code> </td> <td> U+0227E </td> <td> <span class="glyph" title="">&#8830;</span> </td> <tr id="entity-Prime"><td> <code title="">Prime;</code> </td> <td> U+02033 </td> <td> <span class="glyph" title="">&Prime;</span> </td> <tr id="entity-Product"><td> <code title="">Product;</code> </td> <td> U+0220F </td> <td> <span class="glyph" title="">&prod;</span> </td> <tr id="entity-Proportion"><td> <code title="">Proportion;</code> </td> <td> U+02237 </td> <td> <span class="glyph" title="">&#8759;</span> </td> <tr id="entity-Proportional"><td> <code title="">Proportional;</code> </td> <td> U+0221D </td> <td> <span class="glyph" title="">&prop;</span> </td> <tr id="entity-Pscr"><td> <code title="">Pscr;</code> </td> <td> U+1D4AB </td> <td> <span class="glyph" title="">&#119979;</span> </td> <tr id="entity-Psi"><td> <code title="">Psi;</code> </td> <td> U+003A8 </td> <td> <span class="glyph" title="">&Psi;</span> </td> <tr id="entity-QUOT"><td> <code title="">QUOT;</code> </td> <td> U+00022 </td> <td> <span class="glyph" title="">"</span> </td> <tr id="entity-Qfr"><td> <code title="">Qfr;</code> </td> <td> U+1D514 </td> <td> <span class="glyph" title="">&#120084;</span> </td> <tr id="entity-Qopf"><td> <code title="">Qopf;</code> </td> <td> U+0211A </td> <td> <span class="glyph" title="">&#8474;</span> </td> <tr id="entity-Qscr"><td> <code title="">Qscr;</code> </td> <td> U+1D4AC </td> <td> <span class="glyph" title="">&#119980;</span> </td> <tr id="entity-RBarr"><td> <code title="">RBarr;</code> </td> <td> U+02910 </td> <td> <span class="glyph" title="">&#10512;</span> </td> <tr id="entity-REG"><td> <code title="">REG;</code> </td> <td> U+000AE </td> <td> <span class="glyph" title="">&reg;</span> </td> <tr id="entity-Racute"><td> <code title="">Racute;</code> </td> <td> U+00154 </td> <td> <span class="glyph" title="">&#340;</span> </td> <tr id="entity-Rang"><td> <code title="">Rang;</code> </td> <td> U+027EB </td> <td> <span class="glyph" title="">&#10219;</span> </td> <tr id="entity-Rarr"><td> <code title="">Rarr;</code> </td> <td> U+021A0 </td> <td> <span class="glyph" title="">&#8608;</span> </td> <tr id="entity-Rarrtl"><td> <code title="">Rarrtl;</code> </td> <td> U+02916 </td> <td> <span class="glyph" title="">&#10518;</span> </td> <tr id="entity-Rcaron"><td> <code title="">Rcaron;</code> </td> <td> U+00158 </td> <td> <span class="glyph" title="">&#344;</span> </td> <tr id="entity-Rcedil"><td> <code title="">Rcedil;</code> </td> <td> U+00156 </td> <td> <span class="glyph" title="">&#342;</span> </td> <tr id="entity-Rcy"><td> <code title="">Rcy;</code> </td> <td> U+00420 </td> <td> <span class="glyph" title="">&#1056;</span> </td> <tr id="entity-Re"><td> <code title="">Re;</code> </td> <td> U+0211C </td> <td> <span class="glyph" title="">&real;</span> </td> <tr id="entity-ReverseElement"><td> <code title="">ReverseElement;</code> </td> <td> U+0220B </td> <td> <span class="glyph" title="">&ni;</span> </td> <tr id="entity-ReverseEquilibrium"><td> <code title="">ReverseEquilibrium;</code> </td> <td> U+021CB </td> <td> <span class="glyph" title="">&#8651;</span> </td> <tr id="entity-ReverseUpEquilibrium"><td> <code title="">ReverseUpEquilibrium;</code> </td> <td> U+0296F </td> <td> <span class="glyph" title="">&#10607;</span> </td> <tr id="entity-Rfr"><td> <code title="">Rfr;</code> </td> <td> U+0211C </td> <td> <span class="glyph" title="">&real;</span> </td> <tr id="entity-Rho"><td> <code title="">Rho;</code> </td> <td> U+003A1 </td> <td> <span class="glyph" title="">&Rho;</span> </td> <tr id="entity-RightAngleBracket"><td> <code title="">RightAngleBracket;</code> </td> <td> U+027E9 </td> <td> <span class="glyph" title="">&#9002;</span> </td> <tr id="entity-RightArrow"><td> <code title="">RightArrow;</code> </td> <td> U+02192 </td> <td> <span class="glyph" title="">&rarr;</span> </td> <tr id="entity-RightArrowBar"><td> <code title="">RightArrowBar;</code> </td> <td> U+021E5 </td> <td> <span class="glyph" title="">&#8677;</span> </td> <tr id="entity-RightArrowLeftArrow"><td> <code title="">RightArrowLeftArrow;</code> </td> <td> U+021C4 </td> <td> <span class="glyph" title="">&#8644;</span> </td> <tr id="entity-RightCeiling"><td> <code title="">RightCeiling;</code> </td> <td> U+02309 </td> <td> <span class="glyph" title="">&rceil;</span> </td> <tr id="entity-RightDoubleBracket"><td> <code title="">RightDoubleBracket;</code> </td> <td> U+027E7 </td> <td> <span class="glyph" title="">&#10215;</span> </td> <tr id="entity-RightDownTeeVector"><td> <code title="">RightDownTeeVector;</code> </td> <td> U+0295D </td> <td> <span class="glyph" title="">&#10589;</span> </td> <tr id="entity-RightDownVector"><td> <code title="">RightDownVector;</code> </td> <td> U+021C2 </td> <td> <span class="glyph" title="">&#8642;</span> </td> <tr id="entity-RightDownVectorBar"><td> <code title="">RightDownVectorBar;</code> </td> <td> U+02955 </td> <td> <span class="glyph" title="">&#10581;</span> </td> <tr id="entity-RightFloor"><td> <code title="">RightFloor;</code> </td> <td> U+0230B </td> <td> <span class="glyph" title="">&rfloor;</span> </td> <tr id="entity-RightTee"><td> <code title="">RightTee;</code> </td> <td> U+022A2 </td> <td> <span class="glyph" title="">&#8866;</span> </td> <tr id="entity-RightTeeArrow"><td> <code title="">RightTeeArrow;</code> </td> <td> U+021A6 </td> <td> <span class="glyph" title="">&#8614;</span> </td> <tr id="entity-RightTeeVector"><td> <code title="">RightTeeVector;</code> </td> <td> U+0295B </td> <td> <span class="glyph" title="">&#10587;</span> </td> <tr id="entity-RightTriangle"><td> <code title="">RightTriangle;</code> </td> <td> U+022B3 </td> <td> <span class="glyph" title="">&#8883;</span> </td> <tr id="entity-RightTriangleBar"><td> <code title="">RightTriangleBar;</code> </td> <td> U+029D0 </td> <td> <span class="glyph" title="">&#10704;</span> </td> <tr id="entity-RightTriangleEqual"><td> <code title="">RightTriangleEqual;</code> </td> <td> U+022B5 </td> <td> <span class="glyph" title="">&#8885;</span> </td> <tr id="entity-RightUpDownVector"><td> <code title="">RightUpDownVector;</code> </td> <td> U+0294F </td> <td> <span class="glyph" title="">&#10575;</span> </td> <tr id="entity-RightUpTeeVector"><td> <code title="">RightUpTeeVector;</code> </td> <td> U+0295C </td> <td> <span class="glyph" title="">&#10588;</span> </td> <tr id="entity-RightUpVector"><td> <code title="">RightUpVector;</code> </td> <td> U+021BE </td> <td> <span class="glyph" title="">&#8638;</span> </td> <tr id="entity-RightUpVectorBar"><td> <code title="">RightUpVectorBar;</code> </td> <td> U+02954 </td> <td> <span class="glyph" title="">&#10580;</span> </td> <tr id="entity-RightVector"><td> <code title="">RightVector;</code> </td> <td> U+021C0 </td> <td> <span class="glyph" title="">&#8640;</span> </td> <tr id="entity-RightVectorBar"><td> <code title="">RightVectorBar;</code> </td> <td> U+02953 </td> <td> <span class="glyph" title="">&#10579;</span> </td> <tr id="entity-Rightarrow"><td> <code title="">Rightarrow;</code> </td> <td> U+021D2 </td> <td> <span class="glyph" title="">&rArr;</span> </td> <tr id="entity-Ropf"><td> <code title="">Ropf;</code> </td> <td> U+0211D </td> <td> <span class="glyph" title="">&#8477;</span> </td> <tr id="entity-RoundImplies"><td> <code title="">RoundImplies;</code> </td> <td> U+02970 </td> <td> <span class="glyph" title="">&#10608;</span> </td> <tr id="entity-Rrightarrow"><td> <code title="">Rrightarrow;</code> </td> <td> U+021DB </td> <td> <span class="glyph" title="">&#8667;</span> </td> <tr id="entity-Rscr"><td> <code title="">Rscr;</code> </td> <td> U+0211B </td> <td> <span class="glyph" title="">&#8475;</span> </td> <tr id="entity-Rsh"><td> <code title="">Rsh;</code> </td> <td> U+021B1 </td> <td> <span class="glyph" title="">&#8625;</span> </td> <tr id="entity-RuleDelayed"><td> <code title="">RuleDelayed;</code> </td> <td> U+029F4 </td> <td> <span class="glyph" title="">&#10740;</span> </td> <tr id="entity-SHCHcy"><td> <code title="">SHCHcy;</code> </td> <td> U+00429 </td> <td> <span class="glyph" title="">&#1065;</span> </td> <tr id="entity-SHcy"><td> <code title="">SHcy;</code> </td> <td> U+00428 </td> <td> <span class="glyph" title="">&#1064;</span> </td> <tr id="entity-SOFTcy"><td> <code title="">SOFTcy;</code> </td> <td> U+0042C </td> <td> <span class="glyph" title="">&#1068;</span> </td> <tr id="entity-Sacute"><td> <code title="">Sacute;</code> </td> <td> U+0015A </td> <td> <span class="glyph" title="">&#346;</span> </td> <tr id="entity-Sc"><td> <code title="">Sc;</code> </td> <td> U+02ABC </td> <td> <span class="glyph" title="">&#10940;</span> </td> <tr id="entity-Scaron"><td> <code title="">Scaron;</code> </td> <td> U+00160 </td> <td> <span class="glyph" title="">&Scaron;</span> </td> <tr id="entity-Scedil"><td> <code title="">Scedil;</code> </td> <td> U+0015E </td> <td> <span class="glyph" title="">&#350;</span> </td> <tr id="entity-Scirc"><td> <code title="">Scirc;</code> </td> <td> U+0015C </td> <td> <span class="glyph" title="">&#348;</span> </td> <tr id="entity-Scy"><td> <code title="">Scy;</code> </td> <td> U+00421 </td> <td> <span class="glyph" title="">&#1057;</span> </td> <tr id="entity-Sfr"><td> <code title="">Sfr;</code> </td> <td> U+1D516 </td> <td> <span class="glyph" title="">&#120086;</span> </td> <tr id="entity-ShortDownArrow"><td> <code title="">ShortDownArrow;</code> </td> <td> U+02193 </td> <td> <span class="glyph" title="">&darr;</span> </td> <tr id="entity-ShortLeftArrow"><td> <code title="">ShortLeftArrow;</code> </td> <td> U+02190 </td> <td> <span class="glyph" title="">&larr;</span> </td> <tr id="entity-ShortRightArrow"><td> <code title="">ShortRightArrow;</code> </td> <td> U+02192 </td> <td> <span class="glyph" title="">&rarr;</span> </td> <tr id="entity-ShortUpArrow"><td> <code title="">ShortUpArrow;</code> </td> <td> U+02191 </td> <td> <span class="glyph" title="">&uarr;</span> </td> <tr id="entity-Sigma"><td> <code title="">Sigma;</code> </td> <td> U+003A3 </td> <td> <span class="glyph" title="">&Sigma;</span> </td> <tr id="entity-SmallCircle"><td> <code title="">SmallCircle;</code> </td> <td> U+02218 </td> <td> <span class="glyph" title="">&#8728;</span> </td> <tr id="entity-Sopf"><td> <code title="">Sopf;</code> </td> <td> U+1D54A </td> <td> <span class="glyph" title="">&#120138;</span> </td> <tr id="entity-Sqrt"><td> <code title="">Sqrt;</code> </td> <td> U+0221A </td> <td> <span class="glyph" title="">&radic;</span> </td> <tr id="entity-Square"><td> <code title="">Square;</code> </td> <td> U+025A1 </td> <td> <span class="glyph" title="">&#9633;</span> </td> <tr id="entity-SquareIntersection"><td> <code title="">SquareIntersection;</code> </td> <td> U+02293 </td> <td> <span class="glyph" title="">&#8851;</span> </td> <tr id="entity-SquareSubset"><td> <code title="">SquareSubset;</code> </td> <td> U+0228F </td> <td> <span class="glyph" title="">&#8847;</span> </td> <tr id="entity-SquareSubsetEqual"><td> <code title="">SquareSubsetEqual;</code> </td> <td> U+02291 </td> <td> <span class="glyph" title="">&#8849;</span> </td> <tr id="entity-SquareSuperset"><td> <code title="">SquareSuperset;</code> </td> <td> U+02290 </td> <td> <span class="glyph" title="">&#8848;</span> </td> <tr id="entity-SquareSupersetEqual"><td> <code title="">SquareSupersetEqual;</code> </td> <td> U+02292 </td> <td> <span class="glyph" title="">&#8850;</span> </td> <tr id="entity-SquareUnion"><td> <code title="">SquareUnion;</code> </td> <td> U+02294 </td> <td> <span class="glyph" title="">&#8852;</span> </td> <tr id="entity-Sscr"><td> <code title="">Sscr;</code> </td> <td> U+1D4AE </td> <td> <span class="glyph" title="">&#119982;</span> </td> <tr id="entity-Star"><td> <code title="">Star;</code> </td> <td> U+022C6 </td> <td> <span class="glyph" title="">&#8902;</span> </td> <tr id="entity-Sub"><td> <code title="">Sub;</code> </td> <td> U+022D0 </td> <td> <span class="glyph" title="">&#8912;</span> </td> <tr id="entity-Subset"><td> <code title="">Subset;</code> </td> <td> U+022D0 </td> <td> <span class="glyph" title="">&#8912;</span> </td> <tr id="entity-SubsetEqual"><td> <code title="">SubsetEqual;</code> </td> <td> U+02286 </td> <td> <span class="glyph" title="">&sube;</span> </td> <tr id="entity-Succeeds"><td> <code title="">Succeeds;</code> </td> <td> U+0227B </td> <td> <span class="glyph" title="">&#8827;</span> </td> <tr id="entity-SucceedsEqual"><td> <code title="">SucceedsEqual;</code> </td> <td> U+02AB0 </td> <td> <span class="glyph" title="">&#10928;</span> </td> <tr id="entity-SucceedsSlantEqual"><td> <code title="">SucceedsSlantEqual;</code> </td> <td> U+0227D </td> <td> <span class="glyph" title="">&#8829;</span> </td> <tr id="entity-SucceedsTilde"><td> <code title="">SucceedsTilde;</code> </td> <td> U+0227F </td> <td> <span class="glyph" title="">&#8831;</span> </td> <tr id="entity-SuchThat"><td> <code title="">SuchThat;</code> </td> <td> U+0220B </td> <td> <span class="glyph" title="">&ni;</span> </td> <tr id="entity-Sum"><td> <code title="">Sum;</code> </td> <td> U+02211 </td> <td> <span class="glyph" title="">&sum;</span> </td> <tr id="entity-Sup"><td> <code title="">Sup;</code> </td> <td> U+022D1 </td> <td> <span class="glyph" title="">&#8913;</span> </td> <tr id="entity-Superset"><td> <code title="">Superset;</code> </td> <td> U+02283 </td> <td> <span class="glyph" title="">&sup;</span> </td> <tr id="entity-SupersetEqual"><td> <code title="">SupersetEqual;</code> </td> <td> U+02287 </td> <td> <span class="glyph" title="">&supe;</span> </td> <tr id="entity-Supset"><td> <code title="">Supset;</code> </td> <td> U+022D1 </td> <td> <span class="glyph" title="">&#8913;</span> </td> <tr id="entity-THORN"><td> <code title="">THORN;</code> </td> <td> U+000DE </td> <td> <span class="glyph" title="">&THORN;</span> </td> <tr id="entity-TRADE"><td> <code title="">TRADE;</code> </td> <td> U+02122 </td> <td> <span class="glyph" title="">&trade;</span> </td> <tr id="entity-TSHcy"><td> <code title="">TSHcy;</code> </td> <td> U+0040B </td> <td> <span class="glyph" title="">&#1035;</span> </td> <tr id="entity-TScy"><td> <code title="">TScy;</code> </td> <td> U+00426 </td> <td> <span class="glyph" title="">&#1062;</span> </td> <tr id="entity-Tab"><td> <code title="">Tab;</code> </td> <td> U+00009 </td> <td> <span class="glyph control" title="">&#9225;</span> </td> <tr id="entity-Tau"><td> <code title="">Tau;</code> </td> <td> U+003A4 </td> <td> <span class="glyph" title="">&Tau;</span> </td> <tr id="entity-Tcaron"><td> <code title="">Tcaron;</code> </td> <td> U+00164 </td> <td> <span class="glyph" title="">&#356;</span> </td> <tr id="entity-Tcedil"><td> <code title="">Tcedil;</code> </td> <td> U+00162 </td> <td> <span class="glyph" title="">&#354;</span> </td> <tr id="entity-Tcy"><td> <code title="">Tcy;</code> </td> <td> U+00422 </td> <td> <span class="glyph" title="">&#1058;</span> </td> <tr id="entity-Tfr"><td> <code title="">Tfr;</code> </td> <td> U+1D517 </td> <td> <span class="glyph" title="">&#120087;</span> </td> <tr id="entity-Therefore"><td> <code title="">Therefore;</code> </td> <td> U+02234 </td> <td> <span class="glyph" title="">&there4;</span> </td> <tr id="entity-Theta"><td> <code title="">Theta;</code> </td> <td> U+00398 </td> <td> <span class="glyph" title="">&Theta;</span> </td> <tr id="entity-ThickSpace"><td> <code title="">ThickSpace;</code> </td> <td> U+0205F U+0200A </td> <td> <span class="glyph compound" title="">&#8287;&#8202;</span> </td> <tr id="entity-ThinSpace"><td> <code title="">ThinSpace;</code> </td> <td> U+02009 </td> <td> <span class="glyph" title="">&thinsp;</span> </td> <tr id="entity-Tilde"><td> <code title="">Tilde;</code> </td> <td> U+0223C </td> <td> <span class="glyph" title="">&sim;</span> </td> <tr id="entity-TildeEqual"><td> <code title="">TildeEqual;</code> </td> <td> U+02243 </td> <td> <span class="glyph" title="">&#8771;</span> </td> <tr id="entity-TildeFullEqual"><td> <code title="">TildeFullEqual;</code> </td> <td> U+02245 </td> <td> <span class="glyph" title="">&cong;</span> </td> <tr id="entity-TildeTilde"><td> <code title="">TildeTilde;</code> </td> <td> U+02248 </td> <td> <span class="glyph" title="">&asymp;</span> </td> <tr id="entity-Topf"><td> <code title="">Topf;</code> </td> <td> U+1D54B </td> <td> <span class="glyph" title="">&#120139;</span> </td> <tr id="entity-TripleDot"><td> <code title="">TripleDot;</code> </td> <td> U+020DB </td> <td> <span class="glyph composition" title="">&#9676;&#8411;</span> </td> <tr id="entity-Tscr"><td> <code title="">Tscr;</code> </td> <td> U+1D4AF </td> <td> <span class="glyph" title="">&#119983;</span> </td> <tr id="entity-Tstrok"><td> <code title="">Tstrok;</code> </td> <td> U+00166 </td> <td> <span class="glyph" title="">&#358;</span> </td> <tr id="entity-Uacute"><td> <code title="">Uacute;</code> </td> <td> U+000DA </td> <td> <span class="glyph" title="">&Uacute;</span> </td> <tr id="entity-Uarr"><td> <code title="">Uarr;</code> </td> <td> U+0219F </td> <td> <span class="glyph" title="">&#8607;</span> </td> <tr id="entity-Uarrocir"><td> <code title="">Uarrocir;</code> </td> <td> U+02949 </td> <td> <span class="glyph" title="">&#10569;</span> </td> <tr id="entity-Ubrcy"><td> <code title="">Ubrcy;</code> </td> <td> U+0040E </td> <td> <span class="glyph" title="">&#1038;</span> </td> <tr id="entity-Ubreve"><td> <code title="">Ubreve;</code> </td> <td> U+0016C </td> <td> <span class="glyph" title="">&#364;</span> </td> <tr id="entity-Ucirc"><td> <code title="">Ucirc;</code> </td> <td> U+000DB </td> <td> <span class="glyph" title="">&Ucirc;</span> </td> <tr id="entity-Ucy"><td> <code title="">Ucy;</code> </td> <td> U+00423 </td> <td> <span class="glyph" title="">&#1059;</span> </td> <tr id="entity-Udblac"><td> <code title="">Udblac;</code> </td> <td> U+00170 </td> <td> <span class="glyph" title="">&#368;</span> </td> <tr id="entity-Ufr"><td> <code title="">Ufr;</code> </td> <td> U+1D518 </td> <td> <span class="glyph" title="">&#120088;</span> </td> <tr id="entity-Ugrave"><td> <code title="">Ugrave;</code> </td> <td> U+000D9 </td> <td> <span class="glyph" title="">&Ugrave;</span> </td> <tr id="entity-Umacr"><td> <code title="">Umacr;</code> </td> <td> U+0016A </td> <td> <span class="glyph" title="">&#362;</span> </td> <tr id="entity-UnderBar"><td> <code title="">UnderBar;</code> </td> <td> U+0005F </td> <td> <span class="glyph" title="">_</span> </td> <tr id="entity-UnderBrace"><td> <code title="">UnderBrace;</code> </td> <td> U+023DF </td> <td> <span class="glyph" title="">&#9183;</span> </td> <tr id="entity-UnderBracket"><td> <code title="">UnderBracket;</code> </td> <td> U+023B5 </td> <td> <span class="glyph" title="">&#9141;</span> </td> <tr id="entity-UnderParenthesis"><td> <code title="">UnderParenthesis;</code> </td> <td> U+023DD </td> <td> <span class="glyph" title="">&#9181;</span> </td> <tr id="entity-Union"><td> <code title="">Union;</code> </td> <td> U+022C3 </td> <td> <span class="glyph" title="">&#8899;</span> </td> <tr id="entity-UnionPlus"><td> <code title="">UnionPlus;</code> </td> <td> U+0228E </td> <td> <span class="glyph" title="">&#8846;</span> </td> <tr id="entity-Uogon"><td> <code title="">Uogon;</code> </td> <td> U+00172 </td> <td> <span class="glyph" title="">&#370;</span> </td> <tr id="entity-Uopf"><td> <code title="">Uopf;</code> </td> <td> U+1D54C </td> <td> <span class="glyph" title="">&#120140;</span> </td> <tr id="entity-UpArrow"><td> <code title="">UpArrow;</code> </td> <td> U+02191 </td> <td> <span class="glyph" title="">&uarr;</span> </td> <tr id="entity-UpArrowBar"><td> <code title="">UpArrowBar;</code> </td> <td> U+02912 </td> <td> <span class="glyph" title="">&#10514;</span> </td> <tr id="entity-UpArrowDownArrow"><td> <code title="">UpArrowDownArrow;</code> </td> <td> U+021C5 </td> <td> <span class="glyph" title="">&#8645;</span> </td> <tr id="entity-UpDownArrow"><td> <code title="">UpDownArrow;</code> </td> <td> U+02195 </td> <td> <span class="glyph" title="">&#8597;</span> </td> <tr id="entity-UpEquilibrium"><td> <code title="">UpEquilibrium;</code> </td> <td> U+0296E </td> <td> <span class="glyph" title="">&#10606;</span> </td> <tr id="entity-UpTee"><td> <code title="">UpTee;</code> </td> <td> U+022A5 </td> <td> <span class="glyph" title="">&perp;</span> </td> <tr id="entity-UpTeeArrow"><td> <code title="">UpTeeArrow;</code> </td> <td> U+021A5 </td> <td> <span class="glyph" title="">&#8613;</span> </td> <tr id="entity-Uparrow"><td> <code title="">Uparrow;</code> </td> <td> U+021D1 </td> <td> <span class="glyph" title="">&uArr;</span> </td> <tr id="entity-Updownarrow"><td> <code title="">Updownarrow;</code> </td> <td> U+021D5 </td> <td> <span class="glyph" title="">&#8661;</span> </td> <tr id="entity-UpperLeftArrow"><td> <code title="">UpperLeftArrow;</code> </td> <td> U+02196 </td> <td> <span class="glyph" title="">&#8598;</span> </td> <tr id="entity-UpperRightArrow"><td> <code title="">UpperRightArrow;</code> </td> <td> U+02197 </td> <td> <span class="glyph" title="">&#8599;</span> </td> <tr id="entity-Upsi"><td> <code title="">Upsi;</code> </td> <td> U+003D2 </td> <td> <span class="glyph" title="">&upsih;</span> </td> <tr id="entity-Upsilon"><td> <code title="">Upsilon;</code> </td> <td> U+003A5 </td> <td> <span class="glyph" title="">&Upsilon;</span> </td> <tr id="entity-Uring"><td> <code title="">Uring;</code> </td> <td> U+0016E </td> <td> <span class="glyph" title="">&#366;</span> </td> <tr id="entity-Uscr"><td> <code title="">Uscr;</code> </td> <td> U+1D4B0 </td> <td> <span class="glyph" title="">&#119984;</span> </td> <tr id="entity-Utilde"><td> <code title="">Utilde;</code> </td> <td> U+00168 </td> <td> <span class="glyph" title="">&#360;</span> </td> <tr id="entity-Uuml"><td> <code title="">Uuml;</code> </td> <td> U+000DC </td> <td> <span class="glyph" title="">&Uuml;</span> </td> <tr id="entity-VDash"><td> <code title="">VDash;</code> </td> <td> U+022AB </td> <td> <span class="glyph" title="">&#8875;</span> </td> <tr id="entity-Vbar"><td> <code title="">Vbar;</code> </td> <td> U+02AEB </td> <td> <span class="glyph" title="">&#10987;</span> </td> <tr id="entity-Vcy"><td> <code title="">Vcy;</code> </td> <td> U+00412 </td> <td> <span class="glyph" title="">&#1042;</span> </td> <tr id="entity-Vdash"><td> <code title="">Vdash;</code> </td> <td> U+022A9 </td> <td> <span class="glyph" title="">&#8873;</span> </td> <tr id="entity-Vdashl"><td> <code title="">Vdashl;</code> </td> <td> U+02AE6 </td> <td> <span class="glyph" title="">&#10982;</span> </td> <tr id="entity-Vee"><td> <code title="">Vee;</code> </td> <td> U+022C1 </td> <td> <span class="glyph" title="">&#8897;</span> </td> <tr id="entity-Verbar"><td> <code title="">Verbar;</code> </td> <td> U+02016 </td> <td> <span class="glyph" title="">&#8214;</span> </td> <tr id="entity-Vert"><td> <code title="">Vert;</code> </td> <td> U+02016 </td> <td> <span class="glyph" title="">&#8214;</span> </td> <tr id="entity-VerticalBar"><td> <code title="">VerticalBar;</code> </td> <td> U+02223 </td> <td> <span class="glyph" title="">&#8739;</span> </td> <tr id="entity-VerticalLine"><td> <code title="">VerticalLine;</code> </td> <td> U+0007C </td> <td> <span class="glyph" title="">|</span> </td> <tr id="entity-VerticalSeparator"><td> <code title="">VerticalSeparator;</code> </td> <td> U+02758 </td> <td> <span class="glyph" title="">&#10072;</span> </td> <tr id="entity-VerticalTilde"><td> <code title="">VerticalTilde;</code> </td> <td> U+02240 </td> <td> <span class="glyph" title="">&#8768;</span> </td> <tr id="entity-VeryThinSpace"><td> <code title="">VeryThinSpace;</code> </td> <td> U+0200A </td> <td> <span class="glyph" title="">&#8202;</span> </td> <tr id="entity-Vfr"><td> <code title="">Vfr;</code> </td> <td> U+1D519 </td> <td> <span class="glyph" title="">&#120089;</span> </td> <tr id="entity-Vopf"><td> <code title="">Vopf;</code> </td> <td> U+1D54D </td> <td> <span class="glyph" title="">&#120141;</span> </td> <tr id="entity-Vscr"><td> <code title="">Vscr;</code> </td> <td> U+1D4B1 </td> <td> <span class="glyph" title="">&#119985;</span> </td> <tr id="entity-Vvdash"><td> <code title="">Vvdash;</code> </td> <td> U+022AA </td> <td> <span class="glyph" title="">&#8874;</span> </td> <tr id="entity-Wcirc"><td> <code title="">Wcirc;</code> </td> <td> U+00174 </td> <td> <span class="glyph" title="">&#372;</span> </td> <tr id="entity-Wedge"><td> <code title="">Wedge;</code> </td> <td> U+022C0 </td> <td> <span class="glyph" title="">&#8896;</span> </td> <tr id="entity-Wfr"><td> <code title="">Wfr;</code> </td> <td> U+1D51A </td> <td> <span class="glyph" title="">&#120090;</span> </td> <tr id="entity-Wopf"><td> <code title="">Wopf;</code> </td> <td> U+1D54E </td> <td> <span class="glyph" title="">&#120142;</span> </td> <tr id="entity-Wscr"><td> <code title="">Wscr;</code> </td> <td> U+1D4B2 </td> <td> <span class="glyph" title="">&#119986;</span> </td> <tr id="entity-Xfr"><td> <code title="">Xfr;</code> </td> <td> U+1D51B </td> <td> <span class="glyph" title="">&#120091;</span> </td> <tr id="entity-Xi"><td> <code title="">Xi;</code> </td> <td> U+0039E </td> <td> <span class="glyph" title="">&Xi;</span> </td> <tr id="entity-Xopf"><td> <code title="">Xopf;</code> </td> <td> U+1D54F </td> <td> <span class="glyph" title="">&#120143;</span> </td> <tr id="entity-Xscr"><td> <code title="">Xscr;</code> </td> <td> U+1D4B3 </td> <td> <span class="glyph" title="">&#119987;</span> </td> <tr id="entity-YAcy"><td> <code title="">YAcy;</code> </td> <td> U+0042F </td> <td> <span class="glyph" title="">&#1071;</span> </td> <tr id="entity-YIcy"><td> <code title="">YIcy;</code> </td> <td> U+00407 </td> <td> <span class="glyph" title="">&#1031;</span> </td> <tr id="entity-YUcy"><td> <code title="">YUcy;</code> </td> <td> U+0042E </td> <td> <span class="glyph" title="">&#1070;</span> </td> <tr id="entity-Yacute"><td> <code title="">Yacute;</code> </td> <td> U+000DD </td> <td> <span class="glyph" title="">&Yacute;</span> </td> <tr id="entity-Ycirc"><td> <code title="">Ycirc;</code> </td> <td> U+00176 </td> <td> <span class="glyph" title="">&#374;</span> </td> <tr id="entity-Ycy"><td> <code title="">Ycy;</code> </td> <td> U+0042B </td> <td> <span class="glyph" title="">&#1067;</span> </td> <tr id="entity-Yfr"><td> <code title="">Yfr;</code> </td> <td> U+1D51C </td> <td> <span class="glyph" title="">&#120092;</span> </td> <tr id="entity-Yopf"><td> <code title="">Yopf;</code> </td> <td> U+1D550 </td> <td> <span class="glyph" title="">&#120144;</span> </td> <tr id="entity-Yscr"><td> <code title="">Yscr;</code> </td> <td> U+1D4B4 </td> <td> <span class="glyph" title="">&#119988;</span> </td> <tr id="entity-Yuml"><td> <code title="">Yuml;</code> </td> <td> U+00178 </td> <td> <span class="glyph" title="">&Yuml;</span> </td> <tr id="entity-ZHcy"><td> <code title="">ZHcy;</code> </td> <td> U+00416 </td> <td> <span class="glyph" title="">&#1046;</span> </td> <tr id="entity-Zacute"><td> <code title="">Zacute;</code> </td> <td> U+00179 </td> <td> <span class="glyph" title="">&#377;</span> </td> <tr id="entity-Zcaron"><td> <code title="">Zcaron;</code> </td> <td> U+0017D </td> <td> <span class="glyph" title="">&#381;</span> </td> <tr id="entity-Zcy"><td> <code title="">Zcy;</code> </td> <td> U+00417 </td> <td> <span class="glyph" title="">&#1047;</span> </td> <tr id="entity-Zdot"><td> <code title="">Zdot;</code> </td> <td> U+0017B </td> <td> <span class="glyph" title="">&#379;</span> </td> <tr id="entity-ZeroWidthSpace"><td> <code title="">ZeroWidthSpace;</code> </td> <td> U+0200B </td> <td> <span class="glyph" title="">&#8203;</span> </td> <tr id="entity-Zeta"><td> <code title="">Zeta;</code> </td> <td> U+00396 </td> <td> <span class="glyph" title="">&Zeta;</span> </td> <tr id="entity-Zfr"><td> <code title="">Zfr;</code> </td> <td> U+02128 </td> <td> <span class="glyph" title="">&#8488;</span> </td> <tr id="entity-Zopf"><td> <code title="">Zopf;</code> </td> <td> U+02124 </td> <td> <span class="glyph" title="">&#8484;</span> </td> <tr id="entity-Zscr"><td> <code title="">Zscr;</code> </td> <td> U+1D4B5 </td> <td> <span class="glyph" title="">&#119989;</span> </td> <tr id="entity-aacute"><td> <code title="">aacute;</code> </td> <td> U+000E1 </td> <td> <span class="glyph" title="">&aacute;</span> </td> <tr id="entity-abreve"><td> <code title="">abreve;</code> </td> <td> U+00103 </td> <td> <span class="glyph" title="">&#259;</span> </td> <tr id="entity-ac"><td> <code title="">ac;</code> </td> <td> U+0223E </td> <td> <span class="glyph" title="">&#8766;</span> </td> <tr id="entity-acE"><td> <code title="">acE;</code> </td> <td> U+0223E U+00333 </td> <td> <span class="glyph compound" title="">&#8766;&#819;</span> </td> <tr id="entity-acd"><td> <code title="">acd;</code> </td> <td> U+0223F </td> <td> <span class="glyph" title="">&#8767;</span> </td> <tr id="entity-acirc"><td> <code title="">acirc;</code> </td> <td> U+000E2 </td> <td> <span class="glyph" title="">&acirc;</span> </td> <tr id="entity-acute"><td> <code title="">acute;</code> </td> <td> U+000B4 </td> <td> <span class="glyph" title="">&acute;</span> </td> <tr id="entity-acy"><td> <code title="">acy;</code> </td> <td> U+00430 </td> <td> <span class="glyph" title="">&#1072;</span> </td> <tr id="entity-aelig"><td> <code title="">aelig;</code> </td> <td> U+000E6 </td> <td> <span class="glyph" title="">&aelig;</span> </td> <tr id="entity-af"><td> <code title="">af;</code> </td> <td> U+02061 </td> <td> <span class="glyph" title="">&#8289;</span> </td> <tr id="entity-afr"><td> <code title="">afr;</code> </td> <td> U+1D51E </td> <td> <span class="glyph" title="">&#120094;</span> </td> <tr id="entity-agrave"><td> <code title="">agrave;</code> </td> <td> U+000E0 </td> <td> <span class="glyph" title="">&agrave;</span> </td> <tr id="entity-alefsym"><td> <code title="">alefsym;</code> </td> <td> U+02135 </td> <td> <span class="glyph" title="">&alefsym;</span> </td> <tr id="entity-aleph"><td> <code title="">aleph;</code> </td> <td> U+02135 </td> <td> <span class="glyph" title="">&alefsym;</span> </td> <tr id="entity-alpha"><td> <code title="">alpha;</code> </td> <td> U+003B1 </td> <td> <span class="glyph" title="">&alpha;</span> </td> <tr id="entity-amacr"><td> <code title="">amacr;</code> </td> <td> U+00101 </td> <td> <span class="glyph" title="">&#257;</span> </td> <tr id="entity-amalg"><td> <code title="">amalg;</code> </td> <td> U+02A3F </td> <td> <span class="glyph" title="">&#10815;</span> </td> <tr id="entity-amp"><td> <code title="">amp;</code> </td> <td> U+00026 </td> <td> <span class="glyph" title="">&amp;</span> </td> <tr id="entity-and"><td> <code title="">and;</code> </td> <td> U+02227 </td> <td> <span class="glyph" title="">&and;</span> </td> <tr id="entity-andand"><td> <code title="">andand;</code> </td> <td> U+02A55 </td> <td> <span class="glyph" title="">&#10837;</span> </td> <tr id="entity-andd"><td> <code title="">andd;</code> </td> <td> U+02A5C </td> <td> <span class="glyph" title="">&#10844;</span> </td> <tr id="entity-andslope"><td> <code title="">andslope;</code> </td> <td> U+02A58 </td> <td> <span class="glyph" title="">&#10840;</span> </td> <tr id="entity-andv"><td> <code title="">andv;</code> </td> <td> U+02A5A </td> <td> <span class="glyph" title="">&#10842;</span> </td> <tr id="entity-ang"><td> <code title="">ang;</code> </td> <td> U+02220 </td> <td> <span class="glyph" title="">&ang;</span> </td> <tr id="entity-ange"><td> <code title="">ange;</code> </td> <td> U+029A4 </td> <td> <span class="glyph" title="">&#10660;</span> </td> <tr id="entity-angle"><td> <code title="">angle;</code> </td> <td> U+02220 </td> <td> <span class="glyph" title="">&ang;</span> </td> <tr id="entity-angmsd"><td> <code title="">angmsd;</code> </td> <td> U+02221 </td> <td> <span class="glyph" title="">&#8737;</span> </td> <tr id="entity-angmsdaa"><td> <code title="">angmsdaa;</code> </td> <td> U+029A8 </td> <td> <span class="glyph" title="">&#10664;</span> </td> <tr id="entity-angmsdab"><td> <code title="">angmsdab;</code> </td> <td> U+029A9 </td> <td> <span class="glyph" title="">&#10665;</span> </td> <tr id="entity-angmsdac"><td> <code title="">angmsdac;</code> </td> <td> U+029AA </td> <td> <span class="glyph" title="">&#10666;</span> </td> <tr id="entity-angmsdad"><td> <code title="">angmsdad;</code> </td> <td> U+029AB </td> <td> <span class="glyph" title="">&#10667;</span> </td> <tr id="entity-angmsdae"><td> <code title="">angmsdae;</code> </td> <td> U+029AC </td> <td> <span class="glyph" title="">&#10668;</span> </td> <tr id="entity-angmsdaf"><td> <code title="">angmsdaf;</code> </td> <td> U+029AD </td> <td> <span class="glyph" title="">&#10669;</span> </td> <tr id="entity-angmsdag"><td> <code title="">angmsdag;</code> </td> <td> U+029AE </td> <td> <span class="glyph" title="">&#10670;</span> </td> <tr id="entity-angmsdah"><td> <code title="">angmsdah;</code> </td> <td> U+029AF </td> <td> <span class="glyph" title="">&#10671;</span> </td> <tr id="entity-angrt"><td> <code title="">angrt;</code> </td> <td> U+0221F </td> <td> <span class="glyph" title="">&#8735;</span> </td> <tr id="entity-angrtvb"><td> <code title="">angrtvb;</code> </td> <td> U+022BE </td> <td> <span class="glyph" title="">&#8894;</span> </td> <tr id="entity-angrtvbd"><td> <code title="">angrtvbd;</code> </td> <td> U+0299D </td> <td> <span class="glyph" title="">&#10653;</span> </td> <tr id="entity-angsph"><td> <code title="">angsph;</code> </td> <td> U+02222 </td> <td> <span class="glyph" title="">&#8738;</span> </td> <tr id="entity-angst"><td> <code title="">angst;</code> </td> <td> U+000C5 </td> <td> <span class="glyph" title="">&Aring;</span> </td> <tr id="entity-angzarr"><td> <code title="">angzarr;</code> </td> <td> U+0237C </td> <td> <span class="glyph" title="">&#9084;</span> </td> <tr id="entity-aogon"><td> <code title="">aogon;</code> </td> <td> U+00105 </td> <td> <span class="glyph" title="">&#261;</span> </td> <tr id="entity-aopf"><td> <code title="">aopf;</code> </td> <td> U+1D552 </td> <td> <span class="glyph" title="">&#120146;</span> </td> <tr id="entity-ap"><td> <code title="">ap;</code> </td> <td> U+02248 </td> <td> <span class="glyph" title="">&asymp;</span> </td> <tr id="entity-apE"><td> <code title="">apE;</code> </td> <td> U+02A70 </td> <td> <span class="glyph" title="">&#10864;</span> </td> <tr id="entity-apacir"><td> <code title="">apacir;</code> </td> <td> U+02A6F </td> <td> <span class="glyph" title="">&#10863;</span> </td> <tr id="entity-ape"><td> <code title="">ape;</code> </td> <td> U+0224A </td> <td> <span class="glyph" title="">&#8778;</span> </td> <tr id="entity-apid"><td> <code title="">apid;</code> </td> <td> U+0224B </td> <td> <span class="glyph" title="">&#8779;</span> </td> <tr id="entity-apos"><td> <code title="">apos;</code> </td> <td> U+00027 </td> <td> <span class="glyph" title="">'</span> </td> <tr id="entity-approx"><td> <code title="">approx;</code> </td> <td> U+02248 </td> <td> <span class="glyph" title="">&asymp;</span> </td> <tr id="entity-approxeq"><td> <code title="">approxeq;</code> </td> <td> U+0224A </td> <td> <span class="glyph" title="">&#8778;</span> </td> <tr id="entity-aring"><td> <code title="">aring;</code> </td> <td> U+000E5 </td> <td> <span class="glyph" title="">&aring;</span> </td> <tr id="entity-ascr"><td> <code title="">ascr;</code> </td> <td> U+1D4B6 </td> <td> <span class="glyph" title="">&#119990;</span> </td> <tr id="entity-ast"><td> <code title="">ast;</code> </td> <td> U+0002A </td> <td> <span class="glyph" title="">*</span> </td> <tr id="entity-asymp"><td> <code title="">asymp;</code> </td> <td> U+02248 </td> <td> <span class="glyph" title="">&asymp;</span> </td> <tr id="entity-asympeq"><td> <code title="">asympeq;</code> </td> <td> U+0224D </td> <td> <span class="glyph" title="">&#8781;</span> </td> <tr id="entity-atilde"><td> <code title="">atilde;</code> </td> <td> U+000E3 </td> <td> <span class="glyph" title="">&atilde;</span> </td> <tr id="entity-auml"><td> <code title="">auml;</code> </td> <td> U+000E4 </td> <td> <span class="glyph" title="">&auml;</span> </td> <tr id="entity-awconint"><td> <code title="">awconint;</code> </td> <td> U+02233 </td> <td> <span class="glyph" title="">&#8755;</span> </td> <tr id="entity-awint"><td> <code title="">awint;</code> </td> <td> U+02A11 </td> <td> <span class="glyph" title="">&#10769;</span> </td> <tr id="entity-bNot"><td> <code title="">bNot;</code> </td> <td> U+02AED </td> <td> <span class="glyph" title="">&#10989;</span> </td> <tr id="entity-backcong"><td> <code title="">backcong;</code> </td> <td> U+0224C </td> <td> <span class="glyph" title="">&#8780;</span> </td> <tr id="entity-backepsilon"><td> <code title="">backepsilon;</code> </td> <td> U+003F6 </td> <td> <span class="glyph" title="">&#1014;</span> </td> <tr id="entity-backprime"><td> <code title="">backprime;</code> </td> <td> U+02035 </td> <td> <span class="glyph" title="">&#8245;</span> </td> <tr id="entity-backsim"><td> <code title="">backsim;</code> </td> <td> U+0223D </td> <td> <span class="glyph" title="">&#8765;</span> </td> <tr id="entity-backsimeq"><td> <code title="">backsimeq;</code> </td> <td> U+022CD </td> <td> <span class="glyph" title="">&#8909;</span> </td> <tr id="entity-barvee"><td> <code title="">barvee;</code> </td> <td> U+022BD </td> <td> <span class="glyph" title="">&#8893;</span> </td> <tr id="entity-barwed"><td> <code title="">barwed;</code> </td> <td> U+02305 </td> <td> <span class="glyph" title="">&#8965;</span> </td> <tr id="entity-barwedge"><td> <code title="">barwedge;</code> </td> <td> U+02305 </td> <td> <span class="glyph" title="">&#8965;</span> </td> <tr id="entity-bbrk"><td> <code title="">bbrk;</code> </td> <td> U+023B5 </td> <td> <span class="glyph" title="">&#9141;</span> </td> <tr id="entity-bbrktbrk"><td> <code title="">bbrktbrk;</code> </td> <td> U+023B6 </td> <td> <span class="glyph" title="">&#9142;</span> </td> <tr id="entity-bcong"><td> <code title="">bcong;</code> </td> <td> U+0224C </td> <td> <span class="glyph" title="">&#8780;</span> </td> <tr id="entity-bcy"><td> <code title="">bcy;</code> </td> <td> U+00431 </td> <td> <span class="glyph" title="">&#1073;</span> </td> <tr id="entity-bdquo"><td> <code title="">bdquo;</code> </td> <td> U+0201E </td> <td> <span class="glyph" title="">&bdquo;</span> </td> <tr id="entity-becaus"><td> <code title="">becaus;</code> </td> <td> U+02235 </td> <td> <span class="glyph" title="">&#8757;</span> </td> <tr id="entity-because"><td> <code title="">because;</code> </td> <td> U+02235 </td> <td> <span class="glyph" title="">&#8757;</span> </td> <tr id="entity-bemptyv"><td> <code title="">bemptyv;</code> </td> <td> U+029B0 </td> <td> <span class="glyph" title="">&#10672;</span> </td> <tr id="entity-bepsi"><td> <code title="">bepsi;</code> </td> <td> U+003F6 </td> <td> <span class="glyph" title="">&#1014;</span> </td> <tr id="entity-bernou"><td> <code title="">bernou;</code> </td> <td> U+0212C </td> <td> <span class="glyph" title="">&#8492;</span> </td> <tr id="entity-beta"><td> <code title="">beta;</code> </td> <td> U+003B2 </td> <td> <span class="glyph" title="">&beta;</span> </td> <tr id="entity-beth"><td> <code title="">beth;</code> </td> <td> U+02136 </td> <td> <span class="glyph" title="">&#8502;</span> </td> <tr id="entity-between"><td> <code title="">between;</code> </td> <td> U+0226C </td> <td> <span class="glyph" title="">&#8812;</span> </td> <tr id="entity-bfr"><td> <code title="">bfr;</code> </td> <td> U+1D51F </td> <td> <span class="glyph" title="">&#120095;</span> </td> <tr id="entity-bigcap"><td> <code title="">bigcap;</code> </td> <td> U+022C2 </td> <td> <span class="glyph" title="">&#8898;</span> </td> <tr id="entity-bigcirc"><td> <code title="">bigcirc;</code> </td> <td> U+025EF </td> <td> <span class="glyph" title="">&#9711;</span> </td> <tr id="entity-bigcup"><td> <code title="">bigcup;</code> </td> <td> U+022C3 </td> <td> <span class="glyph" title="">&#8899;</span> </td> <tr id="entity-bigodot"><td> <code title="">bigodot;</code> </td> <td> U+02A00 </td> <td> <span class="glyph" title="">&#10752;</span> </td> <tr id="entity-bigoplus"><td> <code title="">bigoplus;</code> </td> <td> U+02A01 </td> <td> <span class="glyph" title="">&#10753;</span> </td> <tr id="entity-bigotimes"><td> <code title="">bigotimes;</code> </td> <td> U+02A02 </td> <td> <span class="glyph" title="">&#10754;</span> </td> <tr id="entity-bigsqcup"><td> <code title="">bigsqcup;</code> </td> <td> U+02A06 </td> <td> <span class="glyph" title="">&#10758;</span> </td> <tr id="entity-bigstar"><td> <code title="">bigstar;</code> </td> <td> U+02605 </td> <td> <span class="glyph" title="">&#9733;</span> </td> <tr id="entity-bigtriangledown"><td> <code title="">bigtriangledown;</code> </td> <td> U+025BD </td> <td> <span class="glyph" title="">&#9661;</span> </td> <tr id="entity-bigtriangleup"><td> <code title="">bigtriangleup;</code> </td> <td> U+025B3 </td> <td> <span class="glyph" title="">&#9651;</span> </td> <tr id="entity-biguplus"><td> <code title="">biguplus;</code> </td> <td> U+02A04 </td> <td> <span class="glyph" title="">&#10756;</span> </td> <tr id="entity-bigvee"><td> <code title="">bigvee;</code> </td> <td> U+022C1 </td> <td> <span class="glyph" title="">&#8897;</span> </td> <tr id="entity-bigwedge"><td> <code title="">bigwedge;</code> </td> <td> U+022C0 </td> <td> <span class="glyph" title="">&#8896;</span> </td> <tr id="entity-bkarow"><td> <code title="">bkarow;</code> </td> <td> U+0290D </td> <td> <span class="glyph" title="">&#10509;</span> </td> <tr id="entity-blacklozenge"><td> <code title="">blacklozenge;</code> </td> <td> U+029EB </td> <td> <span class="glyph" title="">&#10731;</span> </td> <tr id="entity-blacksquare"><td> <code title="">blacksquare;</code> </td> <td> U+025AA </td> <td> <span class="glyph" title="">&#9642;</span> </td> <tr id="entity-blacktriangle"><td> <code title="">blacktriangle;</code> </td> <td> U+025B4 </td> <td> <span class="glyph" title="">&#9652;</span> </td> <tr id="entity-blacktriangledown"><td> <code title="">blacktriangledown;</code> </td> <td> U+025BE </td> <td> <span class="glyph" title="">&#9662;</span> </td> <tr id="entity-blacktriangleleft"><td> <code title="">blacktriangleleft;</code> </td> <td> U+025C2 </td> <td> <span class="glyph" title="">&#9666;</span> </td> <tr id="entity-blacktriangleright"><td> <code title="">blacktriangleright;</code> </td> <td> U+025B8 </td> <td> <span class="glyph" title="">&#9656;</span> </td> <tr id="entity-blank"><td> <code title="">blank;</code> </td> <td> U+02423 </td> <td> <span class="glyph" title="">&#9251;</span> </td> <tr id="entity-blk12"><td> <code title="">blk12;</code> </td> <td> U+02592 </td> <td> <span class="glyph" title="">&#9618;</span> </td> <tr id="entity-blk14"><td> <code title="">blk14;</code> </td> <td> U+02591 </td> <td> <span class="glyph" title="">&#9617;</span> </td> <tr id="entity-blk34"><td> <code title="">blk34;</code> </td> <td> U+02593 </td> <td> <span class="glyph" title="">&#9619;</span> </td> <tr id="entity-block"><td> <code title="">block;</code> </td> <td> U+02588 </td> <td> <span class="glyph" title="">&#9608;</span> </td> <tr id="entity-bne"><td> <code title="">bne;</code> </td> <td> U+0003D U+020E5 </td> <td> <span class="glyph compound" title="">=&#8421;</span> </td> <tr id="entity-bnequiv"><td> <code title="">bnequiv;</code> </td> <td> U+02261 U+020E5 </td> <td> <span class="glyph compound" title="">&equiv;&#8421;</span> </td> <tr id="entity-bnot"><td> <code title="">bnot;</code> </td> <td> U+02310 </td> <td> <span class="glyph" title="">&#8976;</span> </td> <tr id="entity-bopf"><td> <code title="">bopf;</code> </td> <td> U+1D553 </td> <td> <span class="glyph" title="">&#120147;</span> </td> <tr id="entity-bot"><td> <code title="">bot;</code> </td> <td> U+022A5 </td> <td> <span class="glyph" title="">&perp;</span> </td> <tr id="entity-bottom"><td> <code title="">bottom;</code> </td> <td> U+022A5 </td> <td> <span class="glyph" title="">&perp;</span> </td> <tr id="entity-bowtie"><td> <code title="">bowtie;</code> </td> <td> U+022C8 </td> <td> <span class="glyph" title="">&#8904;</span> </td> <tr id="entity-boxDL"><td> <code title="">boxDL;</code> </td> <td> U+02557 </td> <td> <span class="glyph" title="">&#9559;</span> </td> <tr id="entity-boxDR"><td> <code title="">boxDR;</code> </td> <td> U+02554 </td> <td> <span class="glyph" title="">&#9556;</span> </td> <tr id="entity-boxDl"><td> <code title="">boxDl;</code> </td> <td> U+02556 </td> <td> <span class="glyph" title="">&#9558;</span> </td> <tr id="entity-boxDr"><td> <code title="">boxDr;</code> </td> <td> U+02553 </td> <td> <span class="glyph" title="">&#9555;</span> </td> <tr id="entity-boxH"><td> <code title="">boxH;</code> </td> <td> U+02550 </td> <td> <span class="glyph" title="">&#9552;</span> </td> <tr id="entity-boxHD"><td> <code title="">boxHD;</code> </td> <td> U+02566 </td> <td> <span class="glyph" title="">&#9574;</span> </td> <tr id="entity-boxHU"><td> <code title="">boxHU;</code> </td> <td> U+02569 </td> <td> <span class="glyph" title="">&#9577;</span> </td> <tr id="entity-boxHd"><td> <code title="">boxHd;</code> </td> <td> U+02564 </td> <td> <span class="glyph" title="">&#9572;</span> </td> <tr id="entity-boxHu"><td> <code title="">boxHu;</code> </td> <td> U+02567 </td> <td> <span class="glyph" title="">&#9575;</span> </td> <tr id="entity-boxUL"><td> <code title="">boxUL;</code> </td> <td> U+0255D </td> <td> <span class="glyph" title="">&#9565;</span> </td> <tr id="entity-boxUR"><td> <code title="">boxUR;</code> </td> <td> U+0255A </td> <td> <span class="glyph" title="">&#9562;</span> </td> <tr id="entity-boxUl"><td> <code title="">boxUl;</code> </td> <td> U+0255C </td> <td> <span class="glyph" title="">&#9564;</span> </td> <tr id="entity-boxUr"><td> <code title="">boxUr;</code> </td> <td> U+02559 </td> <td> <span class="glyph" title="">&#9561;</span> </td> <tr id="entity-boxV"><td> <code title="">boxV;</code> </td> <td> U+02551 </td> <td> <span class="glyph" title="">&#9553;</span> </td> <tr id="entity-boxVH"><td> <code title="">boxVH;</code> </td> <td> U+0256C </td> <td> <span class="glyph" title="">&#9580;</span> </td> <tr id="entity-boxVL"><td> <code title="">boxVL;</code> </td> <td> U+02563 </td> <td> <span class="glyph" title="">&#9571;</span> </td> <tr id="entity-boxVR"><td> <code title="">boxVR;</code> </td> <td> U+02560 </td> <td> <span class="glyph" title="">&#9568;</span> </td> <tr id="entity-boxVh"><td> <code title="">boxVh;</code> </td> <td> U+0256B </td> <td> <span class="glyph" title="">&#9579;</span> </td> <tr id="entity-boxVl"><td> <code title="">boxVl;</code> </td> <td> U+02562 </td> <td> <span class="glyph" title="">&#9570;</span> </td> <tr id="entity-boxVr"><td> <code title="">boxVr;</code> </td> <td> U+0255F </td> <td> <span class="glyph" title="">&#9567;</span> </td> <tr id="entity-boxbox"><td> <code title="">boxbox;</code> </td> <td> U+029C9 </td> <td> <span class="glyph" title="">&#10697;</span> </td> <tr id="entity-boxdL"><td> <code title="">boxdL;</code> </td> <td> U+02555 </td> <td> <span class="glyph" title="">&#9557;</span> </td> <tr id="entity-boxdR"><td> <code title="">boxdR;</code> </td> <td> U+02552 </td> <td> <span class="glyph" title="">&#9554;</span> </td> <tr id="entity-boxdl"><td> <code title="">boxdl;</code> </td> <td> U+02510 </td> <td> <span class="glyph" title="">&#9488;</span> </td> <tr id="entity-boxdr"><td> <code title="">boxdr;</code> </td> <td> U+0250C </td> <td> <span class="glyph" title="">&#9484;</span> </td> <tr id="entity-boxh"><td> <code title="">boxh;</code> </td> <td> U+02500 </td> <td> <span class="glyph" title="">&#9472;</span> </td> <tr id="entity-boxhD"><td> <code title="">boxhD;</code> </td> <td> U+02565 </td> <td> <span class="glyph" title="">&#9573;</span> </td> <tr id="entity-boxhU"><td> <code title="">boxhU;</code> </td> <td> U+02568 </td> <td> <span class="glyph" title="">&#9576;</span> </td> <tr id="entity-boxhd"><td> <code title="">boxhd;</code> </td> <td> U+0252C </td> <td> <span class="glyph" title="">&#9516;</span> </td> <tr id="entity-boxhu"><td> <code title="">boxhu;</code> </td> <td> U+02534 </td> <td> <span class="glyph" title="">&#9524;</span> </td> <tr id="entity-boxminus"><td> <code title="">boxminus;</code> </td> <td> U+0229F </td> <td> <span class="glyph" title="">&#8863;</span> </td> <tr id="entity-boxplus"><td> <code title="">boxplus;</code> </td> <td> U+0229E </td> <td> <span class="glyph" title="">&#8862;</span> </td> <tr id="entity-boxtimes"><td> <code title="">boxtimes;</code> </td> <td> U+022A0 </td> <td> <span class="glyph" title="">&#8864;</span> </td> <tr id="entity-boxuL"><td> <code title="">boxuL;</code> </td> <td> U+0255B </td> <td> <span class="glyph" title="">&#9563;</span> </td> <tr id="entity-boxuR"><td> <code title="">boxuR;</code> </td> <td> U+02558 </td> <td> <span class="glyph" title="">&#9560;</span> </td> <tr id="entity-boxul"><td> <code title="">boxul;</code> </td> <td> U+02518 </td> <td> <span class="glyph" title="">&#9496;</span> </td> <tr id="entity-boxur"><td> <code title="">boxur;</code> </td> <td> U+02514 </td> <td> <span class="glyph" title="">&#9492;</span> </td> <tr id="entity-boxv"><td> <code title="">boxv;</code> </td> <td> U+02502 </td> <td> <span class="glyph" title="">&#9474;</span> </td> <tr id="entity-boxvH"><td> <code title="">boxvH;</code> </td> <td> U+0256A </td> <td> <span class="glyph" title="">&#9578;</span> </td> <tr id="entity-boxvL"><td> <code title="">boxvL;</code> </td> <td> U+02561 </td> <td> <span class="glyph" title="">&#9569;</span> </td> <tr id="entity-boxvR"><td> <code title="">boxvR;</code> </td> <td> U+0255E </td> <td> <span class="glyph" title="">&#9566;</span> </td> <tr id="entity-boxvh"><td> <code title="">boxvh;</code> </td> <td> U+0253C </td> <td> <span class="glyph" title="">&#9532;</span> </td> <tr id="entity-boxvl"><td> <code title="">boxvl;</code> </td> <td> U+02524 </td> <td> <span class="glyph" title="">&#9508;</span> </td> <tr id="entity-boxvr"><td> <code title="">boxvr;</code> </td> <td> U+0251C </td> <td> <span class="glyph" title="">&#9500;</span> </td> <tr id="entity-bprime"><td> <code title="">bprime;</code> </td> <td> U+02035 </td> <td> <span class="glyph" title="">&#8245;</span> </td> <tr id="entity-breve"><td> <code title="">breve;</code> </td> <td> U+002D8 </td> <td> <span class="glyph" title="">&#728;</span> </td> <tr id="entity-brvbar"><td> <code title="">brvbar;</code> </td> <td> U+000A6 </td> <td> <span class="glyph" title="">&brvbar;</span> </td> <tr id="entity-bscr"><td> <code title="">bscr;</code> </td> <td> U+1D4B7 </td> <td> <span class="glyph" title="">&#119991;</span> </td> <tr id="entity-bsemi"><td> <code title="">bsemi;</code> </td> <td> U+0204F </td> <td> <span class="glyph" title="">&#8271;</span> </td> <tr id="entity-bsim"><td> <code title="">bsim;</code> </td> <td> U+0223D </td> <td> <span class="glyph" title="">&#8765;</span> </td> <tr id="entity-bsime"><td> <code title="">bsime;</code> </td> <td> U+022CD </td> <td> <span class="glyph" title="">&#8909;</span> </td> <tr id="entity-bsol"><td> <code title="">bsol;</code> </td> <td> U+0005C </td> <td> <span class="glyph" title="">\</span> </td> <tr id="entity-bsolb"><td> <code title="">bsolb;</code> </td> <td> U+029C5 </td> <td> <span class="glyph" title="">&#10693;</span> </td> <tr id="entity-bsolhsub"><td> <code title="">bsolhsub;</code> </td> <td> U+027C8 </td> <td> <span class="glyph" title="">&#10184;</span> </td> <tr id="entity-bull"><td> <code title="">bull;</code> </td> <td> U+02022 </td> <td> <span class="glyph" title="">&bull;</span> </td> <tr id="entity-bullet"><td> <code title="">bullet;</code> </td> <td> U+02022 </td> <td> <span class="glyph" title="">&bull;</span> </td> <tr id="entity-bump"><td> <code title="">bump;</code> </td> <td> U+0224E </td> <td> <span class="glyph" title="">&#8782;</span> </td> <tr id="entity-bumpE"><td> <code title="">bumpE;</code> </td> <td> U+02AAE </td> <td> <span class="glyph" title="">&#10926;</span> </td> <tr id="entity-bumpe"><td> <code title="">bumpe;</code> </td> <td> U+0224F </td> <td> <span class="glyph" title="">&#8783;</span> </td> <tr id="entity-bumpeq"><td> <code title="">bumpeq;</code> </td> <td> U+0224F </td> <td> <span class="glyph" title="">&#8783;</span> </td> <tr id="entity-cacute"><td> <code title="">cacute;</code> </td> <td> U+00107 </td> <td> <span class="glyph" title="">&#263;</span> </td> <tr id="entity-cap"><td> <code title="">cap;</code> </td> <td> U+02229 </td> <td> <span class="glyph" title="">&cap;</span> </td> <tr id="entity-capand"><td> <code title="">capand;</code> </td> <td> U+02A44 </td> <td> <span class="glyph" title="">&#10820;</span> </td> <tr id="entity-capbrcup"><td> <code title="">capbrcup;</code> </td> <td> U+02A49 </td> <td> <span class="glyph" title="">&#10825;</span> </td> <tr id="entity-capcap"><td> <code title="">capcap;</code> </td> <td> U+02A4B </td> <td> <span class="glyph" title="">&#10827;</span> </td> <tr id="entity-capcup"><td> <code title="">capcup;</code> </td> <td> U+02A47 </td> <td> <span class="glyph" title="">&#10823;</span> </td> <tr id="entity-capdot"><td> <code title="">capdot;</code> </td> <td> U+02A40 </td> <td> <span class="glyph" title="">&#10816;</span> </td> <tr id="entity-caps"><td> <code title="">caps;</code> </td> <td> U+02229 U+0FE00 </td> <td> <span class="glyph compound" title="">&cap;&#65024;</span> </td> <tr id="entity-caret"><td> <code title="">caret;</code> </td> <td> U+02041 </td> <td> <span class="glyph" title="">&#8257;</span> </td> <tr id="entity-caron"><td> <code title="">caron;</code> </td> <td> U+002C7 </td> <td> <span class="glyph" title="">&#711;</span> </td> <tr id="entity-ccaps"><td> <code title="">ccaps;</code> </td> <td> U+02A4D </td> <td> <span class="glyph" title="">&#10829;</span> </td> <tr id="entity-ccaron"><td> <code title="">ccaron;</code> </td> <td> U+0010D </td> <td> <span class="glyph" title="">&#269;</span> </td> <tr id="entity-ccedil"><td> <code title="">ccedil;</code> </td> <td> U+000E7 </td> <td> <span class="glyph" title="">&ccedil;</span> </td> <tr id="entity-ccirc"><td> <code title="">ccirc;</code> </td> <td> U+00109 </td> <td> <span class="glyph" title="">&#265;</span> </td> <tr id="entity-ccups"><td> <code title="">ccups;</code> </td> <td> U+02A4C </td> <td> <span class="glyph" title="">&#10828;</span> </td> <tr id="entity-ccupssm"><td> <code title="">ccupssm;</code> </td> <td> U+02A50 </td> <td> <span class="glyph" title="">&#10832;</span> </td> <tr id="entity-cdot"><td> <code title="">cdot;</code> </td> <td> U+0010B </td> <td> <span class="glyph" title="">&#267;</span> </td> <tr id="entity-cedil"><td> <code title="">cedil;</code> </td> <td> U+000B8 </td> <td> <span class="glyph" title="">&cedil;</span> </td> <tr id="entity-cemptyv"><td> <code title="">cemptyv;</code> </td> <td> U+029B2 </td> <td> <span class="glyph" title="">&#10674;</span> </td> <tr id="entity-cent"><td> <code title="">cent;</code> </td> <td> U+000A2 </td> <td> <span class="glyph" title="">&cent;</span> </td> <tr id="entity-centerdot"><td> <code title="">centerdot;</code> </td> <td> U+000B7 </td> <td> <span class="glyph" title="">&middot;</span> </td> <tr id="entity-cfr"><td> <code title="">cfr;</code> </td> <td> U+1D520 </td> <td> <span class="glyph" title="">&#120096;</span> </td> <tr id="entity-chcy"><td> <code title="">chcy;</code> </td> <td> U+00447 </td> <td> <span class="glyph" title="">&#1095;</span> </td> <tr id="entity-check"><td> <code title="">check;</code> </td> <td> U+02713 </td> <td> <span class="glyph" title="">&#10003;</span> </td> <tr id="entity-checkmark"><td> <code title="">checkmark;</code> </td> <td> U+02713 </td> <td> <span class="glyph" title="">&#10003;</span> </td> <tr id="entity-chi"><td> <code title="">chi;</code> </td> <td> U+003C7 </td> <td> <span class="glyph" title="">&chi;</span> </td> <tr id="entity-cir"><td> <code title="">cir;</code> </td> <td> U+025CB </td> <td> <span class="glyph" title="">&#9675;</span> </td> <tr id="entity-cirE"><td> <code title="">cirE;</code> </td> <td> U+029C3 </td> <td> <span class="glyph" title="">&#10691;</span> </td> <tr id="entity-circ"><td> <code title="">circ;</code> </td> <td> U+002C6 </td> <td> <span class="glyph" title="">&circ;</span> </td> <tr id="entity-circeq"><td> <code title="">circeq;</code> </td> <td> U+02257 </td> <td> <span class="glyph" title="">&#8791;</span> </td> <tr id="entity-circlearrowleft"><td> <code title="">circlearrowleft;</code> </td> <td> U+021BA </td> <td> <span class="glyph" title="">&#8634;</span> </td> <tr id="entity-circlearrowright"><td> <code title="">circlearrowright;</code> </td> <td> U+021BB </td> <td> <span class="glyph" title="">&#8635;</span> </td> <tr id="entity-circledR"><td> <code title="">circledR;</code> </td> <td> U+000AE </td> <td> <span class="glyph" title="">&reg;</span> </td> <tr id="entity-circledS"><td> <code title="">circledS;</code> </td> <td> U+024C8 </td> <td> <span class="glyph" title="">&#9416;</span> </td> <tr id="entity-circledast"><td> <code title="">circledast;</code> </td> <td> U+0229B </td> <td> <span class="glyph" title="">&#8859;</span> </td> <tr id="entity-circledcirc"><td> <code title="">circledcirc;</code> </td> <td> U+0229A </td> <td> <span class="glyph" title="">&#8858;</span> </td> <tr id="entity-circleddash"><td> <code title="">circleddash;</code> </td> <td> U+0229D </td> <td> <span class="glyph" title="">&#8861;</span> </td> <tr id="entity-cire"><td> <code title="">cire;</code> </td> <td> U+02257 </td> <td> <span class="glyph" title="">&#8791;</span> </td> <tr id="entity-cirfnint"><td> <code title="">cirfnint;</code> </td> <td> U+02A10 </td> <td> <span class="glyph" title="">&#10768;</span> </td> <tr id="entity-cirmid"><td> <code title="">cirmid;</code> </td> <td> U+02AEF </td> <td> <span class="glyph" title="">&#10991;</span> </td> <tr id="entity-cirscir"><td> <code title="">cirscir;</code> </td> <td> U+029C2 </td> <td> <span class="glyph" title="">&#10690;</span> </td> <tr id="entity-clubs"><td> <code title="">clubs;</code> </td> <td> U+02663 </td> <td> <span class="glyph" title="">&clubs;</span> </td> <tr id="entity-clubsuit"><td> <code title="">clubsuit;</code> </td> <td> U+02663 </td> <td> <span class="glyph" title="">&clubs;</span> </td> <tr id="entity-colon"><td> <code title="">colon;</code> </td> <td> U+0003A </td> <td> <span class="glyph" title="">:</span> </td> <tr id="entity-colone"><td> <code title="">colone;</code> </td> <td> U+02254 </td> <td> <span class="glyph" title="">&#8788;</span> </td> <tr id="entity-coloneq"><td> <code title="">coloneq;</code> </td> <td> U+02254 </td> <td> <span class="glyph" title="">&#8788;</span> </td> <tr id="entity-comma"><td> <code title="">comma;</code> </td> <td> U+0002C </td> <td> <span class="glyph" title="">,</span> </td> <tr id="entity-commat"><td> <code title="">commat;</code> </td> <td> U+00040 </td> <td> <span class="glyph" title="">@</span> </td> <tr id="entity-comp"><td> <code title="">comp;</code> </td> <td> U+02201 </td> <td> <span class="glyph" title="">&#8705;</span> </td> <tr id="entity-compfn"><td> <code title="">compfn;</code> </td> <td> U+02218 </td> <td> <span class="glyph" title="">&#8728;</span> </td> <tr id="entity-complement"><td> <code title="">complement;</code> </td> <td> U+02201 </td> <td> <span class="glyph" title="">&#8705;</span> </td> <tr id="entity-complexes"><td> <code title="">complexes;</code> </td> <td> U+02102 </td> <td> <span class="glyph" title="">&#8450;</span> </td> <tr id="entity-cong"><td> <code title="">cong;</code> </td> <td> U+02245 </td> <td> <span class="glyph" title="">&cong;</span> </td> <tr id="entity-congdot"><td> <code title="">congdot;</code> </td> <td> U+02A6D </td> <td> <span class="glyph" title="">&#10861;</span> </td> <tr id="entity-conint"><td> <code title="">conint;</code> </td> <td> U+0222E </td> <td> <span class="glyph" title="">&#8750;</span> </td> <tr id="entity-copf"><td> <code title="">copf;</code> </td> <td> U+1D554 </td> <td> <span class="glyph" title="">&#120148;</span> </td> <tr id="entity-coprod"><td> <code title="">coprod;</code> </td> <td> U+02210 </td> <td> <span class="glyph" title="">&#8720;</span> </td> <tr id="entity-copy"><td> <code title="">copy;</code> </td> <td> U+000A9 </td> <td> <span class="glyph" title="">&copy;</span> </td> <tr id="entity-copysr"><td> <code title="">copysr;</code> </td> <td> U+02117 </td> <td> <span class="glyph" title="">&#8471;</span> </td> <tr id="entity-crarr"><td> <code title="">crarr;</code> </td> <td> U+021B5 </td> <td> <span class="glyph" title="">&crarr;</span> </td> <tr id="entity-cross"><td> <code title="">cross;</code> </td> <td> U+02717 </td> <td> <span class="glyph" title="">&#10007;</span> </td> <tr id="entity-cscr"><td> <code title="">cscr;</code> </td> <td> U+1D4B8 </td> <td> <span class="glyph" title="">&#119992;</span> </td> <tr id="entity-csub"><td> <code title="">csub;</code> </td> <td> U+02ACF </td> <td> <span class="glyph" title="">&#10959;</span> </td> <tr id="entity-csube"><td> <code title="">csube;</code> </td> <td> U+02AD1 </td> <td> <span class="glyph" title="">&#10961;</span> </td> <tr id="entity-csup"><td> <code title="">csup;</code> </td> <td> U+02AD0 </td> <td> <span class="glyph" title="">&#10960;</span> </td> <tr id="entity-csupe"><td> <code title="">csupe;</code> </td> <td> U+02AD2 </td> <td> <span class="glyph" title="">&#10962;</span> </td> <tr id="entity-ctdot"><td> <code title="">ctdot;</code> </td> <td> U+022EF </td> <td> <span class="glyph" title="">&#8943;</span> </td> <tr id="entity-cudarrl"><td> <code title="">cudarrl;</code> </td> <td> U+02938 </td> <td> <span class="glyph" title="">&#10552;</span> </td> <tr id="entity-cudarrr"><td> <code title="">cudarrr;</code> </td> <td> U+02935 </td> <td> <span class="glyph" title="">&#10549;</span> </td> <tr id="entity-cuepr"><td> <code title="">cuepr;</code> </td> <td> U+022DE </td> <td> <span class="glyph" title="">&#8926;</span> </td> <tr id="entity-cuesc"><td> <code title="">cuesc;</code> </td> <td> U+022DF </td> <td> <span class="glyph" title="">&#8927;</span> </td> <tr id="entity-cularr"><td> <code title="">cularr;</code> </td> <td> U+021B6 </td> <td> <span class="glyph" title="">&#8630;</span> </td> <tr id="entity-cularrp"><td> <code title="">cularrp;</code> </td> <td> U+0293D </td> <td> <span class="glyph" title="">&#10557;</span> </td> <tr id="entity-cup"><td> <code title="">cup;</code> </td> <td> U+0222A </td> <td> <span class="glyph" title="">&cup;</span> </td> <tr id="entity-cupbrcap"><td> <code title="">cupbrcap;</code> </td> <td> U+02A48 </td> <td> <span class="glyph" title="">&#10824;</span> </td> <tr id="entity-cupcap"><td> <code title="">cupcap;</code> </td> <td> U+02A46 </td> <td> <span class="glyph" title="">&#10822;</span> </td> <tr id="entity-cupcup"><td> <code title="">cupcup;</code> </td> <td> U+02A4A </td> <td> <span class="glyph" title="">&#10826;</span> </td> <tr id="entity-cupdot"><td> <code title="">cupdot;</code> </td> <td> U+0228D </td> <td> <span class="glyph" title="">&#8845;</span> </td> <tr id="entity-cupor"><td> <code title="">cupor;</code> </td> <td> U+02A45 </td> <td> <span class="glyph" title="">&#10821;</span> </td> <tr id="entity-cups"><td> <code title="">cups;</code> </td> <td> U+0222A U+0FE00 </td> <td> <span class="glyph compound" title="">&cup;&#65024;</span> </td> <tr id="entity-curarr"><td> <code title="">curarr;</code> </td> <td> U+021B7 </td> <td> <span class="glyph" title="">&#8631;</span> </td> <tr id="entity-curarrm"><td> <code title="">curarrm;</code> </td> <td> U+0293C </td> <td> <span class="glyph" title="">&#10556;</span> </td> <tr id="entity-curlyeqprec"><td> <code title="">curlyeqprec;</code> </td> <td> U+022DE </td> <td> <span class="glyph" title="">&#8926;</span> </td> <tr id="entity-curlyeqsucc"><td> <code title="">curlyeqsucc;</code> </td> <td> U+022DF </td> <td> <span class="glyph" title="">&#8927;</span> </td> <tr id="entity-curlyvee"><td> <code title="">curlyvee;</code> </td> <td> U+022CE </td> <td> <span class="glyph" title="">&#8910;</span> </td> <tr id="entity-curlywedge"><td> <code title="">curlywedge;</code> </td> <td> U+022CF </td> <td> <span class="glyph" title="">&#8911;</span> </td> <tr id="entity-curren"><td> <code title="">curren;</code> </td> <td> U+000A4 </td> <td> <span class="glyph" title="">&curren;</span> </td> <tr id="entity-curvearrowleft"><td> <code title="">curvearrowleft;</code> </td> <td> U+021B6 </td> <td> <span class="glyph" title="">&#8630;</span> </td> <tr id="entity-curvearrowright"><td> <code title="">curvearrowright;</code> </td> <td> U+021B7 </td> <td> <span class="glyph" title="">&#8631;</span> </td> <tr id="entity-cuvee"><td> <code title="">cuvee;</code> </td> <td> U+022CE </td> <td> <span class="glyph" title="">&#8910;</span> </td> <tr id="entity-cuwed"><td> <code title="">cuwed;</code> </td> <td> U+022CF </td> <td> <span class="glyph" title="">&#8911;</span> </td> <tr id="entity-cwconint"><td> <code title="">cwconint;</code> </td> <td> U+02232 </td> <td> <span class="glyph" title="">&#8754;</span> </td> <tr id="entity-cwint"><td> <code title="">cwint;</code> </td> <td> U+02231 </td> <td> <span class="glyph" title="">&#8753;</span> </td> <tr id="entity-cylcty"><td> <code title="">cylcty;</code> </td> <td> U+0232D </td> <td> <span class="glyph" title="">&#9005;</span> </td> <tr id="entity-dArr"><td> <code title="">dArr;</code> </td> <td> U+021D3 </td> <td> <span class="glyph" title="">&dArr;</span> </td> <tr id="entity-dHar"><td> <code title="">dHar;</code> </td> <td> U+02965 </td> <td> <span class="glyph" title="">&#10597;</span> </td> <tr id="entity-dagger"><td> <code title="">dagger;</code> </td> <td> U+02020 </td> <td> <span class="glyph" title="">&dagger;</span> </td> <tr id="entity-daleth"><td> <code title="">daleth;</code> </td> <td> U+02138 </td> <td> <span class="glyph" title="">&#8504;</span> </td> <tr id="entity-darr"><td> <code title="">darr;</code> </td> <td> U+02193 </td> <td> <span class="glyph" title="">&darr;</span> </td> <tr id="entity-dash"><td> <code title="">dash;</code> </td> <td> U+02010 </td> <td> <span class="glyph" title="">&#8208;</span> </td> <tr id="entity-dashv"><td> <code title="">dashv;</code> </td> <td> U+022A3 </td> <td> <span class="glyph" title="">&#8867;</span> </td> <tr id="entity-dbkarow"><td> <code title="">dbkarow;</code> </td> <td> U+0290F </td> <td> <span class="glyph" title="">&#10511;</span> </td> <tr id="entity-dblac"><td> <code title="">dblac;</code> </td> <td> U+002DD </td> <td> <span class="glyph" title="">&#733;</span> </td> <tr id="entity-dcaron"><td> <code title="">dcaron;</code> </td> <td> U+0010F </td> <td> <span class="glyph" title="">&#271;</span> </td> <tr id="entity-dcy"><td> <code title="">dcy;</code> </td> <td> U+00434 </td> <td> <span class="glyph" title="">&#1076;</span> </td> <tr id="entity-dd"><td> <code title="">dd;</code> </td> <td> U+02146 </td> <td> <span class="glyph" title="">&#8518;</span> </td> <tr id="entity-ddagger"><td> <code title="">ddagger;</code> </td> <td> U+02021 </td> <td> <span class="glyph" title="">&Dagger;</span> </td> <tr id="entity-ddarr"><td> <code title="">ddarr;</code> </td> <td> U+021CA </td> <td> <span class="glyph" title="">&#8650;</span> </td> <tr id="entity-ddotseq"><td> <code title="">ddotseq;</code> </td> <td> U+02A77 </td> <td> <span class="glyph" title="">&#10871;</span> </td> <tr id="entity-deg"><td> <code title="">deg;</code> </td> <td> U+000B0 </td> <td> <span class="glyph" title="">&deg;</span> </td> <tr id="entity-delta"><td> <code title="">delta;</code> </td> <td> U+003B4 </td> <td> <span class="glyph" title="">&delta;</span> </td> <tr id="entity-demptyv"><td> <code title="">demptyv;</code> </td> <td> U+029B1 </td> <td> <span class="glyph" title="">&#10673;</span> </td> <tr id="entity-dfisht"><td> <code title="">dfisht;</code> </td> <td> U+0297F </td> <td> <span class="glyph" title="">&#10623;</span> </td> <tr id="entity-dfr"><td> <code title="">dfr;</code> </td> <td> U+1D521 </td> <td> <span class="glyph" title="">&#120097;</span> </td> <tr id="entity-dharl"><td> <code title="">dharl;</code> </td> <td> U+021C3 </td> <td> <span class="glyph" title="">&#8643;</span> </td> <tr id="entity-dharr"><td> <code title="">dharr;</code> </td> <td> U+021C2 </td> <td> <span class="glyph" title="">&#8642;</span> </td> <tr id="entity-diam"><td> <code title="">diam;</code> </td> <td> U+022C4 </td> <td> <span class="glyph" title="">&#8900;</span> </td> <tr id="entity-diamond"><td> <code title="">diamond;</code> </td> <td> U+022C4 </td> <td> <span class="glyph" title="">&#8900;</span> </td> <tr id="entity-diamondsuit"><td> <code title="">diamondsuit;</code> </td> <td> U+02666 </td> <td> <span class="glyph" title="">&diams;</span> </td> <tr id="entity-diams"><td> <code title="">diams;</code> </td> <td> U+02666 </td> <td> <span class="glyph" title="">&diams;</span> </td> <tr id="entity-die"><td> <code title="">die;</code> </td> <td> U+000A8 </td> <td> <span class="glyph" title="">&uml;</span> </td> <tr id="entity-digamma"><td> <code title="">digamma;</code> </td> <td> U+003DD </td> <td> <span class="glyph" title="">&#989;</span> </td> <tr id="entity-disin"><td> <code title="">disin;</code> </td> <td> U+022F2 </td> <td> <span class="glyph" title="">&#8946;</span> </td> <tr id="entity-div"><td> <code title="">div;</code> </td> <td> U+000F7 </td> <td> <span class="glyph" title="">&divide;</span> </td> <tr id="entity-divide"><td> <code title="">divide;</code> </td> <td> U+000F7 </td> <td> <span class="glyph" title="">&divide;</span> </td> <tr id="entity-divideontimes"><td> <code title="">divideontimes;</code> </td> <td> U+022C7 </td> <td> <span class="glyph" title="">&#8903;</span> </td> <tr id="entity-divonx"><td> <code title="">divonx;</code> </td> <td> U+022C7 </td> <td> <span class="glyph" title="">&#8903;</span> </td> <tr id="entity-djcy"><td> <code title="">djcy;</code> </td> <td> U+00452 </td> <td> <span class="glyph" title="">&#1106;</span> </td> <tr id="entity-dlcorn"><td> <code title="">dlcorn;</code> </td> <td> U+0231E </td> <td> <span class="glyph" title="">&#8990;</span> </td> <tr id="entity-dlcrop"><td> <code title="">dlcrop;</code> </td> <td> U+0230D </td> <td> <span class="glyph" title="">&#8973;</span> </td> <tr id="entity-dollar"><td> <code title="">dollar;</code> </td> <td> U+00024 </td> <td> <span class="glyph" title="">$</span> </td> <tr id="entity-dopf"><td> <code title="">dopf;</code> </td> <td> U+1D555 </td> <td> <span class="glyph" title="">&#120149;</span> </td> <tr id="entity-dot"><td> <code title="">dot;</code> </td> <td> U+002D9 </td> <td> <span class="glyph" title="">&#729;</span> </td> <tr id="entity-doteq"><td> <code title="">doteq;</code> </td> <td> U+02250 </td> <td> <span class="glyph" title="">&#8784;</span> </td> <tr id="entity-doteqdot"><td> <code title="">doteqdot;</code> </td> <td> U+02251 </td> <td> <span class="glyph" title="">&#8785;</span> </td> <tr id="entity-dotminus"><td> <code title="">dotminus;</code> </td> <td> U+02238 </td> <td> <span class="glyph" title="">&#8760;</span> </td> <tr id="entity-dotplus"><td> <code title="">dotplus;</code> </td> <td> U+02214 </td> <td> <span class="glyph" title="">&#8724;</span> </td> <tr id="entity-dotsquare"><td> <code title="">dotsquare;</code> </td> <td> U+022A1 </td> <td> <span class="glyph" title="">&#8865;</span> </td> <tr id="entity-doublebarwedge"><td> <code title="">doublebarwedge;</code> </td> <td> U+02306 </td> <td> <span class="glyph" title="">&#8966;</span> </td> <tr id="entity-downarrow"><td> <code title="">downarrow;</code> </td> <td> U+02193 </td> <td> <span class="glyph" title="">&darr;</span> </td> <tr id="entity-downdownarrows"><td> <code title="">downdownarrows;</code> </td> <td> U+021CA </td> <td> <span class="glyph" title="">&#8650;</span> </td> <tr id="entity-downharpoonleft"><td> <code title="">downharpoonleft;</code> </td> <td> U+021C3 </td> <td> <span class="glyph" title="">&#8643;</span> </td> <tr id="entity-downharpoonright"><td> <code title="">downharpoonright;</code> </td> <td> U+021C2 </td> <td> <span class="glyph" title="">&#8642;</span> </td> <tr id="entity-drbkarow"><td> <code title="">drbkarow;</code> </td> <td> U+02910 </td> <td> <span class="glyph" title="">&#10512;</span> </td> <tr id="entity-drcorn"><td> <code title="">drcorn;</code> </td> <td> U+0231F </td> <td> <span class="glyph" title="">&#8991;</span> </td> <tr id="entity-drcrop"><td> <code title="">drcrop;</code> </td> <td> U+0230C </td> <td> <span class="glyph" title="">&#8972;</span> </td> <tr id="entity-dscr"><td> <code title="">dscr;</code> </td> <td> U+1D4B9 </td> <td> <span class="glyph" title="">&#119993;</span> </td> <tr id="entity-dscy"><td> <code title="">dscy;</code> </td> <td> U+00455 </td> <td> <span class="glyph" title="">&#1109;</span> </td> <tr id="entity-dsol"><td> <code title="">dsol;</code> </td> <td> U+029F6 </td> <td> <span class="glyph" title="">&#10742;</span> </td> <tr id="entity-dstrok"><td> <code title="">dstrok;</code> </td> <td> U+00111 </td> <td> <span class="glyph" title="">&#273;</span> </td> <tr id="entity-dtdot"><td> <code title="">dtdot;</code> </td> <td> U+022F1 </td> <td> <span class="glyph" title="">&#8945;</span> </td> <tr id="entity-dtri"><td> <code title="">dtri;</code> </td> <td> U+025BF </td> <td> <span class="glyph" title="">&#9663;</span> </td> <tr id="entity-dtrif"><td> <code title="">dtrif;</code> </td> <td> U+025BE </td> <td> <span class="glyph" title="">&#9662;</span> </td> <tr id="entity-duarr"><td> <code title="">duarr;</code> </td> <td> U+021F5 </td> <td> <span class="glyph" title="">&#8693;</span> </td> <tr id="entity-duhar"><td> <code title="">duhar;</code> </td> <td> U+0296F </td> <td> <span class="glyph" title="">&#10607;</span> </td> <tr id="entity-dwangle"><td> <code title="">dwangle;</code> </td> <td> U+029A6 </td> <td> <span class="glyph" title="">&#10662;</span> </td> <tr id="entity-dzcy"><td> <code title="">dzcy;</code> </td> <td> U+0045F </td> <td> <span class="glyph" title="">&#1119;</span> </td> <tr id="entity-dzigrarr"><td> <code title="">dzigrarr;</code> </td> <td> U+027FF </td> <td> <span class="glyph" title="">&#10239;</span> </td> <tr id="entity-eDDot"><td> <code title="">eDDot;</code> </td> <td> U+02A77 </td> <td> <span class="glyph" title="">&#10871;</span> </td> <tr id="entity-eDot"><td> <code title="">eDot;</code> </td> <td> U+02251 </td> <td> <span class="glyph" title="">&#8785;</span> </td> <tr id="entity-eacute"><td> <code title="">eacute;</code> </td> <td> U+000E9 </td> <td> <span class="glyph" title="">&eacute;</span> </td> <tr id="entity-easter"><td> <code title="">easter;</code> </td> <td> U+02A6E </td> <td> <span class="glyph" title="">&#10862;</span> </td> <tr id="entity-ecaron"><td> <code title="">ecaron;</code> </td> <td> U+0011B </td> <td> <span class="glyph" title="">&#283;</span> </td> <tr id="entity-ecir"><td> <code title="">ecir;</code> </td> <td> U+02256 </td> <td> <span class="glyph" title="">&#8790;</span> </td> <tr id="entity-ecirc"><td> <code title="">ecirc;</code> </td> <td> U+000EA </td> <td> <span class="glyph" title="">&ecirc;</span> </td> <tr id="entity-ecolon"><td> <code title="">ecolon;</code> </td> <td> U+02255 </td> <td> <span class="glyph" title="">&#8789;</span> </td> <tr id="entity-ecy"><td> <code title="">ecy;</code> </td> <td> U+0044D </td> <td> <span class="glyph" title="">&#1101;</span> </td> <tr id="entity-edot"><td> <code title="">edot;</code> </td> <td> U+00117 </td> <td> <span class="glyph" title="">&#279;</span> </td> <tr id="entity-ee"><td> <code title="">ee;</code> </td> <td> U+02147 </td> <td> <span class="glyph" title="">&#8519;</span> </td> <tr id="entity-efDot"><td> <code title="">efDot;</code> </td> <td> U+02252 </td> <td> <span class="glyph" title="">&#8786;</span> </td> <tr id="entity-efr"><td> <code title="">efr;</code> </td> <td> U+1D522 </td> <td> <span class="glyph" title="">&#120098;</span> </td> <tr id="entity-eg"><td> <code title="">eg;</code> </td> <td> U+02A9A </td> <td> <span class="glyph" title="">&#10906;</span> </td> <tr id="entity-egrave"><td> <code title="">egrave;</code> </td> <td> U+000E8 </td> <td> <span class="glyph" title="">&egrave;</span> </td> <tr id="entity-egs"><td> <code title="">egs;</code> </td> <td> U+02A96 </td> <td> <span class="glyph" title="">&#10902;</span> </td> <tr id="entity-egsdot"><td> <code title="">egsdot;</code> </td> <td> U+02A98 </td> <td> <span class="glyph" title="">&#10904;</span> </td> <tr id="entity-el"><td> <code title="">el;</code> </td> <td> U+02A99 </td> <td> <span class="glyph" title="">&#10905;</span> </td> <tr id="entity-elinters"><td> <code title="">elinters;</code> </td> <td> U+023E7 </td> <td> <span class="glyph" title="">&#9191;</span> </td> <tr id="entity-ell"><td> <code title="">ell;</code> </td> <td> U+02113 </td> <td> <span class="glyph" title="">&#8467;</span> </td> <tr id="entity-els"><td> <code title="">els;</code> </td> <td> U+02A95 </td> <td> <span class="glyph" title="">&#10901;</span> </td> <tr id="entity-elsdot"><td> <code title="">elsdot;</code> </td> <td> U+02A97 </td> <td> <span class="glyph" title="">&#10903;</span> </td> <tr id="entity-emacr"><td> <code title="">emacr;</code> </td> <td> U+00113 </td> <td> <span class="glyph" title="">&#275;</span> </td> <tr id="entity-empty"><td> <code title="">empty;</code> </td> <td> U+02205 </td> <td> <span class="glyph" title="">&empty;</span> </td> <tr id="entity-emptyset"><td> <code title="">emptyset;</code> </td> <td> U+02205 </td> <td> <span class="glyph" title="">&empty;</span> </td> <tr id="entity-emptyv"><td> <code title="">emptyv;</code> </td> <td> U+02205 </td> <td> <span class="glyph" title="">&empty;</span> </td> <tr id="entity-emsp"><td> <code title="">emsp;</code> </td> <td> U+02003 </td> <td> <span class="glyph" title="">&emsp;</span> </td> <tr id="entity-emsp13"><td> <code title="">emsp13;</code> </td> <td> U+02004 </td> <td> <span class="glyph" title="">&#8196;</span> </td> <tr id="entity-emsp14"><td> <code title="">emsp14;</code> </td> <td> U+02005 </td> <td> <span class="glyph" title="">&#8197;</span> </td> <tr id="entity-eng"><td> <code title="">eng;</code> </td> <td> U+0014B </td> <td> <span class="glyph" title="">&#331;</span> </td> <tr id="entity-ensp"><td> <code title="">ensp;</code> </td> <td> U+02002 </td> <td> <span class="glyph" title="">&ensp;</span> </td> <tr id="entity-eogon"><td> <code title="">eogon;</code> </td> <td> U+00119 </td> <td> <span class="glyph" title="">&#281;</span> </td> <tr id="entity-eopf"><td> <code title="">eopf;</code> </td> <td> U+1D556 </td> <td> <span class="glyph" title="">&#120150;</span> </td> <tr id="entity-epar"><td> <code title="">epar;</code> </td> <td> U+022D5 </td> <td> <span class="glyph" title="">&#8917;</span> </td> <tr id="entity-eparsl"><td> <code title="">eparsl;</code> </td> <td> U+029E3 </td> <td> <span class="glyph" title="">&#10723;</span> </td> <tr id="entity-eplus"><td> <code title="">eplus;</code> </td> <td> U+02A71 </td> <td> <span class="glyph" title="">&#10865;</span> </td> <tr id="entity-epsi"><td> <code title="">epsi;</code> </td> <td> U+003B5 </td> <td> <span class="glyph" title="">&epsilon;</span> </td> <tr id="entity-epsilon"><td> <code title="">epsilon;</code> </td> <td> U+003B5 </td> <td> <span class="glyph" title="">&epsilon;</span> </td> <tr id="entity-epsiv"><td> <code title="">epsiv;</code> </td> <td> U+003F5 </td> <td> <span class="glyph" title="">&#1013;</span> </td> <tr id="entity-eqcirc"><td> <code title="">eqcirc;</code> </td> <td> U+02256 </td> <td> <span class="glyph" title="">&#8790;</span> </td> <tr id="entity-eqcolon"><td> <code title="">eqcolon;</code> </td> <td> U+02255 </td> <td> <span class="glyph" title="">&#8789;</span> </td> <tr id="entity-eqsim"><td> <code title="">eqsim;</code> </td> <td> U+02242 </td> <td> <span class="glyph" title="">&#8770;</span> </td> <tr id="entity-eqslantgtr"><td> <code title="">eqslantgtr;</code> </td> <td> U+02A96 </td> <td> <span class="glyph" title="">&#10902;</span> </td> <tr id="entity-eqslantless"><td> <code title="">eqslantless;</code> </td> <td> U+02A95 </td> <td> <span class="glyph" title="">&#10901;</span> </td> <tr id="entity-equals"><td> <code title="">equals;</code> </td> <td> U+0003D </td> <td> <span class="glyph" title="">=</span> </td> <tr id="entity-equest"><td> <code title="">equest;</code> </td> <td> U+0225F </td> <td> <span class="glyph" title="">&#8799;</span> </td> <tr id="entity-equiv"><td> <code title="">equiv;</code> </td> <td> U+02261 </td> <td> <span class="glyph" title="">&equiv;</span> </td> <tr id="entity-equivDD"><td> <code title="">equivDD;</code> </td> <td> U+02A78 </td> <td> <span class="glyph" title="">&#10872;</span> </td> <tr id="entity-eqvparsl"><td> <code title="">eqvparsl;</code> </td> <td> U+029E5 </td> <td> <span class="glyph" title="">&#10725;</span> </td> <tr id="entity-erDot"><td> <code title="">erDot;</code> </td> <td> U+02253 </td> <td> <span class="glyph" title="">&#8787;</span> </td> <tr id="entity-erarr"><td> <code title="">erarr;</code> </td> <td> U+02971 </td> <td> <span class="glyph" title="">&#10609;</span> </td> <tr id="entity-escr"><td> <code title="">escr;</code> </td> <td> U+0212F </td> <td> <span class="glyph" title="">&#8495;</span> </td> <tr id="entity-esdot"><td> <code title="">esdot;</code> </td> <td> U+02250 </td> <td> <span class="glyph" title="">&#8784;</span> </td> <tr id="entity-esim"><td> <code title="">esim;</code> </td> <td> U+02242 </td> <td> <span class="glyph" title="">&#8770;</span> </td> <tr id="entity-eta"><td> <code title="">eta;</code> </td> <td> U+003B7 </td> <td> <span class="glyph" title="">&eta;</span> </td> <tr id="entity-eth"><td> <code title="">eth;</code> </td> <td> U+000F0 </td> <td> <span class="glyph" title="">&eth;</span> </td> <tr id="entity-euml"><td> <code title="">euml;</code> </td> <td> U+000EB </td> <td> <span class="glyph" title="">&euml;</span> </td> <tr id="entity-euro"><td> <code title="">euro;</code> </td> <td> U+020AC </td> <td> <span class="glyph" title="">&euro;</span> </td> <tr id="entity-excl"><td> <code title="">excl;</code> </td> <td> U+00021 </td> <td> <span class="glyph" title="">!</span> </td> <tr id="entity-exist"><td> <code title="">exist;</code> </td> <td> U+02203 </td> <td> <span class="glyph" title="">&exist;</span> </td> <tr id="entity-expectation"><td> <code title="">expectation;</code> </td> <td> U+02130 </td> <td> <span class="glyph" title="">&#8496;</span> </td> <tr id="entity-exponentiale"><td> <code title="">exponentiale;</code> </td> <td> U+02147 </td> <td> <span class="glyph" title="">&#8519;</span> </td> <tr id="entity-fallingdotseq"><td> <code title="">fallingdotseq;</code> </td> <td> U+02252 </td> <td> <span class="glyph" title="">&#8786;</span> </td> <tr id="entity-fcy"><td> <code title="">fcy;</code> </td> <td> U+00444 </td> <td> <span class="glyph" title="">&#1092;</span> </td> <tr id="entity-female"><td> <code title="">female;</code> </td> <td> U+02640 </td> <td> <span class="glyph" title="">&#9792;</span> </td> <tr id="entity-ffilig"><td> <code title="">ffilig;</code> </td> <td> U+0FB03 </td> <td> <span class="glyph" title="">&#64259;</span> </td> <tr id="entity-fflig"><td> <code title="">fflig;</code> </td> <td> U+0FB00 </td> <td> <span class="glyph" title="">&#64256;</span> </td> <tr id="entity-ffllig"><td> <code title="">ffllig;</code> </td> <td> U+0FB04 </td> <td> <span class="glyph" title="">&#64260;</span> </td> <tr id="entity-ffr"><td> <code title="">ffr;</code> </td> <td> U+1D523 </td> <td> <span class="glyph" title="">&#120099;</span> </td> <tr id="entity-filig"><td> <code title="">filig;</code> </td> <td> U+0FB01 </td> <td> <span class="glyph" title="">&#64257;</span> </td> <tr id="entity-fjlig"><td> <code title="">fjlig;</code> </td> <td> U+00066 U+0006A </td> <td> <span class="glyph compound" title="">fj</span> </td> <tr id="entity-flat"><td> <code title="">flat;</code> </td> <td> U+0266D </td> <td> <span class="glyph" title="">&#9837;</span> </td> <tr id="entity-fllig"><td> <code title="">fllig;</code> </td> <td> U+0FB02 </td> <td> <span class="glyph" title="">&#64258;</span> </td> <tr id="entity-fltns"><td> <code title="">fltns;</code> </td> <td> U+025B1 </td> <td> <span class="glyph" title="">&#9649;</span> </td> <tr id="entity-fnof"><td> <code title="">fnof;</code> </td> <td> U+00192 </td> <td> <span class="glyph" title="">&fnof;</span> </td> <tr id="entity-fopf"><td> <code title="">fopf;</code> </td> <td> U+1D557 </td> <td> <span class="glyph" title="">&#120151;</span> </td> <tr id="entity-forall"><td> <code title="">forall;</code> </td> <td> U+02200 </td> <td> <span class="glyph" title="">&forall;</span> </td> <tr id="entity-fork"><td> <code title="">fork;</code> </td> <td> U+022D4 </td> <td> <span class="glyph" title="">&#8916;</span> </td> <tr id="entity-forkv"><td> <code title="">forkv;</code> </td> <td> U+02AD9 </td> <td> <span class="glyph" title="">&#10969;</span> </td> <tr id="entity-fpartint"><td> <code title="">fpartint;</code> </td> <td> U+02A0D </td> <td> <span class="glyph" title="">&#10765;</span> </td> <tr id="entity-frac12"><td> <code title="">frac12;</code> </td> <td> U+000BD </td> <td> <span class="glyph" title="">&frac12;</span> </td> <tr id="entity-frac13"><td> <code title="">frac13;</code> </td> <td> U+02153 </td> <td> <span class="glyph" title="">&#8531;</span> </td> <tr id="entity-frac14"><td> <code title="">frac14;</code> </td> <td> U+000BC </td> <td> <span class="glyph" title="">&frac14;</span> </td> <tr id="entity-frac15"><td> <code title="">frac15;</code> </td> <td> U+02155 </td> <td> <span class="glyph" title="">&#8533;</span> </td> <tr id="entity-frac16"><td> <code title="">frac16;</code> </td> <td> U+02159 </td> <td> <span class="glyph" title="">&#8537;</span> </td> <tr id="entity-frac18"><td> <code title="">frac18;</code> </td> <td> U+0215B </td> <td> <span class="glyph" title="">&#8539;</span> </td> <tr id="entity-frac23"><td> <code title="">frac23;</code> </td> <td> U+02154 </td> <td> <span class="glyph" title="">&#8532;</span> </td> <tr id="entity-frac25"><td> <code title="">frac25;</code> </td> <td> U+02156 </td> <td> <span class="glyph" title="">&#8534;</span> </td> <tr id="entity-frac34"><td> <code title="">frac34;</code> </td> <td> U+000BE </td> <td> <span class="glyph" title="">&frac34;</span> </td> <tr id="entity-frac35"><td> <code title="">frac35;</code> </td> <td> U+02157 </td> <td> <span class="glyph" title="">&#8535;</span> </td> <tr id="entity-frac38"><td> <code title="">frac38;</code> </td> <td> U+0215C </td> <td> <span class="glyph" title="">&#8540;</span> </td> <tr id="entity-frac45"><td> <code title="">frac45;</code> </td> <td> U+02158 </td> <td> <span class="glyph" title="">&#8536;</span> </td> <tr id="entity-frac56"><td> <code title="">frac56;</code> </td> <td> U+0215A </td> <td> <span class="glyph" title="">&#8538;</span> </td> <tr id="entity-frac58"><td> <code title="">frac58;</code> </td> <td> U+0215D </td> <td> <span class="glyph" title="">&#8541;</span> </td> <tr id="entity-frac78"><td> <code title="">frac78;</code> </td> <td> U+0215E </td> <td> <span class="glyph" title="">&#8542;</span> </td> <tr id="entity-frasl"><td> <code title="">frasl;</code> </td> <td> U+02044 </td> <td> <span class="glyph" title="">&frasl;</span> </td> <tr id="entity-frown"><td> <code title="">frown;</code> </td> <td> U+02322 </td> <td> <span class="glyph" title="">&#8994;</span> </td> <tr id="entity-fscr"><td> <code title="">fscr;</code> </td> <td> U+1D4BB </td> <td> <span class="glyph" title="">&#119995;</span> </td> <tr id="entity-gE"><td> <code title="">gE;</code> </td> <td> U+02267 </td> <td> <span class="glyph" title="">&#8807;</span> </td> <tr id="entity-gEl"><td> <code title="">gEl;</code> </td> <td> U+02A8C </td> <td> <span class="glyph" title="">&#10892;</span> </td> <tr id="entity-gacute"><td> <code title="">gacute;</code> </td> <td> U+001F5 </td> <td> <span class="glyph" title="">&#501;</span> </td> <tr id="entity-gamma"><td> <code title="">gamma;</code> </td> <td> U+003B3 </td> <td> <span class="glyph" title="">&gamma;</span> </td> <tr id="entity-gammad"><td> <code title="">gammad;</code> </td> <td> U+003DD </td> <td> <span class="glyph" title="">&#989;</span> </td> <tr id="entity-gap"><td> <code title="">gap;</code> </td> <td> U+02A86 </td> <td> <span class="glyph" title="">&#10886;</span> </td> <tr id="entity-gbreve"><td> <code title="">gbreve;</code> </td> <td> U+0011F </td> <td> <span class="glyph" title="">&#287;</span> </td> <tr id="entity-gcirc"><td> <code title="">gcirc;</code> </td> <td> U+0011D </td> <td> <span class="glyph" title="">&#285;</span> </td> <tr id="entity-gcy"><td> <code title="">gcy;</code> </td> <td> U+00433 </td> <td> <span class="glyph" title="">&#1075;</span> </td> <tr id="entity-gdot"><td> <code title="">gdot;</code> </td> <td> U+00121 </td> <td> <span class="glyph" title="">&#289;</span> </td> <tr id="entity-ge"><td> <code title="">ge;</code> </td> <td> U+02265 </td> <td> <span class="glyph" title="">&ge;</span> </td> <tr id="entity-gel"><td> <code title="">gel;</code> </td> <td> U+022DB </td> <td> <span class="glyph" title="">&#8923;</span> </td> <tr id="entity-geq"><td> <code title="">geq;</code> </td> <td> U+02265 </td> <td> <span class="glyph" title="">&ge;</span> </td> <tr id="entity-geqq"><td> <code title="">geqq;</code> </td> <td> U+02267 </td> <td> <span class="glyph" title="">&#8807;</span> </td> <tr id="entity-geqslant"><td> <code title="">geqslant;</code> </td> <td> U+02A7E </td> <td> <span class="glyph" title="">&#10878;</span> </td> <tr id="entity-ges"><td> <code title="">ges;</code> </td> <td> U+02A7E </td> <td> <span class="glyph" title="">&#10878;</span> </td> <tr id="entity-gescc"><td> <code title="">gescc;</code> </td> <td> U+02AA9 </td> <td> <span class="glyph" title="">&#10921;</span> </td> <tr id="entity-gesdot"><td> <code title="">gesdot;</code> </td> <td> U+02A80 </td> <td> <span class="glyph" title="">&#10880;</span> </td> <tr id="entity-gesdoto"><td> <code title="">gesdoto;</code> </td> <td> U+02A82 </td> <td> <span class="glyph" title="">&#10882;</span> </td> <tr id="entity-gesdotol"><td> <code title="">gesdotol;</code> </td> <td> U+02A84 </td> <td> <span class="glyph" title="">&#10884;</span> </td> <tr id="entity-gesl"><td> <code title="">gesl;</code> </td> <td> U+022DB U+0FE00 </td> <td> <span class="glyph compound" title="">&#8923;&#65024;</span> </td> <tr id="entity-gesles"><td> <code title="">gesles;</code> </td> <td> U+02A94 </td> <td> <span class="glyph" title="">&#10900;</span> </td> <tr id="entity-gfr"><td> <code title="">gfr;</code> </td> <td> U+1D524 </td> <td> <span class="glyph" title="">&#120100;</span> </td> <tr id="entity-gg"><td> <code title="">gg;</code> </td> <td> U+0226B </td> <td> <span class="glyph" title="">&#8811;</span> </td> <tr id="entity-ggg"><td> <code title="">ggg;</code> </td> <td> U+022D9 </td> <td> <span class="glyph" title="">&#8921;</span> </td> <tr id="entity-gimel"><td> <code title="">gimel;</code> </td> <td> U+02137 </td> <td> <span class="glyph" title="">&#8503;</span> </td> <tr id="entity-gjcy"><td> <code title="">gjcy;</code> </td> <td> U+00453 </td> <td> <span class="glyph" title="">&#1107;</span> </td> <tr id="entity-gl"><td> <code title="">gl;</code> </td> <td> U+02277 </td> <td> <span class="glyph" title="">&#8823;</span> </td> <tr id="entity-glE"><td> <code title="">glE;</code> </td> <td> U+02A92 </td> <td> <span class="glyph" title="">&#10898;</span> </td> <tr id="entity-gla"><td> <code title="">gla;</code> </td> <td> U+02AA5 </td> <td> <span class="glyph" title="">&#10917;</span> </td> <tr id="entity-glj"><td> <code title="">glj;</code> </td> <td> U+02AA4 </td> <td> <span class="glyph" title="">&#10916;</span> </td> <tr id="entity-gnE"><td> <code title="">gnE;</code> </td> <td> U+02269 </td> <td> <span class="glyph" title="">&#8809;</span> </td> <tr id="entity-gnap"><td> <code title="">gnap;</code> </td> <td> U+02A8A </td> <td> <span class="glyph" title="">&#10890;</span> </td> <tr id="entity-gnapprox"><td> <code title="">gnapprox;</code> </td> <td> U+02A8A </td> <td> <span class="glyph" title="">&#10890;</span> </td> <tr id="entity-gne"><td> <code title="">gne;</code> </td> <td> U+02A88 </td> <td> <span class="glyph" title="">&#10888;</span> </td> <tr id="entity-gneq"><td> <code title="">gneq;</code> </td> <td> U+02A88 </td> <td> <span class="glyph" title="">&#10888;</span> </td> <tr id="entity-gneqq"><td> <code title="">gneqq;</code> </td> <td> U+02269 </td> <td> <span class="glyph" title="">&#8809;</span> </td> <tr id="entity-gnsim"><td> <code title="">gnsim;</code> </td> <td> U+022E7 </td> <td> <span class="glyph" title="">&#8935;</span> </td> <tr id="entity-gopf"><td> <code title="">gopf;</code> </td> <td> U+1D558 </td> <td> <span class="glyph" title="">&#120152;</span> </td> <tr id="entity-grave"><td> <code title="">grave;</code> </td> <td> U+00060 </td> <td> <span class="glyph" title="">`</span> </td> <tr id="entity-gscr"><td> <code title="">gscr;</code> </td> <td> U+0210A </td> <td> <span class="glyph" title="">&#8458;</span> </td> <tr id="entity-gsim"><td> <code title="">gsim;</code> </td> <td> U+02273 </td> <td> <span class="glyph" title="">&#8819;</span> </td> <tr id="entity-gsime"><td> <code title="">gsime;</code> </td> <td> U+02A8E </td> <td> <span class="glyph" title="">&#10894;</span> </td> <tr id="entity-gsiml"><td> <code title="">gsiml;</code> </td> <td> U+02A90 </td> <td> <span class="glyph" title="">&#10896;</span> </td> <tr id="entity-gt"><td> <code title="">gt;</code> </td> <td> U+0003E </td> <td> <span class="glyph" title="">&gt;</span> </td> <tr id="entity-gtcc"><td> <code title="">gtcc;</code> </td> <td> U+02AA7 </td> <td> <span class="glyph" title="">&#10919;</span> </td> <tr id="entity-gtcir"><td> <code title="">gtcir;</code> </td> <td> U+02A7A </td> <td> <span class="glyph" title="">&#10874;</span> </td> <tr id="entity-gtdot"><td> <code title="">gtdot;</code> </td> <td> U+022D7 </td> <td> <span class="glyph" title="">&#8919;</span> </td> <tr id="entity-gtlPar"><td> <code title="">gtlPar;</code> </td> <td> U+02995 </td> <td> <span class="glyph" title="">&#10645;</span> </td> <tr id="entity-gtquest"><td> <code title="">gtquest;</code> </td> <td> U+02A7C </td> <td> <span class="glyph" title="">&#10876;</span> </td> <tr id="entity-gtrapprox"><td> <code title="">gtrapprox;</code> </td> <td> U+02A86 </td> <td> <span class="glyph" title="">&#10886;</span> </td> <tr id="entity-gtrarr"><td> <code title="">gtrarr;</code> </td> <td> U+02978 </td> <td> <span class="glyph" title="">&#10616;</span> </td> <tr id="entity-gtrdot"><td> <code title="">gtrdot;</code> </td> <td> U+022D7 </td> <td> <span class="glyph" title="">&#8919;</span> </td> <tr id="entity-gtreqless"><td> <code title="">gtreqless;</code> </td> <td> U+022DB </td> <td> <span class="glyph" title="">&#8923;</span> </td> <tr id="entity-gtreqqless"><td> <code title="">gtreqqless;</code> </td> <td> U+02A8C </td> <td> <span class="glyph" title="">&#10892;</span> </td> <tr id="entity-gtrless"><td> <code title="">gtrless;</code> </td> <td> U+02277 </td> <td> <span class="glyph" title="">&#8823;</span> </td> <tr id="entity-gtrsim"><td> <code title="">gtrsim;</code> </td> <td> U+02273 </td> <td> <span class="glyph" title="">&#8819;</span> </td> <tr id="entity-gvertneqq"><td> <code title="">gvertneqq;</code> </td> <td> U+02269 U+0FE00 </td> <td> <span class="glyph compound" title="">&#8809;&#65024;</span> </td> <tr id="entity-gvnE"><td> <code title="">gvnE;</code> </td> <td> U+02269 U+0FE00 </td> <td> <span class="glyph compound" title="">&#8809;&#65024;</span> </td> <tr id="entity-hArr"><td> <code title="">hArr;</code> </td> <td> U+021D4 </td> <td> <span class="glyph" title="">&hArr;</span> </td> <tr id="entity-hairsp"><td> <code title="">hairsp;</code> </td> <td> U+0200A </td> <td> <span class="glyph" title="">&#8202;</span> </td> <tr id="entity-half"><td> <code title="">half;</code> </td> <td> U+000BD </td> <td> <span class="glyph" title="">&frac12;</span> </td> <tr id="entity-hamilt"><td> <code title="">hamilt;</code> </td> <td> U+0210B </td> <td> <span class="glyph" title="">&#8459;</span> </td> <tr id="entity-hardcy"><td> <code title="">hardcy;</code> </td> <td> U+0044A </td> <td> <span class="glyph" title="">&#1098;</span> </td> <tr id="entity-harr"><td> <code title="">harr;</code> </td> <td> U+02194 </td> <td> <span class="glyph" title="">&harr;</span> </td> <tr id="entity-harrcir"><td> <code title="">harrcir;</code> </td> <td> U+02948 </td> <td> <span class="glyph" title="">&#10568;</span> </td> <tr id="entity-harrw"><td> <code title="">harrw;</code> </td> <td> U+021AD </td> <td> <span class="glyph" title="">&#8621;</span> </td> <tr id="entity-hbar"><td> <code title="">hbar;</code> </td> <td> U+0210F </td> <td> <span class="glyph" title="">&#8463;</span> </td> <tr id="entity-hcirc"><td> <code title="">hcirc;</code> </td> <td> U+00125 </td> <td> <span class="glyph" title="">&#293;</span> </td> <tr id="entity-hearts"><td> <code title="">hearts;</code> </td> <td> U+02665 </td> <td> <span class="glyph" title="">&hearts;</span> </td> <tr id="entity-heartsuit"><td> <code title="">heartsuit;</code> </td> <td> U+02665 </td> <td> <span class="glyph" title="">&hearts;</span> </td> <tr id="entity-hellip"><td> <code title="">hellip;</code> </td> <td> U+02026 </td> <td> <span class="glyph" title="">&hellip;</span> </td> <tr id="entity-hercon"><td> <code title="">hercon;</code> </td> <td> U+022B9 </td> <td> <span class="glyph" title="">&#8889;</span> </td> <tr id="entity-hfr"><td> <code title="">hfr;</code> </td> <td> U+1D525 </td> <td> <span class="glyph" title="">&#120101;</span> </td> <tr id="entity-hksearow"><td> <code title="">hksearow;</code> </td> <td> U+02925 </td> <td> <span class="glyph" title="">&#10533;</span> </td> <tr id="entity-hkswarow"><td> <code title="">hkswarow;</code> </td> <td> U+02926 </td> <td> <span class="glyph" title="">&#10534;</span> </td> <tr id="entity-hoarr"><td> <code title="">hoarr;</code> </td> <td> U+021FF </td> <td> <span class="glyph" title="">&#8703;</span> </td> <tr id="entity-homtht"><td> <code title="">homtht;</code> </td> <td> U+0223B </td> <td> <span class="glyph" title="">&#8763;</span> </td> <tr id="entity-hookleftarrow"><td> <code title="">hookleftarrow;</code> </td> <td> U+021A9 </td> <td> <span class="glyph" title="">&#8617;</span> </td> <tr id="entity-hookrightarrow"><td> <code title="">hookrightarrow;</code> </td> <td> U+021AA </td> <td> <span class="glyph" title="">&#8618;</span> </td> <tr id="entity-hopf"><td> <code title="">hopf;</code> </td> <td> U+1D559 </td> <td> <span class="glyph" title="">&#120153;</span> </td> <tr id="entity-horbar"><td> <code title="">horbar;</code> </td> <td> U+02015 </td> <td> <span class="glyph" title="">&#8213;</span> </td> <tr id="entity-hscr"><td> <code title="">hscr;</code> </td> <td> U+1D4BD </td> <td> <span class="glyph" title="">&#119997;</span> </td> <tr id="entity-hslash"><td> <code title="">hslash;</code> </td> <td> U+0210F </td> <td> <span class="glyph" title="">&#8463;</span> </td> <tr id="entity-hstrok"><td> <code title="">hstrok;</code> </td> <td> U+00127 </td> <td> <span class="glyph" title="">&#295;</span> </td> <tr id="entity-hybull"><td> <code title="">hybull;</code> </td> <td> U+02043 </td> <td> <span class="glyph" title="">&#8259;</span> </td> <tr id="entity-hyphen"><td> <code title="">hyphen;</code> </td> <td> U+02010 </td> <td> <span class="glyph" title="">&#8208;</span> </td> <tr id="entity-iacute"><td> <code title="">iacute;</code> </td> <td> U+000ED </td> <td> <span class="glyph" title="">&iacute;</span> </td> <tr id="entity-ic"><td> <code title="">ic;</code> </td> <td> U+02063 </td> <td> <span class="glyph" title="">&#8291;</span> </td> <tr id="entity-icirc"><td> <code title="">icirc;</code> </td> <td> U+000EE </td> <td> <span class="glyph" title="">&icirc;</span> </td> <tr id="entity-icy"><td> <code title="">icy;</code> </td> <td> U+00438 </td> <td> <span class="glyph" title="">&#1080;</span> </td> <tr id="entity-iecy"><td> <code title="">iecy;</code> </td> <td> U+00435 </td> <td> <span class="glyph" title="">&#1077;</span> </td> <tr id="entity-iexcl"><td> <code title="">iexcl;</code> </td> <td> U+000A1 </td> <td> <span class="glyph" title="">&iexcl;</span> </td> <tr id="entity-iff"><td> <code title="">iff;</code> </td> <td> U+021D4 </td> <td> <span class="glyph" title="">&hArr;</span> </td> <tr id="entity-ifr"><td> <code title="">ifr;</code> </td> <td> U+1D526 </td> <td> <span class="glyph" title="">&#120102;</span> </td> <tr id="entity-igrave"><td> <code title="">igrave;</code> </td> <td> U+000EC </td> <td> <span class="glyph" title="">&igrave;</span> </td> <tr id="entity-ii"><td> <code title="">ii;</code> </td> <td> U+02148 </td> <td> <span class="glyph" title="">&#8520;</span> </td> <tr id="entity-iiiint"><td> <code title="">iiiint;</code> </td> <td> U+02A0C </td> <td> <span class="glyph" title="">&#10764;</span> </td> <tr id="entity-iiint"><td> <code title="">iiint;</code> </td> <td> U+0222D </td> <td> <span class="glyph" title="">&#8749;</span> </td> <tr id="entity-iinfin"><td> <code title="">iinfin;</code> </td> <td> U+029DC </td> <td> <span class="glyph" title="">&#10716;</span> </td> <tr id="entity-iiota"><td> <code title="">iiota;</code> </td> <td> U+02129 </td> <td> <span class="glyph" title="">&#8489;</span> </td> <tr id="entity-ijlig"><td> <code title="">ijlig;</code> </td> <td> U+00133 </td> <td> <span class="glyph" title="">&#307;</span> </td> <tr id="entity-imacr"><td> <code title="">imacr;</code> </td> <td> U+0012B </td> <td> <span class="glyph" title="">&#299;</span> </td> <tr id="entity-image"><td> <code title="">image;</code> </td> <td> U+02111 </td> <td> <span class="glyph" title="">&image;</span> </td> <tr id="entity-imagline"><td> <code title="">imagline;</code> </td> <td> U+02110 </td> <td> <span class="glyph" title="">&#8464;</span> </td> <tr id="entity-imagpart"><td> <code title="">imagpart;</code> </td> <td> U+02111 </td> <td> <span class="glyph" title="">&image;</span> </td> <tr id="entity-imath"><td> <code title="">imath;</code> </td> <td> U+00131 </td> <td> <span class="glyph" title="">&#305;</span> </td> <tr id="entity-imof"><td> <code title="">imof;</code> </td> <td> U+022B7 </td> <td> <span class="glyph" title="">&#8887;</span> </td> <tr id="entity-imped"><td> <code title="">imped;</code> </td> <td> U+001B5 </td> <td> <span class="glyph" title="">&#437;</span> </td> <tr id="entity-in"><td> <code title="">in;</code> </td> <td> U+02208 </td> <td> <span class="glyph" title="">&isin;</span> </td> <tr id="entity-incare"><td> <code title="">incare;</code> </td> <td> U+02105 </td> <td> <span class="glyph" title="">&#8453;</span> </td> <tr id="entity-infin"><td> <code title="">infin;</code> </td> <td> U+0221E </td> <td> <span class="glyph" title="">&infin;</span> </td> <tr id="entity-infintie"><td> <code title="">infintie;</code> </td> <td> U+029DD </td> <td> <span class="glyph" title="">&#10717;</span> </td> <tr id="entity-inodot"><td> <code title="">inodot;</code> </td> <td> U+00131 </td> <td> <span class="glyph" title="">&#305;</span> </td> <tr id="entity-int"><td> <code title="">int;</code> </td> <td> U+0222B </td> <td> <span class="glyph" title="">&int;</span> </td> <tr id="entity-intcal"><td> <code title="">intcal;</code> </td> <td> U+022BA </td> <td> <span class="glyph" title="">&#8890;</span> </td> <tr id="entity-integers"><td> <code title="">integers;</code> </td> <td> U+02124 </td> <td> <span class="glyph" title="">&#8484;</span> </td> <tr id="entity-intercal"><td> <code title="">intercal;</code> </td> <td> U+022BA </td> <td> <span class="glyph" title="">&#8890;</span> </td> <tr id="entity-intlarhk"><td> <code title="">intlarhk;</code> </td> <td> U+02A17 </td> <td> <span class="glyph" title="">&#10775;</span> </td> <tr id="entity-intprod"><td> <code title="">intprod;</code> </td> <td> U+02A3C </td> <td> <span class="glyph" title="">&#10812;</span> </td> <tr id="entity-iocy"><td> <code title="">iocy;</code> </td> <td> U+00451 </td> <td> <span class="glyph" title="">&#1105;</span> </td> <tr id="entity-iogon"><td> <code title="">iogon;</code> </td> <td> U+0012F </td> <td> <span class="glyph" title="">&#303;</span> </td> <tr id="entity-iopf"><td> <code title="">iopf;</code> </td> <td> U+1D55A </td> <td> <span class="glyph" title="">&#120154;</span> </td> <tr id="entity-iota"><td> <code title="">iota;</code> </td> <td> U+003B9 </td> <td> <span class="glyph" title="">&iota;</span> </td> <tr id="entity-iprod"><td> <code title="">iprod;</code> </td> <td> U+02A3C </td> <td> <span class="glyph" title="">&#10812;</span> </td> <tr id="entity-iquest"><td> <code title="">iquest;</code> </td> <td> U+000BF </td> <td> <span class="glyph" title="">&iquest;</span> </td> <tr id="entity-iscr"><td> <code title="">iscr;</code> </td> <td> U+1D4BE </td> <td> <span class="glyph" title="">&#119998;</span> </td> <tr id="entity-isin"><td> <code title="">isin;</code> </td> <td> U+02208 </td> <td> <span class="glyph" title="">&isin;</span> </td> <tr id="entity-isinE"><td> <code title="">isinE;</code> </td> <td> U+022F9 </td> <td> <span class="glyph" title="">&#8953;</span> </td> <tr id="entity-isindot"><td> <code title="">isindot;</code> </td> <td> U+022F5 </td> <td> <span class="glyph" title="">&#8949;</span> </td> <tr id="entity-isins"><td> <code title="">isins;</code> </td> <td> U+022F4 </td> <td> <span class="glyph" title="">&#8948;</span> </td> <tr id="entity-isinsv"><td> <code title="">isinsv;</code> </td> <td> U+022F3 </td> <td> <span class="glyph" title="">&#8947;</span> </td> <tr id="entity-isinv"><td> <code title="">isinv;</code> </td> <td> U+02208 </td> <td> <span class="glyph" title="">&isin;</span> </td> <tr id="entity-it"><td> <code title="">it;</code> </td> <td> U+02062 </td> <td> <span class="glyph" title="">&#8290;</span> </td> <tr id="entity-itilde"><td> <code title="">itilde;</code> </td> <td> U+00129 </td> <td> <span class="glyph" title="">&#297;</span> </td> <tr id="entity-iukcy"><td> <code title="">iukcy;</code> </td> <td> U+00456 </td> <td> <span class="glyph" title="">&#1110;</span> </td> <tr id="entity-iuml"><td> <code title="">iuml;</code> </td> <td> U+000EF </td> <td> <span class="glyph" title="">&iuml;</span> </td> <tr id="entity-jcirc"><td> <code title="">jcirc;</code> </td> <td> U+00135 </td> <td> <span class="glyph" title="">&#309;</span> </td> <tr id="entity-jcy"><td> <code title="">jcy;</code> </td> <td> U+00439 </td> <td> <span class="glyph" title="">&#1081;</span> </td> <tr id="entity-jfr"><td> <code title="">jfr;</code> </td> <td> U+1D527 </td> <td> <span class="glyph" title="">&#120103;</span> </td> <tr id="entity-jmath"><td> <code title="">jmath;</code> </td> <td> U+00237 </td> <td> <span class="glyph" title="">&#567;</span> </td> <tr id="entity-jopf"><td> <code title="">jopf;</code> </td> <td> U+1D55B </td> <td> <span class="glyph" title="">&#120155;</span> </td> <tr id="entity-jscr"><td> <code title="">jscr;</code> </td> <td> U+1D4BF </td> <td> <span class="glyph" title="">&#119999;</span> </td> <tr id="entity-jsercy"><td> <code title="">jsercy;</code> </td> <td> U+00458 </td> <td> <span class="glyph" title="">&#1112;</span> </td> <tr id="entity-jukcy"><td> <code title="">jukcy;</code> </td> <td> U+00454 </td> <td> <span class="glyph" title="">&#1108;</span> </td> <tr id="entity-kappa"><td> <code title="">kappa;</code> </td> <td> U+003BA </td> <td> <span class="glyph" title="">&kappa;</span> </td> <tr id="entity-kappav"><td> <code title="">kappav;</code> </td> <td> U+003F0 </td> <td> <span class="glyph" title="">&#1008;</span> </td> <tr id="entity-kcedil"><td> <code title="">kcedil;</code> </td> <td> U+00137 </td> <td> <span class="glyph" title="">&#311;</span> </td> <tr id="entity-kcy"><td> <code title="">kcy;</code> </td> <td> U+0043A </td> <td> <span class="glyph" title="">&#1082;</span> </td> <tr id="entity-kfr"><td> <code title="">kfr;</code> </td> <td> U+1D528 </td> <td> <span class="glyph" title="">&#120104;</span> </td> <tr id="entity-kgreen"><td> <code title="">kgreen;</code> </td> <td> U+00138 </td> <td> <span class="glyph" title="">&#312;</span> </td> <tr id="entity-khcy"><td> <code title="">khcy;</code> </td> <td> U+00445 </td> <td> <span class="glyph" title="">&#1093;</span> </td> <tr id="entity-kjcy"><td> <code title="">kjcy;</code> </td> <td> U+0045C </td> <td> <span class="glyph" title="">&#1116;</span> </td> <tr id="entity-kopf"><td> <code title="">kopf;</code> </td> <td> U+1D55C </td> <td> <span class="glyph" title="">&#120156;</span> </td> <tr id="entity-kscr"><td> <code title="">kscr;</code> </td> <td> U+1D4C0 </td> <td> <span class="glyph" title="">&#120000;</span> </td> <tr id="entity-lAarr"><td> <code title="">lAarr;</code> </td> <td> U+021DA </td> <td> <span class="glyph" title="">&#8666;</span> </td> <tr id="entity-lArr"><td> <code title="">lArr;</code> </td> <td> U+021D0 </td> <td> <span class="glyph" title="">&lArr;</span> </td> <tr id="entity-lAtail"><td> <code title="">lAtail;</code> </td> <td> U+0291B </td> <td> <span class="glyph" title="">&#10523;</span> </td> <tr id="entity-lBarr"><td> <code title="">lBarr;</code> </td> <td> U+0290E </td> <td> <span class="glyph" title="">&#10510;</span> </td> <tr id="entity-lE"><td> <code title="">lE;</code> </td> <td> U+02266 </td> <td> <span class="glyph" title="">&#8806;</span> </td> <tr id="entity-lEg"><td> <code title="">lEg;</code> </td> <td> U+02A8B </td> <td> <span class="glyph" title="">&#10891;</span> </td> <tr id="entity-lHar"><td> <code title="">lHar;</code> </td> <td> U+02962 </td> <td> <span class="glyph" title="">&#10594;</span> </td> <tr id="entity-lacute"><td> <code title="">lacute;</code> </td> <td> U+0013A </td> <td> <span class="glyph" title="">&#314;</span> </td> <tr id="entity-laemptyv"><td> <code title="">laemptyv;</code> </td> <td> U+029B4 </td> <td> <span class="glyph" title="">&#10676;</span> </td> <tr id="entity-lagran"><td> <code title="">lagran;</code> </td> <td> U+02112 </td> <td> <span class="glyph" title="">&#8466;</span> </td> <tr id="entity-lambda"><td> <code title="">lambda;</code> </td> <td> U+003BB </td> <td> <span class="glyph" title="">&lambda;</span> </td> <tr id="entity-lang"><td> <code title="">lang;</code> </td> <td> U+027E8 </td> <td> <span class="glyph" title="">&#9001;</span> </td> <tr id="entity-langd"><td> <code title="">langd;</code> </td> <td> U+02991 </td> <td> <span class="glyph" title="">&#10641;</span> </td> <tr id="entity-langle"><td> <code title="">langle;</code> </td> <td> U+027E8 </td> <td> <span class="glyph" title="">&#9001;</span> </td> <tr id="entity-lap"><td> <code title="">lap;</code> </td> <td> U+02A85 </td> <td> <span class="glyph" title="">&#10885;</span> </td> <tr id="entity-laquo"><td> <code title="">laquo;</code> </td> <td> U+000AB </td> <td> <span class="glyph" title="">&laquo;</span> </td> <tr id="entity-larr"><td> <code title="">larr;</code> </td> <td> U+02190 </td> <td> <span class="glyph" title="">&larr;</span> </td> <tr id="entity-larrb"><td> <code title="">larrb;</code> </td> <td> U+021E4 </td> <td> <span class="glyph" title="">&#8676;</span> </td> <tr id="entity-larrbfs"><td> <code title="">larrbfs;</code> </td> <td> U+0291F </td> <td> <span class="glyph" title="">&#10527;</span> </td> <tr id="entity-larrfs"><td> <code title="">larrfs;</code> </td> <td> U+0291D </td> <td> <span class="glyph" title="">&#10525;</span> </td> <tr id="entity-larrhk"><td> <code title="">larrhk;</code> </td> <td> U+021A9 </td> <td> <span class="glyph" title="">&#8617;</span> </td> <tr id="entity-larrlp"><td> <code title="">larrlp;</code> </td> <td> U+021AB </td> <td> <span class="glyph" title="">&#8619;</span> </td> <tr id="entity-larrpl"><td> <code title="">larrpl;</code> </td> <td> U+02939 </td> <td> <span class="glyph" title="">&#10553;</span> </td> <tr id="entity-larrsim"><td> <code title="">larrsim;</code> </td> <td> U+02973 </td> <td> <span class="glyph" title="">&#10611;</span> </td> <tr id="entity-larrtl"><td> <code title="">larrtl;</code> </td> <td> U+021A2 </td> <td> <span class="glyph" title="">&#8610;</span> </td> <tr id="entity-lat"><td> <code title="">lat;</code> </td> <td> U+02AAB </td> <td> <span class="glyph" title="">&#10923;</span> </td> <tr id="entity-latail"><td> <code title="">latail;</code> </td> <td> U+02919 </td> <td> <span class="glyph" title="">&#10521;</span> </td> <tr id="entity-late"><td> <code title="">late;</code> </td> <td> U+02AAD </td> <td> <span class="glyph" title="">&#10925;</span> </td> <tr id="entity-lates"><td> <code title="">lates;</code> </td> <td> U+02AAD U+0FE00 </td> <td> <span class="glyph compound" title="">&#10925;&#65024;</span> </td> <tr id="entity-lbarr"><td> <code title="">lbarr;</code> </td> <td> U+0290C </td> <td> <span class="glyph" title="">&#10508;</span> </td> <tr id="entity-lbbrk"><td> <code title="">lbbrk;</code> </td> <td> U+02772 </td> <td> <span class="glyph" title="">&#10098;</span> </td> <tr id="entity-lbrace"><td> <code title="">lbrace;</code> </td> <td> U+0007B </td> <td> <span class="glyph" title="">{</span> </td> <tr id="entity-lbrack"><td> <code title="">lbrack;</code> </td> <td> U+0005B </td> <td> <span class="glyph" title="">[</span> </td> <tr id="entity-lbrke"><td> <code title="">lbrke;</code> </td> <td> U+0298B </td> <td> <span class="glyph" title="">&#10635;</span> </td> <tr id="entity-lbrksld"><td> <code title="">lbrksld;</code> </td> <td> U+0298F </td> <td> <span class="glyph" title="">&#10639;</span> </td> <tr id="entity-lbrkslu"><td> <code title="">lbrkslu;</code> </td> <td> U+0298D </td> <td> <span class="glyph" title="">&#10637;</span> </td> <tr id="entity-lcaron"><td> <code title="">lcaron;</code> </td> <td> U+0013E </td> <td> <span class="glyph" title="">&#318;</span> </td> <tr id="entity-lcedil"><td> <code title="">lcedil;</code> </td> <td> U+0013C </td> <td> <span class="glyph" title="">&#316;</span> </td> <tr id="entity-lceil"><td> <code title="">lceil;</code> </td> <td> U+02308 </td> <td> <span class="glyph" title="">&lceil;</span> </td> <tr id="entity-lcub"><td> <code title="">lcub;</code> </td> <td> U+0007B </td> <td> <span class="glyph" title="">{</span> </td> <tr id="entity-lcy"><td> <code title="">lcy;</code> </td> <td> U+0043B </td> <td> <span class="glyph" title="">&#1083;</span> </td> <tr id="entity-ldca"><td> <code title="">ldca;</code> </td> <td> U+02936 </td> <td> <span class="glyph" title="">&#10550;</span> </td> <tr id="entity-ldquo"><td> <code title="">ldquo;</code> </td> <td> U+0201C </td> <td> <span class="glyph" title="">&ldquo;</span> </td> <tr id="entity-ldquor"><td> <code title="">ldquor;</code> </td> <td> U+0201E </td> <td> <span class="glyph" title="">&bdquo;</span> </td> <tr id="entity-ldrdhar"><td> <code title="">ldrdhar;</code> </td> <td> U+02967 </td> <td> <span class="glyph" title="">&#10599;</span> </td> <tr id="entity-ldrushar"><td> <code title="">ldrushar;</code> </td> <td> U+0294B </td> <td> <span class="glyph" title="">&#10571;</span> </td> <tr id="entity-ldsh"><td> <code title="">ldsh;</code> </td> <td> U+021B2 </td> <td> <span class="glyph" title="">&#8626;</span> </td> <tr id="entity-le"><td> <code title="">le;</code> </td> <td> U+02264 </td> <td> <span class="glyph" title="">&le;</span> </td> <tr id="entity-leftarrow"><td> <code title="">leftarrow;</code> </td> <td> U+02190 </td> <td> <span class="glyph" title="">&larr;</span> </td> <tr id="entity-leftarrowtail"><td> <code title="">leftarrowtail;</code> </td> <td> U+021A2 </td> <td> <span class="glyph" title="">&#8610;</span> </td> <tr id="entity-leftharpoondown"><td> <code title="">leftharpoondown;</code> </td> <td> U+021BD </td> <td> <span class="glyph" title="">&#8637;</span> </td> <tr id="entity-leftharpoonup"><td> <code title="">leftharpoonup;</code> </td> <td> U+021BC </td> <td> <span class="glyph" title="">&#8636;</span> </td> <tr id="entity-leftleftarrows"><td> <code title="">leftleftarrows;</code> </td> <td> U+021C7 </td> <td> <span class="glyph" title="">&#8647;</span> </td> <tr id="entity-leftrightarrow"><td> <code title="">leftrightarrow;</code> </td> <td> U+02194 </td> <td> <span class="glyph" title="">&harr;</span> </td> <tr id="entity-leftrightarrows"><td> <code title="">leftrightarrows;</code> </td> <td> U+021C6 </td> <td> <span class="glyph" title="">&#8646;</span> </td> <tr id="entity-leftrightharpoons"><td> <code title="">leftrightharpoons;</code> </td> <td> U+021CB </td> <td> <span class="glyph" title="">&#8651;</span> </td> <tr id="entity-leftrightsquigarrow"><td> <code title="">leftrightsquigarrow;</code> </td> <td> U+021AD </td> <td> <span class="glyph" title="">&#8621;</span> </td> <tr id="entity-leftthreetimes"><td> <code title="">leftthreetimes;</code> </td> <td> U+022CB </td> <td> <span class="glyph" title="">&#8907;</span> </td> <tr id="entity-leg"><td> <code title="">leg;</code> </td> <td> U+022DA </td> <td> <span class="glyph" title="">&#8922;</span> </td> <tr id="entity-leq"><td> <code title="">leq;</code> </td> <td> U+02264 </td> <td> <span class="glyph" title="">&le;</span> </td> <tr id="entity-leqq"><td> <code title="">leqq;</code> </td> <td> U+02266 </td> <td> <span class="glyph" title="">&#8806;</span> </td> <tr id="entity-leqslant"><td> <code title="">leqslant;</code> </td> <td> U+02A7D </td> <td> <span class="glyph" title="">&#10877;</span> </td> <tr id="entity-les"><td> <code title="">les;</code> </td> <td> U+02A7D </td> <td> <span class="glyph" title="">&#10877;</span> </td> <tr id="entity-lescc"><td> <code title="">lescc;</code> </td> <td> U+02AA8 </td> <td> <span class="glyph" title="">&#10920;</span> </td> <tr id="entity-lesdot"><td> <code title="">lesdot;</code> </td> <td> U+02A7F </td> <td> <span class="glyph" title="">&#10879;</span> </td> <tr id="entity-lesdoto"><td> <code title="">lesdoto;</code> </td> <td> U+02A81 </td> <td> <span class="glyph" title="">&#10881;</span> </td> <tr id="entity-lesdotor"><td> <code title="">lesdotor;</code> </td> <td> U+02A83 </td> <td> <span class="glyph" title="">&#10883;</span> </td> <tr id="entity-lesg"><td> <code title="">lesg;</code> </td> <td> U+022DA U+0FE00 </td> <td> <span class="glyph compound" title="">&#8922;&#65024;</span> </td> <tr id="entity-lesges"><td> <code title="">lesges;</code> </td> <td> U+02A93 </td> <td> <span class="glyph" title="">&#10899;</span> </td> <tr id="entity-lessapprox"><td> <code title="">lessapprox;</code> </td> <td> U+02A85 </td> <td> <span class="glyph" title="">&#10885;</span> </td> <tr id="entity-lessdot"><td> <code title="">lessdot;</code> </td> <td> U+022D6 </td> <td> <span class="glyph" title="">&#8918;</span> </td> <tr id="entity-lesseqgtr"><td> <code title="">lesseqgtr;</code> </td> <td> U+022DA </td> <td> <span class="glyph" title="">&#8922;</span> </td> <tr id="entity-lesseqqgtr"><td> <code title="">lesseqqgtr;</code> </td> <td> U+02A8B </td> <td> <span class="glyph" title="">&#10891;</span> </td> <tr id="entity-lessgtr"><td> <code title="">lessgtr;</code> </td> <td> U+02276 </td> <td> <span class="glyph" title="">&#8822;</span> </td> <tr id="entity-lesssim"><td> <code title="">lesssim;</code> </td> <td> U+02272 </td> <td> <span class="glyph" title="">&#8818;</span> </td> <tr id="entity-lfisht"><td> <code title="">lfisht;</code> </td> <td> U+0297C </td> <td> <span class="glyph" title="">&#10620;</span> </td> <tr id="entity-lfloor"><td> <code title="">lfloor;</code> </td> <td> U+0230A </td> <td> <span class="glyph" title="">&lfloor;</span> </td> <tr id="entity-lfr"><td> <code title="">lfr;</code> </td> <td> U+1D529 </td> <td> <span class="glyph" title="">&#120105;</span> </td> <tr id="entity-lg"><td> <code title="">lg;</code> </td> <td> U+02276 </td> <td> <span class="glyph" title="">&#8822;</span> </td> <tr id="entity-lgE"><td> <code title="">lgE;</code> </td> <td> U+02A91 </td> <td> <span class="glyph" title="">&#10897;</span> </td> <tr id="entity-lhard"><td> <code title="">lhard;</code> </td> <td> U+021BD </td> <td> <span class="glyph" title="">&#8637;</span> </td> <tr id="entity-lharu"><td> <code title="">lharu;</code> </td> <td> U+021BC </td> <td> <span class="glyph" title="">&#8636;</span> </td> <tr id="entity-lharul"><td> <code title="">lharul;</code> </td> <td> U+0296A </td> <td> <span class="glyph" title="">&#10602;</span> </td> <tr id="entity-lhblk"><td> <code title="">lhblk;</code> </td> <td> U+02584 </td> <td> <span class="glyph" title="">&#9604;</span> </td> <tr id="entity-ljcy"><td> <code title="">ljcy;</code> </td> <td> U+00459 </td> <td> <span class="glyph" title="">&#1113;</span> </td> <tr id="entity-ll"><td> <code title="">ll;</code> </td> <td> U+0226A </td> <td> <span class="glyph" title="">&#8810;</span> </td> <tr id="entity-llarr"><td> <code title="">llarr;</code> </td> <td> U+021C7 </td> <td> <span class="glyph" title="">&#8647;</span> </td> <tr id="entity-llcorner"><td> <code title="">llcorner;</code> </td> <td> U+0231E </td> <td> <span class="glyph" title="">&#8990;</span> </td> <tr id="entity-llhard"><td> <code title="">llhard;</code> </td> <td> U+0296B </td> <td> <span class="glyph" title="">&#10603;</span> </td> <tr id="entity-lltri"><td> <code title="">lltri;</code> </td> <td> U+025FA </td> <td> <span class="glyph" title="">&#9722;</span> </td> <tr id="entity-lmidot"><td> <code title="">lmidot;</code> </td> <td> U+00140 </td> <td> <span class="glyph" title="">&#320;</span> </td> <tr id="entity-lmoust"><td> <code title="">lmoust;</code> </td> <td> U+023B0 </td> <td> <span class="glyph" title="">&#9136;</span> </td> <tr id="entity-lmoustache"><td> <code title="">lmoustache;</code> </td> <td> U+023B0 </td> <td> <span class="glyph" title="">&#9136;</span> </td> <tr id="entity-lnE"><td> <code title="">lnE;</code> </td> <td> U+02268 </td> <td> <span class="glyph" title="">&#8808;</span> </td> <tr id="entity-lnap"><td> <code title="">lnap;</code> </td> <td> U+02A89 </td> <td> <span class="glyph" title="">&#10889;</span> </td> <tr id="entity-lnapprox"><td> <code title="">lnapprox;</code> </td> <td> U+02A89 </td> <td> <span class="glyph" title="">&#10889;</span> </td> <tr id="entity-lne"><td> <code title="">lne;</code> </td> <td> U+02A87 </td> <td> <span class="glyph" title="">&#10887;</span> </td> <tr id="entity-lneq"><td> <code title="">lneq;</code> </td> <td> U+02A87 </td> <td> <span class="glyph" title="">&#10887;</span> </td> <tr id="entity-lneqq"><td> <code title="">lneqq;</code> </td> <td> U+02268 </td> <td> <span class="glyph" title="">&#8808;</span> </td> <tr id="entity-lnsim"><td> <code title="">lnsim;</code> </td> <td> U+022E6 </td> <td> <span class="glyph" title="">&#8934;</span> </td> <tr id="entity-loang"><td> <code title="">loang;</code> </td> <td> U+027EC </td> <td> <span class="glyph" title="">&#10220;</span> </td> <tr id="entity-loarr"><td> <code title="">loarr;</code> </td> <td> U+021FD </td> <td> <span class="glyph" title="">&#8701;</span> </td> <tr id="entity-lobrk"><td> <code title="">lobrk;</code> </td> <td> U+027E6 </td> <td> <span class="glyph" title="">&#10214;</span> </td> <tr id="entity-longleftarrow"><td> <code title="">longleftarrow;</code> </td> <td> U+027F5 </td> <td> <span class="glyph" title="">&#10229;</span> </td> <tr id="entity-longleftrightarrow"><td> <code title="">longleftrightarrow;</code> </td> <td> U+027F7 </td> <td> <span class="glyph" title="">&#10231;</span> </td> <tr id="entity-longmapsto"><td> <code title="">longmapsto;</code> </td> <td> U+027FC </td> <td> <span class="glyph" title="">&#10236;</span> </td> <tr id="entity-longrightarrow"><td> <code title="">longrightarrow;</code> </td> <td> U+027F6 </td> <td> <span class="glyph" title="">&#10230;</span> </td> <tr id="entity-looparrowleft"><td> <code title="">looparrowleft;</code> </td> <td> U+021AB </td> <td> <span class="glyph" title="">&#8619;</span> </td> <tr id="entity-looparrowright"><td> <code title="">looparrowright;</code> </td> <td> U+021AC </td> <td> <span class="glyph" title="">&#8620;</span> </td> <tr id="entity-lopar"><td> <code title="">lopar;</code> </td> <td> U+02985 </td> <td> <span class="glyph" title="">&#10629;</span> </td> <tr id="entity-lopf"><td> <code title="">lopf;</code> </td> <td> U+1D55D </td> <td> <span class="glyph" title="">&#120157;</span> </td> <tr id="entity-loplus"><td> <code title="">loplus;</code> </td> <td> U+02A2D </td> <td> <span class="glyph" title="">&#10797;</span> </td> <tr id="entity-lotimes"><td> <code title="">lotimes;</code> </td> <td> U+02A34 </td> <td> <span class="glyph" title="">&#10804;</span> </td> <tr id="entity-lowast"><td> <code title="">lowast;</code> </td> <td> U+02217 </td> <td> <span class="glyph" title="">&lowast;</span> </td> <tr id="entity-lowbar"><td> <code title="">lowbar;</code> </td> <td> U+0005F </td> <td> <span class="glyph" title="">_</span> </td> <tr id="entity-loz"><td> <code title="">loz;</code> </td> <td> U+025CA </td> <td> <span class="glyph" title="">&loz;</span> </td> <tr id="entity-lozenge"><td> <code title="">lozenge;</code> </td> <td> U+025CA </td> <td> <span class="glyph" title="">&loz;</span> </td> <tr id="entity-lozf"><td> <code title="">lozf;</code> </td> <td> U+029EB </td> <td> <span class="glyph" title="">&#10731;</span> </td> <tr id="entity-lpar"><td> <code title="">lpar;</code> </td> <td> U+00028 </td> <td> <span class="glyph" title="">(</span> </td> <tr id="entity-lparlt"><td> <code title="">lparlt;</code> </td> <td> U+02993 </td> <td> <span class="glyph" title="">&#10643;</span> </td> <tr id="entity-lrarr"><td> <code title="">lrarr;</code> </td> <td> U+021C6 </td> <td> <span class="glyph" title="">&#8646;</span> </td> <tr id="entity-lrcorner"><td> <code title="">lrcorner;</code> </td> <td> U+0231F </td> <td> <span class="glyph" title="">&#8991;</span> </td> <tr id="entity-lrhar"><td> <code title="">lrhar;</code> </td> <td> U+021CB </td> <td> <span class="glyph" title="">&#8651;</span> </td> <tr id="entity-lrhard"><td> <code title="">lrhard;</code> </td> <td> U+0296D </td> <td> <span class="glyph" title="">&#10605;</span> </td> <tr id="entity-lrm"><td> <code title="">lrm;</code> </td> <td> U+0200E </td> <td> <span class="glyph" title="">&lrm;</span> </td> <tr id="entity-lrtri"><td> <code title="">lrtri;</code> </td> <td> U+022BF </td> <td> <span class="glyph" title="">&#8895;</span> </td> <tr id="entity-lsaquo"><td> <code title="">lsaquo;</code> </td> <td> U+02039 </td> <td> <span class="glyph" title="">&lsaquo;</span> </td> <tr id="entity-lscr"><td> <code title="">lscr;</code> </td> <td> U+1D4C1 </td> <td> <span class="glyph" title="">&#120001;</span> </td> <tr id="entity-lsh"><td> <code title="">lsh;</code> </td> <td> U+021B0 </td> <td> <span class="glyph" title="">&#8624;</span> </td> <tr id="entity-lsim"><td> <code title="">lsim;</code> </td> <td> U+02272 </td> <td> <span class="glyph" title="">&#8818;</span> </td> <tr id="entity-lsime"><td> <code title="">lsime;</code> </td> <td> U+02A8D </td> <td> <span class="glyph" title="">&#10893;</span> </td> <tr id="entity-lsimg"><td> <code title="">lsimg;</code> </td> <td> U+02A8F </td> <td> <span class="glyph" title="">&#10895;</span> </td> <tr id="entity-lsqb"><td> <code title="">lsqb;</code> </td> <td> U+0005B </td> <td> <span class="glyph" title="">[</span> </td> <tr id="entity-lsquo"><td> <code title="">lsquo;</code> </td> <td> U+02018 </td> <td> <span class="glyph" title="">&lsquo;</span> </td> <tr id="entity-lsquor"><td> <code title="">lsquor;</code> </td> <td> U+0201A </td> <td> <span class="glyph" title="">&sbquo;</span> </td> <tr id="entity-lstrok"><td> <code title="">lstrok;</code> </td> <td> U+00142 </td> <td> <span class="glyph" title="">&#322;</span> </td> <tr id="entity-lt"><td> <code title="">lt;</code> </td> <td> U+0003C </td> <td> <span class="glyph" title="">&lt;</span> </td> <tr id="entity-ltcc"><td> <code title="">ltcc;</code> </td> <td> U+02AA6 </td> <td> <span class="glyph" title="">&#10918;</span> </td> <tr id="entity-ltcir"><td> <code title="">ltcir;</code> </td> <td> U+02A79 </td> <td> <span class="glyph" title="">&#10873;</span> </td> <tr id="entity-ltdot"><td> <code title="">ltdot;</code> </td> <td> U+022D6 </td> <td> <span class="glyph" title="">&#8918;</span> </td> <tr id="entity-lthree"><td> <code title="">lthree;</code> </td> <td> U+022CB </td> <td> <span class="glyph" title="">&#8907;</span> </td> <tr id="entity-ltimes"><td> <code title="">ltimes;</code> </td> <td> U+022C9 </td> <td> <span class="glyph" title="">&#8905;</span> </td> <tr id="entity-ltlarr"><td> <code title="">ltlarr;</code> </td> <td> U+02976 </td> <td> <span class="glyph" title="">&#10614;</span> </td> <tr id="entity-ltquest"><td> <code title="">ltquest;</code> </td> <td> U+02A7B </td> <td> <span class="glyph" title="">&#10875;</span> </td> <tr id="entity-ltrPar"><td> <code title="">ltrPar;</code> </td> <td> U+02996 </td> <td> <span class="glyph" title="">&#10646;</span> </td> <tr id="entity-ltri"><td> <code title="">ltri;</code> </td> <td> U+025C3 </td> <td> <span class="glyph" title="">&#9667;</span> </td> <tr id="entity-ltrie"><td> <code title="">ltrie;</code> </td> <td> U+022B4 </td> <td> <span class="glyph" title="">&#8884;</span> </td> <tr id="entity-ltrif"><td> <code title="">ltrif;</code> </td> <td> U+025C2 </td> <td> <span class="glyph" title="">&#9666;</span> </td> <tr id="entity-lurdshar"><td> <code title="">lurdshar;</code> </td> <td> U+0294A </td> <td> <span class="glyph" title="">&#10570;</span> </td> <tr id="entity-luruhar"><td> <code title="">luruhar;</code> </td> <td> U+02966 </td> <td> <span class="glyph" title="">&#10598;</span> </td> <tr id="entity-lvertneqq"><td> <code title="">lvertneqq;</code> </td> <td> U+02268 U+0FE00 </td> <td> <span class="glyph compound" title="">&#8808;&#65024;</span> </td> <tr id="entity-lvnE"><td> <code title="">lvnE;</code> </td> <td> U+02268 U+0FE00 </td> <td> <span class="glyph compound" title="">&#8808;&#65024;</span> </td> <tr id="entity-mDDot"><td> <code title="">mDDot;</code> </td> <td> U+0223A </td> <td> <span class="glyph" title="">&#8762;</span> </td> <tr id="entity-macr"><td> <code title="">macr;</code> </td> <td> U+000AF </td> <td> <span class="glyph" title="">&macr;</span> </td> <tr id="entity-male"><td> <code title="">male;</code> </td> <td> U+02642 </td> <td> <span class="glyph" title="">&#9794;</span> </td> <tr id="entity-malt"><td> <code title="">malt;</code> </td> <td> U+02720 </td> <td> <span class="glyph" title="">&#10016;</span> </td> <tr id="entity-maltese"><td> <code title="">maltese;</code> </td> <td> U+02720 </td> <td> <span class="glyph" title="">&#10016;</span> </td> <tr id="entity-map"><td> <code title="">map;</code> </td> <td> U+021A6 </td> <td> <span class="glyph" title="">&#8614;</span> </td> <tr id="entity-mapsto"><td> <code title="">mapsto;</code> </td> <td> U+021A6 </td> <td> <span class="glyph" title="">&#8614;</span> </td> <tr id="entity-mapstodown"><td> <code title="">mapstodown;</code> </td> <td> U+021A7 </td> <td> <span class="glyph" title="">&#8615;</span> </td> <tr id="entity-mapstoleft"><td> <code title="">mapstoleft;</code> </td> <td> U+021A4 </td> <td> <span class="glyph" title="">&#8612;</span> </td> <tr id="entity-mapstoup"><td> <code title="">mapstoup;</code> </td> <td> U+021A5 </td> <td> <span class="glyph" title="">&#8613;</span> </td> <tr id="entity-marker"><td> <code title="">marker;</code> </td> <td> U+025AE </td> <td> <span class="glyph" title="">&#9646;</span> </td> <tr id="entity-mcomma"><td> <code title="">mcomma;</code> </td> <td> U+02A29 </td> <td> <span class="glyph" title="">&#10793;</span> </td> <tr id="entity-mcy"><td> <code title="">mcy;</code> </td> <td> U+0043C </td> <td> <span class="glyph" title="">&#1084;</span> </td> <tr id="entity-mdash"><td> <code title="">mdash;</code> </td> <td> U+02014 </td> <td> <span class="glyph" title="">&mdash;</span> </td> <tr id="entity-measuredangle"><td> <code title="">measuredangle;</code> </td> <td> U+02221 </td> <td> <span class="glyph" title="">&#8737;</span> </td> <tr id="entity-mfr"><td> <code title="">mfr;</code> </td> <td> U+1D52A </td> <td> <span class="glyph" title="">&#120106;</span> </td> <tr id="entity-mho"><td> <code title="">mho;</code> </td> <td> U+02127 </td> <td> <span class="glyph" title="">&#8487;</span> </td> <tr id="entity-micro"><td> <code title="">micro;</code> </td> <td> U+000B5 </td> <td> <span class="glyph" title="">&micro;</span> </td> <tr id="entity-mid"><td> <code title="">mid;</code> </td> <td> U+02223 </td> <td> <span class="glyph" title="">&#8739;</span> </td> <tr id="entity-midast"><td> <code title="">midast;</code> </td> <td> U+0002A </td> <td> <span class="glyph" title="">*</span> </td> <tr id="entity-midcir"><td> <code title="">midcir;</code> </td> <td> U+02AF0 </td> <td> <span class="glyph" title="">&#10992;</span> </td> <tr id="entity-middot"><td> <code title="">middot;</code> </td> <td> U+000B7 </td> <td> <span class="glyph" title="">&middot;</span> </td> <tr id="entity-minus"><td> <code title="">minus;</code> </td> <td> U+02212 </td> <td> <span class="glyph" title="">&minus;</span> </td> <tr id="entity-minusb"><td> <code title="">minusb;</code> </td> <td> U+0229F </td> <td> <span class="glyph" title="">&#8863;</span> </td> <tr id="entity-minusd"><td> <code title="">minusd;</code> </td> <td> U+02238 </td> <td> <span class="glyph" title="">&#8760;</span> </td> <tr id="entity-minusdu"><td> <code title="">minusdu;</code> </td> <td> U+02A2A </td> <td> <span class="glyph" title="">&#10794;</span> </td> <tr id="entity-mlcp"><td> <code title="">mlcp;</code> </td> <td> U+02ADB </td> <td> <span class="glyph" title="">&#10971;</span> </td> <tr id="entity-mldr"><td> <code title="">mldr;</code> </td> <td> U+02026 </td> <td> <span class="glyph" title="">&hellip;</span> </td> <tr id="entity-mnplus"><td> <code title="">mnplus;</code> </td> <td> U+02213 </td> <td> <span class="glyph" title="">&#8723;</span> </td> <tr id="entity-models"><td> <code title="">models;</code> </td> <td> U+022A7 </td> <td> <span class="glyph" title="">&#8871;</span> </td> <tr id="entity-mopf"><td> <code title="">mopf;</code> </td> <td> U+1D55E </td> <td> <span class="glyph" title="">&#120158;</span> </td> <tr id="entity-mp"><td> <code title="">mp;</code> </td> <td> U+02213 </td> <td> <span class="glyph" title="">&#8723;</span> </td> <tr id="entity-mscr"><td> <code title="">mscr;</code> </td> <td> U+1D4C2 </td> <td> <span class="glyph" title="">&#120002;</span> </td> <tr id="entity-mstpos"><td> <code title="">mstpos;</code> </td> <td> U+0223E </td> <td> <span class="glyph" title="">&#8766;</span> </td> <tr id="entity-mu"><td> <code title="">mu;</code> </td> <td> U+003BC </td> <td> <span class="glyph" title="">&mu;</span> </td> <tr id="entity-multimap"><td> <code title="">multimap;</code> </td> <td> U+022B8 </td> <td> <span class="glyph" title="">&#8888;</span> </td> <tr id="entity-mumap"><td> <code title="">mumap;</code> </td> <td> U+022B8 </td> <td> <span class="glyph" title="">&#8888;</span> </td> <tr id="entity-nGg"><td> <code title="">nGg;</code> </td> <td> U+022D9 U+00338 </td> <td> <span class="glyph compound" title="">&#8921;&#824;</span> </td> <tr id="entity-nGt"><td> <code title="">nGt;</code> </td> <td> U+0226B U+020D2 </td> <td> <span class="glyph compound" title="">&#8811;&#8402;</span> </td> <tr id="entity-nGtv"><td> <code title="">nGtv;</code> </td> <td> U+0226B U+00338 </td> <td> <span class="glyph compound" title="">&#8811;&#824;</span> </td> <tr id="entity-nLeftarrow"><td> <code title="">nLeftarrow;</code> </td> <td> U+021CD </td> <td> <span class="glyph" title="">&#8653;</span> </td> <tr id="entity-nLeftrightarrow"><td> <code title="">nLeftrightarrow;</code> </td> <td> U+021CE </td> <td> <span class="glyph" title="">&#8654;</span> </td> <tr id="entity-nLl"><td> <code title="">nLl;</code> </td> <td> U+022D8 U+00338 </td> <td> <span class="glyph compound" title="">&#8920;&#824;</span> </td> <tr id="entity-nLt"><td> <code title="">nLt;</code> </td> <td> U+0226A U+020D2 </td> <td> <span class="glyph compound" title="">&#8810;&#8402;</span> </td> <tr id="entity-nLtv"><td> <code title="">nLtv;</code> </td> <td> U+0226A U+00338 </td> <td> <span class="glyph compound" title="">&#8810;&#824;</span> </td> <tr id="entity-nRightarrow"><td> <code title="">nRightarrow;</code> </td> <td> U+021CF </td> <td> <span class="glyph" title="">&#8655;</span> </td> <tr id="entity-nVDash"><td> <code title="">nVDash;</code> </td> <td> U+022AF </td> <td> <span class="glyph" title="">&#8879;</span> </td> <tr id="entity-nVdash"><td> <code title="">nVdash;</code> </td> <td> U+022AE </td> <td> <span class="glyph" title="">&#8878;</span> </td> <tr id="entity-nabla"><td> <code title="">nabla;</code> </td> <td> U+02207 </td> <td> <span class="glyph" title="">&nabla;</span> </td> <tr id="entity-nacute"><td> <code title="">nacute;</code> </td> <td> U+00144 </td> <td> <span class="glyph" title="">&#324;</span> </td> <tr id="entity-nang"><td> <code title="">nang;</code> </td> <td> U+02220 U+020D2 </td> <td> <span class="glyph compound" title="">&ang;&#8402;</span> </td> <tr id="entity-nap"><td> <code title="">nap;</code> </td> <td> U+02249 </td> <td> <span class="glyph" title="">&#8777;</span> </td> <tr id="entity-napE"><td> <code title="">napE;</code> </td> <td> U+02A70 U+00338 </td> <td> <span class="glyph compound" title="">&#10864;&#824;</span> </td> <tr id="entity-napid"><td> <code title="">napid;</code> </td> <td> U+0224B U+00338 </td> <td> <span class="glyph compound" title="">&#8779;&#824;</span> </td> <tr id="entity-napos"><td> <code title="">napos;</code> </td> <td> U+00149 </td> <td> <span class="glyph" title="">&#329;</span> </td> <tr id="entity-napprox"><td> <code title="">napprox;</code> </td> <td> U+02249 </td> <td> <span class="glyph" title="">&#8777;</span> </td> <tr id="entity-natur"><td> <code title="">natur;</code> </td> <td> U+0266E </td> <td> <span class="glyph" title="">&#9838;</span> </td> <tr id="entity-natural"><td> <code title="">natural;</code> </td> <td> U+0266E </td> <td> <span class="glyph" title="">&#9838;</span> </td> <tr id="entity-naturals"><td> <code title="">naturals;</code> </td> <td> U+02115 </td> <td> <span class="glyph" title="">&#8469;</span> </td> <tr id="entity-nbsp"><td> <code title="">nbsp;</code> </td> <td> U+000A0 </td> <td> <span class="glyph" title="">&nbsp;</span> </td> <tr id="entity-nbump"><td> <code title="">nbump;</code> </td> <td> U+0224E U+00338 </td> <td> <span class="glyph compound" title="">&#8782;&#824;</span> </td> <tr id="entity-nbumpe"><td> <code title="">nbumpe;</code> </td> <td> U+0224F U+00338 </td> <td> <span class="glyph compound" title="">&#8783;&#824;</span> </td> <tr id="entity-ncap"><td> <code title="">ncap;</code> </td> <td> U+02A43 </td> <td> <span class="glyph" title="">&#10819;</span> </td> <tr id="entity-ncaron"><td> <code title="">ncaron;</code> </td> <td> U+00148 </td> <td> <span class="glyph" title="">&#328;</span> </td> <tr id="entity-ncedil"><td> <code title="">ncedil;</code> </td> <td> U+00146 </td> <td> <span class="glyph" title="">&#326;</span> </td> <tr id="entity-ncong"><td> <code title="">ncong;</code> </td> <td> U+02247 </td> <td> <span class="glyph" title="">&#8775;</span> </td> <tr id="entity-ncongdot"><td> <code title="">ncongdot;</code> </td> <td> U+02A6D U+00338 </td> <td> <span class="glyph compound" title="">&#10861;&#824;</span> </td> <tr id="entity-ncup"><td> <code title="">ncup;</code> </td> <td> U+02A42 </td> <td> <span class="glyph" title="">&#10818;</span> </td> <tr id="entity-ncy"><td> <code title="">ncy;</code> </td> <td> U+0043D </td> <td> <span class="glyph" title="">&#1085;</span> </td> <tr id="entity-ndash"><td> <code title="">ndash;</code> </td> <td> U+02013 </td> <td> <span class="glyph" title="">&ndash;</span> </td> <tr id="entity-ne"><td> <code title="">ne;</code> </td> <td> U+02260 </td> <td> <span class="glyph" title="">&ne;</span> </td> <tr id="entity-neArr"><td> <code title="">neArr;</code> </td> <td> U+021D7 </td> <td> <span class="glyph" title="">&#8663;</span> </td> <tr id="entity-nearhk"><td> <code title="">nearhk;</code> </td> <td> U+02924 </td> <td> <span class="glyph" title="">&#10532;</span> </td> <tr id="entity-nearr"><td> <code title="">nearr;</code> </td> <td> U+02197 </td> <td> <span class="glyph" title="">&#8599;</span> </td> <tr id="entity-nearrow"><td> <code title="">nearrow;</code> </td> <td> U+02197 </td> <td> <span class="glyph" title="">&#8599;</span> </td> <tr id="entity-nedot"><td> <code title="">nedot;</code> </td> <td> U+02250 U+00338 </td> <td> <span class="glyph compound" title="">&#8784;&#824;</span> </td> <tr id="entity-nequiv"><td> <code title="">nequiv;</code> </td> <td> U+02262 </td> <td> <span class="glyph" title="">&#8802;</span> </td> <tr id="entity-nesear"><td> <code title="">nesear;</code> </td> <td> U+02928 </td> <td> <span class="glyph" title="">&#10536;</span> </td> <tr id="entity-nesim"><td> <code title="">nesim;</code> </td> <td> U+02242 U+00338 </td> <td> <span class="glyph compound" title="">&#8770;&#824;</span> </td> <tr id="entity-nexist"><td> <code title="">nexist;</code> </td> <td> U+02204 </td> <td> <span class="glyph" title="">&#8708;</span> </td> <tr id="entity-nexists"><td> <code title="">nexists;</code> </td> <td> U+02204 </td> <td> <span class="glyph" title="">&#8708;</span> </td> <tr id="entity-nfr"><td> <code title="">nfr;</code> </td> <td> U+1D52B </td> <td> <span class="glyph" title="">&#120107;</span> </td> <tr id="entity-ngE"><td> <code title="">ngE;</code> </td> <td> U+02267 U+00338 </td> <td> <span class="glyph compound" title="">&#8807;&#824;</span> </td> <tr id="entity-nge"><td> <code title="">nge;</code> </td> <td> U+02271 </td> <td> <span class="glyph" title="">&#8817;</span> </td> <tr id="entity-ngeq"><td> <code title="">ngeq;</code> </td> <td> U+02271 </td> <td> <span class="glyph" title="">&#8817;</span> </td> <tr id="entity-ngeqq"><td> <code title="">ngeqq;</code> </td> <td> U+02267 U+00338 </td> <td> <span class="glyph compound" title="">&#8807;&#824;</span> </td> <tr id="entity-ngeqslant"><td> <code title="">ngeqslant;</code> </td> <td> U+02A7E U+00338 </td> <td> <span class="glyph compound" title="">&#10878;&#824;</span> </td> <tr id="entity-nges"><td> <code title="">nges;</code> </td> <td> U+02A7E U+00338 </td> <td> <span class="glyph compound" title="">&#10878;&#824;</span> </td> <tr id="entity-ngsim"><td> <code title="">ngsim;</code> </td> <td> U+02275 </td> <td> <span class="glyph" title="">&#8821;</span> </td> <tr id="entity-ngt"><td> <code title="">ngt;</code> </td> <td> U+0226F </td> <td> <span class="glyph" title="">&#8815;</span> </td> <tr id="entity-ngtr"><td> <code title="">ngtr;</code> </td> <td> U+0226F </td> <td> <span class="glyph" title="">&#8815;</span> </td> <tr id="entity-nhArr"><td> <code title="">nhArr;</code> </td> <td> U+021CE </td> <td> <span class="glyph" title="">&#8654;</span> </td> <tr id="entity-nharr"><td> <code title="">nharr;</code> </td> <td> U+021AE </td> <td> <span class="glyph" title="">&#8622;</span> </td> <tr id="entity-nhpar"><td> <code title="">nhpar;</code> </td> <td> U+02AF2 </td> <td> <span class="glyph" title="">&#10994;</span> </td> <tr id="entity-ni"><td> <code title="">ni;</code> </td> <td> U+0220B </td> <td> <span class="glyph" title="">&ni;</span> </td> <tr id="entity-nis"><td> <code title="">nis;</code> </td> <td> U+022FC </td> <td> <span class="glyph" title="">&#8956;</span> </td> <tr id="entity-nisd"><td> <code title="">nisd;</code> </td> <td> U+022FA </td> <td> <span class="glyph" title="">&#8954;</span> </td> <tr id="entity-niv"><td> <code title="">niv;</code> </td> <td> U+0220B </td> <td> <span class="glyph" title="">&ni;</span> </td> <tr id="entity-njcy"><td> <code title="">njcy;</code> </td> <td> U+0045A </td> <td> <span class="glyph" title="">&#1114;</span> </td> <tr id="entity-nlArr"><td> <code title="">nlArr;</code> </td> <td> U+021CD </td> <td> <span class="glyph" title="">&#8653;</span> </td> <tr id="entity-nlE"><td> <code title="">nlE;</code> </td> <td> U+02266 U+00338 </td> <td> <span class="glyph compound" title="">&#8806;&#824;</span> </td> <tr id="entity-nlarr"><td> <code title="">nlarr;</code> </td> <td> U+0219A </td> <td> <span class="glyph" title="">&#8602;</span> </td> <tr id="entity-nldr"><td> <code title="">nldr;</code> </td> <td> U+02025 </td> <td> <span class="glyph" title="">&#8229;</span> </td> <tr id="entity-nle"><td> <code title="">nle;</code> </td> <td> U+02270 </td> <td> <span class="glyph" title="">&#8816;</span> </td> <tr id="entity-nleftarrow"><td> <code title="">nleftarrow;</code> </td> <td> U+0219A </td> <td> <span class="glyph" title="">&#8602;</span> </td> <tr id="entity-nleftrightarrow"><td> <code title="">nleftrightarrow;</code> </td> <td> U+021AE </td> <td> <span class="glyph" title="">&#8622;</span> </td> <tr id="entity-nleq"><td> <code title="">nleq;</code> </td> <td> U+02270 </td> <td> <span class="glyph" title="">&#8816;</span> </td> <tr id="entity-nleqq"><td> <code title="">nleqq;</code> </td> <td> U+02266 U+00338 </td> <td> <span class="glyph compound" title="">&#8806;&#824;</span> </td> <tr id="entity-nleqslant"><td> <code title="">nleqslant;</code> </td> <td> U+02A7D U+00338 </td> <td> <span class="glyph compound" title="">&#10877;&#824;</span> </td> <tr id="entity-nles"><td> <code title="">nles;</code> </td> <td> U+02A7D U+00338 </td> <td> <span class="glyph compound" title="">&#10877;&#824;</span> </td> <tr id="entity-nless"><td> <code title="">nless;</code> </td> <td> U+0226E </td> <td> <span class="glyph" title="">&#8814;</span> </td> <tr id="entity-nlsim"><td> <code title="">nlsim;</code> </td> <td> U+02274 </td> <td> <span class="glyph" title="">&#8820;</span> </td> <tr id="entity-nlt"><td> <code title="">nlt;</code> </td> <td> U+0226E </td> <td> <span class="glyph" title="">&#8814;</span> </td> <tr id="entity-nltri"><td> <code title="">nltri;</code> </td> <td> U+022EA </td> <td> <span class="glyph" title="">&#8938;</span> </td> <tr id="entity-nltrie"><td> <code title="">nltrie;</code> </td> <td> U+022EC </td> <td> <span class="glyph" title="">&#8940;</span> </td> <tr id="entity-nmid"><td> <code title="">nmid;</code> </td> <td> U+02224 </td> <td> <span class="glyph" title="">&#8740;</span> </td> <tr id="entity-nopf"><td> <code title="">nopf;</code> </td> <td> U+1D55F </td> <td> <span class="glyph" title="">&#120159;</span> </td> <tr id="entity-not"><td> <code title="">not;</code> </td> <td> U+000AC </td> <td> <span class="glyph" title="">&not;</span> </td> <tr id="entity-notin"><td> <code title="">notin;</code> </td> <td> U+02209 </td> <td> <span class="glyph" title="">&notin;</span> </td> <tr id="entity-notinE"><td> <code title="">notinE;</code> </td> <td> U+022F9 U+00338 </td> <td> <span class="glyph compound" title="">&#8953;&#824;</span> </td> <tr id="entity-notindot"><td> <code title="">notindot;</code> </td> <td> U+022F5 U+00338 </td> <td> <span class="glyph compound" title="">&#8949;&#824;</span> </td> <tr id="entity-notinva"><td> <code title="">notinva;</code> </td> <td> U+02209 </td> <td> <span class="glyph" title="">&notin;</span> </td> <tr id="entity-notinvb"><td> <code title="">notinvb;</code> </td> <td> U+022F7 </td> <td> <span class="glyph" title="">&#8951;</span> </td> <tr id="entity-notinvc"><td> <code title="">notinvc;</code> </td> <td> U+022F6 </td> <td> <span class="glyph" title="">&#8950;</span> </td> <tr id="entity-notni"><td> <code title="">notni;</code> </td> <td> U+0220C </td> <td> <span class="glyph" title="">&#8716;</span> </td> <tr id="entity-notniva"><td> <code title="">notniva;</code> </td> <td> U+0220C </td> <td> <span class="glyph" title="">&#8716;</span> </td> <tr id="entity-notnivb"><td> <code title="">notnivb;</code> </td> <td> U+022FE </td> <td> <span class="glyph" title="">&#8958;</span> </td> <tr id="entity-notnivc"><td> <code title="">notnivc;</code> </td> <td> U+022FD </td> <td> <span class="glyph" title="">&#8957;</span> </td> <tr id="entity-npar"><td> <code title="">npar;</code> </td> <td> U+02226 </td> <td> <span class="glyph" title="">&#8742;</span> </td> <tr id="entity-nparallel"><td> <code title="">nparallel;</code> </td> <td> U+02226 </td> <td> <span class="glyph" title="">&#8742;</span> </td> <tr id="entity-nparsl"><td> <code title="">nparsl;</code> </td> <td> U+02AFD U+020E5 </td> <td> <span class="glyph compound" title="">&#11005;&#8421;</span> </td> <tr id="entity-npart"><td> <code title="">npart;</code> </td> <td> U+02202 U+00338 </td> <td> <span class="glyph compound" title="">&part;&#824;</span> </td> <tr id="entity-npolint"><td> <code title="">npolint;</code> </td> <td> U+02A14 </td> <td> <span class="glyph" title="">&#10772;</span> </td> <tr id="entity-npr"><td> <code title="">npr;</code> </td> <td> U+02280 </td> <td> <span class="glyph" title="">&#8832;</span> </td> <tr id="entity-nprcue"><td> <code title="">nprcue;</code> </td> <td> U+022E0 </td> <td> <span class="glyph" title="">&#8928;</span> </td> <tr id="entity-npre"><td> <code title="">npre;</code> </td> <td> U+02AAF U+00338 </td> <td> <span class="glyph compound" title="">&#10927;&#824;</span> </td> <tr id="entity-nprec"><td> <code title="">nprec;</code> </td> <td> U+02280 </td> <td> <span class="glyph" title="">&#8832;</span> </td> <tr id="entity-npreceq"><td> <code title="">npreceq;</code> </td> <td> U+02AAF U+00338 </td> <td> <span class="glyph compound" title="">&#10927;&#824;</span> </td> <tr id="entity-nrArr"><td> <code title="">nrArr;</code> </td> <td> U+021CF </td> <td> <span class="glyph" title="">&#8655;</span> </td> <tr id="entity-nrarr"><td> <code title="">nrarr;</code> </td> <td> U+0219B </td> <td> <span class="glyph" title="">&#8603;</span> </td> <tr id="entity-nrarrc"><td> <code title="">nrarrc;</code> </td> <td> U+02933 U+00338 </td> <td> <span class="glyph compound" title="">&#10547;&#824;</span> </td> <tr id="entity-nrarrw"><td> <code title="">nrarrw;</code> </td> <td> U+0219D U+00338 </td> <td> <span class="glyph compound" title="">&#8605;&#824;</span> </td> <tr id="entity-nrightarrow"><td> <code title="">nrightarrow;</code> </td> <td> U+0219B </td> <td> <span class="glyph" title="">&#8603;</span> </td> <tr id="entity-nrtri"><td> <code title="">nrtri;</code> </td> <td> U+022EB </td> <td> <span class="glyph" title="">&#8939;</span> </td> <tr id="entity-nrtrie"><td> <code title="">nrtrie;</code> </td> <td> U+022ED </td> <td> <span class="glyph" title="">&#8941;</span> </td> <tr id="entity-nsc"><td> <code title="">nsc;</code> </td> <td> U+02281 </td> <td> <span class="glyph" title="">&#8833;</span> </td> <tr id="entity-nsccue"><td> <code title="">nsccue;</code> </td> <td> U+022E1 </td> <td> <span class="glyph" title="">&#8929;</span> </td> <tr id="entity-nsce"><td> <code title="">nsce;</code> </td> <td> U+02AB0 U+00338 </td> <td> <span class="glyph compound" title="">&#10928;&#824;</span> </td> <tr id="entity-nscr"><td> <code title="">nscr;</code> </td> <td> U+1D4C3 </td> <td> <span class="glyph" title="">&#120003;</span> </td> <tr id="entity-nshortmid"><td> <code title="">nshortmid;</code> </td> <td> U+02224 </td> <td> <span class="glyph" title="">&#8740;</span> </td> <tr id="entity-nshortparallel"><td> <code title="">nshortparallel;</code> </td> <td> U+02226 </td> <td> <span class="glyph" title="">&#8742;</span> </td> <tr id="entity-nsim"><td> <code title="">nsim;</code> </td> <td> U+02241 </td> <td> <span class="glyph" title="">&#8769;</span> </td> <tr id="entity-nsime"><td> <code title="">nsime;</code> </td> <td> U+02244 </td> <td> <span class="glyph" title="">&#8772;</span> </td> <tr id="entity-nsimeq"><td> <code title="">nsimeq;</code> </td> <td> U+02244 </td> <td> <span class="glyph" title="">&#8772;</span> </td> <tr id="entity-nsmid"><td> <code title="">nsmid;</code> </td> <td> U+02224 </td> <td> <span class="glyph" title="">&#8740;</span> </td> <tr id="entity-nspar"><td> <code title="">nspar;</code> </td> <td> U+02226 </td> <td> <span class="glyph" title="">&#8742;</span> </td> <tr id="entity-nsqsube"><td> <code title="">nsqsube;</code> </td> <td> U+022E2 </td> <td> <span class="glyph" title="">&#8930;</span> </td> <tr id="entity-nsqsupe"><td> <code title="">nsqsupe;</code> </td> <td> U+022E3 </td> <td> <span class="glyph" title="">&#8931;</span> </td> <tr id="entity-nsub"><td> <code title="">nsub;</code> </td> <td> U+02284 </td> <td> <span class="glyph" title="">&nsub;</span> </td> <tr id="entity-nsubE"><td> <code title="">nsubE;</code> </td> <td> U+02AC5 U+00338 </td> <td> <span class="glyph compound" title="">&#10949;&#824;</span> </td> <tr id="entity-nsube"><td> <code title="">nsube;</code> </td> <td> U+02288 </td> <td> <span class="glyph" title="">&#8840;</span> </td> <tr id="entity-nsubset"><td> <code title="">nsubset;</code> </td> <td> U+02282 U+020D2 </td> <td> <span class="glyph compound" title="">&sub;&#8402;</span> </td> <tr id="entity-nsubseteq"><td> <code title="">nsubseteq;</code> </td> <td> U+02288 </td> <td> <span class="glyph" title="">&#8840;</span> </td> <tr id="entity-nsubseteqq"><td> <code title="">nsubseteqq;</code> </td> <td> U+02AC5 U+00338 </td> <td> <span class="glyph compound" title="">&#10949;&#824;</span> </td> <tr id="entity-nsucc"><td> <code title="">nsucc;</code> </td> <td> U+02281 </td> <td> <span class="glyph" title="">&#8833;</span> </td> <tr id="entity-nsucceq"><td> <code title="">nsucceq;</code> </td> <td> U+02AB0 U+00338 </td> <td> <span class="glyph compound" title="">&#10928;&#824;</span> </td> <tr id="entity-nsup"><td> <code title="">nsup;</code> </td> <td> U+02285 </td> <td> <span class="glyph" title="">&#8837;</span> </td> <tr id="entity-nsupE"><td> <code title="">nsupE;</code> </td> <td> U+02AC6 U+00338 </td> <td> <span class="glyph compound" title="">&#10950;&#824;</span> </td> <tr id="entity-nsupe"><td> <code title="">nsupe;</code> </td> <td> U+02289 </td> <td> <span class="glyph" title="">&#8841;</span> </td> <tr id="entity-nsupset"><td> <code title="">nsupset;</code> </td> <td> U+02283 U+020D2 </td> <td> <span class="glyph compound" title="">&sup;&#8402;</span> </td> <tr id="entity-nsupseteq"><td> <code title="">nsupseteq;</code> </td> <td> U+02289 </td> <td> <span class="glyph" title="">&#8841;</span> </td> <tr id="entity-nsupseteqq"><td> <code title="">nsupseteqq;</code> </td> <td> U+02AC6 U+00338 </td> <td> <span class="glyph compound" title="">&#10950;&#824;</span> </td> <tr id="entity-ntgl"><td> <code title="">ntgl;</code> </td> <td> U+02279 </td> <td> <span class="glyph" title="">&#8825;</span> </td> <tr id="entity-ntilde"><td> <code title="">ntilde;</code> </td> <td> U+000F1 </td> <td> <span class="glyph" title="">&ntilde;</span> </td> <tr id="entity-ntlg"><td> <code title="">ntlg;</code> </td> <td> U+02278 </td> <td> <span class="glyph" title="">&#8824;</span> </td> <tr id="entity-ntriangleleft"><td> <code title="">ntriangleleft;</code> </td> <td> U+022EA </td> <td> <span class="glyph" title="">&#8938;</span> </td> <tr id="entity-ntrianglelefteq"><td> <code title="">ntrianglelefteq;</code> </td> <td> U+022EC </td> <td> <span class="glyph" title="">&#8940;</span> </td> <tr id="entity-ntriangleright"><td> <code title="">ntriangleright;</code> </td> <td> U+022EB </td> <td> <span class="glyph" title="">&#8939;</span> </td> <tr id="entity-ntrianglerighteq"><td> <code title="">ntrianglerighteq;</code> </td> <td> U+022ED </td> <td> <span class="glyph" title="">&#8941;</span> </td> <tr id="entity-nu"><td> <code title="">nu;</code> </td> <td> U+003BD </td> <td> <span class="glyph" title="">&nu;</span> </td> <tr id="entity-num"><td> <code title="">num;</code> </td> <td> U+00023 </td> <td> <span class="glyph" title="">#</span> </td> <tr id="entity-numero"><td> <code title="">numero;</code> </td> <td> U+02116 </td> <td> <span class="glyph" title="">&#8470;</span> </td> <tr id="entity-numsp"><td> <code title="">numsp;</code> </td> <td> U+02007 </td> <td> <span class="glyph" title="">&#8199;</span> </td> <tr id="entity-nvDash"><td> <code title="">nvDash;</code> </td> <td> U+022AD </td> <td> <span class="glyph" title="">&#8877;</span> </td> <tr id="entity-nvHarr"><td> <code title="">nvHarr;</code> </td> <td> U+02904 </td> <td> <span class="glyph" title="">&#10500;</span> </td> <tr id="entity-nvap"><td> <code title="">nvap;</code> </td> <td> U+0224D U+020D2 </td> <td> <span class="glyph compound" title="">&#8781;&#8402;</span> </td> <tr id="entity-nvdash"><td> <code title="">nvdash;</code> </td> <td> U+022AC </td> <td> <span class="glyph" title="">&#8876;</span> </td> <tr id="entity-nvge"><td> <code title="">nvge;</code> </td> <td> U+02265 U+020D2 </td> <td> <span class="glyph compound" title="">&ge;&#8402;</span> </td> <tr id="entity-nvgt"><td> <code title="">nvgt;</code> </td> <td> U+0003E U+020D2 </td> <td> <span class="glyph compound" title="">&gt;&#8402;</span> </td> <tr id="entity-nvinfin"><td> <code title="">nvinfin;</code> </td> <td> U+029DE </td> <td> <span class="glyph" title="">&#10718;</span> </td> <tr id="entity-nvlArr"><td> <code title="">nvlArr;</code> </td> <td> U+02902 </td> <td> <span class="glyph" title="">&#10498;</span> </td> <tr id="entity-nvle"><td> <code title="">nvle;</code> </td> <td> U+02264 U+020D2 </td> <td> <span class="glyph compound" title="">&le;&#8402;</span> </td> <tr id="entity-nvlt"><td> <code title="">nvlt;</code> </td> <td> U+0003C U+020D2 </td> <td> <span class="glyph compound" title="">&lt;&#8402;</span> </td> <tr id="entity-nvltrie"><td> <code title="">nvltrie;</code> </td> <td> U+022B4 U+020D2 </td> <td> <span class="glyph compound" title="">&#8884;&#8402;</span> </td> <tr id="entity-nvrArr"><td> <code title="">nvrArr;</code> </td> <td> U+02903 </td> <td> <span class="glyph" title="">&#10499;</span> </td> <tr id="entity-nvrtrie"><td> <code title="">nvrtrie;</code> </td> <td> U+022B5 U+020D2 </td> <td> <span class="glyph compound" title="">&#8885;&#8402;</span> </td> <tr id="entity-nvsim"><td> <code title="">nvsim;</code> </td> <td> U+0223C U+020D2 </td> <td> <span class="glyph compound" title="">&sim;&#8402;</span> </td> <tr id="entity-nwArr"><td> <code title="">nwArr;</code> </td> <td> U+021D6 </td> <td> <span class="glyph" title="">&#8662;</span> </td> <tr id="entity-nwarhk"><td> <code title="">nwarhk;</code> </td> <td> U+02923 </td> <td> <span class="glyph" title="">&#10531;</span> </td> <tr id="entity-nwarr"><td> <code title="">nwarr;</code> </td> <td> U+02196 </td> <td> <span class="glyph" title="">&#8598;</span> </td> <tr id="entity-nwarrow"><td> <code title="">nwarrow;</code> </td> <td> U+02196 </td> <td> <span class="glyph" title="">&#8598;</span> </td> <tr id="entity-nwnear"><td> <code title="">nwnear;</code> </td> <td> U+02927 </td> <td> <span class="glyph" title="">&#10535;</span> </td> <tr id="entity-oS"><td> <code title="">oS;</code> </td> <td> U+024C8 </td> <td> <span class="glyph" title="">&#9416;</span> </td> <tr id="entity-oacute"><td> <code title="">oacute;</code> </td> <td> U+000F3 </td> <td> <span class="glyph" title="">&oacute;</span> </td> <tr id="entity-oast"><td> <code title="">oast;</code> </td> <td> U+0229B </td> <td> <span class="glyph" title="">&#8859;</span> </td> <tr id="entity-ocir"><td> <code title="">ocir;</code> </td> <td> U+0229A </td> <td> <span class="glyph" title="">&#8858;</span> </td> <tr id="entity-ocirc"><td> <code title="">ocirc;</code> </td> <td> U+000F4 </td> <td> <span class="glyph" title="">&ocirc;</span> </td> <tr id="entity-ocy"><td> <code title="">ocy;</code> </td> <td> U+0043E </td> <td> <span class="glyph" title="">&#1086;</span> </td> <tr id="entity-odash"><td> <code title="">odash;</code> </td> <td> U+0229D </td> <td> <span class="glyph" title="">&#8861;</span> </td> <tr id="entity-odblac"><td> <code title="">odblac;</code> </td> <td> U+00151 </td> <td> <span class="glyph" title="">&#337;</span> </td> <tr id="entity-odiv"><td> <code title="">odiv;</code> </td> <td> U+02A38 </td> <td> <span class="glyph" title="">&#10808;</span> </td> <tr id="entity-odot"><td> <code title="">odot;</code> </td> <td> U+02299 </td> <td> <span class="glyph" title="">&#8857;</span> </td> <tr id="entity-odsold"><td> <code title="">odsold;</code> </td> <td> U+029BC </td> <td> <span class="glyph" title="">&#10684;</span> </td> <tr id="entity-oelig"><td> <code title="">oelig;</code> </td> <td> U+00153 </td> <td> <span class="glyph" title="">&oelig;</span> </td> <tr id="entity-ofcir"><td> <code title="">ofcir;</code> </td> <td> U+029BF </td> <td> <span class="glyph" title="">&#10687;</span> </td> <tr id="entity-ofr"><td> <code title="">ofr;</code> </td> <td> U+1D52C </td> <td> <span class="glyph" title="">&#120108;</span> </td> <tr id="entity-ogon"><td> <code title="">ogon;</code> </td> <td> U+002DB </td> <td> <span class="glyph" title="">&#731;</span> </td> <tr id="entity-ograve"><td> <code title="">ograve;</code> </td> <td> U+000F2 </td> <td> <span class="glyph" title="">&ograve;</span> </td> <tr id="entity-ogt"><td> <code title="">ogt;</code> </td> <td> U+029C1 </td> <td> <span class="glyph" title="">&#10689;</span> </td> <tr id="entity-ohbar"><td> <code title="">ohbar;</code> </td> <td> U+029B5 </td> <td> <span class="glyph" title="">&#10677;</span> </td> <tr id="entity-ohm"><td> <code title="">ohm;</code> </td> <td> U+003A9 </td> <td> <span class="glyph" title="">&Omega;</span> </td> <tr id="entity-oint"><td> <code title="">oint;</code> </td> <td> U+0222E </td> <td> <span class="glyph" title="">&#8750;</span> </td> <tr id="entity-olarr"><td> <code title="">olarr;</code> </td> <td> U+021BA </td> <td> <span class="glyph" title="">&#8634;</span> </td> <tr id="entity-olcir"><td> <code title="">olcir;</code> </td> <td> U+029BE </td> <td> <span class="glyph" title="">&#10686;</span> </td> <tr id="entity-olcross"><td> <code title="">olcross;</code> </td> <td> U+029BB </td> <td> <span class="glyph" title="">&#10683;</span> </td> <tr id="entity-oline"><td> <code title="">oline;</code> </td> <td> U+0203E </td> <td> <span class="glyph" title="">&oline;</span> </td> <tr id="entity-olt"><td> <code title="">olt;</code> </td> <td> U+029C0 </td> <td> <span class="glyph" title="">&#10688;</span> </td> <tr id="entity-omacr"><td> <code title="">omacr;</code> </td> <td> U+0014D </td> <td> <span class="glyph" title="">&#333;</span> </td> <tr id="entity-omega"><td> <code title="">omega;</code> </td> <td> U+003C9 </td> <td> <span class="glyph" title="">&omega;</span> </td> <tr id="entity-omicron"><td> <code title="">omicron;</code> </td> <td> U+003BF </td> <td> <span class="glyph" title="">&omicron;</span> </td> <tr id="entity-omid"><td> <code title="">omid;</code> </td> <td> U+029B6 </td> <td> <span class="glyph" title="">&#10678;</span> </td> <tr id="entity-ominus"><td> <code title="">ominus;</code> </td> <td> U+02296 </td> <td> <span class="glyph" title="">&#8854;</span> </td> <tr id="entity-oopf"><td> <code title="">oopf;</code> </td> <td> U+1D560 </td> <td> <span class="glyph" title="">&#120160;</span> </td> <tr id="entity-opar"><td> <code title="">opar;</code> </td> <td> U+029B7 </td> <td> <span class="glyph" title="">&#10679;</span> </td> <tr id="entity-operp"><td> <code title="">operp;</code> </td> <td> U+029B9 </td> <td> <span class="glyph" title="">&#10681;</span> </td> <tr id="entity-oplus"><td> <code title="">oplus;</code> </td> <td> U+02295 </td> <td> <span class="glyph" title="">&oplus;</span> </td> <tr id="entity-or"><td> <code title="">or;</code> </td> <td> U+02228 </td> <td> <span class="glyph" title="">&or;</span> </td> <tr id="entity-orarr"><td> <code title="">orarr;</code> </td> <td> U+021BB </td> <td> <span class="glyph" title="">&#8635;</span> </td> <tr id="entity-ord"><td> <code title="">ord;</code> </td> <td> U+02A5D </td> <td> <span class="glyph" title="">&#10845;</span> </td> <tr id="entity-order"><td> <code title="">order;</code> </td> <td> U+02134 </td> <td> <span class="glyph" title="">&#8500;</span> </td> <tr id="entity-orderof"><td> <code title="">orderof;</code> </td> <td> U+02134 </td> <td> <span class="glyph" title="">&#8500;</span> </td> <tr id="entity-ordf"><td> <code title="">ordf;</code> </td> <td> U+000AA </td> <td> <span class="glyph" title="">&ordf;</span> </td> <tr id="entity-ordm"><td> <code title="">ordm;</code> </td> <td> U+000BA </td> <td> <span class="glyph" title="">&ordm;</span> </td> <tr id="entity-origof"><td> <code title="">origof;</code> </td> <td> U+022B6 </td> <td> <span class="glyph" title="">&#8886;</span> </td> <tr id="entity-oror"><td> <code title="">oror;</code> </td> <td> U+02A56 </td> <td> <span class="glyph" title="">&#10838;</span> </td> <tr id="entity-orslope"><td> <code title="">orslope;</code> </td> <td> U+02A57 </td> <td> <span class="glyph" title="">&#10839;</span> </td> <tr id="entity-orv"><td> <code title="">orv;</code> </td> <td> U+02A5B </td> <td> <span class="glyph" title="">&#10843;</span> </td> <tr id="entity-oscr"><td> <code title="">oscr;</code> </td> <td> U+02134 </td> <td> <span class="glyph" title="">&#8500;</span> </td> <tr id="entity-oslash"><td> <code title="">oslash;</code> </td> <td> U+000F8 </td> <td> <span class="glyph" title="">&oslash;</span> </td> <tr id="entity-osol"><td> <code title="">osol;</code> </td> <td> U+02298 </td> <td> <span class="glyph" title="">&#8856;</span> </td> <tr id="entity-otilde"><td> <code title="">otilde;</code> </td> <td> U+000F5 </td> <td> <span class="glyph" title="">&otilde;</span> </td> <tr id="entity-otimes"><td> <code title="">otimes;</code> </td> <td> U+02297 </td> <td> <span class="glyph" title="">&otimes;</span> </td> <tr id="entity-otimesas"><td> <code title="">otimesas;</code> </td> <td> U+02A36 </td> <td> <span class="glyph" title="">&#10806;</span> </td> <tr id="entity-ouml"><td> <code title="">ouml;</code> </td> <td> U+000F6 </td> <td> <span class="glyph" title="">&ouml;</span> </td> <tr id="entity-ovbar"><td> <code title="">ovbar;</code> </td> <td> U+0233D </td> <td> <span class="glyph" title="">&#9021;</span> </td> <tr id="entity-par"><td> <code title="">par;</code> </td> <td> U+02225 </td> <td> <span class="glyph" title="">&#8741;</span> </td> <tr id="entity-para"><td> <code title="">para;</code> </td> <td> U+000B6 </td> <td> <span class="glyph" title="">&para;</span> </td> <tr id="entity-parallel"><td> <code title="">parallel;</code> </td> <td> U+02225 </td> <td> <span class="glyph" title="">&#8741;</span> </td> <tr id="entity-parsim"><td> <code title="">parsim;</code> </td> <td> U+02AF3 </td> <td> <span class="glyph" title="">&#10995;</span> </td> <tr id="entity-parsl"><td> <code title="">parsl;</code> </td> <td> U+02AFD </td> <td> <span class="glyph" title="">&#11005;</span> </td> <tr id="entity-part"><td> <code title="">part;</code> </td> <td> U+02202 </td> <td> <span class="glyph" title="">&part;</span> </td> <tr id="entity-pcy"><td> <code title="">pcy;</code> </td> <td> U+0043F </td> <td> <span class="glyph" title="">&#1087;</span> </td> <tr id="entity-percnt"><td> <code title="">percnt;</code> </td> <td> U+00025 </td> <td> <span class="glyph" title="">%</span> </td> <tr id="entity-period"><td> <code title="">period;</code> </td> <td> U+0002E </td> <td> <span class="glyph" title="">.</span> </td> <tr id="entity-permil"><td> <code title="">permil;</code> </td> <td> U+02030 </td> <td> <span class="glyph" title="">&permil;</span> </td> <tr id="entity-perp"><td> <code title="">perp;</code> </td> <td> U+022A5 </td> <td> <span class="glyph" title="">&perp;</span> </td> <tr id="entity-pertenk"><td> <code title="">pertenk;</code> </td> <td> U+02031 </td> <td> <span class="glyph" title="">&#8241;</span> </td> <tr id="entity-pfr"><td> <code title="">pfr;</code> </td> <td> U+1D52D </td> <td> <span class="glyph" title="">&#120109;</span> </td> <tr id="entity-phi"><td> <code title="">phi;</code> </td> <td> U+003C6 </td> <td> <span class="glyph" title="">&phi;</span> </td> <tr id="entity-phiv"><td> <code title="">phiv;</code> </td> <td> U+003D5 </td> <td> <span class="glyph" title="">&#981;</span> </td> <tr id="entity-phmmat"><td> <code title="">phmmat;</code> </td> <td> U+02133 </td> <td> <span class="glyph" title="">&#8499;</span> </td> <tr id="entity-phone"><td> <code title="">phone;</code> </td> <td> U+0260E </td> <td> <span class="glyph" title="">&#9742;</span> </td> <tr id="entity-pi"><td> <code title="">pi;</code> </td> <td> U+003C0 </td> <td> <span class="glyph" title="">&pi;</span> </td> <tr id="entity-pitchfork"><td> <code title="">pitchfork;</code> </td> <td> U+022D4 </td> <td> <span class="glyph" title="">&#8916;</span> </td> <tr id="entity-piv"><td> <code title="">piv;</code> </td> <td> U+003D6 </td> <td> <span class="glyph" title="">&piv;</span> </td> <tr id="entity-planck"><td> <code title="">planck;</code> </td> <td> U+0210F </td> <td> <span class="glyph" title="">&#8463;</span> </td> <tr id="entity-planckh"><td> <code title="">planckh;</code> </td> <td> U+0210E </td> <td> <span class="glyph" title="">&#8462;</span> </td> <tr id="entity-plankv"><td> <code title="">plankv;</code> </td> <td> U+0210F </td> <td> <span class="glyph" title="">&#8463;</span> </td> <tr id="entity-plus"><td> <code title="">plus;</code> </td> <td> U+0002B </td> <td> <span class="glyph" title="">+</span> </td> <tr id="entity-plusacir"><td> <code title="">plusacir;</code> </td> <td> U+02A23 </td> <td> <span class="glyph" title="">&#10787;</span> </td> <tr id="entity-plusb"><td> <code title="">plusb;</code> </td> <td> U+0229E </td> <td> <span class="glyph" title="">&#8862;</span> </td> <tr id="entity-pluscir"><td> <code title="">pluscir;</code> </td> <td> U+02A22 </td> <td> <span class="glyph" title="">&#10786;</span> </td> <tr id="entity-plusdo"><td> <code title="">plusdo;</code> </td> <td> U+02214 </td> <td> <span class="glyph" title="">&#8724;</span> </td> <tr id="entity-plusdu"><td> <code title="">plusdu;</code> </td> <td> U+02A25 </td> <td> <span class="glyph" title="">&#10789;</span> </td> <tr id="entity-pluse"><td> <code title="">pluse;</code> </td> <td> U+02A72 </td> <td> <span class="glyph" title="">&#10866;</span> </td> <tr id="entity-plusmn"><td> <code title="">plusmn;</code> </td> <td> U+000B1 </td> <td> <span class="glyph" title="">&plusmn;</span> </td> <tr id="entity-plussim"><td> <code title="">plussim;</code> </td> <td> U+02A26 </td> <td> <span class="glyph" title="">&#10790;</span> </td> <tr id="entity-plustwo"><td> <code title="">plustwo;</code> </td> <td> U+02A27 </td> <td> <span class="glyph" title="">&#10791;</span> </td> <tr id="entity-pm"><td> <code title="">pm;</code> </td> <td> U+000B1 </td> <td> <span class="glyph" title="">&plusmn;</span> </td> <tr id="entity-pointint"><td> <code title="">pointint;</code> </td> <td> U+02A15 </td> <td> <span class="glyph" title="">&#10773;</span> </td> <tr id="entity-popf"><td> <code title="">popf;</code> </td> <td> U+1D561 </td> <td> <span class="glyph" title="">&#120161;</span> </td> <tr id="entity-pound"><td> <code title="">pound;</code> </td> <td> U+000A3 </td> <td> <span class="glyph" title="">&pound;</span> </td> <tr id="entity-pr"><td> <code title="">pr;</code> </td> <td> U+0227A </td> <td> <span class="glyph" title="">&#8826;</span> </td> <tr id="entity-prE"><td> <code title="">prE;</code> </td> <td> U+02AB3 </td> <td> <span class="glyph" title="">&#10931;</span> </td> <tr id="entity-prap"><td> <code title="">prap;</code> </td> <td> U+02AB7 </td> <td> <span class="glyph" title="">&#10935;</span> </td> <tr id="entity-prcue"><td> <code title="">prcue;</code> </td> <td> U+0227C </td> <td> <span class="glyph" title="">&#8828;</span> </td> <tr id="entity-pre"><td> <code title="">pre;</code> </td> <td> U+02AAF </td> <td> <span class="glyph" title="">&#10927;</span> </td> <tr id="entity-prec"><td> <code title="">prec;</code> </td> <td> U+0227A </td> <td> <span class="glyph" title="">&#8826;</span> </td> <tr id="entity-precapprox"><td> <code title="">precapprox;</code> </td> <td> U+02AB7 </td> <td> <span class="glyph" title="">&#10935;</span> </td> <tr id="entity-preccurlyeq"><td> <code title="">preccurlyeq;</code> </td> <td> U+0227C </td> <td> <span class="glyph" title="">&#8828;</span> </td> <tr id="entity-preceq"><td> <code title="">preceq;</code> </td> <td> U+02AAF </td> <td> <span class="glyph" title="">&#10927;</span> </td> <tr id="entity-precnapprox"><td> <code title="">precnapprox;</code> </td> <td> U+02AB9 </td> <td> <span class="glyph" title="">&#10937;</span> </td> <tr id="entity-precneqq"><td> <code title="">precneqq;</code> </td> <td> U+02AB5 </td> <td> <span class="glyph" title="">&#10933;</span> </td> <tr id="entity-precnsim"><td> <code title="">precnsim;</code> </td> <td> U+022E8 </td> <td> <span class="glyph" title="">&#8936;</span> </td> <tr id="entity-precsim"><td> <code title="">precsim;</code> </td> <td> U+0227E </td> <td> <span class="glyph" title="">&#8830;</span> </td> <tr id="entity-prime"><td> <code title="">prime;</code> </td> <td> U+02032 </td> <td> <span class="glyph" title="">&prime;</span> </td> <tr id="entity-primes"><td> <code title="">primes;</code> </td> <td> U+02119 </td> <td> <span class="glyph" title="">&#8473;</span> </td> <tr id="entity-prnE"><td> <code title="">prnE;</code> </td> <td> U+02AB5 </td> <td> <span class="glyph" title="">&#10933;</span> </td> <tr id="entity-prnap"><td> <code title="">prnap;</code> </td> <td> U+02AB9 </td> <td> <span class="glyph" title="">&#10937;</span> </td> <tr id="entity-prnsim"><td> <code title="">prnsim;</code> </td> <td> U+022E8 </td> <td> <span class="glyph" title="">&#8936;</span> </td> <tr id="entity-prod"><td> <code title="">prod;</code> </td> <td> U+0220F </td> <td> <span class="glyph" title="">&prod;</span> </td> <tr id="entity-profalar"><td> <code title="">profalar;</code> </td> <td> U+0232E </td> <td> <span class="glyph" title="">&#9006;</span> </td> <tr id="entity-profline"><td> <code title="">profline;</code> </td> <td> U+02312 </td> <td> <span class="glyph" title="">&#8978;</span> </td> <tr id="entity-profsurf"><td> <code title="">profsurf;</code> </td> <td> U+02313 </td> <td> <span class="glyph" title="">&#8979;</span> </td> <tr id="entity-prop"><td> <code title="">prop;</code> </td> <td> U+0221D </td> <td> <span class="glyph" title="">&prop;</span> </td> <tr id="entity-propto"><td> <code title="">propto;</code> </td> <td> U+0221D </td> <td> <span class="glyph" title="">&prop;</span> </td> <tr id="entity-prsim"><td> <code title="">prsim;</code> </td> <td> U+0227E </td> <td> <span class="glyph" title="">&#8830;</span> </td> <tr id="entity-prurel"><td> <code title="">prurel;</code> </td> <td> U+022B0 </td> <td> <span class="glyph" title="">&#8880;</span> </td> <tr id="entity-pscr"><td> <code title="">pscr;</code> </td> <td> U+1D4C5 </td> <td> <span class="glyph" title="">&#120005;</span> </td> <tr id="entity-psi"><td> <code title="">psi;</code> </td> <td> U+003C8 </td> <td> <span class="glyph" title="">&psi;</span> </td> <tr id="entity-puncsp"><td> <code title="">puncsp;</code> </td> <td> U+02008 </td> <td> <span class="glyph" title="">&#8200;</span> </td> <tr id="entity-qfr"><td> <code title="">qfr;</code> </td> <td> U+1D52E </td> <td> <span class="glyph" title="">&#120110;</span> </td> <tr id="entity-qint"><td> <code title="">qint;</code> </td> <td> U+02A0C </td> <td> <span class="glyph" title="">&#10764;</span> </td> <tr id="entity-qopf"><td> <code title="">qopf;</code> </td> <td> U+1D562 </td> <td> <span class="glyph" title="">&#120162;</span> </td> <tr id="entity-qprime"><td> <code title="">qprime;</code> </td> <td> U+02057 </td> <td> <span class="glyph" title="">&#8279;</span> </td> <tr id="entity-qscr"><td> <code title="">qscr;</code> </td> <td> U+1D4C6 </td> <td> <span class="glyph" title="">&#120006;</span> </td> <tr id="entity-quaternions"><td> <code title="">quaternions;</code> </td> <td> U+0210D </td> <td> <span class="glyph" title="">&#8461;</span> </td> <tr id="entity-quatint"><td> <code title="">quatint;</code> </td> <td> U+02A16 </td> <td> <span class="glyph" title="">&#10774;</span> </td> <tr id="entity-quest"><td> <code title="">quest;</code> </td> <td> U+0003F </td> <td> <span class="glyph" title="">?</span> </td> <tr id="entity-questeq"><td> <code title="">questeq;</code> </td> <td> U+0225F </td> <td> <span class="glyph" title="">&#8799;</span> </td> <tr id="entity-quot"><td> <code title="">quot;</code> </td> <td> U+00022 </td> <td> <span class="glyph" title="">"</span> </td> <tr id="entity-rAarr"><td> <code title="">rAarr;</code> </td> <td> U+021DB </td> <td> <span class="glyph" title="">&#8667;</span> </td> <tr id="entity-rArr"><td> <code title="">rArr;</code> </td> <td> U+021D2 </td> <td> <span class="glyph" title="">&rArr;</span> </td> <tr id="entity-rAtail"><td> <code title="">rAtail;</code> </td> <td> U+0291C </td> <td> <span class="glyph" title="">&#10524;</span> </td> <tr id="entity-rBarr"><td> <code title="">rBarr;</code> </td> <td> U+0290F </td> <td> <span class="glyph" title="">&#10511;</span> </td> <tr id="entity-rHar"><td> <code title="">rHar;</code> </td> <td> U+02964 </td> <td> <span class="glyph" title="">&#10596;</span> </td> <tr id="entity-race"><td> <code title="">race;</code> </td> <td> U+0223D U+00331 </td> <td> <span class="glyph compound" title="">&#8765;&#817;</span> </td> <tr id="entity-racute"><td> <code title="">racute;</code> </td> <td> U+00155 </td> <td> <span class="glyph" title="">&#341;</span> </td> <tr id="entity-radic"><td> <code title="">radic;</code> </td> <td> U+0221A </td> <td> <span class="glyph" title="">&radic;</span> </td> <tr id="entity-raemptyv"><td> <code title="">raemptyv;</code> </td> <td> U+029B3 </td> <td> <span class="glyph" title="">&#10675;</span> </td> <tr id="entity-rang"><td> <code title="">rang;</code> </td> <td> U+027E9 </td> <td> <span class="glyph" title="">&#9002;</span> </td> <tr id="entity-rangd"><td> <code title="">rangd;</code> </td> <td> U+02992 </td> <td> <span class="glyph" title="">&#10642;</span> </td> <tr id="entity-range"><td> <code title="">range;</code> </td> <td> U+029A5 </td> <td> <span class="glyph" title="">&#10661;</span> </td> <tr id="entity-rangle"><td> <code title="">rangle;</code> </td> <td> U+027E9 </td> <td> <span class="glyph" title="">&#9002;</span> </td> <tr id="entity-raquo"><td> <code title="">raquo;</code> </td> <td> U+000BB </td> <td> <span class="glyph" title="">&raquo;</span> </td> <tr id="entity-rarr"><td> <code title="">rarr;</code> </td> <td> U+02192 </td> <td> <span class="glyph" title="">&rarr;</span> </td> <tr id="entity-rarrap"><td> <code title="">rarrap;</code> </td> <td> U+02975 </td> <td> <span class="glyph" title="">&#10613;</span> </td> <tr id="entity-rarrb"><td> <code title="">rarrb;</code> </td> <td> U+021E5 </td> <td> <span class="glyph" title="">&#8677;</span> </td> <tr id="entity-rarrbfs"><td> <code title="">rarrbfs;</code> </td> <td> U+02920 </td> <td> <span class="glyph" title="">&#10528;</span> </td> <tr id="entity-rarrc"><td> <code title="">rarrc;</code> </td> <td> U+02933 </td> <td> <span class="glyph" title="">&#10547;</span> </td> <tr id="entity-rarrfs"><td> <code title="">rarrfs;</code> </td> <td> U+0291E </td> <td> <span class="glyph" title="">&#10526;</span> </td> <tr id="entity-rarrhk"><td> <code title="">rarrhk;</code> </td> <td> U+021AA </td> <td> <span class="glyph" title="">&#8618;</span> </td> <tr id="entity-rarrlp"><td> <code title="">rarrlp;</code> </td> <td> U+021AC </td> <td> <span class="glyph" title="">&#8620;</span> </td> <tr id="entity-rarrpl"><td> <code title="">rarrpl;</code> </td> <td> U+02945 </td> <td> <span class="glyph" title="">&#10565;</span> </td> <tr id="entity-rarrsim"><td> <code title="">rarrsim;</code> </td> <td> U+02974 </td> <td> <span class="glyph" title="">&#10612;</span> </td> <tr id="entity-rarrtl"><td> <code title="">rarrtl;</code> </td> <td> U+021A3 </td> <td> <span class="glyph" title="">&#8611;</span> </td> <tr id="entity-rarrw"><td> <code title="">rarrw;</code> </td> <td> U+0219D </td> <td> <span class="glyph" title="">&#8605;</span> </td> <tr id="entity-ratail"><td> <code title="">ratail;</code> </td> <td> U+0291A </td> <td> <span class="glyph" title="">&#10522;</span> </td> <tr id="entity-ratio"><td> <code title="">ratio;</code> </td> <td> U+02236 </td> <td> <span class="glyph" title="">&#8758;</span> </td> <tr id="entity-rationals"><td> <code title="">rationals;</code> </td> <td> U+0211A </td> <td> <span class="glyph" title="">&#8474;</span> </td> <tr id="entity-rbarr"><td> <code title="">rbarr;</code> </td> <td> U+0290D </td> <td> <span class="glyph" title="">&#10509;</span> </td> <tr id="entity-rbbrk"><td> <code title="">rbbrk;</code> </td> <td> U+02773 </td> <td> <span class="glyph" title="">&#10099;</span> </td> <tr id="entity-rbrace"><td> <code title="">rbrace;</code> </td> <td> U+0007D </td> <td> <span class="glyph" title="">}</span> </td> <tr id="entity-rbrack"><td> <code title="">rbrack;</code> </td> <td> U+0005D </td> <td> <span class="glyph" title="">]</span> </td> <tr id="entity-rbrke"><td> <code title="">rbrke;</code> </td> <td> U+0298C </td> <td> <span class="glyph" title="">&#10636;</span> </td> <tr id="entity-rbrksld"><td> <code title="">rbrksld;</code> </td> <td> U+0298E </td> <td> <span class="glyph" title="">&#10638;</span> </td> <tr id="entity-rbrkslu"><td> <code title="">rbrkslu;</code> </td> <td> U+02990 </td> <td> <span class="glyph" title="">&#10640;</span> </td> <tr id="entity-rcaron"><td> <code title="">rcaron;</code> </td> <td> U+00159 </td> <td> <span class="glyph" title="">&#345;</span> </td> <tr id="entity-rcedil"><td> <code title="">rcedil;</code> </td> <td> U+00157 </td> <td> <span class="glyph" title="">&#343;</span> </td> <tr id="entity-rceil"><td> <code title="">rceil;</code> </td> <td> U+02309 </td> <td> <span class="glyph" title="">&rceil;</span> </td> <tr id="entity-rcub"><td> <code title="">rcub;</code> </td> <td> U+0007D </td> <td> <span class="glyph" title="">}</span> </td> <tr id="entity-rcy"><td> <code title="">rcy;</code> </td> <td> U+00440 </td> <td> <span class="glyph" title="">&#1088;</span> </td> <tr id="entity-rdca"><td> <code title="">rdca;</code> </td> <td> U+02937 </td> <td> <span class="glyph" title="">&#10551;</span> </td> <tr id="entity-rdldhar"><td> <code title="">rdldhar;</code> </td> <td> U+02969 </td> <td> <span class="glyph" title="">&#10601;</span> </td> <tr id="entity-rdquo"><td> <code title="">rdquo;</code> </td> <td> U+0201D </td> <td> <span class="glyph" title="">&rdquo;</span> </td> <tr id="entity-rdquor"><td> <code title="">rdquor;</code> </td> <td> U+0201D </td> <td> <span class="glyph" title="">&rdquo;</span> </td> <tr id="entity-rdsh"><td> <code title="">rdsh;</code> </td> <td> U+021B3 </td> <td> <span class="glyph" title="">&#8627;</span> </td> <tr id="entity-real"><td> <code title="">real;</code> </td> <td> U+0211C </td> <td> <span class="glyph" title="">&real;</span> </td> <tr id="entity-realine"><td> <code title="">realine;</code> </td> <td> U+0211B </td> <td> <span class="glyph" title="">&#8475;</span> </td> <tr id="entity-realpart"><td> <code title="">realpart;</code> </td> <td> U+0211C </td> <td> <span class="glyph" title="">&real;</span> </td> <tr id="entity-reals"><td> <code title="">reals;</code> </td> <td> U+0211D </td> <td> <span class="glyph" title="">&#8477;</span> </td> <tr id="entity-rect"><td> <code title="">rect;</code> </td> <td> U+025AD </td> <td> <span class="glyph" title="">&#9645;</span> </td> <tr id="entity-reg"><td> <code title="">reg;</code> </td> <td> U+000AE </td> <td> <span class="glyph" title="">&reg;</span> </td> <tr id="entity-rfisht"><td> <code title="">rfisht;</code> </td> <td> U+0297D </td> <td> <span class="glyph" title="">&#10621;</span> </td> <tr id="entity-rfloor"><td> <code title="">rfloor;</code> </td> <td> U+0230B </td> <td> <span class="glyph" title="">&rfloor;</span> </td> <tr id="entity-rfr"><td> <code title="">rfr;</code> </td> <td> U+1D52F </td> <td> <span class="glyph" title="">&#120111;</span> </td> <tr id="entity-rhard"><td> <code title="">rhard;</code> </td> <td> U+021C1 </td> <td> <span class="glyph" title="">&#8641;</span> </td> <tr id="entity-rharu"><td> <code title="">rharu;</code> </td> <td> U+021C0 </td> <td> <span class="glyph" title="">&#8640;</span> </td> <tr id="entity-rharul"><td> <code title="">rharul;</code> </td> <td> U+0296C </td> <td> <span class="glyph" title="">&#10604;</span> </td> <tr id="entity-rho"><td> <code title="">rho;</code> </td> <td> U+003C1 </td> <td> <span class="glyph" title="">&rho;</span> </td> <tr id="entity-rhov"><td> <code title="">rhov;</code> </td> <td> U+003F1 </td> <td> <span class="glyph" title="">&#1009;</span> </td> <tr id="entity-rightarrow"><td> <code title="">rightarrow;</code> </td> <td> U+02192 </td> <td> <span class="glyph" title="">&rarr;</span> </td> <tr id="entity-rightarrowtail"><td> <code title="">rightarrowtail;</code> </td> <td> U+021A3 </td> <td> <span class="glyph" title="">&#8611;</span> </td> <tr id="entity-rightharpoondown"><td> <code title="">rightharpoondown;</code> </td> <td> U+021C1 </td> <td> <span class="glyph" title="">&#8641;</span> </td> <tr id="entity-rightharpoonup"><td> <code title="">rightharpoonup;</code> </td> <td> U+021C0 </td> <td> <span class="glyph" title="">&#8640;</span> </td> <tr id="entity-rightleftarrows"><td> <code title="">rightleftarrows;</code> </td> <td> U+021C4 </td> <td> <span class="glyph" title="">&#8644;</span> </td> <tr id="entity-rightleftharpoons"><td> <code title="">rightleftharpoons;</code> </td> <td> U+021CC </td> <td> <span class="glyph" title="">&#8652;</span> </td> <tr id="entity-rightrightarrows"><td> <code title="">rightrightarrows;</code> </td> <td> U+021C9 </td> <td> <span class="glyph" title="">&#8649;</span> </td> <tr id="entity-rightsquigarrow"><td> <code title="">rightsquigarrow;</code> </td> <td> U+0219D </td> <td> <span class="glyph" title="">&#8605;</span> </td> <tr id="entity-rightthreetimes"><td> <code title="">rightthreetimes;</code> </td> <td> U+022CC </td> <td> <span class="glyph" title="">&#8908;</span> </td> <tr id="entity-ring"><td> <code title="">ring;</code> </td> <td> U+002DA </td> <td> <span class="glyph" title="">&#730;</span> </td> <tr id="entity-risingdotseq"><td> <code title="">risingdotseq;</code> </td> <td> U+02253 </td> <td> <span class="glyph" title="">&#8787;</span> </td> <tr id="entity-rlarr"><td> <code title="">rlarr;</code> </td> <td> U+021C4 </td> <td> <span class="glyph" title="">&#8644;</span> </td> <tr id="entity-rlhar"><td> <code title="">rlhar;</code> </td> <td> U+021CC </td> <td> <span class="glyph" title="">&#8652;</span> </td> <tr id="entity-rlm"><td> <code title="">rlm;</code> </td> <td> U+0200F </td> <td> <span class="glyph" title="">&rlm;</span> </td> <tr id="entity-rmoust"><td> <code title="">rmoust;</code> </td> <td> U+023B1 </td> <td> <span class="glyph" title="">&#9137;</span> </td> <tr id="entity-rmoustache"><td> <code title="">rmoustache;</code> </td> <td> U+023B1 </td> <td> <span class="glyph" title="">&#9137;</span> </td> <tr id="entity-rnmid"><td> <code title="">rnmid;</code> </td> <td> U+02AEE </td> <td> <span class="glyph" title="">&#10990;</span> </td> <tr id="entity-roang"><td> <code title="">roang;</code> </td> <td> U+027ED </td> <td> <span class="glyph" title="">&#10221;</span> </td> <tr id="entity-roarr"><td> <code title="">roarr;</code> </td> <td> U+021FE </td> <td> <span class="glyph" title="">&#8702;</span> </td> <tr id="entity-robrk"><td> <code title="">robrk;</code> </td> <td> U+027E7 </td> <td> <span class="glyph" title="">&#10215;</span> </td> <tr id="entity-ropar"><td> <code title="">ropar;</code> </td> <td> U+02986 </td> <td> <span class="glyph" title="">&#10630;</span> </td> <tr id="entity-ropf"><td> <code title="">ropf;</code> </td> <td> U+1D563 </td> <td> <span class="glyph" title="">&#120163;</span> </td> <tr id="entity-roplus"><td> <code title="">roplus;</code> </td> <td> U+02A2E </td> <td> <span class="glyph" title="">&#10798;</span> </td> <tr id="entity-rotimes"><td> <code title="">rotimes;</code> </td> <td> U+02A35 </td> <td> <span class="glyph" title="">&#10805;</span> </td> <tr id="entity-rpar"><td> <code title="">rpar;</code> </td> <td> U+00029 </td> <td> <span class="glyph" title="">)</span> </td> <tr id="entity-rpargt"><td> <code title="">rpargt;</code> </td> <td> U+02994 </td> <td> <span class="glyph" title="">&#10644;</span> </td> <tr id="entity-rppolint"><td> <code title="">rppolint;</code> </td> <td> U+02A12 </td> <td> <span class="glyph" title="">&#10770;</span> </td> <tr id="entity-rrarr"><td> <code title="">rrarr;</code> </td> <td> U+021C9 </td> <td> <span class="glyph" title="">&#8649;</span> </td> <tr id="entity-rsaquo"><td> <code title="">rsaquo;</code> </td> <td> U+0203A </td> <td> <span class="glyph" title="">&rsaquo;</span> </td> <tr id="entity-rscr"><td> <code title="">rscr;</code> </td> <td> U+1D4C7 </td> <td> <span class="glyph" title="">&#120007;</span> </td> <tr id="entity-rsh"><td> <code title="">rsh;</code> </td> <td> U+021B1 </td> <td> <span class="glyph" title="">&#8625;</span> </td> <tr id="entity-rsqb"><td> <code title="">rsqb;</code> </td> <td> U+0005D </td> <td> <span class="glyph" title="">]</span> </td> <tr id="entity-rsquo"><td> <code title="">rsquo;</code> </td> <td> U+02019 </td> <td> <span class="glyph" title="">&rsquo;</span> </td> <tr id="entity-rsquor"><td> <code title="">rsquor;</code> </td> <td> U+02019 </td> <td> <span class="glyph" title="">&rsquo;</span> </td> <tr id="entity-rthree"><td> <code title="">rthree;</code> </td> <td> U+022CC </td> <td> <span class="glyph" title="">&#8908;</span> </td> <tr id="entity-rtimes"><td> <code title="">rtimes;</code> </td> <td> U+022CA </td> <td> <span class="glyph" title="">&#8906;</span> </td> <tr id="entity-rtri"><td> <code title="">rtri;</code> </td> <td> U+025B9 </td> <td> <span class="glyph" title="">&#9657;</span> </td> <tr id="entity-rtrie"><td> <code title="">rtrie;</code> </td> <td> U+022B5 </td> <td> <span class="glyph" title="">&#8885;</span> </td> <tr id="entity-rtrif"><td> <code title="">rtrif;</code> </td> <td> U+025B8 </td> <td> <span class="glyph" title="">&#9656;</span> </td> <tr id="entity-rtriltri"><td> <code title="">rtriltri;</code> </td> <td> U+029CE </td> <td> <span class="glyph" title="">&#10702;</span> </td> <tr id="entity-ruluhar"><td> <code title="">ruluhar;</code> </td> <td> U+02968 </td> <td> <span class="glyph" title="">&#10600;</span> </td> <tr id="entity-rx"><td> <code title="">rx;</code> </td> <td> U+0211E </td> <td> <span class="glyph" title="">&#8478;</span> </td> <tr id="entity-sacute"><td> <code title="">sacute;</code> </td> <td> U+0015B </td> <td> <span class="glyph" title="">&#347;</span> </td> <tr id="entity-sbquo"><td> <code title="">sbquo;</code> </td> <td> U+0201A </td> <td> <span class="glyph" title="">&sbquo;</span> </td> <tr id="entity-sc"><td> <code title="">sc;</code> </td> <td> U+0227B </td> <td> <span class="glyph" title="">&#8827;</span> </td> <tr id="entity-scE"><td> <code title="">scE;</code> </td> <td> U+02AB4 </td> <td> <span class="glyph" title="">&#10932;</span> </td> <tr id="entity-scap"><td> <code title="">scap;</code> </td> <td> U+02AB8 </td> <td> <span class="glyph" title="">&#10936;</span> </td> <tr id="entity-scaron"><td> <code title="">scaron;</code> </td> <td> U+00161 </td> <td> <span class="glyph" title="">&scaron;</span> </td> <tr id="entity-sccue"><td> <code title="">sccue;</code> </td> <td> U+0227D </td> <td> <span class="glyph" title="">&#8829;</span> </td> <tr id="entity-sce"><td> <code title="">sce;</code> </td> <td> U+02AB0 </td> <td> <span class="glyph" title="">&#10928;</span> </td> <tr id="entity-scedil"><td> <code title="">scedil;</code> </td> <td> U+0015F </td> <td> <span class="glyph" title="">&#351;</span> </td> <tr id="entity-scirc"><td> <code title="">scirc;</code> </td> <td> U+0015D </td> <td> <span class="glyph" title="">&#349;</span> </td> <tr id="entity-scnE"><td> <code title="">scnE;</code> </td> <td> U+02AB6 </td> <td> <span class="glyph" title="">&#10934;</span> </td> <tr id="entity-scnap"><td> <code title="">scnap;</code> </td> <td> U+02ABA </td> <td> <span class="glyph" title="">&#10938;</span> </td> <tr id="entity-scnsim"><td> <code title="">scnsim;</code> </td> <td> U+022E9 </td> <td> <span class="glyph" title="">&#8937;</span> </td> <tr id="entity-scpolint"><td> <code title="">scpolint;</code> </td> <td> U+02A13 </td> <td> <span class="glyph" title="">&#10771;</span> </td> <tr id="entity-scsim"><td> <code title="">scsim;</code> </td> <td> U+0227F </td> <td> <span class="glyph" title="">&#8831;</span> </td> <tr id="entity-scy"><td> <code title="">scy;</code> </td> <td> U+00441 </td> <td> <span class="glyph" title="">&#1089;</span> </td> <tr id="entity-sdot"><td> <code title="">sdot;</code> </td> <td> U+022C5 </td> <td> <span class="glyph" title="">&sdot;</span> </td> <tr id="entity-sdotb"><td> <code title="">sdotb;</code> </td> <td> U+022A1 </td> <td> <span class="glyph" title="">&#8865;</span> </td> <tr id="entity-sdote"><td> <code title="">sdote;</code> </td> <td> U+02A66 </td> <td> <span class="glyph" title="">&#10854;</span> </td> <tr id="entity-seArr"><td> <code title="">seArr;</code> </td> <td> U+021D8 </td> <td> <span class="glyph" title="">&#8664;</span> </td> <tr id="entity-searhk"><td> <code title="">searhk;</code> </td> <td> U+02925 </td> <td> <span class="glyph" title="">&#10533;</span> </td> <tr id="entity-searr"><td> <code title="">searr;</code> </td> <td> U+02198 </td> <td> <span class="glyph" title="">&#8600;</span> </td> <tr id="entity-searrow"><td> <code title="">searrow;</code> </td> <td> U+02198 </td> <td> <span class="glyph" title="">&#8600;</span> </td> <tr id="entity-sect"><td> <code title="">sect;</code> </td> <td> U+000A7 </td> <td> <span class="glyph" title="">&sect;</span> </td> <tr id="entity-semi"><td> <code title="">semi;</code> </td> <td> U+0003B </td> <td> <span class="glyph" title="">;</span> </td> <tr id="entity-seswar"><td> <code title="">seswar;</code> </td> <td> U+02929 </td> <td> <span class="glyph" title="">&#10537;</span> </td> <tr id="entity-setminus"><td> <code title="">setminus;</code> </td> <td> U+02216 </td> <td> <span class="glyph" title="">&#8726;</span> </td> <tr id="entity-setmn"><td> <code title="">setmn;</code> </td> <td> U+02216 </td> <td> <span class="glyph" title="">&#8726;</span> </td> <tr id="entity-sext"><td> <code title="">sext;</code> </td> <td> U+02736 </td> <td> <span class="glyph" title="">&#10038;</span> </td> <tr id="entity-sfr"><td> <code title="">sfr;</code> </td> <td> U+1D530 </td> <td> <span class="glyph" title="">&#120112;</span> </td> <tr id="entity-sfrown"><td> <code title="">sfrown;</code> </td> <td> U+02322 </td> <td> <span class="glyph" title="">&#8994;</span> </td> <tr id="entity-sharp"><td> <code title="">sharp;</code> </td> <td> U+0266F </td> <td> <span class="glyph" title="">&#9839;</span> </td> <tr id="entity-shchcy"><td> <code title="">shchcy;</code> </td> <td> U+00449 </td> <td> <span class="glyph" title="">&#1097;</span> </td> <tr id="entity-shcy"><td> <code title="">shcy;</code> </td> <td> U+00448 </td> <td> <span class="glyph" title="">&#1096;</span> </td> <tr id="entity-shortmid"><td> <code title="">shortmid;</code> </td> <td> U+02223 </td> <td> <span class="glyph" title="">&#8739;</span> </td> <tr id="entity-shortparallel"><td> <code title="">shortparallel;</code> </td> <td> U+02225 </td> <td> <span class="glyph" title="">&#8741;</span> </td> <tr id="entity-shy"><td> <code title="">shy;</code> </td> <td> U+000AD </td> <td> <span class="glyph" title="">&shy;</span> </td> <tr id="entity-sigma"><td> <code title="">sigma;</code> </td> <td> U+003C3 </td> <td> <span class="glyph" title="">&sigma;</span> </td> <tr id="entity-sigmaf"><td> <code title="">sigmaf;</code> </td> <td> U+003C2 </td> <td> <span class="glyph" title="">&sigmaf;</span> </td> <tr id="entity-sigmav"><td> <code title="">sigmav;</code> </td> <td> U+003C2 </td> <td> <span class="glyph" title="">&sigmaf;</span> </td> <tr id="entity-sim"><td> <code title="">sim;</code> </td> <td> U+0223C </td> <td> <span class="glyph" title="">&sim;</span> </td> <tr id="entity-simdot"><td> <code title="">simdot;</code> </td> <td> U+02A6A </td> <td> <span class="glyph" title="">&#10858;</span> </td> <tr id="entity-sime"><td> <code title="">sime;</code> </td> <td> U+02243 </td> <td> <span class="glyph" title="">&#8771;</span> </td> <tr id="entity-simeq"><td> <code title="">simeq;</code> </td> <td> U+02243 </td> <td> <span class="glyph" title="">&#8771;</span> </td> <tr id="entity-simg"><td> <code title="">simg;</code> </td> <td> U+02A9E </td> <td> <span class="glyph" title="">&#10910;</span> </td> <tr id="entity-simgE"><td> <code title="">simgE;</code> </td> <td> U+02AA0 </td> <td> <span class="glyph" title="">&#10912;</span> </td> <tr id="entity-siml"><td> <code title="">siml;</code> </td> <td> U+02A9D </td> <td> <span class="glyph" title="">&#10909;</span> </td> <tr id="entity-simlE"><td> <code title="">simlE;</code> </td> <td> U+02A9F </td> <td> <span class="glyph" title="">&#10911;</span> </td> <tr id="entity-simne"><td> <code title="">simne;</code> </td> <td> U+02246 </td> <td> <span class="glyph" title="">&#8774;</span> </td> <tr id="entity-simplus"><td> <code title="">simplus;</code> </td> <td> U+02A24 </td> <td> <span class="glyph" title="">&#10788;</span> </td> <tr id="entity-simrarr"><td> <code title="">simrarr;</code> </td> <td> U+02972 </td> <td> <span class="glyph" title="">&#10610;</span> </td> <tr id="entity-slarr"><td> <code title="">slarr;</code> </td> <td> U+02190 </td> <td> <span class="glyph" title="">&larr;</span> </td> <tr id="entity-smallsetminus"><td> <code title="">smallsetminus;</code> </td> <td> U+02216 </td> <td> <span class="glyph" title="">&#8726;</span> </td> <tr id="entity-smashp"><td> <code title="">smashp;</code> </td> <td> U+02A33 </td> <td> <span class="glyph" title="">&#10803;</span> </td> <tr id="entity-smeparsl"><td> <code title="">smeparsl;</code> </td> <td> U+029E4 </td> <td> <span class="glyph" title="">&#10724;</span> </td> <tr id="entity-smid"><td> <code title="">smid;</code> </td> <td> U+02223 </td> <td> <span class="glyph" title="">&#8739;</span> </td> <tr id="entity-smile"><td> <code title="">smile;</code> </td> <td> U+02323 </td> <td> <span class="glyph" title="">&#8995;</span> </td> <tr id="entity-smt"><td> <code title="">smt;</code> </td> <td> U+02AAA </td> <td> <span class="glyph" title="">&#10922;</span> </td> <tr id="entity-smte"><td> <code title="">smte;</code> </td> <td> U+02AAC </td> <td> <span class="glyph" title="">&#10924;</span> </td> <tr id="entity-smtes"><td> <code title="">smtes;</code> </td> <td> U+02AAC U+0FE00 </td> <td> <span class="glyph compound" title="">&#10924;&#65024;</span> </td> <tr id="entity-softcy"><td> <code title="">softcy;</code> </td> <td> U+0044C </td> <td> <span class="glyph" title="">&#1100;</span> </td> <tr id="entity-sol"><td> <code title="">sol;</code> </td> <td> U+0002F </td> <td> <span class="glyph" title="">/</span> </td> <tr id="entity-solb"><td> <code title="">solb;</code> </td> <td> U+029C4 </td> <td> <span class="glyph" title="">&#10692;</span> </td> <tr id="entity-solbar"><td> <code title="">solbar;</code> </td> <td> U+0233F </td> <td> <span class="glyph" title="">&#9023;</span> </td> <tr id="entity-sopf"><td> <code title="">sopf;</code> </td> <td> U+1D564 </td> <td> <span class="glyph" title="">&#120164;</span> </td> <tr id="entity-spades"><td> <code title="">spades;</code> </td> <td> U+02660 </td> <td> <span class="glyph" title="">&spades;</span> </td> <tr id="entity-spadesuit"><td> <code title="">spadesuit;</code> </td> <td> U+02660 </td> <td> <span class="glyph" title="">&spades;</span> </td> <tr id="entity-spar"><td> <code title="">spar;</code> </td> <td> U+02225 </td> <td> <span class="glyph" title="">&#8741;</span> </td> <tr id="entity-sqcap"><td> <code title="">sqcap;</code> </td> <td> U+02293 </td> <td> <span class="glyph" title="">&#8851;</span> </td> <tr id="entity-sqcaps"><td> <code title="">sqcaps;</code> </td> <td> U+02293 U+0FE00 </td> <td> <span class="glyph compound" title="">&#8851;&#65024;</span> </td> <tr id="entity-sqcup"><td> <code title="">sqcup;</code> </td> <td> U+02294 </td> <td> <span class="glyph" title="">&#8852;</span> </td> <tr id="entity-sqcups"><td> <code title="">sqcups;</code> </td> <td> U+02294 U+0FE00 </td> <td> <span class="glyph compound" title="">&#8852;&#65024;</span> </td> <tr id="entity-sqsub"><td> <code title="">sqsub;</code> </td> <td> U+0228F </td> <td> <span class="glyph" title="">&#8847;</span> </td> <tr id="entity-sqsube"><td> <code title="">sqsube;</code> </td> <td> U+02291 </td> <td> <span class="glyph" title="">&#8849;</span> </td> <tr id="entity-sqsubset"><td> <code title="">sqsubset;</code> </td> <td> U+0228F </td> <td> <span class="glyph" title="">&#8847;</span> </td> <tr id="entity-sqsubseteq"><td> <code title="">sqsubseteq;</code> </td> <td> U+02291 </td> <td> <span class="glyph" title="">&#8849;</span> </td> <tr id="entity-sqsup"><td> <code title="">sqsup;</code> </td> <td> U+02290 </td> <td> <span class="glyph" title="">&#8848;</span> </td> <tr id="entity-sqsupe"><td> <code title="">sqsupe;</code> </td> <td> U+02292 </td> <td> <span class="glyph" title="">&#8850;</span> </td> <tr id="entity-sqsupset"><td> <code title="">sqsupset;</code> </td> <td> U+02290 </td> <td> <span class="glyph" title="">&#8848;</span> </td> <tr id="entity-sqsupseteq"><td> <code title="">sqsupseteq;</code> </td> <td> U+02292 </td> <td> <span class="glyph" title="">&#8850;</span> </td> <tr id="entity-squ"><td> <code title="">squ;</code> </td> <td> U+025A1 </td> <td> <span class="glyph" title="">&#9633;</span> </td> <tr id="entity-square"><td> <code title="">square;</code> </td> <td> U+025A1 </td> <td> <span class="glyph" title="">&#9633;</span> </td> <tr id="entity-squarf"><td> <code title="">squarf;</code> </td> <td> U+025AA </td> <td> <span class="glyph" title="">&#9642;</span> </td> <tr id="entity-squf"><td> <code title="">squf;</code> </td> <td> U+025AA </td> <td> <span class="glyph" title="">&#9642;</span> </td> <tr id="entity-srarr"><td> <code title="">srarr;</code> </td> <td> U+02192 </td> <td> <span class="glyph" title="">&rarr;</span> </td> <tr id="entity-sscr"><td> <code title="">sscr;</code> </td> <td> U+1D4C8 </td> <td> <span class="glyph" title="">&#120008;</span> </td> <tr id="entity-ssetmn"><td> <code title="">ssetmn;</code> </td> <td> U+02216 </td> <td> <span class="glyph" title="">&#8726;</span> </td> <tr id="entity-ssmile"><td> <code title="">ssmile;</code> </td> <td> U+02323 </td> <td> <span class="glyph" title="">&#8995;</span> </td> <tr id="entity-sstarf"><td> <code title="">sstarf;</code> </td> <td> U+022C6 </td> <td> <span class="glyph" title="">&#8902;</span> </td> <tr id="entity-star"><td> <code title="">star;</code> </td> <td> U+02606 </td> <td> <span class="glyph" title="">&#9734;</span> </td> <tr id="entity-starf"><td> <code title="">starf;</code> </td> <td> U+02605 </td> <td> <span class="glyph" title="">&#9733;</span> </td> <tr id="entity-straightepsilon"><td> <code title="">straightepsilon;</code> </td> <td> U+003F5 </td> <td> <span class="glyph" title="">&#1013;</span> </td> <tr id="entity-straightphi"><td> <code title="">straightphi;</code> </td> <td> U+003D5 </td> <td> <span class="glyph" title="">&#981;</span> </td> <tr id="entity-strns"><td> <code title="">strns;</code> </td> <td> U+000AF </td> <td> <span class="glyph" title="">&macr;</span> </td> <tr id="entity-sub"><td> <code title="">sub;</code> </td> <td> U+02282 </td> <td> <span class="glyph" title="">&sub;</span> </td> <tr id="entity-subE"><td> <code title="">subE;</code> </td> <td> U+02AC5 </td> <td> <span class="glyph" title="">&#10949;</span> </td> <tr id="entity-subdot"><td> <code title="">subdot;</code> </td> <td> U+02ABD </td> <td> <span class="glyph" title="">&#10941;</span> </td> <tr id="entity-sube"><td> <code title="">sube;</code> </td> <td> U+02286 </td> <td> <span class="glyph" title="">&sube;</span> </td> <tr id="entity-subedot"><td> <code title="">subedot;</code> </td> <td> U+02AC3 </td> <td> <span class="glyph" title="">&#10947;</span> </td> <tr id="entity-submult"><td> <code title="">submult;</code> </td> <td> U+02AC1 </td> <td> <span class="glyph" title="">&#10945;</span> </td> <tr id="entity-subnE"><td> <code title="">subnE;</code> </td> <td> U+02ACB </td> <td> <span class="glyph" title="">&#10955;</span> </td> <tr id="entity-subne"><td> <code title="">subne;</code> </td> <td> U+0228A </td> <td> <span class="glyph" title="">&#8842;</span> </td> <tr id="entity-subplus"><td> <code title="">subplus;</code> </td> <td> U+02ABF </td> <td> <span class="glyph" title="">&#10943;</span> </td> <tr id="entity-subrarr"><td> <code title="">subrarr;</code> </td> <td> U+02979 </td> <td> <span class="glyph" title="">&#10617;</span> </td> <tr id="entity-subset"><td> <code title="">subset;</code> </td> <td> U+02282 </td> <td> <span class="glyph" title="">&sub;</span> </td> <tr id="entity-subseteq"><td> <code title="">subseteq;</code> </td> <td> U+02286 </td> <td> <span class="glyph" title="">&sube;</span> </td> <tr id="entity-subseteqq"><td> <code title="">subseteqq;</code> </td> <td> U+02AC5 </td> <td> <span class="glyph" title="">&#10949;</span> </td> <tr id="entity-subsetneq"><td> <code title="">subsetneq;</code> </td> <td> U+0228A </td> <td> <span class="glyph" title="">&#8842;</span> </td> <tr id="entity-subsetneqq"><td> <code title="">subsetneqq;</code> </td> <td> U+02ACB </td> <td> <span class="glyph" title="">&#10955;</span> </td> <tr id="entity-subsim"><td> <code title="">subsim;</code> </td> <td> U+02AC7 </td> <td> <span class="glyph" title="">&#10951;</span> </td> <tr id="entity-subsub"><td> <code title="">subsub;</code> </td> <td> U+02AD5 </td> <td> <span class="glyph" title="">&#10965;</span> </td> <tr id="entity-subsup"><td> <code title="">subsup;</code> </td> <td> U+02AD3 </td> <td> <span class="glyph" title="">&#10963;</span> </td> <tr id="entity-succ"><td> <code title="">succ;</code> </td> <td> U+0227B </td> <td> <span class="glyph" title="">&#8827;</span> </td> <tr id="entity-succapprox"><td> <code title="">succapprox;</code> </td> <td> U+02AB8 </td> <td> <span class="glyph" title="">&#10936;</span> </td> <tr id="entity-succcurlyeq"><td> <code title="">succcurlyeq;</code> </td> <td> U+0227D </td> <td> <span class="glyph" title="">&#8829;</span> </td> <tr id="entity-succeq"><td> <code title="">succeq;</code> </td> <td> U+02AB0 </td> <td> <span class="glyph" title="">&#10928;</span> </td> <tr id="entity-succnapprox"><td> <code title="">succnapprox;</code> </td> <td> U+02ABA </td> <td> <span class="glyph" title="">&#10938;</span> </td> <tr id="entity-succneqq"><td> <code title="">succneqq;</code> </td> <td> U+02AB6 </td> <td> <span class="glyph" title="">&#10934;</span> </td> <tr id="entity-succnsim"><td> <code title="">succnsim;</code> </td> <td> U+022E9 </td> <td> <span class="glyph" title="">&#8937;</span> </td> <tr id="entity-succsim"><td> <code title="">succsim;</code> </td> <td> U+0227F </td> <td> <span class="glyph" title="">&#8831;</span> </td> <tr id="entity-sum"><td> <code title="">sum;</code> </td> <td> U+02211 </td> <td> <span class="glyph" title="">&sum;</span> </td> <tr id="entity-sung"><td> <code title="">sung;</code> </td> <td> U+0266A </td> <td> <span class="glyph" title="">&#9834;</span> </td> <tr id="entity-sup"><td> <code title="">sup;</code> </td> <td> U+02283 </td> <td> <span class="glyph" title="">&sup;</span> </td> <tr id="entity-sup1"><td> <code title="">sup1;</code> </td> <td> U+000B9 </td> <td> <span class="glyph" title="">&sup1;</span> </td> <tr id="entity-sup2"><td> <code title="">sup2;</code> </td> <td> U+000B2 </td> <td> <span class="glyph" title="">&sup2;</span> </td> <tr id="entity-sup3"><td> <code title="">sup3;</code> </td> <td> U+000B3 </td> <td> <span class="glyph" title="">&sup3;</span> </td> <tr id="entity-supE"><td> <code title="">supE;</code> </td> <td> U+02AC6 </td> <td> <span class="glyph" title="">&#10950;</span> </td> <tr id="entity-supdot"><td> <code title="">supdot;</code> </td> <td> U+02ABE </td> <td> <span class="glyph" title="">&#10942;</span> </td> <tr id="entity-supdsub"><td> <code title="">supdsub;</code> </td> <td> U+02AD8 </td> <td> <span class="glyph" title="">&#10968;</span> </td> <tr id="entity-supe"><td> <code title="">supe;</code> </td> <td> U+02287 </td> <td> <span class="glyph" title="">&supe;</span> </td> <tr id="entity-supedot"><td> <code title="">supedot;</code> </td> <td> U+02AC4 </td> <td> <span class="glyph" title="">&#10948;</span> </td> <tr id="entity-suphsol"><td> <code title="">suphsol;</code> </td> <td> U+027C9 </td> <td> <span class="glyph" title="">&#10185;</span> </td> <tr id="entity-suphsub"><td> <code title="">suphsub;</code> </td> <td> U+02AD7 </td> <td> <span class="glyph" title="">&#10967;</span> </td> <tr id="entity-suplarr"><td> <code title="">suplarr;</code> </td> <td> U+0297B </td> <td> <span class="glyph" title="">&#10619;</span> </td> <tr id="entity-supmult"><td> <code title="">supmult;</code> </td> <td> U+02AC2 </td> <td> <span class="glyph" title="">&#10946;</span> </td> <tr id="entity-supnE"><td> <code title="">supnE;</code> </td> <td> U+02ACC </td> <td> <span class="glyph" title="">&#10956;</span> </td> <tr id="entity-supne"><td> <code title="">supne;</code> </td> <td> U+0228B </td> <td> <span class="glyph" title="">&#8843;</span> </td> <tr id="entity-supplus"><td> <code title="">supplus;</code> </td> <td> U+02AC0 </td> <td> <span class="glyph" title="">&#10944;</span> </td> <tr id="entity-supset"><td> <code title="">supset;</code> </td> <td> U+02283 </td> <td> <span class="glyph" title="">&sup;</span> </td> <tr id="entity-supseteq"><td> <code title="">supseteq;</code> </td> <td> U+02287 </td> <td> <span class="glyph" title="">&supe;</span> </td> <tr id="entity-supseteqq"><td> <code title="">supseteqq;</code> </td> <td> U+02AC6 </td> <td> <span class="glyph" title="">&#10950;</span> </td> <tr id="entity-supsetneq"><td> <code title="">supsetneq;</code> </td> <td> U+0228B </td> <td> <span class="glyph" title="">&#8843;</span> </td> <tr id="entity-supsetneqq"><td> <code title="">supsetneqq;</code> </td> <td> U+02ACC </td> <td> <span class="glyph" title="">&#10956;</span> </td> <tr id="entity-supsim"><td> <code title="">supsim;</code> </td> <td> U+02AC8 </td> <td> <span class="glyph" title="">&#10952;</span> </td> <tr id="entity-supsub"><td> <code title="">supsub;</code> </td> <td> U+02AD4 </td> <td> <span class="glyph" title="">&#10964;</span> </td> <tr id="entity-supsup"><td> <code title="">supsup;</code> </td> <td> U+02AD6 </td> <td> <span class="glyph" title="">&#10966;</span> </td> <tr id="entity-swArr"><td> <code title="">swArr;</code> </td> <td> U+021D9 </td> <td> <span class="glyph" title="">&#8665;</span> </td> <tr id="entity-swarhk"><td> <code title="">swarhk;</code> </td> <td> U+02926 </td> <td> <span class="glyph" title="">&#10534;</span> </td> <tr id="entity-swarr"><td> <code title="">swarr;</code> </td> <td> U+02199 </td> <td> <span class="glyph" title="">&#8601;</span> </td> <tr id="entity-swarrow"><td> <code title="">swarrow;</code> </td> <td> U+02199 </td> <td> <span class="glyph" title="">&#8601;</span> </td> <tr id="entity-swnwar"><td> <code title="">swnwar;</code> </td> <td> U+0292A </td> <td> <span class="glyph" title="">&#10538;</span> </td> <tr id="entity-szlig"><td> <code title="">szlig;</code> </td> <td> U+000DF </td> <td> <span class="glyph" title="">&szlig;</span> </td> <tr id="entity-target"><td> <code title="">target;</code> </td> <td> U+02316 </td> <td> <span class="glyph" title="">&#8982;</span> </td> <tr id="entity-tau"><td> <code title="">tau;</code> </td> <td> U+003C4 </td> <td> <span class="glyph" title="">&tau;</span> </td> <tr id="entity-tbrk"><td> <code title="">tbrk;</code> </td> <td> U+023B4 </td> <td> <span class="glyph" title="">&#9140;</span> </td> <tr id="entity-tcaron"><td> <code title="">tcaron;</code> </td> <td> U+00165 </td> <td> <span class="glyph" title="">&#357;</span> </td> <tr id="entity-tcedil"><td> <code title="">tcedil;</code> </td> <td> U+00163 </td> <td> <span class="glyph" title="">&#355;</span> </td> <tr id="entity-tcy"><td> <code title="">tcy;</code> </td> <td> U+00442 </td> <td> <span class="glyph" title="">&#1090;</span> </td> <tr id="entity-tdot"><td> <code title="">tdot;</code> </td> <td> U+020DB </td> <td> <span class="glyph composition" title="">&#9676;&#8411;</span> </td> <tr id="entity-telrec"><td> <code title="">telrec;</code> </td> <td> U+02315 </td> <td> <span class="glyph" title="">&#8981;</span> </td> <tr id="entity-tfr"><td> <code title="">tfr;</code> </td> <td> U+1D531 </td> <td> <span class="glyph" title="">&#120113;</span> </td> <tr id="entity-there4"><td> <code title="">there4;</code> </td> <td> U+02234 </td> <td> <span class="glyph" title="">&there4;</span> </td> <tr id="entity-therefore"><td> <code title="">therefore;</code> </td> <td> U+02234 </td> <td> <span class="glyph" title="">&there4;</span> </td> <tr id="entity-theta"><td> <code title="">theta;</code> </td> <td> U+003B8 </td> <td> <span class="glyph" title="">&theta;</span> </td> <tr id="entity-thetasym"><td> <code title="">thetasym;</code> </td> <td> U+003D1 </td> <td> <span class="glyph" title="">&thetasym;</span> </td> <tr id="entity-thetav"><td> <code title="">thetav;</code> </td> <td> U+003D1 </td> <td> <span class="glyph" title="">&thetasym;</span> </td> <tr id="entity-thickapprox"><td> <code title="">thickapprox;</code> </td> <td> U+02248 </td> <td> <span class="glyph" title="">&asymp;</span> </td> <tr id="entity-thicksim"><td> <code title="">thicksim;</code> </td> <td> U+0223C </td> <td> <span class="glyph" title="">&sim;</span> </td> <tr id="entity-thinsp"><td> <code title="">thinsp;</code> </td> <td> U+02009 </td> <td> <span class="glyph" title="">&thinsp;</span> </td> <tr id="entity-thkap"><td> <code title="">thkap;</code> </td> <td> U+02248 </td> <td> <span class="glyph" title="">&asymp;</span> </td> <tr id="entity-thksim"><td> <code title="">thksim;</code> </td> <td> U+0223C </td> <td> <span class="glyph" title="">&sim;</span> </td> <tr id="entity-thorn"><td> <code title="">thorn;</code> </td> <td> U+000FE </td> <td> <span class="glyph" title="">&thorn;</span> </td> <tr id="entity-tilde"><td> <code title="">tilde;</code> </td> <td> U+002DC </td> <td> <span class="glyph" title="">&tilde;</span> </td> <tr id="entity-times"><td> <code title="">times;</code> </td> <td> U+000D7 </td> <td> <span class="glyph" title="">&times;</span> </td> <tr id="entity-timesb"><td> <code title="">timesb;</code> </td> <td> U+022A0 </td> <td> <span class="glyph" title="">&#8864;</span> </td> <tr id="entity-timesbar"><td> <code title="">timesbar;</code> </td> <td> U+02A31 </td> <td> <span class="glyph" title="">&#10801;</span> </td> <tr id="entity-timesd"><td> <code title="">timesd;</code> </td> <td> U+02A30 </td> <td> <span class="glyph" title="">&#10800;</span> </td> <tr id="entity-tint"><td> <code title="">tint;</code> </td> <td> U+0222D </td> <td> <span class="glyph" title="">&#8749;</span> </td> <tr id="entity-toea"><td> <code title="">toea;</code> </td> <td> U+02928 </td> <td> <span class="glyph" title="">&#10536;</span> </td> <tr id="entity-top"><td> <code title="">top;</code> </td> <td> U+022A4 </td> <td> <span class="glyph" title="">&#8868;</span> </td> <tr id="entity-topbot"><td> <code title="">topbot;</code> </td> <td> U+02336 </td> <td> <span class="glyph" title="">&#9014;</span> </td> <tr id="entity-topcir"><td> <code title="">topcir;</code> </td> <td> U+02AF1 </td> <td> <span class="glyph" title="">&#10993;</span> </td> <tr id="entity-topf"><td> <code title="">topf;</code> </td> <td> U+1D565 </td> <td> <span class="glyph" title="">&#120165;</span> </td> <tr id="entity-topfork"><td> <code title="">topfork;</code> </td> <td> U+02ADA </td> <td> <span class="glyph" title="">&#10970;</span> </td> <tr id="entity-tosa"><td> <code title="">tosa;</code> </td> <td> U+02929 </td> <td> <span class="glyph" title="">&#10537;</span> </td> <tr id="entity-tprime"><td> <code title="">tprime;</code> </td> <td> U+02034 </td> <td> <span class="glyph" title="">&#8244;</span> </td> <tr id="entity-trade"><td> <code title="">trade;</code> </td> <td> U+02122 </td> <td> <span class="glyph" title="">&trade;</span> </td> <tr id="entity-triangle"><td> <code title="">triangle;</code> </td> <td> U+025B5 </td> <td> <span class="glyph" title="">&#9653;</span> </td> <tr id="entity-triangledown"><td> <code title="">triangledown;</code> </td> <td> U+025BF </td> <td> <span class="glyph" title="">&#9663;</span> </td> <tr id="entity-triangleleft"><td> <code title="">triangleleft;</code> </td> <td> U+025C3 </td> <td> <span class="glyph" title="">&#9667;</span> </td> <tr id="entity-trianglelefteq"><td> <code title="">trianglelefteq;</code> </td> <td> U+022B4 </td> <td> <span class="glyph" title="">&#8884;</span> </td> <tr id="entity-triangleq"><td> <code title="">triangleq;</code> </td> <td> U+0225C </td> <td> <span class="glyph" title="">&#8796;</span> </td> <tr id="entity-triangleright"><td> <code title="">triangleright;</code> </td> <td> U+025B9 </td> <td> <span class="glyph" title="">&#9657;</span> </td> <tr id="entity-trianglerighteq"><td> <code title="">trianglerighteq;</code> </td> <td> U+022B5 </td> <td> <span class="glyph" title="">&#8885;</span> </td> <tr id="entity-tridot"><td> <code title="">tridot;</code> </td> <td> U+025EC </td> <td> <span class="glyph" title="">&#9708;</span> </td> <tr id="entity-trie"><td> <code title="">trie;</code> </td> <td> U+0225C </td> <td> <span class="glyph" title="">&#8796;</span> </td> <tr id="entity-triminus"><td> <code title="">triminus;</code> </td> <td> U+02A3A </td> <td> <span class="glyph" title="">&#10810;</span> </td> <tr id="entity-triplus"><td> <code title="">triplus;</code> </td> <td> U+02A39 </td> <td> <span class="glyph" title="">&#10809;</span> </td> <tr id="entity-trisb"><td> <code title="">trisb;</code> </td> <td> U+029CD </td> <td> <span class="glyph" title="">&#10701;</span> </td> <tr id="entity-tritime"><td> <code title="">tritime;</code> </td> <td> U+02A3B </td> <td> <span class="glyph" title="">&#10811;</span> </td> <tr id="entity-trpezium"><td> <code title="">trpezium;</code> </td> <td> U+023E2 </td> <td> <span class="glyph" title="">&#9186;</span> </td> <tr id="entity-tscr"><td> <code title="">tscr;</code> </td> <td> U+1D4C9 </td> <td> <span class="glyph" title="">&#120009;</span> </td> <tr id="entity-tscy"><td> <code title="">tscy;</code> </td> <td> U+00446 </td> <td> <span class="glyph" title="">&#1094;</span> </td> <tr id="entity-tshcy"><td> <code title="">tshcy;</code> </td> <td> U+0045B </td> <td> <span class="glyph" title="">&#1115;</span> </td> <tr id="entity-tstrok"><td> <code title="">tstrok;</code> </td> <td> U+00167 </td> <td> <span class="glyph" title="">&#359;</span> </td> <tr id="entity-twixt"><td> <code title="">twixt;</code> </td> <td> U+0226C </td> <td> <span class="glyph" title="">&#8812;</span> </td> <tr id="entity-twoheadleftarrow"><td> <code title="">twoheadleftarrow;</code> </td> <td> U+0219E </td> <td> <span class="glyph" title="">&#8606;</span> </td> <tr id="entity-twoheadrightarrow"><td> <code title="">twoheadrightarrow;</code> </td> <td> U+021A0 </td> <td> <span class="glyph" title="">&#8608;</span> </td> <tr id="entity-uArr"><td> <code title="">uArr;</code> </td> <td> U+021D1 </td> <td> <span class="glyph" title="">&uArr;</span> </td> <tr id="entity-uHar"><td> <code title="">uHar;</code> </td> <td> U+02963 </td> <td> <span class="glyph" title="">&#10595;</span> </td> <tr id="entity-uacute"><td> <code title="">uacute;</code> </td> <td> U+000FA </td> <td> <span class="glyph" title="">&uacute;</span> </td> <tr id="entity-uarr"><td> <code title="">uarr;</code> </td> <td> U+02191 </td> <td> <span class="glyph" title="">&uarr;</span> </td> <tr id="entity-ubrcy"><td> <code title="">ubrcy;</code> </td> <td> U+0045E </td> <td> <span class="glyph" title="">&#1118;</span> </td> <tr id="entity-ubreve"><td> <code title="">ubreve;</code> </td> <td> U+0016D </td> <td> <span class="glyph" title="">&#365;</span> </td> <tr id="entity-ucirc"><td> <code title="">ucirc;</code> </td> <td> U+000FB </td> <td> <span class="glyph" title="">&ucirc;</span> </td> <tr id="entity-ucy"><td> <code title="">ucy;</code> </td> <td> U+00443 </td> <td> <span class="glyph" title="">&#1091;</span> </td> <tr id="entity-udarr"><td> <code title="">udarr;</code> </td> <td> U+021C5 </td> <td> <span class="glyph" title="">&#8645;</span> </td> <tr id="entity-udblac"><td> <code title="">udblac;</code> </td> <td> U+00171 </td> <td> <span class="glyph" title="">&#369;</span> </td> <tr id="entity-udhar"><td> <code title="">udhar;</code> </td> <td> U+0296E </td> <td> <span class="glyph" title="">&#10606;</span> </td> <tr id="entity-ufisht"><td> <code title="">ufisht;</code> </td> <td> U+0297E </td> <td> <span class="glyph" title="">&#10622;</span> </td> <tr id="entity-ufr"><td> <code title="">ufr;</code> </td> <td> U+1D532 </td> <td> <span class="glyph" title="">&#120114;</span> </td> <tr id="entity-ugrave"><td> <code title="">ugrave;</code> </td> <td> U+000F9 </td> <td> <span class="glyph" title="">&ugrave;</span> </td> <tr id="entity-uharl"><td> <code title="">uharl;</code> </td> <td> U+021BF </td> <td> <span class="glyph" title="">&#8639;</span> </td> <tr id="entity-uharr"><td> <code title="">uharr;</code> </td> <td> U+021BE </td> <td> <span class="glyph" title="">&#8638;</span> </td> <tr id="entity-uhblk"><td> <code title="">uhblk;</code> </td> <td> U+02580 </td> <td> <span class="glyph" title="">&#9600;</span> </td> <tr id="entity-ulcorn"><td> <code title="">ulcorn;</code> </td> <td> U+0231C </td> <td> <span class="glyph" title="">&#8988;</span> </td> <tr id="entity-ulcorner"><td> <code title="">ulcorner;</code> </td> <td> U+0231C </td> <td> <span class="glyph" title="">&#8988;</span> </td> <tr id="entity-ulcrop"><td> <code title="">ulcrop;</code> </td> <td> U+0230F </td> <td> <span class="glyph" title="">&#8975;</span> </td> <tr id="entity-ultri"><td> <code title="">ultri;</code> </td> <td> U+025F8 </td> <td> <span class="glyph" title="">&#9720;</span> </td> <tr id="entity-umacr"><td> <code title="">umacr;</code> </td> <td> U+0016B </td> <td> <span class="glyph" title="">&#363;</span> </td> <tr id="entity-uml"><td> <code title="">uml;</code> </td> <td> U+000A8 </td> <td> <span class="glyph" title="">&uml;</span> </td> <tr id="entity-uogon"><td> <code title="">uogon;</code> </td> <td> U+00173 </td> <td> <span class="glyph" title="">&#371;</span> </td> <tr id="entity-uopf"><td> <code title="">uopf;</code> </td> <td> U+1D566 </td> <td> <span class="glyph" title="">&#120166;</span> </td> <tr id="entity-uparrow"><td> <code title="">uparrow;</code> </td> <td> U+02191 </td> <td> <span class="glyph" title="">&uarr;</span> </td> <tr id="entity-updownarrow"><td> <code title="">updownarrow;</code> </td> <td> U+02195 </td> <td> <span class="glyph" title="">&#8597;</span> </td> <tr id="entity-upharpoonleft"><td> <code title="">upharpoonleft;</code> </td> <td> U+021BF </td> <td> <span class="glyph" title="">&#8639;</span> </td> <tr id="entity-upharpoonright"><td> <code title="">upharpoonright;</code> </td> <td> U+021BE </td> <td> <span class="glyph" title="">&#8638;</span> </td> <tr id="entity-uplus"><td> <code title="">uplus;</code> </td> <td> U+0228E </td> <td> <span class="glyph" title="">&#8846;</span> </td> <tr id="entity-upsi"><td> <code title="">upsi;</code> </td> <td> U+003C5 </td> <td> <span class="glyph" title="">&upsilon;</span> </td> <tr id="entity-upsih"><td> <code title="">upsih;</code> </td> <td> U+003D2 </td> <td> <span class="glyph" title="">&upsih;</span> </td> <tr id="entity-upsilon"><td> <code title="">upsilon;</code> </td> <td> U+003C5 </td> <td> <span class="glyph" title="">&upsilon;</span> </td> <tr id="entity-upuparrows"><td> <code title="">upuparrows;</code> </td> <td> U+021C8 </td> <td> <span class="glyph" title="">&#8648;</span> </td> <tr id="entity-urcorn"><td> <code title="">urcorn;</code> </td> <td> U+0231D </td> <td> <span class="glyph" title="">&#8989;</span> </td> <tr id="entity-urcorner"><td> <code title="">urcorner;</code> </td> <td> U+0231D </td> <td> <span class="glyph" title="">&#8989;</span> </td> <tr id="entity-urcrop"><td> <code title="">urcrop;</code> </td> <td> U+0230E </td> <td> <span class="glyph" title="">&#8974;</span> </td> <tr id="entity-uring"><td> <code title="">uring;</code> </td> <td> U+0016F </td> <td> <span class="glyph" title="">&#367;</span> </td> <tr id="entity-urtri"><td> <code title="">urtri;</code> </td> <td> U+025F9 </td> <td> <span class="glyph" title="">&#9721;</span> </td> <tr id="entity-uscr"><td> <code title="">uscr;</code> </td> <td> U+1D4CA </td> <td> <span class="glyph" title="">&#120010;</span> </td> <tr id="entity-utdot"><td> <code title="">utdot;</code> </td> <td> U+022F0 </td> <td> <span class="glyph" title="">&#8944;</span> </td> <tr id="entity-utilde"><td> <code title="">utilde;</code> </td> <td> U+00169 </td> <td> <span class="glyph" title="">&#361;</span> </td> <tr id="entity-utri"><td> <code title="">utri;</code> </td> <td> U+025B5 </td> <td> <span class="glyph" title="">&#9653;</span> </td> <tr id="entity-utrif"><td> <code title="">utrif;</code> </td> <td> U+025B4 </td> <td> <span class="glyph" title="">&#9652;</span> </td> <tr id="entity-uuarr"><td> <code title="">uuarr;</code> </td> <td> U+021C8 </td> <td> <span class="glyph" title="">&#8648;</span> </td> <tr id="entity-uuml"><td> <code title="">uuml;</code> </td> <td> U+000FC </td> <td> <span class="glyph" title="">&uuml;</span> </td> <tr id="entity-uwangle"><td> <code title="">uwangle;</code> </td> <td> U+029A7 </td> <td> <span class="glyph" title="">&#10663;</span> </td> <tr id="entity-vArr"><td> <code title="">vArr;</code> </td> <td> U+021D5 </td> <td> <span class="glyph" title="">&#8661;</span> </td> <tr id="entity-vBar"><td> <code title="">vBar;</code> </td> <td> U+02AE8 </td> <td> <span class="glyph" title="">&#10984;</span> </td> <tr id="entity-vBarv"><td> <code title="">vBarv;</code> </td> <td> U+02AE9 </td> <td> <span class="glyph" title="">&#10985;</span> </td> <tr id="entity-vDash"><td> <code title="">vDash;</code> </td> <td> U+022A8 </td> <td> <span class="glyph" title="">&#8872;</span> </td> <tr id="entity-vangrt"><td> <code title="">vangrt;</code> </td> <td> U+0299C </td> <td> <span class="glyph" title="">&#10652;</span> </td> <tr id="entity-varepsilon"><td> <code title="">varepsilon;</code> </td> <td> U+003F5 </td> <td> <span class="glyph" title="">&#1013;</span> </td> <tr id="entity-varkappa"><td> <code title="">varkappa;</code> </td> <td> U+003F0 </td> <td> <span class="glyph" title="">&#1008;</span> </td> <tr id="entity-varnothing"><td> <code title="">varnothing;</code> </td> <td> U+02205 </td> <td> <span class="glyph" title="">&empty;</span> </td> <tr id="entity-varphi"><td> <code title="">varphi;</code> </td> <td> U+003D5 </td> <td> <span class="glyph" title="">&#981;</span> </td> <tr id="entity-varpi"><td> <code title="">varpi;</code> </td> <td> U+003D6 </td> <td> <span class="glyph" title="">&piv;</span> </td> <tr id="entity-varpropto"><td> <code title="">varpropto;</code> </td> <td> U+0221D </td> <td> <span class="glyph" title="">&prop;</span> </td> <tr id="entity-varr"><td> <code title="">varr;</code> </td> <td> U+02195 </td> <td> <span class="glyph" title="">&#8597;</span> </td> <tr id="entity-varrho"><td> <code title="">varrho;</code> </td> <td> U+003F1 </td> <td> <span class="glyph" title="">&#1009;</span> </td> <tr id="entity-varsigma"><td> <code title="">varsigma;</code> </td> <td> U+003C2 </td> <td> <span class="glyph" title="">&sigmaf;</span> </td> <tr id="entity-varsubsetneq"><td> <code title="">varsubsetneq;</code> </td> <td> U+0228A U+0FE00 </td> <td> <span class="glyph compound" title="">&#8842;&#65024;</span> </td> <tr id="entity-varsubsetneqq"><td> <code title="">varsubsetneqq;</code> </td> <td> U+02ACB U+0FE00 </td> <td> <span class="glyph compound" title="">&#10955;&#65024;</span> </td> <tr id="entity-varsupsetneq"><td> <code title="">varsupsetneq;</code> </td> <td> U+0228B U+0FE00 </td> <td> <span class="glyph compound" title="">&#8843;&#65024;</span> </td> <tr id="entity-varsupsetneqq"><td> <code title="">varsupsetneqq;</code> </td> <td> U+02ACC U+0FE00 </td> <td> <span class="glyph compound" title="">&#10956;&#65024;</span> </td> <tr id="entity-vartheta"><td> <code title="">vartheta;</code> </td> <td> U+003D1 </td> <td> <span class="glyph" title="">&thetasym;</span> </td> <tr id="entity-vartriangleleft"><td> <code title="">vartriangleleft;</code> </td> <td> U+022B2 </td> <td> <span class="glyph" title="">&#8882;</span> </td> <tr id="entity-vartriangleright"><td> <code title="">vartriangleright;</code> </td> <td> U+022B3 </td> <td> <span class="glyph" title="">&#8883;</span> </td> <tr id="entity-vcy"><td> <code title="">vcy;</code> </td> <td> U+00432 </td> <td> <span class="glyph" title="">&#1074;</span> </td> <tr id="entity-vdash"><td> <code title="">vdash;</code> </td> <td> U+022A2 </td> <td> <span class="glyph" title="">&#8866;</span> </td> <tr id="entity-vee"><td> <code title="">vee;</code> </td> <td> U+02228 </td> <td> <span class="glyph" title="">&or;</span> </td> <tr id="entity-veebar"><td> <code title="">veebar;</code> </td> <td> U+022BB </td> <td> <span class="glyph" title="">&#8891;</span> </td> <tr id="entity-veeeq"><td> <code title="">veeeq;</code> </td> <td> U+0225A </td> <td> <span class="glyph" title="">&#8794;</span> </td> <tr id="entity-vellip"><td> <code title="">vellip;</code> </td> <td> U+022EE </td> <td> <span class="glyph" title="">&#8942;</span> </td> <tr id="entity-verbar"><td> <code title="">verbar;</code> </td> <td> U+0007C </td> <td> <span class="glyph" title="">|</span> </td> <tr id="entity-vert"><td> <code title="">vert;</code> </td> <td> U+0007C </td> <td> <span class="glyph" title="">|</span> </td> <tr id="entity-vfr"><td> <code title="">vfr;</code> </td> <td> U+1D533 </td> <td> <span class="glyph" title="">&#120115;</span> </td> <tr id="entity-vltri"><td> <code title="">vltri;</code> </td> <td> U+022B2 </td> <td> <span class="glyph" title="">&#8882;</span> </td> <tr id="entity-vnsub"><td> <code title="">vnsub;</code> </td> <td> U+02282 U+020D2 </td> <td> <span class="glyph compound" title="">&sub;&#8402;</span> </td> <tr id="entity-vnsup"><td> <code title="">vnsup;</code> </td> <td> U+02283 U+020D2 </td> <td> <span class="glyph compound" title="">&sup;&#8402;</span> </td> <tr id="entity-vopf"><td> <code title="">vopf;</code> </td> <td> U+1D567 </td> <td> <span class="glyph" title="">&#120167;</span> </td> <tr id="entity-vprop"><td> <code title="">vprop;</code> </td> <td> U+0221D </td> <td> <span class="glyph" title="">&prop;</span> </td> <tr id="entity-vrtri"><td> <code title="">vrtri;</code> </td> <td> U+022B3 </td> <td> <span class="glyph" title="">&#8883;</span> </td> <tr id="entity-vscr"><td> <code title="">vscr;</code> </td> <td> U+1D4CB </td> <td> <span class="glyph" title="">&#120011;</span> </td> <tr id="entity-vsubnE"><td> <code title="">vsubnE;</code> </td> <td> U+02ACB U+0FE00 </td> <td> <span class="glyph compound" title="">&#10955;&#65024;</span> </td> <tr id="entity-vsubne"><td> <code title="">vsubne;</code> </td> <td> U+0228A U+0FE00 </td> <td> <span class="glyph compound" title="">&#8842;&#65024;</span> </td> <tr id="entity-vsupnE"><td> <code title="">vsupnE;</code> </td> <td> U+02ACC U+0FE00 </td> <td> <span class="glyph compound" title="">&#10956;&#65024;</span> </td> <tr id="entity-vsupne"><td> <code title="">vsupne;</code> </td> <td> U+0228B U+0FE00 </td> <td> <span class="glyph compound" title="">&#8843;&#65024;</span> </td> <tr id="entity-vzigzag"><td> <code title="">vzigzag;</code> </td> <td> U+0299A </td> <td> <span class="glyph" title="">&#10650;</span> </td> <tr id="entity-wcirc"><td> <code title="">wcirc;</code> </td> <td> U+00175 </td> <td> <span class="glyph" title="">&#373;</span> </td> <tr id="entity-wedbar"><td> <code title="">wedbar;</code> </td> <td> U+02A5F </td> <td> <span class="glyph" title="">&#10847;</span> </td> <tr id="entity-wedge"><td> <code title="">wedge;</code> </td> <td> U+02227 </td> <td> <span class="glyph" title="">&and;</span> </td> <tr id="entity-wedgeq"><td> <code title="">wedgeq;</code> </td> <td> U+02259 </td> <td> <span class="glyph" title="">&#8793;</span> </td> <tr id="entity-weierp"><td> <code title="">weierp;</code> </td> <td> U+02118 </td> <td> <span class="glyph" title="">&weierp;</span> </td> <tr id="entity-wfr"><td> <code title="">wfr;</code> </td> <td> U+1D534 </td> <td> <span class="glyph" title="">&#120116;</span> </td> <tr id="entity-wopf"><td> <code title="">wopf;</code> </td> <td> U+1D568 </td> <td> <span class="glyph" title="">&#120168;</span> </td> <tr id="entity-wp"><td> <code title="">wp;</code> </td> <td> U+02118 </td> <td> <span class="glyph" title="">&weierp;</span> </td> <tr id="entity-wr"><td> <code title="">wr;</code> </td> <td> U+02240 </td> <td> <span class="glyph" title="">&#8768;</span> </td> <tr id="entity-wreath"><td> <code title="">wreath;</code> </td> <td> U+02240 </td> <td> <span class="glyph" title="">&#8768;</span> </td> <tr id="entity-wscr"><td> <code title="">wscr;</code> </td> <td> U+1D4CC </td> <td> <span class="glyph" title="">&#120012;</span> </td> <tr id="entity-xcap"><td> <code title="">xcap;</code> </td> <td> U+022C2 </td> <td> <span class="glyph" title="">&#8898;</span> </td> <tr id="entity-xcirc"><td> <code title="">xcirc;</code> </td> <td> U+025EF </td> <td> <span class="glyph" title="">&#9711;</span> </td> <tr id="entity-xcup"><td> <code title="">xcup;</code> </td> <td> U+022C3 </td> <td> <span class="glyph" title="">&#8899;</span> </td> <tr id="entity-xdtri"><td> <code title="">xdtri;</code> </td> <td> U+025BD </td> <td> <span class="glyph" title="">&#9661;</span> </td> <tr id="entity-xfr"><td> <code title="">xfr;</code> </td> <td> U+1D535 </td> <td> <span class="glyph" title="">&#120117;</span> </td> <tr id="entity-xhArr"><td> <code title="">xhArr;</code> </td> <td> U+027FA </td> <td> <span class="glyph" title="">&#10234;</span> </td> <tr id="entity-xharr"><td> <code title="">xharr;</code> </td> <td> U+027F7 </td> <td> <span class="glyph" title="">&#10231;</span> </td> <tr id="entity-xi"><td> <code title="">xi;</code> </td> <td> U+003BE </td> <td> <span class="glyph" title="">&xi;</span> </td> <tr id="entity-xlArr"><td> <code title="">xlArr;</code> </td> <td> U+027F8 </td> <td> <span class="glyph" title="">&#10232;</span> </td> <tr id="entity-xlarr"><td> <code title="">xlarr;</code> </td> <td> U+027F5 </td> <td> <span class="glyph" title="">&#10229;</span> </td> <tr id="entity-xmap"><td> <code title="">xmap;</code> </td> <td> U+027FC </td> <td> <span class="glyph" title="">&#10236;</span> </td> <tr id="entity-xnis"><td> <code title="">xnis;</code> </td> <td> U+022FB </td> <td> <span class="glyph" title="">&#8955;</span> </td> <tr id="entity-xodot"><td> <code title="">xodot;</code> </td> <td> U+02A00 </td> <td> <span class="glyph" title="">&#10752;</span> </td> <tr id="entity-xopf"><td> <code title="">xopf;</code> </td> <td> U+1D569 </td> <td> <span class="glyph" title="">&#120169;</span> </td> <tr id="entity-xoplus"><td> <code title="">xoplus;</code> </td> <td> U+02A01 </td> <td> <span class="glyph" title="">&#10753;</span> </td> <tr id="entity-xotime"><td> <code title="">xotime;</code> </td> <td> U+02A02 </td> <td> <span class="glyph" title="">&#10754;</span> </td> <tr id="entity-xrArr"><td> <code title="">xrArr;</code> </td> <td> U+027F9 </td> <td> <span class="glyph" title="">&#10233;</span> </td> <tr id="entity-xrarr"><td> <code title="">xrarr;</code> </td> <td> U+027F6 </td> <td> <span class="glyph" title="">&#10230;</span> </td> <tr id="entity-xscr"><td> <code title="">xscr;</code> </td> <td> U+1D4CD </td> <td> <span class="glyph" title="">&#120013;</span> </td> <tr id="entity-xsqcup"><td> <code title="">xsqcup;</code> </td> <td> U+02A06 </td> <td> <span class="glyph" title="">&#10758;</span> </td> <tr id="entity-xuplus"><td> <code title="">xuplus;</code> </td> <td> U+02A04 </td> <td> <span class="glyph" title="">&#10756;</span> </td> <tr id="entity-xutri"><td> <code title="">xutri;</code> </td> <td> U+025B3 </td> <td> <span class="glyph" title="">&#9651;</span> </td> <tr id="entity-xvee"><td> <code title="">xvee;</code> </td> <td> U+022C1 </td> <td> <span class="glyph" title="">&#8897;</span> </td> <tr id="entity-xwedge"><td> <code title="">xwedge;</code> </td> <td> U+022C0 </td> <td> <span class="glyph" title="">&#8896;</span> </td> <tr id="entity-yacute"><td> <code title="">yacute;</code> </td> <td> U+000FD </td> <td> <span class="glyph" title="">&yacute;</span> </td> <tr id="entity-yacy"><td> <code title="">yacy;</code> </td> <td> U+0044F </td> <td> <span class="glyph" title="">&#1103;</span> </td> <tr id="entity-ycirc"><td> <code title="">ycirc;</code> </td> <td> U+00177 </td> <td> <span class="glyph" title="">&#375;</span> </td> <tr id="entity-ycy"><td> <code title="">ycy;</code> </td> <td> U+0044B </td> <td> <span class="glyph" title="">&#1099;</span> </td> <tr id="entity-yen"><td> <code title="">yen;</code> </td> <td> U+000A5 </td> <td> <span class="glyph" title="">&yen;</span> </td> <tr id="entity-yfr"><td> <code title="">yfr;</code> </td> <td> U+1D536 </td> <td> <span class="glyph" title="">&#120118;</span> </td> <tr id="entity-yicy"><td> <code title="">yicy;</code> </td> <td> U+00457 </td> <td> <span class="glyph" title="">&#1111;</span> </td> <tr id="entity-yopf"><td> <code title="">yopf;</code> </td> <td> U+1D56A </td> <td> <span class="glyph" title="">&#120170;</span> </td> <tr id="entity-yscr"><td> <code title="">yscr;</code> </td> <td> U+1D4CE </td> <td> <span class="glyph" title="">&#120014;</span> </td> <tr id="entity-yucy"><td> <code title="">yucy;</code> </td> <td> U+0044E </td> <td> <span class="glyph" title="">&#1102;</span> </td> <tr id="entity-yuml"><td> <code title="">yuml;</code> </td> <td> U+000FF </td> <td> <span class="glyph" title="">&yuml;</span> </td> <tr id="entity-zacute"><td> <code title="">zacute;</code> </td> <td> U+0017A </td> <td> <span class="glyph" title="">&#378;</span> </td> <tr id="entity-zcaron"><td> <code title="">zcaron;</code> </td> <td> U+0017E </td> <td> <span class="glyph" title="">&#382;</span> </td> <tr id="entity-zcy"><td> <code title="">zcy;</code> </td> <td> U+00437 </td> <td> <span class="glyph" title="">&#1079;</span> </td> <tr id="entity-zdot"><td> <code title="">zdot;</code> </td> <td> U+0017C </td> <td> <span class="glyph" title="">&#380;</span> </td> <tr id="entity-zeetrf"><td> <code title="">zeetrf;</code> </td> <td> U+02128 </td> <td> <span class="glyph" title="">&#8488;</span> </td> <tr id="entity-zeta"><td> <code title="">zeta;</code> </td> <td> U+003B6 </td> <td> <span class="glyph" title="">&zeta;</span> </td> <tr id="entity-zfr"><td> <code title="">zfr;</code> </td> <td> U+1D537 </td> <td> <span class="glyph" title="">&#120119;</span> </td> <tr id="entity-zhcy"><td> <code title="">zhcy;</code> </td> <td> U+00436 </td> <td> <span class="glyph" title="">&#1078;</span> </td> <tr id="entity-zigrarr"><td> <code title="">zigrarr;</code> </td> <td> U+021DD </td> <td> <span class="glyph" title="">&#8669;</span> </td> <tr id="entity-zopf"><td> <code title="">zopf;</code> </td> <td> U+1D56B </td> <td> <span class="glyph" title="">&#120171;</span> </td> <tr id="entity-zscr"><td> <code title="">zscr;</code> </td> <td> U+1D4CF </td> <td> <span class="glyph" title="">&#120015;</span> </td> <tr id="entity-zwj"><td> <code title="">zwj;</code> </td> <td> U+0200D </td> <td> <span class="glyph" title="">&zwj;</span> </td> <tr id="entity-zwnj"><td> <code title="">zwnj;</code> </td> <td> U+0200C </td> <td> <span class="glyph" title="">&zwnj;</span> </td> <tr class="impl"><td> <code title="">AElig</code> </td> <td> U+000C6 </td> <td> <span title="">&AElig;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">AMP</code> </td> <td> U+00026 </td> <td> <span title="">&amp;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Aacute</code> </td> <td> U+000C1 </td> <td> <span title="">&Aacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Acirc</code> </td> <td> U+000C2 </td> <td> <span title="">&Acirc;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Agrave</code> </td> <td> U+000C0 </td> <td> <span title="">&Agrave;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Aring</code> </td> <td> U+000C5 </td> <td> <span title="">&Aring;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Atilde</code> </td> <td> U+000C3 </td> <td> <span title="">&Atilde;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Auml</code> </td> <td> U+000C4 </td> <td> <span title="">&Auml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">COPY</code> </td> <td> U+000A9 </td> <td> <span title="">&copy;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Ccedil</code> </td> <td> U+000C7 </td> <td> <span title="">&Ccedil;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ETH</code> </td> <td> U+000D0 </td> <td> <span title="">&ETH;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Eacute</code> </td> <td> U+000C9 </td> <td> <span title="">&Eacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Ecirc</code> </td> <td> U+000CA </td> <td> <span title="">&Ecirc;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Egrave</code> </td> <td> U+000C8 </td> <td> <span title="">&Egrave;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Euml</code> </td> <td> U+000CB </td> <td> <span title="">&Euml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">GT</code> </td> <td> U+0003E </td> <td> <span title="">&gt;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Iacute</code> </td> <td> U+000CD </td> <td> <span title="">&Iacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Icirc</code> </td> <td> U+000CE </td> <td> <span title="">&Icirc;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Igrave</code> </td> <td> U+000CC </td> <td> <span title="">&Igrave;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Iuml</code> </td> <td> U+000CF </td> <td> <span title="">&Iuml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">LT</code> </td> <td> U+0003C </td> <td> <span title="">&lt;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Ntilde</code> </td> <td> U+000D1 </td> <td> <span title="">&Ntilde;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Oacute</code> </td> <td> U+000D3 </td> <td> <span title="">&Oacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Ocirc</code> </td> <td> U+000D4 </td> <td> <span title="">&Ocirc;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Ograve</code> </td> <td> U+000D2 </td> <td> <span title="">&Ograve;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Oslash</code> </td> <td> U+000D8 </td> <td> <span title="">&Oslash;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Otilde</code> </td> <td> U+000D5 </td> <td> <span title="">&Otilde;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Ouml</code> </td> <td> U+000D6 </td> <td> <span title="">&Ouml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">QUOT</code> </td> <td> U+00022 </td> <td> <span title="">"</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">REG</code> </td> <td> U+000AE </td> <td> <span title="">&reg;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">THORN</code> </td> <td> U+000DE </td> <td> <span title="">&THORN;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Uacute</code> </td> <td> U+000DA </td> <td> <span title="">&Uacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Ucirc</code> </td> <td> U+000DB </td> <td> <span title="">&Ucirc;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Ugrave</code> </td> <td> U+000D9 </td> <td> <span title="">&Ugrave;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Uuml</code> </td> <td> U+000DC </td> <td> <span title="">&Uuml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">Yacute</code> </td> <td> U+000DD </td> <td> <span title="">&Yacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">aacute</code> </td> <td> U+000E1 </td> <td> <span title="">&aacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">acirc</code> </td> <td> U+000E2 </td> <td> <span title="">&acirc;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">acute</code> </td> <td> U+000B4 </td> <td> <span title="">&acute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">aelig</code> </td> <td> U+000E6 </td> <td> <span title="">&aelig;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">agrave</code> </td> <td> U+000E0 </td> <td> <span title="">&agrave;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">amp</code> </td> <td> U+00026 </td> <td> <span title="">&amp;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">aring</code> </td> <td> U+000E5 </td> <td> <span title="">&aring;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">atilde</code> </td> <td> U+000E3 </td> <td> <span title="">&atilde;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">auml</code> </td> <td> U+000E4 </td> <td> <span title="">&auml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">brvbar</code> </td> <td> U+000A6 </td> <td> <span title="">&brvbar;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ccedil</code> </td> <td> U+000E7 </td> <td> <span title="">&ccedil;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">cedil</code> </td> <td> U+000B8 </td> <td> <span title="">&cedil;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">cent</code> </td> <td> U+000A2 </td> <td> <span title="">&cent;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">copy</code> </td> <td> U+000A9 </td> <td> <span title="">&copy;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">curren</code> </td> <td> U+000A4 </td> <td> <span title="">&curren;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">deg</code> </td> <td> U+000B0 </td> <td> <span title="">&deg;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">divide</code> </td> <td> U+000F7 </td> <td> <span title="">&divide;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">eacute</code> </td> <td> U+000E9 </td> <td> <span title="">&eacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ecirc</code> </td> <td> U+000EA </td> <td> <span title="">&ecirc;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">egrave</code> </td> <td> U+000E8 </td> <td> <span title="">&egrave;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">eth</code> </td> <td> U+000F0 </td> <td> <span title="">&eth;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">euml</code> </td> <td> U+000EB </td> <td> <span title="">&euml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">frac12</code> </td> <td> U+000BD </td> <td> <span title="">&frac12;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">frac14</code> </td> <td> U+000BC </td> <td> <span title="">&frac14;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">frac34</code> </td> <td> U+000BE </td> <td> <span title="">&frac34;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">gt</code> </td> <td> U+0003E </td> <td> <span title="">&gt;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">iacute</code> </td> <td> U+000ED </td> <td> <span title="">&iacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">icirc</code> </td> <td> U+000EE </td> <td> <span title="">&icirc;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">iexcl</code> </td> <td> U+000A1 </td> <td> <span title="">&iexcl;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">igrave</code> </td> <td> U+000EC </td> <td> <span title="">&igrave;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">iquest</code> </td> <td> U+000BF </td> <td> <span title="">&iquest;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">iuml</code> </td> <td> U+000EF </td> <td> <span title="">&iuml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">laquo</code> </td> <td> U+000AB </td> <td> <span title="">&laquo;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">lt</code> </td> <td> U+0003C </td> <td> <span title="">&lt;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">macr</code> </td> <td> U+000AF </td> <td> <span title="">&macr;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">micro</code> </td> <td> U+000B5 </td> <td> <span title="">&micro;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">middot</code> </td> <td> U+000B7 </td> <td> <span title="">&middot;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">nbsp</code> </td> <td> U+000A0 </td> <td> <span title="">&nbsp;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">not</code> </td> <td> U+000AC </td> <td> <span title="">&not;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ntilde</code> </td> <td> U+000F1 </td> <td> <span title="">&ntilde;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">oacute</code> </td> <td> U+000F3 </td> <td> <span title="">&oacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ocirc</code> </td> <td> U+000F4 </td> <td> <span title="">&ocirc;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ograve</code> </td> <td> U+000F2 </td> <td> <span title="">&ograve;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ordf</code> </td> <td> U+000AA </td> <td> <span title="">&ordf;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ordm</code> </td> <td> U+000BA </td> <td> <span title="">&ordm;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">oslash</code> </td> <td> U+000F8 </td> <td> <span title="">&oslash;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">otilde</code> </td> <td> U+000F5 </td> <td> <span title="">&otilde;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ouml</code> </td> <td> U+000F6 </td> <td> <span title="">&ouml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">para</code> </td> <td> U+000B6 </td> <td> <span title="">&para;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">plusmn</code> </td> <td> U+000B1 </td> <td> <span title="">&plusmn;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">pound</code> </td> <td> U+000A3 </td> <td> <span title="">&pound;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">quot</code> </td> <td> U+00022 </td> <td> <span title="">"</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">raquo</code> </td> <td> U+000BB </td> <td> <span title="">&raquo;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">reg</code> </td> <td> U+000AE </td> <td> <span title="">&reg;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">sect</code> </td> <td> U+000A7 </td> <td> <span title="">&sect;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">shy</code> </td> <td> U+000AD </td> <td> <span title="">&shy;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">sup1</code> </td> <td> U+000B9 </td> <td> <span title="">&sup1;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">sup2</code> </td> <td> U+000B2 </td> <td> <span title="">&sup2;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">sup3</code> </td> <td> U+000B3 </td> <td> <span title="">&sup3;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">szlig</code> </td> <td> U+000DF </td> <td> <span title="">&szlig;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">thorn</code> </td> <td> U+000FE </td> <td> <span title="">&thorn;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">times</code> </td> <td> U+000D7 </td> <td> <span title="">&times;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">uacute</code> </td> <td> U+000FA </td> <td> <span title="">&uacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ucirc</code> </td> <td> U+000FB </td> <td> <span title="">&ucirc;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">ugrave</code> </td> <td> U+000F9 </td> <td> <span title="">&ugrave;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">uml</code> </td> <td> U+000A8 </td> <td> <span title="">&uml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">uuml</code> </td> <td> U+000FC </td> <td> <span title="">&uuml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">yacute</code> </td> <td> U+000FD </td> <td> <span title="">&yacute;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">yen</code> </td> <td> U+000A5 </td> <td> <span title="">&yen;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr class="impl"><td> <code title="">yuml</code> </td> <td> U+000FF </td> <td> <span title="">&yuml;</span> </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --></table><!--
+
diff --git a/components/htmlfive/java/htmlparser/doc/tokenization.txt b/components/htmlfive/java/htmlparser/doc/tokenization.txt
new file mode 100644
index 000000000..21cd7f6e2
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/doc/tokenization.txt
@@ -0,0 +1,1147 @@
+ #8.2 Parsing HTML documents Table of contents 8.2.5 Tree construction
+
+ WHATWG
+
+HTML 5
+
+Draft Recommendation — 7 February 2009
+
+ ↠8.2 Parsing HTML documents – Table of contents – 8.2.5 Tree
+ construction →
+
+ 8.2.4 Tokenization
+
+ Implementations must act as if they used the following state machine to
+ tokenise HTML. The state machine must start in the data state. Most
+ states consume a single character, which may have various side-effects,
+ and either switches the state machine to a new state to reconsume the
+ same character, or switches it to a new state (to consume the next
+ character), or repeats the same state (to consume the next character).
+ Some states have more complicated behavior and can consume several
+ characters before switching to another state.
+
+ The exact behavior of certain states depends on a content model flag
+ that is set after certain tokens are emitted. The flag has several
+ states: PCDATA, RCDATA, CDATA, and PLAINTEXT. Initially it must be in
+ the PCDATA state. In the RCDATA and CDATA states, a further escape flag
+ is used to control the behavior of the tokeniser. It is either true or
+ false, and initially must be set to the false state. The insertion mode
+ and the stack of open elements also affects tokenization.
+
+ The output of the tokenization step is a series of zero or more of the
+ following tokens: DOCTYPE, start tag, end tag, comment, character,
+ end-of-file. DOCTYPE tokens have a name, a public identifier, a system
+ identifier, and a force-quirks flag. When a DOCTYPE token is created,
+ its name, public identifier, and system identifier must be marked as
+ missing (which is a distinct state from the empty string), and the
+ force-quirks flag must be set to off (its other state is on). Start and
+ end tag tokens have a tag name, a self-closing flag, and a list of
+ attributes, each of which has a name and a value. When a start or end
+ tag token is created, its self-closing flag must be unset (its other
+ state is that it be set), and its attributes list must be empty.
+ Comment and character tokens have data.
+
+ When a token is emitted, it must immediately be handled by the tree
+ construction stage. The tree construction stage can affect the state of
+ the content model flag, and can insert additional characters into the
+ stream. (For example, the script element can result in scripts
+ executing and using the dynamic markup insertion APIs to insert
+ characters into the stream being tokenised.)
+
+ When a start tag token is emitted with its self-closing flag set, if
+ the flag is not acknowledged when it is processed by the tree
+ construction stage, that is a parse error.
+
+ When an end tag token is emitted, the content model flag must be
+ switched to the PCDATA state.
+
+ When an end tag token is emitted with attributes, that is a parse
+ error.
+
+ When an end tag token is emitted with its self-closing flag set, that
+ is a parse error.
+
+ Before each step of the tokeniser, the user agent must first check the
+ parser pause flag. If it is true, then the tokeniser must abort the
+ processing of any nested invocations of the tokeniser, yielding control
+ back to the caller. If it is false, then the user agent may then check
+ to see if either one of the scripts in the list of scripts that will
+ execute as soon as possible or the first script in the list of scripts
+ that will execute asynchronously, has completed loading. If one has,
+ then it must be executed and removed from its list.
+
+ The tokeniser state machine consists of the states defined in the
+ following subsections.
+
+ 8.2.4.1 Data state
+
+ Consume the next input character:
+
+ U+0026 AMPERSAND (&)
+ When the content model flag is set to one of the PCDATA or
+ RCDATA states and the escape flag is false: switch to the
+ character reference data state.
+ Otherwise: treat it as per the "anything else" entry below.
+
+ U+002D HYPHEN-MINUS (-)
+ If the content model flag is set to either the RCDATA state or
+ the CDATA state, and the escape flag is false, and there are at
+ least three characters before this one in the input stream, and
+ the last four characters in the input stream, including this
+ one, are U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D
+ HYPHEN-MINUS, and U+002D HYPHEN-MINUS ("<!--"), then set the
+ escape flag to true.
+
+ In any case, emit the input character as a character token. Stay
+ in the data state.
+
+ U+003C LESS-THAN SIGN (<)
+ When the content model flag is set to the PCDATA state: switch
+ to the tag open state.
+ When the content model flag is set to either the RCDATA state or
+ the CDATA state, and the escape flag is false: switch to the tag
+ open state.
+ Otherwise: treat it as per the "anything else" entry below.
+
+ U+003E GREATER-THAN SIGN (>)
+ If the content model flag is set to either the RCDATA state or
+ the CDATA state, and the escape flag is true, and the last three
+ characters in the input stream including this one are U+002D
+ HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN
+ ("-->"), set the escape flag to false.
+
+ In any case, emit the input character as a character token. Stay
+ in the data state.
+
+ EOF
+ Emit an end-of-file token.
+
+ Anything else
+ Emit the input character as a character token. Stay in the data
+ state.
+
+ 8.2.4.2 Character reference data state
+
+ (This cannot happen if the content model flag is set to the CDATA
+ state.)
+
+ Attempt to consume a character reference, with no additional allowed
+ character.
+
+ If nothing is returned, emit a U+0026 AMPERSAND character token.
+
+ Otherwise, emit the character token that was returned.
+
+ Finally, switch to the data state.
+
+ 8.2.4.3 Tag open state
+
+ The behavior of this state depends on the content model flag.
+
+ If the content model flag is set to the RCDATA or CDATA states
+ Consume the next input character. If it is a U+002F SOLIDUS (/)
+ character, switch to the close tag open state. Otherwise, emit a
+ U+003C LESS-THAN SIGN character token and reconsume the current
+ input character in the data state.
+
+ If the content model flag is set to the PCDATA state
+ Consume the next input character:
+
+ U+0021 EXCLAMATION MARK (!)
+ Switch to the markup declaration open state.
+
+ U+002F SOLIDUS (/)
+ Switch to the close tag open state.
+
+ U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL
+ LETTER Z
+ Create a new start tag token, set its tag name to the
+ lowercase version of the input character (add 0x0020 to
+ the character's code point), then switch to the tag name
+ state. (Don't emit the token yet; further details will be
+ filled in before it is emitted.)
+
+ U+0061 LATIN SMALL LETTER A through to U+007A LATIN SMALL LETTER Z
+ Create a new start tag token, set its tag name to the
+ input character, then switch to the tag name state. (Don't
+ emit the token yet; further details will be filled in
+ before it is emitted.)
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Emit a U+003C LESS-THAN SIGN character token
+ and a U+003E GREATER-THAN SIGN character token. Switch to
+ the data state.
+
+ U+003F QUESTION MARK (?)
+ Parse error. Switch to the bogus comment state.
+
+ Anything else
+ Parse error. Emit a U+003C LESS-THAN SIGN character token
+ and reconsume the current input character in the data
+ state.
+
+ 8.2.4.4 Close tag open state
+
+ If the content model flag is set to the RCDATA or CDATA states but no
+ start tag token has ever been emitted by this instance of the tokeniser
+ (fragment case), or, if the content model flag is set to the RCDATA or
+ CDATA states and the next few characters do not match the tag name of
+ the last start tag token emitted (compared in an ASCII case-insensitive
+ manner), or if they do but they are not immediately followed by one of
+ the following characters:
+ * U+0009 CHARACTER TABULATION
+ * U+000A LINE FEED (LF)
+ * U+000C FORM FEED (FF)
+ * U+0020 SPACE
+ * U+003E GREATER-THAN SIGN (>)
+ * U+002F SOLIDUS (/)
+ * EOF
+
+ ...then emit a U+003C LESS-THAN SIGN character token, a U+002F SOLIDUS
+ character token, and switch to the data state to process the next input
+ character.
+
+ Otherwise, if the content model flag is set to the PCDATA state, or if
+ the next few characters do match that tag name, consume the next input
+ character:
+
+ U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Create a new end tag token, set its tag name to the lowercase
+ version of the input character (add 0x0020 to the character's
+ code point), then switch to the tag name state. (Don't emit the
+ token yet; further details will be filled in before it is
+ emitted.)
+
+ U+0061 LATIN SMALL LETTER A through to U+007A LATIN SMALL LETTER Z
+ Create a new end tag token, set its tag name to the input
+ character, then switch to the tag name state. (Don't emit the
+ token yet; further details will be filled in before it is
+ emitted.)
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Switch to the data state.
+
+ EOF
+ Parse error. Emit a U+003C LESS-THAN SIGN character token and a
+ U+002F SOLIDUS character token. Reconsume the EOF character in
+ the data state.
+
+ Anything else
+ Parse error. Switch to the bogus comment state.
+
+ 8.2.4.5 Tag name state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the before attribute name state.
+
+ U+002F SOLIDUS (/)
+ Switch to the self-closing start tag state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state.
+
+ U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Append the lowercase version of the current input character (add
+ 0x0020 to the character's code point) to the current tag token's
+ tag name. Stay in the tag name state.
+
+ EOF
+ Parse error. Emit the current tag token. Reconsume the EOF
+ character in the data state.
+
+ Anything else
+ Append the current input character to the current tag token's
+ tag name. Stay in the tag name state.
+
+ 8.2.4.6 Before attribute name state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the before attribute name state.
+
+ U+002F SOLIDUS (/)
+ Switch to the self-closing start tag state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state.
+
+ U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Start a new attribute in the current tag token. Set that
+ attribute's name to the lowercase version of the current input
+ character (add 0x0020 to the character's code point), and its
+ value to the empty string. Switch to the attribute name state.
+
+ U+0022 QUOTATION MARK (")
+ U+0027 APOSTROPHE (')
+ U+003D EQUALS SIGN (=)
+ Parse error. Treat it as per the "anything else" entry below.
+
+ EOF
+ Parse error. Emit the current tag token. Reconsume the EOF
+ character in the data state.
+
+ Anything else
+ Start a new attribute in the current tag token. Set that
+ attribute's name to the current input character, and its value
+ to the empty string. Switch to the attribute name state.
+
+ 8.2.4.7 Attribute name state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the after attribute name state.
+
+ U+002F SOLIDUS (/)
+ Switch to the self-closing start tag state.
+
+ U+003D EQUALS SIGN (=)
+ Switch to the before attribute value state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state.
+
+ U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Append the lowercase version of the current input character (add
+ 0x0020 to the character's code point) to the current attribute's
+ name. Stay in the attribute name state.
+
+ U+0022 QUOTATION MARK (")
+ U+0027 APOSTROPHE (')
+ Parse error. Treat it as per the "anything else" entry below.
+
+ EOF
+ Parse error. Emit the current tag token. Reconsume the EOF
+ character in the data state.
+
+ Anything else
+ Append the current input character to the current attribute's
+ name. Stay in the attribute name state.
+
+ When the user agent leaves the attribute name state (and before
+ emitting the tag token, if appropriate), the complete attribute's name
+ must be compared to the other attributes on the same token; if there is
+ already an attribute on the token with the exact same name, then this
+ is a parse error and the new attribute must be dropped, along with the
+ value that gets associated with it (if any).
+
+ 8.2.4.8 After attribute name state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the after attribute name state.
+
+ U+002F SOLIDUS (/)
+ Switch to the self-closing start tag state.
+
+ U+003D EQUALS SIGN (=)
+ Switch to the before attribute value state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state.
+
+ U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Start a new attribute in the current tag token. Set that
+ attribute's name to the lowercase version of the current input
+ character (add 0x0020 to the character's code point), and its
+ value to the empty string. Switch to the attribute name state.
+
+ U+0022 QUOTATION MARK (")
+ U+0027 APOSTROPHE (')
+ Parse error. Treat it as per the "anything else" entry below.
+
+ EOF
+ Parse error. Emit the current tag token. Reconsume the EOF
+ character in the data state.
+
+ Anything else
+ Start a new attribute in the current tag token. Set that
+ attribute's name to the current input character, and its value
+ to the empty string. Switch to the attribute name state.
+
+ 8.2.4.9 Before attribute value state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the before attribute value state.
+
+ U+0022 QUOTATION MARK (")
+ Switch to the attribute value (double-quoted) state.
+
+ U+0026 AMPERSAND (&)
+ Switch to the attribute value (unquoted) state and reconsume
+ this input character.
+
+ U+0027 APOSTROPHE (')
+ Switch to the attribute value (single-quoted) state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Emit the current tag token. Switch to the data
+ state.
+
+ U+003D EQUALS SIGN (=)
+ Parse error. Treat it as per the "anything else" entry below.
+
+ EOF
+ Parse error. Emit the current tag token. Reconsume the character
+ in the data state.
+
+ Anything else
+ Append the current input character to the current attribute's
+ value. Switch to the attribute value (unquoted) state.
+
+ 8.2.4.10 Attribute value (double-quoted) state
+
+ Consume the next input character:
+
+ U+0022 QUOTATION MARK (")
+ Switch to the after attribute value (quoted) state.
+
+ U+0026 AMPERSAND (&)
+ Switch to the character reference in attribute value state, with
+ the additional allowed character being U+0022 QUOTATION MARK
+ (").
+
+ EOF
+ Parse error. Emit the current tag token. Reconsume the character
+ in the data state.
+
+ Anything else
+ Append the current input character to the current attribute's
+ value. Stay in the attribute value (double-quoted) state.
+
+ 8.2.4.11 Attribute value (single-quoted) state
+
+ Consume the next input character:
+
+ U+0027 APOSTROPHE (')
+ Switch to the after attribute value (quoted) state.
+
+ U+0026 AMPERSAND (&)
+ Switch to the character reference in attribute value state, with
+ the additional allowed character being U+0027 APOSTROPHE (').
+
+ EOF
+ Parse error. Emit the current tag token. Reconsume the character
+ in the data state.
+
+ Anything else
+ Append the current input character to the current attribute's
+ value. Stay in the attribute value (single-quoted) state.
+
+ 8.2.4.12 Attribute value (unquoted) state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the before attribute name state.
+
+ U+0026 AMPERSAND (&)
+ Switch to the character reference in attribute value state, with
+ no additional allowed character.
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state.
+
+ U+0022 QUOTATION MARK (")
+ U+0027 APOSTROPHE (')
+ U+003D EQUALS SIGN (=)
+ Parse error. Treat it as per the "anything else" entry below.
+
+ EOF
+ Parse error. Emit the current tag token. Reconsume the character
+ in the data state.
+
+ Anything else
+ Append the current input character to the current attribute's
+ value. Stay in the attribute value (unquoted) state.
+
+ 8.2.4.13 Character reference in attribute value state
+
+ Attempt to consume a character reference.
+
+ If nothing is returned, append a U+0026 AMPERSAND character to the
+ current attribute's value.
+
+ Otherwise, append the returned character token to the current
+ attribute's value.
+
+ Finally, switch back to the attribute value state that you were in when
+ were switched into this state.
+
+ 8.2.4.14 After attribute value (quoted) state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the before attribute name state.
+
+ U+002F SOLIDUS (/)
+ Switch to the self-closing start tag state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the current tag token. Switch to the data state.
+
+ EOF
+ Parse error. Emit the current tag token. Reconsume the EOF
+ character in the data state.
+
+ Anything else
+ Parse error. Reconsume the character in the before attribute
+ name state.
+
+ 8.2.4.15 Self-closing start tag state
+
+ Consume the next input character:
+
+ U+003E GREATER-THAN SIGN (>)
+ Set the self-closing flag of the current tag token. Emit the
+ current tag token. Switch to the data state.
+
+ EOF
+ Parse error. Emit the current tag token. Reconsume the EOF
+ character in the data state.
+
+ Anything else
+ Parse error. Reconsume the character in the before attribute
+ name state.
+
+ 8.2.4.16 Bogus comment state
+
+ (This can only happen if the content model flag is set to the PCDATA
+ state.)
+
+ Consume every character up to and including the first U+003E
+ GREATER-THAN SIGN character (>) or the end of the file (EOF), whichever
+ comes first. Emit a comment token whose data is the concatenation of
+ all the characters starting from and including the character that
+ caused the state machine to switch into the bogus comment state, up to
+ and including the character immediately before the last consumed
+ character (i.e. up to the character just before the U+003E or EOF
+ character). (If the comment was started by the end of the file (EOF),
+ the token is empty.)
+
+ Switch to the data state.
+
+ If the end of the file was reached, reconsume the EOF character.
+
+ 8.2.4.17 Markup declaration open state
+
+ (This can only happen if the content model flag is set to the PCDATA
+ state.)
+
+ If the next two characters are both U+002D HYPHEN-MINUS (-) characters,
+ consume those two characters, create a comment token whose data is the
+ empty string, and switch to the comment start state.
+
+ Otherwise, if the next seven characters are an ASCII case-insensitive
+ match for the word "DOCTYPE", then consume those characters and switch
+ to the DOCTYPE state.
+
+ Otherwise, if the insertion mode is "in foreign content" and the
+ current node is not an element in the HTML namespace and the next seven
+ characters are an ASCII case-sensitive match for the string "[CDATA["
+ (the five uppercase letters "CDATA" with a U+005B LEFT SQUARE BRACKET
+ character before and after), then consume those characters and switch
+ to the CDATA section state (which is unrelated to the content model
+ flag's CDATA state).
+
+ Otherwise, this is a parse error. Switch to the bogus comment state.
+ The next character that is consumed, if any, is the first character
+ that will be in the comment.
+
+ 8.2.4.18 Comment start state
+
+ Consume the next input character:
+
+ U+002D HYPHEN-MINUS (-)
+ Switch to the comment start dash state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Emit the comment token. Switch to the data state.
+
+ EOF
+ Parse error. Emit the comment token. Reconsume the EOF character
+ in the data state.
+
+ Anything else
+ Append the input character to the comment token's data. Switch
+ to the comment state.
+
+ 8.2.4.19 Comment start dash state
+
+ Consume the next input character:
+
+ U+002D HYPHEN-MINUS (-)
+ Switch to the comment end state
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Emit the comment token. Switch to the data state.
+
+ EOF
+ Parse error. Emit the comment token. Reconsume the EOF character
+ in the data state.
+
+ Anything else
+ Append a U+002D HYPHEN-MINUS (-) character and the input
+ character to the comment token's data. Switch to the comment
+ state.
+
+ 8.2.4.20 Comment state
+
+ Consume the next input character:
+
+ U+002D HYPHEN-MINUS (-)
+ Switch to the comment end dash state
+
+ EOF
+ Parse error. Emit the comment token. Reconsume the EOF character
+ in the data state.
+
+ Anything else
+ Append the input character to the comment token's data. Stay in
+ the comment state.
+
+ 8.2.4.21 Comment end dash state
+
+ Consume the next input character:
+
+ U+002D HYPHEN-MINUS (-)
+ Switch to the comment end state
+
+ EOF
+ Parse error. Emit the comment token. Reconsume the EOF character
+ in the data state.
+
+ Anything else
+ Append a U+002D HYPHEN-MINUS (-) character and the input
+ character to the comment token's data. Switch to the comment
+ state.
+
+ 8.2.4.22 Comment end state
+
+ Consume the next input character:
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the comment token. Switch to the data state.
+
+ U+002D HYPHEN-MINUS (-)
+ Parse error. Append a U+002D HYPHEN-MINUS (-) character to the
+ comment token's data. Stay in the comment end state.
+
+ EOF
+ Parse error. Emit the comment token. Reconsume the EOF character
+ in the data state.
+
+ Anything else
+ Parse error. Append two U+002D HYPHEN-MINUS (-) characters and
+ the input character to the comment token's data. Switch to the
+ comment state.
+
+ 8.2.4.23 DOCTYPE state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the before DOCTYPE name state.
+
+ Anything else
+ Parse error. Reconsume the current character in the before
+ DOCTYPE name state.
+
+ 8.2.4.24 Before DOCTYPE name state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the before DOCTYPE name state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Create a new DOCTYPE token. Set its force-quirks
+ flag to on. Emit the token. Switch to the data state.
+
+ U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Create a new DOCTYPE token. Set the token's name to the
+ lowercase version of the input character (add 0x0020 to the
+ character's code point). Switch to the DOCTYPE name state.
+
+ EOF
+ Parse error. Create a new DOCTYPE token. Set its force-quirks
+ flag to on. Emit the token. Reconsume the EOF character in the
+ data state.
+
+ Anything else
+ Create a new DOCTYPE token. Set the token's name to the current
+ input character. Switch to the DOCTYPE name state.
+
+ 8.2.4.25 DOCTYPE name state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Switch to the after DOCTYPE name state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the current DOCTYPE token. Switch to the data state.
+
+ U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
+ Append the lowercase version of the input character (add 0x0020
+ to the character's code point) to the current DOCTYPE token's
+ name. Stay in the DOCTYPE name state.
+
+ EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ Append the current input character to the current DOCTYPE
+ token's name. Stay in the DOCTYPE name state.
+
+ 8.2.4.26 After DOCTYPE name state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the after DOCTYPE name state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the current DOCTYPE token. Switch to the data state.
+
+ EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ If the six characters starting from the current input character
+ are an ASCII case-insensitive match for the word "PUBLIC", then
+ consume those characters and switch to the before DOCTYPE public
+ identifier state.
+
+ Otherwise, if the six characters starting from the current input
+ character are an ASCII case-insensitive match for the word
+ "SYSTEM", then consume those characters and switch to the before
+ DOCTYPE system identifier state.
+
+ Otherwise, this is the parse error. Set the DOCTYPE token's
+ force-quirks flag to on. Switch to the bogus DOCTYPE state.
+
+ 8.2.4.27 Before DOCTYPE public identifier state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the before DOCTYPE public identifier state.
+
+ U+0022 QUOTATION MARK (")
+ Set the DOCTYPE token's public identifier to the empty string
+ (not missing), then switch to the DOCTYPE public identifier
+ (double-quoted) state.
+
+ U+0027 APOSTROPHE (')
+ Set the DOCTYPE token's public identifier to the empty string
+ (not missing), then switch to the DOCTYPE public identifier
+ (single-quoted) state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Switch to the data state.
+
+ EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Switch to the bogus DOCTYPE state.
+
+ 8.2.4.28 DOCTYPE public identifier (double-quoted) state
+
+ Consume the next input character:
+
+ U+0022 QUOTATION MARK (")
+ Switch to the after DOCTYPE public identifier state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Switch to the data state.
+
+ EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ Append the current input character to the current DOCTYPE
+ token's public identifier. Stay in the DOCTYPE public identifier
+ (double-quoted) state.
+
+ 8.2.4.29 DOCTYPE public identifier (single-quoted) state
+
+ Consume the next input character:
+
+ U+0027 APOSTROPHE (')
+ Switch to the after DOCTYPE public identifier state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Switch to the data state.
+
+ EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ Append the current input character to the current DOCTYPE
+ token's public identifier. Stay in the DOCTYPE public identifier
+ (single-quoted) state.
+
+ 8.2.4.30 After DOCTYPE public identifier state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the after DOCTYPE public identifier state.
+
+ U+0022 QUOTATION MARK (")
+ Set the DOCTYPE token's system identifier to the empty string
+ (not missing), then switch to the DOCTYPE system identifier
+ (double-quoted) state.
+
+ U+0027 APOSTROPHE (')
+ Set the DOCTYPE token's system identifier to the empty string
+ (not missing), then switch to the DOCTYPE system identifier
+ (single-quoted) state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the current DOCTYPE token. Switch to the data state.
+
+ EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Switch to the bogus DOCTYPE state.
+
+ 8.2.4.31 Before DOCTYPE system identifier state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the before DOCTYPE system identifier state.
+
+ U+0022 QUOTATION MARK (")
+ Set the DOCTYPE token's system identifier to the empty string
+ (not missing), then switch to the DOCTYPE system identifier
+ (double-quoted) state.
+
+ U+0027 APOSTROPHE (')
+ Set the DOCTYPE token's system identifier to the empty string
+ (not missing), then switch to the DOCTYPE system identifier
+ (single-quoted) state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Switch to the data state.
+
+ EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Switch to the bogus DOCTYPE state.
+
+ 8.2.4.32 DOCTYPE system identifier (double-quoted) state
+
+ Consume the next input character:
+
+ U+0022 QUOTATION MARK (")
+ Switch to the after DOCTYPE system identifier state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Switch to the data state.
+
+ EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ Append the current input character to the current DOCTYPE
+ token's system identifier. Stay in the DOCTYPE system identifier
+ (double-quoted) state.
+
+ 8.2.4.33 DOCTYPE system identifier (single-quoted) state
+
+ Consume the next input character:
+
+ U+0027 APOSTROPHE (')
+ Switch to the after DOCTYPE system identifier state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Switch to the data state.
+
+ EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ Append the current input character to the current DOCTYPE
+ token's system identifier. Stay in the DOCTYPE system identifier
+ (single-quoted) state.
+
+ 8.2.4.34 After DOCTYPE system identifier state
+
+ Consume the next input character:
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ Stay in the after DOCTYPE system identifier state.
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the current DOCTYPE token. Switch to the data state.
+
+ EOF
+ Parse error. Set the DOCTYPE token's force-quirks flag to on.
+ Emit that DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ Parse error. Switch to the bogus DOCTYPE state. (This does not
+ set the DOCTYPE token's force-quirks flag to on.)
+
+ 8.2.4.35 Bogus DOCTYPE state
+
+ Consume the next input character:
+
+ U+003E GREATER-THAN SIGN (>)
+ Emit the DOCTYPE token. Switch to the data state.
+
+ EOF
+ Emit the DOCTYPE token. Reconsume the EOF character in the data
+ state.
+
+ Anything else
+ Stay in the bogus DOCTYPE state.
+
+ 8.2.4.36 CDATA section state
+
+ (This can only happen if the content model flag is set to the PCDATA
+ state, and is unrelated to the content model flag's CDATA state.)
+
+ Consume every character up to the next occurrence of the three
+ character sequence U+005D RIGHT SQUARE BRACKET U+005D RIGHT SQUARE
+ BRACKET U+003E GREATER-THAN SIGN (]]>), or the end of the file (EOF),
+ whichever comes first. Emit a series of character tokens consisting of
+ all the characters consumed except the matching three character
+ sequence at the end (if one was found before the end of the file).
+
+ Switch to the data state.
+
+ If the end of the file was reached, reconsume the EOF character.
+
+ 8.2.4.37 Tokenizing character references
+
+ This section defines how to consume a character reference. This
+ definition is used when parsing character references in text and in
+ attributes.
+
+ The behavior depends on the identity of the next character (the one
+ immediately after the U+0026 AMPERSAND character):
+
+ U+0009 CHARACTER TABULATION
+ U+000A LINE FEED (LF)
+ U+000C FORM FEED (FF)
+ U+0020 SPACE
+ U+003C LESS-THAN SIGN
+ U+0026 AMPERSAND
+ EOF
+ The additional allowed character, if there is one
+ Not a character reference. No characters are consumed, and
+ nothing is returned. (This is not an error, either.)
+
+ U+0023 NUMBER SIGN (#)
+ Consume the U+0023 NUMBER SIGN.
+
+ The behavior further depends on the character after the U+0023
+ NUMBER SIGN:
+
+ U+0078 LATIN SMALL LETTER X
+ U+0058 LATIN CAPITAL LETTER X
+ Consume the X.
+
+ Follow the steps below, but using the range of characters
+ U+0030 DIGIT ZERO through to U+0039 DIGIT NINE, U+0061
+ LATIN SMALL LETTER A through to U+0066 LATIN SMALL LETTER
+ F, and U+0041 LATIN CAPITAL LETTER A, through to U+0046
+ LATIN CAPITAL LETTER F (in other words, 0-9, A-F, a-f).
+
+ When it comes to interpreting the number, interpret it as
+ a hexadecimal number.
+
+ Anything else
+ Follow the steps below, but using the range of characters
+ U+0030 DIGIT ZERO through to U+0039 DIGIT NINE (i.e. just
+ 0-9).
+
+ When it comes to interpreting the number, interpret it as
+ a decimal number.
+
+ Consume as many characters as match the range of characters
+ given above.
+
+ If no characters match the range, then don't consume any
+ characters (and unconsume the U+0023 NUMBER SIGN character and,
+ if appropriate, the X character). This is a parse error; nothing
+ is returned.
+
+ Otherwise, if the next character is a U+003B SEMICOLON, consume
+ that too. If it isn't, there is a parse error.
+
+ If one or more characters match the range, then take them all
+ and interpret the string of characters as a number (either
+ hexadecimal or decimal as appropriate).
+
+ If that number is one of the numbers in the first column of the
+ following table, then this is a parse error. Find the row with
+ that number in the first column, and return a character token
+ for the Unicode character given in the second column of that
+ row.
+
+ Number Unicode character
+ 0x0D U+000A LINE FEED (LF)
+ 0x80 U+20AC EURO SIGN ('€')
+ 0x81 U+FFFD REPLACEMENT CHARACTER
+ 0x82 U+201A SINGLE LOW-9 QUOTATION MARK ('‚')
+ 0x83 U+0192 LATIN SMALL LETTER F WITH HOOK ('Æ’')
+ 0x84 U+201E DOUBLE LOW-9 QUOTATION MARK ('„')
+ 0x85 U+2026 HORIZONTAL ELLIPSIS ('…')
+ 0x86 U+2020 DAGGER ('†')
+ 0x87 U+2021 DOUBLE DAGGER ('‡')
+ 0x88 U+02C6 MODIFIER LETTER CIRCUMFLEX ACCENT ('ˆ')
+ 0x89 U+2030 PER MILLE SIGN ('‰')
+ 0x8A U+0160 LATIN CAPITAL LETTER S WITH CARON ('Å ')
+ 0x8B U+2039 SINGLE LEFT-POINTING ANGLE QUOTATION MARK ('‹')
+ 0x8C U+0152 LATIN CAPITAL LIGATURE OE ('Å’')
+ 0x8D U+FFFD REPLACEMENT CHARACTER
+ 0x8E U+017D LATIN CAPITAL LETTER Z WITH CARON ('Ž')
+ 0x8F U+FFFD REPLACEMENT CHARACTER
+ 0x90 U+FFFD REPLACEMENT CHARACTER
+ 0x91 U+2018 LEFT SINGLE QUOTATION MARK ('‘')
+ 0x92 U+2019 RIGHT SINGLE QUOTATION MARK ('’')
+ 0x93 U+201C LEFT DOUBLE QUOTATION MARK ('“')
+ 0x94 U+201D RIGHT DOUBLE QUOTATION MARK ('â€')
+ 0x95 U+2022 BULLET ('•')
+ 0x96 U+2013 EN DASH ('–')
+ 0x97 U+2014 EM DASH ('—')
+ 0x98 U+02DC SMALL TILDE ('˜')
+ 0x99 U+2122 TRADE MARK SIGN ('â„¢')
+ 0x9A U+0161 LATIN SMALL LETTER S WITH CARON ('Å¡')
+ 0x9B U+203A SINGLE RIGHT-POINTING ANGLE QUOTATION MARK ('›')
+ 0x9C U+0153 LATIN SMALL LIGATURE OE ('Å“')
+ 0x9D U+FFFD REPLACEMENT CHARACTER
+ 0x9E U+017E LATIN SMALL LETTER Z WITH CARON ('ž')
+ 0x9F U+0178 LATIN CAPITAL LETTER Y WITH DIAERESIS ('Ÿ')
+
+ Otherwise, if the number is in the range 0x0000 to 0x0008,
+ 0x000E to 0x001F, 0x007F to 0x009F, 0xD800 to 0xDFFF, 0xFDD0 to
+ 0xFDEF, or is one of 0x000B, 0xFFFE, 0xFFFF, 0x1FFFE, 0x1FFFF,
+ 0x2FFFE, 0x2FFFF, 0x3FFFE, 0x3FFFF, 0x4FFFE, 0x4FFFF, 0x5FFFE,
+ 0x5FFFF, 0x6FFFE, 0x6FFFF, 0x7FFFE, 0x7FFFF, 0x8FFFE, 0x8FFFF,
+ 0x9FFFE, 0x9FFFF, 0xAFFFE, 0xAFFFF, 0xBFFFE, 0xBFFFF, 0xCFFFE,
+ 0xCFFFF, 0xDFFFE, 0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF,
+ 0x10FFFE, or 0x10FFFF, or is higher than 0x10FFFF, then this is
+ a parse error; return a character token for the U+FFFD
+ REPLACEMENT CHARACTER character instead.
+
+ Otherwise, return a character token for the Unicode character
+ whose code point is that number.
+
+ Anything else
+ Consume the maximum number of characters possible, with the
+ consumed characters matching one of the identifiers in the first
+ column of the named character references table (in a
+ case-sensitive manner).
+
+ If no match can be made, then this is a parse error. No
+ characters are consumed, and nothing is returned.
+
+ If the last character matched is not a U+003B SEMICOLON (;),
+ there is a parse error.
+
+ If the character reference is being consumed as part of an
+ attribute, and the last character matched is not a U+003B
+ SEMICOLON (;), and the next character is in the range U+0030
+ DIGIT ZERO to U+0039 DIGIT NINE, U+0041 LATIN CAPITAL LETTER A
+ to U+005A LATIN CAPITAL LETTER Z, or U+0061 LATIN SMALL LETTER A
+ to U+007A LATIN SMALL LETTER Z, then, for historical reasons,
+ all the characters that were matched after the U+0026 AMPERSAND
+ (&) must be unconsumed, and nothing is returned.
+
+ Otherwise, return a character token for the character
+ corresponding to the character reference name (as given by the
+ second column of the named character references table).
+
+ If the markup contains I'm &notit; I tell you, the character
+ reference is parsed as "not", as in, I'm ¬it; I tell you. But if
+ the markup was I'm &notin; I tell you, the character reference
+ would be parsed as "notin;", resulting in I'm ∉ I tell you.
diff --git a/components/htmlfive/java/htmlparser/doc/tree-construction.txt b/components/htmlfive/java/htmlparser/doc/tree-construction.txt
new file mode 100644
index 000000000..0febf147a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/doc/tree-construction.txt
@@ -0,0 +1,2201 @@
+ #8.2.4 Tokenization Table of contents 8.4 Serializing HTML fragments
+
+ WHATWG
+
+HTML 5
+
+Draft Recommendation — 13 January 2009
+
+ ↠8.2.4 Tokenization – Table of contents – 8.4 Serializing HTML
+ fragments →
+
+ 8.2.5 Tree construction
+
+ The input to the tree construction stage is a sequence of tokens from
+ the tokenization stage. The tree construction stage is associated with
+ a DOM Document object when a parser is created. The "output" of this
+ stage consists of dynamically modifying or extending that document's
+ DOM tree.
+
+ This specification does not define when an interactive user agent has
+ to render the Document so that it is available to the user, or when it
+ has to begin accepting user input.
+
+ As each token is emitted from the tokeniser, the user agent must
+ process the token according to the rules given in the section
+ corresponding to the current insertion mode.
+
+ When the steps below require the UA to insert a character into a node,
+ if that node has a child immediately before where the character is to
+ be inserted, and that child is a Text node, and that Text node was the
+ last node that the parser inserted into the document, then the
+ character must be appended to that Text node; otherwise, a new Text
+ node whose data is just that character must be inserted in the
+ appropriate place.
+
+ DOM mutation events must not fire for changes caused by the UA parsing
+ the document. (Conceptually, the parser is not mutating the DOM, it is
+ constructing it.) This includes the parsing of any content inserted
+ using document.write() and document.writeln() calls. [DOM3EVENTS]
+
+ Not all of the tag names mentioned below are conformant tag names in
+ this specification; many are included to handle legacy content. They
+ still form part of the algorithm that implementations are required to
+ implement to claim conformance.
+
+ The algorithm described below places no limit on the depth of the DOM
+ tree generated, or on the length of tag names, attribute names,
+ attribute values, text nodes, etc. While implementors are encouraged to
+ avoid arbitrary limits, it is recognized that practical concerns will
+ likely force user agents to impose nesting depths.
+
+ 8.2.5.1 Creating and inserting elements
+
+ When the steps below require the UA to create an element for a token in
+ a particular namespace, the UA must create a node implementing the
+ interface appropriate for the element type corresponding to the tag
+ name of the token in the given namespace (as given in the specification
+ that defines that element, e.g. for an a element in the HTML namespace,
+ this specification defines it to be the HTMLAnchorElement interface),
+ with the tag name being the name of that element, with the node being
+ in the given namespace, and with the attributes on the node being those
+ given in the given token.
+
+ The interface appropriate for an element in the HTML namespace that is
+ not defined in this specification is HTMLElement. The interface
+ appropriate for an element in another namespace that is not defined by
+ that namespace's specification is Element.
+
+ When a resettable element is created in this manner, its reset
+ algorithm must be invoked once the attributes are set. (This
+ initializes the element's value and checkedness based on the element's
+ attributes.)
+ __________________________________________________________________
+
+ When the steps below require the UA to insert an HTML element for a
+ token, the UA must first create an element for the token in the HTML
+ namespace, and then append this node to the current node, and push it
+ onto the stack of open elements so that it is the new current node.
+
+ The steps below may also require that the UA insert an HTML element in
+ a particular place, in which case the UA must follow the same steps
+ except that it must insert or append the new node in the location
+ specified instead of appending it to the current node. (This happens in
+ particular during the parsing of tables with invalid content.)
+
+ If an element created by the insert an HTML element algorithm is a
+ form-associated element, and the form element pointer is not null, and
+ the newly created element doesn't have a form attribute, the user agent
+ must associate the newly created element with the form element pointed
+ to by the form element pointer before inserting it wherever it is to be
+ inserted.
+ __________________________________________________________________
+
+ When the steps below require the UA to insert a foreign element for a
+ token, the UA must first create an element for the token in the given
+ namespace, and then append this node to the current node, and push it
+ onto the stack of open elements so that it is the new current node. If
+ the newly created element has an xmlns attribute in the XMLNS namespace
+ whose value is not exactly the same as the element's namespace, that is
+ a parse error.
+
+ When the steps below require the user agent to adjust MathML attributes
+ for a token, then, if the token has an attribute named definitionurl,
+ change its name to definitionURL (note the case difference).
+
+ When the steps below require the user agent to adjust foreign
+ attributes for a token, then, if any of the attributes on the token
+ match the strings given in the first column of the following table, let
+ the attribute be a namespaced attribute, with the prefix being the
+ string given in the corresponding cell in the second column, the local
+ name being the string given in the corresponding cell in the third
+ column, and the namespace being the namespace given in the
+ corresponding cell in the fourth column. (This fixes the use of
+ namespaced attributes, in particular xml:lang.)
+
+ Attribute name Prefix Local name Namespace
+ xlink:actuate xlink actuate XLink namespace
+ xlink:arcrole xlink arcrole XLink namespace
+ xlink:href xlink href XLink namespace
+ xlink:role xlink role XLink namespace
+ xlink:show xlink show XLink namespace
+ xlink:title xlink title XLink namespace
+ xlink:type xlink type XLink namespace
+ xml:base xml base XML namespace
+ xml:lang xml lang XML namespace
+ xml:space xml space XML namespace
+ xmlns (none) xmlns XMLNS namespace
+ xmlns:xlink xmlns xlink XMLNS namespace
+ __________________________________________________________________
+
+ The generic CDATA element parsing algorithm and the generic RCDATA
+ element parsing algorithm consist of the following steps. These
+ algorithms are always invoked in response to a start tag token.
+ 1. Insert an HTML element for the token.
+ 2. If the algorithm that was invoked is the generic CDATA element
+ parsing algorithm, switch the tokeniser's content model flag to the
+ CDATA state; otherwise the algorithm invoked was the generic RCDATA
+ element parsing algorithm, switch the tokeniser's content model
+ flag to the RCDATA state.
+ 3. Let the original insertion mode be the current insertion mode.
+ 4. Then, switch the insertion mode to "in CDATA/RCDATA".
+
+ 8.2.5.2 Closing elements that have implied end tags
+
+ When the steps below require the UA to generate implied end tags, then,
+ while the current node is a dd element, a dt element, an li element, an
+ option element, an optgroup element, a p element, an rp element, or an
+ rt element, the UA must pop the current node off the stack of open
+ elements.
+
+ If a step requires the UA to generate implied end tags but lists an
+ element to exclude from the process, then the UA must perform the above
+ steps as if that element was not in the above list.
+
+ 8.2.5.3 Foster parenting
+
+ Foster parenting happens when content is misnested in tables.
+
+ When a node node is to be foster parented, the node node must be
+ inserted into the foster parent element, and the current table must be
+ marked as tainted. (Once the current table has been tainted, whitespace
+ characters are inserted into the foster parent element instead of the
+ current node.)
+
+ The foster parent element is the parent element of the last table
+ element in the stack of open elements, if there is a table element and
+ it has such a parent element. If there is no table element in the stack
+ of open elements (fragment case), then the foster parent element is the
+ first element in the stack of open elements (the html element).
+ Otherwise, if there is a table element in the stack of open elements,
+ but the last table element in the stack of open elements has no parent,
+ or its parent node is not an element, then the foster parent element is
+ the element before the last table element in the stack of open
+ elements.
+
+ If the foster parent element is the parent element of the last table
+ element in the stack of open elements, then node must be inserted
+ immediately before the last table element in the stack of open elements
+ in the foster parent element; otherwise, node must be appended to the
+ foster parent element.
+
+ 8.2.5.4 The "initial" insertion mode
+
+ When the insertion mode is "initial", tokens must be handled as
+ follows:
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ Ignore the token.
+
+ A comment token
+ Append a Comment node to the Document object with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ If the DOCTYPE token's name is not a case-sensitive match for
+ the string "html", or if the token's public identifier is
+ neither missing nor a case-sensitive match for the string
+ "XSLT-compat", or if the token's system identifier is not
+ missing, then there is a parse error (this is the DOCTYPE parse
+ error). Conformance checkers may, instead of reporting this
+ error, switch to a conformance checking mode for another
+ language (e.g. based on the DOCTYPE token a conformance checker
+ could recognize that the document is an HTML4-era document, and
+ defer to an HTML4 conformance checker.)
+
+ Append a DocumentType node to the Document node, with the name
+ attribute set to the name given in the DOCTYPE token; the
+ publicId attribute set to the public identifier given in the
+ DOCTYPE token, or the empty string if the public identifier was
+ missing; the systemId attribute set to the system identifier
+ given in the DOCTYPE token, or the empty string if the system
+ identifier was missing; and the other attributes specific to
+ DocumentType objects set to null and empty lists as appropriate.
+ Associate the DocumentType node with the Document object so that
+ it is returned as the value of the doctype attribute of the
+ Document object.
+
+ Then, if the DOCTYPE token matches one of the conditions in the
+ following list, then set the document to quirks mode:
+
+ + The force-quirks flag is set to on.
+ + The name is set to anything other than "HTML".
+ + The public identifier starts with: "+//Silmaril//dtd html Pro
+ v0r11 19970101//"
+ + The public identifier starts with: "-//AdvaSoft Ltd//DTD HTML
+ 3.0 asWedit + extensions//"
+ + The public identifier starts with: "-//AS//DTD HTML 3.0
+ asWedit + extensions//"
+ + The public identifier starts with: "-//IETF//DTD HTML 2.0
+ Level 1//"
+ + The public identifier starts with: "-//IETF//DTD HTML 2.0
+ Level 2//"
+ + The public identifier starts with: "-//IETF//DTD HTML 2.0
+ Strict Level 1//"
+ + The public identifier starts with: "-//IETF//DTD HTML 2.0
+ Strict Level 2//"
+ + The public identifier starts with: "-//IETF//DTD HTML 2.0
+ Strict//"
+ + The public identifier starts with: "-//IETF//DTD HTML 2.0//"
+ + The public identifier starts with: "-//IETF//DTD HTML 2.1E//"
+ + The public identifier starts with: "-//IETF//DTD HTML 3.0//"
+ + The public identifier starts with: "-//IETF//DTD HTML 3.2
+ Final//"
+ + The public identifier starts with: "-//IETF//DTD HTML 3.2//"
+ + The public identifier starts with: "-//IETF//DTD HTML 3//"
+ + The public identifier starts with: "-//IETF//DTD HTML Level
+ 0//"
+ + The public identifier starts with: "-//IETF//DTD HTML Level
+ 1//"
+ + The public identifier starts with: "-//IETF//DTD HTML Level
+ 2//"
+ + The public identifier starts with: "-//IETF//DTD HTML Level
+ 3//"
+ + The public identifier starts with: "-//IETF//DTD HTML Strict
+ Level 0//"
+ + The public identifier starts with: "-//IETF//DTD HTML Strict
+ Level 1//"
+ + The public identifier starts with: "-//IETF//DTD HTML Strict
+ Level 2//"
+ + The public identifier starts with: "-//IETF//DTD HTML Strict
+ Level 3//"
+ + The public identifier starts with: "-//IETF//DTD HTML
+ Strict//"
+ + The public identifier starts with: "-//IETF//DTD HTML//"
+ + The public identifier starts with: "-//Metrius//DTD Metrius
+ Presentational//"
+ + The public identifier starts with: "-//Microsoft//DTD Internet
+ Explorer 2.0 HTML Strict//"
+ + The public identifier starts with: "-//Microsoft//DTD Internet
+ Explorer 2.0 HTML//"
+ + The public identifier starts with: "-//Microsoft//DTD Internet
+ Explorer 2.0 Tables//"
+ + The public identifier starts with: "-//Microsoft//DTD Internet
+ Explorer 3.0 HTML Strict//"
+ + The public identifier starts with: "-//Microsoft//DTD Internet
+ Explorer 3.0 HTML//"
+ + The public identifier starts with: "-//Microsoft//DTD Internet
+ Explorer 3.0 Tables//"
+ + The public identifier starts with: "-//Netscape Comm.
+ Corp.//DTD HTML//"
+ + The public identifier starts with: "-//Netscape Comm.
+ Corp.//DTD Strict HTML//"
+ + The public identifier starts with: "-//O'Reilly and
+ Associates//DTD HTML 2.0//"
+ + The public identifier starts with: "-//O'Reilly and
+ Associates//DTD HTML Extended 1.0//"
+ + The public identifier starts with: "-//O'Reilly and
+ Associates//DTD HTML Extended Relaxed 1.0//"
+ + The public identifier starts with: "-//SoftQuad Software//DTD
+ HoTMetaL PRO 6.0::19990601::extensions to HTML 4.0//"
+ + The public identifier starts with: "-//SoftQuad//DTD HoTMetaL
+ PRO 4.0::19971010::extensions to HTML 4.0//"
+ + The public identifier starts with: "-//Spyglass//DTD HTML 2.0
+ Extended//"
+ + The public identifier starts with: "-//SQ//DTD HTML 2.0
+ HoTMetaL + extensions//"
+ + The public identifier starts with: "-//Sun Microsystems
+ Corp.//DTD HotJava HTML//"
+ + The public identifier starts with: "-//Sun Microsystems
+ Corp.//DTD HotJava Strict HTML//"
+ + The public identifier starts with: "-//W3C//DTD HTML 3
+ 1995-03-24//"
+ + The public identifier starts with: "-//W3C//DTD HTML 3.2
+ Draft//"
+ + The public identifier starts with: "-//W3C//DTD HTML 3.2
+ Final//"
+ + The public identifier starts with: "-//W3C//DTD HTML 3.2//"
+ + The public identifier starts with: "-//W3C//DTD HTML 3.2S
+ Draft//"
+ + The public identifier starts with: "-//W3C//DTD HTML 4.0
+ Frameset//"
+ + The public identifier starts with: "-//W3C//DTD HTML 4.0
+ Transitional//"
+ + The public identifier starts with: "-//W3C//DTD HTML
+ Experimental 19960712//"
+ + The public identifier starts with: "-//W3C//DTD HTML
+ Experimental 970421//"
+ + The public identifier starts with: "-//W3C//DTD W3 HTML//"
+ + The public identifier starts with: "-//W3O//DTD W3 HTML 3.0//"
+ + The public identifier is set to: "-//W3O//DTD W3 HTML Strict
+ 3.0//EN//"
+ + The public identifier starts with: "-//WebTechs//DTD Mozilla
+ HTML 2.0//"
+ + The public identifier starts with: "-//WebTechs//DTD Mozilla
+ HTML//"
+ + The public identifier is set to: "-/W3C/DTD HTML 4.0
+ Transitional/EN"
+ + The public identifier is set to: "HTML"
+ + The system identifier is set to:
+ "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"
+ + The system identifier is missing and the public identifier
+ starts with: "-//W3C//DTD HTML 4.01 Frameset//"
+ + The system identifier is missing and the public identifier
+ starts with: "-//W3C//DTD HTML 4.01 Transitional//"
+
+ Otherwise, if the DOCTYPE token matches one of the conditions in
+ the following list, then set the document to limited quirks
+ mode:
+
+ + The public identifier starts with: "-//W3C//DTD XHTML 1.0
+ Frameset//"
+ + The public identifier starts with: "-//W3C//DTD XHTML 1.0
+ Transitional//"
+ + The system identifier is not missing and the public identifier
+ starts with: "-//W3C//DTD HTML 4.01 Frameset//"
+ + The system identifier is not missing and the public identifier
+ starts with: "-//W3C//DTD HTML 4.01 Transitional//"
+
+ The name, system identifier, and public identifier strings must
+ be compared to the values given in the lists above in an ASCII
+ case-insensitive manner. A system identifier whose value is the
+ empty string is not considered missing for the purposes of the
+ conditions above.
+
+ Then, switch the insertion mode to "before html".
+
+ Anything else
+ Parse error.
+
+ Set the document to quirks mode.
+
+ Switch the insertion mode to "before html", then reprocess the
+ current token.
+
+ 8.2.5.5 The "before html" insertion mode
+
+ When the insertion mode is "before html", tokens must be handled as
+ follows:
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A comment token
+ Append a Comment node to the Document object with the data
+ attribute set to the data given in the comment token.
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ Ignore the token.
+
+ A start tag whose tag name is "html"
+ Create an element for the token in the HTML namespace. Append it
+ to the Document object. Put this element in the stack of open
+ elements.
+
+ If the token has an attribute "manifest", then resolve the value
+ of that attribute to an absolute URL, and if that is successful,
+ run the application cache selection algorithm with the resulting
+ absolute URL. Otherwise, if there is no such attribute or
+ resolving it fails, run the application cache selection
+ algorithm with no manifest. The algorithm must be passed the
+ Document object.
+
+ Switch the insertion mode to "before head".
+
+ Anything else
+ Create an HTMLElement node with the tag name html, in the HTML
+ namespace. Append it to the Document object. Put this element in
+ the stack of open elements.
+
+ Run the application cache selection algorithm with no manifest,
+ passing it the Document object.
+
+ Switch the insertion mode to "before head", then reprocess the
+ current token.
+
+ Should probably make end tags be ignored, so that "</head><!--
+ --><html>" puts the comment before the root node (or should we?)
+
+ The root element can end up being removed from the Document object,
+ e.g. by scripts; nothing in particular happens in such cases, content
+ continues being appended to the nodes as described in the next section.
+
+ 8.2.5.6 The "before head" insertion mode
+
+ When the insertion mode is "before head", tokens must be handled as
+ follows:
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ Ignore the token.
+
+ A comment token
+ Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ A start tag whose tag name is "head"
+ Insert an HTML element for the token.
+
+ Set the head element pointer to the newly created head element.
+
+ Switch the insertion mode to "in head".
+
+ An end tag whose tag name is one of: "head", "br"
+ Act as if a start tag token with the tag name "head" and no
+ attributes had been seen, then reprocess the current token.
+
+ Any other end tag
+ Parse error. Ignore the token.
+
+ Anything else
+ Act as if a start tag token with the tag name "head" and no
+ attributes had been seen, then reprocess the current token.
+
+ This will result in an empty head element being generated, with
+ the current token being reprocessed in the "after head"
+ insertion mode.
+
+ 8.2.5.7 The "in head" insertion mode
+
+ When the insertion mode is "in head", tokens must be handled as
+ follows:
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ Insert the character into the current node.
+
+ A comment token
+ Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ A start tag whose tag name is one of: "base", "command", "eventsource",
+ "link"
+ Insert an HTML element for the token. Immediately pop the
+ current node off the stack of open elements.
+
+ Acknowledge the token's self-closing flag, if it is set.
+
+ A start tag whose tag name is "meta"
+ Insert an HTML element for the token. Immediately pop the
+ current node off the stack of open elements.
+
+ Acknowledge the token's self-closing flag, if it is set.
+
+ If the element has a charset attribute, and its value is a
+ supported encoding, and the confidence is currently tentative,
+ then change the encoding to the encoding given by the value of
+ the charset attribute.
+
+ Otherwise, if the element has a content attribute, and applying
+ the algorithm for extracting an encoding from a Content-Type to
+ its value returns a supported encoding encoding, and the
+ confidence is currently tentative, then change the encoding to
+ the encoding encoding.
+
+ A start tag whose tag name is "title"
+ Follow the generic RCDATA element parsing algorithm.
+
+ A start tag whose tag name is "noscript", if the scripting flag is
+ enabled
+
+ A start tag whose tag name is one of: "noframes", "style"
+ Follow the generic CDATA element parsing algorithm.
+
+ A start tag whose tag name is "noscript", if the scripting flag is
+ disabled
+ Insert an HTML element for the token.
+
+ Switch the insertion mode to "in head noscript".
+
+ A start tag whose tag name is "script"
+
+ 1. Create an element for the token in the HTML namespace.
+ 2. Mark the element as being "parser-inserted".
+ This ensures that, if the script is external, any
+ document.write() calls in the script will execute in-line,
+ instead of blowing the document away, as would happen in most
+ other cases. It also prevents the script from executing until
+ the end tag is seen.
+ 3. If the parser was originally created for the HTML fragment
+ parsing algorithm, then mark the script element as "already
+ executed". (fragment case)
+ 4. Append the new element to the current node.
+ 5. Switch the tokeniser's content model flag to the CDATA state.
+ 6. Let the original insertion mode be the current insertion mode.
+ 7. Switch the insertion mode to "in CDATA/RCDATA".
+
+ An end tag whose tag name is "head"
+ Pop the current node (which will be the head element) off the
+ stack of open elements.
+
+ Switch the insertion mode to "after head".
+
+ An end tag whose tag name is "br"
+ Act as described in the "anything else" entry below.
+
+ A start tag whose tag name is "head"
+ Any other end tag
+ Parse error. Ignore the token.
+
+ Anything else
+ Act as if an end tag token with the tag name "head" had been
+ seen, and reprocess the current token.
+
+ In certain UAs, some elements don't trigger the "in body" mode
+ straight away, but instead get put into the head. Do we want to
+ copy that?
+
+ 8.2.5.8 The "in head noscript" insertion mode
+
+ When the insertion mode is "in head noscript", tokens must be handled
+ as follows:
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ An end tag whose tag name is "noscript"
+ Pop the current node (which will be a noscript element) from the
+ stack of open elements; the new current node will be a head
+ element.
+
+ Switch the insertion mode to "in head".
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+
+ A comment token
+ A start tag whose tag name is one of: "link", "meta", "noframes",
+ "style"
+ Process the token using the rules for the "in head" insertion
+ mode.
+
+ An end tag whose tag name is "br"
+ Act as described in the "anything else" entry below.
+
+ A start tag whose tag name is one of: "head", "noscript"
+ Any other end tag
+ Parse error. Ignore the token.
+
+ Anything else
+ Parse error. Act as if an end tag with the tag name "noscript"
+ had been seen and reprocess the current token.
+
+ 8.2.5.9 The "after head" insertion mode
+
+ When the insertion mode is "after head", tokens must be handled as
+ follows:
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ Insert the character into the current node.
+
+ A comment token
+ Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ A start tag whose tag name is "body"
+ Insert an HTML element for the token.
+
+ Switch the insertion mode to "in body".
+
+ A start tag whose tag name is "frameset"
+ Insert an HTML element for the token.
+
+ Switch the insertion mode to "in frameset".
+
+ A start tag token whose tag name is one of: "base", "link", "meta",
+ "noframes", "script", "style", "title"
+ Parse error.
+
+ Push the node pointed to by the head element pointer onto the
+ stack of open elements.
+
+ Process the token using the rules for the "in head" insertion
+ mode.
+
+ Remove the node pointed to by the head element pointer from the
+ stack of open elements.
+
+ An end tag whose tag name is "br"
+ Act as described in the "anything else" entry below.
+
+ A start tag whose tag name is "head"
+ Any other end tag
+ Parse error. Ignore the token.
+
+ Anything else
+ Act as if a start tag token with the tag name "body" and no
+ attributes had been seen, and then reprocess the current token.
+
+ 8.2.5.10 The "in body" insertion mode
+
+ When the insertion mode is "in body", tokens must be handled as
+ follows:
+
+ A character token
+ Reconstruct the active formatting elements, if any.
+
+ Insert the token's character into the current node.
+
+ A comment token
+ Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "html"
+ Parse error. For each attribute on the token, check to see if
+ the attribute is already present on the top element of the stack
+ of open elements. If it is not, add the attribute and its
+ corresponding value to that element.
+
+ A start tag token whose tag name is one of: "base", "command",
+ "eventsource", "link", "meta", "noframes", "script", "style",
+ "title"
+ Process the token using the rules for the "in head" insertion
+ mode.
+
+ A start tag whose tag name is "body"
+ Parse error.
+
+ If the second element on the stack of open elements is not a
+ body element, or, if the stack of open elements has only one
+ node on it, then ignore the token. (fragment case)
+
+ Otherwise, for each attribute on the token, check to see if the
+ attribute is already present on the body element (the second
+ element) on the stack of open elements. If it is not, add the
+ attribute and its corresponding value to that element.
+
+ An end-of-file token
+ If there is a node in the stack of open elements that is not
+ either a dd element, a dt element, an li element, a p element, a
+ tbody element, a td element, a tfoot element, a th element, a
+ thead element, a tr element, the body element, or the html
+ element, then this is a parse error.
+
+ Stop parsing.
+
+ An end tag whose tag name is "body"
+ If the stack of open elements does not have a body element in
+ scope, this is a parse error; ignore the token.
+
+ Otherwise, if there is a node in the stack of open elements that
+ is not either a dd element, a dt element, an li element, a p
+ element, a tbody element, a td element, a tfoot element, a th
+ element, a thead element, a tr element, the body element, or the
+ html element, then this is a parse error.
+
+ Switch the insertion mode to "after body".
+
+ An end tag whose tag name is "html"
+ Act as if an end tag with tag name "body" had been seen, then,
+ if that token wasn't ignored, reprocess the current token.
+
+ The fake end tag token here can only be ignored in the fragment
+ case.
+
+ A start tag whose tag name is one of: "address", "article", "aside",
+ "blockquote", "center", "datagrid", "details", "dialog", "dir",
+ "div", "dl", "fieldset", "figure", "footer", "header", "menu",
+ "nav", "ol", "p", "section", "ul"
+ If the stack of open elements has a p element in scope, then act
+ as if an end tag with the tag name "p" had been seen.
+
+ Insert an HTML element for the token.
+
+ A start tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5",
+ "h6"
+ If the stack of open elements has a p element in scope, then act
+ as if an end tag with the tag name "p" had been seen.
+
+ If the current node is an element whose tag name is one of "h1",
+ "h2", "h3", "h4", "h5", or "h6", then this is a parse error; pop
+ the current node off the stack of open elements.
+
+ Insert an HTML element for the token.
+
+ A start tag whose tag name is one of: "pre", "listing"
+ If the stack of open elements has a p element in scope, then act
+ as if an end tag with the tag name "p" had been seen.
+
+ Insert an HTML element for the token.
+
+ If the next token is a U+000A LINE FEED (LF) character token,
+ then ignore that token and move on to the next one. (Newlines at
+ the start of pre blocks are ignored as an authoring
+ convenience.)
+
+ A start tag whose tag name is "form"
+ If the form element pointer is not null, then this is a parse
+ error; ignore the token.
+
+ Otherwise:
+
+ If the stack of open elements has a p element in scope, then act
+ as if an end tag with the tag name "p" had been seen.
+
+ Insert an HTML element for the token, and set the form element
+ pointer to point to the element created.
+
+ A start tag whose tag name is "li"
+ Run the following algorithm:
+
+ 1. Initialize node to be the current node (the bottommost node of
+ the stack).
+ 2. If node is an li element, then act as if an end tag with the
+ tag name "li" had been seen, then jump to the last step.
+ 3. If node is not in the formatting category, and is not in the
+ phrasing category, and is not an address, div, or p element,
+ then jump to the last step.
+ 4. Otherwise, set node to the previous entry in the stack of open
+ elements and return to step 2.
+ 5. This is the last step.
+ If the stack of open elements has a p element in scope, then
+ act as if an end tag with the tag name "p" had been seen.
+ Finally, insert an HTML element for the token.
+
+ A start tag whose tag name is one of: "dd", "dt"
+ Run the following algorithm:
+
+ 1. Initialize node to be the current node (the bottommost node of
+ the stack).
+ 2. If node is a dd or dt element, then act as if an end tag with
+ the same tag name as node had been seen, then jump to the last
+ step.
+ 3. If node is not in the formatting category, and is not in the
+ phrasing category, and is not an address, div, or p element,
+ then jump to the last step.
+ 4. Otherwise, set node to the previous entry in the stack of open
+ elements and return to step 2.
+ 5. This is the last step.
+ If the stack of open elements has a p element in scope, then
+ act as if an end tag with the tag name "p" had been seen.
+ Finally, insert an HTML element for the token.
+
+ A start tag whose tag name is "plaintext"
+ If the stack of open elements has a p element in scope, then act
+ as if an end tag with the tag name "p" had been seen.
+
+ Insert an HTML element for the token.
+
+ Switch the content model flag to the PLAINTEXT state.
+
+ Once a start tag with the tag name "plaintext" has been seen,
+ that will be the last token ever seen other than character
+ tokens (and the end-of-file token), because there is no way to
+ switch the content model flag out of the PLAINTEXT state.
+
+ An end tag whose tag name is one of: "address", "article", "aside",
+ "blockquote", "center", "datagrid", "details", "dialog", "dir",
+ "div", "dl", "fieldset", "figure", "footer", "header",
+ "listing", "menu", "nav", "ol", "pre", "section", "ul"
+ If the stack of open elements does not have an element in scope
+ with the same tag name as that of the token, then this is a
+ parse error; ignore the token.
+
+ Otherwise, run these steps:
+
+ 1. Generate implied end tags.
+ 2. If the current node is not an element with the same tag name
+ as that of the token, then this is a parse error.
+ 3. Pop elements from the stack of open elements until an element
+ with the same tag name as the token has been popped from the
+ stack.
+
+ An end tag whose tag name is "form"
+ Let node be the element that the form element pointer is set to.
+
+ Set the form element pointer to null.
+
+ If node is null or the stack of open elements does not have node
+ in scope, then this is a parse error; ignore the token.
+
+ Otherwise, run these steps:
+
+ 1. Generate implied end tags.
+ 2. If the current node is not node, then this is a parse error.
+ 3. Remove node from the stack of open elements.
+
+ An end tag whose tag name is "p"
+ If the stack of open elements does not have an element in scope
+ with the same tag name as that of the token, then this is a
+ parse error; act as if a start tag with the tag name p had been
+ seen, then reprocess the current token.
+
+ Otherwise, run these steps:
+
+ 1. Generate implied end tags, except for elements with the same
+ tag name as the token.
+ 2. If the current node is not an element with the same tag name
+ as that of the token, then this is a parse error.
+ 3. Pop elements from the stack of open elements until an element
+ with the same tag name as the token has been popped from the
+ stack.
+
+ An end tag whose tag name is one of: "dd", "dt", "li"
+ If the stack of open elements does not have an element in scope
+ with the same tag name as that of the token, then this is a
+ parse error; ignore the token.
+
+ Otherwise, run these steps:
+
+ 1. Generate implied end tags, except for elements with the same
+ tag name as the token.
+ 2. If the current node is not an element with the same tag name
+ as that of the token, then this is a parse error.
+ 3. Pop elements from the stack of open elements until an element
+ with the same tag name as the token has been popped from the
+ stack.
+
+ An end tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6"
+ If the stack of open elements does not have an element in scope
+ whose tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6",
+ then this is a parse error; ignore the token.
+
+ Otherwise, run these steps:
+
+ 1. Generate implied end tags.
+ 2. If the current node is not an element with the same tag name
+ as that of the token, then this is a parse error.
+ 3. Pop elements from the stack of open elements until an element
+ whose tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6"
+ has been popped from the stack.
+
+ An end tag whose tag name is "sarcasm"
+ Take a deep breath, then act as described in the "any other end
+ tag" entry below.
+
+ A start tag whose tag name is "a"
+ If the list of active formatting elements contains an element
+ whose tag name is "a" between the end of the list and the last
+ marker on the list (or the start of the list if there is no
+ marker on the list), then this is a parse error; act as if an
+ end tag with the tag name "a" had been seen, then remove that
+ element from the list of active formatting elements and the
+ stack of open elements if the end tag didn't already remove it
+ (it might not have if the element is not in table scope).
+
+ In the non-conforming stream
+ <a href="a">a<table><a href="b">b</table>x, the first a element
+ would be closed upon seeing the second one, and the "x"
+ character would be inside a link to "b", not to "a". This is
+ despite the fact that the outer a element is not in table scope
+ (meaning that a regular </a> end tag at the start of the table
+ wouldn't close the outer a element).
+
+ Reconstruct the active formatting elements, if any.
+
+ Insert an HTML element for the token. Add that element to the
+ list of active formatting elements.
+
+ A start tag whose tag name is one of: "b", "big", "em", "font", "i",
+ "s", "small", "strike", "strong", "tt", "u"
+ Reconstruct the active formatting elements, if any.
+
+ Insert an HTML element for the token. Add that element to the
+ list of active formatting elements.
+
+ A start tag whose tag name is "nobr"
+ Reconstruct the active formatting elements, if any.
+
+ If the stack of open elements has a nobr element in scope, then
+ this is a parse error; act as if an end tag with the tag name
+ "nobr" had been seen, then once again reconstruct the active
+ formatting elements, if any.
+
+ Insert an HTML element for the token. Add that element to the
+ list of active formatting elements.
+
+ An end tag whose tag name is one of: "a", "b", "big", "em", "font",
+ "i", "nobr", "s", "small", "strike", "strong", "tt", "u"
+ Follow these steps:
+
+ 1. Let the formatting element be the last element in the list of
+ active formatting elements that:
+ o is between the end of the list and the last scope marker
+ in the list, if any, or the start of the list otherwise,
+ and
+ o has the same tag name as the token.
+ If there is no such node, or, if that node is also in the
+ stack of open elements but the element is not in scope, then
+ this is a parse error; ignore the token, and abort these
+ steps.
+ Otherwise, if there is such a node, but that node is not in
+ the stack of open elements, then this is a parse error; remove
+ the element from the list, and abort these steps.
+ Otherwise, there is a formatting element and that element is
+ in the stack and is in scope. If the element is not the
+ current node, this is a parse error. In any case, proceed with
+ the algorithm as written in the following steps.
+ 2. Let the furthest block be the topmost node in the stack of
+ open elements that is lower in the stack than the formatting
+ element, and is not an element in the phrasing or formatting
+ categories. There might not be one.
+ 3. If there is no furthest block, then the UA must skip the
+ subsequent steps and instead just pop all the nodes from the
+ bottom of the stack of open elements, from the current node up
+ to and including the formatting element, and remove the
+ formatting element from the list of active formatting
+ elements.
+ 4. Let the common ancestor be the element immediately above the
+ formatting element in the stack of open elements.
+ 5. If the furthest block has a parent node, then remove the
+ furthest block from its parent node.
+ 6. Let a bookmark note the position of the formatting element in
+ the list of active formatting elements relative to the
+ elements on either side of it in the list.
+ 7. Let node and last node be the furthest block. Follow these
+ steps:
+ 1. Let node be the element immediately above node in the
+ stack of open elements.
+ 2. If node is not in the list of active formatting elements,
+ then remove node from the stack of open elements and then
+ go back to step 1.
+ 3. Otherwise, if node is the formatting element, then go to
+ the next step in the overall algorithm.
+ 4. Otherwise, if last node is the furthest block, then move
+ the aforementioned bookmark to be immediately after the
+ node in the list of active formatting elements.
+ 5. If node has any children, perform a shallow clone of
+ node, replace the entry for node in the list of active
+ formatting elements with an entry for the clone, replace
+ the entry for node in the stack of open elements with an
+ entry for the clone, and let node be the clone.
+ 6. Insert last node into node, first removing it from its
+ previous parent node if any.
+ 7. Let last node be node.
+ 8. Return to step 1 of this inner set of steps.
+ 8. If the common ancestor node is a table, tbody, tfoot, thead,
+ or tr element, then, foster parent whatever last node ended up
+ being in the previous step.
+ Otherwise, append whatever last node ended up being in the
+ previous step to the common ancestor node, first removing it
+ from its previous parent node if any.
+ 9. Perform a shallow clone of the formatting element.
+ 10. Take all of the child nodes of the furthest block and append
+ them to the clone created in the last step.
+ 11. Append that clone to the furthest block.
+ 12. Remove the formatting element from the list of active
+ formatting elements, and insert the clone into the list of
+ active formatting elements at the position of the
+ aforementioned bookmark.
+ 13. Remove the formatting element from the stack of open elements,
+ and insert the clone into the stack of open elements
+ immediately below the position of the furthest block in that
+ stack.
+ 14. Jump back to step 1 in this series of steps.
+
+ The way these steps are defined, only elements in the formatting
+ category ever get cloned by this algorithm.
+
+ Because of the way this algorithm causes elements to change
+ parents, it has been dubbed the "adoption agency algorithm" (in
+ contrast with other possibly algorithms for dealing with
+ misnested content, which included the "incest algorithm", the
+ "secret affair algorithm", and the "Heisenberg algorithm").
+
+ A start tag whose tag name is "button"
+ If the stack of open elements has a button element in scope,
+ then this is a parse error; act as if an end tag with the tag
+ name "button" had been seen, then reprocess the token.
+
+ Otherwise:
+
+ Reconstruct the active formatting elements, if any.
+
+ Insert an HTML element for the token.
+
+ Insert a marker at the end of the list of active formatting
+ elements.
+
+ A start tag token whose tag name is one of: "applet", "marquee",
+ "object"
+ Reconstruct the active formatting elements, if any.
+
+ Insert an HTML element for the token.
+
+ Insert a marker at the end of the list of active formatting
+ elements.
+
+ An end tag token whose tag name is one of: "applet", "button",
+ "marquee", "object"
+ If the stack of open elements does not have an element in scope
+ with the same tag name as that of the token, then this is a
+ parse error; ignore the token.
+
+ Otherwise, run these steps:
+
+ 1. Generate implied end tags.
+ 2. If the current node is not an element with the same tag name
+ as that of the token, then this is a parse error.
+ 3. Pop elements from the stack of open elements until an element
+ with the same tag name as the token has been popped from the
+ stack.
+ 4. Clear the list of active formatting elements up to the last
+ marker.
+
+ A start tag whose tag name is "xmp"
+ Reconstruct the active formatting elements, if any.
+
+ Follow the generic CDATA element parsing algorithm.
+
+ A start tag whose tag name is "table"
+ If the stack of open elements has a p element in scope, then act
+ as if an end tag with the tag name "p" had been seen.
+
+ Insert an HTML element for the token.
+
+ Switch the insertion mode to "in table".
+
+ A start tag whose tag name is one of: "area", "basefont", "bgsound",
+ "br", "embed", "img", "input", "spacer", "wbr"
+ Reconstruct the active formatting elements, if any.
+
+ Insert an HTML element for the token. Immediately pop the
+ current node off the stack of open elements.
+
+ Acknowledge the token's self-closing flag, if it is set.
+
+ A start tag whose tag name is one of: "param", "source"
+ Insert an HTML element for the token. Immediately pop the
+ current node off the stack of open elements.
+
+ Acknowledge the token's self-closing flag, if it is set.
+
+ A start tag whose tag name is "hr"
+ If the stack of open elements has a p element in scope, then act
+ as if an end tag with the tag name "p" had been seen.
+
+ Insert an HTML element for the token. Immediately pop the
+ current node off the stack of open elements.
+
+ Acknowledge the token's self-closing flag, if it is set.
+
+ A start tag whose tag name is "image"
+ Parse error. Change the token's tag name to "img" and reprocess
+ it. (Don't ask.)
+
+ A start tag whose tag name is "isindex"
+ Parse error.
+
+ If the form element pointer is not null, then ignore the token.
+
+ Otherwise:
+
+ Acknowledge the token's self-closing flag, if it is set.
+
+ Act as if a start tag token with the tag name "form" had been
+ seen.
+
+ If the token has an attribute called "action", set the action
+ attribute on the resulting form element to the value of the
+ "action" attribute of the token.
+
+ Act as if a start tag token with the tag name "hr" had been
+ seen.
+
+ Act as if a start tag token with the tag name "p" had been seen.
+
+ Act as if a start tag token with the tag name "label" had been
+ seen.
+
+ Act as if a stream of character tokens had been seen (see below
+ for what they should say).
+
+ Act as if a start tag token with the tag name "input" had been
+ seen, with all the attributes from the "isindex" token except
+ "name", "action", and "prompt". Set the name attribute of the
+ resulting input element to the value "isindex".
+
+ Act as if a stream of character tokens had been seen (see below
+ for what they should say).
+
+ Act as if an end tag token with the tag name "label" had been
+ seen.
+
+ Act as if an end tag token with the tag name "p" had been seen.
+
+ Act as if a start tag token with the tag name "hr" had been
+ seen.
+
+ Act as if an end tag token with the tag name "form" had been
+ seen.
+
+ If the token has an attribute with the name "prompt", then the
+ first stream of characters must be the same string as given in
+ that attribute, and the second stream of characters must be
+ empty. Otherwise, the two streams of character tokens together
+ should, together with the input element, express the equivalent
+ of "This is a searchable index. Insert your search keywords
+ here: (input field)" in the user's preferred language.
+
+ A start tag whose tag name is "textarea"
+
+ 1. Insert an HTML element for the token.
+ 2. If the next token is a U+000A LINE FEED (LF) character token,
+ then ignore that token and move on to the next one. (Newlines
+ at the start of textarea elements are ignored as an authoring
+ convenience.)
+ 3. Switch the tokeniser's content model flag to the RCDATA state.
+ 4. Let the original insertion mode be the current insertion mode.
+ 5. Switch the insertion mode to "in CDATA/RCDATA".
+
+ A start tag whose tag name is one of: "iframe", "noembed"
+ A start tag whose tag name is "noscript", if the scripting flag is
+ enabled
+ Follow the generic CDATA element parsing algorithm.
+
+ A start tag whose tag name is "select"
+ Reconstruct the active formatting elements, if any.
+
+ Insert an HTML element for the token.
+
+ If the insertion mode is one of in table", "in caption", "in
+ column group", "in table body", "in row", or "in cell", then
+ switch the insertion mode to "in select in table". Otherwise,
+ switch the insertion mode to "in select".
+
+ A start tag whose tag name is one of: "optgroup", "option"
+ If the stack of open elements has an option element in scope,
+ then act as if an end tag with the tag name "option" had been
+ seen.
+
+ Reconstruct the active formatting elements, if any.
+
+ Insert an HTML element for the token.
+
+ A start tag whose tag name is one of: "rp", "rt"
+ If the stack of open elements has a ruby element in scope, then
+ generate implied end tags. If the current node is not then a
+ ruby element, this is a parse error; pop all the nodes from the
+ current node up to the node immediately before the bottommost
+ ruby element on the stack of open elements.
+
+ Insert an HTML element for the token.
+
+ An end tag whose tag name is "br"
+ Parse error. Act as if a start tag token with the tag name "br"
+ had been seen. Ignore the end tag token.
+
+ A start tag whose tag name is "math"
+ Reconstruct the active formatting elements, if any.
+
+ Adjust MathML attributes for the token. (This fixes the case of
+ MathML attributes that are not all lowercase.)
+
+ Adjust foreign attributes for the token. (This fixes the use of
+ namespaced attributes, in particular XLink.)
+
+ Insert a foreign element for the token, in the MathML namespace.
+
+ If the token has its self-closing flag set, pop the current node
+ off the stack of open elements and acknowledge the token's
+ self-closing flag.
+
+ Otherwise, let the secondary insertion mode be the current
+ insertion mode, and then switch the insertion mode to "in
+ foreign content".
+
+ A start tag whose tag name is one of: "caption", "col", "colgroup",
+ "frame", "frameset", "head", "tbody", "td", "tfoot", "th",
+ "thead", "tr"
+ Parse error. Ignore the token.
+
+ Any other start tag
+ Reconstruct the active formatting elements, if any.
+
+ Insert an HTML element for the token.
+
+ This element will be a phrasing element.
+
+ Any other end tag
+ Run the following steps:
+
+ 1. Initialize node to be the current node (the bottommost node of
+ the stack).
+ 2. If node has the same tag name as the end tag token, then:
+ 1. Generate implied end tags.
+ 2. If the tag name of the end tag token does not match the
+ tag name of the current node, this is a parse error.
+ 3. Pop all the nodes from the current node up to node,
+ including node, then stop these steps.
+ 3. Otherwise, if node is in neither the formatting category nor
+ the phrasing category, then this is a parse error; ignore the
+ token, and abort these steps.
+ 4. Set node to the previous entry in the stack of open elements.
+ 5. Return to step 2.
+
+ 8.2.5.11 The "in CDATA/RCDATA" insertion mode
+
+ When the insertion mode is "in CDATA/RCDATA", tokens must be handled as
+ follows:
+
+ A character token
+ Insert the token's character into the current node.
+
+ An end-of-file token
+ Parse error.
+
+ If the current node is a script element, mark the script element
+ as "already executed".
+
+ Pop the current node off the stack of open elements.
+
+ Switch the insertion mode to the original insertion mode and
+ reprocess the current token.
+
+ An end tag whose tag name is "script"
+ Let script be the current node (which will be a script element).
+
+ Pop the current node off the stack of open elements.
+
+ Switch the insertion mode to the original insertion mode.
+
+ Let the old insertion point have the same value as the current
+ insertion point. Let the insertion point be just before the next
+ input character.
+
+ Increment the parser's script nesting level by one.
+
+ Run the script. This might cause some script to execute, which
+ might cause new characters to be inserted into the tokeniser,
+ and might cause the tokeniser to output more tokens, resulting
+ in a reentrant invocation of the parser.
+
+ Decrement the parser's script nesting level by one. If the
+ parser's script nesting level is zero, then set the parser pause
+ flag to false.
+
+ Let the insertion point have the value of the old insertion
+ point. (In other words, restore the insertion point to the value
+ it had before the previous paragraph. This value might be the
+ "undefined" value.)
+
+ At this stage, if there is a pending external script, then:
+
+ If the tree construction stage is being called reentrantly, say
+ from a call to document.write():
+ Set the parser pause flag to true, and abort the
+ processing of any nested invocations of the tokeniser,
+ yielding control back to the caller. (Tokenization will
+ resume when the caller returns to the "outer" tree
+ construction stage.)
+
+ Otherwise:
+ Follow these steps:
+
+ 1. Let the script be the pending external script. There is
+ no longer a pending external script.
+ 2. Pause until the script has completed loading.
+ 3. Let the insertion point be just before the next input
+ character.
+ 4. Execute the script.
+ 5. Let the insertion point be undefined again.
+ 6. If there is once again a pending external script, then
+ repeat these steps from step 1.
+
+ Any other end tag
+ Pop the current node off the stack of open elements.
+
+ Switch the insertion mode to the original insertion mode.
+
+ 8.2.5.12 The "in table" insertion mode
+
+ When the insertion mode is "in table", tokens must be handled as
+ follows:
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ If the current table is tainted, then act as described in the
+ "anything else" entry below.
+
+ Otherwise, insert the character into the current node.
+
+ A comment token
+ Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "caption"
+ Clear the stack back to a table context. (See below.)
+
+ Insert a marker at the end of the list of active formatting
+ elements.
+
+ Insert an HTML element for the token, then switch the insertion
+ mode to "in caption".
+
+ A start tag whose tag name is "colgroup"
+ Clear the stack back to a table context. (See below.)
+
+ Insert an HTML element for the token, then switch the insertion
+ mode to "in column group".
+
+ A start tag whose tag name is "col"
+ Act as if a start tag token with the tag name "colgroup" had
+ been seen, then reprocess the current token.
+
+ A start tag whose tag name is one of: "tbody", "tfoot", "thead"
+ Clear the stack back to a table context. (See below.)
+
+ Insert an HTML element for the token, then switch the insertion
+ mode to "in table body".
+
+ A start tag whose tag name is one of: "td", "th", "tr"
+ Act as if a start tag token with the tag name "tbody" had been
+ seen, then reprocess the current token.
+
+ A start tag whose tag name is "table"
+ Parse error. Act as if an end tag token with the tag name
+ "table" had been seen, then, if that token wasn't ignored,
+ reprocess the current token.
+
+ The fake end tag token here can only be ignored in the fragment
+ case.
+
+ An end tag whose tag name is "table"
+ If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse
+ error. Ignore the token. (fragment case)
+
+ Otherwise:
+
+ Pop elements from this stack until a table element has been
+ popped from the stack.
+
+ Reset the insertion mode appropriately.
+
+ An end tag whose tag name is one of: "body", "caption", "col",
+ "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr"
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is one of: "style", "script"
+ If the current table is tainted then act as described in the
+ "anything else" entry below.
+
+ Otherwise, process the token using the rules for the "in head"
+ insertion mode.
+
+ A start tag whose tag name is "input"
+ If the token does not have an attribute with the name "type", or
+ if it does, but that attribute's value is not an ASCII
+ case-insensitive match for the string "hidden", or, if the
+ current table is tainted, then: act as described in the
+ "anything else" entry below.
+
+ Otherwise:
+
+ Parse error.
+
+ Insert an HTML element for the token.
+
+ Pop that input element off the stack of open elements.
+
+ An end-of-file token
+ If the current node is not the root html element, then this is a
+ parse error.
+
+ It can only be the current node in the fragment case.
+
+ Stop parsing.
+
+ Anything else
+ Parse error. Process the token using the rules for the "in body"
+ insertion mode, except that if the current node is a table,
+ tbody, tfoot, thead, or tr element, then, whenever a node would
+ be inserted into the current node, it must instead be foster
+ parented.
+
+ When the steps above require the UA to clear the stack back to a table
+ context, it means that the UA must, while the current node is not a
+ table element or an html element, pop elements from the stack of open
+ elements.
+
+ The current node being an html element after this process is a fragment
+ case.
+
+ 8.2.5.13 The "in caption" insertion mode
+
+ When the insertion mode is "in caption", tokens must be handled as
+ follows:
+
+ An end tag whose tag name is "caption"
+ If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse
+ error. Ignore the token. (fragment case)
+
+ Otherwise:
+
+ Generate implied end tags.
+
+ Now, if the current node is not a caption element, then this is
+ a parse error.
+
+ Pop elements from this stack until a caption element has been
+ popped from the stack.
+
+ Clear the list of active formatting elements up to the last
+ marker.
+
+ Switch the insertion mode to "in table".
+
+ A start tag whose tag name is one of: "caption", "col", "colgroup",
+ "tbody", "td", "tfoot", "th", "thead", "tr"
+
+ An end tag whose tag name is "table"
+ Parse error. Act as if an end tag with the tag name "caption"
+ had been seen, then, if that token wasn't ignored, reprocess the
+ current token.
+
+ The fake end tag token here can only be ignored in the fragment
+ case.
+
+ An end tag whose tag name is one of: "body", "col", "colgroup", "html",
+ "tbody", "td", "tfoot", "th", "thead", "tr"
+ Parse error. Ignore the token.
+
+ Anything else
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ 8.2.5.14 The "in column group" insertion mode
+
+ When the insertion mode is "in column group", tokens must be handled as
+ follows:
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ Insert the character into the current node.
+
+ A comment token
+ Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ A start tag whose tag name is "col"
+ Insert an HTML element for the token. Immediately pop the
+ current node off the stack of open elements.
+
+ Acknowledge the token's self-closing flag, if it is set.
+
+ An end tag whose tag name is "colgroup"
+ If the current node is the root html element, then this is a
+ parse error; ignore the token. (fragment case)
+
+ Otherwise, pop the current node (which will be a colgroup
+ element) from the stack of open elements. Switch the insertion
+ mode to "in table".
+
+ An end tag whose tag name is "col"
+ Parse error. Ignore the token.
+
+ An end-of-file token
+ If the current node is the root html element, then stop parsing.
+ (fragment case)
+
+ Otherwise, act as described in the "anything else" entry below.
+
+ Anything else
+ Act as if an end tag with the tag name "colgroup" had been seen,
+ and then, if that token wasn't ignored, reprocess the current
+ token.
+
+ The fake end tag token here can only be ignored in the fragment
+ case.
+
+ 8.2.5.15 The "in table body" insertion mode
+
+ When the insertion mode is "in table body", tokens must be handled as
+ follows:
+
+ A start tag whose tag name is "tr"
+ Clear the stack back to a table body context. (See below.)
+
+ Insert an HTML element for the token, then switch the insertion
+ mode to "in row".
+
+ A start tag whose tag name is one of: "th", "td"
+ Parse error. Act as if a start tag with the tag name "tr" had
+ been seen, then reprocess the current token.
+
+ An end tag whose tag name is one of: "tbody", "tfoot", "thead"
+ If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse
+ error. Ignore the token.
+
+ Otherwise:
+
+ Clear the stack back to a table body context. (See below.)
+
+ Pop the current node from the stack of open elements. Switch the
+ insertion mode to "in table".
+
+ A start tag whose tag name is one of: "caption", "col", "colgroup",
+ "tbody", "tfoot", "thead"
+
+ An end tag whose tag name is "table"
+ If the stack of open elements does not have a tbody, thead, or
+ tfoot element in table scope, this is a parse error. Ignore the
+ token. (fragment case)
+
+ Otherwise:
+
+ Clear the stack back to a table body context. (See below.)
+
+ Act as if an end tag with the same tag name as the current node
+ ("tbody", "tfoot", or "thead") had been seen, then reprocess the
+ current token.
+
+ An end tag whose tag name is one of: "body", "caption", "col",
+ "colgroup", "html", "td", "th", "tr"
+ Parse error. Ignore the token.
+
+ Anything else
+ Process the token using the rules for the "in table" insertion
+ mode.
+
+ When the steps above require the UA to clear the stack back to a table
+ body context, it means that the UA must, while the current node is not
+ a tbody, tfoot, thead, or html element, pop elements from the stack of
+ open elements.
+
+ The current node being an html element after this process is a fragment
+ case.
+
+ 8.2.5.16 The "in row" insertion mode
+
+ When the insertion mode is "in row", tokens must be handled as follows:
+
+ A start tag whose tag name is one of: "th", "td"
+ Clear the stack back to a table row context. (See below.)
+
+ Insert an HTML element for the token, then switch the insertion
+ mode to "in cell".
+
+ Insert a marker at the end of the list of active formatting
+ elements.
+
+ An end tag whose tag name is "tr"
+ If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse
+ error. Ignore the token. (fragment case)
+
+ Otherwise:
+
+ Clear the stack back to a table row context. (See below.)
+
+ Pop the current node (which will be a tr element) from the stack
+ of open elements. Switch the insertion mode to "in table body".
+
+ A start tag whose tag name is one of: "caption", "col", "colgroup",
+ "tbody", "tfoot", "thead", "tr"
+
+ An end tag whose tag name is "table"
+ Act as if an end tag with the tag name "tr" had been seen, then,
+ if that token wasn't ignored, reprocess the current token.
+
+ The fake end tag token here can only be ignored in the fragment
+ case.
+
+ An end tag whose tag name is one of: "tbody", "tfoot", "thead"
+ If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse
+ error. Ignore the token.
+
+ Otherwise, act as if an end tag with the tag name "tr" had been
+ seen, then reprocess the current token.
+
+ An end tag whose tag name is one of: "body", "caption", "col",
+ "colgroup", "html", "td", "th"
+ Parse error. Ignore the token.
+
+ Anything else
+ Process the token using the rules for the "in table" insertion
+ mode.
+
+ When the steps above require the UA to clear the stack back to a table
+ row context, it means that the UA must, while the current node is not a
+ tr element or an html element, pop elements from the stack of open
+ elements.
+
+ The current node being an html element after this process is a fragment
+ case.
+
+ 8.2.5.17 The "in cell" insertion mode
+
+ When the insertion mode is "in cell", tokens must be handled as
+ follows:
+
+ An end tag whose tag name is one of: "td", "th"
+ If the stack of open elements does not have an element in table
+ scope with the same tag name as that of the token, then this is
+ a parse error and the token must be ignored.
+
+ Otherwise:
+
+ Generate implied end tags.
+
+ Now, if the current node is not an element with the same tag
+ name as the token, then this is a parse error.
+
+ Pop elements from this stack until an element with the same tag
+ name as the token has been popped from the stack.
+
+ Clear the list of active formatting elements up to the last
+ marker.
+
+ Switch the insertion mode to "in row". (The current node will be
+ a tr element at this point.)
+
+ A start tag whose tag name is one of: "caption", "col", "colgroup",
+ "tbody", "td", "tfoot", "th", "thead", "tr"
+ If the stack of open elements does not have a td or th element
+ in table scope, then this is a parse error; ignore the token.
+ (fragment case)
+
+ Otherwise, close the cell (see below) and reprocess the current
+ token.
+
+ An end tag whose tag name is one of: "body", "caption", "col",
+ "colgroup", "html"
+ Parse error. Ignore the token.
+
+ An end tag whose tag name is one of: "table", "tbody", "tfoot",
+ "thead", "tr"
+ If the stack of open elements does not have an element in table
+ scope with the same tag name as that of the token (which can
+ only happen for "tbody", "tfoot" and "thead", or, in the
+ fragment case), then this is a parse error and the token must be
+ ignored.
+
+ Otherwise, close the cell (see below) and reprocess the current
+ token.
+
+ Anything else
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ Where the steps above say to close the cell, they mean to run the
+ following algorithm:
+ 1. If the stack of open elements has a td element in table scope, then
+ act as if an end tag token with the tag name "td" had been seen.
+ 2. Otherwise, the stack of open elements will have a th element in
+ table scope; act as if an end tag token with the tag name "th" had
+ been seen.
+
+ The stack of open elements cannot have both a td and a th element in
+ table scope at the same time, nor can it have neither when the
+ insertion mode is "in cell".
+
+ 8.2.5.18 The "in select" insertion mode
+
+ When the insertion mode is "in select", tokens must be handled as
+ follows:
+
+ A character token
+ Insert the token's character into the current node.
+
+ A comment token
+ Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ A start tag whose tag name is "option"
+ If the current node is an option element, act as if an end tag
+ with the tag name "option" had been seen.
+
+ Insert an HTML element for the token.
+
+ A start tag whose tag name is "optgroup"
+ If the current node is an option element, act as if an end tag
+ with the tag name "option" had been seen.
+
+ If the current node is an optgroup element, act as if an end tag
+ with the tag name "optgroup" had been seen.
+
+ Insert an HTML element for the token.
+
+ An end tag whose tag name is "optgroup"
+ First, if the current node is an option element, and the node
+ immediately before it in the stack of open elements is an
+ optgroup element, then act as if an end tag with the tag name
+ "option" had been seen.
+
+ If the current node is an optgroup element, then pop that node
+ from the stack of open elements. Otherwise, this is a parse
+ error; ignore the token.
+
+ An end tag whose tag name is "option"
+ If the current node is an option element, then pop that node
+ from the stack of open elements. Otherwise, this is a parse
+ error; ignore the token.
+
+ An end tag whose tag name is "select"
+ If the stack of open elements does not have an element in table
+ scope with the same tag name as the token, this is a parse
+ error. Ignore the token. (fragment case)
+
+ Otherwise:
+
+ Pop elements from the stack of open elements until a select
+ element has been popped from the stack.
+
+ Reset the insertion mode appropriately.
+
+ A start tag whose tag name is "select"
+ Parse error. Act as if the token had been an end tag with the
+ tag name "select" instead.
+
+ A start tag whose tag name is one of: "input", "textarea"
+ Parse error. Act as if an end tag with the tag name "select" had
+ been seen, and reprocess the token.
+
+ A start tag token whose tag name is "script"
+ Process the token using the rules for the "in head" insertion
+ mode.
+
+ An end-of-file token
+ If the current node is not the root html element, then this is a
+ parse error.
+
+ It can only be the current node in the fragment case.
+
+ Stop parsing.
+
+ Anything else
+ Parse error. Ignore the token.
+
+ 8.2.5.19 The "in select in table" insertion mode
+
+ When the insertion mode is "in select in table", tokens must be handled
+ as follows:
+
+ A start tag whose tag name is one of: "caption", "table", "tbody",
+ "tfoot", "thead", "tr", "td", "th"
+ Parse error. Act as if an end tag with the tag name "select" had
+ been seen, and reprocess the token.
+
+ An end tag whose tag name is one of: "caption", "table", "tbody",
+ "tfoot", "thead", "tr", "td", "th"
+ Parse error.
+
+ If the stack of open elements has an element in table scope with
+ the same tag name as that of the token, then act as if an end
+ tag with the tag name "select" had been seen, and reprocess the
+ token. Otherwise, ignore the token.
+
+ Anything else
+ Process the token using the rules for the "in select" insertion
+ mode.
+
+ 8.2.5.20 The "in foreign content" insertion mode
+
+ When the insertion mode is "in foreign content", tokens must be handled
+ as follows:
+
+ A character token
+ Insert the token's character into the current node.
+
+ A comment token
+ Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is neither "mglyph" nor "malignmark", if the
+ current node is an mi element in the MathML namespace.
+
+ A start tag whose tag name is neither "mglyph" nor "malignmark", if the
+ current node is an mo element in the MathML namespace.
+
+ A start tag whose tag name is neither "mglyph" nor "malignmark", if the
+ current node is an mn element in the MathML namespace.
+
+ A start tag whose tag name is neither "mglyph" nor "malignmark", if the
+ current node is an ms element in the MathML namespace.
+
+ A start tag whose tag name is neither "mglyph" nor "malignmark", if the
+ current node is an mtext element in the MathML namespace.
+
+ A start tag, if the current node is an element in the HTML namespace.
+ An end tag
+ Process the token using the rules for the secondary insertion
+ mode.
+
+ If, after doing so, the insertion mode is still "in foreign
+ content", but there is no element in scope that has a namespace
+ other than the HTML namespace, switch the insertion mode to the
+ secondary insertion mode.
+
+ A start tag whose tag name is one of: "b", "big", "blockquote", "body",
+ "br", "center", "code", "dd", "div", "dl", "dt", "em", "embed",
+ "h1", "h2", "h3", "h4", "h5", "h6", "head", "hr", "i", "img",
+ "li", "listing", "menu", "meta", "nobr", "ol", "p", "pre",
+ "ruby", "s", "small", "span", "strong", "strike", "sub", "sup",
+ "table", "tt", "u", "ul", "var"
+
+ A start tag whose tag name is "font", if the token has any attributes
+ named "color", "face", or "size"
+
+ An end-of-file token
+ Parse error.
+
+ Pop elements from the stack of open elements until the current
+ node is in the HTML namespace.
+
+ Switch the insertion mode to the secondary insertion mode, and
+ reprocess the token.
+
+ Any other start tag
+ If the current node is an element in the MathML namespace,
+ adjust MathML attributes for the token. (This fixes the case of
+ MathML attributes that are not all lowercase.)
+
+ Adjust foreign attributes for the token. (This fixes the use of
+ namespaced attributes, in particular XLink in SVG.)
+
+ Insert a foreign element for the token, in the same namespace as
+ the current node.
+
+ If the token has its self-closing flag set, pop the current node
+ off the stack of open elements and acknowledge the token's
+ self-closing flag.
+
+ 8.2.5.21 The "after body" insertion mode
+
+ When the insertion mode is "after body", tokens must be handled as
+ follows:
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ A comment token
+ Append a Comment node to the first element in the stack of open
+ elements (the html element), with the data attribute set to the
+ data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ An end tag whose tag name is "html"
+ If the parser was originally created as part of the HTML
+ fragment parsing algorithm, this is a parse error; ignore the
+ token. (fragment case)
+
+ Otherwise, switch the insertion mode to "after after body".
+
+ An end-of-file token
+ Stop parsing.
+
+ Anything else
+ Parse error. Switch the insertion mode to "in body" and
+ reprocess the token.
+
+ 8.2.5.22 The "in frameset" insertion mode
+
+ When the insertion mode is "in frameset", tokens must be handled as
+ follows:
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ Insert the character into the current node.
+
+ A comment token
+ Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ A start tag whose tag name is "frameset"
+ Insert an HTML element for the token.
+
+ An end tag whose tag name is "frameset"
+ If the current node is the root html element, then this is a
+ parse error; ignore the token. (fragment case)
+
+ Otherwise, pop the current node from the stack of open elements.
+
+ If the parser was not originally created as part of the HTML
+ fragment parsing algorithm (fragment case), and the current node
+ is no longer a frameset element, then switch the insertion mode
+ to "after frameset".
+
+ A start tag whose tag name is "frame"
+ Insert an HTML element for the token. Immediately pop the
+ current node off the stack of open elements.
+
+ Acknowledge the token's self-closing flag, if it is set.
+
+ A start tag whose tag name is "noframes"
+ Process the token using the rules for the "in head" insertion
+ mode.
+
+ An end-of-file token
+ If the current node is not the root html element, then this is a
+ parse error.
+
+ It can only be the current node in the fragment case.
+
+ Stop parsing.
+
+ Anything else
+ Parse error. Ignore the token.
+
+ 8.2.5.23 The "after frameset" insertion mode
+
+ When the insertion mode is "after frameset", tokens must be handled as
+ follows:
+
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ Insert the character into the current node.
+
+ A comment token
+ Append a Comment node to the current node with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ Parse error. Ignore the token.
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ An end tag whose tag name is "html"
+ Switch the insertion mode to "after after frameset".
+
+ A start tag whose tag name is "noframes"
+ Process the token using the rules for the "in head" insertion
+ mode.
+
+ An end-of-file token
+ Stop parsing.
+
+ Anything else
+ Parse error. Ignore the token.
+
+ This doesn't handle UAs that don't support frames, or that do support
+ frames but want to show the NOFRAMES content. Supporting the former is
+ easy; supporting the latter is harder.
+
+ 8.2.5.24 The "after after body" insertion mode
+
+ When the insertion mode is "after after body", tokens must be handled
+ as follows:
+
+ A comment token
+ Append a Comment node to the Document object with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ An end-of-file token
+ Stop parsing.
+
+ Anything else
+ Parse error. Switch the insertion mode to "in body" and
+ reprocess the token.
+
+ 8.2.5.25 The "after after frameset" insertion mode
+
+ When the insertion mode is "after after frameset", tokens must be
+ handled as follows:
+
+ A comment token
+ Append a Comment node to the Document object with the data
+ attribute set to the data given in the comment token.
+
+ A DOCTYPE token
+ A character token that is one of one of U+0009 CHARACTER TABULATION,
+ U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+
+ A start tag whose tag name is "html"
+ Process the token using the rules for the "in body" insertion
+ mode.
+
+ An end-of-file token
+ Stop parsing.
+
+ A start tag whose tag name is "noframes"
+ Process the token using the rules for the "in head" insertion
+ mode.
+
+ Anything else
+ Parse error. Ignore the token.
+
+ 8.2.6 The end
+
+ Once the user agent stops parsing the document, the user agent must
+ follow the steps in this section.
+
+ First, the current document readiness must be set to "interactive".
+
+ Then, the rules for when a script completes loading start applying
+ (script execution is no longer managed by the parser).
+
+ If any of the scripts in the list of scripts that will execute as soon
+ as possible have completed loading, or if the list of scripts that will
+ execute asynchronously is not empty and the first script in that list
+ has completed loading, then the user agent must act as if those scripts
+ just completed loading, following the rules given for that in the
+ script element definition.
+
+ Then, if the list of scripts that will execute when the document has
+ finished parsing is not empty, and the first item in this list has
+ already completed loading, then the user agent must act as if that
+ script just finished loading.
+
+ By this point, there will be no scripts that have loaded but have not
+ yet been executed.
+
+ The user agent must then fire a simple event called DOMContentLoaded at
+ the Document.
+
+ Once everything that delays the load event has completed, the user
+ agent must set the current document readiness to "complete", and then
+ fire a load event at the body element.
+
+ delaying the load event for things like image loads allows for intranet
+ port scans (even without javascript!). Should we really encode that
+ into the spec?
+
+ 8.2.7 Coercing an HTML DOM into an infoset
+
+ When an application uses an HTML parser in conjunction with an XML
+ pipeline, it is possible that the constructed DOM is not compatible
+ with the XML tool chain in certain subtle ways. For example, an XML
+ toolchain might not be able to represent attributes with the name
+ xmlns, since they conflict with the Namespaces in XML syntax. There is
+ also some data that the HTML parser generates that isn't included in
+ the DOM itself. This section specifies some rules for handling these
+ issues.
+
+ If the XML API being used doesn't support DOCTYPEs, the tool may drop
+ DOCTYPEs altogether.
+
+ If the XML API doesn't support attributes in no namespace that are
+ named "xmlns", attributes whose names start with "xmlns:", or
+ attributes in the XMLNS namespace, then the tool may drop such
+ attributes.
+
+ The tool may annotate the output with any namespace declarations
+ required for proper operation.
+
+ If the XML API being used restricts the allowable characters in the
+ local names of elements and attributes, then the tool may map all
+ element and attribute local names that the API wouldn't support to a
+ set of names that are allowed, by replacing any character that isn't
+ supported with the uppercase letter U and the five digits of the
+ character's Unicode codepoint when expressed in hexadecimal, using
+ digits 0-9 and capital letters A-F as the symbols, in increasing
+ numeric order.
+
+ For example, the element name foo<bar, which can be output by the HTML
+ parser, though it is neither a legal HTML element name nor a
+ well-formed XML element name, would be converted into fooU0003Cbar,
+ which is a well-formed XML element name (though it's still not legal in
+ HTML by any means).
+
+ As another example, consider the attribute xlink:href. Used on a MathML
+ element, it becomes, after being adjusted, an attribute with a prefix
+ "xlink" and a local name "href". However, used on an HTML element, it
+ becomes an attribute with no prefix and the local name "xlink:href",
+ which is not a valid NCName, and thus might not be accepted by an XML
+ API. It could thus get converted, becoming "xlinkU0003Ahref".
+
+ The resulting names from this conversion conveniently can't clash with
+ any attribute generated by the HTML parser, since those are all either
+ lowercase or those listed in the adjust foreign attributes algorithm's
+ table.
+
+ If the XML API restricts comments from having two consecutive U+002D
+ HYPHEN-MINUS characters (--), the tool may insert a single U+0020 SPACE
+ character between any such offending characters.
+
+ If the XML API restricts comments from ending in a U+002D HYPHEN-MINUS
+ character (-), the tool may insert a single U+0020 SPACE character at
+ the end of such comments.
+
+ If the XML API restricts allowed characters in character data, the tool
+ may replace any U+000C FORM FEED (FF) character with a U+0020 SPACE
+ character, and any other literal non-XML character with a U+FFFD
+ REPLACEMENT CHARACTER.
+
+ If the tool has no way to convey out-of-band information, then the tool
+ may drop the following information:
+ * Whether the document is set to no quirks mode, limited quirks mode,
+ or quirks mode
+ * The association between form controls and forms that aren't their
+ nearest form element ancestor (use of the form element pointer in
+ the parser)
+
+ The mutations allowed by this section apply after the HTML parser's
+ rules have been applied. For example, a <a::> start tag will be closed
+ by a </a::> end tag, and never by a </aU0003AU0003A> end tag, even if
+ the user agent is using the rules above to then generate an actual
+ element in the DOM with the name aU0003AU0003A for that start tag.
+
+ 8.3 Namespaces
+
+ The HTML namespace is: http://www.w3.org/1999/xhtml
+
+ The MathML namespace is: http://www.w3.org/1998/Math/MathML
+
+ The SVG namespace is: http://www.w3.org/2000/svg
+
+ The XLink namespace is: http://www.w3.org/1999/xlink
+
+ The XML namespace is: http://www.w3.org/XML/1998/namespace
+
+ The XMLNS namespace is: http://www.w3.org/2000/xmlns/
diff --git a/components/htmlfive/java/htmlparser/generate-encoding-data.py b/components/htmlfive/java/htmlparser/generate-encoding-data.py
new file mode 100644
index 000000000..69b2fdc30
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/generate-encoding-data.py
@@ -0,0 +1,745 @@
+#!/usr/bin/python
+
+# Copyright (c) 2013-2015 Mozilla Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import json
+
+class Label:
+ def __init__(self, label, preferred):
+ self.label = label
+ self.preferred = preferred
+ def __cmp__(self, other):
+ return cmp(self.label, other.label)
+
+# If a multi-byte encoding is on this list, it is assumed to have a
+# non-generated decoder implementation class. Otherwise, the JDK default
+# decoder is used as a placeholder.
+MULTI_BYTE_DECODER_IMPLEMENTED = [
+ u"x-user-defined",
+ u"replacement",
+ u"big5",
+]
+
+MULTI_BYTE_ENCODER_IMPLEMENTED = [
+ u"big5",
+]
+
+preferred = []
+
+labels = []
+
+data = json.load(open("../encoding/encodings.json", "r"))
+
+indexes = json.load(open("../encoding/indexes.json", "r"))
+
+single_byte = []
+
+multi_byte = []
+
+def to_camel_name(name):
+ if name == u"iso-8859-8-i":
+ return u"Iso8I"
+ if name.startswith(u"iso-8859-"):
+ return name.replace(u"iso-8859-", u"Iso")
+ return name.title().replace(u"X-", u"").replace(u"-", u"").replace(u"_", u"")
+
+def to_constant_name(name):
+ return name.replace(u"-", u"_").upper()
+
+# Encoding.java
+
+for group in data:
+ if group["heading"] == "Legacy single-byte encodings":
+ single_byte = group["encodings"]
+ else:
+ multi_byte.extend(group["encodings"])
+ for encoding in group["encodings"]:
+ preferred.append(encoding["name"])
+ for label in encoding["labels"]:
+ labels.append(Label(label, encoding["name"]))
+
+preferred.sort()
+labels.sort()
+
+label_file = open("src/nu/validator/encoding/Encoding.java", "w")
+
+label_file.write("""/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.nio.charset.spi.CharsetProvider;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Represents an <a href="https://encoding.spec.whatwg.org/#encoding">encoding</a>
+ * as defined in the <a href="https://encoding.spec.whatwg.org/">Encoding
+ * Standard</a>, provides access to each encoding defined in the Encoding
+ * Standard via a static constant and provides the
+ * "<a href="https://encoding.spec.whatwg.org/#concept-encoding-get">get an
+ * encoding</a>" algorithm defined in the Encoding Standard.
+ *
+ * <p>This class inherits from {@link Charset} to allow the Encoding
+ * Standard-compliant encodings to be used in contexts that support
+ * <code>Charset</code> instances. However, by design, the Encoding
+ * Standard-compliant encodings are not supplied via a {@link CharsetProvider}
+ * and, therefore, are not available via and do not interfere with the static
+ * methods provided by <code>Charset</code>. (This class provides methods of
+ * the same name to hide each static method of <code>Charset</code> to help
+ * avoid accidental calls to the static methods of the superclass when working
+ * with Encoding Standard-compliant encodings.)
+ *
+ * <p>When an application needs to use a particular encoding, such as utf-8
+ * or windows-1252, the corresponding constant, i.e.
+ * {@link #UTF_8 Encoding.UTF_8} and {@link #WINDOWS_1252 Encoding.WINDOWS_1252}
+ * respectively, should be used. However, when the application receives an
+ * encoding label from external input, the method {@link #forName(String)
+ * forName()} should be used to obtain the object representing the encoding
+ * identified by the label. In contexts where labels that map to the
+ * <a href="https://encoding.spec.whatwg.org/#replacement">replacement
+ * encoding</a> should be treated as unknown, the method {@link
+ * #forNameNoReplacement(String) forNameNoReplacement()} should be used instead.
+ *
+ *
+ * @author hsivonen
+ */
+public abstract class Encoding extends Charset {
+
+ private static final String[] LABELS = {
+""")
+
+for label in labels:
+ label_file.write(" \"%s\",\n" % label.label)
+
+label_file.write(""" };
+
+ private static final Encoding[] ENCODINGS_FOR_LABELS = {
+""")
+
+for label in labels:
+ label_file.write(" %s.INSTANCE,\n" % to_camel_name(label.preferred))
+
+label_file.write(""" };
+
+ private static final Encoding[] ENCODINGS = {
+""")
+
+for label in preferred:
+ label_file.write(" %s.INSTANCE,\n" % to_camel_name(label))
+
+label_file.write(""" };
+
+""")
+
+for label in preferred:
+ label_file.write(""" /**
+ * The %s encoding.
+ */
+ public static final Encoding %s = %s.INSTANCE;
+
+""" % (label, to_constant_name(label), to_camel_name(label)))
+
+label_file.write("""
+private static SortedMap<String, Charset> encodings = null;
+
+ protected Encoding(String canonicalName, String[] aliases) {
+ super(canonicalName, aliases);
+ }
+
+ private enum State {
+ HEAD, LABEL, TAIL
+ };
+
+ public static Encoding forName(String label) {
+ if (label == null) {
+ throw new IllegalArgumentException("Label must not be null.");
+ }
+ if (label.length() == 0) {
+ throw new IllegalCharsetNameException(label);
+ }
+ // First try the fast path
+ int index = Arrays.binarySearch(LABELS, label);
+ if (index >= 0) {
+ return ENCODINGS_FOR_LABELS[index];
+ }
+ // Else, slow path
+ StringBuilder sb = new StringBuilder();
+ State state = State.HEAD;
+ for (int i = 0; i < label.length(); i++) {
+ char c = label.charAt(i);
+ if ((c == ' ') || (c == '\\n') || (c == '\\r') || (c == '\\t')
+ || (c == '\\u000C')) {
+ if (state == State.LABEL) {
+ state = State.TAIL;
+ }
+ continue;
+ }
+ if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
+ switch (state) {
+ case HEAD:
+ state = State.LABEL;
+ // Fall through
+ case LABEL:
+ sb.append(c);
+ continue;
+ case TAIL:
+ throw new IllegalCharsetNameException(label);
+ }
+ }
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ switch (state) {
+ case HEAD:
+ state = State.LABEL;
+ // Fall through
+ case LABEL:
+ sb.append(c);
+ continue;
+ case TAIL:
+ throw new IllegalCharsetNameException(label);
+ }
+ }
+ if ((c == '-') || (c == '+') || (c == '.') || (c == ':')
+ || (c == '_')) {
+ switch (state) {
+ case LABEL:
+ sb.append(c);
+ continue;
+ case HEAD:
+ case TAIL:
+ throw new IllegalCharsetNameException(label);
+ }
+ }
+ throw new IllegalCharsetNameException(label);
+ }
+ index = Arrays.binarySearch(LABELS, sb.toString());
+ if (index >= 0) {
+ return ENCODINGS_FOR_LABELS[index];
+ }
+ throw new UnsupportedCharsetException(label);
+ }
+
+ public static Encoding forNameNoReplacement(String label) {
+ Encoding encoding = Encoding.forName(label);
+ if (encoding == Encoding.REPLACEMENT) {
+ throw new UnsupportedCharsetException(label);
+ }
+ return encoding;
+ }
+
+ public static boolean isSupported(String label) {
+ try {
+ Encoding.forName(label);
+ } catch (UnsupportedCharsetException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isSupportedNoReplacement(String label) {
+ try {
+ Encoding.forNameNoReplacement(label);
+ } catch (UnsupportedCharsetException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public static SortedMap<String, Charset> availableCharsets() {
+ if (encodings == null) {
+ TreeMap<String, Charset> map = new TreeMap<String, Charset>();
+ for (Encoding encoding : ENCODINGS) {
+ map.put(encoding.name(), encoding);
+ }
+ encodings = Collections.unmodifiableSortedMap(map);
+ }
+ return encodings;
+ }
+
+ public static Encoding defaultCharset() {
+ return WINDOWS_1252;
+ }
+
+ @Override public boolean canEncode() {
+ return false;
+ }
+
+ @Override public boolean contains(Charset cs) {
+ return false;
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ throw new UnsupportedOperationException("Encoder not implemented.");
+ }
+}
+""")
+
+label_file.close()
+
+# Single-byte encodings
+
+for encoding in single_byte:
+ name = encoding["name"]
+ labels = encoding["labels"]
+ labels.sort()
+ class_name = to_camel_name(name)
+ mapping_name = name
+ if mapping_name == u"iso-8859-8-i":
+ mapping_name = u"iso-8859-8"
+ mapping = indexes[mapping_name]
+ class_file = open("src/nu/validator/encoding/%s.java" % class_name, "w")
+ class_file.write('''/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class ''')
+ class_file.write(class_name)
+ class_file.write(''' extends Encoding {
+
+ private static final char[] TABLE = {''')
+ fallible = False
+ comma = False
+ for code_point in mapping:
+ # XXX should we have error reporting?
+ if not code_point:
+ code_point = 0xFFFD
+ fallible = True
+ if comma:
+ class_file.write(",")
+ class_file.write("\n '\u%04x'" % code_point);
+ comma = True
+ class_file.write('''
+ };
+
+ private static final String[] LABELS = {''')
+
+ comma = False
+ for label in labels:
+ if comma:
+ class_file.write(",")
+ class_file.write("\n \"%s\"" % label);
+ comma = True
+ class_file.write('''
+ };
+
+ private static final String NAME = "''')
+ class_file.write(name)
+ class_file.write('''";
+
+ static final Encoding INSTANCE = new ''')
+ class_file.write(class_name)
+ class_file.write('''();
+
+ private ''')
+ class_file.write(class_name)
+ class_file.write('''() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new ''')
+ class_file.write("Fallible" if fallible else "Infallible")
+ class_file.write('''SingleByteDecoder(this, TABLE);
+ }
+
+}
+''')
+ class_file.close()
+
+# Multi-byte encodings
+
+for encoding in multi_byte:
+ name = encoding["name"]
+ labels = encoding["labels"]
+ labels.sort()
+ class_name = to_camel_name(name)
+ class_file = open("src/nu/validator/encoding/%s.java" % class_name, "w")
+ class_file.write('''/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class ''')
+ class_file.write(class_name)
+ class_file.write(''' extends Encoding {
+
+ private static final String[] LABELS = {''')
+
+ comma = False
+ for label in labels:
+ if comma:
+ class_file.write(",")
+ class_file.write("\n \"%s\"" % label);
+ comma = True
+ class_file.write('''
+ };
+
+ private static final String NAME = "''')
+ class_file.write(name)
+ class_file.write('''";
+
+ static final ''')
+ class_file.write(class_name)
+ class_file.write(''' INSTANCE = new ''')
+ class_file.write(class_name)
+ class_file.write('''();
+
+ private ''')
+ class_file.write(class_name)
+ class_file.write('''() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ ''')
+ if name == "gbk":
+ class_file.write('''return Charset.forName("gb18030").newDecoder();''')
+ elif name in MULTI_BYTE_DECODER_IMPLEMENTED:
+ class_file.write("return new %sDecoder(this);" % class_name)
+ else:
+ class_file.write('''return Charset.forName(NAME).newDecoder();''')
+ class_file.write('''
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ ''')
+ if name in MULTI_BYTE_ENCODER_IMPLEMENTED:
+ class_file.write("return new %sEncoder(this);" % class_name)
+ else:
+ class_file.write('''return Charset.forName(NAME).newEncoder();''')
+ class_file.write('''
+ }
+}
+''')
+ class_file.close()
+
+# Big5
+
+def null_to_zero(code_point):
+ if not code_point:
+ code_point = 0
+ return code_point
+
+index = []
+
+for code_point in indexes["big5"]:
+ index.append(null_to_zero(code_point))
+
+# There are four major gaps consisting of more than 4 consecutive invalid pointers
+gaps = []
+consecutive = 0
+consecutive_start = 0
+offset = 0
+for code_point in index:
+ if code_point == 0:
+ if consecutive == 0:
+ consecutive_start = offset
+ consecutive +=1
+ else:
+ if consecutive > 4:
+ gaps.append((consecutive_start, consecutive_start + consecutive))
+ consecutive = 0
+ offset += 1
+
+def invert_ranges(ranges, cap):
+ inverted = []
+ invert_start = 0
+ for (start, end) in ranges:
+ if start != 0:
+ inverted.append((invert_start, start))
+ invert_start = end
+ inverted.append((invert_start, cap))
+ return inverted
+
+cap = len(index)
+ranges = invert_ranges(gaps, cap)
+
+# Now compute a compressed lookup table for astralness
+
+gaps = []
+consecutive = 0
+consecutive_start = 0
+offset = 0
+for code_point in index:
+ if code_point <= 0xFFFF:
+ if consecutive == 0:
+ consecutive_start = offset
+ consecutive +=1
+ else:
+ if consecutive > 40:
+ gaps.append((consecutive_start, consecutive_start + consecutive))
+ consecutive = 0
+ offset += 1
+
+astral_ranges = invert_ranges(gaps, cap)
+
+class_file = open("src/nu/validator/encoding/Big5Data.java", "w")
+class_file.write('''/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+final class Big5Data {
+
+ private static final String ASTRALNESS = "''')
+
+bits = []
+for (low, high) in astral_ranges:
+ for i in xrange(low, high):
+ bits.append(1 if index[i] > 0xFFFF else 0)
+# pad length to multiple of 16
+for j in xrange(16 - (len(bits) % 16)):
+ bits.append(0)
+
+i = 0
+while i < len(bits):
+ accu = 0
+ for j in xrange(16):
+ accu |= bits[i + j] << j
+ if accu == 0x22:
+ class_file.write('\\"')
+ else:
+ class_file.write('\\u%04X' % accu)
+ i += 16
+
+class_file.write('''";
+
+''')
+
+j = 0
+for (low, high) in ranges:
+ class_file.write(''' private static final String TABLE%d = "''' % j)
+ for i in xrange(low, high):
+ class_file.write('\\u%04X' % (index[i] & 0xFFFF))
+ class_file.write('''";
+
+''')
+ j += 1
+
+class_file.write(''' private static boolean readBit(int i) {
+ return (ASTRALNESS.charAt(i >> 4) & (1 << (i & 0xF))) != 0;
+ }
+
+ static char lowBits(int pointer) {
+''')
+
+j = 0
+for (low, high) in ranges:
+ class_file.write(''' if (pointer < %d) {
+ return '\\u0000';
+ }
+ if (pointer < %d) {
+ return TABLE%d.charAt(pointer - %d);
+ }
+''' % (low, high, j, low))
+ j += 1
+
+class_file.write(''' return '\\u0000';
+ }
+
+ static boolean isAstral(int pointer) {
+''')
+
+base = 0
+for (low, high) in astral_ranges:
+ if high - low == 1:
+ class_file.write(''' if (pointer < %d) {
+ return false;
+ }
+ if (pointer == %d) {
+ return true;
+ }
+''' % (low, low))
+ else:
+ class_file.write(''' if (pointer < %d) {
+ return false;
+ }
+ if (pointer < %d) {
+ return readBit(%d + (pointer - %d));
+ }
+''' % (low, high, base, low))
+ base += (high - low)
+
+class_file.write(''' return false;
+ }
+
+ public static int findPointer(char lowBits, boolean isAstral) {
+ if (!isAstral) {
+ switch (lowBits) {
+''')
+
+hkscs_bound = (0xA1 - 0x81) * 157
+
+prefer_last = [
+ 0x2550,
+ 0x255E,
+ 0x2561,
+ 0x256A,
+ 0x5341,
+ 0x5345,
+]
+
+for code_point in prefer_last:
+ # Python lists don't have .rindex() :-(
+ for i in xrange(len(index) - 1, -1, -1):
+ candidate = index[i]
+ if candidate == code_point:
+ class_file.write(''' case 0x%04X:
+ return %d;
+''' % (code_point, i))
+ break
+
+class_file.write(''' default:
+ break;
+ }
+ }''')
+
+j = 0
+for (low, high) in ranges:
+ if high > hkscs_bound:
+ start = 0
+ if low <= hkscs_bound and hkscs_bound < high:
+ # This is the first range we don't ignore and the
+ # range that contains the first non-HKSCS pointer.
+ # Avoid searching HKSCS.
+ start = hkscs_bound - low
+ class_file.write('''
+ for (int i = %d; i < TABLE%d.length(); i++) {
+ if (TABLE%d.charAt(i) == lowBits) {
+ int pointer = i + %d;
+ if (isAstral == isAstral(pointer)) {
+ return pointer;
+ }
+ }
+ }''' % (start, j, j, low))
+ j += 1
+
+class_file.write('''
+ return 0;
+ }
+}
+''')
+class_file.close()
diff --git a/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/HtmlParser.gwt.xml b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/HtmlParser.gwt.xml
new file mode 100644
index 000000000..1eab09c21
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/HtmlParser.gwt.xml
@@ -0,0 +1,12 @@
+<module>
+ <inherits name="com.google.gwt.core.Core"/>
+ <inherits name="com.google.gwt.user.User"/>
+ <super-source path="translatable"/>
+ <source path="annotation"/>
+ <source path="common"/>
+ <source path="impl"/>
+ <source path="gwt"/>
+ <set-property name="user.agent" value="gecko1_8"/>
+ <entry-point class="nu.validator.htmlparser.gwt.HtmlParserModule"/>
+ <add-linker name="sso"/>
+</module>
diff --git a/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/BrowserTreeBuilder.java b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/BrowserTreeBuilder.java
new file mode 100644
index 000000000..29ef2a43a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/BrowserTreeBuilder.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.gwt;
+
+import java.util.LinkedList;
+
+import nu.validator.htmlparser.common.DocumentMode;
+import nu.validator.htmlparser.impl.CoalescingTreeBuilder;
+import nu.validator.htmlparser.impl.HtmlAttributes;
+
+import org.xml.sax.SAXException;
+
+import com.google.gwt.core.client.JavaScriptException;
+import com.google.gwt.core.client.JavaScriptObject;
+
+class BrowserTreeBuilder extends CoalescingTreeBuilder<JavaScriptObject> {
+
+ private JavaScriptObject document;
+
+ private JavaScriptObject script;
+
+ private JavaScriptObject placeholder;
+
+ private boolean readyToRun;
+
+ private final LinkedList<ScriptHolder> scriptStack = new LinkedList<ScriptHolder>();
+
+ private class ScriptHolder {
+ private final JavaScriptObject script;
+
+ private final JavaScriptObject placeholder;
+
+ /**
+ * @param script
+ * @param placeholder
+ */
+ public ScriptHolder(JavaScriptObject script,
+ JavaScriptObject placeholder) {
+ this.script = script;
+ this.placeholder = placeholder;
+ }
+
+ /**
+ * Returns the script.
+ *
+ * @return the script
+ */
+ public JavaScriptObject getScript() {
+ return script;
+ }
+
+ /**
+ * Returns the placeholder.
+ *
+ * @return the placeholder
+ */
+ public JavaScriptObject getPlaceholder() {
+ return placeholder;
+ }
+ }
+
+ protected BrowserTreeBuilder(JavaScriptObject document) {
+ super();
+ this.document = document;
+ installExplorerCreateElementNS(document);
+ }
+
+ private static native boolean installExplorerCreateElementNS(
+ JavaScriptObject doc) /*-{
+ if (!doc.createElementNS) {
+ doc.createElementNS = function (uri, local) {
+ if ("http://www.w3.org/1999/xhtml" == uri) {
+ return doc.createElement(local);
+ } else if ("http://www.w3.org/1998/Math/MathML" == uri) {
+ if (!doc.mathplayerinitialized) {
+ var obj = document.createElement("object");
+ obj.setAttribute("id", "mathplayer");
+ obj.setAttribute("classid", "clsid:32F66A20-7614-11D4-BD11-00104BD3F987");
+ document.getElementsByTagName("head")[0].appendChild(obj);
+ document.namespaces.add("m", "http://www.w3.org/1998/Math/MathML", "#mathplayer");
+ doc.mathplayerinitialized = true;
+ }
+ return doc.createElement("m:" + local);
+ } else if ("http://www.w3.org/2000/svg" == uri) {
+ if (!doc.renesisinitialized) {
+ var obj = document.createElement("object");
+ obj.setAttribute("id", "renesis");
+ obj.setAttribute("classid", "clsid:AC159093-1683-4BA2-9DCF-0C350141D7F2");
+ document.getElementsByTagName("head")[0].appendChild(obj);
+ document.namespaces.add("s", "http://www.w3.org/2000/svg", "#renesis");
+ doc.renesisinitialized = true;
+ }
+ return doc.createElement("s:" + local);
+ } else {
+ // throw
+ }
+ }
+ }
+ }-*/;
+
+ private static native boolean hasAttributeNS(JavaScriptObject element,
+ String uri, String localName) /*-{
+ return element.hasAttributeNS(uri, localName);
+ }-*/;
+
+ private static native void setAttributeNS(JavaScriptObject element,
+ String uri, String localName, String value) /*-{
+ element.setAttributeNS(uri, localName, value);
+ }-*/;
+
+ @Override protected void addAttributesToElement(JavaScriptObject element,
+ HtmlAttributes attributes) throws SAXException {
+ try {
+ for (int i = 0; i < attributes.getLength(); i++) {
+ String localName = attributes.getLocalNameNoBoundsCheck(i);
+ String uri = attributes.getURINoBoundsCheck(i);
+ if (!hasAttributeNS(element, uri, localName)) {
+ setAttributeNS(element, uri, localName,
+ attributes.getValueNoBoundsCheck(i));
+ }
+ }
+ } catch (JavaScriptException e) {
+ fatal(e);
+ }
+ }
+
+ private static native void appendChild(JavaScriptObject parent,
+ JavaScriptObject child) /*-{
+ parent.appendChild(child);
+ }-*/;
+
+ private static native JavaScriptObject createTextNode(JavaScriptObject doc,
+ String text) /*-{
+ return doc.createTextNode(text);
+ }-*/;
+
+ private static native JavaScriptObject getLastChild(JavaScriptObject node) /*-{
+ return node.lastChild;
+ }-*/;
+
+ private static native void extendTextNode(JavaScriptObject node, String text) /*-{
+ node.data += text;
+ }-*/;
+
+ @Override protected void appendCharacters(JavaScriptObject parent,
+ String text) throws SAXException {
+ try {
+ if (parent == placeholder) {
+ appendChild(script, createTextNode(document, text));
+
+ }
+ JavaScriptObject lastChild = getLastChild(parent);
+ if (lastChild != null && getNodeType(lastChild) == 3) {
+ extendTextNode(lastChild, text);
+ return;
+ }
+ appendChild(parent, createTextNode(document, text));
+ } catch (JavaScriptException e) {
+ fatal(e);
+ }
+ }
+
+ private static native boolean hasChildNodes(JavaScriptObject element) /*-{
+ return element.hasChildNodes();
+ }-*/;
+
+ private static native JavaScriptObject getFirstChild(
+ JavaScriptObject element) /*-{
+ return element.firstChild;
+ }-*/;
+
+ @Override protected void appendChildrenToNewParent(
+ JavaScriptObject oldParent, JavaScriptObject newParent)
+ throws SAXException {
+ try {
+ while (hasChildNodes(oldParent)) {
+ appendChild(newParent, getFirstChild(oldParent));
+ }
+ } catch (JavaScriptException e) {
+ fatal(e);
+ }
+ }
+
+ private static native JavaScriptObject createComment(JavaScriptObject doc,
+ String text) /*-{
+ return doc.createComment(text);
+ }-*/;
+
+ @Override protected void appendComment(JavaScriptObject parent,
+ String comment) throws SAXException {
+ try {
+ if (parent == placeholder) {
+ appendChild(script, createComment(document, comment));
+ }
+ appendChild(parent, createComment(document, comment));
+ } catch (JavaScriptException e) {
+ fatal(e);
+ }
+ }
+
+ @Override protected void appendCommentToDocument(String comment)
+ throws SAXException {
+ try {
+ appendChild(document, createComment(document, comment));
+ } catch (JavaScriptException e) {
+ fatal(e);
+ }
+ }
+
+ private static native JavaScriptObject createElementNS(
+ JavaScriptObject doc, String ns, String local) /*-{
+ return doc.createElementNS(ns, local);
+ }-*/;
+
+ @Override protected JavaScriptObject createElement(String ns, String name,
+ HtmlAttributes attributes) throws SAXException {
+ try {
+ JavaScriptObject rv = createElementNS(document, ns, name);
+ for (int i = 0; i < attributes.getLength(); i++) {
+ setAttributeNS(rv, attributes.getURINoBoundsCheck(i),
+ attributes.getLocalNameNoBoundsCheck(i),
+ attributes.getValueNoBoundsCheck(i));
+ }
+
+ if ("script" == name) {
+ if (placeholder != null) {
+ scriptStack.addLast(new ScriptHolder(script, placeholder));
+ }
+ script = rv;
+ placeholder = createElementNS(document,
+ "http://n.validator.nu/placeholder/", "script");
+ rv = placeholder;
+ for (int i = 0; i < attributes.getLength(); i++) {
+ setAttributeNS(rv, attributes.getURINoBoundsCheck(i),
+ attributes.getLocalNameNoBoundsCheck(i),
+ attributes.getValueNoBoundsCheck(i));
+ }
+ }
+
+ return rv;
+ } catch (JavaScriptException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ @Override protected JavaScriptObject createHtmlElementSetAsRoot(
+ HtmlAttributes attributes) throws SAXException {
+ try {
+ JavaScriptObject rv = createElementNS(document,
+ "http://www.w3.org/1999/xhtml", "html");
+ for (int i = 0; i < attributes.getLength(); i++) {
+ setAttributeNS(rv, attributes.getURINoBoundsCheck(i),
+ attributes.getLocalNameNoBoundsCheck(i),
+ attributes.getValueNoBoundsCheck(i));
+ }
+ appendChild(document, rv);
+ return rv;
+ } catch (JavaScriptException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ private static native JavaScriptObject getParentNode(
+ JavaScriptObject element) /*-{
+ return element.parentNode;
+ }-*/;
+
+ @Override protected void appendElement(JavaScriptObject child,
+ JavaScriptObject newParent) throws SAXException {
+ try {
+ if (newParent == placeholder) {
+ appendChild(script, cloneNodeDeep(child));
+ }
+ appendChild(newParent, child);
+ } catch (JavaScriptException e) {
+ fatal(e);
+ }
+ }
+
+ @Override protected boolean hasChildren(JavaScriptObject element)
+ throws SAXException {
+ try {
+ return hasChildNodes(element);
+ } catch (JavaScriptException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ private static native void insertBeforeNative(JavaScriptObject parent,
+ JavaScriptObject child, JavaScriptObject sibling) /*-{
+ parent.insertBefore(child, sibling);
+ }-*/;
+
+ private static native int getNodeType(JavaScriptObject node) /*-{
+ return node.nodeType;
+ }-*/;
+
+ private static native JavaScriptObject cloneNodeDeep(JavaScriptObject node) /*-{
+ return node.cloneNode(true);
+ }-*/;
+
+ /**
+ * Returns the document.
+ *
+ * @return the document
+ */
+ JavaScriptObject getDocument() {
+ JavaScriptObject rv = document;
+ document = null;
+ return rv;
+ }
+
+ private static native JavaScriptObject createDocumentFragment(
+ JavaScriptObject doc) /*-{
+ return doc.createDocumentFragment();
+ }-*/;
+
+ JavaScriptObject getDocumentFragment() {
+ JavaScriptObject rv = createDocumentFragment(document);
+ JavaScriptObject rootElt = getFirstChild(document);
+ while (hasChildNodes(rootElt)) {
+ appendChild(rv, getFirstChild(rootElt));
+ }
+ document = null;
+ return rv;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#createJavaScriptObject(String,
+ * java.lang.String, org.xml.sax.Attributes, java.lang.Object)
+ */
+ @Override protected JavaScriptObject createElement(String ns, String name,
+ HtmlAttributes attributes, JavaScriptObject form)
+ throws SAXException {
+ try {
+ JavaScriptObject rv = createElement(ns, name, attributes);
+ // rv.setUserData("nu.validator.form-pointer", form, null);
+ return rv;
+ } catch (JavaScriptException e) {
+ fatal(e);
+ return null;
+ }
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#start()
+ */
+ @Override protected void start(boolean fragment) throws SAXException {
+ script = null;
+ placeholder = null;
+ readyToRun = false;
+ }
+
+ protected void documentMode(DocumentMode mode, String publicIdentifier,
+ String systemIdentifier, boolean html4SpecificAdditionalErrorChecks)
+ throws SAXException {
+ // document.setUserData("nu.validator.document-mode", mode, null);
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#elementPopped(java.lang.String,
+ * java.lang.String, java.lang.Object)
+ */
+ @Override protected void elementPopped(String ns, String name,
+ JavaScriptObject node) throws SAXException {
+ if (node == placeholder) {
+ readyToRun = true;
+ requestSuspension();
+ }
+ }
+
+ private static native void replace(JavaScriptObject oldNode,
+ JavaScriptObject newNode) /*-{
+ oldNode.parentNode.replaceChild(newNode, oldNode);
+ }-*/;
+
+ private static native JavaScriptObject getPreviousSibling(JavaScriptObject node) /*-{
+ return node.previousSibling;
+ }-*/;
+
+ void maybeRunScript() {
+ if (readyToRun) {
+ readyToRun = false;
+ replace(placeholder, script);
+ if (scriptStack.isEmpty()) {
+ script = null;
+ placeholder = null;
+ } else {
+ ScriptHolder scriptHolder = scriptStack.removeLast();
+ script = scriptHolder.getScript();
+ placeholder = scriptHolder.getPlaceholder();
+ }
+ }
+ }
+
+ @Override protected void insertFosterParentedCharacters(String text,
+ JavaScriptObject table, JavaScriptObject stackParent)
+ throws SAXException {
+ try {
+ JavaScriptObject parent = getParentNode(table);
+ if (parent != null) { // always an element if not null
+ JavaScriptObject previousSibling = getPreviousSibling(table);
+ if (previousSibling != null
+ && getNodeType(previousSibling) == 3) {
+ extendTextNode(previousSibling, text);
+ return;
+ }
+ insertBeforeNative(parent, createTextNode(document, text), table);
+ return;
+ }
+ JavaScriptObject lastChild = getLastChild(stackParent);
+ if (lastChild != null && getNodeType(lastChild) == 3) {
+ extendTextNode(lastChild, text);
+ return;
+ }
+ appendChild(stackParent, createTextNode(document, text));
+ } catch (JavaScriptException e) {
+ fatal(e);
+ }
+ }
+
+ @Override protected void insertFosterParentedChild(JavaScriptObject child,
+ JavaScriptObject table, JavaScriptObject stackParent)
+ throws SAXException {
+ JavaScriptObject parent = getParentNode(table);
+ try {
+ if (parent != null && getNodeType(parent) == 1) {
+ insertBeforeNative(parent, child, table);
+ } else {
+ appendChild(stackParent, child);
+ }
+ } catch (JavaScriptException e) {
+ fatal(e);
+ }
+ }
+
+ private static native void removeChild(JavaScriptObject parent,
+ JavaScriptObject child) /*-{
+ parent.removeChild(child);
+ }-*/;
+
+ @Override protected void detachFromParent(JavaScriptObject element)
+ throws SAXException {
+ try {
+ JavaScriptObject parent = getParentNode(element);
+ if (parent != null) {
+ removeChild(parent, element);
+ }
+ } catch (JavaScriptException e) {
+ fatal(e);
+ }
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/HtmlParser.java b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/HtmlParser.java
new file mode 100644
index 000000000..1d71cdfd6
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/HtmlParser.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.gwt;
+
+import java.util.LinkedList;
+
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.impl.ErrorReportingTokenizer;
+import nu.validator.htmlparser.impl.Tokenizer;
+import nu.validator.htmlparser.impl.UTF16Buffer;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.Timer;
+
+/**
+ * This class implements an HTML5 parser that exposes data through the DOM
+ * interface.
+ *
+ * <p>By default, when using the constructor without arguments, the
+ * this parser treats XML 1.0-incompatible infosets as fatal errors.
+ * This corresponds to
+ * <code>FATAL</code> as the general XML violation policy. To make the parser
+ * support non-conforming HTML fully per the HTML 5 spec while on the other
+ * hand potentially violating the DOM API contract, set the general XML
+ * violation policy to <code>ALLOW</code>. This does not work with a standard
+ * DOM implementation. Handling all input without fatal errors and without
+ * violating the DOM API contract is possible by setting
+ * the general XML violation policy to <code>ALTER_INFOSET</code>. <em>This
+ * makes the parser non-conforming</em> but is probably the most useful
+ * setting for most applications.
+ *
+ * <p>The doctype is not represented in the tree.
+ *
+ * <p>The document mode is represented as user data <code>DocumentMode</code>
+ * object with the key <code>nu.validator.document-mode</code> on the document
+ * node.
+ *
+ * <p>The form pointer is also stored as user data with the key
+ * <code>nu.validator.form-pointer</code>.
+ *
+ * @version $Id: HtmlDocumentBuilder.java 255 2008-05-29 08:57:38Z hsivonen $
+ * @author hsivonen
+ */
+public class HtmlParser {
+
+ private static final int CHUNK_SIZE = 512;
+
+ private final Tokenizer tokenizer;
+
+ private final BrowserTreeBuilder domTreeBuilder;
+
+ private final StringBuilder documentWriteBuffer = new StringBuilder();
+
+ private ErrorHandler errorHandler;
+
+ private UTF16Buffer stream;
+
+ private int streamLength;
+
+ private boolean lastWasCR;
+
+ private boolean ending;
+
+ private ParseEndListener parseEndListener;
+
+ private final LinkedList<UTF16Buffer> bufferStack = new LinkedList<UTF16Buffer>();
+
+ /**
+ * Instantiates the parser
+ *
+ * @param implementation
+ * the DOM implementation
+ * @param xmlPolicy the policy
+ */
+ public HtmlParser(JavaScriptObject document) {
+ this.domTreeBuilder = new BrowserTreeBuilder(document);
+ this.tokenizer = new ErrorReportingTokenizer(domTreeBuilder);
+ this.domTreeBuilder.setNamePolicy(XmlViolationPolicy.ALTER_INFOSET);
+ this.tokenizer.setCommentPolicy(XmlViolationPolicy.ALTER_INFOSET);
+ this.tokenizer.setContentNonXmlCharPolicy(XmlViolationPolicy.ALTER_INFOSET);
+ this.tokenizer.setContentSpacePolicy(XmlViolationPolicy.ALTER_INFOSET);
+ this.tokenizer.setNamePolicy(XmlViolationPolicy.ALTER_INFOSET);
+ this.tokenizer.setXmlnsPolicy(XmlViolationPolicy.ALTER_INFOSET);
+ }
+
+ /**
+ * Parses a document from a SAX <code>InputSource</code>.
+ * @param is the source
+ * @return the doc
+ * @see javax.xml.parsers.DocumentBuilder#parse(org.xml.sax.InputSource)
+ */
+ public void parse(String source, ParseEndListener callback) throws SAXException {
+ parseEndListener = callback;
+ domTreeBuilder.setFragmentContext(null);
+ tokenize(source, null);
+ }
+
+ /**
+ * @param is
+ * @throws SAXException
+ * @throws IOException
+ * @throws MalformedURLException
+ */
+ private void tokenize(String source, String context) throws SAXException {
+ lastWasCR = false;
+ ending = false;
+ documentWriteBuffer.setLength(0);
+ streamLength = source.length();
+ stream = new UTF16Buffer(source.toCharArray(), 0,
+ (streamLength < CHUNK_SIZE ? streamLength : CHUNK_SIZE));
+ bufferStack.clear();
+ push(stream);
+ domTreeBuilder.setFragmentContext(context == null ? null : context.intern());
+ tokenizer.start();
+ pump();
+ }
+
+ private void pump() throws SAXException {
+ if (ending) {
+ tokenizer.end();
+ domTreeBuilder.getDocument(); // drops the internal reference
+ parseEndListener.parseComplete();
+ // Don't schedule timeout
+ return;
+ }
+
+ int docWriteLen = documentWriteBuffer.length();
+ if (docWriteLen > 0) {
+ char[] newBuf = new char[docWriteLen];
+ documentWriteBuffer.getChars(0, docWriteLen, newBuf, 0);
+ push(new UTF16Buffer(newBuf, 0, docWriteLen));
+ documentWriteBuffer.setLength(0);
+ }
+
+ for (;;) {
+ UTF16Buffer buffer = peek();
+ if (!buffer.hasMore()) {
+ if (buffer == stream) {
+ if (buffer.getEnd() == streamLength) {
+ // Stop parsing
+ tokenizer.eof();
+ ending = true;
+ break;
+ } else {
+ int newEnd = buffer.getStart() + CHUNK_SIZE;
+ buffer.setEnd(newEnd < streamLength ? newEnd
+ : streamLength);
+ continue;
+ }
+ } else {
+ pop();
+ continue;
+ }
+ }
+ // now we have a non-empty buffer
+ buffer.adjust(lastWasCR);
+ lastWasCR = false;
+ if (buffer.hasMore()) {
+ lastWasCR = tokenizer.tokenizeBuffer(buffer);
+ domTreeBuilder.maybeRunScript();
+ break;
+ } else {
+ continue;
+ }
+ }
+
+ // schedule
+ Timer timer = new Timer() {
+
+ @Override public void run() {
+ try {
+ pump();
+ } catch (SAXException e) {
+ ending = true;
+ if (errorHandler != null) {
+ try {
+ errorHandler.fatalError(new SAXParseException(
+ e.getMessage(), null, null, -1, -1, e));
+ } catch (SAXException e1) {
+ }
+ }
+ }
+ }
+
+ };
+ timer.schedule(1);
+ }
+
+ private void push(UTF16Buffer buffer) {
+ bufferStack.addLast(buffer);
+ }
+
+ private UTF16Buffer peek() {
+ return bufferStack.getLast();
+ }
+
+ private void pop() {
+ bufferStack.removeLast();
+ }
+
+ public void documentWrite(String text) throws SAXException {
+ UTF16Buffer buffer = new UTF16Buffer(text.toCharArray(), 0, text.length());
+ while (buffer.hasMore()) {
+ buffer.adjust(lastWasCR);
+ lastWasCR = false;
+ if (buffer.hasMore()) {
+ lastWasCR = tokenizer.tokenizeBuffer(buffer);
+ domTreeBuilder.maybeRunScript();
+ }
+ }
+ }
+
+ /**
+ * @see javax.xml.parsers.DocumentBuilder#setErrorHandler(org.xml.sax.ErrorHandler)
+ */
+ public void setErrorHandler(ErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ domTreeBuilder.setErrorHandler(errorHandler);
+ tokenizer.setErrorHandler(errorHandler);
+ }
+
+ /**
+ * Sets whether comment nodes appear in the tree.
+ * @param ignoreComments <code>true</code> to ignore comments
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setIgnoringComments(boolean)
+ */
+ public void setIgnoringComments(boolean ignoreComments) {
+ domTreeBuilder.setIgnoringComments(ignoreComments);
+ }
+
+ /**
+ * Sets whether the parser considers scripting to be enabled for noscript treatment.
+ * @param scriptingEnabled <code>true</code> to enable
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setScriptingEnabled(boolean)
+ */
+ public void setScriptingEnabled(boolean scriptingEnabled) {
+ domTreeBuilder.setScriptingEnabled(scriptingEnabled);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/HtmlParserModule.java b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/HtmlParserModule.java
new file mode 100644
index 000000000..255a02d13
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/HtmlParserModule.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.gwt;
+
+import org.xml.sax.SAXException;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class HtmlParserModule implements EntryPoint {
+
+ private static native void zapChildren(JavaScriptObject node) /*-{
+ while (node.hasChildNodes()) {
+ node.removeChild(node.lastChild);
+ }
+ }-*/;
+
+ private static native void installDocWrite(JavaScriptObject doc, HtmlParser parser) /*-{
+ doc.write = function() {
+ if (arguments.length == 0) {
+ return;
+ }
+ var text = arguments[0];
+ for (var i = 1; i < arguments.length; i++) {
+ text += arguments[i];
+ }
+ parser.@nu.validator.htmlparser.gwt.HtmlParser::documentWrite(Ljava/lang/String;)(text);
+ }
+ doc.writeln = function() {
+ if (arguments.length == 0) {
+ parser.@nu.validator.htmlparser.gwt.HtmlParser::documentWrite(Ljava/lang/String;)("\n");
+ return;
+ }
+ var text = arguments[0];
+ for (var i = 1; i < arguments.length; i++) {
+ text += arguments[i];
+ }
+ text += "\n";
+ parser.@nu.validator.htmlparser.gwt.HtmlParser::documentWrite(Ljava/lang/String;)(text);
+ }
+ }-*/;
+
+ @SuppressWarnings("unused")
+ private static void parseHtmlDocument(String source, JavaScriptObject document, JavaScriptObject readyCallback, JavaScriptObject errorHandler) throws SAXException {
+ if (readyCallback == null) {
+ readyCallback = JavaScriptObject.createFunction();
+ }
+ zapChildren(document);
+ HtmlParser parser = new HtmlParser(document);
+ parser.setScriptingEnabled(true);
+ // XXX error handler
+
+ installDocWrite(document, parser);
+
+ parser.parse(source, new ParseEndListener(readyCallback));
+ }
+
+ private static native void exportEntryPoints() /*-{
+ $wnd.parseHtmlDocument = @nu.validator.htmlparser.gwt.HtmlParserModule::parseHtmlDocument(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;);
+ }-*/;
+
+
+ public void onModuleLoad() {
+ exportEntryPoints();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/ParseEndListener.java b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/ParseEndListener.java
new file mode 100644
index 000000000..43235c5be
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/gwt/ParseEndListener.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.gwt;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class ParseEndListener {
+
+ private final JavaScriptObject callback;
+
+ /**
+ * @param callback
+ */
+ public ParseEndListener(JavaScriptObject callback) {
+ this.callback = callback;
+ }
+
+ public void parseComplete() {
+ call(callback);
+ }
+
+ private static native void call(JavaScriptObject callback) /*-{
+ callback();
+ }-*/;
+
+}
diff --git a/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/HtmlParser.html b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/HtmlParser.html
new file mode 100644
index 000000000..4d9cde81c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/HtmlParser.html
@@ -0,0 +1,225 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Live DOM Viewer</title>
+ <script type="text/javascript" language="javascript" src="nu.validator.htmlparser.HtmlParser.nocache.js"></script>
+ <style>
+ h1 { margin: 0; }
+ h2 { font-size: small; margin: 1em 0 0; }
+ p, ul, pre { margin: 0; }
+ p { border: inset thin; }
+ textarea { width: 100%; -width: 99%; height: 8em; border: 0; }
+ iframe { width: 100%; height: 12em; border: 0; }
+/* iframe.large { height: 24em; } */
+ pre { border: inset thin; padding: 0.5em; color: gray; }
+ pre samp { color: black; }
+ #dom { border: inset thin; padding: 0.5em 0.5em 0.5em 1em; color: black; min-height: 5em; font-family: monospace; background: white; }
+ #dom ul { padding: 0 0 0 1em; margin: 0; }
+ #dom li { padding: 0; margin: 0; list-style: none; position: relative; }
+ #dom li li { list-style: disc; }
+ #dom .t1 code { color: purple; font-weight: bold; }
+ #dom .t2 { font-style: normal; font-family: monospace; }
+ #dom .t2 .name { color: black; font-weight: bold; }
+ #dom .t2 .value { color: blue; font-weight: normal; }
+ #dom .t3 code, #dom .t4 code, #dom .t5 code { color: gray; }
+ #dom .t7 code, #dom .t8 code { color: green; }
+ #dom span { font-style: italic; font-family: serif; }
+ #dom .t10 code { color: teal; }
+ #dom .misparented, #dom .misparented code { color: red; font-weight: bold; }
+ #dom.hidden, .hidden { visibility: hidden; margin: 0.5em 0; padding: 0; height: 0; min-height: 0; }
+ pre#log { color: black; font: small monospace; }
+ script + p { border: none; font-size: smaller; margin: 0.8em 0.3em; }
+ </style>
+ <style title="Tree View">
+ #dom li li { list-style: none; }
+ #dom li:first-child::before { position: absolute; top: 0; height: 0.6em; left: -0.75em; width: 0.5em; border-style: none none solid solid; content: ''; border-width: 0.1em; }
+ #dom li:not(:last-child)::after { position: absolute; top: 0; bottom: -0.6em; left: -0.75em; width: 0.5em; border-style: none none solid solid; content: ''; border-width: 0.1em; }
+ </style>
+ <script>
+ if (navigator.userAgent.match('Gecko/(\\d+)') && RegExp.$1 == '20060217' && RegExp.$1 != '00000000') {
+ var style = document.getElementsByTagName('style')[1];
+ style.parentNode.removeChild(style);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1>Live DOM Viewer</h1>
+ <h2>Markup to test (<a href="data:," id="permalink" rel="bookmark">permalink</a>, <a href="javascript:up()">upload</a>, <a href="javascript:down()">download</a>, <a href="#" onclick="toggleVisibility(this); return false">hide</a>): <span id="updown-status"></span></h2>
+ <p><textarea oninput="updateInput(event)" onkeydown="updateInput(event)">&lt;!DOCTYPE html>
+...</textarea></p>
+ <h2><a href="data:," id="domview">DOM view</a> (<a href="#" onclick="toggleVisibility(this); return false;">hide</a>, <a href="#" onclick="updateDOM()">refresh</a>):</h2>
+ <ul id="dom"></ul>
+ <h2><a href="data:," id="link">Rendered view</a>: (<a href="#" onclick="toggleVisibility(this); return false;">hide</a><!--, <a href="#" onclick="grow(this)">grow</a>-->):</h2>
+ <p><iframe src="blank.html"></iframe></p> <!-- data:, -->
+ <h2>innerHTML view: (<a href="#" onclick="toggleVisibility(this); return false;">show</a>, <a href="#" onclick="updateDOM()">refresh</a>):</h2>
+ <pre class="hidden">&lt;!DOCTYPE HTML>&lt;html><samp></samp>&lt;/html></pre>
+ <h2>Log: (<a href="#" onclick="toggleVisibility(this); return false;">hide</a>):</h2>
+ <pre id="log">Script not loaded.</pre>
+ <script>
+ var iframe = document.getElementsByTagName('iframe')[0];
+ var textarea = document.getElementsByTagName('textarea')[0];
+ var pre = document.getElementsByTagName('samp')[0];
+ var dom = document.getElementsByTagName('ul')[0];
+ var log = document.getElementById('log');
+ var updownStatus = document.getElementById('updown-status');
+ var delayedUpdater = 0;
+ var lastString = '';
+ var logBuffer = '';
+ var logBuffering = false;
+ function updateInput(event) {
+ if (delayedUpdater) {
+ clearTimeout(delayedUpdater);
+ delayedUpdater = 0;
+ }
+ delayedUpdater = setTimeout(update, 100);
+ }
+ function afterParse() {
+ lastString = textarea.value;
+ setTimeout(updateDOM, 100);
+ updown('');
+ }
+ function update() {
+ if (lastString != textarea.value) {
+ logBuffering = true;
+ document.getElementById('link').href = 'data:text/html;charset=utf-8,' + encodeURIComponent(textarea.value);
+ iframe.contentWindow.onerror = function (a, b, c) {
+ record('error: ' + a + ' on line ' + c);
+ }
+ iframe.contentWindow.w = function (s) {
+ record('log: ' + s);
+ }
+ window.parseHtmlDocument(textarea.value, iframe.contentWindow.document, afterParse, null);
+ }
+ }
+ function updateDOM() {
+ while (pre.firstChild) pre.removeChild(pre.firstChild);
+ pre.appendChild(document.createTextNode(iframe.contentWindow.document.documentElement.innerHTML));
+ printDOM(dom, iframe.contentWindow.document);
+ document.getElementById('domview').href = 'data:text/plain;charset=utf-8,<ul class="domTree">' + encodeURIComponent(dom.innerHTML + '</ul>');
+ document.getElementById('permalink').href = '?' + encodeURIComponent(textarea.value);
+ record('rendering mode: ' + iframe.contentWindow.document.compatMode);
+ if (iframe.contentWindow.document.title)
+ record('document.title: ' + iframe.contentWindow.document.title);
+ else
+ record('document has no title');
+ while (log.firstChild != log.lastChild)
+ log.removeChild(log.lastChild);
+ log.firstChild.data = logBuffer;
+ logBuffering = false;
+ logBuffer = '';
+ }
+ function printDOM(ul, node) {
+ while (ul.firstChild) ul.removeChild(ul.firstChild);
+ for (var i = 0; i < node.childNodes.length; i += 1) {
+ var li = document.createElement('li');
+ li.className = 't' + node.childNodes[i].nodeType;
+ if (node.childNodes[i].nodeType == 10) {
+ li.appendChild(document.createTextNode('DOCTYPE: '));
+ }
+ var code = document.createElement('code');
+ code.appendChild(document.createTextNode(node.childNodes[i].nodeName));
+ li.appendChild(code);
+ if (node.childNodes[i].nodeValue) {
+ var span = document.createElement('span');
+ span.appendChild(document.createTextNode(node.childNodes[i].nodeValue));
+ li.appendChild(document.createTextNode(': '));
+ li.appendChild(span);
+ }
+ if (node.childNodes[i].attributes)
+ for (var j = 0; j < node.childNodes[i].attributes.length; j += 1) {
+ if (node.childNodes[i].attributes[j].specified) {
+ var attName = document.createElement('code');
+ attName.appendChild(document.createTextNode(node.childNodes[i].attributes[j].nodeName));
+ attName.className = 'attribute name';
+ var attValue = document.createElement('code');
+ attValue.appendChild(document.createTextNode(node.childNodes[i].attributes[j].nodeValue));
+ attValue.className = 'attribute value';
+ var att = document.createElement('span');
+ att.className = 't2';
+ att.appendChild(attName);
+ att.appendChild(document.createTextNode('="'));
+ att.appendChild(attValue);
+ att.appendChild(document.createTextNode('"'));
+ li.appendChild(document.createTextNode(' '));
+ li.appendChild(att);
+ }
+ }
+ if (node.childNodes[i].parentNode == node) {
+ if (node.childNodes[i].childNodes.length) {
+ var ul2 = document.createElement('ul');
+ li.appendChild(ul2);
+ printDOM(ul2, node.childNodes[i]);
+ }
+ } else {
+ li.className += ' misparented';
+ }
+ ul.appendChild(li);
+ }
+ }
+ function toggleVisibility(link) {
+ var n = link.parentNode.nextSibling;
+ if (n.nodeType == 3 /* text node */) n = n.nextSibling; // we should always do this but in IE, text nodes vanish
+ n.className = (n.className == "hidden") ? '' : 'hidden';
+ link.firstChild.data = n.className == "hidden" ? "show" : "hide";
+ }
+/*
+ function grow(link) {
+ var n = link.parentNode.nextSibling;
+ if (n.nodeType == 3 /-* text node *-/) n = n.nextSibling; // we should always do this but in IE, text nodes vanish
+ n.className = (n.className == "large") ? '' : 'large';
+ link.firstChild.data = n.className == "grow" ? "shrink" : "grow";
+ }
+*/
+ function down() {
+ updown('downloading...');
+ var request = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
+ request.onreadystatechange = function () {
+ updown('downloading... ' + request.readyState + '/4');
+ if (request.readyState == 4) {
+ textarea.value = request.responseText;
+ update();
+ updown('downloaded');
+ }
+ };
+ request.open('GET', 'clipboard.cgi', true);
+ request.send(null);
+ }
+ function up() {
+ updown('uploading...');
+ var request = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
+ request.onreadystatechange = function () {
+ updown('uploading... ' + request.readyState + '/4');
+ if (request.readyState == 4) {
+ updown('uploaded');
+ }
+ };
+ request.open('POST', 'clipboard.cgi', true);
+ request.setRequestHeader('Content-Type', 'text/plain');
+ request.send(textarea.value);
+ }
+ function init() {
+ var uri = location.search;
+ if (uri)
+ textarea.value = decodeURIComponent(uri.substring(1, uri.length));
+ update();
+ }
+ function record(s) {
+ if (logBuffering)
+ logBuffer += s + '\r\n';
+ else
+ log.appendChild(document.createTextNode(s + '\r\n'));
+ }
+ function updown(s) {
+ while (updownStatus.firstChild) updownStatus.removeChild(updownStatus.firstChild);
+ updownStatus.appendChild(document.createTextNode(s));
+ }
+ </script>
+ <p>This script puts a function <code>w(<var>s</var>)</code> into the
+ global scope of the test page, where <var>s</vaR> is a string to
+ output to the log. Also, five files are accessible in the current
+ directory for test purposes: <code>image</code> (a GIF image),
+ <code>flash</code> (a Flash file), <code>script</code> (a JS file),
+ <code>style</code> (a CSS file), and <code>document</code> (an HTML
+ file).</p>
+ </body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/LICENSE.Live-DOM-viewer.txt b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/LICENSE.Live-DOM-viewer.txt
new file mode 100644
index 000000000..bd2f4fcf1
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/LICENSE.Live-DOM-viewer.txt
@@ -0,0 +1,25 @@
+From:
+http://software.hixie.ch/utilities/js/live-dom-viewer/LICENSE
+regarding the upstream of HtmlParser.html:
+
+The MIT License
+
+Copyright (c) 2000, 2006, 2008 Ian Hickson and various contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/blank.html b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/blank.html
new file mode 100644
index 000000000..a8756c9f7
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/gwt-src/nu/validator/htmlparser/public/blank.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<title></title> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/mozilla-export-scripts/README.txt b/components/htmlfive/java/htmlparser/mozilla-export-scripts/README.txt
new file mode 100644
index 000000000..3567b846c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/mozilla-export-scripts/README.txt
@@ -0,0 +1,25 @@
+These scripts export the Java-to-C++ translator and the java source files that
+implement the HTML5 parser. The exported translator may be used (with no
+external dependencies) to translate the exported java source files into Gecko-
+compatible C++.
+
+Hacking the translator itself still requires a working copy of the Java HTML5
+parser repository, but hacking the parser (modifying the Java source files and
+performing the translation) should now be possible using only files committed
+to the mozilla source tree.
+
+Run any of these scripts without arguments to receive usage instructions.
+
+ make-translator-jar.sh: compiles the Java-to-C++ translator into a .jar file
+ export-java-srcs.sh: exports minimal java source files implementing the
+ HTML5 parser
+ export-translator.sh: exports the compiled translator and javaparser.jar
+ export-all.sh: runs the previous two scripts
+ util.sh: provides various shell utility functions to the
+ scripts listed above (does nothing if run directly)
+
+All path arguments may be either absolute or relative. This includes the path
+to the script itself ($0), so the directory from which you run these scripts
+doesn't matter.
+
+Ben Newman (7 July 2009)
diff --git a/components/htmlfive/java/htmlparser/mozilla-export-scripts/export-all.sh b/components/htmlfive/java/htmlparser/mozilla-export-scripts/export-all.sh
new file mode 100644
index 000000000..9ae07d33d
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/mozilla-export-scripts/export-all.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env sh
+
+SCRIPT_DIR=`dirname $0`
+source $SCRIPT_DIR/util.sh
+SCRIPT_DIR=`abs $SCRIPT_DIR`
+
+if [ $# -eq 1 ]
+then
+ MOZ_PARSER_PATH=`abs $1`
+else
+ echo
+ echo "Usage: sh `basename $0` /path/to/mozilla-central/parser/html"
+ echo "Note that relative paths will work just fine."
+ echo
+ exit 1
+fi
+
+$SCRIPT_DIR/export-translator.sh $MOZ_PARSER_PATH
+$SCRIPT_DIR/export-java-srcs.sh $MOZ_PARSER_PATH
+
+echo
+echo "Now go to $MOZ_PARSER_PATH and run"
+echo " java -jar javalib/translator.jar javasrc . nsHtml5AtomList.h"
+echo
diff --git a/components/htmlfive/java/htmlparser/mozilla-export-scripts/export-java-srcs.sh b/components/htmlfive/java/htmlparser/mozilla-export-scripts/export-java-srcs.sh
new file mode 100644
index 000000000..6d32b07da
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/mozilla-export-scripts/export-java-srcs.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env sh
+
+SCRIPT_DIR=`dirname $0`
+source $SCRIPT_DIR/util.sh
+SCRIPT_DIR=`abs $SCRIPT_DIR`
+
+SRCDIR=`abs $SCRIPT_DIR/../src/nu/validator/htmlparser/impl`
+
+if [ $# -eq 1 ]
+then
+ MOZ_PARSER_PATH=`abs $1`
+else
+ echo
+ echo "Usage: sh `basename $0` /path/to/mozilla-central/parser/html"
+ echo "Note that relative paths will work just fine."
+ echo
+ exit 1
+fi
+
+SRCTARGET=$MOZ_PARSER_PATH/javasrc
+
+rm -rf $SRCTARGET
+mkdir $SRCTARGET
+# Avoid copying the .svn directory:
+cp -rv $SRCDIR/*.java $SRCTARGET
diff --git a/components/htmlfive/java/htmlparser/mozilla-export-scripts/export-translator.sh b/components/htmlfive/java/htmlparser/mozilla-export-scripts/export-translator.sh
new file mode 100644
index 000000000..d1f4f1c39
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/mozilla-export-scripts/export-translator.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env sh
+
+SCRIPT_DIR=`dirname $0`
+source $SCRIPT_DIR/util.sh
+SCRIPT_DIR=`abs $SCRIPT_DIR`
+
+LIBDIR=`abs $SCRIPT_DIR/../translator-lib`
+
+if [ $# -eq 1 ]
+then
+ MOZ_PARSER_PATH=`abs $1`
+else
+ echo
+ echo "Usage: sh `basename $0` /path/to/mozilla-central/parser/html"
+ echo "Note that relative paths will work just fine."
+ echo "Be sure that you have run `dirname $0`/make-translator-jar.sh before running this script."
+ echo
+ exit 1
+fi
+
+LIBTARGET=$MOZ_PARSER_PATH/javalib
+
+rm -rf $LIBTARGET
+cp -rv $LIBDIR $LIBTARGET
diff --git a/components/htmlfive/java/htmlparser/mozilla-export-scripts/make-translator-jar.sh b/components/htmlfive/java/htmlparser/mozilla-export-scripts/make-translator-jar.sh
new file mode 100644
index 000000000..4f21ae665
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/mozilla-export-scripts/make-translator-jar.sh
@@ -0,0 +1,63 @@
+#!/usr/bin/env sh
+
+SCRIPT_DIR=`dirname $0`
+source $SCRIPT_DIR/util.sh
+SCRIPT_DIR=`abs $SCRIPT_DIR`
+
+SRCDIR=`abs $SCRIPT_DIR/../translator-src`
+BINDIR=`abs $SCRIPT_DIR/../translator-bin`
+LIBDIR=`abs $SCRIPT_DIR/../translator-lib`
+
+if [ $# -eq 1 ]
+then
+ JAVAPARSER_JAR_PATH=`abs $1`
+else
+ echo
+ echo "Usage: sh `basename $0` /path/to/javaparser-1.0.7.jar"
+ echo "Note that relative paths will work just fine."
+ echo "Obtain javaparser-1.0.7.jar from http://code.google.com/p/javaparser"
+ echo
+ exit 1
+fi
+
+set_up() {
+ rm -rf $BINDIR; mkdir $BINDIR
+ rm -rf $LIBDIR; mkdir $LIBDIR
+ cp $JAVAPARSER_JAR_PATH $LIBDIR/javaparser.jar
+}
+
+write_manifest() {
+ rm -f $LIBDIR/manifest
+ echo "Main-Class: nu.validator.htmlparser.cpptranslate.Main" > $LIBDIR/manifest
+ echo "Class-Path: javaparser.jar" >> $LIBDIR/manifest
+}
+
+compile_translator() {
+ find $SRCDIR -name "*.java" | \
+ xargs javac -cp $LIBDIR/javaparser.jar -g -d $BINDIR
+}
+
+generate_jar() {
+ jar cvfm $LIBDIR/translator.jar $LIBDIR/manifest -C $BINDIR .
+}
+
+clean_up() {
+ rm -f $LIBDIR/manifest
+}
+
+success_message() {
+ echo
+ echo "Successfully generated directory \"$LIBDIR\" with contents:"
+ echo
+ ls -al $LIBDIR
+ echo
+ echo "Now run `dirname $0`/export-all.sh with no arguments and follow the usage instructions."
+ echo
+}
+
+set_up && \
+ compile_translator && \
+ write_manifest && \
+ generate_jar && \
+ clean_up && \
+ success_message
diff --git a/components/htmlfive/java/htmlparser/mozilla-export-scripts/util.sh b/components/htmlfive/java/htmlparser/mozilla-export-scripts/util.sh
new file mode 100644
index 000000000..348ca14f9
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/mozilla-export-scripts/util.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env sh
+
+abs() {
+ local rel
+ local p
+ if [ $# -ne 1 ]
+ then
+ rel=.
+ else
+ rel=$1
+ fi
+ if [ -d $rel ]
+ then
+ pushd $rel > /dev/null
+ p=`pwd`
+ popd > /dev/null
+ else
+ pushd `dirname $rel` > /dev/null
+ p=`pwd`/`basename $rel`
+ popd > /dev/null
+ fi
+ echo $p
+}
diff --git a/components/htmlfive/java/htmlparser/pom.xml b/components/htmlfive/java/htmlparser/pom.xml
new file mode 100644
index 000000000..41f46725f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/pom.xml
@@ -0,0 +1,240 @@
+<!--
+ * Copyright (c) 2007-2012 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>nu.validator.htmlparser</groupId>
+ <artifactId>htmlparser</artifactId>
+ <packaging>bundle</packaging>
+ <version>1.4</version>
+ <name>htmlparser</name>
+ <url>http://about.validator.nu/htmlparser/</url>
+ <description>The Validator.nu HTML Parser is an implementation of the HTML5 parsing algorithm in Java for applications. The parser is designed to work as a drop-in replacement for the XML parser in applications that already support XHTML 1.x content with an XML parser and use SAX, DOM or XOM to interface with the parser.</description>
+ <!--
+ Usage notes for this POM:
+
+ To build without signing, run:
+ mvn clean source:jar javadoc:jar repository:bundle-create
+ (enter 0 <return> when prompted)
+
+ To build and sign, run:
+ mvn clean source:jar javadoc:jar package gpg:sign repository:bundle-create
+ (enter 0 <return> when prompted)
+
+ This POM file is used for creating the bundle for distribution via the
+ Maven Central Repository. It is not used as part of the normal development
+ process of the parser and the maintainer of the parser (Henri Sivonen)
+ isn't experienced in POM tweaking. If you need this POM to do something
+ that it currently does not do or do something better, you need to write
+ the changes you need yourself and contribute a patch via
+ http://bugzilla.validator.nu/
+ -->
+ <developers>
+ <developer>
+ <id>hsivonen</id>
+ <name>Henri Sivonen</name>
+ <email>hsivonen@iki.fi</email>
+ <url>http://hsivonen.iki.fi/</url>
+ </developer>
+ </developers>
+ <licenses>
+ <license>
+ <name>The MIT License</name>
+ <url>http://www.opensource.org/licenses/mit-license.php</url>
+ <distribution>repo</distribution>
+ </license>
+ <license>
+ <name>The (New) BSD License</name>
+ <url>http://www.opensource.org/licenses/bsd-license.php</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+ <scm>
+ <connection>scm:hg:http://hg.mozilla.org/projects/htmlparser/</connection>
+ <url>http://hg.mozilla.org/projects/htmlparser/</url>
+ </scm>
+ <build>
+ <sourceDirectory>${project.build.directory}/src</sourceDirectory>
+ <testSourceDirectory>${basedir}/test-src</testSourceDirectory>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <version>1.7</version>
+ <dependencies>
+ <dependency>
+ <groupId>com.sun</groupId>
+ <artifactId>tools</artifactId>
+ <version>1.5.0</version>
+ <scope>system</scope>
+ <systemPath>${java.home}/../lib/tools.jar</systemPath>
+ </dependency>
+ </dependencies>
+ <executions>
+ <execution>
+ <id>intitialize-sources</id>
+ <phase>initialize</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <target>
+ <delete dir="${project.build.sourceDirectory}"/>
+ <mkdir dir="${project.build.sourceDirectory}"/>
+ <copy todir="${project.build.sourceDirectory}">
+ <fileset dir="${basedir}/src"/>
+ </copy>
+ </target>
+ </configuration>
+ </execution>
+ <execution>
+ <id>tokenizer-hotspot-workaround</id>
+ <phase>process-sources</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <target>
+ <property name="translator.sources" value="${basedir}/translator-src"/>
+ <property name="translator.classes" value="${project.build.directory}/translator-classes"/>
+ <mkdir dir="${translator.classes}"/>
+ <javac srcdir="${translator.sources}" includes="nu/validator/htmlparser/generator/ApplyHotSpotWorkaround.java" destdir="${translator.classes}" includeantruntime="false"/>
+ <java classname="nu.validator.htmlparser.generator.ApplyHotSpotWorkaround">
+ <classpath>
+ <pathelement location="${translator.classes}"/>
+ </classpath>
+ <arg value="${project.build.sourceDirectory}/nu/validator/htmlparser/impl/Tokenizer.java"/>
+ <arg value="${project.build.sourceDirectory}/nu/validator/htmlparser/impl/HotSpotWorkaround.txt"/>
+ </java>
+ </target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.3.7</version>
+ <extensions>true</extensions>
+ <configuration>
+ <archive>
+ <addMavenDescriptor>false</addMavenDescriptor>
+ </archive>
+ <instructions>
+ <Bundle-Name>${project.name}</Bundle-Name>
+ <Bundle-SymbolicName>nu.validator.htmlparser</Bundle-SymbolicName>
+ <Bundle-Version>${project.version}</Bundle-Version>
+ <Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
+ <_removeheaders>Built-By,Bnd-LastModified</_removeheaders>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>rpm-maven-plugin</artifactId>
+ <configuration>
+ <release>1</release>
+ <copyright>The MIT License</copyright>
+ <group>Development/Java</group>
+ <workarea>/var/tmp/${project.build.finalName}</workarea>
+ <defineStatements>
+ <defineStatement>_javadir ${rpm.java.dir}</defineStatement>
+ <defineStatement>_javadocdir ${rpm.javadoc.dir}</defineStatement>
+ </defineStatements>
+ <mappings>
+ <mapping>
+ <directory>${rpm.java.dir}</directory>
+ <filemode>644</filemode>
+ <username>root</username>
+ <groupname>root</groupname>
+ <sources>
+ <source>
+ <location>${project.build.directory}/${project.build.finalName}.jar</location>
+ </source>
+ </sources>
+ </mapping>
+ <mapping>
+ <directory>${rpm.javadoc.dir}/${project.build.finalName}</directory>
+ <filemode>644</filemode>
+ <username>root</username>
+ <groupname>root</groupname>
+ <sources>
+ <source>
+ <location>${project.build.directory}/apidocs</location>
+ </source>
+ </sources>
+ </mapping>
+ </mappings>
+ <install>%__ln_s ${project.build.finalName}.jar %{buildroot}%{_javadir}/${project.name}.jar</install>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>com.ibm.icu</groupId>
+ <artifactId>icu4j</artifactId>
+ <version>4.0.1</version>
+ <scope>compile</scope>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>xom</groupId>
+ <artifactId>xom</artifactId>
+ <version>1.1</version>
+ <scope>compile</scope>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>net.sourceforge.jchardet</groupId>
+ <artifactId>jchardet</artifactId>
+ <version>1.0</version>
+ <scope>compile</scope>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>com.sdicons.jsontools</groupId>
+ <artifactId>jsontools-core</artifactId>
+ <version>1.4</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <properties>
+ <rpm.java.dir>/usr/share/java</rpm.java.dir>
+ <rpm.javadoc.dir>/usr/share/javadoc</rpm.javadoc.dir>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+</project>
diff --git a/components/htmlfive/java/htmlparser/ruby-gcj/DomUtils.java b/components/htmlfive/java/htmlparser/ruby-gcj/DomUtils.java
new file mode 100644
index 000000000..dc43da83d
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/ruby-gcj/DomUtils.java
@@ -0,0 +1,36 @@
+import java.util.HashSet;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+
+public class DomUtils {
+
+ private static HashSet<Document> pinned_list = new HashSet<Document>();
+
+ public static synchronized void pin(Document d) {
+ pinned_list.add(d);
+ }
+
+ public static synchronized void unpin(Document d) {
+ pinned_list.remove(d);
+ }
+
+ // return all the text content contained by a single element
+ public static void getElementContent(Element e, StringBuffer b) {
+ for (Node n = e.getFirstChild(); n!=null; n=n.getNextSibling()) {
+ if (n.getNodeType() == n.TEXT_NODE) {
+ b.append(n.getNodeValue());
+ } else if (n.getNodeType() == n.ELEMENT_NODE) {
+ getElementContent((Element) e, b);
+ }
+ }
+ }
+
+ // replace all child nodes of a given element with a single text element
+ public static void setElementContent(Element e, String s) {
+ while (e.hasChildNodes()) {
+ e.removeChild(e.getFirstChild());
+ }
+ e.appendChild(e.getOwnerDocument().createTextNode(s));
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/ruby-gcj/README b/components/htmlfive/java/htmlparser/ruby-gcj/README
new file mode 100644
index 000000000..b368437f7
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/ruby-gcj/README
@@ -0,0 +1,65 @@
+Disclaimer:
+
+ This code is experimental.
+
+ When some people say experimental, they mean "it may not do what it is
+ intended to do; in fact, it might even wipe out your hard drive". I mean
+ that too. But I mean something more than that.
+
+ In this case, experimental means that I don't even know what it is intended
+ to do. I just have a vague vision, and I am trying out various things in
+ the hopes that one of them will work out.
+
+Vision:
+
+ My vague vision is that I would like to see HTML 5 be a success. For me to
+ consider it to be a success, it needs to be a standard, be interoperable,
+ and be ubiquitous.
+
+ I believe that the Validator.nu parser can be used to bootstrap that
+ process. It is written in Java. Has been compiled into JavaScript. Has
+ been translated into C++ based on the Mozilla libraries with the intent of
+ being included in Firefox. It very closely tracks to the standard.
+
+ For the moment, the effort is on extending that to another language (Ruby)
+ on a single environment (i.e., Linux). Once that is complete, intent is to
+ evaluate the results, decide what needs to be changed, and what needs to be
+ done to support other languages and environments.
+
+ The bar I'm setting for myself isn't just another SWIG generated low level
+ interface to a DOM, but rather a best of breed interface; which for Ruby
+ seems to be the one pioneered by Hpricot and adopted by Nokogiri. Success
+ will mean passing all of the tests from one of those two parsers as well as
+ all of the HTML5 tests.
+
+Build instructions:
+
+ You'll need icu4j and chardet jars. If you checked out and ran dldeps you
+ are already all set:
+
+ svn co http://svn.versiondude.net/whattf/build/trunk/ build
+ python build/build.py checkout dldeps
+
+ Fedora 11:
+
+ yum install ruby-devel rubygem-rake java-1.5.0-gcj-devel gcc-c++
+
+ Ubuntu 9.04:
+
+ apt-get install ruby ruby1.8-dev rake gcj g++
+
+ Also at this time, you need to install a jdk (e.g. sun-java6-jdk), simply
+ because the javac that comes with gcj doesn't support -sourcepath, and
+ I haven't spent the time to find a replacement.
+
+ Finally, make sure that libjaxp1.3-java is *not* installed.
+
+ http://gcc.gnu.org/ml/java/2009-06/msg00055.html
+
+ If this is done, you should be all set.
+
+ cd htmlparser/ruby-gcj
+ rake test
+
+ If things are successful, the last lines of the output will list the
+ font attributes and values found in the test/google.html file.
diff --git a/components/htmlfive/java/htmlparser/ruby-gcj/Rakefile b/components/htmlfive/java/htmlparser/ruby-gcj/Rakefile
new file mode 100644
index 000000000..7b5180253
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/ruby-gcj/Rakefile
@@ -0,0 +1,77 @@
+deps = ENV['deps'] || '../../dependencies'
+icu4j = "#{deps}/icu4j-4_0.jar"
+chardet = "#{deps}/mozilla/intl/chardet/java/dist/lib/chardet.jar"
+libgcj = Dir['/usr/share/java/libgcj*.jar'].grep(/gcj[-\d.]*jar$/).sort.last
+
+task :default => %w(headers libs Makefile validator.so)
+
+# headers
+
+hdb = 'nu/validator/htmlparser/dom/HtmlDocumentBuilder'
+task :headers => %W(headers/DomUtils.h headers/#{hdb}.h)
+
+file 'headers/DomUtils.h' => 'DomUtils.java' do |t|
+ mkdir_p %w(classes headers), :verbose => false
+ sh "javac -d classes #{t.prerequisites.first}"
+ sh "gcjh -force -o #{t.name} -cp #{libgcj}:classes DomUtils"
+end
+
+file "headers/#{hdb}.h" => "../src/#{hdb}.java" do |t|
+ mkdir_p %w(classes headers), :verbose => false
+ sh "javac -cp #{icu4j}:#{chardet} -d classes -sourcepath ../src " +
+ t.prerequisites.first
+ sh "gcjh -force -cp classes -o #{t.name} -cp #{libgcj}:classes " +
+ hdb.gsub('/','.')
+end
+
+# libs
+
+task :libs => %w(htmlparser chardet icu).map {|name| "lib/libnu-#{name}.so"}
+
+htmlparser = Dir['../src/**/*.java'].reject {|name| name.include? '/xom/'}
+file 'lib/libnu-htmlparser.so' => htmlparser + ['DomUtils.java'] do |t|
+ mkdir_p 'lib', :verbose => false
+ sh "gcj -shared --classpath=#{icu4j}:#{chardet} -fPIC " +
+ "-o #{t.name} #{t.prerequisites.join(' ')}"
+end
+
+file 'lib/libnu-chardet.so' => chardet do |t|
+ mkdir_p 'lib', :verbose => false
+ sh "gcj -shared -fPIC -o #{t.name} #{t.prerequisites.join(' ')}"
+end
+
+file 'lib/libnu-icu.so' => icu4j do |t|
+ mkdir_p 'lib', :verbose => false
+ sh "gcj -shared -fPIC -o #{t.name} #{t.prerequisites.join(' ')}"
+end
+
+# module
+
+file 'Makefile' do
+ sh "ruby extconf.rb --with-gcj=#{libgcj}"
+end
+
+file 'validator.so' => %w(Makefile validator.cpp headers/DomUtils.h) do
+ system 'make'
+end
+
+file 'nu/validator.so' do
+ mkdir_p 'nu', :verbose => false
+ system 'ln -s -t nu ../validator.so'
+end
+
+# tasks
+
+task :test => [:default, 'nu/validator.so'] do
+ ENV['LD_LIBRARY_PATH']='lib'
+ sh 'ruby test/fonts.rb test/google.html'
+end
+
+task :clean do
+ rm_rf %W(classes lib nu mkmf.log headers/DomUtils.h headers/#{hdb}.h) +
+ Dir['*.o'] + Dir['*.so']
+end
+
+task :clobber => :clean do
+ rm_rf %w(headers Makefile)
+end
diff --git a/components/htmlfive/java/htmlparser/ruby-gcj/extconf.rb b/components/htmlfive/java/htmlparser/ruby-gcj/extconf.rb
new file mode 100644
index 000000000..415cf430a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/ruby-gcj/extconf.rb
@@ -0,0 +1,45 @@
+require 'mkmf'
+
+# system dependencies
+gcj = with_config('gcj', '/usr/share/java/libgcj.jar')
+
+# headers for JAXP
+CONFIG['CC'] = 'g++'
+with_cppflags('-xc++') do
+
+ unless find_header('org/w3c/dom/Document.h', 'headers')
+
+ `jar tf #{gcj}`.split.each do |file|
+ next unless file =~ /\.class$/
+ next unless file =~ /^(javax|org)\/(w3c|xml)/
+ next if file.include? '$'
+
+ dest = 'headers/' + file.sub(/\.class$/,'.h')
+ name = file.sub(/\.class$/,'').gsub('/','.')
+
+ next if File.exist? dest
+
+ cmd = "gcjh -cp #{gcj} -o #{dest} #{name}"
+ puts cmd
+ break unless system cmd
+ system "ruby -pi -e '$_.sub!(/namespace namespace$/," +
+ "\"namespace namespace$\")' #{dest}"
+ system "ruby -pi -e '$_.sub!(/::namespace::/," +
+ "\"::namespace$::\")' #{dest}"
+ end
+
+ exit unless find_header('org/w3c/dom/Document.h', 'headers')
+ end
+
+ find_header 'nu/validator/htmlparser/dom/HtmlDocumentBuilder.h', 'headers'
+end
+
+# Java libraries
+Config::CONFIG['CC'] = 'g++ -shared'
+dir_config('nu-htmlparser', nil, 'lib')
+have_library 'nu-htmlparser'
+have_library 'nu-icu'
+have_library 'nu-chardet'
+
+# Ruby library
+create_makefile 'nu/validator'
diff --git a/components/htmlfive/java/htmlparser/ruby-gcj/test/domencoding.rb b/components/htmlfive/java/htmlparser/ruby-gcj/test/domencoding.rb
new file mode 100644
index 000000000..1beb94c10
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/ruby-gcj/test/domencoding.rb
@@ -0,0 +1,5 @@
+require 'nu/validator'
+
+ARGV.each do |arg|
+ puts Nu::Validator::parse(open(arg)).root.name
+end
diff --git a/components/htmlfive/java/htmlparser/ruby-gcj/test/fonts.rb b/components/htmlfive/java/htmlparser/ruby-gcj/test/fonts.rb
new file mode 100644
index 000000000..595e3ae06
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/ruby-gcj/test/fonts.rb
@@ -0,0 +1,11 @@
+require 'nu/validator'
+require 'open-uri'
+
+ARGV.each do |arg|
+ doc = Nu::Validator::parse(open(arg))
+ doc.xpath("//*[local-name()='font']").each do |font|
+ font.attributes.each do |name, attr|
+ puts "#{name} => #{attr.value}"
+ end
+ end
+end
diff --git a/components/htmlfive/java/htmlparser/ruby-gcj/test/google.html b/components/htmlfive/java/htmlparser/ruby-gcj/test/google.html
new file mode 100644
index 000000000..8d2183b29
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/ruby-gcj/test/google.html
@@ -0,0 +1,10 @@
+<!doctype html><html><head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"><title>Google</title><script>window.google={kEI:"vLhASujeGpTU9QT2iOnWAQ",kEXPI:"17259",kCSIE:"17259",kHL:"en"};
+window.google.sn="webhp";window.google.timers={load:{t:{start:(new Date).getTime()}}};try{window.google.pt=window.gtbExternal&&window.gtbExternal.pageT()||window.external&&window.external.pageT}catch(b){}
+window.google.jsrt_kill=1;
+var _gjwl=location;function _gjuc(){var e=_gjwl.href.indexOf("#");if(e>=0){var a=_gjwl.href.substring(e);if(a.indexOf("&q=")>0||a.indexOf("#q=")>=0){a=a.substring(1);if(a.indexOf("#")==-1){for(var c=0;c<a.length;){var d=c;if(a.charAt(d)=="&")++d;var b=a.indexOf("&",d);if(b==-1)b=a.length;var f=a.substring(d,b);if(f.indexOf("fp=")==0){a=a.substring(0,c)+a.substring(b,a.length);b=c}else if(f=="cad=h")return 0;c=b}_gjwl.href="/search?"+a+"&cad=h";return 1}}}return 0}function _gjp(){!(window._gjwl.hash&&
+window._gjuc())&&setTimeout(_gjp,500)};
+window._gjp && _gjp();</script><style>td{line-height:.8em;}.gac_c{line-height:normal;}form{margin-bottom:20px;}body,td,a,p,.h{font-family:arial,sans-serif}.h{color:#36c;font-size:20px}.q{color:#00c}.ts td{padding:0}.ts{border-collapse:collapse}#gbar{height:22px;padding-left:0px}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}#guser{padding-bottom:7px !important;text-align:right}#gbar,#guser{font-size:13px;padding-top:1px !important}@media all{.gb1,.gb3{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb3{color:#00c !important}.gb3{text-decoration:none}</style><script>google.y={};google.x=function(e,g){google.y[e.id]=[e,g];return false};</script></head><body bgcolor=#ffffff text=#000000 link=#0000cc vlink=#551a8b alink=#ff0000 onload="document.f.q.focus();if(document.images)new Image().src='/images/nav_logo4.png'" topmargin=3 marginheight=3><textarea id=csi style=display:none></textarea><iframe name=wgjf style="display:none"></iframe><div id=gbar><nobr><b class=gb1>Web</b> <a href="http://images.google.com/imghp?hl=en&tab=wi" class=gb1>Images</a> <a href="http://video.google.com/?hl=en&tab=wv" class=gb1>Video</a> <a href="http://maps.google.com/maps?hl=en&tab=wl" class=gb1>Maps</a> <a href="http://news.google.com/nwshp?hl=en&tab=wn" class=gb1>News</a> <a href="http://www.google.com/prdhp?hl=en&tab=wf" class=gb1>Shopping</a> <a href="http://mail.google.com/mail/?hl=en&tab=wm" class=gb1>Gmail</a> <a href="http://www.google.com/intl/en/options/" class=gb3><u>more</u> &raquo;</a></nobr></div><div id=guser width=100%><nobr><a href="/url?sa=p&pref=ig&pval=3&q=http://www.google.com/ig%3Fhl%3Den%26source%3Diglk&usg=AFQjCNFA18XPfgb7dKnXfKz7x7g1GDH1tg">iGoogle</a> | <a href="https://www.google.com/accounts/Login?hl=en&continue=http://www.google.com/">Sign in</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div><center><br clear=all id=lgpd><img alt="Google" height=110 src="/intl/en_ALL/images/logo.gif" width=276 id=logo onload="window.lol&&lol()"><br><br><form action="/search" name=f><table cellpadding=0 cellspacing=0><tr valign=top><td width=25%>&nbsp;</td><td align=center nowrap><input name=hl type=hidden value=en><input type=hidden name=ie value="ISO-8859-1"><input autocomplete="off" maxlength=2048 name=q size=55 title="Google Search" value=""><br><input name=btnG type=submit value="Google Search"><input name=btnI type=submit value="I'm Feeling Lucky"></td><td nowrap width=25% align=left><font size=-2>&nbsp;&nbsp;<a href=/advanced_search?hl=en>Advanced Search</a><br>&nbsp;&nbsp;<a href=/preferences?hl=en>Preferences</a><br>&nbsp;&nbsp;<a href=/language_tools?hl=en>Language Tools</a></font></td></tr></table></form><br><font size=-1><a href="/aclk?sa=L&ai=CqVchLbNASrv7IZa68gS13KTwAc3__IMB29PoogzB2ZzZExABIMFUUK_O0JX______wFgyQaqBAlP0BcDOBRYhqw&num=1&sig=AGiWqty21CD7ixNXZILwCnH7c_3n9v2-tg&q=http://www.allforgood.org#source=hpp">Find an opportunity to volunteer</a> in your community today.</font><br><br><br><font size=-1><a href="/intl/en/ads/">Advertising&nbsp;Programs</a> - <a href="/services/">Business Solutions</a> - <a href="/intl/en/about.html">About Google</a></font><p><font size=-2>&copy;2009 - <a href="/intl/en/privacy.html">Privacy</a></font></p></center><div id=xjsd></div><div id=xjsi><script>if(google.y)google.y.first=[];if(google.y)google.y.first=[];google.dstr=[];google.rein=[];window.setTimeout(function(){var a=document.createElement("script");a.src="/extern_js/f/CgJlbhICdXMgACswCjggQAgsKzAOOAUsKzAYOAQsKzAlOMmIASwrMCY4BCwrMCc4ACw/1t0T7hspHT4.js";(document.getElementById("xjsd")||document.body).appendChild(a)},0);
+;google.y.first.push(function(){google.ac.i(document.f,document.f.q,'','')});google.xjs&&google.j&&google.j.xi&&google.j.xi()</script></div><script>(function(){
+function a(){google.timers.load.t.ol=(new Date).getTime();google.report&&google.report(google.timers.load,{ei:google.kEI,e:google.kCSIE})}if(window.addEventListener)window.addEventListener("load",a,false);else if(window.attachEvent)window.attachEvent("onload",a);google.timers.load.t.prt=(new Date).getTime();
+})();
+</script> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/ruby-gcj/test/greek.xml b/components/htmlfive/java/htmlparser/ruby-gcj/test/greek.xml
new file mode 100644
index 000000000..a14d23eb1
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/ruby-gcj/test/greek.xml
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='iso-8859-7'?>
+<root/>
diff --git a/components/htmlfive/java/htmlparser/ruby-gcj/validator.cpp b/components/htmlfive/java/htmlparser/ruby-gcj/validator.cpp
new file mode 100644
index 000000000..aadd24abe
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/ruby-gcj/validator.cpp
@@ -0,0 +1,210 @@
+#include <gcj/cni.h>
+
+#include <java/io/ByteArrayInputStream.h>
+#include <java/lang/System.h>
+#include <java/lang/Throwable.h>
+#include <java/util/ArrayList.h>
+#include <javax/xml/xpath/XPath.h>
+#include <javax/xml/xpath/XPathFactory.h>
+#include <javax/xml/xpath/XPathExpression.h>
+#include <javax/xml/xpath/XPathConstants.h>
+#include <javax/xml/parsers/DocumentBuilderFactory.h>
+#include <javax/xml/parsers/DocumentBuilder.h>
+#include <org/w3c/dom/Attr.h>
+#include <org/w3c/dom/Document.h>
+#include <org/w3c/dom/Element.h>
+#include <org/w3c/dom/NodeList.h>
+#include <org/w3c/dom/NamedNodeMap.h>
+#include <org/xml/sax/InputSource.h>
+
+#include "nu/validator/htmlparser/dom/HtmlDocumentBuilder.h"
+
+#include "DomUtils.h"
+
+#include "ruby.h"
+
+using namespace java::io;
+using namespace java::lang;
+using namespace java::util;
+using namespace javax::xml::parsers;
+using namespace javax::xml::xpath;
+using namespace nu::validator::htmlparser::dom;
+using namespace org::w3c::dom;
+using namespace org::xml::sax;
+
+static VALUE jaxp_Document;
+static VALUE jaxp_Attr;
+static VALUE jaxp_Element;
+static ID ID_read;
+static ID ID_doc;
+static ID ID_element;
+
+// convert a Java string into a Ruby string
+static VALUE j2r(String *string) {
+ if (string == NULL) return Qnil;
+ jint len = JvGetStringUTFLength(string);
+ char buf[len];
+ JvGetStringUTFRegion(string, 0, len, buf);
+ return rb_str_new(buf, len);
+}
+
+// convert a Ruby string into a Java string
+static String *r2j(VALUE string) {
+ return JvNewStringUTF(RSTRING(string)->ptr);
+}
+
+// release the Java Document associated with this Ruby Document
+static void vnu_document_free(Document *doc) {
+ DomUtils::unpin(doc);
+}
+
+// Nu::Validator::parse( string|file )
+static VALUE vnu_parse(VALUE self, VALUE input) {
+ HtmlDocumentBuilder *parser = new HtmlDocumentBuilder();
+
+ // read file-like objects into memory. TODO: buffer such objects
+ if (rb_respond_to(input, ID_read))
+ input = rb_funcall(input, ID_read, 0);
+
+ // convert input in to a ByteArrayInputStream
+ jbyteArray bytes = JvNewByteArray(RSTRING(input)->len);
+ memcpy(elements(bytes), RSTRING(input)->ptr, RSTRING(input)->len);
+ InputSource *source = new InputSource(new ByteArrayInputStream(bytes));
+
+ // parse, pin, and wrap
+ Document *doc = parser->parse(source);
+ DomUtils::pin(doc);
+ return Data_Wrap_Struct(jaxp_Document, NULL, vnu_document_free, doc);
+}
+
+// Jaxp::parse( string|file )
+static VALUE jaxp_parse(VALUE self, VALUE input) {
+ DocumentBuilderFactory *factory = DocumentBuilderFactory::newInstance();
+ DocumentBuilder *parser = factory->newDocumentBuilder();
+
+ // read file-like objects into memory. TODO: buffer such objects
+ if (rb_respond_to(input, ID_read))
+ input = rb_funcall(input, ID_read, 0);
+
+ try {
+ jbyteArray bytes = JvNewByteArray(RSTRING(input)->len);
+ memcpy(elements(bytes), RSTRING(input)->ptr, RSTRING(input)->len);
+ Document *doc = parser->parse(new ByteArrayInputStream(bytes));
+ DomUtils::pin(doc);
+ return Data_Wrap_Struct(jaxp_Document, NULL, vnu_document_free, doc);
+ } catch (java::lang::Throwable *ex) {
+ ex->printStackTrace();
+ return Qnil;
+ }
+}
+
+
+// Nu::Validator::Document#encoding
+static VALUE jaxp_document_encoding(VALUE rdoc) {
+ Document *jdoc;
+ Data_Get_Struct(rdoc, Document, jdoc);
+ return j2r(jdoc->getXmlEncoding());
+}
+
+// Nu::Validator::Document#root
+static VALUE jaxp_document_root(VALUE rdoc) {
+ Document *jdoc;
+ Data_Get_Struct(rdoc, Document, jdoc);
+
+ Element *jelement = jdoc->getDocumentElement();
+ if (jelement==NULL) return Qnil;
+
+ VALUE relement = Data_Wrap_Struct(jaxp_Element, NULL, NULL, jelement);
+ rb_ivar_set(relement, ID_doc, rdoc);
+ return relement;
+}
+
+// Nu::Validator::Document#xpath
+static VALUE jaxp_document_xpath(VALUE rdoc, VALUE path) {
+ Document *jdoc;
+ Data_Get_Struct(rdoc, Document, jdoc);
+
+ Element *jelement = jdoc->getDocumentElement();
+ if (jelement==NULL) return Qnil;
+
+ XPath *xpath = XPathFactory::newInstance()->newXPath();
+ XPathExpression *expr = xpath->compile(r2j(path));
+ NodeList *list = (NodeList*) expr->evaluate(jdoc, XPathConstants::NODESET);
+
+ VALUE result = rb_ary_new();
+ for (int i=0; i<list->getLength(); i++) {
+ VALUE relement = Data_Wrap_Struct(jaxp_Element, NULL, NULL, list->item(i));
+ rb_ivar_set(relement, ID_doc, rdoc);
+ rb_ary_push(result, relement);
+ }
+ return result;
+}
+
+// Nu::Validator::Element#name
+static VALUE jaxp_element_name(VALUE relement) {
+ Element *jelement;
+ Data_Get_Struct(relement, Element, jelement);
+ return j2r(jelement->getNodeName());
+}
+
+// Nu::Validator::Element#attributes
+static VALUE jaxp_element_attributes(VALUE relement) {
+ Element *jelement;
+ Data_Get_Struct(relement, Element, jelement);
+ VALUE result = rb_hash_new();
+ NamedNodeMap *map = jelement->getAttributes();
+ for (int i=0; i<map->getLength(); i++) {
+ Attr *jattr = (Attr *) map->item(i);
+ VALUE rattr = Data_Wrap_Struct(jaxp_Attr, NULL, NULL, jattr);
+ rb_ivar_set(rattr, ID_element, relement);
+ rb_hash_aset(result, j2r(jattr->getName()), rattr);
+ }
+ return result;
+}
+
+// Nu::Validator::Attribute#value
+static VALUE jaxp_attribute_value(VALUE rattribute) {
+ Attr *jattribute;
+ Data_Get_Struct(rattribute, Attr, jattribute);
+ return j2r(jattribute->getValue());
+}
+
+typedef VALUE (ruby_method)(...);
+
+// Nu::Validator module initialization
+extern "C" void Init_validator() {
+ JvCreateJavaVM(NULL);
+ JvAttachCurrentThread(NULL, NULL);
+ JvInitClass(&DomUtils::class$);
+ JvInitClass(&XPathFactory::class$);
+ JvInitClass(&XPathConstants::class$);
+
+ VALUE jaxp = rb_define_module("Jaxp");
+ rb_define_singleton_method(jaxp, "parse", (ruby_method*)&jaxp_parse, 1);
+
+ VALUE nu = rb_define_module("Nu");
+ VALUE validator = rb_define_module_under(nu, "Validator");
+ rb_define_singleton_method(validator, "parse", (ruby_method*)&vnu_parse, 1);
+
+ jaxp_Document = rb_define_class_under(jaxp, "Document", rb_cObject);
+ rb_define_method(jaxp_Document, "encoding",
+ (ruby_method*)&jaxp_document_encoding, 0);
+ rb_define_method(jaxp_Document, "root",
+ (ruby_method*)&jaxp_document_root, 0);
+ rb_define_method(jaxp_Document, "xpath",
+ (ruby_method*)&jaxp_document_xpath, 1);
+
+ jaxp_Element = rb_define_class_under(jaxp, "Element", rb_cObject);
+ rb_define_method(jaxp_Element, "name",
+ (ruby_method*)&jaxp_element_name, 0);
+ rb_define_method(jaxp_Element, "attributes",
+ (ruby_method*)&jaxp_element_attributes, 0);
+
+ jaxp_Attr = rb_define_class_under(jaxp, "Attr", rb_cObject);
+ rb_define_method(jaxp_Attr, "value",
+ (ruby_method*)&jaxp_attribute_value, 0);
+
+ ID_read = rb_intern("read");
+ ID_doc = rb_intern("@doc");
+ ID_element = rb_intern("@element");
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5.java
new file mode 100644
index 000000000..00e5f7ca7
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class Big5 extends Encoding {
+
+ private static final String[] LABELS = {
+ "big5",
+ "big5-hkscs",
+ "cn-big5",
+ "csbig5",
+ "x-x-big5"
+ };
+
+ private static final String NAME = "big5";
+
+ static final Big5 INSTANCE = new Big5();
+
+ private Big5() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new Big5Decoder(this);
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return new Big5Encoder(this);
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Data.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Data.java
new file mode 100644
index 000000000..9f35be341
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Data.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+final class Big5Data {
+
+ private static final String ASTRALNESS = "\uF829\u7A22\u1290\uC5C4\u0007\u0200\u7549\"\u0000\uA000\u3859\u0300\u002C\u573E\uF72B\u6EFC\u90F2\u3B7B\u83E9\uF049\u9DA6\uBBFC\uBEF7\uFDFE\u0C83\uABD1\u7BFF\u7FBF\u1804\u002C\u4840\u2046\u0408\u2A22\u4858\u091A\u5100\u3122\uC000\u5000\uC00D\u6110\uD44C\u9A24\u0180\u0004\u92B2\u0209\u8631\u1242\u8140\u0351\uAB48\u7460\uD5A2\u3E5C\uE361\u1083\u720B\u08A0\u51D6\uE00A\u8100\u1686\uC443\u1135\u6037\u7AE6\u056D\u7D0C\u0E66\u81E0\u7F88\u2420\u2406\u1D03\u340C\u4268\u454A\uF13F\u080D\u8084\uBB00\u0C4D\u6ED6\u97D7\u41DF\u5D3E\uDA68\u305C\uB800\u26E9\u80BC\u0151\uE078\u89A1\u59C0\u9679\u3BCC\u5EDE\uBC2C\uDF9B\u6C5D\u046D\u6043\u4A36\uD860\u073E\uC8C4\u6C69\uD8B1\u8302\u0F88\u0973\u806E\u3B6B\u5A17\uA503\u2D52\u3F40\u1120\u4101\u5024\uB903\u90EE\u1079\u5CAD\u1820\uDA0A\u8060\u9E26\u6E73\u1021\u080E\u4368\u6FB2\u161F\u8AFE\u76B6\u763A\u8262\u1894\u1801\uFE7D\u578D\u1327\u5BD2\u1937\uDB8C\u4862\u0024\u0000\u0010\u8000\u0000\u0000\u0038\u3800\uB9E2\uFD7D\u75F8\uDCF7\u6FF3\uBBF2\uFF4A\uAE3F\u9FC5\uEAFF\uBABA\uBC5D\u9F73\uD8FA\uDED6\u4B25\u975E\u2ADA\u6DB9\u06E6\u9D36\u53F9\u6FC5\uF98A\u49BF\uDB5D\uFFF8\u14A6\uE605\u96F7\u0A99\u00E5\u0800\u3D81\u5002\u0102\uBF49\u475E\u036F\u6280\uEECA\u4819\u6081\u205A\u24F7\u0000\u0004\u0000\u2804\u22C8\u0200\u0000\u2010\u5082\u3040\u0001\u0010\u1284\u0041\u0504\u2000\uC100\u3F7F\uB059\u8AC1\uAFAF\uAC05\u033F\u0204\u7280\u420A\u0426\u02D0\u0EC3\u0958\u0A80\u20B5\u9206\u8B77\u0560\u21C9\u4606\u6038\uC048\u24B4\u84DE\uC0E0\u3364\u3154\u300D\u688A\u5F2B\u0626\u8496\uB108\uE890\uA394\u734F\u50B8\u0D11\uDFA4\u4003\u5D20\u8480\u6160\u51CE\u800A\u58B7\u0050\uE862\u6750\u7220\u1228";
+
+ private static final String TABLE0 = "\u43F0\u4C32\u4603\u45A6\u4578\u7267\u4D77\u45B3\u7CB1\u4CE2\u7CC5\u3B95\u4736\u4744\u4C47\u4C40\u42BF\u3617\u7352\u6E8B\u70D2\u4C57\uA351\u474F\u45DA\u4C85\u7C6C\u4D07\u4AA4\u46A1\u6B23\u7225\u5A54\u1A63\u3E06\u3F61\u664D\u56FB\u0000\u7D95\u591D\u8BB9\u3DF4\u9734\u7BEF\u5BDB\u1D5E\u5AA4\u3625\u9EB0\u5AD1\u5BB7\u5CFC\u676E\u8593\u9945\u7461\u749D\u3875\u1D53\u369E\u6021\u3EEC\u58DE\u3AF5\u7AFC\u9F97\u4161\u890D\u31EA\u0A8A\u325E\u430A\u8484\u9F96\u942F\u4930\u8613\u5896\u974A\u9218\u79D0\u7A32\u6660\u6A29\u889D\u744C\u7BC5\u6782\u7A2C\u524F\u9046\u34E6\u73C4\u5DB9\u74C6\u9FC7\u57B3\u492F\u544C\u4131\u368E\u5818\u7A72\u7B65\u8B8F\u46AE\u6E88\u4181\u5D99\u7BAE\u24BC\u9FC8\u24C1\u24C9\u24CC\u9FC9\u8504\u35BB\u40B4\u9FCA\u44E1\uADFF\u62C1\u706E\u9FCB";
+
+ private static final String TABLE1 = "\u31C0\u31C1\u31C2\u31C3\u31C4\u010C\u31C5\u00D1\u00CD\u31C6\u31C7\u00CB\u1FE8\u31C8\u00CA\u31C9\u31CA\u31CB\u31CC\u010E\u31CD\u31CE\u0100\u00C1\u01CD\u00C0\u0112\u00C9\u011A\u00C8\u014C\u00D3\u01D1\u00D2\u0000\u1EBE\u0000\u1EC0\u00CA\u0101\u00E1\u01CE\u00E0\u0251\u0113\u00E9\u011B\u00E8\u012B\u00ED\u01D0\u00EC\u014D\u00F3\u01D2\u00F2\u016B\u00FA\u01D4\u00F9\u01D6\u01D8\u01DA\u01DC\u00FC\u0000\u1EBF\u0000\u1EC1\u00EA\u0261\u23DA\u23DB";
+
+ private static final String TABLE2 = "\uA3A9\u1145\u0000\u650A\u0000\u0000\u4E3D\u6EDD\u9D4E\u91DF\u0000\u0000\u7735\u6491\u4F1A\u4F28\u4FA8\u5156\u5174\u519C\u51E4\u52A1\u52A8\u533B\u534E\u53D1\u53D8\u56E2\u58F0\u5904\u5907\u5932\u5934\u5B66\u5B9E\u5B9F\u5C9A\u5E86\u603B\u6589\u67FE\u6804\u6865\u6D4E\u70BC\u7535\u7EA4\u7EAC\u7EBA\u7EC7\u7ECF\u7EDF\u7F06\u7F37\u827A\u82CF\u836F\u89C6\u8BBE\u8BE2\u8F66\u8F67\u8F6E\u7411\u7CFC\u7DCD\u6946\u7AC9\u5227\u0000\u0000\u0000\u0000\u918C\u78B8\u915E\u80BC\u0000\u8D0B\u80F6\u09E7\u0000\u0000\u809F\u9EC7\u4CCD\u9DC9\u9E0C\u4C3E\u9DF6\u700E\u9E0A\uA133\u35C1\u0000\u6E9A\u823E\u7519\u0000\u4911\u9A6C\u9A8F\u9F99\u7987\u846C\u1DCA\u05D0\u2AE6\u4E24\u4E81\u4E80\u4E87\u4EBF\u4EEB\u4F37\u344C\u4FBD\u3E48\u5003\u5088\u347D\u3493\u34A5\u5186\u5905\u51DB\u51FC\u5205\u4E89\u5279\u5290\u5327\u35C7\u53A9\u3551\u53B0\u3553\u53C2\u5423\u356D\u3572\u3681\u5493\u54A3\u54B4\u54B9\u54D0\u54EF\u5518\u5523\u5528\u3598\u553F\u35A5\u35BF\u55D7\u35C5\u7D84\u5525\u0000\u0C42\u0D15\u512B\u5590\u2CC6\u39EC\u0341\u8E46\u4DB8\u94E5\u4053\u80BE\u777A\u2C38\u3A34\u47D5\u815D\u69F2\u4DEA\u64DD\u0D7C\u0FB4\u0CD5\u10F4\u648D\u8E7E\u0E96\u0C0B\u0F64\u2CA9\u8256\u44D3\u0000\u0D46\u9A4D\u80E9\u47F4\u4EA7\u2CC2\u9AB2\u3A67\u95F4\u3FED\u3506\u52C7\u97D4\u78C8\u2D44\u9D6E\u9815\u0000\u43D9\u60A5\u64B4\u54E3\u2D4C\u2BCA\u1077\u39FB\u106F\u66DA\u6716\u79A0\u64EA\u5052\u0C43\u8E68\u21A1\u8B4C\u0731\u0000\u480B\u01A9\u3FFA\u5873\u2D8D\u0000\u45C8\u04FC\u6097\u0F4C\u0D96\u5579\u40BB\u43BA\u0000\u4AB4\u2A66\u109D\u81AA\u98F5\u0D9C\u6379\u39FE\u2775\u8DC0\u56A1\u647C\u3E43\u0000\uA601\u0E09\u2ACF\u2CC9\u0000\u10C8\u39C2\u3992\u3A06\u829B\u3578\u5E49\u20C7\u5652\u0F31\u2CB2\u9720\u34BC\u6C3D\u4E3B\u0000\u0000\u7574\u2E8B\u2208\uA65B\u8CCD\u0E7A\u0C34\u681C\u7F93\u10CF\u2803\u2939\u35FB\u51E3\u0E8C\u0F8D\u0EAA\u3F93\u0F30\u0D47\u114F\u0E4C\u0000\u0EAB\u0BA9\u0D48\u10C0\u113D\u3FF9\u2696\u6432\u0FAD\u33F4\u7639\u2BCE\u0D7E\u0D7F\u2C51\u2C55\u3A18\u0E98\u10C7\u0F2E\uA632\u6B50\u8CD2\u8D99\u8CCA\u95AA\u54CC\u82C4\u55B9\u0000\u9EC3\u9C26\u9AB6\u775E\u2DEE\u7140\u816D\u80EC\u5C1C\u6572\u8134\u3797\u535F\u80BD\u91B6\u0EFA\u0E0F\u0E77\u0EFB\u35DD\u4DEB\u3609\u0CD6\u56AF\u27B5\u10C9\u0E10\u0E78\u1078\u1148\u8207\u1455\u0E79\u4E50\u2DA4\u5A54\u101D\u101E\u10F5\u10F6\u579C\u0E11\u7694\u82CD\u0FB5\u0E7B\u517E\u3703\u0FB6\u1180\u52D8\uA2BD\u49DA\u183A\u4177\u827C\u5899\u5268\u361A\u573D\u7BB2\u5B68\u4800\u4B2C\u9F27\u49E7\u9C1F\u9B8D\u5B74\u313D\u55FB\u35F2\u5689\u4E28\u5902\u1BC1\uF878\u9751\u0086\u4E5B\u4EBB\u353E\u5C23\u5F51\u5FC4\u38FA\u624C\u6535\u6B7A\u6C35\u6C3A\u706C\u722B\u4E2C\u72AD\u48E9\u7F52\u793B\u7CF9\u7F53\u626A\u34C1\u0000\u634B\u8002\u8080\u6612\u6951\u535D\u8864\u89C1\u78B2\u8BA0\u8D1D\u9485\u9578\u957F\u95E8\u8E0F\u97E6\u9875\u98CE\u98DE\u9963\u9810\u9C7C\u9E1F\u9EC4\u6B6F\uF907\u4E37\u0087\u961D\u6237\u94A2\u0000\u503B\u6DFE\u9C73\u9FA6\u3DC9\u888F\u414E\u7077\u5CF5\u4B20\u51CD\u3559\u5D30\u6122\u8A32\u8FA7\u91F6\u7191\u6719\u73BA\u3281\uA107\u3C8B\u1980\u4B10\u78E4\u7402\u51AE\u870F\u4009\u6A63\uA2BA\u4223\u860F\u0A6F\u7A2A\u9947\u8AEA\u9755\u704D\u5324\u207E\u93F4\u76D9\u89E3\u9FA7\u77DD\u4EA3\u4FF0\u50BC\u4E2F\u4F17\u9FA8\u5434\u7D8B\u5892\u58D0\u1DB6\u5E92\u5E99\u5FC2\u2712\u658B\u33F9\u6919\u6A43\u3C63\u6CFF\u0000\u7200\u4505\u738C\u3EDB\u4A13\u5B15\u74B9\u8B83\u5CA4\u5695\u7A93\u7BEC\u7CC3\u7E6C\u82F8\u8597\u9FA9\u8890\u9FAA\u8EB9\u9FAB\u8FCF\u855F\u99E0\u9221\u9FAC\u8DB9\u143F\u4071\u42A2\u5A1A\u0000\u0000\u0000\u9868\u676B\u4276\u573D\u0000\u85D6\u497B\u82BF\u710D\u4C81\u6D74\u5D7B\u6B15\u6FBE\u9FAD\u9FAE\u5B96\u9FAF\u66E7\u7E5B\u6E57\u79CA\u3D88\u44C3\u3256\u2796\u439A\u4536\u0000\u5CD5\u3B1A\u8AF9\u5C78\u3D12\u3551\u5D78\u9FB2\u7157\u4558\u40EC\u1E23\u4C77\u3978\u344A\u01A4\u6C41\u8ACC\u4FB4\u0239\u59BF\u816C\u9856\u98FA\u5F3B\u0B9F\u0000\u21C1\u896D\u4102\u46BB\u9079\u3F07\u9FB3\uA1B5\u40F8\u37D6\u46F7\u6C46\u417C\u86B2\u73FF\u456D\u38D4\u549A\u4561\u451B\u4D89\u4C7B\u4D76\u45EA\u3FC8\u4B0F\u3661\u44DE\u44BD\u41ED\u5D3E\u5D48\u5D56\u3DFC\u380F\u5DA4\u5DB9\u3820\u3838\u5E42\u5EBD\u5F25\u5F83\u3908\u3914\u393F\u394D\u60D7\u613D\u5CE5\u3989\u61B7\u61B9\u61CF\u39B8\u622C\u6290\u62E5\u6318\u39F8\u56B1\u3A03\u63E2\u63FB\u6407\u645A\u3A4B\u64C0\u5D15\u5621\u9F9F\u3A97\u6586\u3ABD\u65FF\u6653\u3AF2\u6692\u3B22\u6716\u3B42\u67A4\u6800\u3B58\u684A\u6884\u3B72\u3B71\u3B7B\u6909\u6943\u725C\u6964\u699F\u6985\u3BBC\u69D6\u3BDD\u6A65\u6A74\u6A71\u6A82\u3BEC\u6A99\u3BF2\u6AAB\u6AB5\u6AD4\u6AF6\u6B81\u6BC1\u6BEA\u6C75\u6CAA\u3CCB\u6D02\u6D06\u6D26\u6D81\u3CEF\u6DA4\u6DB1\u6E15\u6E18\u6E29\u6E86\u89C0\u6EBB\u6EE2\u6EDA\u9F7F\u6EE8\u6EE9\u6F24\u6F34\u3D46\u3F41\u6F81\u6FBE\u3D6A\u3D75\u71B7\u5C99\u3D8A\u702C\u3D91\u7050\u7054\u706F\u707F\u7089\u0325\u43C1\u35F1\u0ED8\u3ED7\u57BE\u6ED3\u713E\u57E0\u364E\u69A2\u8BE9\u5B74\u7A49\u58E1\u94D9\u7A65\u7A7D\u59AC\u7ABB\u7AB0\u7AC2\u7AC3\u71D1\u648D\u41CA\u7ADA\u7ADD\u7AEA\u41EF\u54B2\u5C01\u7B0B\u7B55\u7B29\u530E\u5CFE\u7BA2\u7B6F\u839C\u5BB4\u6C7F\u7BD0\u8421\u7B92\u7BB8\u5D20\u3DAD\u5C65\u8492\u7BFA\u7C06\u7C35\u5CC1\u7C44\u7C83\u4882\u7CA6\u667D\u4578\u7CC9\u7CC7\u7CE6\u7C74\u7CF3\u7CF5\u7CCE\u7E67\u451D\u6E44\u7D5D\u6ED6\u748D\u7D89\u7DAB\u7135\u7DB3\u7DD2\u4057\u6029\u7DE4\u3D13\u7DF5\u17F9\u7DE5\u836D\u7E1D\u6121\u615A\u7E6E\u7E92\u432B\u946C\u7E27\u7F40\u7F41\u7F47\u7936\u62D0\u99E1\u7F97\u6351\u7FA3\u1661\u0068\u455C\u3766\u4503\u833A\u7FFA\u6489\u8005\u8008\u801D\u8028\u802F\uA087\u6CC3\u803B\u803C\u8061\u2714\u4989\u6626\u3DE3\u66E8\u6725\u80A7\u8A48\u8107\u811A\u58B0\u26F6\u6C7F\u6498\u4FB8\u64E7\u148A\u8218\u185E\u6A53\u4A65\u4A95\u447A\u8229\u0B0D\u6A52\u3D7E\u4FF9\u14FD\u84E2\u8362\u6B0A\u49A7\u3530\u1773\u3DF8\u82AA\u691B\uF994\u41DB\u854B\u82D0\u831A\u0E16\u17B4\u36C1\u317D\u355A\u827B\u82E2\u8318\u3E8B\u6DA3\u6B05\u6B97\u35CE\u3DBF\u831D\u55EC\u8385\u450B\u6DA5\u83AC\u83C1\u83D3\u347E\u6ED4\u6A57\u855A\u3496\u6E42\u2EEF\u8458\u5BE4\u8471\u3DD3\u44E4\u6AA7\u844A\u3CB5\u7958\u84A8\u6B96\u6E77\u6E43\u84DE\u840F\u8391\u44A0\u8493\u84E4\u5C91\u4240\u5CC0\u4543\u8534\u5AF2\u6E99\u4527\u8573\u4516\u67BF\u8616\u8625\u863B\u85C1\u7088\u8602\u1582\u70CD\uF9B2\u456A\u8628\u3648\u18A2\u53F7\u739A\u867E\u8771\uA0F8\u87EE\u2C27\u87B1\u87DA\u880F\u5661\u866C\u6856\u460F\u8845\u8846\u75E0\u3DB9\u75E4\u885E\u889C\u465B\u88B4\u88B5\u63C1\u88C5\u7777\u770F\u8987\u898A\u89A6\u89A9\u89A7\u89BC\u8A25\u89E7\u7924\u7ABD\u8A9C\u7793\u91FE\u8A90\u7A59\u7AE9\u7B3A\u3F8F\u4713\u7B38\u717C\u8B0C\u8B1F\u5430\u5565\u8B3F\u8B4C\u8B4D\u8AA9\u4A7A\u8B90\u8B9B\u8AAF\u16DF\u4615\u884F\u8C9B\u7D54\u7D8F\uF9D4\u3725\u7D53\u8CD6\u7D98\u7DBD\u8D12\u8D03\u1910\u8CDB\u705C\u8D11\u4CC9\u3ED0\u8D77\u8DA9\u8002\u1014\u498A\u3B7C\u81BC\u710C\u7AE7\u8EAD\u8EB6\u8EC3\u92D4\u8F19\u8F2D\u8365\u8412\u8FA5\u9303\uA29F\u0A50\u8FB3\u492A\u89DE\u853D\u3DBB\u5EF8\u3262\u8FF9\uA014\u86BC\u8501\u2325\u3980\u6ED7\u9037\u853C\u7ABE\u9061\u856C\u860B\u90A8\u8713\u90C4\u86E6\u90AE\u90FD\u9167\u3AF0\u91A9\u91C4\u7CAC\u8933\u1E89\u920E\u6C9F\u9241\u9262\u55B9\u92B9\u8AC6\u3C9B\u8B0C\u55DB\u0D31\u932C\u936B\u8AE1\u8BEB\u708F\u5AC3\u8AE2\u8AE5\u4965\u9244\u8BEC\u8C39\u8BFF\u9373\u945B\u8EBC\u9585\u95A6\u9426\u95A0\u6FF6\u42B9\u267A\u86D8\u127C\u3E2E\u49DF\u6C1C\u967B\u9696\u416C\u96A3\u6ED5\u61DA\u96B6\u78F5\u8AE0\u96BD\u53CC\u49A1\u6CB8\u0274\u6410\u90AF\u90E5\u4AD1\u1915\u330A\u9731\u8642\u9736\u4A0F\u453D\u4585\u4AE9\u7075\u5B41\u971B\u975C\u91D5\u9757\u5B4A\u91EB\u975F\u9425\u50D0\u30B7\u30BC\u9789\u979F\u97B1\u97BE\u97C0\u97D2\u97E0\u546C\u97EE\u741C\u9433\u97FF\u97F5\u941D\u797A\u4AD1\u9834\u9833\u984B\u9866\u3B0E\u7175\u3D51\u0630\u415C\u5706\u98CA\u98B7\u98C8\u98C7\u4AFF\u6D27\u16D3\u55B0\u98E1\u98E6\u98EC\u9378\u9939\u4A29\u4B72\u9857\u9905\u99F5\u9A0C\u9A3B\u9A10\u9A58\u5725\u36C4\u90B1\u9BD5\u9AE0\u9AE2\u9B05\u9AF4\u4C0E\u9B14\u9B2D\u8600\u5034\u9B34\u69A8\u38C3\u307D\u9B50\u9B40\u9D3E\u5A45\u1863\u9B8E\u424B\u9C02\u9BFF\u9C0C\u9E68\u9DD4\u9FB7\uA192\uA1AB\uA0E1\uA123\uA1DF\u9D7E\u9D83\uA134\u9E0E\u6888\u9DC4\u215B\uA193\uA220\u193B\uA233\u9D39\uA0B9\uA2B4\u9E90\u9E95\u9E9E\u9EA2\u4D34\u9EAA\u9EAF\u4364\u9EC1\u3B60\u39E5\u3D1D\u4F32\u37BE\u8C2B\u9F02\u9F08\u4B96\u9424\u6DA2\u9F17\u9F16\u9F39\u569F\u568A\u9F45\u99B8\u908B\u97F2\u847F\u9F62\u9F69\u7ADC\u9F8E\u7216\u4BBE\u4975\u49BB\u7177\u49F8\u4348\u4A51\u739E\u8BDA\u18FA\u799F\u897E\u8E36\u9369\u93F3\u8A44\u92EC\u9381\u93CB\u896C\u44B9\u7217\u3EEB\u7772\u7A43\u70D0\u4473\u43F8\u717E\u17EF\u70A3\u18BE\u3599\u3EC7\u1885\u542F\u17F8\u3722\u16FB\u1839\u36E1\u1774\u18D1\u5F4B\u3723\u16C0\u575B\u4A25\u13FE\u12A8\u13C6\u14B6\u8503\u36A6\u8503\u8455\u4994\u7165\u3E31\u555C\u3EFB\u7052\u44F4\u36EE\u999D\u6F26\u67F9\u3733\u3C15\u3DE7\u586C\u1922\u6810\u4057\u373F\u40E1\u408B\u410F\u6C21\u54CB\u569E\u66B1\u5692\u0FDF\u0BA8\u0E0D\u93C6\u8B13\u939C\u4EF8\u512B\u3819\u4436\u4EBC\u0465\u037F\u4F4B\u4F8A\u5651\u5A68\u01AB\u03CB\u3999\u030A\u0414\u3435\u4F29\u02C0\u8EB3\u0275\u8ADA\u020C\u4E98\u50CD\u510D\u4FA2\u4F03\u4A0E\u3E8A\u4F42\u502E\u506C\u5081\u4FCC\u4FE5\u5058\u50FC\u5159\u515B\u515D\u515E\u6E76\u3595\u3E39\u3EBF\u6D72\u1884\u3E89\u51A8\u51C3\u05E0\u44DD\u04A3\u0492\u0491\u8D7A\u8A9C\u070E\u5259\u52A4\u0873\u52E1\u936E\u467A\u718C\u438C\u0C20\u49AC\u10E4\u69D1\u0E1D\u7479\u3EDE\u7499\u7414\u7456\u7398\u4B8E\u4ABC\u408D\u53D0\u3584\u720F\u40C9\u55B4\u0345\u54CD\u0BC6\u571D\u925D\u96F4\u9366\u57DD\u578D\u577F\u363E\u58CB\u5A99\u8A46\u16FA\u176F\u1710\u5A2C\u59B8\u928F\u5A7E\u5ACF\u5A12\u5946\u19F3\u1861\u4295\u36F5\u6D05\u7443\u5A21\u5E83\u5A81\u8BD7\u0413\u93E0\u748C\u1303\u7105\u4972\u9408\u89FB\u93BD\u37A0\u5C1E\u5C9E\u5E5E\u5E48\u1996\u197C\u3AEE\u5ECD\u5B4F\u1903\u1904\u3701\u18A0\u36DD\u16FE\u36D3\u812A\u8A47\u1DBA\u3472\u89A8\u5F0C\u5F0E\u1927\u17AB\u5A6B\u173B\u5B44\u8614\u75FD\u8860\u607E\u2860\u262B\u5FDB\u3EB8\u25AF\u25BE\u9088\u6F73\u61C0\u003E\u0046\u261B\u6199\u6198\u6075\u2C9B\u2D07\u46D4\u914D\u6471\u4665\u2B6A\u3A29\u2B22\u3450\u98EA\u2E78\u6337\uA45B\u64B6\u6331\u63D1\u49E3\u2D67\u62A4\u2CA1\u643B\u656B\u6972\u3BF4\u308E\u32AD\u4989\u32AB\u550D\u32E0\u18D9\u943F\u66CE\u3289\u31B3\u3AE0\u4190\u5584\u8B22\u558F\u16FC\u555B\u5425\u78EE\u3103\u182A\u3234\u3464\u320F\u3182\u42C9\u668E\u6D24\u666B\u4B93\u6630\u7870\u1DEB\u6663\u32D2\u32E1\u661E\u5872\u38D1\u383A\u37BC\u3B99\u37A2\u33FE\u74D0\u3B96\u678F\u462A\u68B6\u681E\u3BC4\u6ABE\u3863\u37D5\u4487\u6A33\u6A52\u6AC9\u6B05\u1912\u6511\u6898\u6A4C\u3BD7\u6A7A\u6B57\u3FC0\u3C9A\u93A0\u92F2\u8BEA\u8ACB\u9289\u801E\u89DC\u9467\u6DA5\u6F0B\u49EC\u6D67\u3F7F\u3D8F\u6E04\u403C\u5A3D\u6E0A\u5847\u6D24\u7842\u713B\u431A\u4276\u70F1\u7250\u7287\u7294\u478F\u4725\u5179\u4AA4\u05EB\u747A\u3EF8\u365F\u4A4A\u4917\u5FE1\u3F06\u3EB1\u4ADF\u8C23\u3F35\u60A7\u3EF3\u74CC\u743C\u9387\u7437\u449F\u6DEA\u4551\u7583\u3F63\u4CD9\u4D06\u3F58\u7555\u7673\uA5C6\u3B19\u7468\u8ACC\u49AB\u498E\u3AFB\u3DCD\u4A4E\u3EFF\u49C5\u48F3\u91FA\u5732\u9342\u8AE3\u1864\u50DF\u5221\u51E7\u7778\u3232\u770E\u770F\u777B\u4697\u3781\u3A5E\u48F0\u7438\u749B\u3EBF\u4ABA\u4AC7\u40C8\u4A96\u61AE\u9307\u5581\u781E\u788D\u7888\u78D2\u73D0\u7959\u7741\u56E3\u410E\u799B\u8496\u79A5\u6A2D\u3EFA\u7A3A\u79F4\u416E\u16E6\u4132\u9235\u79F1\u0D4C\u498C\u0299\u3DBA\u176E\u3597\u556B\u3570\u36AA\u01D4\u0C0D\u7AE2\u5A59\u26F5\u5AAF\u5A9C\u5A0D\u025B\u78F0\u5A2A\u5BC6\u7AFE\u41F9\u7C5D\u7C6D\u4211\u5BB3\u5EBC\u5EA6\u7CCD\u49F9\u17B0\u7C8E\u7C7C\u7CAE\u6AB2\u7DDC\u7E07\u7DD3\u7F4E\u6261\u615C\u7B48\u7D97\u5E82\u426A\u6B75\u0916\u67D6\u004E\u35CF\u57C4\u6412\u63F8\u4962\u7FDD\u7B27\u082C\u5AE9\u5D43\u7B0C\u5E0E\u99E6\u8645\u9A63\u6A1C\u343F\u39E2\u49F7\u65AD\u9A1F\u65A0\u8480\u7127\u6CD1\u44EA\u8137\u4402\u80C6\u8109\u8142\u67B4\u98C3\u6A42\u8262\u8265\u6A51\u8453\u6DA7\u8610\u721B\u5A86\u417F\u1840\u5B2B\u18A1\u5AE4\u18D8\u86A0\uF9BC\u3D8F\u882D\u7422\u5A02\u886E\u4F45\u8887\u88BF\u88E6\u8965\u894D\u5683\u8954\u7785\u7784\u8BF5\u8BD9\u8B9C\u89F9\u3EAD\u84A3\u46F5\u46CF\u37F2\u8A3D\u8A1C\u9448\u5F4D\u922B\u4284\u65D4\u7129\u70C4\u1845\u9D6D\u8C9F\u8CE9\u7DDC\u599A\u77C3\u59F0\u436E\u36D4\u8E2A\u8EA7\u4C09\u8F30\u8F4A\u42F4\u6C58\u6FBB\u2321\u489B\u6F79\u6E8B\u17DA\u9BE9\u36B5\u492F\u90BB\u9097\u5571\u4906\u91BB\u9404\u8A4B\u4062\u8AFC\u9427\u8C1D\u8C3B\u84E5\u8A2B\u9599\u95A7\u9597\u9596\u8D34\u7445\u3EC2\u48FF\u4A42\u43EA\u3EE7\u3225\u968F\u8EE7\u8E66\u8E65\u3ECC\u49ED\u4A78\u3FEE\u7412\u746B\u3EFC\u9741\u90B0\u6847\u4A1D\u9093\u57DF\u975D\u9368\u8989\u8C26\u8B2F\u63BE\u92BA\u5B11\u8B69\u493C\u73F9\u421B\u979B\u9771\u9938\u0F26\u5DC1\u8BC5\u4AB2\u981F\u94DA\u92F6\u95D7\u91E5\u44C0\u8B50\u4A67\u8B64\u98DC\u8A45\u3F00\u922A\u4925\u8414\u993B\u994D\u7B06\u3DFD\u999B\u4B6F\u99AA\u9A5C\u8B65\u58C8\u6A8F\u9A21\u5AFE\u9A2F\u98F1\u4B90\u9948\u99BC\u4BBD\u4B97\u937D\u5872\u1302\u5822\u49B8\u14E8\u7844\u271F\u3DB8\u68C5\u3D7D\u9458\u3927\u6150\u2781\u296B\u6107\u9C4F\u9C53\u9C7B\u9C35\u9C10\u9B7F\u9BCF\u9E2D\u9B9F\uA1F5\uA0FE\u9D21\u4CAE\u4104\u9E18\u4CB0\u9D0C\uA1B4\uA0ED\uA0F3\u992F\u9DA5\u84BD\u6E12\u6FDF\u6B82\u85FC\u4533\u6DA4\u6E84\u6DF0\u8420\u85EE\u6E00\u37D7\u6064\u79E2\u359C\u3640\u492D\u49DE\u3D62\u93DB\u92BE\u9348\u02BF\u78B9\u9277\u944D\u4FE4\u3440\u9064\u555D\u783D\u7854\u78B6\u784B\u1757\u31C9\u4941\u369A\u4F72\u6FDA\u6FD9\u701E\u701E\u5414\u41B5\u57BB\u58F3\u578A\u9D16\u57D7\u7134\u34AF\u41AC\u71EB\u6C40\u4F97\u5B28\u17B5\u8A49\u610C\u5ACE\u5A0B\u42BC\u4488\u372C\u4B7B\u89FC\u93BB\u93B8\u18D6\u0F1D\u8472\u6CC0\u1413\u42FA\u2C26\u43C1\u5994\u3DB7\u6741\u7DA8\u615B\u60A4\u49B9\u498B\u89FA\u92E5\u73E2\u3EE9\u74B4\u8B63\u189F\u3EE1\u4AB3\u6AD8\u73F3\u73FB\u3ED6\u4A3E\u4A94\u17D9\u4A66\u03A7\u1424\u49E5\u7448\u4916\u70A5\u4976\u9284\u73E6\u935F\u04FE\u9331\u8ACE\u8A16\u9386\u8BE7\u55D5\u4935\u8A82\u716B\u4943\u0CFF\u56A4\u061A\u0BEB\u0CB8\u5502\u79C4\u17FA\u7DFE\u16C2\u4A50\u1852\u452E\u9401\u370A\u8AC0\u49AD\u59B0\u18BF\u1883\u7484\u5AA1\u36E2\u3D5B\u36B0\u925F\u5A79\u8A81\u1862\u9374\u3CCD\u0AB4\u4A96\u398A\u50F4\u3D69\u3D4C\u139C\u7175\u42FB\u8218\u6E0F\u90E4\u44EB\u6D57\u7E4F\u7067\u6CAF\u3CD6\u3FED\u3E2D\u6E02\u6F0C\u3D6F\u03F5\u7551\u36BC\u34C8\u4680\u3EDA\u4871\u59C4\u926E\u493E\u8F41\u8C1C\u6BC0\u5812\u57C8\u36D6\u1452\u70FE\u4362\u4A71\u2FE3\u12B0\u23BD\u68B9\u6967\u1398\u34E5\u7BF4\u36DF\u8A83\u37D6\u33FA\u4C9F\u6A1A\u36AD\u6CB7\u843E\u44DF\u44CE\u6D26\u6D51\u6C82\u6FDE\u6F17\u7109\u833D\u173A\u83ED\u6C80\u7053\u17DB\u5989\u5A82\u17B3\u5A61\u5A71\u1905\u41FC\u372D\u59EF\u173C\u36C7\u718E\u9390\u669A\u42A5\u5A6E\u5A2B\u4293\u6A2B\u3EF9\u7736\u445B\u42CA\u711D\u4259\u89E1\u4FB0\u6D28\u5CC2\u44CE\u7E4D\u43BD\u6A0C\u4256\u1304\u70A6\u7133\u43E9\u3DA5\u6CDF\uF825\u4A4F\u7E65\u59EB\u5D2F\u3DF3\u5F5C\u4A5D\u17DF\u7DA4\u8426\u5485\u3AFA\u3300\u0214\u577E\u08D5\u0619\u3FE5\u1F9E\uA2B6\u7003\u915B\u5D70\u738F\u7CD3\u8A59\u9420\u4FC8\u7FE7\u72CD\u7310\u7AF4\u7338\u7339\u56F6\u7341\u7348\u3EA9\u7B18\u906C\u71F5\u48F2\u73E1\u81F6\u3ECA\u770C\u3ED1\u6CA2\u56FD\u7419\u741E\u741F\u3EE2\u3EF0\u3EF4\u3EFA\u74D3\u3F0E\u3F53\u7542\u756D\u7572\u758D\u3F7C\u75C8\u75DC\u3FC0\u764D\u3FD7\u7674\u3FDC\u767A\u4F5C\u7188\u5623\u8980\u5869\u401D\u7743\u4039\u6761\u4045\u35DB\u7798\u406A\u406F\u5C5E\u77BE\u77CB\u58F2\u7818\u70B9\u781C\u40A8\u7839\u7847\u7851\u7866\u8448\u5535\u7933\u6803\u7932\u4103\u4109\u7991\u7999\u8FBB\u7A06\u8FBC\u4167\u7A91\u41B2\u7ABC\u8279\u41C4\u7ACF\u7ADB\u41CF\u4E21\u7B62\u7B6C\u7B7B\u7C12\u7C1B\u4260\u427A\u7C7B\u7C9C\u428C\u7CB8\u4294\u7CED\u8F93\u70C0\u0CCF\u7DCF\u7DD4\u7DD0\u7DFD\u7FAE\u7FB4\u729F\u4397\u8020\u8025\u7B39\u802E\u8031\u8054\u3DCC\u57B4\u70A0\u80B7\u80E9\u43ED\u810C\u732A\u810E\u8112\u7560\u8114\u4401\u3B39\u8156\u8159\u815A\u4413\u583A\u817C\u8184\u4425\u8193\u442D\u81A5\u57EF\u81C1\u81E4\u8254\u448F\u82A6\u8276\u82CA\u82D8\u82FF\u44B0\u8357\u9669\u698A\u8405\u70F5\u8464\u60E3\u8488\u4504\u84BE\u84E1\u84F8\u8510\u8538\u8552\u453B\u856F\u8570\u85E0\u4577\u8672\u8692\u86B2\u86EF\u9645\u878B\u4606\u4617\u88AE\u88FF\u8924\u8947\u8991\u7967\u8A29\u8A38\u8A94\u8AB4\u8C51\u8CD4\u8CF2\u8D1C\u4798\u585F\u8DC3\u47ED\u4EEE\u8E3A\u55D8\u5754\u8E71\u55F5\u8EB0\u4837\u8ECE\u8EE2\u8EE4\u8EED\u8EF2\u8FB7\u8FC1\u8FCA\u8FCC\u9033\u99C4\u48AD\u98E0\u9213\u491E\u9228\u9258\u926B\u92B1\u92AE\u92BF\u92E3\u92EB\u92F3\u92F4\u92FD\u9343\u9384\u93AD\u4945\u4951\u9EBF\u9417\u5301\u941D\u942D\u943E\u496A\u9454\u9479\u952D\u95A2\u49A7\u95F4\u9633\u49E5\u67A0\u4A24\u9740\u4A35\u97B2\u97C2\u5654\u4AE4\u60E8\u98B9\u4B19\u98F1\u5844\u990E\u9919\u51B4\u991C\u9937\u9942\u995D\u9962\u4B70\u99C5\u4B9D\u9A3C\u9B0F\u7A83\u9B69\u9B81\u9BDD\u9BF1\u9BF4\u4C6D\u9C20\u376F\u1BC2\u9D49\u9C3A\u9EFE\u5650\u9D93\u9DBD\u9DC0\u9DFC\u94F6\u8FB6\u9E7B\u9EAC\u9EB1\u9EBD\u9EC6\u94DC\u9EE2\u9EF1\u9EF8\u7AC8\u9F44\u0094\u02B7\u03A0\u691A\u94C3\u59AC\u04D7\u5840\u94C1\u37B9\u05D5\u0615\u0676\u16BA\u5757\u7173\u0AC2\u0ACD\u0BBF\u546A\uF83B\u0BCB\u549E\u0BFB\u0C3B\u0C53\u0C65\u0C7C\u60E7\u0C8D\u567A\u0CB5\u0CDD\u0CED\u0D6F\u0DB2\u0DC8\u6955\u9C2F\u87A5\u0E04\u0E0E\u0ED7\u0F90\u0F2D\u0E73\u5C20\u0FBC\u5E0B\u105C\u104F\u1076\u671E\u107B\u1088\u1096\u3647\u10BF\u10D3\u112F\u113B\u5364\u84AD\u12E3\u1375\u1336\u8B81\u1577\u1619\u17C3\u17C7\u4E78\u70BB\u182D\u196A\u1A2D\u1A45\u1C2A\u1C70\u1CAC\u1EC8\u62C3\u1ED5\u1F15\u7198\u6855\u2045\u69E9\u36C8\u227C\u23D7\u23FA\u272A\u2871\u294F\u82FD\u2967\u2993\u2AD5\u89A5\u2AE8\u8FA0\u2B0E\u97B8\u2B3F\u9847\u9ABD\u2C4C\u0000\u2C88\u2CB7\u5BE8\u2D08\u2D12\u2DB7\u2D95\u2E42\u2F74\u2FCC\u3033\u3066\u331F\u33DE\u5FB1\u6648\u66BF\u7A79\u3567\u35F3\u7201\u49BA\u77D7\u361A\u3716\u7E87\u0346\u58B5\u670E\u6918\u3AA7\u7657\u5FE2\u3E11\u3EB9\u75FE\u209A\u48D0\u4AB8\u4119\u8A9A\u42EE\u430D\u403B\u4334\u4396\u4A45\u05CA\u51D2\u0611\u599F\u1EA8\u3BBE\u3CFF\u4404\u44D6\u5788\u4674\u399B\u472F\u85E8\u99C9\u3762\u21C3\u8B5E\u8B4E\u99D6\u4812\u48FB\u4A15\u7209\u4AC0\u0C78\u5965\u4EA5\u4F86\u0779\u8EDA\u502C\u528F\u573F\u7171\u5299\u5419\u3F4A\u4AA7\u55BC\u5446\u546E\u6B52\u91D4\u3473\u553F\u7632\u555E\u4718\u5562\u5566\u57C7\u493F\u585D\u5066\u34FB\u33CC\u60DE\u5903\u477C\u8948\u5AAE\u5B89\u5C06\u1D90\u57A1\u7151\u6FB6\u6102\u7C12\u9056\u61B2\u4F9A\u8B62\u6402\u644A\u5D5B\u6BF7\u8F36\u6484\u191C\u8AEA\u49F6\u6488\u3FEF\u6512\u4BC0\u65BF\u66B5\u271B\u9465\u57E1\u6195\u5A27\uF8CD\u4FBB\u56B9\u4521\u66FC\u4E6A\u4934\u9656\u6D8F\u6CBD\u3618\u8977\u6799\u686E\u6411\u685E\u71DF\u68C7\u7B42\u90C0\u0A11\u6926\u9104\u6939\u7A45\u9DF0\u69FA\u9A26\u6A2D\u365F\u6469\u0021\u7983\u6A34\u6B5B\u5D2C\u3519\u83CF\u6B9D\u46D0\u6CA4\u753B\u8865\u6DAE\u58B6\u371C\u258D\u704B\u71CD\u3C54\u7280\u7285\u9281\u217A\u728B\u9330\u72E6\u49D0\u6C39\u949F\u7450\u0EF8\u8827\u88F5\u2926\u8473\u17B1\u6EB8\u4A2A\u1820\u39A4\u36B9\u5C10\u79E3\u453F\u66B6\u9CAD\u98A4\u8943\u77CC\u7858\u56D6\u40DF\u160A\u39A1\u372F\u80E8\u13C5\u71AD\u8366\u79DD\u91A8\u5A67\u4CB7\u70AF\u89AB\u79FD\u7A0A\u7B0B\u7D66\u417A\u7B43\u797E\u8009\u6FB5\uA2DF\u6A03\u8318\u53A2\u6E07\u93BF\u6836\u975D\u816F\u8023\u69B5\u13ED\u322F\u8048\u5D85\u8C30\u8083\u5715\u9823\u8949\u5DAB\u4988\u65BE\u69D5\u53D2\u4AA5\u3F81\u3C11\u6736\u8090\u80F4\u812E\u1FA1\u814F\u8189\u81AF\u821A\u8306\u832F\u838A\u35CA\u8468\u86AA\u48FA\u63E6\u8956\u7808\u9255\u89B8\u43F2\u89E7\u43DF\u89E8\u8B46\u8BD4\u59F8\u8C09\u8F0B\u8FC5\u90EC\u7B51\u9110\u913C\u3DF7\u915E\u4ACA\u8FD0\u728F\u568B\u94E7\u95E9\u95B0\u95B8\u9732\u98D1\u9949\u996A\u99C3\u9A28\u9B0E\u9D5A\u9D9B\u7E9F\u9EF8\u9F23\u4CA4\u9547\uA293\u71A2\uA2FF\u4D91\u9012\uA5CB\u4D9C\u0C9C\u8FBE\u55C1\u8FBA\u24B0\u8FB9\u4A93\u4509\u7E7F\u6F56\u6AB1\u4EEA\u34E4\u8B2C\u789D\u373A\u8E80\u17F5\u8024\u8B6C\u8B99\u7A3E\u66AF\u3DEB\u7655\u3CB7\u5635\u5956\u4E9A\u5E81\u6258\u56BF\u0E6D\u8E0E\u5B6D\u3E88\u4C9E\u63DE\u62D0\u17F6\u187B\u6530\u562D\u5C4A\u541A\u5311\u3DC6\u9D98\u4C7D\u5622\u561E\u7F49\u5ED8\u5975\u3D40\u8770\u4E1C\u0FEA\u0D49\u36BA\u8117\u9D5E\u8D18\u763B\u9C45\u764E\u77B9\u9345\u5432\u8148\u82F7\u5625\u8132\u8418\u80BD\u55EA\u7962\u5643\u5416\u0E9D\u35CE\u5605\u55F1\u66F1\u82E2\u362D\u7534\u55F0\u55BA\u5497\u5572\u0C41\u0C96\u5ED0\u5148\u0E76\u2C62\u0EA2\u9EAB\u7D5A\u55DE\u1075\u629D\u976D\u5494\u8CCD\u71F6\u9176\u63FC\u63B9\u63FE\u5569\u2B43\u9C72\u2EB3\u519A\u34DF\u0DA7\u51A7\u544D\u551E\u5513\u7666\u8E2D\u688A\u75B1\u80B6\u8804\u8786\u88C7\u81B6\u841C\u10C1\u44EC\u7304\u4706\u5B90\u830B\u6893\u567B\u26F4\u7D2F\u41A3\u7D73\u6ED0\u72B6\u9170\u11D9\u9208\u3CFC\uA6A9\u0EAC\u0EF9\u7266\u1CA2\u474E\u4FC2\u7FF9\u0FEB\u40FA\u9C5D\u651F\u2DA0\u48F3\u47E0\u9D7C\u0FEC\u0E0A\u6062\u75A3\u0FED\u0000\u6048\u1187\u71A3\u7E8E\u9D50\u4E1A\u4E04\u3577\u5B0D\u6CB2\u5367\u36AC\u39DC\u537D\u36A5\u4618\u589A\u4B6E\u822D\u544B\u57AA\u5A95\u0979\u0000\u3A52\u2465\u7374\u9EAC\u4D09\u9BED\u3CFE\u9F30\u4C5B\u4FA9\u959E\u9FDE\u845C\u3DB6\u72B2\u67B3\u3720\u632E\u7D25\u3EF7\u3E2C\u3A2A\u9008\u52CC\u3E74\u367A\u45E9\u048E\u7640\u5AF0\u0EB6\u787A\u7F2E\u58A7\u40BF\u567C\u9B8B\u5D74\u7654\uA434\u9E85\u4CE1\u75F9\u37FB\u6119\u30DA\u43F2\u0000\u565D\u12A9\u57A7\u4963\u9E06\u5234\u70AE\u35AD\u6C4A\u9D7C\u7C56\u9B39\u57DE\u176C\u5C53\u64D3\u94D0\u6335\u7164\u86AD\u0D28\u6D22\u4AE2\u0D71\u0000\u51FE\u1F0F\u5D8E\u9703\u1DD1\u9E81\u904C\u7B1F\u9B02\u5CD1\u7BA3\u6268\u6335\u9AFF\u7BCF\u9B2A\u7C7E\u9B2E\u7C42\u7C86\u9C15\u7BFC\u9B09\u9F17\u9C1B\u493E\u9F5A\u5573\u5BC3\u4FFD\u9E98\u4FF2\u5260\u3E06\u52D1\u5767\u5056\u59B7\u5E12\u97C8\u9DAB\u8F5C\u5469\u97B4\u9940\u97BA\u532C\u6130\u692C\u53DA\u9C0A\u9D02\u4C3B\u9641\u6980\u50A6\u7546\u176D\u99DA\u5273\u0000\u9159\u9681\u915C\u0000\u9151\u8E97\u637F\u6D23\u6ACA\u5611\u918E\u757A\u6285\u03FC\u734F\u7C70\u5C21\u3CFD\u0000\u4919\u76D6\u9B9D\u4E2A\u0CD4\u83BE\u8842\u0000\u5C4A\u69C0\u50ED\u577A\u521F\u5DF5\u4ECE\u6C31\u01F2\u4F39\u549C\u54DA\u529A\u8D82\u35FE\u5F0C\u35F3\u0000\u6B52\u917C\u9FA5\u9B97\u982E\u98B4\u9ABA\u9EA8\u9E84\u717A\u7B14\u0000\u6BFA\u8818\u7F78\u0000\u5620\uA64A\u8E77\u9F53\u0000\u8DD4\u8E4F\u9E1C\u8E01\u6282\u837D\u8E28\u8E75\u7AD3\u4A77\u7A3E\u78D8\u6CEA\u8A67\u7607\u8A5A\u9F26\u6CCE\u87D6\u75C3\uA2B2\u7853\uF840\u8D0C\u72E2\u7371\u8B2D\u7302\u74F1\u8CEB\u4ABB\u862F\u5FBA\u88A0\u44B7\u0000\u183B\u6E05\u0000\u8A7E\u251B\u0000\u60FD\u7667\u9AD7\u9D44\u936E\u9B8F\u87F5\u0000\u880F\u8CF7\u732C\u9721\u9BB0\u35D6\u72B2\u4C07\u7C51\u994A\u6159\u6159\u4C04\u9E96\u617D\u0000\u575F\u616F\u62A6\u6239\u62CE\u3A5C\u61E2\u53AA\u33F5\u6364\u6802\u35D2\u5D57\u8BC2\u8FDA\u8E39\u0000\u50D9\u1D46\u7906\u5332\u9638\u0F3B\u4065\u0000\u77FE\u0000\u7CC2\u5F1A\u7CDA\u7A2D\u8066\u8063\u7D4D\u7505\u74F2\u8994\u821A\u670C\u8062\u7486\u805B\u74F0\u8103\u7724\u8989\u67CC\u7553\u6ED1\u87A9\u87CE\u81C8\u878C\u8A49\u8CAD\u8B43\u772B\u74F8\u84DA\u3635\u69B2\u8DA6\u0000\u89A9\u7468\u6DB9\u87C1\u4011\u74E7\u3DDB\u7176\u60A4\u619C\u3CD1\u7162\u6077\u0000\u7F71\u8B2D\u7250\u60E9\u4B7E\u5220\u3C18\u3CC7\u5ED7\u7656\u5531\u1944\u12FE\u9903\u6DDC\u70AD\u5CC1\u61AD\u8A0F\u3677\u00EE\u6846\u4F0E\u4562\u5B1F\u634C\u9F50\u9EA6\u626B\u3000\uFF0C\u3001\u3002\uFF0E\u2027\uFF1B\uFF1A\uFF1F\uFF01\uFE30\u2026\u2025\uFE50\uFE51\uFE52\u00B7\uFE54\uFE55\uFE56\uFE57\uFF5C\u2013\uFE31\u2014\uFE33\u2574\uFE34\uFE4F\uFF08\uFF09\uFE35\uFE36\uFF5B\uFF5D\uFE37\uFE38\u3014\u3015\uFE39\uFE3A\u3010\u3011\uFE3B\uFE3C\u300A\u300B\uFE3D\uFE3E\u3008\u3009\uFE3F\uFE40\u300C\u300D\uFE41\uFE42\u300E\u300F\uFE43\uFE44\uFE59\uFE5A\uFE5B\uFE5C\uFE5D\uFE5E\u2018\u2019\u201C\u201D\u301D\u301E\u2035\u2032\uFF03\uFF06\uFF0A\u203B\u00A7\u3003\u25CB\u25CF\u25B3\u25B2\u25CE\u2606\u2605\u25C7\u25C6\u25A1\u25A0\u25BD\u25BC\u32A3\u2105\u00AF\uFFE3\uFF3F\u02CD\uFE49\uFE4A\uFE4D\uFE4E\uFE4B\uFE4C\uFE5F\uFE60\uFE61\uFF0B\uFF0D\u00D7\u00F7\u00B1\u221A\uFF1C\uFF1E\uFF1D\u2266\u2267\u2260\u221E\u2252\u2261\uFE62\uFE63\uFE64\uFE65\uFE66\uFF5E\u2229\u222A\u22A5\u2220\u221F\u22BF\u33D2\u33D1\u222B\u222E\u2235\u2234\u2640\u2642\u2295\u2299\u2191\u2193\u2190\u2192\u2196\u2197\u2199\u2198\u2225\u2223\uFF0F\uFF3C\u2215\uFE68\uFF04\uFFE5\u3012\uFFE0\uFFE1\uFF05\uFF20\u2103\u2109\uFE69\uFE6A\uFE6B\u33D5\u339C\u339D\u339E\u33CE\u33A1\u338E\u338F\u33C4\u00B0\u5159\u515B\u515E\u515D\u5161\u5163\u55E7\u74E9\u7CCE\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u258F\u258E\u258D\u258C\u258B\u258A\u2589\u253C\u2534\u252C\u2524\u251C\u2594\u2500\u2502\u2595\u250C\u2510\u2514\u2518\u256D\u256E\u2570\u256F\u2550\u255E\u256A\u2561\u25E2\u25E3\u25E5\u25E4\u2571\u2572\u2573\uFF10\uFF11\uFF12\uFF13\uFF14\uFF15\uFF16\uFF17\uFF18\uFF19\u2160\u2161\u2162\u2163\u2164\u2165\u2166\u2167\u2168\u2169\u3021\u3022\u3023\u3024\u3025\u3026\u3027\u3028\u3029\u5341\u5344\u5345\uFF21\uFF22\uFF23\uFF24\uFF25\uFF26\uFF27\uFF28\uFF29\uFF2A\uFF2B\uFF2C\uFF2D\uFF2E\uFF2F\uFF30\uFF31\uFF32\uFF33\uFF34\uFF35\uFF36\uFF37\uFF38\uFF39\uFF3A\uFF41\uFF42\uFF43\uFF44\uFF45\uFF46\uFF47\uFF48\uFF49\uFF4A\uFF4B\uFF4C\uFF4D\uFF4E\uFF4F\uFF50\uFF51\uFF52\uFF53\uFF54\uFF55\uFF56\uFF57\uFF58\uFF59\uFF5A\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039A\u039B\u039C\u039D\u039E\u039F\u03A0\u03A1\u03A3\u03A4\u03A5\u03A6\u03A7\u03A8\u03A9\u03B1\u03B2\u03B3\u03B4\u03B5\u03B6\u03B7\u03B8\u03B9\u03BA\u03BB\u03BC\u03BD\u03BE\u03BF\u03C0\u03C1\u03C3\u03C4\u03C5\u03C6\u03C7\u03C8\u03C9\u3105\u3106\u3107\u3108\u3109\u310A\u310B\u310C\u310D\u310E\u310F\u3110\u3111\u3112\u3113\u3114\u3115\u3116\u3117\u3118\u3119\u311A\u311B\u311C\u311D\u311E\u311F\u3120\u3121\u3122\u3123\u3124\u3125\u3126\u3127\u3128\u3129\u02D9\u02C9\u02CA\u02C7\u02CB\u2400\u2401\u2402\u2403\u2404\u2405\u2406\u2407\u2408\u2409\u240A\u240B\u240C\u240D\u240E\u240F\u2410\u2411\u2412\u2413\u2414\u2415\u2416\u2417\u2418\u2419\u241A\u241B\u241C\u241D\u241E\u241F\u2421\u20AC";
+
+ private static final String TABLE3 = "\u4E00\u4E59\u4E01\u4E03\u4E43\u4E5D\u4E86\u4E8C\u4EBA\u513F\u5165\u516B\u51E0\u5200\u5201\u529B\u5315\u5341\u535C\u53C8\u4E09\u4E0B\u4E08\u4E0A\u4E2B\u4E38\u51E1\u4E45\u4E48\u4E5F\u4E5E\u4E8E\u4EA1\u5140\u5203\u52FA\u5343\u53C9\u53E3\u571F\u58EB\u5915\u5927\u5973\u5B50\u5B51\u5B53\u5BF8\u5C0F\u5C22\u5C38\u5C71\u5DDD\u5DE5\u5DF1\u5DF2\u5DF3\u5DFE\u5E72\u5EFE\u5F0B\u5F13\u624D\u4E11\u4E10\u4E0D\u4E2D\u4E30\u4E39\u4E4B\u5C39\u4E88\u4E91\u4E95\u4E92\u4E94\u4EA2\u4EC1\u4EC0\u4EC3\u4EC6\u4EC7\u4ECD\u4ECA\u4ECB\u4EC4\u5143\u5141\u5167\u516D\u516E\u516C\u5197\u51F6\u5206\u5207\u5208\u52FB\u52FE\u52FF\u5316\u5339\u5348\u5347\u5345\u535E\u5384\u53CB\u53CA\u53CD\u58EC\u5929\u592B\u592A\u592D\u5B54\u5C11\u5C24\u5C3A\u5C6F\u5DF4\u5E7B\u5EFF\u5F14\u5F15\u5FC3\u6208\u6236\u624B\u624E\u652F\u6587\u6597\u65A4\u65B9\u65E5\u66F0\u6708\u6728\u6B20\u6B62\u6B79\u6BCB\u6BD4\u6BDB\u6C0F\u6C34\u706B\u722A\u7236\u723B\u7247\u7259\u725B\u72AC\u738B\u4E19\u4E16\u4E15\u4E14\u4E18\u4E3B\u4E4D\u4E4F\u4E4E\u4EE5\u4ED8\u4ED4\u4ED5\u4ED6\u4ED7\u4EE3\u4EE4\u4ED9\u4EDE\u5145\u5144\u5189\u518A\u51AC\u51F9\u51FA\u51F8\u520A\u52A0\u529F\u5305\u5306\u5317\u531D\u4EDF\u534A\u5349\u5361\u5360\u536F\u536E\u53BB\u53EF\u53E4\u53F3\u53EC\u53EE\u53E9\u53E8\u53FC\u53F8\u53F5\u53EB\u53E6\u53EA\u53F2\u53F1\u53F0\u53E5\u53ED\u53FB\u56DB\u56DA\u5916\u592E\u5931\u5974\u5976\u5B55\u5B83\u5C3C\u5DE8\u5DE7\u5DE6\u5E02\u5E03\u5E73\u5E7C\u5F01\u5F18\u5F17\u5FC5\u620A\u6253\u6254\u6252\u6251\u65A5\u65E6\u672E\u672C\u672A\u672B\u672D\u6B63\u6BCD\u6C11\u6C10\u6C38\u6C41\u6C40\u6C3E\u72AF\u7384\u7389\u74DC\u74E6\u7518\u751F\u7528\u7529\u7530\u7531\u7532\u7533\u758B\u767D\u76AE\u76BF\u76EE\u77DB\u77E2\u77F3\u793A\u79BE\u7A74\u7ACB\u4E1E\u4E1F\u4E52\u4E53\u4E69\u4E99\u4EA4\u4EA6\u4EA5\u4EFF\u4F09\u4F19\u4F0A\u4F15\u4F0D\u4F10\u4F11\u4F0F\u4EF2\u4EF6\u4EFB\u4EF0\u4EF3\u4EFD\u4F01\u4F0B\u5149\u5147\u5146\u5148\u5168\u5171\u518D\u51B0\u5217\u5211\u5212\u520E\u5216\u52A3\u5308\u5321\u5320\u5370\u5371\u5409\u540F\u540C\u540A\u5410\u5401\u540B\u5404\u5411\u540D\u5408\u5403\u540E\u5406\u5412\u56E0\u56DE\u56DD\u5733\u5730\u5728\u572D\u572C\u572F\u5729\u5919\u591A\u5937\u5938\u5984\u5978\u5983\u597D\u5979\u5982\u5981\u5B57\u5B58\u5B87\u5B88\u5B85\u5B89\u5BFA\u5C16\u5C79\u5DDE\u5E06\u5E76\u5E74\u5F0F\u5F1B\u5FD9\u5FD6\u620E\u620C\u620D\u6210\u6263\u625B\u6258\u6536\u65E9\u65E8\u65EC\u65ED\u66F2\u66F3\u6709\u673D\u6734\u6731\u6735\u6B21\u6B64\u6B7B\u6C16\u6C5D\u6C57\u6C59\u6C5F\u6C60\u6C50\u6C55\u6C61\u6C5B\u6C4D\u6C4E\u7070\u725F\u725D\u767E\u7AF9\u7C73\u7CF8\u7F36\u7F8A\u7FBD\u8001\u8003\u800C\u8012\u8033\u807F\u8089\u808B\u808C\u81E3\u81EA\u81F3\u81FC\u820C\u821B\u821F\u826E\u8272\u827E\u866B\u8840\u884C\u8863\u897F\u9621\u4E32\u4EA8\u4F4D\u4F4F\u4F47\u4F57\u4F5E\u4F34\u4F5B\u4F55\u4F30\u4F50\u4F51\u4F3D\u4F3A\u4F38\u4F43\u4F54\u4F3C\u4F46\u4F63\u4F5C\u4F60\u4F2F\u4F4E\u4F36\u4F59\u4F5D\u4F48\u4F5A\u514C\u514B\u514D\u5175\u51B6\u51B7\u5225\u5224\u5229\u522A\u5228\u52AB\u52A9\u52AA\u52AC\u5323\u5373\u5375\u541D\u542D\u541E\u543E\u5426\u544E\u5427\u5446\u5443\u5433\u5448\u5442\u541B\u5429\u544A\u5439\u543B\u5438\u542E\u5435\u5436\u5420\u543C\u5440\u5431\u542B\u541F\u542C\u56EA\u56F0\u56E4\u56EB\u574A\u5751\u5740\u574D\u5747\u574E\u573E\u5750\u574F\u573B\u58EF\u593E\u599D\u5992\u59A8\u599E\u59A3\u5999\u5996\u598D\u59A4\u5993\u598A\u59A5\u5B5D\u5B5C\u5B5A\u5B5B\u5B8C\u5B8B\u5B8F\u5C2C\u5C40\u5C41\u5C3F\u5C3E\u5C90\u5C91\u5C94\u5C8C\u5DEB\u5E0C\u5E8F\u5E87\u5E8A\u5EF7\u5F04\u5F1F\u5F64\u5F62\u5F77\u5F79\u5FD8\u5FCC\u5FD7\u5FCD\u5FF1\u5FEB\u5FF8\u5FEA\u6212\u6211\u6284\u6297\u6296\u6280\u6276\u6289\u626D\u628A\u627C\u627E\u6279\u6273\u6292\u626F\u6298\u626E\u6295\u6293\u6291\u6286\u6539\u653B\u6538\u65F1\u66F4\u675F\u674E\u674F\u6750\u6751\u675C\u6756\u675E\u6749\u6746\u6760\u6753\u6757\u6B65\u6BCF\u6C42\u6C5E\u6C99\u6C81\u6C88\u6C89\u6C85\u6C9B\u6C6A\u6C7A\u6C90\u6C70\u6C8C\u6C68\u6C96\u6C92\u6C7D\u6C83\u6C72\u6C7E\u6C74\u6C86\u6C76\u6C8D\u6C94\u6C98\u6C82\u7076\u707C\u707D\u7078\u7262\u7261\u7260\u72C4\u72C2\u7396\u752C\u752B\u7537\u7538\u7682\u76EF\u77E3\u79C1\u79C0\u79BF\u7A76\u7CFB\u7F55\u8096\u8093\u809D\u8098\u809B\u809A\u80B2\u826F\u8292\u828B\u828D\u898B\u89D2\u8A00\u8C37\u8C46\u8C55\u8C9D\u8D64\u8D70\u8DB3\u8EAB\u8ECA\u8F9B\u8FB0\u8FC2\u8FC6\u8FC5\u8FC4\u5DE1\u9091\u90A2\u90AA\u90A6\u90A3\u9149\u91C6\u91CC\u9632\u962E\u9631\u962A\u962C\u4E26\u4E56\u4E73\u4E8B\u4E9B\u4E9E\u4EAB\u4EAC\u4F6F\u4F9D\u4F8D\u4F73\u4F7F\u4F6C\u4F9B\u4F8B\u4F86\u4F83\u4F70\u4F75\u4F88\u4F69\u4F7B\u4F96\u4F7E\u4F8F\u4F91\u4F7A\u5154\u5152\u5155\u5169\u5177\u5176\u5178\u51BD\u51FD\u523B\u5238\u5237\u523A\u5230\u522E\u5236\u5241\u52BE\u52BB\u5352\u5354\u5353\u5351\u5366\u5377\u5378\u5379\u53D6\u53D4\u53D7\u5473\u5475\u5496\u5478\u5495\u5480\u547B\u5477\u5484\u5492\u5486\u547C\u5490\u5471\u5476\u548C\u549A\u5462\u5468\u548B\u547D\u548E\u56FA\u5783\u5777\u576A\u5769\u5761\u5766\u5764\u577C\u591C\u5949\u5947\u5948\u5944\u5954\u59BE\u59BB\u59D4\u59B9\u59AE\u59D1\u59C6\u59D0\u59CD\u59CB\u59D3\u59CA\u59AF\u59B3\u59D2\u59C5\u5B5F\u5B64\u5B63\u5B97\u5B9A\u5B98\u5B9C\u5B99\u5B9B\u5C1A\u5C48\u5C45\u5C46\u5CB7\u5CA1\u5CB8\u5CA9\u5CAB\u5CB1\u5CB3\u5E18\u5E1A\u5E16\u5E15\u5E1B\u5E11\u5E78\u5E9A\u5E97\u5E9C\u5E95\u5E96\u5EF6\u5F26\u5F27\u5F29\u5F80\u5F81\u5F7F\u5F7C\u5FDD\u5FE0\u5FFD\u5FF5\u5FFF\u600F\u6014\u602F\u6035\u6016\u602A\u6015\u6021\u6027\u6029\u602B\u601B\u6216\u6215\u623F\u623E\u6240\u627F\u62C9\u62CC\u62C4\u62BF\u62C2\u62B9\u62D2\u62DB\u62AB\u62D3\u62D4\u62CB\u62C8\u62A8\u62BD\u62BC\u62D0\u62D9\u62C7\u62CD\u62B5\u62DA\u62B1\u62D8\u62D6\u62D7\u62C6\u62AC\u62CE\u653E\u65A7\u65BC\u65FA\u6614\u6613\u660C\u6606\u6602\u660E\u6600\u660F\u6615\u660A\u6607\u670D\u670B\u676D\u678B\u6795\u6771\u679C\u6773\u6777\u6787\u679D\u6797\u676F\u6770\u677F\u6789\u677E\u6790\u6775\u679A\u6793\u677C\u676A\u6772\u6B23\u6B66\u6B67\u6B7F\u6C13\u6C1B\u6CE3\u6CE8\u6CF3\u6CB1\u6CCC\u6CE5\u6CB3\u6CBD\u6CBE\u6CBC\u6CE2\u6CAB\u6CD5\u6CD3\u6CB8\u6CC4\u6CB9\u6CC1\u6CAE\u6CD7\u6CC5\u6CF1\u6CBF\u6CBB\u6CE1\u6CDB\u6CCA\u6CAC\u6CEF\u6CDC\u6CD6\u6CE0\u7095\u708E\u7092\u708A\u7099\u722C\u722D\u7238\u7248\u7267\u7269\u72C0\u72CE\u72D9\u72D7\u72D0\u73A9\u73A8\u739F\u73AB\u73A5\u753D\u759D\u7599\u759A\u7684\u76C2\u76F2\u76F4\u77E5\u77FD\u793E\u7940\u7941\u79C9\u79C8\u7A7A\u7A79\u7AFA\u7CFE\u7F54\u7F8C\u7F8B\u8005\u80BA\u80A5\u80A2\u80B1\u80A1\u80AB\u80A9\u80B4\u80AA\u80AF\u81E5\u81FE\u820D\u82B3\u829D\u8299\u82AD\u82BD\u829F\u82B9\u82B1\u82AC\u82A5\u82AF\u82B8\u82A3\u82B0\u82BE\u82B7\u864E\u8671\u521D\u8868\u8ECB\u8FCE\u8FD4\u8FD1\u90B5\u90B8\u90B1\u90B6\u91C7\u91D1\u9577\u9580\u961C\u9640\u963F\u963B\u9644\u9642\u96B9\u96E8\u9752\u975E\u4E9F\u4EAD\u4EAE\u4FE1\u4FB5\u4FAF\u4FBF\u4FE0\u4FD1\u4FCF\u4FDD\u4FC3\u4FB6\u4FD8\u4FDF\u4FCA\u4FD7\u4FAE\u4FD0\u4FC4\u4FC2\u4FDA\u4FCE\u4FDE\u4FB7\u5157\u5192\u5191\u51A0\u524E\u5243\u524A\u524D\u524C\u524B\u5247\u52C7\u52C9\u52C3\u52C1\u530D\u5357\u537B\u539A\u53DB\u54AC\u54C0\u54A8\u54CE\u54C9\u54B8\u54A6\u54B3\u54C7\u54C2\u54BD\u54AA\u54C1\u54C4\u54C8\u54AF\u54AB\u54B1\u54BB\u54A9\u54A7\u54BF\u56FF\u5782\u578B\u57A0\u57A3\u57A2\u57CE\u57AE\u5793\u5955\u5951\u594F\u594E\u5950\u59DC\u59D8\u59FF\u59E3\u59E8\u5A03\u59E5\u59EA\u59DA\u59E6\u5A01\u59FB\u5B69\u5BA3\u5BA6\u5BA4\u5BA2\u5BA5\u5C01\u5C4E\u5C4F\u5C4D\u5C4B\u5CD9\u5CD2\u5DF7\u5E1D\u5E25\u5E1F\u5E7D\u5EA0\u5EA6\u5EFA\u5F08\u5F2D\u5F65\u5F88\u5F85\u5F8A\u5F8B\u5F87\u5F8C\u5F89\u6012\u601D\u6020\u6025\u600E\u6028\u604D\u6070\u6068\u6062\u6046\u6043\u606C\u606B\u606A\u6064\u6241\u62DC\u6316\u6309\u62FC\u62ED\u6301\u62EE\u62FD\u6307\u62F1\u62F7\u62EF\u62EC\u62FE\u62F4\u6311\u6302\u653F\u6545\u65AB\u65BD\u65E2\u6625\u662D\u6620\u6627\u662F\u661F\u6628\u6631\u6624\u66F7\u67FF\u67D3\u67F1\u67D4\u67D0\u67EC\u67B6\u67AF\u67F5\u67E9\u67EF\u67C4\u67D1\u67B4\u67DA\u67E5\u67B8\u67CF\u67DE\u67F3\u67B0\u67D9\u67E2\u67DD\u67D2\u6B6A\u6B83\u6B86\u6BB5\u6BD2\u6BD7\u6C1F\u6CC9\u6D0B\u6D32\u6D2A\u6D41\u6D25\u6D0C\u6D31\u6D1E\u6D17\u6D3B\u6D3D\u6D3E\u6D36\u6D1B\u6CF5\u6D39\u6D27\u6D38\u6D29\u6D2E\u6D35\u6D0E\u6D2B\u70AB\u70BA\u70B3\u70AC\u70AF\u70AD\u70B8\u70AE\u70A4\u7230\u7272\u726F\u7274\u72E9\u72E0\u72E1\u73B7\u73CA\u73BB\u73B2\u73CD\u73C0\u73B3\u751A\u752D\u754F\u754C\u754E\u754B\u75AB\u75A4\u75A5\u75A2\u75A3\u7678\u7686\u7687\u7688\u76C8\u76C6\u76C3\u76C5\u7701\u76F9\u76F8\u7709\u770B\u76FE\u76FC\u7707\u77DC\u7802\u7814\u780C\u780D\u7946\u7949\u7948\u7947\u79B9\u79BA\u79D1\u79D2\u79CB\u7A7F\u7A81\u7AFF\u7AFD\u7C7D\u7D02\u7D05\u7D00\u7D09\u7D07\u7D04\u7D06\u7F38\u7F8E\u7FBF\u8004\u8010\u800D\u8011\u8036\u80D6\u80E5\u80DA\u80C3\u80C4\u80CC\u80E1\u80DB\u80CE\u80DE\u80E4\u80DD\u81F4\u8222\u82E7\u8303\u8305\u82E3\u82DB\u82E6\u8304\u82E5\u8302\u8309\u82D2\u82D7\u82F1\u8301\u82DC\u82D4\u82D1\u82DE\u82D3\u82DF\u82EF\u8306\u8650\u8679\u867B\u867A\u884D\u886B\u8981\u89D4\u8A08\u8A02\u8A03\u8C9E\u8CA0\u8D74\u8D73\u8DB4\u8ECD\u8ECC\u8FF0\u8FE6\u8FE2\u8FEA\u8FE5\u8FED\u8FEB\u8FE4\u8FE8\u90CA\u90CE\u90C1\u90C3\u914B\u914A\u91CD\u9582\u9650\u964B\u964C\u964D\u9762\u9769\u97CB\u97ED\u97F3\u9801\u98A8\u98DB\u98DF\u9996\u9999\u4E58\u4EB3\u500C\u500D\u5023\u4FEF\u5026\u5025\u4FF8\u5029\u5016\u5006\u503C\u501F\u501A\u5012\u5011\u4FFA\u5000\u5014\u5028\u4FF1\u5021\u500B\u5019\u5018\u4FF3\u4FEE\u502D\u502A\u4FFE\u502B\u5009\u517C\u51A4\u51A5\u51A2\u51CD\u51CC\u51C6\u51CB\u5256\u525C\u5254\u525B\u525D\u532A\u537F\u539F\u539D\u53DF\u54E8\u5510\u5501\u5537\u54FC\u54E5\u54F2\u5506\u54FA\u5514\u54E9\u54ED\u54E1\u5509\u54EE\u54EA\u54E6\u5527\u5507\u54FD\u550F\u5703\u5704\u57C2\u57D4\u57CB\u57C3\u5809\u590F\u5957\u5958\u595A\u5A11\u5A18\u5A1C\u5A1F\u5A1B\u5A13\u59EC\u5A20\u5A23\u5A29\u5A25\u5A0C\u5A09\u5B6B\u5C58\u5BB0\u5BB3\u5BB6\u5BB4\u5BAE\u5BB5\u5BB9\u5BB8\u5C04\u5C51\u5C55\u5C50\u5CED\u5CFD\u5CFB\u5CEA\u5CE8\u5CF0\u5CF6\u5D01\u5CF4\u5DEE\u5E2D\u5E2B\u5EAB\u5EAD\u5EA7\u5F31\u5F92\u5F91\u5F90\u6059\u6063\u6065\u6050\u6055\u606D\u6069\u606F\u6084\u609F\u609A\u608D\u6094\u608C\u6085\u6096\u6247\u62F3\u6308\u62FF\u634E\u633E\u632F\u6355\u6342\u6346\u634F\u6349\u633A\u6350\u633D\u632A\u632B\u6328\u634D\u634C\u6548\u6549\u6599\u65C1\u65C5\u6642\u6649\u664F\u6643\u6652\u664C\u6645\u6641\u66F8\u6714\u6715\u6717\u6821\u6838\u6848\u6846\u6853\u6839\u6842\u6854\u6829\u68B3\u6817\u684C\u6851\u683D\u67F4\u6850\u6840\u683C\u6843\u682A\u6845\u6813\u6818\u6841\u6B8A\u6B89\u6BB7\u6C23\u6C27\u6C28\u6C26\u6C24\u6CF0\u6D6A\u6D95\u6D88\u6D87\u6D66\u6D78\u6D77\u6D59\u6D93\u6D6C\u6D89\u6D6E\u6D5A\u6D74\u6D69\u6D8C\u6D8A\u6D79\u6D85\u6D65\u6D94\u70CA\u70D8\u70E4\u70D9\u70C8\u70CF\u7239\u7279\u72FC\u72F9\u72FD\u72F8\u72F7\u7386\u73ED\u7409\u73EE\u73E0\u73EA\u73DE\u7554\u755D\u755C\u755A\u7559\u75BE\u75C5\u75C7\u75B2\u75B3\u75BD\u75BC\u75B9\u75C2\u75B8\u768B\u76B0\u76CA\u76CD\u76CE\u7729\u771F\u7720\u7728\u77E9\u7830\u7827\u7838\u781D\u7834\u7837\u7825\u782D\u7820\u781F\u7832\u7955\u7950\u7960\u795F\u7956\u795E\u795D\u7957\u795A\u79E4\u79E3\u79E7\u79DF\u79E6\u79E9\u79D8\u7A84\u7A88\u7AD9\u7B06\u7B11\u7C89\u7D21\u7D17\u7D0B\u7D0A\u7D20\u7D22\u7D14\u7D10\u7D15\u7D1A\u7D1C\u7D0D\u7D19\u7D1B\u7F3A\u7F5F\u7F94\u7FC5\u7FC1\u8006\u8018\u8015\u8019\u8017\u803D\u803F\u80F1\u8102\u80F0\u8105\u80ED\u80F4\u8106\u80F8\u80F3\u8108\u80FD\u810A\u80FC\u80EF\u81ED\u81EC\u8200\u8210\u822A\u822B\u8228\u822C\u82BB\u832B\u8352\u8354\u834A\u8338\u8350\u8349\u8335\u8334\u834F\u8332\u8339\u8336\u8317\u8340\u8331\u8328\u8343\u8654\u868A\u86AA\u8693\u86A4\u86A9\u868C\u86A3\u869C\u8870\u8877\u8881\u8882\u887D\u8879\u8A18\u8A10\u8A0E\u8A0C\u8A15\u8A0A\u8A17\u8A13\u8A16\u8A0F\u8A11\u8C48\u8C7A\u8C79\u8CA1\u8CA2\u8D77\u8EAC\u8ED2\u8ED4\u8ECF\u8FB1\u9001\u9006\u8FF7\u9000\u8FFA\u8FF4\u9003\u8FFD\u9005\u8FF8\u9095\u90E1\u90DD\u90E2\u9152\u914D\u914C\u91D8\u91DD\u91D7\u91DC\u91D9\u9583\u9662\u9663\u9661\u965B\u965D\u9664\u9658\u965E\u96BB\u98E2\u99AC\u9AA8\u9AD8\u9B25\u9B32\u9B3C\u4E7E\u507A\u507D\u505C\u5047\u5043\u504C\u505A\u5049\u5065\u5076\u504E\u5055\u5075\u5074\u5077\u504F\u500F\u506F\u506D\u515C\u5195\u51F0\u526A\u526F\u52D2\u52D9\u52D8\u52D5\u5310\u530F\u5319\u533F\u5340\u533E\u53C3\u66FC\u5546\u556A\u5566\u5544\u555E\u5561\u5543\u554A\u5531\u5556\u554F\u5555\u552F\u5564\u5538\u552E\u555C\u552C\u5563\u5533\u5541\u5557\u5708\u570B\u5709\u57DF\u5805\u580A\u5806\u57E0\u57E4\u57FA\u5802\u5835\u57F7\u57F9\u5920\u5962\u5A36\u5A41\u5A49\u5A66\u5A6A\u5A40\u5A3C\u5A62\u5A5A\u5A46\u5A4A\u5B70\u5BC7\u5BC5\u5BC4\u5BC2\u5BBF\u5BC6\u5C09\u5C08\u5C07\u5C60\u5C5C\u5C5D\u5D07\u5D06\u5D0E\u5D1B\u5D16\u5D22\u5D11\u5D29\u5D14\u5D19\u5D24\u5D27\u5D17\u5DE2\u5E38\u5E36\u5E33\u5E37\u5EB7\u5EB8\u5EB6\u5EB5\u5EBE\u5F35\u5F37\u5F57\u5F6C\u5F69\u5F6B\u5F97\u5F99\u5F9E\u5F98\u5FA1\u5FA0\u5F9C\u607F\u60A3\u6089\u60A0\u60A8\u60CB\u60B4\u60E6\u60BD\u60C5\u60BB\u60B5\u60DC\u60BC\u60D8\u60D5\u60C6\u60DF\u60B8\u60DA\u60C7\u621A\u621B\u6248\u63A0\u63A7\u6372\u6396\u63A2\u63A5\u6377\u6367\u6398\u63AA\u6371\u63A9\u6389\u6383\u639B\u636B\u63A8\u6384\u6388\u6399\u63A1\u63AC\u6392\u638F\u6380\u637B\u6369\u6368\u637A\u655D\u6556\u6551\u6559\u6557\u555F\u654F\u6558\u6555\u6554\u659C\u659B\u65AC\u65CF\u65CB\u65CC\u65CE\u665D\u665A\u6664\u6668\u6666\u665E\u66F9\u52D7\u671B\u6881\u68AF\u68A2\u6893\u68B5\u687F\u6876\u68B1\u68A7\u6897\u68B0\u6883\u68C4\u68AD\u6886\u6885\u6894\u689D\u68A8\u689F\u68A1\u6882\u6B32\u6BBA\u6BEB\u6BEC\u6C2B\u6D8E\u6DBC\u6DF3\u6DD9\u6DB2\u6DE1\u6DCC\u6DE4\u6DFB\u6DFA\u6E05\u6DC7\u6DCB\u6DAF\u6DD1\u6DAE\u6DDE\u6DF9\u6DB8\u6DF7\u6DF5\u6DC5\u6DD2\u6E1A\u6DB5\u6DDA\u6DEB\u6DD8\u6DEA\u6DF1\u6DEE\u6DE8\u6DC6\u6DC4\u6DAA\u6DEC\u6DBF\u6DE6\u70F9\u7109\u710A\u70FD\u70EF\u723D\u727D\u7281\u731C\u731B\u7316\u7313\u7319\u7387\u7405\u740A\u7403\u7406\u73FE\u740D\u74E0\u74F6\u74F7\u751C\u7522\u7565\u7566\u7562\u7570\u758F\u75D4\u75D5\u75B5\u75CA\u75CD\u768E\u76D4\u76D2\u76DB\u7737\u773E\u773C\u7736\u7738\u773A\u786B\u7843\u784E\u7965\u7968\u796D\u79FB\u7A92\u7A95\u7B20\u7B28\u7B1B\u7B2C\u7B26\u7B19\u7B1E\u7B2E\u7C92\u7C97\u7C95\u7D46\u7D43\u7D71\u7D2E\u7D39\u7D3C\u7D40\u7D30\u7D33\u7D44\u7D2F\u7D42\u7D32\u7D31\u7F3D\u7F9E\u7F9A\u7FCC\u7FCE\u7FD2\u801C\u804A\u8046\u812F\u8116\u8123\u812B\u8129\u8130\u8124\u8202\u8235\u8237\u8236\u8239\u838E\u839E\u8398\u8378\u83A2\u8396\u83BD\u83AB\u8392\u838A\u8393\u8389\u83A0\u8377\u837B\u837C\u8386\u83A7\u8655\u5F6A\u86C7\u86C0\u86B6\u86C4\u86B5\u86C6\u86CB\u86B1\u86AF\u86C9\u8853\u889E\u8888\u88AB\u8892\u8896\u888D\u888B\u8993\u898F\u8A2A\u8A1D\u8A23\u8A25\u8A31\u8A2D\u8A1F\u8A1B\u8A22\u8C49\u8C5A\u8CA9\u8CAC\u8CAB\u8CA8\u8CAA\u8CA7\u8D67\u8D66\u8DBE\u8DBA\u8EDB\u8EDF\u9019\u900D\u901A\u9017\u9023\u901F\u901D\u9010\u9015\u901E\u9020\u900F\u9022\u9016\u901B\u9014\u90E8\u90ED\u90FD\u9157\u91CE\u91F5\u91E6\u91E3\u91E7\u91ED\u91E9\u9589\u966A\u9675\u9673\u9678\u9670\u9674\u9676\u9677\u966C\u96C0\u96EA\u96E9\u7AE0\u7ADF\u9802\u9803\u9B5A\u9CE5\u9E75\u9E7F\u9EA5\u9EBB\u50A2\u508D\u5085\u5099\u5091\u5080\u5096\u5098\u509A\u6700\u51F1\u5272\u5274\u5275\u5269\u52DE\u52DD\u52DB\u535A\u53A5\u557B\u5580\u55A7\u557C\u558A\u559D\u5598\u5582\u559C\u55AA\u5594\u5587\u558B\u5583\u55B3\u55AE\u559F\u553E\u55B2\u559A\u55BB\u55AC\u55B1\u557E\u5589\u55AB\u5599\u570D\u582F\u582A\u5834\u5824\u5830\u5831\u5821\u581D\u5820\u58F9\u58FA\u5960\u5A77\u5A9A\u5A7F\u5A92\u5A9B\u5AA7\u5B73\u5B71\u5BD2\u5BCC\u5BD3\u5BD0\u5C0A\u5C0B\u5C31\u5D4C\u5D50\u5D34\u5D47\u5DFD\u5E45\u5E3D\u5E40\u5E43\u5E7E\u5ECA\u5EC1\u5EC2\u5EC4\u5F3C\u5F6D\u5FA9\u5FAA\u5FA8\u60D1\u60E1\u60B2\u60B6\u60E0\u611C\u6123\u60FA\u6115\u60F0\u60FB\u60F4\u6168\u60F1\u610E\u60F6\u6109\u6100\u6112\u621F\u6249\u63A3\u638C\u63CF\u63C0\u63E9\u63C9\u63C6\u63CD\u63D2\u63E3\u63D0\u63E1\u63D6\u63ED\u63EE\u6376\u63F4\u63EA\u63DB\u6452\u63DA\u63F9\u655E\u6566\u6562\u6563\u6591\u6590\u65AF\u666E\u6670\u6674\u6676\u666F\u6691\u667A\u667E\u6677\u66FE\u66FF\u671F\u671D\u68FA\u68D5\u68E0\u68D8\u68D7\u6905\u68DF\u68F5\u68EE\u68E7\u68F9\u68D2\u68F2\u68E3\u68CB\u68CD\u690D\u6912\u690E\u68C9\u68DA\u696E\u68FB\u6B3E\u6B3A\u6B3D\u6B98\u6B96\u6BBC\u6BEF\u6C2E\u6C2F\u6C2C\u6E2F\u6E38\u6E54\u6E21\u6E32\u6E67\u6E4A\u6E20\u6E25\u6E23\u6E1B\u6E5B\u6E58\u6E24\u6E56\u6E6E\u6E2D\u6E26\u6E6F\u6E34\u6E4D\u6E3A\u6E2C\u6E43\u6E1D\u6E3E\u6ECB\u6E89\u6E19\u6E4E\u6E63\u6E44\u6E72\u6E69\u6E5F\u7119\u711A\u7126\u7130\u7121\u7136\u716E\u711C\u724C\u7284\u7280\u7336\u7325\u7334\u7329\u743A\u742A\u7433\u7422\u7425\u7435\u7436\u7434\u742F\u741B\u7426\u7428\u7525\u7526\u756B\u756A\u75E2\u75DB\u75E3\u75D9\u75D8\u75DE\u75E0\u767B\u767C\u7696\u7693\u76B4\u76DC\u774F\u77ED\u785D\u786C\u786F\u7A0D\u7A08\u7A0B\u7A05\u7A00\u7A98\u7A97\u7A96\u7AE5\u7AE3\u7B49\u7B56\u7B46\u7B50\u7B52\u7B54\u7B4D\u7B4B\u7B4F\u7B51\u7C9F\u7CA5\u7D5E\u7D50\u7D68\u7D55\u7D2B\u7D6E\u7D72\u7D61\u7D66\u7D62\u7D70\u7D73\u5584\u7FD4\u7FD5\u800B\u8052\u8085\u8155\u8154\u814B\u8151\u814E\u8139\u8146\u813E\u814C\u8153\u8174\u8212\u821C\u83E9\u8403\u83F8\u840D\u83E0\u83C5\u840B\u83C1\u83EF\u83F1\u83F4\u8457\u840A\u83F0\u840C\u83CC\u83FD\u83F2\u83CA\u8438\u840E\u8404\u83DC\u8407\u83D4\u83DF\u865B\u86DF\u86D9\u86ED\u86D4\u86DB\u86E4\u86D0\u86DE\u8857\u88C1\u88C2\u88B1\u8983\u8996\u8A3B\u8A60\u8A55\u8A5E\u8A3C\u8A41\u8A54\u8A5B\u8A50\u8A46\u8A34\u8A3A\u8A36\u8A56\u8C61\u8C82\u8CAF\u8CBC\u8CB3\u8CBD\u8CC1\u8CBB\u8CC0\u8CB4\u8CB7\u8CB6\u8CBF\u8CB8\u8D8A\u8D85\u8D81\u8DCE\u8DDD\u8DCB\u8DDA\u8DD1\u8DCC\u8DDB\u8DC6\u8EFB\u8EF8\u8EFC\u8F9C\u902E\u9035\u9031\u9038\u9032\u9036\u9102\u90F5\u9109\u90FE\u9163\u9165\u91CF\u9214\u9215\u9223\u9209\u921E\u920D\u9210\u9207\u9211\u9594\u958F\u958B\u9591\u9593\u9592\u958E\u968A\u968E\u968B\u967D\u9685\u9686\u968D\u9672\u9684\u96C1\u96C5\u96C4\u96C6\u96C7\u96EF\u96F2\u97CC\u9805\u9806\u9808\u98E7\u98EA\u98EF\u98E9\u98F2\u98ED\u99AE\u99AD\u9EC3\u9ECD\u9ED1\u4E82\u50AD\u50B5\u50B2\u50B3\u50C5\u50BE\u50AC\u50B7\u50BB\u50AF\u50C7\u527F\u5277\u527D\u52DF\u52E6\u52E4\u52E2\u52E3\u532F\u55DF\u55E8\u55D3\u55E6\u55CE\u55DC\u55C7\u55D1\u55E3\u55E4\u55EF\u55DA\u55E1\u55C5\u55C6\u55E5\u55C9\u5712\u5713\u585E\u5851\u5858\u5857\u585A\u5854\u586B\u584C\u586D\u584A\u5862\u5852\u584B\u5967\u5AC1\u5AC9\u5ACC\u5ABE\u5ABD\u5ABC\u5AB3\u5AC2\u5AB2\u5D69\u5D6F\u5E4C\u5E79\u5EC9\u5EC8\u5F12\u5F59\u5FAC\u5FAE\u611A\u610F\u6148\u611F\u60F3\u611B\u60F9\u6101\u6108\u614E\u614C\u6144\u614D\u613E\u6134\u6127\u610D\u6106\u6137\u6221\u6222\u6413\u643E\u641E\u642A\u642D\u643D\u642C\u640F\u641C\u6414\u640D\u6436\u6416\u6417\u6406\u656C\u659F\u65B0\u6697\u6689\u6687\u6688\u6696\u6684\u6698\u668D\u6703\u6994\u696D\u695A\u6977\u6960\u6954\u6975\u6930\u6982\u694A\u6968\u696B\u695E\u6953\u6979\u6986\u695D\u6963\u695B\u6B47\u6B72\u6BC0\u6BBF\u6BD3\u6BFD\u6EA2\u6EAF\u6ED3\u6EB6\u6EC2\u6E90\u6E9D\u6EC7\u6EC5\u6EA5\u6E98\u6EBC\u6EBA\u6EAB\u6ED1\u6E96\u6E9C\u6EC4\u6ED4\u6EAA\u6EA7\u6EB4\u714E\u7159\u7169\u7164\u7149\u7167\u715C\u716C\u7166\u714C\u7165\u715E\u7146\u7168\u7156\u723A\u7252\u7337\u7345\u733F\u733E\u746F\u745A\u7455\u745F\u745E\u7441\u743F\u7459\u745B\u745C\u7576\u7578\u7600\u75F0\u7601\u75F2\u75F1\u75FA\u75FF\u75F4\u75F3\u76DE\u76DF\u775B\u776B\u7766\u775E\u7763\u7779\u776A\u776C\u775C\u7765\u7768\u7762\u77EE\u788E\u78B0\u7897\u7898\u788C\u7889\u787C\u7891\u7893\u787F\u797A\u797F\u7981\u842C\u79BD\u7A1C\u7A1A\u7A20\u7A14\u7A1F\u7A1E\u7A9F\u7AA0\u7B77\u7BC0\u7B60\u7B6E\u7B67\u7CB1\u7CB3\u7CB5\u7D93\u7D79\u7D91\u7D81\u7D8F\u7D5B\u7F6E\u7F69\u7F6A\u7F72\u7FA9\u7FA8\u7FA4\u8056\u8058\u8086\u8084\u8171\u8170\u8178\u8165\u816E\u8173\u816B\u8179\u817A\u8166\u8205\u8247\u8482\u8477\u843D\u8431\u8475\u8466\u846B\u8449\u846C\u845B\u843C\u8435\u8461\u8463\u8469\u846D\u8446\u865E\u865C\u865F\u86F9\u8713\u8708\u8707\u8700\u86FE\u86FB\u8702\u8703\u8706\u870A\u8859\u88DF\u88D4\u88D9\u88DC\u88D8\u88DD\u88E1\u88CA\u88D5\u88D2\u899C\u89E3\u8A6B\u8A72\u8A73\u8A66\u8A69\u8A70\u8A87\u8A7C\u8A63\u8AA0\u8A71\u8A85\u8A6D\u8A62\u8A6E\u8A6C\u8A79\u8A7B\u8A3E\u8A68\u8C62\u8C8A\u8C89\u8CCA\u8CC7\u8CC8\u8CC4\u8CB2\u8CC3\u8CC2\u8CC5\u8DE1\u8DDF\u8DE8\u8DEF\u8DF3\u8DFA\u8DEA\u8DE4\u8DE6\u8EB2\u8F03\u8F09\u8EFE\u8F0A\u8F9F\u8FB2\u904B\u904A\u9053\u9042\u9054\u903C\u9055\u9050\u9047\u904F\u904E\u904D\u9051\u903E\u9041\u9112\u9117\u916C\u916A\u9169\u91C9\u9237\u9257\u9238\u923D\u9240\u923E\u925B\u924B\u9264\u9251\u9234\u9249\u924D\u9245\u9239\u923F\u925A\u9598\u9698\u9694\u9695\u96CD\u96CB\u96C9\u96CA\u96F7\u96FB\u96F9\u96F6\u9756\u9774\u9776\u9810\u9811\u9813\u980A\u9812\u980C\u98FC\u98F4\u98FD\u98FE\u99B3\u99B1\u99B4\u9AE1\u9CE9\u9E82\u9F0E\u9F13\u9F20\u50E7\u50EE\u50E5\u50D6\u50ED\u50DA\u50D5\u50CF\u50D1\u50F1\u50CE\u50E9\u5162\u51F3\u5283\u5282\u5331\u53AD\u55FE\u5600\u561B\u5617\u55FD\u5614\u5606\u5609\u560D\u560E\u55F7\u5616\u561F\u5608\u5610\u55F6\u5718\u5716\u5875\u587E\u5883\u5893\u588A\u5879\u5885\u587D\u58FD\u5925\u5922\u5924\u596A\u5969\u5AE1\u5AE6\u5AE9\u5AD7\u5AD6\u5AD8\u5AE3\u5B75\u5BDE\u5BE7\u5BE1\u5BE5\u5BE6\u5BE8\u5BE2\u5BE4\u5BDF\u5C0D\u5C62\u5D84\u5D87\u5E5B\u5E63\u5E55\u5E57\u5E54\u5ED3\u5ED6\u5F0A\u5F46\u5F70\u5FB9\u6147\u613F\u614B\u6177\u6162\u6163\u615F\u615A\u6158\u6175\u622A\u6487\u6458\u6454\u64A4\u6478\u645F\u647A\u6451\u6467\u6434\u646D\u647B\u6572\u65A1\u65D7\u65D6\u66A2\u66A8\u669D\u699C\u69A8\u6995\u69C1\u69AE\u69D3\u69CB\u699B\u69B7\u69BB\u69AB\u69B4\u69D0\u69CD\u69AD\u69CC\u69A6\u69C3\u69A3\u6B49\u6B4C\u6C33\u6F33\u6F14\u6EFE\u6F13\u6EF4\u6F29\u6F3E\u6F20\u6F2C\u6F0F\u6F02\u6F22\u6EFF\u6EEF\u6F06\u6F31\u6F38\u6F32\u6F23\u6F15\u6F2B\u6F2F\u6F88\u6F2A\u6EEC\u6F01\u6EF2\u6ECC\u6EF7\u7194\u7199\u717D\u718A\u7184\u7192\u723E\u7292\u7296\u7344\u7350\u7464\u7463\u746A\u7470\u746D\u7504\u7591\u7627\u760D\u760B\u7609\u7613\u76E1\u76E3\u7784\u777D\u777F\u7761\u78C1\u789F\u78A7\u78B3\u78A9\u78A3\u798E\u798F\u798D\u7A2E\u7A31\u7AAA\u7AA9\u7AED\u7AEF\u7BA1\u7B95\u7B8B\u7B75\u7B97\u7B9D\u7B94\u7B8F\u7BB8\u7B87\u7B84\u7CB9\u7CBD\u7CBE\u7DBB\u7DB0\u7D9C\u7DBD\u7DBE\u7DA0\u7DCA\u7DB4\u7DB2\u7DB1\u7DBA\u7DA2\u7DBF\u7DB5\u7DB8\u7DAD\u7DD2\u7DC7\u7DAC\u7F70\u7FE0\u7FE1\u7FDF\u805E\u805A\u8087\u8150\u8180\u818F\u8188\u818A\u817F\u8182\u81E7\u81FA\u8207\u8214\u821E\u824B\u84C9\u84BF\u84C6\u84C4\u8499\u849E\u84B2\u849C\u84CB\u84B8\u84C0\u84D3\u8490\u84BC\u84D1\u84CA\u873F\u871C\u873B\u8722\u8725\u8734\u8718\u8755\u8737\u8729\u88F3\u8902\u88F4\u88F9\u88F8\u88FD\u88E8\u891A\u88EF\u8AA6\u8A8C\u8A9E\u8AA3\u8A8D\u8AA1\u8A93\u8AA4\u8AAA\u8AA5\u8AA8\u8A98\u8A91\u8A9A\u8AA7\u8C6A\u8C8D\u8C8C\u8CD3\u8CD1\u8CD2\u8D6B\u8D99\u8D95\u8DFC\u8F14\u8F12\u8F15\u8F13\u8FA3\u9060\u9058\u905C\u9063\u9059\u905E\u9062\u905D\u905B\u9119\u9118\u911E\u9175\u9178\u9177\u9174\u9278\u9280\u9285\u9298\u9296\u927B\u9293\u929C\u92A8\u927C\u9291\u95A1\u95A8\u95A9\u95A3\u95A5\u95A4\u9699\u969C\u969B\u96CC\u96D2\u9700\u977C\u9785\u97F6\u9817\u9818\u98AF\u98B1\u9903\u9905\u990C\u9909\u99C1\u9AAF\u9AB0\u9AE6\u9B41\u9B42\u9CF4\u9CF6\u9CF3\u9EBC\u9F3B\u9F4A\u5104\u5100\u50FB\u50F5\u50F9\u5102\u5108\u5109\u5105\u51DC\u5287\u5288\u5289\u528D\u528A\u52F0\u53B2\u562E\u563B\u5639\u5632\u563F\u5634\u5629\u5653\u564E\u5657\u5674\u5636\u562F\u5630\u5880\u589F\u589E\u58B3\u589C\u58AE\u58A9\u58A6\u596D\u5B09\u5AFB\u5B0B\u5AF5\u5B0C\u5B08\u5BEE\u5BEC\u5BE9\u5BEB\u5C64\u5C65\u5D9D\u5D94\u5E62\u5E5F\u5E61\u5EE2\u5EDA\u5EDF\u5EDD\u5EE3\u5EE0\u5F48\u5F71\u5FB7\u5FB5\u6176\u6167\u616E\u615D\u6155\u6182\u617C\u6170\u616B\u617E\u61A7\u6190\u61AB\u618E\u61AC\u619A\u61A4\u6194\u61AE\u622E\u6469\u646F\u6479\u649E\u64B2\u6488\u6490\u64B0\u64A5\u6493\u6495\u64A9\u6492\u64AE\u64AD\u64AB\u649A\u64AC\u6499\u64A2\u64B3\u6575\u6577\u6578\u66AE\u66AB\u66B4\u66B1\u6A23\u6A1F\u69E8\u6A01\u6A1E\u6A19\u69FD\u6A21\u6A13\u6A0A\u69F3\u6A02\u6A05\u69ED\u6A11\u6B50\u6B4E\u6BA4\u6BC5\u6BC6\u6F3F\u6F7C\u6F84\u6F51\u6F66\u6F54\u6F86\u6F6D\u6F5B\u6F78\u6F6E\u6F8E\u6F7A\u6F70\u6F64\u6F97\u6F58\u6ED5\u6F6F\u6F60\u6F5F\u719F\u71AC\u71B1\u71A8\u7256\u729B\u734E\u7357\u7469\u748B\u7483\u747E\u7480\u757F\u7620\u7629\u761F\u7624\u7626\u7621\u7622\u769A\u76BA\u76E4\u778E\u7787\u778C\u7791\u778B\u78CB\u78C5\u78BA\u78CA\u78BE\u78D5\u78BC\u78D0\u7A3F\u7A3C\u7A40\u7A3D\u7A37\u7A3B\u7AAF\u7AAE\u7BAD\u7BB1\u7BC4\u7BB4\u7BC6\u7BC7\u7BC1\u7BA0\u7BCC\u7CCA\u7DE0\u7DF4\u7DEF\u7DFB\u7DD8\u7DEC\u7DDD\u7DE8\u7DE3\u7DDA\u7DDE\u7DE9\u7D9E\u7DD9\u7DF2\u7DF9\u7F75\u7F77\u7FAF\u7FE9\u8026\u819B\u819C\u819D\u81A0\u819A\u8198\u8517\u853D\u851A\u84EE\u852C\u852D\u8513\u8511\u8523\u8521\u8514\u84EC\u8525\u84FF\u8506\u8782\u8774\u8776\u8760\u8766\u8778\u8768\u8759\u8757\u874C\u8753\u885B\u885D\u8910\u8907\u8912\u8913\u8915\u890A\u8ABC\u8AD2\u8AC7\u8AC4\u8A95\u8ACB\u8AF8\u8AB2\u8AC9\u8AC2\u8ABF\u8AB0\u8AD6\u8ACD\u8AB6\u8AB9\u8ADB\u8C4C\u8C4E\u8C6C\u8CE0\u8CDE\u8CE6\u8CE4\u8CEC\u8CED\u8CE2\u8CE3\u8CDC\u8CEA\u8CE1\u8D6D\u8D9F\u8DA3\u8E2B\u8E10\u8E1D\u8E22\u8E0F\u8E29\u8E1F\u8E21\u8E1E\u8EBA\u8F1D\u8F1B\u8F1F\u8F29\u8F26\u8F2A\u8F1C\u8F1E\u8F25\u9069\u906E\u9068\u906D\u9077\u9130\u912D\u9127\u9131\u9187\u9189\u918B\u9183\u92C5\u92BB\u92B7\u92EA\u92AC\u92E4\u92C1\u92B3\u92BC\u92D2\u92C7\u92F0\u92B2\u95AD\u95B1\u9704\u9706\u9707\u9709\u9760\u978D\u978B\u978F\u9821\u982B\u981C\u98B3\u990A\u9913\u9912\u9918\u99DD\u99D0\u99DF\u99DB\u99D1\u99D5\u99D2\u99D9\u9AB7\u9AEE\u9AEF\u9B27\u9B45\u9B44\u9B77\u9B6F\u9D06\u9D09\u9D03\u9EA9\u9EBE\u9ECE\u58A8\u9F52\u5112\u5118\u5114\u5110\u5115\u5180\u51AA\u51DD\u5291\u5293\u52F3\u5659\u566B\u5679\u5669\u5664\u5678\u566A\u5668\u5665\u5671\u566F\u566C\u5662\u5676\u58C1\u58BE\u58C7\u58C5\u596E\u5B1D\u5B34\u5B78\u5BF0\u5C0E\u5F4A\u61B2\u6191\u61A9\u618A\u61CD\u61B6\u61BE\u61CA\u61C8\u6230\u64C5\u64C1\u64CB\u64BB\u64BC\u64DA\u64C4\u64C7\u64C2\u64CD\u64BF\u64D2\u64D4\u64BE\u6574\u66C6\u66C9\u66B9\u66C4\u66C7\u66B8\u6A3D\u6A38\u6A3A\u6A59\u6A6B\u6A58\u6A39\u6A44\u6A62\u6A61\u6A4B\u6A47\u6A35\u6A5F\u6A48\u6B59\u6B77\u6C05\u6FC2\u6FB1\u6FA1\u6FC3\u6FA4\u6FC1\u6FA7\u6FB3\u6FC0\u6FB9\u6FB6\u6FA6\u6FA0\u6FB4\u71BE\u71C9\u71D0\u71D2\u71C8\u71D5\u71B9\u71CE\u71D9\u71DC\u71C3\u71C4\u7368\u749C\u74A3\u7498\u749F\u749E\u74E2\u750C\u750D\u7634\u7638\u763A\u76E7\u76E5\u77A0\u779E\u779F\u77A5\u78E8\u78DA\u78EC\u78E7\u79A6\u7A4D\u7A4E\u7A46\u7A4C\u7A4B\u7ABA\u7BD9\u7C11\u7BC9\u7BE4\u7BDB\u7BE1\u7BE9\u7BE6\u7CD5\u7CD6\u7E0A\u7E11\u7E08\u7E1B\u7E23\u7E1E\u7E1D\u7E09\u7E10\u7F79\u7FB2\u7FF0\u7FF1\u7FEE\u8028\u81B3\u81A9\u81A8\u81FB\u8208\u8258\u8259\u854A\u8559\u8548\u8568\u8569\u8543\u8549\u856D\u856A\u855E\u8783\u879F\u879E\u87A2\u878D\u8861\u892A\u8932\u8925\u892B\u8921\u89AA\u89A6\u8AE6\u8AFA\u8AEB\u8AF1\u8B00\u8ADC\u8AE7\u8AEE\u8AFE\u8B01\u8B02\u8AF7\u8AED\u8AF3\u8AF6\u8AFC\u8C6B\u8C6D\u8C93\u8CF4\u8E44\u8E31\u8E34\u8E42\u8E39\u8E35\u8F3B\u8F2F\u8F38\u8F33\u8FA8\u8FA6\u9075\u9074\u9078\u9072\u907C\u907A\u9134\u9192\u9320\u9336\u92F8\u9333\u932F\u9322\u92FC\u932B\u9304\u931A\u9310\u9326\u9321\u9315\u932E\u9319\u95BB\u96A7\u96A8\u96AA\u96D5\u970E\u9711\u9716\u970D\u9713\u970F\u975B\u975C\u9766\u9798\u9830\u9838\u983B\u9837\u982D\u9839\u9824\u9910\u9928\u991E\u991B\u9921\u991A\u99ED\u99E2\u99F1\u9AB8\u9ABC\u9AFB\u9AED\u9B28\u9B91\u9D15\u9D23\u9D26\u9D28\u9D12\u9D1B\u9ED8\u9ED4\u9F8D\u9F9C\u512A\u511F\u5121\u5132\u52F5\u568E\u5680\u5690\u5685\u5687\u568F\u58D5\u58D3\u58D1\u58CE\u5B30\u5B2A\u5B24\u5B7A\u5C37\u5C68\u5DBC\u5DBA\u5DBD\u5DB8\u5E6B\u5F4C\u5FBD\u61C9\u61C2\u61C7\u61E6\u61CB\u6232\u6234\u64CE\u64CA\u64D8\u64E0\u64F0\u64E6\u64EC\u64F1\u64E2\u64ED\u6582\u6583\u66D9\u66D6\u6A80\u6A94\u6A84\u6AA2\u6A9C\u6ADB\u6AA3\u6A7E\u6A97\u6A90\u6AA0\u6B5C\u6BAE\u6BDA\u6C08\u6FD8\u6FF1\u6FDF\u6FE0\u6FDB\u6FE4\u6FEB\u6FEF\u6F80\u6FEC\u6FE1\u6FE9\u6FD5\u6FEE\u6FF0\u71E7\u71DF\u71EE\u71E6\u71E5\u71ED\u71EC\u71F4\u71E0\u7235\u7246\u7370\u7372\u74A9\u74B0\u74A6\u74A8\u7646\u7642\u764C\u76EA\u77B3\u77AA\u77B0\u77AC\u77A7\u77AD\u77EF\u78F7\u78FA\u78F4\u78EF\u7901\u79A7\u79AA\u7A57\u7ABF\u7C07\u7C0D\u7BFE\u7BF7\u7C0C\u7BE0\u7CE0\u7CDC\u7CDE\u7CE2\u7CDF\u7CD9\u7CDD\u7E2E\u7E3E\u7E46\u7E37\u7E32\u7E43\u7E2B\u7E3D\u7E31\u7E45\u7E41\u7E34\u7E39\u7E48\u7E35\u7E3F\u7E2F\u7F44\u7FF3\u7FFC\u8071\u8072\u8070\u806F\u8073\u81C6\u81C3\u81BA\u81C2\u81C0\u81BF\u81BD\u81C9\u81BE\u81E8\u8209\u8271\u85AA\u8584\u857E\u859C\u8591\u8594\u85AF\u859B\u8587\u85A8\u858A\u8667\u87C0\u87D1\u87B3\u87D2\u87C6\u87AB\u87BB\u87BA\u87C8\u87CB\u893B\u8936\u8944\u8938\u893D\u89AC\u8B0E\u8B17\u8B19\u8B1B\u8B0A\u8B20\u8B1D\u8B04\u8B10\u8C41\u8C3F\u8C73\u8CFA\u8CFD\u8CFC\u8CF8\u8CFB\u8DA8\u8E49\u8E4B\u8E48\u8E4A\u8F44\u8F3E\u8F42\u8F45\u8F3F\u907F\u907D\u9084\u9081\u9082\u9080\u9139\u91A3\u919E\u919C\u934D\u9382\u9328\u9375\u934A\u9365\u934B\u9318\u937E\u936C\u935B\u9370\u935A\u9354\u95CA\u95CB\u95CC\u95C8\u95C6\u96B1\u96B8\u96D6\u971C\u971E\u97A0\u97D3\u9846\u98B6\u9935\u9A01\u99FF\u9BAE\u9BAB\u9BAA\u9BAD\u9D3B\u9D3F\u9E8B\u9ECF\u9EDE\u9EDC\u9EDD\u9EDB\u9F3E\u9F4B\u53E2\u5695\u56AE\u58D9\u58D8\u5B38\u5F5D\u61E3\u6233\u64F4\u64F2\u64FE\u6506\u64FA\u64FB\u64F7\u65B7\u66DC\u6726\u6AB3\u6AAC\u6AC3\u6ABB\u6AB8\u6AC2\u6AAE\u6AAF\u6B5F\u6B78\u6BAF\u7009\u700B\u6FFE\u7006\u6FFA\u7011\u700F\u71FB\u71FC\u71FE\u71F8\u7377\u7375\u74A7\u74BF\u7515\u7656\u7658\u7652\u77BD\u77BF\u77BB\u77BC\u790E\u79AE\u7A61\u7A62\u7A60\u7AC4\u7AC5\u7C2B\u7C27\u7C2A\u7C1E\u7C23\u7C21\u7CE7\u7E54\u7E55\u7E5E\u7E5A\u7E61\u7E52\u7E59\u7F48\u7FF9\u7FFB\u8077\u8076\u81CD\u81CF\u820A\u85CF\u85A9\u85CD\u85D0\u85C9\u85B0\u85BA\u85B9\u85A6\u87EF\u87EC\u87F2\u87E0\u8986\u89B2\u89F4\u8B28\u8B39\u8B2C\u8B2B\u8C50\u8D05\u8E59\u8E63\u8E66\u8E64\u8E5F\u8E55\u8EC0\u8F49\u8F4D\u9087\u9083\u9088\u91AB\u91AC\u91D0\u9394\u938A\u9396\u93A2\u93B3\u93AE\u93AC\u93B0\u9398\u939A\u9397\u95D4\u95D6\u95D0\u95D5\u96E2\u96DC\u96D9\u96DB\u96DE\u9724\u97A3\u97A6\u97AD\u97F9\u984D\u984F\u984C\u984E\u9853\u98BA\u993E\u993F\u993D\u992E\u99A5\u9A0E\u9AC1\u9B03\u9B06\u9B4F\u9B4E\u9B4D\u9BCA\u9BC9\u9BFD\u9BC8\u9BC0\u9D51\u9D5D\u9D60\u9EE0\u9F15\u9F2C\u5133\u56A5\u58DE\u58DF\u58E2\u5BF5\u9F90\u5EEC\u61F2\u61F7\u61F6\u61F5\u6500\u650F\u66E0\u66DD\u6AE5\u6ADD\u6ADA\u6AD3\u701B\u701F\u7028\u701A\u701D\u7015\u7018\u7206\u720D\u7258\u72A2\u7378\u737A\u74BD\u74CA\u74E3\u7587\u7586\u765F\u7661\u77C7\u7919\u79B1\u7A6B\u7A69\u7C3E\u7C3F\u7C38\u7C3D\u7C37\u7C40\u7E6B\u7E6D\u7E79\u7E69\u7E6A\u7F85\u7E73\u7FB6\u7FB9\u7FB8\u81D8\u85E9\u85DD\u85EA\u85D5\u85E4\u85E5\u85F7\u87FB\u8805\u880D\u87F9\u87FE\u8960\u895F\u8956\u895E\u8B41\u8B5C\u8B58\u8B49\u8B5A\u8B4E\u8B4F\u8B46\u8B59\u8D08\u8D0A\u8E7C\u8E72\u8E87\u8E76\u8E6C\u8E7A\u8E74\u8F54\u8F4E\u8FAD\u908A\u908B\u91B1\u91AE\u93E1\u93D1\u93DF\u93C3\u93C8\u93DC\u93DD\u93D6\u93E2\u93CD\u93D8\u93E4\u93D7\u93E8\u95DC\u96B4\u96E3\u972A\u9727\u9761\u97DC\u97FB\u985E\u9858\u985B\u98BC\u9945\u9949\u9A16\u9A19\u9B0D\u9BE8\u9BE7\u9BD6\u9BDB\u9D89\u9D61\u9D72\u9D6A\u9D6C\u9E92\u9E97\u9E93\u9EB4\u52F8\u56A8\u56B7\u56B6\u56B4\u56BC\u58E4\u5B40\u5B43\u5B7D\u5BF6\u5DC9\u61F8\u61FA\u6518\u6514\u6519\u66E6\u6727\u6AEC\u703E\u7030\u7032\u7210\u737B\u74CF\u7662\u7665\u7926\u792A\u792C\u792B\u7AC7\u7AF6\u7C4C\u7C43\u7C4D\u7CEF\u7CF0\u8FAE\u7E7D\u7E7C\u7E82\u7F4C\u8000\u81DA\u8266\u85FB\u85F9\u8611\u85FA\u8606\u860B\u8607\u860A\u8814\u8815\u8964\u89BA\u89F8\u8B70\u8B6C\u8B66\u8B6F\u8B5F\u8B6B\u8D0F\u8D0D\u8E89\u8E81\u8E85\u8E82\u91B4\u91CB\u9418\u9403\u93FD\u95E1\u9730\u98C4\u9952\u9951\u99A8\u9A2B\u9A30\u9A37\u9A35\u9C13\u9C0D\u9E79\u9EB5\u9EE8\u9F2F\u9F5F\u9F63\u9F61\u5137\u5138\u56C1\u56C0\u56C2\u5914\u5C6C\u5DCD\u61FC\u61FE\u651D\u651C\u6595\u66E9\u6AFB\u6B04\u6AFA\u6BB2\u704C\u721B\u72A7\u74D6\u74D4\u7669\u77D3\u7C50\u7E8F\u7E8C\u7FBC\u8617\u862D\u861A\u8823\u8822\u8821\u881F\u896A\u896C\u89BD\u8B74\u8B77\u8B7D\u8D13\u8E8A\u8E8D\u8E8B\u8F5F\u8FAF\u91BA\u942E\u9433\u9435\u943A\u9438\u9432\u942B\u95E2\u9738\u9739\u9732\u97FF\u9867\u9865\u9957\u9A45\u9A43\u9A40\u9A3E\u9ACF\u9B54\u9B51\u9C2D\u9C25\u9DAF\u9DB4\u9DC2\u9DB8\u9E9D\u9EEF\u9F19\u9F5C\u9F66\u9F67\u513C\u513B\u56C8\u56CA\u56C9\u5B7F\u5DD4\u5DD2\u5F4E\u61FF\u6524\u6B0A\u6B61\u7051\u7058\u7380\u74E4\u758A\u766E\u766C\u79B3\u7C60\u7C5F\u807E\u807D\u81DF\u8972\u896F\u89FC\u8B80\u8D16\u8D17\u8E91\u8E93\u8F61\u9148\u9444\u9451\u9452\u973D\u973E\u97C3\u97C1\u986B\u9955\u9A55\u9A4D\u9AD2\u9B1A\u9C49\u9C31\u9C3E\u9C3B\u9DD3\u9DD7\u9F34\u9F6C\u9F6A\u9F94\u56CC\u5DD6\u6200\u6523\u652B\u652A\u66EC\u6B10\u74DA\u7ACA\u7C64\u7C63\u7C65\u7E93\u7E96\u7E94\u81E2\u8638\u863F\u8831\u8B8A\u9090\u908F\u9463\u9460\u9464\u9768\u986F\u995C\u9A5A\u9A5B\u9A57\u9AD3\u9AD4\u9AD1\u9C54\u9C57\u9C56\u9DE5\u9E9F\u9EF4\u56D1\u58E9\u652C\u705E\u7671\u7672\u77D7\u7F50\u7F88\u8836\u8839\u8862\u8B93\u8B92\u8B96\u8277\u8D1B\u91C0\u946A\u9742\u9748\u9744\u97C6\u9870\u9A5F\u9B22\u9B58\u9C5F\u9DF9\u9DFA\u9E7C\u9E7D\u9F07\u9F77\u9F72\u5EF3\u6B16\u7063\u7C6C\u7C6E\u883B\u89C0\u8EA1\u91C1\u9472\u9470\u9871\u995E\u9AD6\u9B23\u9ECC\u7064\u77DA\u8B9A\u9477\u97C9\u9A62\u9A65\u7E9C\u8B9C\u8EAA\u91C5\u947D\u947E\u947C\u9C77\u9C78\u9EF7\u8C54\u947F\u9E1A\u7228\u9A6A\u9B31\u9E1B\u9E1E\u7C72\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u2474\u2475\u2476\u2477\u2478\u2479\u247A\u247B\u247C\u247D\u2170\u2171\u2172\u2173\u2174\u2175\u2176\u2177\u2178\u2179\u4E36\u4E3F\u4E85\u4EA0\u5182\u5196\u51AB\u52F9\u5338\u5369\u53B6\u590A\u5B80\u5DDB\u2F33\u5E7F\u5EF4\u5F50\u5F61\u6534\u65E0\u7592\u7676\u8FB5\u96B6\u00A8\u02C6\u30FD\u30FE\u309D\u309E\u3003\u4EDD\u3005\u3006\u3007\u30FC\uFF3B\uFF3D\u273D\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048\u3049\u304A\u304B\u304C\u304D\u304E\u304F\u3050\u3051\u3052\u3053\u3054\u3055\u3056\u3057\u3058\u3059\u305A\u305B\u305C\u305D\u305E\u305F\u3060\u3061\u3062\u3063\u3064\u3065\u3066\u3067\u3068\u3069\u306A\u306B\u306C\u306D\u306E\u306F\u3070\u3071\u3072\u3073\u3074\u3075\u3076\u3077\u3078\u3079\u307A\u307B\u307C\u307D\u307E\u307F\u3080\u3081\u3082\u3083\u3084\u3085\u3086\u3087\u3088\u3089\u308A\u308B\u308C\u308D\u308E\u308F\u3090\u3091\u3092\u3093\u30A1\u30A2\u30A3\u30A4\u30A5\u30A6\u30A7\u30A8\u30A9\u30AA\u30AB\u30AC\u30AD\u30AE\u30AF\u30B0\u30B1\u30B2\u30B3\u30B4\u30B5\u30B6\u30B7\u30B8\u30B9\u30BA\u30BB\u30BC\u30BD\u30BE\u30BF\u30C0\u30C1\u30C2\u30C3\u30C4\u30C5\u30C6\u30C7\u30C8\u30C9\u30CA\u30CB\u30CC\u30CD\u30CE\u30CF\u30D0\u30D1\u30D2\u30D3\u30D4\u30D5\u30D6\u30D7\u30D8\u30D9\u30DA\u30DB\u30DC\u30DD\u30DE\u30DF\u30E0\u30E1\u30E2\u30E3\u30E4\u30E5\u30E6\u30E7\u30E8\u30E9\u30EA\u30EB\u30EC\u30ED\u30EE\u30EF\u30F0\u30F1\u30F2\u30F3\u30F4\u30F5\u30F6\u0410\u0411\u0412\u0413\u0414\u0415\u0401\u0416\u0417\u0418\u0419\u041A\u041B\u041C\u041D\u041E\u041F\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042A\u042B\u042C\u042D\u042E\u042F\u0430\u0431\u0432\u0433\u0434\u0435\u0451\u0436\u0437\u0438\u0439\u043A\u043B\u043C\u043D\u043E\u043F\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044A\u044B\u044C\u044D\u044E\u044F\u21E7\u21B8\u21B9\u31CF\u00CC\u4E5A\u008A\u5202\u4491\u9FB0\u5188\u9FB1\u7607";
+
+ private static final String TABLE4 = "\uFFE2\uFFE4\uFF07\uFF02\u3231\u2116\u2121\u309B\u309C\u2E80\u2E84\u2E86\u2E87\u2E88\u2E8A\u2E8C\u2E8D\u2E95\u2E9C\u2E9D\u2EA5\u2EA7\u2EAA\u2EAC\u2EAE\u2EB6\u2EBC\u2EBE\u2EC6\u2ECA\u2ECC\u2ECD\u2ECF\u2ED6\u2ED7\u2EDE\u2EE3\u0000\u0000\u0000\u0283\u0250\u025B\u0254\u0275\u0153\u00F8\u014B\u028A\u026A\u4E42\u4E5C\u51F5\u531A\u5382\u4E07\u4E0C\u4E47\u4E8D\u56D7\uFA0C\u5C6E\u5F73\u4E0F\u5187\u4E0E\u4E2E\u4E93\u4EC2\u4EC9\u4EC8\u5198\u52FC\u536C\u53B9\u5720\u5903\u592C\u5C10\u5DFF\u65E1\u6BB3\u6BCC\u6C14\u723F\u4E31\u4E3C\u4EE8\u4EDC\u4EE9\u4EE1\u4EDD\u4EDA\u520C\u531C\u534C\u5722\u5723\u5917\u592F\u5B81\u5B84\u5C12\u5C3B\u5C74\u5C73\u5E04\u5E80\u5E82\u5FC9\u6209\u6250\u6C15\u6C36\u6C43\u6C3F\u6C3B\u72AE\u72B0\u738A\u79B8\u808A\u961E\u4F0E\u4F18\u4F2C\u4EF5\u4F14\u4EF1\u4F00\u4EF7\u4F08\u4F1D\u4F02\u4F05\u4F22\u4F13\u4F04\u4EF4\u4F12\u51B1\u5213\u5209\u5210\u52A6\u5322\u531F\u534D\u538A\u5407\u56E1\u56DF\u572E\u572A\u5734\u593C\u5980\u597C\u5985\u597B\u597E\u5977\u597F\u5B56\u5C15\u5C25\u5C7C\u5C7A\u5C7B\u5C7E\u5DDF\u5E75\u5E84\u5F02\u5F1A\u5F74\u5FD5\u5FD4\u5FCF\u625C\u625E\u6264\u6261\u6266\u6262\u6259\u6260\u625A\u6265\u65EF\u65EE\u673E\u6739\u6738\u673B\u673A\u673F\u673C\u6733\u6C18\u6C46\u6C52\u6C5C\u6C4F\u6C4A\u6C54\u6C4B\u6C4C\u7071\u725E\u72B4\u72B5\u738E\u752A\u767F\u7A75\u7F51\u8278\u827C\u8280\u827D\u827F\u864D\u897E\u9099\u9097\u9098\u909B\u9094\u9622\u9624\u9620\u9623\u4F56\u4F3B\u4F62\u4F49\u4F53\u4F64\u4F3E\u4F67\u4F52\u4F5F\u4F41\u4F58\u4F2D\u4F33\u4F3F\u4F61\u518F\u51B9\u521C\u521E\u5221\u52AD\u52AE\u5309\u5363\u5372\u538E\u538F\u5430\u5437\u542A\u5454\u5445\u5419\u541C\u5425\u5418\u543D\u544F\u5441\u5428\u5424\u5447\u56EE\u56E7\u56E5\u5741\u5745\u574C\u5749\u574B\u5752\u5906\u5940\u59A6\u5998\u59A0\u5997\u598E\u59A2\u5990\u598F\u59A7\u59A1\u5B8E\u5B92\u5C28\u5C2A\u5C8D\u5C8F\u5C88\u5C8B\u5C89\u5C92\u5C8A\u5C86\u5C93\u5C95\u5DE0\u5E0A\u5E0E\u5E8B\u5E89\u5E8C\u5E88\u5E8D\u5F05\u5F1D\u5F78\u5F76\u5FD2\u5FD1\u5FD0\u5FED\u5FE8\u5FEE\u5FF3\u5FE1\u5FE4\u5FE3\u5FFA\u5FEF\u5FF7\u5FFB\u6000\u5FF4\u623A\u6283\u628C\u628E\u628F\u6294\u6287\u6271\u627B\u627A\u6270\u6281\u6288\u6277\u627D\u6272\u6274\u6537\u65F0\u65F4\u65F3\u65F2\u65F5\u6745\u6747\u6759\u6755\u674C\u6748\u675D\u674D\u675A\u674B\u6BD0\u6C19\u6C1A\u6C78\u6C67\u6C6B\u6C84\u6C8B\u6C8F\u6C71\u6C6F\u6C69\u6C9A\u6C6D\u6C87\u6C95\u6C9C\u6C66\u6C73\u6C65\u6C7B\u6C8E\u7074\u707A\u7263\u72BF\u72BD\u72C3\u72C6\u72C1\u72BA\u72C5\u7395\u7397\u7393\u7394\u7392\u753A\u7539\u7594\u7595\u7681\u793D\u8034\u8095\u8099\u8090\u8092\u809C\u8290\u828F\u8285\u828E\u8291\u8293\u828A\u8283\u8284\u8C78\u8FC9\u8FBF\u909F\u90A1\u90A5\u909E\u90A7\u90A0\u9630\u9628\u962F\u962D\u4E33\u4F98\u4F7C\u4F85\u4F7D\u4F80\u4F87\u4F76\u4F74\u4F89\u4F84\u4F77\u4F4C\u4F97\u4F6A\u4F9A\u4F79\u4F81\u4F78\u4F90\u4F9C\u4F94\u4F9E\u4F92\u4F82\u4F95\u4F6B\u4F6E\u519E\u51BC\u51BE\u5235\u5232\u5233\u5246\u5231\u52BC\u530A\u530B\u533C\u5392\u5394\u5487\u547F\u5481\u5491\u5482\u5488\u546B\u547A\u547E\u5465\u546C\u5474\u5466\u548D\u546F\u5461\u5460\u5498\u5463\u5467\u5464\u56F7\u56F9\u576F\u5772\u576D\u576B\u5771\u5770\u5776\u5780\u5775\u577B\u5773\u5774\u5762\u5768\u577D\u590C\u5945\u59B5\u59BA\u59CF\u59CE\u59B2\u59CC\u59C1\u59B6\u59BC\u59C3\u59D6\u59B1\u59BD\u59C0\u59C8\u59B4\u59C7\u5B62\u5B65\u5B93\u5B95\u5C44\u5C47\u5CAE\u5CA4\u5CA0\u5CB5\u5CAF\u5CA8\u5CAC\u5C9F\u5CA3\u5CAD\u5CA2\u5CAA\u5CA7\u5C9D\u5CA5\u5CB6\u5CB0\u5CA6\u5E17\u5E14\u5E19\u5F28\u5F22\u5F23\u5F24\u5F54\u5F82\u5F7E\u5F7D\u5FDE\u5FE5\u602D\u6026\u6019\u6032\u600B\u6034\u600A\u6017\u6033\u601A\u601E\u602C\u6022\u600D\u6010\u602E\u6013\u6011\u600C\u6009\u601C\u6214\u623D\u62AD\u62B4\u62D1\u62BE\u62AA\u62B6\u62CA\u62AE\u62B3\u62AF\u62BB\u62A9\u62B0\u62B8\u653D\u65A8\u65BB\u6609\u65FC\u6604\u6612\u6608\u65FB\u6603\u660B\u660D\u6605\u65FD\u6611\u6610\u66F6\u670A\u6785\u676C\u678E\u6792\u6776\u677B\u6798\u6786\u6784\u6774\u678D\u678C\u677A\u679F\u6791\u6799\u6783\u677D\u6781\u6778\u6779\u6794\u6B25\u6B80\u6B7E\u6BDE\u6C1D\u6C93\u6CEC\u6CEB\u6CEE\u6CD9\u6CB6\u6CD4\u6CAD\u6CE7\u6CB7\u6CD0\u6CC2\u6CBA\u6CC3\u6CC6\u6CED\u6CF2\u6CD2\u6CDD\u6CB4\u6C8A\u6C9D\u6C80\u6CDE\u6CC0\u6D30\u6CCD\u6CC7\u6CB0\u6CF9\u6CCF\u6CE9\u6CD1\u7094\u7098\u7085\u7093\u7086\u7084\u7091\u7096\u7082\u709A\u7083\u726A\u72D6\u72CB\u72D8\u72C9\u72DC\u72D2\u72D4\u72DA\u72CC\u72D1\u73A4\u73A1\u73AD\u73A6\u73A2\u73A0\u73AC\u739D\u74DD\u74E8\u753F\u7540\u753E\u758C\u7598\u76AF\u76F3\u76F1\u76F0\u76F5\u77F8\u77FC\u77F9\u77FB\u77FA\u77F7\u7942\u793F\u79C5\u7A78\u7A7B\u7AFB\u7C75\u7CFD\u8035\u808F\u80AE\u80A3\u80B8\u80B5\u80AD\u8220\u82A0\u82C0\u82AB\u829A\u8298\u829B\u82B5\u82A7\u82AE\u82BC\u829E\u82BA\u82B4\u82A8\u82A1\u82A9\u82C2\u82A4\u82C3\u82B6\u82A2\u8670\u866F\u866D\u866E\u8C56\u8FD2\u8FCB\u8FD3\u8FCD\u8FD6\u8FD5\u8FD7\u90B2\u90B4\u90AF\u90B3\u90B0\u9639\u963D\u963C\u963A\u9643\u4FCD\u4FC5\u4FD3\u4FB2\u4FC9\u4FCB\u4FC1\u4FD4\u4FDC\u4FD9\u4FBB\u4FB3\u4FDB\u4FC7\u4FD6\u4FBA\u4FC0\u4FB9\u4FEC\u5244\u5249\u52C0\u52C2\u533D\u537C\u5397\u5396\u5399\u5398\u54BA\u54A1\u54AD\u54A5\u54CF\u54C3\u830D\u54B7\u54AE\u54D6\u54B6\u54C5\u54C6\u54A0\u5470\u54BC\u54A2\u54BE\u5472\u54DE\u54B0\u57B5\u579E\u579F\u57A4\u578C\u5797\u579D\u579B\u5794\u5798\u578F\u5799\u57A5\u579A\u5795\u58F4\u590D\u5953\u59E1\u59DE\u59EE\u5A00\u59F1\u59DD\u59FA\u59FD\u59FC\u59F6\u59E4\u59F2\u59F7\u59DB\u59E9\u59F3\u59F5\u59E0\u59FE\u59F4\u59ED\u5BA8\u5C4C\u5CD0\u5CD8\u5CCC\u5CD7\u5CCB\u5CDB\u5CDE\u5CDA\u5CC9\u5CC7\u5CCA\u5CD6\u5CD3\u5CD4\u5CCF\u5CC8\u5CC6\u5CCE\u5CDF\u5CF8\u5DF9\u5E21\u5E22\u5E23\u5E20\u5E24\u5EB0\u5EA4\u5EA2\u5E9B\u5EA3\u5EA5\u5F07\u5F2E\u5F56\u5F86\u6037\u6039\u6054\u6072\u605E\u6045\u6053\u6047\u6049\u605B\u604C\u6040\u6042\u605F\u6024\u6044\u6058\u6066\u606E\u6242\u6243\u62CF\u630D\u630B\u62F5\u630E\u6303\u62EB\u62F9\u630F\u630C\u62F8\u62F6\u6300\u6313\u6314\u62FA\u6315\u62FB\u62F0\u6541\u6543\u65AA\u65BF\u6636\u6621\u6632\u6635\u661C\u6626\u6622\u6633\u662B\u663A\u661D\u6634\u6639\u662E\u670F\u6710\u67C1\u67F2\u67C8\u67BA\u67DC\u67BB\u67F8\u67D8\u67C0\u67B7\u67C5\u67EB\u67E4\u67DF\u67B5\u67CD\u67B3\u67F7\u67F6\u67EE\u67E3\u67C2\u67B9\u67CE\u67E7\u67F0\u67B2\u67FC\u67C6\u67ED\u67CC\u67AE\u67E6\u67DB\u67FA\u67C9\u67CA\u67C3\u67EA\u67CB\u6B28\u6B82\u6B84\u6BB6\u6BD6\u6BD8\u6BE0\u6C20\u6C21\u6D28\u6D34\u6D2D\u6D1F\u6D3C\u6D3F\u6D12\u6D0A\u6CDA\u6D33\u6D04\u6D19\u6D3A\u6D1A\u6D11\u6D00\u6D1D\u6D42\u6D01\u6D18\u6D37\u6D03\u6D0F\u6D40\u6D07\u6D20\u6D2C\u6D08\u6D22\u6D09\u6D10\u70B7\u709F\u70BE\u70B1\u70B0\u70A1\u70B4\u70B5\u70A9\u7241\u7249\u724A\u726C\u7270\u7273\u726E\u72CA\u72E4\u72E8\u72EB\u72DF\u72EA\u72E6\u72E3\u7385\u73CC\u73C2\u73C8\u73C5\u73B9\u73B6\u73B5\u73B4\u73EB\u73BF\u73C7\u73BE\u73C3\u73C6\u73B8\u73CB\u74EC\u74EE\u752E\u7547\u7548\u75A7\u75AA\u7679\u76C4\u7708\u7703\u7704\u7705\u770A\u76F7\u76FB\u76FA\u77E7\u77E8\u7806\u7811\u7812\u7805\u7810\u780F\u780E\u7809\u7803\u7813\u794A\u794C\u794B\u7945\u7944\u79D5\u79CD\u79CF\u79D6\u79CE\u7A80\u7A7E\u7AD1\u7B00\u7B01\u7C7A\u7C78\u7C79\u7C7F\u7C80\u7C81\u7D03\u7D08\u7D01\u7F58\u7F91\u7F8D\u7FBE\u8007\u800E\u800F\u8014\u8037\u80D8\u80C7\u80E0\u80D1\u80C8\u80C2\u80D0\u80C5\u80E3\u80D9\u80DC\u80CA\u80D5\u80C9\u80CF\u80D7\u80E6\u80CD\u81FF\u8221\u8294\u82D9\u82FE\u82F9\u8307\u82E8\u8300\u82D5\u833A\u82EB\u82D6\u82F4\u82EC\u82E1\u82F2\u82F5\u830C\u82FB\u82F6\u82F0\u82EA\u82E4\u82E0\u82FA\u82F3\u82ED\u8677\u8674\u867C\u8673\u8841\u884E\u8867\u886A\u8869\u89D3\u8A04\u8A07\u8D72\u8FE3\u8FE1\u8FEE\u8FE0\u90F1\u90BD\u90BF\u90D5\u90C5\u90BE\u90C7\u90CB\u90C8\u91D4\u91D3\u9654\u964F\u9651\u9653\u964A\u964E\u501E\u5005\u5007\u5013\u5022\u5030\u501B\u4FF5\u4FF4\u5033\u5037\u502C\u4FF6\u4FF7\u5017\u501C\u5020\u5027\u5035\u502F\u5031\u500E\u515A\u5194\u5193\u51CA\u51C4\u51C5\u51C8\u51CE\u5261\u525A\u5252\u525E\u525F\u5255\u5262\u52CD\u530E\u539E\u5526\u54E2\u5517\u5512\u54E7\u54F3\u54E4\u551A\u54FF\u5504\u5508\u54EB\u5511\u5505\u54F1\u550A\u54FB\u54F7\u54F8\u54E0\u550E\u5503\u550B\u5701\u5702\u57CC\u5832\u57D5\u57D2\u57BA\u57C6\u57BD\u57BC\u57B8\u57B6\u57BF\u57C7\u57D0\u57B9\u57C1\u590E\u594A\u5A19\u5A16\u5A2D\u5A2E\u5A15\u5A0F\u5A17\u5A0A\u5A1E\u5A33\u5B6C\u5BA7\u5BAD\u5BAC\u5C03\u5C56\u5C54\u5CEC\u5CFF\u5CEE\u5CF1\u5CF7\u5D00\u5CF9\u5E29\u5E28\u5EA8\u5EAE\u5EAA\u5EAC\u5F33\u5F30\u5F67\u605D\u605A\u6067\u6041\u60A2\u6088\u6080\u6092\u6081\u609D\u6083\u6095\u609B\u6097\u6087\u609C\u608E\u6219\u6246\u62F2\u6310\u6356\u632C\u6344\u6345\u6336\u6343\u63E4\u6339\u634B\u634A\u633C\u6329\u6341\u6334\u6358\u6354\u6359\u632D\u6347\u6333\u635A\u6351\u6338\u6357\u6340\u6348\u654A\u6546\u65C6\u65C3\u65C4\u65C2\u664A\u665F\u6647\u6651\u6712\u6713\u681F\u681A\u6849\u6832\u6833\u683B\u684B\u684F\u6816\u6831\u681C\u6835\u682B\u682D\u682F\u684E\u6844\u6834\u681D\u6812\u6814\u6826\u6828\u682E\u684D\u683A\u6825\u6820\u6B2C\u6B2F\u6B2D\u6B31\u6B34\u6B6D\u8082\u6B88\u6BE6\u6BE4\u6BE8\u6BE3\u6BE2\u6BE7\u6C25\u6D7A\u6D63\u6D64\u6D76\u6D0D\u6D61\u6D92\u6D58\u6D62\u6D6D\u6D6F\u6D91\u6D8D\u6DEF\u6D7F\u6D86\u6D5E\u6D67\u6D60\u6D97\u6D70\u6D7C\u6D5F\u6D82\u6D98\u6D2F\u6D68\u6D8B\u6D7E\u6D80\u6D84\u6D16\u6D83\u6D7B\u6D7D\u6D75\u6D90\u70DC\u70D3\u70D1\u70DD\u70CB\u7F39\u70E2\u70D7\u70D2\u70DE\u70E0\u70D4\u70CD\u70C5\u70C6\u70C7\u70DA\u70CE\u70E1\u7242\u7278\u7277\u7276\u7300\u72FA\u72F4\u72FE\u72F6\u72F3\u72FB\u7301\u73D3\u73D9\u73E5\u73D6\u73BC\u73E7\u73E3\u73E9\u73DC\u73D2\u73DB\u73D4\u73DD\u73DA\u73D7\u73D8\u73E8\u74DE\u74DF\u74F4\u74F5\u7521\u755B\u755F\u75B0\u75C1\u75BB\u75C4\u75C0\u75BF\u75B6\u75BA\u768A\u76C9\u771D\u771B\u7710\u7713\u7712\u7723\u7711\u7715\u7719\u771A\u7722\u7727\u7823\u782C\u7822\u7835\u782F\u7828\u782E\u782B\u7821\u7829\u7833\u782A\u7831\u7954\u795B\u794F\u795C\u7953\u7952\u7951\u79EB\u79EC\u79E0\u79EE\u79ED\u79EA\u79DC\u79DE\u79DD\u7A86\u7A89\u7A85\u7A8B\u7A8C\u7A8A\u7A87\u7AD8\u7B10\u7B04\u7B13\u7B05\u7B0F\u7B08\u7B0A\u7B0E\u7B09\u7B12\u7C84\u7C91\u7C8A\u7C8C\u7C88\u7C8D\u7C85\u7D1E\u7D1D\u7D11\u7D0E\u7D18\u7D16\u7D13\u7D1F\u7D12\u7D0F\u7D0C\u7F5C\u7F61\u7F5E\u7F60\u7F5D\u7F5B\u7F96\u7F92\u7FC3\u7FC2\u7FC0\u8016\u803E\u8039\u80FA\u80F2\u80F9\u80F5\u8101\u80FB\u8100\u8201\u822F\u8225\u8333\u832D\u8344\u8319\u8351\u8325\u8356\u833F\u8341\u8326\u831C\u8322\u8342\u834E\u831B\u832A\u8308\u833C\u834D\u8316\u8324\u8320\u8337\u832F\u8329\u8347\u8345\u834C\u8353\u831E\u832C\u834B\u8327\u8348\u8653\u8652\u86A2\u86A8\u8696\u868D\u8691\u869E\u8687\u8697\u8686\u868B\u869A\u8685\u86A5\u8699\u86A1\u86A7\u8695\u8698\u868E\u869D\u8690\u8694\u8843\u8844\u886D\u8875\u8876\u8872\u8880\u8871\u887F\u886F\u8883\u887E\u8874\u887C\u8A12\u8C47\u8C57\u8C7B\u8CA4\u8CA3\u8D76\u8D78\u8DB5\u8DB7\u8DB6\u8ED1\u8ED3\u8FFE\u8FF5\u9002\u8FFF\u8FFB\u9004\u8FFC\u8FF6\u90D6\u90E0\u90D9\u90DA\u90E3\u90DF\u90E5\u90D8\u90DB\u90D7\u90DC\u90E4\u9150\u914E\u914F\u91D5\u91E2\u91DA\u965C\u965F\u96BC\u98E3\u9ADF\u9B2F\u4E7F\u5070\u506A\u5061\u505E\u5060\u5053\u504B\u505D\u5072\u5048\u504D\u5041\u505B\u504A\u5062\u5015\u5045\u505F\u5069\u506B\u5063\u5064\u5046\u5040\u506E\u5073\u5057\u5051\u51D0\u526B\u526D\u526C\u526E\u52D6\u52D3\u532D\u539C\u5575\u5576\u553C\u554D\u5550\u5534\u552A\u5551\u5562\u5536\u5535\u5530\u5552\u5545\u550C\u5532\u5565\u554E\u5539\u5548\u552D\u553B\u5540\u554B\u570A\u5707\u57FB\u5814\u57E2\u57F6\u57DC\u57F4\u5800\u57ED\u57FD\u5808\u57F8\u580B\u57F3\u57CF\u5807\u57EE\u57E3\u57F2\u57E5\u57EC\u57E1\u580E\u57FC\u5810\u57E7\u5801\u580C\u57F1\u57E9\u57F0\u580D\u5804\u595C\u5A60\u5A58\u5A55\u5A67\u5A5E\u5A38\u5A35\u5A6D\u5A50\u5A5F\u5A65\u5A6C\u5A53\u5A64\u5A57\u5A43\u5A5D\u5A52\u5A44\u5A5B\u5A48\u5A8E\u5A3E\u5A4D\u5A39\u5A4C\u5A70\u5A69\u5A47\u5A51\u5A56\u5A42\u5A5C\u5B72\u5B6E\u5BC1\u5BC0\u5C59\u5D1E\u5D0B\u5D1D\u5D1A\u5D20\u5D0C\u5D28\u5D0D\u5D26\u5D25\u5D0F\u5D30\u5D12\u5D23\u5D1F\u5D2E\u5E3E\u5E34\u5EB1\u5EB4\u5EB9\u5EB2\u5EB3\u5F36\u5F38\u5F9B\u5F96\u5F9F\u608A\u6090\u6086\u60BE\u60B0\u60BA\u60D3\u60D4\u60CF\u60E4\u60D9\u60DD\u60C8\u60B1\u60DB\u60B7\u60CA\u60BF\u60C3\u60CD\u60C0\u6332\u6365\u638A\u6382\u637D\u63BD\u639E\u63AD\u639D\u6397\u63AB\u638E\u636F\u6387\u6390\u636E\u63AF\u6375\u639C\u636D\u63AE\u637C\u63A4\u633B\u639F\u6378\u6385\u6381\u6391\u638D\u6370\u6553\u65CD\u6665\u6661\u665B\u6659\u665C\u6662\u6718\u6879\u6887\u6890\u689C\u686D\u686E\u68AE\u68AB\u6956\u686F\u68A3\u68AC\u68A9\u6875\u6874\u68B2\u688F\u6877\u6892\u687C\u686B\u6872\u68AA\u6880\u6871\u687E\u689B\u6896\u688B\u68A0\u6889\u68A4\u6878\u687B\u6891\u688C\u688A\u687D\u6B36\u6B33\u6B37\u6B38\u6B91\u6B8F\u6B8D\u6B8E\u6B8C\u6C2A\u6DC0\u6DAB\u6DB4\u6DB3\u6E74\u6DAC\u6DE9\u6DE2\u6DB7\u6DF6\u6DD4\u6E00\u6DC8\u6DE0\u6DDF\u6DD6\u6DBE\u6DE5\u6DDC\u6DDD\u6DDB\u6DF4\u6DCA\u6DBD\u6DED\u6DF0\u6DBA\u6DD5\u6DC2\u6DCF\u6DC9\u6DD0\u6DF2\u6DD3\u6DFD\u6DD7\u6DCD\u6DE3\u6DBB\u70FA\u710D\u70F7\u7117\u70F4\u710C\u70F0\u7104\u70F3\u7110\u70FC\u70FF\u7106\u7113\u7100\u70F8\u70F6\u710B\u7102\u710E\u727E\u727B\u727C\u727F\u731D\u7317\u7307\u7311\u7318\u730A\u7308\u72FF\u730F\u731E\u7388\u73F6\u73F8\u73F5\u7404\u7401\u73FD\u7407\u7400\u73FA\u73FC\u73FF\u740C\u740B\u73F4\u7408\u7564\u7563\u75CE\u75D2\u75CF\u75CB\u75CC\u75D1\u75D0\u768F\u7689\u76D3\u7739\u772F\u772D\u7731\u7732\u7734\u7733\u773D\u7725\u773B\u7735\u7848\u7852\u7849\u784D\u784A\u784C\u7826\u7845\u7850\u7964\u7967\u7969\u796A\u7963\u796B\u7961\u79BB\u79FA\u79F8\u79F6\u79F7\u7A8F\u7A94\u7A90\u7B35\u7B47\u7B34\u7B25\u7B30\u7B22\u7B24\u7B33\u7B18\u7B2A\u7B1D\u7B31\u7B2B\u7B2D\u7B2F\u7B32\u7B38\u7B1A\u7B23\u7C94\u7C98\u7C96\u7CA3\u7D35\u7D3D\u7D38\u7D36\u7D3A\u7D45\u7D2C\u7D29\u7D41\u7D47\u7D3E\u7D3F\u7D4A\u7D3B\u7D28\u7F63\u7F95\u7F9C\u7F9D\u7F9B\u7FCA\u7FCB\u7FCD\u7FD0\u7FD1\u7FC7\u7FCF\u7FC9\u801F\u801E\u801B\u8047\u8043\u8048\u8118\u8125\u8119\u811B\u812D\u811F\u812C\u811E\u8121\u8115\u8127\u811D\u8122\u8211\u8238\u8233\u823A\u8234\u8232\u8274\u8390\u83A3\u83A8\u838D\u837A\u8373\u83A4\u8374\u838F\u8381\u8395\u8399\u8375\u8394\u83A9\u837D\u8383\u838C\u839D\u839B\u83AA\u838B\u837E\u83A5\u83AF\u8388\u8397\u83B0\u837F\u83A6\u8387\u83AE\u8376\u839A\u8659\u8656\u86BF\u86B7\u86C2\u86C1\u86C5\u86BA\u86B0\u86C8\u86B9\u86B3\u86B8\u86CC\u86B4\u86BB\u86BC\u86C3\u86BD\u86BE\u8852\u8889\u8895\u88A8\u88A2\u88AA\u889A\u8891\u88A1\u889F\u8898\u88A7\u8899\u889B\u8897\u88A4\u88AC\u888C\u8893\u888E\u8982\u89D6\u89D9\u89D5\u8A30\u8A27\u8A2C\u8A1E\u8C39\u8C3B\u8C5C\u8C5D\u8C7D\u8CA5\u8D7D\u8D7B\u8D79\u8DBC\u8DC2\u8DB9\u8DBF\u8DC1\u8ED8\u8EDE\u8EDD\u8EDC\u8ED7\u8EE0\u8EE1\u9024\u900B\u9011\u901C\u900C\u9021\u90EF\u90EA\u90F0\u90F4\u90F2\u90F3\u90D4\u90EB\u90EC\u90E9\u9156\u9158\u915A\u9153\u9155\u91EC\u91F4\u91F1\u91F3\u91F8\u91E4\u91F9\u91EA\u91EB\u91F7\u91E8\u91EE\u957A\u9586\u9588\u967C\u966D\u966B\u9671\u966F\u96BF\u976A\u9804\u98E5\u9997\u509B\u5095\u5094\u509E\u508B\u50A3\u5083\u508C\u508E\u509D\u5068\u509C\u5092\u5082\u5087\u515F\u51D4\u5312\u5311\u53A4\u53A7\u5591\u55A8\u55A5\u55AD\u5577\u5645\u55A2\u5593\u5588\u558F\u55B5\u5581\u55A3\u5592\u55A4\u557D\u558C\u55A6\u557F\u5595\u55A1\u558E\u570C\u5829\u5837\u5819\u581E\u5827\u5823\u5828\u57F5\u5848\u5825\u581C\u581B\u5833\u583F\u5836\u582E\u5839\u5838\u582D\u582C\u583B\u5961\u5AAF\u5A94\u5A9F\u5A7A\u5AA2\u5A9E\u5A78\u5AA6\u5A7C\u5AA5\u5AAC\u5A95\u5AAE\u5A37\u5A84\u5A8A\u5A97\u5A83\u5A8B\u5AA9\u5A7B\u5A7D\u5A8C\u5A9C\u5A8F\u5A93\u5A9D\u5BEA\u5BCD\u5BCB\u5BD4\u5BD1\u5BCA\u5BCE\u5C0C\u5C30\u5D37\u5D43\u5D6B\u5D41\u5D4B\u5D3F\u5D35\u5D51\u5D4E\u5D55\u5D33\u5D3A\u5D52\u5D3D\u5D31\u5D59\u5D42\u5D39\u5D49\u5D38\u5D3C\u5D32\u5D36\u5D40\u5D45\u5E44\u5E41\u5F58\u5FA6\u5FA5\u5FAB\u60C9\u60B9\u60CC\u60E2\u60CE\u60C4\u6114\u60F2\u610A\u6116\u6105\u60F5\u6113\u60F8\u60FC\u60FE\u60C1\u6103\u6118\u611D\u6110\u60FF\u6104\u610B\u624A\u6394\u63B1\u63B0\u63CE\u63E5\u63E8\u63EF\u63C3\u649D\u63F3\u63CA\u63E0\u63F6\u63D5\u63F2\u63F5\u6461\u63DF\u63BE\u63DD\u63DC\u63C4\u63D8\u63D3\u63C2\u63C7\u63CC\u63CB\u63C8\u63F0\u63D7\u63D9\u6532\u6567\u656A\u6564\u655C\u6568\u6565\u658C\u659D\u659E\u65AE\u65D0\u65D2\u667C\u666C\u667B\u6680\u6671\u6679\u666A\u6672\u6701\u690C\u68D3\u6904\u68DC\u692A\u68EC\u68EA\u68F1\u690F\u68D6\u68F7\u68EB\u68E4\u68F6\u6913\u6910\u68F3\u68E1\u6907\u68CC\u6908\u6970\u68B4\u6911\u68EF\u68C6\u6914\u68F8\u68D0\u68FD\u68FC\u68E8\u690B\u690A\u6917\u68CE\u68C8\u68DD\u68DE\u68E6\u68F4\u68D1\u6906\u68D4\u68E9\u6915\u6925\u68C7\u6B39\u6B3B\u6B3F\u6B3C\u6B94\u6B97\u6B99\u6B95\u6BBD\u6BF0\u6BF2\u6BF3\u6C30\u6DFC\u6E46\u6E47\u6E1F\u6E49\u6E88\u6E3C\u6E3D\u6E45\u6E62\u6E2B\u6E3F\u6E41\u6E5D\u6E73\u6E1C\u6E33\u6E4B\u6E40\u6E51\u6E3B\u6E03\u6E2E\u6E5E\u6E68\u6E5C\u6E61\u6E31\u6E28\u6E60\u6E71\u6E6B\u6E39\u6E22\u6E30\u6E53\u6E65\u6E27\u6E78\u6E64\u6E77\u6E55\u6E79\u6E52\u6E66\u6E35\u6E36\u6E5A\u7120\u711E\u712F\u70FB\u712E\u7131\u7123\u7125\u7122\u7132\u711F\u7128\u713A\u711B\u724B\u725A\u7288\u7289\u7286\u7285\u728B\u7312\u730B\u7330\u7322\u7331\u7333\u7327\u7332\u732D\u7326\u7323\u7335\u730C\u742E\u742C\u7430\u742B\u7416\u741A\u7421\u742D\u7431\u7424\u7423\u741D\u7429\u7420\u7432\u74FB\u752F\u756F\u756C\u75E7\u75DA\u75E1\u75E6\u75DD\u75DF\u75E4\u75D7\u7695\u7692\u76DA\u7746\u7747\u7744\u774D\u7745\u774A\u774E\u774B\u774C\u77DE\u77EC\u7860\u7864\u7865\u785C\u786D\u7871\u786A\u786E\u7870\u7869\u7868\u785E\u7862\u7974\u7973\u7972\u7970\u7A02\u7A0A\u7A03\u7A0C\u7A04\u7A99\u7AE6\u7AE4\u7B4A\u7B3B\u7B44\u7B48\u7B4C\u7B4E\u7B40\u7B58\u7B45\u7CA2\u7C9E\u7CA8\u7CA1\u7D58\u7D6F\u7D63\u7D53\u7D56\u7D67\u7D6A\u7D4F\u7D6D\u7D5C\u7D6B\u7D52\u7D54\u7D69\u7D51\u7D5F\u7D4E\u7F3E\u7F3F\u7F65\u7F66\u7FA2\u7FA0\u7FA1\u7FD7\u8051\u804F\u8050\u80FE\u80D4\u8143\u814A\u8152\u814F\u8147\u813D\u814D\u813A\u81E6\u81EE\u81F7\u81F8\u81F9\u8204\u823C\u823D\u823F\u8275\u833B\u83CF\u83F9\u8423\u83C0\u83E8\u8412\u83E7\u83E4\u83FC\u83F6\u8410\u83C6\u83C8\u83EB\u83E3\u83BF\u8401\u83DD\u83E5\u83D8\u83FF\u83E1\u83CB\u83CE\u83D6\u83F5\u83C9\u8409\u840F\u83DE\u8411\u8406\u83C2\u83F3\u83D5\u83FA\u83C7\u83D1\u83EA\u8413\u83C3\u83EC\u83EE\u83C4\u83FB\u83D7\u83E2\u841B\u83DB\u83FE\u86D8\u86E2\u86E6\u86D3\u86E3\u86DA\u86EA\u86DD\u86EB\u86DC\u86EC\u86E9\u86D7\u86E8\u86D1\u8848\u8856\u8855\u88BA\u88D7\u88B9\u88B8\u88C0\u88BE\u88B6\u88BC\u88B7\u88BD\u88B2\u8901\u88C9\u8995\u8998\u8997\u89DD\u89DA\u89DB\u8A4E\u8A4D\u8A39\u8A59\u8A40\u8A57\u8A58\u8A44\u8A45\u8A52\u8A48\u8A51\u8A4A\u8A4C\u8A4F\u8C5F\u8C81\u8C80\u8CBA\u8CBE\u8CB0\u8CB9\u8CB5\u8D84\u8D80\u8D89\u8DD8\u8DD3\u8DCD\u8DC7\u8DD6\u8DDC\u8DCF\u8DD5\u8DD9\u8DC8\u8DD7\u8DC5\u8EEF\u8EF7\u8EFA\u8EF9\u8EE6\u8EEE\u8EE5\u8EF5\u8EE7\u8EE8\u8EF6\u8EEB\u8EF1\u8EEC\u8EF4\u8EE9\u902D\u9034\u902F\u9106\u912C\u9104\u90FF\u90FC\u9108\u90F9\u90FB\u9101\u9100\u9107\u9105\u9103\u9161\u9164\u915F\u9162\u9160\u9201\u920A\u9225\u9203\u921A\u9226\u920F\u920C\u9200\u9212\u91FF\u91FD\u9206\u9204\u9227\u9202\u921C\u9224\u9219\u9217\u9205\u9216\u957B\u958D\u958C\u9590\u9687\u967E\u9688\u9689\u9683\u9680\u96C2\u96C8\u96C3\u96F1\u96F0\u976C\u9770\u976E\u9807\u98A9\u98EB\u9CE6\u9EF9\u4E83\u4E84\u4EB6\u50BD\u50BF\u50C6\u50AE\u50C4\u50CA\u50B4\u50C8\u50C2\u50B0\u50C1\u50BA\u50B1\u50CB\u50C9\u50B6\u50B8\u51D7\u527A\u5278\u527B\u527C\u55C3\u55DB\u55CC\u55D0\u55CB\u55CA\u55DD\u55C0\u55D4\u55C4\u55E9\u55BF\u55D2\u558D\u55CF\u55D5\u55E2\u55D6\u55C8\u55F2\u55CD\u55D9\u55C2\u5714\u5853\u5868\u5864\u584F\u584D\u5849\u586F\u5855\u584E\u585D\u5859\u5865\u585B\u583D\u5863\u5871\u58FC\u5AC7\u5AC4\u5ACB\u5ABA\u5AB8\u5AB1\u5AB5\u5AB0\u5ABF\u5AC8\u5ABB\u5AC6\u5AB7\u5AC0\u5ACA\u5AB4\u5AB6\u5ACD\u5AB9\u5A90\u5BD6\u5BD8\u5BD9\u5C1F\u5C33\u5D71\u5D63\u5D4A\u5D65\u5D72\u5D6C\u5D5E\u5D68\u5D67\u5D62\u5DF0\u5E4F\u5E4E\u5E4A\u5E4D\u5E4B\u5EC5\u5ECC\u5EC6\u5ECB\u5EC7\u5F40\u5FAF\u5FAD\u60F7\u6149\u614A\u612B\u6145\u6136\u6132\u612E\u6146\u612F\u614F\u6129\u6140\u6220\u9168\u6223\u6225\u6224\u63C5\u63F1\u63EB\u6410\u6412\u6409\u6420\u6424\u6433\u6443\u641F\u6415\u6418\u6439\u6437\u6422\u6423\u640C\u6426\u6430\u6428\u6441\u6435\u642F\u640A\u641A\u6440\u6425\u6427\u640B\u63E7\u641B\u642E\u6421\u640E\u656F\u6592\u65D3\u6686\u668C\u6695\u6690\u668B\u668A\u6699\u6694\u6678\u6720\u6966\u695F\u6938\u694E\u6962\u6971\u693F\u6945\u696A\u6939\u6942\u6957\u6959\u697A\u6948\u6949\u6935\u696C\u6933\u693D\u6965\u68F0\u6978\u6934\u6969\u6940\u696F\u6944\u6976\u6958\u6941\u6974\u694C\u693B\u694B\u6937\u695C\u694F\u6951\u6932\u6952\u692F\u697B\u693C\u6B46\u6B45\u6B43\u6B42\u6B48\u6B41\u6B9B\uFA0D\u6BFB\u6BFC\u6BF9\u6BF7\u6BF8\u6E9B\u6ED6\u6EC8\u6E8F\u6EC0\u6E9F\u6E93\u6E94\u6EA0\u6EB1\u6EB9\u6EC6\u6ED2\u6EBD\u6EC1\u6E9E\u6EC9\u6EB7\u6EB0\u6ECD\u6EA6\u6ECF\u6EB2\u6EBE\u6EC3\u6EDC\u6ED8\u6E99\u6E92\u6E8E\u6E8D\u6EA4\u6EA1\u6EBF\u6EB3\u6ED0\u6ECA\u6E97\u6EAE\u6EA3\u7147\u7154\u7152\u7163\u7160\u7141\u715D\u7162\u7172\u7178\u716A\u7161\u7142\u7158\u7143\u714B\u7170\u715F\u7150\u7153\u7144\u714D\u715A\u724F\u728D\u728C\u7291\u7290\u728E\u733C\u7342\u733B\u733A\u7340\u734A\u7349\u7444\u744A\u744B\u7452\u7451\u7457\u7440\u744F\u7450\u744E\u7442\u7446\u744D\u7454\u74E1\u74FF\u74FE\u74FD\u751D\u7579\u7577\u6983\u75EF\u760F\u7603\u75F7\u75FE\u75FC\u75F9\u75F8\u7610\u75FB\u75F6\u75ED\u75F5\u75FD\u7699\u76B5\u76DD\u7755\u775F\u7760\u7752\u7756\u775A\u7769\u7767\u7754\u7759\u776D\u77E0\u7887\u789A\u7894\u788F\u7884\u7895\u7885\u7886\u78A1\u7883\u7879\u7899\u7880\u7896\u787B\u797C\u7982\u797D\u7979\u7A11\u7A18\u7A19\u7A12\u7A17\u7A15\u7A22\u7A13\u7A1B\u7A10\u7AA3\u7AA2\u7A9E\u7AEB\u7B66\u7B64\u7B6D\u7B74\u7B69\u7B72\u7B65\u7B73\u7B71\u7B70\u7B61\u7B78\u7B76\u7B63\u7CB2\u7CB4\u7CAF\u7D88\u7D86\u7D80\u7D8D\u7D7F\u7D85\u7D7A\u7D8E\u7D7B\u7D83\u7D7C\u7D8C\u7D94\u7D84\u7D7D\u7D92\u7F6D\u7F6B\u7F67\u7F68\u7F6C\u7FA6\u7FA5\u7FA7\u7FDB\u7FDC\u8021\u8164\u8160\u8177\u815C\u8169\u815B\u8162\u8172\u6721\u815E\u8176\u8167\u816F\u8144\u8161\u821D\u8249\u8244\u8240\u8242\u8245\u84F1\u843F\u8456\u8476\u8479\u848F\u848D\u8465\u8451\u8440\u8486\u8467\u8430\u844D\u847D\u845A\u8459\u8474\u8473\u845D\u8507\u845E\u8437\u843A\u8434\u847A\u8443\u8478\u8432\u8445\u8429\u83D9\u844B\u842F\u8442\u842D\u845F\u8470\u8439\u844E\u844C\u8452\u846F\u84C5\u848E\u843B\u8447\u8436\u8433\u8468\u847E\u8444\u842B\u8460\u8454\u846E\u8450\u870B\u8704\u86F7\u870C\u86FA\u86D6\u86F5\u874D\u86F8\u870E\u8709\u8701\u86F6\u870D\u8705\u88D6\u88CB\u88CD\u88CE\u88DE\u88DB\u88DA\u88CC\u88D0\u8985\u899B\u89DF\u89E5\u89E4\u89E1\u89E0\u89E2\u89DC\u89E6\u8A76\u8A86\u8A7F\u8A61\u8A3F\u8A77\u8A82\u8A84\u8A75\u8A83\u8A81\u8A74\u8A7A\u8C3C\u8C4B\u8C4A\u8C65\u8C64\u8C66\u8C86\u8C84\u8C85\u8CCC\u8D68\u8D69\u8D91\u8D8C\u8D8E\u8D8F\u8D8D\u8D93\u8D94\u8D90\u8D92\u8DF0\u8DE0\u8DEC\u8DF1\u8DEE\u8DD0\u8DE9\u8DE3\u8DE2\u8DE7\u8DF2\u8DEB\u8DF4\u8F06\u8EFF\u8F01\u8F00\u8F05\u8F07\u8F08\u8F02\u8F0B\u9052\u903F\u9044\u9049\u903D\u9110\u910D\u910F\u9111\u9116\u9114\u910B\u910E\u916E\u916F\u9248\u9252\u9230\u923A\u9266\u9233\u9265\u925E\u9283\u922E\u924A\u9246\u926D\u926C\u924F\u9260\u9267\u926F\u9236\u9261\u9270\u9231\u9254\u9263\u9250\u9272\u924E\u9253\u924C\u9256\u9232\u959F\u959C\u959E\u959B\u9692\u9693\u9691\u9697\u96CE\u96FA\u96FD\u96F8\u96F5\u9773\u9777\u9778\u9772\u980F\u980D\u980E\u98AC\u98F6\u98F9\u99AF\u99B2\u99B0\u99B5\u9AAD\u9AAB\u9B5B\u9CEA\u9CED\u9CE7\u9E80\u9EFD\u50E6\u50D4\u50D7\u50E8\u50F3\u50DB\u50EA\u50DD\u50E4\u50D3\u50EC\u50F0\u50EF\u50E3\u50E0\u51D8\u5280\u5281\u52E9\u52EB\u5330\u53AC\u5627\u5615\u560C\u5612\u55FC\u560F\u561C\u5601\u5613\u5602\u55FA\u561D\u5604\u55FF\u55F9\u5889\u587C\u5890\u5898\u5886\u5881\u587F\u5874\u588B\u587A\u5887\u5891\u588E\u5876\u5882\u5888\u587B\u5894\u588F\u58FE\u596B\u5ADC\u5AEE\u5AE5\u5AD5\u5AEA\u5ADA\u5AED\u5AEB\u5AF3\u5AE2\u5AE0\u5ADB\u5AEC\u5ADE\u5ADD\u5AD9\u5AE8\u5ADF\u5B77\u5BE0\u5BE3\u5C63\u5D82\u5D80\u5D7D\u5D86\u5D7A\u5D81\u5D77\u5D8A\u5D89\u5D88\u5D7E\u5D7C\u5D8D\u5D79\u5D7F\u5E58\u5E59\u5E53\u5ED8\u5ED1\u5ED7\u5ECE\u5EDC\u5ED5\u5ED9\u5ED2\u5ED4\u5F44\u5F43\u5F6F\u5FB6\u612C\u6128\u6141\u615E\u6171\u6173\u6152\u6153\u6172\u616C\u6180\u6174\u6154\u617A\u615B\u6165\u613B\u616A\u6161\u6156\u6229\u6227\u622B\u642B\u644D\u645B\u645D\u6474\u6476\u6472\u6473\u647D\u6475\u6466\u64A6\u644E\u6482\u645E\u645C\u644B\u6453\u6460\u6450\u647F\u643F\u646C\u646B\u6459\u6465\u6477\u6573\u65A0\u66A1\u66A0\u669F\u6705\u6704\u6722\u69B1\u69B6\u69C9\u69A0\u69CE\u6996\u69B0\u69AC\u69BC\u6991\u6999\u698E\u69A7\u698D\u69A9\u69BE\u69AF\u69BF\u69C4\u69BD\u69A4\u69D4\u69B9\u69CA\u699A\u69CF\u69B3\u6993\u69AA\u69A1\u699E\u69D9\u6997\u6990\u69C2\u69B5\u69A5\u69C6\u6B4A\u6B4D\u6B4B\u6B9E\u6B9F\u6BA0\u6BC3\u6BC4\u6BFE\u6ECE\u6EF5\u6EF1\u6F03\u6F25\u6EF8\u6F37\u6EFB\u6F2E\u6F09\u6F4E\u6F19\u6F1A\u6F27\u6F18\u6F3B\u6F12\u6EED\u6F0A\u6F36\u6F73\u6EF9\u6EEE\u6F2D\u6F40\u6F30\u6F3C\u6F35\u6EEB\u6F07\u6F0E\u6F43\u6F05\u6EFD\u6EF6\u6F39\u6F1C\u6EFC\u6F3A\u6F1F\u6F0D\u6F1E\u6F08\u6F21\u7187\u7190\u7189\u7180\u7185\u7182\u718F\u717B\u7186\u7181\u7197\u7244\u7253\u7297\u7295\u7293\u7343\u734D\u7351\u734C\u7462\u7473\u7471\u7475\u7472\u7467\u746E\u7500\u7502\u7503\u757D\u7590\u7616\u7608\u760C\u7615\u7611\u760A\u7614\u76B8\u7781\u777C\u7785\u7782\u776E\u7780\u776F\u777E\u7783\u78B2\u78AA\u78B4\u78AD\u78A8\u787E\u78AB\u789E\u78A5\u78A0\u78AC\u78A2\u78A4\u7998\u798A\u798B\u7996\u7995\u7994\u7993\u7997\u7988\u7992\u7990\u7A2B\u7A4A\u7A30\u7A2F\u7A28\u7A26\u7AA8\u7AAB\u7AAC\u7AEE\u7B88\u7B9C\u7B8A\u7B91\u7B90\u7B96\u7B8D\u7B8C\u7B9B\u7B8E\u7B85\u7B98\u5284\u7B99\u7BA4\u7B82\u7CBB\u7CBF\u7CBC\u7CBA\u7DA7\u7DB7\u7DC2\u7DA3\u7DAA\u7DC1\u7DC0\u7DC5\u7D9D\u7DCE\u7DC4\u7DC6\u7DCB\u7DCC\u7DAF\u7DB9\u7D96\u7DBC\u7D9F\u7DA6\u7DAE\u7DA9\u7DA1\u7DC9\u7F73\u7FE2\u7FE3\u7FE5\u7FDE\u8024\u805D\u805C\u8189\u8186\u8183\u8187\u818D\u818C\u818B\u8215\u8497\u84A4\u84A1\u849F\u84BA\u84CE\u84C2\u84AC\u84AE\u84AB\u84B9\u84B4\u84C1\u84CD\u84AA\u849A\u84B1\u84D0\u849D\u84A7\u84BB\u84A2\u8494\u84C7\u84CC\u849B\u84A9\u84AF\u84A8\u84D6\u8498\u84B6\u84CF\u84A0\u84D7\u84D4\u84D2\u84DB\u84B0\u8491\u8661\u8733\u8723\u8728\u876B\u8740\u872E\u871E\u8721\u8719\u871B\u8743\u872C\u8741\u873E\u8746\u8720\u8732\u872A\u872D\u873C\u8712\u873A\u8731\u8735\u8742\u8726\u8727\u8738\u8724\u871A\u8730\u8711\u88F7\u88E7\u88F1\u88F2\u88FA\u88FE\u88EE\u88FC\u88F6\u88FB\u88F0\u88EC\u88EB\u899D\u89A1\u899F\u899E\u89E9\u89EB\u89E8\u8AAB\u8A99\u8A8B\u8A92\u8A8F\u8A96\u8C3D\u8C68\u8C69\u8CD5\u8CCF\u8CD7\u8D96\u8E09\u8E02\u8DFF\u8E0D\u8DFD\u8E0A\u8E03\u8E07\u8E06\u8E05\u8DFE\u8E00\u8E04\u8F10\u8F11\u8F0E\u8F0D\u9123\u911C\u9120\u9122\u911F\u911D\u911A\u9124\u9121\u911B\u917A\u9172\u9179\u9173\u92A5\u92A4\u9276\u929B\u927A\u92A0\u9294\u92AA\u928D\u92A6\u929A\u92AB\u9279\u9297\u927F\u92A3\u92EE\u928E\u9282\u9295\u92A2\u927D\u9288\u92A1\u928A\u9286\u928C\u9299\u92A7\u927E\u9287\u92A9\u929D\u928B\u922D\u969E\u96A1\u96FF\u9758\u977D\u977A\u977E\u9783\u9780\u9782\u977B\u9784\u9781\u977F\u97CE\u97CD\u9816\u98AD\u98AE\u9902\u9900\u9907\u999D\u999C\u99C3\u99B9\u99BB\u99BA\u99C2\u99BD\u99C7\u9AB1\u9AE3\u9AE7\u9B3E\u9B3F\u9B60\u9B61\u9B5F\u9CF1\u9CF2\u9CF5\u9EA7\u50FF\u5103\u5130\u50F8\u5106\u5107\u50F6\u50FE\u510B\u510C\u50FD\u510A\u528B\u528C\u52F1\u52EF\u5648\u5642\u564C\u5635\u5641\u564A\u5649\u5646\u5658\u565A\u5640\u5633\u563D\u562C\u563E\u5638\u562A\u563A\u571A\u58AB\u589D\u58B1\u58A0\u58A3\u58AF\u58AC\u58A5\u58A1\u58FF\u5AFF\u5AF4\u5AFD\u5AF7\u5AF6\u5B03\u5AF8\u5B02\u5AF9\u5B01\u5B07\u5B05\u5B0F\u5C67\u5D99\u5D97\u5D9F\u5D92\u5DA2\u5D93\u5D95\u5DA0\u5D9C\u5DA1\u5D9A\u5D9E\u5E69\u5E5D\u5E60\u5E5C\u7DF3\u5EDB\u5EDE\u5EE1\u5F49\u5FB2\u618B\u6183\u6179\u61B1\u61B0\u61A2\u6189\u619B\u6193\u61AF\u61AD\u619F\u6192\u61AA\u61A1\u618D\u6166\u61B3\u622D\u646E\u6470\u6496\u64A0\u6485\u6497\u649C\u648F\u648B\u648A\u648C\u64A3\u649F\u6468\u64B1\u6498\u6576\u657A\u6579\u657B\u65B2\u65B3\u66B5\u66B0\u66A9\u66B2\u66B7\u66AA\u66AF\u6A00\u6A06\u6A17\u69E5\u69F8\u6A15\u69F1\u69E4\u6A20\u69FF\u69EC\u69E2\u6A1B\u6A1D\u69FE\u6A27\u69F2\u69EE\u6A14\u69F7\u69E7\u6A40\u6A08\u69E6\u69FB\u6A0D\u69FC\u69EB\u6A09\u6A04\u6A18\u6A25\u6A0F\u69F6\u6A26\u6A07\u69F4\u6A16\u6B51\u6BA5\u6BA3\u6BA2\u6BA6\u6C01\u6C00\u6BFF\u6C02\u6F41\u6F26\u6F7E\u6F87\u6FC6\u6F92\u6F8D\u6F89\u6F8C\u6F62\u6F4F\u6F85\u6F5A\u6F96\u6F76\u6F6C\u6F82\u6F55\u6F72\u6F52\u6F50\u6F57\u6F94\u6F93\u6F5D\u6F00\u6F61\u6F6B\u6F7D\u6F67\u6F90\u6F53\u6F8B\u6F69\u6F7F\u6F95\u6F63\u6F77\u6F6A\u6F7B\u71B2\u71AF\u719B\u71B0\u71A0\u719A\u71A9\u71B5\u719D\u71A5\u719E\u71A4\u71A1\u71AA\u719C\u71A7\u71B3\u7298\u729A\u7358\u7352\u735E\u735F\u7360\u735D\u735B\u7361\u735A\u7359\u7362\u7487\u7489\u748A\u7486\u7481\u747D\u7485\u7488\u747C\u7479\u7508\u7507\u757E\u7625\u761E\u7619\u761D\u761C\u7623\u761A\u7628\u761B\u769C\u769D\u769E\u769B\u778D\u778F\u7789\u7788\u78CD\u78BB\u78CF\u78CC\u78D1\u78CE\u78D4\u78C8\u78C3\u78C4\u78C9\u799A\u79A1\u79A0\u799C\u79A2\u799B\u6B76\u7A39\u7AB2\u7AB4\u7AB3\u7BB7\u7BCB\u7BBE\u7BAC\u7BCE\u7BAF\u7BB9\u7BCA\u7BB5\u7CC5\u7CC8\u7CCC\u7CCB\u7DF7\u7DDB\u7DEA\u7DE7\u7DD7\u7DE1\u7E03\u7DFA\u7DE6\u7DF6\u7DF1\u7DF0\u7DEE\u7DDF\u7F76\u7FAC\u7FB0\u7FAD\u7FED\u7FEB\u7FEA\u7FEC\u7FE6\u7FE8\u8064\u8067\u81A3\u819F\u819E\u8195\u81A2\u8199\u8197\u8216\u824F\u8253\u8252\u8250\u824E\u8251\u8524\u853B\u850F\u8500\u8529\u850E\u8509\u850D\u851F\u850A\u8527\u851C\u84FB\u852B\u84FA\u8508\u850C\u84F4\u852A\u84F2\u8515\u84F7\u84EB\u84F3\u84FC\u8512\u84EA\u84E9\u8516\u84FE\u8528\u851D\u852E\u8502\u84FD\u851E\u84F6\u8531\u8526\u84E7\u84E8\u84F0\u84EF\u84F9\u8518\u8520\u8530\u850B\u8519\u852F\u8662\u8756\u8763\u8764\u8777\u87E1\u8773\u8758\u8754\u875B\u8752\u8761\u875A\u8751\u875E\u876D\u876A\u8750\u874E\u875F\u875D\u876F\u876C\u877A\u876E\u875C\u8765\u874F\u877B\u8775\u8762\u8767\u8769\u885A\u8905\u890C\u8914\u890B\u8917\u8918\u8919\u8906\u8916\u8911\u890E\u8909\u89A2\u89A4\u89A3\u89ED\u89F0\u89EC\u8ACF\u8AC6\u8AB8\u8AD3\u8AD1\u8AD4\u8AD5\u8ABB\u8AD7\u8ABE\u8AC0\u8AC5\u8AD8\u8AC3\u8ABA\u8ABD\u8AD9\u8C3E\u8C4D\u8C8F\u8CE5\u8CDF\u8CD9\u8CE8\u8CDA\u8CDD\u8CE7\u8DA0\u8D9C\u8DA1\u8D9B\u8E20\u8E23\u8E25\u8E24\u8E2E\u8E15\u8E1B\u8E16\u8E11\u8E19\u8E26\u8E27\u8E14\u8E12\u8E18\u8E13\u8E1C\u8E17\u8E1A\u8F2C\u8F24\u8F18\u8F1A\u8F20\u8F23\u8F16\u8F17\u9073\u9070\u906F\u9067\u906B\u912F\u912B\u9129\u912A\u9132\u9126\u912E\u9185\u9186\u918A\u9181\u9182\u9184\u9180\u92D0\u92C3\u92C4\u92C0\u92D9\u92B6\u92CF\u92F1\u92DF\u92D8\u92E9\u92D7\u92DD\u92CC\u92EF\u92C2\u92E8\u92CA\u92C8\u92CE\u92E6\u92CD\u92D5\u92C9\u92E0\u92DE\u92E7\u92D1\u92D3\u92B5\u92E1\u92C6\u92B4\u957C\u95AC\u95AB\u95AE\u95B0\u96A4\u96A2\u96D3\u9705\u9708\u9702\u975A\u978A\u978E\u9788\u97D0\u97CF\u981E\u981D\u9826\u9829\u9828\u9820\u981B\u9827\u98B2\u9908\u98FA\u9911\u9914\u9916\u9917\u9915\u99DC\u99CD\u99CF\u99D3\u99D4\u99CE\u99C9\u99D6\u99D8\u99CB\u99D7\u99CC\u9AB3\u9AEC\u9AEB\u9AF3\u9AF2\u9AF1\u9B46\u9B43\u9B67\u9B74\u9B71\u9B66\u9B76\u9B75\u9B70\u9B68\u9B64\u9B6C\u9CFC\u9CFA\u9CFD\u9CFF\u9CF7\u9D07\u9D00\u9CF9\u9CFB\u9D08\u9D05\u9D04\u9E83\u9ED3\u9F0F\u9F10\u511C\u5113\u5117\u511A\u5111\u51DE\u5334\u53E1\u5670\u5660\u566E\u5673\u5666\u5663\u566D\u5672\u565E\u5677\u571C\u571B\u58C8\u58BD\u58C9\u58BF\u58BA\u58C2\u58BC\u58C6\u5B17\u5B19\u5B1B\u5B21\u5B14\u5B13\u5B10\u5B16\u5B28\u5B1A\u5B20\u5B1E\u5BEF\u5DAC\u5DB1\u5DA9\u5DA7\u5DB5\u5DB0\u5DAE\u5DAA\u5DA8\u5DB2\u5DAD\u5DAF\u5DB4\u5E67\u5E68\u5E66\u5E6F\u5EE9\u5EE7\u5EE6\u5EE8\u5EE5\u5F4B\u5FBC\u619D\u61A8\u6196\u61C5\u61B4\u61C6\u61C1\u61CC\u61BA\u61BF\u61B8\u618C\u64D7\u64D6\u64D0\u64CF\u64C9\u64BD\u6489\u64C3\u64DB\u64F3\u64D9\u6533\u657F\u657C\u65A2\u66C8\u66BE\u66C0\u66CA\u66CB\u66CF\u66BD\u66BB\u66BA\u66CC\u6723\u6A34\u6A66\u6A49\u6A67\u6A32\u6A68\u6A3E\u6A5D\u6A6D\u6A76\u6A5B\u6A51\u6A28\u6A5A\u6A3B\u6A3F\u6A41\u6A6A\u6A64\u6A50\u6A4F\u6A54\u6A6F\u6A69\u6A60\u6A3C\u6A5E\u6A56\u6A55\u6A4D\u6A4E\u6A46\u6B55\u6B54\u6B56\u6BA7\u6BAA\u6BAB\u6BC8\u6BC7\u6C04\u6C03\u6C06\u6FAD\u6FCB\u6FA3\u6FC7\u6FBC\u6FCE\u6FC8\u6F5E\u6FC4\u6FBD\u6F9E\u6FCA\u6FA8\u7004\u6FA5\u6FAE\u6FBA\u6FAC\u6FAA\u6FCF\u6FBF\u6FB8\u6FA2\u6FC9\u6FAB\u6FCD\u6FAF\u6FB2\u6FB0\u71C5\u71C2\u71BF\u71B8\u71D6\u71C0\u71C1\u71CB\u71D4\u71CA\u71C7\u71CF\u71BD\u71D8\u71BC\u71C6\u71DA\u71DB\u729D\u729E\u7369\u7366\u7367\u736C\u7365\u736B\u736A\u747F\u749A\u74A0\u7494\u7492\u7495\u74A1\u750B\u7580\u762F\u762D\u7631\u763D\u7633\u763C\u7635\u7632\u7630\u76BB\u76E6\u779A\u779D\u77A1\u779C\u779B\u77A2\u77A3\u7795\u7799\u7797\u78DD\u78E9\u78E5\u78EA\u78DE\u78E3\u78DB\u78E1\u78E2\u78ED\u78DF\u78E0\u79A4\u7A44\u7A48\u7A47\u7AB6\u7AB8\u7AB5\u7AB1\u7AB7\u7BDE\u7BE3\u7BE7\u7BDD\u7BD5\u7BE5\u7BDA\u7BE8\u7BF9\u7BD4\u7BEA\u7BE2\u7BDC\u7BEB\u7BD8\u7BDF\u7CD2\u7CD4\u7CD7\u7CD0\u7CD1\u7E12\u7E21\u7E17\u7E0C\u7E1F\u7E20\u7E13\u7E0E\u7E1C\u7E15\u7E1A\u7E22\u7E0B\u7E0F\u7E16\u7E0D\u7E14\u7E25\u7E24\u7F43\u7F7B\u7F7C\u7F7A\u7FB1\u7FEF\u802A\u8029\u806C\u81B1\u81A6\u81AE\u81B9\u81B5\u81AB\u81B0\u81AC\u81B4\u81B2\u81B7\u81A7\u81F2\u8255\u8256\u8257\u8556\u8545\u856B\u854D\u8553\u8561\u8558\u8540\u8546\u8564\u8541\u8562\u8544\u8551\u8547\u8563\u853E\u855B\u8571\u854E\u856E\u8575\u8555\u8567\u8560\u858C\u8566\u855D\u8554\u8565\u856C\u8663\u8665\u8664\u879B\u878F\u8797\u8793\u8792\u8788\u8781\u8796\u8798\u8779\u8787\u87A3\u8785\u8790\u8791\u879D\u8784\u8794\u879C\u879A\u8789\u891E\u8926\u8930\u892D\u892E\u8927\u8931\u8922\u8929\u8923\u892F\u892C\u891F\u89F1\u8AE0\u8AE2\u8AF2\u8AF4\u8AF5\u8ADD\u8B14\u8AE4\u8ADF\u8AF0\u8AC8\u8ADE\u8AE1\u8AE8\u8AFF\u8AEF\u8AFB\u8C91\u8C92\u8C90\u8CF5\u8CEE\u8CF1\u8CF0\u8CF3\u8D6C\u8D6E\u8DA5\u8DA7\u8E33\u8E3E\u8E38\u8E40\u8E45\u8E36\u8E3C\u8E3D\u8E41\u8E30\u8E3F\u8EBD\u8F36\u8F2E\u8F35\u8F32\u8F39\u8F37\u8F34\u9076\u9079\u907B\u9086\u90FA\u9133\u9135\u9136\u9193\u9190\u9191\u918D\u918F\u9327\u931E\u9308\u931F\u9306\u930F\u937A\u9338\u933C\u931B\u9323\u9312\u9301\u9346\u932D\u930E\u930D\u92CB\u931D\u92FA\u9325\u9313\u92F9\u92F7\u9334\u9302\u9324\u92FF\u9329\u9339\u9335\u932A\u9314\u930C\u930B\u92FE\u9309\u9300\u92FB\u9316\u95BC\u95CD\u95BE\u95B9\u95BA\u95B6\u95BF\u95B5\u95BD\u96A9\u96D4\u970B\u9712\u9710\u9799\u9797\u9794\u97F0\u97F8\u9835\u982F\u9832\u9924\u991F\u9927\u9929\u999E\u99EE\u99EC\u99E5\u99E4\u99F0\u99E3\u99EA\u99E9\u99E7\u9AB9\u9ABF\u9AB4\u9ABB\u9AF6\u9AFA\u9AF9\u9AF7\u9B33\u9B80\u9B85\u9B87\u9B7C\u9B7E\u9B7B\u9B82\u9B93\u9B92\u9B90\u9B7A\u9B95\u9B7D\u9B88\u9D25\u9D17\u9D20\u9D1E\u9D14\u9D29\u9D1D\u9D18\u9D22\u9D10\u9D19\u9D1F\u9E88\u9E86\u9E87\u9EAE\u9EAD\u9ED5\u9ED6\u9EFA\u9F12\u9F3D\u5126\u5125\u5122\u5124\u5120\u5129\u52F4\u5693\u568C\u568D\u5686\u5684\u5683\u567E\u5682\u567F\u5681\u58D6\u58D4\u58CF\u58D2\u5B2D\u5B25\u5B32\u5B23\u5B2C\u5B27\u5B26\u5B2F\u5B2E\u5B7B\u5BF1\u5BF2\u5DB7\u5E6C\u5E6A\u5FBE\u5FBB\u61C3\u61B5\u61BC\u61E7\u61E0\u61E5\u61E4\u61E8\u61DE\u64EF\u64E9\u64E3\u64EB\u64E4\u64E8\u6581\u6580\u65B6\u65DA\u66D2\u6A8D\u6A96\u6A81\u6AA5\u6A89\u6A9F\u6A9B\u6AA1\u6A9E\u6A87\u6A93\u6A8E\u6A95\u6A83\u6AA8\u6AA4\u6A91\u6A7F\u6AA6\u6A9A\u6A85\u6A8C\u6A92\u6B5B\u6BAD\u6C09\u6FCC\u6FA9\u6FF4\u6FD4\u6FE3\u6FDC\u6FED\u6FE7\u6FE6\u6FDE\u6FF2\u6FDD\u6FE2\u6FE8\u71E1\u71F1\u71E8\u71F2\u71E4\u71F0\u71E2\u7373\u736E\u736F\u7497\u74B2\u74AB\u7490\u74AA\u74AD\u74B1\u74A5\u74AF\u7510\u7511\u7512\u750F\u7584\u7643\u7648\u7649\u7647\u76A4\u76E9\u77B5\u77AB\u77B2\u77B7\u77B6\u77B4\u77B1\u77A8\u77F0\u78F3\u78FD\u7902\u78FB\u78FC\u78F2\u7905\u78F9\u78FE\u7904\u79AB\u79A8\u7A5C\u7A5B\u7A56\u7A58\u7A54\u7A5A\u7ABE\u7AC0\u7AC1\u7C05\u7C0F\u7BF2\u7C00\u7BFF\u7BFB\u7C0E\u7BF4\u7C0B\u7BF3\u7C02\u7C09\u7C03\u7C01\u7BF8\u7BFD\u7C06\u7BF0\u7BF1\u7C10\u7C0A\u7CE8\u7E2D\u7E3C\u7E42\u7E33\u9848\u7E38\u7E2A\u7E49\u7E40\u7E47\u7E29\u7E4C\u7E30\u7E3B\u7E36\u7E44\u7E3A\u7F45\u7F7F\u7F7E\u7F7D\u7FF4\u7FF2\u802C\u81BB\u81C4\u81CC\u81CA\u81C5\u81C7\u81BC\u81E9\u825B\u825A\u825C\u8583\u8580\u858F\u85A7\u8595\u85A0\u858B\u85A3\u857B\u85A4\u859A\u859E\u8577\u857C\u8589\u85A1\u857A\u8578\u8557\u858E\u8596\u8586\u858D\u8599\u859D\u8581\u85A2\u8582\u8588\u8585\u8579\u8576\u8598\u8590\u859F\u8668\u87BE\u87AA\u87AD\u87C5\u87B0\u87AC\u87B9\u87B5\u87BC\u87AE\u87C9\u87C3\u87C2\u87CC\u87B7\u87AF\u87C4\u87CA\u87B4\u87B6\u87BF\u87B8\u87BD\u87DE\u87B2\u8935\u8933\u893C\u893E\u8941\u8952\u8937\u8942\u89AD\u89AF\u89AE\u89F2\u89F3\u8B1E\u8B18\u8B16\u8B11\u8B05\u8B0B\u8B22\u8B0F\u8B12\u8B15\u8B07\u8B0D\u8B08\u8B06\u8B1C\u8B13\u8B1A\u8C4F\u8C70\u8C72\u8C71\u8C6F\u8C95\u8C94\u8CF9\u8D6F\u8E4E\u8E4D\u8E53\u8E50\u8E4C\u8E47\u8F43\u8F40\u9085\u907E\u9138\u919A\u91A2\u919B\u9199\u919F\u91A1\u919D\u91A0\u93A1\u9383\u93AF\u9364\u9356\u9347\u937C\u9358\u935C\u9376\u9349\u9350\u9351\u9360\u936D\u938F\u934C\u936A\u9379\u9357\u9355\u9352\u934F\u9371\u9377\u937B\u9361\u935E\u9363\u9367\u9380\u934E\u9359\u95C7\u95C0\u95C9\u95C3\u95C5\u95B7\u96AE\u96B0\u96AC\u9720\u971F\u9718\u971D\u9719\u979A\u97A1\u979C\u979E\u979D\u97D5\u97D4\u97F1\u9841\u9844\u984A\u9849\u9845\u9843\u9925\u992B\u992C\u992A\u9933\u9932\u992F\u992D\u9931\u9930\u9998\u99A3\u99A1\u9A02\u99FA\u99F4\u99F7\u99F9\u99F8\u99F6\u99FB\u99FD\u99FE\u99FC\u9A03\u9ABE\u9AFE\u9AFD\u9B01\u9AFC\u9B48\u9B9A\u9BA8\u9B9E\u9B9B\u9BA6\u9BA1\u9BA5\u9BA4\u9B86\u9BA2\u9BA0\u9BAF\u9D33\u9D41\u9D67\u9D36\u9D2E\u9D2F\u9D31\u9D38\u9D30\u9D45\u9D42\u9D43\u9D3E\u9D37\u9D40\u9D3D\u7FF5\u9D2D\u9E8A\u9E89\u9E8D\u9EB0\u9EC8\u9EDA\u9EFB\u9EFF\u9F24\u9F23\u9F22\u9F54\u9FA0\u5131\u512D\u512E\u5698\u569C\u5697\u569A\u569D\u5699\u5970\u5B3C\u5C69\u5C6A\u5DC0\u5E6D\u5E6E\u61D8\u61DF\u61ED\u61EE\u61F1\u61EA\u61F0\u61EB\u61D6\u61E9\u64FF\u6504\u64FD\u64F8\u6501\u6503\u64FC\u6594\u65DB\u66DA\u66DB\u66D8\u6AC5\u6AB9\u6ABD\u6AE1\u6AC6\u6ABA\u6AB6\u6AB7\u6AC7\u6AB4\u6AAD\u6B5E\u6BC9\u6C0B\u7007\u700C\u700D\u7001\u7005\u7014\u700E\u6FFF\u7000\u6FFB\u7026\u6FFC\u6FF7\u700A\u7201\u71FF\u71F9\u7203\u71FD\u7376\u74B8\u74C0\u74B5\u74C1\u74BE\u74B6\u74BB\u74C2\u7514\u7513\u765C\u7664\u7659\u7650\u7653\u7657\u765A\u76A6\u76BD\u76EC\u77C2\u77BA\u78FF\u790C\u7913\u7914\u7909\u7910\u7912\u7911\u79AD\u79AC\u7A5F\u7C1C\u7C29\u7C19\u7C20\u7C1F\u7C2D\u7C1D\u7C26\u7C28\u7C22\u7C25\u7C30\u7E5C\u7E50\u7E56\u7E63\u7E58\u7E62\u7E5F\u7E51\u7E60\u7E57\u7E53\u7FB5\u7FB3\u7FF7\u7FF8\u8075\u81D1\u81D2\u81D0\u825F\u825E\u85B4\u85C6\u85C0\u85C3\u85C2\u85B3\u85B5\u85BD\u85C7\u85C4\u85BF\u85CB\u85CE\u85C8\u85C5\u85B1\u85B6\u85D2\u8624\u85B8\u85B7\u85BE\u8669\u87E7\u87E6\u87E2\u87DB\u87EB\u87EA\u87E5\u87DF\u87F3\u87E4\u87D4\u87DC\u87D3\u87ED\u87D8\u87E3\u87A4\u87D7\u87D9\u8801\u87F4\u87E8\u87DD\u8953\u894B\u894F\u894C\u8946\u8950\u8951\u8949\u8B2A\u8B27\u8B23\u8B33\u8B30\u8B35\u8B47\u8B2F\u8B3C\u8B3E\u8B31\u8B25\u8B37\u8B26\u8B36\u8B2E\u8B24\u8B3B\u8B3D\u8B3A\u8C42\u8C75\u8C99\u8C98\u8C97\u8CFE\u8D04\u8D02\u8D00\u8E5C\u8E62\u8E60\u8E57\u8E56\u8E5E\u8E65\u8E67\u8E5B\u8E5A\u8E61\u8E5D\u8E69\u8E54\u8F46\u8F47\u8F48\u8F4B\u9128\u913A\u913B\u913E\u91A8\u91A5\u91A7\u91AF\u91AA\u93B5\u938C\u9392\u93B7\u939B\u939D\u9389\u93A7\u938E\u93AA\u939E\u93A6\u9395\u9388\u9399\u939F\u938D\u93B1\u9391\u93B2\u93A4\u93A8\u93B4\u93A3\u93A5\u95D2\u95D3\u95D1\u96B3\u96D7\u96DA\u5DC2\u96DF\u96D8\u96DD\u9723\u9722\u9725\u97AC\u97AE\u97A8\u97AB\u97A4\u97AA\u97A2\u97A5\u97D7\u97D9\u97D6\u97D8\u97FA\u9850\u9851\u9852\u98B8\u9941\u993C\u993A\u9A0F\u9A0B\u9A09\u9A0D\u9A04\u9A11\u9A0A\u9A05\u9A07\u9A06\u9AC0\u9ADC\u9B08\u9B04\u9B05\u9B29\u9B35\u9B4A\u9B4C\u9B4B\u9BC7\u9BC6\u9BC3\u9BBF\u9BC1\u9BB5\u9BB8\u9BD3\u9BB6\u9BC4\u9BB9\u9BBD\u9D5C\u9D53\u9D4F\u9D4A\u9D5B\u9D4B\u9D59\u9D56\u9D4C\u9D57\u9D52\u9D54\u9D5F\u9D58\u9D5A\u9E8E\u9E8C\u9EDF\u9F01\u9F00\u9F16\u9F25\u9F2B\u9F2A\u9F29\u9F28\u9F4C\u9F55\u5134\u5135\u5296\u52F7\u53B4\u56AB\u56AD\u56A6\u56A7\u56AA\u56AC\u58DA\u58DD\u58DB\u5912\u5B3D\u5B3E\u5B3F\u5DC3\u5E70\u5FBF\u61FB\u6507\u6510\u650D\u6509\u650C\u650E\u6584\u65DE\u65DD\u66DE\u6AE7\u6AE0\u6ACC\u6AD1\u6AD9\u6ACB\u6ADF\u6ADC\u6AD0\u6AEB\u6ACF\u6ACD\u6ADE\u6B60\u6BB0\u6C0C\u7019\u7027\u7020\u7016\u702B\u7021\u7022\u7023\u7029\u7017\u7024\u701C\u702A\u720C\u720A\u7207\u7202\u7205\u72A5\u72A6\u72A4\u72A3\u72A1\u74CB\u74C5\u74B7\u74C3\u7516\u7660\u77C9\u77CA\u77C4\u77F1\u791D\u791B\u7921\u791C\u7917\u791E\u79B0\u7A67\u7A68\u7C33\u7C3C\u7C39\u7C2C\u7C3B\u7CEC\u7CEA\u7E76\u7E75\u7E78\u7E70\u7E77\u7E6F\u7E7A\u7E72\u7E74\u7E68\u7F4B\u7F4A\u7F83\u7F86\u7FB7\u7FFD\u7FFE\u8078\u81D7\u81D5\u8264\u8261\u8263\u85EB\u85F1\u85ED\u85D9\u85E1\u85E8\u85DA\u85D7\u85EC\u85F2\u85F8\u85D8\u85DF\u85E3\u85DC\u85D1\u85F0\u85E6\u85EF\u85DE\u85E2\u8800\u87FA\u8803\u87F6\u87F7\u8809\u880C\u880B\u8806\u87FC\u8808\u87FF\u880A\u8802\u8962\u895A\u895B\u8957\u8961\u895C\u8958\u895D\u8959\u8988\u89B7\u89B6\u89F6\u8B50\u8B48\u8B4A\u8B40\u8B53\u8B56\u8B54\u8B4B\u8B55\u8B51\u8B42\u8B52\u8B57\u8C43\u8C77\u8C76\u8C9A\u8D06\u8D07\u8D09\u8DAC\u8DAA\u8DAD\u8DAB\u8E6D\u8E78\u8E73\u8E6A\u8E6F\u8E7B\u8EC2\u8F52\u8F51\u8F4F\u8F50\u8F53\u8FB4\u9140\u913F\u91B0\u91AD\u93DE\u93C7\u93CF\u93C2\u93DA\u93D0\u93F9\u93EC\u93CC\u93D9\u93A9\u93E6\u93CA\u93D4\u93EE\u93E3\u93D5\u93C4\u93CE\u93C0\u93D2\u93E7\u957D\u95DA\u95DB\u96E1\u9729\u972B\u972C\u9728\u9726\u97B3\u97B7\u97B6\u97DD\u97DE\u97DF\u985C\u9859\u985D\u9857\u98BF\u98BD\u98BB\u98BE\u9948\u9947\u9943\u99A6\u99A7\u9A1A\u9A15\u9A25\u9A1D\u9A24\u9A1B\u9A22\u9A20\u9A27\u9A23\u9A1E\u9A1C\u9A14\u9AC2\u9B0B\u9B0A\u9B0E\u9B0C\u9B37\u9BEA\u9BEB\u9BE0\u9BDE\u9BE4\u9BE6\u9BE2\u9BF0\u9BD4\u9BD7\u9BEC\u9BDC\u9BD9\u9BE5\u9BD5\u9BE1\u9BDA\u9D77\u9D81\u9D8A\u9D84\u9D88\u9D71\u9D80\u9D78\u9D86\u9D8B\u9D8C\u9D7D\u9D6B\u9D74\u9D75\u9D70\u9D69\u9D85\u9D73\u9D7B\u9D82\u9D6F\u9D79\u9D7F\u9D87\u9D68\u9E94\u9E91\u9EC0\u9EFC\u9F2D\u9F40\u9F41\u9F4D\u9F56\u9F57\u9F58\u5337\u56B2\u56B5\u56B3\u58E3\u5B45\u5DC6\u5DC7\u5EEE\u5EEF\u5FC0\u5FC1\u61F9\u6517\u6516\u6515\u6513\u65DF\u66E8\u66E3\u66E4\u6AF3\u6AF0\u6AEA\u6AE8\u6AF9\u6AF1\u6AEE\u6AEF\u703C\u7035\u702F\u7037\u7034\u7031\u7042\u7038\u703F\u703A\u7039\u7040\u703B\u7033\u7041\u7213\u7214\u72A8\u737D\u737C\u74BA\u76AB\u76AA\u76BE\u76ED\u77CC\u77CE\u77CF\u77CD\u77F2\u7925\u7923\u7927\u7928\u7924\u7929\u79B2\u7A6E\u7A6C\u7A6D\u7AF7\u7C49\u7C48\u7C4A\u7C47\u7C45\u7CEE\u7E7B\u7E7E\u7E81\u7E80\u7FBA\u7FFF\u8079\u81DB\u81D9\u820B\u8268\u8269\u8622\u85FF\u8601\u85FE\u861B\u8600\u85F6\u8604\u8609\u8605\u860C\u85FD\u8819\u8810\u8811\u8817\u8813\u8816\u8963\u8966\u89B9\u89F7\u8B60\u8B6A\u8B5D\u8B68\u8B63\u8B65\u8B67\u8B6D\u8DAE\u8E86\u8E88\u8E84\u8F59\u8F56\u8F57\u8F55\u8F58\u8F5A\u908D\u9143\u9141\u91B7\u91B5\u91B2\u91B3\u940B\u9413\u93FB\u9420\u940F\u9414\u93FE\u9415\u9410\u9428\u9419\u940D\u93F5\u9400\u93F7\u9407\u940E\u9416\u9412\u93FA\u9409\u93F8\u940A\u93FF\u93FC\u940C\u93F6\u9411\u9406\u95DE\u95E0\u95DF\u972E\u972F\u97B9\u97BB\u97FD\u97FE\u9860\u9862\u9863\u985F\u98C1\u98C2\u9950\u994E\u9959\u994C\u994B\u9953\u9A32\u9A34\u9A31\u9A2C\u9A2A\u9A36\u9A29\u9A2E\u9A38\u9A2D\u9AC7\u9ACA\u9AC6\u9B10\u9B12\u9B11\u9C0B\u9C08\u9BF7\u9C05\u9C12\u9BF8\u9C40\u9C07\u9C0E\u9C06\u9C17\u9C14\u9C09\u9D9F\u9D99\u9DA4\u9D9D\u9D92\u9D98\u9D90\u9D9B\u9DA0\u9D94\u9D9C\u9DAA\u9D97\u9DA1\u9D9A\u9DA2\u9DA8\u9D9E\u9DA3\u9DBF\u9DA9\u9D96\u9DA6\u9DA7\u9E99\u9E9B\u9E9A\u9EE5\u9EE4\u9EE7\u9EE6\u9F30\u9F2E\u9F5B\u9F60\u9F5E\u9F5D\u9F59\u9F91\u513A\u5139\u5298\u5297\u56C3\u56BD\u56BE\u5B48\u5B47\u5DCB\u5DCF\u5EF1\u61FD\u651B\u6B02\u6AFC\u6B03\u6AF8\u6B00\u7043\u7044\u704A\u7048\u7049\u7045\u7046\u721D\u721A\u7219\u737E\u7517\u766A\u77D0\u792D\u7931\u792F\u7C54\u7C53\u7CF2\u7E8A\u7E87\u7E88\u7E8B\u7E86\u7E8D\u7F4D\u7FBB\u8030\u81DD\u8618\u862A\u8626\u861F\u8623\u861C\u8619\u8627\u862E\u8621\u8620\u8629\u861E\u8625\u8829\u881D\u881B\u8820\u8824\u881C\u882B\u884A\u896D\u8969\u896E\u896B\u89FA\u8B79\u8B78\u8B45\u8B7A\u8B7B\u8D10\u8D14\u8DAF\u8E8E\u8E8C\u8F5E\u8F5B\u8F5D\u9146\u9144\u9145\u91B9\u943F\u943B\u9436\u9429\u943D\u943C\u9430\u9439\u942A\u9437\u942C\u9440\u9431\u95E5\u95E4\u95E3\u9735\u973A\u97BF\u97E1\u9864\u98C9\u98C6\u98C0\u9958\u9956\u9A39\u9A3D\u9A46\u9A44\u9A42\u9A41\u9A3A\u9A3F\u9ACD\u9B15\u9B17\u9B18\u9B16\u9B3A\u9B52\u9C2B\u9C1D\u9C1C\u9C2C\u9C23\u9C28\u9C29\u9C24\u9C21\u9DB7\u9DB6\u9DBC\u9DC1\u9DC7\u9DCA\u9DCF\u9DBE\u9DC5\u9DC3\u9DBB\u9DB5\u9DCE\u9DB9\u9DBA\u9DAC\u9DC8\u9DB1\u9DAD\u9DCC\u9DB3\u9DCD\u9DB2\u9E7A\u9E9C\u9EEB\u9EEE\u9EED\u9F1B\u9F18\u9F1A\u9F31\u9F4E\u9F65\u9F64\u9F92\u4EB9\u56C6\u56C5\u56CB\u5971\u5B4B\u5B4C\u5DD5\u5DD1\u5EF2\u6521\u6520\u6526\u6522\u6B0B\u6B08\u6B09\u6C0D\u7055\u7056\u7057\u7052\u721E\u721F\u72A9\u737F\u74D8\u74D5\u74D9\u74D7\u766D\u76AD\u7935\u79B4\u7A70\u7A71\u7C57\u7C5C\u7C59\u7C5B\u7C5A\u7CF4\u7CF1\u7E91\u7F4F\u7F87\u81DE\u826B\u8634\u8635\u8633\u862C\u8632\u8636\u882C\u8828\u8826\u882A\u8825\u8971\u89BF\u89BE\u89FB\u8B7E\u8B84\u8B82\u8B86\u8B85\u8B7F\u8D15\u8E95\u8E94\u8E9A\u8E92\u8E90\u8E96\u8E97\u8F60\u8F62\u9147\u944C\u9450\u944A\u944B\u944F\u9447\u9445\u9448\u9449\u9446\u973F\u97E3\u986A\u9869\u98CB\u9954\u995B\u9A4E\u9A53\u9A54\u9A4C\u9A4F\u9A48\u9A4A\u9A49\u9A52\u9A50\u9AD0\u9B19\u9B2B\u9B3B\u9B56\u9B55\u9C46\u9C48\u9C3F\u9C44\u9C39\u9C33\u9C41\u9C3C\u9C37\u9C34\u9C32\u9C3D\u9C36\u9DDB\u9DD2\u9DDE\u9DDA\u9DCB\u9DD0\u9DDC\u9DD1\u9DDF\u9DE9\u9DD9\u9DD8\u9DD6\u9DF5\u9DD5\u9DDD\u9EB6\u9EF0\u9F35\u9F33\u9F32\u9F42\u9F6B\u9F95\u9FA2\u513D\u5299\u58E8\u58E7\u5972\u5B4D\u5DD8\u882F\u5F4F\u6201\u6203\u6204\u6529\u6525\u6596\u66EB\u6B11\u6B12\u6B0F\u6BCA\u705B\u705A\u7222\u7382\u7381\u7383\u7670\u77D4\u7C67\u7C66\u7E95\u826C\u863A\u8640\u8639\u863C\u8631\u863B\u863E\u8830\u8832\u882E\u8833\u8976\u8974\u8973\u89FE\u8B8C\u8B8E\u8B8B\u8B88\u8C45\u8D19\u8E98\u8F64\u8F63\u91BC\u9462\u9455\u945D\u9457\u945E\u97C4\u97C5\u9800\u9A56\u9A59\u9B1E\u9B1F\u9B20\u9C52\u9C58\u9C50\u9C4A\u9C4D\u9C4B\u9C55\u9C59\u9C4C\u9C4E\u9DFB\u9DF7\u9DEF\u9DE3\u9DEB\u9DF8\u9DE4\u9DF6\u9DE1\u9DEE\u9DE6\u9DF2\u9DF0\u9DE2\u9DEC\u9DF4\u9DF3\u9DE8\u9DED\u9EC2\u9ED0\u9EF2\u9EF3\u9F06\u9F1C\u9F38\u9F37\u9F36\u9F43\u9F4F\u9F71\u9F70\u9F6E\u9F6F\u56D3\u56CD\u5B4E\u5C6D\u652D\u66ED\u66EE\u6B13\u705F\u7061\u705D\u7060\u7223\u74DB\u74E5\u77D5\u7938\u79B7\u79B6\u7C6A\u7E97\u7F89\u826D\u8643\u8838\u8837\u8835\u884B\u8B94\u8B95\u8E9E\u8E9F\u8EA0\u8E9D\u91BE\u91BD\u91C2\u946B\u9468\u9469\u96E5\u9746\u9743\u9747\u97C7\u97E5\u9A5E\u9AD5\u9B59\u9C63\u9C67\u9C66\u9C62\u9C5E\u9C60\u9E02\u9DFE\u9E07\u9E03\u9E06\u9E05\u9E00\u9E01\u9E09\u9DFF\u9DFD\u9E04\u9EA0\u9F1E\u9F46\u9F74\u9F75\u9F76\u56D4\u652E\u65B8\u6B18\u6B19\u6B17\u6B1A\u7062\u7226\u72AA\u77D8\u77D9\u7939\u7C69\u7C6B\u7CF6\u7E9A\u7E98\u7E9B\u7E99\u81E0\u81E1\u8646\u8647\u8648\u8979\u897A\u897C\u897B\u89FF\u8B98\u8B99\u8EA5\u8EA4\u8EA3\u946E\u946D\u946F\u9471\u9473\u9749\u9872\u995F\u9C68\u9C6E\u9C6D\u9E0B\u9E0D\u9E10\u9E0F\u9E12\u9E11\u9EA1\u9EF5\u9F09\u9F47\u9F78\u9F7B\u9F7A\u9F79\u571E\u7066\u7C6F\u883C\u8DB2\u8EA6\u91C3\u9474\u9478\u9476\u9475\u9A60\u9C74\u9C73\u9C71\u9C75\u9E14\u9E13\u9EF6\u9F0A\u9FA4\u7068\u7065\u7CF7\u866A\u883E\u883D\u883F\u8B9E\u8C9C\u8EA9\u8EC9\u974B\u9873\u9874\u98CC\u9961\u99AB\u9A64\u9A66\u9A67\u9B24\u9E15\u9E17\u9F48\u6207\u6B1E\u7227\u864C\u8EA8\u9482\u9480\u9481\u9A69\u9A68\u9B2E\u9E19\u7229\u864B\u8B9F\u9483\u9C79\u9EB7\u7675\u9A6B\u9C7A\u9E1D\u7069\u706A\u9EA4\u9F7E\u9F49\u9F98\u7881\u92B9\u88CF\u58BB\u6052\u7CA7\u5AFA\u2554\u2566\u2557\u2560\u256C\u2563\u255A\u2569\u255D\u2552\u2564\u2555\u255E\u256A\u2561\u2558\u2567\u255B\u2553\u2565\u2556\u255F\u256B\u2562\u2559\u2568\u255C\u2551\u2550\u256D\u256E\u2570\u256F\uFFED\u0547\u92DB\u05DF\u3FC5\u854C\u42B5\u73EF\u51B5\u3649\u4942\u89E4\u9344\u19DB\u82EE\u3CC8\u783C\u6744\u62DF\u4933\u89AA\u02A0\u6BB3\u1305\u4FAB\u24ED\u5008\u6D29\u7A84\u3600\u4AB1\u2513\u5029\u037E\u5FA4\u0380\u0347\u6EDB\u041F\u507D\u5101\u347A\u510E\u986C\u3743\u8416\u49A4\u0487\u5160\u33B4\u516A\u0BFF\u20FC\u02E5\u2530\u058E\u3233\u1983\u5B82\u877D\u05B3\u3C99\u51B2\u51B8\u9D34\u51C9\u51CF\u51D1\u3CDC\u51D3\u4AA6\u51B3\u51E2\u5342\u51ED\u83CD\u693E\u372D\u5F7B\u520B\u5226\u523C\u52B5\u5257\u5294\u52B9\u52C5\u7C15\u8542\u52E0\u860D\u6B13\u5305\u8ADE\u5549\u6ED9\u3F80\u0954\u3FEC\u5333\u5344\u0BE2\u6CCB\u1726\u681B\u73D5\u604A\u3EAA\u38CC\u16E8\u71DD\u44A2\u536D\u5374\u86AB\u537E\u537F\u1596\u1613\u77E6\u5393\u8A9B\u53A0\u53AB\u53AE\u73A7\u5772\u3F59\u739C\u53C1\u53C5\u6C49\u4E49\u57FE\u53D9\u3AAB\u0B8F\u53E0\u3FEB\u2DA3\u53F6\u0C77\u5413\u7079\u552B\u6657\u6D5B\u546D\u6B53\u0D74\u555D\u548F\u54A4\u47A6\u170D\u0EDD\u3DB4\u0D4D\u89BC\u2698\u5547\u4CED\u542F\u7417\u5586\u55A9\u5605\u18D7\u403A\u4552\u4435\u66B3\u10B4\u5637\u66CD\u328A\u66A4\u66AD\u564D\u564F\u78F1\u56F1\u9787\u53FE\u5700\u56EF\u56ED\u8B66\u3623\u124F\u5746\u41A5\u6C6E\u708B\u5742\u36B1\u6C7E\u57E6\u1416\u5803\u1454\u4363\u5826\u4BF5\u585C\u58AA\u3561\u58E0\u58DC\u123C\u58FB\u5BFF\u5743\uA150\u4278\u93D3\u35A1\u591F\u68A6\u36C3\u6E59\u163E\u5A24\u5553\u1692\u8505\u59C9\u0D4E\u6C81\u6D2A\u17DC\u59D9\u17FB\u17B2\u6DA6\u6D71\u1828\u16D5\u59F9\u6E45\u5AAB\u5A63\u36E6\u49A9\u5A77\u3708\u5A96\u7465\u5AD3\u6FA1\u2554\u3D85\u1911\u3732\u16B8\u5E83\u52D0\u5B76\u6588\u5B7C\u7A0E\u4004\u485D\u0204\u5BD5\u6160\u1A34\u59CC\u05A5\u5BF3\u5B9D\u4D10\u5C05\u1B44\u5C13\u73CE\u5C14\u1CA5\u6B28\u5C49\u48DD\u5C85\u5CE9\u5CEF\u5D8B\u1DF9\u1E37\u5D10\u5D18\u5D46\u1EA4\u5CBA\u5DD7\u82FC\u382D\u4901\u2049\u2173\u8287\u3836\u3BC2\u5E2E\u6A8A\u5E75\u5E7A\u44BC\u0CD3\u53A6\u4EB7\u5ED0\u53A8\u1771\u5E09\u5EF4\u8482\u5EF9\u5EFB\u38A0\u5EFC\u683E\u941B\u5F0D\u01C1\uF894\u3ADE\u48AE\u133A\u5F3A\u6888\u23D0\u5F58\u2471\u5F63\u97BD\u6E6E\u5F72\u9340\u8A36\u5FA7\u5DB6\u3D5F\u5250\u1F6A\u70F8\u2668\u91D6\u029E\u8A29\u6031\u6685\u1877\u3963\u3DC7\u3639\u5790\u27B4\u7971\u3E40\u609E\u60A4\u60B3\u4982\u498F\u7A53\u74A4\u50E1\u5AA0\u6164\u8424\u6142\uF8A6\u6ED2\u6181\u51F4\u0656\u6187\u5BAA\u3FB7\u285F\u61D3\u8B9D\u995D\u61D0\u3932\u2980\u28C1\u6023\u615C\u651E\u638B\u0118\u62C5\u1770\u62D5\u2E0D\u636C\u49DF\u3A17\u6438\u63F8\u138E\u17FC\u6490\u6F8A\u2E36\u9814\u408C\u571D\u64E1\u64E5\u947B\u3A66\u643A\u3A57\u654D\u6F16\u4A28\u4A23\u6585\u656D\u655F\u307E\u65B5\u4940\u4B37\u65D1\u40D8\u1829\u65E0\u65E3\u5FDF\u3400\u6618\u31F7\u31F8\u6644\u31A4\u31A5\u664B\u0E75\u6667\u51E6\u6673\u6674\u1E3D\u3231\u85F4\u31C8\u5313\u77C5\u28F7\u99A4\u6702\u439C\u4A21\u3B2B\u69FA\u37C2\u675E\u6767\u6762\u41CD\u90ED\u67D7\u44E9\u6822\u6E50\u923C\u6801\u33E6\u6DA0\u685D\u346F\u69E1\u6A0B\u8ADF\u6973\u68C3\u35CD\u6901\u6900\u3D32\u3A01\u363C\u3B80\u67AC\u6961\u8A4A\u42FC\u6936\u6998\u3BA1\u03C9\u8363\u5090\u69F9\u3659\u212A\u6A45\u3703\u6A9D\u3BF3\u67B1\u6AC8\u919C\u3C0D\u6B1D\u0923\u60DE\u6B35\u6B74\u27CD\u6EB5\u3ADB\u03B5\u1958\u3740\u5421\u3B5A\u6BE1\u3EFC\u6BDC\u6C37\u248B\u48F1\u6B51\u6C5A\u8226\u6C79\u3DBC\u44C5\u3DBD\u41A4\u490C\u4900\u3CC9\u36E5\u3CEB\u0D32\u9B83\u31F9\u2491\u7F8F\u6837\u6D25\u6DA1\u6DEB\u6D96\u6D5C\u6E7C\u6F04\u497F\u4085\u6E72\u8533\u6F74\u51C7\u6C9C\u6E1D\u842E\u8B21\u6E2F\u3E2F\u7453\u3F82\u79CC\u6E4F\u5A91\u304B\u6FF8\u370D\u6F9D\u3E30\u6EFA\u1497\u403D\u4555\u93F0\u6F44\u6F5C\u3D4E\u6F74\u9170\u3D3B\u6F9F\u4144\u6FD3\u4091\u4155\u4039\u3FF0\u3FB4\u413F\u51DF\u4156\u4157\u4140\u61DD\u704B\u707E\u70A7\u7081\u70CC\u70D5\u70D6\u70DF\u4104\u3DE8\u71B4\u7196\u4277\u712B\u7145\u5A88\u714A\u716E\u5C9C\u4365\u714F\u9362\u42C1\u712C\u445A\u4A27\u4A22\u71BA\u8BE8\u70BD\u720E\u9442\u7215\u5911\u9443\u7224\u9341\u5605\u722E\u7240\u4974\u68BD\u7255\u7257\u3E55\u3044\u680D\u6F3D\u7282\u732A\u732B\u4823\u882B\u48ED\u8804\u7328\u732E\u73CF\u73AA\u0C3A\u6A2E\u73C9\u7449\u41E2\u16E7\u4A24\u6623\u36C5\u49B7\u498D\u49FB\u73F7\u7415\u6903\u4A26\u7439\u05C3\u3ED7\u745C\u28AD\u7460\u8EB2\u7447\u73E4\u7476\u83B9\u746C\u3730\u7474\u93F1\u6A2C\u7482\u4953\u4A8C\u415F\u4A79\u8B8F\u5B46\u8C03\u189E\u74C8\u1988\u750E\u74E9\u751E\u8ED9\u1A4B\u5BD7\u8EAC\u9385\u754D\u754A\u7567\u756E\u4F82\u3F04\u4D13\u758E\u745D\u759E\u75B4\u7602\u762C\u7651\u764F\u766F\u7676\u63F5\u7690\u81EF\u37F8\u6911\u690E\u76A1\u76A5\u76B7\u76CC\u6F9F\u8462\u509D\u517D\u1E1C\u771E\u7726\u7740\u64AF\u5220\u7758\u32AC\u77AF\u8964\u8968\u16C1\u77F4\u7809\u1376\u4A12\u68CA\u78AF\u78C7\u78D3\u96A5\u792E\u55E0\u78D7\u7934\u78B1\u760C\u8FB8\u8884\u8B2B\u6083\u261C\u7986\u8900\u6902\u7980\u5857\u799D\u7B39\u793C\u79A9\u6E2A\u7126\u3EA8\u79C6\u910D\u79D4";
+
+ private static boolean readBit(int i) {
+ return (ASTRALNESS.charAt(i >> 4) & (1 << (i & 0xF))) != 0;
+ }
+
+ static char lowBits(int pointer) {
+ if (pointer < 942) {
+ return '\u0000';
+ }
+ if (pointer < 1068) {
+ return TABLE0.charAt(pointer - 942);
+ }
+ if (pointer < 1099) {
+ return '\u0000';
+ }
+ if (pointer < 1172) {
+ return TABLE1.charAt(pointer - 1099);
+ }
+ if (pointer < 1256) {
+ return '\u0000';
+ }
+ if (pointer < 5466) {
+ return TABLE2.charAt(pointer - 1256);
+ }
+ if (pointer < 5495) {
+ return '\u0000';
+ }
+ if (pointer < 11214) {
+ return TABLE3.charAt(pointer - 5495);
+ }
+ if (pointer < 11254) {
+ return '\u0000';
+ }
+ if (pointer < 19782) {
+ return TABLE4.charAt(pointer - 11254);
+ }
+ return '\u0000';
+ }
+
+ static boolean isAstral(int pointer) {
+ if (pointer < 947) {
+ return false;
+ }
+ if (pointer < 1119) {
+ return readBit(0 + (pointer - 947));
+ }
+ if (pointer < 1256) {
+ return false;
+ }
+ if (pointer < 1269) {
+ return readBit(172 + (pointer - 1256));
+ }
+ if (pointer < 1336) {
+ return false;
+ }
+ if (pointer < 1364) {
+ return readBit(185 + (pointer - 1336));
+ }
+ if (pointer < 1413) {
+ return false;
+ }
+ if (pointer < 1912) {
+ return readBit(213 + (pointer - 1413));
+ }
+ if (pointer < 2012) {
+ return false;
+ }
+ if (pointer < 3800) {
+ return readBit(712 + (pointer - 2012));
+ }
+ if (pointer < 3883) {
+ return false;
+ }
+ if (pointer == 3883) {
+ return true;
+ }
+ if (pointer < 3985) {
+ return false;
+ }
+ if (pointer < 5024) {
+ return readBit(2501 + (pointer - 3985));
+ }
+ if (pointer < 11205) {
+ return false;
+ }
+ if (pointer < 11214) {
+ return readBit(3540 + (pointer - 11205));
+ }
+ if (pointer < 18997) {
+ return false;
+ }
+ if (pointer < 19782) {
+ return readBit(3549 + (pointer - 18997));
+ }
+ return false;
+ }
+
+ public static int findPointer(char lowBits, boolean isAstral) {
+ if (!isAstral) {
+ switch (lowBits) {
+ case 0x2550:
+ return 18991;
+ case 0x255E:
+ return 18975;
+ case 0x2561:
+ return 18977;
+ case 0x256A:
+ return 18976;
+ case 0x5341:
+ return 5512;
+ case 0x5345:
+ return 5599;
+ default:
+ break;
+ }
+ }
+ for (int i = 3768; i < TABLE2.length(); i++) {
+ if (TABLE2.charAt(i) == lowBits) {
+ int pointer = i + 1256;
+ if (isAstral == isAstral(pointer)) {
+ return pointer;
+ }
+ }
+ }
+ for (int i = 0; i < TABLE3.length(); i++) {
+ if (TABLE3.charAt(i) == lowBits) {
+ int pointer = i + 5495;
+ if (isAstral == isAstral(pointer)) {
+ return pointer;
+ }
+ }
+ }
+ for (int i = 0; i < TABLE4.length(); i++) {
+ if (TABLE4.charAt(i) == lowBits) {
+ int pointer = i + 11254;
+ if (isAstral == isAstral(pointer)) {
+ return pointer;
+ }
+ }
+ }
+ return 0;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Decoder.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Decoder.java
new file mode 100644
index 000000000..cc56b892f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Decoder.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CoderResult;
+
+public class Big5Decoder extends Decoder {
+
+ private int big5Lead = 0;
+
+ private char pendingTrail = '\u0000';
+
+ protected Big5Decoder(Charset cs) {
+ super(cs, 0.5f, 1.0f);
+ }
+
+ @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
+ assert !(this.report && (big5Lead != 0)):
+ "When reporting, this method should never return with big5Lead set.";
+ if (pendingTrail != '\u0000') {
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ out.put(pendingTrail);
+ pendingTrail = '\u0000';
+ }
+ for (;;) {
+ if (!in.hasRemaining()) {
+ return CoderResult.UNDERFLOW;
+ }
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ int b = ((int) in.get() & 0xFF);
+ if (big5Lead == 0) {
+ if (b <= 0x7F) {
+ out.put((char) b);
+ continue;
+ }
+ if (b >= 0x81 && b <= 0xFE) {
+ if (this.report && !in.hasRemaining()) {
+ // The Java API is badly documented. Need to do this
+ // crazy thing and hope the caller knows about the
+ // undocumented aspects of the API!
+ in.position(in.position() - 1);
+ return CoderResult.UNDERFLOW;
+ }
+ big5Lead = b;
+ continue;
+ }
+ if (this.report) {
+ in.position(in.position() - 1);
+ return CoderResult.malformedForLength(1);
+ }
+ out.put('\uFFFD');
+ continue;
+ }
+ int lead = big5Lead;
+ big5Lead = 0;
+ int offset = (b < 0x7F) ? 0x40 : 0x62;
+ if ((b >= 0x40 && b <= 0x7E) || (b >= 0xA1 && b <= 0xFE)) {
+ int pointer = (lead - 0x81) * 157 + (b - offset);
+ char outTrail;
+ switch (pointer) {
+ case 1133:
+ out.put('\u00CA');
+ outTrail = '\u0304';
+ break;
+ case 1135:
+ out.put('\u00CA');
+ outTrail = '\u030C';
+ break;
+ case 1164:
+ out.put('\u00EA');
+ outTrail = '\u0304';
+ break;
+ case 1166:
+ out.put('\u00EA');
+ outTrail = '\u030C';
+ break;
+ default:
+ char lowBits = Big5Data.lowBits(pointer);
+ if (lowBits == '\u0000') {
+ // The following |if| block fixes
+ // https://github.com/whatwg/encoding/issues/5
+ if (b <= 0x7F) {
+ // prepend byte to stream
+ // Always legal, since we've always just read a byte
+ // if we come here.
+ in.position(in.position() - 1);
+ }
+ if (this.report) {
+ // This can go past the start of the buffer
+ // if the caller does not conform to the
+ // undocumented aspects of the API.
+ in.position(in.position() - 1);
+ return CoderResult.malformedForLength(b <= 0x7F ? 1 : 2);
+ }
+ out.put('\uFFFD');
+ continue;
+ }
+ if (Big5Data.isAstral(pointer)) {
+ int codePoint = lowBits | 0x20000;
+ out.put((char) (0xD7C0 + (codePoint >> 10)));
+ outTrail = (char) (0xDC00 + (codePoint & 0x3FF));
+ break;
+ }
+ out.put(lowBits);
+ continue;
+ }
+ if (!out.hasRemaining()) {
+ pendingTrail = outTrail;
+ return CoderResult.OVERFLOW;
+ }
+ out.put(outTrail);
+ continue;
+ }
+ // pointer is null
+ if (b <= 0x7F) {
+ // prepend byte to stream
+ // Always legal, since we've always just read a byte
+ // if we come here.
+ in.position(in.position() - 1);
+ }
+ if (this.report) {
+ // if position() == 0, the caller is not using the
+ // undocumented part of the API right and the line
+ // below will throw!
+ in.position(in.position() - 1);
+ return CoderResult.malformedForLength(b <= 0x7F ? 1 : 2);
+ }
+ out.put('\uFFFD');
+ continue;
+ }
+ }
+
+ @Override protected CoderResult implFlush(CharBuffer out) {
+ if (pendingTrail != '\u0000') {
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ out.put(pendingTrail);
+ pendingTrail = '\u0000';
+ }
+ if (big5Lead != 0) {
+ assert !this.report: "How come big5Lead got to be non-zero when decodeLoop() returned in the reporting mode?";
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ out.put('\uFFFD');
+ big5Lead = 0;
+ }
+ return CoderResult.UNDERFLOW;
+ }
+
+ @Override protected void implReset() {
+ big5Lead = 0;
+ pendingTrail = '\u0000';
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Encoder.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Encoder.java
new file mode 100644
index 000000000..de5132151
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Big5Encoder.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CoderResult;
+
+public class Big5Encoder extends Encoder {
+
+ private char utf16Lead = '\u0000';
+
+ private byte pendingTrail = 0;
+
+ protected Big5Encoder(Charset cs) {
+ super(cs, 1.5f, 2.0f);
+ }
+
+ @Override protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
+ assert !((this.reportMalformed || this.reportUnmappable) && (utf16Lead != '\u0000')):
+ "When reporting, this method should never return with utf16Lead set.";
+ if (pendingTrail != 0) {
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ out.put(pendingTrail);
+ pendingTrail = 0;
+ }
+ for (;;) {
+ if (!in.hasRemaining()) {
+ return CoderResult.UNDERFLOW;
+ }
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ boolean isAstral; // true means Plane 2, false means BMP
+ char lowBits; // The low 16 bits of the code point
+ char codeUnit = in.get();
+ int highBits = (codeUnit & 0xFC00);
+ if (highBits == 0xD800) {
+ // high surrogate
+ if (utf16Lead != '\u0000') {
+ // High surrogate follows another high surrogate. The
+ // *previous* code unit is in error.
+ if (this.reportMalformed) {
+ // The caller had better adhere to the API contract.
+ // Otherwise, this may throw.
+ in.position(in.position() - 2);
+ utf16Lead = '\u0000';
+ return CoderResult.malformedForLength(1);
+ }
+ out.put((byte) '?');
+ }
+ utf16Lead = codeUnit;
+ continue;
+ }
+ if (highBits == 0xDC00) {
+ // low surrogate
+ if (utf16Lead == '\u0000') {
+ // Got low surrogate without a previous high surrogate
+ if (this.reportMalformed) {
+ in.position(in.position() - 1);
+ return CoderResult.malformedForLength(1);
+ }
+ out.put((byte) '?');
+ continue;
+ }
+ int codePoint = (utf16Lead << 10) + codeUnit - 56613888;
+ utf16Lead = '\u0000';
+ // Plane 2 is the only astral plane that has potentially
+ // Big5-encodable characters.
+ if ((0xFF0000 & codePoint) != 0x20000) {
+ if (this.reportUnmappable) {
+ in.position(in.position() - 2);
+ return CoderResult.unmappableForLength(2);
+ }
+ out.put((byte) '?');
+ continue;
+ }
+ isAstral = true;
+ lowBits = (char)(codePoint & 0xFFFF);
+ } else {
+ // not a surrogate
+ if (utf16Lead != '\u0000') {
+ // Non-surrogate follows a high surrogate. The *previous*
+ // code unit is in error.
+ utf16Lead = '\u0000';
+ if (this.reportMalformed) {
+ // The caller had better adhere to the API contract.
+ // Otherwise, this may throw.
+ in.position(in.position() - 2);
+ return CoderResult.malformedForLength(1);
+ }
+ out.put((byte) '?');
+ // Let's unconsume this code unit and reloop in order to
+ // re-check if the output buffer still has space.
+ in.position(in.position() - 1);
+ continue;
+ }
+ isAstral = false;
+ lowBits = codeUnit;
+ }
+ // isAstral now tells us if we have a Plane 2 or a BMP character.
+ // lowBits tells us the low 16 bits.
+ // After all the above setup to deal with UTF-16, we are now
+ // finally ready to follow the spec.
+ if (!isAstral && lowBits <= 0x7F) {
+ out.put((byte)lowBits);
+ continue;
+ }
+ int pointer = Big5Data.findPointer(lowBits, isAstral);
+ if (pointer == 0) {
+ if (this.reportUnmappable) {
+ if (isAstral) {
+ in.position(in.position() - 2);
+ return CoderResult.unmappableForLength(2);
+ }
+ in.position(in.position() - 1);
+ return CoderResult.unmappableForLength(1);
+ }
+ out.put((byte)'?');
+ continue;
+ }
+ int lead = pointer / 157 + 0x81;
+ int trail = pointer % 157;
+ if (trail < 0x3F) {
+ trail += 0x40;
+ } else {
+ trail += 0x62;
+ }
+ out.put((byte)lead);
+ if (!out.hasRemaining()) {
+ pendingTrail = (byte)trail;
+ return CoderResult.OVERFLOW;
+ }
+ out.put((byte)trail);
+ continue;
+ }
+ }
+
+ @Override protected CoderResult implFlush(ByteBuffer out) {
+ if (pendingTrail != 0) {
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ out.put(pendingTrail);
+ pendingTrail = 0;
+ }
+ if (utf16Lead != '\u0000') {
+ assert !this.reportMalformed: "How come utf16Lead got to be non-zero when decodeLoop() returned in the reporting mode?";
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ out.put((byte)'?');
+ utf16Lead = '\u0000';
+ }
+ return CoderResult.UNDERFLOW;
+ }
+
+ @Override protected void implReset() {
+ utf16Lead = '\u0000';
+ pendingTrail = 0;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Decoder.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Decoder.java
new file mode 100644
index 000000000..41e06c63a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Decoder.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+
+public abstract class Decoder extends CharsetDecoder {
+
+ protected boolean report = true;
+
+ protected Decoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte) {
+ super(cs, averageCharsPerByte, maxCharsPerByte);
+ }
+
+ @Override protected final void implOnMalformedInput(CodingErrorAction newAction) {
+ if (newAction == null) {
+ throw new IllegalArgumentException("The argument must not be null.");
+ }
+ if (newAction == CodingErrorAction.IGNORE) {
+ throw new IllegalArgumentException("The Encoding Standard does not allow errors to be ignored.");
+ }
+ if (newAction == CodingErrorAction.REPLACE) {
+ this.report = false;
+ return;
+ }
+ if (newAction == CodingErrorAction.REPORT) {
+ this.report = true;
+ return;
+ }
+ assert false: "Unreachable.";
+ throw new IllegalArgumentException("Unknown CodingErrorAction.");
+ }
+
+ @Override protected final void implOnUnmappableCharacter(
+ CodingErrorAction newAction) {
+ if (newAction == null) {
+ throw new IllegalArgumentException("The argument must not be null.");
+ }
+ if (newAction == CodingErrorAction.IGNORE) {
+ throw new IllegalArgumentException("The Encoding Standard does not allow errors to be ignored.");
+ }
+ if (newAction == CodingErrorAction.REPLACE) {
+ return; // We don't actually care, since there are no unmappables.
+ }
+ if (newAction == CodingErrorAction.REPORT) {
+ return; // We don't actually care, since there are no unmappables.
+ }
+ assert false: "Unreachable.";
+ throw new IllegalArgumentException("Unknown CodingErrorAction.");
+ }
+
+ @Override protected final void implReplaceWith(String newReplacement) {
+ if (!"\uFFFD".equals(newReplacement)) {
+ throw new IllegalArgumentException("Only U+FFFD is allowed as the replacement.");
+ }
+ }
+
+ // TODO: Check if the JDK decoders reset the reporting state on reset()
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Encoder.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Encoder.java
new file mode 100644
index 000000000..6fc011ed2
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Encoder.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+
+public abstract class Encoder extends CharsetEncoder {
+
+ boolean reportMalformed = true;
+
+ boolean reportUnmappable = true;
+
+ protected Encoder(Charset cs, float averageBytesPerChar,
+ float maxBytesPerChar) {
+ super(cs, averageBytesPerChar, maxBytesPerChar);
+ }
+
+ @Override protected final void implOnMalformedInput(CodingErrorAction newAction) {
+ if (newAction == null) {
+ throw new IllegalArgumentException("The argument must not be null.");
+ }
+ if (newAction == CodingErrorAction.IGNORE) {
+ throw new IllegalArgumentException("The Encoding Standard does not allow errors to be ignored.");
+ }
+ if (newAction == CodingErrorAction.REPLACE) {
+ this.reportMalformed = false;
+ return;
+ }
+ if (newAction == CodingErrorAction.REPORT) {
+ this.reportUnmappable = true;
+ return;
+ }
+ assert false: "Unreachable.";
+ throw new IllegalArgumentException("Unknown CodingErrorAction.");
+ }
+
+ @Override protected final void implOnUnmappableCharacter(
+ CodingErrorAction newAction) {
+ if (newAction == null) {
+ throw new IllegalArgumentException("The argument must not be null.");
+ }
+ if (newAction == CodingErrorAction.IGNORE) {
+ throw new IllegalArgumentException("The Encoding Standard does not allow errors to be ignored.");
+ }
+ if (newAction == CodingErrorAction.REPLACE) {
+ this.reportUnmappable = false;
+ return;
+ }
+ if (newAction == CodingErrorAction.REPORT) {
+ this.reportMalformed = true;
+ return;
+ }
+ assert false: "Unreachable.";
+ throw new IllegalArgumentException("Unknown CodingErrorAction.");
+ }
+
+ @Override public boolean isLegalReplacement(byte[] repl) {
+ if (repl == null) {
+ return false;
+ }
+ if (repl.length != 1) {
+ return false;
+ }
+ if (repl[0] != '?') {
+ return false;
+ }
+ return true;
+ }
+
+ @Override protected final void implReplaceWith(byte[] newReplacement) {
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Encoding.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Encoding.java
new file mode 100644
index 000000000..6e59ef7c7
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Encoding.java
@@ -0,0 +1,886 @@
+/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.nio.charset.spi.CharsetProvider;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Represents an <a href="https://encoding.spec.whatwg.org/#encoding">encoding</a>
+ * as defined in the <a href="https://encoding.spec.whatwg.org/">Encoding
+ * Standard</a>, provides access to each encoding defined in the Encoding
+ * Standard via a static constant and provides the
+ * "<a href="https://encoding.spec.whatwg.org/#concept-encoding-get">get an
+ * encoding</a>" algorithm defined in the Encoding Standard.
+ *
+ * <p>This class inherits from {@link Charset} to allow the Encoding
+ * Standard-compliant encodings to be used in contexts that support
+ * <code>Charset</code> instances. However, by design, the Encoding
+ * Standard-compliant encodings are not supplied via a {@link CharsetProvider}
+ * and, therefore, are not available via and do not interfere with the static
+ * methods provided by <code>Charset</code>. (This class provides methods of
+ * the same name to hide each static method of <code>Charset</code> to help
+ * avoid accidental calls to the static methods of the superclass when working
+ * with Encoding Standard-compliant encodings.)
+ *
+ * <p>When an application needs to use a particular encoding, such as utf-8
+ * or windows-1252, the corresponding constant, i.e.
+ * {@link #UTF_8 Encoding.UTF_8} and {@link #WINDOWS_1252 Encoding.WINDOWS_1252}
+ * respectively, should be used. However, when the application receives an
+ * encoding label from external input, the method {@link #forName(String)
+ * forName()} should be used to obtain the object representing the encoding
+ * identified by the label. In contexts where labels that map to the
+ * <a href="https://encoding.spec.whatwg.org/#replacement">replacement
+ * encoding</a> should be treated as unknown, the method {@link
+ * #forNameNoReplacement(String) forNameNoReplacement()} should be used instead.
+ *
+ *
+ * @author hsivonen
+ */
+public abstract class Encoding extends Charset {
+
+ private static final String[] LABELS = {
+ "866",
+ "ansi_x3.4-1968",
+ "arabic",
+ "ascii",
+ "asmo-708",
+ "big5",
+ "big5-hkscs",
+ "chinese",
+ "cn-big5",
+ "cp1250",
+ "cp1251",
+ "cp1252",
+ "cp1253",
+ "cp1254",
+ "cp1255",
+ "cp1256",
+ "cp1257",
+ "cp1258",
+ "cp819",
+ "cp866",
+ "csbig5",
+ "cseuckr",
+ "cseucpkdfmtjapanese",
+ "csgb2312",
+ "csibm866",
+ "csiso2022jp",
+ "csiso2022kr",
+ "csiso58gb231280",
+ "csiso88596e",
+ "csiso88596i",
+ "csiso88598e",
+ "csiso88598i",
+ "csisolatin1",
+ "csisolatin2",
+ "csisolatin3",
+ "csisolatin4",
+ "csisolatin5",
+ "csisolatin6",
+ "csisolatin9",
+ "csisolatinarabic",
+ "csisolatincyrillic",
+ "csisolatingreek",
+ "csisolatinhebrew",
+ "cskoi8r",
+ "csksc56011987",
+ "csmacintosh",
+ "csshiftjis",
+ "cyrillic",
+ "dos-874",
+ "ecma-114",
+ "ecma-118",
+ "elot_928",
+ "euc-jp",
+ "euc-kr",
+ "gb18030",
+ "gb2312",
+ "gb_2312",
+ "gb_2312-80",
+ "gbk",
+ "greek",
+ "greek8",
+ "hebrew",
+ "hz-gb-2312",
+ "ibm819",
+ "ibm866",
+ "iso-2022-cn",
+ "iso-2022-cn-ext",
+ "iso-2022-jp",
+ "iso-2022-kr",
+ "iso-8859-1",
+ "iso-8859-10",
+ "iso-8859-11",
+ "iso-8859-13",
+ "iso-8859-14",
+ "iso-8859-15",
+ "iso-8859-16",
+ "iso-8859-2",
+ "iso-8859-3",
+ "iso-8859-4",
+ "iso-8859-5",
+ "iso-8859-6",
+ "iso-8859-6-e",
+ "iso-8859-6-i",
+ "iso-8859-7",
+ "iso-8859-8",
+ "iso-8859-8-e",
+ "iso-8859-8-i",
+ "iso-8859-9",
+ "iso-ir-100",
+ "iso-ir-101",
+ "iso-ir-109",
+ "iso-ir-110",
+ "iso-ir-126",
+ "iso-ir-127",
+ "iso-ir-138",
+ "iso-ir-144",
+ "iso-ir-148",
+ "iso-ir-149",
+ "iso-ir-157",
+ "iso-ir-58",
+ "iso8859-1",
+ "iso8859-10",
+ "iso8859-11",
+ "iso8859-13",
+ "iso8859-14",
+ "iso8859-15",
+ "iso8859-2",
+ "iso8859-3",
+ "iso8859-4",
+ "iso8859-5",
+ "iso8859-6",
+ "iso8859-7",
+ "iso8859-8",
+ "iso8859-9",
+ "iso88591",
+ "iso885910",
+ "iso885911",
+ "iso885913",
+ "iso885914",
+ "iso885915",
+ "iso88592",
+ "iso88593",
+ "iso88594",
+ "iso88595",
+ "iso88596",
+ "iso88597",
+ "iso88598",
+ "iso88599",
+ "iso_8859-1",
+ "iso_8859-15",
+ "iso_8859-1:1987",
+ "iso_8859-2",
+ "iso_8859-2:1987",
+ "iso_8859-3",
+ "iso_8859-3:1988",
+ "iso_8859-4",
+ "iso_8859-4:1988",
+ "iso_8859-5",
+ "iso_8859-5:1988",
+ "iso_8859-6",
+ "iso_8859-6:1987",
+ "iso_8859-7",
+ "iso_8859-7:1987",
+ "iso_8859-8",
+ "iso_8859-8:1988",
+ "iso_8859-9",
+ "iso_8859-9:1989",
+ "koi",
+ "koi8",
+ "koi8-r",
+ "koi8-ru",
+ "koi8-u",
+ "koi8_r",
+ "korean",
+ "ks_c_5601-1987",
+ "ks_c_5601-1989",
+ "ksc5601",
+ "ksc_5601",
+ "l1",
+ "l2",
+ "l3",
+ "l4",
+ "l5",
+ "l6",
+ "l9",
+ "latin1",
+ "latin2",
+ "latin3",
+ "latin4",
+ "latin5",
+ "latin6",
+ "logical",
+ "mac",
+ "macintosh",
+ "ms932",
+ "ms_kanji",
+ "shift-jis",
+ "shift_jis",
+ "sjis",
+ "sun_eu_greek",
+ "tis-620",
+ "unicode-1-1-utf-8",
+ "us-ascii",
+ "utf-16",
+ "utf-16be",
+ "utf-16le",
+ "utf-8",
+ "utf8",
+ "visual",
+ "windows-1250",
+ "windows-1251",
+ "windows-1252",
+ "windows-1253",
+ "windows-1254",
+ "windows-1255",
+ "windows-1256",
+ "windows-1257",
+ "windows-1258",
+ "windows-31j",
+ "windows-874",
+ "windows-949",
+ "x-cp1250",
+ "x-cp1251",
+ "x-cp1252",
+ "x-cp1253",
+ "x-cp1254",
+ "x-cp1255",
+ "x-cp1256",
+ "x-cp1257",
+ "x-cp1258",
+ "x-euc-jp",
+ "x-gbk",
+ "x-mac-cyrillic",
+ "x-mac-roman",
+ "x-mac-ukrainian",
+ "x-sjis",
+ "x-user-defined",
+ "x-x-big5",
+ };
+
+ private static final Encoding[] ENCODINGS_FOR_LABELS = {
+ Ibm866.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso6.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso6.INSTANCE,
+ Big5.INSTANCE,
+ Big5.INSTANCE,
+ Gbk.INSTANCE,
+ Big5.INSTANCE,
+ Windows1250.INSTANCE,
+ Windows1251.INSTANCE,
+ Windows1252.INSTANCE,
+ Windows1253.INSTANCE,
+ Windows1254.INSTANCE,
+ Windows1255.INSTANCE,
+ Windows1256.INSTANCE,
+ Windows1257.INSTANCE,
+ Windows1258.INSTANCE,
+ Windows1252.INSTANCE,
+ Ibm866.INSTANCE,
+ Big5.INSTANCE,
+ EucKr.INSTANCE,
+ EucJp.INSTANCE,
+ Gbk.INSTANCE,
+ Ibm866.INSTANCE,
+ Iso2022Jp.INSTANCE,
+ Replacement.INSTANCE,
+ Gbk.INSTANCE,
+ Iso6.INSTANCE,
+ Iso6.INSTANCE,
+ Iso8.INSTANCE,
+ Iso8I.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso2.INSTANCE,
+ Iso3.INSTANCE,
+ Iso4.INSTANCE,
+ Windows1254.INSTANCE,
+ Iso10.INSTANCE,
+ Iso15.INSTANCE,
+ Iso6.INSTANCE,
+ Iso5.INSTANCE,
+ Iso7.INSTANCE,
+ Iso8.INSTANCE,
+ Koi8R.INSTANCE,
+ EucKr.INSTANCE,
+ Macintosh.INSTANCE,
+ ShiftJis.INSTANCE,
+ Iso5.INSTANCE,
+ Windows874.INSTANCE,
+ Iso6.INSTANCE,
+ Iso7.INSTANCE,
+ Iso7.INSTANCE,
+ EucJp.INSTANCE,
+ EucKr.INSTANCE,
+ Gb18030.INSTANCE,
+ Gbk.INSTANCE,
+ Gbk.INSTANCE,
+ Gbk.INSTANCE,
+ Gbk.INSTANCE,
+ Iso7.INSTANCE,
+ Iso7.INSTANCE,
+ Iso8.INSTANCE,
+ Replacement.INSTANCE,
+ Windows1252.INSTANCE,
+ Ibm866.INSTANCE,
+ Replacement.INSTANCE,
+ Replacement.INSTANCE,
+ Iso2022Jp.INSTANCE,
+ Replacement.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso10.INSTANCE,
+ Windows874.INSTANCE,
+ Iso13.INSTANCE,
+ Iso14.INSTANCE,
+ Iso15.INSTANCE,
+ Iso16.INSTANCE,
+ Iso2.INSTANCE,
+ Iso3.INSTANCE,
+ Iso4.INSTANCE,
+ Iso5.INSTANCE,
+ Iso6.INSTANCE,
+ Iso6.INSTANCE,
+ Iso6.INSTANCE,
+ Iso7.INSTANCE,
+ Iso8.INSTANCE,
+ Iso8.INSTANCE,
+ Iso8I.INSTANCE,
+ Windows1254.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso2.INSTANCE,
+ Iso3.INSTANCE,
+ Iso4.INSTANCE,
+ Iso7.INSTANCE,
+ Iso6.INSTANCE,
+ Iso8.INSTANCE,
+ Iso5.INSTANCE,
+ Windows1254.INSTANCE,
+ EucKr.INSTANCE,
+ Iso10.INSTANCE,
+ Gbk.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso10.INSTANCE,
+ Windows874.INSTANCE,
+ Iso13.INSTANCE,
+ Iso14.INSTANCE,
+ Iso15.INSTANCE,
+ Iso2.INSTANCE,
+ Iso3.INSTANCE,
+ Iso4.INSTANCE,
+ Iso5.INSTANCE,
+ Iso6.INSTANCE,
+ Iso7.INSTANCE,
+ Iso8.INSTANCE,
+ Windows1254.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso10.INSTANCE,
+ Windows874.INSTANCE,
+ Iso13.INSTANCE,
+ Iso14.INSTANCE,
+ Iso15.INSTANCE,
+ Iso2.INSTANCE,
+ Iso3.INSTANCE,
+ Iso4.INSTANCE,
+ Iso5.INSTANCE,
+ Iso6.INSTANCE,
+ Iso7.INSTANCE,
+ Iso8.INSTANCE,
+ Windows1254.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso15.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso2.INSTANCE,
+ Iso2.INSTANCE,
+ Iso3.INSTANCE,
+ Iso3.INSTANCE,
+ Iso4.INSTANCE,
+ Iso4.INSTANCE,
+ Iso5.INSTANCE,
+ Iso5.INSTANCE,
+ Iso6.INSTANCE,
+ Iso6.INSTANCE,
+ Iso7.INSTANCE,
+ Iso7.INSTANCE,
+ Iso8.INSTANCE,
+ Iso8.INSTANCE,
+ Windows1254.INSTANCE,
+ Windows1254.INSTANCE,
+ Koi8R.INSTANCE,
+ Koi8R.INSTANCE,
+ Koi8R.INSTANCE,
+ Koi8U.INSTANCE,
+ Koi8U.INSTANCE,
+ Koi8R.INSTANCE,
+ EucKr.INSTANCE,
+ EucKr.INSTANCE,
+ EucKr.INSTANCE,
+ EucKr.INSTANCE,
+ EucKr.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso2.INSTANCE,
+ Iso3.INSTANCE,
+ Iso4.INSTANCE,
+ Windows1254.INSTANCE,
+ Iso10.INSTANCE,
+ Iso15.INSTANCE,
+ Windows1252.INSTANCE,
+ Iso2.INSTANCE,
+ Iso3.INSTANCE,
+ Iso4.INSTANCE,
+ Windows1254.INSTANCE,
+ Iso10.INSTANCE,
+ Iso8I.INSTANCE,
+ Macintosh.INSTANCE,
+ Macintosh.INSTANCE,
+ ShiftJis.INSTANCE,
+ ShiftJis.INSTANCE,
+ ShiftJis.INSTANCE,
+ ShiftJis.INSTANCE,
+ ShiftJis.INSTANCE,
+ Iso7.INSTANCE,
+ Windows874.INSTANCE,
+ Utf8.INSTANCE,
+ Windows1252.INSTANCE,
+ Utf16Le.INSTANCE,
+ Utf16Be.INSTANCE,
+ Utf16Le.INSTANCE,
+ Utf8.INSTANCE,
+ Utf8.INSTANCE,
+ Iso8.INSTANCE,
+ Windows1250.INSTANCE,
+ Windows1251.INSTANCE,
+ Windows1252.INSTANCE,
+ Windows1253.INSTANCE,
+ Windows1254.INSTANCE,
+ Windows1255.INSTANCE,
+ Windows1256.INSTANCE,
+ Windows1257.INSTANCE,
+ Windows1258.INSTANCE,
+ ShiftJis.INSTANCE,
+ Windows874.INSTANCE,
+ EucKr.INSTANCE,
+ Windows1250.INSTANCE,
+ Windows1251.INSTANCE,
+ Windows1252.INSTANCE,
+ Windows1253.INSTANCE,
+ Windows1254.INSTANCE,
+ Windows1255.INSTANCE,
+ Windows1256.INSTANCE,
+ Windows1257.INSTANCE,
+ Windows1258.INSTANCE,
+ EucJp.INSTANCE,
+ Gbk.INSTANCE,
+ MacCyrillic.INSTANCE,
+ Macintosh.INSTANCE,
+ MacCyrillic.INSTANCE,
+ ShiftJis.INSTANCE,
+ UserDefined.INSTANCE,
+ Big5.INSTANCE,
+ };
+
+ private static final Encoding[] ENCODINGS = {
+ Big5.INSTANCE,
+ EucJp.INSTANCE,
+ EucKr.INSTANCE,
+ Gb18030.INSTANCE,
+ Gbk.INSTANCE,
+ Ibm866.INSTANCE,
+ Iso2022Jp.INSTANCE,
+ Iso10.INSTANCE,
+ Iso13.INSTANCE,
+ Iso14.INSTANCE,
+ Iso15.INSTANCE,
+ Iso16.INSTANCE,
+ Iso2.INSTANCE,
+ Iso3.INSTANCE,
+ Iso4.INSTANCE,
+ Iso5.INSTANCE,
+ Iso6.INSTANCE,
+ Iso7.INSTANCE,
+ Iso8.INSTANCE,
+ Iso8I.INSTANCE,
+ Koi8R.INSTANCE,
+ Koi8U.INSTANCE,
+ Macintosh.INSTANCE,
+ Replacement.INSTANCE,
+ ShiftJis.INSTANCE,
+ Utf16Be.INSTANCE,
+ Utf16Le.INSTANCE,
+ Utf8.INSTANCE,
+ Windows1250.INSTANCE,
+ Windows1251.INSTANCE,
+ Windows1252.INSTANCE,
+ Windows1253.INSTANCE,
+ Windows1254.INSTANCE,
+ Windows1255.INSTANCE,
+ Windows1256.INSTANCE,
+ Windows1257.INSTANCE,
+ Windows1258.INSTANCE,
+ Windows874.INSTANCE,
+ MacCyrillic.INSTANCE,
+ UserDefined.INSTANCE,
+ };
+
+ /**
+ * The big5 encoding.
+ */
+ public static final Encoding BIG5 = Big5.INSTANCE;
+
+ /**
+ * The euc-jp encoding.
+ */
+ public static final Encoding EUC_JP = EucJp.INSTANCE;
+
+ /**
+ * The euc-kr encoding.
+ */
+ public static final Encoding EUC_KR = EucKr.INSTANCE;
+
+ /**
+ * The gb18030 encoding.
+ */
+ public static final Encoding GB18030 = Gb18030.INSTANCE;
+
+ /**
+ * The gbk encoding.
+ */
+ public static final Encoding GBK = Gbk.INSTANCE;
+
+ /**
+ * The ibm866 encoding.
+ */
+ public static final Encoding IBM866 = Ibm866.INSTANCE;
+
+ /**
+ * The iso-2022-jp encoding.
+ */
+ public static final Encoding ISO_2022_JP = Iso2022Jp.INSTANCE;
+
+ /**
+ * The iso-8859-10 encoding.
+ */
+ public static final Encoding ISO_8859_10 = Iso10.INSTANCE;
+
+ /**
+ * The iso-8859-13 encoding.
+ */
+ public static final Encoding ISO_8859_13 = Iso13.INSTANCE;
+
+ /**
+ * The iso-8859-14 encoding.
+ */
+ public static final Encoding ISO_8859_14 = Iso14.INSTANCE;
+
+ /**
+ * The iso-8859-15 encoding.
+ */
+ public static final Encoding ISO_8859_15 = Iso15.INSTANCE;
+
+ /**
+ * The iso-8859-16 encoding.
+ */
+ public static final Encoding ISO_8859_16 = Iso16.INSTANCE;
+
+ /**
+ * The iso-8859-2 encoding.
+ */
+ public static final Encoding ISO_8859_2 = Iso2.INSTANCE;
+
+ /**
+ * The iso-8859-3 encoding.
+ */
+ public static final Encoding ISO_8859_3 = Iso3.INSTANCE;
+
+ /**
+ * The iso-8859-4 encoding.
+ */
+ public static final Encoding ISO_8859_4 = Iso4.INSTANCE;
+
+ /**
+ * The iso-8859-5 encoding.
+ */
+ public static final Encoding ISO_8859_5 = Iso5.INSTANCE;
+
+ /**
+ * The iso-8859-6 encoding.
+ */
+ public static final Encoding ISO_8859_6 = Iso6.INSTANCE;
+
+ /**
+ * The iso-8859-7 encoding.
+ */
+ public static final Encoding ISO_8859_7 = Iso7.INSTANCE;
+
+ /**
+ * The iso-8859-8 encoding.
+ */
+ public static final Encoding ISO_8859_8 = Iso8.INSTANCE;
+
+ /**
+ * The iso-8859-8-i encoding.
+ */
+ public static final Encoding ISO_8859_8_I = Iso8I.INSTANCE;
+
+ /**
+ * The koi8-r encoding.
+ */
+ public static final Encoding KOI8_R = Koi8R.INSTANCE;
+
+ /**
+ * The koi8-u encoding.
+ */
+ public static final Encoding KOI8_U = Koi8U.INSTANCE;
+
+ /**
+ * The macintosh encoding.
+ */
+ public static final Encoding MACINTOSH = Macintosh.INSTANCE;
+
+ /**
+ * The replacement encoding.
+ */
+ public static final Encoding REPLACEMENT = Replacement.INSTANCE;
+
+ /**
+ * The shift_jis encoding.
+ */
+ public static final Encoding SHIFT_JIS = ShiftJis.INSTANCE;
+
+ /**
+ * The utf-16be encoding.
+ */
+ public static final Encoding UTF_16BE = Utf16Be.INSTANCE;
+
+ /**
+ * The utf-16le encoding.
+ */
+ public static final Encoding UTF_16LE = Utf16Le.INSTANCE;
+
+ /**
+ * The utf-8 encoding.
+ */
+ public static final Encoding UTF_8 = Utf8.INSTANCE;
+
+ /**
+ * The windows-1250 encoding.
+ */
+ public static final Encoding WINDOWS_1250 = Windows1250.INSTANCE;
+
+ /**
+ * The windows-1251 encoding.
+ */
+ public static final Encoding WINDOWS_1251 = Windows1251.INSTANCE;
+
+ /**
+ * The windows-1252 encoding.
+ */
+ public static final Encoding WINDOWS_1252 = Windows1252.INSTANCE;
+
+ /**
+ * The windows-1253 encoding.
+ */
+ public static final Encoding WINDOWS_1253 = Windows1253.INSTANCE;
+
+ /**
+ * The windows-1254 encoding.
+ */
+ public static final Encoding WINDOWS_1254 = Windows1254.INSTANCE;
+
+ /**
+ * The windows-1255 encoding.
+ */
+ public static final Encoding WINDOWS_1255 = Windows1255.INSTANCE;
+
+ /**
+ * The windows-1256 encoding.
+ */
+ public static final Encoding WINDOWS_1256 = Windows1256.INSTANCE;
+
+ /**
+ * The windows-1257 encoding.
+ */
+ public static final Encoding WINDOWS_1257 = Windows1257.INSTANCE;
+
+ /**
+ * The windows-1258 encoding.
+ */
+ public static final Encoding WINDOWS_1258 = Windows1258.INSTANCE;
+
+ /**
+ * The windows-874 encoding.
+ */
+ public static final Encoding WINDOWS_874 = Windows874.INSTANCE;
+
+ /**
+ * The x-mac-cyrillic encoding.
+ */
+ public static final Encoding X_MAC_CYRILLIC = MacCyrillic.INSTANCE;
+
+ /**
+ * The x-user-defined encoding.
+ */
+ public static final Encoding X_USER_DEFINED = UserDefined.INSTANCE;
+
+
+private static SortedMap<String, Charset> encodings = null;
+
+ protected Encoding(String canonicalName, String[] aliases) {
+ super(canonicalName, aliases);
+ }
+
+ private enum State {
+ HEAD, LABEL, TAIL
+ };
+
+ public static Encoding forName(String label) {
+ if (label == null) {
+ throw new IllegalArgumentException("Label must not be null.");
+ }
+ if (label.length() == 0) {
+ throw new IllegalCharsetNameException(label);
+ }
+ // First try the fast path
+ int index = Arrays.binarySearch(LABELS, label);
+ if (index >= 0) {
+ return ENCODINGS_FOR_LABELS[index];
+ }
+ // Else, slow path
+ StringBuilder sb = new StringBuilder();
+ State state = State.HEAD;
+ for (int i = 0; i < label.length(); i++) {
+ char c = label.charAt(i);
+ if ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t')
+ || (c == '\u000C')) {
+ if (state == State.LABEL) {
+ state = State.TAIL;
+ }
+ continue;
+ }
+ if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
+ switch (state) {
+ case HEAD:
+ state = State.LABEL;
+ // Fall through
+ case LABEL:
+ sb.append(c);
+ continue;
+ case TAIL:
+ throw new IllegalCharsetNameException(label);
+ }
+ }
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ switch (state) {
+ case HEAD:
+ state = State.LABEL;
+ // Fall through
+ case LABEL:
+ sb.append(c);
+ continue;
+ case TAIL:
+ throw new IllegalCharsetNameException(label);
+ }
+ }
+ if ((c == '-') || (c == '+') || (c == '.') || (c == ':')
+ || (c == '_')) {
+ switch (state) {
+ case LABEL:
+ sb.append(c);
+ continue;
+ case HEAD:
+ case TAIL:
+ throw new IllegalCharsetNameException(label);
+ }
+ }
+ throw new IllegalCharsetNameException(label);
+ }
+ index = Arrays.binarySearch(LABELS, sb.toString());
+ if (index >= 0) {
+ return ENCODINGS_FOR_LABELS[index];
+ }
+ throw new UnsupportedCharsetException(label);
+ }
+
+ public static Encoding forNameNoReplacement(String label) {
+ Encoding encoding = Encoding.forName(label);
+ if (encoding == Encoding.REPLACEMENT) {
+ throw new UnsupportedCharsetException(label);
+ }
+ return encoding;
+ }
+
+ public static boolean isSupported(String label) {
+ try {
+ Encoding.forName(label);
+ } catch (UnsupportedCharsetException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isSupportedNoReplacement(String label) {
+ try {
+ Encoding.forNameNoReplacement(label);
+ } catch (UnsupportedCharsetException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public static SortedMap<String, Charset> availableCharsets() {
+ if (encodings == null) {
+ TreeMap<String, Charset> map = new TreeMap<String, Charset>();
+ for (Encoding encoding : ENCODINGS) {
+ map.put(encoding.name(), encoding);
+ }
+ encodings = Collections.unmodifiableSortedMap(map);
+ }
+ return encodings;
+ }
+
+ public static Encoding defaultCharset() {
+ return WINDOWS_1252;
+ }
+
+ @Override public boolean canEncode() {
+ return false;
+ }
+
+ @Override public boolean contains(Charset cs) {
+ return false;
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ throw new UnsupportedOperationException("Encoder not implemented.");
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/EucJp.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/EucJp.java
new file mode 100644
index 000000000..05fbef810
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/EucJp.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class EucJp extends Encoding {
+
+ private static final String[] LABELS = {
+ "cseucpkdfmtjapanese",
+ "euc-jp",
+ "x-euc-jp"
+ };
+
+ private static final String NAME = "euc-jp";
+
+ static final EucJp INSTANCE = new EucJp();
+
+ private EucJp() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return Charset.forName(NAME).newDecoder();
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/EucKr.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/EucKr.java
new file mode 100644
index 000000000..a3923e224
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/EucKr.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class EucKr extends Encoding {
+
+ private static final String[] LABELS = {
+ "cseuckr",
+ "csksc56011987",
+ "euc-kr",
+ "iso-ir-149",
+ "korean",
+ "ks_c_5601-1987",
+ "ks_c_5601-1989",
+ "ksc5601",
+ "ksc_5601",
+ "windows-949"
+ };
+
+ private static final String NAME = "euc-kr";
+
+ static final EucKr INSTANCE = new EucKr();
+
+ private EucKr() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return Charset.forName(NAME).newDecoder();
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/FallibleSingleByteDecoder.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/FallibleSingleByteDecoder.java
new file mode 100644
index 000000000..34a1f36b5
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/FallibleSingleByteDecoder.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CoderResult;
+
+public final class FallibleSingleByteDecoder extends InfallibleSingleByteDecoder {
+
+ public FallibleSingleByteDecoder(Encoding cs, char[] upperHalf) {
+ super(cs, upperHalf);
+ }
+
+ @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
+ if (!this.report) {
+ return super.decodeLoop(in, out);
+ } else {
+ for (;;) {
+ if (!in.hasRemaining()) {
+ return CoderResult.UNDERFLOW;
+ }
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ int b = (int) in.get();
+ if (b >= 0) {
+ out.put((char) b);
+ } else {
+ char mapped = this.upperHalf[b + 128];
+ if (mapped == '\uFFFD') {
+ in.position(in.position() - 1);
+ return CoderResult.malformedForLength(1);
+ }
+ out.put(mapped);
+ }
+ }
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Gb18030.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Gb18030.java
new file mode 100644
index 000000000..fcb090dde
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Gb18030.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class Gb18030 extends Encoding {
+
+ private static final String[] LABELS = {
+ "gb18030"
+ };
+
+ private static final String NAME = "gb18030";
+
+ static final Gb18030 INSTANCE = new Gb18030();
+
+ private Gb18030() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return Charset.forName(NAME).newDecoder();
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Gbk.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Gbk.java
new file mode 100644
index 000000000..2dc3694ed
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Gbk.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class Gbk extends Encoding {
+
+ private static final String[] LABELS = {
+ "chinese",
+ "csgb2312",
+ "csiso58gb231280",
+ "gb2312",
+ "gb_2312",
+ "gb_2312-80",
+ "gbk",
+ "iso-ir-58",
+ "x-gbk"
+ };
+
+ private static final String NAME = "gbk";
+
+ static final Gbk INSTANCE = new Gbk();
+
+ private Gbk() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return Charset.forName("gb18030").newDecoder();
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Ibm866.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Ibm866.java
new file mode 100644
index 000000000..037e62835
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Ibm866.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Ibm866 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0410',
+ '\u0411',
+ '\u0412',
+ '\u0413',
+ '\u0414',
+ '\u0415',
+ '\u0416',
+ '\u0417',
+ '\u0418',
+ '\u0419',
+ '\u041a',
+ '\u041b',
+ '\u041c',
+ '\u041d',
+ '\u041e',
+ '\u041f',
+ '\u0420',
+ '\u0421',
+ '\u0422',
+ '\u0423',
+ '\u0424',
+ '\u0425',
+ '\u0426',
+ '\u0427',
+ '\u0428',
+ '\u0429',
+ '\u042a',
+ '\u042b',
+ '\u042c',
+ '\u042d',
+ '\u042e',
+ '\u042f',
+ '\u0430',
+ '\u0431',
+ '\u0432',
+ '\u0433',
+ '\u0434',
+ '\u0435',
+ '\u0436',
+ '\u0437',
+ '\u0438',
+ '\u0439',
+ '\u043a',
+ '\u043b',
+ '\u043c',
+ '\u043d',
+ '\u043e',
+ '\u043f',
+ '\u2591',
+ '\u2592',
+ '\u2593',
+ '\u2502',
+ '\u2524',
+ '\u2561',
+ '\u2562',
+ '\u2556',
+ '\u2555',
+ '\u2563',
+ '\u2551',
+ '\u2557',
+ '\u255d',
+ '\u255c',
+ '\u255b',
+ '\u2510',
+ '\u2514',
+ '\u2534',
+ '\u252c',
+ '\u251c',
+ '\u2500',
+ '\u253c',
+ '\u255e',
+ '\u255f',
+ '\u255a',
+ '\u2554',
+ '\u2569',
+ '\u2566',
+ '\u2560',
+ '\u2550',
+ '\u256c',
+ '\u2567',
+ '\u2568',
+ '\u2564',
+ '\u2565',
+ '\u2559',
+ '\u2558',
+ '\u2552',
+ '\u2553',
+ '\u256b',
+ '\u256a',
+ '\u2518',
+ '\u250c',
+ '\u2588',
+ '\u2584',
+ '\u258c',
+ '\u2590',
+ '\u2580',
+ '\u0440',
+ '\u0441',
+ '\u0442',
+ '\u0443',
+ '\u0444',
+ '\u0445',
+ '\u0446',
+ '\u0447',
+ '\u0448',
+ '\u0449',
+ '\u044a',
+ '\u044b',
+ '\u044c',
+ '\u044d',
+ '\u044e',
+ '\u044f',
+ '\u0401',
+ '\u0451',
+ '\u0404',
+ '\u0454',
+ '\u0407',
+ '\u0457',
+ '\u040e',
+ '\u045e',
+ '\u00b0',
+ '\u2219',
+ '\u00b7',
+ '\u221a',
+ '\u2116',
+ '\u00a4',
+ '\u25a0',
+ '\u00a0'
+ };
+
+ private static final String[] LABELS = {
+ "866",
+ "cp866",
+ "csibm866",
+ "ibm866"
+ };
+
+ private static final String NAME = "ibm866";
+
+ static final Encoding INSTANCE = new Ibm866();
+
+ private Ibm866() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/InfallibleSingleByteDecoder.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/InfallibleSingleByteDecoder.java
new file mode 100644
index 000000000..7cc63072c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/InfallibleSingleByteDecoder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CoderResult;
+
+public class InfallibleSingleByteDecoder extends Decoder {
+
+ protected final char[] upperHalf;
+
+ protected InfallibleSingleByteDecoder(Encoding cs, char[] upperHalf) {
+ super(cs, 1.0f, 1.0f);
+ this.upperHalf = upperHalf;
+ }
+
+ @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
+ // TODO figure out if it's worthwhile to optimize the case where both
+ // buffers are array-backed.
+ for (;;) {
+ if (!in.hasRemaining()) {
+ return CoderResult.UNDERFLOW;
+ }
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ int b = (int) in.get();
+ if (b >= 0) {
+ out.put((char) b);
+ } else {
+ out.put(this.upperHalf[b + 128]);
+ }
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso10.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso10.java
new file mode 100644
index 000000000..895cb5eed
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso10.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso10 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u0104',
+ '\u0112',
+ '\u0122',
+ '\u012a',
+ '\u0128',
+ '\u0136',
+ '\u00a7',
+ '\u013b',
+ '\u0110',
+ '\u0160',
+ '\u0166',
+ '\u017d',
+ '\u00ad',
+ '\u016a',
+ '\u014a',
+ '\u00b0',
+ '\u0105',
+ '\u0113',
+ '\u0123',
+ '\u012b',
+ '\u0129',
+ '\u0137',
+ '\u00b7',
+ '\u013c',
+ '\u0111',
+ '\u0161',
+ '\u0167',
+ '\u017e',
+ '\u2015',
+ '\u016b',
+ '\u014b',
+ '\u0100',
+ '\u00c1',
+ '\u00c2',
+ '\u00c3',
+ '\u00c4',
+ '\u00c5',
+ '\u00c6',
+ '\u012e',
+ '\u010c',
+ '\u00c9',
+ '\u0118',
+ '\u00cb',
+ '\u0116',
+ '\u00cd',
+ '\u00ce',
+ '\u00cf',
+ '\u00d0',
+ '\u0145',
+ '\u014c',
+ '\u00d3',
+ '\u00d4',
+ '\u00d5',
+ '\u00d6',
+ '\u0168',
+ '\u00d8',
+ '\u0172',
+ '\u00da',
+ '\u00db',
+ '\u00dc',
+ '\u00dd',
+ '\u00de',
+ '\u00df',
+ '\u0101',
+ '\u00e1',
+ '\u00e2',
+ '\u00e3',
+ '\u00e4',
+ '\u00e5',
+ '\u00e6',
+ '\u012f',
+ '\u010d',
+ '\u00e9',
+ '\u0119',
+ '\u00eb',
+ '\u0117',
+ '\u00ed',
+ '\u00ee',
+ '\u00ef',
+ '\u00f0',
+ '\u0146',
+ '\u014d',
+ '\u00f3',
+ '\u00f4',
+ '\u00f5',
+ '\u00f6',
+ '\u0169',
+ '\u00f8',
+ '\u0173',
+ '\u00fa',
+ '\u00fb',
+ '\u00fc',
+ '\u00fd',
+ '\u00fe',
+ '\u0138'
+ };
+
+ private static final String[] LABELS = {
+ "csisolatin6",
+ "iso-8859-10",
+ "iso-ir-157",
+ "iso8859-10",
+ "iso885910",
+ "l6",
+ "latin6"
+ };
+
+ private static final String NAME = "iso-8859-10";
+
+ static final Encoding INSTANCE = new Iso10();
+
+ private Iso10() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso13.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso13.java
new file mode 100644
index 000000000..60e6f5339
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso13.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso13 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u201d',
+ '\u00a2',
+ '\u00a3',
+ '\u00a4',
+ '\u201e',
+ '\u00a6',
+ '\u00a7',
+ '\u00d8',
+ '\u00a9',
+ '\u0156',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u00c6',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u201c',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u00f8',
+ '\u00b9',
+ '\u0157',
+ '\u00bb',
+ '\u00bc',
+ '\u00bd',
+ '\u00be',
+ '\u00e6',
+ '\u0104',
+ '\u012e',
+ '\u0100',
+ '\u0106',
+ '\u00c4',
+ '\u00c5',
+ '\u0118',
+ '\u0112',
+ '\u010c',
+ '\u00c9',
+ '\u0179',
+ '\u0116',
+ '\u0122',
+ '\u0136',
+ '\u012a',
+ '\u013b',
+ '\u0160',
+ '\u0143',
+ '\u0145',
+ '\u00d3',
+ '\u014c',
+ '\u00d5',
+ '\u00d6',
+ '\u00d7',
+ '\u0172',
+ '\u0141',
+ '\u015a',
+ '\u016a',
+ '\u00dc',
+ '\u017b',
+ '\u017d',
+ '\u00df',
+ '\u0105',
+ '\u012f',
+ '\u0101',
+ '\u0107',
+ '\u00e4',
+ '\u00e5',
+ '\u0119',
+ '\u0113',
+ '\u010d',
+ '\u00e9',
+ '\u017a',
+ '\u0117',
+ '\u0123',
+ '\u0137',
+ '\u012b',
+ '\u013c',
+ '\u0161',
+ '\u0144',
+ '\u0146',
+ '\u00f3',
+ '\u014d',
+ '\u00f5',
+ '\u00f6',
+ '\u00f7',
+ '\u0173',
+ '\u0142',
+ '\u015b',
+ '\u016b',
+ '\u00fc',
+ '\u017c',
+ '\u017e',
+ '\u2019'
+ };
+
+ private static final String[] LABELS = {
+ "iso-8859-13",
+ "iso8859-13",
+ "iso885913"
+ };
+
+ private static final String NAME = "iso-8859-13";
+
+ static final Encoding INSTANCE = new Iso13();
+
+ private Iso13() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso14.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso14.java
new file mode 100644
index 000000000..d4a180e6e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso14.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso14 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u1e02',
+ '\u1e03',
+ '\u00a3',
+ '\u010a',
+ '\u010b',
+ '\u1e0a',
+ '\u00a7',
+ '\u1e80',
+ '\u00a9',
+ '\u1e82',
+ '\u1e0b',
+ '\u1ef2',
+ '\u00ad',
+ '\u00ae',
+ '\u0178',
+ '\u1e1e',
+ '\u1e1f',
+ '\u0120',
+ '\u0121',
+ '\u1e40',
+ '\u1e41',
+ '\u00b6',
+ '\u1e56',
+ '\u1e81',
+ '\u1e57',
+ '\u1e83',
+ '\u1e60',
+ '\u1ef3',
+ '\u1e84',
+ '\u1e85',
+ '\u1e61',
+ '\u00c0',
+ '\u00c1',
+ '\u00c2',
+ '\u00c3',
+ '\u00c4',
+ '\u00c5',
+ '\u00c6',
+ '\u00c7',
+ '\u00c8',
+ '\u00c9',
+ '\u00ca',
+ '\u00cb',
+ '\u00cc',
+ '\u00cd',
+ '\u00ce',
+ '\u00cf',
+ '\u0174',
+ '\u00d1',
+ '\u00d2',
+ '\u00d3',
+ '\u00d4',
+ '\u00d5',
+ '\u00d6',
+ '\u1e6a',
+ '\u00d8',
+ '\u00d9',
+ '\u00da',
+ '\u00db',
+ '\u00dc',
+ '\u00dd',
+ '\u0176',
+ '\u00df',
+ '\u00e0',
+ '\u00e1',
+ '\u00e2',
+ '\u00e3',
+ '\u00e4',
+ '\u00e5',
+ '\u00e6',
+ '\u00e7',
+ '\u00e8',
+ '\u00e9',
+ '\u00ea',
+ '\u00eb',
+ '\u00ec',
+ '\u00ed',
+ '\u00ee',
+ '\u00ef',
+ '\u0175',
+ '\u00f1',
+ '\u00f2',
+ '\u00f3',
+ '\u00f4',
+ '\u00f5',
+ '\u00f6',
+ '\u1e6b',
+ '\u00f8',
+ '\u00f9',
+ '\u00fa',
+ '\u00fb',
+ '\u00fc',
+ '\u00fd',
+ '\u0177',
+ '\u00ff'
+ };
+
+ private static final String[] LABELS = {
+ "iso-8859-14",
+ "iso8859-14",
+ "iso885914"
+ };
+
+ private static final String NAME = "iso-8859-14";
+
+ static final Encoding INSTANCE = new Iso14();
+
+ private Iso14() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso15.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso15.java
new file mode 100644
index 000000000..a60e4b6ef
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso15.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso15 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u00a1',
+ '\u00a2',
+ '\u00a3',
+ '\u20ac',
+ '\u00a5',
+ '\u0160',
+ '\u00a7',
+ '\u0161',
+ '\u00a9',
+ '\u00aa',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u00af',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u017d',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u017e',
+ '\u00b9',
+ '\u00ba',
+ '\u00bb',
+ '\u0152',
+ '\u0153',
+ '\u0178',
+ '\u00bf',
+ '\u00c0',
+ '\u00c1',
+ '\u00c2',
+ '\u00c3',
+ '\u00c4',
+ '\u00c5',
+ '\u00c6',
+ '\u00c7',
+ '\u00c8',
+ '\u00c9',
+ '\u00ca',
+ '\u00cb',
+ '\u00cc',
+ '\u00cd',
+ '\u00ce',
+ '\u00cf',
+ '\u00d0',
+ '\u00d1',
+ '\u00d2',
+ '\u00d3',
+ '\u00d4',
+ '\u00d5',
+ '\u00d6',
+ '\u00d7',
+ '\u00d8',
+ '\u00d9',
+ '\u00da',
+ '\u00db',
+ '\u00dc',
+ '\u00dd',
+ '\u00de',
+ '\u00df',
+ '\u00e0',
+ '\u00e1',
+ '\u00e2',
+ '\u00e3',
+ '\u00e4',
+ '\u00e5',
+ '\u00e6',
+ '\u00e7',
+ '\u00e8',
+ '\u00e9',
+ '\u00ea',
+ '\u00eb',
+ '\u00ec',
+ '\u00ed',
+ '\u00ee',
+ '\u00ef',
+ '\u00f0',
+ '\u00f1',
+ '\u00f2',
+ '\u00f3',
+ '\u00f4',
+ '\u00f5',
+ '\u00f6',
+ '\u00f7',
+ '\u00f8',
+ '\u00f9',
+ '\u00fa',
+ '\u00fb',
+ '\u00fc',
+ '\u00fd',
+ '\u00fe',
+ '\u00ff'
+ };
+
+ private static final String[] LABELS = {
+ "csisolatin9",
+ "iso-8859-15",
+ "iso8859-15",
+ "iso885915",
+ "iso_8859-15",
+ "l9"
+ };
+
+ private static final String NAME = "iso-8859-15";
+
+ static final Encoding INSTANCE = new Iso15();
+
+ private Iso15() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso16.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso16.java
new file mode 100644
index 000000000..5eb1926db
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso16.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso16 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u0104',
+ '\u0105',
+ '\u0141',
+ '\u20ac',
+ '\u201e',
+ '\u0160',
+ '\u00a7',
+ '\u0161',
+ '\u00a9',
+ '\u0218',
+ '\u00ab',
+ '\u0179',
+ '\u00ad',
+ '\u017a',
+ '\u017b',
+ '\u00b0',
+ '\u00b1',
+ '\u010c',
+ '\u0142',
+ '\u017d',
+ '\u201d',
+ '\u00b6',
+ '\u00b7',
+ '\u017e',
+ '\u010d',
+ '\u0219',
+ '\u00bb',
+ '\u0152',
+ '\u0153',
+ '\u0178',
+ '\u017c',
+ '\u00c0',
+ '\u00c1',
+ '\u00c2',
+ '\u0102',
+ '\u00c4',
+ '\u0106',
+ '\u00c6',
+ '\u00c7',
+ '\u00c8',
+ '\u00c9',
+ '\u00ca',
+ '\u00cb',
+ '\u00cc',
+ '\u00cd',
+ '\u00ce',
+ '\u00cf',
+ '\u0110',
+ '\u0143',
+ '\u00d2',
+ '\u00d3',
+ '\u00d4',
+ '\u0150',
+ '\u00d6',
+ '\u015a',
+ '\u0170',
+ '\u00d9',
+ '\u00da',
+ '\u00db',
+ '\u00dc',
+ '\u0118',
+ '\u021a',
+ '\u00df',
+ '\u00e0',
+ '\u00e1',
+ '\u00e2',
+ '\u0103',
+ '\u00e4',
+ '\u0107',
+ '\u00e6',
+ '\u00e7',
+ '\u00e8',
+ '\u00e9',
+ '\u00ea',
+ '\u00eb',
+ '\u00ec',
+ '\u00ed',
+ '\u00ee',
+ '\u00ef',
+ '\u0111',
+ '\u0144',
+ '\u00f2',
+ '\u00f3',
+ '\u00f4',
+ '\u0151',
+ '\u00f6',
+ '\u015b',
+ '\u0171',
+ '\u00f9',
+ '\u00fa',
+ '\u00fb',
+ '\u00fc',
+ '\u0119',
+ '\u021b',
+ '\u00ff'
+ };
+
+ private static final String[] LABELS = {
+ "iso-8859-16"
+ };
+
+ private static final String NAME = "iso-8859-16";
+
+ static final Encoding INSTANCE = new Iso16();
+
+ private Iso16() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso2.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso2.java
new file mode 100644
index 000000000..7a5f6322a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso2.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso2 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u0104',
+ '\u02d8',
+ '\u0141',
+ '\u00a4',
+ '\u013d',
+ '\u015a',
+ '\u00a7',
+ '\u00a8',
+ '\u0160',
+ '\u015e',
+ '\u0164',
+ '\u0179',
+ '\u00ad',
+ '\u017d',
+ '\u017b',
+ '\u00b0',
+ '\u0105',
+ '\u02db',
+ '\u0142',
+ '\u00b4',
+ '\u013e',
+ '\u015b',
+ '\u02c7',
+ '\u00b8',
+ '\u0161',
+ '\u015f',
+ '\u0165',
+ '\u017a',
+ '\u02dd',
+ '\u017e',
+ '\u017c',
+ '\u0154',
+ '\u00c1',
+ '\u00c2',
+ '\u0102',
+ '\u00c4',
+ '\u0139',
+ '\u0106',
+ '\u00c7',
+ '\u010c',
+ '\u00c9',
+ '\u0118',
+ '\u00cb',
+ '\u011a',
+ '\u00cd',
+ '\u00ce',
+ '\u010e',
+ '\u0110',
+ '\u0143',
+ '\u0147',
+ '\u00d3',
+ '\u00d4',
+ '\u0150',
+ '\u00d6',
+ '\u00d7',
+ '\u0158',
+ '\u016e',
+ '\u00da',
+ '\u0170',
+ '\u00dc',
+ '\u00dd',
+ '\u0162',
+ '\u00df',
+ '\u0155',
+ '\u00e1',
+ '\u00e2',
+ '\u0103',
+ '\u00e4',
+ '\u013a',
+ '\u0107',
+ '\u00e7',
+ '\u010d',
+ '\u00e9',
+ '\u0119',
+ '\u00eb',
+ '\u011b',
+ '\u00ed',
+ '\u00ee',
+ '\u010f',
+ '\u0111',
+ '\u0144',
+ '\u0148',
+ '\u00f3',
+ '\u00f4',
+ '\u0151',
+ '\u00f6',
+ '\u00f7',
+ '\u0159',
+ '\u016f',
+ '\u00fa',
+ '\u0171',
+ '\u00fc',
+ '\u00fd',
+ '\u0163',
+ '\u02d9'
+ };
+
+ private static final String[] LABELS = {
+ "csisolatin2",
+ "iso-8859-2",
+ "iso-ir-101",
+ "iso8859-2",
+ "iso88592",
+ "iso_8859-2",
+ "iso_8859-2:1987",
+ "l2",
+ "latin2"
+ };
+
+ private static final String NAME = "iso-8859-2";
+
+ static final Encoding INSTANCE = new Iso2();
+
+ private Iso2() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso2022Jp.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso2022Jp.java
new file mode 100644
index 000000000..6ebadc947
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso2022Jp.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class Iso2022Jp extends Encoding {
+
+ private static final String[] LABELS = {
+ "csiso2022jp",
+ "iso-2022-jp"
+ };
+
+ private static final String NAME = "iso-2022-jp";
+
+ static final Iso2022Jp INSTANCE = new Iso2022Jp();
+
+ private Iso2022Jp() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return Charset.forName(NAME).newDecoder();
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso3.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso3.java
new file mode 100644
index 000000000..0667a160c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso3.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso3 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u0126',
+ '\u02d8',
+ '\u00a3',
+ '\u00a4',
+ '\ufffd',
+ '\u0124',
+ '\u00a7',
+ '\u00a8',
+ '\u0130',
+ '\u015e',
+ '\u011e',
+ '\u0134',
+ '\u00ad',
+ '\ufffd',
+ '\u017b',
+ '\u00b0',
+ '\u0127',
+ '\u00b2',
+ '\u00b3',
+ '\u00b4',
+ '\u00b5',
+ '\u0125',
+ '\u00b7',
+ '\u00b8',
+ '\u0131',
+ '\u015f',
+ '\u011f',
+ '\u0135',
+ '\u00bd',
+ '\ufffd',
+ '\u017c',
+ '\u00c0',
+ '\u00c1',
+ '\u00c2',
+ '\ufffd',
+ '\u00c4',
+ '\u010a',
+ '\u0108',
+ '\u00c7',
+ '\u00c8',
+ '\u00c9',
+ '\u00ca',
+ '\u00cb',
+ '\u00cc',
+ '\u00cd',
+ '\u00ce',
+ '\u00cf',
+ '\ufffd',
+ '\u00d1',
+ '\u00d2',
+ '\u00d3',
+ '\u00d4',
+ '\u0120',
+ '\u00d6',
+ '\u00d7',
+ '\u011c',
+ '\u00d9',
+ '\u00da',
+ '\u00db',
+ '\u00dc',
+ '\u016c',
+ '\u015c',
+ '\u00df',
+ '\u00e0',
+ '\u00e1',
+ '\u00e2',
+ '\ufffd',
+ '\u00e4',
+ '\u010b',
+ '\u0109',
+ '\u00e7',
+ '\u00e8',
+ '\u00e9',
+ '\u00ea',
+ '\u00eb',
+ '\u00ec',
+ '\u00ed',
+ '\u00ee',
+ '\u00ef',
+ '\ufffd',
+ '\u00f1',
+ '\u00f2',
+ '\u00f3',
+ '\u00f4',
+ '\u0121',
+ '\u00f6',
+ '\u00f7',
+ '\u011d',
+ '\u00f9',
+ '\u00fa',
+ '\u00fb',
+ '\u00fc',
+ '\u016d',
+ '\u015d',
+ '\u02d9'
+ };
+
+ private static final String[] LABELS = {
+ "csisolatin3",
+ "iso-8859-3",
+ "iso-ir-109",
+ "iso8859-3",
+ "iso88593",
+ "iso_8859-3",
+ "iso_8859-3:1988",
+ "l3",
+ "latin3"
+ };
+
+ private static final String NAME = "iso-8859-3";
+
+ static final Encoding INSTANCE = new Iso3();
+
+ private Iso3() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new FallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso4.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso4.java
new file mode 100644
index 000000000..b954869ab
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso4.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso4 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u0104',
+ '\u0138',
+ '\u0156',
+ '\u00a4',
+ '\u0128',
+ '\u013b',
+ '\u00a7',
+ '\u00a8',
+ '\u0160',
+ '\u0112',
+ '\u0122',
+ '\u0166',
+ '\u00ad',
+ '\u017d',
+ '\u00af',
+ '\u00b0',
+ '\u0105',
+ '\u02db',
+ '\u0157',
+ '\u00b4',
+ '\u0129',
+ '\u013c',
+ '\u02c7',
+ '\u00b8',
+ '\u0161',
+ '\u0113',
+ '\u0123',
+ '\u0167',
+ '\u014a',
+ '\u017e',
+ '\u014b',
+ '\u0100',
+ '\u00c1',
+ '\u00c2',
+ '\u00c3',
+ '\u00c4',
+ '\u00c5',
+ '\u00c6',
+ '\u012e',
+ '\u010c',
+ '\u00c9',
+ '\u0118',
+ '\u00cb',
+ '\u0116',
+ '\u00cd',
+ '\u00ce',
+ '\u012a',
+ '\u0110',
+ '\u0145',
+ '\u014c',
+ '\u0136',
+ '\u00d4',
+ '\u00d5',
+ '\u00d6',
+ '\u00d7',
+ '\u00d8',
+ '\u0172',
+ '\u00da',
+ '\u00db',
+ '\u00dc',
+ '\u0168',
+ '\u016a',
+ '\u00df',
+ '\u0101',
+ '\u00e1',
+ '\u00e2',
+ '\u00e3',
+ '\u00e4',
+ '\u00e5',
+ '\u00e6',
+ '\u012f',
+ '\u010d',
+ '\u00e9',
+ '\u0119',
+ '\u00eb',
+ '\u0117',
+ '\u00ed',
+ '\u00ee',
+ '\u012b',
+ '\u0111',
+ '\u0146',
+ '\u014d',
+ '\u0137',
+ '\u00f4',
+ '\u00f5',
+ '\u00f6',
+ '\u00f7',
+ '\u00f8',
+ '\u0173',
+ '\u00fa',
+ '\u00fb',
+ '\u00fc',
+ '\u0169',
+ '\u016b',
+ '\u02d9'
+ };
+
+ private static final String[] LABELS = {
+ "csisolatin4",
+ "iso-8859-4",
+ "iso-ir-110",
+ "iso8859-4",
+ "iso88594",
+ "iso_8859-4",
+ "iso_8859-4:1988",
+ "l4",
+ "latin4"
+ };
+
+ private static final String NAME = "iso-8859-4";
+
+ static final Encoding INSTANCE = new Iso4();
+
+ private Iso4() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso5.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso5.java
new file mode 100644
index 000000000..13946cdbb
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso5.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso5 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u0401',
+ '\u0402',
+ '\u0403',
+ '\u0404',
+ '\u0405',
+ '\u0406',
+ '\u0407',
+ '\u0408',
+ '\u0409',
+ '\u040a',
+ '\u040b',
+ '\u040c',
+ '\u00ad',
+ '\u040e',
+ '\u040f',
+ '\u0410',
+ '\u0411',
+ '\u0412',
+ '\u0413',
+ '\u0414',
+ '\u0415',
+ '\u0416',
+ '\u0417',
+ '\u0418',
+ '\u0419',
+ '\u041a',
+ '\u041b',
+ '\u041c',
+ '\u041d',
+ '\u041e',
+ '\u041f',
+ '\u0420',
+ '\u0421',
+ '\u0422',
+ '\u0423',
+ '\u0424',
+ '\u0425',
+ '\u0426',
+ '\u0427',
+ '\u0428',
+ '\u0429',
+ '\u042a',
+ '\u042b',
+ '\u042c',
+ '\u042d',
+ '\u042e',
+ '\u042f',
+ '\u0430',
+ '\u0431',
+ '\u0432',
+ '\u0433',
+ '\u0434',
+ '\u0435',
+ '\u0436',
+ '\u0437',
+ '\u0438',
+ '\u0439',
+ '\u043a',
+ '\u043b',
+ '\u043c',
+ '\u043d',
+ '\u043e',
+ '\u043f',
+ '\u0440',
+ '\u0441',
+ '\u0442',
+ '\u0443',
+ '\u0444',
+ '\u0445',
+ '\u0446',
+ '\u0447',
+ '\u0448',
+ '\u0449',
+ '\u044a',
+ '\u044b',
+ '\u044c',
+ '\u044d',
+ '\u044e',
+ '\u044f',
+ '\u2116',
+ '\u0451',
+ '\u0452',
+ '\u0453',
+ '\u0454',
+ '\u0455',
+ '\u0456',
+ '\u0457',
+ '\u0458',
+ '\u0459',
+ '\u045a',
+ '\u045b',
+ '\u045c',
+ '\u00a7',
+ '\u045e',
+ '\u045f'
+ };
+
+ private static final String[] LABELS = {
+ "csisolatincyrillic",
+ "cyrillic",
+ "iso-8859-5",
+ "iso-ir-144",
+ "iso8859-5",
+ "iso88595",
+ "iso_8859-5",
+ "iso_8859-5:1988"
+ };
+
+ private static final String NAME = "iso-8859-5";
+
+ static final Encoding INSTANCE = new Iso5();
+
+ private Iso5() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso6.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso6.java
new file mode 100644
index 000000000..02e6df8ba
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso6.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso6 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\u00a4',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\u060c',
+ '\u00ad',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\u061b',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\u061f',
+ '\ufffd',
+ '\u0621',
+ '\u0622',
+ '\u0623',
+ '\u0624',
+ '\u0625',
+ '\u0626',
+ '\u0627',
+ '\u0628',
+ '\u0629',
+ '\u062a',
+ '\u062b',
+ '\u062c',
+ '\u062d',
+ '\u062e',
+ '\u062f',
+ '\u0630',
+ '\u0631',
+ '\u0632',
+ '\u0633',
+ '\u0634',
+ '\u0635',
+ '\u0636',
+ '\u0637',
+ '\u0638',
+ '\u0639',
+ '\u063a',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\u0640',
+ '\u0641',
+ '\u0642',
+ '\u0643',
+ '\u0644',
+ '\u0645',
+ '\u0646',
+ '\u0647',
+ '\u0648',
+ '\u0649',
+ '\u064a',
+ '\u064b',
+ '\u064c',
+ '\u064d',
+ '\u064e',
+ '\u064f',
+ '\u0650',
+ '\u0651',
+ '\u0652',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd'
+ };
+
+ private static final String[] LABELS = {
+ "arabic",
+ "asmo-708",
+ "csiso88596e",
+ "csiso88596i",
+ "csisolatinarabic",
+ "ecma-114",
+ "iso-8859-6",
+ "iso-8859-6-e",
+ "iso-8859-6-i",
+ "iso-ir-127",
+ "iso8859-6",
+ "iso88596",
+ "iso_8859-6",
+ "iso_8859-6:1987"
+ };
+
+ private static final String NAME = "iso-8859-6";
+
+ static final Encoding INSTANCE = new Iso6();
+
+ private Iso6() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new FallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso7.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso7.java
new file mode 100644
index 000000000..630e702de
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso7.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso7 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u2018',
+ '\u2019',
+ '\u00a3',
+ '\u20ac',
+ '\u20af',
+ '\u00a6',
+ '\u00a7',
+ '\u00a8',
+ '\u00a9',
+ '\u037a',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\ufffd',
+ '\u2015',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u0384',
+ '\u0385',
+ '\u0386',
+ '\u00b7',
+ '\u0388',
+ '\u0389',
+ '\u038a',
+ '\u00bb',
+ '\u038c',
+ '\u00bd',
+ '\u038e',
+ '\u038f',
+ '\u0390',
+ '\u0391',
+ '\u0392',
+ '\u0393',
+ '\u0394',
+ '\u0395',
+ '\u0396',
+ '\u0397',
+ '\u0398',
+ '\u0399',
+ '\u039a',
+ '\u039b',
+ '\u039c',
+ '\u039d',
+ '\u039e',
+ '\u039f',
+ '\u03a0',
+ '\u03a1',
+ '\ufffd',
+ '\u03a3',
+ '\u03a4',
+ '\u03a5',
+ '\u03a6',
+ '\u03a7',
+ '\u03a8',
+ '\u03a9',
+ '\u03aa',
+ '\u03ab',
+ '\u03ac',
+ '\u03ad',
+ '\u03ae',
+ '\u03af',
+ '\u03b0',
+ '\u03b1',
+ '\u03b2',
+ '\u03b3',
+ '\u03b4',
+ '\u03b5',
+ '\u03b6',
+ '\u03b7',
+ '\u03b8',
+ '\u03b9',
+ '\u03ba',
+ '\u03bb',
+ '\u03bc',
+ '\u03bd',
+ '\u03be',
+ '\u03bf',
+ '\u03c0',
+ '\u03c1',
+ '\u03c2',
+ '\u03c3',
+ '\u03c4',
+ '\u03c5',
+ '\u03c6',
+ '\u03c7',
+ '\u03c8',
+ '\u03c9',
+ '\u03ca',
+ '\u03cb',
+ '\u03cc',
+ '\u03cd',
+ '\u03ce',
+ '\ufffd'
+ };
+
+ private static final String[] LABELS = {
+ "csisolatingreek",
+ "ecma-118",
+ "elot_928",
+ "greek",
+ "greek8",
+ "iso-8859-7",
+ "iso-ir-126",
+ "iso8859-7",
+ "iso88597",
+ "iso_8859-7",
+ "iso_8859-7:1987",
+ "sun_eu_greek"
+ };
+
+ private static final String NAME = "iso-8859-7";
+
+ static final Encoding INSTANCE = new Iso7();
+
+ private Iso7() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new FallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso8.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso8.java
new file mode 100644
index 000000000..10ee33486
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso8.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso8 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\ufffd',
+ '\u00a2',
+ '\u00a3',
+ '\u00a4',
+ '\u00a5',
+ '\u00a6',
+ '\u00a7',
+ '\u00a8',
+ '\u00a9',
+ '\u00d7',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u00af',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u00b4',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u00b8',
+ '\u00b9',
+ '\u00f7',
+ '\u00bb',
+ '\u00bc',
+ '\u00bd',
+ '\u00be',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\u2017',
+ '\u05d0',
+ '\u05d1',
+ '\u05d2',
+ '\u05d3',
+ '\u05d4',
+ '\u05d5',
+ '\u05d6',
+ '\u05d7',
+ '\u05d8',
+ '\u05d9',
+ '\u05da',
+ '\u05db',
+ '\u05dc',
+ '\u05dd',
+ '\u05de',
+ '\u05df',
+ '\u05e0',
+ '\u05e1',
+ '\u05e2',
+ '\u05e3',
+ '\u05e4',
+ '\u05e5',
+ '\u05e6',
+ '\u05e7',
+ '\u05e8',
+ '\u05e9',
+ '\u05ea',
+ '\ufffd',
+ '\ufffd',
+ '\u200e',
+ '\u200f',
+ '\ufffd'
+ };
+
+ private static final String[] LABELS = {
+ "csiso88598e",
+ "csisolatinhebrew",
+ "hebrew",
+ "iso-8859-8",
+ "iso-8859-8-e",
+ "iso-ir-138",
+ "iso8859-8",
+ "iso88598",
+ "iso_8859-8",
+ "iso_8859-8:1988",
+ "visual"
+ };
+
+ private static final String NAME = "iso-8859-8";
+
+ static final Encoding INSTANCE = new Iso8();
+
+ private Iso8() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new FallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso8I.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso8I.java
new file mode 100644
index 000000000..732e1c952
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Iso8I.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Iso8I extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0080',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u0085',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u0091',
+ '\u0092',
+ '\u0093',
+ '\u0094',
+ '\u0095',
+ '\u0096',
+ '\u0097',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\ufffd',
+ '\u00a2',
+ '\u00a3',
+ '\u00a4',
+ '\u00a5',
+ '\u00a6',
+ '\u00a7',
+ '\u00a8',
+ '\u00a9',
+ '\u00d7',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u00af',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u00b4',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u00b8',
+ '\u00b9',
+ '\u00f7',
+ '\u00bb',
+ '\u00bc',
+ '\u00bd',
+ '\u00be',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\u2017',
+ '\u05d0',
+ '\u05d1',
+ '\u05d2',
+ '\u05d3',
+ '\u05d4',
+ '\u05d5',
+ '\u05d6',
+ '\u05d7',
+ '\u05d8',
+ '\u05d9',
+ '\u05da',
+ '\u05db',
+ '\u05dc',
+ '\u05dd',
+ '\u05de',
+ '\u05df',
+ '\u05e0',
+ '\u05e1',
+ '\u05e2',
+ '\u05e3',
+ '\u05e4',
+ '\u05e5',
+ '\u05e6',
+ '\u05e7',
+ '\u05e8',
+ '\u05e9',
+ '\u05ea',
+ '\ufffd',
+ '\ufffd',
+ '\u200e',
+ '\u200f',
+ '\ufffd'
+ };
+
+ private static final String[] LABELS = {
+ "csiso88598i",
+ "iso-8859-8-i",
+ "logical"
+ };
+
+ private static final String NAME = "iso-8859-8-i";
+
+ static final Encoding INSTANCE = new Iso8I();
+
+ private Iso8I() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new FallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Koi8R.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Koi8R.java
new file mode 100644
index 000000000..b6157bd8e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Koi8R.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Koi8R extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u2500',
+ '\u2502',
+ '\u250c',
+ '\u2510',
+ '\u2514',
+ '\u2518',
+ '\u251c',
+ '\u2524',
+ '\u252c',
+ '\u2534',
+ '\u253c',
+ '\u2580',
+ '\u2584',
+ '\u2588',
+ '\u258c',
+ '\u2590',
+ '\u2591',
+ '\u2592',
+ '\u2593',
+ '\u2320',
+ '\u25a0',
+ '\u2219',
+ '\u221a',
+ '\u2248',
+ '\u2264',
+ '\u2265',
+ '\u00a0',
+ '\u2321',
+ '\u00b0',
+ '\u00b2',
+ '\u00b7',
+ '\u00f7',
+ '\u2550',
+ '\u2551',
+ '\u2552',
+ '\u0451',
+ '\u2553',
+ '\u2554',
+ '\u2555',
+ '\u2556',
+ '\u2557',
+ '\u2558',
+ '\u2559',
+ '\u255a',
+ '\u255b',
+ '\u255c',
+ '\u255d',
+ '\u255e',
+ '\u255f',
+ '\u2560',
+ '\u2561',
+ '\u0401',
+ '\u2562',
+ '\u2563',
+ '\u2564',
+ '\u2565',
+ '\u2566',
+ '\u2567',
+ '\u2568',
+ '\u2569',
+ '\u256a',
+ '\u256b',
+ '\u256c',
+ '\u00a9',
+ '\u044e',
+ '\u0430',
+ '\u0431',
+ '\u0446',
+ '\u0434',
+ '\u0435',
+ '\u0444',
+ '\u0433',
+ '\u0445',
+ '\u0438',
+ '\u0439',
+ '\u043a',
+ '\u043b',
+ '\u043c',
+ '\u043d',
+ '\u043e',
+ '\u043f',
+ '\u044f',
+ '\u0440',
+ '\u0441',
+ '\u0442',
+ '\u0443',
+ '\u0436',
+ '\u0432',
+ '\u044c',
+ '\u044b',
+ '\u0437',
+ '\u0448',
+ '\u044d',
+ '\u0449',
+ '\u0447',
+ '\u044a',
+ '\u042e',
+ '\u0410',
+ '\u0411',
+ '\u0426',
+ '\u0414',
+ '\u0415',
+ '\u0424',
+ '\u0413',
+ '\u0425',
+ '\u0418',
+ '\u0419',
+ '\u041a',
+ '\u041b',
+ '\u041c',
+ '\u041d',
+ '\u041e',
+ '\u041f',
+ '\u042f',
+ '\u0420',
+ '\u0421',
+ '\u0422',
+ '\u0423',
+ '\u0416',
+ '\u0412',
+ '\u042c',
+ '\u042b',
+ '\u0417',
+ '\u0428',
+ '\u042d',
+ '\u0429',
+ '\u0427',
+ '\u042a'
+ };
+
+ private static final String[] LABELS = {
+ "cskoi8r",
+ "koi",
+ "koi8",
+ "koi8-r",
+ "koi8_r"
+ };
+
+ private static final String NAME = "koi8-r";
+
+ static final Encoding INSTANCE = new Koi8R();
+
+ private Koi8R() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Koi8U.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Koi8U.java
new file mode 100644
index 000000000..8150838d3
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Koi8U.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Koi8U extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u2500',
+ '\u2502',
+ '\u250c',
+ '\u2510',
+ '\u2514',
+ '\u2518',
+ '\u251c',
+ '\u2524',
+ '\u252c',
+ '\u2534',
+ '\u253c',
+ '\u2580',
+ '\u2584',
+ '\u2588',
+ '\u258c',
+ '\u2590',
+ '\u2591',
+ '\u2592',
+ '\u2593',
+ '\u2320',
+ '\u25a0',
+ '\u2219',
+ '\u221a',
+ '\u2248',
+ '\u2264',
+ '\u2265',
+ '\u00a0',
+ '\u2321',
+ '\u00b0',
+ '\u00b2',
+ '\u00b7',
+ '\u00f7',
+ '\u2550',
+ '\u2551',
+ '\u2552',
+ '\u0451',
+ '\u0454',
+ '\u2554',
+ '\u0456',
+ '\u0457',
+ '\u2557',
+ '\u2558',
+ '\u2559',
+ '\u255a',
+ '\u255b',
+ '\u0491',
+ '\u045e',
+ '\u255e',
+ '\u255f',
+ '\u2560',
+ '\u2561',
+ '\u0401',
+ '\u0404',
+ '\u2563',
+ '\u0406',
+ '\u0407',
+ '\u2566',
+ '\u2567',
+ '\u2568',
+ '\u2569',
+ '\u256a',
+ '\u0490',
+ '\u040e',
+ '\u00a9',
+ '\u044e',
+ '\u0430',
+ '\u0431',
+ '\u0446',
+ '\u0434',
+ '\u0435',
+ '\u0444',
+ '\u0433',
+ '\u0445',
+ '\u0438',
+ '\u0439',
+ '\u043a',
+ '\u043b',
+ '\u043c',
+ '\u043d',
+ '\u043e',
+ '\u043f',
+ '\u044f',
+ '\u0440',
+ '\u0441',
+ '\u0442',
+ '\u0443',
+ '\u0436',
+ '\u0432',
+ '\u044c',
+ '\u044b',
+ '\u0437',
+ '\u0448',
+ '\u044d',
+ '\u0449',
+ '\u0447',
+ '\u044a',
+ '\u042e',
+ '\u0410',
+ '\u0411',
+ '\u0426',
+ '\u0414',
+ '\u0415',
+ '\u0424',
+ '\u0413',
+ '\u0425',
+ '\u0418',
+ '\u0419',
+ '\u041a',
+ '\u041b',
+ '\u041c',
+ '\u041d',
+ '\u041e',
+ '\u041f',
+ '\u042f',
+ '\u0420',
+ '\u0421',
+ '\u0422',
+ '\u0423',
+ '\u0416',
+ '\u0412',
+ '\u042c',
+ '\u042b',
+ '\u0417',
+ '\u0428',
+ '\u042d',
+ '\u0429',
+ '\u0427',
+ '\u042a'
+ };
+
+ private static final String[] LABELS = {
+ "koi8-ru",
+ "koi8-u"
+ };
+
+ private static final String NAME = "koi8-u";
+
+ static final Encoding INSTANCE = new Koi8U();
+
+ private Koi8U() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/MacCyrillic.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/MacCyrillic.java
new file mode 100644
index 000000000..f46546ce2
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/MacCyrillic.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class MacCyrillic extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0410',
+ '\u0411',
+ '\u0412',
+ '\u0413',
+ '\u0414',
+ '\u0415',
+ '\u0416',
+ '\u0417',
+ '\u0418',
+ '\u0419',
+ '\u041a',
+ '\u041b',
+ '\u041c',
+ '\u041d',
+ '\u041e',
+ '\u041f',
+ '\u0420',
+ '\u0421',
+ '\u0422',
+ '\u0423',
+ '\u0424',
+ '\u0425',
+ '\u0426',
+ '\u0427',
+ '\u0428',
+ '\u0429',
+ '\u042a',
+ '\u042b',
+ '\u042c',
+ '\u042d',
+ '\u042e',
+ '\u042f',
+ '\u2020',
+ '\u00b0',
+ '\u0490',
+ '\u00a3',
+ '\u00a7',
+ '\u2022',
+ '\u00b6',
+ '\u0406',
+ '\u00ae',
+ '\u00a9',
+ '\u2122',
+ '\u0402',
+ '\u0452',
+ '\u2260',
+ '\u0403',
+ '\u0453',
+ '\u221e',
+ '\u00b1',
+ '\u2264',
+ '\u2265',
+ '\u0456',
+ '\u00b5',
+ '\u0491',
+ '\u0408',
+ '\u0404',
+ '\u0454',
+ '\u0407',
+ '\u0457',
+ '\u0409',
+ '\u0459',
+ '\u040a',
+ '\u045a',
+ '\u0458',
+ '\u0405',
+ '\u00ac',
+ '\u221a',
+ '\u0192',
+ '\u2248',
+ '\u2206',
+ '\u00ab',
+ '\u00bb',
+ '\u2026',
+ '\u00a0',
+ '\u040b',
+ '\u045b',
+ '\u040c',
+ '\u045c',
+ '\u0455',
+ '\u2013',
+ '\u2014',
+ '\u201c',
+ '\u201d',
+ '\u2018',
+ '\u2019',
+ '\u00f7',
+ '\u201e',
+ '\u040e',
+ '\u045e',
+ '\u040f',
+ '\u045f',
+ '\u2116',
+ '\u0401',
+ '\u0451',
+ '\u044f',
+ '\u0430',
+ '\u0431',
+ '\u0432',
+ '\u0433',
+ '\u0434',
+ '\u0435',
+ '\u0436',
+ '\u0437',
+ '\u0438',
+ '\u0439',
+ '\u043a',
+ '\u043b',
+ '\u043c',
+ '\u043d',
+ '\u043e',
+ '\u043f',
+ '\u0440',
+ '\u0441',
+ '\u0442',
+ '\u0443',
+ '\u0444',
+ '\u0445',
+ '\u0446',
+ '\u0447',
+ '\u0448',
+ '\u0449',
+ '\u044a',
+ '\u044b',
+ '\u044c',
+ '\u044d',
+ '\u044e',
+ '\u20ac'
+ };
+
+ private static final String[] LABELS = {
+ "x-mac-cyrillic",
+ "x-mac-ukrainian"
+ };
+
+ private static final String NAME = "x-mac-cyrillic";
+
+ static final Encoding INSTANCE = new MacCyrillic();
+
+ private MacCyrillic() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Macintosh.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Macintosh.java
new file mode 100644
index 000000000..70e356f23
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Macintosh.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Macintosh extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u00c4',
+ '\u00c5',
+ '\u00c7',
+ '\u00c9',
+ '\u00d1',
+ '\u00d6',
+ '\u00dc',
+ '\u00e1',
+ '\u00e0',
+ '\u00e2',
+ '\u00e4',
+ '\u00e3',
+ '\u00e5',
+ '\u00e7',
+ '\u00e9',
+ '\u00e8',
+ '\u00ea',
+ '\u00eb',
+ '\u00ed',
+ '\u00ec',
+ '\u00ee',
+ '\u00ef',
+ '\u00f1',
+ '\u00f3',
+ '\u00f2',
+ '\u00f4',
+ '\u00f6',
+ '\u00f5',
+ '\u00fa',
+ '\u00f9',
+ '\u00fb',
+ '\u00fc',
+ '\u2020',
+ '\u00b0',
+ '\u00a2',
+ '\u00a3',
+ '\u00a7',
+ '\u2022',
+ '\u00b6',
+ '\u00df',
+ '\u00ae',
+ '\u00a9',
+ '\u2122',
+ '\u00b4',
+ '\u00a8',
+ '\u2260',
+ '\u00c6',
+ '\u00d8',
+ '\u221e',
+ '\u00b1',
+ '\u2264',
+ '\u2265',
+ '\u00a5',
+ '\u00b5',
+ '\u2202',
+ '\u2211',
+ '\u220f',
+ '\u03c0',
+ '\u222b',
+ '\u00aa',
+ '\u00ba',
+ '\u03a9',
+ '\u00e6',
+ '\u00f8',
+ '\u00bf',
+ '\u00a1',
+ '\u00ac',
+ '\u221a',
+ '\u0192',
+ '\u2248',
+ '\u2206',
+ '\u00ab',
+ '\u00bb',
+ '\u2026',
+ '\u00a0',
+ '\u00c0',
+ '\u00c3',
+ '\u00d5',
+ '\u0152',
+ '\u0153',
+ '\u2013',
+ '\u2014',
+ '\u201c',
+ '\u201d',
+ '\u2018',
+ '\u2019',
+ '\u00f7',
+ '\u25ca',
+ '\u00ff',
+ '\u0178',
+ '\u2044',
+ '\u20ac',
+ '\u2039',
+ '\u203a',
+ '\ufb01',
+ '\ufb02',
+ '\u2021',
+ '\u00b7',
+ '\u201a',
+ '\u201e',
+ '\u2030',
+ '\u00c2',
+ '\u00ca',
+ '\u00c1',
+ '\u00cb',
+ '\u00c8',
+ '\u00cd',
+ '\u00ce',
+ '\u00cf',
+ '\u00cc',
+ '\u00d3',
+ '\u00d4',
+ '\uf8ff',
+ '\u00d2',
+ '\u00da',
+ '\u00db',
+ '\u00d9',
+ '\u0131',
+ '\u02c6',
+ '\u02dc',
+ '\u00af',
+ '\u02d8',
+ '\u02d9',
+ '\u02da',
+ '\u00b8',
+ '\u02dd',
+ '\u02db',
+ '\u02c7'
+ };
+
+ private static final String[] LABELS = {
+ "csmacintosh",
+ "mac",
+ "macintosh",
+ "x-mac-roman"
+ };
+
+ private static final String NAME = "macintosh";
+
+ static final Encoding INSTANCE = new Macintosh();
+
+ private Macintosh() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Replacement.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Replacement.java
new file mode 100644
index 000000000..abb6e24e7
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Replacement.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class Replacement extends Encoding {
+
+ private static final String[] LABELS = {
+ "csiso2022kr",
+ "hz-gb-2312",
+ "iso-2022-cn",
+ "iso-2022-cn-ext",
+ "iso-2022-kr"
+ };
+
+ private static final String NAME = "replacement";
+
+ static final Replacement INSTANCE = new Replacement();
+
+ private Replacement() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new ReplacementDecoder(this);
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/ReplacementDecoder.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/ReplacementDecoder.java
new file mode 100644
index 000000000..f6f2448f6
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/ReplacementDecoder.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CoderResult;
+
+class ReplacementDecoder extends Decoder {
+
+ private boolean haveEmitted = false;
+
+ ReplacementDecoder(Charset cs) {
+ super(cs, 1.0f, 1.0f);
+ }
+
+ @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
+ for (;;) {
+ if (!in.hasRemaining()) {
+ return CoderResult.UNDERFLOW;
+ }
+ if (haveEmitted) {
+ in.position(in.limit());
+ return CoderResult.UNDERFLOW;
+ }
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ in.position(in.limit());
+ haveEmitted = true;
+ if (this.report) {
+ return CoderResult.malformedForLength(1);
+ }
+ out.put('\uFFFD');
+ }
+ }
+
+ /**
+ * @see java.nio.charset.CharsetDecoder#implFlush(java.nio.CharBuffer)
+ */
+ @Override protected CoderResult implFlush(CharBuffer out) {
+ // TODO Auto-generated method stub
+ return super.implFlush(out);
+ }
+
+ /**
+ * @see java.nio.charset.CharsetDecoder#implReset()
+ */
+ @Override protected void implReset() {
+ // TODO Auto-generated method stub
+ super.implReset();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/ShiftJis.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/ShiftJis.java
new file mode 100644
index 000000000..6638eab39
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/ShiftJis.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class ShiftJis extends Encoding {
+
+ private static final String[] LABELS = {
+ "csshiftjis",
+ "ms932",
+ "ms_kanji",
+ "shift-jis",
+ "shift_jis",
+ "sjis",
+ "windows-31j",
+ "x-sjis"
+ };
+
+ private static final String NAME = "shift_jis";
+
+ static final ShiftJis INSTANCE = new ShiftJis();
+
+ private ShiftJis() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return Charset.forName(NAME).newDecoder();
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/UserDefined.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/UserDefined.java
new file mode 100644
index 000000000..61534cb28
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/UserDefined.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class UserDefined extends Encoding {
+
+ private static final String[] LABELS = {
+ "x-user-defined"
+ };
+
+ private static final String NAME = "x-user-defined";
+
+ static final UserDefined INSTANCE = new UserDefined();
+
+ private UserDefined() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new UserDefinedDecoder(this);
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/UserDefinedDecoder.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/UserDefinedDecoder.java
new file mode 100644
index 000000000..c14ca8627
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/UserDefinedDecoder.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+
+class UserDefinedDecoder extends Decoder {
+
+ UserDefinedDecoder(Charset cs) {
+ super(cs, 1.0f, 1.0f);
+ }
+
+ @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
+ // TODO figure out if it's worthwhile to optimize the case where both
+ // buffers are array-backed.
+ for (;;) {
+ if (!in.hasRemaining()) {
+ return CoderResult.UNDERFLOW;
+ }
+ if (!out.hasRemaining()) {
+ return CoderResult.OVERFLOW;
+ }
+ int b = (int)in.get();
+ if (b >= 0) {
+ out.put((char)b);
+ } else {
+ out.put((char)(b + 128 + 0xF780));
+ }
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf16Be.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf16Be.java
new file mode 100644
index 000000000..16c0d2fd5
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf16Be.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class Utf16Be extends Encoding {
+
+ private static final String[] LABELS = {
+ "utf-16be"
+ };
+
+ private static final String NAME = "utf-16be";
+
+ static final Utf16Be INSTANCE = new Utf16Be();
+
+ private Utf16Be() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return Charset.forName(NAME).newDecoder();
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf16Le.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf16Le.java
new file mode 100644
index 000000000..7381235b5
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf16Le.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class Utf16Le extends Encoding {
+
+ private static final String[] LABELS = {
+ "utf-16",
+ "utf-16le"
+ };
+
+ private static final String NAME = "utf-16le";
+
+ static final Utf16Le INSTANCE = new Utf16Le();
+
+ private Utf16Le() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return Charset.forName(NAME).newDecoder();
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf8.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf8.java
new file mode 100644
index 000000000..d6ea7b514
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Utf8.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+class Utf8 extends Encoding {
+
+ private static final String[] LABELS = {
+ "unicode-1-1-utf-8",
+ "utf-8",
+ "utf8"
+ };
+
+ private static final String NAME = "utf-8";
+
+ static final Utf8 INSTANCE = new Utf8();
+
+ private Utf8() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return Charset.forName(NAME).newDecoder();
+ }
+
+ @Override public CharsetEncoder newEncoder() {
+ return Charset.forName(NAME).newEncoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1250.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1250.java
new file mode 100644
index 000000000..0b3f50875
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1250.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Windows1250 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u20ac',
+ '\u0081',
+ '\u201a',
+ '\u0083',
+ '\u201e',
+ '\u2026',
+ '\u2020',
+ '\u2021',
+ '\u0088',
+ '\u2030',
+ '\u0160',
+ '\u2039',
+ '\u015a',
+ '\u0164',
+ '\u017d',
+ '\u0179',
+ '\u0090',
+ '\u2018',
+ '\u2019',
+ '\u201c',
+ '\u201d',
+ '\u2022',
+ '\u2013',
+ '\u2014',
+ '\u0098',
+ '\u2122',
+ '\u0161',
+ '\u203a',
+ '\u015b',
+ '\u0165',
+ '\u017e',
+ '\u017a',
+ '\u00a0',
+ '\u02c7',
+ '\u02d8',
+ '\u0141',
+ '\u00a4',
+ '\u0104',
+ '\u00a6',
+ '\u00a7',
+ '\u00a8',
+ '\u00a9',
+ '\u015e',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u017b',
+ '\u00b0',
+ '\u00b1',
+ '\u02db',
+ '\u0142',
+ '\u00b4',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u00b8',
+ '\u0105',
+ '\u015f',
+ '\u00bb',
+ '\u013d',
+ '\u02dd',
+ '\u013e',
+ '\u017c',
+ '\u0154',
+ '\u00c1',
+ '\u00c2',
+ '\u0102',
+ '\u00c4',
+ '\u0139',
+ '\u0106',
+ '\u00c7',
+ '\u010c',
+ '\u00c9',
+ '\u0118',
+ '\u00cb',
+ '\u011a',
+ '\u00cd',
+ '\u00ce',
+ '\u010e',
+ '\u0110',
+ '\u0143',
+ '\u0147',
+ '\u00d3',
+ '\u00d4',
+ '\u0150',
+ '\u00d6',
+ '\u00d7',
+ '\u0158',
+ '\u016e',
+ '\u00da',
+ '\u0170',
+ '\u00dc',
+ '\u00dd',
+ '\u0162',
+ '\u00df',
+ '\u0155',
+ '\u00e1',
+ '\u00e2',
+ '\u0103',
+ '\u00e4',
+ '\u013a',
+ '\u0107',
+ '\u00e7',
+ '\u010d',
+ '\u00e9',
+ '\u0119',
+ '\u00eb',
+ '\u011b',
+ '\u00ed',
+ '\u00ee',
+ '\u010f',
+ '\u0111',
+ '\u0144',
+ '\u0148',
+ '\u00f3',
+ '\u00f4',
+ '\u0151',
+ '\u00f6',
+ '\u00f7',
+ '\u0159',
+ '\u016f',
+ '\u00fa',
+ '\u0171',
+ '\u00fc',
+ '\u00fd',
+ '\u0163',
+ '\u02d9'
+ };
+
+ private static final String[] LABELS = {
+ "cp1250",
+ "windows-1250",
+ "x-cp1250"
+ };
+
+ private static final String NAME = "windows-1250";
+
+ static final Encoding INSTANCE = new Windows1250();
+
+ private Windows1250() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1251.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1251.java
new file mode 100644
index 000000000..def5cf11e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1251.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Windows1251 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u0402',
+ '\u0403',
+ '\u201a',
+ '\u0453',
+ '\u201e',
+ '\u2026',
+ '\u2020',
+ '\u2021',
+ '\u20ac',
+ '\u2030',
+ '\u0409',
+ '\u2039',
+ '\u040a',
+ '\u040c',
+ '\u040b',
+ '\u040f',
+ '\u0452',
+ '\u2018',
+ '\u2019',
+ '\u201c',
+ '\u201d',
+ '\u2022',
+ '\u2013',
+ '\u2014',
+ '\u0098',
+ '\u2122',
+ '\u0459',
+ '\u203a',
+ '\u045a',
+ '\u045c',
+ '\u045b',
+ '\u045f',
+ '\u00a0',
+ '\u040e',
+ '\u045e',
+ '\u0408',
+ '\u00a4',
+ '\u0490',
+ '\u00a6',
+ '\u00a7',
+ '\u0401',
+ '\u00a9',
+ '\u0404',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u0407',
+ '\u00b0',
+ '\u00b1',
+ '\u0406',
+ '\u0456',
+ '\u0491',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u0451',
+ '\u2116',
+ '\u0454',
+ '\u00bb',
+ '\u0458',
+ '\u0405',
+ '\u0455',
+ '\u0457',
+ '\u0410',
+ '\u0411',
+ '\u0412',
+ '\u0413',
+ '\u0414',
+ '\u0415',
+ '\u0416',
+ '\u0417',
+ '\u0418',
+ '\u0419',
+ '\u041a',
+ '\u041b',
+ '\u041c',
+ '\u041d',
+ '\u041e',
+ '\u041f',
+ '\u0420',
+ '\u0421',
+ '\u0422',
+ '\u0423',
+ '\u0424',
+ '\u0425',
+ '\u0426',
+ '\u0427',
+ '\u0428',
+ '\u0429',
+ '\u042a',
+ '\u042b',
+ '\u042c',
+ '\u042d',
+ '\u042e',
+ '\u042f',
+ '\u0430',
+ '\u0431',
+ '\u0432',
+ '\u0433',
+ '\u0434',
+ '\u0435',
+ '\u0436',
+ '\u0437',
+ '\u0438',
+ '\u0439',
+ '\u043a',
+ '\u043b',
+ '\u043c',
+ '\u043d',
+ '\u043e',
+ '\u043f',
+ '\u0440',
+ '\u0441',
+ '\u0442',
+ '\u0443',
+ '\u0444',
+ '\u0445',
+ '\u0446',
+ '\u0447',
+ '\u0448',
+ '\u0449',
+ '\u044a',
+ '\u044b',
+ '\u044c',
+ '\u044d',
+ '\u044e',
+ '\u044f'
+ };
+
+ private static final String[] LABELS = {
+ "cp1251",
+ "windows-1251",
+ "x-cp1251"
+ };
+
+ private static final String NAME = "windows-1251";
+
+ static final Encoding INSTANCE = new Windows1251();
+
+ private Windows1251() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1252.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1252.java
new file mode 100644
index 000000000..4b3fa1ffa
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1252.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Windows1252 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u20ac',
+ '\u0081',
+ '\u201a',
+ '\u0192',
+ '\u201e',
+ '\u2026',
+ '\u2020',
+ '\u2021',
+ '\u02c6',
+ '\u2030',
+ '\u0160',
+ '\u2039',
+ '\u0152',
+ '\u008d',
+ '\u017d',
+ '\u008f',
+ '\u0090',
+ '\u2018',
+ '\u2019',
+ '\u201c',
+ '\u201d',
+ '\u2022',
+ '\u2013',
+ '\u2014',
+ '\u02dc',
+ '\u2122',
+ '\u0161',
+ '\u203a',
+ '\u0153',
+ '\u009d',
+ '\u017e',
+ '\u0178',
+ '\u00a0',
+ '\u00a1',
+ '\u00a2',
+ '\u00a3',
+ '\u00a4',
+ '\u00a5',
+ '\u00a6',
+ '\u00a7',
+ '\u00a8',
+ '\u00a9',
+ '\u00aa',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u00af',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u00b4',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u00b8',
+ '\u00b9',
+ '\u00ba',
+ '\u00bb',
+ '\u00bc',
+ '\u00bd',
+ '\u00be',
+ '\u00bf',
+ '\u00c0',
+ '\u00c1',
+ '\u00c2',
+ '\u00c3',
+ '\u00c4',
+ '\u00c5',
+ '\u00c6',
+ '\u00c7',
+ '\u00c8',
+ '\u00c9',
+ '\u00ca',
+ '\u00cb',
+ '\u00cc',
+ '\u00cd',
+ '\u00ce',
+ '\u00cf',
+ '\u00d0',
+ '\u00d1',
+ '\u00d2',
+ '\u00d3',
+ '\u00d4',
+ '\u00d5',
+ '\u00d6',
+ '\u00d7',
+ '\u00d8',
+ '\u00d9',
+ '\u00da',
+ '\u00db',
+ '\u00dc',
+ '\u00dd',
+ '\u00de',
+ '\u00df',
+ '\u00e0',
+ '\u00e1',
+ '\u00e2',
+ '\u00e3',
+ '\u00e4',
+ '\u00e5',
+ '\u00e6',
+ '\u00e7',
+ '\u00e8',
+ '\u00e9',
+ '\u00ea',
+ '\u00eb',
+ '\u00ec',
+ '\u00ed',
+ '\u00ee',
+ '\u00ef',
+ '\u00f0',
+ '\u00f1',
+ '\u00f2',
+ '\u00f3',
+ '\u00f4',
+ '\u00f5',
+ '\u00f6',
+ '\u00f7',
+ '\u00f8',
+ '\u00f9',
+ '\u00fa',
+ '\u00fb',
+ '\u00fc',
+ '\u00fd',
+ '\u00fe',
+ '\u00ff'
+ };
+
+ private static final String[] LABELS = {
+ "ansi_x3.4-1968",
+ "ascii",
+ "cp1252",
+ "cp819",
+ "csisolatin1",
+ "ibm819",
+ "iso-8859-1",
+ "iso-ir-100",
+ "iso8859-1",
+ "iso88591",
+ "iso_8859-1",
+ "iso_8859-1:1987",
+ "l1",
+ "latin1",
+ "us-ascii",
+ "windows-1252",
+ "x-cp1252"
+ };
+
+ private static final String NAME = "windows-1252";
+
+ static final Encoding INSTANCE = new Windows1252();
+
+ private Windows1252() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1253.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1253.java
new file mode 100644
index 000000000..c96e8630c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1253.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Windows1253 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u20ac',
+ '\u0081',
+ '\u201a',
+ '\u0192',
+ '\u201e',
+ '\u2026',
+ '\u2020',
+ '\u2021',
+ '\u0088',
+ '\u2030',
+ '\u008a',
+ '\u2039',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u2018',
+ '\u2019',
+ '\u201c',
+ '\u201d',
+ '\u2022',
+ '\u2013',
+ '\u2014',
+ '\u0098',
+ '\u2122',
+ '\u009a',
+ '\u203a',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u0385',
+ '\u0386',
+ '\u00a3',
+ '\u00a4',
+ '\u00a5',
+ '\u00a6',
+ '\u00a7',
+ '\u00a8',
+ '\u00a9',
+ '\ufffd',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u2015',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u0384',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u0388',
+ '\u0389',
+ '\u038a',
+ '\u00bb',
+ '\u038c',
+ '\u00bd',
+ '\u038e',
+ '\u038f',
+ '\u0390',
+ '\u0391',
+ '\u0392',
+ '\u0393',
+ '\u0394',
+ '\u0395',
+ '\u0396',
+ '\u0397',
+ '\u0398',
+ '\u0399',
+ '\u039a',
+ '\u039b',
+ '\u039c',
+ '\u039d',
+ '\u039e',
+ '\u039f',
+ '\u03a0',
+ '\u03a1',
+ '\ufffd',
+ '\u03a3',
+ '\u03a4',
+ '\u03a5',
+ '\u03a6',
+ '\u03a7',
+ '\u03a8',
+ '\u03a9',
+ '\u03aa',
+ '\u03ab',
+ '\u03ac',
+ '\u03ad',
+ '\u03ae',
+ '\u03af',
+ '\u03b0',
+ '\u03b1',
+ '\u03b2',
+ '\u03b3',
+ '\u03b4',
+ '\u03b5',
+ '\u03b6',
+ '\u03b7',
+ '\u03b8',
+ '\u03b9',
+ '\u03ba',
+ '\u03bb',
+ '\u03bc',
+ '\u03bd',
+ '\u03be',
+ '\u03bf',
+ '\u03c0',
+ '\u03c1',
+ '\u03c2',
+ '\u03c3',
+ '\u03c4',
+ '\u03c5',
+ '\u03c6',
+ '\u03c7',
+ '\u03c8',
+ '\u03c9',
+ '\u03ca',
+ '\u03cb',
+ '\u03cc',
+ '\u03cd',
+ '\u03ce',
+ '\ufffd'
+ };
+
+ private static final String[] LABELS = {
+ "cp1253",
+ "windows-1253",
+ "x-cp1253"
+ };
+
+ private static final String NAME = "windows-1253";
+
+ static final Encoding INSTANCE = new Windows1253();
+
+ private Windows1253() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new FallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1254.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1254.java
new file mode 100644
index 000000000..fc3aa9839
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1254.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Windows1254 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u20ac',
+ '\u0081',
+ '\u201a',
+ '\u0192',
+ '\u201e',
+ '\u2026',
+ '\u2020',
+ '\u2021',
+ '\u02c6',
+ '\u2030',
+ '\u0160',
+ '\u2039',
+ '\u0152',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u2018',
+ '\u2019',
+ '\u201c',
+ '\u201d',
+ '\u2022',
+ '\u2013',
+ '\u2014',
+ '\u02dc',
+ '\u2122',
+ '\u0161',
+ '\u203a',
+ '\u0153',
+ '\u009d',
+ '\u009e',
+ '\u0178',
+ '\u00a0',
+ '\u00a1',
+ '\u00a2',
+ '\u00a3',
+ '\u00a4',
+ '\u00a5',
+ '\u00a6',
+ '\u00a7',
+ '\u00a8',
+ '\u00a9',
+ '\u00aa',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u00af',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u00b4',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u00b8',
+ '\u00b9',
+ '\u00ba',
+ '\u00bb',
+ '\u00bc',
+ '\u00bd',
+ '\u00be',
+ '\u00bf',
+ '\u00c0',
+ '\u00c1',
+ '\u00c2',
+ '\u00c3',
+ '\u00c4',
+ '\u00c5',
+ '\u00c6',
+ '\u00c7',
+ '\u00c8',
+ '\u00c9',
+ '\u00ca',
+ '\u00cb',
+ '\u00cc',
+ '\u00cd',
+ '\u00ce',
+ '\u00cf',
+ '\u011e',
+ '\u00d1',
+ '\u00d2',
+ '\u00d3',
+ '\u00d4',
+ '\u00d5',
+ '\u00d6',
+ '\u00d7',
+ '\u00d8',
+ '\u00d9',
+ '\u00da',
+ '\u00db',
+ '\u00dc',
+ '\u0130',
+ '\u015e',
+ '\u00df',
+ '\u00e0',
+ '\u00e1',
+ '\u00e2',
+ '\u00e3',
+ '\u00e4',
+ '\u00e5',
+ '\u00e6',
+ '\u00e7',
+ '\u00e8',
+ '\u00e9',
+ '\u00ea',
+ '\u00eb',
+ '\u00ec',
+ '\u00ed',
+ '\u00ee',
+ '\u00ef',
+ '\u011f',
+ '\u00f1',
+ '\u00f2',
+ '\u00f3',
+ '\u00f4',
+ '\u00f5',
+ '\u00f6',
+ '\u00f7',
+ '\u00f8',
+ '\u00f9',
+ '\u00fa',
+ '\u00fb',
+ '\u00fc',
+ '\u0131',
+ '\u015f',
+ '\u00ff'
+ };
+
+ private static final String[] LABELS = {
+ "cp1254",
+ "csisolatin5",
+ "iso-8859-9",
+ "iso-ir-148",
+ "iso8859-9",
+ "iso88599",
+ "iso_8859-9",
+ "iso_8859-9:1989",
+ "l5",
+ "latin5",
+ "windows-1254",
+ "x-cp1254"
+ };
+
+ private static final String NAME = "windows-1254";
+
+ static final Encoding INSTANCE = new Windows1254();
+
+ private Windows1254() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1255.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1255.java
new file mode 100644
index 000000000..957203d80
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1255.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Windows1255 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u20ac',
+ '\u0081',
+ '\u201a',
+ '\u0192',
+ '\u201e',
+ '\u2026',
+ '\u2020',
+ '\u2021',
+ '\u02c6',
+ '\u2030',
+ '\u008a',
+ '\u2039',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u2018',
+ '\u2019',
+ '\u201c',
+ '\u201d',
+ '\u2022',
+ '\u2013',
+ '\u2014',
+ '\u02dc',
+ '\u2122',
+ '\u009a',
+ '\u203a',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u00a1',
+ '\u00a2',
+ '\u00a3',
+ '\u20aa',
+ '\u00a5',
+ '\u00a6',
+ '\u00a7',
+ '\u00a8',
+ '\u00a9',
+ '\u00d7',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u00af',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u00b4',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u00b8',
+ '\u00b9',
+ '\u00f7',
+ '\u00bb',
+ '\u00bc',
+ '\u00bd',
+ '\u00be',
+ '\u00bf',
+ '\u05b0',
+ '\u05b1',
+ '\u05b2',
+ '\u05b3',
+ '\u05b4',
+ '\u05b5',
+ '\u05b6',
+ '\u05b7',
+ '\u05b8',
+ '\u05b9',
+ '\ufffd',
+ '\u05bb',
+ '\u05bc',
+ '\u05bd',
+ '\u05be',
+ '\u05bf',
+ '\u05c0',
+ '\u05c1',
+ '\u05c2',
+ '\u05c3',
+ '\u05f0',
+ '\u05f1',
+ '\u05f2',
+ '\u05f3',
+ '\u05f4',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\u05d0',
+ '\u05d1',
+ '\u05d2',
+ '\u05d3',
+ '\u05d4',
+ '\u05d5',
+ '\u05d6',
+ '\u05d7',
+ '\u05d8',
+ '\u05d9',
+ '\u05da',
+ '\u05db',
+ '\u05dc',
+ '\u05dd',
+ '\u05de',
+ '\u05df',
+ '\u05e0',
+ '\u05e1',
+ '\u05e2',
+ '\u05e3',
+ '\u05e4',
+ '\u05e5',
+ '\u05e6',
+ '\u05e7',
+ '\u05e8',
+ '\u05e9',
+ '\u05ea',
+ '\ufffd',
+ '\ufffd',
+ '\u200e',
+ '\u200f',
+ '\ufffd'
+ };
+
+ private static final String[] LABELS = {
+ "cp1255",
+ "windows-1255",
+ "x-cp1255"
+ };
+
+ private static final String NAME = "windows-1255";
+
+ static final Encoding INSTANCE = new Windows1255();
+
+ private Windows1255() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new FallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1256.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1256.java
new file mode 100644
index 000000000..87d805e1e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1256.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Windows1256 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u20ac',
+ '\u067e',
+ '\u201a',
+ '\u0192',
+ '\u201e',
+ '\u2026',
+ '\u2020',
+ '\u2021',
+ '\u02c6',
+ '\u2030',
+ '\u0679',
+ '\u2039',
+ '\u0152',
+ '\u0686',
+ '\u0698',
+ '\u0688',
+ '\u06af',
+ '\u2018',
+ '\u2019',
+ '\u201c',
+ '\u201d',
+ '\u2022',
+ '\u2013',
+ '\u2014',
+ '\u06a9',
+ '\u2122',
+ '\u0691',
+ '\u203a',
+ '\u0153',
+ '\u200c',
+ '\u200d',
+ '\u06ba',
+ '\u00a0',
+ '\u060c',
+ '\u00a2',
+ '\u00a3',
+ '\u00a4',
+ '\u00a5',
+ '\u00a6',
+ '\u00a7',
+ '\u00a8',
+ '\u00a9',
+ '\u06be',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u00af',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u00b4',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u00b8',
+ '\u00b9',
+ '\u061b',
+ '\u00bb',
+ '\u00bc',
+ '\u00bd',
+ '\u00be',
+ '\u061f',
+ '\u06c1',
+ '\u0621',
+ '\u0622',
+ '\u0623',
+ '\u0624',
+ '\u0625',
+ '\u0626',
+ '\u0627',
+ '\u0628',
+ '\u0629',
+ '\u062a',
+ '\u062b',
+ '\u062c',
+ '\u062d',
+ '\u062e',
+ '\u062f',
+ '\u0630',
+ '\u0631',
+ '\u0632',
+ '\u0633',
+ '\u0634',
+ '\u0635',
+ '\u0636',
+ '\u00d7',
+ '\u0637',
+ '\u0638',
+ '\u0639',
+ '\u063a',
+ '\u0640',
+ '\u0641',
+ '\u0642',
+ '\u0643',
+ '\u00e0',
+ '\u0644',
+ '\u00e2',
+ '\u0645',
+ '\u0646',
+ '\u0647',
+ '\u0648',
+ '\u00e7',
+ '\u00e8',
+ '\u00e9',
+ '\u00ea',
+ '\u00eb',
+ '\u0649',
+ '\u064a',
+ '\u00ee',
+ '\u00ef',
+ '\u064b',
+ '\u064c',
+ '\u064d',
+ '\u064e',
+ '\u00f4',
+ '\u064f',
+ '\u0650',
+ '\u00f7',
+ '\u0651',
+ '\u00f9',
+ '\u0652',
+ '\u00fb',
+ '\u00fc',
+ '\u200e',
+ '\u200f',
+ '\u06d2'
+ };
+
+ private static final String[] LABELS = {
+ "cp1256",
+ "windows-1256",
+ "x-cp1256"
+ };
+
+ private static final String NAME = "windows-1256";
+
+ static final Encoding INSTANCE = new Windows1256();
+
+ private Windows1256() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1257.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1257.java
new file mode 100644
index 000000000..140e9b458
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1257.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Windows1257 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u20ac',
+ '\u0081',
+ '\u201a',
+ '\u0083',
+ '\u201e',
+ '\u2026',
+ '\u2020',
+ '\u2021',
+ '\u0088',
+ '\u2030',
+ '\u008a',
+ '\u2039',
+ '\u008c',
+ '\u00a8',
+ '\u02c7',
+ '\u00b8',
+ '\u0090',
+ '\u2018',
+ '\u2019',
+ '\u201c',
+ '\u201d',
+ '\u2022',
+ '\u2013',
+ '\u2014',
+ '\u0098',
+ '\u2122',
+ '\u009a',
+ '\u203a',
+ '\u009c',
+ '\u00af',
+ '\u02db',
+ '\u009f',
+ '\u00a0',
+ '\ufffd',
+ '\u00a2',
+ '\u00a3',
+ '\u00a4',
+ '\ufffd',
+ '\u00a6',
+ '\u00a7',
+ '\u00d8',
+ '\u00a9',
+ '\u0156',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u00c6',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u00b4',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u00f8',
+ '\u00b9',
+ '\u0157',
+ '\u00bb',
+ '\u00bc',
+ '\u00bd',
+ '\u00be',
+ '\u00e6',
+ '\u0104',
+ '\u012e',
+ '\u0100',
+ '\u0106',
+ '\u00c4',
+ '\u00c5',
+ '\u0118',
+ '\u0112',
+ '\u010c',
+ '\u00c9',
+ '\u0179',
+ '\u0116',
+ '\u0122',
+ '\u0136',
+ '\u012a',
+ '\u013b',
+ '\u0160',
+ '\u0143',
+ '\u0145',
+ '\u00d3',
+ '\u014c',
+ '\u00d5',
+ '\u00d6',
+ '\u00d7',
+ '\u0172',
+ '\u0141',
+ '\u015a',
+ '\u016a',
+ '\u00dc',
+ '\u017b',
+ '\u017d',
+ '\u00df',
+ '\u0105',
+ '\u012f',
+ '\u0101',
+ '\u0107',
+ '\u00e4',
+ '\u00e5',
+ '\u0119',
+ '\u0113',
+ '\u010d',
+ '\u00e9',
+ '\u017a',
+ '\u0117',
+ '\u0123',
+ '\u0137',
+ '\u012b',
+ '\u013c',
+ '\u0161',
+ '\u0144',
+ '\u0146',
+ '\u00f3',
+ '\u014d',
+ '\u00f5',
+ '\u00f6',
+ '\u00f7',
+ '\u0173',
+ '\u0142',
+ '\u015b',
+ '\u016b',
+ '\u00fc',
+ '\u017c',
+ '\u017e',
+ '\u02d9'
+ };
+
+ private static final String[] LABELS = {
+ "cp1257",
+ "windows-1257",
+ "x-cp1257"
+ };
+
+ private static final String NAME = "windows-1257";
+
+ static final Encoding INSTANCE = new Windows1257();
+
+ private Windows1257() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new FallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1258.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1258.java
new file mode 100644
index 000000000..130107789
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows1258.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Windows1258 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u20ac',
+ '\u0081',
+ '\u201a',
+ '\u0192',
+ '\u201e',
+ '\u2026',
+ '\u2020',
+ '\u2021',
+ '\u02c6',
+ '\u2030',
+ '\u008a',
+ '\u2039',
+ '\u0152',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u2018',
+ '\u2019',
+ '\u201c',
+ '\u201d',
+ '\u2022',
+ '\u2013',
+ '\u2014',
+ '\u02dc',
+ '\u2122',
+ '\u009a',
+ '\u203a',
+ '\u0153',
+ '\u009d',
+ '\u009e',
+ '\u0178',
+ '\u00a0',
+ '\u00a1',
+ '\u00a2',
+ '\u00a3',
+ '\u00a4',
+ '\u00a5',
+ '\u00a6',
+ '\u00a7',
+ '\u00a8',
+ '\u00a9',
+ '\u00aa',
+ '\u00ab',
+ '\u00ac',
+ '\u00ad',
+ '\u00ae',
+ '\u00af',
+ '\u00b0',
+ '\u00b1',
+ '\u00b2',
+ '\u00b3',
+ '\u00b4',
+ '\u00b5',
+ '\u00b6',
+ '\u00b7',
+ '\u00b8',
+ '\u00b9',
+ '\u00ba',
+ '\u00bb',
+ '\u00bc',
+ '\u00bd',
+ '\u00be',
+ '\u00bf',
+ '\u00c0',
+ '\u00c1',
+ '\u00c2',
+ '\u0102',
+ '\u00c4',
+ '\u00c5',
+ '\u00c6',
+ '\u00c7',
+ '\u00c8',
+ '\u00c9',
+ '\u00ca',
+ '\u00cb',
+ '\u0300',
+ '\u00cd',
+ '\u00ce',
+ '\u00cf',
+ '\u0110',
+ '\u00d1',
+ '\u0309',
+ '\u00d3',
+ '\u00d4',
+ '\u01a0',
+ '\u00d6',
+ '\u00d7',
+ '\u00d8',
+ '\u00d9',
+ '\u00da',
+ '\u00db',
+ '\u00dc',
+ '\u01af',
+ '\u0303',
+ '\u00df',
+ '\u00e0',
+ '\u00e1',
+ '\u00e2',
+ '\u0103',
+ '\u00e4',
+ '\u00e5',
+ '\u00e6',
+ '\u00e7',
+ '\u00e8',
+ '\u00e9',
+ '\u00ea',
+ '\u00eb',
+ '\u0301',
+ '\u00ed',
+ '\u00ee',
+ '\u00ef',
+ '\u0111',
+ '\u00f1',
+ '\u0323',
+ '\u00f3',
+ '\u00f4',
+ '\u01a1',
+ '\u00f6',
+ '\u00f7',
+ '\u00f8',
+ '\u00f9',
+ '\u00fa',
+ '\u00fb',
+ '\u00fc',
+ '\u01b0',
+ '\u20ab',
+ '\u00ff'
+ };
+
+ private static final String[] LABELS = {
+ "cp1258",
+ "windows-1258",
+ "x-cp1258"
+ };
+
+ private static final String NAME = "windows-1258";
+
+ static final Encoding INSTANCE = new Windows1258();
+
+ private Windows1258() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new InfallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows874.java b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows874.java
new file mode 100644
index 000000000..f93be0175
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/encoding/Windows874.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2013-2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Instead, please regenerate using generate-encoding-data.py
+ */
+
+package nu.validator.encoding;
+
+import java.nio.charset.CharsetDecoder;
+
+class Windows874 extends Encoding {
+
+ private static final char[] TABLE = {
+ '\u20ac',
+ '\u0081',
+ '\u0082',
+ '\u0083',
+ '\u0084',
+ '\u2026',
+ '\u0086',
+ '\u0087',
+ '\u0088',
+ '\u0089',
+ '\u008a',
+ '\u008b',
+ '\u008c',
+ '\u008d',
+ '\u008e',
+ '\u008f',
+ '\u0090',
+ '\u2018',
+ '\u2019',
+ '\u201c',
+ '\u201d',
+ '\u2022',
+ '\u2013',
+ '\u2014',
+ '\u0098',
+ '\u0099',
+ '\u009a',
+ '\u009b',
+ '\u009c',
+ '\u009d',
+ '\u009e',
+ '\u009f',
+ '\u00a0',
+ '\u0e01',
+ '\u0e02',
+ '\u0e03',
+ '\u0e04',
+ '\u0e05',
+ '\u0e06',
+ '\u0e07',
+ '\u0e08',
+ '\u0e09',
+ '\u0e0a',
+ '\u0e0b',
+ '\u0e0c',
+ '\u0e0d',
+ '\u0e0e',
+ '\u0e0f',
+ '\u0e10',
+ '\u0e11',
+ '\u0e12',
+ '\u0e13',
+ '\u0e14',
+ '\u0e15',
+ '\u0e16',
+ '\u0e17',
+ '\u0e18',
+ '\u0e19',
+ '\u0e1a',
+ '\u0e1b',
+ '\u0e1c',
+ '\u0e1d',
+ '\u0e1e',
+ '\u0e1f',
+ '\u0e20',
+ '\u0e21',
+ '\u0e22',
+ '\u0e23',
+ '\u0e24',
+ '\u0e25',
+ '\u0e26',
+ '\u0e27',
+ '\u0e28',
+ '\u0e29',
+ '\u0e2a',
+ '\u0e2b',
+ '\u0e2c',
+ '\u0e2d',
+ '\u0e2e',
+ '\u0e2f',
+ '\u0e30',
+ '\u0e31',
+ '\u0e32',
+ '\u0e33',
+ '\u0e34',
+ '\u0e35',
+ '\u0e36',
+ '\u0e37',
+ '\u0e38',
+ '\u0e39',
+ '\u0e3a',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\u0e3f',
+ '\u0e40',
+ '\u0e41',
+ '\u0e42',
+ '\u0e43',
+ '\u0e44',
+ '\u0e45',
+ '\u0e46',
+ '\u0e47',
+ '\u0e48',
+ '\u0e49',
+ '\u0e4a',
+ '\u0e4b',
+ '\u0e4c',
+ '\u0e4d',
+ '\u0e4e',
+ '\u0e4f',
+ '\u0e50',
+ '\u0e51',
+ '\u0e52',
+ '\u0e53',
+ '\u0e54',
+ '\u0e55',
+ '\u0e56',
+ '\u0e57',
+ '\u0e58',
+ '\u0e59',
+ '\u0e5a',
+ '\u0e5b',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd',
+ '\ufffd'
+ };
+
+ private static final String[] LABELS = {
+ "dos-874",
+ "iso-8859-11",
+ "iso8859-11",
+ "iso885911",
+ "tis-620",
+ "windows-874"
+ };
+
+ private static final String NAME = "windows-874";
+
+ static final Encoding INSTANCE = new Windows874();
+
+ private Windows874() {
+ super(NAME, LABELS);
+ }
+
+ @Override public CharsetDecoder newDecoder() {
+ return new FallibleSingleByteDecoder(this, TABLE);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Auto.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Auto.java
new file mode 100644
index 000000000..0967a5814
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Auto.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+public @interface Auto {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/CharacterName.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/CharacterName.java
new file mode 100644
index 000000000..bcb8a2b00
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/CharacterName.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+public @interface CharacterName {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Const.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Const.java
new file mode 100644
index 000000000..2ba7f418a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Const.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+/**
+ * Marker for translating into the C++ const keyword on the declaration in
+ * question.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public @interface Const {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Creator.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Creator.java
new file mode 100644
index 000000000..0be53cd59
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Creator.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Applied to an integer type to generate the unsigned variant in C++.
+ */
+package nu.validator.htmlparser.annotation;
+
+public @interface Creator {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/HtmlCreator.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/HtmlCreator.java
new file mode 100644
index 000000000..a96bf0f0d
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/HtmlCreator.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Applied to an integer type to generate the unsigned variant in C++.
+ */
+package nu.validator.htmlparser.annotation;
+
+public @interface HtmlCreator {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/IdType.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/IdType.java
new file mode 100644
index 000000000..117da8d3c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/IdType.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+/**
+ * The type for attribute IDness. (In Java, an interned string
+ * <code>"CDATA"</code> or <code>"ID"</code>.)
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public @interface IdType {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Inline.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Inline.java
new file mode 100644
index 000000000..cc0728b1b
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Inline.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2009-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+/**
+ * Translates into the C++ inline keyword.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public @interface Inline {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Literal.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Literal.java
new file mode 100644
index 000000000..44444d525
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Literal.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2009-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+/**
+ * Marks a string type as being the literal string type (typically const char*)
+ * in C++.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public @interface Literal {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Local.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Local.java
new file mode 100644
index 000000000..1f91ba93b
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Local.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+/**
+ * The local name of an element or attribute. Must be comparable with
+ * <code>==</code> (interned <code>String</code> in Java).
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public @interface Local {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/NoLength.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/NoLength.java
new file mode 100644
index 000000000..cf011d33e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/NoLength.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+/**
+ * The array type marked with this annotation won't have its
+ * <code>.length</code> read.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public @interface NoLength {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/NsUri.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/NsUri.java
new file mode 100644
index 000000000..03baa75f5
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/NsUri.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+/**
+ * The namespace URI type. (In Java, an interned <code>String</code>.)
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public @interface NsUri {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Prefix.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Prefix.java
new file mode 100644
index 000000000..268e531a3
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Prefix.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+/**
+ * The type for namespace prefixes. (In Java, an interned <code>String</code>.)
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public @interface Prefix {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/QName.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/QName.java
new file mode 100644
index 000000000..e6d4807b6
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/QName.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+/**
+ * The type for qualified names. (In Java, an interned <code>String</code>.)
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public @interface QName {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/SvgCreator.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/SvgCreator.java
new file mode 100644
index 000000000..f317b09f1
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/SvgCreator.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Applied to an integer type to generate the unsigned variant in C++.
+ */
+package nu.validator.htmlparser.annotation;
+
+public @interface SvgCreator {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Unsigned.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Unsigned.java
new file mode 100644
index 000000000..53606572a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Unsigned.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * Applied to an integer type to generate the unsigned variant in C++.
+ */
+package nu.validator.htmlparser.annotation;
+
+public @interface Unsigned {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Virtual.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Virtual.java
new file mode 100644
index 000000000..e293e1af5
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/Virtual.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.annotation;
+
+/**
+ * Marks a method as virtualy in C++.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public @interface Virtual {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/package.html b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/package.html
new file mode 100644
index 000000000..af15d3827
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/annotation/package.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head><title>Package Overview</title>
+<!--
+ Copyright (c) 2008 Mozilla Foundation
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+-->
+</head>
+<body bgcolor="white">
+<p>This package provides annotations for facilitating automated translation
+of the source code into other programming languages.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/ByteReadable.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/ByteReadable.java
new file mode 100644
index 000000000..f3b3e74ca
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/ByteReadable.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+import java.io.IOException;
+
+/**
+ * An interface for providing a method for reading a stream of bytes one byte at
+ * a time.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public interface ByteReadable {
+ /**
+ * Returns the value of the next byte as an integer from 0 to 0xFF or -1 if
+ * the stream has ended.
+ *
+ * @return integer from 0 to 0xFF or -1 on EOF
+ * @throws IOException
+ */
+ public int readByte() throws IOException;
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/CharacterHandler.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/CharacterHandler.java
new file mode 100644
index 000000000..4a5769f54
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/CharacterHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2007-2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+import org.xml.sax.SAXException;
+
+/**
+ * An interface for receiving notifications of UTF-16 code units read from a character stream.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public interface CharacterHandler {
+
+ /**
+ * Receive notification of a run of UTF-16 code units.
+ * @param ch the buffer
+ * @param start start index in the buffer
+ * @param length the number of characters to process starting from <code>start</code>
+ * @throws SAXException if things go wrong
+ */
+ public void characters(char[] ch, int start, int length)
+ throws SAXException;
+
+ /**
+ * Signals the end of the stream. Can be used for cleanup. Doesn't mean that the stream ended successfully.
+ *
+ * @throws SAXException if things go wrong
+ */
+ public void end() throws SAXException;
+
+ /**
+ * Signals the start of the stream. Can be used for setup.
+ *
+ * @throws SAXException if things go wrong
+ */
+ public void start() throws SAXException;
+
+} \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DoctypeExpectation.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DoctypeExpectation.java
new file mode 100644
index 000000000..a34af51fa
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DoctypeExpectation.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+/**
+ * Used for indicating desired behavior with legacy doctypes.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public enum DoctypeExpectation {
+ /**
+ * Be a pure HTML5 parser.
+ */
+ HTML,
+
+ /**
+ * Require the HTML 4.01 Transitional public id. Turn on HTML4-specific
+ * additional errors regardless of doctype.
+ */
+ HTML401_TRANSITIONAL,
+
+ /**
+ * Require the HTML 4.01 Transitional public id and a system id. Turn on
+ * HTML4-specific additional errors regardless of doctype.
+ */
+ HTML401_STRICT,
+
+ /**
+ * Treat the doctype required by HTML 5, doctypes with the HTML 4.01 Strict
+ * public id and doctypes with the HTML 4.01 Transitional public id and a
+ * system id as non-errors. Turn on HTML4-specific additional errors if the
+ * public id is the HTML 4.01 Strict or Transitional public id.
+ */
+ AUTO,
+
+ /**
+ * Never enable HTML4-specific error checks. Never report any doctype
+ * condition as an error. (Doctype tokens in wrong places will be
+ * reported as errors, though.) The application may decide what to log
+ * in response to calls to <code>DocumentModeHanler</code>. This mode
+ * in meant for doing surveys on existing content.
+ */
+ NO_DOCTYPE_ERRORS
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DocumentMode.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DocumentMode.java
new file mode 100644
index 000000000..e30eddd87
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DocumentMode.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+/**
+ * Represents the HTML document compatibility mode.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public enum DocumentMode {
+ /**
+ * The Standards Mode
+ */
+ STANDARDS_MODE,
+
+ /**
+ * The Limited Quirks Mode aka. The Almost Standards Mode
+ */
+ ALMOST_STANDARDS_MODE,
+
+ /**
+ * The Quirks Mode
+ */
+ QUIRKS_MODE
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DocumentModeHandler.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DocumentModeHandler.java
new file mode 100644
index 000000000..55377e0e4
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/DocumentModeHandler.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+
+import org.xml.sax.SAXException;
+
+/**
+ * A callback interface for receiving notification about the document mode.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public interface DocumentModeHandler {
+
+ /**
+ * Receive notification of the document mode.
+ *
+ * @param mode the document mode
+ * @param publicIdentifier the public id of the doctype or <code>null</code> if unavailable
+ * @param systemIdentifier the system id of the doctype or <code>null</code> if unavailable
+ * @param html4SpecificAdditionalErrorChecks <code>true</code> if HTML 4-specific checks were enabled, <code>false</code> otherwise
+ * @throws SAXException if things go wrong
+ */
+ public void documentMode(DocumentMode mode, String publicIdentifier, String systemIdentifier, boolean html4SpecificAdditionalErrorChecks) throws SAXException;
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/EncodingDeclarationHandler.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/EncodingDeclarationHandler.java
new file mode 100644
index 000000000..6f185aeaf
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/EncodingDeclarationHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+import org.xml.sax.SAXException;
+
+/**
+ * An interface for communicating about character encoding names with the
+ * environment of the parser.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public interface EncodingDeclarationHandler {
+
+ /**
+ * Indicates that the parser has found an internal encoding declaration with
+ * the charset value <code>charset</code>.
+ *
+ * @param charset
+ * the charset name found.
+ * @return <code>true</code> if the value of <code>charset</code> was an
+ * encoding name for a supported ASCII-superset encoding.
+ * @throws SAXException
+ * if something went wrong
+ */
+ public boolean internalEncodingDeclaration(String charset) throws SAXException;
+
+ /**
+ * Queries the environment for the encoding in use (for error reporting).
+ *
+ * @return the encoding in use
+ * @throws SAXException
+ * if something went wrong
+ */
+ public String getCharacterEncoding() throws SAXException;
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/Heuristics.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/Heuristics.java
new file mode 100644
index 000000000..40f15ce7d
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/Heuristics.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+/**
+ * Indicates a request for character encoding sniffer choice.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public enum Heuristics {
+
+ /**
+ * Perform no heuristic sniffing.
+ */
+ NONE,
+
+ /**
+ * Use both jchardet and ICU4J.
+ */
+ ALL,
+
+ /**
+ * Use jchardet only.
+ */
+ CHARDET,
+
+ /**
+ * Use ICU4J only.
+ */
+ ICU
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/Interner.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/Interner.java
new file mode 100644
index 000000000..deab4c60f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/Interner.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2009-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+/**
+ * A placeholder type that translates into the type of the C++ class that
+ * implements an interning service for local names (<code>@Local</code> in
+ * Java).
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public interface Interner {
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/TokenHandler.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/TokenHandler.java
new file mode 100644
index 000000000..18f49e99d
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/TokenHandler.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+import nu.validator.htmlparser.annotation.Const;
+import nu.validator.htmlparser.annotation.NoLength;
+import nu.validator.htmlparser.impl.ElementName;
+import nu.validator.htmlparser.impl.HtmlAttributes;
+import nu.validator.htmlparser.impl.Tokenizer;
+
+import org.xml.sax.SAXException;
+
+/**
+ * <code>Tokenizer</code> reports tokens through this interface.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public interface TokenHandler {
+
+ /**
+ * This method is called at the start of tokenization before any other
+ * methods on this interface are called. Implementations should hold the
+ * reference to the <code>Tokenizer</code> in order to set the content
+ * model flag and in order to be able to query for <code>Locator</code>
+ * data.
+ *
+ * @param self
+ * the <code>Tokenizer</code>.
+ * @throws SAXException
+ * if something went wrong
+ */
+ public void startTokenization(Tokenizer self) throws SAXException;
+
+ /**
+ * If this handler implementation cares about comments, return
+ * <code>true</code>. If not, return <code>false</code>.
+ *
+ * @return whether this handler wants comments
+ * @throws SAXException
+ * if something went wrong
+ */
+ public boolean wantsComments() throws SAXException;
+
+ /**
+ * Receive a doctype token.
+ *
+ * @param name
+ * the name
+ * @param publicIdentifier
+ * the public id
+ * @param systemIdentifier
+ * the system id
+ * @param forceQuirks
+ * whether the token is correct
+ * @throws SAXException
+ * if something went wrong
+ */
+ public void doctype(String name, String publicIdentifier,
+ String systemIdentifier, boolean forceQuirks) throws SAXException;
+
+ /**
+ * Receive a start tag token.
+ *
+ * @param eltName
+ * the tag name
+ * @param attributes
+ * the attributes
+ * @param selfClosing
+ * TODO
+ * @throws SAXException
+ * if something went wrong
+ */
+ public void startTag(ElementName eltName, HtmlAttributes attributes,
+ boolean selfClosing) throws SAXException;
+
+ /**
+ * Receive an end tag token.
+ *
+ * @param eltName
+ * the tag name
+ * @throws SAXException
+ * if something went wrong
+ */
+ public void endTag(ElementName eltName) throws SAXException;
+
+ /**
+ * Receive a comment token. The data is junk if the
+ * <code>wantsComments()</code> returned <code>false</code>.
+ *
+ * @param buf
+ * a buffer holding the data
+ * @param start the offset into the buffer
+ * @param length
+ * the number of code units to read
+ * @throws SAXException
+ * if something went wrong
+ */
+ public void comment(@NoLength char[] buf, int start, int length) throws SAXException;
+
+ /**
+ * Receive character tokens. This method has the same semantics as the SAX
+ * method of the same name.
+ *
+ * @param buf
+ * a buffer holding the data
+ * @param start
+ * offset into the buffer
+ * @param length
+ * the number of code units to read
+ * @throws SAXException
+ * if something went wrong
+ * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+ */
+ public void characters(@Const @NoLength char[] buf, int start, int length)
+ throws SAXException;
+
+ /**
+ * Reports a U+0000 that's being turned into a U+FFFD.
+ *
+ * @throws SAXException
+ * if something went wrong
+ */
+ public void zeroOriginatingReplacementCharacter() throws SAXException;
+
+ /**
+ * The end-of-file token.
+ *
+ * @throws SAXException
+ * if something went wrong
+ */
+ public void eof() throws SAXException;
+
+ /**
+ * The perform final cleanup.
+ *
+ * @throws SAXException
+ * if something went wrong
+ */
+ public void endTokenization() throws SAXException;
+
+ /**
+ * Checks if the CDATA sections are allowed.
+ *
+ * @return <code>true</code> if CDATA sections are allowed
+ * @throws SAXException
+ * if something went wrong
+ */
+ public boolean cdataSectionAllowed() throws SAXException;
+
+ /**
+ * Notifies the token handler of the worst case amount of data to be
+ * reported via <code>characters()</code> and
+ * <code>zeroOriginatingReplacementCharacter()</code>.
+ *
+ * @param inputLength the maximum number of chars that can be reported
+ * via <code>characters()</code> and
+ * <code>zeroOriginatingReplacementCharacter()</code> before a new call to
+ * this method.
+ */
+ public void ensureBufferSpace(int inputLength) throws SAXException;
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/TransitionHandler.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/TransitionHandler.java
new file mode 100644
index 000000000..eec23c71c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/TransitionHandler.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+import org.xml.sax.SAXException;
+
+/**
+ * An interface for intercepting information about the state transitions that
+ * the tokenizer is making.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public interface TransitionHandler {
+
+ /**
+ * This method is called for every tokenizer state transition.
+ *
+ * @param from
+ * the state the tokenizer is transitioning from
+ * @param to
+ * the state being transitioned to
+ * @param reconsume
+ * <code>true</code> if the current input character is going to
+ * be reconsumed in the new state
+ * @param pos
+ * the current index into the input stream
+ * @throws SAXException
+ * if something went wrong
+ */
+ void transition(int from, int to, boolean reconsume, int pos)
+ throws SAXException;
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/XmlViolationPolicy.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/XmlViolationPolicy.java
new file mode 100644
index 000000000..c959df655
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/XmlViolationPolicy.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.common;
+
+/**
+ * Policy for XML 1.0 violations.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public enum XmlViolationPolicy {
+ /**
+ * Conform to HTML 5, allow XML 1.0 to be violated.
+ */
+ ALLOW,
+
+ /**
+ * Halt when something cannot be mapped to XML 1.0.
+ */
+ FATAL,
+
+ /**
+ * Be non-conforming and alter the infoset to fit
+ * XML 1.0 when something would otherwise not be
+ * mappable to XML 1.0.
+ */
+ ALTER_INFOSET
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/package.html b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/package.html
new file mode 100644
index 000000000..43f141cd8
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/common/package.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head><title>Package Overview</title>
+<!--
+ Copyright (c) 2007 Henri Sivonen
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+-->
+</head>
+<body bgcolor="white">
+<p>This package provides common interfaces and enumerations.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/DOMTreeBuilder.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/DOMTreeBuilder.java
new file mode 100644
index 000000000..2b8eff230
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/DOMTreeBuilder.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.dom;
+
+import nu.validator.htmlparser.common.DocumentMode;
+import nu.validator.htmlparser.impl.CoalescingTreeBuilder;
+import nu.validator.htmlparser.impl.HtmlAttributes;
+
+import org.w3c.dom.DOMException;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+import org.xml.sax.SAXException;
+
+/**
+ * The tree builder glue for building a tree through the public DOM APIs.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+class DOMTreeBuilder extends CoalescingTreeBuilder<Element> {
+
+ /**
+ * The DOM impl.
+ */
+ private DOMImplementation implementation;
+
+ /**
+ * The current doc.
+ */
+ private Document document;
+
+ /**
+ * The constructor.
+ *
+ * @param implementation
+ * the DOM impl.
+ */
+ protected DOMTreeBuilder(DOMImplementation implementation) {
+ super();
+ this.implementation = implementation;
+ }
+
+ /**
+ *
+ * @see nu.validator.htmlparser.impl.TreeBuilder#addAttributesToElement(java.lang.Object,
+ * nu.validator.htmlparser.impl.HtmlAttributes)
+ */
+ @Override protected void addAttributesToElement(Element element,
+ HtmlAttributes attributes) throws SAXException {
+ try {
+ for (int i = 0; i < attributes.getLength(); i++) {
+ String localName = attributes.getLocalNameNoBoundsCheck(i);
+ String uri = attributes.getURINoBoundsCheck(i);
+ if (!element.hasAttributeNS(uri, localName)) {
+ element.setAttributeNS(uri, localName,
+ attributes.getValueNoBoundsCheck(i));
+ }
+ }
+ } catch (DOMException e) {
+ fatal(e);
+ }
+ }
+
+ /**
+ *
+ * @see nu.validator.htmlparser.impl.CoalescingTreeBuilder#appendCharacters(java.lang.Object,
+ * java.lang.String)
+ */
+ @Override protected void appendCharacters(Element parent, String text)
+ throws SAXException {
+ try {
+ Node lastChild = parent.getLastChild();
+ if (lastChild != null && lastChild.getNodeType() == Node.TEXT_NODE) {
+ Text lastAsText = (Text) lastChild;
+ lastAsText.setData(lastAsText.getData() + text);
+ return;
+ }
+ parent.appendChild(document.createTextNode(text));
+ } catch (DOMException e) {
+ fatal(e);
+ }
+ }
+
+ /**
+ *
+ * @see nu.validator.htmlparser.impl.TreeBuilder#appendChildrenToNewParent(java.lang.Object,
+ * java.lang.Object)
+ */
+ @Override protected void appendChildrenToNewParent(Element oldParent,
+ Element newParent) throws SAXException {
+ try {
+ while (oldParent.hasChildNodes()) {
+ newParent.appendChild(oldParent.getFirstChild());
+ }
+ } catch (DOMException e) {
+ fatal(e);
+ }
+ }
+
+ /**
+ *
+ * @see nu.validator.htmlparser.impl.CoalescingTreeBuilder#appendComment(java.lang.Object,
+ * java.lang.String)
+ */
+ @Override protected void appendComment(Element parent, String comment)
+ throws SAXException {
+ try {
+ parent.appendChild(document.createComment(comment));
+ } catch (DOMException e) {
+ fatal(e);
+ }
+ }
+
+ /**
+ *
+ * @see nu.validator.htmlparser.impl.CoalescingTreeBuilder#appendCommentToDocument(java.lang.String)
+ */
+ @Override protected void appendCommentToDocument(String comment)
+ throws SAXException {
+ try {
+ document.appendChild(document.createComment(comment));
+ } catch (DOMException e) {
+ fatal(e);
+ }
+ }
+
+ /**
+ *
+ * @see nu.validator.htmlparser.impl.TreeBuilder#createElement(String, String, nu.validator.htmlparser.impl.HtmlAttributes, Object)
+ */
+ @Override protected Element createElement(String ns, String name,
+ HtmlAttributes attributes, Element intendedParent) throws SAXException {
+ try {
+ Element rv = document.createElementNS(ns, name);
+ for (int i = 0; i < attributes.getLength(); i++) {
+ rv.setAttributeNS(attributes.getURINoBoundsCheck(i),
+ attributes.getLocalNameNoBoundsCheck(i),
+ attributes.getValueNoBoundsCheck(i));
+ if (attributes.getTypeNoBoundsCheck(i) == "ID") {
+ rv.setIdAttributeNS(null, attributes.getLocalName(i), true);
+ }
+ }
+ return rv;
+ } catch (DOMException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ /**
+ *
+ * @see nu.validator.htmlparser.impl.TreeBuilder#createHtmlElementSetAsRoot(nu.validator.htmlparser.impl.HtmlAttributes)
+ */
+ @Override protected Element createHtmlElementSetAsRoot(
+ HtmlAttributes attributes) throws SAXException {
+ try {
+ Element rv = document.createElementNS(
+ "http://www.w3.org/1999/xhtml", "html");
+ for (int i = 0; i < attributes.getLength(); i++) {
+ rv.setAttributeNS(attributes.getURINoBoundsCheck(i),
+ attributes.getLocalNameNoBoundsCheck(i),
+ attributes.getValueNoBoundsCheck(i));
+ }
+ document.appendChild(rv);
+ return rv;
+ } catch (DOMException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ /**
+ *
+ * @see nu.validator.htmlparser.impl.TreeBuilder#appendElement(java.lang.Object,
+ * java.lang.Object)
+ */
+ @Override protected void appendElement(Element child, Element newParent)
+ throws SAXException {
+ try {
+ newParent.appendChild(child);
+ } catch (DOMException e) {
+ fatal(e);
+ }
+ }
+
+ /**
+ *
+ * @see nu.validator.htmlparser.impl.TreeBuilder#hasChildren(java.lang.Object)
+ */
+ @Override protected boolean hasChildren(Element element)
+ throws SAXException {
+ try {
+ return element.hasChildNodes();
+ } catch (DOMException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#createElement(String,
+ * java.lang.String, org.xml.sax.Attributes, java.lang.Object)
+ */
+ @Override protected Element createElement(String ns, String name,
+ HtmlAttributes attributes, Element form, Element intendedParent) throws SAXException {
+ try {
+ Element rv = createElement(ns, name, attributes, intendedParent);
+ rv.setUserData("nu.validator.form-pointer", form, null);
+ return rv;
+ } catch (DOMException e) {
+ fatal(e);
+ return null;
+ }
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#start()
+ */
+ @Override protected void start(boolean fragment) throws SAXException {
+ document = implementation.createDocument(null, null, null);
+ }
+
+ /**
+ *
+ * @see nu.validator.htmlparser.impl.TreeBuilder#documentMode(nu.validator.htmlparser.common.DocumentMode,
+ * java.lang.String, java.lang.String, boolean)
+ */
+ protected void documentMode(DocumentMode mode, String publicIdentifier,
+ String systemIdentifier, boolean html4SpecificAdditionalErrorChecks)
+ throws SAXException {
+ document.setUserData("nu.validator.document-mode", mode, null);
+ }
+
+ /**
+ * Returns the document.
+ *
+ * @return the document
+ */
+ Document getDocument() {
+ Document rv = document;
+ document = null;
+ return rv;
+ }
+
+ /**
+ * Return the document fragment.
+ *
+ * @return the document fragment
+ */
+ DocumentFragment getDocumentFragment() {
+ DocumentFragment rv = document.createDocumentFragment();
+ Node rootElt = document.getFirstChild();
+ while (rootElt.hasChildNodes()) {
+ rv.appendChild(rootElt.getFirstChild());
+ }
+ document = null;
+ return rv;
+ }
+
+ @Override
+ protected Element createAndInsertFosterParentedElement(String ns, String name,
+ HtmlAttributes attributes, Element table, Element stackParent) throws SAXException {
+ try {
+ Node parent = table.getParentNode();
+ Element child = createElement(ns, name, attributes, parent != null ? (Element) parent : stackParent);
+
+ if (parent != null) { // always an element if not null
+ parent.insertBefore(child, table);
+ } else {
+ stackParent.appendChild(child);
+ }
+
+ return child;
+ } catch (DOMException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ @Override protected void insertFosterParentedCharacters(String text,
+ Element table, Element stackParent) throws SAXException {
+ try {
+ Node parent = table.getParentNode();
+ if (parent != null) { // always an element if not null
+ Node previousSibling = table.getPreviousSibling();
+ if (previousSibling != null
+ && previousSibling.getNodeType() == Node.TEXT_NODE) {
+ Text lastAsText = (Text) previousSibling;
+ lastAsText.setData(lastAsText.getData() + text);
+ return;
+ }
+ parent.insertBefore(document.createTextNode(text), table);
+ return;
+ }
+ Node lastChild = stackParent.getLastChild();
+ if (lastChild != null && lastChild.getNodeType() == Node.TEXT_NODE) {
+ Text lastAsText = (Text) lastChild;
+ lastAsText.setData(lastAsText.getData() + text);
+ return;
+ }
+ stackParent.appendChild(document.createTextNode(text));
+ } catch (DOMException e) {
+ fatal(e);
+ }
+ }
+
+ @Override protected void insertFosterParentedChild(Element child,
+ Element table, Element stackParent) throws SAXException {
+ try {
+ Node parent = table.getParentNode();
+ if (parent != null) { // always an element if not null
+ parent.insertBefore(child, table);
+ } else {
+ stackParent.appendChild(child);
+ }
+ } catch (DOMException e) {
+ fatal(e);
+ }
+ }
+
+ @Override protected void detachFromParent(Element element)
+ throws SAXException {
+ try {
+ Node parent = element.getParentNode();
+ if (parent != null) {
+ parent.removeChild(element);
+ }
+ } catch (DOMException e) {
+ fatal(e);
+ }
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/Dom2Sax.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/Dom2Sax.java
new file mode 100644
index 000000000..5e366be7b
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/Dom2Sax.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.dom;
+
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+public class Dom2Sax {
+
+ private static String emptyIfNull(String namespaceURI) {
+ return namespaceURI == null ? "" : namespaceURI;
+ }
+
+ private final NamedNodeMapAttributes attributes = new NamedNodeMapAttributes();
+
+ private final ContentHandler contentHandler;
+
+ private final LexicalHandler lexicalHandler;
+
+ /**
+ * @param contentHandler
+ * @param lexicalHandler
+ */
+ public Dom2Sax(ContentHandler contentHandler, LexicalHandler lexicalHandler) {
+ if (contentHandler == null) {
+ throw new IllegalArgumentException("ContentHandler must not be null.");
+ }
+ this.contentHandler = contentHandler;
+ this.lexicalHandler = lexicalHandler;
+ }
+
+ public void parse(Node node) throws SAXException {
+ Node current = node;
+ Node next;
+ char[] buf;
+ for (;;) {
+ switch (current.getNodeType()) {
+ case Node.ELEMENT_NODE:
+ attributes.setNamedNodeMap(current.getAttributes());
+ // To work around severe bogosity in the default DOM
+ // impl, use the node name if local name is null.
+ String localName = current.getLocalName();
+ contentHandler.startElement(
+ emptyIfNull(current.getNamespaceURI()),
+ localName == null ? current.getNodeName()
+ : localName, null, attributes);
+ attributes.clear();
+ break;
+ case Node.TEXT_NODE:
+ buf = current.getNodeValue().toCharArray();
+ contentHandler.characters(buf, 0, buf.length);
+ break;
+ case Node.CDATA_SECTION_NODE:
+ if (lexicalHandler != null) {
+ lexicalHandler.startCDATA();
+ }
+ buf = current.getNodeValue().toCharArray();
+ contentHandler.characters(buf, 0, buf.length);
+ if (lexicalHandler != null) {
+ lexicalHandler.endCDATA();
+ }
+ break;
+ case Node.COMMENT_NODE:
+ if (lexicalHandler != null) {
+ buf = current.getNodeValue().toCharArray();
+ lexicalHandler.comment(buf, 0, buf.length);
+ }
+ break;
+ case Node.DOCUMENT_NODE:
+ contentHandler.startDocument();
+ break;
+ case Node.DOCUMENT_TYPE_NODE:
+ if (lexicalHandler != null) {
+ DocumentType doctype = (DocumentType) current;
+ lexicalHandler.startDTD(doctype.getName(),
+ doctype.getPublicId(), doctype.getSystemId());
+ lexicalHandler.endDTD();
+ }
+ break;
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ contentHandler.processingInstruction(current.getNodeName(), current.getNodeValue());
+ break;
+ case Node.ENTITY_REFERENCE_NODE:
+ contentHandler.skippedEntity(current.getNodeName());
+ break;
+ }
+ if ((next = current.getFirstChild()) != null) {
+ current = next;
+ continue;
+ }
+ for (;;) {
+ switch (current.getNodeType()) {
+ case Node.ELEMENT_NODE:
+ // To work around severe bogosity in the default DOM
+ // impl, use the node name if local name is null.
+ String localName = current.getLocalName();
+ contentHandler.endElement(
+ emptyIfNull(current.getNamespaceURI()),
+ localName == null ? current.getNodeName()
+ : localName, null);
+ break;
+ case Node.DOCUMENT_NODE:
+ contentHandler.endDocument();
+ break;
+ }
+ if (current == node) {
+ return;
+ }
+ if ((next = current.getNextSibling()) != null) {
+ current = next;
+ break;
+ }
+ current = current.getParentNode();
+ }
+ }
+ }
+
+ private class NamedNodeMapAttributes implements Attributes {
+
+ private NamedNodeMap map;
+
+ private int length;
+
+ public void setNamedNodeMap(NamedNodeMap attributes) {
+ this.map = attributes;
+ this.length = attributes.getLength();
+ }
+
+ public void clear() {
+ this.map = null;
+ }
+
+ public int getIndex(String qName) {
+ for (int i = 0; i < length; i++) {
+ Node n = map.item(i);
+ if (n.getNodeName().equals(qName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int getIndex(String uri, String localName) {
+ for (int i = 0; i < length; i++) {
+ Node n = map.item(i);
+ if (n.getLocalName().equals(localName) && emptyIfNull(n.getNamespaceURI()).equals(uri)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public String getLocalName(int index) {
+ if (index < length && index >= 0) {
+ return map.item(index).getLocalName();
+ } else {
+ return null;
+ }
+ }
+
+ public String getQName(int index) {
+ if (index < length && index >= 0) {
+ return map.item(index).getNodeName();
+ } else {
+ return null;
+ }
+ }
+
+ public String getType(int index) {
+ if (index < length && index >= 0) {
+ return "id".equals(map.item(index).getLocalName()) ? "ID" : "CDATA";
+ } else {
+ return null;
+ }
+ }
+
+ public String getType(String qName) {
+ int index = getIndex(qName);
+ if (index == -1) {
+ return null;
+ } else {
+ return getType(index);
+ }
+ }
+
+ public String getType(String uri, String localName) {
+ int index = getIndex(uri, localName);
+ if (index == -1) {
+ return null;
+ } else {
+ return getType(index);
+ }
+ }
+
+ public String getURI(int index) {
+ if (index < length && index >= 0) {
+ return emptyIfNull(map.item(index).getNamespaceURI());
+ } else {
+ return null;
+ }
+ }
+
+ public String getValue(int index) {
+ if (index < length && index >= 0) {
+ return map.item(index).getNodeValue();
+ } else {
+ return null;
+ }
+ }
+
+ public String getValue(String qName) {
+ int index = getIndex(qName);
+ if (index == -1) {
+ return null;
+ } else {
+ return getValue(index);
+ }
+ }
+
+ public String getValue(String uri, String localName) {
+ int index = getIndex(uri, localName);
+ if (index == -1) {
+ return null;
+ } else {
+ return getValue(index);
+ }
+ }
+
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/HtmlDocumentBuilder.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/HtmlDocumentBuilder.java
new file mode 100644
index 000000000..f4a307c9f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/HtmlDocumentBuilder.java
@@ -0,0 +1,736 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.dom;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import nu.validator.htmlparser.common.CharacterHandler;
+import nu.validator.htmlparser.common.DoctypeExpectation;
+import nu.validator.htmlparser.common.DocumentModeHandler;
+import nu.validator.htmlparser.common.Heuristics;
+import nu.validator.htmlparser.common.TokenHandler;
+import nu.validator.htmlparser.common.TransitionHandler;
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.impl.ErrorReportingTokenizer;
+import nu.validator.htmlparser.impl.Tokenizer;
+import nu.validator.htmlparser.io.Driver;
+
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * This class implements an HTML5 parser that exposes data through the DOM
+ * interface.
+ *
+ * <p>By default, when using the constructor without arguments, the
+ * this parser coerces XML 1.0-incompatible infosets into XML 1.0-compatible
+ * infosets. This corresponds to <code>ALTER_INFOSET</code> as the general
+ * XML violation policy. To make the parser support non-conforming HTML fully
+ * per the HTML 5 spec while on the other hand potentially violating the SAX2
+ * API contract, set the general XML violation policy to <code>ALLOW</code>.
+ * This does not work with a standard DOM implementation.
+ * It is possible to treat XML 1.0 infoset violations as fatal by setting
+ * the general XML violation policy to <code>FATAL</code>.
+ *
+ * <p>The doctype is not represented in the tree.
+ *
+ * <p>The document mode is represented as user data <code>DocumentMode</code>
+ * object with the key <code>nu.validator.document-mode</code> on the document
+ * node.
+ *
+ * <p>The form pointer is also stored as user data with the key
+ * <code>nu.validator.form-pointer</code>.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public class HtmlDocumentBuilder extends DocumentBuilder {
+
+ /**
+ * Returns the JAXP DOM implementation.
+ *
+ * @return the JAXP DOM implementation
+ */
+ private static DOMImplementation jaxpDOMImplementation() {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ DocumentBuilder builder;
+ try {
+ builder = factory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ return builder.getDOMImplementation();
+ }
+
+ /**
+ * The tokenizer.
+ */
+ private Driver driver;
+
+ /**
+ * The tree builder.
+ */
+ private final DOMTreeBuilder treeBuilder;
+
+ /**
+ * The DOM impl.
+ */
+ private final DOMImplementation implementation;
+
+ /**
+ * The entity resolver.
+ */
+ private EntityResolver entityResolver;
+
+ private ErrorHandler errorHandler = null;
+
+ private DocumentModeHandler documentModeHandler = null;
+
+ private DoctypeExpectation doctypeExpectation = DoctypeExpectation.HTML;
+
+ private boolean checkingNormalization = false;
+
+ private boolean scriptingEnabled = false;
+
+ private final List<CharacterHandler> characterHandlers = new LinkedList<CharacterHandler>();
+
+ private XmlViolationPolicy contentSpacePolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy contentNonXmlCharPolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy commentPolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy namePolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy streamabilityViolationPolicy = XmlViolationPolicy.ALLOW;
+
+ private boolean html4ModeCompatibleWithXhtml1Schemata = false;
+
+ private boolean mappingLangToXmlLang = false;
+
+ private XmlViolationPolicy xmlnsPolicy = XmlViolationPolicy.FATAL;
+
+ private boolean reportingDoctype = true;
+
+ private ErrorHandler treeBuilderErrorHandler = null;
+
+ private Heuristics heuristics = Heuristics.NONE;
+
+ private TransitionHandler transitionHandler = null;
+
+ /**
+ * Instantiates the document builder with a specific DOM
+ * implementation and XML violation policy.
+ *
+ * @param implementation
+ * the DOM implementation
+ * @param xmlPolicy the policy
+ */
+ public HtmlDocumentBuilder(DOMImplementation implementation,
+ XmlViolationPolicy xmlPolicy) {
+ this.implementation = implementation;
+ this.treeBuilder = new DOMTreeBuilder(implementation);
+ this.driver = null;
+ setXmlPolicy(xmlPolicy);
+ }
+
+ /**
+ * Instantiates the document builder with a specific DOM implementation
+ * and the infoset-altering XML violation policy.
+ *
+ * @param implementation
+ * the DOM implementation
+ */
+ public HtmlDocumentBuilder(DOMImplementation implementation) {
+ this(implementation, XmlViolationPolicy.ALTER_INFOSET);
+ }
+
+ /**
+ * Instantiates the document builder with the JAXP DOM implementation
+ * and the infoset-altering XML violation policy.
+ */
+ public HtmlDocumentBuilder() {
+ this(XmlViolationPolicy.ALTER_INFOSET);
+ }
+
+ /**
+ * Instantiates the document builder with the JAXP DOM implementation
+ * and a specific XML violation policy.
+ * @param xmlPolicy the policy
+ */
+ public HtmlDocumentBuilder(XmlViolationPolicy xmlPolicy) {
+ this(jaxpDOMImplementation(), xmlPolicy);
+ }
+
+
+ private Tokenizer newTokenizer(TokenHandler handler,
+ boolean newAttributesEachTime) {
+ if (errorHandler == null && transitionHandler == null
+ && contentNonXmlCharPolicy == XmlViolationPolicy.ALLOW) {
+ return new Tokenizer(handler, newAttributesEachTime);
+ } else {
+ return new ErrorReportingTokenizer(handler, newAttributesEachTime);
+ }
+ }
+
+ /**
+ * This class wraps different tree builders depending on configuration. This
+ * method does the work of hiding this from the user of the class.
+ */
+ private void lazyInit() {
+ if (driver == null) {
+ this.driver = new Driver(newTokenizer(treeBuilder, false));
+ this.driver.setErrorHandler(errorHandler);
+ this.driver.setTransitionHandler(transitionHandler);
+ this.treeBuilder.setErrorHandler(treeBuilderErrorHandler);
+ this.driver.setCheckingNormalization(checkingNormalization);
+ this.driver.setCommentPolicy(commentPolicy);
+ this.driver.setContentNonXmlCharPolicy(contentNonXmlCharPolicy);
+ this.driver.setContentSpacePolicy(contentSpacePolicy);
+ this.driver.setHtml4ModeCompatibleWithXhtml1Schemata(html4ModeCompatibleWithXhtml1Schemata);
+ this.driver.setMappingLangToXmlLang(mappingLangToXmlLang);
+ this.driver.setXmlnsPolicy(xmlnsPolicy);
+ this.driver.setHeuristics(heuristics);
+ for (CharacterHandler characterHandler : characterHandlers) {
+ this.driver.addCharacterHandler(characterHandler);
+ }
+ this.treeBuilder.setDoctypeExpectation(doctypeExpectation);
+ this.treeBuilder.setDocumentModeHandler(documentModeHandler);
+ this.treeBuilder.setScriptingEnabled(scriptingEnabled);
+ this.treeBuilder.setReportingDoctype(reportingDoctype);
+ this.treeBuilder.setNamePolicy(namePolicy);
+ }
+ }
+
+ /**
+ * Tokenizes the input source.
+ *
+ * @param is the source
+ * @throws SAXException if stuff goes wrong
+ * @throws IOException if IO goes wrong
+ * @throws MalformedURLException if the system ID is malformed and the entity resolver is <code>null</code>
+ */
+ private void tokenize(InputSource is) throws SAXException, IOException,
+ MalformedURLException {
+ if (is == null) {
+ throw new IllegalArgumentException("Null input.");
+ }
+ if (is.getByteStream() == null && is.getCharacterStream() == null) {
+ String systemId = is.getSystemId();
+ if (systemId == null) {
+ throw new IllegalArgumentException(
+ "No byte stream, no character stream nor URI.");
+ }
+ if (entityResolver != null) {
+ is = entityResolver.resolveEntity(is.getPublicId(), systemId);
+ }
+ if (is.getByteStream() == null || is.getCharacterStream() == null) {
+ is = new InputSource();
+ is.setSystemId(systemId);
+ is.setByteStream(new URL(systemId).openStream());
+ }
+ }
+ if (driver == null) lazyInit();
+ driver.tokenize(is);
+ }
+
+ /**
+ * Returns the DOM implementation
+ * @return the DOM implementation
+ * @see javax.xml.parsers.DocumentBuilder#getDOMImplementation()
+ */
+ @Override public DOMImplementation getDOMImplementation() {
+ return implementation;
+ }
+
+ /**
+ * Returns <code>true</code>.
+ * @return <code>true</code>
+ * @see javax.xml.parsers.DocumentBuilder#isNamespaceAware()
+ */
+ @Override public boolean isNamespaceAware() {
+ return true;
+ }
+
+ /**
+ * Returns <code>false</code>
+ * @return <code>false</code>
+ * @see javax.xml.parsers.DocumentBuilder#isValidating()
+ */
+ @Override public boolean isValidating() {
+ return false;
+ }
+
+ /**
+ * For API compatibility.
+ * @see javax.xml.parsers.DocumentBuilder#newDocument()
+ */
+ @Override public Document newDocument() {
+ return implementation.createDocument(null, null, null);
+ }
+
+ /**
+ * Parses a document from a SAX <code>InputSource</code>.
+ * @param is the source
+ * @return the doc
+ * @throws SAXException if stuff goes wrong
+ * @throws IOException if IO goes wrong
+ * @see javax.xml.parsers.DocumentBuilder#parse(org.xml.sax.InputSource)
+ */
+ @Override public Document parse(InputSource is) throws SAXException,
+ IOException {
+ treeBuilder.setFragmentContext(null);
+ tokenize(is);
+ return treeBuilder.getDocument();
+ }
+
+ /**
+ * Parses a document fragment from a SAX <code>InputSource</code> with
+ * an HTML element as the fragment context.
+ * @param is the source
+ * @param context the context element name (HTML namespace assumed)
+ * @return the document fragment
+ * @throws SAXException if stuff goes wrong
+ * @throws IOException if IO goes wrong
+ */
+ public DocumentFragment parseFragment(InputSource is, String context)
+ throws IOException, SAXException {
+ treeBuilder.setFragmentContext(context.intern());
+ tokenize(is);
+ return treeBuilder.getDocumentFragment();
+ }
+
+ /**
+ * Parses a document fragment from a SAX <code>InputSource</code>.
+ * @param is the source
+ * @param contextLocal the local name of the context element
+ * @param contextNamespace the namespace of the context element
+ * @return the document fragment
+ * @throws SAXException if stuff goes wrong
+ * @throws IOException if IO goes wrong
+ */
+ public DocumentFragment parseFragment(InputSource is, String contextLocal,
+ String contextNamespace) throws IOException, SAXException {
+ treeBuilder.setFragmentContext(contextLocal.intern(),
+ contextNamespace.intern(), null, false);
+ tokenize(is);
+ return treeBuilder.getDocumentFragment();
+ }
+
+ /**
+ * Sets the entity resolver for URI-only inputs.
+ * @param resolver the resolver
+ * @see javax.xml.parsers.DocumentBuilder#setEntityResolver(org.xml.sax.EntityResolver)
+ */
+ @Override public void setEntityResolver(EntityResolver resolver) {
+ this.entityResolver = resolver;
+ }
+
+ /**
+ * Sets the error handler.
+ * @param errorHandler the handler
+ * @see javax.xml.parsers.DocumentBuilder#setErrorHandler(org.xml.sax.ErrorHandler)
+ */
+ @Override public void setErrorHandler(ErrorHandler errorHandler) {
+ treeBuilder.setErrorHandler(errorHandler);
+ if (driver != null) {
+ driver.setErrorHandler(errorHandler);
+ }
+ }
+
+ public void setTransitionHander(TransitionHandler handler) {
+ transitionHandler = handler;
+ driver = null;
+ }
+
+ /**
+ * Indicates whether NFC normalization of source is being checked.
+ * @return <code>true</code> if NFC normalization of source is being checked.
+ * @see nu.validator.htmlparser.impl.Tokenizer#isCheckingNormalization()
+ */
+ public boolean isCheckingNormalization() {
+ return checkingNormalization;
+ }
+
+ /**
+ * Toggles the checking of the NFC normalization of source.
+ * @param enable <code>true</code> to check normalization
+ * @see nu.validator.htmlparser.impl.Tokenizer#setCheckingNormalization(boolean)
+ */
+ public void setCheckingNormalization(boolean enable) {
+ this.checkingNormalization = enable;
+ if (driver != null) {
+ driver.setCheckingNormalization(checkingNormalization);
+ }
+ }
+
+ /**
+ * Sets the policy for consecutive hyphens in comments.
+ * @param commentPolicy the policy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setCommentPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setCommentPolicy(XmlViolationPolicy commentPolicy) {
+ this.commentPolicy = commentPolicy;
+ if (driver != null) {
+ driver.setCommentPolicy(commentPolicy);
+ }
+ }
+
+ /**
+ * Sets the policy for non-XML characters except white space.
+ * @param contentNonXmlCharPolicy the policy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setContentNonXmlCharPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setContentNonXmlCharPolicy(
+ XmlViolationPolicy contentNonXmlCharPolicy) {
+ this.contentNonXmlCharPolicy = contentNonXmlCharPolicy;
+ driver = null;
+ }
+
+ /**
+ * Sets the policy for non-XML white space.
+ * @param contentSpacePolicy the policy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setContentSpacePolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setContentSpacePolicy(XmlViolationPolicy contentSpacePolicy) {
+ this.contentSpacePolicy = contentSpacePolicy;
+ if (driver != null) {
+ driver.setContentSpacePolicy(contentSpacePolicy);
+ }
+ }
+
+ /**
+ * Whether the parser considers scripting to be enabled for noscript treatment.
+ *
+ * @return <code>true</code> if enabled
+ * @see nu.validator.htmlparser.impl.TreeBuilder#isScriptingEnabled()
+ */
+ public boolean isScriptingEnabled() {
+ return scriptingEnabled;
+ }
+
+ /**
+ * Sets whether the parser considers scripting to be enabled for noscript treatment.
+ * @param scriptingEnabled <code>true</code> to enable
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setScriptingEnabled(boolean)
+ */
+ public void setScriptingEnabled(boolean scriptingEnabled) {
+ this.scriptingEnabled = scriptingEnabled;
+ if (treeBuilder != null) {
+ treeBuilder.setScriptingEnabled(scriptingEnabled);
+ }
+ }
+
+ /**
+ * Returns the doctype expectation.
+ *
+ * @return the doctypeExpectation
+ */
+ public DoctypeExpectation getDoctypeExpectation() {
+ return doctypeExpectation;
+ }
+
+ /**
+ * Sets the doctype expectation.
+ *
+ * @param doctypeExpectation
+ * the doctypeExpectation to set
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setDoctypeExpectation(nu.validator.htmlparser.common.DoctypeExpectation)
+ */
+ public void setDoctypeExpectation(DoctypeExpectation doctypeExpectation) {
+ this.doctypeExpectation = doctypeExpectation;
+ if (treeBuilder != null) {
+ treeBuilder.setDoctypeExpectation(doctypeExpectation);
+ }
+ }
+
+ /**
+ * Returns the document mode handler.
+ *
+ * @return the documentModeHandler
+ */
+ public DocumentModeHandler getDocumentModeHandler() {
+ return documentModeHandler;
+ }
+
+ /**
+ * Sets the document mode handler.
+ *
+ * @param documentModeHandler
+ * the documentModeHandler to set
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setDocumentModeHandler(nu.validator.htmlparser.common.DocumentModeHandler)
+ */
+ public void setDocumentModeHandler(DocumentModeHandler documentModeHandler) {
+ this.documentModeHandler = documentModeHandler;
+ }
+
+ /**
+ * Returns the streamabilityViolationPolicy.
+ *
+ * @return the streamabilityViolationPolicy
+ */
+ public XmlViolationPolicy getStreamabilityViolationPolicy() {
+ return streamabilityViolationPolicy;
+ }
+
+ /**
+ * Sets the streamabilityViolationPolicy.
+ *
+ * @param streamabilityViolationPolicy
+ * the streamabilityViolationPolicy to set
+ */
+ public void setStreamabilityViolationPolicy(
+ XmlViolationPolicy streamabilityViolationPolicy) {
+ this.streamabilityViolationPolicy = streamabilityViolationPolicy;
+ driver = null;
+ }
+
+ /**
+ * Whether the HTML 4 mode reports boolean attributes in a way that repeats
+ * the name in the value.
+ * @param html4ModeCompatibleWithXhtml1Schemata
+ */
+ public void setHtml4ModeCompatibleWithXhtml1Schemata(
+ boolean html4ModeCompatibleWithXhtml1Schemata) {
+ this.html4ModeCompatibleWithXhtml1Schemata = html4ModeCompatibleWithXhtml1Schemata;
+ if (driver != null) {
+ driver.setHtml4ModeCompatibleWithXhtml1Schemata(html4ModeCompatibleWithXhtml1Schemata);
+ }
+ }
+
+ /**
+ * Returns the <code>Locator</code> during parse.
+ * @return the <code>Locator</code>
+ */
+ public Locator getDocumentLocator() {
+ return driver.getDocumentLocator();
+ }
+
+ /**
+ * Whether the HTML 4 mode reports boolean attributes in a way that repeats
+ * the name in the value.
+ *
+ * @return the html4ModeCompatibleWithXhtml1Schemata
+ */
+ public boolean isHtml4ModeCompatibleWithXhtml1Schemata() {
+ return html4ModeCompatibleWithXhtml1Schemata;
+ }
+
+ /**
+ * Whether <code>lang</code> is mapped to <code>xml:lang</code>.
+ * @param mappingLangToXmlLang
+ * @see nu.validator.htmlparser.impl.Tokenizer#setMappingLangToXmlLang(boolean)
+ */
+ public void setMappingLangToXmlLang(boolean mappingLangToXmlLang) {
+ this.mappingLangToXmlLang = mappingLangToXmlLang;
+ if (driver != null) {
+ driver.setMappingLangToXmlLang(mappingLangToXmlLang);
+ }
+ }
+
+ /**
+ * Whether <code>lang</code> is mapped to <code>xml:lang</code>.
+ *
+ * @return the mappingLangToXmlLang
+ */
+ public boolean isMappingLangToXmlLang() {
+ return mappingLangToXmlLang;
+ }
+
+ /**
+ * Whether the <code>xmlns</code> attribute on the root element is
+ * passed to through. (FATAL not allowed.)
+ * @param xmlnsPolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setXmlnsPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setXmlnsPolicy(XmlViolationPolicy xmlnsPolicy) {
+ if (xmlnsPolicy == XmlViolationPolicy.FATAL) {
+ throw new IllegalArgumentException("Can't use FATAL here.");
+ }
+ this.xmlnsPolicy = xmlnsPolicy;
+ if (driver != null) {
+ driver.setXmlnsPolicy(xmlnsPolicy);
+ }
+ }
+
+ /**
+ * Returns the xmlnsPolicy.
+ *
+ * @return the xmlnsPolicy
+ */
+ public XmlViolationPolicy getXmlnsPolicy() {
+ return xmlnsPolicy;
+ }
+
+ /**
+ * Returns the commentPolicy.
+ *
+ * @return the commentPolicy
+ */
+ public XmlViolationPolicy getCommentPolicy() {
+ return commentPolicy;
+ }
+
+ /**
+ * Returns the contentNonXmlCharPolicy.
+ *
+ * @return the contentNonXmlCharPolicy
+ */
+ public XmlViolationPolicy getContentNonXmlCharPolicy() {
+ return contentNonXmlCharPolicy;
+ }
+
+ /**
+ * Returns the contentSpacePolicy.
+ *
+ * @return the contentSpacePolicy
+ */
+ public XmlViolationPolicy getContentSpacePolicy() {
+ return contentSpacePolicy;
+ }
+
+ /**
+ * @param reportingDoctype
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setReportingDoctype(boolean)
+ */
+ public void setReportingDoctype(boolean reportingDoctype) {
+ this.reportingDoctype = reportingDoctype;
+ if (treeBuilder != null) {
+ treeBuilder.setReportingDoctype(reportingDoctype);
+ }
+ }
+
+ /**
+ * Returns the reportingDoctype.
+ *
+ * @return the reportingDoctype
+ */
+ public boolean isReportingDoctype() {
+ return reportingDoctype;
+ }
+
+ /**
+ * The policy for non-NCName element and attribute names.
+ * @param namePolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setNamePolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setNamePolicy(XmlViolationPolicy namePolicy) {
+ this.namePolicy = namePolicy;
+ if (driver != null) {
+ driver.setNamePolicy(namePolicy);
+ treeBuilder.setNamePolicy(namePolicy);
+ }
+ }
+
+ /**
+ * Sets the encoding sniffing heuristics.
+ *
+ * @param heuristics the heuristics to set
+ * @see nu.validator.htmlparser.impl.Tokenizer#setHeuristics(nu.validator.htmlparser.common.Heuristics)
+ */
+ public void setHeuristics(Heuristics heuristics) {
+ this.heuristics = heuristics;
+ if (driver != null) {
+ driver.setHeuristics(heuristics);
+ }
+ }
+
+ public Heuristics getHeuristics() {
+ return this.heuristics;
+ }
+
+ /**
+ * This is a catch-all convenience method for setting name, xmlns, content space,
+ * content non-XML char and comment policies in one go. This does not affect the
+ * streamability policy or doctype reporting.
+ *
+ * @param xmlPolicy
+ */
+ public void setXmlPolicy(XmlViolationPolicy xmlPolicy) {
+ setNamePolicy(xmlPolicy);
+ setXmlnsPolicy(xmlPolicy == XmlViolationPolicy.FATAL ? XmlViolationPolicy.ALTER_INFOSET : xmlPolicy);
+ setContentSpacePolicy(xmlPolicy);
+ setContentNonXmlCharPolicy(xmlPolicy);
+ setCommentPolicy(xmlPolicy);
+ }
+
+ /**
+ * The policy for non-NCName element and attribute names.
+ *
+ * @return the namePolicy
+ */
+ public XmlViolationPolicy getNamePolicy() {
+ return namePolicy;
+ }
+
+ /**
+ * Does nothing.
+ * @deprecated
+ */
+ public void setBogusXmlnsPolicy(
+ XmlViolationPolicy bogusXmlnsPolicy) {
+ }
+
+ /**
+ * Returns <code>XmlViolationPolicy.ALTER_INFOSET</code>.
+ * @deprecated
+ * @return <code>XmlViolationPolicy.ALTER_INFOSET</code>
+ */
+ public XmlViolationPolicy getBogusXmlnsPolicy() {
+ return XmlViolationPolicy.ALTER_INFOSET;
+ }
+
+ public void addCharacterHandler(CharacterHandler characterHandler) {
+ this.characterHandlers.add(characterHandler);
+ if (driver != null) {
+ driver.addCharacterHandler(characterHandler);
+ }
+ }
+
+
+ /**
+ * Sets whether comment nodes appear in the tree.
+ * @param ignoreComments <code>true</code> to ignore comments
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setIgnoringComments(boolean)
+ */
+ public void setIgnoringComments(boolean ignoreComments) {
+ treeBuilder.setIgnoringComments(ignoreComments);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/package.html b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/package.html
new file mode 100644
index 000000000..d793bcf86
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/dom/package.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head><title>Package Overview</title>
+<!--
+ Copyright (c) 2007 Henri Sivonen
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+-->
+</head>
+<body bgcolor="white">
+<p>This package provides an HTML5 parser that exposes the document using the DOM API.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/ChardetSniffer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/ChardetSniffer.java
new file mode 100644
index 000000000..a75750398
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/ChardetSniffer.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.extra;
+
+import java.io.IOException;
+import java.nio.charset.UnsupportedCharsetException;
+
+import nu.validator.htmlparser.io.Encoding;
+
+import org.mozilla.intl.chardet.nsDetector;
+import org.mozilla.intl.chardet.nsICharsetDetectionObserver;
+import org.mozilla.intl.chardet.nsPSMDetector;
+
+import com.ibm.icu.text.CharsetDetector;
+
+public class ChardetSniffer implements nsICharsetDetectionObserver {
+
+ private final byte[] source;
+
+ private final int length;
+
+ private Encoding returnValue = null;
+
+ /**
+ * @param source
+ */
+ public ChardetSniffer(final byte[] source, final int length) {
+ this.source = source;
+ this.length = length;
+ }
+
+ public Encoding sniff() throws IOException {
+ nsDetector detector = new nsDetector(nsPSMDetector.ALL);
+ detector.Init(this);
+ detector.DoIt(source, length, false);
+ detector.DataEnd();
+ if (returnValue != null && returnValue != Encoding.WINDOWS1252 && returnValue.isAsciiSuperset()) {
+ return returnValue;
+ } else {
+ return null;
+ }
+ }
+
+ public static void main(String[] args) {
+ String[] detectable = CharsetDetector.getAllDetectableCharsets();
+ for (int i = 0; i < detectable.length; i++) {
+ String charset = detectable[i];
+ System.out.println(charset);
+ }
+ }
+
+ public void Notify(String charsetName) {
+ try {
+ Encoding enc = Encoding.forName(charsetName);
+ Encoding actual = enc.getActualHtmlEncoding();
+ if (actual != null) {
+ enc = actual;
+ }
+ returnValue = enc;
+ } catch (UnsupportedCharsetException e) {
+ returnValue = null;
+ }
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/IcuDetectorSniffer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/IcuDetectorSniffer.java
new file mode 100644
index 000000000..f3caab5c4
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/IcuDetectorSniffer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.extra;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import nu.validator.htmlparser.common.ByteReadable;
+import nu.validator.htmlparser.io.Encoding;
+
+import com.ibm.icu.text.CharsetDetector;
+import com.ibm.icu.text.CharsetMatch;
+
+public class IcuDetectorSniffer extends InputStream {
+
+ private final ByteReadable source;
+
+ /**
+ * @param source
+ */
+ public IcuDetectorSniffer(final ByteReadable source) {
+ this.source = source;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return source.readByte();
+ }
+
+ public Encoding sniff() throws IOException {
+ try {
+ CharsetDetector detector = new CharsetDetector();
+ detector.setText(this);
+ CharsetMatch match = detector.detect();
+ Encoding enc = Encoding.forName(match.getName());
+ Encoding actual = enc.getActualHtmlEncoding();
+ if (actual != null) {
+ enc = actual;
+ }
+ if (enc != Encoding.WINDOWS1252 && enc.isAsciiSuperset()) {
+ return enc;
+ } else {
+ return null;
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public static void main(String[] args) {
+ String[] detectable = CharsetDetector.getAllDetectableCharsets();
+ for (int i = 0; i < detectable.length; i++) {
+ String charset = detectable[i];
+ System.out.println(charset);
+ }
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/NormalizationChecker.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/NormalizationChecker.java
new file mode 100644
index 000000000..45df62fb7
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/extra/NormalizationChecker.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2006, 2007 Henri Sivonen
+ * Copyright (c) 2007 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.extra;
+
+import nu.validator.htmlparser.common.CharacterHandler;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import com.ibm.icu.lang.UCharacter;
+import com.ibm.icu.text.Normalizer;
+import com.ibm.icu.text.UnicodeSet;
+
+/**
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class NormalizationChecker implements CharacterHandler {
+
+ private ErrorHandler errorHandler;
+
+ private Locator locator;
+
+ /**
+ * A thread-safe set of composing characters as per Charmod Norm.
+ */
+ @SuppressWarnings("deprecation")
+ private static final UnicodeSet COMPOSING_CHARACTERS = (UnicodeSet) new UnicodeSet(
+ "[[:nfc_qc=maybe:][:^ccc=0:]]").freeze();
+
+ // see http://sourceforge.net/mailarchive/message.php?msg_id=37279908
+
+ /**
+ * A buffer for holding sequences overlap the SAX buffer boundary.
+ */
+ private char[] buf = new char[128];
+
+ /**
+ * A holder for the original buffer (for the memory leak prevention
+ * mechanism).
+ */
+ private char[] bufHolder = null;
+
+ /**
+ * The current used length of the buffer, i.e. the index of the first slot
+ * that does not hold current data.
+ */
+ private int pos;
+
+ /**
+ * Indicates whether the checker the next call to <code>characters()</code>
+ * is the first call in a run.
+ */
+ private boolean atStartOfRun;
+
+ /**
+ * Indicates whether the current run has already caused an error.
+ */
+ private boolean alreadyComplainedAboutThisRun;
+
+ /**
+ * Emit an error. The locator is used.
+ *
+ * @param message the error message
+ * @throws SAXException if something goes wrong
+ */
+ public void err(String message) throws SAXException {
+ if (errorHandler != null) {
+ SAXParseException spe = new SAXParseException(message, locator);
+ errorHandler.error(spe);
+ }
+ }
+
+ /**
+ * Returns <code>true</code> if the argument is a composing BMP character
+ * or a surrogate and <code>false</code> otherwise.
+ *
+ * @param c a UTF-16 code unit
+ * @return <code>true</code> if the argument is a composing BMP character
+ * or a surrogate and <code>false</code> otherwise
+ */
+ private static boolean isComposingCharOrSurrogate(char c) {
+ if (UCharacter.isHighSurrogate(c) || UCharacter.isLowSurrogate(c)) {
+ return true;
+ }
+ return isComposingChar(c);
+ }
+
+ /**
+ * Returns <code>true</code> if the argument is a composing character
+ * and <code>false</code> otherwise.
+ *
+ * @param c a Unicode code point
+ * @return <code>true</code> if the argument is a composing character
+ * <code>false</code> otherwise
+ */
+ private static boolean isComposingChar(int c) {
+ return COMPOSING_CHARACTERS.contains(c);
+ }
+
+ /**
+ * Constructor with mode selection.
+ *
+ * @param sourceTextMode whether the source text-related messages
+ * should be enabled.
+ */
+ public NormalizationChecker(Locator locator) {
+ super();
+ start();
+ }
+
+ /**
+ * @see nu.validator.htmlparser.common.CharacterHandler#start()
+ */
+ public void start() {
+ atStartOfRun = true;
+ alreadyComplainedAboutThisRun = false;
+ pos = 0;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.common.CharacterHandler#characters(char[], int, int)
+ */
+ public void characters(char[] ch, int start, int length)
+ throws SAXException {
+ if (alreadyComplainedAboutThisRun) {
+ return;
+ }
+ if (atStartOfRun) {
+ char c = ch[start];
+ if (pos == 1) {
+ // there's a single high surrogate in buf
+ if (isComposingChar(UCharacter.getCodePoint(buf[0], c))) {
+ err("Text run starts with a composing character.");
+ }
+ atStartOfRun = false;
+ } else {
+ if (length == 1 && UCharacter.isHighSurrogate(c)) {
+ buf[0] = c;
+ pos = 1;
+ return;
+ } else {
+ if (UCharacter.isHighSurrogate(c)) {
+ if (isComposingChar(UCharacter.getCodePoint(c,
+ ch[start + 1]))) {
+ err("Text run starts with a composing character.");
+ }
+ } else {
+ if (isComposingCharOrSurrogate(c)) {
+ err("Text run starts with a composing character.");
+ }
+ }
+ atStartOfRun = false;
+ }
+ }
+ }
+ int i = start;
+ int stop = start + length;
+ if (pos > 0) {
+ // there's stuff in buf
+ while (i < stop && isComposingCharOrSurrogate(ch[i])) {
+ i++;
+ }
+ appendToBuf(ch, start, i);
+ if (i == stop) {
+ return;
+ } else {
+ if (!Normalizer.isNormalized(buf, 0, pos, Normalizer.NFC, 0)) {
+ errAboutTextRun();
+ }
+ pos = 0;
+ }
+ }
+ if (i < stop) {
+ start = i;
+ i = stop - 1;
+ while (i > start && isComposingCharOrSurrogate(ch[i])) {
+ i--;
+ }
+ if (i > start) {
+ if (!Normalizer.isNormalized(ch, start, i, Normalizer.NFC, 0)) {
+ errAboutTextRun();
+ }
+ }
+ appendToBuf(ch, i, stop);
+ }
+ }
+
+ /**
+ * Emits an error stating that the current text run or the source
+ * text is not in NFC.
+ *
+ * @throws SAXException if the <code>ErrorHandler</code> throws
+ */
+ private void errAboutTextRun() throws SAXException {
+ err("Source text is not in Unicode Normalization Form C.");
+ alreadyComplainedAboutThisRun = true;
+ }
+
+ /**
+ * Appends a slice of an UTF-16 code unit array to the internal
+ * buffer.
+ *
+ * @param ch the array from which to copy
+ * @param start the index of the first element that is copied
+ * @param end the index of the first element that is not copied
+ */
+ private void appendToBuf(char[] ch, int start, int end) {
+ if (start == end) {
+ return;
+ }
+ int neededBufLen = pos + (end - start);
+ if (neededBufLen > buf.length) {
+ char[] newBuf = new char[neededBufLen];
+ System.arraycopy(buf, 0, newBuf, 0, pos);
+ if (bufHolder == null) {
+ bufHolder = buf; // keep the original around
+ }
+ buf = newBuf;
+ }
+ System.arraycopy(ch, start, buf, pos, end - start);
+ pos += (end - start);
+ }
+
+ /**
+ * @see nu.validator.htmlparser.common.CharacterHandler#end()
+ */
+ public void end() throws SAXException {
+ if (!alreadyComplainedAboutThisRun
+ && !Normalizer.isNormalized(buf, 0, pos, Normalizer.NFC, 0)) {
+ errAboutTextRun();
+ }
+ if (bufHolder != null) {
+ // restore the original small buffer to avoid leaking
+ // memory if this checker is recycled
+ buf = bufHolder;
+ bufHolder = null;
+ }
+ }
+
+ public void setErrorHandler(ErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/AttributeName.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/AttributeName.java
new file mode 100644
index 000000000..28ada917a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/AttributeName.java
@@ -0,0 +1,2539 @@
+/*
+ * Copyright (c) 2008-2017 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import nu.validator.htmlparser.annotation.Inline;
+import nu.validator.htmlparser.annotation.Local;
+import nu.validator.htmlparser.annotation.NoLength;
+import nu.validator.htmlparser.annotation.NsUri;
+import nu.validator.htmlparser.annotation.Prefix;
+import nu.validator.htmlparser.annotation.QName;
+import nu.validator.htmlparser.annotation.Unsigned;
+import nu.validator.htmlparser.common.Interner;
+
+public final class AttributeName
+// Uncomment to regenerate
+// implements Comparable<AttributeName>
+{
+ // [NOCPP[
+
+ public static final int NCNAME_HTML = 1;
+
+ public static final int NCNAME_FOREIGN = (1 << 1) | (1 << 2);
+
+ public static final int NCNAME_LANG = (1 << 3);
+
+ public static final int IS_XMLNS = (1 << 4);
+
+ public static final int CASE_FOLDED = (1 << 5);
+
+ public static final int BOOLEAN = (1 << 6);
+
+ // ]NOCPP]
+
+ /**
+ * An array representing no namespace regardless of namespace mode (HTML,
+ * SVG, MathML, lang-mapping HTML) used.
+ */
+ static final @NoLength @NsUri String[] ALL_NO_NS = { "", "", "",
+ // [NOCPP[
+ ""
+ // ]NOCPP]
+ };
+
+ /**
+ * An array that has no namespace for the HTML mode but the XMLNS namespace
+ * for the SVG and MathML modes.
+ */
+ private static final @NoLength @NsUri String[] XMLNS_NS = { "",
+ "http://www.w3.org/2000/xmlns/", "http://www.w3.org/2000/xmlns/",
+ // [NOCPP[
+ ""
+ // ]NOCPP]
+ };
+
+ /**
+ * An array that has no namespace for the HTML mode but the XML namespace
+ * for the SVG and MathML modes.
+ */
+ private static final @NoLength @NsUri String[] XML_NS = { "",
+ "http://www.w3.org/XML/1998/namespace",
+ "http://www.w3.org/XML/1998/namespace",
+ // [NOCPP[
+ ""
+ // ]NOCPP]
+ };
+
+ /**
+ * An array that has no namespace for the HTML mode but the XLink namespace
+ * for the SVG and MathML modes.
+ */
+ private static final @NoLength @NsUri String[] XLINK_NS = { "",
+ "http://www.w3.org/1999/xlink", "http://www.w3.org/1999/xlink",
+ // [NOCPP[
+ ""
+ // ]NOCPP]
+ };
+
+ // [NOCPP[
+ /**
+ * An array that has no namespace for the HTML, SVG and MathML modes but has
+ * the XML namespace for the lang-mapping HTML mode.
+ */
+ private static final @NoLength @NsUri String[] LANG_NS = { "", "", "",
+ "http://www.w3.org/XML/1998/namespace" };
+
+ // ]NOCPP]
+
+ /**
+ * An array for no prefixes in any mode.
+ */
+ static final @NoLength @Prefix String[] ALL_NO_PREFIX = { null, null, null,
+ // [NOCPP[
+ null
+ // ]NOCPP]
+ };
+
+ /**
+ * An array for no prefixe in the HTML mode and the <code>xmlns</code>
+ * prefix in the SVG and MathML modes.
+ */
+ private static final @NoLength @Prefix String[] XMLNS_PREFIX = { null,
+ "xmlns", "xmlns",
+ // [NOCPP[
+ null
+ // ]NOCPP]
+ };
+
+ /**
+ * An array for no prefixe in the HTML mode and the <code>xlink</code>
+ * prefix in the SVG and MathML modes.
+ */
+ private static final @NoLength @Prefix String[] XLINK_PREFIX = { null,
+ "xlink", "xlink",
+ // [NOCPP[
+ null
+ // ]NOCPP]
+ };
+
+ /**
+ * An array for no prefixe in the HTML mode and the <code>xml</code> prefix
+ * in the SVG and MathML modes.
+ */
+ private static final @NoLength @Prefix String[] XML_PREFIX = { null, "xml",
+ "xml",
+ // [NOCPP[
+ null
+ // ]NOCPP]
+ };
+
+ // [NOCPP[
+
+ private static final @NoLength @Prefix String[] LANG_PREFIX = { null, null,
+ null, "xml" };
+
+ private static @QName String[] COMPUTE_QNAME(String[] local, String[] prefix) {
+ @QName String[] arr = new String[4];
+ for (int i = 0; i < arr.length; i++) {
+ if (prefix[i] == null) {
+ arr[i] = local[i];
+ } else {
+ arr[i] = (prefix[i] + ':' + local[i]).intern();
+ }
+ }
+ return arr;
+ }
+
+ // ]NOCPP]
+
+ /**
+ * An initialization helper for having a one name in the SVG mode and
+ * another name in the other modes.
+ *
+ * @param name
+ * the name for the non-SVG modes
+ * @param camel
+ * the name for the SVG mode
+ * @return the initialized name array
+ */
+ private static @NoLength @Local String[] SVG_DIFFERENT(@Local String name,
+ @Local String camel) {
+ @NoLength @Local String[] arr = new String[4];
+ arr[0] = name;
+ arr[1] = name;
+ arr[2] = camel;
+ // [NOCPP[
+ arr[3] = name;
+ // ]NOCPP]
+ return arr;
+ }
+
+ /**
+ * An initialization helper for having a one name in the MathML mode and
+ * another name in the other modes.
+ *
+ * @param name
+ * the name for the non-MathML modes
+ * @param camel
+ * the name for the MathML mode
+ * @return the initialized name array
+ */
+ private static @NoLength @Local String[] MATH_DIFFERENT(@Local String name,
+ @Local String camel) {
+ @NoLength @Local String[] arr = new String[4];
+ arr[0] = name;
+ arr[1] = camel;
+ arr[2] = name;
+ // [NOCPP[
+ arr[3] = name;
+ // ]NOCPP]
+ return arr;
+ }
+
+ /**
+ * An initialization helper for having a different local name in the HTML
+ * mode and the SVG and MathML modes.
+ *
+ * @param name
+ * the name for the HTML mode
+ * @param suffix
+ * the name for the SVG and MathML modes
+ * @return the initialized name array
+ */
+ private static @NoLength @Local String[] COLONIFIED_LOCAL(
+ @Local String name, @Local String suffix) {
+ @NoLength @Local String[] arr = new String[4];
+ arr[0] = name;
+ arr[1] = suffix;
+ arr[2] = suffix;
+ // [NOCPP[
+ arr[3] = name;
+ // ]NOCPP]
+ return arr;
+ }
+
+ /**
+ * An initialization helper for having the same local name in all modes.
+ *
+ * @param name
+ * the name
+ * @return the initialized name array
+ */
+ static @NoLength @Local String[] SAME_LOCAL(@Local String name) {
+ @NoLength @Local String[] arr = new String[4];
+ arr[0] = name;
+ arr[1] = name;
+ arr[2] = name;
+ // [NOCPP[
+ arr[3] = name;
+ // ]NOCPP]
+ return arr;
+ }
+
+ @Inline static int levelOrderBinarySearch(int[] data, int key) {
+ int n = data.length;
+ int i = 0;
+
+ while (i < n) {
+ int val = data[i];
+ if (val < key) {
+ i = 2 * i + 2;
+ } else if (val > key) {
+ i = 2 * i + 1;
+ } else {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Returns an attribute name by buffer.
+ *
+ * <p>
+ * C++ ownership: The return value is either released by the caller if the
+ * attribute is a duplicate or the ownership is transferred to
+ * HtmlAttributes and released upon clearing or destroying that object.
+ *
+ * @param buf
+ * the buffer
+ * @param offset
+ * ignored
+ * @param length
+ * length of data
+ * @param checkNcName
+ * whether to check ncnameness
+ * @return an <code>AttributeName</code> corresponding to the argument data
+ */
+ @Inline static AttributeName nameByBuffer(@NoLength char[] buf, int offset,
+ int length
+ , Interner interner) {
+ // XXX deal with offset
+ @Unsigned int hash = AttributeName.bufToHash(buf, length);
+ int[] hashes;
+ hashes = AttributeName.ATTRIBUTE_HASHES;
+ int index = levelOrderBinarySearch(hashes, hash);
+ if (index < 0) {
+ return null;
+ }
+ AttributeName attributeName = AttributeName.ATTRIBUTE_NAMES[index];
+ @Local String name = attributeName.getLocal(0);
+ if (!Portability.localEqualsBuffer(name, buf, offset, length)) {
+ return null;
+ }
+ return attributeName;
+ }
+
+ /**
+ * This method has to return a unique positive integer for each well-known
+ * lower-cased attribute name.
+ *
+ * @param buf
+ * @param len
+ * @return
+ */
+ @Inline private static @Unsigned int bufToHash(@NoLength char[] buf, int length) {
+ @Unsigned int len = length;
+ @Unsigned int first = buf[0];
+ first <<= 19;
+ @Unsigned int second = 1 << 23;
+ @Unsigned int third = 0;
+ @Unsigned int fourth = 0;
+ @Unsigned int fifth = 0;
+ @Unsigned int sixth = 0;
+ if (length >= 4) {
+ second = buf[length - 4];
+ second <<= 4;
+ third = buf[1];
+ third <<= 9;
+ fourth = buf[length - 2];
+ fourth <<= 14;
+ fifth = buf[3];
+ fifth <<= 24;
+ sixth = buf[length - 1];
+ sixth <<= 11;
+ } else if (length == 3) {
+ second = buf[1];
+ second <<= 4;
+ third = buf[2];
+ third <<= 9;
+ } else if (length == 2) {
+ second = buf[1];
+ second <<= 24;
+ }
+ return len + first + second + third + fourth + fifth + sixth;
+ }
+
+ /**
+ * The mode value for HTML.
+ */
+ public static final int HTML = 0;
+
+ /**
+ * The mode value for MathML.
+ */
+ public static final int MATHML = 1;
+
+ /**
+ * The mode value for SVG.
+ */
+ public static final int SVG = 2;
+
+ // [NOCPP[
+
+ /**
+ * The mode value for lang-mapping HTML.
+ */
+ public static final int HTML_LANG = 3;
+
+ // ]NOCPP]
+
+ /**
+ * The namespaces indexable by mode.
+ */
+ private final @NsUri @NoLength String[] uri;
+
+ /**
+ * The local names indexable by mode.
+ */
+ private final @Local @NoLength String[] local;
+
+ /**
+ * The prefixes indexably by mode.
+ */
+ private final @Prefix @NoLength String[] prefix;
+
+ // CPPONLY: private final boolean custom;
+
+ // [NOCPP[
+
+ private final int flags;
+
+ /**
+ * The qnames indexable by mode.
+ */
+ private final @QName @NoLength String[] qName;
+
+ // ]NOCPP]
+
+ /**
+ * The startup-time constructor.
+ *
+ * @param uri
+ * the namespace
+ * @param local
+ * the local name
+ * @param prefix
+ * the prefix
+ * @param ncname
+ * the ncnameness
+ * @param xmlns
+ * whether this is an xmlns attribute
+ */
+ private AttributeName(@NsUri @NoLength String[] uri,
+ @Local @NoLength String[] local, @Prefix @NoLength String[] prefix
+ // [NOCPP[
+ , int flags
+ // ]NOCPP]
+ ) {
+ this.uri = uri;
+ this.local = local;
+ this.prefix = prefix;
+ // [NOCPP[
+ this.qName = COMPUTE_QNAME(local, prefix);
+ this.flags = flags;
+ // ]NOCPP]
+ // CPPONLY: this.custom = false;
+ }
+
+ // CPPONLY: public AttributeName() {
+ // CPPONLY: this.uri = AttributeName.ALL_NO_NS;
+ // CPPONLY: this.local = AttributeName.SAME_LOCAL(null);
+ // CPPONLY: this.prefix = ALL_NO_PREFIX;
+ // CPPONLY: this.custom = true;
+ // CPPONLY: }
+ // CPPONLY:
+ // CPPONLY: @Inline public boolean isInterned() {
+ // CPPONLY: return !custom;
+ // CPPONLY: }
+ // CPPONLY:
+ // CPPONLY: @Inline public void setNameForNonInterned(@Local String name) {
+ // CPPONLY: assert custom;
+ // CPPONLY: local[0] = name;
+ // CPPONLY: local[1] = name;
+ // CPPONLY: local[2] = name;
+ // CPPONLY: }
+
+ /**
+ * Creates an <code>AttributeName</code> for a local name.
+ *
+ * @param name
+ * the name
+ * @param checkNcName
+ * whether to check ncnameness
+ * @return an <code>AttributeName</code>
+ */
+ static AttributeName createAttributeName(@Local String name
+ // [NOCPP[
+ , boolean checkNcName
+ // ]NOCPP]
+ ) {
+ // [NOCPP[
+ int flags = NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG;
+ if (name.startsWith("xmlns:")) {
+ flags = IS_XMLNS;
+ } else if (checkNcName && !NCName.isNCName(name)) {
+ flags = 0;
+ }
+ // ]NOCPP]
+ return new AttributeName(AttributeName.ALL_NO_NS,
+ AttributeName.SAME_LOCAL(name), ALL_NO_PREFIX, flags);
+ }
+
+ /**
+ * The C++ destructor.
+ */
+ @SuppressWarnings("unused") private void destructor() {
+ Portability.deleteArray(local);
+ }
+
+ // [NOCPP[
+ /**
+ * Creator for use when the XML violation policy requires an attribute name
+ * to be changed.
+ *
+ * @param name
+ * the name of the attribute to create
+ */
+ static AttributeName create(@Local String name) {
+ return new AttributeName(AttributeName.ALL_NO_NS,
+ AttributeName.SAME_LOCAL(name), ALL_NO_PREFIX,
+ NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ }
+
+ /**
+ * Queries whether this name is an XML 1.0 4th ed. NCName.
+ *
+ * @param mode
+ * the SVG/MathML/HTML mode
+ * @return <code>true</code> if this is an NCName in the given mode
+ */
+ public boolean isNcName(int mode) {
+ return (flags & (1 << mode)) != 0;
+ }
+
+ /**
+ * Queries whether this is an <code>xmlns</code> attribute.
+ *
+ * @return <code>true</code> if this is an <code>xmlns</code> attribute
+ */
+ public boolean isXmlns() {
+ return (flags & IS_XMLNS) != 0;
+ }
+
+ /**
+ * Queries whether this attribute has a case-folded value in the HTML4 mode
+ * of the parser.
+ *
+ * @return <code>true</code> if the value is case-folded
+ */
+ boolean isCaseFolded() {
+ return (flags & CASE_FOLDED) != 0;
+ }
+
+ boolean isBoolean() {
+ return (flags & BOOLEAN) != 0;
+ }
+
+ public @QName String getQName(int mode) {
+ return qName[mode];
+ }
+
+ // ]NOCPP]
+
+ public @NsUri String getUri(int mode) {
+ return uri[mode];
+ }
+
+ public @Local String getLocal(int mode) {
+ return local[mode];
+ }
+
+ public @Prefix String getPrefix(int mode) {
+ return prefix[mode];
+ }
+
+ boolean equalsAnother(AttributeName another) {
+ return this.getLocal(AttributeName.HTML) == another.getLocal(AttributeName.HTML);
+ }
+
+ // START CODE ONLY USED FOR GENERATING CODE uncomment to regenerate
+
+// /**
+// * @see java.lang.Object#toString()
+// */
+// @Override public String toString() {
+// return "(" + formatNs() + ", " + formatLocal() + ", " + formatPrefix()
+// + ", " + formatFlags() + ")";
+// }
+//
+// private String formatFlags() {
+// StringBuilder builder = new StringBuilder();
+// if ((flags & NCNAME_HTML) != 0) {
+// if (builder.length() != 0) {
+// builder.append(" | ");
+// }
+// builder.append("NCNAME_HTML");
+// }
+// if ((flags & NCNAME_FOREIGN) != 0) {
+// if (builder.length() != 0) {
+// builder.append(" | ");
+// }
+// builder.append("NCNAME_FOREIGN");
+// }
+// if ((flags & NCNAME_LANG) != 0) {
+// if (builder.length() != 0) {
+// builder.append(" | ");
+// }
+// builder.append("NCNAME_LANG");
+// }
+// if (isXmlns()) {
+// if (builder.length() != 0) {
+// builder.append(" | ");
+// }
+// builder.append("IS_XMLNS");
+// }
+// if (isCaseFolded()) {
+// if (builder.length() != 0) {
+// builder.append(" | ");
+// }
+// builder.append("CASE_FOLDED");
+// }
+// if (isBoolean()) {
+// if (builder.length() != 0) {
+// builder.append(" | ");
+// }
+// builder.append("BOOLEAN");
+// }
+// if (builder.length() == 0) {
+// return "0";
+// }
+// return builder.toString();
+// }
+//
+// public int compareTo(AttributeName other) {
+// int thisHash = this.hash();
+// int otherHash = other.hash();
+// if (thisHash < otherHash) {
+// return -1;
+// } else if (thisHash == otherHash) {
+// return 0;
+// } else {
+// return 1;
+// }
+// }
+//
+// private String formatPrefix() {
+// if (prefix[0] == null && prefix[1] == null && prefix[2] == null
+// && prefix[3] == null) {
+// return "ALL_NO_PREFIX";
+// } else if (prefix[0] == null && prefix[1] == prefix[2]
+// && prefix[3] == null) {
+// if ("xmlns".equals(prefix[1])) {
+// return "XMLNS_PREFIX";
+// } else if ("xml".equals(prefix[1])) {
+// return "XML_PREFIX";
+// } else if ("xlink".equals(prefix[1])) {
+// return "XLINK_PREFIX";
+// } else {
+// throw new IllegalStateException();
+// }
+// } else if (prefix[0] == null && prefix[1] == null && prefix[2] == null
+// && prefix[3] == "xml") {
+// return "LANG_PREFIX";
+// } else {
+// throw new IllegalStateException();
+// }
+// }
+//
+// private String formatLocal() {
+// if (local[0] == local[1] && local[0] == local[3]
+// && local[0] != local[2]) {
+// return "SVG_DIFFERENT(\"" + local[0] + "\", \"" + local[2] + "\")";
+// }
+// if (local[0] == local[2] && local[0] == local[3]
+// && local[0] != local[1]) {
+// return "MATH_DIFFERENT(\"" + local[0] + "\", \"" + local[1] + "\")";
+// }
+// if (local[0] == local[3] && local[1] == local[2]
+// && local[0] != local[1]) {
+// return "COLONIFIED_LOCAL(\"" + local[0] + "\", \"" + local[1]
+// + "\")";
+// }
+// for (int i = 1; i < local.length; i++) {
+// if (local[0] != local[i]) {
+// throw new IllegalStateException();
+// }
+// }
+// return "SAME_LOCAL(\"" + local[0] + "\")";
+// }
+//
+// private String formatNs() {
+// if (uri[0] == "" && uri[1] == "" && uri[2] == "" && uri[3] == "") {
+// return "ALL_NO_NS";
+// } else if (uri[0] == "" && uri[1] == uri[2] && uri[3] == "") {
+// if ("http://www.w3.org/2000/xmlns/".equals(uri[1])) {
+// return "XMLNS_NS";
+// } else if ("http://www.w3.org/XML/1998/namespace".equals(uri[1])) {
+// return "XML_NS";
+// } else if ("http://www.w3.org/1999/xlink".equals(uri[1])) {
+// return "XLINK_NS";
+// } else {
+// throw new IllegalStateException();
+// }
+// } else if (uri[0] == "" && uri[1] == "" && uri[2] == ""
+// && uri[3] == "http://www.w3.org/XML/1998/namespace") {
+// return "LANG_NS";
+// } else {
+// throw new IllegalStateException();
+// }
+// }
+//
+// private String constName() {
+// String name = getLocal(HTML);
+// char[] buf = new char[name.length()];
+// for (int i = 0; i < name.length(); i++) {
+// char c = name.charAt(i);
+// if (c == '-' || c == ':') {
+// buf[i] = '_';
+// } else if (c >= 'a' && c <= 'z') {
+// buf[i] = (char) (c - 0x20);
+// } else {
+// buf[i] = c;
+// }
+// }
+// return new String(buf);
+// }
+//
+// private int hash() {
+// String name = getLocal(HTML);
+// return bufToHash(name.toCharArray(), name.length());
+// }
+//
+// private static void fillLevelOrderArray(List<AttributeName> sorted, int depth,
+// int rootIdx, AttributeName[] levelOrder) {
+// if (rootIdx >= levelOrder.length) {
+// return;
+// }
+//
+// if (depth > 0) {
+// fillLevelOrderArray(sorted, depth - 1, rootIdx * 2 + 1, levelOrder);
+// }
+//
+// if (!sorted.isEmpty()) {
+// levelOrder[rootIdx] = sorted.remove(0);
+// }
+//
+// if (depth > 0) {
+// fillLevelOrderArray(sorted, depth - 1, rootIdx * 2 + 2, levelOrder);
+// }
+// }
+//
+// /**
+// * Regenerate self
+// *
+// * @param args
+// */
+// public static void main(String[] args) {
+// Arrays.sort(ATTRIBUTE_NAMES);
+// for (int i = 0; i < ATTRIBUTE_NAMES.length; i++) {
+// int hash = ATTRIBUTE_NAMES[i].hash();
+// if (hash < 0) {
+// System.err.println("Negative hash: " + ATTRIBUTE_NAMES[i].local[0]);
+// return;
+// }
+// for (int j = i + 1; j < ATTRIBUTE_NAMES.length; j++) {
+// if (hash == ATTRIBUTE_NAMES[j].hash()) {
+// System.err.println(
+// "Hash collision: " + ATTRIBUTE_NAMES[i].local[0] + ", "
+// + ATTRIBUTE_NAMES[j].local[0]);
+// return;
+// }
+// }
+// }
+// for (int i = 0; i < ATTRIBUTE_NAMES.length; i++) {
+// AttributeName att = ATTRIBUTE_NAMES[i];
+// System.out.println("public static final AttributeName "
+// + att.constName() + " = new AttributeName" + att.toString()
+// + ";");
+// }
+//
+// LinkedList<AttributeName> sortedNames = new LinkedList<AttributeName>();
+// Collections.addAll(sortedNames, ATTRIBUTE_NAMES);
+// AttributeName[] levelOrder = new AttributeName[ATTRIBUTE_NAMES.length];
+// int bstDepth = (int) Math.ceil(Math.log(ATTRIBUTE_NAMES.length) / Math.log(2));
+// fillLevelOrderArray(sortedNames, bstDepth, 0, levelOrder);
+//
+// System.out.println("private final static @NoLength AttributeName[] ATTRIBUTE_NAMES = {");
+// for (int i = 0; i < levelOrder.length; i++) {
+// AttributeName att = levelOrder[i];
+// System.out.println(att.constName() + ",");
+// }
+// System.out.println("};");
+// System.out.println("private final static int[] ATTRIBUTE_HASHES = {");
+// for (int i = 0; i < levelOrder.length; i++) {
+// AttributeName att = levelOrder[i];
+// System.out.println(Integer.toString(att.hash()) + ",");
+// }
+// System.out.println("};");
+// }
+
+ // START GENERATED CODE
+ public static final AttributeName ALT = new AttributeName(ALL_NO_NS, SAME_LOCAL("alt"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DIR = new AttributeName(ALL_NO_NS, SAME_LOCAL("dir"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName DUR = new AttributeName(ALL_NO_NS, SAME_LOCAL("dur"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName END = new AttributeName(ALL_NO_NS, SAME_LOCAL("end"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("for"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName IN2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("in2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LOW = new AttributeName(ALL_NO_NS, SAME_LOCAL("low"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("min"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MAX = new AttributeName(ALL_NO_NS, SAME_LOCAL("max"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REL = new AttributeName(ALL_NO_NS, SAME_LOCAL("rel"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REV = new AttributeName(ALL_NO_NS, SAME_LOCAL("rev"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SRC = new AttributeName(ALL_NO_NS, SAME_LOCAL("src"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName D = new AttributeName(ALL_NO_NS, SAME_LOCAL("d"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName K = new AttributeName(ALL_NO_NS, SAME_LOCAL("k"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName R = new AttributeName(ALL_NO_NS, SAME_LOCAL("r"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName X = new AttributeName(ALL_NO_NS, SAME_LOCAL("x"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName Y = new AttributeName(ALL_NO_NS, SAME_LOCAL("y"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName Z = new AttributeName(ALL_NO_NS, SAME_LOCAL("z"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CAP_HEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("cap-height"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName G1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("g1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName K1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("k1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName U1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("u1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName X1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("x1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName Y1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("y1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName G2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("g2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName K2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("k2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName U2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("u2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName X2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("x2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName Y2 = new AttributeName(ALL_NO_NS, SAME_LOCAL("y2"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName K3 = new AttributeName(ALL_NO_NS, SAME_LOCAL("k3"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName K4 = new AttributeName(ALL_NO_NS, SAME_LOCAL("k4"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName XML_SPACE = new AttributeName(XML_NS, COLONIFIED_LOCAL("xml:space", "space"), XML_PREFIX, NCNAME_FOREIGN);
+ public static final AttributeName XML_LANG = new AttributeName(XML_NS, COLONIFIED_LOCAL("xml:lang", "lang"), XML_PREFIX, NCNAME_FOREIGN);
+ public static final AttributeName XML_BASE = new AttributeName(XML_NS, COLONIFIED_LOCAL("xml:base", "base"), XML_PREFIX, NCNAME_FOREIGN);
+ public static final AttributeName ARIA_GRAB = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-grab"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_VALUEMAX = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-valuemax"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_LABELLEDBY = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-labelledby"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_DESCRIBEDBY = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-describedby"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_DISABLED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-disabled"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_CHECKED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-checked"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_SELECTED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-selected"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_DROPEFFECT = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-dropeffect"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_REQUIRED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-required"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_EXPANDED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-expanded"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_PRESSED = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-pressed"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_LEVEL = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-level"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_CHANNEL = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-channel"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_HIDDEN = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-hidden"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_SECRET = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-secret"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_POSINSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-posinset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_ATOMIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-atomic"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_INVALID = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-invalid"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_TEMPLATEID = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-templateid"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_VALUEMIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-valuemin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_MULTISELECTABLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-multiselectable"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_CONTROLS = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-controls"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_MULTILINE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-multiline"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_READONLY = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-readonly"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_OWNS = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-owns"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_ACTIVEDESCENDANT = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-activedescendant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_RELEVANT = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-relevant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_DATATYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-datatype"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_VALUENOW = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-valuenow"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_SORT = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-sort"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_AUTOCOMPLETE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-autocomplete"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_FLOWTO = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-flowto"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_BUSY = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-busy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_LIVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-live"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_HASPOPUP = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-haspopup"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARIA_SETSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("aria-setsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CLEAR = new AttributeName(ALL_NO_NS, SAME_LOCAL("clear"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName DATAFORMATAS = new AttributeName(ALL_NO_NS, SAME_LOCAL("dataformatas"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName DISABLED = new AttributeName(ALL_NO_NS, SAME_LOCAL("disabled"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName DATAFLD = new AttributeName(ALL_NO_NS, SAME_LOCAL("datafld"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DEFAULT = new AttributeName(ALL_NO_NS, SAME_LOCAL("default"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName DATASRC = new AttributeName(ALL_NO_NS, SAME_LOCAL("datasrc"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DATA = new AttributeName(ALL_NO_NS, SAME_LOCAL("data"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName EQUALCOLUMNS = new AttributeName(ALL_NO_NS, SAME_LOCAL("equalcolumns"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName EQUALROWS = new AttributeName(ALL_NO_NS, SAME_LOCAL("equalrows"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("hspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ISMAP = new AttributeName(ALL_NO_NS, SAME_LOCAL("ismap"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName LOCAL = new AttributeName(ALL_NO_NS, SAME_LOCAL("local"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("lspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MOVABLELIMITS = new AttributeName(ALL_NO_NS, SAME_LOCAL("movablelimits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName NOTATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("notation"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDATASETCHANGED = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondatasetchanged"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDATAAVAILABLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondataavailable"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONPASTE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onpaste"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDATASETCOMPLETE = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondatasetcomplete"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName RSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("rspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ROWALIGN = new AttributeName(ALL_NO_NS, SAME_LOCAL("rowalign"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ROTATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("rotate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SEPARATOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("separator"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SEPARATORS = new AttributeName(ALL_NO_NS, SAME_LOCAL("separators"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName V_MATHEMATICAL = new AttributeName(ALL_NO_NS, SAME_LOCAL("v-mathematical"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("vspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName V_HANGING = new AttributeName(ALL_NO_NS, SAME_LOCAL("v-hanging"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName XCHANNELSELECTOR = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("xchannelselector", "xChannelSelector"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName YCHANNELSELECTOR = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("ychannelselector", "yChannelSelector"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARABIC_FORM = new AttributeName(ALL_NO_NS, SAME_LOCAL("arabic-form"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ENABLE_BACKGROUND = new AttributeName(ALL_NO_NS, SAME_LOCAL("enable-background"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDBLCLICK = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondblclick"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONABORT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onabort"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CALCMODE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("calcmode", "calcMode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CHECKED = new AttributeName(ALL_NO_NS, SAME_LOCAL("checked"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName DESCENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("descent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FENCE = new AttributeName(ALL_NO_NS, SAME_LOCAL("fence"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONSCROLL = new AttributeName(ALL_NO_NS, SAME_LOCAL("onscroll"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONACTIVATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onactivate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OPACITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("opacity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("spacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SPECULAREXPONENT = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("specularexponent", "specularExponent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SPECULARCONSTANT = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("specularconstant", "specularConstant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SPECIFICATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("specification"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName THICKMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("thickmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName UNICODE = new AttributeName(ALL_NO_NS, SAME_LOCAL("unicode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName UNICODE_BIDI = new AttributeName(ALL_NO_NS, SAME_LOCAL("unicode-bidi"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName UNICODE_RANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("unicode-range"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BORDER = new AttributeName(ALL_NO_NS, SAME_LOCAL("border"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ID = new AttributeName(ALL_NO_NS, SAME_LOCAL("id"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName GRADIENTTRANSFORM = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("gradienttransform", "gradientTransform"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName GRADIENTUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("gradientunits", "gradientUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HIDDEN = new AttributeName(ALL_NO_NS, SAME_LOCAL("hidden"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HEADERS = new AttributeName(ALL_NO_NS, SAME_LOCAL("headers"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName READONLY = new AttributeName(ALL_NO_NS, SAME_LOCAL("readonly"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName RENDERING_INTENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("rendering-intent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SEED = new AttributeName(ALL_NO_NS, SAME_LOCAL("seed"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SRCDOC = new AttributeName(ALL_NO_NS, SAME_LOCAL("srcdoc"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STDDEVIATION = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("stddeviation", "stdDeviation"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SANDBOX = new AttributeName(ALL_NO_NS, SAME_LOCAL("sandbox"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName V_IDEOGRAPHIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("v-ideographic"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName WORD_SPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("word-spacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ACCENTUNDER = new AttributeName(ALL_NO_NS, SAME_LOCAL("accentunder"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ACCEPT_CHARSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("accept-charset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ACCESSKEY = new AttributeName(ALL_NO_NS, SAME_LOCAL("accesskey"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ACCENT_HEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("accent-height"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ACCENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("accent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ASCENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("ascent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ACCEPT = new AttributeName(ALL_NO_NS, SAME_LOCAL("accept"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BEVELLED = new AttributeName(ALL_NO_NS, SAME_LOCAL("bevelled"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BASEFREQUENCY = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("basefrequency", "baseFrequency"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BASELINE_SHIFT = new AttributeName(ALL_NO_NS, SAME_LOCAL("baseline-shift"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BASEPROFILE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("baseprofile", "baseProfile"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BASELINE = new AttributeName(ALL_NO_NS, SAME_LOCAL("baseline"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BASE = new AttributeName(ALL_NO_NS, SAME_LOCAL("base"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CODE = new AttributeName(ALL_NO_NS, SAME_LOCAL("code"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CODETYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("codetype"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CODEBASE = new AttributeName(ALL_NO_NS, SAME_LOCAL("codebase"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CITE = new AttributeName(ALL_NO_NS, SAME_LOCAL("cite"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DEFER = new AttributeName(ALL_NO_NS, SAME_LOCAL("defer"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName DATETIME = new AttributeName(ALL_NO_NS, SAME_LOCAL("datetime"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DIRECTION = new AttributeName(ALL_NO_NS, SAME_LOCAL("direction"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName EDGEMODE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("edgemode", "edgeMode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName EDGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("edge"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("face"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HIDEFOCUS = new AttributeName(ALL_NO_NS, SAME_LOCAL("hidefocus"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName INDEX = new AttributeName(ALL_NO_NS, SAME_LOCAL("index"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName IRRELEVANT = new AttributeName(ALL_NO_NS, SAME_LOCAL("irrelevant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName INTERCEPT = new AttributeName(ALL_NO_NS, SAME_LOCAL("intercept"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName INTEGRITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("integrity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LINEBREAK = new AttributeName(ALL_NO_NS, SAME_LOCAL("linebreak"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LABEL = new AttributeName(ALL_NO_NS, SAME_LOCAL("label"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LINETHICKNESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("linethickness"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MODE = new AttributeName(ALL_NO_NS, SAME_LOCAL("mode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName NAME = new AttributeName(ALL_NO_NS, SAME_LOCAL("name"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName NORESIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("noresize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName ONBEFOREUNLOAD = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforeunload"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONREPEAT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onrepeat"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OBJECT = new AttributeName(ALL_NO_NS, SAME_LOCAL("object"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONSELECT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onselect"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ORDER = new AttributeName(ALL_NO_NS, SAME_LOCAL("order"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OTHER = new AttributeName(ALL_NO_NS, SAME_LOCAL("other"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONRESET = new AttributeName(ALL_NO_NS, SAME_LOCAL("onreset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONCELLCHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("oncellchange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONREADYSTATECHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onreadystatechange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMESSAGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmessage"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBEGIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbegin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONHELP = new AttributeName(ALL_NO_NS, SAME_LOCAL("onhelp"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBEFOREPRINT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforeprint"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ORIENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("orient"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ORIENTATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("orientation"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBEFORECOPY = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforecopy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONSELECTSTART = new AttributeName(ALL_NO_NS, SAME_LOCAL("onselectstart"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBEFOREPASTE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforepaste"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBEFOREUPDATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforeupdate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDEACTIVATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondeactivate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBEFOREACTIVATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforeactivate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBEFORDEACTIVATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbefordeactivate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONKEYPRESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("onkeypress"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONKEYUP = new AttributeName(ALL_NO_NS, SAME_LOCAL("onkeyup"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBEFOREEDITFOCUS = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforeeditfocus"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBEFORECUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbeforecut"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONKEYDOWN = new AttributeName(ALL_NO_NS, SAME_LOCAL("onkeydown"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONRESIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onresize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REPEAT = new AttributeName(ALL_NO_NS, SAME_LOCAL("repeat"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REPEAT_MAX = new AttributeName(ALL_NO_NS, SAME_LOCAL("repeat-max"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REFERRERPOLICY = new AttributeName(ALL_NO_NS, SAME_LOCAL("referrerpolicy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName RULES = new AttributeName(ALL_NO_NS, SAME_LOCAL("rules"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName REPEAT_MIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("repeat-min"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ROLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("role"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REPEATCOUNT = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("repeatcount", "repeatCount"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REPEAT_START = new AttributeName(ALL_NO_NS, SAME_LOCAL("repeat-start"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REPEAT_TEMPLATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("repeat-template"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REPEATDUR = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("repeatdur", "repeatDur"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SELECTED = new AttributeName(ALL_NO_NS, SAME_LOCAL("selected"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName SPEED = new AttributeName(ALL_NO_NS, SAME_LOCAL("speed"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SIZES = new AttributeName(ALL_NO_NS, SAME_LOCAL("sizes"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SUPERSCRIPTSHIFT = new AttributeName(ALL_NO_NS, SAME_LOCAL("superscriptshift"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STRETCHY = new AttributeName(ALL_NO_NS, SAME_LOCAL("stretchy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SCHEME = new AttributeName(ALL_NO_NS, SAME_LOCAL("scheme"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SPREADMETHOD = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("spreadmethod", "spreadMethod"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SELECTION = new AttributeName(ALL_NO_NS, SAME_LOCAL("selection"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("size"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("type"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName UNSELECTABLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("unselectable"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName UNDERLINE_POSITION = new AttributeName(ALL_NO_NS, SAME_LOCAL("underline-position"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName UNDERLINE_THICKNESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("underline-thickness"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName X_HEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("x-height"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DIFFUSECONSTANT = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("diffuseconstant", "diffuseConstant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HREF = new AttributeName(ALL_NO_NS, SAME_LOCAL("href"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HREFLANG = new AttributeName(ALL_NO_NS, SAME_LOCAL("hreflang"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONAFTERPRINT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onafterprint"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONAFTERUPDATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onafterupdate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PROFILE = new AttributeName(ALL_NO_NS, SAME_LOCAL("profile"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SURFACESCALE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("surfacescale", "surfaceScale"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName XREF = new AttributeName(ALL_NO_NS, SAME_LOCAL("xref"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ALIGN = new AttributeName(ALL_NO_NS, SAME_LOCAL("align"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName ALIGNMENT_BASELINE = new AttributeName(ALL_NO_NS, SAME_LOCAL("alignment-baseline"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ALIGNMENTSCOPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("alignmentscope"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DRAGGABLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("draggable"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("height"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HANGING = new AttributeName(ALL_NO_NS, SAME_LOCAL("hanging"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName IMAGE_RENDERING = new AttributeName(ALL_NO_NS, SAME_LOCAL("image-rendering"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LANGUAGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("language"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LANG = new AttributeName(LANG_NS, SAME_LOCAL("lang"), LANG_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LARGEOP = new AttributeName(ALL_NO_NS, SAME_LOCAL("largeop"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LONGDESC = new AttributeName(ALL_NO_NS, SAME_LOCAL("longdesc"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LENGTHADJUST = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("lengthadjust", "lengthAdjust"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MARGINHEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("marginheight"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MARGINWIDTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("marginwidth"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName NARGS = new AttributeName(ALL_NO_NS, SAME_LOCAL("nargs"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ORIGIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("origin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PING = new AttributeName(ALL_NO_NS, SAME_LOCAL("ping"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TARGET = new AttributeName(ALL_NO_NS, SAME_LOCAL("target"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TARGETX = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("targetx", "targetX"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TARGETY = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("targety", "targetY"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ALPHABETIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("alphabetic"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ARCHIVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("archive"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HIGH = new AttributeName(ALL_NO_NS, SAME_LOCAL("high"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LIGHTING_COLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("lighting-color"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MATHEMATICAL = new AttributeName(ALL_NO_NS, SAME_LOCAL("mathematical"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MATHBACKGROUND = new AttributeName(ALL_NO_NS, SAME_LOCAL("mathbackground"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName METHOD = new AttributeName(ALL_NO_NS, SAME_LOCAL("method"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName MATHVARIANT = new AttributeName(ALL_NO_NS, SAME_LOCAL("mathvariant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MATHCOLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("mathcolor"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MATHSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("mathsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName NOSHADE = new AttributeName(ALL_NO_NS, SAME_LOCAL("noshade"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName ONCHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onchange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PATHLENGTH = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("pathlength", "pathLength"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PATH = new AttributeName(ALL_NO_NS, SAME_LOCAL("path"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ALTIMG = new AttributeName(ALL_NO_NS, SAME_LOCAL("altimg"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ACTIONTYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("actiontype"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ACTION = new AttributeName(ALL_NO_NS, SAME_LOCAL("action"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ACTIVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("active"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName ADDITIVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("additive"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BEGIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("begin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DOMINANT_BASELINE = new AttributeName(ALL_NO_NS, SAME_LOCAL("dominant-baseline"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DIVISOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("divisor"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DEFINITIONURL = new AttributeName(ALL_NO_NS, MATH_DIFFERENT("definitionurl", "definitionURL"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HORIZ_ADV_X = new AttributeName(ALL_NO_NS, SAME_LOCAL("horiz-adv-x"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HORIZ_ORIGIN_X = new AttributeName(ALL_NO_NS, SAME_LOCAL("horiz-origin-x"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HORIZ_ORIGIN_Y = new AttributeName(ALL_NO_NS, SAME_LOCAL("horiz-origin-y"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LIMITINGCONEANGLE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("limitingconeangle", "limitingConeAngle"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MEDIUMMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("mediummathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MEDIA = new AttributeName(ALL_NO_NS, SAME_LOCAL("media"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MANIFEST = new AttributeName(ALL_NO_NS, SAME_LOCAL("manifest"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONFILTERCHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onfilterchange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONFINISH = new AttributeName(ALL_NO_NS, SAME_LOCAL("onfinish"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OPTIMUM = new AttributeName(ALL_NO_NS, SAME_LOCAL("optimum"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName RADIOGROUP = new AttributeName(ALL_NO_NS, SAME_LOCAL("radiogroup"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName RADIUS = new AttributeName(ALL_NO_NS, SAME_LOCAL("radius"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SCRIPTLEVEL = new AttributeName(ALL_NO_NS, SAME_LOCAL("scriptlevel"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SCRIPTSIZEMULTIPLIER = new AttributeName(ALL_NO_NS, SAME_LOCAL("scriptsizemultiplier"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STRING = new AttributeName(ALL_NO_NS, SAME_LOCAL("string"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STRIKETHROUGH_POSITION = new AttributeName(ALL_NO_NS, SAME_LOCAL("strikethrough-position"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STRIKETHROUGH_THICKNESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("strikethrough-thickness"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SCRIPTMINSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("scriptminsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TABINDEX = new AttributeName(ALL_NO_NS, SAME_LOCAL("tabindex"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VALIGN = new AttributeName(ALL_NO_NS, SAME_LOCAL("valign"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName VISIBILITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("visibility"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BACKGROUND = new AttributeName(ALL_NO_NS, SAME_LOCAL("background"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LINK = new AttributeName(ALL_NO_NS, SAME_LOCAL("link"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MARKER_MID = new AttributeName(ALL_NO_NS, SAME_LOCAL("marker-mid"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MARKERHEIGHT = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("markerheight", "markerHeight"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MARKER_END = new AttributeName(ALL_NO_NS, SAME_LOCAL("marker-end"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MASK = new AttributeName(ALL_NO_NS, SAME_LOCAL("mask"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MARKER_START = new AttributeName(ALL_NO_NS, SAME_LOCAL("marker-start"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MARKERWIDTH = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("markerwidth", "markerWidth"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MASKUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("maskunits", "maskUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MARKERUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("markerunits", "markerUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MASKCONTENTUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("maskcontentunits", "maskContentUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName AMPLITUDE = new AttributeName(ALL_NO_NS, SAME_LOCAL("amplitude"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CELLSPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("cellspacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CELLPADDING = new AttributeName(ALL_NO_NS, SAME_LOCAL("cellpadding"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DECLARE = new AttributeName(ALL_NO_NS, SAME_LOCAL("declare"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName FILL_RULE = new AttributeName(ALL_NO_NS, SAME_LOCAL("fill-rule"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FILL = new AttributeName(ALL_NO_NS, SAME_LOCAL("fill"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FILL_OPACITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("fill-opacity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MAXLENGTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("maxlength"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONCLICK = new AttributeName(ALL_NO_NS, SAME_LOCAL("onclick"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBLUR = new AttributeName(ALL_NO_NS, SAME_LOCAL("onblur"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REPLACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("replace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName ROWLINES = new AttributeName(ALL_NO_NS, SAME_LOCAL("rowlines"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SCALE = new AttributeName(ALL_NO_NS, SAME_LOCAL("scale"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STYLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("style"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TABLEVALUES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("tablevalues", "tableValues"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TITLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("title"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName V_ALPHABETIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("v-alphabetic"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName AZIMUTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("azimuth"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FORMAT = new AttributeName(ALL_NO_NS, SAME_LOCAL("format"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FRAMEBORDER = new AttributeName(ALL_NO_NS, SAME_LOCAL("frameborder"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FRAME = new AttributeName(ALL_NO_NS, SAME_LOCAL("frame"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName FRAMESPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("framespacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FROM = new AttributeName(ALL_NO_NS, SAME_LOCAL("from"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FORM = new AttributeName(ALL_NO_NS, SAME_LOCAL("form"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PROMPT = new AttributeName(ALL_NO_NS, SAME_LOCAL("prompt"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PRIMITIVEUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("primitiveunits", "primitiveUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SYMMETRIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("symmetric"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STEMH = new AttributeName(ALL_NO_NS, SAME_LOCAL("stemh"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STEMV = new AttributeName(ALL_NO_NS, SAME_LOCAL("stemv"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SEAMLESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("seamless"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SUMMARY = new AttributeName(ALL_NO_NS, SAME_LOCAL("summary"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName USEMAP = new AttributeName(ALL_NO_NS, SAME_LOCAL("usemap"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ZOOMANDPAN = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("zoomandpan", "zoomAndPan"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ASYNC = new AttributeName(ALL_NO_NS, SAME_LOCAL("async"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName ALINK = new AttributeName(ALL_NO_NS, SAME_LOCAL("alink"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName IN = new AttributeName(ALL_NO_NS, SAME_LOCAL("in"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ICON = new AttributeName(ALL_NO_NS, SAME_LOCAL("icon"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName KERNELMATRIX = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("kernelmatrix", "kernelMatrix"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName KERNING = new AttributeName(ALL_NO_NS, SAME_LOCAL("kerning"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName KERNELUNITLENGTH = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("kernelunitlength", "kernelUnitLength"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONUNLOAD = new AttributeName(ALL_NO_NS, SAME_LOCAL("onunload"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OPEN = new AttributeName(ALL_NO_NS, SAME_LOCAL("open"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONINVALID = new AttributeName(ALL_NO_NS, SAME_LOCAL("oninvalid"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONEND = new AttributeName(ALL_NO_NS, SAME_LOCAL("onend"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONINPUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("oninput"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName POINTER_EVENTS = new AttributeName(ALL_NO_NS, SAME_LOCAL("pointer-events"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName POINTS = new AttributeName(ALL_NO_NS, SAME_LOCAL("points"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName POINTSATX = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("pointsatx", "pointsAtX"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName POINTSATY = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("pointsaty", "pointsAtY"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName POINTSATZ = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("pointsatz", "pointsAtZ"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SPAN = new AttributeName(ALL_NO_NS, SAME_LOCAL("span"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STANDBY = new AttributeName(ALL_NO_NS, SAME_LOCAL("standby"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName THINMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("thinmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TRANSFORM = new AttributeName(ALL_NO_NS, SAME_LOCAL("transform"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VLINK = new AttributeName(ALL_NO_NS, SAME_LOCAL("vlink"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName WHEN = new AttributeName(ALL_NO_NS, SAME_LOCAL("when"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName XLINK_HREF = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:href", "href"), XLINK_PREFIX, NCNAME_FOREIGN);
+ public static final AttributeName XLINK_TITLE = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:title", "title"), XLINK_PREFIX, NCNAME_FOREIGN);
+ public static final AttributeName XLINK_ROLE = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:role", "role"), XLINK_PREFIX, NCNAME_FOREIGN);
+ public static final AttributeName XLINK_ARCROLE = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:arcrole", "arcrole"), XLINK_PREFIX, NCNAME_FOREIGN);
+ public static final AttributeName XMLNS_XLINK = new AttributeName(XMLNS_NS, COLONIFIED_LOCAL("xmlns:xlink", "xlink"), XMLNS_PREFIX, IS_XMLNS);
+ public static final AttributeName XMLNS = new AttributeName(XMLNS_NS, SAME_LOCAL("xmlns"), ALL_NO_PREFIX, IS_XMLNS);
+ public static final AttributeName XLINK_TYPE = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:type", "type"), XLINK_PREFIX, NCNAME_FOREIGN);
+ public static final AttributeName XLINK_SHOW = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:show", "show"), XLINK_PREFIX, NCNAME_FOREIGN);
+ public static final AttributeName XLINK_ACTUATE = new AttributeName(XLINK_NS, COLONIFIED_LOCAL("xlink:actuate", "actuate"), XLINK_PREFIX, NCNAME_FOREIGN);
+ public static final AttributeName AUTOPLAY = new AttributeName(ALL_NO_NS, SAME_LOCAL("autoplay"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName AUTOSUBMIT = new AttributeName(ALL_NO_NS, SAME_LOCAL("autosubmit"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName AUTOCOMPLETE = new AttributeName(ALL_NO_NS, SAME_LOCAL("autocomplete"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName AUTOFOCUS = new AttributeName(ALL_NO_NS, SAME_LOCAL("autofocus"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName BGCOLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("bgcolor"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLOR_PROFILE = new AttributeName(ALL_NO_NS, SAME_LOCAL("color-profile"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLOR_RENDERING = new AttributeName(ALL_NO_NS, SAME_LOCAL("color-rendering"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLOR_INTERPOLATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("color-interpolation"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("color"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLOR_INTERPOLATION_FILTERS = new AttributeName(ALL_NO_NS, SAME_LOCAL("color-interpolation-filters"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ENCODING = new AttributeName(ALL_NO_NS, SAME_LOCAL("encoding"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName EXPONENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("exponent"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FLOOD_COLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("flood-color"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FLOOD_OPACITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("flood-opacity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName IDEOGRAPHIC = new AttributeName(ALL_NO_NS, SAME_LOCAL("ideographic"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LQUOTE = new AttributeName(ALL_NO_NS, SAME_LOCAL("lquote"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PANOSE_1 = new AttributeName(ALL_NO_NS, SAME_LOCAL("panose-1"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName NUMOCTAVES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("numoctaves", "numOctaves"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName NOMODULE = new AttributeName(ALL_NO_NS, SAME_LOCAL("nomodule"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName ONLOAD = new AttributeName(ALL_NO_NS, SAME_LOCAL("onload"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONBOUNCE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onbounce"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONCONTROLSELECT = new AttributeName(ALL_NO_NS, SAME_LOCAL("oncontrolselect"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONROWSINSERTED = new AttributeName(ALL_NO_NS, SAME_LOCAL("onrowsinserted"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOUSEWHEEL = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmousewheel"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONROWENTER = new AttributeName(ALL_NO_NS, SAME_LOCAL("onrowenter"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOUSEENTER = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmouseenter"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOUSEOVER = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmouseover"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONFORMCHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onformchange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONFOCUSIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("onfocusin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONROWEXIT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onrowexit"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOVEEND = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmoveend"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONCONTEXTMENU = new AttributeName(ALL_NO_NS, SAME_LOCAL("oncontextmenu"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONZOOM = new AttributeName(ALL_NO_NS, SAME_LOCAL("onzoom"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONLOSECAPTURE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onlosecapture"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONCOPY = new AttributeName(ALL_NO_NS, SAME_LOCAL("oncopy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOVESTART = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmovestart"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONROWSDELETE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onrowsdelete"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOUSELEAVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmouseleave"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmove"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOUSEMOVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmousemove"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOUSEUP = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmouseup"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONFOCUS = new AttributeName(ALL_NO_NS, SAME_LOCAL("onfocus"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOUSEOUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmouseout"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONFORMINPUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onforminput"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONFOCUSOUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onfocusout"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONMOUSEDOWN = new AttributeName(ALL_NO_NS, SAME_LOCAL("onmousedown"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TO = new AttributeName(ALL_NO_NS, SAME_LOCAL("to"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName RQUOTE = new AttributeName(ALL_NO_NS, SAME_LOCAL("rquote"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STROKE_LINECAP = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-linecap"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SCROLLDELAY = new AttributeName(ALL_NO_NS, SAME_LOCAL("scrolldelay"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STROKE_DASHARRAY = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-dasharray"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STROKE_DASHOFFSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-dashoffset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STROKE_LINEJOIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-linejoin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STROKE_MITERLIMIT = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-miterlimit"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STROKE = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SCROLLING = new AttributeName(ALL_NO_NS, SAME_LOCAL("scrolling"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName STROKE_WIDTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-width"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STROKE_OPACITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("stroke-opacity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COMPACT = new AttributeName(ALL_NO_NS, SAME_LOCAL("compact"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName CLIP = new AttributeName(ALL_NO_NS, SAME_LOCAL("clip"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CLIP_RULE = new AttributeName(ALL_NO_NS, SAME_LOCAL("clip-rule"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CLIP_PATH = new AttributeName(ALL_NO_NS, SAME_LOCAL("clip-path"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CLIPPATHUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("clippathunits", "clipPathUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DISPLAY = new AttributeName(ALL_NO_NS, SAME_LOCAL("display"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DISPLAYSTYLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("displaystyle"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName GLYPH_ORIENTATION_VERTICAL = new AttributeName(ALL_NO_NS, SAME_LOCAL("glyph-orientation-vertical"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName GLYPH_ORIENTATION_HORIZONTAL = new AttributeName(ALL_NO_NS, SAME_LOCAL("glyph-orientation-horizontal"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName GLYPHREF = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("glyphref", "glyphRef"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName GLYPH_NAME = new AttributeName(ALL_NO_NS, SAME_LOCAL("glyph-name"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName HTTP_EQUIV = new AttributeName(ALL_NO_NS, SAME_LOCAL("http-equiv"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName KEYPOINTS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("keypoints", "keyPoints"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LOOP = new AttributeName(ALL_NO_NS, SAME_LOCAL("loop"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PROPERTY = new AttributeName(ALL_NO_NS, SAME_LOCAL("property"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SCOPED = new AttributeName(ALL_NO_NS, SAME_LOCAL("scoped"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STEP = new AttributeName(ALL_NO_NS, SAME_LOCAL("step"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName SHAPE_RENDERING = new AttributeName(ALL_NO_NS, SAME_LOCAL("shape-rendering"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SCOPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("scope"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName SHAPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("shape"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName SLOPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("slope"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STOP_COLOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("stop-color"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STOP_OPACITY = new AttributeName(ALL_NO_NS, SAME_LOCAL("stop-opacity"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TEMPLATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("template"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName WRAP = new AttributeName(ALL_NO_NS, SAME_LOCAL("wrap"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ABBR = new AttributeName(ALL_NO_NS, SAME_LOCAL("abbr"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ATTRIBUTENAME = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("attributename", "attributeName"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ATTRIBUTETYPE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("attributetype", "attributeType"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CHAR = new AttributeName(ALL_NO_NS, SAME_LOCAL("char"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COORDS = new AttributeName(ALL_NO_NS, SAME_LOCAL("coords"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CHAROFF = new AttributeName(ALL_NO_NS, SAME_LOCAL("charoff"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CHARSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("charset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MACROS = new AttributeName(ALL_NO_NS, SAME_LOCAL("macros"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName NOWRAP = new AttributeName(ALL_NO_NS, SAME_LOCAL("nowrap"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName NOHREF = new AttributeName(ALL_NO_NS, SAME_LOCAL("nohref"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName ONDRAG = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondrag"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDRAGENTER = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragenter"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDRAGOVER = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragover"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONPROPERTYCHANGE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onpropertychange"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDRAGEND = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragend"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDROP = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondrop"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDRAGDROP = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragdrop"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OVERLINE_POSITION = new AttributeName(ALL_NO_NS, SAME_LOCAL("overline-position"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONERROR = new AttributeName(ALL_NO_NS, SAME_LOCAL("onerror"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OPERATOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("operator"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OVERFLOW = new AttributeName(ALL_NO_NS, SAME_LOCAL("overflow"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDRAGSTART = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragstart"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONERRORUPDATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("onerrorupdate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OVERLINE_THICKNESS = new AttributeName(ALL_NO_NS, SAME_LOCAL("overline-thickness"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONDRAGLEAVE = new AttributeName(ALL_NO_NS, SAME_LOCAL("ondragleave"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STARTOFFSET = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("startoffset", "startOffset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName START = new AttributeName(ALL_NO_NS, SAME_LOCAL("start"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName AXIS = new AttributeName(ALL_NO_NS, SAME_LOCAL("axis"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BIAS = new AttributeName(ALL_NO_NS, SAME_LOCAL("bias"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLSPAN = new AttributeName(ALL_NO_NS, SAME_LOCAL("colspan"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CLASSID = new AttributeName(ALL_NO_NS, SAME_LOCAL("classid"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CROSSORIGIN = new AttributeName(ALL_NO_NS, SAME_LOCAL("crossorigin"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLS = new AttributeName(ALL_NO_NS, SAME_LOCAL("cols"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CURSOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("cursor"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CLOSURE = new AttributeName(ALL_NO_NS, SAME_LOCAL("closure"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CLOSE = new AttributeName(ALL_NO_NS, SAME_LOCAL("close"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CLASS = new AttributeName(ALL_NO_NS, SAME_LOCAL("class"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName IS = new AttributeName(ALL_NO_NS, SAME_LOCAL("is"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName KEYSYSTEM = new AttributeName(ALL_NO_NS, SAME_LOCAL("keysystem"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName KEYSPLINES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("keysplines", "keySplines"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LOWSRC = new AttributeName(ALL_NO_NS, SAME_LOCAL("lowsrc"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MAXSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("maxsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MINSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("minsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OFFSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("offset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PRESERVEALPHA = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("preservealpha", "preserveAlpha"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PRESERVEASPECTRATIO = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("preserveaspectratio", "preserveAspectRatio"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ROWSPAN = new AttributeName(ALL_NO_NS, SAME_LOCAL("rowspan"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ROWSPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("rowspacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ROWS = new AttributeName(ALL_NO_NS, SAME_LOCAL("rows"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SRCSET = new AttributeName(ALL_NO_NS, SAME_LOCAL("srcset"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SUBSCRIPTSHIFT = new AttributeName(ALL_NO_NS, SAME_LOCAL("subscriptshift"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VERSION = new AttributeName(ALL_NO_NS, SAME_LOCAL("version"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ALTTEXT = new AttributeName(ALL_NO_NS, SAME_LOCAL("alttext"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CONTENTEDITABLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("contenteditable"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CONTROLS = new AttributeName(ALL_NO_NS, SAME_LOCAL("controls"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CONTENT = new AttributeName(ALL_NO_NS, SAME_LOCAL("content"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CONTEXTMENU = new AttributeName(ALL_NO_NS, SAME_LOCAL("contextmenu"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DEPTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("depth"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ENCTYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("enctype"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName FONT_STRETCH = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-stretch"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FILTER = new AttributeName(ALL_NO_NS, SAME_LOCAL("filter"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FONTWEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("fontweight"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FONT_WEIGHT = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-weight"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FONTSTYLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("fontstyle"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FONT_STYLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-style"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FONTFAMILY = new AttributeName(ALL_NO_NS, SAME_LOCAL("fontfamily"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FONT_FAMILY = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-family"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FONT_VARIANT = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-variant"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FONT_SIZE_ADJUST = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-size-adjust"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FILTERUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("filterunits", "filterUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FONTSIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("fontsize"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FONT_SIZE = new AttributeName(ALL_NO_NS, SAME_LOCAL("font-size"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName KEYTIMES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("keytimes", "keyTimes"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LETTER_SPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("letter-spacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName LIST = new AttributeName(ALL_NO_NS, SAME_LOCAL("list"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName MULTIPLE = new AttributeName(ALL_NO_NS, SAME_LOCAL("multiple"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName RT = new AttributeName(ALL_NO_NS, SAME_LOCAL("rt"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONSTOP = new AttributeName(ALL_NO_NS, SAME_LOCAL("onstop"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONSTART = new AttributeName(ALL_NO_NS, SAME_LOCAL("onstart"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName POSTER = new AttributeName(ALL_NO_NS, SAME_LOCAL("poster"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PATTERNTRANSFORM = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("patterntransform", "patternTransform"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PATTERN = new AttributeName(ALL_NO_NS, SAME_LOCAL("pattern"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PATTERNUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("patternunits", "patternUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName PATTERNCONTENTUNITS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("patterncontentunits", "patternContentUnits"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName RESTART = new AttributeName(ALL_NO_NS, SAME_LOCAL("restart"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName STITCHTILES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("stitchtiles", "stitchTiles"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName SYSTEMLANGUAGE = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("systemlanguage", "systemLanguage"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TEXT_RENDERING = new AttributeName(ALL_NO_NS, SAME_LOCAL("text-rendering"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VERT_ORIGIN_X = new AttributeName(ALL_NO_NS, SAME_LOCAL("vert-origin-x"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VERT_ADV_Y = new AttributeName(ALL_NO_NS, SAME_LOCAL("vert-adv-y"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VERT_ORIGIN_Y = new AttributeName(ALL_NO_NS, SAME_LOCAL("vert-origin-y"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TEXT_DECORATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("text-decoration"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TEXT_ANCHOR = new AttributeName(ALL_NO_NS, SAME_LOCAL("text-anchor"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TEXTLENGTH = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("textlength", "textLength"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName TEXT = new AttributeName(ALL_NO_NS, SAME_LOCAL("text"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName UNITS_PER_EM = new AttributeName(ALL_NO_NS, SAME_LOCAL("units-per-em"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName WRITING_MODE = new AttributeName(ALL_NO_NS, SAME_LOCAL("writing-mode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName WIDTHS = new AttributeName(ALL_NO_NS, SAME_LOCAL("widths"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName WIDTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("width"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ACCUMULATE = new AttributeName(ALL_NO_NS, SAME_LOCAL("accumulate"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLUMNSPAN = new AttributeName(ALL_NO_NS, SAME_LOCAL("columnspan"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLUMNLINES = new AttributeName(ALL_NO_NS, SAME_LOCAL("columnlines"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLUMNALIGN = new AttributeName(ALL_NO_NS, SAME_LOCAL("columnalign"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLUMNSPACING = new AttributeName(ALL_NO_NS, SAME_LOCAL("columnspacing"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName COLUMNWIDTH = new AttributeName(ALL_NO_NS, SAME_LOCAL("columnwidth"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName GROUPALIGN = new AttributeName(ALL_NO_NS, SAME_LOCAL("groupalign"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName INPUTMODE = new AttributeName(ALL_NO_NS, SAME_LOCAL("inputmode"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName OCCURRENCE = new AttributeName(ALL_NO_NS, SAME_LOCAL("occurrence"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONSUBMIT = new AttributeName(ALL_NO_NS, SAME_LOCAL("onsubmit"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ONCUT = new AttributeName(ALL_NO_NS, SAME_LOCAL("oncut"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REQUIRED = new AttributeName(ALL_NO_NS, SAME_LOCAL("required"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED | BOOLEAN);
+ public static final AttributeName REQUIREDFEATURES = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("requiredfeatures", "requiredFeatures"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName RESULT = new AttributeName(ALL_NO_NS, SAME_LOCAL("result"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REQUIREDEXTENSIONS = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("requiredextensions", "requiredExtensions"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VALUES = new AttributeName(ALL_NO_NS, SAME_LOCAL("values"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VALUETYPE = new AttributeName(ALL_NO_NS, SAME_LOCAL("valuetype"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG | CASE_FOLDED);
+ public static final AttributeName VALUE = new AttributeName(ALL_NO_NS, SAME_LOCAL("value"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName ELEVATION = new AttributeName(ALL_NO_NS, SAME_LOCAL("elevation"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VIEWTARGET = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("viewtarget", "viewTarget"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VIEWBOX = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("viewbox", "viewBox"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CX = new AttributeName(ALL_NO_NS, SAME_LOCAL("cx"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DX = new AttributeName(ALL_NO_NS, SAME_LOCAL("dx"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FX = new AttributeName(ALL_NO_NS, SAME_LOCAL("fx"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BBOX = new AttributeName(ALL_NO_NS, SAME_LOCAL("bbox"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName RX = new AttributeName(ALL_NO_NS, SAME_LOCAL("rx"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REFX = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("refx", "refX"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName BY = new AttributeName(ALL_NO_NS, SAME_LOCAL("by"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName CY = new AttributeName(ALL_NO_NS, SAME_LOCAL("cy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName DY = new AttributeName(ALL_NO_NS, SAME_LOCAL("dy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName FY = new AttributeName(ALL_NO_NS, SAME_LOCAL("fy"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName RY = new AttributeName(ALL_NO_NS, SAME_LOCAL("ry"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName REFY = new AttributeName(ALL_NO_NS, SVG_DIFFERENT("refy", "refY"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VERYTHINMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("verythinmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VERYTHICKMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("verythickmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VERYVERYTHINMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("veryverythinmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ public static final AttributeName VERYVERYTHICKMATHSPACE = new AttributeName(ALL_NO_NS, SAME_LOCAL("veryverythickmathspace"), ALL_NO_PREFIX, NCNAME_HTML | NCNAME_FOREIGN | NCNAME_LANG);
+ private final static @NoLength AttributeName[] ATTRIBUTE_NAMES = {
+ STEMV,
+ REPEAT_START,
+ NOWRAP,
+ SRCDOC,
+ BEGIN,
+ ONFORMCHANGE,
+ KEYTIMES,
+ ARIA_SORT,
+ ONSELECT,
+ LANGUAGE,
+ MARKERWIDTH,
+ XMLNS_XLINK,
+ CLIP,
+ LOWSRC,
+ COLUMNWIDTH,
+ XML_SPACE,
+ VSPACE,
+ EDGE,
+ ONDEACTIVATE,
+ X_HEIGHT,
+ LIGHTING_COLOR,
+ SCRIPTLEVEL,
+ SCALE,
+ ONINPUT,
+ EXPONENT,
+ ONFORMINPUT,
+ SHAPE_RENDERING,
+ ONDRAGLEAVE,
+ CONTEXTMENU,
+ VERT_ORIGIN_X,
+ CX,
+ X,
+ ARIA_HIDDEN,
+ HSPACE,
+ SPECULAREXPONENT,
+ BASELINE,
+ LABEL,
+ ONHELP,
+ ONRESIZE,
+ SCHEME,
+ XREF,
+ ORIGIN,
+ ONCHANGE,
+ MEDIUMMATHSPACE,
+ VISIBILITY,
+ FILL_RULE,
+ FRAME,
+ ICON,
+ THINMATHSPACE,
+ AUTOFOCUS,
+ ONLOAD,
+ ONMOVESTART,
+ STROKE_DASHOFFSET,
+ GLYPHREF,
+ ABBR,
+ ONDRAGDROP,
+ COLS,
+ ROWS,
+ FONT_STYLE,
+ PATTERNTRANSFORM,
+ WRITING_MODE,
+ RESULT,
+ DY,
+ MIN,
+ Y1,
+ ARIA_CHECKED,
+ ARIA_CONTROLS,
+ DATAFORMATAS,
+ ONPASTE,
+ CALCMODE,
+ ID,
+ ACCENT_HEIGHT,
+ DEFER,
+ IRRELEVANT,
+ NORESIZE,
+ ONCELLCHANGE,
+ ONBEFORECOPY,
+ ONKEYUP,
+ RULES,
+ SPEED,
+ TYPE,
+ ONAFTERPRINT,
+ DRAGGABLE,
+ LENGTHADJUST,
+ TARGETY,
+ MATHVARIANT,
+ ACTIONTYPE,
+ HORIZ_ADV_X,
+ ONFINISH,
+ STRIKETHROUGH_THICKNESS,
+ MARKERHEIGHT,
+ AMPLITUDE,
+ ONCLICK,
+ V_ALPHABETIC,
+ PROMPT,
+ ZOOMANDPAN,
+ ONUNLOAD,
+ POINTSATY,
+ XLINK_HREF,
+ XLINK_ACTUATE,
+ COLOR_INTERPOLATION,
+ LQUOTE,
+ ONMOUSEWHEEL,
+ ONCONTEXTMENU,
+ ONMOUSEMOVE,
+ RQUOTE,
+ SCROLLING,
+ DISPLAY,
+ LOOP,
+ STOP_COLOR,
+ COORDS,
+ ONDRAGOVER,
+ OVERFLOW,
+ BIAS,
+ CLASS,
+ PRESERVEALPHA,
+ ALTTEXT,
+ FILTER,
+ FONT_SIZE_ADJUST,
+ RT,
+ RESTART,
+ TEXT_ANCHOR,
+ COLUMNSPAN,
+ ONSUBMIT,
+ VALUE,
+ RX,
+ VERYTHINMATHSPACE,
+ END,
+ SRC,
+ G1,
+ X2,
+ ARIA_VALUEMAX,
+ ARIA_EXPANDED,
+ ARIA_INVALID,
+ ARIA_ACTIVEDESCENDANT,
+ ARIA_LIVE,
+ DATASRC,
+ MOVABLELIMITS,
+ ROTATE,
+ ARABIC_FORM,
+ ONSCROLL,
+ UNICODE,
+ HEADERS,
+ WORD_SPACING,
+ BEVELLED,
+ CODEBASE,
+ DIRECTION,
+ HIDEFOCUS,
+ INTEGRITY,
+ MODE,
+ ONREPEAT,
+ OTHER,
+ ONMESSAGE,
+ ORIENT,
+ ONBEFOREPASTE,
+ ONBEFORDEACTIVATE,
+ ONBEFORECUT,
+ REPEAT_MAX,
+ ROLE,
+ REPEATDUR,
+ SUPERSCRIPTSHIFT,
+ SELECTION,
+ UNDERLINE_POSITION,
+ HREF,
+ PROFILE,
+ ALIGNMENT_BASELINE,
+ HANGING,
+ LARGEOP,
+ MARGINWIDTH,
+ TARGET,
+ ARCHIVE,
+ MATHBACKGROUND,
+ MATHSIZE,
+ PATH,
+ ACTIVE,
+ DIVISOR,
+ HORIZ_ORIGIN_Y,
+ MANIFEST,
+ RADIOGROUP,
+ STRING,
+ TABINDEX,
+ LINK,
+ MASK,
+ MARKERUNITS,
+ CELLPADDING,
+ FILL_OPACITY,
+ REPLACE,
+ TABLEVALUES,
+ FORMAT,
+ FROM,
+ SYMMETRIC,
+ SUMMARY,
+ ALINK,
+ KERNING,
+ ONINVALID,
+ POINTS,
+ SPAN,
+ VLINK,
+ XLINK_ROLE,
+ XLINK_TYPE,
+ AUTOSUBMIT,
+ COLOR_PROFILE,
+ COLOR_INTERPOLATION_FILTERS,
+ FLOOD_OPACITY,
+ NUMOCTAVES,
+ ONCONTROLSELECT,
+ ONMOUSEENTER,
+ ONROWEXIT,
+ ONLOSECAPTURE,
+ ONMOUSELEAVE,
+ ONFOCUS,
+ ONMOUSEDOWN,
+ SCROLLDELAY,
+ STROKE_MITERLIMIT,
+ STROKE_OPACITY,
+ CLIP_PATH,
+ GLYPH_ORIENTATION_VERTICAL,
+ HTTP_EQUIV,
+ SCOPED,
+ SHAPE,
+ TEMPLATE,
+ ATTRIBUTETYPE,
+ CHARSET,
+ ONDRAG,
+ ONDRAGEND,
+ ONERROR,
+ ONERRORUPDATE,
+ START,
+ CLASSID,
+ CLOSURE,
+ KEYSYSTEM,
+ MINSIZE,
+ ROWSPAN,
+ SUBSCRIPTSHIFT,
+ CONTROLS,
+ ENCTYPE,
+ FONT_WEIGHT,
+ FONT_FAMILY,
+ FONTSIZE,
+ LIST,
+ ONSTART,
+ PATTERNUNITS,
+ SYSTEMLANGUAGE,
+ VERT_ORIGIN_Y,
+ TEXT,
+ WIDTH,
+ COLUMNALIGN,
+ INPUTMODE,
+ REQUIRED,
+ VALUES,
+ VIEWTARGET,
+ FX,
+ BY,
+ RY,
+ VERYVERYTHINMATHSPACE,
+ DIR,
+ IN2,
+ REL,
+ K,
+ Z,
+ U1,
+ K2,
+ K3,
+ XML_BASE,
+ ARIA_DESCRIBEDBY,
+ ARIA_DROPEFFECT,
+ ARIA_LEVEL,
+ ARIA_POSINSET,
+ ARIA_VALUEMIN,
+ ARIA_READONLY,
+ ARIA_DATATYPE,
+ ARIA_FLOWTO,
+ ARIA_SETSIZE,
+ DATAFLD,
+ EQUALCOLUMNS,
+ LOCAL,
+ ONDATASETCHANGED,
+ RSPACE,
+ SEPARATORS,
+ XCHANNELSELECTOR,
+ ONDBLCLICK,
+ DESCENT,
+ OPACITY,
+ SPECIFICATION,
+ UNICODE_RANGE,
+ GRADIENTUNITS,
+ RENDERING_INTENT,
+ SANDBOX,
+ ACCEPT_CHARSET,
+ ASCENT,
+ BASELINE_SHIFT,
+ CODE,
+ CITE,
+ DATETIME,
+ EDGEMODE,
+ FACE,
+ INDEX,
+ INTERCEPT,
+ LINEBREAK,
+ LINETHICKNESS,
+ NAME,
+ ONBEFOREUNLOAD,
+ OBJECT,
+ ORDER,
+ ONRESET,
+ ONREADYSTATECHANGE,
+ ONBEGIN,
+ ONBEFOREPRINT,
+ ORIENTATION,
+ ONSELECTSTART,
+ ONBEFOREUPDATE,
+ ONBEFOREACTIVATE,
+ ONKEYPRESS,
+ ONBEFOREEDITFOCUS,
+ ONKEYDOWN,
+ REPEAT,
+ REFERRERPOLICY,
+ REPEAT_MIN,
+ REPEATCOUNT,
+ REPEAT_TEMPLATE,
+ SELECTED,
+ SIZES,
+ STRETCHY,
+ SPREADMETHOD,
+ SIZE,
+ UNSELECTABLE,
+ UNDERLINE_THICKNESS,
+ DIFFUSECONSTANT,
+ HREFLANG,
+ ONAFTERUPDATE,
+ SURFACESCALE,
+ ALIGN,
+ ALIGNMENTSCOPE,
+ HEIGHT,
+ IMAGE_RENDERING,
+ LANG,
+ LONGDESC,
+ MARGINHEIGHT,
+ NARGS,
+ PING,
+ TARGETX,
+ ALPHABETIC,
+ HIGH,
+ MATHEMATICAL,
+ METHOD,
+ MATHCOLOR,
+ NOSHADE,
+ PATHLENGTH,
+ ALTIMG,
+ ACTION,
+ ADDITIVE,
+ DOMINANT_BASELINE,
+ DEFINITIONURL,
+ HORIZ_ORIGIN_X,
+ LIMITINGCONEANGLE,
+ MEDIA,
+ ONFILTERCHANGE,
+ OPTIMUM,
+ RADIUS,
+ SCRIPTSIZEMULTIPLIER,
+ STRIKETHROUGH_POSITION,
+ SCRIPTMINSIZE,
+ VALIGN,
+ BACKGROUND,
+ MARKER_MID,
+ MARKER_END,
+ MARKER_START,
+ MASKUNITS,
+ MASKCONTENTUNITS,
+ CELLSPACING,
+ DECLARE,
+ FILL,
+ MAXLENGTH,
+ ONBLUR,
+ ROWLINES,
+ STYLE,
+ TITLE,
+ AZIMUTH,
+ FRAMEBORDER,
+ FRAMESPACING,
+ FORM,
+ PRIMITIVEUNITS,
+ STEMH,
+ SEAMLESS,
+ USEMAP,
+ ASYNC,
+ IN,
+ KERNELMATRIX,
+ KERNELUNITLENGTH,
+ OPEN,
+ ONEND,
+ POINTER_EVENTS,
+ POINTSATX,
+ POINTSATZ,
+ STANDBY,
+ TRANSFORM,
+ WHEN,
+ XLINK_TITLE,
+ XLINK_ARCROLE,
+ XMLNS,
+ XLINK_SHOW,
+ AUTOPLAY,
+ AUTOCOMPLETE,
+ BGCOLOR,
+ COLOR_RENDERING,
+ COLOR,
+ ENCODING,
+ FLOOD_COLOR,
+ IDEOGRAPHIC,
+ PANOSE_1,
+ NOMODULE,
+ ONBOUNCE,
+ ONROWSINSERTED,
+ ONROWENTER,
+ ONMOUSEOVER,
+ ONFOCUSIN,
+ ONMOVEEND,
+ ONZOOM,
+ ONCOPY,
+ ONROWSDELETE,
+ ONMOVE,
+ ONMOUSEUP,
+ ONMOUSEOUT,
+ ONFOCUSOUT,
+ TO,
+ STROKE_LINECAP,
+ STROKE_DASHARRAY,
+ STROKE_LINEJOIN,
+ STROKE,
+ STROKE_WIDTH,
+ COMPACT,
+ CLIP_RULE,
+ CLIPPATHUNITS,
+ DISPLAYSTYLE,
+ GLYPH_ORIENTATION_HORIZONTAL,
+ GLYPH_NAME,
+ KEYPOINTS,
+ PROPERTY,
+ STEP,
+ SCOPE,
+ SLOPE,
+ STOP_OPACITY,
+ WRAP,
+ ATTRIBUTENAME,
+ CHAR,
+ CHAROFF,
+ MACROS,
+ NOHREF,
+ ONDRAGENTER,
+ ONPROPERTYCHANGE,
+ ONDROP,
+ OVERLINE_POSITION,
+ OPERATOR,
+ ONDRAGSTART,
+ OVERLINE_THICKNESS,
+ STARTOFFSET,
+ AXIS,
+ COLSPAN,
+ CROSSORIGIN,
+ CURSOR,
+ CLOSE,
+ IS,
+ KEYSPLINES,
+ MAXSIZE,
+ OFFSET,
+ PRESERVEASPECTRATIO,
+ ROWSPACING,
+ SRCSET,
+ VERSION,
+ CONTENTEDITABLE,
+ CONTENT,
+ DEPTH,
+ FONT_STRETCH,
+ FONTWEIGHT,
+ FONTSTYLE,
+ FONTFAMILY,
+ FONT_VARIANT,
+ FILTERUNITS,
+ FONT_SIZE,
+ LETTER_SPACING,
+ MULTIPLE,
+ ONSTOP,
+ POSTER,
+ PATTERN,
+ PATTERNCONTENTUNITS,
+ STITCHTILES,
+ TEXT_RENDERING,
+ VERT_ADV_Y,
+ TEXT_DECORATION,
+ TEXTLENGTH,
+ UNITS_PER_EM,
+ WIDTHS,
+ ACCUMULATE,
+ COLUMNLINES,
+ COLUMNSPACING,
+ GROUPALIGN,
+ OCCURRENCE,
+ ONCUT,
+ REQUIREDFEATURES,
+ REQUIREDEXTENSIONS,
+ VALUETYPE,
+ ELEVATION,
+ VIEWBOX,
+ DX,
+ BBOX,
+ REFX,
+ CY,
+ FY,
+ REFY,
+ VERYTHICKMATHSPACE,
+ VERYVERYTHICKMATHSPACE,
+ ALT,
+ DUR,
+ FOR,
+ LOW,
+ MAX,
+ REV,
+ D,
+ R,
+ Y,
+ CAP_HEIGHT,
+ K1,
+ X1,
+ G2,
+ U2,
+ Y2,
+ K4,
+ XML_LANG,
+ ARIA_GRAB,
+ ARIA_LABELLEDBY,
+ ARIA_DISABLED,
+ ARIA_SELECTED,
+ ARIA_REQUIRED,
+ ARIA_PRESSED,
+ ARIA_CHANNEL,
+ ARIA_SECRET,
+ ARIA_ATOMIC,
+ ARIA_TEMPLATEID,
+ ARIA_MULTISELECTABLE,
+ ARIA_MULTILINE,
+ ARIA_OWNS,
+ ARIA_RELEVANT,
+ ARIA_VALUENOW,
+ ARIA_AUTOCOMPLETE,
+ ARIA_BUSY,
+ ARIA_HASPOPUP,
+ CLEAR,
+ DISABLED,
+ DEFAULT,
+ DATA,
+ EQUALROWS,
+ ISMAP,
+ LSPACE,
+ NOTATION,
+ ONDATAAVAILABLE,
+ ONDATASETCOMPLETE,
+ ROWALIGN,
+ SEPARATOR,
+ V_MATHEMATICAL,
+ V_HANGING,
+ YCHANNELSELECTOR,
+ ENABLE_BACKGROUND,
+ ONABORT,
+ CHECKED,
+ FENCE,
+ ONACTIVATE,
+ SPACING,
+ SPECULARCONSTANT,
+ THICKMATHSPACE,
+ UNICODE_BIDI,
+ BORDER,
+ GRADIENTTRANSFORM,
+ HIDDEN,
+ READONLY,
+ SEED,
+ STDDEVIATION,
+ V_IDEOGRAPHIC,
+ ACCENTUNDER,
+ ACCESSKEY,
+ ACCENT,
+ ACCEPT,
+ BASEFREQUENCY,
+ BASEPROFILE,
+ BASE,
+ CODETYPE,
+ };
+ private final static int[] ATTRIBUTE_HASHES = {
+ 1891098437,
+ 1756426572,
+ 1972151670,
+ 1740096054,
+ 1814986837,
+ 1922419228,
+ 2004199576,
+ 1680411449,
+ 1754612424,
+ 1786622296,
+ 1854474395,
+ 1910487243,
+ 1932959284,
+ 1988132214,
+ 2017010843,
+ 1037879561,
+ 1691145478,
+ 1749399124,
+ 1754860396,
+ 1759379608,
+ 1803561214,
+ 1823829083,
+ 1874261045,
+ 1905902311,
+ 1917327080,
+ 1922679531,
+ 1941409583,
+ 1972996699,
+ 2000162011,
+ 2009059485,
+ 2065170434,
+ 71303169,
+ 1680185931,
+ 1683805446,
+ 1723336432,
+ 1747939528,
+ 1753049109,
+ 1754751622,
+ 1754958648,
+ 1756836998,
+ 1776114564,
+ 1788254870,
+ 1804978712,
+ 1820637455,
+ 1825677514,
+ 1867448617,
+ 1884246821,
+ 1902640276,
+ 1908195085,
+ 1915341049,
+ 1922319046,
+ 1922630475,
+ 1924517489,
+ 1934970504,
+ 1965349396,
+ 1972904522,
+ 1983347764,
+ 1991392548,
+ 2001669450,
+ 2007019632,
+ 2010452700,
+ 2024763702,
+ 2082471938,
+ 57205395,
+ 885522434,
+ 1680165436,
+ 1680311085,
+ 1681694748,
+ 1687751191,
+ 1714745560,
+ 1732771842,
+ 1747348637,
+ 1748869205,
+ 1751649130,
+ 1754434872,
+ 1754647068,
+ 1754835516,
+ 1754899031,
+ 1756219733,
+ 1756710661,
+ 1757421892,
+ 1771569964,
+ 1782518297,
+ 1786851500,
+ 1791070327,
+ 1804069019,
+ 1814558026,
+ 1817175115,
+ 1821958888,
+ 1824081655,
+ 1854302364,
+ 1864698185,
+ 1872034503,
+ 1875753052,
+ 1889569526,
+ 1894552650,
+ 1905541832,
+ 1906421049,
+ 1910328970,
+ 1910572893,
+ 1916278099,
+ 1921061206,
+ 1922400908,
+ 1922566877,
+ 1922665179,
+ 1924206934,
+ 1924629705,
+ 1933369607,
+ 1937777860,
+ 1941454586,
+ 1966439670,
+ 1972744954,
+ 1972922984,
+ 1982640164,
+ 1983461061,
+ 1990062797,
+ 1999273799,
+ 2001578182,
+ 2001814704,
+ 2005925890,
+ 2008084807,
+ 2009079867,
+ 2016711994,
+ 2023146024,
+ 2026975253,
+ 2073034754,
+ 2093791505,
+ 53006051,
+ 60345635,
+ 876085250,
+ 901775362,
+ 1680140893,
+ 1680165613,
+ 1680230940,
+ 1680345685,
+ 1680446153,
+ 1681940503,
+ 1686731997,
+ 1689324870,
+ 1697174123,
+ 1721189160,
+ 1724189239,
+ 1734404167,
+ 1742183484,
+ 1747792072,
+ 1748552744,
+ 1749027145,
+ 1751232761,
+ 1751755561,
+ 1753550036,
+ 1754579720,
+ 1754644293,
+ 1754647353,
+ 1754794646,
+ 1754860061,
+ 1754860401,
+ 1754907227,
+ 1756155098,
+ 1756302628,
+ 1756471625,
+ 1756762256,
+ 1756889417,
+ 1757942610,
+ 1767725700,
+ 1772032615,
+ 1780975314,
+ 1784643703,
+ 1786775671,
+ 1787365531,
+ 1790814502,
+ 1797886599,
+ 1804036350,
+ 1804235064,
+ 1805715716,
+ 1814656326,
+ 1816144023,
+ 1817177246,
+ 1820928104,
+ 1823574314,
+ 1823975206,
+ 1824377064,
+ 1853862084,
+ 1854464212,
+ 1854497003,
+ 1865910347,
+ 1867620412,
+ 1873590471,
+ 1874698443,
+ 1884079398,
+ 1884295780,
+ 1890996553,
+ 1891186903,
+ 1898428101,
+ 1903659239,
+ 1905672729,
+ 1906408598,
+ 1907660596,
+ 1909438149,
+ 1910441770,
+ 1910507338,
+ 1915146282,
+ 1916210285,
+ 1916337499,
+ 1917953597,
+ 1921894426,
+ 1922384591,
+ 1922413292,
+ 1922482777,
+ 1922599757,
+ 1922665052,
+ 1922677495,
+ 1922699851,
+ 1924453467,
+ 1924583073,
+ 1924773438,
+ 1933123337,
+ 1934917290,
+ 1935597338,
+ 1941253366,
+ 1941438085,
+ 1942026440,
+ 1965561677,
+ 1966454567,
+ 1972656710,
+ 1972863609,
+ 1972908839,
+ 1972963917,
+ 1975062341,
+ 1983266615,
+ 1983416119,
+ 1987410233,
+ 1988788535,
+ 1991021879,
+ 1991643278,
+ 2000125224,
+ 2001210183,
+ 2001634459,
+ 2001710299,
+ 2001898808,
+ 2004957380,
+ 2006516551,
+ 2007064812,
+ 2008408414,
+ 2009061533,
+ 2009231684,
+ 2010716309,
+ 2016810187,
+ 2019887833,
+ 2024616088,
+ 2026741958,
+ 2060302634,
+ 2066743298,
+ 2081423362,
+ 2089811970,
+ 2093791509,
+ 52488851,
+ 55077603,
+ 59825747,
+ 64487425,
+ 72351745,
+ 883425282,
+ 894959618,
+ 911736834,
+ 1038141480,
+ 1680159328,
+ 1680165487,
+ 1680181850,
+ 1680198381,
+ 1680251485,
+ 1680323325,
+ 1680347981,
+ 1680433915,
+ 1680511804,
+ 1681844247,
+ 1682440540,
+ 1685882101,
+ 1687503600,
+ 1689048326,
+ 1689839946,
+ 1692408896,
+ 1704262346,
+ 1715466295,
+ 1721347639,
+ 1723340621,
+ 1724238365,
+ 1733919469,
+ 1739583824,
+ 1740130375,
+ 1747299630,
+ 1747455030,
+ 1747839118,
+ 1748306996,
+ 1748566068,
+ 1748971848,
+ 1749350104,
+ 1749856356,
+ 1751507685,
+ 1751679545,
+ 1752985897,
+ 1753297133,
+ 1754214628,
+ 1754546894,
+ 1754606246,
+ 1754643237,
+ 1754645079,
+ 1754647074,
+ 1754698327,
+ 1754792749,
+ 1754798923,
+ 1754858317,
+ 1754860110,
+ 1754860400,
+ 1754872618,
+ 1754905345,
+ 1754927689,
+ 1756147974,
+ 1756190926,
+ 1756265690,
+ 1756360955,
+ 1756428495,
+ 1756704824,
+ 1756737685,
+ 1756804936,
+ 1756874572,
+ 1757053236,
+ 1757874716,
+ 1758018291,
+ 1765800271,
+ 1767875272,
+ 1771637325,
+ 1773606972,
+ 1780879045,
+ 1781007934,
+ 1784574102,
+ 1785174319,
+ 1786740932,
+ 1786821704,
+ 1787193500,
+ 1787699221,
+ 1788842244,
+ 1791068279,
+ 1797666394,
+ 1801312388,
+ 1803839644,
+ 1804054854,
+ 1804081401,
+ 1804405895,
+ 1805715690,
+ 1814517574,
+ 1814560070,
+ 1814656840,
+ 1816104145,
+ 1816178925,
+ 1817175198,
+ 1820262641,
+ 1820727381,
+ 1821755934,
+ 1822002839,
+ 1823580230,
+ 1823841492,
+ 1824005974,
+ 1824159037,
+ 1825437894,
+ 1848600826,
+ 1854285018,
+ 1854366938,
+ 1854466380,
+ 1854497001,
+ 1854497008,
+ 1865910331,
+ 1866496199,
+ 1867462756,
+ 1871251689,
+ 1872343590,
+ 1873656984,
+ 1874270021,
+ 1874788501,
+ 1881750231,
+ 1884142379,
+ 1884267068,
+ 1884343396,
+ 1889633006,
+ 1891069765,
+ 1891182792,
+ 1891937366,
+ 1898415413,
+ 1900544002,
+ 1903612236,
+ 1903759600,
+ 1905628916,
+ 1905754853,
+ 1906408542,
+ 1906419001,
+ 1906423097,
+ 1907701479,
+ 1908462185,
+ 1909819252,
+ 1910441627,
+ 1910441773,
+ 1910503637,
+ 1910527802,
+ 1915025672,
+ 1915295948,
+ 1915757815,
+ 1916247343,
+ 1916286197,
+ 1917295176,
+ 1917857531,
+ 1919297291,
+ 1921880376,
+ 1921977416,
+ 1922354008,
+ 1922384686,
+ 1922413290,
+ 1922413307,
+ 1922470745,
+ 1922531929,
+ 1922567078,
+ 1922607670,
+ 1922632396,
+ 1922665174,
+ 1922671417,
+ 1922679386,
+ 1922679610,
+ 1923088386,
+ 1924443742,
+ 1924462384,
+ 1924570799,
+ 1924585254,
+ 1924738716,
+ 1932870919,
+ 1932986153,
+ 1933145837,
+ 1933508940,
+ 1934917372,
+ 1935099626,
+ 1937336473,
+ 1939976792,
+ 1941286708,
+ 1941435445,
+ 1941440197,
+ 1941550652,
+ 1943317364,
+ 1965512429,
+ 1966384692,
+ 1966442279,
+ 1971855414,
+ 1972196486,
+ 1972744939,
+ 1972750880,
+ 1972904518,
+ 1972904785,
+ 1972909592,
+ 1972962123,
+ 1972980466,
+ 1974849131,
+ 1982254612,
+ 1983157559,
+ 1983290011,
+ 1983398182,
+ 1983432389,
+ 1984430082,
+ 1987422362,
+ 1988784439,
+ 1989522022,
+ 1990107683,
+ 1991220282,
+ 1991625270,
+ 1993343287,
+ 2000096287,
+ 2000160071,
+ 2000752725,
+ 2001527900,
+ 2001634458,
+ 2001669449,
+ 2001710298,
+ 2001732764,
+ 2001826027,
+ 2001898809,
+ 2004846654,
+ 2005342360,
+ 2006459190,
+ 2006824246,
+ 2007021895,
+ 2007064819,
+ 2008401563,
+ 2009041198,
+ 2009061450,
+ 2009071951,
+ 2009141482,
+ 2009434924,
+ 2010542150,
+ 2015950026,
+ 2016787611,
+ 2016910397,
+ 2018908874,
+ 2023011418,
+ 2023342821,
+ 2024647008,
+ 2024794274,
+ 2026893641,
+ 2034765641,
+ 2060474743,
+ 2065694722,
+ 2066762276,
+ 2075005220,
+ 2081947650,
+ 2083520514,
+ 2091784484,
+ 2093791506,
+ 2093791510,
+ 50917059,
+ 52489043,
+ 53537523,
+ 56685811,
+ 57210387,
+ 59830867,
+ 60817409,
+ 68157441,
+ 71827457,
+ 808872090,
+ 878182402,
+ 884998146,
+ 892862466,
+ 900202498,
+ 902299650,
+ 928514050,
+ 1038063816,
+ 1680095865,
+ 1680159327,
+ 1680165421,
+ 1680165437,
+ 1680165533,
+ 1680165692,
+ 1680181996,
+ 1680198203,
+ 1680229115,
+ 1680231247,
+ 1680282148,
+ 1680315086,
+ 1680343801,
+ 1680345965,
+ 1680368221,
+ 1680413393,
+ 1680437801,
+ 1680452349,
+ 1681174213,
+ 1681733672,
+ 1681879063,
+ 1681969220,
+ 1682587945,
+ 1684319541,
+ 1685902598,
+ 1687164232,
+ 1687620127,
+ 1687751377,
+ 1689130184,
+ 1689788441,
+ 1691091102,
+ 1691293817,
+ 1692933184,
+ 1699185409,
+ 1704526375,
+ 1714763319,
+ 1716303957,
+ 1721305962,
+ 1723309623,
+ 1723336528,
+ 1723645710,
+ 1724197420,
+ 1731048742,
+ 1733874289,
+ 1734182982,
+ 1739561208,
+ 1739927860,
+ 1740119884,
+ 1741535501,
+ 1747295467,
+ 1747309881,
+ 1747446838,
+ 1747479606,
+ 1747800157,
+ 1747906667,
+ 1748021284,
+ 1748503880,
+ };
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/CoalescingTreeBuilder.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/CoalescingTreeBuilder.java
new file mode 100644
index 000000000..3d87422aa
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/CoalescingTreeBuilder.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import org.xml.sax.SAXException;
+
+import nu.validator.htmlparser.annotation.NoLength;
+
+/**
+ * A common superclass for tree builders that coalesce their text nodes.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public abstract class CoalescingTreeBuilder<T> extends TreeBuilder<T> {
+
+ protected final void accumulateCharacters(@NoLength char[] buf, int start,
+ int length) throws SAXException {
+ System.arraycopy(buf, start, charBuffer, charBufferLen, length);
+ charBufferLen += length;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#appendCharacters(java.lang.Object, char[], int, int)
+ */
+ @Override protected final void appendCharacters(T parent, char[] buf, int start,
+ int length) throws SAXException {
+ appendCharacters(parent, new String(buf, start, length));
+ }
+
+ protected abstract void appendCharacters(T parent, String text) throws SAXException;
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#appendComment(java.lang.Object, char[], int, int)
+ */
+ @Override final protected void appendComment(T parent, char[] buf, int start,
+ int length) throws SAXException {
+ appendComment(parent, new String(buf, start, length));
+ }
+
+ protected abstract void appendComment(T parent, String comment) throws SAXException;
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#appendCommentToDocument(char[], int, int)
+ */
+ @Override protected final void appendCommentToDocument(char[] buf, int start,
+ int length) throws SAXException {
+ // TODO Auto-generated method stub
+ appendCommentToDocument(new String(buf, start, length));
+ }
+
+ protected abstract void appendCommentToDocument(String comment) throws SAXException;
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#insertFosterParentedCharacters(char[], int, int, java.lang.Object, java.lang.Object)
+ */
+ @Override protected final void insertFosterParentedCharacters(char[] buf, int start,
+ int length, T table, T stackParent) throws SAXException {
+ insertFosterParentedCharacters(new String(buf, start, length), table, stackParent);
+ }
+
+ protected abstract void insertFosterParentedCharacters(String text, T table, T stackParent) throws SAXException;
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/ElementName.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/ElementName.java
new file mode 100644
index 000000000..c888cdae1
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/ElementName.java
@@ -0,0 +1,3068 @@
+/*
+ * Copyright (c) 2008-2017 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+// uncomment to regenerate self
+//import java.io.BufferedReader;
+//import java.io.File;
+//import java.io.FileInputStream;
+//import java.io.IOException;
+//import java.io.InputStreamReader;
+//import java.util.Arrays;
+//import java.util.Collections;
+//import java.util.HashMap;
+//import java.util.LinkedList;
+//import java.util.List;
+//import java.util.Map;
+//import java.util.Map.Entry;
+//import java.util.regex.Matcher;
+//import java.util.regex.Pattern;
+
+import nu.validator.htmlparser.annotation.Inline;
+import nu.validator.htmlparser.annotation.Local;
+import nu.validator.htmlparser.annotation.NoLength;
+import nu.validator.htmlparser.annotation.Unsigned;
+import nu.validator.htmlparser.common.Interner;
+
+public final class ElementName
+// uncomment when regenerating self
+// implements Comparable<ElementName>
+{
+
+ /**
+ * The mask for extracting the dispatch group.
+ */
+ public static final int GROUP_MASK = 127;
+
+ /**
+ * Indicates that the element is not a pre-interned element. Forbidden on
+ * preinterned elements.
+ */
+ public static final int NOT_INTERNED = (1 << 30);
+
+ /**
+ * Indicates that the element is in the "special" category. This bit should
+ * not be pre-set on MathML or SVG specials--only on HTML specials.
+ */
+ public static final int SPECIAL = (1 << 29);
+
+ /**
+ * The element is foster-parenting. This bit should be pre-set on elements
+ * that are foster-parenting as HTML.
+ */
+ public static final int FOSTER_PARENTING = (1 << 28);
+
+ /**
+ * The element is scoping. This bit should be pre-set on elements that are
+ * scoping as HTML.
+ */
+ public static final int SCOPING = (1 << 27);
+
+ /**
+ * The element is scoping as SVG.
+ */
+ public static final int SCOPING_AS_SVG = (1 << 26);
+
+ /**
+ * The element is scoping as MathML.
+ */
+ public static final int SCOPING_AS_MATHML = (1 << 25);
+
+ /**
+ * The element is an HTML integration point.
+ */
+ public static final int HTML_INTEGRATION_POINT = (1 << 24);
+
+ /**
+ * The element has an optional end tag.
+ */
+ public static final int OPTIONAL_END_TAG = (1 << 23);
+
+ private @Local String name;
+
+ private @Local String camelCaseName;
+
+ // CPPONLY: private @HtmlCreator Object htmlCreator;
+
+ // CPPONLY: private @SvgCreator Object svgCreator;
+
+ /**
+ * The lowest 7 bits are the dispatch group. The high bits are flags.
+ */
+ public final int flags;
+
+ @Inline public @Local String getName() {
+ return name;
+ }
+
+ @Inline public @Local String getCamelCaseName() {
+ return camelCaseName;
+ }
+
+ // CPPONLY: @Inline public @HtmlCreator Object getHtmlCreator() {
+ // CPPONLY: return htmlCreator;
+ // CPPONLY: }
+
+ // CPPONLY: @Inline public @SvgCreator Object getSvgCreator() {
+ // CPPONLY: return svgCreator;
+ // CPPONLY: }
+
+ @Inline public int getFlags() {
+ return flags;
+ }
+
+ @Inline public int getGroup() {
+ return flags & ElementName.GROUP_MASK;
+ }
+
+ @Inline public boolean isInterned() {
+ return (flags & ElementName.NOT_INTERNED) == 0;
+ }
+
+ @Inline static int levelOrderBinarySearch(int[] data, int key) {
+ int n = data.length;
+ int i = 0;
+
+ while (i < n) {
+ int val = data[i];
+ if (val < key) {
+ i = 2 * i + 2;
+ } else if (val > key) {
+ i = 2 * i + 1;
+ } else {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ @Inline static ElementName elementNameByBuffer(@NoLength char[] buf,
+ int offset, int length, Interner interner) {
+ @Unsigned int hash = ElementName.bufToHash(buf, length);
+ int[] hashes;
+ hashes = ElementName.ELEMENT_HASHES;
+ int index = levelOrderBinarySearch(hashes, hash);
+ if (index < 0) {
+ return null;
+ } else {
+ ElementName elementName = ElementName.ELEMENT_NAMES[index];
+ @Local String name = elementName.name;
+ if (!Portability.localEqualsBuffer(name, buf, offset, length)) {
+ return null;
+ }
+ return elementName;
+ }
+ }
+
+ /**
+ * This method has to return a unique positive integer for each well-known
+ * lower-cased element name.
+ *
+ * @param buf
+ * @param len
+ * @return
+ */
+ @Inline private static @Unsigned int bufToHash(@NoLength char[] buf,
+ int length) {
+ @Unsigned int len = length;
+ @Unsigned int first = buf[0];
+ first <<= 19;
+ @Unsigned int second = 1 << 23;
+ @Unsigned int third = 0;
+ @Unsigned int fourth = 0;
+ @Unsigned int fifth = 0;
+ if (length >= 4) {
+ second = buf[length - 4];
+ second <<= 4;
+ third = buf[length - 3];
+ third <<= 9;
+ fourth = buf[length - 2];
+ fourth <<= 14;
+ fifth = buf[length - 1];
+ fifth <<= 24;
+ } else if (length == 3) {
+ second = buf[1];
+ second <<= 4;
+ third = buf[2];
+ third <<= 9;
+ } else if (length == 2) {
+ second = buf[1];
+ second <<= 24;
+ }
+ return len + first + second + third + fourth + fifth;
+ }
+
+ private ElementName(@Local String name, @Local String camelCaseName,
+ // CPPONLY: @HtmlCreator Object htmlCreator, @SvgCreator Object
+ // CPPONLY: svgCreator,
+ int flags) {
+ this.name = name;
+ this.camelCaseName = camelCaseName;
+ // CPPONLY: this.htmlCreator = htmlCreator;
+ // CPPONLY: this.svgCreator = svgCreator;
+ this.flags = flags;
+ }
+
+ public ElementName() {
+ this.name = null;
+ this.camelCaseName = null;
+ // CPPONLY: this.htmlCreator = NS_NewHTMLUnknownElement;
+ // CPPONLY: this.svgCreator = NS_NewSVGUnknownElement;
+ this.flags = TreeBuilder.OTHER | NOT_INTERNED;
+ }
+
+ public void destructor() {
+ // The translator adds refcount debug code here.
+ }
+
+ @Inline public void setNameForNonInterned(@Local String name
+ // CPPONLY: , boolean custom
+ ) {
+ // No need to worry about refcounting the local name, because in the
+ // C++ case the scoped atom table remembers its own atoms.
+ this.name = name;
+ this.camelCaseName = name;
+ // CPPONLY: if (custom) {
+ // CPPONLY: this.htmlCreator = NS_NewCustomElement;
+ // CPPONLY: } else {
+ // CPPONLY: this.htmlCreator = NS_NewHTMLUnknownElement;
+ // CPPONLY: }
+ // The assertion below relies on TreeBuilder.OTHER being zero!
+ // TreeBuilder.OTHER isn't referenced here, because it would create
+ // a circular C++ header dependency given that this method is inlined.
+ assert this.flags == ElementName.NOT_INTERNED;
+ }
+
+ // CPPONLY: @Inline public boolean isCustom() {
+ // CPPONLY: return this.htmlCreator == NS_NewCustomElement;
+ // CPPONLY: }
+
+ // CPPONLY: public static final ElementName ISINDEX = new ElementName(
+ // CPPONLY: "isindex", "isindex",
+ // CPPONLY: NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement,
+ // CPPONLY: TreeBuilder.ISINDEX | SPECIAL);
+ // [NOCPP[
+ public static final ElementName ISINDEX = new ElementName("isindex", "isindex", TreeBuilder.OTHER);
+ // ]NOCPP]
+
+ public static final ElementName ANNOTATION_XML = new ElementName(
+ "annotation-xml", "annotation-xml",
+ // CPPONLY: NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement,
+ TreeBuilder.ANNOTATION_XML | SCOPING_AS_MATHML);
+
+ // START CODE ONLY USED FOR GENERATING CODE uncomment and run to regenerate
+
+// private static final Pattern HTML_TAG_DEF = Pattern.compile(
+// "^HTML_TAG\\(([^,]+),\\s*([^,]+),\\s*[^,]+\\).*$");
+//
+// private static final Pattern HTML_HTMLELEMENT_TAG_DEF = Pattern.compile(
+// "^HTML_HTMLELEMENT_TAG\\(([^\\)]+)\\).*$");
+//
+// private static final Pattern SVG_TAG_DEF = Pattern.compile(
+// "^SVG_(?:FROM_PARSER_)?TAG\\(([^,]+),\\s*([^\\)]+)\\).*$");
+//
+// private static final Map<String, String> htmlMap = new HashMap<String, String>();
+//
+// private static final Map<String, String> svgMap = new HashMap<String, String>();
+//
+// private static void ingestHtmlTags(File htmlList) throws IOException {
+// // This doesn't need to be efficient, so let's make it easy to write.
+// BufferedReader htmlReader = new BufferedReader(
+// new InputStreamReader(new FileInputStream(htmlList), "utf-8"));
+// try {
+// String line;
+// while ((line = htmlReader.readLine()) != null) {
+// if (!line.startsWith("HTML_")) {
+// continue;
+// }
+// if (line.startsWith("HTML_OTHER")) {
+// continue;
+// }
+// Matcher m = HTML_TAG_DEF.matcher(line);
+// if (m.matches()) {
+// String iface = m.group(2);
+// if ("Unknown".equals(iface)) {
+// continue;
+// }
+// htmlMap.put(m.group(1), "NS_NewHTML" + iface + "Element");
+// } else {
+// m = HTML_HTMLELEMENT_TAG_DEF.matcher(line);
+// if (!m.matches()) {
+// throw new RuntimeException(
+// "Malformed HTML element definition: " + line);
+// }
+// htmlMap.put(m.group(1), "NS_NewHTMLElement");
+// }
+// }
+// } finally {
+// htmlReader.close();
+// }
+// }
+//
+// private static void ingestSvgTags(File svgList) throws IOException {
+// // This doesn't need to be efficient, so let's make it easy to write.
+// BufferedReader svgReader = new BufferedReader(
+// new InputStreamReader(new FileInputStream(svgList), "utf-8"));
+// try {
+// String line;
+// while ((line = svgReader.readLine()) != null) {
+// if (!line.startsWith("SVG_")) {
+// continue;
+// }
+// Matcher m = SVG_TAG_DEF.matcher(line);
+// if (!m.matches()) {
+// throw new RuntimeException(
+// "Malformed SVG element definition: " + line);
+// }
+// String name = m.group(1);
+// if ("svgSwitch".equals(name)) {
+// name = "switch";
+// }
+// svgMap.put(name, "NS_NewSVG" + m.group(2) + "Element");
+// }
+// } finally {
+// svgReader.close();
+// }
+// }
+//
+// private static String htmlCreator(String name) {
+// String creator = htmlMap.remove(name);
+// if (creator != null) {
+// return creator;
+// }
+// return "NS_NewHTMLUnknownElement";
+// }
+//
+// private static String svgCreator(String name) {
+// String creator = svgMap.remove(name);
+// if (creator != null) {
+// return creator;
+// }
+// return "NS_NewSVGUnknownElement";
+// }
+//
+// /**
+// * @see java.lang.Object#toString()
+// */
+// @Override public String toString() {
+// return "(\"" + name + "\", \"" + camelCaseName + "\", \n// CPP"
+// + "ONLY: " + htmlCreator(name) + ",\n// CPP" + "ONLY: "
+// + svgCreator(camelCaseName) + ", \n" + decomposedFlags() + ")";
+// }
+//
+// private String decomposedFlags() {
+// StringBuilder buf = new StringBuilder("TreeBuilder.");
+// buf.append(treeBuilderGroupToName());
+// if ((flags & SPECIAL) != 0) {
+// buf.append(" | SPECIAL");
+// }
+// if ((flags & FOSTER_PARENTING) != 0) {
+// buf.append(" | FOSTER_PARENTING");
+// }
+// if ((flags & SCOPING) != 0) {
+// buf.append(" | SCOPING");
+// }
+// if ((flags & SCOPING_AS_MATHML) != 0) {
+// buf.append(" | SCOPING_AS_MATHML");
+// }
+// if ((flags & SCOPING_AS_SVG) != 0) {
+// buf.append(" | SCOPING_AS_SVG");
+// }
+// if ((flags & OPTIONAL_END_TAG) != 0) {
+// buf.append(" | OPTIONAL_END_TAG");
+// }
+// return buf.toString();
+// }
+//
+// private String constName() {
+// char[] buf = new char[name.length()];
+// for (int i = 0; i < name.length(); i++) {
+// char c = name.charAt(i);
+// if (c == '-') {
+// // if (!"annotation-xml".equals(name)) {
+// // throw new RuntimeException(
+// // "Non-annotation-xml element name with hyphen: "
+// // + name);
+// // }
+// buf[i] = '_';
+// } else if (c >= '0' && c <= '9') {
+// buf[i] = c;
+// } else {
+// buf[i] = (char) (c - 0x20);
+// }
+// }
+// return new String(buf);
+// }
+//
+// private int hash() {
+// return bufToHash(name.toCharArray(), name.length());
+// }
+//
+// public int compareTo(ElementName other) {
+// int thisHash = this.hash();
+// int otherHash = other.hash();
+// if (thisHash < otherHash) {
+// return -1;
+// } else if (thisHash == otherHash) {
+// return 0;
+// } else {
+// return 1;
+// }
+// }
+//
+// private String treeBuilderGroupToName() {
+// switch (getGroup()) {
+// case TreeBuilder.OTHER:
+// return "OTHER";
+// case TreeBuilder.A:
+// return "A";
+// case TreeBuilder.BASE:
+// return "BASE";
+// case TreeBuilder.BODY:
+// return "BODY";
+// case TreeBuilder.BR:
+// return "BR";
+// case TreeBuilder.BUTTON:
+// return "BUTTON";
+// case TreeBuilder.CAPTION:
+// return "CAPTION";
+// case TreeBuilder.COL:
+// return "COL";
+// case TreeBuilder.COLGROUP:
+// return "COLGROUP";
+// case TreeBuilder.FONT:
+// return "FONT";
+// case TreeBuilder.FORM:
+// return "FORM";
+// case TreeBuilder.FRAME:
+// return "FRAME";
+// case TreeBuilder.FRAMESET:
+// return "FRAMESET";
+// case TreeBuilder.IMAGE:
+// return "IMAGE";
+// case TreeBuilder.INPUT:
+// return "INPUT";
+// case TreeBuilder.ISINDEX:
+// return "ISINDEX";
+// case TreeBuilder.LI:
+// return "LI";
+// case TreeBuilder.LINK_OR_BASEFONT_OR_BGSOUND:
+// return "LINK_OR_BASEFONT_OR_BGSOUND";
+// case TreeBuilder.MATH:
+// return "MATH";
+// case TreeBuilder.META:
+// return "META";
+// case TreeBuilder.SVG:
+// return "SVG";
+// case TreeBuilder.HEAD:
+// return "HEAD";
+// case TreeBuilder.HR:
+// return "HR";
+// case TreeBuilder.HTML:
+// return "HTML";
+// case TreeBuilder.KEYGEN:
+// return "KEYGEN";
+// case TreeBuilder.NOBR:
+// return "NOBR";
+// case TreeBuilder.NOFRAMES:
+// return "NOFRAMES";
+// case TreeBuilder.NOSCRIPT:
+// return "NOSCRIPT";
+// case TreeBuilder.OPTGROUP:
+// return "OPTGROUP";
+// case TreeBuilder.OPTION:
+// return "OPTION";
+// case TreeBuilder.P:
+// return "P";
+// case TreeBuilder.PLAINTEXT:
+// return "PLAINTEXT";
+// case TreeBuilder.SCRIPT:
+// return "SCRIPT";
+// case TreeBuilder.SELECT:
+// return "SELECT";
+// case TreeBuilder.STYLE:
+// return "STYLE";
+// case TreeBuilder.TABLE:
+// return "TABLE";
+// case TreeBuilder.TEXTAREA:
+// return "TEXTAREA";
+// case TreeBuilder.TITLE:
+// return "TITLE";
+// case TreeBuilder.TEMPLATE:
+// return "TEMPLATE";
+// case TreeBuilder.TR:
+// return "TR";
+// case TreeBuilder.XMP:
+// return "XMP";
+// case TreeBuilder.TBODY_OR_THEAD_OR_TFOOT:
+// return "TBODY_OR_THEAD_OR_TFOOT";
+// case TreeBuilder.TD_OR_TH:
+// return "TD_OR_TH";
+// case TreeBuilder.DD_OR_DT:
+// return "DD_OR_DT";
+// case TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6:
+// return "H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6";
+// case TreeBuilder.OBJECT:
+// return "OBJECT";
+// case TreeBuilder.OUTPUT:
+// return "OUTPUT";
+// case TreeBuilder.MARQUEE_OR_APPLET:
+// return "MARQUEE_OR_APPLET";
+// case TreeBuilder.PRE_OR_LISTING:
+// return "PRE_OR_LISTING";
+// case TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
+// return "B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U";
+// case TreeBuilder.UL_OR_OL_OR_DL:
+// return "UL_OR_OL_OR_DL";
+// case TreeBuilder.IFRAME:
+// return "IFRAME";
+// case TreeBuilder.NOEMBED:
+// return "NOEMBED";
+// case TreeBuilder.EMBED:
+// return "EMBED";
+// case TreeBuilder.IMG:
+// return "IMG";
+// case TreeBuilder.AREA_OR_WBR:
+// return "AREA_OR_WBR";
+// case TreeBuilder.DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU:
+// return "DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU";
+// case TreeBuilder.FIELDSET:
+// return "FIELDSET";
+// case TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY:
+// return "ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY";
+// case TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR:
+// return "RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR";
+// case TreeBuilder.RB_OR_RTC:
+// return "RB_OR_RTC";
+// case TreeBuilder.RT_OR_RP:
+// return "RT_OR_RP";
+// case TreeBuilder.PARAM_OR_SOURCE_OR_TRACK:
+// return "PARAM_OR_SOURCE_OR_TRACK";
+// case TreeBuilder.MGLYPH_OR_MALIGNMARK:
+// return "MGLYPH_OR_MALIGNMARK";
+// case TreeBuilder.MI_MO_MN_MS_MTEXT:
+// return "MI_MO_MN_MS_MTEXT";
+// case TreeBuilder.ANNOTATION_XML:
+// return "ANNOTATION_XML";
+// case TreeBuilder.FOREIGNOBJECT_OR_DESC:
+// return "FOREIGNOBJECT_OR_DESC";
+// case TreeBuilder.MENUITEM:
+// return "MENUITEM";
+// }
+// return null;
+// }
+//
+// private static void fillLevelOrderArray(List<ElementName> sorted, int depth,
+// int rootIdx, ElementName[] levelOrder) {
+// if (rootIdx >= levelOrder.length) {
+// return;
+// }
+//
+// if (depth > 0) {
+// fillLevelOrderArray(sorted, depth - 1, rootIdx * 2 + 1, levelOrder);
+// }
+//
+// if (!sorted.isEmpty()) {
+// levelOrder[rootIdx] = sorted.remove(0);
+// }
+//
+// if (depth > 0) {
+// fillLevelOrderArray(sorted, depth - 1, rootIdx * 2 + 2, levelOrder);
+// }
+// }
+//
+// /**
+// * Regenerate self
+// *
+// * The args should be the paths to m-c files
+// * parser/htmlparser/nsHTMLTagList.h and dom/svg/SVGTagList.h.
+// */
+// public static void main(String[] args) {
+// File htmlList = new File(args[0]);
+// File svgList = new File(args[1]);
+// try {
+// ingestHtmlTags(htmlList);
+// } catch (IOException e) {
+// throw new RuntimeException(e);
+// }
+// try {
+// ingestSvgTags(svgList);
+// } catch (IOException e) {
+// throw new RuntimeException(e);
+// }
+//
+// Arrays.sort(ELEMENT_NAMES);
+// for (int i = 0; i < ELEMENT_NAMES.length; i++) {
+// int hash = ELEMENT_NAMES[i].hash();
+// if (hash < 0) {
+// System.err.println("Negative hash: " + ELEMENT_NAMES[i].name);
+// return;
+// }
+// for (int j = i + 1; j < ELEMENT_NAMES.length; j++) {
+// if (hash == ELEMENT_NAMES[j].hash()) {
+// System.err.println(
+// "Hash collision: " + ELEMENT_NAMES[i].name + ", "
+// + ELEMENT_NAMES[j].name);
+// return;
+// }
+// }
+// }
+// for (int i = 0; i < ELEMENT_NAMES.length; i++) {
+// ElementName el = ELEMENT_NAMES[i];
+// if ("isindex".equals(el.name)) {
+// continue;
+// }
+// System.out.println(
+// "public static final ElementName " + el.constName()
+// + " = new ElementName" + el.toString() + ";");
+// }
+//
+// LinkedList<ElementName> sortedNames = new LinkedList<ElementName>();
+// Collections.addAll(sortedNames, ELEMENT_NAMES);
+// ElementName[] levelOrder = new ElementName[ELEMENT_NAMES.length];
+// int bstDepth = (int) Math.ceil(
+// Math.log(ELEMENT_NAMES.length) / Math.log(2));
+// fillLevelOrderArray(sortedNames, bstDepth, 0, levelOrder);
+//
+// System.out.println(
+// "private final static @NoLength ElementName[] ELEMENT_NAMES = {");
+// for (int i = 0; i < levelOrder.length; i++) {
+// ElementName el = levelOrder[i];
+// System.out.println(el.constName() + ",");
+// }
+// System.out.println("};");
+// System.out.println("private final static int[] ELEMENT_HASHES = {");
+// for (int i = 0; i < levelOrder.length; i++) {
+// ElementName el = levelOrder[i];
+// System.out.println(Integer.toString(el.hash()) + ",");
+// }
+// System.out.println("};");
+//
+// for (Entry<String, String> entry : htmlMap.entrySet()) {
+// System.err.println("Missing HTML element: " + entry.getKey());
+// }
+// for (Entry<String, String> entry : svgMap.entrySet()) {
+// System.err.println("Missing SVG element: " + entry.getKey());
+// }
+// }
+
+
+ // START GENERATED CODE
+ public static final ElementName AND = new ElementName("and", "and",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ARG = new ElementName("arg", "arg",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ABS = new ElementName("abs", "abs",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName BIG = new ElementName("big", "big",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName BDO = new ElementName("bdo", "bdo",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CSC = new ElementName("csc", "csc",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName COL = new ElementName("col", "col",
+ // CPPONLY: NS_NewHTMLTableColElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.COL | SPECIAL);
+ public static final ElementName COS = new ElementName("cos", "cos",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName COT = new ElementName("cot", "cot",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DEL = new ElementName("del", "del",
+ // CPPONLY: NS_NewHTMLModElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DFN = new ElementName("dfn", "dfn",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DIR = new ElementName("dir", "dir",
+ // CPPONLY: NS_NewHTMLSharedElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName DIV = new ElementName("div", "div",
+ // CPPONLY: NS_NewHTMLDivElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL);
+ public static final ElementName EXP = new ElementName("exp", "exp",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName GCD = new ElementName("gcd", "gcd",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName GEQ = new ElementName("geq", "geq",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName IMG = new ElementName("img", "img",
+ // CPPONLY: NS_NewHTMLImageElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.IMG | SPECIAL);
+ public static final ElementName INS = new ElementName("ins", "ins",
+ // CPPONLY: NS_NewHTMLModElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName INT = new ElementName("int", "int",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName KBD = new ElementName("kbd", "kbd",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LOG = new ElementName("log", "log",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LCM = new ElementName("lcm", "lcm",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LEQ = new ElementName("leq", "leq",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MTD = new ElementName("mtd", "mtd",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MIN = new ElementName("min", "min",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MAP = new ElementName("map", "map",
+ // CPPONLY: NS_NewHTMLMapElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MTR = new ElementName("mtr", "mtr",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MAX = new ElementName("max", "max",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName NEQ = new ElementName("neq", "neq",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName NOT = new ElementName("not", "not",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName NAV = new ElementName("nav", "nav",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName PRE = new ElementName("pre", "pre",
+ // CPPONLY: NS_NewHTMLPreElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.PRE_OR_LISTING | SPECIAL);
+ public static final ElementName A = new ElementName("a", "a",
+ // CPPONLY: NS_NewHTMLAnchorElement,
+ // CPPONLY: NS_NewSVGAElement,
+ TreeBuilder.A);
+ public static final ElementName B = new ElementName("b", "b",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName RTC = new ElementName("rtc", "rtc",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.RB_OR_RTC | OPTIONAL_END_TAG);
+ public static final ElementName REM = new ElementName("rem", "rem",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SUB = new ElementName("sub", "sub",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR);
+ public static final ElementName SEC = new ElementName("sec", "sec",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SVG = new ElementName("svg", "svg",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGSVGElement,
+ TreeBuilder.SVG);
+ public static final ElementName SUM = new ElementName("sum", "sum",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SIN = new ElementName("sin", "sin",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SEP = new ElementName("sep", "sep",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SUP = new ElementName("sup", "sup",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR);
+ public static final ElementName SET = new ElementName("set", "set",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGSetElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TAN = new ElementName("tan", "tan",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName USE = new ElementName("use", "use",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUseElement,
+ TreeBuilder.OTHER);
+ public static final ElementName VAR = new ElementName("var", "var",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR);
+ public static final ElementName G = new ElementName("g", "g",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGGElement,
+ TreeBuilder.OTHER);
+ public static final ElementName WBR = new ElementName("wbr", "wbr",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.AREA_OR_WBR | SPECIAL);
+ public static final ElementName XMP = new ElementName("xmp", "xmp",
+ // CPPONLY: NS_NewHTMLPreElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.XMP | SPECIAL);
+ public static final ElementName XOR = new ElementName("xor", "xor",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName I = new ElementName("i", "i",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName P = new ElementName("p", "p",
+ // CPPONLY: NS_NewHTMLParagraphElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.P | SPECIAL | OPTIONAL_END_TAG);
+ public static final ElementName Q = new ElementName("q", "q",
+ // CPPONLY: NS_NewHTMLSharedElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName S = new ElementName("s", "s",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName U = new ElementName("u", "u",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName H1 = new ElementName("h1", "h1",
+ // CPPONLY: NS_NewHTMLHeadingElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ public static final ElementName H2 = new ElementName("h2", "h2",
+ // CPPONLY: NS_NewHTMLHeadingElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ public static final ElementName H3 = new ElementName("h3", "h3",
+ // CPPONLY: NS_NewHTMLHeadingElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ public static final ElementName H4 = new ElementName("h4", "h4",
+ // CPPONLY: NS_NewHTMLHeadingElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ public static final ElementName H5 = new ElementName("h5", "h5",
+ // CPPONLY: NS_NewHTMLHeadingElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ public static final ElementName H6 = new ElementName("h6", "h6",
+ // CPPONLY: NS_NewHTMLHeadingElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ public static final ElementName AREA = new ElementName("area", "area",
+ // CPPONLY: NS_NewHTMLAreaElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.AREA_OR_WBR | SPECIAL);
+ public static final ElementName DATA = new ElementName("data", "data",
+ // CPPONLY: NS_NewHTMLDataElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName EULERGAMMA = new ElementName("eulergamma", "eulergamma",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FEFUNCA = new ElementName("fefunca", "feFuncA",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEFuncAElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LAMBDA = new ElementName("lambda", "lambda",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName METADATA = new ElementName("metadata", "metadata",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGMetadataElement,
+ TreeBuilder.OTHER);
+ public static final ElementName META = new ElementName("meta", "meta",
+ // CPPONLY: NS_NewHTMLMetaElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.META | SPECIAL);
+ public static final ElementName TEXTAREA = new ElementName("textarea", "textarea",
+ // CPPONLY: NS_NewHTMLTextAreaElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.TEXTAREA | SPECIAL);
+ public static final ElementName FEFUNCB = new ElementName("fefuncb", "feFuncB",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEFuncBElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MSUB = new ElementName("msub", "msub",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName RB = new ElementName("rb", "rb",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.RB_OR_RTC | OPTIONAL_END_TAG);
+ public static final ElementName ARCSEC = new ElementName("arcsec", "arcsec",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ARCCSC = new ElementName("arccsc", "arccsc",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DEFINITION_SRC = new ElementName("definition-src", "definition-src",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DESC = new ElementName("desc", "desc",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGDescElement,
+ TreeBuilder.FOREIGNOBJECT_OR_DESC | SCOPING_AS_SVG);
+ public static final ElementName FONT_FACE_SRC = new ElementName("font-face-src", "font-face-src",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MFRAC = new ElementName("mfrac", "mfrac",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DD = new ElementName("dd", "dd",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.DD_OR_DT | SPECIAL | OPTIONAL_END_TAG);
+ public static final ElementName BGSOUND = new ElementName("bgsound", "bgsound",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.LINK_OR_BASEFONT_OR_BGSOUND | SPECIAL);
+ public static final ElementName CARD = new ElementName("card", "card",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DISCARD = new ElementName("discard", "discard",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName EMBED = new ElementName("embed", "embed",
+ // CPPONLY: NS_NewHTMLSharedObjectElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.EMBED | SPECIAL);
+ public static final ElementName FEBLEND = new ElementName("feblend", "feBlend",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEBlendElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FEFLOOD = new ElementName("feflood", "feFlood",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEFloodElement,
+ TreeBuilder.OTHER);
+ public static final ElementName GRAD = new ElementName("grad", "grad",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName HEAD = new ElementName("head", "head",
+ // CPPONLY: NS_NewHTMLSharedElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.HEAD | SPECIAL | OPTIONAL_END_TAG);
+ public static final ElementName LEGEND = new ElementName("legend", "legend",
+ // CPPONLY: NS_NewHTMLLegendElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MFENCED = new ElementName("mfenced", "mfenced",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MPADDED = new ElementName("mpadded", "mpadded",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName NOEMBED = new ElementName("noembed", "noembed",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.NOEMBED | SPECIAL);
+ public static final ElementName TD = new ElementName("td", "td",
+ // CPPONLY: NS_NewHTMLTableCellElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.TD_OR_TH | SPECIAL | SCOPING | OPTIONAL_END_TAG);
+ public static final ElementName THEAD = new ElementName("thead", "thead",
+ // CPPONLY: NS_NewHTMLTableSectionElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.TBODY_OR_THEAD_OR_TFOOT | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG);
+ public static final ElementName ASIDE = new ElementName("aside", "aside",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName ARTICLE = new ElementName("article", "article",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName ANIMATE = new ElementName("animate", "animate",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGAnimateElement,
+ TreeBuilder.OTHER);
+ public static final ElementName BASE = new ElementName("base", "base",
+ // CPPONLY: NS_NewHTMLSharedElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.BASE | SPECIAL);
+ public static final ElementName BLOCKQUOTE = new ElementName("blockquote", "blockquote",
+ // CPPONLY: NS_NewHTMLSharedElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL);
+ public static final ElementName CODE = new ElementName("code", "code",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName CIRCLE = new ElementName("circle", "circle",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGCircleElement,
+ TreeBuilder.OTHER);
+ public static final ElementName COLOR_PROFILE = new ElementName("color-profile", "color-profile",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName COMPOSE = new ElementName("compose", "compose",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CONJUGATE = new ElementName("conjugate", "conjugate",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CITE = new ElementName("cite", "cite",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DIVERGENCE = new ElementName("divergence", "divergence",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DIVIDE = new ElementName("divide", "divide",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DEGREE = new ElementName("degree", "degree",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DECLARE = new ElementName("declare", "declare",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DATATEMPLATE = new ElementName("datatemplate", "datatemplate",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName EXPONENTIALE = new ElementName("exponentiale", "exponentiale",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ELLIPSE = new ElementName("ellipse", "ellipse",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGEllipseElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FONT_FACE = new ElementName("font-face", "font-face",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FETURBULENCE = new ElementName("feturbulence", "feTurbulence",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFETurbulenceElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FEMERGENODE = new ElementName("femergenode", "feMergeNode",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEMergeNodeElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FEIMAGE = new ElementName("feimage", "feImage",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEImageElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FEMERGE = new ElementName("femerge", "feMerge",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEMergeElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FETILE = new ElementName("fetile", "feTile",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFETileElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FONT_FACE_NAME = new ElementName("font-face-name", "font-face-name",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FRAME = new ElementName("frame", "frame",
+ // CPPONLY: NS_NewHTMLFrameElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.FRAME | SPECIAL);
+ public static final ElementName FIGURE = new ElementName("figure", "figure",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName FALSE = new ElementName("false", "false",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FECOMPOSITE = new ElementName("fecomposite", "feComposite",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFECompositeElement,
+ TreeBuilder.OTHER);
+ public static final ElementName IMAGE = new ElementName("image", "image",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGImageElement,
+ TreeBuilder.IMAGE);
+ public static final ElementName IFRAME = new ElementName("iframe", "iframe",
+ // CPPONLY: NS_NewHTMLIFrameElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.IFRAME | SPECIAL);
+ public static final ElementName INVERSE = new ElementName("inverse", "inverse",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LINE = new ElementName("line", "line",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGLineElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LOGBASE = new ElementName("logbase", "logbase",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MSPACE = new ElementName("mspace", "mspace",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MODE = new ElementName("mode", "mode",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MTABLE = new ElementName("mtable", "mtable",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MSTYLE = new ElementName("mstyle", "mstyle",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MENCLOSE = new ElementName("menclose", "menclose",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName NONE = new ElementName("none", "none",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName OTHERWISE = new ElementName("otherwise", "otherwise",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PIECE = new ElementName("piece", "piece",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName POLYLINE = new ElementName("polyline", "polyline",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGPolylineElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PICTURE = new ElementName("picture", "picture",
+ // CPPONLY: NS_NewHTMLPictureElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PIECEWISE = new ElementName("piecewise", "piecewise",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName RULE = new ElementName("rule", "rule",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SOURCE = new ElementName("source", "source",
+ // CPPONLY: NS_NewHTMLSourceElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.PARAM_OR_SOURCE_OR_TRACK);
+ public static final ElementName STRIKE = new ElementName("strike", "strike",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName STYLE = new ElementName("style", "style",
+ // CPPONLY: NS_NewHTMLStyleElement,
+ // CPPONLY: NS_NewSVGStyleElement,
+ TreeBuilder.STYLE | SPECIAL);
+ public static final ElementName TABLE = new ElementName("table", "table",
+ // CPPONLY: NS_NewHTMLTableElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.TABLE | SPECIAL | FOSTER_PARENTING | SCOPING);
+ public static final ElementName TITLE = new ElementName("title", "title",
+ // CPPONLY: NS_NewHTMLTitleElement,
+ // CPPONLY: NS_NewSVGTitleElement,
+ TreeBuilder.TITLE | SPECIAL | SCOPING_AS_SVG);
+ public static final ElementName TIME = new ElementName("time", "time",
+ // CPPONLY: NS_NewHTMLTimeElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TRANSPOSE = new ElementName("transpose", "transpose",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TEMPLATE = new ElementName("template", "template",
+ // CPPONLY: NS_NewHTMLTemplateElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.TEMPLATE | SPECIAL | SCOPING);
+ public static final ElementName TRUE = new ElementName("true", "true",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName VARIANCE = new ElementName("variance", "variance",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ALTGLYPHDEF = new ElementName("altglyphdef", "altGlyphDef",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DIFF = new ElementName("diff", "diff",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FACTOROF = new ElementName("factorof", "factorof",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName GLYPHREF = new ElementName("glyphref", "glyphRef",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PARTIALDIFF = new ElementName("partialdiff", "partialdiff",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SETDIFF = new ElementName("setdiff", "setdiff",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TREF = new ElementName("tref", "tref",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CEILING = new ElementName("ceiling", "ceiling",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DIALOG = new ElementName("dialog", "dialog",
+ // CPPONLY: NS_NewHTMLDialogElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName FEFUNCG = new ElementName("fefuncg", "feFuncG",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEFuncGElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FEDIFFUSELIGHTING = new ElementName("fediffuselighting", "feDiffuseLighting",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEDiffuseLightingElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FESPECULARLIGHTING = new ElementName("fespecularlighting", "feSpecularLighting",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFESpecularLightingElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LISTING = new ElementName("listing", "listing",
+ // CPPONLY: NS_NewHTMLPreElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.PRE_OR_LISTING | SPECIAL);
+ public static final ElementName STRONG = new ElementName("strong", "strong",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName ARCSECH = new ElementName("arcsech", "arcsech",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ARCCSCH = new ElementName("arccsch", "arccsch",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ARCTANH = new ElementName("arctanh", "arctanh",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ARCSINH = new ElementName("arcsinh", "arcsinh",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ALTGLYPH = new ElementName("altglyph", "altGlyph",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ARCCOSH = new ElementName("arccosh", "arccosh",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ARCCOTH = new ElementName("arccoth", "arccoth",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CSCH = new ElementName("csch", "csch",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName COSH = new ElementName("cosh", "cosh",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CLIPPATH = new ElementName("clippath", "clipPath",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGClipPathElement,
+ TreeBuilder.OTHER);
+ public static final ElementName COTH = new ElementName("coth", "coth",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName GLYPH = new ElementName("glyph", "glyph",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MGLYPH = new ElementName("mglyph", "mglyph",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.MGLYPH_OR_MALIGNMARK);
+ public static final ElementName MISSING_GLYPH = new ElementName("missing-glyph", "missing-glyph",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MATH = new ElementName("math", "math",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.MATH);
+ public static final ElementName MPATH = new ElementName("mpath", "mpath",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGMPathElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PREFETCH = new ElementName("prefetch", "prefetch",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PATH = new ElementName("path", "path",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGPathElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TH = new ElementName("th", "th",
+ // CPPONLY: NS_NewHTMLTableCellElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.TD_OR_TH | SPECIAL | SCOPING | OPTIONAL_END_TAG);
+ public static final ElementName SECH = new ElementName("sech", "sech",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SWITCH = new ElementName("switch", "switch",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGSwitchElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SINH = new ElementName("sinh", "sinh",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TANH = new ElementName("tanh", "tanh",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TEXTPATH = new ElementName("textpath", "textPath",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGTextPathElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CI = new ElementName("ci", "ci",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FONT_FACE_URI = new ElementName("font-face-uri", "font-face-uri",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LI = new ElementName("li", "li",
+ // CPPONLY: NS_NewHTMLLIElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.LI | SPECIAL | OPTIONAL_END_TAG);
+ public static final ElementName IMAGINARYI = new ElementName("imaginaryi", "imaginaryi",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MI = new ElementName("mi", "mi",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML);
+ public static final ElementName PI = new ElementName("pi", "pi",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LINK = new ElementName("link", "link",
+ // CPPONLY: NS_NewHTMLLinkElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.LINK_OR_BASEFONT_OR_BGSOUND | SPECIAL);
+ public static final ElementName MARK = new ElementName("mark", "mark",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MALIGNMARK = new ElementName("malignmark", "malignmark",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.MGLYPH_OR_MALIGNMARK);
+ public static final ElementName MASK = new ElementName("mask", "mask",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGMaskElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TBREAK = new ElementName("tbreak", "tbreak",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TRACK = new ElementName("track", "track",
+ // CPPONLY: NS_NewHTMLTrackElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.PARAM_OR_SOURCE_OR_TRACK | SPECIAL);
+ public static final ElementName DL = new ElementName("dl", "dl",
+ // CPPONLY: NS_NewHTMLSharedListElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.UL_OR_OL_OR_DL | SPECIAL);
+ public static final ElementName CSYMBOL = new ElementName("csymbol", "csymbol",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CURL = new ElementName("curl", "curl",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FACTORIAL = new ElementName("factorial", "factorial",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FORALL = new ElementName("forall", "forall",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName HTML = new ElementName("html", "html",
+ // CPPONLY: NS_NewHTMLSharedElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.HTML | SPECIAL | SCOPING | OPTIONAL_END_TAG);
+ public static final ElementName INTERVAL = new ElementName("interval", "interval",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName OL = new ElementName("ol", "ol",
+ // CPPONLY: NS_NewHTMLSharedListElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.UL_OR_OL_OR_DL | SPECIAL);
+ public static final ElementName LABEL = new ElementName("label", "label",
+ // CPPONLY: NS_NewHTMLLabelElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName UL = new ElementName("ul", "ul",
+ // CPPONLY: NS_NewHTMLSharedListElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.UL_OR_OL_OR_DL | SPECIAL);
+ public static final ElementName REAL = new ElementName("real", "real",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SMALL = new ElementName("small", "small",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName SYMBOL = new ElementName("symbol", "symbol",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGSymbolElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ALTGLYPHITEM = new ElementName("altglyphitem", "altGlyphItem",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ANIMATETRANSFORM = new ElementName("animatetransform", "animateTransform",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGAnimateTransformElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ACRONYM = new ElementName("acronym", "acronym",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName EM = new ElementName("em", "em",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName FORM = new ElementName("form", "form",
+ // CPPONLY: NS_NewHTMLFormElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.FORM | SPECIAL);
+ public static final ElementName MENUITEM = new ElementName("menuitem", "menuitem",
+ // CPPONLY: NS_NewHTMLMenuItemElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.MENUITEM);
+ public static final ElementName MPHANTOM = new ElementName("mphantom", "mphantom",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PARAM = new ElementName("param", "param",
+ // CPPONLY: NS_NewHTMLSharedElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.PARAM_OR_SOURCE_OR_TRACK | SPECIAL);
+ public static final ElementName CN = new ElementName("cn", "cn",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ARCTAN = new ElementName("arctan", "arctan",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ARCSIN = new ElementName("arcsin", "arcsin",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ANIMATION = new ElementName("animation", "animation",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ANNOTATION = new ElementName("annotation", "annotation",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ANIMATEMOTION = new ElementName("animatemotion", "animateMotion",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGAnimateMotionElement,
+ TreeBuilder.OTHER);
+ public static final ElementName BUTTON = new ElementName("button", "button",
+ // CPPONLY: NS_NewHTMLButtonElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.BUTTON | SPECIAL);
+ public static final ElementName FN = new ElementName("fn", "fn",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CODOMAIN = new ElementName("codomain", "codomain",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CAPTION = new ElementName("caption", "caption",
+ // CPPONLY: NS_NewHTMLTableCaptionElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.CAPTION | SPECIAL | SCOPING);
+ public static final ElementName CONDITION = new ElementName("condition", "condition",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DOMAIN = new ElementName("domain", "domain",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DOMAINOFAPPLICATION = new ElementName("domainofapplication", "domainofapplication",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName IN = new ElementName("in", "in",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FIGCAPTION = new ElementName("figcaption", "figcaption",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName HKERN = new ElementName("hkern", "hkern",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LN = new ElementName("ln", "ln",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MN = new ElementName("mn", "mn",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML);
+ public static final ElementName KEYGEN = new ElementName("keygen", "keygen",
+ // CPPONLY: NS_NewHTMLSpanElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.KEYGEN);
+ public static final ElementName LAPLACIAN = new ElementName("laplacian", "laplacian",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MEAN = new ElementName("mean", "mean",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MEDIAN = new ElementName("median", "median",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MAIN = new ElementName("main", "main",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName MACTION = new ElementName("maction", "maction",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName NOTIN = new ElementName("notin", "notin",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName OPTION = new ElementName("option", "option",
+ // CPPONLY: NS_NewHTMLOptionElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OPTION | OPTIONAL_END_TAG);
+ public static final ElementName POLYGON = new ElementName("polygon", "polygon",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGPolygonElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PATTERN = new ElementName("pattern", "pattern",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGPatternElement,
+ TreeBuilder.OTHER);
+ public static final ElementName RELN = new ElementName("reln", "reln",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SPAN = new ElementName("span", "span",
+ // CPPONLY: NS_NewHTMLSpanElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR);
+ public static final ElementName SECTION = new ElementName("section", "section",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName TSPAN = new ElementName("tspan", "tspan",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGTSpanElement,
+ TreeBuilder.OTHER);
+ public static final ElementName UNION = new ElementName("union", "union",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName VKERN = new ElementName("vkern", "vkern",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName AUDIO = new ElementName("audio", "audio",
+ // CPPONLY: NS_NewHTMLAudioElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MO = new ElementName("mo", "mo",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML);
+ public static final ElementName TENDSTO = new ElementName("tendsto", "tendsto",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName VIDEO = new ElementName("video", "video",
+ // CPPONLY: NS_NewHTMLVideoElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName COLGROUP = new ElementName("colgroup", "colgroup",
+ // CPPONLY: NS_NewHTMLTableColElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.COLGROUP | SPECIAL | OPTIONAL_END_TAG);
+ public static final ElementName FEDISPLACEMENTMAP = new ElementName("fedisplacementmap", "feDisplacementMap",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEDisplacementMapElement,
+ TreeBuilder.OTHER);
+ public static final ElementName HGROUP = new ElementName("hgroup", "hgroup",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName MALIGNGROUP = new ElementName("maligngroup", "maligngroup",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MSUBSUP = new ElementName("msubsup", "msubsup",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MSUP = new ElementName("msup", "msup",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName RP = new ElementName("rp", "rp",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.RT_OR_RP | OPTIONAL_END_TAG);
+ public static final ElementName OPTGROUP = new ElementName("optgroup", "optgroup",
+ // CPPONLY: NS_NewHTMLOptGroupElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OPTGROUP | OPTIONAL_END_TAG);
+ public static final ElementName SAMP = new ElementName("samp", "samp",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName STOP = new ElementName("stop", "stop",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGStopElement,
+ TreeBuilder.OTHER);
+ public static final ElementName EQ = new ElementName("eq", "eq",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName BR = new ElementName("br", "br",
+ // CPPONLY: NS_NewHTMLBRElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.BR | SPECIAL);
+ public static final ElementName ABBR = new ElementName("abbr", "abbr",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ANIMATECOLOR = new ElementName("animatecolor", "animateColor",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName BVAR = new ElementName("bvar", "bvar",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName CENTER = new ElementName("center", "center",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL);
+ public static final ElementName CURSOR = new ElementName("cursor", "cursor",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName HR = new ElementName("hr", "hr",
+ // CPPONLY: NS_NewHTMLHRElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.HR | SPECIAL);
+ public static final ElementName FEFUNCR = new ElementName("fefuncr", "feFuncR",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEFuncRElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FECOMPONENTTRANSFER = new ElementName("fecomponenttransfer", "feComponentTransfer",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEComponentTransferElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FILTER = new ElementName("filter", "filter",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFilterElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FOOTER = new ElementName("footer", "footer",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName FLOOR = new ElementName("floor", "floor",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FEGAUSSIANBLUR = new ElementName("fegaussianblur", "feGaussianBlur",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEGaussianBlurElement,
+ TreeBuilder.OTHER);
+ public static final ElementName HEADER = new ElementName("header", "header",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName HANDLER = new ElementName("handler", "handler",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName OR = new ElementName("or", "or",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LISTENER = new ElementName("listener", "listener",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MUNDER = new ElementName("munder", "munder",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MARKER = new ElementName("marker", "marker",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGMarkerElement,
+ TreeBuilder.OTHER);
+ public static final ElementName METER = new ElementName("meter", "meter",
+ // CPPONLY: NS_NewHTMLMeterElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MOVER = new ElementName("mover", "mover",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MUNDEROVER = new ElementName("munderover", "munderover",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MERROR = new ElementName("merror", "merror",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MLABELEDTR = new ElementName("mlabeledtr", "mlabeledtr",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName NOBR = new ElementName("nobr", "nobr",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.NOBR);
+ public static final ElementName NOTANUMBER = new ElementName("notanumber", "notanumber",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName POWER = new ElementName("power", "power",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TR = new ElementName("tr", "tr",
+ // CPPONLY: NS_NewHTMLTableRowElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.TR | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG);
+ public static final ElementName SOLIDCOLOR = new ElementName("solidcolor", "solidcolor",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SELECTOR = new ElementName("selector", "selector",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName VECTOR = new ElementName("vector", "vector",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ARCCOS = new ElementName("arccos", "arccos",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ADDRESS = new ElementName("address", "address",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName CANVAS = new ElementName("canvas", "canvas",
+ // CPPONLY: NS_NewHTMLCanvasElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName COMPLEXES = new ElementName("complexes", "complexes",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DEFS = new ElementName("defs", "defs",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGDefsElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DETAILS = new ElementName("details", "details",
+ // CPPONLY: NS_NewHTMLDetailsElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName EXISTS = new ElementName("exists", "exists",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName IMPLIES = new ElementName("implies", "implies",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName INTEGERS = new ElementName("integers", "integers",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MS = new ElementName("ms", "ms",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML);
+ public static final ElementName MPRESCRIPTS = new ElementName("mprescripts", "mprescripts",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MMULTISCRIPTS = new ElementName("mmultiscripts", "mmultiscripts",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MINUS = new ElementName("minus", "minus",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName NOFRAMES = new ElementName("noframes", "noframes",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.NOFRAMES | SPECIAL);
+ public static final ElementName NATURALNUMBERS = new ElementName("naturalnumbers", "naturalnumbers",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PRIMES = new ElementName("primes", "primes",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PROGRESS = new ElementName("progress", "progress",
+ // CPPONLY: NS_NewHTMLProgressElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PLUS = new ElementName("plus", "plus",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName REALS = new ElementName("reals", "reals",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName RATIONALS = new ElementName("rationals", "rationals",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SEMANTICS = new ElementName("semantics", "semantics",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName TIMES = new ElementName("times", "times",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DT = new ElementName("dt", "dt",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.DD_OR_DT | SPECIAL | OPTIONAL_END_TAG);
+ public static final ElementName APPLET = new ElementName("applet", "applet",
+ // CPPONLY: NS_NewHTMLSharedObjectElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.MARQUEE_OR_APPLET | SPECIAL | SCOPING);
+ public static final ElementName ARCCOT = new ElementName("arccot", "arccot",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName BASEFONT = new ElementName("basefont", "basefont",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.LINK_OR_BASEFONT_OR_BGSOUND | SPECIAL);
+ public static final ElementName CARTESIANPRODUCT = new ElementName("cartesianproduct", "cartesianproduct",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName GT = new ElementName("gt", "gt",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DETERMINANT = new ElementName("determinant", "determinant",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName DATALIST = new ElementName("datalist", "datalist",
+ // CPPONLY: NS_NewHTMLDataListElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName EMPTYSET = new ElementName("emptyset", "emptyset",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName EQUIVALENT = new ElementName("equivalent", "equivalent",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FONT_FACE_FORMAT = new ElementName("font-face-format", "font-face-format",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FOREIGNOBJECT = new ElementName("foreignobject", "foreignObject",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGForeignObjectElement,
+ TreeBuilder.FOREIGNOBJECT_OR_DESC | SCOPING_AS_SVG);
+ public static final ElementName FIELDSET = new ElementName("fieldset", "fieldset",
+ // CPPONLY: NS_NewHTMLFieldSetElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.FIELDSET | SPECIAL);
+ public static final ElementName FRAMESET = new ElementName("frameset", "frameset",
+ // CPPONLY: NS_NewHTMLFrameSetElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.FRAMESET | SPECIAL);
+ public static final ElementName FEOFFSET = new ElementName("feoffset", "feOffset",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEOffsetElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FESPOTLIGHT = new ElementName("fespotlight", "feSpotLight",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFESpotLightElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FEPOINTLIGHT = new ElementName("fepointlight", "fePointLight",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEPointLightElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FEDISTANTLIGHT = new ElementName("fedistantlight", "feDistantLight",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEDistantLightElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FONT = new ElementName("font", "font",
+ // CPPONLY: NS_NewHTMLFontElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.FONT);
+ public static final ElementName LT = new ElementName("lt", "lt",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName INTERSECT = new ElementName("intersect", "intersect",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName IDENT = new ElementName("ident", "ident",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName INPUT = new ElementName("input", "input",
+ // CPPONLY: NS_NewHTMLInputElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.INPUT | SPECIAL);
+ public static final ElementName LIMIT = new ElementName("limit", "limit",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LOWLIMIT = new ElementName("lowlimit", "lowlimit",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LINEARGRADIENT = new ElementName("lineargradient", "linearGradient",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGLinearGradientElement,
+ TreeBuilder.OTHER);
+ public static final ElementName LIST = new ElementName("list", "list",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MOMENT = new ElementName("moment", "moment",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MROOT = new ElementName("mroot", "mroot",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MSQRT = new ElementName("msqrt", "msqrt",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MOMENTABOUT = new ElementName("momentabout", "momentabout",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MTEXT = new ElementName("mtext", "mtext",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML);
+ public static final ElementName NOTSUBSET = new ElementName("notsubset", "notsubset",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName NOTPRSUBSET = new ElementName("notprsubset", "notprsubset",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName NOSCRIPT = new ElementName("noscript", "noscript",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.NOSCRIPT | SPECIAL);
+ public static final ElementName NEST = new ElementName("nest", "nest",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName RT = new ElementName("rt", "rt",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.RT_OR_RP | OPTIONAL_END_TAG);
+ public static final ElementName OBJECT = new ElementName("object", "object",
+ // CPPONLY: NS_NewHTMLObjectElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OBJECT | SPECIAL | SCOPING);
+ public static final ElementName OUTERPRODUCT = new ElementName("outerproduct", "outerproduct",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName OUTPUT = new ElementName("output", "output",
+ // CPPONLY: NS_NewHTMLOutputElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OUTPUT);
+ public static final ElementName PRODUCT = new ElementName("product", "product",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PRSUBSET = new ElementName("prsubset", "prsubset",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName PLAINTEXT = new ElementName("plaintext", "plaintext",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.PLAINTEXT | SPECIAL);
+ public static final ElementName TT = new ElementName("tt", "tt",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ public static final ElementName QUOTIENT = new ElementName("quotient", "quotient",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName RECT = new ElementName("rect", "rect",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGRectElement,
+ TreeBuilder.OTHER);
+ public static final ElementName RADIALGRADIENT = new ElementName("radialgradient", "radialGradient",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGRadialGradientElement,
+ TreeBuilder.OTHER);
+ public static final ElementName ROOT = new ElementName("root", "root",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SELECT = new ElementName("select", "select",
+ // CPPONLY: NS_NewHTMLSelectElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.SELECT | SPECIAL);
+ public static final ElementName SCALARPRODUCT = new ElementName("scalarproduct", "scalarproduct",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SUBSET = new ElementName("subset", "subset",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SLOT = new ElementName("slot", "slot",
+ // CPPONLY: NS_NewHTMLSlotElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName SCRIPT = new ElementName("script", "script",
+ // CPPONLY: NS_NewHTMLScriptElement,
+ // CPPONLY: NS_NewSVGScriptElement,
+ TreeBuilder.SCRIPT | SPECIAL);
+ public static final ElementName TFOOT = new ElementName("tfoot", "tfoot",
+ // CPPONLY: NS_NewHTMLTableSectionElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.TBODY_OR_THEAD_OR_TFOOT | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG);
+ public static final ElementName TEXT = new ElementName("text", "text",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGTextElement,
+ TreeBuilder.OTHER);
+ public static final ElementName UPLIMIT = new ElementName("uplimit", "uplimit",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName VECTORPRODUCT = new ElementName("vectorproduct", "vectorproduct",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MENU = new ElementName("menu", "menu",
+ // CPPONLY: NS_NewHTMLMenuElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL);
+ public static final ElementName SDEV = new ElementName("sdev", "sdev",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FEDROPSHADOW = new ElementName("fedropshadow", "feDropShadow",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEDropShadowElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MROW = new ElementName("mrow", "mrow",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MATRIXROW = new ElementName("matrixrow", "matrixrow",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName VIEW = new ElementName("view", "view",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGViewElement,
+ TreeBuilder.OTHER);
+ public static final ElementName APPROX = new ElementName("approx", "approx",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FECOLORMATRIX = new ElementName("fecolormatrix", "feColorMatrix",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEColorMatrixElement,
+ TreeBuilder.OTHER);
+ public static final ElementName FECONVOLVEMATRIX = new ElementName("feconvolvematrix", "feConvolveMatrix",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEConvolveMatrixElement,
+ TreeBuilder.OTHER);
+ public static final ElementName MATRIX = new ElementName("matrix", "matrix",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName APPLY = new ElementName("apply", "apply",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName BODY = new ElementName("body", "body",
+ // CPPONLY: NS_NewHTMLBodyElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.BODY | SPECIAL | OPTIONAL_END_TAG);
+ public static final ElementName FEMORPHOLOGY = new ElementName("femorphology", "feMorphology",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGFEMorphologyElement,
+ TreeBuilder.OTHER);
+ public static final ElementName IMAGINARY = new ElementName("imaginary", "imaginary",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName INFINITY = new ElementName("infinity", "infinity",
+ // CPPONLY: NS_NewHTMLUnknownElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.OTHER);
+ public static final ElementName RUBY = new ElementName("ruby", "ruby",
+ // CPPONLY: NS_NewHTMLElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR);
+ public static final ElementName SUMMARY = new ElementName("summary", "summary",
+ // CPPONLY: NS_NewHTMLSummaryElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ public static final ElementName TBODY = new ElementName("tbody", "tbody",
+ // CPPONLY: NS_NewHTMLTableSectionElement,
+ // CPPONLY: NS_NewSVGUnknownElement,
+ TreeBuilder.TBODY_OR_THEAD_OR_TFOOT | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG);
+ private final static @NoLength ElementName[] ELEMENT_NAMES = {
+ AUDIO,
+ LOGBASE,
+ FIELDSET,
+ DATA,
+ IMAGINARYI,
+ CANVAS,
+ QUOTIENT,
+ PRE,
+ ARTICLE,
+ FEFUNCG,
+ ARCSIN,
+ MUNDER,
+ REALS,
+ MROOT,
+ MROW,
+ GEQ,
+ G,
+ DD,
+ ELLIPSE,
+ TABLE,
+ GLYPH,
+ OL,
+ KEYGEN,
+ ABBR,
+ NOTANUMBER,
+ MPRESCRIPTS,
+ CARTESIANPRODUCT,
+ INTERSECT,
+ RT,
+ SCRIPT,
+ APPLY,
+ COS,
+ MTD,
+ SUM,
+ U,
+ MSUB,
+ HEAD,
+ CONJUGATE,
+ FRAME,
+ PIECE,
+ DIFF,
+ ARCSINH,
+ SECH,
+ TRACK,
+ ACRONYM,
+ CONDITION,
+ POLYGON,
+ MSUBSUP,
+ FILTER,
+ MUNDEROVER,
+ SELECTOR,
+ EXISTS,
+ NATURALNUMBERS,
+ DT,
+ EMPTYSET,
+ FEPOINTLIGHT,
+ LOWLIMIT,
+ NOTSUBSET,
+ PRODUCT,
+ SELECT,
+ VECTORPRODUCT,
+ FECOLORMATRIX,
+ INFINITY,
+ BIG,
+ DIR,
+ KBD,
+ MAX,
+ REM,
+ SET,
+ I,
+ H4,
+ METADATA,
+ DEFINITION_SRC,
+ EMBED,
+ NOEMBED,
+ CODE,
+ DEGREE,
+ FEIMAGE,
+ IMAGE,
+ MSTYLE,
+ RULE,
+ TEMPLATE,
+ SETDIFF,
+ STRONG,
+ CSCH,
+ MPATH,
+ TEXTPATH,
+ MARK,
+ FACTORIAL,
+ SMALL,
+ MPHANTOM,
+ BUTTON,
+ FIGCAPTION,
+ MAIN,
+ SECTION,
+ COLGROUP,
+ SAMP,
+ CURSOR,
+ HEADER,
+ METER,
+ MLABELEDTR,
+ TR,
+ ARCCOS,
+ DEFS,
+ INTEGERS,
+ MINUS,
+ PROGRESS,
+ SEMANTICS,
+ ARCCOT,
+ DETERMINANT,
+ FONT_FACE_FORMAT,
+ FEOFFSET,
+ FONT,
+ INPUT,
+ LIST,
+ MOMENTABOUT,
+ NOSCRIPT,
+ OUTERPRODUCT,
+ PLAINTEXT,
+ RADIALGRADIENT,
+ SUBSET,
+ TEXT,
+ SDEV,
+ VIEW,
+ ISINDEX,
+ FEMORPHOLOGY,
+ SUMMARY,
+ ARG,
+ CSC,
+ DEL,
+ EXP,
+ INS,
+ LCM,
+ MAP,
+ NOT,
+ B,
+ SEC,
+ SEP,
+ USE,
+ XMP,
+ Q,
+ H2,
+ H6,
+ FEFUNCA,
+ TEXTAREA,
+ ARCSEC,
+ FONT_FACE_SRC,
+ CARD,
+ FEFLOOD,
+ MFENCED,
+ THEAD,
+ BASE,
+ COLOR_PROFILE,
+ DIVERGENCE,
+ DATATEMPLATE,
+ FETURBULENCE,
+ FETILE,
+ FALSE,
+ INVERSE,
+ MODE,
+ NONE,
+ PICTURE,
+ STRIKE,
+ TIME,
+ VARIANCE,
+ GLYPHREF,
+ CEILING,
+ FESPECULARLIGHTING,
+ ARCCSCH,
+ ARCCOSH,
+ CLIPPATH,
+ MISSING_GLYPH,
+ PATH,
+ SINH,
+ FONT_FACE_URI,
+ PI,
+ MASK,
+ CSYMBOL,
+ HTML,
+ UL,
+ ALTGLYPHITEM,
+ FORM,
+ CN,
+ ANNOTATION,
+ CODOMAIN,
+ DOMAINOFAPPLICATION,
+ LN,
+ MEAN,
+ NOTIN,
+ RELN,
+ UNION,
+ TENDSTO,
+ HGROUP,
+ RP,
+ EQ,
+ BVAR,
+ FEFUNCR,
+ FLOOR,
+ OR,
+ MARKER,
+ MOVER,
+ MERROR,
+ NOBR,
+ POWER,
+ SOLIDCOLOR,
+ VECTOR,
+ ADDRESS,
+ COMPLEXES,
+ DETAILS,
+ IMPLIES,
+ MS,
+ MMULTISCRIPTS,
+ NOFRAMES,
+ PRIMES,
+ PLUS,
+ RATIONALS,
+ TIMES,
+ APPLET,
+ BASEFONT,
+ GT,
+ DATALIST,
+ EQUIVALENT,
+ FOREIGNOBJECT,
+ FRAMESET,
+ FESPOTLIGHT,
+ FEDISTANTLIGHT,
+ LT,
+ IDENT,
+ LIMIT,
+ LINEARGRADIENT,
+ MOMENT,
+ MSQRT,
+ MTEXT,
+ NOTPRSUBSET,
+ NEST,
+ OBJECT,
+ OUTPUT,
+ PRSUBSET,
+ TT,
+ RECT,
+ ROOT,
+ SCALARPRODUCT,
+ SLOT,
+ TFOOT,
+ UPLIMIT,
+ MENU,
+ FEDROPSHADOW,
+ MATRIXROW,
+ APPROX,
+ FECONVOLVEMATRIX,
+ MATRIX,
+ BODY,
+ IMAGINARY,
+ RUBY,
+ TBODY,
+ AND,
+ ABS,
+ BDO,
+ COL,
+ COT,
+ DFN,
+ DIV,
+ GCD,
+ IMG,
+ INT,
+ LOG,
+ LEQ,
+ MIN,
+ MTR,
+ NEQ,
+ NAV,
+ A,
+ RTC,
+ SUB,
+ SVG,
+ SIN,
+ SUP,
+ TAN,
+ VAR,
+ WBR,
+ XOR,
+ P,
+ S,
+ H1,
+ H3,
+ H5,
+ AREA,
+ EULERGAMMA,
+ LAMBDA,
+ META,
+ FEFUNCB,
+ RB,
+ ARCCSC,
+ DESC,
+ MFRAC,
+ BGSOUND,
+ DISCARD,
+ FEBLEND,
+ GRAD,
+ LEGEND,
+ MPADDED,
+ TD,
+ ASIDE,
+ ANIMATE,
+ BLOCKQUOTE,
+ CIRCLE,
+ COMPOSE,
+ CITE,
+ DIVIDE,
+ DECLARE,
+ EXPONENTIALE,
+ FONT_FACE,
+ FEMERGENODE,
+ FEMERGE,
+ FONT_FACE_NAME,
+ FIGURE,
+ FECOMPOSITE,
+ IFRAME,
+ LINE,
+ MSPACE,
+ MTABLE,
+ MENCLOSE,
+ OTHERWISE,
+ POLYLINE,
+ PIECEWISE,
+ SOURCE,
+ STYLE,
+ TITLE,
+ TRANSPOSE,
+ TRUE,
+ ALTGLYPHDEF,
+ FACTOROF,
+ PARTIALDIFF,
+ TREF,
+ DIALOG,
+ FEDIFFUSELIGHTING,
+ LISTING,
+ ARCSECH,
+ ARCTANH,
+ ALTGLYPH,
+ ARCCOTH,
+ COSH,
+ COTH,
+ MGLYPH,
+ MATH,
+ PREFETCH,
+ TH,
+ SWITCH,
+ TANH,
+ CI,
+ LI,
+ MI,
+ LINK,
+ MALIGNMARK,
+ TBREAK,
+ DL,
+ CURL,
+ FORALL,
+ INTERVAL,
+ LABEL,
+ REAL,
+ SYMBOL,
+ ANIMATETRANSFORM,
+ EM,
+ MENUITEM,
+ PARAM,
+ ARCTAN,
+ ANIMATION,
+ ANIMATEMOTION,
+ FN,
+ CAPTION,
+ DOMAIN,
+ IN,
+ HKERN,
+ MN,
+ LAPLACIAN,
+ MEDIAN,
+ MACTION,
+ OPTION,
+ PATTERN,
+ SPAN,
+ TSPAN,
+ VKERN,
+ MO,
+ VIDEO,
+ FEDISPLACEMENTMAP,
+ MALIGNGROUP,
+ MSUP,
+ OPTGROUP,
+ STOP,
+ BR,
+ ANIMATECOLOR,
+ CENTER,
+ HR,
+ FECOMPONENTTRANSFER,
+ FOOTER,
+ FEGAUSSIANBLUR,
+ HANDLER,
+ LISTENER,
+ };
+ private final static int[] ELEMENT_HASHES = {
+ 1914900309,
+ 1753057319,
+ 2001349704,
+ 1681770564,
+ 1818700314,
+ 1982935782,
+ 2007257240,
+ 58773795,
+ 1747176599,
+ 1783210839,
+ 1898130486,
+ 1971457766,
+ 1990969429,
+ 2005181733,
+ 2055514836,
+ 54061139,
+ 62390273,
+ 1730150402,
+ 1749395095,
+ 1757137429,
+ 1800730821,
+ 1870135298,
+ 1903302038,
+ 1965115924,
+ 1971981018,
+ 1988486811,
+ 1999745104,
+ 2002882873,
+ 2005925890,
+ 2008340774,
+ 2082727685,
+ 51965171,
+ 57200451,
+ 60350803,
+ 69730305,
+ 1703292116,
+ 1733890180,
+ 1748355193,
+ 1749813541,
+ 1754894485,
+ 1765431364,
+ 1797544247,
+ 1806799156,
+ 1857653029,
+ 1881613047,
+ 1899272521,
+ 1906087319,
+ 1938172967,
+ 1967795910,
+ 1971467002,
+ 1974775352,
+ 1984294038,
+ 1988972590,
+ 1998585858,
+ 2000825752,
+ 2001392796,
+ 2004557976,
+ 2005543977,
+ 2006560839,
+ 2008125638,
+ 2009706573,
+ 2068523853,
+ 2087049448,
+ 51434643,
+ 52488851,
+ 56151587,
+ 57210387,
+ 59826259,
+ 60354131,
+ 63438849,
+ 926941186,
+ 1686489160,
+ 1715300574,
+ 1732381397,
+ 1737099991,
+ 1748100148,
+ 1748642422,
+ 1749715159,
+ 1751288021,
+ 1753479494,
+ 1756098852,
+ 1757268168,
+ 1773295687,
+ 1790207270,
+ 1798417460,
+ 1803929861,
+ 1807599880,
+ 1854228692,
+ 1867061545,
+ 1874053333,
+ 1887743720,
+ 1898753862,
+ 1900845386,
+ 1904412884,
+ 1907661127,
+ 1932928296,
+ 1941178676,
+ 1966386470,
+ 1968836118,
+ 1971465813,
+ 1971703386,
+ 1973420034,
+ 1982106678,
+ 1983533124,
+ 1986351224,
+ 1988502165,
+ 1990037800,
+ 1991350601,
+ 1998883894,
+ 2000439531,
+ 2001281328,
+ 2001349736,
+ 2001495140,
+ 2003183333,
+ 2004719812,
+ 2005279787,
+ 2005719336,
+ 2006036556,
+ 2006896969,
+ 2007781534,
+ 2008165414,
+ 2008994116,
+ 2041712436,
+ 2060065124,
+ 2070023911,
+ 2085266636,
+ 2092255447,
+ 50910499,
+ 51957043,
+ 52485715,
+ 53012355,
+ 55110883,
+ 56680499,
+ 57206291,
+ 57732851,
+ 59768833,
+ 60345427,
+ 60352083,
+ 61395251,
+ 62973651,
+ 67633153,
+ 893386754,
+ 960495618,
+ 1682547543,
+ 1689922072,
+ 1713515574,
+ 1716349149,
+ 1731545140,
+ 1733076167,
+ 1736576231,
+ 1740181637,
+ 1747814436,
+ 1748228205,
+ 1748607578,
+ 1748879564,
+ 1749656156,
+ 1749801286,
+ 1749917205,
+ 1751493207,
+ 1753343188,
+ 1754031332,
+ 1755148615,
+ 1756600614,
+ 1757157700,
+ 1758044696,
+ 1766992520,
+ 1781815495,
+ 1783388498,
+ 1797368887,
+ 1797628983,
+ 1798686984,
+ 1803876557,
+ 1805502724,
+ 1806981428,
+ 1817013469,
+ 1820327938,
+ 1854245076,
+ 1865714391,
+ 1868312196,
+ 1873281026,
+ 1881288348,
+ 1884120164,
+ 1897398274,
+ 1898223946,
+ 1899170008,
+ 1899796819,
+ 1902116866,
+ 1904283860,
+ 1904946933,
+ 1907085604,
+ 1908709605,
+ 1925049415,
+ 1935549734,
+ 1938817026,
+ 1948778498,
+ 1965634084,
+ 1967760215,
+ 1967957189,
+ 1970798594,
+ 1971461414,
+ 1971466997,
+ 1971628838,
+ 1971938532,
+ 1973040373,
+ 1974771450,
+ 1976348214,
+ 1982173479,
+ 1983002201,
+ 1983633431,
+ 1986140359,
+ 1986527234,
+ 1988486813,
+ 1988763672,
+ 1989812374,
+ 1990074116,
+ 1990969577,
+ 1991909525,
+ 1998724870,
+ 1999397992,
+ 2000158722,
+ 2000525512,
+ 2000965834,
+ 2001309869,
+ 2001349720,
+ 2001392795,
+ 2001392798,
+ 2002780162,
+ 2003062853,
+ 2004557973,
+ 2004635806,
+ 2005160150,
+ 2005231925,
+ 2005324101,
+ 2005543979,
+ 2005766372,
+ 2006028454,
+ 2006329158,
+ 2006592552,
+ 2006974466,
+ 2007601444,
+ 2007803172,
+ 2008133709,
+ 2008325940,
+ 2008851557,
+ 2009276567,
+ 2021937364,
+ 2051837468,
+ 2055515017,
+ 2066000646,
+ 2068523856,
+ 2072193862,
+ 2083120164,
+ 2087012585,
+ 2091479332,
+ 2092557349,
+ 50908899,
+ 50916387,
+ 51438659,
+ 51961587,
+ 51965683,
+ 52486755,
+ 52490899,
+ 54054451,
+ 55104723,
+ 55111395,
+ 56677619,
+ 56682579,
+ 57205395,
+ 57207619,
+ 57731155,
+ 57733651,
+ 59244545,
+ 59821379,
+ 60345171,
+ 60347747,
+ 60351123,
+ 60352339,
+ 60875283,
+ 61925907,
+ 62450211,
+ 62974707,
+ 67108865,
+ 68681729,
+ 876609538,
+ 910163970,
+ 943718402,
+ 1679960596,
+ 1682186266,
+ 1685703382,
+ 1686491348,
+ 1699324759,
+ 1703936002,
+ 1713736758,
+ 1715310660,
+ 1719741029,
+ 1730965751,
+ 1732069431,
+ 1733054663,
+ 1733372532,
+ 1736200310,
+ 1736576583,
+ 1738539010,
+ 1747048757,
+ 1747306711,
+ 1747838298,
+ 1748225318,
+ 1748346119,
+ 1748359220,
+ 1748621670,
+ 1748846791,
+ 1749272732,
+ 1749649513,
+ 1749673195,
+ 1749723735,
+ 1749813486,
+ 1749905526,
+ 1749932347,
+ 1751386406,
+ 1752979652,
+ 1753319686,
+ 1753467414,
+ 1753588936,
+ 1754634617,
+ 1755076808,
+ 1755158905,
+ 1756474198,
+ 1756625221,
+ 1757146773,
+ 1757259017,
+ 1757293380,
+ 1763839627,
+ 1766632184,
+ 1771722827,
+ 1773808452,
+ 1782357526,
+ 1783388497,
+ 1786534215,
+ 1797361975,
+ 1797540167,
+ 1797585096,
+ 1797645367,
+ 1798677556,
+ 1798693940,
+ 1803876550,
+ 1803929812,
+ 1805233752,
+ 1805647874,
+ 1806806678,
+ 1807501636,
+ 1813512194,
+ 1818230786,
+ 1818755074,
+ 1853642948,
+ 1854228698,
+ 1857622310,
+ 1864368130,
+ 1865773108,
+ 1867237670,
+ 1868641064,
+ 1870268949,
+ 1873350948,
+ 1874102998,
+ 1881498736,
+ 1881669634,
+ 1887579800,
+ 1889085973,
+ 1897999926,
+ 1898223945,
+ 1898223949,
+ 1898971138,
+ 1899272519,
+ 1899694294,
+ 1900544002,
+ 1901940917,
+ 1902641154,
+ 1903761465,
+ 1904285766,
+ 1904515399,
+ 1905563974,
+ 1906135367,
+ 1907435316,
+ 1907959605,
+ 1909280949,
+ 1919418370,
+ 1925844629,
+ 1934172497,
+ 1938171179,
+ 1938173140,
+ 1939219752,
+ 1941221172,
+ 1963982850,
+ 1965334268,
+ 1966223078,
+ 1967128578,
+ 1967788867,
+ 1967795958,
+ 1968053806,
+ 1968840263,
+ 1970938456,
+ };
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/ErrorReportingTokenizer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/ErrorReportingTokenizer.java
new file mode 100644
index 000000000..437e83031
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/ErrorReportingTokenizer.java
@@ -0,0 +1,772 @@
+/*
+ * Copyright (c) 2009-2013 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import java.util.HashMap;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import nu.validator.htmlparser.annotation.Inline;
+import nu.validator.htmlparser.annotation.NoLength;
+import nu.validator.htmlparser.common.TokenHandler;
+import nu.validator.htmlparser.common.TransitionHandler;
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+
+public class ErrorReportingTokenizer extends Tokenizer {
+
+ /**
+ * Magic value for UTF-16 operations.
+ */
+ private static final int SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00);
+
+ /**
+ * The policy for non-space non-XML characters.
+ */
+ private XmlViolationPolicy contentNonXmlCharPolicy = XmlViolationPolicy.ALTER_INFOSET;
+
+ /**
+ * Keeps track of PUA warnings.
+ */
+ private boolean alreadyWarnedAboutPrivateUseCharacters;
+
+ /**
+ * The current line number in the current resource being parsed. (First line
+ * is 1.) Passed on as locator data.
+ */
+ private int line;
+
+ private int linePrev;
+
+ /**
+ * The current column number in the current resource being tokenized. (First
+ * column is 1, counted by UTF-16 code units.) Passed on as locator data.
+ */
+ private int col;
+
+ private int colPrev;
+
+ private boolean nextCharOnNewLine;
+
+ private char prev;
+
+ private HashMap<String, String> errorProfileMap = null;
+
+ private TransitionHandler transitionHandler = null;
+
+ private int transitionBaseOffset = 0;
+
+ /**
+ * @param tokenHandler
+ * @param newAttributesEachTime
+ */
+ public ErrorReportingTokenizer(TokenHandler tokenHandler,
+ boolean newAttributesEachTime) {
+ super(tokenHandler, newAttributesEachTime);
+ }
+
+ /**
+ * @param tokenHandler
+ */
+ public ErrorReportingTokenizer(TokenHandler tokenHandler) {
+ super(tokenHandler);
+ }
+
+ /**
+ * @see org.xml.sax.Locator#getLineNumber()
+ */
+ public int getLineNumber() {
+ if (line > 0) {
+ return line;
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * @see org.xml.sax.Locator#getColumnNumber()
+ */
+ public int getColumnNumber() {
+ if (col > 0) {
+ return col;
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Sets the contentNonXmlCharPolicy.
+ *
+ * @param contentNonXmlCharPolicy
+ * the contentNonXmlCharPolicy to set
+ */
+ public void setContentNonXmlCharPolicy(
+ XmlViolationPolicy contentNonXmlCharPolicy) {
+ this.contentNonXmlCharPolicy = contentNonXmlCharPolicy;
+ }
+
+ /**
+ * Sets the errorProfile.
+ *
+ * @param errorProfile
+ */
+ public void setErrorProfile(HashMap<String, String> errorProfileMap) {
+ this.errorProfileMap = errorProfileMap;
+ }
+
+ /**
+ * Reports on an event based on profile selected.
+ *
+ * @param profile
+ * the profile this message belongs to
+ * @param message
+ * the message itself
+ * @throws SAXException
+ */
+ public void note(String profile, String message) throws SAXException {
+ if (errorProfileMap == null)
+ return;
+ String level = errorProfileMap.get(profile);
+ if ("warn".equals(level)) {
+ warn(message);
+ } else if ("err".equals(level)) {
+ err(message);
+ // } else if ("info".equals(level)) {
+ // info(message);
+ }
+ }
+
+ protected void startErrorReporting() throws SAXException {
+ line = linePrev = 0;
+ col = colPrev = 1;
+ nextCharOnNewLine = true;
+ prev = '\u0000';
+ alreadyWarnedAboutPrivateUseCharacters = false;
+ transitionBaseOffset = 0;
+ }
+
+ @Inline protected void silentCarriageReturn() {
+ nextCharOnNewLine = true;
+ lastCR = true;
+ }
+
+ @Inline protected void silentLineFeed() {
+ nextCharOnNewLine = true;
+ }
+
+ /**
+ * Returns the line.
+ *
+ * @return the line
+ */
+ public int getLine() {
+ return line;
+ }
+
+ /**
+ * Returns the col.
+ *
+ * @return the col
+ */
+ public int getCol() {
+ return col;
+ }
+
+ /**
+ * Returns the nextCharOnNewLine.
+ *
+ * @return the nextCharOnNewLine
+ */
+ public boolean isNextCharOnNewLine() {
+ return nextCharOnNewLine;
+ }
+
+ /**
+ * Flushes coalesced character tokens.
+ *
+ * @param buf
+ * TODO
+ * @param pos
+ * TODO
+ *
+ * @throws SAXException
+ */
+ @Override protected void flushChars(char[] buf, int pos)
+ throws SAXException {
+ if (pos > cstart) {
+ int currLine = line;
+ int currCol = col;
+ line = linePrev;
+ col = colPrev;
+ tokenHandler.characters(buf, cstart, pos - cstart);
+ line = currLine;
+ col = currCol;
+ }
+ cstart = 0x7fffffff;
+ }
+
+ @Override protected char checkChar(@NoLength char[] buf, int pos)
+ throws SAXException {
+ linePrev = line;
+ colPrev = col;
+ if (nextCharOnNewLine) {
+ line++;
+ col = 1;
+ nextCharOnNewLine = false;
+ } else {
+ col++;
+ }
+
+ char c = buf[pos];
+ switch (c) {
+ case '\u0000':
+ err("Saw U+0000 in stream.");
+ case '\t':
+ case '\r':
+ case '\n':
+ break;
+ case '\u000C':
+ if (contentNonXmlCharPolicy == XmlViolationPolicy.FATAL) {
+ fatal("This document is not mappable to XML 1.0 without data loss due to "
+ + toUPlusString(c)
+ + " which is not a legal XML 1.0 character.");
+ } else {
+ if (contentNonXmlCharPolicy == XmlViolationPolicy.ALTER_INFOSET) {
+ c = buf[pos] = ' ';
+ }
+ warn("This document is not mappable to XML 1.0 without data loss due to "
+ + toUPlusString(c)
+ + " which is not a legal XML 1.0 character.");
+ }
+ break;
+ default:
+ if ((c & 0xFC00) == 0xDC00) {
+ // Got a low surrogate. See if prev was high
+ // surrogate
+ if ((prev & 0xFC00) == 0xD800) {
+ int intVal = (prev << 10) + c + SURROGATE_OFFSET;
+ if ((intVal & 0xFFFE) == 0xFFFE) {
+ err("Astral non-character.");
+ }
+ if (isAstralPrivateUse(intVal)) {
+ warnAboutPrivateUseChar();
+ }
+ }
+ } else if ((c < ' ' || ((c & 0xFFFE) == 0xFFFE))) {
+ switch (contentNonXmlCharPolicy) {
+ case FATAL:
+ fatal("Forbidden code point " + toUPlusString(c)
+ + ".");
+ break;
+ case ALTER_INFOSET:
+ c = buf[pos] = '\uFFFD';
+ // fall through
+ case ALLOW:
+ err("Forbidden code point " + toUPlusString(c)
+ + ".");
+ }
+ } else if ((c >= '\u007F') && (c <= '\u009F')
+ || (c >= '\uFDD0') && (c <= '\uFDEF')) {
+ err("Forbidden code point " + toUPlusString(c) + ".");
+ } else if (isPrivateUse(c)) {
+ warnAboutPrivateUseChar();
+ }
+ }
+ prev = c;
+ return c;
+ }
+
+ /**
+ * @throws SAXException
+ * @see nu.validator.htmlparser.impl.Tokenizer#transition(int, int, boolean,
+ * int)
+ */
+ @Override protected int transition(int from, int to, boolean reconsume,
+ int pos) throws SAXException {
+ if (transitionHandler != null) {
+ transitionHandler.transition(from, to, reconsume,
+ transitionBaseOffset + pos);
+ }
+ return to;
+ }
+
+ private String toUPlusString(int c) {
+ String hexString = Integer.toHexString(c);
+ switch (hexString.length()) {
+ case 1:
+ return "U+000" + hexString;
+ case 2:
+ return "U+00" + hexString;
+ case 3:
+ return "U+0" + hexString;
+ default:
+ return "U+" + hexString;
+ }
+ }
+
+ /**
+ * Emits a warning about private use characters if the warning has not been
+ * emitted yet.
+ *
+ * @throws SAXException
+ */
+ private void warnAboutPrivateUseChar() throws SAXException {
+ if (!alreadyWarnedAboutPrivateUseCharacters) {
+ warn("Document uses the Unicode Private Use Area(s), which should not be used in publicly exchanged documents. (Charmod C073)");
+ alreadyWarnedAboutPrivateUseCharacters = true;
+ }
+ }
+
+ /**
+ * Tells if the argument is a BMP PUA character.
+ *
+ * @param c
+ * the UTF-16 code unit to check
+ * @return <code>true</code> if PUA character
+ */
+ private boolean isPrivateUse(char c) {
+ return c >= '\uE000' && c <= '\uF8FF';
+ }
+
+ /**
+ * Tells if the argument is an astral PUA character.
+ *
+ * @param c
+ * the code point to check
+ * @return <code>true</code> if astral private use
+ */
+ private boolean isAstralPrivateUse(int c) {
+ return (c >= 0xF0000 && c <= 0xFFFFD)
+ || (c >= 0x100000 && c <= 0x10FFFD);
+ }
+
+ @Override protected void errGarbageAfterLtSlash() throws SAXException {
+ err("Garbage after \u201C</\u201D.");
+ }
+
+ @Override protected void errLtSlashGt() throws SAXException {
+ err("Saw \u201C</>\u201D. Probable causes: Unescaped \u201C<\u201D (escape as \u201C&lt;\u201D) or mistyped end tag.");
+ }
+
+ @Override protected void errWarnLtSlashInRcdata() throws SAXException {
+ if (html4) {
+ err((stateSave == Tokenizer.DATA ? "CDATA" : "RCDATA")
+ + " element \u201C"
+ + endTagExpectation
+ + "\u201D contained the string \u201C</\u201D, but it was not the start of the end tag. (HTML4-only error)");
+ } else {
+ warn((stateSave == Tokenizer.DATA ? "CDATA" : "RCDATA")
+ + " element \u201C"
+ + endTagExpectation
+ + "\u201D contained the string \u201C</\u201D, but this did not close the element.");
+ }
+ }
+
+ @Override protected void errHtml4LtSlashInRcdata(char folded)
+ throws SAXException {
+ if (html4 && (index > 0 || (folded >= 'a' && folded <= 'z'))
+ && ElementName.IFRAME != endTagExpectation) {
+ err((stateSave == Tokenizer.DATA ? "CDATA" : "RCDATA")
+ + " element \u201C"
+ + endTagExpectation.getName()
+ + "\u201D contained the string \u201C</\u201D, but it was not the start of the end tag. (HTML4-only error)");
+ }
+ }
+
+ @Override protected void errCharRefLacksSemicolon() throws SAXException {
+ err("Character reference was not terminated by a semicolon.");
+ }
+
+ @Override protected void errNoDigitsInNCR() throws SAXException {
+ err("No digits after \u201C" + strBufToString() + "\u201D.");
+ }
+
+ @Override protected void errGtInSystemId() throws SAXException {
+ err("\u201C>\u201D in system identifier.");
+ }
+
+ @Override protected void errGtInPublicId() throws SAXException {
+ err("\u201C>\u201D in public identifier.");
+ }
+
+ @Override protected void errNamelessDoctype() throws SAXException {
+ err("Nameless doctype.");
+ }
+
+ @Override protected void errConsecutiveHyphens() throws SAXException {
+ err("Consecutive hyphens did not terminate a comment. \u201C--\u201D is not permitted inside a comment, but e.g. \u201C- -\u201D is.");
+ }
+
+ @Override protected void errPrematureEndOfComment() throws SAXException {
+ err("Premature end of comment. Use \u201C-->\u201D to end a comment properly.");
+ }
+
+ @Override protected void errBogusComment() throws SAXException {
+ err("Bogus comment.");
+ }
+
+ @Override protected void errUnquotedAttributeValOrNull(char c)
+ throws SAXException {
+ switch (c) {
+ case '<':
+ err("\u201C<\u201D in an unquoted attribute value. Probable cause: Missing \u201C>\u201D immediately before.");
+ return;
+ case '`':
+ err("\u201C`\u201D in an unquoted attribute value. Probable cause: Using the wrong character as a quote.");
+ return;
+ case '\uFFFD':
+ return;
+ default:
+ err("\u201C"
+ + c
+ + "\u201D in an unquoted attribute value. Probable causes: Attributes running together or a URL query string in an unquoted attribute value.");
+ return;
+ }
+ }
+
+ @Override protected void errSlashNotFollowedByGt() throws SAXException {
+ err("A slash was not immediately followed by \u201C>\u201D.");
+ }
+
+ @Override protected void errHtml4XmlVoidSyntax() throws SAXException {
+ if (html4) {
+ err("The \u201C/>\u201D syntax on void elements is not allowed. (This is an HTML4-only error.)");
+ }
+ }
+
+ @Override protected void errNoSpaceBetweenAttributes() throws SAXException {
+ err("No space between attributes.");
+ }
+
+ @Override protected void errHtml4NonNameInUnquotedAttribute(char c)
+ throws SAXException {
+ if (html4
+ && !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+ || (c >= '0' && c <= '9') || c == '.' || c == '-'
+ || c == '_' || c == ':')) {
+ err("Non-name character in an unquoted attribute value. (This is an HTML4-only error.)");
+ }
+ }
+
+ @Override protected void errLtOrEqualsOrGraveInUnquotedAttributeOrNull(
+ char c) throws SAXException {
+ switch (c) {
+ case '=':
+ err("\u201C=\u201D at the start of an unquoted attribute value. Probable cause: Stray duplicate equals sign.");
+ return;
+ case '<':
+ err("\u201C<\u201D at the start of an unquoted attribute value. Probable cause: Missing \u201C>\u201D immediately before.");
+ return;
+ case '`':
+ err("\u201C`\u201D at the start of an unquoted attribute value. Probable cause: Using the wrong character as a quote.");
+ return;
+ }
+ }
+
+ @Override protected void errAttributeValueMissing() throws SAXException {
+ err("Attribute value missing.");
+ }
+
+ @Override protected void errBadCharBeforeAttributeNameOrNull(char c)
+ throws SAXException {
+ if (c == '<') {
+ err("Saw \u201C<\u201D when expecting an attribute name. Probable cause: Missing \u201C>\u201D immediately before.");
+ } else if (c == '=') {
+ errEqualsSignBeforeAttributeName();
+ } else if (c != '\uFFFD') {
+ errQuoteBeforeAttributeName(c);
+ }
+ }
+
+ @Override protected void errEqualsSignBeforeAttributeName()
+ throws SAXException {
+ err("Saw \u201C=\u201D when expecting an attribute name. Probable cause: Attribute name missing.");
+ }
+
+ @Override protected void errBadCharAfterLt(char c) throws SAXException {
+ err("Bad character \u201C"
+ + c
+ + "\u201D after \u201C<\u201D. Probable cause: Unescaped \u201C<\u201D. Try escaping it as \u201C&lt;\u201D.");
+ }
+
+ @Override protected void errLtGt() throws SAXException {
+ err("Saw \u201C<>\u201D. Probable causes: Unescaped \u201C<\u201D (escape as \u201C&lt;\u201D) or mistyped start tag.");
+ }
+
+ @Override protected void errProcessingInstruction() throws SAXException {
+ err("Saw \u201C<?\u201D. Probable cause: Attempt to use an XML processing instruction in HTML. (XML processing instructions are not supported in HTML.)");
+ }
+
+ @Override protected void errUnescapedAmpersandInterpretedAsCharacterReference()
+ throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ SAXParseException spe = new SAXParseException(
+ "The string following \u201C&\u201D was interpreted as a character reference. (\u201C&\u201D probably should have been escaped as \u201C&amp;\u201D.)",
+ ampersandLocation);
+ errorHandler.error(spe);
+ }
+
+ @Override protected void errNotSemicolonTerminated() throws SAXException {
+ err("Named character reference was not terminated by a semicolon. (Or \u201C&\u201D should have been escaped as \u201C&amp;\u201D.)");
+ }
+
+ @Override protected void errNoNamedCharacterMatch() throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ SAXParseException spe = new SAXParseException(
+ "\u201C&\u201D did not start a character reference. (\u201C&\u201D probably should have been escaped as \u201C&amp;\u201D.)",
+ ampersandLocation);
+ errorHandler.error(spe);
+ }
+
+ @Override protected void errQuoteBeforeAttributeName(char c)
+ throws SAXException {
+ err("Saw \u201C"
+ + c
+ + "\u201D when expecting an attribute name. Probable cause: \u201C=\u201D missing immediately before.");
+ }
+
+ @Override protected void errQuoteOrLtInAttributeNameOrNull(char c)
+ throws SAXException {
+ if (c == '<') {
+ err("\u201C<\u201D in attribute name. Probable cause: \u201C>\u201D missing immediately before.");
+ } else if (c != '\uFFFD') {
+ err("Quote \u201C"
+ + c
+ + "\u201D in attribute name. Probable cause: Matching quote missing somewhere earlier.");
+ }
+ }
+
+ @Override protected void errExpectedPublicId() throws SAXException {
+ err("Expected a public identifier but the doctype ended.");
+ }
+
+ @Override protected void errBogusDoctype() throws SAXException {
+ err("Bogus doctype.");
+ }
+
+ @Override protected void maybeWarnPrivateUseAstral() throws SAXException {
+ if (errorHandler != null && isAstralPrivateUse(value)) {
+ warnAboutPrivateUseChar();
+ }
+ }
+
+ @Override protected void maybeWarnPrivateUse(char ch) throws SAXException {
+ if (errorHandler != null && isPrivateUse(ch)) {
+ warnAboutPrivateUseChar();
+ }
+ }
+
+ @Override protected void maybeErrAttributesOnEndTag(HtmlAttributes attrs)
+ throws SAXException {
+ if (attrs.getLength() != 0) {
+ /*
+ * When an end tag token is emitted with attributes, that is a parse
+ * error.
+ */
+ err("End tag had attributes.");
+ }
+ }
+
+ @Override protected void maybeErrSlashInEndTag(boolean selfClosing)
+ throws SAXException {
+ if (selfClosing && endTag) {
+ err("Stray \u201C/\u201D at the end of an end tag.");
+ }
+ }
+
+ @Override protected char errNcrNonCharacter(char ch) throws SAXException {
+ switch (contentNonXmlCharPolicy) {
+ case FATAL:
+ fatal("Character reference expands to a non-character ("
+ + toUPlusString((char) value) + ").");
+ break;
+ case ALTER_INFOSET:
+ ch = '\uFFFD';
+ // fall through
+ case ALLOW:
+ err("Character reference expands to a non-character ("
+ + toUPlusString((char) value) + ").");
+ }
+ return ch;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.Tokenizer#errAstralNonCharacter(int)
+ */
+ @Override protected void errAstralNonCharacter(int ch) throws SAXException {
+ err("Character reference expands to an astral non-character ("
+ + toUPlusString(value) + ").");
+ }
+
+ @Override protected void errNcrSurrogate() throws SAXException {
+ err("Character reference expands to a surrogate.");
+ }
+
+ @Override protected char errNcrControlChar(char ch) throws SAXException {
+ switch (contentNonXmlCharPolicy) {
+ case FATAL:
+ fatal("Character reference expands to a control character ("
+ + toUPlusString((char) value) + ").");
+ break;
+ case ALTER_INFOSET:
+ ch = '\uFFFD';
+ // fall through
+ case ALLOW:
+ err("Character reference expands to a control character ("
+ + toUPlusString((char) value) + ").");
+ }
+ return ch;
+ }
+
+ @Override protected void errNcrCr() throws SAXException {
+ err("A numeric character reference expanded to carriage return.");
+ }
+
+ @Override protected void errNcrInC1Range() throws SAXException {
+ err("A numeric character reference expanded to the C1 controls range.");
+ }
+
+ @Override protected void errEofInPublicId() throws SAXException {
+ err("End of file inside public identifier.");
+ }
+
+ @Override protected void errEofInComment() throws SAXException {
+ err("End of file inside comment.");
+ }
+
+ @Override protected void errEofInDoctype() throws SAXException {
+ err("End of file inside doctype.");
+ }
+
+ @Override protected void errEofInAttributeValue() throws SAXException {
+ err("End of file reached when inside an attribute value. Ignoring tag.");
+ }
+
+ @Override protected void errEofInAttributeName() throws SAXException {
+ err("End of file occurred in an attribute name. Ignoring tag.");
+ }
+
+ @Override protected void errEofWithoutGt() throws SAXException {
+ err("Saw end of file without the previous tag ending with \u201C>\u201D. Ignoring tag.");
+ }
+
+ @Override protected void errEofInTagName() throws SAXException {
+ err("End of file seen when looking for tag name. Ignoring tag.");
+ }
+
+ @Override protected void errEofInEndTag() throws SAXException {
+ err("End of file inside end tag. Ignoring tag.");
+ }
+
+ @Override protected void errEofAfterLt() throws SAXException {
+ err("End of file after \u201C<\u201D.");
+ }
+
+ @Override protected void errNcrOutOfRange() throws SAXException {
+ err("Character reference outside the permissible Unicode range.");
+ }
+
+ @Override protected void errNcrUnassigned() throws SAXException {
+ err("Character reference expands to a permanently unassigned code point.");
+ }
+
+ @Override protected void errDuplicateAttribute() throws SAXException {
+ err("Duplicate attribute \u201C"
+ + attributeName.getLocal(AttributeName.HTML) + "\u201D.");
+ }
+
+ @Override protected void errEofInSystemId() throws SAXException {
+ err("End of file inside system identifier.");
+ }
+
+ @Override protected void errExpectedSystemId() throws SAXException {
+ err("Expected a system identifier but the doctype ended.");
+ }
+
+ @Override protected void errMissingSpaceBeforeDoctypeName()
+ throws SAXException {
+ err("Missing space before doctype name.");
+ }
+
+ @Override protected void errHyphenHyphenBang() throws SAXException {
+ err("\u201C--!\u201D found in comment.");
+ }
+
+ @Override protected void errNcrControlChar() throws SAXException {
+ err("Character reference expands to a control character ("
+ + toUPlusString((char) value) + ").");
+ }
+
+ @Override protected void errNcrZero() throws SAXException {
+ err("Character reference expands to zero.");
+ }
+
+ @Override protected void errNoSpaceBetweenDoctypeSystemKeywordAndQuote()
+ throws SAXException {
+ err("No space between the doctype \u201CSYSTEM\u201D keyword and the quote.");
+ }
+
+ @Override protected void errNoSpaceBetweenPublicAndSystemIds()
+ throws SAXException {
+ err("No space between the doctype public and system identifiers.");
+ }
+
+ @Override protected void errNoSpaceBetweenDoctypePublicKeywordAndQuote()
+ throws SAXException {
+ err("No space between the doctype \u201CPUBLIC\u201D keyword and the quote.");
+ }
+
+ @Override protected void noteAttributeWithoutValue() throws SAXException {
+ note("xhtml2", "Attribute without value");
+ }
+
+ @Override protected void noteUnquotedAttributeValue() throws SAXException {
+ note("xhtml1", "Unquoted attribute value.");
+ }
+
+ /**
+ * Sets the transitionHandler.
+ *
+ * @param transitionHandler
+ * the transitionHandler to set
+ */
+ public void setTransitionHandler(TransitionHandler transitionHandler) {
+ this.transitionHandler = transitionHandler;
+ }
+
+ /**
+ * Sets an offset to be added to the position reported to
+ * <code>TransitionHandler</code>.
+ *
+ * @param offset
+ * the offset
+ */
+ public void setTransitionBaseOffset(int offset) {
+ this.transitionBaseOffset = offset;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/HotSpotWorkaround.txt b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/HotSpotWorkaround.txt
new file mode 100644
index 000000000..c389a8cac
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/HotSpotWorkaround.txt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+ /**
+ * compressed returnValue:
+ * int returnState = returnValue >> 33
+ * boolean breakOuterState = ((returnValue >> 32) & 0x1) != 0)
+ * int pos = returnValue & 0xFFFFFFFF // same as (int)returnValue
+ */
+ @SuppressWarnings("unused") private long workAroundHotSpotHugeMethodLimit(
+ int state, char c, int pos, @NoLength char[] buf,
+ boolean reconsume, int returnState, int endPos) throws SAXException {
+ stateloop: for (;;) {
+ switch (state) {
+ // BEGIN HOTSPOT WORKAROUND
+ default:
+ long returnStateAndPos = workAroundHotSpotHugeMethodLimit(
+ state, c, pos, buf, reconsume, returnState, endPos);
+ pos = (int)returnStateAndPos; // 5.1.3 in the Java spec
+ returnState = (int)(returnStateAndPos >> 33);
+ state = stateSave;
+ if ( (pos == endPos) || ( (((int)(returnStateAndPos >> 32)) & 0x1) != 0) ) {
+ break stateloop;
+ }
+ continue stateloop;
+ // END HOTSPOT WORKAROUND
+ default:
+ assert !reconsume : "Must not reconsume when returning from HotSpot workaround.";
+ stateSave = state;
+ return (((long)returnState) << 33) | pos;
+ }
+ }
+ assert !reconsume : "Must not reconsume when returning from HotSpot workaround.";
+ stateSave = state;
+ return (((long)returnState) << 33) | (1L << 32) | pos ;
+ }
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/HtmlAttributes.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/HtmlAttributes.java
new file mode 100644
index 000000000..c24ae74f2
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/HtmlAttributes.java
@@ -0,0 +1,520 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2011 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import nu.validator.htmlparser.annotation.IdType;
+import nu.validator.htmlparser.annotation.Local;
+import nu.validator.htmlparser.annotation.NsUri;
+import nu.validator.htmlparser.annotation.Prefix;
+import nu.validator.htmlparser.annotation.QName;
+import nu.validator.htmlparser.common.Interner;
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+
+/**
+ * Be careful with this class. QName is the name in from HTML tokenization.
+ * Otherwise, please refer to the interface doc.
+ *
+ * @version $Id: AttributesImpl.java 206 2008-03-20 14:09:29Z hsivonen $
+ * @author hsivonen
+ */
+public final class HtmlAttributes implements Attributes {
+
+ private static final AttributeName[] EMPTY_ATTRIBUTENAMES = new AttributeName[0];
+
+ private static final String[] EMPTY_STRINGS = new String[0];
+
+ public static final HtmlAttributes EMPTY_ATTRIBUTES = new HtmlAttributes(
+ AttributeName.HTML);
+
+ private int mode;
+
+ private int length;
+
+ private AttributeName[] names;
+
+ private String[] values;
+
+ private String idValue;
+
+ private int xmlnsLength;
+
+ private AttributeName[] xmlnsNames;
+
+ private String[] xmlnsValues;
+
+ public HtmlAttributes(int mode) {
+ this.mode = mode;
+ this.length = 0;
+ /*
+ * The length of 5 covers covers 98.3% of elements
+ * according to Hixie.
+ */
+ this.names = new AttributeName[5];
+ this.values = new String[5];
+
+ this.idValue = null;
+
+ this.xmlnsLength = 0;
+
+ this.xmlnsNames = HtmlAttributes.EMPTY_ATTRIBUTENAMES;
+
+ this.xmlnsValues = HtmlAttributes.EMPTY_STRINGS;
+
+ }
+
+ /**
+ * Only use with a static argument
+ *
+ * @param name
+ * @return
+ */
+ public int getIndex(AttributeName name) {
+ for (int i = 0; i < length; i++) {
+ if (names[i] == name) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Only use with static argument.
+ *
+ * @see org.xml.sax.Attributes#getValue(java.lang.String)
+ */
+ public String getValue(AttributeName name) {
+ int index = getIndex(name);
+ if (index == -1) {
+ return null;
+ } else {
+ return getValueNoBoundsCheck(index);
+ }
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ /**
+ * Variant of <code>getLocalName(int index)</code> without bounds check.
+ * @param index a valid attribute index
+ * @return the local name at index
+ */
+ public @Local String getLocalNameNoBoundsCheck(int index) {
+ return names[index].getLocal(mode);
+ }
+
+ /**
+ * Variant of <code>getURI(int index)</code> without bounds check.
+ * @param index a valid attribute index
+ * @return the namespace URI at index
+ */
+ public @NsUri String getURINoBoundsCheck(int index) {
+ return names[index].getUri(mode);
+ }
+
+ /**
+ * Variant of <code>getPrefix(int index)</code> without bounds check.
+ * @param index a valid attribute index
+ * @return the namespace prefix at index
+ */
+ public @Prefix String getPrefixNoBoundsCheck(int index) {
+ return names[index].getPrefix(mode);
+ }
+
+ /**
+ * Variant of <code>getValue(int index)</code> without bounds check.
+ * @param index a valid attribute index
+ * @return the attribute value at index
+ */
+ public String getValueNoBoundsCheck(int index) {
+ return values[index];
+ }
+
+ /**
+ * Variant of <code>getAttributeName(int index)</code> without bounds check.
+ * @param index a valid attribute index
+ * @return the attribute name at index
+ */
+ public AttributeName getAttributeNameNoBoundsCheck(int index) {
+ return names[index];
+ }
+
+
+ /**
+ * Variant of <code>getQName(int index)</code> without bounds check.
+ * @param index a valid attribute index
+ * @return the QName at index
+ */
+ public @QName String getQNameNoBoundsCheck(int index) {
+ return names[index].getQName(mode);
+ }
+
+ /**
+ * Variant of <code>getType(int index)</code> without bounds check.
+ * @param index a valid attribute index
+ * @return the attribute type at index
+ */
+ public @IdType String getTypeNoBoundsCheck(int index) {
+ return (names[index] == AttributeName.ID) ? "ID" : "CDATA";
+ }
+
+ public int getIndex(String qName) {
+ for (int i = 0; i < length; i++) {
+ if (names[i].getQName(mode).equals(qName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int getIndex(String uri, String localName) {
+ for (int i = 0; i < length; i++) {
+ if (names[i].getLocal(mode).equals(localName)
+ && names[i].getUri(mode).equals(uri)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public @IdType String getType(String qName) {
+ int index = getIndex(qName);
+ if (index == -1) {
+ return null;
+ } else {
+ return getType(index);
+ }
+ }
+
+ public @IdType String getType(String uri, String localName) {
+ int index = getIndex(uri, localName);
+ if (index == -1) {
+ return null;
+ } else {
+ return getType(index);
+ }
+ }
+
+ public String getValue(String qName) {
+ int index = getIndex(qName);
+ if (index == -1) {
+ return null;
+ } else {
+ return getValue(index);
+ }
+ }
+
+ public String getValue(String uri, String localName) {
+ int index = getIndex(uri, localName);
+ if (index == -1) {
+ return null;
+ } else {
+ return getValue(index);
+ }
+ }
+
+ public @Local String getLocalName(int index) {
+ if (index < length && index >= 0) {
+ return names[index].getLocal(mode);
+ } else {
+ return null;
+ }
+ }
+
+ public @QName String getQName(int index) {
+ if (index < length && index >= 0) {
+ return names[index].getQName(mode);
+ } else {
+ return null;
+ }
+ }
+
+ public @IdType String getType(int index) {
+ if (index < length && index >= 0) {
+ return (names[index] == AttributeName.ID) ? "ID" : "CDATA";
+ } else {
+ return null;
+ }
+ }
+
+ public AttributeName getAttributeName(int index) {
+ if (index < length && index >= 0) {
+ return names[index];
+ } else {
+ return null;
+ }
+ }
+
+ public @NsUri String getURI(int index) {
+ if (index < length && index >= 0) {
+ return names[index].getUri(mode);
+ } else {
+ return null;
+ }
+ }
+
+ public @Prefix String getPrefix(int index) {
+ if (index < length && index >= 0) {
+ return names[index].getPrefix(mode);
+ } else {
+ return null;
+ }
+ }
+
+ public String getValue(int index) {
+ if (index < length && index >= 0) {
+ return values[index];
+ } else {
+ return null;
+ }
+ }
+
+ public String getId() {
+ return idValue;
+ }
+
+ public int getXmlnsLength() {
+ return xmlnsLength;
+ }
+
+ public @Local String getXmlnsLocalName(int index) {
+ if (index < xmlnsLength && index >= 0) {
+ return xmlnsNames[index].getLocal(mode);
+ } else {
+ return null;
+ }
+ }
+
+ public @NsUri String getXmlnsURI(int index) {
+ if (index < xmlnsLength && index >= 0) {
+ return xmlnsNames[index].getUri(mode);
+ } else {
+ return null;
+ }
+ }
+
+ public String getXmlnsValue(int index) {
+ if (index < xmlnsLength && index >= 0) {
+ return xmlnsValues[index];
+ } else {
+ return null;
+ }
+ }
+
+ public int getXmlnsIndex(AttributeName name) {
+ for (int i = 0; i < xmlnsLength; i++) {
+ if (xmlnsNames[i] == name) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public String getXmlnsValue(AttributeName name) {
+ int index = getXmlnsIndex(name);
+ if (index == -1) {
+ return null;
+ } else {
+ return getXmlnsValue(index);
+ }
+ }
+
+ public AttributeName getXmlnsAttributeName(int index) {
+ if (index < xmlnsLength && index >= 0) {
+ return xmlnsNames[index];
+ } else {
+ return null;
+ }
+ }
+
+ void addAttribute(AttributeName name, String value
+ , XmlViolationPolicy xmlnsPolicy
+ ) throws SAXException {
+ if (name == AttributeName.ID) {
+ idValue = value;
+ }
+
+ if (name.isXmlns()) {
+ if (xmlnsNames.length == xmlnsLength) {
+ int newLen = xmlnsLength == 0 ? 2 : xmlnsLength << 1;
+ AttributeName[] newNames = new AttributeName[newLen];
+ System.arraycopy(xmlnsNames, 0, newNames, 0, xmlnsNames.length);
+ xmlnsNames = newNames;
+ String[] newValues = new String[newLen];
+ System.arraycopy(xmlnsValues, 0, newValues, 0, xmlnsValues.length);
+ xmlnsValues = newValues;
+ }
+ xmlnsNames[xmlnsLength] = name;
+ xmlnsValues[xmlnsLength] = value;
+ xmlnsLength++;
+ switch (xmlnsPolicy) {
+ case FATAL:
+ // this is ugly
+ throw new SAXException("Saw an xmlns attribute.");
+ case ALTER_INFOSET:
+ return;
+ case ALLOW:
+ // fall through
+ }
+ }
+
+ if (names.length == length) {
+ int newLen = length << 1; // The first growth covers virtually
+ // 100% of elements according to
+ // Hixie
+ AttributeName[] newNames = new AttributeName[newLen];
+ System.arraycopy(names, 0, newNames, 0, names.length);
+ names = newNames;
+ String[] newValues = new String[newLen];
+ System.arraycopy(values, 0, newValues, 0, values.length);
+ values = newValues;
+ }
+ names[length] = name;
+ values[length] = value;
+ length++;
+ }
+
+ void clear(int m) {
+ for (int i = 0; i < length; i++) {
+ names[i] = null;
+ values[i] = null;
+ }
+ length = 0;
+ mode = m;
+ idValue = null;
+ for (int i = 0; i < xmlnsLength; i++) {
+ xmlnsNames[i] = null;
+ xmlnsValues[i] = null;
+ }
+ xmlnsLength = 0;
+ }
+
+ boolean contains(AttributeName name) {
+ for (int i = 0; i < length; i++) {
+ if (name.equalsAnother(names[i])) {
+ return true;
+ }
+ }
+ for (int i = 0; i < xmlnsLength; i++) {
+ if (name.equalsAnother(xmlnsNames[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void adjustForMath() {
+ mode = AttributeName.MATHML;
+ }
+
+ public void adjustForSvg() {
+ mode = AttributeName.SVG;
+ }
+
+ public HtmlAttributes cloneAttributes(Interner interner)
+ throws SAXException {
+ assert (length == 0
+ && xmlnsLength == 0
+ )
+ || mode == 0 || mode == 3;
+ HtmlAttributes clone = new HtmlAttributes(0);
+ for (int i = 0; i < length; i++) {
+ clone.addAttribute(names[i],
+ values[i]
+ , XmlViolationPolicy.ALLOW
+ );
+ }
+ for (int i = 0; i < xmlnsLength; i++) {
+ clone.addAttribute(xmlnsNames[i], xmlnsValues[i],
+ XmlViolationPolicy.ALLOW);
+ }
+ return clone; // XXX!!!
+ }
+
+ public boolean equalsAnother(HtmlAttributes other) {
+ assert mode == 0 || mode == 3 : "Trying to compare attributes in foreign content.";
+ int otherLength = other.getLength();
+ if (length != otherLength) {
+ return false;
+ }
+ for (int i = 0; i < length; i++) {
+ // Work around the limitations of C++
+ boolean found = false;
+ // The comparing just the local names is OK, since these attribute
+ // holders are both supposed to belong to HTML formatting elements
+ @Local String ownLocal = names[i].getLocal(AttributeName.HTML);
+ for (int j = 0; j < otherLength; j++) {
+ if (ownLocal == other.names[j].getLocal(AttributeName.HTML)) {
+ found = true;
+ if (!values[i].equals(other.values[j])) {
+ return false;
+ }
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void processNonNcNames(TreeBuilder<?> treeBuilder, XmlViolationPolicy namePolicy) throws SAXException {
+ for (int i = 0; i < length; i++) {
+ AttributeName attName = names[i];
+ if (!attName.isNcName(mode)) {
+ String name = attName.getLocal(mode);
+ switch (namePolicy) {
+ case ALTER_INFOSET:
+ names[i] = AttributeName.create(NCName.escapeName(name));
+ // fall through
+ case ALLOW:
+ if (attName != AttributeName.XML_LANG) {
+ treeBuilder.warn("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0.");
+ }
+ break;
+ case FATAL:
+ treeBuilder.fatal("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0.");
+ break;
+ }
+ }
+ }
+ }
+
+ public void merge(HtmlAttributes attributes) throws SAXException {
+ int len = attributes.getLength();
+ for (int i = 0; i < len; i++) {
+ AttributeName name = attributes.getAttributeNameNoBoundsCheck(i);
+ if (!contains(name)) {
+ addAttribute(name, attributes.getValueNoBoundsCheck(i), XmlViolationPolicy.ALLOW);
+ }
+ }
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/LocatorImpl.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/LocatorImpl.java
new file mode 100644
index 000000000..7a559d903
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/LocatorImpl.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2011 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import org.xml.sax.Locator;
+
+public class LocatorImpl implements Locator {
+
+ private final String systemId;
+
+ private final String publicId;
+
+ private final int column;
+
+ private final int line;
+
+ public LocatorImpl(Locator locator) {
+ this.systemId = locator.getSystemId();
+ this.publicId = locator.getPublicId();
+ this.column = locator.getColumnNumber();
+ this.line = locator.getLineNumber();
+ }
+
+ public final int getColumnNumber() {
+ return column;
+ }
+
+ public final int getLineNumber() {
+ return line;
+ }
+
+ public final String getPublicId() {
+ return publicId;
+ }
+
+ public final String getSystemId() {
+ return systemId;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/MetaScanner.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/MetaScanner.java
new file mode 100644
index 000000000..29c5138a4
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/MetaScanner.java
@@ -0,0 +1,856 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2015 Mozilla Foundation
+ * Copyright (c) 2018-2021 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import java.io.IOException;
+
+import nu.validator.htmlparser.annotation.Auto;
+import nu.validator.htmlparser.annotation.Inline;
+import nu.validator.htmlparser.common.ByteReadable;
+
+import org.xml.sax.SAXException;
+
+public abstract class MetaScanner {
+
+ /**
+ * Constant for "charset".
+ */
+ private static final char[] CHARSET = { 'h', 'a', 'r', 's', 'e', 't' };
+
+ /**
+ * Constant for "content".
+ */
+ private static final char[] CONTENT = { 'o', 'n', 't', 'e', 'n', 't' };
+
+ /**
+ * Constant for "http-equiv".
+ */
+ private static final char[] HTTP_EQUIV = { 't', 't', 'p', '-', 'e', 'q',
+ 'u', 'i', 'v' };
+
+ /**
+ * Constant for "content-type".
+ */
+ private static final char[] CONTENT_TYPE = { 'c', 'o', 'n', 't', 'e', 'n',
+ 't', '-', 't', 'y', 'p', 'e' };
+
+ private static final int NO = 0;
+
+ private static final int M = 1;
+
+ private static final int E = 2;
+
+ private static final int T = 3;
+
+ private static final int A = 4;
+
+ private static final int DATA = 0;
+
+ private static final int TAG_OPEN = 1;
+
+ private static final int SCAN_UNTIL_GT = 2;
+
+ private static final int TAG_NAME = 3;
+
+ private static final int BEFORE_ATTRIBUTE_NAME = 4;
+
+ private static final int ATTRIBUTE_NAME = 5;
+
+ private static final int AFTER_ATTRIBUTE_NAME = 6;
+
+ private static final int BEFORE_ATTRIBUTE_VALUE = 7;
+
+ private static final int ATTRIBUTE_VALUE_DOUBLE_QUOTED = 8;
+
+ private static final int ATTRIBUTE_VALUE_SINGLE_QUOTED = 9;
+
+ private static final int ATTRIBUTE_VALUE_UNQUOTED = 10;
+
+ private static final int AFTER_ATTRIBUTE_VALUE_QUOTED = 11;
+
+ private static final int MARKUP_DECLARATION_OPEN = 13;
+
+ private static final int MARKUP_DECLARATION_HYPHEN = 14;
+
+ private static final int COMMENT_START = 15;
+
+ private static final int COMMENT_START_DASH = 16;
+
+ private static final int COMMENT = 17;
+
+ private static final int COMMENT_END_DASH = 18;
+
+ private static final int COMMENT_END = 19;
+
+ private static final int SELF_CLOSING_START_TAG = 20;
+
+ private static final int HTTP_EQUIV_NOT_SEEN = 0;
+
+ private static final int HTTP_EQUIV_CONTENT_TYPE = 1;
+
+ private static final int HTTP_EQUIV_OTHER = 2;
+
+ /**
+ * The data source.
+ */
+ protected ByteReadable readable;
+
+ /**
+ * The state of the state machine that recognizes the tag name "meta".
+ */
+ private int metaState = NO;
+
+ /**
+ * The current position in recognizing the attribute name "content".
+ */
+ private int contentIndex = Integer.MAX_VALUE;
+
+ /**
+ * The current position in recognizing the attribute name "charset".
+ */
+ private int charsetIndex = Integer.MAX_VALUE;
+
+ /**
+ * The current position in recognizing the attribute name "http-equive".
+ */
+ private int httpEquivIndex = Integer.MAX_VALUE;
+
+ /**
+ * The current position in recognizing the attribute value "content-type".
+ */
+ private int contentTypeIndex = Integer.MAX_VALUE;
+
+ /**
+ * The tokenizer state.
+ */
+ protected int stateSave = DATA;
+
+ /**
+ * The currently filled length of strBuf.
+ */
+ private int strBufLen;
+
+ /**
+ * Accumulation buffer for attribute values.
+ */
+ private @Auto char[] strBuf;
+
+ private String content;
+
+ private String charset;
+
+ private int httpEquivState;
+
+ // CPPONLY: private TreeBuilder treeBuilder;
+
+ public MetaScanner(
+ // CPPONLY: TreeBuilder tb
+ ) {
+ this.readable = null;
+ this.metaState = NO;
+ this.contentIndex = Integer.MAX_VALUE;
+ this.charsetIndex = Integer.MAX_VALUE;
+ this.httpEquivIndex = Integer.MAX_VALUE;
+ this.contentTypeIndex = Integer.MAX_VALUE;
+ this.stateSave = DATA;
+ this.strBufLen = 0;
+ this.strBuf = new char[36];
+ this.content = null;
+ this.charset = null;
+ this.httpEquivState = HTTP_EQUIV_NOT_SEEN;
+ // CPPONLY: this.treeBuilder = tb;
+ }
+
+ @SuppressWarnings("unused") private void destructor() {
+ Portability.releaseString(content);
+ Portability.releaseString(charset);
+ }
+
+ // [NOCPP[
+
+ /**
+ * Reads a byte from the data source.
+ *
+ * -1 means end.
+ * @return
+ * @throws IOException
+ */
+ protected int read() throws IOException {
+ return readable.readByte();
+ }
+
+ // ]NOCPP]
+
+ // WARNING When editing this, makes sure the bytecode length shown by javap
+ // stays under 8000 bytes!
+ /**
+ * The runs the meta scanning algorithm.
+ */
+ protected final void stateLoop(int state)
+ throws SAXException, IOException {
+ int c = -1;
+ boolean reconsume = false;
+ stateloop: for (;;) {
+ switch (state) {
+ case DATA:
+ dataloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '<':
+ state = MetaScanner.TAG_OPEN;
+ break dataloop; // FALL THROUGH continue
+ // stateloop;
+ default:
+ continue;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case TAG_OPEN:
+ tagopenloop: for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case 'm':
+ case 'M':
+ metaState = M;
+ state = MetaScanner.TAG_NAME;
+ break tagopenloop;
+ // continue stateloop;
+ case '!':
+ state = MetaScanner.MARKUP_DECLARATION_OPEN;
+ continue stateloop;
+ case '?':
+ case '/':
+ state = MetaScanner.SCAN_UNTIL_GT;
+ continue stateloop;
+ case '>':
+ state = MetaScanner.DATA;
+ continue stateloop;
+ default:
+ if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
+ metaState = NO;
+ state = MetaScanner.TAG_NAME;
+ break tagopenloop;
+ // continue stateloop;
+ }
+ state = MetaScanner.DATA;
+ reconsume = true;
+ continue stateloop;
+ }
+ }
+ // FALL THROUGH DON'T REORDER
+ case TAG_NAME:
+ tagnameloop: for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\u000C':
+ state = MetaScanner.BEFORE_ATTRIBUTE_NAME;
+ break tagnameloop;
+ // continue stateloop;
+ case '/':
+ state = MetaScanner.SELF_CLOSING_START_TAG;
+ continue stateloop;
+ case '>':
+ state = MetaScanner.DATA;
+ continue stateloop;
+ case 'e':
+ case 'E':
+ if (metaState == M) {
+ metaState = E;
+ } else {
+ metaState = NO;
+ }
+ continue;
+ case 't':
+ case 'T':
+ if (metaState == E) {
+ metaState = T;
+ } else {
+ metaState = NO;
+ }
+ continue;
+ case 'a':
+ case 'A':
+ if (metaState == T) {
+ metaState = A;
+ } else {
+ metaState = NO;
+ }
+ continue;
+ default:
+ metaState = NO;
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case BEFORE_ATTRIBUTE_NAME:
+ beforeattributenameloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case -1:
+ break stateloop;
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\u000C':
+ continue;
+ case '/':
+ state = MetaScanner.SELF_CLOSING_START_TAG;
+ continue stateloop;
+ case '>':
+ if (handleTag()) {
+ break stateloop;
+ }
+ state = DATA;
+ continue stateloop;
+ case 'c':
+ case 'C':
+ contentIndex = 0;
+ charsetIndex = 0;
+ httpEquivIndex = Integer.MAX_VALUE;
+ contentTypeIndex = Integer.MAX_VALUE;
+ state = MetaScanner.ATTRIBUTE_NAME;
+ break beforeattributenameloop;
+ case 'h':
+ case 'H':
+ contentIndex = Integer.MAX_VALUE;
+ charsetIndex = Integer.MAX_VALUE;
+ httpEquivIndex = 0;
+ contentTypeIndex = Integer.MAX_VALUE;
+ state = MetaScanner.ATTRIBUTE_NAME;
+ break beforeattributenameloop;
+ default:
+ contentIndex = Integer.MAX_VALUE;
+ charsetIndex = Integer.MAX_VALUE;
+ httpEquivIndex = Integer.MAX_VALUE;
+ contentTypeIndex = Integer.MAX_VALUE;
+ state = MetaScanner.ATTRIBUTE_NAME;
+ break beforeattributenameloop;
+ // continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case ATTRIBUTE_NAME:
+ attributenameloop: for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\u000C':
+ state = MetaScanner.AFTER_ATTRIBUTE_NAME;
+ continue stateloop;
+ case '/':
+ state = MetaScanner.SELF_CLOSING_START_TAG;
+ continue stateloop;
+ case '=':
+ strBufLen = 0;
+ contentTypeIndex = 0;
+ state = MetaScanner.BEFORE_ATTRIBUTE_VALUE;
+ break attributenameloop;
+ // continue stateloop;
+ case '>':
+ if (handleTag()) {
+ break stateloop;
+ }
+ state = MetaScanner.DATA;
+ continue stateloop;
+ default:
+ if (metaState == A) {
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ if (contentIndex < CONTENT.length && c == CONTENT[contentIndex]) {
+ ++contentIndex;
+ } else {
+ contentIndex = Integer.MAX_VALUE;
+ }
+ if (charsetIndex < CHARSET.length && c == CHARSET[charsetIndex]) {
+ ++charsetIndex;
+ } else {
+ charsetIndex = Integer.MAX_VALUE;
+ }
+ if (httpEquivIndex < HTTP_EQUIV.length && c == HTTP_EQUIV[httpEquivIndex]) {
+ ++httpEquivIndex;
+ } else {
+ httpEquivIndex = Integer.MAX_VALUE;
+ }
+ }
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case BEFORE_ATTRIBUTE_VALUE:
+ beforeattributevalueloop: for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\u000C':
+ continue;
+ case '"':
+ state = MetaScanner.ATTRIBUTE_VALUE_DOUBLE_QUOTED;
+ break beforeattributevalueloop;
+ // continue stateloop;
+ case '\'':
+ state = MetaScanner.ATTRIBUTE_VALUE_SINGLE_QUOTED;
+ continue stateloop;
+ case '>':
+ if (handleTag()) {
+ break stateloop;
+ }
+ state = MetaScanner.DATA;
+ continue stateloop;
+ default:
+ handleCharInAttributeValue(c);
+ state = MetaScanner.ATTRIBUTE_VALUE_UNQUOTED;
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case ATTRIBUTE_VALUE_DOUBLE_QUOTED:
+ attributevaluedoublequotedloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '"':
+ handleAttributeValue();
+ state = MetaScanner.AFTER_ATTRIBUTE_VALUE_QUOTED;
+ break attributevaluedoublequotedloop;
+ // continue stateloop;
+ default:
+ handleCharInAttributeValue(c);
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case AFTER_ATTRIBUTE_VALUE_QUOTED:
+ afterattributevaluequotedloop: for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\u000C':
+ state = MetaScanner.BEFORE_ATTRIBUTE_NAME;
+ continue stateloop;
+ case '/':
+ state = MetaScanner.SELF_CLOSING_START_TAG;
+ break afterattributevaluequotedloop;
+ // continue stateloop;
+ case '>':
+ if (handleTag()) {
+ break stateloop;
+ }
+ state = MetaScanner.DATA;
+ continue stateloop;
+ default:
+ state = MetaScanner.BEFORE_ATTRIBUTE_NAME;
+ reconsume = true;
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case SELF_CLOSING_START_TAG:
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '>':
+ if (handleTag()) {
+ break stateloop;
+ }
+ state = MetaScanner.DATA;
+ continue stateloop;
+ default:
+ state = MetaScanner.BEFORE_ATTRIBUTE_NAME;
+ reconsume = true;
+ continue stateloop;
+ }
+ // XXX reorder point
+ case ATTRIBUTE_VALUE_UNQUOTED:
+ for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch (c) {
+ case -1:
+ break stateloop;
+ case ' ':
+ case '\t':
+ case '\n':
+
+ case '\u000C':
+ handleAttributeValue();
+ state = MetaScanner.BEFORE_ATTRIBUTE_NAME;
+ continue stateloop;
+ case '>':
+ handleAttributeValue();
+ if (handleTag()) {
+ break stateloop;
+ }
+ state = MetaScanner.DATA;
+ continue stateloop;
+ default:
+ handleCharInAttributeValue(c);
+ continue;
+ }
+ }
+ // XXX reorder point
+ case AFTER_ATTRIBUTE_NAME:
+ for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\u000C':
+ continue;
+ case '/':
+ handleAttributeValue();
+ state = MetaScanner.SELF_CLOSING_START_TAG;
+ continue stateloop;
+ case '=':
+ strBufLen = 0;
+ contentTypeIndex = 0;
+ state = MetaScanner.BEFORE_ATTRIBUTE_VALUE;
+ continue stateloop;
+ case '>':
+ handleAttributeValue();
+ if (handleTag()) {
+ break stateloop;
+ }
+ state = MetaScanner.DATA;
+ continue stateloop;
+ case 'c':
+ case 'C':
+ contentIndex = 0;
+ charsetIndex = 0;
+ state = MetaScanner.ATTRIBUTE_NAME;
+ continue stateloop;
+ default:
+ contentIndex = Integer.MAX_VALUE;
+ charsetIndex = Integer.MAX_VALUE;
+ state = MetaScanner.ATTRIBUTE_NAME;
+ continue stateloop;
+ }
+ }
+ // XXX reorder point
+ case MARKUP_DECLARATION_OPEN:
+ markupdeclarationopenloop: for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '-':
+ state = MetaScanner.MARKUP_DECLARATION_HYPHEN;
+ break markupdeclarationopenloop;
+ // continue stateloop;
+ default:
+ state = MetaScanner.SCAN_UNTIL_GT;
+ reconsume = true;
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case MARKUP_DECLARATION_HYPHEN:
+ markupdeclarationhyphenloop: for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '-':
+ state = MetaScanner.COMMENT_START;
+ break markupdeclarationhyphenloop;
+ // continue stateloop;
+ default:
+ state = MetaScanner.SCAN_UNTIL_GT;
+ reconsume = true;
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case COMMENT_START:
+ commentstartloop: for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '-':
+ state = MetaScanner.COMMENT_START_DASH;
+ continue stateloop;
+ case '>':
+ state = MetaScanner.DATA;
+ continue stateloop;
+ default:
+ state = MetaScanner.COMMENT;
+ break commentstartloop;
+ // continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case COMMENT:
+ commentloop: for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '-':
+ state = MetaScanner.COMMENT_END_DASH;
+ break commentloop;
+ // continue stateloop;
+ default:
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case COMMENT_END_DASH:
+ commentenddashloop: for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '-':
+ state = MetaScanner.COMMENT_END;
+ break commentenddashloop;
+ // continue stateloop;
+ default:
+ state = MetaScanner.COMMENT;
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case COMMENT_END:
+ for (;;) {
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '>':
+ state = MetaScanner.DATA;
+ continue stateloop;
+ case '-':
+ continue;
+ default:
+ state = MetaScanner.COMMENT;
+ continue stateloop;
+ }
+ }
+ // XXX reorder point
+ case COMMENT_START_DASH:
+ c = read();
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '-':
+ state = MetaScanner.COMMENT_END;
+ continue stateloop;
+ case '>':
+ state = MetaScanner.DATA;
+ continue stateloop;
+ default:
+ state = MetaScanner.COMMENT;
+ continue stateloop;
+ }
+ // XXX reorder point
+ case ATTRIBUTE_VALUE_SINGLE_QUOTED:
+ for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '\'':
+ handleAttributeValue();
+ state = MetaScanner.AFTER_ATTRIBUTE_VALUE_QUOTED;
+ continue stateloop;
+ default:
+ handleCharInAttributeValue(c);
+ continue;
+ }
+ }
+ // XXX reorder point
+ case SCAN_UNTIL_GT:
+ for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch (c) {
+ case -1:
+ break stateloop;
+ case '>':
+ state = MetaScanner.DATA;
+ continue stateloop;
+ default:
+ continue;
+ }
+ }
+ }
+ }
+ stateSave = state;
+ }
+
+ private void handleCharInAttributeValue(int c) throws SAXException {
+ if (metaState == A) {
+ if (contentIndex == CONTENT.length || charsetIndex == CHARSET.length) {
+ addToBuffer(c);
+ } else if (httpEquivIndex == HTTP_EQUIV.length) {
+ if (contentTypeIndex < CONTENT_TYPE.length && toAsciiLowerCase(c) == CONTENT_TYPE[contentTypeIndex]) {
+ ++contentTypeIndex;
+ } else {
+ contentTypeIndex = Integer.MAX_VALUE;
+ }
+ }
+ }
+ }
+
+ @Inline private int toAsciiLowerCase(int c) {
+ if (c >= 'A' && c <= 'Z') {
+ return c + 0x20;
+ }
+ return c;
+ }
+
+ /**
+ * Adds a character to the accumulation buffer.
+ * @param c the character to add
+ */
+ private void addToBuffer(int c) throws SAXException {
+ if (strBufLen == strBuf.length) {
+ char[] newBuf = new char[Portability.checkedAdd(strBuf.length, (strBuf.length << 1))];
+ System.arraycopy(strBuf, 0, newBuf, 0, strBuf.length);
+ strBuf = newBuf;
+ }
+ strBuf[strBufLen++] = (char)c;
+ }
+
+ /**
+ * Attempts to extract a charset name from the accumulation buffer.
+ * @return <code>true</code> if successful
+ * @throws SAXException
+ */
+ private void handleAttributeValue() throws SAXException {
+ if (metaState != A) {
+ return;
+ }
+ if (contentIndex == CONTENT.length && content == null) {
+ content = Portability.newStringFromBuffer(strBuf, 0, strBufLen
+ // CPPONLY: , treeBuilder, false
+ );
+ return;
+ }
+ if (charsetIndex == CHARSET.length && charset == null) {
+ charset = Portability.newStringFromBuffer(strBuf, 0, strBufLen
+ // CPPONLY: , treeBuilder, false
+ );
+ return;
+ }
+ if (httpEquivIndex == HTTP_EQUIV.length
+ && httpEquivState == HTTP_EQUIV_NOT_SEEN) {
+ httpEquivState = (contentTypeIndex == CONTENT_TYPE.length) ? HTTP_EQUIV_CONTENT_TYPE
+ : HTTP_EQUIV_OTHER;
+ return;
+ }
+ }
+
+ private boolean handleTag() throws SAXException {
+ boolean stop = handleTagInner();
+ Portability.releaseString(content);
+ content = null;
+ Portability.releaseString(charset);
+ charset = null;
+ httpEquivState = HTTP_EQUIV_NOT_SEEN;
+ return stop;
+ }
+
+ private boolean handleTagInner() throws SAXException {
+ if (charset != null && tryCharset(charset)) {
+ return true;
+ }
+ if (content != null && httpEquivState == HTTP_EQUIV_CONTENT_TYPE) {
+ String extract = TreeBuilder.extractCharsetFromContent(content
+ // CPPONLY: , treeBuilder
+ );
+ if (extract == null) {
+ return false;
+ }
+ boolean success = tryCharset(extract);
+ Portability.releaseString(extract);
+ return success;
+ }
+ return false;
+ }
+
+ /**
+ * Tries to switch to an encoding.
+ *
+ * @param encoding
+ * @return <code>true</code> if successful
+ * @throws SAXException
+ */
+ protected abstract boolean tryCharset(String encoding) throws SAXException;
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NCName.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NCName.java
new file mode 100644
index 000000000..940cf2e9c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NCName.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) 2008-2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+public final class NCName {
+ // [NOCPP[
+
+ private static final int SURROGATE_OFFSET = 0x10000 - (0xD800 << 10) - 0xDC00;
+
+ private static final char[] HEX_TABLE = "0123456789ABCDEF".toCharArray();
+
+ public static boolean isNCNameStart(char c) {
+ return ((c >= '\u0041' && c <= '\u005A')
+ || (c >= '\u0061' && c <= '\u007A')
+ || (c >= '\u00C0' && c <= '\u00D6')
+ || (c >= '\u00D8' && c <= '\u00F6')
+ || (c >= '\u00F8' && c <= '\u00FF')
+ || (c >= '\u0100' && c <= '\u0131')
+ || (c >= '\u0134' && c <= '\u013E')
+ || (c >= '\u0141' && c <= '\u0148')
+ || (c >= '\u014A' && c <= '\u017E')
+ || (c >= '\u0180' && c <= '\u01C3')
+ || (c >= '\u01CD' && c <= '\u01F0')
+ || (c >= '\u01F4' && c <= '\u01F5')
+ || (c >= '\u01FA' && c <= '\u0217')
+ || (c >= '\u0250' && c <= '\u02A8')
+ || (c >= '\u02BB' && c <= '\u02C1') || (c == '\u0386')
+ || (c >= '\u0388' && c <= '\u038A') || (c == '\u038C')
+ || (c >= '\u038E' && c <= '\u03A1')
+ || (c >= '\u03A3' && c <= '\u03CE')
+ || (c >= '\u03D0' && c <= '\u03D6') || (c == '\u03DA')
+ || (c == '\u03DC') || (c == '\u03DE') || (c == '\u03E0')
+ || (c >= '\u03E2' && c <= '\u03F3')
+ || (c >= '\u0401' && c <= '\u040C')
+ || (c >= '\u040E' && c <= '\u044F')
+ || (c >= '\u0451' && c <= '\u045C')
+ || (c >= '\u045E' && c <= '\u0481')
+ || (c >= '\u0490' && c <= '\u04C4')
+ || (c >= '\u04C7' && c <= '\u04C8')
+ || (c >= '\u04CB' && c <= '\u04CC')
+ || (c >= '\u04D0' && c <= '\u04EB')
+ || (c >= '\u04EE' && c <= '\u04F5')
+ || (c >= '\u04F8' && c <= '\u04F9')
+ || (c >= '\u0531' && c <= '\u0556') || (c == '\u0559')
+ || (c >= '\u0561' && c <= '\u0586')
+ || (c >= '\u05D0' && c <= '\u05EA')
+ || (c >= '\u05F0' && c <= '\u05F2')
+ || (c >= '\u0621' && c <= '\u063A')
+ || (c >= '\u0641' && c <= '\u064A')
+ || (c >= '\u0671' && c <= '\u06B7')
+ || (c >= '\u06BA' && c <= '\u06BE')
+ || (c >= '\u06C0' && c <= '\u06CE')
+ || (c >= '\u06D0' && c <= '\u06D3') || (c == '\u06D5')
+ || (c >= '\u06E5' && c <= '\u06E6')
+ || (c >= '\u0905' && c <= '\u0939') || (c == '\u093D')
+ || (c >= '\u0958' && c <= '\u0961')
+ || (c >= '\u0985' && c <= '\u098C')
+ || (c >= '\u098F' && c <= '\u0990')
+ || (c >= '\u0993' && c <= '\u09A8')
+ || (c >= '\u09AA' && c <= '\u09B0') || (c == '\u09B2')
+ || (c >= '\u09B6' && c <= '\u09B9')
+ || (c >= '\u09DC' && c <= '\u09DD')
+ || (c >= '\u09DF' && c <= '\u09E1')
+ || (c >= '\u09F0' && c <= '\u09F1')
+ || (c >= '\u0A05' && c <= '\u0A0A')
+ || (c >= '\u0A0F' && c <= '\u0A10')
+ || (c >= '\u0A13' && c <= '\u0A28')
+ || (c >= '\u0A2A' && c <= '\u0A30')
+ || (c >= '\u0A32' && c <= '\u0A33')
+ || (c >= '\u0A35' && c <= '\u0A36')
+ || (c >= '\u0A38' && c <= '\u0A39')
+ || (c >= '\u0A59' && c <= '\u0A5C') || (c == '\u0A5E')
+ || (c >= '\u0A72' && c <= '\u0A74')
+ || (c >= '\u0A85' && c <= '\u0A8B') || (c == '\u0A8D')
+ || (c >= '\u0A8F' && c <= '\u0A91')
+ || (c >= '\u0A93' && c <= '\u0AA8')
+ || (c >= '\u0AAA' && c <= '\u0AB0')
+ || (c >= '\u0AB2' && c <= '\u0AB3')
+ || (c >= '\u0AB5' && c <= '\u0AB9') || (c == '\u0ABD')
+ || (c == '\u0AE0') || (c >= '\u0B05' && c <= '\u0B0C')
+ || (c >= '\u0B0F' && c <= '\u0B10')
+ || (c >= '\u0B13' && c <= '\u0B28')
+ || (c >= '\u0B2A' && c <= '\u0B30')
+ || (c >= '\u0B32' && c <= '\u0B33')
+ || (c >= '\u0B36' && c <= '\u0B39') || (c == '\u0B3D')
+ || (c >= '\u0B5C' && c <= '\u0B5D')
+ || (c >= '\u0B5F' && c <= '\u0B61')
+ || (c >= '\u0B85' && c <= '\u0B8A')
+ || (c >= '\u0B8E' && c <= '\u0B90')
+ || (c >= '\u0B92' && c <= '\u0B95')
+ || (c >= '\u0B99' && c <= '\u0B9A') || (c == '\u0B9C')
+ || (c >= '\u0B9E' && c <= '\u0B9F')
+ || (c >= '\u0BA3' && c <= '\u0BA4')
+ || (c >= '\u0BA8' && c <= '\u0BAA')
+ || (c >= '\u0BAE' && c <= '\u0BB5')
+ || (c >= '\u0BB7' && c <= '\u0BB9')
+ || (c >= '\u0C05' && c <= '\u0C0C')
+ || (c >= '\u0C0E' && c <= '\u0C10')
+ || (c >= '\u0C12' && c <= '\u0C28')
+ || (c >= '\u0C2A' && c <= '\u0C33')
+ || (c >= '\u0C35' && c <= '\u0C39')
+ || (c >= '\u0C60' && c <= '\u0C61')
+ || (c >= '\u0C85' && c <= '\u0C8C')
+ || (c >= '\u0C8E' && c <= '\u0C90')
+ || (c >= '\u0C92' && c <= '\u0CA8')
+ || (c >= '\u0CAA' && c <= '\u0CB3')
+ || (c >= '\u0CB5' && c <= '\u0CB9') || (c == '\u0CDE')
+ || (c >= '\u0CE0' && c <= '\u0CE1')
+ || (c >= '\u0D05' && c <= '\u0D0C')
+ || (c >= '\u0D0E' && c <= '\u0D10')
+ || (c >= '\u0D12' && c <= '\u0D28')
+ || (c >= '\u0D2A' && c <= '\u0D39')
+ || (c >= '\u0D60' && c <= '\u0D61')
+ || (c >= '\u0E01' && c <= '\u0E2E') || (c == '\u0E30')
+ || (c >= '\u0E32' && c <= '\u0E33')
+ || (c >= '\u0E40' && c <= '\u0E45')
+ || (c >= '\u0E81' && c <= '\u0E82') || (c == '\u0E84')
+ || (c >= '\u0E87' && c <= '\u0E88') || (c == '\u0E8A')
+ || (c == '\u0E8D') || (c >= '\u0E94' && c <= '\u0E97')
+ || (c >= '\u0E99' && c <= '\u0E9F')
+ || (c >= '\u0EA1' && c <= '\u0EA3') || (c == '\u0EA5')
+ || (c == '\u0EA7') || (c >= '\u0EAA' && c <= '\u0EAB')
+ || (c >= '\u0EAD' && c <= '\u0EAE') || (c == '\u0EB0')
+ || (c >= '\u0EB2' && c <= '\u0EB3') || (c == '\u0EBD')
+ || (c >= '\u0EC0' && c <= '\u0EC4')
+ || (c >= '\u0F40' && c <= '\u0F47')
+ || (c >= '\u0F49' && c <= '\u0F69')
+ || (c >= '\u10A0' && c <= '\u10C5')
+ || (c >= '\u10D0' && c <= '\u10F6') || (c == '\u1100')
+ || (c >= '\u1102' && c <= '\u1103')
+ || (c >= '\u1105' && c <= '\u1107') || (c == '\u1109')
+ || (c >= '\u110B' && c <= '\u110C')
+ || (c >= '\u110E' && c <= '\u1112') || (c == '\u113C')
+ || (c == '\u113E') || (c == '\u1140') || (c == '\u114C')
+ || (c == '\u114E') || (c == '\u1150')
+ || (c >= '\u1154' && c <= '\u1155') || (c == '\u1159')
+ || (c >= '\u115F' && c <= '\u1161') || (c == '\u1163')
+ || (c == '\u1165') || (c == '\u1167') || (c == '\u1169')
+ || (c >= '\u116D' && c <= '\u116E')
+ || (c >= '\u1172' && c <= '\u1173') || (c == '\u1175')
+ || (c == '\u119E') || (c == '\u11A8') || (c == '\u11AB')
+ || (c >= '\u11AE' && c <= '\u11AF')
+ || (c >= '\u11B7' && c <= '\u11B8') || (c == '\u11BA')
+ || (c >= '\u11BC' && c <= '\u11C2') || (c == '\u11EB')
+ || (c == '\u11F0') || (c == '\u11F9')
+ || (c >= '\u1E00' && c <= '\u1E9B')
+ || (c >= '\u1EA0' && c <= '\u1EF9')
+ || (c >= '\u1F00' && c <= '\u1F15')
+ || (c >= '\u1F18' && c <= '\u1F1D')
+ || (c >= '\u1F20' && c <= '\u1F45')
+ || (c >= '\u1F48' && c <= '\u1F4D')
+ || (c >= '\u1F50' && c <= '\u1F57') || (c == '\u1F59')
+ || (c == '\u1F5B') || (c == '\u1F5D')
+ || (c >= '\u1F5F' && c <= '\u1F7D')
+ || (c >= '\u1F80' && c <= '\u1FB4')
+ || (c >= '\u1FB6' && c <= '\u1FBC') || (c == '\u1FBE')
+ || (c >= '\u1FC2' && c <= '\u1FC4')
+ || (c >= '\u1FC6' && c <= '\u1FCC')
+ || (c >= '\u1FD0' && c <= '\u1FD3')
+ || (c >= '\u1FD6' && c <= '\u1FDB')
+ || (c >= '\u1FE0' && c <= '\u1FEC')
+ || (c >= '\u1FF2' && c <= '\u1FF4')
+ || (c >= '\u1FF6' && c <= '\u1FFC') || (c == '\u2126')
+ || (c >= '\u212A' && c <= '\u212B') || (c == '\u212E')
+ || (c >= '\u2180' && c <= '\u2182')
+ || (c >= '\u3041' && c <= '\u3094')
+ || (c >= '\u30A1' && c <= '\u30FA')
+ || (c >= '\u3105' && c <= '\u312C')
+ || (c >= '\uAC00' && c <= '\uD7A3')
+ || (c >= '\u4E00' && c <= '\u9FA5') || (c == '\u3007')
+ || (c >= '\u3021' && c <= '\u3029') || (c == '_'));
+ }
+
+ public static boolean isNCNameTrail(char c) {
+ return ((c >= '\u0030' && c <= '\u0039')
+ || (c >= '\u0660' && c <= '\u0669')
+ || (c >= '\u06F0' && c <= '\u06F9')
+ || (c >= '\u0966' && c <= '\u096F')
+ || (c >= '\u09E6' && c <= '\u09EF')
+ || (c >= '\u0A66' && c <= '\u0A6F')
+ || (c >= '\u0AE6' && c <= '\u0AEF')
+ || (c >= '\u0B66' && c <= '\u0B6F')
+ || (c >= '\u0BE7' && c <= '\u0BEF')
+ || (c >= '\u0C66' && c <= '\u0C6F')
+ || (c >= '\u0CE6' && c <= '\u0CEF')
+ || (c >= '\u0D66' && c <= '\u0D6F')
+ || (c >= '\u0E50' && c <= '\u0E59')
+ || (c >= '\u0ED0' && c <= '\u0ED9')
+ || (c >= '\u0F20' && c <= '\u0F29')
+ || (c >= '\u0041' && c <= '\u005A')
+ || (c >= '\u0061' && c <= '\u007A')
+ || (c >= '\u00C0' && c <= '\u00D6')
+ || (c >= '\u00D8' && c <= '\u00F6')
+ || (c >= '\u00F8' && c <= '\u00FF')
+ || (c >= '\u0100' && c <= '\u0131')
+ || (c >= '\u0134' && c <= '\u013E')
+ || (c >= '\u0141' && c <= '\u0148')
+ || (c >= '\u014A' && c <= '\u017E')
+ || (c >= '\u0180' && c <= '\u01C3')
+ || (c >= '\u01CD' && c <= '\u01F0')
+ || (c >= '\u01F4' && c <= '\u01F5')
+ || (c >= '\u01FA' && c <= '\u0217')
+ || (c >= '\u0250' && c <= '\u02A8')
+ || (c >= '\u02BB' && c <= '\u02C1') || (c == '\u0386')
+ || (c >= '\u0388' && c <= '\u038A') || (c == '\u038C')
+ || (c >= '\u038E' && c <= '\u03A1')
+ || (c >= '\u03A3' && c <= '\u03CE')
+ || (c >= '\u03D0' && c <= '\u03D6') || (c == '\u03DA')
+ || (c == '\u03DC') || (c == '\u03DE') || (c == '\u03E0')
+ || (c >= '\u03E2' && c <= '\u03F3')
+ || (c >= '\u0401' && c <= '\u040C')
+ || (c >= '\u040E' && c <= '\u044F')
+ || (c >= '\u0451' && c <= '\u045C')
+ || (c >= '\u045E' && c <= '\u0481')
+ || (c >= '\u0490' && c <= '\u04C4')
+ || (c >= '\u04C7' && c <= '\u04C8')
+ || (c >= '\u04CB' && c <= '\u04CC')
+ || (c >= '\u04D0' && c <= '\u04EB')
+ || (c >= '\u04EE' && c <= '\u04F5')
+ || (c >= '\u04F8' && c <= '\u04F9')
+ || (c >= '\u0531' && c <= '\u0556') || (c == '\u0559')
+ || (c >= '\u0561' && c <= '\u0586')
+ || (c >= '\u05D0' && c <= '\u05EA')
+ || (c >= '\u05F0' && c <= '\u05F2')
+ || (c >= '\u0621' && c <= '\u063A')
+ || (c >= '\u0641' && c <= '\u064A')
+ || (c >= '\u0671' && c <= '\u06B7')
+ || (c >= '\u06BA' && c <= '\u06BE')
+ || (c >= '\u06C0' && c <= '\u06CE')
+ || (c >= '\u06D0' && c <= '\u06D3') || (c == '\u06D5')
+ || (c >= '\u06E5' && c <= '\u06E6')
+ || (c >= '\u0905' && c <= '\u0939') || (c == '\u093D')
+ || (c >= '\u0958' && c <= '\u0961')
+ || (c >= '\u0985' && c <= '\u098C')
+ || (c >= '\u098F' && c <= '\u0990')
+ || (c >= '\u0993' && c <= '\u09A8')
+ || (c >= '\u09AA' && c <= '\u09B0') || (c == '\u09B2')
+ || (c >= '\u09B6' && c <= '\u09B9')
+ || (c >= '\u09DC' && c <= '\u09DD')
+ || (c >= '\u09DF' && c <= '\u09E1')
+ || (c >= '\u09F0' && c <= '\u09F1')
+ || (c >= '\u0A05' && c <= '\u0A0A')
+ || (c >= '\u0A0F' && c <= '\u0A10')
+ || (c >= '\u0A13' && c <= '\u0A28')
+ || (c >= '\u0A2A' && c <= '\u0A30')
+ || (c >= '\u0A32' && c <= '\u0A33')
+ || (c >= '\u0A35' && c <= '\u0A36')
+ || (c >= '\u0A38' && c <= '\u0A39')
+ || (c >= '\u0A59' && c <= '\u0A5C') || (c == '\u0A5E')
+ || (c >= '\u0A72' && c <= '\u0A74')
+ || (c >= '\u0A85' && c <= '\u0A8B') || (c == '\u0A8D')
+ || (c >= '\u0A8F' && c <= '\u0A91')
+ || (c >= '\u0A93' && c <= '\u0AA8')
+ || (c >= '\u0AAA' && c <= '\u0AB0')
+ || (c >= '\u0AB2' && c <= '\u0AB3')
+ || (c >= '\u0AB5' && c <= '\u0AB9') || (c == '\u0ABD')
+ || (c == '\u0AE0') || (c >= '\u0B05' && c <= '\u0B0C')
+ || (c >= '\u0B0F' && c <= '\u0B10')
+ || (c >= '\u0B13' && c <= '\u0B28')
+ || (c >= '\u0B2A' && c <= '\u0B30')
+ || (c >= '\u0B32' && c <= '\u0B33')
+ || (c >= '\u0B36' && c <= '\u0B39') || (c == '\u0B3D')
+ || (c >= '\u0B5C' && c <= '\u0B5D')
+ || (c >= '\u0B5F' && c <= '\u0B61')
+ || (c >= '\u0B85' && c <= '\u0B8A')
+ || (c >= '\u0B8E' && c <= '\u0B90')
+ || (c >= '\u0B92' && c <= '\u0B95')
+ || (c >= '\u0B99' && c <= '\u0B9A') || (c == '\u0B9C')
+ || (c >= '\u0B9E' && c <= '\u0B9F')
+ || (c >= '\u0BA3' && c <= '\u0BA4')
+ || (c >= '\u0BA8' && c <= '\u0BAA')
+ || (c >= '\u0BAE' && c <= '\u0BB5')
+ || (c >= '\u0BB7' && c <= '\u0BB9')
+ || (c >= '\u0C05' && c <= '\u0C0C')
+ || (c >= '\u0C0E' && c <= '\u0C10')
+ || (c >= '\u0C12' && c <= '\u0C28')
+ || (c >= '\u0C2A' && c <= '\u0C33')
+ || (c >= '\u0C35' && c <= '\u0C39')
+ || (c >= '\u0C60' && c <= '\u0C61')
+ || (c >= '\u0C85' && c <= '\u0C8C')
+ || (c >= '\u0C8E' && c <= '\u0C90')
+ || (c >= '\u0C92' && c <= '\u0CA8')
+ || (c >= '\u0CAA' && c <= '\u0CB3')
+ || (c >= '\u0CB5' && c <= '\u0CB9') || (c == '\u0CDE')
+ || (c >= '\u0CE0' && c <= '\u0CE1')
+ || (c >= '\u0D05' && c <= '\u0D0C')
+ || (c >= '\u0D0E' && c <= '\u0D10')
+ || (c >= '\u0D12' && c <= '\u0D28')
+ || (c >= '\u0D2A' && c <= '\u0D39')
+ || (c >= '\u0D60' && c <= '\u0D61')
+ || (c >= '\u0E01' && c <= '\u0E2E') || (c == '\u0E30')
+ || (c >= '\u0E32' && c <= '\u0E33')
+ || (c >= '\u0E40' && c <= '\u0E45')
+ || (c >= '\u0E81' && c <= '\u0E82') || (c == '\u0E84')
+ || (c >= '\u0E87' && c <= '\u0E88') || (c == '\u0E8A')
+ || (c == '\u0E8D') || (c >= '\u0E94' && c <= '\u0E97')
+ || (c >= '\u0E99' && c <= '\u0E9F')
+ || (c >= '\u0EA1' && c <= '\u0EA3') || (c == '\u0EA5')
+ || (c == '\u0EA7') || (c >= '\u0EAA' && c <= '\u0EAB')
+ || (c >= '\u0EAD' && c <= '\u0EAE') || (c == '\u0EB0')
+ || (c >= '\u0EB2' && c <= '\u0EB3') || (c == '\u0EBD')
+ || (c >= '\u0EC0' && c <= '\u0EC4')
+ || (c >= '\u0F40' && c <= '\u0F47')
+ || (c >= '\u0F49' && c <= '\u0F69')
+ || (c >= '\u10A0' && c <= '\u10C5')
+ || (c >= '\u10D0' && c <= '\u10F6') || (c == '\u1100')
+ || (c >= '\u1102' && c <= '\u1103')
+ || (c >= '\u1105' && c <= '\u1107') || (c == '\u1109')
+ || (c >= '\u110B' && c <= '\u110C')
+ || (c >= '\u110E' && c <= '\u1112') || (c == '\u113C')
+ || (c == '\u113E') || (c == '\u1140') || (c == '\u114C')
+ || (c == '\u114E') || (c == '\u1150')
+ || (c >= '\u1154' && c <= '\u1155') || (c == '\u1159')
+ || (c >= '\u115F' && c <= '\u1161') || (c == '\u1163')
+ || (c == '\u1165') || (c == '\u1167') || (c == '\u1169')
+ || (c >= '\u116D' && c <= '\u116E')
+ || (c >= '\u1172' && c <= '\u1173') || (c == '\u1175')
+ || (c == '\u119E') || (c == '\u11A8') || (c == '\u11AB')
+ || (c >= '\u11AE' && c <= '\u11AF')
+ || (c >= '\u11B7' && c <= '\u11B8') || (c == '\u11BA')
+ || (c >= '\u11BC' && c <= '\u11C2') || (c == '\u11EB')
+ || (c == '\u11F0') || (c == '\u11F9')
+ || (c >= '\u1E00' && c <= '\u1E9B')
+ || (c >= '\u1EA0' && c <= '\u1EF9')
+ || (c >= '\u1F00' && c <= '\u1F15')
+ || (c >= '\u1F18' && c <= '\u1F1D')
+ || (c >= '\u1F20' && c <= '\u1F45')
+ || (c >= '\u1F48' && c <= '\u1F4D')
+ || (c >= '\u1F50' && c <= '\u1F57') || (c == '\u1F59')
+ || (c == '\u1F5B') || (c == '\u1F5D')
+ || (c >= '\u1F5F' && c <= '\u1F7D')
+ || (c >= '\u1F80' && c <= '\u1FB4')
+ || (c >= '\u1FB6' && c <= '\u1FBC') || (c == '\u1FBE')
+ || (c >= '\u1FC2' && c <= '\u1FC4')
+ || (c >= '\u1FC6' && c <= '\u1FCC')
+ || (c >= '\u1FD0' && c <= '\u1FD3')
+ || (c >= '\u1FD6' && c <= '\u1FDB')
+ || (c >= '\u1FE0' && c <= '\u1FEC')
+ || (c >= '\u1FF2' && c <= '\u1FF4')
+ || (c >= '\u1FF6' && c <= '\u1FFC') || (c == '\u2126')
+ || (c >= '\u212A' && c <= '\u212B') || (c == '\u212E')
+ || (c >= '\u2180' && c <= '\u2182')
+ || (c >= '\u3041' && c <= '\u3094')
+ || (c >= '\u30A1' && c <= '\u30FA')
+ || (c >= '\u3105' && c <= '\u312C')
+ || (c >= '\uAC00' && c <= '\uD7A3')
+ || (c >= '\u4E00' && c <= '\u9FA5') || (c == '\u3007')
+ || (c >= '\u3021' && c <= '\u3029') || (c == '_') || (c == '.')
+ || (c == '-') || (c >= '\u0300' && c <= '\u0345')
+ || (c >= '\u0360' && c <= '\u0361')
+ || (c >= '\u0483' && c <= '\u0486')
+ || (c >= '\u0591' && c <= '\u05A1')
+ || (c >= '\u05A3' && c <= '\u05B9')
+ || (c >= '\u05BB' && c <= '\u05BD') || (c == '\u05BF')
+ || (c >= '\u05C1' && c <= '\u05C2') || (c == '\u05C4')
+ || (c >= '\u064B' && c <= '\u0652') || (c == '\u0670')
+ || (c >= '\u06D6' && c <= '\u06DC')
+ || (c >= '\u06DD' && c <= '\u06DF')
+ || (c >= '\u06E0' && c <= '\u06E4')
+ || (c >= '\u06E7' && c <= '\u06E8')
+ || (c >= '\u06EA' && c <= '\u06ED')
+ || (c >= '\u0901' && c <= '\u0903') || (c == '\u093C')
+ || (c >= '\u093E' && c <= '\u094C') || (c == '\u094D')
+ || (c >= '\u0951' && c <= '\u0954')
+ || (c >= '\u0962' && c <= '\u0963')
+ || (c >= '\u0981' && c <= '\u0983') || (c == '\u09BC')
+ || (c == '\u09BE') || (c == '\u09BF')
+ || (c >= '\u09C0' && c <= '\u09C4')
+ || (c >= '\u09C7' && c <= '\u09C8')
+ || (c >= '\u09CB' && c <= '\u09CD') || (c == '\u09D7')
+ || (c >= '\u09E2' && c <= '\u09E3') || (c == '\u0A02')
+ || (c == '\u0A3C') || (c == '\u0A3E') || (c == '\u0A3F')
+ || (c >= '\u0A40' && c <= '\u0A42')
+ || (c >= '\u0A47' && c <= '\u0A48')
+ || (c >= '\u0A4B' && c <= '\u0A4D')
+ || (c >= '\u0A70' && c <= '\u0A71')
+ || (c >= '\u0A81' && c <= '\u0A83') || (c == '\u0ABC')
+ || (c >= '\u0ABE' && c <= '\u0AC5')
+ || (c >= '\u0AC7' && c <= '\u0AC9')
+ || (c >= '\u0ACB' && c <= '\u0ACD')
+ || (c >= '\u0B01' && c <= '\u0B03') || (c == '\u0B3C')
+ || (c >= '\u0B3E' && c <= '\u0B43')
+ || (c >= '\u0B47' && c <= '\u0B48')
+ || (c >= '\u0B4B' && c <= '\u0B4D')
+ || (c >= '\u0B56' && c <= '\u0B57')
+ || (c >= '\u0B82' && c <= '\u0B83')
+ || (c >= '\u0BBE' && c <= '\u0BC2')
+ || (c >= '\u0BC6' && c <= '\u0BC8')
+ || (c >= '\u0BCA' && c <= '\u0BCD') || (c == '\u0BD7')
+ || (c >= '\u0C01' && c <= '\u0C03')
+ || (c >= '\u0C3E' && c <= '\u0C44')
+ || (c >= '\u0C46' && c <= '\u0C48')
+ || (c >= '\u0C4A' && c <= '\u0C4D')
+ || (c >= '\u0C55' && c <= '\u0C56')
+ || (c >= '\u0C82' && c <= '\u0C83')
+ || (c >= '\u0CBE' && c <= '\u0CC4')
+ || (c >= '\u0CC6' && c <= '\u0CC8')
+ || (c >= '\u0CCA' && c <= '\u0CCD')
+ || (c >= '\u0CD5' && c <= '\u0CD6')
+ || (c >= '\u0D02' && c <= '\u0D03')
+ || (c >= '\u0D3E' && c <= '\u0D43')
+ || (c >= '\u0D46' && c <= '\u0D48')
+ || (c >= '\u0D4A' && c <= '\u0D4D') || (c == '\u0D57')
+ || (c == '\u0E31') || (c >= '\u0E34' && c <= '\u0E3A')
+ || (c >= '\u0E47' && c <= '\u0E4E') || (c == '\u0EB1')
+ || (c >= '\u0EB4' && c <= '\u0EB9')
+ || (c >= '\u0EBB' && c <= '\u0EBC')
+ || (c >= '\u0EC8' && c <= '\u0ECD')
+ || (c >= '\u0F18' && c <= '\u0F19') || (c == '\u0F35')
+ || (c == '\u0F37') || (c == '\u0F39') || (c == '\u0F3E')
+ || (c == '\u0F3F') || (c >= '\u0F71' && c <= '\u0F84')
+ || (c >= '\u0F86' && c <= '\u0F8B')
+ || (c >= '\u0F90' && c <= '\u0F95') || (c == '\u0F97')
+ || (c >= '\u0F99' && c <= '\u0FAD')
+ || (c >= '\u0FB1' && c <= '\u0FB7') || (c == '\u0FB9')
+ || (c >= '\u20D0' && c <= '\u20DC') || (c == '\u20E1')
+ || (c >= '\u302A' && c <= '\u302F') || (c == '\u3099')
+ || (c == '\u309A') || (c == '\u00B7') || (c == '\u02D0')
+ || (c == '\u02D1') || (c == '\u0387') || (c == '\u0640')
+ || (c == '\u0E46') || (c == '\u0EC6') || (c == '\u3005')
+ || (c >= '\u3031' && c <= '\u3035')
+ || (c >= '\u309D' && c <= '\u309E') || (c >= '\u30FC' && c <= '\u30FE'));
+ }
+
+ public static boolean isNCName(String str) {
+ if (str == null) {
+ return false;
+ } else {
+ int len = str.length();
+ switch (len) {
+ case 0:
+ return false;
+ case 1:
+ return NCName.isNCNameStart(str.charAt(0));
+ default:
+ if (!NCName.isNCNameStart(str.charAt(0))) {
+ return false;
+ }
+ for (int i = 1; i < len; i++) {
+ if (!NCName.isNCNameTrail(str.charAt(i))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ private static void appendUHexTo(StringBuilder sb, int c) {
+ sb.append('U');
+ for (int i = 0; i < 6; i++) {
+ sb.append(HEX_TABLE[(c & 0xF00000) >> 20]);
+ c <<= 4;
+ }
+ }
+
+ public static String escapeName(String str) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if ((c & 0xFC00) == 0xD800) {
+ char next = str.charAt(++i);
+ appendUHexTo(sb, (c << 10) + next + SURROGATE_OFFSET);
+ } else if (i == 0 && !isNCNameStart(c)) {
+ appendUHexTo(sb, c);
+ } else if (i != 0 && !isNCNameTrail(c)) {
+ appendUHexTo(sb, c);
+ } else {
+ sb.append(c);
+ }
+ }
+ return sb.toString().intern();
+ }
+ // ]NOCPP]
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NamedCharacters.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NamedCharacters.java
new file mode 100644
index 000000000..266a5a28e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NamedCharacters.java
@@ -0,0 +1,944 @@
+/*
+ * Copyright 2004-2010 Apple Computer, Inc., Mozilla Foundation, and Opera
+ * Software ASA.
+ *
+ * You are granted a license to use, reproduce and create derivative works of
+ * this document.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import nu.validator.htmlparser.annotation.CharacterName;
+import nu.validator.htmlparser.annotation.NoLength;
+
+/**
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class NamedCharacters {
+
+ static final @NoLength @CharacterName String[] NAMES = { "lig", "lig;",
+ "P", "P;", "cute", "cute;", "reve;", "irc", "irc;", "y;", "r;",
+ "rave", "rave;", "pha;", "acr;", "d;", "gon;", "pf;",
+ "plyFunction;", "ing", "ing;", "cr;", "sign;", "ilde", "ilde;",
+ "ml", "ml;", "ckslash;", "rv;", "rwed;", "y;", "cause;",
+ "rnoullis;", "ta;", "r;", "pf;", "eve;", "cr;", "mpeq;", "cy;",
+ "PY", "PY;", "cute;", "p;", "pitalDifferentialD;", "yleys;",
+ "aron;", "edil", "edil;", "irc;", "onint;", "ot;", "dilla;",
+ "nterDot;", "r;", "i;", "rcleDot;", "rcleMinus;", "rclePlus;",
+ "rcleTimes;", "ockwiseContourIntegral;", "oseCurlyDoubleQuote;",
+ "oseCurlyQuote;", "lon;", "lone;", "ngruent;", "nint;",
+ "ntourIntegral;", "pf;", "product;",
+ "unterClockwiseContourIntegral;", "oss;", "cr;", "p;", "pCap;",
+ ";", "otrahd;", "cy;", "cy;", "cy;", "gger;", "rr;", "shv;",
+ "aron;", "y;", "l;", "lta;", "r;", "acriticalAcute;",
+ "acriticalDot;", "acriticalDoubleAcute;", "acriticalGrave;",
+ "acriticalTilde;", "amond;", "fferentialD;", "pf;", "t;", "tDot;",
+ "tEqual;", "ubleContourIntegral;", "ubleDot;", "ubleDownArrow;",
+ "ubleLeftArrow;", "ubleLeftRightArrow;", "ubleLeftTee;",
+ "ubleLongLeftArrow;", "ubleLongLeftRightArrow;",
+ "ubleLongRightArrow;", "ubleRightArrow;", "ubleRightTee;",
+ "ubleUpArrow;", "ubleUpDownArrow;", "ubleVerticalBar;", "wnArrow;",
+ "wnArrowBar;", "wnArrowUpArrow;", "wnBreve;", "wnLeftRightVector;",
+ "wnLeftTeeVector;", "wnLeftVector;", "wnLeftVectorBar;",
+ "wnRightTeeVector;", "wnRightVector;", "wnRightVectorBar;",
+ "wnTee;", "wnTeeArrow;", "wnarrow;", "cr;", "trok;", "G;", "H",
+ "H;", "cute", "cute;", "aron;", "irc", "irc;", "y;", "ot;", "r;",
+ "rave", "rave;", "ement;", "acr;", "ptySmallSquare;",
+ "ptyVerySmallSquare;", "gon;", "pf;", "silon;", "ual;",
+ "ualTilde;", "uilibrium;", "cr;", "im;", "a;", "ml", "ml;",
+ "ists;", "ponentialE;", "y;", "r;", "lledSmallSquare;",
+ "lledVerySmallSquare;", "pf;", "rAll;", "uriertrf;", "cr;", "cy;",
+ "", ";", "mma;", "mmad;", "reve;", "edil;", "irc;", "y;", "ot;",
+ "r;", ";", "pf;", "eaterEqual;", "eaterEqualLess;",
+ "eaterFullEqual;", "eaterGreater;", "eaterLess;",
+ "eaterSlantEqual;", "eaterTilde;", "cr;", ";", "RDcy;", "cek;",
+ "t;", "irc;", "r;", "lbertSpace;", "pf;", "rizontalLine;", "cr;",
+ "trok;", "mpDownHump;", "mpEqual;", "cy;", "lig;", "cy;", "cute",
+ "cute;", "irc", "irc;", "y;", "ot;", "r;", "rave", "rave;", ";",
+ "acr;", "aginaryI;", "plies;", "t;", "tegral;", "tersection;",
+ "visibleComma;", "visibleTimes;", "gon;", "pf;", "ta;", "cr;",
+ "ilde;", "kcy;", "ml", "ml;", "irc;", "y;", "r;", "pf;", "cr;",
+ "ercy;", "kcy;", "cy;", "cy;", "ppa;", "edil;", "y;", "r;", "pf;",
+ "cr;", "cy;", "", ";", "cute;", "mbda;", "ng;", "placetrf;", "rr;",
+ "aron;", "edil;", "y;", "ftAngleBracket;", "ftArrow;",
+ "ftArrowBar;", "ftArrowRightArrow;", "ftCeiling;",
+ "ftDoubleBracket;", "ftDownTeeVector;", "ftDownVector;",
+ "ftDownVectorBar;", "ftFloor;", "ftRightArrow;", "ftRightVector;",
+ "ftTee;", "ftTeeArrow;", "ftTeeVector;", "ftTriangle;",
+ "ftTriangleBar;", "ftTriangleEqual;", "ftUpDownVector;",
+ "ftUpTeeVector;", "ftUpVector;", "ftUpVectorBar;", "ftVector;",
+ "ftVectorBar;", "ftarrow;", "ftrightarrow;", "ssEqualGreater;",
+ "ssFullEqual;", "ssGreater;", "ssLess;", "ssSlantEqual;",
+ "ssTilde;", "r;", ";", "eftarrow;", "idot;", "ngLeftArrow;",
+ "ngLeftRightArrow;", "ngRightArrow;", "ngleftarrow;",
+ "ngleftrightarrow;", "ngrightarrow;", "pf;", "werLeftArrow;",
+ "werRightArrow;", "cr;", "h;", "trok;", ";", "p;", "y;",
+ "diumSpace;", "llintrf;", "r;", "nusPlus;", "pf;", "cr;", ";",
+ "cy;", "cute;", "aron;", "edil;", "y;", "gativeMediumSpace;",
+ "gativeThickSpace;", "gativeThinSpace;", "gativeVeryThinSpace;",
+ "stedGreaterGreater;", "stedLessLess;", "wLine;", "r;", "Break;",
+ "nBreakingSpace;", "pf;", "t;", "tCongruent;", "tCupCap;",
+ "tDoubleVerticalBar;", "tElement;", "tEqual;", "tEqualTilde;",
+ "tExists;", "tGreater;", "tGreaterEqual;", "tGreaterFullEqual;",
+ "tGreaterGreater;", "tGreaterLess;", "tGreaterSlantEqual;",
+ "tGreaterTilde;", "tHumpDownHump;", "tHumpEqual;",
+ "tLeftTriangle;", "tLeftTriangleBar;", "tLeftTriangleEqual;",
+ "tLess;", "tLessEqual;", "tLessGreater;", "tLessLess;",
+ "tLessSlantEqual;", "tLessTilde;", "tNestedGreaterGreater;",
+ "tNestedLessLess;", "tPrecedes;", "tPrecedesEqual;",
+ "tPrecedesSlantEqual;", "tReverseElement;", "tRightTriangle;",
+ "tRightTriangleBar;", "tRightTriangleEqual;", "tSquareSubset;",
+ "tSquareSubsetEqual;", "tSquareSuperset;", "tSquareSupersetEqual;",
+ "tSubset;", "tSubsetEqual;", "tSucceeds;", "tSucceedsEqual;",
+ "tSucceedsSlantEqual;", "tSucceedsTilde;", "tSuperset;",
+ "tSupersetEqual;", "tTilde;", "tTildeEqual;", "tTildeFullEqual;",
+ "tTildeTilde;", "tVerticalBar;", "cr;", "ilde", "ilde;", ";",
+ "lig;", "cute", "cute;", "irc", "irc;", "y;", "blac;", "r;",
+ "rave", "rave;", "acr;", "ega;", "icron;", "pf;",
+ "enCurlyDoubleQuote;", "enCurlyQuote;", ";", "cr;", "lash",
+ "lash;", "ilde", "ilde;", "imes;", "ml", "ml;", "erBar;",
+ "erBrace;", "erBracket;", "erParenthesis;", "rtialD;", "y;", "r;",
+ "i;", ";", "usMinus;", "incareplane;", "pf;", ";", "ecedes;",
+ "ecedesEqual;", "ecedesSlantEqual;", "ecedesTilde;", "ime;",
+ "oduct;", "oportion;", "oportional;", "cr;", "i;", "OT", "OT;",
+ "r;", "pf;", "cr;", "arr;", "G", "G;", "cute;", "ng;", "rr;",
+ "rrtl;", "aron;", "edil;", "y;", ";", "verseElement;",
+ "verseEquilibrium;", "verseUpEquilibrium;", "r;", "o;",
+ "ghtAngleBracket;", "ghtArrow;", "ghtArrowBar;",
+ "ghtArrowLeftArrow;", "ghtCeiling;", "ghtDoubleBracket;",
+ "ghtDownTeeVector;", "ghtDownVector;", "ghtDownVectorBar;",
+ "ghtFloor;", "ghtTee;", "ghtTeeArrow;", "ghtTeeVector;",
+ "ghtTriangle;", "ghtTriangleBar;", "ghtTriangleEqual;",
+ "ghtUpDownVector;", "ghtUpTeeVector;", "ghtUpVector;",
+ "ghtUpVectorBar;", "ghtVector;", "ghtVectorBar;", "ghtarrow;",
+ "pf;", "undImplies;", "ightarrow;", "cr;", "h;", "leDelayed;",
+ "CHcy;", "cy;", "FTcy;", "cute;", ";", "aron;", "edil;", "irc;",
+ "y;", "r;", "ortDownArrow;", "ortLeftArrow;", "ortRightArrow;",
+ "ortUpArrow;", "gma;", "allCircle;", "pf;", "rt;", "uare;",
+ "uareIntersection;", "uareSubset;", "uareSubsetEqual;",
+ "uareSuperset;", "uareSupersetEqual;", "uareUnion;", "cr;", "ar;",
+ "b;", "bset;", "bsetEqual;", "cceeds;", "cceedsEqual;",
+ "cceedsSlantEqual;", "cceedsTilde;", "chThat;", "m;", "p;",
+ "perset;", "persetEqual;", "pset;", "ORN", "ORN;", "ADE;", "Hcy;",
+ "cy;", "b;", "u;", "aron;", "edil;", "y;", "r;", "erefore;",
+ "eta;", "ickSpace;", "inSpace;", "lde;", "ldeEqual;",
+ "ldeFullEqual;", "ldeTilde;", "pf;", "ipleDot;", "cr;", "trok;",
+ "cute", "cute;", "rr;", "rrocir;", "rcy;", "reve;", "irc", "irc;",
+ "y;", "blac;", "r;", "rave", "rave;", "acr;", "derBar;",
+ "derBrace;", "derBracket;", "derParenthesis;", "ion;", "ionPlus;",
+ "gon;", "pf;", "Arrow;", "ArrowBar;", "ArrowDownArrow;",
+ "DownArrow;", "Equilibrium;", "Tee;", "TeeArrow;", "arrow;",
+ "downarrow;", "perLeftArrow;", "perRightArrow;", "si;", "silon;",
+ "ing;", "cr;", "ilde;", "ml", "ml;", "ash;", "ar;", "y;", "ash;",
+ "ashl;", "e;", "rbar;", "rt;", "rticalBar;", "rticalLine;",
+ "rticalSeparator;", "rticalTilde;", "ryThinSpace;", "r;", "pf;",
+ "cr;", "dash;", "irc;", "dge;", "r;", "pf;", "cr;", "r;", ";",
+ "pf;", "cr;", "cy;", "cy;", "cy;", "cute", "cute;", "irc;", "y;",
+ "r;", "pf;", "cr;", "ml;", "cy;", "cute;", "aron;", "y;", "ot;",
+ "roWidthSpace;", "ta;", "r;", "pf;", "cr;", "cute", "cute;",
+ "reve;", ";", "E;", "d;", "irc", "irc;", "ute", "ute;", "y;",
+ "lig", "lig;", ";", "r;", "rave", "rave;", "efsym;", "eph;",
+ "pha;", "acr;", "alg;", "p", "p;", "d;", "dand;", "dd;", "dslope;",
+ "dv;", "g;", "ge;", "gle;", "gmsd;", "gmsdaa;", "gmsdab;",
+ "gmsdac;", "gmsdad;", "gmsdae;", "gmsdaf;", "gmsdag;", "gmsdah;",
+ "grt;", "grtvb;", "grtvbd;", "gsph;", "gst;", "gzarr;", "gon;",
+ "pf;", ";", "E;", "acir;", "e;", "id;", "os;", "prox;", "proxeq;",
+ "ing", "ing;", "cr;", "t;", "ymp;", "ympeq;", "ilde", "ilde;",
+ "ml", "ml;", "conint;", "int;", "ot;", "ckcong;", "ckepsilon;",
+ "ckprime;", "cksim;", "cksimeq;", "rvee;", "rwed;", "rwedge;",
+ "rk;", "rktbrk;", "ong;", "y;", "quo;", "caus;", "cause;",
+ "mptyv;", "psi;", "rnou;", "ta;", "th;", "tween;", "r;", "gcap;",
+ "gcirc;", "gcup;", "godot;", "goplus;", "gotimes;", "gsqcup;",
+ "gstar;", "gtriangledown;", "gtriangleup;", "guplus;", "gvee;",
+ "gwedge;", "arow;", "acklozenge;", "acksquare;", "acktriangle;",
+ "acktriangledown;", "acktriangleleft;", "acktriangleright;",
+ "ank;", "k12;", "k14;", "k34;", "ock;", "e;", "equiv;", "ot;",
+ "pf;", "t;", "ttom;", "wtie;", "xDL;", "xDR;", "xDl;", "xDr;",
+ "xH;", "xHD;", "xHU;", "xHd;", "xHu;", "xUL;", "xUR;", "xUl;",
+ "xUr;", "xV;", "xVH;", "xVL;", "xVR;", "xVh;", "xVl;", "xVr;",
+ "xbox;", "xdL;", "xdR;", "xdl;", "xdr;", "xh;", "xhD;", "xhU;",
+ "xhd;", "xhu;", "xminus;", "xplus;", "xtimes;", "xuL;", "xuR;",
+ "xul;", "xur;", "xv;", "xvH;", "xvL;", "xvR;", "xvh;", "xvl;",
+ "xvr;", "rime;", "eve;", "vbar", "vbar;", "cr;", "emi;", "im;",
+ "ime;", "ol;", "olb;", "olhsub;", "ll;", "llet;", "mp;", "mpE;",
+ "mpe;", "mpeq;", "cute;", "p;", "pand;", "pbrcup;", "pcap;",
+ "pcup;", "pdot;", "ps;", "ret;", "ron;", "aps;", "aron;", "edil",
+ "edil;", "irc;", "ups;", "upssm;", "ot;", "dil", "dil;", "mptyv;",
+ "nt", "nt;", "nterdot;", "r;", "cy;", "eck;", "eckmark;", "i;",
+ "r;", "rE;", "rc;", "rceq;", "rclearrowleft;", "rclearrowright;",
+ "rcledR;", "rcledS;", "rcledast;", "rcledcirc;", "rcleddash;",
+ "re;", "rfnint;", "rmid;", "rscir;", "ubs;", "ubsuit;", "lon;",
+ "lone;", "loneq;", "mma;", "mmat;", "mp;", "mpfn;", "mplement;",
+ "mplexes;", "ng;", "ngdot;", "nint;", "pf;", "prod;", "py", "py;",
+ "pysr;", "arr;", "oss;", "cr;", "ub;", "ube;", "up;", "upe;",
+ "dot;", "darrl;", "darrr;", "epr;", "esc;", "larr;", "larrp;",
+ "p;", "pbrcap;", "pcap;", "pcup;", "pdot;", "por;", "ps;", "rarr;",
+ "rarrm;", "rlyeqprec;", "rlyeqsucc;", "rlyvee;", "rlywedge;",
+ "rren", "rren;", "rvearrowleft;", "rvearrowright;", "vee;", "wed;",
+ "conint;", "int;", "lcty;", "rr;", "ar;", "gger;", "leth;", "rr;",
+ "sh;", "shv;", "karow;", "lac;", "aron;", "y;", ";", "agger;",
+ "arr;", "otseq;", "g", "g;", "lta;", "mptyv;", "isht;", "r;",
+ "arl;", "arr;", "am;", "amond;", "amondsuit;", "ams;", "e;",
+ "gamma;", "sin;", "v;", "vide", "vide;", "videontimes;", "vonx;",
+ "cy;", "corn;", "crop;", "llar;", "pf;", "t;", "teq;", "teqdot;",
+ "tminus;", "tplus;", "tsquare;", "ublebarwedge;", "wnarrow;",
+ "wndownarrows;", "wnharpoonleft;", "wnharpoonright;", "bkarow;",
+ "corn;", "crop;", "cr;", "cy;", "ol;", "trok;", "dot;", "ri;",
+ "rif;", "arr;", "har;", "angle;", "cy;", "igrarr;", "Dot;", "ot;",
+ "cute", "cute;", "ster;", "aron;", "ir;", "irc", "irc;", "olon;",
+ "y;", "ot;", ";", "Dot;", "r;", ";", "rave", "rave;", "s;",
+ "sdot;", ";", "inters;", "l;", "s;", "sdot;", "acr;", "pty;",
+ "ptyset;", "ptyv;", "sp13;", "sp14;", "sp;", "g;", "sp;", "gon;",
+ "pf;", "ar;", "arsl;", "lus;", "si;", "silon;", "siv;", "circ;",
+ "colon;", "sim;", "slantgtr;", "slantless;", "uals;", "uest;",
+ "uiv;", "uivDD;", "vparsl;", "Dot;", "arr;", "cr;", "dot;", "im;",
+ "a;", "h", "h;", "ml", "ml;", "ro;", "cl;", "ist;", "pectation;",
+ "ponentiale;", "llingdotseq;", "y;", "male;", "ilig;", "lig;",
+ "llig;", "r;", "lig;", "lig;", "at;", "lig;", "tns;", "of;", "pf;",
+ "rall;", "rk;", "rkv;", "artint;", "ac12", "ac12;", "ac13;",
+ "ac14", "ac14;", "ac15;", "ac16;", "ac18;", "ac23;", "ac25;",
+ "ac34", "ac34;", "ac35;", "ac38;", "ac45;", "ac56;", "ac58;",
+ "ac78;", "asl;", "own;", "cr;", ";", "l;", "cute;", "mma;",
+ "mmad;", "p;", "reve;", "irc;", "y;", "ot;", ";", "l;", "q;",
+ "qq;", "qslant;", "s;", "scc;", "sdot;", "sdoto;", "sdotol;",
+ "sl;", "sles;", "r;", ";", "g;", "mel;", "cy;", ";", "E;", "a;",
+ "j;", "E;", "ap;", "approx;", "e;", "eq;", "eqq;", "sim;", "pf;",
+ "ave;", "cr;", "im;", "ime;", "iml;", "", ";", "cc;", "cir;",
+ "dot;", "lPar;", "quest;", "rapprox;", "rarr;", "rdot;",
+ "reqless;", "reqqless;", "rless;", "rsim;", "ertneqq;", "nE;",
+ "rr;", "irsp;", "lf;", "milt;", "rdcy;", "rr;", "rrcir;", "rrw;",
+ "ar;", "irc;", "arts;", "artsuit;", "llip;", "rcon;", "r;",
+ "searow;", "swarow;", "arr;", "mtht;", "okleftarrow;",
+ "okrightarrow;", "pf;", "rbar;", "cr;", "lash;", "trok;", "bull;",
+ "phen;", "cute", "cute;", ";", "irc", "irc;", "y;", "cy;", "xcl",
+ "xcl;", "f;", "r;", "rave", "rave;", ";", "iint;", "int;", "nfin;",
+ "ota;", "lig;", "acr;", "age;", "agline;", "agpart;", "ath;",
+ "of;", "ped;", ";", "care;", "fin;", "fintie;", "odot;", "t;",
+ "tcal;", "tegers;", "tercal;", "tlarhk;", "tprod;", "cy;", "gon;",
+ "pf;", "ta;", "rod;", "uest", "uest;", "cr;", "in;", "inE;",
+ "indot;", "ins;", "insv;", "inv;", ";", "ilde;", "kcy;", "ml",
+ "ml;", "irc;", "y;", "r;", "ath;", "pf;", "cr;", "ercy;", "kcy;",
+ "ppa;", "ppav;", "edil;", "y;", "r;", "reen;", "cy;", "cy;", "pf;",
+ "cr;", "arr;", "rr;", "tail;", "arr;", ";", "g;", "ar;", "cute;",
+ "emptyv;", "gran;", "mbda;", "ng;", "ngd;", "ngle;", "p;", "quo",
+ "quo;", "rr;", "rrb;", "rrbfs;", "rrfs;", "rrhk;", "rrlp;",
+ "rrpl;", "rrsim;", "rrtl;", "t;", "tail;", "te;", "tes;", "arr;",
+ "brk;", "race;", "rack;", "rke;", "rksld;", "rkslu;", "aron;",
+ "edil;", "eil;", "ub;", "y;", "ca;", "quo;", "quor;", "rdhar;",
+ "rushar;", "sh;", ";", "ftarrow;", "ftarrowtail;",
+ "ftharpoondown;", "ftharpoonup;", "ftleftarrows;", "ftrightarrow;",
+ "ftrightarrows;", "ftrightharpoons;", "ftrightsquigarrow;",
+ "ftthreetimes;", "g;", "q;", "qq;", "qslant;", "s;", "scc;",
+ "sdot;", "sdoto;", "sdotor;", "sg;", "sges;", "ssapprox;",
+ "ssdot;", "sseqgtr;", "sseqqgtr;", "ssgtr;", "sssim;", "isht;",
+ "loor;", "r;", ";", "E;", "ard;", "aru;", "arul;", "blk;", "cy;",
+ ";", "arr;", "corner;", "hard;", "tri;", "idot;", "oust;",
+ "oustache;", "E;", "ap;", "approx;", "e;", "eq;", "eqq;", "sim;",
+ "ang;", "arr;", "brk;", "ngleftarrow;", "ngleftrightarrow;",
+ "ngmapsto;", "ngrightarrow;", "oparrowleft;", "oparrowright;",
+ "par;", "pf;", "plus;", "times;", "wast;", "wbar;", "z;", "zenge;",
+ "zf;", "ar;", "arlt;", "arr;", "corner;", "har;", "hard;", "m;",
+ "tri;", "aquo;", "cr;", "h;", "im;", "ime;", "img;", "qb;", "quo;",
+ "quor;", "trok;", "", ";", "cc;", "cir;", "dot;", "hree;", "imes;",
+ "larr;", "quest;", "rPar;", "ri;", "rie;", "rif;", "rdshar;",
+ "ruhar;", "ertneqq;", "nE;", "Dot;", "cr", "cr;", "le;", "lt;",
+ "ltese;", "p;", "psto;", "pstodown;", "pstoleft;", "pstoup;",
+ "rker;", "omma;", "y;", "ash;", "asuredangle;", "r;", "o;", "cro",
+ "cro;", "d;", "dast;", "dcir;", "ddot", "ddot;", "nus;", "nusb;",
+ "nusd;", "nusdu;", "cp;", "dr;", "plus;", "dels;", "pf;", ";",
+ "cr;", "tpos;", ";", "ltimap;", "map;", "g;", "t;", "tv;",
+ "eftarrow;", "eftrightarrow;", "l;", "t;", "tv;", "ightarrow;",
+ "Dash;", "dash;", "bla;", "cute;", "ng;", "p;", "pE;", "pid;",
+ "pos;", "pprox;", "tur;", "tural;", "turals;", "sp", "sp;", "ump;",
+ "umpe;", "ap;", "aron;", "edil;", "ong;", "ongdot;", "up;", "y;",
+ "ash;", ";", "Arr;", "arhk;", "arr;", "arrow;", "dot;", "quiv;",
+ "sear;", "sim;", "xist;", "xists;", "r;", "E;", "e;", "eq;",
+ "eqq;", "eqslant;", "es;", "sim;", "t;", "tr;", "Arr;", "arr;",
+ "par;", ";", "s;", "sd;", "v;", "cy;", "Arr;", "E;", "arr;", "dr;",
+ "e;", "eftarrow;", "eftrightarrow;", "eq;", "eqq;", "eqslant;",
+ "es;", "ess;", "sim;", "t;", "tri;", "trie;", "id;", "pf;", "t",
+ "t;", "tin;", "tinE;", "tindot;", "tinva;", "tinvb;", "tinvc;",
+ "tni;", "tniva;", "tnivb;", "tnivc;", "ar;", "arallel;", "arsl;",
+ "art;", "olint;", "r;", "rcue;", "re;", "rec;", "receq;", "Arr;",
+ "arr;", "arrc;", "arrw;", "ightarrow;", "tri;", "trie;", "c;",
+ "ccue;", "ce;", "cr;", "hortmid;", "hortparallel;", "im;", "ime;",
+ "imeq;", "mid;", "par;", "qsube;", "qsupe;", "ub;", "ubE;", "ube;",
+ "ubset;", "ubseteq;", "ubseteqq;", "ucc;", "ucceq;", "up;", "upE;",
+ "upe;", "upset;", "upseteq;", "upseteqq;", "gl;", "ilde", "ilde;",
+ "lg;", "riangleleft;", "rianglelefteq;", "riangleright;",
+ "rianglerighteq;", ";", "m;", "mero;", "msp;", "Dash;", "Harr;",
+ "ap;", "dash;", "ge;", "gt;", "infin;", "lArr;", "le;", "lt;",
+ "ltrie;", "rArr;", "rtrie;", "sim;", "Arr;", "arhk;", "arr;",
+ "arrow;", "near;", ";", "cute", "cute;", "st;", "ir;", "irc",
+ "irc;", "y;", "ash;", "blac;", "iv;", "ot;", "sold;", "lig;",
+ "cir;", "r;", "on;", "rave", "rave;", "t;", "bar;", "m;", "nt;",
+ "arr;", "cir;", "cross;", "ine;", "t;", "acr;", "ega;", "icron;",
+ "id;", "inus;", "pf;", "ar;", "erp;", "lus;", ";", "arr;", "d;",
+ "der;", "derof;", "df", "df;", "dm", "dm;", "igof;", "or;",
+ "slope;", "v;", "cr;", "lash", "lash;", "ol;", "ilde", "ilde;",
+ "imes;", "imesas;", "ml", "ml;", "bar;", "r;", "ra", "ra;",
+ "rallel;", "rsim;", "rsl;", "rt;", "y;", "rcnt;", "riod;", "rmil;",
+ "rp;", "rtenk;", "r;", "i;", "iv;", "mmat;", "one;", ";",
+ "tchfork;", "v;", "anck;", "anckh;", "ankv;", "us;", "usacir;",
+ "usb;", "uscir;", "usdo;", "usdu;", "use;", "usmn", "usmn;",
+ "ussim;", "ustwo;", ";", "intint;", "pf;", "und", "und;", ";",
+ "E;", "ap;", "cue;", "e;", "ec;", "ecapprox;", "eccurlyeq;",
+ "eceq;", "ecnapprox;", "ecneqq;", "ecnsim;", "ecsim;", "ime;",
+ "imes;", "nE;", "nap;", "nsim;", "od;", "ofalar;", "ofline;",
+ "ofsurf;", "op;", "opto;", "sim;", "urel;", "cr;", "i;", "ncsp;",
+ "r;", "nt;", "pf;", "rime;", "cr;", "aternions;", "atint;", "est;",
+ "esteq;", "ot", "ot;", "arr;", "rr;", "tail;", "arr;", "ar;",
+ "ce;", "cute;", "dic;", "emptyv;", "ng;", "ngd;", "nge;", "ngle;",
+ "quo", "quo;", "rr;", "rrap;", "rrb;", "rrbfs;", "rrc;", "rrfs;",
+ "rrhk;", "rrlp;", "rrpl;", "rrsim;", "rrtl;", "rrw;", "tail;",
+ "tio;", "tionals;", "arr;", "brk;", "race;", "rack;", "rke;",
+ "rksld;", "rkslu;", "aron;", "edil;", "eil;", "ub;", "y;", "ca;",
+ "ldhar;", "quo;", "quor;", "sh;", "al;", "aline;", "alpart;",
+ "als;", "ct;", "g", "g;", "isht;", "loor;", "r;", "ard;", "aru;",
+ "arul;", "o;", "ov;", "ghtarrow;", "ghtarrowtail;",
+ "ghtharpoondown;", "ghtharpoonup;", "ghtleftarrows;",
+ "ghtleftharpoons;", "ghtrightarrows;", "ghtsquigarrow;",
+ "ghtthreetimes;", "ng;", "singdotseq;", "arr;", "har;", "m;",
+ "oust;", "oustache;", "mid;", "ang;", "arr;", "brk;", "par;",
+ "pf;", "plus;", "times;", "ar;", "argt;", "polint;", "arr;",
+ "aquo;", "cr;", "h;", "qb;", "quo;", "quor;", "hree;", "imes;",
+ "ri;", "rie;", "rif;", "riltri;", "luhar;", ";", "cute;", "quo;",
+ ";", "E;", "ap;", "aron;", "cue;", "e;", "edil;", "irc;", "nE;",
+ "nap;", "nsim;", "polint;", "sim;", "y;", "ot;", "otb;", "ote;",
+ "Arr;", "arhk;", "arr;", "arrow;", "ct", "ct;", "mi;", "swar;",
+ "tminus;", "tmn;", "xt;", "r;", "rown;", "arp;", "chcy;", "cy;",
+ "ortmid;", "ortparallel;", "y", "y;", "gma;", "gmaf;", "gmav;",
+ "m;", "mdot;", "me;", "meq;", "mg;", "mgE;", "ml;", "mlE;", "mne;",
+ "mplus;", "mrarr;", "arr;", "allsetminus;", "ashp;", "eparsl;",
+ "id;", "ile;", "t;", "te;", "tes;", "ftcy;", "l;", "lb;", "lbar;",
+ "pf;", "ades;", "adesuit;", "ar;", "cap;", "caps;", "cup;",
+ "cups;", "sub;", "sube;", "subset;", "subseteq;", "sup;", "supe;",
+ "supset;", "supseteq;", "u;", "uare;", "uarf;", "uf;", "arr;",
+ "cr;", "etmn;", "mile;", "tarf;", "ar;", "arf;", "raightepsilon;",
+ "raightphi;", "rns;", "b;", "bE;", "bdot;", "be;", "bedot;",
+ "bmult;", "bnE;", "bne;", "bplus;", "brarr;", "bset;", "bseteq;",
+ "bseteqq;", "bsetneq;", "bsetneqq;", "bsim;", "bsub;", "bsup;",
+ "cc;", "ccapprox;", "cccurlyeq;", "cceq;", "ccnapprox;", "ccneqq;",
+ "ccnsim;", "ccsim;", "m;", "ng;", "p1", "p1;", "p2", "p2;", "p3",
+ "p3;", "p;", "pE;", "pdot;", "pdsub;", "pe;", "pedot;", "phsol;",
+ "phsub;", "plarr;", "pmult;", "pnE;", "pne;", "pplus;", "pset;",
+ "pseteq;", "pseteqq;", "psetneq;", "psetneqq;", "psim;", "psub;",
+ "psup;", "Arr;", "arhk;", "arr;", "arrow;", "nwar;", "lig", "lig;",
+ "rget;", "u;", "rk;", "aron;", "edil;", "y;", "ot;", "lrec;", "r;",
+ "ere4;", "erefore;", "eta;", "etasym;", "etav;", "ickapprox;",
+ "icksim;", "insp;", "kap;", "ksim;", "orn", "orn;", "lde;", "mes",
+ "mes;", "mesb;", "mesbar;", "mesd;", "nt;", "ea;", "p;", "pbot;",
+ "pcir;", "pf;", "pfork;", "sa;", "rime;", "ade;", "iangle;",
+ "iangledown;", "iangleleft;", "ianglelefteq;", "iangleq;",
+ "iangleright;", "ianglerighteq;", "idot;", "ie;", "iminus;",
+ "iplus;", "isb;", "itime;", "pezium;", "cr;", "cy;", "hcy;",
+ "trok;", "ixt;", "oheadleftarrow;", "oheadrightarrow;", "rr;",
+ "ar;", "cute", "cute;", "rr;", "rcy;", "reve;", "irc", "irc;",
+ "y;", "arr;", "blac;", "har;", "isht;", "r;", "rave", "rave;",
+ "arl;", "arr;", "blk;", "corn;", "corner;", "crop;", "tri;",
+ "acr;", "l", "l;", "gon;", "pf;", "arrow;", "downarrow;",
+ "harpoonleft;", "harpoonright;", "lus;", "si;", "sih;", "silon;",
+ "uparrows;", "corn;", "corner;", "crop;", "ing;", "tri;", "cr;",
+ "dot;", "ilde;", "ri;", "rif;", "arr;", "ml", "ml;", "angle;",
+ "rr;", "ar;", "arv;", "ash;", "ngrt;", "repsilon;", "rkappa;",
+ "rnothing;", "rphi;", "rpi;", "rpropto;", "rr;", "rrho;",
+ "rsigma;", "rsubsetneq;", "rsubsetneqq;", "rsupsetneq;",
+ "rsupsetneqq;", "rtheta;", "rtriangleleft;", "rtriangleright;",
+ "y;", "ash;", "e;", "ebar;", "eeq;", "llip;", "rbar;", "rt;", "r;",
+ "tri;", "sub;", "sup;", "pf;", "rop;", "tri;", "cr;", "ubnE;",
+ "ubne;", "upnE;", "upne;", "igzag;", "irc;", "dbar;", "dge;",
+ "dgeq;", "ierp;", "r;", "pf;", ";", ";", "eath;", "cr;", "ap;",
+ "irc;", "up;", "tri;", "r;", "Arr;", "arr;", ";", "Arr;", "arr;",
+ "ap;", "is;", "dot;", "pf;", "plus;", "time;", "Arr;", "arr;",
+ "cr;", "qcup;", "plus;", "tri;", "ee;", "edge;", "cute", "cute;",
+ "cy;", "irc;", "y;", "n", "n;", "r;", "cy;", "pf;", "cr;", "cy;",
+ "ml", "ml;", "cute;", "aron;", "y;", "ot;", "etrf;", "ta;", "r;",
+ "cy;", "grarr;", "pf;", "cr;", "j;", "nj;", };
+
+ static final @NoLength char[][] VALUES = { { '\u00c6' }, { '\u00c6' },
+ { '\u0026' }, { '\u0026' }, { '\u00c1' }, { '\u00c1' },
+ { '\u0102' }, { '\u00c2' }, { '\u00c2' }, { '\u0410' },
+ { '\ud835', '\udd04' }, { '\u00c0' }, { '\u00c0' }, { '\u0391' },
+ { '\u0100' }, { '\u2a53' }, { '\u0104' }, { '\ud835', '\udd38' },
+ { '\u2061' }, { '\u00c5' }, { '\u00c5' }, { '\ud835', '\udc9c' },
+ { '\u2254' }, { '\u00c3' }, { '\u00c3' }, { '\u00c4' },
+ { '\u00c4' }, { '\u2216' }, { '\u2ae7' }, { '\u2306' },
+ { '\u0411' }, { '\u2235' }, { '\u212c' }, { '\u0392' },
+ { '\ud835', '\udd05' }, { '\ud835', '\udd39' }, { '\u02d8' },
+ { '\u212c' }, { '\u224e' }, { '\u0427' }, { '\u00a9' },
+ { '\u00a9' }, { '\u0106' }, { '\u22d2' }, { '\u2145' },
+ { '\u212d' }, { '\u010c' }, { '\u00c7' }, { '\u00c7' },
+ { '\u0108' }, { '\u2230' }, { '\u010a' }, { '\u00b8' },
+ { '\u00b7' }, { '\u212d' }, { '\u03a7' }, { '\u2299' },
+ { '\u2296' }, { '\u2295' }, { '\u2297' }, { '\u2232' },
+ { '\u201d' }, { '\u2019' }, { '\u2237' }, { '\u2a74' },
+ { '\u2261' }, { '\u222f' }, { '\u222e' }, { '\u2102' },
+ { '\u2210' }, { '\u2233' }, { '\u2a2f' }, { '\ud835', '\udc9e' },
+ { '\u22d3' }, { '\u224d' }, { '\u2145' }, { '\u2911' },
+ { '\u0402' }, { '\u0405' }, { '\u040f' }, { '\u2021' },
+ { '\u21a1' }, { '\u2ae4' }, { '\u010e' }, { '\u0414' },
+ { '\u2207' }, { '\u0394' }, { '\ud835', '\udd07' }, { '\u00b4' },
+ { '\u02d9' }, { '\u02dd' }, { '\u0060' }, { '\u02dc' },
+ { '\u22c4' }, { '\u2146' }, { '\ud835', '\udd3b' }, { '\u00a8' },
+ { '\u20dc' }, { '\u2250' }, { '\u222f' }, { '\u00a8' },
+ { '\u21d3' }, { '\u21d0' }, { '\u21d4' }, { '\u2ae4' },
+ { '\u27f8' }, { '\u27fa' }, { '\u27f9' }, { '\u21d2' },
+ { '\u22a8' }, { '\u21d1' }, { '\u21d5' }, { '\u2225' },
+ { '\u2193' }, { '\u2913' }, { '\u21f5' }, { '\u0311' },
+ { '\u2950' }, { '\u295e' }, { '\u21bd' }, { '\u2956' },
+ { '\u295f' }, { '\u21c1' }, { '\u2957' }, { '\u22a4' },
+ { '\u21a7' }, { '\u21d3' }, { '\ud835', '\udc9f' }, { '\u0110' },
+ { '\u014a' }, { '\u00d0' }, { '\u00d0' }, { '\u00c9' },
+ { '\u00c9' }, { '\u011a' }, { '\u00ca' }, { '\u00ca' },
+ { '\u042d' }, { '\u0116' }, { '\ud835', '\udd08' }, { '\u00c8' },
+ { '\u00c8' }, { '\u2208' }, { '\u0112' }, { '\u25fb' },
+ { '\u25ab' }, { '\u0118' }, { '\ud835', '\udd3c' }, { '\u0395' },
+ { '\u2a75' }, { '\u2242' }, { '\u21cc' }, { '\u2130' },
+ { '\u2a73' }, { '\u0397' }, { '\u00cb' }, { '\u00cb' },
+ { '\u2203' }, { '\u2147' }, { '\u0424' }, { '\ud835', '\udd09' },
+ { '\u25fc' }, { '\u25aa' }, { '\ud835', '\udd3d' }, { '\u2200' },
+ { '\u2131' }, { '\u2131' }, { '\u0403' }, { '\u003e' },
+ { '\u003e' }, { '\u0393' }, { '\u03dc' }, { '\u011e' },
+ { '\u0122' }, { '\u011c' }, { '\u0413' }, { '\u0120' },
+ { '\ud835', '\udd0a' }, { '\u22d9' }, { '\ud835', '\udd3e' },
+ { '\u2265' }, { '\u22db' }, { '\u2267' }, { '\u2aa2' },
+ { '\u2277' }, { '\u2a7e' }, { '\u2273' }, { '\ud835', '\udca2' },
+ { '\u226b' }, { '\u042a' }, { '\u02c7' }, { '\u005e' },
+ { '\u0124' }, { '\u210c' }, { '\u210b' }, { '\u210d' },
+ { '\u2500' }, { '\u210b' }, { '\u0126' }, { '\u224e' },
+ { '\u224f' }, { '\u0415' }, { '\u0132' }, { '\u0401' },
+ { '\u00cd' }, { '\u00cd' }, { '\u00ce' }, { '\u00ce' },
+ { '\u0418' }, { '\u0130' }, { '\u2111' }, { '\u00cc' },
+ { '\u00cc' }, { '\u2111' }, { '\u012a' }, { '\u2148' },
+ { '\u21d2' }, { '\u222c' }, { '\u222b' }, { '\u22c2' },
+ { '\u2063' }, { '\u2062' }, { '\u012e' }, { '\ud835', '\udd40' },
+ { '\u0399' }, { '\u2110' }, { '\u0128' }, { '\u0406' },
+ { '\u00cf' }, { '\u00cf' }, { '\u0134' }, { '\u0419' },
+ { '\ud835', '\udd0d' }, { '\ud835', '\udd41' },
+ { '\ud835', '\udca5' }, { '\u0408' }, { '\u0404' }, { '\u0425' },
+ { '\u040c' }, { '\u039a' }, { '\u0136' }, { '\u041a' },
+ { '\ud835', '\udd0e' }, { '\ud835', '\udd42' },
+ { '\ud835', '\udca6' }, { '\u0409' }, { '\u003c' }, { '\u003c' },
+ { '\u0139' }, { '\u039b' }, { '\u27ea' }, { '\u2112' },
+ { '\u219e' }, { '\u013d' }, { '\u013b' }, { '\u041b' },
+ { '\u27e8' }, { '\u2190' }, { '\u21e4' }, { '\u21c6' },
+ { '\u2308' }, { '\u27e6' }, { '\u2961' }, { '\u21c3' },
+ { '\u2959' }, { '\u230a' }, { '\u2194' }, { '\u294e' },
+ { '\u22a3' }, { '\u21a4' }, { '\u295a' }, { '\u22b2' },
+ { '\u29cf' }, { '\u22b4' }, { '\u2951' }, { '\u2960' },
+ { '\u21bf' }, { '\u2958' }, { '\u21bc' }, { '\u2952' },
+ { '\u21d0' }, { '\u21d4' }, { '\u22da' }, { '\u2266' },
+ { '\u2276' }, { '\u2aa1' }, { '\u2a7d' }, { '\u2272' },
+ { '\ud835', '\udd0f' }, { '\u22d8' }, { '\u21da' }, { '\u013f' },
+ { '\u27f5' }, { '\u27f7' }, { '\u27f6' }, { '\u27f8' },
+ { '\u27fa' }, { '\u27f9' }, { '\ud835', '\udd43' }, { '\u2199' },
+ { '\u2198' }, { '\u2112' }, { '\u21b0' }, { '\u0141' },
+ { '\u226a' }, { '\u2905' }, { '\u041c' }, { '\u205f' },
+ { '\u2133' }, { '\ud835', '\udd10' }, { '\u2213' },
+ { '\ud835', '\udd44' }, { '\u2133' }, { '\u039c' }, { '\u040a' },
+ { '\u0143' }, { '\u0147' }, { '\u0145' }, { '\u041d' },
+ { '\u200b' }, { '\u200b' }, { '\u200b' }, { '\u200b' },
+ { '\u226b' }, { '\u226a' }, { '\n' }, { '\ud835', '\udd11' },
+ { '\u2060' }, { '\u00a0' }, { '\u2115' }, { '\u2aec' },
+ { '\u2262' }, { '\u226d' }, { '\u2226' }, { '\u2209' },
+ { '\u2260' }, { '\u2242', '\u0338' }, { '\u2204' }, { '\u226f' },
+ { '\u2271' }, { '\u2267', '\u0338' }, { '\u226b', '\u0338' },
+ { '\u2279' }, { '\u2a7e', '\u0338' }, { '\u2275' },
+ { '\u224e', '\u0338' }, { '\u224f', '\u0338' }, { '\u22ea' },
+ { '\u29cf', '\u0338' }, { '\u22ec' }, { '\u226e' }, { '\u2270' },
+ { '\u2278' }, { '\u226a', '\u0338' }, { '\u2a7d', '\u0338' },
+ { '\u2274' }, { '\u2aa2', '\u0338' }, { '\u2aa1', '\u0338' },
+ { '\u2280' }, { '\u2aaf', '\u0338' }, { '\u22e0' }, { '\u220c' },
+ { '\u22eb' }, { '\u29d0', '\u0338' }, { '\u22ed' },
+ { '\u228f', '\u0338' }, { '\u22e2' }, { '\u2290', '\u0338' },
+ { '\u22e3' }, { '\u2282', '\u20d2' }, { '\u2288' }, { '\u2281' },
+ { '\u2ab0', '\u0338' }, { '\u22e1' }, { '\u227f', '\u0338' },
+ { '\u2283', '\u20d2' }, { '\u2289' }, { '\u2241' }, { '\u2244' },
+ { '\u2247' }, { '\u2249' }, { '\u2224' }, { '\ud835', '\udca9' },
+ { '\u00d1' }, { '\u00d1' }, { '\u039d' }, { '\u0152' },
+ { '\u00d3' }, { '\u00d3' }, { '\u00d4' }, { '\u00d4' },
+ { '\u041e' }, { '\u0150' }, { '\ud835', '\udd12' }, { '\u00d2' },
+ { '\u00d2' }, { '\u014c' }, { '\u03a9' }, { '\u039f' },
+ { '\ud835', '\udd46' }, { '\u201c' }, { '\u2018' }, { '\u2a54' },
+ { '\ud835', '\udcaa' }, { '\u00d8' }, { '\u00d8' }, { '\u00d5' },
+ { '\u00d5' }, { '\u2a37' }, { '\u00d6' }, { '\u00d6' },
+ { '\u203e' }, { '\u23de' }, { '\u23b4' }, { '\u23dc' },
+ { '\u2202' }, { '\u041f' }, { '\ud835', '\udd13' }, { '\u03a6' },
+ { '\u03a0' }, { '\u00b1' }, { '\u210c' }, { '\u2119' },
+ { '\u2abb' }, { '\u227a' }, { '\u2aaf' }, { '\u227c' },
+ { '\u227e' }, { '\u2033' }, { '\u220f' }, { '\u2237' },
+ { '\u221d' }, { '\ud835', '\udcab' }, { '\u03a8' }, { '\u0022' },
+ { '\u0022' }, { '\ud835', '\udd14' }, { '\u211a' },
+ { '\ud835', '\udcac' }, { '\u2910' }, { '\u00ae' }, { '\u00ae' },
+ { '\u0154' }, { '\u27eb' }, { '\u21a0' }, { '\u2916' },
+ { '\u0158' }, { '\u0156' }, { '\u0420' }, { '\u211c' },
+ { '\u220b' }, { '\u21cb' }, { '\u296f' }, { '\u211c' },
+ { '\u03a1' }, { '\u27e9' }, { '\u2192' }, { '\u21e5' },
+ { '\u21c4' }, { '\u2309' }, { '\u27e7' }, { '\u295d' },
+ { '\u21c2' }, { '\u2955' }, { '\u230b' }, { '\u22a2' },
+ { '\u21a6' }, { '\u295b' }, { '\u22b3' }, { '\u29d0' },
+ { '\u22b5' }, { '\u294f' }, { '\u295c' }, { '\u21be' },
+ { '\u2954' }, { '\u21c0' }, { '\u2953' }, { '\u21d2' },
+ { '\u211d' }, { '\u2970' }, { '\u21db' }, { '\u211b' },
+ { '\u21b1' }, { '\u29f4' }, { '\u0429' }, { '\u0428' },
+ { '\u042c' }, { '\u015a' }, { '\u2abc' }, { '\u0160' },
+ { '\u015e' }, { '\u015c' }, { '\u0421' }, { '\ud835', '\udd16' },
+ { '\u2193' }, { '\u2190' }, { '\u2192' }, { '\u2191' },
+ { '\u03a3' }, { '\u2218' }, { '\ud835', '\udd4a' }, { '\u221a' },
+ { '\u25a1' }, { '\u2293' }, { '\u228f' }, { '\u2291' },
+ { '\u2290' }, { '\u2292' }, { '\u2294' }, { '\ud835', '\udcae' },
+ { '\u22c6' }, { '\u22d0' }, { '\u22d0' }, { '\u2286' },
+ { '\u227b' }, { '\u2ab0' }, { '\u227d' }, { '\u227f' },
+ { '\u220b' }, { '\u2211' }, { '\u22d1' }, { '\u2283' },
+ { '\u2287' }, { '\u22d1' }, { '\u00de' }, { '\u00de' },
+ { '\u2122' }, { '\u040b' }, { '\u0426' }, { '\u0009' },
+ { '\u03a4' }, { '\u0164' }, { '\u0162' }, { '\u0422' },
+ { '\ud835', '\udd17' }, { '\u2234' }, { '\u0398' },
+ { '\u205f', '\u200a' }, { '\u2009' }, { '\u223c' }, { '\u2243' },
+ { '\u2245' }, { '\u2248' }, { '\ud835', '\udd4b' }, { '\u20db' },
+ { '\ud835', '\udcaf' }, { '\u0166' }, { '\u00da' }, { '\u00da' },
+ { '\u219f' }, { '\u2949' }, { '\u040e' }, { '\u016c' },
+ { '\u00db' }, { '\u00db' }, { '\u0423' }, { '\u0170' },
+ { '\ud835', '\udd18' }, { '\u00d9' }, { '\u00d9' }, { '\u016a' },
+ { '\u005f' }, { '\u23df' }, { '\u23b5' }, { '\u23dd' },
+ { '\u22c3' }, { '\u228e' }, { '\u0172' }, { '\ud835', '\udd4c' },
+ { '\u2191' }, { '\u2912' }, { '\u21c5' }, { '\u2195' },
+ { '\u296e' }, { '\u22a5' }, { '\u21a5' }, { '\u21d1' },
+ { '\u21d5' }, { '\u2196' }, { '\u2197' }, { '\u03d2' },
+ { '\u03a5' }, { '\u016e' }, { '\ud835', '\udcb0' }, { '\u0168' },
+ { '\u00dc' }, { '\u00dc' }, { '\u22ab' }, { '\u2aeb' },
+ { '\u0412' }, { '\u22a9' }, { '\u2ae6' }, { '\u22c1' },
+ { '\u2016' }, { '\u2016' }, { '\u2223' }, { '\u007c' },
+ { '\u2758' }, { '\u2240' }, { '\u200a' }, { '\ud835', '\udd19' },
+ { '\ud835', '\udd4d' }, { '\ud835', '\udcb1' }, { '\u22aa' },
+ { '\u0174' }, { '\u22c0' }, { '\ud835', '\udd1a' },
+ { '\ud835', '\udd4e' }, { '\ud835', '\udcb2' },
+ { '\ud835', '\udd1b' }, { '\u039e' }, { '\ud835', '\udd4f' },
+ { '\ud835', '\udcb3' }, { '\u042f' }, { '\u0407' }, { '\u042e' },
+ { '\u00dd' }, { '\u00dd' }, { '\u0176' }, { '\u042b' },
+ { '\ud835', '\udd1c' }, { '\ud835', '\udd50' },
+ { '\ud835', '\udcb4' }, { '\u0178' }, { '\u0416' }, { '\u0179' },
+ { '\u017d' }, { '\u0417' }, { '\u017b' }, { '\u200b' },
+ { '\u0396' }, { '\u2128' }, { '\u2124' }, { '\ud835', '\udcb5' },
+ { '\u00e1' }, { '\u00e1' }, { '\u0103' }, { '\u223e' },
+ { '\u223e', '\u0333' }, { '\u223f' }, { '\u00e2' }, { '\u00e2' },
+ { '\u00b4' }, { '\u00b4' }, { '\u0430' }, { '\u00e6' },
+ { '\u00e6' }, { '\u2061' }, { '\ud835', '\udd1e' }, { '\u00e0' },
+ { '\u00e0' }, { '\u2135' }, { '\u2135' }, { '\u03b1' },
+ { '\u0101' }, { '\u2a3f' }, { '\u0026' }, { '\u0026' },
+ { '\u2227' }, { '\u2a55' }, { '\u2a5c' }, { '\u2a58' },
+ { '\u2a5a' }, { '\u2220' }, { '\u29a4' }, { '\u2220' },
+ { '\u2221' }, { '\u29a8' }, { '\u29a9' }, { '\u29aa' },
+ { '\u29ab' }, { '\u29ac' }, { '\u29ad' }, { '\u29ae' },
+ { '\u29af' }, { '\u221f' }, { '\u22be' }, { '\u299d' },
+ { '\u2222' }, { '\u00c5' }, { '\u237c' }, { '\u0105' },
+ { '\ud835', '\udd52' }, { '\u2248' }, { '\u2a70' }, { '\u2a6f' },
+ { '\u224a' }, { '\u224b' }, { '\'' }, { '\u2248' }, { '\u224a' },
+ { '\u00e5' }, { '\u00e5' }, { '\ud835', '\udcb6' }, { '\u002a' },
+ { '\u2248' }, { '\u224d' }, { '\u00e3' }, { '\u00e3' },
+ { '\u00e4' }, { '\u00e4' }, { '\u2233' }, { '\u2a11' },
+ { '\u2aed' }, { '\u224c' }, { '\u03f6' }, { '\u2035' },
+ { '\u223d' }, { '\u22cd' }, { '\u22bd' }, { '\u2305' },
+ { '\u2305' }, { '\u23b5' }, { '\u23b6' }, { '\u224c' },
+ { '\u0431' }, { '\u201e' }, { '\u2235' }, { '\u2235' },
+ { '\u29b0' }, { '\u03f6' }, { '\u212c' }, { '\u03b2' },
+ { '\u2136' }, { '\u226c' }, { '\ud835', '\udd1f' }, { '\u22c2' },
+ { '\u25ef' }, { '\u22c3' }, { '\u2a00' }, { '\u2a01' },
+ { '\u2a02' }, { '\u2a06' }, { '\u2605' }, { '\u25bd' },
+ { '\u25b3' }, { '\u2a04' }, { '\u22c1' }, { '\u22c0' },
+ { '\u290d' }, { '\u29eb' }, { '\u25aa' }, { '\u25b4' },
+ { '\u25be' }, { '\u25c2' }, { '\u25b8' }, { '\u2423' },
+ { '\u2592' }, { '\u2591' }, { '\u2593' }, { '\u2588' },
+ { '\u003d', '\u20e5' }, { '\u2261', '\u20e5' }, { '\u2310' },
+ { '\ud835', '\udd53' }, { '\u22a5' }, { '\u22a5' }, { '\u22c8' },
+ { '\u2557' }, { '\u2554' }, { '\u2556' }, { '\u2553' },
+ { '\u2550' }, { '\u2566' }, { '\u2569' }, { '\u2564' },
+ { '\u2567' }, { '\u255d' }, { '\u255a' }, { '\u255c' },
+ { '\u2559' }, { '\u2551' }, { '\u256c' }, { '\u2563' },
+ { '\u2560' }, { '\u256b' }, { '\u2562' }, { '\u255f' },
+ { '\u29c9' }, { '\u2555' }, { '\u2552' }, { '\u2510' },
+ { '\u250c' }, { '\u2500' }, { '\u2565' }, { '\u2568' },
+ { '\u252c' }, { '\u2534' }, { '\u229f' }, { '\u229e' },
+ { '\u22a0' }, { '\u255b' }, { '\u2558' }, { '\u2518' },
+ { '\u2514' }, { '\u2502' }, { '\u256a' }, { '\u2561' },
+ { '\u255e' }, { '\u253c' }, { '\u2524' }, { '\u251c' },
+ { '\u2035' }, { '\u02d8' }, { '\u00a6' }, { '\u00a6' },
+ { '\ud835', '\udcb7' }, { '\u204f' }, { '\u223d' }, { '\u22cd' },
+ { '\\' }, { '\u29c5' }, { '\u27c8' }, { '\u2022' }, { '\u2022' },
+ { '\u224e' }, { '\u2aae' }, { '\u224f' }, { '\u224f' },
+ { '\u0107' }, { '\u2229' }, { '\u2a44' }, { '\u2a49' },
+ { '\u2a4b' }, { '\u2a47' }, { '\u2a40' }, { '\u2229', '\ufe00' },
+ { '\u2041' }, { '\u02c7' }, { '\u2a4d' }, { '\u010d' },
+ { '\u00e7' }, { '\u00e7' }, { '\u0109' }, { '\u2a4c' },
+ { '\u2a50' }, { '\u010b' }, { '\u00b8' }, { '\u00b8' },
+ { '\u29b2' }, { '\u00a2' }, { '\u00a2' }, { '\u00b7' },
+ { '\ud835', '\udd20' }, { '\u0447' }, { '\u2713' }, { '\u2713' },
+ { '\u03c7' }, { '\u25cb' }, { '\u29c3' }, { '\u02c6' },
+ { '\u2257' }, { '\u21ba' }, { '\u21bb' }, { '\u00ae' },
+ { '\u24c8' }, { '\u229b' }, { '\u229a' }, { '\u229d' },
+ { '\u2257' }, { '\u2a10' }, { '\u2aef' }, { '\u29c2' },
+ { '\u2663' }, { '\u2663' }, { '\u003a' }, { '\u2254' },
+ { '\u2254' }, { '\u002c' }, { '\u0040' }, { '\u2201' },
+ { '\u2218' }, { '\u2201' }, { '\u2102' }, { '\u2245' },
+ { '\u2a6d' }, { '\u222e' }, { '\ud835', '\udd54' }, { '\u2210' },
+ { '\u00a9' }, { '\u00a9' }, { '\u2117' }, { '\u21b5' },
+ { '\u2717' }, { '\ud835', '\udcb8' }, { '\u2acf' }, { '\u2ad1' },
+ { '\u2ad0' }, { '\u2ad2' }, { '\u22ef' }, { '\u2938' },
+ { '\u2935' }, { '\u22de' }, { '\u22df' }, { '\u21b6' },
+ { '\u293d' }, { '\u222a' }, { '\u2a48' }, { '\u2a46' },
+ { '\u2a4a' }, { '\u228d' }, { '\u2a45' }, { '\u222a', '\ufe00' },
+ { '\u21b7' }, { '\u293c' }, { '\u22de' }, { '\u22df' },
+ { '\u22ce' }, { '\u22cf' }, { '\u00a4' }, { '\u00a4' },
+ { '\u21b6' }, { '\u21b7' }, { '\u22ce' }, { '\u22cf' },
+ { '\u2232' }, { '\u2231' }, { '\u232d' }, { '\u21d3' },
+ { '\u2965' }, { '\u2020' }, { '\u2138' }, { '\u2193' },
+ { '\u2010' }, { '\u22a3' }, { '\u290f' }, { '\u02dd' },
+ { '\u010f' }, { '\u0434' }, { '\u2146' }, { '\u2021' },
+ { '\u21ca' }, { '\u2a77' }, { '\u00b0' }, { '\u00b0' },
+ { '\u03b4' }, { '\u29b1' }, { '\u297f' }, { '\ud835', '\udd21' },
+ { '\u21c3' }, { '\u21c2' }, { '\u22c4' }, { '\u22c4' },
+ { '\u2666' }, { '\u2666' }, { '\u00a8' }, { '\u03dd' },
+ { '\u22f2' }, { '\u00f7' }, { '\u00f7' }, { '\u00f7' },
+ { '\u22c7' }, { '\u22c7' }, { '\u0452' }, { '\u231e' },
+ { '\u230d' }, { '\u0024' }, { '\ud835', '\udd55' }, { '\u02d9' },
+ { '\u2250' }, { '\u2251' }, { '\u2238' }, { '\u2214' },
+ { '\u22a1' }, { '\u2306' }, { '\u2193' }, { '\u21ca' },
+ { '\u21c3' }, { '\u21c2' }, { '\u2910' }, { '\u231f' },
+ { '\u230c' }, { '\ud835', '\udcb9' }, { '\u0455' }, { '\u29f6' },
+ { '\u0111' }, { '\u22f1' }, { '\u25bf' }, { '\u25be' },
+ { '\u21f5' }, { '\u296f' }, { '\u29a6' }, { '\u045f' },
+ { '\u27ff' }, { '\u2a77' }, { '\u2251' }, { '\u00e9' },
+ { '\u00e9' }, { '\u2a6e' }, { '\u011b' }, { '\u2256' },
+ { '\u00ea' }, { '\u00ea' }, { '\u2255' }, { '\u044d' },
+ { '\u0117' }, { '\u2147' }, { '\u2252' }, { '\ud835', '\udd22' },
+ { '\u2a9a' }, { '\u00e8' }, { '\u00e8' }, { '\u2a96' },
+ { '\u2a98' }, { '\u2a99' }, { '\u23e7' }, { '\u2113' },
+ { '\u2a95' }, { '\u2a97' }, { '\u0113' }, { '\u2205' },
+ { '\u2205' }, { '\u2205' }, { '\u2004' }, { '\u2005' },
+ { '\u2003' }, { '\u014b' }, { '\u2002' }, { '\u0119' },
+ { '\ud835', '\udd56' }, { '\u22d5' }, { '\u29e3' }, { '\u2a71' },
+ { '\u03b5' }, { '\u03b5' }, { '\u03f5' }, { '\u2256' },
+ { '\u2255' }, { '\u2242' }, { '\u2a96' }, { '\u2a95' },
+ { '\u003d' }, { '\u225f' }, { '\u2261' }, { '\u2a78' },
+ { '\u29e5' }, { '\u2253' }, { '\u2971' }, { '\u212f' },
+ { '\u2250' }, { '\u2242' }, { '\u03b7' }, { '\u00f0' },
+ { '\u00f0' }, { '\u00eb' }, { '\u00eb' }, { '\u20ac' },
+ { '\u0021' }, { '\u2203' }, { '\u2130' }, { '\u2147' },
+ { '\u2252' }, { '\u0444' }, { '\u2640' }, { '\ufb03' },
+ { '\ufb00' }, { '\ufb04' }, { '\ud835', '\udd23' }, { '\ufb01' },
+ { '\u0066', '\u006a' }, { '\u266d' }, { '\ufb02' }, { '\u25b1' },
+ { '\u0192' }, { '\ud835', '\udd57' }, { '\u2200' }, { '\u22d4' },
+ { '\u2ad9' }, { '\u2a0d' }, { '\u00bd' }, { '\u00bd' },
+ { '\u2153' }, { '\u00bc' }, { '\u00bc' }, { '\u2155' },
+ { '\u2159' }, { '\u215b' }, { '\u2154' }, { '\u2156' },
+ { '\u00be' }, { '\u00be' }, { '\u2157' }, { '\u215c' },
+ { '\u2158' }, { '\u215a' }, { '\u215d' }, { '\u215e' },
+ { '\u2044' }, { '\u2322' }, { '\ud835', '\udcbb' }, { '\u2267' },
+ { '\u2a8c' }, { '\u01f5' }, { '\u03b3' }, { '\u03dd' },
+ { '\u2a86' }, { '\u011f' }, { '\u011d' }, { '\u0433' },
+ { '\u0121' }, { '\u2265' }, { '\u22db' }, { '\u2265' },
+ { '\u2267' }, { '\u2a7e' }, { '\u2a7e' }, { '\u2aa9' },
+ { '\u2a80' }, { '\u2a82' }, { '\u2a84' }, { '\u22db', '\ufe00' },
+ { '\u2a94' }, { '\ud835', '\udd24' }, { '\u226b' }, { '\u22d9' },
+ { '\u2137' }, { '\u0453' }, { '\u2277' }, { '\u2a92' },
+ { '\u2aa5' }, { '\u2aa4' }, { '\u2269' }, { '\u2a8a' },
+ { '\u2a8a' }, { '\u2a88' }, { '\u2a88' }, { '\u2269' },
+ { '\u22e7' }, { '\ud835', '\udd58' }, { '\u0060' }, { '\u210a' },
+ { '\u2273' }, { '\u2a8e' }, { '\u2a90' }, { '\u003e' },
+ { '\u003e' }, { '\u2aa7' }, { '\u2a7a' }, { '\u22d7' },
+ { '\u2995' }, { '\u2a7c' }, { '\u2a86' }, { '\u2978' },
+ { '\u22d7' }, { '\u22db' }, { '\u2a8c' }, { '\u2277' },
+ { '\u2273' }, { '\u2269', '\ufe00' }, { '\u2269', '\ufe00' },
+ { '\u21d4' }, { '\u200a' }, { '\u00bd' }, { '\u210b' },
+ { '\u044a' }, { '\u2194' }, { '\u2948' }, { '\u21ad' },
+ { '\u210f' }, { '\u0125' }, { '\u2665' }, { '\u2665' },
+ { '\u2026' }, { '\u22b9' }, { '\ud835', '\udd25' }, { '\u2925' },
+ { '\u2926' }, { '\u21ff' }, { '\u223b' }, { '\u21a9' },
+ { '\u21aa' }, { '\ud835', '\udd59' }, { '\u2015' },
+ { '\ud835', '\udcbd' }, { '\u210f' }, { '\u0127' }, { '\u2043' },
+ { '\u2010' }, { '\u00ed' }, { '\u00ed' }, { '\u2063' },
+ { '\u00ee' }, { '\u00ee' }, { '\u0438' }, { '\u0435' },
+ { '\u00a1' }, { '\u00a1' }, { '\u21d4' }, { '\ud835', '\udd26' },
+ { '\u00ec' }, { '\u00ec' }, { '\u2148' }, { '\u2a0c' },
+ { '\u222d' }, { '\u29dc' }, { '\u2129' }, { '\u0133' },
+ { '\u012b' }, { '\u2111' }, { '\u2110' }, { '\u2111' },
+ { '\u0131' }, { '\u22b7' }, { '\u01b5' }, { '\u2208' },
+ { '\u2105' }, { '\u221e' }, { '\u29dd' }, { '\u0131' },
+ { '\u222b' }, { '\u22ba' }, { '\u2124' }, { '\u22ba' },
+ { '\u2a17' }, { '\u2a3c' }, { '\u0451' }, { '\u012f' },
+ { '\ud835', '\udd5a' }, { '\u03b9' }, { '\u2a3c' }, { '\u00bf' },
+ { '\u00bf' }, { '\ud835', '\udcbe' }, { '\u2208' }, { '\u22f9' },
+ { '\u22f5' }, { '\u22f4' }, { '\u22f3' }, { '\u2208' },
+ { '\u2062' }, { '\u0129' }, { '\u0456' }, { '\u00ef' },
+ { '\u00ef' }, { '\u0135' }, { '\u0439' }, { '\ud835', '\udd27' },
+ { '\u0237' }, { '\ud835', '\udd5b' }, { '\ud835', '\udcbf' },
+ { '\u0458' }, { '\u0454' }, { '\u03ba' }, { '\u03f0' },
+ { '\u0137' }, { '\u043a' }, { '\ud835', '\udd28' }, { '\u0138' },
+ { '\u0445' }, { '\u045c' }, { '\ud835', '\udd5c' },
+ { '\ud835', '\udcc0' }, { '\u21da' }, { '\u21d0' }, { '\u291b' },
+ { '\u290e' }, { '\u2266' }, { '\u2a8b' }, { '\u2962' },
+ { '\u013a' }, { '\u29b4' }, { '\u2112' }, { '\u03bb' },
+ { '\u27e8' }, { '\u2991' }, { '\u27e8' }, { '\u2a85' },
+ { '\u00ab' }, { '\u00ab' }, { '\u2190' }, { '\u21e4' },
+ { '\u291f' }, { '\u291d' }, { '\u21a9' }, { '\u21ab' },
+ { '\u2939' }, { '\u2973' }, { '\u21a2' }, { '\u2aab' },
+ { '\u2919' }, { '\u2aad' }, { '\u2aad', '\ufe00' }, { '\u290c' },
+ { '\u2772' }, { '\u007b' }, { '\u005b' }, { '\u298b' },
+ { '\u298f' }, { '\u298d' }, { '\u013e' }, { '\u013c' },
+ { '\u2308' }, { '\u007b' }, { '\u043b' }, { '\u2936' },
+ { '\u201c' }, { '\u201e' }, { '\u2967' }, { '\u294b' },
+ { '\u21b2' }, { '\u2264' }, { '\u2190' }, { '\u21a2' },
+ { '\u21bd' }, { '\u21bc' }, { '\u21c7' }, { '\u2194' },
+ { '\u21c6' }, { '\u21cb' }, { '\u21ad' }, { '\u22cb' },
+ { '\u22da' }, { '\u2264' }, { '\u2266' }, { '\u2a7d' },
+ { '\u2a7d' }, { '\u2aa8' }, { '\u2a7f' }, { '\u2a81' },
+ { '\u2a83' }, { '\u22da', '\ufe00' }, { '\u2a93' }, { '\u2a85' },
+ { '\u22d6' }, { '\u22da' }, { '\u2a8b' }, { '\u2276' },
+ { '\u2272' }, { '\u297c' }, { '\u230a' }, { '\ud835', '\udd29' },
+ { '\u2276' }, { '\u2a91' }, { '\u21bd' }, { '\u21bc' },
+ { '\u296a' }, { '\u2584' }, { '\u0459' }, { '\u226a' },
+ { '\u21c7' }, { '\u231e' }, { '\u296b' }, { '\u25fa' },
+ { '\u0140' }, { '\u23b0' }, { '\u23b0' }, { '\u2268' },
+ { '\u2a89' }, { '\u2a89' }, { '\u2a87' }, { '\u2a87' },
+ { '\u2268' }, { '\u22e6' }, { '\u27ec' }, { '\u21fd' },
+ { '\u27e6' }, { '\u27f5' }, { '\u27f7' }, { '\u27fc' },
+ { '\u27f6' }, { '\u21ab' }, { '\u21ac' }, { '\u2985' },
+ { '\ud835', '\udd5d' }, { '\u2a2d' }, { '\u2a34' }, { '\u2217' },
+ { '\u005f' }, { '\u25ca' }, { '\u25ca' }, { '\u29eb' },
+ { '\u0028' }, { '\u2993' }, { '\u21c6' }, { '\u231f' },
+ { '\u21cb' }, { '\u296d' }, { '\u200e' }, { '\u22bf' },
+ { '\u2039' }, { '\ud835', '\udcc1' }, { '\u21b0' }, { '\u2272' },
+ { '\u2a8d' }, { '\u2a8f' }, { '\u005b' }, { '\u2018' },
+ { '\u201a' }, { '\u0142' }, { '\u003c' }, { '\u003c' },
+ { '\u2aa6' }, { '\u2a79' }, { '\u22d6' }, { '\u22cb' },
+ { '\u22c9' }, { '\u2976' }, { '\u2a7b' }, { '\u2996' },
+ { '\u25c3' }, { '\u22b4' }, { '\u25c2' }, { '\u294a' },
+ { '\u2966' }, { '\u2268', '\ufe00' }, { '\u2268', '\ufe00' },
+ { '\u223a' }, { '\u00af' }, { '\u00af' }, { '\u2642' },
+ { '\u2720' }, { '\u2720' }, { '\u21a6' }, { '\u21a6' },
+ { '\u21a7' }, { '\u21a4' }, { '\u21a5' }, { '\u25ae' },
+ { '\u2a29' }, { '\u043c' }, { '\u2014' }, { '\u2221' },
+ { '\ud835', '\udd2a' }, { '\u2127' }, { '\u00b5' }, { '\u00b5' },
+ { '\u2223' }, { '\u002a' }, { '\u2af0' }, { '\u00b7' },
+ { '\u00b7' }, { '\u2212' }, { '\u229f' }, { '\u2238' },
+ { '\u2a2a' }, { '\u2adb' }, { '\u2026' }, { '\u2213' },
+ { '\u22a7' }, { '\ud835', '\udd5e' }, { '\u2213' },
+ { '\ud835', '\udcc2' }, { '\u223e' }, { '\u03bc' }, { '\u22b8' },
+ { '\u22b8' }, { '\u22d9', '\u0338' }, { '\u226b', '\u20d2' },
+ { '\u226b', '\u0338' }, { '\u21cd' }, { '\u21ce' },
+ { '\u22d8', '\u0338' }, { '\u226a', '\u20d2' },
+ { '\u226a', '\u0338' }, { '\u21cf' }, { '\u22af' }, { '\u22ae' },
+ { '\u2207' }, { '\u0144' }, { '\u2220', '\u20d2' }, { '\u2249' },
+ { '\u2a70', '\u0338' }, { '\u224b', '\u0338' }, { '\u0149' },
+ { '\u2249' }, { '\u266e' }, { '\u266e' }, { '\u2115' },
+ { '\u00a0' }, { '\u00a0' }, { '\u224e', '\u0338' },
+ { '\u224f', '\u0338' }, { '\u2a43' }, { '\u0148' }, { '\u0146' },
+ { '\u2247' }, { '\u2a6d', '\u0338' }, { '\u2a42' }, { '\u043d' },
+ { '\u2013' }, { '\u2260' }, { '\u21d7' }, { '\u2924' },
+ { '\u2197' }, { '\u2197' }, { '\u2250', '\u0338' }, { '\u2262' },
+ { '\u2928' }, { '\u2242', '\u0338' }, { '\u2204' }, { '\u2204' },
+ { '\ud835', '\udd2b' }, { '\u2267', '\u0338' }, { '\u2271' },
+ { '\u2271' }, { '\u2267', '\u0338' }, { '\u2a7e', '\u0338' },
+ { '\u2a7e', '\u0338' }, { '\u2275' }, { '\u226f' }, { '\u226f' },
+ { '\u21ce' }, { '\u21ae' }, { '\u2af2' }, { '\u220b' },
+ { '\u22fc' }, { '\u22fa' }, { '\u220b' }, { '\u045a' },
+ { '\u21cd' }, { '\u2266', '\u0338' }, { '\u219a' }, { '\u2025' },
+ { '\u2270' }, { '\u219a' }, { '\u21ae' }, { '\u2270' },
+ { '\u2266', '\u0338' }, { '\u2a7d', '\u0338' },
+ { '\u2a7d', '\u0338' }, { '\u226e' }, { '\u2274' }, { '\u226e' },
+ { '\u22ea' }, { '\u22ec' }, { '\u2224' }, { '\ud835', '\udd5f' },
+ { '\u00ac' }, { '\u00ac' }, { '\u2209' }, { '\u22f9', '\u0338' },
+ { '\u22f5', '\u0338' }, { '\u2209' }, { '\u22f7' }, { '\u22f6' },
+ { '\u220c' }, { '\u220c' }, { '\u22fe' }, { '\u22fd' },
+ { '\u2226' }, { '\u2226' }, { '\u2afd', '\u20e5' },
+ { '\u2202', '\u0338' }, { '\u2a14' }, { '\u2280' }, { '\u22e0' },
+ { '\u2aaf', '\u0338' }, { '\u2280' }, { '\u2aaf', '\u0338' },
+ { '\u21cf' }, { '\u219b' }, { '\u2933', '\u0338' },
+ { '\u219d', '\u0338' }, { '\u219b' }, { '\u22eb' }, { '\u22ed' },
+ { '\u2281' }, { '\u22e1' }, { '\u2ab0', '\u0338' },
+ { '\ud835', '\udcc3' }, { '\u2224' }, { '\u2226' }, { '\u2241' },
+ { '\u2244' }, { '\u2244' }, { '\u2224' }, { '\u2226' },
+ { '\u22e2' }, { '\u22e3' }, { '\u2284' }, { '\u2ac5', '\u0338' },
+ { '\u2288' }, { '\u2282', '\u20d2' }, { '\u2288' },
+ { '\u2ac5', '\u0338' }, { '\u2281' }, { '\u2ab0', '\u0338' },
+ { '\u2285' }, { '\u2ac6', '\u0338' }, { '\u2289' },
+ { '\u2283', '\u20d2' }, { '\u2289' }, { '\u2ac6', '\u0338' },
+ { '\u2279' }, { '\u00f1' }, { '\u00f1' }, { '\u2278' },
+ { '\u22ea' }, { '\u22ec' }, { '\u22eb' }, { '\u22ed' },
+ { '\u03bd' }, { '\u0023' }, { '\u2116' }, { '\u2007' },
+ { '\u22ad' }, { '\u2904' }, { '\u224d', '\u20d2' }, { '\u22ac' },
+ { '\u2265', '\u20d2' }, { '\u003e', '\u20d2' }, { '\u29de' },
+ { '\u2902' }, { '\u2264', '\u20d2' }, { '\u003c', '\u20d2' },
+ { '\u22b4', '\u20d2' }, { '\u2903' }, { '\u22b5', '\u20d2' },
+ { '\u223c', '\u20d2' }, { '\u21d6' }, { '\u2923' }, { '\u2196' },
+ { '\u2196' }, { '\u2927' }, { '\u24c8' }, { '\u00f3' },
+ { '\u00f3' }, { '\u229b' }, { '\u229a' }, { '\u00f4' },
+ { '\u00f4' }, { '\u043e' }, { '\u229d' }, { '\u0151' },
+ { '\u2a38' }, { '\u2299' }, { '\u29bc' }, { '\u0153' },
+ { '\u29bf' }, { '\ud835', '\udd2c' }, { '\u02db' }, { '\u00f2' },
+ { '\u00f2' }, { '\u29c1' }, { '\u29b5' }, { '\u03a9' },
+ { '\u222e' }, { '\u21ba' }, { '\u29be' }, { '\u29bb' },
+ { '\u203e' }, { '\u29c0' }, { '\u014d' }, { '\u03c9' },
+ { '\u03bf' }, { '\u29b6' }, { '\u2296' }, { '\ud835', '\udd60' },
+ { '\u29b7' }, { '\u29b9' }, { '\u2295' }, { '\u2228' },
+ { '\u21bb' }, { '\u2a5d' }, { '\u2134' }, { '\u2134' },
+ { '\u00aa' }, { '\u00aa' }, { '\u00ba' }, { '\u00ba' },
+ { '\u22b6' }, { '\u2a56' }, { '\u2a57' }, { '\u2a5b' },
+ { '\u2134' }, { '\u00f8' }, { '\u00f8' }, { '\u2298' },
+ { '\u00f5' }, { '\u00f5' }, { '\u2297' }, { '\u2a36' },
+ { '\u00f6' }, { '\u00f6' }, { '\u233d' }, { '\u2225' },
+ { '\u00b6' }, { '\u00b6' }, { '\u2225' }, { '\u2af3' },
+ { '\u2afd' }, { '\u2202' }, { '\u043f' }, { '\u0025' },
+ { '\u002e' }, { '\u2030' }, { '\u22a5' }, { '\u2031' },
+ { '\ud835', '\udd2d' }, { '\u03c6' }, { '\u03d5' }, { '\u2133' },
+ { '\u260e' }, { '\u03c0' }, { '\u22d4' }, { '\u03d6' },
+ { '\u210f' }, { '\u210e' }, { '\u210f' }, { '\u002b' },
+ { '\u2a23' }, { '\u229e' }, { '\u2a22' }, { '\u2214' },
+ { '\u2a25' }, { '\u2a72' }, { '\u00b1' }, { '\u00b1' },
+ { '\u2a26' }, { '\u2a27' }, { '\u00b1' }, { '\u2a15' },
+ { '\ud835', '\udd61' }, { '\u00a3' }, { '\u00a3' }, { '\u227a' },
+ { '\u2ab3' }, { '\u2ab7' }, { '\u227c' }, { '\u2aaf' },
+ { '\u227a' }, { '\u2ab7' }, { '\u227c' }, { '\u2aaf' },
+ { '\u2ab9' }, { '\u2ab5' }, { '\u22e8' }, { '\u227e' },
+ { '\u2032' }, { '\u2119' }, { '\u2ab5' }, { '\u2ab9' },
+ { '\u22e8' }, { '\u220f' }, { '\u232e' }, { '\u2312' },
+ { '\u2313' }, { '\u221d' }, { '\u221d' }, { '\u227e' },
+ { '\u22b0' }, { '\ud835', '\udcc5' }, { '\u03c8' }, { '\u2008' },
+ { '\ud835', '\udd2e' }, { '\u2a0c' }, { '\ud835', '\udd62' },
+ { '\u2057' }, { '\ud835', '\udcc6' }, { '\u210d' }, { '\u2a16' },
+ { '\u003f' }, { '\u225f' }, { '\u0022' }, { '\u0022' },
+ { '\u21db' }, { '\u21d2' }, { '\u291c' }, { '\u290f' },
+ { '\u2964' }, { '\u223d', '\u0331' }, { '\u0155' }, { '\u221a' },
+ { '\u29b3' }, { '\u27e9' }, { '\u2992' }, { '\u29a5' },
+ { '\u27e9' }, { '\u00bb' }, { '\u00bb' }, { '\u2192' },
+ { '\u2975' }, { '\u21e5' }, { '\u2920' }, { '\u2933' },
+ { '\u291e' }, { '\u21aa' }, { '\u21ac' }, { '\u2945' },
+ { '\u2974' }, { '\u21a3' }, { '\u219d' }, { '\u291a' },
+ { '\u2236' }, { '\u211a' }, { '\u290d' }, { '\u2773' },
+ { '\u007d' }, { '\u005d' }, { '\u298c' }, { '\u298e' },
+ { '\u2990' }, { '\u0159' }, { '\u0157' }, { '\u2309' },
+ { '\u007d' }, { '\u0440' }, { '\u2937' }, { '\u2969' },
+ { '\u201d' }, { '\u201d' }, { '\u21b3' }, { '\u211c' },
+ { '\u211b' }, { '\u211c' }, { '\u211d' }, { '\u25ad' },
+ { '\u00ae' }, { '\u00ae' }, { '\u297d' }, { '\u230b' },
+ { '\ud835', '\udd2f' }, { '\u21c1' }, { '\u21c0' }, { '\u296c' },
+ { '\u03c1' }, { '\u03f1' }, { '\u2192' }, { '\u21a3' },
+ { '\u21c1' }, { '\u21c0' }, { '\u21c4' }, { '\u21cc' },
+ { '\u21c9' }, { '\u219d' }, { '\u22cc' }, { '\u02da' },
+ { '\u2253' }, { '\u21c4' }, { '\u21cc' }, { '\u200f' },
+ { '\u23b1' }, { '\u23b1' }, { '\u2aee' }, { '\u27ed' },
+ { '\u21fe' }, { '\u27e7' }, { '\u2986' }, { '\ud835', '\udd63' },
+ { '\u2a2e' }, { '\u2a35' }, { '\u0029' }, { '\u2994' },
+ { '\u2a12' }, { '\u21c9' }, { '\u203a' }, { '\ud835', '\udcc7' },
+ { '\u21b1' }, { '\u005d' }, { '\u2019' }, { '\u2019' },
+ { '\u22cc' }, { '\u22ca' }, { '\u25b9' }, { '\u22b5' },
+ { '\u25b8' }, { '\u29ce' }, { '\u2968' }, { '\u211e' },
+ { '\u015b' }, { '\u201a' }, { '\u227b' }, { '\u2ab4' },
+ { '\u2ab8' }, { '\u0161' }, { '\u227d' }, { '\u2ab0' },
+ { '\u015f' }, { '\u015d' }, { '\u2ab6' }, { '\u2aba' },
+ { '\u22e9' }, { '\u2a13' }, { '\u227f' }, { '\u0441' },
+ { '\u22c5' }, { '\u22a1' }, { '\u2a66' }, { '\u21d8' },
+ { '\u2925' }, { '\u2198' }, { '\u2198' }, { '\u00a7' },
+ { '\u00a7' }, { '\u003b' }, { '\u2929' }, { '\u2216' },
+ { '\u2216' }, { '\u2736' }, { '\ud835', '\udd30' }, { '\u2322' },
+ { '\u266f' }, { '\u0449' }, { '\u0448' }, { '\u2223' },
+ { '\u2225' }, { '\u00ad' }, { '\u00ad' }, { '\u03c3' },
+ { '\u03c2' }, { '\u03c2' }, { '\u223c' }, { '\u2a6a' },
+ { '\u2243' }, { '\u2243' }, { '\u2a9e' }, { '\u2aa0' },
+ { '\u2a9d' }, { '\u2a9f' }, { '\u2246' }, { '\u2a24' },
+ { '\u2972' }, { '\u2190' }, { '\u2216' }, { '\u2a33' },
+ { '\u29e4' }, { '\u2223' }, { '\u2323' }, { '\u2aaa' },
+ { '\u2aac' }, { '\u2aac', '\ufe00' }, { '\u044c' }, { '\u002f' },
+ { '\u29c4' }, { '\u233f' }, { '\ud835', '\udd64' }, { '\u2660' },
+ { '\u2660' }, { '\u2225' }, { '\u2293' }, { '\u2293', '\ufe00' },
+ { '\u2294' }, { '\u2294', '\ufe00' }, { '\u228f' }, { '\u2291' },
+ { '\u228f' }, { '\u2291' }, { '\u2290' }, { '\u2292' },
+ { '\u2290' }, { '\u2292' }, { '\u25a1' }, { '\u25a1' },
+ { '\u25aa' }, { '\u25aa' }, { '\u2192' }, { '\ud835', '\udcc8' },
+ { '\u2216' }, { '\u2323' }, { '\u22c6' }, { '\u2606' },
+ { '\u2605' }, { '\u03f5' }, { '\u03d5' }, { '\u00af' },
+ { '\u2282' }, { '\u2ac5' }, { '\u2abd' }, { '\u2286' },
+ { '\u2ac3' }, { '\u2ac1' }, { '\u2acb' }, { '\u228a' },
+ { '\u2abf' }, { '\u2979' }, { '\u2282' }, { '\u2286' },
+ { '\u2ac5' }, { '\u228a' }, { '\u2acb' }, { '\u2ac7' },
+ { '\u2ad5' }, { '\u2ad3' }, { '\u227b' }, { '\u2ab8' },
+ { '\u227d' }, { '\u2ab0' }, { '\u2aba' }, { '\u2ab6' },
+ { '\u22e9' }, { '\u227f' }, { '\u2211' }, { '\u266a' },
+ { '\u00b9' }, { '\u00b9' }, { '\u00b2' }, { '\u00b2' },
+ { '\u00b3' }, { '\u00b3' }, { '\u2283' }, { '\u2ac6' },
+ { '\u2abe' }, { '\u2ad8' }, { '\u2287' }, { '\u2ac4' },
+ { '\u27c9' }, { '\u2ad7' }, { '\u297b' }, { '\u2ac2' },
+ { '\u2acc' }, { '\u228b' }, { '\u2ac0' }, { '\u2283' },
+ { '\u2287' }, { '\u2ac6' }, { '\u228b' }, { '\u2acc' },
+ { '\u2ac8' }, { '\u2ad4' }, { '\u2ad6' }, { '\u21d9' },
+ { '\u2926' }, { '\u2199' }, { '\u2199' }, { '\u292a' },
+ { '\u00df' }, { '\u00df' }, { '\u2316' }, { '\u03c4' },
+ { '\u23b4' }, { '\u0165' }, { '\u0163' }, { '\u0442' },
+ { '\u20db' }, { '\u2315' }, { '\ud835', '\udd31' }, { '\u2234' },
+ { '\u2234' }, { '\u03b8' }, { '\u03d1' }, { '\u03d1' },
+ { '\u2248' }, { '\u223c' }, { '\u2009' }, { '\u2248' },
+ { '\u223c' }, { '\u00fe' }, { '\u00fe' }, { '\u02dc' },
+ { '\u00d7' }, { '\u00d7' }, { '\u22a0' }, { '\u2a31' },
+ { '\u2a30' }, { '\u222d' }, { '\u2928' }, { '\u22a4' },
+ { '\u2336' }, { '\u2af1' }, { '\ud835', '\udd65' }, { '\u2ada' },
+ { '\u2929' }, { '\u2034' }, { '\u2122' }, { '\u25b5' },
+ { '\u25bf' }, { '\u25c3' }, { '\u22b4' }, { '\u225c' },
+ { '\u25b9' }, { '\u22b5' }, { '\u25ec' }, { '\u225c' },
+ { '\u2a3a' }, { '\u2a39' }, { '\u29cd' }, { '\u2a3b' },
+ { '\u23e2' }, { '\ud835', '\udcc9' }, { '\u0446' }, { '\u045b' },
+ { '\u0167' }, { '\u226c' }, { '\u219e' }, { '\u21a0' },
+ { '\u21d1' }, { '\u2963' }, { '\u00fa' }, { '\u00fa' },
+ { '\u2191' }, { '\u045e' }, { '\u016d' }, { '\u00fb' },
+ { '\u00fb' }, { '\u0443' }, { '\u21c5' }, { '\u0171' },
+ { '\u296e' }, { '\u297e' }, { '\ud835', '\udd32' }, { '\u00f9' },
+ { '\u00f9' }, { '\u21bf' }, { '\u21be' }, { '\u2580' },
+ { '\u231c' }, { '\u231c' }, { '\u230f' }, { '\u25f8' },
+ { '\u016b' }, { '\u00a8' }, { '\u00a8' }, { '\u0173' },
+ { '\ud835', '\udd66' }, { '\u2191' }, { '\u2195' }, { '\u21bf' },
+ { '\u21be' }, { '\u228e' }, { '\u03c5' }, { '\u03d2' },
+ { '\u03c5' }, { '\u21c8' }, { '\u231d' }, { '\u231d' },
+ { '\u230e' }, { '\u016f' }, { '\u25f9' }, { '\ud835', '\udcca' },
+ { '\u22f0' }, { '\u0169' }, { '\u25b5' }, { '\u25b4' },
+ { '\u21c8' }, { '\u00fc' }, { '\u00fc' }, { '\u29a7' },
+ { '\u21d5' }, { '\u2ae8' }, { '\u2ae9' }, { '\u22a8' },
+ { '\u299c' }, { '\u03f5' }, { '\u03f0' }, { '\u2205' },
+ { '\u03d5' }, { '\u03d6' }, { '\u221d' }, { '\u2195' },
+ { '\u03f1' }, { '\u03c2' }, { '\u228a', '\ufe00' },
+ { '\u2acb', '\ufe00' }, { '\u228b', '\ufe00' },
+ { '\u2acc', '\ufe00' }, { '\u03d1' }, { '\u22b2' }, { '\u22b3' },
+ { '\u0432' }, { '\u22a2' }, { '\u2228' }, { '\u22bb' },
+ { '\u225a' }, { '\u22ee' }, { '\u007c' }, { '\u007c' },
+ { '\ud835', '\udd33' }, { '\u22b2' }, { '\u2282', '\u20d2' },
+ { '\u2283', '\u20d2' }, { '\ud835', '\udd67' }, { '\u221d' },
+ { '\u22b3' }, { '\ud835', '\udccb' }, { '\u2acb', '\ufe00' },
+ { '\u228a', '\ufe00' }, { '\u2acc', '\ufe00' },
+ { '\u228b', '\ufe00' }, { '\u299a' }, { '\u0175' }, { '\u2a5f' },
+ { '\u2227' }, { '\u2259' }, { '\u2118' }, { '\ud835', '\udd34' },
+ { '\ud835', '\udd68' }, { '\u2118' }, { '\u2240' }, { '\u2240' },
+ { '\ud835', '\udccc' }, { '\u22c2' }, { '\u25ef' }, { '\u22c3' },
+ { '\u25bd' }, { '\ud835', '\udd35' }, { '\u27fa' }, { '\u27f7' },
+ { '\u03be' }, { '\u27f8' }, { '\u27f5' }, { '\u27fc' },
+ { '\u22fb' }, { '\u2a00' }, { '\ud835', '\udd69' }, { '\u2a01' },
+ { '\u2a02' }, { '\u27f9' }, { '\u27f6' }, { '\ud835', '\udccd' },
+ { '\u2a06' }, { '\u2a04' }, { '\u25b3' }, { '\u22c1' },
+ { '\u22c0' }, { '\u00fd' }, { '\u00fd' }, { '\u044f' },
+ { '\u0177' }, { '\u044b' }, { '\u00a5' }, { '\u00a5' },
+ { '\ud835', '\udd36' }, { '\u0457' }, { '\ud835', '\udd6a' },
+ { '\ud835', '\udcce' }, { '\u044e' }, { '\u00ff' }, { '\u00ff' },
+ { '\u017a' }, { '\u017e' }, { '\u0437' }, { '\u017c' },
+ { '\u2128' }, { '\u03b6' }, { '\ud835', '\udd37' }, { '\u0436' },
+ { '\u21dd' }, { '\ud835', '\udd6b' }, { '\ud835', '\udccf' },
+ { '\u200d' }, { '\u200c' }, };
+
+ final static char[][] WINDOWS_1252 = { { '\u20AC' }, { '\u0081' },
+ { '\u201A' }, { '\u0192' }, { '\u201E' }, { '\u2026' },
+ { '\u2020' }, { '\u2021' }, { '\u02C6' }, { '\u2030' },
+ { '\u0160' }, { '\u2039' }, { '\u0152' }, { '\u008D' },
+ { '\u017D' }, { '\u008F' }, { '\u0090' }, { '\u2018' },
+ { '\u2019' }, { '\u201C' }, { '\u201D' }, { '\u2022' },
+ { '\u2013' }, { '\u2014' }, { '\u02DC' }, { '\u2122' },
+ { '\u0161' }, { '\u203A' }, { '\u0153' }, { '\u009D' },
+ { '\u017E' }, { '\u0178' } };
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NamedCharactersAccel.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NamedCharactersAccel.java
new file mode 100644
index 000000000..311f8f77f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/NamedCharactersAccel.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2004-2010 Apple Computer, Inc., Mozilla Foundation, and Opera
+ * Software ASA.
+ *
+ * You are granted a license to use, reproduce and create derivative works of
+ * this document.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import nu.validator.htmlparser.annotation.NoLength;
+
+/**
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class NamedCharactersAccel {
+
+ static final @NoLength int[][] HILO_ACCEL = {
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ { 0, 0, 0, 0, 0, 0, 0, 12386493, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 40174181, 0, 0, 0, 0, 60162966, 0, 0, 0,
+ 75367550, 0, 0, 0, 82183396, 0, 0, 0, 0, 0, 115148507, 0,
+ 0, 135989275, 139397199, 0, 0, 0, 0, },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28770743, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 82248935, 0, 0, 0, 0, 0, 115214046, 0, 0, 0, 139528272, 0,
+ 0, 0, 0, },
+ null,
+ { 0, 0, 0, 4980811, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 38470219, 0, 0, 0, 0, 0, 0, 0, 0, 64553944, 0, 0, 0, 0,
+ 0, 0, 0, 92145022, 0, 0, 0, 0, 0, 0, 0, 0, 139593810, 0, 0,
+ 0, 0, },
+ { 65536, 0, 0, 0, 0, 0, 0, 0, 13172937, 0, 0, 0, 0, 0, 25297282, 0,
+ 0, 28901816, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 71500866, 0, 0, 0, 0, 82380008, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, },
+ null,
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 94897574, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 2555943, 0, 0, 0, 0, 0, 0, 0, 15532269, 0, 0, 0, 0, 0, 0,
+ 0, 31785444, 34406924, 0, 0, 0, 0, 0, 40895088, 0, 0, 0,
+ 60228503, 0, 0, 0, 0, 0, 0, 0, 82445546, 0, 0, 0, 0, 0,
+ 115279583, 0, 0, 136054812, 0, 0, 0, 0, 0, },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 40239718, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 0, 5046349, 0, 0, 10944679, 0, 13238474, 0, 15597806,
+ 16056565, 0, 20578618, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, },
+ null,
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 95225257, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 196610, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 0, 0, 8454273, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 46072511, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 2687016, 0, 0, 0, 0, 0, 13304011, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 31850982, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ null,
+ null,
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 34472462, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 95290798, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 0, 5111886, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 34603535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 105776718, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 0, 0, 8585346, 0, 11075752, 0, 0, 0, 0, 16187638, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28508594, 0, 0,
+ 0, 0, 0, 0, 0, 40305255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 95421871, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ null,
+ null,
+ null,
+ { 0, 0, 0, 5177423, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ { 327684, 1900571, 2949162, 5374032, 8716420, 0, 11206826,
+ 12517566, 13435084, 0, 15663343, 16515320, 19988785,
+ 20644155, 25428355, 27197855, 0, 29163962, 31916519,
+ 34734609, 36045347, 0, 0, 0, 40436328, 40960625, 41615994,
+ 46596800, 54264627, 60556184, 64750554, 68879387, 71763012,
+ 75826303, 77268122, 0, 81462490, 83952875, 92865919,
+ 96142769, 105973327, 110167691, 0, 116917984, 121833283,
+ 132253665, 136251421, 140707923, 0, 0, 144574620,
+ 145361066, },
+ { 393222, 0, 0, 0, 0, 0, 11272364, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 36176423, 38535756, 0, 0, 0, 0, 41681532, 46727880,
+ 0, 60687261, 0, 0, 71828552, 75891846, 0, 0, 0, 84411650,
+ 0, 96404924, 0, 0, 0, 117376761, 121898820, 132319203,
+ 136382496, 0, 0, 0, 0, 0, },
+ { 589831, 1966110, 3276846, 5505107, 8978566, 10420383, 11468973,
+ 12583104, 13631694, 15139046, 15794416, 16711933, 20054322,
+ 20840764, 25624965, 27263392, 0, 29360574, 32244200,
+ 34931219, 36373033, 38601293, 39584348, 0, 40567402,
+ 41091698, 42205821, 46858954, 54723389, 60818335, 65143773,
+ 68944924, 71959625, 75957383, 77530268, 80938194, 81593564,
+ 84739337, 92997002, 96863680, 106235474, 110233234, 0,
+ 117704448, 122816325, 132515812, 136579106, 140773476,
+ 142149753, 143001732, 144705695, 145492139, },
+ { 0, 0, 3342387, 0, 9044106, 0, 11534512, 0, 13697233, 0, 0, 0, 0,
+ 0, 25690504, 0, 0, 0, 0, 0, 36438572, 38732366, 0, 0, 0,
+ 41157236, 0, 46924492, 54788932, 61080481, 65209315, 0,
+ 72025163, 0, 0, 0, 0, 85132558, 93062540, 96929223,
+ 106563158, 0, 0, 118032133, 123012947, 132581351,
+ 136775717, 140839013, 0, 143067271, 0, 145557677, },
+ { 0, 2162719, 3473460, 5636181, 0, 0, 0, 0, 0, 0, 0, 18809088,
+ 20185395, 21299519, 0, 0, 0, 29622721, 0, 0, 0, 39256656,
+ 39649885, 0, 0, 41288309, 42336901, 47448781, 55182149,
+ 61342629, 65274852, 69010461, 72811596, 76219528, 77726880,
+ 0, 0, 86967572, 93128077, 97650120, 106628699, 110560915,
+ 0, 118490890, 123733846, 132646888, 0, 141232230,
+ 142411898, 0, 144836769, 145688750, },
+ { 655370, 2228258, 3538998, 5701719, 9109643, 10485920, 11600049,
+ 12648641, 13762770, 15204584, 15859954, 18874656, 20250933,
+ 21365062, 25756041, 27328929, 28574132, 29688261, 32309741,
+ 34996758, 36504109, 39322200, 39715422, 39912033, 40632940,
+ 41353847, 42467975, 47514325, 55247691, 61473705, 65405925,
+ 69272606, 72877144, 76285068, 77857955, 81003732, 81659102,
+ 87164208, 93193614, 97715667, 106759772, 110626456,
+ 114296528, 118687505, 123864929, 132712425, 136906792,
+ 141297772, 142477438, 143132808, 144902307, 145754288, },
+ { 786443, 0, 0, 0, 9240716, 0, 11665586, 0, 13893843, 0, 0, 0, 0,
+ 0, 25887114, 0, 0, 0, 0, 0, 36635182, 0, 0, 0, 0, 0,
+ 42599049, 0, 0, 0, 65733607, 0, 73008217, 0, 77989029, 0,
+ 81724639, 87295283, 0, 98305492, 107021918, 0, 0, 0, 0, 0,
+ 137037866, 0, 0, 0, 0, 0, },
+ { 0, 0, 3604535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27394466, 0,
+ 29753798, 32571886, 35258903, 0, 0, 0, 0, 0, 0, 0, 0,
+ 55509836, 61604779, 0, 0, 0, 0, 0, 0, 81790176, 87557429,
+ 93259151, 98502109, 107152994, 110888601, 0, 119015188,
+ 124323683, 133498858, 137234476, 0, 0, 143263881, 0,
+ 145819825, },
+ { 0, 0, 3866680, 6160472, 0, 10616993, 0, 12714178, 0, 0, 0, 0,
+ 20316470, 0, 0, 27460003, 0, 31261127, 32637426, 35521051,
+ 0, 0, 0, 39977570, 0, 0, 0, 48366294, 56492880, 62391213,
+ 0, 69338146, 73073755, 0, 78316711, 0, 0, 0, 93980048,
+ 98764256, 107218532, 111085213, 114362065, 119736089,
+ 125241194, 133957622, 0, 0, 0, 143329419, 144967844,
+ 145885362, },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 62456761, 0, 69403683, 73139292, 0,
+ 78382252, 0, 81855713, 87622969, 0, 98829796, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 48431843, 0, 0, 0, 0, 0, 76416141, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 851981, 0, 4063292, 0, 9306254, 0, 0, 0, 0, 0, 0, 19005729, 0, 0,
+ 0, 27525540, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42795659,
+ 49152740, 56623967, 62587834, 66061292, 69600292, 73401437,
+ 0, 0, 0, 0, 87950650, 94111131, 99878373, 107546213,
+ 112002720, 0, 119932708, 125306744, 0, 137496623,
+ 141363309, 0, 143460492, 0, 0, },
+ { 917518, 0, 0, 0, 9502863, 0, 0, 0, 14155989, 0, 0, 19071267, 0,
+ 0, 26083724, 0, 0, 0, 32702963, 0, 36700720, 0, 0, 0, 0, 0,
+ 43057806, 0, 0, 0, 66520049, 0, 0, 0, 78841005, 81069269,
+ 0, 88147263, 0, 99943925, 107873898, 112068270, 0,
+ 120063783, 125831033, 0, 137693235, 0, 0, 143526030, 0, 0, },
+ { 983055, 0, 0, 0, 0, 0, 0, 0, 14483673, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 37093937, 0, 0, 0, 0, 0, 44565138, 49349359, 0, 0,
+ 66651128, 69665831, 73860193, 0, 79561908, 0, 0, 88606018,
+ 94176669, 0, 0, 0, 0, 120129321, 0, 0, 0, 141494382, 0,
+ 143591567, 0, 0, },
+ { 1114128, 2293795, 4587583, 8257631, 9633938, 10813603, 11731123,
+ 12845251, 14680286, 15270121, 15925491, 19661092, 20382007,
+ 24969543, 26149263, 27656613, 28639669, 31392222, 32768500,
+ 35586591, 37225015, 39387737, 39780959, 40043107, 40698477,
+ 41419384, 44696233, 52495090, 57738081, 63439804, 66782202,
+ 69927976, 73925736, 76809359, 79824063, 81134806, 81921250,
+ 89785673, 94307742, 100795894, 107939439, 112330415,
+ 114427602, 120588074, 126158721, 134416381, 137824310,
+ 141559920, 142542975, 143853712, 145033381, 145950899, },
+ { 1179666, 0, 0, 0, 9699476, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26280336,
+ 0, 0, 0, 0, 0, 38076985, 0, 0, 0, 0, 0, 45220523, 52560674,
+ 0, 0, 67175420, 69993516, 0, 0, 79889603, 0, 0, 89916763,
+ 94373280, 101451267, 108136048, 0, 114493139, 120784689,
+ 126355334, 134481924, 138414136, 141625457, 142608512, 0,
+ 0, 0, },
+ { 0, 0, 0, 0, 9896085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 33292789, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67830786, 0, 0,
+ 0, 80020676, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127403913, 0, 0, 0,
+ 0, 0, 0, 0, },
+ { 1310739, 2359332, 4653127, 0, 0, 0, 12189876, 0, 0, 0, 0, 0, 0,
+ 0, 26345874, 28246439, 0, 31457760, 0, 35652128, 38142534,
+ 0, 0, 0, 0, 0, 45351603, 52757283, 57869170, 63636425,
+ 67961868, 71304237, 73991273, 0, 0, 0, 0, 90309981, 0,
+ 101910029, 108988019, 114034355, 0, 120850228, 127469465,
+ 135464965, 138741825, 141690994, 142739585, 143984788, 0,
+ 0, },
+ { 1441813, 2424869, 4718664, 8388735, 10027160, 10879142, 12255419,
+ 12976325, 14745825, 15401194, 15991028, 19857709, 20447544,
+ 25035134, 26542483, 28377520, 28705206, 31588833, 33358333,
+ 35783201, 38208071, 39453274, 39846496, 40108644, 40764014,
+ 41484921, 45613749, 53216038, 58196852, 63898572, 68158478,
+ 71369793, 74253418, 77005973, 80479430, 81265879, 81986787,
+ 90965347, 94504353, 103679508, 109250176, 114165453,
+ 114558676, 121243445, 127731610, 135727124, 138807366,
+ 142018675, 142805123, 144115862, 145098918, 146016436, },
+ { 1572887, 0, 0, 0, 10092698, 0, 12320956, 0, 14811362, 0, 0,
+ 19923248, 0, 25166207, 26739094, 0, 0, 0, 33423870, 0,
+ 38273608, 0, 0, 0, 0, 0, 45744825, 0, 58262393, 64095184,
+ 68355089, 0, 75170926, 0, 80610509, 0, 0, 91817325, 0,
+ 104203823, 109512324, 0, 0, 121636667, 128059294, 0,
+ 139069511, 0, 0, 0, 0, 0, },
+ { 1703961, 2490406, 4849737, 0, 10223771, 0, 0, 13107399, 15007971,
+ 15466732, 0, 0, 20513081, 25231745, 26870169, 0, 0,
+ 31654371, 34275839, 0, 38404681, 0, 0, 0, 40829551, 0,
+ 45875899, 53609261, 59900794, 64226259, 68551700, 0, 0, 0,
+ 80807119, 81331417, 0, 91948410, 94700963, 104465975,
+ 109643400, 114230991, 114951893, 121702209, 131663779, 0,
+ 139266123, 0, 0, 144246936, 145295527, 0, },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27132315, 0, 0, 0, 0,
+ 0, 0, 39518811, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75302012, 0,
+ 0, 0, 0, 92079484, 0, 105383483, 109708938, 0, 0, 0, 0, 0,
+ 0, 0, 0, 144312474, 0, 0, },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 46006973, 0, 60031891, 64291797, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 105711177, 0, 0, 0, 0, 131991514, 135923736,
+ 139331662, 0, 0, 144378011, 0, 146147509, },
+ { 0, 0, 0, 0, 10354845, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68813847, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 121767746, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 60097429, 0, 0, 0, 0, 77137048, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 64422870, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 132122591, 0, 0, 142084216, 0, 0, 0, 0, }, };
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/Portability.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/Portability.java
new file mode 100644
index 000000000..a83c3d519
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/Portability.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2008-2015 Mozilla Foundation
+ * Copyright (c) 2018-2021 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import org.xml.sax.SAXException;
+
+import nu.validator.htmlparser.annotation.Literal;
+import nu.validator.htmlparser.annotation.Local;
+import nu.validator.htmlparser.annotation.NoLength;
+import nu.validator.htmlparser.common.Interner;
+
+public final class Portability {
+
+ public static int checkedAdd(int a, int b) throws SAXException {
+ // This can't be translated code, because in C++ signed integer overflow is UB, so the below code would be wrong.
+ assert a >= 0;
+ assert b >= 0;
+ int sum = a + b;
+ if (sum < a || sum < b) {
+ throw new SAXException("Integer overflow");
+ }
+ return sum;
+ }
+
+ // Allocating methods
+
+ /**
+ * Allocates a new local name object. In C++, the refcount must be set up in such a way that
+ * calling <code>releaseLocal</code> on the return value balances the refcount set by this method.
+ */
+ public static @Local String newLocalNameFromBuffer(@NoLength char[] buf, int offset, int length, Interner interner) {
+ return new String(buf, offset, length).intern();
+ }
+
+ public static String newStringFromBuffer(@NoLength char[] buf, int offset, int length
+ // CPPONLY: , TreeBuilder treeBuilder, boolean maybeAtomize
+ ) {
+ return new String(buf, offset, length);
+ }
+
+ public static String newEmptyString() {
+ return "";
+ }
+
+ public static String newStringFromLiteral(@Literal String literal) {
+ return literal;
+ }
+
+ public static String newStringFromString(String string) {
+ return string;
+ }
+
+ // XXX get rid of this
+ public static char[] newCharArrayFromLocal(@Local String local) {
+ return local.toCharArray();
+ }
+
+ public static char[] newCharArrayFromString(String string) {
+ return string.toCharArray();
+ }
+
+ public static @Local String newLocalFromLocal(@Local String local, Interner interner) {
+ return local;
+ }
+
+ // Deallocation methods
+
+ public static void releaseString(String str) {
+ // No-op in Java
+ }
+
+ // Comparison methods
+
+ public static boolean localEqualsBuffer(@Local String local, @NoLength char[] buf, int offset, int length) {
+ if (local.length() != length) {
+ return false;
+ }
+ for (int i = 0; i < length; i++) {
+ if (local.charAt(i) != buf[offset + i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString(@Literal String lowerCaseLiteral,
+ String string) {
+ if (string == null) {
+ return false;
+ }
+ if (lowerCaseLiteral.length() > string.length()) {
+ return false;
+ }
+ for (int i = 0; i < lowerCaseLiteral.length(); i++) {
+ char c0 = lowerCaseLiteral.charAt(i);
+ char c1 = string.charAt(i);
+ if (c1 >= 'A' && c1 <= 'Z') {
+ c1 += 0x20;
+ }
+ if (c0 != c1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean lowerCaseLiteralEqualsIgnoreAsciiCaseString(@Literal String lowerCaseLiteral,
+ String string) {
+ if (string == null) {
+ return false;
+ }
+ if (lowerCaseLiteral.length() != string.length()) {
+ return false;
+ }
+ for (int i = 0; i < lowerCaseLiteral.length(); i++) {
+ char c0 = lowerCaseLiteral.charAt(i);
+ char c1 = string.charAt(i);
+ if (c1 >= 'A' && c1 <= 'Z') {
+ c1 += 0x20;
+ }
+ if (c0 != c1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean literalEqualsString(@Literal String literal, String string) {
+ return literal.equals(string);
+ }
+
+ public static boolean stringEqualsString(String one, String other) {
+ return one.equals(other);
+ }
+
+ public static void delete(Object o) {
+
+ }
+
+ public static void deleteArray(Object o) {
+
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/PushedLocation.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/PushedLocation.java
new file mode 100644
index 000000000..fad5f43db
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/PushedLocation.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+public class PushedLocation {
+ private final int line;
+
+ private final int linePrev;
+
+ private final int col;
+
+ private final int colPrev;
+
+ private final boolean nextCharOnNewLine;
+
+ private final String publicId;
+
+ private final String systemId;
+
+ private final PushedLocation next;
+
+ /**
+ * @param line
+ * @param linePrev
+ * @param col
+ * @param colPrev
+ * @param nextCharOnNewLine
+ * @param publicId
+ * @param systemId
+ * @param next
+ */
+ public PushedLocation(int line, int linePrev, int col, int colPrev,
+ boolean nextCharOnNewLine, String publicId, String systemId,
+ PushedLocation next) {
+ this.line = line;
+ this.linePrev = linePrev;
+ this.col = col;
+ this.colPrev = colPrev;
+ this.nextCharOnNewLine = nextCharOnNewLine;
+ this.publicId = publicId;
+ this.systemId = systemId;
+ this.next = next;
+ }
+
+ /**
+ * Returns the line.
+ *
+ * @return the line
+ */
+ public int getLine() {
+ return line;
+ }
+
+ /**
+ * Returns the linePrev.
+ *
+ * @return the linePrev
+ */
+ public int getLinePrev() {
+ return linePrev;
+ }
+
+ /**
+ * Returns the col.
+ *
+ * @return the col
+ */
+ public int getCol() {
+ return col;
+ }
+
+ /**
+ * Returns the colPrev.
+ *
+ * @return the colPrev
+ */
+ public int getColPrev() {
+ return colPrev;
+ }
+
+ /**
+ * Returns the nextCharOnNewLine.
+ *
+ * @return the nextCharOnNewLine
+ */
+ public boolean isNextCharOnNewLine() {
+ return nextCharOnNewLine;
+ }
+
+ /**
+ * Returns the publicId.
+ *
+ * @return the publicId
+ */
+ public String getPublicId() {
+ return publicId;
+ }
+
+ /**
+ * Returns the systemId.
+ *
+ * @return the systemId
+ */
+ public String getSystemId() {
+ return systemId;
+ }
+
+ /**
+ * Returns the next.
+ *
+ * @return the next
+ */
+ public PushedLocation getNext() {
+ return next;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/StackNode.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/StackNode.java
new file mode 100644
index 000000000..5225145e0
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/StackNode.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2011 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import nu.validator.htmlparser.annotation.Inline;
+import nu.validator.htmlparser.annotation.Local;
+import nu.validator.htmlparser.annotation.NsUri;
+
+final class StackNode<T> {
+ final int flags;
+
+ final @Local String name;
+
+ final @Local String popName;
+
+ final @NsUri String ns;
+
+ final T node;
+
+ // Only used on the list of formatting elements
+ HtmlAttributes attributes;
+
+ private int refcount = 1;
+
+ /*
+ * Only valid for formatting elements
+ */
+ // CPPONLY: private @HtmlCreator Object htmlCreator;
+
+ // [NOCPP[
+
+ private final TaintableLocatorImpl locator;
+
+ public TaintableLocatorImpl getLocator() {
+ return locator;
+ }
+
+ // ]NOCPP]
+
+ @Inline public int getFlags() {
+ return flags;
+ }
+
+ public int getGroup() {
+ return flags & ElementName.GROUP_MASK;
+ }
+
+ public boolean isScoping() {
+ return (flags & ElementName.SCOPING) != 0;
+ }
+
+ public boolean isSpecial() {
+ return (flags & ElementName.SPECIAL) != 0;
+ }
+
+ public boolean isFosterParenting() {
+ return (flags & ElementName.FOSTER_PARENTING) != 0;
+ }
+
+ public boolean isHtmlIntegrationPoint() {
+ return (flags & ElementName.HTML_INTEGRATION_POINT) != 0;
+ }
+
+ // [NOCPP[
+
+ public boolean isOptionalEndTag() {
+ return (flags & ElementName.OPTIONAL_END_TAG) != 0;
+ }
+
+ // ]NOCPP]
+
+ // CPPONLY: public @HtmlCreator Object getHtmlCreator() {
+ // CPPONLY: return htmlCreator;
+ // CPPONLY: }
+
+ /**
+ * Constructor for copying. This doesn't take another <code>StackNode</code>
+ * because in C++ the caller is reponsible for reobtaining the local names
+ * from another interner.
+ *
+ * @param flags
+ * @param ns
+ * @param name
+ * @param node
+ * @param popName
+ * @param attributes
+ */
+ StackNode(int flags, @NsUri String ns, @Local String name, T node,
+ @Local String popName, HtmlAttributes attributes,
+ // CPPONLY: @HtmlCreator Object htmlCreator
+ // [NOCPP[
+ TaintableLocatorImpl locator
+ // ]NOCPP]
+ ) {
+ this.flags = flags;
+ this.name = name;
+ this.popName = popName;
+ this.ns = ns;
+ this.node = node;
+ this.attributes = attributes;
+ this.refcount = 1;
+ /*
+ * Need to track creator for formatting elements when copying.
+ */
+ // CPPONLY: this.htmlCreator = htmlCreator;
+ // [NOCPP[
+ this.locator = locator;
+ // ]NOCPP]
+ }
+
+ /**
+ * Short hand for well-known HTML elements.
+ *
+ * @param elementName
+ * @param node
+ */
+ StackNode(ElementName elementName, T node
+ // [NOCPP[
+ , TaintableLocatorImpl locator
+ // ]NOCPP]
+ ) {
+ this.flags = elementName.getFlags();
+ this.name = elementName.getName();
+ this.popName = elementName.getName();
+ this.ns = "http://www.w3.org/1999/xhtml";
+ this.node = node;
+ this.attributes = null;
+ this.refcount = 1;
+ assert elementName.isInterned() : "Don't use this constructor for custom elements.";
+ /*
+ * Not used for formatting elements, so no need to track creator.
+ */
+ // CPPONLY: this.htmlCreator = null;
+ // [NOCPP[
+ this.locator = locator;
+ // ]NOCPP]
+ }
+
+ /**
+ * Constructor for HTML formatting elements.
+ *
+ * @param elementName
+ * @param node
+ * @param attributes
+ */
+ StackNode(ElementName elementName, T node, HtmlAttributes attributes
+ // [NOCPP[
+ , TaintableLocatorImpl locator
+ // ]NOCPP]
+ ) {
+ this.flags = elementName.getFlags();
+ this.name = elementName.getName();
+ this.popName = elementName.getName();
+ this.ns = "http://www.w3.org/1999/xhtml";
+ this.node = node;
+ this.attributes = attributes;
+ this.refcount = 1;
+ assert elementName.isInterned() : "Don't use this constructor for custom elements.";
+ /*
+ * Need to track creator for formatting elements in order to be able
+ * to clone them.
+ */
+ // CPPONLY: this.htmlCreator = elementName.getHtmlCreator();
+ // [NOCPP[
+ this.locator = locator;
+ // ]NOCPP]
+ }
+
+ /**
+ * The common-case HTML constructor.
+ *
+ * @param elementName
+ * @param node
+ * @param popName
+ */
+ StackNode(ElementName elementName, T node, @Local String popName
+ // [NOCPP[
+ , TaintableLocatorImpl locator
+ // ]NOCPP]
+ ) {
+ this.flags = elementName.getFlags();
+ this.name = elementName.getName();
+ this.popName = popName;
+ this.ns = "http://www.w3.org/1999/xhtml";
+ this.node = node;
+ this.attributes = null;
+ this.refcount = 1;
+ /*
+ * Not used for formatting elements, so no need to track creator.
+ */
+ // CPPONLY: this.htmlCreator = null;
+ // [NOCPP[
+ this.locator = locator;
+ // ]NOCPP]
+ }
+
+ /**
+ * Constructor for SVG elements. Note that the order of the arguments is
+ * what distinguishes this from the HTML constructor. This is ugly, but
+ * AFAICT the least disruptive way to make this work with Java's generics
+ * and without unnecessary branches. :-(
+ *
+ * @param elementName
+ * @param popName
+ * @param node
+ */
+ StackNode(ElementName elementName, @Local String popName, T node
+ // [NOCPP[
+ , TaintableLocatorImpl locator
+ // ]NOCPP]
+ ) {
+ this.flags = prepareSvgFlags(elementName.getFlags());
+ this.name = elementName.getName();
+ this.popName = popName;
+ this.ns = "http://www.w3.org/2000/svg";
+ this.node = node;
+ this.attributes = null;
+ this.refcount = 1;
+ /*
+ * Not used for formatting elements, so no need to track creator.
+ */
+ // CPPONLY: this.htmlCreator = null;
+ // [NOCPP[
+ this.locator = locator;
+ // ]NOCPP]
+ }
+
+ /**
+ * Constructor for MathML.
+ *
+ * @param elementName
+ * @param node
+ * @param popName
+ * @param markAsIntegrationPoint
+ */
+ StackNode(ElementName elementName, T node, @Local String popName,
+ boolean markAsIntegrationPoint
+ // [NOCPP[
+ , TaintableLocatorImpl locator
+ // ]NOCPP]
+ ) {
+ this.flags = prepareMathFlags(elementName.getFlags(),
+ markAsIntegrationPoint);
+ this.name = elementName.getName();
+ this.popName = popName;
+ this.ns = "http://www.w3.org/1998/Math/MathML";
+ this.node = node;
+ this.attributes = null;
+ this.refcount = 1;
+ /*
+ * Not used for formatting elements, so no need to track creator.
+ */
+ // CPPONLY: this.htmlCreator = null;
+ // [NOCPP[
+ this.locator = locator;
+ // ]NOCPP]
+ }
+
+ private static int prepareSvgFlags(int flags) {
+ flags &= ~(ElementName.FOSTER_PARENTING | ElementName.SCOPING
+ | ElementName.SPECIAL | ElementName.OPTIONAL_END_TAG);
+ if ((flags & ElementName.SCOPING_AS_SVG) != 0) {
+ flags |= (ElementName.SCOPING | ElementName.SPECIAL | ElementName.HTML_INTEGRATION_POINT);
+ }
+ return flags;
+ }
+
+ private static int prepareMathFlags(int flags,
+ boolean markAsIntegrationPoint) {
+ flags &= ~(ElementName.FOSTER_PARENTING | ElementName.SCOPING
+ | ElementName.SPECIAL | ElementName.OPTIONAL_END_TAG);
+ if ((flags & ElementName.SCOPING_AS_MATHML) != 0) {
+ flags |= (ElementName.SCOPING | ElementName.SPECIAL);
+ }
+ if (markAsIntegrationPoint) {
+ flags |= ElementName.HTML_INTEGRATION_POINT;
+ }
+ return flags;
+ }
+
+ @SuppressWarnings("unused") private void destructor() {
+ Portability.delete(attributes);
+ }
+
+ public void dropAttributes() {
+ attributes = null;
+ }
+
+ // [NOCPP[
+ /**
+ * @see java.lang.Object#toString()
+ */
+ @Override public @Local String toString() {
+ return name;
+ }
+
+ // ]NOCPP]
+
+ public void retain() {
+ refcount++;
+ }
+
+ public void release() {
+ refcount--;
+ if (refcount == 0) {
+ Portability.delete(this);
+ }
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/StateSnapshot.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/StateSnapshot.java
new file mode 100644
index 000000000..d79641bcb
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/StateSnapshot.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2009-2010 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import nu.validator.htmlparser.annotation.Auto;
+
+
+public class StateSnapshot<T> implements TreeBuilderState<T> {
+
+ private final @Auto StackNode<T>[] stack;
+
+ private final @Auto StackNode<T>[] listOfActiveFormattingElements;
+
+ private final @Auto int[] templateModeStack;
+
+ private final T formPointer;
+
+ private final T headPointer;
+
+ private final T deepTreeSurrogateParent;
+
+ private final int mode;
+
+ private final int originalMode;
+
+ private final boolean framesetOk;
+
+ private final boolean needToDropLF;
+
+ private final boolean quirks;
+
+ /**
+ * @param stack
+ * @param listOfActiveFormattingElements
+ * @param templateModeStack
+ * @param formPointer
+ * @param headPointer
+ * @param deepTreeSurrogateParent
+ * @param mode
+ * @param originalMode
+ * @param framesetOk
+ * @param needToDropLF
+ * @param quirks
+ */
+ StateSnapshot(StackNode<T>[] stack,
+ StackNode<T>[] listOfActiveFormattingElements, int[] templateModeStack, T formPointer,
+ T headPointer, T deepTreeSurrogateParent, int mode, int originalMode,
+ boolean framesetOk, boolean needToDropLF, boolean quirks) {
+ this.stack = stack;
+ this.listOfActiveFormattingElements = listOfActiveFormattingElements;
+ this.templateModeStack = templateModeStack;
+ this.formPointer = formPointer;
+ this.headPointer = headPointer;
+ this.deepTreeSurrogateParent = deepTreeSurrogateParent;
+ this.mode = mode;
+ this.originalMode = originalMode;
+ this.framesetOk = framesetOk;
+ this.needToDropLF = needToDropLF;
+ this.quirks = quirks;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getStack()
+ */
+ public StackNode<T>[] getStack() {
+ return stack;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStack()
+ */
+ public int[] getTemplateModeStack() {
+ return templateModeStack;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getListOfActiveFormattingElements()
+ */
+ public StackNode<T>[] getListOfActiveFormattingElements() {
+ return listOfActiveFormattingElements;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getFormPointer()
+ */
+ public T getFormPointer() {
+ return formPointer;
+ }
+
+ /**
+ * Returns the headPointer.
+ *
+ * @return the headPointer
+ */
+ public T getHeadPointer() {
+ return headPointer;
+ }
+
+ /**
+ * Returns the deepTreeSurrogateParent.
+ *
+ * @return the deepTreeSurrogateParent
+ */
+ public T getDeepTreeSurrogateParent() {
+ return deepTreeSurrogateParent;
+ }
+
+ /**
+ * Returns the mode.
+ *
+ * @return the mode
+ */
+ public int getMode() {
+ return mode;
+ }
+
+ /**
+ * Returns the originalMode.
+ *
+ * @return the originalMode
+ */
+ public int getOriginalMode() {
+ return originalMode;
+ }
+
+ /**
+ * Returns the framesetOk.
+ *
+ * @return the framesetOk
+ */
+ public boolean isFramesetOk() {
+ return framesetOk;
+ }
+
+ /**
+ * Returns the needToDropLF.
+ *
+ * @return the needToDropLF
+ */
+ public boolean isNeedToDropLF() {
+ return needToDropLF;
+ }
+
+ /**
+ * Returns the quirks.
+ *
+ * @return the quirks
+ */
+ public boolean isQuirks() {
+ return quirks;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getListOfActiveFormattingElementsLength()
+ */
+ public int getListOfActiveFormattingElementsLength() {
+ return listOfActiveFormattingElements.length;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getStackLength()
+ */
+ public int getStackLength() {
+ return stack.length;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStackLength()
+ */
+ public int getTemplateModeStackLength() {
+ return templateModeStack.length;
+ }
+
+ @SuppressWarnings("unused") private void destructor() {
+ for (int i = 0; i < stack.length; i++) {
+ stack[i].release();
+ }
+ for (int i = 0; i < listOfActiveFormattingElements.length; i++) {
+ if (listOfActiveFormattingElements[i] != null) {
+ listOfActiveFormattingElements[i].release();
+ }
+ }
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TaintableLocatorImpl.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TaintableLocatorImpl.java
new file mode 100644
index 000000000..37cdb75d3
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TaintableLocatorImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2011 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import org.xml.sax.Locator;
+
+public class TaintableLocatorImpl extends LocatorImpl {
+
+ private boolean tainted;
+
+ public TaintableLocatorImpl(Locator locator) {
+ super(locator);
+ this.tainted = false;
+ }
+
+ public void markTainted() {
+ tainted = true;
+ }
+
+ public boolean isTainted() {
+ return tainted;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/Tokenizer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/Tokenizer.java
new file mode 100644
index 000000000..c6f6797ff
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/Tokenizer.java
@@ -0,0 +1,7140 @@
+/*
+ * Copyright (c) 2005-2007 Henri Sivonen
+ * Copyright (c) 2007-2015 Mozilla Foundation
+ * Copyright (c) 2018-2021 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ * Portions of comments Copyright 2004-2010 Apple Computer, Inc., Mozilla
+ * Foundation, and Opera Software ASA.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The comments following this one that use the same comment syntax as this
+ * comment are quotes from the WHATWG HTML 5 spec as of 2 June 2007
+ * amended as of June 18 2008 and May 31 2010.
+ * That document came with this statement:
+ * "© Copyright 2004-2010 Apple Computer, Inc., Mozilla Foundation, and
+ * Opera Software ASA. You are granted a license to use, reproduce and
+ * create derivative works of this document."
+ */
+
+package nu.validator.htmlparser.impl;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import nu.validator.htmlparser.annotation.Auto;
+import nu.validator.htmlparser.annotation.CharacterName;
+import nu.validator.htmlparser.annotation.Const;
+import nu.validator.htmlparser.annotation.Inline;
+import nu.validator.htmlparser.annotation.Local;
+import nu.validator.htmlparser.annotation.NoLength;
+import nu.validator.htmlparser.common.EncodingDeclarationHandler;
+import nu.validator.htmlparser.common.Interner;
+import nu.validator.htmlparser.common.TokenHandler;
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+
+/**
+ * An implementation of
+ * https://html.spec.whatwg.org/multipage/syntax.html#tokenization
+ *
+ * This class implements the <code>Locator</code> interface. This is not an
+ * incidental implementation detail: Users of this class are encouraged to make
+ * use of the <code>Locator</code> nature.
+ *
+ * By default, the tokenizer may report data that XML 1.0 bans. The tokenizer
+ * can be configured to treat these conditions as fatal or to coerce the infoset
+ * to something that XML 1.0 allows.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public class Tokenizer implements Locator {
+
+ private static final int DATA_AND_RCDATA_MASK = ~1;
+
+ public static final int DATA = 0;
+
+ public static final int RCDATA = 1;
+
+ public static final int SCRIPT_DATA = 2;
+
+ public static final int RAWTEXT = 3;
+
+ public static final int SCRIPT_DATA_ESCAPED = 4;
+
+ public static final int ATTRIBUTE_VALUE_DOUBLE_QUOTED = 5;
+
+ public static final int ATTRIBUTE_VALUE_SINGLE_QUOTED = 6;
+
+ public static final int ATTRIBUTE_VALUE_UNQUOTED = 7;
+
+ public static final int PLAINTEXT = 8;
+
+ public static final int TAG_OPEN = 9;
+
+ public static final int CLOSE_TAG_OPEN = 10;
+
+ public static final int TAG_NAME = 11;
+
+ public static final int BEFORE_ATTRIBUTE_NAME = 12;
+
+ public static final int ATTRIBUTE_NAME = 13;
+
+ public static final int AFTER_ATTRIBUTE_NAME = 14;
+
+ public static final int BEFORE_ATTRIBUTE_VALUE = 15;
+
+ public static final int AFTER_ATTRIBUTE_VALUE_QUOTED = 16;
+
+ public static final int BOGUS_COMMENT = 17;
+
+ public static final int MARKUP_DECLARATION_OPEN = 18;
+
+ public static final int DOCTYPE = 19;
+
+ public static final int BEFORE_DOCTYPE_NAME = 20;
+
+ public static final int DOCTYPE_NAME = 21;
+
+ public static final int AFTER_DOCTYPE_NAME = 22;
+
+ public static final int BEFORE_DOCTYPE_PUBLIC_IDENTIFIER = 23;
+
+ public static final int DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED = 24;
+
+ public static final int DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED = 25;
+
+ public static final int AFTER_DOCTYPE_PUBLIC_IDENTIFIER = 26;
+
+ public static final int BEFORE_DOCTYPE_SYSTEM_IDENTIFIER = 27;
+
+ public static final int DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED = 28;
+
+ public static final int DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED = 29;
+
+ public static final int AFTER_DOCTYPE_SYSTEM_IDENTIFIER = 30;
+
+ public static final int BOGUS_DOCTYPE = 31;
+
+ public static final int COMMENT_START = 32;
+
+ public static final int COMMENT_START_DASH = 33;
+
+ public static final int COMMENT = 34;
+
+ public static final int COMMENT_END_DASH = 35;
+
+ public static final int COMMENT_END = 36;
+
+ public static final int COMMENT_END_BANG = 37;
+
+ public static final int NON_DATA_END_TAG_NAME = 38;
+
+ public static final int MARKUP_DECLARATION_HYPHEN = 39;
+
+ public static final int MARKUP_DECLARATION_OCTYPE = 40;
+
+ public static final int DOCTYPE_UBLIC = 41;
+
+ public static final int DOCTYPE_YSTEM = 42;
+
+ public static final int AFTER_DOCTYPE_PUBLIC_KEYWORD = 43;
+
+ public static final int BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS = 44;
+
+ public static final int AFTER_DOCTYPE_SYSTEM_KEYWORD = 45;
+
+ public static final int CONSUME_CHARACTER_REFERENCE = 46;
+
+ public static final int CONSUME_NCR = 47;
+
+ public static final int CHARACTER_REFERENCE_TAIL = 48;
+
+ public static final int HEX_NCR_LOOP = 49;
+
+ public static final int DECIMAL_NRC_LOOP = 50;
+
+ public static final int HANDLE_NCR_VALUE = 51;
+
+ public static final int HANDLE_NCR_VALUE_RECONSUME = 52;
+
+ public static final int CHARACTER_REFERENCE_HILO_LOOKUP = 53;
+
+ public static final int SELF_CLOSING_START_TAG = 54;
+
+ public static final int CDATA_START = 55;
+
+ public static final int CDATA_SECTION = 56;
+
+ public static final int CDATA_RSQB = 57;
+
+ public static final int CDATA_RSQB_RSQB = 58;
+
+ public static final int SCRIPT_DATA_LESS_THAN_SIGN = 59;
+
+ public static final int SCRIPT_DATA_ESCAPE_START = 60;
+
+ public static final int SCRIPT_DATA_ESCAPE_START_DASH = 61;
+
+ public static final int SCRIPT_DATA_ESCAPED_DASH = 62;
+
+ public static final int SCRIPT_DATA_ESCAPED_DASH_DASH = 63;
+
+ public static final int BOGUS_COMMENT_HYPHEN = 64;
+
+ public static final int RAWTEXT_RCDATA_LESS_THAN_SIGN = 65;
+
+ public static final int SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN = 66;
+
+ public static final int SCRIPT_DATA_DOUBLE_ESCAPE_START = 67;
+
+ public static final int SCRIPT_DATA_DOUBLE_ESCAPED = 68;
+
+ public static final int SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN = 69;
+
+ public static final int SCRIPT_DATA_DOUBLE_ESCAPED_DASH = 70;
+
+ public static final int SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH = 71;
+
+ public static final int SCRIPT_DATA_DOUBLE_ESCAPE_END = 72;
+
+ public static final int PROCESSING_INSTRUCTION = 73;
+
+ public static final int PROCESSING_INSTRUCTION_QUESTION_MARK = 74;
+
+ /**
+ * Magic value for UTF-16 operations.
+ */
+ private static final int LEAD_OFFSET = (0xD800 - (0x10000 >> 10));
+
+ /**
+ * UTF-16 code unit array containing less than and greater than for emitting
+ * those characters on certain parse errors.
+ */
+ private static final @NoLength char[] LT_GT = { '<', '>' };
+
+ /**
+ * UTF-16 code unit array containing less than and solidus for emitting
+ * those characters on certain parse errors.
+ */
+ private static final @NoLength char[] LT_SOLIDUS = { '<', '/' };
+
+ /**
+ * UTF-16 code unit array containing ]] for emitting those characters on
+ * state transitions.
+ */
+ private static final @NoLength char[] RSQB_RSQB = { ']', ']' };
+
+ /**
+ * Array version of U+FFFD.
+ */
+ private static final @NoLength char[] REPLACEMENT_CHARACTER = { '\uFFFD' };
+
+ // [NOCPP[
+
+ /**
+ * Array version of space.
+ */
+ private static final @NoLength char[] SPACE = { ' ' };
+
+ // ]NOCPP]
+
+ /**
+ * Array version of line feed.
+ */
+ private static final @NoLength char[] LF = { '\n' };
+
+ /**
+ * "CDATA[" as <code>char[]</code>
+ */
+ private static final @NoLength char[] CDATA_LSQB = { 'C', 'D', 'A', 'T',
+ 'A', '[' };
+
+ /**
+ * "octype" as <code>char[]</code>
+ */
+ private static final @NoLength char[] OCTYPE = { 'o', 'c', 't', 'y', 'p',
+ 'e' };
+
+ /**
+ * "ublic" as <code>char[]</code>
+ */
+ private static final @NoLength char[] UBLIC = { 'u', 'b', 'l', 'i', 'c' };
+
+ /**
+ * "ystem" as <code>char[]</code>
+ */
+ private static final @NoLength char[] YSTEM = { 'y', 's', 't', 'e', 'm' };
+
+ private static final char[] TITLE_ARR = { 't', 'i', 't', 'l', 'e' };
+
+ private static final char[] SCRIPT_ARR = { 's', 'c', 'r', 'i', 'p', 't' };
+
+ private static final char[] STYLE_ARR = { 's', 't', 'y', 'l', 'e' };
+
+ private static final char[] PLAINTEXT_ARR = { 'p', 'l', 'a', 'i', 'n', 't',
+ 'e', 'x', 't' };
+
+ private static final char[] XMP_ARR = { 'x', 'm', 'p' };
+
+ private static final char[] TEXTAREA_ARR = { 't', 'e', 'x', 't', 'a', 'r',
+ 'e', 'a' };
+
+ private static final char[] IFRAME_ARR = { 'i', 'f', 'r', 'a', 'm', 'e' };
+
+ private static final char[] NOEMBED_ARR = { 'n', 'o', 'e', 'm', 'b', 'e',
+ 'd' };
+
+ private static final char[] NOSCRIPT_ARR = { 'n', 'o', 's', 'c', 'r', 'i',
+ 'p', 't' };
+
+ private static final char[] NOFRAMES_ARR = { 'n', 'o', 'f', 'r', 'a', 'm',
+ 'e', 's' };
+
+ /**
+ * The token handler.
+ */
+ protected final TokenHandler tokenHandler;
+
+ protected EncodingDeclarationHandler encodingDeclarationHandler;
+
+ // [NOCPP[
+
+ /**
+ * The error handler.
+ */
+ protected ErrorHandler errorHandler;
+
+ // ]NOCPP]
+
+ /**
+ * Whether the previous char read was CR.
+ */
+ protected boolean lastCR;
+
+ protected int stateSave;
+
+ private int returnStateSave;
+
+ protected int index;
+
+ private boolean forceQuirks;
+
+ private char additional;
+
+ private int entCol;
+
+ private int firstCharKey;
+
+ private int lo;
+
+ private int hi;
+
+ private int candidate;
+
+ private int charRefBufMark;
+
+ protected int value;
+
+ private boolean seenDigits;
+
+ protected int cstart;
+
+ /**
+ * The SAX public id for the resource being tokenized. (Only passed to back
+ * as part of locator data.)
+ */
+ private String publicId;
+
+ /**
+ * The SAX system id for the resource being tokenized. (Only passed to back
+ * as part of locator data.)
+ */
+ private String systemId;
+
+ /**
+ * Buffer for bufferable things other than those that fit the description
+ * of <code>charRefBuf</code>.
+ */
+ private @Auto char[] strBuf;
+
+ /**
+ * Number of significant <code>char</code>s in <code>strBuf</code>.
+ */
+ private int strBufLen;
+
+ /**
+ * Buffer for characters that might form a character reference but may
+ * end up not forming one.
+ */
+ private final @Auto char[] charRefBuf;
+
+ /**
+ * Number of significant <code>char</code>s in <code>charRefBuf</code>.
+ */
+ private int charRefBufLen;
+
+ /**
+ * Buffer for expanding NCRs falling into the Basic Multilingual Plane.
+ */
+ private final @Auto char[] bmpChar;
+
+ /**
+ * Buffer for expanding astral NCRs.
+ */
+ private final @Auto char[] astralChar;
+
+ /**
+ * The element whose end tag closes the current CDATA or RCDATA element.
+ */
+ protected ElementName endTagExpectation = null;
+
+ private char[] endTagExpectationAsArray; // not @Auto!
+
+ /**
+ * <code>true</code> if tokenizing an end tag
+ */
+ protected boolean endTag;
+
+ /**
+ * <code>true</code> iff the current element/attribute name contains
+ * a hyphen.
+ */
+ private boolean containsHyphen;
+
+ /**
+ * The current tag token name. One of
+ * 1) null,
+ * 2) non-owning reference to nonInternedTagName
+ * 3) non-owning reference to a pre-interned ElementName
+ */
+ private ElementName tagName = null;
+
+ /**
+ * The recycled ElementName instance for the non-pre-interned cases.
+ */
+ private ElementName nonInternedTagName = null;
+
+ /**
+ * The current attribute name.
+ */
+ protected AttributeName attributeName = null;
+
+ // CPPONLY: private AttributeName nonInternedAttributeName = null;
+
+ // [NOCPP[
+
+ /**
+ * Whether comment tokens are emitted.
+ */
+ private boolean wantsComments = false;
+
+ /**
+ * <code>true</code> when HTML4-specific additional errors are requested.
+ */
+ protected boolean html4;
+
+ /**
+ * Whether the stream is past the first 1024 bytes.
+ */
+ private boolean metaBoundaryPassed;
+
+ // ]NOCPP]
+
+ /**
+ * The name of the current doctype token.
+ */
+ private @Local String doctypeName;
+
+ /**
+ * The public id of the current doctype token.
+ */
+ private String publicIdentifier;
+
+ /**
+ * The system id of the current doctype token.
+ */
+ private String systemIdentifier;
+
+ /**
+ * The attribute holder.
+ */
+ private HtmlAttributes attributes;
+
+ // [NOCPP[
+
+ /**
+ * The policy for vertical tab and form feed.
+ */
+ private XmlViolationPolicy contentSpacePolicy = XmlViolationPolicy.ALTER_INFOSET;
+
+ /**
+ * The policy for comments.
+ */
+ private XmlViolationPolicy commentPolicy = XmlViolationPolicy.ALTER_INFOSET;
+
+ private XmlViolationPolicy xmlnsPolicy = XmlViolationPolicy.ALTER_INFOSET;
+
+ private XmlViolationPolicy namePolicy = XmlViolationPolicy.ALTER_INFOSET;
+
+ private boolean html4ModeCompatibleWithXhtml1Schemata;
+
+ private int mappingLangToXmlLang;
+
+ // ]NOCPP]
+
+ private final boolean newAttributesEachTime;
+
+ private boolean shouldSuspend;
+
+ protected boolean confident;
+
+ private int line;
+
+ /*
+ * The line number of the current attribute. First set to the line of the
+ * attribute name and if there is a value, set to the line the value
+ * started on.
+ */
+ // CPPONLY: private int attributeLine;
+
+ private Interner interner;
+
+ // CPPONLY: private boolean viewingXmlSource;
+
+ // [NOCPP[
+
+ protected LocatorImpl ampersandLocation;
+
+ public Tokenizer(TokenHandler tokenHandler, boolean newAttributesEachTime) {
+ this.tokenHandler = tokenHandler;
+ this.encodingDeclarationHandler = null;
+ this.newAttributesEachTime = newAttributesEachTime;
+ // &CounterClockwiseContourIntegral; is the longest valid char ref and
+ // the semicolon never gets appended to the buffer.
+ this.charRefBuf = new char[32];
+ this.bmpChar = new char[1];
+ this.astralChar = new char[2];
+ this.containsHyphen = false;
+ this.tagName = null;
+ this.nonInternedTagName = new ElementName();
+ this.attributeName = null;
+ // CPPONLY: this.nonInternedAttributeName = new AttributeName();
+ this.doctypeName = null;
+ this.publicIdentifier = null;
+ this.systemIdentifier = null;
+ this.attributes = null;
+ }
+
+ // ]NOCPP]
+
+ /**
+ * The constructor.
+ *
+ * @param tokenHandler
+ * the handler for receiving tokens
+ */
+ public Tokenizer(TokenHandler tokenHandler
+ // CPPONLY: , boolean viewingXmlSource
+ ) {
+ this.tokenHandler = tokenHandler;
+ this.encodingDeclarationHandler = null;
+ // [NOCPP[
+ this.newAttributesEachTime = false;
+ // ]NOCPP]
+ // &CounterClockwiseContourIntegral; is the longest valid char ref and
+ // the semicolon never gets appended to the buffer.
+ this.charRefBuf = new char[32];
+ this.bmpChar = new char[1];
+ this.astralChar = new char[2];
+ this.containsHyphen = false;
+ this.tagName = null;
+ this.nonInternedTagName = new ElementName();
+ this.attributeName = null;
+ // CPPONLY: this.nonInternedAttributeName = new AttributeName();
+ this.doctypeName = null;
+ this.publicIdentifier = null;
+ this.systemIdentifier = null;
+ // [NOCPP[
+ this.attributes = null;
+ // ]NOCPP]
+ // CPPONLY: this.attributes = tokenHandler.HasBuilder() ? new HtmlAttributes(mappingLangToXmlLang) : null;
+ // CPPONLY: this.newAttributesEachTime = !tokenHandler.HasBuilder();
+ // CPPONLY: this.viewingXmlSource = viewingXmlSource;
+ }
+
+ public void setInterner(Interner interner) {
+ this.interner = interner;
+ }
+
+ public void initLocation(String newPublicId, String newSystemId) {
+ this.systemId = newSystemId;
+ this.publicId = newPublicId;
+
+ }
+
+ // CPPONLY: boolean isViewingXmlSource() {
+ // CPPONLY: return viewingXmlSource;
+ // CPPONLY: }
+
+ // [NOCPP[
+
+ /**
+ * Returns the mappingLangToXmlLang.
+ *
+ * @return the mappingLangToXmlLang
+ */
+ public boolean isMappingLangToXmlLang() {
+ return mappingLangToXmlLang == AttributeName.HTML_LANG;
+ }
+
+ /**
+ * Sets the mappingLangToXmlLang.
+ *
+ * @param mappingLangToXmlLang
+ * the mappingLangToXmlLang to set
+ */
+ public void setMappingLangToXmlLang(boolean mappingLangToXmlLang) {
+ this.mappingLangToXmlLang = mappingLangToXmlLang ? AttributeName.HTML_LANG
+ : AttributeName.HTML;
+ }
+
+ /**
+ * Sets the error handler.
+ *
+ * @see org.xml.sax.XMLReader#setErrorHandler(org.xml.sax.ErrorHandler)
+ */
+ public void setErrorHandler(ErrorHandler eh) {
+ this.errorHandler = eh;
+ }
+
+ public ErrorHandler getErrorHandler() {
+ return this.errorHandler;
+ }
+
+ /**
+ * Sets the commentPolicy.
+ *
+ * @param commentPolicy
+ * the commentPolicy to set
+ */
+ public void setCommentPolicy(XmlViolationPolicy commentPolicy) {
+ this.commentPolicy = commentPolicy;
+ }
+
+ /**
+ * Sets the contentNonXmlCharPolicy.
+ *
+ * @param contentNonXmlCharPolicy
+ * the contentNonXmlCharPolicy to set
+ */
+ public void setContentNonXmlCharPolicy(
+ XmlViolationPolicy contentNonXmlCharPolicy) {
+ if (contentNonXmlCharPolicy != XmlViolationPolicy.ALLOW) {
+ throw new IllegalArgumentException(
+ "Must use ErrorReportingTokenizer to set contentNonXmlCharPolicy to non-ALLOW.");
+ }
+ }
+
+ /**
+ * Sets the contentSpacePolicy.
+ *
+ * @param contentSpacePolicy
+ * the contentSpacePolicy to set
+ */
+ public void setContentSpacePolicy(XmlViolationPolicy contentSpacePolicy) {
+ this.contentSpacePolicy = contentSpacePolicy;
+ }
+
+ /**
+ * Sets the xmlnsPolicy.
+ *
+ * @param xmlnsPolicy
+ * the xmlnsPolicy to set
+ */
+ public void setXmlnsPolicy(XmlViolationPolicy xmlnsPolicy) {
+ if (xmlnsPolicy == XmlViolationPolicy.FATAL) {
+ throw new IllegalArgumentException("Can't use FATAL here.");
+ }
+ this.xmlnsPolicy = xmlnsPolicy;
+ }
+
+ public void setNamePolicy(XmlViolationPolicy namePolicy) {
+ this.namePolicy = namePolicy;
+ }
+
+ /**
+ * Sets the html4ModeCompatibleWithXhtml1Schemata.
+ *
+ * @param html4ModeCompatibleWithXhtml1Schemata
+ * the html4ModeCompatibleWithXhtml1Schemata to set
+ */
+ public void setHtml4ModeCompatibleWithXhtml1Schemata(
+ boolean html4ModeCompatibleWithXhtml1Schemata) {
+ this.html4ModeCompatibleWithXhtml1Schemata = html4ModeCompatibleWithXhtml1Schemata;
+ }
+
+ // ]NOCPP]
+
+ // For the token handler to call
+ /**
+ * Sets the tokenizer state and the associated element name. This should
+ * only ever used to put the tokenizer into one of the states that have
+ * a special end tag expectation.
+ *
+ * @param specialTokenizerState
+ * the tokenizer state to set
+ */
+ public void setState(int specialTokenizerState) {
+ this.stateSave = specialTokenizerState;
+ this.endTagExpectation = null;
+ this.endTagExpectationAsArray = null;
+ }
+
+ // [NOCPP[
+
+ /**
+ * Sets the tokenizer state and the associated element name. This should
+ * only ever used to put the tokenizer into one of the states that have
+ * a special end tag expectation. For use from the tokenizer test harness.
+ *
+ * @param specialTokenizerState
+ * the tokenizer state to set
+ * @param endTagExpectation
+ * the expected end tag for transitioning back to normal
+ */
+ public void setStateAndEndTagExpectation(int specialTokenizerState,
+ @Local String endTagExpectation) {
+ this.stateSave = specialTokenizerState;
+ if (specialTokenizerState == Tokenizer.DATA) {
+ return;
+ }
+ @Auto char[] asArray = Portability.newCharArrayFromLocal(endTagExpectation);
+ this.endTagExpectation = ElementName.elementNameByBuffer(asArray, 0,
+ asArray.length, interner);
+ assert this.endTagExpectation != null;
+ endTagExpectationToArray();
+ }
+
+ // ]NOCPP]
+
+ /**
+ * Sets the tokenizer state and the associated element name. This should
+ * only ever used to put the tokenizer into one of the states that have
+ * a special end tag expectation.
+ *
+ * @param specialTokenizerState
+ * the tokenizer state to set
+ * @param endTagExpectation
+ * the expected end tag for transitioning back to normal
+ */
+ public void setStateAndEndTagExpectation(int specialTokenizerState,
+ ElementName endTagExpectation) {
+ this.stateSave = specialTokenizerState;
+ this.endTagExpectation = endTagExpectation;
+ endTagExpectationToArray();
+ }
+
+ private void endTagExpectationToArray() {
+ switch (endTagExpectation.getGroup()) {
+ case TreeBuilder.TITLE:
+ endTagExpectationAsArray = TITLE_ARR;
+ return;
+ case TreeBuilder.SCRIPT:
+ endTagExpectationAsArray = SCRIPT_ARR;
+ return;
+ case TreeBuilder.STYLE:
+ endTagExpectationAsArray = STYLE_ARR;
+ return;
+ case TreeBuilder.PLAINTEXT:
+ endTagExpectationAsArray = PLAINTEXT_ARR;
+ return;
+ case TreeBuilder.XMP:
+ endTagExpectationAsArray = XMP_ARR;
+ return;
+ case TreeBuilder.TEXTAREA:
+ endTagExpectationAsArray = TEXTAREA_ARR;
+ return;
+ case TreeBuilder.IFRAME:
+ endTagExpectationAsArray = IFRAME_ARR;
+ return;
+ case TreeBuilder.NOEMBED:
+ endTagExpectationAsArray = NOEMBED_ARR;
+ return;
+ case TreeBuilder.NOSCRIPT:
+ endTagExpectationAsArray = NOSCRIPT_ARR;
+ return;
+ case TreeBuilder.NOFRAMES:
+ endTagExpectationAsArray = NOFRAMES_ARR;
+ return;
+ default:
+ assert false: "Bad end tag expectation.";
+ return;
+ }
+ }
+
+ /**
+ * For C++ use only.
+ */
+ public void setLineNumber(int line) {
+ // CPPONLY: this.attributeLine = line; // XXX is this needed?
+ this.line = line;
+ }
+
+ // start Locator impl
+
+ /**
+ * @see org.xml.sax.Locator#getLineNumber()
+ */
+ @Inline public int getLineNumber() {
+ return line;
+ }
+
+ // [NOCPP[
+
+ /**
+ * @see org.xml.sax.Locator#getColumnNumber()
+ */
+ @Inline public int getColumnNumber() {
+ return -1;
+ }
+
+ /**
+ * @see org.xml.sax.Locator#getPublicId()
+ */
+ public String getPublicId() {
+ return publicId;
+ }
+
+ /**
+ * @see org.xml.sax.Locator#getSystemId()
+ */
+ public String getSystemId() {
+ return systemId;
+ }
+
+ // end Locator impl
+
+ // end public API
+
+ public void notifyAboutMetaBoundary() {
+ metaBoundaryPassed = true;
+ }
+
+ void turnOnAdditionalHtml4Errors() {
+ html4 = true;
+ }
+
+ // ]NOCPP]
+
+ HtmlAttributes emptyAttributes() {
+ // [NOCPP[
+ if (newAttributesEachTime) {
+ return new HtmlAttributes(mappingLangToXmlLang);
+ } else {
+ // ]NOCPP]
+ return HtmlAttributes.EMPTY_ATTRIBUTES;
+ // [NOCPP[
+ }
+ // ]NOCPP]
+ }
+
+ @Inline private void appendCharRefBuf(char c) {
+ // CPPONLY: assert charRefBufLen < charRefBuf.length:
+ // CPPONLY: "RELEASE: Attempted to overrun charRefBuf!";
+ charRefBuf[charRefBufLen++] = c;
+ }
+
+ private void emitOrAppendCharRefBuf(int returnState) throws SAXException {
+ if ((returnState & DATA_AND_RCDATA_MASK) != 0) {
+ appendCharRefBufToStrBuf();
+ } else {
+ if (charRefBufLen > 0) {
+ tokenHandler.characters(charRefBuf, 0, charRefBufLen);
+ charRefBufLen = 0;
+ }
+ }
+ }
+
+ @Inline private void clearStrBufAfterUse() {
+ strBufLen = 0;
+ }
+
+ @Inline private void clearStrBufBeforeUse() {
+ assert strBufLen == 0: "strBufLen not reset after previous use!";
+ strBufLen = 0; // no-op in the absence of bugs
+ }
+
+ @Inline private void clearStrBufAfterOneHyphen() {
+ assert strBufLen == 1: "strBufLen length not one!";
+ assert strBuf[0] == '-': "strBuf does not start with a hyphen!";
+ strBufLen = 0;
+ }
+
+ /**
+ * Appends to the buffer.
+ *
+ * @param c
+ * the UTF-16 code unit to append
+ */
+ @Inline private void appendStrBuf(char c) {
+ // CPPONLY: assert strBufLen < strBuf.length: "Previous buffer length insufficient.";
+ // CPPONLY: if (strBufLen == strBuf.length) {
+ // CPPONLY: if (!EnsureBufferSpace(1)) {
+ // CPPONLY: assert false: "RELEASE: Unable to recover from buffer reallocation failure";
+ // CPPONLY: }
+ // CPPONLY: }
+ strBuf[strBufLen++] = c;
+ }
+
+ /**
+ * The buffer as a String. Currently only used for error reporting.
+ *
+ * <p>
+ * C++ memory note: The return value must be released.
+ *
+ * @return the buffer as a string
+ */
+ protected String strBufToString() {
+ String str = Portability.newStringFromBuffer(strBuf, 0, strBufLen
+ // CPPONLY: , tokenHandler, !newAttributesEachTime && attributeName == AttributeName.CLASS
+ );
+ clearStrBufAfterUse();
+ return str;
+ }
+
+ /**
+ * Returns the buffer as a local name. The return value is released in
+ * emitDoctypeToken().
+ *
+ * @return the buffer as local name
+ */
+ private void strBufToDoctypeName() {
+ doctypeName = Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen,
+ interner);
+ clearStrBufAfterUse();
+ }
+
+ /**
+ * Emits the buffer as character tokens.
+ *
+ * @throws SAXException
+ * if the token handler threw
+ */
+ private void emitStrBuf() throws SAXException {
+ if (strBufLen > 0) {
+ tokenHandler.characters(strBuf, 0, strBufLen);
+ clearStrBufAfterUse();
+ }
+ }
+
+ @Inline private void appendSecondHyphenToBogusComment() throws SAXException {
+ // [NOCPP[
+ switch (commentPolicy) {
+ case ALTER_INFOSET:
+ appendStrBuf(' ');
+ // FALLTHROUGH
+ case ALLOW:
+ warn("The document is not mappable to XML 1.0 due to two consecutive hyphens in a comment.");
+ // ]NOCPP]
+ appendStrBuf('-');
+ // [NOCPP[
+ break;
+ case FATAL:
+ fatal("The document is not mappable to XML 1.0 due to two consecutive hyphens in a comment.");
+ break;
+ }
+ // ]NOCPP]
+ }
+
+ // [NOCPP[
+ private void maybeAppendSpaceToBogusComment() throws SAXException {
+ switch (commentPolicy) {
+ case ALTER_INFOSET:
+ appendStrBuf(' ');
+ // FALLTHROUGH
+ case ALLOW:
+ warn("The document is not mappable to XML 1.0 due to a trailing hyphen in a comment.");
+ break;
+ case FATAL:
+ fatal("The document is not mappable to XML 1.0 due to a trailing hyphen in a comment.");
+ break;
+ }
+ }
+
+ // ]NOCPP]
+
+ @Inline private void adjustDoubleHyphenAndAppendToStrBufAndErr(char c)
+ throws SAXException {
+ errConsecutiveHyphens();
+ // [NOCPP[
+ switch (commentPolicy) {
+ case ALTER_INFOSET:
+ strBufLen--;
+ // WARNING!!! This expands the worst case of the buffer length
+ // given the length of input!
+ appendStrBuf(' ');
+ appendStrBuf('-');
+ // FALLTHROUGH
+ case ALLOW:
+ warn("The document is not mappable to XML 1.0 due to two consecutive hyphens in a comment.");
+ // ]NOCPP]
+ appendStrBuf(c);
+ // [NOCPP[
+ break;
+ case FATAL:
+ fatal("The document is not mappable to XML 1.0 due to two consecutive hyphens in a comment.");
+ break;
+ }
+ // ]NOCPP]
+ }
+
+ private void appendStrBuf(@NoLength char[] buffer, int offset, int length) throws SAXException {
+ int newLen = Portability.checkedAdd(strBufLen, length);
+ // CPPONLY: assert newLen <= strBuf.length: "Previous buffer length insufficient.";
+ // CPPONLY: if (strBuf.length < newLen) {
+ // CPPONLY: if (!EnsureBufferSpace(length)) {
+ // CPPONLY: assert false: "RELEASE: Unable to recover from buffer reallocation failure";
+ // CPPONLY: } // TODO: Add telemetry when outer if fires but inner does not
+ // CPPONLY: }
+ System.arraycopy(buffer, offset, strBuf, strBufLen, length);
+ strBufLen = newLen;
+ }
+
+ /**
+ * Append the contents of the char reference buffer to the main one.
+ */
+ @Inline private void appendCharRefBufToStrBuf() throws SAXException {
+ appendStrBuf(charRefBuf, 0, charRefBufLen);
+ charRefBufLen = 0;
+ }
+
+ /**
+ * Emits the current comment token.
+ *
+ * @param pos
+ * TODO
+ *
+ * @throws SAXException
+ */
+ private void emitComment(int provisionalHyphens, int pos)
+ throws SAXException {
+ // [NOCPP[
+ if (wantsComments) {
+ // ]NOCPP]
+ tokenHandler.comment(strBuf, 0, strBufLen
+ - provisionalHyphens);
+ // [NOCPP[
+ }
+ // ]NOCPP]
+ clearStrBufAfterUse();
+ cstart = pos + 1;
+ }
+
+ /**
+ * Flushes coalesced character tokens.
+ *
+ * @param buf
+ * TODO
+ * @param pos
+ * TODO
+ *
+ * @throws SAXException
+ */
+ protected void flushChars(@NoLength char[] buf, int pos)
+ throws SAXException {
+ if (pos > cstart) {
+ tokenHandler.characters(buf, cstart, pos - cstart);
+ }
+ cstart = Integer.MAX_VALUE;
+ }
+
+ /**
+ * Reports an condition that would make the infoset incompatible with XML
+ * 1.0 as fatal.
+ *
+ * @param message
+ * the message
+ * @throws SAXException
+ * @throws SAXParseException
+ */
+ public void fatal(String message) throws SAXException {
+ SAXParseException spe = new SAXParseException(message, this);
+ if (errorHandler != null) {
+ errorHandler.fatalError(spe);
+ }
+ throw spe;
+ }
+
+ /**
+ * Reports a Parse Error.
+ *
+ * @param message
+ * the message
+ * @throws SAXException
+ */
+ public void err(String message) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ SAXParseException spe = new SAXParseException(message, this);
+ errorHandler.error(spe);
+ }
+
+ public void errTreeBuilder(String message) throws SAXException {
+ ErrorHandler eh = null;
+ if (tokenHandler instanceof TreeBuilder<?>) {
+ TreeBuilder<?> treeBuilder = (TreeBuilder<?>) tokenHandler;
+ eh = treeBuilder.getErrorHandler();
+ }
+ if (eh == null) {
+ eh = errorHandler;
+ }
+ if (eh == null) {
+ return;
+ }
+ SAXParseException spe = new SAXParseException(message, this);
+ eh.error(spe);
+ }
+
+ /**
+ * Reports a warning
+ *
+ * @param message
+ * the message
+ * @throws SAXException
+ */
+ public void warn(String message) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ SAXParseException spe = new SAXParseException(message, this);
+ errorHandler.warning(spe);
+ }
+
+ private void strBufToElementNameString() {
+ if (containsHyphen) {
+ // We've got a custom element or annotation-xml.
+ @Local String annotationName = ElementName.ANNOTATION_XML.getName();
+ if (Portability.localEqualsBuffer(annotationName, strBuf, 0, strBufLen)) {
+ tagName = ElementName.ANNOTATION_XML;
+ } else {
+ nonInternedTagName.setNameForNonInterned(Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen,
+ interner)
+ // CPPONLY: , true
+ );
+ tagName = nonInternedTagName;
+ }
+ } else {
+ tagName = ElementName.elementNameByBuffer(strBuf, 0, strBufLen,
+ interner);
+ if (tagName == null) {
+ nonInternedTagName.setNameForNonInterned(Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen,
+ interner)
+ // CPPONLY: , false
+ );
+ tagName = nonInternedTagName;
+ }
+ }
+ containsHyphen = false;
+ clearStrBufAfterUse();
+ }
+
+ private int emitCurrentTagToken(boolean selfClosing, int pos)
+ throws SAXException {
+ cstart = pos + 1;
+ maybeErrSlashInEndTag(selfClosing);
+ stateSave = Tokenizer.DATA;
+ HtmlAttributes attrs = (attributes == null ? HtmlAttributes.EMPTY_ATTRIBUTES
+ : attributes);
+ if (endTag) {
+ /*
+ * When an end tag token is emitted, the content model flag must be
+ * switched to the PCDATA state.
+ */
+ maybeErrAttributesOnEndTag(attrs);
+ // CPPONLY: if (!viewingXmlSource) {
+ tokenHandler.endTag(tagName);
+ // CPPONLY: }
+ // CPPONLY: if (newAttributesEachTime) {
+ // CPPONLY: Portability.delete(attributes);
+ // CPPONLY: attributes = null;
+ // CPPONLY: }
+ } else {
+ // CPPONLY: if (viewingXmlSource) {
+ // CPPONLY: assert newAttributesEachTime;
+ // CPPONLY: Portability.delete(attributes);
+ // CPPONLY: attributes = null;
+ // CPPONLY: } else {
+ tokenHandler.startTag(tagName, attrs, selfClosing);
+ // CPPONLY: }
+ }
+ tagName = null;
+ if (newAttributesEachTime) {
+ attributes = null;
+ } else {
+ attributes.clear(mappingLangToXmlLang);
+ }
+ /*
+ * The token handler may have called setStateAndEndTagExpectation
+ * and changed stateSave since the start of this method.
+ */
+ return stateSave;
+ }
+
+ private void attributeNameComplete() throws SAXException {
+ attributeName = AttributeName.nameByBuffer(strBuf, 0, strBufLen, interner);
+ if (attributeName == null) {
+ // [NOCPP[
+ attributeName = AttributeName.createAttributeName(
+ Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen,
+ interner),
+ namePolicy != XmlViolationPolicy.ALLOW);
+ // ]NOCPP]
+ // CPPONLY: nonInternedAttributeName.setNameForNonInterned(Portability.newLocalNameFromBuffer(strBuf, 0, strBufLen, interner));
+ // CPPONLY: attributeName = nonInternedAttributeName;
+ }
+ clearStrBufAfterUse();
+
+ if (attributes == null) {
+ attributes = new HtmlAttributes(mappingLangToXmlLang);
+ }
+
+ /*
+ * When the user agent leaves the attribute name state (and before
+ * emitting the tag token, if appropriate), the complete attribute's
+ * name must be compared to the other attributes on the same token; if
+ * there is already an attribute on the token with the exact same name,
+ * then this is a parse error and the new attribute must be dropped,
+ * along with the value that gets associated with it (if any).
+ */
+ if (attributes.contains(attributeName)) {
+ errDuplicateAttribute();
+ attributeName = null;
+ }
+ }
+
+ private void addAttributeWithoutValue() throws SAXException {
+ noteAttributeWithoutValue();
+
+ // [NOCPP[
+ if (metaBoundaryPassed && AttributeName.CHARSET == attributeName
+ && ElementName.META == tagName) {
+ err("A \u201Ccharset\u201D attribute on a \u201Cmeta\u201D element found after the first 512 bytes.");
+ }
+ // ]NOCPP]
+ if (attributeName != null) {
+ // [NOCPP[
+ if (html4) {
+ if (attributeName.isBoolean()) {
+ if (html4ModeCompatibleWithXhtml1Schemata) {
+ attributes.addAttribute(attributeName,
+ attributeName.getLocal(AttributeName.HTML),
+ xmlnsPolicy);
+ } else {
+ attributes.addAttribute(attributeName, "", xmlnsPolicy);
+ }
+ } else {
+ if (AttributeName.BORDER != attributeName) {
+ err("Attribute value omitted for a non-boolean attribute. (HTML4-only error.)");
+ attributes.addAttribute(attributeName, "", xmlnsPolicy);
+ }
+ }
+ } else {
+ if (AttributeName.SRC == attributeName
+ || AttributeName.HREF == attributeName) {
+ warn("Attribute \u201C"
+ + attributeName.getLocal(AttributeName.HTML)
+ + "\u201D without an explicit value seen. The attribute may be dropped by IE7.");
+ }
+ // ]NOCPP]
+ attributes.addAttribute(attributeName,
+ Portability.newEmptyString()
+ // [NOCPP[
+ , xmlnsPolicy
+ // ]NOCPP]
+ // CPPONLY: , attributeLine
+ );
+ // [NOCPP[
+ }
+ // ]NOCPP]
+ attributeName = null;
+ } else {
+ clearStrBufAfterUse();
+ }
+ }
+
+ private void addAttributeWithValue() throws SAXException {
+ // [NOCPP[
+ if (metaBoundaryPassed && ElementName.META == tagName
+ && AttributeName.CHARSET == attributeName) {
+ err("A \u201Ccharset\u201D attribute on a \u201Cmeta\u201D element found after the first 512 bytes.");
+ }
+ // ]NOCPP]
+ if (attributeName != null) {
+ String val = strBufToString(); // Ownership transferred to
+ // HtmlAttributes
+ // CPPONLY: if (mViewSource) {
+ // CPPONLY: mViewSource.MaybeLinkifyAttributeValue(attributeName, val);
+ // CPPONLY: }
+ // [NOCPP[
+ if (!endTag && html4 && html4ModeCompatibleWithXhtml1Schemata
+ && attributeName.isCaseFolded()) {
+ val = newAsciiLowerCaseStringFromString(val);
+ }
+ // ]NOCPP]
+ attributes.addAttribute(attributeName, val
+ // [NOCPP[
+ , xmlnsPolicy
+ // ]NOCPP]
+ // CPPONLY: , attributeLine
+ );
+ attributeName = null;
+ } else {
+ // We have a duplicate attribute. Explicitly discard its value.
+ clearStrBufAfterUse();
+ }
+ }
+
+ // [NOCPP[
+
+ private static String newAsciiLowerCaseStringFromString(String str) {
+ if (str == null) {
+ return null;
+ }
+ char[] buf = new char[str.length()];
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ buf[i] = c;
+ }
+ return new String(buf);
+ }
+
+ protected void startErrorReporting() throws SAXException {
+
+ }
+
+ // ]NOCPP]
+
+ public void start() throws SAXException {
+ initializeWithoutStarting();
+ tokenHandler.startTokenization(this);
+ // [NOCPP[
+ startErrorReporting();
+ // ]NOCPP]
+ }
+
+ public boolean tokenizeBuffer(UTF16Buffer buffer) throws SAXException {
+ int state = stateSave;
+ int returnState = returnStateSave;
+ char c = '\u0000';
+ shouldSuspend = false;
+ lastCR = false;
+
+ int start = buffer.getStart();
+ int end = buffer.getEnd();
+
+ // In C++, the caller of tokenizeBuffer needs to do this explicitly.
+ // [NOCPP[
+ ensureBufferSpace(end - start);
+ // ]NOCPP]
+
+ /**
+ * The index of the last <code>char</code> read from <code>buf</code>.
+ */
+ int pos = start - 1;
+
+ /**
+ * The index of the first <code>char</code> in <code>buf</code> that is
+ * part of a coalesced run of character tokens or
+ * <code>Integer.MAX_VALUE</code> if there is not a current run being
+ * coalesced.
+ */
+ switch (state) {
+ case DATA:
+ case RCDATA:
+ case SCRIPT_DATA:
+ case PLAINTEXT:
+ case RAWTEXT:
+ case CDATA_SECTION:
+ case SCRIPT_DATA_ESCAPED:
+ case SCRIPT_DATA_ESCAPE_START:
+ case SCRIPT_DATA_ESCAPE_START_DASH:
+ case SCRIPT_DATA_ESCAPED_DASH:
+ case SCRIPT_DATA_ESCAPED_DASH_DASH:
+ case SCRIPT_DATA_DOUBLE_ESCAPE_START:
+ case SCRIPT_DATA_DOUBLE_ESCAPED:
+ case SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN:
+ case SCRIPT_DATA_DOUBLE_ESCAPED_DASH:
+ case SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH:
+ case SCRIPT_DATA_DOUBLE_ESCAPE_END:
+ cstart = start;
+ break;
+ default:
+ cstart = Integer.MAX_VALUE;
+ break;
+ }
+
+ /**
+ * The number of <code>char</code>s in <code>buf</code> that have
+ * meaning. (The rest of the array is garbage and should not be
+ * examined.)
+ */
+ // CPPONLY: if (mViewSource) {
+ // CPPONLY: mViewSource.SetBuffer(buffer);
+ // CPPONLY: pos = stateLoop(state, c, pos, buffer.getBuffer(), false, returnState, buffer.getEnd());
+ // CPPONLY: mViewSource.DropBuffer((pos == buffer.getEnd()) ? pos : pos + 1);
+ // CPPONLY: } else {
+ // CPPONLY: pos = stateLoop(state, c, pos, buffer.getBuffer(), false, returnState, buffer.getEnd());
+ // CPPONLY: }
+ // [NOCPP[
+ pos = stateLoop(state, c, pos, buffer.getBuffer(), false, returnState,
+ end);
+ // ]NOCPP]
+ if (pos == end) {
+ // exiting due to end of buffer
+ buffer.setStart(pos);
+ } else {
+ buffer.setStart(pos + 1);
+ }
+ return lastCR;
+ }
+
+ // [NOCPP[
+ private void ensureBufferSpace(int inputLength) throws SAXException {
+ // Add 2 to account for emissions of LT_GT, LT_SOLIDUS and RSQB_RSQB.
+ // Adding to the general worst case instead of only the
+ // TreeBuilder-exposed worst case to avoid re-introducing a bug when
+ // unifying the tokenizer and tree builder buffers in the future.
+ int worstCase = strBufLen + inputLength + charRefBufLen + 2;
+ tokenHandler.ensureBufferSpace(worstCase);
+ if (commentPolicy == XmlViolationPolicy.ALTER_INFOSET) {
+ // When altering infoset, if the comment contents are consecutive
+ // hyphens, each hyphen generates a space, too. These buffer
+ // contents never get emitted as characters() to the tokenHandler,
+ // which is why this calculation happens after the call to
+ // ensureBufferSpace on tokenHandler.
+ worstCase *= 2;
+ }
+ if (strBuf == null) {
+ // Add an arbitrary small value to avoid immediate reallocation
+ // once there are a few characters in the buffer.
+ strBuf = new char[worstCase + 128];
+ } else if (worstCase > strBuf.length) {
+ // HotSpot reportedly allocates memory with 8-byte accuracy, so
+ // there's no point in trying to do math here to avoid slop.
+ // Maybe we should add some small constant to worstCase here
+ // but not doing that without profiling. In C++ with jemalloc,
+ // the corresponding method should do math to round up here
+ // to avoid slop.
+ char[] newBuf = new char[worstCase];
+ System.arraycopy(strBuf, 0, newBuf, 0, strBufLen);
+ strBuf = newBuf;
+ }
+ }
+ // ]NOCPP]
+
+ @SuppressWarnings("unused") private int stateLoop(int state, char c,
+ int pos, @NoLength char[] buf, boolean reconsume, int returnState,
+ int endPos) throws SAXException {
+ /*
+ * Idioms used in this code:
+ *
+ *
+ * Consuming the next input character
+ *
+ * To consume the next input character, the code does this: if (++pos ==
+ * endPos) { break stateloop; } c = checkChar(buf, pos);
+ *
+ *
+ * Staying in a state
+ *
+ * When there's a state that the tokenizer may stay in over multiple
+ * input characters, the state has a wrapper |for(;;)| loop and staying
+ * in the state continues the loop.
+ *
+ *
+ * Switching to another state
+ *
+ * To switch to another state, the code sets the state variable to the
+ * magic number of the new state. Then it either continues stateloop or
+ * breaks out of the state's own wrapper loop if the target state is
+ * right after the current state in source order. (This is a partial
+ * workaround for Java's lack of goto.)
+ *
+ *
+ * Reconsume support
+ *
+ * The spec sometimes says that an input character is reconsumed in
+ * another state. If a state can ever be entered so that an input
+ * character can be reconsumed in it, the state's code starts with an
+ * |if (reconsume)| that sets reconsume to false and skips over the
+ * normal code for consuming a new character.
+ *
+ * To reconsume the current character in another state, the code sets
+ * |reconsume| to true and then switches to the other state.
+ *
+ *
+ * Emitting character tokens
+ *
+ * This method emits character tokens lazily. Whenever a new range of
+ * character tokens starts, the field cstart must be set to the start
+ * index of the range. The flushChars() method must be called at the end
+ * of a range to flush it.
+ *
+ *
+ * U+0000 handling
+ *
+ * The various states have to handle the replacement of U+0000 with
+ * U+FFFD. However, if U+0000 would be reconsumed in another state, the
+ * replacement doesn't need to happen, because it's handled by the
+ * reconsuming state.
+ *
+ *
+ * LF handling
+ *
+ * Every state needs to increment the line number upon LF unless the LF
+ * gets reconsumed by another state which increments the line number.
+ *
+ *
+ * CR handling
+ *
+ * Every state needs to handle CR unless the CR gets reconsumed and is
+ * handled by the reconsuming state. The CR needs to be handled as if it
+ * were and LF, the lastCR field must be set to true and then this
+ * method must return. The IO driver will then swallow the next
+ * character if it is an LF to coalesce CRLF.
+ */
+ stateloop: for (;;) {
+ switch (state) {
+ case DATA:
+ dataloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ switch (c) {
+ case '&':
+ /*
+ * U+0026 AMPERSAND (&) Switch to the character
+ * reference in data state.
+ */
+ flushChars(buf, pos);
+ assert charRefBufLen == 0: "charRefBufLen not reset after previous use!";
+ appendCharRefBuf(c);
+ setAdditionalAndRememberAmpersandLocation('\u0000');
+ returnState = state;
+ state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos);
+ continue stateloop;
+ case '<':
+ /*
+ * U+003C LESS-THAN SIGN (<) Switch to the tag
+ * open state.
+ */
+ flushChars(buf, pos);
+
+ state = transition(state, Tokenizer.TAG_OPEN, reconsume, pos);
+ break dataloop; // FALL THROUGH continue
+ // stateloop;
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ continue;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Anything else Emit the input character as a
+ * character token.
+ *
+ * Stay in the data state.
+ */
+ continue;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case TAG_OPEN:
+ tagopenloop: for (;;) {
+ /*
+ * The behavior of this state depends on the content
+ * model flag.
+ */
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * If the content model flag is set to the PCDATA state
+ * Consume the next input character:
+ */
+ if (c >= 'A' && c <= 'Z') {
+ /*
+ * U+0041 LATIN CAPITAL LETTER A through to U+005A
+ * LATIN CAPITAL LETTER Z Create a new start tag
+ * token,
+ */
+ endTag = false;
+ /*
+ * set its tag name to the lowercase version of the
+ * input character (add 0x0020 to the character's
+ * code point),
+ */
+ clearStrBufBeforeUse();
+ appendStrBuf((char) (c + 0x20));
+ containsHyphen = false;
+ /* then switch to the tag name state. */
+ state = transition(state, Tokenizer.TAG_NAME, reconsume, pos);
+ /*
+ * (Don't emit the token yet; further details will
+ * be filled in before it is emitted.)
+ */
+ break tagopenloop;
+ // continue stateloop;
+ } else if (c >= 'a' && c <= 'z') {
+ /*
+ * U+0061 LATIN SMALL LETTER A through to U+007A
+ * LATIN SMALL LETTER Z Create a new start tag
+ * token,
+ */
+ endTag = false;
+ /*
+ * set its tag name to the input character,
+ */
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ containsHyphen = false;
+ /* then switch to the tag name state. */
+ state = transition(state, Tokenizer.TAG_NAME, reconsume, pos);
+ /*
+ * (Don't emit the token yet; further details will
+ * be filled in before it is emitted.)
+ */
+ break tagopenloop;
+ // continue stateloop;
+ }
+ switch (c) {
+ case '!':
+ /*
+ * U+0021 EXCLAMATION MARK (!) Switch to the
+ * markup declaration open state.
+ */
+ state = transition(state, Tokenizer.MARKUP_DECLARATION_OPEN, reconsume, pos);
+ continue stateloop;
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) Switch to the close tag
+ * open state.
+ */
+ state = transition(state, Tokenizer.CLOSE_TAG_OPEN, reconsume, pos);
+ continue stateloop;
+ case '?':
+ // CPPONLY: if (viewingXmlSource) {
+ // CPPONLY: state = transition(state,
+ // CPPONLY: Tokenizer.PROCESSING_INSTRUCTION,
+ // CPPONLY: reconsume,
+ // CPPONLY: pos);
+ // CPPONLY: continue stateloop;
+ // CPPONLY: }
+ /*
+ * U+003F QUESTION MARK (?) Parse error.
+ */
+ errProcessingInstruction();
+ /*
+ * Switch to the bogus comment state.
+ */
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Parse error.
+ */
+ errLtGt();
+ /*
+ * Emit a U+003C LESS-THAN SIGN character token
+ * and a U+003E GREATER-THAN SIGN character
+ * token.
+ */
+ tokenHandler.characters(Tokenizer.LT_GT, 0, 2);
+ /* Switch to the data state. */
+ cstart = pos + 1;
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ default:
+ /*
+ * Anything else Parse error.
+ */
+ errBadCharAfterLt(c);
+ /*
+ * Emit a U+003C LESS-THAN SIGN character token
+ */
+ tokenHandler.characters(Tokenizer.LT_GT, 0, 1);
+ /*
+ * and reconsume the current input character in
+ * the data state.
+ */
+ cstart = pos;
+ reconsume = true;
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALL THROUGH DON'T REORDER
+ case TAG_NAME:
+ tagnameloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ strBufToElementNameString();
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * Switch to the before attribute name state.
+ */
+ strBufToElementNameString();
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ break tagnameloop;
+ // continue stateloop;
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) Switch to the self-closing
+ * start tag state.
+ */
+ strBufToElementNameString();
+ state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * tag token.
+ */
+ strBufToElementNameString();
+ state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ break stateloop;
+ }
+ /*
+ * Switch to the data state.
+ */
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ if (c >= 'A' && c <= 'Z') {
+ /*
+ * U+0041 LATIN CAPITAL LETTER A through to
+ * U+005A LATIN CAPITAL LETTER Z Append the
+ * lowercase version of the current input
+ * character (add 0x0020 to the character's
+ * code point) to the current tag token's
+ * tag name.
+ */
+ c += 0x20;
+ } else if (c == '-') {
+ containsHyphen = true;
+ }
+ /*
+ * Anything else Append the current input
+ * character to the current tag token's tag
+ * name.
+ */
+ appendStrBuf(c);
+ containsHyphen = false;
+ /*
+ * Stay in the tag name state.
+ */
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case BEFORE_ATTRIBUTE_NAME:
+ beforeattributenameloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay
+ * in the before attribute name state.
+ */
+ continue;
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) Switch to the self-closing
+ * start tag state.
+ */
+ state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * tag token.
+ */
+ state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ break stateloop;
+ }
+ /*
+ * Switch to the data state.
+ */
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ case '\"':
+ case '\'':
+ case '<':
+ case '=':
+ /*
+ * U+0022 QUOTATION MARK (") U+0027 APOSTROPHE
+ * (') U+003C LESS-THAN SIGN (<) U+003D EQUALS
+ * SIGN (=) Parse error.
+ */
+ errBadCharBeforeAttributeNameOrNull(c);
+ /*
+ * Treat it as per the "anything else" entry
+ * below.
+ */
+ default:
+ /*
+ * Anything else Start a new attribute in the
+ * current tag token.
+ */
+ if (c >= 'A' && c <= 'Z') {
+ /*
+ * U+0041 LATIN CAPITAL LETTER A through to
+ * U+005A LATIN CAPITAL LETTER Z Set that
+ * attribute's name to the lowercase version
+ * of the current input character (add
+ * 0x0020 to the character's code point)
+ */
+ c += 0x20;
+ }
+ // CPPONLY: attributeLine = line;
+ /*
+ * Set that attribute's name to the current
+ * input character,
+ */
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ /*
+ * and its value to the empty string.
+ */
+ // Will do later.
+ /*
+ * Switch to the attribute name state.
+ */
+ state = transition(state, Tokenizer.ATTRIBUTE_NAME, reconsume, pos);
+ break beforeattributenameloop;
+ // continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case ATTRIBUTE_NAME:
+ attributenameloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ attributeNameComplete();
+ state = transition(state, Tokenizer.AFTER_ATTRIBUTE_NAME, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * Switch to the after attribute name state.
+ */
+ attributeNameComplete();
+ state = transition(state, Tokenizer.AFTER_ATTRIBUTE_NAME, reconsume, pos);
+ continue stateloop;
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) Switch to the self-closing
+ * start tag state.
+ */
+ attributeNameComplete();
+ addAttributeWithoutValue();
+ state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos);
+ continue stateloop;
+ case '=':
+ /*
+ * U+003D EQUALS SIGN (=) Switch to the before
+ * attribute value state.
+ */
+ attributeNameComplete();
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_VALUE, reconsume, pos);
+ break attributenameloop;
+ // continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * tag token.
+ */
+ attributeNameComplete();
+ addAttributeWithoutValue();
+ state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ break stateloop;
+ }
+ /*
+ * Switch to the data state.
+ */
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ case '\"':
+ case '\'':
+ case '<':
+ /*
+ * U+0022 QUOTATION MARK (") U+0027 APOSTROPHE
+ * (') U+003C LESS-THAN SIGN (<) Parse error.
+ */
+ errQuoteOrLtInAttributeNameOrNull(c);
+ /*
+ * Treat it as per the "anything else" entry
+ * below.
+ */
+ default:
+ if (c >= 'A' && c <= 'Z') {
+ /*
+ * U+0041 LATIN CAPITAL LETTER A through to
+ * U+005A LATIN CAPITAL LETTER Z Append the
+ * lowercase version of the current input
+ * character (add 0x0020 to the character's
+ * code point) to the current attribute's
+ * name.
+ */
+ c += 0x20;
+ }
+ /*
+ * Anything else Append the current input
+ * character to the current attribute's name.
+ */
+ appendStrBuf(c);
+ /*
+ * Stay in the attribute name state.
+ */
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case BEFORE_ATTRIBUTE_VALUE:
+ beforeattributevalueloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay
+ * in the before attribute value state.
+ */
+ continue;
+ case '"':
+ /*
+ * U+0022 QUOTATION MARK (") Switch to the
+ * attribute value (double-quoted) state.
+ */
+ // CPPONLY: attributeLine = line;
+ clearStrBufBeforeUse();
+ state = transition(state, Tokenizer.ATTRIBUTE_VALUE_DOUBLE_QUOTED, reconsume, pos);
+ break beforeattributevalueloop;
+ // continue stateloop;
+ case '&':
+ /*
+ * U+0026 AMPERSAND (&) Switch to the attribute
+ * value (unquoted) state and reconsume this
+ * input character.
+ */
+ // CPPONLY: attributeLine = line;
+ clearStrBufBeforeUse();
+ reconsume = true;
+ state = transition(state, Tokenizer.ATTRIBUTE_VALUE_UNQUOTED, reconsume, pos);
+ noteUnquotedAttributeValue();
+ continue stateloop;
+ case '\'':
+ /*
+ * U+0027 APOSTROPHE (') Switch to the attribute
+ * value (single-quoted) state.
+ */
+ // CPPONLY: attributeLine = line;
+ clearStrBufBeforeUse();
+ state = transition(state, Tokenizer.ATTRIBUTE_VALUE_SINGLE_QUOTED, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Parse error.
+ */
+ errAttributeValueMissing();
+ /*
+ * Emit the current tag token.
+ */
+ addAttributeWithoutValue();
+ state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ break stateloop;
+ }
+ /*
+ * Switch to the data state.
+ */
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ case '<':
+ case '=':
+ case '`':
+ /*
+ * U+003C LESS-THAN SIGN (<) U+003D EQUALS SIGN
+ * (=) U+0060 GRAVE ACCENT (`)
+ */
+ errLtOrEqualsOrGraveInUnquotedAttributeOrNull(c);
+ /*
+ * Treat it as per the "anything else" entry
+ * below.
+ */
+ default:
+ // [NOCPP[
+ errHtml4NonNameInUnquotedAttribute(c);
+ // ]NOCPP]
+ /*
+ * Anything else Append the current input
+ * character to the current attribute's value.
+ */
+ // CPPONLY: attributeLine = line;
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ /*
+ * Switch to the attribute value (unquoted)
+ * state.
+ */
+
+ state = transition(state, Tokenizer.ATTRIBUTE_VALUE_UNQUOTED, reconsume, pos);
+ noteUnquotedAttributeValue();
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case ATTRIBUTE_VALUE_DOUBLE_QUOTED:
+ attributevaluedoublequotedloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '"':
+ /*
+ * U+0022 QUOTATION MARK (") Switch to the after
+ * attribute value (quoted) state.
+ */
+ addAttributeWithValue();
+
+ state = transition(state, Tokenizer.AFTER_ATTRIBUTE_VALUE_QUOTED, reconsume, pos);
+ break attributevaluedoublequotedloop;
+ // continue stateloop;
+ case '&':
+ /*
+ * U+0026 AMPERSAND (&) Switch to the character
+ * reference in attribute value state, with the
+ * additional allowed character being U+0022
+ * QUOTATION MARK (").
+ */
+ assert charRefBufLen == 0: "charRefBufLen not reset after previous use!";
+ appendCharRefBuf(c);
+ setAdditionalAndRememberAmpersandLocation('\"');
+ returnState = state;
+ state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ continue;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Anything else Append the current input
+ * character to the current attribute's value.
+ */
+ appendStrBuf(c);
+ /*
+ * Stay in the attribute value (double-quoted)
+ * state.
+ */
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case AFTER_ATTRIBUTE_VALUE_QUOTED:
+ afterattributevaluequotedloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * Switch to the before attribute name state.
+ */
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ continue stateloop;
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) Switch to the self-closing
+ * start tag state.
+ */
+ state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos);
+ break afterattributevaluequotedloop;
+ // continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * tag token.
+ */
+ state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ break stateloop;
+ }
+ /*
+ * Switch to the data state.
+ */
+ continue stateloop;
+ default:
+ /*
+ * Anything else Parse error.
+ */
+ errNoSpaceBetweenAttributes();
+ /*
+ * Reconsume the character in the before
+ * attribute name state.
+ */
+ reconsume = true;
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case SELF_CLOSING_START_TAG:
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Set the self-closing
+ * flag of the current tag token. Emit the current
+ * tag token.
+ */
+ // [NOCPP[
+ errHtml4XmlVoidSyntax();
+ // ]NOCPP]
+ state = transition(state, emitCurrentTagToken(true, pos), reconsume, pos);
+ if (shouldSuspend) {
+ break stateloop;
+ }
+ /*
+ * Switch to the data state.
+ */
+ continue stateloop;
+ default:
+ /* Anything else Parse error. */
+ errSlashNotFollowedByGt();
+ /*
+ * Reconsume the character in the before attribute
+ * name state.
+ */
+ reconsume = true;
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ continue stateloop;
+ }
+ // XXX reorder point
+ case ATTRIBUTE_VALUE_UNQUOTED:
+ for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ addAttributeWithValue();
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * Switch to the before attribute name state.
+ */
+ addAttributeWithValue();
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ continue stateloop;
+ case '&':
+ /*
+ * U+0026 AMPERSAND (&) Switch to the character
+ * reference in attribute value state, with the
+ * additional allowed character being U+003E
+ * GREATER-THAN SIGN (>)
+ */
+ assert charRefBufLen == 0: "charRefBufLen not reset after previous use!";
+ appendCharRefBuf(c);
+ setAdditionalAndRememberAmpersandLocation('>');
+ returnState = state;
+ state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * tag token.
+ */
+ addAttributeWithValue();
+ state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ break stateloop;
+ }
+ /*
+ * Switch to the data state.
+ */
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ case '<':
+ case '\"':
+ case '\'':
+ case '=':
+ case '`':
+ /*
+ * U+0022 QUOTATION MARK (") U+0027 APOSTROPHE
+ * (') U+003C LESS-THAN SIGN (<) U+003D EQUALS
+ * SIGN (=) U+0060 GRAVE ACCENT (`) Parse error.
+ */
+ errUnquotedAttributeValOrNull(c);
+ /*
+ * Treat it as per the "anything else" entry
+ * below.
+ */
+ // fall through
+ default:
+ // [NOCPP]
+ errHtml4NonNameInUnquotedAttribute(c);
+ // ]NOCPP]
+ /*
+ * Anything else Append the current input
+ * character to the current attribute's value.
+ */
+ appendStrBuf(c);
+ /*
+ * Stay in the attribute value (unquoted) state.
+ */
+ continue;
+ }
+ }
+ // XXX reorder point
+ case AFTER_ATTRIBUTE_NAME:
+ for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay
+ * in the after attribute name state.
+ */
+ continue;
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) Switch to the self-closing
+ * start tag state.
+ */
+ addAttributeWithoutValue();
+ state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos);
+ continue stateloop;
+ case '=':
+ /*
+ * U+003D EQUALS SIGN (=) Switch to the before
+ * attribute value state.
+ */
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_VALUE, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * tag token.
+ */
+ addAttributeWithoutValue();
+ state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ break stateloop;
+ }
+ /*
+ * Switch to the data state.
+ */
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ case '\"':
+ case '\'':
+ case '<':
+ errQuoteOrLtInAttributeNameOrNull(c);
+ /*
+ * Treat it as per the "anything else" entry
+ * below.
+ */
+ default:
+ addAttributeWithoutValue();
+ /*
+ * Anything else Start a new attribute in the
+ * current tag token.
+ */
+ if (c >= 'A' && c <= 'Z') {
+ /*
+ * U+0041 LATIN CAPITAL LETTER A through to
+ * U+005A LATIN CAPITAL LETTER Z Set that
+ * attribute's name to the lowercase version
+ * of the current input character (add
+ * 0x0020 to the character's code point)
+ */
+ c += 0x20;
+ }
+ /*
+ * Set that attribute's name to the current
+ * input character,
+ */
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ /*
+ * and its value to the empty string.
+ */
+ // Will do later.
+ /*
+ * Switch to the attribute name state.
+ */
+ state = transition(state, Tokenizer.ATTRIBUTE_NAME, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // XXX reorder point
+ case MARKUP_DECLARATION_OPEN:
+ markupdeclarationopenloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * If the next two characters are both U+002D
+ * HYPHEN-MINUS characters (-), consume those two
+ * characters, create a comment token whose data is the
+ * empty string, and switch to the comment start state.
+ *
+ * Otherwise, if the next seven characters are an ASCII
+ * case-insensitive match for the word "DOCTYPE", then
+ * consume those characters and switch to the DOCTYPE
+ * state.
+ *
+ * Otherwise, if the insertion mode is
+ * "in foreign content" and the current node is not an
+ * element in the HTML namespace and the next seven
+ * characters are an case-sensitive match for the string
+ * "[CDATA[" (the five uppercase letters "CDATA" with a
+ * U+005B LEFT SQUARE BRACKET character before and
+ * after), then consume those characters and switch to
+ * the CDATA section state.
+ *
+ * Otherwise, is is a parse error. Switch to the bogus
+ * comment state. The next character that is consumed,
+ * if any, is the first character that will be in the
+ * comment.
+ */
+ switch (c) {
+ case '-':
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.MARKUP_DECLARATION_HYPHEN, reconsume, pos);
+ break markupdeclarationopenloop;
+ // continue stateloop;
+ case 'd':
+ case 'D':
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ index = 0;
+ state = transition(state, Tokenizer.MARKUP_DECLARATION_OCTYPE, reconsume, pos);
+ continue stateloop;
+ case '[':
+ if (tokenHandler.cdataSectionAllowed()) {
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ index = 0;
+ state = transition(state, Tokenizer.CDATA_START, reconsume, pos);
+ continue stateloop;
+ }
+ // else fall through
+ default:
+ errBogusComment();
+ clearStrBufBeforeUse();
+ reconsume = true;
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case MARKUP_DECLARATION_HYPHEN:
+ markupdeclarationhyphenloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ switch (c) {
+ case '-':
+ clearStrBufAfterOneHyphen();
+ state = transition(state, Tokenizer.COMMENT_START, reconsume, pos);
+ break markupdeclarationhyphenloop;
+ // continue stateloop;
+ default:
+ errBogusComment();
+ reconsume = true;
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case COMMENT_START:
+ commentstartloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Comment start state
+ *
+ *
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Switch to the comment
+ * start dash state.
+ */
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.COMMENT_START_DASH, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Parse error.
+ */
+ errPrematureEndOfComment();
+ /* Emit the comment token. */
+ emitComment(0, pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ break commentstartloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Anything else Append the input character to
+ * the comment token's data.
+ */
+ appendStrBuf(c);
+ /*
+ * Switch to the comment state.
+ */
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ break commentstartloop;
+ // continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case COMMENT:
+ commentloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Comment state Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Switch to the comment
+ * end dash state
+ */
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.COMMENT_END_DASH, reconsume, pos);
+ break commentloop;
+ // continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ continue;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Anything else Append the input character to
+ * the comment token's data.
+ */
+ appendStrBuf(c);
+ /*
+ * Stay in the comment state.
+ */
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case COMMENT_END_DASH:
+ commentenddashloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Comment end dash state Consume the next input
+ * character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Switch to the comment
+ * end state
+ */
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.COMMENT_END, reconsume, pos);
+ break commentenddashloop;
+ // continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Anything else Append a U+002D HYPHEN-MINUS
+ * (-) character and the input character to the
+ * comment token's data.
+ */
+ appendStrBuf(c);
+ /*
+ * Switch to the comment state.
+ */
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case COMMENT_END:
+ commentendloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Comment end dash state Consume the next input
+ * character:
+ */
+ switch (c) {
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the comment
+ * token.
+ */
+ emitComment(2, pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '-':
+ /* U+002D HYPHEN-MINUS (-) Parse error. */
+ /*
+ * Append a U+002D HYPHEN-MINUS (-) character to
+ * the comment token's data.
+ */
+ adjustDoubleHyphenAndAppendToStrBufAndErr(c);
+ /*
+ * Stay in the comment end state.
+ */
+ continue;
+ case '\r':
+ adjustDoubleHyphenAndAppendToStrBufCarriageReturn();
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ break stateloop;
+ case '\n':
+ adjustDoubleHyphenAndAppendToStrBufLineFeed();
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ continue stateloop;
+ case '!':
+ errHyphenHyphenBang();
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.COMMENT_END_BANG, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Append two U+002D HYPHEN-MINUS (-) characters
+ * and the input character to the comment
+ * token's data.
+ */
+ adjustDoubleHyphenAndAppendToStrBufAndErr(c);
+ /*
+ * Switch to the comment state.
+ */
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // XXX reorder point
+ case COMMENT_END_BANG:
+ for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Comment end bang state
+ *
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the comment
+ * token.
+ */
+ emitComment(3, pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '-':
+ /*
+ * Append two U+002D HYPHEN-MINUS (-) characters
+ * and a U+0021 EXCLAMATION MARK (!) character
+ * to the comment token's data.
+ */
+ appendStrBuf(c);
+ /*
+ * Switch to the comment end dash state.
+ */
+ state = transition(state, Tokenizer.COMMENT_END_DASH, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ continue;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Anything else Append two U+002D HYPHEN-MINUS
+ * (-) characters, a U+0021 EXCLAMATION MARK (!)
+ * character, and the input character to the
+ * comment token's data. Switch to the comment
+ * state.
+ */
+ appendStrBuf(c);
+ /*
+ * Switch to the comment state.
+ */
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // XXX reorder point
+ case COMMENT_START_DASH:
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Comment start dash state
+ *
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Switch to the comment end
+ * state
+ */
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.COMMENT_END, reconsume, pos);
+ continue stateloop;
+ case '>':
+ errPrematureEndOfComment();
+ /* Emit the comment token. */
+ emitComment(1, pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Append a U+002D HYPHEN-MINUS character (-) and
+ * the current input character to the comment
+ * token's data.
+ */
+ appendStrBuf(c);
+ /*
+ * Switch to the comment state.
+ */
+ state = transition(state, Tokenizer.COMMENT, reconsume, pos);
+ continue stateloop;
+ }
+ // XXX reorder point
+ case CDATA_START:
+ for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ if (index < 6) { // CDATA_LSQB.length
+ if (c == Tokenizer.CDATA_LSQB[index]) {
+ appendStrBuf(c);
+ } else {
+ errBogusComment();
+ reconsume = true;
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ continue stateloop;
+ }
+ index++;
+ continue;
+ } else {
+ clearStrBufAfterUse();
+ cstart = pos; // start coalescing
+ reconsume = true;
+ state = transition(state, Tokenizer.CDATA_SECTION, reconsume, pos);
+ break; // FALL THROUGH continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case CDATA_SECTION:
+ cdatasectionloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ switch (c) {
+ case ']':
+ flushChars(buf, pos);
+ state = transition(state, Tokenizer.CDATA_RSQB, reconsume, pos);
+ break cdatasectionloop; // FALL THROUGH
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ continue;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ default:
+ continue;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case CDATA_RSQB:
+ cdatarsqb: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ switch (c) {
+ case ']':
+ state = transition(state, Tokenizer.CDATA_RSQB_RSQB, reconsume, pos);
+ break cdatarsqb;
+ default:
+ tokenHandler.characters(Tokenizer.RSQB_RSQB, 0,
+ 1);
+ cstart = pos;
+ reconsume = true;
+ state = transition(state, Tokenizer.CDATA_SECTION, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case CDATA_RSQB_RSQB:
+ cdatarsqbrsqb: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ switch (c) {
+ case ']':
+ // Saw a third ]. Emit one ] (logically the
+ // first one) and stay in this state to
+ // remember that the last two characters seen
+ // have been ]].
+ tokenHandler.characters(Tokenizer.RSQB_RSQB, 0, 1);
+ continue;
+ case '>':
+ cstart = pos + 1;
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ default:
+ tokenHandler.characters(Tokenizer.RSQB_RSQB, 0, 2);
+ cstart = pos;
+ reconsume = true;
+ state = transition(state, Tokenizer.CDATA_SECTION, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // XXX reorder point
+ case ATTRIBUTE_VALUE_SINGLE_QUOTED:
+ attributevaluesinglequotedloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\'':
+ /*
+ * U+0027 APOSTROPHE (') Switch to the after
+ * attribute value (quoted) state.
+ */
+ addAttributeWithValue();
+
+ state = transition(state, Tokenizer.AFTER_ATTRIBUTE_VALUE_QUOTED, reconsume, pos);
+ continue stateloop;
+ case '&':
+ /*
+ * U+0026 AMPERSAND (&) Switch to the character
+ * reference in attribute value state, with the
+ * + additional allowed character being U+0027
+ * APOSTROPHE (').
+ */
+ assert charRefBufLen == 0: "charRefBufLen not reset after previous use!";
+ appendCharRefBuf(c);
+ setAdditionalAndRememberAmpersandLocation('\'');
+ returnState = state;
+ state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos);
+ break attributevaluesinglequotedloop;
+ // continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ continue;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Anything else Append the current input
+ * character to the current attribute's value.
+ */
+ appendStrBuf(c);
+ /*
+ * Stay in the attribute value (double-quoted)
+ * state.
+ */
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case CONSUME_CHARACTER_REFERENCE:
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Unlike the definition is the spec, this state does not
+ * return a value and never requires the caller to
+ * backtrack. This state takes care of emitting characters
+ * or appending to the current attribute value. It also
+ * takes care of that in the case when consuming the
+ * character reference fails.
+ */
+ /*
+ * This section defines how to consume a character
+ * reference. This definition is used when parsing character
+ * references in text and in attributes.
+ *
+ * The behavior depends on the identity of the next
+ * character (the one immediately after the U+0026 AMPERSAND
+ * character):
+ */
+ switch (c) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r': // we'll reconsume!
+ case '\u000C':
+ case '<':
+ case '&':
+ case '\u0000':
+ emitOrAppendCharRefBuf(returnState);
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ case '#':
+ /*
+ * U+0023 NUMBER SIGN (#) Consume the U+0023 NUMBER
+ * SIGN.
+ */
+ appendCharRefBuf('#');
+ state = transition(state, Tokenizer.CONSUME_NCR, reconsume, pos);
+ continue stateloop;
+ default:
+ if (c == additional) {
+ emitOrAppendCharRefBuf(returnState);
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ }
+ if (c >= 'a' && c <= 'z') {
+ firstCharKey = c - 'a' + 26;
+ } else if (c >= 'A' && c <= 'Z') {
+ firstCharKey = c - 'A';
+ } else {
+ // No match
+ /*
+ * If no match can be made, then this is a parse
+ * error.
+ */
+ errNoNamedCharacterMatch();
+ emitOrAppendCharRefBuf(returnState);
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ }
+ // Didn't fail yet
+ appendCharRefBuf(c);
+ state = transition(state, Tokenizer.CHARACTER_REFERENCE_HILO_LOOKUP, reconsume, pos);
+ // FALL THROUGH continue stateloop;
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case CHARACTER_REFERENCE_HILO_LOOKUP:
+ {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * The data structure is as follows:
+ *
+ * HILO_ACCEL is a two-dimensional int array whose major
+ * index corresponds to the second character of the
+ * character reference (code point as index) and the
+ * minor index corresponds to the first character of the
+ * character reference (packed so that A-Z runs from 0
+ * to 25 and a-z runs from 26 to 51). This layout makes
+ * it easier to use the sparseness of the data structure
+ * to omit parts of it: The second dimension of the
+ * table is null when no character reference starts with
+ * the character corresponding to that row.
+ *
+ * The int value HILO_ACCEL (by these indeces) is zero
+ * if there exists no character reference starting with
+ * that two-letter prefix. Otherwise, the value is an
+ * int that packs two shorts so that the higher short is
+ * the index of the highest character reference name
+ * with that prefix in NAMES and the lower short
+ * corresponds to the index of the lowest character
+ * reference name with that prefix. (It happens that the
+ * first two character reference names share their
+ * prefix so the packed int cannot be 0 by packing the
+ * two shorts.)
+ *
+ * NAMES is an array of byte arrays where each byte
+ * array encodes the name of a character references as
+ * ASCII. The names omit the first two letters of the
+ * name. (Since storing the first two letters would be
+ * redundant with the data contained in HILO_ACCEL.) The
+ * entries are lexically sorted.
+ *
+ * For a given index in NAMES, the same index in VALUES
+ * contains the corresponding expansion as an array of
+ * two UTF-16 code units (either the character and
+ * U+0000 or a suggogate pair).
+ */
+ int hilo = 0;
+ if (c <= 'z') {
+ @Const @NoLength int[] row = NamedCharactersAccel.HILO_ACCEL[c];
+ if (row != null) {
+ hilo = row[firstCharKey];
+ }
+ }
+ if (hilo == 0) {
+ /*
+ * If no match can be made, then this is a parse
+ * error.
+ */
+ errNoNamedCharacterMatch();
+ emitOrAppendCharRefBuf(returnState);
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ }
+ // Didn't fail yet
+ appendCharRefBuf(c);
+ lo = hilo & 0xFFFF;
+ hi = hilo >> 16;
+ entCol = -1;
+ candidate = -1;
+ charRefBufMark = 0;
+ state = transition(state, Tokenizer.CHARACTER_REFERENCE_TAIL, reconsume, pos);
+ // FALL THROUGH continue stateloop;
+ }
+ case CHARACTER_REFERENCE_TAIL:
+ outer: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ entCol++;
+ /*
+ * Consume the maximum number of characters possible,
+ * with the consumed characters matching one of the
+ * identifiers in the first column of the named
+ * character references table (in a case-sensitive
+ * manner).
+ */
+ loloop: for (;;) {
+ if (hi < lo) {
+ break outer;
+ }
+ if (entCol == NamedCharacters.NAMES[lo].length()) {
+ candidate = lo;
+ charRefBufMark = charRefBufLen;
+ lo++;
+ } else if (entCol > NamedCharacters.NAMES[lo].length()) {
+ break outer;
+ } else if (c > NamedCharacters.NAMES[lo].charAt(entCol)) {
+ lo++;
+ } else {
+ break loloop;
+ }
+ }
+
+ hiloop: for (;;) {
+ if (hi < lo) {
+ break outer;
+ }
+ if (entCol == NamedCharacters.NAMES[hi].length()) {
+ break hiloop;
+ }
+ if (entCol > NamedCharacters.NAMES[hi].length()) {
+ break outer;
+ } else if (c < NamedCharacters.NAMES[hi].charAt(entCol)) {
+ hi--;
+ } else {
+ break hiloop;
+ }
+ }
+
+ if (c == ';') {
+ // If we see a semicolon, there cannot be a
+ // longer match. Break the loop. However, before
+ // breaking, take the longest match so far as the
+ // candidate, if we are just about to complete a
+ // match.
+ if (entCol + 1 == NamedCharacters.NAMES[lo].length()) {
+ candidate = lo;
+ charRefBufMark = charRefBufLen;
+ }
+ break outer;
+ }
+
+ if (hi < lo) {
+ break outer;
+ }
+ appendCharRefBuf(c);
+ continue;
+ }
+
+ if (candidate == -1) {
+ // reconsume deals with CR, LF or nul
+ /*
+ * If no match can be made, then this is a parse error.
+ */
+ errNoNamedCharacterMatch();
+ emitOrAppendCharRefBuf(returnState);
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ } else {
+ // c can't be CR, LF or nul if we got here
+ @Const @CharacterName String candidateName = NamedCharacters.NAMES[candidate];
+ if (candidateName.length() == 0
+ || candidateName.charAt(candidateName.length() - 1) != ';') {
+ /*
+ * If the last character matched is not a U+003B
+ * SEMICOLON (;), there is a parse error.
+ */
+ if ((returnState & DATA_AND_RCDATA_MASK) != 0) {
+ /*
+ * If the entity is being consumed as part of an
+ * attribute, and the last character matched is
+ * not a U+003B SEMICOLON (;),
+ */
+ char ch;
+ if (charRefBufMark == charRefBufLen) {
+ ch = c;
+ } else {
+ ch = charRefBuf[charRefBufMark];
+ }
+ if (ch == '=' || (ch >= '0' && ch <= '9')
+ || (ch >= 'A' && ch <= 'Z')
+ || (ch >= 'a' && ch <= 'z')) {
+ /*
+ * and the next character is either a U+003D
+ * EQUALS SIGN character (=) or in the range
+ * U+0030 DIGIT ZERO to U+0039 DIGIT NINE,
+ * U+0041 LATIN CAPITAL LETTER A to U+005A
+ * LATIN CAPITAL LETTER Z, or U+0061 LATIN
+ * SMALL LETTER A to U+007A LATIN SMALL
+ * LETTER Z, then, for historical reasons,
+ * all the characters that were matched
+ * after the U+0026 AMPERSAND (&) must be
+ * unconsumed, and nothing is returned.
+ */
+ errNoNamedCharacterMatch();
+ appendCharRefBufToStrBuf();
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ if ((returnState & DATA_AND_RCDATA_MASK) != 0) {
+ errUnescapedAmpersandInterpretedAsCharacterReference();
+ } else {
+ errNotSemicolonTerminated();
+ }
+ }
+
+ /*
+ * Otherwise, return a character token for the character
+ * corresponding to the entity name (as given by the
+ * second column of the named character references
+ * table).
+ */
+ // CPPONLY: completedNamedCharacterReference();
+ @Const @NoLength char[] val = NamedCharacters.VALUES[candidate];
+ if (
+ // [NOCPP[
+ val.length == 1
+ // ]NOCPP]
+ // CPPONLY: val[1] == 0
+ ) {
+ emitOrAppendOne(val, returnState);
+ } else {
+ emitOrAppendTwo(val, returnState);
+ }
+ // this is so complicated!
+ if (charRefBufMark < charRefBufLen) {
+ if ((returnState & DATA_AND_RCDATA_MASK) != 0) {
+ appendStrBuf(charRefBuf, charRefBufMark,
+ charRefBufLen - charRefBufMark);
+ } else {
+ tokenHandler.characters(charRefBuf, charRefBufMark,
+ charRefBufLen - charRefBufMark);
+ }
+ }
+ // charRefBufLen will be zeroed below!
+
+ // Check if we broke out early with c being the last
+ // character that matched as opposed to being the
+ // first one that didn't match. In the case of an
+ // early break, the next run on text should start
+ // *after* the current character and the current
+ // character shouldn't be reconsumed.
+ boolean earlyBreak = (c == ';' && charRefBufMark == charRefBufLen);
+ charRefBufLen = 0;
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = earlyBreak ? pos + 1 : pos;
+ }
+ reconsume = !earlyBreak;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ /*
+ * If the markup contains I'm &notit; I tell you, the
+ * entity is parsed as "not", as in, I'm ¬it; I tell
+ * you. But if the markup was I'm &notin; I tell you,
+ * the entity would be parsed as "notin;", resulting in
+ * I'm ∉ I tell you.
+ */
+ }
+ // XXX reorder point
+ case CONSUME_NCR:
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ value = 0;
+ seenDigits = false;
+ /*
+ * The behavior further depends on the character after the
+ * U+0023 NUMBER SIGN:
+ */
+ switch (c) {
+ case 'x':
+ case 'X':
+
+ /*
+ * U+0078 LATIN SMALL LETTER X U+0058 LATIN CAPITAL
+ * LETTER X Consume the X.
+ *
+ * Follow the steps below, but using the range of
+ * characters U+0030 DIGIT ZERO through to U+0039
+ * DIGIT NINE, U+0061 LATIN SMALL LETTER A through
+ * to U+0066 LATIN SMALL LETTER F, and U+0041 LATIN
+ * CAPITAL LETTER A, through to U+0046 LATIN CAPITAL
+ * LETTER F (in other words, 0-9, A-F, a-f).
+ *
+ * When it comes to interpreting the number,
+ * interpret it as a hexadecimal number.
+ */
+ appendCharRefBuf(c);
+ state = transition(state, Tokenizer.HEX_NCR_LOOP, reconsume, pos);
+ continue stateloop;
+ default:
+ /*
+ * Anything else Follow the steps below, but using
+ * the range of characters U+0030 DIGIT ZERO through
+ * to U+0039 DIGIT NINE (i.e. just 0-9).
+ *
+ * When it comes to interpreting the number,
+ * interpret it as a decimal number.
+ */
+ reconsume = true;
+ state = transition(state, Tokenizer.DECIMAL_NRC_LOOP, reconsume, pos);
+ // FALL THROUGH continue stateloop;
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case DECIMAL_NRC_LOOP:
+ decimalloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume as many characters as match the range of
+ * characters given above.
+ */
+ assert value >= 0: "value must not become negative.";
+ if (c >= '0' && c <= '9') {
+ seenDigits = true;
+ // Avoid overflow
+ if (value <= 0x10FFFF) {
+ value *= 10;
+ value += c - '0';
+ }
+ continue;
+ } else if (c == ';') {
+ if (seenDigits) {
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos + 1;
+ }
+ state = transition(state, Tokenizer.HANDLE_NCR_VALUE, reconsume, pos);
+ // FALL THROUGH continue stateloop;
+ break decimalloop;
+ } else {
+ errNoDigitsInNCR();
+ appendCharRefBuf(';');
+ emitOrAppendCharRefBuf(returnState);
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos + 1;
+ }
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ }
+ } else {
+ /*
+ * If no characters match the range, then don't
+ * consume any characters (and unconsume the U+0023
+ * NUMBER SIGN character and, if appropriate, the X
+ * character). This is a parse error; nothing is
+ * returned.
+ *
+ * Otherwise, if the next character is a U+003B
+ * SEMICOLON, consume that too. If it isn't, there
+ * is a parse error.
+ */
+ if (!seenDigits) {
+ errNoDigitsInNCR();
+ emitOrAppendCharRefBuf(returnState);
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ } else {
+ errCharRefLacksSemicolon();
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = transition(state, Tokenizer.HANDLE_NCR_VALUE, reconsume, pos);
+ // FALL THROUGH continue stateloop;
+ break decimalloop;
+ }
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case HANDLE_NCR_VALUE:
+ // WARNING previous state sets reconsume
+ // We are not going to emit the contents of charRefBuf.
+ charRefBufLen = 0;
+ // XXX inline this case if the method size can take it
+ handleNcrValue(returnState);
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ // XXX reorder point
+ case HEX_NCR_LOOP:
+ for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume as many characters as match the range of
+ * characters given above.
+ */
+ assert value >= 0: "value must not become negative.";
+ if (c >= '0' && c <= '9') {
+ seenDigits = true;
+ // Avoid overflow
+ if (value <= 0x10FFFF) {
+ value *= 16;
+ value += c - '0';
+ }
+ continue;
+ } else if (c >= 'A' && c <= 'F') {
+ seenDigits = true;
+ // Avoid overflow
+ if (value <= 0x10FFFF) {
+ value *= 16;
+ value += c - 'A' + 10;
+ }
+ continue;
+ } else if (c >= 'a' && c <= 'f') {
+ seenDigits = true;
+ // Avoid overflow
+ if (value <= 0x10FFFF) {
+ value *= 16;
+ value += c - 'a' + 10;
+ }
+ continue;
+ } else if (c == ';') {
+ if (seenDigits) {
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos + 1;
+ }
+ state = transition(state, Tokenizer.HANDLE_NCR_VALUE, reconsume, pos);
+ continue stateloop;
+ } else {
+ errNoDigitsInNCR();
+ appendCharRefBuf(';');
+ emitOrAppendCharRefBuf(returnState);
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos + 1;
+ }
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ }
+ } else {
+ /*
+ * If no characters match the range, then don't
+ * consume any characters (and unconsume the U+0023
+ * NUMBER SIGN character and, if appropriate, the X
+ * character). This is a parse error; nothing is
+ * returned.
+ *
+ * Otherwise, if the next character is a U+003B
+ * SEMICOLON, consume that too. If it isn't, there
+ * is a parse error.
+ */
+ if (!seenDigits) {
+ errNoDigitsInNCR();
+ emitOrAppendCharRefBuf(returnState);
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ } else {
+ errCharRefLacksSemicolon();
+ if ((returnState & DATA_AND_RCDATA_MASK) == 0) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = transition(state, Tokenizer.HANDLE_NCR_VALUE, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ }
+ // XXX reorder point
+ case PLAINTEXT:
+ plaintextloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ switch (c) {
+ case '\u0000':
+ emitPlaintextReplacementCharacter(buf, pos);
+ continue;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Anything else Emit the current input
+ * character as a character token. Stay in the
+ * RAWTEXT state.
+ */
+ continue;
+ }
+ }
+ // XXX reorder point
+ case CLOSE_TAG_OPEN:
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Otherwise, if the content model flag is set to the PCDATA
+ * state, or if the next few characters do match that tag
+ * name, consume the next input character:
+ */
+ switch (c) {
+ case '>':
+ /* U+003E GREATER-THAN SIGN (>) Parse error. */
+ errLtSlashGt();
+ /*
+ * Switch to the data state.
+ */
+ cstart = pos + 1;
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ silentCarriageReturn();
+ /* Anything else Parse error. */
+ errGarbageAfterLtSlash();
+ /*
+ * Switch to the bogus comment state.
+ */
+ clearStrBufBeforeUse();
+ appendStrBuf('\n');
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ /* Anything else Parse error. */
+ errGarbageAfterLtSlash();
+ /*
+ * Switch to the bogus comment state.
+ */
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ if (c >= 'a' && c <= 'z') {
+ /*
+ * U+0061 LATIN SMALL LETTER A through to U+007A
+ * LATIN SMALL LETTER Z Create a new end tag
+ * token,
+ */
+ endTag = true;
+ /*
+ * set its tag name to the input character,
+ */
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ /*
+ * then switch to the tag name state. (Don't
+ * emit the token yet; further details will be
+ * filled in before it is emitted.)
+ */
+ state = transition(state, Tokenizer.TAG_NAME, reconsume, pos);
+ continue stateloop;
+ } else {
+ /* Anything else Parse error. */
+ errGarbageAfterLtSlash();
+ /*
+ * Switch to the bogus comment state.
+ */
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // XXX reorder point
+ case RCDATA:
+ rcdataloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ switch (c) {
+ case '&':
+ /*
+ * U+0026 AMPERSAND (&) Switch to the character
+ * reference in RCDATA state.
+ */
+ flushChars(buf, pos);
+ assert charRefBufLen == 0: "charRefBufLen not reset after previous use!";
+ appendCharRefBuf(c);
+ setAdditionalAndRememberAmpersandLocation('\u0000');
+ returnState = state;
+ state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos);
+ continue stateloop;
+ case '<':
+ /*
+ * U+003C LESS-THAN SIGN (<) Switch to the
+ * RCDATA less-than sign state.
+ */
+ flushChars(buf, pos);
+
+ returnState = state;
+ state = transition(state, Tokenizer.RAWTEXT_RCDATA_LESS_THAN_SIGN, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ continue;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Emit the current input character as a
+ * character token. Stay in the RCDATA state.
+ */
+ continue;
+ }
+ }
+ // XXX reorder point
+ case RAWTEXT:
+ rawtextloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ switch (c) {
+ case '<':
+ /*
+ * U+003C LESS-THAN SIGN (<) Switch to the
+ * RAWTEXT less-than sign state.
+ */
+ flushChars(buf, pos);
+
+ returnState = state;
+ state = transition(state, Tokenizer.RAWTEXT_RCDATA_LESS_THAN_SIGN, reconsume, pos);
+ break rawtextloop;
+ // FALL THRU continue stateloop;
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ continue;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Emit the current input character as a
+ * character token. Stay in the RAWTEXT state.
+ */
+ continue;
+ }
+ }
+ // XXX fallthru don't reorder
+ case RAWTEXT_RCDATA_LESS_THAN_SIGN:
+ rawtextrcdatalessthansignloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ switch (c) {
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) Set the temporary buffer
+ * to the empty string. Switch to the script
+ * data end tag open state.
+ */
+ index = 0;
+ clearStrBufBeforeUse();
+ state = transition(state, Tokenizer.NON_DATA_END_TAG_NAME, reconsume, pos);
+ break rawtextrcdatalessthansignloop;
+ // FALL THRU continue stateloop;
+ default:
+ /*
+ * Otherwise, emit a U+003C LESS-THAN SIGN
+ * character token
+ */
+ tokenHandler.characters(Tokenizer.LT_GT, 0, 1);
+ /*
+ * and reconsume the current input character in
+ * the data state.
+ */
+ cstart = pos;
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // XXX fall thru. don't reorder.
+ case NON_DATA_END_TAG_NAME:
+ for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * ASSERT! when entering this state, set index to 0 and
+ * call clearStrBufBeforeUse(); Let's implement the above
+ * without lookahead. strBuf is the 'temporary buffer'.
+ */
+ if (endTagExpectationAsArray == null) {
+ tokenHandler.characters(Tokenizer.LT_SOLIDUS,
+ 0, 2);
+ cstart = pos;
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ } else if (index < endTagExpectationAsArray.length) {
+ char e = endTagExpectationAsArray[index];
+ char folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded != e) {
+ // [NOCPP[
+ errHtml4LtSlashInRcdata(folded);
+ // ]NOCPP]
+ tokenHandler.characters(Tokenizer.LT_SOLIDUS,
+ 0, 2);
+ emitStrBuf();
+ cstart = pos;
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ }
+ appendStrBuf(c);
+ index++;
+ continue;
+ } else {
+ endTag = true;
+ // XXX replace contentModelElement with different
+ // type
+ tagName = endTagExpectation;
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ clearStrBufAfterUse(); // strBuf not used
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE
+ * FEED (LF) U+000C FORM FEED (FF) U+0020
+ * SPACE If the current end tag token is an
+ * appropriate end tag token, then switch to
+ * the before attribute name state.
+ */
+ clearStrBufAfterUse(); // strBuf not used
+ state = transition(state, Tokenizer.BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ continue stateloop;
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) If the current end tag
+ * token is an appropriate end tag token,
+ * then switch to the self-closing start tag
+ * state.
+ */
+ clearStrBufAfterUse(); // strBuf not used
+ state = transition(state, Tokenizer.SELF_CLOSING_START_TAG, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) If the
+ * current end tag token is an appropriate
+ * end tag token, then emit the current tag
+ * token and switch to the data state.
+ */
+ clearStrBufAfterUse(); // strBuf not used
+ state = transition(state, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ break stateloop;
+ }
+ continue stateloop;
+ default:
+ /*
+ * Emit a U+003C LESS-THAN SIGN character
+ * token, a U+002F SOLIDUS character token,
+ * a character token for each of the
+ * characters in the temporary buffer (in
+ * the order they were added to the buffer),
+ * and reconsume the current input character
+ * in the RAWTEXT state.
+ */
+ // [NOCPP[
+ errWarnLtSlashInRcdata();
+ // ]NOCPP]
+ tokenHandler.characters(
+ Tokenizer.LT_SOLIDUS, 0, 2);
+ emitStrBuf();
+ cstart = pos; // don't drop the
+ // character
+ reconsume = true;
+ state = transition(state, returnState, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ }
+ // XXX reorder point
+ // BEGIN HOTSPOT WORKAROUND
+ case BOGUS_COMMENT:
+ boguscommentloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume every character up to and including the first
+ * U+003E GREATER-THAN SIGN character (>) or the end of
+ * the file (EOF), whichever comes first. Emit a comment
+ * token whose data is the concatenation of all the
+ * characters starting from and including the character
+ * that caused the state machine to switch into the
+ * bogus comment state, up to and including the
+ * character immediately before the last consumed
+ * character (i.e. up to the character just before the
+ * U+003E or EOF character). (If the comment was started
+ * by the end of the file (EOF), the token is empty.)
+ *
+ * Switch to the data state.
+ *
+ * If the end of the file was reached, reconsume the EOF
+ * character.
+ */
+ switch (c) {
+ case '>':
+ emitComment(0, pos);
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '-':
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.BOGUS_COMMENT_HYPHEN, reconsume, pos);
+ break boguscommentloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ continue;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case BOGUS_COMMENT_HYPHEN:
+ boguscommenthyphenloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ switch (c) {
+ case '>':
+ // [NOCPP[
+ maybeAppendSpaceToBogusComment();
+ // ]NOCPP]
+ emitComment(0, pos);
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '-':
+ appendSecondHyphenToBogusComment();
+ continue boguscommenthyphenloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ appendStrBuf(c);
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // XXX reorder point
+ case SCRIPT_DATA:
+ scriptdataloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ switch (c) {
+ case '<':
+ /*
+ * U+003C LESS-THAN SIGN (<) Switch to the
+ * script data less-than sign state.
+ */
+ flushChars(buf, pos);
+ returnState = state;
+ state = transition(state, Tokenizer.SCRIPT_DATA_LESS_THAN_SIGN, reconsume, pos);
+ break scriptdataloop; // FALL THRU continue
+ // stateloop;
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ continue;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Anything else Emit the current input
+ * character as a character token. Stay in the
+ * script data state.
+ */
+ continue;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_LESS_THAN_SIGN:
+ scriptdatalessthansignloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ switch (c) {
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) Set the temporary buffer
+ * to the empty string. Switch to the script
+ * data end tag open state.
+ */
+ index = 0;
+ clearStrBufBeforeUse();
+ state = transition(state, Tokenizer.NON_DATA_END_TAG_NAME, reconsume, pos);
+ continue stateloop;
+ case '!':
+ tokenHandler.characters(Tokenizer.LT_GT, 0, 1);
+ cstart = pos;
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPE_START, reconsume, pos);
+ break scriptdatalessthansignloop; // FALL THRU
+ // continue
+ // stateloop;
+ default:
+ /*
+ * Otherwise, emit a U+003C LESS-THAN SIGN
+ * character token
+ */
+ tokenHandler.characters(Tokenizer.LT_GT, 0, 1);
+ /*
+ * and reconsume the current input character in
+ * the data state.
+ */
+ cstart = pos;
+ reconsume = true;
+ state = transition(state, Tokenizer.SCRIPT_DATA, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_ESCAPE_START:
+ scriptdataescapestartloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Emit a U+002D
+ * HYPHEN-MINUS character token. Switch to the
+ * script data escape start dash state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPE_START_DASH, reconsume, pos);
+ break scriptdataescapestartloop; // FALL THRU
+ // continue
+ // stateloop;
+ default:
+ /*
+ * Anything else Reconsume the current input
+ * character in the script data state.
+ */
+ reconsume = true;
+ state = transition(state, Tokenizer.SCRIPT_DATA, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_ESCAPE_START_DASH:
+ scriptdataescapestartdashloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Emit a U+002D
+ * HYPHEN-MINUS character token. Switch to the
+ * script data escaped dash dash state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_DASH_DASH, reconsume, pos);
+ break scriptdataescapestartdashloop;
+ // continue stateloop;
+ default:
+ /*
+ * Anything else Reconsume the current input
+ * character in the script data state.
+ */
+ reconsume = true;
+ state = transition(state, Tokenizer.SCRIPT_DATA, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_ESCAPED_DASH_DASH:
+ scriptdataescapeddashdashloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Emit a U+002D
+ * HYPHEN-MINUS character token. Stay in the
+ * script data escaped dash dash state.
+ */
+ continue;
+ case '<':
+ /*
+ * U+003C LESS-THAN SIGN (<) Switch to the
+ * script data escaped less-than sign state.
+ */
+ flushChars(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit a U+003E
+ * GREATER-THAN SIGN character token. Switch to
+ * the script data state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ break scriptdataescapeddashdashloop;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Anything else Emit the current input
+ * character as a character token. Switch to the
+ * script data escaped state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ break scriptdataescapeddashdashloop;
+ // continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_ESCAPED:
+ scriptdataescapedloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Emit a U+002D
+ * HYPHEN-MINUS character token. Switch to the
+ * script data escaped dash state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_DASH, reconsume, pos);
+ break scriptdataescapedloop; // FALL THRU
+ // continue
+ // stateloop;
+ case '<':
+ /*
+ * U+003C LESS-THAN SIGN (<) Switch to the
+ * script data escaped less-than sign state.
+ */
+ flushChars(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ continue;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Anything else Emit the current input
+ * character as a character token. Stay in the
+ * script data escaped state.
+ */
+ continue;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_ESCAPED_DASH:
+ scriptdataescapeddashloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Emit a U+002D
+ * HYPHEN-MINUS character token. Switch to the
+ * script data escaped dash dash state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_DASH_DASH, reconsume, pos);
+ continue stateloop;
+ case '<':
+ /*
+ * U+003C LESS-THAN SIGN (<) Switch to the
+ * script data escaped less-than sign state.
+ */
+ flushChars(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ break scriptdataescapeddashloop;
+ // continue stateloop;
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Anything else Emit the current input
+ * character as a character token. Switch to the
+ * script data escaped state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN:
+ scriptdataescapedlessthanloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) Set the temporary buffer
+ * to the empty string. Switch to the script
+ * data escaped end tag open state.
+ */
+ index = 0;
+ clearStrBufBeforeUse();
+ returnState = Tokenizer.SCRIPT_DATA_ESCAPED;
+ state = transition(state, Tokenizer.NON_DATA_END_TAG_NAME, reconsume, pos);
+ continue stateloop;
+ case 'S':
+ case 's':
+ /*
+ * U+0041 LATIN CAPITAL LETTER A through to
+ * U+005A LATIN CAPITAL LETTER Z Emit a U+003C
+ * LESS-THAN SIGN character token and the
+ * current input character as a character token.
+ */
+ tokenHandler.characters(Tokenizer.LT_GT, 0, 1);
+ cstart = pos;
+ index = 1;
+ /*
+ * Set the temporary buffer to the empty string.
+ * Append the lowercase version of the current
+ * input character (add 0x0020 to the
+ * character's code point) to the temporary
+ * buffer. Switch to the script data double
+ * escape start state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPE_START, reconsume, pos);
+ break scriptdataescapedlessthanloop;
+ // continue stateloop;
+ default:
+ /*
+ * Anything else Emit a U+003C LESS-THAN SIGN
+ * character token and reconsume the current
+ * input character in the script data escaped
+ * state.
+ */
+ tokenHandler.characters(Tokenizer.LT_GT, 0, 1);
+ cstart = pos;
+ reconsume = true;
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_DOUBLE_ESCAPE_START:
+ scriptdatadoubleescapestartloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ assert index > 0;
+ if (index < 6) { // SCRIPT_ARR.length
+ char folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded != Tokenizer.SCRIPT_ARR[index]) {
+ reconsume = true;
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ continue stateloop;
+ }
+ index++;
+ continue;
+ }
+ switch (c) {
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ case ' ':
+ case '\t':
+ case '\u000C':
+ case '/':
+ case '>':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * U+002F SOLIDUS (/) U+003E GREATER-THAN SIGN
+ * (>) Emit the current input character as a
+ * character token. If the temporary buffer is
+ * the string "script", then switch to the
+ * script data double escaped state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ break scriptdatadoubleescapestartloop;
+ // continue stateloop;
+ default:
+ /*
+ * Anything else Reconsume the current input
+ * character in the script data escaped state.
+ */
+ reconsume = true;
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_DOUBLE_ESCAPED:
+ scriptdatadoubleescapedloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Emit a U+002D
+ * HYPHEN-MINUS character token. Switch to the
+ * script data double escaped dash state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED_DASH, reconsume, pos);
+ break scriptdatadoubleescapedloop; // FALL THRU
+ // continue
+ // stateloop;
+ case '<':
+ /*
+ * U+003C LESS-THAN SIGN (<) Emit a U+003C
+ * LESS-THAN SIGN character token. Switch to the
+ * script data double escaped less-than sign
+ * state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ continue;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Anything else Emit the current input
+ * character as a character token. Stay in the
+ * script data double escaped state.
+ */
+ continue;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_DOUBLE_ESCAPED_DASH:
+ scriptdatadoubleescapeddashloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Emit a U+002D
+ * HYPHEN-MINUS character token. Switch to the
+ * script data double escaped dash dash state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH, reconsume, pos);
+ break scriptdatadoubleescapeddashloop;
+ // continue stateloop;
+ case '<':
+ /*
+ * U+003C LESS-THAN SIGN (<) Emit a U+003C
+ * LESS-THAN SIGN character token. Switch to the
+ * script data double escaped less-than sign
+ * state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Anything else Emit the current input
+ * character as a character token. Switch to the
+ * script data double escaped state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH:
+ scriptdatadoubleescapeddashdashloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '-':
+ /*
+ * U+002D HYPHEN-MINUS (-) Emit a U+002D
+ * HYPHEN-MINUS character token. Stay in the
+ * script data double escaped dash dash state.
+ */
+ continue;
+ case '<':
+ /*
+ * U+003C LESS-THAN SIGN (<) Emit a U+003C
+ * LESS-THAN SIGN character token. Switch to the
+ * script data double escaped less-than sign
+ * state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ break scriptdatadoubleescapeddashdashloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit a U+003E
+ * GREATER-THAN SIGN character token. Switch to
+ * the script data state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ emitReplacementCharacter(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ default:
+ /*
+ * Anything else Emit the current input
+ * character as a character token. Switch to the
+ * script data double escaped state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN:
+ scriptdatadoubleescapedlessthanloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '/':
+ /*
+ * U+002F SOLIDUS (/) Emit a U+002F SOLIDUS
+ * character token. Set the temporary buffer to
+ * the empty string. Switch to the script data
+ * double escape end state.
+ */
+ index = 0;
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPE_END, reconsume, pos);
+ break scriptdatadoubleescapedlessthanloop;
+ default:
+ /*
+ * Anything else Reconsume the current input
+ * character in the script data double escaped
+ * state.
+ */
+ reconsume = true;
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // WARNING FALLTHRU CASE TRANSITION: DON'T REORDER
+ case SCRIPT_DATA_DOUBLE_ESCAPE_END:
+ scriptdatadoubleescapeendloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ if (index < 6) { // SCRIPT_ARR.length
+ char folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded != Tokenizer.SCRIPT_ARR[index]) {
+ reconsume = true;
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ continue stateloop;
+ }
+ index++;
+ continue;
+ }
+ switch (c) {
+ case '\r':
+ emitCarriageReturn(buf, pos);
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ case ' ':
+ case '\t':
+ case '\u000C':
+ case '/':
+ case '>':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * U+002F SOLIDUS (/) U+003E GREATER-THAN SIGN
+ * (>) Emit the current input character as a
+ * character token. If the temporary buffer is
+ * the string "script", then switch to the
+ * script data escaped state.
+ */
+ state = transition(state, Tokenizer.SCRIPT_DATA_ESCAPED, reconsume, pos);
+ continue stateloop;
+ default:
+ /*
+ * Reconsume the current input character in the
+ * script data double escaped state.
+ */
+ reconsume = true;
+ state = transition(state, Tokenizer.SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // XXX reorder point
+ case MARKUP_DECLARATION_OCTYPE:
+ markupdeclarationdoctypeloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ if (index < 6) { // OCTYPE.length
+ char folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded == Tokenizer.OCTYPE[index]) {
+ appendStrBuf(c);
+ } else {
+ errBogusComment();
+ reconsume = true;
+ state = transition(state, Tokenizer.BOGUS_COMMENT, reconsume, pos);
+ continue stateloop;
+ }
+ index++;
+ continue;
+ } else {
+ reconsume = true;
+ state = transition(state, Tokenizer.DOCTYPE, reconsume, pos);
+ break markupdeclarationdoctypeloop;
+ // continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case DOCTYPE:
+ doctypeloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ initDoctypeFields();
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ state = transition(state, Tokenizer.BEFORE_DOCTYPE_NAME, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * Switch to the before DOCTYPE name state.
+ */
+ state = transition(state, Tokenizer.BEFORE_DOCTYPE_NAME, reconsume, pos);
+ break doctypeloop;
+ // continue stateloop;
+ default:
+ /*
+ * Anything else Parse error.
+ */
+ errMissingSpaceBeforeDoctypeName();
+ /*
+ * Reconsume the current character in the before
+ * DOCTYPE name state.
+ */
+ reconsume = true;
+ state = transition(state, Tokenizer.BEFORE_DOCTYPE_NAME, reconsume, pos);
+ break doctypeloop;
+ // continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case BEFORE_DOCTYPE_NAME:
+ beforedoctypenameloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay
+ * in the before DOCTYPE name state.
+ */
+ continue;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Parse error.
+ */
+ errNamelessDoctype();
+ /*
+ * Create a new DOCTYPE token. Set its
+ * force-quirks flag to on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit the token.
+ */
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ if (c >= 'A' && c <= 'Z') {
+ /*
+ * U+0041 LATIN CAPITAL LETTER A through to
+ * U+005A LATIN CAPITAL LETTER Z Create a
+ * new DOCTYPE token. Set the token's name
+ * to the lowercase version of the input
+ * character (add 0x0020 to the character's
+ * code point).
+ */
+ c += 0x20;
+ }
+ /* Anything else Create a new DOCTYPE token. */
+ /*
+ * Set the token's name name to the current
+ * input character.
+ */
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ /*
+ * Switch to the DOCTYPE name state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_NAME, reconsume, pos);
+ break beforedoctypenameloop;
+ // continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case DOCTYPE_NAME:
+ doctypenameloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ strBufToDoctypeName();
+ state = transition(state, Tokenizer.AFTER_DOCTYPE_NAME, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * Switch to the after DOCTYPE name state.
+ */
+ strBufToDoctypeName();
+ state = transition(state, Tokenizer.AFTER_DOCTYPE_NAME, reconsume, pos);
+ break doctypenameloop;
+ // continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * DOCTYPE token.
+ */
+ strBufToDoctypeName();
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * U+0041 LATIN CAPITAL LETTER A through to
+ * U+005A LATIN CAPITAL LETTER Z Append the
+ * lowercase version of the input character (add
+ * 0x0020 to the character's code point) to the
+ * current DOCTYPE token's name.
+ */
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x0020;
+ }
+ /*
+ * Anything else Append the current input
+ * character to the current DOCTYPE token's
+ * name.
+ */
+ appendStrBuf(c);
+ /*
+ * Stay in the DOCTYPE name state.
+ */
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case AFTER_DOCTYPE_NAME:
+ afterdoctypenameloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay
+ * in the after DOCTYPE name state.
+ */
+ continue;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * DOCTYPE token.
+ */
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case 'p':
+ case 'P':
+ index = 0;
+ state = transition(state, Tokenizer.DOCTYPE_UBLIC, reconsume, pos);
+ break afterdoctypenameloop;
+ // continue stateloop;
+ case 's':
+ case 'S':
+ index = 0;
+ state = transition(state, Tokenizer.DOCTYPE_YSTEM, reconsume, pos);
+ continue stateloop;
+ default:
+ /*
+ * Otherwise, this is the parse error.
+ */
+ bogusDoctype();
+
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ // done by bogusDoctype();
+ /*
+ * Switch to the bogus DOCTYPE state.
+ */
+ state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case DOCTYPE_UBLIC:
+ doctypeublicloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * If the six characters starting from the current input
+ * character are an ASCII case-insensitive match for the
+ * word "PUBLIC", then consume those characters and
+ * switch to the before DOCTYPE public identifier state.
+ */
+ if (index < 5) { // UBLIC.length
+ char folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded != Tokenizer.UBLIC[index]) {
+ bogusDoctype();
+ // forceQuirks = true;
+ reconsume = true;
+ state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos);
+ continue stateloop;
+ }
+ index++;
+ continue;
+ } else {
+ reconsume = true;
+ state = transition(state, Tokenizer.AFTER_DOCTYPE_PUBLIC_KEYWORD, reconsume, pos);
+ break doctypeublicloop;
+ // continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case AFTER_DOCTYPE_PUBLIC_KEYWORD:
+ afterdoctypepublickeywordloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ state = transition(state, Tokenizer.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * Switch to the before DOCTYPE public
+ * identifier state.
+ */
+ state = transition(state, Tokenizer.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos);
+ break afterdoctypepublickeywordloop;
+ // FALL THROUGH continue stateloop
+ case '"':
+ /*
+ * U+0022 QUOTATION MARK (") Parse Error.
+ */
+ errNoSpaceBetweenDoctypePublicKeywordAndQuote();
+ /*
+ * Set the DOCTYPE token's public identifier to
+ * the empty string (not missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE public identifier
+ * (double-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ continue stateloop;
+ case '\'':
+ /*
+ * U+0027 APOSTROPHE (') Parse Error.
+ */
+ errNoSpaceBetweenDoctypePublicKeywordAndQuote();
+ /*
+ * Set the DOCTYPE token's public identifier to
+ * the empty string (not missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE public identifier
+ * (single-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /* U+003E GREATER-THAN SIGN (>) Parse error. */
+ errExpectedPublicId();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ default:
+ bogusDoctype();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ // done by bogusDoctype();
+ /*
+ * Switch to the bogus DOCTYPE state.
+ */
+ state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case BEFORE_DOCTYPE_PUBLIC_IDENTIFIER:
+ beforedoctypepublicidentifierloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay
+ * in the before DOCTYPE public identifier
+ * state.
+ */
+ continue;
+ case '"':
+ /*
+ * U+0022 QUOTATION MARK (") Set the DOCTYPE
+ * token's public identifier to the empty string
+ * (not missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE public identifier
+ * (double-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ break beforedoctypepublicidentifierloop;
+ // continue stateloop;
+ case '\'':
+ /*
+ * U+0027 APOSTROPHE (') Set the DOCTYPE token's
+ * public identifier to the empty string (not
+ * missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE public identifier
+ * (single-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /* U+003E GREATER-THAN SIGN (>) Parse error. */
+ errExpectedPublicId();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ default:
+ bogusDoctype();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ // done by bogusDoctype();
+ /*
+ * Switch to the bogus DOCTYPE state.
+ */
+ state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED:
+ doctypepublicidentifierdoublequotedloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '"':
+ /*
+ * U+0022 QUOTATION MARK (") Switch to the after
+ * DOCTYPE public identifier state.
+ */
+ publicIdentifier = strBufToString();
+ state = transition(state, Tokenizer.AFTER_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos);
+ break doctypepublicidentifierdoublequotedloop;
+ // continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Parse error.
+ */
+ errGtInPublicId();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ publicIdentifier = strBufToString();
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ continue;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Anything else Append the current input
+ * character to the current DOCTYPE token's
+ * public identifier.
+ */
+ appendStrBuf(c);
+ /*
+ * Stay in the DOCTYPE public identifier
+ * (double-quoted) state.
+ */
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case AFTER_DOCTYPE_PUBLIC_IDENTIFIER:
+ afterdoctypepublicidentifierloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ state = transition(state, Tokenizer.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * Switch to the between DOCTYPE public and
+ * system identifiers state.
+ */
+ state = transition(state, Tokenizer.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS, reconsume, pos);
+ break afterdoctypepublicidentifierloop;
+ // continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * DOCTYPE token.
+ */
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '"':
+ /*
+ * U+0022 QUOTATION MARK (") Parse error.
+ */
+ errNoSpaceBetweenPublicAndSystemIds();
+ /*
+ * Set the DOCTYPE token's system identifier to
+ * the empty string (not missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE system identifier
+ * (double-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ continue stateloop;
+ case '\'':
+ /*
+ * U+0027 APOSTROPHE (') Parse error.
+ */
+ errNoSpaceBetweenPublicAndSystemIds();
+ /*
+ * Set the DOCTYPE token's system identifier to
+ * the empty string (not missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE system identifier
+ * (single-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ continue stateloop;
+ default:
+ bogusDoctype();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ // done by bogusDoctype();
+ /*
+ * Switch to the bogus DOCTYPE state.
+ */
+ state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS:
+ betweendoctypepublicandsystemidentifiersloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay
+ * in the between DOCTYPE public and system
+ * identifiers state.
+ */
+ continue;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * DOCTYPE token.
+ */
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '"':
+ /*
+ * U+0022 QUOTATION MARK (") Set the DOCTYPE
+ * token's system identifier to the empty string
+ * (not missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE system identifier
+ * (double-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ break betweendoctypepublicandsystemidentifiersloop;
+ // continue stateloop;
+ case '\'':
+ /*
+ * U+0027 APOSTROPHE (') Set the DOCTYPE token's
+ * system identifier to the empty string (not
+ * missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE system identifier
+ * (single-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ continue stateloop;
+ default:
+ bogusDoctype();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ // done by bogusDoctype();
+ /*
+ * Switch to the bogus DOCTYPE state.
+ */
+ state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED:
+ doctypesystemidentifierdoublequotedloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '"':
+ /*
+ * U+0022 QUOTATION MARK (") Switch to the after
+ * DOCTYPE system identifier state.
+ */
+ systemIdentifier = strBufToString();
+ state = transition(state, Tokenizer.AFTER_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Parse error.
+ */
+ errGtInSystemId();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ systemIdentifier = strBufToString();
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ continue;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Anything else Append the current input
+ * character to the current DOCTYPE token's
+ * system identifier.
+ */
+ appendStrBuf(c);
+ /*
+ * Stay in the DOCTYPE system identifier
+ * (double-quoted) state.
+ */
+ continue;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case AFTER_DOCTYPE_SYSTEM_IDENTIFIER:
+ afterdoctypesystemidentifierloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay
+ * in the after DOCTYPE system identifier state.
+ */
+ continue;
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit the current
+ * DOCTYPE token.
+ */
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ default:
+ /*
+ * Switch to the bogus DOCTYPE state. (This does
+ * not set the DOCTYPE token's force-quirks flag
+ * to on.)
+ */
+ bogusDoctypeWithoutQuirks();
+ state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos);
+ break afterdoctypesystemidentifierloop;
+ // continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case BOGUS_DOCTYPE:
+ for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '>':
+ /*
+ * U+003E GREATER-THAN SIGN (>) Emit that
+ * DOCTYPE token.
+ */
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ silentCarriageReturn();
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ default:
+ /*
+ * Anything else Stay in the bogus DOCTYPE
+ * state.
+ */
+ continue;
+ }
+ }
+ // XXX reorder point
+ case DOCTYPE_YSTEM:
+ doctypeystemloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Otherwise, if the six characters starting from the
+ * current input character are an ASCII case-insensitive
+ * match for the word "SYSTEM", then consume those
+ * characters and switch to the before DOCTYPE system
+ * identifier state.
+ */
+ if (index < 5) { // YSTEM.length
+ char folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded != Tokenizer.YSTEM[index]) {
+ bogusDoctype();
+ reconsume = true;
+ state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos);
+ continue stateloop;
+ }
+ index++;
+ continue stateloop;
+ } else {
+ reconsume = true;
+ state = transition(state, Tokenizer.AFTER_DOCTYPE_SYSTEM_KEYWORD, reconsume, pos);
+ break doctypeystemloop;
+ // continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case AFTER_DOCTYPE_SYSTEM_KEYWORD:
+ afterdoctypesystemkeywordloop: for (;;) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ }
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ state = transition(state, Tokenizer.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos);
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE
+ * Switch to the before DOCTYPE public
+ * identifier state.
+ */
+ state = transition(state, Tokenizer.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos);
+ break afterdoctypesystemkeywordloop;
+ // FALL THROUGH continue stateloop
+ case '"':
+ /*
+ * U+0022 QUOTATION MARK (") Parse Error.
+ */
+ errNoSpaceBetweenDoctypeSystemKeywordAndQuote();
+ /*
+ * Set the DOCTYPE token's system identifier to
+ * the empty string (not missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE public identifier
+ * (double-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ continue stateloop;
+ case '\'':
+ /*
+ * U+0027 APOSTROPHE (') Parse Error.
+ */
+ errNoSpaceBetweenDoctypeSystemKeywordAndQuote();
+ /*
+ * Set the DOCTYPE token's public identifier to
+ * the empty string (not missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE public identifier
+ * (single-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ continue stateloop;
+ case '>':
+ /* U+003E GREATER-THAN SIGN (>) Parse error. */
+ errExpectedPublicId();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ default:
+ bogusDoctype();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ // done by bogusDoctype();
+ /*
+ * Switch to the bogus DOCTYPE state.
+ */
+ state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case BEFORE_DOCTYPE_SYSTEM_IDENTIFIER:
+ beforedoctypesystemidentifierloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\r':
+ silentCarriageReturn();
+ break stateloop;
+ case '\n':
+ silentLineFeed();
+ // fall thru
+ case ' ':
+ case '\t':
+ case '\u000C':
+ /*
+ * U+0009 CHARACTER TABULATION U+000A LINE FEED
+ * (LF) U+000C FORM FEED (FF) U+0020 SPACE Stay
+ * in the before DOCTYPE system identifier
+ * state.
+ */
+ continue;
+ case '"':
+ /*
+ * U+0022 QUOTATION MARK (") Set the DOCTYPE
+ * token's system identifier to the empty string
+ * (not missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE system identifier
+ * (double-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ continue stateloop;
+ case '\'':
+ /*
+ * U+0027 APOSTROPHE (') Set the DOCTYPE token's
+ * system identifier to the empty string (not
+ * missing),
+ */
+ clearStrBufBeforeUse();
+ /*
+ * then switch to the DOCTYPE system identifier
+ * (single-quoted) state.
+ */
+ state = transition(state, Tokenizer.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ break beforedoctypesystemidentifierloop;
+ // continue stateloop;
+ case '>':
+ /* U+003E GREATER-THAN SIGN (>) Parse error. */
+ errExpectedSystemId();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ default:
+ bogusDoctype();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ // done by bogusDoctype();
+ /*
+ * Switch to the bogus DOCTYPE state.
+ */
+ state = transition(state, Tokenizer.BOGUS_DOCTYPE, reconsume, pos);
+ continue stateloop;
+ }
+ }
+ // FALLTHRU DON'T REORDER
+ case DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED:
+ for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\'':
+ /*
+ * U+0027 APOSTROPHE (') Switch to the after
+ * DOCTYPE system identifier state.
+ */
+ systemIdentifier = strBufToString();
+ state = transition(state, Tokenizer.AFTER_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos);
+ continue stateloop;
+ case '>':
+ errGtInSystemId();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ systemIdentifier = strBufToString();
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ continue;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Anything else Append the current input
+ * character to the current DOCTYPE token's
+ * system identifier.
+ */
+ appendStrBuf(c);
+ /*
+ * Stay in the DOCTYPE system identifier
+ * (double-quoted) state.
+ */
+ continue;
+ }
+ }
+ // XXX reorder point
+ case DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED:
+ for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ /*
+ * Consume the next input character:
+ */
+ switch (c) {
+ case '\'':
+ /*
+ * U+0027 APOSTROPHE (') Switch to the after
+ * DOCTYPE public identifier state.
+ */
+ publicIdentifier = strBufToString();
+ state = transition(state, Tokenizer.AFTER_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos);
+ continue stateloop;
+ case '>':
+ errGtInPublicId();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to
+ * on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ publicIdentifier = strBufToString();
+ emitDoctypeToken(pos);
+ /*
+ * Switch to the data state.
+ */
+ state = transition(state, Tokenizer.DATA, reconsume, pos);
+ continue stateloop;
+ case '\r':
+ appendStrBufCarriageReturn();
+ break stateloop;
+ case '\n':
+ appendStrBufLineFeed();
+ continue;
+ case '\u0000':
+ c = '\uFFFD';
+ // fall thru
+ default:
+ /*
+ * Anything else Append the current input
+ * character to the current DOCTYPE token's
+ * public identifier.
+ */
+ appendStrBuf(c);
+ /*
+ * Stay in the DOCTYPE public identifier
+ * (single-quoted) state.
+ */
+ continue;
+ }
+ }
+ // XXX reorder point
+ case PROCESSING_INSTRUCTION:
+ processinginstructionloop: for (;;) {
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ switch (c) {
+ case '?':
+ state = transition(
+ state,
+ Tokenizer.PROCESSING_INSTRUCTION_QUESTION_MARK,
+ reconsume, pos);
+ break processinginstructionloop;
+ // continue stateloop;
+ default:
+ continue;
+ }
+ }
+ case PROCESSING_INSTRUCTION_QUESTION_MARK:
+ if (++pos == endPos) {
+ break stateloop;
+ }
+ c = checkChar(buf, pos);
+ switch (c) {
+ case '>':
+ state = transition(state, Tokenizer.DATA,
+ reconsume, pos);
+ continue stateloop;
+ default:
+ state = transition(state,
+ Tokenizer.PROCESSING_INSTRUCTION,
+ reconsume, pos);
+ continue stateloop;
+ }
+ // END HOTSPOT WORKAROUND
+ }
+ }
+ flushChars(buf, pos);
+ /*
+ * if (prevCR && pos != endPos) { // why is this needed? pos--; col--; }
+ */
+ // Save locals
+ stateSave = state;
+ returnStateSave = returnState;
+ return pos;
+ }
+
+ // HOTSPOT WORKAROUND INSERTION POINT
+
+ // [NOCPP[
+
+ protected int transition(int from, int to, boolean reconsume, int pos) throws SAXException {
+ return to;
+ }
+
+ // ]NOCPP]
+
+ private void initDoctypeFields() {
+ // Discard the characters "DOCTYPE" accumulated as a potential bogus
+ // comment into strBuf.
+ clearStrBufAfterUse();
+ doctypeName = "";
+ if (systemIdentifier != null) {
+ Portability.releaseString(systemIdentifier);
+ systemIdentifier = null;
+ }
+ if (publicIdentifier != null) {
+ Portability.releaseString(publicIdentifier);
+ publicIdentifier = null;
+ }
+ forceQuirks = false;
+ }
+
+ @Inline private void adjustDoubleHyphenAndAppendToStrBufCarriageReturn()
+ throws SAXException {
+ silentCarriageReturn();
+ adjustDoubleHyphenAndAppendToStrBufAndErr('\n');
+ }
+
+ @Inline private void adjustDoubleHyphenAndAppendToStrBufLineFeed()
+ throws SAXException {
+ silentLineFeed();
+ adjustDoubleHyphenAndAppendToStrBufAndErr('\n');
+ }
+
+ @Inline private void appendStrBufLineFeed() {
+ silentLineFeed();
+ appendStrBuf('\n');
+ }
+
+ @Inline private void appendStrBufCarriageReturn() {
+ silentCarriageReturn();
+ appendStrBuf('\n');
+ }
+
+ @Inline protected void silentCarriageReturn() {
+ ++line;
+ lastCR = true;
+ }
+
+ @Inline protected void silentLineFeed() {
+ ++line;
+ }
+
+ private void emitCarriageReturn(@NoLength char[] buf, int pos)
+ throws SAXException {
+ silentCarriageReturn();
+ flushChars(buf, pos);
+ tokenHandler.characters(Tokenizer.LF, 0, 1);
+ cstart = Integer.MAX_VALUE;
+ }
+
+ private void emitReplacementCharacter(@NoLength char[] buf, int pos)
+ throws SAXException {
+ flushChars(buf, pos);
+ tokenHandler.zeroOriginatingReplacementCharacter();
+ cstart = pos + 1;
+ }
+
+ private void emitPlaintextReplacementCharacter(@NoLength char[] buf, int pos)
+ throws SAXException {
+ flushChars(buf, pos);
+ tokenHandler.characters(REPLACEMENT_CHARACTER, 0, 1);
+ cstart = pos + 1;
+ }
+
+ private void setAdditionalAndRememberAmpersandLocation(char add) {
+ additional = add;
+ // [NOCPP[
+ ampersandLocation = new LocatorImpl(this);
+ // ]NOCPP]
+ }
+
+ private void bogusDoctype() throws SAXException {
+ errBogusDoctype();
+ forceQuirks = true;
+ }
+
+ private void bogusDoctypeWithoutQuirks() throws SAXException {
+ errBogusDoctype();
+ forceQuirks = false;
+ }
+
+ private void handleNcrValue(int returnState) throws SAXException {
+ /*
+ * If one or more characters match the range, then take them all and
+ * interpret the string of characters as a number (either hexadecimal or
+ * decimal as appropriate).
+ */
+ if (value <= 0xFFFF) {
+ if (value >= 0x80 && value <= 0x9f) {
+ /*
+ * If that number is one of the numbers in the first column of
+ * the following table, then this is a parse error.
+ */
+ errNcrInC1Range();
+ /*
+ * Find the row with that number in the first column, and return
+ * a character token for the Unicode character given in the
+ * second column of that row.
+ */
+ @NoLength char[] val = NamedCharacters.WINDOWS_1252[value - 0x80];
+ emitOrAppendOne(val, returnState);
+ // [NOCPP[
+ } else if (value == 0xC
+ && contentSpacePolicy != XmlViolationPolicy.ALLOW) {
+ if (contentSpacePolicy == XmlViolationPolicy.ALTER_INFOSET) {
+ emitOrAppendOne(Tokenizer.SPACE, returnState);
+ } else if (contentSpacePolicy == XmlViolationPolicy.FATAL) {
+ fatal("A character reference expanded to a form feed which is not legal XML 1.0 white space.");
+ }
+ // ]NOCPP]
+ } else if (value == 0x0) {
+ errNcrZero();
+ emitOrAppendOne(Tokenizer.REPLACEMENT_CHARACTER, returnState);
+ } else if ((value & 0xF800) == 0xD800) {
+ errNcrSurrogate();
+ emitOrAppendOne(Tokenizer.REPLACEMENT_CHARACTER, returnState);
+ } else {
+ /*
+ * Otherwise, return a character token for the Unicode character
+ * whose code point is that number.
+ */
+ char ch = (char) value;
+ // [NOCPP[
+ if (value == 0x0D) {
+ errNcrCr();
+ } else if ((value <= 0x0008) || (value == 0x000B)
+ || (value >= 0x000E && value <= 0x001F)) {
+ ch = errNcrControlChar(ch);
+ } else if (value >= 0xFDD0 && value <= 0xFDEF) {
+ errNcrUnassigned();
+ } else if ((value & 0xFFFE) == 0xFFFE) {
+ ch = errNcrNonCharacter(ch);
+ } else if (value >= 0x007F && value <= 0x009F) {
+ errNcrControlChar();
+ } else {
+ maybeWarnPrivateUse(ch);
+ }
+ // ]NOCPP]
+ bmpChar[0] = ch;
+ emitOrAppendOne(bmpChar, returnState);
+ }
+ } else if (value <= 0x10FFFF) {
+ // [NOCPP[
+ maybeWarnPrivateUseAstral();
+ if ((value & 0xFFFE) == 0xFFFE) {
+ errAstralNonCharacter(value);
+ }
+ // ]NOCPP]
+ astralChar[0] = (char) (Tokenizer.LEAD_OFFSET + (value >> 10));
+ astralChar[1] = (char) (0xDC00 + (value & 0x3FF));
+ emitOrAppendTwo(astralChar, returnState);
+ } else {
+ errNcrOutOfRange();
+ emitOrAppendOne(Tokenizer.REPLACEMENT_CHARACTER, returnState);
+ }
+ }
+
+ public void eof() throws SAXException {
+ int state = stateSave;
+ int returnState = returnStateSave;
+
+ eofloop: for (;;) {
+ switch (state) {
+ case SCRIPT_DATA_LESS_THAN_SIGN:
+ case SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN:
+ /*
+ * Otherwise, emit a U+003C LESS-THAN SIGN character token
+ */
+ tokenHandler.characters(Tokenizer.LT_GT, 0, 1);
+ /*
+ * and reconsume the current input character in the data
+ * state.
+ */
+ break eofloop;
+ case TAG_OPEN:
+ /*
+ * The behavior of this state depends on the content model
+ * flag.
+ */
+ /*
+ * Anything else Parse error.
+ */
+ errEofAfterLt();
+ /*
+ * Emit a U+003C LESS-THAN SIGN character token
+ */
+ tokenHandler.characters(Tokenizer.LT_GT, 0, 1);
+ /*
+ * and reconsume the current input character in the data
+ * state.
+ */
+ break eofloop;
+ case RAWTEXT_RCDATA_LESS_THAN_SIGN:
+ /*
+ * Emit a U+003C LESS-THAN SIGN character token
+ */
+ tokenHandler.characters(Tokenizer.LT_GT, 0, 1);
+ /*
+ * and reconsume the current input character in the RCDATA
+ * state.
+ */
+ break eofloop;
+ case NON_DATA_END_TAG_NAME:
+ /*
+ * Emit a U+003C LESS-THAN SIGN character token, a U+002F
+ * SOLIDUS character token,
+ */
+ tokenHandler.characters(Tokenizer.LT_SOLIDUS, 0, 2);
+ /*
+ * a character token for each of the characters in the
+ * temporary buffer (in the order they were added to the
+ * buffer),
+ */
+ emitStrBuf();
+ /*
+ * and reconsume the current input character in the RCDATA
+ * state.
+ */
+ break eofloop;
+ case CLOSE_TAG_OPEN:
+ /* EOF Parse error. */
+ errEofAfterLt();
+ /*
+ * Emit a U+003C LESS-THAN SIGN character token and a U+002F
+ * SOLIDUS character token.
+ */
+ tokenHandler.characters(Tokenizer.LT_SOLIDUS, 0, 2);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case TAG_NAME:
+ /*
+ * EOF Parse error.
+ */
+ errEofInTagName();
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case BEFORE_ATTRIBUTE_NAME:
+ case AFTER_ATTRIBUTE_VALUE_QUOTED:
+ case SELF_CLOSING_START_TAG:
+ /* EOF Parse error. */
+ errEofWithoutGt();
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case ATTRIBUTE_NAME:
+ /*
+ * EOF Parse error.
+ */
+ errEofInAttributeName();
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case AFTER_ATTRIBUTE_NAME:
+ case BEFORE_ATTRIBUTE_VALUE:
+ /* EOF Parse error. */
+ errEofWithoutGt();
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case ATTRIBUTE_VALUE_DOUBLE_QUOTED:
+ case ATTRIBUTE_VALUE_SINGLE_QUOTED:
+ case ATTRIBUTE_VALUE_UNQUOTED:
+ /* EOF Parse error. */
+ errEofInAttributeValue();
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case BOGUS_COMMENT:
+ emitComment(0, 0);
+ break eofloop;
+ case BOGUS_COMMENT_HYPHEN:
+ // [NOCPP[
+ maybeAppendSpaceToBogusComment();
+ // ]NOCPP]
+ emitComment(0, 0);
+ break eofloop;
+ case MARKUP_DECLARATION_OPEN:
+ errBogusComment();
+ emitComment(0, 0);
+ break eofloop;
+ case MARKUP_DECLARATION_HYPHEN:
+ errBogusComment();
+ emitComment(0, 0);
+ break eofloop;
+ case MARKUP_DECLARATION_OCTYPE:
+ if (index < 6) {
+ errBogusComment();
+ emitComment(0, 0);
+ } else {
+ /* EOF Parse error. */
+ errEofInDoctype();
+ /*
+ * Create a new DOCTYPE token. Set its force-quirks flag
+ * to on.
+ */
+ doctypeName = "";
+ if (systemIdentifier != null) {
+ Portability.releaseString(systemIdentifier);
+ systemIdentifier = null;
+ }
+ if (publicIdentifier != null) {
+ Portability.releaseString(publicIdentifier);
+ publicIdentifier = null;
+ }
+ forceQuirks = true;
+ /*
+ * Emit the token.
+ */
+ emitDoctypeToken(0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ }
+ break eofloop;
+ case COMMENT_START:
+ case COMMENT:
+ /*
+ * EOF Parse error.
+ */
+ errEofInComment();
+ /* Emit the comment token. */
+ emitComment(0, 0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case COMMENT_END:
+ errEofInComment();
+ /* Emit the comment token. */
+ emitComment(2, 0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case COMMENT_END_DASH:
+ case COMMENT_START_DASH:
+ errEofInComment();
+ /* Emit the comment token. */
+ emitComment(1, 0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case COMMENT_END_BANG:
+ errEofInComment();
+ /* Emit the comment token. */
+ emitComment(3, 0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case DOCTYPE:
+ case BEFORE_DOCTYPE_NAME:
+ errEofInDoctype();
+ /*
+ * Create a new DOCTYPE token. Set its force-quirks flag to
+ * on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit the token.
+ */
+ emitDoctypeToken(0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case DOCTYPE_NAME:
+ errEofInDoctype();
+ strBufToDoctypeName();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ emitDoctypeToken(0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case DOCTYPE_UBLIC:
+ case DOCTYPE_YSTEM:
+ case AFTER_DOCTYPE_NAME:
+ case AFTER_DOCTYPE_PUBLIC_KEYWORD:
+ case AFTER_DOCTYPE_SYSTEM_KEYWORD:
+ case BEFORE_DOCTYPE_PUBLIC_IDENTIFIER:
+ errEofInDoctype();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ emitDoctypeToken(0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED:
+ case DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED:
+ /* EOF Parse error. */
+ errEofInPublicId();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ publicIdentifier = strBufToString();
+ emitDoctypeToken(0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case AFTER_DOCTYPE_PUBLIC_IDENTIFIER:
+ case BEFORE_DOCTYPE_SYSTEM_IDENTIFIER:
+ case BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS:
+ errEofInDoctype();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ emitDoctypeToken(0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED:
+ case DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED:
+ /* EOF Parse error. */
+ errEofInSystemId();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ systemIdentifier = strBufToString();
+ emitDoctypeToken(0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case AFTER_DOCTYPE_SYSTEM_IDENTIFIER:
+ errEofInDoctype();
+ /*
+ * Set the DOCTYPE token's force-quirks flag to on.
+ */
+ forceQuirks = true;
+ /*
+ * Emit that DOCTYPE token.
+ */
+ emitDoctypeToken(0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case BOGUS_DOCTYPE:
+ /*
+ * Emit that DOCTYPE token.
+ */
+ emitDoctypeToken(0);
+ /*
+ * Reconsume the EOF character in the data state.
+ */
+ break eofloop;
+ case CONSUME_CHARACTER_REFERENCE:
+ /*
+ * Unlike the definition is the spec, this state does not
+ * return a value and never requires the caller to
+ * backtrack. This state takes care of emitting characters
+ * or appending to the current attribute value. It also
+ * takes care of that in the case when consuming the entity
+ * fails.
+ */
+ /*
+ * This section defines how to consume an entity. This
+ * definition is used when parsing entities in text and in
+ * attributes.
+ *
+ * The behavior depends on the identity of the next
+ * character (the one immediately after the U+0026 AMPERSAND
+ * character):
+ */
+
+ emitOrAppendCharRefBuf(returnState);
+ state = returnState;
+ continue;
+ case CHARACTER_REFERENCE_HILO_LOOKUP:
+ errNoNamedCharacterMatch();
+ emitOrAppendCharRefBuf(returnState);
+ state = returnState;
+ continue;
+ case CHARACTER_REFERENCE_TAIL:
+ outer: for (;;) {
+ char c = '\u0000';
+ entCol++;
+ /*
+ * Consume the maximum number of characters possible,
+ * with the consumed characters matching one of the
+ * identifiers in the first column of the named
+ * character references table (in a case-sensitive
+ * manner).
+ */
+ hiloop: for (;;) {
+ if (hi == -1) {
+ break hiloop;
+ }
+ if (entCol == NamedCharacters.NAMES[hi].length()) {
+ break hiloop;
+ }
+ if (entCol > NamedCharacters.NAMES[hi].length()) {
+ break outer;
+ } else if (c < NamedCharacters.NAMES[hi].charAt(entCol)) {
+ hi--;
+ } else {
+ break hiloop;
+ }
+ }
+
+ loloop: for (;;) {
+ if (hi < lo) {
+ break outer;
+ }
+ if (entCol == NamedCharacters.NAMES[lo].length()) {
+ candidate = lo;
+ charRefBufMark = charRefBufLen;
+ lo++;
+ } else if (entCol > NamedCharacters.NAMES[lo].length()) {
+ break outer;
+ } else if (c > NamedCharacters.NAMES[lo].charAt(entCol)) {
+ lo++;
+ } else {
+ break loloop;
+ }
+ }
+ if (hi < lo) {
+ break outer;
+ }
+ continue;
+ }
+
+ if (candidate == -1) {
+ /*
+ * If no match can be made, then this is a parse error.
+ */
+ errNoNamedCharacterMatch();
+ emitOrAppendCharRefBuf(returnState);
+ state = returnState;
+ continue eofloop;
+ } else {
+ @Const @CharacterName String candidateName = NamedCharacters.NAMES[candidate];
+ if (candidateName.length() == 0
+ || candidateName.charAt(candidateName.length() - 1) != ';') {
+ /*
+ * If the last character matched is not a U+003B
+ * SEMICOLON (;), there is a parse error.
+ */
+ if ((returnState & DATA_AND_RCDATA_MASK) != 0) {
+ /*
+ * If the entity is being consumed as part of an
+ * attribute, and the last character matched is
+ * not a U+003B SEMICOLON (;),
+ */
+ char ch;
+ if (charRefBufMark == charRefBufLen) {
+ ch = '\u0000';
+ } else {
+ ch = charRefBuf[charRefBufMark];
+ }
+ if ((ch >= '0' && ch <= '9')
+ || (ch >= 'A' && ch <= 'Z')
+ || (ch >= 'a' && ch <= 'z')) {
+ /*
+ * and the next character is in the range
+ * U+0030 DIGIT ZERO to U+0039 DIGIT NINE,
+ * U+0041 LATIN CAPITAL LETTER A to U+005A
+ * LATIN CAPITAL LETTER Z, or U+0061 LATIN
+ * SMALL LETTER A to U+007A LATIN SMALL
+ * LETTER Z, then, for historical reasons,
+ * all the characters that were matched
+ * after the U+0026 AMPERSAND (&) must be
+ * unconsumed, and nothing is returned.
+ */
+ errNoNamedCharacterMatch();
+ appendCharRefBufToStrBuf();
+ state = returnState;
+ continue eofloop;
+ }
+ }
+ if ((returnState & DATA_AND_RCDATA_MASK) != 0) {
+ errUnescapedAmpersandInterpretedAsCharacterReference();
+ } else {
+ errNotSemicolonTerminated();
+ }
+ }
+
+ /*
+ * Otherwise, return a character token for the character
+ * corresponding to the entity name (as given by the
+ * second column of the named character references
+ * table).
+ */
+ @Const @NoLength char[] val = NamedCharacters.VALUES[candidate];
+ if (
+ // [NOCPP[
+ val.length == 1
+ // ]NOCPP]
+ // CPPONLY: val[1] == 0
+ ) {
+ emitOrAppendOne(val, returnState);
+ } else {
+ emitOrAppendTwo(val, returnState);
+ }
+ // this is so complicated!
+ if (charRefBufMark < charRefBufLen) {
+ if ((returnState & DATA_AND_RCDATA_MASK) != 0) {
+ appendStrBuf(charRefBuf, charRefBufMark,
+ charRefBufLen - charRefBufMark);
+ } else {
+ tokenHandler.characters(charRefBuf, charRefBufMark,
+ charRefBufLen - charRefBufMark);
+ }
+ }
+ charRefBufLen = 0;
+ state = returnState;
+ continue eofloop;
+ /*
+ * If the markup contains I'm &notit; I tell you, the
+ * entity is parsed as "not", as in, I'm ¬it; I tell
+ * you. But if the markup was I'm &notin; I tell you,
+ * the entity would be parsed as "notin;", resulting in
+ * I'm ∉ I tell you.
+ */
+ }
+ case CONSUME_NCR:
+ case DECIMAL_NRC_LOOP:
+ case HEX_NCR_LOOP:
+ /*
+ * If no characters match the range, then don't consume any
+ * characters (and unconsume the U+0023 NUMBER SIGN
+ * character and, if appropriate, the X character). This is
+ * a parse error; nothing is returned.
+ *
+ * Otherwise, if the next character is a U+003B SEMICOLON,
+ * consume that too. If it isn't, there is a parse error.
+ */
+ if (!seenDigits) {
+ errNoDigitsInNCR();
+ emitOrAppendCharRefBuf(returnState);
+ state = returnState;
+ continue;
+ } else {
+ errCharRefLacksSemicolon();
+ }
+ // WARNING previous state sets reconsume
+ handleNcrValue(returnState);
+ state = returnState;
+ continue;
+ case CDATA_RSQB:
+ tokenHandler.characters(Tokenizer.RSQB_RSQB, 0, 1);
+ break eofloop;
+ case CDATA_RSQB_RSQB:
+ tokenHandler.characters(Tokenizer.RSQB_RSQB, 0, 2);
+ break eofloop;
+ case DATA:
+ default:
+ break eofloop;
+ }
+ }
+ // case DATA:
+ /*
+ * EOF Emit an end-of-file token.
+ */
+ tokenHandler.eof();
+ return;
+ }
+
+ private void emitDoctypeToken(int pos) throws SAXException {
+ cstart = pos + 1;
+ tokenHandler.doctype(doctypeName, publicIdentifier, systemIdentifier,
+ forceQuirks);
+ // It is OK and sufficient to release these here, since
+ // there's no way out of the doctype states than through paths
+ // that call this method.
+ doctypeName = null;
+ Portability.releaseString(publicIdentifier);
+ publicIdentifier = null;
+ Portability.releaseString(systemIdentifier);
+ systemIdentifier = null;
+ }
+
+ @Inline protected char checkChar(@NoLength char[] buf, int pos)
+ throws SAXException {
+ return buf[pos];
+ }
+
+ public boolean internalEncodingDeclaration(String internalCharset)
+ throws SAXException {
+ if (encodingDeclarationHandler != null) {
+ return encodingDeclarationHandler.internalEncodingDeclaration(internalCharset);
+ }
+ return false;
+ }
+
+ /**
+ * @param val
+ * @throws SAXException
+ */
+ private void emitOrAppendTwo(@Const @NoLength char[] val, int returnState)
+ throws SAXException {
+ if ((returnState & DATA_AND_RCDATA_MASK) != 0) {
+ appendStrBuf(val[0]);
+ appendStrBuf(val[1]);
+ } else {
+ tokenHandler.characters(val, 0, 2);
+ }
+ }
+
+ private void emitOrAppendOne(@Const @NoLength char[] val, int returnState)
+ throws SAXException {
+ if ((returnState & DATA_AND_RCDATA_MASK) != 0) {
+ appendStrBuf(val[0]);
+ } else {
+ tokenHandler.characters(val, 0, 1);
+ }
+ }
+
+ public void end() throws SAXException {
+ strBuf = null;
+ doctypeName = null;
+ if (systemIdentifier != null) {
+ Portability.releaseString(systemIdentifier);
+ systemIdentifier = null;
+ }
+ if (publicIdentifier != null) {
+ Portability.releaseString(publicIdentifier);
+ publicIdentifier = null;
+ }
+ tagName = null;
+ nonInternedTagName.setNameForNonInterned(null
+ // CPPONLY: , false
+ );
+ attributeName = null;
+ // CPPONLY: nonInternedAttributeName.setNameForNonInterned(null);
+ tokenHandler.endTokenization();
+ if (attributes != null) {
+ // [NOCPP[
+ attributes = null;
+ // ]NOCPP]
+ // CPPONLY: attributes.clear(mappingLangToXmlLang);
+ }
+ }
+
+ public void requestSuspension() {
+ shouldSuspend = true;
+ }
+
+ // [NOCPP[
+
+ public void becomeConfident() {
+ confident = true;
+ }
+
+ /**
+ * Returns the nextCharOnNewLine.
+ *
+ * @return the nextCharOnNewLine
+ */
+ public boolean isNextCharOnNewLine() {
+ return false;
+ }
+
+ public boolean isPrevCR() {
+ return lastCR;
+ }
+
+ /**
+ * Returns the line.
+ *
+ * @return the line
+ */
+ public int getLine() {
+ return -1;
+ }
+
+ /**
+ * Returns the col.
+ *
+ * @return the col
+ */
+ public int getCol() {
+ return -1;
+ }
+
+ // ]NOCPP]
+
+ public boolean isInDataState() {
+ return (stateSave == DATA);
+ }
+
+ public void resetToDataState() {
+ clearStrBufAfterUse();
+ charRefBufLen = 0;
+ stateSave = Tokenizer.DATA;
+ // line = 1; XXX line numbers
+ lastCR = false;
+ index = 0;
+ forceQuirks = false;
+ additional = '\u0000';
+ entCol = -1;
+ firstCharKey = -1;
+ lo = 0;
+ hi = 0; // will always be overwritten before use anyway
+ candidate = -1;
+ charRefBufMark = 0;
+ value = 0;
+ seenDigits = false;
+ endTag = false;
+ shouldSuspend = false;
+ initDoctypeFields();
+ containsHyphen = false;
+ tagName = null;
+ attributeName = null;
+ if (newAttributesEachTime) {
+ if (attributes != null) {
+ Portability.delete(attributes);
+ attributes = null;
+ }
+ }
+ }
+
+ public void loadState(Tokenizer other) throws SAXException {
+ strBufLen = other.strBufLen;
+ if (strBufLen > strBuf.length) {
+ strBuf = new char[strBufLen];
+ }
+ System.arraycopy(other.strBuf, 0, strBuf, 0, strBufLen);
+
+ charRefBufLen = other.charRefBufLen;
+ System.arraycopy(other.charRefBuf, 0, charRefBuf, 0, charRefBufLen);
+
+ stateSave = other.stateSave;
+ returnStateSave = other.returnStateSave;
+ endTagExpectation = other.endTagExpectation;
+ endTagExpectationAsArray = other.endTagExpectationAsArray;
+ // line = 1; XXX line numbers
+ lastCR = other.lastCR;
+ index = other.index;
+ forceQuirks = other.forceQuirks;
+ additional = other.additional;
+ entCol = other.entCol;
+ firstCharKey = other.firstCharKey;
+ lo = other.lo;
+ hi = other.hi;
+ candidate = other.candidate;
+ charRefBufMark = other.charRefBufMark;
+ value = other.value;
+ seenDigits = other.seenDigits;
+ endTag = other.endTag;
+ shouldSuspend = false;
+
+ if (other.doctypeName == null) {
+ doctypeName = null;
+ } else {
+ doctypeName = Portability.newLocalFromLocal(other.doctypeName,
+ interner);
+ }
+
+ Portability.releaseString(systemIdentifier);
+ if (other.systemIdentifier == null) {
+ systemIdentifier = null;
+ } else {
+ systemIdentifier = Portability.newStringFromString(other.systemIdentifier);
+ }
+
+ Portability.releaseString(publicIdentifier);
+ if (other.publicIdentifier == null) {
+ publicIdentifier = null;
+ } else {
+ publicIdentifier = Portability.newStringFromString(other.publicIdentifier);
+ }
+
+ containsHyphen = other.containsHyphen;
+ if (other.tagName == null) {
+ tagName = null;
+ } else if (other.tagName.isInterned()) {
+ tagName = other.tagName;
+ } else {
+ // In the C++ case, the atoms in the other tokenizer are from a
+ // different tokenizer-scoped atom table. Therefore, we have to
+ // obtain the correspoding atom from our own atom table.
+ nonInternedTagName.setNameForNonInterned(Portability.newLocalFromLocal(other.tagName.getName(), interner)
+ // CPPONLY: , other.tagName.isCustom()
+ );
+ tagName = nonInternedTagName;
+ }
+
+ // [NOCPP[
+ attributeName = other.attributeName;
+ // ]NOCPP]
+ // CPPONLY: if (other.attributeName == null) {
+ // CPPONLY: attributeName = null;
+ // CPPONLY: } else if (other.attributeName.isInterned()) {
+ // CPPONLY: attributeName = other.attributeName;
+ // CPPONLY: } else {
+ // CPPONLY: // In the C++ case, the atoms in the other tokenizer are from a
+ // CPPONLY: // different tokenizer-scoped atom table. Therefore, we have to
+ // CPPONLY: // obtain the correspoding atom from our own atom table.
+ // CPPONLY: nonInternedAttributeName.setNameForNonInterned(Portability.newLocalFromLocal(other.attributeName.getLocal(AttributeName.HTML), interner));
+ // CPPONLY: attributeName = nonInternedAttributeName;
+ // CPPONLY: }
+
+ Portability.delete(attributes);
+ if (other.attributes == null) {
+ attributes = null;
+ } else {
+ attributes = other.attributes.cloneAttributes(interner);
+ }
+ }
+
+ public void initializeWithoutStarting() throws SAXException {
+ confident = false;
+ strBuf = null;
+ line = 1;
+ // CPPONLY: attributeLine = 1;
+ // [NOCPP[
+ html4 = false;
+ metaBoundaryPassed = false;
+ wantsComments = tokenHandler.wantsComments();
+ if (!newAttributesEachTime) {
+ attributes = new HtmlAttributes(mappingLangToXmlLang);
+ }
+ // ]NOCPP]
+ resetToDataState();
+ }
+
+ protected void errGarbageAfterLtSlash() throws SAXException {
+ }
+
+ protected void errLtSlashGt() throws SAXException {
+ }
+
+ protected void errWarnLtSlashInRcdata() throws SAXException {
+ }
+
+ protected void errHtml4LtSlashInRcdata(char folded) throws SAXException {
+ }
+
+ protected void errCharRefLacksSemicolon() throws SAXException {
+ }
+
+ protected void errNoDigitsInNCR() throws SAXException {
+ }
+
+ protected void errGtInSystemId() throws SAXException {
+ }
+
+ protected void errGtInPublicId() throws SAXException {
+ }
+
+ protected void errNamelessDoctype() throws SAXException {
+ }
+
+ protected void errConsecutiveHyphens() throws SAXException {
+ }
+
+ protected void errPrematureEndOfComment() throws SAXException {
+ }
+
+ protected void errBogusComment() throws SAXException {
+ }
+
+ protected void errUnquotedAttributeValOrNull(char c) throws SAXException {
+ }
+
+ protected void errSlashNotFollowedByGt() throws SAXException {
+ }
+
+ protected void errHtml4XmlVoidSyntax() throws SAXException {
+ }
+
+ protected void errNoSpaceBetweenAttributes() throws SAXException {
+ }
+
+ protected void errHtml4NonNameInUnquotedAttribute(char c)
+ throws SAXException {
+ }
+
+ protected void errLtOrEqualsOrGraveInUnquotedAttributeOrNull(char c)
+ throws SAXException {
+ }
+
+ protected void errAttributeValueMissing() throws SAXException {
+ }
+
+ protected void errBadCharBeforeAttributeNameOrNull(char c)
+ throws SAXException {
+ }
+
+ protected void errEqualsSignBeforeAttributeName() throws SAXException {
+ }
+
+ protected void errBadCharAfterLt(char c) throws SAXException {
+ }
+
+ protected void errLtGt() throws SAXException {
+ }
+
+ protected void errProcessingInstruction() throws SAXException {
+ }
+
+ protected void errUnescapedAmpersandInterpretedAsCharacterReference()
+ throws SAXException {
+ }
+
+ protected void errNotSemicolonTerminated() throws SAXException {
+ }
+
+ protected void errNoNamedCharacterMatch() throws SAXException {
+ }
+
+ protected void errQuoteBeforeAttributeName(char c) throws SAXException {
+ }
+
+ protected void errQuoteOrLtInAttributeNameOrNull(char c)
+ throws SAXException {
+ }
+
+ protected void errExpectedPublicId() throws SAXException {
+ }
+
+ protected void errBogusDoctype() throws SAXException {
+ }
+
+ protected void maybeWarnPrivateUseAstral() throws SAXException {
+ }
+
+ protected void maybeWarnPrivateUse(char ch) throws SAXException {
+ }
+
+ protected void maybeErrAttributesOnEndTag(HtmlAttributes attrs)
+ throws SAXException {
+ }
+
+ protected void maybeErrSlashInEndTag(boolean selfClosing)
+ throws SAXException {
+ }
+
+ protected char errNcrNonCharacter(char ch) throws SAXException {
+ return ch;
+ }
+
+ protected void errAstralNonCharacter(int ch) throws SAXException {
+ }
+
+ protected void errNcrSurrogate() throws SAXException {
+ }
+
+ protected char errNcrControlChar(char ch) throws SAXException {
+ return ch;
+ }
+
+ protected void errNcrCr() throws SAXException {
+ }
+
+ protected void errNcrInC1Range() throws SAXException {
+ }
+
+ protected void errEofInPublicId() throws SAXException {
+ }
+
+ protected void errEofInComment() throws SAXException {
+ }
+
+ protected void errEofInDoctype() throws SAXException {
+ }
+
+ protected void errEofInAttributeValue() throws SAXException {
+ }
+
+ protected void errEofInAttributeName() throws SAXException {
+ }
+
+ protected void errEofWithoutGt() throws SAXException {
+ }
+
+ protected void errEofInTagName() throws SAXException {
+ }
+
+ protected void errEofInEndTag() throws SAXException {
+ }
+
+ protected void errEofAfterLt() throws SAXException {
+ }
+
+ protected void errNcrOutOfRange() throws SAXException {
+ }
+
+ protected void errNcrUnassigned() throws SAXException {
+ }
+
+ protected void errDuplicateAttribute() throws SAXException {
+ }
+
+ protected void errEofInSystemId() throws SAXException {
+ }
+
+ protected void errExpectedSystemId() throws SAXException {
+ }
+
+ protected void errMissingSpaceBeforeDoctypeName() throws SAXException {
+ }
+
+ protected void errHyphenHyphenBang() throws SAXException {
+ }
+
+ protected void errNcrControlChar() throws SAXException {
+ }
+
+ protected void errNcrZero() throws SAXException {
+ }
+
+ protected void errNoSpaceBetweenDoctypeSystemKeywordAndQuote()
+ throws SAXException {
+ }
+
+ protected void errNoSpaceBetweenPublicAndSystemIds() throws SAXException {
+ }
+
+ protected void errNoSpaceBetweenDoctypePublicKeywordAndQuote()
+ throws SAXException {
+ }
+
+ protected void noteAttributeWithoutValue() throws SAXException {
+ }
+
+ protected void noteUnquotedAttributeValue() throws SAXException {
+ }
+
+ /**
+ * Sets the encodingDeclarationHandler.
+ *
+ * @param encodingDeclarationHandler
+ * the encodingDeclarationHandler to set
+ */
+ public void setEncodingDeclarationHandler(
+ EncodingDeclarationHandler encodingDeclarationHandler) {
+ this.encodingDeclarationHandler = encodingDeclarationHandler;
+ }
+
+ void destructor() {
+ Portability.delete(nonInternedTagName);
+ // CPPONLY: Portability.delete(nonInternedAttributeName);
+ nonInternedTagName = null;
+ // The translator will write refcount tracing stuff here
+ Portability.delete(attributes);
+ attributes = null;
+ }
+
+ // [NOCPP[
+
+ /**
+ * Sets an offset to be added to the position reported to
+ * <code>TransitionHandler</code>.
+ *
+ * @param offset the offset
+ */
+ public void setTransitionBaseOffset(int offset) {
+
+ }
+
+ // ]NOCPP]
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TreeBuilder.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TreeBuilder.java
new file mode 100644
index 000000000..ef9576ee3
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TreeBuilder.java
@@ -0,0 +1,6639 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2015 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ * Portions of comments Copyright 2004-2008 Apple Computer, Inc., Mozilla
+ * Foundation, and Opera Software ASA.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The comments following this one that use the same comment syntax as this
+ * comment are quotes from the WHATWG HTML 5 spec as of 27 June 2007
+ * amended as of June 28 2007.
+ * That document came with this statement:
+ * "© Copyright 2004-2007 Apple Computer, Inc., Mozilla Foundation, and
+ * Opera Software ASA. You are granted a license to use, reproduce and
+ * create derivative works of this document."
+ */
+
+package nu.validator.htmlparser.impl;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import nu.validator.htmlparser.annotation.Auto;
+import nu.validator.htmlparser.annotation.Const;
+import nu.validator.htmlparser.annotation.IdType;
+import nu.validator.htmlparser.annotation.Inline;
+import nu.validator.htmlparser.annotation.Literal;
+import nu.validator.htmlparser.annotation.Local;
+import nu.validator.htmlparser.annotation.NoLength;
+import nu.validator.htmlparser.annotation.NsUri;
+import nu.validator.htmlparser.common.DoctypeExpectation;
+import nu.validator.htmlparser.common.DocumentMode;
+import nu.validator.htmlparser.common.DocumentModeHandler;
+import nu.validator.htmlparser.common.Interner;
+import nu.validator.htmlparser.common.TokenHandler;
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+
+public abstract class TreeBuilder<T> implements TokenHandler,
+ TreeBuilderState<T> {
+
+ /**
+ * Array version of U+FFFD.
+ */
+ private static final @NoLength char[] REPLACEMENT_CHARACTER = { '\uFFFD' };
+
+ // Start dispatch groups
+
+ final static int OTHER = 0;
+
+ final static int A = 1;
+
+ final static int BASE = 2;
+
+ final static int BODY = 3;
+
+ final static int BR = 4;
+
+ final static int BUTTON = 5;
+
+ final static int CAPTION = 6;
+
+ final static int COL = 7;
+
+ final static int COLGROUP = 8;
+
+ final static int FORM = 9;
+
+ final static int FRAME = 10;
+
+ final static int FRAMESET = 11;
+
+ final static int IMAGE = 12;
+
+ final static int INPUT = 13;
+
+ final static int ISINDEX = 14;
+
+ final static int LI = 15;
+
+ final static int LINK_OR_BASEFONT_OR_BGSOUND = 16;
+
+ final static int MATH = 17;
+
+ final static int META = 18;
+
+ final static int SVG = 19;
+
+ final static int HEAD = 20;
+
+ final static int HR = 22;
+
+ final static int HTML = 23;
+
+ final static int NOBR = 24;
+
+ final static int NOFRAMES = 25;
+
+ final static int NOSCRIPT = 26;
+
+ final static int OPTGROUP = 27;
+
+ final static int OPTION = 28;
+
+ final static int P = 29;
+
+ final static int PLAINTEXT = 30;
+
+ final static int SCRIPT = 31;
+
+ final static int SELECT = 32;
+
+ final static int STYLE = 33;
+
+ final static int TABLE = 34;
+
+ final static int TEXTAREA = 35;
+
+ final static int TITLE = 36;
+
+ final static int TR = 37;
+
+ final static int XMP = 38;
+
+ final static int TBODY_OR_THEAD_OR_TFOOT = 39;
+
+ final static int TD_OR_TH = 40;
+
+ final static int DD_OR_DT = 41;
+
+ final static int H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 = 42;
+
+ final static int MARQUEE_OR_APPLET = 43;
+
+ final static int PRE_OR_LISTING = 44;
+
+ final static int B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U = 45;
+
+ final static int UL_OR_OL_OR_DL = 46;
+
+ final static int IFRAME = 47;
+
+ final static int EMBED = 48;
+
+ final static int AREA_OR_WBR = 49;
+
+ final static int DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU = 50;
+
+ final static int ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY = 51;
+
+ final static int RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR = 52;
+
+ final static int RB_OR_RTC = 53;
+
+ final static int PARAM_OR_SOURCE_OR_TRACK = 55;
+
+ final static int MGLYPH_OR_MALIGNMARK = 56;
+
+ final static int MI_MO_MN_MS_MTEXT = 57;
+
+ final static int ANNOTATION_XML = 58;
+
+ final static int FOREIGNOBJECT_OR_DESC = 59;
+
+ final static int NOEMBED = 60;
+
+ final static int FIELDSET = 61;
+
+ final static int OUTPUT = 62;
+
+ final static int OBJECT = 63;
+
+ final static int FONT = 64;
+
+ final static int KEYGEN = 65;
+
+ final static int MENUITEM = 66;
+
+ final static int TEMPLATE = 67;
+
+ final static int IMG = 68;
+
+ final static int RT_OR_RP = 69;
+
+ // start insertion modes
+
+ private static final int IN_ROW = 0;
+
+ private static final int IN_TABLE_BODY = 1;
+
+ private static final int IN_TABLE = 2;
+
+ private static final int IN_CAPTION = 3;
+
+ private static final int IN_CELL = 4;
+
+ private static final int FRAMESET_OK = 5;
+
+ private static final int IN_BODY = 6;
+
+ private static final int IN_HEAD = 7;
+
+ private static final int IN_HEAD_NOSCRIPT = 8;
+
+ // no fall-through
+
+ private static final int IN_COLUMN_GROUP = 9;
+
+ // no fall-through
+
+ private static final int IN_SELECT_IN_TABLE = 10;
+
+ private static final int IN_SELECT = 11;
+
+ // no fall-through
+
+ private static final int AFTER_BODY = 12;
+
+ // no fall-through
+
+ private static final int IN_FRAMESET = 13;
+
+ private static final int AFTER_FRAMESET = 14;
+
+ // no fall-through
+
+ private static final int INITIAL = 15;
+
+ // could add fall-through
+
+ private static final int BEFORE_HTML = 16;
+
+ // could add fall-through
+
+ private static final int BEFORE_HEAD = 17;
+
+ // no fall-through
+
+ private static final int AFTER_HEAD = 18;
+
+ // no fall-through
+
+ private static final int AFTER_AFTER_BODY = 19;
+
+ // no fall-through
+
+ private static final int AFTER_AFTER_FRAMESET = 20;
+
+ // no fall-through
+
+ private static final int TEXT = 21;
+
+ private static final int IN_TEMPLATE = 22;
+
+ // start charset states
+
+ private static final int CHARSET_INITIAL = 0;
+
+ private static final int CHARSET_C = 1;
+
+ private static final int CHARSET_H = 2;
+
+ private static final int CHARSET_A = 3;
+
+ private static final int CHARSET_R = 4;
+
+ private static final int CHARSET_S = 5;
+
+ private static final int CHARSET_E = 6;
+
+ private static final int CHARSET_T = 7;
+
+ private static final int CHARSET_EQUALS = 8;
+
+ private static final int CHARSET_SINGLE_QUOTED = 9;
+
+ private static final int CHARSET_DOUBLE_QUOTED = 10;
+
+ private static final int CHARSET_UNQUOTED = 11;
+
+ // end pseudo enums
+
+ // [NOCPP[
+
+ private final static String[] HTML4_PUBLIC_IDS = {
+ "-//W3C//DTD HTML 4.0 Frameset//EN",
+ "-//W3C//DTD HTML 4.0 Transitional//EN",
+ "-//W3C//DTD HTML 4.0//EN", "-//W3C//DTD HTML 4.01 Frameset//EN",
+ "-//W3C//DTD HTML 4.01 Transitional//EN",
+ "-//W3C//DTD HTML 4.01//EN" };
+
+ // ]NOCPP]
+
+ @Literal private final static String[] QUIRKY_PUBLIC_IDS = {
+ "+//silmaril//dtd html pro v0r11 19970101//",
+ "-//advasoft ltd//dtd html 3.0 aswedit + extensions//",
+ "-//as//dtd html 3.0 aswedit + extensions//",
+ "-//ietf//dtd html 2.0 level 1//",
+ "-//ietf//dtd html 2.0 level 2//",
+ "-//ietf//dtd html 2.0 strict level 1//",
+ "-//ietf//dtd html 2.0 strict level 2//",
+ "-//ietf//dtd html 2.0 strict//",
+ "-//ietf//dtd html 2.0//",
+ "-//ietf//dtd html 2.1e//",
+ "-//ietf//dtd html 3.0//",
+ "-//ietf//dtd html 3.2 final//",
+ "-//ietf//dtd html 3.2//",
+ "-//ietf//dtd html 3//",
+ "-//ietf//dtd html level 0//",
+ "-//ietf//dtd html level 1//",
+ "-//ietf//dtd html level 2//",
+ "-//ietf//dtd html level 3//",
+ "-//ietf//dtd html strict level 0//",
+ "-//ietf//dtd html strict level 1//",
+ "-//ietf//dtd html strict level 2//",
+ "-//ietf//dtd html strict level 3//",
+ "-//ietf//dtd html strict//",
+ "-//ietf//dtd html//",
+ "-//metrius//dtd metrius presentational//",
+ "-//microsoft//dtd internet explorer 2.0 html strict//",
+ "-//microsoft//dtd internet explorer 2.0 html//",
+ "-//microsoft//dtd internet explorer 2.0 tables//",
+ "-//microsoft//dtd internet explorer 3.0 html strict//",
+ "-//microsoft//dtd internet explorer 3.0 html//",
+ "-//microsoft//dtd internet explorer 3.0 tables//",
+ "-//netscape comm. corp.//dtd html//",
+ "-//netscape comm. corp.//dtd strict html//",
+ "-//o'reilly and associates//dtd html 2.0//",
+ "-//o'reilly and associates//dtd html extended 1.0//",
+ "-//o'reilly and associates//dtd html extended relaxed 1.0//",
+ "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//",
+ "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//",
+ "-//spyglass//dtd html 2.0 extended//",
+ "-//sq//dtd html 2.0 hotmetal + extensions//",
+ "-//sun microsystems corp.//dtd hotjava html//",
+ "-//sun microsystems corp.//dtd hotjava strict html//",
+ "-//w3c//dtd html 3 1995-03-24//", "-//w3c//dtd html 3.2 draft//",
+ "-//w3c//dtd html 3.2 final//", "-//w3c//dtd html 3.2//",
+ "-//w3c//dtd html 3.2s draft//", "-//w3c//dtd html 4.0 frameset//",
+ "-//w3c//dtd html 4.0 transitional//",
+ "-//w3c//dtd html experimental 19960712//",
+ "-//w3c//dtd html experimental 970421//", "-//w3c//dtd w3 html//",
+ "-//w3o//dtd w3 html 3.0//", "-//webtechs//dtd mozilla html 2.0//",
+ "-//webtechs//dtd mozilla html//" };
+
+ private static final int NOT_FOUND_ON_STACK = Integer.MAX_VALUE;
+
+ // [NOCPP[
+
+ private static final @Local String HTML_LOCAL = "html";
+
+ // ]NOCPP]
+
+ private int mode = INITIAL;
+
+ private int originalMode = INITIAL;
+
+ /**
+ * Used only when moving back to IN_BODY.
+ */
+ private boolean framesetOk = true;
+
+ protected Tokenizer tokenizer;
+
+ // [NOCPP[
+
+ protected ErrorHandler errorHandler;
+
+ private DocumentModeHandler documentModeHandler;
+
+ private DoctypeExpectation doctypeExpectation = DoctypeExpectation.HTML;
+
+ private LocatorImpl firstCommentLocation;
+
+ // ]NOCPP]
+
+ private boolean scriptingEnabled = false;
+
+ private boolean needToDropLF;
+
+ // [NOCPP[
+
+ private boolean wantingComments;
+
+ // ]NOCPP]
+
+ private boolean fragment;
+
+ private @Local String contextName;
+
+ private @NsUri String contextNamespace;
+
+ private T contextNode;
+
+ /**
+ * Stack of template insertion modes
+ */
+ private @Auto int[] templateModeStack;
+
+ /**
+ * Current template mode stack pointer.
+ */
+ private int templateModePtr = -1;
+
+ private @Auto StackNode<T>[] stack;
+
+ private int currentPtr = -1;
+
+ private @Auto StackNode<T>[] listOfActiveFormattingElements;
+
+ private int listPtr = -1;
+
+ private T formPointer;
+
+ private T headPointer;
+
+ /**
+ * Used to work around Gecko limitations. Not used in Java.
+ */
+ private T deepTreeSurrogateParent;
+
+ protected @Auto char[] charBuffer;
+
+ protected int charBufferLen = 0;
+
+ private boolean quirks = false;
+
+ private boolean isSrcdocDocument = false;
+
+ // [NOCPP[
+
+ private boolean reportingDoctype = true;
+
+ private XmlViolationPolicy namePolicy = XmlViolationPolicy.ALTER_INFOSET;
+
+ private final Map<String, LocatorImpl> idLocations = new HashMap<String, LocatorImpl>();
+
+ private boolean html4;
+
+ // ]NOCPP]
+
+ protected TreeBuilder() {
+ fragment = false;
+ }
+
+ /**
+ * Reports an condition that would make the infoset incompatible with XML
+ * 1.0 as fatal.
+ *
+ * @throws SAXException
+ * @throws SAXParseException
+ */
+ protected void fatal() throws SAXException {
+ }
+
+ // CPPONLY: @Inline private @Creator Object htmlCreator(@HtmlCreator Object htmlCreator) {
+ // CPPONLY: @Creator Object creator;
+ // CPPONLY: creator.html = htmlCreator;
+ // CPPONLY: return creator;
+ // CPPONLY: }
+ // CPPONLY:
+ // CPPONLY: @Inline private @Creator Object svgCreator(@SvgCreator Object svgCreator) {
+ // CPPONLY: @Creator Object creator;
+ // CPPONLY: creator.svg = svgCreator;
+ // CPPONLY: return creator;
+ // CPPONLY: }
+
+ // [NOCPP[
+
+ protected final void fatal(Exception e) throws SAXException {
+ SAXParseException spe = new SAXParseException(e.getMessage(),
+ tokenizer, e);
+ if (errorHandler != null) {
+ errorHandler.fatalError(spe);
+ }
+ throw spe;
+ }
+
+ final void fatal(String s) throws SAXException {
+ SAXParseException spe = new SAXParseException(s, tokenizer);
+ if (errorHandler != null) {
+ errorHandler.fatalError(spe);
+ }
+ throw spe;
+ }
+
+ /**
+ * Reports a Parse Error.
+ *
+ * @param message
+ * the message
+ * @throws SAXException
+ */
+ final void err(String message) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck(message);
+ }
+
+ /**
+ * Reports a Parse Error without checking if an error handler is present.
+ *
+ * @param message
+ * the message
+ * @throws SAXException
+ */
+ final void errNoCheck(String message) throws SAXException {
+ SAXParseException spe = new SAXParseException(message, tokenizer);
+ errorHandler.error(spe);
+ }
+
+ private void errListUnclosedStartTags(int eltPos) throws SAXException {
+ if (currentPtr != -1) {
+ for (int i = currentPtr; i > eltPos; i--) {
+ reportUnclosedElementNameAndLocation(i);
+ }
+ }
+ }
+
+ /**
+ * Reports the name and location of an unclosed element.
+ *
+ * @throws SAXException
+ */
+ private final void reportUnclosedElementNameAndLocation(int pos) throws SAXException {
+ StackNode<T> node = stack[pos];
+ if (node.isOptionalEndTag()) {
+ return;
+ }
+ TaintableLocatorImpl locator = node.getLocator();
+ if (locator.isTainted()) {
+ return;
+ }
+ locator.markTainted();
+ SAXParseException spe = new SAXParseException(
+ "Unclosed element \u201C" + node.popName + "\u201D.", locator);
+ errorHandler.error(spe);
+ }
+
+ /**
+ * Reports a warning
+ *
+ * @param message
+ * the message
+ * @throws SAXException
+ */
+ final void warn(String message) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ SAXParseException spe = new SAXParseException(message, tokenizer);
+ errorHandler.warning(spe);
+ }
+
+ /**
+ * Reports a warning with an explicit locator
+ *
+ * @param message
+ * the message
+ * @throws SAXException
+ */
+ final void warn(String message, Locator locator) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ SAXParseException spe = new SAXParseException(message, locator);
+ errorHandler.warning(spe);
+ }
+
+ // ]NOCPP]
+
+ @SuppressWarnings("unchecked") public final void startTokenization(Tokenizer self) throws SAXException {
+ tokenizer = self;
+ stack = new StackNode[64];
+ templateModeStack = new int[64];
+ listOfActiveFormattingElements = new StackNode[64];
+ needToDropLF = false;
+ originalMode = INITIAL;
+ templateModePtr = -1;
+ currentPtr = -1;
+ listPtr = -1;
+ formPointer = null;
+ headPointer = null;
+ deepTreeSurrogateParent = null;
+ // [NOCPP[
+ html4 = false;
+ idLocations.clear();
+ wantingComments = wantsComments();
+ firstCommentLocation = null;
+ // ]NOCPP]
+ start(fragment);
+ charBufferLen = 0;
+ charBuffer = null;
+ framesetOk = true;
+ if (fragment) {
+ T elt;
+ if (contextNode != null) {
+ elt = contextNode;
+ } else {
+ elt = createHtmlElementSetAsRoot(tokenizer.emptyAttributes());
+ }
+ // When the context node is not in the HTML namespace, contrary
+ // to the spec, the first node on the stack is not set to "html"
+ // in the HTML namespace. Instead, it is set to a node that has
+ // the characteristics of the appropriate "adjusted current node".
+ // This way, there is no need to perform "adjusted current node"
+ // checks during tree construction. Instead, it's sufficient to
+ // just look at the current node. However, this also means that it
+ // is not safe to treat "html" in the HTML namespace as a sentinel
+ // that ends stack popping. Instead, stack popping loops that are
+ // meant not to pop the first element on the stack need to check
+ // for currentPos becoming zero.
+ if (contextNamespace == "http://www.w3.org/2000/svg") {
+ ElementName elementName = ElementName.SVG;
+ if ("title" == contextName || "desc" == contextName
+ || "foreignObject" == contextName) {
+ // These elements are all alike and we don't care about
+ // the exact name.
+ elementName = ElementName.FOREIGNOBJECT;
+ }
+ // This is the SVG variant of the StackNode constructor.
+ StackNode<T> node = new StackNode<T>(elementName,
+ elementName.getCamelCaseName(), elt
+ // [NOCPP[
+ , errorHandler == null ? null
+ : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ currentPtr++;
+ stack[currentPtr] = node;
+ tokenizer.setState(Tokenizer.DATA);
+ // The frameset-ok flag is set even though <frameset> never
+ // ends up being allowed as HTML frameset in the fragment case.
+ mode = FRAMESET_OK;
+ } else if (contextNamespace == "http://www.w3.org/1998/Math/MathML") {
+ ElementName elementName = ElementName.MATH;
+ if ("mi" == contextName || "mo" == contextName
+ || "mn" == contextName || "ms" == contextName
+ || "mtext" == contextName) {
+ // These elements are all alike and we don't care about
+ // the exact name.
+ elementName = ElementName.MTEXT;
+ } else if ("annotation-xml" == contextName) {
+ elementName = ElementName.ANNOTATION_XML;
+ // Blink does not check the encoding attribute of the
+ // annotation-xml element innerHTML is being set on.
+ // Let's do the same at least until
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=26783
+ // is resolved.
+ }
+ // This is the MathML variant of the StackNode constructor.
+ StackNode<T> node = new StackNode<T>(elementName, elt,
+ elementName.getName(), false
+ // [NOCPP[
+ , errorHandler == null ? null
+ : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ currentPtr++;
+ stack[currentPtr] = node;
+ tokenizer.setState(Tokenizer.DATA);
+ // The frameset-ok flag is set even though <frameset> never
+ // ends up being allowed as HTML frameset in the fragment case.
+ mode = FRAMESET_OK;
+ } else { // html
+ StackNode<T> node = new StackNode<T>(ElementName.HTML, elt
+ // [NOCPP[
+ , errorHandler == null ? null
+ : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ currentPtr++;
+ stack[currentPtr] = node;
+ if ("template" == contextName) {
+ pushTemplateMode(IN_TEMPLATE);
+ }
+ resetTheInsertionMode();
+ formPointer = getFormPointerForContext(contextNode);
+ if ("title" == contextName || "textarea" == contextName) {
+ tokenizer.setState(Tokenizer.RCDATA);
+ } else if ("style" == contextName || "xmp" == contextName
+ || "iframe" == contextName || "noembed" == contextName
+ || "noframes" == contextName
+ || (scriptingEnabled && "noscript" == contextName)) {
+ tokenizer.setState(Tokenizer.RAWTEXT);
+ } else if ("plaintext" == contextName) {
+ tokenizer.setState(Tokenizer.PLAINTEXT);
+ } else if ("script" == contextName) {
+ tokenizer.setState(Tokenizer.SCRIPT_DATA);
+ } else {
+ tokenizer.setState(Tokenizer.DATA);
+ }
+ }
+ } else {
+ mode = INITIAL;
+ // If we are viewing XML source, put a foreign element permanently
+ // on the stack so that cdataSectionAllowed() returns true.
+ // CPPONLY: if (tokenizer.isViewingXmlSource()) {
+ // CPPONLY: T elt = createElement("http://www.w3.org/2000/svg",
+ // CPPONLY: "svg",
+ // CPPONLY: tokenizer.emptyAttributes(), null,
+ // CPPONLY: svgCreator(NS_NewSVGSVGElement));
+ // CPPONLY: StackNode<T> node = new StackNode<T>(ElementName.SVG,
+ // CPPONLY: "svg",
+ // CPPONLY: elt);
+ // CPPONLY: currentPtr++;
+ // CPPONLY: stack[currentPtr] = node;
+ // CPPONLY: }
+ }
+ }
+
+ public final void doctype(@Local String name, String publicIdentifier,
+ String systemIdentifier, boolean forceQuirks) throws SAXException {
+ needToDropLF = false;
+ if (!isInForeign() && mode == INITIAL) {
+ // [NOCPP[
+ if (reportingDoctype) {
+ // ]NOCPP]
+ String emptyString = Portability.newEmptyString();
+ appendDoctypeToDocument(name == null ? "" : name,
+ publicIdentifier == null ? emptyString
+ : publicIdentifier,
+ systemIdentifier == null ? emptyString
+ : systemIdentifier);
+ Portability.releaseString(emptyString);
+ // [NOCPP[
+ }
+ switch (doctypeExpectation) {
+ case HTML:
+ // ]NOCPP]
+ if (isQuirky(name, publicIdentifier, systemIdentifier,
+ forceQuirks)) {
+ errQuirkyDoctype();
+ documentModeInternal(DocumentMode.QUIRKS_MODE,
+ publicIdentifier, systemIdentifier, false);
+ } else if (isAlmostStandards(publicIdentifier,
+ systemIdentifier)) {
+ // [NOCPP[
+ if (firstCommentLocation != null) {
+ warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.",
+ firstCommentLocation);
+ }
+ // ]NOCPP]
+ errAlmostStandardsDoctype();
+ documentModeInternal(
+ DocumentMode.ALMOST_STANDARDS_MODE,
+ publicIdentifier, systemIdentifier, false);
+ } else {
+ // [NOCPP[
+ if (firstCommentLocation != null) {
+ warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.",
+ firstCommentLocation);
+ }
+ if ((Portability.literalEqualsString(
+ "-//W3C//DTD HTML 4.0//EN", publicIdentifier) && (systemIdentifier == null || Portability.literalEqualsString(
+ "http://www.w3.org/TR/REC-html40/strict.dtd",
+ systemIdentifier)))
+ || (Portability.literalEqualsString(
+ "-//W3C//DTD HTML 4.01//EN",
+ publicIdentifier) && (systemIdentifier == null || Portability.literalEqualsString(
+ "http://www.w3.org/TR/html4/strict.dtd",
+ systemIdentifier)))
+ || (Portability.literalEqualsString(
+ "-//W3C//DTD XHTML 1.0 Strict//EN",
+ publicIdentifier) && Portability.literalEqualsString(
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd",
+ systemIdentifier))
+ || (Portability.literalEqualsString(
+ "-//W3C//DTD XHTML 1.1//EN",
+ publicIdentifier) && Portability.literalEqualsString(
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd",
+ systemIdentifier))
+
+ ) {
+ warn("Obsolete doctype. Expected \u201C<!DOCTYPE html>\u201D.");
+ } else if (!((systemIdentifier == null || Portability.literalEqualsString(
+ "about:legacy-compat", systemIdentifier)) && publicIdentifier == null)) {
+ err("Legacy doctype. Expected \u201C<!DOCTYPE html>\u201D.");
+ }
+ // ]NOCPP]
+ documentModeInternal(DocumentMode.STANDARDS_MODE,
+ publicIdentifier, systemIdentifier, false);
+ }
+ // [NOCPP[
+ break;
+ case HTML401_STRICT:
+ html4 = true;
+ tokenizer.turnOnAdditionalHtml4Errors();
+ if (isQuirky(name, publicIdentifier, systemIdentifier,
+ forceQuirks)) {
+ err("Quirky doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D.");
+ documentModeInternal(DocumentMode.QUIRKS_MODE,
+ publicIdentifier, systemIdentifier, true);
+ } else if (isAlmostStandards(publicIdentifier,
+ systemIdentifier)) {
+ if (firstCommentLocation != null) {
+ warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.",
+ firstCommentLocation);
+ }
+ err("Almost standards mode doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D.");
+ documentModeInternal(
+ DocumentMode.ALMOST_STANDARDS_MODE,
+ publicIdentifier, systemIdentifier, true);
+ } else {
+ if (firstCommentLocation != null) {
+ warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.",
+ firstCommentLocation);
+ }
+ if ("-//W3C//DTD HTML 4.01//EN".equals(publicIdentifier)) {
+ if (!"http://www.w3.org/TR/html4/strict.dtd".equals(systemIdentifier)) {
+ warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D.");
+ }
+ } else {
+ err("The doctype was not the HTML 4.01 Strict doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D.");
+ }
+ documentModeInternal(DocumentMode.STANDARDS_MODE,
+ publicIdentifier, systemIdentifier, true);
+ }
+ break;
+ case HTML401_TRANSITIONAL:
+ html4 = true;
+ tokenizer.turnOnAdditionalHtml4Errors();
+ if (isQuirky(name, publicIdentifier, systemIdentifier,
+ forceQuirks)) {
+ err("Quirky doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D.");
+ documentModeInternal(DocumentMode.QUIRKS_MODE,
+ publicIdentifier, systemIdentifier, true);
+ } else if (isAlmostStandards(publicIdentifier,
+ systemIdentifier)) {
+ if (firstCommentLocation != null) {
+ warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.",
+ firstCommentLocation);
+ }
+ if ("-//W3C//DTD HTML 4.01 Transitional//EN".equals(publicIdentifier)
+ && systemIdentifier != null) {
+ if (!"http://www.w3.org/TR/html4/loose.dtd".equals(systemIdentifier)) {
+ warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D.");
+ }
+ } else {
+ err("The doctype was not a non-quirky HTML 4.01 Transitional doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D.");
+ }
+ documentModeInternal(
+ DocumentMode.ALMOST_STANDARDS_MODE,
+ publicIdentifier, systemIdentifier, true);
+ } else {
+ if (firstCommentLocation != null) {
+ warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.",
+ firstCommentLocation);
+ }
+ err("The doctype was not the HTML 4.01 Transitional doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D.");
+ documentModeInternal(DocumentMode.STANDARDS_MODE,
+ publicIdentifier, systemIdentifier, true);
+ }
+ break;
+ case AUTO:
+ html4 = isHtml4Doctype(publicIdentifier);
+ if (html4) {
+ tokenizer.turnOnAdditionalHtml4Errors();
+ }
+ if (isQuirky(name, publicIdentifier, systemIdentifier,
+ forceQuirks)) {
+ err("Quirky doctype. Expected e.g. \u201C<!DOCTYPE html>\u201D.");
+ documentModeInternal(DocumentMode.QUIRKS_MODE,
+ publicIdentifier, systemIdentifier, html4);
+ } else if (isAlmostStandards(publicIdentifier,
+ systemIdentifier)) {
+ if (firstCommentLocation != null) {
+ warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.",
+ firstCommentLocation);
+ }
+ if ("-//W3C//DTD HTML 4.01 Transitional//EN".equals(publicIdentifier)) {
+ if (!"http://www.w3.org/TR/html4/loose.dtd".equals(systemIdentifier)) {
+ warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D.");
+ }
+ } else {
+ err("Almost standards mode doctype. Expected e.g. \u201C<!DOCTYPE html>\u201D.");
+ }
+ documentModeInternal(
+ DocumentMode.ALMOST_STANDARDS_MODE,
+ publicIdentifier, systemIdentifier, html4);
+ } else {
+ if (firstCommentLocation != null) {
+ warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.",
+ firstCommentLocation);
+ }
+ if ("-//W3C//DTD HTML 4.01//EN".equals(publicIdentifier)) {
+ if (!"http://www.w3.org/TR/html4/strict.dtd".equals(systemIdentifier)) {
+ warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D.");
+ }
+ } else if ("-//W3C//DTD XHTML 1.0 Strict//EN".equals(publicIdentifier)) {
+ if (!"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd".equals(systemIdentifier)) {
+ warn("The doctype did not contain the system identifier prescribed by the XHTML 1.0 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\u201D.");
+ }
+ } else if ("//W3C//DTD XHTML 1.1//EN".equals(publicIdentifier)) {
+ if (!"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd".equals(systemIdentifier)) {
+ warn("The doctype did not contain the system identifier prescribed by the XHTML 1.1 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\u201D.");
+ }
+ } else if (!((systemIdentifier == null || Portability.literalEqualsString(
+ "about:legacy-compat", systemIdentifier)) && publicIdentifier == null)) {
+ err("Unexpected doctype. Expected, e.g., \u201C<!DOCTYPE html>\u201D.");
+ }
+ documentModeInternal(DocumentMode.STANDARDS_MODE,
+ publicIdentifier, systemIdentifier, html4);
+ }
+ break;
+ case NO_DOCTYPE_ERRORS:
+ if (isQuirky(name, publicIdentifier, systemIdentifier,
+ forceQuirks)) {
+ documentModeInternal(DocumentMode.QUIRKS_MODE,
+ publicIdentifier, systemIdentifier, false);
+ } else if (isAlmostStandards(publicIdentifier,
+ systemIdentifier)) {
+ documentModeInternal(
+ DocumentMode.ALMOST_STANDARDS_MODE,
+ publicIdentifier, systemIdentifier, false);
+ } else {
+ documentModeInternal(DocumentMode.STANDARDS_MODE,
+ publicIdentifier, systemIdentifier, false);
+ }
+ break;
+ }
+ // ]NOCPP]
+
+ /*
+ *
+ * Then, switch to the root element mode of the tree construction
+ * stage.
+ */
+ mode = BEFORE_HTML;
+ return;
+ }
+ /*
+ * A DOCTYPE token Parse error.
+ */
+ errStrayDoctype();
+ /*
+ * Ignore the token.
+ */
+ return;
+ }
+
+ // [NOCPP[
+
+ private boolean isHtml4Doctype(String publicIdentifier) {
+ if (publicIdentifier != null
+ && (Arrays.binarySearch(TreeBuilder.HTML4_PUBLIC_IDS,
+ publicIdentifier) > -1)) {
+ return true;
+ }
+ return false;
+ }
+
+ // ]NOCPP]
+
+ public final void comment(@NoLength char[] buf, int start, int length)
+ throws SAXException {
+ needToDropLF = false;
+ // [NOCPP[
+ if (firstCommentLocation == null) {
+ firstCommentLocation = new LocatorImpl(tokenizer);
+ }
+ if (!wantingComments) {
+ return;
+ }
+ // ]NOCPP]
+ if (!isInForeign()) {
+ switch (mode) {
+ case INITIAL:
+ case BEFORE_HTML:
+ case AFTER_AFTER_BODY:
+ case AFTER_AFTER_FRAMESET:
+ /*
+ * A comment token Append a Comment node to the Document
+ * object with the data attribute set to the data given in
+ * the comment token.
+ */
+ appendCommentToDocument(buf, start, length);
+ return;
+ case AFTER_BODY:
+ /*
+ * A comment token Append a Comment node to the first
+ * element in the stack of open elements (the html element),
+ * with the data attribute set to the data given in the
+ * comment token.
+ */
+ flushCharacters();
+ appendComment(stack[0].node, buf, start, length);
+ return;
+ default:
+ break;
+ }
+ }
+ /*
+ * A comment token Append a Comment node to the current node with the
+ * data attribute set to the data given in the comment token.
+ */
+ flushCharacters();
+ appendComment(stack[currentPtr].node, buf, start, length);
+ return;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.common.TokenHandler#characters(char[], int,
+ * int)
+ */
+ public final void characters(@Const @NoLength char[] buf, int start, int length)
+ throws SAXException {
+ // Note: Can't attach error messages to EOF in C++ yet
+
+ // CPPONLY: if (tokenizer.isViewingXmlSource()) {
+ // CPPONLY: return;
+ // CPPONLY: }
+ if (needToDropLF) {
+ needToDropLF = false;
+ if (buf[start] == '\n') {
+ start++;
+ length--;
+ if (length == 0) {
+ return;
+ }
+ }
+ }
+
+ // optimize the most common case
+ switch (mode) {
+ case IN_BODY:
+ case IN_CELL:
+ case IN_CAPTION:
+ if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) {
+ reconstructTheActiveFormattingElements();
+ }
+ // fall through
+ case TEXT:
+ accumulateCharacters(buf, start, length);
+ return;
+ case IN_TABLE:
+ case IN_TABLE_BODY:
+ case IN_ROW:
+ accumulateCharactersForced(buf, start, length);
+ return;
+ default:
+ int end = start + length;
+ charactersloop: for (int i = start; i < end; i++) {
+ switch (buf[i]) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\u000C':
+ /*
+ * A character token that is one of one of U+0009
+ * CHARACTER TABULATION, U+000A LINE FEED (LF),
+ * U+000C FORM FEED (FF), or U+0020 SPACE
+ */
+ switch (mode) {
+ case INITIAL:
+ case BEFORE_HTML:
+ case BEFORE_HEAD:
+ /*
+ * Ignore the token.
+ */
+ start = i + 1;
+ continue;
+ case IN_HEAD:
+ case IN_HEAD_NOSCRIPT:
+ case AFTER_HEAD:
+ case IN_COLUMN_GROUP:
+ case IN_FRAMESET:
+ case AFTER_FRAMESET:
+ /*
+ * Append the character to the current node.
+ */
+ continue;
+ case FRAMESET_OK:
+ case IN_TEMPLATE:
+ case IN_BODY:
+ case IN_CELL:
+ case IN_CAPTION:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ start = i;
+ }
+
+ /*
+ * Reconstruct the active formatting
+ * elements, if any.
+ */
+ if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) {
+ flushCharacters();
+ reconstructTheActiveFormattingElements();
+ }
+ /*
+ * Append the token's character to the
+ * current node.
+ */
+ break charactersloop;
+ case IN_SELECT:
+ case IN_SELECT_IN_TABLE:
+ break charactersloop;
+ case IN_TABLE:
+ case IN_TABLE_BODY:
+ case IN_ROW:
+ accumulateCharactersForced(buf, i, 1);
+ start = i + 1;
+ continue;
+ case AFTER_BODY:
+ case AFTER_AFTER_BODY:
+ case AFTER_AFTER_FRAMESET:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ start = i;
+ }
+ /*
+ * Reconstruct the active formatting
+ * elements, if any.
+ */
+ flushCharacters();
+ reconstructTheActiveFormattingElements();
+ /*
+ * Append the token's character to the
+ * current node.
+ */
+ continue;
+ }
+ default:
+ /*
+ * A character token that is not one of one of
+ * U+0009 CHARACTER TABULATION, U+000A LINE FEED
+ * (LF), U+000C FORM FEED (FF), or U+0020 SPACE
+ */
+ switch (mode) {
+ case INITIAL:
+ /*
+ * Parse error.
+ */
+ // [NOCPP[
+ switch (doctypeExpectation) {
+ case AUTO:
+ err("Non-space characters found without seeing a doctype first. Expected e.g. \u201C<!DOCTYPE html>\u201D.");
+ break;
+ case HTML:
+ // XXX figure out a way to report this in the Gecko View Source case
+ err("Non-space characters found without seeing a doctype first. Expected \u201C<!DOCTYPE html>\u201D.");
+ break;
+ case HTML401_STRICT:
+ err("Non-space characters found without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D.");
+ break;
+ case HTML401_TRANSITIONAL:
+ err("Non-space characters found without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D.");
+ break;
+ case NO_DOCTYPE_ERRORS:
+ }
+ // ]NOCPP]
+ /*
+ *
+ * Set the document to quirks mode.
+ */
+ documentModeInternal(
+ DocumentMode.QUIRKS_MODE, null,
+ null, false);
+ /*
+ * Then, switch to the root element mode of
+ * the tree construction stage
+ */
+ mode = BEFORE_HTML;
+ /*
+ * and reprocess the current token.
+ */
+ i--;
+ continue;
+ case BEFORE_HTML:
+ /*
+ * Create an HTMLElement node with the tag
+ * name html, in the HTML namespace. Append
+ * it to the Document object.
+ */
+ // No need to flush characters here,
+ // because there's nothing to flush.
+ appendHtmlElementToDocumentAndPush();
+ /* Switch to the main mode */
+ mode = BEFORE_HEAD;
+ /*
+ * reprocess the current token.
+ */
+ i--;
+ continue;
+ case BEFORE_HEAD:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ start = i;
+ }
+ /*
+ * /Act as if a start tag token with the tag
+ * name "head" and no attributes had been
+ * seen,
+ */
+ flushCharacters();
+ appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES);
+ mode = IN_HEAD;
+ /*
+ * then reprocess the current token.
+ *
+ * This will result in an empty head element
+ * being generated, with the current token
+ * being reprocessed in the "after head"
+ * insertion mode.
+ */
+ i--;
+ continue;
+ case IN_HEAD:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ start = i;
+ }
+ /*
+ * Act as if an end tag token with the tag
+ * name "head" had been seen,
+ */
+ flushCharacters();
+ pop();
+ mode = AFTER_HEAD;
+ /*
+ * and reprocess the current token.
+ */
+ i--;
+ continue;
+ case IN_HEAD_NOSCRIPT:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ start = i;
+ }
+ /*
+ * Parse error. Act as if an end tag with
+ * the tag name "noscript" had been seen
+ */
+ errNonSpaceInNoscriptInHead();
+ flushCharacters();
+ pop();
+ mode = IN_HEAD;
+ /*
+ * and reprocess the current token.
+ */
+ i--;
+ continue;
+ case AFTER_HEAD:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ start = i;
+ }
+ /*
+ * Act as if a start tag token with the tag
+ * name "body" and no attributes had been
+ * seen,
+ */
+ flushCharacters();
+ appendToCurrentNodeAndPushBodyElement();
+ mode = FRAMESET_OK;
+ /*
+ * and then reprocess the current token.
+ */
+ i--;
+ continue;
+ case FRAMESET_OK:
+ framesetOk = false;
+ mode = IN_BODY;
+ i--;
+ continue;
+ case IN_TEMPLATE:
+ case IN_BODY:
+ case IN_CELL:
+ case IN_CAPTION:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ start = i;
+ }
+ /*
+ * Reconstruct the active formatting
+ * elements, if any.
+ */
+ if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) {
+ flushCharacters();
+ reconstructTheActiveFormattingElements();
+ }
+ /*
+ * Append the token's character to the
+ * current node.
+ */
+ break charactersloop;
+ case IN_TABLE:
+ case IN_TABLE_BODY:
+ case IN_ROW:
+ accumulateCharactersForced(buf, i, 1);
+ start = i + 1;
+ continue;
+ case IN_COLUMN_GROUP:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ start = i;
+ }
+ /*
+ * Act as if an end tag with the tag name
+ * "colgroup" had been seen, and then, if
+ * that token wasn't ignored, reprocess the
+ * current token.
+ */
+ if (currentPtr == 0 || stack[currentPtr].getGroup() ==
+ TreeBuilder.TEMPLATE) {
+ errNonSpaceInColgroupInFragment();
+ start = i + 1;
+ continue;
+ }
+ flushCharacters();
+ pop();
+ mode = IN_TABLE;
+ i--;
+ continue;
+ case IN_SELECT:
+ case IN_SELECT_IN_TABLE:
+ break charactersloop;
+ case AFTER_BODY:
+ errNonSpaceAfterBody();
+ fatal();
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ i--;
+ continue;
+ case IN_FRAMESET:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ // start index is adjusted below.
+ }
+ /*
+ * Parse error.
+ */
+ errNonSpaceInFrameset();
+ /*
+ * Ignore the token.
+ */
+ start = i + 1;
+ continue;
+ case AFTER_FRAMESET:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ // start index is adjusted below.
+ }
+ /*
+ * Parse error.
+ */
+ errNonSpaceAfterFrameset();
+ /*
+ * Ignore the token.
+ */
+ start = i + 1;
+ continue;
+ case AFTER_AFTER_BODY:
+ /*
+ * Parse error.
+ */
+ errNonSpaceInTrailer();
+ /*
+ * Switch back to the main mode and
+ * reprocess the token.
+ */
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ i--;
+ continue;
+ case AFTER_AFTER_FRAMESET:
+ if (start < i) {
+ accumulateCharacters(buf, start, i
+ - start);
+ // start index is adjusted below.
+ }
+ /*
+ * Parse error.
+ */
+ errNonSpaceInTrailer();
+ /*
+ * Ignore the token.
+ */
+ start = i + 1;
+ continue;
+ }
+ }
+ }
+ if (start < end) {
+ accumulateCharacters(buf, start, end - start);
+ }
+ }
+ }
+
+ /**
+ * @see nu.validator.htmlparser.common.TokenHandler#zeroOriginatingReplacementCharacter()
+ */
+ public void zeroOriginatingReplacementCharacter() throws SAXException {
+ if (mode == TEXT) {
+ accumulateCharacters(REPLACEMENT_CHARACTER, 0, 1);
+ return;
+ }
+ if (currentPtr >= 0) {
+ if (isSpecialParentInForeign(stack[currentPtr])) {
+ return;
+ }
+ accumulateCharacters(REPLACEMENT_CHARACTER, 0, 1);
+ }
+ }
+
+ public final void eof() throws SAXException {
+ flushCharacters();
+ // Note: Can't attach error messages to EOF in C++ yet
+ eofloop: for (;;) {
+ switch (mode) {
+ case INITIAL:
+ /*
+ * Parse error.
+ */
+ // [NOCPP[
+ switch (doctypeExpectation) {
+ case AUTO:
+ err("End of file seen without seeing a doctype first. Expected e.g. \u201C<!DOCTYPE html>\u201D.");
+ break;
+ case HTML:
+ err("End of file seen without seeing a doctype first. Expected \u201C<!DOCTYPE html>\u201D.");
+ break;
+ case HTML401_STRICT:
+ err("End of file seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D.");
+ break;
+ case HTML401_TRANSITIONAL:
+ err("End of file seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D.");
+ break;
+ case NO_DOCTYPE_ERRORS:
+ }
+ // ]NOCPP]
+ /*
+ *
+ * Set the document to quirks mode.
+ */
+ documentModeInternal(DocumentMode.QUIRKS_MODE, null, null,
+ false);
+ /*
+ * Then, switch to the root element mode of the tree
+ * construction stage
+ */
+ mode = BEFORE_HTML;
+ /*
+ * and reprocess the current token.
+ */
+ continue;
+ case BEFORE_HTML:
+ /*
+ * Create an HTMLElement node with the tag name html, in the
+ * HTML namespace. Append it to the Document object.
+ */
+ appendHtmlElementToDocumentAndPush();
+ // XXX application cache manifest
+ /* Switch to the main mode */
+ mode = BEFORE_HEAD;
+ /*
+ * reprocess the current token.
+ */
+ continue;
+ case BEFORE_HEAD:
+ appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES);
+ mode = IN_HEAD;
+ continue;
+ case IN_HEAD:
+ // [NOCPP[
+ if (errorHandler != null && currentPtr > 1) {
+ errEofWithUnclosedElements();
+ }
+ // ]NOCPP]
+ while (currentPtr > 0) {
+ popOnEof();
+ }
+ mode = AFTER_HEAD;
+ continue;
+ case IN_HEAD_NOSCRIPT:
+ // [NOCPP[
+ errEofWithUnclosedElements();
+ // ]NOCPP]
+ while (currentPtr > 1) {
+ popOnEof();
+ }
+ mode = IN_HEAD;
+ continue;
+ case AFTER_HEAD:
+ appendToCurrentNodeAndPushBodyElement();
+ mode = IN_BODY;
+ continue;
+ case IN_TABLE_BODY:
+ case IN_ROW:
+ case IN_TABLE:
+ case IN_SELECT_IN_TABLE:
+ case IN_SELECT:
+ case IN_COLUMN_GROUP:
+ case FRAMESET_OK:
+ case IN_CAPTION:
+ case IN_CELL:
+ case IN_BODY:
+ // [NOCPP[
+ // i > 0 to stop in time in the foreign fragment case.
+ openelementloop: for (int i = currentPtr; i > 0; i--) {
+ int group = stack[i].getGroup();
+ switch (group) {
+ case DD_OR_DT:
+ case LI:
+ case P:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TD_OR_TH:
+ case BODY:
+ case HTML:
+ break;
+ default:
+ errEofWithUnclosedElements();
+ break openelementloop;
+ }
+ }
+ // ]NOCPP]
+
+ if (isTemplateModeStackEmpty()) {
+ break eofloop;
+ }
+
+ // fall through to IN_TEMPLATE
+ case IN_TEMPLATE:
+ int eltPos = findLast("template");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ assert fragment;
+ break eofloop;
+ }
+ if (errorHandler != null) {
+ errUnclosedElements(eltPos, "template");
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ popTemplateMode();
+ resetTheInsertionMode();
+
+ // Reprocess token.
+ continue;
+ case TEXT:
+ // [NOCPP[
+ if (errorHandler != null) {
+ errNoCheck("End of file seen when expecting text or an end tag.");
+ errListUnclosedStartTags(0);
+ }
+ // ]NOCPP]
+ // XXX mark script as already executed
+ if (originalMode == AFTER_HEAD) {
+ popOnEof();
+ }
+ popOnEof();
+ mode = originalMode;
+ continue;
+ case IN_FRAMESET:
+ // [NOCPP[
+ if (errorHandler != null && currentPtr > 0) {
+ errEofWithUnclosedElements();
+ }
+ // ]NOCPP]
+ break eofloop;
+ case AFTER_BODY:
+ case AFTER_FRAMESET:
+ case AFTER_AFTER_BODY:
+ case AFTER_AFTER_FRAMESET:
+ default:
+ // [NOCPP[
+ if (currentPtr == 0) { // This silliness is here to poison
+ // buggy compiler optimizations in
+ // GWT
+ System.currentTimeMillis();
+ }
+ // ]NOCPP]
+ break eofloop;
+ }
+ }
+ while (currentPtr > 0) {
+ popOnEof();
+ }
+ if (!fragment) {
+ popOnEof();
+ }
+ /* Stop parsing. */
+ }
+
+ /**
+ * @see nu.validator.htmlparser.common.TokenHandler#endTokenization()
+ */
+ public final void endTokenization() throws SAXException {
+ formPointer = null;
+ headPointer = null;
+ contextName = null;
+ contextNode = null;
+ deepTreeSurrogateParent = null;
+ templateModeStack = null;
+ if (stack != null) {
+ while (currentPtr > -1) {
+ stack[currentPtr].release();
+ currentPtr--;
+ }
+ stack = null;
+ }
+ if (listOfActiveFormattingElements != null) {
+ while (listPtr > -1) {
+ if (listOfActiveFormattingElements[listPtr] != null) {
+ listOfActiveFormattingElements[listPtr].release();
+ }
+ listPtr--;
+ }
+ listOfActiveFormattingElements = null;
+ }
+ // [NOCPP[
+ idLocations.clear();
+ // ]NOCPP]
+ charBuffer = null;
+ end();
+ }
+
+ public final void startTag(ElementName elementName,
+ HtmlAttributes attributes, boolean selfClosing) throws SAXException {
+ flushCharacters();
+
+ // [NOCPP[
+ if (errorHandler != null) {
+ // ID uniqueness
+ @IdType String id = attributes.getId();
+ if (id != null) {
+ LocatorImpl oldLoc = idLocations.get(id);
+ if (oldLoc != null) {
+ err("Duplicate ID \u201C" + id + "\u201D.");
+ errorHandler.warning(new SAXParseException(
+ "The first occurrence of ID \u201C" + id
+ + "\u201D was here.", oldLoc));
+ } else {
+ idLocations.put(id, new LocatorImpl(tokenizer));
+ }
+ }
+ }
+ // ]NOCPP]
+
+ int eltPos;
+ needToDropLF = false;
+ starttagloop: for (;;) {
+ int group = elementName.getGroup();
+ @Local String name = elementName.getName();
+ if (isInForeign()) {
+ StackNode<T> currentNode = stack[currentPtr];
+ @NsUri String currNs = currentNode.ns;
+ if (!(currentNode.isHtmlIntegrationPoint() || (currNs == "http://www.w3.org/1998/Math/MathML" && ((currentNode.getGroup() == MI_MO_MN_MS_MTEXT && group != MGLYPH_OR_MALIGNMARK) || (currentNode.getGroup() == ANNOTATION_XML && group == SVG))))) {
+ switch (group) {
+ case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
+ case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU:
+ case BODY:
+ case BR:
+ case RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR:
+ case DD_OR_DT:
+ case UL_OR_OL_OR_DL:
+ case EMBED:
+ case IMG:
+ case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6:
+ case HEAD:
+ case HR:
+ case LI:
+ case META:
+ case NOBR:
+ case P:
+ case PRE_OR_LISTING:
+ case TABLE:
+ case FONT:
+ // re-check FONT to deal with the special case
+ if (!(group == FONT && !(attributes.contains(AttributeName.COLOR)
+ || attributes.contains(AttributeName.FACE) || attributes.contains(AttributeName.SIZE)))) {
+ errHtmlStartTagInForeignContext(name);
+ if (!fragment) {
+ while (!isSpecialParentInForeign(stack[currentPtr])) {
+ pop();
+ }
+ continue starttagloop;
+ } // else fall thru
+ }
+ // else fall thru
+ default:
+ if ("http://www.w3.org/2000/svg" == currNs) {
+ attributes.adjustForSvg();
+ if (selfClosing) {
+ appendVoidElementToCurrentMayFosterSVG(
+ elementName, attributes);
+ selfClosing = false;
+ } else {
+ appendToCurrentNodeAndPushElementMayFosterSVG(
+ elementName, attributes);
+ }
+ attributes = null; // CPP
+ break starttagloop;
+ } else {
+ attributes.adjustForMath();
+ if (selfClosing) {
+ appendVoidElementToCurrentMayFosterMathML(
+ elementName, attributes);
+ selfClosing = false;
+ } else {
+ appendToCurrentNodeAndPushElementMayFosterMathML(
+ elementName, attributes);
+ }
+ attributes = null; // CPP
+ break starttagloop;
+ }
+ } // switch
+ } // foreignObject / annotation-xml
+ }
+ switch (mode) {
+ case IN_TEMPLATE:
+ switch (group) {
+ case COL:
+ popTemplateMode();
+ pushTemplateMode(IN_COLUMN_GROUP);
+ mode = IN_COLUMN_GROUP;
+ // Reprocess token.
+ continue;
+ case CAPTION:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ popTemplateMode();
+ pushTemplateMode(IN_TABLE);
+ mode = IN_TABLE;
+ // Reprocess token.
+ continue;
+ case TR:
+ popTemplateMode();
+ pushTemplateMode(IN_TABLE_BODY);
+ mode = IN_TABLE_BODY;
+ // Reprocess token.
+ continue;
+ case TD_OR_TH:
+ popTemplateMode();
+ pushTemplateMode(IN_ROW);
+ mode = IN_ROW;
+ // Reprocess token.
+ continue;
+ case META:
+ checkMetaCharset(attributes);
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ case TITLE:
+ startTagTitleInHead(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case BASE:
+ case LINK_OR_BASEFONT_OR_BGSOUND:
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ case SCRIPT:
+ startTagScriptInHead(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case NOFRAMES:
+ case STYLE:
+ startTagGenericRawText(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case TEMPLATE:
+ startTagTemplateInHead(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ default:
+ popTemplateMode();
+ pushTemplateMode(IN_BODY);
+ mode = IN_BODY;
+ // Reprocess token.
+ continue;
+ }
+ case IN_ROW:
+ switch (group) {
+ case TD_OR_TH:
+ clearStackBackTo(findLastOrRoot(TreeBuilder.TR));
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ mode = IN_CELL;
+ insertMarker();
+ attributes = null; // CPP
+ break starttagloop;
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ eltPos = findLastOrRoot(TreeBuilder.TR);
+ if (eltPos == 0) {
+ assert fragment || isTemplateContents();
+ errNoTableRowToClose();
+ break starttagloop;
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE_BODY;
+ continue;
+ default:
+ // fall through to IN_TABLE
+ }
+ case IN_TABLE_BODY:
+ switch (group) {
+ case TR:
+ clearStackBackTo(findLastInTableScopeOrRootTemplateTbodyTheadTfoot());
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ mode = IN_ROW;
+ attributes = null; // CPP
+ break starttagloop;
+ case TD_OR_TH:
+ errStartTagInTableBody(name);
+ clearStackBackTo(findLastInTableScopeOrRootTemplateTbodyTheadTfoot());
+ appendToCurrentNodeAndPushElement(
+ ElementName.TR,
+ HtmlAttributes.EMPTY_ATTRIBUTES);
+ mode = IN_ROW;
+ continue;
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ eltPos = findLastInTableScopeOrRootTemplateTbodyTheadTfoot();
+ if (eltPos == 0 || stack[eltPos].getGroup() == TEMPLATE) {
+ assert fragment || isTemplateContents();
+ errStrayStartTag(name);
+ break starttagloop;
+ } else {
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE;
+ continue;
+ }
+ default:
+ // fall through to IN_TABLE
+ }
+ case IN_TABLE:
+ intableloop: for (;;) {
+ switch (group) {
+ case CAPTION:
+ clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE));
+ insertMarker();
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ mode = IN_CAPTION;
+ attributes = null; // CPP
+ break starttagloop;
+ case COLGROUP:
+ clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE));
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ mode = IN_COLUMN_GROUP;
+ attributes = null; // CPP
+ break starttagloop;
+ case COL:
+ clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE));
+ appendToCurrentNodeAndPushElement(
+ ElementName.COLGROUP,
+ HtmlAttributes.EMPTY_ATTRIBUTES);
+ mode = IN_COLUMN_GROUP;
+ continue starttagloop;
+ case TBODY_OR_THEAD_OR_TFOOT:
+ clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE));
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ mode = IN_TABLE_BODY;
+ attributes = null; // CPP
+ break starttagloop;
+ case TR:
+ case TD_OR_TH:
+ clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE));
+ appendToCurrentNodeAndPushElement(
+ ElementName.TBODY,
+ HtmlAttributes.EMPTY_ATTRIBUTES);
+ mode = IN_TABLE_BODY;
+ continue starttagloop;
+ case TEMPLATE:
+ // fall through to IN_HEAD
+ break intableloop;
+ case TABLE:
+ errTableSeenWhileTableOpen();
+ eltPos = findLastInTableScope(name);
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ assert fragment || isTemplateContents();
+ break starttagloop;
+ }
+ generateImpliedEndTags();
+ if (errorHandler != null && !isCurrent("table")) {
+ errNoCheckUnclosedElementsOnStack();
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ continue starttagloop;
+ case SCRIPT:
+ // XXX need to manage much more stuff
+ // here if
+ // supporting
+ // document.write()
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.SCRIPT_DATA, elementName);
+ attributes = null; // CPP
+ break starttagloop;
+ case STYLE:
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.RAWTEXT, elementName);
+ attributes = null; // CPP
+ break starttagloop;
+ case INPUT:
+ errStartTagInTable(name);
+ if (!Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "hidden",
+ attributes.getValue(AttributeName.TYPE))) {
+ break intableloop;
+ }
+ appendVoidInputToCurrent(
+ attributes,
+ formPointer);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ case FORM:
+ if (formPointer != null || isTemplateContents()) {
+ errFormWhenFormOpen();
+ break starttagloop;
+ } else {
+ errStartTagInTable(name);
+ appendVoidFormToCurrent(attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ }
+ default:
+ errStartTagInTable(name);
+ // fall through to IN_BODY
+ break intableloop;
+ }
+ }
+ case IN_CAPTION:
+ switch (group) {
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ case TD_OR_TH:
+ errStrayStartTag(name);
+ eltPos = findLastInTableScope("caption");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ break starttagloop;
+ }
+ generateImpliedEndTags();
+ if (errorHandler != null && currentPtr != eltPos) {
+ errNoCheckUnclosedElementsOnStack();
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ mode = IN_TABLE;
+ continue;
+ default:
+ // fall through to IN_BODY
+ }
+ case IN_CELL:
+ switch (group) {
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ case TD_OR_TH:
+ eltPos = findLastInTableScopeTdTh();
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errNoCellToClose();
+ break starttagloop;
+ } else {
+ closeTheCell(eltPos);
+ continue;
+ }
+ default:
+ // fall through to IN_BODY
+ }
+ case FRAMESET_OK:
+ switch (group) {
+ case FRAMESET:
+ if (mode == FRAMESET_OK) {
+ if (currentPtr == 0 || stack[1].getGroup() != BODY) {
+ assert fragment || isTemplateContents();
+ errStrayStartTag(name);
+ break starttagloop;
+ } else {
+ errFramesetStart();
+ detachFromParent(stack[1].node);
+ while (currentPtr > 0) {
+ pop();
+ }
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ mode = IN_FRAMESET;
+ attributes = null; // CPP
+ break starttagloop;
+ }
+ } else {
+ errStrayStartTag(name);
+ break starttagloop;
+ }
+ // NOT falling through!
+ case PRE_OR_LISTING:
+ case LI:
+ case DD_OR_DT:
+ case BUTTON:
+ case MARQUEE_OR_APPLET:
+ case OBJECT:
+ case TABLE:
+ case AREA_OR_WBR:
+ case BR:
+ case EMBED:
+ case IMG:
+ case INPUT:
+ case KEYGEN:
+ case HR:
+ case TEXTAREA:
+ case XMP:
+ case IFRAME:
+ case SELECT:
+ if (mode == FRAMESET_OK
+ && !(group == INPUT && Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "hidden",
+ attributes.getValue(AttributeName.TYPE)))) {
+ framesetOk = false;
+ mode = IN_BODY;
+ }
+ // fall through to IN_BODY
+ default:
+ // fall through to IN_BODY
+ }
+ case IN_BODY:
+ inbodyloop: for (;;) {
+ switch (group) {
+ case HTML:
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ case BASE:
+ case LINK_OR_BASEFONT_OR_BGSOUND:
+ case META:
+ case STYLE:
+ case SCRIPT:
+ case TITLE:
+ case TEMPLATE:
+ // Fall through to IN_HEAD
+ break inbodyloop;
+ case BODY:
+ if (currentPtr == 0 || stack[1].getGroup() != BODY || isTemplateContents()) {
+ assert fragment || isTemplateContents();
+ errStrayStartTag(name);
+ break starttagloop;
+ }
+ errFooSeenWhenFooOpen(name);
+ framesetOk = false;
+ if (mode == FRAMESET_OK) {
+ mode = IN_BODY;
+ }
+ if (addAttributesToBody(attributes)) {
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ case P:
+ case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU:
+ case UL_OR_OL_OR_DL:
+ case ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY:
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6:
+ implicitlyCloseP();
+ if (stack[currentPtr].getGroup() == H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6) {
+ errHeadingWhenHeadingOpen();
+ pop();
+ }
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case FIELDSET:
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes, formPointer);
+ attributes = null; // CPP
+ break starttagloop;
+ case PRE_OR_LISTING:
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ needToDropLF = true;
+ attributes = null; // CPP
+ break starttagloop;
+ case FORM:
+ if (formPointer != null && !isTemplateContents()) {
+ errFormWhenFormOpen();
+ break starttagloop;
+ } else {
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushFormElementMayFoster(attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ }
+ case LI:
+ case DD_OR_DT:
+ eltPos = currentPtr;
+ for (;;) {
+ StackNode<T> node = stack[eltPos]; // weak
+ // ref
+ if (node.getGroup() == group) { // LI or
+ // DD_OR_DT
+ generateImpliedEndTagsExceptFor(node.name);
+ if (errorHandler != null
+ && eltPos != currentPtr) {
+ errUnclosedElementsImplied(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ break;
+ } else if (eltPos == 0 || (node.isSpecial()
+ && (node.ns != "http://www.w3.org/1999/xhtml"
+ || (node.name != "p"
+ && node.name != "address"
+ && node.name != "div")))) {
+ break;
+ }
+ eltPos--;
+ }
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case PLAINTEXT:
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.PLAINTEXT, elementName);
+ attributes = null; // CPP
+ break starttagloop;
+ case A:
+ int activeAPos = findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker("a");
+ if (activeAPos != -1) {
+ errFooSeenWhenFooOpen(name);
+ StackNode<T> activeA = listOfActiveFormattingElements[activeAPos];
+ activeA.retain();
+ adoptionAgencyEndTag("a");
+ removeFromStack(activeA);
+ activeAPos = findInListOfActiveFormattingElements(activeA);
+ if (activeAPos != -1) {
+ removeFromListOfActiveFormattingElements(activeAPos);
+ }
+ activeA.release();
+ }
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushFormattingElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
+ case FONT:
+ reconstructTheActiveFormattingElements();
+ maybeForgetEarlierDuplicateFormattingElement(elementName.getName(), attributes);
+ appendToCurrentNodeAndPushFormattingElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case NOBR:
+ reconstructTheActiveFormattingElements();
+ if (TreeBuilder.NOT_FOUND_ON_STACK != findLastInScope("nobr")) {
+ errFooSeenWhenFooOpen(name);
+ adoptionAgencyEndTag("nobr");
+ reconstructTheActiveFormattingElements();
+ }
+ appendToCurrentNodeAndPushFormattingElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case BUTTON:
+ eltPos = findLastInScope(name);
+ if (eltPos != TreeBuilder.NOT_FOUND_ON_STACK) {
+ errFooSeenWhenFooOpen(name);
+ generateImpliedEndTags();
+ if (errorHandler != null
+ && !isCurrent(name)) {
+ errUnclosedElementsImplied(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ continue starttagloop;
+ } else {
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes, formPointer);
+ attributes = null; // CPP
+ break starttagloop;
+ }
+ case OBJECT:
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes, formPointer);
+ insertMarker();
+ attributes = null; // CPP
+ break starttagloop;
+ case MARQUEE_OR_APPLET:
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ insertMarker();
+ attributes = null; // CPP
+ break starttagloop;
+ case TABLE:
+ // The only quirk. Blame Hixie and
+ // Acid2.
+ if (!quirks) {
+ implicitlyCloseP();
+ }
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ mode = IN_TABLE;
+ attributes = null; // CPP
+ break starttagloop;
+ case BR:
+ case EMBED:
+ case AREA_OR_WBR:
+ reconstructTheActiveFormattingElements();
+ // FALL THROUGH to PARAM_OR_SOURCE_OR_TRACK
+ // CPPONLY: case MENUITEM:
+ case PARAM_OR_SOURCE_OR_TRACK:
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ case HR:
+ implicitlyCloseP();
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ case IMAGE:
+ errImage();
+ elementName = ElementName.IMG;
+ continue starttagloop;
+ case IMG:
+ case KEYGEN:
+ case INPUT:
+ reconstructTheActiveFormattingElements();
+ appendVoidElementToCurrentMayFoster(
+ elementName, attributes,
+ formPointer);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ // CPPONLY:case ISINDEX:
+ // CPPONLY: errIsindex();
+ // CPPONLY: if (formPointer != null && !isTemplateContents()) {
+ // CPPONLY: break starttagloop;
+ // CPPONLY: }
+ // CPPONLY: implicitlyCloseP();
+ // CPPONLY: HtmlAttributes formAttrs = new HtmlAttributes(0);
+ // CPPONLY: int actionIndex = attributes.getIndex(AttributeName.ACTION);
+ // CPPONLY: if (actionIndex > -1) {
+ // CPPONLY: formAttrs.addAttribute(
+ // CPPONLY: AttributeName.ACTION,
+ // CPPONLY: attributes.getValueNoBoundsCheck(actionIndex),
+ // CPPONLY: attributes.getLineNoBoundsCheck(actionIndex)
+ // CPPONLY: );
+ // CPPONLY: }
+ // CPPONLY: appendToCurrentNodeAndPushFormElementMayFoster(formAttrs);
+ // CPPONLY: appendVoidElementToCurrentMayFoster(
+ // CPPONLY: ElementName.HR,
+ // CPPONLY: HtmlAttributes.EMPTY_ATTRIBUTES);
+ // CPPONLY: appendToCurrentNodeAndPushElementMayFoster(
+ // CPPONLY: ElementName.LABEL,
+ // CPPONLY: HtmlAttributes.EMPTY_ATTRIBUTES);
+ // CPPONLY: int promptIndex = attributes.getIndex(AttributeName.PROMPT);
+ // CPPONLY: if (promptIndex > -1) {
+ // CPPONLY: @Auto char[] prompt = Portability.newCharArrayFromString(attributes.getValueNoBoundsCheck(promptIndex));
+ // CPPONLY: appendCharacters(stack[currentPtr].node,
+ // CPPONLY: prompt, 0, prompt.length);
+ // CPPONLY: } else {
+ // CPPONLY: appendIsindexPrompt(stack[currentPtr].node);
+ // CPPONLY: }
+ // CPPONLY: HtmlAttributes inputAttributes = new HtmlAttributes(
+ // CPPONLY: 0);
+ // CPPONLY: inputAttributes.addAttribute(
+ // CPPONLY: AttributeName.NAME,
+ // CPPONLY: Portability.newStringFromLiteral("isindex"),
+ // CPPONLY: tokenizer.getLineNumber()
+ // CPPONLY: );
+ // CPPONLY: for (int i = 0; i < attributes.getLength(); i++) {
+ // CPPONLY: @Local String attributeQName = attributes.getLocalNameNoBoundsCheck(i);
+ // CPPONLY: if ("name" == attributeQName
+ // CPPONLY: || "prompt" == attributeQName) {
+ // CPPONLY: attributes.releaseValue(i);
+ // CPPONLY: } else if ("action" != attributeQName) {
+ // CPPONLY: inputAttributes.AddAttributeWithLocal(
+ // CPPONLY: attributeQName,
+ // CPPONLY: attributes.getValueNoBoundsCheck(i),
+ // CPPONLY: attributes.getLineNoBoundsCheck(i)
+ // CPPONLY: );
+ // CPPONLY: }
+ // CPPONLY: }
+ // CPPONLY: attributes.clearWithoutReleasingContents();
+ // CPPONLY: appendVoidElementToCurrentMayFoster(
+ // CPPONLY: ElementName.INPUT,
+ // CPPONLY: inputAttributes, formPointer);
+ // CPPONLY: pop(); // label
+ // CPPONLY: appendVoidElementToCurrentMayFoster(
+ // CPPONLY: ElementName.HR,
+ // CPPONLY: HtmlAttributes.EMPTY_ATTRIBUTES);
+ // CPPONLY: pop(); // form
+ // CPPONLY:
+ // CPPONLY: if (!isTemplateContents()) {
+ // CPPONLY: formPointer = null;
+ // CPPONLY: }
+ // CPPONLY:
+ // CPPONLY: selfClosing = false;
+ // CPPONLY: // Don't delete attributes, they are deleted
+ // CPPONLY: // later
+ // CPPONLY: break starttagloop;
+ case TEXTAREA:
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes, formPointer);
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.RCDATA, elementName);
+ originalMode = mode;
+ mode = TEXT;
+ needToDropLF = true;
+ attributes = null; // CPP
+ break starttagloop;
+ case XMP:
+ implicitlyCloseP();
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.RAWTEXT, elementName);
+ attributes = null; // CPP
+ break starttagloop;
+ case NOSCRIPT:
+ if (!scriptingEnabled) {
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ } else {
+ // fall through
+ }
+ case NOFRAMES:
+ case IFRAME:
+ case NOEMBED:
+ startTagGenericRawText(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case SELECT:
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes, formPointer);
+ switch (mode) {
+ case IN_TABLE:
+ case IN_CAPTION:
+ case IN_COLUMN_GROUP:
+ case IN_TABLE_BODY:
+ case IN_ROW:
+ case IN_CELL:
+ mode = IN_SELECT_IN_TABLE;
+ break;
+ default:
+ mode = IN_SELECT;
+ break;
+ }
+ attributes = null; // CPP
+ break starttagloop;
+ case OPTGROUP:
+ case OPTION:
+ if (isCurrent("option")) {
+ pop();
+ }
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case RB_OR_RTC:
+ eltPos = findLastInScope("ruby");
+ if (eltPos != NOT_FOUND_ON_STACK) {
+ generateImpliedEndTags();
+ }
+ if (eltPos != currentPtr) {
+ if (eltPos == NOT_FOUND_ON_STACK) {
+ errStartTagSeenWithoutRuby(name);
+ } else {
+ errUnclosedChildrenInRuby();
+ }
+ }
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case RT_OR_RP:
+ eltPos = findLastInScope("ruby");
+ if (eltPos != NOT_FOUND_ON_STACK) {
+ generateImpliedEndTagsExceptFor("rtc");
+ }
+ if (eltPos != currentPtr) {
+ if (!isCurrent("rtc")) {
+ if (eltPos == NOT_FOUND_ON_STACK) {
+ errStartTagSeenWithoutRuby(name);
+ } else {
+ errUnclosedChildrenInRuby();
+ }
+ }
+ }
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case MATH:
+ reconstructTheActiveFormattingElements();
+ attributes.adjustForMath();
+ if (selfClosing) {
+ appendVoidElementToCurrentMayFosterMathML(
+ elementName, attributes);
+ selfClosing = false;
+ } else {
+ appendToCurrentNodeAndPushElementMayFosterMathML(
+ elementName, attributes);
+ }
+ attributes = null; // CPP
+ break starttagloop;
+ case SVG:
+ reconstructTheActiveFormattingElements();
+ attributes.adjustForSvg();
+ if (selfClosing) {
+ appendVoidElementToCurrentMayFosterSVG(
+ elementName,
+ attributes);
+ selfClosing = false;
+ } else {
+ appendToCurrentNodeAndPushElementMayFosterSVG(
+ elementName, attributes);
+ }
+ attributes = null; // CPP
+ break starttagloop;
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ case TD_OR_TH:
+ case FRAME:
+ case FRAMESET:
+ case HEAD:
+ errStrayStartTag(name);
+ break starttagloop;
+ case OUTPUT:
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes, formPointer);
+ attributes = null; // CPP
+ break starttagloop;
+ default:
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ }
+ }
+ case IN_HEAD:
+ inheadloop: for (;;) {
+ switch (group) {
+ case HTML:
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ case BASE:
+ case LINK_OR_BASEFONT_OR_BGSOUND:
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ case META:
+ // Fall through to IN_HEAD_NOSCRIPT
+ break inheadloop;
+ case TITLE:
+ startTagTitleInHead(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case NOSCRIPT:
+ if (scriptingEnabled) {
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.RAWTEXT, elementName);
+ } else {
+ appendToCurrentNodeAndPushElementMayFoster(
+ elementName,
+ attributes);
+ mode = IN_HEAD_NOSCRIPT;
+ }
+ attributes = null; // CPP
+ break starttagloop;
+ case SCRIPT:
+ startTagScriptInHead(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case STYLE:
+ case NOFRAMES:
+ startTagGenericRawText(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case HEAD:
+ /* Parse error. */
+ errFooSeenWhenFooOpen(name);
+ /* Ignore the token. */
+ break starttagloop;
+ case TEMPLATE:
+ startTagTemplateInHead(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ default:
+ pop();
+ mode = AFTER_HEAD;
+ continue starttagloop;
+ }
+ }
+ case IN_HEAD_NOSCRIPT:
+ switch (group) {
+ case HTML:
+ // XXX did Hixie really mean to omit "base"
+ // here?
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ case LINK_OR_BASEFONT_OR_BGSOUND:
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ case META:
+ checkMetaCharset(attributes);
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ case STYLE:
+ case NOFRAMES:
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.RAWTEXT, elementName);
+ attributes = null; // CPP
+ break starttagloop;
+ case HEAD:
+ errFooSeenWhenFooOpen(name);
+ break starttagloop;
+ case NOSCRIPT:
+ errFooSeenWhenFooOpen(name);
+ break starttagloop;
+ default:
+ errBadStartTagInHead(name);
+ pop();
+ mode = IN_HEAD;
+ continue;
+ }
+ case IN_COLUMN_GROUP:
+ switch (group) {
+ case HTML:
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ case COL:
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ case TEMPLATE:
+ startTagTemplateInHead(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ default:
+ if (currentPtr == 0 || stack[currentPtr].getGroup() == TEMPLATE) {
+ assert fragment || isTemplateContents();
+ errGarbageInColgroup();
+ break starttagloop;
+ }
+ pop();
+ mode = IN_TABLE;
+ continue;
+ }
+ case IN_SELECT_IN_TABLE:
+ switch (group) {
+ case CAPTION:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ case TD_OR_TH:
+ case TABLE:
+ errStartTagWithSelectOpen(name);
+ eltPos = findLastInTableScope("select");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ assert fragment;
+ break starttagloop; // http://www.w3.org/Bugs/Public/show_bug.cgi?id=8375
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ continue;
+ default:
+ // fall through to IN_SELECT
+ }
+ case IN_SELECT:
+ switch (group) {
+ case HTML:
+ errStrayStartTag(name);
+ if (!fragment) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ case OPTION:
+ if (isCurrent("option")) {
+ pop();
+ }
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case OPTGROUP:
+ if (isCurrent("option")) {
+ pop();
+ }
+ if (isCurrent("optgroup")) {
+ pop();
+ }
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case SELECT:
+ errStartSelectWhereEndSelectExpected();
+ eltPos = findLastInTableScope(name);
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ assert fragment;
+ errNoSelectInTableScope();
+ break starttagloop;
+ } else {
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ break starttagloop;
+ }
+ case INPUT:
+ case TEXTAREA:
+ case KEYGEN:
+ errStartTagWithSelectOpen(name);
+ eltPos = findLastInTableScope("select");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ assert fragment;
+ break starttagloop;
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ continue;
+ case SCRIPT:
+ startTagScriptInHead(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case TEMPLATE:
+ startTagTemplateInHead(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ default:
+ errStrayStartTag(name);
+ break starttagloop;
+ }
+ case AFTER_BODY:
+ switch (group) {
+ case HTML:
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ default:
+ errStrayStartTag(name);
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ continue;
+ }
+ case IN_FRAMESET:
+ switch (group) {
+ case FRAMESET:
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ case FRAME:
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ attributes = null; // CPP
+ break starttagloop;
+ default:
+ // fall through to AFTER_FRAMESET
+ }
+ case AFTER_FRAMESET:
+ switch (group) {
+ case HTML:
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ case NOFRAMES:
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.RAWTEXT, elementName);
+ attributes = null; // CPP
+ break starttagloop;
+ default:
+ errStrayStartTag(name);
+ break starttagloop;
+ }
+ case INITIAL:
+ /*
+ * Parse error.
+ */
+ // [NOCPP[
+ switch (doctypeExpectation) {
+ case AUTO:
+ err("Start tag seen without seeing a doctype first. Expected e.g. \u201C<!DOCTYPE html>\u201D.");
+ break;
+ case HTML:
+ // ]NOCPP]
+ errStartTagWithoutDoctype();
+ // [NOCPP[
+ break;
+ case HTML401_STRICT:
+ err("Start tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D.");
+ break;
+ case HTML401_TRANSITIONAL:
+ err("Start tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D.");
+ break;
+ case NO_DOCTYPE_ERRORS:
+ }
+ // ]NOCPP]
+ /*
+ *
+ * Set the document to quirks mode.
+ */
+ documentModeInternal(DocumentMode.QUIRKS_MODE, null, null,
+ false);
+ /*
+ * Then, switch to the root element mode of the tree
+ * construction stage
+ */
+ mode = BEFORE_HTML;
+ /*
+ * and reprocess the current token.
+ */
+ continue;
+ case BEFORE_HTML:
+ switch (group) {
+ case HTML:
+ // optimize error check and streaming SAX by
+ // hoisting
+ // "html" handling here.
+ if (attributes == HtmlAttributes.EMPTY_ATTRIBUTES) {
+ // This has the right magic side effect
+ // that
+ // it
+ // makes attributes in SAX Tree mutable.
+ appendHtmlElementToDocumentAndPush();
+ } else {
+ appendHtmlElementToDocumentAndPush(attributes);
+ }
+ // XXX application cache should fire here
+ mode = BEFORE_HEAD;
+ attributes = null; // CPP
+ break starttagloop;
+ default:
+ /*
+ * Create an HTMLElement node with the tag name
+ * html, in the HTML namespace. Append it to the
+ * Document object.
+ */
+ appendHtmlElementToDocumentAndPush();
+ /* Switch to the main mode */
+ mode = BEFORE_HEAD;
+ /*
+ * reprocess the current token.
+ */
+ continue;
+ }
+ case BEFORE_HEAD:
+ switch (group) {
+ case HTML:
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ case HEAD:
+ /*
+ * A start tag whose tag name is "head"
+ *
+ * Create an element for the token.
+ *
+ * Set the head element pointer to this new element
+ * node.
+ *
+ * Append the new element to the current node and
+ * push it onto the stack of open elements.
+ */
+ appendToCurrentNodeAndPushHeadElement(attributes);
+ /*
+ * Change the insertion mode to "in head".
+ */
+ mode = IN_HEAD;
+ attributes = null; // CPP
+ break starttagloop;
+ default:
+ /*
+ * Any other start tag token
+ *
+ * Act as if a start tag token with the tag name
+ * "head" and no attributes had been seen,
+ */
+ appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES);
+ mode = IN_HEAD;
+ /*
+ * then reprocess the current token.
+ *
+ * This will result in an empty head element being
+ * generated, with the current token being
+ * reprocessed in the "after head" insertion mode.
+ */
+ continue;
+ }
+ case AFTER_HEAD:
+ switch (group) {
+ case HTML:
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ case BODY:
+ if (attributes.getLength() == 0) {
+ // This has the right magic side effect
+ // that
+ // it
+ // makes attributes in SAX Tree mutable.
+ appendToCurrentNodeAndPushBodyElement();
+ } else {
+ appendToCurrentNodeAndPushBodyElement(attributes);
+ }
+ framesetOk = false;
+ mode = IN_BODY;
+ attributes = null; // CPP
+ break starttagloop;
+ case FRAMESET:
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ mode = IN_FRAMESET;
+ attributes = null; // CPP
+ break starttagloop;
+ case TEMPLATE:
+ errFooBetweenHeadAndBody(name);
+ pushHeadPointerOntoStack();
+ StackNode<T> headOnStack = stack[currentPtr];
+ startTagTemplateInHead(elementName, attributes);
+ removeFromStack(headOnStack);
+ attributes = null; // CPP
+ break starttagloop;
+ case BASE:
+ case LINK_OR_BASEFONT_OR_BGSOUND:
+ errFooBetweenHeadAndBody(name);
+ pushHeadPointerOntoStack();
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ pop(); // head
+ attributes = null; // CPP
+ break starttagloop;
+ case META:
+ errFooBetweenHeadAndBody(name);
+ checkMetaCharset(attributes);
+ pushHeadPointerOntoStack();
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ attributes);
+ selfClosing = false;
+ pop(); // head
+ attributes = null; // CPP
+ break starttagloop;
+ case SCRIPT:
+ errFooBetweenHeadAndBody(name);
+ pushHeadPointerOntoStack();
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.SCRIPT_DATA, elementName);
+ attributes = null; // CPP
+ break starttagloop;
+ case STYLE:
+ case NOFRAMES:
+ errFooBetweenHeadAndBody(name);
+ pushHeadPointerOntoStack();
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.RAWTEXT, elementName);
+ attributes = null; // CPP
+ break starttagloop;
+ case TITLE:
+ errFooBetweenHeadAndBody(name);
+ pushHeadPointerOntoStack();
+ appendToCurrentNodeAndPushElement(
+ elementName,
+ attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(
+ Tokenizer.RCDATA, elementName);
+ attributes = null; // CPP
+ break starttagloop;
+ case HEAD:
+ errStrayStartTag(name);
+ break starttagloop;
+ default:
+ appendToCurrentNodeAndPushBodyElement();
+ mode = FRAMESET_OK;
+ continue;
+ }
+ case AFTER_AFTER_BODY:
+ switch (group) {
+ case HTML:
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ default:
+ errStrayStartTag(name);
+ fatal();
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ continue;
+ }
+ case AFTER_AFTER_FRAMESET:
+ switch (group) {
+ case HTML:
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = null; // CPP
+ }
+ break starttagloop;
+ case NOFRAMES:
+ startTagGenericRawText(elementName, attributes);
+ attributes = null; // CPP
+ break starttagloop;
+ default:
+ errStrayStartTag(name);
+ break starttagloop;
+ }
+ case TEXT:
+ assert false;
+ break starttagloop; // Avoid infinite loop if the assertion
+ // fails
+ }
+ }
+ if (selfClosing) {
+ errSelfClosing();
+ }
+ // CPPONLY: if (mBuilder == null && attributes != HtmlAttributes.EMPTY_ATTRIBUTES) {
+ // CPPONLY: Portability.delete(attributes);
+ // CPPONLY: }
+ }
+
+ private void startTagTitleInHead(ElementName elementName, HtmlAttributes attributes) throws SAXException {
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(Tokenizer.RCDATA, elementName);
+ }
+
+ private void startTagGenericRawText(ElementName elementName, HtmlAttributes attributes) throws SAXException {
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(Tokenizer.RAWTEXT, elementName);
+ }
+
+ private void startTagScriptInHead(ElementName elementName, HtmlAttributes attributes) throws SAXException {
+ // XXX need to manage much more stuff here if supporting document.write()
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer.setStateAndEndTagExpectation(Tokenizer.SCRIPT_DATA, elementName);
+ }
+
+ private void startTagTemplateInHead(ElementName elementName, HtmlAttributes attributes) throws SAXException {
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ insertMarker();
+ framesetOk = false;
+ originalMode = mode;
+ mode = IN_TEMPLATE;
+ pushTemplateMode(IN_TEMPLATE);
+ }
+
+ private boolean isTemplateContents() {
+ return TreeBuilder.NOT_FOUND_ON_STACK != findLast("template");
+ }
+
+ private boolean isTemplateModeStackEmpty() {
+ return templateModePtr == -1;
+ }
+
+ private boolean isSpecialParentInForeign(StackNode<T> stackNode) {
+ @NsUri String ns = stackNode.ns;
+ return ("http://www.w3.org/1999/xhtml" == ns)
+ || (stackNode.isHtmlIntegrationPoint())
+ || (("http://www.w3.org/1998/Math/MathML" == ns) && (stackNode.getGroup() == MI_MO_MN_MS_MTEXT));
+ }
+
+ /**
+ *
+ * <p>
+ * C++ memory note: The return value must be released.
+ *
+ * @return
+ * @throws SAXException
+ * @throws StopSniffingException
+ */
+ public static String extractCharsetFromContent(String attributeValue
+ // CPPONLY: , TreeBuilder tb
+ ) {
+ // This is a bit ugly. Converting the string to char array in order to
+ // make the portability layer smaller.
+ int charsetState = CHARSET_INITIAL;
+ int start = -1;
+ int end = -1;
+ @Auto char[] buffer = Portability.newCharArrayFromString(attributeValue);
+
+ charsetloop: for (int i = 0; i < buffer.length; i++) {
+ char c = buffer[i];
+ switch (charsetState) {
+ case CHARSET_INITIAL:
+ switch (c) {
+ case 'c':
+ case 'C':
+ charsetState = CHARSET_C;
+ continue;
+ default:
+ continue;
+ }
+ case CHARSET_C:
+ switch (c) {
+ case 'h':
+ case 'H':
+ charsetState = CHARSET_H;
+ continue;
+ default:
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ case CHARSET_H:
+ switch (c) {
+ case 'a':
+ case 'A':
+ charsetState = CHARSET_A;
+ continue;
+ default:
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ case CHARSET_A:
+ switch (c) {
+ case 'r':
+ case 'R':
+ charsetState = CHARSET_R;
+ continue;
+ default:
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ case CHARSET_R:
+ switch (c) {
+ case 's':
+ case 'S':
+ charsetState = CHARSET_S;
+ continue;
+ default:
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ case CHARSET_S:
+ switch (c) {
+ case 'e':
+ case 'E':
+ charsetState = CHARSET_E;
+ continue;
+ default:
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ case CHARSET_E:
+ switch (c) {
+ case 't':
+ case 'T':
+ charsetState = CHARSET_T;
+ continue;
+ default:
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ case CHARSET_T:
+ switch (c) {
+ case '\t':
+ case '\n':
+ case '\u000C':
+ case '\r':
+ case ' ':
+ continue;
+ case '=':
+ charsetState = CHARSET_EQUALS;
+ continue;
+ default:
+ return null;
+ }
+ case CHARSET_EQUALS:
+ switch (c) {
+ case '\t':
+ case '\n':
+ case '\u000C':
+ case '\r':
+ case ' ':
+ continue;
+ case '\'':
+ start = i + 1;
+ charsetState = CHARSET_SINGLE_QUOTED;
+ continue;
+ case '\"':
+ start = i + 1;
+ charsetState = CHARSET_DOUBLE_QUOTED;
+ continue;
+ default:
+ start = i;
+ charsetState = CHARSET_UNQUOTED;
+ continue;
+ }
+ case CHARSET_SINGLE_QUOTED:
+ switch (c) {
+ case '\'':
+ end = i;
+ break charsetloop;
+ default:
+ continue;
+ }
+ case CHARSET_DOUBLE_QUOTED:
+ switch (c) {
+ case '\"':
+ end = i;
+ break charsetloop;
+ default:
+ continue;
+ }
+ case CHARSET_UNQUOTED:
+ switch (c) {
+ case '\t':
+ case '\n':
+ case '\u000C':
+ case '\r':
+ case ' ':
+ case ';':
+ end = i;
+ break charsetloop;
+ default:
+ continue;
+ }
+ }
+ }
+ String charset = null;
+ if (start != -1) {
+ if (end == -1) {
+ end = buffer.length;
+ }
+ charset = Portability.newStringFromBuffer(buffer, start, end
+ - start
+ // CPPONLY: , tb, false
+ );
+ }
+ return charset;
+ }
+
+ private void checkMetaCharset(HtmlAttributes attributes)
+ throws SAXException {
+ String charset = attributes.getValue(AttributeName.CHARSET);
+ if (charset != null) {
+ if (tokenizer.internalEncodingDeclaration(charset)) {
+ requestSuspension();
+ return;
+ }
+ return;
+ }
+ if (!Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "content-type",
+ attributes.getValue(AttributeName.HTTP_EQUIV))) {
+ return;
+ }
+ String content = attributes.getValue(AttributeName.CONTENT);
+ if (content != null) {
+ String extract = TreeBuilder.extractCharsetFromContent(content
+ // CPPONLY: , this
+ );
+ // remember not to return early without releasing the string
+ if (extract != null) {
+ if (tokenizer.internalEncodingDeclaration(extract)) {
+ requestSuspension();
+ }
+ }
+ Portability.releaseString(extract);
+ }
+ }
+
+ public final void endTag(ElementName elementName) throws SAXException {
+ flushCharacters();
+ needToDropLF = false;
+ int eltPos;
+ int group = elementName.getGroup();
+ @Local String name = elementName.getName();
+ endtagloop: for (;;) {
+ if (isInForeign()) {
+ if (stack[currentPtr].name != name) {
+ if (currentPtr == 0) {
+ errStrayEndTag(name);
+ } else {
+ errEndTagDidNotMatchCurrentOpenElement(name, stack[currentPtr].popName);
+ }
+ }
+ eltPos = currentPtr;
+ for (;;) {
+ if (eltPos == 0) {
+ assert fragment: "We can get this close to the root of the stack in foreign content only in the fragment case.";
+ break endtagloop;
+ }
+ if (stack[eltPos].name == name) {
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ break endtagloop;
+ }
+ if (stack[--eltPos].ns == "http://www.w3.org/1999/xhtml") {
+ break;
+ }
+ }
+ }
+ switch (mode) {
+ case IN_TEMPLATE:
+ switch (group) {
+ case TEMPLATE:
+ // fall through to IN_HEAD
+ break;
+ default:
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ case IN_ROW:
+ switch (group) {
+ case TR:
+ eltPos = findLastOrRoot(TreeBuilder.TR);
+ if (eltPos == 0) {
+ assert fragment || isTemplateContents();
+ errNoTableRowToClose();
+ break endtagloop;
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE_BODY;
+ break endtagloop;
+ case TABLE:
+ eltPos = findLastOrRoot(TreeBuilder.TR);
+ if (eltPos == 0) {
+ assert fragment || isTemplateContents();
+ errNoTableRowToClose();
+ break endtagloop;
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE_BODY;
+ continue;
+ case TBODY_OR_THEAD_OR_TFOOT:
+ if (findLastInTableScope(name) == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ eltPos = findLastOrRoot(TreeBuilder.TR);
+ if (eltPos == 0) {
+ assert fragment || isTemplateContents();
+ errNoTableRowToClose();
+ break endtagloop;
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE_BODY;
+ continue;
+ case BODY:
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case HTML:
+ case TD_OR_TH:
+ errStrayEndTag(name);
+ break endtagloop;
+ default:
+ // fall through to IN_TABLE
+ }
+ case IN_TABLE_BODY:
+ switch (group) {
+ case TBODY_OR_THEAD_OR_TFOOT:
+ eltPos = findLastOrRoot(name);
+ if (eltPos == 0) {
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE;
+ break endtagloop;
+ case TABLE:
+ eltPos = findLastInTableScopeOrRootTemplateTbodyTheadTfoot();
+ if (eltPos == 0 || stack[eltPos].getGroup() == TEMPLATE) {
+ assert fragment || isTemplateContents();
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE;
+ continue;
+ case BODY:
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case HTML:
+ case TD_OR_TH:
+ case TR:
+ errStrayEndTag(name);
+ break endtagloop;
+ default:
+ // fall through to IN_TABLE
+ }
+ case IN_TABLE:
+ switch (group) {
+ case TABLE:
+ eltPos = findLast("table");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ assert fragment || isTemplateContents();
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ break endtagloop;
+ case BODY:
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case HTML:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TD_OR_TH:
+ case TR:
+ errStrayEndTag(name);
+ break endtagloop;
+ case TEMPLATE:
+ // fall through to IN_HEAD
+ break;
+ default:
+ errStrayEndTag(name);
+ // fall through to IN_BODY
+ }
+ case IN_CAPTION:
+ switch (group) {
+ case CAPTION:
+ eltPos = findLastInTableScope("caption");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ break endtagloop;
+ }
+ generateImpliedEndTags();
+ if (errorHandler != null && currentPtr != eltPos) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ mode = IN_TABLE;
+ break endtagloop;
+ case TABLE:
+ errTableClosedWhileCaptionOpen();
+ eltPos = findLastInTableScope("caption");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ break endtagloop;
+ }
+ generateImpliedEndTags();
+ if (errorHandler != null && currentPtr != eltPos) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ mode = IN_TABLE;
+ continue;
+ case BODY:
+ case COL:
+ case COLGROUP:
+ case HTML:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TD_OR_TH:
+ case TR:
+ errStrayEndTag(name);
+ break endtagloop;
+ default:
+ // fall through to IN_BODY
+ }
+ case IN_CELL:
+ switch (group) {
+ case TD_OR_TH:
+ eltPos = findLastInTableScope(name);
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ generateImpliedEndTags();
+ if (errorHandler != null && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ mode = IN_ROW;
+ break endtagloop;
+ case TABLE:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ if (findLastInTableScope(name) == TreeBuilder.NOT_FOUND_ON_STACK) {
+ assert name == "tbody" || name == "tfoot" || name == "thead" || fragment || isTemplateContents();
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ closeTheCell(findLastInTableScopeTdTh());
+ continue;
+ case BODY:
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case HTML:
+ errStrayEndTag(name);
+ break endtagloop;
+ default:
+ // fall through to IN_BODY
+ }
+ case FRAMESET_OK:
+ case IN_BODY:
+ switch (group) {
+ case BODY:
+ if (!isSecondOnStackBody()) {
+ assert fragment || isTemplateContents();
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ assert currentPtr >= 1;
+ if (errorHandler != null) {
+ uncloseloop1: for (int i = 2; i <= currentPtr; i++) {
+ switch (stack[i].getGroup()) {
+ case DD_OR_DT:
+ case LI:
+ case OPTGROUP:
+ case OPTION: // is this possible?
+ case P:
+ case RB_OR_RTC:
+ case RT_OR_RP:
+ case TD_OR_TH:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ break;
+ default:
+ errEndWithUnclosedElements(name);
+ break uncloseloop1;
+ }
+ }
+ }
+ mode = AFTER_BODY;
+ break endtagloop;
+ case HTML:
+ if (!isSecondOnStackBody()) {
+ assert fragment || isTemplateContents();
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ if (errorHandler != null) {
+ uncloseloop2: for (int i = 0; i <= currentPtr; i++) {
+ switch (stack[i].getGroup()) {
+ case DD_OR_DT:
+ case LI:
+ case P:
+ case RB_OR_RTC:
+ case RT_OR_RP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TD_OR_TH:
+ case BODY:
+ case HTML:
+ break;
+ default:
+ errEndWithUnclosedElements(name);
+ break uncloseloop2;
+ }
+ }
+ }
+ mode = AFTER_BODY;
+ continue;
+ case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU:
+ case UL_OR_OL_OR_DL:
+ case PRE_OR_LISTING:
+ case FIELDSET:
+ case BUTTON:
+ case ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY:
+ eltPos = findLastInScope(name);
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ } else {
+ generateImpliedEndTags();
+ if (errorHandler != null && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ }
+ break endtagloop;
+ case FORM:
+ if (!isTemplateContents()) {
+ if (formPointer == null) {
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ formPointer = null;
+ eltPos = findLastInScope(name);
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ generateImpliedEndTags();
+ if (errorHandler != null && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ removeFromStack(eltPos);
+ break endtagloop;
+ } else {
+ eltPos = findLastInScope(name);
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ generateImpliedEndTags();
+ if (errorHandler != null && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ break endtagloop;
+ }
+ case P:
+ eltPos = findLastInButtonScope("p");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errNoElementToCloseButEndTagSeen("p");
+ // XXX Can the 'in foreign' case happen anymore?
+ if (isInForeign()) {
+ errHtmlStartTagInForeignContext(name);
+ // Check for currentPtr for the fragment
+ // case.
+ while (currentPtr >= 0 && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml") {
+ pop();
+ }
+ }
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ HtmlAttributes.EMPTY_ATTRIBUTES);
+ break endtagloop;
+ }
+ generateImpliedEndTagsExceptFor("p");
+ assert eltPos != TreeBuilder.NOT_FOUND_ON_STACK;
+ if (errorHandler != null && eltPos != currentPtr) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ break endtagloop;
+ case LI:
+ eltPos = findLastInListScope(name);
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errNoElementToCloseButEndTagSeen(name);
+ } else {
+ generateImpliedEndTagsExceptFor(name);
+ if (errorHandler != null
+ && eltPos != currentPtr) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ }
+ break endtagloop;
+ case DD_OR_DT:
+ eltPos = findLastInScope(name);
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errNoElementToCloseButEndTagSeen(name);
+ } else {
+ generateImpliedEndTagsExceptFor(name);
+ if (errorHandler != null
+ && eltPos != currentPtr) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ }
+ break endtagloop;
+ case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6:
+ eltPos = findLastInScopeHn();
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ } else {
+ generateImpliedEndTags();
+ if (errorHandler != null && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ }
+ break endtagloop;
+ case OBJECT:
+ case MARQUEE_OR_APPLET:
+ eltPos = findLastInScope(name);
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ } else {
+ generateImpliedEndTags();
+ if (errorHandler != null && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ }
+ break endtagloop;
+ case BR:
+ errEndTagBr();
+ if (isInForeign()) {
+ // XXX can this happen anymore?
+ errHtmlStartTagInForeignContext(name);
+ // Check for currentPtr for the fragment
+ // case.
+ while (currentPtr >= 0 && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml") {
+ pop();
+ }
+ }
+ reconstructTheActiveFormattingElements();
+ appendVoidElementToCurrentMayFoster(
+ elementName,
+ HtmlAttributes.EMPTY_ATTRIBUTES);
+ break endtagloop;
+ case TEMPLATE:
+ // fall through to IN_HEAD;
+ break;
+ case AREA_OR_WBR:
+ // CPPONLY: case MENUITEM:
+ case PARAM_OR_SOURCE_OR_TRACK:
+ case EMBED:
+ case IMG:
+ case IMAGE:
+ case INPUT:
+ case KEYGEN: // XXX??
+ case HR:
+ // CPPONLY: case ISINDEX:
+ case IFRAME:
+ case NOEMBED: // XXX???
+ case NOFRAMES: // XXX??
+ case SELECT:
+ case TABLE:
+ case TEXTAREA: // XXX??
+ errStrayEndTag(name);
+ break endtagloop;
+ case NOSCRIPT:
+ if (scriptingEnabled) {
+ errStrayEndTag(name);
+ break endtagloop;
+ } else {
+ // fall through
+ }
+ case A:
+ case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
+ case FONT:
+ case NOBR:
+ if (adoptionAgencyEndTag(name)) {
+ break endtagloop;
+ }
+ // else handle like any other tag
+ default:
+ if (isCurrent(name)) {
+ pop();
+ break endtagloop;
+ }
+
+ eltPos = currentPtr;
+ for (;;) {
+ StackNode<T> node = stack[eltPos];
+ if (node.ns == "http://www.w3.org/1999/xhtml" && node.name == name) {
+ generateImpliedEndTags();
+ if (errorHandler != null
+ && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ break endtagloop;
+ } else if (eltPos == 0 || node.isSpecial()) {
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ eltPos--;
+ }
+ }
+ case IN_HEAD:
+ switch (group) {
+ case HEAD:
+ pop();
+ mode = AFTER_HEAD;
+ break endtagloop;
+ case BR:
+ case HTML:
+ case BODY:
+ pop();
+ mode = AFTER_HEAD;
+ continue;
+ case TEMPLATE:
+ endTagTemplateInHead();
+ break endtagloop;
+ default:
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ case IN_HEAD_NOSCRIPT:
+ switch (group) {
+ case NOSCRIPT:
+ pop();
+ mode = IN_HEAD;
+ break endtagloop;
+ case BR:
+ errStrayEndTag(name);
+ pop();
+ mode = IN_HEAD;
+ continue;
+ default:
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ case IN_COLUMN_GROUP:
+ switch (group) {
+ case COLGROUP:
+ if (currentPtr == 0 || stack[currentPtr].getGroup() ==
+ TreeBuilder.TEMPLATE) {
+ assert fragment || isTemplateContents();
+ errGarbageInColgroup();
+ break endtagloop;
+ }
+ pop();
+ mode = IN_TABLE;
+ break endtagloop;
+ case COL:
+ errStrayEndTag(name);
+ break endtagloop;
+ case TEMPLATE:
+ endTagTemplateInHead();
+ break endtagloop;
+ default:
+ if (currentPtr == 0 || stack[currentPtr].getGroup() ==
+ TreeBuilder.TEMPLATE) {
+ assert fragment || isTemplateContents();
+ errGarbageInColgroup();
+ break endtagloop;
+ }
+ pop();
+ mode = IN_TABLE;
+ continue;
+ }
+ case IN_SELECT_IN_TABLE:
+ switch (group) {
+ case CAPTION:
+ case TABLE:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ case TD_OR_TH:
+ errEndTagSeenWithSelectOpen(name);
+ if (findLastInTableScope(name) != TreeBuilder.NOT_FOUND_ON_STACK) {
+ eltPos = findLastInTableScope("select");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ assert fragment;
+ break endtagloop; // http://www.w3.org/Bugs/Public/show_bug.cgi?id=8375
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ continue;
+ } else {
+ break endtagloop;
+ }
+ default:
+ // fall through to IN_SELECT
+ }
+ case IN_SELECT:
+ switch (group) {
+ case OPTION:
+ if (isCurrent("option")) {
+ pop();
+ break endtagloop;
+ } else {
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ case OPTGROUP:
+ if (isCurrent("option")
+ && "optgroup" == stack[currentPtr - 1].name) {
+ pop();
+ }
+ if (isCurrent("optgroup")) {
+ pop();
+ } else {
+ errStrayEndTag(name);
+ }
+ break endtagloop;
+ case SELECT:
+ eltPos = findLastInTableScope("select");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ assert fragment;
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ break endtagloop;
+ case TEMPLATE:
+ endTagTemplateInHead();
+ break endtagloop;
+ default:
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ case AFTER_BODY:
+ switch (group) {
+ case HTML:
+ if (fragment) {
+ errStrayEndTag(name);
+ break endtagloop;
+ } else {
+ mode = AFTER_AFTER_BODY;
+ break endtagloop;
+ }
+ default:
+ errEndTagAfterBody();
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ continue;
+ }
+ case IN_FRAMESET:
+ switch (group) {
+ case FRAMESET:
+ if (currentPtr == 0) {
+ assert fragment;
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ pop();
+ if ((!fragment) && !isCurrent("frameset")) {
+ mode = AFTER_FRAMESET;
+ }
+ break endtagloop;
+ default:
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ case AFTER_FRAMESET:
+ switch (group) {
+ case HTML:
+ mode = AFTER_AFTER_FRAMESET;
+ break endtagloop;
+ default:
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ case INITIAL:
+ /*
+ * Parse error.
+ */
+ // [NOCPP[
+ switch (doctypeExpectation) {
+ case AUTO:
+ err("End tag seen without seeing a doctype first. Expected e.g. \u201C<!DOCTYPE html>\u201D.");
+ break;
+ case HTML:
+ // ]NOCPP]
+ errEndTagSeenWithoutDoctype();
+ // [NOCPP[
+ break;
+ case HTML401_STRICT:
+ err("End tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D.");
+ break;
+ case HTML401_TRANSITIONAL:
+ err("End tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D.");
+ break;
+ case NO_DOCTYPE_ERRORS:
+ }
+ // ]NOCPP]
+ /*
+ *
+ * Set the document to quirks mode.
+ */
+ documentModeInternal(DocumentMode.QUIRKS_MODE, null, null,
+ false);
+ /*
+ * Then, switch to the root element mode of the tree
+ * construction stage
+ */
+ mode = BEFORE_HTML;
+ /*
+ * and reprocess the current token.
+ */
+ continue;
+ case BEFORE_HTML:
+ switch (group) {
+ case HEAD:
+ case BR:
+ case HTML:
+ case BODY:
+ /*
+ * Create an HTMLElement node with the tag name
+ * html, in the HTML namespace. Append it to the
+ * Document object.
+ */
+ appendHtmlElementToDocumentAndPush();
+ /* Switch to the main mode */
+ mode = BEFORE_HEAD;
+ /*
+ * reprocess the current token.
+ */
+ continue;
+ default:
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ case BEFORE_HEAD:
+ switch (group) {
+ case HEAD:
+ case BR:
+ case HTML:
+ case BODY:
+ appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES);
+ mode = IN_HEAD;
+ continue;
+ default:
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ case AFTER_HEAD:
+ switch (group) {
+ case TEMPLATE:
+ endTagTemplateInHead();
+ break endtagloop;
+ case HTML:
+ case BODY:
+ case BR:
+ appendToCurrentNodeAndPushBodyElement();
+ mode = FRAMESET_OK;
+ continue;
+ default:
+ errStrayEndTag(name);
+ break endtagloop;
+ }
+ case AFTER_AFTER_BODY:
+ errStrayEndTag(name);
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ continue;
+ case AFTER_AFTER_FRAMESET:
+ errStrayEndTag(name);
+ break endtagloop;
+ case TEXT:
+ // XXX need to manage insertion point here
+ pop();
+ if (originalMode == AFTER_HEAD) {
+ silentPop();
+ }
+ mode = originalMode;
+ break endtagloop;
+ }
+ } // endtagloop
+ }
+
+ private void endTagTemplateInHead() throws SAXException {
+ int eltPos = findLast("template");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ errStrayEndTag("template");
+ return;
+ }
+ generateImpliedEndTags();
+ if (errorHandler != null && !isCurrent("template")) {
+ errUnclosedElements(eltPos, "template");
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ popTemplateMode();
+ resetTheInsertionMode();
+ }
+
+ private int findLastInTableScopeOrRootTemplateTbodyTheadTfoot() {
+ for (int i = currentPtr; i > 0; i--) {
+ if (stack[i].getGroup() == TreeBuilder.TBODY_OR_THEAD_OR_TFOOT ||
+ stack[i].getGroup() == TreeBuilder.TEMPLATE) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ private int findLast(@Local String name) {
+ for (int i = currentPtr; i > 0; i--) {
+ if (stack[i].ns == "http://www.w3.org/1999/xhtml" && stack[i].name == name) {
+ return i;
+ }
+ }
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+
+ private int findLastInTableScope(@Local String name) {
+ for (int i = currentPtr; i > 0; i--) {
+ if (stack[i].ns == "http://www.w3.org/1999/xhtml") {
+ if (stack[i].name == name) {
+ return i;
+ } else if (stack[i].name == "table" || stack[i].name == "template") {
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+ }
+ }
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+
+ private int findLastInButtonScope(@Local String name) {
+ for (int i = currentPtr; i > 0; i--) {
+ if (stack[i].ns == "http://www.w3.org/1999/xhtml") {
+ if (stack[i].name == name) {
+ return i;
+ } else if (stack[i].name == "button") {
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+ }
+
+ if (stack[i].isScoping()) {
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+ }
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+
+ private int findLastInScope(@Local String name) {
+ for (int i = currentPtr; i > 0; i--) {
+ if (stack[i].ns == "http://www.w3.org/1999/xhtml" && stack[i].name == name) {
+ return i;
+ } else if (stack[i].isScoping()) {
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+ }
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+
+ private int findLastInListScope(@Local String name) {
+ for (int i = currentPtr; i > 0; i--) {
+ if (stack[i].ns == "http://www.w3.org/1999/xhtml") {
+ if (stack[i].name == name) {
+ return i;
+ } else if (stack[i].name == "ul" || stack[i].name == "ol") {
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+ }
+
+ if (stack[i].isScoping()) {
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+ }
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+
+ private int findLastInScopeHn() {
+ for (int i = currentPtr; i > 0; i--) {
+ if (stack[i].getGroup() == TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6) {
+ return i;
+ } else if (stack[i].isScoping()) {
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+ }
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+
+ private void generateImpliedEndTagsExceptFor(@Local String name)
+ throws SAXException {
+ for (;;) {
+ StackNode<T> node = stack[currentPtr];
+ switch (node.getGroup()) {
+ case P:
+ case LI:
+ case DD_OR_DT:
+ case OPTION:
+ case OPTGROUP:
+ case RB_OR_RTC:
+ case RT_OR_RP:
+ if (node.ns == "http://www.w3.org/1999/xhtml" && node.name == name) {
+ return;
+ }
+ pop();
+ continue;
+ default:
+ return;
+ }
+ }
+ }
+
+ private void generateImpliedEndTags() throws SAXException {
+ for (;;) {
+ switch (stack[currentPtr].getGroup()) {
+ case P:
+ case LI:
+ case DD_OR_DT:
+ case OPTION:
+ case OPTGROUP:
+ case RB_OR_RTC:
+ case RT_OR_RP:
+ pop();
+ continue;
+ default:
+ return;
+ }
+ }
+ }
+
+ private boolean isSecondOnStackBody() {
+ return currentPtr >= 1 && stack[1].getGroup() == TreeBuilder.BODY;
+ }
+
+ private void documentModeInternal(DocumentMode m, String publicIdentifier,
+ String systemIdentifier, boolean html4SpecificAdditionalErrorChecks)
+ throws SAXException {
+
+ if (isSrcdocDocument) {
+ // Srcdoc documents are always rendered in standards mode.
+ quirks = false;
+ if (documentModeHandler != null) {
+ documentModeHandler.documentMode(
+ DocumentMode.STANDARDS_MODE
+ // [NOCPP[
+ , null, null, false
+ // ]NOCPP]
+ );
+ }
+ return;
+ }
+
+ quirks = (m == DocumentMode.QUIRKS_MODE);
+ if (documentModeHandler != null) {
+ documentModeHandler.documentMode(
+ m
+ // [NOCPP[
+ , publicIdentifier, systemIdentifier,
+ html4SpecificAdditionalErrorChecks
+ // ]NOCPP]
+ );
+ }
+ // [NOCPP[
+ documentMode(m, publicIdentifier, systemIdentifier,
+ html4SpecificAdditionalErrorChecks);
+ // ]NOCPP]
+ }
+
+ private boolean isAlmostStandards(String publicIdentifier,
+ String systemIdentifier) {
+ if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "-//w3c//dtd xhtml 1.0 transitional//en", publicIdentifier)) {
+ return true;
+ }
+ if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "-//w3c//dtd xhtml 1.0 frameset//en", publicIdentifier)) {
+ return true;
+ }
+ if (systemIdentifier != null) {
+ if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "-//w3c//dtd html 4.01 transitional//en", publicIdentifier)) {
+ return true;
+ }
+ if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "-//w3c//dtd html 4.01 frameset//en", publicIdentifier)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isQuirky(@Local String name, String publicIdentifier,
+ String systemIdentifier, boolean forceQuirks) {
+ if (forceQuirks) {
+ return true;
+ }
+ if (name != HTML_LOCAL) {
+ return true;
+ }
+ if (publicIdentifier != null) {
+ for (int i = 0; i < TreeBuilder.QUIRKY_PUBLIC_IDS.length; i++) {
+ if (Portability.lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString(
+ TreeBuilder.QUIRKY_PUBLIC_IDS[i], publicIdentifier)) {
+ return true;
+ }
+ }
+ if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "-//w3o//dtd w3 html strict 3.0//en//", publicIdentifier)
+ || Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "-/w3c/dtd html 4.0 transitional/en",
+ publicIdentifier)
+ || Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "html", publicIdentifier)) {
+ return true;
+ }
+ }
+ if (systemIdentifier == null) {
+ if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "-//w3c//dtd html 4.01 transitional//en", publicIdentifier)) {
+ return true;
+ } else if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "-//w3c//dtd html 4.01 frameset//en", publicIdentifier)) {
+ return true;
+ }
+ } else if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd",
+ systemIdentifier)) {
+ return true;
+ }
+ return false;
+ }
+
+ private void closeTheCell(int eltPos) throws SAXException {
+ generateImpliedEndTags();
+ if (errorHandler != null && eltPos != currentPtr) {
+ errUnclosedElementsCell(eltPos);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ mode = IN_ROW;
+ return;
+ }
+
+ private int findLastInTableScopeTdTh() {
+ for (int i = currentPtr; i > 0; i--) {
+ @Local String name = stack[i].name;
+ if (stack[i].ns == "http://www.w3.org/1999/xhtml") {
+ if ("td" == name || "th" == name) {
+ return i;
+ } else if (name == "table" || name == "template") {
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+ }
+ }
+ return TreeBuilder.NOT_FOUND_ON_STACK;
+ }
+
+ private void clearStackBackTo(int eltPos) throws SAXException {
+ int eltGroup = stack[eltPos].getGroup();
+ while (currentPtr > eltPos) { // > not >= intentional
+ if (stack[currentPtr].ns == "http://www.w3.org/1999/xhtml"
+ && stack[currentPtr].getGroup() == TEMPLATE
+ && (eltGroup == TABLE || eltGroup == TBODY_OR_THEAD_OR_TFOOT|| eltGroup == TR || eltPos == 0)) {
+ return;
+ }
+ pop();
+ }
+ }
+
+ private void resetTheInsertionMode() {
+ StackNode<T> node;
+ @Local String name;
+ @NsUri String ns;
+ for (int i = currentPtr; i >= 0; i--) {
+ node = stack[i];
+ name = node.name;
+ ns = node.ns;
+ if (i == 0) {
+ if (!(contextNamespace == "http://www.w3.org/1999/xhtml" && (contextName == "td" || contextName == "th"))) {
+ if (fragment) {
+ // Make sure we are parsing a fragment otherwise the context element doesn't make sense.
+ name = contextName;
+ ns = contextNamespace;
+ }
+ } else {
+ mode = framesetOk ? FRAMESET_OK : IN_BODY; // XXX from Hixie's email
+ return;
+ }
+ }
+ if ("select" == name) {
+ int ancestorIndex = i;
+ while (ancestorIndex > 0) {
+ StackNode<T> ancestor = stack[ancestorIndex--];
+ if ("http://www.w3.org/1999/xhtml" == ancestor.ns) {
+ if ("template" == ancestor.name) {
+ break;
+ }
+ if ("table" == ancestor.name) {
+ mode = IN_SELECT_IN_TABLE;
+ return;
+ }
+ }
+ }
+ mode = IN_SELECT;
+ return;
+ } else if ("td" == name || "th" == name) {
+ mode = IN_CELL;
+ return;
+ } else if ("tr" == name) {
+ mode = IN_ROW;
+ return;
+ } else if ("tbody" == name || "thead" == name || "tfoot" == name) {
+ mode = IN_TABLE_BODY;
+ return;
+ } else if ("caption" == name) {
+ mode = IN_CAPTION;
+ return;
+ } else if ("colgroup" == name) {
+ mode = IN_COLUMN_GROUP;
+ return;
+ } else if ("table" == name) {
+ mode = IN_TABLE;
+ return;
+ } else if ("http://www.w3.org/1999/xhtml" != ns) {
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ return;
+ } else if ("template" == name) {
+ assert templateModePtr >= 0;
+ mode = templateModeStack[templateModePtr];
+ return;
+ } else if ("head" == name) {
+ if (name == contextName) {
+ mode = framesetOk ? FRAMESET_OK : IN_BODY; // really
+ } else {
+ mode = IN_HEAD;
+ }
+ return;
+ } else if ("body" == name) {
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ return;
+ } else if ("frameset" == name) {
+ // TODO: Fragment case. Add error reporting.
+ mode = IN_FRAMESET;
+ return;
+ } else if ("html" == name) {
+ if (headPointer == null) {
+ // TODO: Fragment case. Add error reporting.
+ mode = BEFORE_HEAD;
+ } else {
+ mode = AFTER_HEAD;
+ }
+ return;
+ } else if (i == 0) {
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ return;
+ }
+ }
+ }
+
+ /**
+ * @throws SAXException
+ *
+ */
+ private void implicitlyCloseP() throws SAXException {
+ int eltPos = findLastInButtonScope("p");
+ if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) {
+ return;
+ }
+ generateImpliedEndTagsExceptFor("p");
+ if (errorHandler != null && eltPos != currentPtr) {
+ errUnclosedElementsImplied(eltPos, "p");
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ }
+
+ private boolean debugOnlyClearLastStackSlot() {
+ stack[currentPtr] = null;
+ return true;
+ }
+
+ private boolean debugOnlyClearLastListSlot() {
+ listOfActiveFormattingElements[listPtr] = null;
+ return true;
+ }
+
+ private void pushTemplateMode(int mode) {
+ templateModePtr++;
+ if (templateModePtr == templateModeStack.length) {
+ int[] newStack = new int[templateModeStack.length + 64];
+ System.arraycopy(templateModeStack, 0, newStack, 0, templateModeStack.length);
+ templateModeStack = newStack;
+ }
+ templateModeStack[templateModePtr] = mode;
+ }
+
+ @SuppressWarnings("unchecked") private void push(StackNode<T> node) throws SAXException {
+ currentPtr++;
+ if (currentPtr == stack.length) {
+ StackNode<T>[] newStack = new StackNode[stack.length + 64];
+ System.arraycopy(stack, 0, newStack, 0, stack.length);
+ stack = newStack;
+ }
+ stack[currentPtr] = node;
+ elementPushed(node.ns, node.popName, node.node);
+ }
+
+ @SuppressWarnings("unchecked") private void silentPush(StackNode<T> node) throws SAXException {
+ currentPtr++;
+ if (currentPtr == stack.length) {
+ StackNode<T>[] newStack = new StackNode[stack.length + 64];
+ System.arraycopy(stack, 0, newStack, 0, stack.length);
+ stack = newStack;
+ }
+ stack[currentPtr] = node;
+ }
+
+ @SuppressWarnings("unchecked") private void append(StackNode<T> node) {
+ listPtr++;
+ if (listPtr == listOfActiveFormattingElements.length) {
+ StackNode<T>[] newList = new StackNode[listOfActiveFormattingElements.length + 64];
+ System.arraycopy(listOfActiveFormattingElements, 0, newList, 0,
+ listOfActiveFormattingElements.length);
+ listOfActiveFormattingElements = newList;
+ }
+ listOfActiveFormattingElements[listPtr] = node;
+ }
+
+ @Inline private void insertMarker() {
+ append(null);
+ }
+
+ private void clearTheListOfActiveFormattingElementsUpToTheLastMarker() {
+ while (listPtr > -1) {
+ if (listOfActiveFormattingElements[listPtr] == null) {
+ --listPtr;
+ return;
+ }
+ listOfActiveFormattingElements[listPtr].release();
+ --listPtr;
+ }
+ }
+
+ @Inline private boolean isCurrent(@Local String name) {
+ return stack[currentPtr].ns == "http://www.w3.org/1999/xhtml" &&
+ name == stack[currentPtr].name;
+ }
+
+ private void removeFromStack(int pos) throws SAXException {
+ if (currentPtr == pos) {
+ pop();
+ } else {
+ fatal();
+ stack[pos].release();
+ System.arraycopy(stack, pos + 1, stack, pos, currentPtr - pos);
+ assert debugOnlyClearLastStackSlot();
+ currentPtr--;
+ }
+ }
+
+ private void removeFromStack(StackNode<T> node) throws SAXException {
+ if (stack[currentPtr] == node) {
+ pop();
+ } else {
+ int pos = currentPtr - 1;
+ while (pos >= 0 && stack[pos] != node) {
+ pos--;
+ }
+ if (pos == -1) {
+ // dead code?
+ return;
+ }
+ fatal();
+ node.release();
+ System.arraycopy(stack, pos + 1, stack, pos, currentPtr - pos);
+ currentPtr--;
+ }
+ }
+
+ private void removeFromListOfActiveFormattingElements(int pos) {
+ assert listOfActiveFormattingElements[pos] != null;
+ listOfActiveFormattingElements[pos].release();
+ if (pos == listPtr) {
+ assert debugOnlyClearLastListSlot();
+ listPtr--;
+ return;
+ }
+ assert pos < listPtr;
+ System.arraycopy(listOfActiveFormattingElements, pos + 1,
+ listOfActiveFormattingElements, pos, listPtr - pos);
+ assert debugOnlyClearLastListSlot();
+ listPtr--;
+ }
+
+ /**
+ * Adoption agency algorithm.
+ *
+ * @param name subject as described in the specified algorithm.
+ * @return Returns true if the algorithm has completed and there is nothing remaining to
+ * be done. Returns false if the algorithm needs to "act as described in the 'any other
+ * end tag' entry" as described in the specified algorithm.
+ * @throws SAXException
+ */
+ private boolean adoptionAgencyEndTag(@Local String name) throws SAXException {
+ // This check intends to ensure that for properly nested tags, closing tags will match
+ // against the stack instead of the listOfActiveFormattingElements.
+ if (stack[currentPtr].ns == "http://www.w3.org/1999/xhtml" &&
+ stack[currentPtr].name == name &&
+ findInListOfActiveFormattingElements(stack[currentPtr]) == -1) {
+ // If the current element matches the name but isn't on the list of active
+ // formatting elements, then it is possible that the list was mangled by the Noah's Ark
+ // clause. In this case, we want to match the end tag against the stack instead of
+ // proceeding with the AAA algorithm that may match against the list of
+ // active formatting elements (and possibly mangle the tree in unexpected ways).
+ pop();
+ return true;
+ }
+
+ // If you crash around here, perhaps some stack node variable claimed to
+ // be a weak ref isn't.
+ for (int i = 0; i < 8; ++i) {
+ int formattingEltListPos = listPtr;
+ while (formattingEltListPos > -1) {
+ StackNode<T> listNode = listOfActiveFormattingElements[formattingEltListPos]; // weak ref
+ if (listNode == null) {
+ formattingEltListPos = -1;
+ break;
+ } else if (listNode.name == name) {
+ break;
+ }
+ formattingEltListPos--;
+ }
+ if (formattingEltListPos == -1) {
+ return false;
+ }
+ // this *looks* like a weak ref to the list of formatting elements
+ StackNode<T> formattingElt = listOfActiveFormattingElements[formattingEltListPos];
+ int formattingEltStackPos = currentPtr;
+ boolean inScope = true;
+ while (formattingEltStackPos > -1) {
+ StackNode<T> node = stack[formattingEltStackPos]; // weak ref
+ if (node == formattingElt) {
+ break;
+ } else if (node.isScoping()) {
+ inScope = false;
+ }
+ formattingEltStackPos--;
+ }
+ if (formattingEltStackPos == -1) {
+ errNoElementToCloseButEndTagSeen(name);
+ removeFromListOfActiveFormattingElements(formattingEltListPos);
+ return true;
+ }
+ if (!inScope) {
+ errNoElementToCloseButEndTagSeen(name);
+ return true;
+ }
+ // stackPos now points to the formatting element and it is in scope
+ if (formattingEltStackPos != currentPtr) {
+ errEndTagViolatesNestingRules(name);
+ }
+ int furthestBlockPos = formattingEltStackPos + 1;
+ while (furthestBlockPos <= currentPtr) {
+ StackNode<T> node = stack[furthestBlockPos]; // weak ref
+ assert furthestBlockPos > 0: "How is formattingEltStackPos + 1 not > 0?";
+ if (node.isSpecial()) {
+ break;
+ }
+ furthestBlockPos++;
+ }
+ if (furthestBlockPos > currentPtr) {
+ // no furthest block
+ while (currentPtr >= formattingEltStackPos) {
+ pop();
+ }
+ removeFromListOfActiveFormattingElements(formattingEltListPos);
+ return true;
+ }
+ StackNode<T> commonAncestor = stack[formattingEltStackPos - 1]; // weak ref
+ StackNode<T> furthestBlock = stack[furthestBlockPos]; // weak ref
+ // detachFromParent(furthestBlock.node); XXX AAA CHANGE
+ int bookmark = formattingEltListPos;
+ int nodePos = furthestBlockPos;
+ StackNode<T> lastNode = furthestBlock; // weak ref
+ int j = 0;
+ for (;;) {
+ ++j;
+ nodePos--;
+ if (nodePos == formattingEltStackPos) {
+ break;
+ }
+ StackNode<T> node = stack[nodePos]; // weak ref
+ int nodeListPos = findInListOfActiveFormattingElements(node);
+
+ if (j > 3 && nodeListPos != -1) {
+ removeFromListOfActiveFormattingElements(nodeListPos);
+
+ // Adjust the indices into the list to account
+ // for the removal of nodeListPos.
+ if (nodeListPos <= formattingEltListPos) {
+ formattingEltListPos--;
+ }
+ if (nodeListPos <= bookmark) {
+ bookmark--;
+ }
+
+ // Update position to reflect removal from list.
+ nodeListPos = -1;
+ }
+
+ if (nodeListPos == -1) {
+ assert formattingEltStackPos < nodePos;
+ assert bookmark < nodePos;
+ assert furthestBlockPos > nodePos;
+ removeFromStack(nodePos); // node is now a bad pointer in C++
+ furthestBlockPos--;
+ continue;
+ }
+ // now node is both on stack and in the list
+ if (nodePos == furthestBlockPos) {
+ bookmark = nodeListPos + 1;
+ }
+ // if (hasChildren(node.node)) { XXX AAA CHANGE
+ assert node == listOfActiveFormattingElements[nodeListPos];
+ assert node == stack[nodePos];
+ T clone = createElement("http://www.w3.org/1999/xhtml",
+ node.name, node.attributes.cloneAttributes(null), commonAncestor.node
+ // CPPONLY: , htmlCreator(node.getHtmlCreator())
+ );
+ StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns,
+ node.name, clone, node.popName, node.attributes
+ // CPPONLY: , node.getHtmlCreator()
+ // [NOCPP[
+ , node.getLocator()
+ // ]NOCPP]
+ ); // creation ownership goes to stack
+ node.dropAttributes(); // adopt ownership to newNode
+ stack[nodePos] = newNode;
+ newNode.retain(); // retain for list
+ listOfActiveFormattingElements[nodeListPos] = newNode;
+ node.release(); // release from stack
+ node.release(); // release from list
+ node = newNode;
+ // } XXX AAA CHANGE
+ detachFromParent(lastNode.node);
+ appendElement(lastNode.node, node.node);
+ lastNode = node;
+ }
+ if (commonAncestor.isFosterParenting()) {
+ fatal();
+ detachFromParent(lastNode.node);
+ insertIntoFosterParent(lastNode.node);
+ } else {
+ detachFromParent(lastNode.node);
+ appendElement(lastNode.node, commonAncestor.node);
+ }
+ T clone = createElement("http://www.w3.org/1999/xhtml",
+ formattingElt.name,
+ formattingElt.attributes.cloneAttributes(null), furthestBlock.node
+ // CPPONLY: , htmlCreator(formattingElt.getHtmlCreator())
+ );
+ StackNode<T> formattingClone = new StackNode<T>(
+ formattingElt.getFlags(), formattingElt.ns,
+ formattingElt.name, clone, formattingElt.popName,
+ formattingElt.attributes
+ // CPPONLY: , formattingElt.getHtmlCreator()
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ ); // Ownership transfers to stack below
+ formattingElt.dropAttributes(); // transfer ownership to
+ // formattingClone
+ appendChildrenToNewParent(furthestBlock.node, clone);
+ appendElement(clone, furthestBlock.node);
+ removeFromListOfActiveFormattingElements(formattingEltListPos);
+ insertIntoListOfActiveFormattingElements(formattingClone, bookmark);
+ assert formattingEltStackPos < furthestBlockPos;
+ removeFromStack(formattingEltStackPos);
+ // furthestBlockPos is now off by one and points to the slot after
+ // it
+ insertIntoStack(formattingClone, furthestBlockPos);
+ }
+ return true;
+ }
+
+ private void insertIntoStack(StackNode<T> node, int position)
+ throws SAXException {
+ assert currentPtr + 1 < stack.length;
+ assert position <= currentPtr + 1;
+ if (position == currentPtr + 1) {
+ push(node);
+ } else {
+ System.arraycopy(stack, position, stack, position + 1,
+ (currentPtr - position) + 1);
+ currentPtr++;
+ stack[position] = node;
+ }
+ }
+
+ private void insertIntoListOfActiveFormattingElements(
+ StackNode<T> formattingClone, int bookmark) {
+ formattingClone.retain();
+ assert listPtr + 1 < listOfActiveFormattingElements.length;
+ if (bookmark <= listPtr) {
+ System.arraycopy(listOfActiveFormattingElements, bookmark,
+ listOfActiveFormattingElements, bookmark + 1,
+ (listPtr - bookmark) + 1);
+ }
+ listPtr++;
+ listOfActiveFormattingElements[bookmark] = formattingClone;
+ }
+
+ private int findInListOfActiveFormattingElements(StackNode<T> node) {
+ for (int i = listPtr; i >= 0; i--) {
+ if (node == listOfActiveFormattingElements[i]) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private int findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker(
+ @Local String name) {
+ for (int i = listPtr; i >= 0; i--) {
+ StackNode<T> node = listOfActiveFormattingElements[i];
+ if (node == null) {
+ return -1;
+ } else if (node.name == name) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+
+ private void maybeForgetEarlierDuplicateFormattingElement(
+ @Local String name, HtmlAttributes attributes) throws SAXException {
+ int candidate = -1;
+ int count = 0;
+ for (int i = listPtr; i >= 0; i--) {
+ StackNode<T> node = listOfActiveFormattingElements[i];
+ if (node == null) {
+ break;
+ }
+ if (node.name == name && node.attributes.equalsAnother(attributes)) {
+ candidate = i;
+ ++count;
+ }
+ }
+ if (count >= 3) {
+ removeFromListOfActiveFormattingElements(candidate);
+ }
+ }
+
+ private int findLastOrRoot(@Local String name) {
+ for (int i = currentPtr; i > 0; i--) {
+ if (stack[i].ns == "http://www.w3.org/1999/xhtml" && stack[i].name == name) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ private int findLastOrRoot(int group) {
+ for (int i = currentPtr; i > 0; i--) {
+ if (stack[i].getGroup() == group) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Attempt to add attribute to the body element.
+ * @param attributes the attributes
+ * @return <code>true</code> iff the attributes were added
+ * @throws SAXException
+ */
+ private boolean addAttributesToBody(HtmlAttributes attributes)
+ throws SAXException {
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+ if (currentPtr >= 1) {
+ StackNode<T> body = stack[1];
+ if (body.getGroup() == TreeBuilder.BODY) {
+ addAttributesToElement(body.node, attributes);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void addAttributesToHtml(HtmlAttributes attributes)
+ throws SAXException {
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+ addAttributesToElement(stack[0].node, attributes);
+ }
+
+ private void pushHeadPointerOntoStack() throws SAXException {
+ assert headPointer != null;
+ assert mode == AFTER_HEAD;
+ fatal();
+ silentPush(new StackNode<T>(ElementName.HEAD, headPointer
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ ));
+ }
+
+ /**
+ * @throws SAXException
+ *
+ */
+ private void reconstructTheActiveFormattingElements() throws SAXException {
+ if (listPtr == -1) {
+ return;
+ }
+ StackNode<T> mostRecent = listOfActiveFormattingElements[listPtr];
+ if (mostRecent == null || isInStack(mostRecent)) {
+ return;
+ }
+ int entryPos = listPtr;
+ for (;;) {
+ entryPos--;
+ if (entryPos == -1) {
+ break;
+ }
+ if (listOfActiveFormattingElements[entryPos] == null) {
+ break;
+ }
+ if (isInStack(listOfActiveFormattingElements[entryPos])) {
+ break;
+ }
+ }
+ while (entryPos < listPtr) {
+ entryPos++;
+ StackNode<T> entry = listOfActiveFormattingElements[entryPos];
+ StackNode<T> currentNode = stack[currentPtr];
+
+ T clone;
+ if (currentNode.isFosterParenting()) {
+ clone = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", entry.name,
+ entry.attributes.cloneAttributes(null)
+ // CPPONLY: , htmlCreator(entry.getHtmlCreator())
+ );
+ } else {
+ clone = createElement("http://www.w3.org/1999/xhtml", entry.name,
+ entry.attributes.cloneAttributes(null), currentNode.node
+ // CPPONLY: , htmlCreator(entry.getHtmlCreator())
+ );
+ appendElement(clone, currentNode.node);
+ }
+
+ StackNode<T> entryClone = new StackNode<T>(entry.getFlags(),
+ entry.ns, entry.name, clone, entry.popName,
+ entry.attributes
+ // CPPONLY: , entry.getHtmlCreator()
+ // [NOCPP[
+ , entry.getLocator()
+ // ]NOCPP]
+ );
+
+ entry.dropAttributes(); // transfer ownership to entryClone
+
+ push(entryClone);
+ // stack takes ownership of the local variable
+ listOfActiveFormattingElements[entryPos] = entryClone;
+ // overwriting the old entry on the list, so release & retain
+ entry.release();
+ entryClone.retain();
+ }
+ }
+
+ private void insertIntoFosterParent(T child) throws SAXException {
+ int tablePos = findLastOrRoot(TreeBuilder.TABLE);
+ int templatePos = findLastOrRoot(TreeBuilder.TEMPLATE);
+
+ if (templatePos >= tablePos) {
+ appendElement(child, stack[templatePos].node);
+ return;
+ }
+
+ StackNode<T> node = stack[tablePos];
+ insertFosterParentedChild(child, node.node, stack[tablePos - 1].node);
+ }
+
+ private T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name,
+ HtmlAttributes attributes
+ // CPPONLY: , @Creator Object creator
+ ) throws SAXException {
+ return createAndInsertFosterParentedElement(ns, name, attributes, null
+ // CPPONLY: , creator
+ );
+ }
+
+ private T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name,
+ HtmlAttributes attributes, T form
+ // CPPONLY: , @Creator Object creator
+ ) throws SAXException {
+ int tablePos = findLastOrRoot(TreeBuilder.TABLE);
+ int templatePos = findLastOrRoot(TreeBuilder.TEMPLATE);
+
+ if (templatePos >= tablePos) {
+ T child = createElement(ns, name, attributes, form, stack[templatePos].node
+ // CPPONLY: , creator
+ );
+ appendElement(child, stack[templatePos].node);
+ return child;
+ }
+
+ StackNode<T> node = stack[tablePos];
+ return createAndInsertFosterParentedElement(ns, name, attributes, form, node.node, stack[tablePos - 1].node
+ // CPPONLY: , creator
+ );
+ }
+
+ private boolean isInStack(StackNode<T> node) {
+ for (int i = currentPtr; i >= 0; i--) {
+ if (stack[i] == node) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void popTemplateMode() {
+ templateModePtr--;
+ }
+
+ private void pop() throws SAXException {
+ StackNode<T> node = stack[currentPtr];
+ assert debugOnlyClearLastStackSlot();
+ currentPtr--;
+ elementPopped(node.ns, node.popName, node.node);
+ node.release();
+ }
+
+ private void silentPop() throws SAXException {
+ StackNode<T> node = stack[currentPtr];
+ assert debugOnlyClearLastStackSlot();
+ currentPtr--;
+ node.release();
+ }
+
+ private void popOnEof() throws SAXException {
+ StackNode<T> node = stack[currentPtr];
+ assert debugOnlyClearLastStackSlot();
+ currentPtr--;
+ markMalformedIfScript(node.node);
+ elementPopped(node.ns, node.popName, node.node);
+ node.release();
+ }
+
+ // [NOCPP[
+ private void checkAttributes(HtmlAttributes attributes, @NsUri String ns)
+ throws SAXException {
+ if (errorHandler != null) {
+ int len = attributes.getXmlnsLength();
+ for (int i = 0; i < len; i++) {
+ AttributeName name = attributes.getXmlnsAttributeName(i);
+ if (name == AttributeName.XMLNS) {
+ if (html4) {
+ err("Attribute \u201Cxmlns\u201D not allowed here. (HTML4-only error.)");
+ } else {
+ String xmlns = attributes.getXmlnsValue(i);
+ if (!ns.equals(xmlns)) {
+ err("Bad value \u201C"
+ + xmlns
+ + "\u201D for the attribute \u201Cxmlns\u201D (only \u201C"
+ + ns + "\u201D permitted here).");
+ switch (namePolicy) {
+ case ALTER_INFOSET:
+ // fall through
+ case ALLOW:
+ warn("Attribute \u201Cxmlns\u201D is not serializable as XML 1.0.");
+ break;
+ case FATAL:
+ fatal("Attribute \u201Cxmlns\u201D is not serializable as XML 1.0.");
+ break;
+ }
+ }
+ }
+ } else if (ns != "http://www.w3.org/1999/xhtml"
+ && name == AttributeName.XMLNS_XLINK) {
+ String xmlns = attributes.getXmlnsValue(i);
+ if (!"http://www.w3.org/1999/xlink".equals(xmlns)) {
+ err("Bad value \u201C"
+ + xmlns
+ + "\u201D for the attribute \u201Cxmlns:link\u201D (only \u201Chttp://www.w3.org/1999/xlink\u201D permitted here).");
+ switch (namePolicy) {
+ case ALTER_INFOSET:
+ // fall through
+ case ALLOW:
+ warn("Attribute \u201Cxmlns:xlink\u201D with a value other than \u201Chttp://www.w3.org/1999/xlink\u201D is not serializable as XML 1.0 without changing document semantics.");
+ break;
+ case FATAL:
+ fatal("Attribute \u201Cxmlns:xlink\u201D with a value other than \u201Chttp://www.w3.org/1999/xlink\u201D is not serializable as XML 1.0 without changing document semantics.");
+ break;
+ }
+ }
+ } else {
+ err("Attribute \u201C" + attributes.getXmlnsLocalName(i)
+ + "\u201D not allowed here.");
+ switch (namePolicy) {
+ case ALTER_INFOSET:
+ // fall through
+ case ALLOW:
+ warn("Attribute with the local name \u201C"
+ + attributes.getXmlnsLocalName(i)
+ + "\u201D is not serializable as XML 1.0.");
+ break;
+ case FATAL:
+ fatal("Attribute with the local name \u201C"
+ + attributes.getXmlnsLocalName(i)
+ + "\u201D is not serializable as XML 1.0.");
+ break;
+ }
+ }
+ }
+ }
+ attributes.processNonNcNames(this, namePolicy);
+ }
+
+ private String checkPopName(@Local String name) throws SAXException {
+ if (NCName.isNCName(name)) {
+ return name;
+ } else {
+ switch (namePolicy) {
+ case ALLOW:
+ warn("Element name \u201C" + name
+ + "\u201D cannot be represented as XML 1.0.");
+ return name;
+ case ALTER_INFOSET:
+ warn("Element name \u201C" + name
+ + "\u201D cannot be represented as XML 1.0.");
+ return NCName.escapeName(name);
+ case FATAL:
+ fatal("Element name \u201C" + name
+ + "\u201D cannot be represented as XML 1.0.");
+ }
+ }
+ return null; // keep compiler happy
+ }
+
+ // ]NOCPP]
+
+ private void appendHtmlElementToDocumentAndPush(HtmlAttributes attributes)
+ throws SAXException {
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+ T elt = createHtmlElementSetAsRoot(attributes);
+ StackNode<T> node = new StackNode<T>(ElementName.HTML,
+ elt
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ push(node);
+ }
+
+ private void appendHtmlElementToDocumentAndPush() throws SAXException {
+ appendHtmlElementToDocumentAndPush(tokenizer.emptyAttributes());
+ }
+
+ private void appendToCurrentNodeAndPushHeadElement(HtmlAttributes attributes)
+ throws SAXException {
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+ T currentNode = stack[currentPtr].node;
+ T elt = createElement("http://www.w3.org/1999/xhtml", "head", attributes, currentNode
+ /*
+ * head uses NS_NewHTMLSharedElement creator
+ */
+ // CPPONLY: , htmlCreator(NS_NewHTMLSharedElement)
+ );
+ appendElement(elt, currentNode);
+ headPointer = elt;
+ StackNode<T> node = new StackNode<T>(ElementName.HEAD,
+ elt
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ push(node);
+ }
+
+ private void appendToCurrentNodeAndPushBodyElement(HtmlAttributes attributes)
+ throws SAXException {
+ appendToCurrentNodeAndPushElement(ElementName.BODY,
+ attributes);
+ }
+
+ private void appendToCurrentNodeAndPushBodyElement() throws SAXException {
+ appendToCurrentNodeAndPushBodyElement(tokenizer.emptyAttributes());
+ }
+
+ private void appendToCurrentNodeAndPushFormElementMayFoster(
+ HtmlAttributes attributes) throws SAXException {
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+
+ T elt;
+ StackNode<T> current = stack[currentPtr];
+ if (current.isFosterParenting()) {
+ fatal();
+ elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", "form", attributes
+ // CPPONLY: , htmlCreator(NS_NewHTMLFormElement)
+ );
+ } else {
+ elt = createElement("http://www.w3.org/1999/xhtml", "form", attributes, current.node
+ // CPPONLY: , htmlCreator(NS_NewHTMLFormElement)
+ );
+ appendElement(elt, current.node);
+ }
+
+ if (!isTemplateContents()) {
+ formPointer = elt;
+ }
+
+ StackNode<T> node = new StackNode<T>(ElementName.FORM,
+ elt
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ push(node);
+ }
+
+ private void appendToCurrentNodeAndPushFormattingElementMayFoster(
+ ElementName elementName, HtmlAttributes attributes)
+ throws SAXException {
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+ // This method can't be called for custom elements
+ HtmlAttributes clone = attributes.cloneAttributes(null);
+ // Attributes must not be read after calling createElement, because
+ // createElement may delete attributes in C++.
+ T elt;
+ StackNode<T> current = stack[currentPtr];
+ if (current.isFosterParenting()) {
+ fatal();
+ elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", elementName.getName(), attributes
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ } else {
+ elt = createElement("http://www.w3.org/1999/xhtml", elementName.getName(), attributes, current.node
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ appendElement(elt, current.node);
+ }
+ StackNode<T> node = new StackNode<T>(elementName, elt, clone
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ push(node);
+ append(node);
+ node.retain(); // append doesn't retain itself
+ }
+
+ private void appendToCurrentNodeAndPushElement(ElementName elementName,
+ HtmlAttributes attributes)
+ throws SAXException {
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+ // This method can't be called for custom elements
+ T currentNode = stack[currentPtr].node;
+ T elt = createElement("http://www.w3.org/1999/xhtml", elementName.getName(), attributes, currentNode
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ appendElement(elt, currentNode);
+ if (ElementName.TEMPLATE == elementName) {
+ elt = getDocumentFragmentForTemplate(elt);
+ }
+ StackNode<T> node = new StackNode<T>(elementName, elt
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ push(node);
+ }
+
+ private void appendToCurrentNodeAndPushElementMayFoster(ElementName elementName,
+ HtmlAttributes attributes)
+ throws SAXException {
+ @Local String popName = elementName.getName();
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ if (!elementName.isInterned()) {
+ popName = checkPopName(popName);
+ }
+ // ]NOCPP]
+ T elt;
+ StackNode<T> current = stack[currentPtr];
+ if (current.isFosterParenting()) {
+ fatal();
+ elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", popName, attributes
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ } else {
+ elt = createElement("http://www.w3.org/1999/xhtml", popName, attributes, current.node
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ appendElement(elt, current.node);
+ }
+ StackNode<T> node = new StackNode<T>(elementName, elt, popName
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ push(node);
+ }
+
+ private void appendToCurrentNodeAndPushElementMayFosterMathML(
+ ElementName elementName, HtmlAttributes attributes)
+ throws SAXException {
+ @Local String popName = elementName.getName();
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1998/Math/MathML");
+ if (!elementName.isInterned()) {
+ popName = checkPopName(popName);
+ }
+ // ]NOCPP]
+ boolean markAsHtmlIntegrationPoint = false;
+ if (ElementName.ANNOTATION_XML == elementName
+ && annotationXmlEncodingPermitsHtml(attributes)) {
+ markAsHtmlIntegrationPoint = true;
+ }
+ // Attributes must not be read after calling createElement(), since
+ // createElement may delete the object in C++.
+ T elt;
+ StackNode<T> current = stack[currentPtr];
+ if (current.isFosterParenting()) {
+ fatal();
+ elt = createAndInsertFosterParentedElement("http://www.w3.org/1998/Math/MathML", popName, attributes
+ // CPPONLY: , htmlCreator(null)
+ );
+ } else {
+ elt = createElement("http://www.w3.org/1998/Math/MathML", popName, attributes, current.node
+ // CPPONLY: , htmlCreator(null)
+ );
+ appendElement(elt, current.node);
+ }
+ StackNode<T> node = new StackNode<T>(elementName, elt, popName,
+ markAsHtmlIntegrationPoint
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ push(node);
+ }
+
+ // [NOCPP[
+ T getDocumentFragmentForTemplate(T template) {
+ return template;
+ }
+
+ T getFormPointerForContext(T context) {
+ return null;
+ }
+ // ]NOCPP]
+
+ private boolean annotationXmlEncodingPermitsHtml(HtmlAttributes attributes) {
+ String encoding = attributes.getValue(AttributeName.ENCODING);
+ if (encoding == null) {
+ return false;
+ }
+ return Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "application/xhtml+xml", encoding)
+ || Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "text/html", encoding);
+ }
+
+ private void appendToCurrentNodeAndPushElementMayFosterSVG(
+ ElementName elementName, HtmlAttributes attributes)
+ throws SAXException {
+ @Local String popName = elementName.getCamelCaseName();
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/2000/svg");
+ if (!elementName.isInterned()) {
+ popName = checkPopName(popName);
+ }
+ // ]NOCPP]
+ T elt;
+ StackNode<T> current = stack[currentPtr];
+ if (current.isFosterParenting()) {
+ fatal();
+ elt = createAndInsertFosterParentedElement("http://www.w3.org/2000/svg", popName, attributes
+ // CPPONLY: , svgCreator(elementName.getSvgCreator())
+ );
+ } else {
+ elt = createElement("http://www.w3.org/2000/svg", popName, attributes, current.node
+ // CPPONLY: , svgCreator(elementName.getSvgCreator())
+ );
+ appendElement(elt, current.node);
+ }
+ StackNode<T> node = new StackNode<T>(elementName, popName, elt
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ push(node);
+ }
+
+ private void appendToCurrentNodeAndPushElementMayFoster(ElementName elementName,
+ HtmlAttributes attributes, T form)
+ throws SAXException {
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+ // Can't be called for custom elements
+ T elt;
+ T formOwner = form == null || fragment || isTemplateContents() ? null : form;
+ StackNode<T> current = stack[currentPtr];
+ if (current.isFosterParenting()) {
+ fatal();
+ elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", elementName.getName(),
+ attributes, formOwner
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ } else {
+ elt = createElement("http://www.w3.org/1999/xhtml", elementName.getName(),
+ attributes, formOwner, current.node
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ appendElement(elt, current.node);
+ }
+ StackNode<T> node = new StackNode<T>(elementName, elt
+ // [NOCPP[
+ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer)
+ // ]NOCPP]
+ );
+ push(node);
+ }
+
+ private void appendVoidElementToCurrentMayFoster(
+ ElementName elementName, HtmlAttributes attributes, T form) throws SAXException {
+ @Local String name = elementName.getName();
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+ // Can't be called for custom elements
+ T elt;
+ T formOwner = form == null || fragment || isTemplateContents() ? null : form;
+ StackNode<T> current = stack[currentPtr];
+ if (current.isFosterParenting()) {
+ fatal();
+ elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", name,
+ attributes, formOwner
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ } else {
+ elt = createElement("http://www.w3.org/1999/xhtml", name,
+ attributes, formOwner, current.node
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ appendElement(elt, current.node);
+ }
+ elementPushed("http://www.w3.org/1999/xhtml", name, elt);
+ elementPopped("http://www.w3.org/1999/xhtml", name, elt);
+ }
+
+ private void appendVoidElementToCurrentMayFoster(
+ ElementName elementName, HtmlAttributes attributes)
+ throws SAXException {
+ @Local String popName = elementName.getName();
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ if (!elementName.isInterned()) {
+ popName = checkPopName(popName);
+ }
+ // ]NOCPP]
+ T elt;
+ StackNode<T> current = stack[currentPtr];
+ if (current.isFosterParenting()) {
+ fatal();
+ elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", popName, attributes
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ } else {
+ elt = createElement("http://www.w3.org/1999/xhtml", popName, attributes, current.node
+ // CPPONLY: , htmlCreator(elementName.getHtmlCreator())
+ );
+ appendElement(elt, current.node);
+ }
+ elementPushed("http://www.w3.org/1999/xhtml", popName, elt);
+ elementPopped("http://www.w3.org/1999/xhtml", popName, elt);
+ }
+
+ private void appendVoidElementToCurrentMayFosterSVG(
+ ElementName elementName, HtmlAttributes attributes)
+ throws SAXException {
+ @Local String popName = elementName.getCamelCaseName();
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/2000/svg");
+ if (!elementName.isInterned()) {
+ popName = checkPopName(popName);
+ }
+ // ]NOCPP]
+ T elt;
+ StackNode<T> current = stack[currentPtr];
+ if (current.isFosterParenting()) {
+ fatal();
+ elt = createAndInsertFosterParentedElement("http://www.w3.org/2000/svg", popName, attributes
+ // CPPONLY: , svgCreator(elementName.getSvgCreator())
+ );
+ } else {
+ elt = createElement("http://www.w3.org/2000/svg", popName, attributes, current.node
+ // CPPONLY: , svgCreator(elementName.getSvgCreator())
+ );
+ appendElement(elt, current.node);
+ }
+ elementPushed("http://www.w3.org/2000/svg", popName, elt);
+ elementPopped("http://www.w3.org/2000/svg", popName, elt);
+ }
+
+ private void appendVoidElementToCurrentMayFosterMathML(
+ ElementName elementName, HtmlAttributes attributes)
+ throws SAXException {
+ @Local String popName = elementName.getName();
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1998/Math/MathML");
+ if (!elementName.isInterned()) {
+ popName = checkPopName(popName);
+ }
+ // ]NOCPP]
+ T elt;
+ StackNode<T> current = stack[currentPtr];
+ if (current.isFosterParenting()) {
+ fatal();
+ elt = createAndInsertFosterParentedElement("http://www.w3.org/1998/Math/MathML", popName, attributes
+ // CPPONLY: , htmlCreator(null)
+ );
+ } else {
+ elt = createElement("http://www.w3.org/1998/Math/MathML", popName, attributes, current.node
+ // CPPONLY: , htmlCreator(null)
+ );
+ appendElement(elt, current.node);
+ }
+ elementPushed("http://www.w3.org/1998/Math/MathML", popName, elt);
+ elementPopped("http://www.w3.org/1998/Math/MathML", popName, elt);
+ }
+
+ private void appendVoidInputToCurrent(HtmlAttributes attributes, T form) throws SAXException {
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+ // Can't be called for custom elements
+ T currentNode = stack[currentPtr].node;
+ T elt = createElement("http://www.w3.org/1999/xhtml", "input", attributes,
+ form == null || fragment || isTemplateContents() ? null : form, currentNode
+ // CPPONLY: , htmlCreator(NS_NewHTMLInputElement)
+ );
+ appendElement(elt, currentNode);
+ elementPushed("http://www.w3.org/1999/xhtml", "input", elt);
+ elementPopped("http://www.w3.org/1999/xhtml", "input", elt);
+ }
+
+ private void appendVoidFormToCurrent(HtmlAttributes attributes) throws SAXException {
+ // [NOCPP[
+ checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
+ // ]NOCPP]
+ T currentNode = stack[currentPtr].node;
+ T elt = createElement("http://www.w3.org/1999/xhtml", "form",
+ attributes, currentNode
+ // CPPONLY: , htmlCreator(NS_NewHTMLFormElement)
+ );
+ formPointer = elt;
+ // ownership transferred to form pointer
+ appendElement(elt, currentNode);
+ elementPushed("http://www.w3.org/1999/xhtml", "form", elt);
+ elementPopped("http://www.w3.org/1999/xhtml", "form", elt);
+ }
+
+ // [NOCPP[
+
+ private final void accumulateCharactersForced(@Const @NoLength char[] buf,
+ int start, int length) throws SAXException {
+ System.arraycopy(buf, start, charBuffer, charBufferLen, length);
+ charBufferLen += length;
+ }
+
+ @Override public void ensureBufferSpace(int inputLength)
+ throws SAXException {
+ // TODO: Unify Tokenizer.strBuf and TreeBuilder.charBuffer so that
+ // this method becomes unnecessary.
+ int worstCase = charBufferLen + inputLength;
+ if (charBuffer == null) {
+ // Add an arbitrary small value to avoid immediate reallocation
+ // once there are a few characters in the buffer.
+ charBuffer = new char[worstCase + 128];
+ } else if (worstCase > charBuffer.length) {
+ // HotSpot reportedly allocates memory with 8-byte accuracy, so
+ // there's no point in trying to do math here to avoid slop.
+ // Maybe we should add some small constant to worstCase here
+ // but not doing that without profiling. In C++ with jemalloc,
+ // the corresponding method should do math to round up here
+ // to avoid slop.
+ char[] newBuf = new char[worstCase];
+ System.arraycopy(charBuffer, 0, newBuf, 0, charBufferLen);
+ charBuffer = newBuf;
+ }
+ }
+
+ // ]NOCPP]
+
+ protected void accumulateCharacters(@Const @NoLength char[] buf, int start,
+ int length) throws SAXException {
+ appendCharacters(stack[currentPtr].node, buf, start, length);
+ }
+
+ // ------------------------------- //
+
+ protected final void requestSuspension() {
+ tokenizer.requestSuspension();
+ }
+
+ protected abstract T createElement(@NsUri String ns, @Local String name,
+ HtmlAttributes attributes, T intendedParent
+ // CPPONLY: , @Creator Object creator
+ ) throws SAXException;
+
+ protected T createElement(@NsUri String ns, @Local String name,
+ HtmlAttributes attributes, T form, T intendedParent
+ // CPPONLY: , @Creator Object creator
+ ) throws SAXException {
+ return createElement("http://www.w3.org/1999/xhtml", name, attributes, intendedParent
+ // CPPONLY: , creator
+ );
+ }
+
+ protected abstract T createHtmlElementSetAsRoot(HtmlAttributes attributes)
+ throws SAXException;
+
+ protected abstract void detachFromParent(T element) throws SAXException;
+
+ protected abstract boolean hasChildren(T element) throws SAXException;
+
+ protected abstract void appendElement(T child, T newParent)
+ throws SAXException;
+
+ protected abstract void appendChildrenToNewParent(T oldParent, T newParent)
+ throws SAXException;
+
+ protected abstract void insertFosterParentedChild(T child, T table,
+ T stackParent) throws SAXException;
+
+ // We don't generate CPP code for this method because it is not used in generated CPP
+ // code. Instead, the form owner version of this method is called with a null form owner.
+ // [NOCPP[
+
+ protected abstract T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name,
+ HtmlAttributes attributes, T table, T stackParent) throws SAXException;
+
+ // ]NOCPP]
+
+ protected T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name,
+ HtmlAttributes attributes, T form, T table, T stackParent
+ // CPPONLY: , @Creator Object creator
+ ) throws SAXException {
+ return createAndInsertFosterParentedElement(ns, name, attributes, table, stackParent);
+ };
+
+ protected abstract void insertFosterParentedCharacters(
+ @NoLength char[] buf, int start, int length, T table, T stackParent)
+ throws SAXException;
+
+ protected abstract void appendCharacters(T parent, @NoLength char[] buf,
+ int start, int length) throws SAXException;
+
+ // CPPONLY: protected abstract void appendIsindexPrompt(T parent) throws SAXException;
+
+ protected abstract void appendComment(T parent, @NoLength char[] buf,
+ int start, int length) throws SAXException;
+
+ protected abstract void appendCommentToDocument(@NoLength char[] buf,
+ int start, int length) throws SAXException;
+
+ protected abstract void addAttributesToElement(T element,
+ HtmlAttributes attributes) throws SAXException;
+
+ protected void markMalformedIfScript(T elt) throws SAXException {
+
+ }
+
+ protected void start(boolean fragmentMode) throws SAXException {
+
+ }
+
+ protected void end() throws SAXException {
+
+ }
+
+ protected void appendDoctypeToDocument(@Local String name,
+ String publicIdentifier, String systemIdentifier)
+ throws SAXException {
+
+ }
+
+ protected void elementPushed(@NsUri String ns, @Local String name, T node)
+ throws SAXException {
+
+ }
+
+ protected void elementPopped(@NsUri String ns, @Local String name, T node)
+ throws SAXException {
+
+ }
+
+ // [NOCPP[
+
+ protected void documentMode(DocumentMode m, String publicIdentifier,
+ String systemIdentifier, boolean html4SpecificAdditionalErrorChecks)
+ throws SAXException {
+
+ }
+
+ /**
+ * @see nu.validator.htmlparser.common.TokenHandler#wantsComments()
+ */
+ public boolean wantsComments() {
+ return wantingComments;
+ }
+
+ public void setIgnoringComments(boolean ignoreComments) {
+ wantingComments = !ignoreComments;
+ }
+
+ /**
+ * Sets the errorHandler.
+ *
+ * @param errorHandler
+ * the errorHandler to set
+ */
+ public final void setErrorHandler(ErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+ /**
+ * Returns the errorHandler.
+ *
+ * @return the errorHandler
+ */
+ public ErrorHandler getErrorHandler() {
+ return errorHandler;
+ }
+
+ /**
+ * The argument MUST be an interned string or <code>null</code>.
+ *
+ * @param context
+ */
+ public final void setFragmentContext(@Local String context) {
+ this.contextName = context;
+ this.contextNamespace = "http://www.w3.org/1999/xhtml";
+ this.contextNode = null;
+ this.fragment = (contextName != null);
+ this.quirks = false;
+ }
+
+ // ]NOCPP]
+
+ /**
+ * @see nu.validator.htmlparser.common.TokenHandler#cdataSectionAllowed()
+ */
+ @Inline public boolean cdataSectionAllowed() throws SAXException {
+ return isInForeign();
+ }
+
+ private boolean isInForeign() {
+ return currentPtr >= 0
+ && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml";
+ }
+
+ private boolean isInForeignButNotHtmlOrMathTextIntegrationPoint() {
+ if (currentPtr < 0) {
+ return false;
+ }
+ return !isSpecialParentInForeign(stack[currentPtr]);
+ }
+
+ /**
+ * The argument MUST be an interned string or <code>null</code>.
+ *
+ * @param context
+ */
+ public final void setFragmentContext(@Local String context,
+ @NsUri String ns, T node, boolean quirks) {
+ // [NOCPP[
+ if (!((context == null && ns == null)
+ || "http://www.w3.org/1999/xhtml" == ns
+ || "http://www.w3.org/2000/svg" == ns || "http://www.w3.org/1998/Math/MathML" == ns)) {
+ throw new IllegalArgumentException(
+ "The namespace must be the HTML, SVG or MathML namespace (or null when the local name is null). Got: "
+ + ns);
+ }
+ // ]NOCPP]
+ this.contextName = context;
+ this.contextNamespace = ns;
+ this.contextNode = node;
+ this.fragment = (contextName != null);
+ this.quirks = quirks;
+ }
+
+ protected final T currentNode() {
+ return stack[currentPtr].node;
+ }
+
+ /**
+ * Returns the scriptingEnabled.
+ *
+ * @return the scriptingEnabled
+ */
+ public boolean isScriptingEnabled() {
+ return scriptingEnabled;
+ }
+
+ /**
+ * Sets the scriptingEnabled.
+ *
+ * @param scriptingEnabled
+ * the scriptingEnabled to set
+ */
+ public void setScriptingEnabled(boolean scriptingEnabled) {
+ this.scriptingEnabled = scriptingEnabled;
+ }
+
+ public void setIsSrcdocDocument(boolean isSrcdocDocument) {
+ this.isSrcdocDocument = isSrcdocDocument;
+ }
+
+ // [NOCPP[
+
+ /**
+ * Sets the doctypeExpectation.
+ *
+ * @param doctypeExpectation
+ * the doctypeExpectation to set
+ */
+ public void setDoctypeExpectation(DoctypeExpectation doctypeExpectation) {
+ this.doctypeExpectation = doctypeExpectation;
+ }
+
+ public void setNamePolicy(XmlViolationPolicy namePolicy) {
+ this.namePolicy = namePolicy;
+ }
+
+ /**
+ * Sets the documentModeHandler.
+ *
+ * @param documentModeHandler
+ * the documentModeHandler to set
+ */
+ public void setDocumentModeHandler(DocumentModeHandler documentModeHandler) {
+ this.documentModeHandler = documentModeHandler;
+ }
+
+ /**
+ * Sets the reportingDoctype.
+ *
+ * @param reportingDoctype
+ * the reportingDoctype to set
+ */
+ public void setReportingDoctype(boolean reportingDoctype) {
+ this.reportingDoctype = reportingDoctype;
+ }
+
+ // ]NOCPP]
+
+ /**
+ * Flushes the pending characters. Public for document.write use cases only.
+ * @throws SAXException
+ */
+ public final void flushCharacters() throws SAXException {
+ if (charBufferLen > 0) {
+ if ((mode == IN_TABLE || mode == IN_TABLE_BODY || mode == IN_ROW)
+ && charBufferContainsNonWhitespace()) {
+ errNonSpaceInTable();
+ reconstructTheActiveFormattingElements();
+ if (!stack[currentPtr].isFosterParenting()) {
+ // reconstructing gave us a new current node
+ appendCharacters(currentNode(), charBuffer, 0,
+ charBufferLen);
+ charBufferLen = 0;
+ return;
+ }
+
+ int tablePos = findLastOrRoot(TreeBuilder.TABLE);
+ int templatePos = findLastOrRoot(TreeBuilder.TEMPLATE);
+
+ if (templatePos >= tablePos) {
+ appendCharacters(stack[templatePos].node, charBuffer, 0, charBufferLen);
+ charBufferLen = 0;
+ return;
+ }
+
+ StackNode<T> tableElt = stack[tablePos];
+ insertFosterParentedCharacters(charBuffer, 0, charBufferLen,
+ tableElt.node, stack[tablePos - 1].node);
+ charBufferLen = 0;
+ return;
+ }
+ appendCharacters(currentNode(), charBuffer, 0, charBufferLen);
+ charBufferLen = 0;
+ }
+ }
+
+ private boolean charBufferContainsNonWhitespace() {
+ for (int i = 0; i < charBufferLen; i++) {
+ switch (charBuffer[i]) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\u000C':
+ continue;
+ default:
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Creates a comparable snapshot of the tree builder state. Snapshot
+ * creation is only supported immediately after a script end tag has been
+ * processed. In C++ the caller is responsible for calling
+ * <code>delete</code> on the returned object.
+ *
+ * @return a snapshot.
+ * @throws SAXException
+ */
+ @SuppressWarnings("unchecked") public TreeBuilderState<T> newSnapshot()
+ throws SAXException {
+ StackNode<T>[] listCopy = new StackNode[listPtr + 1];
+ for (int i = 0; i < listCopy.length; i++) {
+ StackNode<T> node = listOfActiveFormattingElements[i];
+ if (node != null) {
+ StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns,
+ node.name, node.node, node.popName,
+ node.attributes.cloneAttributes(null)
+ // CPPONLY: , node.getHtmlCreator()
+ // [NOCPP[
+ , node.getLocator()
+ // ]NOCPP]
+ );
+ listCopy[i] = newNode;
+ } else {
+ listCopy[i] = null;
+ }
+ }
+ StackNode<T>[] stackCopy = new StackNode[currentPtr + 1];
+ for (int i = 0; i < stackCopy.length; i++) {
+ StackNode<T> node = stack[i];
+ int listIndex = findInListOfActiveFormattingElements(node);
+ if (listIndex == -1) {
+ StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns,
+ node.name, node.node, node.popName,
+ null
+ // CPPONLY: , node.getHtmlCreator()
+ // [NOCPP[
+ , node.getLocator()
+ // ]NOCPP]
+ );
+ stackCopy[i] = newNode;
+ } else {
+ stackCopy[i] = listCopy[listIndex];
+ stackCopy[i].retain();
+ }
+ }
+ int[] templateModeStackCopy = new int[templateModePtr + 1];
+ System.arraycopy(templateModeStack, 0, templateModeStackCopy, 0,
+ templateModeStackCopy.length);
+ return new StateSnapshot<T>(stackCopy, listCopy, templateModeStackCopy, formPointer,
+ headPointer, deepTreeSurrogateParent, mode, originalMode, framesetOk,
+ needToDropLF, quirks);
+ }
+
+ public boolean snapshotMatches(TreeBuilderState<T> snapshot) {
+ StackNode<T>[] stackCopy = snapshot.getStack();
+ int stackLen = snapshot.getStackLength();
+ StackNode<T>[] listCopy = snapshot.getListOfActiveFormattingElements();
+ int listLen = snapshot.getListOfActiveFormattingElementsLength();
+ int[] templateModeStackCopy = snapshot.getTemplateModeStack();
+ int templateModeStackLen = snapshot.getTemplateModeStackLength();
+
+ if (stackLen != currentPtr + 1
+ || listLen != listPtr + 1
+ || templateModeStackLen != templateModePtr + 1
+ || formPointer != snapshot.getFormPointer()
+ || headPointer != snapshot.getHeadPointer()
+ || deepTreeSurrogateParent != snapshot.getDeepTreeSurrogateParent()
+ || mode != snapshot.getMode()
+ || originalMode != snapshot.getOriginalMode()
+ || framesetOk != snapshot.isFramesetOk()
+ || needToDropLF != snapshot.isNeedToDropLF()
+ || quirks != snapshot.isQuirks()) { // maybe just assert quirks
+ return false;
+ }
+ for (int i = listLen - 1; i >= 0; i--) {
+ if (listCopy[i] == null
+ && listOfActiveFormattingElements[i] == null) {
+ continue;
+ } else if (listCopy[i] == null
+ || listOfActiveFormattingElements[i] == null) {
+ return false;
+ }
+ if (listCopy[i].node != listOfActiveFormattingElements[i].node) {
+ return false; // it's possible that this condition is overly
+ // strict
+ }
+ }
+ for (int i = stackLen - 1; i >= 0; i--) {
+ if (stackCopy[i].node != stack[i].node) {
+ return false;
+ }
+ }
+ for (int i = templateModeStackLen - 1; i >=0; i--) {
+ if (templateModeStackCopy[i] != templateModeStack[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @SuppressWarnings("unchecked") public void loadState(
+ TreeBuilderState<T> snapshot, Interner interner)
+ throws SAXException {
+ StackNode<T>[] stackCopy = snapshot.getStack();
+ int stackLen = snapshot.getStackLength();
+ StackNode<T>[] listCopy = snapshot.getListOfActiveFormattingElements();
+ int listLen = snapshot.getListOfActiveFormattingElementsLength();
+ int[] templateModeStackCopy = snapshot.getTemplateModeStack();
+ int templateModeStackLen = snapshot.getTemplateModeStackLength();
+
+ for (int i = 0; i <= listPtr; i++) {
+ if (listOfActiveFormattingElements[i] != null) {
+ listOfActiveFormattingElements[i].release();
+ }
+ }
+ if (listOfActiveFormattingElements.length < listLen) {
+ listOfActiveFormattingElements = new StackNode[listLen];
+ }
+ listPtr = listLen - 1;
+
+ for (int i = 0; i <= currentPtr; i++) {
+ stack[i].release();
+ }
+ if (stack.length < stackLen) {
+ stack = new StackNode[stackLen];
+ }
+ currentPtr = stackLen - 1;
+
+ if (templateModeStack.length < templateModeStackLen) {
+ templateModeStack = new int[templateModeStackLen];
+ }
+ templateModePtr = templateModeStackLen - 1;
+
+ for (int i = 0; i < listLen; i++) {
+ StackNode<T> node = listCopy[i];
+ if (node != null) {
+ StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns,
+ Portability.newLocalFromLocal(node.name, interner), node.node,
+ Portability.newLocalFromLocal(node.popName, interner),
+ node.attributes.cloneAttributes(null)
+ // CPPONLY: , node.getHtmlCreator()
+ // [NOCPP[
+ , node.getLocator()
+ // ]NOCPP]
+ );
+ listOfActiveFormattingElements[i] = newNode;
+ } else {
+ listOfActiveFormattingElements[i] = null;
+ }
+ }
+ for (int i = 0; i < stackLen; i++) {
+ StackNode<T> node = stackCopy[i];
+ int listIndex = findInArray(node, listCopy);
+ if (listIndex == -1) {
+ StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns,
+ Portability.newLocalFromLocal(node.name, interner), node.node,
+ Portability.newLocalFromLocal(node.popName, interner),
+ null
+ // CPPONLY: , node.getHtmlCreator()
+ // [NOCPP[
+ , node.getLocator()
+ // ]NOCPP]
+ );
+ stack[i] = newNode;
+ } else {
+ stack[i] = listOfActiveFormattingElements[listIndex];
+ stack[i].retain();
+ }
+ }
+ System.arraycopy(templateModeStackCopy, 0, templateModeStack, 0, templateModeStackLen);
+ formPointer = snapshot.getFormPointer();
+ headPointer = snapshot.getHeadPointer();
+ deepTreeSurrogateParent = snapshot.getDeepTreeSurrogateParent();
+ mode = snapshot.getMode();
+ originalMode = snapshot.getOriginalMode();
+ framesetOk = snapshot.isFramesetOk();
+ needToDropLF = snapshot.isNeedToDropLF();
+ quirks = snapshot.isQuirks();
+ }
+
+ private int findInArray(StackNode<T> node, StackNode<T>[] arr) {
+ for (int i = listPtr; i >= 0; i--) {
+ if (node == arr[i]) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getFormPointer()
+ */
+ public T getFormPointer() {
+ return formPointer;
+ }
+
+ /**
+ * Returns the headPointer.
+ *
+ * @return the headPointer
+ */
+ public T getHeadPointer() {
+ return headPointer;
+ }
+
+ /**
+ * Returns the deepTreeSurrogateParent.
+ *
+ * @return the deepTreeSurrogateParent
+ */
+ public T getDeepTreeSurrogateParent() {
+ return deepTreeSurrogateParent;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getListOfActiveFormattingElements()
+ */
+ public StackNode<T>[] getListOfActiveFormattingElements() {
+ return listOfActiveFormattingElements;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getStack()
+ */
+ public StackNode<T>[] getStack() {
+ return stack;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStack()
+ */
+ public int[] getTemplateModeStack() {
+ return templateModeStack;
+ }
+
+ /**
+ * Returns the mode.
+ *
+ * @return the mode
+ */
+ public int getMode() {
+ return mode;
+ }
+
+ /**
+ * Returns the originalMode.
+ *
+ * @return the originalMode
+ */
+ public int getOriginalMode() {
+ return originalMode;
+ }
+
+ /**
+ * Returns the framesetOk.
+ *
+ * @return the framesetOk
+ */
+ public boolean isFramesetOk() {
+ return framesetOk;
+ }
+
+ /**
+ * Returns the needToDropLF.
+ *
+ * @return the needToDropLF
+ */
+ public boolean isNeedToDropLF() {
+ return needToDropLF;
+ }
+
+ /**
+ * Returns the quirks.
+ *
+ * @return the quirks
+ */
+ public boolean isQuirks() {
+ return quirks;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getListOfActiveFormattingElementsLength()
+ */
+ public int getListOfActiveFormattingElementsLength() {
+ return listPtr + 1;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getStackLength()
+ */
+ public int getStackLength() {
+ return currentPtr + 1;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStackLength()
+ */
+ public int getTemplateModeStackLength() {
+ return templateModePtr + 1;
+ }
+
+ /**
+ * Reports a stray start tag.
+ * @param name the name of the stray tag
+ *
+ * @throws SAXException
+ */
+ private void errStrayStartTag(@Local String name) throws SAXException {
+ err("Stray start tag \u201C" + name + "\u201D.");
+ }
+
+ /**
+ * Reports a stray end tag.
+ * @param name the name of the stray tag
+ *
+ * @throws SAXException
+ */
+ private void errStrayEndTag(@Local String name) throws SAXException {
+ err("Stray end tag \u201C" + name + "\u201D.");
+ }
+
+ /**
+ * Reports a state when elements expected to be closed were not.
+ *
+ * @param eltPos the position of the start tag on the stack of the element
+ * being closed.
+ * @param name the name of the end tag
+ *
+ * @throws SAXException
+ */
+ private void errUnclosedElements(int eltPos, @Local String name) throws SAXException {
+ errNoCheck("End tag \u201C" + name + "\u201D seen, but there were open elements.");
+ errListUnclosedStartTags(eltPos);
+ }
+
+ /**
+ * Reports a state when elements expected to be closed ahead of an implied
+ * end tag but were not.
+ *
+ * @param eltPos the position of the start tag on the stack of the element
+ * being closed.
+ * @param name the name of the end tag
+ *
+ * @throws SAXException
+ */
+ private void errUnclosedElementsImplied(int eltPos, String name) throws SAXException {
+ errNoCheck("End tag \u201C" + name + "\u201D implied, but there were open elements.");
+ errListUnclosedStartTags(eltPos);
+ }
+
+ /**
+ * Reports a state when elements expected to be closed ahead of an implied
+ * table cell close.
+ *
+ * @param eltPos the position of the start tag on the stack of the element
+ * being closed.
+ * @throws SAXException
+ */
+ private void errUnclosedElementsCell(int eltPos) throws SAXException {
+ errNoCheck("A table cell was implicitly closed, but there were open elements.");
+ errListUnclosedStartTags(eltPos);
+ }
+
+ private void errStrayDoctype() throws SAXException {
+ err("Stray doctype.");
+ }
+
+ private void errAlmostStandardsDoctype() throws SAXException {
+ if (!isSrcdocDocument) {
+ err("Almost standards mode doctype. Expected \u201C<!DOCTYPE html>\u201D.");
+ }
+ }
+
+ private void errQuirkyDoctype() throws SAXException {
+ if (!isSrcdocDocument) {
+ err("Quirky doctype. Expected \u201C<!DOCTYPE html>\u201D.");
+ }
+ }
+
+ private void errNonSpaceInTrailer() throws SAXException {
+ err("Non-space character in page trailer.");
+ }
+
+ private void errNonSpaceAfterFrameset() throws SAXException {
+ err("Non-space after \u201Cframeset\u201D.");
+ }
+
+ private void errNonSpaceInFrameset() throws SAXException {
+ err("Non-space in \u201Cframeset\u201D.");
+ }
+
+ private void errNonSpaceAfterBody() throws SAXException {
+ err("Non-space character after body.");
+ }
+
+ private void errNonSpaceInColgroupInFragment() throws SAXException {
+ err("Non-space in \u201Ccolgroup\u201D when parsing fragment.");
+ }
+
+ private void errNonSpaceInNoscriptInHead() throws SAXException {
+ err("Non-space character inside \u201Cnoscript\u201D inside \u201Chead\u201D.");
+ }
+
+ private void errFooBetweenHeadAndBody(@Local String name) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("\u201C" + name + "\u201D element between \u201Chead\u201D and \u201Cbody\u201D.");
+ }
+
+ private void errStartTagWithoutDoctype() throws SAXException {
+ if (!isSrcdocDocument) {
+ err("Start tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE html>\u201D.");
+ }
+ }
+
+ private void errNoSelectInTableScope() throws SAXException {
+ err("No \u201Cselect\u201D in table scope.");
+ }
+
+ private void errStartSelectWhereEndSelectExpected() throws SAXException {
+ err("\u201Cselect\u201D start tag where end tag expected.");
+ }
+
+ private void errStartTagWithSelectOpen(@Local String name)
+ throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("\u201C" + name
+ + "\u201D start tag with \u201Cselect\u201D open.");
+ }
+
+ private void errBadStartTagInHead(@Local String name) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("Bad start tag in \u201C" + name
+ + "\u201D in \u201Chead\u201D.");
+ }
+
+ private void errImage() throws SAXException {
+ err("Saw a start tag \u201Cimage\u201D.");
+ }
+
+ // CPPONLY: private void errIsindex() throws SAXException {
+ // CPPONLY: err("\u201Cisindex\u201D seen.");
+ // CPPONLY: }
+
+ private void errFooSeenWhenFooOpen(@Local String name) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("An \u201C" + name + "\u201D start tag seen but an element of the same type was already open.");
+ }
+
+ private void errHeadingWhenHeadingOpen() throws SAXException {
+ err("Heading cannot be a child of another heading.");
+ }
+
+ private void errFramesetStart() throws SAXException {
+ err("\u201Cframeset\u201D start tag seen.");
+ }
+
+ private void errNoCellToClose() throws SAXException {
+ err("No cell to close.");
+ }
+
+ private void errStartTagInTable(@Local String name) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("Start tag \u201C" + name
+ + "\u201D seen in \u201Ctable\u201D.");
+ }
+
+ private void errFormWhenFormOpen() throws SAXException {
+ err("Saw a \u201Cform\u201D start tag, but there was already an active \u201Cform\u201D element. Nested forms are not allowed. Ignoring the tag.");
+ }
+
+ private void errTableSeenWhileTableOpen() throws SAXException {
+ err("Start tag for \u201Ctable\u201D seen but the previous \u201Ctable\u201D is still open.");
+ }
+
+ private void errStartTagInTableBody(@Local String name) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("\u201C" + name + "\u201D start tag in table body.");
+ }
+
+ private void errEndTagSeenWithoutDoctype() throws SAXException {
+ if (!isSrcdocDocument) {
+ err("End tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE html>\u201D.");
+ }
+ }
+
+ private void errEndTagAfterBody() throws SAXException {
+ err("Saw an end tag after \u201Cbody\u201D had been closed.");
+ }
+
+ private void errEndTagSeenWithSelectOpen(@Local String name) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("\u201C" + name
+ + "\u201D end tag with \u201Cselect\u201D open.");
+ }
+
+ private void errGarbageInColgroup() throws SAXException {
+ err("Garbage in \u201Ccolgroup\u201D fragment.");
+ }
+
+ private void errEndTagBr() throws SAXException {
+ err("End tag \u201Cbr\u201D.");
+ }
+
+ private void errNoElementToCloseButEndTagSeen(@Local String name)
+ throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("No \u201C" + name + "\u201D element in scope but a \u201C"
+ + name + "\u201D end tag seen.");
+ }
+
+ private void errHtmlStartTagInForeignContext(@Local String name)
+ throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("HTML start tag \u201C" + name
+ + "\u201D in a foreign namespace context.");
+ }
+
+ private void errTableClosedWhileCaptionOpen() throws SAXException {
+ err("\u201Ctable\u201D closed but \u201Ccaption\u201D was still open.");
+ }
+
+ private void errNoTableRowToClose() throws SAXException {
+ err("No table row to close.");
+ }
+
+ private void errNonSpaceInTable() throws SAXException {
+ err("Misplaced non-space characters insided a table.");
+ }
+
+ private void errUnclosedChildrenInRuby() throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("Unclosed children in \u201Cruby\u201D.");
+ }
+
+ private void errStartTagSeenWithoutRuby(@Local String name) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("Start tag \u201C"
+ + name
+ + "\u201D seen without a \u201Cruby\u201D element being open.");
+ }
+
+ private void errSelfClosing() throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("Self-closing syntax (\u201C/>\u201D) used on a non-void HTML element. Ignoring the slash and treating as a start tag.");
+ }
+
+ private void errNoCheckUnclosedElementsOnStack() throws SAXException {
+ errNoCheck("Unclosed elements on stack.");
+ }
+
+ private void errEndTagDidNotMatchCurrentOpenElement(@Local String name,
+ @Local String currOpenName) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("End tag \u201C"
+ + name
+ + "\u201D did not match the name of the current open element (\u201C"
+ + currOpenName + "\u201D).");
+ }
+
+ private void errEndTagViolatesNestingRules(@Local String name) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("End tag \u201C" + name + "\u201D violates nesting rules.");
+ }
+
+ private void errEofWithUnclosedElements() throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("End of file seen and there were open elements.");
+ // just report all remaining unclosed elements
+ errListUnclosedStartTags(0);
+ }
+
+ /**
+ * Reports arriving at/near end of document with unclosed elements remaining.
+ *
+ * @param message
+ * the message
+ * @throws SAXException
+ */
+ private void errEndWithUnclosedElements(@Local String name) throws SAXException {
+ if (errorHandler == null) {
+ return;
+ }
+ errNoCheck("End tag for \u201C"
+ + name
+ + "\u201D seen, but there were unclosed elements.");
+ // just report all remaining unclosed elements
+ errListUnclosedStartTags(0);
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TreeBuilderState.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TreeBuilderState.java
new file mode 100644
index 000000000..c4e2d4afb
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/TreeBuilderState.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2009-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+/**
+ * Interface for exposing the state of the HTML5 tree builder so that the
+ * interface can be implemented by the tree builder itself and by snapshots.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public interface TreeBuilderState<T> {
+
+ /**
+ * Returns the stack.
+ *
+ * @return the stack
+ */
+ public StackNode<T>[] getStack();
+
+ /**
+ * Returns the listOfActiveFormattingElements.
+ *
+ * @return the listOfActiveFormattingElements
+ */
+ public StackNode<T>[] getListOfActiveFormattingElements();
+
+ /**
+ * Returns the stack of template insertion modes.
+ *
+ * @return the stack of template insertion modes
+ */
+ public int[] getTemplateModeStack();
+
+ /**
+ * Returns the formPointer.
+ *
+ * @return the formPointer
+ */
+ public T getFormPointer();
+
+ /**
+ * Returns the headPointer.
+ *
+ * @return the headPointer
+ */
+ public T getHeadPointer();
+
+ /**
+ * Returns the deepTreeSurrogateParent.
+ *
+ * @return the deepTreeSurrogateParent
+ */
+ public T getDeepTreeSurrogateParent();
+
+ /**
+ * Returns the mode.
+ *
+ * @return the mode
+ */
+ public int getMode();
+
+ /**
+ * Returns the originalMode.
+ *
+ * @return the originalMode
+ */
+ public int getOriginalMode();
+
+ /**
+ * Returns the framesetOk.
+ *
+ * @return the framesetOk
+ */
+ public boolean isFramesetOk();
+
+ /**
+ * Returns the needToDropLF.
+ *
+ * @return the needToDropLF
+ */
+ public boolean isNeedToDropLF();
+
+ /**
+ * Returns the quirks.
+ *
+ * @return the quirks
+ */
+ public boolean isQuirks();
+
+ /**
+ * Return the length of the stack.
+ * @return the length of the stack.
+ */
+ public int getStackLength();
+
+ /**
+ * Return the length of the list of active formatting elements.
+ * @return the length of the list of active formatting elements.
+ */
+ public int getListOfActiveFormattingElementsLength();
+
+ /**
+ * Return the length of the stack of template insertion modes.
+ *
+ * @return the length of the stack of template insertion modes.
+ */
+ int getTemplateModeStackLength();
+} \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/UTF16Buffer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/UTF16Buffer.java
new file mode 100644
index 000000000..35f1ac055
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/UTF16Buffer.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.impl;
+
+import nu.validator.htmlparser.annotation.NoLength;
+
+/**
+ * An UTF-16 buffer that knows the start and end indeces of its unconsumed
+ * content.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class UTF16Buffer {
+
+ /**
+ * The backing store of the buffer. May be larger than the logical content
+ * of this <code>UTF16Buffer</code>.
+ */
+ private final @NoLength char[] buffer;
+
+ /**
+ * The index of the first unconsumed character in the backing buffer.
+ */
+ private int start;
+
+ /**
+ * The index of the slot immediately after the last character in the backing
+ * buffer that is part of the logical content of this
+ * <code>UTF16Buffer</code>.
+ */
+ private int end;
+
+ //[NOCPP[
+
+ /**
+ * Constructor for wrapping an existing UTF-16 code unit array.
+ *
+ * @param buffer
+ * the backing buffer
+ * @param start
+ * the index of the first character to consume
+ * @param end
+ * the index immediately after the last character to consume
+ */
+ public UTF16Buffer(@NoLength char[] buffer, int start, int end) {
+ this.buffer = buffer;
+ this.start = start;
+ this.end = end;
+ }
+
+ // ]NOCPP]
+
+ /**
+ * Returns the start index.
+ *
+ * @return the start index
+ */
+ public int getStart() {
+ return start;
+ }
+
+ /**
+ * Sets the start index.
+ *
+ * @param start
+ * the start index
+ */
+ public void setStart(int start) {
+ this.start = start;
+ }
+
+ /**
+ * Returns the backing buffer.
+ *
+ * @return the backing buffer
+ */
+ public @NoLength char[] getBuffer() {
+ return buffer;
+ }
+
+ /**
+ * Returns the end index.
+ *
+ * @return the end index
+ */
+ public int getEnd() {
+ return end;
+ }
+
+ /**
+ * Checks if the buffer has data left.
+ *
+ * @return <code>true</code> if there's data left
+ */
+ public boolean hasMore() {
+ return start < end;
+ }
+
+ /**
+ * Returns <code>end - start</code>.
+ *
+ * @return <code>end - start</code>
+ */
+ public int getLength() {
+ return end - start;
+ }
+
+ /**
+ * Adjusts the start index to skip over the first character if it is a line
+ * feed and the previous character was a carriage return.
+ *
+ * @param lastWasCR
+ * whether the previous character was a carriage return
+ */
+ public void adjust(boolean lastWasCR) {
+ if (lastWasCR && buffer[start] == '\n') {
+ start++;
+ }
+ }
+
+ /**
+ * Sets the end index.
+ *
+ * @param end
+ * the end index
+ */
+ public void setEnd(int end) {
+ this.end = end;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/package.html b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/package.html
new file mode 100644
index 000000000..6d029a13e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/impl/package.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head><title>Package Overview</title>
+<!--
+ Copyright (c) 2007 Henri Sivonen
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+-->
+</head>
+<body bgcolor="white">
+<p>This package contains the bulk of parser internals. Only implementors of
+additional tree builders or token handlers should look here.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/BomSniffer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/BomSniffer.java
new file mode 100644
index 000000000..42d7a837f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/BomSniffer.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.io;
+
+import java.io.IOException;
+
+import nu.validator.htmlparser.common.ByteReadable;
+
+/**
+ * The BOM sniffing part of the HTML5 encoding sniffing algorithm.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class BomSniffer {
+
+ private final ByteReadable source;
+
+ /**
+ * @param source
+ */
+ public BomSniffer(final ByteReadable source) {
+ this.source = source;
+ }
+
+ Encoding sniff() throws IOException {
+ int b = source.readByte();
+ if (b == 0xEF) { // UTF-8
+ b = source.readByte();
+ if (b == 0xBB) {
+ b = source.readByte();
+ if (b == 0xBF) {
+ return Encoding.UTF8;
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ } else if (b == 0xFF) { // little-endian
+ b = source.readByte();
+ if (b == 0xFE) {
+ return Encoding.UTF16LE;
+ } else {
+ return null;
+ }
+ } else if (b == 0xFE) { // big-endian UTF-16
+ b = source.readByte();
+ if (b == 0xFF) {
+ return Encoding.UTF16BE;
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Confidence.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Confidence.java
new file mode 100644
index 000000000..1a2d49746
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Confidence.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.io;
+
+public enum Confidence {
+ TENTATIVE, CERTAIN
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Driver.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Driver.java
new file mode 100644
index 000000000..f0b0cc55d
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Driver.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2005, 2006, 2007 Henri Sivonen
+ * Copyright (c) 2007-2013 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.charset.UnsupportedCharsetException;
+
+import nu.validator.htmlparser.common.CharacterHandler;
+import nu.validator.htmlparser.common.EncodingDeclarationHandler;
+import nu.validator.htmlparser.common.Heuristics;
+import nu.validator.htmlparser.common.TransitionHandler;
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.extra.NormalizationChecker;
+import nu.validator.htmlparser.impl.ErrorReportingTokenizer;
+import nu.validator.htmlparser.impl.Tokenizer;
+import nu.validator.htmlparser.impl.UTF16Buffer;
+import nu.validator.htmlparser.rewindable.RewindableInputStream;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+public class Driver implements EncodingDeclarationHandler {
+
+ /**
+ * The input UTF-16 code unit stream. If a byte stream was given, this
+ * object is an instance of <code>HtmlInputStreamReader</code>.
+ */
+ private Reader reader;
+
+ /**
+ * The reference to the rewindable byte stream. <code>null</code> if
+ * prohibited or no longer needed.
+ */
+ private RewindableInputStream rewindableInputStream;
+
+ private boolean swallowBom;
+
+ private Encoding characterEncoding;
+
+ private boolean allowRewinding = true;
+
+ private Heuristics heuristics = Heuristics.NONE;
+
+ private final Tokenizer tokenizer;
+
+ private Confidence confidence;
+
+ /**
+ * Used for NFC checking if non-<code>null</code>, source code capture,
+ * etc.
+ */
+ private CharacterHandler[] characterHandlers = new CharacterHandler[0];
+
+ public Driver(Tokenizer tokenizer) {
+ this.tokenizer = tokenizer;
+ tokenizer.setEncodingDeclarationHandler(this);
+ }
+
+ /**
+ * Returns the allowRewinding.
+ *
+ * @return the allowRewinding
+ */
+ public boolean isAllowRewinding() {
+ return allowRewinding;
+ }
+
+ /**
+ * Sets the allowRewinding.
+ *
+ * @param allowRewinding
+ * the allowRewinding to set
+ */
+ public void setAllowRewinding(boolean allowRewinding) {
+ this.allowRewinding = allowRewinding;
+ }
+
+ /**
+ * Turns NFC checking on or off.
+ *
+ * @param enable
+ * <code>true</code> if checking on
+ */
+ public void setCheckingNormalization(boolean enable) {
+ if (enable) {
+ if (isCheckingNormalization()) {
+ return;
+ } else {
+ NormalizationChecker normalizationChecker = new NormalizationChecker(tokenizer);
+ normalizationChecker.setErrorHandler(tokenizer.getErrorHandler());
+
+ }
+ } else {
+ if (isCheckingNormalization()) {
+ CharacterHandler[] newHandlers = new CharacterHandler[characterHandlers.length - 1];
+ boolean skipped = false;
+ int j = 0;
+ for (int i = 0; i < characterHandlers.length; i++) {
+ CharacterHandler ch = characterHandlers[i];
+ if (!(!skipped && (ch instanceof NormalizationChecker))) {
+ newHandlers[j] = ch;
+ j++;
+ }
+ }
+ characterHandlers = newHandlers;
+ } else {
+ return;
+ }
+ }
+ }
+
+ public void addCharacterHandler(CharacterHandler characterHandler) {
+ if (characterHandler == null) {
+ throw new IllegalArgumentException("Null argument.");
+ }
+ CharacterHandler[] newHandlers = new CharacterHandler[characterHandlers.length + 1];
+ System.arraycopy(characterHandlers, 0, newHandlers, 0,
+ characterHandlers.length);
+ newHandlers[characterHandlers.length] = characterHandler;
+ characterHandlers = newHandlers;
+ }
+
+ /**
+ * Query if checking normalization.
+ *
+ * @return <code>true</code> if checking on
+ */
+ public boolean isCheckingNormalization() {
+ for (int i = 0; i < characterHandlers.length; i++) {
+ CharacterHandler ch = characterHandlers[i];
+ if (ch instanceof NormalizationChecker) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Runs the tokenization. This is the main entry point.
+ *
+ * @param is
+ * the input source
+ * @throws SAXException
+ * on fatal error (if configured to treat XML violations as
+ * fatal) or if the token handler threw
+ * @throws IOException
+ * if the stream threw
+ */
+ public void tokenize(InputSource is) throws SAXException, IOException {
+ if (is == null) {
+ throw new IllegalArgumentException("InputSource was null.");
+ }
+ tokenizer.start();
+ confidence = Confidence.TENTATIVE;
+ swallowBom = true;
+ rewindableInputStream = null;
+ tokenizer.initLocation(is.getPublicId(), is.getSystemId());
+ this.reader = is.getCharacterStream();
+ this.characterEncoding = encodingFromExternalDeclaration(is.getEncoding());
+ if (this.reader == null) {
+ InputStream inputStream = is.getByteStream();
+ if (inputStream == null) {
+ throw new SAXException("Both streams in InputSource were null.");
+ }
+ if (this.characterEncoding == null) {
+ if (allowRewinding) {
+ inputStream = rewindableInputStream = new RewindableInputStream(
+ inputStream);
+ }
+ this.reader = new HtmlInputStreamReader(inputStream,
+ tokenizer.getErrorHandler(), tokenizer, this, heuristics);
+ } else {
+ if (this.characterEncoding != Encoding.UTF8) {
+ warnWithoutLocation("Legacy encoding \u201C"
+ + this.characterEncoding.getCanonName()
+ + "\u201D used. Documents should use UTF-8.");
+ }
+ becomeConfident();
+ this.reader = new HtmlInputStreamReader(inputStream,
+ tokenizer.getErrorHandler(), tokenizer, this, this.characterEncoding);
+ }
+ } else {
+ becomeConfident();
+ }
+ Throwable t = null;
+ try {
+ for (;;) {
+ try {
+ for (int i = 0; i < characterHandlers.length; i++) {
+ CharacterHandler ch = characterHandlers[i];
+ ch.start();
+ }
+ runStates();
+ break;
+ } catch (ReparseException e) {
+ if (rewindableInputStream == null) {
+ tokenizer.fatal("Changing encoding at this point would need non-streamable behavior.");
+ } else {
+ rewindableInputStream.rewind();
+ becomeConfident();
+ this.reader = new HtmlInputStreamReader(
+ rewindableInputStream, tokenizer.getErrorHandler(), tokenizer,
+ this, this.characterEncoding);
+ }
+ continue;
+ }
+ }
+ } catch (Throwable tr) {
+ t = tr;
+ } finally {
+ try {
+ tokenizer.end();
+ characterEncoding = null;
+ for (int i = 0; i < characterHandlers.length; i++) {
+ CharacterHandler ch = characterHandlers[i];
+ ch.end();
+ }
+ reader.close();
+ reader = null;
+ rewindableInputStream = null;
+ } catch (Throwable tr) {
+ if (t == null) {
+ t = tr;
+ } // else drop the later throwable
+ }
+ if (t != null) {
+ if (t instanceof IOException) {
+ throw (IOException) t;
+ } else if (t instanceof SAXException) {
+ throw (SAXException) t;
+ } else if (t instanceof RuntimeException) {
+ throw (RuntimeException) t;
+ } else if (t instanceof Error) {
+ throw (Error) t;
+ } else {
+ // impossible
+ throw new RuntimeException(t);
+ }
+ }
+ }
+ }
+
+ void dontSwallowBom() {
+ swallowBom = false;
+ }
+
+ private void runStates() throws SAXException, IOException {
+ char[] buffer = new char[2048];
+ UTF16Buffer bufr = new UTF16Buffer(buffer, 0, 0);
+ boolean lastWasCR = false;
+ int len = -1;
+ if ((len = reader.read(buffer)) != -1) {
+ assert len > 0;
+ int streamOffset = 0;
+ int offset = 0;
+ int length = len;
+ if (swallowBom) {
+ if (buffer[0] == '\uFEFF') {
+ streamOffset = -1;
+ offset = 1;
+ length--;
+ }
+ }
+ if (length > 0) {
+ for (int i = 0; i < characterHandlers.length; i++) {
+ CharacterHandler ch = characterHandlers[i];
+ ch.characters(buffer, offset, length);
+ }
+ tokenizer.setTransitionBaseOffset(streamOffset);
+ bufr.setStart(offset);
+ bufr.setEnd(offset + length);
+ while (bufr.hasMore()) {
+ bufr.adjust(lastWasCR);
+ lastWasCR = false;
+ if (bufr.hasMore()) {
+ lastWasCR = tokenizer.tokenizeBuffer(bufr);
+ }
+ }
+ }
+ streamOffset = length;
+ while ((len = reader.read(buffer)) != -1) {
+ assert len > 0;
+ for (int i = 0; i < characterHandlers.length; i++) {
+ CharacterHandler ch = characterHandlers[i];
+ ch.characters(buffer, 0, len);
+ }
+ tokenizer.setTransitionBaseOffset(streamOffset);
+ bufr.setStart(0);
+ bufr.setEnd(len);
+ while (bufr.hasMore()) {
+ bufr.adjust(lastWasCR);
+ lastWasCR = false;
+ if (bufr.hasMore()) {
+ lastWasCR = tokenizer.tokenizeBuffer(bufr);
+ }
+ }
+ streamOffset += len;
+ }
+ }
+ tokenizer.eof();
+ }
+
+ public void setEncoding(Encoding encoding, Confidence confidence) {
+ this.characterEncoding = encoding;
+ if (confidence == Confidence.CERTAIN) {
+ becomeConfident();
+ }
+ }
+
+ public boolean internalEncodingDeclaration(String internalCharset)
+ throws SAXException {
+ try {
+ internalCharset = Encoding.toAsciiLowerCase(internalCharset);
+ Encoding cs;
+ if ("utf-16".equals(internalCharset)
+ || "utf-16be".equals(internalCharset)
+ || "utf-16le".equals(internalCharset)) {
+ tokenizer.errTreeBuilder("Internal encoding declaration specified \u201C"
+ + internalCharset
+ + "\u201D which is not an ASCII superset. Continuing as if the encoding had been \u201Cutf-8\u201D.");
+ cs = Encoding.UTF8;
+ internalCharset = "utf-8";
+ } else {
+ cs = Encoding.forName(internalCharset);
+ }
+ Encoding actual = cs.getActualHtmlEncoding();
+ if (actual == null) {
+ actual = cs;
+ }
+ if (!actual.isAsciiSuperset()) {
+ tokenizer.errTreeBuilder("Internal encoding declaration specified \u201C"
+ + internalCharset
+ + "\u201D which is not an ASCII superset. Not changing the encoding.");
+ return false;
+ }
+ if (characterEncoding == null) {
+ // Reader case
+ return true;
+ }
+ if (characterEncoding == actual) {
+ becomeConfident();
+ return true;
+ }
+ if (confidence == Confidence.CERTAIN && actual != characterEncoding) {
+ tokenizer.errTreeBuilder("Internal encoding declaration \u201C"
+ + internalCharset
+ + "\u201D disagrees with the actual encoding of the document (\u201C"
+ + characterEncoding.getCanonName() + "\u201D).");
+ } else {
+ Encoding newEnc = whineAboutEncodingAndReturnActual(
+ internalCharset, cs);
+ tokenizer.errTreeBuilder("Changing character encoding \u201C"
+ + internalCharset + "\u201D and reparsing.");
+ characterEncoding = newEnc;
+ throw new ReparseException();
+ }
+ return true;
+ } catch (UnsupportedCharsetException e) {
+ tokenizer.errTreeBuilder("Internal encoding declaration named an unsupported chararacter encoding \u201C"
+ + internalCharset + "\u201D.");
+ return false;
+ }
+ }
+
+ /**
+ *
+ */
+ private void becomeConfident() {
+ if (rewindableInputStream != null) {
+ rewindableInputStream.willNotRewind();
+ }
+ confidence = Confidence.CERTAIN;
+ tokenizer.becomeConfident();
+ }
+
+ /**
+ * Sets the encoding sniffing heuristics.
+ *
+ * @param heuristics
+ * the heuristics to set
+ */
+ public void setHeuristics(Heuristics heuristics) {
+ this.heuristics = heuristics;
+ }
+
+ /**
+ * Reports a warning without line/col
+ *
+ * @param message
+ * the message
+ * @throws SAXException
+ */
+ protected void warnWithoutLocation(String message) throws SAXException {
+ ErrorHandler errorHandler = tokenizer.getErrorHandler();
+ if (errorHandler == null) {
+ return;
+ }
+ SAXParseException spe = new SAXParseException(message, null,
+ tokenizer.getSystemId(), -1, -1);
+ errorHandler.warning(spe);
+ }
+
+ /**
+ * Initializes a decoder from external decl.
+ */
+ protected Encoding encodingFromExternalDeclaration(String encoding)
+ throws SAXException {
+ if (encoding == null) {
+ return null;
+ }
+ encoding = Encoding.toAsciiLowerCase(encoding);
+ try {
+ Encoding cs = Encoding.forName(encoding);
+ if ("utf-16".equals(cs.getCanonName())
+ || "utf-32".equals(cs.getCanonName())) {
+ swallowBom = false;
+ }
+ return whineAboutEncodingAndReturnActual(encoding, cs);
+ } catch (UnsupportedCharsetException e) {
+ tokenizer.err("Unsupported character encoding name: \u201C" + encoding
+ + "\u201D. Will sniff.");
+ swallowBom = true;
+ }
+ return null; // keep the compiler happy
+ }
+
+ /**
+ * @param encoding
+ * @param cs
+ * @return
+ * @throws SAXException
+ */
+ protected Encoding whineAboutEncodingAndReturnActual(String encoding,
+ Encoding cs) throws SAXException {
+ String canonName = cs.getCanonName();
+ if (!cs.isRegistered()) {
+ if (encoding.startsWith("x-")) {
+ tokenizer.err("The encoding \u201C"
+ + encoding
+ + "\u201D is not an IANA-registered encoding. (Charmod C022)");
+ } else {
+ tokenizer.err("The encoding \u201C"
+ + encoding
+ + "\u201D is not an IANA-registered encoding and did not use the \u201Cx-\u201D prefix. (Charmod C023)");
+ }
+ } else if (!canonName.equals(encoding)) {
+ tokenizer.err("The encoding \u201C"
+ + encoding
+ + "\u201D is not the preferred name of the character encoding in use. The preferred name is \u201C"
+ + canonName + "\u201D. (Charmod C024)");
+ }
+ if (cs.isShouldNot()) {
+ tokenizer.warn("Authors should not use the character encoding \u201C"
+ + encoding
+ + "\u201D. It is recommended to use \u201CUTF-8\u201D.");
+ } else if (cs.isLikelyEbcdic()) {
+ tokenizer.warn("Authors should not use EBCDIC-based encodings. It is recommended to use \u201CUTF-8\u201D.");
+ } else if (cs.isObscure()) {
+ tokenizer.warn("The character encoding \u201C"
+ + encoding
+ + "\u201D is not widely supported. Better interoperability may be achieved by using \u201CUTF-8\u201D.");
+ }
+ Encoding actual = cs.getActualHtmlEncoding();
+ if (actual == null) {
+ return cs;
+ } else {
+ tokenizer.warn("Using \u201C" + actual.getCanonName()
+ + "\u201D instead of the declared encoding \u201C"
+ + encoding + "\u201D.");
+ return actual;
+ }
+ }
+
+ private class ReparseException extends SAXException {
+
+ }
+
+ void notifyAboutMetaBoundary() {
+ tokenizer.notifyAboutMetaBoundary();
+ }
+
+ /**
+ * @param commentPolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setCommentPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setCommentPolicy(XmlViolationPolicy commentPolicy) {
+ tokenizer.setCommentPolicy(commentPolicy);
+ }
+
+ /**
+ * @param contentNonXmlCharPolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setContentNonXmlCharPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setContentNonXmlCharPolicy(
+ XmlViolationPolicy contentNonXmlCharPolicy) {
+ tokenizer.setContentNonXmlCharPolicy(contentNonXmlCharPolicy);
+ }
+
+ /**
+ * @param contentSpacePolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setContentSpacePolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setContentSpacePolicy(XmlViolationPolicy contentSpacePolicy) {
+ tokenizer.setContentSpacePolicy(contentSpacePolicy);
+ }
+
+ /**
+ * @param eh
+ * @see nu.validator.htmlparser.impl.Tokenizer#setErrorHandler(org.xml.sax.ErrorHandler)
+ */
+ public void setErrorHandler(ErrorHandler eh) {
+ tokenizer.setErrorHandler(eh);
+ for (int i = 0; i < characterHandlers.length; i++) {
+ CharacterHandler ch = characterHandlers[i];
+ if (ch instanceof NormalizationChecker) {
+ NormalizationChecker nc = (NormalizationChecker) ch;
+ nc.setErrorHandler(eh);
+ }
+ }
+ }
+
+ public void setTransitionHandler(TransitionHandler transitionHandler) {
+ if (tokenizer instanceof ErrorReportingTokenizer) {
+ ErrorReportingTokenizer ert = (ErrorReportingTokenizer) tokenizer;
+ ert.setTransitionHandler(transitionHandler);
+ } else if (transitionHandler != null) {
+ throw new IllegalStateException("Attempt to set a transition handler on a plain tokenizer.");
+ }
+ }
+
+ /**
+ * @param html4ModeCompatibleWithXhtml1Schemata
+ * @see nu.validator.htmlparser.impl.Tokenizer#setHtml4ModeCompatibleWithXhtml1Schemata(boolean)
+ */
+ public void setHtml4ModeCompatibleWithXhtml1Schemata(
+ boolean html4ModeCompatibleWithXhtml1Schemata) {
+ tokenizer.setHtml4ModeCompatibleWithXhtml1Schemata(html4ModeCompatibleWithXhtml1Schemata);
+ }
+
+ /**
+ * @param mappingLangToXmlLang
+ * @see nu.validator.htmlparser.impl.Tokenizer#setMappingLangToXmlLang(boolean)
+ */
+ public void setMappingLangToXmlLang(boolean mappingLangToXmlLang) {
+ tokenizer.setMappingLangToXmlLang(mappingLangToXmlLang);
+ }
+
+ /**
+ * @param namePolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setNamePolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setNamePolicy(XmlViolationPolicy namePolicy) {
+ tokenizer.setNamePolicy(namePolicy);
+ }
+
+ /**
+ * @param xmlnsPolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setXmlnsPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setXmlnsPolicy(XmlViolationPolicy xmlnsPolicy) {
+ tokenizer.setXmlnsPolicy(xmlnsPolicy);
+ }
+
+ public String getCharacterEncoding() throws SAXException {
+ return characterEncoding.getCanonName();
+ }
+
+ public Locator getDocumentLocator() {
+ return tokenizer;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Encoding.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Encoding.java
new file mode 100644
index 000000000..3bbc606fa
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/Encoding.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2006 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderMalfunctionError;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+
+public class Encoding {
+
+ public static final Encoding UTF8;
+
+ public static final Encoding UTF16;
+
+ public static final Encoding UTF16LE;
+
+ public static final Encoding UTF16BE;
+
+ public static final Encoding WINDOWS1252;
+
+ private static String[] SHOULD_NOT = { "jisx02121990", "xjis0208" };
+
+ private static String[] BANNED = { "bocu1", "cesu8", "compoundtext",
+ "iscii91", "macarabic", "maccentraleurroman", "maccroatian",
+ "maccyrillic", "macdevanagari", "macfarsi", "macgreek",
+ "macgujarati", "macgurmukhi", "machebrew", "macicelandic",
+ "macroman", "macromanian", "macthai", "macturkish", "macukranian",
+ "scsu", "utf32", "utf32be", "utf32le", "utf7", "ximapmailboxname",
+ "xjisautodetect", "xutf16bebom", "xutf16lebom", "xutf32bebom",
+ "xutf32lebom", "xutf16oppositeendian", "xutf16platformendian",
+ "xutf32oppositeendian", "xutf32platformendian" };
+
+ private static String[] NOT_OBSCURE = { "big5", "big5hkscs", "eucjp",
+ "euckr", "gb18030", "gbk", "iso2022jp", "iso2022kr", "iso88591",
+ "iso885913", "iso885915", "iso88592", "iso88593", "iso88594",
+ "iso88595", "iso88596", "iso88597", "iso88598", "iso88599",
+ "koi8r", "shiftjis", "tis620", "usascii", "utf16", "utf16be",
+ "utf16le", "utf8", "windows1250", "windows1251", "windows1252",
+ "windows1253", "windows1254", "windows1255", "windows1256",
+ "windows1257", "windows1258" };
+
+ private static Map<String, Encoding> encodingByCookedName = new HashMap<String, Encoding>();
+
+ private final String canonName;
+
+ private final Charset charset;
+
+ private final boolean asciiSuperset;
+
+ private final boolean obscure;
+
+ private final boolean shouldNot;
+
+ private final boolean likelyEbcdic;
+
+ private Encoding actualHtmlEncoding = null;
+
+ static {
+ byte[] testBuf = new byte[0x7F];
+ for (int i = 0; i < 0x7F; i++) {
+ if (isAsciiSupersetnessSensitive(i)) {
+ testBuf[i] = (byte) i;
+ } else {
+ testBuf[i] = (byte) 0x20;
+ }
+ }
+
+ Set<Encoding> encodings = new HashSet<Encoding>();
+
+ SortedMap<String, Charset> charsets = Charset.availableCharsets();
+ for (Map.Entry<String, Charset> entry : charsets.entrySet()) {
+ Charset cs = entry.getValue();
+ String name = toNameKey(cs.name());
+ String canonName = toAsciiLowerCase(cs.name());
+ if (!isBanned(name)) {
+ name = name.intern();
+ boolean asciiSuperset = asciiMapsToBasicLatin(testBuf, cs);
+ Encoding enc = new Encoding(canonName.intern(), cs,
+ asciiSuperset, isObscure(name), isShouldNot(name),
+ isLikelyEbcdic(name, asciiSuperset));
+ encodings.add(enc);
+ Set<String> aliases = cs.aliases();
+ for (String alias : aliases) {
+ encodingByCookedName.put(toNameKey(alias).intern(), enc);
+ }
+ }
+ }
+ // Overwrite possible overlapping aliases with the real things--just in
+ // case
+ for (Encoding encoding : encodings) {
+ encodingByCookedName.put(toNameKey(encoding.getCanonName()),
+ encoding);
+ }
+ UTF8 = forName("utf-8");
+ UTF16 = forName("utf-16");
+ UTF16BE = forName("utf-16be");
+ UTF16LE = forName("utf-16le");
+ WINDOWS1252 = forName("windows-1252");
+ try {
+ forName("iso-8859-1").actualHtmlEncoding = forName("windows-1252");
+ } catch (UnsupportedCharsetException e) {
+ }
+ try {
+ forName("iso-8859-9").actualHtmlEncoding = forName("windows-1254");
+ } catch (UnsupportedCharsetException e) {
+ }
+ try {
+ forName("iso-8859-11").actualHtmlEncoding = forName("windows-874");
+ } catch (UnsupportedCharsetException e) {
+ }
+ try {
+ forName("x-iso-8859-11").actualHtmlEncoding = forName("windows-874");
+ } catch (UnsupportedCharsetException e) {
+ }
+ try {
+ forName("tis-620").actualHtmlEncoding = forName("windows-874");
+ } catch (UnsupportedCharsetException e) {
+ }
+ try {
+ forName("gb_2312-80").actualHtmlEncoding = forName("gbk");
+ } catch (UnsupportedCharsetException e) {
+ }
+ try {
+ forName("gb2312").actualHtmlEncoding = forName("gbk");
+ } catch (UnsupportedCharsetException e) {
+ }
+ try {
+ encodingByCookedName.put("x-x-big5", forName("big5"));
+ } catch (UnsupportedCharsetException e) {
+ }
+ try {
+ encodingByCookedName.put("euc-kr", forName("windows-949"));
+ } catch (UnsupportedCharsetException e) {
+ }
+ try {
+ encodingByCookedName.put("ks_c_5601-1987", forName("windows-949"));
+ } catch (UnsupportedCharsetException e) {
+ }
+ }
+
+ private static boolean isAsciiSupersetnessSensitive(int c) {
+ return (c >= 0x09 && c <= 0x0D) || (c >= 0x20 && c <= 0x22)
+ || (c >= 0x26 && c <= 0x27) || (c >= 0x2C && c <= 0x3F)
+ || (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);
+ }
+
+ private static boolean isObscure(String lowerCasePreferredIanaName) {
+ return !(Arrays.binarySearch(NOT_OBSCURE, lowerCasePreferredIanaName) > -1);
+ }
+
+ private static boolean isBanned(String lowerCasePreferredIanaName) {
+ if (lowerCasePreferredIanaName.startsWith("xibm")) {
+ return true;
+ }
+ return (Arrays.binarySearch(BANNED, lowerCasePreferredIanaName) > -1);
+ }
+
+ private static boolean isShouldNot(String lowerCasePreferredIanaName) {
+ return (Arrays.binarySearch(SHOULD_NOT, lowerCasePreferredIanaName) > -1);
+ }
+
+ /**
+ * @param testBuf
+ * @param cs
+ */
+ private static boolean asciiMapsToBasicLatin(byte[] testBuf, Charset cs) {
+ CharsetDecoder dec = cs.newDecoder();
+ dec.onMalformedInput(CodingErrorAction.REPORT);
+ dec.onUnmappableCharacter(CodingErrorAction.REPORT);
+ Reader r = new InputStreamReader(new ByteArrayInputStream(testBuf), dec);
+ try {
+ for (int i = 0; i < 0x7F; i++) {
+ if (isAsciiSupersetnessSensitive(i)) {
+ if (r.read() != i) {
+ return false;
+ }
+ } else {
+ if (r.read() != 0x20) {
+ return false;
+ }
+ }
+ }
+ } catch (IOException e) {
+ return false;
+ } catch (Exception e) {
+ return false;
+ } catch (CoderMalfunctionError e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static boolean isLikelyEbcdic(String canonName,
+ boolean asciiSuperset) {
+ if (!asciiSuperset) {
+ return (canonName.startsWith("cp") || canonName.startsWith("ibm") || canonName.startsWith("xibm"));
+ } else {
+ return false;
+ }
+ }
+
+ public static Encoding forName(String name) {
+ Encoding rv = encodingByCookedName.get(toNameKey(name));
+ if (rv == null) {
+ throw new UnsupportedCharsetException(name);
+ } else {
+ return rv;
+ }
+ }
+
+ public static String toNameKey(String str) {
+ if (str == null) {
+ return null;
+ }
+ int j = 0;
+ char[] buf = new char[str.length()];
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ if (!((c >= '\t' && c <= '\r') || (c >= '\u0020' && c <= '\u002F')
+ || (c >= '\u003A' && c <= '\u0040')
+ || (c >= '\u005B' && c <= '\u0060') || (c >= '\u007B' && c <= '\u007E'))) {
+ buf[j] = c;
+ j++;
+ }
+ }
+ return new String(buf, 0, j);
+ }
+
+ public static String toAsciiLowerCase(String str) {
+ if (str == null) {
+ return null;
+ }
+ char[] buf = new char[str.length()];
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ buf[i] = c;
+ }
+ return new String(buf);
+ }
+
+ /**
+ * @param canonName
+ * @param charset
+ * @param asciiSuperset
+ * @param obscure
+ * @param shouldNot
+ * @param likelyEbcdic
+ */
+ private Encoding(final String canonName, final Charset charset,
+ final boolean asciiSuperset, final boolean obscure,
+ final boolean shouldNot, final boolean likelyEbcdic) {
+ this.canonName = canonName;
+ this.charset = charset;
+ this.asciiSuperset = asciiSuperset;
+ this.obscure = obscure;
+ this.shouldNot = shouldNot;
+ this.likelyEbcdic = likelyEbcdic;
+ }
+
+ /**
+ * Returns the asciiSuperset.
+ *
+ * @return the asciiSuperset
+ */
+ public boolean isAsciiSuperset() {
+ return asciiSuperset;
+ }
+
+ /**
+ * Returns the canonName.
+ *
+ * @return the canonName
+ */
+ public String getCanonName() {
+ return canonName;
+ }
+
+ /**
+ * Returns the likelyEbcdic.
+ *
+ * @return the likelyEbcdic
+ */
+ public boolean isLikelyEbcdic() {
+ return likelyEbcdic;
+ }
+
+ /**
+ * Returns the obscure.
+ *
+ * @return the obscure
+ */
+ public boolean isObscure() {
+ return obscure;
+ }
+
+ /**
+ * Returns the shouldNot.
+ *
+ * @return the shouldNot
+ */
+ public boolean isShouldNot() {
+ return shouldNot;
+ }
+
+ public boolean isRegistered() {
+ return !canonName.startsWith("x-");
+ }
+
+ /**
+ * @return
+ * @see java.nio.charset.Charset#canEncode()
+ */
+ public boolean canEncode() {
+ return charset.canEncode();
+ }
+
+ /**
+ * @return
+ * @see java.nio.charset.Charset#newDecoder()
+ */
+ public CharsetDecoder newDecoder() {
+ return charset.newDecoder();
+ }
+
+ /**
+ * @return
+ * @see java.nio.charset.Charset#newEncoder()
+ */
+ public CharsetEncoder newEncoder() {
+ return charset.newEncoder();
+ }
+
+ /**
+ * Returns the actualHtmlEncoding.
+ *
+ * @return the actualHtmlEncoding
+ */
+ public Encoding getActualHtmlEncoding() {
+ return actualHtmlEncoding;
+ }
+
+ public static void main(String[] args) {
+ for (Map.Entry<String, Encoding> entry : encodingByCookedName.entrySet()) {
+ String name = entry.getKey();
+ Encoding enc = entry.getValue();
+ System.out.printf(
+ "%21s: canon %21s, obs %5s, reg %5s, asc %5s, ebc %5s\n",
+ name, enc.getCanonName(), enc.isObscure(),
+ enc.isRegistered(), enc.isAsciiSuperset(),
+ enc.isLikelyEbcdic());
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/HtmlInputStreamReader.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/HtmlInputStreamReader.java
new file mode 100644
index 000000000..413f0d9e9
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/HtmlInputStreamReader.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2013 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+import nu.validator.htmlparser.common.ByteReadable;
+import nu.validator.htmlparser.common.Heuristics;
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.extra.ChardetSniffer;
+import nu.validator.htmlparser.extra.IcuDetectorSniffer;
+import nu.validator.htmlparser.impl.Tokenizer;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Be very careful with this class. It is not a general-purpose subclass of of
+ * <code>Reader</code>. Instead, it is the minimal implementation that does
+ * what <code>Tokenizer</code> needs while being an instance of
+ * <code>Reader</code>.
+ *
+ * The only reason why this is a public class is that it needs to be visible to
+ * test code in another package.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class HtmlInputStreamReader extends Reader implements
+ ByteReadable, Locator {
+
+ private static final int SNIFFING_LIMIT = 1024;
+
+ private final InputStream inputStream;
+
+ private final ErrorHandler errorHandler;
+
+ private final Tokenizer tokenizer;
+
+ private final Driver driver;
+
+ private CharsetDecoder decoder = null;
+
+ private boolean sniffing = true;
+
+ private int limit = 0;
+
+ private int position = 0;
+
+ private int bytesRead = 0;
+
+ private boolean eofSeen = false;
+
+ private boolean shouldReadBytes = false;
+
+ private boolean charsetBoundaryPassed = false;
+
+ private final byte[] byteArray = new byte[4096]; // Length must be >=
+
+ // SNIFFING_LIMIT
+
+ private final ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
+
+ private boolean needToNotifyTokenizer = false;
+
+ private boolean flushing = false;
+
+ private int line = -1;
+
+ private int col = -1;
+
+ private int lineColPos;
+
+ private boolean hasPendingReplacementCharacter = false;
+
+ private boolean nextCharOnNewLine;
+
+ private boolean prevWasCR;
+
+ /**
+ * @param inputStream
+ * @param errorHandler
+ * @param locator
+ * @throws IOException
+ * @throws SAXException
+ */
+ public HtmlInputStreamReader(InputStream inputStream,
+ ErrorHandler errorHandler, Tokenizer tokenizer, Driver driver,
+ Heuristics heuristics) throws SAXException, IOException {
+ this.inputStream = inputStream;
+ this.errorHandler = errorHandler;
+ this.tokenizer = tokenizer;
+ this.driver = driver;
+ this.sniffing = true;
+ Encoding encoding = (new BomSniffer(this)).sniff();
+ if (encoding == null) {
+ position = 0;
+ encoding = (new MetaSniffer(errorHandler, this)).sniff(this);
+ boolean declared = true;
+ if (encoding == null) {
+ declared = false;
+ } else if (encoding != Encoding.UTF8) {
+ warn("Legacy encoding \u201C"
+ + encoding.getCanonName()
+ + "\u201D used. Documents should use UTF-8.");
+ }
+ if (encoding == null
+ && (heuristics == Heuristics.CHARDET || heuristics == Heuristics.ALL)) {
+ encoding = (new ChardetSniffer(byteArray, limit)).sniff();
+ }
+ if (encoding == null
+ && (heuristics == Heuristics.ICU || heuristics == Heuristics.ALL)) {
+ position = 0;
+ encoding = (new IcuDetectorSniffer(this)).sniff();
+ }
+ sniffing = false;
+ if (encoding == null) {
+ encoding = Encoding.WINDOWS1252;
+ }
+ if (!declared) {
+ err("The character encoding was not declared. Proceeding using \u201C" + encoding.getCanonName() + "\u201D.");
+ }
+ if (driver != null) {
+ driver.setEncoding(encoding, Confidence.TENTATIVE);
+ }
+ } else {
+ if (encoding == Encoding.UTF8) {
+ if (driver != null) {
+ driver.setEncoding(Encoding.UTF8, Confidence.CERTAIN);
+ }
+ } else {
+ warn("Legacy encoding \u201C"
+ + encoding.getCanonName()
+ + "\u201D used. Documents should use UTF-8.");
+ if (driver != null) {
+ driver.setEncoding(Encoding.UTF16, Confidence.CERTAIN);
+ }
+ }
+ }
+ this.decoder = encoding.newDecoder();
+ sniffing = false;
+ position = 0;
+ bytesRead = 0;
+ byteBuffer.position(position);
+ byteBuffer.limit(limit);
+ initDecoder();
+ }
+
+ /**
+ *
+ */
+ private void initDecoder() {
+ this.decoder.onMalformedInput(CodingErrorAction.REPORT);
+ this.decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
+ }
+
+ public HtmlInputStreamReader(InputStream inputStream,
+ ErrorHandler errorHandler, Tokenizer tokenizer, Driver driver,
+ Encoding encoding) throws SAXException, IOException {
+ this.inputStream = inputStream;
+ this.errorHandler = errorHandler;
+ this.tokenizer = tokenizer;
+ this.driver = driver;
+ this.decoder = encoding.newDecoder();
+ this.sniffing = false;
+ position = 0;
+ bytesRead = 0;
+ byteBuffer.position(0);
+ byteBuffer.limit(0);
+ shouldReadBytes = true;
+ initDecoder();
+ }
+
+ @Override public void close() throws IOException {
+ inputStream.close();
+ }
+
+ @Override public int read(char[] charArray) throws IOException {
+ lineColPos = 0;
+ assert !sniffing;
+ assert charArray.length >= 2;
+ if (needToNotifyTokenizer) {
+ if (driver != null) {
+ driver.notifyAboutMetaBoundary();
+ }
+ needToNotifyTokenizer = false;
+ }
+ CharBuffer charBuffer = CharBuffer.wrap(charArray);
+ charBuffer.limit(charArray.length);
+ charBuffer.position(0);
+ if (flushing) {
+ decoder.flush(charBuffer);
+ // return -1 if zero
+ int cPos = charBuffer.position();
+ return cPos == 0 ? -1 : cPos;
+ }
+ if (hasPendingReplacementCharacter) {
+ charBuffer.put('\uFFFD');
+ hasPendingReplacementCharacter = false;
+ }
+ for (;;) {
+ if (shouldReadBytes) {
+ int oldLimit = byteBuffer.limit();
+ int readLen;
+ if (charsetBoundaryPassed) {
+ readLen = byteArray.length - oldLimit;
+ } else {
+ readLen = SNIFFING_LIMIT - oldLimit;
+ }
+ int num = inputStream.read(byteArray, oldLimit, readLen);
+ if (num == -1) {
+ eofSeen = true;
+ inputStream.close();
+ } else {
+ byteBuffer.position(0);
+ byteBuffer.limit(oldLimit + num);
+ }
+ shouldReadBytes = false;
+ }
+ boolean finalDecode = false;
+ for (;;) {
+ int oldBytePos = byteBuffer.position();
+ CoderResult cr = decoder.decode(byteBuffer, charBuffer,
+ finalDecode);
+ bytesRead += byteBuffer.position() - oldBytePos;
+ if (cr == CoderResult.OVERFLOW) {
+ // Decoder will remember surrogates
+ return charBuffer.position();
+ } else if (cr == CoderResult.UNDERFLOW) {
+ int remaining = byteBuffer.remaining();
+ if (!charsetBoundaryPassed) {
+ if (bytesRead + remaining >= SNIFFING_LIMIT) {
+ needToNotifyTokenizer = true;
+ charsetBoundaryPassed = true;
+ }
+ }
+
+ // XXX what happens if the entire byte buffer consists of
+ // a pathologically long malformed sequence?
+
+ // If the buffer was not fully consumed, there may be an
+ // incomplete byte sequence that needs to seed the next
+ // buffer.
+ if (remaining > 0) {
+ System.arraycopy(byteArray, byteBuffer.position(),
+ byteArray, 0, remaining);
+ }
+ byteBuffer.position(0);
+ byteBuffer.limit(remaining);
+ if (flushing) {
+ // The final decode was successful. Not sure if this
+ // ever happens.
+ // Let's get out in any case.
+ int cPos = charBuffer.position();
+ return cPos == 0 ? -1 : cPos;
+ } else if (eofSeen) {
+ // If there's something left, it isn't something that
+ // would be
+ // consumed in the middle of the stream. Rerun the loop
+ // once
+ // in the final mode.
+ shouldReadBytes = false;
+ finalDecode = true;
+ flushing = true;
+ continue;
+ } else {
+ // The usual stuff. Want more bytes next time.
+ shouldReadBytes = true;
+ int cPos = charBuffer.position();
+ if (cPos == 0) {
+ // No output. Read more bytes right away
+ break;
+ }
+ return cPos;
+ }
+ } else {
+ // The result is in error. No need to test.
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < cr.length(); i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append('\u201C');
+ sb.append(Integer.toHexString(byteBuffer.get() & 0xFF));
+ bytesRead++;
+ sb.append('\u201D');
+ }
+ if (charBuffer.hasRemaining()) {
+ charBuffer.put('\uFFFD');
+ } else {
+ hasPendingReplacementCharacter = true;
+ }
+ calculateLineAndCol(charBuffer);
+ if (cr.isMalformed()) {
+ err("Malformed byte sequence: " + sb + ".");
+ } else if (cr.isUnmappable()) {
+ err("Unmappable byte sequence: " + sb + ".");
+ } else {
+ throw new RuntimeException(
+ "CoderResult was none of overflow, underflow, malformed or unmappable.");
+ }
+ if (finalDecode) {
+ // These were the last bytes of input. Return without
+ // relooping.
+ // return -1 if zero
+ int cPos = charBuffer.position();
+ return cPos == 0 ? -1 : cPos;
+ }
+ }
+ }
+ }
+ }
+
+ private void calculateLineAndCol(CharBuffer charBuffer) {
+ if (tokenizer != null) {
+ if (lineColPos == 0) {
+ line = tokenizer.getLine();
+ col = tokenizer.getCol();
+ nextCharOnNewLine = tokenizer.isNextCharOnNewLine();
+ prevWasCR = tokenizer.isPrevCR();
+ }
+
+ char[] charArray = charBuffer.array();
+ int i = lineColPos;
+ while (i < charBuffer.position()) {
+ char c;
+ if (nextCharOnNewLine) {
+ line++;
+ col = 1;
+ nextCharOnNewLine = false;
+ } else {
+ col++;
+ }
+
+ c = charArray[i];
+ switch (c) {
+ case '\r':
+ nextCharOnNewLine = true;
+ prevWasCR = true;
+ break;
+ case '\n':
+ if (prevWasCR) {
+ col--;
+ } else {
+ nextCharOnNewLine = true;
+ }
+ break;
+ }
+ i++;
+ }
+ lineColPos = i;
+ }
+ }
+
+ public int readByte() throws IOException {
+ if (!sniffing) {
+ throw new IllegalStateException(
+ "readByte() called when not in the sniffing state.");
+ }
+ if (position == SNIFFING_LIMIT) {
+ return -1;
+ } else if (position < limit) {
+ return byteArray[position++] & 0xFF;
+ } else {
+ int num = inputStream.read(byteArray, limit, SNIFFING_LIMIT - limit);
+ if (num == -1) {
+ return -1;
+ } else {
+ limit += num;
+ return byteArray[position++] & 0xFF;
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ CharsetDecoder dec = Charset.forName("UTF-8").newDecoder();
+ dec.onMalformedInput(CodingErrorAction.REPORT);
+ dec.onUnmappableCharacter(CodingErrorAction.REPORT);
+ byte[] bytes = { (byte) 0xF0, (byte) 0x9D, (byte) 0x80, (byte) 0x80 };
+ byte[] bytes2 = { (byte) 0xB8, (byte) 0x80, 0x63, 0x64, 0x65 };
+ ByteBuffer byteBuf = ByteBuffer.wrap(bytes);
+ ByteBuffer byteBuf2 = ByteBuffer.wrap(bytes2);
+ char[] chars = new char[1];
+ CharBuffer charBuf = CharBuffer.wrap(chars);
+
+ CoderResult cr = dec.decode(byteBuf, charBuf, false);
+ System.out.println(cr);
+ System.out.println(byteBuf);
+ // byteBuf.get();
+ cr = dec.decode(byteBuf2, charBuf, false);
+ System.out.println(cr);
+ System.out.println(byteBuf2);
+
+ }
+
+ public int getColumnNumber() {
+ if (tokenizer != null) {
+ return col;
+ }
+ return -1;
+ }
+
+ public int getLineNumber() {
+ if (tokenizer != null) {
+ return line;
+ }
+ return -1;
+ }
+
+ public String getPublicId() {
+ if (tokenizer != null) {
+ return tokenizer.getPublicId();
+ }
+ return null;
+ }
+
+ public String getSystemId() {
+ if (tokenizer != null) {
+ return tokenizer.getSystemId();
+ }
+ return null;
+ }
+
+ /**
+ * @param string
+ * @throws SAXException
+ */
+ private void err(String message) throws IOException {
+ // TODO remove wrapping when changing read() to take a CharBuffer
+ try {
+ if (errorHandler != null) {
+ SAXParseException spe = new SAXParseException(message, this);
+ errorHandler.error(spe);
+ }
+ } catch (SAXException e) {
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ }
+ }
+
+ private void warn(String message) throws IOException {
+ // TODO remove wrapping when changing read() to take a CharBuffer
+ try {
+ if (errorHandler != null) {
+ SAXParseException spe = new SAXParseException(message, this);
+ errorHandler.warning(spe);
+ }
+ } catch (SAXException e) {
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ }
+ }
+
+ public Charset getCharset() {
+ return decoder.charset();
+ }
+
+ /**
+ * @see java.io.Reader#read()
+ */
+ @Override public int read() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @see java.io.Reader#read(char[], int, int)
+ */
+ @Override public int read(char[] cbuf, int off, int len) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @see java.io.Reader#read(java.nio.CharBuffer)
+ */
+ @Override public int read(CharBuffer target) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ public void switchEncoding(Encoding newEnc) {
+ this.decoder = newEnc.newDecoder();
+ initDecoder();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/MetaSniffer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/MetaSniffer.java
new file mode 100644
index 000000000..baa04e44f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/io/MetaSniffer.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.io;
+
+import java.io.IOException;
+import java.nio.charset.UnsupportedCharsetException;
+
+import nu.validator.htmlparser.common.ByteReadable;
+import nu.validator.htmlparser.impl.MetaScanner;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+public class MetaSniffer extends MetaScanner implements Locator {
+
+ private Encoding characterEncoding = null;
+
+ private final ErrorHandler errorHandler;
+
+ private final Locator locator;
+
+ private int line = 1;
+
+ private int col = 0;
+
+ private boolean prevWasCR = false;
+
+ public MetaSniffer(ErrorHandler eh, Locator locator) {
+ this.errorHandler = eh;
+ this.locator = locator;
+ this.characterEncoding = null;
+ }
+
+ /**
+ * -1 means end.
+ * @return
+ * @throws IOException
+ */
+ protected int read() throws IOException {
+ int b = readable.readByte();
+ // [NOCPP[
+ switch (b) {
+ case '\n':
+ if (!prevWasCR) {
+ line++;
+ col = 0;
+ }
+ prevWasCR = false;
+ break;
+ case '\r':
+ line++;
+ col = 0;
+ prevWasCR = true;
+ break;
+ default:
+ col++;
+ prevWasCR = false;
+ break;
+ }
+ // ]NOCPP]
+ return b;
+ }
+
+ /**
+ * Main loop.
+ *
+ * @return
+ *
+ * @throws SAXException
+ * @throws IOException
+ * @throws
+ */
+ public Encoding sniff(ByteReadable readable) throws SAXException, IOException {
+ this.readable = readable;
+ stateLoop(stateSave);
+ return characterEncoding;
+ }
+
+
+ /**
+ * @param string
+ * @throws SAXException
+ */
+ private void err(String message) throws SAXException {
+ if (errorHandler != null) {
+ SAXParseException spe = new SAXParseException(message, this);
+ errorHandler.error(spe);
+ }
+ }
+
+ /**
+ * @param string
+ * @throws SAXException
+ */
+ private void warn(String message) throws SAXException {
+ if (errorHandler != null) {
+ SAXParseException spe = new SAXParseException(message, this);
+ errorHandler.warning(spe);
+ }
+ }
+
+ public int getColumnNumber() {
+ return col;
+ }
+
+ public int getLineNumber() {
+ return line;
+ }
+
+ public String getPublicId() {
+ if (locator != null) {
+ return locator.getPublicId();
+ }
+ return null;
+ }
+
+ public String getSystemId() {
+ if (locator != null) {
+ return locator.getSystemId();
+ }
+ return null;
+ }
+
+ protected boolean tryCharset(String encoding) throws SAXException {
+ encoding = Encoding.toAsciiLowerCase(encoding);
+ try {
+ // XXX spec says only UTF-16
+ if ("utf-16".equals(encoding) || "utf-16be".equals(encoding) || "utf-16le".equals(encoding) || "utf-32".equals(encoding) || "utf-32be".equals(encoding) || "utf-32le".equals(encoding)) {
+ this.characterEncoding = Encoding.UTF8;
+ err("The internal character encoding declaration specified \u201C" + encoding + "\u201D which is not a rough superset of ASCII. Using \u201CUTF-8\u201D instead.");
+ return true;
+ } else {
+ Encoding cs = Encoding.forName(encoding);
+ String canonName = cs.getCanonName();
+ if (!cs.isAsciiSuperset()) {
+ err("The encoding \u201C"
+ + encoding
+ + "\u201D is not an ASCII superset and, therefore, cannot be used in an internal encoding declaration. Continuing the sniffing algorithm.");
+ return false;
+ }
+ if (!cs.isRegistered()) {
+ if (encoding.startsWith("x-")) {
+ err("The encoding \u201C"
+ + encoding
+ + "\u201D is not an IANA-registered encoding. (Charmod C022)");
+ } else {
+ err("The encoding \u201C"
+ + encoding
+ + "\u201D is not an IANA-registered encoding and did not use the \u201Cx-\u201D prefix. (Charmod C023)");
+ }
+ } else if (!cs.getCanonName().equals(encoding)) {
+ err("The encoding \u201C" + encoding
+ + "\u201D is not the preferred name of the character encoding in use. The preferred name is \u201C"
+ + canonName + "\u201D. (Charmod C024)");
+ }
+ if (cs.isShouldNot()) {
+ warn("Authors should not use the character encoding \u201C"
+ + encoding
+ + "\u201D. It is recommended to use \u201CUTF-8\u201D.");
+ } else if (cs.isObscure()) {
+ warn("The character encoding \u201C" + encoding + "\u201D is not widely supported. Better interoperability may be achieved by using \u201CUTF-8\u201D.");
+ }
+ Encoding actual = cs.getActualHtmlEncoding();
+ if (actual == null) {
+ this.characterEncoding = cs;
+ } else {
+ warn("Using \u201C" + actual.getCanonName() + "\u201D instead of the declared encoding \u201C" + encoding + "\u201D.");
+ this.characterEncoding = actual;
+ }
+ return true;
+ }
+ } catch (UnsupportedCharsetException e) {
+ err("Unsupported character encoding name: \u201C" + encoding + "\u201D. Will continue sniffing.");
+ }
+ return false;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/rewindable/Rewindable.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/rewindable/Rewindable.java
new file mode 100644
index 000000000..47a3d5eb0
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/rewindable/Rewindable.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2001-2003 Thai Open Source Software Center Ltd
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of the Thai Open Source Software Center Ltd nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package nu.validator.htmlparser.rewindable;
+
+public interface Rewindable {
+ void willNotRewind();
+
+ void rewind();
+
+ boolean canRewind();
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/rewindable/RewindableInputStream.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/rewindable/RewindableInputStream.java
new file mode 100644
index 000000000..3a1cc1b91
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/rewindable/RewindableInputStream.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2001-2003 Thai Open Source Software Center Ltd
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of the Thai Open Source Software Center Ltd nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package nu.validator.htmlparser.rewindable;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class RewindableInputStream extends InputStream implements Rewindable {
+ static class Block {
+ Block next;
+
+ final byte[] buf;
+
+ int used = 0;
+
+ static final int MIN_SIZE = 1024;
+
+ Block(int minSize) {
+ buf = new byte[Math.max(MIN_SIZE, minSize)];
+ }
+
+ Block() {
+ this(0);
+ }
+
+ void append(byte b) {
+ buf[used++] = b;
+ }
+
+ void append(byte[] b, int off, int len) {
+ System.arraycopy(b, off, buf, used, len);
+ used += len;
+ }
+ }
+
+ private Block head;
+
+ /**
+ * If curBlockAvail > 0, then there are curBlockAvail bytes available to be
+ * returned starting at curBlockPos in curBlock.buf.
+ */
+ private int curBlockAvail;
+
+ private Block curBlock;
+
+ private int curBlockPos;
+
+ private Block lastBlock;
+
+ /**
+ * true unless willNotRewind has been called
+ */
+ private boolean saving = true;
+
+ private final InputStream in;
+
+ private boolean pretendClosed = false;
+
+ /**
+ * true if we have got an EOF from the underlying InputStream
+ */
+ private boolean eof;
+
+ public RewindableInputStream(InputStream in) {
+ if (in == null)
+ throw new NullPointerException();
+ this.in = in;
+ }
+
+ public void close() throws IOException {
+ if (saving) {
+ curBlockAvail = 0;
+ curBlock = null;
+ pretendClosed = true;
+ } else {
+ head = null;
+ curBlock = null;
+ lastBlock = null;
+ saving = false;
+ curBlockAvail = 0;
+ in.close();
+ }
+ }
+
+ public void rewind() {
+ if (!saving)
+ throw new IllegalStateException("rewind() after willNotRewind()");
+ pretendClosed = false;
+ if (head == null)
+ return;
+ curBlock = head;
+ curBlockPos = 0;
+ curBlockAvail = curBlock.used;
+ }
+
+ public boolean canRewind() {
+ return saving;
+ }
+
+ public void willNotRewind() {
+ saving = false;
+ head = null;
+ lastBlock = null;
+ if (pretendClosed) {
+ pretendClosed = false;
+ try {
+ in.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ public int read() throws IOException {
+ if (curBlockAvail > 0) {
+ int c = curBlock.buf[curBlockPos++] & 0xFF;
+ --curBlockAvail;
+ if (curBlockAvail == 0) {
+ curBlock = curBlock.next;
+ if (curBlock != null) {
+ curBlockPos = 0;
+ curBlockAvail = curBlock.used;
+ }
+ }
+ return c;
+ }
+ int c = in.read();
+ if (saving && c != -1) {
+ if (lastBlock == null)
+ lastBlock = head = new Block();
+ else if (lastBlock.used == lastBlock.buf.length)
+ lastBlock = lastBlock.next = new Block();
+ lastBlock.append((byte) c);
+ }
+ return c;
+ }
+
+ public int read(byte b[], int off, int len) throws IOException {
+ if (curBlockAvail == 0 && !saving)
+ return in.read(b, off, len);
+ if (b == null)
+ throw new NullPointerException();
+ if (len < 0)
+ throw new IndexOutOfBoundsException();
+ int nRead = 0;
+ if (curBlockAvail != 0) {
+ for (;;) {
+ if (len == 0)
+ return nRead;
+ b[off++] = curBlock.buf[curBlockPos++];
+ --len;
+ nRead++;
+ --curBlockAvail;
+ if (curBlockAvail == 0) {
+ curBlock = curBlock.next;
+ if (curBlock == null)
+ break;
+ curBlockAvail = curBlock.used;
+ curBlockPos = 0;
+ }
+ }
+ }
+ if (len == 0)
+ return nRead;
+ if (eof)
+ return nRead > 0 ? nRead : -1;
+ try {
+ int n = in.read(b, off, len);
+ if (n < 0) {
+ eof = true;
+ return nRead > 0 ? nRead : -1;
+ }
+ nRead += n;
+ if (saving) {
+ if (lastBlock == null)
+ lastBlock = head = new Block(n);
+ else if (lastBlock.buf.length - lastBlock.used < n) {
+ if (lastBlock.used != lastBlock.buf.length) {
+ int free = lastBlock.buf.length - lastBlock.used;
+ lastBlock.append(b, off, free);
+ off += free;
+ n -= free;
+ }
+ lastBlock = lastBlock.next = new Block(n);
+ }
+ lastBlock.append(b, off, n);
+ }
+ } catch (IOException e) {
+ eof = true;
+ if (nRead == 0)
+ throw e;
+ }
+ return nRead;
+ }
+
+ public int available() throws IOException {
+ if (curBlockAvail == 0)
+ return in.available();
+ int n = curBlockAvail;
+ for (Block b = curBlock.next; b != null; b = b.next)
+ n += b.used;
+ return n + in.available();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/HtmlParser.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/HtmlParser.java
new file mode 100644
index 000000000..714053e70
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/HtmlParser.java
@@ -0,0 +1,1097 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.sax;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.HashMap;
+
+import nu.validator.htmlparser.common.CharacterHandler;
+import nu.validator.htmlparser.common.DoctypeExpectation;
+import nu.validator.htmlparser.common.DocumentModeHandler;
+import nu.validator.htmlparser.common.Heuristics;
+import nu.validator.htmlparser.common.TokenHandler;
+import nu.validator.htmlparser.common.TransitionHandler;
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.impl.ErrorReportingTokenizer;
+import nu.validator.htmlparser.impl.Tokenizer;
+import nu.validator.htmlparser.impl.TreeBuilder;
+import nu.validator.htmlparser.io.Driver;
+import nu.validator.saxtree.Document;
+import nu.validator.saxtree.DocumentFragment;
+import nu.validator.saxtree.TreeParser;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * This class implements an HTML5 parser that exposes data through the SAX2
+ * interface.
+ *
+ * <p>By default, when using the constructor without arguments, the
+ * this parser coerces XML 1.0-incompatible infosets into XML 1.0-compatible
+ * infosets. This corresponds to <code>ALTER_INFOSET</code> as the general
+ * XML violation policy. To make the parser support non-conforming HTML fully
+ * per the HTML 5 spec while on the other hand potentially violating the SAX2
+ * API contract, set the general XML violation policy to <code>ALLOW</code>.
+ * It is possible to treat XML 1.0 infoset violations as fatal by setting
+ * the general XML violation policy to <code>FATAL</code>.
+ *
+ * <p>By default, this parser doesn't do true streaming but buffers everything
+ * first. The parser can be made truly streaming by calling
+ * <code>setStreamabilityViolationPolicy(XmlViolationPolicy.FATAL)</code>. This
+ * has the consequence that errors that require non-streamable recovery are
+ * treated as fatal.
+ *
+ * <p>By default, in order to make the parse events emulate the parse events
+ * for a DTDless XML document, the parser does not report the doctype through
+ * <code>LexicalHandler</code>. Doctype reporting through
+ * <code>LexicalHandler</code> can be turned on by calling
+ * <code>setReportingDoctype(true)</code>.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public class HtmlParser implements XMLReader {
+
+ private Driver driver = null;
+
+ private TreeBuilder<?> treeBuilder = null;
+
+ private SAXStreamer saxStreamer = null; // work around javac bug
+
+ private SAXTreeBuilder saxTreeBuilder = null; // work around javac bug
+
+ private ContentHandler contentHandler = null;
+
+ private LexicalHandler lexicalHandler = null;
+
+ private DTDHandler dtdHandler = null;
+
+ private EntityResolver entityResolver = null;
+
+ private ErrorHandler errorHandler = null;
+
+ private DocumentModeHandler documentModeHandler = null;
+
+ private DoctypeExpectation doctypeExpectation = DoctypeExpectation.HTML;
+
+ private boolean checkingNormalization = false;
+
+ private boolean scriptingEnabled = false;
+
+ private final List<CharacterHandler> characterHandlers = new LinkedList<CharacterHandler>();
+
+ private XmlViolationPolicy contentSpacePolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy contentNonXmlCharPolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy commentPolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy namePolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy streamabilityViolationPolicy = XmlViolationPolicy.ALLOW;
+
+ private boolean html4ModeCompatibleWithXhtml1Schemata = false;
+
+ private boolean mappingLangToXmlLang = false;
+
+ private XmlViolationPolicy xmlnsPolicy = XmlViolationPolicy.FATAL;
+
+ private boolean reportingDoctype = true;
+
+ private ErrorHandler treeBuilderErrorHandler = null;
+
+ private Heuristics heuristics = Heuristics.NONE;
+
+ private HashMap<String, String> errorProfileMap = null;
+
+ private TransitionHandler transitionHandler = null;
+
+ /**
+ * Instantiates the parser with a fatal XML violation policy.
+ *
+ */
+ public HtmlParser() {
+ this(XmlViolationPolicy.FATAL);
+ }
+
+ /**
+ * Instantiates the parser with a specific XML violation policy.
+ * @param xmlPolicy the policy
+ */
+ public HtmlParser(XmlViolationPolicy xmlPolicy) {
+ setXmlPolicy(xmlPolicy);
+ }
+
+ private Tokenizer newTokenizer(TokenHandler handler, boolean newAttributesEachTime) {
+ if (errorHandler == null && transitionHandler == null &&
+ contentNonXmlCharPolicy == XmlViolationPolicy.ALLOW) {
+ return new Tokenizer(handler, newAttributesEachTime);
+ }
+ ErrorReportingTokenizer tokenizer =
+ new ErrorReportingTokenizer(handler, newAttributesEachTime);
+ tokenizer.setErrorProfile(errorProfileMap);
+ return tokenizer;
+ }
+
+ /**
+ * This class wraps different tree builders depending on configuration. This
+ * method does the work of hiding this from the user of the class.
+ */
+ private void lazyInit() {
+ if (driver == null) {
+ if (streamabilityViolationPolicy == XmlViolationPolicy.ALLOW) {
+ this.saxTreeBuilder = new SAXTreeBuilder();
+ this.treeBuilder = this.saxTreeBuilder;
+ this.saxStreamer = null;
+ this.driver = new Driver(newTokenizer(treeBuilder, true));
+ } else {
+ this.saxStreamer = new SAXStreamer();
+ this.treeBuilder = this.saxStreamer;
+ this.saxTreeBuilder = null;
+ this.driver = new Driver(newTokenizer(treeBuilder, false));
+ }
+ this.driver.setErrorHandler(errorHandler);
+ this.driver.setTransitionHandler(transitionHandler);
+ this.treeBuilder.setErrorHandler(treeBuilderErrorHandler);
+ this.driver.setCheckingNormalization(checkingNormalization);
+ this.driver.setCommentPolicy(commentPolicy);
+ this.driver.setContentNonXmlCharPolicy(contentNonXmlCharPolicy);
+ this.driver.setContentSpacePolicy(contentSpacePolicy);
+ this.driver.setHtml4ModeCompatibleWithXhtml1Schemata(html4ModeCompatibleWithXhtml1Schemata);
+ this.driver.setMappingLangToXmlLang(mappingLangToXmlLang);
+ this.driver.setXmlnsPolicy(xmlnsPolicy);
+ this.driver.setHeuristics(heuristics);
+ for (CharacterHandler characterHandler : characterHandlers) {
+ this.driver.addCharacterHandler(characterHandler);
+ }
+ this.treeBuilder.setDoctypeExpectation(doctypeExpectation);
+ this.treeBuilder.setDocumentModeHandler(documentModeHandler);
+ this.treeBuilder.setIgnoringComments(lexicalHandler == null);
+ this.treeBuilder.setScriptingEnabled(scriptingEnabled);
+ this.treeBuilder.setReportingDoctype(reportingDoctype);
+ this.treeBuilder.setNamePolicy(namePolicy);
+ if (saxStreamer != null) {
+ saxStreamer.setContentHandler(contentHandler == null ? new DefaultHandler()
+ : contentHandler);
+ saxStreamer.setLexicalHandler(lexicalHandler);
+ driver.setAllowRewinding(false);
+ }
+ }
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#getContentHandler()
+ */
+ public ContentHandler getContentHandler() {
+ return contentHandler;
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#getDTDHandler()
+ */
+ public DTDHandler getDTDHandler() {
+ return dtdHandler;
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#getEntityResolver()
+ */
+ public EntityResolver getEntityResolver() {
+ return entityResolver;
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#getErrorHandler()
+ */
+ public ErrorHandler getErrorHandler() {
+ return errorHandler;
+ }
+
+ /**
+ * Exposes the configuration of the emulated XML parser as well as
+ * boolean-valued configuration without using non-<code>XMLReader</code>
+ * getters directly.
+ *
+ * <dl>
+ * <dt><code>http://xml.org/sax/features/external-general-entities</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://xml.org/sax/features/external-parameter-entities</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://xml.org/sax/features/is-standalone</code></dt>
+ * <dd><code>true</code></dd>
+ * <dt><code>http://xml.org/sax/features/lexical-handler/parameter-entities</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://xml.org/sax/features/namespaces</code></dt>
+ * <dd><code>true</code></dd>
+ * <dt><code>http://xml.org/sax/features/namespace-prefixes</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://xml.org/sax/features/resolve-dtd-uris</code></dt>
+ * <dd><code>true</code></dd>
+ * <dt><code>http://xml.org/sax/features/string-interning</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://xml.org/sax/features/unicode-normalization-checking</code></dt>
+ * <dd><code>isCheckingNormalization</code></dd>
+ * <dt><code>http://xml.org/sax/features/use-attributes2</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://xml.org/sax/features/use-locator2</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://xml.org/sax/features/use-entity-resolver2</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://xml.org/sax/features/validation</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://xml.org/sax/features/xmlns-uris</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://xml.org/sax/features/xml-1.1</code></dt>
+ * <dd><code>false</code></dd>
+ * <dt><code>http://validator.nu/features/html4-mode-compatible-with-xhtml1-schemata</code></dt>
+ * <dd><code>isHtml4ModeCompatibleWithXhtml1Schemata</code></dd>
+ * <dt><code>http://validator.nu/features/mapping-lang-to-xml-lang</code></dt>
+ * <dd><code>isMappingLangToXmlLang</code></dd>
+ * <dt><code>http://validator.nu/features/scripting-enabled</code></dt>
+ * <dd><code>isScriptingEnabled</code></dd>
+ * </dl>
+ *
+ * @param name
+ * feature URI string
+ * @return a value per the list above
+ * @see org.xml.sax.XMLReader#getFeature(java.lang.String)
+ */
+ public boolean getFeature(String name) throws SAXNotRecognizedException,
+ SAXNotSupportedException {
+ if ("http://xml.org/sax/features/external-general-entities".equals(name)) {
+ return false;
+ } else if ("http://xml.org/sax/features/external-parameter-entities".equals(name)) {
+ return false;
+ } else if ("http://xml.org/sax/features/is-standalone".equals(name)) {
+ return true;
+ } else if ("http://xml.org/sax/features/lexical-handler/parameter-entities".equals(name)) {
+ return false;
+ } else if ("http://xml.org/sax/features/namespaces".equals(name)) {
+ return true;
+ } else if ("http://xml.org/sax/features/namespace-prefixes".equals(name)) {
+ return false;
+ } else if ("http://xml.org/sax/features/resolve-dtd-uris".equals(name)) {
+ return true; // default value--applicable scenario never happens
+ } else if ("http://xml.org/sax/features/string-interning".equals(name)) {
+ return true;
+ } else if ("http://xml.org/sax/features/unicode-normalization-checking".equals(name)) {
+ return isCheckingNormalization(); // the checks aren't really per
+ // XML 1.1
+ } else if ("http://xml.org/sax/features/use-attributes2".equals(name)) {
+ return false;
+ } else if ("http://xml.org/sax/features/use-locator2".equals(name)) {
+ return false;
+ } else if ("http://xml.org/sax/features/use-entity-resolver2".equals(name)) {
+ return false;
+ } else if ("http://xml.org/sax/features/validation".equals(name)) {
+ return false;
+ } else if ("http://xml.org/sax/features/xmlns-uris".equals(name)) {
+ return false;
+ } else if ("http://xml.org/sax/features/xml-1.1".equals(name)) {
+ return false;
+ } else if ("http://validator.nu/features/html4-mode-compatible-with-xhtml1-schemata".equals(name)) {
+ return isHtml4ModeCompatibleWithXhtml1Schemata();
+ } else if ("http://validator.nu/features/mapping-lang-to-xml-lang".equals(name)) {
+ return isMappingLangToXmlLang();
+ } else if ("http://validator.nu/features/scripting-enabled".equals(name)) {
+ return isScriptingEnabled();
+ } else {
+ throw new SAXNotRecognizedException();
+ }
+ }
+
+ /**
+ * Allows <code>XMLReader</code>-level access to non-boolean valued
+ * getters.
+ *
+ * <p>
+ * The properties are mapped as follows:
+ *
+ * <dl>
+ * <dt><code>http://xml.org/sax/properties/document-xml-version</code></dt>
+ * <dd><code>"1.0"</code></dd>
+ * <dt><code>http://xml.org/sax/properties/lexical-handler</code></dt>
+ * <dd><code>getLexicalHandler</code></dd>
+ * <dt><code>http://validator.nu/properties/content-space-policy</code></dt>
+ * <dd><code>getContentSpacePolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/content-non-xml-char-policy</code></dt>
+ * <dd><code>getContentNonXmlCharPolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/comment-policy</code></dt>
+ * <dd><code>getCommentPolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/xmlns-policy</code></dt>
+ * <dd><code>getXmlnsPolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/name-policy</code></dt>
+ * <dd><code>getNamePolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/streamability-violation-policy</code></dt>
+ * <dd><code>getStreamabilityViolationPolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/document-mode-handler</code></dt>
+ * <dd><code>getDocumentModeHandler</code></dd>
+ * <dt><code>http://validator.nu/properties/doctype-expectation</code></dt>
+ * <dd><code>getDoctypeExpectation</code></dd>
+ * <dt><code>http://xml.org/sax/features/unicode-normalization-checking</code></dt>
+ * </dl>
+ *
+ * @param name
+ * property URI string
+ * @return a value per the list above
+ * @see org.xml.sax.XMLReader#getProperty(java.lang.String)
+ */
+ public Object getProperty(String name) throws SAXNotRecognizedException,
+ SAXNotSupportedException {
+ if ("http://xml.org/sax/properties/declaration-handler".equals(name)) {
+ throw new SAXNotSupportedException(
+ "This parser does not suppert DeclHandler.");
+ } else if ("http://xml.org/sax/properties/document-xml-version".equals(name)) {
+ return "1.0"; // Emulating an XML 1.1 parser is not supported.
+ } else if ("http://xml.org/sax/properties/dom-node".equals(name)) {
+ throw new SAXNotSupportedException(
+ "This parser does not walk the DOM.");
+ } else if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
+ return getLexicalHandler();
+ } else if ("http://xml.org/sax/properties/xml-string".equals(name)) {
+ throw new SAXNotSupportedException(
+ "This parser does not expose the source as a string.");
+ } else if ("http://validator.nu/properties/content-space-policy".equals(name)) {
+ return getContentSpacePolicy();
+ } else if ("http://validator.nu/properties/content-non-xml-char-policy".equals(name)) {
+ return getContentNonXmlCharPolicy();
+ } else if ("http://validator.nu/properties/comment-policy".equals(name)) {
+ return getCommentPolicy();
+ } else if ("http://validator.nu/properties/xmlns-policy".equals(name)) {
+ return getXmlnsPolicy();
+ } else if ("http://validator.nu/properties/name-policy".equals(name)) {
+ return getNamePolicy();
+ } else if ("http://validator.nu/properties/streamability-violation-policy".equals(name)) {
+ return getStreamabilityViolationPolicy();
+ } else if ("http://validator.nu/properties/document-mode-handler".equals(name)) {
+ return getDocumentModeHandler();
+ } else if ("http://validator.nu/properties/doctype-expectation".equals(name)) {
+ return getDoctypeExpectation();
+ } else if ("http://validator.nu/properties/xml-policy".equals(name)) {
+ throw new SAXNotSupportedException(
+ "Cannot get a convenience setter.");
+ } else if ("http://validator.nu/properties/heuristics".equals(name)) {
+ return getHeuristics();
+ } else {
+ throw new SAXNotRecognizedException();
+ }
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#parse(org.xml.sax.InputSource)
+ */
+ public void parse(InputSource input) throws IOException, SAXException {
+ lazyInit();
+ try {
+ treeBuilder.setFragmentContext(null);
+ tokenize(input);
+ } finally {
+ if (saxTreeBuilder != null) {
+ Document document = saxTreeBuilder.getDocument();
+ if (document != null) {
+ new TreeParser(contentHandler, lexicalHandler).parse(document);
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses a fragment with HTML context.
+ *
+ * @param input the input to parse
+ * @param context the name of the context element (HTML namespace assumed)
+ * @throws IOException
+ * @throws SAXException
+ */
+ public void parseFragment(InputSource input, String context)
+ throws IOException, SAXException {
+ lazyInit();
+ try {
+ treeBuilder.setFragmentContext(context.intern());
+ tokenize(input);
+ } finally {
+ if (saxTreeBuilder != null) {
+ DocumentFragment fragment = saxTreeBuilder.getDocumentFragment();
+ new TreeParser(contentHandler, lexicalHandler).parse(fragment);
+ }
+ }
+ }
+
+ /**
+ * Parses a fragment.
+ *
+ * @param input the input to parse
+ * @param contextLocal the local name of the context element
+ * @param contextNamespace the namespace of the context element
+ * @throws IOException
+ * @throws SAXException
+ */
+ public void parseFragment(InputSource input, String contextLocal, String contextNamespace)
+ throws IOException, SAXException {
+ lazyInit();
+ try {
+ treeBuilder.setFragmentContext(contextLocal.intern(), contextNamespace.intern(), null, false);
+ tokenize(input);
+ } finally {
+ if (saxTreeBuilder != null) {
+ DocumentFragment fragment = saxTreeBuilder.getDocumentFragment();
+ new TreeParser(contentHandler, lexicalHandler).parse(fragment);
+ }
+ }
+ }
+
+ /**
+ * @param is
+ * @throws SAXException
+ * @throws IOException
+ * @throws MalformedURLException
+ */
+ private void tokenize(InputSource is) throws SAXException, IOException, MalformedURLException {
+ if (is == null) {
+ throw new IllegalArgumentException("Null input.");
+ }
+ if (is.getByteStream() == null && is.getCharacterStream() == null) {
+ String systemId = is.getSystemId();
+ if (systemId == null) {
+ throw new IllegalArgumentException("No byte stream, no character stream nor URI.");
+ }
+ if (entityResolver != null) {
+ is = entityResolver.resolveEntity(is.getPublicId(), systemId);
+ }
+ if (is.getByteStream() == null || is.getCharacterStream() == null) {
+ is = new InputSource();
+ is.setSystemId(systemId);
+ is.setByteStream(new URL(systemId).openStream());
+ }
+ }
+ driver.tokenize(is);
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#parse(java.lang.String)
+ */
+ public void parse(String systemId) throws IOException, SAXException {
+ parse(new InputSource(systemId));
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#setContentHandler(org.xml.sax.ContentHandler)
+ */
+ public void setContentHandler(ContentHandler handler) {
+ contentHandler = handler;
+ if (saxStreamer != null) {
+ saxStreamer.setContentHandler(contentHandler == null ? new DefaultHandler()
+ : contentHandler);
+ }
+ }
+
+ /**
+ * Sets the lexical handler.
+ * @param handler the hander.
+ */
+ public void setLexicalHandler(LexicalHandler handler) {
+ lexicalHandler = handler;
+ if (treeBuilder != null) {
+ treeBuilder.setIgnoringComments(handler == null);
+ if (saxStreamer != null) {
+ saxStreamer.setLexicalHandler(handler);
+ }
+ }
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#setDTDHandler(org.xml.sax.DTDHandler)
+ */
+ public void setDTDHandler(DTDHandler handler) {
+ dtdHandler = handler;
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#setEntityResolver(org.xml.sax.EntityResolver)
+ */
+ public void setEntityResolver(EntityResolver resolver) {
+ entityResolver = resolver;
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#setErrorHandler(org.xml.sax.ErrorHandler)
+ */
+ public void setErrorHandler(ErrorHandler handler) {
+ errorHandler = handler;
+ treeBuilderErrorHandler = handler;
+ driver = null;
+ }
+
+ public void setTransitionHandler(TransitionHandler handler) {
+ transitionHandler = handler;
+ driver = null;
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#setErrorHandler(org.xml.sax.ErrorHandler)
+ * @deprecated For Validator.nu internal use
+ */
+ public void setTreeBuilderErrorHandlerOverride(ErrorHandler handler) {
+ treeBuilderErrorHandler = handler;
+ if (driver != null) {
+ treeBuilder.setErrorHandler(handler);
+ }
+ }
+
+ /**
+ * Sets a boolean feature without having to use non-<code>XMLReader</code>
+ * setters directly.
+ *
+ * <p>
+ * The supported features are:
+ *
+ * <dl>
+ * <dt><code>http://xml.org/sax/features/unicode-normalization-checking</code></dt>
+ * <dd><code>setCheckingNormalization</code></dd>
+ * <dt><code>http://validator.nu/features/html4-mode-compatible-with-xhtml1-schemata</code></dt>
+ * <dd><code>setHtml4ModeCompatibleWithXhtml1Schemata</code></dd>
+ * <dt><code>http://validator.nu/features/mapping-lang-to-xml-lang</code></dt>
+ * <dd><code>setMappingLangToXmlLang</code></dd>
+ * <dt><code>http://validator.nu/features/scripting-enabled</code></dt>
+ * <dd><code>setScriptingEnabled</code></dd>
+ * </dl>
+ *
+ * @see org.xml.sax.XMLReader#setFeature(java.lang.String, boolean)
+ */
+ public void setFeature(String name, boolean value)
+ throws SAXNotRecognizedException, SAXNotSupportedException {
+ if ("http://xml.org/sax/features/external-general-entities".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/external-parameter-entities".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/is-standalone".equals(name)) {
+ if (!value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/lexical-handler/parameter-entities".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/namespaces".equals(name)) {
+ if (!value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/namespace-prefixes".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/resolve-dtd-uris".equals(name)) {
+ if (!value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/string-interning".equals(name)) {
+ if (!value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/unicode-normalization-checking".equals(name)) {
+ setCheckingNormalization(value);
+ } else if ("http://xml.org/sax/features/use-attributes2".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/use-locator2".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/use-entity-resolver2".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/validation".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/xmlns-uris".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://xml.org/sax/features/xml-1.1".equals(name)) {
+ if (value) {
+ throw new SAXNotSupportedException("Cannot set " + name + ".");
+ }
+ } else if ("http://validator.nu/features/html4-mode-compatible-with-xhtml1-schemata".equals(name)) {
+ setHtml4ModeCompatibleWithXhtml1Schemata(value);
+ } else if ("http://validator.nu/features/mapping-lang-to-xml-lang".equals(name)) {
+ setMappingLangToXmlLang(value);
+ } else if ("http://validator.nu/features/scripting-enabled".equals(name)) {
+ setScriptingEnabled(value);
+ } else {
+ throw new SAXNotRecognizedException();
+ }
+ }
+
+ /**
+ * Sets a non-boolean property without having to use non-<code>XMLReader</code>
+ * setters directly.
+ *
+ * <dl>
+ * <dt><code>http://xml.org/sax/properties/lexical-handler</code></dt>
+ * <dd><code>setLexicalHandler</code></dd>
+ * <dt><code>http://validator.nu/properties/content-space-policy</code></dt>
+ * <dd><code>setContentSpacePolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/content-non-xml-char-policy</code></dt>
+ * <dd><code>setContentNonXmlCharPolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/comment-policy</code></dt>
+ * <dd><code>setCommentPolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/xmlns-policy</code></dt>
+ * <dd><code>setXmlnsPolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/name-policy</code></dt>
+ * <dd><code>setNamePolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/streamability-violation-policy</code></dt>
+ * <dd><code>setStreamabilityViolationPolicy</code></dd>
+ * <dt><code>http://validator.nu/properties/document-mode-handler</code></dt>
+ * <dd><code>setDocumentModeHandler</code></dd>
+ * <dt><code>http://validator.nu/properties/doctype-expectation</code></dt>
+ * <dd><code>setDoctypeExpectation</code></dd>
+ * <dt><code>http://validator.nu/properties/xml-policy</code></dt>
+ * <dd><code>setXmlPolicy</code></dd>
+ * </dl>
+ *
+ * @see org.xml.sax.XMLReader#setProperty(java.lang.String,
+ * java.lang.Object)
+ */
+ public void setProperty(String name, Object value)
+ throws SAXNotRecognizedException, SAXNotSupportedException {
+ if ("http://xml.org/sax/properties/declaration-handler".equals(name)) {
+ throw new SAXNotSupportedException(
+ "This parser does not suppert DeclHandler.");
+ } else if ("http://xml.org/sax/properties/document-xml-version".equals(name)) {
+ throw new SAXNotSupportedException(
+ "Can't set document-xml-version.");
+ } else if ("http://xml.org/sax/properties/dom-node".equals(name)) {
+ throw new SAXNotSupportedException("Can't set dom-node.");
+ } else if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
+ setLexicalHandler((LexicalHandler) value);
+ } else if ("http://xml.org/sax/properties/xml-string".equals(name)) {
+ throw new SAXNotSupportedException("Can't set xml-string.");
+ } else if ("http://validator.nu/properties/content-space-policy".equals(name)) {
+ setContentSpacePolicy((XmlViolationPolicy) value);
+ } else if ("http://validator.nu/properties/content-non-xml-char-policy".equals(name)) {
+ setContentNonXmlCharPolicy((XmlViolationPolicy) value);
+ } else if ("http://validator.nu/properties/comment-policy".equals(name)) {
+ setCommentPolicy((XmlViolationPolicy) value);
+ } else if ("http://validator.nu/properties/xmlns-policy".equals(name)) {
+ setXmlnsPolicy((XmlViolationPolicy) value);
+ } else if ("http://validator.nu/properties/name-policy".equals(name)) {
+ setNamePolicy((XmlViolationPolicy) value);
+ } else if ("http://validator.nu/properties/streamability-violation-policy".equals(name)) {
+ setStreamabilityViolationPolicy((XmlViolationPolicy) value);
+ } else if ("http://validator.nu/properties/document-mode-handler".equals(name)) {
+ setDocumentModeHandler((DocumentModeHandler) value);
+ } else if ("http://validator.nu/properties/doctype-expectation".equals(name)) {
+ setDoctypeExpectation((DoctypeExpectation) value);
+ } else if ("http://validator.nu/properties/xml-policy".equals(name)) {
+ setXmlPolicy((XmlViolationPolicy) value);
+ } else if ("http://validator.nu/properties/heuristics".equals(name)) {
+ setHeuristics((Heuristics) value);
+ } else {
+ throw new SAXNotRecognizedException();
+ }
+ }
+
+ /**
+ * Indicates whether NFC normalization of source is being checked.
+ * @return <code>true</code> if NFC normalization of source is being checked.
+ * @see nu.validator.htmlparser.impl.Tokenizer#isCheckingNormalization()
+ */
+ public boolean isCheckingNormalization() {
+ return checkingNormalization;
+ }
+
+ /**
+ * Toggles the checking of the NFC normalization of source.
+ * @param enable <code>true</code> to check normalization
+ * @see nu.validator.htmlparser.impl.Tokenizer#setCheckingNormalization(boolean)
+ */
+ public void setCheckingNormalization(boolean enable) {
+ this.checkingNormalization = enable;
+ if (driver != null) {
+ driver.setCheckingNormalization(checkingNormalization);
+ }
+ }
+
+ /**
+ * Sets the policy for consecutive hyphens in comments.
+ * @param commentPolicy the policy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setCommentPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setCommentPolicy(XmlViolationPolicy commentPolicy) {
+ this.commentPolicy = commentPolicy;
+ if (driver != null) {
+ driver.setCommentPolicy(commentPolicy);
+ }
+ }
+
+ /**
+ * Sets the policy for non-XML characters except white space.
+ * @param contentNonXmlCharPolicy the policy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setContentNonXmlCharPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setContentNonXmlCharPolicy(
+ XmlViolationPolicy contentNonXmlCharPolicy) {
+ this.contentNonXmlCharPolicy = contentNonXmlCharPolicy;
+ driver = null;
+ }
+
+ /**
+ * Sets the policy for non-XML white space.
+ * @param contentSpacePolicy the policy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setContentSpacePolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setContentSpacePolicy(XmlViolationPolicy contentSpacePolicy) {
+ this.contentSpacePolicy = contentSpacePolicy;
+ if (driver != null) {
+ driver.setContentSpacePolicy(contentSpacePolicy);
+ }
+ }
+
+ /**
+ * Whether the parser considers scripting to be enabled for noscript treatment.
+ *
+ * @return <code>true</code> if enabled
+ * @see nu.validator.htmlparser.impl.TreeBuilder#isScriptingEnabled()
+ */
+ public boolean isScriptingEnabled() {
+ return scriptingEnabled;
+ }
+
+ /**
+ * Sets whether the parser considers scripting to be enabled for noscript treatment.
+ * @param scriptingEnabled <code>true</code> to enable
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setScriptingEnabled(boolean)
+ */
+ public void setScriptingEnabled(boolean scriptingEnabled) {
+ this.scriptingEnabled = scriptingEnabled;
+ if (treeBuilder != null) {
+ treeBuilder.setScriptingEnabled(scriptingEnabled);
+ }
+ }
+
+ /**
+ * Returns the doctype expectation.
+ *
+ * @return the doctypeExpectation
+ */
+ public DoctypeExpectation getDoctypeExpectation() {
+ return doctypeExpectation;
+ }
+
+ /**
+ * Sets the doctype expectation.
+ *
+ * @param doctypeExpectation
+ * the doctypeExpectation to set
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setDoctypeExpectation(nu.validator.htmlparser.common.DoctypeExpectation)
+ */
+ public void setDoctypeExpectation(DoctypeExpectation doctypeExpectation) {
+ this.doctypeExpectation = doctypeExpectation;
+ if (treeBuilder != null) {
+ treeBuilder.setDoctypeExpectation(doctypeExpectation);
+ }
+ }
+
+ /**
+ * Returns the document mode handler.
+ *
+ * @return the documentModeHandler
+ */
+ public DocumentModeHandler getDocumentModeHandler() {
+ return documentModeHandler;
+ }
+
+ /**
+ * Sets the document mode handler.
+ *
+ * @param documentModeHandler
+ * the documentModeHandler to set
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setDocumentModeHandler(nu.validator.htmlparser.common.DocumentModeHandler)
+ */
+ public void setDocumentModeHandler(DocumentModeHandler documentModeHandler) {
+ this.documentModeHandler = documentModeHandler;
+ }
+
+ /**
+ * Returns the streamabilityViolationPolicy.
+ *
+ * @return the streamabilityViolationPolicy
+ */
+ public XmlViolationPolicy getStreamabilityViolationPolicy() {
+ return streamabilityViolationPolicy;
+ }
+
+ /**
+ * Sets the streamabilityViolationPolicy.
+ *
+ * @param streamabilityViolationPolicy
+ * the streamabilityViolationPolicy to set
+ */
+ public void setStreamabilityViolationPolicy(
+ XmlViolationPolicy streamabilityViolationPolicy) {
+ this.streamabilityViolationPolicy = streamabilityViolationPolicy;
+ driver = null;
+ }
+
+ /**
+ * Whether the HTML 4 mode reports boolean attributes in a way that repeats
+ * the name in the value.
+ * @param html4ModeCompatibleWithXhtml1Schemata
+ */
+ public void setHtml4ModeCompatibleWithXhtml1Schemata(
+ boolean html4ModeCompatibleWithXhtml1Schemata) {
+ this.html4ModeCompatibleWithXhtml1Schemata = html4ModeCompatibleWithXhtml1Schemata;
+ if (driver != null) {
+ driver.setHtml4ModeCompatibleWithXhtml1Schemata(html4ModeCompatibleWithXhtml1Schemata);
+ }
+ }
+
+ /**
+ * Returns the <code>Locator</code> during parse.
+ * @return the <code>Locator</code>
+ */
+ public Locator getDocumentLocator() {
+ return driver.getDocumentLocator();
+ }
+
+ /**
+ * Whether the HTML 4 mode reports boolean attributes in a way that repeats
+ * the name in the value.
+ *
+ * @return the html4ModeCompatibleWithXhtml1Schemata
+ */
+ public boolean isHtml4ModeCompatibleWithXhtml1Schemata() {
+ return html4ModeCompatibleWithXhtml1Schemata;
+ }
+
+ /**
+ * Whether <code>lang</code> is mapped to <code>xml:lang</code>.
+ * @param mappingLangToXmlLang
+ * @see nu.validator.htmlparser.impl.Tokenizer#setMappingLangToXmlLang(boolean)
+ */
+ public void setMappingLangToXmlLang(boolean mappingLangToXmlLang) {
+ this.mappingLangToXmlLang = mappingLangToXmlLang;
+ if (driver != null) {
+ driver.setMappingLangToXmlLang(mappingLangToXmlLang);
+ }
+ }
+
+ /**
+ * Whether <code>lang</code> is mapped to <code>xml:lang</code>.
+ *
+ * @return the mappingLangToXmlLang
+ */
+ public boolean isMappingLangToXmlLang() {
+ return mappingLangToXmlLang;
+ }
+
+ /**
+ * Whether the <code>xmlns</code> attribute on the root element is
+ * passed to through. (FATAL not allowed.)
+ * @param xmlnsPolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setXmlnsPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setXmlnsPolicy(XmlViolationPolicy xmlnsPolicy) {
+ if (xmlnsPolicy == XmlViolationPolicy.FATAL) {
+ throw new IllegalArgumentException("Can't use FATAL here.");
+ }
+ this.xmlnsPolicy = xmlnsPolicy;
+ if (driver != null) {
+ driver.setXmlnsPolicy(xmlnsPolicy);
+ }
+ }
+
+ /**
+ * Returns the xmlnsPolicy.
+ *
+ * @return the xmlnsPolicy
+ */
+ public XmlViolationPolicy getXmlnsPolicy() {
+ return xmlnsPolicy;
+ }
+
+ /**
+ * Returns the lexicalHandler.
+ *
+ * @return the lexicalHandler
+ */
+ public LexicalHandler getLexicalHandler() {
+ return lexicalHandler;
+ }
+
+ /**
+ * Returns the commentPolicy.
+ *
+ * @return the commentPolicy
+ */
+ public XmlViolationPolicy getCommentPolicy() {
+ return commentPolicy;
+ }
+
+ /**
+ * Returns the contentNonXmlCharPolicy.
+ *
+ * @return the contentNonXmlCharPolicy
+ */
+ public XmlViolationPolicy getContentNonXmlCharPolicy() {
+ return contentNonXmlCharPolicy;
+ }
+
+ /**
+ * Returns the contentSpacePolicy.
+ *
+ * @return the contentSpacePolicy
+ */
+ public XmlViolationPolicy getContentSpacePolicy() {
+ return contentSpacePolicy;
+ }
+
+ /**
+ * @param reportingDoctype
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setReportingDoctype(boolean)
+ */
+ public void setReportingDoctype(boolean reportingDoctype) {
+ this.reportingDoctype = reportingDoctype;
+ if (treeBuilder != null) {
+ treeBuilder.setReportingDoctype(reportingDoctype);
+ }
+ }
+
+ /**
+ * Returns the reportingDoctype.
+ *
+ * @return the reportingDoctype
+ */
+ public boolean isReportingDoctype() {
+ return reportingDoctype;
+ }
+
+ /**
+ * @param errorProfile
+ * @see nu.validator.htmlparser.impl.errorReportingTokenizer#setErrorProfile(set)
+ */
+ public void setErrorProfile(HashMap<String, String> errorProfileMap) {
+ this.errorProfileMap = errorProfileMap;
+ }
+
+ /**
+ * The policy for non-NCName element and attribute names.
+ * @param namePolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setNamePolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setNamePolicy(XmlViolationPolicy namePolicy) {
+ this.namePolicy = namePolicy;
+ if (driver != null) {
+ driver.setNamePolicy(namePolicy);
+ treeBuilder.setNamePolicy(namePolicy);
+ }
+ }
+
+ /**
+ * Sets the encoding sniffing heuristics.
+ *
+ * @param heuristics the heuristics to set
+ * @see nu.validator.htmlparser.impl.Tokenizer#setHeuristics(nu.validator.htmlparser.common.Heuristics)
+ */
+ public void setHeuristics(Heuristics heuristics) {
+ this.heuristics = heuristics;
+ if (driver != null) {
+ driver.setHeuristics(heuristics);
+ }
+ }
+
+ public Heuristics getHeuristics() {
+ return this.heuristics;
+ }
+
+ /**
+ * This is a catch-all convenience method for setting name, xmlns, content space,
+ * content non-XML char and comment policies in one go. This does not affect the
+ * streamability policy or doctype reporting.
+ *
+ * @param xmlPolicy
+ */
+ public void setXmlPolicy(XmlViolationPolicy xmlPolicy) {
+ setNamePolicy(xmlPolicy);
+ setXmlnsPolicy(xmlPolicy == XmlViolationPolicy.FATAL ? XmlViolationPolicy.ALTER_INFOSET : xmlPolicy);
+ setContentSpacePolicy(xmlPolicy);
+ setContentNonXmlCharPolicy(xmlPolicy);
+ setCommentPolicy(xmlPolicy);
+ }
+
+ /**
+ * The policy for non-NCName element and attribute names.
+ *
+ * @return the namePolicy
+ */
+ public XmlViolationPolicy getNamePolicy() {
+ return namePolicy;
+ }
+
+ /**
+ * Does nothing.
+ * @deprecated
+ */
+ public void setBogusXmlnsPolicy(
+ XmlViolationPolicy bogusXmlnsPolicy) {
+ }
+
+ /**
+ * Returns <code>XmlViolationPolicy.ALTER_INFOSET</code>.
+ * @deprecated
+ * @return <code>XmlViolationPolicy.ALTER_INFOSET</code>
+ */
+ public XmlViolationPolicy getBogusXmlnsPolicy() {
+ return XmlViolationPolicy.ALTER_INFOSET;
+ }
+
+ public void addCharacterHandler(CharacterHandler characterHandler) {
+ this.characterHandlers.add(characterHandler);
+ if (driver != null) {
+ driver.addCharacterHandler(characterHandler);
+ }
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/HtmlSerializer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/HtmlSerializer.java
new file mode 100644
index 000000000..3312398d5
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/HtmlSerializer.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2011 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.sax;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Arrays;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+public class HtmlSerializer implements ContentHandler, LexicalHandler {
+
+ private static final String[] VOID_ELEMENTS = { "area", "base", "basefont",
+ "bgsound", "br", "col", "command", "embed", "frame", "hr", "img",
+ "input", "keygen", "link", "meta", "param", "source", "track",
+ "wbr" };
+
+ private static final String[] NON_ESCAPING = { "iframe", "noembed",
+ "noframes", "noscript", "plaintext", "script", "style", "xmp" };
+
+ private static Writer wrap(OutputStream out) {
+ try {
+ return new OutputStreamWriter(out, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private int ignoreLevel = 0;
+
+ private int escapeLevel = 0;
+
+ private final Writer writer;
+
+ public HtmlSerializer(OutputStream out) {
+ this(wrap(out));
+ }
+
+ public HtmlSerializer(Writer out) {
+ this.writer = out;
+ }
+
+ public void characters(char[] ch, int start, int length)
+ throws SAXException {
+ try {
+ if (escapeLevel > 0) {
+ writer.write(ch, start, length);
+ } else {
+ for (int i = start; i < start + length; i++) {
+ char c = ch[i];
+ switch (c) {
+ case '<':
+ writer.write("&lt;");
+ break;
+ case '>':
+ writer.write("&gt;");
+ break;
+ case '&':
+ writer.write("&amp;");
+ break;
+ case '\u00A0':
+ writer.write("&nbsp;");
+ break;
+ default:
+ writer.write(c);
+ break;
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void endDocument() throws SAXException {
+ try {
+ writer.flush();
+ writer.close();
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ if (escapeLevel > 0) {
+ escapeLevel--;
+ }
+ if (ignoreLevel > 0) {
+ ignoreLevel--;
+ } else {
+ try {
+ writer.write('<');
+ writer.write('/');
+ writer.write(localName);
+ writer.write('>');
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+ }
+
+ public void ignorableWhitespace(char[] ch, int start, int length)
+ throws SAXException {
+ characters(ch, start, length);
+ }
+
+ public void processingInstruction(String target, String data)
+ throws SAXException {
+ }
+
+ public void setDocumentLocator(Locator locator) {
+ }
+
+ public void startDocument() throws SAXException {
+ try {
+ writer.write("<!DOCTYPE html>\n");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void startElement(String uri, String localName, String qName,
+ Attributes atts) throws SAXException {
+ if (escapeLevel > 0) {
+ escapeLevel++;
+ }
+ boolean xhtml = "http://www.w3.org/1999/xhtml".equals(uri);
+ if (ignoreLevel > 0
+ || !(xhtml || "http://www.w3.org/2000/svg".equals(uri) || "http://www.w3.org/1998/Math/MathML".equals(uri))) {
+ ignoreLevel++;
+ return;
+ }
+ try {
+ writer.write('<');
+ writer.write(localName);
+ for (int i = 0; i < atts.getLength(); i++) {
+ String attUri = atts.getURI(i);
+ String attLocal = atts.getLocalName(i);
+ if (attUri.length() == 0) {
+ writer.write(' ');
+ } else if (!xhtml
+ && "http://www.w3.org/1999/xlink".equals(attUri)) {
+ writer.write(" xlink:");
+ } else if ("http://www.w3.org/XML/1998/namespace".equals(attUri)) {
+ if (xhtml) {
+ if ("lang".equals(attLocal)) {
+ writer.write(' ');
+ } else {
+ continue;
+ }
+ } else {
+ writer.write(" xml:");
+ }
+ } else {
+ continue;
+ }
+ writer.write(atts.getLocalName(i));
+ writer.write('=');
+ writer.write('"');
+ String val = atts.getValue(i);
+ for (int j = 0; j < val.length(); j++) {
+ char c = val.charAt(j);
+ switch (c) {
+ case '"':
+ writer.write("&quot;");
+ break;
+ case '&':
+ writer.write("&amp;");
+ break;
+ case '\u00A0':
+ writer.write("&nbsp;");
+ break;
+ default:
+ writer.write(c);
+ break;
+ }
+ }
+ writer.write('"');
+ }
+ writer.write('>');
+ if (Arrays.binarySearch(VOID_ELEMENTS, localName) > -1) {
+ ignoreLevel++;
+ return;
+ }
+ if ("pre".equals(localName) || "textarea".equals(localName)
+ || "listing".equals(localName)) {
+ writer.write('\n');
+ }
+ if (escapeLevel == 0
+ && Arrays.binarySearch(NON_ESCAPING, localName) > -1) {
+ escapeLevel = 1;
+ }
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void comment(char[] ch, int start, int length) throws SAXException {
+ if (ignoreLevel > 0 || escapeLevel > 0) {
+ return;
+ }
+ try {
+ writer.write("<!--");
+ writer.write(ch, start, length);
+ writer.write("-->");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void endCDATA() throws SAXException {
+ }
+
+ public void endDTD() throws SAXException {
+ }
+
+ public void endEntity(String name) throws SAXException {
+ }
+
+ public void startCDATA() throws SAXException {
+ }
+
+ public void startDTD(String name, String publicId, String systemId)
+ throws SAXException {
+ }
+
+ public void startEntity(String name) throws SAXException {
+ }
+
+ public void startPrefixMapping(String prefix, String uri)
+ throws SAXException {
+ }
+
+ public void endPrefixMapping(String prefix) throws SAXException {
+ }
+
+ public void skippedEntity(String name) throws SAXException {
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/InfosetCoercingHtmlParser.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/InfosetCoercingHtmlParser.java
new file mode 100644
index 000000000..33e98dbe8
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/InfosetCoercingHtmlParser.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.sax;
+
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+
+/**
+ * This subclass of <code>HtmlParser</code> simply provides a no-argument
+ * constructor that calls the constructor of the superclass with the
+ * <code>ALTER_INFOSET</code> policy. This is convenient when another Java
+ * component wants an implementation of <code>XMLReader</code> with a
+ * no-argument constructor and infoset coercion is the wanted behavior.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public class InfosetCoercingHtmlParser extends HtmlParser {
+
+ /**
+ * A constructor that passes <code>ALTER_INFOSET</code> to the superclass'
+ * constructor.
+ */
+ public InfosetCoercingHtmlParser() {
+ super(XmlViolationPolicy.ALTER_INFOSET);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/NameCheckingXmlSerializer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/NameCheckingXmlSerializer.java
new file mode 100644
index 000000000..b6cb2f872
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/NameCheckingXmlSerializer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.sax;
+
+import java.io.OutputStream;
+import java.io.Writer;
+
+import nu.validator.htmlparser.impl.NCName;
+
+import org.xml.sax.SAXException;
+
+public class NameCheckingXmlSerializer extends XmlSerializer {
+
+ public NameCheckingXmlSerializer(OutputStream out) {
+ super(out);
+ }
+
+ public NameCheckingXmlSerializer(Writer out) {
+ super(out);
+ }
+
+ /**
+ * @see nu.validator.htmlparser.sax.XmlSerializer#checkNCName()
+ */
+ @Override protected void checkNCName(String name) throws SAXException {
+ if (!NCName.isNCName(name)) {
+ throw new SAXException("Not an XML 1.0 4th ed. NCName: " + name);
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/SAXStreamer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/SAXStreamer.java
new file mode 100644
index 000000000..d2dc60bdb
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/SAXStreamer.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.sax;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.ext.LexicalHandler;
+
+import nu.validator.htmlparser.impl.HtmlAttributes;
+import nu.validator.htmlparser.impl.TreeBuilder;
+
+class SAXStreamer extends TreeBuilder<Attributes>{
+
+ private ContentHandler contentHandler = null;
+ private LexicalHandler lexicalHandler = null;
+
+ SAXStreamer() {
+ super();
+ }
+
+ @Override
+ protected void addAttributesToElement(Attributes element, HtmlAttributes attributes) throws SAXException {
+ Attributes existingAttrs = element;
+ for (int i = 0; i < attributes.getLength(); i++) {
+ String qName = attributes.getQNameNoBoundsCheck(i);
+ if (existingAttrs.getIndex(qName) < 0) {
+ fatal();
+ }
+ }
+ }
+
+ @Override
+ protected void appendCharacters(Attributes parent, char[] buf, int start, int length) throws SAXException {
+ contentHandler.characters(buf, start, length);
+ }
+
+ @Override
+ protected void appendChildrenToNewParent(Attributes oldParent, Attributes newParent) throws SAXException {
+ fatal();
+ }
+
+ @Override
+ protected void appendComment(Attributes parent, char[] buf, int start, int length) throws SAXException {
+ if (lexicalHandler != null) {
+ lexicalHandler.comment(buf, start, length);
+ }
+ }
+
+ @Override
+ protected void appendCommentToDocument(char[] buf, int start, int length)
+ throws SAXException {
+ if (lexicalHandler != null) {
+ lexicalHandler.comment(buf, start, length);
+ }
+ }
+
+ @Override
+ protected Attributes createElement(String ns, String name, HtmlAttributes attributes, Attributes intendedParent) throws SAXException {
+ return attributes;
+ }
+
+ @Override
+ protected Attributes createHtmlElementSetAsRoot(HtmlAttributes attributes) throws SAXException {
+ return attributes;
+ }
+
+ @Override
+ protected void detachFromParent(Attributes element) throws SAXException {
+ fatal();
+ }
+
+ @Override
+ protected void appendElement(Attributes child, Attributes newParent) throws SAXException {
+ }
+
+ @Override
+ protected boolean hasChildren(Attributes element) throws SAXException {
+ return false;
+ }
+
+ public void setContentHandler(ContentHandler handler) {
+ contentHandler = handler;
+ }
+
+ public void setLexicalHandler(LexicalHandler handler) {
+ lexicalHandler = handler;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#appendDoctypeToDocument(java.lang.String, java.lang.String, java.lang.String)
+ */
+ @Override
+ protected void appendDoctypeToDocument(String name, String publicIdentifier, String systemIdentifier) throws SAXException {
+ if (lexicalHandler != null) {
+ lexicalHandler.startDTD(name, publicIdentifier, systemIdentifier);
+ lexicalHandler.endDTD();
+ }
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#elementPopped(String, java.lang.String, java.lang.Object)
+ */
+ @Override
+ protected void elementPopped(String ns, String name, Attributes node) throws SAXException {
+ contentHandler.endElement(ns, name, name);
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#elementPushed(String, java.lang.String, java.lang.Object)
+ */
+ @Override
+ protected void elementPushed(String ns, String name, Attributes node) throws SAXException {
+ contentHandler.startElement(ns, name, name, node);
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#end()
+ */
+ @Override
+ protected void end() throws SAXException {
+ contentHandler.endDocument();
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#start()
+ */
+ @Override
+ protected void start(boolean fragment) throws SAXException {
+ contentHandler.setDocumentLocator(tokenizer);
+ if (!fragment) {
+ contentHandler.startDocument();
+ }
+ }
+
+ protected void fatal() throws SAXException {
+ SAXParseException spe = new SAXParseException(
+ "Cannot recover after last error. Any further errors will be ignored.",
+ tokenizer);
+ if (errorHandler != null) {
+ errorHandler.fatalError(spe);
+ }
+ throw spe;
+ }
+
+ @Override
+ protected Attributes createAndInsertFosterParentedElement(String ns, String name,
+ HtmlAttributes attributes, Attributes table, Attributes stackParent) throws SAXException {
+ fatal();
+ throw new RuntimeException("Unreachable");
+ }
+
+ @Override protected void insertFosterParentedCharacters(char[] buf,
+ int start, int length, Attributes table, Attributes stackParent)
+ throws SAXException {
+ fatal();
+ }
+
+ @Override protected void insertFosterParentedChild(Attributes child,
+ Attributes table, Attributes stackParent) throws SAXException {
+ fatal();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/SAXTreeBuilder.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/SAXTreeBuilder.java
new file mode 100644
index 000000000..3e099b579
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/SAXTreeBuilder.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.sax;
+
+import org.xml.sax.SAXException;
+
+import nu.validator.htmlparser.impl.HtmlAttributes;
+import nu.validator.htmlparser.impl.TreeBuilder;
+import nu.validator.saxtree.Characters;
+import nu.validator.saxtree.Comment;
+import nu.validator.saxtree.DTD;
+import nu.validator.saxtree.Document;
+import nu.validator.saxtree.DocumentFragment;
+import nu.validator.saxtree.Element;
+import nu.validator.saxtree.Node;
+import nu.validator.saxtree.ParentNode;
+
+class SAXTreeBuilder extends TreeBuilder<Element> {
+
+ private Document document;
+
+ private Node cachedTable = null;
+
+ private Node cachedTablePreviousSibling = null;
+
+ SAXTreeBuilder() {
+ super();
+ }
+
+ @Override
+ protected void appendComment(Element parent, char[] buf, int start, int length) {
+ parent.appendChild(new Comment(tokenizer, buf, start, length));
+ }
+
+ @Override
+ protected void appendCommentToDocument(char[] buf, int start, int length) {
+ document.appendChild(new Comment(tokenizer, buf, start, length));
+ }
+
+ @Override
+ protected void appendCharacters(Element parent, char[] buf, int start, int length) {
+ parent.appendChild(new Characters(tokenizer, buf, start, length));
+ }
+
+ @Override
+ protected boolean hasChildren(Element element) {
+ return element.getFirstChild() != null;
+ }
+
+ @Override
+ protected void appendElement(Element child, Element newParent) {
+ newParent.appendChild(child);
+ }
+
+ @Override
+ protected Element createHtmlElementSetAsRoot(HtmlAttributes attributes) {
+ Element newElt = new Element(tokenizer, "http://www.w3.org/1999/xhtml", "html", "html", attributes, true, null);
+ document.appendChild(newElt);
+ return newElt;
+ }
+
+ @Override
+ protected void addAttributesToElement(Element element, HtmlAttributes attributes) throws SAXException {
+ HtmlAttributes existingAttrs = (HtmlAttributes) element.getAttributes();
+ existingAttrs.merge(attributes);
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#appendDoctypeToDocument(java.lang.String, java.lang.String, java.lang.String)
+ */
+ @Override
+ protected void appendDoctypeToDocument(String name, String publicIdentifier, String systemIdentifier) {
+ DTD dtd = new DTD(tokenizer, name, publicIdentifier, systemIdentifier);
+ dtd.setEndLocator(tokenizer);
+ document.appendChild(dtd);
+ }
+
+ /**
+ * Returns the document.
+ *
+ * @return the document
+ */
+ Document getDocument() {
+ Document rv = document;
+ document = null;
+ return rv;
+ }
+
+ DocumentFragment getDocumentFragment() {
+ DocumentFragment rv = new DocumentFragment();
+ rv.appendChildren(document.getFirstChild());
+ document = null;
+ return rv;
+ }
+
+ /**
+ * @throws SAXException
+ * @see nu.validator.htmlparser.impl.TreeBuilder#end()
+ */
+ @Override
+ protected void end() throws SAXException {
+ document.setEndLocator(tokenizer);
+ cachedTable = null;
+ cachedTablePreviousSibling = null;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#start()
+ */
+ @Override
+ protected void start(boolean fragment) {
+ document = new Document(tokenizer);
+ cachedTable = null;
+ cachedTablePreviousSibling = null;
+ }
+
+ @Override
+ protected void appendChildrenToNewParent(Element oldParent, Element newParent) throws SAXException {
+ newParent.appendChildren(oldParent);
+ }
+
+ @Override
+ protected Element createElement(String ns, String name, HtmlAttributes attributes,
+ Element intendedParent) throws SAXException {
+ return new Element(tokenizer, ns, name, name, attributes, true, null);
+ }
+
+ @Override
+ protected Element createAndInsertFosterParentedElement(String ns, String name,
+ HtmlAttributes attributes, Element table, Element stackParent) throws SAXException {
+ ParentNode parent = table.getParentNode();
+ Element child = createElement(ns, name, attributes, parent != null ? (Element) parent : stackParent);
+ if (parent != null) { // always an element if not null
+ parent.insertBetween(child, previousSibling(table), table);
+ cachedTablePreviousSibling = child;
+ } else {
+ stackParent.appendChild(child);
+ }
+
+ return child;
+ }
+
+ @Override protected void insertFosterParentedCharacters(char[] buf,
+ int start, int length, Element table, Element stackParent) throws SAXException {
+ Node child = new Characters(tokenizer, buf, start, length);
+ ParentNode parent = table.getParentNode();
+ if (parent != null) { // always an element if not null
+ parent.insertBetween(child, previousSibling(table), table);
+ cachedTablePreviousSibling = child;
+ } else {
+ stackParent.appendChild(child);
+ }
+ }
+
+ @Override protected void insertFosterParentedChild(Element child,
+ Element table, Element stackParent) throws SAXException {
+ ParentNode parent = table.getParentNode();
+ if (parent != null) { // always an element if not null
+ parent.insertBetween(child, previousSibling(table), table);
+ cachedTablePreviousSibling = child;
+ } else {
+ stackParent.appendChild(child);
+ }
+ }
+
+ private Node previousSibling(Node table) {
+ if (table == cachedTable) {
+ return cachedTablePreviousSibling;
+ } else {
+ cachedTable = table;
+ return (cachedTablePreviousSibling = table.getPreviousSibling());
+ }
+ }
+
+ @Override protected void detachFromParent(Element element)
+ throws SAXException {
+ element.detach();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/XmlSerializer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/XmlSerializer.java
new file mode 100644
index 000000000..5dccf5d3a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/XmlSerializer.java
@@ -0,0 +1,737 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.sax;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+public class XmlSerializer implements ContentHandler, LexicalHandler {
+
+ private final class PrefixMapping {
+ public final String uri;
+
+ public final String prefix;
+
+ /**
+ * @param uri
+ * @param prefix
+ */
+ public PrefixMapping(String uri, String prefix) {
+ this.uri = uri;
+ this.prefix = prefix;
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override public final boolean equals(Object obj) {
+ if (obj instanceof PrefixMapping) {
+ PrefixMapping other = (PrefixMapping) obj;
+ return this.prefix.equals(other.prefix);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ @Override public final int hashCode() {
+ return prefix.hashCode();
+ }
+
+ }
+
+ private final class StackNode {
+ public final String uri;
+
+ public final String prefix;
+
+ public final String qName;
+
+ public final Set<PrefixMapping> mappings = new HashSet<PrefixMapping>();
+
+ /**
+ * @param uri
+ * @param qName
+ */
+ public StackNode(String uri, String qName, String prefix) {
+ this.uri = uri;
+ this.qName = qName;
+ this.prefix = prefix;
+ }
+ }
+
+ private final static Map<String, String> WELL_KNOWN_ATTRIBUTE_PREFIXES = new HashMap<String, String>();
+
+ static {
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("adobe:ns:meta/", "x");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd",
+ "sodipodi");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://ns.adobe.com/AdobeIllustrator/10.0/", "i");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/", "a");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://ns.adobe.com/Extensibility/1.0/", "x");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://ns.adobe.com/illustrator/1.0/", "illustrator");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://ns.adobe.com/pdf/1.3/", "pdf");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://ns.adobe.com/photoshop/1.0/",
+ "photoshop");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://ns.adobe.com/tiff/1.0/",
+ "tiff");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://ns.adobe.com/xap/1.0/", "xap");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://ns.adobe.com/xap/1.0/g/",
+ "xapG");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://ns.adobe.com/xap/1.0/mm/",
+ "xapMM");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://ns.adobe.com/xap/1.0/rights/", "xapRights");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://ns.adobe.com/xap/1.0/sType/Dimensions#", "stDim");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://ns.adobe.com/xap/1.0/sType/ResourceRef#", "stRef");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://ns.adobe.com/xap/1.0/t/pg/",
+ "xapTPg");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://purl.org/dc/elements/1.1/",
+ "dc");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://schemas.microsoft.com/visio/2003/SVGExtensions/", "v");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
+ "sodipodi");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://w3.org/1999/xlink", "xlink");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://www.carto.net/attrib/",
+ "attrib");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://www.iki.fi/pav/software/textext/", "textext");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://www.inkscape.org/namespaces/inkscape", "inkscape");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://www.justsystem.co.jp/hanako13/svg", "jsh");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://www.w3.org/1999/xlink",
+ "xlink");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put(
+ "http://www.w3.org/2001/XMLSchema-instance", "xsi");
+ WELL_KNOWN_ATTRIBUTE_PREFIXES.put("http://www.w3.org/1999/xlink",
+ "xlink");
+ }
+
+ private final static Map<String, String> WELL_KNOWN_ELEMENT_PREFIXES = new HashMap<String, String>();
+
+ static {
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://www.w3.org/1999/XSL/Transform",
+ "xsl");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "rdf");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://purl.org/dc/elements/1.1/",
+ "dc");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://www.w3.org/2001/XMLSchema-instance", "xsi");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://www.ascc.net/xml/schematron",
+ "sch");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://purl.oclc.org/dsdl/schematron",
+ "sch");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://www.inkscape.org/namespaces/inkscape", "inkscape");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
+ "sodipodi");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/", "a");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://ns.adobe.com/AdobeIllustrator/10.0/", "i");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("adobe:ns:meta/", "x");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/xap/1.0/", "xap");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/pdf/1.3/", "pdf");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/tiff/1.0/", "tiff");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://creativecommons.org/ns#", "cc");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd",
+ "sodipodi");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/", "Iptc4xmpCore");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/exif/1.0/", "exif");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://ns.adobe.com/Extensibility/1.0/", "x");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/illustrator/1.0/",
+ "illustrator");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/pdfx/1.3/", "pdfx");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/photoshop/1.0/",
+ "photoshop");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/Variables/1.0/",
+ "v");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/xap/1.0/g/",
+ "xapG");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/xap/1.0/g/img/",
+ "xapGImg");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/xap/1.0/mm/",
+ "xapMM");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/xap/1.0/rights/",
+ "xapRights");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://ns.adobe.com/xap/1.0/sType/Dimensions#", "stDim");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://ns.adobe.com/xap/1.0/sType/Font#", "stFnt");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://ns.adobe.com/xap/1.0/sType/ResourceRef#", "stRef");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://ns.adobe.com/xap/1.0/t/pg/",
+ "xapTPg");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://product.corel.com/CGS/11/cddns/", "odm");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://schemas.microsoft.com/visio/2003/SVGExtensions/", "v");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://web.resource.org/cc/", "cc");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://www.freesoftware.fsf.org/bkchem/cdml", "cdml");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://www.opengis.net/gml", "gml");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://www.svgmaker.com/svgns",
+ "svgmaker");
+ WELL_KNOWN_ELEMENT_PREFIXES.put(
+ "http://www.w3.org/2000/01/rdf-schema#", "rdfs");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://xmlns.com/foaf/0.1/", "foaf");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://www.xml-cml.org/schema/stmml",
+ "stm");
+ WELL_KNOWN_ELEMENT_PREFIXES.put("http://www.iupac.org/foo/ichi", "ichi");
+ }
+
+ private final static Writer wrap(OutputStream out) {
+ Charset charset = Charset.forName("utf-8");
+ CharsetEncoder encoder = charset.newEncoder();
+ encoder.onMalformedInput(CodingErrorAction.REPLACE);
+ encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+ try {
+ encoder.replaceWith("\uFFFD".getBytes("utf-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ return new OutputStreamWriter(out, encoder);
+ }
+
+ // grows from head
+ private final LinkedList<StackNode> stack = new LinkedList<StackNode>();
+
+ private final Writer writer;
+
+ public XmlSerializer(OutputStream out) {
+ this(wrap(out));
+ }
+
+ public XmlSerializer(Writer out) {
+ this.writer = out;
+ }
+
+ protected void checkNCName(String name) throws SAXException {
+
+ }
+
+ private final void push(String uri, String local, String prefix) {
+ stack.addFirst(new StackNode(uri, local, prefix));
+ }
+
+ private final String pop() {
+ String rv = stack.removeFirst().qName;
+ stack.getFirst().mappings.clear();
+ return rv;
+ }
+
+ private final String lookupPrefixAttribute(String ns) {
+ if ("http://www.w3.org/XML/1998/namespace".equals(ns)) {
+ return "xml";
+ }
+ Set<String> hidden = new HashSet<String>();
+ for (StackNode node : stack) {
+ for (PrefixMapping mapping : node.mappings) {
+ if (mapping.prefix.length() != 0 && mapping.uri.equals(ns)
+ && !hidden.contains(mapping.prefix)) {
+ return mapping.prefix;
+ }
+ hidden.add(mapping.prefix);
+ }
+ }
+ return null;
+ }
+
+ private final String lookupUri(String prefix) {
+ for (StackNode node : stack) {
+ for (PrefixMapping mapping : node.mappings) {
+ if (mapping.prefix.equals(prefix)) {
+ return mapping.uri;
+ }
+ }
+ }
+ return null;
+ }
+
+ private final boolean xmlNsQname(String name) {
+ if (name == null) {
+ return false;
+ } else if ("xmlns".equals(name)) {
+ return true;
+ } else if (name.startsWith("xmlns:")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private final void writeAttributeValue(String val) throws IOException {
+ boolean prevWasSpace = true;
+ int last = val.length() - 1;
+ for (int i = 0; i <= last; i++) {
+ char c = val.charAt(i);
+ switch (c) {
+ case '<':
+ writer.write("&lt;");
+ prevWasSpace = false;
+ break;
+ case '>':
+ writer.write("&gt;");
+ prevWasSpace = false;
+ break;
+ case '&':
+ writer.write("&amp;");
+ prevWasSpace = false;
+ break;
+ case '"':
+ writer.write("&quot;");
+ prevWasSpace = false;
+ break;
+ case '\r':
+ writer.write("&#xD;");
+ prevWasSpace = false;
+ break;
+ case '\t':
+ writer.write("&#x9;");
+ prevWasSpace = false;
+ break;
+ case '\n':
+ writer.write("&#xA;");
+ prevWasSpace = false;
+ break;
+ case ' ':
+ if (prevWasSpace || i == last) {
+ writer.write("&#x20;");
+ prevWasSpace = false;
+ } else {
+ writer.write(' ');
+ prevWasSpace = true;
+ }
+ break;
+ case '\uFFFE':
+ writer.write('\uFFFD');
+ prevWasSpace = false;
+ break;
+ case '\uFFFF':
+ writer.write('\uFFFD');
+ prevWasSpace = false;
+ break;
+ default:
+ if (c < ' ') {
+ writer.write('\uFFFD');
+ } else {
+ writer.write(c);
+ }
+ prevWasSpace = false;
+ break;
+ }
+ }
+ }
+
+ private final void generatePrefix(String uri) throws SAXException {
+ int counter = 0;
+ String candidate = WELL_KNOWN_ATTRIBUTE_PREFIXES.get(uri);
+ if (candidate == null) {
+ candidate = "p" + (counter++);
+ }
+ while (lookupUri(candidate) != null) {
+ candidate = "p" + (counter++);
+ }
+ startPrefixMappingPrivate(candidate, uri);
+ }
+
+ public final void characters(char[] ch, int start, int length)
+ throws SAXException {
+ try {
+ for (int i = start; i < start + length; i++) {
+ char c = ch[i];
+ switch (c) {
+ case '<':
+ writer.write("&lt;");
+ break;
+ case '>':
+ writer.write("&gt;");
+ break;
+ case '&':
+ writer.write("&amp;");
+ break;
+ case '\r':
+ writer.write("&#xD;");
+ break;
+ case '\t':
+ writer.write('\t');
+ break;
+ case '\n':
+ writer.write('\n');
+ break;
+ case '\uFFFE':
+ writer.write('\uFFFD');
+ break;
+ case '\uFFFF':
+ writer.write('\uFFFD');
+ break;
+ default:
+ if (c < ' ') {
+ writer.write('\uFFFD');
+ } else {
+ writer.write(c);
+ }
+ break;
+ }
+ }
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public final void endDocument() throws SAXException {
+ try {
+ stack.clear();
+ writer.flush();
+ writer.close();
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public final void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ try {
+ writer.write('<');
+ writer.write('/');
+ writer.write(pop());
+ writer.write('>');
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public final void ignorableWhitespace(char[] ch, int start, int length)
+ throws SAXException {
+ characters(ch, start, length);
+ }
+
+ public final void processingInstruction(String target, String data)
+ throws SAXException {
+ try {
+ checkNCName(target);
+ writer.write("<?");
+ writer.write(target);
+ writer.write(' ');
+ boolean prevWasQuestionmark = false;
+ for (int i = 0; i < data.length(); i++) {
+ char c = data.charAt(i);
+ switch (c) {
+ case '?':
+ writer.write('?');
+ prevWasQuestionmark = true;
+ break;
+ case '>':
+ if (prevWasQuestionmark) {
+ writer.write(" >");
+ } else {
+ writer.write('>');
+ }
+ prevWasQuestionmark = false;
+ break;
+ case '\t':
+ writer.write('\t');
+ prevWasQuestionmark = false;
+ break;
+ case '\r':
+ case '\n':
+ writer.write('\n');
+ prevWasQuestionmark = false;
+ break;
+ case '\uFFFE':
+ writer.write('\uFFFD');
+ prevWasQuestionmark = false;
+ break;
+ case '\uFFFF':
+ writer.write('\uFFFD');
+ prevWasQuestionmark = false;
+ break;
+ default:
+ if (c < ' ') {
+ writer.write('\uFFFD');
+ } else {
+ writer.write(c);
+ }
+ prevWasQuestionmark = false;
+ break;
+ }
+ }
+ writer.write("?>");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public final void setDocumentLocator(Locator locator) {
+ }
+
+ public final void startDocument() throws SAXException {
+ try {
+ writer.write("<?xml version='1.0' encoding='utf-8'?>\n");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ stack.clear();
+ push(null, null, null);
+ }
+
+ public final void startElement(String uri, String localName, String q,
+ Attributes atts) throws SAXException {
+ checkNCName(localName);
+ String prefix;
+ String qName;
+ if (uri.length() == 0) {
+ prefix = "";
+ qName = localName;
+ // generate xmlns
+ startPrefixMappingPrivate(prefix, uri);
+ } else {
+ prefix = WELL_KNOWN_ELEMENT_PREFIXES.get(uri);
+ if (prefix == null) {
+ prefix = "";
+ }
+ String lookup = lookupUri(prefix);
+ if (lookup != null && !lookup.equals(uri)) {
+ prefix = "";
+ }
+ startPrefixMappingPrivate(prefix, uri);
+ if (prefix.length() == 0) {
+ qName = localName;
+ } else {
+ qName = prefix + ':' + localName;
+ }
+ }
+
+ int attLen = atts.getLength();
+ for (int i = 0; i < attLen; i++) {
+ String attUri = atts.getURI(i);
+ if (attUri.length() == 0
+ || "http://www.w3.org/XML/1998/namespace".equals(attUri)
+ || "http://www.w3.org/2000/xmlns/".equals(attUri)
+ || atts.getLocalName(i).length() == 0
+ || xmlNsQname(atts.getQName(i))) {
+ continue;
+ }
+ if (lookupPrefixAttribute(attUri) == null) {
+ generatePrefix(attUri);
+ }
+ }
+
+ try {
+ writer.write('<');
+ writer.write(qName);
+ for (PrefixMapping mapping : stack.getFirst().mappings) {
+ writer.write(' ');
+ if (mapping.prefix.length() == 0) {
+ writer.write("xmlns");
+ } else {
+ writer.write("xmlns:");
+ writer.write(mapping.prefix);
+ }
+ writer.write('=');
+ writer.write('"');
+ writeAttributeValue(mapping.uri);
+ writer.write('"');
+ }
+
+ for (int i = 0; i < attLen; i++) {
+ String attUri = atts.getURI(i);
+ if ("http://www.w3.org/XML/1998/namespace".equals(attUri)
+ || "http://www.w3.org/2000/xmlns/".equals(attUri)
+ || atts.getLocalName(i).length() == 0
+ || xmlNsQname(atts.getQName(i))) {
+ continue;
+ }
+ writer.write(' ');
+ if (attUri.length() != 0) {
+ writer.write(lookupPrefixAttribute(attUri));
+ writer.write(':');
+ }
+ String attLocal = atts.getLocalName(i);
+ checkNCName(attLocal);
+ writer.write(attLocal);
+ writer.write('=');
+ writer.write('"');
+ writeAttributeValue(atts.getValue(i));
+ writer.write('"');
+ }
+ writer.write('>');
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ push(uri, qName, prefix);
+ }
+
+ public final void comment(char[] ch, int start, int length) throws SAXException {
+ try {
+ boolean prevWasHyphen = false;
+ writer.write("<!--");
+ for (int i = start; i < start + length; i++) {
+ char c = ch[i];
+ switch (c) {
+ case '-':
+ if (prevWasHyphen) {
+ writer.write(" -");
+ } else {
+ writer.write('-');
+ prevWasHyphen = true;
+ }
+ break;
+ case '\t':
+ writer.write('\t');
+ prevWasHyphen = false;
+ break;
+ case '\r':
+ case '\n':
+ writer.write('\n');
+ prevWasHyphen = false;
+ break;
+ case '\uFFFE':
+ writer.write('\uFFFD');
+ prevWasHyphen = false;
+ break;
+ case '\uFFFF':
+ writer.write('\uFFFD');
+ prevWasHyphen = false;
+ break;
+ default:
+ if (c < ' ') {
+ writer.write('\uFFFD');
+ } else {
+ writer.write(c);
+ }
+ prevWasHyphen = false;
+ break;
+ }
+ }
+ if (prevWasHyphen) {
+ writer.write(' ');
+ }
+ writer.write("-->");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public final void endCDATA() throws SAXException {
+ }
+
+ public final void endDTD() throws SAXException {
+ }
+
+ public final void endEntity(String name) throws SAXException {
+ }
+
+ public final void startCDATA() throws SAXException {
+ }
+
+ public final void startDTD(String name, String publicId, String systemId)
+ throws SAXException {
+ }
+
+ public final void startEntity(String name) throws SAXException {
+ }
+
+ public final void startPrefixMapping(String prefix, String uri)
+ throws SAXException {
+ if (prefix.length() == 0 || uri.equals(lookupUri(prefix))) {
+ return;
+ }
+ if (uri.equals(lookupUri(prefix))) {
+ return;
+ }
+ if ("http://www.w3.org/XML/1998/namespace".equals(uri)) {
+ if ("xml".equals(prefix)) {
+ return;
+ } else {
+ throw new SAXException("Attempt to declare a reserved NS uri.");
+ }
+ }
+ if ("http://www.w3.org/2000/xmlns/".equals(uri)) {
+ throw new SAXException("Attempt to declare a reserved NS uri.");
+ }
+ if (uri.length() == 0 && prefix.length() != 0) {
+ throw new SAXException("Can bind a prefix to no namespace.");
+ }
+ checkNCName(prefix);
+ Set<PrefixMapping> theSet = stack.getFirst().mappings;
+ PrefixMapping mapping = new PrefixMapping(uri, prefix);
+ if (theSet.contains(mapping)) {
+ throw new SAXException(
+ "Attempt to map one prefix to two URIs on one element.");
+ }
+ theSet.add(mapping);
+ }
+
+ public final void startPrefixMappingPrivate(String prefix, String uri)
+ throws SAXException {
+ if (uri.equals(lookupUri(prefix))) {
+ return;
+ }
+ stack.getFirst().mappings.add(new PrefixMapping(uri, prefix));
+ }
+
+ public final void endPrefixMapping(String prefix) throws SAXException {
+ }
+
+ public final void skippedEntity(String name) throws SAXException {
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/package.html b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/package.html
new file mode 100644
index 000000000..60532962f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/sax/package.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head><title>Package Overview</title>
+<!--
+ Copyright (c) 2007 Henri Sivonen
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+-->
+</head>
+<body bgcolor="white">
+<p>This package provides an HTML5 parser that exposes the document through the SAX API.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/FormPointer.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/FormPointer.java
new file mode 100644
index 000000000..6dcff5600
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/FormPointer.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.xom;
+
+import nu.xom.Element;
+
+/**
+ * Interface for elements that have an associated form pointer.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public interface FormPointer {
+
+ /**
+ * Returns the form.
+ *
+ * @return the form
+ */
+ public abstract Element getForm();
+
+ /**
+ * Sets the form.
+ *
+ * @param form the form to set
+ */
+ public abstract void setForm(Element form);
+
+} \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/FormPtrElement.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/FormPtrElement.java
new file mode 100644
index 000000000..2e2e18df7
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/FormPtrElement.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.xom;
+
+import nu.xom.Element;
+
+/**
+ * Element with an associated form.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public class FormPtrElement extends Element implements FormPointer {
+
+ private Element form = null;
+
+ /**
+ * Copy constructor (<code>FormPointer</code>-aware).
+ * @param elt
+ */
+ public FormPtrElement(Element elt) {
+ super(elt);
+ if (elt instanceof FormPointer) {
+ FormPointer other = (FormPointer) elt;
+ this.setForm(other.getForm());
+ }
+ }
+
+ /**
+ * Null form.
+ *
+ * @param name
+ * @param uri
+ */
+ public FormPtrElement(String name, String uri) {
+ super(name, uri);
+ }
+
+ /**
+ * Full constructor.
+ *
+ * @param name
+ * @param uri
+ * @param form
+ */
+ public FormPtrElement(String name, String uri, Element form) {
+ super(name, uri);
+ this.form = form;
+ }
+
+ /**
+ * Gets the form.
+ * @see nu.validator.htmlparser.xom.FormPointer#getForm()
+ */
+ public Element getForm() {
+ return form;
+ }
+
+ /**
+ * Sets the form.
+ * @see nu.validator.htmlparser.xom.FormPointer#setForm(nu.xom.Element)
+ */
+ public void setForm(Element form) {
+ this.form = form;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/HtmlBuilder.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/HtmlBuilder.java
new file mode 100644
index 000000000..845ea15cf
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/HtmlBuilder.java
@@ -0,0 +1,773 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.xom;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+
+import nu.validator.htmlparser.common.CharacterHandler;
+import nu.validator.htmlparser.common.DoctypeExpectation;
+import nu.validator.htmlparser.common.DocumentModeHandler;
+import nu.validator.htmlparser.common.Heuristics;
+import nu.validator.htmlparser.common.TokenHandler;
+import nu.validator.htmlparser.common.TransitionHandler;
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.impl.ErrorReportingTokenizer;
+import nu.validator.htmlparser.impl.Tokenizer;
+import nu.validator.htmlparser.io.Driver;
+import nu.xom.Builder;
+import nu.xom.Document;
+import nu.xom.Nodes;
+import nu.xom.ParsingException;
+import nu.xom.ValidityException;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * This class implements an HTML5 parser that exposes data through the XOM
+ * interface.
+ *
+ * <p>By default, when using the constructor without arguments, the
+ * this parser coerces XML 1.0-incompatible infosets into XML 1.0-compatible
+ * infosets. This corresponds to <code>ALTER_INFOSET</code> as the general
+ * XML violation policy. It is possible to treat XML 1.0 infoset violations
+ * as fatal by setting the general XML violation policy to <code>FATAL</code>.
+ *
+ * <p>The doctype is not represented in the tree.
+ *
+ * <p>The document mode is represented via the <code>Mode</code>
+ * interface on the <code>Document</code> node if the node implements
+ * that interface (depends on the used node factory).
+ *
+ * <p>The form pointer is stored if the node factory supports storing it.
+ *
+ * <p>This package has its own node factory class because the official
+ * XOM node factory may return multiple nodes instead of one confusing
+ * the assumptions of the DOM-oriented HTML5 parsing algorithm.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public class HtmlBuilder extends Builder {
+
+ private Driver driver;
+
+ private final XOMTreeBuilder treeBuilder;
+
+ private final SimpleNodeFactory simpleNodeFactory;
+
+ private EntityResolver entityResolver;
+
+ private ErrorHandler errorHandler = null;
+
+ private DocumentModeHandler documentModeHandler = null;
+
+ private DoctypeExpectation doctypeExpectation = DoctypeExpectation.HTML;
+
+ private boolean checkingNormalization = false;
+
+ private boolean scriptingEnabled = false;
+
+ private final List<CharacterHandler> characterHandlers = new LinkedList<CharacterHandler>();
+
+ private XmlViolationPolicy contentSpacePolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy contentNonXmlCharPolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy commentPolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy namePolicy = XmlViolationPolicy.FATAL;
+
+ private XmlViolationPolicy streamabilityViolationPolicy = XmlViolationPolicy.ALLOW;
+
+ private boolean html4ModeCompatibleWithXhtml1Schemata = false;
+
+ private boolean mappingLangToXmlLang = false;
+
+ private XmlViolationPolicy xmlnsPolicy = XmlViolationPolicy.FATAL;
+
+ private boolean reportingDoctype = true;
+
+ private ErrorHandler treeBuilderErrorHandler = null;
+
+ private Heuristics heuristics = Heuristics.NONE;
+
+ private TransitionHandler transitionHandler = null;
+
+ /**
+ * Constructor with default node factory and fatal XML violation policy.
+ */
+ public HtmlBuilder() {
+ this(new SimpleNodeFactory(), XmlViolationPolicy.FATAL);
+ }
+
+ /**
+ * Constructor with given node factory and fatal XML violation policy.
+ * @param nodeFactory the factory
+ */
+ public HtmlBuilder(SimpleNodeFactory nodeFactory) {
+ this(nodeFactory, XmlViolationPolicy.FATAL);
+ }
+
+ /**
+ * Constructor with default node factory and given XML violation policy.
+ * @param xmlPolicy the policy
+ */
+ public HtmlBuilder(XmlViolationPolicy xmlPolicy) {
+ this(new SimpleNodeFactory(), xmlPolicy);
+ }
+
+ /**
+ * Constructor with given node factory and given XML violation policy.
+ * @param nodeFactory the factory
+ * @param xmlPolicy the policy
+ */
+ public HtmlBuilder(SimpleNodeFactory nodeFactory, XmlViolationPolicy xmlPolicy) {
+ super();
+ this.simpleNodeFactory = nodeFactory;
+ this.treeBuilder = new XOMTreeBuilder(nodeFactory);
+ this.driver = null;
+ setXmlPolicy(xmlPolicy);
+ }
+
+ private Tokenizer newTokenizer(TokenHandler handler, boolean newAttributesEachTime) {
+ if (errorHandler == null && transitionHandler == null
+ && contentNonXmlCharPolicy == XmlViolationPolicy.ALLOW) {
+ return new Tokenizer(handler, newAttributesEachTime);
+ } else {
+ return new ErrorReportingTokenizer(handler, newAttributesEachTime);
+ }
+ }
+
+ /**
+ * This class wraps different tree builders depending on configuration. This
+ * method does the work of hiding this from the user of the class.
+ */
+ private void lazyInit() {
+ if (driver == null) {
+ this.driver = new Driver(newTokenizer(treeBuilder, false));
+ this.driver.setErrorHandler(errorHandler);
+ this.driver.setTransitionHandler(transitionHandler);
+ this.treeBuilder.setErrorHandler(treeBuilderErrorHandler);
+ this.driver.setCheckingNormalization(checkingNormalization);
+ this.driver.setCommentPolicy(commentPolicy);
+ this.driver.setContentNonXmlCharPolicy(contentNonXmlCharPolicy);
+ this.driver.setContentSpacePolicy(contentSpacePolicy);
+ this.driver.setHtml4ModeCompatibleWithXhtml1Schemata(html4ModeCompatibleWithXhtml1Schemata);
+ this.driver.setMappingLangToXmlLang(mappingLangToXmlLang);
+ this.driver.setXmlnsPolicy(xmlnsPolicy);
+ this.driver.setHeuristics(heuristics);
+ for (CharacterHandler characterHandler : characterHandlers) {
+ this.driver.addCharacterHandler(characterHandler);
+ }
+ this.treeBuilder.setDoctypeExpectation(doctypeExpectation);
+ this.treeBuilder.setDocumentModeHandler(documentModeHandler);
+ this.treeBuilder.setScriptingEnabled(scriptingEnabled);
+ this.treeBuilder.setReportingDoctype(reportingDoctype);
+ this.treeBuilder.setNamePolicy(namePolicy);
+ }
+ }
+
+
+ private void tokenize(InputSource is) throws ParsingException, IOException,
+ MalformedURLException {
+ try {
+ if (is == null) {
+ throw new IllegalArgumentException("Null input.");
+ }
+ if (is.getByteStream() == null && is.getCharacterStream() == null) {
+ String systemId = is.getSystemId();
+ if (systemId == null) {
+ throw new IllegalArgumentException(
+ "No byte stream, no character stream nor URI.");
+ }
+ if (entityResolver != null) {
+ is = entityResolver.resolveEntity(is.getPublicId(),
+ systemId);
+ }
+ if (is.getByteStream() == null
+ || is.getCharacterStream() == null) {
+ is = new InputSource();
+ is.setSystemId(systemId);
+ is.setByteStream(new URL(systemId).openStream());
+ }
+ }
+ driver.tokenize(is);
+ } catch (SAXParseException e) {
+ throw new ParsingException(e.getMessage(), e.getSystemId(), e.getLineNumber(),
+ e.getColumnNumber(), e);
+ } catch (SAXException e) {
+ throw new ParsingException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Parse from SAX <code>InputSource</code>.
+ * @param is the <code>InputSource</code>
+ * @return the document
+ * @throws ParsingException in case of an XML violation
+ * @throws IOException if IO goes wrang
+ */
+ public Document build(InputSource is) throws ParsingException, IOException {
+ lazyInit();
+ treeBuilder.setFragmentContext(null);
+ tokenize(is);
+ return treeBuilder.getDocument();
+ }
+
+ /**
+ * Parse a fragment from SAX <code>InputSource</code> assuming an HTML
+ * context.
+ * @param is the <code>InputSource</code>
+ * @param context the name of the context element (HTML namespace assumed)
+ * @return the fragment
+ * @throws ParsingException in case of an XML violation
+ * @throws IOException if IO goes wrang
+ */
+ public Nodes buildFragment(InputSource is, String context)
+ throws IOException, ParsingException {
+ lazyInit();
+ treeBuilder.setFragmentContext(context.intern());
+ tokenize(is);
+ return treeBuilder.getDocumentFragment();
+ }
+
+ /**
+ * Parse a fragment from SAX <code>InputSource</code>.
+ * @param is the <code>InputSource</code>
+ * @param contextLocal the local name of the context element
+ * @parem contextNamespace the namespace of the context element
+ * @return the fragment
+ * @throws ParsingException in case of an XML violation
+ * @throws IOException if IO goes wrang
+ */
+ public Nodes buildFragment(InputSource is, String contextLocal, String contextNamespace)
+ throws IOException, ParsingException {
+ lazyInit();
+ treeBuilder.setFragmentContext(contextLocal.intern(), contextNamespace.intern(), null, false);
+ tokenize(is);
+ return treeBuilder.getDocumentFragment();
+ }
+
+ /**
+ * Parse from <code>File</code>.
+ * @param file the file
+ * @return the document
+ * @throws ParsingException in case of an XML violation
+ * @throws IOException if IO goes wrang
+ * @see nu.xom.Builder#build(java.io.File)
+ */
+ @Override
+ public Document build(File file) throws ParsingException,
+ ValidityException, IOException {
+ return build(new FileInputStream(file), file.toURI().toASCIIString());
+ }
+
+ /**
+ * Parse from <code>InputStream</code>.
+ * @param stream the stream
+ * @param uri the base URI
+ * @return the document
+ * @throws ParsingException in case of an XML violation
+ * @throws IOException if IO goes wrang
+ * @see nu.xom.Builder#build(java.io.InputStream, java.lang.String)
+ */
+ @Override
+ public Document build(InputStream stream, String uri)
+ throws ParsingException, ValidityException, IOException {
+ InputSource is = new InputSource(stream);
+ is.setSystemId(uri);
+ return build(is);
+ }
+
+ /**
+ * Parse from <code>InputStream</code>.
+ * @param stream the stream
+ * @return the document
+ * @throws ParsingException in case of an XML violation
+ * @throws IOException if IO goes wrang
+ * @see nu.xom.Builder#build(java.io.InputStream)
+ */
+ @Override
+ public Document build(InputStream stream) throws ParsingException,
+ ValidityException, IOException {
+ return build(new InputSource(stream));
+ }
+
+ /**
+ * Parse from <code>Reader</code>.
+ * @param stream the reader
+ * @param uri the base URI
+ * @return the document
+ * @throws ParsingException in case of an XML violation
+ * @throws IOException if IO goes wrang
+ * @see nu.xom.Builder#build(java.io.Reader, java.lang.String)
+ */
+ @Override
+ public Document build(Reader stream, String uri) throws ParsingException,
+ ValidityException, IOException {
+ InputSource is = new InputSource(stream);
+ is.setSystemId(uri);
+ return build(is);
+ }
+
+ /**
+ * Parse from <code>Reader</code>.
+ * @param stream the reader
+ * @return the document
+ * @throws ParsingException in case of an XML violation
+ * @throws IOException if IO goes wrang
+ * @see nu.xom.Builder#build(java.io.Reader)
+ */
+ @Override
+ public Document build(Reader stream) throws ParsingException,
+ ValidityException, IOException {
+ return build(new InputSource(stream));
+ }
+
+ /**
+ * Parse from <code>String</code>.
+ * @param content the HTML source as string
+ * @param uri the base URI
+ * @return the document
+ * @throws ParsingException in case of an XML violation
+ * @throws IOException if IO goes wrang
+ * @see nu.xom.Builder#build(java.lang.String, java.lang.String)
+ */
+ @Override
+ public Document build(String content, String uri) throws ParsingException,
+ ValidityException, IOException {
+ return build(new StringReader(content), uri);
+ }
+
+ /**
+ * Parse from URI.
+ * @param uri the URI of the document
+ * @return the document
+ * @throws ParsingException in case of an XML violation
+ * @throws IOException if IO goes wrang
+ * @see nu.xom.Builder#build(java.lang.String)
+ */
+ @Override
+ public Document build(String uri) throws ParsingException,
+ ValidityException, IOException {
+ return build(new InputSource(uri));
+ }
+
+ /**
+ * Gets the node factory
+ */
+ public SimpleNodeFactory getSimpleNodeFactory() {
+ return simpleNodeFactory;
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#setEntityResolver(org.xml.sax.EntityResolver)
+ */
+ public void setEntityResolver(EntityResolver resolver) {
+ entityResolver = resolver;
+ }
+
+ /**
+ * @see org.xml.sax.XMLReader#setErrorHandler(org.xml.sax.ErrorHandler)
+ */
+ public void setErrorHandler(ErrorHandler handler) {
+ errorHandler = handler;
+ treeBuilderErrorHandler = handler;
+ driver = null;
+ }
+
+ public void setTransitionHander(TransitionHandler handler) {
+ transitionHandler = handler;
+ driver = null;
+ }
+
+ /**
+ * Indicates whether NFC normalization of source is being checked.
+ * @return <code>true</code> if NFC normalization of source is being checked.
+ * @see nu.validator.htmlparser.impl.Tokenizer#isCheckingNormalization()
+ */
+ public boolean isCheckingNormalization() {
+ return checkingNormalization;
+ }
+
+ /**
+ * Toggles the checking of the NFC normalization of source.
+ * @param enable <code>true</code> to check normalization
+ * @see nu.validator.htmlparser.impl.Tokenizer#setCheckingNormalization(boolean)
+ */
+ public void setCheckingNormalization(boolean enable) {
+ this.checkingNormalization = enable;
+ if (driver != null) {
+ driver.setCheckingNormalization(checkingNormalization);
+ }
+ }
+
+ /**
+ * Sets the policy for consecutive hyphens in comments.
+ * @param commentPolicy the policy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setCommentPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setCommentPolicy(XmlViolationPolicy commentPolicy) {
+ this.commentPolicy = commentPolicy;
+ if (driver != null) {
+ driver.setCommentPolicy(commentPolicy);
+ }
+ }
+
+ /**
+ * Sets the policy for non-XML characters except white space.
+ * @param contentNonXmlCharPolicy the policy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setContentNonXmlCharPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setContentNonXmlCharPolicy(
+ XmlViolationPolicy contentNonXmlCharPolicy) {
+ this.contentNonXmlCharPolicy = contentNonXmlCharPolicy;
+ driver = null;
+ }
+
+ /**
+ * Sets the policy for non-XML white space.
+ * @param contentSpacePolicy the policy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setContentSpacePolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setContentSpacePolicy(XmlViolationPolicy contentSpacePolicy) {
+ this.contentSpacePolicy = contentSpacePolicy;
+ if (driver != null) {
+ driver.setContentSpacePolicy(contentSpacePolicy);
+ }
+ }
+
+ /**
+ * Whether the parser considers scripting to be enabled for noscript treatment.
+ *
+ * @return <code>true</code> if enabled
+ * @see nu.validator.htmlparser.impl.TreeBuilder#isScriptingEnabled()
+ */
+ public boolean isScriptingEnabled() {
+ return scriptingEnabled;
+ }
+
+ /**
+ * Sets whether the parser considers scripting to be enabled for noscript treatment.
+ * @param scriptingEnabled <code>true</code> to enable
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setScriptingEnabled(boolean)
+ */
+ public void setScriptingEnabled(boolean scriptingEnabled) {
+ this.scriptingEnabled = scriptingEnabled;
+ if (treeBuilder != null) {
+ treeBuilder.setScriptingEnabled(scriptingEnabled);
+ }
+ }
+
+ /**
+ * Returns the doctype expectation.
+ *
+ * @return the doctypeExpectation
+ */
+ public DoctypeExpectation getDoctypeExpectation() {
+ return doctypeExpectation;
+ }
+
+ /**
+ * Sets the doctype expectation.
+ *
+ * @param doctypeExpectation
+ * the doctypeExpectation to set
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setDoctypeExpectation(nu.validator.htmlparser.common.DoctypeExpectation)
+ */
+ public void setDoctypeExpectation(DoctypeExpectation doctypeExpectation) {
+ this.doctypeExpectation = doctypeExpectation;
+ if (treeBuilder != null) {
+ treeBuilder.setDoctypeExpectation(doctypeExpectation);
+ }
+ }
+
+ /**
+ * Returns the document mode handler.
+ *
+ * @return the documentModeHandler
+ */
+ public DocumentModeHandler getDocumentModeHandler() {
+ return documentModeHandler;
+ }
+
+ /**
+ * Sets the document mode handler.
+ *
+ * @param documentModeHandler
+ * the documentModeHandler to set
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setDocumentModeHandler(nu.validator.htmlparser.common.DocumentModeHandler)
+ */
+ public void setDocumentModeHandler(DocumentModeHandler documentModeHandler) {
+ this.documentModeHandler = documentModeHandler;
+ }
+
+ /**
+ * Returns the streamabilityViolationPolicy.
+ *
+ * @return the streamabilityViolationPolicy
+ */
+ public XmlViolationPolicy getStreamabilityViolationPolicy() {
+ return streamabilityViolationPolicy;
+ }
+
+ /**
+ * Sets the streamabilityViolationPolicy.
+ *
+ * @param streamabilityViolationPolicy
+ * the streamabilityViolationPolicy to set
+ */
+ public void setStreamabilityViolationPolicy(
+ XmlViolationPolicy streamabilityViolationPolicy) {
+ this.streamabilityViolationPolicy = streamabilityViolationPolicy;
+ driver = null;
+ }
+
+ /**
+ * Whether the HTML 4 mode reports boolean attributes in a way that repeats
+ * the name in the value.
+ * @param html4ModeCompatibleWithXhtml1Schemata
+ */
+ public void setHtml4ModeCompatibleWithXhtml1Schemata(
+ boolean html4ModeCompatibleWithXhtml1Schemata) {
+ this.html4ModeCompatibleWithXhtml1Schemata = html4ModeCompatibleWithXhtml1Schemata;
+ if (driver != null) {
+ driver.setHtml4ModeCompatibleWithXhtml1Schemata(html4ModeCompatibleWithXhtml1Schemata);
+ }
+ }
+
+ /**
+ * Returns the <code>Locator</code> during parse.
+ * @return the <code>Locator</code>
+ */
+ public Locator getDocumentLocator() {
+ return driver.getDocumentLocator();
+ }
+
+ /**
+ * Whether the HTML 4 mode reports boolean attributes in a way that repeats
+ * the name in the value.
+ *
+ * @return the html4ModeCompatibleWithXhtml1Schemata
+ */
+ public boolean isHtml4ModeCompatibleWithXhtml1Schemata() {
+ return html4ModeCompatibleWithXhtml1Schemata;
+ }
+
+ /**
+ * Whether <code>lang</code> is mapped to <code>xml:lang</code>.
+ * @param mappingLangToXmlLang
+ * @see nu.validator.htmlparser.impl.Tokenizer#setMappingLangToXmlLang(boolean)
+ */
+ public void setMappingLangToXmlLang(boolean mappingLangToXmlLang) {
+ this.mappingLangToXmlLang = mappingLangToXmlLang;
+ if (driver != null) {
+ driver.setMappingLangToXmlLang(mappingLangToXmlLang);
+ }
+ }
+
+ /**
+ * Whether <code>lang</code> is mapped to <code>xml:lang</code>.
+ *
+ * @return the mappingLangToXmlLang
+ */
+ public boolean isMappingLangToXmlLang() {
+ return mappingLangToXmlLang;
+ }
+
+ /**
+ * Whether the <code>xmlns</code> attribute on the root element is
+ * passed to through. (FATAL not allowed.)
+ * @param xmlnsPolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setXmlnsPolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setXmlnsPolicy(XmlViolationPolicy xmlnsPolicy) {
+ if (xmlnsPolicy == XmlViolationPolicy.FATAL) {
+ throw new IllegalArgumentException("Can't use FATAL here.");
+ }
+ this.xmlnsPolicy = xmlnsPolicy;
+ if (driver != null) {
+ driver.setXmlnsPolicy(xmlnsPolicy);
+ }
+ }
+
+ /**
+ * Returns the xmlnsPolicy.
+ *
+ * @return the xmlnsPolicy
+ */
+ public XmlViolationPolicy getXmlnsPolicy() {
+ return xmlnsPolicy;
+ }
+
+ /**
+ * Returns the commentPolicy.
+ *
+ * @return the commentPolicy
+ */
+ public XmlViolationPolicy getCommentPolicy() {
+ return commentPolicy;
+ }
+
+ /**
+ * Returns the contentNonXmlCharPolicy.
+ *
+ * @return the contentNonXmlCharPolicy
+ */
+ public XmlViolationPolicy getContentNonXmlCharPolicy() {
+ return contentNonXmlCharPolicy;
+ }
+
+ /**
+ * Returns the contentSpacePolicy.
+ *
+ * @return the contentSpacePolicy
+ */
+ public XmlViolationPolicy getContentSpacePolicy() {
+ return contentSpacePolicy;
+ }
+
+ /**
+ * @param reportingDoctype
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setReportingDoctype(boolean)
+ */
+ public void setReportingDoctype(boolean reportingDoctype) {
+ this.reportingDoctype = reportingDoctype;
+ if (treeBuilder != null) {
+ treeBuilder.setReportingDoctype(reportingDoctype);
+ }
+ }
+
+ /**
+ * Returns the reportingDoctype.
+ *
+ * @return the reportingDoctype
+ */
+ public boolean isReportingDoctype() {
+ return reportingDoctype;
+ }
+
+ /**
+ * The policy for non-NCName element and attribute names.
+ * @param namePolicy
+ * @see nu.validator.htmlparser.impl.Tokenizer#setNamePolicy(nu.validator.htmlparser.common.XmlViolationPolicy)
+ */
+ public void setNamePolicy(XmlViolationPolicy namePolicy) {
+ this.namePolicy = namePolicy;
+ if (driver != null) {
+ driver.setNamePolicy(namePolicy);
+ treeBuilder.setNamePolicy(namePolicy);
+ }
+ }
+
+ /**
+ * Sets the encoding sniffing heuristics.
+ *
+ * @param heuristics the heuristics to set
+ * @see nu.validator.htmlparser.impl.Tokenizer#setHeuristics(nu.validator.htmlparser.common.Heuristics)
+ */
+ public void setHeuristics(Heuristics heuristics) {
+ this.heuristics = heuristics;
+ if (driver != null) {
+ driver.setHeuristics(heuristics);
+ }
+ }
+
+ public Heuristics getHeuristics() {
+ return this.heuristics;
+ }
+
+ /**
+ * This is a catch-all convenience method for setting name, xmlns, content space,
+ * content non-XML char and comment policies in one go. This does not affect the
+ * streamability policy or doctype reporting.
+ *
+ * @param xmlPolicy
+ */
+ public void setXmlPolicy(XmlViolationPolicy xmlPolicy) {
+ setNamePolicy(xmlPolicy);
+ setXmlnsPolicy(xmlPolicy == XmlViolationPolicy.FATAL ? XmlViolationPolicy.ALTER_INFOSET : xmlPolicy);
+ setContentSpacePolicy(xmlPolicy);
+ setContentNonXmlCharPolicy(xmlPolicy);
+ setCommentPolicy(xmlPolicy);
+ }
+
+ /**
+ * The policy for non-NCName element and attribute names.
+ *
+ * @return the namePolicy
+ */
+ public XmlViolationPolicy getNamePolicy() {
+ return namePolicy;
+ }
+
+ /**
+ * Does nothing.
+ * @deprecated
+ */
+ public void setBogusXmlnsPolicy(
+ XmlViolationPolicy bogusXmlnsPolicy) {
+ }
+
+ /**
+ * Returns <code>XmlViolationPolicy.ALTER_INFOSET</code>.
+ * @deprecated
+ * @return <code>XmlViolationPolicy.ALTER_INFOSET</code>
+ */
+ public XmlViolationPolicy getBogusXmlnsPolicy() {
+ return XmlViolationPolicy.ALTER_INFOSET;
+ }
+
+ public void addCharacterHandler(CharacterHandler characterHandler) {
+ this.characterHandlers.add(characterHandler);
+ if (driver != null) {
+ driver.addCharacterHandler(characterHandler);
+ }
+ }
+
+
+ /**
+ * Sets whether comment nodes appear in the tree.
+ * @param ignoreComments <code>true</code> to ignore comments
+ * @see nu.validator.htmlparser.impl.TreeBuilder#setIgnoringComments(boolean)
+ */
+ public void setIgnoringComments(boolean ignoreComments) {
+ treeBuilder.setIgnoringComments(ignoreComments);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/ModalDocument.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/ModalDocument.java
new file mode 100644
index 000000000..3b76b1421
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/ModalDocument.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.xom;
+
+import nu.validator.htmlparser.common.DocumentMode;
+import nu.xom.Document;
+import nu.xom.Element;
+
+/**
+ * Document with <code>Mode</code>.
+ * @version $Id$
+ * @author hsivonen
+ */
+public class ModalDocument extends Document implements Mode {
+
+ private DocumentMode mode = null;
+
+ /**
+ * Copy constructor (<code>Mode</code>-aware).
+ * @param doc
+ */
+ public ModalDocument(Document doc) {
+ super(doc);
+ if (doc instanceof Mode) {
+ Mode modal = (Mode) doc;
+ setMode(modal.getMode());
+ }
+ }
+
+ /**
+ * With root.
+ *
+ * @param elt
+ */
+ public ModalDocument(Element elt) {
+ super(elt);
+ }
+
+ /**
+ * Gets the mode.
+ * @see nu.validator.htmlparser.xom.Mode#getMode()
+ */
+ public DocumentMode getMode() {
+ return mode;
+ }
+
+ /**
+ * Sets the mode.
+ * @see nu.validator.htmlparser.xom.Mode#setMode(nu.validator.htmlparser.common.DocumentMode)
+ */
+ public void setMode(DocumentMode mode) {
+ this.mode = mode;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/Mode.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/Mode.java
new file mode 100644
index 000000000..bd2dcbc26
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/Mode.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.xom;
+
+import nu.validator.htmlparser.common.DocumentMode;
+
+/**
+ * Interface for attaching a <code>DocumentMode</code> on a Document.
+ * @version $Id$
+ * @author hsivonen
+ */
+public interface Mode {
+
+ /**
+ * Returns the mode.
+ *
+ * @return the mode
+ */
+ public abstract DocumentMode getMode();
+
+ /**
+ * Sets the mode.
+ *
+ * @param mode the mode to set
+ */
+ public abstract void setMode(DocumentMode mode);
+
+} \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/SimpleNodeFactory.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/SimpleNodeFactory.java
new file mode 100644
index 000000000..147b5d930
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/SimpleNodeFactory.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.xom;
+
+import nu.xom.Attribute;
+import nu.xom.Comment;
+import nu.xom.Document;
+import nu.xom.Element;
+import nu.xom.Text;
+import nu.xom.Attribute.Type;
+
+/**
+ * A simpler node factory that does not use <code>Nodes</code>..
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public class SimpleNodeFactory {
+
+ /**
+ * <code>return new Attribute(localName, uri, value, type);</code>
+ * @param localName
+ * @param uri
+ * @param value
+ * @param type
+ * @return
+ */
+ public Attribute makeAttribute(String localName, String uri, String value, Type type) {
+ return new Attribute(localName, uri, value, type);
+ }
+
+ /**
+ * <code>return new Text(string);</code>
+ * @param string
+ * @return
+ */
+ public Text makeText(String string) {
+ return new Text(string);
+ }
+
+ /**
+ * <code>return new Comment(string);</code>
+ * @param string
+ * @return
+ */
+ public Comment makeComment(String string) {
+ return new Comment(string);
+ }
+
+ /**
+ * <code>return new Element(name, namespace);</code>
+ * @param name
+ * @param namespace
+ * @return
+ */
+ public Element makeElement(String name, String namespace) {
+ return new Element(name, namespace);
+ }
+
+ /**
+ * <code>return new FormPtrElement(name, namespace, form);</code>
+ * @param name
+ * @param namespace
+ * @param form
+ * @return
+ */
+ public Element makeElement(String name, String namespace, Element form) {
+ return new FormPtrElement(name, namespace, form);
+ }
+
+ /**
+ * <code>return new ModalDocument(new Element("root", "http://www.xom.nu/fakeRoot"));</code>
+ *
+ * <p>Subclasses adviced to return an instance of <code>Mode</code>. (Not required, though.)
+ *
+ * @return
+ */
+ public Document makeDocument() {
+ return new ModalDocument(new Element("root", "http://www.xom.nu/fakeRoot"));
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/XOMTreeBuilder.java b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/XOMTreeBuilder.java
new file mode 100644
index 000000000..623f31927
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/XOMTreeBuilder.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.xom;
+
+import nu.validator.htmlparser.common.DocumentMode;
+import nu.validator.htmlparser.impl.CoalescingTreeBuilder;
+import nu.validator.htmlparser.impl.HtmlAttributes;
+import nu.xom.Attribute;
+import nu.xom.Document;
+import nu.xom.Element;
+import nu.xom.Node;
+import nu.xom.Nodes;
+import nu.xom.ParentNode;
+import nu.xom.Text;
+import nu.xom.XMLException;
+
+import org.xml.sax.SAXException;
+
+class XOMTreeBuilder extends CoalescingTreeBuilder<Element> {
+
+ private final SimpleNodeFactory nodeFactory;
+
+ private Document document;
+
+ private int cachedTableIndex = -1;
+
+ private Element cachedTable = null;
+
+ protected XOMTreeBuilder(SimpleNodeFactory nodeFactory) {
+ super();
+ this.nodeFactory = nodeFactory;
+ }
+
+ @Override
+ protected void addAttributesToElement(Element element, HtmlAttributes attributes)
+ throws SAXException {
+ try {
+ for (int i = 0; i < attributes.getLength(); i++) {
+ String localName = attributes.getLocalNameNoBoundsCheck(i);
+ String uri = attributes.getURINoBoundsCheck(i);
+ if (element.getAttribute(localName, uri) == null) {
+ element.addAttribute(nodeFactory.makeAttribute(
+ localName,
+ uri,
+ attributes.getValueNoBoundsCheck(i),
+ attributes.getTypeNoBoundsCheck(i) == "ID" ? Attribute.Type.ID
+ : Attribute.Type.CDATA));
+ }
+ }
+ } catch (XMLException e) {
+ fatal(e);
+ }
+ }
+
+ @Override protected void appendCharacters(Element parent, String text)
+ throws SAXException {
+ try {
+ int childCount = parent.getChildCount();
+ Node lastChild;
+ if (childCount != 0
+ && ((lastChild = parent.getChild(childCount - 1)) instanceof Text)) {
+ Text lastAsText = (Text) lastChild;
+ lastAsText.setValue(lastAsText.getValue() + text);
+ return;
+ }
+ parent.appendChild(nodeFactory.makeText(text));
+ } catch (XMLException e) {
+ fatal(e);
+ }
+ }
+
+ @Override
+ protected void appendChildrenToNewParent(Element oldParent,
+ Element newParent) throws SAXException {
+ try {
+ Nodes children = oldParent.removeChildren();
+ for (int i = 0; i < children.size(); i++) {
+ newParent.appendChild(children.get(i));
+ }
+ } catch (XMLException e) {
+ fatal(e);
+ }
+ }
+
+ @Override
+ protected void appendComment(Element parent, String comment) throws SAXException {
+ try {
+ parent.appendChild(nodeFactory.makeComment(comment));
+ } catch (XMLException e) {
+ fatal(e);
+ }
+ }
+
+ @Override
+ protected void appendCommentToDocument(String comment)
+ throws SAXException {
+ try {
+ Element root = document.getRootElement();
+ if ("http://www.xom.nu/fakeRoot".equals(root.getNamespaceURI())) {
+ document.insertChild(nodeFactory.makeComment(comment), document.indexOf(root));
+ } else {
+ document.appendChild(nodeFactory.makeComment(comment));
+ }
+ } catch (XMLException e) {
+ fatal(e);
+ }
+ }
+
+ @Override
+ protected Element createElement(String ns, String name,
+ HtmlAttributes attributes, Element intendedParent) throws SAXException {
+ try {
+ Element rv = nodeFactory.makeElement(name, ns);
+ for (int i = 0; i < attributes.getLength(); i++) {
+ rv.addAttribute(nodeFactory.makeAttribute(
+ attributes.getLocalNameNoBoundsCheck(i),
+ attributes.getURINoBoundsCheck(i),
+ attributes.getValueNoBoundsCheck(i),
+ attributes.getTypeNoBoundsCheck(i) == "ID" ? Attribute.Type.ID
+ : Attribute.Type.CDATA));
+ }
+ return rv;
+ } catch (XMLException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ @Override
+ protected Element createHtmlElementSetAsRoot(
+ HtmlAttributes attributes) throws SAXException {
+ try {
+ Element rv = nodeFactory.makeElement("html",
+ "http://www.w3.org/1999/xhtml");
+ for (int i = 0; i < attributes.getLength(); i++) {
+ rv.addAttribute(nodeFactory.makeAttribute(
+ attributes.getLocalNameNoBoundsCheck(i),
+ attributes.getURINoBoundsCheck(i),
+ attributes.getValueNoBoundsCheck(i),
+ attributes.getTypeNoBoundsCheck(i) == "ID" ? Attribute.Type.ID
+ : Attribute.Type.CDATA));
+ }
+ document.setRootElement(rv);
+ return rv;
+ } catch (XMLException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ @Override
+ protected void detachFromParent(Element element) throws SAXException {
+ try {
+ element.detach();
+ } catch (XMLException e) {
+ fatal(e);
+ }
+ }
+
+ @Override
+ protected void appendElement(Element child,
+ Element newParent) throws SAXException {
+ try {
+ child.detach();
+ newParent.appendChild(child);
+ } catch (XMLException e) {
+ fatal(e);
+ }
+ }
+
+ @Override
+ protected boolean hasChildren(Element element) throws SAXException {
+ try {
+ return element.getChildCount() != 0;
+ } catch (XMLException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ /**
+ * Returns the document.
+ *
+ * @return the document
+ */
+ Document getDocument() {
+ Document rv = document;
+ document = null;
+ return rv;
+ }
+
+ Nodes getDocumentFragment() {
+ Element rootElt = document.getRootElement();
+ Nodes rv = rootElt.removeChildren();
+ document = null;
+ return rv;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#createElement(String,
+ * java.lang.String, org.xml.sax.Attributes, java.lang.Object)
+ */
+ @Override
+ protected Element createElement(String ns, String name,
+ HtmlAttributes attributes, Element form, Element intendedParent) throws SAXException {
+ try {
+ Element rv = nodeFactory.makeElement(name,
+ ns, form);
+ for (int i = 0; i < attributes.getLength(); i++) {
+ rv.addAttribute(nodeFactory.makeAttribute(
+ attributes.getLocalName(i),
+ attributes.getURINoBoundsCheck(i),
+ attributes.getValueNoBoundsCheck(i),
+ attributes.getTypeNoBoundsCheck(i) == "ID" ? Attribute.Type.ID
+ : Attribute.Type.CDATA));
+ }
+ return rv;
+ } catch (XMLException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#start()
+ */
+ @Override
+ protected void start(boolean fragment) throws SAXException {
+ document = nodeFactory.makeDocument();
+ cachedTableIndex = -1;
+ cachedTable = null;
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#documentMode(nu.validator.htmlparser.common.DocumentMode,
+ * java.lang.String, java.lang.String, boolean)
+ */
+ @Override
+ protected void documentMode(DocumentMode mode, String publicIdentifier,
+ String systemIdentifier, boolean html4SpecificAdditionalErrorChecks)
+ throws SAXException {
+ if (document instanceof Mode) {
+ Mode modal = (Mode) document;
+ modal.setMode(mode);
+ }
+ }
+
+ @Override
+ protected Element createAndInsertFosterParentedElement(String ns, String name,
+ HtmlAttributes attributes, Element table, Element stackParent) throws SAXException {
+ try {
+ Node parent = table.getParent();
+ Element child = createElement(ns, name, attributes, parent != null ? (Element) parent : stackParent);
+ if (parent != null) { // always an element if not null
+ ((ParentNode) parent).insertChild(child, indexOfTable(table, stackParent));
+ cachedTableIndex++;
+ } else {
+ stackParent.appendChild(child);
+ }
+ return child;
+ } catch (XMLException e) {
+ fatal(e);
+ throw new RuntimeException("Unreachable");
+ }
+ }
+
+ @Override protected void insertFosterParentedCharacters(String text,
+ Element table, Element stackParent) throws SAXException {
+ try {
+ Node parent = table.getParent();
+ if (parent != null) { // always an element if not null
+ Element parentAsElt = (Element) parent;
+ int tableIndex = indexOfTable(table, parentAsElt);
+ Node prevSibling;
+ if (tableIndex != 0
+ && ((prevSibling = parentAsElt.getChild(tableIndex - 1)) instanceof Text)) {
+ Text prevAsText = (Text) prevSibling;
+ prevAsText.setValue(prevAsText.getValue() + text);
+ return;
+ }
+ parentAsElt.insertChild(nodeFactory.makeText(text), tableIndex);
+ cachedTableIndex++;
+ return;
+ }
+ int childCount = stackParent.getChildCount();
+ Node lastChild;
+ if (childCount != 0
+ && ((lastChild = stackParent.getChild(childCount - 1)) instanceof Text)) {
+ Text lastAsText = (Text) lastChild;
+ lastAsText.setValue(lastAsText.getValue() + text);
+ return;
+ }
+ stackParent.appendChild(nodeFactory.makeText(text));
+ } catch (XMLException e) {
+ fatal(e);
+ }
+ }
+
+ @Override protected void insertFosterParentedChild(Element child,
+ Element table, Element stackParent) throws SAXException {
+ try {
+ Node parent = table.getParent();
+ if (parent != null) { // always an element if not null
+ ((ParentNode)parent).insertChild(child, indexOfTable(table, stackParent));
+ cachedTableIndex++;
+ } else {
+ stackParent.appendChild(child);
+ }
+ } catch (XMLException e) {
+ fatal(e);
+ }
+ }
+
+ private int indexOfTable(Element table, Element stackParent) {
+ if (table == cachedTable) {
+ return cachedTableIndex;
+ } else {
+ cachedTable = table;
+ return (cachedTableIndex = stackParent.indexOf(table));
+ }
+ }
+
+ /**
+ * @see nu.validator.htmlparser.impl.TreeBuilder#end()
+ */
+ @Override protected void end() throws SAXException {
+ cachedTableIndex = -1;
+ cachedTable = null;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/package.html b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/package.html
new file mode 100644
index 000000000..a936d5e3a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/htmlparser/xom/package.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head><title>Package Overview</title>
+<!--
+ Copyright (c) 2007 Henri Sivonen
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+-->
+</head>
+<body bgcolor="white">
+<p>This package provides an HTML5 parser that exposes the document through the XOM API.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/CDATA.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/CDATA.java
new file mode 100644
index 000000000..f17ce3f89
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/CDATA.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * A CDATA section.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class CDATA extends ParentNode {
+
+ /**
+ * The constructor.
+ * @param locator the locator
+ */
+ public CDATA(Locator locator) {
+ super(locator);
+ }
+
+ /**
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void visit(TreeParser treeParser) throws SAXException {
+ treeParser.startCDATA(this);
+ }
+
+ /**
+ *
+ * @throws SAXException if things go wrong
+ * @see nu.validator.saxtree.Node#revisit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void revisit(TreeParser treeParser) throws SAXException {
+ treeParser.endCDATA(endLocator);
+ }
+
+ /**
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override
+ public NodeType getNodeType() {
+ return NodeType.CDATA;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/CharBufferNode.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/CharBufferNode.java
new file mode 100644
index 000000000..55c7715f6
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/CharBufferNode.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+
+/**
+ * A common superclass for character buffer node classes.
+ * @version $Id$
+ * @author hsivonen
+ */
+public abstract class CharBufferNode extends Node {
+
+ /**
+ * The buffer.
+ */
+ protected final char[] buffer;
+
+ /**
+ * The constructor.
+ * @param locator the locator
+ * @param buf the buffer
+ * @param start the offset
+ * @param length the length
+ */
+ CharBufferNode(Locator locator, char[] buf, int start, int length) {
+ super(locator);
+ this.buffer = new char[length];
+ System.arraycopy(buf, start, buffer, 0, length);
+ }
+
+ /**
+ * Returns the wrapped buffer as a string.
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return new String(buffer);
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Characters.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Characters.java
new file mode 100644
index 000000000..b8cc2d6d6
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Characters.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * A run of characters
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class Characters extends CharBufferNode {
+
+ /**
+ * The constructor.
+ * @param locator the locator
+ * @param buf the buffer
+ * @param start the offset in the buffer
+ * @param length the length
+ */
+ public Characters(Locator locator, char[] buf, int start, int length) {
+ super(locator, buf, start, length);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void visit(TreeParser treeParser) throws SAXException {
+ treeParser.characters(buffer, 0, buffer.length, this);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override
+ public NodeType getNodeType() {
+ return NodeType.CHARACTERS;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Comment.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Comment.java
new file mode 100644
index 000000000..f010462fb
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Comment.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * A comment.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class Comment extends CharBufferNode {
+
+ /**
+ * The constructor.
+ * @param locator the locator
+ * @param buf the buffer
+ * @param start the offset
+ * @param length the length
+ */
+ public Comment(Locator locator, char[] buf, int start, int length) {
+ super(locator, buf, start, length);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void visit(TreeParser treeParser) throws SAXException {
+ treeParser.comment(buffer, 0, buffer.length, this);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override
+ public NodeType getNodeType() {
+ return NodeType.COMMENT;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/DTD.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/DTD.java
new file mode 100644
index 000000000..2169e0571
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/DTD.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * A doctype.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class DTD extends ParentNode {
+
+ /**
+ * The name.
+ */
+ private final String name;
+
+ /**
+ * The public id.
+ */
+ private final String publicIdentifier;
+
+ /**
+ * The system id.
+ */
+ private final String systemIdentifier;
+
+ /**
+ * The constructor.
+ * @param locator the locator
+ * @param name the name
+ * @param publicIdentifier the public id
+ * @param systemIdentifier the system id
+ */
+ public DTD(Locator locator, String name, String publicIdentifier, String systemIdentifier) {
+ super(locator);
+ this.name = name;
+ this.publicIdentifier = publicIdentifier;
+ this.systemIdentifier = systemIdentifier;
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void visit(TreeParser treeParser) throws SAXException {
+ treeParser.startDTD(name, publicIdentifier, systemIdentifier, this);
+ }
+
+ /**
+ * @see nu.validator.saxtree.Node#revisit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void revisit(TreeParser treeParser) throws SAXException {
+ treeParser.endDTD(endLocator);
+ }
+
+ /**
+ * Returns the name.
+ *
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the publicIdentifier.
+ *
+ * @return the publicIdentifier
+ */
+ public String getPublicIdentifier() {
+ return publicIdentifier;
+ }
+
+ /**
+ * Returns the systemIdentifier.
+ *
+ * @return the systemIdentifier
+ */
+ public String getSystemIdentifier() {
+ return systemIdentifier;
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override
+ public NodeType getNodeType() {
+ return NodeType.DTD;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Document.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Document.java
new file mode 100644
index 000000000..3bb6f09c7
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Document.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * A document.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class Document extends ParentNode {
+
+ /**
+ * The constructor.
+ * @param locator the locator
+ */
+ public Document(Locator locator) {
+ super(locator);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void visit(TreeParser treeParser) throws SAXException {
+ treeParser.startDocument(this);
+ }
+
+ /**
+ * @see nu.validator.saxtree.Node#revisit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void revisit(TreeParser treeParser) throws SAXException {
+ treeParser.endDocument(endLocator);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override
+ public NodeType getNodeType() {
+ return NodeType.DOCUMENT;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/DocumentFragment.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/DocumentFragment.java
new file mode 100644
index 000000000..06816932f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/DocumentFragment.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.helpers.LocatorImpl;
+
+/**
+ * A document fragment.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class DocumentFragment extends ParentNode {
+
+ /**
+ * The constructor.
+ */
+ public DocumentFragment() {
+ super(new LocatorImpl());
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override void visit(TreeParser treeParser) {
+ // nothing
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override public NodeType getNodeType() {
+ return NodeType.DOCUMENT_FRAGMENT;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Element.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Element.java
new file mode 100644
index 000000000..3d33164e5
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Element.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import java.util.List;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * An element.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class Element extends ParentNode {
+
+ /**
+ * The namespace URI.
+ */
+ private final String uri;
+
+ /**
+ * The local name.
+ */
+ private final String localName;
+
+ /**
+ * The qualified name.
+ */
+ private final String qName;
+
+ /**
+ * The attributes.
+ */
+ private final Attributes attributes;
+
+ /**
+ * The namespace prefix mappings.
+ */
+ private final List<PrefixMapping> prefixMappings;
+
+ /**
+ * The contructor.
+ * @param locator the locator.
+ * @param uri the namespace URI
+ * @param localName the local name
+ * @param qName the qualified name
+ * @param atts the attributes
+ * @param retainAttributes <code>true</code> to retain the attributes instead of copying
+ * @param prefixMappings the prefix mappings
+ */
+ public Element(Locator locator, String uri, String localName, String qName,
+ Attributes atts, boolean retainAttributes,
+ List<PrefixMapping> prefixMappings) {
+ super(locator);
+ this.uri = uri;
+ this.localName = localName;
+ this.qName = qName;
+ if (retainAttributes) {
+ this.attributes = atts;
+ } else {
+ this.attributes = new AttributesImpl(atts);
+ }
+ this.prefixMappings = prefixMappings;
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void visit(TreeParser treeParser) throws SAXException {
+ if (prefixMappings != null) {
+ for (PrefixMapping mapping : prefixMappings) {
+ treeParser.startPrefixMapping(mapping.getPrefix(),
+ mapping.getUri(), this);
+ }
+ }
+ treeParser.startElement(uri, localName, qName, attributes, this);
+ }
+
+ /**
+ * @see nu.validator.saxtree.Node#revisit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void revisit(TreeParser treeParser) throws SAXException {
+ treeParser.endElement(uri, localName, qName, endLocator);
+ if (prefixMappings != null) {
+ for (PrefixMapping mapping : prefixMappings) {
+ treeParser.endPrefixMapping(mapping.getPrefix(), endLocator);
+ }
+ }
+ }
+
+ /**
+ * Returns the attributes.
+ *
+ * @return the attributes
+ */
+ public Attributes getAttributes() {
+ return attributes;
+ }
+
+ /**
+ * Returns the localName.
+ *
+ * @return the localName
+ */
+ public String getLocalName() {
+ return localName;
+ }
+
+ /**
+ * Returns the prefixMappings.
+ *
+ * @return the prefixMappings
+ */
+ public List<PrefixMapping> getPrefixMappings() {
+ return prefixMappings;
+ }
+
+ /**
+ * Returns the qName.
+ *
+ * @return the qName
+ */
+ public String getQName() {
+ return qName;
+ }
+
+ /**
+ * Returns the uri.
+ *
+ * @return the uri
+ */
+ public String getUri() {
+ return uri;
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override
+ public NodeType getNodeType() {
+ return NodeType.ELEMENT;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Entity.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Entity.java
new file mode 100644
index 000000000..091013736
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Entity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * An entity.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class Entity extends ParentNode {
+
+ /**
+ * The name.
+ */
+ private final String name;
+
+ /**
+ * The constructor.
+ * @param locator the locator
+ * @param name the name
+ */
+ public Entity(Locator locator, String name) {
+ super(locator);
+ this.name = name;
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void visit(TreeParser treeParser) throws SAXException {
+ treeParser.startEntity(name, this);
+ }
+
+ /**
+ * @see nu.validator.saxtree.Node#revisit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void revisit(TreeParser treeParser) throws SAXException {
+ treeParser.endEntity(name, endLocator);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override
+ public NodeType getNodeType() {
+ return NodeType.ENTITY;
+ }
+
+ /**
+ * Returns the name.
+ *
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/IgnorableWhitespace.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/IgnorableWhitespace.java
new file mode 100644
index 000000000..e5fcf350f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/IgnorableWhitespace.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * A run ignorable whitespace.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class IgnorableWhitespace extends CharBufferNode {
+
+ /**
+ * The constructor.
+ * @param locator the locator
+ * @param buf the buffer
+ * @param start the offset
+ * @param length the length
+ */
+ public IgnorableWhitespace(Locator locator, char[] buf, int start, int length) {
+ super(locator, buf, start, length);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void visit(TreeParser treeParser) throws SAXException {
+ treeParser.ignorableWhitespace(buffer, 0, buffer.length, this);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override
+ public NodeType getNodeType() {
+ return NodeType.IGNORABLE_WHITESPACE;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/LocatorImpl.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/LocatorImpl.java
new file mode 100644
index 000000000..37c0c6325
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/LocatorImpl.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+
+/**
+ * A locator implementation.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class LocatorImpl implements Locator {
+
+ /**
+ * The system id.
+ */
+ private final String systemId;
+
+ /**
+ * The public id.
+ */
+ private final String publicId;
+
+ /**
+ * The column.
+ */
+ private final int column;
+
+ /**
+ * The line.
+ */
+ private final int line;
+
+ /**
+ * The constructor.
+ * @param locator the locator
+ */
+ public LocatorImpl(Locator locator) {
+ if (locator == null) {
+ this.systemId = null;
+ this.publicId = null;
+ this.column = -1;
+ this.line = -1;
+ } else {
+ this.systemId = locator.getSystemId();
+ this.publicId = locator.getPublicId();
+ this.column = locator.getColumnNumber();
+ this.line = locator.getLineNumber();
+ }
+ }
+
+ /**
+ *
+ * @see org.xml.sax.Locator#getColumnNumber()
+ */
+ public int getColumnNumber() {
+ return column;
+ }
+
+ /**
+ *
+ * @see org.xml.sax.Locator#getLineNumber()
+ */
+ public int getLineNumber() {
+ return line;
+ }
+
+ /**
+ *
+ * @see org.xml.sax.Locator#getPublicId()
+ */
+ public String getPublicId() {
+ return publicId;
+ }
+
+ /**
+ *
+ * @see org.xml.sax.Locator#getSystemId()
+ */
+ public String getSystemId() {
+ return systemId;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Node.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Node.java
new file mode 100644
index 000000000..7aed83b75
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/Node.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import java.util.List;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * The common node superclass.
+ * @version $Id$
+ * @author hsivonen
+ */
+public abstract class Node implements Locator {
+
+ /**
+ * The system id.
+ */
+ private final String systemId;
+
+ /**
+ * The public id.
+ */
+ private final String publicId;
+
+ /**
+ * The column.
+ */
+ private final int column;
+
+ /**
+ * The line.
+ */
+ private final int line;
+
+ /**
+ * The next sibling.
+ */
+ private Node nextSibling = null;
+
+ /**
+ * The parent.
+ */
+ private ParentNode parentNode = null;
+
+ /**
+ * The constructor.
+ *
+ * @param locator the locator
+ */
+ Node(Locator locator) {
+ if (locator == null) {
+ this.systemId = null;
+ this.publicId = null;
+ this.column = -1;
+ this.line = -1;
+ } else {
+ this.systemId = locator.getSystemId();
+ this.publicId = locator.getPublicId();
+ this.column = locator.getColumnNumber();
+ this.line = locator.getLineNumber();
+ }
+ }
+
+ /**
+ *
+ * @see org.xml.sax.Locator#getColumnNumber()
+ */
+ public int getColumnNumber() {
+ return column;
+ }
+
+ /**
+ *
+ * @see org.xml.sax.Locator#getLineNumber()
+ */
+ public int getLineNumber() {
+ return line;
+ }
+
+ /**
+ *
+ * @see org.xml.sax.Locator#getPublicId()
+ */
+ public String getPublicId() {
+ return publicId;
+ }
+
+ /**
+ *
+ * @see org.xml.sax.Locator#getSystemId()
+ */
+ public String getSystemId() {
+ return systemId;
+ }
+
+ /**
+ * Visit the node.
+ *
+ * @param treeParser the visitor
+ * @throws SAXException if stuff goes wrong
+ */
+ abstract void visit(TreeParser treeParser) throws SAXException;
+
+ /**
+ * Revisit the node.
+ *
+ * @param treeParser the visitor
+ * @throws SAXException if stuff goes wrong
+ */
+ void revisit(TreeParser treeParser) throws SAXException {
+ return;
+ }
+
+ /**
+ * Return the first child.
+ * @return the first child
+ */
+ public Node getFirstChild() {
+ return null;
+ }
+
+ /**
+ * Returns the nextSibling.
+ *
+ * @return the nextSibling
+ */
+ public final Node getNextSibling() {
+ return nextSibling;
+ }
+
+ /**
+ * Returns the previous sibling
+ * @return the previous sibling
+ */
+ public final Node getPreviousSibling() {
+ Node prev = null;
+ Node next = parentNode.getFirstChild();
+ for(;;) {
+ if (this == next) {
+ return prev;
+ }
+ prev = next;
+ next = next.nextSibling;
+ }
+ }
+
+ /**
+ * Sets the nextSibling.
+ *
+ * @param nextSibling the nextSibling to set
+ */
+ void setNextSibling(Node nextSibling) {
+ this.nextSibling = nextSibling;
+ }
+
+
+ /**
+ * Returns the parentNode.
+ *
+ * @return the parentNode
+ */
+ public final ParentNode getParentNode() {
+ return parentNode;
+ }
+
+ /**
+ * Sets the parentNode.
+ *
+ * @param parentNode the parentNode to set
+ */
+ void setParentNode(ParentNode parentNode) {
+ this.parentNode = parentNode;
+ }
+
+ /**
+ * Return the node type.
+ * @return the node type
+ */
+ public abstract NodeType getNodeType();
+
+ // Subclass-specific accessors that are hoisted here to
+ // avoid casting.
+
+ /**
+ * Detach this node from its parent.
+ */
+ public void detach() {
+ if (parentNode != null) {
+ parentNode.removeChild(this);
+ parentNode = null;
+ }
+ }
+
+ /**
+ * Returns the name.
+ *
+ * @return the name
+ */
+ public String getName() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the publicIdentifier.
+ *
+ * @return the publicIdentifier
+ */
+ public String getPublicIdentifier() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the systemIdentifier.
+ *
+ * @return the systemIdentifier
+ */
+ public String getSystemIdentifier() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the attributes.
+ *
+ * @return the attributes
+ */
+ public Attributes getAttributes() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the localName.
+ *
+ * @return the localName
+ */
+ public String getLocalName() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the prefixMappings.
+ *
+ * @return the prefixMappings
+ */
+ public List<PrefixMapping> getPrefixMappings() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the qName.
+ *
+ * @return the qName
+ */
+ public String getQName() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the uri.
+ *
+ * @return the uri
+ */
+ public String getUri() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the data.
+ *
+ * @return the data
+ */
+ public String getData() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the target.
+ *
+ * @return the target
+ */
+ public String getTarget() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/NodeType.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/NodeType.java
new file mode 100644
index 000000000..c3c927f0d
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/NodeType.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+/**
+ * The node type.
+ * @version $Id$
+ * @author hsivonen
+ */
+public enum NodeType {
+ /**
+ * A CDATA section.
+ */
+ CDATA,
+ /**
+ * A run of characters.
+ */
+ CHARACTERS,
+ /**
+ * A comment.
+ */
+ COMMENT,
+ /**
+ * A document.
+ */
+ DOCUMENT,
+ /**
+ * A document fragment.
+ */
+ DOCUMENT_FRAGMENT,
+ /**
+ * A DTD.
+ */
+ DTD,
+ /**
+ * An element.
+ */
+ ELEMENT,
+ /**
+ * An entity.
+ */
+ ENTITY,
+ /**
+ * A run of ignorable whitespace.
+ */
+ IGNORABLE_WHITESPACE,
+ /**
+ * A processing instruction.
+ */
+ PROCESSING_INSTRUCTION,
+ /**
+ * A skipped entity.
+ */
+ SKIPPED_ENTITY
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/NullLexicalHandler.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/NullLexicalHandler.java
new file mode 100644
index 000000000..de63f3b57
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/NullLexicalHandler.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * A lexical handler that does nothing.
+ * @version $Id$
+ * @author hsivonen
+ */
+final class NullLexicalHandler implements LexicalHandler {
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
+ */
+ public void comment(char[] arg0, int arg1, int arg2) throws SAXException {
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#endCDATA()
+ */
+ public void endCDATA() throws SAXException {
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#endDTD()
+ */
+ public void endDTD() throws SAXException {
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String)
+ */
+ public void endEntity(String arg0) throws SAXException {
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#startCDATA()
+ */
+ public void startCDATA() throws SAXException {
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#startDTD(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void startDTD(String arg0, String arg1, String arg2) throws SAXException {
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String)
+ */
+ public void startEntity(String arg0) throws SAXException {
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/ParentNode.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/ParentNode.java
new file mode 100644
index 000000000..6cc96003f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/ParentNode.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+
+/**
+ * Common superclass for parent nodes.
+ * @version $Id$
+ * @author hsivonen
+ */
+public abstract class ParentNode extends Node {
+
+ /**
+ * The end locator.
+ */
+ protected Locator endLocator;
+
+ /**
+ * The first child.
+ */
+ private Node firstChild = null;
+
+ /**
+ * The last child (for efficiency).
+ */
+ private Node lastChild = null;
+
+ /**
+ * The constuctor.
+ * @param locator the locator
+ */
+ ParentNode(Locator locator) {
+ super(locator);
+ }
+
+ /**
+ * Sets the endLocator.
+ *
+ * @param endLocator the endLocator to set
+ */
+ public void setEndLocator(Locator endLocator) {
+ this.endLocator = new LocatorImpl(endLocator);
+ }
+
+ /**
+ * Copies the endLocator from another node.
+ *
+ * @param another the another node
+ */
+ public void copyEndLocator(ParentNode another) {
+ this.endLocator = another.endLocator;
+ }
+
+ /**
+ * Returns the firstChild.
+ *
+ * @return the firstChild
+ */
+ public final Node getFirstChild() {
+ return firstChild;
+ }
+
+ /**
+ * Returns the lastChild.
+ *
+ * @return the lastChild
+ */
+ public final Node getLastChild() {
+ return lastChild;
+ }
+
+ /**
+ * Insert a new child before a pre-existing child and return the newly inserted child.
+ * @param child the new child
+ * @param sibling the existing child before which to insert (must be a child of this node) or <code>null</code> to append
+ * @return <code>child</code>
+ */
+ public Node insertBefore(Node child, Node sibling) {
+ assert sibling == null || this == sibling.getParentNode();
+ if (sibling == null) {
+ return appendChild(child);
+ }
+ child.detach();
+ child.setParentNode(this);
+ if (firstChild == sibling) {
+ child.setNextSibling(sibling);
+ firstChild = child;
+ } else {
+ Node prev = firstChild;
+ Node next = firstChild.getNextSibling();
+ while (next != sibling) {
+ prev = next;
+ next = next.getNextSibling();
+ }
+ prev.setNextSibling(child);
+ child.setNextSibling(next);
+ }
+ return child;
+ }
+
+ public Node insertBetween(Node child, Node prev, Node next) {
+ assert prev == null || this == prev.getParentNode();
+ assert next == null || this == next.getParentNode();
+ assert prev != null || next == firstChild;
+ assert next != null || prev == lastChild;
+ assert prev == null || next == null || prev.getNextSibling() == next;
+ if (next == null) {
+ return appendChild(child);
+ }
+ child.detach();
+ child.setParentNode(this);
+ child.setNextSibling(next);
+ if (prev == null) {
+ firstChild = child;
+ } else {
+ prev.setNextSibling(child);
+ }
+ return child;
+ }
+
+ /**
+ * Append a child to this node and return the child.
+ *
+ * @param child the child to append.
+ * @return <code>child</code>
+ */
+ public Node appendChild(Node child) {
+ child.detach();
+ child.setParentNode(this);
+ if (firstChild == null) {
+ firstChild = child;
+ } else {
+ lastChild.setNextSibling(child);
+ }
+ lastChild = child;
+ return child;
+ }
+
+ /**
+ * Append the children of another node to this node removing them from the other node .
+ * @param parent the other node whose children to append to this one
+ */
+ public void appendChildren(Node parent) {
+ Node child = parent.getFirstChild();
+ if (child == null) {
+ return;
+ }
+ ParentNode another = (ParentNode) parent;
+ if (firstChild == null) {
+ firstChild = child;
+ } else {
+ lastChild.setNextSibling(child);
+ }
+ lastChild = another.lastChild;
+ do {
+ child.setParentNode(this);
+ } while ((child = child.getNextSibling()) != null);
+ another.firstChild = null;
+ another.lastChild = null;
+ }
+
+ /**
+ * Remove a child from this node.
+ * @param node the child to remove
+ */
+ void removeChild(Node node) {
+ assert this == node.getParentNode();
+ if (firstChild == node) {
+ firstChild = node.getNextSibling();
+ if (lastChild == node) {
+ lastChild = null;
+ }
+ } else {
+ Node prev = firstChild;
+ Node next = firstChild.getNextSibling();
+ while (next != node) {
+ prev = next;
+ next = next.getNextSibling();
+ }
+ prev.setNextSibling(node.getNextSibling());
+ if (lastChild == node) {
+ lastChild = prev;
+ }
+ }
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/PrefixMapping.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/PrefixMapping.java
new file mode 100644
index 000000000..8ffaf4a2c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/PrefixMapping.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+/**
+ * A prefix mapping.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class PrefixMapping {
+ /**
+ * The namespace prefix.
+ */
+ private final String prefix;
+ /**
+ * The namespace URI.
+ */
+ private final String uri;
+ /**
+ * Constructor.
+ * @param prefix the prefix
+ * @param uri the URI
+ */
+ public PrefixMapping(final String prefix, final String uri) {
+ this.prefix = prefix;
+ this.uri = uri;
+ }
+ /**
+ * Returns the prefix.
+ *
+ * @return the prefix
+ */
+ public String getPrefix() {
+ return prefix;
+ }
+ /**
+ * Returns the uri.
+ *
+ * @return the uri
+ */
+ public String getUri() {
+ return uri;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/ProcessingInstruction.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/ProcessingInstruction.java
new file mode 100644
index 000000000..014e63821
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/ProcessingInstruction.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * A processing instruction.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class ProcessingInstruction extends Node {
+
+ /**
+ * PI target.
+ */
+ private final String target;
+
+ /**
+ * PI data.
+ */
+ private final String data;
+
+ /**
+ * Constructor.
+ * @param locator the locator
+ * @param target PI target
+ * @param data PI data
+ */
+ public ProcessingInstruction(Locator locator, String target, String data) {
+ super(locator);
+ this.target = target;
+ this.data = data;
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void visit(TreeParser treeParser) throws SAXException {
+ treeParser.processingInstruction(target, data, this);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override
+ public NodeType getNodeType() {
+ return NodeType.PROCESSING_INSTRUCTION;
+ }
+
+ /**
+ * Returns the data.
+ *
+ * @return the data
+ */
+ public String getData() {
+ return data;
+ }
+
+ /**
+ * Returns the target.
+ *
+ * @return the target
+ */
+ public String getTarget() {
+ return target;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/SkippedEntity.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/SkippedEntity.java
new file mode 100644
index 000000000..01ca61490
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/SkippedEntity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * A skipped entity.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class SkippedEntity extends Node {
+
+ /**
+ * The name.
+ */
+ private final String name;
+
+ /**
+ * Constructor.
+ * @param locator the locator
+ * @param name the name
+ */
+ public SkippedEntity(Locator locator, String name) {
+ super(locator);
+ this.name = name;
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#visit(nu.validator.saxtree.TreeParser)
+ */
+ @Override
+ void visit(TreeParser treeParser) throws SAXException {
+ treeParser.skippedEntity(name, this);
+ }
+
+ /**
+ *
+ * @see nu.validator.saxtree.Node#getNodeType()
+ */
+ @Override
+ public NodeType getNodeType() {
+ return NodeType.SKIPPED_ENTITY;
+ }
+
+ /**
+ * Returns the name.
+ *
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/TreeBuilder.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/TreeBuilder.java
new file mode 100644
index 000000000..39fe236b3
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/TreeBuilder.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Builds a SAX Tree representation of a document or a fragment
+ * streamed as <code>ContentHandler</code> and
+ * <code>LexicalHandler</code> events. The start/end event matching
+ * is expected to adhere to the SAX API contract. Things will
+ * simply break if this is not the case. Fragments are expected to
+ * omit <code>startDocument()</code> and <code>endDocument()</code>
+ * calls.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public class TreeBuilder implements ContentHandler, LexicalHandler {
+
+ /**
+ * The locator.
+ */
+ private Locator locator;
+
+ /**
+ * The current node.
+ */
+ private ParentNode current;
+
+ /**
+ * Whether to retain attribute objects.
+ */
+ private final boolean retainAttributes;
+
+ /**
+ * The prefix mappings for the next element to be inserted.
+ */
+ private List<PrefixMapping> prefixMappings;
+
+ /**
+ * Constructs a reusable <code>TreeBuilder</code> that builds
+ * <code>Document</code>s and copies attributes.
+ */
+ public TreeBuilder() {
+ this(false, false);
+ }
+
+ /**
+ * The constructor. The instance will be reusabe if building a full
+ * document and not reusable if building a fragment.
+ *
+ * @param fragment whether this <code>TreeBuilder</code> should build
+ * a <code>DocumentFragment</code> instead of a <code>Document</code>.
+ * @param retainAttributes whether instances of the <code>Attributes</code>
+ * interface passed to <code>startElement</code> should be retained
+ * (the alternative is copying).
+ */
+ public TreeBuilder(boolean fragment, boolean retainAttributes) {
+ if (fragment) {
+ current = new DocumentFragment();
+ }
+ this.retainAttributes = retainAttributes;
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+ */
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ current.appendChild(new Characters(locator, ch, start, length));
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ContentHandler#endDocument()
+ */
+ public void endDocument() throws SAXException {
+ current.setEndLocator(locator);
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ current.setEndLocator(locator);
+ current = current.getParentNode();
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
+ */
+ public void endPrefixMapping(String prefix) throws SAXException {
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
+ */
+ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+ current.appendChild(new IgnorableWhitespace(locator, ch, start, length));
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String)
+ */
+ public void processingInstruction(String target, String data) throws SAXException {
+ current.appendChild(new ProcessingInstruction(locator, target, data));
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
+ */
+ public void setDocumentLocator(Locator locator) {
+ this.locator = locator;
+ }
+
+ public void skippedEntity(String name) throws SAXException {
+ current.appendChild(new SkippedEntity(locator, name));
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ContentHandler#startDocument()
+ */
+ public void startDocument() throws SAXException {
+ current = new Document(locator);
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
+ current = (ParentNode) current.appendChild(new Element(locator, uri, localName, qName, atts, retainAttributes, prefixMappings));
+ prefixMappings = null;
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String)
+ */
+ public void startPrefixMapping(String prefix, String uri) throws SAXException {
+ if (prefixMappings == null) {
+ prefixMappings = new LinkedList<PrefixMapping>();
+ }
+ prefixMappings.add(new PrefixMapping(prefix, uri));
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
+ */
+ public void comment(char[] ch, int start, int length) throws SAXException {
+ current.appendChild(new Comment(locator, ch, start, length));
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#endCDATA()
+ */
+ public void endCDATA() throws SAXException {
+ current.setEndLocator(locator);
+ current = current.getParentNode();
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#endDTD()
+ */
+ public void endDTD() throws SAXException {
+ current.setEndLocator(locator);
+ current = current.getParentNode();
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String)
+ */
+ public void endEntity(String name) throws SAXException {
+ current.setEndLocator(locator);
+ current = current.getParentNode();
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#startCDATA()
+ */
+ public void startCDATA() throws SAXException {
+ current = (ParentNode) current.appendChild(new CDATA(locator));
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#startDTD(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void startDTD(String name, String publicId, String systemId) throws SAXException {
+ current = (ParentNode) current.appendChild(new DTD(locator, name, publicId, systemId));
+ }
+
+ /**
+ *
+ * @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String)
+ */
+ public void startEntity(String name) throws SAXException {
+ current = (ParentNode) current.appendChild(new Entity(locator, name));
+ }
+
+ /**
+ * Returns the root (<code>Document</code> if building a full document or
+ * <code>DocumentFragment</code> if building a fragment.).
+ *
+ * @return the root
+ */
+ public ParentNode getRoot() {
+ return current;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/TreeParser.java b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/TreeParser.java
new file mode 100644
index 000000000..a9d92deb0
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/TreeParser.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * A tree visitor that replays a tree as SAX events.
+ * @version $Id$
+ * @author hsivonen
+ */
+public final class TreeParser implements Locator {
+
+ /**
+ * The content handler.
+ */
+ private final ContentHandler contentHandler;
+
+ /**
+ * The lexical handler.
+ */
+ private final LexicalHandler lexicalHandler;
+
+ /**
+ * The current locator.
+ */
+ private Locator locatorDelegate;
+
+ /**
+ * The constructor.
+ *
+ * @param contentHandler
+ * must not be <code>null</code>
+ * @param lexicalHandler
+ * may be <code>null</code>
+ */
+ public TreeParser(final ContentHandler contentHandler,
+ final LexicalHandler lexicalHandler) {
+ if (contentHandler == null) {
+ throw new IllegalArgumentException("contentHandler was null.");
+ }
+ this.contentHandler = contentHandler;
+ if (lexicalHandler == null) {
+ this.lexicalHandler = new NullLexicalHandler();
+ } else {
+ this.lexicalHandler = lexicalHandler;
+ }
+ }
+
+ /**
+ * Causes SAX events for the tree rooted at the argument to be emitted.
+ * <code>startDocument()</code> and <code>endDocument()</code> are only
+ * emitted for a <code>Document</code> node.
+ *
+ * @param node
+ * the root
+ * @throws SAXException
+ */
+ public void parse(Node node) throws SAXException {
+ contentHandler.setDocumentLocator(this);
+ Node current = node;
+ Node next;
+ for (;;) {
+ current.visit(this);
+ if ((next = current.getFirstChild()) != null) {
+ current = next;
+ continue;
+ }
+ for (;;) {
+ current.revisit(this);
+ if (current == node) {
+ return;
+ }
+ if ((next = current.getNextSibling()) != null) {
+ current = next;
+ break;
+ }
+ current = current.getParentNode();
+ }
+ }
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+ */
+ void characters(char[] ch, int start, int length, Locator locator)
+ throws SAXException {
+ this.locatorDelegate = locator;
+ contentHandler.characters(ch, start, length);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#endDocument()
+ */
+ void endDocument(Locator locator) throws SAXException {
+ this.locatorDelegate = locator;
+ contentHandler.endDocument();
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#endElement(java.lang.String,
+ * java.lang.String, java.lang.String)
+ */
+ void endElement(String uri, String localName, String qName, Locator locator)
+ throws SAXException {
+ this.locatorDelegate = locator;
+ contentHandler.endElement(uri, localName, qName);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
+ */
+ void endPrefixMapping(String prefix, Locator locator) throws SAXException {
+ this.locatorDelegate = locator;
+ contentHandler.endPrefixMapping(prefix);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
+ */
+ void ignorableWhitespace(char[] ch, int start, int length, Locator locator)
+ throws SAXException {
+ this.locatorDelegate = locator;
+ contentHandler.ignorableWhitespace(ch, start, length);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String,
+ * java.lang.String)
+ */
+ void processingInstruction(String target, String data, Locator locator)
+ throws SAXException {
+ this.locatorDelegate = locator;
+ contentHandler.processingInstruction(target, data);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String)
+ */
+ void skippedEntity(String name, Locator locator) throws SAXException {
+ this.locatorDelegate = locator;
+ contentHandler.skippedEntity(name);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#startDocument()
+ */
+ void startDocument(Locator locator) throws SAXException {
+ this.locatorDelegate = locator;
+ contentHandler.startDocument();
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#startElement(java.lang.String,
+ * java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ void startElement(String uri, String localName, String qName,
+ Attributes atts, Locator locator) throws SAXException {
+ this.locatorDelegate = locator;
+ contentHandler.startElement(uri, localName, qName, atts);
+ }
+
+ /**
+ * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String,
+ * java.lang.String)
+ */
+ void startPrefixMapping(String prefix, String uri, Locator locator)
+ throws SAXException {
+ this.locatorDelegate = locator;
+ contentHandler.startPrefixMapping(prefix, uri);
+ }
+
+ /**
+ * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
+ */
+ void comment(char[] ch, int start, int length, Locator locator)
+ throws SAXException {
+ this.locatorDelegate = locator;
+ lexicalHandler.comment(ch, start, length);
+ }
+
+ /**
+ * @see org.xml.sax.ext.LexicalHandler#endCDATA()
+ */
+ void endCDATA(Locator locator) throws SAXException {
+ this.locatorDelegate = locator;
+ lexicalHandler.endCDATA();
+ }
+
+ /**
+ * @see org.xml.sax.ext.LexicalHandler#endDTD()
+ */
+ void endDTD(Locator locator) throws SAXException {
+ this.locatorDelegate = locator;
+ lexicalHandler.endDTD();
+ }
+
+ /**
+ * @see org.xml.sax.ext.LexicalHandler#endEntity(java.lang.String)
+ */
+ void endEntity(String name, Locator locator) throws SAXException {
+ this.locatorDelegate = locator;
+ lexicalHandler.endEntity(name);
+ }
+
+ /**
+ * @see org.xml.sax.ext.LexicalHandler#startCDATA()
+ */
+ void startCDATA(Locator locator) throws SAXException {
+ this.locatorDelegate = locator;
+ lexicalHandler.startCDATA();
+ }
+
+ /**
+ * @see org.xml.sax.ext.LexicalHandler#startDTD(java.lang.String,
+ * java.lang.String, java.lang.String)
+ */
+ void startDTD(String name, String publicId, String systemId, Locator locator)
+ throws SAXException {
+ this.locatorDelegate = locator;
+ lexicalHandler.startDTD(name, publicId, systemId);
+ }
+
+ /**
+ * @see org.xml.sax.ext.LexicalHandler#startEntity(java.lang.String)
+ */
+ void startEntity(String name, Locator locator) throws SAXException {
+ this.locatorDelegate = locator;
+ lexicalHandler.startEntity(name);
+ }
+
+ /**
+ * @see org.xml.sax.Locator#getColumnNumber()
+ */
+ public int getColumnNumber() {
+ if (locatorDelegate == null) {
+ return -1;
+ } else {
+ return locatorDelegate.getColumnNumber();
+ }
+ }
+
+ /**
+ * @see org.xml.sax.Locator#getLineNumber()
+ */
+ public int getLineNumber() {
+ if (locatorDelegate == null) {
+ return -1;
+ } else {
+ return locatorDelegate.getLineNumber();
+ }
+ }
+
+ /**
+ * @see org.xml.sax.Locator#getPublicId()
+ */
+ public String getPublicId() {
+ if (locatorDelegate == null) {
+ return null;
+ } else {
+
+ return locatorDelegate.getPublicId();
+ }
+ }
+
+ /**
+ * @see org.xml.sax.Locator#getSystemId()
+ */
+ public String getSystemId() {
+ if (locatorDelegate == null) {
+ return null;
+ } else {
+ return locatorDelegate.getSystemId();
+ }
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/package.html b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/package.html
new file mode 100644
index 000000000..0c34dad81
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/src/nu/validator/saxtree/package.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head><title>Package Overview</title>
+<!--
+ Copyright (c) 2007 Henri Sivonen
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+-->
+</head>
+<body bgcolor="white">
+<p>This package provides SAX Tree: a tree model optimized for creation from SAX
+events and replay as SAX events.</p>
+<h2>Design Principles</h2>
+<ol>
+<li>Preserve information exposed through <code>ContentHandler</code>,
+<code>LexicalHandler</code> <em>and</em> <code>Locator</code>.
+<li>Creation from SAX events or as part of the parse of a conforming
+HTML5 document should be <em>fast</em>.</li>
+<li>Emitting SAX events based on the tree should be <em>fast</em>.</li>
+<li>Mutations should be <em>possible</em> but should not make the above
+"fast" cases slower.</li>
+<li>Concurrent reads should work without locking when there are no
+concurrent mutations.</li>
+<li>The user of the API has the responsibility of using the API properly:
+for the sake of performance, the model does not check if it is being
+used properly. Improper use may, therefore, put the model in and
+inconsistent state.</li>
+</ol>
+</body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/java/io/IOException.java b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/java/io/IOException.java
new file mode 100644
index 000000000..f323f1e31
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/java/io/IOException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package java.io;
+
+public class IOException extends Exception {
+
+ public IOException() {
+ }
+
+ public IOException(String arg0) {
+ super(arg0);
+ }
+
+ public IOException(Throwable arg0) {
+ super(arg0);
+ }
+
+ public IOException(String arg0, Throwable arg1) {
+ super(arg0, arg1);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/Attributes.java b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/Attributes.java
new file mode 100644
index 000000000..b25432d45
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/Attributes.java
@@ -0,0 +1,257 @@
+// Attributes.java - attribute list with Namespace support
+// http://www.saxproject.org
+// Written by David Megginson
+// NO WARRANTY! This class is in the public domain.
+// $Id: Attributes.java,v 1.13 2004/03/18 12:28:05 dmegginson Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Interface for a list of XML attributes.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This interface allows access to a list of attributes in
+ * three different ways:</p>
+ *
+ * <ol>
+ * <li>by attribute index;</li>
+ * <li>by Namespace-qualified name; or</li>
+ * <li>by qualified (prefixed) name.</li>
+ * </ol>
+ *
+ * <p>The list will not contain attributes that were declared
+ * #IMPLIED but not specified in the start tag. It will also not
+ * contain attributes used as Namespace declarations (xmlns*) unless
+ * the <code>http://xml.org/sax/features/namespace-prefixes</code>
+ * feature is set to <var>true</var> (it is <var>false</var> by
+ * default).
+ * Because SAX2 conforms to the original "Namespaces in XML"
+ * recommendation, it normally does not
+ * give namespace declaration attributes a namespace URI.
+ * </p>
+ *
+ * <p>Some SAX2 parsers may support using an optional feature flag
+ * (<code>http://xml.org/sax/features/xmlns-uris</code>) to request
+ * that those attributes be given URIs, conforming to a later
+ * backwards-incompatible revision of that recommendation. (The
+ * attribute's "local name" will be the prefix, or "xmlns" when
+ * defining a default element namespace.) For portability, handler
+ * code should always resolve that conflict, rather than requiring
+ * parsers that can change the setting of that feature flag. </p>
+ *
+ * <p>If the namespace-prefixes feature (see above) is
+ * <var>false</var>, access by qualified name may not be available; if
+ * the <code>http://xml.org/sax/features/namespaces</code> feature is
+ * <var>false</var>, access by Namespace-qualified names may not be
+ * available.</p>
+ *
+ * <p>This interface replaces the now-deprecated SAX1 {@link
+ * org.xml.sax.AttributeList AttributeList} interface, which does not
+ * contain Namespace support. In addition to Namespace support, it
+ * adds the <var>getIndex</var> methods (below).</p>
+ *
+ * <p>The order of attributes in the list is unspecified, and will
+ * vary from implementation to implementation.</p>
+ *
+ * @since SAX 2.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.helpers.AttributesImpl
+ * @see org.xml.sax.ext.DeclHandler#attributeDecl
+ */
+public interface Attributes
+{
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Indexed access.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Return the number of attributes in the list.
+ *
+ * <p>Once you know the number of attributes, you can iterate
+ * through the list.</p>
+ *
+ * @return The number of attributes in the list.
+ * @see #getURI(int)
+ * @see #getLocalName(int)
+ * @see #getQName(int)
+ * @see #getType(int)
+ * @see #getValue(int)
+ */
+ public abstract int getLength ();
+
+
+ /**
+ * Look up an attribute's Namespace URI by index.
+ *
+ * @param index The attribute index (zero-based).
+ * @return The Namespace URI, or the empty string if none
+ * is available, or null if the index is out of
+ * range.
+ * @see #getLength
+ */
+ public abstract String getURI (int index);
+
+
+ /**
+ * Look up an attribute's local name by index.
+ *
+ * @param index The attribute index (zero-based).
+ * @return The local name, or the empty string if Namespace
+ * processing is not being performed, or null
+ * if the index is out of range.
+ * @see #getLength
+ */
+ public abstract String getLocalName (int index);
+
+
+ /**
+ * Look up an attribute's XML qualified (prefixed) name by index.
+ *
+ * @param index The attribute index (zero-based).
+ * @return The XML qualified name, or the empty string
+ * if none is available, or null if the index
+ * is out of range.
+ * @see #getLength
+ */
+ public abstract String getQName (int index);
+
+
+ /**
+ * Look up an attribute's type by index.
+ *
+ * <p>The attribute type is one of the strings "CDATA", "ID",
+ * "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES",
+ * or "NOTATION" (always in upper case).</p>
+ *
+ * <p>If the parser has not read a declaration for the attribute,
+ * or if the parser does not report attribute types, then it must
+ * return the value "CDATA" as stated in the XML 1.0 Recommendation
+ * (clause 3.3.3, "Attribute-Value Normalization").</p>
+ *
+ * <p>For an enumerated attribute that is not a notation, the
+ * parser will report the type as "NMTOKEN".</p>
+ *
+ * @param index The attribute index (zero-based).
+ * @return The attribute's type as a string, or null if the
+ * index is out of range.
+ * @see #getLength
+ */
+ public abstract String getType (int index);
+
+
+ /**
+ * Look up an attribute's value by index.
+ *
+ * <p>If the attribute value is a list of tokens (IDREFS,
+ * ENTITIES, or NMTOKENS), the tokens will be concatenated
+ * into a single string with each token separated by a
+ * single space.</p>
+ *
+ * @param index The attribute index (zero-based).
+ * @return The attribute's value as a string, or null if the
+ * index is out of range.
+ * @see #getLength
+ */
+ public abstract String getValue (int index);
+
+
+
+ ////////////////////////////////////////////////////////////////////
+ // Name-based query.
+ ////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Look up the index of an attribute by Namespace name.
+ *
+ * @param uri The Namespace URI, or the empty string if
+ * the name has no Namespace URI.
+ * @param localName The attribute's local name.
+ * @return The index of the attribute, or -1 if it does not
+ * appear in the list.
+ */
+ public int getIndex (String uri, String localName);
+
+
+ /**
+ * Look up the index of an attribute by XML qualified (prefixed) name.
+ *
+ * @param qName The qualified (prefixed) name.
+ * @return The index of the attribute, or -1 if it does not
+ * appear in the list.
+ */
+ public int getIndex (String qName);
+
+
+ /**
+ * Look up an attribute's type by Namespace name.
+ *
+ * <p>See {@link #getType(int) getType(int)} for a description
+ * of the possible types.</p>
+ *
+ * @param uri The Namespace URI, or the empty String if the
+ * name has no Namespace URI.
+ * @param localName The local name of the attribute.
+ * @return The attribute type as a string, or null if the
+ * attribute is not in the list or if Namespace
+ * processing is not being performed.
+ */
+ public abstract String getType (String uri, String localName);
+
+
+ /**
+ * Look up an attribute's type by XML qualified (prefixed) name.
+ *
+ * <p>See {@link #getType(int) getType(int)} for a description
+ * of the possible types.</p>
+ *
+ * @param qName The XML qualified name.
+ * @return The attribute type as a string, or null if the
+ * attribute is not in the list or if qualified names
+ * are not available.
+ */
+ public abstract String getType (String qName);
+
+
+ /**
+ * Look up an attribute's value by Namespace name.
+ *
+ * <p>See {@link #getValue(int) getValue(int)} for a description
+ * of the possible values.</p>
+ *
+ * @param uri The Namespace URI, or the empty String if the
+ * name has no Namespace URI.
+ * @param localName The local name of the attribute.
+ * @return The attribute value as a string, or null if the
+ * attribute is not in the list.
+ */
+ public abstract String getValue (String uri, String localName);
+
+
+ /**
+ * Look up an attribute's value by XML qualified (prefixed) name.
+ *
+ * <p>See {@link #getValue(int) getValue(int)} for a description
+ * of the possible values.</p>
+ *
+ * @param qName The XML qualified name.
+ * @return The attribute value as a string, or null if the
+ * attribute is not in the list or if qualified names
+ * are not available.
+ */
+ public abstract String getValue (String qName);
+
+}
+
+// end of Attributes.java
diff --git a/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/ErrorHandler.java b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/ErrorHandler.java
new file mode 100644
index 000000000..37d250143
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/ErrorHandler.java
@@ -0,0 +1,139 @@
+// SAX error handler.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: ErrorHandler.java,v 1.10 2004/03/08 13:01:00 dmegginson Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Basic interface for SAX error handlers.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>If a SAX application needs to implement customized error
+ * handling, it must implement this interface and then register an
+ * instance with the XML reader using the
+ * {@link org.xml.sax.XMLReader#setErrorHandler setErrorHandler}
+ * method. The parser will then report all errors and warnings
+ * through this interface.</p>
+ *
+ * <p><strong>WARNING:</strong> If an application does <em>not</em>
+ * register an ErrorHandler, XML parsing errors will go unreported,
+ * except that <em>SAXParseException</em>s will be thrown for fatal errors.
+ * In order to detect validity errors, an ErrorHandler that does something
+ * with {@link #error error()} calls must be registered.</p>
+ *
+ * <p>For XML processing errors, a SAX driver must use this interface
+ * in preference to throwing an exception: it is up to the application
+ * to decide whether to throw an exception for different types of
+ * errors and warnings. Note, however, that there is no requirement that
+ * the parser continue to report additional errors after a call to
+ * {@link #fatalError fatalError}. In other words, a SAX driver class
+ * may throw an exception after reporting any fatalError.
+ * Also parsers may throw appropriate exceptions for non-XML errors.
+ * For example, {@link XMLReader#parse XMLReader.parse()} would throw
+ * an IOException for errors accessing entities or the document.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1+ (sax2r3pre1)
+ * @see org.xml.sax.XMLReader#setErrorHandler
+ * @see org.xml.sax.SAXParseException
+ */
+public interface ErrorHandler {
+
+
+ /**
+ * Receive notification of a warning.
+ *
+ * <p>SAX parsers will use this method to report conditions that
+ * are not errors or fatal errors as defined by the XML
+ * recommendation. The default behaviour is to take no
+ * action.</p>
+ *
+ * <p>The SAX parser must continue to provide normal parsing events
+ * after invoking this method: it should still be possible for the
+ * application to process the document through to the end.</p>
+ *
+ * <p>Filters may use this method to report other, non-XML warnings
+ * as well.</p>
+ *
+ * @param exception The warning information encapsulated in a
+ * SAX parse exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.SAXParseException
+ */
+ public abstract void warning (SAXParseException exception)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of a recoverable error.
+ *
+ * <p>This corresponds to the definition of "error" in section 1.2
+ * of the W3C XML 1.0 Recommendation. For example, a validating
+ * parser would use this callback to report the violation of a
+ * validity constraint. The default behaviour is to take no
+ * action.</p>
+ *
+ * <p>The SAX parser must continue to provide normal parsing
+ * events after invoking this method: it should still be possible
+ * for the application to process the document through to the end.
+ * If the application cannot do so, then the parser should report
+ * a fatal error even if the XML recommendation does not require
+ * it to do so.</p>
+ *
+ * <p>Filters may use this method to report other, non-XML errors
+ * as well.</p>
+ *
+ * @param exception The error information encapsulated in a
+ * SAX parse exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.SAXParseException
+ */
+ public abstract void error (SAXParseException exception)
+ throws SAXException;
+
+
+ /**
+ * Receive notification of a non-recoverable error.
+ *
+ * <p><strong>There is an apparent contradiction between the
+ * documentation for this method and the documentation for {@link
+ * org.xml.sax.ContentHandler#endDocument}. Until this ambiguity
+ * is resolved in a future major release, clients should make no
+ * assumptions about whether endDocument() will or will not be
+ * invoked when the parser has reported a fatalError() or thrown
+ * an exception.</strong></p>
+ *
+ * <p>This corresponds to the definition of "fatal error" in
+ * section 1.2 of the W3C XML 1.0 Recommendation. For example, a
+ * parser would use this callback to report the violation of a
+ * well-formedness constraint.</p>
+ *
+ * <p>The application must assume that the document is unusable
+ * after the parser has invoked this method, and should continue
+ * (if at all) only for the sake of collecting additional error
+ * messages: in fact, SAX parsers are free to stop reporting any
+ * other events once this method has been invoked.</p>
+ *
+ * @param exception The error information encapsulated in a
+ * SAX parse exception.
+ * @exception org.xml.sax.SAXException Any SAX exception, possibly
+ * wrapping another exception.
+ * @see org.xml.sax.SAXParseException
+ */
+ public abstract void fatalError (SAXParseException exception)
+ throws SAXException;
+
+}
+
+// end of ErrorHandler.java
diff --git a/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/Locator.java b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/Locator.java
new file mode 100644
index 000000000..f8f3484c1
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/Locator.java
@@ -0,0 +1,136 @@
+// SAX locator interface for document events.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: Locator.java,v 1.8 2002/01/30 21:13:47 dbrownell Exp $
+
+package org.xml.sax;
+
+
+/**
+ * Interface for associating a SAX event with a document location.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>If a SAX parser provides location information to the SAX
+ * application, it does so by implementing this interface and then
+ * passing an instance to the application using the content
+ * handler's {@link org.xml.sax.ContentHandler#setDocumentLocator
+ * setDocumentLocator} method. The application can use the
+ * object to obtain the location of any other SAX event
+ * in the XML source document.</p>
+ *
+ * <p>Note that the results returned by the object will be valid only
+ * during the scope of each callback method: the application
+ * will receive unpredictable results if it attempts to use the
+ * locator at any other time, or after parsing completes.</p>
+ *
+ * <p>SAX parsers are not required to supply a locator, but they are
+ * very strongly encouraged to do so. If the parser supplies a
+ * locator, it must do so before reporting any other document events.
+ * If no locator has been set by the time the application receives
+ * the {@link org.xml.sax.ContentHandler#startDocument startDocument}
+ * event, the application should assume that a locator is not
+ * available.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.ContentHandler#setDocumentLocator
+ */
+public interface Locator {
+
+
+ /**
+ * Return the public identifier for the current document event.
+ *
+ * <p>The return value is the public identifier of the document
+ * entity or of the external parsed entity in which the markup
+ * triggering the event appears.</p>
+ *
+ * @return A string containing the public identifier, or
+ * null if none is available.
+ * @see #getSystemId
+ */
+ public abstract String getPublicId ();
+
+
+ /**
+ * Return the system identifier for the current document event.
+ *
+ * <p>The return value is the system identifier of the document
+ * entity or of the external parsed entity in which the markup
+ * triggering the event appears.</p>
+ *
+ * <p>If the system identifier is a URL, the parser must resolve it
+ * fully before passing it to the application. For example, a file
+ * name must always be provided as a <em>file:...</em> URL, and other
+ * kinds of relative URI are also resolved against their bases.</p>
+ *
+ * @return A string containing the system identifier, or null
+ * if none is available.
+ * @see #getPublicId
+ */
+ public abstract String getSystemId ();
+
+
+ /**
+ * Return the line number where the current document event ends.
+ * Lines are delimited by line ends, which are defined in
+ * the XML specification.
+ *
+ * <p><strong>Warning:</strong> The return value from the method
+ * is intended only as an approximation for the sake of diagnostics;
+ * it is not intended to provide sufficient information
+ * to edit the character content of the original XML document.
+ * In some cases, these "line" numbers match what would be displayed
+ * as columns, and in others they may not match the source text
+ * due to internal entity expansion. </p>
+ *
+ * <p>The return value is an approximation of the line number
+ * in the document entity or external parsed entity where the
+ * markup triggering the event appears.</p>
+ *
+ * <p>If possible, the SAX driver should provide the line position
+ * of the first character after the text associated with the document
+ * event. The first line is line 1.</p>
+ *
+ * @return The line number, or -1 if none is available.
+ * @see #getColumnNumber
+ */
+ public abstract int getLineNumber ();
+
+
+ /**
+ * Return the column number where the current document event ends.
+ * This is one-based number of Java <code>char</code> values since
+ * the last line end.
+ *
+ * <p><strong>Warning:</strong> The return value from the method
+ * is intended only as an approximation for the sake of diagnostics;
+ * it is not intended to provide sufficient information
+ * to edit the character content of the original XML document.
+ * For example, when lines contain combining character sequences, wide
+ * characters, surrogate pairs, or bi-directional text, the value may
+ * not correspond to the column in a text editor's display. </p>
+ *
+ * <p>The return value is an approximation of the column number
+ * in the document entity or external parsed entity where the
+ * markup triggering the event appears.</p>
+ *
+ * <p>If possible, the SAX driver should provide the line position
+ * of the first character after the text associated with the document
+ * event. The first column in each line is column 1.</p>
+ *
+ * @return The column number, or -1 if none is available.
+ * @see #getLineNumber
+ */
+ public abstract int getColumnNumber ();
+
+}
+
+// end of Locator.java
diff --git a/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/SAXException.java b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/SAXException.java
new file mode 100644
index 000000000..256719cef
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/SAXException.java
@@ -0,0 +1,153 @@
+// SAX exception class.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: SAXException.java,v 1.7 2002/01/30 21:13:48 dbrownell Exp $
+
+package org.xml.sax;
+
+/**
+ * Encapsulate a general SAX error or warning.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This class can contain basic error or warning information from
+ * either the XML parser or the application: a parser writer or
+ * application writer can subclass it to provide additional
+ * functionality. SAX handlers may throw this exception or
+ * any exception subclassed from it.</p>
+ *
+ * <p>If the application needs to pass through other types of
+ * exceptions, it must wrap those exceptions in a SAXException
+ * or an exception derived from a SAXException.</p>
+ *
+ * <p>If the parser or application needs to include information about a
+ * specific location in an XML document, it should use the
+ * {@link org.xml.sax.SAXParseException SAXParseException} subclass.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.SAXParseException
+ */
+public class SAXException extends Exception {
+
+
+ /**
+ * Create a new SAXException.
+ */
+ public SAXException ()
+ {
+ super();
+ this.exception = null;
+ }
+
+
+ /**
+ * Create a new SAXException.
+ *
+ * @param message The error or warning message.
+ */
+ public SAXException (String message) {
+ super(message);
+ this.exception = null;
+ }
+
+
+ /**
+ * Create a new SAXException wrapping an existing exception.
+ *
+ * <p>The existing exception will be embedded in the new
+ * one, and its message will become the default message for
+ * the SAXException.</p>
+ *
+ * @param e The exception to be wrapped in a SAXException.
+ */
+ public SAXException (Exception e)
+ {
+ super();
+ this.exception = e;
+ }
+
+
+ /**
+ * Create a new SAXException from an existing exception.
+ *
+ * <p>The existing exception will be embedded in the new
+ * one, but the new exception will have its own message.</p>
+ *
+ * @param message The detail message.
+ * @param e The exception to be wrapped in a SAXException.
+ */
+ public SAXException (String message, Exception e)
+ {
+ super(message);
+ this.exception = e;
+ }
+
+
+ /**
+ * Return a detail message for this exception.
+ *
+ * <p>If there is an embedded exception, and if the SAXException
+ * has no detail message of its own, this method will return
+ * the detail message from the embedded exception.</p>
+ *
+ * @return The error or warning message.
+ */
+ public String getMessage ()
+ {
+ String message = super.getMessage();
+
+ if (message == null && exception != null) {
+ return exception.getMessage();
+ } else {
+ return message;
+ }
+ }
+
+
+ /**
+ * Return the embedded exception, if any.
+ *
+ * @return The embedded exception, or null if there is none.
+ */
+ public Exception getException ()
+ {
+ return exception;
+ }
+
+
+ /**
+ * Override toString to pick up any embedded exception.
+ *
+ * @return A string representation of this exception.
+ */
+ public String toString ()
+ {
+ if (exception != null) {
+ return exception.toString();
+ } else {
+ return super.toString();
+ }
+ }
+
+
+
+ //////////////////////////////////////////////////////////////////////
+ // Internal state.
+ //////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * @serial The embedded exception if tunnelling, or null.
+ */
+ private Exception exception;
+
+}
+
+// end of SAXException.java
diff --git a/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/SAXParseException.java b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/SAXParseException.java
new file mode 100644
index 000000000..1df5e1423
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/SAXParseException.java
@@ -0,0 +1,269 @@
+// SAX exception class.
+// http://www.saxproject.org
+// No warranty; no copyright -- use this as you will.
+// $Id: SAXParseException.java,v 1.11 2004/04/21 13:05:02 dmegginson Exp $
+
+package org.xml.sax;
+
+/**
+ * Encapsulate an XML parse error or warning.
+ *
+ * <blockquote>
+ * <em>This module, both source code and documentation, is in the
+ * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
+ * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+ * for further information.
+ * </blockquote>
+ *
+ * <p>This exception may include information for locating the error
+ * in the original XML document, as if it came from a {@link Locator}
+ * object. Note that although the application
+ * will receive a SAXParseException as the argument to the handlers
+ * in the {@link org.xml.sax.ErrorHandler ErrorHandler} interface,
+ * the application is not actually required to throw the exception;
+ * instead, it can simply read the information in it and take a
+ * different action.</p>
+ *
+ * <p>Since this exception is a subclass of {@link org.xml.sax.SAXException
+ * SAXException}, it inherits the ability to wrap another exception.</p>
+ *
+ * @since SAX 1.0
+ * @author David Megginson
+ * @version 2.0.1 (sax2r2)
+ * @see org.xml.sax.SAXException
+ * @see org.xml.sax.Locator
+ * @see org.xml.sax.ErrorHandler
+ */
+public class SAXParseException extends SAXException {
+
+
+ //////////////////////////////////////////////////////////////////////
+ // Constructors.
+ //////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Create a new SAXParseException from a message and a Locator.
+ *
+ * <p>This constructor is especially useful when an application is
+ * creating its own exception from within a {@link org.xml.sax.ContentHandler
+ * ContentHandler} callback.</p>
+ *
+ * @param message The error or warning message.
+ * @param locator The locator object for the error or warning (may be
+ * null).
+ * @see org.xml.sax.Locator
+ */
+ public SAXParseException (String message, Locator locator) {
+ super(message);
+ if (locator != null) {
+ init(locator.getPublicId(), locator.getSystemId(),
+ locator.getLineNumber(), locator.getColumnNumber());
+ } else {
+ init(null, null, -1, -1);
+ }
+ }
+
+
+ /**
+ * Wrap an existing exception in a SAXParseException.
+ *
+ * <p>This constructor is especially useful when an application is
+ * creating its own exception from within a {@link org.xml.sax.ContentHandler
+ * ContentHandler} callback, and needs to wrap an existing exception that is not a
+ * subclass of {@link org.xml.sax.SAXException SAXException}.</p>
+ *
+ * @param message The error or warning message, or null to
+ * use the message from the embedded exception.
+ * @param locator The locator object for the error or warning (may be
+ * null).
+ * @param e Any exception.
+ * @see org.xml.sax.Locator
+ */
+ public SAXParseException (String message, Locator locator,
+ Exception e) {
+ super(message, e);
+ if (locator != null) {
+ init(locator.getPublicId(), locator.getSystemId(),
+ locator.getLineNumber(), locator.getColumnNumber());
+ } else {
+ init(null, null, -1, -1);
+ }
+ }
+
+
+ /**
+ * Create a new SAXParseException.
+ *
+ * <p>This constructor is most useful for parser writers.</p>
+ *
+ * <p>All parameters except the message are as if
+ * they were provided by a {@link Locator}. For example, if the
+ * system identifier is a URL (including relative filename), the
+ * caller must resolve it fully before creating the exception.</p>
+ *
+ *
+ * @param message The error or warning message.
+ * @param publicId The public identifier of the entity that generated
+ * the error or warning.
+ * @param systemId The system identifier of the entity that generated
+ * the error or warning.
+ * @param lineNumber The line number of the end of the text that
+ * caused the error or warning.
+ * @param columnNumber The column number of the end of the text that
+ * cause the error or warning.
+ */
+ public SAXParseException (String message, String publicId, String systemId,
+ int lineNumber, int columnNumber)
+ {
+ super(message);
+ init(publicId, systemId, lineNumber, columnNumber);
+ }
+
+
+ /**
+ * Create a new SAXParseException with an embedded exception.
+ *
+ * <p>This constructor is most useful for parser writers who
+ * need to wrap an exception that is not a subclass of
+ * {@link org.xml.sax.SAXException SAXException}.</p>
+ *
+ * <p>All parameters except the message and exception are as if
+ * they were provided by a {@link Locator}. For example, if the
+ * system identifier is a URL (including relative filename), the
+ * caller must resolve it fully before creating the exception.</p>
+ *
+ * @param message The error or warning message, or null to use
+ * the message from the embedded exception.
+ * @param publicId The public identifier of the entity that generated
+ * the error or warning.
+ * @param systemId The system identifier of the entity that generated
+ * the error or warning.
+ * @param lineNumber The line number of the end of the text that
+ * caused the error or warning.
+ * @param columnNumber The column number of the end of the text that
+ * cause the error or warning.
+ * @param e Another exception to embed in this one.
+ */
+ public SAXParseException (String message, String publicId, String systemId,
+ int lineNumber, int columnNumber, Exception e)
+ {
+ super(message, e);
+ init(publicId, systemId, lineNumber, columnNumber);
+ }
+
+
+ /**
+ * Internal initialization method.
+ *
+ * @param publicId The public identifier of the entity which generated the exception,
+ * or null.
+ * @param systemId The system identifier of the entity which generated the exception,
+ * or null.
+ * @param lineNumber The line number of the error, or -1.
+ * @param columnNumber The column number of the error, or -1.
+ */
+ private void init (String publicId, String systemId,
+ int lineNumber, int columnNumber)
+ {
+ this.publicId = publicId;
+ this.systemId = systemId;
+ this.lineNumber = lineNumber;
+ this.columnNumber = columnNumber;
+ }
+
+
+ /**
+ * Get the public identifier of the entity where the exception occurred.
+ *
+ * @return A string containing the public identifier, or null
+ * if none is available.
+ * @see org.xml.sax.Locator#getPublicId
+ */
+ public String getPublicId ()
+ {
+ return this.publicId;
+ }
+
+
+ /**
+ * Get the system identifier of the entity where the exception occurred.
+ *
+ * <p>If the system identifier is a URL, it will have been resolved
+ * fully.</p>
+ *
+ * @return A string containing the system identifier, or null
+ * if none is available.
+ * @see org.xml.sax.Locator#getSystemId
+ */
+ public String getSystemId ()
+ {
+ return this.systemId;
+ }
+
+
+ /**
+ * The line number of the end of the text where the exception occurred.
+ *
+ * <p>The first line is line 1.</p>
+ *
+ * @return An integer representing the line number, or -1
+ * if none is available.
+ * @see org.xml.sax.Locator#getLineNumber
+ */
+ public int getLineNumber ()
+ {
+ return this.lineNumber;
+ }
+
+
+ /**
+ * The column number of the end of the text where the exception occurred.
+ *
+ * <p>The first column in a line is position 1.</p>
+ *
+ * @return An integer representing the column number, or -1
+ * if none is available.
+ * @see org.xml.sax.Locator#getColumnNumber
+ */
+ public int getColumnNumber ()
+ {
+ return this.columnNumber;
+ }
+
+
+ //////////////////////////////////////////////////////////////////////
+ // Internal state.
+ //////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * @serial The public identifier, or null.
+ * @see #getPublicId
+ */
+ private String publicId;
+
+
+ /**
+ * @serial The system identifier, or null.
+ * @see #getSystemId
+ */
+ private String systemId;
+
+
+ /**
+ * @serial The line number, or -1.
+ * @see #getLineNumber
+ */
+ private int lineNumber;
+
+
+ /**
+ * @serial The column number, or -1.
+ * @see #getColumnNumber
+ */
+ private int columnNumber;
+
+}
+
+// end of SAXParseException.java
diff --git a/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/package.html b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/package.html
new file mode 100644
index 000000000..dd7030e24
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/super/nu/validator/htmlparser/translatable/org/xml/sax/package.html
@@ -0,0 +1,297 @@
+<html><head>
+<!-- $Id: package.html,v 1.18 2004/04/21 13:06:01 dmegginson Exp $ -->
+</head><body>
+
+<p> This package provides the core SAX APIs.
+Some SAX1 APIs are deprecated to encourage integration of
+namespace-awareness into designs of new applications
+and into maintenance of existing infrastructure. </p>
+
+<p>See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
+for more information about SAX.</p>
+
+
+<h2> SAX2 Standard Feature Flags </h2>
+
+<p> One of the essential characteristics of SAX2 is that it added
+feature flags which can be used to examine and perhaps modify
+parser modes, in particular modes such as validation.
+Since features are identified by (absolute) URIs, anyone
+can define such features.
+Currently defined standard feature URIs have the prefix
+<code>http://xml.org/sax/features/</code> before an identifier such as
+<code>validation</code>. Turn features on or off using
+<em>setFeature</em>. Those standard identifiers are: </p>
+
+
+<table border="1" cellpadding="3" cellspacing="0" width="100%">
+ <tr align="center" bgcolor="#ccccff">
+ <th>Feature ID</th>
+ <th>Access</th>
+ <th>Default</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>external-general-entities</td>
+ <td><em>read/write</em></td>
+ <td><em>unspecified</em></td>
+ <td> Reports whether this parser processes external
+ general entities; always true if validating.
+ </td>
+ </tr>
+
+ <tr>
+ <td>external-parameter-entities</td>
+ <td><em>read/write</em></td>
+ <td><em>unspecified</em></td>
+ <td> Reports whether this parser processes external
+ parameter entities; always true if validating.
+ </td>
+ </tr>
+
+ <tr>
+ <td>is-standalone</td>
+ <td>(parsing) <em>read-only</em>, (not parsing) <em>none</em></td>
+ <td>not applicable</td>
+ <td> May be examined only during a parse, after the
+ <em>startDocument()</em> callback has been completed; read-only.
+ The value is true if the document specified standalone="yes" in
+ its XML declaration, and otherwise is false.
+ </td>
+ </tr>
+
+ <tr>
+ <td>lexical-handler/parameter-entities</td>
+ <td><em>read/write</em></td>
+ <td><em>unspecified</em></td>
+ <td> A value of "true" indicates that the LexicalHandler will report
+ the beginning and end of parameter entities.
+ </td>
+ </tr>
+
+ <tr>
+ <td>namespaces</td>
+ <td><em>read/write</em></td>
+ <td>true</td>
+ <td> A value of "true" indicates namespace URIs and unprefixed local names
+ for element and attribute names will be available.
+ </td>
+ </tr>
+
+ <tr>
+ <td>namespace-prefixes</td>
+ <td><em>read/write</em></td>
+ <td>false</td>
+ <td> A value of "true" indicates that XML qualified names (with prefixes) and
+ attributes (including <em>xmlns*</em> attributes) will be available.
+ </td>
+ </tr>
+
+ <tr>
+ <td>resolve-dtd-uris</td>
+ <td><em>read/write</em></td>
+ <td><em>true</em></td>
+ <td> A value of "true" indicates that system IDs in declarations will
+ be absolutized (relative to their base URIs) before reporting.
+ (That is the default behavior for all SAX2 XML parsers.)
+ A value of "false" indicates those IDs will not be absolutized;
+ parsers will provide the base URI from
+ <em>Locator.getSystemId()</em>.
+ This applies to system IDs passed in <ul>
+ <li><em>DTDHandler.notationDecl()</em>,
+ <li><em>DTDHandler.unparsedEntityDecl()</em>, and
+ <li><em>DeclHandler.externalEntityDecl()</em>.
+ </ul>
+ It does not apply to <em>EntityResolver.resolveEntity()</em>,
+ which is not used to report declarations, or to
+ <em>LexicalHandler.startDTD()</em>, which already provides
+ the non-absolutized URI.
+ </td>
+ </tr>
+
+ <tr>
+ <td>string-interning</td>
+ <td><em>read/write</em></td>
+ <td><em>unspecified</em></td>
+ <td> Has a value of "true" if all XML names (for elements, prefixes,
+ attributes, entities, notations, and local names),
+ as well as Namespace URIs, will have been interned
+ using <em>java.lang.String.intern</em>. This supports fast
+ testing of equality/inequality against string constants,
+ rather than forcing slower calls to <em>String.equals()</em>.
+ </td>
+ </tr>
+
+ <tr>
+ <td>unicode-normalization-checking</td>
+ <td><em>read/write</em></td>
+ <td><em>false</em></td>
+ <td> Controls whether the parser reports Unicode normalization
+ errors as described in section 2.13 and Appendix B of the
+ XML 1.1 Recommendation. If true, Unicode normalization
+ errors are reported using the ErrorHandler.error() callback.
+ Such errors are not fatal in themselves (though, obviously,
+ other Unicode-related encoding errors may be).
+ </td>
+ </tr>
+
+ <tr>
+ <td>use-attributes2</td>
+ <td><em>read-only</em></td>
+ <td>not applicable</td>
+ <td> Returns "true" if the <em>Attributes</em> objects passed by
+ this parser in <em>ContentHandler.startElement()</em>
+ implement the <a href="ext/Attributes2.html"
+ ><em>org.xml.sax.ext.Attributes2</em></a> interface.
+ That interface exposes additional DTD-related information,
+ such as whether the attribute was specified in the
+ source text rather than defaulted.
+ </td>
+ </tr>
+
+ <tr>
+ <td>use-locator2</td>
+ <td><em>read-only</em></td>
+ <td>not applicable</td>
+ <td> Returns "true" if the <em>Locator</em> objects passed by
+ this parser in <em>ContentHandler.setDocumentLocator()</em>
+ implement the <a href="ext/Locator2.html"
+ ><em>org.xml.sax.ext.Locator2</em></a> interface.
+ That interface exposes additional entity information,
+ such as the character encoding and XML version used.
+ </td>
+ </tr>
+
+ <tr>
+ <td>use-entity-resolver2</td>
+ <td><em>read/write</em></td>
+ <td><em>true</em></td>
+ <td> Returns "true" if, when <em>setEntityResolver</em> is given
+ an object implementing the <a href="ext/EntityResolver2.html"
+ ><em>org.xml.sax.ext.EntityResolver2</em></a> interface,
+ those new methods will be used.
+ Returns "false" to indicate that those methods will not be used.
+ </td>
+ </tr>
+
+ <tr>
+ <td>validation</td>
+ <td><em>read/write</em></td>
+ <td><em>unspecified</em></td>
+ <td> Controls whether the parser is reporting all validity
+ errors; if true, all external entities will be read.
+ </td>
+ </tr>
+
+ <tr>
+ <td>xmlns-uris</td>
+ <td><em>read/write</em></td>
+ <td><em>false</em></td>
+ <td> Controls whether, when the <em>namespace-prefixes</em> feature
+ is set, the parser treats namespace declaration attributes as
+ being in the <em>http://www.w3.org/2000/xmlns/</em> namespace.
+ By default, SAX2 conforms to the original "Namespaces in XML"
+ Recommendation, which explicitly states that such attributes are
+ not in any namespace.
+ Setting this optional flag to "true" makes the SAX2 events conform to
+ a later backwards-incompatible revision of that recommendation,
+ placing those attributes in a namespace.
+ </td>
+ </tr>
+
+ <tr>
+ <td>xml-1.1</td>
+ <td><em>read-only</em></td>
+ <td>not applicable</td>
+ <td> Returns "true" if the parser supports both XML 1.1 and XML 1.0.
+ Returns "false" if the parser supports only XML 1.0.
+ </td>
+ </tr>
+
+</table>
+
+<p> Support for the default values of the
+<em>namespaces</em> and <em>namespace-prefixes</em>
+properties is required.
+Support for any other feature flags is entirely optional.
+</p>
+
+<p> For default values not specified by SAX2,
+each XMLReader implementation specifies its default,
+or may choose not to expose the feature flag.
+Unless otherwise specified here,
+implementations may support changing current values
+of these standard feature flags, but not while parsing.
+</p>
+
+<h2> SAX2 Standard Handler and Property IDs </h2>
+
+<p> For parser interface characteristics that are described
+as objects, a separate namespace is defined. The
+objects in this namespace are again identified by URI, and
+the standard property URIs have the prefix
+<code>http://xml.org/sax/properties/</code> before an identifier such as
+<code>lexical-handler</code> or
+<code>dom-node</code>. Manage those properties using
+<em>setProperty()</em>. Those identifiers are: </p>
+
+<table border="1" cellpadding="3" cellspacing="0" width="100%">
+ <tr align="center" bgcolor="#ccccff">
+ <th>Property ID</th>
+ <th>Description</th>
+ </tr>
+
+ <tr>
+ <td>declaration-handler</td>
+ <td> Used to see most DTD declarations except those treated
+ as lexical ("document element name is ...") or which are
+ mandatory for all SAX parsers (<em>DTDHandler</em>).
+ The Object must implement <a href="ext/DeclHandler.html"
+ ><em>org.xml.sax.ext.DeclHandler</em></a>.
+ </td>
+ </tr>
+
+ <tr>
+ <td>document-xml-version</td>
+ <td> May be examined only during a parse, after the startDocument()
+ callback has been completed; read-only. This property is a
+ literal string describing the actual XML version of the document,
+ such as "1.0" or "1.1".
+ </td>
+ </tr>
+
+ <tr>
+ <td>dom-node</td>
+ <td> For "DOM Walker" style parsers, which ignore their
+ <em>parser.parse()</em> parameters, this is used to
+ specify the DOM (sub)tree being walked by the parser.
+ The Object must implement the
+ <em>org.w3c.dom.Node</em> interface.
+ </td>
+ </tr>
+
+ <tr>
+ <td>lexical-handler</td>
+ <td> Used to see some syntax events that are essential in some
+ applications: comments, CDATA delimiters, selected general
+ entity inclusions, and the start and end of the DTD
+ (and declaration of document element name).
+ The Object must implement <a href="ext/LexicalHandler.html"
+ ><em>org.xml.sax.ext.LexicalHandler</em></a>.
+ </td>
+ </tr>
+
+ <tr>
+ <td>xml-string</td>
+ <td> Readable only during a parser callback, this exposes a <b>TBS</b>
+ chunk of characters responsible for the current event. </td>
+ </tr>
+
+</table>
+
+<p> All of these standard properties are optional;
+XMLReader implementations need not support them.
+</p>
+
+</body></html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/encoding/test/Big5Tester.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/encoding/test/Big5Tester.java
new file mode 100644
index 000000000..395f9eb15
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/encoding/test/Big5Tester.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding.test;
+
+import nu.validator.encoding.Encoding;
+
+public class Big5Tester extends EncodingTester {
+
+ public static void main(String[] args) {
+ new Big5Tester().test();
+ }
+
+ private void test() {
+ // ASCII
+ decodeBig5("\u6162", "\u0061\u0062");
+ // Edge cases
+ decodeBig5("\u8740", "\u43F0");
+ decodeBig5("\uFEFE", "\u79D4");
+ decodeBig5("\uFEFD", "\uD864\uDD0D");
+ decodeBig5("\u8862", "\u00CA\u0304");
+ decodeBig5("\u8864", "\u00CA\u030C");
+ decodeBig5("\u8866", "\u00CA");
+ decodeBig5("\u88A3", "\u00EA\u0304");
+ decodeBig5("\u88A5", "\u00EA\u030C");
+ decodeBig5("\u88A7", "\u00EA");
+ decodeBig5("\u99D4", "\u8991");
+ decodeBig5("\u99D5", "\uD85E\uDD67");
+ decodeBig5("\u99D6", "\u8A29");
+ // Edge cases surrounded with ASCII
+ decodeBig5("\u6187\u4062", "\u0061\u43F0\u0062");
+ decodeBig5("\u61FE\uFE62", "\u0061\u79D4\u0062");
+ decodeBig5("\u61FE\uFD62", "\u0061\uD864\uDD0D\u0062");
+ decodeBig5("\u6188\u6262", "\u0061\u00CA\u0304\u0062");
+ decodeBig5("\u6188\u6462", "\u0061\u00CA\u030C\u0062");
+ decodeBig5("\u6188\u6662", "\u0061\u00CA\u0062");
+ decodeBig5("\u6188\uA362", "\u0061\u00EA\u0304\u0062");
+ decodeBig5("\u6188\uA562", "\u0061\u00EA\u030C\u0062");
+ decodeBig5("\u6188\uA762", "\u0061\u00EA\u0062");
+ decodeBig5("\u6199\uD462", "\u0061\u8991\u0062");
+ decodeBig5("\u6199\uD562", "\u0061\uD85E\uDD67\u0062");
+ decodeBig5("\u6199\uD662", "\u0061\u8A29\u0062");
+ // Bad sequences
+ decodeBig5("\u8061", "\uFFFD\u0061");
+ decodeBig5("\uFF61", "\uFFFD\u0061");
+ decodeBig5("\uFE39", "\uFFFD\u0039");
+ decodeBig5("\u8766", "\uFFFD\u0066");
+ decodeBig5("\u8140", "\uFFFD\u0040");
+ decodeBig5("\u6181", "\u0061\uFFFD");
+
+ // ASCII
+ encodeBig5("\u0061\u0062", "\u6162");
+ // Edge cases
+ encodeBig5("\u9EA6\u0061", "\u3F61");
+ encodeBig5("\uD858\uDE6B\u0061", "\u3F61");
+ encodeBig5("\u3000", "\uA140");
+ encodeBig5("\u20AC", "\uA3E1");
+ encodeBig5("\u4E00", "\uA440");
+ encodeBig5("\uD85D\uDE07", "\uC8A4");
+ encodeBig5("\uFFE2", "\uC8CD");
+ encodeBig5("\u79D4", "\uFEFE");
+ // Not in index
+ encodeBig5("\u2603\u0061", "\u3F61");
+ // duplicate low bits
+ encodeBig5("\uD840\uDFB5", "\uFD6A");
+ // prefer last
+ encodeBig5("\u2550", "\uF9F9");
+ }
+
+ private void decodeBig5(String input, String expectation) {
+ decode(input, expectation, Encoding.BIG5);
+ }
+
+ private void encodeBig5(String input, String expectation) {
+ encode(input, expectation, Encoding.BIG5);
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/encoding/test/EncodingTester.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/encoding/test/EncodingTester.java
new file mode 100644
index 000000000..a910a01e9
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/encoding/test/EncodingTester.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 2015 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.encoding.test;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+import nu.validator.encoding.Encoding;
+
+public class EncodingTester {
+
+ protected byte[] stringToBytes(String str) {
+ byte[] bytes = new byte[str.length() * 2];
+ for (int i = 0; i < str.length(); i++) {
+ int pair = (int) str.charAt(i);
+ bytes[i * 2] = (byte) (pair >> 8);
+ bytes[i * 2 + 1] = (byte) (pair & 0xFF);
+ }
+ return bytes;
+ }
+
+ protected void decode(String input, String expectation, Encoding encoding) {
+ // Use the convenience method from Charset
+
+ byte[] bytes = stringToBytes(input);
+ ByteBuffer byteBuf = ByteBuffer.wrap(bytes);
+ CharBuffer charBuf = encoding.decode(byteBuf);
+
+ if (charBuf.remaining() != expectation.length()) {
+ err("When decoding from a single long buffer, the output length was wrong. Expected: "
+ + expectation.length() + ", got: " + charBuf.remaining(),
+ bytes, expectation);
+ return;
+ }
+
+ for (int i = 0; i < expectation.length(); i++) {
+ char expect = expectation.charAt(i);
+ char actual = charBuf.get();
+ if (actual != expect) {
+ err("When decoding from a single long buffer, failed at position "
+ + i
+ + ", expected: "
+ + charToHex(expect)
+ + ", got: "
+ + charToHex(actual), bytes, expectation);
+ return;
+ }
+ }
+
+ // Decode with a 1-byte input buffer
+
+ byteBuf = ByteBuffer.allocate(1);
+ charBuf = CharBuffer.allocate(expectation.length() + 2);
+ CharsetDecoder decoder = encoding.newDecoder();
+ decoder.onMalformedInput(CodingErrorAction.REPLACE);
+ for (int i = 0; i < bytes.length; i++) {
+ byteBuf.position(0);
+ byteBuf.put(bytes[i]);
+ byteBuf.position(0);
+ CoderResult result = decoder.decode(byteBuf, charBuf,
+ (i + 1) == bytes.length);
+ if (result.isMalformed()) {
+ err("Decoder reported a malformed sequence when asked to replace at index: "
+ + i, bytes, expectation);
+ return;
+ } else if (result.isUnmappable()) {
+ err("Decoder claimed unmappable sequence, which none of these decoders should do.",
+ bytes, expectation);
+ return;
+ } else if (result.isOverflow()) {
+ err("Decoder claimed overflow when the output buffer is know to be large enough.",
+ bytes, expectation);
+ } else if (!result.isUnderflow()) {
+ err("Bogus coder result, expected underflow.", bytes,
+ expectation);
+ }
+ }
+ CoderResult result = decoder.flush(charBuf);
+ if (result.isMalformed()) {
+ err("Decoder reported a malformed sequence when asked to replace when flushing.",
+ bytes, expectation);
+ return;
+ } else if (result.isUnmappable()) {
+ err("Decoder claimed unmappable sequence when flushing, which none of these decoders should do.",
+ bytes, expectation);
+ return;
+ } else if (result.isOverflow()) {
+ err("Decoder claimed overflow when flushing when the output buffer is know to be large enough.",
+ bytes, expectation);
+ } else if (!result.isUnderflow()) {
+ err("Bogus coder result when flushing, expected underflow.", bytes,
+ expectation);
+ }
+
+ charBuf.limit(charBuf.position());
+ charBuf.position(0);
+
+ for (int i = 0; i < expectation.length(); i++) {
+ char expect = expectation.charAt(i);
+ char actual = charBuf.get();
+ if (actual != expect) {
+ err("When decoding one byte at a time in REPORT mode, failed at position "
+ + i
+ + ", expected: "
+ + charToHex(expect)
+ + ", got: "
+ + charToHex(actual), bytes, expectation);
+ return;
+ }
+ }
+
+ // Decode with 1-char output buffer
+
+ byteBuf = ByteBuffer.wrap(bytes);
+ charBuf = CharBuffer.allocate(1);
+
+ decoder.reset(); // Let's test this while at it
+ decoder.onMalformedInput(CodingErrorAction.REPLACE);
+ int codeUnitPos = 0;
+ while (byteBuf.hasRemaining()) {
+ charBuf.position(0);
+ charBuf.put('\u0000');
+ charBuf.position(0);
+ result = decoder.decode(byteBuf, charBuf, false);
+ if (result.isMalformed()) {
+ err("Decoder reported a malformed sequence when asked to replace at index (decoding one output code unit at a time): "
+ + byteBuf.position(), bytes, expectation);
+ return;
+ } else if (result.isUnmappable()) {
+ err("Decoder claimed unmappable sequence (decoding one output code unit at a time), which none of these decoders should do.",
+ bytes, expectation);
+ return;
+ } else if (result.isUnderflow()) {
+ if (byteBuf.hasRemaining()) {
+ err("When decoding one output code unit at a time, decoder claimed underflow when there was input remaining.",
+ bytes, expectation);
+ return;
+ }
+ } else if (!result.isOverflow()) {
+ err("Bogus coder result, expected overflow.", bytes,
+ expectation);
+ }
+ if (charBuf.position() == 1) {
+ charBuf.position(0);
+ char actual = charBuf.get();
+ char expect = expectation.charAt(codeUnitPos);
+ if (actual != expect) {
+ err("When decoding one output code unit at a time in REPLACE mode, failed at position "
+ + byteBuf.position()
+ + ", expected: "
+ + charToHex(expect) + ", got: " + charToHex(actual),
+ bytes, expectation);
+ return;
+ }
+ codeUnitPos++;
+ }
+ }
+
+ charBuf.position(0);
+ charBuf.put('\u0000');
+ charBuf.position(0);
+ result = decoder.decode(byteBuf, charBuf, true);
+
+ if (charBuf.position() == 1) {
+ charBuf.position(0);
+ char actual = charBuf.get();
+ char expect = expectation.charAt(codeUnitPos);
+ if (actual != expect) {
+ err("When decoding one output code unit at a time in REPLACE mode, failed at position "
+ + byteBuf.position()
+ + ", expected: "
+ + charToHex(expect) + ", got: " + charToHex(actual),
+ bytes, expectation);
+ return;
+ }
+ codeUnitPos++;
+ }
+
+ charBuf.position(0);
+ charBuf.put('\u0000');
+ charBuf.position(0);
+ result = decoder.flush(charBuf);
+ if (result.isMalformed()) {
+ err("Decoder reported a malformed sequence when asked to replace when flushing (one output at a time).",
+ bytes, expectation);
+ return;
+ } else if (result.isUnmappable()) {
+ err("Decoder claimed unmappable sequence when flushing, which none of these decoders should do (one output at a time).",
+ bytes, expectation);
+ return;
+ } else if (result.isOverflow()) {
+ err("Decoder claimed overflow when flushing when the output buffer is know to be large enough (one output at a time).",
+ bytes, expectation);
+ } else if (!result.isUnderflow()) {
+ err("Bogus coder result when flushing, expected underflow (one output at a time).",
+ bytes, expectation);
+ }
+
+ if (charBuf.position() == 1) {
+ charBuf.position(0);
+ char actual = charBuf.get();
+ char expect = expectation.charAt(codeUnitPos);
+ if (actual != expect) {
+ err("When decoding one output code unit at a time in REPLACE mode, failed when flushing, expected: "
+ + charToHex(expect) + ", got: " + charToHex(actual),
+ bytes, expectation);
+ return;
+ }
+ }
+
+ // TODO: 2 bytes at a time starting at 0 and 2 bytes at a time starting
+ // at 1
+ }
+
+ protected void encode(String input, String expectation, Encoding encoding) {
+ byte[] expectedBytes = stringToBytes(expectation);
+ CharBuffer charBuf = CharBuffer.wrap(input);
+
+ // Use the convenience method from Charset
+
+ ByteBuffer byteBuf = encoding.encode(charBuf);
+
+ if (byteBuf.remaining() != expectedBytes.length) {
+ err("When encoding from a single long buffer, the output length was wrong. Expected: "
+ + expectedBytes.length + ", got: " + byteBuf.remaining(),
+ input, expectedBytes);
+ return;
+ }
+
+ for (int i = 0; i < expectedBytes.length; i++) {
+ byte expect = expectedBytes[i];
+ byte actual = byteBuf.get();
+ if (actual != expect) {
+ err("When encoding from a single long buffer, failed at position "
+ + i
+ + ", expected: "
+ + byteToHex(expect)
+ + ", got: "
+ + byteToHex(actual), input, expectedBytes);
+ return;
+ }
+ }
+
+ // Encode with a 1-char input buffer
+
+ charBuf = CharBuffer.allocate(1);
+ byteBuf = ByteBuffer.allocate(expectedBytes.length + 2);
+ CharsetEncoder encoder = encoding.newEncoder();
+ encoder.onMalformedInput(CodingErrorAction.REPLACE);
+ encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+ for (int i = 0; i < input.length(); i++) {
+ charBuf.position(0);
+ charBuf.put(input.charAt(i));
+ charBuf.position(0);
+ CoderResult result = encoder.encode(charBuf, byteBuf,
+ (i + 1) == input.length());
+ if (result.isMalformed()) {
+ err("Encoder reported a malformed sequence when asked to replace at index: "
+ + i, input, expectedBytes);
+ return;
+ } else if (result.isUnmappable()) {
+ err("Encoder reported an upmappable sequence when asked to replace at index: "
+ + i, input, expectedBytes);
+ return;
+ } else if (result.isOverflow()) {
+ err("Encoder claimed overflow when the output buffer is know to be large enough.",
+ input, expectedBytes);
+ } else if (!result.isUnderflow()) {
+ err("Bogus coder result, expected underflow.", input,
+ expectedBytes);
+ }
+ }
+ CoderResult result = encoder.flush(byteBuf);
+ if (result.isMalformed()) {
+ err("Encoder reported a malformed sequence when asked to replace when flushing.",
+ input, expectedBytes);
+ return;
+ } else if (result.isUnmappable()) {
+ err("Encoder reported an unmappable sequence when asked to replace when flushing.",
+ input, expectedBytes);
+ return;
+ } else if (result.isOverflow()) {
+ err("Encoder claimed overflow when flushing when the output buffer is know to be large enough.",
+ input, expectedBytes);
+ } else if (!result.isUnderflow()) {
+ err("Bogus coder result when flushing, expected underflow.", input,
+ expectedBytes);
+
+ }
+
+ byteBuf.limit(byteBuf.position());
+ byteBuf.position(0);
+
+ for (int i = 0; i < expectedBytes.length; i++) {
+ byte expect = expectedBytes[i];
+ byte actual = byteBuf.get();
+ if (actual != expect) {
+ err("When encoding one char at a time in REPORT mode, failed at position "
+ + i
+ + ", expected: "
+ + byteToHex(expect)
+ + ", got: "
+ + byteToHex(actual), input, expectedBytes);
+ return;
+ }
+ }
+
+ // Decode with 1-byte output buffer
+
+ charBuf = CharBuffer.wrap(input);
+ byteBuf = ByteBuffer.allocate(1);
+
+ encoder.reset(); // Let's test this while at it
+ encoder.onMalformedInput(CodingErrorAction.REPLACE);
+ encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+ int bytePos = 0;
+ while (charBuf.hasRemaining()) {
+ byteBuf.position(0);
+ byteBuf.put((byte)0);
+ byteBuf.position(0);
+ result = encoder.encode(charBuf, byteBuf, false);
+ if (result.isMalformed()) {
+ err("Encoder reported a malformed sequence when asked to replace at index (decoding one output code unit at a time): "
+ + charBuf.position(), input, expectedBytes);
+ return;
+ } else if (result.isUnmappable()) {
+ err("Encoder reported an unmappable sequence when asked to replace at index (decoding one output code unit at a time): "
+ + charBuf.position(), input, expectedBytes);
+ return;
+ } else if (result.isUnderflow()) {
+ if (charBuf.hasRemaining()) {
+ err("When encoding one output byte at a time, encoder claimed underflow when there was input remaining.",
+ input, expectedBytes);
+ return;
+ }
+ } else if (!result.isOverflow()) {
+ err("Bogus coder result, expected overflow.", input, expectedBytes);
+ }
+ if (byteBuf.position() == 1) {
+ byteBuf.position(0);
+ byte actual = byteBuf.get();
+ byte expect = expectedBytes[bytePos];
+ if (actual != expect) {
+ err("When encoding one output byte at a time in REPLACE mode, failed at position "
+ + charBuf.position()
+ + ", expected: "
+ + byteToHex(expect) + ", got: " + byteToHex(actual),
+ input, expectedBytes);
+ return;
+ }
+ bytePos++;
+ }
+ }
+
+ byteBuf.position(0);
+ byteBuf.put((byte)0);
+ byteBuf.position(0);
+ result = encoder.encode(charBuf, byteBuf, true);
+
+ if (byteBuf.position() == 1) {
+ byteBuf.position(0);
+ byte actual = byteBuf.get();
+ byte expect = expectedBytes[bytePos];
+ if (actual != expect) {
+ err("When encoding one output byte at a time in REPLACE mode, failed at position "
+ + charBuf.position()
+ + ", expected: "
+ + byteToHex(expect) + ", got: " + byteToHex(actual),
+ input, expectedBytes);
+ return;
+ }
+ bytePos++;
+ }
+
+ byteBuf.position(0);
+ byteBuf.put((byte)0);
+ byteBuf.position(0);
+ result = encoder.flush(byteBuf);
+ if (result.isMalformed()) {
+ err("Encoder reported a malformed sequence when asked to replace when flushing (one output at a time).",
+ input, expectedBytes);
+ return;
+ } else if (result.isUnmappable()) {
+ err("Encoder reported an unmappable sequence when asked to replace when flushing (one output at a time).",
+ input, expectedBytes);
+ return;
+ } else if (result.isOverflow()) {
+ err("Encoder claimed overflow when flushing when the output buffer is know to be large enough (one output at a time).",
+ input, expectedBytes);
+ } else if (!result.isUnderflow()) {
+ err("Bogus coder result when flushing, expected underflow (one output at a time).",
+ input, expectedBytes);
+ }
+
+ if (byteBuf.position() == 1) {
+ byteBuf.position(0);
+ byte actual = byteBuf.get();
+ byte expect = expectedBytes[bytePos];
+ if (actual != expect) {
+ err("When encoding one output code unit at a time in REPLACE mode, failed when flushing, expected: "
+ + byteToHex(expect) + ", got: " + byteToHex(actual),
+ input, expectedBytes);
+ return;
+ }
+ }
+
+ // TODO: 2 bytes at a time starting at 0 and 2 bytes at a time starting
+ // at 1
+ }
+
+ private String charToHex(char c) {
+ String hex = Integer.toHexString(c);
+ switch (hex.length()) {
+ case 1:
+ return "000" + hex;
+ case 2:
+ return "00" + hex;
+ case 3:
+ return "0" + hex;
+ default:
+ return hex;
+ }
+ }
+
+ private String byteToHex(byte b) {
+ String hex = Integer.toHexString(((int) b & 0xFF));
+ switch (hex.length()) {
+ case 1:
+ return "0" + hex;
+ default:
+ return hex;
+ }
+ }
+
+ private void err(String msg, byte[] bytes, String expectation) {
+ System.err.println(msg);
+ System.err.print("Input:");
+ for (int i = 0; i < bytes.length; i++) {
+ System.err.print(' ');
+ System.err.print(byteToHex(bytes[i]));
+ }
+ System.err.println();
+ System.err.print("Expect:");
+ for (int i = 0; i < expectation.length(); i++) {
+ System.err.print(' ');
+ System.err.print(charToHex(expectation.charAt(i)));
+ }
+ System.err.println();
+ }
+
+ private void err(String msg, String chars, byte[] expectation) {
+ System.err.println(msg);
+ System.err.print("Input:");
+ for (int i = 0; i < chars.length(); i++) {
+ System.err.print(' ');
+ System.err.print(charToHex(chars.charAt(i)));
+ }
+ System.err.println();
+ System.err.print("Expect:");
+ for (int i = 0; i < expectation.length; i++) {
+ System.err.print(' ');
+ System.err.print(byteToHex(expectation[i]));
+ }
+ System.err.println();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DecoderLoopTester.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DecoderLoopTester.java
new file mode 100644
index 000000000..3337a6555
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DecoderLoopTester.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+
+import nu.validator.htmlparser.common.Heuristics;
+import nu.validator.htmlparser.io.Encoding;
+import nu.validator.htmlparser.io.HtmlInputStreamReader;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+
+public class DecoderLoopTester {
+
+ private static final int LEAD_OFFSET = 0xD800 - (0x10000 >> 10);
+
+ private static final int NUMBER_OR_ASTRAL_CHARS = 24500;
+
+ private void runTest(int padding) throws SAXException, IOException {
+ Encoding utf8 = Encoding.forName("UTF-8");
+ char[] charArr = new char[1 + padding + 2 * NUMBER_OR_ASTRAL_CHARS];
+ byte[] byteArr;
+ int i = 0;
+ charArr[i++] = '\uFEFF';
+ for (int j = 0; j < padding; j++) {
+ charArr[i++] = 'x';
+ }
+ for (int j = 0; j < NUMBER_OR_ASTRAL_CHARS; j++) {
+ int value = 0x10000 + j;
+ charArr[i++] = (char) (LEAD_OFFSET + (value >> 10));
+ charArr[i++] = (char) (0xDC00 + (value & 0x3FF));
+// charArr[i++] = 'y';
+// charArr[i++] = 'z';
+
+ }
+ CharBuffer charBuffer = CharBuffer.wrap(charArr);
+ CharsetEncoder enc = utf8.newEncoder();
+ enc.onMalformedInput(CodingErrorAction.REPORT);
+ enc.onUnmappableCharacter(CodingErrorAction.REPORT);
+ ByteBuffer byteBuffer = enc.encode(charBuffer);
+ byteArr = new byte[byteBuffer.limit()];
+ byteBuffer.get(byteArr);
+
+ ErrorHandler eh = new SystemErrErrorHandler();
+ compare(new HtmlInputStreamReader(new ByteArrayInputStream(byteArr), eh, null, null, Heuristics.NONE), padding, charArr, byteArr);
+ compare(new HtmlInputStreamReader(new ByteArrayInputStream(byteArr), eh, null, null, utf8), padding, charArr, byteArr);
+ }
+
+ /**
+ * @param padding
+ * @param charArr
+ * @param byteArr
+ * @throws SAXException
+ * @throws IOException
+ */
+ private void compare(HtmlInputStreamReader reader, int padding, char[] charArr, byte[] byteArr) throws SAXException, IOException {
+ char[] readBuffer = new char[2048];
+ int offset = 0;
+ int num = 0;
+ int readNum = 0;
+ while ((num = reader.read(readBuffer)) != -1) {
+ for (int j = 0; j < num; j++) {
+ System.out.println(offset + j);
+ if (readBuffer[j] != charArr[offset + j]) {
+ throw new RuntimeException("Test failed. Char: " + Integer.toHexString(readBuffer[j]) + " j: " + j + " readNum: " + readNum);
+ }
+ }
+ offset += num;
+ readNum++;
+ }
+ }
+
+ void runTests() throws SAXException, IOException {
+ for (int i = 0; i < 4; i++) {
+ runTest(i);
+ }
+ }
+
+ /**
+ * @param args
+ * @throws IOException
+ * @throws SAXException
+ */
+ public static void main(String[] args) throws IOException, SAXException {
+ new DecoderLoopTester().runTests();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DomIdTester.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DomIdTester.java
new file mode 100644
index 000000000..a3866f5d9
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DomIdTester.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import nu.validator.htmlparser.dom.HtmlDocumentBuilder;
+
+public class DomIdTester {
+
+ private static final String testSrc = "<div><h1 id='bar' class='foo'>buoeoa</h1><p id='foo'>uoeuo</p></div>";
+
+ /**
+ * @param args
+ * @throws IOException
+ * @throws SAXException
+ */
+ public static void main(String[] args) throws SAXException, IOException {
+ HtmlDocumentBuilder builder = new HtmlDocumentBuilder();
+ Document doc = builder.parse(new InputSource(new StringReader(testSrc)));
+ System.out.println(doc.getElementById("foo").getLocalName());
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DomTest.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DomTest.java
new file mode 100644
index 000000000..07d054b9e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/DomTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class DomTest {
+ public static void main(String[] args) throws Exception {
+ DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
+ f.setNamespaceAware(true); // not setting this causes pain and suffering with SVG
+ DocumentBuilder b = f.newDocumentBuilder();
+ Document d = b.newDocument();
+ Element e = d.createElementNS("http://www.w3.org/1999/xhtml", "html");
+ e.setAttribute("xmlns:foo", "bar");
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/EncodingTester.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/EncodingTester.java
new file mode 100644
index 000000000..95cd3018e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/EncodingTester.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+import nu.validator.htmlparser.common.Heuristics;
+import nu.validator.htmlparser.io.Encoding;
+import nu.validator.htmlparser.io.HtmlInputStreamReader;
+
+import org.xml.sax.SAXException;
+
+public class EncodingTester {
+
+ private final InputStream aggregateStream;
+
+ private final StringBuilder builder = new StringBuilder();
+
+ /**
+ * @param aggregateStream
+ */
+ public EncodingTester(InputStream aggregateStream) {
+ this.aggregateStream = aggregateStream;
+ }
+
+ private void runTests() throws IOException, SAXException {
+ while (runTest()) {
+ // spin
+ }
+ }
+
+ private boolean runTest() throws IOException, SAXException {
+ if (skipLabel()) {
+ return false;
+ }
+ UntilHashInputStream stream = new UntilHashInputStream(aggregateStream);
+ HtmlInputStreamReader reader = new HtmlInputStreamReader(stream, null,
+ null, null, Heuristics.NONE);
+ Charset charset = reader.getCharset();
+ stream.close();
+ if (skipLabel()) {
+ System.err.println("Premature end of test data.");
+ return false;
+ }
+ builder.setLength(0);
+ loop: for (;;) {
+ int b = aggregateStream.read();
+ switch (b) {
+ case '\n':
+ break loop;
+ case -1:
+ System.err.println("Premature end of test data.");
+ return false;
+ default:
+ builder.append(((char) b));
+ }
+ }
+ String sniffed = charset.name();
+ String expected = Encoding.forName(builder.toString()).newDecoder().charset().name();
+ if (expected.equalsIgnoreCase(sniffed)) {
+ System.err.println("Success.");
+ // System.err.println(stream);
+ } else {
+ System.err.println("Failure. Expected: " + expected + " got "
+ + sniffed + ".");
+ System.err.println(stream);
+ }
+ return true;
+ }
+
+ private boolean skipLabel() throws IOException {
+ int b = aggregateStream.read();
+ if (b == -1) {
+ return true;
+ }
+ for (;;) {
+ b = aggregateStream.read();
+ if (b == -1) {
+ return true;
+ } else if (b == 0x0A) {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @param args
+ * @throws SAXException
+ * @throws IOException
+ */
+ public static void main(String[] args) throws IOException, SAXException {
+ for (int i = 0; i < args.length; i++) {
+ EncodingTester tester = new EncodingTester(new FileInputStream(
+ args[i]));
+ tester.runTests();
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/JSONArrayTokenHandler.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/JSONArrayTokenHandler.java
new file mode 100644
index 000000000..430bbdc44
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/JSONArrayTokenHandler.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import com.sdicons.json.model.JSONArray;
+import com.sdicons.json.model.JSONBoolean;
+import com.sdicons.json.model.JSONNull;
+import com.sdicons.json.model.JSONObject;
+import com.sdicons.json.model.JSONString;
+
+import nu.validator.htmlparser.common.TokenHandler;
+import nu.validator.htmlparser.impl.ElementName;
+import nu.validator.htmlparser.impl.HtmlAttributes;
+import nu.validator.htmlparser.impl.Tokenizer;
+
+public class JSONArrayTokenHandler implements TokenHandler, ErrorHandler {
+
+ private static final JSONString DOCTYPE = new JSONString("DOCTYPE");
+
+ private static final JSONString START_TAG = new JSONString("StartTag");
+
+ private static final JSONString END_TAG = new JSONString("EndTag");
+
+ private static final JSONString COMMENT = new JSONString("Comment");
+
+ private static final JSONString CHARACTER = new JSONString("Character");
+
+ private static final JSONString PARSE_ERROR = new JSONString("ParseError");
+
+ private static final char[] REPLACEMENT_CHARACTER = { '\uFFFD' };
+
+ private final StringBuilder builder = new StringBuilder();
+
+ private JSONArray array = null;
+
+ private int contentModelFlag;
+
+ private String contentModelElement;
+
+ public void setContentModelFlag(int contentModelFlag, String contentModelElement) {
+ this.contentModelFlag = contentModelFlag;
+ this.contentModelElement = contentModelElement;
+ }
+
+ public void characters(char[] buf, int start, int length)
+ throws SAXException {
+ builder.append(buf, start, length);
+ }
+
+ private void flushCharacters() {
+ if (builder.length() > 0) {
+ JSONArray token = new JSONArray();
+ token.getValue().add(CHARACTER);
+ token.getValue().add(new JSONString(builder.toString()));
+ array.getValue().add(token);
+ builder.setLength(0);
+ }
+ }
+
+ public void comment(char[] buf, int start, int length) throws SAXException {
+ flushCharacters();
+ JSONArray token = new JSONArray();
+ token.getValue().add(COMMENT);
+ token.getValue().add(new JSONString(new String(buf, start, length)));
+ array.getValue().add(token);
+ }
+
+ public void doctype(String name, String publicIdentifier, String systemIdentifier, boolean forceQuirks) throws SAXException {
+ flushCharacters();
+ JSONArray token = new JSONArray();
+ token.getValue().add(DOCTYPE);
+ token.getValue().add(new JSONString(name));
+ token.getValue().add(publicIdentifier == null ? JSONNull.NULL : new JSONString(publicIdentifier));
+ token.getValue().add(systemIdentifier == null ? JSONNull.NULL : new JSONString(systemIdentifier));
+ token.getValue().add(new JSONBoolean(!forceQuirks));
+ array.getValue().add(token);
+ }
+
+ public void endTag(ElementName eltName) throws SAXException {
+ String name = eltName.getName();
+ flushCharacters();
+ JSONArray token = new JSONArray();
+ token.getValue().add(END_TAG);
+ token.getValue().add(new JSONString(name));
+ array.getValue().add(token);
+ }
+
+ public void eof() throws SAXException {
+ flushCharacters();
+ }
+
+ public void startTokenization(Tokenizer self) throws SAXException {
+ array = new JSONArray();
+ if (contentModelElement != null) {
+ self.setStateAndEndTagExpectation(contentModelFlag, contentModelElement);
+ }
+ }
+
+ public void startTag(ElementName eltName, HtmlAttributes attributes,
+ boolean selfClosing) throws SAXException {
+ String name = eltName.getName();
+ flushCharacters();
+ JSONArray token = new JSONArray();
+ token.getValue().add(START_TAG);
+ token.getValue().add(new JSONString(name));
+ JSONObject attrs = new JSONObject();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ attrs.getValue().put(attributes.getQNameNoBoundsCheck(i),
+ new JSONString(attributes.getValueNoBoundsCheck(i)));
+ }
+ token.getValue().add(attrs);
+ if (selfClosing) {
+ token.getValue().add(JSONBoolean.TRUE);
+ }
+ array.getValue().add(token);
+ }
+
+ public boolean wantsComments() throws SAXException {
+ return true;
+ }
+
+ public void error(SAXParseException exception) throws SAXException {
+ flushCharacters();
+ array.getValue().add(PARSE_ERROR);
+ }
+
+ public void fatalError(SAXParseException exception) throws SAXException {
+ throw new RuntimeException("Should never happen.");
+ }
+
+ public void warning(SAXParseException exception) throws SAXException {
+ }
+
+ /**
+ * Returns the array.
+ *
+ * @return the array
+ */
+ public JSONArray getArray() {
+ return array;
+ }
+
+ public void endTokenization() throws SAXException {
+
+ }
+
+ @Override public void zeroOriginatingReplacementCharacter()
+ throws SAXException {
+ builder.append(REPLACEMENT_CHARACTER, 0, 1);
+ }
+
+ @Override public boolean cdataSectionAllowed() throws SAXException {
+ return false;
+ }
+
+ @Override public void ensureBufferSpace(int inputLength)
+ throws SAXException {
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/ListErrorHandler.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/ListErrorHandler.java
new file mode 100644
index 000000000..9a207f277
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/ListErrorHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.util.LinkedList;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+public class ListErrorHandler implements ErrorHandler {
+
+ private boolean fatal = false;
+
+ private LinkedList<String> errors = new LinkedList<String>();
+
+ public void error(SAXParseException spe) throws SAXException {
+ errors.add(Integer.toString(spe.getColumnNumber()) + ": " + spe.getMessage());
+ }
+
+ public void fatalError(SAXParseException arg0) throws SAXException {
+ fatal = true;
+ }
+
+ public void warning(SAXParseException arg0) throws SAXException {
+ }
+
+ /**
+ * Returns the errors.
+ *
+ * @return the errors
+ */
+ public LinkedList<String> getErrors() {
+ return errors;
+ }
+
+ /**
+ * Returns the fatal.
+ *
+ * @return the fatal
+ */
+ public boolean isFatal() {
+ return fatal;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/SystemErrErrorHandler.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/SystemErrErrorHandler.java
new file mode 100644
index 000000000..9ee490b9e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/SystemErrErrorHandler.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2005, 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.SourceLocator;
+import javax.xml.transform.TransformerException;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * @version $Id$
+ * @author hsivonen
+ */
+public class SystemErrErrorHandler implements ErrorHandler, ErrorListener {
+
+ private Writer out;
+
+ private boolean inError = false;
+
+ public SystemErrErrorHandler() {
+ try {
+ out = new OutputStreamWriter(System.err, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
+ */
+ public void warning(SAXParseException e) throws SAXException {
+ try {
+ out.write("Warning:\n");
+ out.write(e.getMessage());
+ out.write("\nFile: ");
+ String systemId = e.getSystemId();
+ out.write((systemId == null) ? "Unknown" : systemId);
+ out.write("\nLine: ");
+ out.write(Integer.toString(e.getLineNumber()));
+ out.write(" Col: ");
+ out.write(Integer.toString(e.getColumnNumber()));
+ out.write("\n\n");
+ out.flush();
+ } catch (IOException e1) {
+ throw new SAXException(e1);
+ }
+ }
+
+ /**
+ * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
+ */
+ public void error(SAXParseException e) throws SAXException {
+ inError = true;
+ try {
+ out.write("Error:\n");
+ out.write(e.getMessage());
+ out.write("\nFile: ");
+ String systemId = e.getSystemId();
+ out.write((systemId == null) ? "Unknown" : systemId);
+ out.write("\nLine: ");
+ out.write(Integer.toString(e.getLineNumber()));
+ out.write(" Col: ");
+ out.write(Integer.toString(e.getColumnNumber()));
+ out.write("\n\n");
+ out.flush();
+ } catch (IOException e1) {
+ throw new SAXException(e1);
+ }
+ }
+
+ /**
+ * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
+ */
+ public void fatalError(SAXParseException e) throws SAXException {
+ inError = true;
+ try {
+ out.write("Fatal Error:\n");
+ out.write(e.getMessage());
+ out.write("\nFile: ");
+ String systemId = e.getSystemId();
+ out.write((systemId == null) ? "Unknown" : systemId);
+ out.write("\nLine: ");
+ out.write(Integer.toString(e.getLineNumber()));
+ out.write(" Col: ");
+ out.write(Integer.toString(e.getColumnNumber()));
+ out.write("\n\n");
+ out.flush();
+ } catch (IOException e1) {
+ throw new SAXException(e1);
+ }
+ }
+
+ /**
+ * Returns the inError.
+ *
+ * @return the inError
+ */
+ public boolean isInError() {
+ return inError;
+ }
+
+ public void reset() {
+ inError = false;
+ }
+
+ public void error(TransformerException e) throws TransformerException {
+ inError = true;
+ try {
+ out.write("Error:\n");
+ out.write(e.getMessage());
+ SourceLocator sourceLocator = e.getLocator();
+ if (sourceLocator != null) {
+ out.write("\nFile: ");
+ String systemId = sourceLocator.getSystemId();
+ out.write((systemId == null) ? "Unknown" : systemId);
+ out.write("\nLine: ");
+ out.write(Integer.toString(sourceLocator.getLineNumber()));
+ out.write(" Col: ");
+ out.write(Integer.toString(sourceLocator.getColumnNumber()));
+ }
+ out.write("\n\n");
+ out.flush();
+ } catch (IOException e1) {
+ throw new TransformerException(e1);
+ }
+ }
+
+ public void fatalError(TransformerException e)
+ throws TransformerException {
+ inError = true;
+ try {
+ out.write("Fatal Error:\n");
+ out.write(e.getMessage());
+ SourceLocator sourceLocator = e.getLocator();
+ if (sourceLocator != null) {
+ out.write("\nFile: ");
+ String systemId = sourceLocator.getSystemId();
+ out.write((systemId == null) ? "Unknown" : systemId);
+ out.write("\nLine: ");
+ out.write(Integer.toString(sourceLocator.getLineNumber()));
+ out.write(" Col: ");
+ out.write(Integer.toString(sourceLocator.getColumnNumber()));
+ }
+ out.write("\n\n");
+ out.flush();
+ } catch (IOException e1) {
+ throw new TransformerException(e1);
+ }
+ }
+
+ public void warning(TransformerException e)
+ throws TransformerException {
+ try {
+ out.write("Warning:\n");
+ out.write(e.getMessage());
+ SourceLocator sourceLocator = e.getLocator();
+ if (sourceLocator != null) {
+ out.write("\nFile: ");
+ String systemId = sourceLocator.getSystemId();
+ out.write((systemId == null) ? "Unknown" : systemId);
+ out.write("\nLine: ");
+ out.write(Integer.toString(sourceLocator.getLineNumber()));
+ out.write(" Col: ");
+ out.write(Integer.toString(sourceLocator.getColumnNumber()));
+ }
+ out.write("\n\n");
+ out.flush();
+ } catch (IOException e1) {
+ throw new TransformerException(e1);
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TokenPrinter.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TokenPrinter.java
new file mode 100644
index 000000000..03b8c8597
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TokenPrinter.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import nu.validator.htmlparser.common.TokenHandler;
+import nu.validator.htmlparser.impl.ElementName;
+import nu.validator.htmlparser.impl.ErrorReportingTokenizer;
+import nu.validator.htmlparser.impl.HtmlAttributes;
+import nu.validator.htmlparser.impl.Tokenizer;
+import nu.validator.htmlparser.io.Driver;
+
+public class TokenPrinter implements TokenHandler, ErrorHandler {
+
+ private final Writer writer;
+
+ public void characters(char[] buf, int start, int length)
+ throws SAXException {
+ try {
+ boolean lineStarted = true;
+ writer.write('-');
+ for (int i = start; i < start + length; i++) {
+ if (!lineStarted) {
+ writer.write("\n-");
+ lineStarted = true;
+ }
+ char c = buf[i];
+ if (c == '\n') {
+ writer.write("\\n");
+ lineStarted = false;
+ } else {
+ writer.write(c);
+ }
+ }
+ writer.write('\n');
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void comment(char[] buf, int start, int length) throws SAXException {
+ try {
+ writer.write('!');
+ writer.write(buf, start, length);
+ writer.write('\n');
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void doctype(String name, String publicIdentifier, String systemIdentifier, boolean forceQuirks) throws SAXException {
+ try {
+ writer.write('D');
+ writer.write(name);
+ writer.write(' ');
+ writer.write("" + forceQuirks);
+ writer.write('\n');
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void endTag(ElementName eltName) throws SAXException {
+ try {
+ writer.write(')');
+ writer.write(eltName.getName());
+ writer.write('\n');
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void eof() throws SAXException {
+ try {
+ writer.write("E\n");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void startTokenization(Tokenizer self) throws SAXException {
+
+ }
+
+ public void startTag(ElementName eltName, HtmlAttributes attributes, boolean selfClosing)
+ throws SAXException {
+ try {
+ writer.write('(');
+ writer.write(eltName.getName());
+ writer.write('\n');
+ for (int i = 0; i < attributes.getLength(); i++) {
+ writer.write('A');
+ writer.write(attributes.getQNameNoBoundsCheck(i));
+ writer.write(' ');
+ writer.write(attributes.getValueNoBoundsCheck(i));
+ writer.write('\n');
+ }
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public boolean wantsComments() throws SAXException {
+ return true;
+ }
+
+ public static void main(String[] args) throws SAXException, IOException {
+ TokenPrinter printer = new TokenPrinter(new OutputStreamWriter(System.out, "UTF-8"));
+ Driver tokenizer = new Driver(new ErrorReportingTokenizer(printer));
+ tokenizer.setErrorHandler(printer);
+ File file = new File(args[0]);
+ InputSource is = new InputSource(new FileInputStream(file));
+ is.setSystemId(file.toURI().toASCIIString());
+ tokenizer.tokenize(is);
+ }
+
+ /**
+ * @param writer
+ */
+ public TokenPrinter(final Writer writer) {
+ this.writer = writer;
+ }
+
+ public void error(SAXParseException exception) throws SAXException {
+ try {
+ writer.write("R ");
+ writer.write(exception.getMessage());
+ writer.write("\n");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void fatalError(SAXParseException exception) throws SAXException {
+ try {
+ writer.write("F ");
+ writer.write(exception.getMessage());
+ writer.write("\n");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void warning(SAXParseException exception) throws SAXException {
+ try {
+ writer.write("W ");
+ writer.write(exception.getMessage());
+ writer.write("\n");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void endTokenization() throws SAXException {
+ try {
+ writer.flush();
+ writer.close();
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ @Override public void zeroOriginatingReplacementCharacter()
+ throws SAXException {
+ try {
+ writer.write("0\n");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ @Override public boolean cdataSectionAllowed() throws SAXException {
+ return false;
+ }
+
+ @Override public void ensureBufferSpace(int inputLength)
+ throws SAXException {
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TokenizerTester.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TokenizerTester.java
new file mode 100644
index 000000000..76ea7543a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TokenizerTester.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.impl.ErrorReportingTokenizer;
+import nu.validator.htmlparser.impl.Tokenizer;
+import nu.validator.htmlparser.io.Driver;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import antlr.RecognitionException;
+import antlr.TokenStreamException;
+
+import com.sdicons.json.model.JSONArray;
+import com.sdicons.json.model.JSONObject;
+import com.sdicons.json.model.JSONString;
+import com.sdicons.json.model.JSONValue;
+import com.sdicons.json.parser.JSONParser;
+
+public class TokenizerTester {
+
+ private static JSONString PLAINTEXT = new JSONString("PLAINTEXT state");
+
+ private static JSONString PCDATA = new JSONString("DATA state");
+
+ private static JSONString RCDATA = new JSONString("RCDATA state");
+
+ private static JSONString RAWTEXT = new JSONString("RAWTEXT state");
+
+ private static boolean jsonDeepEquals(JSONValue one, JSONValue other) {
+ if (one.isSimple()) {
+ return one.equals(other);
+ } else if (one.isArray()) {
+ if (other.isArray()) {
+ JSONArray oneArr = (JSONArray) one;
+ JSONArray otherArr = (JSONArray) other;
+ return oneArr.getValue().equals(otherArr.getValue());
+ } else {
+ return false;
+ }
+ } else if (one.isObject()) {
+ if (other.isObject()) {
+ JSONObject oneObject = (JSONObject) one;
+ JSONObject otherObject = (JSONObject) other;
+ return oneObject.getValue().equals(otherObject.getValue());
+ } else {
+ return false;
+ }
+ } else {
+ throw new RuntimeException("Should never happen.");
+ }
+ }
+
+ private JSONArray tests;
+
+ private final JSONArrayTokenHandler tokenHandler;
+
+ private final Driver driver;
+
+ private final Writer writer;
+
+ private TokenizerTester(InputStream stream) throws TokenStreamException,
+ RecognitionException, UnsupportedEncodingException {
+ tokenHandler = new JSONArrayTokenHandler();
+ driver = new Driver(new ErrorReportingTokenizer(tokenHandler));
+ driver.setCommentPolicy(XmlViolationPolicy.ALLOW);
+ driver.setContentNonXmlCharPolicy(XmlViolationPolicy.ALLOW);
+ driver.setContentSpacePolicy(XmlViolationPolicy.ALLOW);
+ driver.setNamePolicy(XmlViolationPolicy.ALLOW);
+ driver.setXmlnsPolicy(XmlViolationPolicy.ALLOW);
+ driver.setErrorHandler(tokenHandler);
+ writer = new OutputStreamWriter(System.out, "UTF-8");
+ JSONParser jsonParser = new JSONParser(new InputStreamReader(stream,
+ "UTF-8"));
+ JSONObject obj = (JSONObject) jsonParser.nextValue();
+ tests = (JSONArray) obj.get("tests");
+ if (tests == null) {
+ tests = (JSONArray) obj.get("xmlViolationTests");
+ driver.setCommentPolicy(XmlViolationPolicy.ALTER_INFOSET);
+ driver.setContentNonXmlCharPolicy(XmlViolationPolicy.ALTER_INFOSET);
+ driver.setNamePolicy(XmlViolationPolicy.ALTER_INFOSET);
+ driver.setXmlnsPolicy(XmlViolationPolicy.ALTER_INFOSET);
+ }
+ }
+
+ private void runTests() throws SAXException, IOException {
+ for (JSONValue val : tests.getValue()) {
+ runTest((JSONObject) val);
+ }
+ writer.flush();
+ }
+
+ private void runTest(JSONObject test) throws SAXException, IOException {
+ String inputString = ((JSONString) test.get("input")).getValue();
+ JSONArray expectedTokens = (JSONArray) test.get("output");
+ String description = ((JSONString) test.get("description")).getValue();
+ JSONString lastStartTagJSON = ((JSONString) test.get("lastStartTag"));
+ String lastStartTag = lastStartTagJSON == null ? null
+ : lastStartTagJSON.getValue();
+ JSONArray contentModelFlags = (JSONArray) test.get("initialStates");
+ if (contentModelFlags == null) {
+ runTestInner(inputString, expectedTokens, description,
+ Tokenizer.DATA, null);
+ } else {
+ for (JSONValue value : contentModelFlags.getValue()) {
+ if (PCDATA.equals(value)) {
+ runTestInner(inputString, expectedTokens, description,
+ Tokenizer.DATA, lastStartTag);
+ } else if (RAWTEXT.equals(value)) {
+ runTestInner(inputString, expectedTokens, description,
+ Tokenizer.RAWTEXT, lastStartTag);
+ } else if (RCDATA.equals(value)) {
+ runTestInner(inputString, expectedTokens, description,
+ Tokenizer.RCDATA, lastStartTag);
+ } else if (PLAINTEXT.equals(value)) {
+ runTestInner(inputString, expectedTokens, description,
+ Tokenizer.PLAINTEXT, lastStartTag);
+ } else {
+ throw new RuntimeException("Broken test data.");
+ }
+ }
+ }
+ }
+
+ /**
+ * @param contentModelElement
+ * @param contentModelFlag
+ * @param test
+ * @throws SAXException
+ * @throws IOException
+ */
+ private void runTestInner(String inputString, JSONArray expectedTokens,
+ String description, int contentModelFlag,
+ String contentModelElement) throws SAXException, IOException {
+ tokenHandler.setContentModelFlag(contentModelFlag, contentModelElement);
+ InputSource is = new InputSource(new StringReader(inputString));
+ try {
+ driver.tokenize(is);
+ JSONArray actualTokens = tokenHandler.getArray();
+ if (jsonDeepEquals(actualTokens, expectedTokens)) {
+ writer.write("Success\n");
+ } else {
+ writer.write("Failure\n");
+ writer.write(description);
+ writer.write("\nInput:\n");
+ writer.write(inputString);
+ writer.write("\nExpected tokens:\n");
+ writer.write(expectedTokens.render(false));
+ writer.write("\nActual tokens:\n");
+ writer.write(actualTokens.render(false));
+ writer.write("\n");
+ }
+ } catch (Throwable t) {
+ writer.write("Failure\n");
+ writer.write(description);
+ writer.write("\nInput:\n");
+ writer.write(inputString);
+ writer.write("\n");
+ t.printStackTrace(new PrintWriter(writer, false));
+ }
+ }
+
+ /**
+ * @param args
+ * @throws RecognitionException
+ * @throws TokenStreamException
+ * @throws IOException
+ * @throws SAXException
+ */
+ public static void main(String[] args) throws TokenStreamException,
+ RecognitionException, SAXException, IOException {
+ for (int i = 0; i < args.length; i++) {
+ TokenizerTester tester = new TokenizerTester(new FileInputStream(
+ args[i]));
+ tester.runTests();
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreeDumpContentHandler.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreeDumpContentHandler.java
new file mode 100644
index 000000000..9b95b763e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreeDumpContentHandler.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+public class TreeDumpContentHandler implements ContentHandler, LexicalHandler {
+
+ private final Writer writer;
+
+ private int level = 0;
+
+ private boolean inCharacters = false;
+
+ private boolean close;
+
+ /**
+ * @param writer
+ */
+ public TreeDumpContentHandler(final Writer writer, boolean close) {
+ this.writer = writer;
+ this.close = close;
+ }
+
+ public TreeDumpContentHandler(final Writer writer) {
+ this(writer, true);
+ }
+
+ private void printLead() throws IOException {
+ if (inCharacters) {
+ writer.write("\"\n");
+ inCharacters = false;
+ }
+ writer.write("| ");
+ for (int i = 0; i < level; i++) {
+ writer.write(" ");
+ }
+ }
+
+ public void characters(char[] ch, int start, int length)
+ throws SAXException {
+ try {
+ if (!inCharacters) {
+ printLead();
+ writer.write('"');
+ inCharacters = true;
+ }
+ writer.write(ch, start, length);
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ try {
+ if (inCharacters) {
+ writer.write("\"\n");
+ inCharacters = false;
+ }
+ level--;
+ if ("http://www.w3.org/1999/xhtml" == uri &&
+ "template" == localName) {
+ // decrement level for the "content"
+ level--;
+ }
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void startElement(String uri, String localName, String qName,
+ Attributes atts) throws SAXException {
+ try {
+ printLead();
+ writer.write('<');
+ if ("http://www.w3.org/1998/Math/MathML" == uri) {
+ writer.write("math ");
+ } else if ("http://www.w3.org/2000/svg" == uri) {
+ writer.write("svg ");
+ } else if ("http://www.w3.org/1999/xhtml" != uri) {
+ writer.write("otherns ");
+ }
+ writer.write(localName);
+ writer.write(">\n");
+ level++;
+ TreeMap<String, String> map = new TreeMap<String, String>();
+ for (int i = 0; i < atts.getLength(); i++) {
+ String ns = atts.getURI(i);
+ String name;
+ if ("http://www.w3.org/1999/xlink" == ns) {
+ name = "xlink " + atts.getLocalName(i);
+ } else if ("http://www.w3.org/XML/1998/namespace" == ns) {
+ name = "xml " + atts.getLocalName(i);
+ } else if ("http://www.w3.org/2000/xmlns/" == ns) {
+ name = "xmlns " + atts.getLocalName(i);
+ } else if ("" != uri) {
+ name = atts.getLocalName(i);
+ } else {
+ name = "otherns " + atts.getLocalName(i);
+ }
+ map.put(name, atts.getValue(i));
+ }
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ printLead();
+ writer.write(entry.getKey());
+ writer.write("=\"");
+ writer.write(entry.getValue());
+ writer.write("\"\n");
+ }
+ if ("http://www.w3.org/1999/xhtml" == uri &&
+ "template" == localName) {
+ printLead();
+ level++;
+ writer.write("content\n");
+ }
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void comment(char[] ch, int offset, int len) throws SAXException {
+ try {
+ printLead();
+ writer.write("<!-- ");
+ writer.write(ch, offset, len);
+ writer.write(" -->\n");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void startDTD(String name, String publicIdentifier,
+ String systemIdentifier) throws SAXException {
+ try {
+ printLead();
+ writer.write("<!DOCTYPE ");
+ writer.write(name);
+ if (publicIdentifier.length() > 0 || systemIdentifier.length() > 0) {
+ writer.write(' ');
+ writer.write('\"');
+ writer.write(publicIdentifier);
+ writer.write('\"');
+ writer.write(' ');
+ writer.write('\"');
+ writer.write(systemIdentifier);
+ writer.write('\"');
+ }
+ writer.write(">\n");
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void endDocument() throws SAXException {
+ try {
+ if (inCharacters) {
+ writer.write("\"\n");
+ inCharacters = false;
+ }
+ if (close) {
+ writer.flush();
+ writer.close();
+ }
+ } catch (IOException e) {
+ throw new SAXException(e);
+ }
+ }
+
+ public void startPrefixMapping(String prefix, String uri)
+ throws SAXException {
+ }
+
+ public void startEntity(String arg0) throws SAXException {
+ }
+
+ public void endCDATA() throws SAXException {
+ }
+
+ public void endDTD() throws SAXException {
+ }
+
+ public void endEntity(String arg0) throws SAXException {
+ }
+
+ public void startCDATA() throws SAXException {
+ }
+
+ public void endPrefixMapping(String prefix) throws SAXException {
+ }
+
+ public void ignorableWhitespace(char[] ch, int start, int length)
+ throws SAXException {
+ }
+
+ public void processingInstruction(String target, String data)
+ throws SAXException {
+ }
+
+ public void setDocumentLocator(Locator locator) {
+ }
+
+ public void skippedEntity(String name) throws SAXException {
+ }
+
+ public void startDocument() throws SAXException {
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreePrinter.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreePrinter.java
new file mode 100644
index 000000000..c09169383
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreePrinter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.sax.HtmlParser;
+
+public class TreePrinter {
+
+ public static void main(String[] args) throws SAXException, IOException {
+ TreeDumpContentHandler treeDumpContentHandler = new TreeDumpContentHandler(new OutputStreamWriter(System.out, "UTF-8"));
+ HtmlParser htmlParser = new HtmlParser();
+ htmlParser.setContentHandler(treeDumpContentHandler);
+ htmlParser.setLexicalHandler(treeDumpContentHandler);
+ htmlParser.setErrorHandler(new SystemErrErrorHandler());
+ htmlParser.setXmlPolicy(XmlViolationPolicy.ALLOW);
+ File file = new File(args[0]);
+ InputSource is = new InputSource(new FileInputStream(file));
+ is.setSystemId(file.toURI().toASCIIString());
+ htmlParser.parse(is);
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreeTester.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreeTester.java
new file mode 100644
index 000000000..62d3ab530
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/TreeTester.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.util.LinkedList;
+
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.sax.HtmlParser;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXParseException;
+
+public class TreeTester {
+
+ private final BufferedInputStream aggregateStream;
+
+ private boolean streaming = false;
+
+ /**
+ * @param aggregateStream
+ */
+ public TreeTester(InputStream aggregateStream) {
+ this.aggregateStream = new BufferedInputStream(aggregateStream);
+ }
+
+ private void runTests() throws Throwable {
+ if (aggregateStream.read() != '#') {
+ System.err.println("No hash at start!");
+ return;
+ }
+ while (runTest()) {
+ // spin
+ }
+ }
+
+ private boolean runTest() throws Throwable {
+ UntilHashInputStream stream = null;
+ try {
+ String context = null;
+ boolean scriptingEnabled = true;
+ boolean hadScriptingDirective = false;
+ aggregateStream.mark(12288);
+ if (skipLabel()) { // #data
+ return false;
+ }
+ stream = new UntilHashInputStream(aggregateStream);
+ while (stream.read() != -1) {
+ // spin
+ }
+ if (skipLabel()) { // #errors
+ System.err.println("Premature end of test data.");
+ return false;
+ }
+ stream = new UntilHashInputStream(aggregateStream);
+ while (stream.read() != -1) {
+ // spin
+ }
+
+ StringBuilder sb = new StringBuilder();
+ int c;
+ while ((c = aggregateStream.read()) != '\n') {
+ sb.append((char) c);
+ }
+ String label = sb.toString();
+ if ("document-fragment".equals(label)) {
+ sb.setLength(0);
+ while ((c = aggregateStream.read()) != '\n') {
+ sb.append((char) c);
+ }
+ context = sb.toString();
+ // Now potentially gather #script-on/off
+ sb.setLength(0);
+ while ((c = aggregateStream.read()) != '\n') {
+ sb.append((char) c);
+ }
+ label = sb.toString();
+ }
+ if ("script-on".equals(label)) {
+ hadScriptingDirective = true;
+ } else if ("script-off".equals(label)) {
+ hadScriptingDirective = true;
+ scriptingEnabled = false;
+ }
+ aggregateStream.reset();
+ if (skipLabel()) { // #data
+ System.err.println("Premature end of test data.");
+ return false;
+ }
+ stream = new UntilHashInputStream(aggregateStream);
+ InputSource is = new InputSource(stream);
+ is.setEncoding("UTF-8");
+ StringWriter sw = new StringWriter();
+ ListErrorHandler leh = new ListErrorHandler();
+ TreeDumpContentHandler treeDumpContentHandler = new TreeDumpContentHandler(
+ sw);
+ HtmlParser htmlParser = new HtmlParser(XmlViolationPolicy.ALLOW);
+ if (streaming) {
+ htmlParser.setStreamabilityViolationPolicy(XmlViolationPolicy.FATAL);
+ }
+ htmlParser.setContentHandler(treeDumpContentHandler);
+ htmlParser.setLexicalHandler(treeDumpContentHandler);
+ htmlParser.setErrorHandler(leh);
+ htmlParser.setScriptingEnabled(scriptingEnabled);
+ try {
+ if (context == null) {
+ htmlParser.parse(is);
+ } else {
+ String ns = "http://www.w3.org/1999/xhtml";
+ if (context.startsWith("svg ")) {
+ ns = "http://www.w3.org/2000/svg";
+ context = context.substring(4);
+ } else if (context.startsWith("math ")) {
+ ns = "http://www.w3.org/1998/Math/MathML";
+ context = context.substring(5);
+ }
+ htmlParser.parseFragment(is, context, ns);
+ treeDumpContentHandler.endDocument();
+ }
+ } catch (SAXParseException e) {
+ // ignore
+ }
+ stream.close();
+
+ if (skipLabel()) { // #errors
+ System.err.println("Premature end of test data.");
+ return false;
+ }
+ LinkedList<String> expectedErrors = new LinkedList<String>();
+ BufferedReader br = new BufferedReader(new InputStreamReader(
+ new UntilHashInputStream(aggregateStream), "UTF-8"));
+ String line = null;
+ while ((line = br.readLine()) != null) {
+ expectedErrors.add(line);
+ }
+
+ if (context != null) {
+ if (skipLabel()) { // #document-fragment
+ System.err.println("Premature end of test data.");
+ return false;
+ }
+ UntilHashInputStream stream2 = new UntilHashInputStream(aggregateStream);
+ while (stream2.read() != -1) {
+ // spin
+ }
+ }
+ if (hadScriptingDirective && skipLabel()) { // #script-on/off
+ System.err.println("Premature end of test data.");
+ return false;
+ }
+
+ if (skipLabel()) { // #document
+ System.err.println("Premature end of test data.");
+ return false;
+ }
+
+ StringBuilder expectedBuilder = new StringBuilder();
+ br = new BufferedReader(new InputStreamReader(
+ new UntilHashInputStream(aggregateStream), "UTF-8"));
+ int ch;
+ while ((ch = br.read()) != -1) {
+ expectedBuilder.append((char)ch);
+ }
+ String expected = expectedBuilder.toString();
+ String actual = sw.toString();
+
+ LinkedList<String> actualErrors = leh.getErrors();
+
+ if (expected.equals(actual) || (streaming && leh.isFatal()) /*
+ * && expectedErrors.size() ==
+ * actualErrors.size()
+ */) {
+ System.err.println("Success.");
+ // System.err.println(stream);
+ } else {
+ System.err.print("Failure.\nData:\n" + stream + "\nExpected:\n"
+ + expected + "Got: \n" + actual);
+ System.err.println("Expected errors:");
+ for (String err : expectedErrors) {
+ System.err.println(err);
+ }
+ System.err.println("Actual errors:");
+ for (String err : actualErrors) {
+ System.err.println(err);
+ }
+ }
+ } catch (Throwable t) {
+ System.err.println("Failure.\nData:\n" + stream);
+ throw t;
+ }
+ return true;
+ }
+
+ private boolean skipLabel() throws IOException {
+ int b = aggregateStream.read();
+ if (b == -1) {
+ return true;
+ }
+ for (;;) {
+ b = aggregateStream.read();
+ if (b == -1) {
+ return true;
+ } else if (b == 0x0A) {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @param args
+ * @throws Throwable
+ */
+ public static void main(String[] args) throws Throwable {
+ for (int i = 0; i < args.length; i++) {
+ TreeTester tester = new TreeTester(new FileInputStream(args[i]));
+ tester.runTests();
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/UntilHashInputStream.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/UntilHashInputStream.java
new file mode 100644
index 000000000..473a9f7f9
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/UntilHashInputStream.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class UntilHashInputStream extends InputStream {
+
+ private final StringBuilder builder = new StringBuilder();
+
+ private final InputStream delegate;
+
+ private int buffer = -1;
+
+ private boolean closed = false;
+
+ /**
+ * @param delegate
+ * @throws IOException
+ */
+ public UntilHashInputStream(final InputStream delegate) throws IOException {
+ this.delegate = delegate;
+ this.buffer = delegate.read();
+ if (buffer == '#') {
+ closed = true;
+ }
+ }
+
+ public int read() throws IOException {
+ if (closed) {
+ return -1;
+ }
+ int rv = buffer;
+ buffer = delegate.read();
+ if (buffer == '#' && rv == '\n') {
+ // end of stream
+ closed = true;
+ return -1;
+ } else {
+ if (rv >= 0x20 && rv < 0x80) {
+ builder.append(((char)rv));
+ } else {
+ builder.append("0x");
+ builder.append(Integer.toHexString(rv));
+ }
+ return rv;
+ }
+ }
+
+ /**
+ * @see java.io.InputStream#close()
+ */
+ @Override
+ public void close() throws IOException {
+ super.close();
+ if (closed) {
+ return;
+ }
+ for (;;) {
+ int b = delegate.read();
+ if (b == 0x23 || b == -1) {
+ break;
+ }
+ }
+ closed = true;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return builder.toString();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/XmlSerializerTester.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/XmlSerializerTester.java
new file mode 100644
index 000000000..0d23fda3c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/XmlSerializerTester.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import nu.validator.htmlparser.sax.XmlSerializer;
+
+public class XmlSerializerTester {
+
+
+
+ /**
+ * @param args
+ * @throws SAXException
+ */
+ public static void main(String[] args) throws SAXException {
+ AttributesImpl attrs = new AttributesImpl();
+ XmlSerializer serializer = new XmlSerializer(System.out);
+ serializer.startDocument();
+ serializer.startElement("1", "a", null, attrs);
+ serializer.startElement("1", "b", null, attrs);
+ serializer.endElement("1", "b", null);
+ serializer.startElement("2", "c", null, attrs);
+ serializer.endElement("2", "c", null);
+ attrs.addAttribute("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "about", null, "CDATA", "");
+ serializer.startElement("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "d", null, attrs);
+ serializer.endElement("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "d", null);
+ serializer.startPrefixMapping("rdf", "foo");
+ serializer.startElement("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "e", null, attrs);
+ serializer.startPrefixMapping("p0", "bar");
+ serializer.startElement("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "f", null, attrs);
+ serializer.characters("a\uD834\uDD21a\uD834a\uDD21a".toCharArray(), 0, 8);
+ serializer.endElement("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "f", null);
+ serializer.endElement("http://www.w3.org/1999/02/22-rdf-syntax-ns#", "e", null);
+
+ serializer.endPrefixMapping("rdf");
+ serializer.endElement("1", "a", null);
+ serializer.endDocument();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/XomTest.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/XomTest.java
new file mode 100644
index 000000000..66d706ae9
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/XomTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.test;
+
+import nu.xom.Attribute;
+import nu.xom.Element;
+
+public class XomTest {
+ public static void main(String[] args) {
+ Element elt = new Element("html", "http://www.w3.org/1999/xhtml");
+ elt.addAttribute(new Attribute("xmlns:foo", "bar"));
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/package.html b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/package.html
new file mode 100644
index 000000000..57809b84e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/test/package.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head><title>Package Overview</title>
+<!--
+ Copyright (c) 2007 Henri Sivonen
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+-->
+</head>
+<body bgcolor="white">
+<p>Test drivers.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/HTML2HTML.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/HTML2HTML.java
new file mode 100644
index 000000000..5e2cf1f58
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/HTML2HTML.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.tools;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.sax.HtmlParser;
+import nu.validator.htmlparser.sax.HtmlSerializer;
+import nu.validator.htmlparser.sax.XmlSerializer;
+import nu.validator.htmlparser.test.SystemErrErrorHandler;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class HTML2HTML {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) throws SAXException,
+ ParserConfigurationException, MalformedURLException, IOException,
+ TransformerException {
+ InputStream in;
+ OutputStream out;
+
+ switch (args.length) {
+ case 0:
+ in = System.in;
+ out = System.out;
+ break;
+ case 1:
+ in = new FileInputStream(args[0]);
+ out = System.out;
+ break;
+ case 2:
+ in = new FileInputStream(args[0]);
+ out = new FileOutputStream(args[1]);
+ break;
+ default:
+ System.err.println("Too many arguments. No arguments to use stdin/stdout. One argument to reading from file and write to stdout. Two arguments to read from first file and write to second.");
+ System.exit(1);
+ return;
+ }
+
+ ContentHandler serializer = new HtmlSerializer(out);
+
+ HtmlParser parser = new HtmlParser(XmlViolationPolicy.ALLOW);
+
+ parser.setErrorHandler(new SystemErrErrorHandler());
+ parser.setContentHandler(serializer);
+ parser.setProperty("http://xml.org/sax/properties/lexical-handler",
+ serializer);
+ parser.parse(new InputSource(in));
+ out.flush();
+ out.close();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/HTML2XML.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/HTML2XML.java
new file mode 100644
index 000000000..57666f93b
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/HTML2XML.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.tools;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.sax.HtmlParser;
+import nu.validator.htmlparser.sax.XmlSerializer;
+import nu.validator.htmlparser.test.SystemErrErrorHandler;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class HTML2XML {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) throws SAXException,
+ ParserConfigurationException, MalformedURLException, IOException,
+ TransformerException {
+ InputStream in;
+ OutputStream out;
+
+ switch (args.length) {
+ case 0:
+ in = System.in;
+ out = System.out;
+ break;
+ case 1:
+ in = new FileInputStream(args[0]);
+ out = System.out;
+ break;
+ case 2:
+ in = new FileInputStream(args[0]);
+ out = new FileOutputStream(args[1]);
+ break;
+ default:
+ System.err.println("Too many arguments. No arguments to use stdin/stdout. One argument to reading from file and write to stdout. Two arguments to read from first file and write to second.");
+ System.exit(1);
+ return;
+ }
+
+ ContentHandler serializer = new XmlSerializer(out);
+
+ HtmlParser parser = new HtmlParser(XmlViolationPolicy.ALTER_INFOSET);
+
+ parser.setErrorHandler(new SystemErrErrorHandler());
+ parser.setContentHandler(serializer);
+ parser.setProperty("http://xml.org/sax/properties/lexical-handler",
+ serializer);
+ parser.parse(new InputSource(in));
+ out.flush();
+ out.close();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XML2HTML.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XML2HTML.java
new file mode 100644
index 000000000..dad89a5b2
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XML2HTML.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.tools;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.TransformerException;
+
+import nu.validator.htmlparser.sax.HtmlSerializer;
+import nu.validator.htmlparser.sax.XmlSerializer;
+import nu.validator.htmlparser.test.SystemErrErrorHandler;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
+public class XML2HTML {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) throws SAXException,
+ ParserConfigurationException, MalformedURLException, IOException,
+ TransformerException {
+ InputStream in;
+ OutputStream out;
+
+ switch (args.length) {
+ case 0:
+ in = System.in;
+ out = System.out;
+ break;
+ case 1:
+ in = new FileInputStream(args[0]);
+ out = System.out;
+ break;
+ case 2:
+ in = new FileInputStream(args[0]);
+ out = new FileOutputStream(args[1]);
+ break;
+ default:
+ System.err.println("Too many arguments. No arguments to use stdin/stdout. One argument to reading from file and write to stdout. Two arguments to read from first file and write to second.");
+ System.exit(1);
+ return;
+ }
+
+ ContentHandler serializer = new HtmlSerializer(out);
+
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ XMLReader parser = factory.newSAXParser().getXMLReader();
+ parser.setErrorHandler(new SystemErrErrorHandler());
+ parser.setContentHandler(serializer);
+ parser.setProperty("http://xml.org/sax/properties/lexical-handler",
+ serializer);
+ parser.parse(new InputSource(in));
+ out.flush();
+ out.close();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XML2XML.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XML2XML.java
new file mode 100644
index 000000000..2f6aa24d8
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XML2XML.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.tools;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.TransformerException;
+
+import nu.validator.htmlparser.sax.NameCheckingXmlSerializer;
+import nu.validator.htmlparser.sax.XmlSerializer;
+import nu.validator.htmlparser.test.SystemErrErrorHandler;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
+public class XML2XML {
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) throws SAXException,
+ ParserConfigurationException, MalformedURLException, IOException,
+ TransformerException {
+ InputStream in;
+ OutputStream out;
+
+ switch (args.length) {
+ case 0:
+ in = System.in;
+ out = System.out;
+ break;
+ case 1:
+ in = new FileInputStream(args[0]);
+ out = System.out;
+ break;
+ case 2:
+ in = new FileInputStream(args[0]);
+ out = new FileOutputStream(args[1]);
+ break;
+ default:
+ System.err.println("Too many arguments. No arguments to use stdin/stdout. One argument to reading from file and write to stdout. Two arguments to read from first file and write to second.");
+ System.exit(1);
+ return;
+ }
+
+ ContentHandler serializer = new NameCheckingXmlSerializer(out);
+
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ XMLReader parser = factory.newSAXParser().getXMLReader();
+ parser.setErrorHandler(new SystemErrErrorHandler());
+ parser.setContentHandler(serializer);
+ parser.setProperty("http://xml.org/sax/properties/lexical-handler",
+ serializer);
+ parser.parse(new InputSource(in));
+ out.flush();
+ out.close();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XSLT4HTML5.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XSLT4HTML5.java
new file mode 100644
index 000000000..05d8193c1
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XSLT4HTML5.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.tools;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.Templates;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TemplatesHandler;
+import javax.xml.transform.sax.TransformerHandler;
+
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.dom.HtmlDocumentBuilder;
+import nu.validator.htmlparser.sax.HtmlParser;
+import nu.validator.htmlparser.sax.HtmlSerializer;
+import nu.validator.htmlparser.sax.XmlSerializer;
+import nu.validator.htmlparser.test.SystemErrErrorHandler;
+
+import org.w3c.dom.Document;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+public class XSLT4HTML5 {
+
+ private enum Mode {
+ STREAMING_SAX, BUFFERED_SAX, DOM,
+ }
+
+ private static final String TEMPLATE = "--template=";
+
+ private static final String INPUT_HTML = "--input-html=";
+
+ private static final String INPUT_XML = "--input-xml=";
+
+ private static final String OUTPUT_HTML = "--output-html=";
+
+ private static final String OUTPUT_XML = "--output-xml=";
+
+ private static final String MODE = "--mode=";
+
+ /**
+ * @param args
+ * @throws ParserConfigurationException
+ * @throws SAXException
+ * @throws IOException
+ * @throws MalformedURLException
+ * @throws TransformerException
+ */
+ public static void main(String[] args) throws SAXException,
+ ParserConfigurationException, MalformedURLException, IOException, TransformerException {
+ if (args.length == 0) {
+ System.out.println("--template=file --input-[html|xml]=file --output-[html|xml]=file --mode=[sax-streaming|sax-buffered|dom]");
+ System.exit(0);
+ }
+ String template = null;
+ String input = null;
+ boolean inputHtml = false;
+ String output = null;
+ boolean outputHtml = false;
+ Mode mode = null;
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if (arg.startsWith(TEMPLATE)) {
+ if (template == null) {
+ template = arg.substring(TEMPLATE.length());
+ } else {
+ System.err.println("Tried to set template twice.");
+ System.exit(1);
+ }
+ } else if (arg.startsWith(INPUT_HTML)) {
+ if (input == null) {
+ input = arg.substring(INPUT_HTML.length());
+ inputHtml = true;
+ } else {
+ System.err.println("Tried to set input twice.");
+ System.exit(2);
+ }
+ } else if (arg.startsWith(INPUT_XML)) {
+ if (input == null) {
+ input = arg.substring(INPUT_XML.length());
+ inputHtml = false;
+ } else {
+ System.err.println("Tried to set input twice.");
+ System.exit(2);
+ }
+ } else if (arg.startsWith(OUTPUT_HTML)) {
+ if (output == null) {
+ output = arg.substring(OUTPUT_HTML.length());
+ outputHtml = true;
+ } else {
+ System.err.println("Tried to set output twice.");
+ System.exit(3);
+ }
+ } else if (arg.startsWith(OUTPUT_XML)) {
+ if (output == null) {
+ output = arg.substring(OUTPUT_XML.length());
+ outputHtml = false;
+ } else {
+ System.err.println("Tried to set output twice.");
+ System.exit(3);
+ }
+ } else if (arg.startsWith(MODE)) {
+ if (mode == null) {
+ String modeStr = arg.substring(MODE.length());
+ if ("dom".equals(modeStr)) {
+ mode = Mode.DOM;
+ } else if ("sax-buffered".equals(modeStr)) {
+ mode = Mode.BUFFERED_SAX;
+ } else if ("sax-streaming".equals(modeStr)) {
+ mode = Mode.STREAMING_SAX;
+ } else {
+ System.err.println("Unrecognized mode.");
+ System.exit(5);
+ }
+ } else {
+ System.err.println("Tried to set mode twice.");
+ System.exit(4);
+ }
+ }
+ }
+
+ if (template == null) {
+ System.err.println("No template specified.");
+ System.exit(6);
+ }
+ if (input == null) {
+ System.err.println("No input specified.");
+ System.exit(7);
+ }
+ if (output == null) {
+ System.err.println("No output specified.");
+ System.exit(8);
+ }
+ if (mode == null) {
+ mode = Mode.BUFFERED_SAX;
+ }
+
+ SystemErrErrorHandler errorHandler = new SystemErrErrorHandler();
+
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ XMLReader reader = factory.newSAXParser().getXMLReader();
+ reader.setErrorHandler(errorHandler);
+
+ SAXTransformerFactory transformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance();
+ transformerFactory.setErrorListener(errorHandler);
+ TemplatesHandler templatesHandler = transformerFactory.newTemplatesHandler();
+ reader.setContentHandler(templatesHandler);
+ reader.parse(new File(template).toURI().toASCIIString());
+
+ Templates templates = templatesHandler.getTemplates();
+
+ FileOutputStream outputStream = new FileOutputStream(output);
+ ContentHandler serializer;
+ if (outputHtml) {
+ serializer = new HtmlSerializer(outputStream);
+ } else {
+ serializer = new XmlSerializer(outputStream);
+ }
+ SAXResult result = new SAXResult(new XmlnsDropper(serializer));
+ result.setLexicalHandler((LexicalHandler) serializer);
+
+ if (mode == Mode.DOM) {
+ Document inputDoc;
+ DocumentBuilder builder;
+ if (inputHtml) {
+ builder = new HtmlDocumentBuilder(XmlViolationPolicy.ALTER_INFOSET);
+ } else {
+ DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ try {
+ builder = builderFactory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ inputDoc = builder.parse(new File(input));
+ DOMSource inputSource = new DOMSource(inputDoc,
+ new File(input).toURI().toASCIIString());
+ Transformer transformer = templates.newTransformer();
+ transformer.setErrorListener(errorHandler);
+ transformer.transform(inputSource, result);
+ } else {
+ if (inputHtml) {
+ reader = new HtmlParser(XmlViolationPolicy.ALTER_INFOSET);
+ if (mode == Mode.STREAMING_SAX) {
+ reader.setProperty("http://validator.nu/properties/streamability-violation-policy", XmlViolationPolicy.FATAL);
+ }
+ }
+ TransformerHandler transformerHandler = transformerFactory.newTransformerHandler(templates);
+ transformerHandler.setResult(result);
+ reader.setErrorHandler(errorHandler);
+ reader.setContentHandler(transformerHandler);
+ reader.setProperty("http://xml.org/sax/properties/lexical-handler", transformerHandler);
+ reader.parse(new File(input).toURI().toASCIIString());
+ }
+ outputStream.flush();
+ outputStream.close();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XSLT4HTML5XOM.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XSLT4HTML5XOM.java
new file mode 100644
index 000000000..b364cc521
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XSLT4HTML5XOM.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.tools;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import nu.validator.htmlparser.common.XmlViolationPolicy;
+import nu.validator.htmlparser.sax.HtmlSerializer;
+import nu.validator.htmlparser.xom.HtmlBuilder;
+import nu.xom.Builder;
+import nu.xom.Document;
+import nu.xom.Element;
+import nu.xom.Nodes;
+import nu.xom.ParsingException;
+import nu.xom.Serializer;
+import nu.xom.ValidityException;
+import nu.xom.converters.SAXConverter;
+import nu.xom.xslt.XSLException;
+import nu.xom.xslt.XSLTransform;
+
+import org.xml.sax.SAXException;
+
+public class XSLT4HTML5XOM {
+
+ private static final String TEMPLATE = "--template=";
+
+ private static final String INPUT_HTML = "--input-html=";
+
+ private static final String INPUT_XML = "--input-xml=";
+
+ private static final String OUTPUT_HTML = "--output-html=";
+
+ private static final String OUTPUT_XML = "--output-xml=";
+
+ /**
+ * @param args
+ * @throws IOException
+ * @throws ParsingException
+ * @throws ValidityException
+ * @throws XSLException
+ * @throws SAXException
+ */
+ public static void main(String[] args) throws ValidityException,
+ ParsingException, IOException, XSLException, SAXException {
+ if (args.length == 0) {
+ System.out.println("--template=file --input-[html|xml]=file --output-[html|xml]=file --mode=[sax-streaming|sax-buffered|dom]");
+ System.exit(0);
+ }
+ String template = null;
+ String input = null;
+ boolean inputHtml = false;
+ String output = null;
+ boolean outputHtml = false;
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if (arg.startsWith(TEMPLATE)) {
+ if (template == null) {
+ template = arg.substring(TEMPLATE.length());
+ } else {
+ System.err.println("Tried to set template twice.");
+ System.exit(1);
+ }
+ } else if (arg.startsWith(INPUT_HTML)) {
+ if (input == null) {
+ input = arg.substring(INPUT_HTML.length());
+ inputHtml = true;
+ } else {
+ System.err.println("Tried to set input twice.");
+ System.exit(2);
+ }
+ } else if (arg.startsWith(INPUT_XML)) {
+ if (input == null) {
+ input = arg.substring(INPUT_XML.length());
+ inputHtml = false;
+ } else {
+ System.err.println("Tried to set input twice.");
+ System.exit(2);
+ }
+ } else if (arg.startsWith(OUTPUT_HTML)) {
+ if (output == null) {
+ output = arg.substring(OUTPUT_HTML.length());
+ outputHtml = true;
+ } else {
+ System.err.println("Tried to set output twice.");
+ System.exit(3);
+ }
+ } else if (arg.startsWith(OUTPUT_XML)) {
+ if (output == null) {
+ output = arg.substring(OUTPUT_XML.length());
+ outputHtml = false;
+ } else {
+ System.err.println("Tried to set output twice.");
+ System.exit(3);
+ }
+ }
+ }
+
+ if (template == null) {
+ System.err.println("No template specified.");
+ System.exit(6);
+ }
+ if (input == null) {
+ System.err.println("No input specified.");
+ System.exit(7);
+ }
+ if (output == null) {
+ System.err.println("No output specified.");
+ System.exit(8);
+ }
+
+ Builder builder = new Builder();
+
+ Document transformationDoc = builder.build(new File(template));
+
+ XSLTransform transform = new XSLTransform(transformationDoc);
+
+ FileOutputStream outputStream = new FileOutputStream(output);
+
+ Document inputDoc;
+ if (inputHtml) {
+ builder = new HtmlBuilder(XmlViolationPolicy.ALTER_INFOSET);
+ }
+ inputDoc = builder.build(new File(input));
+ Nodes result = transform.transform(inputDoc);
+ Document outputDoc = new Document((Element) result.get(0));
+ if (outputHtml) {
+ HtmlSerializer htmlSerializer = new HtmlSerializer(outputStream);
+ SAXConverter converter = new SAXConverter(htmlSerializer);
+ converter.setLexicalHandler(htmlSerializer);
+ converter.convert(outputDoc);
+ } else {
+ Serializer serializer = new Serializer(outputStream);
+ serializer.write(outputDoc);
+ }
+ outputStream.flush();
+ outputStream.close();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XmlnsDropper.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XmlnsDropper.java
new file mode 100644
index 000000000..0e6d4b1c2
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/XmlnsDropper.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.tools;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Quick and dirty hack to work around Xalan xmlns weirdness.
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+class XmlnsDropper implements ContentHandler {
+
+ private final ContentHandler delegate;
+
+ /**
+ * @param delegate
+ */
+ public XmlnsDropper(final ContentHandler delegate) {
+ this.delegate = delegate;
+ }
+
+ /**
+ * @param ch
+ * @param start
+ * @param length
+ * @throws SAXException
+ * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+ */
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ delegate.characters(ch, start, length);
+ }
+
+ /**
+ * @throws SAXException
+ * @see org.xml.sax.ContentHandler#endDocument()
+ */
+ public void endDocument() throws SAXException {
+ delegate.endDocument();
+ }
+
+ /**
+ * @param uri
+ * @param localName
+ * @param qName
+ * @throws SAXException
+ * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ delegate.endElement(uri, localName, qName);
+ }
+
+ /**
+ * @param prefix
+ * @throws SAXException
+ * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
+ */
+ public void endPrefixMapping(String prefix) throws SAXException {
+ delegate.endPrefixMapping(prefix);
+ }
+
+ /**
+ * @param ch
+ * @param start
+ * @param length
+ * @throws SAXException
+ * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
+ */
+ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+ delegate.ignorableWhitespace(ch, start, length);
+ }
+
+ /**
+ * @param target
+ * @param data
+ * @throws SAXException
+ * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String)
+ */
+ public void processingInstruction(String target, String data) throws SAXException {
+ delegate.processingInstruction(target, data);
+ }
+
+ /**
+ * @param locator
+ * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
+ */
+ public void setDocumentLocator(Locator locator) {
+ delegate.setDocumentLocator(locator);
+ }
+
+ /**
+ * @param name
+ * @throws SAXException
+ * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String)
+ */
+ public void skippedEntity(String name) throws SAXException {
+ delegate.skippedEntity(name);
+ }
+
+ /**
+ * @throws SAXException
+ * @see org.xml.sax.ContentHandler#startDocument()
+ */
+ public void startDocument() throws SAXException {
+ delegate.startDocument();
+ }
+
+ /**
+ * @param uri
+ * @param localName
+ * @param qName
+ * @param atts
+ * @throws SAXException
+ * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+ */
+ public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
+ AttributesImpl ai = new AttributesImpl();
+ for (int i = 0; i < atts.getLength(); i++) {
+ String u = atts.getURI(i);
+ String t = atts.getType(i);
+ String v = atts.getValue(i);
+ String n = atts.getLocalName(i);
+ String q = atts.getQName(i);
+ if (q != null) {
+ if ("xmlns".equals(q) || q.startsWith("xmlns:")) {
+ continue;
+ }
+ }
+ ai.addAttribute(u, n, q, t, v);
+ }
+ delegate.startElement(uri, localName, qName, ai);
+ }
+
+ /**
+ * @param prefix
+ * @param uri
+ * @throws SAXException
+ * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String)
+ */
+ public void startPrefixMapping(String prefix, String uri) throws SAXException {
+ delegate.startPrefixMapping(prefix, uri);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/package.html b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/package.html
new file mode 100644
index 000000000..a04bf3cd0
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/htmlparser/tools/package.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head><title>Package Overview</title>
+<!--
+ Copyright (c) 2007 Henri Sivonen
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+-->
+</head>
+<body bgcolor="white">
+<p>Demo apps.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/saxtree/test/PassThruPrinter.java b/components/htmlfive/java/htmlparser/test-src/nu/validator/saxtree/test/PassThruPrinter.java
new file mode 100644
index 000000000..df391d4b4
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/saxtree/test/PassThruPrinter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.saxtree.test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+
+import nu.validator.htmlparser.sax.XmlSerializer;
+import nu.validator.saxtree.Node;
+import nu.validator.saxtree.TreeBuilder;
+import nu.validator.saxtree.TreeParser;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+public class PassThruPrinter {
+ public static void main(String[] args) throws SAXException, IOException, ParserConfigurationException {
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ XMLReader reader = factory.newSAXParser().getXMLReader();
+
+ TreeBuilder treeBuilder = new TreeBuilder();
+ reader.setContentHandler(treeBuilder);
+ reader.setProperty("http://xml.org/sax/properties/lexical-handler", treeBuilder);
+
+ File file = new File(args[0]);
+ InputSource is = new InputSource(new FileInputStream(file));
+ is.setSystemId(file.toURI().toASCIIString());
+ reader.parse(is);
+
+ Node doc = treeBuilder.getRoot();
+
+ ContentHandler xmlSerializer = new XmlSerializer(System.out);
+
+ TreeParser treeParser = new TreeParser(xmlSerializer, (LexicalHandler) xmlSerializer);
+ treeParser.parse(doc);
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/test-src/nu/validator/saxtree/test/package.html b/components/htmlfive/java/htmlparser/test-src/nu/validator/saxtree/test/package.html
new file mode 100644
index 000000000..57809b84e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/test-src/nu/validator/saxtree/test/package.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head><title>Package Overview</title>
+<!--
+ Copyright (c) 2007 Henri Sivonen
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+-->
+</head>
+<body bgcolor="white">
+<p>Test drivers.</p>
+</body>
+</html> \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/AnnotationHelperVisitor.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/AnnotationHelperVisitor.java
new file mode 100644
index 000000000..432f08b90
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/AnnotationHelperVisitor.java
@@ -0,0 +1,155 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.util.List;
+
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.MarkerAnnotationExpr;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.visitor.VoidVisitorAdapter;
+
+public class AnnotationHelperVisitor<T> extends VoidVisitorAdapter<T> {
+
+ protected List<AnnotationExpr> currentAnnotations;
+
+ protected boolean nsUri() {
+ return hasAnnotation("NsUri");
+ }
+
+ protected boolean prefix() {
+ return hasAnnotation("Prefix");
+ }
+
+ protected boolean local() {
+ return hasAnnotation("Local");
+ }
+
+ protected boolean literal() {
+ return hasAnnotation("Literal");
+ }
+
+ protected boolean inline() {
+ return hasAnnotation("Inline");
+ }
+
+ protected boolean noLength() {
+ return hasAnnotation("NoLength");
+ }
+
+ protected boolean unsigned() {
+ return hasAnnotation("Unsigned");
+ }
+
+ protected boolean auto() {
+ return hasAnnotation("Auto");
+ }
+
+ protected boolean virtual() {
+ return hasAnnotation("Virtual");
+ }
+
+ protected boolean isConst() {
+ return hasAnnotation("Const");
+ }
+
+ protected boolean characterName() {
+ return hasAnnotation("CharacterName");
+ }
+
+ protected boolean creator() {
+ return hasAnnotation("Creator");
+ }
+
+ protected boolean htmlCreator() {
+ return hasAnnotation("HtmlCreator");
+ }
+
+ protected boolean svgCreator() {
+ return hasAnnotation("SvgCreator");
+ }
+
+ private boolean hasAnnotation(String anno) {
+ if (currentAnnotations == null) {
+ return false;
+ }
+ for (AnnotationExpr ann : currentAnnotations) {
+ if (ann instanceof MarkerAnnotationExpr) {
+ MarkerAnnotationExpr marker = (MarkerAnnotationExpr) ann;
+ if (marker.getName().getName().equals(anno)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected Type convertType(japa.parser.ast.type.Type type, int modifiers) {
+ if (type instanceof ReferenceType) {
+ ReferenceType referenceType = (ReferenceType) type;
+ return new Type(convertTypeName(referenceType.getType().toString()), referenceType.getArrayCount(), noLength(), modifiers);
+ } else {
+ return new Type(convertTypeName(type.toString()), 0, false, modifiers);
+ }
+ }
+
+ private String convertTypeName(String name) {
+ if ("String".equals(name)) {
+ if (local()) {
+ return "@Local";
+ }
+ if (nsUri()) {
+ return "@NsUri";
+ }
+ if (prefix()) {
+ return "@Prefix";
+ }
+ if (literal()) {
+ return "@Literal";
+ }
+ if (auto()) {
+ return "@Auto";
+ }
+ if (characterName()) {
+ return "@CharacterName";
+ }
+ }
+ return name;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppOnlyInputStream.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppOnlyInputStream.java
new file mode 100644
index 000000000..587b81604
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppOnlyInputStream.java
@@ -0,0 +1,70 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class CppOnlyInputStream extends InputStream {
+
+ private static final String DROP = "// CPPONLY:";
+
+ private final InputStream delegate;
+
+ public CppOnlyInputStream(InputStream delegate) {
+ this.delegate = new BufferedInputStream(delegate);
+ }
+
+ @Override public int read() throws IOException {
+ int c = delegate.read();
+ if (c == DROP.charAt(0)) {
+ delegate.mark(DROP.length());
+ for (int i = 1; i < DROP.length(); ++i) {
+ int d = delegate.read();
+ if (d != DROP.charAt(i)) {
+ delegate.reset();
+ return c;
+ }
+ }
+ return delegate.read();
+ }
+ return c;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppTypes.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppTypes.java
new file mode 100644
index 000000000..963d1ac68
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppTypes.java
@@ -0,0 +1,462 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008-2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class CppTypes {
+
+ /**
+ * The license for the atom list written by this program.
+ */
+ private static final String ATOM_LICENSE = "/*\n"
+ + " * Copyright (c) 2008-2010 Mozilla Foundation\n"
+ + " *\n"
+ + " * Permission is hereby granted, free of charge, to any person obtaining a \n"
+ + " * copy of this software and associated documentation files (the \"Software\"), \n"
+ + " * to deal in the Software without restriction, including without limitation \n"
+ + " * the rights to use, copy, modify, merge, publish, distribute, sublicense, \n"
+ + " * and/or sell copies of the Software, and to permit persons to whom the \n"
+ + " * Software is furnished to do so, subject to the following conditions:\n"
+ + " *\n"
+ + " * The above copyright notice and this permission notice shall be included in \n"
+ + " * all copies or substantial portions of the Software.\n"
+ + " *\n"
+ + " * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \n"
+ + " * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \n"
+ + " * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL \n"
+ + " * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \n"
+ + " * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \n"
+ + " * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER \n"
+ + " * DEALINGS IN THE SOFTWARE.\n" + " */\n\n";
+
+ private static Set<String> reservedWords = new HashSet<String>();
+
+ static {
+ reservedWords.add("small");
+ reservedWords.add("for");
+ reservedWords.add("false");
+ reservedWords.add("true");
+ reservedWords.add("default");
+ reservedWords.add("class");
+ reservedWords.add("switch");
+ reservedWords.add("union");
+ reservedWords.add("template");
+ reservedWords.add("int");
+ reservedWords.add("char");
+ reservedWords.add("operator");
+ reservedWords.add("or");
+ reservedWords.add("and");
+ reservedWords.add("not");
+ reservedWords.add("xor");
+ reservedWords.add("unicode");
+ }
+
+ private static final String[] TREE_BUILDER_INCLUDES = { "nsContentUtils",
+ "nsIAtom", "nsHtml5AtomTable", "nsITimer", "nsHtml5String",
+ "nsNameSpaceManager", "nsIContent", "nsTraceRefcnt", "jArray",
+ "nsHtml5DocumentMode", "nsHtml5ArrayCopy", "nsHtml5Parser",
+ "nsHtml5Atoms", "nsHtml5TreeOperation", "nsHtml5StateSnapshot",
+ "nsHtml5StackNode", "nsHtml5TreeOpExecutor", "nsHtml5StreamParser",
+ "nsAHtml5TreeBuilderState", "nsHtml5Highlighter",
+ "nsHtml5PlainTextUtils", "nsHtml5ViewSourceUtils",
+ "mozilla/Likely", "nsIContentHandle", "nsHtml5OplessBuilder" };
+
+ private static final String[] TOKENIZER_INCLUDES = { "nsIAtom",
+ "nsHtml5AtomTable", "nsHtml5String", "nsIContent", "nsTraceRefcnt",
+ "jArray", "nsHtml5DocumentMode", "nsHtml5ArrayCopy",
+ "nsHtml5NamedCharacters", "nsHtml5NamedCharactersAccel",
+ "nsHtml5Atoms", "nsAHtml5TreeBuilderState", "nsHtml5Macros",
+ "nsHtml5Highlighter", "nsHtml5TokenizerLoopPolicies" };
+
+ private static final String[] INCLUDES = { "nsIAtom", "nsHtml5AtomTable",
+ "nsHtml5String", "nsNameSpaceManager", "nsIContent", "nsTraceRefcnt",
+ "jArray", "nsHtml5ArrayCopy", "nsAHtml5TreeBuilderState",
+ "nsHtml5Atoms", "nsHtml5ByteReadable", "nsIUnicodeDecoder",
+ "nsHtml5Macros", "nsIContentHandle", "nsHtml5Portability",
+ "nsHtml5ContentCreatorFunction"};
+
+ private static final String[] OTHER_DECLATIONS = {};
+
+ private static final String[] TREE_BUILDER_OTHER_DECLATIONS = {};
+
+ private static final String[] NAMED_CHARACTERS_INCLUDES = { "jArray",
+ "nscore", "nsDebug", "prlog", "mozilla/ArrayUtils" };
+
+ private static final String[] FORWARD_DECLARATIONS = { "nsHtml5StreamParser" };
+
+ private static final String[] CLASSES_THAT_NEED_SUPPLEMENT = {
+ "MetaScanner", "Tokenizer", "TreeBuilder", "UTF16Buffer", };
+
+ private static final String[] STATE_LOOP_POLICIES = {
+ "nsHtml5ViewSourcePolicy", "nsHtml5SilentPolicy" };
+
+ private final Map<String, String> atomMap = new HashMap<String, String>();
+
+ private final Writer atomWriter;
+
+ public CppTypes(File atomList) {
+ if (atomList == null) {
+ atomWriter = null;
+ } else {
+ try {
+ atomWriter = new OutputStreamWriter(new FileOutputStream(
+ atomList), "utf-8");
+ atomWriter.write(ATOM_LICENSE);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public void finished() {
+ try {
+ if (atomWriter != null) {
+ atomWriter.flush();
+ atomWriter.close();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String classPrefix() {
+ return "nsHtml5";
+ }
+
+ public String booleanType() {
+ return "bool";
+ }
+
+ public String byteType() {
+ return "int8_t";
+ }
+
+ public String charType() {
+ return "char16_t";
+ }
+
+ /**
+ * Only used for named characters.
+ *
+ * @return
+ */
+ public String unsignedShortType() {
+ return "uint16_t";
+ }
+
+ public String intType() {
+ return "int32_t";
+ }
+
+ public String unsignedIntType() {
+ return "uint32_t";
+ }
+
+ public String stringType() {
+ return "nsHtml5String";
+ }
+
+ public String localType() {
+ return "nsIAtom*";
+ }
+
+ public String prefixType() {
+ return "nsIAtom*";
+ }
+
+ public String nsUriType() {
+ return "int32_t";
+ }
+
+ public String falseLiteral() {
+ return "false";
+ }
+
+ public String trueLiteral() {
+ return "true";
+ }
+
+ public String nullLiteral() {
+ return "nullptr";
+ }
+
+ public String encodingDeclarationHandlerType() {
+ return "nsHtml5StreamParser*";
+ }
+
+ public String nodeType() {
+ return "nsIContentHandle*";
+ }
+
+ public String htmlCreatorType() {
+ return "mozilla::dom::HTMLContentCreatorFunction";
+ }
+
+ public String svgCreatorType() {
+ return "mozilla::dom::SVGContentCreatorFunction";
+ }
+
+ public String creatorType() {
+ return "nsHtml5ContentCreatorFunction";
+ }
+
+ public String xhtmlNamespaceLiteral() {
+ return "kNameSpaceID_XHTML";
+ }
+
+ public String svgNamespaceLiteral() {
+ return "kNameSpaceID_SVG";
+ }
+
+ public String xmlnsNamespaceLiteral() {
+ return "kNameSpaceID_XMLNS";
+ }
+
+ public String xmlNamespaceLiteral() {
+ return "kNameSpaceID_XML";
+ }
+
+ public String noNamespaceLiteral() {
+ return "kNameSpaceID_None";
+ }
+
+ public String xlinkNamespaceLiteral() {
+ return "kNameSpaceID_XLink";
+ }
+
+ public String mathmlNamespaceLiteral() {
+ return "kNameSpaceID_MathML";
+ }
+
+ public String arrayTemplate() {
+ return "jArray";
+ }
+
+ public String autoArrayTemplate() {
+ return "autoJArray";
+ }
+
+ public String localForLiteral(String literal) {
+ String atom = atomMap.get(literal);
+ if (atom == null) {
+ atom = createAtomName(literal);
+ atomMap.put(literal, atom);
+ if (atomWriter != null) {
+ try {
+ atomWriter.write("HTML5_ATOM(" + atom + ", \"" + literal
+ + "\")\n");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return "nsHtml5Atoms::" + atom;
+ }
+
+ private String createAtomName(String literal) {
+ String candidate = literal.replaceAll("[^a-zA-Z0-9_]", "_");
+ if ("".equals(candidate)) {
+ candidate = "emptystring";
+ }
+ while (atomMap.values().contains(candidate)
+ || reservedWords.contains(candidate)) {
+ candidate = candidate + '_';
+ }
+ return candidate;
+ }
+
+ public String stringForLiteral(String literal) {
+ return '"' + literal + '"';
+ }
+
+ public String staticArrayTemplate() {
+ return "staticJArray";
+ }
+
+ public String newArrayCreator() {
+ return "newJArray";
+ }
+
+ public String[] boilerplateIncludes(String javaClass) {
+ if ("TreeBuilder".equals(javaClass)) {
+ return TREE_BUILDER_INCLUDES;
+ } else if ("Tokenizer".equals(javaClass)) {
+ return TOKENIZER_INCLUDES;
+ } else {
+ return INCLUDES;
+ }
+ }
+
+ public String[] boilerplateDeclarations(String javaClass) {
+ if ("TreeBuilder".equals(javaClass)) {
+ return TREE_BUILDER_OTHER_DECLATIONS;
+ } else {
+ return OTHER_DECLATIONS;
+ }
+ }
+
+ public String[] namedCharactersIncludes() {
+ return NAMED_CHARACTERS_INCLUDES;
+ }
+
+ public String[] boilerplateForwardDeclarations() {
+ return FORWARD_DECLARATIONS;
+ }
+
+ public String documentModeHandlerType() {
+ return "nsHtml5TreeBuilder*";
+ }
+
+ public String documentModeType() {
+ return "nsHtml5DocumentMode";
+ }
+
+ public String arrayCopy() {
+ return "nsHtml5ArrayCopy::arraycopy";
+ }
+
+ public String maxInteger() {
+ return "INT32_MAX";
+ }
+
+ public String constructorBoilerplate(String className) {
+ return "MOZ_COUNT_CTOR(" + className + ");";
+ }
+
+ public String destructorBoilderplate(String className) {
+ return "MOZ_COUNT_DTOR(" + className + ");";
+ }
+
+ public String literalType() {
+ return "const char*";
+ }
+
+ public boolean hasSupplement(String javaClass) {
+ return Arrays.binarySearch(CLASSES_THAT_NEED_SUPPLEMENT, javaClass) > -1;
+ }
+
+ public String internerType() {
+ return "nsHtml5AtomTable*";
+ }
+
+ public String treeBuilderStateInterface() {
+ return "nsAHtml5TreeBuilderState";
+ }
+
+ public String treeBuilderStateType() {
+ return "nsAHtml5TreeBuilderState*";
+ }
+
+ public String arrayLengthMacro() {
+ return "MOZ_ARRAY_LENGTH";
+ }
+
+ public String staticAssert() {
+ return "PR_STATIC_ASSERT";
+ }
+
+ public String abortIfFalse() {
+ return "NS_ABORT_IF_FALSE";
+ }
+
+ public String continueMacro() {
+ return "NS_HTML5_CONTINUE";
+ }
+
+ public String breakMacro() {
+ return "NS_HTML5_BREAK";
+ }
+
+ public String characterNameType() {
+ return "nsHtml5CharacterName&";
+ }
+
+ public String characterNameTypeDeclaration() {
+ return "nsHtml5CharacterName";
+ }
+
+ public String transition() {
+ return "P::transition";
+ }
+
+ public String tokenizerErrorCondition() {
+ return "P::reportErrors";
+ }
+
+ public String firstTransitionArg() {
+ return "mViewSource";
+ }
+
+ public String errorHandler() {
+ return this.unlikely() + "(mViewSource)";
+ }
+
+ public String unlikely() {
+ return "MOZ_UNLIKELY";
+ }
+
+ public String completedCharacterReference() {
+ return "P::completedNamedCharacterReference(mViewSource)";
+ }
+
+ public String[] stateLoopPolicies() {
+ return STATE_LOOP_POLICIES;
+ }
+
+ public String assertionMacro() {
+ return "MOZ_ASSERT";
+ }
+
+ public String releaseAssertionMacro() {
+ return "MOZ_RELEASE_ASSERT";
+ }
+
+ public String crashMacro() {
+ return "MOZ_CRASH";
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppVisitor.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppVisitor.java
new file mode 100644
index 000000000..a97a9eba1
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/CppVisitor.java
@@ -0,0 +1,2418 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ * Copyright (C) 2008 Mozilla Foundation
+ *
+ * This file is part of HTML Parser C++ Translator. It was derived from DumpVisitor
+ * which was part of Java 1.5 parser and Abstract Syntax Tree and came with the following notice:
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package nu.validator.htmlparser.cpptranslate;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import japa.parser.ast.BlockComment;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.ImportDeclaration;
+import japa.parser.ast.LineComment;
+import japa.parser.ast.Node;
+import japa.parser.ast.PackageDeclaration;
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.body.AnnotationDeclaration;
+import japa.parser.ast.body.AnnotationMemberDeclaration;
+import japa.parser.ast.body.BodyDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.ConstructorDeclaration;
+import japa.parser.ast.body.EmptyMemberDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumConstantDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.InitializerDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.ModifierSet;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.TypeDeclaration;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.ArrayAccessExpr;
+import japa.parser.ast.expr.ArrayCreationExpr;
+import japa.parser.ast.expr.ArrayInitializerExpr;
+import japa.parser.ast.expr.AssignExpr;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BooleanLiteralExpr;
+import japa.parser.ast.expr.CastExpr;
+import japa.parser.ast.expr.CharLiteralExpr;
+import japa.parser.ast.expr.ClassExpr;
+import japa.parser.ast.expr.ConditionalExpr;
+import japa.parser.ast.expr.DoubleLiteralExpr;
+import japa.parser.ast.expr.EnclosedExpr;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.expr.FieldAccessExpr;
+import japa.parser.ast.expr.InstanceOfExpr;
+import japa.parser.ast.expr.IntegerLiteralExpr;
+import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
+import japa.parser.ast.expr.LongLiteralExpr;
+import japa.parser.ast.expr.LongLiteralMinValueExpr;
+import japa.parser.ast.expr.MarkerAnnotationExpr;
+import japa.parser.ast.expr.MemberValuePair;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NormalAnnotationExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.expr.ObjectCreationExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.SingleMemberAnnotationExpr;
+import japa.parser.ast.expr.StringLiteralExpr;
+import japa.parser.ast.expr.SuperExpr;
+import japa.parser.ast.expr.ThisExpr;
+import japa.parser.ast.expr.UnaryExpr;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.WildcardType;
+
+/**
+ * @author Julio Vilmar Gesser
+ * @author Henri Sivonen
+ */
+
+public class CppVisitor extends AnnotationHelperVisitor<LocalSymbolTable> {
+
+ private static final String[] CLASS_NAMES = { "AttributeName",
+ "ElementName", "HtmlAttributes", "LocatorImpl", "MetaScanner",
+ "NamedCharacters", "NamedCharactersAccel", "Portability",
+ "StackNode", "Tokenizer", "TreeBuilder", "UTF16Buffer" };
+
+ private static final String[] METHODS_WITH_UNLIKELY_CONDITIONS = {
+ "appendStrBuf" };
+
+ public class SourcePrinter {
+
+ private int level = 0;
+
+ private boolean indented = false;
+
+ private final StringBuilder buf = new StringBuilder();
+
+ public void indent() {
+ level++;
+ }
+
+ public void unindent() {
+ level--;
+ }
+
+ private void makeIndent() {
+ for (int i = 0; i < level; i++) {
+ buf.append(" ");
+ }
+ }
+
+ public void printWithoutIndent(String arg) {
+ indented = false;
+ buf.append(arg);
+ }
+
+ public void print(String arg) {
+ if (!indented) {
+ makeIndent();
+ indented = true;
+ }
+ buf.append(arg);
+ }
+
+ public void printLn(String arg) {
+ print(arg);
+ printLn();
+ }
+
+ public void printLn() {
+ buf.append("\n");
+ indented = false;
+ }
+
+ public String getSource() {
+ return buf.toString();
+ }
+
+ @Override public String toString() {
+ return getSource();
+ }
+ }
+
+ private boolean supportErrorReporting = true;
+
+ protected SourcePrinter printer = new SourcePrinter();
+
+ private SourcePrinter staticInitializerPrinter = new SourcePrinter();
+
+ private SourcePrinter tempPrinterHolder;
+
+ protected final CppTypes cppTypes;
+
+ protected String className = "";
+
+ protected int currentArrayCount;
+
+ protected Set<String> forLoopsWithCondition = new HashSet<String>();
+
+ protected boolean inPrimitiveNoLengthFieldDeclarator = false;
+
+ protected final SymbolTable symbolTable;
+
+ protected String definePrefix;
+
+ protected String javaClassName;
+
+ protected boolean suppressPointer = false;
+
+ private final List<String> staticReleases = new LinkedList<String>();
+
+ private boolean inConstructorBody = false;
+
+ private String currentMethod = null;
+
+ private Set<String> labels = null;
+
+ private boolean destructor;
+
+ protected boolean inStatic = false;
+
+ private boolean reportTransitions = false;
+
+ private int stateLoopCallCount = 0;
+
+ /**
+ * @param cppTypes
+ */
+ public CppVisitor(CppTypes cppTypes, SymbolTable symbolTable) {
+ this.cppTypes = cppTypes;
+ this.symbolTable = symbolTable;
+ staticInitializerPrinter.indent();
+ }
+
+ public String getSource() {
+ return printer.getSource();
+ }
+
+ private String classNameFromExpression(Expression e) {
+ if (e instanceof NameExpr) {
+ NameExpr nameExpr = (NameExpr) e;
+ String name = nameExpr.getName();
+ if (Arrays.binarySearch(CLASS_NAMES, name) > -1) {
+ return name;
+ }
+ }
+ return null;
+ }
+
+ protected void printModifiers(int modifiers) {
+ }
+
+ private void printMembers(List<BodyDeclaration> members,
+ LocalSymbolTable arg) {
+ for (BodyDeclaration member : members) {
+ if ("Tokenizer".equals(javaClassName)
+ && member instanceof MethodDeclaration
+ && "stateLoop".equals(((MethodDeclaration) member).getName())) {
+ reportTransitions = true;
+ }
+ member.accept(this, arg);
+ reportTransitions = false;
+ }
+ }
+
+ private void printTypeArgs(List<Type> args, LocalSymbolTable arg) {
+ // if (args != null) {
+ // printer.print("<");
+ // for (Iterator<Type> i = args.iterator(); i.hasNext();) {
+ // Type t = i.next();
+ // t.accept(this, arg);
+ // if (i.hasNext()) {
+ // printer.print(", ");
+ // }
+ // }
+ // printer.print(">");
+ // }
+ }
+
+ private void printTypeParameters(List<TypeParameter> args,
+ LocalSymbolTable arg) {
+ // if (args != null) {
+ // printer.print("<");
+ // for (Iterator<TypeParameter> i = args.iterator(); i.hasNext();) {
+ // TypeParameter t = i.next();
+ // t.accept(this, arg);
+ // if (i.hasNext()) {
+ // printer.print(", ");
+ // }
+ // }
+ // printer.print(">");
+ // }
+ }
+
+ public void visit(Node n, LocalSymbolTable arg) {
+ throw new IllegalStateException(n.getClass().getName());
+ }
+
+ public void visit(CompilationUnit n, LocalSymbolTable arg) {
+ if (n.getTypes() != null) {
+ for (Iterator<TypeDeclaration> i = n.getTypes().iterator(); i.hasNext();) {
+ i.next().accept(this, arg);
+ printer.printLn();
+ if (i.hasNext()) {
+ printer.printLn();
+ }
+ }
+ }
+ }
+
+ public void visit(PackageDeclaration n, LocalSymbolTable arg) {
+ throw new IllegalStateException(n.getClass().getName());
+ }
+
+ public void visit(NameExpr n, LocalSymbolTable arg) {
+ if ("mappingLangToXmlLang".equals(n.getName())) {
+ printer.print("0");
+ } else if ("LANG_NS".equals(n.getName())) {
+ printer.print("ALL_NO_NS");
+ } else if ("LANG_PREFIX".equals(n.getName())) {
+ printer.print("ALL_NO_PREFIX");
+ } else if ("HTML_LOCAL".equals(n.getName())) {
+ printer.print(cppTypes.localForLiteral("html"));
+ } else if ("documentModeHandler".equals(n.getName())) {
+ printer.print("this");
+ } else if ("errorHandler".equals(n.getName())) {
+ printer.print(cppTypes.errorHandler());
+ } else {
+ printer.print(n.getName());
+ }
+ }
+
+ public void visit(QualifiedNameExpr n, LocalSymbolTable arg) {
+ n.getQualifier().accept(this, arg);
+ printer.print(".");
+ printer.print(n.getName());
+ }
+
+ public void visit(ImportDeclaration n, LocalSymbolTable arg) {
+ throw new IllegalStateException(n.getClass().getName());
+ }
+
+ public void visit(ClassOrInterfaceDeclaration n, LocalSymbolTable arg) {
+ javaClassName = n.getName();
+ className = cppTypes.classPrefix() + javaClassName;
+ definePrefix = makeDefinePrefix(className);
+
+ startClassDeclaration();
+
+ if (n.getMembers() != null) {
+ printMembers(n.getMembers(), arg);
+ }
+
+ endClassDeclaration();
+ }
+
+ private String makeDefinePrefix(String name) {
+ StringBuilder sb = new StringBuilder();
+ boolean prevWasLowerCase = true;
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+ if (c >= 'a' && c <= 'z') {
+ sb.append((char) (c - 0x20));
+ prevWasLowerCase = true;
+ } else if (c >= 'A' && c <= 'Z') {
+ if (prevWasLowerCase) {
+ sb.append('_');
+ }
+ sb.append(c);
+ prevWasLowerCase = false;
+ } else if (c >= '0' && c <= '9') {
+ sb.append(c);
+ prevWasLowerCase = false;
+ }
+ }
+ sb.append('_');
+ return sb.toString();
+ }
+
+ protected void endClassDeclaration() {
+ printer.printLn("void");
+ printer.print(className);
+ printer.printLn("::initializeStatics()");
+ printer.printLn("{");
+ printer.print(staticInitializerPrinter.getSource());
+ printer.printLn("}");
+ printer.printLn();
+
+ printer.printLn("void");
+ printer.print(className);
+ printer.printLn("::releaseStatics()");
+ printer.printLn("{");
+ printer.indent();
+ for (String del : staticReleases) {
+ printer.print(del);
+ printer.printLn(";");
+ }
+ printer.unindent();
+ printer.printLn("}");
+ printer.printLn();
+
+ if (cppTypes.hasSupplement(javaClassName)) {
+ printer.printLn();
+ printer.print("#include \"");
+ printer.print(className);
+ printer.printLn("CppSupplement.h\"");
+ }
+ }
+
+ protected void startClassDeclaration() {
+ printer.print("#define ");
+ printer.print(className);
+ printer.printLn("_cpp__");
+ printer.printLn();
+
+ String[] incs = cppTypes.boilerplateIncludes(javaClassName);
+ for (int i = 0; i < incs.length; i++) {
+ String inc = incs[i];
+ printer.print("#include \"");
+ printer.print(inc);
+ printer.printLn(".h\"");
+ }
+
+ printer.printLn();
+
+ for (int i = 0; i < Main.H_LIST.length; i++) {
+ String klazz = Main.H_LIST[i];
+ if (!klazz.equals(javaClassName)) {
+ printer.print("#include \"");
+ printer.print(cppTypes.classPrefix());
+ printer.print(klazz);
+ printer.printLn(".h\"");
+ }
+ }
+
+ printer.printLn();
+ printer.print("#include \"");
+ printer.print(className);
+ printer.printLn(".h\"");
+ printer.printLn();
+ }
+
+ public void visit(EmptyTypeDeclaration n, LocalSymbolTable arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(JavadocComment n, LocalSymbolTable arg) {
+ printer.print("/**");
+ printer.print(n.getContent());
+ printer.printLn("*/");
+ }
+
+ public void visit(ClassOrInterfaceType n, LocalSymbolTable arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ throw new IllegalStateException("Can't translate nested classes.");
+ }
+ String name = n.getName();
+ if ("String".equals(name)) {
+ if (local()) {
+ name = cppTypes.localType();
+ } else if (prefix()) {
+ name = cppTypes.prefixType();
+ } else if (nsUri()) {
+ name = cppTypes.nsUriType();
+ } else if (literal()) {
+ name = cppTypes.literalType();
+ } else if (characterName()) {
+ name = cppTypes.characterNameType();
+ } else {
+ name = cppTypes.stringType();
+ }
+ } else if ("T".equals(name) || "Object".equals(name)) {
+ if (htmlCreator()) {
+ name = cppTypes.htmlCreatorType();
+ } else if (svgCreator()) {
+ name = cppTypes.svgCreatorType();
+ } else if (creator()) {
+ name = cppTypes.creatorType();
+ } else {
+ name = cppTypes.nodeType();
+ }
+ } else if ("TokenHandler".equals(name)) {
+ name = cppTypes.classPrefix() + "TreeBuilder*";
+ } else if ("EncodingDeclarationHandler".equals(name)) {
+ name = cppTypes.encodingDeclarationHandlerType();
+ } else if ("Interner".equals(name)) {
+ name = cppTypes.internerType();
+ } else if ("TreeBuilderState".equals(name)) {
+ name = cppTypes.treeBuilderStateType();
+ } else if ("DocumentModeHandler".equals(name)) {
+ name = cppTypes.documentModeHandlerType();
+ } else if ("DocumentMode".equals(name)) {
+ name = cppTypes.documentModeType();
+ } else {
+ name = cppTypes.classPrefix() + name + (suppressPointer ? "" : "*");
+ }
+ printer.print(name);
+ printTypeArgs(n.getTypeArgs(), arg);
+ }
+
+ protected boolean inHeader() {
+ return false;
+ }
+
+ public void visit(TypeParameter n, LocalSymbolTable arg) {
+ printer.print(n.getName());
+ if (n.getTypeBound() != null) {
+ printer.print(" extends ");
+ for (Iterator<ClassOrInterfaceType> i = n.getTypeBound().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(" & ");
+ }
+ }
+ }
+ }
+
+ public void visit(PrimitiveType n, LocalSymbolTable arg) {
+ switch (n.getType()) {
+ case Boolean:
+ printer.print(cppTypes.booleanType());
+ break;
+ case Byte:
+ printer.print(cppTypes.byteType());
+ break;
+ case Char:
+ printer.print(cppTypes.charType());
+ break;
+ case Double:
+ throw new IllegalStateException("Unsupported primitive.");
+ case Float:
+ throw new IllegalStateException("Unsupported primitive.");
+ case Int:
+ if (unsigned()) {
+ printer.print(cppTypes.unsignedIntType());
+ } else {
+ printer.print(cppTypes.intType());
+ }
+ break;
+ case Long:
+ throw new IllegalStateException("Unsupported primitive.");
+ case Short:
+ throw new IllegalStateException("Unsupported primitive.");
+ }
+ }
+
+ public void visit(ReferenceType n, LocalSymbolTable arg) {
+ if (isConst()) {
+ printer.print("const ");
+ }
+ if (noLength()) {
+ n.getType().accept(this, arg);
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ if (!inPrimitiveNoLengthFieldDeclarator) {
+ printer.print("*");
+ }
+ }
+ } else {
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ if (inStatic) {
+ printer.print(cppTypes.staticArrayTemplate());
+ } else {
+ if (auto()) {
+ printer.print(cppTypes.autoArrayTemplate());
+ } else {
+ printer.print(cppTypes.arrayTemplate());
+ }
+ }
+ printer.print("<");
+ }
+ n.getType().accept(this, arg);
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print(",");
+ printer.print(cppTypes.intType());
+ printer.print(">");
+ }
+ }
+ }
+
+ public void visit(WildcardType n, LocalSymbolTable arg) {
+ printer.print("?");
+ if (n.getExtends() != null) {
+ printer.print(" extends ");
+ n.getExtends().accept(this, arg);
+ }
+ if (n.getSuper() != null) {
+ printer.print(" super ");
+ n.getSuper().accept(this, arg);
+ }
+ }
+
+ public void visit(FieldDeclaration n, LocalSymbolTable arg) {
+ currentAnnotations = n.getAnnotations();
+ fieldDeclaration(n, arg);
+ currentAnnotations = null;
+ }
+
+ protected boolean isNonToCharArrayMethodCall(Expression exp) {
+ if (exp instanceof MethodCallExpr) {
+ MethodCallExpr mce = (MethodCallExpr) exp;
+ return !"toCharArray".equals(mce.getName());
+ } else {
+ return false;
+ }
+ }
+
+ protected void fieldDeclaration(FieldDeclaration n, LocalSymbolTable arg) {
+ tempPrinterHolder = printer;
+ printer = staticInitializerPrinter;
+ int modifiers = n.getModifiers();
+ List<VariableDeclarator> variables = n.getVariables();
+ VariableDeclarator declarator = variables.get(0);
+ if (ModifierSet.isStatic(modifiers) && ModifierSet.isFinal(modifiers)
+ && !(n.getType() instanceof PrimitiveType)
+ && declarator.getInit() != null) {
+ if (n.getType() instanceof ReferenceType) {
+ ReferenceType rt = (ReferenceType) n.getType();
+ currentArrayCount = rt.getArrayCount();
+ if (currentArrayCount > 0) {
+ if (currentArrayCount != 1) {
+ throw new IllegalStateException(
+ "Multidimensional arrays not supported. " + n);
+ }
+ if (noLength()) {
+ if (rt.getType() instanceof PrimitiveType) {
+ inPrimitiveNoLengthFieldDeclarator = true;
+ printer = tempPrinterHolder;
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ printer.print(className);
+ printer.print("::");
+ declarator.getId().accept(this, arg);
+
+ printer.print(" = ");
+
+ declarator.getInit().accept(this, arg);
+
+ printer.printLn(";");
+ printer = staticInitializerPrinter;
+ } else {
+ printer = tempPrinterHolder;
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ printer.print(className);
+ printer.print("::");
+ declarator.getId().accept(this, arg);
+
+ printer.printLn(" = 0;");
+ printer = staticInitializerPrinter;
+
+ staticReleases.add("delete[] "
+ + declarator.getId().getName());
+
+ ArrayInitializerExpr aie = (ArrayInitializerExpr) declarator.getInit();
+
+ declarator.getId().accept(this, arg);
+ printer.print(" = new ");
+ // suppressPointer = true;
+ rt.getType().accept(this, arg);
+ // suppressPointer = false;
+ printer.print("[");
+ printer.print("" + aie.getValues().size());
+ printer.printLn("];");
+
+ printArrayInit(declarator.getId(), aie.getValues(),
+ arg);
+ }
+ } else if ((rt.getType() instanceof PrimitiveType) || "String".equals(rt.getType().toString())) {
+ printer = tempPrinterHolder;
+ printer.print("static ");
+ rt.getType().accept(this, arg);
+ printer.print(" const ");
+ declarator.getId().accept(this, arg);
+ printer.print("_DATA[] = ");
+ declarator.getInit().accept(this, arg);
+ printer.printLn(";");
+ printer.print(cppTypes.staticArrayTemplate());
+ printer.print("<");
+ suppressPointer = true;
+ rt.getType().accept(this, arg);
+ suppressPointer = false;
+ printer.print(",");
+ printer.print(cppTypes.intType());
+ printer.print("> ");
+ printer.print(className);
+ printer.print("::");
+ declarator.getId().accept(this, arg);
+ printer.print(" = { ");
+ declarator.getId().accept(this, arg);
+ printer.print("_DATA, ");
+ printer.print(cppTypes.arrayLengthMacro());
+ printer.print("(");
+ declarator.getId().accept(this, arg);
+ printer.printLn("_DATA) };");
+ printer = staticInitializerPrinter;
+ } else if (isNonToCharArrayMethodCall(declarator.getInit())) {
+ staticReleases.add(declarator.getId().getName()
+ + ".release()");
+ declarator.getId().accept(this, arg);
+ printer.print(" = ");
+ if (declarator.getInit() instanceof ArrayInitializerExpr) {
+
+ ArrayInitializerExpr aie = (ArrayInitializerExpr) declarator.getInit();
+ printer.print(cppTypes.arrayTemplate());
+ printer.print("<");
+ suppressPointer = true;
+ rt.getType().accept(this, arg);
+ suppressPointer = false;
+ printer.print(",");
+ printer.print(cppTypes.intType());
+ printer.print(">::");
+ printer.print(cppTypes.newArrayCreator());
+ printer.print("(");
+ printer.print("" + aie.getValues().size());
+ printer.printLn(");");
+ printArrayInit(declarator.getId(), aie.getValues(),
+ arg);
+ } else {
+ declarator.getInit().accept(this, arg);
+ printer.printLn(";");
+ }
+ }
+ } else {
+ if (ModifierSet.isStatic(modifiers)) {
+ printer = tempPrinterHolder;
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ printer.print(className);
+ printer.print("::");
+ String clazzName = n.getType().toString();
+ String field = declarator.getId().toString();
+ if (symbolTable.isAttributeOrElementName(clazzName, field)) {
+ if ("AttributeName".equals(clazzName)) {
+ printer.print("ATTR_");
+ } else if ("ElementName".equals(clazzName)) {
+ printer.print("ELT_");
+ }
+ }
+ declarator.getId().accept(this, arg);
+ printer.print(" = ");
+ printer.print(cppTypes.nullLiteral());
+ printer.printLn(";");
+ printer = staticInitializerPrinter;
+ }
+
+ if ("AttributeName".equals(n.getType().toString())) {
+ printer.print("ATTR_");
+ staticReleases.add("delete ATTR_"
+ + declarator.getId().getName());
+ } else if ("ElementName".equals(n.getType().toString())) {
+ printer.print("ELT_");
+ staticReleases.add("delete ELT_"
+ + declarator.getId().getName());
+ } else {
+ staticReleases.add("delete "
+ + declarator.getId().getName());
+ }
+ declarator.accept(this, arg);
+ printer.printLn(";");
+ }
+ } else {
+ throw new IllegalStateException(
+ "Non-reference, non-primitive fields not supported.");
+ }
+ }
+ currentArrayCount = 0;
+ printer = tempPrinterHolder;
+ inPrimitiveNoLengthFieldDeclarator = false;
+ }
+
+ private void printArrayInit(VariableDeclaratorId variableDeclaratorId,
+ List<Expression> values, LocalSymbolTable arg) {
+ for (int i = 0; i < values.size(); i++) {
+ Expression exp = values.get(i);
+ variableDeclaratorId.accept(this, arg);
+ printer.print("[");
+ printer.print("" + i);
+ printer.print("] = ");
+ if (exp instanceof NameExpr) {
+ if ("AttributeName".equals(javaClassName)) {
+ printer.print("ATTR_");
+ } else if ("ElementName".equals(javaClassName)) {
+ printer.print("ELT_");
+ }
+ }
+ exp.accept(this, arg);
+ printer.printLn(";");
+ }
+ }
+
+ public void visit(VariableDeclarator n, LocalSymbolTable arg) {
+ n.getId().accept(this, arg);
+
+ if (n.getInit() != null) {
+ printer.print(" = ");
+ n.getInit().accept(this, arg);
+ }
+ }
+
+ public void visit(VariableDeclaratorId n, LocalSymbolTable arg) {
+ printer.print(n.getName());
+ if (noLength()) {
+ for (int i = 0; i < currentArrayCount; i++) {
+ if (inPrimitiveNoLengthFieldDeclarator) {
+ printer.print("[]");
+ }
+ }
+ }
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ }
+
+ public void visit(ArrayInitializerExpr n, LocalSymbolTable arg) {
+ printer.print("{");
+ if (n.getValues() != null) {
+ printer.print(" ");
+ for (Iterator<Expression> i = n.getValues().iterator(); i.hasNext();) {
+ Expression expr = i.next();
+ expr.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ printer.print(" ");
+ }
+ printer.print("}");
+ }
+
+ public void visit(VoidType n, LocalSymbolTable arg) {
+ printer.print("void");
+ }
+
+ public void visit(ArrayAccessExpr n, LocalSymbolTable arg) {
+ n.getName().accept(this, arg);
+ printer.print("[");
+ n.getIndex().accept(this, arg);
+ printer.print("]");
+ }
+
+ public void visit(ArrayCreationExpr n, LocalSymbolTable arg) {
+ // printer.print("new ");
+ // n.getType().accept(this, arg);
+ // printTypeArgs(n.getTypeArgs(), arg);
+
+ if (n.getDimensions() != null) {
+ if (noLength()) {
+ for (Expression dim : n.getDimensions()) {
+ printer.print("new ");
+ n.getType().accept(this, arg);
+ printer.print("[");
+ dim.accept(this, arg);
+ printer.print("]");
+ }
+ } else {
+ for (Expression dim : n.getDimensions()) {
+ printer.print(cppTypes.arrayTemplate());
+ printer.print("<");
+ n.getType().accept(this, arg);
+ printer.print(",");
+ printer.print(cppTypes.intType());
+ printer.print(">::");
+ printer.print(cppTypes.newArrayCreator());
+ printer.print("(");
+ dim.accept(this, arg);
+ printer.print(")");
+ }
+ }
+ if (n.getArrayCount() > 0) {
+ throw new IllegalStateException(
+ "Nested array allocation not supported. "
+ + n.toString());
+ }
+ } else {
+ throw new IllegalStateException(
+ "Array initializer as part of array creation not supported. "
+ + n.toString());
+ }
+ }
+
+ public void visit(AssignExpr n, LocalSymbolTable arg) {
+ if (inConstructorBody) {
+ n.getTarget().accept(this, arg);
+ printer.print("(");
+ n.getValue().accept(this, arg);
+ printer.print(")");
+ } else {
+ n.getTarget().accept(this, arg);
+ printer.print(" ");
+ switch (n.getOperator()) {
+ case assign:
+ printer.print("=");
+ break;
+ case and:
+ printer.print("&=");
+ break;
+ case or:
+ printer.print("|=");
+ break;
+ case xor:
+ printer.print("^=");
+ break;
+ case plus:
+ printer.print("+=");
+ break;
+ case minus:
+ printer.print("-=");
+ break;
+ case rem:
+ printer.print("%=");
+ break;
+ case slash:
+ printer.print("/=");
+ break;
+ case star:
+ printer.print("*=");
+ break;
+ case lShift:
+ printer.print("<<=");
+ break;
+ case rSignedShift:
+ printer.print(">>=");
+ break;
+ case rUnsignedShift:
+ printer.print(">>>=");
+ break;
+ }
+ printer.print(" ");
+ n.getValue().accept(this, arg);
+ }
+ }
+
+ public void visit(BinaryExpr n, LocalSymbolTable arg) {
+ Expression right = n.getRight();
+ switch (n.getOperator()) {
+ case notEquals:
+ if (right instanceof NullLiteralExpr) {
+ printer.print("!!");
+ n.getLeft().accept(this, arg);
+ return;
+ } else if (right instanceof IntegerLiteralExpr) {
+ IntegerLiteralExpr ile = (IntegerLiteralExpr) right;
+ if ("0".equals(ile.getValue())) {
+ n.getLeft().accept(this, arg);
+ return;
+ }
+ }
+ case equals:
+ if (right instanceof NullLiteralExpr) {
+ printer.print("!");
+ n.getLeft().accept(this, arg);
+ return;
+ } else if (right instanceof IntegerLiteralExpr) {
+ IntegerLiteralExpr ile = (IntegerLiteralExpr) right;
+ if ("0".equals(ile.getValue())) {
+ printer.print("!");
+ n.getLeft().accept(this, arg);
+ return;
+ }
+ }
+ default:
+ // fall thru
+ }
+
+ n.getLeft().accept(this, arg);
+ printer.print(" ");
+ switch (n.getOperator()) {
+ case or:
+ printer.print("||");
+ break;
+ case and:
+ printer.print("&&");
+ break;
+ case binOr:
+ printer.print("|");
+ break;
+ case binAnd:
+ printer.print("&");
+ break;
+ case xor:
+ printer.print("^");
+ break;
+ case equals:
+ printer.print("==");
+ break;
+ case notEquals:
+ printer.print("!=");
+ break;
+ case less:
+ printer.print("<");
+ break;
+ case greater:
+ printer.print(">");
+ break;
+ case lessEquals:
+ printer.print("<=");
+ break;
+ case greaterEquals:
+ printer.print(">=");
+ break;
+ case lShift:
+ printer.print("<<");
+ break;
+ case rSignedShift:
+ printer.print(">>");
+ break;
+ case rUnsignedShift:
+ printer.print(">>>");
+ break;
+ case plus:
+ printer.print("+");
+ break;
+ case minus:
+ printer.print("-");
+ break;
+ case times:
+ printer.print("*");
+ break;
+ case divide:
+ printer.print("/");
+ break;
+ case remainder:
+ printer.print("%");
+ break;
+ }
+ printer.print(" ");
+ n.getRight().accept(this, arg);
+ }
+
+ public void visit(CastExpr n, LocalSymbolTable arg) {
+ printer.print("(");
+ n.getType().accept(this, arg);
+ printer.print(") ");
+ n.getExpr().accept(this, arg);
+ }
+
+ public void visit(ClassExpr n, LocalSymbolTable arg) {
+ n.getType().accept(this, arg);
+ printer.print(".class");
+ }
+
+ public void visit(ConditionalExpr n, LocalSymbolTable arg) {
+ n.getCondition().accept(this, arg);
+ printer.print(" ? ");
+ n.getThenExpr().accept(this, arg);
+ printer.print(" : ");
+ n.getElseExpr().accept(this, arg);
+ }
+
+ public void visit(EnclosedExpr n, LocalSymbolTable arg) {
+ printer.print("(");
+ n.getInner().accept(this, arg);
+ printer.print(")");
+ }
+
+ public void visit(FieldAccessExpr n, LocalSymbolTable arg) {
+ Expression scope = n.getScope();
+ String field = n.getField();
+ if (inConstructorBody && (scope instanceof ThisExpr)) {
+ printer.print(field);
+ } else if ("length".equals(field) && !(scope instanceof ThisExpr)) {
+ scope.accept(this, arg);
+ printer.print(".length");
+ } else if ("MAX_VALUE".equals(field)
+ && "Integer".equals(scope.toString())) {
+ printer.print(cppTypes.maxInteger());
+ } else {
+ String clazzName = classNameFromExpression(scope);
+ if (clazzName == null) {
+ if ("DocumentMode".equals(scope.toString())) {
+ // printer.print(cppTypes.documentModeType());
+ // printer.print(".");
+ } else if ("creator".equals(scope.toString()) || "this.creator".equals(scope.toString())) {
+ scope.accept(this, arg);
+ printer.print(".");
+ } else {
+ scope.accept(this, arg);
+ printer.print("->");
+ }
+ } else {
+ printer.print(cppTypes.classPrefix());
+ printer.print(clazzName);
+ printer.print("::");
+ if (symbolTable.isAttributeOrElementName(clazzName, field)) {
+ if ("AttributeName".equals(clazzName)) {
+ printer.print("ATTR_");
+ } else if ("ElementName".equals(clazzName)) {
+ printer.print("ELT_");
+ }
+ }
+ }
+ printer.print(field);
+ }
+ }
+
+ public void visit(InstanceOfExpr n, LocalSymbolTable arg) {
+ n.getExpr().accept(this, arg);
+ printer.print(" instanceof ");
+ n.getType().accept(this, arg);
+ }
+
+ public void visit(CharLiteralExpr n, LocalSymbolTable arg) {
+ printCharLiteral(n.getValue());
+ }
+
+ private void printCharLiteral(String val) {
+ if (val.length() != 1) {
+ printer.print("'");
+ printer.print(val);
+ printer.print("'");
+ return;
+ }
+ char c = val.charAt(0);
+ switch (c) {
+ case 0:
+ printer.print("'\\0'");
+ break;
+ case '\n':
+ printer.print("'\\n'");
+ break;
+ case '\t':
+ printer.print("'\\t'");
+ break;
+ case 0xB:
+ printer.print("'\\v'");
+ break;
+ case '\b':
+ printer.print("'\\b'");
+ break;
+ case '\r':
+ printer.print("'\\r'");
+ break;
+ case 0xC:
+ printer.print("'\\f'");
+ break;
+ case 0x7:
+ printer.print("'\\a'");
+ break;
+ case '\\':
+ printer.print("'\\\\'");
+ break;
+ case '?':
+ printer.print("'\\?'");
+ break;
+ case '\'':
+ printer.print("'\\''");
+ break;
+ case '"':
+ printer.print("'\\\"'");
+ break;
+ default:
+ if (c >= 0x20 && c <= 0x7F) {
+ printer.print("'" + c);
+ printer.print("'");
+ } else {
+ printer.print("0x");
+ printer.print(Integer.toHexString(c));
+ }
+ break;
+ }
+ }
+
+ public void visit(DoubleLiteralExpr n, LocalSymbolTable arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(IntegerLiteralExpr n, LocalSymbolTable arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(LongLiteralExpr n, LocalSymbolTable arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(IntegerLiteralMinValueExpr n, LocalSymbolTable arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(LongLiteralMinValueExpr n, LocalSymbolTable arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(StringLiteralExpr n, LocalSymbolTable arg) {
+ String val = n.getValue();
+ if ("http://www.w3.org/1999/xhtml".equals(val)) {
+ printer.print(cppTypes.xhtmlNamespaceLiteral());
+ } else if ("http://www.w3.org/2000/svg".equals(val)) {
+ printer.print(cppTypes.svgNamespaceLiteral());
+ } else if ("http://www.w3.org/2000/xmlns/".equals(val)) {
+ printer.print(cppTypes.xmlnsNamespaceLiteral());
+ } else if ("http://www.w3.org/XML/1998/namespace".equals(val)) {
+ printer.print(cppTypes.xmlNamespaceLiteral());
+ } else if ("http://www.w3.org/1999/xlink".equals(val)) {
+ printer.print(cppTypes.xlinkNamespaceLiteral());
+ } else if ("http://www.w3.org/1998/Math/MathML".equals(val)) {
+ printer.print(cppTypes.mathmlNamespaceLiteral());
+ } else if ("".equals(val) && "AttributeName".equals(javaClassName)) {
+ printer.print(cppTypes.noNamespaceLiteral());
+ } else if (val.startsWith("-/") || val.startsWith("+//")
+ || val.startsWith("http://") || val.startsWith("XSLT")) {
+ printer.print(cppTypes.stringForLiteral(val));
+ } else if (("hidden".equals(val) || "isindex".equals(val)
+ || "text/html".equals(val)
+ || "application/xhtml+xml".equals(val) || "content-type".equals(val))
+ && "TreeBuilder".equals(javaClassName)) {
+ printer.print(cppTypes.stringForLiteral(val));
+ } else if ("isQuirky".equals(currentMethod) && "html".equals(val)) {
+ printer.print(cppTypes.stringForLiteral(val));
+ } else {
+ printer.print(cppTypes.localForLiteral(val));
+ }
+ }
+
+ public void visit(BooleanLiteralExpr n, LocalSymbolTable arg) {
+ if (n.getValue()) {
+ printer.print(cppTypes.trueLiteral());
+ } else {
+ printer.print(cppTypes.falseLiteral());
+ }
+ }
+
+ public void visit(NullLiteralExpr n, LocalSymbolTable arg) {
+ printer.print(cppTypes.nullLiteral());
+ }
+
+ public void visit(ThisExpr n, LocalSymbolTable arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print("this");
+ }
+
+ public void visit(SuperExpr n, LocalSymbolTable arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print("super");
+ }
+
+ public void visit(MethodCallExpr n, LocalSymbolTable arg) {
+ if ("releaseArray".equals(n.getName())
+ && "Portability".equals(n.getScope().toString())) {
+ n.getArgs().get(0).accept(this, arg);
+ printer.print(".release()");
+ } else if ("releaseString".equals(n.getName())
+ && "Portability".equals(n.getScope().toString())) {
+ n.getArgs().get(0).accept(this, arg);
+ printer.print(".Release()");
+ } else if ("deleteArray".equals(n.getName())
+ && "Portability".equals(n.getScope().toString())) {
+ printer.print("delete[] ");
+ n.getArgs().get(0).accept(this, arg);
+ } else if ("delete".equals(n.getName())
+ && "Portability".equals(n.getScope().toString())) {
+ printer.print("delete ");
+ n.getArgs().get(0).accept(this, arg);
+ } else if (("retainElement".equals(n.getName()) || "releaseElement".equals(n.getName()))
+ && "Portability".equals(n.getScope().toString())) {
+ // ignore for now
+ } else if ("transition".equals(n.getName())
+ && n.getScope() == null) {
+ visitTransition(n, arg);
+ } else if ("arraycopy".equals(n.getName())
+ && "System".equals(n.getScope().toString())) {
+ printer.print(cppTypes.arrayCopy());
+ printer.print("(");
+ if (n.getArgs().get(0).toString().equals(
+ n.getArgs().get(2).toString())) {
+ n.getArgs().get(0).accept(this, arg);
+ printer.print(", ");
+ n.getArgs().get(1).accept(this, arg);
+ printer.print(", ");
+ n.getArgs().get(3).accept(this, arg);
+ printer.print(", ");
+ n.getArgs().get(4).accept(this, arg);
+ } else if (n.getArgs().get(1).toString().equals("0")
+ && n.getArgs().get(3).toString().equals("0")) {
+ n.getArgs().get(0).accept(this, arg);
+ printer.print(", ");
+ n.getArgs().get(2).accept(this, arg);
+ printer.print(", ");
+ n.getArgs().get(4).accept(this, arg);
+ } else {
+ for (Iterator<Expression> i = n.getArgs().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+ } else if ("binarySearch".equals(n.getName())
+ && "Arrays".equals(n.getScope().toString())) {
+ n.getArgs().get(0).accept(this, arg);
+ printer.print(".binarySearch(");
+ n.getArgs().get(1).accept(this, arg);
+ printer.print(")");
+ } else {
+ Expression scope = n.getScope();
+ if (scope != null) {
+ if (scope instanceof StringLiteralExpr) {
+ StringLiteralExpr strLit = (StringLiteralExpr) scope;
+ String str = strLit.getValue();
+ if (!"toCharArray".equals(n.getName())) {
+ throw new IllegalStateException(
+ "Unsupported method call on string literal: "
+ + n.getName());
+ }
+ printer.print("{ ");
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (i != 0) {
+ printer.print(", ");
+ }
+ printCharLiteral("" + c);
+ }
+ printer.print(" }");
+ return;
+ } else {
+ String clazzName = classNameFromExpression(scope);
+ if (clazzName == null) {
+ scope.accept(this, arg);
+ if ("length".equals(n.getName())
+ || "charAt".equals(n.getName())
+ || "creator".equals(scope.toString())) {
+ printer.print(".");
+ } else {
+ printer.print("->");
+ }
+ } else {
+ printer.print(cppTypes.classPrefix());
+ printer.print(clazzName);
+ printer.print("::");
+ }
+ }
+ }
+ printTypeArgs(n.getTypeArgs(), arg);
+ printer.print(n.getName());
+ if ("stateLoop".equals(n.getName())
+ && "Tokenizer".equals(javaClassName)
+ && cppTypes.stateLoopPolicies().length > 0) {
+ printer.print("<");
+ printer.print(cppTypes.stateLoopPolicies()[stateLoopCallCount]);
+ printer.print(">");
+ stateLoopCallCount++;
+ }
+ printer.print("(");
+ if (n.getArgs() != null) {
+ for (Iterator<Expression> i = n.getArgs().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+ }
+ }
+
+ public void visit(ObjectCreationExpr n, LocalSymbolTable arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ }
+
+ printer.print("new ");
+
+ suppressPointer = true;
+ printTypeArgs(n.getTypeArgs(), arg);
+ n.getType().accept(this, arg);
+ suppressPointer = false;
+
+ if ("AttributeName".equals(n.getType().getName())) {
+ List<Expression> args = n.getArgs();
+ while (args != null && args.size() > 3) {
+ args.remove(3);
+ }
+ }
+
+ printer.print("(");
+ if (n.getArgs() != null) {
+ for (Iterator<Expression> i = n.getArgs().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+
+ if (n.getAnonymousClassBody() != null) {
+ printer.printLn(" {");
+ printer.indent();
+ printMembers(n.getAnonymousClassBody(), arg);
+ printer.unindent();
+ printer.print("}");
+ }
+ }
+
+ public void visit(UnaryExpr n, LocalSymbolTable arg) {
+ switch (n.getOperator()) {
+ case positive:
+ printer.print("+");
+ break;
+ case negative:
+ printer.print("-");
+ break;
+ case inverse:
+ printer.print("~");
+ break;
+ case not:
+ printer.print("!");
+ break;
+ case preIncrement:
+ printer.print("++");
+ break;
+ case preDecrement:
+ printer.print("--");
+ break;
+ }
+
+ n.getExpr().accept(this, arg);
+
+ switch (n.getOperator()) {
+ case posIncrement:
+ printer.print("++");
+ break;
+ case posDecrement:
+ printer.print("--");
+ break;
+ }
+ }
+
+ public void visit(ConstructorDeclaration n, LocalSymbolTable arg) {
+ if ("TreeBuilder".equals(javaClassName)) {
+ return;
+ }
+
+ arg = new LocalSymbolTable(javaClassName, symbolTable);
+
+ // if (n.getJavaDoc() != null) {
+ // n.getJavaDoc().accept(this, arg);
+ // }
+ currentAnnotations = n.getAnnotations();
+
+ printModifiers(n.getModifiers());
+
+ printMethodNamespace();
+ printConstructorExplicit(n.getParameters());
+ printer.print(className);
+ currentAnnotations = null;
+
+ printer.print("(");
+ if (n.getParameters() != null) {
+ for (Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext();) {
+ Parameter p = i.next();
+ p.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+
+ printConstructorBody(n.getBlock(), arg);
+ }
+
+ protected void printConstructorExplicit(List<Parameter> params) {
+ }
+
+ protected void printConstructorBody(BlockStmt block, LocalSymbolTable arg) {
+ inConstructorBody = true;
+ List<Statement> statements = block.getStmts();
+ List<Statement> nonAssigns = new LinkedList<Statement>();
+ int i = 0;
+ boolean needOutdent = false;
+ for (Statement statement : statements) {
+ if (statement instanceof ExpressionStmt
+ && ((ExpressionStmt) statement).getExpression() instanceof AssignExpr) {
+ if (i == 0) {
+ printer.printLn();
+ printer.indent();
+ printer.print(": ");
+ needOutdent = true;
+ } else {
+ printer.print(",");
+ printer.printLn();
+ printer.print(" ");
+ }
+ statement.accept(this, arg);
+ i++;
+ } else {
+ nonAssigns.add(statement);
+ }
+ }
+ if (needOutdent) {
+ printer.unindent();
+ }
+ inConstructorBody = false;
+ printer.printLn();
+ printer.printLn("{");
+ printer.indent();
+ String boilerplate = cppTypes.constructorBoilerplate(className);
+ if (boilerplate != null) {
+ printer.printLn(boilerplate);
+ }
+ for (Statement statement : nonAssigns) {
+ statement.accept(this, arg);
+ printer.printLn();
+ }
+ printer.unindent();
+ printer.printLn("}");
+ printer.printLn();
+ }
+
+ public void visit(MethodDeclaration n, LocalSymbolTable arg) {
+ arg = new LocalSymbolTable(javaClassName, symbolTable);
+ if (isPrintableMethod(n.getModifiers())
+ && !(n.getName().equals("endCoalescing") || n.getName().equals(
+ "startCoalescing"))) {
+ printMethodDeclaration(n, arg);
+ }
+ }
+
+ private boolean isPrintableMethod(int modifiers) {
+ return !(ModifierSet.isAbstract(modifiers) || (ModifierSet.isProtected(modifiers) && !(ModifierSet.isFinal(modifiers) || "Tokenizer".equals(javaClassName))));
+ }
+
+ protected void printMethodDeclaration(MethodDeclaration n,
+ LocalSymbolTable arg) {
+ if (n.getName().startsWith("fatal") || n.getName().startsWith("err")
+ || n.getName().startsWith("warn")
+ || n.getName().startsWith("maybeErr")
+ || n.getName().startsWith("maybeWarn")
+ || n.getName().startsWith("note")
+ || "releaseArray".equals(n.getName())
+ || "releaseString".equals(n.getName())
+ || "deleteArray".equals(n.getName())
+ || "delete".equals(n.getName())) {
+ return;
+ }
+
+ currentMethod = n.getName();
+
+ destructor = "destructor".equals(currentMethod);
+
+ // if (n.getJavaDoc() != null) {
+ // n.getJavaDoc().accept(this, arg);
+ // }
+ currentAnnotations = n.getAnnotations();
+ boolean isInline = inline();
+ if (isInline && !inHeader()) {
+ return;
+ }
+
+ if (destructor) {
+ printModifiers(ModifierSet.PUBLIC);
+ } else {
+ printModifiers(n.getModifiers());
+ }
+
+ if ("stateLoop".equals(currentMethod)
+ && "Tokenizer".equals(javaClassName)
+ && cppTypes.stateLoopPolicies().length > 0) {
+ printer.print("template<class P>");
+ if (inHeader()) {
+ printer.print(" ");
+ } else {
+ printer.printLn();
+ }
+ }
+
+ printTypeParameters(n.getTypeParameters(), arg);
+ if (n.getTypeParameters() != null) {
+ printer.print(" ");
+ }
+ if (!destructor) {
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ }
+ printMethodNamespace();
+ if (destructor) {
+ printer.print("~");
+ printer.print(className);
+ } else {
+ printer.print(n.getName());
+ }
+
+ currentAnnotations = null;
+ printer.print("(");
+ if (n.getParameters() != null) {
+ for (Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext();) {
+ Parameter p = i.next();
+ p.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+
+ if (inHeader() == isInline) {
+ printMethodBody(n.getBody(), arg);
+ } else {
+ printer.printLn(";");
+ }
+ }
+
+ private void printMethodBody(BlockStmt n, LocalSymbolTable arg) {
+ if (n == null) {
+ printer.print(";");
+ } else {
+ printer.printLn();
+ printer.printLn("{");
+ printer.indent();
+ if (destructor) {
+ String boilerplate = cppTypes.destructorBoilderplate(className);
+ if (boilerplate != null) {
+ printer.printLn(boilerplate);
+ }
+ }
+ if (n.getStmts() != null) {
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ printer.printLn();
+ }
+ }
+ printer.unindent();
+ printer.print("}");
+ }
+ printer.printLn();
+ printer.printLn();
+ }
+
+ protected void printMethodNamespace() {
+ printer.printLn();
+ printer.print(className);
+ printer.print("::");
+ }
+
+ public void visit(Parameter n, LocalSymbolTable arg) {
+ currentAnnotations = n.getAnnotations();
+
+ arg.putLocalType(n.getId().getName(), convertType(n.getType(),
+ n.getModifiers()));
+
+ n.getType().accept(this, arg);
+ if (n.isVarArgs()) {
+ printer.print("...");
+ }
+ printer.print(" ");
+ n.getId().accept(this, arg);
+ currentAnnotations = null;
+ }
+
+ public void visit(ExplicitConstructorInvocationStmt n, LocalSymbolTable arg) {
+ if (n.isThis()) {
+ printTypeArgs(n.getTypeArgs(), arg);
+ printer.print("this");
+ } else {
+ if (n.getExpr() != null) {
+ n.getExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printTypeArgs(n.getTypeArgs(), arg);
+ printer.print("super");
+ }
+ printer.print("(");
+ if (n.getArgs() != null) {
+ for (Iterator<Expression> i = n.getArgs().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(");");
+ }
+
+ public void visit(VariableDeclarationExpr n, LocalSymbolTable arg) {
+ currentAnnotations = n.getAnnotations();
+
+ arg.putLocalType(n.getVars().get(0).toString(), convertType(
+ n.getType(), n.getModifiers()));
+
+ n.getType().accept(this, arg);
+ printer.print(" ");
+
+ for (Iterator<VariableDeclarator> i = n.getVars().iterator(); i.hasNext();) {
+ VariableDeclarator v = i.next();
+ v.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ currentAnnotations = null;
+ }
+
+ public void visit(TypeDeclarationStmt n, LocalSymbolTable arg) {
+ n.getTypeDeclaration().accept(this, arg);
+ }
+
+ public void visit(AssertStmt n, LocalSymbolTable arg) {
+ String message = null;
+ Expression msg = n.getMessage();
+ boolean hasCheck = true;
+ if (msg != null) {
+ if (msg instanceof StringLiteralExpr) {
+ StringLiteralExpr sle = (StringLiteralExpr) msg;
+ message = sle.getValue();
+ } else {
+ throw new RuntimeException("Bad assertion message.");
+ }
+ }
+ String macro = cppTypes.assertionMacro();
+ if (message != null && message.startsWith("RELEASE: ")) {
+ message = message.substring("RELEASE: ".length());
+ macro = cppTypes.releaseAssertionMacro();
+ Expression check = n.getCheck();
+ if (check instanceof BooleanLiteralExpr) {
+ BooleanLiteralExpr expr = (BooleanLiteralExpr) check;
+ if (!expr.getValue()) {
+ hasCheck = false;
+ macro = cppTypes.crashMacro();
+ }
+ }
+ }
+ if (macro != null) {
+ printer.print(macro);
+ printer.print("(");
+ if (hasCheck) {
+ n.getCheck().accept(this, arg);
+ }
+ if (message != null) {
+ if (hasCheck) {
+ printer.print(", ");
+ }
+ printer.print("\"");
+ for (int i = 0; i < message.length(); i++) {
+ char c = message.charAt(i);
+ if (c == '"') {
+ printer.print("\"");
+ } else if (c >= ' ' && c <= '~') {
+ printer.print("" + c);
+ } else {
+ throw new RuntimeException("Bad assertion message string.");
+ }
+ }
+ printer.print("\"");
+ }
+ printer.print(");");
+ }
+ }
+
+ public void visit(BlockStmt n, LocalSymbolTable arg) {
+ printer.printLn("{");
+ if (n.getStmts() != null) {
+ printer.indent();
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ printer.printLn();
+ }
+ printer.unindent();
+ }
+ printer.print("}");
+
+ }
+
+ public void visit(LabeledStmt n, LocalSymbolTable arg) {
+ // Only conditionless for loops are needed and supported
+ // Not implementing general Java continue semantics in order
+ // to keep the generated C++ more readable.
+ Statement stmt = n.getStmt();
+ if (stmt instanceof ForStmt) {
+ ForStmt forLoop = (ForStmt) stmt;
+ if (!(forLoop.getInit() == null && forLoop.getCompare() == null && forLoop.getUpdate() == null)) {
+ forLoopsWithCondition.add(n.getLabel());
+ }
+ } else {
+ throw new IllegalStateException(
+ "Only for loop supported as labeled statement. Line: "
+ + n.getBeginLine());
+ }
+ String label = n.getLabel();
+ if (labels.contains(label)) {
+ printer.print(label);
+ printer.print(": ");
+ }
+ stmt.accept(this, arg);
+ printer.printLn();
+ label += "_end";
+ if (labels.contains(label)) {
+ printer.print(label);
+ printer.print(": ;");
+ }
+ }
+
+ public void visit(EmptyStmt n, LocalSymbolTable arg) {
+ printer.print(";");
+ }
+
+ public void visit(ExpressionStmt n, LocalSymbolTable arg) {
+ Expression e = n.getExpression();
+ if (isCompletedCharacterReference(e)) {
+ printer.print(cppTypes.completedCharacterReference());
+ printer.print(";");
+ return;
+ }
+ boolean needsCondition = isTokenizerErrorReportingExpression(e);
+ if (!needsCondition && isDroppedExpression(e)) {
+ return;
+ }
+ if (needsCondition) {
+ printer.print("if (");
+ printer.print(cppTypes.tokenizerErrorCondition());
+ printer.printLn(") {");
+ printer.indent();
+ }
+ e.accept(this, arg);
+ if (!inConstructorBody) {
+ printer.print(";");
+ }
+ if (needsCondition) {
+ printer.printLn();
+ printer.unindent();
+ printer.print("}");
+ }
+ }
+
+ private void visitTransition(MethodCallExpr call, LocalSymbolTable arg) {
+ List<Expression> args = call.getArgs();
+ if (reportTransitions) {
+ printer.print(cppTypes.transition());
+ printer.print("(");
+ printer.print(cppTypes.firstTransitionArg());
+ printer.print(", ");
+ args.get(1).accept(this, arg);
+ printer.print(", ");
+ args.get(2).accept(this, arg);
+ printer.print(", ");
+ args.get(3).accept(this, arg);
+ printer.print(")");
+ } else {
+ args.get(1).accept(this, arg);
+ }
+ }
+
+ private boolean isTokenizerErrorReportingExpression(Expression e) {
+ if (!reportTransitions) {
+ return false;
+ }
+ if (e instanceof MethodCallExpr) {
+ MethodCallExpr methodCallExpr = (MethodCallExpr) e;
+ String name = methodCallExpr.getName();
+ if (supportErrorReporting && !name.startsWith("errHtml4")
+ && ("stateLoop".equals(currentMethod))
+ && (name.startsWith("err") || name.startsWith("maybeErr"))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isCompletedCharacterReference(Expression e) {
+ if (!reportTransitions) {
+ return false;
+ }
+ if (e instanceof MethodCallExpr) {
+ MethodCallExpr methodCallExpr = (MethodCallExpr) e;
+ String name = methodCallExpr.getName();
+ if (name.equals("completedNamedCharacterReference")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isDroppedExpression(Expression e) {
+ if (e instanceof MethodCallExpr) {
+ MethodCallExpr methodCallExpr = (MethodCallExpr) e;
+ String name = methodCallExpr.getName();
+ if (name.startsWith("fatal") || name.startsWith("note")
+ || name.startsWith("errHtml4") || name.startsWith("warn")
+ || name.startsWith("maybeWarn")) {
+ return true;
+ }
+ if (supportErrorReporting
+ && ("stateLoop".equals(currentMethod) && !reportTransitions)
+ && (name.startsWith("err") || name.startsWith("maybeErr"))) {
+ return true;
+ }
+ if (name.equals("completedNamedCharacterReference")
+ && !reportTransitions) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void visit(SwitchStmt n, LocalSymbolTable arg) {
+ printer.print("switch(");
+ n.getSelector().accept(this, arg);
+ printer.printLn(") {");
+ if (n.getEntries() != null) {
+ printer.indent();
+ for (SwitchEntryStmt e : n.getEntries()) {
+ e.accept(this, arg);
+ }
+ printer.unindent();
+ }
+ printer.print("}");
+
+ }
+
+ public void visit(SwitchEntryStmt n, LocalSymbolTable arg) {
+ if (n.getLabel() != null) {
+ boolean isMenuitem = n.getLabel().toString().equals("MENUITEM");
+ if (isMenuitem) {
+ printer.printWithoutIndent("#ifdef ENABLE_VOID_MENUITEM\n");
+ }
+ printer.print("case ");
+ n.getLabel().accept(this, arg);
+ printer.print(":");
+ if (isMenuitem) {
+ printer.printWithoutIndent("\n#endif");
+ }
+ } else {
+ printer.print("default:");
+ }
+ if (isNoStatement(n.getStmts())) {
+ printer.printLn();
+ printer.indent();
+ if (n.getLabel() == null) {
+ printer.printLn("; // fall through");
+ }
+ printer.unindent();
+ } else {
+ printer.printLn(" {");
+ printer.indent();
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ printer.printLn();
+ }
+ printer.unindent();
+ printer.printLn("}");
+ }
+ }
+
+ private boolean isNoStatement(List<Statement> stmts) {
+ if (stmts == null) {
+ return true;
+ }
+ for (Statement statement : stmts) {
+ if (!isDroppableStatement(statement)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isDroppableStatement(Statement statement) {
+ if (statement instanceof AssertStmt) {
+ return true;
+ } else if (statement instanceof ExpressionStmt) {
+ ExpressionStmt es = (ExpressionStmt) statement;
+ if (isDroppedExpression(es.getExpression())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void visit(BreakStmt n, LocalSymbolTable arg) {
+ if (n.getId() != null) {
+ printer.print(cppTypes.breakMacro());
+ printer.print("(");
+ printer.print(n.getId());
+ printer.print(")");
+ } else {
+ printer.print("break");
+ }
+ printer.print(";");
+ }
+
+ public void visit(ReturnStmt n, LocalSymbolTable arg) {
+ printer.print("return");
+ if (n.getExpr() != null) {
+ printer.print(" ");
+ n.getExpr().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(EnumDeclaration n, LocalSymbolTable arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ currentAnnotations = n.getAnnotations();
+ // if (annotations != null) {
+ // for (AnnotationExpr a : annotations) {
+ // a.accept(this, arg);
+ // printer.printLn();
+ // }
+ // }
+ printModifiers(n.getModifiers());
+
+ printer.print("enum ");
+ printer.print(n.getName());
+
+ currentAnnotations = null;
+
+ if (n.getImplements() != null) {
+ printer.print(" implements ");
+ for (Iterator<ClassOrInterfaceType> i = n.getImplements().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getEntries() != null) {
+ printer.printLn();
+ for (Iterator<EnumConstantDeclaration> i = n.getEntries().iterator(); i.hasNext();) {
+ EnumConstantDeclaration e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ if (n.getMembers() != null) {
+ printer.printLn(";");
+ printMembers(n.getMembers(), arg);
+ } else {
+ if (n.getEntries() != null) {
+ printer.printLn();
+ }
+ }
+ printer.unindent();
+ printer.print("}");
+ }
+
+ public void visit(EnumConstantDeclaration n, LocalSymbolTable arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ currentAnnotations = n.getAnnotations();
+ // if (annotations != null) {
+ // for (AnnotationExpr a : annotations) {
+ // a.accept(this, arg);
+ // printer.printLn();
+ // }
+ // }
+ printer.print(n.getName());
+
+ currentAnnotations = null;
+
+ if (n.getArgs() != null) {
+ printer.print("(");
+ for (Iterator<Expression> i = n.getArgs().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ printer.print(")");
+ }
+
+ if (n.getClassBody() != null) {
+ printer.printLn(" {");
+ printer.indent();
+ printMembers(n.getClassBody(), arg);
+ printer.unindent();
+ printer.printLn("}");
+ }
+ }
+
+ public void visit(EmptyMemberDeclaration n, LocalSymbolTable arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(InitializerDeclaration n, LocalSymbolTable arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.isStatic()) {
+ printer.print("static ");
+ }
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(IfStmt n, LocalSymbolTable arg) {
+ if (TranslatorUtils.isDocumentModeHandlerNullCheck(n.getCondition())) {
+ Statement then = n.getThenStmt();
+ if (then instanceof BlockStmt) {
+ BlockStmt block = (BlockStmt) then;
+ List<Statement> statements = block.getStmts();
+ if (statements != null && statements.size() == 1) {
+ statements.get(0).accept(this, arg);
+ } else {
+ then.accept(this, arg);
+ }
+ } else {
+ then.accept(this, arg);
+ }
+ } else if (!TranslatorUtils.isErrorHandlerIf(n.getCondition(), supportErrorReporting)) {
+ if (TranslatorUtils.isErrorOnlyBlock(n.getThenStmt(), supportErrorReporting)) {
+ if (n.getElseStmt() != null
+ && !TranslatorUtils.isErrorOnlyBlock(n.getElseStmt(), supportErrorReporting)) {
+ printer.print("if (");
+ if (n.getCondition() instanceof BinaryExpr) {
+ BinaryExpr binExpr = (BinaryExpr) n.getCondition();
+ switch (binExpr.getOperator()) {
+ case equals:
+ binExpr.getLeft().accept(this, arg);
+ printer.print(" != ");
+ binExpr.getRight().accept(this, arg);
+ break;
+ case notEquals:
+ binExpr.getLeft().accept(this, arg);
+ printer.print(" == ");
+ binExpr.getRight().accept(this, arg);
+ break;
+ default:
+ printer.print("!(");
+ formatCondition(n.getCondition(), arg);
+ printer.print(")");
+ break;
+ }
+ } else {
+ printer.print("!(");
+ formatCondition(n.getCondition(), arg);
+ printer.print(")");
+ }
+ printer.print(") ");
+ n.getElseStmt().accept(this, arg);
+ }
+ } else {
+ boolean unlikely = (currentMethod != null)
+ && (Arrays.binarySearch(
+ METHODS_WITH_UNLIKELY_CONDITIONS,
+ currentMethod) >= 0);
+ printer.print("if (");
+ if (unlikely) {
+ printer.print(cppTypes.unlikely());
+ printer.print("(");
+ }
+ formatCondition(n.getCondition(), arg);
+ if (unlikely) {
+ printer.print(")");
+ }
+ printer.print(") ");
+ n.getThenStmt().accept(this, arg);
+ if (n.getElseStmt() != null
+ && !TranslatorUtils.isErrorOnlyBlock(n.getElseStmt(), supportErrorReporting)) {
+ printer.print(" else ");
+ n.getElseStmt().accept(this, arg);
+ }
+ }
+ }
+ }
+
+ private void formatCondition(Expression expr, LocalSymbolTable arg) {
+ if (expr instanceof BinaryExpr) {
+ BinaryExpr binExpr = (BinaryExpr) expr;
+ switch (binExpr.getOperator()) {
+ case notEquals:
+ if (binExpr.getRight() instanceof NullLiteralExpr) {
+ binExpr.getLeft().accept(this, arg);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ expr.accept(this, arg);
+ }
+
+
+ public void visit(WhileStmt n, LocalSymbolTable arg) {
+ printer.print("while (");
+ n.getCondition().accept(this, arg);
+ printer.print(") ");
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ContinueStmt n, LocalSymbolTable arg) {
+ // Not supporting the general Java continue semantics.
+ // Instead, making the generated code more readable for the
+ // case at hand.
+ if (n.getId() != null) {
+ printer.print(cppTypes.continueMacro());
+ printer.print("(");
+ printer.print(n.getId());
+ printer.print(")");
+ if (forLoopsWithCondition.contains(n.getId())) {
+ throw new IllegalStateException(
+ "Continue attempted with a loop that has a condition. "
+ + className + " " + n.getId());
+ }
+ } else {
+ printer.print("continue");
+ }
+ printer.print(";");
+ }
+
+ public void visit(DoStmt n, LocalSymbolTable arg) {
+ printer.print("do ");
+ n.getBody().accept(this, arg);
+ printer.print(" while (");
+ n.getCondition().accept(this, arg);
+ printer.print(");");
+ }
+
+ public void visit(ForeachStmt n, LocalSymbolTable arg) {
+ printer.print("for (");
+ n.getVariable().accept(this, arg);
+ printer.print(" : ");
+ n.getIterable().accept(this, arg);
+ printer.print(") ");
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ForStmt n, LocalSymbolTable arg) {
+ printer.print("for (");
+ if (n.getInit() != null) {
+ for (Iterator<Expression> i = n.getInit().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print("; ");
+ if (n.getCompare() != null) {
+ n.getCompare().accept(this, arg);
+ }
+ printer.print("; ");
+ if (n.getUpdate() != null) {
+ for (Iterator<Expression> i = n.getUpdate().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(") ");
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ThrowStmt n, LocalSymbolTable arg) {
+ printer.print("throw ");
+ n.getExpr().accept(this, arg);
+ printer.print(";");
+ }
+
+ public void visit(SynchronizedStmt n, LocalSymbolTable arg) {
+ printer.print("synchronized (");
+ n.getExpr().accept(this, arg);
+ printer.print(") ");
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(TryStmt n, LocalSymbolTable arg) {
+ printer.print("try ");
+ n.getTryBlock().accept(this, arg);
+ if (n.getCatchs() != null) {
+ for (CatchClause c : n.getCatchs()) {
+ c.accept(this, arg);
+ }
+ }
+ if (n.getFinallyBlock() != null) {
+ printer.print(" finally ");
+ n.getFinallyBlock().accept(this, arg);
+ }
+ }
+
+ public void visit(CatchClause n, LocalSymbolTable arg) {
+ printer.print(" catch (");
+ n.getExcept().accept(this, arg);
+ printer.print(") ");
+ n.getCatchBlock().accept(this, arg);
+
+ }
+
+ public void visit(AnnotationDeclaration n, LocalSymbolTable arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ currentAnnotations = n.getAnnotations();
+ // if (annotations != null) {
+ // for (AnnotationExpr a : annotations) {
+ // a.accept(this, arg);
+ // printer.printLn();
+ // }
+ // }
+ printModifiers(n.getModifiers());
+
+ printer.print("@interface ");
+ printer.print(n.getName());
+ currentAnnotations = null;
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getMembers() != null) {
+ printMembers(n.getMembers(), arg);
+ }
+ printer.unindent();
+ printer.print("}");
+ }
+
+ public void visit(AnnotationMemberDeclaration n, LocalSymbolTable arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ currentAnnotations = n.getAnnotations();
+ // if (annotations != null) {
+ // for (AnnotationExpr a : annotations) {
+ // a.accept(this, arg);
+ // printer.printLn();
+ // }
+ // }
+ printModifiers(n.getModifiers());
+
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ printer.print(n.getName());
+ currentAnnotations = null;
+ printer.print("()");
+ if (n.getDefaultValue() != null) {
+ printer.print(" default ");
+ n.getDefaultValue().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(MarkerAnnotationExpr n, LocalSymbolTable arg) {
+ printer.print("@");
+ n.getName().accept(this, arg);
+ }
+
+ public void visit(SingleMemberAnnotationExpr n, LocalSymbolTable arg) {
+ printer.print("@");
+ n.getName().accept(this, arg);
+ printer.print("(");
+ n.getMemberValue().accept(this, arg);
+ printer.print(")");
+ }
+
+ public void visit(NormalAnnotationExpr n, LocalSymbolTable arg) {
+ printer.print("@");
+ n.getName().accept(this, arg);
+ printer.print("(");
+ if (n.getPairs() != null) {
+ for (Iterator<MemberValuePair> i = n.getPairs().iterator(); i.hasNext();) {
+ MemberValuePair m = i.next();
+ m.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+ }
+
+ public void visit(MemberValuePair n, LocalSymbolTable arg) {
+ printer.print(n.getName());
+ printer.print(" = ");
+ n.getValue().accept(this, arg);
+ }
+
+ public void visit(LineComment n, LocalSymbolTable arg) {
+ printer.print("//");
+ printer.printLn(n.getContent());
+ }
+
+ public void visit(BlockComment n, LocalSymbolTable arg) {
+ printer.print("/*");
+ printer.print(n.getContent());
+ printer.printLn("*/");
+ }
+
+ public void setLabels(Set<String> labels) {
+ this.labels = labels;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/GkAtomParser.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/GkAtomParser.java
new file mode 100644
index 000000000..3d642c0e0
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/GkAtomParser.java
@@ -0,0 +1,70 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GkAtomParser {
+
+ private static final Pattern ATOM = Pattern.compile("^GK_ATOM\\(([^,]+),\\s*\"([^\"]*)\"\\).*$");
+
+ private final BufferedReader reader;
+
+ public GkAtomParser(Reader reader) {
+ this.reader = new BufferedReader(reader);
+ }
+
+ public Map<String, String> parse() throws IOException {
+ Map<String, String> map = new HashMap<String, String>();
+ String line;
+ while((line = reader.readLine()) != null) {
+ Matcher m = ATOM.matcher(line);
+ if (m.matches()) {
+ map.put(m.group(2), m.group(1));
+ }
+ }
+ return map;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/HVisitor.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/HVisitor.java
new file mode 100644
index 000000000..902eb348a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/HVisitor.java
@@ -0,0 +1,291 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.util.List;
+
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.ModifierSet;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+
+public class HVisitor extends CppVisitor {
+
+ private enum Visibility {
+ NONE, PRIVATE, PUBLIC, PROTECTED,
+ }
+
+ private Visibility previousVisibility = Visibility.NONE;
+
+ /**
+ * @see nu.validator.htmlparser.cpptranslate.CppVisitor#printMethodNamespace()
+ */
+ @Override protected void printMethodNamespace() {
+ }
+
+ public HVisitor(CppTypes cppTypes, SymbolTable symbolTable) {
+ super(cppTypes, symbolTable);
+ }
+
+ /**
+ * @see nu.validator.htmlparser.cpptranslate.CppVisitor#startClassDeclaration()
+ */
+ @Override protected void startClassDeclaration() {
+ printer.print("#ifndef ");
+ printer.print(className);
+ printer.printLn("_h");
+ printer.print("#define ");
+ printer.print(className);
+ printer.printLn("_h");
+
+ printer.printLn();
+
+ String[] incs = cppTypes.boilerplateIncludes(javaClassName);
+ for (int i = 0; i < incs.length; i++) {
+ String inc = incs[i];
+ if (className.equals(inc)) {
+ continue;
+ }
+ printer.print("#include \"");
+ printer.print(inc);
+ printer.printLn(".h\"");
+ }
+
+ printer.printLn();
+
+ String[] forwDecls = cppTypes.boilerplateForwardDeclarations();
+ for (int i = 0; i < forwDecls.length; i++) {
+ String decl = forwDecls[i];
+ printer.print("class ");
+ printer.print(decl);
+ printer.printLn(";");
+ }
+
+ printer.printLn();
+
+ for (int i = 0; i < Main.H_LIST.length; i++) {
+ String klazz = Main.H_LIST[i];
+ if (!(klazz.equals(javaClassName) || klazz.equals("StackNode"))) {
+ printer.print("class ");
+ printer.print(cppTypes.classPrefix());
+ printer.print(klazz);
+ printer.printLn(";");
+ }
+ }
+
+ printer.printLn();
+
+ String[] otherDecls = cppTypes.boilerplateDeclarations(javaClassName);
+ for (int i = 0; i < otherDecls.length; i++) {
+ String decl = otherDecls[i];
+ printer.printLn(decl);
+ }
+
+ printer.printLn();
+
+ printer.print("class ");
+ printer.print(className);
+ if ("StateSnapshot".equals(javaClassName) || "TreeBuilder".equals(javaClassName)) {
+ printer.print(" : public ");
+ printer.print(cppTypes.treeBuilderStateInterface());
+ }
+ printer.printLn();
+ printer.printLn("{");
+ printer.indent();
+ printer.indent();
+ }
+
+ /**
+ * @see nu.validator.htmlparser.cpptranslate.CppVisitor#endClassDeclaration()
+ */
+ @Override protected void endClassDeclaration() {
+ printModifiers(ModifierSet.PUBLIC | ModifierSet.STATIC);
+ printer.printLn("void initializeStatics();");
+ printModifiers(ModifierSet.PUBLIC | ModifierSet.STATIC);
+ printer.printLn("void releaseStatics();");
+
+ printer.unindent();
+ printer.unindent();
+
+ if (cppTypes.hasSupplement(javaClassName)) {
+ printer.printLn();
+ printer.print("#include \"");
+ printer.print(className);
+ printer.printLn("HSupplement.h\"");
+ }
+
+ printer.printLn("};");
+ printer.printLn();
+ printer.printLn("#endif");
+ }
+
+ /**
+ * @see nu.validator.htmlparser.cpptranslate.CppVisitor#printModifiers(int)
+ */
+ @Override protected void printModifiers(int modifiers) {
+ if (ModifierSet.isPrivate(modifiers)) {
+ if (previousVisibility != Visibility.PRIVATE) {
+ printer.unindent();
+ printer.printLn("private:");
+ printer.indent();
+ previousVisibility = Visibility.PRIVATE;
+ }
+ } else if (ModifierSet.isProtected(modifiers)) {
+ if (previousVisibility != Visibility.PROTECTED) {
+ printer.unindent();
+ printer.printLn("protected:");
+ printer.indent();
+ previousVisibility = Visibility.PROTECTED;
+ }
+ } else {
+ if (previousVisibility != Visibility.PUBLIC) {
+ printer.unindent();
+ printer.printLn("public:");
+ printer.indent();
+ previousVisibility = Visibility.PUBLIC;
+ }
+ }
+ if (inline()) {
+ printer.print("inline ");
+ }
+ if (virtual()) {
+ printer.print("virtual ");
+ }
+ if (ModifierSet.isStatic(modifiers)) {
+ printer.print("static ");
+ }
+ }
+
+ /**
+ * @see nu.validator.htmlparser.cpptranslate.CppVisitor#fieldDeclaration(japa.parser.ast.body.FieldDeclaration, java.lang.LocalSymbolTable)
+ */
+ @Override protected void fieldDeclaration(FieldDeclaration n, LocalSymbolTable arg) {
+ int modifiers = n.getModifiers();
+ List<VariableDeclarator> variables = n.getVariables();
+ VariableDeclarator declarator = variables.get(0);
+ if (ModifierSet.isStatic(modifiers) && ModifierSet.isFinal(modifiers)
+ && n.getType() instanceof PrimitiveType) {
+ PrimitiveType type = (PrimitiveType) n.getType();
+ if (type.getType() != PrimitiveType.Primitive.Int) {
+ throw new IllegalStateException(
+ "Only int constant #defines supported.");
+ }
+ if (variables.size() != 1) {
+ throw new IllegalStateException(
+ "More than one variable declared by one declarator.");
+ }
+ printModifiers(modifiers);
+ printer.print("const ");
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ declarator.getId().accept(this, arg);
+ printer.print(" = ");
+ declarator.getInit().accept(this, arg);
+ printer.printLn(";");
+ printer.printLn();
+ symbolTable.addPrimitiveConstant(javaClassName, declarator.getId().toString());
+ } else {
+ if (n.getType() instanceof ReferenceType) {
+ ReferenceType rt = (ReferenceType) n.getType();
+ currentArrayCount = rt.getArrayCount();
+ if (currentArrayCount > 0
+ && (rt.getType() instanceof PrimitiveType) && declarator.getInit() != null) {
+ if (!ModifierSet.isStatic(modifiers)) {
+ throw new IllegalStateException(
+ "Non-static array case not supported here." + declarator);
+ }
+ if (noLength()) {
+ inPrimitiveNoLengthFieldDeclarator = true;
+ }
+ }
+ }
+ printModifiers(modifiers);
+ inStatic = ModifierSet.isStatic(modifiers);
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ if (ModifierSet.isStatic(modifiers)) {
+ if ("AttributeName".equals(n.getType().toString())) {
+ printer.print("ATTR_");
+ } else if ("ElementName".equals(n.getType().toString())) {
+ printer.print("ELT_");
+ }
+ }
+ declarator.getId().accept(this, arg);
+ printer.printLn(";");
+ currentArrayCount = 0;
+ inStatic = false;
+ inPrimitiveNoLengthFieldDeclarator = false;
+ }
+ }
+
+ /**
+ * @see nu.validator.htmlparser.cpptranslate.CppVisitor#printConstructorExplicit(java.util.List<japa.parser.ast.body.Parameter>)
+ */
+ @Override protected void printConstructorExplicit(List<Parameter> params) {
+ if (params != null && params.size() == 1) {
+ printer.print("explicit ");
+ }
+ }
+
+ /**
+ * @see nu.validator.htmlparser.cpptranslate.CppVisitor#printConstructorBody(japa.parser.ast.stmt.BlockStmt, java.lang.LocalSymbolTable)
+ */
+ @Override protected void printConstructorBody(BlockStmt block, LocalSymbolTable arg) {
+ printer.printLn(";");
+ }
+
+ /**
+ * @see nu.validator.htmlparser.cpptranslate.CppVisitor#visit(japa.parser.ast.body.MethodDeclaration, java.lang.LocalSymbolTable)
+ */
+ @Override public void visit(MethodDeclaration n, LocalSymbolTable arg) {
+ arg = new LocalSymbolTable(javaClassName, symbolTable);
+ printMethodDeclaration(n, arg);
+ }
+
+ /**
+ * @see nu.validator.htmlparser.cpptranslate.CppVisitor#inHeader()
+ */
+ @Override protected boolean inHeader() {
+ return true;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LabelVisitor.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LabelVisitor.java
new file mode 100644
index 000000000..f27d465a3
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LabelVisitor.java
@@ -0,0 +1,84 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.visitor.VoidVisitorAdapter;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class LabelVisitor extends VoidVisitorAdapter<Object> {
+
+ private final Set<String> labels = new HashSet<String>();
+
+ public LabelVisitor() {
+ }
+
+ /**
+ * @see japa.parser.ast.visitor.VoidVisitorAdapter#visit(japa.parser.ast.stmt.BreakStmt, java.lang.Object)
+ */
+ @Override
+ public void visit(BreakStmt n, Object arg) {
+ String label = n.getId();
+ if (label != null) {
+ labels.add(label + "_end");
+ }
+ }
+
+ /**
+ * @see japa.parser.ast.visitor.VoidVisitorAdapter#visit(japa.parser.ast.stmt.ContinueStmt, java.lang.Object)
+ */
+ @Override
+ public void visit(ContinueStmt n, Object arg) {
+ String label = n.getId();
+ if (label != null) {
+ labels.add(label);
+ }
+ }
+
+ /**
+ * Returns the labels.
+ *
+ * @return the labels
+ */
+ public Set<String> getLabels() {
+ return labels;
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LicenseExtractor.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LicenseExtractor.java
new file mode 100644
index 000000000..e4030f438
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LicenseExtractor.java
@@ -0,0 +1,75 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+public class LicenseExtractor {
+
+ private final Reader reader;
+
+ public LicenseExtractor(File file) throws IOException {
+ this.reader = new InputStreamReader(new FileInputStream(file), "utf-8");
+ }
+
+ public String extract() throws IOException {
+ boolean prevWasAsterisk = false;
+ StringBuilder sb = new StringBuilder();
+ int c;
+ while ((c = reader.read()) != -1) {
+ sb.append((char)c);
+ switch (c) {
+ case '*':
+ prevWasAsterisk = true;
+ continue;
+ case '/':
+ if (prevWasAsterisk) {
+ return sb.toString();
+ }
+ default:
+ prevWasAsterisk = false;
+ continue;
+ }
+ }
+ return "";
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LocalSymbolTable.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LocalSymbolTable.java
new file mode 100644
index 000000000..a9375e88a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/LocalSymbolTable.java
@@ -0,0 +1,89 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class LocalSymbolTable {
+
+ private final Map<String, Type> locals = new HashMap<String, Type>();
+
+ private final String javaClassName;
+
+ private final SymbolTable delegate;
+
+ /**
+ * @param javaClassName
+ * @param delegate
+ */
+ public LocalSymbolTable(String javaClassName, SymbolTable delegate) {
+ this.javaClassName = javaClassName;
+ this.delegate = delegate;
+ }
+
+ public void putLocalType(String name, Type type) {
+ locals.put(name, type);
+ }
+
+ /**
+ * @param klazz
+ * @param variable
+ * @return
+ * @see nu.validator.htmlparser.cpptranslate.SymbolTable#getFieldType(java.lang.String, java.lang.String)
+ */
+ public Type getVariableType(String klazz, String variable) {
+ if (klazz == null) {
+ Type type = locals.get(variable);
+ if (type != null) {
+ return type;
+ }
+ }
+ return delegate.getFieldType(((klazz == null || "this".equals(klazz)) ? javaClassName : klazz), variable);
+ }
+
+ /**
+ * @param klazz may be <code>null</code> or "this"
+ * @param method
+ * @return
+ * @see nu.validator.htmlparser.cpptranslate.SymbolTable#getMethodReturnType(java.lang.String, java.lang.String)
+ */
+ public Type getMethodReturnType(String klazz, String method) {
+ return delegate.getMethodReturnType(((klazz == null || "this".equals(klazz)) ? javaClassName : klazz), method);
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/Main.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/Main.java
new file mode 100644
index 000000000..307d7d8a6
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/Main.java
@@ -0,0 +1,145 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+import japa.parser.JavaParser;
+import japa.parser.ParseException;
+import japa.parser.ast.CompilationUnit;
+
+public class Main {
+
+ static final String[] H_LIST = {
+ "AttributeName",
+ "ElementName",
+ "Tokenizer",
+ "TreeBuilder",
+ "MetaScanner",
+ "StackNode",
+ "UTF16Buffer",
+ "StateSnapshot",
+ "Portability",
+ };
+
+ private static final String[] CPP_LIST = {
+ "AttributeName",
+ "ElementName",
+ "Tokenizer",
+ "TreeBuilder",
+ "MetaScanner",
+ "StackNode",
+ "UTF16Buffer",
+ "StateSnapshot",
+ };
+
+ /**
+ * @param args
+ * @throws ParseException
+ * @throws IOException
+ */
+ public static void main(String[] args) throws ParseException, IOException {
+ CppTypes cppTypes = new CppTypes(new File(args[2]));
+ SymbolTable symbolTable = new SymbolTable();
+
+ File javaDirectory = new File(args[0]);
+ File targetDirectory = new File(args[1]);
+ File cppDirectory = targetDirectory;
+ File javaCopyDirectory = new File(targetDirectory, "javasrc");
+
+ for (int i = 0; i < H_LIST.length; i++) {
+ parseFile(cppTypes, javaDirectory, cppDirectory, H_LIST[i], ".h", new HVisitor(cppTypes, symbolTable));
+ copyFile(new File(javaDirectory, H_LIST[i] + ".java"), new File(javaCopyDirectory, H_LIST[i] + ".java"));
+ }
+ for (int i = 0; i < CPP_LIST.length; i++) {
+ parseFile(cppTypes, javaDirectory, cppDirectory, CPP_LIST[i], ".cpp", new CppVisitor(cppTypes, symbolTable));
+ }
+ cppTypes.finished();
+ }
+
+ private static void copyFile(File input, File output) throws IOException {
+ if (input.getCanonicalFile().equals(output.getCanonicalFile())) {
+ return; // files are the same!
+ }
+ // This is horribly inefficient, but perf is not really much of a concern here.
+ FileInputStream in = new FileInputStream(input);
+ FileOutputStream out = new FileOutputStream(output);
+ int b;
+ while ((b = in.read()) != -1) {
+ out.write(b);
+ }
+ out.flush();
+ out.close();
+ in.close();
+ }
+
+ private static void parseFile(CppTypes cppTypes, File javaDirectory,
+ File cppDirectory, String className, String fne, CppVisitor visitor)
+ throws FileNotFoundException, UnsupportedEncodingException,
+ IOException {
+ File file = null;
+ try {
+ file = new File(javaDirectory, className + ".java");
+ String license = new LicenseExtractor(file).extract();
+ CompilationUnit cu = JavaParser.parse(new NoCppInputStream(
+ new CppOnlyInputStream(new FileInputStream(file))), "utf-8");
+ LabelVisitor labelVisitor = new LabelVisitor();
+ cu.accept(labelVisitor, null);
+ visitor.setLabels(labelVisitor.getLabels());
+ cu.accept(visitor, null);
+ FileOutputStream out = new FileOutputStream(new File(cppDirectory,
+ cppTypes.classPrefix() + className + fne));
+ OutputStreamWriter w = new OutputStreamWriter(out, "utf-8");
+ w.write(license);
+ w.write("\n\n/*\n * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.\n * Please edit "
+ + className + ".java instead and regenerate.\n */\n\n");
+ w.write(visitor.getSource());
+ w.close();
+ } catch (ParseException e) {
+ System.err.println(file);
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/NoCppInputStream.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/NoCppInputStream.java
new file mode 100644
index 000000000..86f9ae7ff
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/NoCppInputStream.java
@@ -0,0 +1,86 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class NoCppInputStream extends InputStream {
+
+ private final static char[] START = "[NOCPP[".toCharArray();
+
+ private final static char[] END = "]NOCPP]".toCharArray();
+
+ private int state;
+
+ private final InputStream delegate;
+
+
+
+ /**
+ * @param delegate
+ */
+ public NoCppInputStream(InputStream delegate) {
+ this.delegate = delegate;
+ this.state = 0;
+ }
+
+ @Override public int read() throws IOException {
+ int c;
+ if (state == START.length) {
+ int endState = 0;
+ while (endState != END.length) {
+ c = delegate.read();
+ if (END[endState] == c) {
+ endState++;
+ } else {
+ endState = 0;
+ }
+ }
+ state = 0;
+ }
+ c = delegate.read();
+ if (START[state] == c) {
+ state++;
+ } else {
+ state = 0;
+ }
+ return c;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/StringLiteralParser.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/StringLiteralParser.java
new file mode 100644
index 000000000..305f516a7
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/StringLiteralParser.java
@@ -0,0 +1,70 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class StringLiteralParser {
+
+ private static final Pattern STRING_DECL = Pattern.compile("^.*\\(([^ ]+) = new nsString\\(\\)\\)->Assign\\(NS_LITERAL_STRING\\(\"([^\"]*)\"\\)\\);.*$");
+
+ private final BufferedReader reader;
+
+ public StringLiteralParser(Reader reader) {
+ this.reader = new BufferedReader(reader);
+ }
+
+ public Map<String, String> parse() throws IOException {
+ Map<String, String> map = new HashMap<String, String>();
+ String line;
+ while((line = reader.readLine()) != null) {
+ Matcher m = STRING_DECL.matcher(line);
+ if (m.matches()) {
+ map.put(m.group(2), m.group(1));
+ }
+ }
+ return map;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/StringPair.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/StringPair.java
new file mode 100644
index 000000000..e24247f7e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/StringPair.java
@@ -0,0 +1,73 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+public class StringPair {
+
+ /**
+ * @param first
+ * @param second
+ */
+ public StringPair(String first, String second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ private final String first;
+
+ private final String second;
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override public boolean equals(Object o) {
+ if (o instanceof StringPair) {
+ StringPair other = (StringPair) o;
+ return first.equals(other.first) && second.equals(other.second);
+ }
+ return false;
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ @Override public int hashCode() {
+ return first.hashCode() ^ second.hashCode();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/SymbolTable.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/SymbolTable.java
new file mode 100644
index 000000000..3619c923a
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/SymbolTable.java
@@ -0,0 +1,93 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class SymbolTable {
+
+ private final Set<StringPair> primitiveConstants = new HashSet<StringPair>();
+
+ private final Map<StringPair, Type> fields = new HashMap<StringPair, Type>();
+
+ private final Map<StringPair, Type> methodReturns = new HashMap<StringPair, Type>();
+
+ /**
+ * This is a sad hack to work around the fact the there's no real symbol
+ * table yet.
+ *
+ * @param field
+ * @return
+ */
+ public boolean isAttributeOrElementName(String klazz, String field) {
+ if (isPrimitiveConstant(klazz, field)) {
+ return false;
+ }
+ return !("ATTRIBUTE_HASHES".equals(field)
+ || "ATTRIBUTE_NAMES".equals(field)
+ || "ELEMENT_HASHES".equals(field)
+ || "ELEMENT_NAMES".equals(field) || "ALL_NO_NS".equals(field));
+ }
+
+ public void addPrimitiveConstant(String klazz, String field) {
+ primitiveConstants.add(new StringPair(klazz, field));
+ }
+
+ public void putFieldType(String klazz, String field, Type type) {
+ fields.put(new StringPair(klazz, field), type);
+ }
+
+ public void putMethodReturnType(String klazz, String method, Type type) {
+ methodReturns.put(new StringPair(klazz, method), type);
+ }
+
+ public boolean isPrimitiveConstant(String klazz, String field) {
+ return primitiveConstants.contains(new StringPair(klazz, field));
+ }
+
+ public Type getFieldType(String klazz, String field) {
+ return fields.get(new StringPair(klazz, field));
+ }
+
+ public Type getMethodReturnType(String klazz, String method) {
+ return methodReturns.get(new StringPair(klazz, method));
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/SymbolTableVisitor.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/SymbolTableVisitor.java
new file mode 100644
index 000000000..00f7c5741
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/SymbolTableVisitor.java
@@ -0,0 +1,71 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.MethodDeclaration;
+
+public class SymbolTableVisitor extends AnnotationHelperVisitor<SymbolTable> {
+
+ private String javaClassName;
+
+ /**
+ * @see japa.parser.ast.visitor.VoidVisitorAdapter#visit(japa.parser.ast.body.FieldDeclaration, java.lang.Object)
+ */
+ @Override public void visit(FieldDeclaration n, SymbolTable arg) {
+ currentAnnotations = n.getAnnotations();
+ arg.putFieldType(javaClassName, n.getVariables().get(0).getId().getName(), convertType(n.getType(), n.getModifiers()));
+ }
+
+ /**
+ * @see japa.parser.ast.visitor.VoidVisitorAdapter#visit(japa.parser.ast.body.MethodDeclaration, java.lang.Object)
+ */
+ @Override public void visit(MethodDeclaration n, SymbolTable arg) {
+ currentAnnotations = n.getAnnotations();
+ arg.putMethodReturnType(javaClassName, n.getName(), convertType(n.getType(), n.getModifiers()));
+ }
+
+ /**
+ * @see japa.parser.ast.visitor.VoidVisitorAdapter#visit(japa.parser.ast.body.ClassOrInterfaceDeclaration, java.lang.Object)
+ */
+ @Override public void visit(ClassOrInterfaceDeclaration n, SymbolTable arg) {
+ javaClassName = n.getName();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/TranslatorUtils.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/TranslatorUtils.java
new file mode 100644
index 000000000..866db093d
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/TranslatorUtils.java
@@ -0,0 +1,81 @@
+package nu.validator.htmlparser.cpptranslate;
+
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BinaryExpr.Operator;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.Statement;
+
+import java.util.List;
+
+public class TranslatorUtils {
+ public static boolean isErrorOnlyBlock(Statement elseStmt, boolean supportErrorReporting) {
+ if (supportErrorReporting) {
+ return false;
+ }
+ if (elseStmt instanceof BlockStmt) {
+ BlockStmt block = (BlockStmt) elseStmt;
+ List<Statement> statements = block.getStmts();
+ if (statements == null) {
+ return false;
+ }
+ if (statements.size() != 1) {
+ return false;
+ }
+ Statement statement = statements.get(0);
+ if (statement instanceof ExpressionStmt) {
+ ExpressionStmt exprStmt = (ExpressionStmt) statement;
+ Expression expr = exprStmt.getExpression();
+ if (expr instanceof MethodCallExpr) {
+ MethodCallExpr call = (MethodCallExpr) expr;
+ if (call.getName().startsWith("err")) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public static boolean isErrorHandlerIf(Expression condition, boolean supportErrorReporting) {
+ if (supportErrorReporting) {
+ return false;
+ }
+ while (condition instanceof BinaryExpr) {
+ BinaryExpr binex = (BinaryExpr) condition;
+ condition = binex.getLeft();
+ if (condition instanceof NameExpr) {
+ NameExpr name = (NameExpr) condition;
+ if ("errorHandler".equals(name.getName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static boolean isDocumentModeHandlerNullCheck(Expression condition) {
+ if (condition instanceof BinaryExpr) {
+ BinaryExpr binex = (BinaryExpr) condition;
+ if (binex.getOperator() != Operator.notEquals) {
+ return false;
+ }
+ if (!(binex.getRight() instanceof NullLiteralExpr)) {
+ return false;
+ }
+ Expression left = binex.getLeft();
+ if (left instanceof NameExpr) {
+ NameExpr name = (NameExpr) left;
+ if ("documentModeHandler".equals(name.getName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/Type.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/Type.java
new file mode 100644
index 000000000..783a3bbd0
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/cpptranslate/Type.java
@@ -0,0 +1,99 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.cpptranslate;
+
+public class Type {
+
+ /**
+ * @param type
+ * @param arrayCount
+ * @param noLength
+ * @param modifiers
+ */
+ public Type(String type, int arrayCount, boolean noLength, int modifiers) {
+ this.type = type;
+ this.arrayCount = arrayCount;
+ this.noLength = noLength;
+ this.modifiers = modifiers;
+ }
+
+ private final String type;
+
+ private final int arrayCount;
+
+ private final boolean noLength;
+
+ private final int modifiers;
+
+ /**
+ * Returns the type.
+ *
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Returns the arrayCount.
+ *
+ * @return the arrayCount
+ */
+ public int getArrayCount() {
+ return arrayCount;
+ }
+
+ /**
+ * Returns the noLength.
+ *
+ * @return the noLength
+ */
+ public boolean isNoLength() {
+ return noLength;
+ }
+
+ /**
+ * Returns the modifiers.
+ *
+ * @return the modifiers
+ */
+ public int getModifiers() {
+ return modifiers;
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/ApplyHotSpotWorkaround.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/ApplyHotSpotWorkaround.java
new file mode 100644
index 000000000..eb580e70c
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/ApplyHotSpotWorkaround.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2010-2011 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.generator;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Applies a workaround that splits the <code>stateLoop</code> method in the
+ * tokenizer into two methods. This way, each method stays under 8000 bytes in
+ * size. By default, HotSpot doesn't compile methods that are over 8000 bytes in
+ * size, which is a performance problem.
+ *
+ * This program should have been written in Perl, but to avoid introducing new
+ * dependencies, it's written in Java. No attempt at efficiency has been made.
+ *
+ * Warning! This modifies Tokenizer.java in place!
+ *
+ * @version $Id$
+ * @author hsivonen
+ */
+public class ApplyHotSpotWorkaround {
+
+ private static final String BEGIN_WORKAROUND = "// BEGIN HOTSPOT WORKAROUND";
+
+ private static final String END_WORKAROUND = "// END HOTSPOT WORKAROUND";
+
+ public static void main(String[] args) throws Throwable {
+ String tokenizer = readFileIntoString(args[0]);
+ String workaround = readFileIntoString(args[1]);
+
+ int beginIndex = tokenizer.indexOf(BEGIN_WORKAROUND);
+ int endIndex = tokenizer.indexOf(END_WORKAROUND);
+ String tokenizerHead = tokenizer.substring(0, beginIndex);
+ String tokenizerMiddle = tokenizer.substring(beginIndex, endIndex);
+ String tokenizerTail = tokenizer.substring(endIndex);
+
+ beginIndex = workaround.indexOf(BEGIN_WORKAROUND);
+ endIndex = workaround.indexOf(END_WORKAROUND);
+ String workaroundHead = workaround.substring(0, beginIndex);
+ String workaroundMiddle = workaround.substring(beginIndex, endIndex);
+ String workaroundTail = workaround.substring(endIndex);
+
+ String newTokenizer = tokenizerHead + workaroundMiddle + tokenizerTail;
+ String newWorkaround = workaroundHead + tokenizerMiddle
+ + workaroundTail;
+
+ int insertionPoint = newTokenizer.indexOf("// HOTSPOT WORKAROUND INSERTION POINT");
+
+ tokenizerHead = newTokenizer.substring(0, insertionPoint);
+ tokenizerTail = newTokenizer.substring(insertionPoint);
+
+ newTokenizer = tokenizerHead + newWorkaround + tokenizerTail;
+
+ Pattern pat = Pattern.compile("state = transition\\(state, ([^,]*), reconsume, pos\\)");
+ Matcher m = pat.matcher(newTokenizer);
+ newTokenizer = m.replaceAll("state = $1");
+
+ Writer out = new OutputStreamWriter(new FileOutputStream(args[0]),
+ "utf-8");
+ out.write(newTokenizer);
+ out.flush();
+ out.close();
+ }
+
+ private static String readFileIntoString(String name) throws IOException {
+ Reader in = new InputStreamReader(new FileInputStream(name), "UTF-8");
+ StringBuilder builder = new StringBuilder();
+ int c;
+ while ((c = in.read()) != -1) {
+ builder.append((char) c);
+ }
+ in.close();
+ return builder.toString();
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/GenerateNamedCharacters.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/GenerateNamedCharacters.java
new file mode 100644
index 000000000..69ddb318e
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/GenerateNamedCharacters.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2008-2009 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package nu.validator.htmlparser.generator;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GenerateNamedCharacters {
+
+ private static final int LEAD_OFFSET = 0xD800 - (0x10000 >> 10);
+
+ private static final Pattern LINE_PATTERN = Pattern.compile("<td> <code title=\"\">([^<]*)</code> </td> <td> U\\+(\\S*) (?:U\\+(\\S*) )?</td>");
+
+ private static String toUString(int c) {
+ String hexString = Integer.toHexString(c);
+ switch (hexString.length()) {
+ case 1:
+ return "\\u000" + hexString;
+ case 2:
+ return "\\u00" + hexString;
+ case 3:
+ return "\\u0" + hexString;
+ case 4:
+ return "\\u" + hexString;
+ default:
+ throw new RuntimeException("Unreachable.");
+ }
+ }
+
+ private static int charToIndex(char c) {
+ if (c >= 'a' && c <= 'z') {
+ return c - 'a' + 26;
+ } else if (c >= 'A' && c <= 'Z') {
+ return c - 'A';
+ }
+ throw new IllegalArgumentException("Bad char in named character name: "
+ + c);
+ }
+
+ private static boolean allZero(int[] arr) {
+ for (int i = 0; i < arr.length; i++) {
+ if (arr[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @param args
+ * @throws IOException
+ */
+ public static void main(String[] args) throws IOException {
+ TreeMap<String, String> entities = new TreeMap<String, String>();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ System.in, "utf-8"));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Matcher m = LINE_PATTERN.matcher(line);
+ while (m.find()) {
+ String value;
+ if (m.group(3) != null) {
+ // two BMP chars
+ int firstIntVal = Integer.parseInt(m.group(2), 16);
+ int secondIntVal = Integer.parseInt(m.group(3), 16);
+ value = ("" + (char)firstIntVal) + (char)secondIntVal;
+ } else {
+ // one code point
+ int intVal = Integer.parseInt(m.group(2), 16);
+ if (intVal <= 0xFFFF) {
+ value = "" + (char)intVal;
+ } else {
+ int high = (LEAD_OFFSET + (intVal >> 10));
+ int low = (0xDC00 + (intVal & 0x3FF));
+ value = ("" + (char)high) + (char)low;
+ }
+ }
+ entities.put(m.group(1), value);
+ }
+ }
+
+ // Java initializes arrays to zero. Zero is our magic value for no hilo
+ // value.
+ int[][] hiLoTable = new int['z' + 1]['Z' - 'A' + 1 + 'z' - 'a' + 1];
+
+ String firstName = entities.entrySet().iterator().next().getKey();
+ int firstKey = charToIndex(firstName.charAt(0));
+ int secondKey = firstName.charAt(1);
+ int row = 0;
+ int lo = 0;
+
+ System.out.print("static final @NoLength @CharacterName String[] NAMES = {\n");
+ for (Map.Entry<String, String> entity : entities.entrySet()) {
+ String name = entity.getKey();
+ int newFirst = charToIndex(name.charAt(0));
+ int newSecond = name.charAt(1);
+ assert !(newFirst == 0 && newSecond == 0) : "Not prepared for name starting with AA";
+ if (firstKey != newFirst || secondKey != newSecond) {
+ hiLoTable[secondKey][firstKey] = ((row - 1) << 16) | lo;
+ lo = row;
+ firstKey = newFirst;
+ secondKey = newSecond;
+ }
+ System.out.print("\"");
+ System.out.print(name.substring(2));
+ System.out.print("\",\n");
+ row++;
+ }
+ System.out.print("};\n");
+
+ hiLoTable[secondKey][firstKey] = ((entities.size() - 1) << 16) | lo;
+
+ System.out.print("static final @NoLength char[][] VALUES = {\n");
+ for (Map.Entry<String, String> entity : entities.entrySet()) {
+ String value = entity.getValue();
+ System.out.print("{");
+ if (value.length() == 1) {
+ char c = value.charAt(0);
+ if (c == '\'') {
+ System.out.print("\'\\\'\'");
+ } else if (c == '\n') {
+ System.out.print("\'\\n\'");
+ } else if (c == '\\') {
+ System.out.print("\'\\\\\'");
+ } else if (c <= 0xFFFF) {
+ System.out.print("\'");
+ System.out.print(toUString(c));
+ System.out.print("\'");
+ }
+ } else {
+ System.out.print("\'");
+ System.out.print(toUString(value.charAt(0)));
+ System.out.print("\', \'");
+ System.out.print(toUString(value.charAt(1)));
+ System.out.print("\'");
+ }
+ System.out.print("},\n");
+ }
+ System.out.print("};\n");
+
+ System.out.print("static final @NoLength int[][] HILO_ACCEL = {\n");
+ for (int i = 0; i < hiLoTable.length; i++) {
+ if (allZero(hiLoTable[i])) {
+ System.out.print("null,\n");
+ } else {
+ System.out.print("{");
+ for (int j = 0; j < hiLoTable[i].length; j++) {
+ System.out.print(hiLoTable[i][j]);
+ System.out.print(", ");
+ }
+ System.out.print("},\n");
+ }
+ }
+ System.out.print("};\n");
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/GenerateNamedCharactersCpp.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/GenerateNamedCharactersCpp.java
new file mode 100644
index 000000000..2cfe7b112
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/generator/GenerateNamedCharactersCpp.java
@@ -0,0 +1,580 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser C++ Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.generator;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nu.validator.htmlparser.cpptranslate.CppTypes;
+
+public class GenerateNamedCharactersCpp {
+
+ /**
+ * The license for the output of this program except for data files.
+ */
+ private static final String OUTPUT_LICENSE = "/*\n"
+ + " * Copyright (c) 2008-2010 Mozilla Foundation\n"
+ + " *\n"
+ + " * Permission is hereby granted, free of charge, to any person obtaining a \n"
+ + " * copy of this software and associated documentation files (the \"Software\"), \n"
+ + " * to deal in the Software without restriction, including without limitation \n"
+ + " * the rights to use, copy, modify, merge, publish, distribute, sublicense, \n"
+ + " * and/or sell copies of the Software, and to permit persons to whom the \n"
+ + " * Software is furnished to do so, subject to the following conditions:\n"
+ + " *\n"
+ + " * The above copyright notice and this permission notice shall be included in \n"
+ + " * all copies or substantial portions of the Software.\n"
+ + " *\n"
+ + " * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \n"
+ + " * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \n"
+ + " * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL \n"
+ + " * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \n"
+ + " * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \n"
+ + " * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER \n"
+ + " * DEALINGS IN THE SOFTWARE.\n" + " */\n\n";
+
+ /**
+ * The license for the generated data files.
+ */
+ private static final String DATA_LICENSE = "/*\n"
+ + " * Copyright 2004-2010 Apple Computer, Inc., Mozilla Foundation, and Opera \n"
+ + " * Software ASA.\n"
+ + " * \n"
+ + " * You are granted a license to use, reproduce and create derivative works of \n"
+ + " * this document.\n" + " */\n\n";
+
+ private static final int LEAD_OFFSET = 0xD800 - (0x10000 >> 10);
+
+ private static final Pattern LINE_PATTERN = Pattern.compile("<td> <code title=\"\">([^<]*)</code> </td> <td> U\\+(\\S*) (?:U\\+(\\S*) )?</td>");
+
+ private static String toHexString(int c) {
+ String hexString = Integer.toHexString(c);
+ switch (hexString.length()) {
+ case 1:
+ return "0x000" + hexString;
+ case 2:
+ return "0x00" + hexString;
+ case 3:
+ return "0x0" + hexString;
+ case 4:
+ return "0x" + hexString;
+ default:
+ throw new RuntimeException("Unreachable.");
+ }
+ }
+
+ /**
+ * @param args
+ * @throws IOException
+ */
+ public static void main(String[] args) throws IOException {
+ TreeMap<String, String> entities = new TreeMap<String, String>();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ new FileInputStream(args[0]), "utf-8"));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Matcher m = LINE_PATTERN.matcher(line);
+ while (m.find()) {
+ String value;
+ if (m.group(3) != null) {
+ // two BMP chars
+ int firstIntVal = Integer.parseInt(m.group(2), 16);
+ int secondIntVal = Integer.parseInt(m.group(3), 16);
+ value = ("" + (char)firstIntVal) + (char)secondIntVal;
+ } else {
+ // one code point
+ int intVal = Integer.parseInt(m.group(2), 16);
+ if (intVal <= 0xFFFF) {
+ value = "" + (char)intVal;
+ } else {
+ int high = (LEAD_OFFSET + (intVal >> 10));
+ int low = (0xDC00 + (intVal & 0x3FF));
+ value = ("" + (char)high) + (char)low;
+ }
+ }
+ entities.put(m.group(1), value);
+ }
+ }
+
+ CppTypes cppTypes = new CppTypes(null);
+ File targetDirectory = new File(args[1]);
+
+ generateH(targetDirectory, cppTypes, entities);
+ generateInclude(targetDirectory, cppTypes, entities);
+ generateCpp(targetDirectory, cppTypes, entities);
+ generateAccelH(targetDirectory, cppTypes, entities);
+ generateAccelCpp(targetDirectory, cppTypes, entities);
+ }
+
+ private static void generateAccelCpp(File targetDirectory,
+ CppTypes cppTypes, TreeMap<String, String> entities) throws IOException {
+ String includeFile = cppTypes.classPrefix()
+ + "NamedCharactersInclude.h";
+ File cppFile = new File(targetDirectory, cppTypes.classPrefix()
+ + "NamedCharactersAccel.cpp");
+ Writer out = new OutputStreamWriter(new FileOutputStream(cppFile),
+ "utf-8");
+
+ out.write(DATA_LICENSE);
+ out.write('\n');
+ out.write("#include \"" + cppTypes.classPrefix()
+ + "NamedCharactersAccel.h\"\n");
+ out.write("\n");
+
+ // Java initializes arrays to zero. Zero is our magic value for no hilo
+ // value.
+ int[][] hiLoTable = new int['z' + 1]['Z' - 'A' + 1 + 'z' - 'a' + 1];
+
+ String firstName = entities.entrySet().iterator().next().getKey();
+ int firstKey = charToIndex(firstName.charAt(0));
+ int secondKey = firstName.charAt(1);
+ int row = 0;
+ int lo = 0;
+
+ for (Map.Entry<String, String> entity : entities.entrySet()) {
+ String name = entity.getKey();
+ int newFirst = charToIndex(name.charAt(0));
+ int newSecond = name.charAt(1);
+ assert !(newFirst == 0 && newSecond == 0) : "Not prepared for name starting with AA";
+ if (firstKey != newFirst || secondKey != newSecond) {
+ hiLoTable[secondKey][firstKey] = ((row - 1) << 16) | lo;
+ lo = row;
+ firstKey = newFirst;
+ secondKey = newSecond;
+ }
+ row++;
+ }
+
+ hiLoTable[secondKey][firstKey] = ((entities.size() - 1) << 16) | lo;
+
+ for (int i = 0; i < hiLoTable.length; i++) {
+ if (!allZero(hiLoTable[i])) {
+ out.write("static " + cppTypes.intType() + " const HILO_ACCEL_"
+ + i + "[] = {\n");
+ for (int j = 0; j < hiLoTable[i].length; j++) {
+ if (j != 0) {
+ out.write(", ");
+ }
+ out.write("" + hiLoTable[i][j]);
+ }
+ out.write("\n};\n\n");
+ }
+ }
+
+ out.write("const int32_t* const " + cppTypes.classPrefix()
+ + "NamedCharactersAccel::HILO_ACCEL[] = {\n");
+ for (int i = 0; i < hiLoTable.length; i++) {
+ if (i != 0) {
+ out.write(",\n");
+ }
+ if (allZero(hiLoTable[i])) {
+ out.write(" 0");
+ } else {
+ out.write(" HILO_ACCEL_" + i);
+ }
+ }
+ out.write("\n};\n\n");
+
+ out.flush();
+ out.close();
+ }
+
+ private static void generateAccelH(File targetDirectory, CppTypes cppTypes,
+ TreeMap<String, String> entities) throws IOException {
+ File hFile = new File(targetDirectory, cppTypes.classPrefix()
+ + "NamedCharactersAccel.h");
+ Writer out = new OutputStreamWriter(new FileOutputStream(hFile),
+ "utf-8");
+ out.write(DATA_LICENSE);
+ out.write("#ifndef " + cppTypes.classPrefix() + "NamedCharactersAccel_h\n");
+ out.write("#define " + cppTypes.classPrefix() + "NamedCharactersAccel_h\n");
+ out.write('\n');
+
+ String[] includes = cppTypes.namedCharactersIncludes();
+ for (int i = 0; i < includes.length; i++) {
+ String include = includes[i];
+ out.write("#include \"" + include + ".h\"\n");
+ }
+
+ out.write('\n');
+
+ out.write("class " + cppTypes.classPrefix() + "NamedCharactersAccel\n");
+ out.write("{\n");
+ out.write(" public:\n");
+ out.write(" static const " + cppTypes.intType()
+ + "* const HILO_ACCEL[];\n");
+ out.write("};\n");
+
+ out.write("\n#endif // " + cppTypes.classPrefix()
+ + "NamedCharactersAccel_h\n");
+ out.flush();
+ out.close();
+ }
+
+ private static void generateH(File targetDirectory, CppTypes cppTypes,
+ Map<String, String> entities) throws IOException {
+ File hFile = new File(targetDirectory, cppTypes.classPrefix()
+ + "NamedCharacters.h");
+ Writer out = new OutputStreamWriter(new FileOutputStream(hFile),
+ "utf-8");
+ out.write(OUTPUT_LICENSE);
+ out.write("#ifndef " + cppTypes.classPrefix() + "NamedCharacters_h\n");
+ out.write("#define " + cppTypes.classPrefix() + "NamedCharacters_h\n");
+ out.write('\n');
+
+ String[] includes = cppTypes.namedCharactersIncludes();
+ for (int i = 0; i < includes.length; i++) {
+ String include = includes[i];
+ out.write("#include \"" + include + ".h\"\n");
+ }
+
+ out.write("\nstruct ");
+ out.write(cppTypes.characterNameTypeDeclaration());
+ out.write(" {\n ");
+ out.write(cppTypes.unsignedShortType());
+ out.write(" nameStart;\n ");
+ out.write(cppTypes.unsignedShortType());
+ out.write(" nameLen;\n #ifdef DEBUG\n ");
+ out.write(cppTypes.intType());
+ out.write(" n;\n #endif\n ");
+ out.write(cppTypes.intType());
+ out.write(" length() const;\n ");
+ out.write(cppTypes.charType());
+ out.write(" charAt(");
+ out.write(cppTypes.intType());
+ out.write(" index) const;\n};\n\n");
+
+ out.write("class " + cppTypes.classPrefix() + "NamedCharacters\n");
+ out.write("{\n");
+ out.write(" public:\n");
+ out.write(" static const " + cppTypes.characterNameTypeDeclaration() + " NAMES[];\n");
+ out.write(" static const " + cppTypes.charType() + " VALUES[][2];\n");
+ out.write(" static " + cppTypes.charType() + "** WINDOWS_1252;\n");
+ out.write(" static void initializeStatics();\n");
+ out.write(" static void releaseStatics();\n");
+ out.write("};\n");
+
+ out.write("\n#endif // " + cppTypes.classPrefix()
+ + "NamedCharacters_h\n");
+ out.flush();
+ out.close();
+ }
+
+ private static void generateInclude(File targetDirectory,
+ CppTypes cppTypes, Map<String, String> entities) throws IOException {
+ File includeFile = new File(targetDirectory, cppTypes.classPrefix()
+ + "NamedCharactersInclude.h");
+ Writer out = new OutputStreamWriter(new FileOutputStream(includeFile),
+ "utf-8");
+
+ out.write(DATA_LICENSE);
+ out.write("/* Data generated from the table of named character references found at\n");
+ out.write(" *\n");
+ out.write(" * http://www.whatwg.org/specs/web-apps/current-work/multipage/named-character-references.html#named-character-references\n");
+ out.write(" *\n");
+ out.write(" * Files that #include this file must #define NAMED_CHARACTER_REFERENCE as a\n");
+ out.write(" * macro of four parameters:\n");
+ out.write(" *\n");
+ out.write(" * 1. a unique integer N identifying the Nth [0,1,..] macro expansion in this file,\n");
+ out.write(" * 2. a comma-separated sequence of characters comprising the character name,\n");
+ out.write(" * without the first two letters or 0 if the sequence would be empty. \n");
+ out.write(" * See Tokenizer.java.\n");
+ out.write(" * 3. the length of this sequence of characters,\n");
+ out.write(" * 4. placeholder flag (0 if argument #is not a placeholder and 1 if it is),\n");
+ out.write(" * 5. a comma-separated sequence of char16_t literals corresponding\n");
+ out.write(" * to the code-point(s) of the named character.\n");
+ out.write(" *\n");
+ out.write(" * The macro expansion doesn't have to refer to all or any of these parameters,\n");
+ out.write(" * but common sense dictates that it should involve at least one of them.\n");
+ out.write(" */\n");
+ out.write("\n");
+ out.write("// This #define allows the NAMED_CHARACTER_REFERENCE macro to accept comma-\n");
+ out.write("// separated sequences as single macro arguments. Using commas directly would\n");
+ out.write("// split the sequence into multiple macro arguments.\n");
+ out.write("#define _ ,\n");
+ out.write("\n");
+
+ int i = 0;
+ for (Map.Entry<String, String> entity : entities.entrySet()) {
+ out.write("NAMED_CHARACTER_REFERENCE(" + i++ + ", ");
+ String name = entity.getKey();
+ writeNameInitializer(out, name, " _ ");
+ out.write(", " + (name.length() - 2) + ", ");
+ out.write((name.length() == 2 ? "1" : "0") + ", ");
+ writeValueInitializer(out, entity.getValue(), " _ ");
+ out.write(")\n");
+ }
+
+ out.write("\n");
+ out.write("#undef _\n");
+
+ out.flush();
+ out.close();
+ }
+
+ private static void writeNameInitializer(Writer out,
+ String name, String separator)
+ throws IOException {
+ out.write("/* " + name.charAt(0) + " " + name.charAt(1) + " */ ");
+ if (name.length() == 2) {
+ out.write("0");
+ } else {
+ for (int i = 2; i < name.length(); i++) {
+ out.write("'" + name.charAt(i) + "'");
+ if (i < name.length() - 1)
+ out.write(separator);
+ }
+ }
+ }
+
+ private static void writeValueInitializer(Writer out,
+ String value, String separator)
+ throws IOException {
+ if (value.length() == 1) {
+ out.write(toHexString(value.charAt(0)));
+ out.write(separator);
+ out.write("0");
+ } else {
+ out.write(toHexString(value.charAt(0)));
+ out.write(separator);
+ out.write(toHexString(value.charAt(1)));
+ }
+ }
+
+ private static void defineMacroAndInclude(Writer out, String expansion,
+ String includeFile) throws IOException {
+ out.write("#define NAMED_CHARACTER_REFERENCE(N, CHARS, LEN, FLAG, VALUE) \\\n"
+ + expansion + "\n");
+ out.write("#include \"" + includeFile + "\"\n");
+ out.write("#undef NAMED_CHARACTER_REFERENCE\n");
+ }
+
+ private static void defineMacroAndInclude(Writer out, String expansion,
+ String debugExpansion, String includeFile) throws IOException {
+ out.write("#ifdef DEBUG\n");
+ out.write(" #define NAMED_CHARACTER_REFERENCE(N, CHARS, LEN, FLAG, VALUE) \\\n"
+ + debugExpansion + "\n");
+ out.write("#else\n");
+ out.write(" #define NAMED_CHARACTER_REFERENCE(N, CHARS, LEN, FLAG, VALUE) \\\n"
+ + expansion + "\n");
+ out.write("#endif\n");
+ out.write("#include \"" + includeFile + "\"\n");
+ out.write("#undef NAMED_CHARACTER_REFERENCE\n");
+ }
+
+ private static void writeStaticMemberDeclaration(Writer out,
+ CppTypes cppTypes, String type, String name) throws IOException {
+ out.write(type + " " + cppTypes.classPrefix() + "NamedCharacters::"
+ + name + ";\n");
+ }
+
+ private static int charToIndex(char c) {
+ if (c >= 'a' && c <= 'z') {
+ return c - 'a' + 26;
+ } else if (c >= 'A' && c <= 'Z') {
+ return c - 'A';
+ }
+ throw new IllegalArgumentException("Bad char in named character name: "
+ + c);
+ }
+
+ private static boolean allZero(int[] arr) {
+ for (int i = 0; i < arr.length; i++) {
+ if (arr[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static void generateCpp(File targetDirectory, CppTypes cppTypes,
+ Map<String, String> entities) throws IOException {
+ String includeFile = cppTypes.classPrefix()
+ + "NamedCharactersInclude.h";
+ File cppFile = new File(targetDirectory, cppTypes.classPrefix()
+ + "NamedCharacters.cpp");
+ Writer out = new OutputStreamWriter(new FileOutputStream(cppFile),
+ "utf-8");
+
+ out.write(OUTPUT_LICENSE);
+ out.write("#define " + cppTypes.classPrefix()
+ + "NamedCharacters_cpp_\n");
+
+ String[] includes = cppTypes.namedCharactersIncludes();
+ for (int i = 0; i < includes.length; i++) {
+ String include = includes[i];
+ out.write("#include \"" + include + ".h\"\n");
+ }
+
+ out.write('\n');
+ out.write("#include \"" + cppTypes.classPrefix()
+ + "NamedCharacters.h\"\n");
+ out.write("\n");
+
+ out.write("const " + cppTypes.charType() + " " + cppTypes.classPrefix()
+ + "NamedCharacters::VALUES[][2] = {\n");
+ defineMacroAndInclude(out, "{ VALUE },", includeFile);
+ // The useless terminator entry makes the above macro simpler with
+ // compilers that whine about a comma after the last item
+ out.write("{0, 0} };\n\n");
+
+ String staticMemberType = cppTypes.charType() + "**";
+ writeStaticMemberDeclaration(out, cppTypes, staticMemberType,
+ "WINDOWS_1252");
+
+ out.write("static " + cppTypes.charType()
+ + " const WINDOWS_1252_DATA[] = {\n");
+ out.write(" 0x20AC,\n");
+ out.write(" 0x0081,\n");
+ out.write(" 0x201A,\n");
+ out.write(" 0x0192,\n");
+ out.write(" 0x201E,\n");
+ out.write(" 0x2026,\n");
+ out.write(" 0x2020,\n");
+ out.write(" 0x2021,\n");
+ out.write(" 0x02C6,\n");
+ out.write(" 0x2030,\n");
+ out.write(" 0x0160,\n");
+ out.write(" 0x2039,\n");
+ out.write(" 0x0152,\n");
+ out.write(" 0x008D,\n");
+ out.write(" 0x017D,\n");
+ out.write(" 0x008F,\n");
+ out.write(" 0x0090,\n");
+ out.write(" 0x2018,\n");
+ out.write(" 0x2019,\n");
+ out.write(" 0x201C,\n");
+ out.write(" 0x201D,\n");
+ out.write(" 0x2022,\n");
+ out.write(" 0x2013,\n");
+ out.write(" 0x2014,\n");
+ out.write(" 0x02DC,\n");
+ out.write(" 0x2122,\n");
+ out.write(" 0x0161,\n");
+ out.write(" 0x203A,\n");
+ out.write(" 0x0153,\n");
+ out.write(" 0x009D,\n");
+ out.write(" 0x017E,\n");
+ out.write(" 0x0178\n");
+ out.write("};\n\n");
+
+ out.write("/**\n");
+ out.write(" * To avoid having lots of pointers in the |charData| array, below,\n");
+ out.write(" * which would cause us to have to do lots of relocations at library\n");
+ out.write(" * load time, store all the string data for the names in one big array.\n");
+ out.write(" * Then use tricks with enums to help us build an array that contains\n");
+ out.write(" * the positions of each within the big arrays.\n");
+ out.write(" */\n\n");
+
+ out.write("static const " + cppTypes.byteType() + " ALL_NAMES[] = {\n");
+
+ defineMacroAndInclude(out, "CHARS ,", includeFile);
+
+ out.write("};\n\n");
+
+ out.write("enum NamePositions {\n");
+ out.write(" DUMMY_INITIAL_NAME_POSITION = 0,\n");
+
+ out.write("/* enums don't take up space, so generate _START and _END */\n");
+ defineMacroAndInclude(out,
+ "NAME_##N##_DUMMY, /* automatically one higher than previous */ \\\n"
+ + "NAME_##N##_START = NAME_##N##_DUMMY - 1, \\\n"
+ + "NAME_##N##_END = NAME_##N##_START + LEN + FLAG,",
+ includeFile);
+
+ out.write(" DUMMY_FINAL_NAME_VALUE\n");
+ out.write("};\n\n");
+
+ String arrayLengthMacro = cppTypes.arrayLengthMacro();
+ String staticAssert = cppTypes.staticAssert();
+ if (staticAssert != null && arrayLengthMacro != null) {
+ out.write("/* check that the start positions will fit in 16 bits */\n");
+ out.write(staticAssert + "(" + arrayLengthMacro
+ + "(ALL_NAMES) < 0x10000);\n\n");
+ }
+
+ out.write("const " + cppTypes.characterNameTypeDeclaration() + " " + cppTypes.classPrefix()
+ + "NamedCharacters::NAMES[] = {\n");
+ defineMacroAndInclude(out, "{ NAME_##N##_START, LEN, },", "{ NAME_##N##_START, LEN, N },", includeFile);
+ out.write("};\n\n");
+
+ out.write(cppTypes.intType());
+ out.write("\n");
+ out.write(cppTypes.characterNameTypeDeclaration());
+ out.write("::length() const\n{\n return nameLen;\n}\n\n");
+ out.write(cppTypes.charType());
+ out.write("\n");
+ out.write(cppTypes.characterNameTypeDeclaration());
+ out.write("::charAt(");
+ out.write("int32_t");
+ out.write(" index) const\n{\n return static_cast<");
+ out.write(cppTypes.charType());
+ out.write("> (ALL_NAMES[nameStart + index]);\n}\n\n");
+
+ out.write("void\n");
+ out.write(cppTypes.classPrefix()
+ + "NamedCharacters::initializeStatics()\n");
+ out.write("{\n");
+ out.write(" WINDOWS_1252 = new " + cppTypes.charType() + "*[32];\n");
+ out.write(" for (" + cppTypes.intType() + " i = 0; i < 32; ++i) {\n");
+ out.write(" WINDOWS_1252[i] = (" + cppTypes.charType()
+ + "*)&(WINDOWS_1252_DATA[i]);\n");
+ out.write(" }\n");
+ out.write("}\n");
+ out.write("\n");
+
+ out.write("void\n");
+ out.write(cppTypes.classPrefix()
+ + "NamedCharacters::releaseStatics()\n");
+ out.write("{\n");
+ out.write(" delete[] WINDOWS_1252;\n");
+ out.write("}\n");
+ out.flush();
+ out.close();
+ }
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/DuplicatingFallThroughRemover.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/DuplicatingFallThroughRemover.java
new file mode 100644
index 000000000..b88107361
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/DuplicatingFallThroughRemover.java
@@ -0,0 +1,79 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser Rust Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.rusttranslate;
+
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.visitor.VoidVisitorAdapter;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class DuplicatingFallThroughRemover extends VoidVisitorAdapter<Object> {
+
+ private static final SwitchBreakAnalyzerVisitor ANALYZER_VISITOR = new SwitchBreakAnalyzerVisitor();
+
+ @Override public void visit(SwitchStmt sw, Object arg) {
+ if ("state".equals(sw.getSelector().toString())) {
+ super.visit(sw, arg);
+ return;
+ }
+
+ List<Statement> tail = new LinkedList<Statement>();
+ tail.add(new BreakStmt());
+
+ List<SwitchEntryStmt> entries = sw.getEntries();
+ for (int i = entries.size() - 1; i >= 0; i--) {
+ SwitchEntryStmt stmt = entries.get(i);
+ List<Statement> list = stmt.getStmts();
+ if (list != null) {
+ if (!(list.size() > 0
+ && list.get(list.size() - 1).accept(ANALYZER_VISITOR, true))) {
+ list.addAll(tail);
+ }
+ tail = list;
+ for (Statement statement : list) {
+ statement.accept(this, arg);
+ }
+ }
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/JavaVisitor.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/JavaVisitor.java
new file mode 100644
index 000000000..97ded525f
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/JavaVisitor.java
@@ -0,0 +1,1349 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package nu.validator.htmlparser.rusttranslate;
+
+import japa.parser.ast.BlockComment;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.ImportDeclaration;
+import japa.parser.ast.LineComment;
+import japa.parser.ast.PackageDeclaration;
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.body.AnnotationDeclaration;
+import japa.parser.ast.body.AnnotationMemberDeclaration;
+import japa.parser.ast.body.BodyDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.ConstructorDeclaration;
+import japa.parser.ast.body.EmptyMemberDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumConstantDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.InitializerDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.ModifierSet;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.TypeDeclaration;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.ArrayAccessExpr;
+import japa.parser.ast.expr.ArrayCreationExpr;
+import japa.parser.ast.expr.ArrayInitializerExpr;
+import japa.parser.ast.expr.AssignExpr;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BooleanLiteralExpr;
+import japa.parser.ast.expr.CastExpr;
+import japa.parser.ast.expr.CharLiteralExpr;
+import japa.parser.ast.expr.ClassExpr;
+import japa.parser.ast.expr.ConditionalExpr;
+import japa.parser.ast.expr.DoubleLiteralExpr;
+import japa.parser.ast.expr.EnclosedExpr;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.expr.FieldAccessExpr;
+import japa.parser.ast.expr.InstanceOfExpr;
+import japa.parser.ast.expr.IntegerLiteralExpr;
+import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
+import japa.parser.ast.expr.LongLiteralExpr;
+import japa.parser.ast.expr.LongLiteralMinValueExpr;
+import japa.parser.ast.expr.MarkerAnnotationExpr;
+import japa.parser.ast.expr.MemberValuePair;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NormalAnnotationExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.expr.ObjectCreationExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.SingleMemberAnnotationExpr;
+import japa.parser.ast.expr.StringLiteralExpr;
+import japa.parser.ast.expr.SuperExpr;
+import japa.parser.ast.expr.ThisExpr;
+import japa.parser.ast.expr.UnaryExpr;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.WildcardType;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ * @author Henri Sivonen
+ */
+
+public final class JavaVisitor implements VoidVisitor<Object> {
+
+ private static class SourcePrinter {
+
+ private int level = 0;
+
+ private boolean indented = false;
+
+ private final StringBuilder buf = new StringBuilder();
+
+ public void indent() {
+ level++;
+ }
+
+ public void unindent() {
+ level--;
+ }
+
+ private void makeIndent() {
+ for (int i = 0; i < level; i++) {
+ buf.append(" ");
+ }
+ }
+
+ public void print(String arg) {
+ if (!indented) {
+ makeIndent();
+ indented = true;
+ }
+ buf.append(arg);
+ }
+
+ public void printLn(String arg) {
+ print(arg);
+ printLn();
+ }
+
+ public void printLn() {
+ buf.append("\n");
+ indented = false;
+ }
+
+ public String getSource() {
+ return buf.toString();
+ }
+
+ @Override
+ public String toString() {
+ return getSource();
+ }
+ }
+
+ private final SourcePrinter printer = new SourcePrinter();
+
+ public String getSource() {
+ return printer.getSource();
+ }
+
+ private void printModifiers(int modifiers) {
+ if (ModifierSet.isPrivate(modifiers)) {
+ printer.print("private ");
+ }
+ if (ModifierSet.isProtected(modifiers)) {
+ printer.print("protected ");
+ }
+ if (ModifierSet.isPublic(modifiers)) {
+ printer.print("public ");
+ }
+ if (ModifierSet.isAbstract(modifiers)) {
+ printer.print("abstract ");
+ }
+ if (ModifierSet.isStatic(modifiers)) {
+ printer.print("static ");
+ }
+ if (ModifierSet.isFinal(modifiers)) {
+ printer.print("final ");
+ }
+ if (ModifierSet.isNative(modifiers)) {
+ printer.print("native ");
+ }
+ if (ModifierSet.isStrictfp(modifiers)) {
+ printer.print("strictfp ");
+ }
+ if (ModifierSet.isSynchronized(modifiers)) {
+ printer.print("synchronized ");
+ }
+ if (ModifierSet.isTransient(modifiers)) {
+ printer.print("transient ");
+ }
+ if (ModifierSet.isVolatile(modifiers)) {
+ printer.print("volatile ");
+ }
+ }
+
+ private void printMembers(List<BodyDeclaration> members, Object arg) {
+ for (BodyDeclaration member : members) {
+ printer.printLn();
+ member.accept(this, arg);
+ printer.printLn();
+ }
+ }
+
+ private void printMemberAnnotations(List<AnnotationExpr> annotations, Object arg) {
+ if (annotations != null) {
+ for (AnnotationExpr a : annotations) {
+ a.accept(this, arg);
+ printer.printLn();
+ }
+ }
+ }
+
+ private void printAnnotations(List<AnnotationExpr> annotations, Object arg) {
+ if (annotations != null) {
+ for (AnnotationExpr a : annotations) {
+ a.accept(this, arg);
+ printer.print(" ");
+ }
+ }
+ }
+
+ private void printTypeArgs(List<Type> args, Object arg) {
+ if (args != null) {
+ printer.print("<");
+ for (Iterator<Type> i = args.iterator(); i.hasNext();) {
+ Type t = i.next();
+ t.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ printer.print(">");
+ }
+ }
+
+ private void printTypeParameters(List<TypeParameter> args, Object arg) {
+ if (args != null) {
+ printer.print("<");
+ for (Iterator<TypeParameter> i = args.iterator(); i.hasNext();) {
+ TypeParameter t = i.next();
+ t.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ printer.print(">");
+ }
+ }
+
+ private void printArguments(List<Expression> args, Object arg) {
+ printer.print("(");
+ if (args != null) {
+ for (Iterator<Expression> i = args.iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+ }
+
+ private void printJavadoc(JavadocComment javadoc, Object arg) {
+ if (javadoc != null) {
+ javadoc.accept(this, arg);
+ }
+ }
+
+ public void visit(CompilationUnit n, Object arg) {
+ if (n.getPackage() != null) {
+ n.getPackage().accept(this, arg);
+ }
+ if (n.getImports() != null) {
+ for (ImportDeclaration i : n.getImports()) {
+ i.accept(this, arg);
+ }
+ printer.printLn();
+ }
+ if (n.getTypes() != null) {
+ for (Iterator<TypeDeclaration> i = n.getTypes().iterator(); i.hasNext();) {
+ i.next().accept(this, arg);
+ printer.printLn();
+ if (i.hasNext()) {
+ printer.printLn();
+ }
+ }
+ }
+ }
+
+ public void visit(PackageDeclaration n, Object arg) {
+ printAnnotations(n.getAnnotations(), arg);
+ printer.print("package ");
+ n.getName().accept(this, arg);
+ printer.printLn(";");
+ printer.printLn();
+ }
+
+ public void visit(NameExpr n, Object arg) {
+ printer.print(n.getName());
+ }
+
+ public void visit(QualifiedNameExpr n, Object arg) {
+ n.getQualifier().accept(this, arg);
+ printer.print(".");
+ printer.print(n.getName());
+ }
+
+ public void visit(ImportDeclaration n, Object arg) {
+ printer.print("import ");
+ if (n.isStatic()) {
+ printer.print("static ");
+ }
+ n.getName().accept(this, arg);
+ if (n.isAsterisk()) {
+ printer.print(".*");
+ }
+ printer.printLn(";");
+ }
+
+ public void visit(ClassOrInterfaceDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ if (n.isInterface()) {
+ printer.print("interface ");
+ } else {
+ printer.print("class ");
+ }
+
+ printer.print(n.getName());
+
+ printTypeParameters(n.getTypeParameters(), arg);
+
+ if (n.getExtends() != null) {
+ printer.print(" extends ");
+ for (Iterator<ClassOrInterfaceType> i = n.getExtends().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+
+ if (n.getImplements() != null) {
+ printer.print(" implements ");
+ for (Iterator<ClassOrInterfaceType> i = n.getImplements().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getMembers() != null) {
+ printMembers(n.getMembers(), arg);
+ }
+ printer.unindent();
+ printer.print("}");
+ }
+
+ public void visit(EmptyTypeDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printer.print(";");
+ }
+
+ public void visit(JavadocComment n, Object arg) {
+ printer.print("/**");
+ printer.print(n.getContent());
+ printer.printLn("*/");
+ }
+
+ public void visit(ClassOrInterfaceType n, Object arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print(n.getName());
+ printTypeArgs(n.getTypeArgs(), arg);
+ }
+
+ public void visit(TypeParameter n, Object arg) {
+ printer.print(n.getName());
+ if (n.getTypeBound() != null) {
+ printer.print(" extends ");
+ for (Iterator<ClassOrInterfaceType> i = n.getTypeBound().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(" & ");
+ }
+ }
+ }
+ }
+
+ public void visit(PrimitiveType n, Object arg) {
+ switch (n.getType()) {
+ case Boolean:
+ printer.print("boolean");
+ break;
+ case Byte:
+ printer.print("byte");
+ break;
+ case Char:
+ printer.print("char");
+ break;
+ case Double:
+ printer.print("double");
+ break;
+ case Float:
+ printer.print("float");
+ break;
+ case Int:
+ printer.print("int");
+ break;
+ case Long:
+ printer.print("long");
+ break;
+ case Short:
+ printer.print("short");
+ break;
+ }
+ }
+
+ public void visit(ReferenceType n, Object arg) {
+ n.getType().accept(this, arg);
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ }
+
+ public void visit(WildcardType n, Object arg) {
+ printer.print("?");
+ if (n.getExtends() != null) {
+ printer.print(" extends ");
+ n.getExtends().accept(this, arg);
+ }
+ if (n.getSuper() != null) {
+ printer.print(" super ");
+ n.getSuper().accept(this, arg);
+ }
+ }
+
+ public void visit(FieldDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+ n.getType().accept(this, arg);
+
+ printer.print(" ");
+ for (Iterator<VariableDeclarator> i = n.getVariables().iterator(); i.hasNext();) {
+ VariableDeclarator var = i.next();
+ var.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+
+ printer.print(";");
+ }
+
+ public void visit(VariableDeclarator n, Object arg) {
+ n.getId().accept(this, arg);
+ if (n.getInit() != null) {
+ printer.print(" = ");
+ n.getInit().accept(this, arg);
+ }
+ }
+
+ public void visit(VariableDeclaratorId n, Object arg) {
+ printer.print(n.getName());
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ }
+
+ public void visit(ArrayInitializerExpr n, Object arg) {
+ printer.print("{");
+ if (n.getValues() != null) {
+ printer.print(" ");
+ for (Iterator<Expression> i = n.getValues().iterator(); i.hasNext();) {
+ Expression expr = i.next();
+ expr.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ printer.print(" ");
+ }
+ printer.print("}");
+ }
+
+ public void visit(VoidType n, Object arg) {
+ printer.print("void");
+ }
+
+ public void visit(ArrayAccessExpr n, Object arg) {
+ n.getName().accept(this, arg);
+ printer.print("[");
+ n.getIndex().accept(this, arg);
+ printer.print("]");
+ }
+
+ public void visit(ArrayCreationExpr n, Object arg) {
+ printer.print("new ");
+ n.getType().accept(this, arg);
+
+ if (n.getDimensions() != null) {
+ for (Expression dim : n.getDimensions()) {
+ printer.print("[");
+ dim.accept(this, arg);
+ printer.print("]");
+ }
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ } else {
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ printer.print(" ");
+ n.getInitializer().accept(this, arg);
+ }
+ }
+
+ public void visit(AssignExpr n, Object arg) {
+ n.getTarget().accept(this, arg);
+ printer.print(" ");
+ switch (n.getOperator()) {
+ case assign:
+ printer.print("=");
+ break;
+ case and:
+ printer.print("&=");
+ break;
+ case or:
+ printer.print("|=");
+ break;
+ case xor:
+ printer.print("^=");
+ break;
+ case plus:
+ printer.print("+=");
+ break;
+ case minus:
+ printer.print("-=");
+ break;
+ case rem:
+ printer.print("%=");
+ break;
+ case slash:
+ printer.print("/=");
+ break;
+ case star:
+ printer.print("*=");
+ break;
+ case lShift:
+ printer.print("<<=");
+ break;
+ case rSignedShift:
+ printer.print(">>=");
+ break;
+ case rUnsignedShift:
+ printer.print(">>>=");
+ break;
+ }
+ printer.print(" ");
+ n.getValue().accept(this, arg);
+ }
+
+ public void visit(BinaryExpr n, Object arg) {
+ n.getLeft().accept(this, arg);
+ printer.print(" ");
+ switch (n.getOperator()) {
+ case or:
+ printer.print("||");
+ break;
+ case and:
+ printer.print("&&");
+ break;
+ case binOr:
+ printer.print("|");
+ break;
+ case binAnd:
+ printer.print("&");
+ break;
+ case xor:
+ printer.print("^");
+ break;
+ case equals:
+ printer.print("==");
+ break;
+ case notEquals:
+ printer.print("!=");
+ break;
+ case less:
+ printer.print("<");
+ break;
+ case greater:
+ printer.print(">");
+ break;
+ case lessEquals:
+ printer.print("<=");
+ break;
+ case greaterEquals:
+ printer.print(">=");
+ break;
+ case lShift:
+ printer.print("<<");
+ break;
+ case rSignedShift:
+ printer.print(">>");
+ break;
+ case rUnsignedShift:
+ printer.print(">>>");
+ break;
+ case plus:
+ printer.print("+");
+ break;
+ case minus:
+ printer.print("-");
+ break;
+ case times:
+ printer.print("*");
+ break;
+ case divide:
+ printer.print("/");
+ break;
+ case remainder:
+ printer.print("%");
+ break;
+ }
+ printer.print(" ");
+ n.getRight().accept(this, arg);
+ }
+
+ public void visit(CastExpr n, Object arg) {
+ printer.print("(");
+ n.getType().accept(this, arg);
+ printer.print(") ");
+ n.getExpr().accept(this, arg);
+ }
+
+ public void visit(ClassExpr n, Object arg) {
+ n.getType().accept(this, arg);
+ printer.print(".class");
+ }
+
+ public void visit(ConditionalExpr n, Object arg) {
+ n.getCondition().accept(this, arg);
+ printer.print(" ? ");
+ n.getThenExpr().accept(this, arg);
+ printer.print(" : ");
+ n.getElseExpr().accept(this, arg);
+ }
+
+ public void visit(EnclosedExpr n, Object arg) {
+ printer.print("(");
+ n.getInner().accept(this, arg);
+ printer.print(")");
+ }
+
+ public void visit(FieldAccessExpr n, Object arg) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ printer.print(n.getField());
+ }
+
+ public void visit(InstanceOfExpr n, Object arg) {
+ n.getExpr().accept(this, arg);
+ printer.print(" instanceof ");
+ n.getType().accept(this, arg);
+ }
+
+ public void visit(CharLiteralExpr n, Object arg) {
+ printer.print("'");
+ char c = n.getValue().charAt(0);
+ switch (c) {
+ case '\b':
+ printer.print("\\b");
+ break;
+ case '\t':
+ printer.print("\\t");
+ break;
+ case '\n':
+ printer.print("\\n");
+ break;
+ case '\f':
+ printer.print("\\f");
+ break;
+ case '\r':
+ printer.print("\\r");
+ break;
+ case '\'':
+ printer.print("\\'");
+ break;
+ case '\\':
+ printer.print(n.getValue());
+ break;
+ default:
+ if (c < ' ' || c > '~') {
+ String hex = Integer.toHexString(c);
+ switch (hex.length()) {
+ case 1:
+ printer.print("\\u000"+hex);
+ break;
+ case 2:
+ printer.print("\\u00"+hex);
+ break;
+ case 3:
+ printer.print("\\u0"+hex);
+ break;
+ case 4:
+ printer.print("\\u"+hex);
+ break;
+ }
+ } else {
+ printer.print(""+c);
+ }
+ break;
+ }
+ printer.print("'");
+ }
+
+ public void visit(DoubleLiteralExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(IntegerLiteralExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(LongLiteralExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(IntegerLiteralMinValueExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(LongLiteralMinValueExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(StringLiteralExpr n, Object arg) {
+ printer.print("\"");
+ printer.print(n.getValue());
+ printer.print("\"");
+ }
+
+ public void visit(BooleanLiteralExpr n, Object arg) {
+ printer.print(String.valueOf(n.getValue()));
+ }
+
+ public void visit(NullLiteralExpr n, Object arg) {
+ printer.print("null");
+ }
+
+ public void visit(ThisExpr n, Object arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print("this");
+ }
+
+ public void visit(SuperExpr n, Object arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print("super");
+ }
+
+ public void visit(MethodCallExpr n, Object arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ }
+ printTypeArgs(n.getTypeArgs(), arg);
+ printer.print(n.getName());
+ printArguments(n.getArgs(), arg);
+ }
+
+ public void visit(ObjectCreationExpr n, Object arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ }
+
+ printer.print("new ");
+
+ printTypeArgs(n.getTypeArgs(), arg);
+ n.getType().accept(this, arg);
+
+ printArguments(n.getArgs(), arg);
+
+ if (n.getAnonymousClassBody() != null) {
+ printer.printLn(" {");
+ printer.indent();
+ printMembers(n.getAnonymousClassBody(), arg);
+ printer.unindent();
+ printer.print("}");
+ }
+ }
+
+ public void visit(UnaryExpr n, Object arg) {
+ switch (n.getOperator()) {
+ case positive:
+ printer.print("+");
+ break;
+ case negative:
+ printer.print("-");
+ break;
+ case inverse:
+ printer.print("~");
+ break;
+ case not:
+ printer.print("!");
+ break;
+ case preIncrement:
+ printer.print("++");
+ break;
+ case preDecrement:
+ printer.print("--");
+ break;
+ }
+
+ n.getExpr().accept(this, arg);
+
+ switch (n.getOperator()) {
+ case posIncrement:
+ printer.print("++");
+ break;
+ case posDecrement:
+ printer.print("--");
+ break;
+ }
+ }
+
+ public void visit(ConstructorDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ printTypeParameters(n.getTypeParameters(), arg);
+ if (n.getTypeParameters() != null) {
+ printer.print(" ");
+ }
+ printer.print(n.getName());
+
+ printer.print("(");
+ if (n.getParameters() != null) {
+ for (Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext();) {
+ Parameter p = i.next();
+ p.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+
+ if (n.getThrows() != null) {
+ printer.print(" throws ");
+ for (Iterator<NameExpr> i = n.getThrows().iterator(); i.hasNext();) {
+ NameExpr name = i.next();
+ name.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(" ");
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(MethodDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ printTypeParameters(n.getTypeParameters(), arg);
+ if (n.getTypeParameters() != null) {
+ printer.print(" ");
+ }
+
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ printer.print(n.getName());
+
+ printer.print("(");
+ if (n.getParameters() != null) {
+ for (Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext();) {
+ Parameter p = i.next();
+ p.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+
+ if (n.getThrows() != null) {
+ printer.print(" throws ");
+ for (Iterator<NameExpr> i = n.getThrows().iterator(); i.hasNext();) {
+ NameExpr name = i.next();
+ name.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ if (n.getBody() == null) {
+ printer.print(";");
+ } else {
+ printer.print(" ");
+ n.getBody().accept(this, arg);
+ }
+ }
+
+ public void visit(Parameter n, Object arg) {
+ printAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ n.getType().accept(this, arg);
+ if (n.isVarArgs()) {
+ printer.print("...");
+ }
+ printer.print(" ");
+ n.getId().accept(this, arg);
+ }
+
+ public void visit(ExplicitConstructorInvocationStmt n, Object arg) {
+ if (n.isThis()) {
+ printTypeArgs(n.getTypeArgs(), arg);
+ printer.print("this");
+ } else {
+ if (n.getExpr() != null) {
+ n.getExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printTypeArgs(n.getTypeArgs(), arg);
+ printer.print("super");
+ }
+ printArguments(n.getArgs(), arg);
+ printer.print(";");
+ }
+
+ public void visit(VariableDeclarationExpr n, Object arg) {
+ printAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ n.getType().accept(this, arg);
+ printer.print(" ");
+
+ for (Iterator<VariableDeclarator> i = n.getVars().iterator(); i.hasNext();) {
+ VariableDeclarator v = i.next();
+ v.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+
+ public void visit(TypeDeclarationStmt n, Object arg) {
+ n.getTypeDeclaration().accept(this, arg);
+ }
+
+ public void visit(AssertStmt n, Object arg) {
+ printer.print("assert ");
+ n.getCheck().accept(this, arg);
+ if (n.getMessage() != null) {
+ printer.print(" : ");
+ n.getMessage().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(BlockStmt n, Object arg) {
+ printer.printLn("{");
+ if (n.getStmts() != null) {
+ printer.indent();
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ printer.printLn();
+ }
+ printer.unindent();
+ }
+ printer.print("}");
+
+ }
+
+ public void visit(LabeledStmt n, Object arg) {
+ printer.print(n.getLabel());
+ printer.print(": ");
+ n.getStmt().accept(this, arg);
+ }
+
+ public void visit(EmptyStmt n, Object arg) {
+ printer.print(";");
+ }
+
+ public void visit(ExpressionStmt n, Object arg) {
+ n.getExpression().accept(this, arg);
+ printer.print(";");
+ }
+
+ public void visit(SwitchStmt n, Object arg) {
+ printer.print("switch(");
+ n.getSelector().accept(this, arg);
+ printer.printLn(") {");
+ if (n.getEntries() != null) {
+ printer.indent();
+ for (SwitchEntryStmt e : n.getEntries()) {
+ e.accept(this, arg);
+ }
+ printer.unindent();
+ }
+ printer.print("}");
+
+ }
+
+ public void visit(SwitchEntryStmt n, Object arg) {
+ if (n.getLabel() != null) {
+ printer.print("case ");
+ n.getLabel().accept(this, arg);
+ printer.print(":");
+ } else {
+ printer.print("default:");
+ }
+ printer.printLn();
+ printer.indent();
+ if (n.getStmts() != null) {
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ printer.printLn();
+ }
+ }
+ printer.unindent();
+ }
+
+ public void visit(BreakStmt n, Object arg) {
+ printer.print("break");
+ if (n.getId() != null) {
+ printer.print(" ");
+ printer.print(n.getId());
+ }
+ printer.print(";");
+ }
+
+ public void visit(ReturnStmt n, Object arg) {
+ printer.print("return");
+ if (n.getExpr() != null) {
+ printer.print(" ");
+ n.getExpr().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(EnumDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ printer.print("enum ");
+ printer.print(n.getName());
+
+ if (n.getImplements() != null) {
+ printer.print(" implements ");
+ for (Iterator<ClassOrInterfaceType> i = n.getImplements().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getEntries() != null) {
+ printer.printLn();
+ for (Iterator<EnumConstantDeclaration> i = n.getEntries().iterator(); i.hasNext();) {
+ EnumConstantDeclaration e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ if (n.getMembers() != null) {
+ printer.printLn(";");
+ printMembers(n.getMembers(), arg);
+ } else {
+ if (n.getEntries() != null) {
+ printer.printLn();
+ }
+ }
+ printer.unindent();
+ printer.print("}");
+ }
+
+ public void visit(EnumConstantDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printer.print(n.getName());
+
+ if (n.getArgs() != null) {
+ printArguments(n.getArgs(), arg);
+ }
+
+ if (n.getClassBody() != null) {
+ printer.printLn(" {");
+ printer.indent();
+ printMembers(n.getClassBody(), arg);
+ printer.unindent();
+ printer.printLn("}");
+ }
+ }
+
+ public void visit(EmptyMemberDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printer.print(";");
+ }
+
+ public void visit(InitializerDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ if (n.isStatic()) {
+ printer.print("static ");
+ }
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(IfStmt n, Object arg) {
+ printer.print("if (");
+ n.getCondition().accept(this, arg);
+ printer.print(") ");
+ n.getThenStmt().accept(this, arg);
+ if (n.getElseStmt() != null) {
+ printer.print(" else ");
+ n.getElseStmt().accept(this, arg);
+ }
+ }
+
+ public void visit(WhileStmt n, Object arg) {
+ printer.print("while (");
+ n.getCondition().accept(this, arg);
+ printer.print(") ");
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ContinueStmt n, Object arg) {
+ printer.print("continue");
+ if (n.getId() != null) {
+ printer.print(" ");
+ printer.print(n.getId());
+ }
+ printer.print(";");
+ }
+
+ public void visit(DoStmt n, Object arg) {
+ printer.print("do ");
+ n.getBody().accept(this, arg);
+ printer.print(" while (");
+ n.getCondition().accept(this, arg);
+ printer.print(");");
+ }
+
+ public void visit(ForeachStmt n, Object arg) {
+ printer.print("for (");
+ n.getVariable().accept(this, arg);
+ printer.print(" : ");
+ n.getIterable().accept(this, arg);
+ printer.print(") ");
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ForStmt n, Object arg) {
+ printer.print("for (");
+ if (n.getInit() != null) {
+ for (Iterator<Expression> i = n.getInit().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print("; ");
+ if (n.getCompare() != null) {
+ n.getCompare().accept(this, arg);
+ }
+ printer.print("; ");
+ if (n.getUpdate() != null) {
+ for (Iterator<Expression> i = n.getUpdate().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(") ");
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ThrowStmt n, Object arg) {
+ printer.print("throw ");
+ n.getExpr().accept(this, arg);
+ printer.print(";");
+ }
+
+ public void visit(SynchronizedStmt n, Object arg) {
+ printer.print("synchronized (");
+ n.getExpr().accept(this, arg);
+ printer.print(") ");
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(TryStmt n, Object arg) {
+ printer.print("try ");
+ n.getTryBlock().accept(this, arg);
+ if (n.getCatchs() != null) {
+ for (CatchClause c : n.getCatchs()) {
+ c.accept(this, arg);
+ }
+ }
+ if (n.getFinallyBlock() != null) {
+ printer.print(" finally ");
+ n.getFinallyBlock().accept(this, arg);
+ }
+ }
+
+ public void visit(CatchClause n, Object arg) {
+ printer.print(" catch (");
+ n.getExcept().accept(this, arg);
+ printer.print(") ");
+ n.getCatchBlock().accept(this, arg);
+
+ }
+
+ public void visit(AnnotationDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ printer.print("@interface ");
+ printer.print(n.getName());
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getMembers() != null) {
+ printMembers(n.getMembers(), arg);
+ }
+ printer.unindent();
+ printer.print("}");
+ }
+
+ public void visit(AnnotationMemberDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ printer.print(n.getName());
+ printer.print("()");
+ if (n.getDefaultValue() != null) {
+ printer.print(" default ");
+ n.getDefaultValue().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(MarkerAnnotationExpr n, Object arg) {
+ printer.print("@");
+ n.getName().accept(this, arg);
+ }
+
+ public void visit(SingleMemberAnnotationExpr n, Object arg) {
+ printer.print("@");
+ n.getName().accept(this, arg);
+ printer.print("(");
+ n.getMemberValue().accept(this, arg);
+ printer.print(")");
+ }
+
+ public void visit(NormalAnnotationExpr n, Object arg) {
+ printer.print("@");
+ n.getName().accept(this, arg);
+ printer.print("(");
+ if (n.getPairs() != null) {
+ for (Iterator<MemberValuePair> i = n.getPairs().iterator(); i.hasNext();) {
+ MemberValuePair m = i.next();
+ m.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+ }
+
+ public void visit(MemberValuePair n, Object arg) {
+ printer.print(n.getName());
+ printer.print(" = ");
+ n.getValue().accept(this, arg);
+ }
+
+ public void visit(LineComment n, Object arg) {
+ printer.print("//");
+ printer.printLn(n.getContent());
+ }
+
+ public void visit(BlockComment n, Object arg) {
+ printer.print("/*");
+ printer.print(n.getContent());
+ printer.printLn("*/");
+ }
+
+} \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/LoopBreakAnalyzerVisitor.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/LoopBreakAnalyzerVisitor.java
new file mode 100644
index 000000000..384716e0b
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/LoopBreakAnalyzerVisitor.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 09/06/2008
+ */
+package nu.validator.htmlparser.rusttranslate;
+
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.WildcardType;
+import japa.parser.ast.visitor.GenericVisitorAdapter;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ * @author Henri Sivonen
+ */
+public class LoopBreakAnalyzerVisitor extends GenericVisitorAdapter<Boolean, Boolean> {
+
+ public Boolean visit(AssertStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(BlockStmt n, Boolean arg) {
+ for (Statement stmt : n.getStmts()) {
+ if (stmt.accept(this, arg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Boolean visit(BreakStmt n, Boolean arg) {
+ return n.getId() != null;
+ }
+
+ public Boolean visit(CatchClause n, Boolean arg) {
+ return n.getCatchBlock().accept(this, arg);
+ }
+
+ public Boolean visit(ContinueStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(DoStmt n, Boolean arg) {
+ return n.getBody().accept(this, arg);
+ }
+
+ public Boolean visit(EmptyStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(ExplicitConstructorInvocationStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(ExpressionStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(ForeachStmt n, Boolean arg) {
+ return n.getBody().accept(this, arg);
+ }
+
+ public Boolean visit(ForStmt n, Boolean arg) {
+ //bogus
+ return false;
+ }
+
+ public Boolean visit(IfStmt n, Boolean arg) {
+ if (n.getElseStmt() != null) {
+ if (n.getElseStmt().accept(this, arg)) {
+ return true;
+ }
+ }
+ if (n.getThenStmt().accept(this, arg)) {
+ return true;
+ }
+ return false;
+ }
+
+ public Boolean visit(LabeledStmt n, Boolean arg) {
+ return n.getStmt().accept(this, arg);
+ }
+
+ public Boolean visit(ReturnStmt n, Boolean arg) {
+ return true;
+ }
+
+ public Boolean visit(SwitchEntryStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(SwitchStmt n, Boolean arg) {
+ /*
+ List<SwitchEntryStmt> entries = n.getEntries();
+ for (int i = 0; i < array.length; i++) {
+ array_type array_element = array[i];
+
+ }
+ */
+ return true;
+ }
+
+ public Boolean visit(SynchronizedStmt n, Boolean arg) {
+ return n.getBlock().accept(this, arg);
+ }
+
+ public Boolean visit(ThrowStmt n, Boolean arg) {
+ return true;
+ }
+
+ public Boolean visit(TryStmt n, Boolean arg) {
+ if (n.getFinallyBlock() != null) {
+ return n.getFinallyBlock().accept(this, arg);
+ }
+ if (n.getCatchs() != null) {
+ for (CatchClause c : n.getCatchs()) {
+ boolean brk = c.accept(this, arg);
+ if (!brk) {
+ return false;
+ }
+ }
+ }
+ return n.getTryBlock().accept(this, arg);
+ }
+
+ public Boolean visit(TypeDeclarationStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(WhileStmt n, Boolean arg) {
+ return n.getBody().accept(this, arg);
+ }
+
+ public Boolean visit(WildcardType n, Boolean arg) {
+ if (n.getExtends() != null) {
+ n.getExtends().accept(this, arg);
+ }
+ if (n.getSuper() != null) {
+ n.getSuper().accept(this, arg);
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/Main.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/Main.java
new file mode 100644
index 000000000..4e1b0a7dd
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/Main.java
@@ -0,0 +1,144 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is HTML Parser Rust Translator code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Henri Sivonen <hsivonen@iki.fi>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package nu.validator.htmlparser.rusttranslate;
+
+import japa.parser.JavaParser;
+import japa.parser.ParseException;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.visitor.DumpVisitor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+import nu.validator.htmlparser.cpptranslate.CppOnlyInputStream;
+import nu.validator.htmlparser.cpptranslate.LicenseExtractor;
+import nu.validator.htmlparser.cpptranslate.NoCppInputStream;
+
+public class Main {
+
+ private static final String[] CLASSLIST = {
+ "Tokenizer",
+ "TreeBuilder",
+ "MetaScanner",
+ "AttributeName",
+ "ElementName",
+ "HtmlAttributes",
+ "StackNode",
+ "UTF16Buffer",
+ "StateSnapshot",
+ };
+
+ /**
+ * @param args
+ * @throws ParseException
+ * @throws IOException
+ */
+ public static void main(String[] args) throws ParseException, IOException {
+ File javaDirectory = new File(args[0]);
+ File targetDirectory = new File(args[1]);
+
+ for (int i = 0; i < CLASSLIST.length; i++) {
+ parseFile(javaDirectory, targetDirectory, CLASSLIST[i], ".java");
+ }
+ }
+
+ private static void parseFile(File javaDirectory,
+ File targetDirectory, String className, String fne)
+ throws FileNotFoundException, UnsupportedEncodingException,
+ IOException {
+ File file = null;
+// try {
+// file = new File(javaDirectory, className + ".java");
+// String license = new LicenseExtractor(file).extract();
+// CompilationUnit cu = JavaParser.parse(new FileInputStream(file), "utf-8");
+//
+// ModeFallThroughRemover mftr = new ModeFallThroughRemover();
+// cu.accept(mftr, null);
+//
+// DuplicatingFallThroughRemover dftr = new DuplicatingFallThroughRemover();
+// cu.accept(dftr, null);
+//
+// JavaVisitor visitor = new JavaVisitor();
+// cu.accept(visitor, null);
+// FileOutputStream out = new FileOutputStream(new File(targetDirectory,
+// className + fne));
+// OutputStreamWriter w = new OutputStreamWriter(out, "utf-8");
+// w.write(license);
+// w.write("\n\n/*\n * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.\n * Please edit "
+// + className + ".java instead and regenerate.\n */\n\n");
+// w.write(visitor.getSource());
+// w.close();
+// } catch (ParseException e) {
+// System.err.println(file);
+// e.printStackTrace();
+// }
+ try {
+ file = new File(javaDirectory, className + ".java");
+ String license = new LicenseExtractor(file).extract();
+ CompilationUnit cu = JavaParser.parse(new NoCppInputStream(
+ new CppOnlyInputStream(new FileInputStream(file))), "utf-8");
+
+ ModeFallThroughRemover mftr = new ModeFallThroughRemover();
+ cu.accept(mftr, null);
+
+ DuplicatingFallThroughRemover dftr = new DuplicatingFallThroughRemover();
+ cu.accept(dftr, null);
+
+ RustVisitor visitor = new RustVisitor();
+ cu.accept(visitor, null);
+ FileOutputStream out = new FileOutputStream(new File(targetDirectory,
+ className + ".rs"));
+ OutputStreamWriter w = new OutputStreamWriter(out, "utf-8");
+ w.write(license);
+ w.write("\n\n/*\n * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.\n * Please edit "
+ + className + ".java instead and regenerate.\n */\n\n");
+ w.write(visitor.getSource());
+ w.close();
+ } catch (ParseException e) {
+ System.err.println(file);
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/ModeFallThroughRemover.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/ModeFallThroughRemover.java
new file mode 100644
index 000000000..a89926748
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/ModeFallThroughRemover.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 09/06/2008
+ */
+package nu.validator.htmlparser.rusttranslate;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BinaryExpr.Operator;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.visitor.VoidVisitorAdapter;
+
+/**
+ * @author Julio Vilmar Gesser
+ * @author Henri Sivonen
+ */
+public class ModeFallThroughRemover extends VoidVisitorAdapter<Object> {
+
+ private String method;
+
+ public void visit(BlockStmt n, Object arg) {
+ if (!("startTag".equals(method) || "endTag".equals(method))) {
+ super.visit(n, arg);
+ return;
+ }
+ List<Statement> list = n.getStmts();
+ if (list != null) {
+ for (int i = 0; i < list.size(); i++) {
+ Statement s = list.get(i);
+ if (s instanceof SwitchStmt) {
+ SwitchStmt sw = (SwitchStmt) s;
+ if ("mode".equals(sw.getSelector().toString())) {
+ list.remove(i);
+ int j = 0;
+ for (SwitchEntryStmt entry : sw.getEntries()) {
+ List<Statement> statements = entry.getStmts();
+ if (statements == null) {
+ continue;
+ }
+ Statement last = statements.get(statements.size() - 1);
+ if (last instanceof BreakStmt) {
+ BreakStmt brk = (BreakStmt) last;
+ if (brk.getId() == null) {
+ statements.remove(last);
+ }
+ }
+ Statement stm;
+ Expression label = entry.getLabel();
+ if (label == null) {
+ stm = new BlockStmt(statements);
+ } else {
+ Expression lte = new BinaryExpr(
+ sw.getSelector(), label,
+ Operator.lessEquals);
+ stm = new IfStmt(lte,
+ new BlockStmt(statements), null);
+ }
+ list.add(i + j, stm);
+ j++;
+ }
+ } else {
+ s.accept(this, arg);
+ }
+ } else {
+ s.accept(this, arg);
+ }
+ }
+ }
+ }
+
+ /**
+ * @see japa.parser.ast.visitor.VoidVisitorAdapter#visit(japa.parser.ast.body.MethodDeclaration, java.lang.Object)
+ */
+ @Override public void visit(MethodDeclaration md, Object arg) {
+ method = md.getName();
+ super.visit(md, arg);
+ }
+
+} \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/RustVisitor.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/RustVisitor.java
new file mode 100644
index 000000000..36feced04
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/RustVisitor.java
@@ -0,0 +1,1586 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package nu.validator.htmlparser.rusttranslate;
+
+import japa.parser.ast.BlockComment;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.LineComment;
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.body.BodyDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.ConstructorDeclaration;
+import japa.parser.ast.body.EmptyMemberDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumConstantDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.InitializerDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.ModifierSet;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.TypeDeclaration;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.ArrayAccessExpr;
+import japa.parser.ast.expr.ArrayCreationExpr;
+import japa.parser.ast.expr.ArrayInitializerExpr;
+import japa.parser.ast.expr.AssignExpr;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BooleanLiteralExpr;
+import japa.parser.ast.expr.CastExpr;
+import japa.parser.ast.expr.CharLiteralExpr;
+import japa.parser.ast.expr.ClassExpr;
+import japa.parser.ast.expr.ConditionalExpr;
+import japa.parser.ast.expr.DoubleLiteralExpr;
+import japa.parser.ast.expr.EnclosedExpr;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.expr.FieldAccessExpr;
+import japa.parser.ast.expr.InstanceOfExpr;
+import japa.parser.ast.expr.IntegerLiteralExpr;
+import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
+import japa.parser.ast.expr.LongLiteralExpr;
+import japa.parser.ast.expr.LongLiteralMinValueExpr;
+import japa.parser.ast.expr.MemberValuePair;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.expr.ObjectCreationExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.StringLiteralExpr;
+import japa.parser.ast.expr.SuperExpr;
+import japa.parser.ast.expr.ThisExpr;
+import japa.parser.ast.expr.UnaryExpr;
+import japa.parser.ast.expr.UnaryExpr.Operator;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.WildcardType;
+import japa.parser.ast.visitor.VoidVisitorAdapter;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import nu.validator.htmlparser.cpptranslate.TranslatorUtils;
+
+/**
+ * @author Julio Vilmar Gesser
+ * @author Henri Sivonen
+ */
+
+public final class RustVisitor extends VoidVisitorAdapter<Object> {
+
+ private static final String[] MODS = {
+ "Tokenizer",
+ "TreeBuilder",
+ "MetaScanner",
+ "AttributeName",
+ "ElementName",
+ "HtmlAttributes",
+ "StackNode",
+ "UTF16Buffer",
+ "StateSnapshot",
+ };
+
+ private boolean inMethodSignature = false;
+
+ private Set<String> fields = new HashSet<String>();
+
+ private Set<String> constants = new HashSet<String>();
+
+ private Expression loopUpdate = null;
+
+ private static class SourcePrinter {
+
+ private int level = 0;
+
+ private boolean indented = false;
+
+ private final StringBuilder buf = new StringBuilder();
+
+ public void indent() {
+ level++;
+ }
+
+ public void unindent() {
+ level--;
+ }
+
+ private void makeIndent() {
+ for (int i = 0; i < level; i++) {
+ buf.append(" ");
+ }
+ }
+
+ public void print(String arg) {
+ if (!indented) {
+ makeIndent();
+ indented = true;
+ }
+ buf.append(arg);
+ }
+
+ public void printLn(String arg) {
+ print(arg);
+ printLn();
+ }
+
+ public void printLn() {
+ buf.append("\n");
+ indented = false;
+ }
+
+ public String getSource() {
+ return buf.toString();
+ }
+
+ @Override
+ public String toString() {
+ return getSource();
+ }
+ }
+
+ private final SourcePrinter printer = new SourcePrinter();
+
+ public String getSource() {
+ return printer.getSource();
+ }
+
+ private void printModifiers(int modifiers) {
+ if (ModifierSet.isPrivate(modifiers)) {
+ printer.print("private ");
+ }
+ if (ModifierSet.isProtected(modifiers)) {
+ printer.print("protected ");
+ }
+ if (ModifierSet.isPublic(modifiers)) {
+ printer.print("public ");
+ }
+ if (ModifierSet.isAbstract(modifiers)) {
+ printer.print("abstract ");
+ }
+ if (ModifierSet.isStatic(modifiers)) {
+ printer.print("static ");
+ }
+ if (ModifierSet.isFinal(modifiers)) {
+ printer.print("final ");
+ }
+ if (ModifierSet.isNative(modifiers)) {
+ printer.print("native ");
+ }
+ if (ModifierSet.isStrictfp(modifiers)) {
+ printer.print("strictfp ");
+ }
+ if (ModifierSet.isSynchronized(modifiers)) {
+ printer.print("synchronized ");
+ }
+ if (ModifierSet.isTransient(modifiers)) {
+ printer.print("transient ");
+ }
+ if (ModifierSet.isVolatile(modifiers)) {
+ printer.print("volatile ");
+ }
+ }
+
+ private void printMethods(List<BodyDeclaration> members, Object arg) {
+ for (BodyDeclaration member : members) {
+ if (member instanceof MethodDeclaration) {
+ MethodDeclaration meth = (MethodDeclaration) member;
+ if (meth.getName().startsWith("fatal") || meth.getName().startsWith("err")
+ || meth.getName().startsWith("warn")
+ || meth.getName().startsWith("maybeErr")
+ || meth.getName().startsWith("maybeWarn")
+ || meth.getName().startsWith("note")
+ || "releaseArray".equals(meth.getName())
+ || "deleteArray".equals(meth.getName())
+ || "delete".equals(meth.getName())) {
+ continue;
+ }
+ printer.printLn();
+ member.accept(this, arg);
+ printer.printLn();
+ }
+ }
+ }
+
+ private void printFields(List<BodyDeclaration> members, Object arg) {
+ for (BodyDeclaration member : members) {
+ if (member instanceof FieldDeclaration) {
+ FieldDeclaration field = (FieldDeclaration) member;
+ int mods = field.getModifiers();
+ if (ModifierSet.isStatic(mods) && ModifierSet.isFinal(mods)) {
+ continue;
+ }
+ fields.add(field.getVariables().get(0).getId().getName());
+ printer.printLn();
+ member.accept(this, arg);
+ printer.printLn();
+ }
+ }
+ }
+
+ private void printConstants(List<BodyDeclaration> members, Object arg) {
+ for (BodyDeclaration member : members) {
+ if (member instanceof FieldDeclaration) {
+ FieldDeclaration field = (FieldDeclaration) member;
+ int mods = field.getModifiers();
+ if (!(ModifierSet.isStatic(mods) && ModifierSet.isFinal(mods))) {
+ continue;
+ }
+ constants.add(field.getVariables().get(0).getId().getName());
+ printer.printLn();
+ member.accept(this, arg);
+ printer.printLn();
+ }
+ }
+ }
+
+ private void printMemberAnnotations(List<AnnotationExpr> annotations, Object arg) {
+ if (annotations != null) {
+ for (AnnotationExpr a : annotations) {
+ a.accept(this, arg);
+ printer.printLn();
+ }
+ }
+ }
+
+ private void printArguments(List<Expression> args, Object arg) {
+ printer.print("(");
+ if (args != null) {
+ for (Iterator<Expression> i = args.iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+ }
+
+ private void printJavadoc(JavadocComment javadoc, Object arg) {
+ if (javadoc != null) {
+ javadoc.accept(this, arg);
+ }
+ }
+
+ public void visit(CompilationUnit n, Object arg) {
+ if (n.getTypes() != null) {
+ for (Iterator<TypeDeclaration> i = n.getTypes().iterator(); i.hasNext();) {
+ i.next().accept(this, arg);
+ printer.printLn();
+ if (i.hasNext()) {
+ printer.printLn();
+ }
+ }
+ }
+ }
+
+ public void visit(NameExpr n, Object arg) {
+ if (fields.contains(n.getName())) {
+ printer.print("self.");
+ }
+ printer.print(n.getName());
+ }
+
+ public void visit(QualifiedNameExpr n, Object arg) {
+ n.getQualifier().accept(this, arg);
+ printer.print(".");
+ printer.print(n.getName());
+ }
+
+ public void visit(ClassOrInterfaceDeclaration n, Object arg) {
+ for (int i = 0; i < MODS.length; i++) {
+ String mod = MODS[i];
+ if (!mod.equals(n.getName())) {
+ printer.print("mod ");
+ printer.print(mod);
+ printer.printLn(";");
+ }
+ }
+
+ printJavadoc(n.getJavaDoc(), arg);
+
+
+ if (n.getMembers() != null) {
+ printConstants(n.getMembers(), arg);
+ }
+ printer.printLn();
+ printer.printLn();
+
+ printer.print("struct ");
+
+ printer.print(n.getName());
+
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getMembers() != null) {
+ printFields(n.getMembers(), arg);
+ }
+ printer.unindent();
+ printer.print("}");
+
+ printer.printLn();
+ printer.printLn();
+
+ printer.print("impl ");
+
+ printer.print(n.getName());
+
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getMembers() != null) {
+ printMethods(n.getMembers(), arg);
+ }
+ printer.unindent();
+ printer.print("}");
+
+ }
+
+ public void visit(EmptyTypeDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printer.print(";");
+ }
+
+ public void visit(JavadocComment n, Object arg) {
+ printer.print("/**");
+ printer.print(n.getContent());
+ printer.printLn("*/");
+ }
+
+ public void visit(ClassOrInterfaceType n, Object arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print(n.getName());
+ }
+
+ public void visit(TypeParameter n, Object arg) {
+ printer.print(n.getName());
+ if (n.getTypeBound() != null) {
+ printer.print(" extends ");
+ for (Iterator<ClassOrInterfaceType> i = n.getTypeBound().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(" & ");
+ }
+ }
+ }
+ }
+
+ public void visit(PrimitiveType n, Object arg) {
+ switch (n.getType()) {
+ case Boolean:
+ printer.print("bool");
+ break;
+ case Byte:
+ printer.print("i8");
+ break;
+ case Char:
+ printer.print("u16");
+ break;
+ case Double:
+ printer.print("f64");
+ break;
+ case Float:
+ printer.print("f32");
+ break;
+ case Int:
+ printer.print("i32");
+ break;
+ case Long:
+ printer.print("i64");
+ break;
+ case Short:
+ printer.print("i16");
+ break;
+ }
+ }
+
+ public void visit(ReferenceType n, Object arg) {
+// if (inMethodSignature) {
+// printer.print("&");
+// } else {
+// printer.print("~");
+// }
+ printer.print("@");
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[");
+ }
+ n.getType().accept(this, arg);
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("]");
+ }
+ }
+
+ public void visit(WildcardType n, Object arg) {
+ printer.print("?");
+ if (n.getExtends() != null) {
+ printer.print(" extends ");
+ n.getExtends().accept(this, arg);
+ }
+ if (n.getSuper() != null) {
+ printer.print(" super ");
+ n.getSuper().accept(this, arg);
+ }
+ }
+
+ public void visit(FieldDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+// printMemberAnnotations(n.getAnnotations(), arg);
+
+ boolean field = true;
+ int mods = n.getModifiers();
+ if (ModifierSet.isStatic(mods) && ModifierSet.isFinal(mods)) {
+ if (!ModifierSet.isPrivate(mods)) {
+ printer.print("pub ");
+ }
+ printer.print("const ");
+ field = false;
+ } else if (!ModifierSet.isFinal(mods)) {
+ printer.print("mut ");
+ }
+
+ List<VariableDeclarator> vars = n.getVariables();
+
+ printVariableDeclarator(n.getType(), vars, arg, field);
+
+ printer.print(field ? "," : ";");
+ }
+
+ private void printVariableDeclarator(Type type, List<VariableDeclarator> vars,
+ Object arg, boolean field) {
+ if (vars.size() != 1) {
+ throw new RuntimeException();
+ }
+
+ VariableDeclarator decl = vars.get(0);
+
+ VariableDeclaratorId id = decl.getId();
+
+ printer.print(id.getName());
+
+ printer.print(": ");
+
+ for (int i = 0; i < id.getArrayCount(); i++) {
+ printer.print("[");
+ }
+
+ type.accept(this, arg);
+
+ for (int i = 0; i < id.getArrayCount(); i++) {
+ printer.print("]");
+ }
+
+ Expression init = decl.getInit();
+
+ if (init != null && !field) {
+ printer.print(" = ");
+ init.accept(this, arg);
+ }
+ }
+
+ public void visit(ArrayInitializerExpr n, Object arg) {
+ printer.print("[");
+ if (n.getValues() != null) {
+ printer.print(" ");
+ for (Iterator<Expression> i = n.getValues().iterator(); i.hasNext();) {
+ Expression expr = i.next();
+ expr.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ printer.print(" ");
+ }
+ printer.print("]");
+ }
+
+ public void visit(VoidType n, Object arg) {
+ printer.print("void");
+ }
+
+ public void visit(ArrayAccessExpr n, Object arg) {
+ n.getName().accept(this, arg);
+ printer.print("[");
+ n.getIndex().accept(this, arg);
+ printer.print("]");
+ }
+
+ public void visit(ArrayCreationExpr n, Object arg) {
+ printer.print("new ");
+ n.getType().accept(this, arg);
+
+ if (n.getDimensions() != null) {
+ for (Expression dim : n.getDimensions()) {
+ printer.print("[");
+ dim.accept(this, arg);
+ printer.print("]");
+ }
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ } else {
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ printer.print(" ");
+ n.getInitializer().accept(this, arg);
+ }
+ }
+
+ public void visit(AssignExpr n, Object arg) {
+ n.getTarget().accept(this, arg);
+ printer.print(" ");
+ switch (n.getOperator()) {
+ case assign:
+ printer.print("=");
+ break;
+ case and:
+ printer.print("&=");
+ break;
+ case or:
+ printer.print("|=");
+ break;
+ case xor:
+ printer.print("^=");
+ break;
+ case plus:
+ printer.print("+=");
+ break;
+ case minus:
+ printer.print("-=");
+ break;
+ case rem:
+ printer.print("%=");
+ break;
+ case slash:
+ printer.print("/=");
+ break;
+ case star:
+ printer.print("*=");
+ break;
+ case lShift:
+ printer.print("<<=");
+ break;
+ case rSignedShift:
+ printer.print(">>=");
+ break;
+ case rUnsignedShift:
+ printer.print(">>>=");
+ break;
+ }
+ printer.print(" ");
+ n.getValue().accept(this, arg);
+ }
+
+ public void visit(BinaryExpr n, Object arg) {
+ n.getLeft().accept(this, arg);
+ printer.print(" ");
+ switch (n.getOperator()) {
+ case or:
+ printer.print("||");
+ break;
+ case and:
+ printer.print("&&");
+ break;
+ case binOr:
+ printer.print("|");
+ break;
+ case binAnd:
+ printer.print("&");
+ break;
+ case xor:
+ printer.print("^");
+ break;
+ case equals:
+ printer.print("==");
+ break;
+ case notEquals:
+ printer.print("!=");
+ break;
+ case less:
+ printer.print("<");
+ break;
+ case greater:
+ printer.print(">");
+ break;
+ case lessEquals:
+ printer.print("<=");
+ break;
+ case greaterEquals:
+ printer.print(">=");
+ break;
+ case lShift:
+ printer.print("<<");
+ break;
+ case rSignedShift:
+ printer.print(">>");
+ break;
+ case rUnsignedShift:
+ printer.print(">>>");
+ break;
+ case plus:
+ printer.print("+");
+ break;
+ case minus:
+ printer.print("-");
+ break;
+ case times:
+ printer.print("*");
+ break;
+ case divide:
+ printer.print("/");
+ break;
+ case remainder:
+ printer.print("%");
+ break;
+ }
+ printer.print(" ");
+ n.getRight().accept(this, arg);
+ }
+
+ public void visit(CastExpr n, Object arg) {
+ printer.print("(");
+ n.getType().accept(this, arg);
+ printer.print(") ");
+ n.getExpr().accept(this, arg);
+ }
+
+ public void visit(ClassExpr n, Object arg) {
+ n.getType().accept(this, arg);
+ printer.print(".class");
+ }
+
+ public void visit(ConditionalExpr n, Object arg) {
+ n.getCondition().accept(this, arg);
+ printer.print(" ? ");
+ n.getThenExpr().accept(this, arg);
+ printer.print(" : ");
+ n.getElseExpr().accept(this, arg);
+ }
+
+ public void visit(EnclosedExpr n, Object arg) {
+ printer.print("(");
+ n.getInner().accept(this, arg);
+ printer.print(")");
+ }
+
+ public void visit(FieldAccessExpr n, Object arg) {
+ String scope = n.getScope().toString();
+ printer.print(scope);
+ boolean mod = false;
+ for (int i = 0; i < MODS.length; i++) {
+ if (MODS[i].equals(scope)) {
+ mod = true;
+ break;
+ }
+ }
+ printer.print(mod ? "::" : ".");
+ if ("length".equals(n.getField())) {
+ printer.print("len() as i32");
+ } else {
+ printer.print(n.getField());
+ }
+ }
+
+ public void visit(InstanceOfExpr n, Object arg) {
+ n.getExpr().accept(this, arg);
+ printer.print(" instanceof ");
+ n.getType().accept(this, arg);
+ }
+
+ public void visit(CharLiteralExpr n, Object arg) {
+// printer.print("'");
+// char c = n.getValue().charAt(0);
+// switch (c) {
+// case '\b':
+// printer.print("\\b");
+// break;
+// case '\t':
+// printer.print("\\t");
+// break;
+// case '\n':
+// printer.print("\\n");
+// break;
+// case '\f':
+// printer.print("\\f");
+// break;
+// case '\r':
+// printer.print("\\r");
+// break;
+// case '\'':
+// printer.print("\\'");
+// break;
+// case '\\':
+// printer.print(n.getValue());
+// break;
+// default:
+// if (c < ' ' || c > '~') {
+// String hex = Integer.toHexString(c);
+// switch (hex.length()) {
+// case 1:
+// printer.print("\\u000"+hex);
+// break;
+// case 2:
+// printer.print("\\u00"+hex);
+// break;
+// case 3:
+// printer.print("\\u0"+hex);
+// break;
+// case 4:
+// printer.print("\\u"+hex);
+// break;
+// }
+// } else {
+// printer.print(""+c);
+// }
+// break;
+// }
+// printer.print("'");
+ String str = n.getValue();
+ if (str.length() == 1) {
+ String hex = Integer.toHexString(str.charAt(0));
+ switch (hex.length()) {
+ case 1:
+ printer.print("0x0"+hex);
+ break;
+ case 2:
+ printer.print("0x"+hex);
+ break;
+ case 3:
+ printer.print("0x0"+hex);
+ break;
+ case 4:
+ printer.print("0x"+hex);
+ break;
+ }
+ } else if ("\\n".equals(str)) {
+ printer.print("0x0A");
+ } else if ("\\r".equals(str)) {
+ printer.print("0x0D");
+ } else if ("\\t".equals(str)) {
+ printer.print("0x09");
+ } else if ("\\\"".equals(str)) {
+ printer.print("0x22");
+ } else if ("\\'".equals(str)) {
+ printer.print("0x27");
+ } else {
+ throw new RuntimeException(str);
+ }
+ }
+
+ public void visit(DoubleLiteralExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(IntegerLiteralExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(LongLiteralExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(IntegerLiteralMinValueExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(LongLiteralMinValueExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(StringLiteralExpr n, Object arg) {
+ printer.print("\"");
+ printer.print(n.getValue());
+ printer.print("\"");
+ }
+
+ public void visit(BooleanLiteralExpr n, Object arg) {
+ printer.print(String.valueOf(n.getValue()));
+ }
+
+ public void visit(NullLiteralExpr n, Object arg) {
+ printer.print("null");
+ }
+
+ public void visit(ThisExpr n, Object arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print("self");
+ }
+
+ public void visit(SuperExpr n, Object arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print("super");
+ }
+
+ public void visit(MethodCallExpr n, Object arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print(n.getName());
+ printArguments(n.getArgs(), arg);
+ }
+
+ public void visit(ObjectCreationExpr n, Object arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ }
+
+ printer.print("new ");
+
+ n.getType().accept(this, arg);
+
+ printArguments(n.getArgs(), arg);
+
+ if (n.getAnonymousClassBody() != null) {
+ printer.printLn(" {");
+ printer.indent();
+ printMethods(n.getAnonymousClassBody(), arg);
+ printer.unindent();
+ printer.print("}");
+ }
+ }
+
+ public void visit(UnaryExpr n, Object arg) {
+ Operator op = n.getOperator();
+ if (op == null) {
+ n.getExpr().accept(this, arg);
+ return;
+ }
+ switch (op) {
+ case positive:
+ printer.print("+");
+ n.getExpr().accept(this, arg);
+ break;
+ case negative:
+ printer.print("-");
+ n.getExpr().accept(this, arg);
+ break;
+ case inverse:
+ printer.print("i32::compl(");
+ n.getExpr().accept(this, arg);
+ printer.print(")");
+ break;
+ case not:
+ printer.print("!");
+ n.getExpr().accept(this, arg);
+ break;
+ case preIncrement:
+ case posIncrement:
+ n.getExpr().accept(this, arg);
+ printer.print(" = ");
+ n.getExpr().accept(this, arg);
+ printer.print(" + 1");
+ break;
+ case preDecrement:
+ case posDecrement:
+ n.getExpr().accept(this, arg);
+ printer.print(" = ");
+ n.getExpr().accept(this, arg);
+ printer.print(" - 1");
+ break;
+ }
+ }
+
+ public void visit(ConstructorDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ if (n.getTypeParameters() != null) {
+ printer.print(" ");
+ }
+ printer.print(n.getName());
+
+ printer.print("(");
+ if (n.getParameters() != null) {
+ for (Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext();) {
+ Parameter p = i.next();
+ p.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+
+ if (n.getThrows() != null) {
+ printer.print(" throws ");
+ for (Iterator<NameExpr> i = n.getThrows().iterator(); i.hasNext();) {
+ NameExpr name = i.next();
+ name.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(" ");
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(MethodDeclaration n, Object arg) {
+
+ printJavadoc(n.getJavaDoc(), arg);
+// printMemberAnnotations(n.getAnnotations(), arg);
+// printModifiers(n.getModifiers());
+
+// printTypeParameters(n.getTypeParameters(), arg);
+// if (n.getTypeParameters() != null) {
+// printer.print(" ");
+// }
+
+ printer.print("fn ");
+ printer.print(n.getName());
+
+ printer.print("(");
+ inMethodSignature = true;
+ if (n.getParameters() != null) {
+ for (Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext();) {
+ Parameter p = i.next();
+ p.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ inMethodSignature = false;
+ printer.print(")");
+
+ Type type = n.getType();
+
+ if (!(type instanceof VoidType)) {
+ printer.print(" -> ");
+ type.accept(this, arg);
+ }
+
+// for (int i = 0; i < n.getArrayCount(); i++) {
+// printer.print("[]");
+// }
+
+// if (n.getThrows() != null) {
+// printer.print(" throws ");
+// for (Iterator<NameExpr> i = n.getThrows().iterator(); i.hasNext();) {
+// NameExpr name = i.next();
+// name.accept(this, arg);
+// if (i.hasNext()) {
+// printer.print(", ");
+// }
+// }
+// }
+ if (n.getBody() == null) {
+ printer.print(";");
+ } else {
+ printer.print(" ");
+ n.getBody().accept(this, arg);
+ }
+ }
+
+ public void visit(Parameter n, Object arg) {
+// printAnnotations(n.getAnnotations(), arg);
+// printModifiers(n.getModifiers());
+
+ VariableDeclaratorId id = n.getId();
+
+ printer.print(id.getName());
+// if (n.isVarArgs()) {
+// printer.print("...");
+// }
+ printer.print(": ");
+ n.getType().accept(this, arg);
+ }
+
+ public void visit(ExplicitConstructorInvocationStmt n, Object arg) {
+ if (n.isThis()) {
+ printer.print("this");
+ } else {
+ if (n.getExpr() != null) {
+ n.getExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print("super");
+ }
+ printArguments(n.getArgs(), arg);
+ printer.print(";");
+ }
+
+ public void visit(VariableDeclarationExpr n, Object arg) {
+// printAnnotations(n.getAnnotations(), arg);
+
+ printer.print("let ");
+
+ if (!ModifierSet.isFinal(n.getModifiers())) {
+ printer.print("mut ");
+ }
+
+// printModifiers(n.getModifiers());
+
+ List<VariableDeclarator> vars = n.getVars();
+
+ printVariableDeclarator(n.getType(), vars, arg, false);
+ }
+
+ public void visit(TypeDeclarationStmt n, Object arg) {
+ n.getTypeDeclaration().accept(this, arg);
+ }
+
+ public void visit(AssertStmt n, Object arg) {
+ Expression check = n.getCheck();
+ if (check instanceof BooleanLiteralExpr) {
+ BooleanLiteralExpr bool = (BooleanLiteralExpr) check;
+ if (!bool.getValue()) {
+ printer.print("fail;");
+ return;
+ }
+ }
+ printer.print("assert ");
+ check.accept(this, arg);
+ printer.print(";");
+ }
+
+ public void visit(BlockStmt n, Object arg) {
+ printer.printLn("{");
+ if (n.getStmts() != null) {
+ printer.indent();
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ printer.printLn();
+ }
+ printer.unindent();
+ }
+ printer.print("}");
+ }
+
+ public void visit(LabeledStmt n, Object arg) {
+ assert arg == null;
+ n.getStmt().accept(this, n.getLabel());
+ }
+
+ public void visit(EmptyStmt n, Object arg) {
+ printer.print(";");
+ }
+
+ public void visit(ExpressionStmt n, Object arg) {
+ Expression plusplus = null;
+ Expression ex = n.getExpression();
+
+ if (ex instanceof MethodCallExpr) {
+ MethodCallExpr meth = (MethodCallExpr) ex;
+ if (meth.getName().startsWith("fatal") || meth.getName().startsWith("err")
+ || meth.getName().startsWith("warn")
+ || meth.getName().startsWith("maybeErr")
+ || meth.getName().startsWith("maybeWarn")
+ || meth.getName().startsWith("note")
+ || "releaseArray".equals(meth.getName())
+ || "deleteArray".equals(meth.getName())
+ || "delete".equals(meth.getName())) {
+ return;
+ }
+ }
+
+ if (ex instanceof AssignExpr) {
+ AssignExpr ax = (AssignExpr) ex;
+ Expression left = ax.getTarget();
+ if (left instanceof ArrayAccessExpr) {
+ ArrayAccessExpr aae = (ArrayAccessExpr) left;
+ Expression index = aae.getIndex();
+ if (index instanceof UnaryExpr) {
+ UnaryExpr unex = (UnaryExpr) index;
+ if (unex.getOperator() == Operator.posIncrement) {
+ plusplus = unex.getExpr();
+ unex.setOperator(null);
+ }
+ }
+ }
+ }
+ n.getExpression().accept(this, arg);
+ printer.print(";");
+ if (plusplus != null) {
+ printer.printLn();
+ plusplus.accept(this, arg);
+ printer.print(" = ");
+ plusplus.accept(this, arg);
+ printer.print(" + 1;");
+ }
+ }
+
+ public void visit(SwitchStmt n, Object arg) {
+ printer.print("match ");
+ n.getSelector().accept(this, arg);
+ printer.printLn(" {");
+ if (n.getEntries() != null) {
+ printer.indent();
+ List<Expression> labels = new LinkedList<Expression>();
+ for (SwitchEntryStmt e : n.getEntries()) {
+ labels.add(e.getLabel());
+ List<Statement> stmts = e.getStmts();
+ if (stmts != null) {
+ if (stmts.get(stmts.size() - 1) instanceof BreakStmt) {
+ BreakStmt brk = (BreakStmt)stmts.get(stmts.size() - 1);
+ if (brk.getId() == null) {
+ stmts.remove(stmts.size() - 1);
+ }
+ }
+ if (!stmts.isEmpty()) {
+ boolean first = true;
+ for (Expression label : labels) {
+ if (!first) {
+ printer.print(" | ");
+ }
+ first = false;
+ if (label == null) {
+ printer.print("_");
+ } else {
+ label.accept(this, arg);
+ }
+ }
+ printer.printLn(" => {");
+ printer.indent();
+ for (Statement statement : stmts) {
+ statement.accept(this, arg);
+ printer.printLn();
+ }
+ printer.unindent();
+ printer.printLn("}");
+ }
+ labels.clear();
+ }
+ }
+ printer.unindent();
+ }
+ printer.print("}");
+
+ }
+
+ public void visit(SwitchEntryStmt n, Object arg) {
+ throw new RuntimeException("Not supposed to come here.");
+ }
+
+ public void visit(BreakStmt n, Object arg) {
+ printer.print("break");
+ if (n.getId() != null && !"charsetloop".equals(n.getId()) && !"charactersloop".equals(n.getId())) {
+ printer.print(" ");
+ printer.print(n.getId());
+ }
+ printer.print(";");
+ }
+
+ public void visit(ReturnStmt n, Object arg) {
+ printer.print("return");
+ if (n.getExpr() != null) {
+ printer.print(" ");
+ n.getExpr().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(EnumDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ printer.print("enum ");
+ printer.print(n.getName());
+
+ if (n.getImplements() != null) {
+ printer.print(" implements ");
+ for (Iterator<ClassOrInterfaceType> i = n.getImplements().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getEntries() != null) {
+ printer.printLn();
+ for (Iterator<EnumConstantDeclaration> i = n.getEntries().iterator(); i.hasNext();) {
+ EnumConstantDeclaration e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ if (n.getMembers() != null) {
+ printer.printLn(";");
+ printMethods(n.getMembers(), arg);
+ } else {
+ if (n.getEntries() != null) {
+ printer.printLn();
+ }
+ }
+ printer.unindent();
+ printer.print("}");
+ throw new RuntimeException("Unsupported syntax.");
+ }
+
+ public void visit(EnumConstantDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printer.print(n.getName());
+
+ if (n.getArgs() != null) {
+ printArguments(n.getArgs(), arg);
+ }
+
+ if (n.getClassBody() != null) {
+ printer.printLn(" {");
+ printer.indent();
+ printMethods(n.getClassBody(), arg);
+ printer.unindent();
+ printer.printLn("}");
+ }
+ throw new RuntimeException("Unsupported syntax.");
+ }
+
+ public void visit(EmptyMemberDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printer.print(";");
+ }
+
+ public void visit(InitializerDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ if (n.isStatic()) {
+ printer.print("static ");
+ }
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(IfStmt n, Object arg) {
+ Expression cond = n.getCondition();
+ if (cond instanceof BinaryExpr) {
+ BinaryExpr binex = (BinaryExpr) cond;
+ Expression left = binex.getLeft();
+ if (left instanceof UnaryExpr) {
+ UnaryExpr unex = (UnaryExpr) left;
+ if (unex.getOperator() == Operator.preIncrement) {
+ unex.getExpr().accept(this, arg);
+ printer.print(" = ");
+ unex.getExpr().accept(this, arg);
+ printer.printLn(" + 1;");
+ unex.setOperator(null);
+ }
+ }
+ }
+
+ if (!TranslatorUtils.isErrorHandlerIf(n.getCondition(), false)) {
+ if (TranslatorUtils.isErrorOnlyBlock(n.getThenStmt(), false)) {
+ if (n.getElseStmt() != null
+ && !TranslatorUtils.isErrorOnlyBlock(n.getElseStmt(), false)) {
+ printer.print("if ");
+ if (n.getCondition() instanceof BinaryExpr) {
+ BinaryExpr binExpr = (BinaryExpr) n.getCondition();
+ switch (binExpr.getOperator()) {
+ case equals:
+ binExpr.getLeft().accept(this, arg);
+ printer.print(" != ");
+ binExpr.getRight().accept(this, arg);
+ break;
+ case notEquals:
+ binExpr.getLeft().accept(this, arg);
+ printer.print(" == ");
+ binExpr.getRight().accept(this, arg);
+ break;
+ default:
+ printer.print("!(");
+ n.getCondition().accept(this, arg);
+ printer.print(")");
+ break;
+ }
+ } else {
+ printer.print("!(");
+ n.getCondition().accept(this, arg);
+ printer.print(")");
+ }
+ printer.print(" ");
+ n.getElseStmt().accept(this, arg);
+ }
+ } else {
+ printer.print("if ");
+ n.getCondition().accept(this, arg);
+ printer.print(" ");
+ n.getThenStmt().accept(this, arg);
+ if (n.getElseStmt() != null
+ && !TranslatorUtils.isErrorOnlyBlock(n.getElseStmt(), false)) {
+ printer.print(" else ");
+ n.getElseStmt().accept(this, arg);
+ }
+ }
+ }
+
+ }
+
+ public void visit(WhileStmt n, Object arg) {
+ printer.print("while ");
+ n.getCondition().accept(this, arg);
+ printer.print(" ");
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ContinueStmt n, Object arg) {
+ if (loopUpdate != null) {
+ loopUpdate.accept(this, arg);
+ printer.printLn(";");
+ }
+ printer.print("loop");
+ if (n.getId() != null) {
+ printer.print(" ");
+ printer.print(n.getId());
+ }
+ printer.print(";");
+ }
+
+ public void visit(DoStmt n, Object arg) {
+ printer.print("do ");
+ n.getBody().accept(this, arg);
+ printer.print(" while (");
+ n.getCondition().accept(this, arg);
+ printer.print(");");
+ throw new RuntimeException("Unsupported syntax.");
+ }
+
+ public void visit(ForeachStmt n, Object arg) {
+ printer.print("for (");
+ n.getVariable().accept(this, arg);
+ printer.print(" : ");
+ n.getIterable().accept(this, arg);
+ printer.print(") ");
+ n.getBody().accept(this, arg);
+ throw new RuntimeException("Unsupported syntax.");
+ }
+
+ public void visit(ForStmt n, Object arg) {
+ String label = null;
+ if (arg instanceof String) {
+ label = (String) arg;
+ arg = null;
+ }
+ if (n.getInit() == null && n.getCompare() == null && n.getUpdate() == null) {
+ printer.print("loop ");
+ if (label != null) {
+ printer.print(label);
+ printer.print(": ");
+ }
+ n.getBody().accept(this, arg);
+ return;
+ }
+
+ assert label == null || "charsetloop".equals(label) || "charactersloop".equals(label);
+
+ Expression oldLoopUpdate = loopUpdate;
+ loopUpdate = n.getUpdate().get(0);
+
+ if (n.getInit() != null) {
+ n.getInit().get(0).accept(this, arg);
+ printer.printLn(";");
+ }
+
+ if (n.getCompare() == null) {
+ printer.print("loop ");
+ } else {
+ printer.print("while ");
+ n.getCompare().accept(this, arg);
+ printer.print(" ");
+ }
+
+ Statement body = n.getBody();
+ if (body instanceof BlockStmt) {
+ BlockStmt blockStmt = (BlockStmt) body;
+ printer.printLn("{");
+ printer.indent();
+ if (blockStmt.getStmts() != null) {
+ for (Statement s : blockStmt.getStmts()) {
+ s.accept(this, arg);
+ printer.printLn();
+ }
+ }
+ if (loopUpdate != null) {
+ loopUpdate.accept(this, arg);
+ printer.printLn(";");
+ }
+ printer.unindent();
+ printer.print("}");
+ } else {
+ throw new RuntimeException();
+ }
+
+ loopUpdate = oldLoopUpdate;
+ }
+
+ public void visit(ThrowStmt n, Object arg) {
+ printer.print("throw ");
+ n.getExpr().accept(this, arg);
+ printer.print(";");
+ }
+
+ public void visit(SynchronizedStmt n, Object arg) {
+ printer.print("synchronized (");
+ n.getExpr().accept(this, arg);
+ printer.print(") ");
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(TryStmt n, Object arg) {
+ printer.print("try ");
+ n.getTryBlock().accept(this, arg);
+ if (n.getCatchs() != null) {
+ for (CatchClause c : n.getCatchs()) {
+ c.accept(this, arg);
+ }
+ }
+ if (n.getFinallyBlock() != null) {
+ printer.print(" finally ");
+ n.getFinallyBlock().accept(this, arg);
+ }
+ }
+
+ public void visit(CatchClause n, Object arg) {
+ printer.print(" catch (");
+ n.getExcept().accept(this, arg);
+ printer.print(") ");
+ n.getCatchBlock().accept(this, arg);
+
+ }
+
+// public void visit(AnnotationDeclaration n, Object arg) {
+// printJavadoc(n.getJavaDoc(), arg);
+// printMemberAnnotations(n.getAnnotations(), arg);
+// printModifiers(n.getModifiers());
+//
+// printer.print("@interface ");
+// printer.print(n.getName());
+// printer.printLn(" {");
+// printer.indent();
+// if (n.getMembers() != null) {
+// printMembers(n.getMembers(), arg);
+// }
+// printer.unindent();
+// printer.print("}");
+// }
+//
+// public void visit(AnnotationMemberDeclaration n, Object arg) {
+// printJavadoc(n.getJavaDoc(), arg);
+// printMemberAnnotations(n.getAnnotations(), arg);
+// printModifiers(n.getModifiers());
+//
+// n.getType().accept(this, arg);
+// printer.print(" ");
+// printer.print(n.getName());
+// printer.print("()");
+// if (n.getDefaultValue() != null) {
+// printer.print(" default ");
+// n.getDefaultValue().accept(this, arg);
+// }
+// printer.print(";");
+// }
+//
+// public void visit(MarkerAnnotationExpr n, Object arg) {
+// printer.print("@");
+// n.getName().accept(this, arg);
+// }
+//
+// public void visit(SingleMemberAnnotationExpr n, Object arg) {
+// printer.print("@");
+// n.getName().accept(this, arg);
+// printer.print("(");
+// n.getMemberValue().accept(this, arg);
+// printer.print(")");
+// }
+//
+// public void visit(NormalAnnotationExpr n, Object arg) {
+// printer.print("@");
+// n.getName().accept(this, arg);
+// printer.print("(");
+// if (n.getPairs() != null) {
+// for (Iterator<MemberValuePair> i = n.getPairs().iterator(); i.hasNext();) {
+// MemberValuePair m = i.next();
+// m.accept(this, arg);
+// if (i.hasNext()) {
+// printer.print(", ");
+// }
+// }
+// }
+// printer.print(")");
+// }
+
+ public void visit(MemberValuePair n, Object arg) {
+ printer.print(n.getName());
+ printer.print(" = ");
+ n.getValue().accept(this, arg);
+ }
+
+ public void visit(LineComment n, Object arg) {
+ printer.print("//");
+ printer.printLn(n.getContent());
+ }
+
+ public void visit(BlockComment n, Object arg) {
+ printer.print("/*");
+ printer.print(n.getContent());
+ printer.printLn("*/");
+ }
+
+} \ No newline at end of file
diff --git a/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/SwitchBreakAnalyzerVisitor.java b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/SwitchBreakAnalyzerVisitor.java
new file mode 100644
index 000000000..766b349ce
--- /dev/null
+++ b/components/htmlfive/java/htmlparser/translator-src/nu/validator/htmlparser/rusttranslate/SwitchBreakAnalyzerVisitor.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 09/06/2008
+ */
+package nu.validator.htmlparser.rusttranslate;
+
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.WildcardType;
+import japa.parser.ast.visitor.GenericVisitorAdapter;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ * @author Henri Sivonen
+ */
+public class SwitchBreakAnalyzerVisitor extends GenericVisitorAdapter<Boolean, Boolean> {
+
+ private static final LoopBreakAnalyzerVisitor ANALYZER_VISITOR = new LoopBreakAnalyzerVisitor();
+
+ public Boolean visit(AssertStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(BlockStmt n, Boolean arg) {
+ // Bogus in the loop case
+ if (n.getStmts() != null) {
+ List<Statement> stms = n.getStmts();
+ return stms.get(stms.size() - 1).accept(this, arg);
+ }
+ return false;
+ }
+
+ public Boolean visit(BreakStmt n, Boolean arg) {
+ // Bogus in the general case
+ if (arg) {
+ return true;
+ }
+ return n.getId() != null;
+ }
+
+ public Boolean visit(CatchClause n, Boolean arg) {
+ return n.getCatchBlock().accept(this, arg);
+ }
+
+ public Boolean visit(ContinueStmt n, Boolean arg) {
+ // Bogus in the general case
+ if (arg) {
+ return true;
+ }
+ return n.getId() != null;
+ }
+
+ public Boolean visit(DoStmt n, Boolean arg) {
+ return n.getBody().accept(this, arg);
+ }
+
+ public Boolean visit(EmptyStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(ExplicitConstructorInvocationStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(ExpressionStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(ForeachStmt n, Boolean arg) {
+ return n.getBody().accept(this, arg);
+ }
+
+ public Boolean visit(ForStmt n, Boolean arg) {
+ return n.getBody().accept(ANALYZER_VISITOR, arg);
+ }
+
+ public Boolean visit(IfStmt n, Boolean arg) {
+ if (n.getElseStmt() != null) {
+ return n.getThenStmt().accept(this, arg) && n.getElseStmt().accept(this, arg);
+ }
+ return false;
+ }
+
+ public Boolean visit(LabeledStmt n, Boolean arg) {
+ return n.getStmt().accept(this, arg);
+ }
+
+ public Boolean visit(ReturnStmt n, Boolean arg) {
+ return true;
+ }
+
+ public Boolean visit(SwitchEntryStmt n, Boolean arg) {
+ if (n.getStmts() != null) {
+ List<Statement> stms = n.getStmts();
+ return stms.get(stms.size() - 1).accept(this, arg);
+ }
+ return false;
+ }
+
+ public Boolean visit(SwitchStmt n, Boolean arg) {
+ /*
+ List<SwitchEntryStmt> entries = n.getEntries();
+ for (int i = 0; i < array.length; i++) {
+ array_type array_element = array[i];
+
+ }
+ */
+ return true;
+ }
+
+ public Boolean visit(SynchronizedStmt n, Boolean arg) {
+ return n.getBlock().accept(this, arg);
+ }
+
+ public Boolean visit(ThrowStmt n, Boolean arg) {
+ return true;
+ }
+
+ public Boolean visit(TryStmt n, Boolean arg) {
+ if (n.getFinallyBlock() != null) {
+ return n.getFinallyBlock().accept(this, arg);
+ }
+ if (n.getCatchs() != null) {
+ for (CatchClause c : n.getCatchs()) {
+ boolean brk = c.accept(this, arg);
+ if (!brk) {
+ return false;
+ }
+ }
+ }
+ return n.getTryBlock().accept(this, arg);
+ }
+
+ public Boolean visit(TypeDeclarationStmt n, Boolean arg) {
+ return false;
+ }
+
+ public Boolean visit(WhileStmt n, Boolean arg) {
+ return n.getBody().accept(this, arg);
+ }
+
+ public Boolean visit(WildcardType n, Boolean arg) {
+ if (n.getExtends() != null) {
+ n.getExtends().accept(this, arg);
+ }
+ if (n.getSuper() != null) {
+ n.getSuper().accept(this, arg);
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/components/htmlfive/java/javaparser/LICENSE b/components/htmlfive/java/javaparser/LICENSE
new file mode 100644
index 000000000..b87303c39
--- /dev/null
+++ b/components/htmlfive/java/javaparser/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/components/htmlfive/java/javaparser/ant/ant-googlecode-0.0.1.jar b/components/htmlfive/java/javaparser/ant/ant-googlecode-0.0.1.jar
new file mode 100644
index 000000000..e58f69ba0
--- /dev/null
+++ b/components/htmlfive/java/javaparser/ant/ant-googlecode-0.0.1.jar
Binary files differ
diff --git a/components/htmlfive/java/javaparser/ant/build.xml b/components/htmlfive/java/javaparser/ant/build.xml
new file mode 100644
index 000000000..395ef4e6b
--- /dev/null
+++ b/components/htmlfive/java/javaparser/ant/build.xml
@@ -0,0 +1,86 @@
+<project name="JavaParserBuilder" default="all">
+
+ <!-- current version -->
+ <property name="version" value="1.0.6" />
+
+ <!-- package build properties -->
+ <property name="source.dir" location="../" />
+ <property name="temp.dir" value="${user.home}/Desktop/" />
+ <property name="file.name" value="javaparser-${version}" />
+
+ <property name="src.file.name" value="${file.name}-src.zip" />
+ <property name="bin.jar.name" value="${file.name}.jar" />
+ <property name="bin.file.name" value="${file.name}.zip" />
+
+ <property name="src.temp.file" value="${temp.dir}/${src.file.name}" />
+ <property name="bin.temp.file" value="${temp.dir}/${bin.file.name}" />
+ <property name="bin.temp.jar" value="${temp.dir}/${bin.jar.name}" />
+
+
+ <!-- googlecode properties -->
+ <taskdef classname="net.bluecow.googlecode.ant.GoogleCodeUploadTask" classpath="ant-googlecode-0.0.1.jar" name="gcupload" />
+ <property name="googlecode.user" value="jgesser" />
+ <!-- the file must have a property named "googlecode.password" -->
+ <property file="../../JavaParser-ant/pass.txt" />
+ <property name="googlecode.project" value="javaparser" />
+ <property name="googlecode.file.summary.src" value="Java1.5 parser and AST release ${version} (source)" />
+ <property name="googlecode.file.summary.bin" value="Java1.5 parser and AST release ${version} (binary)" />
+ <property name="googlecode.file.labels.src" value="Featured, Type-Source, OpSys-All" />
+ <property name="googlecode.file.labels.bin" value="Featured, Type-Archive, OpSys-All" />
+
+ <target name="build-src">
+ <zip destfile="${src.temp.file}" level="9">
+ <fileset dir="${source.dir}">
+ <exclude name=".*" />
+ <exclude name="bin/**" />
+ <exclude name="ant/**" />
+ <exclude name="test/ignore/**" />
+ </fileset>
+ </zip>
+ </target>
+
+ <target name="build-bin">
+ <jar destfile="${bin.temp.jar}">
+ <fileset dir="${source.dir}/bin">
+ <exclude name="TestRunner*.class" />
+ <exclude name="**/test/**/*" />
+ </fileset>
+ </jar>
+
+ <zip destfile="${bin.temp.file}" level="9">
+ <fileset dir="${source.dir}">
+ <include name="COPYING" />
+ <include name="COPYING.LESSER" />
+ <include name="readme.txt" />
+ </fileset>
+ <fileset file="${bin.temp.jar}" />
+ </zip>
+ </target>
+
+ <target name="upload-src">
+ <gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="${googlecode.project}" filename="${src.temp.file}" targetfilename="${src.file.name}" summary="${googlecode.file.summary.src}" labels="${googlecode.file.labels.src}" />
+ </target>
+
+ <target name="upload-bin">
+ <gcupload username="${googlecode.user}" password="${googlecode.password}" projectname="${googlecode.project}" filename="${bin.temp.file}" targetfilename="${bin.file.name}" summary="${googlecode.file.summary.bin}" labels="${googlecode.file.labels.bin}" />
+ </target>
+
+ <target name="clean">
+ <delete file="${src.temp.file}" failonerror="true" />
+ <delete file="${bin.temp.file}" failonerror="true" />
+ <delete file="${bin.temp.jar}" failonerror="true" />
+ </target>
+
+ <target name="all" description="default">
+ <antcall target="clean" />
+
+ <antcall target="build-src" />
+ <antcall target="build-bin" />
+
+ <antcall target="upload-src" />
+ <antcall target="upload-bin" />
+
+ <antcall target="clean" />
+ </target>
+
+</project> \ No newline at end of file
diff --git a/components/htmlfive/java/javaparser/readme.txt b/components/htmlfive/java/javaparser/readme.txt
new file mode 100644
index 000000000..f8759c8d6
--- /dev/null
+++ b/components/htmlfive/java/javaparser/readme.txt
@@ -0,0 +1,138 @@
++-------------------------------------------------------------------------------+
+| Java 1.5 parser and Abstract Syntax Tree. |
++-------------------------------------------------------------------------------+
+| Copyright (C) 2007 Júlio Vilmar Gesser |
+| jgesser@gmail.com |
+| http://code.google.com/p/javaparser/ |
++-------------------------------------------------------------------------------+
+| This program is free software: you can redistribute it and/or modify |
+| it under the terms of the GNU Lesser General Public License as published by |
+| the Free Software Foundation, either version 3 of the License, or |
+| (at your option) any later version. |
+| |
+| This program is distributed in the hope that it will be useful, |
+| but WITHOUT ANY WARRANTY; without even the implied warranty of |
+| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
+| GNU Lesser General Public License for more details. |
+| |
+| You should have received a copy of the GNU Lesser General Public License |
+| along with this program. If not, see <http://www.gnu.org/licenses/>. |
++-------------------------------------------------------------------------------+
+
+This package contains a Java 1.5 Parser with AST generation and visitor support.
+The AST records the source code structure, javadoc and comments. Soon will be
+possible change the AST nodes or create new ones to modify source code like refactoring.
+This parser is based on Sreenivasa Viswanadha Java 1.5 parser.
+
+Visit the project site, there you can get help, view some sample codes, report
+bugs and feature enhacement and download the latest version:
+ http://code.google.com/p/javaparser/
+
+
+Version history
+---------------
+
+1.0.6 (2009-01-11)
+- Issue 11 fixed: changed method get/setPakage to get/setPackage in the class CompilationUnit
+- Created new visitor adapter to help AST modification: ModifierVisitorAdapter
+- Changed visitor adapters to abstract
+
+1.0.5 (2008-10-26)
+- Created simplified constructors in the nodes of the AST (without positional arguments)
+- Created ASTHelper class with some helpful methods (more methods are still needed)
+
+1.0.4 (2008-10-07)
+- Moved to javacc 4.1.
+- The java_1_5.jj can be build alone without compilation errors
+
+1.0.3 (2008-09-06)
+- Removed SuperMemberAccessExpr class, it was no longer used
+- Removed the methods get/setTypeArgs() from ArrayCreationExpr, this node shouldn't have these methods.
+- Fixed the bug with start/end position of the nodes IntegerLiteralMinValueExpr and LongLiteralMinValueExpr
+- The methods get/setAnnotations() from all BodyDeclaration subclasses were pushed down to BodyDeclaration class
+
+1.0.2 (2008-07-20)
+ Issue fixed: Issue 1: Add support for editing AST nodes or create new ones
+
+1.0.1 (2008-07-01)
+- Issue fixed: Issue 5: end line and end column equal to begin line and begin column
+
+1.0.0 (2008-06-25)
+- Changed version numbering, starting version 1.0.0
+- Javadoc done for packages:
+ - japa.parser
+ - japa.parser.ast
+- Corrected bug when parsing in multithread:
+ - JavaParser.setCacheParser(false) must be called before to use the parser concurrent
+
+2008-06-19
+- No code changes, added binary distribution to download page
+
+2008-06-11
+- Bug corrected: NPE in VoidVisitorAdapter
+ - http://code.google.com/p/javaparser/issues/detail?id=2
+
+2008-06-09
+- Added Adapters for de visitors
+
+2008-05-28
+- This project now is published at Google Code:
+ - http://code.google.com/p/javaparser/
+
+2008-05-25
+- Added support for comments and javadoc to the tree.
+ - Javadocs are stored directly to members (BodyDeclaration and all deriveds (classes, methods, fields, etc.)), accessible by the method getJavadoc().
+ - All comments are stored in the CompilationUnit, accessible by the method getComments().
+
+2008-04-01
+- Changed all nodes public attributes to be private and created getters to access them
+- Changed the methods of the Node getLine e getColumn to getBeginLine and getBeginColumn
+- Added the methods getEndLine and getEndColumn to the Node class (works only in the BlockNode)
+
+2007-12-22
+- Corrected ConditionalExpression bug
+
+2007-10-21
+- Added LGPL License
+
+2007-10-21
+- Bugs corrected:
+ - Created PackageDeclaration member of CompilationUnit to add suport for annotations in the package declaration
+ - Parameterized anonymous constructor invocation
+ - Explicit constructor invotation Type Arguments
+ - ctrl+z ("\u001A") ar end of compilation unit
+
+2007-10-09
+- EnumConstantDeclaration annotation support corrected
+- Parssing Java Unicode escape characters suport added
+
+2007-10-03
+- Bug corrected: "MotifComboPopup.this.super()" statement was generating parser error
+
+2007-10-01
+- Bug corrected: Casting signed primitive values
+ double d = (double) -1;
+ ^
+
+2007-08-06
+- Bug with the ingle line comments in the final of the unit corrected
+
+2007-07-31
+- Fixed the bug with the following expression:
+ Class c = (int.class);
+
+2007-06-26
+- Bug fixes from Leon Poyyayil work
+ - suport for hex floating point
+ - unicode digits in indentifier
+ - MemberValueArrayInitializer
+
+2007-03-09
+- Long and Integer literal MIN_VALUE bug
+
+2007-02-24
+- '\0' bug fixed
+
+2007-02-01
+- Many bug fixes
+- Added line/column to nodes \ No newline at end of file
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ASTHelper.java b/components/htmlfive/java/javaparser/src/japa/parser/ASTHelper.java
new file mode 100644
index 000000000..a932209d5
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ASTHelper.java
@@ -0,0 +1,264 @@
+package japa.parser;
+
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.body.BodyDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.TypeDeclaration;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.PrimitiveType.Primitive;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class helps to construct new nodes.
+ *
+ * @author Julio Vilmar Gesser
+ */
+public final class ASTHelper {
+
+ public static final PrimitiveType BYTE_TYPE = new PrimitiveType(Primitive.Byte);
+
+ public static final PrimitiveType SHORT_TYPE = new PrimitiveType(Primitive.Short);
+
+ public static final PrimitiveType INT_TYPE = new PrimitiveType(Primitive.Int);
+
+ public static final PrimitiveType LONG_TYPE = new PrimitiveType(Primitive.Long);
+
+ public static final PrimitiveType FLOAT_TYPE = new PrimitiveType(Primitive.Float);
+
+ public static final PrimitiveType DOUBLE_TYPE = new PrimitiveType(Primitive.Double);
+
+ public static final PrimitiveType BOOLEAN_TYPE = new PrimitiveType(Primitive.Boolean);
+
+ public static final PrimitiveType CHAR_TYPE = new PrimitiveType(Primitive.Char);
+
+ public static final VoidType VOID_TYPE = new VoidType();
+
+ private ASTHelper() {
+ // nop
+ }
+
+ /**
+ * Creates a new {@link NameExpr} from a qualified name.<br>
+ * The qualified name can contains "." (dot) characters.
+ *
+ * @param qualifiedName
+ * qualified name
+ * @return instanceof {@link NameExpr}
+ */
+ public static NameExpr createNameExpr(String qualifiedName) {
+ String[] split = qualifiedName.split("\\.");
+ NameExpr ret = new NameExpr(split[0]);
+ for (int i = 1; i < split.length; i++) {
+ ret = new QualifiedNameExpr(ret, split[i]);
+ }
+ return ret;
+ }
+
+ /**
+ * Creates a new {@link Parameter}.
+ *
+ * @param type
+ * type of the parameter
+ * @param name
+ * name of the parameter
+ * @return instance of {@link Parameter}
+ */
+ public static Parameter createParameter(Type type, String name) {
+ return new Parameter(type, new VariableDeclaratorId(name));
+ }
+
+ /**
+ * Creates a {@link FieldDeclaration}.
+ *
+ * @param modifiers
+ * modifiers
+ * @param type
+ * type
+ * @param variable
+ * variable declarator
+ * @return instance of {@link FieldDeclaration}
+ */
+ public static FieldDeclaration createFieldDeclaration(int modifiers, Type type, VariableDeclarator variable) {
+ List<VariableDeclarator> variables = new ArrayList<VariableDeclarator>();
+ variables.add(variable);
+ FieldDeclaration ret = new FieldDeclaration(modifiers, type, variables);
+ return ret;
+ }
+
+ /**
+ * Creates a {@link FieldDeclaration}.
+ *
+ * @param modifiers
+ * modifiers
+ * @param type
+ * type
+ * @param name
+ * field name
+ * @return instance of {@link FieldDeclaration}
+ */
+ public static FieldDeclaration createFieldDeclaration(int modifiers, Type type, String name) {
+ VariableDeclaratorId id = new VariableDeclaratorId(name);
+ VariableDeclarator variable = new VariableDeclarator(id);
+ return createFieldDeclaration(modifiers, type, variable);
+ }
+
+ /**
+ * Creates a {@link VariableDeclarationExpr}.
+ *
+ * @param type
+ * type
+ * @param name
+ * name
+ * @return instance of {@link VariableDeclarationExpr}
+ */
+ public static VariableDeclarationExpr createVariableDeclarationExpr(Type type, String name) {
+ List<VariableDeclarator> vars = new ArrayList<VariableDeclarator>();
+ vars.add(new VariableDeclarator(new VariableDeclaratorId(name)));
+ return new VariableDeclarationExpr(type, vars);
+ }
+
+ /**
+ * Adds the given parameter to the method. The list of parameters will be
+ * initialized if it is <code>null</code>.
+ *
+ * @param method
+ * method
+ * @param parameter
+ * parameter
+ */
+ public static void addParameter(MethodDeclaration method, Parameter parameter) {
+ List<Parameter> parameters = method.getParameters();
+ if (parameters == null) {
+ parameters = new ArrayList<Parameter>();
+ method.setParameters(parameters);
+ }
+ parameters.add(parameter);
+ }
+
+ /**
+ * Adds the given argument to the method call. The list of arguments will be
+ * initialized if it is <code>null</code>.
+ *
+ * @param call
+ * method call
+ * @param arg
+ * argument value
+ */
+ public static void addArgument(MethodCallExpr call, Expression arg) {
+ List<Expression> args = call.getArgs();
+ if (args == null) {
+ args = new ArrayList<Expression>();
+ call.setArgs(args);
+ }
+ args.add(arg);
+ }
+
+ /**
+ * Adds the given type declaration to the compilation unit. The list of
+ * types will be initialized if it is <code>null</code>.
+ *
+ * @param cu
+ * compilation unit
+ * @param type
+ * type declaration
+ */
+ public static void addTypeDeclaration(CompilationUnit cu, TypeDeclaration type) {
+ List<TypeDeclaration> types = cu.getTypes();
+ if (types == null) {
+ types = new ArrayList<TypeDeclaration>();
+ cu.setTypes(types);
+ }
+ types.add(type);
+
+ }
+
+ /**
+ * Creates a new {@link ReferenceType} for a class or interface.
+ *
+ * @param name
+ * name of the class or interface
+ * @param arrayCount
+ * number os arrays or <code>0</code> if is not a array.
+ * @return instanceof {@link ReferenceType}
+ */
+ public static ReferenceType createReferenceType(String name, int arrayCount) {
+ return new ReferenceType(new ClassOrInterfaceType(name), arrayCount);
+ }
+
+ /**
+ * Creates a new {@link ReferenceType} for the given primitive type.
+ *
+ * @param type
+ * primitive type
+ * @param arrayCount
+ * number os arrays or <code>0</code> if is not a array.
+ * @return instanceof {@link ReferenceType}
+ */
+ public static ReferenceType createReferenceType(PrimitiveType type, int arrayCount) {
+ return new ReferenceType(type, arrayCount);
+ }
+
+ /**
+ * Adds the given statement to the specified block. The list of statements
+ * will be initialized if it is <code>null</code>.
+ *
+ * @param block
+ * @param stmt
+ */
+ public static void addStmt(BlockStmt block, Statement stmt) {
+ List<Statement> stmts = block.getStmts();
+ if (stmts == null) {
+ stmts = new ArrayList<Statement>();
+ block.setStmts(stmts);
+ }
+ stmts.add(stmt);
+ }
+
+ /**
+ * Adds the given expression to the specified block. The list of statements
+ * will be initialized if it is <code>null</code>.
+ *
+ * @param block
+ * @param stmt
+ */
+ public static void addStmt(BlockStmt block, Expression expr) {
+ addStmt(block, new ExpressionStmt(expr));
+ }
+
+ /**
+ * Adds the given declaration to the specified type. The list of members
+ * will be initialized if it is <code>null</code>.
+ *
+ * @param type
+ * type declaration
+ * @param decl
+ * member declaration
+ */
+ public static void addMember(TypeDeclaration type, BodyDeclaration decl) {
+ List<BodyDeclaration> members = type.getMembers();
+ if (members == null) {
+ members = new ArrayList<BodyDeclaration>();
+ type.setMembers(members);
+ }
+ members.add(decl);
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ASTParser.java b/components/htmlfive/java/javaparser/src/japa/parser/ASTParser.java
new file mode 100644
index 000000000..36c70fe3e
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ASTParser.java
@@ -0,0 +1,7803 @@
+/* Generated By:JavaCC: Do not edit this line. ASTParser.java */
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+package japa.parser;
+
+import java.io.*;
+import java.util.*;
+import japa.parser.ast.*;
+import japa.parser.ast.body.*;
+import japa.parser.ast.expr.*;
+import japa.parser.ast.stmt.*;
+import japa.parser.ast.type.*;
+
+/**
+ * <p>This class was generated automatically by javacc, do not edit.</p>
+ * @author Júlio Vilmar Gesser
+ */
+final class ASTParser implements ASTParserConstants {
+
+ void reset(InputStream in, String encoding) {
+ ReInit(in, encoding);
+ token_source.clearComments();
+ }
+
+ private List add(List list, Object obj) {
+ if (list == null) {
+ list = new LinkedList();
+ }
+ list.add(obj);
+ return list;
+ }
+
+ private List add(int pos, List list, Object obj) {
+ if (list == null) {
+ list = new LinkedList();
+ }
+ list.add(pos, obj);
+ return list;
+ }
+
+ private class Modifier {
+
+ final int modifiers;
+ final List annotations;
+ final int beginLine;
+ final int beginColumn;
+
+ public Modifier(int beginLine, int beginColumn, int modifiers, List annotations) {
+ this.beginLine = beginLine;
+ this.beginColumn = beginColumn;
+ this.modifiers = modifiers;
+ this.annotations = annotations;
+ }
+ }
+
+ public int addModifier(int modifiers, int mod, Token token) throws ParseException {
+ if ((ModifierSet.hasModifier(modifiers, mod))) {
+ throwParseException(token, "Duplicated modifier");
+ }
+ return ModifierSet.addModifier(modifiers, mod);
+ }
+
+ private void pushJavadoc() {
+ token_source.pushJavadoc();
+ }
+
+ private JavadocComment popJavadoc() {
+ return token_source.popJavadoc();
+ }
+
+ private List<Comment> getComments() {
+ return token_source.getComments();
+ }
+
+ private void throwParseException(Token token, String message) throws ParseException {
+ StringBuilder buf = new StringBuilder();
+ buf.append(message);
+ buf.append(": \"");
+ buf.append(token.image);
+ buf.append("\" at line ");
+ buf.append(token.beginLine);
+ buf.append(", column ");
+ buf.append(token.beginColumn);
+ ParseException e = new ParseException(buf.toString());
+ e.currentToken = token;
+ throw e;
+ }
+
+ static final class GTToken extends Token {
+
+ int realKind = ASTParserConstants.GT;
+
+ GTToken(int kind, String image) {
+ this.kind = kind;
+ this.image = image;
+ }
+
+ public static Token newToken(int kind, String image) {
+ return new GTToken(kind, image);
+ }
+ }
+
+/*****************************************
+ * THE JAVA LANGUAGE GRAMMAR STARTS HERE *
+ *****************************************/
+
+/*
+ * Program structuring syntax follows.
+ */
+ final public CompilationUnit CompilationUnit() throws ParseException {
+ PackageDeclaration pakage = null;
+ List imports = null;
+ ImportDeclaration in = null;
+ List types = null;
+ TypeDeclaration tn = null;
+ int line = -1;
+ int column = 0;
+ if (jj_2_1(2147483647)) {
+ pakage = PackageDeclaration();
+ line = pakage.getBeginLine(); column = pakage.getBeginColumn();
+ } else {
+ ;
+ }
+ label_1:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IMPORT:
+ ;
+ break;
+ default:
+ jj_la1[0] = jj_gen;
+ break label_1;
+ }
+ in = ImportDeclaration();
+ if(line==-1){line = in.getBeginLine(); column = in.getBeginColumn();} imports = add(imports, in);
+ }
+ label_2:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ABSTRACT:
+ case CLASS:
+ case ENUM:
+ case FINAL:
+ case INTERFACE:
+ case NATIVE:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case STATIC:
+ case STRICTFP:
+ case SYNCHRONIZED:
+ case TRANSIENT:
+ case VOLATILE:
+ case SEMICOLON:
+ case AT:
+ ;
+ break;
+ default:
+ jj_la1[1] = jj_gen;
+ break label_2;
+ }
+ tn = TypeDeclaration();
+ if(line==-1){line = tn.getBeginLine(); column = tn.getBeginColumn();} types = add(types, tn);
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case 0:
+ jj_consume_token(0);
+ break;
+ case 128:
+ jj_consume_token(128);
+ break;
+ default:
+ jj_la1[2] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return new CompilationUnit(line == -1 ? 0 : line, column, token.endLine, token.endColumn,pakage, imports, types, getComments());}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public PackageDeclaration PackageDeclaration() throws ParseException {
+ List annotations = null;
+ AnnotationExpr ann;
+ NameExpr name;
+ int line;
+ int column;
+ label_3:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case AT:
+ ;
+ break;
+ default:
+ jj_la1[3] = jj_gen;
+ break label_3;
+ }
+ ann = Annotation();
+ annotations = add(annotations, ann);
+ }
+ jj_consume_token(PACKAGE);
+ line=token.beginLine; column=token.beginColumn;
+ name = Name();
+ jj_consume_token(SEMICOLON);
+ {if (true) return new PackageDeclaration(line, column, token.endLine, token.endColumn,annotations, name);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ImportDeclaration ImportDeclaration() throws ParseException {
+ NameExpr name;
+ boolean isStatic = false;
+ boolean isAsterisk = false;
+ int line;
+ int column;
+ jj_consume_token(IMPORT);
+ line=token.beginLine; column=token.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STATIC:
+ jj_consume_token(STATIC);
+ isStatic = true;
+ break;
+ default:
+ jj_la1[4] = jj_gen;
+ ;
+ }
+ name = Name();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case DOT:
+ jj_consume_token(DOT);
+ jj_consume_token(STAR);
+ isAsterisk = true;
+ break;
+ default:
+ jj_la1[5] = jj_gen;
+ ;
+ }
+ jj_consume_token(SEMICOLON);
+ {if (true) return new ImportDeclaration(line, column, token.endLine, token.endColumn,name, isStatic, isAsterisk);}
+ throw new Error("Missing return statement in function");
+ }
+
+/*
+ * Modifiers. We match all modifiers in a single rule to reduce the chances of
+ * syntax errors for simple modifier mistakes. It will also enable us to give
+ * better error messages.
+ */
+ final public Modifier Modifiers() throws ParseException {
+ int beginLine = -1;
+ int beginColumn = -1;
+ int modifiers = 0;
+ List annotations = null;
+ AnnotationExpr ann;
+ label_4:
+ while (true) {
+ if (jj_2_2(2)) {
+ ;
+ } else {
+ break label_4;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case PUBLIC:
+ jj_consume_token(PUBLIC);
+ modifiers = addModifier(modifiers, ModifierSet.PUBLIC, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case STATIC:
+ jj_consume_token(STATIC);
+ modifiers = addModifier(modifiers, ModifierSet.STATIC, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case PROTECTED:
+ jj_consume_token(PROTECTED);
+ modifiers = addModifier(modifiers, ModifierSet.PROTECTED, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case PRIVATE:
+ jj_consume_token(PRIVATE);
+ modifiers = addModifier(modifiers, ModifierSet.PRIVATE, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case FINAL:
+ jj_consume_token(FINAL);
+ modifiers = addModifier(modifiers, ModifierSet.FINAL, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case ABSTRACT:
+ jj_consume_token(ABSTRACT);
+ modifiers = addModifier(modifiers, ModifierSet.ABSTRACT, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case SYNCHRONIZED:
+ jj_consume_token(SYNCHRONIZED);
+ modifiers = addModifier(modifiers, ModifierSet.SYNCHRONIZED, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case NATIVE:
+ jj_consume_token(NATIVE);
+ modifiers = addModifier(modifiers, ModifierSet.NATIVE, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case TRANSIENT:
+ jj_consume_token(TRANSIENT);
+ modifiers = addModifier(modifiers, ModifierSet.TRANSIENT, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case VOLATILE:
+ jj_consume_token(VOLATILE);
+ modifiers = addModifier(modifiers, ModifierSet.VOLATILE, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case STRICTFP:
+ jj_consume_token(STRICTFP);
+ modifiers = addModifier(modifiers, ModifierSet.STRICTFP, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ break;
+ case AT:
+ ann = Annotation();
+ annotations = add(annotations, ann); if(beginLine==-1) {beginLine=ann.getBeginLine(); beginColumn=ann.getBeginColumn();}
+ break;
+ default:
+ jj_la1[6] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ {if (true) return new Modifier(beginLine, beginColumn, modifiers, annotations);}
+ throw new Error("Missing return statement in function");
+ }
+
+/*
+ * Declaration syntax follows.
+ */
+ final public TypeDeclaration TypeDeclaration() throws ParseException {
+ Modifier modifier;
+ TypeDeclaration ret;
+ pushJavadoc();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case SEMICOLON:
+ jj_consume_token(SEMICOLON);
+ ret = new EmptyTypeDeclaration(token.beginLine, token.beginColumn, token.endLine, token.endColumn, popJavadoc());
+ break;
+ case ABSTRACT:
+ case CLASS:
+ case ENUM:
+ case FINAL:
+ case INTERFACE:
+ case NATIVE:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case STATIC:
+ case STRICTFP:
+ case SYNCHRONIZED:
+ case TRANSIENT:
+ case VOLATILE:
+ case AT:
+ modifier = Modifiers();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CLASS:
+ case INTERFACE:
+ ret = ClassOrInterfaceDeclaration(modifier);
+ break;
+ case ENUM:
+ ret = EnumDeclaration(modifier);
+ break;
+ case AT:
+ ret = AnnotationTypeDeclaration(modifier);
+ break;
+ default:
+ jj_la1[7] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ break;
+ default:
+ jj_la1[8] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ClassOrInterfaceDeclaration ClassOrInterfaceDeclaration(Modifier modifier) throws ParseException {
+ boolean isInterface = false;
+ String name;
+ List typePar = null;
+ List extList = null;
+ List impList = null;
+ List members;
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CLASS:
+ jj_consume_token(CLASS);
+ break;
+ case INTERFACE:
+ jj_consume_token(INTERFACE);
+ isInterface = true;
+ break;
+ default:
+ jj_la1[9] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ if (line == -1) {line=token.beginLine; column=token.beginColumn;}
+ jj_consume_token(IDENTIFIER);
+ name = token.image;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LT:
+ typePar = TypeParameters();
+ typePar.remove(0);
+ break;
+ default:
+ jj_la1[10] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case EXTENDS:
+ extList = ExtendsList(isInterface);
+ break;
+ default:
+ jj_la1[11] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IMPLEMENTS:
+ impList = ImplementsList(isInterface);
+ break;
+ default:
+ jj_la1[12] = jj_gen;
+ ;
+ }
+ members = ClassOrInterfaceBody(isInterface);
+ {if (true) return new ClassOrInterfaceDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), modifier.modifiers, modifier.annotations, isInterface, name, typePar, extList, impList, members);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List ExtendsList(boolean isInterface) throws ParseException {
+ boolean extendsMoreThanOne = false;
+ List ret = new LinkedList();
+ ClassOrInterfaceType cit;
+ jj_consume_token(EXTENDS);
+ cit = ClassOrInterfaceType();
+ ret.add(cit);
+ label_5:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[13] = jj_gen;
+ break label_5;
+ }
+ jj_consume_token(COMMA);
+ cit = ClassOrInterfaceType();
+ ret.add(cit); extendsMoreThanOne = true;
+ }
+ if (extendsMoreThanOne && !isInterface)
+ throwParseException(token, "A class cannot extend more than one other class");
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List ImplementsList(boolean isInterface) throws ParseException {
+ List ret = new LinkedList();
+ ClassOrInterfaceType cit;
+ jj_consume_token(IMPLEMENTS);
+ cit = ClassOrInterfaceType();
+ ret.add(cit);
+ label_6:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[14] = jj_gen;
+ break label_6;
+ }
+ jj_consume_token(COMMA);
+ cit = ClassOrInterfaceType();
+ ret.add(cit);
+ }
+ if (isInterface)
+ throwParseException(token, "An interface cannot implement other interfaces");
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public EnumDeclaration EnumDeclaration(Modifier modifier) throws ParseException {
+ String name;
+ List impList = null;
+ EnumConstantDeclaration entry;
+ List entries = null;
+ BodyDeclaration member;
+ List members = null;
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ jj_consume_token(ENUM);
+ if (line == -1) {line=token.beginLine; column=token.beginColumn;}
+ jj_consume_token(IDENTIFIER);
+ name = token.image;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IMPLEMENTS:
+ impList = ImplementsList(false);
+ break;
+ default:
+ jj_la1[15] = jj_gen;
+ ;
+ }
+ jj_consume_token(LBRACE);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ case AT:
+ entries = new LinkedList();
+ entry = EnumConstantDeclaration();
+ entries.add(entry);
+ label_7:
+ while (true) {
+ if (jj_2_3(2)) {
+ ;
+ } else {
+ break label_7;
+ }
+ jj_consume_token(COMMA);
+ entry = EnumConstantDeclaration();
+ entries.add(entry);
+ }
+ break;
+ default:
+ jj_la1[16] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ jj_consume_token(COMMA);
+ break;
+ default:
+ jj_la1[17] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case SEMICOLON:
+ jj_consume_token(SEMICOLON);
+ label_8:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ABSTRACT:
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case CLASS:
+ case DOUBLE:
+ case ENUM:
+ case FINAL:
+ case FLOAT:
+ case INT:
+ case INTERFACE:
+ case LONG:
+ case NATIVE:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case SHORT:
+ case STATIC:
+ case STRICTFP:
+ case SYNCHRONIZED:
+ case TRANSIENT:
+ case VOID:
+ case VOLATILE:
+ case IDENTIFIER:
+ case LBRACE:
+ case SEMICOLON:
+ case AT:
+ case LT:
+ ;
+ break;
+ default:
+ jj_la1[18] = jj_gen;
+ break label_8;
+ }
+ member = ClassOrInterfaceBodyDeclaration(false);
+ members = add(members, member);
+ }
+ break;
+ default:
+ jj_la1[19] = jj_gen;
+ ;
+ }
+ jj_consume_token(RBRACE);
+ {if (true) return new EnumDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), modifier.modifiers, modifier.annotations, name, impList, entries, members);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public EnumConstantDeclaration EnumConstantDeclaration() throws ParseException {
+ List annotations = null;
+ AnnotationExpr ann;
+ String name;
+ List args = null;
+ List classBody = null;
+ int line = -1;
+ int column = -1;
+ pushJavadoc();
+ label_9:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case AT:
+ ;
+ break;
+ default:
+ jj_la1[20] = jj_gen;
+ break label_9;
+ }
+ ann = Annotation();
+ annotations = add(annotations, ann); if(line==-1){line=ann.getBeginLine(); column=ann.getBeginColumn();}
+ }
+ jj_consume_token(IDENTIFIER);
+ name = token.image; if(line==-1){line=token.beginLine; column=token.beginColumn;}
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LPAREN:
+ args = Arguments();
+ break;
+ default:
+ jj_la1[21] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LBRACE:
+ classBody = ClassOrInterfaceBody(false);
+ break;
+ default:
+ jj_la1[22] = jj_gen;
+ ;
+ }
+ {if (true) return new EnumConstantDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), annotations, name, args, classBody);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List TypeParameters() throws ParseException {
+ List ret = new LinkedList();
+ TypeParameter tp;
+ jj_consume_token(LT);
+ ret.add(new int[]{token.beginLine, token.beginColumn});
+ tp = TypeParameter();
+ ret.add(tp);
+ label_10:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[23] = jj_gen;
+ break label_10;
+ }
+ jj_consume_token(COMMA);
+ tp = TypeParameter();
+ ret.add(tp);
+ }
+ jj_consume_token(GT);
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public TypeParameter TypeParameter() throws ParseException {
+ String name;
+ List typeBound = null;
+ int line;
+ int column;
+ jj_consume_token(IDENTIFIER);
+ name = token.image; line=token.beginLine; column=token.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case EXTENDS:
+ typeBound = TypeBound();
+ break;
+ default:
+ jj_la1[24] = jj_gen;
+ ;
+ }
+ {if (true) return new TypeParameter(line, column, token.endLine, token.endColumn,name, typeBound);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List TypeBound() throws ParseException {
+ List ret = new LinkedList();
+ ClassOrInterfaceType cit;
+ jj_consume_token(EXTENDS);
+ cit = ClassOrInterfaceType();
+ ret.add(cit);
+ label_11:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BIT_AND:
+ ;
+ break;
+ default:
+ jj_la1[25] = jj_gen;
+ break label_11;
+ }
+ jj_consume_token(BIT_AND);
+ cit = ClassOrInterfaceType();
+ ret.add(cit);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List ClassOrInterfaceBody(boolean isInterface) throws ParseException {
+ List ret = new LinkedList();
+ BodyDeclaration member;
+ jj_consume_token(LBRACE);
+ label_12:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ABSTRACT:
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case CLASS:
+ case DOUBLE:
+ case ENUM:
+ case FINAL:
+ case FLOAT:
+ case INT:
+ case INTERFACE:
+ case LONG:
+ case NATIVE:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case SHORT:
+ case STATIC:
+ case STRICTFP:
+ case SYNCHRONIZED:
+ case TRANSIENT:
+ case VOID:
+ case VOLATILE:
+ case IDENTIFIER:
+ case LBRACE:
+ case SEMICOLON:
+ case AT:
+ case LT:
+ ;
+ break;
+ default:
+ jj_la1[26] = jj_gen;
+ break label_12;
+ }
+ member = ClassOrInterfaceBodyDeclaration(isInterface);
+ ret.add(member);
+ }
+ jj_consume_token(RBRACE);
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public BodyDeclaration ClassOrInterfaceBodyDeclaration(boolean isInterface) throws ParseException {
+ boolean isNestedInterface = false;
+ Modifier modifier;
+ BodyDeclaration ret;
+ pushJavadoc();
+ if (jj_2_6(2)) {
+ ret = InitializerDeclaration();
+ if (isInterface)
+ throwParseException(token, "An interface cannot have initializers");
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ABSTRACT:
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case CLASS:
+ case DOUBLE:
+ case ENUM:
+ case FINAL:
+ case FLOAT:
+ case INT:
+ case INTERFACE:
+ case LONG:
+ case NATIVE:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case SHORT:
+ case STATIC:
+ case STRICTFP:
+ case SYNCHRONIZED:
+ case TRANSIENT:
+ case VOID:
+ case VOLATILE:
+ case IDENTIFIER:
+ case AT:
+ case LT:
+ modifier = Modifiers();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CLASS:
+ case INTERFACE:
+ ret = ClassOrInterfaceDeclaration(modifier);
+ break;
+ case ENUM:
+ ret = EnumDeclaration(modifier);
+ break;
+ case AT:
+ ret = AnnotationTypeDeclaration(modifier);
+ break;
+ default:
+ jj_la1[27] = jj_gen;
+ if (jj_2_4(2147483647)) {
+ ret = ConstructorDeclaration(modifier);
+ } else if (jj_2_5(2147483647)) {
+ ret = FieldDeclaration(modifier);
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ case VOID:
+ case IDENTIFIER:
+ case LT:
+ ret = MethodDeclaration(modifier);
+ break;
+ default:
+ jj_la1[28] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+ break;
+ case SEMICOLON:
+ jj_consume_token(SEMICOLON);
+ ret = new EmptyMemberDeclaration(token.beginLine, token.beginColumn, token.endLine, token.endColumn, popJavadoc());
+ break;
+ default:
+ jj_la1[29] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public FieldDeclaration FieldDeclaration(Modifier modifier) throws ParseException {
+ Type type;
+ List variables = new LinkedList();
+ VariableDeclarator val;
+ // Modifiers are already matched in the caller
+ type = Type();
+ val = VariableDeclarator();
+ variables.add(val);
+ label_13:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[30] = jj_gen;
+ break label_13;
+ }
+ jj_consume_token(COMMA);
+ val = VariableDeclarator();
+ variables.add(val);
+ }
+ jj_consume_token(SEMICOLON);
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ if (line == -1) { line=type.getBeginLine(); column=type.getBeginColumn(); }
+ {if (true) return new FieldDeclaration(line, column, token.endLine, token.endColumn, popJavadoc(), modifier.modifiers, modifier.annotations, type, variables);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public VariableDeclarator VariableDeclarator() throws ParseException {
+ VariableDeclaratorId id;
+ Expression init = null;
+ id = VariableDeclaratorId();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ASSIGN:
+ jj_consume_token(ASSIGN);
+ init = VariableInitializer();
+ break;
+ default:
+ jj_la1[31] = jj_gen;
+ ;
+ }
+ {if (true) return new VariableDeclarator(id.getBeginLine(), id.getBeginColumn(), token.endLine, token.endColumn, id, init);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public VariableDeclaratorId VariableDeclaratorId() throws ParseException {
+ String name;
+ int arrayCount = 0;
+ int line;
+ int column;
+ jj_consume_token(IDENTIFIER);
+ name = token.image; line=token.beginLine; column=token.beginColumn;
+ label_14:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LBRACKET:
+ ;
+ break;
+ default:
+ jj_la1[32] = jj_gen;
+ break label_14;
+ }
+ jj_consume_token(LBRACKET);
+ jj_consume_token(RBRACKET);
+ arrayCount++;
+ }
+ {if (true) return new VariableDeclaratorId(line, column, token.endLine, token.endColumn,name, arrayCount);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression VariableInitializer() throws ParseException {
+ Expression ret;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LBRACE:
+ ret = ArrayInitializer();
+ break;
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ ret = Expression();
+ break;
+ default:
+ jj_la1[33] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ArrayInitializerExpr ArrayInitializer() throws ParseException {
+ List values = null;
+ Expression val;
+ int line;
+ int column;
+ jj_consume_token(LBRACE);
+ line=token.beginLine; column=token.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case LBRACE:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ val = VariableInitializer();
+ values = add(values, val);
+ label_15:
+ while (true) {
+ if (jj_2_7(2)) {
+ ;
+ } else {
+ break label_15;
+ }
+ jj_consume_token(COMMA);
+ val = VariableInitializer();
+ values = add(values, val);
+ }
+ break;
+ default:
+ jj_la1[34] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ jj_consume_token(COMMA);
+ break;
+ default:
+ jj_la1[35] = jj_gen;
+ ;
+ }
+ jj_consume_token(RBRACE);
+ {if (true) return new ArrayInitializerExpr(line, column, token.endLine, token.endColumn,values);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public MethodDeclaration MethodDeclaration(Modifier modifier) throws ParseException {
+ List typeParameters = null;
+ Type type;
+ String name;
+ List parameters;
+ int arrayCount = 0;
+ List throws_ = null;
+ BlockStmt block = null;
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LT:
+ typeParameters = TypeParameters();
+ int[] lineCol=(int[])typeParameters.remove(0); if(line==-1){ line=lineCol[0]; column=lineCol[1];}
+ break;
+ default:
+ jj_la1[36] = jj_gen;
+ ;
+ }
+ type = ResultType();
+ if(line==-1){line=type.getBeginLine(); column=type.getBeginColumn();}
+ jj_consume_token(IDENTIFIER);
+ name = token.image;
+ parameters = FormalParameters();
+ label_16:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LBRACKET:
+ ;
+ break;
+ default:
+ jj_la1[37] = jj_gen;
+ break label_16;
+ }
+ jj_consume_token(LBRACKET);
+ jj_consume_token(RBRACKET);
+ arrayCount++;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case THROWS:
+ jj_consume_token(THROWS);
+ throws_ = NameList();
+ break;
+ default:
+ jj_la1[38] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LBRACE:
+ block = Block();
+ break;
+ case SEMICOLON:
+ jj_consume_token(SEMICOLON);
+ break;
+ default:
+ jj_la1[39] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return new MethodDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), modifier.modifiers, modifier.annotations, typeParameters, type, name, parameters, arrayCount, throws_, block);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List FormalParameters() throws ParseException {
+ List ret = null;
+ Parameter par;
+ jj_consume_token(LPAREN);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ABSTRACT:
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FINAL:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NATIVE:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case SHORT:
+ case STATIC:
+ case STRICTFP:
+ case SYNCHRONIZED:
+ case TRANSIENT:
+ case VOLATILE:
+ case IDENTIFIER:
+ case AT:
+ par = FormalParameter();
+ ret = add(ret, par);
+ label_17:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[40] = jj_gen;
+ break label_17;
+ }
+ jj_consume_token(COMMA);
+ par = FormalParameter();
+ ret = add(ret, par);
+ }
+ break;
+ default:
+ jj_la1[41] = jj_gen;
+ ;
+ }
+ jj_consume_token(RPAREN);
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Parameter FormalParameter() throws ParseException {
+ Modifier modifier;
+ Type type;
+ boolean isVarArg = false;
+ VariableDeclaratorId id;
+ modifier = Modifiers();
+ type = Type();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ELLIPSIS:
+ jj_consume_token(ELLIPSIS);
+ isVarArg = true;
+ break;
+ default:
+ jj_la1[42] = jj_gen;
+ ;
+ }
+ id = VariableDeclaratorId();
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ if(line==-1){ line=type.getBeginLine(); column=type.getBeginColumn(); }
+ {if (true) return new Parameter(line, column, token.endLine, token.endColumn, modifier.modifiers, modifier.annotations, type, isVarArg, id);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ConstructorDeclaration ConstructorDeclaration(Modifier modifier) throws ParseException {
+ List typeParameters = null;
+ String name;
+ List parameters;
+ List throws_ = null;
+ ExplicitConstructorInvocationStmt exConsInv = null;
+ List stmts;
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ int bbLine = 0;
+ int bbColumn = 0;
+ int beLine = 0;
+ int beColumn = 0;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LT:
+ typeParameters = TypeParameters();
+ int[] lineCol=(int[])typeParameters.remove(0); if(line==-1){ line=lineCol[0]; column=lineCol[1];}
+ break;
+ default:
+ jj_la1[43] = jj_gen;
+ ;
+ }
+ jj_consume_token(IDENTIFIER);
+ name = token.image; if(line==-1){line=token.beginLine; column=token.beginColumn;}
+ parameters = FormalParameters();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case THROWS:
+ jj_consume_token(THROWS);
+ throws_ = NameList();
+ break;
+ default:
+ jj_la1[44] = jj_gen;
+ ;
+ }
+ jj_consume_token(LBRACE);
+ bbLine=token.beginLine; bbColumn=token.beginColumn;
+ if (jj_2_8(2147483647)) {
+ exConsInv = ExplicitConstructorInvocation();
+ } else {
+ ;
+ }
+ stmts = Statements();
+ jj_consume_token(RBRACE);
+ if (exConsInv != null) {
+ stmts = add(0, stmts, exConsInv);
+ }
+ {if (true) return new ConstructorDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), modifier.modifiers, modifier.annotations, typeParameters, name, parameters, throws_, new BlockStmt(bbLine, bbColumn, token.endLine, token.endColumn, stmts));}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ExplicitConstructorInvocationStmt ExplicitConstructorInvocation() throws ParseException {
+ boolean isThis = false;
+ List args;
+ Expression expr = null;
+ List typeArgs = null;
+ int line = -1;
+ int column = 0;
+ if (jj_2_10(2147483647)) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LT:
+ typeArgs = TypeArguments();
+ int[] lineCol=(int[])typeArgs.remove(0); line=lineCol[0]; column=lineCol[1];
+ break;
+ default:
+ jj_la1[45] = jj_gen;
+ ;
+ }
+ jj_consume_token(THIS);
+ if (line == -1) {line=token.beginLine; column=token.beginColumn;} isThis = true;
+ args = Arguments();
+ jj_consume_token(SEMICOLON);
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case LT:
+ if (jj_2_9(2147483647)) {
+ expr = PrimaryExpressionWithoutSuperSuffix();
+ jj_consume_token(DOT);
+ line=expr.getBeginLine(); column=expr.getBeginColumn();
+ } else {
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LT:
+ typeArgs = TypeArguments();
+ int[] lineCol=(int[])typeArgs.remove(0); if (line == -1) {line=lineCol[0]; column=lineCol[1];}
+ break;
+ default:
+ jj_la1[46] = jj_gen;
+ ;
+ }
+ jj_consume_token(SUPER);
+ if (line == -1) {line=token.beginLine; column=token.beginColumn;}
+ args = Arguments();
+ jj_consume_token(SEMICOLON);
+ break;
+ default:
+ jj_la1[47] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ {if (true) return new ExplicitConstructorInvocationStmt(line, column, token.endLine, token.endColumn,typeArgs, isThis, expr, args);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List Statements() throws ParseException {
+ List ret = null;
+ Statement stmt;
+ label_18:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ABSTRACT:
+ case ASSERT:
+ case BOOLEAN:
+ case BREAK:
+ case BYTE:
+ case CHAR:
+ case CLASS:
+ case CONTINUE:
+ case DO:
+ case DOUBLE:
+ case FALSE:
+ case FINAL:
+ case FLOAT:
+ case FOR:
+ case IF:
+ case INT:
+ case INTERFACE:
+ case LONG:
+ case NATIVE:
+ case NEW:
+ case NULL:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case RETURN:
+ case SHORT:
+ case STATIC:
+ case STRICTFP:
+ case SUPER:
+ case SWITCH:
+ case SYNCHRONIZED:
+ case THIS:
+ case THROW:
+ case TRANSIENT:
+ case TRUE:
+ case TRY:
+ case VOID:
+ case VOLATILE:
+ case WHILE:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case LBRACE:
+ case SEMICOLON:
+ case AT:
+ case INCR:
+ case DECR:
+ ;
+ break;
+ default:
+ jj_la1[48] = jj_gen;
+ break label_18;
+ }
+ stmt = BlockStatement();
+ ret = add(ret, stmt);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public InitializerDeclaration InitializerDeclaration() throws ParseException {
+ BlockStmt block;
+ int line = -1;
+ int column = 0;
+ boolean isStatic = false;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STATIC:
+ jj_consume_token(STATIC);
+ isStatic = true; line=token.beginLine; column=token.beginColumn;
+ break;
+ default:
+ jj_la1[49] = jj_gen;
+ ;
+ }
+ block = Block();
+ if(line==-1){line=block.getBeginLine(); column=block.getBeginColumn();}
+ {if (true) return new InitializerDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), isStatic, block);}
+ throw new Error("Missing return statement in function");
+ }
+
+/*
+ * Type, name and expression syntax follows.
+ */
+ final public Type Type() throws ParseException {
+ Type ret;
+ if (jj_2_11(2)) {
+ ret = ReferenceType();
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ ret = PrimitiveType();
+ break;
+ default:
+ jj_la1[50] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ReferenceType ReferenceType() throws ParseException {
+ Type type;
+ int arrayCount = 0;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ type = PrimitiveType();
+ label_19:
+ while (true) {
+ jj_consume_token(LBRACKET);
+ jj_consume_token(RBRACKET);
+ arrayCount++;
+ if (jj_2_12(2)) {
+ ;
+ } else {
+ break label_19;
+ }
+ }
+ break;
+ case IDENTIFIER:
+ type = ClassOrInterfaceType();
+ label_20:
+ while (true) {
+ if (jj_2_13(2)) {
+ ;
+ } else {
+ break label_20;
+ }
+ jj_consume_token(LBRACKET);
+ jj_consume_token(RBRACKET);
+ arrayCount++;
+ }
+ break;
+ default:
+ jj_la1[51] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return new ReferenceType(type.getBeginLine(), type.getBeginColumn(), token.endLine, token.endColumn, type, arrayCount);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ClassOrInterfaceType ClassOrInterfaceType() throws ParseException {
+ ClassOrInterfaceType ret;
+ String name;
+ List typeArgs = null;
+ int line;
+ int column;
+ jj_consume_token(IDENTIFIER);
+ line=token.beginLine; column=token.beginColumn;
+ name = token.image;
+ if (jj_2_14(2)) {
+ typeArgs = TypeArguments();
+ typeArgs.remove(0);
+ } else {
+ ;
+ }
+ ret = new ClassOrInterfaceType(line, column, token.endLine, token.endColumn,null, name, typeArgs);
+ label_21:
+ while (true) {
+ if (jj_2_15(2)) {
+ ;
+ } else {
+ break label_21;
+ }
+ jj_consume_token(DOT);
+ jj_consume_token(IDENTIFIER);
+ name = token.image;
+ if (jj_2_16(2)) {
+ typeArgs = TypeArguments();
+ typeArgs.remove(0);
+ } else {
+ ;
+ }
+ ret = new ClassOrInterfaceType(line, column, token.endLine, token.endColumn,ret, name, typeArgs);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List TypeArguments() throws ParseException {
+ List ret = new LinkedList();
+ Type type;
+ jj_consume_token(LT);
+ ret.add(new int[]{token.beginLine, token.beginColumn});
+ type = TypeArgument();
+ ret.add(type);
+ label_22:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[52] = jj_gen;
+ break label_22;
+ }
+ jj_consume_token(COMMA);
+ type = TypeArgument();
+ ret.add(type);
+ }
+ jj_consume_token(GT);
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Type TypeArgument() throws ParseException {
+ Type ret;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ case IDENTIFIER:
+ ret = ReferenceType();
+ break;
+ case HOOK:
+ ret = Wildcard();
+ break;
+ default:
+ jj_la1[53] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public WildcardType Wildcard() throws ParseException {
+ ReferenceType ext = null;
+ ReferenceType sup = null;
+ int line;
+ int column;
+ jj_consume_token(HOOK);
+ line=token.beginLine; column=token.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case EXTENDS:
+ case SUPER:
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case EXTENDS:
+ jj_consume_token(EXTENDS);
+ ext = ReferenceType();
+ break;
+ case SUPER:
+ jj_consume_token(SUPER);
+ sup = ReferenceType();
+ break;
+ default:
+ jj_la1[54] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ break;
+ default:
+ jj_la1[55] = jj_gen;
+ ;
+ }
+ {if (true) return new WildcardType(line, column, token.endLine, token.endColumn,ext, sup);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public PrimitiveType PrimitiveType() throws ParseException {
+ PrimitiveType ret;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ jj_consume_token(BOOLEAN);
+ ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Boolean);
+ break;
+ case CHAR:
+ jj_consume_token(CHAR);
+ ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Char);
+ break;
+ case BYTE:
+ jj_consume_token(BYTE);
+ ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Byte);
+ break;
+ case SHORT:
+ jj_consume_token(SHORT);
+ ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Short);
+ break;
+ case INT:
+ jj_consume_token(INT);
+ ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Int);
+ break;
+ case LONG:
+ jj_consume_token(LONG);
+ ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Long);
+ break;
+ case FLOAT:
+ jj_consume_token(FLOAT);
+ ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Float);
+ break;
+ case DOUBLE:
+ jj_consume_token(DOUBLE);
+ ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Double);
+ break;
+ default:
+ jj_la1[56] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Type ResultType() throws ParseException {
+ Type ret;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case VOID:
+ jj_consume_token(VOID);
+ ret = new VoidType(token.beginLine, token.beginColumn, token.endLine, token.endColumn);
+ break;
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ case IDENTIFIER:
+ ret = Type();
+ break;
+ default:
+ jj_la1[57] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public NameExpr Name() throws ParseException {
+ NameExpr ret;
+ jj_consume_token(IDENTIFIER);
+ ret = new NameExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image);
+ label_23:
+ while (true) {
+ if (jj_2_17(2)) {
+ ;
+ } else {
+ break label_23;
+ }
+ jj_consume_token(DOT);
+ jj_consume_token(IDENTIFIER);
+ ret = new QualifiedNameExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, token.image);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List NameList() throws ParseException {
+ List ret = new LinkedList();
+ NameExpr name;
+ name = Name();
+ ret.add(name);
+ label_24:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[58] = jj_gen;
+ break label_24;
+ }
+ jj_consume_token(COMMA);
+ name = Name();
+ ret.add(name);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+/*
+ * Expression syntax follows.
+ */
+ final public Expression Expression() throws ParseException {
+ Expression ret;
+ AssignExpr.Operator op;
+ Expression value;
+ ret = ConditionalExpression();
+ if (jj_2_18(2)) {
+ op = AssignmentOperator();
+ value = Expression();
+ ret = new AssignExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, value, op);
+ } else {
+ ;
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public AssignExpr.Operator AssignmentOperator() throws ParseException {
+ AssignExpr.Operator ret;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ASSIGN:
+ jj_consume_token(ASSIGN);
+ ret = AssignExpr.Operator.assign;
+ break;
+ case STARASSIGN:
+ jj_consume_token(STARASSIGN);
+ ret = AssignExpr.Operator.star;
+ break;
+ case SLASHASSIGN:
+ jj_consume_token(SLASHASSIGN);
+ ret = AssignExpr.Operator.slash;
+ break;
+ case REMASSIGN:
+ jj_consume_token(REMASSIGN);
+ ret = AssignExpr.Operator.rem;
+ break;
+ case PLUSASSIGN:
+ jj_consume_token(PLUSASSIGN);
+ ret = AssignExpr.Operator.plus;
+ break;
+ case MINUSASSIGN:
+ jj_consume_token(MINUSASSIGN);
+ ret = AssignExpr.Operator.minus;
+ break;
+ case LSHIFTASSIGN:
+ jj_consume_token(LSHIFTASSIGN);
+ ret = AssignExpr.Operator.lShift;
+ break;
+ case RSIGNEDSHIFTASSIGN:
+ jj_consume_token(RSIGNEDSHIFTASSIGN);
+ ret = AssignExpr.Operator.rSignedShift;
+ break;
+ case RUNSIGNEDSHIFTASSIGN:
+ jj_consume_token(RUNSIGNEDSHIFTASSIGN);
+ ret = AssignExpr.Operator.rUnsignedShift;
+ break;
+ case ANDASSIGN:
+ jj_consume_token(ANDASSIGN);
+ ret = AssignExpr.Operator.and;
+ break;
+ case XORASSIGN:
+ jj_consume_token(XORASSIGN);
+ ret = AssignExpr.Operator.xor;
+ break;
+ case ORASSIGN:
+ jj_consume_token(ORASSIGN);
+ ret = AssignExpr.Operator.or;
+ break;
+ default:
+ jj_la1[59] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression ConditionalExpression() throws ParseException {
+ Expression ret;
+ Expression left;
+ Expression right;
+ ret = ConditionalOrExpression();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case HOOK:
+ jj_consume_token(HOOK);
+ left = Expression();
+ jj_consume_token(COLON);
+ right = ConditionalExpression();
+ ret = new ConditionalExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, left, right);
+ break;
+ default:
+ jj_la1[60] = jj_gen;
+ ;
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression ConditionalOrExpression() throws ParseException {
+ Expression ret;
+ Expression right;
+ ret = ConditionalAndExpression();
+ label_25:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case SC_OR:
+ ;
+ break;
+ default:
+ jj_la1[61] = jj_gen;
+ break label_25;
+ }
+ jj_consume_token(SC_OR);
+ right = ConditionalAndExpression();
+ ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, BinaryExpr.Operator.or);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression ConditionalAndExpression() throws ParseException {
+ Expression ret;
+ Expression right;
+ ret = InclusiveOrExpression();
+ label_26:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case SC_AND:
+ ;
+ break;
+ default:
+ jj_la1[62] = jj_gen;
+ break label_26;
+ }
+ jj_consume_token(SC_AND);
+ right = InclusiveOrExpression();
+ ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, BinaryExpr.Operator.and);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression InclusiveOrExpression() throws ParseException {
+ Expression ret;
+ Expression right;
+ ret = ExclusiveOrExpression();
+ label_27:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BIT_OR:
+ ;
+ break;
+ default:
+ jj_la1[63] = jj_gen;
+ break label_27;
+ }
+ jj_consume_token(BIT_OR);
+ right = ExclusiveOrExpression();
+ ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, BinaryExpr.Operator.binOr);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression ExclusiveOrExpression() throws ParseException {
+ Expression ret;
+ Expression right;
+ ret = AndExpression();
+ label_28:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case XOR:
+ ;
+ break;
+ default:
+ jj_la1[64] = jj_gen;
+ break label_28;
+ }
+ jj_consume_token(XOR);
+ right = AndExpression();
+ ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, BinaryExpr.Operator.xor);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression AndExpression() throws ParseException {
+ Expression ret;
+ Expression right;
+ ret = EqualityExpression();
+ label_29:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BIT_AND:
+ ;
+ break;
+ default:
+ jj_la1[65] = jj_gen;
+ break label_29;
+ }
+ jj_consume_token(BIT_AND);
+ right = EqualityExpression();
+ ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, BinaryExpr.Operator.binAnd);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression EqualityExpression() throws ParseException {
+ Expression ret;
+ Expression right;
+ BinaryExpr.Operator op;
+ ret = InstanceOfExpression();
+ label_30:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case EQ:
+ case NE:
+ ;
+ break;
+ default:
+ jj_la1[66] = jj_gen;
+ break label_30;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case EQ:
+ jj_consume_token(EQ);
+ op = BinaryExpr.Operator.equals;
+ break;
+ case NE:
+ jj_consume_token(NE);
+ op = BinaryExpr.Operator.notEquals;
+ break;
+ default:
+ jj_la1[67] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ right = InstanceOfExpression();
+ ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, op);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression InstanceOfExpression() throws ParseException {
+ Expression ret;
+ Type type;
+ ret = RelationalExpression();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case INSTANCEOF:
+ jj_consume_token(INSTANCEOF);
+ type = Type();
+ ret = new InstanceOfExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, type);
+ break;
+ default:
+ jj_la1[68] = jj_gen;
+ ;
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression RelationalExpression() throws ParseException {
+ Expression ret;
+ Expression right;
+ BinaryExpr.Operator op;
+ ret = ShiftExpression();
+ label_31:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LT:
+ case LE:
+ case GE:
+ case GT:
+ ;
+ break;
+ default:
+ jj_la1[69] = jj_gen;
+ break label_31;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LT:
+ jj_consume_token(LT);
+ op = BinaryExpr.Operator.less;
+ break;
+ case GT:
+ jj_consume_token(GT);
+ op = BinaryExpr.Operator.greater;
+ break;
+ case LE:
+ jj_consume_token(LE);
+ op = BinaryExpr.Operator.lessEquals;
+ break;
+ case GE:
+ jj_consume_token(GE);
+ op = BinaryExpr.Operator.greaterEquals;
+ break;
+ default:
+ jj_la1[70] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ right = ShiftExpression();
+ ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, op);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression ShiftExpression() throws ParseException {
+ Expression ret;
+ Expression right;
+ BinaryExpr.Operator op;
+ ret = AdditiveExpression();
+ label_32:
+ while (true) {
+ if (jj_2_19(1)) {
+ ;
+ } else {
+ break label_32;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LSHIFT:
+ jj_consume_token(LSHIFT);
+ op = BinaryExpr.Operator.lShift;
+ break;
+ default:
+ jj_la1[71] = jj_gen;
+ if (jj_2_20(1)) {
+ RSIGNEDSHIFT();
+ op = BinaryExpr.Operator.rSignedShift;
+ } else if (jj_2_21(1)) {
+ RUNSIGNEDSHIFT();
+ op = BinaryExpr.Operator.rUnsignedShift;
+ } else {
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ right = AdditiveExpression();
+ ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, op);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression AdditiveExpression() throws ParseException {
+ Expression ret;
+ Expression right;
+ BinaryExpr.Operator op;
+ ret = MultiplicativeExpression();
+ label_33:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case PLUS:
+ case MINUS:
+ ;
+ break;
+ default:
+ jj_la1[72] = jj_gen;
+ break label_33;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case PLUS:
+ jj_consume_token(PLUS);
+ op = BinaryExpr.Operator.plus;
+ break;
+ case MINUS:
+ jj_consume_token(MINUS);
+ op = BinaryExpr.Operator.minus;
+ break;
+ default:
+ jj_la1[73] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ right = MultiplicativeExpression();
+ ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, op);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression MultiplicativeExpression() throws ParseException {
+ Expression ret;
+ Expression right;
+ BinaryExpr.Operator op;
+ ret = UnaryExpression();
+ label_34:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STAR:
+ case SLASH:
+ case REM:
+ ;
+ break;
+ default:
+ jj_la1[74] = jj_gen;
+ break label_34;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STAR:
+ jj_consume_token(STAR);
+ op = BinaryExpr.Operator.times;
+ break;
+ case SLASH:
+ jj_consume_token(SLASH);
+ op = BinaryExpr.Operator.divide;
+ break;
+ case REM:
+ jj_consume_token(REM);
+ op = BinaryExpr.Operator.remainder;
+ break;
+ default:
+ jj_la1[75] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ right = UnaryExpression();
+ ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, op);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression UnaryExpression() throws ParseException {
+ Expression ret;
+ UnaryExpr.Operator op;
+ int line = 0;
+ int column = 0;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case PLUS:
+ case MINUS:
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case PLUS:
+ jj_consume_token(PLUS);
+ op = UnaryExpr.Operator.positive; line=token.beginLine; column=token.beginColumn;
+ break;
+ case MINUS:
+ jj_consume_token(MINUS);
+ op = UnaryExpr.Operator.negative; line=token.beginLine; column=token.beginColumn;
+ break;
+ default:
+ jj_la1[76] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ ret = UnaryExpression();
+ if(op == UnaryExpr.Operator.negative) {
+ if (ret instanceof IntegerLiteralExpr && ((IntegerLiteralExpr)ret).isMinValue()) {
+ ret = new IntegerLiteralMinValueExpr(line, column, token.endLine, token.endColumn);
+ } else if (ret instanceof LongLiteralExpr && ((LongLiteralExpr)ret).isMinValue()) {
+ ret = new LongLiteralMinValueExpr(line, column, token.endLine, token.endColumn);
+ } else {
+ ret = new UnaryExpr(line, column, token.endLine, token.endColumn,ret, op);
+ }
+ } else {
+ ret = new UnaryExpr(line, column, token.endLine, token.endColumn,ret, op);
+ }
+ break;
+ case INCR:
+ ret = PreIncrementExpression();
+ break;
+ case DECR:
+ ret = PreDecrementExpression();
+ break;
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case BANG:
+ case TILDE:
+ ret = UnaryExpressionNotPlusMinus();
+ break;
+ default:
+ jj_la1[77] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression PreIncrementExpression() throws ParseException {
+ Expression ret;
+ int line;
+ int column;
+ jj_consume_token(INCR);
+ line=token.beginLine; column=token.beginColumn;
+ ret = PrimaryExpression();
+ ret = new UnaryExpr(line, column, token.endLine, token.endColumn,ret, UnaryExpr.Operator.preIncrement);
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression PreDecrementExpression() throws ParseException {
+ Expression ret;
+ int line;
+ int column;
+ jj_consume_token(DECR);
+ line=token.beginLine; column=token.beginColumn;
+ ret = PrimaryExpression();
+ ret = new UnaryExpr(line, column, token.endLine, token.endColumn,ret, UnaryExpr.Operator.preDecrement);
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression UnaryExpressionNotPlusMinus() throws ParseException {
+ Expression ret;
+ UnaryExpr.Operator op;
+ int line = 0;
+ int column = 0;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BANG:
+ case TILDE:
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case TILDE:
+ jj_consume_token(TILDE);
+ op = UnaryExpr.Operator.inverse; line=token.beginLine; column=token.beginColumn;
+ break;
+ case BANG:
+ jj_consume_token(BANG);
+ op = UnaryExpr.Operator.not; line=token.beginLine; column=token.beginColumn;
+ break;
+ default:
+ jj_la1[78] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ ret = UnaryExpression();
+ ret = new UnaryExpr(line, column, token.endLine, token.endColumn,ret, op);
+ break;
+ default:
+ jj_la1[79] = jj_gen;
+ if (jj_2_22(2147483647)) {
+ ret = CastExpression();
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ ret = PostfixExpression();
+ break;
+ default:
+ jj_la1[80] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+// This production is to determine lookahead only. The LOOKAHEAD specifications
+// below are not used, but they are there just to indicate that we know about
+// this.
+ final public void CastLookahead() throws ParseException {
+ if (jj_2_23(2147483647)) {
+ jj_consume_token(LPAREN);
+ Type();
+ jj_consume_token(LBRACKET);
+ jj_consume_token(RBRACKET);
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LPAREN:
+ jj_consume_token(LPAREN);
+ Type();
+ jj_consume_token(RPAREN);
+ UnaryExpression();
+ break;
+ default:
+ jj_la1[81] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+
+ final public Expression PostfixExpression() throws ParseException {
+ Expression ret;
+ UnaryExpr.Operator op;
+ ret = PrimaryExpression();
+ if (jj_2_24(2)) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case INCR:
+ jj_consume_token(INCR);
+ op = UnaryExpr.Operator.posIncrement;
+ break;
+ case DECR:
+ jj_consume_token(DECR);
+ op = UnaryExpr.Operator.posDecrement;
+ break;
+ default:
+ jj_la1[82] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ ret = new UnaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, op);
+ } else {
+ ;
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression CastExpression() throws ParseException {
+ Expression ret;
+ Type type;
+ int line;
+ int column;
+ jj_consume_token(LPAREN);
+ line=token.beginLine; column=token.beginColumn;
+ type = Type();
+ jj_consume_token(RPAREN);
+ ret = UnaryExpression();
+ ret = new CastExpr(line, column, token.endLine, token.endColumn,type, ret);
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression PrimaryExpression() throws ParseException {
+ Expression ret;
+ Expression inner;
+ ret = PrimaryPrefix();
+ label_35:
+ while (true) {
+ if (jj_2_25(2)) {
+ ;
+ } else {
+ break label_35;
+ }
+ ret = PrimarySuffix(ret);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression PrimaryExpressionWithoutSuperSuffix() throws ParseException {
+ Expression ret;
+ Expression inner;
+ ret = PrimaryPrefix();
+ label_36:
+ while (true) {
+ if (jj_2_26(2147483647)) {
+ ;
+ } else {
+ break label_36;
+ }
+ ret = PrimarySuffixWithoutSuper(ret);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression PrimaryPrefix() throws ParseException {
+ Expression ret;
+ String name;
+ List typeArgs = null;
+ List args = null;
+ boolean hasArgs = false;
+ Type type;
+ int line;
+ int column;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case FALSE:
+ case NULL:
+ case TRUE:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ ret = Literal();
+ break;
+ case THIS:
+ jj_consume_token(THIS);
+ ret = new ThisExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, null);
+ break;
+ case SUPER:
+ jj_consume_token(SUPER);
+ ret = new SuperExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, null);
+ jj_consume_token(DOT);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LT:
+ typeArgs = TypeArguments();
+ typeArgs.remove(0);
+ break;
+ default:
+ jj_la1[83] = jj_gen;
+ ;
+ }
+ jj_consume_token(IDENTIFIER);
+ name = token.image;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LPAREN:
+ args = Arguments();
+ hasArgs=true;
+ break;
+ default:
+ jj_la1[84] = jj_gen;
+ ;
+ }
+ ret = hasArgs
+ ? new MethodCallExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, typeArgs, name, args)
+ : new FieldAccessExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, null, name);
+ break;
+ case LPAREN:
+ jj_consume_token(LPAREN);
+ line=token.beginLine; column=token.beginColumn;
+ ret = Expression();
+ jj_consume_token(RPAREN);
+ ret = new EnclosedExpr(line, column, token.endLine, token.endColumn,ret);
+ break;
+ case NEW:
+ ret = AllocationExpression(null);
+ break;
+ default:
+ jj_la1[86] = jj_gen;
+ if (jj_2_27(2147483647)) {
+ type = ResultType();
+ jj_consume_token(DOT);
+ jj_consume_token(CLASS);
+ ret = new ClassExpr(type.getBeginLine(), type.getBeginColumn(), token.endLine, token.endColumn, type);
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ jj_consume_token(IDENTIFIER);
+ name = token.image; line=token.beginLine; column=token.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LPAREN:
+ args = Arguments();
+ hasArgs=true;
+ break;
+ default:
+ jj_la1[85] = jj_gen;
+ ;
+ }
+ ret = hasArgs
+ ? new MethodCallExpr(line, column, token.endLine, token.endColumn, null, null, name, args)
+ : new NameExpr(line, column, token.endLine, token.endColumn, name);
+ break;
+ default:
+ jj_la1[87] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression PrimarySuffix(Expression scope) throws ParseException {
+ Expression ret;
+ if (jj_2_28(2)) {
+ ret = PrimarySuffixWithoutSuper(scope);
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case DOT:
+ jj_consume_token(DOT);
+ jj_consume_token(SUPER);
+ ret = new SuperExpr(scope.getBeginLine(), scope.getBeginColumn(), token.endLine, token.endColumn, scope);
+ break;
+ default:
+ jj_la1[88] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression PrimarySuffixWithoutSuper(Expression scope) throws ParseException {
+ Expression ret;
+ List typeArgs = null;
+ List args = null;
+ boolean hasArgs = false;
+ String name;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case DOT:
+ jj_consume_token(DOT);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case THIS:
+ jj_consume_token(THIS);
+ ret = new ThisExpr(scope.getBeginLine(), scope.getBeginColumn(), token.endLine, token.endColumn, scope);
+ break;
+ case NEW:
+ ret = AllocationExpression(scope);
+ break;
+ default:
+ jj_la1[91] = jj_gen;
+ if (jj_2_29(2147483647)) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LT:
+ typeArgs = TypeArguments();
+ typeArgs.remove(0);
+ break;
+ default:
+ jj_la1[89] = jj_gen;
+ ;
+ }
+ jj_consume_token(IDENTIFIER);
+ name = token.image;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LPAREN:
+ args = Arguments();
+ hasArgs=true;
+ break;
+ default:
+ jj_la1[90] = jj_gen;
+ ;
+ }
+ ret = hasArgs
+ ? new MethodCallExpr(scope.getBeginLine(), scope.getBeginColumn(), token.endLine, token.endColumn, scope, typeArgs, name, args)
+ : new FieldAccessExpr(scope.getBeginLine(), scope.getBeginColumn(), token.endLine, token.endColumn, scope, typeArgs, name);
+ } else {
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ break;
+ case LBRACKET:
+ jj_consume_token(LBRACKET);
+ ret = Expression();
+ jj_consume_token(RBRACKET);
+ ret = new ArrayAccessExpr(scope.getBeginLine(), scope.getBeginColumn(), token.endLine, token.endColumn, scope, ret);
+ break;
+ default:
+ jj_la1[92] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression Literal() throws ParseException {
+ Expression ret;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case INTEGER_LITERAL:
+ jj_consume_token(INTEGER_LITERAL);
+ ret = new IntegerLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image);
+ break;
+ case LONG_LITERAL:
+ jj_consume_token(LONG_LITERAL);
+ ret = new LongLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image);
+ break;
+ case FLOATING_POINT_LITERAL:
+ jj_consume_token(FLOATING_POINT_LITERAL);
+ ret = new DoubleLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image);
+ break;
+ case CHARACTER_LITERAL:
+ jj_consume_token(CHARACTER_LITERAL);
+ ret = new CharLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image.substring(1, token.image.length()-1));
+ break;
+ case STRING_LITERAL:
+ jj_consume_token(STRING_LITERAL);
+ ret = new StringLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image.substring(1, token.image.length()-1));
+ break;
+ case FALSE:
+ case TRUE:
+ ret = BooleanLiteral();
+ break;
+ case NULL:
+ ret = NullLiteral();
+ break;
+ default:
+ jj_la1[93] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression BooleanLiteral() throws ParseException {
+ Expression ret;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case TRUE:
+ jj_consume_token(TRUE);
+ ret = new BooleanLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, true);
+ break;
+ case FALSE:
+ jj_consume_token(FALSE);
+ ret = new BooleanLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, false);
+ break;
+ default:
+ jj_la1[94] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression NullLiteral() throws ParseException {
+ jj_consume_token(NULL);
+ {if (true) return new NullLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List Arguments() throws ParseException {
+ List ret = null;
+ jj_consume_token(LPAREN);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ ret = ArgumentList();
+ break;
+ default:
+ jj_la1[95] = jj_gen;
+ ;
+ }
+ jj_consume_token(RPAREN);
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List ArgumentList() throws ParseException {
+ List ret = new LinkedList();
+ Expression expr;
+ expr = Expression();
+ ret.add(expr);
+ label_37:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[96] = jj_gen;
+ break label_37;
+ }
+ jj_consume_token(COMMA);
+ expr = Expression();
+ ret.add(expr);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression AllocationExpression(Expression scope) throws ParseException {
+ Expression ret;
+ Type type;
+ Object[] arr = null;
+ List typeArgs = null;
+ List anonymousBody = null;
+ List args;
+ int line;
+ int column;
+ jj_consume_token(NEW);
+ if(scope==null) {line=token.beginLine; column=token.beginColumn;} else {line=scope.getBeginLine(); column=scope.getBeginColumn();}
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ type = PrimitiveType();
+ arr = ArrayDimsAndInits();
+ if (arr[0] instanceof Integer) {
+ ret = new ArrayCreationExpr(line, column, token.endLine, token.endColumn, type, ((Integer)arr[0]).intValue(), (ArrayInitializerExpr)arr[1]);
+ } else {
+ ret = new ArrayCreationExpr(line, column, token.endLine, token.endColumn, type, (List)arr[0], ((Integer)arr[1]).intValue());
+ }
+ break;
+ default:
+ jj_la1[98] = jj_gen;
+ if (jj_2_31(2147483647)) {
+ type = ClassOrInterfaceType();
+ arr = ArrayDimsAndInits();
+ if (arr[0] instanceof Integer) {
+ ret = new ArrayCreationExpr(line, column, token.endLine, token.endColumn, type, ((Integer)arr[0]).intValue(), (ArrayInitializerExpr)arr[1]);
+ } else {
+ ret = new ArrayCreationExpr(line, column, token.endLine, token.endColumn, type, (List)arr[0], ((Integer)arr[1]).intValue());
+ }
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ case LT:
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LT:
+ typeArgs = TypeArguments();
+ typeArgs.remove(0);
+ break;
+ default:
+ jj_la1[97] = jj_gen;
+ ;
+ }
+ type = ClassOrInterfaceType();
+ args = Arguments();
+ if (jj_2_30(2)) {
+ anonymousBody = ClassOrInterfaceBody(false);
+ } else {
+ ;
+ }
+ ret = new ObjectCreationExpr(line, column, token.endLine, token.endColumn, scope, (ClassOrInterfaceType) type, typeArgs, args, anonymousBody);
+ break;
+ default:
+ jj_la1[99] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+/*
+ * The third LOOKAHEAD specification below is to parse to PrimarySuffix
+ * if there is an expression between the "[...]".
+ */
+ final public Object[] ArrayDimsAndInits() throws ParseException {
+ Object[] ret = new Object[2];
+ Expression expr;
+ List inits = null;
+ int i = 0;
+ if (jj_2_34(2)) {
+ label_38:
+ while (true) {
+ jj_consume_token(LBRACKET);
+ expr = Expression();
+ inits = add(inits, expr);
+ jj_consume_token(RBRACKET);
+ if (jj_2_32(2)) {
+ ;
+ } else {
+ break label_38;
+ }
+ }
+ label_39:
+ while (true) {
+ if (jj_2_33(2)) {
+ ;
+ } else {
+ break label_39;
+ }
+ jj_consume_token(LBRACKET);
+ jj_consume_token(RBRACKET);
+ i++;
+ }
+ ret[0] = inits; ret[1] = new Integer(i);
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LBRACKET:
+ label_40:
+ while (true) {
+ jj_consume_token(LBRACKET);
+ jj_consume_token(RBRACKET);
+ i++;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LBRACKET:
+ ;
+ break;
+ default:
+ jj_la1[100] = jj_gen;
+ break label_40;
+ }
+ }
+ expr = ArrayInitializer();
+ ret[0] = new Integer(i); ret[1] = expr;
+ break;
+ default:
+ jj_la1[101] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+/*
+ * Statement syntax follows.
+ */
+ final public Statement Statement() throws ParseException {
+ Statement ret;
+ if (jj_2_35(2)) {
+ ret = LabeledStatement();
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ASSERT:
+ ret = AssertStatement();
+ break;
+ case LBRACE:
+ ret = Block();
+ break;
+ case SEMICOLON:
+ ret = EmptyStatement();
+ break;
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case INCR:
+ case DECR:
+ ret = StatementExpression();
+ break;
+ case SWITCH:
+ ret = SwitchStatement();
+ break;
+ case IF:
+ ret = IfStatement();
+ break;
+ case WHILE:
+ ret = WhileStatement();
+ break;
+ case DO:
+ ret = DoStatement();
+ break;
+ case FOR:
+ ret = ForStatement();
+ break;
+ case BREAK:
+ ret = BreakStatement();
+ break;
+ case CONTINUE:
+ ret = ContinueStatement();
+ break;
+ case RETURN:
+ ret = ReturnStatement();
+ break;
+ case THROW:
+ ret = ThrowStatement();
+ break;
+ case SYNCHRONIZED:
+ ret = SynchronizedStatement();
+ break;
+ case TRY:
+ ret = TryStatement();
+ break;
+ default:
+ jj_la1[102] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public AssertStmt AssertStatement() throws ParseException {
+ Expression check;
+ Expression msg = null;
+ int line;
+ int column;
+ jj_consume_token(ASSERT);
+ line=token.beginLine; column=token.beginColumn;
+ check = Expression();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COLON:
+ jj_consume_token(COLON);
+ msg = Expression();
+ break;
+ default:
+ jj_la1[103] = jj_gen;
+ ;
+ }
+ jj_consume_token(SEMICOLON);
+ {if (true) return new AssertStmt(line, column, token.endLine, token.endColumn,check, msg);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public LabeledStmt LabeledStatement() throws ParseException {
+ String label;
+ Statement stmt;
+ int line;
+ int column;
+ jj_consume_token(IDENTIFIER);
+ line=token.beginLine; column=token.beginColumn;
+ label = token.image;
+ jj_consume_token(COLON);
+ stmt = Statement();
+ {if (true) return new LabeledStmt(line, column, token.endLine, token.endColumn,label, stmt);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public BlockStmt Block() throws ParseException {
+ List stmts;
+ int beginLine;
+ int beginColumn;
+ jj_consume_token(LBRACE);
+ beginLine=token.beginLine; beginColumn=token.beginColumn;
+ stmts = Statements();
+ jj_consume_token(RBRACE);
+ {if (true) return new BlockStmt(beginLine, beginColumn, token.endLine, token.endColumn, stmts);}
+ throw new Error("Missing return statement in function");
+ }
+
+/*
+ * Classes inside block stametents can only be abstract or final. The semantic must check it.
+ */
+ final public Statement BlockStatement() throws ParseException {
+ Statement ret;
+ Expression expr;
+ ClassOrInterfaceDeclaration typeDecl;
+ Modifier modifier;
+ if (jj_2_36(2147483647)) {
+ pushJavadoc();
+ modifier = Modifiers();
+ typeDecl = ClassOrInterfaceDeclaration(modifier);
+ ret = new TypeDeclarationStmt(typeDecl.getBeginLine(), typeDecl.getBeginColumn(), token.endLine, token.endColumn, typeDecl);
+ } else if (jj_2_37(2147483647)) {
+ expr = VariableDeclarationExpression();
+ jj_consume_token(SEMICOLON);
+ ret = new ExpressionStmt(expr.getBeginLine(), expr.getBeginColumn(), token.endLine, token.endColumn, expr);
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ASSERT:
+ case BOOLEAN:
+ case BREAK:
+ case BYTE:
+ case CHAR:
+ case CONTINUE:
+ case DO:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case FOR:
+ case IF:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case RETURN:
+ case SHORT:
+ case SUPER:
+ case SWITCH:
+ case SYNCHRONIZED:
+ case THIS:
+ case THROW:
+ case TRUE:
+ case TRY:
+ case VOID:
+ case WHILE:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case LBRACE:
+ case SEMICOLON:
+ case INCR:
+ case DECR:
+ ret = Statement();
+ break;
+ default:
+ jj_la1[104] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public VariableDeclarationExpr VariableDeclarationExpression() throws ParseException {
+ Modifier modifier;
+ Type type;
+ List vars = new LinkedList();
+ VariableDeclarator var;
+ modifier = Modifiers();
+ type = Type();
+ var = VariableDeclarator();
+ vars.add(var);
+ label_41:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[105] = jj_gen;
+ break label_41;
+ }
+ jj_consume_token(COMMA);
+ var = VariableDeclarator();
+ vars.add(var);
+ }
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ if(line==-1) {line=type.getBeginLine(); column=type.getBeginColumn(); }
+ {if (true) return new VariableDeclarationExpr(line, column, token.endLine, token.endColumn, modifier.modifiers, modifier.annotations, type, vars);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public EmptyStmt EmptyStatement() throws ParseException {
+ jj_consume_token(SEMICOLON);
+ {if (true) return new EmptyStmt(token.beginLine, token.beginColumn, token.endLine, token.endColumn);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ExpressionStmt StatementExpression() throws ParseException {
+ Expression expr;
+ AssignExpr.Operator op;
+ Expression value;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case INCR:
+ expr = PreIncrementExpression();
+ break;
+ case DECR:
+ expr = PreDecrementExpression();
+ break;
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ expr = PrimaryExpression();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ASSIGN:
+ case INCR:
+ case DECR:
+ case PLUSASSIGN:
+ case MINUSASSIGN:
+ case STARASSIGN:
+ case SLASHASSIGN:
+ case ANDASSIGN:
+ case ORASSIGN:
+ case XORASSIGN:
+ case REMASSIGN:
+ case LSHIFTASSIGN:
+ case RSIGNEDSHIFTASSIGN:
+ case RUNSIGNEDSHIFTASSIGN:
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case INCR:
+ jj_consume_token(INCR);
+ expr = new UnaryExpr(expr.getBeginLine(), expr.getBeginColumn(), token.endLine, token.endColumn, expr, UnaryExpr.Operator.posIncrement);
+ break;
+ case DECR:
+ jj_consume_token(DECR);
+ expr = new UnaryExpr(expr.getBeginLine(), expr.getBeginColumn(), token.endLine, token.endColumn, expr, UnaryExpr.Operator.posDecrement);
+ break;
+ case ASSIGN:
+ case PLUSASSIGN:
+ case MINUSASSIGN:
+ case STARASSIGN:
+ case SLASHASSIGN:
+ case ANDASSIGN:
+ case ORASSIGN:
+ case XORASSIGN:
+ case REMASSIGN:
+ case LSHIFTASSIGN:
+ case RSIGNEDSHIFTASSIGN:
+ case RUNSIGNEDSHIFTASSIGN:
+ op = AssignmentOperator();
+ value = Expression();
+ expr = new AssignExpr(expr.getBeginLine(), expr.getBeginColumn(), token.endLine, token.endColumn, expr, value, op);
+ break;
+ default:
+ jj_la1[106] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ break;
+ default:
+ jj_la1[107] = jj_gen;
+ ;
+ }
+ break;
+ default:
+ jj_la1[108] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ jj_consume_token(SEMICOLON);
+ {if (true) return new ExpressionStmt(expr.getBeginLine(), expr.getBeginColumn(), token.endLine, token.endColumn, expr);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public SwitchStmt SwitchStatement() throws ParseException {
+ Expression selector;
+ SwitchEntryStmt entry;
+ List entries = null;
+ int line;
+ int column;
+ jj_consume_token(SWITCH);
+ line=token.beginLine; column=token.beginColumn;
+ jj_consume_token(LPAREN);
+ selector = Expression();
+ jj_consume_token(RPAREN);
+ jj_consume_token(LBRACE);
+ label_42:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CASE:
+ case _DEFAULT:
+ ;
+ break;
+ default:
+ jj_la1[109] = jj_gen;
+ break label_42;
+ }
+ entry = SwitchEntry();
+ entries = add(entries, entry);
+ }
+ jj_consume_token(RBRACE);
+ {if (true) return new SwitchStmt(line, column, token.endLine, token.endColumn,selector, entries);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public SwitchEntryStmt SwitchEntry() throws ParseException {
+ Expression label = null;
+ List stmts;
+ int line;
+ int column;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CASE:
+ jj_consume_token(CASE);
+ line=token.beginLine; column=token.beginColumn;
+ label = Expression();
+ break;
+ case _DEFAULT:
+ jj_consume_token(_DEFAULT);
+ line=token.beginLine; column=token.beginColumn;
+ break;
+ default:
+ jj_la1[110] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ jj_consume_token(COLON);
+ stmts = Statements();
+ {if (true) return new SwitchEntryStmt(line, column, token.endLine, token.endColumn,label, stmts);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public IfStmt IfStatement() throws ParseException {
+ Expression condition;
+ Statement thenStmt;
+ Statement elseStmt = null;
+ int line;
+ int column;
+ jj_consume_token(IF);
+ line=token.beginLine; column=token.beginColumn;
+ jj_consume_token(LPAREN);
+ condition = Expression();
+ jj_consume_token(RPAREN);
+ thenStmt = Statement();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ELSE:
+ jj_consume_token(ELSE);
+ elseStmt = Statement();
+ break;
+ default:
+ jj_la1[111] = jj_gen;
+ ;
+ }
+ {if (true) return new IfStmt(line, column, token.endLine, token.endColumn,condition, thenStmt, elseStmt);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public WhileStmt WhileStatement() throws ParseException {
+ Expression condition;
+ Statement body;
+ int line;
+ int column;
+ jj_consume_token(WHILE);
+ line=token.beginLine; column=token.beginColumn;
+ jj_consume_token(LPAREN);
+ condition = Expression();
+ jj_consume_token(RPAREN);
+ body = Statement();
+ {if (true) return new WhileStmt(line, column, token.endLine, token.endColumn,condition, body);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public DoStmt DoStatement() throws ParseException {
+ Expression condition;
+ Statement body;
+ int line;
+ int column;
+ jj_consume_token(DO);
+ line=token.beginLine; column=token.beginColumn;
+ body = Statement();
+ jj_consume_token(WHILE);
+ jj_consume_token(LPAREN);
+ condition = Expression();
+ jj_consume_token(RPAREN);
+ jj_consume_token(SEMICOLON);
+ {if (true) return new DoStmt(line, column, token.endLine, token.endColumn,body, condition);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Statement ForStatement() throws ParseException {
+ String id = null;
+ VariableDeclarationExpr varExpr = null;
+ Expression expr = null;
+ List init = null;
+ List update = null;
+ Statement body;
+ int line;
+ int column;
+ jj_consume_token(FOR);
+ line=token.beginLine; column=token.beginColumn;
+ jj_consume_token(LPAREN);
+ if (jj_2_38(2147483647)) {
+ varExpr = VariableDeclarationExpression();
+ jj_consume_token(COLON);
+ expr = Expression();
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ABSTRACT:
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FINAL:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NATIVE:
+ case NEW:
+ case NULL:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case SHORT:
+ case STATIC:
+ case STRICTFP:
+ case SUPER:
+ case SYNCHRONIZED:
+ case THIS:
+ case TRANSIENT:
+ case TRUE:
+ case VOID:
+ case VOLATILE:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case SEMICOLON:
+ case AT:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ABSTRACT:
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FINAL:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NATIVE:
+ case NEW:
+ case NULL:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case SHORT:
+ case STATIC:
+ case STRICTFP:
+ case SUPER:
+ case SYNCHRONIZED:
+ case THIS:
+ case TRANSIENT:
+ case TRUE:
+ case VOID:
+ case VOLATILE:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case AT:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ init = ForInit();
+ break;
+ default:
+ jj_la1[112] = jj_gen;
+ ;
+ }
+ jj_consume_token(SEMICOLON);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ expr = Expression();
+ break;
+ default:
+ jj_la1[113] = jj_gen;
+ ;
+ }
+ jj_consume_token(SEMICOLON);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ update = ForUpdate();
+ break;
+ default:
+ jj_la1[114] = jj_gen;
+ ;
+ }
+ break;
+ default:
+ jj_la1[115] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(RPAREN);
+ body = Statement();
+ if (varExpr != null) {
+ {if (true) return new ForeachStmt(line, column, token.endLine, token.endColumn,varExpr, expr, body);}
+ }
+ {if (true) return new ForStmt(line, column, token.endLine, token.endColumn,init, expr, update, body);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List ForInit() throws ParseException {
+ List ret;
+ Expression expr;
+ if (jj_2_39(2147483647)) {
+ expr = VariableDeclarationExpression();
+ ret = new LinkedList(); ret.add(expr);
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ ret = ExpressionList();
+ break;
+ default:
+ jj_la1[116] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List ExpressionList() throws ParseException {
+ List ret = new LinkedList();
+ Expression expr;
+ expr = Expression();
+ ret.add(expr);
+ label_43:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[117] = jj_gen;
+ break label_43;
+ }
+ jj_consume_token(COMMA);
+ expr = Expression();
+ ret.add(expr);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List ForUpdate() throws ParseException {
+ List ret;
+ ret = ExpressionList();
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public BreakStmt BreakStatement() throws ParseException {
+ String id = null;
+ int line;
+ int column;
+ jj_consume_token(BREAK);
+ line=token.beginLine; column=token.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ jj_consume_token(IDENTIFIER);
+ id = token.image;
+ break;
+ default:
+ jj_la1[118] = jj_gen;
+ ;
+ }
+ jj_consume_token(SEMICOLON);
+ {if (true) return new BreakStmt(line, column, token.endLine, token.endColumn,id);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ContinueStmt ContinueStatement() throws ParseException {
+ String id = null;
+ int line;
+ int column;
+ jj_consume_token(CONTINUE);
+ line=token.beginLine; column=token.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ jj_consume_token(IDENTIFIER);
+ id = token.image;
+ break;
+ default:
+ jj_la1[119] = jj_gen;
+ ;
+ }
+ jj_consume_token(SEMICOLON);
+ {if (true) return new ContinueStmt(line, column, token.endLine, token.endColumn,id);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ReturnStmt ReturnStatement() throws ParseException {
+ Expression expr = null;
+ int line;
+ int column;
+ jj_consume_token(RETURN);
+ line=token.beginLine; column=token.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ expr = Expression();
+ break;
+ default:
+ jj_la1[120] = jj_gen;
+ ;
+ }
+ jj_consume_token(SEMICOLON);
+ {if (true) return new ReturnStmt(line, column, token.endLine, token.endColumn,expr);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ThrowStmt ThrowStatement() throws ParseException {
+ Expression expr;
+ int line;
+ int column;
+ jj_consume_token(THROW);
+ line=token.beginLine; column=token.beginColumn;
+ expr = Expression();
+ jj_consume_token(SEMICOLON);
+ {if (true) return new ThrowStmt(line, column, token.endLine, token.endColumn,expr);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public SynchronizedStmt SynchronizedStatement() throws ParseException {
+ Expression expr;
+ BlockStmt block;
+ int line;
+ int column;
+ jj_consume_token(SYNCHRONIZED);
+ line=token.beginLine; column=token.beginColumn;
+ jj_consume_token(LPAREN);
+ expr = Expression();
+ jj_consume_token(RPAREN);
+ block = Block();
+ {if (true) return new SynchronizedStmt(line, column, token.endLine, token.endColumn,expr, block);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public TryStmt TryStatement() throws ParseException {
+ BlockStmt tryBlock;
+ BlockStmt finallyBlock = null;
+ List catchs = null;
+ Parameter except;
+ BlockStmt catchBlock;
+ int line;
+ int column;
+ int cLine;
+ int cColumn;
+ jj_consume_token(TRY);
+ line=token.beginLine; column=token.beginColumn;
+ tryBlock = Block();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CATCH:
+ label_44:
+ while (true) {
+ jj_consume_token(CATCH);
+ cLine=token.beginLine; cColumn=token.beginColumn;
+ jj_consume_token(LPAREN);
+ except = FormalParameter();
+ jj_consume_token(RPAREN);
+ catchBlock = Block();
+ catchs = add(catchs, new CatchClause(cLine, cColumn, token.endLine, token.endColumn, except, catchBlock));
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CATCH:
+ ;
+ break;
+ default:
+ jj_la1[121] = jj_gen;
+ break label_44;
+ }
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case FINALLY:
+ jj_consume_token(FINALLY);
+ finallyBlock = Block();
+ break;
+ default:
+ jj_la1[122] = jj_gen;
+ ;
+ }
+ break;
+ case FINALLY:
+ jj_consume_token(FINALLY);
+ finallyBlock = Block();
+ break;
+ default:
+ jj_la1[123] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return new TryStmt(line, column, token.endLine, token.endColumn,tryBlock, catchs, finallyBlock);}
+ throw new Error("Missing return statement in function");
+ }
+
+/* We use productions to match >>>, >> and > so that we can keep the
+ * type declaration syntax with generics clean
+ */
+ final public void RUNSIGNEDSHIFT() throws ParseException {
+ if (getToken(1).kind == GT &&
+ ((GTToken)getToken(1)).realKind == RUNSIGNEDSHIFT) {
+
+ } else {
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ jj_consume_token(GT);
+ jj_consume_token(GT);
+ jj_consume_token(GT);
+ }
+
+ final public void RSIGNEDSHIFT() throws ParseException {
+ if (getToken(1).kind == GT &&
+ ((GTToken)getToken(1)).realKind == RSIGNEDSHIFT) {
+
+ } else {
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ jj_consume_token(GT);
+ jj_consume_token(GT);
+ }
+
+/* Annotation syntax follows. */
+ final public AnnotationExpr Annotation() throws ParseException {
+ AnnotationExpr ret;
+ if (jj_2_40(2147483647)) {
+ ret = NormalAnnotation();
+ } else if (jj_2_41(2147483647)) {
+ ret = SingleMemberAnnotation();
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case AT:
+ ret = MarkerAnnotation();
+ break;
+ default:
+ jj_la1[124] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public NormalAnnotationExpr NormalAnnotation() throws ParseException {
+ NameExpr name;
+ List pairs = null;
+ int line;
+ int column;
+ jj_consume_token(AT);
+ line=token.beginLine; column=token.beginColumn;
+ name = Name();
+ jj_consume_token(LPAREN);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ pairs = MemberValuePairs();
+ break;
+ default:
+ jj_la1[125] = jj_gen;
+ ;
+ }
+ jj_consume_token(RPAREN);
+ {if (true) return new NormalAnnotationExpr(line, column, token.endLine, token.endColumn,name, pairs);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public MarkerAnnotationExpr MarkerAnnotation() throws ParseException {
+ NameExpr name;
+ int line;
+ int column;
+ jj_consume_token(AT);
+ line=token.beginLine; column=token.beginColumn;
+ name = Name();
+ {if (true) return new MarkerAnnotationExpr(line, column, token.endLine, token.endColumn,name);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public SingleMemberAnnotationExpr SingleMemberAnnotation() throws ParseException {
+ NameExpr name;
+ Expression memberVal;
+ int line;
+ int column;
+ jj_consume_token(AT);
+ line=token.beginLine; column=token.beginColumn;
+ name = Name();
+ jj_consume_token(LPAREN);
+ memberVal = MemberValue();
+ jj_consume_token(RPAREN);
+ {if (true) return new SingleMemberAnnotationExpr(line, column, token.endLine, token.endColumn,name, memberVal);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List MemberValuePairs() throws ParseException {
+ List ret = new LinkedList();
+ MemberValuePair pair;
+ pair = MemberValuePair();
+ ret.add(pair);
+ label_45:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[126] = jj_gen;
+ break label_45;
+ }
+ jj_consume_token(COMMA);
+ pair = MemberValuePair();
+ ret.add(pair);
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public MemberValuePair MemberValuePair() throws ParseException {
+ String name;
+ Expression value;
+ int line;
+ int column;
+ jj_consume_token(IDENTIFIER);
+ name = token.image; line=token.beginLine; column=token.beginColumn;
+ jj_consume_token(ASSIGN);
+ value = MemberValue();
+ {if (true) return new MemberValuePair(line, column, token.endLine, token.endColumn,name, value);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression MemberValue() throws ParseException {
+ Expression ret;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case AT:
+ ret = Annotation();
+ break;
+ case LBRACE:
+ ret = MemberValueArrayInitializer();
+ break;
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ ret = ConditionalExpression();
+ break;
+ default:
+ jj_la1[127] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression MemberValueArrayInitializer() throws ParseException {
+ List ret = new LinkedList();
+ Expression member;
+ int line;
+ int column;
+ jj_consume_token(LBRACE);
+ line=token.beginLine; column=token.beginColumn;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FALSE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case NEW:
+ case NULL:
+ case SHORT:
+ case SUPER:
+ case THIS:
+ case TRUE:
+ case VOID:
+ case LONG_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case CHARACTER_LITERAL:
+ case STRING_LITERAL:
+ case IDENTIFIER:
+ case LPAREN:
+ case LBRACE:
+ case AT:
+ case BANG:
+ case TILDE:
+ case INCR:
+ case DECR:
+ case PLUS:
+ case MINUS:
+ member = MemberValue();
+ ret.add(member);
+ label_46:
+ while (true) {
+ if (jj_2_42(2)) {
+ ;
+ } else {
+ break label_46;
+ }
+ jj_consume_token(COMMA);
+ member = MemberValue();
+ ret.add(member);
+ }
+ break;
+ default:
+ jj_la1[128] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ jj_consume_token(COMMA);
+ break;
+ default:
+ jj_la1[129] = jj_gen;
+ ;
+ }
+ jj_consume_token(RBRACE);
+ {if (true) return new ArrayInitializerExpr(line, column, token.endLine, token.endColumn,ret);}
+ throw new Error("Missing return statement in function");
+ }
+
+/* Annotation Types. */
+ final public AnnotationDeclaration AnnotationTypeDeclaration(Modifier modifier) throws ParseException {
+ String name;
+ List members;
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ jj_consume_token(AT);
+ if (line == -1) {line=token.beginLine; column=token.beginColumn;}
+ jj_consume_token(INTERFACE);
+ jj_consume_token(IDENTIFIER);
+ name = token.image;
+ members = AnnotationTypeBody();
+ {if (true) return new AnnotationDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), modifier.modifiers, modifier.annotations, name, members);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public List AnnotationTypeBody() throws ParseException {
+ List ret = null;
+ BodyDeclaration member;
+ jj_consume_token(LBRACE);
+ label_47:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ABSTRACT:
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case CLASS:
+ case DOUBLE:
+ case ENUM:
+ case FINAL:
+ case FLOAT:
+ case INT:
+ case INTERFACE:
+ case LONG:
+ case NATIVE:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case SHORT:
+ case STATIC:
+ case STRICTFP:
+ case SYNCHRONIZED:
+ case TRANSIENT:
+ case VOLATILE:
+ case IDENTIFIER:
+ case SEMICOLON:
+ case AT:
+ ;
+ break;
+ default:
+ jj_la1[130] = jj_gen;
+ break label_47;
+ }
+ member = AnnotationBodyDeclaration();
+ ret = add(ret, member);
+ }
+ jj_consume_token(RBRACE);
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public BodyDeclaration AnnotationBodyDeclaration() throws ParseException {
+ Modifier modifier;
+ BodyDeclaration ret;
+ pushJavadoc();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case SEMICOLON:
+ jj_consume_token(SEMICOLON);
+ ret = new EmptyTypeDeclaration(token.beginLine, token.beginColumn, token.endLine, token.endColumn, popJavadoc());
+ break;
+ case ABSTRACT:
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case CLASS:
+ case DOUBLE:
+ case ENUM:
+ case FINAL:
+ case FLOAT:
+ case INT:
+ case INTERFACE:
+ case LONG:
+ case NATIVE:
+ case PRIVATE:
+ case PROTECTED:
+ case PUBLIC:
+ case SHORT:
+ case STATIC:
+ case STRICTFP:
+ case SYNCHRONIZED:
+ case TRANSIENT:
+ case VOLATILE:
+ case IDENTIFIER:
+ case AT:
+ modifier = Modifiers();
+ if (jj_2_43(2147483647)) {
+ ret = AnnotationTypeMemberDeclaration(modifier);
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CLASS:
+ case INTERFACE:
+ ret = ClassOrInterfaceDeclaration(modifier);
+ break;
+ case ENUM:
+ ret = EnumDeclaration(modifier);
+ break;
+ case AT:
+ ret = AnnotationTypeDeclaration(modifier);
+ break;
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ case IDENTIFIER:
+ ret = FieldDeclaration(modifier);
+ break;
+ default:
+ jj_la1[131] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ break;
+ default:
+ jj_la1[132] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public AnnotationMemberDeclaration AnnotationTypeMemberDeclaration(Modifier modifier) throws ParseException {
+ Type type;
+ String name;
+ Expression defaultVal = null;
+ type = Type();
+ jj_consume_token(IDENTIFIER);
+ name = token.image;
+ jj_consume_token(LPAREN);
+ jj_consume_token(RPAREN);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case _DEFAULT:
+ defaultVal = DefaultValue();
+ break;
+ default:
+ jj_la1[133] = jj_gen;
+ ;
+ }
+ jj_consume_token(SEMICOLON);
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ { if (line == -1) {line=type.getBeginLine(); column=type.getBeginColumn();} }
+ {if (true) return new AnnotationMemberDeclaration(line, column, token.endLine, token.endColumn, popJavadoc(), modifier.modifiers, modifier.annotations, type, name, defaultVal);}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Expression DefaultValue() throws ParseException {
+ Expression ret;
+ jj_consume_token(_DEFAULT);
+ ret = MemberValue();
+ {if (true) return ret;}
+ throw new Error("Missing return statement in function");
+ }
+
+ private boolean jj_2_1(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_1(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(0, xla); }
+ }
+
+ private boolean jj_2_2(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_2(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(1, xla); }
+ }
+
+ private boolean jj_2_3(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_3(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(2, xla); }
+ }
+
+ private boolean jj_2_4(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_4(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(3, xla); }
+ }
+
+ private boolean jj_2_5(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_5(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(4, xla); }
+ }
+
+ private boolean jj_2_6(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_6(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(5, xla); }
+ }
+
+ private boolean jj_2_7(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_7(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(6, xla); }
+ }
+
+ private boolean jj_2_8(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_8(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(7, xla); }
+ }
+
+ private boolean jj_2_9(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_9(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(8, xla); }
+ }
+
+ private boolean jj_2_10(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_10(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(9, xla); }
+ }
+
+ private boolean jj_2_11(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_11(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(10, xla); }
+ }
+
+ private boolean jj_2_12(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_12(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(11, xla); }
+ }
+
+ private boolean jj_2_13(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_13(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(12, xla); }
+ }
+
+ private boolean jj_2_14(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_14(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(13, xla); }
+ }
+
+ private boolean jj_2_15(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_15(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(14, xla); }
+ }
+
+ private boolean jj_2_16(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_16(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(15, xla); }
+ }
+
+ private boolean jj_2_17(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_17(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(16, xla); }
+ }
+
+ private boolean jj_2_18(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_18(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(17, xla); }
+ }
+
+ private boolean jj_2_19(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_19(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(18, xla); }
+ }
+
+ private boolean jj_2_20(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_20(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(19, xla); }
+ }
+
+ private boolean jj_2_21(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_21(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(20, xla); }
+ }
+
+ private boolean jj_2_22(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_22(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(21, xla); }
+ }
+
+ private boolean jj_2_23(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_23(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(22, xla); }
+ }
+
+ private boolean jj_2_24(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_24(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(23, xla); }
+ }
+
+ private boolean jj_2_25(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_25(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(24, xla); }
+ }
+
+ private boolean jj_2_26(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_26(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(25, xla); }
+ }
+
+ private boolean jj_2_27(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_27(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(26, xla); }
+ }
+
+ private boolean jj_2_28(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_28(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(27, xla); }
+ }
+
+ private boolean jj_2_29(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_29(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(28, xla); }
+ }
+
+ private boolean jj_2_30(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_30(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(29, xla); }
+ }
+
+ private boolean jj_2_31(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_31(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(30, xla); }
+ }
+
+ private boolean jj_2_32(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_32(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(31, xla); }
+ }
+
+ private boolean jj_2_33(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_33(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(32, xla); }
+ }
+
+ private boolean jj_2_34(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_34(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(33, xla); }
+ }
+
+ private boolean jj_2_35(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_35(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(34, xla); }
+ }
+
+ private boolean jj_2_36(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_36(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(35, xla); }
+ }
+
+ private boolean jj_2_37(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_37(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(36, xla); }
+ }
+
+ private boolean jj_2_38(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_38(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(37, xla); }
+ }
+
+ private boolean jj_2_39(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_39(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(38, xla); }
+ }
+
+ private boolean jj_2_40(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_40(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(39, xla); }
+ }
+
+ private boolean jj_2_41(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_41(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(40, xla); }
+ }
+
+ private boolean jj_2_42(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_42(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(41, xla); }
+ }
+
+ private boolean jj_2_43(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_43(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(42, xla); }
+ }
+
+ private boolean jj_3R_100() {
+ if (jj_3R_145()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_168() {
+ if (jj_scan_token(ASSIGN)) return true;
+ if (jj_3R_66()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_207() {
+ if (jj_scan_token(CHARACTER_LITERAL)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_206() {
+ if (jj_scan_token(FLOATING_POINT_LITERAL)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_66() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_100()) {
+ jj_scanpos = xsp;
+ if (jj_3R_101()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_205() {
+ if (jj_scan_token(LONG_LITERAL)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_361() {
+ if (jj_3R_372()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_204() {
+ if (jj_scan_token(INTEGER_LITERAL)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_83() {
+ if (jj_3R_71()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_167() {
+ if (jj_scan_token(IDENTIFIER)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_200()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3_29() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_83()) jj_scanpos = xsp;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_64() {
+ if (jj_scan_token(LBRACKET)) return true;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_184() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_204()) {
+ jj_scanpos = xsp;
+ if (jj_3R_205()) {
+ jj_scanpos = xsp;
+ if (jj_3R_206()) {
+ jj_scanpos = xsp;
+ if (jj_3R_207()) {
+ jj_scanpos = xsp;
+ if (jj_3R_208()) {
+ jj_scanpos = xsp;
+ if (jj_3R_209()) {
+ jj_scanpos = xsp;
+ if (jj_3R_210()) return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_128() {
+ if (jj_scan_token(LBRACKET)) return true;
+ if (jj_3R_73()) return true;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_196() {
+ if (jj_3R_147()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_195() {
+ if (jj_3R_71()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_133() {
+ if (jj_3R_167()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_168()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_164() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_195()) jj_scanpos = xsp;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ xsp = jj_scanpos;
+ if (jj_3R_196()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_163() {
+ if (jj_3R_187()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_173() {
+ if (jj_3R_201()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_162() {
+ if (jj_scan_token(THIS)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_321() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_133()) return true;
+ return false;
+ }
+
+ private boolean jj_3_5() {
+ if (jj_3R_63()) return true;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_64()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_scan_token(87)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(90)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(86)) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_62() {
+ if (jj_3R_96()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_127() {
+ if (jj_scan_token(DOT)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_162()) {
+ jj_scanpos = xsp;
+ if (jj_3R_163()) {
+ jj_scanpos = xsp;
+ if (jj_3R_164()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3_4() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_62()) jj_scanpos = xsp;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_247() {
+ if (jj_3R_63()) return true;
+ if (jj_3R_133()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_321()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_226() {
+ if (jj_3R_248()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_81() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_127()) {
+ jj_scanpos = xsp;
+ if (jj_3R_128()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_198() {
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_225() {
+ if (jj_3R_247()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_224() {
+ if (jj_3R_246()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_372() {
+ if (jj_scan_token(_DEFAULT)) return true;
+ if (jj_3R_92()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_223() {
+ if (jj_3R_245()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_126() {
+ if (jj_scan_token(DOT)) return true;
+ if (jj_scan_token(SUPER)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_222() {
+ if (jj_3R_244()) return true;
+ return false;
+ }
+
+ private boolean jj_3_28() {
+ if (jj_3R_81()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_221() {
+ if (jj_3R_243()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_188() {
+ if (jj_3R_147()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_80() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_28()) {
+ jj_scanpos = xsp;
+ if (jj_3R_126()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3_27() {
+ if (jj_3R_82()) return true;
+ if (jj_scan_token(DOT)) return true;
+ if (jj_scan_token(CLASS)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_197() {
+ if (jj_3R_88()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_221()) {
+ jj_scanpos = xsp;
+ if (jj_3R_222()) {
+ jj_scanpos = xsp;
+ if (jj_3R_223()) {
+ jj_scanpos = xsp;
+ if (jj_3R_224()) {
+ jj_scanpos = xsp;
+ if (jj_3R_225()) {
+ jj_scanpos = xsp;
+ if (jj_3R_226()) return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_156() {
+ if (jj_scan_token(IDENTIFIER)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_188()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_360() {
+ if (jj_3R_63()) return true;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_361()) jj_scanpos = xsp;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3_43() {
+ if (jj_3R_63()) return true;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_356() {
+ if (jj_3R_247()) return true;
+ return false;
+ }
+
+ private boolean jj_3_6() {
+ if (jj_3R_65()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_355() {
+ if (jj_3R_245()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_155() {
+ if (jj_3R_82()) return true;
+ if (jj_scan_token(DOT)) return true;
+ if (jj_scan_token(CLASS)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_354() {
+ if (jj_3R_244()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_154() {
+ if (jj_3R_187()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_353() {
+ if (jj_3R_243()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_153() {
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_73()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_352() {
+ if (jj_3R_360()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_165() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_6()) {
+ jj_scanpos = xsp;
+ if (jj_3R_197()) {
+ jj_scanpos = xsp;
+ if (jj_3R_198()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3_42() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_92()) return true;
+ return false;
+ }
+
+ private boolean jj_3_26() {
+ if (jj_3R_81()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_186() {
+ if (jj_3R_147()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_185() {
+ if (jj_3R_71()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_131() {
+ if (jj_3R_165()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_346() {
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_347() {
+ if (jj_3R_88()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_352()) {
+ jj_scanpos = xsp;
+ if (jj_3R_353()) {
+ jj_scanpos = xsp;
+ if (jj_3R_354()) {
+ jj_scanpos = xsp;
+ if (jj_3R_355()) {
+ jj_scanpos = xsp;
+ if (jj_3R_356()) return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_152() {
+ if (jj_scan_token(SUPER)) return true;
+ if (jj_scan_token(DOT)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_185()) jj_scanpos = xsp;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ xsp = jj_scanpos;
+ if (jj_3R_186()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_151() {
+ if (jj_scan_token(THIS)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_84() {
+ if (jj_scan_token(LBRACE)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_131()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(RBRACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_150() {
+ if (jj_3R_184()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_105() {
+ if (jj_3R_81()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_341() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_346()) {
+ jj_scanpos = xsp;
+ if (jj_3R_347()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3_3() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_61()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_228() {
+ if (jj_scan_token(BIT_AND)) return true;
+ if (jj_3R_85()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_330() {
+ if (jj_3R_341()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_201() {
+ if (jj_scan_token(EXTENDS)) return true;
+ if (jj_3R_85()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_228()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_104() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_150()) {
+ jj_scanpos = xsp;
+ if (jj_3R_151()) {
+ jj_scanpos = xsp;
+ if (jj_3R_152()) {
+ jj_scanpos = xsp;
+ if (jj_3R_153()) {
+ jj_scanpos = xsp;
+ if (jj_3R_154()) {
+ jj_scanpos = xsp;
+ if (jj_3R_155()) {
+ jj_scanpos = xsp;
+ if (jj_3R_156()) return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3_25() {
+ if (jj_3R_80()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_317() {
+ if (jj_scan_token(LBRACE)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_330()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(RBRACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_141() {
+ if (jj_scan_token(IDENTIFIER)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_173()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_340() {
+ if (jj_3R_84()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_68() {
+ if (jj_3R_104()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_105()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_245() {
+ if (jj_scan_token(AT)) return true;
+ if (jj_scan_token(INTERFACE)) return true;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ if (jj_3R_317()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_142() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_141()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_96() {
+ if (jj_scan_token(LT)) return true;
+ if (jj_3R_141()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_142()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(GT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_265() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_264()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_238() {
+ if (jj_3R_104()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_25()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_283() {
+ if (jj_3R_92()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_42()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_227() {
+ if (jj_3R_249()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_169() {
+ if (jj_scan_token(LBRACE)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_283()) jj_scanpos = xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(87)) jj_scanpos = xsp;
+ if (jj_scan_token(RBRACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_339() {
+ if (jj_3R_147()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_329() {
+ if (jj_3R_165()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_95() {
+ if (jj_3R_94()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_258() {
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_63()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ if (jj_3R_161()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_137() {
+ if (jj_3R_121()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_61() {
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_95()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(IDENTIFIER)) return true;
+ xsp = jj_scanpos;
+ if (jj_3R_339()) jj_scanpos = xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_340()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_136() {
+ if (jj_3R_169()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_135() {
+ if (jj_3R_94()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_316() {
+ if (jj_scan_token(SEMICOLON)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_329()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_79() {
+ if (jj_scan_token(DECR)) return true;
+ return false;
+ }
+
+ private boolean jj_3_24() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_78()) {
+ jj_scanpos = xsp;
+ if (jj_3R_79()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_78() {
+ if (jj_scan_token(INCR)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_92() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_135()) {
+ jj_scanpos = xsp;
+ if (jj_3R_136()) {
+ jj_scanpos = xsp;
+ if (jj_3R_137()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_259() {
+ if (jj_3R_238()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_24()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3_23() {
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_63()) return true;
+ if (jj_scan_token(LBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_315() {
+ if (jj_3R_61()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_3()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_264() {
+ if (jj_scan_token(IDENTIFIER)) return true;
+ if (jj_scan_token(ASSIGN)) return true;
+ if (jj_3R_92()) return true;
+ return false;
+ }
+
+ private boolean jj_3_22() {
+ if (jj_3R_77()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_125() {
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_63()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ if (jj_3R_161()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_314() {
+ if (jj_3R_328()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_124() {
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_63()) return true;
+ if (jj_scan_token(LBRACKET)) return true;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_77() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_124()) {
+ jj_scanpos = xsp;
+ if (jj_3R_125()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_244() {
+ if (jj_scan_token(ENUM)) return true;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_314()) jj_scanpos = xsp;
+ if (jj_scan_token(LBRACE)) return true;
+ xsp = jj_scanpos;
+ if (jj_3R_315()) jj_scanpos = xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(87)) jj_scanpos = xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_316()) jj_scanpos = xsp;
+ if (jj_scan_token(RBRACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_241() {
+ if (jj_3R_259()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_249() {
+ if (jj_3R_264()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_265()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_240() {
+ if (jj_3R_258()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_257() {
+ if (jj_scan_token(BANG)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_256() {
+ if (jj_scan_token(TILDE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_239() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_256()) {
+ jj_scanpos = xsp;
+ if (jj_3R_257()) return true;
+ }
+ if (jj_3R_161()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_171() {
+ if (jj_scan_token(AT)) return true;
+ if (jj_3R_90()) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_92()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_338() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_85()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_219() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_239()) {
+ jj_scanpos = xsp;
+ if (jj_3R_240()) {
+ jj_scanpos = xsp;
+ if (jj_3R_241()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_91() {
+ if (jj_scan_token(IDENTIFIER)) return true;
+ if (jj_scan_token(ASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_328() {
+ if (jj_scan_token(IMPLEMENTS)) return true;
+ if (jj_3R_85()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_338()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_172() {
+ if (jj_scan_token(AT)) return true;
+ if (jj_3R_90()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_218() {
+ if (jj_scan_token(DECR)) return true;
+ if (jj_3R_238()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_337() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_85()) return true;
+ return false;
+ }
+
+ private boolean jj_3_41() {
+ if (jj_scan_token(AT)) return true;
+ if (jj_3R_90()) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_327() {
+ if (jj_scan_token(EXTENDS)) return true;
+ if (jj_3R_85()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_337()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3_40() {
+ if (jj_scan_token(AT)) return true;
+ if (jj_3R_90()) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_91()) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(81)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_170() {
+ if (jj_scan_token(AT)) return true;
+ if (jj_3R_90()) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_227()) jj_scanpos = xsp;
+ if (jj_scan_token(RPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_217() {
+ if (jj_scan_token(INCR)) return true;
+ if (jj_3R_238()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_261() {
+ if (jj_scan_token(INTERFACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_140() {
+ if (jj_3R_172()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_194() {
+ if (jj_3R_219()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_139() {
+ if (jj_3R_171()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_193() {
+ if (jj_3R_218()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_313() {
+ if (jj_3R_328()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_138() {
+ if (jj_3R_170()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_312() {
+ if (jj_3R_327()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_192() {
+ if (jj_3R_217()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_311() {
+ if (jj_3R_96()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_243() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(20)) {
+ jj_scanpos = xsp;
+ if (jj_3R_261()) return true;
+ }
+ if (jj_scan_token(IDENTIFIER)) return true;
+ xsp = jj_scanpos;
+ if (jj_3R_311()) jj_scanpos = xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_312()) jj_scanpos = xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_313()) jj_scanpos = xsp;
+ if (jj_3R_84()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_94() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_138()) {
+ jj_scanpos = xsp;
+ if (jj_3R_139()) {
+ jj_scanpos = xsp;
+ if (jj_3R_140()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_122() {
+ return false;
+ }
+
+ private boolean jj_3R_216() {
+ if (jj_scan_token(MINUS)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_215() {
+ if (jj_scan_token(PLUS)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_191() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_215()) {
+ jj_scanpos = xsp;
+ if (jj_3R_216()) return true;
+ }
+ if (jj_3R_161()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_123() {
+ return false;
+ }
+
+ private boolean jj_3R_75() {
+ jj_lookingAhead = true;
+ jj_semLA = getToken(1).kind == GT &&
+ ((GTToken)getToken(1)).realKind == RSIGNEDSHIFT;
+ jj_lookingAhead = false;
+ if (!jj_semLA || jj_3R_122()) return true;
+ if (jj_scan_token(GT)) return true;
+ if (jj_scan_token(GT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_161() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_191()) {
+ jj_scanpos = xsp;
+ if (jj_3R_192()) {
+ jj_scanpos = xsp;
+ if (jj_3R_193()) {
+ jj_scanpos = xsp;
+ if (jj_3R_194()) return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_378() {
+ if (jj_scan_token(CATCH)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_342()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ if (jj_3R_99()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_379() {
+ if (jj_scan_token(FINALLY)) return true;
+ if (jj_3R_99()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_371() {
+ if (jj_scan_token(FINALLY)) return true;
+ if (jj_3R_99()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_76() {
+ jj_lookingAhead = true;
+ jj_semLA = getToken(1).kind == GT &&
+ ((GTToken)getToken(1)).realKind == RUNSIGNEDSHIFT;
+ jj_lookingAhead = false;
+ if (!jj_semLA || jj_3R_123()) return true;
+ if (jj_scan_token(GT)) return true;
+ if (jj_scan_token(GT)) return true;
+ if (jj_scan_token(GT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_359() {
+ if (jj_scan_token(REM)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_358() {
+ if (jj_scan_token(SLASH)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_357() {
+ if (jj_scan_token(STAR)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_349() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_357()) {
+ jj_scanpos = xsp;
+ if (jj_3R_358()) {
+ jj_scanpos = xsp;
+ if (jj_3R_359()) return true;
+ }
+ }
+ if (jj_3R_161()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_369() {
+ if (jj_3R_73()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_370() {
+ Token xsp;
+ if (jj_3R_378()) return true;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_378()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_379()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_325() {
+ if (jj_3R_161()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_349()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_368() {
+ if (jj_scan_token(IDENTIFIER)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_351() {
+ if (jj_scan_token(MINUS)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_350() {
+ if (jj_scan_token(PLUS)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_60() {
+ if (jj_3R_94()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_345() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_350()) {
+ jj_scanpos = xsp;
+ if (jj_3R_351()) return true;
+ }
+ if (jj_3R_325()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_59() {
+ if (jj_scan_token(STRICTFP)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_297() {
+ if (jj_scan_token(TRY)) return true;
+ if (jj_3R_99()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_370()) {
+ jj_scanpos = xsp;
+ if (jj_3R_371()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_58() {
+ if (jj_scan_token(VOLATILE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_57() {
+ if (jj_scan_token(TRANSIENT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_56() {
+ if (jj_scan_token(NATIVE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_367() {
+ if (jj_scan_token(IDENTIFIER)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_307() {
+ if (jj_3R_325()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_345()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_55() {
+ if (jj_scan_token(SYNCHRONIZED)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_54() {
+ if (jj_scan_token(ABSTRACT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_53() {
+ if (jj_scan_token(FINAL)) return true;
+ return false;
+ }
+
+ private boolean jj_3_21() {
+ if (jj_3R_76()) return true;
+ return false;
+ }
+
+ private boolean jj_3_20() {
+ if (jj_3R_75()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_52() {
+ if (jj_scan_token(PRIVATE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_74() {
+ if (jj_scan_token(LSHIFT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_51() {
+ if (jj_scan_token(PROTECTED)) return true;
+ return false;
+ }
+
+ private boolean jj_3_19() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_74()) {
+ jj_scanpos = xsp;
+ if (jj_3_20()) {
+ jj_scanpos = xsp;
+ if (jj_3_21()) return true;
+ }
+ }
+ if (jj_3R_307()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_50() {
+ if (jj_scan_token(STATIC)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_49() {
+ if (jj_scan_token(PUBLIC)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_308() {
+ if (jj_scan_token(INSTANCEOF)) return true;
+ if (jj_3R_63()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_296() {
+ if (jj_scan_token(SYNCHRONIZED)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_73()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ if (jj_3R_99()) return true;
+ return false;
+ }
+
+ private boolean jj_3_2() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_49()) {
+ jj_scanpos = xsp;
+ if (jj_3R_50()) {
+ jj_scanpos = xsp;
+ if (jj_3R_51()) {
+ jj_scanpos = xsp;
+ if (jj_3R_52()) {
+ jj_scanpos = xsp;
+ if (jj_3R_53()) {
+ jj_scanpos = xsp;
+ if (jj_3R_54()) {
+ jj_scanpos = xsp;
+ if (jj_3R_55()) {
+ jj_scanpos = xsp;
+ if (jj_3R_56()) {
+ jj_scanpos = xsp;
+ if (jj_3R_57()) {
+ jj_scanpos = xsp;
+ if (jj_3R_58()) {
+ jj_scanpos = xsp;
+ if (jj_3R_59()) {
+ jj_scanpos = xsp;
+ if (jj_3R_60()) return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_305() {
+ if (jj_3R_307()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_19()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_88() {
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_2()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_336() {
+ if (jj_scan_token(GE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_335() {
+ if (jj_scan_token(LE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_334() {
+ if (jj_scan_token(GT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_333() {
+ if (jj_scan_token(LT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_326() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_333()) {
+ jj_scanpos = xsp;
+ if (jj_3R_334()) {
+ jj_scanpos = xsp;
+ if (jj_3R_335()) {
+ jj_scanpos = xsp;
+ if (jj_3R_336()) return true;
+ }
+ }
+ }
+ if (jj_3R_305()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_295() {
+ if (jj_scan_token(THROW)) return true;
+ if (jj_3R_73()) return true;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_303() {
+ if (jj_3R_305()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_326()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_294() {
+ if (jj_scan_token(RETURN)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_369()) jj_scanpos = xsp;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_390() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_73()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_304() {
+ if (jj_scan_token(BIT_AND)) return true;
+ if (jj_3R_281()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_298() {
+ if (jj_3R_303()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_308()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_364() {
+ if (jj_scan_token(ELSE)) return true;
+ if (jj_3R_250()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_293() {
+ if (jj_scan_token(CONTINUE)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_368()) jj_scanpos = xsp;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_93() {
+ if (jj_3R_94()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_48() {
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_93()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(PACKAGE)) return true;
+ if (jj_3R_90()) return true;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_310() {
+ if (jj_scan_token(NE)) return true;
+ return false;
+ }
+
+ private boolean jj_3_1() {
+ if (jj_3R_48()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_309() {
+ if (jj_scan_token(EQ)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_306() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_309()) {
+ jj_scanpos = xsp;
+ if (jj_3R_310()) return true;
+ }
+ if (jj_3R_298()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_377() {
+ if (jj_3R_386()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_299() {
+ if (jj_scan_token(XOR)) return true;
+ if (jj_3R_255()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_292() {
+ if (jj_scan_token(BREAK)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_367()) jj_scanpos = xsp;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_282() {
+ if (jj_scan_token(BIT_OR)) return true;
+ if (jj_3R_237()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_281() {
+ if (jj_3R_298()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_306()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_260() {
+ if (jj_scan_token(SC_AND)) return true;
+ if (jj_3R_214()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_386() {
+ if (jj_3R_389()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_255() {
+ if (jj_3R_281()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_304()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_242() {
+ if (jj_scan_token(SC_OR)) return true;
+ if (jj_3R_190()) return true;
+ return false;
+ }
+
+ private boolean jj_3_39() {
+ if (jj_3R_88()) return true;
+ if (jj_3R_63()) return true;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_389() {
+ if (jj_3R_73()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_390()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_237() {
+ if (jj_3R_255()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_299()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_388() {
+ if (jj_3R_389()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_376() {
+ if (jj_3R_73()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_387() {
+ if (jj_3R_89()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_214() {
+ if (jj_3R_237()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_282()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_385() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_387()) {
+ jj_scanpos = xsp;
+ if (jj_3R_388()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_190() {
+ if (jj_3R_214()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_260()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3_38() {
+ if (jj_3R_89()) return true;
+ if (jj_scan_token(COLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_375() {
+ if (jj_3R_385()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_160() {
+ if (jj_3R_190()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_242()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_366() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_375()) jj_scanpos = xsp;
+ if (jj_scan_token(SEMICOLON)) return true;
+ xsp = jj_scanpos;
+ if (jj_3R_376()) jj_scanpos = xsp;
+ if (jj_scan_token(SEMICOLON)) return true;
+ xsp = jj_scanpos;
+ if (jj_3R_377()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_365() {
+ if (jj_3R_89()) return true;
+ if (jj_scan_token(COLON)) return true;
+ if (jj_3R_73()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_220() {
+ if (jj_scan_token(HOOK)) return true;
+ if (jj_3R_73()) return true;
+ if (jj_scan_token(COLON)) return true;
+ if (jj_3R_121()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_291() {
+ if (jj_scan_token(FOR)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_365()) {
+ jj_scanpos = xsp;
+ if (jj_3R_366()) return true;
+ }
+ if (jj_scan_token(RPAREN)) return true;
+ if (jj_3R_250()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_121() {
+ if (jj_3R_160()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_220()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_120() {
+ if (jj_scan_token(ORASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_119() {
+ if (jj_scan_token(XORASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_118() {
+ if (jj_scan_token(ANDASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_117() {
+ if (jj_scan_token(RUNSIGNEDSHIFTASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_116() {
+ if (jj_scan_token(RSIGNEDSHIFTASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_115() {
+ if (jj_scan_token(LSHIFTASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_114() {
+ if (jj_scan_token(MINUSASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_113() {
+ if (jj_scan_token(PLUSASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_290() {
+ if (jj_scan_token(DO)) return true;
+ if (jj_3R_250()) return true;
+ if (jj_scan_token(WHILE)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_73()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_112() {
+ if (jj_scan_token(REMASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_111() {
+ if (jj_scan_token(SLASHASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_110() {
+ if (jj_scan_token(STARASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_109() {
+ if (jj_scan_token(ASSIGN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_72() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_109()) {
+ jj_scanpos = xsp;
+ if (jj_3R_110()) {
+ jj_scanpos = xsp;
+ if (jj_3R_111()) {
+ jj_scanpos = xsp;
+ if (jj_3R_112()) {
+ jj_scanpos = xsp;
+ if (jj_3R_113()) {
+ jj_scanpos = xsp;
+ if (jj_3R_114()) {
+ jj_scanpos = xsp;
+ if (jj_3R_115()) {
+ jj_scanpos = xsp;
+ if (jj_3R_116()) {
+ jj_scanpos = xsp;
+ if (jj_3R_117()) {
+ jj_scanpos = xsp;
+ if (jj_3R_118()) {
+ jj_scanpos = xsp;
+ if (jj_3R_119()) {
+ jj_scanpos = xsp;
+ if (jj_3R_120()) return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_344() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_90()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_289() {
+ if (jj_scan_token(WHILE)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_73()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ if (jj_3R_250()) return true;
+ return false;
+ }
+
+ private boolean jj_3_18() {
+ if (jj_3R_72()) return true;
+ if (jj_3R_73()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_73() {
+ if (jj_3R_121()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_18()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_288() {
+ if (jj_scan_token(IF)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_73()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ if (jj_3R_250()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_364()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_134() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_133()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_384() {
+ if (jj_scan_token(_DEFAULT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_383() {
+ if (jj_scan_token(CASE)) return true;
+ if (jj_3R_73()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_374() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_383()) {
+ jj_scanpos = xsp;
+ if (jj_3R_384()) return true;
+ }
+ if (jj_scan_token(COLON)) return true;
+ if (jj_3R_144()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_332() {
+ if (jj_3R_90()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_344()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3_17() {
+ if (jj_scan_token(DOT)) return true;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_363() {
+ if (jj_3R_374()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_90() {
+ if (jj_scan_token(IDENTIFIER)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_17()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_287() {
+ if (jj_scan_token(SWITCH)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_73()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ if (jj_scan_token(LBRACE)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_363()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(RBRACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_130() {
+ if (jj_3R_63()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_129() {
+ if (jj_scan_token(VOID)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_382() {
+ if (jj_3R_72()) return true;
+ if (jj_3R_73()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_381() {
+ if (jj_scan_token(DECR)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_380() {
+ if (jj_scan_token(INCR)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_373() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_380()) {
+ jj_scanpos = xsp;
+ if (jj_3R_381()) {
+ jj_scanpos = xsp;
+ if (jj_3R_382()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_82() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_129()) {
+ jj_scanpos = xsp;
+ if (jj_3R_130()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_302() {
+ if (jj_3R_238()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_373()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_301() {
+ if (jj_3R_218()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_300() {
+ if (jj_3R_217()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_181() {
+ if (jj_scan_token(DOUBLE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_180() {
+ if (jj_scan_token(FLOAT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_179() {
+ if (jj_scan_token(LONG)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_286() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_300()) {
+ jj_scanpos = xsp;
+ if (jj_3R_301()) {
+ jj_scanpos = xsp;
+ if (jj_3R_302()) return true;
+ }
+ }
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_178() {
+ if (jj_scan_token(INT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_177() {
+ if (jj_scan_token(SHORT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_176() {
+ if (jj_scan_token(BYTE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_157() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_108()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_254() {
+ if (jj_scan_token(SUPER)) return true;
+ if (jj_3R_70()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_175() {
+ if (jj_scan_token(CHAR)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_362() {
+ if (jj_scan_token(COLON)) return true;
+ if (jj_3R_73()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_236() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_253()) {
+ jj_scanpos = xsp;
+ if (jj_3R_254()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_253() {
+ if (jj_scan_token(EXTENDS)) return true;
+ if (jj_3R_70()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_174() {
+ if (jj_scan_token(BOOLEAN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_143() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_174()) {
+ jj_scanpos = xsp;
+ if (jj_3R_175()) {
+ jj_scanpos = xsp;
+ if (jj_3R_176()) {
+ jj_scanpos = xsp;
+ if (jj_3R_177()) {
+ jj_scanpos = xsp;
+ if (jj_3R_178()) {
+ jj_scanpos = xsp;
+ if (jj_3R_179()) {
+ jj_scanpos = xsp;
+ if (jj_3R_180()) {
+ jj_scanpos = xsp;
+ if (jj_3R_181()) return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_285() {
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_189() {
+ if (jj_scan_token(HOOK)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_236()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3_37() {
+ if (jj_3R_89()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_89() {
+ if (jj_3R_88()) return true;
+ if (jj_3R_63()) return true;
+ if (jj_3R_133()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_134()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3_36() {
+ if (jj_3R_88()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(20)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(40)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_231() {
+ if (jj_3R_250()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_159() {
+ if (jj_3R_189()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_230() {
+ if (jj_3R_89()) return true;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_158() {
+ if (jj_3R_70()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_319() {
+ if (jj_scan_token(THROWS)) return true;
+ if (jj_3R_332()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_108() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_158()) {
+ jj_scanpos = xsp;
+ if (jj_3R_159()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_229() {
+ if (jj_3R_88()) return true;
+ if (jj_3R_243()) return true;
+ return false;
+ }
+
+ private boolean jj_3_13() {
+ if (jj_scan_token(LBRACKET)) return true;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_71() {
+ if (jj_scan_token(LT)) return true;
+ if (jj_3R_108()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_157()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(GT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_202() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_229()) {
+ jj_scanpos = xsp;
+ if (jj_3R_230()) {
+ jj_scanpos = xsp;
+ if (jj_3R_231()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3_16() {
+ if (jj_3R_71()) return true;
+ return false;
+ }
+
+ private boolean jj_3_15() {
+ if (jj_scan_token(DOT)) return true;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_16()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3_12() {
+ if (jj_scan_token(LBRACKET)) return true;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3_14() {
+ if (jj_3R_71()) return true;
+ return false;
+ }
+
+ private boolean jj_3_33() {
+ if (jj_scan_token(LBRACKET)) return true;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_85() {
+ if (jj_scan_token(IDENTIFIER)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_14()) jj_scanpos = xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_15()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_99() {
+ if (jj_scan_token(LBRACE)) return true;
+ if (jj_3R_144()) return true;
+ if (jj_scan_token(RBRACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_87() {
+ if (jj_scan_token(IDENTIFIER)) return true;
+ if (jj_scan_token(COLON)) return true;
+ if (jj_3R_250()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_107() {
+ if (jj_3R_85()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_13()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_106() {
+ if (jj_3R_143()) return true;
+ Token xsp;
+ if (jj_3_12()) return true;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_12()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_70() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_106()) {
+ jj_scanpos = xsp;
+ if (jj_3R_107()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_284() {
+ if (jj_scan_token(ASSERT)) return true;
+ if (jj_3R_73()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_362()) jj_scanpos = xsp;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_97() {
+ if (jj_3R_143()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_280() {
+ if (jj_3R_297()) return true;
+ return false;
+ }
+
+ private boolean jj_3_11() {
+ if (jj_3R_70()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_279() {
+ if (jj_3R_296()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_63() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_11()) {
+ jj_scanpos = xsp;
+ if (jj_3R_97()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_278() {
+ if (jj_3R_295()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_277() {
+ if (jj_3R_294()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_276() {
+ if (jj_3R_293()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_275() {
+ if (jj_3R_292()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_274() {
+ if (jj_3R_291()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_273() {
+ if (jj_3R_290()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_98() {
+ if (jj_scan_token(STATIC)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_272() {
+ if (jj_3R_289()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_65() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_98()) jj_scanpos = xsp;
+ if (jj_3R_99()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_271() {
+ if (jj_3R_288()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_270() {
+ if (jj_3R_287()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_182() {
+ if (jj_3R_202()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_269() {
+ if (jj_3R_286()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_144() {
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_182()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_268() {
+ if (jj_3R_285()) return true;
+ return false;
+ }
+
+ private boolean jj_3_9() {
+ if (jj_3R_68()) return true;
+ if (jj_scan_token(DOT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_267() {
+ if (jj_3R_99()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_266() {
+ if (jj_3R_284()) return true;
+ return false;
+ }
+
+ private boolean jj_3_35() {
+ if (jj_3R_87()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_69() {
+ if (jj_3R_71()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_149() {
+ if (jj_3R_71()) return true;
+ return false;
+ }
+
+ private boolean jj_3_7() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_66()) return true;
+ return false;
+ }
+
+ private boolean jj_3_10() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_69()) jj_scanpos = xsp;
+ if (jj_scan_token(THIS)) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_148() {
+ if (jj_3R_68()) return true;
+ if (jj_scan_token(DOT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_103() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_148()) jj_scanpos = xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_149()) jj_scanpos = xsp;
+ if (jj_scan_token(SUPER)) return true;
+ if (jj_3R_147()) return true;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_250() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_35()) {
+ jj_scanpos = xsp;
+ if (jj_3R_266()) {
+ jj_scanpos = xsp;
+ if (jj_3R_267()) {
+ jj_scanpos = xsp;
+ if (jj_3R_268()) {
+ jj_scanpos = xsp;
+ if (jj_3R_269()) {
+ jj_scanpos = xsp;
+ if (jj_3R_270()) {
+ jj_scanpos = xsp;
+ if (jj_3R_271()) {
+ jj_scanpos = xsp;
+ if (jj_3R_272()) {
+ jj_scanpos = xsp;
+ if (jj_3R_273()) {
+ jj_scanpos = xsp;
+ if (jj_3R_274()) {
+ jj_scanpos = xsp;
+ if (jj_3R_275()) {
+ jj_scanpos = xsp;
+ if (jj_3R_276()) {
+ jj_scanpos = xsp;
+ if (jj_3R_277()) {
+ jj_scanpos = xsp;
+ if (jj_3R_278()) {
+ jj_scanpos = xsp;
+ if (jj_3R_279()) {
+ jj_scanpos = xsp;
+ if (jj_3R_280()) return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_146() {
+ if (jj_3R_71()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_102() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_146()) jj_scanpos = xsp;
+ if (jj_scan_token(THIS)) return true;
+ if (jj_3R_147()) return true;
+ if (jj_scan_token(SEMICOLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_166() {
+ if (jj_scan_token(LBRACKET)) return true;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_132() {
+ Token xsp;
+ if (jj_3R_166()) return true;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_166()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_3R_145()) return true;
+ return false;
+ }
+
+ private boolean jj_3_32() {
+ if (jj_scan_token(LBRACKET)) return true;
+ if (jj_3R_73()) return true;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3_34() {
+ Token xsp;
+ if (jj_3_32()) return true;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_32()) { jj_scanpos = xsp; break; }
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_33()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3_30() {
+ if (jj_3R_84()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_67() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_102()) {
+ jj_scanpos = xsp;
+ if (jj_3R_103()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_86() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_34()) {
+ jj_scanpos = xsp;
+ if (jj_3R_132()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_322() {
+ if (jj_scan_token(LBRACKET)) return true;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_343() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_342()) return true;
+ return false;
+ }
+
+ private boolean jj_3_8() {
+ if (jj_3R_67()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_348() {
+ if (jj_scan_token(ELLIPSIS)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_235() {
+ if (jj_3R_71()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_320() {
+ if (jj_3R_67()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_213() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_235()) jj_scanpos = xsp;
+ if (jj_3R_85()) return true;
+ if (jj_3R_147()) return true;
+ xsp = jj_scanpos;
+ if (jj_3_30()) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3_31() {
+ if (jj_3R_85()) return true;
+ if (jj_3R_86()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_262() {
+ if (jj_3R_96()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_232() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_73()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_246() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_262()) jj_scanpos = xsp;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ if (jj_3R_318()) return true;
+ xsp = jj_scanpos;
+ if (jj_3R_319()) jj_scanpos = xsp;
+ if (jj_scan_token(LBRACE)) return true;
+ xsp = jj_scanpos;
+ if (jj_3R_320()) jj_scanpos = xsp;
+ if (jj_3R_144()) return true;
+ if (jj_scan_token(RBRACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_212() {
+ if (jj_3R_85()) return true;
+ if (jj_3R_86()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_200() {
+ if (jj_scan_token(LBRACKET)) return true;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_211() {
+ if (jj_3R_143()) return true;
+ if (jj_3R_86()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_199() {
+ if (jj_3R_66()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_7()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_187() {
+ if (jj_scan_token(NEW)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_211()) {
+ jj_scanpos = xsp;
+ if (jj_3R_212()) {
+ jj_scanpos = xsp;
+ if (jj_3R_213()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_342() {
+ if (jj_3R_88()) return true;
+ if (jj_3R_63()) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_348()) jj_scanpos = xsp;
+ if (jj_3R_167()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_331() {
+ if (jj_3R_342()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_343()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_318() {
+ if (jj_scan_token(LPAREN)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_331()) jj_scanpos = xsp;
+ if (jj_scan_token(RPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_203() {
+ if (jj_3R_73()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_232()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_183() {
+ if (jj_3R_203()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_324() {
+ if (jj_3R_99()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_323() {
+ if (jj_scan_token(THROWS)) return true;
+ if (jj_3R_332()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_147() {
+ if (jj_scan_token(LPAREN)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_183()) jj_scanpos = xsp;
+ if (jj_scan_token(RPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_263() {
+ if (jj_3R_96()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_248() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_263()) jj_scanpos = xsp;
+ if (jj_3R_82()) return true;
+ if (jj_scan_token(IDENTIFIER)) return true;
+ if (jj_3R_318()) return true;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_322()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_323()) jj_scanpos = xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_324()) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(86)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_234() {
+ if (jj_scan_token(NULL)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_252() {
+ if (jj_scan_token(FALSE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_251() {
+ if (jj_scan_token(TRUE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_233() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_251()) {
+ jj_scanpos = xsp;
+ if (jj_3R_252()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_210() {
+ if (jj_3R_234()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_145() {
+ if (jj_scan_token(LBRACE)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_199()) jj_scanpos = xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(87)) jj_scanpos = xsp;
+ if (jj_scan_token(RBRACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_209() {
+ if (jj_3R_233()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_101() {
+ if (jj_3R_73()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_208() {
+ if (jj_scan_token(STRING_LITERAL)) return true;
+ return false;
+ }
+
+ /** Generated Token Manager. */
+ public ASTParserTokenManager token_source;
+ JavaCharStream jj_input_stream;
+ /** Current token. */
+ public Token token;
+ /** Next token. */
+ public Token jj_nt;
+ private int jj_ntk;
+ private Token jj_scanpos, jj_lastpos;
+ private int jj_la;
+ /** Whether we are looking ahead. */
+ private boolean jj_lookingAhead = false;
+ private boolean jj_semLA;
+ private int jj_gen;
+ final private int[] jj_la1 = new int[134];
+ static private int[] jj_la1_0;
+ static private int[] jj_la1_1;
+ static private int[] jj_la1_2;
+ static private int[] jj_la1_3;
+ static private int[] jj_la1_4;
+ static {
+ jj_la1_init_0();
+ jj_la1_init_1();
+ jj_la1_init_2();
+ jj_la1_init_3();
+ jj_la1_init_4();
+ }
+ private static void jj_la1_init_0() {
+ jj_la1_0 = new int[] {0x0,0x48101000,0x1,0x0,0x0,0x0,0x40001000,0x8100000,0x48101000,0x100000,0x0,0x10000000,0x0,0x0,0x0,0x0,0x0,0x0,0x4a195000,0x0,0x0,0x0,0x0,0x0,0x10000000,0x0,0x4a195000,0x8100000,0x2094000,0x4a195000,0x0,0x0,0x0,0x22094000,0x22094000,0x0,0x0,0x0,0x0,0x0,0x0,0x42095000,0x0,0x0,0x0,0x0,0x0,0x22094000,0x6359f000,0x0,0x2094000,0x2094000,0x0,0x2094000,0x10000000,0x10000000,0x2094000,0x2094000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x22094000,0x0,0x0,0x22094000,0x0,0x0,0x0,0x0,0x0,0x20000000,0x0,0x0,0x0,0x0,0x0,0x0,0x20000000,0x20000000,0x22094000,0x0,0x0,0x2094000,0x0,0x0,0x0,0x2349e000,0x0,0x2349e000,0x0,0x0,0x0,0x22094000,0x820000,0x820000,0x4000000,0x62095000,0x22094000,0x22094000,0x62095000,0x22094000,0x0,0x0,0x0,0x22094000,0x40000,0x80000000,0x80040000,0x0,0x0,0x0,0x22094000,0x22094000,0x0,0x4a195000,0xa194000,0x4a195000,0x800000,};
+ }
+ private static void jj_la1_init_1() {
+ jj_la1_1 = new int[] {0x20,0x8899c500,0x0,0x0,0x80000,0x0,0x8899c400,0x100,0x8899c500,0x100,0x0,0x0,0x10,0x0,0x0,0x10,0x0,0x0,0xc89dc781,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc89dc781,0x100,0x40040281,0xc89dc781,0x0,0x0,0x0,0x51241a81,0x51241a81,0x0,0x0,0x0,0x4000000,0x0,0x0,0x889dc681,0x0,0x0,0x4000000,0x0,0x0,0x51241a81,0xfbffdf8b,0x80000,0x40281,0x40281,0x0,0x40281,0x200000,0x200000,0x40281,0x40040281,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x51241a81,0x0,0x0,0x51241a81,0x0,0x0,0x0,0x0,0x0,0x11201800,0x0,0x0,0x0,0x0,0x1000800,0x0,0x10001000,0x10000000,0x51241a81,0x0,0x0,0x40281,0x0,0x0,0x0,0x73e61a8b,0x0,0x73e61a8b,0x0,0x0,0x0,0x51241a81,0x0,0x0,0x0,0xd9bdde81,0x51241a81,0x51241a81,0xd9bdde81,0x51241a81,0x0,0x0,0x0,0x51241a81,0x0,0x0,0x0,0x0,0x0,0x0,0x51241a81,0x51241a81,0x0,0x889dc781,0x40381,0x889dc781,0x0,};
+ }
+ private static void jj_la1_init_2() {
+ jj_la1_2 = new int[] {0x0,0x2400000,0x0,0x2000000,0x0,0x1000000,0x2000000,0x2000000,0x2400000,0x0,0x8000000,0x0,0x0,0x800000,0x800000,0x0,0x2002000,0x800000,0xa442000,0x400000,0x2000000,0x10000,0x40000,0x800000,0x0,0x0,0xa442000,0x2000000,0x8002000,0xa402000,0x800000,0x4000000,0x100000,0x30053846,0x30053846,0x800000,0x8000000,0x100000,0x0,0x440000,0x800000,0x2002000,0x0,0x8000000,0x0,0x8000000,0x8000000,0x8013846,0x2453847,0x0,0x0,0x2000,0x800000,0x40002000,0x0,0x0,0x0,0x2000,0x800000,0x4000000,0x40000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000000,0x8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x30013846,0x30000000,0x30000000,0x13846,0x10000,0x0,0x8000000,0x10000,0x10000,0x11846,0x2000,0x1000000,0x8000000,0x10000,0x0,0x1100000,0x1846,0x0,0x30013846,0x800000,0x8000000,0x0,0x8002000,0x100000,0x100000,0x453847,0x80000000,0x453847,0x800000,0x4000000,0x4000000,0x13846,0x0,0x0,0x0,0x32013846,0x30013846,0x30013846,0x32413846,0x30013846,0x800000,0x2000,0x2000,0x30013846,0x0,0x0,0x0,0x2000000,0x2000,0x800000,0x32053846,0x32053846,0x800000,0x2402000,0x2002000,0x2402000,0x0,};
+ }
+ private static void jj_la1_init_3() {
+ jj_la1_3 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10000000,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffe0000,0x0,0x10,0x20,0x2000,0x4000,0x1000,0x9,0x9,0x0,0x80000006,0x80000006,0x10000,0x300,0x300,0x8c00,0x8c00,0x300,0x3c0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0xc0,0x0,0xffe00c0,0xffe00c0,0xc0,0x0,0x0,0x0,0x3c0,0x3c0,0x3c0,0x3c0,0x3c0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x3c0,0x0,0x0,0x0,0x0,0x0,};
+ }
+ private static void jj_la1_init_4() {
+ jj_la1_4 = new int[] {0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,};
+ }
+ final private JJCalls[] jj_2_rtns = new JJCalls[43];
+ private boolean jj_rescan = false;
+ private int jj_gc = 0;
+
+ /** Constructor with InputStream. */
+ public ASTParser(java.io.InputStream stream) {
+ this(stream, null);
+ }
+ /** Constructor with InputStream and supplied encoding */
+ public ASTParser(java.io.InputStream stream, String encoding) {
+ try { jj_input_stream = new JavaCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+ token_source = new ASTParserTokenManager(jj_input_stream);
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 134; i++) jj_la1[i] = -1;
+ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+ }
+
+ /** Reinitialise. */
+ public void ReInit(java.io.InputStream stream) {
+ ReInit(stream, null);
+ }
+ /** Reinitialise. */
+ public void ReInit(java.io.InputStream stream, String encoding) {
+ try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+ token_source.ReInit(jj_input_stream);
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 134; i++) jj_la1[i] = -1;
+ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+ }
+
+ /** Constructor. */
+ public ASTParser(java.io.Reader stream) {
+ jj_input_stream = new JavaCharStream(stream, 1, 1);
+ token_source = new ASTParserTokenManager(jj_input_stream);
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 134; i++) jj_la1[i] = -1;
+ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+ }
+
+ /** Reinitialise. */
+ public void ReInit(java.io.Reader stream) {
+ jj_input_stream.ReInit(stream, 1, 1);
+ token_source.ReInit(jj_input_stream);
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 134; i++) jj_la1[i] = -1;
+ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+ }
+
+ /** Constructor with generated Token Manager. */
+ public ASTParser(ASTParserTokenManager tm) {
+ token_source = tm;
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 134; i++) jj_la1[i] = -1;
+ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+ }
+
+ /** Reinitialise. */
+ public void ReInit(ASTParserTokenManager tm) {
+ token_source = tm;
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 134; i++) jj_la1[i] = -1;
+ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+ }
+
+ private Token jj_consume_token(int kind) throws ParseException {
+ Token oldToken;
+ if ((oldToken = token).next != null) token = token.next;
+ else token = token.next = token_source.getNextToken();
+ jj_ntk = -1;
+ if (token.kind == kind) {
+ jj_gen++;
+ if (++jj_gc > 100) {
+ jj_gc = 0;
+ for (int i = 0; i < jj_2_rtns.length; i++) {
+ JJCalls c = jj_2_rtns[i];
+ while (c != null) {
+ if (c.gen < jj_gen) c.first = null;
+ c = c.next;
+ }
+ }
+ }
+ return token;
+ }
+ token = oldToken;
+ jj_kind = kind;
+ throw generateParseException();
+ }
+
+ static private final class LookaheadSuccess extends java.lang.Error { }
+ final private LookaheadSuccess jj_ls = new LookaheadSuccess();
+ private boolean jj_scan_token(int kind) {
+ if (jj_scanpos == jj_lastpos) {
+ jj_la--;
+ if (jj_scanpos.next == null) {
+ jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken();
+ } else {
+ jj_lastpos = jj_scanpos = jj_scanpos.next;
+ }
+ } else {
+ jj_scanpos = jj_scanpos.next;
+ }
+ if (jj_rescan) {
+ int i = 0; Token tok = token;
+ while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; }
+ if (tok != null) jj_add_error_token(kind, i);
+ }
+ if (jj_scanpos.kind != kind) return true;
+ if (jj_la == 0 && jj_scanpos == jj_lastpos) throw jj_ls;
+ return false;
+ }
+
+
+/** Get the next Token. */
+ final public Token getNextToken() {
+ if (token.next != null) token = token.next;
+ else token = token.next = token_source.getNextToken();
+ jj_ntk = -1;
+ jj_gen++;
+ return token;
+ }
+
+/** Get the specific Token. */
+ final public Token getToken(int index) {
+ Token t = jj_lookingAhead ? jj_scanpos : token;
+ for (int i = 0; i < index; i++) {
+ if (t.next != null) t = t.next;
+ else t = t.next = token_source.getNextToken();
+ }
+ return t;
+ }
+
+ private int jj_ntk() {
+ if ((jj_nt=token.next) == null)
+ return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+ else
+ return (jj_ntk = jj_nt.kind);
+ }
+
+ private java.util.List<int[]> jj_expentries = new java.util.ArrayList<int[]>();
+ private int[] jj_expentry;
+ private int jj_kind = -1;
+ private int[] jj_lasttokens = new int[100];
+ private int jj_endpos;
+
+ private void jj_add_error_token(int kind, int pos) {
+ if (pos >= 100) return;
+ if (pos == jj_endpos + 1) {
+ jj_lasttokens[jj_endpos++] = kind;
+ } else if (jj_endpos != 0) {
+ jj_expentry = new int[jj_endpos];
+ for (int i = 0; i < jj_endpos; i++) {
+ jj_expentry[i] = jj_lasttokens[i];
+ }
+ jj_entries_loop: for (java.util.Iterator it = jj_expentries.iterator(); it.hasNext();) {
+ int[] oldentry = (int[])(it.next());
+ if (oldentry.length == jj_expentry.length) {
+ for (int i = 0; i < jj_expentry.length; i++) {
+ if (oldentry[i] != jj_expentry[i]) {
+ continue jj_entries_loop;
+ }
+ }
+ jj_expentries.add(jj_expentry);
+ break jj_entries_loop;
+ }
+ }
+ if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind;
+ }
+ }
+
+ /** Generate ParseException. */
+ public ParseException generateParseException() {
+ jj_expentries.clear();
+ boolean[] la1tokens = new boolean[129];
+ if (jj_kind >= 0) {
+ la1tokens[jj_kind] = true;
+ jj_kind = -1;
+ }
+ for (int i = 0; i < 134; i++) {
+ if (jj_la1[i] == jj_gen) {
+ for (int j = 0; j < 32; j++) {
+ if ((jj_la1_0[i] & (1<<j)) != 0) {
+ la1tokens[j] = true;
+ }
+ if ((jj_la1_1[i] & (1<<j)) != 0) {
+ la1tokens[32+j] = true;
+ }
+ if ((jj_la1_2[i] & (1<<j)) != 0) {
+ la1tokens[64+j] = true;
+ }
+ if ((jj_la1_3[i] & (1<<j)) != 0) {
+ la1tokens[96+j] = true;
+ }
+ if ((jj_la1_4[i] & (1<<j)) != 0) {
+ la1tokens[128+j] = true;
+ }
+ }
+ }
+ }
+ for (int i = 0; i < 129; i++) {
+ if (la1tokens[i]) {
+ jj_expentry = new int[1];
+ jj_expentry[0] = i;
+ jj_expentries.add(jj_expentry);
+ }
+ }
+ jj_endpos = 0;
+ jj_rescan_token();
+ jj_add_error_token(0, 0);
+ int[][] exptokseq = new int[jj_expentries.size()][];
+ for (int i = 0; i < jj_expentries.size(); i++) {
+ exptokseq[i] = jj_expentries.get(i);
+ }
+ return new ParseException(token, exptokseq, tokenImage);
+ }
+
+ /** Enable tracing. */
+ final public void enable_tracing() {
+ }
+
+ /** Disable tracing. */
+ final public void disable_tracing() {
+ }
+
+ private void jj_rescan_token() {
+ jj_rescan = true;
+ for (int i = 0; i < 43; i++) {
+ try {
+ JJCalls p = jj_2_rtns[i];
+ do {
+ if (p.gen > jj_gen) {
+ jj_la = p.arg; jj_lastpos = jj_scanpos = p.first;
+ switch (i) {
+ case 0: jj_3_1(); break;
+ case 1: jj_3_2(); break;
+ case 2: jj_3_3(); break;
+ case 3: jj_3_4(); break;
+ case 4: jj_3_5(); break;
+ case 5: jj_3_6(); break;
+ case 6: jj_3_7(); break;
+ case 7: jj_3_8(); break;
+ case 8: jj_3_9(); break;
+ case 9: jj_3_10(); break;
+ case 10: jj_3_11(); break;
+ case 11: jj_3_12(); break;
+ case 12: jj_3_13(); break;
+ case 13: jj_3_14(); break;
+ case 14: jj_3_15(); break;
+ case 15: jj_3_16(); break;
+ case 16: jj_3_17(); break;
+ case 17: jj_3_18(); break;
+ case 18: jj_3_19(); break;
+ case 19: jj_3_20(); break;
+ case 20: jj_3_21(); break;
+ case 21: jj_3_22(); break;
+ case 22: jj_3_23(); break;
+ case 23: jj_3_24(); break;
+ case 24: jj_3_25(); break;
+ case 25: jj_3_26(); break;
+ case 26: jj_3_27(); break;
+ case 27: jj_3_28(); break;
+ case 28: jj_3_29(); break;
+ case 29: jj_3_30(); break;
+ case 30: jj_3_31(); break;
+ case 31: jj_3_32(); break;
+ case 32: jj_3_33(); break;
+ case 33: jj_3_34(); break;
+ case 34: jj_3_35(); break;
+ case 35: jj_3_36(); break;
+ case 36: jj_3_37(); break;
+ case 37: jj_3_38(); break;
+ case 38: jj_3_39(); break;
+ case 39: jj_3_40(); break;
+ case 40: jj_3_41(); break;
+ case 41: jj_3_42(); break;
+ case 42: jj_3_43(); break;
+ }
+ }
+ p = p.next;
+ } while (p != null);
+ } catch(LookaheadSuccess ls) { }
+ }
+ jj_rescan = false;
+ }
+
+ private void jj_save(int index, int xla) {
+ JJCalls p = jj_2_rtns[index];
+ while (p.gen > jj_gen) {
+ if (p.next == null) { p = p.next = new JJCalls(); break; }
+ p = p.next;
+ }
+ p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla;
+ }
+
+ static final class JJCalls {
+ int gen;
+ Token first;
+ int arg;
+ JJCalls next;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ASTParserConstants.java b/components/htmlfive/java/javaparser/src/japa/parser/ASTParserConstants.java
new file mode 100644
index 000000000..ef44c7be3
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ASTParserConstants.java
@@ -0,0 +1,410 @@
+/* Generated By:JavaCC: Do not edit this line. ASTParserConstants.java */
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+package japa.parser;
+
+
+/**
+ * Token literal values and constants.
+ * Generated by org.javacc.parser.OtherFilesGen#start()
+ */
+public interface ASTParserConstants {
+
+ /** End of File. */
+ int EOF = 0;
+ /** RegularExpression Id. */
+ int SINGLE_LINE_COMMENT = 6;
+ /** RegularExpression Id. */
+ int JAVA_DOC_COMMENT = 9;
+ /** RegularExpression Id. */
+ int MULTI_LINE_COMMENT = 10;
+ /** RegularExpression Id. */
+ int ABSTRACT = 12;
+ /** RegularExpression Id. */
+ int ASSERT = 13;
+ /** RegularExpression Id. */
+ int BOOLEAN = 14;
+ /** RegularExpression Id. */
+ int BREAK = 15;
+ /** RegularExpression Id. */
+ int BYTE = 16;
+ /** RegularExpression Id. */
+ int CASE = 17;
+ /** RegularExpression Id. */
+ int CATCH = 18;
+ /** RegularExpression Id. */
+ int CHAR = 19;
+ /** RegularExpression Id. */
+ int CLASS = 20;
+ /** RegularExpression Id. */
+ int CONST = 21;
+ /** RegularExpression Id. */
+ int CONTINUE = 22;
+ /** RegularExpression Id. */
+ int _DEFAULT = 23;
+ /** RegularExpression Id. */
+ int DO = 24;
+ /** RegularExpression Id. */
+ int DOUBLE = 25;
+ /** RegularExpression Id. */
+ int ELSE = 26;
+ /** RegularExpression Id. */
+ int ENUM = 27;
+ /** RegularExpression Id. */
+ int EXTENDS = 28;
+ /** RegularExpression Id. */
+ int FALSE = 29;
+ /** RegularExpression Id. */
+ int FINAL = 30;
+ /** RegularExpression Id. */
+ int FINALLY = 31;
+ /** RegularExpression Id. */
+ int FLOAT = 32;
+ /** RegularExpression Id. */
+ int FOR = 33;
+ /** RegularExpression Id. */
+ int GOTO = 34;
+ /** RegularExpression Id. */
+ int IF = 35;
+ /** RegularExpression Id. */
+ int IMPLEMENTS = 36;
+ /** RegularExpression Id. */
+ int IMPORT = 37;
+ /** RegularExpression Id. */
+ int INSTANCEOF = 38;
+ /** RegularExpression Id. */
+ int INT = 39;
+ /** RegularExpression Id. */
+ int INTERFACE = 40;
+ /** RegularExpression Id. */
+ int LONG = 41;
+ /** RegularExpression Id. */
+ int NATIVE = 42;
+ /** RegularExpression Id. */
+ int NEW = 43;
+ /** RegularExpression Id. */
+ int NULL = 44;
+ /** RegularExpression Id. */
+ int PACKAGE = 45;
+ /** RegularExpression Id. */
+ int PRIVATE = 46;
+ /** RegularExpression Id. */
+ int PROTECTED = 47;
+ /** RegularExpression Id. */
+ int PUBLIC = 48;
+ /** RegularExpression Id. */
+ int RETURN = 49;
+ /** RegularExpression Id. */
+ int SHORT = 50;
+ /** RegularExpression Id. */
+ int STATIC = 51;
+ /** RegularExpression Id. */
+ int STRICTFP = 52;
+ /** RegularExpression Id. */
+ int SUPER = 53;
+ /** RegularExpression Id. */
+ int SWITCH = 54;
+ /** RegularExpression Id. */
+ int SYNCHRONIZED = 55;
+ /** RegularExpression Id. */
+ int THIS = 56;
+ /** RegularExpression Id. */
+ int THROW = 57;
+ /** RegularExpression Id. */
+ int THROWS = 58;
+ /** RegularExpression Id. */
+ int TRANSIENT = 59;
+ /** RegularExpression Id. */
+ int TRUE = 60;
+ /** RegularExpression Id. */
+ int TRY = 61;
+ /** RegularExpression Id. */
+ int VOID = 62;
+ /** RegularExpression Id. */
+ int VOLATILE = 63;
+ /** RegularExpression Id. */
+ int WHILE = 64;
+ /** RegularExpression Id. */
+ int LONG_LITERAL = 65;
+ /** RegularExpression Id. */
+ int INTEGER_LITERAL = 66;
+ /** RegularExpression Id. */
+ int DECIMAL_LITERAL = 67;
+ /** RegularExpression Id. */
+ int HEX_LITERAL = 68;
+ /** RegularExpression Id. */
+ int OCTAL_LITERAL = 69;
+ /** RegularExpression Id. */
+ int FLOATING_POINT_LITERAL = 70;
+ /** RegularExpression Id. */
+ int DECIMAL_FLOATING_POINT_LITERAL = 71;
+ /** RegularExpression Id. */
+ int DECIMAL_EXPONENT = 72;
+ /** RegularExpression Id. */
+ int HEXADECIMAL_FLOATING_POINT_LITERAL = 73;
+ /** RegularExpression Id. */
+ int HEXADECIMAL_EXPONENT = 74;
+ /** RegularExpression Id. */
+ int CHARACTER_LITERAL = 75;
+ /** RegularExpression Id. */
+ int STRING_LITERAL = 76;
+ /** RegularExpression Id. */
+ int IDENTIFIER = 77;
+ /** RegularExpression Id. */
+ int LETTER = 78;
+ /** RegularExpression Id. */
+ int PART_LETTER = 79;
+ /** RegularExpression Id. */
+ int LPAREN = 80;
+ /** RegularExpression Id. */
+ int RPAREN = 81;
+ /** RegularExpression Id. */
+ int LBRACE = 82;
+ /** RegularExpression Id. */
+ int RBRACE = 83;
+ /** RegularExpression Id. */
+ int LBRACKET = 84;
+ /** RegularExpression Id. */
+ int RBRACKET = 85;
+ /** RegularExpression Id. */
+ int SEMICOLON = 86;
+ /** RegularExpression Id. */
+ int COMMA = 87;
+ /** RegularExpression Id. */
+ int DOT = 88;
+ /** RegularExpression Id. */
+ int AT = 89;
+ /** RegularExpression Id. */
+ int ASSIGN = 90;
+ /** RegularExpression Id. */
+ int LT = 91;
+ /** RegularExpression Id. */
+ int BANG = 92;
+ /** RegularExpression Id. */
+ int TILDE = 93;
+ /** RegularExpression Id. */
+ int HOOK = 94;
+ /** RegularExpression Id. */
+ int COLON = 95;
+ /** RegularExpression Id. */
+ int EQ = 96;
+ /** RegularExpression Id. */
+ int LE = 97;
+ /** RegularExpression Id. */
+ int GE = 98;
+ /** RegularExpression Id. */
+ int NE = 99;
+ /** RegularExpression Id. */
+ int SC_OR = 100;
+ /** RegularExpression Id. */
+ int SC_AND = 101;
+ /** RegularExpression Id. */
+ int INCR = 102;
+ /** RegularExpression Id. */
+ int DECR = 103;
+ /** RegularExpression Id. */
+ int PLUS = 104;
+ /** RegularExpression Id. */
+ int MINUS = 105;
+ /** RegularExpression Id. */
+ int STAR = 106;
+ /** RegularExpression Id. */
+ int SLASH = 107;
+ /** RegularExpression Id. */
+ int BIT_AND = 108;
+ /** RegularExpression Id. */
+ int BIT_OR = 109;
+ /** RegularExpression Id. */
+ int XOR = 110;
+ /** RegularExpression Id. */
+ int REM = 111;
+ /** RegularExpression Id. */
+ int LSHIFT = 112;
+ /** RegularExpression Id. */
+ int PLUSASSIGN = 113;
+ /** RegularExpression Id. */
+ int MINUSASSIGN = 114;
+ /** RegularExpression Id. */
+ int STARASSIGN = 115;
+ /** RegularExpression Id. */
+ int SLASHASSIGN = 116;
+ /** RegularExpression Id. */
+ int ANDASSIGN = 117;
+ /** RegularExpression Id. */
+ int ORASSIGN = 118;
+ /** RegularExpression Id. */
+ int XORASSIGN = 119;
+ /** RegularExpression Id. */
+ int REMASSIGN = 120;
+ /** RegularExpression Id. */
+ int LSHIFTASSIGN = 121;
+ /** RegularExpression Id. */
+ int RSIGNEDSHIFTASSIGN = 122;
+ /** RegularExpression Id. */
+ int RUNSIGNEDSHIFTASSIGN = 123;
+ /** RegularExpression Id. */
+ int ELLIPSIS = 124;
+ /** RegularExpression Id. */
+ int RUNSIGNEDSHIFT = 125;
+ /** RegularExpression Id. */
+ int RSIGNEDSHIFT = 126;
+ /** RegularExpression Id. */
+ int GT = 127;
+
+ /** Lexical state. */
+ int DEFAULT = 0;
+ /** Lexical state. */
+ int IN_JAVA_DOC_COMMENT = 1;
+ /** Lexical state. */
+ int IN_MULTI_LINE_COMMENT = 2;
+
+ /** Literal token values. */
+ String[] tokenImage = {
+ "<EOF>",
+ "\" \"",
+ "\"\\t\"",
+ "\"\\n\"",
+ "\"\\r\"",
+ "\"\\f\"",
+ "<SINGLE_LINE_COMMENT>",
+ "<token of kind 7>",
+ "\"/*\"",
+ "\"*/\"",
+ "\"*/\"",
+ "<token of kind 11>",
+ "\"abstract\"",
+ "\"assert\"",
+ "\"boolean\"",
+ "\"break\"",
+ "\"byte\"",
+ "\"case\"",
+ "\"catch\"",
+ "\"char\"",
+ "\"class\"",
+ "\"const\"",
+ "\"continue\"",
+ "\"default\"",
+ "\"do\"",
+ "\"double\"",
+ "\"else\"",
+ "\"enum\"",
+ "\"extends\"",
+ "\"false\"",
+ "\"final\"",
+ "\"finally\"",
+ "\"float\"",
+ "\"for\"",
+ "\"goto\"",
+ "\"if\"",
+ "\"implements\"",
+ "\"import\"",
+ "\"instanceof\"",
+ "\"int\"",
+ "\"interface\"",
+ "\"long\"",
+ "\"native\"",
+ "\"new\"",
+ "\"null\"",
+ "\"package\"",
+ "\"private\"",
+ "\"protected\"",
+ "\"public\"",
+ "\"return\"",
+ "\"short\"",
+ "\"static\"",
+ "\"strictfp\"",
+ "\"super\"",
+ "\"switch\"",
+ "\"synchronized\"",
+ "\"this\"",
+ "\"throw\"",
+ "\"throws\"",
+ "\"transient\"",
+ "\"true\"",
+ "\"try\"",
+ "\"void\"",
+ "\"volatile\"",
+ "\"while\"",
+ "<LONG_LITERAL>",
+ "<INTEGER_LITERAL>",
+ "<DECIMAL_LITERAL>",
+ "<HEX_LITERAL>",
+ "<OCTAL_LITERAL>",
+ "<FLOATING_POINT_LITERAL>",
+ "<DECIMAL_FLOATING_POINT_LITERAL>",
+ "<DECIMAL_EXPONENT>",
+ "<HEXADECIMAL_FLOATING_POINT_LITERAL>",
+ "<HEXADECIMAL_EXPONENT>",
+ "<CHARACTER_LITERAL>",
+ "<STRING_LITERAL>",
+ "<IDENTIFIER>",
+ "<LETTER>",
+ "<PART_LETTER>",
+ "\"(\"",
+ "\")\"",
+ "\"{\"",
+ "\"}\"",
+ "\"[\"",
+ "\"]\"",
+ "\";\"",
+ "\",\"",
+ "\".\"",
+ "\"@\"",
+ "\"=\"",
+ "\"<\"",
+ "\"!\"",
+ "\"~\"",
+ "\"?\"",
+ "\":\"",
+ "\"==\"",
+ "\"<=\"",
+ "\">=\"",
+ "\"!=\"",
+ "\"||\"",
+ "\"&&\"",
+ "\"++\"",
+ "\"--\"",
+ "\"+\"",
+ "\"-\"",
+ "\"*\"",
+ "\"/\"",
+ "\"&\"",
+ "\"|\"",
+ "\"^\"",
+ "\"%\"",
+ "\"<<\"",
+ "\"+=\"",
+ "\"-=\"",
+ "\"*=\"",
+ "\"/=\"",
+ "\"&=\"",
+ "\"|=\"",
+ "\"^=\"",
+ "\"%=\"",
+ "\"<<=\"",
+ "\">>=\"",
+ "\">>>=\"",
+ "\"...\"",
+ "\">>>\"",
+ "\">>\"",
+ "\">\"",
+ "\"\\u001a\"",
+ };
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ASTParserTokenManager.java b/components/htmlfive/java/javaparser/src/japa/parser/ASTParserTokenManager.java
new file mode 100644
index 000000000..22e584a3b
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ASTParserTokenManager.java
@@ -0,0 +1,2323 @@
+/* Generated By:JavaCC: Do not edit this line. ASTParserTokenManager.java */
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+package japa.parser;
+import java.io.*;
+import java.util.*;
+import japa.parser.ast.*;
+import japa.parser.ast.body.*;
+import japa.parser.ast.expr.*;
+import japa.parser.ast.stmt.*;
+import japa.parser.ast.type.*;
+
+/** Token Manager. */
+public class ASTParserTokenManager implements ASTParserConstants
+{
+ private List<Comment> comments;
+ private final Stack<JavadocComment> javadocStack = new Stack<JavadocComment>();
+ private JavadocComment lastJavadoc;
+
+ void pushJavadoc() {
+ javadocStack.push(lastJavadoc);
+ }
+
+ JavadocComment popJavadoc() {
+ return javadocStack.pop();
+ }
+
+ List<Comment> getComments() {
+ return comments;
+ }
+
+ void clearComments() {
+ comments = null;
+ javadocStack.clear();
+ lastJavadoc = null;
+ }
+
+ private void CommonTokenAction(Token token) {
+ lastJavadoc = null;
+ if (token.specialToken != null) {
+ if(comments == null) {
+ comments = new LinkedList<Comment>();
+ }
+ Token special = token.specialToken;
+ if(special.kind == JAVA_DOC_COMMENT) {
+ lastJavadoc = new JavadocComment(special.beginLine, special.beginColumn, special.endLine, special.endColumn, special.image.substring(3, special.image.length()-2));
+ comments.add(lastJavadoc);
+ } else if(special.kind == SINGLE_LINE_COMMENT) {
+ LineComment comment = new LineComment(special.beginLine, special.beginColumn, special.endLine, special.endColumn, special.image.substring(2));
+ comments.add(comment);
+ } else if(special.kind == MULTI_LINE_COMMENT) {
+ BlockComment comment = new BlockComment(special.beginLine, special.beginColumn, special.endLine, special.endColumn, special.image.substring(2, special.image.length()-2));
+ comments.add(comment);
+ }
+ }
+ }
+
+ /** Debug output. */
+ public java.io.PrintStream debugStream = System.out;
+ /** Set debug output. */
+ public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0, long active1, long active2)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0xfffffffffffff000L) != 0L || (active1 & 0x1L) != 0L)
+ {
+ jjmatchedKind = 77;
+ return 37;
+ }
+ if ((active1 & 0x1000000001000000L) != 0L)
+ return 1;
+ if ((active0 & 0x100L) != 0L || (active1 & 0x10080000000000L) != 0L)
+ return 54;
+ return -1;
+ case 1:
+ if ((active0 & 0x100L) != 0L)
+ return 59;
+ if ((active0 & 0x803000000L) != 0L)
+ return 37;
+ if ((active0 & 0xfffffff7fcfff000L) != 0L || (active1 & 0x1L) != 0L)
+ {
+ if (jjmatchedPos != 1)
+ {
+ jjmatchedKind = 77;
+ jjmatchedPos = 1;
+ }
+ return 37;
+ }
+ return -1;
+ case 2:
+ if ((active0 & 0xdffff675fefff000L) != 0L || (active1 & 0x1L) != 0L)
+ {
+ if (jjmatchedPos != 2)
+ {
+ jjmatchedKind = 77;
+ jjmatchedPos = 2;
+ }
+ return 37;
+ }
+ if ((active0 & 0x2000098200000000L) != 0L)
+ return 37;
+ return -1;
+ case 3:
+ if ((active0 & 0x8effe571f2f4f000L) != 0L || (active1 & 0x1L) != 0L)
+ {
+ jjmatchedKind = 77;
+ jjmatchedPos = 3;
+ return 37;
+ }
+ if ((active0 & 0x510012040c0b0000L) != 0L)
+ return 37;
+ return -1;
+ case 4:
+ if ((active0 & 0x88dbe57012c07000L) != 0L)
+ {
+ if (jjmatchedPos != 4)
+ {
+ jjmatchedKind = 77;
+ jjmatchedPos = 4;
+ }
+ return 37;
+ }
+ if ((active0 & 0x6240001e0348000L) != 0L || (active1 & 0x1L) != 0L)
+ return 37;
+ return -1;
+ case 5:
+ if ((active0 & 0x8890e15090c05000L) != 0L)
+ {
+ jjmatchedKind = 77;
+ jjmatchedPos = 5;
+ return 37;
+ }
+ if ((active0 & 0x44b042002002000L) != 0L)
+ return 37;
+ return -1;
+ case 6:
+ if ((active0 & 0x8890815000401000L) != 0L)
+ {
+ jjmatchedKind = 77;
+ jjmatchedPos = 6;
+ return 37;
+ }
+ if ((active0 & 0x600090804000L) != 0L)
+ return 37;
+ return -1;
+ case 7:
+ if ((active0 & 0x880815000000000L) != 0L)
+ {
+ jjmatchedKind = 77;
+ jjmatchedPos = 7;
+ return 37;
+ }
+ if ((active0 & 0x8010000000401000L) != 0L)
+ return 37;
+ return -1;
+ case 8:
+ if ((active0 & 0x800810000000000L) != 0L)
+ return 37;
+ if ((active0 & 0x80005000000000L) != 0L)
+ {
+ jjmatchedKind = 77;
+ jjmatchedPos = 8;
+ return 37;
+ }
+ return -1;
+ case 9:
+ if ((active0 & 0x80000000000000L) != 0L)
+ {
+ jjmatchedKind = 77;
+ jjmatchedPos = 9;
+ return 37;
+ }
+ if ((active0 & 0x5000000000L) != 0L)
+ return 37;
+ return -1;
+ case 10:
+ if ((active0 & 0x80000000000000L) != 0L)
+ {
+ jjmatchedKind = 77;
+ jjmatchedPos = 10;
+ return 37;
+ }
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_0(int pos, long active0, long active1, long active2)
+{
+ return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0, active1, active2), pos + 1);
+}
+private int jjStopAtPos(int pos, int kind)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ return pos + 1;
+}
+private int jjMoveStringLiteralDfa0_0()
+{
+ switch(curChar)
+ {
+ case 26:
+ return jjStopAtPos(0, 128);
+ case 33:
+ jjmatchedKind = 92;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x800000000L);
+ case 37:
+ jjmatchedKind = 111;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x100000000000000L);
+ case 38:
+ jjmatchedKind = 108;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x20002000000000L);
+ case 40:
+ return jjStopAtPos(0, 80);
+ case 41:
+ return jjStopAtPos(0, 81);
+ case 42:
+ jjmatchedKind = 106;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x8000000000000L);
+ case 43:
+ jjmatchedKind = 104;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x2004000000000L);
+ case 44:
+ return jjStopAtPos(0, 87);
+ case 45:
+ jjmatchedKind = 105;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x4008000000000L);
+ case 46:
+ jjmatchedKind = 88;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x1000000000000000L);
+ case 47:
+ jjmatchedKind = 107;
+ return jjMoveStringLiteralDfa1_0(0x100L, 0x10000000000000L);
+ case 58:
+ return jjStopAtPos(0, 95);
+ case 59:
+ return jjStopAtPos(0, 86);
+ case 60:
+ jjmatchedKind = 91;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x201000200000000L);
+ case 61:
+ jjmatchedKind = 90;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x100000000L);
+ case 62:
+ jjmatchedKind = 127;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x6c00000400000000L);
+ case 63:
+ return jjStopAtPos(0, 94);
+ case 64:
+ return jjStopAtPos(0, 89);
+ case 91:
+ return jjStopAtPos(0, 84);
+ case 93:
+ return jjStopAtPos(0, 85);
+ case 94:
+ jjmatchedKind = 110;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x80000000000000L);
+ case 97:
+ return jjMoveStringLiteralDfa1_0(0x3000L, 0x0L);
+ case 98:
+ return jjMoveStringLiteralDfa1_0(0x1c000L, 0x0L);
+ case 99:
+ return jjMoveStringLiteralDfa1_0(0x7e0000L, 0x0L);
+ case 100:
+ return jjMoveStringLiteralDfa1_0(0x3800000L, 0x0L);
+ case 101:
+ return jjMoveStringLiteralDfa1_0(0x1c000000L, 0x0L);
+ case 102:
+ return jjMoveStringLiteralDfa1_0(0x3e0000000L, 0x0L);
+ case 103:
+ return jjMoveStringLiteralDfa1_0(0x400000000L, 0x0L);
+ case 105:
+ return jjMoveStringLiteralDfa1_0(0x1f800000000L, 0x0L);
+ case 108:
+ return jjMoveStringLiteralDfa1_0(0x20000000000L, 0x0L);
+ case 110:
+ return jjMoveStringLiteralDfa1_0(0x1c0000000000L, 0x0L);
+ case 112:
+ return jjMoveStringLiteralDfa1_0(0x1e00000000000L, 0x0L);
+ case 114:
+ return jjMoveStringLiteralDfa1_0(0x2000000000000L, 0x0L);
+ case 115:
+ return jjMoveStringLiteralDfa1_0(0xfc000000000000L, 0x0L);
+ case 116:
+ return jjMoveStringLiteralDfa1_0(0x3f00000000000000L, 0x0L);
+ case 118:
+ return jjMoveStringLiteralDfa1_0(0xc000000000000000L, 0x0L);
+ case 119:
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x1L);
+ case 123:
+ return jjStopAtPos(0, 82);
+ case 124:
+ jjmatchedKind = 109;
+ return jjMoveStringLiteralDfa1_0(0x0L, 0x40001000000000L);
+ case 125:
+ return jjStopAtPos(0, 83);
+ case 126:
+ return jjStopAtPos(0, 93);
+ default :
+ return jjMoveNfa_0(0, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_0(long active0, long active1)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(0, active0, active1, 0L);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 38:
+ if ((active1 & 0x2000000000L) != 0L)
+ return jjStopAtPos(1, 101);
+ break;
+ case 42:
+ if ((active0 & 0x100L) != 0L)
+ return jjStartNfaWithStates_0(1, 8, 59);
+ break;
+ case 43:
+ if ((active1 & 0x4000000000L) != 0L)
+ return jjStopAtPos(1, 102);
+ break;
+ case 45:
+ if ((active1 & 0x8000000000L) != 0L)
+ return jjStopAtPos(1, 103);
+ break;
+ case 46:
+ return jjMoveStringLiteralDfa2_0(active0, 0L, active1, 0x1000000000000000L);
+ case 60:
+ if ((active1 & 0x1000000000000L) != 0L)
+ {
+ jjmatchedKind = 112;
+ jjmatchedPos = 1;
+ }
+ return jjMoveStringLiteralDfa2_0(active0, 0L, active1, 0x200000000000000L);
+ case 61:
+ if ((active1 & 0x100000000L) != 0L)
+ return jjStopAtPos(1, 96);
+ else if ((active1 & 0x200000000L) != 0L)
+ return jjStopAtPos(1, 97);
+ else if ((active1 & 0x400000000L) != 0L)
+ return jjStopAtPos(1, 98);
+ else if ((active1 & 0x800000000L) != 0L)
+ return jjStopAtPos(1, 99);
+ else if ((active1 & 0x2000000000000L) != 0L)
+ return jjStopAtPos(1, 113);
+ else if ((active1 & 0x4000000000000L) != 0L)
+ return jjStopAtPos(1, 114);
+ else if ((active1 & 0x8000000000000L) != 0L)
+ return jjStopAtPos(1, 115);
+ else if ((active1 & 0x10000000000000L) != 0L)
+ return jjStopAtPos(1, 116);
+ else if ((active1 & 0x20000000000000L) != 0L)
+ return jjStopAtPos(1, 117);
+ else if ((active1 & 0x40000000000000L) != 0L)
+ return jjStopAtPos(1, 118);
+ else if ((active1 & 0x80000000000000L) != 0L)
+ return jjStopAtPos(1, 119);
+ else if ((active1 & 0x100000000000000L) != 0L)
+ return jjStopAtPos(1, 120);
+ break;
+ case 62:
+ if ((active1 & 0x4000000000000000L) != 0L)
+ {
+ jjmatchedKind = 126;
+ jjmatchedPos = 1;
+ }
+ return jjMoveStringLiteralDfa2_0(active0, 0L, active1, 0x2c00000000000000L);
+ case 97:
+ return jjMoveStringLiteralDfa2_0(active0, 0x240020060000L, active1, 0L);
+ case 98:
+ return jjMoveStringLiteralDfa2_0(active0, 0x1000L, active1, 0L);
+ case 101:
+ return jjMoveStringLiteralDfa2_0(active0, 0x2080000800000L, active1, 0L);
+ case 102:
+ if ((active0 & 0x800000000L) != 0L)
+ return jjStartNfaWithStates_0(1, 35, 37);
+ break;
+ case 104:
+ return jjMoveStringLiteralDfa2_0(active0, 0x704000000080000L, active1, 0x1L);
+ case 105:
+ return jjMoveStringLiteralDfa2_0(active0, 0xc0000000L, active1, 0L);
+ case 108:
+ return jjMoveStringLiteralDfa2_0(active0, 0x104100000L, active1, 0L);
+ case 109:
+ return jjMoveStringLiteralDfa2_0(active0, 0x3000000000L, active1, 0L);
+ case 110:
+ return jjMoveStringLiteralDfa2_0(active0, 0x1c008000000L, active1, 0L);
+ case 111:
+ if ((active0 & 0x1000000L) != 0L)
+ {
+ jjmatchedKind = 24;
+ jjmatchedPos = 1;
+ }
+ return jjMoveStringLiteralDfa2_0(active0, 0xc000020602604000L, active1, 0L);
+ case 114:
+ return jjMoveStringLiteralDfa2_0(active0, 0x3800c00000008000L, active1, 0L);
+ case 115:
+ return jjMoveStringLiteralDfa2_0(active0, 0x2000L, active1, 0L);
+ case 116:
+ return jjMoveStringLiteralDfa2_0(active0, 0x18000000000000L, active1, 0L);
+ case 117:
+ return jjMoveStringLiteralDfa2_0(active0, 0x21100000000000L, active1, 0L);
+ case 119:
+ return jjMoveStringLiteralDfa2_0(active0, 0x40000000000000L, active1, 0L);
+ case 120:
+ return jjMoveStringLiteralDfa2_0(active0, 0x10000000L, active1, 0L);
+ case 121:
+ return jjMoveStringLiteralDfa2_0(active0, 0x80000000010000L, active1, 0L);
+ case 124:
+ if ((active1 & 0x1000000000L) != 0L)
+ return jjStopAtPos(1, 100);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_0(0, active0, active1, 0L);
+}
+private int jjMoveStringLiteralDfa2_0(long old0, long active0, long old1, long active1)
+{
+ if (((active0 &= old0) | (active1 &= old1)) == 0L)
+ return jjStartNfa_0(0, old0, old1, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(1, active0, active1, 0L);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 46:
+ if ((active1 & 0x1000000000000000L) != 0L)
+ return jjStopAtPos(2, 124);
+ break;
+ case 61:
+ if ((active1 & 0x200000000000000L) != 0L)
+ return jjStopAtPos(2, 121);
+ else if ((active1 & 0x400000000000000L) != 0L)
+ return jjStopAtPos(2, 122);
+ break;
+ case 62:
+ if ((active1 & 0x2000000000000000L) != 0L)
+ {
+ jjmatchedKind = 125;
+ jjmatchedPos = 2;
+ }
+ return jjMoveStringLiteralDfa3_0(active0, 0L, active1, 0x800000000000000L);
+ case 97:
+ return jjMoveStringLiteralDfa3_0(active0, 0x808000000180000L, active1, 0L);
+ case 98:
+ return jjMoveStringLiteralDfa3_0(active0, 0x1000000000000L, active1, 0L);
+ case 99:
+ return jjMoveStringLiteralDfa3_0(active0, 0x200000000000L, active1, 0L);
+ case 101:
+ return jjMoveStringLiteralDfa3_0(active0, 0x8000L, active1, 0L);
+ case 102:
+ return jjMoveStringLiteralDfa3_0(active0, 0x800000L, active1, 0L);
+ case 105:
+ return jjMoveStringLiteralDfa3_0(active0, 0x4140400000000000L, active1, 0x1L);
+ case 108:
+ return jjMoveStringLiteralDfa3_0(active0, 0x8000100020000000L, active1, 0L);
+ case 110:
+ return jjMoveStringLiteralDfa3_0(active0, 0x800200c0600000L, active1, 0L);
+ case 111:
+ return jjMoveStringLiteralDfa3_0(active0, 0x4800100004000L, active1, 0L);
+ case 112:
+ return jjMoveStringLiteralDfa3_0(active0, 0x20003000000000L, active1, 0L);
+ case 114:
+ if ((active0 & 0x200000000L) != 0L)
+ return jjStartNfaWithStates_0(2, 33, 37);
+ return jjMoveStringLiteralDfa3_0(active0, 0x610000000000000L, active1, 0L);
+ case 115:
+ return jjMoveStringLiteralDfa3_0(active0, 0x4004023000L, active1, 0L);
+ case 116:
+ if ((active0 & 0x8000000000L) != 0L)
+ {
+ jjmatchedKind = 39;
+ jjmatchedPos = 2;
+ }
+ return jjMoveStringLiteralDfa3_0(active0, 0x2050410050000L, active1, 0L);
+ case 117:
+ return jjMoveStringLiteralDfa3_0(active0, 0x100000000a000000L, active1, 0L);
+ case 119:
+ if ((active0 & 0x80000000000L) != 0L)
+ return jjStartNfaWithStates_0(2, 43, 37);
+ break;
+ case 121:
+ if ((active0 & 0x2000000000000000L) != 0L)
+ return jjStartNfaWithStates_0(2, 61, 37);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_0(1, active0, active1, 0L);
+}
+private int jjMoveStringLiteralDfa3_0(long old0, long active0, long old1, long active1)
+{
+ if (((active0 &= old0) | (active1 &= old1)) == 0L)
+ return jjStartNfa_0(1, old0, old1, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(2, active0, active1, 0L);
+ return 3;
+ }
+ switch(curChar)
+ {
+ case 61:
+ if ((active1 & 0x800000000000000L) != 0L)
+ return jjStopAtPos(3, 123);
+ break;
+ case 97:
+ return jjMoveStringLiteralDfa4_0(active0, 0x80000001c0808000L, active1, 0L);
+ case 98:
+ return jjMoveStringLiteralDfa4_0(active0, 0x2000000L, active1, 0L);
+ case 99:
+ return jjMoveStringLiteralDfa4_0(active0, 0x80000000040000L, active1, 0L);
+ case 100:
+ if ((active0 & 0x4000000000000000L) != 0L)
+ return jjStartNfaWithStates_0(3, 62, 37);
+ break;
+ case 101:
+ if ((active0 & 0x10000L) != 0L)
+ return jjStartNfaWithStates_0(3, 16, 37);
+ else if ((active0 & 0x20000L) != 0L)
+ return jjStartNfaWithStates_0(3, 17, 37);
+ else if ((active0 & 0x4000000L) != 0L)
+ return jjStartNfaWithStates_0(3, 26, 37);
+ else if ((active0 & 0x1000000000000000L) != 0L)
+ return jjStartNfaWithStates_0(3, 60, 37);
+ return jjMoveStringLiteralDfa4_0(active0, 0x20010010002000L, active1, 0L);
+ case 103:
+ if ((active0 & 0x20000000000L) != 0L)
+ return jjStartNfaWithStates_0(3, 41, 37);
+ break;
+ case 105:
+ return jjMoveStringLiteralDfa4_0(active0, 0x10040000000000L, active1, 0L);
+ case 107:
+ return jjMoveStringLiteralDfa4_0(active0, 0x200000000000L, active1, 0L);
+ case 108:
+ if ((active0 & 0x100000000000L) != 0L)
+ return jjStartNfaWithStates_0(3, 44, 37);
+ return jjMoveStringLiteralDfa4_0(active0, 0x1001000004000L, active1, 0x1L);
+ case 109:
+ if ((active0 & 0x8000000L) != 0L)
+ return jjStartNfaWithStates_0(3, 27, 37);
+ break;
+ case 110:
+ return jjMoveStringLiteralDfa4_0(active0, 0x800000000000000L, active1, 0L);
+ case 111:
+ if ((active0 & 0x400000000L) != 0L)
+ return jjStartNfaWithStates_0(3, 34, 37);
+ return jjMoveStringLiteralDfa4_0(active0, 0x600002000000000L, active1, 0L);
+ case 114:
+ if ((active0 & 0x80000L) != 0L)
+ return jjStartNfaWithStates_0(3, 19, 37);
+ return jjMoveStringLiteralDfa4_0(active0, 0x4000000000000L, active1, 0L);
+ case 115:
+ if ((active0 & 0x100000000000000L) != 0L)
+ return jjStartNfaWithStates_0(3, 56, 37);
+ return jjMoveStringLiteralDfa4_0(active0, 0x20300000L, active1, 0L);
+ case 116:
+ return jjMoveStringLiteralDfa4_0(active0, 0x48804000401000L, active1, 0L);
+ case 117:
+ return jjMoveStringLiteralDfa4_0(active0, 0x2000000000000L, active1, 0L);
+ case 118:
+ return jjMoveStringLiteralDfa4_0(active0, 0x400000000000L, active1, 0L);
+ default :
+ break;
+ }
+ return jjStartNfa_0(2, active0, active1, 0L);
+}
+private int jjMoveStringLiteralDfa4_0(long old0, long active0, long old1, long active1)
+{
+ if (((active0 &= old0) | (active1 &= old1)) == 0L)
+ return jjStartNfa_0(2, old0, old1, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(3, active0, active1, 0L);
+ return 4;
+ }
+ switch(curChar)
+ {
+ case 97:
+ return jjMoveStringLiteralDfa5_0(active0, 0x604000000000L, active1, 0L);
+ case 99:
+ return jjMoveStringLiteralDfa5_0(active0, 0x50000000000000L, active1, 0L);
+ case 101:
+ if ((active0 & 0x20000000L) != 0L)
+ return jjStartNfaWithStates_0(4, 29, 37);
+ else if ((active1 & 0x1L) != 0L)
+ return jjStartNfaWithStates_0(4, 64, 37);
+ return jjMoveStringLiteralDfa5_0(active0, 0x801000004000L, active1, 0L);
+ case 104:
+ if ((active0 & 0x40000L) != 0L)
+ return jjStartNfaWithStates_0(4, 18, 37);
+ return jjMoveStringLiteralDfa5_0(active0, 0x80000000000000L, active1, 0L);
+ case 105:
+ return jjMoveStringLiteralDfa5_0(active0, 0x9000000400000L, active1, 0L);
+ case 107:
+ if ((active0 & 0x8000L) != 0L)
+ return jjStartNfaWithStates_0(4, 15, 37);
+ break;
+ case 108:
+ if ((active0 & 0x40000000L) != 0L)
+ {
+ jjmatchedKind = 30;
+ jjmatchedPos = 4;
+ }
+ return jjMoveStringLiteralDfa5_0(active0, 0x82000000L, active1, 0L);
+ case 110:
+ return jjMoveStringLiteralDfa5_0(active0, 0x10000000L, active1, 0L);
+ case 114:
+ if ((active0 & 0x20000000000000L) != 0L)
+ return jjStartNfaWithStates_0(4, 53, 37);
+ return jjMoveStringLiteralDfa5_0(active0, 0x2012000003000L, active1, 0L);
+ case 115:
+ if ((active0 & 0x100000L) != 0L)
+ return jjStartNfaWithStates_0(4, 20, 37);
+ return jjMoveStringLiteralDfa5_0(active0, 0x800000000000000L, active1, 0L);
+ case 116:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStartNfaWithStates_0(4, 21, 37);
+ else if ((active0 & 0x100000000L) != 0L)
+ return jjStartNfaWithStates_0(4, 32, 37);
+ else if ((active0 & 0x4000000000000L) != 0L)
+ return jjStartNfaWithStates_0(4, 50, 37);
+ return jjMoveStringLiteralDfa5_0(active0, 0x8000000000000000L, active1, 0L);
+ case 117:
+ return jjMoveStringLiteralDfa5_0(active0, 0x800000L, active1, 0L);
+ case 118:
+ return jjMoveStringLiteralDfa5_0(active0, 0x40000000000L, active1, 0L);
+ case 119:
+ if ((active0 & 0x200000000000000L) != 0L)
+ {
+ jjmatchedKind = 57;
+ jjmatchedPos = 4;
+ }
+ return jjMoveStringLiteralDfa5_0(active0, 0x400000000000000L, active1, 0L);
+ default :
+ break;
+ }
+ return jjStartNfa_0(3, active0, active1, 0L);
+}
+private int jjMoveStringLiteralDfa5_0(long old0, long active0, long old1, long active1)
+{
+ if (((active0 &= old0) | (active1 &= old1)) == 0L)
+ return jjStartNfa_0(3, old0, old1, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(4, active0, 0L, 0L);
+ return 5;
+ }
+ switch(curChar)
+ {
+ case 97:
+ return jjMoveStringLiteralDfa6_0(active0, 0x5000L);
+ case 99:
+ if ((active0 & 0x1000000000000L) != 0L)
+ return jjStartNfaWithStates_0(5, 48, 37);
+ else if ((active0 & 0x8000000000000L) != 0L)
+ return jjStartNfaWithStates_0(5, 51, 37);
+ return jjMoveStringLiteralDfa6_0(active0, 0x800000000000L);
+ case 100:
+ return jjMoveStringLiteralDfa6_0(active0, 0x10000000L);
+ case 101:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStartNfaWithStates_0(5, 25, 37);
+ else if ((active0 & 0x40000000000L) != 0L)
+ return jjStartNfaWithStates_0(5, 42, 37);
+ break;
+ case 102:
+ return jjMoveStringLiteralDfa6_0(active0, 0x10000000000L);
+ case 103:
+ return jjMoveStringLiteralDfa6_0(active0, 0x200000000000L);
+ case 104:
+ if ((active0 & 0x40000000000000L) != 0L)
+ return jjStartNfaWithStates_0(5, 54, 37);
+ break;
+ case 105:
+ return jjMoveStringLiteralDfa6_0(active0, 0x8800000000000000L);
+ case 108:
+ return jjMoveStringLiteralDfa6_0(active0, 0x80800000L);
+ case 109:
+ return jjMoveStringLiteralDfa6_0(active0, 0x1000000000L);
+ case 110:
+ if ((active0 & 0x2000000000000L) != 0L)
+ return jjStartNfaWithStates_0(5, 49, 37);
+ return jjMoveStringLiteralDfa6_0(active0, 0x4000400000L);
+ case 114:
+ return jjMoveStringLiteralDfa6_0(active0, 0x80000000000000L);
+ case 115:
+ if ((active0 & 0x400000000000000L) != 0L)
+ return jjStartNfaWithStates_0(5, 58, 37);
+ break;
+ case 116:
+ if ((active0 & 0x2000L) != 0L)
+ return jjStartNfaWithStates_0(5, 13, 37);
+ else if ((active0 & 0x2000000000L) != 0L)
+ return jjStartNfaWithStates_0(5, 37, 37);
+ return jjMoveStringLiteralDfa6_0(active0, 0x10400000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_0(4, active0, 0L, 0L);
+}
+private int jjMoveStringLiteralDfa6_0(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_0(4, old0, 0L, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(5, active0, 0L, 0L);
+ return 6;
+ }
+ switch(curChar)
+ {
+ case 97:
+ return jjMoveStringLiteralDfa7_0(active0, 0x10000000000L);
+ case 99:
+ return jjMoveStringLiteralDfa7_0(active0, 0x4000001000L);
+ case 101:
+ if ((active0 & 0x200000000000L) != 0L)
+ return jjStartNfaWithStates_0(6, 45, 37);
+ else if ((active0 & 0x400000000000L) != 0L)
+ return jjStartNfaWithStates_0(6, 46, 37);
+ return jjMoveStringLiteralDfa7_0(active0, 0x800001000000000L);
+ case 102:
+ return jjMoveStringLiteralDfa7_0(active0, 0x10000000000000L);
+ case 108:
+ return jjMoveStringLiteralDfa7_0(active0, 0x8000000000000000L);
+ case 110:
+ if ((active0 & 0x4000L) != 0L)
+ return jjStartNfaWithStates_0(6, 14, 37);
+ break;
+ case 111:
+ return jjMoveStringLiteralDfa7_0(active0, 0x80000000000000L);
+ case 115:
+ if ((active0 & 0x10000000L) != 0L)
+ return jjStartNfaWithStates_0(6, 28, 37);
+ break;
+ case 116:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_0(6, 23, 37);
+ return jjMoveStringLiteralDfa7_0(active0, 0x800000000000L);
+ case 117:
+ return jjMoveStringLiteralDfa7_0(active0, 0x400000L);
+ case 121:
+ if ((active0 & 0x80000000L) != 0L)
+ return jjStartNfaWithStates_0(6, 31, 37);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_0(5, active0, 0L, 0L);
+}
+private int jjMoveStringLiteralDfa7_0(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_0(5, old0, 0L, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(6, active0, 0L, 0L);
+ return 7;
+ }
+ switch(curChar)
+ {
+ case 99:
+ return jjMoveStringLiteralDfa8_0(active0, 0x10000000000L);
+ case 101:
+ if ((active0 & 0x400000L) != 0L)
+ return jjStartNfaWithStates_0(7, 22, 37);
+ else if ((active0 & 0x8000000000000000L) != 0L)
+ return jjStartNfaWithStates_0(7, 63, 37);
+ return jjMoveStringLiteralDfa8_0(active0, 0x804000000000L);
+ case 110:
+ return jjMoveStringLiteralDfa8_0(active0, 0x880001000000000L);
+ case 112:
+ if ((active0 & 0x10000000000000L) != 0L)
+ return jjStartNfaWithStates_0(7, 52, 37);
+ break;
+ case 116:
+ if ((active0 & 0x1000L) != 0L)
+ return jjStartNfaWithStates_0(7, 12, 37);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_0(6, active0, 0L, 0L);
+}
+private int jjMoveStringLiteralDfa8_0(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_0(6, old0, 0L, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(7, active0, 0L, 0L);
+ return 8;
+ }
+ switch(curChar)
+ {
+ case 100:
+ if ((active0 & 0x800000000000L) != 0L)
+ return jjStartNfaWithStates_0(8, 47, 37);
+ break;
+ case 101:
+ if ((active0 & 0x10000000000L) != 0L)
+ return jjStartNfaWithStates_0(8, 40, 37);
+ break;
+ case 105:
+ return jjMoveStringLiteralDfa9_0(active0, 0x80000000000000L);
+ case 111:
+ return jjMoveStringLiteralDfa9_0(active0, 0x4000000000L);
+ case 116:
+ if ((active0 & 0x800000000000000L) != 0L)
+ return jjStartNfaWithStates_0(8, 59, 37);
+ return jjMoveStringLiteralDfa9_0(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_0(7, active0, 0L, 0L);
+}
+private int jjMoveStringLiteralDfa9_0(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_0(7, old0, 0L, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(8, active0, 0L, 0L);
+ return 9;
+ }
+ switch(curChar)
+ {
+ case 102:
+ if ((active0 & 0x4000000000L) != 0L)
+ return jjStartNfaWithStates_0(9, 38, 37);
+ break;
+ case 115:
+ if ((active0 & 0x1000000000L) != 0L)
+ return jjStartNfaWithStates_0(9, 36, 37);
+ break;
+ case 122:
+ return jjMoveStringLiteralDfa10_0(active0, 0x80000000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_0(8, active0, 0L, 0L);
+}
+private int jjMoveStringLiteralDfa10_0(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_0(8, old0, 0L, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(9, active0, 0L, 0L);
+ return 10;
+ }
+ switch(curChar)
+ {
+ case 101:
+ return jjMoveStringLiteralDfa11_0(active0, 0x80000000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_0(9, active0, 0L, 0L);
+}
+private int jjMoveStringLiteralDfa11_0(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_0(9, old0, 0L, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(10, active0, 0L, 0L);
+ return 11;
+ }
+ switch(curChar)
+ {
+ case 100:
+ if ((active0 & 0x80000000000000L) != 0L)
+ return jjStartNfaWithStates_0(11, 55, 37);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_0(10, active0, 0L, 0L);
+}
+private int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_0(state, pos + 1);
+}
+static final long[] jjbitVec0 = {
+ 0xfffffffffffffffeL, 0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+static final long[] jjbitVec2 = {
+ 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+static final long[] jjbitVec3 = {
+ 0xfff0000000200002L, 0xffffffffffffdfffL, 0xfffff00f7fffffffL, 0x12000000007fffffL
+};
+static final long[] jjbitVec4 = {
+ 0x0L, 0x0L, 0x420043c00000000L, 0xff7fffffff7fffffL
+};
+static final long[] jjbitVec5 = {
+ 0x7fffffffffffffL, 0xffffffffffff0000L, 0xffffffffffffffffL, 0x401f0003ffc3L
+};
+static final long[] jjbitVec6 = {
+ 0x0L, 0x400000000000000L, 0xfffffffbffffd740L, 0xfbfffffffff7fffL
+};
+static final long[] jjbitVec7 = {
+ 0xffffffffffffffffL, 0xffffffffffffffffL, 0xfffffffffffffc03L, 0x33fffffffff7fffL
+};
+static final long[] jjbitVec8 = {
+ 0xfffe00000000ffffL, 0xfffffffe027fffffL, 0xffL, 0x707ffffff0000L
+};
+static final long[] jjbitVec9 = {
+ 0x7fffffe00000000L, 0xfffec000000007ffL, 0xffffffffffffffffL, 0x9c00c060002fffffL
+};
+static final long[] jjbitVec10 = {
+ 0xfffffffd0000L, 0xe000L, 0x2003fffffffffL, 0x0L
+};
+static final long[] jjbitVec11 = {
+ 0x23fffffffffffff0L, 0x3ff010000L, 0x23c5fdfffff99fe0L, 0xf0003b0000000L
+};
+static final long[] jjbitVec12 = {
+ 0x36dfdfffff987e0L, 0x1c00005e000000L, 0x23edfdfffffbbfe0L, 0x2000300010000L
+};
+static final long[] jjbitVec13 = {
+ 0x23edfdfffff99fe0L, 0x20003b0000000L, 0x3bfc718d63dc7e8L, 0x200000000000000L
+};
+static final long[] jjbitVec14 = {
+ 0x3effdfffffddfe0L, 0x300000000L, 0x23effdfffffddfe0L, 0x340000000L
+};
+static final long[] jjbitVec15 = {
+ 0x3fffdfffffddfe0L, 0x300000000L, 0x2ffbfffffc7fffe0L, 0x7fL
+};
+static final long[] jjbitVec16 = {
+ 0x800dfffffffffffeL, 0x7fL, 0x200decaefef02596L, 0x3000005fL
+};
+static final long[] jjbitVec17 = {
+ 0x1L, 0x7fffffffeffL, 0xf00L, 0x0L
+};
+static final long[] jjbitVec18 = {
+ 0x6fbffffffffL, 0x3f0000L, 0xffffffff00000000L, 0x1ffffffffff003fL
+};
+static final long[] jjbitVec19 = {
+ 0xffffffffffffffffL, 0xffffffff83ffffffL, 0xffffff07ffffffffL, 0x3ffffffffffffffL
+};
+static final long[] jjbitVec20 = {
+ 0xffffffffffffff7fL, 0xffffffff3d7f3d7fL, 0x7f3d7fffffff3d7fL, 0xffff7fffff7f7f3dL
+};
+static final long[] jjbitVec21 = {
+ 0xffffffff7f3d7fffL, 0x7ffff7fL, 0xffffffff00000000L, 0x1fffffffffffffL
+};
+static final long[] jjbitVec22 = {
+ 0xffffffffffffffffL, 0x7f9fffffffffffL, 0xffffffff07fffffeL, 0x1c7ffffffffffL
+};
+static final long[] jjbitVec23 = {
+ 0x3ffff0003dfffL, 0x1dfff0003ffffL, 0xfffffffffffffL, 0x18800000L
+};
+static final long[] jjbitVec24 = {
+ 0xffffffff00000000L, 0xffffffffffffffL, 0x1ffffffffffL, 0x0L
+};
+static final long[] jjbitVec25 = {
+ 0x1fffffffL, 0x1f3fffffff0000L, 0x0L, 0x0L
+};
+static final long[] jjbitVec26 = {
+ 0xffffffffffffffffL, 0xfffffffffffL, 0x0L, 0x0L
+};
+static final long[] jjbitVec27 = {
+ 0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffffff0fffffffL, 0x3ffffffffffffffL
+};
+static final long[] jjbitVec28 = {
+ 0xffffffff3f3fffffL, 0x3fffffffaaff3f3fL, 0x5fdfffffffffffffL, 0x1fdc1fff0fcf1fdcL
+};
+static final long[] jjbitVec29 = {
+ 0x8000000000000000L, 0x8002000000100001L, 0x3ffff00000000L, 0x0L
+};
+static final long[] jjbitVec30 = {
+ 0xe3fbbd503e2ffc84L, 0xffffffff000003e0L, 0xfL, 0x0L
+};
+static final long[] jjbitVec31 = {
+ 0x1f3e03fe000000e0L, 0xfffffffffffffffeL, 0xfffffffee07fffffL, 0xffffffffffffffffL
+};
+static final long[] jjbitVec32 = {
+ 0xfffe1fffffffffe0L, 0xffffffffffffffffL, 0xffffff00007fffL, 0xffff000000000000L
+};
+static final long[] jjbitVec33 = {
+ 0xffffffffffffffffL, 0xffffffffffffffffL, 0x3fffffffffffffL, 0x0L
+};
+static final long[] jjbitVec34 = {
+ 0xffffffffffffffffL, 0xffffffffffffffffL, 0x3fffffffffL, 0x0L
+};
+static final long[] jjbitVec35 = {
+ 0xffffffffffffffffL, 0xffffffffffffffffL, 0x1fffL, 0x0L
+};
+static final long[] jjbitVec36 = {
+ 0xffffffffffffffffL, 0xffffffffffffffffL, 0xfffffffffL, 0x0L
+};
+static final long[] jjbitVec37 = {
+ 0x6L, 0x0L, 0x0L, 0x0L
+};
+static final long[] jjbitVec38 = {
+ 0xffff3fffffffffffL, 0x7ffffffffffL, 0x0L, 0x0L
+};
+static final long[] jjbitVec39 = {
+ 0x5f7ffdffa0f8007fL, 0xffffffffffffffdbL, 0x3ffffffffffffL, 0xfffffffffff80000L
+};
+static final long[] jjbitVec40 = {
+ 0x3fffffffffffffffL, 0xffffffffffff0000L, 0xfffffffffffcffffL, 0x1fff0000000000ffL
+};
+static final long[] jjbitVec41 = {
+ 0x18000000000000L, 0xffdf02000000e000L, 0xffffffffffffffffL, 0x1fffffffffffffffL
+};
+static final long[] jjbitVec42 = {
+ 0x87fffffe00000010L, 0xffffffe007fffffeL, 0x7fffffffffffffffL, 0x631cfcfcfcL
+};
+static final long[] jjbitVec43 = {
+ 0x0L, 0x0L, 0x420243cffffffffL, 0xff7fffffff7fffffL
+};
+static final long[] jjbitVec44 = {
+ 0xffffffffffffffffL, 0x400ffffe0ffffffL, 0xfffffffbffffd740L, 0xfbfffffffff7fffL
+};
+static final long[] jjbitVec45 = {
+ 0xffffffffffffffffL, 0xffffffffffffffffL, 0xfffffffffffffc7bL, 0x33fffffffff7fffL
+};
+static final long[] jjbitVec46 = {
+ 0xfffe00000000ffffL, 0xfffffffe027fffffL, 0xbbfffffbfffe00ffL, 0x707ffffff0016L
+};
+static final long[] jjbitVec47 = {
+ 0x7fffffe003f000fL, 0xffffc3ff01ffffffL, 0xffffffffffffffffL, 0x9ffffdffbfefffffL
+};
+static final long[] jjbitVec48 = {
+ 0xffffffffffff8000L, 0xe7ffL, 0x3ffffffffffffL, 0x0L
+};
+static final long[] jjbitVec49 = {
+ 0xf3fffffffffffffeL, 0xffcfff1f3fffL, 0xf3c5fdfffff99feeL, 0xfffcfb080399fL
+};
+static final long[] jjbitVec50 = {
+ 0xd36dfdfffff987eeL, 0x1fffc05e003987L, 0xf3edfdfffffbbfeeL, 0x2ffcf00013bbfL
+};
+static final long[] jjbitVec51 = {
+ 0xf3edfdfffff99feeL, 0x2ffc3b0c0398fL, 0xc3bfc718d63dc7ecL, 0x200ff8000803dc7L
+};
+static final long[] jjbitVec52 = {
+ 0xc3effdfffffddfeeL, 0xffc300603ddfL, 0xf3effdfffffddfecL, 0xffc340603ddfL
+};
+static final long[] jjbitVec53 = {
+ 0xc3fffdfffffddfecL, 0xffc300803dcfL, 0x2ffbfffffc7fffecL, 0xc0000ff5f847fL
+};
+static final long[] jjbitVec54 = {
+ 0x87fffffffffffffeL, 0x3ff7fffL, 0x3bffecaefef02596L, 0x33ff3f5fL
+};
+static final long[] jjbitVec55 = {
+ 0xc2a003ff03000001L, 0xfffe07fffffffeffL, 0x1ffffffffeff0fdfL, 0x40L
+};
+static final long[] jjbitVec56 = {
+ 0x3c7f6fbffffffffL, 0x3ff03ffL, 0xffffffff00000000L, 0x1ffffffffff003fL
+};
+static final long[] jjbitVec57 = {
+ 0xffffffff7f3d7fffL, 0x3fe0007ffff7fL, 0xffffffff00000000L, 0x1fffffffffffffL
+};
+static final long[] jjbitVec58 = {
+ 0x1fffff001fdfffL, 0xddfff000fffffL, 0xffffffffffffffffL, 0x3ff388fffffL
+};
+static final long[] jjbitVec59 = {
+ 0xffffffff03ff3800L, 0xffffffffffffffL, 0x3ffffffffffL, 0x0L
+};
+static final long[] jjbitVec60 = {
+ 0xfff0fff1fffffffL, 0x1f3fffffffffc0L, 0x0L, 0x0L
+};
+static final long[] jjbitVec61 = {
+ 0x80007c000000f000L, 0x8002fc0f00100001L, 0x3ffff00000000L, 0x7e21fff0000L
+};
+static final long[] jjbitVec62 = {
+ 0x1f3efffe000000e0L, 0xfffffffffffffffeL, 0xfffffffee67fffffL, 0xffffffffffffffffL
+};
+static final long[] jjbitVec63 = {
+ 0x10000000000006L, 0x0L, 0x0L, 0x0L
+};
+static final long[] jjbitVec64 = {
+ 0x3L, 0x0L, 0x0L, 0x0L
+};
+static final long[] jjbitVec65 = {
+ 0x0L, 0x800000000000000L, 0x0L, 0x0L
+};
+static final long[] jjbitVec66 = {
+ 0x5f7ffdffe0f8007fL, 0xffffffffffffffdbL, 0x3ffffffffffffL, 0xfffffffffff80000L
+};
+static final long[] jjbitVec67 = {
+ 0x18000f0000ffffL, 0xffdf02000000e000L, 0xffffffffffffffffL, 0x9fffffffffffffffL
+};
+static final long[] jjbitVec68 = {
+ 0x87fffffe03ff0010L, 0xffffffe007fffffeL, 0x7fffffffffffffffL, 0xe0000631cfcfcfcL
+};
+private int jjMoveNfa_0(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 86;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 54:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 59;
+ else if (curChar == 47)
+ {
+ if (kind > 6)
+ kind = 6;
+ jjCheckNAddStates(0, 2);
+ }
+ break;
+ case 0:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddStates(3, 9);
+ else if (curChar == 47)
+ jjAddStates(10, 11);
+ else if (curChar == 36)
+ {
+ if (kind > 77)
+ kind = 77;
+ jjCheckNAdd(37);
+ }
+ else if (curChar == 34)
+ jjCheckNAddStates(12, 15);
+ else if (curChar == 39)
+ jjAddStates(16, 18);
+ else if (curChar == 46)
+ jjCheckNAdd(1);
+ if ((0x3fe000000000000L & l) != 0L)
+ {
+ if (kind > 66)
+ kind = 66;
+ jjCheckNAddStates(19, 21);
+ }
+ else if (curChar == 48)
+ {
+ if (kind > 66)
+ kind = 66;
+ jjCheckNAddStates(22, 28);
+ }
+ break;
+ case 1:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAddStates(29, 31);
+ break;
+ case 3:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(4);
+ break;
+ case 4:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAddTwoStates(4, 5);
+ break;
+ case 6:
+ if (curChar == 39)
+ jjAddStates(16, 18);
+ break;
+ case 7:
+ if ((0xffffff7fffffdbffL & l) != 0L)
+ jjCheckNAdd(8);
+ break;
+ case 8:
+ if (curChar == 39 && kind > 75)
+ kind = 75;
+ break;
+ case 10:
+ if ((0x8400000000L & l) != 0L)
+ jjCheckNAdd(8);
+ break;
+ case 11:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(12, 8);
+ break;
+ case 12:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAdd(8);
+ break;
+ case 13:
+ if ((0xf000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 14;
+ break;
+ case 14:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAdd(12);
+ break;
+ case 16:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 17;
+ break;
+ case 17:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 18:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 19;
+ break;
+ case 19:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAdd(8);
+ break;
+ case 21:
+ if (curChar == 34)
+ jjCheckNAddStates(12, 15);
+ break;
+ case 22:
+ if ((0xfffffffbffffdbffL & l) != 0L)
+ jjCheckNAddStates(12, 15);
+ break;
+ case 24:
+ if ((0x8400000000L & l) != 0L)
+ jjCheckNAddStates(12, 15);
+ break;
+ case 26:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 27;
+ break;
+ case 27:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 28;
+ break;
+ case 28:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 29;
+ break;
+ case 29:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddStates(12, 15);
+ break;
+ case 31:
+ if (curChar == 34 && kind > 76)
+ kind = 76;
+ break;
+ case 32:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddStates(32, 36);
+ break;
+ case 33:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddStates(12, 15);
+ break;
+ case 34:
+ if ((0xf000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 35;
+ break;
+ case 35:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAdd(33);
+ break;
+ case 36:
+ if (curChar != 36)
+ break;
+ if (kind > 77)
+ kind = 77;
+ jjCheckNAdd(37);
+ break;
+ case 37:
+ if ((0x3ff00100fffc1ffL & l) == 0L)
+ break;
+ if (kind > 77)
+ kind = 77;
+ jjCheckNAdd(37);
+ break;
+ case 38:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddStates(3, 9);
+ break;
+ case 39:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddStates(37, 39);
+ break;
+ case 41:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(42);
+ break;
+ case 42:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(42, 5);
+ break;
+ case 43:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(43, 44);
+ break;
+ case 45:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(46);
+ break;
+ case 46:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAddTwoStates(46, 5);
+ break;
+ case 47:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(47, 48);
+ break;
+ case 48:
+ if (curChar != 46)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAddStates(40, 42);
+ break;
+ case 49:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAddStates(40, 42);
+ break;
+ case 51:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(52);
+ break;
+ case 52:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAddTwoStates(52, 5);
+ break;
+ case 53:
+ if (curChar == 47)
+ jjAddStates(10, 11);
+ break;
+ case 55:
+ if ((0xffffffffffffdbffL & l) == 0L)
+ break;
+ if (kind > 6)
+ kind = 6;
+ jjCheckNAddStates(0, 2);
+ break;
+ case 56:
+ if ((0x2400L & l) != 0L && kind > 6)
+ kind = 6;
+ break;
+ case 57:
+ if (curChar == 10 && kind > 6)
+ kind = 6;
+ break;
+ case 58:
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 57;
+ break;
+ case 59:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 60;
+ break;
+ case 60:
+ if ((0xffff7fffffffffffL & l) != 0L && kind > 7)
+ kind = 7;
+ break;
+ case 61:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 59;
+ break;
+ case 62:
+ if ((0x3fe000000000000L & l) == 0L)
+ break;
+ if (kind > 66)
+ kind = 66;
+ jjCheckNAddStates(19, 21);
+ break;
+ case 63:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(63, 64);
+ break;
+ case 65:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 66)
+ kind = 66;
+ jjCheckNAdd(65);
+ break;
+ case 66:
+ if (curChar != 48)
+ break;
+ if (kind > 66)
+ kind = 66;
+ jjCheckNAddStates(22, 28);
+ break;
+ case 68:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(68, 64);
+ break;
+ case 69:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(69, 64);
+ break;
+ case 71:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 66)
+ kind = 66;
+ jjstateSet[jjnewStateCnt++] = 71;
+ break;
+ case 72:
+ if ((0xff000000000000L & l) == 0L)
+ break;
+ if (kind > 66)
+ kind = 66;
+ jjCheckNAdd(72);
+ break;
+ case 74:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjAddStates(43, 44);
+ break;
+ case 75:
+ if (curChar == 46)
+ jjCheckNAdd(76);
+ break;
+ case 76:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(76, 77);
+ break;
+ case 78:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(79);
+ break;
+ case 79:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAddTwoStates(79, 5);
+ break;
+ case 81:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddStates(45, 47);
+ break;
+ case 82:
+ if (curChar == 46)
+ jjCheckNAdd(83);
+ break;
+ case 84:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(85);
+ break;
+ case 85:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAddTwoStates(85, 5);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 0:
+ if ((0x7fffffe87fffffeL & l) == 0L)
+ break;
+ if (kind > 77)
+ kind = 77;
+ jjCheckNAdd(37);
+ break;
+ case 2:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(48, 49);
+ break;
+ case 5:
+ if ((0x5000000050L & l) != 0L && kind > 70)
+ kind = 70;
+ break;
+ case 7:
+ if ((0xffffffffefffffffL & l) != 0L)
+ jjCheckNAdd(8);
+ break;
+ case 9:
+ if (curChar == 92)
+ jjAddStates(50, 52);
+ break;
+ case 10:
+ if ((0x14404410000000L & l) != 0L)
+ jjCheckNAdd(8);
+ break;
+ case 15:
+ if (curChar == 117)
+ jjstateSet[jjnewStateCnt++] = 16;
+ break;
+ case 16:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 17;
+ break;
+ case 17:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 18:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 19;
+ break;
+ case 19:
+ if ((0x7e0000007eL & l) != 0L)
+ jjCheckNAdd(8);
+ break;
+ case 20:
+ if (curChar == 92)
+ jjstateSet[jjnewStateCnt++] = 15;
+ break;
+ case 22:
+ if ((0xffffffffefffffffL & l) != 0L)
+ jjCheckNAddStates(12, 15);
+ break;
+ case 23:
+ if (curChar == 92)
+ jjAddStates(53, 55);
+ break;
+ case 24:
+ if ((0x14404410000000L & l) != 0L)
+ jjCheckNAddStates(12, 15);
+ break;
+ case 25:
+ if (curChar == 117)
+ jjstateSet[jjnewStateCnt++] = 26;
+ break;
+ case 26:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 27;
+ break;
+ case 27:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 28;
+ break;
+ case 28:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 29;
+ break;
+ case 29:
+ if ((0x7e0000007eL & l) != 0L)
+ jjCheckNAddStates(12, 15);
+ break;
+ case 30:
+ if (curChar == 92)
+ jjstateSet[jjnewStateCnt++] = 25;
+ break;
+ case 37:
+ if ((0x87fffffe87fffffeL & l) == 0L)
+ break;
+ if (kind > 77)
+ kind = 77;
+ jjCheckNAdd(37);
+ break;
+ case 40:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(56, 57);
+ break;
+ case 44:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(58, 59);
+ break;
+ case 50:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(60, 61);
+ break;
+ case 55:
+ if (kind > 6)
+ kind = 6;
+ jjAddStates(0, 2);
+ break;
+ case 60:
+ if (kind > 7)
+ kind = 7;
+ break;
+ case 64:
+ if ((0x100000001000L & l) != 0L && kind > 65)
+ kind = 65;
+ break;
+ case 67:
+ if ((0x100000001000000L & l) != 0L)
+ jjCheckNAdd(68);
+ break;
+ case 68:
+ if ((0x7e0000007eL & l) != 0L)
+ jjCheckNAddTwoStates(68, 64);
+ break;
+ case 70:
+ if ((0x100000001000000L & l) != 0L)
+ jjCheckNAdd(71);
+ break;
+ case 71:
+ if ((0x7e0000007eL & l) == 0L)
+ break;
+ if (kind > 66)
+ kind = 66;
+ jjCheckNAdd(71);
+ break;
+ case 73:
+ if ((0x100000001000000L & l) != 0L)
+ jjCheckNAddTwoStates(74, 75);
+ break;
+ case 74:
+ if ((0x7e0000007eL & l) != 0L)
+ jjCheckNAddTwoStates(74, 75);
+ break;
+ case 76:
+ if ((0x7e0000007eL & l) != 0L)
+ jjAddStates(62, 63);
+ break;
+ case 77:
+ if ((0x1000000010000L & l) != 0L)
+ jjAddStates(64, 65);
+ break;
+ case 80:
+ if ((0x100000001000000L & l) != 0L)
+ jjCheckNAdd(81);
+ break;
+ case 81:
+ if ((0x7e0000007eL & l) != 0L)
+ jjCheckNAddStates(45, 47);
+ break;
+ case 83:
+ if ((0x1000000010000L & l) != 0L)
+ jjAddStates(66, 67);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 0:
+ if (!jjCanMove_1(hiByte, i1, i2, l1, l2))
+ break;
+ if (kind > 77)
+ kind = 77;
+ jjCheckNAdd(37);
+ break;
+ case 7:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjstateSet[jjnewStateCnt++] = 8;
+ break;
+ case 22:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjAddStates(12, 15);
+ break;
+ case 37:
+ if (!jjCanMove_2(hiByte, i1, i2, l1, l2))
+ break;
+ if (kind > 77)
+ kind = 77;
+ jjCheckNAdd(37);
+ break;
+ case 55:
+ if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+ break;
+ if (kind > 6)
+ kind = 6;
+ jjAddStates(0, 2);
+ break;
+ case 60:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 7)
+ kind = 7;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 86 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private int jjMoveStringLiteralDfa0_2()
+{
+ switch(curChar)
+ {
+ case 42:
+ return jjMoveStringLiteralDfa1_2(0x400L);
+ default :
+ return 1;
+ }
+}
+private int jjMoveStringLiteralDfa1_2(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 47:
+ if ((active0 & 0x400L) != 0L)
+ return jjStopAtPos(1, 10);
+ break;
+ default :
+ return 2;
+ }
+ return 2;
+}
+private int jjMoveStringLiteralDfa0_1()
+{
+ switch(curChar)
+ {
+ case 42:
+ return jjMoveStringLiteralDfa1_1(0x200L);
+ default :
+ return 1;
+ }
+}
+private int jjMoveStringLiteralDfa1_1(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 47:
+ if ((active0 & 0x200L) != 0L)
+ return jjStopAtPos(1, 9);
+ break;
+ default :
+ return 2;
+ }
+ return 2;
+}
+static final int[] jjnextStates = {
+ 55, 56, 58, 39, 40, 5, 43, 44, 47, 48, 54, 61, 22, 23, 30, 31,
+ 7, 9, 20, 63, 64, 65, 67, 69, 64, 70, 72, 73, 80, 1, 2, 5,
+ 22, 23, 33, 30, 31, 39, 40, 5, 49, 50, 5, 74, 75, 81, 82, 83,
+ 3, 4, 10, 11, 13, 24, 32, 34, 41, 42, 45, 46, 51, 52, 76, 77,
+ 78, 79, 84, 85,
+};
+private static final boolean jjCanMove_0(int hiByte, int i1, int i2, long l1, long l2)
+{
+ switch(hiByte)
+ {
+ case 0:
+ return ((jjbitVec2[i2] & l2) != 0L);
+ default :
+ if ((jjbitVec0[i1] & l1) != 0L)
+ return true;
+ return false;
+ }
+}
+private static final boolean jjCanMove_1(int hiByte, int i1, int i2, long l1, long l2)
+{
+ switch(hiByte)
+ {
+ case 0:
+ return ((jjbitVec4[i2] & l2) != 0L);
+ case 2:
+ return ((jjbitVec5[i2] & l2) != 0L);
+ case 3:
+ return ((jjbitVec6[i2] & l2) != 0L);
+ case 4:
+ return ((jjbitVec7[i2] & l2) != 0L);
+ case 5:
+ return ((jjbitVec8[i2] & l2) != 0L);
+ case 6:
+ return ((jjbitVec9[i2] & l2) != 0L);
+ case 7:
+ return ((jjbitVec10[i2] & l2) != 0L);
+ case 9:
+ return ((jjbitVec11[i2] & l2) != 0L);
+ case 10:
+ return ((jjbitVec12[i2] & l2) != 0L);
+ case 11:
+ return ((jjbitVec13[i2] & l2) != 0L);
+ case 12:
+ return ((jjbitVec14[i2] & l2) != 0L);
+ case 13:
+ return ((jjbitVec15[i2] & l2) != 0L);
+ case 14:
+ return ((jjbitVec16[i2] & l2) != 0L);
+ case 15:
+ return ((jjbitVec17[i2] & l2) != 0L);
+ case 16:
+ return ((jjbitVec18[i2] & l2) != 0L);
+ case 17:
+ return ((jjbitVec19[i2] & l2) != 0L);
+ case 18:
+ return ((jjbitVec20[i2] & l2) != 0L);
+ case 19:
+ return ((jjbitVec21[i2] & l2) != 0L);
+ case 20:
+ return ((jjbitVec0[i2] & l2) != 0L);
+ case 22:
+ return ((jjbitVec22[i2] & l2) != 0L);
+ case 23:
+ return ((jjbitVec23[i2] & l2) != 0L);
+ case 24:
+ return ((jjbitVec24[i2] & l2) != 0L);
+ case 25:
+ return ((jjbitVec25[i2] & l2) != 0L);
+ case 29:
+ return ((jjbitVec26[i2] & l2) != 0L);
+ case 30:
+ return ((jjbitVec27[i2] & l2) != 0L);
+ case 31:
+ return ((jjbitVec28[i2] & l2) != 0L);
+ case 32:
+ return ((jjbitVec29[i2] & l2) != 0L);
+ case 33:
+ return ((jjbitVec30[i2] & l2) != 0L);
+ case 48:
+ return ((jjbitVec31[i2] & l2) != 0L);
+ case 49:
+ return ((jjbitVec32[i2] & l2) != 0L);
+ case 77:
+ return ((jjbitVec33[i2] & l2) != 0L);
+ case 159:
+ return ((jjbitVec34[i2] & l2) != 0L);
+ case 164:
+ return ((jjbitVec35[i2] & l2) != 0L);
+ case 215:
+ return ((jjbitVec36[i2] & l2) != 0L);
+ case 216:
+ return ((jjbitVec37[i2] & l2) != 0L);
+ case 250:
+ return ((jjbitVec38[i2] & l2) != 0L);
+ case 251:
+ return ((jjbitVec39[i2] & l2) != 0L);
+ case 253:
+ return ((jjbitVec40[i2] & l2) != 0L);
+ case 254:
+ return ((jjbitVec41[i2] & l2) != 0L);
+ case 255:
+ return ((jjbitVec42[i2] & l2) != 0L);
+ default :
+ if ((jjbitVec3[i1] & l1) != 0L)
+ return true;
+ return false;
+ }
+}
+private static final boolean jjCanMove_2(int hiByte, int i1, int i2, long l1, long l2)
+{
+ switch(hiByte)
+ {
+ case 0:
+ return ((jjbitVec43[i2] & l2) != 0L);
+ case 2:
+ return ((jjbitVec5[i2] & l2) != 0L);
+ case 3:
+ return ((jjbitVec44[i2] & l2) != 0L);
+ case 4:
+ return ((jjbitVec45[i2] & l2) != 0L);
+ case 5:
+ return ((jjbitVec46[i2] & l2) != 0L);
+ case 6:
+ return ((jjbitVec47[i2] & l2) != 0L);
+ case 7:
+ return ((jjbitVec48[i2] & l2) != 0L);
+ case 9:
+ return ((jjbitVec49[i2] & l2) != 0L);
+ case 10:
+ return ((jjbitVec50[i2] & l2) != 0L);
+ case 11:
+ return ((jjbitVec51[i2] & l2) != 0L);
+ case 12:
+ return ((jjbitVec52[i2] & l2) != 0L);
+ case 13:
+ return ((jjbitVec53[i2] & l2) != 0L);
+ case 14:
+ return ((jjbitVec54[i2] & l2) != 0L);
+ case 15:
+ return ((jjbitVec55[i2] & l2) != 0L);
+ case 16:
+ return ((jjbitVec56[i2] & l2) != 0L);
+ case 17:
+ return ((jjbitVec19[i2] & l2) != 0L);
+ case 18:
+ return ((jjbitVec20[i2] & l2) != 0L);
+ case 19:
+ return ((jjbitVec57[i2] & l2) != 0L);
+ case 20:
+ return ((jjbitVec0[i2] & l2) != 0L);
+ case 22:
+ return ((jjbitVec22[i2] & l2) != 0L);
+ case 23:
+ return ((jjbitVec58[i2] & l2) != 0L);
+ case 24:
+ return ((jjbitVec59[i2] & l2) != 0L);
+ case 25:
+ return ((jjbitVec60[i2] & l2) != 0L);
+ case 29:
+ return ((jjbitVec26[i2] & l2) != 0L);
+ case 30:
+ return ((jjbitVec27[i2] & l2) != 0L);
+ case 31:
+ return ((jjbitVec28[i2] & l2) != 0L);
+ case 32:
+ return ((jjbitVec61[i2] & l2) != 0L);
+ case 33:
+ return ((jjbitVec30[i2] & l2) != 0L);
+ case 48:
+ return ((jjbitVec62[i2] & l2) != 0L);
+ case 49:
+ return ((jjbitVec32[i2] & l2) != 0L);
+ case 77:
+ return ((jjbitVec33[i2] & l2) != 0L);
+ case 159:
+ return ((jjbitVec34[i2] & l2) != 0L);
+ case 164:
+ return ((jjbitVec35[i2] & l2) != 0L);
+ case 215:
+ return ((jjbitVec36[i2] & l2) != 0L);
+ case 216:
+ return ((jjbitVec63[i2] & l2) != 0L);
+ case 220:
+ return ((jjbitVec64[i2] & l2) != 0L);
+ case 221:
+ return ((jjbitVec65[i2] & l2) != 0L);
+ case 250:
+ return ((jjbitVec38[i2] & l2) != 0L);
+ case 251:
+ return ((jjbitVec66[i2] & l2) != 0L);
+ case 253:
+ return ((jjbitVec40[i2] & l2) != 0L);
+ case 254:
+ return ((jjbitVec67[i2] & l2) != 0L);
+ case 255:
+ return ((jjbitVec68[i2] & l2) != 0L);
+ default :
+ if ((jjbitVec3[i1] & l1) != 0L)
+ return true;
+ return false;
+ }
+}
+
+/** Token literal values. */
+public static final String[] jjstrLiteralImages = {
+"", null, null, null, null, null, null, null, null, null, null, null,
+"\141\142\163\164\162\141\143\164", "\141\163\163\145\162\164", "\142\157\157\154\145\141\156",
+"\142\162\145\141\153", "\142\171\164\145", "\143\141\163\145", "\143\141\164\143\150",
+"\143\150\141\162", "\143\154\141\163\163", "\143\157\156\163\164",
+"\143\157\156\164\151\156\165\145", "\144\145\146\141\165\154\164", "\144\157", "\144\157\165\142\154\145",
+"\145\154\163\145", "\145\156\165\155", "\145\170\164\145\156\144\163", "\146\141\154\163\145",
+"\146\151\156\141\154", "\146\151\156\141\154\154\171", "\146\154\157\141\164", "\146\157\162",
+"\147\157\164\157", "\151\146", "\151\155\160\154\145\155\145\156\164\163",
+"\151\155\160\157\162\164", "\151\156\163\164\141\156\143\145\157\146", "\151\156\164",
+"\151\156\164\145\162\146\141\143\145", "\154\157\156\147", "\156\141\164\151\166\145", "\156\145\167",
+"\156\165\154\154", "\160\141\143\153\141\147\145", "\160\162\151\166\141\164\145",
+"\160\162\157\164\145\143\164\145\144", "\160\165\142\154\151\143", "\162\145\164\165\162\156",
+"\163\150\157\162\164", "\163\164\141\164\151\143", "\163\164\162\151\143\164\146\160",
+"\163\165\160\145\162", "\163\167\151\164\143\150",
+"\163\171\156\143\150\162\157\156\151\172\145\144", "\164\150\151\163", "\164\150\162\157\167", "\164\150\162\157\167\163",
+"\164\162\141\156\163\151\145\156\164", "\164\162\165\145", "\164\162\171", "\166\157\151\144",
+"\166\157\154\141\164\151\154\145", "\167\150\151\154\145", null, null, null, null, null, null, null, null, null,
+null, null, null, null, null, null, "\50", "\51", "\173", "\175", "\133", "\135",
+"\73", "\54", "\56", "\100", "\75", "\74", "\41", "\176", "\77", "\72", "\75\75",
+"\74\75", "\76\75", "\41\75", "\174\174", "\46\46", "\53\53", "\55\55", "\53", "\55",
+"\52", "\57", "\46", "\174", "\136", "\45", "\74\74", "\53\75", "\55\75", "\52\75",
+"\57\75", "\46\75", "\174\75", "\136\75", "\45\75", "\74\74\75", "\76\76\75",
+"\76\76\76\75", "\56\56\56", "\76\76\76", "\76\76", "\76", "\32", };
+
+/** Lexer state names. */
+public static final String[] lexStateNames = {
+ "DEFAULT",
+ "IN_JAVA_DOC_COMMENT",
+ "IN_MULTI_LINE_COMMENT",
+};
+
+/** Lex State array. */
+public static final int[] jjnewLexState = {
+ -1, -1, -1, -1, -1, -1, -1, 1, 2, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1,
+};
+static final long[] jjtoToken = {
+ 0xfffffffffffff001L, 0xffffffffffff3847L, 0x1L,
+};
+static final long[] jjtoSkip = {
+ 0x67eL, 0x0L, 0x0L,
+};
+static final long[] jjtoSpecial = {
+ 0x640L, 0x0L, 0x0L,
+};
+static final long[] jjtoMore = {
+ 0x980L, 0x0L, 0x0L,
+};
+protected JavaCharStream input_stream;
+private final int[] jjrounds = new int[86];
+private final int[] jjstateSet = new int[172];
+private final StringBuilder jjimage = new StringBuilder();
+private StringBuilder image = jjimage;
+private int jjimageLen;
+private int lengthOfMatch;
+protected char curChar;
+/** Constructor. */
+public ASTParserTokenManager(JavaCharStream stream){
+ if (JavaCharStream.staticFlag)
+ throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+ input_stream = stream;
+}
+
+/** Constructor. */
+public ASTParserTokenManager(JavaCharStream stream, int lexState){
+ this(stream);
+ SwitchTo(lexState);
+}
+
+/** Reinitialise parser. */
+public void ReInit(JavaCharStream stream)
+{
+ jjmatchedPos = jjnewStateCnt = 0;
+ curLexState = defaultLexState;
+ input_stream = stream;
+ ReInitRounds();
+}
+private void ReInitRounds()
+{
+ int i;
+ jjround = 0x80000001;
+ for (i = 86; i-- > 0;)
+ jjrounds[i] = 0x80000000;
+}
+
+/** Reinitialise parser. */
+public void ReInit(JavaCharStream stream, int lexState)
+{
+ ReInit(stream);
+ SwitchTo(lexState);
+}
+
+/** Switch to specified lex state. */
+public void SwitchTo(int lexState)
+{
+ if (lexState >= 3 || lexState < 0)
+ throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+ else
+ curLexState = lexState;
+}
+
+protected Token jjFillToken()
+{
+ final Token t;
+ final String curTokenImage;
+ final int beginLine;
+ final int endLine;
+ final int beginColumn;
+ final int endColumn;
+ String im = jjstrLiteralImages[jjmatchedKind];
+ curTokenImage = (im == null) ? input_stream.GetImage() : im;
+ beginLine = input_stream.getBeginLine();
+ beginColumn = input_stream.getBeginColumn();
+ endLine = input_stream.getEndLine();
+ endColumn = input_stream.getEndColumn();
+ t = ASTParser.GTToken.newToken(jjmatchedKind, curTokenImage);
+
+ t.beginLine = beginLine;
+ t.endLine = endLine;
+ t.beginColumn = beginColumn;
+ t.endColumn = endColumn;
+
+ return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+/** Get the next Token. */
+public Token getNextToken()
+{
+ Token specialToken = null;
+ Token matchedToken;
+ int curPos = 0;
+
+ EOFLoop :
+ for (;;)
+ {
+ try
+ {
+ curChar = input_stream.BeginToken();
+ }
+ catch(java.io.IOException e)
+ {
+ jjmatchedKind = 0;
+ matchedToken = jjFillToken();
+ matchedToken.specialToken = specialToken;
+ CommonTokenAction(matchedToken);
+ return matchedToken;
+ }
+ image = jjimage;
+ image.setLength(0);
+ jjimageLen = 0;
+
+ for (;;)
+ {
+ switch(curLexState)
+ {
+ case 0:
+ try { input_stream.backup(0);
+ while (curChar <= 32 && (0x100003600L & (1L << curChar)) != 0L)
+ curChar = input_stream.BeginToken();
+ }
+ catch (java.io.IOException e1) { continue EOFLoop; }
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_0();
+ break;
+ case 1:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_1();
+ if (jjmatchedPos == 0 && jjmatchedKind > 11)
+ {
+ jjmatchedKind = 11;
+ }
+ break;
+ case 2:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_2();
+ if (jjmatchedPos == 0 && jjmatchedKind > 11)
+ {
+ jjmatchedKind = 11;
+ }
+ break;
+ }
+ if (jjmatchedKind != 0x7fffffff)
+ {
+ if (jjmatchedPos + 1 < curPos)
+ input_stream.backup(curPos - jjmatchedPos - 1);
+ if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+ {
+ matchedToken = jjFillToken();
+ matchedToken.specialToken = specialToken;
+ TokenLexicalActions(matchedToken);
+ if (jjnewLexState[jjmatchedKind] != -1)
+ curLexState = jjnewLexState[jjmatchedKind];
+ CommonTokenAction(matchedToken);
+ return matchedToken;
+ }
+ else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+ {
+ if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+ {
+ matchedToken = jjFillToken();
+ if (specialToken == null)
+ specialToken = matchedToken;
+ else
+ {
+ matchedToken.specialToken = specialToken;
+ specialToken = (specialToken.next = matchedToken);
+ }
+ SkipLexicalActions(matchedToken);
+ }
+ else
+ SkipLexicalActions(null);
+ if (jjnewLexState[jjmatchedKind] != -1)
+ curLexState = jjnewLexState[jjmatchedKind];
+ continue EOFLoop;
+ }
+ MoreLexicalActions();
+ if (jjnewLexState[jjmatchedKind] != -1)
+ curLexState = jjnewLexState[jjmatchedKind];
+ curPos = 0;
+ jjmatchedKind = 0x7fffffff;
+ try {
+ curChar = input_stream.readChar();
+ continue;
+ }
+ catch (java.io.IOException e1) { }
+ }
+ int error_line = input_stream.getEndLine();
+ int error_column = input_stream.getEndColumn();
+ String error_after = null;
+ boolean EOFSeen = false;
+ try { input_stream.readChar(); input_stream.backup(1); }
+ catch (java.io.IOException e1) {
+ EOFSeen = true;
+ error_after = curPos <= 1 ? "" : input_stream.GetImage();
+ if (curChar == '\n' || curChar == '\r') {
+ error_line++;
+ error_column = 0;
+ }
+ else
+ error_column++;
+ }
+ if (!EOFSeen) {
+ input_stream.backup(1);
+ error_after = curPos <= 1 ? "" : input_stream.GetImage();
+ }
+ throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+ }
+ }
+}
+
+void SkipLexicalActions(Token matchedToken)
+{
+ switch(jjmatchedKind)
+ {
+ default :
+ break;
+ }
+}
+void MoreLexicalActions()
+{
+ jjimageLen += (lengthOfMatch = jjmatchedPos + 1);
+ switch(jjmatchedKind)
+ {
+ case 7 :
+ image.append(input_stream.GetSuffix(jjimageLen));
+ jjimageLen = 0;
+ input_stream.backup(1);
+ break;
+ default :
+ break;
+ }
+}
+void TokenLexicalActions(Token matchedToken)
+{
+ switch(jjmatchedKind)
+ {
+ case 125 :
+ image.append(jjstrLiteralImages[125]);
+ lengthOfMatch = jjstrLiteralImages[125].length();
+ matchedToken.kind = GT;
+ ((ASTParser.GTToken)matchedToken).realKind = RUNSIGNEDSHIFT;
+ input_stream.backup(2);
+ break;
+ case 126 :
+ image.append(jjstrLiteralImages[126]);
+ lengthOfMatch = jjstrLiteralImages[126].length();
+ matchedToken.kind = GT;
+ ((ASTParser.GTToken)matchedToken).realKind = RSIGNEDSHIFT;
+ input_stream.backup(1);
+ break;
+ default :
+ break;
+ }
+}
+private void jjCheckNAdd(int state)
+{
+ if (jjrounds[state] != jjround)
+ {
+ jjstateSet[jjnewStateCnt++] = state;
+ jjrounds[state] = jjround;
+ }
+}
+private void jjAddStates(int start, int end)
+{
+ do {
+ jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+ } while (start++ != end);
+}
+private void jjCheckNAddTwoStates(int state1, int state2)
+{
+ jjCheckNAdd(state1);
+ jjCheckNAdd(state2);
+}
+
+private void jjCheckNAddStates(int start, int end)
+{
+ do {
+ jjCheckNAdd(jjnextStates[start]);
+ } while (start++ != end);
+}
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/JavaCharStream.java b/components/htmlfive/java/javaparser/src/japa/parser/JavaCharStream.java
new file mode 100644
index 000000000..b0499baed
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/JavaCharStream.java
@@ -0,0 +1,634 @@
+/* Generated By:JavaCC: Do not edit this line. JavaCharStream.java Version 4.1 */
+/* JavaCCOptions:STATIC=false */
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+package japa.parser;
+
+/**
+ * An implementation of interface CharStream, where the stream is assumed to
+ * contain only ASCII characters (with java-like unicode escape processing).
+ */
+
+public class JavaCharStream
+{
+/** Whether parser is static. */
+ public static final boolean staticFlag = false;
+ static final int hexval(char c) throws java.io.IOException {
+ switch(c)
+ {
+ case '0' :
+ return 0;
+ case '1' :
+ return 1;
+ case '2' :
+ return 2;
+ case '3' :
+ return 3;
+ case '4' :
+ return 4;
+ case '5' :
+ return 5;
+ case '6' :
+ return 6;
+ case '7' :
+ return 7;
+ case '8' :
+ return 8;
+ case '9' :
+ return 9;
+
+ case 'a' :
+ case 'A' :
+ return 10;
+ case 'b' :
+ case 'B' :
+ return 11;
+ case 'c' :
+ case 'C' :
+ return 12;
+ case 'd' :
+ case 'D' :
+ return 13;
+ case 'e' :
+ case 'E' :
+ return 14;
+ case 'f' :
+ case 'F' :
+ return 15;
+ }
+
+ throw new java.io.IOException(); // Should never come here
+ }
+
+/** Position in buffer. */
+ public int bufpos = -1;
+ int bufsize;
+ int available;
+ int tokenBegin;
+ protected int bufline[];
+ protected int bufcolumn[];
+
+ protected int column = 0;
+ protected int line = 1;
+
+ protected boolean prevCharIsCR = false;
+ protected boolean prevCharIsLF = false;
+
+ protected java.io.Reader inputStream;
+
+ protected char[] nextCharBuf;
+ protected char[] buffer;
+ protected int maxNextCharInd = 0;
+ protected int nextCharInd = -1;
+ protected int inBuf = 0;
+ protected int tabSize = 8;
+
+ protected void setTabSize(int i) { tabSize = i; }
+ protected int getTabSize(int i) { return tabSize; }
+
+ protected void ExpandBuff(boolean wrapAround)
+ {
+ char[] newbuffer = new char[bufsize + 2048];
+ int newbufline[] = new int[bufsize + 2048];
+ int newbufcolumn[] = new int[bufsize + 2048];
+
+ try
+ {
+ if (wrapAround)
+ {
+ System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+ System.arraycopy(buffer, 0, newbuffer,
+ bufsize - tokenBegin, bufpos);
+ buffer = newbuffer;
+
+ System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+ System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
+ bufline = newbufline;
+
+ System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+ System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
+ bufcolumn = newbufcolumn;
+
+ bufpos += (bufsize - tokenBegin);
+ }
+ else
+ {
+ System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+ buffer = newbuffer;
+
+ System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+ bufline = newbufline;
+
+ System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+ bufcolumn = newbufcolumn;
+
+ bufpos -= tokenBegin;
+ }
+ }
+ catch (Throwable t)
+ {
+ throw new Error(t.getMessage());
+ }
+
+ available = (bufsize += 2048);
+ tokenBegin = 0;
+ }
+
+ protected void FillBuff() throws java.io.IOException
+ {
+ int i;
+ if (maxNextCharInd == 4096)
+ maxNextCharInd = nextCharInd = 0;
+
+ try {
+ if ((i = inputStream.read(nextCharBuf, maxNextCharInd,
+ 4096 - maxNextCharInd)) == -1)
+ {
+ inputStream.close();
+ throw new java.io.IOException();
+ }
+ else
+ maxNextCharInd += i;
+ return;
+ }
+ catch(java.io.IOException e) {
+ if (bufpos != 0)
+ {
+ --bufpos;
+ backup(0);
+ }
+ else
+ {
+ bufline[bufpos] = line;
+ bufcolumn[bufpos] = column;
+ }
+ throw e;
+ }
+ }
+
+ protected char ReadByte() throws java.io.IOException
+ {
+ if (++nextCharInd >= maxNextCharInd)
+ FillBuff();
+
+ return nextCharBuf[nextCharInd];
+ }
+
+/** @return starting character for token. */
+ public char BeginToken() throws java.io.IOException
+ {
+ if (inBuf > 0)
+ {
+ --inBuf;
+
+ if (++bufpos == bufsize)
+ bufpos = 0;
+
+ tokenBegin = bufpos;
+ return buffer[bufpos];
+ }
+
+ tokenBegin = 0;
+ bufpos = -1;
+
+ return readChar();
+ }
+
+ protected void AdjustBuffSize()
+ {
+ if (available == bufsize)
+ {
+ if (tokenBegin > 2048)
+ {
+ bufpos = 0;
+ available = tokenBegin;
+ }
+ else
+ ExpandBuff(false);
+ }
+ else if (available > tokenBegin)
+ available = bufsize;
+ else if ((tokenBegin - available) < 2048)
+ ExpandBuff(true);
+ else
+ available = tokenBegin;
+ }
+
+ protected void UpdateLineColumn(char c)
+ {
+ column++;
+
+ if (prevCharIsLF)
+ {
+ prevCharIsLF = false;
+ line += (column = 1);
+ }
+ else if (prevCharIsCR)
+ {
+ prevCharIsCR = false;
+ if (c == '\n')
+ {
+ prevCharIsLF = true;
+ }
+ else
+ line += (column = 1);
+ }
+
+ switch (c)
+ {
+ case '\r' :
+ prevCharIsCR = true;
+ break;
+ case '\n' :
+ prevCharIsLF = true;
+ break;
+ case '\t' :
+ column--;
+ column += (tabSize - (column % tabSize));
+ break;
+ default :
+ break;
+ }
+
+ bufline[bufpos] = line;
+ bufcolumn[bufpos] = column;
+ }
+
+/** Read a character. */
+ public char readChar() throws java.io.IOException
+ {
+ if (inBuf > 0)
+ {
+ --inBuf;
+
+ if (++bufpos == bufsize)
+ bufpos = 0;
+
+ return buffer[bufpos];
+ }
+
+ char c;
+
+ if (++bufpos == available)
+ AdjustBuffSize();
+
+ if ((buffer[bufpos] = c = ReadByte()) == '\\')
+ {
+ UpdateLineColumn(c);
+
+ int backSlashCnt = 1;
+
+ for (;;) // Read all the backslashes
+ {
+ if (++bufpos == available)
+ AdjustBuffSize();
+
+ try
+ {
+ if ((buffer[bufpos] = c = ReadByte()) != '\\')
+ {
+ UpdateLineColumn(c);
+ // found a non-backslash char.
+ if ((c == 'u') && ((backSlashCnt & 1) == 1))
+ {
+ if (--bufpos < 0)
+ bufpos = bufsize - 1;
+
+ break;
+ }
+
+ backup(backSlashCnt);
+ return '\\';
+ }
+ }
+ catch(java.io.IOException e)
+ {
+ if (backSlashCnt > 1)
+ backup(backSlashCnt-1);
+
+ return '\\';
+ }
+
+ UpdateLineColumn(c);
+ backSlashCnt++;
+ }
+
+ // Here, we have seen an odd number of backslash's followed by a 'u'
+ try
+ {
+ while ((c = ReadByte()) == 'u')
+ ++column;
+
+ buffer[bufpos] = c = (char)(hexval(c) << 12 |
+ hexval(ReadByte()) << 8 |
+ hexval(ReadByte()) << 4 |
+ hexval(ReadByte()));
+
+ column += 4;
+ }
+ catch(java.io.IOException e)
+ {
+ throw new Error("Invalid escape character at line " + line +
+ " column " + column + ".");
+ }
+
+ if (backSlashCnt == 1)
+ return c;
+ else
+ {
+ backup(backSlashCnt - 1);
+ return '\\';
+ }
+ }
+ else
+ {
+ UpdateLineColumn(c);
+ return c;
+ }
+ }
+
+ @Deprecated
+ /**
+ * @deprecated
+ * @see #getEndColumn
+ */
+ public int getColumn() {
+ return bufcolumn[bufpos];
+ }
+
+ @Deprecated
+ /**
+ * @deprecated
+ * @see #getEndLine
+ */
+ public int getLine() {
+ return bufline[bufpos];
+ }
+
+/** Get end column. */
+ public int getEndColumn() {
+ return bufcolumn[bufpos];
+ }
+
+/** Get end line. */
+ public int getEndLine() {
+ return bufline[bufpos];
+ }
+
+/** @return column of token start */
+ public int getBeginColumn() {
+ return bufcolumn[tokenBegin];
+ }
+
+/** @return line number of token start */
+ public int getBeginLine() {
+ return bufline[tokenBegin];
+ }
+
+/** Retreat. */
+ public void backup(int amount) {
+
+ inBuf += amount;
+ if ((bufpos -= amount) < 0)
+ bufpos += bufsize;
+ }
+
+/** Constructor. */
+ public JavaCharStream(java.io.Reader dstream,
+ int startline, int startcolumn, int buffersize)
+ {
+ inputStream = dstream;
+ line = startline;
+ column = startcolumn - 1;
+
+ available = bufsize = buffersize;
+ buffer = new char[buffersize];
+ bufline = new int[buffersize];
+ bufcolumn = new int[buffersize];
+ nextCharBuf = new char[4096];
+ }
+
+/** Constructor. */
+ public JavaCharStream(java.io.Reader dstream,
+ int startline, int startcolumn)
+ {
+ this(dstream, startline, startcolumn, 4096);
+ }
+
+/** Constructor. */
+ public JavaCharStream(java.io.Reader dstream)
+ {
+ this(dstream, 1, 1, 4096);
+ }
+/** Reinitialise. */
+ public void ReInit(java.io.Reader dstream,
+ int startline, int startcolumn, int buffersize)
+ {
+ inputStream = dstream;
+ line = startline;
+ column = startcolumn - 1;
+
+ if (buffer == null || buffersize != buffer.length)
+ {
+ available = bufsize = buffersize;
+ buffer = new char[buffersize];
+ bufline = new int[buffersize];
+ bufcolumn = new int[buffersize];
+ nextCharBuf = new char[4096];
+ }
+ prevCharIsLF = prevCharIsCR = false;
+ tokenBegin = inBuf = maxNextCharInd = 0;
+ nextCharInd = bufpos = -1;
+ }
+
+/** Reinitialise. */
+ public void ReInit(java.io.Reader dstream,
+ int startline, int startcolumn)
+ {
+ ReInit(dstream, startline, startcolumn, 4096);
+ }
+
+/** Reinitialise. */
+ public void ReInit(java.io.Reader dstream)
+ {
+ ReInit(dstream, 1, 1, 4096);
+ }
+/** Constructor. */
+ public JavaCharStream(java.io.InputStream dstream, String encoding, int startline,
+ int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+ {
+ this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+ }
+
+/** Constructor. */
+ public JavaCharStream(java.io.InputStream dstream, int startline,
+ int startcolumn, int buffersize)
+ {
+ this(new java.io.InputStreamReader(dstream), startline, startcolumn, 4096);
+ }
+
+/** Constructor. */
+ public JavaCharStream(java.io.InputStream dstream, String encoding, int startline,
+ int startcolumn) throws java.io.UnsupportedEncodingException
+ {
+ this(dstream, encoding, startline, startcolumn, 4096);
+ }
+
+/** Constructor. */
+ public JavaCharStream(java.io.InputStream dstream, int startline,
+ int startcolumn)
+ {
+ this(dstream, startline, startcolumn, 4096);
+ }
+
+/** Constructor. */
+ public JavaCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+ {
+ this(dstream, encoding, 1, 1, 4096);
+ }
+
+/** Constructor. */
+ public JavaCharStream(java.io.InputStream dstream)
+ {
+ this(dstream, 1, 1, 4096);
+ }
+
+/** Reinitialise. */
+ public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+ int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+ {
+ ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+ }
+
+/** Reinitialise. */
+ public void ReInit(java.io.InputStream dstream, int startline,
+ int startcolumn, int buffersize)
+ {
+ ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+ }
+/** Reinitialise. */
+ public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+ int startcolumn) throws java.io.UnsupportedEncodingException
+ {
+ ReInit(dstream, encoding, startline, startcolumn, 4096);
+ }
+/** Reinitialise. */
+ public void ReInit(java.io.InputStream dstream, int startline,
+ int startcolumn)
+ {
+ ReInit(dstream, startline, startcolumn, 4096);
+ }
+/** Reinitialise. */
+ public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+ {
+ ReInit(dstream, encoding, 1, 1, 4096);
+ }
+
+/** Reinitialise. */
+ public void ReInit(java.io.InputStream dstream)
+ {
+ ReInit(dstream, 1, 1, 4096);
+ }
+
+ /** @return token image as String */
+ public String GetImage()
+ {
+ if (bufpos >= tokenBegin)
+ return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
+ else
+ return new String(buffer, tokenBegin, bufsize - tokenBegin) +
+ new String(buffer, 0, bufpos + 1);
+ }
+
+ /** @return suffix */
+ public char[] GetSuffix(int len)
+ {
+ char[] ret = new char[len];
+
+ if ((bufpos + 1) >= len)
+ System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+ else
+ {
+ System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
+ len - bufpos - 1);
+ System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
+ }
+
+ return ret;
+ }
+
+ /** Set buffers back to null when finished. */
+ public void Done()
+ {
+ nextCharBuf = null;
+ buffer = null;
+ bufline = null;
+ bufcolumn = null;
+ }
+
+ /**
+ * Method to adjust line and column numbers for the start of a token.
+ */
+ public void adjustBeginLineColumn(int newLine, int newCol)
+ {
+ int start = tokenBegin;
+ int len;
+
+ if (bufpos >= tokenBegin)
+ {
+ len = bufpos - tokenBegin + inBuf + 1;
+ }
+ else
+ {
+ len = bufsize - tokenBegin + bufpos + 1 + inBuf;
+ }
+
+ int i = 0, j = 0, k = 0;
+ int nextColDiff = 0, columnDiff = 0;
+
+ while (i < len &&
+ bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
+ {
+ bufline[j] = newLine;
+ nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+ bufcolumn[j] = newCol + columnDiff;
+ columnDiff = nextColDiff;
+ i++;
+ }
+
+ if (i < len)
+ {
+ bufline[j] = newLine++;
+ bufcolumn[j] = newCol + columnDiff;
+
+ while (i++ < len)
+ {
+ if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+ bufline[j] = newLine++;
+ else
+ bufline[j] = newLine;
+ }
+ }
+
+ line = bufline[j];
+ column = bufcolumn[j];
+ }
+
+}
+/* JavaCC - OriginalChecksum=46aebc46574be349188fc26719761bcb (do not edit this line) */
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/JavaParser.java b/components/htmlfive/java/javaparser/src/japa/parser/JavaParser.java
new file mode 100644
index 000000000..f1a259e3c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/JavaParser.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser;
+
+import japa.parser.ast.CompilationUnit;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * <p>This class was generated automatically by javacc, do not edit.</p>
+ * <p>Parse Java 1.5 source code and creates Abstract Syntax Tree classes.</p>
+ * <p><b>Note:</b> To use this parser asynchronously, disable de parser cache
+ * by calling the method {@link setCacheParser} with <code>false</code>
+ * as argument.</p>
+ *
+ * @author Júlio Vilmar Gesser
+ */
+public final class JavaParser {
+
+ private static ASTParser parser;
+
+ private static boolean cacheParser = true;
+
+ private JavaParser() {
+ // hide the constructor
+ }
+
+ /**
+ * Changes the way that the parser acts when starts to parse. If the
+ * parser cache is enabled, only one insance of this object will be
+ * used in every call to parse methods.
+ * If this parser is intend to be used asynchonously, the cache must
+ * be disabled setting this flag to <code>false</code>.
+ * By default, the cache is enabled.
+ * @param value <code>false</code> to disable the parser instance cache.
+ */
+ public static void setCacheParser(boolean value) {
+ cacheParser = value;
+ if (!value) {
+ parser = null;
+ }
+ }
+
+ /**
+ * Parses the Java code contained in the {@link InputStream} and returns
+ * a {@link CompilationUnit} that represents it.
+ * @param in {@link InputStream} containing Java source code
+ * @param encoding encoding of the source code
+ * @return CompilationUnit representing the Java source code
+ * @throws ParseException if the source code has parser errors
+ */
+ public static CompilationUnit parse(InputStream in, String encoding) throws ParseException {
+ if (cacheParser) {
+ if (parser == null) {
+ parser = new ASTParser(in, encoding);
+ } else {
+ parser.reset(in, encoding);
+ }
+ return parser.CompilationUnit();
+ }
+ return new ASTParser(in, encoding).CompilationUnit();
+ }
+
+ /**
+ * Parses the Java code contained in the {@link InputStream} and returns
+ * a {@link CompilationUnit} that represents it.
+ * @param in {@link InputStream} containing Java source code
+ * @return CompilationUnit representing the Java source code
+ * @throws ParseException if the source code has parser errors
+ */
+ public static CompilationUnit parse(InputStream in) throws ParseException {
+ return parse(in, null);
+ }
+
+ /**
+ * Parses the Java code contained in a {@link File} and returns
+ * a {@link CompilationUnit} that represents it.
+ * @param file {@link File} containing Java source code
+ * @param encoding encoding of the source code
+ * @return CompilationUnit representing the Java source code
+ * @throws ParseException if the source code has parser errors
+ * @throws IOException
+ */
+ public static CompilationUnit parse(File file, String encoding) throws ParseException, IOException {
+ FileInputStream in = new FileInputStream(file);
+ try {
+ return parse(in, encoding);
+ } finally {
+ in.close();
+ }
+ }
+
+ /**
+ * Parses the Java code contained in a {@link File} and returns
+ * a {@link CompilationUnit} that represents it.
+ * @param file {@link File} containing Java source code
+ * @return CompilationUnit representing the Java source code
+ * @throws ParseException if the source code has parser errors
+ * @throws IOException
+ */
+ public static CompilationUnit parse(File file) throws ParseException, IOException {
+ return parse(file, null);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ParseException.java b/components/htmlfive/java/javaparser/src/japa/parser/ParseException.java
new file mode 100644
index 000000000..958ce191d
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ParseException.java
@@ -0,0 +1,216 @@
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 4.1 */
+/* JavaCCOptions:KEEP_LINE_COL=null */
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+package japa.parser;
+
+/**
+ * This exception is thrown when parse errors are encountered.
+ * You can explicitly create objects of this exception type by
+ * calling the method generateParseException in the generated
+ * parser.
+ *
+ * You can modify this class to customize your error reporting
+ * mechanisms so long as you retain the public fields.
+ */
+public class ParseException extends Exception {
+
+ /**
+ * This constructor is used by the method "generateParseException"
+ * in the generated parser. Calling this constructor generates
+ * a new object of this type with the fields "currentToken",
+ * "expectedTokenSequences", and "tokenImage" set. The boolean
+ * flag "specialConstructor" is also set to true to indicate that
+ * this constructor was used to create this object.
+ * This constructor calls its super class with the empty string
+ * to force the "toString" method of parent class "Throwable" to
+ * print the error message in the form:
+ * ParseException: <result of getMessage>
+ */
+ public ParseException(Token currentTokenVal,
+ int[][] expectedTokenSequencesVal,
+ String[] tokenImageVal
+ )
+ {
+ super("");
+ specialConstructor = true;
+ currentToken = currentTokenVal;
+ expectedTokenSequences = expectedTokenSequencesVal;
+ tokenImage = tokenImageVal;
+ }
+
+ /**
+ * The following constructors are for use by you for whatever
+ * purpose you can think of. Constructing the exception in this
+ * manner makes the exception behave in the normal way - i.e., as
+ * documented in the class "Throwable". The fields "errorToken",
+ * "expectedTokenSequences", and "tokenImage" do not contain
+ * relevant information. The JavaCC generated code does not use
+ * these constructors.
+ */
+
+ public ParseException() {
+ super();
+ specialConstructor = false;
+ }
+
+ /** Constructor with message. */
+ public ParseException(String message) {
+ super(message);
+ specialConstructor = false;
+ }
+
+ /**
+ * This variable determines which constructor was used to create
+ * this object and thereby affects the semantics of the
+ * "getMessage" method (see below).
+ */
+ protected boolean specialConstructor;
+
+ /**
+ * This is the last token that has been consumed successfully. If
+ * this object has been created due to a parse error, the token
+ * followng this token will (therefore) be the first error token.
+ */
+ public Token currentToken;
+
+ /**
+ * Each entry in this array is an array of integers. Each array
+ * of integers represents a sequence of tokens (by their ordinal
+ * values) that is expected at this point of the parse.
+ */
+ public int[][] expectedTokenSequences;
+
+ /**
+ * This is a reference to the "tokenImage" array of the generated
+ * parser within which the parse error occurred. This array is
+ * defined in the generated ...Constants interface.
+ */
+ public String[] tokenImage;
+
+ /**
+ * This method has the standard behavior when this object has been
+ * created using the standard constructors. Otherwise, it uses
+ * "currentToken" and "expectedTokenSequences" to generate a parse
+ * error message and returns it. If this object has been created
+ * due to a parse error, and you do not catch it (it gets thrown
+ * from the parser), then this method is called during the printing
+ * of the final stack trace, and hence the correct error message
+ * gets displayed.
+ */
+ public String getMessage() {
+ if (!specialConstructor) {
+ return super.getMessage();
+ }
+ StringBuffer expected = new StringBuffer();
+ int maxSize = 0;
+ for (int i = 0; i < expectedTokenSequences.length; i++) {
+ if (maxSize < expectedTokenSequences[i].length) {
+ maxSize = expectedTokenSequences[i].length;
+ }
+ for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+ expected.append(tokenImage[expectedTokenSequences[i][j]]).append(' ');
+ }
+ if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
+ expected.append("...");
+ }
+ expected.append(eol).append(" ");
+ }
+ String retval = "Encountered \"";
+ Token tok = currentToken.next;
+ for (int i = 0; i < maxSize; i++) {
+ if (i != 0) retval += " ";
+ if (tok.kind == 0) {
+ retval += tokenImage[0];
+ break;
+ }
+ retval += " " + tokenImage[tok.kind];
+ retval += " \"";
+ retval += add_escapes(tok.image);
+ retval += " \"";
+ tok = tok.next;
+ }
+ retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn;
+ retval += "." + eol;
+ if (expectedTokenSequences.length == 1) {
+ retval += "Was expecting:" + eol + " ";
+ } else {
+ retval += "Was expecting one of:" + eol + " ";
+ }
+ retval += expected.toString();
+ return retval;
+ }
+
+ /**
+ * The end of line string for this machine.
+ */
+ protected String eol = System.getProperty("line.separator", "\n");
+
+ /**
+ * Used to convert raw characters to their escaped version
+ * when these raw version cannot be used as part of an ASCII
+ * string literal.
+ */
+ protected String add_escapes(String str) {
+ StringBuffer retval = new StringBuffer();
+ char ch;
+ for (int i = 0; i < str.length(); i++) {
+ switch (str.charAt(i))
+ {
+ case 0 :
+ continue;
+ case '\b':
+ retval.append("\\b");
+ continue;
+ case '\t':
+ retval.append("\\t");
+ continue;
+ case '\n':
+ retval.append("\\n");
+ continue;
+ case '\f':
+ retval.append("\\f");
+ continue;
+ case '\r':
+ retval.append("\\r");
+ continue;
+ case '\"':
+ retval.append("\\\"");
+ continue;
+ case '\'':
+ retval.append("\\\'");
+ continue;
+ case '\\':
+ retval.append("\\\\");
+ continue;
+ default:
+ if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+ String s = "0000" + Integer.toString(ch, 16);
+ retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+ } else {
+ retval.append(ch);
+ }
+ continue;
+ }
+ }
+ return retval.toString();
+ }
+
+}
+/* JavaCC - OriginalChecksum=1164e36971d84a0433c5f702fcb960dd (do not edit this line) */
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/Token.java b/components/htmlfive/java/javaparser/src/japa/parser/Token.java
new file mode 100644
index 000000000..34c00d0eb
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/Token.java
@@ -0,0 +1,142 @@
+/* Generated By:JavaCC: Do not edit this line. Token.java Version 4.1 */
+/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null */
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+package japa.parser;
+
+/**
+ * Describes the input token stream.
+ */
+
+public class Token {
+
+ /**
+ * An integer that describes the kind of this token. This numbering
+ * system is determined by JavaCCParser, and a table of these numbers is
+ * stored in the file ...Constants.java.
+ */
+ public int kind;
+
+ /** The line number of the first character of this Token. */
+ public int beginLine;
+ /** The column number of the first character of this Token. */
+ public int beginColumn;
+ /** The line number of the last character of this Token. */
+ public int endLine;
+ /** The column number of the last character of this Token. */
+ public int endColumn;
+
+ /**
+ * The string image of the token.
+ */
+ public String image;
+
+ /**
+ * A reference to the next regular (non-special) token from the input
+ * stream. If this is the last token from the input stream, or if the
+ * token manager has not read tokens beyond this one, this field is
+ * set to null. This is true only if this token is also a regular
+ * token. Otherwise, see below for a description of the contents of
+ * this field.
+ */
+ public Token next;
+
+ /**
+ * This field is used to access special tokens that occur prior to this
+ * token, but after the immediately preceding regular (non-special) token.
+ * If there are no such special tokens, this field is set to null.
+ * When there are more than one such special token, this field refers
+ * to the last of these special tokens, which in turn refers to the next
+ * previous special token through its specialToken field, and so on
+ * until the first special token (whose specialToken field is null).
+ * The next fields of special tokens refer to other special tokens that
+ * immediately follow it (without an intervening regular token). If there
+ * is no such token, this field is null.
+ */
+ public Token specialToken;
+
+ /**
+ * An optional attribute value of the Token.
+ * Tokens which are not used as syntactic sugar will often contain
+ * meaningful values that will be used later on by the compiler or
+ * interpreter. This attribute value is often different from the image.
+ * Any subclass of Token that actually wants to return a non-null value can
+ * override this method as appropriate.
+ */
+ public Object getValue() {
+ return null;
+ }
+
+ /**
+ * No-argument constructor
+ */
+ public Token() {}
+
+ /**
+ * Constructs a new token for the specified Image.
+ */
+ public Token(int kind)
+ {
+ this(kind, null);
+ }
+
+ /**
+ * Constructs a new token for the specified Image and Kind.
+ */
+ public Token(int kind, String image)
+ {
+ this.kind = kind;
+ this.image = image;
+ }
+
+ /**
+ * Returns the image.
+ */
+ public String toString()
+ {
+ return image;
+ }
+
+ /**
+ * Returns a new Token object, by default. However, if you want, you
+ * can create and return subclass objects based on the value of ofKind.
+ * Simply add the cases to the switch for all those special cases.
+ * For example, if you have a subclass of Token called IDToken that
+ * you want to create if ofKind is ID, simply add something like :
+ *
+ * case MyParserConstants.ID : return new IDToken(ofKind, image);
+ *
+ * to the following switch statement. Then you can cast matchedToken
+ * variable to the appropriate type and use sit in your lexical actions.
+ */
+ public static Token newToken(int ofKind, String image)
+ {
+ switch(ofKind)
+ {
+ default : return new Token(ofKind, image);
+ }
+ }
+
+ public static Token newToken(int ofKind)
+ {
+ return newToken(ofKind, null);
+ }
+
+}
+/* JavaCC - OriginalChecksum=36e116391da53a8cb5fc7d23289ae0c7 (do not edit this line) */
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/TokenMgrError.java b/components/htmlfive/java/javaparser/src/japa/parser/TokenMgrError.java
new file mode 100644
index 000000000..9a79b1252
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/TokenMgrError.java
@@ -0,0 +1,159 @@
+/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 4.1 */
+/* JavaCCOptions: */
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+package japa.parser;
+
+/** Token Manager Error. */
+@SuppressWarnings("serial")
+public class TokenMgrError extends Error
+{
+
+ /*
+ * Ordinals for various reasons why an Error of this type can be thrown.
+ */
+
+ /**
+ * Lexical error occurred.
+ */
+ static final int LEXICAL_ERROR = 0;
+
+ /**
+ * An attempt was made to create a second instance of a static token manager.
+ */
+ static final int STATIC_LEXER_ERROR = 1;
+
+ /**
+ * Tried to change to an invalid lexical state.
+ */
+ static final int INVALID_LEXICAL_STATE = 2;
+
+ /**
+ * Detected (and bailed out of) an infinite loop in the token manager.
+ */
+ static final int LOOP_DETECTED = 3;
+
+ /**
+ * Indicates the reason why the exception is thrown. It will have
+ * one of the above 4 values.
+ */
+ int errorCode;
+
+ /**
+ * Replaces unprintable characters by their escaped (or unicode escaped)
+ * equivalents in the given string
+ */
+ protected static final String addEscapes(String str) {
+ StringBuffer retval = new StringBuffer();
+ char ch;
+ for (int i = 0; i < str.length(); i++) {
+ switch (str.charAt(i))
+ {
+ case 0 :
+ continue;
+ case '\b':
+ retval.append("\\b");
+ continue;
+ case '\t':
+ retval.append("\\t");
+ continue;
+ case '\n':
+ retval.append("\\n");
+ continue;
+ case '\f':
+ retval.append("\\f");
+ continue;
+ case '\r':
+ retval.append("\\r");
+ continue;
+ case '\"':
+ retval.append("\\\"");
+ continue;
+ case '\'':
+ retval.append("\\\'");
+ continue;
+ case '\\':
+ retval.append("\\\\");
+ continue;
+ default:
+ if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+ String s = "0000" + Integer.toString(ch, 16);
+ retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+ } else {
+ retval.append(ch);
+ }
+ continue;
+ }
+ }
+ return retval.toString();
+ }
+
+ /**
+ * Returns a detailed message for the Error when it is thrown by the
+ * token manager to indicate a lexical error.
+ * Parameters :
+ * EOFSeen : indicates if EOF caused the lexical error
+ * curLexState : lexical state in which this error occurred
+ * errorLine : line number when the error occurred
+ * errorColumn : column number when the error occurred
+ * errorAfter : prefix that was seen before this error occurred
+ * curchar : the offending character
+ * Note: You can customize the lexical error message by modifying this method.
+ */
+ protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) {
+ return("Lexical error at line " +
+ errorLine + ", column " +
+ errorColumn + ". Encountered: " +
+ (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") +
+ "after : \"" + addEscapes(errorAfter) + "\"");
+ }
+
+ /**
+ * You can also modify the body of this method to customize your error messages.
+ * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not
+ * of end-users concern, so you can return something like :
+ *
+ * "Internal Error : Please file a bug report .... "
+ *
+ * from this method for such cases in the release version of your parser.
+ */
+ public String getMessage() {
+ return super.getMessage();
+ }
+
+ /*
+ * Constructors of various flavors follow.
+ */
+
+ /** No arg constructor. */
+ public TokenMgrError() {
+ }
+
+ /** Constructor with message and reason. */
+ public TokenMgrError(String message, int reason) {
+ super(message);
+ errorCode = reason;
+ }
+
+ /** Full Constructor. */
+ public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) {
+ this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+ }
+}
+/* JavaCC - OriginalChecksum=c0bde2b885c772c9db66b8a6b4f07329 (do not edit this line) */
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/BlockComment.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/BlockComment.java
new file mode 100644
index 000000000..1130298fb
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/BlockComment.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 23/05/2008
+ */
+package japa.parser.ast;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * <p>
+ * AST node that represent block comments.
+ * </p>
+ * Block comments can has multi lines and are delimited by "/&#42;" and
+ * "&#42;/".
+ *
+ * @author Julio Vilmar Gesser
+ */
+public final class BlockComment extends Comment {
+
+ public BlockComment() {
+ }
+
+ public BlockComment(String content) {
+ super(content);
+ }
+
+ public BlockComment(int beginLine, int beginColumn, int endLine, int endColumn, String content) {
+ super(beginLine, beginColumn, endLine, endColumn, content);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/Comment.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/Comment.java
new file mode 100644
index 000000000..c29ca5edb
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/Comment.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 23/05/2008
+ */
+package japa.parser.ast;
+
+import japa.parser.ast.body.JavadocComment;
+
+/**
+ * Abstract class for all AST nodes that represent comments.
+ *
+ * @see BlockComment
+ * @see LineComment
+ * @see JavadocComment
+ * @author Julio Vilmar Gesser
+ */
+public abstract class Comment extends Node {
+
+ private String content;
+
+ public Comment() {
+ }
+
+ public Comment(String content) {
+ this.content = content;
+ }
+
+ public Comment(int beginLine, int beginColumn, int endLine, int endColumn, String content) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.content = content;
+ }
+
+ /**
+ * Return the text of the comment.
+ *
+ * @return text of the comment
+ */
+ public final String getContent() {
+ return content;
+ }
+
+ /**
+ * Sets the text of the comment.
+ *
+ * @param content
+ * the text of the comment to set
+ */
+ public void setContent(String content) {
+ this.content = content;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/CompilationUnit.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/CompilationUnit.java
new file mode 100644
index 000000000..9f2f68604
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/CompilationUnit.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast;
+
+import japa.parser.ast.body.AnnotationDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.TypeDeclaration;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * <p>
+ * This class represents the entire compilation unit. Each java file denotes a
+ * compilation unit.
+ * </p>
+ * The CompilationUnit is constructed following the syntax:<br>
+ * <code>
+ * <table>
+ * <tr valign=baseline>
+ * <td align=right>CompilationUnit</td>
+ * <td align=center>::=</td>
+ * <td align=left>
+ * ( {@link PackageDeclaration} )?<br>
+ * ( {@link ImportDeclaration} )*<br>
+ * ( {@link TypeDeclaration} )*<br>
+ * </td>
+ * </tr>
+ * </table>
+ * </code>
+ *
+ * @author Julio Vilmar Gesser
+ */
+public final class CompilationUnit extends Node {
+
+ private PackageDeclaration pakage;
+
+ private List<ImportDeclaration> imports;
+
+ private List<TypeDeclaration> types;
+
+ private List<Comment> comments;
+
+ public CompilationUnit() {
+ }
+
+ public CompilationUnit(PackageDeclaration pakage, List<ImportDeclaration> imports, List<TypeDeclaration> types, List<Comment> comments) {
+ this.pakage = pakage;
+ this.imports = imports;
+ this.types = types;
+ this.comments = comments;
+ }
+
+ public CompilationUnit(int beginLine, int beginColumn, int endLine, int endColumn, PackageDeclaration pakage, List<ImportDeclaration> imports, List<TypeDeclaration> types, List<Comment> comments) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.pakage = pakage;
+ this.imports = imports;
+ this.types = types;
+ this.comments = comments;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ /**
+ * Return a list containing all comments declared in this compilation unit.
+ * Including javadocs, line comments and block comments of all types,
+ * inner-classes and other members.<br>
+ * If there is no comment, <code>null</code> is returned.
+ *
+ * @return list with all comments of this compilation unit or
+ * <code>null</code>
+ * @see JavadocComment
+ * @see LineComment
+ * @see BlockComment
+ */
+ public List<Comment> getComments() {
+ return comments;
+ }
+
+ /**
+ * Retrieves the list of imports declared in this compilation unit or
+ * <code>null</code> if there is no import.
+ *
+ * @return the list of imports or <code>null</code> if there is no import
+ */
+ public List<ImportDeclaration> getImports() {
+ return imports;
+ }
+
+ /**
+ * Retrieves the package declaration of this compilation unit.<br>
+ * If this compilation unit has no package declaration (default package),
+ * <code>null</code> is returned.
+ *
+ * @return the package declaration or <code>null</code>
+ */
+ public PackageDeclaration getPackage() {
+ return pakage;
+ }
+
+ /**
+ * Return the list of types declared in this compilation unit.<br>
+ * If there is no types declared, <code>null</code> is returned.
+ *
+ * @return the list of types or <code>null</code> null if there is no type
+ * @see AnnotationDeclaration
+ * @see ClassOrInterfaceDeclaration
+ * @see EmptyTypeDeclaration
+ * @see EnumDeclaration
+ */
+ public List<TypeDeclaration> getTypes() {
+ return types;
+ }
+
+ /**
+ * Sets the list of comments of this compilation unit.
+ *
+ * @param comments
+ * the list of comments
+ */
+ public void setComments(List<Comment> comments) {
+ this.comments = comments;
+ }
+
+ /**
+ * Sets the list of imports of this compilation unit. The list is initially
+ * <code>null</code>.
+ *
+ * @param imports
+ * the list of imports
+ */
+ public void setImports(List<ImportDeclaration> imports) {
+ this.imports = imports;
+ }
+
+ /**
+ * Sets or clear the package declarations of this compilation unit.
+ *
+ * @param pakage
+ * the pakage declaration to set or <code>null</code> to default
+ * package
+ */
+ public void setPackage(PackageDeclaration pakage) {
+ this.pakage = pakage;
+ }
+
+ /**
+ * Sets the list of types declared in this compilation unit.
+ *
+ * @param types
+ * the lis of types
+ */
+ public void setTypes(List<TypeDeclaration> types) {
+ this.types = types;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/ImportDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/ImportDeclaration.java
new file mode 100644
index 000000000..5e99b8e35
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/ImportDeclaration.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast;
+
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * <p>
+ * This class represents a import declaration. Imports are optional for the
+ * {@link CompilationUnit}.
+ * </p>
+ * The ImportDeclaration is constructed following the syntax:<br>
+ * <code>
+ * <table>
+ * <tr valign=baseline>
+ * <td align=right>ImportDeclaration</td>
+ * <td align=center>::=</td>
+ * <td align=left>
+ * "import" ( "static" )? {@link NameExpr} ( "." "*" )? ";"
+ * </td>
+ * </tr>
+ * </table>
+ * </code>
+ *
+ * @author Julio Vilmar Gesser
+ */
+public final class ImportDeclaration extends Node {
+
+ private NameExpr name;
+
+ private boolean static_;
+
+ private boolean asterisk;
+
+ public ImportDeclaration() {
+ }
+
+ public ImportDeclaration(NameExpr name, boolean isStatic, boolean isAsterisk) {
+ this.name = name;
+ this.static_ = isStatic;
+ this.asterisk = isAsterisk;
+ }
+
+ public ImportDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, NameExpr name, boolean isStatic, boolean isAsterisk) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.name = name;
+ this.static_ = isStatic;
+ this.asterisk = isAsterisk;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ /**
+ * Retrieves the name of the import.
+ *
+ * @return the name of the import
+ */
+ public NameExpr getName() {
+ return name;
+ }
+
+ /**
+ * Return if the import ends with "*".
+ *
+ * @return <code>true</code> if the import ends with "*", <code>false</code>
+ * otherwise
+ */
+ public boolean isAsterisk() {
+ return asterisk;
+ }
+
+ /**
+ * Return if the import is static.
+ *
+ * @return <code>true</code> if the import is static, <code>false</code>
+ * otherwise
+ */
+ public boolean isStatic() {
+ return static_;
+ }
+
+ /**
+ * Sets if this import is asterisk.
+ *
+ * @param asterisk
+ * <code>true</code> if this import is asterisk
+ */
+ public void setAsterisk(boolean asterisk) {
+ this.asterisk = asterisk;
+ }
+
+ /**
+ * Sets the name this import.
+ *
+ * @param name
+ * the name to set
+ */
+ public void setName(NameExpr name) {
+ this.name = name;
+ }
+
+ /**
+ * Sets if this import is static.
+ *
+ * @param static_
+ * <code>true</code> if this import is static
+ */
+ public void setStatic(boolean static_) {
+ this.static_ = static_;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/LineComment.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/LineComment.java
new file mode 100644
index 000000000..18edd8ea7
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/LineComment.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 23/05/2008
+ */
+package japa.parser.ast;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * <p>
+ * AST node that represent line comments.
+ * </p>
+ * Line comments are started with "//" and finish at the end of the line ("\n").
+ *
+ * @author Julio Vilmar Gesser
+ */
+public final class LineComment extends Comment {
+
+ public LineComment() {
+ }
+
+ public LineComment(String content) {
+ super(content);
+ }
+
+ public LineComment(int beginLine, int beginColumn, int endLine, int endColumn, String content) {
+ super(beginLine, beginColumn, endLine, endColumn, content);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/Node.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/Node.java
new file mode 100644
index 000000000..b91762f5b
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/Node.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast;
+
+import japa.parser.ast.visitor.DumpVisitor;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * Abstract class for all nodes of the AST.
+ *
+ * @author Julio Vilmar Gesser
+ */
+public abstract class Node {
+
+ private int beginLine;
+
+ private int beginColumn;
+
+ private int endLine;
+
+ private int endColumn;
+
+ /**
+ * This attribute can store additional information from semantic analysis.
+ */
+ private Object data;
+
+ public Node() {
+ }
+
+ public Node(int beginLine, int beginColumn, int endLine, int endColumn) {
+ this.beginLine = beginLine;
+ this.beginColumn = beginColumn;
+ this.endLine = endLine;
+ this.endColumn = endColumn;
+ }
+
+ /**
+ * Accept method for visitor support.
+ *
+ * @param <R>
+ * the type the return value of the visitor
+ * @param <A>
+ * the type the argument passed for the visitor
+ * @param v
+ * the visitor implementation
+ * @param arg
+ * any value relevant for the visitor
+ * @return the result of the visit
+ */
+ public abstract <R, A> R accept(GenericVisitor<R, A> v, A arg);
+
+ /**
+ * Accept method for visitor support.
+ *
+ * @param <A>
+ * the type the argument passed for the visitor
+ * @param v
+ * the visitor implementation
+ * @param arg
+ * any value relevant for the visitor
+ */
+ public abstract <A> void accept(VoidVisitor<A> v, A arg);
+
+ /**
+ * Return the begin column of this node.
+ *
+ * @return the begin column of this node
+ */
+ public final int getBeginColumn() {
+ return beginColumn;
+ }
+
+ /**
+ * Return the begin line of this node.
+ *
+ * @return the begin line of this node
+ */
+ public final int getBeginLine() {
+ return beginLine;
+ }
+
+ /**
+ * Use this to retrieve additional information associated to this node.
+ */
+ public final Object getData() {
+ return data;
+ }
+
+ /**
+ * Return the end column of this node.
+ *
+ * @return the end column of this node
+ */
+ public final int getEndColumn() {
+ return endColumn;
+ }
+
+ /**
+ * Return the end line of this node.
+ *
+ * @return the end line of this node
+ */
+ public final int getEndLine() {
+ return endLine;
+ }
+
+ /**
+ * Sets the begin column of this node.
+ *
+ * @param beginColumn
+ * the begin column of this node
+ */
+ public final void setBeginColumn(int beginColumn) {
+ this.beginColumn = beginColumn;
+ }
+
+ /**
+ * Sets the begin line of this node.
+ *
+ * @param beginLine
+ * the begin line of this node
+ */
+ public final void setBeginLine(int beginLine) {
+ this.beginLine = beginLine;
+ }
+
+ /**
+ * Use this to store additional information to this node.
+ */
+ public final void setData(Object data) {
+ this.data = data;
+ }
+
+ /**
+ * Sets the end column of this node.
+ *
+ * @param endColumn
+ * the end column of this node
+ */
+ public final void setEndColumn(int endColumn) {
+ this.endColumn = endColumn;
+ }
+
+ /**
+ * Sets the end line of this node.
+ *
+ * @param endLine
+ * the end line of this node
+ */
+ public final void setEndLine(int endLine) {
+ this.endLine = endLine;
+ }
+
+ /**
+ * Return the String representation of this node.
+ *
+ * @return the String representation of this node
+ */
+ @Override
+ public final String toString() {
+ DumpVisitor visitor = new DumpVisitor();
+ accept(visitor, null);
+ return visitor.getSource();
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/PackageDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/PackageDeclaration.java
new file mode 100644
index 000000000..dbc327e7a
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/PackageDeclaration.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 17/10/2007
+ */
+package japa.parser.ast;
+
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * <p>
+ * This class represents the package declaration. The package declaration is
+ * optional for the {@link CompilationUnit}.
+ * </p>
+ * The PackageDeclaration is constructed following the syntax:<br>
+ * <code>
+ * <table>
+ * <tr valign=baseline>
+ * <td align=right>PackageDeclaration</td>
+ * <td align=center>::=</td>
+ * <td align=left>
+ * ( {@link AnnotationExpr} )* "package" {@link NameExpr} ) ";"
+ * </td>
+ * </tr>
+ * </table>
+ * </code>
+ *
+ * @author Julio Vilmar Gesser
+ */
+public final class PackageDeclaration extends Node {
+
+ private List<AnnotationExpr> annotations;
+
+ private NameExpr name;
+
+ public PackageDeclaration() {
+ }
+
+ public PackageDeclaration(NameExpr name) {
+ this.name = name;
+ }
+
+ public PackageDeclaration(List<AnnotationExpr> annotations, NameExpr name) {
+ this.annotations = annotations;
+ this.name = name;
+ }
+
+ public PackageDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, List<AnnotationExpr> annotations, NameExpr name) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.annotations = annotations;
+ this.name = name;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ /**
+ * Retrieves the list of annotations declared before the package
+ * declaration. Return <code>null</code> if there are no annotations.
+ *
+ * @return list of annotations or <code>null</code>
+ */
+ public List<AnnotationExpr> getAnnotations() {
+ return annotations;
+ }
+
+ /**
+ * Return the name of the package.
+ *
+ * @return the name of the package
+ */
+ public NameExpr getName() {
+ return name;
+ }
+
+ /**
+ * @param annotations
+ * the annotations to set
+ */
+ public void setAnnotations(List<AnnotationExpr> annotations) {
+ this.annotations = annotations;
+ }
+
+ /**
+ * Sets the name of this package declaration.
+ *
+ * @param name
+ * the name to set
+ */
+ public void setName(NameExpr name) {
+ this.name = name;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/TypeParameter.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/TypeParameter.java
new file mode 100644
index 000000000..18ffea567
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/TypeParameter.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast;
+
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * <p>
+ * This class represents the declaration of a genetics argument.
+ * </p>
+ * The TypeParameter is constructed following the syntax:<br>
+ * <code>
+ * <table>
+ * <tr valign=baseline>
+ * <td align=right>TypeParameter</td>
+ * <td align=center>::=</td>
+ * <td align=left>
+ * &lt;IDENTIFIER&gt; ( "extends" {@link ClassOrInterfaceType} ( "&" {@link ClassOrInterfaceType} )* )?
+ * </td>
+ * </tr>
+ * </table>
+ * </code>
+ *
+ * @author Julio Vilmar Gesser
+ */
+public final class TypeParameter extends Node {
+
+ private String name;
+
+ private List<ClassOrInterfaceType> typeBound;
+
+ public TypeParameter() {
+ }
+
+ public TypeParameter(String name, List<ClassOrInterfaceType> typeBound) {
+ this.name = name;
+ this.typeBound = typeBound;
+ }
+
+ public TypeParameter(int beginLine, int beginColumn, int endLine, int endColumn, String name, List<ClassOrInterfaceType> typeBound) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.name = name;
+ this.typeBound = typeBound;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ /**
+ * Return the name of the paramenter.
+ *
+ * @return the name of the paramenter
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Return the list of {@link ClassOrInterfaceType} that this parameter
+ * extends. Return <code>null</code> null if there are no type.
+ *
+ * @return list of types that this paramente extends or <code>null</code>
+ */
+ public List<ClassOrInterfaceType> getTypeBound() {
+ return typeBound;
+ }
+
+ /**
+ * Sets the name of this type parameter.
+ *
+ * @param name
+ * the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Sets the list o types.
+ *
+ * @param typeBound
+ * the typeBound to set
+ */
+ public void setTypeBound(List<ClassOrInterfaceType> typeBound) {
+ this.typeBound = typeBound;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/AnnotationDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/AnnotationDeclaration.java
new file mode 100644
index 000000000..a721bb08c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/AnnotationDeclaration.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 21/11/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class AnnotationDeclaration extends TypeDeclaration {
+
+ public AnnotationDeclaration() {
+ }
+
+ public AnnotationDeclaration(int modifiers, String name) {
+ super(modifiers, name);
+ }
+
+ public AnnotationDeclaration(JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, String name, List<BodyDeclaration> members) {
+ super(annotations, javaDoc, modifiers, name, members);
+ }
+
+ public AnnotationDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, String name, List<BodyDeclaration> members) {
+ super(beginLine, beginColumn, endLine, endColumn, annotations, javaDoc, modifiers, name, members);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/AnnotationMemberDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/AnnotationMemberDeclaration.java
new file mode 100644
index 000000000..2271b4826
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/AnnotationMemberDeclaration.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 21/11/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class AnnotationMemberDeclaration extends BodyDeclaration {
+
+ private int modifiers;
+
+ private Type type;
+
+ private String name;
+
+ private Expression defaultValue;
+
+ public AnnotationMemberDeclaration() {
+ }
+
+ public AnnotationMemberDeclaration(int modifiers, Type type, String name, Expression defaultValue) {
+ this.modifiers = modifiers;
+ this.type = type;
+ this.name = name;
+ this.defaultValue = defaultValue;
+ }
+
+ public AnnotationMemberDeclaration(JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, Type type, String name, Expression defaultValue) {
+ super(annotations, javaDoc);
+ this.modifiers = modifiers;
+ this.type = type;
+ this.name = name;
+ this.defaultValue = defaultValue;
+ }
+
+ public AnnotationMemberDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, Type type, String name, Expression defaultValue) {
+ super(beginLine, beginColumn, endLine, endColumn, annotations, javaDoc);
+ this.modifiers = modifiers;
+ this.type = type;
+ this.name = name;
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getDefaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * Return the modifiers of this member declaration.
+ *
+ * @see ModifierSet
+ * @return modifiers
+ */
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setDefaultValue(Expression defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ public void setModifiers(int modifiers) {
+ this.modifiers = modifiers;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/BodyDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/BodyDeclaration.java
new file mode 100644
index 000000000..86957c846
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/BodyDeclaration.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.Node;
+import japa.parser.ast.expr.AnnotationExpr;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public abstract class BodyDeclaration extends Node {
+
+ private JavadocComment javaDoc;
+
+ private List<AnnotationExpr> annotations;
+
+ public BodyDeclaration() {
+ }
+
+ public BodyDeclaration(List<AnnotationExpr> annotations, JavadocComment javaDoc) {
+ this.javaDoc = javaDoc;
+ this.annotations = annotations;
+ }
+
+ public BodyDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, List<AnnotationExpr> annotations, JavadocComment javaDoc) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.javaDoc = javaDoc;
+ this.annotations = annotations;
+ }
+
+ public final JavadocComment getJavaDoc() {
+ return javaDoc;
+ }
+
+ public final List<AnnotationExpr> getAnnotations() {
+ return annotations;
+ }
+
+ public final void setJavaDoc(JavadocComment javaDoc) {
+ this.javaDoc = javaDoc;
+ }
+
+ public final void setAnnotations(List<AnnotationExpr> annotations) {
+ this.annotations = annotations;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/ClassOrInterfaceDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/ClassOrInterfaceDeclaration.java
new file mode 100644
index 000000000..35b231cbe
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/ClassOrInterfaceDeclaration.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ClassOrInterfaceDeclaration extends TypeDeclaration {
+
+ private boolean interface_;
+
+ private List<TypeParameter> typeParameters;
+
+ private List<ClassOrInterfaceType> extendsList;
+
+ private List<ClassOrInterfaceType> implementsList;
+
+ public ClassOrInterfaceDeclaration() {
+ }
+
+ public ClassOrInterfaceDeclaration(int modifiers, boolean isInterface, String name) {
+ super(modifiers, name);
+ this.interface_ = isInterface;
+ }
+
+ public ClassOrInterfaceDeclaration(JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, boolean isInterface, String name, List<TypeParameter> typeParameters, List<ClassOrInterfaceType> extendsList, List<ClassOrInterfaceType> implementsList, List<BodyDeclaration> members) {
+ super(annotations, javaDoc, modifiers, name, members);
+ this.interface_ = isInterface;
+ this.typeParameters = typeParameters;
+ this.extendsList = extendsList;
+ this.implementsList = implementsList;
+ }
+
+ public ClassOrInterfaceDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, boolean isInterface, String name, List<TypeParameter> typeParameters, List<ClassOrInterfaceType> extendsList, List<ClassOrInterfaceType> implementsList, List<BodyDeclaration> members) {
+ super(beginLine, beginColumn, endLine, endColumn, annotations, javaDoc, modifiers, name, members);
+ this.interface_ = isInterface;
+ this.typeParameters = typeParameters;
+ this.extendsList = extendsList;
+ this.implementsList = implementsList;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<ClassOrInterfaceType> getExtends() {
+ return extendsList;
+ }
+
+ public List<ClassOrInterfaceType> getImplements() {
+ return implementsList;
+ }
+
+ public List<TypeParameter> getTypeParameters() {
+ return typeParameters;
+ }
+
+ public boolean isInterface() {
+ return interface_;
+ }
+
+ public void setExtends(List<ClassOrInterfaceType> extendsList) {
+ this.extendsList = extendsList;
+ }
+
+ public void setImplements(List<ClassOrInterfaceType> implementsList) {
+ this.implementsList = implementsList;
+ }
+
+ public void setInterface(boolean interface_) {
+ this.interface_ = interface_;
+ }
+
+ public void setTypeParameters(List<TypeParameter> typeParameters) {
+ this.typeParameters = typeParameters;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/ConstructorDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/ConstructorDeclaration.java
new file mode 100644
index 000000000..705c5050e
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/ConstructorDeclaration.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ConstructorDeclaration extends BodyDeclaration {
+
+ private int modifiers;
+
+ private List<TypeParameter> typeParameters;
+
+ private String name;
+
+ private List<Parameter> parameters;
+
+ private List<NameExpr> throws_;
+
+ private BlockStmt block;
+
+ public ConstructorDeclaration() {
+ }
+
+ public ConstructorDeclaration(int modifiers, String name) {
+ this.modifiers = modifiers;
+ this.name = name;
+ }
+
+ public ConstructorDeclaration(JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, List<TypeParameter> typeParameters, String name, List<Parameter> parameters, List<NameExpr> throws_, BlockStmt block) {
+ super(annotations, javaDoc);
+ this.modifiers = modifiers;
+ this.typeParameters = typeParameters;
+ this.name = name;
+ this.parameters = parameters;
+ this.throws_ = throws_;
+ this.block = block;
+ }
+
+ public ConstructorDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, List<TypeParameter> typeParameters, String name, List<Parameter> parameters, List<NameExpr> throws_, BlockStmt block) {
+ super(beginLine, beginColumn, endLine, endColumn, annotations, javaDoc);
+ this.modifiers = modifiers;
+ this.typeParameters = typeParameters;
+ this.name = name;
+ this.parameters = parameters;
+ this.throws_ = throws_;
+ this.block = block;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public BlockStmt getBlock() {
+ return block;
+ }
+
+ /**
+ * Return the modifiers of this member declaration.
+ *
+ * @see ModifierSet
+ * @return modifiers
+ */
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List<Parameter> getParameters() {
+ return parameters;
+ }
+
+ public List<NameExpr> getThrows() {
+ return throws_;
+ }
+
+ public List<TypeParameter> getTypeParameters() {
+ return typeParameters;
+ }
+
+ public void setBlock(BlockStmt block) {
+ this.block = block;
+ }
+
+ public void setModifiers(int modifiers) {
+ this.modifiers = modifiers;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setParameters(List<Parameter> parameters) {
+ this.parameters = parameters;
+ }
+
+ public void setThrows(List<NameExpr> throws_) {
+ this.throws_ = throws_;
+ }
+
+ public void setTypeParameters(List<TypeParameter> typeParameters) {
+ this.typeParameters = typeParameters;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EmptyMemberDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EmptyMemberDeclaration.java
new file mode 100644
index 000000000..13ee0e1e2
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EmptyMemberDeclaration.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 07/11/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class EmptyMemberDeclaration extends BodyDeclaration {
+
+ public EmptyMemberDeclaration() {
+ }
+
+ public EmptyMemberDeclaration(JavadocComment javaDoc) {
+ super(null, javaDoc);
+ }
+
+ public EmptyMemberDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc) {
+ super(beginLine, beginColumn, endLine, endColumn, null, javaDoc);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EmptyTypeDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EmptyTypeDeclaration.java
new file mode 100644
index 000000000..0f0c1895c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EmptyTypeDeclaration.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 20/01/2007
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class EmptyTypeDeclaration extends TypeDeclaration {
+
+ public EmptyTypeDeclaration() {
+ }
+
+ public EmptyTypeDeclaration(JavadocComment javaDoc) {
+ super(null, javaDoc, 0, null, null);
+ }
+
+ public EmptyTypeDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc) {
+ super(beginLine, beginColumn, endLine, endColumn, null, javaDoc, 0, null, null);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EnumConstantDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EnumConstantDeclaration.java
new file mode 100644
index 000000000..62c653842
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EnumConstantDeclaration.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/11/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class EnumConstantDeclaration extends BodyDeclaration {
+
+ private String name;
+
+ private List<Expression> args;
+
+ private List<BodyDeclaration> classBody;
+
+ public EnumConstantDeclaration() {
+ }
+
+ public EnumConstantDeclaration(String name) {
+ this.name = name;
+ }
+
+ public EnumConstantDeclaration(JavadocComment javaDoc, List<AnnotationExpr> annotations, String name, List<Expression> args, List<BodyDeclaration> classBody) {
+ super(annotations, javaDoc);
+ this.name = name;
+ this.args = args;
+ this.classBody = classBody;
+ }
+
+ public EnumConstantDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc, List<AnnotationExpr> annotations, String name, List<Expression> args, List<BodyDeclaration> classBody) {
+ super(beginLine, beginColumn, endLine, endColumn, annotations, javaDoc);
+ this.name = name;
+ this.args = args;
+ this.classBody = classBody;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<Expression> getArgs() {
+ return args;
+ }
+
+ public List<BodyDeclaration> getClassBody() {
+ return classBody;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setArgs(List<Expression> args) {
+ this.args = args;
+ }
+
+ public void setClassBody(List<BodyDeclaration> classBody) {
+ this.classBody = classBody;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EnumDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EnumDeclaration.java
new file mode 100644
index 000000000..b8776c194
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/EnumDeclaration.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class EnumDeclaration extends TypeDeclaration {
+
+ private List<ClassOrInterfaceType> implementsList;
+
+ private List<EnumConstantDeclaration> entries;
+
+ public EnumDeclaration() {
+ }
+
+ public EnumDeclaration(int modifiers, String name) {
+ super(modifiers, name);
+ }
+
+ public EnumDeclaration(JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, String name, List<ClassOrInterfaceType> implementsList, List<EnumConstantDeclaration> entries, List<BodyDeclaration> members) {
+ super(annotations, javaDoc, modifiers, name, members);
+ this.implementsList = implementsList;
+ this.entries = entries;
+ }
+
+ public EnumDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, String name, List<ClassOrInterfaceType> implementsList, List<EnumConstantDeclaration> entries, List<BodyDeclaration> members) {
+ super(beginLine, beginColumn, endLine, endColumn, annotations, javaDoc, modifiers, name, members);
+ this.implementsList = implementsList;
+ this.entries = entries;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<EnumConstantDeclaration> getEntries() {
+ return entries;
+ }
+
+ public List<ClassOrInterfaceType> getImplements() {
+ return implementsList;
+ }
+
+ public void setEntries(List<EnumConstantDeclaration> entries) {
+ this.entries = entries;
+ }
+
+ public void setImplements(List<ClassOrInterfaceType> implementsList) {
+ this.implementsList = implementsList;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/FieldDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/FieldDeclaration.java
new file mode 100644
index 000000000..179436a43
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/FieldDeclaration.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class FieldDeclaration extends BodyDeclaration {
+
+ private int modifiers;
+
+ private Type type;
+
+ private List<VariableDeclarator> variables;
+
+ public FieldDeclaration() {
+ }
+
+ public FieldDeclaration(int modifiers, Type type, VariableDeclarator variable) {
+ this.modifiers = modifiers;
+ this.type = type;
+ this.variables = new ArrayList<VariableDeclarator>();
+ this.variables.add(variable);
+ }
+
+ public FieldDeclaration(int modifiers, Type type, List<VariableDeclarator> variables) {
+ this.modifiers = modifiers;
+ this.type = type;
+ this.variables = variables;
+ }
+
+ public FieldDeclaration(JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, Type type, List<VariableDeclarator> variables) {
+ super(annotations, javaDoc);
+ this.modifiers = modifiers;
+ this.type = type;
+ this.variables = variables;
+ }
+
+ public FieldDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, Type type, List<VariableDeclarator> variables) {
+ super(beginLine, beginColumn, endLine, endColumn, annotations, javaDoc);
+ this.modifiers = modifiers;
+ this.type = type;
+ this.variables = variables;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ /**
+ * Return the modifiers of this member declaration.
+ *
+ * @see ModifierSet
+ * @return modifiers
+ */
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public List<VariableDeclarator> getVariables() {
+ return variables;
+ }
+
+ public void setModifiers(int modifiers) {
+ this.modifiers = modifiers;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ public void setVariables(List<VariableDeclarator> variables) {
+ this.variables = variables;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/InitializerDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/InitializerDeclaration.java
new file mode 100644
index 000000000..be18e7fe5
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/InitializerDeclaration.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 07/11/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class InitializerDeclaration extends BodyDeclaration {
+
+ private boolean isStatic;
+
+ private BlockStmt block;
+
+ public InitializerDeclaration() {
+ }
+
+ public InitializerDeclaration(boolean isStatic, BlockStmt block) {
+ this.isStatic = isStatic;
+ this.block = block;
+ }
+
+ public InitializerDeclaration(JavadocComment javaDoc, boolean isStatic, BlockStmt block) {
+ super(null, javaDoc);
+ this.isStatic = isStatic;
+ this.block = block;
+ }
+
+ public InitializerDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc, boolean isStatic, BlockStmt block) {
+ super(beginLine, beginColumn, endLine, endColumn, null, javaDoc);
+ this.isStatic = isStatic;
+ this.block = block;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public BlockStmt getBlock() {
+ return block;
+ }
+
+ public boolean isStatic() {
+ return isStatic;
+ }
+
+ public void setBlock(BlockStmt block) {
+ this.block = block;
+ }
+
+ public void setStatic(boolean isStatic) {
+ this.isStatic = isStatic;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/JavadocComment.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/JavadocComment.java
new file mode 100644
index 000000000..589500854
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/JavadocComment.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 23/05/2008
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.Comment;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class JavadocComment extends Comment {
+
+ public JavadocComment() {
+ }
+
+ public JavadocComment(String content) {
+ super(content);
+ }
+
+ public JavadocComment(int beginLine, int beginColumn, int endLine, int endColumn, String content) {
+ super(beginLine, beginColumn, endLine, endColumn, content);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/MethodDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/MethodDeclaration.java
new file mode 100644
index 000000000..4f73e44d1
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/MethodDeclaration.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class MethodDeclaration extends BodyDeclaration {
+
+ private int modifiers;
+
+ private List<TypeParameter> typeParameters;
+
+ private Type type;
+
+ private String name;
+
+ private List<Parameter> parameters;
+
+ private int arrayCount;
+
+ private List<NameExpr> throws_;
+
+ private BlockStmt body;
+
+ public MethodDeclaration() {
+ }
+
+ public MethodDeclaration(int modifiers, Type type, String name) {
+ this.modifiers = modifiers;
+ this.type = type;
+ this.name = name;
+ }
+
+ public MethodDeclaration(int modifiers, Type type, String name, List<Parameter> parameters) {
+ this.modifiers = modifiers;
+ this.type = type;
+ this.name = name;
+ this.parameters = parameters;
+ }
+
+ public MethodDeclaration(JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, List<TypeParameter> typeParameters, Type type, String name, List<Parameter> parameters, int arrayCount, List<NameExpr> throws_, BlockStmt block) {
+ super(annotations, javaDoc);
+ this.modifiers = modifiers;
+ this.typeParameters = typeParameters;
+ this.type = type;
+ this.name = name;
+ this.parameters = parameters;
+ this.arrayCount = arrayCount;
+ this.throws_ = throws_;
+ this.body = block;
+ }
+
+ public MethodDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, JavadocComment javaDoc, int modifiers, List<AnnotationExpr> annotations, List<TypeParameter> typeParameters, Type type, String name, List<Parameter> parameters, int arrayCount, List<NameExpr> throws_, BlockStmt block) {
+ super(beginLine, beginColumn, endLine, endColumn, annotations, javaDoc);
+ this.modifiers = modifiers;
+ this.typeParameters = typeParameters;
+ this.type = type;
+ this.name = name;
+ this.parameters = parameters;
+ this.arrayCount = arrayCount;
+ this.throws_ = throws_;
+ this.body = block;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public int getArrayCount() {
+ return arrayCount;
+ }
+
+ public BlockStmt getBody() {
+ return body;
+ }
+
+ /**
+ * Return the modifiers of this member declaration.
+ *
+ * @see ModifierSet
+ * @return modifiers
+ */
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List<Parameter> getParameters() {
+ return parameters;
+ }
+
+ public List<NameExpr> getThrows() {
+ return throws_;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public List<TypeParameter> getTypeParameters() {
+ return typeParameters;
+ }
+
+ public void setArrayCount(int arrayCount) {
+ this.arrayCount = arrayCount;
+ }
+
+ public void setBody(BlockStmt body) {
+ this.body = body;
+ }
+
+ public void setModifiers(int modifiers) {
+ this.modifiers = modifiers;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setParameters(List<Parameter> parameters) {
+ this.parameters = parameters;
+ }
+
+ public void setThrows(List<NameExpr> throws_) {
+ this.throws_ = throws_;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ public void setTypeParameters(List<TypeParameter> typeParameters) {
+ this.typeParameters = typeParameters;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/ModifierSet.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/ModifierSet.java
new file mode 100644
index 000000000..fd9b3c23d
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/ModifierSet.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+package japa.parser.ast.body;
+
+/**
+ * Class to hold modifiers.
+ */
+public final class ModifierSet {
+
+ /* Definitions of the bits in the modifiers field. */
+ public static final int PUBLIC = 0x0001;
+
+ public static final int PROTECTED = 0x0002;
+
+ public static final int PRIVATE = 0x0004;
+
+ public static final int ABSTRACT = 0x0008;
+
+ public static final int STATIC = 0x0010;
+
+ public static final int FINAL = 0x0020;
+
+ public static final int SYNCHRONIZED = 0x0040;
+
+ public static final int NATIVE = 0x0080;
+
+ public static final int TRANSIENT = 0x0100;
+
+ public static final int VOLATILE = 0x0200;
+
+ public static final int STRICTFP = 0x1000;
+
+ /**
+ * Adds the given modifier.
+ */
+ public static int addModifier(int modifiers, int mod) {
+ return modifiers |= mod;
+ }
+
+ public static boolean hasModifier(int modifiers, int modifier) {
+ return (modifiers & modifier) != 0;
+ }
+
+ public static boolean isAbstract(int modifiers) {
+ return (modifiers & ABSTRACT) != 0;
+ }
+
+ public static boolean isFinal(int modifiers) {
+ return (modifiers & FINAL) != 0;
+ }
+
+ public static boolean isNative(int modifiers) {
+ return (modifiers & NATIVE) != 0;
+ }
+
+ public static boolean isPrivate(int modifiers) {
+ return (modifiers & PRIVATE) != 0;
+ }
+
+ public static boolean isProtected(int modifiers) {
+ return (modifiers & PROTECTED) != 0;
+ }
+
+ /**
+ * A set of accessors that indicate whether the specified modifier is in the
+ * set.
+ */
+
+ public static boolean isPublic(int modifiers) {
+ return (modifiers & PUBLIC) != 0;
+ }
+
+ public static boolean isStatic(int modifiers) {
+ return (modifiers & STATIC) != 0;
+ }
+
+ public static boolean isStrictfp(int modifiers) {
+ return (modifiers & STRICTFP) != 0;
+ }
+
+ public static boolean isSynchronized(int modifiers) {
+ return (modifiers & SYNCHRONIZED) != 0;
+ }
+
+ public static boolean isTransient(int modifiers) {
+ return (modifiers & TRANSIENT) != 0;
+ }
+
+ public static boolean isVolatile(int modifiers) {
+ return (modifiers & VOLATILE) != 0;
+ }
+
+ /**
+ * Removes the given modifier.
+ */
+ public static int removeModifier(int modifiers, int mod) {
+ return modifiers &= ~mod;
+ }
+
+ private ModifierSet() {
+ }
+} \ No newline at end of file
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/Parameter.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/Parameter.java
new file mode 100644
index 000000000..5dd17c7d1
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/Parameter.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 03/11/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.Node;
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class Parameter extends Node {
+
+ private int modifiers;
+
+ private List<AnnotationExpr> annotations;
+
+ private Type type;
+
+ private boolean isVarArgs;
+
+ private VariableDeclaratorId id;
+
+ public Parameter() {
+ }
+
+ public Parameter(Type type, VariableDeclaratorId id) {
+ this.type = type;
+ this.id = id;
+ }
+
+ public Parameter(int modifiers, Type type, VariableDeclaratorId id) {
+ this.modifiers = modifiers;
+ this.type = type;
+ this.id = id;
+ }
+
+ public Parameter(int beginLine, int beginColumn, int endLine, int endColumn, int modifiers, List<AnnotationExpr> annotations, Type type, boolean isVarArgs, VariableDeclaratorId id) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.modifiers = modifiers;
+ this.annotations = annotations;
+ this.type = type;
+ this.isVarArgs = isVarArgs;
+ this.id = id;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<AnnotationExpr> getAnnotations() {
+ return annotations;
+ }
+
+ public VariableDeclaratorId getId() {
+ return id;
+ }
+
+ /**
+ * Return the modifiers of this parameter declaration.
+ *
+ * @see ModifierSet
+ * @return modifiers
+ */
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public boolean isVarArgs() {
+ return isVarArgs;
+ }
+
+ public void setAnnotations(List<AnnotationExpr> annotations) {
+ this.annotations = annotations;
+ }
+
+ public void setId(VariableDeclaratorId id) {
+ this.id = id;
+ }
+
+ public void setModifiers(int modifiers) {
+ this.modifiers = modifiers;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ public void setVarArgs(boolean isVarArgs) {
+ this.isVarArgs = isVarArgs;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/TypeDeclaration.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/TypeDeclaration.java
new file mode 100644
index 000000000..aa37b776c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/TypeDeclaration.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.expr.AnnotationExpr;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public abstract class TypeDeclaration extends BodyDeclaration {
+
+ private String name;
+
+ private int modifiers;
+
+ private List<BodyDeclaration> members;
+
+ public TypeDeclaration() {
+ }
+
+ public TypeDeclaration(int modifiers, String name) {
+ this.name = name;
+ this.modifiers = modifiers;
+ }
+
+ public TypeDeclaration(List<AnnotationExpr> annotations, JavadocComment javaDoc, int modifiers, String name, List<BodyDeclaration> members) {
+ super(annotations, javaDoc);
+ this.name = name;
+ this.modifiers = modifiers;
+ this.members = members;
+ }
+
+ public TypeDeclaration(int beginLine, int beginColumn, int endLine, int endColumn, List<AnnotationExpr> annotations, JavadocComment javaDoc, int modifiers, String name, List<BodyDeclaration> members) {
+ super(beginLine, beginColumn, endLine, endColumn, annotations, javaDoc);
+ this.name = name;
+ this.modifiers = modifiers;
+ this.members = members;
+ }
+
+ public final List<BodyDeclaration> getMembers() {
+ return members;
+ }
+
+ /**
+ * Return the modifiers of this type declaration.
+ *
+ * @see ModifierSet
+ * @return modifiers
+ */
+ public final int getModifiers() {
+ return modifiers;
+ }
+
+ public final String getName() {
+ return name;
+ }
+
+ public void setMembers(List<BodyDeclaration> members) {
+ this.members = members;
+ }
+
+ public final void setModifiers(int modifiers) {
+ this.modifiers = modifiers;
+ }
+
+ public final void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/VariableDeclarator.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/VariableDeclarator.java
new file mode 100644
index 000000000..25f9313f3
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/VariableDeclarator.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.Node;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class VariableDeclarator extends Node {
+
+ private VariableDeclaratorId id;
+
+ private Expression init;
+
+ public VariableDeclarator() {
+ }
+
+ public VariableDeclarator(VariableDeclaratorId id) {
+ this.id = id;
+ }
+
+ public VariableDeclarator(VariableDeclaratorId id, Expression init) {
+ this.id = id;
+ this.init = init;
+ }
+
+ public VariableDeclarator(int beginLine, int beginColumn, int endLine, int endColumn, VariableDeclaratorId id, Expression init) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.id = id;
+ this.init = init;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public VariableDeclaratorId getId() {
+ return id;
+ }
+
+ public Expression getInit() {
+ return init;
+ }
+
+ public void setId(VariableDeclaratorId id) {
+ this.id = id;
+ }
+
+ public void setInit(Expression init) {
+ this.init = init;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/body/VariableDeclaratorId.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/VariableDeclaratorId.java
new file mode 100644
index 000000000..45029477c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/body/VariableDeclaratorId.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.body;
+
+import japa.parser.ast.Node;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class VariableDeclaratorId extends Node {
+
+ private String name;
+
+ private int arrayCount;
+
+ public VariableDeclaratorId() {
+ }
+
+ public VariableDeclaratorId(String name) {
+ this.name = name;
+ }
+
+ public VariableDeclaratorId(int beginLine, int beginColumn, int endLine, int endColumn, String name, int arrayCount) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.name = name;
+ this.arrayCount = arrayCount;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public int getArrayCount() {
+ return arrayCount;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setArrayCount(int arrayCount) {
+ this.arrayCount = arrayCount;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/AnnotationExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/AnnotationExpr.java
new file mode 100644
index 000000000..874b5b649
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/AnnotationExpr.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 21/11/2006
+ */
+package japa.parser.ast.expr;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public abstract class AnnotationExpr extends Expression {
+
+ public AnnotationExpr() {
+ }
+
+ public AnnotationExpr(int beginLine, int beginColumn, int endLine, int endColumn) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayAccessExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayAccessExpr.java
new file mode 100644
index 000000000..62a8bade2
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayAccessExpr.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ArrayAccessExpr extends Expression {
+
+ private Expression name;
+
+ private Expression index;
+
+ public ArrayAccessExpr() {
+ }
+
+ public ArrayAccessExpr(Expression name, Expression index) {
+ this.name = name;
+ this.index = index;
+ }
+
+ public ArrayAccessExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression name, Expression index) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.name = name;
+ this.index = index;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getIndex() {
+ return index;
+ }
+
+ public Expression getName() {
+ return name;
+ }
+
+ public void setIndex(Expression index) {
+ this.index = index;
+ }
+
+ public void setName(Expression name) {
+ this.name = name;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayCreationExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayCreationExpr.java
new file mode 100644
index 000000000..00ac5070e
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayCreationExpr.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ArrayCreationExpr extends Expression {
+
+ private Type type;
+
+ private int arrayCount;
+
+ private ArrayInitializerExpr initializer;
+
+ private List<Expression> dimensions;
+
+ public ArrayCreationExpr() {
+ }
+
+ public ArrayCreationExpr(Type type, int arrayCount, ArrayInitializerExpr initializer) {
+ this.type = type;
+ this.arrayCount = arrayCount;
+ this.initializer = initializer;
+ this.dimensions = null;
+ }
+
+ public ArrayCreationExpr(int beginLine, int beginColumn, int endLine, int endColumn, Type type, int arrayCount, ArrayInitializerExpr initializer) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.type = type;
+ this.arrayCount = arrayCount;
+ this.initializer = initializer;
+ this.dimensions = null;
+ }
+
+ public ArrayCreationExpr(Type type, List<Expression> dimensions, int arrayCount) {
+ this.type = type;
+ this.arrayCount = arrayCount;
+ this.dimensions = dimensions;
+ this.initializer = null;
+ }
+
+ public ArrayCreationExpr(int beginLine, int beginColumn, int endLine, int endColumn, Type type, List<Expression> dimensions, int arrayCount) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.type = type;
+ this.arrayCount = arrayCount;
+ this.dimensions = dimensions;
+ this.initializer = null;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public int getArrayCount() {
+ return arrayCount;
+ }
+
+ public List<Expression> getDimensions() {
+ return dimensions;
+ }
+
+ public ArrayInitializerExpr getInitializer() {
+ return initializer;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setArrayCount(int arrayCount) {
+ this.arrayCount = arrayCount;
+ }
+
+ public void setDimensions(List<Expression> dimensions) {
+ this.dimensions = dimensions;
+ }
+
+ public void setInitializer(ArrayInitializerExpr initializer) {
+ this.initializer = initializer;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayInitializerExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayInitializerExpr.java
new file mode 100644
index 000000000..d6e9cd7d8
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ArrayInitializerExpr.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ArrayInitializerExpr extends Expression {
+
+ private List<Expression> values;
+
+ public ArrayInitializerExpr() {
+ }
+
+ public ArrayInitializerExpr(List<Expression> values) {
+ this.values = values;
+ }
+
+ public ArrayInitializerExpr(int beginLine, int beginColumn, int endLine, int endColumn, List<Expression> values) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.values = values;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<Expression> getValues() {
+ return values;
+ }
+
+ public void setValues(List<Expression> values) {
+ this.values = values;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/AssignExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/AssignExpr.java
new file mode 100644
index 000000000..1b716b4bd
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/AssignExpr.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class AssignExpr extends Expression {
+
+ public static enum Operator {
+ assign, // =
+ plus, // +=
+ minus, // -=
+ star, // *=
+ slash, // /=
+ and, // &=
+ or, // |=
+ xor, // ^=
+ rem, // %=
+ lShift, // <<=
+ rSignedShift, // >>=
+ rUnsignedShift, // >>>=
+ }
+
+ private Expression target;
+
+ private Expression value;
+
+ private Operator op;
+
+ public AssignExpr() {
+ }
+
+ public AssignExpr(Expression target, Expression value, Operator op) {
+ this.target = target;
+ this.value = value;
+ this.op = op;
+ }
+
+ public AssignExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression target, Expression value, Operator op) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.target = target;
+ this.value = value;
+ this.op = op;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Operator getOperator() {
+ return op;
+ }
+
+ public Expression getTarget() {
+ return target;
+ }
+
+ public Expression getValue() {
+ return value;
+ }
+
+ public void setOperator(Operator op) {
+ this.op = op;
+ }
+
+ public void setTarget(Expression target) {
+ this.target = target;
+ }
+
+ public void setValue(Expression value) {
+ this.value = value;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/BinaryExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/BinaryExpr.java
new file mode 100644
index 000000000..18327a852
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/BinaryExpr.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class BinaryExpr extends Expression {
+
+ public static enum Operator {
+ or, // ||
+ and, // &&
+ binOr, // |
+ binAnd, // &
+ xor, // ^
+ equals, // ==
+ notEquals, // !=
+ less, // <
+ greater, // >
+ lessEquals, // <=
+ greaterEquals, // >=
+ lShift, // <<
+ rSignedShift, // >>
+ rUnsignedShift, // >>>
+ plus, // +
+ minus, // -
+ times, // *
+ divide, // /
+ remainder, // %
+ }
+
+ private Expression left;
+
+ private Expression right;
+
+ private Operator op;
+
+ public BinaryExpr() {
+ }
+
+ public BinaryExpr(Expression left, Expression right, Operator op) {
+ this.left = left;
+ this.right = right;
+ this.op = op;
+ }
+
+ public BinaryExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression left, Expression right, Operator op) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.left = left;
+ this.right = right;
+ this.op = op;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getLeft() {
+ return left;
+ }
+
+ public Operator getOperator() {
+ return op;
+ }
+
+ public Expression getRight() {
+ return right;
+ }
+
+ public void setLeft(Expression left) {
+ this.left = left;
+ }
+
+ public void setOperator(Operator op) {
+ this.op = op;
+ }
+
+ public void setRight(Expression right) {
+ this.right = right;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/BooleanLiteralExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/BooleanLiteralExpr.java
new file mode 100644
index 000000000..b6bd2481c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/BooleanLiteralExpr.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 02/03/2007
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class BooleanLiteralExpr extends LiteralExpr {
+
+ private boolean value;
+
+ public BooleanLiteralExpr() {
+ }
+
+ public BooleanLiteralExpr(boolean value) {
+ this.value = value;
+ }
+
+ public BooleanLiteralExpr(int beginLine, int beginColumn, int endLine, int endColumn, boolean value) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.value = value;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public boolean getValue() {
+ return value;
+ }
+
+ public void setValue(boolean value) {
+ this.value = value;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/CastExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/CastExpr.java
new file mode 100644
index 000000000..921452cff
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/CastExpr.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class CastExpr extends Expression {
+
+ private Type type;
+
+ private Expression expr;
+
+ public CastExpr() {
+ }
+
+ public CastExpr(Type type, Expression expr) {
+ this.type = type;
+ this.expr = expr;
+ }
+
+ public CastExpr(int beginLine, int beginColumn, int endLine, int endColumn, Type type, Expression expr) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.type = type;
+ this.expr = expr;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getExpr() {
+ return expr;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setExpr(Expression expr) {
+ this.expr = expr;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/CharLiteralExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/CharLiteralExpr.java
new file mode 100644
index 000000000..bc059748e
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/CharLiteralExpr.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 02/03/2007
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class CharLiteralExpr extends StringLiteralExpr {
+
+ public CharLiteralExpr() {
+ }
+
+ public CharLiteralExpr(String value) {
+ super(value);
+ }
+
+ public CharLiteralExpr(int beginLine, int beginColumn, int endLine, int endColumn, String value) {
+ super(beginLine, beginColumn, endLine, endColumn, value);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ClassExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ClassExpr.java
new file mode 100644
index 000000000..9493a8b52
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ClassExpr.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ClassExpr extends Expression {
+
+ private Type type;
+
+ public ClassExpr() {
+ }
+
+ public ClassExpr(Type type) {
+ this.type = type;
+ }
+
+ public ClassExpr(int beginLine, int beginColumn, int endLine, int endColumn, Type type) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.type = type;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ConditionalExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ConditionalExpr.java
new file mode 100644
index 000000000..845a3b1d5
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ConditionalExpr.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ConditionalExpr extends Expression {
+
+ private Expression condition;
+
+ private Expression thenExpr;
+
+ private Expression elseExpr;
+
+ public ConditionalExpr() {
+ }
+
+ public ConditionalExpr(Expression condition, Expression thenExpr, Expression elseExpr) {
+ this.condition = condition;
+ this.thenExpr = thenExpr;
+ this.elseExpr = elseExpr;
+ }
+
+ public ConditionalExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression condition, Expression thenExpr, Expression elseExpr) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.condition = condition;
+ this.thenExpr = thenExpr;
+ this.elseExpr = elseExpr;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getCondition() {
+ return condition;
+ }
+
+ public Expression getElseExpr() {
+ return elseExpr;
+ }
+
+ public Expression getThenExpr() {
+ return thenExpr;
+ }
+
+ public void setCondition(Expression condition) {
+ this.condition = condition;
+ }
+
+ public void setElseExpr(Expression elseExpr) {
+ this.elseExpr = elseExpr;
+ }
+
+ public void setThenExpr(Expression thenExpr) {
+ this.thenExpr = thenExpr;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/DoubleLiteralExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/DoubleLiteralExpr.java
new file mode 100644
index 000000000..1622318e1
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/DoubleLiteralExpr.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 02/03/2007
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class DoubleLiteralExpr extends StringLiteralExpr {
+
+ public DoubleLiteralExpr() {
+ }
+
+ public DoubleLiteralExpr(String value) {
+ super(value);
+ }
+
+ public DoubleLiteralExpr(int beginLine, int beginColumn, int endLine, int endColumn, String value) {
+ super(beginLine, beginColumn, endLine, endColumn, value);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/EnclosedExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/EnclosedExpr.java
new file mode 100644
index 000000000..89175677c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/EnclosedExpr.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class EnclosedExpr extends Expression {
+
+ private Expression inner;
+
+ public EnclosedExpr() {
+ }
+
+ public EnclosedExpr(Expression inner) {
+ this.inner = inner;
+ }
+
+ public EnclosedExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression inner) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.inner = inner;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getInner() {
+ return inner;
+ }
+
+ public void setInner(Expression inner) {
+ this.inner = inner;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/Expression.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/Expression.java
new file mode 100644
index 000000000..743cb4e0c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/Expression.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 10/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.Node;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public abstract class Expression extends Node {
+
+ public Expression() {
+ }
+
+ public Expression(int beginLine, int beginColumn, int endLine, int endColumn) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/FieldAccessExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/FieldAccessExpr.java
new file mode 100644
index 000000000..eec8b674c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/FieldAccessExpr.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class FieldAccessExpr extends Expression {
+
+ private Expression scope;
+
+ private List<Type> typeArgs;
+
+ private String field;
+
+ public FieldAccessExpr() {
+ }
+
+ public FieldAccessExpr(Expression scope, String field) {
+ this.scope = scope;
+ this.field = field;
+ }
+
+ public FieldAccessExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression scope, List<Type> typeArgs, String field) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.scope = scope;
+ this.typeArgs = typeArgs;
+ this.field = field;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public String getField() {
+ return field;
+ }
+
+ public Expression getScope() {
+ return scope;
+ }
+
+ public List<Type> getTypeArgs() {
+ return typeArgs;
+ }
+
+ public void setField(String field) {
+ this.field = field;
+ }
+
+ public void setScope(Expression scope) {
+ this.scope = scope;
+ }
+
+ public void setTypeArgs(List<Type> typeArgs) {
+ this.typeArgs = typeArgs;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/InstanceOfExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/InstanceOfExpr.java
new file mode 100644
index 000000000..3fb942a24
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/InstanceOfExpr.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class InstanceOfExpr extends Expression {
+
+ private Expression expr;
+
+ private Type type;
+
+ public InstanceOfExpr() {
+ }
+
+ public InstanceOfExpr(Expression expr, Type type) {
+ this.expr = expr;
+ this.type = type;
+ }
+
+ public InstanceOfExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression expr, Type type) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.expr = expr;
+ this.type = type;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getExpr() {
+ return expr;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setExpr(Expression expr) {
+ this.expr = expr;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/IntegerLiteralExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/IntegerLiteralExpr.java
new file mode 100644
index 000000000..8df0c4aeb
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/IntegerLiteralExpr.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 02/03/2007
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public class IntegerLiteralExpr extends StringLiteralExpr {
+
+ private static final String UNSIGNED_MIN_VALUE = "2147483648";
+
+ protected static final String MIN_VALUE = "-" + UNSIGNED_MIN_VALUE;
+
+ public IntegerLiteralExpr() {
+ }
+
+ public IntegerLiteralExpr(String value) {
+ super(value);
+ }
+
+ public IntegerLiteralExpr(int beginLine, int beginColumn, int endLine, int endColumn, String value) {
+ super(beginLine, beginColumn, endLine, endColumn, value);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public final boolean isMinValue() {
+ return value != null && //
+ value.length() == 10 && //
+ value.equals(UNSIGNED_MIN_VALUE);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/IntegerLiteralMinValueExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/IntegerLiteralMinValueExpr.java
new file mode 100644
index 000000000..f4d9068de
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/IntegerLiteralMinValueExpr.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 09/03/2007
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class IntegerLiteralMinValueExpr extends IntegerLiteralExpr {
+
+ public IntegerLiteralMinValueExpr() {
+ super(MIN_VALUE);
+ }
+
+ public IntegerLiteralMinValueExpr(int beginLine, int beginColumn, int endLine, int endColumn) {
+ super(beginLine, beginColumn, endLine, endColumn, MIN_VALUE);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LiteralExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LiteralExpr.java
new file mode 100644
index 000000000..65b020e0f
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LiteralExpr.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public abstract class LiteralExpr extends Expression {
+
+ public LiteralExpr() {
+ }
+
+ public LiteralExpr(int beginLine, int beginColumn, int endLine, int endColumn) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LongLiteralExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LongLiteralExpr.java
new file mode 100644
index 000000000..b804cd5ae
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LongLiteralExpr.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 02/03/2007
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public class LongLiteralExpr extends StringLiteralExpr {
+
+ private static final String UNSIGNED_MIN_VALUE = "9223372036854775808";
+
+ protected static final String MIN_VALUE = "-" + UNSIGNED_MIN_VALUE + "L";
+
+ public LongLiteralExpr() {
+ }
+
+ public LongLiteralExpr(String value) {
+ super(value);
+ }
+
+ public LongLiteralExpr(int beginLine, int beginColumn, int endLine, int endColumn, String value) {
+ super(beginLine, beginColumn, endLine, endColumn, value);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public final boolean isMinValue() {
+ return value != null && //
+ value.length() == 20 && //
+ value.startsWith(UNSIGNED_MIN_VALUE) && //
+ (value.charAt(19) == 'L' || value.charAt(19) == 'l');
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LongLiteralMinValueExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LongLiteralMinValueExpr.java
new file mode 100644
index 000000000..212306626
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/LongLiteralMinValueExpr.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 09/03/2007
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class LongLiteralMinValueExpr extends LongLiteralExpr {
+
+ public LongLiteralMinValueExpr() {
+ super(MIN_VALUE);
+ }
+
+ public LongLiteralMinValueExpr(int beginLine, int beginColumn, int endLine, int endColumn) {
+ super(beginLine, beginColumn, endLine, endColumn, MIN_VALUE);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MarkerAnnotationExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MarkerAnnotationExpr.java
new file mode 100644
index 000000000..c8f6163ee
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MarkerAnnotationExpr.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 21/11/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class MarkerAnnotationExpr extends AnnotationExpr {
+
+ private NameExpr name;
+
+ public MarkerAnnotationExpr() {
+ }
+
+ public MarkerAnnotationExpr(NameExpr name) {
+ this.name = name;
+ }
+
+ public MarkerAnnotationExpr(int beginLine, int beginColumn, int endLine, int endColumn, NameExpr name) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.name = name;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public NameExpr getName() {
+ return name;
+ }
+
+ public void setName(NameExpr name) {
+ this.name = name;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MemberValuePair.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MemberValuePair.java
new file mode 100644
index 000000000..fe0a9cf38
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MemberValuePair.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 21/11/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.Node;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class MemberValuePair extends Node {
+
+ private String name;
+
+ private Expression value;
+
+ public MemberValuePair() {
+ }
+
+ public MemberValuePair(String name, Expression value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public MemberValuePair(int beginLine, int beginColumn, int endLine, int endColumn, String name, Expression value) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Expression getValue() {
+ return value;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setValue(Expression value) {
+ this.value = value;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MethodCallExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MethodCallExpr.java
new file mode 100644
index 000000000..5c68818f4
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/MethodCallExpr.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class MethodCallExpr extends Expression {
+
+ private Expression scope;
+
+ private List<Type> typeArgs;
+
+ private String name;
+
+ private List<Expression> args;
+
+ public MethodCallExpr() {
+ }
+
+ public MethodCallExpr(Expression scope, String name) {
+ this.scope = scope;
+ this.name = name;
+ }
+
+ public MethodCallExpr(Expression scope, String name, List<Expression> args) {
+ this.scope = scope;
+ this.name = name;
+ this.args = args;
+ }
+
+ public MethodCallExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression scope, List<Type> typeArgs, String name, List<Expression> args) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.scope = scope;
+ this.typeArgs = typeArgs;
+ this.name = name;
+ this.args = args;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<Expression> getArgs() {
+ return args;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Expression getScope() {
+ return scope;
+ }
+
+ public List<Type> getTypeArgs() {
+ return typeArgs;
+ }
+
+ public void setArgs(List<Expression> args) {
+ this.args = args;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setScope(Expression scope) {
+ this.scope = scope;
+ }
+
+ public void setTypeArgs(List<Type> typeArgs) {
+ this.typeArgs = typeArgs;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NameExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NameExpr.java
new file mode 100644
index 000000000..ebf2610ca
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NameExpr.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public class NameExpr extends Expression {
+
+ private String name;
+
+ public NameExpr() {
+ }
+
+ public NameExpr(String name) {
+ this.name = name;
+ }
+
+ public NameExpr(int beginLine, int beginColumn, int endLine, int endColumn, String name) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.name = name;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public final String getName() {
+ return name;
+ }
+
+ public final void setName(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NormalAnnotationExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NormalAnnotationExpr.java
new file mode 100644
index 000000000..24f53a442
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NormalAnnotationExpr.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 21/11/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class NormalAnnotationExpr extends AnnotationExpr {
+
+ private NameExpr name;
+
+ private List<MemberValuePair> pairs;
+
+ public NormalAnnotationExpr() {
+ }
+
+ public NormalAnnotationExpr(NameExpr name, List<MemberValuePair> pairs) {
+ this.name = name;
+ this.pairs = pairs;
+ }
+
+ public NormalAnnotationExpr(int beginLine, int beginColumn, int endLine, int endColumn, NameExpr name, List<MemberValuePair> pairs) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.name = name;
+ this.pairs = pairs;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public NameExpr getName() {
+ return name;
+ }
+
+ public List<MemberValuePair> getPairs() {
+ return pairs;
+ }
+
+ public void setName(NameExpr name) {
+ this.name = name;
+ }
+
+ public void setPairs(List<MemberValuePair> pairs) {
+ this.pairs = pairs;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NullLiteralExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NullLiteralExpr.java
new file mode 100644
index 000000000..bc04d9cd4
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/NullLiteralExpr.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 02/03/2007
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class NullLiteralExpr extends LiteralExpr {
+
+ public NullLiteralExpr() {
+ }
+
+ public NullLiteralExpr(int beginLine, int beginColumn, int endLine, int endColumn) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ObjectCreationExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ObjectCreationExpr.java
new file mode 100644
index 000000000..a3b467980
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ObjectCreationExpr.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.body.BodyDeclaration;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ObjectCreationExpr extends Expression {
+
+ private Expression scope;
+
+ private ClassOrInterfaceType type;
+
+ private List<Type> typeArgs;
+
+ private List<Expression> args;
+
+ private List<BodyDeclaration> anonymousClassBody;
+
+ public ObjectCreationExpr() {
+ }
+
+ public ObjectCreationExpr(Expression scope, ClassOrInterfaceType type, List<Expression> args) {
+ this.scope = scope;
+ this.type = type;
+ this.args = args;
+ }
+
+ public ObjectCreationExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression scope, ClassOrInterfaceType type, List<Type> typeArgs, List<Expression> args, List<BodyDeclaration> anonymousBody) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.scope = scope;
+ this.type = type;
+ this.typeArgs = typeArgs;
+ this.args = args;
+ this.anonymousClassBody = anonymousBody;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<BodyDeclaration> getAnonymousClassBody() {
+ return anonymousClassBody;
+ }
+
+ public List<Expression> getArgs() {
+ return args;
+ }
+
+ public Expression getScope() {
+ return scope;
+ }
+
+ public ClassOrInterfaceType getType() {
+ return type;
+ }
+
+ public List<Type> getTypeArgs() {
+ return typeArgs;
+ }
+
+ public void setAnonymousClassBody(List<BodyDeclaration> anonymousClassBody) {
+ this.anonymousClassBody = anonymousClassBody;
+ }
+
+ public void setArgs(List<Expression> args) {
+ this.args = args;
+ }
+
+ public void setScope(Expression scope) {
+ this.scope = scope;
+ }
+
+ public void setType(ClassOrInterfaceType type) {
+ this.type = type;
+ }
+
+ public void setTypeArgs(List<Type> typeArgs) {
+ this.typeArgs = typeArgs;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/QualifiedNameExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/QualifiedNameExpr.java
new file mode 100644
index 000000000..5bb8003e1
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/QualifiedNameExpr.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class QualifiedNameExpr extends NameExpr {
+
+ private NameExpr qualifier;
+
+ public QualifiedNameExpr() {
+ }
+
+ public QualifiedNameExpr(NameExpr scope, String name) {
+ super(name);
+ this.qualifier = scope;
+ }
+
+ public QualifiedNameExpr(int beginLine, int beginColumn, int endLine, int endColumn, NameExpr scope, String name) {
+ super(beginLine, beginColumn, endLine, endColumn, name);
+ this.qualifier = scope;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public NameExpr getQualifier() {
+ return qualifier;
+ }
+
+ public void setQualifier(NameExpr qualifier) {
+ this.qualifier = qualifier;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/SingleMemberAnnotationExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/SingleMemberAnnotationExpr.java
new file mode 100644
index 000000000..3f29dd312
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/SingleMemberAnnotationExpr.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 21/11/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class SingleMemberAnnotationExpr extends AnnotationExpr {
+
+ private NameExpr name;
+
+ private Expression memberValue;
+
+ public SingleMemberAnnotationExpr() {
+ }
+
+ public SingleMemberAnnotationExpr(NameExpr name, Expression memberValue) {
+ this.name = name;
+ this.memberValue = memberValue;
+ }
+
+ public SingleMemberAnnotationExpr(int beginLine, int beginColumn, int endLine, int endColumn, NameExpr name, Expression memberValue) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.name = name;
+ this.memberValue = memberValue;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getMemberValue() {
+ return memberValue;
+ }
+
+ public NameExpr getName() {
+ return name;
+ }
+
+ public void setMemberValue(Expression memberValue) {
+ this.memberValue = memberValue;
+ }
+
+ public void setName(NameExpr name) {
+ this.name = name;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/StringLiteralExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/StringLiteralExpr.java
new file mode 100644
index 000000000..05180c48c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/StringLiteralExpr.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 02/03/2007
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public class StringLiteralExpr extends LiteralExpr {
+
+ protected String value;
+
+ public StringLiteralExpr() {
+ }
+
+ public StringLiteralExpr(String value) {
+ this.value = value;
+ }
+
+ public StringLiteralExpr(int beginLine, int beginColumn, int endLine, int endColumn, String value) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.value = value;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public final String getValue() {
+ return value;
+ }
+
+ public final void setValue(String value) {
+ this.value = value;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/SuperExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/SuperExpr.java
new file mode 100644
index 000000000..8eb735c6b
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/SuperExpr.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 20/01/2007
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class SuperExpr extends Expression {
+
+ private Expression classExpr;
+
+ public SuperExpr() {
+ }
+
+ public SuperExpr(Expression classExpr) {
+ this.classExpr = classExpr;
+ }
+
+ public SuperExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression classExpr) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.classExpr = classExpr;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getClassExpr() {
+ return classExpr;
+ }
+
+ public void setClassExpr(Expression classExpr) {
+ this.classExpr = classExpr;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ThisExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ThisExpr.java
new file mode 100644
index 000000000..c453552f5
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/ThisExpr.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ThisExpr extends Expression {
+
+ private Expression classExpr;
+
+ public ThisExpr() {
+ }
+
+ public ThisExpr(Expression classExpr) {
+ this.classExpr = classExpr;
+ }
+
+ public ThisExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression classExpr) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.classExpr = classExpr;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getClassExpr() {
+ return classExpr;
+ }
+
+ public void setClassExpr(Expression classExpr) {
+ this.classExpr = classExpr;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/UnaryExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/UnaryExpr.java
new file mode 100644
index 000000000..1708c52a5
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/UnaryExpr.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class UnaryExpr extends Expression {
+
+ public static enum Operator {
+ positive, // +
+ negative, // -
+ preIncrement, // ++
+ preDecrement, // --
+ not, // !
+ inverse, // ~
+ posIncrement, // ++
+ posDecrement, // --
+ }
+
+ private Expression expr;
+
+ private Operator op;
+
+ public UnaryExpr() {
+ }
+
+ public UnaryExpr(Expression expr, Operator op) {
+ this.expr = expr;
+ this.op = op;
+ }
+
+ public UnaryExpr(int beginLine, int beginColumn, int endLine, int endColumn, Expression expr, Operator op) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.expr = expr;
+ this.op = op;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getExpr() {
+ return expr;
+ }
+
+ public Operator getOperator() {
+ return op;
+ }
+
+ public void setExpr(Expression expr) {
+ this.expr = expr;
+ }
+
+ public void setOperator(Operator op) {
+ this.op = op;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/VariableDeclarationExpr.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/VariableDeclarationExpr.java
new file mode 100644
index 000000000..d76a78d0e
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/expr/VariableDeclarationExpr.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 03/11/2006
+ */
+package japa.parser.ast.expr;
+
+import japa.parser.ast.body.ModifierSet;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class VariableDeclarationExpr extends Expression {
+
+ private int modifiers;
+
+ private List<AnnotationExpr> annotations;
+
+ private Type type;
+
+ private List<VariableDeclarator> vars;
+
+ public VariableDeclarationExpr() {
+ }
+
+ public VariableDeclarationExpr(Type type, List<VariableDeclarator> vars) {
+ this.type = type;
+ this.vars = vars;
+ }
+
+ public VariableDeclarationExpr(int modifiers, Type type, List<VariableDeclarator> vars) {
+ this.modifiers = modifiers;
+ this.type = type;
+ this.vars = vars;
+ }
+
+ public VariableDeclarationExpr(int beginLine, int beginColumn, int endLine, int endColumn, int modifiers, List<AnnotationExpr> annotations, Type type, List<VariableDeclarator> vars) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.modifiers = modifiers;
+ this.annotations = annotations;
+ this.type = type;
+ this.vars = vars;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<AnnotationExpr> getAnnotations() {
+ return annotations;
+ }
+
+ /**
+ * Return the modifiers of this variable declaration.
+ *
+ * @see ModifierSet
+ * @return modifiers
+ */
+ public int getModifiers() {
+ return modifiers;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public List<VariableDeclarator> getVars() {
+ return vars;
+ }
+
+ public void setAnnotations(List<AnnotationExpr> annotations) {
+ this.annotations = annotations;
+ }
+
+ public void setModifiers(int modifiers) {
+ this.modifiers = modifiers;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ public void setVars(List<VariableDeclarator> vars) {
+ this.vars = vars;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/AssertStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/AssertStmt.java
new file mode 100644
index 000000000..a483d3bb0
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/AssertStmt.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 04/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class AssertStmt extends Statement {
+
+ private Expression check;
+
+ private Expression msg;
+
+ public AssertStmt() {
+ }
+
+ public AssertStmt(Expression check) {
+ this.check = check;
+ }
+
+ public AssertStmt(Expression check, Expression msg) {
+ this.check = check;
+ this.msg = msg;
+ }
+
+ public AssertStmt(int beginLine, int beginColumn, int endLine, int endColumn, Expression check, Expression msg) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.check = check;
+ this.msg = msg;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getCheck() {
+ return check;
+ }
+
+ public Expression getMessage() {
+ return msg;
+ }
+
+ public void setCheck(Expression check) {
+ this.check = check;
+ }
+
+ public void setMessage(Expression msg) {
+ this.msg = msg;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/BlockStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/BlockStmt.java
new file mode 100644
index 000000000..eba5b5f15
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/BlockStmt.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 04/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class BlockStmt extends Statement {
+
+ private List<Statement> stmts;
+
+ public BlockStmt() {
+ }
+
+ public BlockStmt(List<Statement> stmts) {
+ this.stmts = stmts;
+ }
+
+ public BlockStmt(int beginLine, int beginColumn, int endLine, int endColumn, List<Statement> stmts) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.stmts = stmts;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<Statement> getStmts() {
+ return stmts;
+ }
+
+ public void setStmts(List<Statement> stmts) {
+ this.stmts = stmts;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/BreakStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/BreakStmt.java
new file mode 100644
index 000000000..10ac739f1
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/BreakStmt.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 04/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class BreakStmt extends Statement {
+
+ private String id;
+
+ public BreakStmt() {
+ }
+
+ public BreakStmt(String id) {
+ this.id = id;
+ }
+
+ public BreakStmt(int beginLine, int beginColumn, int endLine, int endColumn, String id) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.id = id;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/CatchClause.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/CatchClause.java
new file mode 100644
index 000000000..2f5065c20
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/CatchClause.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 18/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.Node;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class CatchClause extends Node {
+
+ private Parameter except;
+
+ private BlockStmt catchBlock;
+
+ public CatchClause() {
+ }
+
+ public CatchClause(Parameter except, BlockStmt catchBlock) {
+ this.except = except;
+ this.catchBlock = catchBlock;
+ }
+
+ public CatchClause(int beginLine, int beginColumn, int endLine, int endColumn, Parameter except, BlockStmt catchBlock) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.except = except;
+ this.catchBlock = catchBlock;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public BlockStmt getCatchBlock() {
+ return catchBlock;
+ }
+
+ public Parameter getExcept() {
+ return except;
+ }
+
+ public void setCatchBlock(BlockStmt catchBlock) {
+ this.catchBlock = catchBlock;
+ }
+
+ public void setExcept(Parameter except) {
+ this.except = except;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ContinueStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ContinueStmt.java
new file mode 100644
index 000000000..73c704cf6
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ContinueStmt.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 07/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ContinueStmt extends Statement {
+
+ private String id;
+
+ public ContinueStmt() {
+ }
+
+ public ContinueStmt(String id) {
+ this.id = id;
+ }
+
+ public ContinueStmt(int beginLine, int beginColumn, int endLine, int endColumn, String id) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.id = id;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/DoStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/DoStmt.java
new file mode 100644
index 000000000..cb1241b15
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/DoStmt.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 07/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class DoStmt extends Statement {
+
+ private Statement body;
+
+ private Expression condition;
+
+ public DoStmt() {
+ }
+
+ public DoStmt(Statement body, Expression condition) {
+ this.body = body;
+ this.condition = condition;
+ }
+
+ public DoStmt(int beginLine, int beginColumn, int endLine, int endColumn, Statement body, Expression condition) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.body = body;
+ this.condition = condition;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Statement getBody() {
+ return body;
+ }
+
+ public Expression getCondition() {
+ return condition;
+ }
+
+ public void setBody(Statement body) {
+ this.body = body;
+ }
+
+ public void setCondition(Expression condition) {
+ this.condition = condition;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/EmptyStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/EmptyStmt.java
new file mode 100644
index 000000000..5d625bd3f
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/EmptyStmt.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 04/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class EmptyStmt extends Statement {
+
+ public EmptyStmt() {
+ }
+
+ public EmptyStmt(int beginLine, int beginColumn, int endLine, int endColumn) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ExplicitConstructorInvocationStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ExplicitConstructorInvocationStmt.java
new file mode 100644
index 000000000..ce9b8b2e8
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ExplicitConstructorInvocationStmt.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 03/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ExplicitConstructorInvocationStmt extends Statement {
+
+ private List<Type> typeArgs;
+
+ private boolean isThis;
+
+ private Expression expr;
+
+ private List<Expression> args;
+
+ public ExplicitConstructorInvocationStmt() {
+ }
+
+ public ExplicitConstructorInvocationStmt(boolean isThis, Expression expr, List<Expression> args) {
+ this.isThis = isThis;
+ this.expr = expr;
+ this.args = args;
+ }
+
+ public ExplicitConstructorInvocationStmt(int beginLine, int beginColumn, int endLine, int endColumn, List<Type> typeArgs, boolean isThis, Expression expr, List<Expression> args) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.typeArgs = typeArgs;
+ this.isThis = isThis;
+ this.expr = expr;
+ this.args = args;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<Expression> getArgs() {
+ return args;
+ }
+
+ public Expression getExpr() {
+ return expr;
+ }
+
+ public List<Type> getTypeArgs() {
+ return typeArgs;
+ }
+
+ public boolean isThis() {
+ return isThis;
+ }
+
+ public void setArgs(List<Expression> args) {
+ this.args = args;
+ }
+
+ public void setExpr(Expression expr) {
+ this.expr = expr;
+ }
+
+ public void setThis(boolean isThis) {
+ this.isThis = isThis;
+ }
+
+ public void setTypeArgs(List<Type> typeArgs) {
+ this.typeArgs = typeArgs;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ExpressionStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ExpressionStmt.java
new file mode 100644
index 000000000..3331f0143
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ExpressionStmt.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 04/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ExpressionStmt extends Statement {
+
+ private Expression expr;
+
+ public ExpressionStmt() {
+ }
+
+ public ExpressionStmt(Expression expr) {
+ this.expr = expr;
+ }
+
+ public ExpressionStmt(int beginLine, int beginColumn, int endLine, int endColumn, Expression expr) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.expr = expr;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getExpression() {
+ return expr;
+ }
+
+ public void setExpression(Expression expr) {
+ this.expr = expr;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ForStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ForStmt.java
new file mode 100644
index 000000000..78230291a
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ForStmt.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 07/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ForStmt extends Statement {
+
+ private List<Expression> init;
+
+ private Expression compare;
+
+ private List<Expression> update;
+
+ private Statement body;
+
+ public ForStmt() {
+ }
+
+ public ForStmt(List<Expression> init, Expression compare, List<Expression> update, Statement body) {
+ this.compare = compare;
+ this.init = init;
+ this.update = update;
+ this.body = body;
+ }
+
+ public ForStmt(int beginLine, int beginColumn, int endLine, int endColumn, List<Expression> init, Expression compare, List<Expression> update, Statement body) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.compare = compare;
+ this.init = init;
+ this.update = update;
+ this.body = body;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Statement getBody() {
+ return body;
+ }
+
+ public Expression getCompare() {
+ return compare;
+ }
+
+ public List<Expression> getInit() {
+ return init;
+ }
+
+ public List<Expression> getUpdate() {
+ return update;
+ }
+
+ public void setBody(Statement body) {
+ this.body = body;
+ }
+
+ public void setCompare(Expression compare) {
+ this.compare = compare;
+ }
+
+ public void setInit(List<Expression> init) {
+ this.init = init;
+ }
+
+ public void setUpdate(List<Expression> update) {
+ this.update = update;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ForeachStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ForeachStmt.java
new file mode 100644
index 000000000..3a9d94b70
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ForeachStmt.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 07/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ForeachStmt extends Statement {
+
+ private VariableDeclarationExpr var;
+
+ private Expression iterable;
+
+ private Statement body;
+
+ public ForeachStmt() {
+ }
+
+ public ForeachStmt(VariableDeclarationExpr var, Expression iterable, Statement body) {
+ this.var = var;
+ this.iterable = iterable;
+ this.body = body;
+ }
+
+ public ForeachStmt(int beginLine, int beginColumn, int endLine, int endColumn, VariableDeclarationExpr var, Expression iterable, Statement body) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.var = var;
+ this.iterable = iterable;
+ this.body = body;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Statement getBody() {
+ return body;
+ }
+
+ public Expression getIterable() {
+ return iterable;
+ }
+
+ public VariableDeclarationExpr getVariable() {
+ return var;
+ }
+
+ public void setBody(Statement body) {
+ this.body = body;
+ }
+
+ public void setIterable(Expression iterable) {
+ this.iterable = iterable;
+ }
+
+ public void setVariable(VariableDeclarationExpr var) {
+ this.var = var;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/IfStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/IfStmt.java
new file mode 100644
index 000000000..7ce3adb98
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/IfStmt.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 07/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class IfStmt extends Statement {
+
+ private Expression condition;
+
+ private Statement thenStmt;
+
+ private Statement elseStmt;
+
+ public IfStmt() {
+ }
+
+ public IfStmt(Expression condition, Statement thenStmt, Statement elseStmt) {
+ this.condition = condition;
+ this.thenStmt = thenStmt;
+ this.elseStmt = elseStmt;
+ }
+
+ public IfStmt(int beginLine, int beginColumn, int endLine, int endColumn, Expression condition, Statement thenStmt, Statement elseStmt) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.condition = condition;
+ this.thenStmt = thenStmt;
+ this.elseStmt = elseStmt;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getCondition() {
+ return condition;
+ }
+
+ public Statement getElseStmt() {
+ return elseStmt;
+ }
+
+ public Statement getThenStmt() {
+ return thenStmt;
+ }
+
+ public void setCondition(Expression condition) {
+ this.condition = condition;
+ }
+
+ public void setElseStmt(Statement elseStmt) {
+ this.elseStmt = elseStmt;
+ }
+
+ public void setThenStmt(Statement thenStmt) {
+ this.thenStmt = thenStmt;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/LabeledStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/LabeledStmt.java
new file mode 100644
index 000000000..178d9d0e6
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/LabeledStmt.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 04/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class LabeledStmt extends Statement {
+
+ private String label;
+
+ private Statement stmt;
+
+ public LabeledStmt() {
+ }
+
+ public LabeledStmt(String label, Statement stmt) {
+ this.label = label;
+ this.stmt = stmt;
+ }
+
+ public LabeledStmt(int beginLine, int beginColumn, int endLine, int endColumn, String label, Statement stmt) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.label = label;
+ this.stmt = stmt;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public Statement getStmt() {
+ return stmt;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public void setStmt(Statement stmt) {
+ this.stmt = stmt;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ReturnStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ReturnStmt.java
new file mode 100644
index 000000000..ccbf459f2
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ReturnStmt.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 04/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ReturnStmt extends Statement {
+
+ private Expression expr;
+
+ public ReturnStmt() {
+ }
+
+ public ReturnStmt(Expression expr) {
+ this.expr = expr;
+ }
+
+ public ReturnStmt(int beginLine, int beginColumn, int endLine, int endColumn, Expression expr) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.expr = expr;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getExpr() {
+ return expr;
+ }
+
+ public void setExpr(Expression expr) {
+ this.expr = expr;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/Statement.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/Statement.java
new file mode 100644
index 000000000..b22feff31
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/Statement.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 03/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.Node;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public abstract class Statement extends Node {
+
+ public Statement() {
+ }
+
+ public Statement(int beginLine, int beginColumn, int endLine, int endColumn) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SwitchEntryStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SwitchEntryStmt.java
new file mode 100644
index 000000000..702770c99
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SwitchEntryStmt.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 04/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class SwitchEntryStmt extends Statement {
+
+ private Expression label;
+
+ private List<Statement> stmts;
+
+ public SwitchEntryStmt() {
+ }
+
+ public SwitchEntryStmt(Expression label, List<Statement> stmts) {
+ this.label = label;
+ this.stmts = stmts;
+ }
+
+ public SwitchEntryStmt(int beginLine, int beginColumn, int endLine, int endColumn, Expression label, List<Statement> stmts) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.label = label;
+ this.stmts = stmts;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getLabel() {
+ return label;
+ }
+
+ public List<Statement> getStmts() {
+ return stmts;
+ }
+
+ public void setLabel(Expression label) {
+ this.label = label;
+ }
+
+ public void setStmts(List<Statement> stmts) {
+ this.stmts = stmts;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SwitchStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SwitchStmt.java
new file mode 100644
index 000000000..33ad7bc72
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SwitchStmt.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 04/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class SwitchStmt extends Statement {
+
+ private Expression selector;
+
+ private List<SwitchEntryStmt> entries;
+
+ public SwitchStmt() {
+ }
+
+ public SwitchStmt(Expression selector, List<SwitchEntryStmt> entries) {
+ this.selector = selector;
+ this.entries = entries;
+ }
+
+ public SwitchStmt(int beginLine, int beginColumn, int endLine, int endColumn, Expression selector, List<SwitchEntryStmt> entries) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.selector = selector;
+ this.entries = entries;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<SwitchEntryStmt> getEntries() {
+ return entries;
+ }
+
+ public Expression getSelector() {
+ return selector;
+ }
+
+ public void setEntries(List<SwitchEntryStmt> entries) {
+ this.entries = entries;
+ }
+
+ public void setSelector(Expression selector) {
+ this.selector = selector;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SynchronizedStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SynchronizedStmt.java
new file mode 100644
index 000000000..dd6332ba6
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/SynchronizedStmt.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 18/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class SynchronizedStmt extends Statement {
+
+ private Expression expr;
+
+ private BlockStmt block;
+
+ public SynchronizedStmt() {
+ }
+
+ public SynchronizedStmt(Expression expr, BlockStmt block) {
+ this.expr = expr;
+ this.block = block;
+ }
+
+ public SynchronizedStmt(int beginLine, int beginColumn, int endLine, int endColumn, Expression expr, BlockStmt block) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.expr = expr;
+ this.block = block;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public BlockStmt getBlock() {
+ return block;
+ }
+
+ public Expression getExpr() {
+ return expr;
+ }
+
+ public void setBlock(BlockStmt block) {
+ this.block = block;
+ }
+
+ public void setExpr(Expression expr) {
+ this.expr = expr;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ThrowStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ThrowStmt.java
new file mode 100644
index 000000000..dc37360ca
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/ThrowStmt.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 18/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ThrowStmt extends Statement {
+
+ private Expression expr;
+
+ public ThrowStmt() {
+ }
+
+ public ThrowStmt(Expression expr) {
+ this.expr = expr;
+ }
+
+ public ThrowStmt(int beginLine, int beginColumn, int endLine, int endColumn, Expression expr) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.expr = expr;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Expression getExpr() {
+ return expr;
+ }
+
+ public void setExpr(Expression expr) {
+ this.expr = expr;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/TryStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/TryStmt.java
new file mode 100644
index 000000000..eee78b4e5
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/TryStmt.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 18/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class TryStmt extends Statement {
+
+ private BlockStmt tryBlock;
+
+ private List<CatchClause> catchs;
+
+ private BlockStmt finallyBlock;
+
+ public TryStmt() {
+ }
+
+ public TryStmt(BlockStmt tryBlock, List<CatchClause> catchs, BlockStmt finallyBlock) {
+ this.tryBlock = tryBlock;
+ this.catchs = catchs;
+ this.finallyBlock = finallyBlock;
+ }
+
+ public TryStmt(int beginLine, int beginColumn, int endLine, int endColumn, BlockStmt tryBlock, List<CatchClause> catchs, BlockStmt finallyBlock) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.tryBlock = tryBlock;
+ this.catchs = catchs;
+ this.finallyBlock = finallyBlock;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public List<CatchClause> getCatchs() {
+ return catchs;
+ }
+
+ public BlockStmt getFinallyBlock() {
+ return finallyBlock;
+ }
+
+ public BlockStmt getTryBlock() {
+ return tryBlock;
+ }
+
+ public void setCatchs(List<CatchClause> catchs) {
+ this.catchs = catchs;
+ }
+
+ public void setFinallyBlock(BlockStmt finallyBlock) {
+ this.finallyBlock = finallyBlock;
+ }
+
+ public void setTryBlock(BlockStmt tryBlock) {
+ this.tryBlock = tryBlock;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/TypeDeclarationStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/TypeDeclarationStmt.java
new file mode 100644
index 000000000..fe95876b4
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/TypeDeclarationStmt.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 04/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.body.TypeDeclaration;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class TypeDeclarationStmt extends Statement {
+
+ private TypeDeclaration typeDecl;
+
+ public TypeDeclarationStmt() {
+ }
+
+ public TypeDeclarationStmt(TypeDeclaration typeDecl) {
+ this.typeDecl = typeDecl;
+ }
+
+ public TypeDeclarationStmt(int beginLine, int beginColumn, int endLine, int endColumn, TypeDeclaration typeDecl) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.typeDecl = typeDecl;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public TypeDeclaration getTypeDeclaration() {
+ return typeDecl;
+ }
+
+ public void setTypeDeclaration(TypeDeclaration typeDecl) {
+ this.typeDecl = typeDecl;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/WhileStmt.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/WhileStmt.java
new file mode 100644
index 000000000..ab6301d18
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/stmt/WhileStmt.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 07/11/2006
+ */
+package japa.parser.ast.stmt;
+
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class WhileStmt extends Statement {
+
+ private Expression condition;
+
+ private Statement body;
+
+ public WhileStmt() {
+ }
+
+ public WhileStmt(Expression condition, Statement body) {
+ this.condition = condition;
+ this.body = body;
+ }
+
+ public WhileStmt(int beginLine, int beginColumn, int endLine, int endColumn, Expression condition, Statement body) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.condition = condition;
+ this.body = body;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Statement getBody() {
+ return body;
+ }
+
+ public Expression getCondition() {
+ return condition;
+ }
+
+ public void setBody(Statement body) {
+ this.body = body;
+ }
+
+ public void setCondition(Expression condition) {
+ this.condition = condition;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/type/ClassOrInterfaceType.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/ClassOrInterfaceType.java
new file mode 100644
index 000000000..84e05a3df
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/ClassOrInterfaceType.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.type;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ClassOrInterfaceType extends Type {
+
+ private ClassOrInterfaceType scope;
+
+ private String name;
+
+ private List<Type> typeArgs;
+
+ public ClassOrInterfaceType() {
+ }
+
+ public ClassOrInterfaceType(String name) {
+ this.name = name;
+ }
+
+ public ClassOrInterfaceType(ClassOrInterfaceType scope, String name) {
+ this.scope = scope;
+ this.name = name;
+ }
+
+ public ClassOrInterfaceType(int beginLine, int beginColumn, int endLine, int endColumn, ClassOrInterfaceType scope, String name, List<Type> typeArgs) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.scope = scope;
+ this.name = name;
+ this.typeArgs = typeArgs;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ClassOrInterfaceType getScope() {
+ return scope;
+ }
+
+ public List<Type> getTypeArgs() {
+ return typeArgs;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setScope(ClassOrInterfaceType scope) {
+ this.scope = scope;
+ }
+
+ public void setTypeArgs(List<Type> typeArgs) {
+ this.typeArgs = typeArgs;
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/type/PrimitiveType.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/PrimitiveType.java
new file mode 100644
index 000000000..9b4eccf4e
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/PrimitiveType.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.type;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class PrimitiveType extends Type {
+
+ public enum Primitive {
+ Boolean, Char, Byte, Short, Int, Long, Float, Double
+ }
+
+ private Primitive type;
+
+ public PrimitiveType() {
+ }
+
+ public PrimitiveType(Primitive type) {
+ this.type = type;
+ }
+
+ public PrimitiveType(int beginLine, int beginColumn, int endLine, int endColumn, Primitive type) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.type = type;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public Primitive getType() {
+ return type;
+ }
+
+ public void setType(Primitive type) {
+ this.type = type;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/type/ReferenceType.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/ReferenceType.java
new file mode 100644
index 000000000..a8e8aea8d
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/ReferenceType.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.type;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class ReferenceType extends Type {
+
+ private Type type;
+
+ private int arrayCount;
+
+ public ReferenceType() {
+ }
+
+ public ReferenceType(Type type) {
+ this.type = type;
+ }
+
+ public ReferenceType(Type type, int arrayCount) {
+ this.type = type;
+ this.arrayCount = arrayCount;
+ }
+
+ public ReferenceType(int beginLine, int beginColumn, int endLine, int endColumn, Type type, int arrayCount) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.type = type;
+ this.arrayCount = arrayCount;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public int getArrayCount() {
+ return arrayCount;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setArrayCount(int arrayCount) {
+ this.arrayCount = arrayCount;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/type/Type.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/Type.java
new file mode 100644
index 000000000..642f5d302
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/Type.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.type;
+
+import japa.parser.ast.Node;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public abstract class Type extends Node {
+
+ public Type() {
+ }
+
+ public Type(int beginLine, int beginColumn, int endLine, int endColumn) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/type/VoidType.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/VoidType.java
new file mode 100644
index 000000000..d527a19e0
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/VoidType.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.type;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class VoidType extends Type {
+
+ public VoidType() {
+ }
+
+ public VoidType(int beginLine, int beginColumn, int endLine, int endColumn) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/type/WildcardType.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/WildcardType.java
new file mode 100644
index 000000000..ce7dbf473
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/type/WildcardType.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.type;
+
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.VoidVisitor;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public final class WildcardType extends Type {
+
+ private ReferenceType ext;
+
+ private ReferenceType sup;
+
+ public WildcardType() {
+ }
+
+ public WildcardType(ReferenceType ext) {
+ this.ext = ext;
+ }
+
+ public WildcardType(ReferenceType ext, ReferenceType sup) {
+ this.ext = ext;
+ this.sup = sup;
+ }
+
+ public WildcardType(int beginLine, int beginColumn, int endLine, int endColumn, ReferenceType ext, ReferenceType sup) {
+ super(beginLine, beginColumn, endLine, endColumn);
+ this.ext = ext;
+ this.sup = sup;
+ }
+
+ @Override
+ public <R, A> R accept(GenericVisitor<R, A> v, A arg) {
+ return v.visit(this, arg);
+ }
+
+ @Override
+ public <A> void accept(VoidVisitor<A> v, A arg) {
+ v.visit(this, arg);
+ }
+
+ public ReferenceType getExtends() {
+ return ext;
+ }
+
+ public ReferenceType getSuper() {
+ return sup;
+ }
+
+ public void setExtends(ReferenceType ext) {
+ this.ext = ext;
+ }
+
+ public void setSuper(ReferenceType sup) {
+ this.sup = sup;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/DumpVisitor.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/DumpVisitor.java
new file mode 100644
index 000000000..d8d1fc130
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/DumpVisitor.java
@@ -0,0 +1,1302 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.visitor;
+
+import japa.parser.ast.BlockComment;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.ImportDeclaration;
+import japa.parser.ast.LineComment;
+import japa.parser.ast.PackageDeclaration;
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.body.AnnotationDeclaration;
+import japa.parser.ast.body.AnnotationMemberDeclaration;
+import japa.parser.ast.body.BodyDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.ConstructorDeclaration;
+import japa.parser.ast.body.EmptyMemberDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumConstantDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.InitializerDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.ModifierSet;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.TypeDeclaration;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.ArrayAccessExpr;
+import japa.parser.ast.expr.ArrayCreationExpr;
+import japa.parser.ast.expr.ArrayInitializerExpr;
+import japa.parser.ast.expr.AssignExpr;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BooleanLiteralExpr;
+import japa.parser.ast.expr.CastExpr;
+import japa.parser.ast.expr.CharLiteralExpr;
+import japa.parser.ast.expr.ClassExpr;
+import japa.parser.ast.expr.ConditionalExpr;
+import japa.parser.ast.expr.DoubleLiteralExpr;
+import japa.parser.ast.expr.EnclosedExpr;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.expr.FieldAccessExpr;
+import japa.parser.ast.expr.InstanceOfExpr;
+import japa.parser.ast.expr.IntegerLiteralExpr;
+import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
+import japa.parser.ast.expr.LongLiteralExpr;
+import japa.parser.ast.expr.LongLiteralMinValueExpr;
+import japa.parser.ast.expr.MarkerAnnotationExpr;
+import japa.parser.ast.expr.MemberValuePair;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NormalAnnotationExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.expr.ObjectCreationExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.SingleMemberAnnotationExpr;
+import japa.parser.ast.expr.StringLiteralExpr;
+import japa.parser.ast.expr.SuperExpr;
+import japa.parser.ast.expr.ThisExpr;
+import japa.parser.ast.expr.UnaryExpr;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.WildcardType;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+
+public final class DumpVisitor implements VoidVisitor<Object> {
+
+ private class SourcePrinter {
+
+ private int level = 0;
+
+ private boolean indented = false;
+
+ private final StringBuilder buf = new StringBuilder();
+
+ public void indent() {
+ level++;
+ }
+
+ public void unindent() {
+ level--;
+ }
+
+ private void makeIndent() {
+ for (int i = 0; i < level; i++) {
+ buf.append(" ");
+ }
+ }
+
+ public void print(String arg) {
+ if (!indented) {
+ makeIndent();
+ indented = true;
+ }
+ buf.append(arg);
+ }
+
+ public void printLn(String arg) {
+ print(arg);
+ printLn();
+ }
+
+ public void printLn() {
+ buf.append("\n");
+ indented = false;
+ }
+
+ public String getSource() {
+ return buf.toString();
+ }
+
+ @Override
+ public String toString() {
+ return getSource();
+ }
+ }
+
+ private final SourcePrinter printer = new SourcePrinter();
+
+ public String getSource() {
+ return printer.getSource();
+ }
+
+ private void printModifiers(int modifiers) {
+ if (ModifierSet.isPrivate(modifiers)) {
+ printer.print("private ");
+ }
+ if (ModifierSet.isProtected(modifiers)) {
+ printer.print("protected ");
+ }
+ if (ModifierSet.isPublic(modifiers)) {
+ printer.print("public ");
+ }
+ if (ModifierSet.isAbstract(modifiers)) {
+ printer.print("abstract ");
+ }
+ if (ModifierSet.isStatic(modifiers)) {
+ printer.print("static ");
+ }
+ if (ModifierSet.isFinal(modifiers)) {
+ printer.print("final ");
+ }
+ if (ModifierSet.isNative(modifiers)) {
+ printer.print("native ");
+ }
+ if (ModifierSet.isStrictfp(modifiers)) {
+ printer.print("strictfp ");
+ }
+ if (ModifierSet.isSynchronized(modifiers)) {
+ printer.print("synchronized ");
+ }
+ if (ModifierSet.isTransient(modifiers)) {
+ printer.print("transient ");
+ }
+ if (ModifierSet.isVolatile(modifiers)) {
+ printer.print("volatile ");
+ }
+ }
+
+ private void printMembers(List<BodyDeclaration> members, Object arg) {
+ for (BodyDeclaration member : members) {
+ printer.printLn();
+ member.accept(this, arg);
+ printer.printLn();
+ }
+ }
+
+ private void printMemberAnnotations(List<AnnotationExpr> annotations, Object arg) {
+ if (annotations != null) {
+ for (AnnotationExpr a : annotations) {
+ a.accept(this, arg);
+ printer.printLn();
+ }
+ }
+ }
+
+ private void printAnnotations(List<AnnotationExpr> annotations, Object arg) {
+ if (annotations != null) {
+ for (AnnotationExpr a : annotations) {
+ a.accept(this, arg);
+ printer.print(" ");
+ }
+ }
+ }
+
+ private void printTypeArgs(List<Type> args, Object arg) {
+ if (args != null) {
+ printer.print("<");
+ for (Iterator<Type> i = args.iterator(); i.hasNext();) {
+ Type t = i.next();
+ t.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ printer.print(">");
+ }
+ }
+
+ private void printTypeParameters(List<TypeParameter> args, Object arg) {
+ if (args != null) {
+ printer.print("<");
+ for (Iterator<TypeParameter> i = args.iterator(); i.hasNext();) {
+ TypeParameter t = i.next();
+ t.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ printer.print(">");
+ }
+ }
+
+ private void printArguments(List<Expression> args, Object arg) {
+ printer.print("(");
+ if (args != null) {
+ for (Iterator<Expression> i = args.iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+ }
+
+ private void printJavadoc(JavadocComment javadoc, Object arg) {
+ if (javadoc != null) {
+ javadoc.accept(this, arg);
+ }
+ }
+
+ public void visit(CompilationUnit n, Object arg) {
+ if (n.getPackage() != null) {
+ n.getPackage().accept(this, arg);
+ }
+ if (n.getImports() != null) {
+ for (ImportDeclaration i : n.getImports()) {
+ i.accept(this, arg);
+ }
+ printer.printLn();
+ }
+ if (n.getTypes() != null) {
+ for (Iterator<TypeDeclaration> i = n.getTypes().iterator(); i.hasNext();) {
+ i.next().accept(this, arg);
+ printer.printLn();
+ if (i.hasNext()) {
+ printer.printLn();
+ }
+ }
+ }
+ }
+
+ public void visit(PackageDeclaration n, Object arg) {
+ printAnnotations(n.getAnnotations(), arg);
+ printer.print("package ");
+ n.getName().accept(this, arg);
+ printer.printLn(";");
+ printer.printLn();
+ }
+
+ public void visit(NameExpr n, Object arg) {
+ printer.print(n.getName());
+ }
+
+ public void visit(QualifiedNameExpr n, Object arg) {
+ n.getQualifier().accept(this, arg);
+ printer.print(".");
+ printer.print(n.getName());
+ }
+
+ public void visit(ImportDeclaration n, Object arg) {
+ printer.print("import ");
+ if (n.isStatic()) {
+ printer.print("static ");
+ }
+ n.getName().accept(this, arg);
+ if (n.isAsterisk()) {
+ printer.print(".*");
+ }
+ printer.printLn(";");
+ }
+
+ public void visit(ClassOrInterfaceDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ if (n.isInterface()) {
+ printer.print("interface ");
+ } else {
+ printer.print("class ");
+ }
+
+ printer.print(n.getName());
+
+ printTypeParameters(n.getTypeParameters(), arg);
+
+ if (n.getExtends() != null) {
+ printer.print(" extends ");
+ for (Iterator<ClassOrInterfaceType> i = n.getExtends().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+
+ if (n.getImplements() != null) {
+ printer.print(" implements ");
+ for (Iterator<ClassOrInterfaceType> i = n.getImplements().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getMembers() != null) {
+ printMembers(n.getMembers(), arg);
+ }
+ printer.unindent();
+ printer.print("}");
+ }
+
+ public void visit(EmptyTypeDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printer.print(";");
+ }
+
+ public void visit(JavadocComment n, Object arg) {
+ printer.print("/**");
+ printer.print(n.getContent());
+ printer.printLn("*/");
+ }
+
+ public void visit(ClassOrInterfaceType n, Object arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print(n.getName());
+ printTypeArgs(n.getTypeArgs(), arg);
+ }
+
+ public void visit(TypeParameter n, Object arg) {
+ printer.print(n.getName());
+ if (n.getTypeBound() != null) {
+ printer.print(" extends ");
+ for (Iterator<ClassOrInterfaceType> i = n.getTypeBound().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(" & ");
+ }
+ }
+ }
+ }
+
+ public void visit(PrimitiveType n, Object arg) {
+ switch (n.getType()) {
+ case Boolean:
+ printer.print("boolean");
+ break;
+ case Byte:
+ printer.print("byte");
+ break;
+ case Char:
+ printer.print("char");
+ break;
+ case Double:
+ printer.print("double");
+ break;
+ case Float:
+ printer.print("float");
+ break;
+ case Int:
+ printer.print("int");
+ break;
+ case Long:
+ printer.print("long");
+ break;
+ case Short:
+ printer.print("short");
+ break;
+ }
+ }
+
+ public void visit(ReferenceType n, Object arg) {
+ n.getType().accept(this, arg);
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ }
+
+ public void visit(WildcardType n, Object arg) {
+ printer.print("?");
+ if (n.getExtends() != null) {
+ printer.print(" extends ");
+ n.getExtends().accept(this, arg);
+ }
+ if (n.getSuper() != null) {
+ printer.print(" super ");
+ n.getSuper().accept(this, arg);
+ }
+ }
+
+ public void visit(FieldDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+ n.getType().accept(this, arg);
+
+ printer.print(" ");
+ for (Iterator<VariableDeclarator> i = n.getVariables().iterator(); i.hasNext();) {
+ VariableDeclarator var = i.next();
+ var.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+
+ printer.print(";");
+ }
+
+ public void visit(VariableDeclarator n, Object arg) {
+ n.getId().accept(this, arg);
+ if (n.getInit() != null) {
+ printer.print(" = ");
+ n.getInit().accept(this, arg);
+ }
+ }
+
+ public void visit(VariableDeclaratorId n, Object arg) {
+ printer.print(n.getName());
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ }
+
+ public void visit(ArrayInitializerExpr n, Object arg) {
+ printer.print("{");
+ if (n.getValues() != null) {
+ printer.print(" ");
+ for (Iterator<Expression> i = n.getValues().iterator(); i.hasNext();) {
+ Expression expr = i.next();
+ expr.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ printer.print(" ");
+ }
+ printer.print("}");
+ }
+
+ public void visit(VoidType n, Object arg) {
+ printer.print("void");
+ }
+
+ public void visit(ArrayAccessExpr n, Object arg) {
+ n.getName().accept(this, arg);
+ printer.print("[");
+ n.getIndex().accept(this, arg);
+ printer.print("]");
+ }
+
+ public void visit(ArrayCreationExpr n, Object arg) {
+ printer.print("new ");
+ n.getType().accept(this, arg);
+
+ if (n.getDimensions() != null) {
+ for (Expression dim : n.getDimensions()) {
+ printer.print("[");
+ dim.accept(this, arg);
+ printer.print("]");
+ }
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ } else {
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+ printer.print(" ");
+ n.getInitializer().accept(this, arg);
+ }
+ }
+
+ public void visit(AssignExpr n, Object arg) {
+ n.getTarget().accept(this, arg);
+ printer.print(" ");
+ switch (n.getOperator()) {
+ case assign:
+ printer.print("=");
+ break;
+ case and:
+ printer.print("&=");
+ break;
+ case or:
+ printer.print("|=");
+ break;
+ case xor:
+ printer.print("^=");
+ break;
+ case plus:
+ printer.print("+=");
+ break;
+ case minus:
+ printer.print("-=");
+ break;
+ case rem:
+ printer.print("%=");
+ break;
+ case slash:
+ printer.print("/=");
+ break;
+ case star:
+ printer.print("*=");
+ break;
+ case lShift:
+ printer.print("<<=");
+ break;
+ case rSignedShift:
+ printer.print(">>=");
+ break;
+ case rUnsignedShift:
+ printer.print(">>>=");
+ break;
+ }
+ printer.print(" ");
+ n.getValue().accept(this, arg);
+ }
+
+ public void visit(BinaryExpr n, Object arg) {
+ n.getLeft().accept(this, arg);
+ printer.print(" ");
+ switch (n.getOperator()) {
+ case or:
+ printer.print("||");
+ break;
+ case and:
+ printer.print("&&");
+ break;
+ case binOr:
+ printer.print("|");
+ break;
+ case binAnd:
+ printer.print("&");
+ break;
+ case xor:
+ printer.print("^");
+ break;
+ case equals:
+ printer.print("==");
+ break;
+ case notEquals:
+ printer.print("!=");
+ break;
+ case less:
+ printer.print("<");
+ break;
+ case greater:
+ printer.print(">");
+ break;
+ case lessEquals:
+ printer.print("<=");
+ break;
+ case greaterEquals:
+ printer.print(">=");
+ break;
+ case lShift:
+ printer.print("<<");
+ break;
+ case rSignedShift:
+ printer.print(">>");
+ break;
+ case rUnsignedShift:
+ printer.print(">>>");
+ break;
+ case plus:
+ printer.print("+");
+ break;
+ case minus:
+ printer.print("-");
+ break;
+ case times:
+ printer.print("*");
+ break;
+ case divide:
+ printer.print("/");
+ break;
+ case remainder:
+ printer.print("%");
+ break;
+ }
+ printer.print(" ");
+ n.getRight().accept(this, arg);
+ }
+
+ public void visit(CastExpr n, Object arg) {
+ printer.print("(");
+ n.getType().accept(this, arg);
+ printer.print(") ");
+ n.getExpr().accept(this, arg);
+ }
+
+ public void visit(ClassExpr n, Object arg) {
+ n.getType().accept(this, arg);
+ printer.print(".class");
+ }
+
+ public void visit(ConditionalExpr n, Object arg) {
+ n.getCondition().accept(this, arg);
+ printer.print(" ? ");
+ n.getThenExpr().accept(this, arg);
+ printer.print(" : ");
+ n.getElseExpr().accept(this, arg);
+ }
+
+ public void visit(EnclosedExpr n, Object arg) {
+ printer.print("(");
+ n.getInner().accept(this, arg);
+ printer.print(")");
+ }
+
+ public void visit(FieldAccessExpr n, Object arg) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ printer.print(n.getField());
+ }
+
+ public void visit(InstanceOfExpr n, Object arg) {
+ n.getExpr().accept(this, arg);
+ printer.print(" instanceof ");
+ n.getType().accept(this, arg);
+ }
+
+ public void visit(CharLiteralExpr n, Object arg) {
+ printer.print("'");
+ printer.print(n.getValue());
+ printer.print("'");
+ }
+
+ public void visit(DoubleLiteralExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(IntegerLiteralExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(LongLiteralExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(IntegerLiteralMinValueExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(LongLiteralMinValueExpr n, Object arg) {
+ printer.print(n.getValue());
+ }
+
+ public void visit(StringLiteralExpr n, Object arg) {
+ printer.print("\"");
+ printer.print(n.getValue());
+ printer.print("\"");
+ }
+
+ public void visit(BooleanLiteralExpr n, Object arg) {
+ printer.print(String.valueOf(n.getValue()));
+ }
+
+ public void visit(NullLiteralExpr n, Object arg) {
+ printer.print("null");
+ }
+
+ public void visit(ThisExpr n, Object arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print("this");
+ }
+
+ public void visit(SuperExpr n, Object arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printer.print("super");
+ }
+
+ public void visit(MethodCallExpr n, Object arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ }
+ printTypeArgs(n.getTypeArgs(), arg);
+ printer.print(n.getName());
+ printArguments(n.getArgs(), arg);
+ }
+
+ public void visit(ObjectCreationExpr n, Object arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ printer.print(".");
+ }
+
+ printer.print("new ");
+
+ printTypeArgs(n.getTypeArgs(), arg);
+ n.getType().accept(this, arg);
+
+ printArguments(n.getArgs(), arg);
+
+ if (n.getAnonymousClassBody() != null) {
+ printer.printLn(" {");
+ printer.indent();
+ printMembers(n.getAnonymousClassBody(), arg);
+ printer.unindent();
+ printer.print("}");
+ }
+ }
+
+ public void visit(UnaryExpr n, Object arg) {
+ switch (n.getOperator()) {
+ case positive:
+ printer.print("+");
+ break;
+ case negative:
+ printer.print("-");
+ break;
+ case inverse:
+ printer.print("~");
+ break;
+ case not:
+ printer.print("!");
+ break;
+ case preIncrement:
+ printer.print("++");
+ break;
+ case preDecrement:
+ printer.print("--");
+ break;
+ }
+
+ n.getExpr().accept(this, arg);
+
+ switch (n.getOperator()) {
+ case posIncrement:
+ printer.print("++");
+ break;
+ case posDecrement:
+ printer.print("--");
+ break;
+ }
+ }
+
+ public void visit(ConstructorDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ printTypeParameters(n.getTypeParameters(), arg);
+ if (n.getTypeParameters() != null) {
+ printer.print(" ");
+ }
+ printer.print(n.getName());
+
+ printer.print("(");
+ if (n.getParameters() != null) {
+ for (Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext();) {
+ Parameter p = i.next();
+ p.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+
+ if (n.getThrows() != null) {
+ printer.print(" throws ");
+ for (Iterator<NameExpr> i = n.getThrows().iterator(); i.hasNext();) {
+ NameExpr name = i.next();
+ name.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(" ");
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(MethodDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ printTypeParameters(n.getTypeParameters(), arg);
+ if (n.getTypeParameters() != null) {
+ printer.print(" ");
+ }
+
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ printer.print(n.getName());
+
+ printer.print("(");
+ if (n.getParameters() != null) {
+ for (Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext();) {
+ Parameter p = i.next();
+ p.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+
+ for (int i = 0; i < n.getArrayCount(); i++) {
+ printer.print("[]");
+ }
+
+ if (n.getThrows() != null) {
+ printer.print(" throws ");
+ for (Iterator<NameExpr> i = n.getThrows().iterator(); i.hasNext();) {
+ NameExpr name = i.next();
+ name.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ if (n.getBody() == null) {
+ printer.print(";");
+ } else {
+ printer.print(" ");
+ n.getBody().accept(this, arg);
+ }
+ }
+
+ public void visit(Parameter n, Object arg) {
+ printAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ n.getType().accept(this, arg);
+ if (n.isVarArgs()) {
+ printer.print("...");
+ }
+ printer.print(" ");
+ n.getId().accept(this, arg);
+ }
+
+ public void visit(ExplicitConstructorInvocationStmt n, Object arg) {
+ if (n.isThis()) {
+ printTypeArgs(n.getTypeArgs(), arg);
+ printer.print("this");
+ } else {
+ if (n.getExpr() != null) {
+ n.getExpr().accept(this, arg);
+ printer.print(".");
+ }
+ printTypeArgs(n.getTypeArgs(), arg);
+ printer.print("super");
+ }
+ printArguments(n.getArgs(), arg);
+ printer.print(";");
+ }
+
+ public void visit(VariableDeclarationExpr n, Object arg) {
+ printAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ n.getType().accept(this, arg);
+ printer.print(" ");
+
+ for (Iterator<VariableDeclarator> i = n.getVars().iterator(); i.hasNext();) {
+ VariableDeclarator v = i.next();
+ v.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+
+ public void visit(TypeDeclarationStmt n, Object arg) {
+ n.getTypeDeclaration().accept(this, arg);
+ }
+
+ public void visit(AssertStmt n, Object arg) {
+ printer.print("assert ");
+ n.getCheck().accept(this, arg);
+ if (n.getMessage() != null) {
+ printer.print(" : ");
+ n.getMessage().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(BlockStmt n, Object arg) {
+ printer.printLn("{");
+ if (n.getStmts() != null) {
+ printer.indent();
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ printer.printLn();
+ }
+ printer.unindent();
+ }
+ printer.print("}");
+
+ }
+
+ public void visit(LabeledStmt n, Object arg) {
+ printer.print(n.getLabel());
+ printer.print(": ");
+ n.getStmt().accept(this, arg);
+ }
+
+ public void visit(EmptyStmt n, Object arg) {
+ printer.print(";");
+ }
+
+ public void visit(ExpressionStmt n, Object arg) {
+ n.getExpression().accept(this, arg);
+ printer.print(";");
+ }
+
+ public void visit(SwitchStmt n, Object arg) {
+ printer.print("switch(");
+ n.getSelector().accept(this, arg);
+ printer.printLn(") {");
+ if (n.getEntries() != null) {
+ printer.indent();
+ for (SwitchEntryStmt e : n.getEntries()) {
+ e.accept(this, arg);
+ }
+ printer.unindent();
+ }
+ printer.print("}");
+
+ }
+
+ public void visit(SwitchEntryStmt n, Object arg) {
+ if (n.getLabel() != null) {
+ printer.print("case ");
+ n.getLabel().accept(this, arg);
+ printer.print(":");
+ } else {
+ printer.print("default:");
+ }
+ printer.printLn();
+ printer.indent();
+ if (n.getStmts() != null) {
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ printer.printLn();
+ }
+ }
+ printer.unindent();
+ }
+
+ public void visit(BreakStmt n, Object arg) {
+ printer.print("break");
+ if (n.getId() != null) {
+ printer.print(" ");
+ printer.print(n.getId());
+ }
+ printer.print(";");
+ }
+
+ public void visit(ReturnStmt n, Object arg) {
+ printer.print("return");
+ if (n.getExpr() != null) {
+ printer.print(" ");
+ n.getExpr().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(EnumDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ printer.print("enum ");
+ printer.print(n.getName());
+
+ if (n.getImplements() != null) {
+ printer.print(" implements ");
+ for (Iterator<ClassOrInterfaceType> i = n.getImplements().iterator(); i.hasNext();) {
+ ClassOrInterfaceType c = i.next();
+ c.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getEntries() != null) {
+ printer.printLn();
+ for (Iterator<EnumConstantDeclaration> i = n.getEntries().iterator(); i.hasNext();) {
+ EnumConstantDeclaration e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ if (n.getMembers() != null) {
+ printer.printLn(";");
+ printMembers(n.getMembers(), arg);
+ } else {
+ if (n.getEntries() != null) {
+ printer.printLn();
+ }
+ }
+ printer.unindent();
+ printer.print("}");
+ }
+
+ public void visit(EnumConstantDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printer.print(n.getName());
+
+ if (n.getArgs() != null) {
+ printArguments(n.getArgs(), arg);
+ }
+
+ if (n.getClassBody() != null) {
+ printer.printLn(" {");
+ printer.indent();
+ printMembers(n.getClassBody(), arg);
+ printer.unindent();
+ printer.printLn("}");
+ }
+ }
+
+ public void visit(EmptyMemberDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printer.print(";");
+ }
+
+ public void visit(InitializerDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ if (n.isStatic()) {
+ printer.print("static ");
+ }
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(IfStmt n, Object arg) {
+ printer.print("if (");
+ n.getCondition().accept(this, arg);
+ printer.print(") ");
+ n.getThenStmt().accept(this, arg);
+ if (n.getElseStmt() != null) {
+ printer.print(" else ");
+ n.getElseStmt().accept(this, arg);
+ }
+ }
+
+ public void visit(WhileStmt n, Object arg) {
+ printer.print("while (");
+ n.getCondition().accept(this, arg);
+ printer.print(") ");
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ContinueStmt n, Object arg) {
+ printer.print("continue");
+ if (n.getId() != null) {
+ printer.print(" ");
+ printer.print(n.getId());
+ }
+ printer.print(";");
+ }
+
+ public void visit(DoStmt n, Object arg) {
+ printer.print("do ");
+ n.getBody().accept(this, arg);
+ printer.print(" while (");
+ n.getCondition().accept(this, arg);
+ printer.print(");");
+ }
+
+ public void visit(ForeachStmt n, Object arg) {
+ printer.print("for (");
+ n.getVariable().accept(this, arg);
+ printer.print(" : ");
+ n.getIterable().accept(this, arg);
+ printer.print(") ");
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ForStmt n, Object arg) {
+ printer.print("for (");
+ if (n.getInit() != null) {
+ for (Iterator<Expression> i = n.getInit().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print("; ");
+ if (n.getCompare() != null) {
+ n.getCompare().accept(this, arg);
+ }
+ printer.print("; ");
+ if (n.getUpdate() != null) {
+ for (Iterator<Expression> i = n.getUpdate().iterator(); i.hasNext();) {
+ Expression e = i.next();
+ e.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(") ");
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ThrowStmt n, Object arg) {
+ printer.print("throw ");
+ n.getExpr().accept(this, arg);
+ printer.print(";");
+ }
+
+ public void visit(SynchronizedStmt n, Object arg) {
+ printer.print("synchronized (");
+ n.getExpr().accept(this, arg);
+ printer.print(") ");
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(TryStmt n, Object arg) {
+ printer.print("try ");
+ n.getTryBlock().accept(this, arg);
+ if (n.getCatchs() != null) {
+ for (CatchClause c : n.getCatchs()) {
+ c.accept(this, arg);
+ }
+ }
+ if (n.getFinallyBlock() != null) {
+ printer.print(" finally ");
+ n.getFinallyBlock().accept(this, arg);
+ }
+ }
+
+ public void visit(CatchClause n, Object arg) {
+ printer.print(" catch (");
+ n.getExcept().accept(this, arg);
+ printer.print(") ");
+ n.getCatchBlock().accept(this, arg);
+
+ }
+
+ public void visit(AnnotationDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ printer.print("@interface ");
+ printer.print(n.getName());
+ printer.printLn(" {");
+ printer.indent();
+ if (n.getMembers() != null) {
+ printMembers(n.getMembers(), arg);
+ }
+ printer.unindent();
+ printer.print("}");
+ }
+
+ public void visit(AnnotationMemberDeclaration n, Object arg) {
+ printJavadoc(n.getJavaDoc(), arg);
+ printMemberAnnotations(n.getAnnotations(), arg);
+ printModifiers(n.getModifiers());
+
+ n.getType().accept(this, arg);
+ printer.print(" ");
+ printer.print(n.getName());
+ printer.print("()");
+ if (n.getDefaultValue() != null) {
+ printer.print(" default ");
+ n.getDefaultValue().accept(this, arg);
+ }
+ printer.print(";");
+ }
+
+ public void visit(MarkerAnnotationExpr n, Object arg) {
+ printer.print("@");
+ n.getName().accept(this, arg);
+ }
+
+ public void visit(SingleMemberAnnotationExpr n, Object arg) {
+ printer.print("@");
+ n.getName().accept(this, arg);
+ printer.print("(");
+ n.getMemberValue().accept(this, arg);
+ printer.print(")");
+ }
+
+ public void visit(NormalAnnotationExpr n, Object arg) {
+ printer.print("@");
+ n.getName().accept(this, arg);
+ printer.print("(");
+ if (n.getPairs() != null) {
+ for (Iterator<MemberValuePair> i = n.getPairs().iterator(); i.hasNext();) {
+ MemberValuePair m = i.next();
+ m.accept(this, arg);
+ if (i.hasNext()) {
+ printer.print(", ");
+ }
+ }
+ }
+ printer.print(")");
+ }
+
+ public void visit(MemberValuePair n, Object arg) {
+ printer.print(n.getName());
+ printer.print(" = ");
+ n.getValue().accept(this, arg);
+ }
+
+ public void visit(LineComment n, Object arg) {
+ printer.print("//");
+ printer.printLn(n.getContent());
+ }
+
+ public void visit(BlockComment n, Object arg) {
+ printer.print("/*");
+ printer.print(n.getContent());
+ printer.printLn("*/");
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/GenericVisitor.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/GenericVisitor.java
new file mode 100644
index 000000000..088378cab
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/GenericVisitor.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.visitor;
+
+import japa.parser.ast.BlockComment;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.ImportDeclaration;
+import japa.parser.ast.LineComment;
+import japa.parser.ast.PackageDeclaration;
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.body.AnnotationDeclaration;
+import japa.parser.ast.body.AnnotationMemberDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.ConstructorDeclaration;
+import japa.parser.ast.body.EmptyMemberDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumConstantDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.InitializerDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.ArrayAccessExpr;
+import japa.parser.ast.expr.ArrayCreationExpr;
+import japa.parser.ast.expr.ArrayInitializerExpr;
+import japa.parser.ast.expr.AssignExpr;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BooleanLiteralExpr;
+import japa.parser.ast.expr.CastExpr;
+import japa.parser.ast.expr.CharLiteralExpr;
+import japa.parser.ast.expr.ClassExpr;
+import japa.parser.ast.expr.ConditionalExpr;
+import japa.parser.ast.expr.DoubleLiteralExpr;
+import japa.parser.ast.expr.EnclosedExpr;
+import japa.parser.ast.expr.FieldAccessExpr;
+import japa.parser.ast.expr.InstanceOfExpr;
+import japa.parser.ast.expr.IntegerLiteralExpr;
+import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
+import japa.parser.ast.expr.LongLiteralExpr;
+import japa.parser.ast.expr.LongLiteralMinValueExpr;
+import japa.parser.ast.expr.MarkerAnnotationExpr;
+import japa.parser.ast.expr.MemberValuePair;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NormalAnnotationExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.expr.ObjectCreationExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.SingleMemberAnnotationExpr;
+import japa.parser.ast.expr.StringLiteralExpr;
+import japa.parser.ast.expr.SuperExpr;
+import japa.parser.ast.expr.ThisExpr;
+import japa.parser.ast.expr.UnaryExpr;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.WildcardType;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public interface GenericVisitor<R, A> {
+
+ //- Compilation Unit ----------------------------------
+
+ public R visit(CompilationUnit n, A arg);
+
+ public R visit(PackageDeclaration n, A arg);
+
+ public R visit(ImportDeclaration n, A arg);
+
+ public R visit(TypeParameter n, A arg);
+
+ public R visit(LineComment n, A arg);
+
+ public R visit(BlockComment n, A arg);
+
+ //- Body ----------------------------------------------
+
+ public R visit(ClassOrInterfaceDeclaration n, A arg);
+
+ public R visit(EnumDeclaration n, A arg);
+
+ public R visit(EmptyTypeDeclaration n, A arg);
+
+ public R visit(EnumConstantDeclaration n, A arg);
+
+ public R visit(AnnotationDeclaration n, A arg);
+
+ public R visit(AnnotationMemberDeclaration n, A arg);
+
+ public R visit(FieldDeclaration n, A arg);
+
+ public R visit(VariableDeclarator n, A arg);
+
+ public R visit(VariableDeclaratorId n, A arg);
+
+ public R visit(ConstructorDeclaration n, A arg);
+
+ public R visit(MethodDeclaration n, A arg);
+
+ public R visit(Parameter n, A arg);
+
+ public R visit(EmptyMemberDeclaration n, A arg);
+
+ public R visit(InitializerDeclaration n, A arg);
+
+ public R visit(JavadocComment n, A arg);
+
+ //- Type ----------------------------------------------
+
+ public R visit(ClassOrInterfaceType n, A arg);
+
+ public R visit(PrimitiveType n, A arg);
+
+ public R visit(ReferenceType n, A arg);
+
+ public R visit(VoidType n, A arg);
+
+ public R visit(WildcardType n, A arg);
+
+ //- Expression ----------------------------------------
+
+ public R visit(ArrayAccessExpr n, A arg);
+
+ public R visit(ArrayCreationExpr n, A arg);
+
+ public R visit(ArrayInitializerExpr n, A arg);
+
+ public R visit(AssignExpr n, A arg);
+
+ public R visit(BinaryExpr n, A arg);
+
+ public R visit(CastExpr n, A arg);
+
+ public R visit(ClassExpr n, A arg);
+
+ public R visit(ConditionalExpr n, A arg);
+
+ public R visit(EnclosedExpr n, A arg);
+
+ public R visit(FieldAccessExpr n, A arg);
+
+ public R visit(InstanceOfExpr n, A arg);
+
+ public R visit(StringLiteralExpr n, A arg);
+
+ public R visit(IntegerLiteralExpr n, A arg);
+
+ public R visit(LongLiteralExpr n, A arg);
+
+ public R visit(IntegerLiteralMinValueExpr n, A arg);
+
+ public R visit(LongLiteralMinValueExpr n, A arg);
+
+ public R visit(CharLiteralExpr n, A arg);
+
+ public R visit(DoubleLiteralExpr n, A arg);
+
+ public R visit(BooleanLiteralExpr n, A arg);
+
+ public R visit(NullLiteralExpr n, A arg);
+
+ public R visit(MethodCallExpr n, A arg);
+
+ public R visit(NameExpr n, A arg);
+
+ public R visit(ObjectCreationExpr n, A arg);
+
+ public R visit(QualifiedNameExpr n, A arg);
+
+ public R visit(ThisExpr n, A arg);
+
+ public R visit(SuperExpr n, A arg);
+
+ public R visit(UnaryExpr n, A arg);
+
+ public R visit(VariableDeclarationExpr n, A arg);
+
+ public R visit(MarkerAnnotationExpr n, A arg);
+
+ public R visit(SingleMemberAnnotationExpr n, A arg);
+
+ public R visit(NormalAnnotationExpr n, A arg);
+
+ public R visit(MemberValuePair n, A arg);
+
+ //- Statements ----------------------------------------
+
+ public R visit(ExplicitConstructorInvocationStmt n, A arg);
+
+ public R visit(TypeDeclarationStmt n, A arg);
+
+ public R visit(AssertStmt n, A arg);
+
+ public R visit(BlockStmt n, A arg);
+
+ public R visit(LabeledStmt n, A arg);
+
+ public R visit(EmptyStmt n, A arg);
+
+ public R visit(ExpressionStmt n, A arg);
+
+ public R visit(SwitchStmt n, A arg);
+
+ public R visit(SwitchEntryStmt n, A arg);
+
+ public R visit(BreakStmt n, A arg);
+
+ public R visit(ReturnStmt n, A arg);
+
+ public R visit(IfStmt n, A arg);
+
+ public R visit(WhileStmt n, A arg);
+
+ public R visit(ContinueStmt n, A arg);
+
+ public R visit(DoStmt n, A arg);
+
+ public R visit(ForeachStmt n, A arg);
+
+ public R visit(ForStmt n, A arg);
+
+ public R visit(ThrowStmt n, A arg);
+
+ public R visit(SynchronizedStmt n, A arg);
+
+ public R visit(TryStmt n, A arg);
+
+ public R visit(CatchClause n, A arg);
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/GenericVisitorAdapter.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/GenericVisitorAdapter.java
new file mode 100644
index 000000000..e2a09b983
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/GenericVisitorAdapter.java
@@ -0,0 +1,825 @@
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 09/06/2008
+ */
+package japa.parser.ast.visitor;
+
+import japa.parser.ast.BlockComment;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.ImportDeclaration;
+import japa.parser.ast.LineComment;
+import japa.parser.ast.PackageDeclaration;
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.body.AnnotationDeclaration;
+import japa.parser.ast.body.AnnotationMemberDeclaration;
+import japa.parser.ast.body.BodyDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.ConstructorDeclaration;
+import japa.parser.ast.body.EmptyMemberDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumConstantDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.InitializerDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.TypeDeclaration;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.ArrayAccessExpr;
+import japa.parser.ast.expr.ArrayCreationExpr;
+import japa.parser.ast.expr.ArrayInitializerExpr;
+import japa.parser.ast.expr.AssignExpr;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BooleanLiteralExpr;
+import japa.parser.ast.expr.CastExpr;
+import japa.parser.ast.expr.CharLiteralExpr;
+import japa.parser.ast.expr.ClassExpr;
+import japa.parser.ast.expr.ConditionalExpr;
+import japa.parser.ast.expr.DoubleLiteralExpr;
+import japa.parser.ast.expr.EnclosedExpr;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.expr.FieldAccessExpr;
+import japa.parser.ast.expr.InstanceOfExpr;
+import japa.parser.ast.expr.IntegerLiteralExpr;
+import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
+import japa.parser.ast.expr.LongLiteralExpr;
+import japa.parser.ast.expr.LongLiteralMinValueExpr;
+import japa.parser.ast.expr.MarkerAnnotationExpr;
+import japa.parser.ast.expr.MemberValuePair;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NormalAnnotationExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.expr.ObjectCreationExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.SingleMemberAnnotationExpr;
+import japa.parser.ast.expr.StringLiteralExpr;
+import japa.parser.ast.expr.SuperExpr;
+import japa.parser.ast.expr.ThisExpr;
+import japa.parser.ast.expr.UnaryExpr;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.WildcardType;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public abstract class GenericVisitorAdapter<R, A> implements GenericVisitor<R, A> {
+
+ public R visit(AnnotationDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getMembers() != null) {
+ for (BodyDeclaration member : n.getMembers()) {
+ member.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(AnnotationMemberDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ if (n.getDefaultValue() != null) {
+ n.getDefaultValue().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(ArrayAccessExpr n, A arg) {
+ n.getName().accept(this, arg);
+ n.getIndex().accept(this, arg);
+ return null;
+ }
+
+ public R visit(ArrayCreationExpr n, A arg) {
+ n.getType().accept(this, arg);
+ if (n.getDimensions() != null) {
+ for (Expression dim : n.getDimensions()) {
+ dim.accept(this, arg);
+ }
+ } else {
+ n.getInitializer().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(ArrayInitializerExpr n, A arg) {
+ if (n.getValues() != null) {
+ for (Expression expr : n.getValues()) {
+ expr.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(AssertStmt n, A arg) {
+ n.getCheck().accept(this, arg);
+ if (n.getMessage() != null) {
+ n.getMessage().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(AssignExpr n, A arg) {
+ n.getTarget().accept(this, arg);
+ n.getValue().accept(this, arg);
+ return null;
+ }
+
+ public R visit(BinaryExpr n, A arg) {
+ n.getLeft().accept(this, arg);
+ n.getRight().accept(this, arg);
+ return null;
+ }
+
+ public R visit(BlockStmt n, A arg) {
+ if (n.getStmts() != null) {
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ }
+ }
+ return null;
+
+ }
+
+ public R visit(BooleanLiteralExpr n, A arg) {
+ return null;
+ }
+
+ public R visit(BreakStmt n, A arg) {
+ return null;
+ }
+
+ public R visit(CastExpr n, A arg) {
+ n.getType().accept(this, arg);
+ n.getExpr().accept(this, arg);
+ return null;
+ }
+
+ public R visit(CatchClause n, A arg) {
+ n.getExcept().accept(this, arg);
+ n.getCatchBlock().accept(this, arg);
+ return null;
+
+ }
+
+ public R visit(CharLiteralExpr n, A arg) {
+ return null;
+ }
+
+ public R visit(ClassExpr n, A arg) {
+ n.getType().accept(this, arg);
+ return null;
+ }
+
+ public R visit(ClassOrInterfaceDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getTypeParameters() != null) {
+ for (TypeParameter t : n.getTypeParameters()) {
+ t.accept(this, arg);
+ }
+ }
+ if (n.getExtends() != null) {
+ for (ClassOrInterfaceType c : n.getExtends()) {
+ c.accept(this, arg);
+ }
+ }
+
+ if (n.getImplements() != null) {
+ for (ClassOrInterfaceType c : n.getImplements()) {
+ c.accept(this, arg);
+ }
+ }
+ if (n.getMembers() != null) {
+ for (BodyDeclaration member : n.getMembers()) {
+ member.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(ClassOrInterfaceType n, A arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ }
+ if (n.getTypeArgs() != null) {
+ for (Type t : n.getTypeArgs()) {
+ t.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(CompilationUnit n, A arg) {
+ if (n.getPackage() != null) {
+ n.getPackage().accept(this, arg);
+ }
+ if (n.getImports() != null) {
+ for (ImportDeclaration i : n.getImports()) {
+ i.accept(this, arg);
+ }
+ }
+ if (n.getTypes() != null) {
+ for (TypeDeclaration typeDeclaration : n.getTypes()) {
+ typeDeclaration.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(ConditionalExpr n, A arg) {
+ n.getCondition().accept(this, arg);
+ n.getThenExpr().accept(this, arg);
+ n.getElseExpr().accept(this, arg);
+ return null;
+ }
+
+ public R visit(ConstructorDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getTypeParameters() != null) {
+ for (TypeParameter t : n.getTypeParameters()) {
+ t.accept(this, arg);
+ }
+ }
+ if (n.getParameters() != null) {
+ for (Parameter p : n.getParameters()) {
+ p.accept(this, arg);
+ }
+ }
+ if (n.getThrows() != null) {
+ for (NameExpr name : n.getThrows()) {
+ name.accept(this, arg);
+ }
+ }
+ n.getBlock().accept(this, arg);
+ return null;
+ }
+
+ public R visit(ContinueStmt n, A arg) {
+ return null;
+ }
+
+ public R visit(DoStmt n, A arg) {
+ n.getBody().accept(this, arg);
+ n.getCondition().accept(this, arg);
+ return null;
+ }
+
+ public R visit(DoubleLiteralExpr n, A arg) {
+ return null;
+ }
+
+ public R visit(EmptyMemberDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(EmptyStmt n, A arg) {
+ return null;
+ }
+
+ public R visit(EmptyTypeDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(EnclosedExpr n, A arg) {
+ n.getInner().accept(this, arg);
+ return null;
+ }
+
+ public R visit(EnumConstantDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getArgs() != null) {
+ for (Expression e : n.getArgs()) {
+ e.accept(this, arg);
+ }
+ }
+ if (n.getClassBody() != null) {
+ for (BodyDeclaration member : n.getClassBody()) {
+ member.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(EnumDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getImplements() != null) {
+ for (ClassOrInterfaceType c : n.getImplements()) {
+ c.accept(this, arg);
+ }
+ }
+ if (n.getEntries() != null) {
+ for (EnumConstantDeclaration e : n.getEntries()) {
+ e.accept(this, arg);
+ }
+ }
+ if (n.getMembers() != null) {
+ for (BodyDeclaration member : n.getMembers()) {
+ member.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(ExplicitConstructorInvocationStmt n, A arg) {
+ if (!n.isThis()) {
+ if (n.getExpr() != null) {
+ n.getExpr().accept(this, arg);
+ }
+ }
+ if (n.getTypeArgs() != null) {
+ for (Type t : n.getTypeArgs()) {
+ t.accept(this, arg);
+ }
+ }
+ if (n.getArgs() != null) {
+ for (Expression e : n.getArgs()) {
+ e.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(ExpressionStmt n, A arg) {
+ n.getExpression().accept(this, arg);
+ return null;
+ }
+
+ public R visit(FieldAccessExpr n, A arg) {
+ n.getScope().accept(this, arg);
+ return null;
+ }
+
+ public R visit(FieldDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ for (VariableDeclarator var : n.getVariables()) {
+ var.accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(ForeachStmt n, A arg) {
+ n.getVariable().accept(this, arg);
+ n.getIterable().accept(this, arg);
+ n.getBody().accept(this, arg);
+ return null;
+ }
+
+ public R visit(ForStmt n, A arg) {
+ if (n.getInit() != null) {
+ for (Expression e : n.getInit()) {
+ e.accept(this, arg);
+ }
+ }
+ if (n.getCompare() != null) {
+ n.getCompare().accept(this, arg);
+ }
+ if (n.getUpdate() != null) {
+ for (Expression e : n.getUpdate()) {
+ e.accept(this, arg);
+ }
+ }
+ n.getBody().accept(this, arg);
+ return null;
+ }
+
+ public R visit(IfStmt n, A arg) {
+ n.getCondition().accept(this, arg);
+ n.getThenStmt().accept(this, arg);
+ if (n.getElseStmt() != null) {
+ n.getElseStmt().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(ImportDeclaration n, A arg) {
+ n.getName().accept(this, arg);
+ return null;
+ }
+
+ public R visit(InitializerDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ n.getBlock().accept(this, arg);
+ return null;
+ }
+
+ public R visit(InstanceOfExpr n, A arg) {
+ n.getExpr().accept(this, arg);
+ n.getType().accept(this, arg);
+ return null;
+ }
+
+ public R visit(IntegerLiteralExpr n, A arg) {
+ return null;
+ }
+
+ public R visit(IntegerLiteralMinValueExpr n, A arg) {
+ return null;
+ }
+
+ public R visit(JavadocComment n, A arg) {
+ return null;
+ }
+
+ public R visit(LabeledStmt n, A arg) {
+ n.getStmt().accept(this, arg);
+ return null;
+ }
+
+ public R visit(LongLiteralExpr n, A arg) {
+ return null;
+ }
+
+ public R visit(LongLiteralMinValueExpr n, A arg) {
+ return null;
+ }
+
+ public R visit(MarkerAnnotationExpr n, A arg) {
+ n.getName().accept(this, arg);
+ return null;
+ }
+
+ public R visit(MemberValuePair n, A arg) {
+ n.getValue().accept(this, arg);
+ return null;
+ }
+
+ public R visit(MethodCallExpr n, A arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ }
+ if (n.getTypeArgs() != null) {
+ for (Type t : n.getTypeArgs()) {
+ t.accept(this, arg);
+ }
+ }
+ if (n.getArgs() != null) {
+ for (Expression e : n.getArgs()) {
+ e.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(MethodDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getTypeParameters() != null) {
+ for (TypeParameter t : n.getTypeParameters()) {
+ t.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ if (n.getParameters() != null) {
+ for (Parameter p : n.getParameters()) {
+ p.accept(this, arg);
+ }
+ }
+ if (n.getThrows() != null) {
+ for (NameExpr name : n.getThrows()) {
+ name.accept(this, arg);
+ }
+ }
+ if (n.getBody() != null) {
+ n.getBody().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(NameExpr n, A arg) {
+ return null;
+ }
+
+ public R visit(NormalAnnotationExpr n, A arg) {
+ n.getName().accept(this, arg);
+ if (n.getPairs() != null) {
+ for (MemberValuePair m : n.getPairs()) {
+ m.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(NullLiteralExpr n, A arg) {
+ return null;
+ }
+
+ public R visit(ObjectCreationExpr n, A arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ }
+ if (n.getTypeArgs() != null) {
+ for (Type t : n.getTypeArgs()) {
+ t.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ if (n.getArgs() != null) {
+ for (Expression e : n.getArgs()) {
+ e.accept(this, arg);
+ }
+ }
+ if (n.getAnonymousClassBody() != null) {
+ for (BodyDeclaration member : n.getAnonymousClassBody()) {
+ member.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(PackageDeclaration n, A arg) {
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ n.getName().accept(this, arg);
+ return null;
+ }
+
+ public R visit(Parameter n, A arg) {
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ n.getId().accept(this, arg);
+ return null;
+ }
+
+ public R visit(PrimitiveType n, A arg) {
+ return null;
+ }
+
+ public R visit(QualifiedNameExpr n, A arg) {
+ n.getQualifier().accept(this, arg);
+ return null;
+ }
+
+ public R visit(ReferenceType n, A arg) {
+ n.getType().accept(this, arg);
+ return null;
+ }
+
+ public R visit(ReturnStmt n, A arg) {
+ if (n.getExpr() != null) {
+ n.getExpr().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(SingleMemberAnnotationExpr n, A arg) {
+ n.getName().accept(this, arg);
+ n.getMemberValue().accept(this, arg);
+ return null;
+ }
+
+ public R visit(StringLiteralExpr n, A arg) {
+ return null;
+ }
+
+ public R visit(SuperExpr n, A arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(SwitchEntryStmt n, A arg) {
+ if (n.getLabel() != null) {
+ n.getLabel().accept(this, arg);
+ }
+ if (n.getStmts() != null) {
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(SwitchStmt n, A arg) {
+ n.getSelector().accept(this, arg);
+ if (n.getEntries() != null) {
+ for (SwitchEntryStmt e : n.getEntries()) {
+ e.accept(this, arg);
+ }
+ }
+ return null;
+
+ }
+
+ public R visit(SynchronizedStmt n, A arg) {
+ n.getExpr().accept(this, arg);
+ n.getBlock().accept(this, arg);
+ return null;
+ }
+
+ public R visit(ThisExpr n, A arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(ThrowStmt n, A arg) {
+ n.getExpr().accept(this, arg);
+ return null;
+ }
+
+ public R visit(TryStmt n, A arg) {
+ n.getTryBlock().accept(this, arg);
+ if (n.getCatchs() != null) {
+ for (CatchClause c : n.getCatchs()) {
+ c.accept(this, arg);
+ }
+ }
+ if (n.getFinallyBlock() != null) {
+ n.getFinallyBlock().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(TypeDeclarationStmt n, A arg) {
+ n.getTypeDeclaration().accept(this, arg);
+ return null;
+ }
+
+ public R visit(TypeParameter n, A arg) {
+ if (n.getTypeBound() != null) {
+ for (ClassOrInterfaceType c : n.getTypeBound()) {
+ c.accept(this, arg);
+ }
+ }
+ return null;
+ }
+
+ public R visit(UnaryExpr n, A arg) {
+ n.getExpr().accept(this, arg);
+ return null;
+ }
+
+ public R visit(VariableDeclarationExpr n, A arg) {
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ for (VariableDeclarator v : n.getVars()) {
+ v.accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(VariableDeclarator n, A arg) {
+ n.getId().accept(this, arg);
+ if (n.getInit() != null) {
+ n.getInit().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(VariableDeclaratorId n, A arg) {
+ return null;
+ }
+
+ public R visit(VoidType n, A arg) {
+ return null;
+ }
+
+ public R visit(WhileStmt n, A arg) {
+ n.getCondition().accept(this, arg);
+ n.getBody().accept(this, arg);
+ return null;
+ }
+
+ public R visit(WildcardType n, A arg) {
+ if (n.getExtends() != null) {
+ n.getExtends().accept(this, arg);
+ }
+ if (n.getSuper() != null) {
+ n.getSuper().accept(this, arg);
+ }
+ return null;
+ }
+
+ public R visit(BlockComment n, A arg) {
+ return null;
+ }
+
+ public R visit(LineComment n, A arg) {
+ return null;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/ModifierVisitorAdapter.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/ModifierVisitorAdapter.java
new file mode 100644
index 000000000..e26d1c417
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/ModifierVisitorAdapter.java
@@ -0,0 +1,940 @@
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 09/06/2008
+ */
+package japa.parser.ast.visitor;
+
+import japa.parser.ast.BlockComment;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.ImportDeclaration;
+import japa.parser.ast.LineComment;
+import japa.parser.ast.Node;
+import japa.parser.ast.PackageDeclaration;
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.body.AnnotationDeclaration;
+import japa.parser.ast.body.AnnotationMemberDeclaration;
+import japa.parser.ast.body.BodyDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.ConstructorDeclaration;
+import japa.parser.ast.body.EmptyMemberDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumConstantDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.InitializerDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.TypeDeclaration;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.ArrayAccessExpr;
+import japa.parser.ast.expr.ArrayCreationExpr;
+import japa.parser.ast.expr.ArrayInitializerExpr;
+import japa.parser.ast.expr.AssignExpr;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BooleanLiteralExpr;
+import japa.parser.ast.expr.CastExpr;
+import japa.parser.ast.expr.CharLiteralExpr;
+import japa.parser.ast.expr.ClassExpr;
+import japa.parser.ast.expr.ConditionalExpr;
+import japa.parser.ast.expr.DoubleLiteralExpr;
+import japa.parser.ast.expr.EnclosedExpr;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.expr.FieldAccessExpr;
+import japa.parser.ast.expr.InstanceOfExpr;
+import japa.parser.ast.expr.IntegerLiteralExpr;
+import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
+import japa.parser.ast.expr.LongLiteralExpr;
+import japa.parser.ast.expr.LongLiteralMinValueExpr;
+import japa.parser.ast.expr.MarkerAnnotationExpr;
+import japa.parser.ast.expr.MemberValuePair;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NormalAnnotationExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.expr.ObjectCreationExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.SingleMemberAnnotationExpr;
+import japa.parser.ast.expr.StringLiteralExpr;
+import japa.parser.ast.expr.SuperExpr;
+import japa.parser.ast.expr.ThisExpr;
+import japa.parser.ast.expr.UnaryExpr;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.WildcardType;
+
+import java.util.List;
+
+/**
+ * This visitor adapter can be used to save time when some specific nodes needs
+ * to be changed. To do that just extend this class and override the methods
+ * from the nodes who needs to be changed, returning the changed node.
+ *
+ * @author Julio Vilmar Gesser
+ */
+public abstract class ModifierVisitorAdapter<A> implements GenericVisitor<Node, A> {
+
+ private void removeNulls(List< ? > list) {
+ for (int i = list.size() - 1; i >= 0; i--) {
+ if (list.get(i) == null) {
+ list.remove(i);
+ }
+ }
+ }
+
+ public Node visit(AnnotationDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ List<BodyDeclaration> members = n.getMembers();
+ if (members != null) {
+ for (int i = 0; i < members.size(); i++) {
+ members.set(i, (BodyDeclaration) members.get(i).accept(this, arg));
+ }
+ removeNulls(members);
+ }
+ return n;
+ }
+
+ public Node visit(AnnotationMemberDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ n.setType((Type) n.getType().accept(this, arg));
+ if (n.getDefaultValue() != null) {
+ n.setDefaultValue((Expression) n.getDefaultValue().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(ArrayAccessExpr n, A arg) {
+ n.setName((Expression) n.getName().accept(this, arg));
+ n.setIndex((Expression) n.getIndex().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(ArrayCreationExpr n, A arg) {
+ n.setType((Type) n.getType().accept(this, arg));
+ if (n.getDimensions() != null) {
+ List<Expression> dimensions = n.getDimensions();
+ if (dimensions != null) {
+ for (int i = 0; i < dimensions.size(); i++) {
+ dimensions.set(i, (Expression) dimensions.get(i).accept(this, arg));
+ }
+ removeNulls(dimensions);
+ }
+ } else {
+ n.setInitializer((ArrayInitializerExpr) n.getInitializer().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(ArrayInitializerExpr n, A arg) {
+ if (n.getValues() != null) {
+ List<Expression> values = n.getValues();
+ if (values != null) {
+ for (int i = 0; i < values.size(); i++) {
+ values.set(i, (Expression) values.get(i).accept(this, arg));
+ }
+ removeNulls(values);
+ }
+ }
+ return n;
+ }
+
+ public Node visit(AssertStmt n, A arg) {
+ n.setCheck((Expression) n.getCheck().accept(this, arg));
+ if (n.getMessage() != null) {
+ n.setMessage((Expression) n.getMessage().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(AssignExpr n, A arg) {
+ n.setTarget((Expression) n.getTarget().accept(this, arg));
+ n.setValue((Expression) n.getValue().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(BinaryExpr n, A arg) {
+ n.setLeft((Expression) n.getLeft().accept(this, arg));
+ n.setRight((Expression) n.getRight().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(BlockStmt n, A arg) {
+ List<Statement> stmts = n.getStmts();
+ if (stmts != null) {
+ for (int i = 0; i < stmts.size(); i++) {
+ stmts.set(i, (Statement) stmts.get(i).accept(this, arg));
+ }
+ removeNulls(stmts);
+ }
+ return n;
+ }
+
+ public Node visit(BooleanLiteralExpr n, A arg) {
+ return n;
+ }
+
+ public Node visit(BreakStmt n, A arg) {
+ return n;
+ }
+
+ public Node visit(CastExpr n, A arg) {
+ n.setType((Type) n.getType().accept(this, arg));
+ n.setExpr((Expression) n.getExpr().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(CatchClause n, A arg) {
+ n.setExcept((Parameter) n.getExcept().accept(this, arg));
+ n.setCatchBlock((BlockStmt) n.getCatchBlock().accept(this, arg));
+ return n;
+
+ }
+
+ public Node visit(CharLiteralExpr n, A arg) {
+ return n;
+ }
+
+ public Node visit(ClassExpr n, A arg) {
+ n.setType((Type) n.getType().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(ClassOrInterfaceDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ List<TypeParameter> typeParameters = n.getTypeParameters();
+ if (typeParameters != null) {
+ for (int i = 0; i < typeParameters.size(); i++) {
+ typeParameters.set(i, (TypeParameter) typeParameters.get(i).accept(this, arg));
+ }
+ removeNulls(typeParameters);
+ }
+ List<ClassOrInterfaceType> extendz = n.getExtends();
+ if (extendz != null) {
+ for (int i = 0; i < extendz.size(); i++) {
+ extendz.set(i, (ClassOrInterfaceType) extendz.get(i).accept(this, arg));
+ }
+ removeNulls(extendz);
+ }
+ List<ClassOrInterfaceType> implementz = n.getImplements();
+ if (implementz != null) {
+ for (int i = 0; i < implementz.size(); i++) {
+ implementz.set(i, (ClassOrInterfaceType) implementz.get(i).accept(this, arg));
+ }
+ removeNulls(implementz);
+ }
+ List<BodyDeclaration> members = n.getMembers();
+ if (members != null) {
+ for (int i = 0; i < members.size(); i++) {
+ members.set(i, (BodyDeclaration) members.get(i).accept(this, arg));
+ }
+ removeNulls(members);
+ }
+ return n;
+ }
+
+ public Node visit(ClassOrInterfaceType n, A arg) {
+ if (n.getScope() != null) {
+ n.setScope((ClassOrInterfaceType) n.getScope().accept(this, arg));
+ }
+ List<Type> typeArgs = n.getTypeArgs();
+ if (typeArgs != null) {
+ for (int i = 0; i < typeArgs.size(); i++) {
+ typeArgs.set(i, (Type) typeArgs.get(i).accept(this, arg));
+ }
+ removeNulls(typeArgs);
+ }
+ return n;
+ }
+
+ public Node visit(CompilationUnit n, A arg) {
+ if (n.getPackage() != null) {
+ n.setPackage((PackageDeclaration) n.getPackage().accept(this, arg));
+ }
+ List<ImportDeclaration> imports = n.getImports();
+ if (imports != null) {
+ for (int i = 0; i < imports.size(); i++) {
+ imports.set(i, (ImportDeclaration) imports.get(i).accept(this, arg));
+ }
+ removeNulls(imports);
+ }
+ List<TypeDeclaration> types = n.getTypes();
+ if (types != null) {
+ for (int i = 0; i < types.size(); i++) {
+ types.set(i, (TypeDeclaration) types.get(i).accept(this, arg));
+ }
+ removeNulls(types);
+ }
+ return n;
+ }
+
+ public Node visit(ConditionalExpr n, A arg) {
+ n.setCondition((Expression) n.getCondition().accept(this, arg));
+ n.setThenExpr((Expression) n.getThenExpr().accept(this, arg));
+ n.setElseExpr((Expression) n.getElseExpr().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(ConstructorDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ List<TypeParameter> typeParameters = n.getTypeParameters();
+ if (typeParameters != null) {
+ for (int i = 0; i < typeParameters.size(); i++) {
+ typeParameters.set(i, (TypeParameter) typeParameters.get(i).accept(this, arg));
+ }
+ removeNulls(typeParameters);
+ }
+ List<Parameter> parameters = n.getParameters();
+ if (parameters != null) {
+ for (int i = 0; i < parameters.size(); i++) {
+ parameters.set(i, (Parameter) parameters.get(i).accept(this, arg));
+ }
+ removeNulls(parameters);
+ }
+ List<NameExpr> throwz = n.getThrows();
+ if (throwz != null) {
+ for (int i = 0; i < throwz.size(); i++) {
+ throwz.set(i, (NameExpr) throwz.get(i).accept(this, arg));
+ }
+ removeNulls(throwz);
+ }
+ n.setBlock((BlockStmt) n.getBlock().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(ContinueStmt n, A arg) {
+ return n;
+ }
+
+ public Node visit(DoStmt n, A arg) {
+ n.setBody((Statement) n.getBody().accept(this, arg));
+ n.setCondition((Expression) n.getCondition().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(DoubleLiteralExpr n, A arg) {
+ return n;
+ }
+
+ public Node visit(EmptyMemberDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(EmptyStmt n, A arg) {
+ return n;
+ }
+
+ public Node visit(EmptyTypeDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(EnclosedExpr n, A arg) {
+ n.setInner((Expression) n.getInner().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(EnumConstantDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ List<Expression> args = n.getArgs();
+ if (args != null) {
+ for (int i = 0; i < args.size(); i++) {
+ args.set(i, (Expression) args.get(i).accept(this, arg));
+ }
+ removeNulls(args);
+ }
+ List<BodyDeclaration> classBody = n.getClassBody();
+ if (classBody != null) {
+ for (int i = 0; i < classBody.size(); i++) {
+ classBody.set(i, (BodyDeclaration) classBody.get(i).accept(this, arg));
+ }
+ removeNulls(classBody);
+ }
+ return n;
+ }
+
+ public Node visit(EnumDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ List<ClassOrInterfaceType> implementz = n.getImplements();
+ if (implementz != null) {
+ for (int i = 0; i < implementz.size(); i++) {
+ implementz.set(i, (ClassOrInterfaceType) implementz.get(i).accept(this, arg));
+ }
+ removeNulls(implementz);
+ }
+ List<EnumConstantDeclaration> entries = n.getEntries();
+ if (entries != null) {
+ for (int i = 0; i < entries.size(); i++) {
+ entries.set(i, (EnumConstantDeclaration) entries.get(i).accept(this, arg));
+ }
+ removeNulls(entries);
+ }
+ List<BodyDeclaration> members = n.getMembers();
+ if (members != null) {
+ for (int i = 0; i < members.size(); i++) {
+ members.set(i, (BodyDeclaration) members.get(i).accept(this, arg));
+ }
+ removeNulls(members);
+ }
+ return n;
+ }
+
+ public Node visit(ExplicitConstructorInvocationStmt n, A arg) {
+ if (!n.isThis()) {
+ if (n.getExpr() != null) {
+ n.setExpr((Expression) n.getExpr().accept(this, arg));
+ }
+ }
+ List<Type> typeArgs = n.getTypeArgs();
+ if (typeArgs != null) {
+ for (int i = 0; i < typeArgs.size(); i++) {
+ typeArgs.set(i, (Type) typeArgs.get(i).accept(this, arg));
+ }
+ removeNulls(typeArgs);
+ }
+ List<Expression> args = n.getArgs();
+ if (args != null) {
+ for (int i = 0; i < args.size(); i++) {
+ args.set(i, (Expression) args.get(i).accept(this, arg));
+ }
+ removeNulls(args);
+ }
+ return n;
+ }
+
+ public Node visit(ExpressionStmt n, A arg) {
+ n.setExpression((Expression) n.getExpression().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(FieldAccessExpr n, A arg) {
+ n.setScope((Expression) n.getScope().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(FieldDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ n.setType((Type) n.getType().accept(this, arg));
+ List<VariableDeclarator> variables = n.getVariables();
+ for (int i = 0; i < variables.size(); i++) {
+ variables.set(i, (VariableDeclarator) variables.get(i).accept(this, arg));
+ }
+ removeNulls(variables);
+ return n;
+ }
+
+ public Node visit(ForeachStmt n, A arg) {
+ n.setVariable((VariableDeclarationExpr) n.getVariable().accept(this, arg));
+ n.setIterable((Expression) n.getIterable().accept(this, arg));
+ n.setBody((Statement) n.getBody().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(ForStmt n, A arg) {
+ List<Expression> init = n.getInit();
+ if (init != null) {
+ for (int i = 0; i < init.size(); i++) {
+ init.set(i, (Expression) init.get(i).accept(this, arg));
+ }
+ removeNulls(init);
+ }
+ if (n.getCompare() != null) {
+ n.setCompare((Expression) n.getCompare().accept(this, arg));
+ }
+ List<Expression> update = n.getUpdate();
+ if (update != null) {
+ for (int i = 0; i < update.size(); i++) {
+ update.set(i, (Expression) update.get(i).accept(this, arg));
+ }
+ removeNulls(update);
+ }
+ n.setBody((Statement) n.getBody().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(IfStmt n, A arg) {
+ n.setCondition((Expression) n.getCondition().accept(this, arg));
+ n.setThenStmt((Statement) n.getThenStmt().accept(this, arg));
+ if (n.getElseStmt() != null) {
+ n.setElseStmt((Statement) n.getElseStmt().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(ImportDeclaration n, A arg) {
+ n.setName((NameExpr) n.getName().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(InitializerDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ n.setBlock((BlockStmt) n.getBlock().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(InstanceOfExpr n, A arg) {
+ n.setExpr((Expression) n.getExpr().accept(this, arg));
+ n.setType((Type) n.getType().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(IntegerLiteralExpr n, A arg) {
+ return n;
+ }
+
+ public Node visit(IntegerLiteralMinValueExpr n, A arg) {
+ return n;
+ }
+
+ public Node visit(JavadocComment n, A arg) {
+ return n;
+ }
+
+ public Node visit(LabeledStmt n, A arg) {
+ n.setStmt((Statement) n.getStmt().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(LongLiteralExpr n, A arg) {
+ return n;
+ }
+
+ public Node visit(LongLiteralMinValueExpr n, A arg) {
+ return n;
+ }
+
+ public Node visit(MarkerAnnotationExpr n, A arg) {
+ n.setName((NameExpr) n.getName().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(MemberValuePair n, A arg) {
+ n.setValue((Expression) n.getValue().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(MethodCallExpr n, A arg) {
+ if (n.getScope() != null) {
+ n.setScope((Expression) n.getScope().accept(this, arg));
+ }
+ List<Type> typeArgs = n.getTypeArgs();
+ if (typeArgs != null) {
+ for (int i = 0; i < typeArgs.size(); i++) {
+ typeArgs.set(i, (Type) typeArgs.get(i).accept(this, arg));
+ }
+ removeNulls(typeArgs);
+ }
+ List<Expression> args = n.getArgs();
+ if (args != null) {
+ for (int i = 0; i < args.size(); i++) {
+ args.set(i, (Expression) args.get(i).accept(this, arg));
+ }
+ removeNulls(args);
+ }
+ return n;
+ }
+
+ public Node visit(MethodDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.setJavaDoc((JavadocComment) n.getJavaDoc().accept(this, arg));
+ }
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ List<TypeParameter> typeParameters = n.getTypeParameters();
+ if (typeParameters != null) {
+ for (int i = 0; i < typeParameters.size(); i++) {
+ typeParameters.set(i, (TypeParameter) typeParameters.get(i).accept(this, arg));
+ }
+ removeNulls(typeParameters);
+ }
+ n.setType((Type) n.getType().accept(this, arg));
+ List<Parameter> parameters = n.getParameters();
+ if (parameters != null) {
+ for (int i = 0; i < parameters.size(); i++) {
+ parameters.set(i, (Parameter) parameters.get(i).accept(this, arg));
+ }
+ removeNulls(parameters);
+ }
+ List<NameExpr> throwz = n.getThrows();
+ if (throwz != null) {
+ for (int i = 0; i < throwz.size(); i++) {
+ throwz.set(i, (NameExpr) throwz.get(i).accept(this, arg));
+ }
+ removeNulls(throwz);
+ }
+ if (n.getBody() != null) {
+ n.setBody((BlockStmt) n.getBody().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(NameExpr n, A arg) {
+ return n;
+ }
+
+ public Node visit(NormalAnnotationExpr n, A arg) {
+ n.setName((NameExpr) n.getName().accept(this, arg));
+ List<MemberValuePair> pairs = n.getPairs();
+ if (pairs != null) {
+ for (int i = 0; i < pairs.size(); i++) {
+ pairs.set(i, (MemberValuePair) pairs.get(i).accept(this, arg));
+ }
+ removeNulls(pairs);
+ }
+ return n;
+ }
+
+ public Node visit(NullLiteralExpr n, A arg) {
+ return n;
+ }
+
+ public Node visit(ObjectCreationExpr n, A arg) {
+ if (n.getScope() != null) {
+ n.setScope((Expression) n.getScope().accept(this, arg));
+ }
+ List<Type> typeArgs = n.getTypeArgs();
+ if (typeArgs != null) {
+ for (int i = 0; i < typeArgs.size(); i++) {
+ typeArgs.set(i, (Type) typeArgs.get(i).accept(this, arg));
+ }
+ removeNulls(typeArgs);
+ }
+ n.setType((ClassOrInterfaceType) n.getType().accept(this, arg));
+ List<Expression> args = n.getArgs();
+ if (args != null) {
+ for (int i = 0; i < args.size(); i++) {
+ args.set(i, (Expression) args.get(i).accept(this, arg));
+ }
+ removeNulls(args);
+ }
+ List<BodyDeclaration> anonymousClassBody = n.getAnonymousClassBody();
+ if (anonymousClassBody != null) {
+ for (int i = 0; i < anonymousClassBody.size(); i++) {
+ anonymousClassBody.set(i, (BodyDeclaration) anonymousClassBody.get(i).accept(this, arg));
+ }
+ removeNulls(anonymousClassBody);
+ }
+ return n;
+ }
+
+ public Node visit(PackageDeclaration n, A arg) {
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ n.setName((NameExpr) n.getName().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(Parameter n, A arg) {
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ n.setType((Type) n.getType().accept(this, arg));
+ n.setId((VariableDeclaratorId) n.getId().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(PrimitiveType n, A arg) {
+ return n;
+ }
+
+ public Node visit(QualifiedNameExpr n, A arg) {
+ n.setQualifier((NameExpr) n.getQualifier().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(ReferenceType n, A arg) {
+ n.setType((Type) n.getType().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(ReturnStmt n, A arg) {
+ if (n.getExpr() != null) {
+ n.setExpr((Expression) n.getExpr().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(SingleMemberAnnotationExpr n, A arg) {
+ n.setName((NameExpr) n.getName().accept(this, arg));
+ n.setMemberValue((Expression) n.getMemberValue().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(StringLiteralExpr n, A arg) {
+ return n;
+ }
+
+ public Node visit(SuperExpr n, A arg) {
+ if (n.getClassExpr() != null) {
+ n.setClassExpr((Expression) n.getClassExpr().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(SwitchEntryStmt n, A arg) {
+ if (n.getLabel() != null) {
+ n.setLabel((Expression) n.getLabel().accept(this, arg));
+ }
+ List<Statement> stmts = n.getStmts();
+ if (stmts != null) {
+ for (int i = 0; i < stmts.size(); i++) {
+ stmts.set(i, (Statement) stmts.get(i).accept(this, arg));
+ }
+ removeNulls(stmts);
+ }
+ return n;
+ }
+
+ public Node visit(SwitchStmt n, A arg) {
+ n.setSelector((Expression) n.getSelector().accept(this, arg));
+ List<SwitchEntryStmt> entries = n.getEntries();
+ if (entries != null) {
+ for (int i = 0; i < entries.size(); i++) {
+ entries.set(i, (SwitchEntryStmt) entries.get(i).accept(this, arg));
+ }
+ removeNulls(entries);
+ }
+ return n;
+
+ }
+
+ public Node visit(SynchronizedStmt n, A arg) {
+ n.setExpr((Expression) n.getExpr().accept(this, arg));
+ n.setBlock((BlockStmt) n.getBlock().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(ThisExpr n, A arg) {
+ if (n.getClassExpr() != null) {
+ n.setClassExpr((Expression) n.getClassExpr().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(ThrowStmt n, A arg) {
+ n.setExpr((Expression) n.getExpr().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(TryStmt n, A arg) {
+ n.setTryBlock((BlockStmt) n.getTryBlock().accept(this, arg));
+ List<CatchClause> catchs = n.getCatchs();
+ if (catchs != null) {
+ for (int i = 0; i < catchs.size(); i++) {
+ catchs.set(i, (CatchClause) catchs.get(i).accept(this, arg));
+ }
+ removeNulls(catchs);
+ }
+ if (n.getFinallyBlock() != null) {
+ n.setFinallyBlock((BlockStmt) n.getFinallyBlock().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(TypeDeclarationStmt n, A arg) {
+ n.setTypeDeclaration((TypeDeclaration) n.getTypeDeclaration().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(TypeParameter n, A arg) {
+ List<ClassOrInterfaceType> typeBound = n.getTypeBound();
+ if (typeBound != null) {
+ for (int i = 0; i < typeBound.size(); i++) {
+ typeBound.set(i, (ClassOrInterfaceType) typeBound.get(i).accept(this, arg));
+ }
+ removeNulls(typeBound);
+ }
+ return n;
+ }
+
+ public Node visit(UnaryExpr n, A arg) {
+ n.setExpr((Expression) n.getExpr().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(VariableDeclarationExpr n, A arg) {
+ List<AnnotationExpr> annotations = n.getAnnotations();
+ if (annotations != null) {
+ for (int i = 0; i < annotations.size(); i++) {
+ annotations.set(i, (AnnotationExpr) annotations.get(i).accept(this, arg));
+ }
+ removeNulls(annotations);
+ }
+ n.setType((Type) n.getType().accept(this, arg));
+ List<VariableDeclarator> vars = n.getVars();
+ for (int i = 0; i < vars.size(); i++) {
+ vars.set(i, (VariableDeclarator) vars.get(i).accept(this, arg));
+ }
+ removeNulls(vars);
+ return n;
+ }
+
+ public Node visit(VariableDeclarator n, A arg) {
+ n.setId((VariableDeclaratorId) n.getId().accept(this, arg));
+ if (n.getInit() != null) {
+ n.setInit((Expression) n.getInit().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(VariableDeclaratorId n, A arg) {
+ return n;
+ }
+
+ public Node visit(VoidType n, A arg) {
+ return n;
+ }
+
+ public Node visit(WhileStmt n, A arg) {
+ n.setCondition((Expression) n.getCondition().accept(this, arg));
+ n.setBody((Statement) n.getBody().accept(this, arg));
+ return n;
+ }
+
+ public Node visit(WildcardType n, A arg) {
+ if (n.getExtends() != null) {
+ n.setExtends((ReferenceType) n.getExtends().accept(this, arg));
+ }
+ if (n.getSuper() != null) {
+ n.setSuper((ReferenceType) n.getSuper().accept(this, arg));
+ }
+ return n;
+ }
+
+ public Node visit(BlockComment n, A arg) {
+ return n;
+ }
+
+ public Node visit(LineComment n, A arg) {
+ return n;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/VoidVisitor.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/VoidVisitor.java
new file mode 100644
index 000000000..951d36b40
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/VoidVisitor.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 05/10/2006
+ */
+package japa.parser.ast.visitor;
+
+import japa.parser.ast.BlockComment;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.ImportDeclaration;
+import japa.parser.ast.LineComment;
+import japa.parser.ast.PackageDeclaration;
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.body.AnnotationDeclaration;
+import japa.parser.ast.body.AnnotationMemberDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.ConstructorDeclaration;
+import japa.parser.ast.body.EmptyMemberDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumConstantDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.InitializerDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.ArrayAccessExpr;
+import japa.parser.ast.expr.ArrayCreationExpr;
+import japa.parser.ast.expr.ArrayInitializerExpr;
+import japa.parser.ast.expr.AssignExpr;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BooleanLiteralExpr;
+import japa.parser.ast.expr.CastExpr;
+import japa.parser.ast.expr.CharLiteralExpr;
+import japa.parser.ast.expr.ClassExpr;
+import japa.parser.ast.expr.ConditionalExpr;
+import japa.parser.ast.expr.DoubleLiteralExpr;
+import japa.parser.ast.expr.EnclosedExpr;
+import japa.parser.ast.expr.FieldAccessExpr;
+import japa.parser.ast.expr.InstanceOfExpr;
+import japa.parser.ast.expr.IntegerLiteralExpr;
+import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
+import japa.parser.ast.expr.LongLiteralExpr;
+import japa.parser.ast.expr.LongLiteralMinValueExpr;
+import japa.parser.ast.expr.MarkerAnnotationExpr;
+import japa.parser.ast.expr.MemberValuePair;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NormalAnnotationExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.expr.ObjectCreationExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.SingleMemberAnnotationExpr;
+import japa.parser.ast.expr.StringLiteralExpr;
+import japa.parser.ast.expr.SuperExpr;
+import japa.parser.ast.expr.ThisExpr;
+import japa.parser.ast.expr.UnaryExpr;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.WildcardType;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public interface VoidVisitor<A> {
+
+ //- Compilation Unit ----------------------------------
+
+ public void visit(CompilationUnit n, A arg);
+
+ public void visit(PackageDeclaration n, A arg);
+
+ public void visit(ImportDeclaration n, A arg);
+
+ public void visit(TypeParameter n, A arg);
+
+ public void visit(LineComment n, A arg);
+
+ public void visit(BlockComment n, A arg);
+
+ //- Body ----------------------------------------------
+
+ public void visit(ClassOrInterfaceDeclaration n, A arg);
+
+ public void visit(EnumDeclaration n, A arg);
+
+ public void visit(EmptyTypeDeclaration n, A arg);
+
+ public void visit(EnumConstantDeclaration n, A arg);
+
+ public void visit(AnnotationDeclaration n, A arg);
+
+ public void visit(AnnotationMemberDeclaration n, A arg);
+
+ public void visit(FieldDeclaration n, A arg);
+
+ public void visit(VariableDeclarator n, A arg);
+
+ public void visit(VariableDeclaratorId n, A arg);
+
+ public void visit(ConstructorDeclaration n, A arg);
+
+ public void visit(MethodDeclaration n, A arg);
+
+ public void visit(Parameter n, A arg);
+
+ public void visit(EmptyMemberDeclaration n, A arg);
+
+ public void visit(InitializerDeclaration n, A arg);
+
+ public void visit(JavadocComment n, A arg);
+
+ //- Type ----------------------------------------------
+
+ public void visit(ClassOrInterfaceType n, A arg);
+
+ public void visit(PrimitiveType n, A arg);
+
+ public void visit(ReferenceType n, A arg);
+
+ public void visit(VoidType n, A arg);
+
+ public void visit(WildcardType n, A arg);
+
+ //- Expression ----------------------------------------
+
+ public void visit(ArrayAccessExpr n, A arg);
+
+ public void visit(ArrayCreationExpr n, A arg);
+
+ public void visit(ArrayInitializerExpr n, A arg);
+
+ public void visit(AssignExpr n, A arg);
+
+ public void visit(BinaryExpr n, A arg);
+
+ public void visit(CastExpr n, A arg);
+
+ public void visit(ClassExpr n, A arg);
+
+ public void visit(ConditionalExpr n, A arg);
+
+ public void visit(EnclosedExpr n, A arg);
+
+ public void visit(FieldAccessExpr n, A arg);
+
+ public void visit(InstanceOfExpr n, A arg);
+
+ public void visit(StringLiteralExpr n, A arg);
+
+ public void visit(IntegerLiteralExpr n, A arg);
+
+ public void visit(LongLiteralExpr n, A arg);
+
+ public void visit(IntegerLiteralMinValueExpr n, A arg);
+
+ public void visit(LongLiteralMinValueExpr n, A arg);
+
+ public void visit(CharLiteralExpr n, A arg);
+
+ public void visit(DoubleLiteralExpr n, A arg);
+
+ public void visit(BooleanLiteralExpr n, A arg);
+
+ public void visit(NullLiteralExpr n, A arg);
+
+ public void visit(MethodCallExpr n, A arg);
+
+ public void visit(NameExpr n, A arg);
+
+ public void visit(ObjectCreationExpr n, A arg);
+
+ public void visit(QualifiedNameExpr n, A arg);
+
+ public void visit(ThisExpr n, A arg);
+
+ public void visit(SuperExpr n, A arg);
+
+ public void visit(UnaryExpr n, A arg);
+
+ public void visit(VariableDeclarationExpr n, A arg);
+
+ public void visit(MarkerAnnotationExpr n, A arg);
+
+ public void visit(SingleMemberAnnotationExpr n, A arg);
+
+ public void visit(NormalAnnotationExpr n, A arg);
+
+ public void visit(MemberValuePair n, A arg);
+
+ //- Statements ----------------------------------------
+
+ public void visit(ExplicitConstructorInvocationStmt n, A arg);
+
+ public void visit(TypeDeclarationStmt n, A arg);
+
+ public void visit(AssertStmt n, A arg);
+
+ public void visit(BlockStmt n, A arg);
+
+ public void visit(LabeledStmt n, A arg);
+
+ public void visit(EmptyStmt n, A arg);
+
+ public void visit(ExpressionStmt n, A arg);
+
+ public void visit(SwitchStmt n, A arg);
+
+ public void visit(SwitchEntryStmt n, A arg);
+
+ public void visit(BreakStmt n, A arg);
+
+ public void visit(ReturnStmt n, A arg);
+
+ public void visit(IfStmt n, A arg);
+
+ public void visit(WhileStmt n, A arg);
+
+ public void visit(ContinueStmt n, A arg);
+
+ public void visit(DoStmt n, A arg);
+
+ public void visit(ForeachStmt n, A arg);
+
+ public void visit(ForStmt n, A arg);
+
+ public void visit(ThrowStmt n, A arg);
+
+ public void visit(SynchronizedStmt n, A arg);
+
+ public void visit(TryStmt n, A arg);
+
+ public void visit(CatchClause n, A arg);
+
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/VoidVisitorAdapter.java b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/VoidVisitorAdapter.java
new file mode 100644
index 000000000..c79e8b545
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/ast/visitor/VoidVisitorAdapter.java
@@ -0,0 +1,743 @@
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 09/06/2008
+ */
+package japa.parser.ast.visitor;
+
+import japa.parser.ast.BlockComment;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.ImportDeclaration;
+import japa.parser.ast.LineComment;
+import japa.parser.ast.PackageDeclaration;
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.body.AnnotationDeclaration;
+import japa.parser.ast.body.AnnotationMemberDeclaration;
+import japa.parser.ast.body.BodyDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.ConstructorDeclaration;
+import japa.parser.ast.body.EmptyMemberDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumConstantDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.InitializerDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.TypeDeclaration;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.AnnotationExpr;
+import japa.parser.ast.expr.ArrayAccessExpr;
+import japa.parser.ast.expr.ArrayCreationExpr;
+import japa.parser.ast.expr.ArrayInitializerExpr;
+import japa.parser.ast.expr.AssignExpr;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BooleanLiteralExpr;
+import japa.parser.ast.expr.CastExpr;
+import japa.parser.ast.expr.CharLiteralExpr;
+import japa.parser.ast.expr.ClassExpr;
+import japa.parser.ast.expr.ConditionalExpr;
+import japa.parser.ast.expr.DoubleLiteralExpr;
+import japa.parser.ast.expr.EnclosedExpr;
+import japa.parser.ast.expr.Expression;
+import japa.parser.ast.expr.FieldAccessExpr;
+import japa.parser.ast.expr.InstanceOfExpr;
+import japa.parser.ast.expr.IntegerLiteralExpr;
+import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
+import japa.parser.ast.expr.LongLiteralExpr;
+import japa.parser.ast.expr.LongLiteralMinValueExpr;
+import japa.parser.ast.expr.MarkerAnnotationExpr;
+import japa.parser.ast.expr.MemberValuePair;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NormalAnnotationExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.expr.ObjectCreationExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.SingleMemberAnnotationExpr;
+import japa.parser.ast.expr.StringLiteralExpr;
+import japa.parser.ast.expr.SuperExpr;
+import japa.parser.ast.expr.ThisExpr;
+import japa.parser.ast.expr.UnaryExpr;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.Statement;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.Type;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.WildcardType;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public abstract class VoidVisitorAdapter<A> implements VoidVisitor<A> {
+
+ public void visit(AnnotationDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getMembers() != null) {
+ for (BodyDeclaration member : n.getMembers()) {
+ member.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(AnnotationMemberDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ if (n.getDefaultValue() != null) {
+ n.getDefaultValue().accept(this, arg);
+ }
+ }
+
+ public void visit(ArrayAccessExpr n, A arg) {
+ n.getName().accept(this, arg);
+ n.getIndex().accept(this, arg);
+ }
+
+ public void visit(ArrayCreationExpr n, A arg) {
+ n.getType().accept(this, arg);
+ if (n.getDimensions() != null) {
+ for (Expression dim : n.getDimensions()) {
+ dim.accept(this, arg);
+ }
+ } else {
+ n.getInitializer().accept(this, arg);
+ }
+ }
+
+ public void visit(ArrayInitializerExpr n, A arg) {
+ if (n.getValues() != null) {
+ for (Expression expr : n.getValues()) {
+ expr.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(AssertStmt n, A arg) {
+ n.getCheck().accept(this, arg);
+ if (n.getMessage() != null) {
+ n.getMessage().accept(this, arg);
+ }
+ }
+
+ public void visit(AssignExpr n, A arg) {
+ n.getTarget().accept(this, arg);
+ n.getValue().accept(this, arg);
+ }
+
+ public void visit(BinaryExpr n, A arg) {
+ n.getLeft().accept(this, arg);
+ n.getRight().accept(this, arg);
+ }
+
+ public void visit(BlockComment n, A arg) {
+ }
+
+ public void visit(BlockStmt n, A arg) {
+ if (n.getStmts() != null) {
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(BooleanLiteralExpr n, A arg) {
+ }
+
+ public void visit(BreakStmt n, A arg) {
+ }
+
+ public void visit(CastExpr n, A arg) {
+ n.getType().accept(this, arg);
+ n.getExpr().accept(this, arg);
+ }
+
+ public void visit(CatchClause n, A arg) {
+ n.getExcept().accept(this, arg);
+ n.getCatchBlock().accept(this, arg);
+ }
+
+ public void visit(CharLiteralExpr n, A arg) {
+ }
+
+ public void visit(ClassExpr n, A arg) {
+ n.getType().accept(this, arg);
+ }
+
+ public void visit(ClassOrInterfaceDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getTypeParameters() != null) {
+ for (TypeParameter t : n.getTypeParameters()) {
+ t.accept(this, arg);
+ }
+ }
+ if (n.getExtends() != null) {
+ for (ClassOrInterfaceType c : n.getExtends()) {
+ c.accept(this, arg);
+ }
+ }
+
+ if (n.getImplements() != null) {
+ for (ClassOrInterfaceType c : n.getImplements()) {
+ c.accept(this, arg);
+ }
+ }
+ if (n.getMembers() != null) {
+ for (BodyDeclaration member : n.getMembers()) {
+ member.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(ClassOrInterfaceType n, A arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ }
+ if (n.getTypeArgs() != null) {
+ for (Type t : n.getTypeArgs()) {
+ t.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(CompilationUnit n, A arg) {
+ if (n.getPackage() != null) {
+ n.getPackage().accept(this, arg);
+ }
+ if (n.getImports() != null) {
+ for (ImportDeclaration i : n.getImports()) {
+ i.accept(this, arg);
+ }
+ }
+ if (n.getTypes() != null) {
+ for (TypeDeclaration typeDeclaration : n.getTypes()) {
+ typeDeclaration.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(ConditionalExpr n, A arg) {
+ n.getCondition().accept(this, arg);
+ n.getThenExpr().accept(this, arg);
+ n.getElseExpr().accept(this, arg);
+ }
+
+ public void visit(ConstructorDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getTypeParameters() != null) {
+ for (TypeParameter t : n.getTypeParameters()) {
+ t.accept(this, arg);
+ }
+ }
+ if (n.getParameters() != null) {
+ for (Parameter p : n.getParameters()) {
+ p.accept(this, arg);
+ }
+ }
+ if (n.getThrows() != null) {
+ for (NameExpr name : n.getThrows()) {
+ name.accept(this, arg);
+ }
+ }
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(ContinueStmt n, A arg) {
+ }
+
+ public void visit(DoStmt n, A arg) {
+ n.getBody().accept(this, arg);
+ n.getCondition().accept(this, arg);
+ }
+
+ public void visit(DoubleLiteralExpr n, A arg) {
+ }
+
+ public void visit(EmptyMemberDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ }
+
+ public void visit(EmptyStmt n, A arg) {
+ }
+
+ public void visit(EmptyTypeDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ }
+
+ public void visit(EnclosedExpr n, A arg) {
+ n.getInner().accept(this, arg);
+ }
+
+ public void visit(EnumConstantDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getArgs() != null) {
+ for (Expression e : n.getArgs()) {
+ e.accept(this, arg);
+ }
+ }
+ if (n.getClassBody() != null) {
+ for (BodyDeclaration member : n.getClassBody()) {
+ member.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(EnumDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getImplements() != null) {
+ for (ClassOrInterfaceType c : n.getImplements()) {
+ c.accept(this, arg);
+ }
+ }
+ if (n.getEntries() != null) {
+ for (EnumConstantDeclaration e : n.getEntries()) {
+ e.accept(this, arg);
+ }
+ }
+ if (n.getMembers() != null) {
+ for (BodyDeclaration member : n.getMembers()) {
+ member.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(ExplicitConstructorInvocationStmt n, A arg) {
+ if (!n.isThis()) {
+ if (n.getExpr() != null) {
+ n.getExpr().accept(this, arg);
+ }
+ }
+ if (n.getTypeArgs() != null) {
+ for (Type t : n.getTypeArgs()) {
+ t.accept(this, arg);
+ }
+ }
+ if (n.getArgs() != null) {
+ for (Expression e : n.getArgs()) {
+ e.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(ExpressionStmt n, A arg) {
+ n.getExpression().accept(this, arg);
+ }
+
+ public void visit(FieldAccessExpr n, A arg) {
+ n.getScope().accept(this, arg);
+ }
+
+ public void visit(FieldDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ for (VariableDeclarator var : n.getVariables()) {
+ var.accept(this, arg);
+ }
+ }
+
+ public void visit(ForeachStmt n, A arg) {
+ n.getVariable().accept(this, arg);
+ n.getIterable().accept(this, arg);
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(ForStmt n, A arg) {
+ if (n.getInit() != null) {
+ for (Expression e : n.getInit()) {
+ e.accept(this, arg);
+ }
+ }
+ if (n.getCompare() != null) {
+ n.getCompare().accept(this, arg);
+ }
+ if (n.getUpdate() != null) {
+ for (Expression e : n.getUpdate()) {
+ e.accept(this, arg);
+ }
+ }
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(IfStmt n, A arg) {
+ n.getCondition().accept(this, arg);
+ n.getThenStmt().accept(this, arg);
+ if (n.getElseStmt() != null) {
+ n.getElseStmt().accept(this, arg);
+ }
+ }
+
+ public void visit(ImportDeclaration n, A arg) {
+ n.getName().accept(this, arg);
+ }
+
+ public void visit(InitializerDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ n.getBlock().accept(this, arg);
+ }
+
+ public void visit(InstanceOfExpr n, A arg) {
+ n.getExpr().accept(this, arg);
+ n.getType().accept(this, arg);
+ }
+
+ public void visit(IntegerLiteralExpr n, A arg) {
+ }
+
+ public void visit(IntegerLiteralMinValueExpr n, A arg) {
+ }
+
+ public void visit(JavadocComment n, A arg) {
+ }
+
+ public void visit(LabeledStmt n, A arg) {
+ n.getStmt().accept(this, arg);
+ }
+
+ public void visit(LineComment n, A arg) {
+ }
+
+ public void visit(LongLiteralExpr n, A arg) {
+ }
+
+ public void visit(LongLiteralMinValueExpr n, A arg) {
+ }
+
+ public void visit(MarkerAnnotationExpr n, A arg) {
+ n.getName().accept(this, arg);
+ }
+
+ public void visit(MemberValuePair n, A arg) {
+ n.getValue().accept(this, arg);
+ }
+
+ public void visit(MethodCallExpr n, A arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ }
+ if (n.getTypeArgs() != null) {
+ for (Type t : n.getTypeArgs()) {
+ t.accept(this, arg);
+ }
+ }
+ if (n.getArgs() != null) {
+ for (Expression e : n.getArgs()) {
+ e.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(MethodDeclaration n, A arg) {
+ if (n.getJavaDoc() != null) {
+ n.getJavaDoc().accept(this, arg);
+ }
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ if (n.getTypeParameters() != null) {
+ for (TypeParameter t : n.getTypeParameters()) {
+ t.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ if (n.getParameters() != null) {
+ for (Parameter p : n.getParameters()) {
+ p.accept(this, arg);
+ }
+ }
+ if (n.getThrows() != null) {
+ for (NameExpr name : n.getThrows()) {
+ name.accept(this, arg);
+ }
+ }
+ if (n.getBody() != null) {
+ n.getBody().accept(this, arg);
+ }
+ }
+
+ public void visit(NameExpr n, A arg) {
+ }
+
+ public void visit(NormalAnnotationExpr n, A arg) {
+ n.getName().accept(this, arg);
+ if (n.getPairs() != null) {
+ for (MemberValuePair m : n.getPairs()) {
+ m.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(NullLiteralExpr n, A arg) {
+ }
+
+ public void visit(ObjectCreationExpr n, A arg) {
+ if (n.getScope() != null) {
+ n.getScope().accept(this, arg);
+ }
+ if (n.getTypeArgs() != null) {
+ for (Type t : n.getTypeArgs()) {
+ t.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ if (n.getArgs() != null) {
+ for (Expression e : n.getArgs()) {
+ e.accept(this, arg);
+ }
+ }
+ if (n.getAnonymousClassBody() != null) {
+ for (BodyDeclaration member : n.getAnonymousClassBody()) {
+ member.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(PackageDeclaration n, A arg) {
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ n.getName().accept(this, arg);
+ }
+
+ public void visit(Parameter n, A arg) {
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ n.getId().accept(this, arg);
+ }
+
+ public void visit(PrimitiveType n, A arg) {
+ }
+
+ public void visit(QualifiedNameExpr n, A arg) {
+ n.getQualifier().accept(this, arg);
+ }
+
+ public void visit(ReferenceType n, A arg) {
+ n.getType().accept(this, arg);
+ }
+
+ public void visit(ReturnStmt n, A arg) {
+ if (n.getExpr() != null) {
+ n.getExpr().accept(this, arg);
+ }
+ }
+
+ public void visit(SingleMemberAnnotationExpr n, A arg) {
+ n.getName().accept(this, arg);
+ n.getMemberValue().accept(this, arg);
+ }
+
+ public void visit(StringLiteralExpr n, A arg) {
+ }
+
+ public void visit(SuperExpr n, A arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ }
+ }
+
+ public void visit(SwitchEntryStmt n, A arg) {
+ if (n.getLabel() != null) {
+ n.getLabel().accept(this, arg);
+ }
+ if (n.getStmts() != null) {
+ for (Statement s : n.getStmts()) {
+ s.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(SwitchStmt n, A arg) {
+ n.getSelector().accept(this, arg);
+ if (n.getEntries() != null) {
+ for (SwitchEntryStmt e : n.getEntries()) {
+ e.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(SynchronizedStmt n, A arg) {
+ n.getExpr().accept(this, arg);
+ n.getBlock().accept(this, arg);
+
+ }
+
+ public void visit(ThisExpr n, A arg) {
+ if (n.getClassExpr() != null) {
+ n.getClassExpr().accept(this, arg);
+ }
+ }
+
+ public void visit(ThrowStmt n, A arg) {
+ n.getExpr().accept(this, arg);
+ }
+
+ public void visit(TryStmt n, A arg) {
+ n.getTryBlock().accept(this, arg);
+ if (n.getCatchs() != null) {
+ for (CatchClause c : n.getCatchs()) {
+ c.accept(this, arg);
+ }
+ }
+ if (n.getFinallyBlock() != null) {
+ n.getFinallyBlock().accept(this, arg);
+ }
+ }
+
+ public void visit(TypeDeclarationStmt n, A arg) {
+ n.getTypeDeclaration().accept(this, arg);
+ }
+
+ public void visit(TypeParameter n, A arg) {
+ if (n.getTypeBound() != null) {
+ for (ClassOrInterfaceType c : n.getTypeBound()) {
+ c.accept(this, arg);
+ }
+ }
+ }
+
+ public void visit(UnaryExpr n, A arg) {
+ n.getExpr().accept(this, arg);
+ }
+
+ public void visit(VariableDeclarationExpr n, A arg) {
+ if (n.getAnnotations() != null) {
+ for (AnnotationExpr a : n.getAnnotations()) {
+ a.accept(this, arg);
+ }
+ }
+ n.getType().accept(this, arg);
+ for (VariableDeclarator v : n.getVars()) {
+ v.accept(this, arg);
+ }
+ }
+
+ public void visit(VariableDeclarator n, A arg) {
+ n.getId().accept(this, arg);
+ if (n.getInit() != null) {
+ n.getInit().accept(this, arg);
+ }
+ }
+
+ public void visit(VariableDeclaratorId n, A arg) {
+ }
+
+ public void visit(VoidType n, A arg) {
+ }
+
+ public void visit(WhileStmt n, A arg) {
+ n.getCondition().accept(this, arg);
+ n.getBody().accept(this, arg);
+ }
+
+ public void visit(WildcardType n, A arg) {
+ if (n.getExtends() != null) {
+ n.getExtends().accept(this, arg);
+ }
+ if (n.getSuper() != null) {
+ n.getSuper().accept(this, arg);
+ }
+ }
+}
diff --git a/components/htmlfive/java/javaparser/src/japa/parser/java_1_5.jj b/components/htmlfive/java/javaparser/src/japa/parser/java_1_5.jj
new file mode 100644
index 000000000..f35074082
--- /dev/null
+++ b/components/htmlfive/java/javaparser/src/japa/parser/java_1_5.jj
@@ -0,0 +1,3006 @@
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+options {
+ LOOKAHEAD=1;
+ STATIC=false;
+ JAVA_UNICODE_ESCAPE=true;
+ COMMON_TOKEN_ACTION=true;
+ //SUPPORT_CLASS_VISIBILITY_PUBLIC=false;
+ JDK_VERSION = "1.5";
+ TOKEN_FACTORY = "ASTParser.GTToken";
+}
+
+PARSER_BEGIN(ASTParser)
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+package japa.parser;
+
+import java.io.*;
+import java.util.*;
+import japa.parser.ast.*;
+import japa.parser.ast.body.*;
+import japa.parser.ast.expr.*;
+import japa.parser.ast.stmt.*;
+import japa.parser.ast.type.*;
+
+/**
+ * <p>This class was generated automatically by javacc, do not edit.</p>
+ * @author Júlio Vilmar Gesser
+ */
+final class ASTParser {
+
+ void reset(InputStream in, String encoding) {
+ ReInit(in, encoding);
+ token_source.clearComments();
+ }
+
+ private List add(List list, Object obj) {
+ if (list == null) {
+ list = new LinkedList();
+ }
+ list.add(obj);
+ return list;
+ }
+
+ private List add(int pos, List list, Object obj) {
+ if (list == null) {
+ list = new LinkedList();
+ }
+ list.add(pos, obj);
+ return list;
+ }
+
+ private class Modifier {
+
+ final int modifiers;
+ final List annotations;
+ final int beginLine;
+ final int beginColumn;
+
+ public Modifier(int beginLine, int beginColumn, int modifiers, List annotations) {
+ this.beginLine = beginLine;
+ this.beginColumn = beginColumn;
+ this.modifiers = modifiers;
+ this.annotations = annotations;
+ }
+ }
+
+ public int addModifier(int modifiers, int mod, Token token) throws ParseException {
+ if ((ModifierSet.hasModifier(modifiers, mod))) {
+ throwParseException(token, "Duplicated modifier");
+ }
+ return ModifierSet.addModifier(modifiers, mod);
+ }
+
+ private void pushJavadoc() {
+ token_source.pushJavadoc();
+ }
+
+ private JavadocComment popJavadoc() {
+ return token_source.popJavadoc();
+ }
+
+ private List<Comment> getComments() {
+ return token_source.getComments();
+ }
+
+ private void throwParseException(Token token, String message) throws ParseException {
+ StringBuilder buf = new StringBuilder();
+ buf.append(message);
+ buf.append(": \"");
+ buf.append(token.image);
+ buf.append("\" at line ");
+ buf.append(token.beginLine);
+ buf.append(", column ");
+ buf.append(token.beginColumn);
+ ParseException e = new ParseException(buf.toString());
+ e.currentToken = token;
+ throw e;
+ }
+
+ static final class GTToken extends Token {
+
+ int realKind = ASTParserConstants.GT;
+
+ GTToken(int kind, String image) {
+ this.kind = kind;
+ this.image = image;
+ }
+
+ public static Token newToken(int kind, String image) {
+ return new GTToken(kind, image);
+ }
+ }
+}
+
+PARSER_END(ASTParser)
+
+/* WHITE SPACE */
+
+SKIP :
+{
+ " "
+| "\t"
+| "\n"
+| "\r"
+| "\f"
+}
+
+/* COMMENTS */
+
+TOKEN_MGR_DECLS :
+{
+ private List<Comment> comments;
+ private final Stack<JavadocComment> javadocStack = new Stack<JavadocComment>();
+ private JavadocComment lastJavadoc;
+
+ void pushJavadoc() {
+ javadocStack.push(lastJavadoc);
+ }
+
+ JavadocComment popJavadoc() {
+ return javadocStack.pop();
+ }
+
+ List<Comment> getComments() {
+ return comments;
+ }
+
+ void clearComments() {
+ comments = null;
+ javadocStack.clear();
+ lastJavadoc = null;
+ }
+
+ private void CommonTokenAction(Token token) {
+ lastJavadoc = null;
+ if (token.specialToken != null) {
+ if(comments == null) {
+ comments = new LinkedList<Comment>();
+ }
+ Token special = token.specialToken;
+ if(special.kind == JAVA_DOC_COMMENT) {
+ lastJavadoc = new JavadocComment(special.beginLine, special.beginColumn, special.endLine, special.endColumn, special.image.substring(3, special.image.length()-2));
+ comments.add(lastJavadoc);
+ } else if(special.kind == SINGLE_LINE_COMMENT) {
+ LineComment comment = new LineComment(special.beginLine, special.beginColumn, special.endLine, special.endColumn, special.image.substring(2));
+ comments.add(comment);
+ } else if(special.kind == MULTI_LINE_COMMENT) {
+ BlockComment comment = new BlockComment(special.beginLine, special.beginColumn, special.endLine, special.endColumn, special.image.substring(2, special.image.length()-2));
+ comments.add(comment);
+ }
+ }
+ }
+}
+
+SPECIAL_TOKEN :
+{
+ <SINGLE_LINE_COMMENT: "//" (~["\n","\r"])* ("\n"|"\r"|"\r\n")? >
+}
+
+MORE :
+{
+ <"/**" ~["/"]> { input_stream.backup(1); } : IN_JAVA_DOC_COMMENT
+|
+ <"/*"> : IN_MULTI_LINE_COMMENT
+}
+
+<IN_JAVA_DOC_COMMENT>
+SPECIAL_TOKEN :
+{
+ <JAVA_DOC_COMMENT: "*/" > : DEFAULT
+}
+
+<IN_MULTI_LINE_COMMENT>
+SPECIAL_TOKEN :
+{
+ <MULTI_LINE_COMMENT: "*/" > : DEFAULT
+}
+
+<IN_JAVA_DOC_COMMENT, IN_MULTI_LINE_COMMENT>
+MORE :
+{
+ < ~[] >
+}
+
+/* RESERVED WORDS AND LITERALS */
+
+TOKEN :
+{
+ < ABSTRACT: "abstract" >
+| < ASSERT: "assert" >
+| < BOOLEAN: "boolean" >
+| < BREAK: "break" >
+| < BYTE: "byte" >
+| < CASE: "case" >
+| < CATCH: "catch" >
+| < CHAR: "char" >
+| < CLASS: "class" >
+| < CONST: "const" >
+| < CONTINUE: "continue" >
+| < _DEFAULT: "default" >
+| < DO: "do" >
+| < DOUBLE: "double" >
+| < ELSE: "else" >
+| < ENUM: "enum" >
+| < EXTENDS: "extends" >
+| < FALSE: "false" >
+| < FINAL: "final" >
+| < FINALLY: "finally" >
+| < FLOAT: "float" >
+| < FOR: "for" >
+| < GOTO: "goto" >
+| < IF: "if" >
+| < IMPLEMENTS: "implements" >
+| < IMPORT: "import" >
+| < INSTANCEOF: "instanceof" >
+| < INT: "int" >
+| < INTERFACE: "interface" >
+| < LONG: "long" >
+| < NATIVE: "native" >
+| < NEW: "new" >
+| < NULL: "null" >
+| < PACKAGE: "package">
+| < PRIVATE: "private" >
+| < PROTECTED: "protected" >
+| < PUBLIC: "public" >
+| < RETURN: "return" >
+| < SHORT: "short" >
+| < STATIC: "static" >
+| < STRICTFP: "strictfp" >
+| < SUPER: "super" >
+| < SWITCH: "switch" >
+| < SYNCHRONIZED: "synchronized" >
+| < THIS: "this" >
+| < THROW: "throw" >
+| < THROWS: "throws" >
+| < TRANSIENT: "transient" >
+| < TRUE: "true" >
+| < TRY: "try" >
+| < VOID: "void" >
+| < VOLATILE: "volatile" >
+| < WHILE: "while" >
+}
+
+/* LITERALS */
+
+TOKEN :
+{
+ < LONG_LITERAL:
+ <DECIMAL_LITERAL> (["l","L"])
+ | <HEX_LITERAL> (["l","L"])
+ | <OCTAL_LITERAL> (["l","L"])
+ >
+|
+ < INTEGER_LITERAL:
+ <DECIMAL_LITERAL>
+ | <HEX_LITERAL>
+ | <OCTAL_LITERAL>
+ >
+|
+ < #DECIMAL_LITERAL: ["1"-"9"] (["0"-"9"])* >
+|
+ < #HEX_LITERAL: "0" ["x","X"] (["0"-"9","a"-"f","A"-"F"])+ >
+|
+ < #OCTAL_LITERAL: "0" (["0"-"7"])* >
+|
+ < FLOATING_POINT_LITERAL:
+ <DECIMAL_FLOATING_POINT_LITERAL>
+ | <HEXADECIMAL_FLOATING_POINT_LITERAL>
+ >
+|
+ < #DECIMAL_FLOATING_POINT_LITERAL:
+ (["0"-"9"])+ "." (["0"-"9"])* (<DECIMAL_EXPONENT>)? (["f","F","d","D"])?
+ | "." (["0"-"9"])+ (<DECIMAL_EXPONENT>)? (["f","F","d","D"])?
+ | (["0"-"9"])+ <DECIMAL_EXPONENT> (["f","F","d","D"])?
+ | (["0"-"9"])+ (<DECIMAL_EXPONENT>)? ["f","F","d","D"]
+ >
+|
+ < #DECIMAL_EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ >
+|
+ < #HEXADECIMAL_FLOATING_POINT_LITERAL:
+ "0" ["x", "X"] (["0"-"9","a"-"f","A"-"F"])+ (".")? <HEXADECIMAL_EXPONENT> (["f","F","d","D"])?
+ | "0" ["x", "X"] (["0"-"9","a"-"f","A"-"F"])* "." (["0"-"9","a"-"f","A"-"F"])+ <HEXADECIMAL_EXPONENT> (["f","F","d","D"])?
+ >
+|
+ < #HEXADECIMAL_EXPONENT: ["p","P"] (["+","-"])? (["0"-"9"])+ >
+|
+ < CHARACTER_LITERAL:
+ "'"
+ ( (~["'","\\","\n","\r"])
+ | ("\\"
+ ( ["n","t","b","r","f","\\","'","\""]
+ | ["0"-"7"] ( ["0"-"7"] )?
+ | ["0"-"3"] ["0"-"7"] ["0"-"7"]
+ )
+ )
+ | ("\\u"
+ ["0"-"9","A"-"F","a"-"f"]
+ ["0"-"9","A"-"F","a"-"f"]
+ ["0"-"9","A"-"F","a"-"f"]
+ ["0"-"9","A"-"F","a"-"f"]
+ )
+ )
+ "'"
+ >
+|
+ < STRING_LITERAL:
+ "\""
+ ( (~["\"","\\","\n","\r"])
+ | ("\\"
+ ( ["n","t","b","r","f","\\","'","\""]
+ | ["0"-"7"] ( ["0"-"7"] )?
+ | ["0"-"3"] ["0"-"7"] ["0"-"7"]
+ )
+ )
+ | ("\\u"
+ ["0"-"9","A"-"F","a"-"f"]
+ ["0"-"9","A"-"F","a"-"f"]
+ ["0"-"9","A"-"F","a"-"f"]
+ ["0"-"9","A"-"F","a"-"f"]
+ )
+ )*
+ "\""
+ >
+}
+
+/* IDENTIFIERS */
+
+TOKEN :
+{
+ < IDENTIFIER: <LETTER> (<PART_LETTER>)* >
+|
+ < #LETTER:
+ [ // all chars for which Character.isIdentifierStart is true
+ "\u0024", // "$"
+ "\u0041"-"\u005a", // "A"-"Z"
+ "\u005f", // "_"
+ "\u0061"-"\u007a", // "a"-"z"
+ "\u00a2"-"\u00a5",
+ "\u00aa",
+ "\u00b5",
+ "\u00ba",
+ "\u00c0"-"\u00d6",
+ "\u00d8"-"\u00f6",
+ "\u00f8"-"\u0236",
+ "\u0250"-"\u02c1",
+ "\u02c6"-"\u02d1",
+ "\u02e0"-"\u02e4",
+ "\u02ee",
+ "\u037a",
+ "\u0386",
+ "\u0388"-"\u038a",
+ "\u038c",
+ "\u038e"-"\u03a1",
+ "\u03a3"-"\u03ce",
+ "\u03d0"-"\u03f5",
+ "\u03f7"-"\u03fb",
+ "\u0400"-"\u0481",
+ "\u048a"-"\u04ce",
+ "\u04d0"-"\u04f5",
+ "\u04f8"-"\u04f9",
+ "\u0500"-"\u050f",
+ "\u0531"-"\u0556",
+ "\u0559",
+ "\u0561"-"\u0587",
+ "\u05d0"-"\u05ea",
+ "\u05f0"-"\u05f2",
+ "\u0621"-"\u063a",
+ "\u0640"-"\u064a",
+ "\u066e"-"\u066f",
+ "\u0671"-"\u06d3",
+ "\u06d5",
+ "\u06e5"-"\u06e6",
+ "\u06ee"-"\u06ef",
+ "\u06fa"-"\u06fc",
+ "\u06ff",
+ "\u0710",
+ "\u0712"-"\u072f",
+ "\u074d"-"\u074f",
+ "\u0780"-"\u07a5",
+ "\u07b1",
+ "\u0904"-"\u0939",
+ "\u093d",
+ "\u0950",
+ "\u0958"-"\u0961",
+ "\u0985"-"\u098c",
+ "\u098f"-"\u0990",
+ "\u0993"-"\u09a8",
+ "\u09aa"-"\u09b0",
+ "\u09b2",
+ "\u09b6"-"\u09b9",
+ "\u09bd",
+ "\u09dc"-"\u09dd",
+ "\u09df"-"\u09e1",
+ "\u09f0"-"\u09f3",
+ "\u0a05"-"\u0a0a",
+ "\u0a0f"-"\u0a10",
+ "\u0a13"-"\u0a28",
+ "\u0a2a"-"\u0a30",
+ "\u0a32"-"\u0a33",
+ "\u0a35"-"\u0a36",
+ "\u0a38"-"\u0a39",
+ "\u0a59"-"\u0a5c",
+ "\u0a5e",
+ "\u0a72"-"\u0a74",
+ "\u0a85"-"\u0a8d",
+ "\u0a8f"-"\u0a91",
+ "\u0a93"-"\u0aa8",
+ "\u0aaa"-"\u0ab0",
+ "\u0ab2"-"\u0ab3",
+ "\u0ab5"-"\u0ab9",
+ "\u0abd",
+ "\u0ad0",
+ "\u0ae0"-"\u0ae1",
+ "\u0af1",
+ "\u0b05"-"\u0b0c",
+ "\u0b0f"-"\u0b10",
+ "\u0b13"-"\u0b28",
+ "\u0b2a"-"\u0b30",
+ "\u0b32"-"\u0b33",
+ "\u0b35"-"\u0b39",
+ "\u0b3d",
+ "\u0b5c"-"\u0b5d",
+ "\u0b5f"-"\u0b61",
+ "\u0b71",
+ "\u0b83",
+ "\u0b85"-"\u0b8a",
+ "\u0b8e"-"\u0b90",
+ "\u0b92"-"\u0b95",
+ "\u0b99"-"\u0b9a",
+ "\u0b9c",
+ "\u0b9e"-"\u0b9f",
+ "\u0ba3"-"\u0ba4",
+ "\u0ba8"-"\u0baa",
+ "\u0bae"-"\u0bb5",
+ "\u0bb7"-"\u0bb9",
+ "\u0bf9",
+ "\u0c05"-"\u0c0c",
+ "\u0c0e"-"\u0c10",
+ "\u0c12"-"\u0c28",
+ "\u0c2a"-"\u0c33",
+ "\u0c35"-"\u0c39",
+ "\u0c60"-"\u0c61",
+ "\u0c85"-"\u0c8c",
+ "\u0c8e"-"\u0c90",
+ "\u0c92"-"\u0ca8",
+ "\u0caa"-"\u0cb3",
+ "\u0cb5"-"\u0cb9",
+ "\u0cbd",
+ "\u0cde",
+ "\u0ce0"-"\u0ce1",
+ "\u0d05"-"\u0d0c",
+ "\u0d0e"-"\u0d10",
+ "\u0d12"-"\u0d28",
+ "\u0d2a"-"\u0d39",
+ "\u0d60"-"\u0d61",
+ "\u0d85"-"\u0d96",
+ "\u0d9a"-"\u0db1",
+ "\u0db3"-"\u0dbb",
+ "\u0dbd",
+ "\u0dc0"-"\u0dc6",
+ "\u0e01"-"\u0e30",
+ "\u0e32"-"\u0e33",
+ "\u0e3f"-"\u0e46",
+ "\u0e81"-"\u0e82",
+ "\u0e84",
+ "\u0e87"-"\u0e88",
+ "\u0e8a",
+ "\u0e8d",
+ "\u0e94"-"\u0e97",
+ "\u0e99"-"\u0e9f",
+ "\u0ea1"-"\u0ea3",
+ "\u0ea5",
+ "\u0ea7",
+ "\u0eaa"-"\u0eab",
+ "\u0ead"-"\u0eb0",
+ "\u0eb2"-"\u0eb3",
+ "\u0ebd",
+ "\u0ec0"-"\u0ec4",
+ "\u0ec6",
+ "\u0edc"-"\u0edd",
+ "\u0f00",
+ "\u0f40"-"\u0f47",
+ "\u0f49"-"\u0f6a",
+ "\u0f88"-"\u0f8b",
+ "\u1000"-"\u1021",
+ "\u1023"-"\u1027",
+ "\u1029"-"\u102a",
+ "\u1050"-"\u1055",
+ "\u10a0"-"\u10c5",
+ "\u10d0"-"\u10f8",
+ "\u1100"-"\u1159",
+ "\u115f"-"\u11a2",
+ "\u11a8"-"\u11f9",
+ "\u1200"-"\u1206",
+ "\u1208"-"\u1246",
+ "\u1248",
+ "\u124a"-"\u124d",
+ "\u1250"-"\u1256",
+ "\u1258",
+ "\u125a"-"\u125d",
+ "\u1260"-"\u1286",
+ "\u1288",
+ "\u128a"-"\u128d",
+ "\u1290"-"\u12ae",
+ "\u12b0",
+ "\u12b2"-"\u12b5",
+ "\u12b8"-"\u12be",
+ "\u12c0",
+ "\u12c2"-"\u12c5",
+ "\u12c8"-"\u12ce",
+ "\u12d0"-"\u12d6",
+ "\u12d8"-"\u12ee",
+ "\u12f0"-"\u130e",
+ "\u1310",
+ "\u1312"-"\u1315",
+ "\u1318"-"\u131e",
+ "\u1320"-"\u1346",
+ "\u1348"-"\u135a",
+ "\u13a0"-"\u13f4",
+ "\u1401"-"\u166c",
+ "\u166f"-"\u1676",
+ "\u1681"-"\u169a",
+ "\u16a0"-"\u16ea",
+ "\u16ee"-"\u16f0",
+ "\u1700"-"\u170c",
+ "\u170e"-"\u1711",
+ "\u1720"-"\u1731",
+ "\u1740"-"\u1751",
+ "\u1760"-"\u176c",
+ "\u176e"-"\u1770",
+ "\u1780"-"\u17b3",
+ "\u17d7",
+ "\u17db"-"\u17dc",
+ "\u1820"-"\u1877",
+ "\u1880"-"\u18a8",
+ "\u1900"-"\u191c",
+ "\u1950"-"\u196d",
+ "\u1970"-"\u1974",
+ "\u1d00"-"\u1d6b",
+ "\u1e00"-"\u1e9b",
+ "\u1ea0"-"\u1ef9",
+ "\u1f00"-"\u1f15",
+ "\u1f18"-"\u1f1d",
+ "\u1f20"-"\u1f45",
+ "\u1f48"-"\u1f4d",
+ "\u1f50"-"\u1f57",
+ "\u1f59",
+ "\u1f5b",
+ "\u1f5d",
+ "\u1f5f"-"\u1f7d",
+ "\u1f80"-"\u1fb4",
+ "\u1fb6"-"\u1fbc",
+ "\u1fbe",
+ "\u1fc2"-"\u1fc4",
+ "\u1fc6"-"\u1fcc",
+ "\u1fd0"-"\u1fd3",
+ "\u1fd6"-"\u1fdb",
+ "\u1fe0"-"\u1fec",
+ "\u1ff2"-"\u1ff4",
+ "\u1ff6"-"\u1ffc",
+ "\u203f"-"\u2040",
+ "\u2054",
+ "\u2071",
+ "\u207f",
+ "\u20a0"-"\u20b1",
+ "\u2102",
+ "\u2107",
+ "\u210a"-"\u2113",
+ "\u2115",
+ "\u2119"-"\u211d",
+ "\u2124",
+ "\u2126",
+ "\u2128",
+ "\u212a"-"\u212d",
+ "\u212f"-"\u2131",
+ "\u2133"-"\u2139",
+ "\u213d"-"\u213f",
+ "\u2145"-"\u2149",
+ "\u2160"-"\u2183",
+ "\u3005"-"\u3007",
+ "\u3021"-"\u3029",
+ "\u3031"-"\u3035",
+ "\u3038"-"\u303c",
+ "\u3041"-"\u3096",
+ "\u309d"-"\u309f",
+ "\u30a1"-"\u30ff",
+ "\u3105"-"\u312c",
+ "\u3131"-"\u318e",
+ "\u31a0"-"\u31b7",
+ "\u31f0"-"\u31ff",
+ "\u3400"-"\u4db5",
+ "\u4e00"-"\u9fa5",
+ "\ua000"-"\ua48c",
+ "\uac00"-"\ud7a3",
+ "\ud801", //for supplementary characters suport
+ "\ud802", //for supplementary characters suport
+ "\uf900"-"\ufa2d",
+ "\ufa30"-"\ufa6a",
+ "\ufb00"-"\ufb06",
+ "\ufb13"-"\ufb17",
+ "\ufb1d",
+ "\ufb1f"-"\ufb28",
+ "\ufb2a"-"\ufb36",
+ "\ufb38"-"\ufb3c",
+ "\ufb3e",
+ "\ufb40"-"\ufb41",
+ "\ufb43"-"\ufb44",
+ "\ufb46"-"\ufbb1",
+ "\ufbd3"-"\ufd3d",
+ "\ufd50"-"\ufd8f",
+ "\ufd92"-"\ufdc7",
+ "\ufdf0"-"\ufdfc",
+ "\ufe33"-"\ufe34",
+ "\ufe4d"-"\ufe4f",
+ "\ufe69",
+ "\ufe70"-"\ufe74",
+ "\ufe76"-"\ufefc",
+ "\uff04",
+ "\uff21"-"\uff3a",
+ "\uff3f",
+ "\uff41"-"\uff5a",
+ "\uff65"-"\uffbe",
+ "\uffc2"-"\uffc7",
+ "\uffca"-"\uffcf",
+ "\uffd2"-"\uffd7",
+ "\uffda"-"\uffdc",
+ "\uffe0"-"\uffe1",
+ "\uffe5"-"\uffe6"
+ ]
+ >
+|
+ < #PART_LETTER:
+ [ // all chars for which Character.isIdentifierPart is true
+ "\u0000"-"\u0008",
+ "\u000e"-"\u001b",
+ "\u0024", // "$"
+ "\u0030"-"\u0039", // "0"-"9"
+ "\u0041"-"\u005a", // "A"-"Z"
+ "\u005f", // "_"
+ "\u0061"-"\u007a", // "a"-"z"
+ "\u007f"-"\u009f",
+ "\u00a2"-"\u00a5",
+ "\u00aa",
+ "\u00ad",
+ "\u00b5",
+ "\u00ba",
+ "\u00c0"-"\u00d6",
+ "\u00d8"-"\u00f6",
+ "\u00f8"-"\u0236",
+ "\u0250"-"\u02c1",
+ "\u02c6"-"\u02d1",
+ "\u02e0"-"\u02e4",
+ "\u02ee",
+ "\u0300"-"\u0357",
+ "\u035d"-"\u036f",
+ "\u037a",
+ "\u0386",
+ "\u0388"-"\u038a",
+ "\u038c",
+ "\u038e"-"\u03a1",
+ "\u03a3"-"\u03ce",
+ "\u03d0"-"\u03f5",
+ "\u03f7"-"\u03fb",
+ "\u0400"-"\u0481",
+ "\u0483"-"\u0486",
+ "\u048a"-"\u04ce",
+ "\u04d0"-"\u04f5",
+ "\u04f8"-"\u04f9",
+ "\u0500"-"\u050f",
+ "\u0531"-"\u0556",
+ "\u0559",
+ "\u0561"-"\u0587",
+ "\u0591"-"\u05a1",
+ "\u05a3"-"\u05b9",
+ "\u05bb"-"\u05bd",
+ "\u05bf",
+ "\u05c1"-"\u05c2",
+ "\u05c4",
+ "\u05d0"-"\u05ea",
+ "\u05f0"-"\u05f2",
+ "\u0600"-"\u0603",
+ "\u0610"-"\u0615",
+ "\u0621"-"\u063a",
+ "\u0640"-"\u0658",
+ "\u0660"-"\u0669",
+ "\u066e"-"\u06d3",
+ "\u06d5"-"\u06dd",
+ "\u06df"-"\u06e8",
+ "\u06ea"-"\u06fc",
+ "\u06ff",
+ "\u070f"-"\u074a",
+ "\u074d"-"\u074f",
+ "\u0780"-"\u07b1",
+ "\u0901"-"\u0939",
+ "\u093c"-"\u094d",
+ "\u0950"-"\u0954",
+ "\u0958"-"\u0963",
+ "\u0966"-"\u096f",
+ "\u0981"-"\u0983",
+ "\u0985"-"\u098c",
+ "\u098f"-"\u0990",
+ "\u0993"-"\u09a8",
+ "\u09aa"-"\u09b0",
+ "\u09b2",
+ "\u09b6"-"\u09b9",
+ "\u09bc"-"\u09c4",
+ "\u09c7"-"\u09c8",
+ "\u09cb"-"\u09cd",
+ "\u09d7",
+ "\u09dc"-"\u09dd",
+ "\u09df"-"\u09e3",
+ "\u09e6"-"\u09f3",
+ "\u0a01"-"\u0a03",
+ "\u0a05"-"\u0a0a",
+ "\u0a0f"-"\u0a10",
+ "\u0a13"-"\u0a28",
+ "\u0a2a"-"\u0a30",
+ "\u0a32"-"\u0a33",
+ "\u0a35"-"\u0a36",
+ "\u0a38"-"\u0a39",
+ "\u0a3c",
+ "\u0a3e"-"\u0a42",
+ "\u0a47"-"\u0a48",
+ "\u0a4b"-"\u0a4d",
+ "\u0a59"-"\u0a5c",
+ "\u0a5e",
+ "\u0a66"-"\u0a74",
+ "\u0a81"-"\u0a83",
+ "\u0a85"-"\u0a8d",
+ "\u0a8f"-"\u0a91",
+ "\u0a93"-"\u0aa8",
+ "\u0aaa"-"\u0ab0",
+ "\u0ab2"-"\u0ab3",
+ "\u0ab5"-"\u0ab9",
+ "\u0abc"-"\u0ac5",
+ "\u0ac7"-"\u0ac9",
+ "\u0acb"-"\u0acd",
+ "\u0ad0",
+ "\u0ae0"-"\u0ae3",
+ "\u0ae6"-"\u0aef",
+ "\u0af1",
+ "\u0b01"-"\u0b03",
+ "\u0b05"-"\u0b0c",
+ "\u0b0f"-"\u0b10",
+ "\u0b13"-"\u0b28",
+ "\u0b2a"-"\u0b30",
+ "\u0b32"-"\u0b33",
+ "\u0b35"-"\u0b39",
+ "\u0b3c"-"\u0b43",
+ "\u0b47"-"\u0b48",
+ "\u0b4b"-"\u0b4d",
+ "\u0b56"-"\u0b57",
+ "\u0b5c"-"\u0b5d",
+ "\u0b5f"-"\u0b61",
+ "\u0b66"-"\u0b6f",
+ "\u0b71",
+ "\u0b82"-"\u0b83",
+ "\u0b85"-"\u0b8a",
+ "\u0b8e"-"\u0b90",
+ "\u0b92"-"\u0b95",
+ "\u0b99"-"\u0b9a",
+ "\u0b9c",
+ "\u0b9e"-"\u0b9f",
+ "\u0ba3"-"\u0ba4",
+ "\u0ba8"-"\u0baa",
+ "\u0bae"-"\u0bb5",
+ "\u0bb7"-"\u0bb9",
+ "\u0bbe"-"\u0bc2",
+ "\u0bc6"-"\u0bc8",
+ "\u0bca"-"\u0bcd",
+ "\u0bd7",
+ "\u0be7"-"\u0bef",
+ "\u0bf9",
+ "\u0c01"-"\u0c03",
+ "\u0c05"-"\u0c0c",
+ "\u0c0e"-"\u0c10",
+ "\u0c12"-"\u0c28",
+ "\u0c2a"-"\u0c33",
+ "\u0c35"-"\u0c39",
+ "\u0c3e"-"\u0c44",
+ "\u0c46"-"\u0c48",
+ "\u0c4a"-"\u0c4d",
+ "\u0c55"-"\u0c56",
+ "\u0c60"-"\u0c61",
+ "\u0c66"-"\u0c6f",
+ "\u0c82"-"\u0c83",
+ "\u0c85"-"\u0c8c",
+ "\u0c8e"-"\u0c90",
+ "\u0c92"-"\u0ca8",
+ "\u0caa"-"\u0cb3",
+ "\u0cb5"-"\u0cb9",
+ "\u0cbc"-"\u0cc4",
+ "\u0cc6"-"\u0cc8",
+ "\u0cca"-"\u0ccd",
+ "\u0cd5"-"\u0cd6",
+ "\u0cde",
+ "\u0ce0"-"\u0ce1",
+ "\u0ce6"-"\u0cef",
+ "\u0d02"-"\u0d03",
+ "\u0d05"-"\u0d0c",
+ "\u0d0e"-"\u0d10",
+ "\u0d12"-"\u0d28",
+ "\u0d2a"-"\u0d39",
+ "\u0d3e"-"\u0d43",
+ "\u0d46"-"\u0d48",
+ "\u0d4a"-"\u0d4d",
+ "\u0d57",
+ "\u0d60"-"\u0d61",
+ "\u0d66"-"\u0d6f",
+ "\u0d82"-"\u0d83",
+ "\u0d85"-"\u0d96",
+ "\u0d9a"-"\u0db1",
+ "\u0db3"-"\u0dbb",
+ "\u0dbd",
+ "\u0dc0"-"\u0dc6",
+ "\u0dca",
+ "\u0dcf"-"\u0dd4",
+ "\u0dd6",
+ "\u0dd8"-"\u0ddf",
+ "\u0df2"-"\u0df3",
+ "\u0e01"-"\u0e3a",
+ "\u0e3f"-"\u0e4e",
+ "\u0e50"-"\u0e59",
+ "\u0e81"-"\u0e82",
+ "\u0e84",
+ "\u0e87"-"\u0e88",
+ "\u0e8a",
+ "\u0e8d",
+ "\u0e94"-"\u0e97",
+ "\u0e99"-"\u0e9f",
+ "\u0ea1"-"\u0ea3",
+ "\u0ea5",
+ "\u0ea7",
+ "\u0eaa"-"\u0eab",
+ "\u0ead"-"\u0eb9",
+ "\u0ebb"-"\u0ebd",
+ "\u0ec0"-"\u0ec4",
+ "\u0ec6",
+ "\u0ec8"-"\u0ecd",
+ "\u0ed0"-"\u0ed9",
+ "\u0edc"-"\u0edd",
+ "\u0f00",
+ "\u0f18"-"\u0f19",
+ "\u0f20"-"\u0f29",
+ "\u0f35",
+ "\u0f37",
+ "\u0f39",
+ "\u0f3e"-"\u0f47",
+ "\u0f49"-"\u0f6a",
+ "\u0f71"-"\u0f84",
+ "\u0f86"-"\u0f8b",
+ "\u0f90"-"\u0f97",
+ "\u0f99"-"\u0fbc",
+ "\u0fc6",
+ "\u1000"-"\u1021",
+ "\u1023"-"\u1027",
+ "\u1029"-"\u102a",
+ "\u102c"-"\u1032",
+ "\u1036"-"\u1039",
+ "\u1040"-"\u1049",
+ "\u1050"-"\u1059",
+ "\u10a0"-"\u10c5",
+ "\u10d0"-"\u10f8",
+ "\u1100"-"\u1159",
+ "\u115f"-"\u11a2",
+ "\u11a8"-"\u11f9",
+ "\u1200"-"\u1206",
+ "\u1208"-"\u1246",
+ "\u1248",
+ "\u124a"-"\u124d",
+ "\u1250"-"\u1256",
+ "\u1258",
+ "\u125a"-"\u125d",
+ "\u1260"-"\u1286",
+ "\u1288",
+ "\u128a"-"\u128d",
+ "\u1290"-"\u12ae",
+ "\u12b0",
+ "\u12b2"-"\u12b5",
+ "\u12b8"-"\u12be",
+ "\u12c0",
+ "\u12c2"-"\u12c5",
+ "\u12c8"-"\u12ce",
+ "\u12d0"-"\u12d6",
+ "\u12d8"-"\u12ee",
+ "\u12f0"-"\u130e",
+ "\u1310",
+ "\u1312"-"\u1315",
+ "\u1318"-"\u131e",
+ "\u1320"-"\u1346",
+ "\u1348"-"\u135a",
+ "\u1369"-"\u1371",
+ "\u13a0"-"\u13f4",
+ "\u1401"-"\u166c",
+ "\u166f"-"\u1676",
+ "\u1681"-"\u169a",
+ "\u16a0"-"\u16ea",
+ "\u16ee"-"\u16f0",
+ "\u1700"-"\u170c",
+ "\u170e"-"\u1714",
+ "\u1720"-"\u1734",
+ "\u1740"-"\u1753",
+ "\u1760"-"\u176c",
+ "\u176e"-"\u1770",
+ "\u1772"-"\u1773",
+ "\u1780"-"\u17d3",
+ "\u17d7",
+ "\u17db"-"\u17dd",
+ "\u17e0"-"\u17e9",
+ "\u180b"-"\u180d",
+ "\u1810"-"\u1819",
+ "\u1820"-"\u1877",
+ "\u1880"-"\u18a9",
+ "\u1900"-"\u191c",
+ "\u1920"-"\u192b",
+ "\u1930"-"\u193b",
+ "\u1946"-"\u196d",
+ "\u1970"-"\u1974",
+ "\u1d00"-"\u1d6b",
+ "\u1e00"-"\u1e9b",
+ "\u1ea0"-"\u1ef9",
+ "\u1f00"-"\u1f15",
+ "\u1f18"-"\u1f1d",
+ "\u1f20"-"\u1f45",
+ "\u1f48"-"\u1f4d",
+ "\u1f50"-"\u1f57",
+ "\u1f59",
+ "\u1f5b",
+ "\u1f5d",
+ "\u1f5f"-"\u1f7d",
+ "\u1f80"-"\u1fb4",
+ "\u1fb6"-"\u1fbc",
+ "\u1fbe",
+ "\u1fc2"-"\u1fc4",
+ "\u1fc6"-"\u1fcc",
+ "\u1fd0"-"\u1fd3",
+ "\u1fd6"-"\u1fdb",
+ "\u1fe0"-"\u1fec",
+ "\u1ff2"-"\u1ff4",
+ "\u1ff6"-"\u1ffc",
+ "\u200c"-"\u200f",
+ "\u202a"-"\u202e",
+ "\u203f"-"\u2040",
+ "\u2054",
+ "\u2060"-"\u2063",
+ "\u206a"-"\u206f",
+ "\u2071",
+ "\u207f",
+ "\u20a0"-"\u20b1",
+ "\u20d0"-"\u20dc",
+ "\u20e1",
+ "\u20e5"-"\u20ea",
+ "\u2102",
+ "\u2107",
+ "\u210a"-"\u2113",
+ "\u2115",
+ "\u2119"-"\u211d",
+ "\u2124",
+ "\u2126",
+ "\u2128",
+ "\u212a"-"\u212d",
+ "\u212f"-"\u2131",
+ "\u2133"-"\u2139",
+ "\u213d"-"\u213f",
+ "\u2145"-"\u2149",
+ "\u2160"-"\u2183",
+ "\u3005"-"\u3007",
+ "\u3021"-"\u302f",
+ "\u3031"-"\u3035",
+ "\u3038"-"\u303c",
+ "\u3041"-"\u3096",
+ "\u3099"-"\u309a",
+ "\u309d"-"\u309f",
+ "\u30a1"-"\u30ff",
+ "\u3105"-"\u312c",
+ "\u3131"-"\u318e",
+ "\u31a0"-"\u31b7",
+ "\u31f0"-"\u31ff",
+ "\u3400"-"\u4db5",
+ "\u4e00"-"\u9fa5",
+ "\ua000"-"\ua48c",
+ "\uac00"-"\ud7a3",
+ "\ud801", //for supplementary characters suport
+ "\ud802", //for supplementary characters suport
+ "\ud834", //for supplementary characters suport
+ "\udc00", //for supplementary characters suport
+ "\udc01", //for supplementary characters suport
+ "\udd7b", //for supplementary characters suport
+ "\uf900"-"\ufa2d",
+ "\ufa30"-"\ufa6a",
+ "\ufb00"-"\ufb06",
+ "\ufb13"-"\ufb17",
+ "\ufb1d"-"\ufb28",
+ "\ufb2a"-"\ufb36",
+ "\ufb38"-"\ufb3c",
+ "\ufb3e",
+ "\ufb40"-"\ufb41",
+ "\ufb43"-"\ufb44",
+ "\ufb46"-"\ufbb1",
+ "\ufbd3"-"\ufd3d",
+ "\ufd50"-"\ufd8f",
+ "\ufd92"-"\ufdc7",
+ "\ufdf0"-"\ufdfc",
+ "\ufe00"-"\ufe0f",
+ "\ufe20"-"\ufe23",
+ "\ufe33"-"\ufe34",
+ "\ufe4d"-"\ufe4f",
+ "\ufe69",
+ "\ufe70"-"\ufe74",
+ "\ufe76"-"\ufefc",
+ "\ufeff",
+ "\uff04",
+ "\uff10"-"\uff19",
+ "\uff21"-"\uff3a",
+ "\uff3f",
+ "\uff41"-"\uff5a",
+ "\uff65"-"\uffbe",
+ "\uffc2"-"\uffc7",
+ "\uffca"-"\uffcf",
+ "\uffd2"-"\uffd7",
+ "\uffda"-"\uffdc",
+ "\uffe0"-"\uffe1",
+ "\uffe5"-"\uffe6",
+ "\ufff9"-"\ufffb"
+ ]
+ >
+}
+
+/* SEPARATORS */
+
+TOKEN :
+{
+ < LPAREN: "(" >
+| < RPAREN: ")" >
+| < LBRACE: "{" >
+| < RBRACE: "}" >
+| < LBRACKET: "[" >
+| < RBRACKET: "]" >
+| < SEMICOLON: ";" >
+| < COMMA: "," >
+| < DOT: "." >
+| < AT: "@" >
+}
+
+/* OPERATORS */
+
+TOKEN :
+{
+ < ASSIGN: "=" >
+| < LT: "<" >
+| < BANG: "!" >
+| < TILDE: "~" >
+| < HOOK: "?" >
+| < COLON: ":" >
+| < EQ: "==" >
+| < LE: "<=" >
+| < GE: ">=" >
+| < NE: "!=" >
+| < SC_OR: "||" >
+| < SC_AND: "&&" >
+| < INCR: "++" >
+| < DECR: "--" >
+| < PLUS: "+" >
+| < MINUS: "-" >
+| < STAR: "*" >
+| < SLASH: "/" >
+| < BIT_AND: "&" >
+| < BIT_OR: "|" >
+| < XOR: "^" >
+| < REM: "%" >
+| < LSHIFT: "<<" >
+| < PLUSASSIGN: "+=" >
+| < MINUSASSIGN: "-=" >
+| < STARASSIGN: "*=" >
+| < SLASHASSIGN: "/=" >
+| < ANDASSIGN: "&=" >
+| < ORASSIGN: "|=" >
+| < XORASSIGN: "^=" >
+| < REMASSIGN: "%=" >
+| < LSHIFTASSIGN: "<<=" >
+| < RSIGNEDSHIFTASSIGN: ">>=" >
+| < RUNSIGNEDSHIFTASSIGN: ">>>=" >
+| < ELLIPSIS: "..." >
+}
+
+/* >'s need special attention due to generics syntax. */
+TOKEN :
+{
+ < RUNSIGNEDSHIFT: ">>>" >
+ {
+ matchedToken.kind = GT;
+ ((ASTParser.GTToken)matchedToken).realKind = RUNSIGNEDSHIFT;
+ input_stream.backup(2);
+ }
+| < RSIGNEDSHIFT: ">>" >
+ {
+ matchedToken.kind = GT;
+ ((ASTParser.GTToken)matchedToken).realKind = RSIGNEDSHIFT;
+ input_stream.backup(1);
+ }
+| < GT: ">" >
+}
+
+
+/*****************************************
+ * THE JAVA LANGUAGE GRAMMAR STARTS HERE *
+ *****************************************/
+
+/*
+ * Program structuring syntax follows.
+ */
+
+CompilationUnit CompilationUnit():
+{
+ PackageDeclaration pakage = null;
+ List imports = null;
+ ImportDeclaration in = null;
+ List types = null;
+ TypeDeclaration tn = null;
+ int line = -1;
+ int column = 0;
+}
+{
+ [ LOOKAHEAD(PackageDeclaration()) pakage = PackageDeclaration() {line = pakage.getBeginLine(); column = pakage.getBeginColumn();} ]
+ ( in = ImportDeclaration() { if(line==-1){line = in.getBeginLine(); column = in.getBeginColumn();} imports = add(imports, in); } )*
+ ( tn = TypeDeclaration() { if(line==-1){line = tn.getBeginLine(); column = tn.getBeginColumn();} types = add(types, tn); } )*
+ (<EOF> | "\u001A" /** ctrl+z char **/)
+ { return new CompilationUnit(line == -1 ? 0 : line, column, token.endLine, token.endColumn,pakage, imports, types, getComments()); }
+}
+
+PackageDeclaration PackageDeclaration():
+{
+ List annotations = null;
+ AnnotationExpr ann;
+ NameExpr name;
+ int line;
+ int column;
+}
+{
+( ann = Annotation() { annotations = add(annotations, ann); } )*
+ "package" {line=token.beginLine; column=token.beginColumn;} name = Name() ";"
+ { return new PackageDeclaration(line, column, token.endLine, token.endColumn,annotations, name); }
+}
+
+ImportDeclaration ImportDeclaration():
+{
+ NameExpr name;
+ boolean isStatic = false;
+ boolean isAsterisk = false;
+ int line;
+ int column;
+}
+{
+ "import" {line=token.beginLine; column=token.beginColumn;} [ "static" { isStatic = true; } ] name = Name() [ "." "*" { isAsterisk = true; } ] ";"
+ { return new ImportDeclaration(line, column, token.endLine, token.endColumn,name, isStatic, isAsterisk); }
+}
+
+/*
+ * Modifiers. We match all modifiers in a single rule to reduce the chances of
+ * syntax errors for simple modifier mistakes. It will also enable us to give
+ * better error messages.
+ */
+
+Modifier Modifiers():
+{
+ int beginLine = -1;
+ int beginColumn = -1;
+ int modifiers = 0;
+ List annotations = null;
+ AnnotationExpr ann;
+}
+{
+ (
+ LOOKAHEAD(2)
+ (
+ "public" { modifiers = addModifier(modifiers, ModifierSet.PUBLIC, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ "static" { modifiers = addModifier(modifiers, ModifierSet.STATIC, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ "protected" { modifiers = addModifier(modifiers, ModifierSet.PROTECTED, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ "private" { modifiers = addModifier(modifiers, ModifierSet.PRIVATE, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ "final" { modifiers = addModifier(modifiers, ModifierSet.FINAL, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ "abstract" { modifiers = addModifier(modifiers, ModifierSet.ABSTRACT, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ "synchronized" { modifiers = addModifier(modifiers, ModifierSet.SYNCHRONIZED, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ "native" { modifiers = addModifier(modifiers, ModifierSet.NATIVE, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ "transient" { modifiers = addModifier(modifiers, ModifierSet.TRANSIENT, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ "volatile" { modifiers = addModifier(modifiers, ModifierSet.VOLATILE, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ "strictfp" { modifiers = addModifier(modifiers, ModifierSet.STRICTFP, token); if(beginLine==-1) {beginLine=token.beginLine; beginColumn=token.beginColumn;} }
+ |
+ ann = Annotation() { annotations = add(annotations, ann); if(beginLine==-1) {beginLine=ann.getBeginLine(); beginColumn=ann.getBeginColumn();} }
+ )
+ )*
+
+ {
+ return new Modifier(beginLine, beginColumn, modifiers, annotations);
+ }
+}
+
+/*
+ * Declaration syntax follows.
+ */
+TypeDeclaration TypeDeclaration():
+{
+ Modifier modifier;
+ TypeDeclaration ret;
+}
+{
+ { pushJavadoc(); }
+ (
+ ";" { ret = new EmptyTypeDeclaration(token.beginLine, token.beginColumn, token.endLine, token.endColumn, popJavadoc()); }
+ |
+ modifier = Modifiers()
+ (
+ ret = ClassOrInterfaceDeclaration(modifier)
+ |
+ ret = EnumDeclaration(modifier)
+ |
+ ret = AnnotationTypeDeclaration(modifier)
+ )
+ )
+ { return ret; }
+}
+
+
+ClassOrInterfaceDeclaration ClassOrInterfaceDeclaration(Modifier modifier):
+{
+ boolean isInterface = false;
+ String name;
+ List typePar = null;
+ List extList = null;
+ List impList = null;
+ List members;
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+}
+{
+ ( "class" | "interface" { isInterface = true; } ) { if (line == -1) {line=token.beginLine; column=token.beginColumn;} }
+ <IDENTIFIER> { name = token.image; }
+ [ typePar = TypeParameters() {typePar.remove(0);} ]
+ [ extList = ExtendsList(isInterface) ]
+ [ impList = ImplementsList(isInterface) ]
+ members = ClassOrInterfaceBody(isInterface)
+
+ { return new ClassOrInterfaceDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), modifier.modifiers, modifier.annotations, isInterface, name, typePar, extList, impList, members); }
+}
+
+List ExtendsList(boolean isInterface):
+{
+ boolean extendsMoreThanOne = false;
+ List ret = new LinkedList();
+ ClassOrInterfaceType cit;
+}
+{
+ "extends" cit = ClassOrInterfaceType() { ret.add(cit); }
+ ( "," cit = ClassOrInterfaceType() { ret.add(cit); extendsMoreThanOne = true; } )*
+ {
+ if (extendsMoreThanOne && !isInterface)
+ throwParseException(token, "A class cannot extend more than one other class");
+ }
+ { return ret; }
+}
+
+List ImplementsList(boolean isInterface):
+{
+ List ret = new LinkedList();
+ ClassOrInterfaceType cit;
+}
+{
+ "implements" cit = ClassOrInterfaceType() { ret.add(cit); }
+ ( "," cit = ClassOrInterfaceType() { ret.add(cit); } )*
+ {
+ if (isInterface)
+ throwParseException(token, "An interface cannot implement other interfaces");
+ }
+ { return ret; }
+}
+
+EnumDeclaration EnumDeclaration(Modifier modifier):
+{
+ String name;
+ List impList = null;
+ EnumConstantDeclaration entry;
+ List entries = null;
+ BodyDeclaration member;
+ List members = null;
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+}
+{
+ "enum" { if (line == -1) {line=token.beginLine; column=token.beginColumn;} }
+ <IDENTIFIER> { name = token.image; }
+ [ impList = ImplementsList(false) ]
+ "{"
+ [
+ { entries = new LinkedList(); }
+ entry = EnumConstantDeclaration() { entries.add(entry); } ( LOOKAHEAD(2) "," entry = EnumConstantDeclaration() { entries.add(entry); } )*
+ ]
+ [ "," ]
+ [
+ ( ";" ( member = ClassOrInterfaceBodyDeclaration(false) { members = add(members, member); } )* )
+ ]
+ "}"
+
+ { return new EnumDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), modifier.modifiers, modifier.annotations, name, impList, entries, members); }
+}
+
+
+EnumConstantDeclaration EnumConstantDeclaration():
+{
+ List annotations = null;
+ AnnotationExpr ann;
+ String name;
+ List args = null;
+ List classBody = null;
+ int line = -1;
+ int column = -1;
+}
+{
+ { pushJavadoc(); }
+ ( ann = Annotation() { annotations = add(annotations, ann); if(line==-1){line=ann.getBeginLine(); column=ann.getBeginColumn();} } )*
+ <IDENTIFIER> { name = token.image; if(line==-1){line=token.beginLine; column=token.beginColumn;} }
+ [ args = Arguments() ] [ classBody = ClassOrInterfaceBody(false) ]
+ { return new EnumConstantDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), annotations, name, args, classBody); }
+}
+
+List TypeParameters():
+{
+ List ret = new LinkedList();
+ TypeParameter tp;
+}
+{
+ "<" { ret.add(new int[]{token.beginLine, token.beginColumn}); }
+ tp = TypeParameter() { ret.add(tp); }
+ ( "," tp = TypeParameter() { ret.add(tp); } )*
+ ">"
+ { return ret; }
+}
+
+TypeParameter TypeParameter():
+{
+ String name;
+ List typeBound = null;
+ int line;
+ int column;
+}
+{
+ <IDENTIFIER> { name = token.image; line=token.beginLine; column=token.beginColumn;} [ typeBound = TypeBound() ]
+ { return new TypeParameter(line, column, token.endLine, token.endColumn,name, typeBound); }
+}
+
+List TypeBound():
+{
+ List ret = new LinkedList();
+ ClassOrInterfaceType cit;
+}
+{
+ "extends" cit = ClassOrInterfaceType() { ret.add(cit); }
+ ( "&" cit = ClassOrInterfaceType() { ret.add(cit); } )*
+ { return ret; }
+}
+
+List ClassOrInterfaceBody(boolean isInterface):
+{
+ List ret = new LinkedList();
+ BodyDeclaration member;
+}
+{
+ "{" ( member = ClassOrInterfaceBodyDeclaration(isInterface) { ret.add(member); } )* "}"
+ { return ret; }
+}
+
+BodyDeclaration ClassOrInterfaceBodyDeclaration(boolean isInterface):
+{
+ boolean isNestedInterface = false;
+ Modifier modifier;
+ BodyDeclaration ret;
+}
+{
+ { pushJavadoc(); }
+ (
+ LOOKAHEAD(2)
+ ret = InitializerDeclaration()
+ {
+ if (isInterface)
+ throwParseException(token, "An interface cannot have initializers");
+ }
+ |
+ modifier = Modifiers() // Just get all the modifiers out of the way. If you want to do
+ // more checks, pass the modifiers down to the member
+ (
+ ret = ClassOrInterfaceDeclaration(modifier)
+ |
+ ret = EnumDeclaration(modifier)
+ |
+ ret = AnnotationTypeDeclaration(modifier)
+ |
+ LOOKAHEAD( [ TypeParameters() ] <IDENTIFIER> "(" )
+ ret = ConstructorDeclaration(modifier)
+ |
+ LOOKAHEAD( Type() <IDENTIFIER> ( "[" "]" )* ( "," | "=" | ";" ) )
+ ret = FieldDeclaration(modifier)
+ |
+ ret = MethodDeclaration(modifier)
+ )
+ |
+ ";" { ret = new EmptyMemberDeclaration(token.beginLine, token.beginColumn, token.endLine, token.endColumn, popJavadoc()); }
+ )
+ { return ret; }
+}
+
+FieldDeclaration FieldDeclaration(Modifier modifier):
+{
+ Type type;
+ List variables = new LinkedList();
+ VariableDeclarator val;
+}
+{
+ // Modifiers are already matched in the caller
+ type = Type()
+ val = VariableDeclarator() { variables.add(val); }
+ ( "," val = VariableDeclarator() { variables.add(val); } )* ";"
+
+ {
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ if (line == -1) { line=type.getBeginLine(); column=type.getBeginColumn(); }
+ return new FieldDeclaration(line, column, token.endLine, token.endColumn, popJavadoc(), modifier.modifiers, modifier.annotations, type, variables);
+ }
+}
+
+VariableDeclarator VariableDeclarator():
+{
+ VariableDeclaratorId id;
+ Expression init = null;
+}
+{
+ id = VariableDeclaratorId() [ "=" init = VariableInitializer() ]
+ { return new VariableDeclarator(id.getBeginLine(), id.getBeginColumn(), token.endLine, token.endColumn, id, init); }
+}
+
+VariableDeclaratorId VariableDeclaratorId():
+{
+ String name;
+ int arrayCount = 0;
+ int line;
+ int column;
+}
+{
+ <IDENTIFIER> { name = token.image; line=token.beginLine; column=token.beginColumn;} ( "[" "]" { arrayCount++; } )*
+ { return new VariableDeclaratorId(line, column, token.endLine, token.endColumn,name, arrayCount); }
+}
+
+Expression VariableInitializer():
+{
+ Expression ret;
+}
+{
+ (
+ ret = ArrayInitializer()
+ |
+ ret = Expression()
+ )
+ { return ret;}
+}
+
+ArrayInitializerExpr ArrayInitializer():
+{
+ List values = null;
+ Expression val;
+ int line;
+ int column;
+}
+{
+ "{" {line=token.beginLine; column=token.beginColumn;} [ val = VariableInitializer() { values = add(values, val); } ( LOOKAHEAD(2) "," val = VariableInitializer() { values = add(values, val); } )* ] [ "," ] "}"
+ { return new ArrayInitializerExpr(line, column, token.endLine, token.endColumn,values); }
+}
+
+MethodDeclaration MethodDeclaration(Modifier modifier):
+{
+ List typeParameters = null;
+ Type type;
+ String name;
+ List parameters;
+ int arrayCount = 0;
+ List throws_ = null;
+ BlockStmt block = null;
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+}
+{
+ // Modifiers already matched in the caller!
+ [ typeParameters = TypeParameters() { int[] lineCol=(int[])typeParameters.remove(0); if(line==-1){ line=lineCol[0]; column=lineCol[1];} } ]
+ type = ResultType() { if(line==-1){line=type.getBeginLine(); column=type.getBeginColumn();}}
+ <IDENTIFIER> { name = token.image; } parameters = FormalParameters() ( "[" "]" { arrayCount++; } )*
+ [ "throws" throws_ = NameList() ]
+ ( block = Block() | ";" )
+
+ { return new MethodDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), modifier.modifiers, modifier.annotations, typeParameters, type, name, parameters, arrayCount, throws_, block); }
+}
+
+List FormalParameters():
+{
+ List ret = null;
+ Parameter par;
+}
+{
+ "(" [ par = FormalParameter() { ret = add(ret, par); } ( "," par = FormalParameter() { ret = add(ret, par); } )* ] ")"
+
+ { return ret; }
+}
+
+Parameter FormalParameter():
+{
+ Modifier modifier;
+ Type type;
+ boolean isVarArg = false;
+ VariableDeclaratorId id;
+}
+{
+ modifier = Modifiers() type = Type() [ "..." { isVarArg = true;} ] id = VariableDeclaratorId()
+
+ {
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ if(line==-1){ line=type.getBeginLine(); column=type.getBeginColumn(); }
+ return new Parameter(line, column, token.endLine, token.endColumn, modifier.modifiers, modifier.annotations, type, isVarArg, id);
+ }
+}
+
+ConstructorDeclaration ConstructorDeclaration(Modifier modifier):
+{
+ List typeParameters = null;
+ String name;
+ List parameters;
+ List throws_ = null;
+ ExplicitConstructorInvocationStmt exConsInv = null;
+ List stmts;
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ int bbLine = 0;
+ int bbColumn = 0;
+ int beLine = 0;
+ int beColumn = 0;
+}
+{
+ [ typeParameters = TypeParameters() { int[] lineCol=(int[])typeParameters.remove(0); if(line==-1){ line=lineCol[0]; column=lineCol[1];} } ]
+ // Modifiers matched in the caller
+ <IDENTIFIER> { name = token.image; if(line==-1){line=token.beginLine; column=token.beginColumn;}} parameters = FormalParameters() [ "throws" throws_ = NameList() ]
+ "{" { bbLine=token.beginLine; bbColumn=token.beginColumn; }
+ [ LOOKAHEAD(ExplicitConstructorInvocation()) exConsInv = ExplicitConstructorInvocation() ]
+ stmts = Statements()
+ "}"
+
+ {
+ if (exConsInv != null) {
+ stmts = add(0, stmts, exConsInv);
+ }
+ return new ConstructorDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), modifier.modifiers, modifier.annotations, typeParameters, name, parameters, throws_, new BlockStmt(bbLine, bbColumn, token.endLine, token.endColumn, stmts));
+ }
+}
+
+ExplicitConstructorInvocationStmt ExplicitConstructorInvocation():
+{
+ boolean isThis = false;
+ List args;
+ Expression expr = null;
+ List typeArgs = null;
+ int line = -1;
+ int column = 0;
+}
+{
+ (
+ LOOKAHEAD([ TypeArguments() ] "this" "(")
+ [ typeArgs = TypeArguments() { int[] lineCol=(int[])typeArgs.remove(0); line=lineCol[0]; column=lineCol[1]; } ]
+ "this" { if (line == -1) {line=token.beginLine; column=token.beginColumn;} isThis = true; }
+ args = Arguments() ";"
+ |
+ [
+ LOOKAHEAD( PrimaryExpressionWithoutSuperSuffix() "." )
+ expr = PrimaryExpressionWithoutSuperSuffix() "."
+ { line=expr.getBeginLine(); column=expr.getBeginColumn(); }
+ ]
+ [ typeArgs = TypeArguments() {int[] lineCol=(int[])typeArgs.remove(0); if (line == -1) {line=lineCol[0]; column=lineCol[1];}} ]
+ "super" {if (line == -1) {line=token.beginLine; column=token.beginColumn;}}
+ args = Arguments() ";"
+ )
+ { return new ExplicitConstructorInvocationStmt(line, column, token.endLine, token.endColumn,typeArgs, isThis, expr, args); }
+}
+
+List Statements():
+{
+ List ret = null;
+ Statement stmt;
+}
+{
+ ( stmt = BlockStatement() { ret = add(ret, stmt); } )*
+ { return ret; }
+}
+
+InitializerDeclaration InitializerDeclaration():
+{
+ BlockStmt block;
+ int line = -1;
+ int column = 0;
+ boolean isStatic = false;
+}
+{
+ [ "static" { isStatic = true; line=token.beginLine; column=token.beginColumn;} ] block = Block() {if(line==-1){line=block.getBeginLine(); column=block.getBeginColumn();}}
+ { return new InitializerDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), isStatic, block); }
+}
+
+
+/*
+ * Type, name and expression syntax follows.
+ */
+
+Type Type():
+{
+ Type ret;
+}
+{
+ (
+ LOOKAHEAD(2) ret = ReferenceType()
+ |
+ ret = PrimitiveType()
+ )
+ { return ret; }
+}
+
+ReferenceType ReferenceType():
+{
+ Type type;
+ int arrayCount = 0;
+}
+{
+ (
+ type = PrimitiveType() ( LOOKAHEAD(2) "[" "]" { arrayCount++; } )+
+ |
+ type = ClassOrInterfaceType() ( LOOKAHEAD(2) "[" "]" { arrayCount++; } )*
+ )
+ { return new ReferenceType(type.getBeginLine(), type.getBeginColumn(), token.endLine, token.endColumn, type, arrayCount); }
+}
+
+ClassOrInterfaceType ClassOrInterfaceType():
+{
+ ClassOrInterfaceType ret;
+ String name;
+ List typeArgs = null;
+ int line;
+ int column;
+}
+{
+ <IDENTIFIER> {line=token.beginLine; column=token.beginColumn;} { name = token.image; }
+ [ LOOKAHEAD(2) typeArgs = TypeArguments() {typeArgs.remove(0);} ]
+ { ret = new ClassOrInterfaceType(line, column, token.endLine, token.endColumn,null, name, typeArgs); }
+ (
+ LOOKAHEAD(2) "." <IDENTIFIER> { name = token.image; }
+ [ LOOKAHEAD(2) typeArgs = TypeArguments() {typeArgs.remove(0);} ] { ret = new ClassOrInterfaceType(line, column, token.endLine, token.endColumn,ret, name, typeArgs); }
+ )*
+ { return ret; }
+}
+
+List TypeArguments():
+{
+ List ret = new LinkedList();
+ Type type;
+}
+{
+ "<" { ret.add(new int[]{token.beginLine, token.beginColumn}); }
+ type = TypeArgument() { ret.add(type); } ( "," type = TypeArgument() { ret.add(type); } )*
+ ">"
+ { return ret; }
+}
+
+Type TypeArgument():
+{
+ Type ret;
+}
+{
+ (
+ ret = ReferenceType()
+ |
+ ret = Wildcard()
+ )
+ { return ret; }
+}
+
+WildcardType Wildcard():
+{
+ ReferenceType ext = null;
+ ReferenceType sup = null;
+ int line;
+ int column;
+}
+{
+ "?" {line=token.beginLine; column=token.beginColumn;}
+ [
+ "extends" ext = ReferenceType()
+ |
+ "super" sup = ReferenceType()
+ ]
+ { return new WildcardType(line, column, token.endLine, token.endColumn,ext, sup); }
+}
+
+PrimitiveType PrimitiveType():
+{
+ PrimitiveType ret;
+}
+{
+(
+ "boolean" { ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Boolean); }
+|
+ "char" { ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Char); }
+|
+ "byte" { ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Byte); }
+|
+ "short" { ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Short); }
+|
+ "int" { ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Int); }
+|
+ "long" { ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Long); }
+|
+ "float" { ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Float); }
+|
+ "double" { ret = new PrimitiveType(token.beginLine, token.beginColumn, token.endLine, token.endColumn, PrimitiveType.Primitive.Double); }
+)
+{ return ret; }
+}
+
+Type ResultType():
+{
+ Type ret;
+}
+{
+ (
+ "void" { ret = new VoidType(token.beginLine, token.beginColumn, token.endLine, token.endColumn); }
+ |
+ ret = Type()
+ )
+ { return ret; }
+}
+
+NameExpr Name():
+/*
+ * A lookahead of 2 is required below since "Name" can be followed
+ * by a ".*" when used in the context of an "ImportDeclaration".
+ */
+{
+ NameExpr ret;
+}
+{
+ <IDENTIFIER> { ret = new NameExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image); }
+ ( LOOKAHEAD(2) "." <IDENTIFIER> { ret = new QualifiedNameExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, token.image); } )*
+ { return ret; }
+}
+
+List NameList():
+{
+ List ret = new LinkedList();
+ NameExpr name;
+}
+{
+ name = Name() { ret.add(name); } ( "," name = Name() { ret.add(name); } )*
+
+ { return ret; }
+}
+
+
+/*
+ * Expression syntax follows.
+ */
+
+Expression Expression():
+/*
+ * This expansion has been written this way instead of:
+ * Assignment() | ConditionalExpression()
+ * for performance reasons.
+ * However, it is a weakening of the grammar for it allows the LHS of
+ * assignments to be any conditional expression whereas it can only be
+ * a primary expression. Consider adding a semantic predicate to work
+ * around this.
+ */
+{
+ Expression ret;
+ AssignExpr.Operator op;
+ Expression value;
+}
+{
+ ret = ConditionalExpression()
+ [
+ LOOKAHEAD(2)
+ op = AssignmentOperator() value = Expression() { ret = new AssignExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, value, op); }
+ ]
+ { return ret; }
+}
+
+AssignExpr.Operator AssignmentOperator():
+{
+ AssignExpr.Operator ret;
+}
+{
+ (
+ "=" { ret = AssignExpr.Operator.assign; }
+ | "*=" { ret = AssignExpr.Operator.star; }
+ | "/=" { ret = AssignExpr.Operator.slash; }
+ | "%=" { ret = AssignExpr.Operator.rem; }
+ | "+=" { ret = AssignExpr.Operator.plus; }
+ | "-=" { ret = AssignExpr.Operator.minus; }
+ | "<<=" { ret = AssignExpr.Operator.lShift; }
+ | ">>=" { ret = AssignExpr.Operator.rSignedShift; }
+ | ">>>=" { ret = AssignExpr.Operator.rUnsignedShift; }
+ | "&=" { ret = AssignExpr.Operator.and; }
+ | "^=" { ret = AssignExpr.Operator.xor; }
+ | "|=" { ret = AssignExpr.Operator.or; }
+ )
+ { return ret; }
+}
+
+Expression ConditionalExpression():
+{
+ Expression ret;
+ Expression left;
+ Expression right;
+}
+{
+ ret = ConditionalOrExpression()
+ [ "?" left = Expression() ":" right = ConditionalExpression() { ret = new ConditionalExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, left, right); } ]
+ { return ret; }
+}
+
+Expression ConditionalOrExpression():
+{
+ Expression ret;
+ Expression right;
+}
+{
+ ret = ConditionalAndExpression() ( "||" right = ConditionalAndExpression() { ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, BinaryExpr.Operator.or); } )*
+ { return ret; }
+}
+
+Expression ConditionalAndExpression():
+{
+ Expression ret;
+ Expression right;
+}
+{
+ ret = InclusiveOrExpression() ( "&&" right = InclusiveOrExpression() { ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, BinaryExpr.Operator.and); } )*
+ { return ret; }
+}
+
+Expression InclusiveOrExpression():
+{
+ Expression ret;
+ Expression right;
+}
+{
+ ret = ExclusiveOrExpression() ( "|" right = ExclusiveOrExpression() { ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, BinaryExpr.Operator.binOr); } )*
+ { return ret; }
+}
+
+Expression ExclusiveOrExpression():
+{
+ Expression ret;
+ Expression right;
+}
+{
+ ret = AndExpression() ( "^" right = AndExpression() { ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, BinaryExpr.Operator.xor); } )*
+ { return ret; }
+}
+
+Expression AndExpression():
+{
+ Expression ret;
+ Expression right;
+}
+{
+ ret = EqualityExpression() ( "&" right = EqualityExpression() { ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, BinaryExpr.Operator.binAnd); } )*
+ { return ret; }
+}
+
+Expression EqualityExpression():
+{
+ Expression ret;
+ Expression right;
+ BinaryExpr.Operator op;
+}
+{
+ ret = InstanceOfExpression()
+ (
+ ( "==" { op = BinaryExpr.Operator.equals; } |
+ "!=" { op = BinaryExpr.Operator.notEquals; }
+ ) right = InstanceOfExpression() { ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, op); }
+ )*
+ { return ret; }
+}
+
+Expression InstanceOfExpression():
+{
+ Expression ret;
+ Type type;
+}
+{
+ ret = RelationalExpression() [ "instanceof" type = Type() { ret = new InstanceOfExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, type); } ]
+ { return ret; }
+}
+
+Expression RelationalExpression():
+{
+ Expression ret;
+ Expression right;
+ BinaryExpr.Operator op;
+}
+{
+ ret = ShiftExpression()
+ (
+ ( "<" { op = BinaryExpr.Operator.less; } |
+ ">" { op = BinaryExpr.Operator.greater; } |
+ "<=" { op = BinaryExpr.Operator.lessEquals; } |
+ ">=" { op = BinaryExpr.Operator.greaterEquals; }
+ ) right = ShiftExpression() { ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, op); }
+ )*
+ { return ret; }
+}
+
+Expression ShiftExpression():
+{
+ Expression ret;
+ Expression right;
+ BinaryExpr.Operator op;
+}
+{
+ ret = AdditiveExpression()
+ (
+ ( "<<" { op = BinaryExpr.Operator.lShift; } |
+ RSIGNEDSHIFT() { op = BinaryExpr.Operator.rSignedShift; } |
+ RUNSIGNEDSHIFT() { op = BinaryExpr.Operator.rUnsignedShift; }
+ ) right = AdditiveExpression() { ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, op); }
+ )*
+ { return ret; }
+}
+
+Expression AdditiveExpression():
+{
+ Expression ret;
+ Expression right;
+ BinaryExpr.Operator op;
+}
+{
+ ret = MultiplicativeExpression()
+ (
+ ( "+" { op = BinaryExpr.Operator.plus; } |
+ "-" { op = BinaryExpr.Operator.minus; }
+ ) right = MultiplicativeExpression() { ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, op); }
+ )*
+ { return ret; }
+}
+
+Expression MultiplicativeExpression():
+{
+ Expression ret;
+ Expression right;
+ BinaryExpr.Operator op;
+}
+{
+ ret = UnaryExpression()
+ (
+ ( "*" { op = BinaryExpr.Operator.times; } |
+ "/" { op = BinaryExpr.Operator.divide; } |
+ "%" { op = BinaryExpr.Operator.remainder; }
+ ) right = UnaryExpression() { ret = new BinaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, right, op); }
+ )*
+ { return ret; }
+}
+
+Expression UnaryExpression():
+{
+ Expression ret;
+ UnaryExpr.Operator op;
+ int line = 0;
+ int column = 0;
+}
+{
+ (
+ ( "+" { op = UnaryExpr.Operator.positive; line=token.beginLine; column=token.beginColumn;} |
+ "-" { op = UnaryExpr.Operator.negative; line=token.beginLine; column=token.beginColumn;}
+ ) ret = UnaryExpression()
+ {
+ if(op == UnaryExpr.Operator.negative) {
+ if (ret instanceof IntegerLiteralExpr && ((IntegerLiteralExpr)ret).isMinValue()) {
+ ret = new IntegerLiteralMinValueExpr(line, column, token.endLine, token.endColumn);
+ } else if (ret instanceof LongLiteralExpr && ((LongLiteralExpr)ret).isMinValue()) {
+ ret = new LongLiteralMinValueExpr(line, column, token.endLine, token.endColumn);
+ } else {
+ ret = new UnaryExpr(line, column, token.endLine, token.endColumn,ret, op);
+ }
+ } else {
+ ret = new UnaryExpr(line, column, token.endLine, token.endColumn,ret, op);
+ }
+ }
+ |
+ ret = PreIncrementExpression()
+ |
+ ret = PreDecrementExpression()
+ |
+ ret = UnaryExpressionNotPlusMinus()
+ )
+ { return ret; }
+}
+
+Expression PreIncrementExpression():
+{
+ Expression ret;
+ int line;
+ int column;
+}
+{
+ "++" {line=token.beginLine; column=token.beginColumn;} ret = PrimaryExpression() { ret = new UnaryExpr(line, column, token.endLine, token.endColumn,ret, UnaryExpr.Operator.preIncrement); }
+ { return ret; }
+}
+
+Expression PreDecrementExpression():
+{
+ Expression ret;
+ int line;
+ int column;
+}
+{
+ "--" {line=token.beginLine; column=token.beginColumn;} ret = PrimaryExpression() { ret = new UnaryExpr(line, column, token.endLine, token.endColumn,ret, UnaryExpr.Operator.preDecrement); }
+ { return ret; }
+}
+
+Expression UnaryExpressionNotPlusMinus():
+{
+ Expression ret;
+ UnaryExpr.Operator op;
+ int line = 0;
+ int column = 0;
+}
+{
+ (
+ ( "~" { op = UnaryExpr.Operator.inverse; line=token.beginLine; column=token.beginColumn;} |
+ "!" { op = UnaryExpr.Operator.not; line=token.beginLine; column=token.beginColumn;}
+ ) ret = UnaryExpression() { ret = new UnaryExpr(line, column, token.endLine, token.endColumn,ret, op); }
+ |
+ LOOKAHEAD( CastLookahead() )
+ ret = CastExpression()
+ |
+ ret = PostfixExpression()
+ )
+ { return ret; }
+}
+
+// This production is to determine lookahead only. The LOOKAHEAD specifications
+// below are not used, but they are there just to indicate that we know about
+// this.
+void CastLookahead():
+{}
+{
+ LOOKAHEAD("(" Type() "[")
+ "(" Type() "[" "]"
+|
+ "(" Type() ")" UnaryExpression()
+}
+
+Expression PostfixExpression():
+{
+ Expression ret;
+ UnaryExpr.Operator op;
+}
+{
+ ret = PrimaryExpression()
+ [
+ LOOKAHEAD(2)
+ ( "++" { op = UnaryExpr.Operator.posIncrement; } |
+ "--" { op = UnaryExpr.Operator.posDecrement; }
+ ) { ret = new UnaryExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, op); }
+ ]
+ { return ret; }
+}
+
+Expression CastExpression():
+{
+ Expression ret;
+ Type type;
+ int line;
+ int column;
+}
+{
+ "(" {line=token.beginLine; column=token.beginColumn;} type = Type() ")" ret = UnaryExpression() { ret = new CastExpr(line, column, token.endLine, token.endColumn,type, ret); }
+ { return ret; }
+}
+
+Expression PrimaryExpression():
+{
+ Expression ret;
+ Expression inner;
+}
+{
+ ret = PrimaryPrefix() ( LOOKAHEAD(2) ret = PrimarySuffix(ret) )*
+ { return ret; }
+}
+
+Expression PrimaryExpressionWithoutSuperSuffix():
+{
+ Expression ret;
+ Expression inner;
+}
+{
+ ret = PrimaryPrefix() ( LOOKAHEAD( PrimarySuffixWithoutSuper(null) ) ret = PrimarySuffixWithoutSuper(ret) )*
+ { return ret; }
+}
+
+Expression PrimaryPrefix():
+{
+ Expression ret;
+ String name;
+ List typeArgs = null;
+ List args = null;
+ boolean hasArgs = false;
+ Type type;
+ int line;
+ int column;
+}
+{
+ (
+ ret = Literal()
+ |
+ "this" { ret = new ThisExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, null); }
+ |
+ "super" { ret = new SuperExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, null); }
+ "."
+ [ typeArgs = TypeArguments() {typeArgs.remove(0);} ]
+ <IDENTIFIER> { name = token.image; }
+ [ args = Arguments() {hasArgs=true;} ]
+ {
+ ret = hasArgs
+ ? new MethodCallExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, typeArgs, name, args)
+ : new FieldAccessExpr(ret.getBeginLine(), ret.getBeginColumn(), token.endLine, token.endColumn, ret, null, name);
+ }
+ |
+ "(" {line=token.beginLine; column=token.beginColumn;} ret = Expression() ")" { ret = new EnclosedExpr(line, column, token.endLine, token.endColumn,ret); }
+ |
+ ret = AllocationExpression(null)
+ |
+ LOOKAHEAD( ResultType() "." "class" )
+ type = ResultType() "." "class" { ret = new ClassExpr(type.getBeginLine(), type.getBeginColumn(), token.endLine, token.endColumn, type); }
+ |
+ <IDENTIFIER> { name = token.image; line=token.beginLine; column=token.beginColumn; }
+ [ args = Arguments() {hasArgs=true;} ]
+ {
+ ret = hasArgs
+ ? new MethodCallExpr(line, column, token.endLine, token.endColumn, null, null, name, args)
+ : new NameExpr(line, column, token.endLine, token.endColumn, name);
+ }
+ )
+ { return ret; }
+}
+
+Expression PrimarySuffix(Expression scope):
+{
+ Expression ret;
+}
+{
+ (
+ LOOKAHEAD(2)
+ ret = PrimarySuffixWithoutSuper(scope)
+ |
+ "." "super" { ret = new SuperExpr(scope.getBeginLine(), scope.getBeginColumn(), token.endLine, token.endColumn, scope); }
+ )
+ { return ret; }
+}
+
+Expression PrimarySuffixWithoutSuper(Expression scope):
+{
+ Expression ret;
+ List typeArgs = null;
+ List args = null;
+ boolean hasArgs = false;
+ String name;
+}
+{
+ (
+ "."
+ (
+ // TODO: está deixando passar "this.this", verificar na JLS se é possível
+ "this" { ret = new ThisExpr(scope.getBeginLine(), scope.getBeginColumn(), token.endLine, token.endColumn, scope); }
+ |
+ ret = AllocationExpression(scope)
+ |
+ LOOKAHEAD( [ TypeArguments() ] <IDENTIFIER> )
+ [ typeArgs = TypeArguments() {typeArgs.remove(0);} ]
+ <IDENTIFIER> { name = token.image; }
+ [ args = Arguments() {hasArgs=true;} ]
+ {
+ ret = hasArgs
+ ? new MethodCallExpr(scope.getBeginLine(), scope.getBeginColumn(), token.endLine, token.endColumn, scope, typeArgs, name, args)
+ : new FieldAccessExpr(scope.getBeginLine(), scope.getBeginColumn(), token.endLine, token.endColumn, scope, typeArgs, name);
+ }
+ )
+ |
+ "["ret = Expression() "]" { ret = new ArrayAccessExpr(scope.getBeginLine(), scope.getBeginColumn(), token.endLine, token.endColumn, scope, ret); }
+ )
+ { return ret; }
+}
+
+Expression Literal():
+{
+ Expression ret;
+}
+{
+ (
+ <INTEGER_LITERAL> {
+ ret = new IntegerLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image);
+ }
+ |
+ <LONG_LITERAL> {
+ ret = new LongLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image);
+ }
+ |
+ <FLOATING_POINT_LITERAL> {
+ ret = new DoubleLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image);
+ }
+ |
+ <CHARACTER_LITERAL> {
+ ret = new CharLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image.substring(1, token.image.length()-1));
+ }
+ |
+ <STRING_LITERAL> {
+ ret = new StringLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, token.image.substring(1, token.image.length()-1));
+ }
+ |
+ ret = BooleanLiteral()
+ |
+ ret = NullLiteral()
+ )
+ { return ret; }
+}
+
+Expression BooleanLiteral():
+{
+ Expression ret;
+}
+{
+ (
+ "true" { ret = new BooleanLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, true); }
+ |
+ "false" { ret = new BooleanLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn, false); }
+ )
+ { return ret; }
+}
+
+Expression NullLiteral():
+{}
+{
+ "null"
+ { return new NullLiteralExpr(token.beginLine, token.beginColumn, token.endLine, token.endColumn); }
+}
+
+List Arguments():
+{
+ List ret = null;
+}
+{
+ "(" [ ret = ArgumentList() ] ")"
+ { return ret; }
+}
+
+List ArgumentList():
+{
+ List ret = new LinkedList();
+ Expression expr;
+}
+{
+ expr = Expression() { ret.add(expr); } ( "," expr = Expression() { ret.add(expr); } )*
+ { return ret; }
+}
+
+Expression AllocationExpression(Expression scope):
+{
+ Expression ret;
+ Type type;
+ Object[] arr = null;
+ List typeArgs = null;
+ List anonymousBody = null;
+ List args;
+ int line;
+ int column;
+}
+{
+ "new" { if(scope==null) {line=token.beginLine; column=token.beginColumn;} else {line=scope.getBeginLine(); column=scope.getBeginColumn();} }
+ (
+ type = PrimitiveType()
+ arr = ArrayDimsAndInits()
+ {
+ if (arr[0] instanceof Integer) {
+ ret = new ArrayCreationExpr(line, column, token.endLine, token.endColumn, type, ((Integer)arr[0]).intValue(), (ArrayInitializerExpr)arr[1]);
+ } else {
+ ret = new ArrayCreationExpr(line, column, token.endLine, token.endColumn, type, (List)arr[0], ((Integer)arr[1]).intValue());
+ }
+ }
+ |
+ LOOKAHEAD(ClassOrInterfaceType() ArrayDimsAndInits())
+ type = ClassOrInterfaceType()
+ arr = ArrayDimsAndInits()
+ {
+ if (arr[0] instanceof Integer) {
+ ret = new ArrayCreationExpr(line, column, token.endLine, token.endColumn, type, ((Integer)arr[0]).intValue(), (ArrayInitializerExpr)arr[1]);
+ } else {
+ ret = new ArrayCreationExpr(line, column, token.endLine, token.endColumn, type, (List)arr[0], ((Integer)arr[1]).intValue());
+ }
+ }
+ |
+ [ typeArgs = TypeArguments() {typeArgs.remove(0);} ]
+ type = ClassOrInterfaceType()
+ args = Arguments() [ LOOKAHEAD(2) anonymousBody = ClassOrInterfaceBody(false) ]
+ { ret = new ObjectCreationExpr(line, column, token.endLine, token.endColumn, scope, (ClassOrInterfaceType) type, typeArgs, args, anonymousBody); }
+ )
+ { return ret; }
+}
+
+/*
+ * The third LOOKAHEAD specification below is to parse to PrimarySuffix
+ * if there is an expression between the "[...]".
+ */
+Object[] ArrayDimsAndInits():
+{
+ Object[] ret = new Object[2];
+ Expression expr;
+ List inits = null;
+ int i = 0;
+}
+{
+ (
+ LOOKAHEAD(2)
+ ( LOOKAHEAD(2) "[" expr = Expression() { inits = add(inits, expr); } "]" )+ ( LOOKAHEAD(2) "[" "]" { i++; } )* { ret[0] = inits; ret[1] = new Integer(i); }
+ |
+ ( "[" "]" { i++; } )+ expr = ArrayInitializer() { ret[0] = new Integer(i); ret[1] = expr; }
+ )
+ { return ret; }
+}
+
+
+/*
+ * Statement syntax follows.
+ */
+
+Statement Statement():
+{
+ Statement ret;
+}
+{
+ (
+ LOOKAHEAD(2)
+ ret = LabeledStatement()
+ |
+ ret = AssertStatement()
+ |
+ ret = Block()
+ |
+ ret = EmptyStatement()
+ |
+ ret = StatementExpression()
+ |
+ ret = SwitchStatement()
+ |
+ ret = IfStatement()
+ |
+ ret = WhileStatement()
+ |
+ ret = DoStatement()
+ |
+ ret = ForStatement()
+ |
+ ret = BreakStatement()
+ |
+ ret = ContinueStatement()
+ |
+ ret = ReturnStatement()
+ |
+ ret = ThrowStatement()
+ |
+ ret = SynchronizedStatement()
+ |
+ ret = TryStatement()
+ )
+ { return ret; }
+}
+
+AssertStmt AssertStatement():
+{
+ Expression check;
+ Expression msg = null;
+ int line;
+ int column;
+}
+{
+ "assert" {line=token.beginLine; column=token.beginColumn;} check = Expression() [ ":" msg = Expression() ] ";"
+ { return new AssertStmt(line, column, token.endLine, token.endColumn,check, msg); }
+}
+
+LabeledStmt LabeledStatement():
+{
+ String label;
+ Statement stmt;
+ int line;
+ int column;
+}
+{
+ <IDENTIFIER> {line=token.beginLine; column=token.beginColumn;} { label = token.image; } ":" stmt = Statement()
+ { return new LabeledStmt(line, column, token.endLine, token.endColumn,label, stmt); }
+}
+
+BlockStmt Block():
+{
+ List stmts;
+ int beginLine;
+ int beginColumn;
+}
+{
+ "{" {beginLine=token.beginLine; beginColumn=token.beginColumn;}
+ stmts = Statements()
+ "}"
+ { return new BlockStmt(beginLine, beginColumn, token.endLine, token.endColumn, stmts); }
+}
+
+/*
+ * Classes inside block stametents can only be abstract or final. The semantic must check it.
+ */
+Statement BlockStatement():
+{
+ Statement ret;
+ Expression expr;
+ ClassOrInterfaceDeclaration typeDecl;
+ Modifier modifier;
+}
+{
+ (
+ LOOKAHEAD( Modifiers() ("class" | "interface") )
+ { pushJavadoc(); }
+ modifier = Modifiers()
+ typeDecl = ClassOrInterfaceDeclaration(modifier) { ret = new TypeDeclarationStmt(typeDecl.getBeginLine(), typeDecl.getBeginColumn(), token.endLine, token.endColumn, typeDecl); }
+ |
+ LOOKAHEAD(VariableDeclarationExpression() )
+ expr = VariableDeclarationExpression() ";"
+ { ret = new ExpressionStmt(expr.getBeginLine(), expr.getBeginColumn(), token.endLine, token.endColumn, expr); }
+ |
+ ret = Statement()
+ )
+ { return ret; }
+}
+
+VariableDeclarationExpr VariableDeclarationExpression():
+{
+ Modifier modifier;
+ Type type;
+ List vars = new LinkedList();
+ VariableDeclarator var;
+}
+{
+ modifier = Modifiers() type = Type() var = VariableDeclarator() { vars.add(var); } ( "," var = VariableDeclarator() { vars.add(var); } )*
+ {
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ if(line==-1) {line=type.getBeginLine(); column=type.getBeginColumn(); }
+ return new VariableDeclarationExpr(line, column, token.endLine, token.endColumn, modifier.modifiers, modifier.annotations, type, vars);
+ }
+}
+
+EmptyStmt EmptyStatement():
+{}
+{
+ ";"
+ { return new EmptyStmt(token.beginLine, token.beginColumn, token.endLine, token.endColumn); }
+}
+
+ExpressionStmt StatementExpression():
+/*
+ * The last expansion of this production accepts more than the legal
+ * Java expansions for StatementExpression. This expansion does not
+ * use PostfixExpression for performance reasons.
+ */
+{
+ Expression expr;
+ AssignExpr.Operator op;
+ Expression value;
+}
+{
+ (
+ expr = PreIncrementExpression()
+ |
+ expr = PreDecrementExpression()
+ |
+ expr = PrimaryExpression()
+ [
+ "++" { expr = new UnaryExpr(expr.getBeginLine(), expr.getBeginColumn(), token.endLine, token.endColumn, expr, UnaryExpr.Operator.posIncrement); }
+ |
+ "--" { expr = new UnaryExpr(expr.getBeginLine(), expr.getBeginColumn(), token.endLine, token.endColumn, expr, UnaryExpr.Operator.posDecrement); }
+ |
+ op = AssignmentOperator() value = Expression() { expr = new AssignExpr(expr.getBeginLine(), expr.getBeginColumn(), token.endLine, token.endColumn, expr, value, op); }
+ ]
+ )
+ ";"
+ { return new ExpressionStmt(expr.getBeginLine(), expr.getBeginColumn(), token.endLine, token.endColumn, expr); }
+}
+
+SwitchStmt SwitchStatement():
+{
+ Expression selector;
+ SwitchEntryStmt entry;
+ List entries = null;
+ int line;
+ int column;
+}
+{
+ "switch" {line=token.beginLine; column=token.beginColumn;} "(" selector = Expression() ")" "{"
+ ( entry = SwitchEntry() { entries = add(entries, entry); } )*
+ "}"
+
+ { return new SwitchStmt(line, column, token.endLine, token.endColumn,selector, entries); }
+}
+
+SwitchEntryStmt SwitchEntry():
+{
+ Expression label = null;
+ List stmts;
+ int line;
+ int column;
+}
+{
+ (
+ "case" {line=token.beginLine; column=token.beginColumn;} label = Expression()
+ |
+ "default" {line=token.beginLine; column=token.beginColumn;}
+ )
+ ":" stmts = Statements()
+
+ { return new SwitchEntryStmt(line, column, token.endLine, token.endColumn,label, stmts); }
+}
+
+IfStmt IfStatement():
+/*
+ * The disambiguating algorithm of JavaCC automatically binds dangling
+ * else's to the innermost if statement. The LOOKAHEAD specification
+ * is to tell JavaCC that we know what we are doing.
+ */
+{
+ Expression condition;
+ Statement thenStmt;
+ Statement elseStmt = null;
+ int line;
+ int column;
+}
+{
+ "if" {line=token.beginLine; column=token.beginColumn;} "(" condition = Expression() ")" thenStmt = Statement() [ LOOKAHEAD(1) "else" elseStmt = Statement() ]
+ { return new IfStmt(line, column, token.endLine, token.endColumn,condition, thenStmt, elseStmt); }
+}
+
+WhileStmt WhileStatement():
+{
+ Expression condition;
+ Statement body;
+ int line;
+ int column;
+}
+{
+ "while" {line=token.beginLine; column=token.beginColumn;} "(" condition = Expression() ")" body = Statement()
+ { return new WhileStmt(line, column, token.endLine, token.endColumn,condition, body); }
+}
+
+DoStmt DoStatement():
+{
+ Expression condition;
+ Statement body;
+ int line;
+ int column;
+}
+{
+ "do" {line=token.beginLine; column=token.beginColumn;} body = Statement() "while" "(" condition = Expression() ")" ";"
+ { return new DoStmt(line, column, token.endLine, token.endColumn,body, condition); }
+}
+
+Statement ForStatement():
+{
+ String id = null;
+ VariableDeclarationExpr varExpr = null;
+ Expression expr = null;
+ List init = null;
+ List update = null;
+ Statement body;
+ int line;
+ int column;
+}
+{
+ "for" {line=token.beginLine; column=token.beginColumn;} "("
+
+ (
+ LOOKAHEAD(VariableDeclarationExpression() ":")
+ varExpr = VariableDeclarationExpression() ":" expr = Expression()
+ |
+ [ init = ForInit() ] ";" [ expr = Expression() ] ";" [ update = ForUpdate() ]
+ )
+
+ ")" body = Statement()
+
+ {
+ if (varExpr != null) {
+ return new ForeachStmt(line, column, token.endLine, token.endColumn,varExpr, expr, body);
+ }
+ return new ForStmt(line, column, token.endLine, token.endColumn,init, expr, update, body);
+ }
+}
+
+List ForInit():
+{
+ List ret;
+ Expression expr;
+}
+{
+ (
+ LOOKAHEAD( Modifiers() Type() <IDENTIFIER> )
+ expr = VariableDeclarationExpression() { ret = new LinkedList(); ret.add(expr); }
+ |
+ ret = ExpressionList()
+ )
+ { return ret; }
+}
+
+List ExpressionList():
+{
+ List ret = new LinkedList();
+ Expression expr;
+}
+{
+ expr = Expression() { ret.add(expr); } ( "," expr = Expression() { ret.add(expr); } )*
+
+ { return ret; }
+}
+
+List ForUpdate():
+{
+ List ret;
+}
+{
+ ret = ExpressionList()
+
+ { return ret; }
+}
+
+BreakStmt BreakStatement():
+{
+ String id = null;
+ int line;
+ int column;
+}
+{
+ "break" {line=token.beginLine; column=token.beginColumn;} [ <IDENTIFIER> { id = token.image; } ] ";"
+ { return new BreakStmt(line, column, token.endLine, token.endColumn,id); }
+}
+
+ContinueStmt ContinueStatement():
+{
+ String id = null;
+ int line;
+ int column;
+}
+{
+ "continue" {line=token.beginLine; column=token.beginColumn;} [ <IDENTIFIER> { id = token.image; } ] ";"
+ { return new ContinueStmt(line, column, token.endLine, token.endColumn,id); }
+}
+
+ReturnStmt ReturnStatement():
+{
+ Expression expr = null;
+ int line;
+ int column;
+}
+{
+ "return" {line=token.beginLine; column=token.beginColumn;} [ expr = Expression() ] ";"
+ { return new ReturnStmt(line, column, token.endLine, token.endColumn,expr); }
+}
+
+ThrowStmt ThrowStatement():
+{
+ Expression expr;
+ int line;
+ int column;
+}
+{
+ "throw" {line=token.beginLine; column=token.beginColumn;} expr = Expression() ";"
+ { return new ThrowStmt(line, column, token.endLine, token.endColumn,expr); }
+}
+
+SynchronizedStmt SynchronizedStatement():
+{
+ Expression expr;
+ BlockStmt block;
+ int line;
+ int column;
+}
+{
+ "synchronized" {line=token.beginLine; column=token.beginColumn;} "(" expr = Expression() ")" block = Block()
+ { return new SynchronizedStmt(line, column, token.endLine, token.endColumn,expr, block); }
+}
+
+TryStmt TryStatement():
+/*
+ * Semantic check required here to make sure that at least one
+ * finally/catch is present.
+ */
+{
+ BlockStmt tryBlock;
+ BlockStmt finallyBlock = null;
+ List catchs = null;
+ Parameter except;
+ BlockStmt catchBlock;
+ int line;
+ int column;
+ int cLine;
+ int cColumn;
+}
+{
+ "try" {line=token.beginLine; column=token.beginColumn;} tryBlock = Block()
+ (
+ (
+ "catch" {cLine=token.beginLine; cColumn=token.beginColumn;}
+ "(" except = FormalParameter() ")" catchBlock = Block()
+ { catchs = add(catchs, new CatchClause(cLine, cColumn, token.endLine, token.endColumn, except, catchBlock)); }
+ )+
+ [ "finally" finallyBlock = Block() ]
+ |
+ "finally" finallyBlock = Block()
+ )
+ { return new TryStmt(line, column, token.endLine, token.endColumn,tryBlock, catchs, finallyBlock); }
+}
+
+
+
+/* We use productions to match >>>, >> and > so that we can keep the
+ * type declaration syntax with generics clean
+ */
+
+void RUNSIGNEDSHIFT():
+{}
+{
+ ( LOOKAHEAD({ getToken(1).kind == GT &&
+ ((GTToken)getToken(1)).realKind == RUNSIGNEDSHIFT} )
+ ">" ">" ">"
+ )
+}
+
+void RSIGNEDSHIFT():
+{}
+{
+ ( LOOKAHEAD({ getToken(1).kind == GT &&
+ ((GTToken)getToken(1)).realKind == RSIGNEDSHIFT} )
+ ">" ">"
+ )
+}
+
+/* Annotation syntax follows. */
+
+AnnotationExpr Annotation():
+{
+ AnnotationExpr ret;
+}
+{
+ (
+ LOOKAHEAD( "@" Name() "(" ( <IDENTIFIER> "=" | ")" ))
+ ret = NormalAnnotation()
+ |
+ LOOKAHEAD( "@" Name() "(" )
+ ret = SingleMemberAnnotation()
+ |
+ ret = MarkerAnnotation()
+ )
+ { return ret; }
+}
+
+NormalAnnotationExpr NormalAnnotation():
+{
+ NameExpr name;
+ List pairs = null;
+ int line;
+ int column;
+}
+{
+ "@" {line=token.beginLine; column=token.beginColumn;} name = Name() "(" [ pairs = MemberValuePairs() ] ")"
+ { return new NormalAnnotationExpr(line, column, token.endLine, token.endColumn,name, pairs); }
+}
+
+MarkerAnnotationExpr MarkerAnnotation():
+{
+ NameExpr name;
+ int line;
+ int column;
+}
+{
+ "@" {line=token.beginLine; column=token.beginColumn;} name = Name()
+ { return new MarkerAnnotationExpr(line, column, token.endLine, token.endColumn,name); }
+}
+
+SingleMemberAnnotationExpr SingleMemberAnnotation():
+{
+ NameExpr name;
+ Expression memberVal;
+ int line;
+ int column;
+}
+{
+ "@" {line=token.beginLine; column=token.beginColumn;} name = Name() "(" memberVal = MemberValue() ")"
+ { return new SingleMemberAnnotationExpr(line, column, token.endLine, token.endColumn,name, memberVal); }
+}
+
+List MemberValuePairs():
+{
+ List ret = new LinkedList();
+ MemberValuePair pair;
+}
+{
+ pair = MemberValuePair() { ret.add(pair); } ( "," pair = MemberValuePair() { ret.add(pair); } )*
+ { return ret; }
+}
+
+MemberValuePair MemberValuePair():
+{
+ String name;
+ Expression value;
+ int line;
+ int column;
+}
+{
+ <IDENTIFIER> { name = token.image; line=token.beginLine; column=token.beginColumn;} "=" value = MemberValue()
+ { return new MemberValuePair(line, column, token.endLine, token.endColumn,name, value); }
+}
+
+Expression MemberValue():
+{
+ Expression ret;
+}
+{
+ (
+ ret = Annotation()
+ |
+ ret = MemberValueArrayInitializer()
+ |
+ ret = ConditionalExpression()
+ )
+ { return ret; }
+}
+
+Expression MemberValueArrayInitializer():
+{
+ List ret = new LinkedList();
+ Expression member;
+ int line;
+ int column;
+}
+{
+ "{" {line=token.beginLine; column=token.beginColumn;}
+ ( member = MemberValue() { ret.add(member); } ( LOOKAHEAD(2) "," member = MemberValue() { ret.add(member); } )* )? [ "," ]
+ "}"
+ { return new ArrayInitializerExpr(line, column, token.endLine, token.endColumn,ret); }
+}
+
+
+/* Annotation Types. */
+
+AnnotationDeclaration AnnotationTypeDeclaration(Modifier modifier):
+{
+ String name;
+ List members;
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+}
+{
+ "@" { if (line == -1) {line=token.beginLine; column=token.beginColumn;} }
+ "interface" <IDENTIFIER> { name = token.image; } members = AnnotationTypeBody()
+
+ { return new AnnotationDeclaration(line, column, token.endLine, token.endColumn,popJavadoc(), modifier.modifiers, modifier.annotations, name, members); }
+}
+
+List AnnotationTypeBody():
+{
+ List ret = null;
+ BodyDeclaration member;
+}
+{
+ "{" ( member = AnnotationBodyDeclaration() { ret = add(ret, member); } )* "}"
+
+ { return ret; }
+}
+
+BodyDeclaration AnnotationBodyDeclaration():
+{
+ Modifier modifier;
+ BodyDeclaration ret;
+}
+{
+ { pushJavadoc(); }
+ (
+ ";" { ret = new EmptyTypeDeclaration(token.beginLine, token.beginColumn, token.endLine, token.endColumn, popJavadoc()); }
+ |
+ modifier = Modifiers()
+ (
+ LOOKAHEAD(Type() <IDENTIFIER> "(")
+ ret = AnnotationTypeMemberDeclaration(modifier)
+ |
+ ret = ClassOrInterfaceDeclaration(modifier)
+ |
+ ret = EnumDeclaration(modifier)
+ |
+ ret = AnnotationTypeDeclaration(modifier)
+ |
+ ret = FieldDeclaration(modifier)
+ )
+ )
+ { return ret; }
+}
+
+AnnotationMemberDeclaration AnnotationTypeMemberDeclaration(Modifier modifier):
+{
+ Type type;
+ String name;
+ Expression defaultVal = null;
+}
+{
+ type = Type() <IDENTIFIER> { name = token.image; } "(" ")" [ defaultVal = DefaultValue() ] ";"
+
+ {
+ int line = modifier.beginLine;
+ int column = modifier.beginColumn;
+ { if (line == -1) {line=type.getBeginLine(); column=type.getBeginColumn();} }
+ return new AnnotationMemberDeclaration(line, column, token.endLine, token.endColumn, popJavadoc(), modifier.modifiers, modifier.annotations, type, name, defaultVal);
+ }
+}
+
+Expression DefaultValue():
+{
+ Expression ret;
+}
+{
+ "default" ret = MemberValue()
+ { return ret; }
+}
diff --git a/components/htmlfive/java/javaparser/test/ignore/TestRunner.java b/components/htmlfive/java/javaparser/test/ignore/TestRunner.java
new file mode 100644
index 000000000..574da0b2e
--- /dev/null
+++ b/components/htmlfive/java/javaparser/test/ignore/TestRunner.java
@@ -0,0 +1,127 @@
+package ignore;
+
+import japa.parser.JavaParser;
+import japa.parser.ParseException;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/*
+ * Created on 14/10/2007
+ */
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public class TestRunner {
+
+ private static final File ROOT = //
+ new File("D:/Downloads/openjdk-7-ea-src-b27-22_may_2008/openjdk/langtools/test/tools/javac" //
+ //"C:/Documents and Settings/jgesser/Desktop/openjdk-7-ea-src-b27-22_may_2008/openjdk" //
+ //"C:/Documents and Settings/jgesser/Desktop/openjdk-6-src-b09-11_apr_2008/jdk" //
+ );
+
+ public static void main(String[] args) {
+ new TestRunner().run();
+ }
+
+ private void visitAllJavaFiles(FileFilter callback, File dir) {
+ File[] listFiles = dir.listFiles(new FileFilter() {
+
+ public boolean accept(File file) {
+ return file.isDirectory() || file.getName().endsWith(".java");
+ }
+
+ });
+ if (listFiles != null) {
+ for (File element : listFiles) {
+ if (element.isDirectory()) {
+ visitAllJavaFiles(callback, element);
+ } else {
+ callback.accept(element);
+ }
+ }
+ }
+ }
+
+ int runCount = 0;
+
+ long runTime = 0;
+
+ public void run() {
+ visitAllJavaFiles(new FileFilter() {
+
+ public boolean accept(File javaFile) {
+ //System.out.println("Visiting file: " + javaFile.getPath());
+ try {
+ runTest(javaFile);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return false;
+ }
+
+ }, ROOT);
+
+ System.out.println("Compiled " + runCount + " in " + runTime + " ms, avarage of " + (((double) runTime) / runCount));
+ }
+
+ private void runTest(File javaFile) throws IOException {
+
+ // try {
+ // JavaParser.parse(javaFile);
+ // } catch (ParseException e) {
+ // System.out.println("<<error>> " + e.getMessage());
+ // }
+
+ StringBuilder buf = new StringBuilder();
+ try {
+ FileInputStream in = new FileInputStream(javaFile);
+ try {
+ int i;
+ while ((i = in.read()) >= 0) {
+ buf.append((char) i);
+ }
+ } finally {
+ in.close();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (buf.indexOf("@test") < 0) {
+ return;
+ // System.out.println("Skiping file: " + javaFile.getPath());
+ }
+ boolean fail = false;
+ // if (buf.indexOf("@compile ") == -1) {
+ // fail = buf.indexOf("compile/fail") >= 0;
+ // }
+ if (!(buf.indexOf("@compile ") >= 0 && buf.indexOf("compile/fail") < 0)) {
+ return;
+ }
+
+ try {
+ //System.out.println("Parsing file: " + javaFile.getPath());
+
+ runCount++;
+ long time = System.currentTimeMillis();
+ JavaParser.parse(javaFile);
+ runTime += System.currentTimeMillis() - time;
+ if (fail) {
+ System.out.println("Testing file: " + javaFile.getPath());
+ System.out.println(" >>Parser error expected but not ocurred");
+ }
+ } catch (ParseException e) {
+ if (!fail) {
+ System.out.println("Testing file: " + javaFile.getPath());
+ System.out.println(" >>Parser error not expected: " + e.getMessage());
+ }
+ } catch (Error e) {
+ System.out.println("Testing file: " + javaFile.getPath());
+ System.out.println(" >>Unknow error: " + e.getMessage());
+ }
+ }
+} \ No newline at end of file
diff --git a/components/htmlfive/java/javaparser/test/japa/parser/ast/test/AllTests.java b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/AllTests.java
new file mode 100644
index 000000000..f0a236833
--- /dev/null
+++ b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/AllTests.java
@@ -0,0 +1,25 @@
+/*
+ * Created on 11/01/2009
+ */
+package japa.parser.ast.test;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public class AllTests {
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite("Test for japa.parser.ast.test");
+ //$JUnit-BEGIN$
+ suite.addTestSuite(TestAdapters.class);
+ suite.addTestSuite(TestNodePositions.class);
+ suite.addTestSuite(TestDumper.class);
+ //$JUnit-END$
+ return suite;
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestAdapters.java b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestAdapters.java
new file mode 100644
index 000000000..7876a970b
--- /dev/null
+++ b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestAdapters.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2008 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 11/06/2008
+ */
+package japa.parser.ast.test;
+
+import japa.parser.ParseException;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.test.classes.DumperTestClass;
+import japa.parser.ast.test.classes.JavadocTestClass;
+import japa.parser.ast.visitor.GenericVisitor;
+import japa.parser.ast.visitor.GenericVisitorAdapter;
+import japa.parser.ast.visitor.ModifierVisitorAdapter;
+import japa.parser.ast.visitor.VoidVisitor;
+import japa.parser.ast.visitor.VoidVisitorAdapter;
+import junit.framework.TestCase;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public class TestAdapters extends TestCase {
+
+ class ConcreteVoidVisitorAdapter extends VoidVisitorAdapter {
+
+ }
+
+ class ConcreteGenericVisitorAdapter extends GenericVisitorAdapter {
+
+ }
+
+ class ConcreteModifierVisitorAdapter extends ModifierVisitorAdapter {
+
+ }
+
+ private void doTest(VoidVisitor< ? > visitor) throws ParseException {
+ CompilationUnit cu = TestHelper.parserClass("./test", DumperTestClass.class);
+ cu.accept(visitor, null);
+
+ cu = TestHelper.parserClass("./test", JavadocTestClass.class);
+ cu.accept(visitor, null);
+ }
+
+ private void doTest(GenericVisitor< ? , ? > visitor) throws ParseException {
+ CompilationUnit cu = TestHelper.parserClass("./test", DumperTestClass.class);
+ cu.accept(visitor, null);
+
+ cu = TestHelper.parserClass("./test", JavadocTestClass.class);
+ cu.accept(visitor, null);
+ }
+
+ public void testVoidVisitorAdapter() throws Exception {
+ doTest(new ConcreteVoidVisitorAdapter());
+ }
+
+ public void testGenericVisitorAdapter() throws Exception {
+ doTest(new ConcreteGenericVisitorAdapter());
+ }
+
+ public void testModifierVisitorAdapter() throws Exception {
+ doTest(new ConcreteModifierVisitorAdapter());
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestDumper.java b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestDumper.java
new file mode 100644
index 000000000..f6fefe187
--- /dev/null
+++ b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestDumper.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 Júlio Vilmar Gesser.
+ *
+ * This file is part of Java 1.5 parser and Abstract Syntax Tree.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Java 1.5 parser and Abstract Syntax Tree is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Java 1.5 parser and Abstract Syntax Tree. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ * Created on 22/11/2006
+ */
+package japa.parser.ast.test;
+
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.test.classes.DumperTestClass;
+import japa.parser.ast.test.classes.JavadocTestClass;
+import junit.framework.TestCase;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public class TestDumper extends TestCase {
+
+ public void testDumpVisitor() throws Exception {
+ String source = TestHelper.readClass("./test", DumperTestClass.class);
+ CompilationUnit cu = TestHelper.parserString(source);
+ assertEquals(source, cu.toString());
+ }
+
+ public void testJavadoc() throws Exception {
+ String source = TestHelper.readClass("./test", JavadocTestClass.class);
+ CompilationUnit cu = TestHelper.parserString(source);
+ assertEquals(source, cu.toString());
+ assertEquals(19, cu.getComments().size());
+ }
+
+ public void testComments() throws Exception {
+ final String source_with_comment = //
+ "package japa.parser.javacc;\n" + //
+ "public class Teste {\n" + //
+ "//line comment\n" + //
+ "int a = 0;" + //
+ "//line comment\r\n" + //
+ "int b = 0;" + //
+ "//line comment\r" + //
+ "int c = 0;" + //
+ "/* multi-line\n comment\n*/" + //
+ "int d = 0;" + //
+ "/** multi-line\r\n javadoc\n*/" + //
+ "int e = 0;" + //
+ "}\n" + //
+ "//final comment" + //
+ "";
+ final String source_without_comment = //
+ "package japa.parser.javacc;\n" + //
+ "\n" + //
+ "public class Teste {\n" + //
+ "\n" + //
+ " int a = 0;\n" + //
+ "\n" + //
+ " int b = 0;\n" + //
+ "\n" + //
+ " int c = 0;\n" + //
+ "\n" + //
+ " int d = 0;\n" + //
+ "\n" + //
+ " /** multi-line\r\n javadoc\n*/\n" + //
+ " int e = 0;\n" + //
+ "}\n" + //
+ "";
+
+ CompilationUnit cu = TestHelper.parserString(source_with_comment);
+ assertEquals(source_without_comment, cu.toString());
+ assertEquals(6, cu.getComments().size());
+ }
+} \ No newline at end of file
diff --git a/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestHelper.java b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestHelper.java
new file mode 100644
index 000000000..a6098633a
--- /dev/null
+++ b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestHelper.java
@@ -0,0 +1,61 @@
+/*
+ * Created on 30/06/2008
+ */
+package japa.parser.ast.test;
+
+import japa.parser.JavaParser;
+import japa.parser.ParseException;
+import japa.parser.ast.CompilationUnit;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringBufferInputStream;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+final class TestHelper {
+
+ private TestHelper() {
+ // hide the constructor
+ }
+
+ private static File getFile(String sourceFolder, Class<?> clazz) {
+ return new File(sourceFolder, clazz.getName().replace('.', '/') + ".java");
+ }
+
+ public static CompilationUnit parserClass(String sourceFolder, Class<?> clazz) throws ParseException {
+ try {
+ return JavaParser.parse(getFile(sourceFolder, clazz));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static CompilationUnit parserString(String source) throws ParseException {
+ return JavaParser.parse(new StringBufferInputStream(source));
+ }
+
+ public static String readFile(File file) throws IOException {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
+ try {
+ StringBuilder ret = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ ret.append(line);
+ ret.append("\n");
+ }
+ return ret.toString();
+ } finally {
+ reader.close();
+ }
+ }
+
+ public static String readClass(String sourceFolder, Class<?> clazz) throws IOException {
+ return readFile(getFile(sourceFolder, clazz));
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestNodePositions.java b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestNodePositions.java
new file mode 100644
index 000000000..70087a89c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/TestNodePositions.java
@@ -0,0 +1,639 @@
+/*
+ * Created on 30/06/2008
+ */
+package japa.parser.ast.test;
+
+import japa.parser.ast.BlockComment;
+import japa.parser.ast.CompilationUnit;
+import japa.parser.ast.ImportDeclaration;
+import japa.parser.ast.LineComment;
+import japa.parser.ast.Node;
+import japa.parser.ast.PackageDeclaration;
+import japa.parser.ast.TypeParameter;
+import japa.parser.ast.body.AnnotationDeclaration;
+import japa.parser.ast.body.AnnotationMemberDeclaration;
+import japa.parser.ast.body.ClassOrInterfaceDeclaration;
+import japa.parser.ast.body.ConstructorDeclaration;
+import japa.parser.ast.body.EmptyMemberDeclaration;
+import japa.parser.ast.body.EmptyTypeDeclaration;
+import japa.parser.ast.body.EnumConstantDeclaration;
+import japa.parser.ast.body.EnumDeclaration;
+import japa.parser.ast.body.FieldDeclaration;
+import japa.parser.ast.body.InitializerDeclaration;
+import japa.parser.ast.body.JavadocComment;
+import japa.parser.ast.body.MethodDeclaration;
+import japa.parser.ast.body.Parameter;
+import japa.parser.ast.body.VariableDeclarator;
+import japa.parser.ast.body.VariableDeclaratorId;
+import japa.parser.ast.expr.ArrayAccessExpr;
+import japa.parser.ast.expr.ArrayCreationExpr;
+import japa.parser.ast.expr.ArrayInitializerExpr;
+import japa.parser.ast.expr.AssignExpr;
+import japa.parser.ast.expr.BinaryExpr;
+import japa.parser.ast.expr.BooleanLiteralExpr;
+import japa.parser.ast.expr.CastExpr;
+import japa.parser.ast.expr.CharLiteralExpr;
+import japa.parser.ast.expr.ClassExpr;
+import japa.parser.ast.expr.ConditionalExpr;
+import japa.parser.ast.expr.DoubleLiteralExpr;
+import japa.parser.ast.expr.EnclosedExpr;
+import japa.parser.ast.expr.FieldAccessExpr;
+import japa.parser.ast.expr.InstanceOfExpr;
+import japa.parser.ast.expr.IntegerLiteralExpr;
+import japa.parser.ast.expr.IntegerLiteralMinValueExpr;
+import japa.parser.ast.expr.LongLiteralExpr;
+import japa.parser.ast.expr.LongLiteralMinValueExpr;
+import japa.parser.ast.expr.MarkerAnnotationExpr;
+import japa.parser.ast.expr.MemberValuePair;
+import japa.parser.ast.expr.MethodCallExpr;
+import japa.parser.ast.expr.NameExpr;
+import japa.parser.ast.expr.NormalAnnotationExpr;
+import japa.parser.ast.expr.NullLiteralExpr;
+import japa.parser.ast.expr.ObjectCreationExpr;
+import japa.parser.ast.expr.QualifiedNameExpr;
+import japa.parser.ast.expr.SingleMemberAnnotationExpr;
+import japa.parser.ast.expr.StringLiteralExpr;
+import japa.parser.ast.expr.SuperExpr;
+import japa.parser.ast.expr.ThisExpr;
+import japa.parser.ast.expr.UnaryExpr;
+import japa.parser.ast.expr.VariableDeclarationExpr;
+import japa.parser.ast.stmt.AssertStmt;
+import japa.parser.ast.stmt.BlockStmt;
+import japa.parser.ast.stmt.BreakStmt;
+import japa.parser.ast.stmt.CatchClause;
+import japa.parser.ast.stmt.ContinueStmt;
+import japa.parser.ast.stmt.DoStmt;
+import japa.parser.ast.stmt.EmptyStmt;
+import japa.parser.ast.stmt.ExplicitConstructorInvocationStmt;
+import japa.parser.ast.stmt.ExpressionStmt;
+import japa.parser.ast.stmt.ForStmt;
+import japa.parser.ast.stmt.ForeachStmt;
+import japa.parser.ast.stmt.IfStmt;
+import japa.parser.ast.stmt.LabeledStmt;
+import japa.parser.ast.stmt.ReturnStmt;
+import japa.parser.ast.stmt.SwitchEntryStmt;
+import japa.parser.ast.stmt.SwitchStmt;
+import japa.parser.ast.stmt.SynchronizedStmt;
+import japa.parser.ast.stmt.ThrowStmt;
+import japa.parser.ast.stmt.TryStmt;
+import japa.parser.ast.stmt.TypeDeclarationStmt;
+import japa.parser.ast.stmt.WhileStmt;
+import japa.parser.ast.test.classes.DumperTestClass;
+import japa.parser.ast.type.ClassOrInterfaceType;
+import japa.parser.ast.type.PrimitiveType;
+import japa.parser.ast.type.ReferenceType;
+import japa.parser.ast.type.VoidType;
+import japa.parser.ast.type.WildcardType;
+import japa.parser.ast.visitor.VoidVisitorAdapter;
+import junit.framework.TestCase;
+
+/**
+ * @author Julio Vilmar Gesser
+ */
+public class TestNodePositions extends TestCase {
+
+ public void testNodePositions() throws Exception {
+ String source = TestHelper.readClass("./test", DumperTestClass.class);
+ CompilationUnit cu = TestHelper.parserString(source);
+
+ cu.accept(new TestVisitor(source), null);
+ }
+
+ void doTest(String source, Node node) {
+ String parsed = node.toString();
+
+ assertTrue(node.getClass().getName() + ": " + parsed, node.getBeginLine() >= 0);
+ assertTrue(node.getClass().getName() + ": " + parsed, node.getBeginColumn() >= 0);
+ assertTrue(node.getClass().getName() + ": " + parsed, node.getEndLine() >= 0);
+ assertTrue(node.getClass().getName() + ": " + parsed, node.getEndColumn() >= 0);
+
+ if (node.getBeginLine() == node.getEndLine()) {
+ assertTrue(node.getClass().getName() + ": " + parsed, node.getBeginColumn() <= node.getEndColumn());
+ } else {
+ assertTrue(node.getClass().getName() + ": " + parsed, node.getBeginLine() <= node.getEndLine());
+ }
+
+ String substr = substring(source, node.getBeginLine(), node.getBeginColumn(), node.getEndLine(), node.getEndColumn());
+ assertEquals(node.getClass().getName(), trimLines(parsed), trimLines(substr));
+ }
+
+ private String trimLines(String str) {
+ String[] split = str.split("\n");
+ StringBuilder ret = new StringBuilder();
+ for (int i = 0; i < split.length; i++) {
+ ret.append(split[i].trim());
+ if (i < split.length - 1) {
+ ret.append("\n");
+ }
+ }
+
+ return ret.toString();
+ }
+
+ private String substring(String source, int beginLine, int beginColumn, int endLine, int endColumn) {
+ int pos = 0;
+ while (beginLine > 1) {
+ if (source.charAt(pos) == '\n') {
+ beginLine--;
+ endLine--;
+ }
+ pos++;
+ }
+ int start = pos + beginColumn - 1;
+
+ while (endLine > 1) {
+ if (source.charAt(pos) == '\n') {
+ endLine--;
+ }
+ pos++;
+ }
+ int end = pos + endColumn;
+
+ return source.substring(start, end);
+ }
+
+ class TestVisitor extends VoidVisitorAdapter<Object> {
+
+ private final String source;
+
+ public TestVisitor(String source) {
+ this.source = source;
+ }
+
+ @Override
+ public void visit(AnnotationDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(AnnotationMemberDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ArrayAccessExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ArrayCreationExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ArrayInitializerExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(AssertStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(AssignExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(BinaryExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(BlockComment n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(BlockStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(BooleanLiteralExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(BreakStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(CastExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(CatchClause n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(CharLiteralExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ClassExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ClassOrInterfaceDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ClassOrInterfaceType n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(CompilationUnit n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ConditionalExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ConstructorDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ContinueStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(DoStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(DoubleLiteralExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(EmptyMemberDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(EmptyStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(EmptyTypeDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(EnclosedExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(EnumConstantDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(EnumDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ExplicitConstructorInvocationStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ExpressionStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(FieldAccessExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(FieldDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ForeachStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ForStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(IfStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ImportDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(InitializerDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(InstanceOfExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(IntegerLiteralExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(IntegerLiteralMinValueExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(JavadocComment n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(LabeledStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(LineComment n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(LongLiteralExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(LongLiteralMinValueExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(MarkerAnnotationExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(MemberValuePair n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(MethodCallExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(MethodDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(NameExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(NormalAnnotationExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(NullLiteralExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ObjectCreationExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(PackageDeclaration n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(Parameter n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(PrimitiveType n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(QualifiedNameExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ReferenceType n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ReturnStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(SingleMemberAnnotationExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(StringLiteralExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(SuperExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(SwitchEntryStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(SwitchStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(SynchronizedStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ThisExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(ThrowStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(TryStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(TypeDeclarationStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(TypeParameter n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(UnaryExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(VariableDeclarationExpr n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(VariableDeclarator n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(VariableDeclaratorId n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(VoidType n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(WhileStmt n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ @Override
+ public void visit(WildcardType n, Object arg) {
+ doTest(source, n);
+ super.visit(n, arg);
+ }
+
+ }
+
+}
diff --git a/components/htmlfive/java/javaparser/test/japa/parser/ast/test/classes/DumperTestClass.java b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/classes/DumperTestClass.java
new file mode 100644
index 000000000..390f77a51
--- /dev/null
+++ b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/classes/DumperTestClass.java
@@ -0,0 +1,364 @@
+package japa.parser.ast.test.classes;
+
+import japa.parser.JavaParser;
+import japa.parser.ParseException;
+import japa.parser.ast.CompilationUnit;
+import java.io.File;
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.annotation.Documented;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import static java.util.Map.Entry;
+import java.applet.*;
+
+@Deprecated
+public class DumperTestClass<T extends List<int[]>, X> extends Base implements Serializable {
+
+ static Class clz1 = String.class;
+
+ protected Class clz2 = (String.class);
+
+ Class clz3 = int.class;
+
+ Class clz4 = (int.class);
+
+ int[] arr = new int[10];
+
+ byte bye = 0;
+
+ short sh1, sh2 = 1;
+
+ List<String>[][] arrLS = (List<String>[][]) new List<?>[10][];
+
+ ;
+
+ @Deprecated()
+ static class Ugly {
+
+ static int x = 0;
+
+ public static void main(String[] args) {
+ x = +x;
+ x = ~x;
+ --x;
+ boolean b = !false;
+ x &= 2;
+ x |= 2;
+ x ^= 2;
+ x -= 2;
+ x %= 2;
+ x /= 2;
+ x *= 2;
+ x <<= 2;
+ x >>= 2;
+ x >>>= 2;
+ b = b || false;
+ b = b | false;
+ b = b & false;
+ b = b ^ false;
+ b = b != false;
+ b = x > 1;
+ b = x < 1;
+ b = x >= 1;
+ b = x <= 1;
+ x = x << 1;
+ x = x >> 1;
+ x = x >>> 1;
+ x = x - 1;
+ x = x * 1;
+ x = x % 1;
+ x = x / 1;
+ }
+ }
+
+ ;
+
+ @Deprecated()
+ int[][][][] arr2 = new int[10][2][1][0];
+
+ volatile float fff = 0x1.fffeP+127f;
+
+ char cc = 'a';
+
+ int[][] arr3 = { { 1, 2 }, { 3, 4 } };
+
+ static int[] arr4[] = {};
+
+ public static DumperTestClass t;
+
+ static {
+ arr4 = new int[][] { { 2 }, { 1 } };
+ }
+
+ {
+ arr3 = new int[][] { { 2 }, { 1 } };
+ }
+
+ public enum Teste {
+
+ asc, def
+ }
+
+ public static enum Sexo {
+
+ m, @Deprecated
+ f;
+
+ public static enum Sexo_ implements Serializable, Cloneable {
+ }
+
+ private Sexo() {
+ }
+ }
+
+ @Deprecated
+ public static enum Enum {
+
+ m(1) {
+
+ @Override
+ void mm() {
+ }
+ }
+ , f(2) {
+
+ void mm() {
+ }
+ }
+ ;
+
+ native void nnn();
+
+ transient int x;
+
+ private Enum(int x) {
+ this.x = x;
+ }
+
+ abstract void mm();
+ }
+
+ strictfp double ddd() {
+ return 0.0;
+ }
+
+ public <T, E> DumperTestClass(int x) {
+ this.arr[0] = x;
+ T val1 = null;
+ E val2 = null;
+ super.<T, E>check2(val1, val2);
+ boolean b = true, y = false;
+ abstract class X {
+
+ int i = 0;
+
+ public <D> X() {
+ }
+
+ public void m() {
+ }
+ }
+ @Deprecated
+ final class Y extends X {
+
+ public Y() {
+ super();
+ DumperTestClass.this.cc = 'c';
+ super.i = 1;
+ Y.super.m();
+ }
+
+ public Y(int y) {
+ t.<Object>super();
+ }
+
+ public Y(long x) {
+ this();
+ }
+ }
+ }
+
+ public <T> DumperTestClass(String str) {
+ }
+
+ private class QWE extends DumperTestClass<List<int[]>, String> {
+
+ @Deprecated
+ final int z = 0;
+
+ int i = (int) -1;
+
+ public QWE(String... x) {
+ <String>super(x[0]);
+ }
+
+ public QWE(int... x) {
+ super(x[0]);
+ i = x[0];
+ assert true;
+ assert 1 == 1 : 2;
+ {
+ int iii = 3;
+ iii += 3;
+ }
+ label: {
+ int iii = 1;
+ }
+ ;
+ ;
+ int min = -2147483648;
+ long sl = 123123123123l;
+ long minl = -9223372036854775808L;
+ switch(i) {
+ }
+ ll: switch(i) {
+ case 1:
+ System.out.println(1);
+ break ll;
+ default:
+ {
+ System.out.println("default");
+ break;
+ }
+ case 2:
+ if (t instanceof Base) {
+ System.out.println(1);
+ }
+ i++;
+ ++i;
+ }
+ }
+
+ private synchronized int doSomething()[] {
+ List<? extends Number> x = new ArrayList<Integer>();
+ return new int[] { 1 };
+ }
+ }
+
+ public static void main(String[] args) throws ParseException, IOException {
+ int x = 2;
+ CompilationUnit cu = parse(new File("src/japa/parser/javacc/Parser.java"));
+ System.out.println(cu);
+ DumperTestClass teste = new DumperTestClass(2);
+ DumperTestClass.QWE qwe = teste.new QWE(1);
+ if (1 + 1 == 2) {
+ teste = null;
+ teste = new DumperTestClass(1);
+ } else {
+ x = 3;
+ teste = new DumperTestClass(1);
+ x = x == 0 ? 2 : 4;
+ }
+ if (true) x = 1; else x = 3;
+ while (true) {
+ xxx: while (x == 3) continue xxx;
+ break;
+ }
+ do {
+ x++;
+ } while (x < 100);
+ do x++; while (x < 100);
+ for (@Deprecated int i : arr4[0]) {
+ x--;
+ }
+ for (@Deprecated final int i = 0, j = 1; i < 10; x++) {
+ break;
+ }
+ int i, j;
+ for (i = 0, j = 1; i < 10 && j < 2; i++, j--) {
+ break;
+ }
+ }
+
+ @AnnotationTest(value = "x")
+ public static CompilationUnit parse(@Deprecated File file) throws ParseException, IOException {
+ String a = ((String) "qwe");
+ String x = ((String) clz1.getName());
+ int y = ((Integer) (Object) x).intValue();
+ synchronized (file) {
+ file = null;
+ file = new File("");
+ }
+ try {
+ if (file == null) {
+ throw new NullPointerException("blah");
+ }
+ } catch (final NullPointerException e) {
+ System.out.println("catch");
+ } catch (RuntimeException e) {
+ System.out.println("catch");
+ } finally {
+ System.out.println("finally");
+ }
+ try {
+ if (file == null) {
+ throw new NullPointerException("blah");
+ }
+ } finally {
+ System.out.println("finally");
+ }
+ try {
+ if (file == null) {
+ throw new NullPointerException("blah");
+ }
+ } catch (RuntimeException e) {
+ System.out.println("catch");
+ }
+ return JavaParser.parse(file);
+ }
+
+ class A<T extends Integer & Serializable> implements XXX, Serializable {
+
+ @AnnotationTest
+ public <ABC> A(Integer integer, ABC string) throws Exception, IOException {
+ }
+ }
+
+ private <Y> void x(Map<? extends X, ? super T> x) {
+ @Deprecated Comparator c = new Comparator() {
+
+ public int compare(Object o1, Object o2) {
+ try {
+ A<Integer> a = new <String>A<Integer>(new Integer(11), "foo") {
+ };
+ } catch (Exception e) {
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return super.equals(obj);
+ }
+ };
+ }
+
+ @Documented
+ public @interface AnnotationTest {
+
+ String value() default "asd";
+
+ @Deprecated
+ int[] valueI() default { 1, 2 };
+
+ AnnotationTest valueA1() default @AnnotationTest;
+
+ AnnotationTest valueA2() default @AnnotationTest("qwe");
+
+ AnnotationTest valueA3() default @AnnotationTest(value = "qwe", valueI = { 1 });
+ }
+
+ ;
+}
+
+class Base {
+
+ public <A, B> void check2(A val1, B val2) {
+ }
+}
+
+interface XXX extends Serializable, Cloneable {
+} \ No newline at end of file
diff --git a/components/htmlfive/java/javaparser/test/japa/parser/ast/test/classes/JavadocTestClass.java b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/classes/JavadocTestClass.java
new file mode 100644
index 000000000..e8b96179c
--- /dev/null
+++ b/components/htmlfive/java/javaparser/test/japa/parser/ast/test/classes/JavadocTestClass.java
@@ -0,0 +1,127 @@
+package japa.parser.ast.test.classes;
+
+/**
+ * Javadoc 1
+ * @author Julio Vilmar Gesser
+ */
+public abstract class JavadocTestClass {
+
+ ;
+
+ /**
+ * 1.1
+ */
+ ;
+
+ private static int x;
+
+ /**
+ * 1.2
+ */
+ private int y[];
+
+ /**
+ * 1.3
+ */
+ @Annotation(x = 10)
+ private int z;
+
+ private static final int m(int x) {
+ return 0;
+ }
+
+ /**
+ * 1.4
+ * @param x
+ * @return
+ */
+ private static final int m2(int x) {
+ x = 10;
+ /**
+ * 1.4.1
+ * @author jgesser
+ */
+ class Teste {
+
+ /**
+ * 1.4.1.1
+ */
+ int x;
+ }
+ return 0;
+ }
+
+ /**
+ * 1.5
+ */
+ public JavadocTestClass() {
+ }
+
+ /**
+ * 1.5
+ */
+ public <O> JavadocTestClass(int x) {
+ }
+
+ /**
+ * 1.6
+ * init
+ */
+ {
+ z = 10;
+ }
+
+ /**
+ * 1.6
+ * init
+ */
+ static {
+ x = 10;
+ }
+}
+
+/**
+ * Javadoc 2
+ */
+@Deprecated
+@SuppressWarnings(value = "")
+abstract class Class2 {
+}
+
+/**
+ * Javadoc 3
+ */
+;
+
+/**
+ * Javadoc 4
+ */
+enum Enum {
+
+ /**
+ * 4.1
+ */
+ item1, item2, item3, /**
+ * 4.2
+ */
+ item4
+}
+
+/**
+ * Javadoc 5
+ */
+@interface Annotation {
+
+ ;
+
+ /**
+ * Javadoc 5.1
+ * @return
+ */
+ int x();
+
+ /**
+ * Javadoc 5.2
+ */
+ ;
+} \ No newline at end of file
diff --git a/components/htmlfive/java/manifest.txt b/components/htmlfive/java/manifest.txt
new file mode 100644
index 000000000..14cd9d081
--- /dev/null
+++ b/components/htmlfive/java/manifest.txt
@@ -0,0 +1,2 @@
+Main-Class: nu.validator.htmlparser.cpptranslate.Main
+Class-Path: javaparser.jar
diff --git a/components/htmlfive/java/named-character-references.html b/components/htmlfive/java/named-character-references.html
new file mode 100644
index 000000000..c8d1e08da
--- /dev/null
+++ b/components/htmlfive/java/named-character-references.html
@@ -0,0 +1,7 @@
+<!--
+Spec rev 4381
+-->
+
+ <table><thead><tr><th> Name </th> <th> Character </th> <tbody><tr><td> <code title="">AElig;</code> </td> <td> U+000C6 </td> <tr><td> <code title="">AElig</code> </td> <td> U+000C6 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">AMP;</code> </td> <td> U+00026 </td> <tr><td> <code title="">AMP</code> </td> <td> U+00026 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Aacute;</code> </td> <td> U+000C1 </td> <tr><td> <code title="">Aacute</code> </td> <td> U+000C1 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Abreve;</code> </td> <td> U+00102 </td> <tr><td> <code title="">Acirc;</code> </td> <td> U+000C2 </td> <tr><td> <code title="">Acirc</code> </td> <td> U+000C2 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Acy;</code> </td> <td> U+00410 </td> <tr><td> <code title="">Afr;</code> </td> <td> U+1D504 </td> <tr><td> <code title="">Agrave;</code> </td> <td> U+000C0 </td> <tr><td> <code title="">Agrave</code> </td> <td> U+000C0 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Alpha;</code> </td> <td> U+00391 </td> <tr><td> <code title="">Amacr;</code> </td> <td> U+00100 </td> <tr><td> <code title="">And;</code> </td> <td> U+02A53 </td> <tr><td> <code title="">Aogon;</code> </td> <td> U+00104 </td> <tr><td> <code title="">Aopf;</code> </td> <td> U+1D538 </td> <tr><td> <code title="">ApplyFunction;</code> </td> <td> U+02061 </td> <tr><td> <code title="">Aring;</code> </td> <td> U+000C5 </td> <tr><td> <code title="">Aring</code> </td> <td> U+000C5 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Ascr;</code> </td> <td> U+1D49C </td> <tr><td> <code title="">Assign;</code> </td> <td> U+02254 </td> <tr><td> <code title="">Atilde;</code> </td> <td> U+000C3 </td> <tr><td> <code title="">Atilde</code> </td> <td> U+000C3 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Auml;</code> </td> <td> U+000C4 </td> <tr><td> <code title="">Auml</code> </td> <td> U+000C4 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Backslash;</code> </td> <td> U+02216 </td> <tr><td> <code title="">Barv;</code> </td> <td> U+02AE7 </td> <tr><td> <code title="">Barwed;</code> </td> <td> U+02306 </td> <tr><td> <code title="">Bcy;</code> </td> <td> U+00411 </td> <tr><td> <code title="">Because;</code> </td> <td> U+02235 </td> <tr><td> <code title="">Bernoullis;</code> </td> <td> U+0212C </td> <tr><td> <code title="">Beta;</code> </td> <td> U+00392 </td> <tr><td> <code title="">Bfr;</code> </td> <td> U+1D505 </td> <tr><td> <code title="">Bopf;</code> </td> <td> U+1D539 </td> <tr><td> <code title="">Breve;</code> </td> <td> U+002D8 </td> <tr><td> <code title="">Bscr;</code> </td> <td> U+0212C </td> <tr><td> <code title="">Bumpeq;</code> </td> <td> U+0224E </td> <tr><td> <code title="">CHcy;</code> </td> <td> U+00427 </td> <tr><td> <code title="">COPY;</code> </td> <td> U+000A9 </td> <tr><td> <code title="">COPY</code> </td> <td> U+000A9 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Cacute;</code> </td> <td> U+00106 </td> <tr><td> <code title="">Cap;</code> </td> <td> U+022D2 </td> <tr><td> <code title="">CapitalDifferentialD;</code> </td> <td> U+02145 </td> <tr><td> <code title="">Cayleys;</code> </td> <td> U+0212D </td> <tr><td> <code title="">Ccaron;</code> </td> <td> U+0010C </td> <tr><td> <code title="">Ccedil;</code> </td> <td> U+000C7 </td> <tr><td> <code title="">Ccedil</code> </td> <td> U+000C7 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Ccirc;</code> </td> <td> U+00108 </td> <tr><td> <code title="">Cconint;</code> </td> <td> U+02230 </td> <tr><td> <code title="">Cdot;</code> </td> <td> U+0010A </td> <tr><td> <code title="">Cedilla;</code> </td> <td> U+000B8 </td> <tr><td> <code title="">CenterDot;</code> </td> <td> U+000B7 </td> <tr><td> <code title="">Cfr;</code> </td> <td> U+0212D </td> <tr><td> <code title="">Chi;</code> </td> <td> U+003A7 </td> <tr><td> <code title="">CircleDot;</code> </td> <td> U+02299 </td> <tr><td> <code title="">CircleMinus;</code> </td> <td> U+02296 </td> <tr><td> <code title="">CirclePlus;</code> </td> <td> U+02295 </td> <tr><td> <code title="">CircleTimes;</code> </td> <td> U+02297 </td> <tr><td> <code title="">ClockwiseContourIntegral;</code> </td> <td> U+02232 </td> <tr><td> <code title="">CloseCurlyDoubleQuote;</code> </td> <td> U+0201D </td> <tr><td> <code title="">CloseCurlyQuote;</code> </td> <td> U+02019 </td> <tr><td> <code title="">Colon;</code> </td> <td> U+02237 </td> <tr><td> <code title="">Colone;</code> </td> <td> U+02A74 </td> <tr><td> <code title="">Congruent;</code> </td> <td> U+02261 </td> <tr><td> <code title="">Conint;</code> </td> <td> U+0222F </td> <tr><td> <code title="">ContourIntegral;</code> </td> <td> U+0222E </td> <tr><td> <code title="">Copf;</code> </td> <td> U+02102 </td> <tr><td> <code title="">Coproduct;</code> </td> <td> U+02210 </td> <tr><td> <code title="">CounterClockwiseContourIntegral;</code> </td> <td> U+02233 </td> <tr><td> <code title="">Cross;</code> </td> <td> U+02A2F </td> <tr><td> <code title="">Cscr;</code> </td> <td> U+1D49E </td> <tr><td> <code title="">Cup;</code> </td> <td> U+022D3 </td> <tr><td> <code title="">CupCap;</code> </td> <td> U+0224D </td> <tr><td> <code title="">DD;</code> </td> <td> U+02145 </td> <tr><td> <code title="">DDotrahd;</code> </td> <td> U+02911 </td> <tr><td> <code title="">DJcy;</code> </td> <td> U+00402 </td> <tr><td> <code title="">DScy;</code> </td> <td> U+00405 </td> <tr><td> <code title="">DZcy;</code> </td> <td> U+0040F </td> <tr><td> <code title="">Dagger;</code> </td> <td> U+02021 </td> <tr><td> <code title="">Darr;</code> </td> <td> U+021A1 </td> <tr><td> <code title="">Dashv;</code> </td> <td> U+02AE4 </td> <tr><td> <code title="">Dcaron;</code> </td> <td> U+0010E </td> <tr><td> <code title="">Dcy;</code> </td> <td> U+00414 </td> <tr><td> <code title="">Del;</code> </td> <td> U+02207 </td> <tr><td> <code title="">Delta;</code> </td> <td> U+00394 </td> <tr><td> <code title="">Dfr;</code> </td> <td> U+1D507 </td> <tr><td> <code title="">DiacriticalAcute;</code> </td> <td> U+000B4 </td> <tr><td> <code title="">DiacriticalDot;</code> </td> <td> U+002D9 </td> <tr><td> <code title="">DiacriticalDoubleAcute;</code> </td> <td> U+002DD </td> <tr><td> <code title="">DiacriticalGrave;</code> </td> <td> U+00060 </td> <tr><td> <code title="">DiacriticalTilde;</code> </td> <td> U+002DC </td> <tr><td> <code title="">Diamond;</code> </td> <td> U+022C4 </td> <tr><td> <code title="">DifferentialD;</code> </td> <td> U+02146 </td> <tr><td> <code title="">Dopf;</code> </td> <td> U+1D53B </td> <tr><td> <code title="">Dot;</code> </td> <td> U+000A8 </td> <tr><td> <code title="">DotDot;</code> </td> <td> U+020DC </td> <tr><td> <code title="">DotEqual;</code> </td> <td> U+02250 </td> <tr><td> <code title="">DoubleContourIntegral;</code> </td> <td> U+0222F </td> <tr><td> <code title="">DoubleDot;</code> </td> <td> U+000A8 </td> <tr><td> <code title="">DoubleDownArrow;</code> </td> <td> U+021D3 </td> <tr><td> <code title="">DoubleLeftArrow;</code> </td> <td> U+021D0 </td> <tr><td> <code title="">DoubleLeftRightArrow;</code> </td> <td> U+021D4 </td> <tr><td> <code title="">DoubleLeftTee;</code> </td> <td> U+02AE4 </td> <tr><td> <code title="">DoubleLongLeftArrow;</code> </td> <td> U+027F8 </td> <tr><td> <code title="">DoubleLongLeftRightArrow;</code> </td> <td> U+027FA </td> <tr><td> <code title="">DoubleLongRightArrow;</code> </td> <td> U+027F9 </td> <tr><td> <code title="">DoubleRightArrow;</code> </td> <td> U+021D2 </td> <tr><td> <code title="">DoubleRightTee;</code> </td> <td> U+022A8 </td> <tr><td> <code title="">DoubleUpArrow;</code> </td> <td> U+021D1 </td> <tr><td> <code title="">DoubleUpDownArrow;</code> </td> <td> U+021D5 </td> <tr><td> <code title="">DoubleVerticalBar;</code> </td> <td> U+02225 </td> <tr><td> <code title="">DownArrow;</code> </td> <td> U+02193 </td> <tr><td> <code title="">DownArrowBar;</code> </td> <td> U+02913 </td> <tr><td> <code title="">DownArrowUpArrow;</code> </td> <td> U+021F5 </td> <tr><td> <code title="">DownBreve;</code> </td> <td> U+00311 </td> <tr><td> <code title="">DownLeftRightVector;</code> </td> <td> U+02950 </td> <tr><td> <code title="">DownLeftTeeVector;</code> </td> <td> U+0295E </td> <tr><td> <code title="">DownLeftVector;</code> </td> <td> U+021BD </td> <tr><td> <code title="">DownLeftVectorBar;</code> </td> <td> U+02956 </td> <tr><td> <code title="">DownRightTeeVector;</code> </td> <td> U+0295F </td> <tr><td> <code title="">DownRightVector;</code> </td> <td> U+021C1 </td> <tr><td> <code title="">DownRightVectorBar;</code> </td> <td> U+02957 </td> <tr><td> <code title="">DownTee;</code> </td> <td> U+022A4 </td> <tr><td> <code title="">DownTeeArrow;</code> </td> <td> U+021A7 </td> <tr><td> <code title="">Downarrow;</code> </td> <td> U+021D3 </td> <tr><td> <code title="">Dscr;</code> </td> <td> U+1D49F </td> <tr><td> <code title="">Dstrok;</code> </td> <td> U+00110 </td> <tr><td> <code title="">ENG;</code> </td> <td> U+0014A </td> <tr><td> <code title="">ETH;</code> </td> <td> U+000D0 </td> <tr><td> <code title="">ETH</code> </td> <td> U+000D0 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Eacute;</code> </td> <td> U+000C9 </td> <tr><td> <code title="">Eacute</code> </td> <td> U+000C9 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Ecaron;</code> </td> <td> U+0011A </td> <tr><td> <code title="">Ecirc;</code> </td> <td> U+000CA </td> <tr><td> <code title="">Ecirc</code> </td> <td> U+000CA </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Ecy;</code> </td> <td> U+0042D </td> <tr><td> <code title="">Edot;</code> </td> <td> U+00116 </td> <tr><td> <code title="">Efr;</code> </td> <td> U+1D508 </td> <tr><td> <code title="">Egrave;</code> </td> <td> U+000C8 </td> <tr><td> <code title="">Egrave</code> </td> <td> U+000C8 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Element;</code> </td> <td> U+02208 </td> <tr><td> <code title="">Emacr;</code> </td> <td> U+00112 </td> <tr><td> <code title="">EmptySmallSquare;</code> </td> <td> U+025FB </td> <tr><td> <code title="">EmptyVerySmallSquare;</code> </td> <td> U+025AB </td> <tr><td> <code title="">Eogon;</code> </td> <td> U+00118 </td> <tr><td> <code title="">Eopf;</code> </td> <td> U+1D53C </td> <tr><td> <code title="">Epsilon;</code> </td> <td> U+00395 </td> <tr><td> <code title="">Equal;</code> </td> <td> U+02A75 </td> <tr><td> <code title="">EqualTilde;</code> </td> <td> U+02242 </td> <tr><td> <code title="">Equilibrium;</code> </td> <td> U+021CC </td> <tr><td> <code title="">Escr;</code> </td> <td> U+02130 </td> <tr><td> <code title="">Esim;</code> </td> <td> U+02A73 </td> <tr><td> <code title="">Eta;</code> </td> <td> U+00397 </td> <tr><td> <code title="">Euml;</code> </td> <td> U+000CB </td> <tr><td> <code title="">Euml</code> </td> <td> U+000CB </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Exists;</code> </td> <td> U+02203 </td> <tr><td> <code title="">ExponentialE;</code> </td> <td> U+02147 </td> <tr><td> <code title="">Fcy;</code> </td> <td> U+00424 </td> <tr><td> <code title="">Ffr;</code> </td> <td> U+1D509 </td> <tr><td> <code title="">FilledSmallSquare;</code> </td> <td> U+025FC </td> <tr><td> <code title="">FilledVerySmallSquare;</code> </td> <td> U+025AA </td> <tr><td> <code title="">Fopf;</code> </td> <td> U+1D53D </td> <tr><td> <code title="">ForAll;</code> </td> <td> U+02200 </td> <tr><td> <code title="">Fouriertrf;</code> </td> <td> U+02131 </td> <tr><td> <code title="">Fscr;</code> </td> <td> U+02131 </td> <tr><td> <code title="">GJcy;</code> </td> <td> U+00403 </td> <tr><td> <code title="">GT;</code> </td> <td> U+0003E </td> <tr><td> <code title="">GT</code> </td> <td> U+0003E </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Gamma;</code> </td> <td> U+00393 </td> <tr><td> <code title="">Gammad;</code> </td> <td> U+003DC </td> <tr><td> <code title="">Gbreve;</code> </td> <td> U+0011E </td> <tr><td> <code title="">Gcedil;</code> </td> <td> U+00122 </td> <tr><td> <code title="">Gcirc;</code> </td> <td> U+0011C </td> <tr><td> <code title="">Gcy;</code> </td> <td> U+00413 </td> <tr><td> <code title="">Gdot;</code> </td> <td> U+00120 </td> <tr><td> <code title="">Gfr;</code> </td> <td> U+1D50A </td> <tr><td> <code title="">Gg;</code> </td> <td> U+022D9 </td> <tr><td> <code title="">Gopf;</code> </td> <td> U+1D53E </td> <tr><td> <code title="">GreaterEqual;</code> </td> <td> U+02265 </td> <tr><td> <code title="">GreaterEqualLess;</code> </td> <td> U+022DB </td> <tr><td> <code title="">GreaterFullEqual;</code> </td> <td> U+02267 </td> <tr><td> <code title="">GreaterGreater;</code> </td> <td> U+02AA2 </td> <tr><td> <code title="">GreaterLess;</code> </td> <td> U+02277 </td> <tr><td> <code title="">GreaterSlantEqual;</code> </td> <td> U+02A7E </td> <tr><td> <code title="">GreaterTilde;</code> </td> <td> U+02273 </td> <tr><td> <code title="">Gscr;</code> </td> <td> U+1D4A2 </td> <tr><td> <code title="">Gt;</code> </td> <td> U+0226B </td> <tr><td> <code title="">HARDcy;</code> </td> <td> U+0042A </td> <tr><td> <code title="">Hacek;</code> </td> <td> U+002C7 </td> <tr><td> <code title="">Hat;</code> </td> <td> U+0005E </td> <tr><td> <code title="">Hcirc;</code> </td> <td> U+00124 </td> <tr><td> <code title="">Hfr;</code> </td> <td> U+0210C </td> <tr><td> <code title="">HilbertSpace;</code> </td> <td> U+0210B </td> <tr><td> <code title="">Hopf;</code> </td> <td> U+0210D </td> <tr><td> <code title="">HorizontalLine;</code> </td> <td> U+02500 </td> <tr><td> <code title="">Hscr;</code> </td> <td> U+0210B </td> <tr><td> <code title="">Hstrok;</code> </td> <td> U+00126 </td> <tr><td> <code title="">HumpDownHump;</code> </td> <td> U+0224E </td> <tr><td> <code title="">HumpEqual;</code> </td> <td> U+0224F </td> <tr><td> <code title="">IEcy;</code> </td> <td> U+00415 </td> <tr><td> <code title="">IJlig;</code> </td> <td> U+00132 </td> <tr><td> <code title="">IOcy;</code> </td> <td> U+00401 </td> <tr><td> <code title="">Iacute;</code> </td> <td> U+000CD </td> <tr><td> <code title="">Iacute</code> </td> <td> U+000CD </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Icirc;</code> </td> <td> U+000CE </td> <tr><td> <code title="">Icirc</code> </td> <td> U+000CE </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Icy;</code> </td> <td> U+00418 </td> <tr><td> <code title="">Idot;</code> </td> <td> U+00130 </td> <tr><td> <code title="">Ifr;</code> </td> <td> U+02111 </td> <tr><td> <code title="">Igrave;</code> </td> <td> U+000CC </td> <tr><td> <code title="">Igrave</code> </td> <td> U+000CC </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Im;</code> </td> <td> U+02111 </td> <tr><td> <code title="">Imacr;</code> </td> <td> U+0012A </td> <tr><td> <code title="">ImaginaryI;</code> </td> <td> U+02148 </td> <tr><td> <code title="">Implies;</code> </td> <td> U+021D2 </td> <tr><td> <code title="">Int;</code> </td> <td> U+0222C </td> <tr><td> <code title="">Integral;</code> </td> <td> U+0222B </td> <tr><td> <code title="">Intersection;</code> </td> <td> U+022C2 </td> <tr><td> <code title="">InvisibleComma;</code> </td> <td> U+02063 </td> <tr><td> <code title="">InvisibleTimes;</code> </td> <td> U+02062 </td> <tr><td> <code title="">Iogon;</code> </td> <td> U+0012E </td> <tr><td> <code title="">Iopf;</code> </td> <td> U+1D540 </td> <tr><td> <code title="">Iota;</code> </td> <td> U+00399 </td> <tr><td> <code title="">Iscr;</code> </td> <td> U+02110 </td> <tr><td> <code title="">Itilde;</code> </td> <td> U+00128 </td> <tr><td> <code title="">Iukcy;</code> </td> <td> U+00406 </td> <tr><td> <code title="">Iuml;</code> </td> <td> U+000CF </td> <tr><td> <code title="">Iuml</code> </td> <td> U+000CF </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Jcirc;</code> </td> <td> U+00134 </td> <tr><td> <code title="">Jcy;</code> </td> <td> U+00419 </td> <tr><td> <code title="">Jfr;</code> </td> <td> U+1D50D </td> <tr><td> <code title="">Jopf;</code> </td> <td> U+1D541 </td> <tr><td> <code title="">Jscr;</code> </td> <td> U+1D4A5 </td> <tr><td> <code title="">Jsercy;</code> </td> <td> U+00408 </td> <tr><td> <code title="">Jukcy;</code> </td> <td> U+00404 </td> <tr><td> <code title="">KHcy;</code> </td> <td> U+00425 </td> <tr><td> <code title="">KJcy;</code> </td> <td> U+0040C </td> <tr><td> <code title="">Kappa;</code> </td> <td> U+0039A </td> <tr><td> <code title="">Kcedil;</code> </td> <td> U+00136 </td> <tr><td> <code title="">Kcy;</code> </td> <td> U+0041A </td> <tr><td> <code title="">Kfr;</code> </td> <td> U+1D50E </td> <tr><td> <code title="">Kopf;</code> </td> <td> U+1D542 </td> <tr><td> <code title="">Kscr;</code> </td> <td> U+1D4A6 </td> <tr><td> <code title="">LJcy;</code> </td> <td> U+00409 </td> <tr><td> <code title="">LT;</code> </td> <td> U+0003C </td> <tr><td> <code title="">LT</code> </td> <td> U+0003C </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Lacute;</code> </td> <td> U+00139 </td> <tr><td> <code title="">Lambda;</code> </td> <td> U+0039B </td> <tr><td> <code title="">Lang;</code> </td> <td> U+027EA </td> <tr><td> <code title="">Laplacetrf;</code> </td> <td> U+02112 </td> <tr><td> <code title="">Larr;</code> </td> <td> U+0219E </td> <tr><td> <code title="">Lcaron;</code> </td> <td> U+0013D </td> <tr><td> <code title="">Lcedil;</code> </td> <td> U+0013B </td> <tr><td> <code title="">Lcy;</code> </td> <td> U+0041B </td> <tr><td> <code title="">LeftAngleBracket;</code> </td> <td> U+027E8 </td> <tr><td> <code title="">LeftArrow;</code> </td> <td> U+02190 </td> <tr><td> <code title="">LeftArrowBar;</code> </td> <td> U+021E4 </td> <tr><td> <code title="">LeftArrowRightArrow;</code> </td> <td> U+021C6 </td> <tr><td> <code title="">LeftCeiling;</code> </td> <td> U+02308 </td> <tr><td> <code title="">LeftDoubleBracket;</code> </td> <td> U+027E6 </td> <tr><td> <code title="">LeftDownTeeVector;</code> </td> <td> U+02961 </td> <tr><td> <code title="">LeftDownVector;</code> </td> <td> U+021C3 </td> <tr><td> <code title="">LeftDownVectorBar;</code> </td> <td> U+02959 </td> <tr><td> <code title="">LeftFloor;</code> </td> <td> U+0230A </td> <tr><td> <code title="">LeftRightArrow;</code> </td> <td> U+02194 </td> <tr><td> <code title="">LeftRightVector;</code> </td> <td> U+0294E </td> <tr><td> <code title="">LeftTee;</code> </td> <td> U+022A3 </td> <tr><td> <code title="">LeftTeeArrow;</code> </td> <td> U+021A4 </td> <tr><td> <code title="">LeftTeeVector;</code> </td> <td> U+0295A </td> <tr><td> <code title="">LeftTriangle;</code> </td> <td> U+022B2 </td> <tr><td> <code title="">LeftTriangleBar;</code> </td> <td> U+029CF </td> <tr><td> <code title="">LeftTriangleEqual;</code> </td> <td> U+022B4 </td> <tr><td> <code title="">LeftUpDownVector;</code> </td> <td> U+02951 </td> <tr><td> <code title="">LeftUpTeeVector;</code> </td> <td> U+02960 </td> <tr><td> <code title="">LeftUpVector;</code> </td> <td> U+021BF </td> <tr><td> <code title="">LeftUpVectorBar;</code> </td> <td> U+02958 </td> <tr><td> <code title="">LeftVector;</code> </td> <td> U+021BC </td> <tr><td> <code title="">LeftVectorBar;</code> </td> <td> U+02952 </td> <tr><td> <code title="">Leftarrow;</code> </td> <td> U+021D0 </td> <tr><td> <code title="">Leftrightarrow;</code> </td> <td> U+021D4 </td> <tr><td> <code title="">LessEqualGreater;</code> </td> <td> U+022DA </td> <tr><td> <code title="">LessFullEqual;</code> </td> <td> U+02266 </td> <tr><td> <code title="">LessGreater;</code> </td> <td> U+02276 </td> <tr><td> <code title="">LessLess;</code> </td> <td> U+02AA1 </td> <tr><td> <code title="">LessSlantEqual;</code> </td> <td> U+02A7D </td> <tr><td> <code title="">LessTilde;</code> </td> <td> U+02272 </td> <tr><td> <code title="">Lfr;</code> </td> <td> U+1D50F </td> <tr><td> <code title="">Ll;</code> </td> <td> U+022D8 </td> <tr><td> <code title="">Lleftarrow;</code> </td> <td> U+021DA </td> <tr><td> <code title="">Lmidot;</code> </td> <td> U+0013F </td> <tr><td> <code title="">LongLeftArrow;</code> </td> <td> U+027F5 </td> <tr><td> <code title="">LongLeftRightArrow;</code> </td> <td> U+027F7 </td> <tr><td> <code title="">LongRightArrow;</code> </td> <td> U+027F6 </td> <tr><td> <code title="">Longleftarrow;</code> </td> <td> U+027F8 </td> <tr><td> <code title="">Longleftrightarrow;</code> </td> <td> U+027FA </td> <tr><td> <code title="">Longrightarrow;</code> </td> <td> U+027F9 </td> <tr><td> <code title="">Lopf;</code> </td> <td> U+1D543 </td> <tr><td> <code title="">LowerLeftArrow;</code> </td> <td> U+02199 </td> <tr><td> <code title="">LowerRightArrow;</code> </td> <td> U+02198 </td> <tr><td> <code title="">Lscr;</code> </td> <td> U+02112 </td> <tr><td> <code title="">Lsh;</code> </td> <td> U+021B0 </td> <tr><td> <code title="">Lstrok;</code> </td> <td> U+00141 </td> <tr><td> <code title="">Lt;</code> </td> <td> U+0226A </td> <tr><td> <code title="">Map;</code> </td> <td> U+02905 </td> <tr><td> <code title="">Mcy;</code> </td> <td> U+0041C </td> <tr><td> <code title="">MediumSpace;</code> </td> <td> U+0205F </td> <tr><td> <code title="">Mellintrf;</code> </td> <td> U+02133 </td> <tr><td> <code title="">Mfr;</code> </td> <td> U+1D510 </td> <tr><td> <code title="">MinusPlus;</code> </td> <td> U+02213 </td> <tr><td> <code title="">Mopf;</code> </td> <td> U+1D544 </td> <tr><td> <code title="">Mscr;</code> </td> <td> U+02133 </td> <tr><td> <code title="">Mu;</code> </td> <td> U+0039C </td> <tr><td> <code title="">NJcy;</code> </td> <td> U+0040A </td> <tr><td> <code title="">Nacute;</code> </td> <td> U+00143 </td> <tr><td> <code title="">Ncaron;</code> </td> <td> U+00147 </td> <tr><td> <code title="">Ncedil;</code> </td> <td> U+00145 </td> <tr><td> <code title="">Ncy;</code> </td> <td> U+0041D </td> <tr><td> <code title="">NegativeMediumSpace;</code> </td> <td> U+0200B </td> <tr><td> <code title="">NegativeThickSpace;</code> </td> <td> U+0200B </td> <tr><td> <code title="">NegativeThinSpace;</code> </td> <td> U+0200B </td> <tr><td> <code title="">NegativeVeryThinSpace;</code> </td> <td> U+0200B </td> <tr><td> <code title="">NestedGreaterGreater;</code> </td> <td> U+0226B </td> <tr><td> <code title="">NestedLessLess;</code> </td> <td> U+0226A </td> <tr><td> <code title="">NewLine;</code> </td> <td> U+0000A </td> <tr><td> <code title="">Nfr;</code> </td> <td> U+1D511 </td> <tr><td> <code title="">NoBreak;</code> </td> <td> U+02060 </td> <tr><td> <code title="">NonBreakingSpace;</code> </td> <td> U+000A0 </td> <tr><td> <code title="">Nopf;</code> </td> <td> U+02115 </td> <tr><td> <code title="">Not;</code> </td> <td> U+02AEC </td> <tr><td> <code title="">NotCongruent;</code> </td> <td> U+02262 </td> <tr><td> <code title="">NotCupCap;</code> </td> <td> U+0226D </td> <tr><td> <code title="">NotDoubleVerticalBar;</code> </td> <td> U+02226 </td> <tr><td> <code title="">NotElement;</code> </td> <td> U+02209 </td> <tr><td> <code title="">NotEqual;</code> </td> <td> U+02260 </td> <tr><td> <code title="">NotExists;</code> </td> <td> U+02204 </td> <tr><td> <code title="">NotGreater;</code> </td> <td> U+0226F </td> <tr><td> <code title="">NotGreaterEqual;</code> </td> <td> U+02271 </td> <tr><td> <code title="">NotGreaterLess;</code> </td> <td> U+02279 </td> <tr><td> <code title="">NotGreaterTilde;</code> </td> <td> U+02275 </td> <tr><td> <code title="">NotLeftTriangle;</code> </td> <td> U+022EA </td> <tr><td> <code title="">NotLeftTriangleEqual;</code> </td> <td> U+022EC </td> <tr><td> <code title="">NotLess;</code> </td> <td> U+0226E </td> <tr><td> <code title="">NotLessEqual;</code> </td> <td> U+02270 </td> <tr><td> <code title="">NotLessGreater;</code> </td> <td> U+02278 </td> <tr><td> <code title="">NotLessTilde;</code> </td> <td> U+02274 </td> <tr><td> <code title="">NotPrecedes;</code> </td> <td> U+02280 </td> <tr><td> <code title="">NotPrecedesSlantEqual;</code> </td> <td> U+022E0 </td> <tr><td> <code title="">NotReverseElement;</code> </td> <td> U+0220C </td> <tr><td> <code title="">NotRightTriangle;</code> </td> <td> U+022EB </td> <tr><td> <code title="">NotRightTriangleEqual;</code> </td> <td> U+022ED </td> <tr><td> <code title="">NotSquareSubsetEqual;</code> </td> <td> U+022E2 </td> <tr><td> <code title="">NotSquareSupersetEqual;</code> </td> <td> U+022E3 </td> <tr><td> <code title="">NotSubsetEqual;</code> </td> <td> U+02288 </td> <tr><td> <code title="">NotSucceeds;</code> </td> <td> U+02281 </td> <tr><td> <code title="">NotSucceedsSlantEqual;</code> </td> <td> U+022E1 </td> <tr><td> <code title="">NotSupersetEqual;</code> </td> <td> U+02289 </td> <tr><td> <code title="">NotTilde;</code> </td> <td> U+02241 </td> <tr><td> <code title="">NotTildeEqual;</code> </td> <td> U+02244 </td> <tr><td> <code title="">NotTildeFullEqual;</code> </td> <td> U+02247 </td> <tr><td> <code title="">NotTildeTilde;</code> </td> <td> U+02249 </td> <tr><td> <code title="">NotVerticalBar;</code> </td> <td> U+02224 </td> <tr><td> <code title="">Nscr;</code> </td> <td> U+1D4A9 </td> <tr><td> <code title="">Ntilde;</code> </td> <td> U+000D1 </td> <tr><td> <code title="">Ntilde</code> </td> <td> U+000D1 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Nu;</code> </td> <td> U+0039D </td> <tr><td> <code title="">OElig;</code> </td> <td> U+00152 </td> <tr><td> <code title="">Oacute;</code> </td> <td> U+000D3 </td> <tr><td> <code title="">Oacute</code> </td> <td> U+000D3 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Ocirc;</code> </td> <td> U+000D4 </td> <tr><td> <code title="">Ocirc</code> </td> <td> U+000D4 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Ocy;</code> </td> <td> U+0041E </td> <tr><td> <code title="">Odblac;</code> </td> <td> U+00150 </td> <tr><td> <code title="">Ofr;</code> </td> <td> U+1D512 </td> <tr><td> <code title="">Ograve;</code> </td> <td> U+000D2 </td> <tr><td> <code title="">Ograve</code> </td> <td> U+000D2 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Omacr;</code> </td> <td> U+0014C </td> <tr><td> <code title="">Omega;</code> </td> <td> U+003A9 </td> <tr><td> <code title="">Omicron;</code> </td> <td> U+0039F </td> <tr><td> <code title="">Oopf;</code> </td> <td> U+1D546 </td> <tr><td> <code title="">OpenCurlyDoubleQuote;</code> </td> <td> U+0201C </td> <tr><td> <code title="">OpenCurlyQuote;</code> </td> <td> U+02018 </td> <tr><td> <code title="">Or;</code> </td> <td> U+02A54 </td> <tr><td> <code title="">Oscr;</code> </td> <td> U+1D4AA </td> <tr><td> <code title="">Oslash;</code> </td> <td> U+000D8 </td> <tr><td> <code title="">Oslash</code> </td> <td> U+000D8 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Otilde;</code> </td> <td> U+000D5 </td> <tr><td> <code title="">Otilde</code> </td> <td> U+000D5 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Otimes;</code> </td> <td> U+02A37 </td> <tr><td> <code title="">Ouml;</code> </td> <td> U+000D6 </td> <tr><td> <code title="">Ouml</code> </td> <td> U+000D6 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">OverBar;</code> </td> <td> U+0203E </td> <tr><td> <code title="">OverBrace;</code> </td> <td> U+023DE </td> <tr><td> <code title="">OverBracket;</code> </td> <td> U+023B4 </td> <tr><td> <code title="">OverParenthesis;</code> </td> <td> U+023DC </td> <tr><td> <code title="">PartialD;</code> </td> <td> U+02202 </td> <tr><td> <code title="">Pcy;</code> </td> <td> U+0041F </td> <tr><td> <code title="">Pfr;</code> </td> <td> U+1D513 </td> <tr><td> <code title="">Phi;</code> </td> <td> U+003A6 </td> <tr><td> <code title="">Pi;</code> </td> <td> U+003A0 </td> <tr><td> <code title="">PlusMinus;</code> </td> <td> U+000B1 </td> <tr><td> <code title="">Poincareplane;</code> </td> <td> U+0210C </td> <tr><td> <code title="">Popf;</code> </td> <td> U+02119 </td> <tr><td> <code title="">Pr;</code> </td> <td> U+02ABB </td> <tr><td> <code title="">Precedes;</code> </td> <td> U+0227A </td> <tr><td> <code title="">PrecedesEqual;</code> </td> <td> U+02AAF </td> <tr><td> <code title="">PrecedesSlantEqual;</code> </td> <td> U+0227C </td> <tr><td> <code title="">PrecedesTilde;</code> </td> <td> U+0227E </td> <tr><td> <code title="">Prime;</code> </td> <td> U+02033 </td> <tr><td> <code title="">Product;</code> </td> <td> U+0220F </td> <tr><td> <code title="">Proportion;</code> </td> <td> U+02237 </td> <tr><td> <code title="">Proportional;</code> </td> <td> U+0221D </td> <tr><td> <code title="">Pscr;</code> </td> <td> U+1D4AB </td> <tr><td> <code title="">Psi;</code> </td> <td> U+003A8 </td> <tr><td> <code title="">QUOT;</code> </td> <td> U+00022 </td> <tr><td> <code title="">QUOT</code> </td> <td> U+00022 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Qfr;</code> </td> <td> U+1D514 </td> <tr><td> <code title="">Qopf;</code> </td> <td> U+0211A </td> <tr><td> <code title="">Qscr;</code> </td> <td> U+1D4AC </td> <tr><td> <code title="">RBarr;</code> </td> <td> U+02910 </td> <tr><td> <code title="">REG;</code> </td> <td> U+000AE </td> <tr><td> <code title="">REG</code> </td> <td> U+000AE </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Racute;</code> </td> <td> U+00154 </td> <tr><td> <code title="">Rang;</code> </td> <td> U+027EB </td> <tr><td> <code title="">Rarr;</code> </td> <td> U+021A0 </td> <tr><td> <code title="">Rarrtl;</code> </td> <td> U+02916 </td> <tr><td> <code title="">Rcaron;</code> </td> <td> U+00158 </td> <tr><td> <code title="">Rcedil;</code> </td> <td> U+00156 </td> <tr><td> <code title="">Rcy;</code> </td> <td> U+00420 </td> <tr><td> <code title="">Re;</code> </td> <td> U+0211C </td> <tr><td> <code title="">ReverseElement;</code> </td> <td> U+0220B </td> <tr><td> <code title="">ReverseEquilibrium;</code> </td> <td> U+021CB </td> <tr><td> <code title="">ReverseUpEquilibrium;</code> </td> <td> U+0296F </td> <tr><td> <code title="">Rfr;</code> </td> <td> U+0211C </td> <tr><td> <code title="">Rho;</code> </td> <td> U+003A1 </td> <tr><td> <code title="">RightAngleBracket;</code> </td> <td> U+027E9 </td> <tr><td> <code title="">RightArrow;</code> </td> <td> U+02192 </td> <tr><td> <code title="">RightArrowBar;</code> </td> <td> U+021E5 </td> <tr><td> <code title="">RightArrowLeftArrow;</code> </td> <td> U+021C4 </td> <tr><td> <code title="">RightCeiling;</code> </td> <td> U+02309 </td> <tr><td> <code title="">RightDoubleBracket;</code> </td> <td> U+027E7 </td> <tr><td> <code title="">RightDownTeeVector;</code> </td> <td> U+0295D </td> <tr><td> <code title="">RightDownVector;</code> </td> <td> U+021C2 </td> <tr><td> <code title="">RightDownVectorBar;</code> </td> <td> U+02955 </td> <tr><td> <code title="">RightFloor;</code> </td> <td> U+0230B </td> <tr><td> <code title="">RightTee;</code> </td> <td> U+022A2 </td> <tr><td> <code title="">RightTeeArrow;</code> </td> <td> U+021A6 </td> <tr><td> <code title="">RightTeeVector;</code> </td> <td> U+0295B </td> <tr><td> <code title="">RightTriangle;</code> </td> <td> U+022B3 </td> <tr><td> <code title="">RightTriangleBar;</code> </td> <td> U+029D0 </td> <tr><td> <code title="">RightTriangleEqual;</code> </td> <td> U+022B5 </td> <tr><td> <code title="">RightUpDownVector;</code> </td> <td> U+0294F </td> <tr><td> <code title="">RightUpTeeVector;</code> </td> <td> U+0295C </td> <tr><td> <code title="">RightUpVector;</code> </td> <td> U+021BE </td> <tr><td> <code title="">RightUpVectorBar;</code> </td> <td> U+02954 </td> <tr><td> <code title="">RightVector;</code> </td> <td> U+021C0 </td> <tr><td> <code title="">RightVectorBar;</code> </td> <td> U+02953 </td> <tr><td> <code title="">Rightarrow;</code> </td> <td> U+021D2 </td> <tr><td> <code title="">Ropf;</code> </td> <td> U+0211D </td> <tr><td> <code title="">RoundImplies;</code> </td> <td> U+02970 </td> <tr><td> <code title="">Rrightarrow;</code> </td> <td> U+021DB </td> <tr><td> <code title="">Rscr;</code> </td> <td> U+0211B </td> <tr><td> <code title="">Rsh;</code> </td> <td> U+021B1 </td> <tr><td> <code title="">RuleDelayed;</code> </td> <td> U+029F4 </td> <tr><td> <code title="">SHCHcy;</code> </td> <td> U+00429 </td> <tr><td> <code title="">SHcy;</code> </td> <td> U+00428 </td> <tr><td> <code title="">SOFTcy;</code> </td> <td> U+0042C </td> <tr><td> <code title="">Sacute;</code> </td> <td> U+0015A </td> <tr><td> <code title="">Sc;</code> </td> <td> U+02ABC </td> <tr><td> <code title="">Scaron;</code> </td> <td> U+00160 </td> <tr><td> <code title="">Scedil;</code> </td> <td> U+0015E </td> <tr><td> <code title="">Scirc;</code> </td> <td> U+0015C </td> <tr><td> <code title="">Scy;</code> </td> <td> U+00421 </td> <tr><td> <code title="">Sfr;</code> </td> <td> U+1D516 </td> <tr><td> <code title="">ShortDownArrow;</code> </td> <td> U+02193 </td> <tr><td> <code title="">ShortLeftArrow;</code> </td> <td> U+02190 </td> <tr><td> <code title="">ShortRightArrow;</code> </td> <td> U+02192 </td> <tr><td> <code title="">ShortUpArrow;</code> </td> <td> U+02191 </td> <tr><td> <code title="">Sigma;</code> </td> <td> U+003A3 </td> <tr><td> <code title="">SmallCircle;</code> </td> <td> U+02218 </td> <tr><td> <code title="">Sopf;</code> </td> <td> U+1D54A </td> <tr><td> <code title="">Sqrt;</code> </td> <td> U+0221A </td> <tr><td> <code title="">Square;</code> </td> <td> U+025A1 </td> <tr><td> <code title="">SquareIntersection;</code> </td> <td> U+02293 </td> <tr><td> <code title="">SquareSubset;</code> </td> <td> U+0228F </td> <tr><td> <code title="">SquareSubsetEqual;</code> </td> <td> U+02291 </td> <tr><td> <code title="">SquareSuperset;</code> </td> <td> U+02290 </td> <tr><td> <code title="">SquareSupersetEqual;</code> </td> <td> U+02292 </td> <tr><td> <code title="">SquareUnion;</code> </td> <td> U+02294 </td> <tr><td> <code title="">Sscr;</code> </td> <td> U+1D4AE </td> <tr><td> <code title="">Star;</code> </td> <td> U+022C6 </td> <tr><td> <code title="">Sub;</code> </td> <td> U+022D0 </td> <tr><td> <code title="">Subset;</code> </td> <td> U+022D0 </td> <tr><td> <code title="">SubsetEqual;</code> </td> <td> U+02286 </td> <tr><td> <code title="">Succeeds;</code> </td> <td> U+0227B </td> <tr><td> <code title="">SucceedsEqual;</code> </td> <td> U+02AB0 </td> <tr><td> <code title="">SucceedsSlantEqual;</code> </td> <td> U+0227D </td> <tr><td> <code title="">SucceedsTilde;</code> </td> <td> U+0227F </td> <tr><td> <code title="">SuchThat;</code> </td> <td> U+0220B </td> <tr><td> <code title="">Sum;</code> </td> <td> U+02211 </td> <tr><td> <code title="">Sup;</code> </td> <td> U+022D1 </td> <tr><td> <code title="">Superset;</code> </td> <td> U+02283 </td> <tr><td> <code title="">SupersetEqual;</code> </td> <td> U+02287 </td> <tr><td> <code title="">Supset;</code> </td> <td> U+022D1 </td> <tr><td> <code title="">THORN;</code> </td> <td> U+000DE </td> <tr><td> <code title="">THORN</code> </td> <td> U+000DE </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">TRADE;</code> </td> <td> U+02122 </td> <tr><td> <code title="">TSHcy;</code> </td> <td> U+0040B </td> <tr><td> <code title="">TScy;</code> </td> <td> U+00426 </td> <tr><td> <code title="">Tab;</code> </td> <td> U+00009 </td> <tr><td> <code title="">Tau;</code> </td> <td> U+003A4 </td> <tr><td> <code title="">Tcaron;</code> </td> <td> U+00164 </td> <tr><td> <code title="">Tcedil;</code> </td> <td> U+00162 </td> <tr><td> <code title="">Tcy;</code> </td> <td> U+00422 </td> <tr><td> <code title="">Tfr;</code> </td> <td> U+1D517 </td> <tr><td> <code title="">Therefore;</code> </td> <td> U+02234 </td> <tr><td> <code title="">Theta;</code> </td> <td> U+00398 </td> <tr><td> <code title="">ThinSpace;</code> </td> <td> U+02009 </td> <tr><td> <code title="">Tilde;</code> </td> <td> U+0223C </td> <tr><td> <code title="">TildeEqual;</code> </td> <td> U+02243 </td> <tr><td> <code title="">TildeFullEqual;</code> </td> <td> U+02245 </td> <tr><td> <code title="">TildeTilde;</code> </td> <td> U+02248 </td> <tr><td> <code title="">Topf;</code> </td> <td> U+1D54B </td> <tr><td> <code title="">TripleDot;</code> </td> <td> U+020DB </td> <tr><td> <code title="">Tscr;</code> </td> <td> U+1D4AF </td> <tr><td> <code title="">Tstrok;</code> </td> <td> U+00166 </td> <tr><td> <code title="">Uacute;</code> </td> <td> U+000DA </td> <tr><td> <code title="">Uacute</code> </td> <td> U+000DA </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Uarr;</code> </td> <td> U+0219F </td> <tr><td> <code title="">Uarrocir;</code> </td> <td> U+02949 </td> <tr><td> <code title="">Ubrcy;</code> </td> <td> U+0040E </td> <tr><td> <code title="">Ubreve;</code> </td> <td> U+0016C </td> <tr><td> <code title="">Ucirc;</code> </td> <td> U+000DB </td> <tr><td> <code title="">Ucirc</code> </td> <td> U+000DB </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Ucy;</code> </td> <td> U+00423 </td> <tr><td> <code title="">Udblac;</code> </td> <td> U+00170 </td> <tr><td> <code title="">Ufr;</code> </td> <td> U+1D518 </td> <tr><td> <code title="">Ugrave;</code> </td> <td> U+000D9 </td> <tr><td> <code title="">Ugrave</code> </td> <td> U+000D9 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Umacr;</code> </td> <td> U+0016A </td> <tr><td> <code title="">UnderBar;</code> </td> <td> U+0005F </td> <tr><td> <code title="">UnderBrace;</code> </td> <td> U+023DF </td> <tr><td> <code title="">UnderBracket;</code> </td> <td> U+023B5 </td> <tr><td> <code title="">UnderParenthesis;</code> </td> <td> U+023DD </td> <tr><td> <code title="">Union;</code> </td> <td> U+022C3 </td> <tr><td> <code title="">UnionPlus;</code> </td> <td> U+0228E </td> <tr><td> <code title="">Uogon;</code> </td> <td> U+00172 </td> <tr><td> <code title="">Uopf;</code> </td> <td> U+1D54C </td> <tr><td> <code title="">UpArrow;</code> </td> <td> U+02191 </td> <tr><td> <code title="">UpArrowBar;</code> </td> <td> U+02912 </td> <tr><td> <code title="">UpArrowDownArrow;</code> </td> <td> U+021C5 </td> <tr><td> <code title="">UpDownArrow;</code> </td> <td> U+02195 </td> <tr><td> <code title="">UpEquilibrium;</code> </td> <td> U+0296E </td> <tr><td> <code title="">UpTee;</code> </td> <td> U+022A5 </td> <tr><td> <code title="">UpTeeArrow;</code> </td> <td> U+021A5 </td> <tr><td> <code title="">Uparrow;</code> </td> <td> U+021D1 </td> <tr><td> <code title="">Updownarrow;</code> </td> <td> U+021D5 </td> <tr><td> <code title="">UpperLeftArrow;</code> </td> <td> U+02196 </td> <tr><td> <code title="">UpperRightArrow;</code> </td> <td> U+02197 </td> <tr><td> <code title="">Upsi;</code> </td> <td> U+003D2 </td> <tr><td> <code title="">Upsilon;</code> </td> <td> U+003A5 </td> <tr><td> <code title="">Uring;</code> </td> <td> U+0016E </td> <tr><td> <code title="">Uscr;</code> </td> <td> U+1D4B0 </td> <tr><td> <code title="">Utilde;</code> </td> <td> U+00168 </td> <tr><td> <code title="">Uuml;</code> </td> <td> U+000DC </td> <tr><td> <code title="">Uuml</code> </td> <td> U+000DC </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">VDash;</code> </td> <td> U+022AB </td> <tr><td> <code title="">Vbar;</code> </td> <td> U+02AEB </td> <tr><td> <code title="">Vcy;</code> </td> <td> U+00412 </td> <tr><td> <code title="">Vdash;</code> </td> <td> U+022A9 </td> <tr><td> <code title="">Vdashl;</code> </td> <td> U+02AE6 </td> <tr><td> <code title="">Vee;</code> </td> <td> U+022C1 </td> <tr><td> <code title="">Verbar;</code> </td> <td> U+02016 </td> <tr><td> <code title="">Vert;</code> </td> <td> U+02016 </td> <tr><td> <code title="">VerticalBar;</code> </td> <td> U+02223 </td> <tr><td> <code title="">VerticalLine;</code> </td> <td> U+0007C </td> <tr><td> <code title="">VerticalSeparator;</code> </td> <td> U+02758 </td> <tr><td> <code title="">VerticalTilde;</code> </td> <td> U+02240 </td> <tr><td> <code title="">VeryThinSpace;</code> </td> <td> U+0200A </td> <tr><td> <code title="">Vfr;</code> </td> <td> U+1D519 </td> <tr><td> <code title="">Vopf;</code> </td> <td> U+1D54D </td> <tr><td> <code title="">Vscr;</code> </td> <td> U+1D4B1 </td> <tr><td> <code title="">Vvdash;</code> </td> <td> U+022AA </td> <tr><td> <code title="">Wcirc;</code> </td> <td> U+00174 </td> <tr><td> <code title="">Wedge;</code> </td> <td> U+022C0 </td> <tr><td> <code title="">Wfr;</code> </td> <td> U+1D51A </td> <tr><td> <code title="">Wopf;</code> </td> <td> U+1D54E </td> <tr><td> <code title="">Wscr;</code> </td> <td> U+1D4B2 </td> <tr><td> <code title="">Xfr;</code> </td> <td> U+1D51B </td> <tr><td> <code title="">Xi;</code> </td> <td> U+0039E </td> <tr><td> <code title="">Xopf;</code> </td> <td> U+1D54F </td> <tr><td> <code title="">Xscr;</code> </td> <td> U+1D4B3 </td> <tr><td> <code title="">YAcy;</code> </td> <td> U+0042F </td> <tr><td> <code title="">YIcy;</code> </td> <td> U+00407 </td> <tr><td> <code title="">YUcy;</code> </td> <td> U+0042E </td> <tr><td> <code title="">Yacute;</code> </td> <td> U+000DD </td> <tr><td> <code title="">Yacute</code> </td> <td> U+000DD </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">Ycirc;</code> </td> <td> U+00176 </td> <tr><td> <code title="">Ycy;</code> </td> <td> U+0042B </td> <tr><td> <code title="">Yfr;</code> </td> <td> U+1D51C </td> <tr><td> <code title="">Yopf;</code> </td> <td> U+1D550 </td> <tr><td> <code title="">Yscr;</code> </td> <td> U+1D4B4 </td> <tr><td> <code title="">Yuml;</code> </td> <td> U+00178 </td> <tr><td> <code title="">ZHcy;</code> </td> <td> U+00416 </td> <tr><td> <code title="">Zacute;</code> </td> <td> U+00179 </td> <tr><td> <code title="">Zcaron;</code> </td> <td> U+0017D </td> <tr><td> <code title="">Zcy;</code> </td> <td> U+00417 </td> <tr><td> <code title="">Zdot;</code> </td> <td> U+0017B </td> <tr><td> <code title="">ZeroWidthSpace;</code> </td> <td> U+0200B </td> <tr><td> <code title="">Zeta;</code> </td> <td> U+00396 </td> <tr><td> <code title="">Zfr;</code> </td> <td> U+02128 </td> <tr><td> <code title="">Zopf;</code> </td> <td> U+02124 </td> <tr><td> <code title="">Zscr;</code> </td> <td> U+1D4B5 </td> <tr><td> <code title="">aacute;</code> </td> <td> U+000E1 </td> <tr><td> <code title="">aacute</code> </td> <td> U+000E1 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">abreve;</code> </td> <td> U+00103 </td> <tr><td> <code title="">ac;</code> </td> <td> U+0223E </td> <tr><td> <code title="">acd;</code> </td> <td> U+0223F </td> <tr><td> <code title="">acirc;</code> </td> <td> U+000E2 </td> <tr><td> <code title="">acirc</code> </td> <td> U+000E2 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">acute;</code> </td> <td> U+000B4 </td> <tr><td> <code title="">acute</code> </td> <td> U+000B4 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">acy;</code> </td> <td> U+00430 </td> <tr><td> <code title="">aelig;</code> </td> <td> U+000E6 </td> <tr><td> <code title="">aelig</code> </td> <td> U+000E6 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">af;</code> </td> <td> U+02061 </td> <tr><td> <code title="">afr;</code> </td> <td> U+1D51E </td> <tr><td> <code title="">agrave;</code> </td> <td> U+000E0 </td> <tr><td> <code title="">agrave</code> </td> <td> U+000E0 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">alefsym;</code> </td> <td> U+02135 </td> <tr><td> <code title="">aleph;</code> </td> <td> U+02135 </td> <tr><td> <code title="">alpha;</code> </td> <td> U+003B1 </td> <tr><td> <code title="">amacr;</code> </td> <td> U+00101 </td> <tr><td> <code title="">amalg;</code> </td> <td> U+02A3F </td> <tr><td> <code title="">amp;</code> </td> <td> U+00026 </td> <tr><td> <code title="">amp</code> </td> <td> U+00026 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">and;</code> </td> <td> U+02227 </td> <tr><td> <code title="">andand;</code> </td> <td> U+02A55 </td> <tr><td> <code title="">andd;</code> </td> <td> U+02A5C </td> <tr><td> <code title="">andslope;</code> </td> <td> U+02A58 </td> <tr><td> <code title="">andv;</code> </td> <td> U+02A5A </td> <tr><td> <code title="">ang;</code> </td> <td> U+02220 </td> <tr><td> <code title="">ange;</code> </td> <td> U+029A4 </td> <tr><td> <code title="">angle;</code> </td> <td> U+02220 </td> <tr><td> <code title="">angmsd;</code> </td> <td> U+02221 </td> <tr><td> <code title="">angmsdaa;</code> </td> <td> U+029A8 </td> <tr><td> <code title="">angmsdab;</code> </td> <td> U+029A9 </td> <tr><td> <code title="">angmsdac;</code> </td> <td> U+029AA </td> <tr><td> <code title="">angmsdad;</code> </td> <td> U+029AB </td> <tr><td> <code title="">angmsdae;</code> </td> <td> U+029AC </td> <tr><td> <code title="">angmsdaf;</code> </td> <td> U+029AD </td> <tr><td> <code title="">angmsdag;</code> </td> <td> U+029AE </td> <tr><td> <code title="">angmsdah;</code> </td> <td> U+029AF </td> <tr><td> <code title="">angrt;</code> </td> <td> U+0221F </td> <tr><td> <code title="">angrtvb;</code> </td> <td> U+022BE </td> <tr><td> <code title="">angrtvbd;</code> </td> <td> U+0299D </td> <tr><td> <code title="">angsph;</code> </td> <td> U+02222 </td> <tr><td> <code title="">angst;</code> </td> <td> U+000C5 </td> <tr><td> <code title="">angzarr;</code> </td> <td> U+0237C </td> <tr><td> <code title="">aogon;</code> </td> <td> U+00105 </td> <tr><td> <code title="">aopf;</code> </td> <td> U+1D552 </td> <tr><td> <code title="">ap;</code> </td> <td> U+02248 </td> <tr><td> <code title="">apE;</code> </td> <td> U+02A70 </td> <tr><td> <code title="">apacir;</code> </td> <td> U+02A6F </td> <tr><td> <code title="">ape;</code> </td> <td> U+0224A </td> <tr><td> <code title="">apid;</code> </td> <td> U+0224B </td> <tr><td> <code title="">apos;</code> </td> <td> U+00027 </td> <tr><td> <code title="">approx;</code> </td> <td> U+02248 </td> <tr><td> <code title="">approxeq;</code> </td> <td> U+0224A </td> <tr><td> <code title="">aring;</code> </td> <td> U+000E5 </td> <tr><td> <code title="">aring</code> </td> <td> U+000E5 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ascr;</code> </td> <td> U+1D4B6 </td> <tr><td> <code title="">ast;</code> </td> <td> U+0002A </td> <tr><td> <code title="">asymp;</code> </td> <td> U+02248 </td> <tr><td> <code title="">asympeq;</code> </td> <td> U+0224D </td> <tr><td> <code title="">atilde;</code> </td> <td> U+000E3 </td> <tr><td> <code title="">atilde</code> </td> <td> U+000E3 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">auml;</code> </td> <td> U+000E4 </td> <tr><td> <code title="">auml</code> </td> <td> U+000E4 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">awconint;</code> </td> <td> U+02233 </td> <tr><td> <code title="">awint;</code> </td> <td> U+02A11 </td> <tr><td> <code title="">bNot;</code> </td> <td> U+02AED </td> <tr><td> <code title="">backcong;</code> </td> <td> U+0224C </td> <tr><td> <code title="">backepsilon;</code> </td> <td> U+003F6 </td> <tr><td> <code title="">backprime;</code> </td> <td> U+02035 </td> <tr><td> <code title="">backsim;</code> </td> <td> U+0223D </td> <tr><td> <code title="">backsimeq;</code> </td> <td> U+022CD </td> <tr><td> <code title="">barvee;</code> </td> <td> U+022BD </td> <tr><td> <code title="">barwed;</code> </td> <td> U+02305 </td> <tr><td> <code title="">barwedge;</code> </td> <td> U+02305 </td> <tr><td> <code title="">bbrk;</code> </td> <td> U+023B5 </td> <tr><td> <code title="">bbrktbrk;</code> </td> <td> U+023B6 </td> <tr><td> <code title="">bcong;</code> </td> <td> U+0224C </td> <tr><td> <code title="">bcy;</code> </td> <td> U+00431 </td> <tr><td> <code title="">bdquo;</code> </td> <td> U+0201E </td> <tr><td> <code title="">becaus;</code> </td> <td> U+02235 </td> <tr><td> <code title="">because;</code> </td> <td> U+02235 </td> <tr><td> <code title="">bemptyv;</code> </td> <td> U+029B0 </td> <tr><td> <code title="">bepsi;</code> </td> <td> U+003F6 </td> <tr><td> <code title="">bernou;</code> </td> <td> U+0212C </td> <tr><td> <code title="">beta;</code> </td> <td> U+003B2 </td> <tr><td> <code title="">beth;</code> </td> <td> U+02136 </td> <tr><td> <code title="">between;</code> </td> <td> U+0226C </td> <tr><td> <code title="">bfr;</code> </td> <td> U+1D51F </td> <tr><td> <code title="">bigcap;</code> </td> <td> U+022C2 </td> <tr><td> <code title="">bigcirc;</code> </td> <td> U+025EF </td> <tr><td> <code title="">bigcup;</code> </td> <td> U+022C3 </td> <tr><td> <code title="">bigodot;</code> </td> <td> U+02A00 </td> <tr><td> <code title="">bigoplus;</code> </td> <td> U+02A01 </td> <tr><td> <code title="">bigotimes;</code> </td> <td> U+02A02 </td> <tr><td> <code title="">bigsqcup;</code> </td> <td> U+02A06 </td> <tr><td> <code title="">bigstar;</code> </td> <td> U+02605 </td> <tr><td> <code title="">bigtriangledown;</code> </td> <td> U+025BD </td> <tr><td> <code title="">bigtriangleup;</code> </td> <td> U+025B3 </td> <tr><td> <code title="">biguplus;</code> </td> <td> U+02A04 </td> <tr><td> <code title="">bigvee;</code> </td> <td> U+022C1 </td> <tr><td> <code title="">bigwedge;</code> </td> <td> U+022C0 </td> <tr><td> <code title="">bkarow;</code> </td> <td> U+0290D </td> <tr><td> <code title="">blacklozenge;</code> </td> <td> U+029EB </td> <tr><td> <code title="">blacksquare;</code> </td> <td> U+025AA </td> <tr><td> <code title="">blacktriangle;</code> </td> <td> U+025B4 </td> <tr><td> <code title="">blacktriangledown;</code> </td> <td> U+025BE </td> <tr><td> <code title="">blacktriangleleft;</code> </td> <td> U+025C2 </td> <tr><td> <code title="">blacktriangleright;</code> </td> <td> U+025B8 </td> <tr><td> <code title="">blank;</code> </td> <td> U+02423 </td> <tr><td> <code title="">blk12;</code> </td> <td> U+02592 </td> <tr><td> <code title="">blk14;</code> </td> <td> U+02591 </td> <tr><td> <code title="">blk34;</code> </td> <td> U+02593 </td> <tr><td> <code title="">block;</code> </td> <td> U+02588 </td> <tr><td> <code title="">bnot;</code> </td> <td> U+02310 </td> <tr><td> <code title="">bopf;</code> </td> <td> U+1D553 </td> <tr><td> <code title="">bot;</code> </td> <td> U+022A5 </td> <tr><td> <code title="">bottom;</code> </td> <td> U+022A5 </td> <tr><td> <code title="">bowtie;</code> </td> <td> U+022C8 </td> <tr><td> <code title="">boxDL;</code> </td> <td> U+02557 </td> <tr><td> <code title="">boxDR;</code> </td> <td> U+02554 </td> <tr><td> <code title="">boxDl;</code> </td> <td> U+02556 </td> <tr><td> <code title="">boxDr;</code> </td> <td> U+02553 </td> <tr><td> <code title="">boxH;</code> </td> <td> U+02550 </td> <tr><td> <code title="">boxHD;</code> </td> <td> U+02566 </td> <tr><td> <code title="">boxHU;</code> </td> <td> U+02569 </td> <tr><td> <code title="">boxHd;</code> </td> <td> U+02564 </td> <tr><td> <code title="">boxHu;</code> </td> <td> U+02567 </td> <tr><td> <code title="">boxUL;</code> </td> <td> U+0255D </td> <tr><td> <code title="">boxUR;</code> </td> <td> U+0255A </td> <tr><td> <code title="">boxUl;</code> </td> <td> U+0255C </td> <tr><td> <code title="">boxUr;</code> </td> <td> U+02559 </td> <tr><td> <code title="">boxV;</code> </td> <td> U+02551 </td> <tr><td> <code title="">boxVH;</code> </td> <td> U+0256C </td> <tr><td> <code title="">boxVL;</code> </td> <td> U+02563 </td> <tr><td> <code title="">boxVR;</code> </td> <td> U+02560 </td> <tr><td> <code title="">boxVh;</code> </td> <td> U+0256B </td> <tr><td> <code title="">boxVl;</code> </td> <td> U+02562 </td> <tr><td> <code title="">boxVr;</code> </td> <td> U+0255F </td> <tr><td> <code title="">boxbox;</code> </td> <td> U+029C9 </td> <tr><td> <code title="">boxdL;</code> </td> <td> U+02555 </td> <tr><td> <code title="">boxdR;</code> </td> <td> U+02552 </td> <tr><td> <code title="">boxdl;</code> </td> <td> U+02510 </td> <tr><td> <code title="">boxdr;</code> </td> <td> U+0250C </td> <tr><td> <code title="">boxh;</code> </td> <td> U+02500 </td> <tr><td> <code title="">boxhD;</code> </td> <td> U+02565 </td> <tr><td> <code title="">boxhU;</code> </td> <td> U+02568 </td> <tr><td> <code title="">boxhd;</code> </td> <td> U+0252C </td> <tr><td> <code title="">boxhu;</code> </td> <td> U+02534 </td> <tr><td> <code title="">boxminus;</code> </td> <td> U+0229F </td> <tr><td> <code title="">boxplus;</code> </td> <td> U+0229E </td> <tr><td> <code title="">boxtimes;</code> </td> <td> U+022A0 </td> <tr><td> <code title="">boxuL;</code> </td> <td> U+0255B </td> <tr><td> <code title="">boxuR;</code> </td> <td> U+02558 </td> <tr><td> <code title="">boxul;</code> </td> <td> U+02518 </td> <tr><td> <code title="">boxur;</code> </td> <td> U+02514 </td> <tr><td> <code title="">boxv;</code> </td> <td> U+02502 </td> <tr><td> <code title="">boxvH;</code> </td> <td> U+0256A </td> <tr><td> <code title="">boxvL;</code> </td> <td> U+02561 </td> <tr><td> <code title="">boxvR;</code> </td> <td> U+0255E </td> <tr><td> <code title="">boxvh;</code> </td> <td> U+0253C </td> <tr><td> <code title="">boxvl;</code> </td> <td> U+02524 </td> <tr><td> <code title="">boxvr;</code> </td> <td> U+0251C </td> <tr><td> <code title="">bprime;</code> </td> <td> U+02035 </td> <tr><td> <code title="">breve;</code> </td> <td> U+002D8 </td> <tr><td> <code title="">brvbar;</code> </td> <td> U+000A6 </td> <tr><td> <code title="">brvbar</code> </td> <td> U+000A6 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">bscr;</code> </td> <td> U+1D4B7 </td> <tr><td> <code title="">bsemi;</code> </td> <td> U+0204F </td> <tr><td> <code title="">bsim;</code> </td> <td> U+0223D </td> <tr><td> <code title="">bsime;</code> </td> <td> U+022CD </td> <tr><td> <code title="">bsol;</code> </td> <td> U+0005C </td> <tr><td> <code title="">bsolb;</code> </td> <td> U+029C5 </td> <tr><td> <code title="">bsolhsub;</code> </td> <td> U+027C8 </td> <tr><td> <code title="">bull;</code> </td> <td> U+02022 </td> <tr><td> <code title="">bullet;</code> </td> <td> U+02022 </td> <tr><td> <code title="">bump;</code> </td> <td> U+0224E </td> <tr><td> <code title="">bumpE;</code> </td> <td> U+02AAE </td> <tr><td> <code title="">bumpe;</code> </td> <td> U+0224F </td> <tr><td> <code title="">bumpeq;</code> </td> <td> U+0224F </td> <tr><td> <code title="">cacute;</code> </td> <td> U+00107 </td> <tr><td> <code title="">cap;</code> </td> <td> U+02229 </td> <tr><td> <code title="">capand;</code> </td> <td> U+02A44 </td> <tr><td> <code title="">capbrcup;</code> </td> <td> U+02A49 </td> <tr><td> <code title="">capcap;</code> </td> <td> U+02A4B </td> <tr><td> <code title="">capcup;</code> </td> <td> U+02A47 </td> <tr><td> <code title="">capdot;</code> </td> <td> U+02A40 </td> <tr><td> <code title="">caret;</code> </td> <td> U+02041 </td> <tr><td> <code title="">caron;</code> </td> <td> U+002C7 </td> <tr><td> <code title="">ccaps;</code> </td> <td> U+02A4D </td> <tr><td> <code title="">ccaron;</code> </td> <td> U+0010D </td> <tr><td> <code title="">ccedil;</code> </td> <td> U+000E7 </td> <tr><td> <code title="">ccedil</code> </td> <td> U+000E7 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ccirc;</code> </td> <td> U+00109 </td> <tr><td> <code title="">ccups;</code> </td> <td> U+02A4C </td> <tr><td> <code title="">ccupssm;</code> </td> <td> U+02A50 </td> <tr><td> <code title="">cdot;</code> </td> <td> U+0010B </td> <tr><td> <code title="">cedil;</code> </td> <td> U+000B8 </td> <tr><td> <code title="">cedil</code> </td> <td> U+000B8 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">cemptyv;</code> </td> <td> U+029B2 </td> <tr><td> <code title="">cent;</code> </td> <td> U+000A2 </td> <tr><td> <code title="">cent</code> </td> <td> U+000A2 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">centerdot;</code> </td> <td> U+000B7 </td> <tr><td> <code title="">cfr;</code> </td> <td> U+1D520 </td> <tr><td> <code title="">chcy;</code> </td> <td> U+00447 </td> <tr><td> <code title="">check;</code> </td> <td> U+02713 </td> <tr><td> <code title="">checkmark;</code> </td> <td> U+02713 </td> <tr><td> <code title="">chi;</code> </td> <td> U+003C7 </td> <tr><td> <code title="">cir;</code> </td> <td> U+025CB </td> <tr><td> <code title="">cirE;</code> </td> <td> U+029C3 </td> <tr><td> <code title="">circ;</code> </td> <td> U+002C6 </td> <tr><td> <code title="">circeq;</code> </td> <td> U+02257 </td> <tr><td> <code title="">circlearrowleft;</code> </td> <td> U+021BA </td> <tr><td> <code title="">circlearrowright;</code> </td> <td> U+021BB </td> <tr><td> <code title="">circledR;</code> </td> <td> U+000AE </td> <tr><td> <code title="">circledS;</code> </td> <td> U+024C8 </td> <tr><td> <code title="">circledast;</code> </td> <td> U+0229B </td> <tr><td> <code title="">circledcirc;</code> </td> <td> U+0229A </td> <tr><td> <code title="">circleddash;</code> </td> <td> U+0229D </td> <tr><td> <code title="">cire;</code> </td> <td> U+02257 </td> <tr><td> <code title="">cirfnint;</code> </td> <td> U+02A10 </td> <tr><td> <code title="">cirmid;</code> </td> <td> U+02AEF </td> <tr><td> <code title="">cirscir;</code> </td> <td> U+029C2 </td> <tr><td> <code title="">clubs;</code> </td> <td> U+02663 </td> <tr><td> <code title="">clubsuit;</code> </td> <td> U+02663 </td> <tr><td> <code title="">colon;</code> </td> <td> U+0003A </td> <tr><td> <code title="">colone;</code> </td> <td> U+02254 </td> <tr><td> <code title="">coloneq;</code> </td> <td> U+02254 </td> <tr><td> <code title="">comma;</code> </td> <td> U+0002C </td> <tr><td> <code title="">commat;</code> </td> <td> U+00040 </td> <tr><td> <code title="">comp;</code> </td> <td> U+02201 </td> <tr><td> <code title="">compfn;</code> </td> <td> U+02218 </td> <tr><td> <code title="">complement;</code> </td> <td> U+02201 </td> <tr><td> <code title="">complexes;</code> </td> <td> U+02102 </td> <tr><td> <code title="">cong;</code> </td> <td> U+02245 </td> <tr><td> <code title="">congdot;</code> </td> <td> U+02A6D </td> <tr><td> <code title="">conint;</code> </td> <td> U+0222E </td> <tr><td> <code title="">copf;</code> </td> <td> U+1D554 </td> <tr><td> <code title="">coprod;</code> </td> <td> U+02210 </td> <tr><td> <code title="">copy;</code> </td> <td> U+000A9 </td> <tr><td> <code title="">copy</code> </td> <td> U+000A9 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">copysr;</code> </td> <td> U+02117 </td> <tr><td> <code title="">crarr;</code> </td> <td> U+021B5 </td> <tr><td> <code title="">cross;</code> </td> <td> U+02717 </td> <tr><td> <code title="">cscr;</code> </td> <td> U+1D4B8 </td> <tr><td> <code title="">csub;</code> </td> <td> U+02ACF </td> <tr><td> <code title="">csube;</code> </td> <td> U+02AD1 </td> <tr><td> <code title="">csup;</code> </td> <td> U+02AD0 </td> <tr><td> <code title="">csupe;</code> </td> <td> U+02AD2 </td> <tr><td> <code title="">ctdot;</code> </td> <td> U+022EF </td> <tr><td> <code title="">cudarrl;</code> </td> <td> U+02938 </td> <tr><td> <code title="">cudarrr;</code> </td> <td> U+02935 </td> <tr><td> <code title="">cuepr;</code> </td> <td> U+022DE </td> <tr><td> <code title="">cuesc;</code> </td> <td> U+022DF </td> <tr><td> <code title="">cularr;</code> </td> <td> U+021B6 </td> <tr><td> <code title="">cularrp;</code> </td> <td> U+0293D </td> <tr><td> <code title="">cup;</code> </td> <td> U+0222A </td> <tr><td> <code title="">cupbrcap;</code> </td> <td> U+02A48 </td> <tr><td> <code title="">cupcap;</code> </td> <td> U+02A46 </td> <tr><td> <code title="">cupcup;</code> </td> <td> U+02A4A </td> <tr><td> <code title="">cupdot;</code> </td> <td> U+0228D </td> <tr><td> <code title="">cupor;</code> </td> <td> U+02A45 </td> <tr><td> <code title="">curarr;</code> </td> <td> U+021B7 </td> <tr><td> <code title="">curarrm;</code> </td> <td> U+0293C </td> <tr><td> <code title="">curlyeqprec;</code> </td> <td> U+022DE </td> <tr><td> <code title="">curlyeqsucc;</code> </td> <td> U+022DF </td> <tr><td> <code title="">curlyvee;</code> </td> <td> U+022CE </td> <tr><td> <code title="">curlywedge;</code> </td> <td> U+022CF </td> <tr><td> <code title="">curren;</code> </td> <td> U+000A4 </td> <tr><td> <code title="">curren</code> </td> <td> U+000A4 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">curvearrowleft;</code> </td> <td> U+021B6 </td> <tr><td> <code title="">curvearrowright;</code> </td> <td> U+021B7 </td> <tr><td> <code title="">cuvee;</code> </td> <td> U+022CE </td> <tr><td> <code title="">cuwed;</code> </td> <td> U+022CF </td> <tr><td> <code title="">cwconint;</code> </td> <td> U+02232 </td> <tr><td> <code title="">cwint;</code> </td> <td> U+02231 </td> <tr><td> <code title="">cylcty;</code> </td> <td> U+0232D </td> <tr><td> <code title="">dArr;</code> </td> <td> U+021D3 </td> <tr><td> <code title="">dHar;</code> </td> <td> U+02965 </td> <tr><td> <code title="">dagger;</code> </td> <td> U+02020 </td> <tr><td> <code title="">daleth;</code> </td> <td> U+02138 </td> <tr><td> <code title="">darr;</code> </td> <td> U+02193 </td> <tr><td> <code title="">dash;</code> </td> <td> U+02010 </td> <tr><td> <code title="">dashv;</code> </td> <td> U+022A3 </td> <tr><td> <code title="">dbkarow;</code> </td> <td> U+0290F </td> <tr><td> <code title="">dblac;</code> </td> <td> U+002DD </td> <tr><td> <code title="">dcaron;</code> </td> <td> U+0010F </td> <tr><td> <code title="">dcy;</code> </td> <td> U+00434 </td> <tr><td> <code title="">dd;</code> </td> <td> U+02146 </td> <tr><td> <code title="">ddagger;</code> </td> <td> U+02021 </td> <tr><td> <code title="">ddarr;</code> </td> <td> U+021CA </td> <tr><td> <code title="">ddotseq;</code> </td> <td> U+02A77 </td> <tr><td> <code title="">deg;</code> </td> <td> U+000B0 </td> <tr><td> <code title="">deg</code> </td> <td> U+000B0 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">delta;</code> </td> <td> U+003B4 </td> <tr><td> <code title="">demptyv;</code> </td> <td> U+029B1 </td> <tr><td> <code title="">dfisht;</code> </td> <td> U+0297F </td> <tr><td> <code title="">dfr;</code> </td> <td> U+1D521 </td> <tr><td> <code title="">dharl;</code> </td> <td> U+021C3 </td> <tr><td> <code title="">dharr;</code> </td> <td> U+021C2 </td> <tr><td> <code title="">diam;</code> </td> <td> U+022C4 </td> <tr><td> <code title="">diamond;</code> </td> <td> U+022C4 </td> <tr><td> <code title="">diamondsuit;</code> </td> <td> U+02666 </td> <tr><td> <code title="">diams;</code> </td> <td> U+02666 </td> <tr><td> <code title="">die;</code> </td> <td> U+000A8 </td> <tr><td> <code title="">digamma;</code> </td> <td> U+003DD </td> <tr><td> <code title="">disin;</code> </td> <td> U+022F2 </td> <tr><td> <code title="">div;</code> </td> <td> U+000F7 </td> <tr><td> <code title="">divide;</code> </td> <td> U+000F7 </td> <tr><td> <code title="">divide</code> </td> <td> U+000F7 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">divideontimes;</code> </td> <td> U+022C7 </td> <tr><td> <code title="">divonx;</code> </td> <td> U+022C7 </td> <tr><td> <code title="">djcy;</code> </td> <td> U+00452 </td> <tr><td> <code title="">dlcorn;</code> </td> <td> U+0231E </td> <tr><td> <code title="">dlcrop;</code> </td> <td> U+0230D </td> <tr><td> <code title="">dollar;</code> </td> <td> U+00024 </td> <tr><td> <code title="">dopf;</code> </td> <td> U+1D555 </td> <tr><td> <code title="">dot;</code> </td> <td> U+002D9 </td> <tr><td> <code title="">doteq;</code> </td> <td> U+02250 </td> <tr><td> <code title="">doteqdot;</code> </td> <td> U+02251 </td> <tr><td> <code title="">dotminus;</code> </td> <td> U+02238 </td> <tr><td> <code title="">dotplus;</code> </td> <td> U+02214 </td> <tr><td> <code title="">dotsquare;</code> </td> <td> U+022A1 </td> <tr><td> <code title="">doublebarwedge;</code> </td> <td> U+02306 </td> <tr><td> <code title="">downarrow;</code> </td> <td> U+02193 </td> <tr><td> <code title="">downdownarrows;</code> </td> <td> U+021CA </td> <tr><td> <code title="">downharpoonleft;</code> </td> <td> U+021C3 </td> <tr><td> <code title="">downharpoonright;</code> </td> <td> U+021C2 </td> <tr><td> <code title="">drbkarow;</code> </td> <td> U+02910 </td> <tr><td> <code title="">drcorn;</code> </td> <td> U+0231F </td> <tr><td> <code title="">drcrop;</code> </td> <td> U+0230C </td> <tr><td> <code title="">dscr;</code> </td> <td> U+1D4B9 </td> <tr><td> <code title="">dscy;</code> </td> <td> U+00455 </td> <tr><td> <code title="">dsol;</code> </td> <td> U+029F6 </td> <tr><td> <code title="">dstrok;</code> </td> <td> U+00111 </td> <tr><td> <code title="">dtdot;</code> </td> <td> U+022F1 </td> <tr><td> <code title="">dtri;</code> </td> <td> U+025BF </td> <tr><td> <code title="">dtrif;</code> </td> <td> U+025BE </td> <tr><td> <code title="">duarr;</code> </td> <td> U+021F5 </td> <tr><td> <code title="">duhar;</code> </td> <td> U+0296F </td> <tr><td> <code title="">dwangle;</code> </td> <td> U+029A6 </td> <tr><td> <code title="">dzcy;</code> </td> <td> U+0045F </td> <tr><td> <code title="">dzigrarr;</code> </td> <td> U+027FF </td> <tr><td> <code title="">eDDot;</code> </td> <td> U+02A77 </td> <tr><td> <code title="">eDot;</code> </td> <td> U+02251 </td> <tr><td> <code title="">eacute;</code> </td> <td> U+000E9 </td> <tr><td> <code title="">eacute</code> </td> <td> U+000E9 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">easter;</code> </td> <td> U+02A6E </td> <tr><td> <code title="">ecaron;</code> </td> <td> U+0011B </td> <tr><td> <code title="">ecir;</code> </td> <td> U+02256 </td> <tr><td> <code title="">ecirc;</code> </td> <td> U+000EA </td> <tr><td> <code title="">ecirc</code> </td> <td> U+000EA </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ecolon;</code> </td> <td> U+02255 </td> <tr><td> <code title="">ecy;</code> </td> <td> U+0044D </td> <tr><td> <code title="">edot;</code> </td> <td> U+00117 </td> <tr><td> <code title="">ee;</code> </td> <td> U+02147 </td> <tr><td> <code title="">efDot;</code> </td> <td> U+02252 </td> <tr><td> <code title="">efr;</code> </td> <td> U+1D522 </td> <tr><td> <code title="">eg;</code> </td> <td> U+02A9A </td> <tr><td> <code title="">egrave;</code> </td> <td> U+000E8 </td> <tr><td> <code title="">egrave</code> </td> <td> U+000E8 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">egs;</code> </td> <td> U+02A96 </td> <tr><td> <code title="">egsdot;</code> </td> <td> U+02A98 </td> <tr><td> <code title="">el;</code> </td> <td> U+02A99 </td> <tr><td> <code title="">elinters;</code> </td> <td> U+023E7 </td> <tr><td> <code title="">ell;</code> </td> <td> U+02113 </td> <tr><td> <code title="">els;</code> </td> <td> U+02A95 </td> <tr><td> <code title="">elsdot;</code> </td> <td> U+02A97 </td> <tr><td> <code title="">emacr;</code> </td> <td> U+00113 </td> <tr><td> <code title="">empty;</code> </td> <td> U+02205 </td> <tr><td> <code title="">emptyset;</code> </td> <td> U+02205 </td> <tr><td> <code title="">emptyv;</code> </td> <td> U+02205 </td> <tr><td> <code title="">emsp13;</code> </td> <td> U+02004 </td> <tr><td> <code title="">emsp14;</code> </td> <td> U+02005 </td> <tr><td> <code title="">emsp;</code> </td> <td> U+02003 </td> <tr><td> <code title="">eng;</code> </td> <td> U+0014B </td> <tr><td> <code title="">ensp;</code> </td> <td> U+02002 </td> <tr><td> <code title="">eogon;</code> </td> <td> U+00119 </td> <tr><td> <code title="">eopf;</code> </td> <td> U+1D556 </td> <tr><td> <code title="">epar;</code> </td> <td> U+022D5 </td> <tr><td> <code title="">eparsl;</code> </td> <td> U+029E3 </td> <tr><td> <code title="">eplus;</code> </td> <td> U+02A71 </td> <tr><td> <code title="">epsi;</code> </td> <td> U+003B5 </td> <tr><td> <code title="">epsilon;</code> </td> <td> U+003B5 </td> <tr><td> <code title="">epsiv;</code> </td> <td> U+003F5 </td> <tr><td> <code title="">eqcirc;</code> </td> <td> U+02256 </td> <tr><td> <code title="">eqcolon;</code> </td> <td> U+02255 </td> <tr><td> <code title="">eqsim;</code> </td> <td> U+02242 </td> <tr><td> <code title="">eqslantgtr;</code> </td> <td> U+02A96 </td> <tr><td> <code title="">eqslantless;</code> </td> <td> U+02A95 </td> <tr><td> <code title="">equals;</code> </td> <td> U+0003D </td> <tr><td> <code title="">equest;</code> </td> <td> U+0225F </td> <tr><td> <code title="">equiv;</code> </td> <td> U+02261 </td> <tr><td> <code title="">equivDD;</code> </td> <td> U+02A78 </td> <tr><td> <code title="">eqvparsl;</code> </td> <td> U+029E5 </td> <tr><td> <code title="">erDot;</code> </td> <td> U+02253 </td> <tr><td> <code title="">erarr;</code> </td> <td> U+02971 </td> <tr><td> <code title="">escr;</code> </td> <td> U+0212F </td> <tr><td> <code title="">esdot;</code> </td> <td> U+02250 </td> <tr><td> <code title="">esim;</code> </td> <td> U+02242 </td> <tr><td> <code title="">eta;</code> </td> <td> U+003B7 </td> <tr><td> <code title="">eth;</code> </td> <td> U+000F0 </td> <tr><td> <code title="">eth</code> </td> <td> U+000F0 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">euml;</code> </td> <td> U+000EB </td> <tr><td> <code title="">euml</code> </td> <td> U+000EB </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">euro;</code> </td> <td> U+020AC </td> <tr><td> <code title="">excl;</code> </td> <td> U+00021 </td> <tr><td> <code title="">exist;</code> </td> <td> U+02203 </td> <tr><td> <code title="">expectation;</code> </td> <td> U+02130 </td> <tr><td> <code title="">exponentiale;</code> </td> <td> U+02147 </td> <tr><td> <code title="">fallingdotseq;</code> </td> <td> U+02252 </td> <tr><td> <code title="">fcy;</code> </td> <td> U+00444 </td> <tr><td> <code title="">female;</code> </td> <td> U+02640 </td> <tr><td> <code title="">ffilig;</code> </td> <td> U+0FB03 </td> <tr><td> <code title="">fflig;</code> </td> <td> U+0FB00 </td> <tr><td> <code title="">ffllig;</code> </td> <td> U+0FB04 </td> <tr><td> <code title="">ffr;</code> </td> <td> U+1D523 </td> <tr><td> <code title="">filig;</code> </td> <td> U+0FB01 </td> <tr><td> <code title="">flat;</code> </td> <td> U+0266D </td> <tr><td> <code title="">fllig;</code> </td> <td> U+0FB02 </td> <tr><td> <code title="">fltns;</code> </td> <td> U+025B1 </td> <tr><td> <code title="">fnof;</code> </td> <td> U+00192 </td> <tr><td> <code title="">fopf;</code> </td> <td> U+1D557 </td> <tr><td> <code title="">forall;</code> </td> <td> U+02200 </td> <tr><td> <code title="">fork;</code> </td> <td> U+022D4 </td> <tr><td> <code title="">forkv;</code> </td> <td> U+02AD9 </td> <tr><td> <code title="">fpartint;</code> </td> <td> U+02A0D </td> <tr><td> <code title="">frac12;</code> </td> <td> U+000BD </td> <tr><td> <code title="">frac12</code> </td> <td> U+000BD </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">frac13;</code> </td> <td> U+02153 </td> <tr><td> <code title="">frac14;</code> </td> <td> U+000BC </td> <tr><td> <code title="">frac14</code> </td> <td> U+000BC </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">frac15;</code> </td> <td> U+02155 </td> <tr><td> <code title="">frac16;</code> </td> <td> U+02159 </td> <tr><td> <code title="">frac18;</code> </td> <td> U+0215B </td> <tr><td> <code title="">frac23;</code> </td> <td> U+02154 </td> <tr><td> <code title="">frac25;</code> </td> <td> U+02156 </td> <tr><td> <code title="">frac34;</code> </td> <td> U+000BE </td> <tr><td> <code title="">frac34</code> </td> <td> U+000BE </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">frac35;</code> </td> <td> U+02157 </td> <tr><td> <code title="">frac38;</code> </td> <td> U+0215C </td> <tr><td> <code title="">frac45;</code> </td> <td> U+02158 </td> <tr><td> <code title="">frac56;</code> </td> <td> U+0215A </td> <tr><td> <code title="">frac58;</code> </td> <td> U+0215D </td> <tr><td> <code title="">frac78;</code> </td> <td> U+0215E </td> <tr><td> <code title="">frasl;</code> </td> <td> U+02044 </td> <tr><td> <code title="">frown;</code> </td> <td> U+02322 </td> <tr><td> <code title="">fscr;</code> </td> <td> U+1D4BB </td> <tr><td> <code title="">gE;</code> </td> <td> U+02267 </td> <tr><td> <code title="">gEl;</code> </td> <td> U+02A8C </td> <tr><td> <code title="">gacute;</code> </td> <td> U+001F5 </td> <tr><td> <code title="">gamma;</code> </td> <td> U+003B3 </td> <tr><td> <code title="">gammad;</code> </td> <td> U+003DD </td> <tr><td> <code title="">gap;</code> </td> <td> U+02A86 </td> <tr><td> <code title="">gbreve;</code> </td> <td> U+0011F </td> <tr><td> <code title="">gcirc;</code> </td> <td> U+0011D </td> <tr><td> <code title="">gcy;</code> </td> <td> U+00433 </td> <tr><td> <code title="">gdot;</code> </td> <td> U+00121 </td> <tr><td> <code title="">ge;</code> </td> <td> U+02265 </td> <tr><td> <code title="">gel;</code> </td> <td> U+022DB </td> <tr><td> <code title="">geq;</code> </td> <td> U+02265 </td> <tr><td> <code title="">geqq;</code> </td> <td> U+02267 </td> <tr><td> <code title="">geqslant;</code> </td> <td> U+02A7E </td> <tr><td> <code title="">ges;</code> </td> <td> U+02A7E </td> <tr><td> <code title="">gescc;</code> </td> <td> U+02AA9 </td> <tr><td> <code title="">gesdot;</code> </td> <td> U+02A80 </td> <tr><td> <code title="">gesdoto;</code> </td> <td> U+02A82 </td> <tr><td> <code title="">gesdotol;</code> </td> <td> U+02A84 </td> <tr><td> <code title="">gesles;</code> </td> <td> U+02A94 </td> <tr><td> <code title="">gfr;</code> </td> <td> U+1D524 </td> <tr><td> <code title="">gg;</code> </td> <td> U+0226B </td> <tr><td> <code title="">ggg;</code> </td> <td> U+022D9 </td> <tr><td> <code title="">gimel;</code> </td> <td> U+02137 </td> <tr><td> <code title="">gjcy;</code> </td> <td> U+00453 </td> <tr><td> <code title="">gl;</code> </td> <td> U+02277 </td> <tr><td> <code title="">glE;</code> </td> <td> U+02A92 </td> <tr><td> <code title="">gla;</code> </td> <td> U+02AA5 </td> <tr><td> <code title="">glj;</code> </td> <td> U+02AA4 </td> <tr><td> <code title="">gnE;</code> </td> <td> U+02269 </td> <tr><td> <code title="">gnap;</code> </td> <td> U+02A8A </td> <tr><td> <code title="">gnapprox;</code> </td> <td> U+02A8A </td> <tr><td> <code title="">gne;</code> </td> <td> U+02A88 </td> <tr><td> <code title="">gneq;</code> </td> <td> U+02A88 </td> <tr><td> <code title="">gneqq;</code> </td> <td> U+02269 </td> <tr><td> <code title="">gnsim;</code> </td> <td> U+022E7 </td> <tr><td> <code title="">gopf;</code> </td> <td> U+1D558 </td> <tr><td> <code title="">grave;</code> </td> <td> U+00060 </td> <tr><td> <code title="">gscr;</code> </td> <td> U+0210A </td> <tr><td> <code title="">gsim;</code> </td> <td> U+02273 </td> <tr><td> <code title="">gsime;</code> </td> <td> U+02A8E </td> <tr><td> <code title="">gsiml;</code> </td> <td> U+02A90 </td> <tr><td> <code title="">gt;</code> </td> <td> U+0003E </td> <tr><td> <code title="">gt</code> </td> <td> U+0003E </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">gtcc;</code> </td> <td> U+02AA7 </td> <tr><td> <code title="">gtcir;</code> </td> <td> U+02A7A </td> <tr><td> <code title="">gtdot;</code> </td> <td> U+022D7 </td> <tr><td> <code title="">gtlPar;</code> </td> <td> U+02995 </td> <tr><td> <code title="">gtquest;</code> </td> <td> U+02A7C </td> <tr><td> <code title="">gtrapprox;</code> </td> <td> U+02A86 </td> <tr><td> <code title="">gtrarr;</code> </td> <td> U+02978 </td> <tr><td> <code title="">gtrdot;</code> </td> <td> U+022D7 </td> <tr><td> <code title="">gtreqless;</code> </td> <td> U+022DB </td> <tr><td> <code title="">gtreqqless;</code> </td> <td> U+02A8C </td> <tr><td> <code title="">gtrless;</code> </td> <td> U+02277 </td> <tr><td> <code title="">gtrsim;</code> </td> <td> U+02273 </td> <tr><td> <code title="">hArr;</code> </td> <td> U+021D4 </td> <tr><td> <code title="">hairsp;</code> </td> <td> U+0200A </td> <tr><td> <code title="">half;</code> </td> <td> U+000BD </td> <tr><td> <code title="">hamilt;</code> </td> <td> U+0210B </td> <tr><td> <code title="">hardcy;</code> </td> <td> U+0044A </td> <tr><td> <code title="">harr;</code> </td> <td> U+02194 </td> <tr><td> <code title="">harrcir;</code> </td> <td> U+02948 </td> <tr><td> <code title="">harrw;</code> </td> <td> U+021AD </td> <tr><td> <code title="">hbar;</code> </td> <td> U+0210F </td> <tr><td> <code title="">hcirc;</code> </td> <td> U+00125 </td> <tr><td> <code title="">hearts;</code> </td> <td> U+02665 </td> <tr><td> <code title="">heartsuit;</code> </td> <td> U+02665 </td> <tr><td> <code title="">hellip;</code> </td> <td> U+02026 </td> <tr><td> <code title="">hercon;</code> </td> <td> U+022B9 </td> <tr><td> <code title="">hfr;</code> </td> <td> U+1D525 </td> <tr><td> <code title="">hksearow;</code> </td> <td> U+02925 </td> <tr><td> <code title="">hkswarow;</code> </td> <td> U+02926 </td> <tr><td> <code title="">hoarr;</code> </td> <td> U+021FF </td> <tr><td> <code title="">homtht;</code> </td> <td> U+0223B </td> <tr><td> <code title="">hookleftarrow;</code> </td> <td> U+021A9 </td> <tr><td> <code title="">hookrightarrow;</code> </td> <td> U+021AA </td> <tr><td> <code title="">hopf;</code> </td> <td> U+1D559 </td> <tr><td> <code title="">horbar;</code> </td> <td> U+02015 </td> <tr><td> <code title="">hscr;</code> </td> <td> U+1D4BD </td> <tr><td> <code title="">hslash;</code> </td> <td> U+0210F </td> <tr><td> <code title="">hstrok;</code> </td> <td> U+00127 </td> <tr><td> <code title="">hybull;</code> </td> <td> U+02043 </td> <tr><td> <code title="">hyphen;</code> </td> <td> U+02010 </td> <tr><td> <code title="">iacute;</code> </td> <td> U+000ED </td> <tr><td> <code title="">iacute</code> </td> <td> U+000ED </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ic;</code> </td> <td> U+02063 </td> <tr><td> <code title="">icirc;</code> </td> <td> U+000EE </td> <tr><td> <code title="">icirc</code> </td> <td> U+000EE </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">icy;</code> </td> <td> U+00438 </td> <tr><td> <code title="">iecy;</code> </td> <td> U+00435 </td> <tr><td> <code title="">iexcl;</code> </td> <td> U+000A1 </td> <tr><td> <code title="">iexcl</code> </td> <td> U+000A1 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">iff;</code> </td> <td> U+021D4 </td> <tr><td> <code title="">ifr;</code> </td> <td> U+1D526 </td> <tr><td> <code title="">igrave;</code> </td> <td> U+000EC </td> <tr><td> <code title="">igrave</code> </td> <td> U+000EC </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ii;</code> </td> <td> U+02148 </td> <tr><td> <code title="">iiiint;</code> </td> <td> U+02A0C </td> <tr><td> <code title="">iiint;</code> </td> <td> U+0222D </td> <tr><td> <code title="">iinfin;</code> </td> <td> U+029DC </td> <tr><td> <code title="">iiota;</code> </td> <td> U+02129 </td> <tr><td> <code title="">ijlig;</code> </td> <td> U+00133 </td> <tr><td> <code title="">imacr;</code> </td> <td> U+0012B </td> <tr><td> <code title="">image;</code> </td> <td> U+02111 </td> <tr><td> <code title="">imagline;</code> </td> <td> U+02110 </td> <tr><td> <code title="">imagpart;</code> </td> <td> U+02111 </td> <tr><td> <code title="">imath;</code> </td> <td> U+00131 </td> <tr><td> <code title="">imof;</code> </td> <td> U+022B7 </td> <tr><td> <code title="">imped;</code> </td> <td> U+001B5 </td> <tr><td> <code title="">in;</code> </td> <td> U+02208 </td> <tr><td> <code title="">incare;</code> </td> <td> U+02105 </td> <tr><td> <code title="">infin;</code> </td> <td> U+0221E </td> <tr><td> <code title="">infintie;</code> </td> <td> U+029DD </td> <tr><td> <code title="">inodot;</code> </td> <td> U+00131 </td> <tr><td> <code title="">int;</code> </td> <td> U+0222B </td> <tr><td> <code title="">intcal;</code> </td> <td> U+022BA </td> <tr><td> <code title="">integers;</code> </td> <td> U+02124 </td> <tr><td> <code title="">intercal;</code> </td> <td> U+022BA </td> <tr><td> <code title="">intlarhk;</code> </td> <td> U+02A17 </td> <tr><td> <code title="">intprod;</code> </td> <td> U+02A3C </td> <tr><td> <code title="">iocy;</code> </td> <td> U+00451 </td> <tr><td> <code title="">iogon;</code> </td> <td> U+0012F </td> <tr><td> <code title="">iopf;</code> </td> <td> U+1D55A </td> <tr><td> <code title="">iota;</code> </td> <td> U+003B9 </td> <tr><td> <code title="">iprod;</code> </td> <td> U+02A3C </td> <tr><td> <code title="">iquest;</code> </td> <td> U+000BF </td> <tr><td> <code title="">iquest</code> </td> <td> U+000BF </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">iscr;</code> </td> <td> U+1D4BE </td> <tr><td> <code title="">isin;</code> </td> <td> U+02208 </td> <tr><td> <code title="">isinE;</code> </td> <td> U+022F9 </td> <tr><td> <code title="">isindot;</code> </td> <td> U+022F5 </td> <tr><td> <code title="">isins;</code> </td> <td> U+022F4 </td> <tr><td> <code title="">isinsv;</code> </td> <td> U+022F3 </td> <tr><td> <code title="">isinv;</code> </td> <td> U+02208 </td> <tr><td> <code title="">it;</code> </td> <td> U+02062 </td> <tr><td> <code title="">itilde;</code> </td> <td> U+00129 </td> <tr><td> <code title="">iukcy;</code> </td> <td> U+00456 </td> <tr><td> <code title="">iuml;</code> </td> <td> U+000EF </td> <tr><td> <code title="">iuml</code> </td> <td> U+000EF </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">jcirc;</code> </td> <td> U+00135 </td> <tr><td> <code title="">jcy;</code> </td> <td> U+00439 </td> <tr><td> <code title="">jfr;</code> </td> <td> U+1D527 </td> <tr><td> <code title="">jmath;</code> </td> <td> U+00237 </td> <tr><td> <code title="">jopf;</code> </td> <td> U+1D55B </td> <tr><td> <code title="">jscr;</code> </td> <td> U+1D4BF </td> <tr><td> <code title="">jsercy;</code> </td> <td> U+00458 </td> <tr><td> <code title="">jukcy;</code> </td> <td> U+00454 </td> <tr><td> <code title="">kappa;</code> </td> <td> U+003BA </td> <tr><td> <code title="">kappav;</code> </td> <td> U+003F0 </td> <tr><td> <code title="">kcedil;</code> </td> <td> U+00137 </td> <tr><td> <code title="">kcy;</code> </td> <td> U+0043A </td> <tr><td> <code title="">kfr;</code> </td> <td> U+1D528 </td> <tr><td> <code title="">kgreen;</code> </td> <td> U+00138 </td> <tr><td> <code title="">khcy;</code> </td> <td> U+00445 </td> <tr><td> <code title="">kjcy;</code> </td> <td> U+0045C </td> <tr><td> <code title="">kopf;</code> </td> <td> U+1D55C </td> <tr><td> <code title="">kscr;</code> </td> <td> U+1D4C0 </td> <tr><td> <code title="">lAarr;</code> </td> <td> U+021DA </td> <tr><td> <code title="">lArr;</code> </td> <td> U+021D0 </td> <tr><td> <code title="">lAtail;</code> </td> <td> U+0291B </td> <tr><td> <code title="">lBarr;</code> </td> <td> U+0290E </td> <tr><td> <code title="">lE;</code> </td> <td> U+02266 </td> <tr><td> <code title="">lEg;</code> </td> <td> U+02A8B </td> <tr><td> <code title="">lHar;</code> </td> <td> U+02962 </td> <tr><td> <code title="">lacute;</code> </td> <td> U+0013A </td> <tr><td> <code title="">laemptyv;</code> </td> <td> U+029B4 </td> <tr><td> <code title="">lagran;</code> </td> <td> U+02112 </td> <tr><td> <code title="">lambda;</code> </td> <td> U+003BB </td> <tr><td> <code title="">lang;</code> </td> <td> U+027E8 </td> <tr><td> <code title="">langd;</code> </td> <td> U+02991 </td> <tr><td> <code title="">langle;</code> </td> <td> U+027E8 </td> <tr><td> <code title="">lap;</code> </td> <td> U+02A85 </td> <tr><td> <code title="">laquo;</code> </td> <td> U+000AB </td> <tr><td> <code title="">laquo</code> </td> <td> U+000AB </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">larr;</code> </td> <td> U+02190 </td> <tr><td> <code title="">larrb;</code> </td> <td> U+021E4 </td> <tr><td> <code title="">larrbfs;</code> </td> <td> U+0291F </td> <tr><td> <code title="">larrfs;</code> </td> <td> U+0291D </td> <tr><td> <code title="">larrhk;</code> </td> <td> U+021A9 </td> <tr><td> <code title="">larrlp;</code> </td> <td> U+021AB </td> <tr><td> <code title="">larrpl;</code> </td> <td> U+02939 </td> <tr><td> <code title="">larrsim;</code> </td> <td> U+02973 </td> <tr><td> <code title="">larrtl;</code> </td> <td> U+021A2 </td> <tr><td> <code title="">lat;</code> </td> <td> U+02AAB </td> <tr><td> <code title="">latail;</code> </td> <td> U+02919 </td> <tr><td> <code title="">late;</code> </td> <td> U+02AAD </td> <tr><td> <code title="">lbarr;</code> </td> <td> U+0290C </td> <tr><td> <code title="">lbbrk;</code> </td> <td> U+02772 </td> <tr><td> <code title="">lbrace;</code> </td> <td> U+0007B </td> <tr><td> <code title="">lbrack;</code> </td> <td> U+0005B </td> <tr><td> <code title="">lbrke;</code> </td> <td> U+0298B </td> <tr><td> <code title="">lbrksld;</code> </td> <td> U+0298F </td> <tr><td> <code title="">lbrkslu;</code> </td> <td> U+0298D </td> <tr><td> <code title="">lcaron;</code> </td> <td> U+0013E </td> <tr><td> <code title="">lcedil;</code> </td> <td> U+0013C </td> <tr><td> <code title="">lceil;</code> </td> <td> U+02308 </td> <tr><td> <code title="">lcub;</code> </td> <td> U+0007B </td> <tr><td> <code title="">lcy;</code> </td> <td> U+0043B </td> <tr><td> <code title="">ldca;</code> </td> <td> U+02936 </td> <tr><td> <code title="">ldquo;</code> </td> <td> U+0201C </td> <tr><td> <code title="">ldquor;</code> </td> <td> U+0201E </td> <tr><td> <code title="">ldrdhar;</code> </td> <td> U+02967 </td> <tr><td> <code title="">ldrushar;</code> </td> <td> U+0294B </td> <tr><td> <code title="">ldsh;</code> </td> <td> U+021B2 </td> <tr><td> <code title="">le;</code> </td> <td> U+02264 </td> <tr><td> <code title="">leftarrow;</code> </td> <td> U+02190 </td> <tr><td> <code title="">leftarrowtail;</code> </td> <td> U+021A2 </td> <tr><td> <code title="">leftharpoondown;</code> </td> <td> U+021BD </td> <tr><td> <code title="">leftharpoonup;</code> </td> <td> U+021BC </td> <tr><td> <code title="">leftleftarrows;</code> </td> <td> U+021C7 </td> <tr><td> <code title="">leftrightarrow;</code> </td> <td> U+02194 </td> <tr><td> <code title="">leftrightarrows;</code> </td> <td> U+021C6 </td> <tr><td> <code title="">leftrightharpoons;</code> </td> <td> U+021CB </td> <tr><td> <code title="">leftrightsquigarrow;</code> </td> <td> U+021AD </td> <tr><td> <code title="">leftthreetimes;</code> </td> <td> U+022CB </td> <tr><td> <code title="">leg;</code> </td> <td> U+022DA </td> <tr><td> <code title="">leq;</code> </td> <td> U+02264 </td> <tr><td> <code title="">leqq;</code> </td> <td> U+02266 </td> <tr><td> <code title="">leqslant;</code> </td> <td> U+02A7D </td> <tr><td> <code title="">les;</code> </td> <td> U+02A7D </td> <tr><td> <code title="">lescc;</code> </td> <td> U+02AA8 </td> <tr><td> <code title="">lesdot;</code> </td> <td> U+02A7F </td> <tr><td> <code title="">lesdoto;</code> </td> <td> U+02A81 </td> <tr><td> <code title="">lesdotor;</code> </td> <td> U+02A83 </td> <tr><td> <code title="">lesges;</code> </td> <td> U+02A93 </td> <tr><td> <code title="">lessapprox;</code> </td> <td> U+02A85 </td> <tr><td> <code title="">lessdot;</code> </td> <td> U+022D6 </td> <tr><td> <code title="">lesseqgtr;</code> </td> <td> U+022DA </td> <tr><td> <code title="">lesseqqgtr;</code> </td> <td> U+02A8B </td> <tr><td> <code title="">lessgtr;</code> </td> <td> U+02276 </td> <tr><td> <code title="">lesssim;</code> </td> <td> U+02272 </td> <tr><td> <code title="">lfisht;</code> </td> <td> U+0297C </td> <tr><td> <code title="">lfloor;</code> </td> <td> U+0230A </td> <tr><td> <code title="">lfr;</code> </td> <td> U+1D529 </td> <tr><td> <code title="">lg;</code> </td> <td> U+02276 </td> <tr><td> <code title="">lgE;</code> </td> <td> U+02A91 </td> <tr><td> <code title="">lhard;</code> </td> <td> U+021BD </td> <tr><td> <code title="">lharu;</code> </td> <td> U+021BC </td> <tr><td> <code title="">lharul;</code> </td> <td> U+0296A </td> <tr><td> <code title="">lhblk;</code> </td> <td> U+02584 </td> <tr><td> <code title="">ljcy;</code> </td> <td> U+00459 </td> <tr><td> <code title="">ll;</code> </td> <td> U+0226A </td> <tr><td> <code title="">llarr;</code> </td> <td> U+021C7 </td> <tr><td> <code title="">llcorner;</code> </td> <td> U+0231E </td> <tr><td> <code title="">llhard;</code> </td> <td> U+0296B </td> <tr><td> <code title="">lltri;</code> </td> <td> U+025FA </td> <tr><td> <code title="">lmidot;</code> </td> <td> U+00140 </td> <tr><td> <code title="">lmoust;</code> </td> <td> U+023B0 </td> <tr><td> <code title="">lmoustache;</code> </td> <td> U+023B0 </td> <tr><td> <code title="">lnE;</code> </td> <td> U+02268 </td> <tr><td> <code title="">lnap;</code> </td> <td> U+02A89 </td> <tr><td> <code title="">lnapprox;</code> </td> <td> U+02A89 </td> <tr><td> <code title="">lne;</code> </td> <td> U+02A87 </td> <tr><td> <code title="">lneq;</code> </td> <td> U+02A87 </td> <tr><td> <code title="">lneqq;</code> </td> <td> U+02268 </td> <tr><td> <code title="">lnsim;</code> </td> <td> U+022E6 </td> <tr><td> <code title="">loang;</code> </td> <td> U+027EC </td> <tr><td> <code title="">loarr;</code> </td> <td> U+021FD </td> <tr><td> <code title="">lobrk;</code> </td> <td> U+027E6 </td> <tr><td> <code title="">longleftarrow;</code> </td> <td> U+027F5 </td> <tr><td> <code title="">longleftrightarrow;</code> </td> <td> U+027F7 </td> <tr><td> <code title="">longmapsto;</code> </td> <td> U+027FC </td> <tr><td> <code title="">longrightarrow;</code> </td> <td> U+027F6 </td> <tr><td> <code title="">looparrowleft;</code> </td> <td> U+021AB </td> <tr><td> <code title="">looparrowright;</code> </td> <td> U+021AC </td> <tr><td> <code title="">lopar;</code> </td> <td> U+02985 </td> <tr><td> <code title="">lopf;</code> </td> <td> U+1D55D </td> <tr><td> <code title="">loplus;</code> </td> <td> U+02A2D </td> <tr><td> <code title="">lotimes;</code> </td> <td> U+02A34 </td> <tr><td> <code title="">lowast;</code> </td> <td> U+02217 </td> <tr><td> <code title="">lowbar;</code> </td> <td> U+0005F </td> <tr><td> <code title="">loz;</code> </td> <td> U+025CA </td> <tr><td> <code title="">lozenge;</code> </td> <td> U+025CA </td> <tr><td> <code title="">lozf;</code> </td> <td> U+029EB </td> <tr><td> <code title="">lpar;</code> </td> <td> U+00028 </td> <tr><td> <code title="">lparlt;</code> </td> <td> U+02993 </td> <tr><td> <code title="">lrarr;</code> </td> <td> U+021C6 </td> <tr><td> <code title="">lrcorner;</code> </td> <td> U+0231F </td> <tr><td> <code title="">lrhar;</code> </td> <td> U+021CB </td> <tr><td> <code title="">lrhard;</code> </td> <td> U+0296D </td> <tr><td> <code title="">lrm;</code> </td> <td> U+0200E </td> <tr><td> <code title="">lrtri;</code> </td> <td> U+022BF </td> <tr><td> <code title="">lsaquo;</code> </td> <td> U+02039 </td> <tr><td> <code title="">lscr;</code> </td> <td> U+1D4C1 </td> <tr><td> <code title="">lsh;</code> </td> <td> U+021B0 </td> <tr><td> <code title="">lsim;</code> </td> <td> U+02272 </td> <tr><td> <code title="">lsime;</code> </td> <td> U+02A8D </td> <tr><td> <code title="">lsimg;</code> </td> <td> U+02A8F </td> <tr><td> <code title="">lsqb;</code> </td> <td> U+0005B </td> <tr><td> <code title="">lsquo;</code> </td> <td> U+02018 </td> <tr><td> <code title="">lsquor;</code> </td> <td> U+0201A </td> <tr><td> <code title="">lstrok;</code> </td> <td> U+00142 </td> <tr><td> <code title="">lt;</code> </td> <td> U+0003C </td> <tr><td> <code title="">lt</code> </td> <td> U+0003C </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ltcc;</code> </td> <td> U+02AA6 </td> <tr><td> <code title="">ltcir;</code> </td> <td> U+02A79 </td> <tr><td> <code title="">ltdot;</code> </td> <td> U+022D6 </td> <tr><td> <code title="">lthree;</code> </td> <td> U+022CB </td> <tr><td> <code title="">ltimes;</code> </td> <td> U+022C9 </td> <tr><td> <code title="">ltlarr;</code> </td> <td> U+02976 </td> <tr><td> <code title="">ltquest;</code> </td> <td> U+02A7B </td> <tr><td> <code title="">ltrPar;</code> </td> <td> U+02996 </td> <tr><td> <code title="">ltri;</code> </td> <td> U+025C3 </td> <tr><td> <code title="">ltrie;</code> </td> <td> U+022B4 </td> <tr><td> <code title="">ltrif;</code> </td> <td> U+025C2 </td> <tr><td> <code title="">lurdshar;</code> </td> <td> U+0294A </td> <tr><td> <code title="">luruhar;</code> </td> <td> U+02966 </td> <tr><td> <code title="">mDDot;</code> </td> <td> U+0223A </td> <tr><td> <code title="">macr;</code> </td> <td> U+000AF </td> <tr><td> <code title="">macr</code> </td> <td> U+000AF </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">male;</code> </td> <td> U+02642 </td> <tr><td> <code title="">malt;</code> </td> <td> U+02720 </td> <tr><td> <code title="">maltese;</code> </td> <td> U+02720 </td> <tr><td> <code title="">map;</code> </td> <td> U+021A6 </td> <tr><td> <code title="">mapsto;</code> </td> <td> U+021A6 </td> <tr><td> <code title="">mapstodown;</code> </td> <td> U+021A7 </td> <tr><td> <code title="">mapstoleft;</code> </td> <td> U+021A4 </td> <tr><td> <code title="">mapstoup;</code> </td> <td> U+021A5 </td> <tr><td> <code title="">marker;</code> </td> <td> U+025AE </td> <tr><td> <code title="">mcomma;</code> </td> <td> U+02A29 </td> <tr><td> <code title="">mcy;</code> </td> <td> U+0043C </td> <tr><td> <code title="">mdash;</code> </td> <td> U+02014 </td> <tr><td> <code title="">measuredangle;</code> </td> <td> U+02221 </td> <tr><td> <code title="">mfr;</code> </td> <td> U+1D52A </td> <tr><td> <code title="">mho;</code> </td> <td> U+02127 </td> <tr><td> <code title="">micro;</code> </td> <td> U+000B5 </td> <tr><td> <code title="">micro</code> </td> <td> U+000B5 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">mid;</code> </td> <td> U+02223 </td> <tr><td> <code title="">midast;</code> </td> <td> U+0002A </td> <tr><td> <code title="">midcir;</code> </td> <td> U+02AF0 </td> <tr><td> <code title="">middot;</code> </td> <td> U+000B7 </td> <tr><td> <code title="">middot</code> </td> <td> U+000B7 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">minus;</code> </td> <td> U+02212 </td> <tr><td> <code title="">minusb;</code> </td> <td> U+0229F </td> <tr><td> <code title="">minusd;</code> </td> <td> U+02238 </td> <tr><td> <code title="">minusdu;</code> </td> <td> U+02A2A </td> <tr><td> <code title="">mlcp;</code> </td> <td> U+02ADB </td> <tr><td> <code title="">mldr;</code> </td> <td> U+02026 </td> <tr><td> <code title="">mnplus;</code> </td> <td> U+02213 </td> <tr><td> <code title="">models;</code> </td> <td> U+022A7 </td> <tr><td> <code title="">mopf;</code> </td> <td> U+1D55E </td> <tr><td> <code title="">mp;</code> </td> <td> U+02213 </td> <tr><td> <code title="">mscr;</code> </td> <td> U+1D4C2 </td> <tr><td> <code title="">mstpos;</code> </td> <td> U+0223E </td> <tr><td> <code title="">mu;</code> </td> <td> U+003BC </td> <tr><td> <code title="">multimap;</code> </td> <td> U+022B8 </td> <tr><td> <code title="">mumap;</code> </td> <td> U+022B8 </td> <tr><td> <code title="">nLeftarrow;</code> </td> <td> U+021CD </td> <tr><td> <code title="">nLeftrightarrow;</code> </td> <td> U+021CE </td> <tr><td> <code title="">nRightarrow;</code> </td> <td> U+021CF </td> <tr><td> <code title="">nVDash;</code> </td> <td> U+022AF </td> <tr><td> <code title="">nVdash;</code> </td> <td> U+022AE </td> <tr><td> <code title="">nabla;</code> </td> <td> U+02207 </td> <tr><td> <code title="">nacute;</code> </td> <td> U+00144 </td> <tr><td> <code title="">nap;</code> </td> <td> U+02249 </td> <tr><td> <code title="">napos;</code> </td> <td> U+00149 </td> <tr><td> <code title="">napprox;</code> </td> <td> U+02249 </td> <tr><td> <code title="">natur;</code> </td> <td> U+0266E </td> <tr><td> <code title="">natural;</code> </td> <td> U+0266E </td> <tr><td> <code title="">naturals;</code> </td> <td> U+02115 </td> <tr><td> <code title="">nbsp;</code> </td> <td> U+000A0 </td> <tr><td> <code title="">nbsp</code> </td> <td> U+000A0 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ncap;</code> </td> <td> U+02A43 </td> <tr><td> <code title="">ncaron;</code> </td> <td> U+00148 </td> <tr><td> <code title="">ncedil;</code> </td> <td> U+00146 </td> <tr><td> <code title="">ncong;</code> </td> <td> U+02247 </td> <tr><td> <code title="">ncup;</code> </td> <td> U+02A42 </td> <tr><td> <code title="">ncy;</code> </td> <td> U+0043D </td> <tr><td> <code title="">ndash;</code> </td> <td> U+02013 </td> <tr><td> <code title="">ne;</code> </td> <td> U+02260 </td> <tr><td> <code title="">neArr;</code> </td> <td> U+021D7 </td> <tr><td> <code title="">nearhk;</code> </td> <td> U+02924 </td> <tr><td> <code title="">nearr;</code> </td> <td> U+02197 </td> <tr><td> <code title="">nearrow;</code> </td> <td> U+02197 </td> <tr><td> <code title="">nequiv;</code> </td> <td> U+02262 </td> <tr><td> <code title="">nesear;</code> </td> <td> U+02928 </td> <tr><td> <code title="">nexist;</code> </td> <td> U+02204 </td> <tr><td> <code title="">nexists;</code> </td> <td> U+02204 </td> <tr><td> <code title="">nfr;</code> </td> <td> U+1D52B </td> <tr><td> <code title="">nge;</code> </td> <td> U+02271 </td> <tr><td> <code title="">ngeq;</code> </td> <td> U+02271 </td> <tr><td> <code title="">ngsim;</code> </td> <td> U+02275 </td> <tr><td> <code title="">ngt;</code> </td> <td> U+0226F </td> <tr><td> <code title="">ngtr;</code> </td> <td> U+0226F </td> <tr><td> <code title="">nhArr;</code> </td> <td> U+021CE </td> <tr><td> <code title="">nharr;</code> </td> <td> U+021AE </td> <tr><td> <code title="">nhpar;</code> </td> <td> U+02AF2 </td> <tr><td> <code title="">ni;</code> </td> <td> U+0220B </td> <tr><td> <code title="">nis;</code> </td> <td> U+022FC </td> <tr><td> <code title="">nisd;</code> </td> <td> U+022FA </td> <tr><td> <code title="">niv;</code> </td> <td> U+0220B </td> <tr><td> <code title="">njcy;</code> </td> <td> U+0045A </td> <tr><td> <code title="">nlArr;</code> </td> <td> U+021CD </td> <tr><td> <code title="">nlarr;</code> </td> <td> U+0219A </td> <tr><td> <code title="">nldr;</code> </td> <td> U+02025 </td> <tr><td> <code title="">nle;</code> </td> <td> U+02270 </td> <tr><td> <code title="">nleftarrow;</code> </td> <td> U+0219A </td> <tr><td> <code title="">nleftrightarrow;</code> </td> <td> U+021AE </td> <tr><td> <code title="">nleq;</code> </td> <td> U+02270 </td> <tr><td> <code title="">nless;</code> </td> <td> U+0226E </td> <tr><td> <code title="">nlsim;</code> </td> <td> U+02274 </td> <tr><td> <code title="">nlt;</code> </td> <td> U+0226E </td> <tr><td> <code title="">nltri;</code> </td> <td> U+022EA </td> <tr><td> <code title="">nltrie;</code> </td> <td> U+022EC </td> <tr><td> <code title="">nmid;</code> </td> <td> U+02224 </td> <tr><td> <code title="">nopf;</code> </td> <td> U+1D55F </td> <tr><td> <code title="">not;</code> </td> <td> U+000AC </td> <tr><td> <code title="">not</code> </td> <td> U+000AC </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">notin;</code> </td> <td> U+02209 </td> <tr><td> <code title="">notinva;</code> </td> <td> U+02209 </td> <tr><td> <code title="">notinvb;</code> </td> <td> U+022F7 </td> <tr><td> <code title="">notinvc;</code> </td> <td> U+022F6 </td> <tr><td> <code title="">notni;</code> </td> <td> U+0220C </td> <tr><td> <code title="">notniva;</code> </td> <td> U+0220C </td> <tr><td> <code title="">notnivb;</code> </td> <td> U+022FE </td> <tr><td> <code title="">notnivc;</code> </td> <td> U+022FD </td> <tr><td> <code title="">npar;</code> </td> <td> U+02226 </td> <tr><td> <code title="">nparallel;</code> </td> <td> U+02226 </td> <tr><td> <code title="">npolint;</code> </td> <td> U+02A14 </td> <tr><td> <code title="">npr;</code> </td> <td> U+02280 </td> <tr><td> <code title="">nprcue;</code> </td> <td> U+022E0 </td> <tr><td> <code title="">nprec;</code> </td> <td> U+02280 </td> <tr><td> <code title="">nrArr;</code> </td> <td> U+021CF </td> <tr><td> <code title="">nrarr;</code> </td> <td> U+0219B </td> <tr><td> <code title="">nrightarrow;</code> </td> <td> U+0219B </td> <tr><td> <code title="">nrtri;</code> </td> <td> U+022EB </td> <tr><td> <code title="">nrtrie;</code> </td> <td> U+022ED </td> <tr><td> <code title="">nsc;</code> </td> <td> U+02281 </td> <tr><td> <code title="">nsccue;</code> </td> <td> U+022E1 </td> <tr><td> <code title="">nscr;</code> </td> <td> U+1D4C3 </td> <tr><td> <code title="">nshortmid;</code> </td> <td> U+02224 </td> <tr><td> <code title="">nshortparallel;</code> </td> <td> U+02226 </td> <tr><td> <code title="">nsim;</code> </td> <td> U+02241 </td> <tr><td> <code title="">nsime;</code> </td> <td> U+02244 </td> <tr><td> <code title="">nsimeq;</code> </td> <td> U+02244 </td> <tr><td> <code title="">nsmid;</code> </td> <td> U+02224 </td> <tr><td> <code title="">nspar;</code> </td> <td> U+02226 </td> <tr><td> <code title="">nsqsube;</code> </td> <td> U+022E2 </td> <tr><td> <code title="">nsqsupe;</code> </td> <td> U+022E3 </td> <tr><td> <code title="">nsub;</code> </td> <td> U+02284 </td> <tr><td> <code title="">nsube;</code> </td> <td> U+02288 </td> <tr><td> <code title="">nsubseteq;</code> </td> <td> U+02288 </td> <tr><td> <code title="">nsucc;</code> </td> <td> U+02281 </td> <tr><td> <code title="">nsup;</code> </td> <td> U+02285 </td> <tr><td> <code title="">nsupe;</code> </td> <td> U+02289 </td> <tr><td> <code title="">nsupseteq;</code> </td> <td> U+02289 </td> <tr><td> <code title="">ntgl;</code> </td> <td> U+02279 </td> <tr><td> <code title="">ntilde;</code> </td> <td> U+000F1 </td> <tr><td> <code title="">ntilde</code> </td> <td> U+000F1 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ntlg;</code> </td> <td> U+02278 </td> <tr><td> <code title="">ntriangleleft;</code> </td> <td> U+022EA </td> <tr><td> <code title="">ntrianglelefteq;</code> </td> <td> U+022EC </td> <tr><td> <code title="">ntriangleright;</code> </td> <td> U+022EB </td> <tr><td> <code title="">ntrianglerighteq;</code> </td> <td> U+022ED </td> <tr><td> <code title="">nu;</code> </td> <td> U+003BD </td> <tr><td> <code title="">num;</code> </td> <td> U+00023 </td> <tr><td> <code title="">numero;</code> </td> <td> U+02116 </td> <tr><td> <code title="">numsp;</code> </td> <td> U+02007 </td> <tr><td> <code title="">nvDash;</code> </td> <td> U+022AD </td> <tr><td> <code title="">nvHarr;</code> </td> <td> U+02904 </td> <tr><td> <code title="">nvdash;</code> </td> <td> U+022AC </td> <tr><td> <code title="">nvinfin;</code> </td> <td> U+029DE </td> <tr><td> <code title="">nvlArr;</code> </td> <td> U+02902 </td> <tr><td> <code title="">nvrArr;</code> </td> <td> U+02903 </td> <tr><td> <code title="">nwArr;</code> </td> <td> U+021D6 </td> <tr><td> <code title="">nwarhk;</code> </td> <td> U+02923 </td> <tr><td> <code title="">nwarr;</code> </td> <td> U+02196 </td> <tr><td> <code title="">nwarrow;</code> </td> <td> U+02196 </td> <tr><td> <code title="">nwnear;</code> </td> <td> U+02927 </td> <tr><td> <code title="">oS;</code> </td> <td> U+024C8 </td> <tr><td> <code title="">oacute;</code> </td> <td> U+000F3 </td> <tr><td> <code title="">oacute</code> </td> <td> U+000F3 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">oast;</code> </td> <td> U+0229B </td> <tr><td> <code title="">ocir;</code> </td> <td> U+0229A </td> <tr><td> <code title="">ocirc;</code> </td> <td> U+000F4 </td> <tr><td> <code title="">ocirc</code> </td> <td> U+000F4 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ocy;</code> </td> <td> U+0043E </td> <tr><td> <code title="">odash;</code> </td> <td> U+0229D </td> <tr><td> <code title="">odblac;</code> </td> <td> U+00151 </td> <tr><td> <code title="">odiv;</code> </td> <td> U+02A38 </td> <tr><td> <code title="">odot;</code> </td> <td> U+02299 </td> <tr><td> <code title="">odsold;</code> </td> <td> U+029BC </td> <tr><td> <code title="">oelig;</code> </td> <td> U+00153 </td> <tr><td> <code title="">ofcir;</code> </td> <td> U+029BF </td> <tr><td> <code title="">ofr;</code> </td> <td> U+1D52C </td> <tr><td> <code title="">ogon;</code> </td> <td> U+002DB </td> <tr><td> <code title="">ograve;</code> </td> <td> U+000F2 </td> <tr><td> <code title="">ograve</code> </td> <td> U+000F2 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ogt;</code> </td> <td> U+029C1 </td> <tr><td> <code title="">ohbar;</code> </td> <td> U+029B5 </td> <tr><td> <code title="">ohm;</code> </td> <td> U+003A9 </td> <tr><td> <code title="">oint;</code> </td> <td> U+0222E </td> <tr><td> <code title="">olarr;</code> </td> <td> U+021BA </td> <tr><td> <code title="">olcir;</code> </td> <td> U+029BE </td> <tr><td> <code title="">olcross;</code> </td> <td> U+029BB </td> <tr><td> <code title="">oline;</code> </td> <td> U+0203E </td> <tr><td> <code title="">olt;</code> </td> <td> U+029C0 </td> <tr><td> <code title="">omacr;</code> </td> <td> U+0014D </td> <tr><td> <code title="">omega;</code> </td> <td> U+003C9 </td> <tr><td> <code title="">omicron;</code> </td> <td> U+003BF </td> <tr><td> <code title="">omid;</code> </td> <td> U+029B6 </td> <tr><td> <code title="">ominus;</code> </td> <td> U+02296 </td> <tr><td> <code title="">oopf;</code> </td> <td> U+1D560 </td> <tr><td> <code title="">opar;</code> </td> <td> U+029B7 </td> <tr><td> <code title="">operp;</code> </td> <td> U+029B9 </td> <tr><td> <code title="">oplus;</code> </td> <td> U+02295 </td> <tr><td> <code title="">or;</code> </td> <td> U+02228 </td> <tr><td> <code title="">orarr;</code> </td> <td> U+021BB </td> <tr><td> <code title="">ord;</code> </td> <td> U+02A5D </td> <tr><td> <code title="">order;</code> </td> <td> U+02134 </td> <tr><td> <code title="">orderof;</code> </td> <td> U+02134 </td> <tr><td> <code title="">ordf;</code> </td> <td> U+000AA </td> <tr><td> <code title="">ordf</code> </td> <td> U+000AA </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ordm;</code> </td> <td> U+000BA </td> <tr><td> <code title="">ordm</code> </td> <td> U+000BA </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">origof;</code> </td> <td> U+022B6 </td> <tr><td> <code title="">oror;</code> </td> <td> U+02A56 </td> <tr><td> <code title="">orslope;</code> </td> <td> U+02A57 </td> <tr><td> <code title="">orv;</code> </td> <td> U+02A5B </td> <tr><td> <code title="">oscr;</code> </td> <td> U+02134 </td> <tr><td> <code title="">oslash;</code> </td> <td> U+000F8 </td> <tr><td> <code title="">oslash</code> </td> <td> U+000F8 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">osol;</code> </td> <td> U+02298 </td> <tr><td> <code title="">otilde;</code> </td> <td> U+000F5 </td> <tr><td> <code title="">otilde</code> </td> <td> U+000F5 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">otimes;</code> </td> <td> U+02297 </td> <tr><td> <code title="">otimesas;</code> </td> <td> U+02A36 </td> <tr><td> <code title="">ouml;</code> </td> <td> U+000F6 </td> <tr><td> <code title="">ouml</code> </td> <td> U+000F6 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ovbar;</code> </td> <td> U+0233D </td> <tr><td> <code title="">par;</code> </td> <td> U+02225 </td> <tr><td> <code title="">para;</code> </td> <td> U+000B6 </td> <tr><td> <code title="">para</code> </td> <td> U+000B6 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">parallel;</code> </td> <td> U+02225 </td> <tr><td> <code title="">parsim;</code> </td> <td> U+02AF3 </td> <tr><td> <code title="">parsl;</code> </td> <td> U+02AFD </td> <tr><td> <code title="">part;</code> </td> <td> U+02202 </td> <tr><td> <code title="">pcy;</code> </td> <td> U+0043F </td> <tr><td> <code title="">percnt;</code> </td> <td> U+00025 </td> <tr><td> <code title="">period;</code> </td> <td> U+0002E </td> <tr><td> <code title="">permil;</code> </td> <td> U+02030 </td> <tr><td> <code title="">perp;</code> </td> <td> U+022A5 </td> <tr><td> <code title="">pertenk;</code> </td> <td> U+02031 </td> <tr><td> <code title="">pfr;</code> </td> <td> U+1D52D </td> <tr><td> <code title="">phi;</code> </td> <td> U+003C6 </td> <tr><td> <code title="">phiv;</code> </td> <td> U+003D5 </td> <tr><td> <code title="">phmmat;</code> </td> <td> U+02133 </td> <tr><td> <code title="">phone;</code> </td> <td> U+0260E </td> <tr><td> <code title="">pi;</code> </td> <td> U+003C0 </td> <tr><td> <code title="">pitchfork;</code> </td> <td> U+022D4 </td> <tr><td> <code title="">piv;</code> </td> <td> U+003D6 </td> <tr><td> <code title="">planck;</code> </td> <td> U+0210F </td> <tr><td> <code title="">planckh;</code> </td> <td> U+0210E </td> <tr><td> <code title="">plankv;</code> </td> <td> U+0210F </td> <tr><td> <code title="">plus;</code> </td> <td> U+0002B </td> <tr><td> <code title="">plusacir;</code> </td> <td> U+02A23 </td> <tr><td> <code title="">plusb;</code> </td> <td> U+0229E </td> <tr><td> <code title="">pluscir;</code> </td> <td> U+02A22 </td> <tr><td> <code title="">plusdo;</code> </td> <td> U+02214 </td> <tr><td> <code title="">plusdu;</code> </td> <td> U+02A25 </td> <tr><td> <code title="">pluse;</code> </td> <td> U+02A72 </td> <tr><td> <code title="">plusmn;</code> </td> <td> U+000B1 </td> <tr><td> <code title="">plusmn</code> </td> <td> U+000B1 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">plussim;</code> </td> <td> U+02A26 </td> <tr><td> <code title="">plustwo;</code> </td> <td> U+02A27 </td> <tr><td> <code title="">pm;</code> </td> <td> U+000B1 </td> <tr><td> <code title="">pointint;</code> </td> <td> U+02A15 </td> <tr><td> <code title="">popf;</code> </td> <td> U+1D561 </td> <tr><td> <code title="">pound;</code> </td> <td> U+000A3 </td> <tr><td> <code title="">pound</code> </td> <td> U+000A3 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">pr;</code> </td> <td> U+0227A </td> <tr><td> <code title="">prE;</code> </td> <td> U+02AB3 </td> <tr><td> <code title="">prap;</code> </td> <td> U+02AB7 </td> <tr><td> <code title="">prcue;</code> </td> <td> U+0227C </td> <tr><td> <code title="">pre;</code> </td> <td> U+02AAF </td> <tr><td> <code title="">prec;</code> </td> <td> U+0227A </td> <tr><td> <code title="">precapprox;</code> </td> <td> U+02AB7 </td> <tr><td> <code title="">preccurlyeq;</code> </td> <td> U+0227C </td> <tr><td> <code title="">preceq;</code> </td> <td> U+02AAF </td> <tr><td> <code title="">precnapprox;</code> </td> <td> U+02AB9 </td> <tr><td> <code title="">precneqq;</code> </td> <td> U+02AB5 </td> <tr><td> <code title="">precnsim;</code> </td> <td> U+022E8 </td> <tr><td> <code title="">precsim;</code> </td> <td> U+0227E </td> <tr><td> <code title="">prime;</code> </td> <td> U+02032 </td> <tr><td> <code title="">primes;</code> </td> <td> U+02119 </td> <tr><td> <code title="">prnE;</code> </td> <td> U+02AB5 </td> <tr><td> <code title="">prnap;</code> </td> <td> U+02AB9 </td> <tr><td> <code title="">prnsim;</code> </td> <td> U+022E8 </td> <tr><td> <code title="">prod;</code> </td> <td> U+0220F </td> <tr><td> <code title="">profalar;</code> </td> <td> U+0232E </td> <tr><td> <code title="">profline;</code> </td> <td> U+02312 </td> <tr><td> <code title="">profsurf;</code> </td> <td> U+02313 </td> <tr><td> <code title="">prop;</code> </td> <td> U+0221D </td> <tr><td> <code title="">propto;</code> </td> <td> U+0221D </td> <tr><td> <code title="">prsim;</code> </td> <td> U+0227E </td> <tr><td> <code title="">prurel;</code> </td> <td> U+022B0 </td> <tr><td> <code title="">pscr;</code> </td> <td> U+1D4C5 </td> <tr><td> <code title="">psi;</code> </td> <td> U+003C8 </td> <tr><td> <code title="">puncsp;</code> </td> <td> U+02008 </td> <tr><td> <code title="">qfr;</code> </td> <td> U+1D52E </td> <tr><td> <code title="">qint;</code> </td> <td> U+02A0C </td> <tr><td> <code title="">qopf;</code> </td> <td> U+1D562 </td> <tr><td> <code title="">qprime;</code> </td> <td> U+02057 </td> <tr><td> <code title="">qscr;</code> </td> <td> U+1D4C6 </td> <tr><td> <code title="">quaternions;</code> </td> <td> U+0210D </td> <tr><td> <code title="">quatint;</code> </td> <td> U+02A16 </td> <tr><td> <code title="">quest;</code> </td> <td> U+0003F </td> <tr><td> <code title="">questeq;</code> </td> <td> U+0225F </td> <tr><td> <code title="">quot;</code> </td> <td> U+00022 </td> <tr><td> <code title="">quot</code> </td> <td> U+00022 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">rAarr;</code> </td> <td> U+021DB </td> <tr><td> <code title="">rArr;</code> </td> <td> U+021D2 </td> <tr><td> <code title="">rAtail;</code> </td> <td> U+0291C </td> <tr><td> <code title="">rBarr;</code> </td> <td> U+0290F </td> <tr><td> <code title="">rHar;</code> </td> <td> U+02964 </td> <tr><td> <code title="">racute;</code> </td> <td> U+00155 </td> <tr><td> <code title="">radic;</code> </td> <td> U+0221A </td> <tr><td> <code title="">raemptyv;</code> </td> <td> U+029B3 </td> <tr><td> <code title="">rang;</code> </td> <td> U+027E9 </td> <tr><td> <code title="">rangd;</code> </td> <td> U+02992 </td> <tr><td> <code title="">range;</code> </td> <td> U+029A5 </td> <tr><td> <code title="">rangle;</code> </td> <td> U+027E9 </td> <tr><td> <code title="">raquo;</code> </td> <td> U+000BB </td> <tr><td> <code title="">raquo</code> </td> <td> U+000BB </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">rarr;</code> </td> <td> U+02192 </td> <tr><td> <code title="">rarrap;</code> </td> <td> U+02975 </td> <tr><td> <code title="">rarrb;</code> </td> <td> U+021E5 </td> <tr><td> <code title="">rarrbfs;</code> </td> <td> U+02920 </td> <tr><td> <code title="">rarrc;</code> </td> <td> U+02933 </td> <tr><td> <code title="">rarrfs;</code> </td> <td> U+0291E </td> <tr><td> <code title="">rarrhk;</code> </td> <td> U+021AA </td> <tr><td> <code title="">rarrlp;</code> </td> <td> U+021AC </td> <tr><td> <code title="">rarrpl;</code> </td> <td> U+02945 </td> <tr><td> <code title="">rarrsim;</code> </td> <td> U+02974 </td> <tr><td> <code title="">rarrtl;</code> </td> <td> U+021A3 </td> <tr><td> <code title="">rarrw;</code> </td> <td> U+0219D </td> <tr><td> <code title="">ratail;</code> </td> <td> U+0291A </td> <tr><td> <code title="">ratio;</code> </td> <td> U+02236 </td> <tr><td> <code title="">rationals;</code> </td> <td> U+0211A </td> <tr><td> <code title="">rbarr;</code> </td> <td> U+0290D </td> <tr><td> <code title="">rbbrk;</code> </td> <td> U+02773 </td> <tr><td> <code title="">rbrace;</code> </td> <td> U+0007D </td> <tr><td> <code title="">rbrack;</code> </td> <td> U+0005D </td> <tr><td> <code title="">rbrke;</code> </td> <td> U+0298C </td> <tr><td> <code title="">rbrksld;</code> </td> <td> U+0298E </td> <tr><td> <code title="">rbrkslu;</code> </td> <td> U+02990 </td> <tr><td> <code title="">rcaron;</code> </td> <td> U+00159 </td> <tr><td> <code title="">rcedil;</code> </td> <td> U+00157 </td> <tr><td> <code title="">rceil;</code> </td> <td> U+02309 </td> <tr><td> <code title="">rcub;</code> </td> <td> U+0007D </td> <tr><td> <code title="">rcy;</code> </td> <td> U+00440 </td> <tr><td> <code title="">rdca;</code> </td> <td> U+02937 </td> <tr><td> <code title="">rdldhar;</code> </td> <td> U+02969 </td> <tr><td> <code title="">rdquo;</code> </td> <td> U+0201D </td> <tr><td> <code title="">rdquor;</code> </td> <td> U+0201D </td> <tr><td> <code title="">rdsh;</code> </td> <td> U+021B3 </td> <tr><td> <code title="">real;</code> </td> <td> U+0211C </td> <tr><td> <code title="">realine;</code> </td> <td> U+0211B </td> <tr><td> <code title="">realpart;</code> </td> <td> U+0211C </td> <tr><td> <code title="">reals;</code> </td> <td> U+0211D </td> <tr><td> <code title="">rect;</code> </td> <td> U+025AD </td> <tr><td> <code title="">reg;</code> </td> <td> U+000AE </td> <tr><td> <code title="">reg</code> </td> <td> U+000AE </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">rfisht;</code> </td> <td> U+0297D </td> <tr><td> <code title="">rfloor;</code> </td> <td> U+0230B </td> <tr><td> <code title="">rfr;</code> </td> <td> U+1D52F </td> <tr><td> <code title="">rhard;</code> </td> <td> U+021C1 </td> <tr><td> <code title="">rharu;</code> </td> <td> U+021C0 </td> <tr><td> <code title="">rharul;</code> </td> <td> U+0296C </td> <tr><td> <code title="">rho;</code> </td> <td> U+003C1 </td> <tr><td> <code title="">rhov;</code> </td> <td> U+003F1 </td> <tr><td> <code title="">rightarrow;</code> </td> <td> U+02192 </td> <tr><td> <code title="">rightarrowtail;</code> </td> <td> U+021A3 </td> <tr><td> <code title="">rightharpoondown;</code> </td> <td> U+021C1 </td> <tr><td> <code title="">rightharpoonup;</code> </td> <td> U+021C0 </td> <tr><td> <code title="">rightleftarrows;</code> </td> <td> U+021C4 </td> <tr><td> <code title="">rightleftharpoons;</code> </td> <td> U+021CC </td> <tr><td> <code title="">rightrightarrows;</code> </td> <td> U+021C9 </td> <tr><td> <code title="">rightsquigarrow;</code> </td> <td> U+0219D </td> <tr><td> <code title="">rightthreetimes;</code> </td> <td> U+022CC </td> <tr><td> <code title="">ring;</code> </td> <td> U+002DA </td> <tr><td> <code title="">risingdotseq;</code> </td> <td> U+02253 </td> <tr><td> <code title="">rlarr;</code> </td> <td> U+021C4 </td> <tr><td> <code title="">rlhar;</code> </td> <td> U+021CC </td> <tr><td> <code title="">rlm;</code> </td> <td> U+0200F </td> <tr><td> <code title="">rmoust;</code> </td> <td> U+023B1 </td> <tr><td> <code title="">rmoustache;</code> </td> <td> U+023B1 </td> <tr><td> <code title="">rnmid;</code> </td> <td> U+02AEE </td> <tr><td> <code title="">roang;</code> </td> <td> U+027ED </td> <tr><td> <code title="">roarr;</code> </td> <td> U+021FE </td> <tr><td> <code title="">robrk;</code> </td> <td> U+027E7 </td> <tr><td> <code title="">ropar;</code> </td> <td> U+02986 </td> <tr><td> <code title="">ropf;</code> </td> <td> U+1D563 </td> <tr><td> <code title="">roplus;</code> </td> <td> U+02A2E </td> <tr><td> <code title="">rotimes;</code> </td> <td> U+02A35 </td> <tr><td> <code title="">rpar;</code> </td> <td> U+00029 </td> <tr><td> <code title="">rpargt;</code> </td> <td> U+02994 </td> <tr><td> <code title="">rppolint;</code> </td> <td> U+02A12 </td> <tr><td> <code title="">rrarr;</code> </td> <td> U+021C9 </td> <tr><td> <code title="">rsaquo;</code> </td> <td> U+0203A </td> <tr><td> <code title="">rscr;</code> </td> <td> U+1D4C7 </td> <tr><td> <code title="">rsh;</code> </td> <td> U+021B1 </td> <tr><td> <code title="">rsqb;</code> </td> <td> U+0005D </td> <tr><td> <code title="">rsquo;</code> </td> <td> U+02019 </td> <tr><td> <code title="">rsquor;</code> </td> <td> U+02019 </td> <tr><td> <code title="">rthree;</code> </td> <td> U+022CC </td> <tr><td> <code title="">rtimes;</code> </td> <td> U+022CA </td> <tr><td> <code title="">rtri;</code> </td> <td> U+025B9 </td> <tr><td> <code title="">rtrie;</code> </td> <td> U+022B5 </td> <tr><td> <code title="">rtrif;</code> </td> <td> U+025B8 </td> <tr><td> <code title="">rtriltri;</code> </td> <td> U+029CE </td> <tr><td> <code title="">ruluhar;</code> </td> <td> U+02968 </td> <tr><td> <code title="">rx;</code> </td> <td> U+0211E </td> <tr><td> <code title="">sacute;</code> </td> <td> U+0015B </td> <tr><td> <code title="">sbquo;</code> </td> <td> U+0201A </td> <tr><td> <code title="">sc;</code> </td> <td> U+0227B </td> <tr><td> <code title="">scE;</code> </td> <td> U+02AB4 </td> <tr><td> <code title="">scap;</code> </td> <td> U+02AB8 </td> <tr><td> <code title="">scaron;</code> </td> <td> U+00161 </td> <tr><td> <code title="">sccue;</code> </td> <td> U+0227D </td> <tr><td> <code title="">sce;</code> </td> <td> U+02AB0 </td> <tr><td> <code title="">scedil;</code> </td> <td> U+0015F </td> <tr><td> <code title="">scirc;</code> </td> <td> U+0015D </td> <tr><td> <code title="">scnE;</code> </td> <td> U+02AB6 </td> <tr><td> <code title="">scnap;</code> </td> <td> U+02ABA </td> <tr><td> <code title="">scnsim;</code> </td> <td> U+022E9 </td> <tr><td> <code title="">scpolint;</code> </td> <td> U+02A13 </td> <tr><td> <code title="">scsim;</code> </td> <td> U+0227F </td> <tr><td> <code title="">scy;</code> </td> <td> U+00441 </td> <tr><td> <code title="">sdot;</code> </td> <td> U+022C5 </td> <tr><td> <code title="">sdotb;</code> </td> <td> U+022A1 </td> <tr><td> <code title="">sdote;</code> </td> <td> U+02A66 </td> <tr><td> <code title="">seArr;</code> </td> <td> U+021D8 </td> <tr><td> <code title="">searhk;</code> </td> <td> U+02925 </td> <tr><td> <code title="">searr;</code> </td> <td> U+02198 </td> <tr><td> <code title="">searrow;</code> </td> <td> U+02198 </td> <tr><td> <code title="">sect;</code> </td> <td> U+000A7 </td> <tr><td> <code title="">sect</code> </td> <td> U+000A7 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">semi;</code> </td> <td> U+0003B </td> <tr><td> <code title="">seswar;</code> </td> <td> U+02929 </td> <tr><td> <code title="">setminus;</code> </td> <td> U+02216 </td> <tr><td> <code title="">setmn;</code> </td> <td> U+02216 </td> <tr><td> <code title="">sext;</code> </td> <td> U+02736 </td> <tr><td> <code title="">sfr;</code> </td> <td> U+1D530 </td> <tr><td> <code title="">sfrown;</code> </td> <td> U+02322 </td> <tr><td> <code title="">sharp;</code> </td> <td> U+0266F </td> <tr><td> <code title="">shchcy;</code> </td> <td> U+00449 </td> <tr><td> <code title="">shcy;</code> </td> <td> U+00448 </td> <tr><td> <code title="">shortmid;</code> </td> <td> U+02223 </td> <tr><td> <code title="">shortparallel;</code> </td> <td> U+02225 </td> <tr><td> <code title="">shy;</code> </td> <td> U+000AD </td> <tr><td> <code title="">shy</code> </td> <td> U+000AD </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">sigma;</code> </td> <td> U+003C3 </td> <tr><td> <code title="">sigmaf;</code> </td> <td> U+003C2 </td> <tr><td> <code title="">sigmav;</code> </td> <td> U+003C2 </td> <tr><td> <code title="">sim;</code> </td> <td> U+0223C </td> <tr><td> <code title="">simdot;</code> </td> <td> U+02A6A </td> <tr><td> <code title="">sime;</code> </td> <td> U+02243 </td> <tr><td> <code title="">simeq;</code> </td> <td> U+02243 </td> <tr><td> <code title="">simg;</code> </td> <td> U+02A9E </td> <tr><td> <code title="">simgE;</code> </td> <td> U+02AA0 </td> <tr><td> <code title="">siml;</code> </td> <td> U+02A9D </td> <tr><td> <code title="">simlE;</code> </td> <td> U+02A9F </td> <tr><td> <code title="">simne;</code> </td> <td> U+02246 </td> <tr><td> <code title="">simplus;</code> </td> <td> U+02A24 </td> <tr><td> <code title="">simrarr;</code> </td> <td> U+02972 </td> <tr><td> <code title="">slarr;</code> </td> <td> U+02190 </td> <tr><td> <code title="">smallsetminus;</code> </td> <td> U+02216 </td> <tr><td> <code title="">smashp;</code> </td> <td> U+02A33 </td> <tr><td> <code title="">smeparsl;</code> </td> <td> U+029E4 </td> <tr><td> <code title="">smid;</code> </td> <td> U+02223 </td> <tr><td> <code title="">smile;</code> </td> <td> U+02323 </td> <tr><td> <code title="">smt;</code> </td> <td> U+02AAA </td> <tr><td> <code title="">smte;</code> </td> <td> U+02AAC </td> <tr><td> <code title="">softcy;</code> </td> <td> U+0044C </td> <tr><td> <code title="">sol;</code> </td> <td> U+0002F </td> <tr><td> <code title="">solb;</code> </td> <td> U+029C4 </td> <tr><td> <code title="">solbar;</code> </td> <td> U+0233F </td> <tr><td> <code title="">sopf;</code> </td> <td> U+1D564 </td> <tr><td> <code title="">spades;</code> </td> <td> U+02660 </td> <tr><td> <code title="">spadesuit;</code> </td> <td> U+02660 </td> <tr><td> <code title="">spar;</code> </td> <td> U+02225 </td> <tr><td> <code title="">sqcap;</code> </td> <td> U+02293 </td> <tr><td> <code title="">sqcup;</code> </td> <td> U+02294 </td> <tr><td> <code title="">sqsub;</code> </td> <td> U+0228F </td> <tr><td> <code title="">sqsube;</code> </td> <td> U+02291 </td> <tr><td> <code title="">sqsubset;</code> </td> <td> U+0228F </td> <tr><td> <code title="">sqsubseteq;</code> </td> <td> U+02291 </td> <tr><td> <code title="">sqsup;</code> </td> <td> U+02290 </td> <tr><td> <code title="">sqsupe;</code> </td> <td> U+02292 </td> <tr><td> <code title="">sqsupset;</code> </td> <td> U+02290 </td> <tr><td> <code title="">sqsupseteq;</code> </td> <td> U+02292 </td> <tr><td> <code title="">squ;</code> </td> <td> U+025A1 </td> <tr><td> <code title="">square;</code> </td> <td> U+025A1 </td> <tr><td> <code title="">squarf;</code> </td> <td> U+025AA </td> <tr><td> <code title="">squf;</code> </td> <td> U+025AA </td> <tr><td> <code title="">srarr;</code> </td> <td> U+02192 </td> <tr><td> <code title="">sscr;</code> </td> <td> U+1D4C8 </td> <tr><td> <code title="">ssetmn;</code> </td> <td> U+02216 </td> <tr><td> <code title="">ssmile;</code> </td> <td> U+02323 </td> <tr><td> <code title="">sstarf;</code> </td> <td> U+022C6 </td> <tr><td> <code title="">star;</code> </td> <td> U+02606 </td> <tr><td> <code title="">starf;</code> </td> <td> U+02605 </td> <tr><td> <code title="">straightepsilon;</code> </td> <td> U+003F5 </td> <tr><td> <code title="">straightphi;</code> </td> <td> U+003D5 </td> <tr><td> <code title="">strns;</code> </td> <td> U+000AF </td> <tr><td> <code title="">sub;</code> </td> <td> U+02282 </td> <tr><td> <code title="">subE;</code> </td> <td> U+02AC5 </td> <tr><td> <code title="">subdot;</code> </td> <td> U+02ABD </td> <tr><td> <code title="">sube;</code> </td> <td> U+02286 </td> <tr><td> <code title="">subedot;</code> </td> <td> U+02AC3 </td> <tr><td> <code title="">submult;</code> </td> <td> U+02AC1 </td> <tr><td> <code title="">subnE;</code> </td> <td> U+02ACB </td> <tr><td> <code title="">subne;</code> </td> <td> U+0228A </td> <tr><td> <code title="">subplus;</code> </td> <td> U+02ABF </td> <tr><td> <code title="">subrarr;</code> </td> <td> U+02979 </td> <tr><td> <code title="">subset;</code> </td> <td> U+02282 </td> <tr><td> <code title="">subseteq;</code> </td> <td> U+02286 </td> <tr><td> <code title="">subseteqq;</code> </td> <td> U+02AC5 </td> <tr><td> <code title="">subsetneq;</code> </td> <td> U+0228A </td> <tr><td> <code title="">subsetneqq;</code> </td> <td> U+02ACB </td> <tr><td> <code title="">subsim;</code> </td> <td> U+02AC7 </td> <tr><td> <code title="">subsub;</code> </td> <td> U+02AD5 </td> <tr><td> <code title="">subsup;</code> </td> <td> U+02AD3 </td> <tr><td> <code title="">succ;</code> </td> <td> U+0227B </td> <tr><td> <code title="">succapprox;</code> </td> <td> U+02AB8 </td> <tr><td> <code title="">succcurlyeq;</code> </td> <td> U+0227D </td> <tr><td> <code title="">succeq;</code> </td> <td> U+02AB0 </td> <tr><td> <code title="">succnapprox;</code> </td> <td> U+02ABA </td> <tr><td> <code title="">succneqq;</code> </td> <td> U+02AB6 </td> <tr><td> <code title="">succnsim;</code> </td> <td> U+022E9 </td> <tr><td> <code title="">succsim;</code> </td> <td> U+0227F </td> <tr><td> <code title="">sum;</code> </td> <td> U+02211 </td> <tr><td> <code title="">sung;</code> </td> <td> U+0266A </td> <tr><td> <code title="">sup1;</code> </td> <td> U+000B9 </td> <tr><td> <code title="">sup1</code> </td> <td> U+000B9 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">sup2;</code> </td> <td> U+000B2 </td> <tr><td> <code title="">sup2</code> </td> <td> U+000B2 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">sup3;</code> </td> <td> U+000B3 </td> <tr><td> <code title="">sup3</code> </td> <td> U+000B3 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">sup;</code> </td> <td> U+02283 </td> <tr><td> <code title="">supE;</code> </td> <td> U+02AC6 </td> <tr><td> <code title="">supdot;</code> </td> <td> U+02ABE </td> <tr><td> <code title="">supdsub;</code> </td> <td> U+02AD8 </td> <tr><td> <code title="">supe;</code> </td> <td> U+02287 </td> <tr><td> <code title="">supedot;</code> </td> <td> U+02AC4 </td> <tr><td> <code title="">suphsol;</code> </td> <td> U+027C9 </td> <tr><td> <code title="">suphsub;</code> </td> <td> U+02AD7 </td> <tr><td> <code title="">suplarr;</code> </td> <td> U+0297B </td> <tr><td> <code title="">supmult;</code> </td> <td> U+02AC2 </td> <tr><td> <code title="">supnE;</code> </td> <td> U+02ACC </td> <tr><td> <code title="">supne;</code> </td> <td> U+0228B </td> <tr><td> <code title="">supplus;</code> </td> <td> U+02AC0 </td> <tr><td> <code title="">supset;</code> </td> <td> U+02283 </td> <tr><td> <code title="">supseteq;</code> </td> <td> U+02287 </td> <tr><td> <code title="">supseteqq;</code> </td> <td> U+02AC6 </td> <tr><td> <code title="">supsetneq;</code> </td> <td> U+0228B </td> <tr><td> <code title="">supsetneqq;</code> </td> <td> U+02ACC </td> <tr><td> <code title="">supsim;</code> </td> <td> U+02AC8 </td> <tr><td> <code title="">supsub;</code> </td> <td> U+02AD4 </td> <tr><td> <code title="">supsup;</code> </td> <td> U+02AD6 </td> <tr><td> <code title="">swArr;</code> </td> <td> U+021D9 </td> <tr><td> <code title="">swarhk;</code> </td> <td> U+02926 </td> <tr><td> <code title="">swarr;</code> </td> <td> U+02199 </td> <tr><td> <code title="">swarrow;</code> </td> <td> U+02199 </td> <tr><td> <code title="">swnwar;</code> </td> <td> U+0292A </td> <tr><td> <code title="">szlig;</code> </td> <td> U+000DF </td> <tr><td> <code title="">szlig</code> </td> <td> U+000DF </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">target;</code> </td> <td> U+02316 </td> <tr><td> <code title="">tau;</code> </td> <td> U+003C4 </td> <tr><td> <code title="">tbrk;</code> </td> <td> U+023B4 </td> <tr><td> <code title="">tcaron;</code> </td> <td> U+00165 </td> <tr><td> <code title="">tcedil;</code> </td> <td> U+00163 </td> <tr><td> <code title="">tcy;</code> </td> <td> U+00442 </td> <tr><td> <code title="">tdot;</code> </td> <td> U+020DB </td> <tr><td> <code title="">telrec;</code> </td> <td> U+02315 </td> <tr><td> <code title="">tfr;</code> </td> <td> U+1D531 </td> <tr><td> <code title="">there4;</code> </td> <td> U+02234 </td> <tr><td> <code title="">therefore;</code> </td> <td> U+02234 </td> <tr><td> <code title="">theta;</code> </td> <td> U+003B8 </td> <tr><td> <code title="">thetasym;</code> </td> <td> U+003D1 </td> <tr><td> <code title="">thetav;</code> </td> <td> U+003D1 </td> <tr><td> <code title="">thickapprox;</code> </td> <td> U+02248 </td> <tr><td> <code title="">thicksim;</code> </td> <td> U+0223C </td> <tr><td> <code title="">thinsp;</code> </td> <td> U+02009 </td> <tr><td> <code title="">thkap;</code> </td> <td> U+02248 </td> <tr><td> <code title="">thksim;</code> </td> <td> U+0223C </td> <tr><td> <code title="">thorn;</code> </td> <td> U+000FE </td> <tr><td> <code title="">thorn</code> </td> <td> U+000FE </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">tilde;</code> </td> <td> U+002DC </td> <tr><td> <code title="">times;</code> </td> <td> U+000D7 </td> <tr><td> <code title="">times</code> </td> <td> U+000D7 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">timesb;</code> </td> <td> U+022A0 </td> <tr><td> <code title="">timesbar;</code> </td> <td> U+02A31 </td> <tr><td> <code title="">timesd;</code> </td> <td> U+02A30 </td> <tr><td> <code title="">tint;</code> </td> <td> U+0222D </td> <tr><td> <code title="">toea;</code> </td> <td> U+02928 </td> <tr><td> <code title="">top;</code> </td> <td> U+022A4 </td> <tr><td> <code title="">topbot;</code> </td> <td> U+02336 </td> <tr><td> <code title="">topcir;</code> </td> <td> U+02AF1 </td> <tr><td> <code title="">topf;</code> </td> <td> U+1D565 </td> <tr><td> <code title="">topfork;</code> </td> <td> U+02ADA </td> <tr><td> <code title="">tosa;</code> </td> <td> U+02929 </td> <tr><td> <code title="">tprime;</code> </td> <td> U+02034 </td> <tr><td> <code title="">trade;</code> </td> <td> U+02122 </td> <tr><td> <code title="">triangle;</code> </td> <td> U+025B5 </td> <tr><td> <code title="">triangledown;</code> </td> <td> U+025BF </td> <tr><td> <code title="">triangleleft;</code> </td> <td> U+025C3 </td> <tr><td> <code title="">trianglelefteq;</code> </td> <td> U+022B4 </td> <tr><td> <code title="">triangleq;</code> </td> <td> U+0225C </td> <tr><td> <code title="">triangleright;</code> </td> <td> U+025B9 </td> <tr><td> <code title="">trianglerighteq;</code> </td> <td> U+022B5 </td> <tr><td> <code title="">tridot;</code> </td> <td> U+025EC </td> <tr><td> <code title="">trie;</code> </td> <td> U+0225C </td> <tr><td> <code title="">triminus;</code> </td> <td> U+02A3A </td> <tr><td> <code title="">triplus;</code> </td> <td> U+02A39 </td> <tr><td> <code title="">trisb;</code> </td> <td> U+029CD </td> <tr><td> <code title="">tritime;</code> </td> <td> U+02A3B </td> <tr><td> <code title="">trpezium;</code> </td> <td> U+023E2 </td> <tr><td> <code title="">tscr;</code> </td> <td> U+1D4C9 </td> <tr><td> <code title="">tscy;</code> </td> <td> U+00446 </td> <tr><td> <code title="">tshcy;</code> </td> <td> U+0045B </td> <tr><td> <code title="">tstrok;</code> </td> <td> U+00167 </td> <tr><td> <code title="">twixt;</code> </td> <td> U+0226C </td> <tr><td> <code title="">twoheadleftarrow;</code> </td> <td> U+0219E </td> <tr><td> <code title="">twoheadrightarrow;</code> </td> <td> U+021A0 </td> <tr><td> <code title="">uArr;</code> </td> <td> U+021D1 </td> <tr><td> <code title="">uHar;</code> </td> <td> U+02963 </td> <tr><td> <code title="">uacute;</code> </td> <td> U+000FA </td> <tr><td> <code title="">uacute</code> </td> <td> U+000FA </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">uarr;</code> </td> <td> U+02191 </td> <tr><td> <code title="">ubrcy;</code> </td> <td> U+0045E </td> <tr><td> <code title="">ubreve;</code> </td> <td> U+0016D </td> <tr><td> <code title="">ucirc;</code> </td> <td> U+000FB </td> <tr><td> <code title="">ucirc</code> </td> <td> U+000FB </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">ucy;</code> </td> <td> U+00443 </td> <tr><td> <code title="">udarr;</code> </td> <td> U+021C5 </td> <tr><td> <code title="">udblac;</code> </td> <td> U+00171 </td> <tr><td> <code title="">udhar;</code> </td> <td> U+0296E </td> <tr><td> <code title="">ufisht;</code> </td> <td> U+0297E </td> <tr><td> <code title="">ufr;</code> </td> <td> U+1D532 </td> <tr><td> <code title="">ugrave;</code> </td> <td> U+000F9 </td> <tr><td> <code title="">ugrave</code> </td> <td> U+000F9 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">uharl;</code> </td> <td> U+021BF </td> <tr><td> <code title="">uharr;</code> </td> <td> U+021BE </td> <tr><td> <code title="">uhblk;</code> </td> <td> U+02580 </td> <tr><td> <code title="">ulcorn;</code> </td> <td> U+0231C </td> <tr><td> <code title="">ulcorner;</code> </td> <td> U+0231C </td> <tr><td> <code title="">ulcrop;</code> </td> <td> U+0230F </td> <tr><td> <code title="">ultri;</code> </td> <td> U+025F8 </td> <tr><td> <code title="">umacr;</code> </td> <td> U+0016B </td> <tr><td> <code title="">uml;</code> </td> <td> U+000A8 </td> <tr><td> <code title="">uml</code> </td> <td> U+000A8 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">uogon;</code> </td> <td> U+00173 </td> <tr><td> <code title="">uopf;</code> </td> <td> U+1D566 </td> <tr><td> <code title="">uparrow;</code> </td> <td> U+02191 </td> <tr><td> <code title="">updownarrow;</code> </td> <td> U+02195 </td> <tr><td> <code title="">upharpoonleft;</code> </td> <td> U+021BF </td> <tr><td> <code title="">upharpoonright;</code> </td> <td> U+021BE </td> <tr><td> <code title="">uplus;</code> </td> <td> U+0228E </td> <tr><td> <code title="">upsi;</code> </td> <td> U+003C5 </td> <tr><td> <code title="">upsih;</code> </td> <td> U+003D2 </td> <tr><td> <code title="">upsilon;</code> </td> <td> U+003C5 </td> <tr><td> <code title="">upuparrows;</code> </td> <td> U+021C8 </td> <tr><td> <code title="">urcorn;</code> </td> <td> U+0231D </td> <tr><td> <code title="">urcorner;</code> </td> <td> U+0231D </td> <tr><td> <code title="">urcrop;</code> </td> <td> U+0230E </td> <tr><td> <code title="">uring;</code> </td> <td> U+0016F </td> <tr><td> <code title="">urtri;</code> </td> <td> U+025F9 </td> <tr><td> <code title="">uscr;</code> </td> <td> U+1D4CA </td> <tr><td> <code title="">utdot;</code> </td> <td> U+022F0 </td> <tr><td> <code title="">utilde;</code> </td> <td> U+00169 </td> <tr><td> <code title="">utri;</code> </td> <td> U+025B5 </td> <tr><td> <code title="">utrif;</code> </td> <td> U+025B4 </td> <tr><td> <code title="">uuarr;</code> </td> <td> U+021C8 </td> <tr><td> <code title="">uuml;</code> </td> <td> U+000FC </td> <tr><td> <code title="">uuml</code> </td> <td> U+000FC </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">uwangle;</code> </td> <td> U+029A7 </td> <tr><td> <code title="">vArr;</code> </td> <td> U+021D5 </td> <tr><td> <code title="">vBar;</code> </td> <td> U+02AE8 </td> <tr><td> <code title="">vBarv;</code> </td> <td> U+02AE9 </td> <tr><td> <code title="">vDash;</code> </td> <td> U+022A8 </td> <tr><td> <code title="">vangrt;</code> </td> <td> U+0299C </td> <tr><td> <code title="">varepsilon;</code> </td> <td> U+003F5 </td> <tr><td> <code title="">varkappa;</code> </td> <td> U+003F0 </td> <tr><td> <code title="">varnothing;</code> </td> <td> U+02205 </td> <tr><td> <code title="">varphi;</code> </td> <td> U+003D5 </td> <tr><td> <code title="">varpi;</code> </td> <td> U+003D6 </td> <tr><td> <code title="">varpropto;</code> </td> <td> U+0221D </td> <tr><td> <code title="">varr;</code> </td> <td> U+02195 </td> <tr><td> <code title="">varrho;</code> </td> <td> U+003F1 </td> <tr><td> <code title="">varsigma;</code> </td> <td> U+003C2 </td> <tr><td> <code title="">vartheta;</code> </td> <td> U+003D1 </td> <tr><td> <code title="">vartriangleleft;</code> </td> <td> U+022B2 </td> <tr><td> <code title="">vartriangleright;</code> </td> <td> U+022B3 </td> <tr><td> <code title="">vcy;</code> </td> <td> U+00432 </td> <tr><td> <code title="">vdash;</code> </td> <td> U+022A2 </td> <tr><td> <code title="">vee;</code> </td> <td> U+02228 </td> <tr><td> <code title="">veebar;</code> </td> <td> U+022BB </td> <tr><td> <code title="">veeeq;</code> </td> <td> U+0225A </td> <tr><td> <code title="">vellip;</code> </td> <td> U+022EE </td> <tr><td> <code title="">verbar;</code> </td> <td> U+0007C </td> <tr><td> <code title="">vert;</code> </td> <td> U+0007C </td> <tr><td> <code title="">vfr;</code> </td> <td> U+1D533 </td> <tr><td> <code title="">vltri;</code> </td> <td> U+022B2 </td> <tr><td> <code title="">vopf;</code> </td> <td> U+1D567 </td> <tr><td> <code title="">vprop;</code> </td> <td> U+0221D </td> <tr><td> <code title="">vrtri;</code> </td> <td> U+022B3 </td> <tr><td> <code title="">vscr;</code> </td> <td> U+1D4CB </td> <tr><td> <code title="">vzigzag;</code> </td> <td> U+0299A </td> <tr><td> <code title="">wcirc;</code> </td> <td> U+00175 </td> <tr><td> <code title="">wedbar;</code> </td> <td> U+02A5F </td> <tr><td> <code title="">wedge;</code> </td> <td> U+02227 </td> <tr><td> <code title="">wedgeq;</code> </td> <td> U+02259 </td> <tr><td> <code title="">weierp;</code> </td> <td> U+02118 </td> <tr><td> <code title="">wfr;</code> </td> <td> U+1D534 </td> <tr><td> <code title="">wopf;</code> </td> <td> U+1D568 </td> <tr><td> <code title="">wp;</code> </td> <td> U+02118 </td> <tr><td> <code title="">wr;</code> </td> <td> U+02240 </td> <tr><td> <code title="">wreath;</code> </td> <td> U+02240 </td> <tr><td> <code title="">wscr;</code> </td> <td> U+1D4CC </td> <tr><td> <code title="">xcap;</code> </td> <td> U+022C2 </td> <tr><td> <code title="">xcirc;</code> </td> <td> U+025EF </td> <tr><td> <code title="">xcup;</code> </td> <td> U+022C3 </td> <tr><td> <code title="">xdtri;</code> </td> <td> U+025BD </td> <tr><td> <code title="">xfr;</code> </td> <td> U+1D535 </td> <tr><td> <code title="">xhArr;</code> </td> <td> U+027FA </td> <tr><td> <code title="">xharr;</code> </td> <td> U+027F7 </td> <tr><td> <code title="">xi;</code> </td> <td> U+003BE </td> <tr><td> <code title="">xlArr;</code> </td> <td> U+027F8 </td> <tr><td> <code title="">xlarr;</code> </td> <td> U+027F5 </td> <tr><td> <code title="">xmap;</code> </td> <td> U+027FC </td> <tr><td> <code title="">xnis;</code> </td> <td> U+022FB </td> <tr><td> <code title="">xodot;</code> </td> <td> U+02A00 </td> <tr><td> <code title="">xopf;</code> </td> <td> U+1D569 </td> <tr><td> <code title="">xoplus;</code> </td> <td> U+02A01 </td> <tr><td> <code title="">xotime;</code> </td> <td> U+02A02 </td> <tr><td> <code title="">xrArr;</code> </td> <td> U+027F9 </td> <tr><td> <code title="">xrarr;</code> </td> <td> U+027F6 </td> <tr><td> <code title="">xscr;</code> </td> <td> U+1D4CD </td> <tr><td> <code title="">xsqcup;</code> </td> <td> U+02A06 </td> <tr><td> <code title="">xuplus;</code> </td> <td> U+02A04 </td> <tr><td> <code title="">xutri;</code> </td> <td> U+025B3 </td> <tr><td> <code title="">xvee;</code> </td> <td> U+022C1 </td> <tr><td> <code title="">xwedge;</code> </td> <td> U+022C0 </td> <tr><td> <code title="">yacute;</code> </td> <td> U+000FD </td> <tr><td> <code title="">yacute</code> </td> <td> U+000FD </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">yacy;</code> </td> <td> U+0044F </td> <tr><td> <code title="">ycirc;</code> </td> <td> U+00177 </td> <tr><td> <code title="">ycy;</code> </td> <td> U+0044B </td> <tr><td> <code title="">yen;</code> </td> <td> U+000A5 </td> <tr><td> <code title="">yen</code> </td> <td> U+000A5 </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">yfr;</code> </td> <td> U+1D536 </td> <tr><td> <code title="">yicy;</code> </td> <td> U+00457 </td> <tr><td> <code title="">yopf;</code> </td> <td> U+1D56A </td> <tr><td> <code title="">yscr;</code> </td> <td> U+1D4CE </td> <tr><td> <code title="">yucy;</code> </td> <td> U+0044E </td> <tr><td> <code title="">yuml;</code> </td> <td> U+000FF </td> <tr><td> <code title="">yuml</code> </td> <td> U+000FF </td> </tr><!-- (invalid entity with missing semicolon for legacy support only) --><tr><td> <code title="">zacute;</code> </td> <td> U+0017A </td> <tr><td> <code title="">zcaron;</code> </td> <td> U+0017E </td> <tr><td> <code title="">zcy;</code> </td> <td> U+00437 </td> <tr><td> <code title="">zdot;</code> </td> <td> U+0017C </td> <tr><td> <code title="">zeetrf;</code> </td> <td> U+02128 </td> <tr><td> <code title="">zeta;</code> </td> <td> U+003B6 </td> <tr><td> <code title="">zfr;</code> </td> <td> U+1D537 </td> <tr><td> <code title="">zhcy;</code> </td> <td> U+00436 </td> <tr><td> <code title="">zigrarr;</code> </td> <td> U+021DD </td> <tr><td> <code title="">zopf;</code> </td> <td> U+1D56B </td> <tr><td> <code title="">zscr;</code> </td> <td> U+1D4CF </td> <tr><td> <code title="">zwj;</code> </td> <td> U+0200D </td> <tr><td> <code title="">zwnj;</code> </td> <td> U+0200C </td> </table>
+
+ \ No newline at end of file
diff --git a/components/htmlfive/moz.build b/components/htmlfive/moz.build
new file mode 100644
index 000000000..802463f49
--- /dev/null
+++ b/components/htmlfive/moz.build
@@ -0,0 +1,110 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+# Because this is largely done by codegen the policies regarding the
+# traditional structure and reverting UNIFIED_SOURCES are hereby rescinded
+# for this component.
+
+XPIDL_SOURCES += [
+ 'nsIParserUtils.idl',
+ 'nsIScriptableUnescapeHTML.idl',
+]
+
+XPIDL_MODULE = 'html5'
+
+EXPORTS += [
+ 'jArray.h',
+ 'nsAHtml5TreeBuilderState.h',
+ 'nsAHtml5TreeOpSink.h',
+ 'nsHtml5ArrayCopy.h',
+ 'nsHtml5AtomList.h',
+ 'nsHtml5Atoms.h',
+ 'nsHtml5AtomTable.h',
+ 'nsHtml5AttributeEntry.h',
+ 'nsHtml5AttributeName.h',
+ 'nsHtml5ByteReadable.h',
+ 'nsHtml5ContentCreatorFunction.h',
+ 'nsHtml5DependentUTF16Buffer.h',
+ 'nsHtml5DocumentBuilder.h',
+ 'nsHtml5DocumentMode.h',
+ 'nsHtml5HtmlAttributes.h',
+ 'nsHtml5Macros.h',
+ 'nsHtml5MetaScanner.h',
+ 'nsHtml5MetaScannerHSupplement.h',
+ 'nsHtml5Module.h',
+ 'nsHtml5NamedCharacters.h',
+ 'nsHtml5NamedCharactersAccel.h',
+ 'nsHtml5OplessBuilder.h',
+ 'nsHtml5OwningUTF16Buffer.h',
+ 'nsHtml5Parser.h',
+ 'nsHtml5PlainTextUtils.h',
+ 'nsHtml5Portability.h',
+ 'nsHtml5RefPtr.h',
+ 'nsHtml5Speculation.h',
+ 'nsHtml5SpeculativeLoad.h',
+ 'nsHtml5StreamListener.h',
+ 'nsHtml5StreamParser.h',
+ 'nsHtml5String.h',
+ 'nsHtml5StringParser.h',
+ 'nsHtml5SVGLoadDispatcher.h',
+ 'nsHtml5TreeOperation.h',
+ 'nsHtml5TreeOpExecutor.h',
+ 'nsHtml5TreeOpStage.h',
+ 'nsHtml5UTF16Buffer.h',
+ 'nsHtml5UTF16BufferHSupplement.h',
+ 'nsHtml5ViewSourceUtils.h',
+ 'nsIContentHandle.h',
+ 'nsParserUtils.h',
+]
+
+UNIFIED_SOURCES += [
+ 'nsHtml5Atom.cpp',
+ 'nsHtml5Atoms.cpp',
+ 'nsHtml5AtomTable.cpp',
+ 'nsHtml5AttributeName.cpp',
+ 'nsHtml5DependentUTF16Buffer.cpp',
+ 'nsHtml5DocumentBuilder.cpp',
+ 'nsHtml5ElementName.cpp',
+ 'nsHtml5Highlighter.cpp',
+ 'nsHtml5HtmlAttributes.cpp',
+ 'nsHtml5MetaScanner.cpp',
+ 'nsHtml5Module.cpp',
+ 'nsHtml5NamedCharacters.cpp',
+ 'nsHtml5NamedCharactersAccel.cpp',
+ 'nsHtml5OplessBuilder.cpp',
+ 'nsHtml5OwningUTF16Buffer.cpp',
+ 'nsHtml5Parser.cpp',
+ 'nsHtml5PlainTextUtils.cpp',
+ 'nsHtml5Portability.cpp',
+ 'nsHtml5Speculation.cpp',
+ 'nsHtml5SpeculativeLoad.cpp',
+ 'nsHtml5StackNode.cpp',
+ 'nsHtml5StateSnapshot.cpp',
+ 'nsHtml5StreamListener.cpp',
+ 'nsHtml5StreamParser.cpp',
+ 'nsHtml5String.cpp',
+ 'nsHtml5StringParser.cpp',
+ 'nsHtml5SVGLoadDispatcher.cpp',
+ 'nsHtml5Tokenizer.cpp',
+ 'nsHtml5TreeBuilder.cpp',
+ 'nsHtml5TreeOperation.cpp',
+ 'nsHtml5TreeOpExecutor.cpp',
+ 'nsHtml5TreeOpStage.cpp',
+ 'nsHtml5UTF16Buffer.cpp',
+ 'nsHtml5ViewSourceUtils.cpp',
+ 'nsParserUtils.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+# DEFINES['ENABLE_VOID_MENUITEM'] = True
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+]
+
+if CONFIG['GNU_CXX'] or CONFIG['CLANG_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow',
+ '-Wno-implicit-fallthrough']
diff --git a/components/htmlfive/nsAHtml5TreeBuilderState.h b/components/htmlfive/nsAHtml5TreeBuilderState.h
new file mode 100644
index 000000000..f5fb4e2bd
--- /dev/null
+++ b/components/htmlfive/nsAHtml5TreeBuilderState.h
@@ -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/. */
+
+#ifndef nsAHtml5TreeBuilderState_h
+#define nsAHtml5TreeBuilderState_h
+
+#include "nsIContentHandle.h"
+
+/**
+ * Interface for exposing the internal state of the HTML5 tree builder.
+ * For more documentation, please see
+ * http://hg.mozilla.org/projects/htmlparser/file/tip/src/nu/validator/htmlparser/impl/StateSnapshot.java
+ */
+class nsAHtml5TreeBuilderState {
+ public:
+
+ virtual jArray<nsHtml5StackNode*,int32_t> getStack() = 0;
+
+ virtual jArray<nsHtml5StackNode*,int32_t> getListOfActiveFormattingElements() = 0;
+
+ virtual jArray<int32_t,int32_t> getTemplateModeStack() = 0;
+
+ virtual int32_t getStackLength() = 0;
+
+ virtual int32_t getListOfActiveFormattingElementsLength() = 0;
+
+ virtual int32_t getTemplateModeStackLength() = 0;
+
+ virtual nsIContentHandle* getFormPointer() = 0;
+
+ virtual nsIContentHandle* getHeadPointer() = 0;
+
+ virtual nsIContentHandle* getDeepTreeSurrogateParent() = 0;
+
+ virtual int32_t getMode() = 0;
+
+ virtual int32_t getOriginalMode() = 0;
+
+ virtual bool isFramesetOk() = 0;
+
+ virtual bool isNeedToDropLF() = 0;
+
+ virtual bool isQuirks() = 0;
+
+ virtual ~nsAHtml5TreeBuilderState() {
+ }
+};
+
+#endif /* nsAHtml5TreeBuilderState_h */
diff --git a/components/htmlfive/nsAHtml5TreeOpSink.h b/components/htmlfive/nsAHtml5TreeOpSink.h
new file mode 100644
index 000000000..f3d10c4d4
--- /dev/null
+++ b/components/htmlfive/nsAHtml5TreeOpSink.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAHtml5TreeOpSink_h
+#define nsAHtml5TreeOpSink_h
+
+/**
+ * The purpose of this interface is to connect a tree op executor
+ * (main-thread case), a tree op stage (non-speculative off-the-main-thread
+ * case) or a speculation (speculative case).
+ */
+class nsAHtml5TreeOpSink {
+ public:
+
+ /**
+ * Flush the operations from the tree operations from the argument
+ * queue into this sink unconditionally.
+ */
+ virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue) = 0;
+
+};
+
+#endif /* nsAHtml5TreeOpSink_h */
diff --git a/components/htmlfive/nsHtml5ArrayCopy.h b/components/htmlfive/nsHtml5ArrayCopy.h
new file mode 100644
index 000000000..5e2b37858
--- /dev/null
+++ b/components/htmlfive/nsHtml5ArrayCopy.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2008 Mozilla Foundation
+ * Copyright (c) 2019 Moonchild Productions
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef nsHtml5ArrayCopy_h
+#define nsHtml5ArrayCopy_h
+
+
+class nsString;
+class nsHtml5StackNode;
+class nsHtml5AttributeName;
+
+// Unfortunately, these don't work as template functions because the arguments
+// would need coercion from a template class, which complicates things.
+class nsHtml5ArrayCopy {
+ public:
+
+ static inline void
+ arraycopy(char16_t* source, int32_t sourceOffset, char16_t* target, int32_t targetOffset, int32_t length)
+ {
+ memcpy(&(target[targetOffset]), &(source[sourceOffset]), size_t(length) * sizeof(char16_t));
+ }
+
+ static inline void
+ arraycopy(char16_t* source, char16_t* target, int32_t length)
+ {
+ memcpy(target, source, size_t(length) * sizeof(char16_t));
+ }
+
+ static inline void
+ arraycopy(int32_t* source, int32_t* target, int32_t length)
+ {
+ memcpy(target, source, size_t(length) * sizeof(int32_t));
+ }
+
+ static inline void arraycopy(nsHtml5String* source,
+ nsHtml5String* target,
+ int32_t length)
+ {
+ memcpy(target, source, size_t(length) * sizeof(nsHtml5String));
+ }
+
+ static inline void
+ arraycopy(nsHtml5AttributeName** source, nsHtml5AttributeName** target, int32_t length)
+ {
+ memcpy(target, source, size_t(length) * sizeof(nsHtml5AttributeName*));
+ }
+
+ static inline void
+ arraycopy(nsHtml5StackNode** source, nsHtml5StackNode** target, int32_t length)
+ {
+ memcpy(target, source, size_t(length) * sizeof(nsHtml5StackNode*));
+ }
+
+ static inline void
+ arraycopy(nsHtml5StackNode** arr, int32_t sourceOffset, int32_t targetOffset, int32_t length)
+ {
+ memmove(&(arr[targetOffset]), &(arr[sourceOffset]), size_t(length) * sizeof(nsHtml5StackNode*));
+ }
+};
+#endif // nsHtml5ArrayCopy_h
diff --git a/components/htmlfive/nsHtml5Atom.cpp b/components/htmlfive/nsHtml5Atom.cpp
new file mode 100644
index 000000000..4d15c5e41
--- /dev/null
+++ b/components/htmlfive/nsHtml5Atom.cpp
@@ -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/. */
+
+#include "nsHtml5Atom.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Unused.h"
+
+nsHtml5Atom::nsHtml5Atom(const nsAString& aString)
+{
+ mLength = aString.Length();
+ mIsStatic = false;
+ RefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aString);
+ if (buf) {
+ mString = static_cast<char16_t*>(buf->Data());
+ } else {
+ const size_t size = (mLength + 1) * sizeof(char16_t);
+ buf = nsStringBuffer::Alloc(size);
+ if (MOZ_UNLIKELY(!buf)) {
+ // We OOM because atom allocations should be small and it's hard to
+ // handle them more gracefully in a constructor.
+ NS_ABORT_OOM(size);
+ }
+ mString = static_cast<char16_t*>(buf->Data());
+ CopyUnicodeTo(aString, 0, mString, mLength);
+ mString[mLength] = char16_t(0);
+ }
+
+ NS_ASSERTION(mString[mLength] == char16_t(0), "null terminated");
+ NS_ASSERTION(buf && buf->StorageSize() >= (mLength+1) * sizeof(char16_t),
+ "enough storage");
+ NS_ASSERTION(Equals(aString), "correct data");
+
+ // Take ownership of buffer
+ mozilla::Unused << buf.forget();
+}
+
+nsHtml5Atom::~nsHtml5Atom()
+{
+ nsStringBuffer::FromData(mString)->Release();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsHtml5Atom::AddRef()
+{
+ NS_NOTREACHED("Attempt to AddRef an nsHtml5Atom.");
+ return 2;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsHtml5Atom::Release()
+{
+ NS_NOTREACHED("Attempt to Release an nsHtml5Atom.");
+ return 1;
+}
+
+NS_IMETHODIMP
+nsHtml5Atom::QueryInterface(REFNSIID aIID, void** aInstancePtr)
+{
+ NS_NOTREACHED("Attempt to call QueryInterface an nsHtml5Atom.");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsHtml5Atom::ScriptableToString(nsAString& aBuf)
+{
+ NS_NOTREACHED("Should not call ScriptableToString.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHtml5Atom::ToUTF8String(nsACString& aReturn)
+{
+ NS_NOTREACHED("Should not attempt to convert to an UTF-8 string.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHtml5Atom::ScriptableEquals(const nsAString& aString, bool* aResult)
+{
+ NS_NOTREACHED("Should not call ScriptableEquals.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(size_t)
+nsHtml5Atom::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+{
+ NS_NOTREACHED("Should not call SizeOfIncludingThis.");
+ return 0;
+}
+
diff --git a/components/htmlfive/nsHtml5Atom.h b/components/htmlfive/nsHtml5Atom.h
new file mode 100644
index 000000000..c11fb0bad
--- /dev/null
+++ b/components/htmlfive/nsHtml5Atom.h
@@ -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/. */
+
+#ifndef nsHtml5Atom_h
+#define nsHtml5Atom_h
+
+#include "nsIAtom.h"
+#include "mozilla/Attributes.h"
+
+/**
+ * A dynamic atom implementation meant for use within the nsHtml5Tokenizer and
+ * nsHtml5TreeBuilder owned by one nsHtml5Parser or nsHtml5StreamParser
+ * instance.
+ *
+ * Usage is documented in nsHtml5AtomTable and nsIAtom.
+ */
+class nsHtml5Atom final : public nsIAtom
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIATOM
+
+ explicit nsHtml5Atom(const nsAString& aString);
+ ~nsHtml5Atom();
+};
+
+#endif // nsHtml5Atom_h
diff --git a/components/htmlfive/nsHtml5AtomList.h b/components/htmlfive/nsHtml5AtomList.h
new file mode 100644
index 000000000..bd53b5a42
--- /dev/null
+++ b/components/htmlfive/nsHtml5AtomList.h
@@ -0,0 +1,1078 @@
+/*
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+HTML5_ATOM(xmlns, "xmlns")
+HTML5_ATOM(xlink, "xlink")
+HTML5_ATOM(xml, "xml")
+HTML5_ATOM(alt, "alt")
+HTML5_ATOM(dir, "dir")
+HTML5_ATOM(dur, "dur")
+HTML5_ATOM(end, "end")
+HTML5_ATOM(for_, "for")
+HTML5_ATOM(in2, "in2")
+HTML5_ATOM(low, "low")
+HTML5_ATOM(min, "min")
+HTML5_ATOM(max, "max")
+HTML5_ATOM(rel, "rel")
+HTML5_ATOM(rev, "rev")
+HTML5_ATOM(src, "src")
+HTML5_ATOM(d, "d")
+HTML5_ATOM(k, "k")
+HTML5_ATOM(r, "r")
+HTML5_ATOM(x, "x")
+HTML5_ATOM(y, "y")
+HTML5_ATOM(z, "z")
+HTML5_ATOM(cap_height, "cap-height")
+HTML5_ATOM(g1, "g1")
+HTML5_ATOM(k1, "k1")
+HTML5_ATOM(u1, "u1")
+HTML5_ATOM(x1, "x1")
+HTML5_ATOM(y1, "y1")
+HTML5_ATOM(g2, "g2")
+HTML5_ATOM(k2, "k2")
+HTML5_ATOM(u2, "u2")
+HTML5_ATOM(x2, "x2")
+HTML5_ATOM(y2, "y2")
+HTML5_ATOM(k3, "k3")
+HTML5_ATOM(k4, "k4")
+HTML5_ATOM(xml_space, "xml:space")
+HTML5_ATOM(space, "space")
+HTML5_ATOM(xml_lang, "xml:lang")
+HTML5_ATOM(lang, "lang")
+HTML5_ATOM(xml_base, "xml:base")
+HTML5_ATOM(base, "base")
+HTML5_ATOM(aria_grab, "aria-grab")
+HTML5_ATOM(aria_valuemax, "aria-valuemax")
+HTML5_ATOM(aria_labelledby, "aria-labelledby")
+HTML5_ATOM(aria_describedby, "aria-describedby")
+HTML5_ATOM(aria_disabled, "aria-disabled")
+HTML5_ATOM(aria_checked, "aria-checked")
+HTML5_ATOM(aria_selected, "aria-selected")
+HTML5_ATOM(aria_dropeffect, "aria-dropeffect")
+HTML5_ATOM(aria_required, "aria-required")
+HTML5_ATOM(aria_expanded, "aria-expanded")
+HTML5_ATOM(aria_pressed, "aria-pressed")
+HTML5_ATOM(aria_level, "aria-level")
+HTML5_ATOM(aria_channel, "aria-channel")
+HTML5_ATOM(aria_hidden, "aria-hidden")
+HTML5_ATOM(aria_secret, "aria-secret")
+HTML5_ATOM(aria_posinset, "aria-posinset")
+HTML5_ATOM(aria_atomic, "aria-atomic")
+HTML5_ATOM(aria_invalid, "aria-invalid")
+HTML5_ATOM(aria_templateid, "aria-templateid")
+HTML5_ATOM(aria_valuemin, "aria-valuemin")
+HTML5_ATOM(aria_multiselectable, "aria-multiselectable")
+HTML5_ATOM(aria_controls, "aria-controls")
+HTML5_ATOM(aria_multiline, "aria-multiline")
+HTML5_ATOM(aria_readonly, "aria-readonly")
+HTML5_ATOM(aria_owns, "aria-owns")
+HTML5_ATOM(aria_activedescendant, "aria-activedescendant")
+HTML5_ATOM(aria_relevant, "aria-relevant")
+HTML5_ATOM(aria_datatype, "aria-datatype")
+HTML5_ATOM(aria_valuenow, "aria-valuenow")
+HTML5_ATOM(aria_sort, "aria-sort")
+HTML5_ATOM(aria_autocomplete, "aria-autocomplete")
+HTML5_ATOM(aria_flowto, "aria-flowto")
+HTML5_ATOM(aria_busy, "aria-busy")
+HTML5_ATOM(aria_live, "aria-live")
+HTML5_ATOM(aria_haspopup, "aria-haspopup")
+HTML5_ATOM(aria_setsize, "aria-setsize")
+HTML5_ATOM(clear, "clear")
+HTML5_ATOM(dataformatas, "dataformatas")
+HTML5_ATOM(disabled, "disabled")
+HTML5_ATOM(datafld, "datafld")
+HTML5_ATOM(default_, "default")
+HTML5_ATOM(datasrc, "datasrc")
+HTML5_ATOM(data, "data")
+HTML5_ATOM(equalcolumns, "equalcolumns")
+HTML5_ATOM(equalrows, "equalrows")
+HTML5_ATOM(hspace, "hspace")
+HTML5_ATOM(ismap, "ismap")
+HTML5_ATOM(local, "local")
+HTML5_ATOM(lspace, "lspace")
+HTML5_ATOM(movablelimits, "movablelimits")
+HTML5_ATOM(notation, "notation")
+HTML5_ATOM(ondatasetchanged, "ondatasetchanged")
+HTML5_ATOM(ondataavailable, "ondataavailable")
+HTML5_ATOM(onpaste, "onpaste")
+HTML5_ATOM(ondatasetcomplete, "ondatasetcomplete")
+HTML5_ATOM(rspace, "rspace")
+HTML5_ATOM(rowalign, "rowalign")
+HTML5_ATOM(rotate, "rotate")
+HTML5_ATOM(separator, "separator")
+HTML5_ATOM(separators, "separators")
+HTML5_ATOM(v_mathematical, "v-mathematical")
+HTML5_ATOM(vspace, "vspace")
+HTML5_ATOM(v_hanging, "v-hanging")
+HTML5_ATOM(xchannelselector, "xchannelselector")
+HTML5_ATOM(xChannelSelector, "xChannelSelector")
+HTML5_ATOM(ychannelselector, "ychannelselector")
+HTML5_ATOM(yChannelSelector, "yChannelSelector")
+HTML5_ATOM(arabic_form, "arabic-form")
+HTML5_ATOM(enable_background, "enable-background")
+HTML5_ATOM(ondblclick, "ondblclick")
+HTML5_ATOM(onabort, "onabort")
+HTML5_ATOM(calcmode, "calcmode")
+HTML5_ATOM(calcMode, "calcMode")
+HTML5_ATOM(checked, "checked")
+HTML5_ATOM(descent, "descent")
+HTML5_ATOM(fence, "fence")
+HTML5_ATOM(onscroll, "onscroll")
+HTML5_ATOM(onactivate, "onactivate")
+HTML5_ATOM(opacity, "opacity")
+HTML5_ATOM(spacing, "spacing")
+HTML5_ATOM(specularexponent, "specularexponent")
+HTML5_ATOM(specularExponent, "specularExponent")
+HTML5_ATOM(specularconstant, "specularconstant")
+HTML5_ATOM(specularConstant, "specularConstant")
+HTML5_ATOM(specification, "specification")
+HTML5_ATOM(thickmathspace, "thickmathspace")
+HTML5_ATOM(unicode_, "unicode")
+HTML5_ATOM(unicode_bidi, "unicode-bidi")
+HTML5_ATOM(unicode_range, "unicode-range")
+HTML5_ATOM(border, "border")
+HTML5_ATOM(id, "id")
+HTML5_ATOM(gradienttransform, "gradienttransform")
+HTML5_ATOM(gradientTransform, "gradientTransform")
+HTML5_ATOM(gradientunits, "gradientunits")
+HTML5_ATOM(gradientUnits, "gradientUnits")
+HTML5_ATOM(hidden, "hidden")
+HTML5_ATOM(headers, "headers")
+HTML5_ATOM(readonly, "readonly")
+HTML5_ATOM(rendering_intent, "rendering-intent")
+HTML5_ATOM(seed, "seed")
+HTML5_ATOM(srcdoc, "srcdoc")
+HTML5_ATOM(stddeviation, "stddeviation")
+HTML5_ATOM(stdDeviation, "stdDeviation")
+HTML5_ATOM(sandbox, "sandbox")
+HTML5_ATOM(v_ideographic, "v-ideographic")
+HTML5_ATOM(word_spacing, "word-spacing")
+HTML5_ATOM(accentunder, "accentunder")
+HTML5_ATOM(accept_charset, "accept-charset")
+HTML5_ATOM(accesskey, "accesskey")
+HTML5_ATOM(accent_height, "accent-height")
+HTML5_ATOM(accent, "accent")
+HTML5_ATOM(ascent, "ascent")
+HTML5_ATOM(accept, "accept")
+HTML5_ATOM(bevelled, "bevelled")
+HTML5_ATOM(basefrequency, "basefrequency")
+HTML5_ATOM(baseFrequency, "baseFrequency")
+HTML5_ATOM(baseline_shift, "baseline-shift")
+HTML5_ATOM(baseprofile, "baseprofile")
+HTML5_ATOM(baseProfile, "baseProfile")
+HTML5_ATOM(baseline, "baseline")
+HTML5_ATOM(code, "code")
+HTML5_ATOM(codetype, "codetype")
+HTML5_ATOM(codebase, "codebase")
+HTML5_ATOM(cite, "cite")
+HTML5_ATOM(defer, "defer")
+HTML5_ATOM(datetime, "datetime")
+HTML5_ATOM(direction, "direction")
+HTML5_ATOM(edgemode, "edgemode")
+HTML5_ATOM(edgeMode, "edgeMode")
+HTML5_ATOM(edge, "edge")
+HTML5_ATOM(face, "face")
+HTML5_ATOM(hidefocus, "hidefocus")
+HTML5_ATOM(index, "index")
+HTML5_ATOM(irrelevant, "irrelevant")
+HTML5_ATOM(intercept, "intercept")
+HTML5_ATOM(integrity, "integrity")
+HTML5_ATOM(linebreak, "linebreak")
+HTML5_ATOM(label, "label")
+HTML5_ATOM(linethickness, "linethickness")
+HTML5_ATOM(mode, "mode")
+HTML5_ATOM(name, "name")
+HTML5_ATOM(noresize, "noresize")
+HTML5_ATOM(onbeforeunload, "onbeforeunload")
+HTML5_ATOM(onrepeat, "onrepeat")
+HTML5_ATOM(object, "object")
+HTML5_ATOM(onselect, "onselect")
+HTML5_ATOM(order, "order")
+HTML5_ATOM(other, "other")
+HTML5_ATOM(onreset, "onreset")
+HTML5_ATOM(oncellchange, "oncellchange")
+HTML5_ATOM(onreadystatechange, "onreadystatechange")
+HTML5_ATOM(onmessage, "onmessage")
+HTML5_ATOM(onbegin, "onbegin")
+HTML5_ATOM(onhelp, "onhelp")
+HTML5_ATOM(onbeforeprint, "onbeforeprint")
+HTML5_ATOM(orient, "orient")
+HTML5_ATOM(orientation, "orientation")
+HTML5_ATOM(onbeforecopy, "onbeforecopy")
+HTML5_ATOM(onselectstart, "onselectstart")
+HTML5_ATOM(onbeforepaste, "onbeforepaste")
+HTML5_ATOM(onbeforeupdate, "onbeforeupdate")
+HTML5_ATOM(ondeactivate, "ondeactivate")
+HTML5_ATOM(onbeforeactivate, "onbeforeactivate")
+HTML5_ATOM(onbefordeactivate, "onbefordeactivate")
+HTML5_ATOM(onkeypress, "onkeypress")
+HTML5_ATOM(onkeyup, "onkeyup")
+HTML5_ATOM(onbeforeeditfocus, "onbeforeeditfocus")
+HTML5_ATOM(onbeforecut, "onbeforecut")
+HTML5_ATOM(onkeydown, "onkeydown")
+HTML5_ATOM(onresize, "onresize")
+HTML5_ATOM(repeat, "repeat")
+HTML5_ATOM(repeat_max, "repeat-max")
+HTML5_ATOM(referrerpolicy, "referrerpolicy")
+HTML5_ATOM(rules, "rules")
+HTML5_ATOM(repeat_min, "repeat-min")
+HTML5_ATOM(role, "role")
+HTML5_ATOM(repeatcount, "repeatcount")
+HTML5_ATOM(repeatCount, "repeatCount")
+HTML5_ATOM(repeat_start, "repeat-start")
+HTML5_ATOM(repeat_template, "repeat-template")
+HTML5_ATOM(repeatdur, "repeatdur")
+HTML5_ATOM(repeatDur, "repeatDur")
+HTML5_ATOM(selected, "selected")
+HTML5_ATOM(speed, "speed")
+HTML5_ATOM(sizes, "sizes")
+HTML5_ATOM(superscriptshift, "superscriptshift")
+HTML5_ATOM(stretchy, "stretchy")
+HTML5_ATOM(scheme, "scheme")
+HTML5_ATOM(spreadmethod, "spreadmethod")
+HTML5_ATOM(spreadMethod, "spreadMethod")
+HTML5_ATOM(selection, "selection")
+HTML5_ATOM(size, "size")
+HTML5_ATOM(type, "type")
+HTML5_ATOM(unselectable, "unselectable")
+HTML5_ATOM(underline_position, "underline-position")
+HTML5_ATOM(underline_thickness, "underline-thickness")
+HTML5_ATOM(x_height, "x-height")
+HTML5_ATOM(diffuseconstant, "diffuseconstant")
+HTML5_ATOM(diffuseConstant, "diffuseConstant")
+HTML5_ATOM(href, "href")
+HTML5_ATOM(hreflang, "hreflang")
+HTML5_ATOM(onafterprint, "onafterprint")
+HTML5_ATOM(onafterupdate, "onafterupdate")
+HTML5_ATOM(profile, "profile")
+HTML5_ATOM(surfacescale, "surfacescale")
+HTML5_ATOM(surfaceScale, "surfaceScale")
+HTML5_ATOM(xref, "xref")
+HTML5_ATOM(align, "align")
+HTML5_ATOM(alignment_baseline, "alignment-baseline")
+HTML5_ATOM(alignmentscope, "alignmentscope")
+HTML5_ATOM(draggable, "draggable")
+HTML5_ATOM(height, "height")
+HTML5_ATOM(hanging, "hanging")
+HTML5_ATOM(image_rendering, "image-rendering")
+HTML5_ATOM(language, "language")
+HTML5_ATOM(largeop, "largeop")
+HTML5_ATOM(longdesc, "longdesc")
+HTML5_ATOM(lengthadjust, "lengthadjust")
+HTML5_ATOM(lengthAdjust, "lengthAdjust")
+HTML5_ATOM(marginheight, "marginheight")
+HTML5_ATOM(marginwidth, "marginwidth")
+HTML5_ATOM(nargs, "nargs")
+HTML5_ATOM(origin, "origin")
+HTML5_ATOM(ping, "ping")
+HTML5_ATOM(target, "target")
+HTML5_ATOM(targetx, "targetx")
+HTML5_ATOM(targetX, "targetX")
+HTML5_ATOM(targety, "targety")
+HTML5_ATOM(targetY, "targetY")
+HTML5_ATOM(alphabetic, "alphabetic")
+HTML5_ATOM(archive, "archive")
+HTML5_ATOM(high, "high")
+HTML5_ATOM(lighting_color, "lighting-color")
+HTML5_ATOM(mathematical, "mathematical")
+HTML5_ATOM(mathbackground, "mathbackground")
+HTML5_ATOM(method, "method")
+HTML5_ATOM(mathvariant, "mathvariant")
+HTML5_ATOM(mathcolor, "mathcolor")
+HTML5_ATOM(mathsize, "mathsize")
+HTML5_ATOM(noshade, "noshade")
+HTML5_ATOM(onchange, "onchange")
+HTML5_ATOM(pathlength, "pathlength")
+HTML5_ATOM(pathLength, "pathLength")
+HTML5_ATOM(path, "path")
+HTML5_ATOM(altimg, "altimg")
+HTML5_ATOM(actiontype, "actiontype")
+HTML5_ATOM(action, "action")
+HTML5_ATOM(active, "active")
+HTML5_ATOM(additive, "additive")
+HTML5_ATOM(begin, "begin")
+HTML5_ATOM(dominant_baseline, "dominant-baseline")
+HTML5_ATOM(divisor, "divisor")
+HTML5_ATOM(definitionurl, "definitionurl")
+HTML5_ATOM(definitionURL, "definitionURL")
+HTML5_ATOM(horiz_adv_x, "horiz-adv-x")
+HTML5_ATOM(horiz_origin_x, "horiz-origin-x")
+HTML5_ATOM(horiz_origin_y, "horiz-origin-y")
+HTML5_ATOM(limitingconeangle, "limitingconeangle")
+HTML5_ATOM(limitingConeAngle, "limitingConeAngle")
+HTML5_ATOM(mediummathspace, "mediummathspace")
+HTML5_ATOM(media, "media")
+HTML5_ATOM(manifest, "manifest")
+HTML5_ATOM(onfilterchange, "onfilterchange")
+HTML5_ATOM(onfinish, "onfinish")
+HTML5_ATOM(optimum, "optimum")
+HTML5_ATOM(radiogroup, "radiogroup")
+HTML5_ATOM(radius, "radius")
+HTML5_ATOM(scriptlevel, "scriptlevel")
+HTML5_ATOM(scriptsizemultiplier, "scriptsizemultiplier")
+HTML5_ATOM(string, "string")
+HTML5_ATOM(strikethrough_position, "strikethrough-position")
+HTML5_ATOM(strikethrough_thickness, "strikethrough-thickness")
+HTML5_ATOM(scriptminsize, "scriptminsize")
+HTML5_ATOM(tabindex, "tabindex")
+HTML5_ATOM(valign, "valign")
+HTML5_ATOM(visibility, "visibility")
+HTML5_ATOM(background, "background")
+HTML5_ATOM(link, "link")
+HTML5_ATOM(marker_mid, "marker-mid")
+HTML5_ATOM(markerheight, "markerheight")
+HTML5_ATOM(markerHeight, "markerHeight")
+HTML5_ATOM(marker_end, "marker-end")
+HTML5_ATOM(mask, "mask")
+HTML5_ATOM(marker_start, "marker-start")
+HTML5_ATOM(markerwidth, "markerwidth")
+HTML5_ATOM(markerWidth, "markerWidth")
+HTML5_ATOM(maskunits, "maskunits")
+HTML5_ATOM(maskUnits, "maskUnits")
+HTML5_ATOM(markerunits, "markerunits")
+HTML5_ATOM(markerUnits, "markerUnits")
+HTML5_ATOM(maskcontentunits, "maskcontentunits")
+HTML5_ATOM(maskContentUnits, "maskContentUnits")
+HTML5_ATOM(amplitude, "amplitude")
+HTML5_ATOM(cellspacing, "cellspacing")
+HTML5_ATOM(cellpadding, "cellpadding")
+HTML5_ATOM(declare, "declare")
+HTML5_ATOM(fill_rule, "fill-rule")
+HTML5_ATOM(fill, "fill")
+HTML5_ATOM(fill_opacity, "fill-opacity")
+HTML5_ATOM(maxlength, "maxlength")
+HTML5_ATOM(onclick, "onclick")
+HTML5_ATOM(onblur, "onblur")
+HTML5_ATOM(replace, "replace")
+HTML5_ATOM(rowlines, "rowlines")
+HTML5_ATOM(scale, "scale")
+HTML5_ATOM(style, "style")
+HTML5_ATOM(tablevalues, "tablevalues")
+HTML5_ATOM(tableValues, "tableValues")
+HTML5_ATOM(title, "title")
+HTML5_ATOM(v_alphabetic, "v-alphabetic")
+HTML5_ATOM(azimuth, "azimuth")
+HTML5_ATOM(format, "format")
+HTML5_ATOM(frameborder, "frameborder")
+HTML5_ATOM(frame, "frame")
+HTML5_ATOM(framespacing, "framespacing")
+HTML5_ATOM(from, "from")
+HTML5_ATOM(form, "form")
+HTML5_ATOM(prompt, "prompt")
+HTML5_ATOM(primitiveunits, "primitiveunits")
+HTML5_ATOM(primitiveUnits, "primitiveUnits")
+HTML5_ATOM(symmetric, "symmetric")
+HTML5_ATOM(stemh, "stemh")
+HTML5_ATOM(stemv, "stemv")
+HTML5_ATOM(seamless, "seamless")
+HTML5_ATOM(summary, "summary")
+HTML5_ATOM(usemap, "usemap")
+HTML5_ATOM(zoomandpan, "zoomandpan")
+HTML5_ATOM(zoomAndPan, "zoomAndPan")
+HTML5_ATOM(async, "async")
+HTML5_ATOM(alink, "alink")
+HTML5_ATOM(in, "in")
+HTML5_ATOM(icon, "icon")
+HTML5_ATOM(kernelmatrix, "kernelmatrix")
+HTML5_ATOM(kernelMatrix, "kernelMatrix")
+HTML5_ATOM(kerning, "kerning")
+HTML5_ATOM(kernelunitlength, "kernelunitlength")
+HTML5_ATOM(kernelUnitLength, "kernelUnitLength")
+HTML5_ATOM(onunload, "onunload")
+HTML5_ATOM(open, "open")
+HTML5_ATOM(oninvalid, "oninvalid")
+HTML5_ATOM(onend, "onend")
+HTML5_ATOM(oninput, "oninput")
+HTML5_ATOM(pointer_events, "pointer-events")
+HTML5_ATOM(points, "points")
+HTML5_ATOM(pointsatx, "pointsatx")
+HTML5_ATOM(pointsAtX, "pointsAtX")
+HTML5_ATOM(pointsaty, "pointsaty")
+HTML5_ATOM(pointsAtY, "pointsAtY")
+HTML5_ATOM(pointsatz, "pointsatz")
+HTML5_ATOM(pointsAtZ, "pointsAtZ")
+HTML5_ATOM(span, "span")
+HTML5_ATOM(standby, "standby")
+HTML5_ATOM(thinmathspace, "thinmathspace")
+HTML5_ATOM(transform, "transform")
+HTML5_ATOM(vlink, "vlink")
+HTML5_ATOM(when, "when")
+HTML5_ATOM(xlink_href, "xlink:href")
+HTML5_ATOM(xlink_title, "xlink:title")
+HTML5_ATOM(xlink_role, "xlink:role")
+HTML5_ATOM(xlink_arcrole, "xlink:arcrole")
+HTML5_ATOM(arcrole, "arcrole")
+HTML5_ATOM(xmlns_xlink, "xmlns:xlink")
+HTML5_ATOM(xlink_type, "xlink:type")
+HTML5_ATOM(xlink_show, "xlink:show")
+HTML5_ATOM(show, "show")
+HTML5_ATOM(xlink_actuate, "xlink:actuate")
+HTML5_ATOM(actuate, "actuate")
+HTML5_ATOM(autoplay, "autoplay")
+HTML5_ATOM(autosubmit, "autosubmit")
+HTML5_ATOM(autocomplete, "autocomplete")
+HTML5_ATOM(autofocus, "autofocus")
+HTML5_ATOM(bgcolor, "bgcolor")
+HTML5_ATOM(color_profile, "color-profile")
+HTML5_ATOM(color_rendering, "color-rendering")
+HTML5_ATOM(color_interpolation, "color-interpolation")
+HTML5_ATOM(color, "color")
+HTML5_ATOM(color_interpolation_filters, "color-interpolation-filters")
+HTML5_ATOM(encoding, "encoding")
+HTML5_ATOM(exponent, "exponent")
+HTML5_ATOM(flood_color, "flood-color")
+HTML5_ATOM(flood_opacity, "flood-opacity")
+HTML5_ATOM(ideographic, "ideographic")
+HTML5_ATOM(lquote, "lquote")
+HTML5_ATOM(panose_1, "panose-1")
+HTML5_ATOM(numoctaves, "numoctaves")
+HTML5_ATOM(numOctaves, "numOctaves")
+HTML5_ATOM(nomodule, "nomodule")
+HTML5_ATOM(onload, "onload")
+HTML5_ATOM(onbounce, "onbounce")
+HTML5_ATOM(oncontrolselect, "oncontrolselect")
+HTML5_ATOM(onrowsinserted, "onrowsinserted")
+HTML5_ATOM(onmousewheel, "onmousewheel")
+HTML5_ATOM(onrowenter, "onrowenter")
+HTML5_ATOM(onmouseenter, "onmouseenter")
+HTML5_ATOM(onmouseover, "onmouseover")
+HTML5_ATOM(onformchange, "onformchange")
+HTML5_ATOM(onfocusin, "onfocusin")
+HTML5_ATOM(onrowexit, "onrowexit")
+HTML5_ATOM(onmoveend, "onmoveend")
+HTML5_ATOM(oncontextmenu, "oncontextmenu")
+HTML5_ATOM(onzoom, "onzoom")
+HTML5_ATOM(onlosecapture, "onlosecapture")
+HTML5_ATOM(oncopy, "oncopy")
+HTML5_ATOM(onmovestart, "onmovestart")
+HTML5_ATOM(onrowsdelete, "onrowsdelete")
+HTML5_ATOM(onmouseleave, "onmouseleave")
+HTML5_ATOM(onmove, "onmove")
+HTML5_ATOM(onmousemove, "onmousemove")
+HTML5_ATOM(onmouseup, "onmouseup")
+HTML5_ATOM(onfocus, "onfocus")
+HTML5_ATOM(onmouseout, "onmouseout")
+HTML5_ATOM(onforminput, "onforminput")
+HTML5_ATOM(onfocusout, "onfocusout")
+HTML5_ATOM(onmousedown, "onmousedown")
+HTML5_ATOM(to, "to")
+HTML5_ATOM(rquote, "rquote")
+HTML5_ATOM(stroke_linecap, "stroke-linecap")
+HTML5_ATOM(scrolldelay, "scrolldelay")
+HTML5_ATOM(stroke_dasharray, "stroke-dasharray")
+HTML5_ATOM(stroke_dashoffset, "stroke-dashoffset")
+HTML5_ATOM(stroke_linejoin, "stroke-linejoin")
+HTML5_ATOM(stroke_miterlimit, "stroke-miterlimit")
+HTML5_ATOM(stroke, "stroke")
+HTML5_ATOM(scrolling, "scrolling")
+HTML5_ATOM(stroke_width, "stroke-width")
+HTML5_ATOM(stroke_opacity, "stroke-opacity")
+HTML5_ATOM(compact, "compact")
+HTML5_ATOM(clip, "clip")
+HTML5_ATOM(clip_rule, "clip-rule")
+HTML5_ATOM(clip_path, "clip-path")
+HTML5_ATOM(clippathunits, "clippathunits")
+HTML5_ATOM(clipPathUnits, "clipPathUnits")
+HTML5_ATOM(display, "display")
+HTML5_ATOM(displaystyle, "displaystyle")
+HTML5_ATOM(glyph_orientation_vertical, "glyph-orientation-vertical")
+HTML5_ATOM(glyph_orientation_horizontal, "glyph-orientation-horizontal")
+HTML5_ATOM(glyphref, "glyphref")
+HTML5_ATOM(glyphRef, "glyphRef")
+HTML5_ATOM(glyph_name, "glyph-name")
+HTML5_ATOM(http_equiv, "http-equiv")
+HTML5_ATOM(keypoints, "keypoints")
+HTML5_ATOM(keyPoints, "keyPoints")
+HTML5_ATOM(loop, "loop")
+HTML5_ATOM(property, "property")
+HTML5_ATOM(scoped, "scoped")
+HTML5_ATOM(step, "step")
+HTML5_ATOM(shape_rendering, "shape-rendering")
+HTML5_ATOM(scope, "scope")
+HTML5_ATOM(shape, "shape")
+HTML5_ATOM(slope, "slope")
+HTML5_ATOM(stop_color, "stop-color")
+HTML5_ATOM(stop_opacity, "stop-opacity")
+HTML5_ATOM(template_, "template")
+HTML5_ATOM(wrap, "wrap")
+HTML5_ATOM(abbr, "abbr")
+HTML5_ATOM(attributename, "attributename")
+HTML5_ATOM(attributeName, "attributeName")
+HTML5_ATOM(attributetype, "attributetype")
+HTML5_ATOM(attributeType, "attributeType")
+HTML5_ATOM(char_, "char")
+HTML5_ATOM(coords, "coords")
+HTML5_ATOM(charoff, "charoff")
+HTML5_ATOM(charset, "charset")
+HTML5_ATOM(macros, "macros")
+HTML5_ATOM(nowrap, "nowrap")
+HTML5_ATOM(nohref, "nohref")
+HTML5_ATOM(ondrag, "ondrag")
+HTML5_ATOM(ondragenter, "ondragenter")
+HTML5_ATOM(ondragover, "ondragover")
+HTML5_ATOM(onpropertychange, "onpropertychange")
+HTML5_ATOM(ondragend, "ondragend")
+HTML5_ATOM(ondrop, "ondrop")
+HTML5_ATOM(ondragdrop, "ondragdrop")
+HTML5_ATOM(overline_position, "overline-position")
+HTML5_ATOM(onerror, "onerror")
+HTML5_ATOM(operator_, "operator")
+HTML5_ATOM(overflow, "overflow")
+HTML5_ATOM(ondragstart, "ondragstart")
+HTML5_ATOM(onerrorupdate, "onerrorupdate")
+HTML5_ATOM(overline_thickness, "overline-thickness")
+HTML5_ATOM(ondragleave, "ondragleave")
+HTML5_ATOM(startoffset, "startoffset")
+HTML5_ATOM(startOffset, "startOffset")
+HTML5_ATOM(start, "start")
+HTML5_ATOM(axis, "axis")
+HTML5_ATOM(bias, "bias")
+HTML5_ATOM(colspan, "colspan")
+HTML5_ATOM(classid, "classid")
+HTML5_ATOM(crossorigin, "crossorigin")
+HTML5_ATOM(cols, "cols")
+HTML5_ATOM(cursor, "cursor")
+HTML5_ATOM(closure, "closure")
+HTML5_ATOM(close, "close")
+HTML5_ATOM(class_, "class")
+HTML5_ATOM(is, "is")
+HTML5_ATOM(keysystem, "keysystem")
+HTML5_ATOM(keysplines, "keysplines")
+HTML5_ATOM(keySplines, "keySplines")
+HTML5_ATOM(lowsrc, "lowsrc")
+HTML5_ATOM(maxsize, "maxsize")
+HTML5_ATOM(minsize, "minsize")
+HTML5_ATOM(offset, "offset")
+HTML5_ATOM(preservealpha, "preservealpha")
+HTML5_ATOM(preserveAlpha, "preserveAlpha")
+HTML5_ATOM(preserveaspectratio, "preserveaspectratio")
+HTML5_ATOM(preserveAspectRatio, "preserveAspectRatio")
+HTML5_ATOM(rowspan, "rowspan")
+HTML5_ATOM(rowspacing, "rowspacing")
+HTML5_ATOM(rows, "rows")
+HTML5_ATOM(srcset, "srcset")
+HTML5_ATOM(subscriptshift, "subscriptshift")
+HTML5_ATOM(version, "version")
+HTML5_ATOM(alttext, "alttext")
+HTML5_ATOM(contenteditable, "contenteditable")
+HTML5_ATOM(controls, "controls")
+HTML5_ATOM(content, "content")
+HTML5_ATOM(contextmenu, "contextmenu")
+HTML5_ATOM(depth, "depth")
+HTML5_ATOM(enctype, "enctype")
+HTML5_ATOM(font_stretch, "font-stretch")
+HTML5_ATOM(filter, "filter")
+HTML5_ATOM(fontweight, "fontweight")
+HTML5_ATOM(font_weight, "font-weight")
+HTML5_ATOM(fontstyle, "fontstyle")
+HTML5_ATOM(font_style, "font-style")
+HTML5_ATOM(fontfamily, "fontfamily")
+HTML5_ATOM(font_family, "font-family")
+HTML5_ATOM(font_variant, "font-variant")
+HTML5_ATOM(font_size_adjust, "font-size-adjust")
+HTML5_ATOM(filterunits, "filterunits")
+HTML5_ATOM(filterUnits, "filterUnits")
+HTML5_ATOM(fontsize, "fontsize")
+HTML5_ATOM(font_size, "font-size")
+HTML5_ATOM(keytimes, "keytimes")
+HTML5_ATOM(keyTimes, "keyTimes")
+HTML5_ATOM(letter_spacing, "letter-spacing")
+HTML5_ATOM(list, "list")
+HTML5_ATOM(multiple, "multiple")
+HTML5_ATOM(rt, "rt")
+HTML5_ATOM(onstop, "onstop")
+HTML5_ATOM(onstart, "onstart")
+HTML5_ATOM(poster, "poster")
+HTML5_ATOM(patterntransform, "patterntransform")
+HTML5_ATOM(patternTransform, "patternTransform")
+HTML5_ATOM(pattern, "pattern")
+HTML5_ATOM(patternunits, "patternunits")
+HTML5_ATOM(patternUnits, "patternUnits")
+HTML5_ATOM(patterncontentunits, "patterncontentunits")
+HTML5_ATOM(patternContentUnits, "patternContentUnits")
+HTML5_ATOM(restart, "restart")
+HTML5_ATOM(stitchtiles, "stitchtiles")
+HTML5_ATOM(stitchTiles, "stitchTiles")
+HTML5_ATOM(systemlanguage, "systemlanguage")
+HTML5_ATOM(systemLanguage, "systemLanguage")
+HTML5_ATOM(text_rendering, "text-rendering")
+HTML5_ATOM(vert_origin_x, "vert-origin-x")
+HTML5_ATOM(vert_adv_y, "vert-adv-y")
+HTML5_ATOM(vert_origin_y, "vert-origin-y")
+HTML5_ATOM(text_decoration, "text-decoration")
+HTML5_ATOM(text_anchor, "text-anchor")
+HTML5_ATOM(textlength, "textlength")
+HTML5_ATOM(textLength, "textLength")
+HTML5_ATOM(text, "text")
+HTML5_ATOM(units_per_em, "units-per-em")
+HTML5_ATOM(writing_mode, "writing-mode")
+HTML5_ATOM(widths, "widths")
+HTML5_ATOM(width, "width")
+HTML5_ATOM(accumulate, "accumulate")
+HTML5_ATOM(columnspan, "columnspan")
+HTML5_ATOM(columnlines, "columnlines")
+HTML5_ATOM(columnalign, "columnalign")
+HTML5_ATOM(columnspacing, "columnspacing")
+HTML5_ATOM(columnwidth, "columnwidth")
+HTML5_ATOM(groupalign, "groupalign")
+HTML5_ATOM(inputmode, "inputmode")
+HTML5_ATOM(occurrence, "occurrence")
+HTML5_ATOM(onsubmit, "onsubmit")
+HTML5_ATOM(oncut, "oncut")
+HTML5_ATOM(required, "required")
+HTML5_ATOM(requiredfeatures, "requiredfeatures")
+HTML5_ATOM(requiredFeatures, "requiredFeatures")
+HTML5_ATOM(result, "result")
+HTML5_ATOM(requiredextensions, "requiredextensions")
+HTML5_ATOM(requiredExtensions, "requiredExtensions")
+HTML5_ATOM(values, "values")
+HTML5_ATOM(valuetype, "valuetype")
+HTML5_ATOM(value, "value")
+HTML5_ATOM(elevation, "elevation")
+HTML5_ATOM(viewtarget, "viewtarget")
+HTML5_ATOM(viewTarget, "viewTarget")
+HTML5_ATOM(viewbox, "viewbox")
+HTML5_ATOM(viewBox, "viewBox")
+HTML5_ATOM(cx, "cx")
+HTML5_ATOM(dx, "dx")
+HTML5_ATOM(fx, "fx")
+HTML5_ATOM(bbox, "bbox")
+HTML5_ATOM(rx, "rx")
+HTML5_ATOM(refx, "refx")
+HTML5_ATOM(refX, "refX")
+HTML5_ATOM(by, "by")
+HTML5_ATOM(cy, "cy")
+HTML5_ATOM(dy, "dy")
+HTML5_ATOM(fy, "fy")
+HTML5_ATOM(ry, "ry")
+HTML5_ATOM(refy, "refy")
+HTML5_ATOM(refY, "refY")
+HTML5_ATOM(verythinmathspace, "verythinmathspace")
+HTML5_ATOM(verythickmathspace, "verythickmathspace")
+HTML5_ATOM(veryverythinmathspace, "veryverythinmathspace")
+HTML5_ATOM(veryverythickmathspace, "veryverythickmathspace")
+HTML5_ATOM(isindex, "isindex")
+HTML5_ATOM(annotation_xml, "annotation-xml")
+HTML5_ATOM(and_, "and")
+HTML5_ATOM(arg, "arg")
+HTML5_ATOM(abs, "abs")
+HTML5_ATOM(big, "big")
+HTML5_ATOM(bdo, "bdo")
+HTML5_ATOM(csc, "csc")
+HTML5_ATOM(col, "col")
+HTML5_ATOM(cos, "cos")
+HTML5_ATOM(cot, "cot")
+HTML5_ATOM(del, "del")
+HTML5_ATOM(dfn, "dfn")
+HTML5_ATOM(div, "div")
+HTML5_ATOM(exp, "exp")
+HTML5_ATOM(gcd, "gcd")
+HTML5_ATOM(geq, "geq")
+HTML5_ATOM(img, "img")
+HTML5_ATOM(ins, "ins")
+HTML5_ATOM(int_, "int")
+HTML5_ATOM(kbd, "kbd")
+HTML5_ATOM(log, "log")
+HTML5_ATOM(lcm, "lcm")
+HTML5_ATOM(leq, "leq")
+HTML5_ATOM(mtd, "mtd")
+HTML5_ATOM(map, "map")
+HTML5_ATOM(mtr, "mtr")
+HTML5_ATOM(neq, "neq")
+HTML5_ATOM(not_, "not")
+HTML5_ATOM(nav, "nav")
+HTML5_ATOM(pre, "pre")
+HTML5_ATOM(a, "a")
+HTML5_ATOM(b, "b")
+HTML5_ATOM(rtc, "rtc")
+HTML5_ATOM(rem, "rem")
+HTML5_ATOM(sub, "sub")
+HTML5_ATOM(sec, "sec")
+HTML5_ATOM(svg, "svg")
+HTML5_ATOM(sum, "sum")
+HTML5_ATOM(sin, "sin")
+HTML5_ATOM(sep, "sep")
+HTML5_ATOM(sup, "sup")
+HTML5_ATOM(set, "set")
+HTML5_ATOM(tan, "tan")
+HTML5_ATOM(use, "use")
+HTML5_ATOM(var, "var")
+HTML5_ATOM(g, "g")
+HTML5_ATOM(wbr, "wbr")
+HTML5_ATOM(xmp, "xmp")
+HTML5_ATOM(xor_, "xor")
+HTML5_ATOM(i, "i")
+HTML5_ATOM(p, "p")
+HTML5_ATOM(q, "q")
+HTML5_ATOM(s, "s")
+HTML5_ATOM(u, "u")
+HTML5_ATOM(h1, "h1")
+HTML5_ATOM(h2, "h2")
+HTML5_ATOM(h3, "h3")
+HTML5_ATOM(h4, "h4")
+HTML5_ATOM(h5, "h5")
+HTML5_ATOM(h6, "h6")
+HTML5_ATOM(area, "area")
+HTML5_ATOM(eulergamma, "eulergamma")
+HTML5_ATOM(fefunca, "fefunca")
+HTML5_ATOM(feFuncA, "feFuncA")
+HTML5_ATOM(lambda, "lambda")
+HTML5_ATOM(metadata, "metadata")
+HTML5_ATOM(meta, "meta")
+HTML5_ATOM(textarea, "textarea")
+HTML5_ATOM(fefuncb, "fefuncb")
+HTML5_ATOM(feFuncB, "feFuncB")
+HTML5_ATOM(msub, "msub")
+HTML5_ATOM(rb, "rb")
+HTML5_ATOM(arcsec, "arcsec")
+HTML5_ATOM(arccsc, "arccsc")
+HTML5_ATOM(definition_src, "definition-src")
+HTML5_ATOM(desc, "desc")
+HTML5_ATOM(font_face_src, "font-face-src")
+HTML5_ATOM(mfrac, "mfrac")
+HTML5_ATOM(dd, "dd")
+HTML5_ATOM(bgsound, "bgsound")
+HTML5_ATOM(card, "card")
+HTML5_ATOM(discard, "discard")
+HTML5_ATOM(embed, "embed")
+HTML5_ATOM(feblend, "feblend")
+HTML5_ATOM(feBlend, "feBlend")
+HTML5_ATOM(feflood, "feflood")
+HTML5_ATOM(feFlood, "feFlood")
+HTML5_ATOM(grad, "grad")
+HTML5_ATOM(head, "head")
+HTML5_ATOM(legend, "legend")
+HTML5_ATOM(mfenced, "mfenced")
+HTML5_ATOM(mpadded, "mpadded")
+HTML5_ATOM(noembed, "noembed")
+HTML5_ATOM(td, "td")
+HTML5_ATOM(thead, "thead")
+HTML5_ATOM(aside, "aside")
+HTML5_ATOM(article, "article")
+HTML5_ATOM(animate, "animate")
+HTML5_ATOM(blockquote, "blockquote")
+HTML5_ATOM(circle, "circle")
+HTML5_ATOM(compose, "compose")
+HTML5_ATOM(conjugate, "conjugate")
+HTML5_ATOM(divergence, "divergence")
+HTML5_ATOM(divide, "divide")
+HTML5_ATOM(degree, "degree")
+HTML5_ATOM(datatemplate, "datatemplate")
+HTML5_ATOM(exponentiale, "exponentiale")
+HTML5_ATOM(ellipse, "ellipse")
+HTML5_ATOM(font_face, "font-face")
+HTML5_ATOM(feturbulence, "feturbulence")
+HTML5_ATOM(feTurbulence, "feTurbulence")
+HTML5_ATOM(femergenode, "femergenode")
+HTML5_ATOM(feMergeNode, "feMergeNode")
+HTML5_ATOM(feimage, "feimage")
+HTML5_ATOM(feImage, "feImage")
+HTML5_ATOM(femerge, "femerge")
+HTML5_ATOM(feMerge, "feMerge")
+HTML5_ATOM(fetile, "fetile")
+HTML5_ATOM(feTile, "feTile")
+HTML5_ATOM(font_face_name, "font-face-name")
+HTML5_ATOM(figure, "figure")
+HTML5_ATOM(false_, "false")
+HTML5_ATOM(fecomposite, "fecomposite")
+HTML5_ATOM(feComposite, "feComposite")
+HTML5_ATOM(image, "image")
+HTML5_ATOM(iframe, "iframe")
+HTML5_ATOM(inverse, "inverse")
+HTML5_ATOM(line, "line")
+HTML5_ATOM(logbase, "logbase")
+HTML5_ATOM(mspace, "mspace")
+HTML5_ATOM(mtable, "mtable")
+HTML5_ATOM(mstyle, "mstyle")
+HTML5_ATOM(menclose, "menclose")
+HTML5_ATOM(none, "none")
+HTML5_ATOM(otherwise, "otherwise")
+HTML5_ATOM(piece, "piece")
+HTML5_ATOM(polyline, "polyline")
+HTML5_ATOM(picture, "picture")
+HTML5_ATOM(piecewise, "piecewise")
+HTML5_ATOM(rule, "rule")
+HTML5_ATOM(source, "source")
+HTML5_ATOM(strike, "strike")
+HTML5_ATOM(table, "table")
+HTML5_ATOM(time, "time")
+HTML5_ATOM(transpose, "transpose")
+HTML5_ATOM(true_, "true")
+HTML5_ATOM(variance, "variance")
+HTML5_ATOM(altglyphdef, "altglyphdef")
+HTML5_ATOM(altGlyphDef, "altGlyphDef")
+HTML5_ATOM(diff, "diff")
+HTML5_ATOM(factorof, "factorof")
+HTML5_ATOM(partialdiff, "partialdiff")
+HTML5_ATOM(setdiff, "setdiff")
+HTML5_ATOM(tref, "tref")
+HTML5_ATOM(ceiling, "ceiling")
+HTML5_ATOM(dialog, "dialog")
+HTML5_ATOM(fefuncg, "fefuncg")
+HTML5_ATOM(feFuncG, "feFuncG")
+HTML5_ATOM(fediffuselighting, "fediffuselighting")
+HTML5_ATOM(feDiffuseLighting, "feDiffuseLighting")
+HTML5_ATOM(fespecularlighting, "fespecularlighting")
+HTML5_ATOM(feSpecularLighting, "feSpecularLighting")
+HTML5_ATOM(listing, "listing")
+HTML5_ATOM(strong, "strong")
+HTML5_ATOM(arcsech, "arcsech")
+HTML5_ATOM(arccsch, "arccsch")
+HTML5_ATOM(arctanh, "arctanh")
+HTML5_ATOM(arcsinh, "arcsinh")
+HTML5_ATOM(altglyph, "altglyph")
+HTML5_ATOM(altGlyph, "altGlyph")
+HTML5_ATOM(arccosh, "arccosh")
+HTML5_ATOM(arccoth, "arccoth")
+HTML5_ATOM(csch, "csch")
+HTML5_ATOM(cosh, "cosh")
+HTML5_ATOM(clippath, "clippath")
+HTML5_ATOM(clipPath, "clipPath")
+HTML5_ATOM(coth, "coth")
+HTML5_ATOM(glyph, "glyph")
+HTML5_ATOM(mglyph, "mglyph")
+HTML5_ATOM(missing_glyph, "missing-glyph")
+HTML5_ATOM(math, "math")
+HTML5_ATOM(mpath, "mpath")
+HTML5_ATOM(prefetch, "prefetch")
+HTML5_ATOM(th, "th")
+HTML5_ATOM(sech, "sech")
+HTML5_ATOM(switch_, "switch")
+HTML5_ATOM(sinh, "sinh")
+HTML5_ATOM(tanh, "tanh")
+HTML5_ATOM(textpath, "textpath")
+HTML5_ATOM(textPath, "textPath")
+HTML5_ATOM(ci, "ci")
+HTML5_ATOM(font_face_uri, "font-face-uri")
+HTML5_ATOM(li, "li")
+HTML5_ATOM(imaginaryi, "imaginaryi")
+HTML5_ATOM(mi, "mi")
+HTML5_ATOM(pi, "pi")
+HTML5_ATOM(mark, "mark")
+HTML5_ATOM(malignmark, "malignmark")
+HTML5_ATOM(tbreak, "tbreak")
+HTML5_ATOM(track, "track")
+HTML5_ATOM(dl, "dl")
+HTML5_ATOM(csymbol, "csymbol")
+HTML5_ATOM(curl, "curl")
+HTML5_ATOM(factorial, "factorial")
+HTML5_ATOM(forall, "forall")
+HTML5_ATOM(html, "html")
+HTML5_ATOM(interval, "interval")
+HTML5_ATOM(ol, "ol")
+HTML5_ATOM(ul, "ul")
+HTML5_ATOM(real, "real")
+HTML5_ATOM(small_, "small")
+HTML5_ATOM(symbol, "symbol")
+HTML5_ATOM(altglyphitem, "altglyphitem")
+HTML5_ATOM(altGlyphItem, "altGlyphItem")
+HTML5_ATOM(animatetransform, "animatetransform")
+HTML5_ATOM(animateTransform, "animateTransform")
+HTML5_ATOM(acronym, "acronym")
+HTML5_ATOM(em, "em")
+HTML5_ATOM(menuitem, "menuitem")
+HTML5_ATOM(mphantom, "mphantom")
+HTML5_ATOM(param, "param")
+HTML5_ATOM(cn, "cn")
+HTML5_ATOM(arctan, "arctan")
+HTML5_ATOM(arcsin, "arcsin")
+HTML5_ATOM(animation, "animation")
+HTML5_ATOM(annotation, "annotation")
+HTML5_ATOM(animatemotion, "animatemotion")
+HTML5_ATOM(animateMotion, "animateMotion")
+HTML5_ATOM(button, "button")
+HTML5_ATOM(fn, "fn")
+HTML5_ATOM(codomain, "codomain")
+HTML5_ATOM(caption, "caption")
+HTML5_ATOM(condition, "condition")
+HTML5_ATOM(domain, "domain")
+HTML5_ATOM(domainofapplication, "domainofapplication")
+HTML5_ATOM(figcaption, "figcaption")
+HTML5_ATOM(hkern, "hkern")
+HTML5_ATOM(ln, "ln")
+HTML5_ATOM(mn, "mn")
+HTML5_ATOM(keygen, "keygen")
+HTML5_ATOM(laplacian, "laplacian")
+HTML5_ATOM(mean, "mean")
+HTML5_ATOM(median, "median")
+HTML5_ATOM(main, "main")
+HTML5_ATOM(maction, "maction")
+HTML5_ATOM(notin, "notin")
+HTML5_ATOM(option, "option")
+HTML5_ATOM(polygon, "polygon")
+HTML5_ATOM(reln, "reln")
+HTML5_ATOM(section, "section")
+HTML5_ATOM(tspan, "tspan")
+HTML5_ATOM(union_, "union")
+HTML5_ATOM(vkern, "vkern")
+HTML5_ATOM(audio, "audio")
+HTML5_ATOM(mo, "mo")
+HTML5_ATOM(tendsto, "tendsto")
+HTML5_ATOM(video, "video")
+HTML5_ATOM(colgroup, "colgroup")
+HTML5_ATOM(fedisplacementmap, "fedisplacementmap")
+HTML5_ATOM(feDisplacementMap, "feDisplacementMap")
+HTML5_ATOM(hgroup, "hgroup")
+HTML5_ATOM(maligngroup, "maligngroup")
+HTML5_ATOM(msubsup, "msubsup")
+HTML5_ATOM(msup, "msup")
+HTML5_ATOM(rp, "rp")
+HTML5_ATOM(optgroup, "optgroup")
+HTML5_ATOM(samp, "samp")
+HTML5_ATOM(stop, "stop")
+HTML5_ATOM(eq, "eq")
+HTML5_ATOM(br, "br")
+HTML5_ATOM(animatecolor, "animatecolor")
+HTML5_ATOM(animateColor, "animateColor")
+HTML5_ATOM(bvar, "bvar")
+HTML5_ATOM(center, "center")
+HTML5_ATOM(hr, "hr")
+HTML5_ATOM(fefuncr, "fefuncr")
+HTML5_ATOM(feFuncR, "feFuncR")
+HTML5_ATOM(fecomponenttransfer, "fecomponenttransfer")
+HTML5_ATOM(feComponentTransfer, "feComponentTransfer")
+HTML5_ATOM(footer, "footer")
+HTML5_ATOM(floor, "floor")
+HTML5_ATOM(fegaussianblur, "fegaussianblur")
+HTML5_ATOM(feGaussianBlur, "feGaussianBlur")
+HTML5_ATOM(header, "header")
+HTML5_ATOM(handler, "handler")
+HTML5_ATOM(or_, "or")
+HTML5_ATOM(listener, "listener")
+HTML5_ATOM(munder, "munder")
+HTML5_ATOM(marker, "marker")
+HTML5_ATOM(meter, "meter")
+HTML5_ATOM(mover, "mover")
+HTML5_ATOM(munderover, "munderover")
+HTML5_ATOM(merror, "merror")
+HTML5_ATOM(mlabeledtr, "mlabeledtr")
+HTML5_ATOM(nobr, "nobr")
+HTML5_ATOM(notanumber, "notanumber")
+HTML5_ATOM(power, "power")
+HTML5_ATOM(tr, "tr")
+HTML5_ATOM(solidcolor, "solidcolor")
+HTML5_ATOM(selector, "selector")
+HTML5_ATOM(vector, "vector")
+HTML5_ATOM(arccos, "arccos")
+HTML5_ATOM(address, "address")
+HTML5_ATOM(canvas, "canvas")
+HTML5_ATOM(complexes, "complexes")
+HTML5_ATOM(defs, "defs")
+HTML5_ATOM(details, "details")
+HTML5_ATOM(exists, "exists")
+HTML5_ATOM(implies, "implies")
+HTML5_ATOM(integers, "integers")
+HTML5_ATOM(ms, "ms")
+HTML5_ATOM(mprescripts, "mprescripts")
+HTML5_ATOM(mmultiscripts, "mmultiscripts")
+HTML5_ATOM(minus, "minus")
+HTML5_ATOM(noframes, "noframes")
+HTML5_ATOM(naturalnumbers, "naturalnumbers")
+HTML5_ATOM(primes, "primes")
+HTML5_ATOM(progress, "progress")
+HTML5_ATOM(plus, "plus")
+HTML5_ATOM(reals, "reals")
+HTML5_ATOM(rationals, "rationals")
+HTML5_ATOM(semantics, "semantics")
+HTML5_ATOM(times, "times")
+HTML5_ATOM(dt, "dt")
+HTML5_ATOM(applet, "applet")
+HTML5_ATOM(arccot, "arccot")
+HTML5_ATOM(basefont, "basefont")
+HTML5_ATOM(cartesianproduct, "cartesianproduct")
+HTML5_ATOM(gt, "gt")
+HTML5_ATOM(determinant, "determinant")
+HTML5_ATOM(datalist, "datalist")
+HTML5_ATOM(emptyset, "emptyset")
+HTML5_ATOM(equivalent, "equivalent")
+HTML5_ATOM(font_face_format, "font-face-format")
+HTML5_ATOM(foreignobject, "foreignobject")
+HTML5_ATOM(foreignObject, "foreignObject")
+HTML5_ATOM(fieldset, "fieldset")
+HTML5_ATOM(frameset, "frameset")
+HTML5_ATOM(feoffset, "feoffset")
+HTML5_ATOM(feOffset, "feOffset")
+HTML5_ATOM(fespotlight, "fespotlight")
+HTML5_ATOM(feSpotLight, "feSpotLight")
+HTML5_ATOM(fepointlight, "fepointlight")
+HTML5_ATOM(fePointLight, "fePointLight")
+HTML5_ATOM(fedistantlight, "fedistantlight")
+HTML5_ATOM(feDistantLight, "feDistantLight")
+HTML5_ATOM(font, "font")
+HTML5_ATOM(lt, "lt")
+HTML5_ATOM(intersect, "intersect")
+HTML5_ATOM(ident, "ident")
+HTML5_ATOM(input, "input")
+HTML5_ATOM(limit, "limit")
+HTML5_ATOM(lowlimit, "lowlimit")
+HTML5_ATOM(lineargradient, "lineargradient")
+HTML5_ATOM(linearGradient, "linearGradient")
+HTML5_ATOM(moment, "moment")
+HTML5_ATOM(mroot, "mroot")
+HTML5_ATOM(msqrt, "msqrt")
+HTML5_ATOM(momentabout, "momentabout")
+HTML5_ATOM(mtext, "mtext")
+HTML5_ATOM(notsubset, "notsubset")
+HTML5_ATOM(notprsubset, "notprsubset")
+HTML5_ATOM(noscript, "noscript")
+HTML5_ATOM(nest, "nest")
+HTML5_ATOM(outerproduct, "outerproduct")
+HTML5_ATOM(output, "output")
+HTML5_ATOM(product, "product")
+HTML5_ATOM(prsubset, "prsubset")
+HTML5_ATOM(plaintext, "plaintext")
+HTML5_ATOM(tt, "tt")
+HTML5_ATOM(quotient, "quotient")
+HTML5_ATOM(rect, "rect")
+HTML5_ATOM(radialgradient, "radialgradient")
+HTML5_ATOM(radialGradient, "radialGradient")
+HTML5_ATOM(root, "root")
+HTML5_ATOM(select, "select")
+HTML5_ATOM(scalarproduct, "scalarproduct")
+HTML5_ATOM(subset, "subset")
+HTML5_ATOM(slot, "slot")
+HTML5_ATOM(script, "script")
+HTML5_ATOM(tfoot, "tfoot")
+HTML5_ATOM(uplimit, "uplimit")
+HTML5_ATOM(vectorproduct, "vectorproduct")
+HTML5_ATOM(menu, "menu")
+HTML5_ATOM(sdev, "sdev")
+HTML5_ATOM(fedropshadow, "fedropshadow")
+HTML5_ATOM(feDropShadow, "feDropShadow")
+HTML5_ATOM(mrow, "mrow")
+HTML5_ATOM(matrixrow, "matrixrow")
+HTML5_ATOM(view, "view")
+HTML5_ATOM(approx, "approx")
+HTML5_ATOM(fecolormatrix, "fecolormatrix")
+HTML5_ATOM(feColorMatrix, "feColorMatrix")
+HTML5_ATOM(feconvolvematrix, "feconvolvematrix")
+HTML5_ATOM(feConvolveMatrix, "feConvolveMatrix")
+HTML5_ATOM(matrix, "matrix")
+HTML5_ATOM(apply, "apply")
+HTML5_ATOM(body, "body")
+HTML5_ATOM(femorphology, "femorphology")
+HTML5_ATOM(feMorphology, "feMorphology")
+HTML5_ATOM(imaginary, "imaginary")
+HTML5_ATOM(infinity, "infinity")
+HTML5_ATOM(ruby, "ruby")
+HTML5_ATOM(tbody, "tbody")
+HTML5_ATOM(emptystring, "")
diff --git a/components/htmlfive/nsHtml5AtomTable.cpp b/components/htmlfive/nsHtml5AtomTable.cpp
new file mode 100644
index 000000000..ae35791f3
--- /dev/null
+++ b/components/htmlfive/nsHtml5AtomTable.cpp
@@ -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/. */
+
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5Atom.h"
+#include "nsThreadUtils.h"
+
+nsHtml5AtomEntry::nsHtml5AtomEntry(KeyTypePointer aStr)
+ : nsStringHashKey(aStr)
+ , mAtom(new nsHtml5Atom(*aStr))
+{
+}
+
+nsHtml5AtomEntry::nsHtml5AtomEntry(const nsHtml5AtomEntry& aOther)
+ : nsStringHashKey(aOther)
+ , mAtom(nullptr)
+{
+ NS_NOTREACHED("nsHtml5AtomTable is broken and tried to copy an entry");
+}
+
+nsHtml5AtomEntry::~nsHtml5AtomEntry()
+{
+}
+
+nsHtml5AtomTable::nsHtml5AtomTable()
+ : mRecentlyUsedParserAtoms{}
+{
+#ifdef DEBUG
+ NS_GetMainThread(getter_AddRefs(mPermittedLookupThread));
+#endif
+}
+
+nsHtml5AtomTable::~nsHtml5AtomTable()
+{
+}
+
+nsIAtom*
+nsHtml5AtomTable::GetAtom(const nsAString& aKey)
+{
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIThread> currentThread;
+ NS_GetCurrentThread(getter_AddRefs(currentThread));
+ NS_ASSERTION(mPermittedLookupThread == currentThread, "Wrong thread!");
+ }
+#endif
+
+ uint32_t index = mozilla::HashString(aKey) % RECENTLY_USED_PARSER_ATOMS_SIZE;
+ nsIAtom* cachedAtom = mRecentlyUsedParserAtoms[index];
+ if (cachedAtom && cachedAtom->Equals(aKey)) {
+ return cachedAtom;
+ }
+
+ nsIAtom* atom = NS_GetStaticAtom(aKey);
+ if (atom) {
+ mRecentlyUsedParserAtoms[index] = atom;
+ return atom;
+ }
+ nsHtml5AtomEntry* entry = mTable.PutEntry(aKey);
+ if (!entry) {
+ return nullptr;
+ }
+
+ mRecentlyUsedParserAtoms[index] = entry->GetAtom();
+ return entry->GetAtom();
+}
diff --git a/components/htmlfive/nsHtml5AtomTable.h b/components/htmlfive/nsHtml5AtomTable.h
new file mode 100644
index 000000000..b0dc2f678
--- /dev/null
+++ b/components/htmlfive/nsHtml5AtomTable.h
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsHtml5AtomTable_h
+#define nsHtml5AtomTable_h
+
+#include "nsHashKeys.h"
+#include "nsTHashtable.h"
+#include "nsAutoPtr.h"
+#include "nsIAtom.h"
+#include "nsIThread.h"
+
+#define RECENTLY_USED_PARSER_ATOMS_SIZE 31
+
+class nsHtml5Atom;
+
+class nsHtml5AtomEntry : public nsStringHashKey
+{
+ public:
+ explicit nsHtml5AtomEntry(KeyTypePointer aStr);
+ nsHtml5AtomEntry(const nsHtml5AtomEntry& aOther);
+ ~nsHtml5AtomEntry();
+ inline nsHtml5Atom* GetAtom()
+ {
+ return mAtom;
+ }
+ private:
+ nsAutoPtr<nsHtml5Atom> mAtom;
+};
+
+/**
+ * nsHtml5AtomTable provides non-locking lookup and creation of atoms for
+ * nsHtml5Parser or nsHtml5StreamParser.
+ *
+ * The hashtable holds dynamically allocated atoms that are private to an
+ * instance of nsHtml5Parser or nsHtml5StreamParser. (Static atoms are used on
+ * interned nsHtml5ElementNames and interned nsHtml5AttributeNames. Also, when
+ * the doctype name is 'html', that identifier needs to be represented as a
+ * static atom.)
+ *
+ * Each instance of nsHtml5Parser has a single instance of nsHtml5AtomTable,
+ * and each instance of nsHtml5StreamParser has a single instance of
+ * nsHtml5AtomTable. Dynamic atoms obtained from an nsHtml5AtomTable are valid
+ * for == comparison with each other or with atoms declared in nsHtml5Atoms
+ * within the nsHtml5Tokenizer and the nsHtml5TreeBuilder instances owned by
+ * the same nsHtml5Parser/nsHtml5StreamParser instance that owns the
+ * nsHtml5AtomTable instance.
+ *
+ * Dynamic atoms (atoms whose IsStaticAtom() returns false) obtained from
+ * nsHtml5AtomTable must be re-obtained from another atom table when there's a
+ * need to migrate atoms from an nsHtml5Parser to its nsHtml5StreamParser
+ * (re-obtain from the other nsHtml5AtomTable), from an nsHtml5Parser to its
+ * owner nsHtml5Parser (re-obtain from the other nsHtml5AtomTable) or from the
+ * parser to the DOM (re-obtain from the application-wide atom table). To
+ * re-obtain an atom from another atom table, obtain a string from the atom
+ * using ToString(nsAString&) and look up an atom in the other table using that
+ * string.
+ *
+ * An instance of nsHtml5AtomTable that belongs to an nsHtml5Parser is only
+ * accessed from the main thread. An instance of nsHtml5AtomTable that belongs
+ * to an nsHtml5StreamParser is accessed both from the main thread and from the
+ * thread that executes the runnables of the nsHtml5StreamParser instance.
+ * However, the threads never access the nsHtml5AtomTable instance concurrently
+ * in the nsHtml5StreamParser case.
+ *
+ * Methods on the atoms obtained from nsHtml5AtomTable may be called on any
+ * thread, although they only need to be called on the main thread or on the
+ * thread working for the nsHtml5StreamParser when nsHtml5AtomTable belongs to
+ * an nsHtml5StreamParser.
+ *
+ * Dynamic atoms obtained from nsHtml5AtomTable are deleted when the
+ * nsHtml5AtomTable itself is destructed, which happens when the owner
+ * nsHtml5Parser or nsHtml5StreamParser is destructed.
+ */
+class nsHtml5AtomTable
+{
+ public:
+ nsHtml5AtomTable();
+ ~nsHtml5AtomTable();
+
+ /**
+ * Obtains the atom for the given string in the scope of this atom table.
+ */
+ nsIAtom* GetAtom(const nsAString& aKey);
+
+ /**
+ * Empties the table.
+ */
+ void Clear()
+ {
+ for (uint32_t i = 0; i < RECENTLY_USED_PARSER_ATOMS_SIZE; ++i) {
+ mRecentlyUsedParserAtoms[i] = nullptr;
+ }
+ mTable.Clear();
+ }
+
+#ifdef DEBUG
+ void SetPermittedLookupThread(nsIThread* aThread)
+ {
+ mPermittedLookupThread = aThread;
+ }
+#endif
+
+ private:
+ nsTHashtable<nsHtml5AtomEntry> mTable;
+ nsIAtom* mRecentlyUsedParserAtoms[RECENTLY_USED_PARSER_ATOMS_SIZE];
+#ifdef DEBUG
+ nsCOMPtr<nsIThread> mPermittedLookupThread;
+#endif
+};
+
+#endif // nsHtml5AtomTable_h
diff --git a/components/htmlfive/nsHtml5Atoms.cpp b/components/htmlfive/nsHtml5Atoms.cpp
new file mode 100644
index 000000000..ae8136179
--- /dev/null
+++ b/components/htmlfive/nsHtml5Atoms.cpp
@@ -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/. */
+
+/*
+ * This class wraps up the creation (and destruction) of the standard
+ * set of atoms used by the HTML5 parser; the atoms are created when
+ * nsHtml5Module is loaded and they are destroyed when nsHtml5Module is
+ * unloaded.
+ */
+
+#include "nsHtml5Atoms.h"
+#include "nsStaticAtom.h"
+
+using namespace mozilla;
+
+// define storage for all atoms
+#define HTML5_ATOM(_name, _value) nsIAtom* nsHtml5Atoms::_name;
+#include "nsHtml5AtomList.h"
+#undef HTML5_ATOM
+
+#define HTML5_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_)
+#include "nsHtml5AtomList.h"
+#undef HTML5_ATOM
+
+static const nsStaticAtom Html5Atoms_info[] = {
+#define HTML5_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &nsHtml5Atoms::name_),
+#include "nsHtml5AtomList.h"
+#undef HTML5_ATOM
+};
+
+void nsHtml5Atoms::AddRefAtoms()
+{
+ NS_RegisterStaticAtoms(Html5Atoms_info);
+}
diff --git a/components/htmlfive/nsHtml5Atoms.h b/components/htmlfive/nsHtml5Atoms.h
new file mode 100644
index 000000000..069aa8445
--- /dev/null
+++ b/components/htmlfive/nsHtml5Atoms.h
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+/*
+ * This class wraps up the creation (and destruction) of the standard
+ * set of atoms used by gklayout; the atoms are created when gklayout
+ * is loaded and they are destroyed when gklayout is unloaded.
+ */
+
+#ifndef nsHtml5Atoms_h
+#define nsHtml5Atoms_h
+
+#include "nsIAtom.h"
+
+class nsHtml5Atoms {
+ public:
+ static void AddRefAtoms();
+ /* Declare all atoms
+ The atom names and values are stored in nsGkAtomList.h and
+ are brought to you by the magic of C preprocessing
+ Add new atoms to nsGkAtomList and all support logic will be auto-generated
+ */
+#define HTML5_ATOM(_name, _value) static nsIAtom* _name;
+#include "nsHtml5AtomList.h"
+#undef HTML5_ATOM
+};
+
+#endif /* nsHtml5Atoms_h */
diff --git a/components/htmlfive/nsHtml5AttributeEntry.h b/components/htmlfive/nsHtml5AttributeEntry.h
new file mode 100644
index 000000000..75dd10842
--- /dev/null
+++ b/components/htmlfive/nsHtml5AttributeEntry.h
@@ -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/. */
+
+#ifndef nsHtml5AttributeEntry_h
+#define nsHtml5AttributeEntry_h
+
+#include "nsHtml5AttributeName.h"
+
+class nsHtml5AttributeEntry final
+{
+public:
+ nsHtml5AttributeEntry(nsHtml5AttributeName* aName,
+ nsHtml5String aValue,
+ int32_t aLine)
+ : mLine(aLine)
+ , mValue(aValue)
+ {
+ // Let's hope the compiler coalesces the following as appropriate.
+ mLocals[0] = aName->getLocal(0);
+ mLocals[1] = aName->getLocal(1);
+ mLocals[2] = aName->getLocal(2);
+ mPrefixes[0] = aName->getPrefix(0);
+ mPrefixes[1] = aName->getPrefix(1);
+ mPrefixes[2] = aName->getPrefix(2);
+ mUris[0] = aName->getUri(0);
+ mUris[1] = aName->getUri(1);
+ mUris[2] = aName->getUri(2);
+ }
+
+ // Isindex-only so doesn't need to deal with SVG and MathML
+ nsHtml5AttributeEntry(nsIAtom* aName, nsHtml5String aValue, int32_t aLine)
+ : mLine(aLine)
+ , mValue(aValue)
+ {
+ // Let's hope the compiler coalesces the following as appropriate.
+ mLocals[0] = aName;
+ mLocals[1] = aName;
+ mLocals[2] = aName;
+ mPrefixes[0] = nullptr;
+ mPrefixes[1] = nullptr;
+ mPrefixes[2] = nullptr;
+ mUris[0] = kNameSpaceID_None;
+ mUris[1] = kNameSpaceID_None;
+ mUris[2] = kNameSpaceID_None;
+ }
+
+ inline nsIAtom* GetLocal(int32_t aMode) { return mLocals[aMode]; }
+
+ inline nsIAtom* GetPrefix(int32_t aMode) { return mPrefixes[aMode]; }
+
+ inline int32_t GetUri(int32_t aMode) { return mUris[aMode]; }
+
+ inline nsHtml5String GetValue() { return mValue; }
+
+ inline int32_t GetLine() { return mLine; }
+
+ inline void ReleaseValue() { mValue.Release(); }
+
+ inline nsHtml5AttributeEntry Clone(nsHtml5AtomTable* aInterner)
+ {
+ // Copy the memory
+ nsHtml5AttributeEntry clone(*this);
+ // Increment refcount for value
+ clone.mValue = this->mValue.Clone();
+ if (aInterner) {
+ // Now if we have an interner, we'll need to rewrite non-static atoms.
+ // Only the local names may be non-static, in which case all three
+ // are the same.
+ nsIAtom* local = GetLocal(0);
+ if (!local->IsStaticAtom()) {
+ nsAutoString str;
+ local->ToString(str);
+ local = aInterner->GetAtom(str);
+ clone.mLocals[0] = local;
+ clone.mLocals[1] = local;
+ clone.mLocals[2] = local;
+ }
+ }
+ return clone;
+ }
+
+private:
+ nsIAtom* mLocals[3];
+ nsIAtom* mPrefixes[3];
+ int32_t mUris[3];
+ int32_t mLine;
+ nsHtml5String mValue;
+};
+
+#endif // nsHtml5AttributeEntry_h
diff --git a/components/htmlfive/nsHtml5AttributeName.cpp b/components/htmlfive/nsHtml5AttributeName.cpp
new file mode 100644
index 000000000..6bb9a8a16
--- /dev/null
+++ b/components/htmlfive/nsHtml5AttributeName.cpp
@@ -0,0 +1,2560 @@
+/*
+ * Copyright (c) 2008-2017 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit AttributeName.java instead and regenerate.
+ */
+
+#define nsHtml5AttributeName_cpp__
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+#include "nsHtml5ElementName.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5MetaScanner.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5UTF16Buffer.h"
+#include "nsHtml5StateSnapshot.h"
+#include "nsHtml5Portability.h"
+
+#include "nsHtml5AttributeName.h"
+
+int32_t* nsHtml5AttributeName::ALL_NO_NS = 0;
+int32_t* nsHtml5AttributeName::XMLNS_NS = 0;
+int32_t* nsHtml5AttributeName::XML_NS = 0;
+int32_t* nsHtml5AttributeName::XLINK_NS = 0;
+nsIAtom** nsHtml5AttributeName::ALL_NO_PREFIX = 0;
+nsIAtom** nsHtml5AttributeName::XMLNS_PREFIX = 0;
+nsIAtom** nsHtml5AttributeName::XLINK_PREFIX = 0;
+nsIAtom** nsHtml5AttributeName::XML_PREFIX = 0;
+nsIAtom**
+nsHtml5AttributeName::SVG_DIFFERENT(nsIAtom* name, nsIAtom* camel)
+{
+ nsIAtom** arr = new nsIAtom*[4];
+ arr[0] = name;
+ arr[1] = name;
+ arr[2] = camel;
+ return arr;
+}
+
+nsIAtom**
+nsHtml5AttributeName::MATH_DIFFERENT(nsIAtom* name, nsIAtom* camel)
+{
+ nsIAtom** arr = new nsIAtom*[4];
+ arr[0] = name;
+ arr[1] = camel;
+ arr[2] = name;
+ return arr;
+}
+
+nsIAtom**
+nsHtml5AttributeName::COLONIFIED_LOCAL(nsIAtom* name, nsIAtom* suffix)
+{
+ nsIAtom** arr = new nsIAtom*[4];
+ arr[0] = name;
+ arr[1] = suffix;
+ arr[2] = suffix;
+ return arr;
+}
+
+nsIAtom**
+nsHtml5AttributeName::SAME_LOCAL(nsIAtom* name)
+{
+ nsIAtom** arr = new nsIAtom*[4];
+ arr[0] = name;
+ arr[1] = name;
+ arr[2] = name;
+ return arr;
+}
+
+
+nsHtml5AttributeName::nsHtml5AttributeName(int32_t* uri, nsIAtom** local, nsIAtom** prefix)
+ : uri(uri),
+ local(local),
+ prefix(prefix),
+ custom(false)
+{
+ MOZ_COUNT_CTOR(nsHtml5AttributeName);
+}
+
+
+nsHtml5AttributeName::nsHtml5AttributeName()
+ : uri(nsHtml5AttributeName::ALL_NO_NS),
+ local(nsHtml5AttributeName::SAME_LOCAL(nullptr)),
+ prefix(ALL_NO_PREFIX),
+ custom(true)
+{
+ MOZ_COUNT_CTOR(nsHtml5AttributeName);
+}
+
+nsHtml5AttributeName*
+nsHtml5AttributeName::createAttributeName(nsIAtom* name)
+{
+ return new nsHtml5AttributeName(nsHtml5AttributeName::ALL_NO_NS, nsHtml5AttributeName::SAME_LOCAL(name), ALL_NO_PREFIX);
+}
+
+
+nsHtml5AttributeName::~nsHtml5AttributeName()
+{
+ MOZ_COUNT_DTOR(nsHtml5AttributeName);
+ delete[] local;
+}
+
+int32_t
+nsHtml5AttributeName::getUri(int32_t mode)
+{
+ return uri[mode];
+}
+
+nsIAtom*
+nsHtml5AttributeName::getLocal(int32_t mode)
+{
+ return local[mode];
+}
+
+nsIAtom*
+nsHtml5AttributeName::getPrefix(int32_t mode)
+{
+ return prefix[mode];
+}
+
+bool
+nsHtml5AttributeName::equalsAnother(nsHtml5AttributeName* another)
+{
+ return this->getLocal(nsHtml5AttributeName::HTML) == another->getLocal(nsHtml5AttributeName::HTML);
+}
+
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ALT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DIR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DUR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_END = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_IN2 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LOW = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MIN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MAX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REV = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SRC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_D = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_K = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_R = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_X = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_Y = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_Z = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CAP_HEIGHT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_G1 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_K1 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_U1 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_X1 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_Y1 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_G2 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_K2 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_U2 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_X2 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_Y2 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_K3 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_K4 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XML_SPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XML_LANG = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XML_BASE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_GRAB = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_VALUEMAX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_LABELLEDBY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_DESCRIBEDBY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_DISABLED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_CHECKED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_SELECTED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_DROPEFFECT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_REQUIRED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_EXPANDED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_PRESSED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_LEVEL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_CHANNEL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_HIDDEN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_SECRET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_POSINSET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_ATOMIC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_INVALID = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_TEMPLATEID = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_VALUEMIN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_MULTISELECTABLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_CONTROLS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_MULTILINE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_READONLY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_OWNS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_ACTIVEDESCENDANT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_RELEVANT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_DATATYPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_VALUENOW = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_SORT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_AUTOCOMPLETE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_FLOWTO = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_BUSY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_LIVE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_HASPOPUP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARIA_SETSIZE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CLEAR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DATAFORMATAS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DISABLED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DATAFLD = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DEFAULT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DATASRC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DATA = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_EQUALCOLUMNS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_EQUALROWS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HSPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ISMAP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LOCAL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LSPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MOVABLELIMITS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_NOTATION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDATASETCHANGED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDATAAVAILABLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONPASTE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDATASETCOMPLETE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RSPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ROWALIGN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ROTATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SEPARATOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SEPARATORS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_V_MATHEMATICAL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VSPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_V_HANGING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XCHANNELSELECTOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_YCHANNELSELECTOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARABIC_FORM = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ENABLE_BACKGROUND = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDBLCLICK = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONABORT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CALCMODE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CHECKED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DESCENT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FENCE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONSCROLL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONACTIVATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OPACITY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SPACING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SPECULAREXPONENT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SPECULARCONSTANT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SPECIFICATION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_THICKMATHSPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_UNICODE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_UNICODE_BIDI = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_UNICODE_RANGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BORDER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ID = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_GRADIENTTRANSFORM = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_GRADIENTUNITS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HIDDEN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HEADERS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_READONLY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RENDERING_INTENT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SEED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SRCDOC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STDDEVIATION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SANDBOX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_V_IDEOGRAPHIC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_WORD_SPACING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ACCENTUNDER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ACCEPT_CHARSET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ACCESSKEY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ACCENT_HEIGHT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ACCENT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ASCENT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ACCEPT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BEVELLED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BASEFREQUENCY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BASELINE_SHIFT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BASEPROFILE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BASELINE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BASE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CODE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CODETYPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CODEBASE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CITE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DEFER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DATETIME = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DIRECTION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_EDGEMODE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_EDGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HIDEFOCUS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_INDEX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_IRRELEVANT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_INTERCEPT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_INTEGRITY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LINEBREAK = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LABEL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LINETHICKNESS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MODE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_NAME = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_NORESIZE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBEFOREUNLOAD = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONREPEAT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OBJECT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONSELECT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ORDER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OTHER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONRESET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONCELLCHANGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONREADYSTATECHANGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMESSAGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBEGIN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONHELP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBEFOREPRINT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ORIENT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ORIENTATION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBEFORECOPY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONSELECTSTART = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBEFOREPASTE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBEFOREUPDATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDEACTIVATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBEFOREACTIVATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBEFORDEACTIVATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONKEYPRESS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONKEYUP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBEFOREEDITFOCUS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBEFORECUT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONKEYDOWN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONRESIZE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REPEAT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REPEAT_MAX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REFERRERPOLICY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RULES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REPEAT_MIN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ROLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REPEATCOUNT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REPEAT_START = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REPEAT_TEMPLATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REPEATDUR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SELECTED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SPEED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SIZES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SUPERSCRIPTSHIFT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STRETCHY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SCHEME = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SPREADMETHOD = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SELECTION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SIZE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TYPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_UNSELECTABLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_UNDERLINE_POSITION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_UNDERLINE_THICKNESS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_X_HEIGHT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DIFFUSECONSTANT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HREF = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HREFLANG = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONAFTERPRINT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONAFTERUPDATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PROFILE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SURFACESCALE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XREF = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ALIGN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ALIGNMENT_BASELINE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ALIGNMENTSCOPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DRAGGABLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HEIGHT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HANGING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_IMAGE_RENDERING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LANGUAGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LANG = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LARGEOP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LONGDESC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LENGTHADJUST = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MARGINHEIGHT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MARGINWIDTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_NARGS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ORIGIN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TARGET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TARGETX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TARGETY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ALPHABETIC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ARCHIVE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HIGH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LIGHTING_COLOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MATHEMATICAL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MATHBACKGROUND = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_METHOD = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MATHVARIANT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MATHCOLOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MATHSIZE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_NOSHADE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONCHANGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PATHLENGTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PATH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ALTIMG = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ACTIONTYPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ACTION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ACTIVE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ADDITIVE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BEGIN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DOMINANT_BASELINE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DIVISOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DEFINITIONURL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HORIZ_ADV_X = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HORIZ_ORIGIN_X = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HORIZ_ORIGIN_Y = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LIMITINGCONEANGLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MEDIUMMATHSPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MEDIA = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MANIFEST = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONFILTERCHANGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONFINISH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OPTIMUM = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RADIOGROUP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RADIUS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SCRIPTLEVEL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SCRIPTSIZEMULTIPLIER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STRING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STRIKETHROUGH_POSITION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STRIKETHROUGH_THICKNESS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SCRIPTMINSIZE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TABINDEX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VALIGN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VISIBILITY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BACKGROUND = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LINK = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MARKER_MID = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MARKERHEIGHT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MARKER_END = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MASK = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MARKER_START = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MARKERWIDTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MASKUNITS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MARKERUNITS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MASKCONTENTUNITS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_AMPLITUDE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CELLSPACING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CELLPADDING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DECLARE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FILL_RULE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FILL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FILL_OPACITY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MAXLENGTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONCLICK = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBLUR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REPLACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ROWLINES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SCALE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STYLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TABLEVALUES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TITLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_V_ALPHABETIC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_AZIMUTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FORMAT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FRAMEBORDER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FRAME = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FRAMESPACING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FROM = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FORM = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PROMPT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PRIMITIVEUNITS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SYMMETRIC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STEMH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STEMV = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SEAMLESS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SUMMARY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_USEMAP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ZOOMANDPAN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ASYNC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ALINK = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_IN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ICON = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_KERNELMATRIX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_KERNING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_KERNELUNITLENGTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONUNLOAD = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OPEN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONINVALID = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONEND = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONINPUT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_POINTER_EVENTS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_POINTS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_POINTSATX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_POINTSATY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_POINTSATZ = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SPAN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STANDBY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_THINMATHSPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TRANSFORM = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VLINK = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_WHEN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XLINK_HREF = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XLINK_TITLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XLINK_ROLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XLINK_ARCROLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XMLNS_XLINK = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XMLNS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XLINK_TYPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XLINK_SHOW = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_XLINK_ACTUATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_AUTOPLAY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_AUTOSUBMIT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_AUTOCOMPLETE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_AUTOFOCUS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BGCOLOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLOR_PROFILE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLOR_RENDERING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLOR_INTERPOLATION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLOR_INTERPOLATION_FILTERS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ENCODING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_EXPONENT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FLOOD_COLOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FLOOD_OPACITY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_IDEOGRAPHIC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LQUOTE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PANOSE_1 = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_NUMOCTAVES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_NOMODULE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONLOAD = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONBOUNCE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONCONTROLSELECT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONROWSINSERTED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOUSEWHEEL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONROWENTER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOUSEENTER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOUSEOVER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONFORMCHANGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONFOCUSIN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONROWEXIT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOVEEND = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONCONTEXTMENU = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONZOOM = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONLOSECAPTURE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONCOPY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOVESTART = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONROWSDELETE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOUSELEAVE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOVE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOUSEMOVE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOUSEUP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONFOCUS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOUSEOUT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONFORMINPUT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONFOCUSOUT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONMOUSEDOWN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TO = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RQUOTE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STROKE_LINECAP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SCROLLDELAY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STROKE_DASHARRAY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STROKE_DASHOFFSET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STROKE_LINEJOIN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STROKE_MITERLIMIT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STROKE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SCROLLING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STROKE_WIDTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STROKE_OPACITY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COMPACT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CLIP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CLIP_RULE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CLIP_PATH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CLIPPATHUNITS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DISPLAY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DISPLAYSTYLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_GLYPH_ORIENTATION_VERTICAL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_GLYPH_ORIENTATION_HORIZONTAL = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_GLYPHREF = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_GLYPH_NAME = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_HTTP_EQUIV = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_KEYPOINTS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LOOP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PROPERTY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SCOPED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STEP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SHAPE_RENDERING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SCOPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SHAPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SLOPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STOP_COLOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STOP_OPACITY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TEMPLATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_WRAP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ABBR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ATTRIBUTENAME = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ATTRIBUTETYPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CHAR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COORDS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CHAROFF = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CHARSET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MACROS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_NOWRAP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_NOHREF = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDRAG = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDRAGENTER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDRAGOVER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONPROPERTYCHANGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDRAGEND = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDROP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDRAGDROP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OVERLINE_POSITION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONERROR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OPERATOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OVERFLOW = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDRAGSTART = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONERRORUPDATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OVERLINE_THICKNESS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONDRAGLEAVE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STARTOFFSET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_START = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_AXIS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BIAS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLSPAN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CLASSID = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CROSSORIGIN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CURSOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CLOSURE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CLOSE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CLASS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_IS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_KEYSYSTEM = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_KEYSPLINES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LOWSRC = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MAXSIZE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MINSIZE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OFFSET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PRESERVEALPHA = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PRESERVEASPECTRATIO = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ROWSPAN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ROWSPACING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ROWS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SRCSET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SUBSCRIPTSHIFT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VERSION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ALTTEXT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CONTENTEDITABLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CONTROLS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CONTENT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CONTEXTMENU = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DEPTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ENCTYPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONT_STRETCH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FILTER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONTWEIGHT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONT_WEIGHT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONTSTYLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONT_STYLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONTFAMILY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONT_FAMILY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONT_VARIANT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONT_SIZE_ADJUST = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FILTERUNITS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONTSIZE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FONT_SIZE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_KEYTIMES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LETTER_SPACING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_LIST = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_MULTIPLE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONSTOP = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONSTART = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_POSTER = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PATTERNTRANSFORM = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PATTERN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PATTERNUNITS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_PATTERNCONTENTUNITS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RESTART = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_STITCHTILES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_SYSTEMLANGUAGE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TEXT_RENDERING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VERT_ORIGIN_X = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VERT_ADV_Y = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VERT_ORIGIN_Y = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TEXT_DECORATION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TEXT_ANCHOR = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TEXTLENGTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_TEXT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_UNITS_PER_EM = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_WRITING_MODE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_WIDTHS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_WIDTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ACCUMULATE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLUMNSPAN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLUMNLINES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLUMNALIGN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLUMNSPACING = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_COLUMNWIDTH = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_GROUPALIGN = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_INPUTMODE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_OCCURRENCE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONSUBMIT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ONCUT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REQUIRED = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REQUIREDFEATURES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RESULT = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REQUIREDEXTENSIONS = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VALUES = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VALUETYPE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VALUE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_ELEVATION = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VIEWTARGET = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VIEWBOX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BBOX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REFX = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_BY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_CY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_DY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_FY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_RY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_REFY = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VERYTHINMATHSPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VERYTHICKMATHSPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VERYVERYTHINMATHSPACE = nullptr;
+nsHtml5AttributeName* nsHtml5AttributeName::ATTR_VERYVERYTHICKMATHSPACE = nullptr;
+nsHtml5AttributeName** nsHtml5AttributeName::ATTRIBUTE_NAMES = 0;
+static int32_t const ATTRIBUTE_HASHES_DATA[] = { 1891098437, 1756426572, 1972151670, 1740096054, 1814986837, 1922419228, 2004199576, 1680411449, 1754612424, 1786622296, 1854474395, 1910487243, 1932959284, 1988132214, 2017010843, 1037879561, 1691145478, 1749399124, 1754860396, 1759379608, 1803561214, 1823829083, 1874261045, 1905902311, 1917327080, 1922679531, 1941409583, 1972996699, 2000162011, 2009059485, 2065170434, 71303169, 1680185931, 1683805446, 1723336432, 1747939528, 1753049109, 1754751622, 1754958648, 1756836998, 1776114564, 1788254870, 1804978712, 1820637455, 1825677514, 1867448617, 1884246821, 1902640276, 1908195085, 1915341049, 1922319046, 1922630475, 1924517489, 1934970504, 1965349396, 1972904522, 1983347764, 1991392548, 2001669450, 2007019632, 2010452700, 2024763702, 2082471938, 57205395, 885522434, 1680165436, 1680311085, 1681694748, 1687751191, 1714745560, 1732771842, 1747348637, 1748869205, 1751649130, 1754434872, 1754647068, 1754835516, 1754899031, 1756219733, 1756710661, 1757421892, 1771569964, 1782518297, 1786851500, 1791070327, 1804069019, 1814558026, 1817175115, 1821958888, 1824081655, 1854302364, 1864698185, 1872034503, 1875753052, 1889569526, 1894552650, 1905541832, 1906421049, 1910328970, 1910572893, 1916278099, 1921061206, 1922400908, 1922566877, 1922665179, 1924206934, 1924629705, 1933369607, 1937777860, 1941454586, 1966439670, 1972744954, 1972922984, 1982640164, 1983461061, 1990062797, 1999273799, 2001578182, 2001814704, 2005925890, 2008084807, 2009079867, 2016711994, 2023146024, 2026975253, 2073034754, 2093791505, 53006051, 60345635, 876085250, 901775362, 1680140893, 1680165613, 1680230940, 1680345685, 1680446153, 1681940503, 1686731997, 1689324870, 1697174123, 1721189160, 1724189239, 1734404167, 1742183484, 1747792072, 1748552744, 1749027145, 1751232761, 1751755561, 1753550036, 1754579720, 1754644293, 1754647353, 1754794646, 1754860061, 1754860401, 1754907227, 1756155098, 1756302628, 1756471625, 1756762256, 1756889417, 1757942610, 1767725700, 1772032615, 1780975314, 1784643703, 1786775671, 1787365531, 1790814502, 1797886599, 1804036350, 1804235064, 1805715716, 1814656326, 1816144023, 1817177246, 1820928104, 1823574314, 1823975206, 1824377064, 1853862084, 1854464212, 1854497003, 1865910347, 1867620412, 1873590471, 1874698443, 1884079398, 1884295780, 1890996553, 1891186903, 1898428101, 1903659239, 1905672729, 1906408598, 1907660596, 1909438149, 1910441770, 1910507338, 1915146282, 1916210285, 1916337499, 1917953597, 1921894426, 1922384591, 1922413292, 1922482777, 1922599757, 1922665052, 1922677495, 1922699851, 1924453467, 1924583073, 1924773438, 1933123337, 1934917290, 1935597338, 1941253366, 1941438085, 1942026440, 1965561677, 1966454567, 1972656710, 1972863609, 1972908839, 1972963917, 1975062341, 1983266615, 1983416119, 1987410233, 1988788535, 1991021879, 1991643278, 2000125224, 2001210183, 2001634459, 2001710299, 2001898808, 2004957380, 2006516551, 2007064812, 2008408414, 2009061533, 2009231684, 2010716309, 2016810187, 2019887833, 2024616088, 2026741958, 2060302634, 2066743298, 2081423362, 2089811970, 2093791509, 52488851, 55077603, 59825747, 64487425, 72351745, 883425282, 894959618, 911736834, 1038141480, 1680159328, 1680165487, 1680181850, 1680198381, 1680251485, 1680323325, 1680347981, 1680433915, 1680511804, 1681844247, 1682440540, 1685882101, 1687503600, 1689048326, 1689839946, 1692408896, 1704262346, 1715466295, 1721347639, 1723340621, 1724238365, 1733919469, 1739583824, 1740130375, 1747299630, 1747455030, 1747839118, 1748306996, 1748566068, 1748971848, 1749350104, 1749856356, 1751507685, 1751679545, 1752985897, 1753297133, 1754214628, 1754546894, 1754606246, 1754643237, 1754645079, 1754647074, 1754698327, 1754792749, 1754798923, 1754858317, 1754860110, 1754860400, 1754872618, 1754905345, 1754927689, 1756147974, 1756190926, 1756265690, 1756360955, 1756428495, 1756704824, 1756737685, 1756804936, 1756874572, 1757053236, 1757874716, 1758018291, 1765800271, 1767875272, 1771637325, 1773606972, 1780879045, 1781007934, 1784574102, 1785174319, 1786740932, 1786821704, 1787193500, 1787699221, 1788842244, 1791068279, 1797666394, 1801312388, 1803839644, 1804054854, 1804081401, 1804405895, 1805715690, 1814517574, 1814560070, 1814656840, 1816104145, 1816178925, 1817175198, 1820262641, 1820727381, 1821755934, 1822002839, 1823580230, 1823841492, 1824005974, 1824159037, 1825437894, 1848600826, 1854285018, 1854366938, 1854466380, 1854497001, 1854497008, 1865910331, 1866496199, 1867462756, 1871251689, 1872343590, 1873656984, 1874270021, 1874788501, 1881750231, 1884142379, 1884267068, 1884343396, 1889633006, 1891069765, 1891182792, 1891937366, 1898415413, 1900544002, 1903612236, 1903759600, 1905628916, 1905754853, 1906408542, 1906419001, 1906423097, 1907701479, 1908462185, 1909819252, 1910441627, 1910441773, 1910503637, 1910527802, 1915025672, 1915295948, 1915757815, 1916247343, 1916286197, 1917295176, 1917857531, 1919297291, 1921880376, 1921977416, 1922354008, 1922384686, 1922413290, 1922413307, 1922470745, 1922531929, 1922567078, 1922607670, 1922632396, 1922665174, 1922671417, 1922679386, 1922679610, 1923088386, 1924443742, 1924462384, 1924570799, 1924585254, 1924738716, 1932870919, 1932986153, 1933145837, 1933508940, 1934917372, 1935099626, 1937336473, 1939976792, 1941286708, 1941435445, 1941440197, 1941550652, 1943317364, 1965512429, 1966384692, 1966442279, 1971855414, 1972196486, 1972744939, 1972750880, 1972904518, 1972904785, 1972909592, 1972962123, 1972980466, 1974849131, 1982254612, 1983157559, 1983290011, 1983398182, 1983432389, 1984430082, 1987422362, 1988784439, 1989522022, 1990107683, 1991220282, 1991625270, 1993343287, 2000096287, 2000160071, 2000752725, 2001527900, 2001634458, 2001669449, 2001710298, 2001732764, 2001826027, 2001898809, 2004846654, 2005342360, 2006459190, 2006824246, 2007021895, 2007064819, 2008401563, 2009041198, 2009061450, 2009071951, 2009141482, 2009434924, 2010542150, 2015950026, 2016787611, 2016910397, 2018908874, 2023011418, 2023342821, 2024647008, 2024794274, 2026893641, 2034765641, 2060474743, 2065694722, 2066762276, 2075005220, 2081947650, 2083520514, 2091784484, 2093791506, 2093791510, 50917059, 52489043, 53537523, 56685811, 57210387, 59830867, 60817409, 68157441, 71827457, 808872090, 878182402, 884998146, 892862466, 900202498, 902299650, 928514050, 1038063816, 1680095865, 1680159327, 1680165421, 1680165437, 1680165533, 1680165692, 1680181996, 1680198203, 1680229115, 1680231247, 1680282148, 1680315086, 1680343801, 1680345965, 1680368221, 1680413393, 1680437801, 1680452349, 1681174213, 1681733672, 1681879063, 1681969220, 1682587945, 1684319541, 1685902598, 1687164232, 1687620127, 1687751377, 1689130184, 1689788441, 1691091102, 1691293817, 1692933184, 1699185409, 1704526375, 1714763319, 1716303957, 1721305962, 1723309623, 1723336528, 1723645710, 1724197420, 1731048742, 1733874289, 1734182982, 1739561208, 1739927860, 1740119884, 1741535501, 1747295467, 1747309881, 1747446838, 1747479606, 1747800157, 1747906667, 1748021284, 1748503880 };
+staticJArray<int32_t,int32_t> nsHtml5AttributeName::ATTRIBUTE_HASHES = { ATTRIBUTE_HASHES_DATA, MOZ_ARRAY_LENGTH(ATTRIBUTE_HASHES_DATA) };
+void
+nsHtml5AttributeName::initializeStatics()
+{
+ ALL_NO_NS = new int32_t[3];
+ ALL_NO_NS[0] = kNameSpaceID_None;
+ ALL_NO_NS[1] = kNameSpaceID_None;
+ ALL_NO_NS[2] = kNameSpaceID_None;
+ XMLNS_NS = new int32_t[3];
+ XMLNS_NS[0] = kNameSpaceID_None;
+ XMLNS_NS[1] = kNameSpaceID_XMLNS;
+ XMLNS_NS[2] = kNameSpaceID_XMLNS;
+ XML_NS = new int32_t[3];
+ XML_NS[0] = kNameSpaceID_None;
+ XML_NS[1] = kNameSpaceID_XML;
+ XML_NS[2] = kNameSpaceID_XML;
+ XLINK_NS = new int32_t[3];
+ XLINK_NS[0] = kNameSpaceID_None;
+ XLINK_NS[1] = kNameSpaceID_XLink;
+ XLINK_NS[2] = kNameSpaceID_XLink;
+ ALL_NO_PREFIX = new nsIAtom*[3];
+ ALL_NO_PREFIX[0] = nullptr;
+ ALL_NO_PREFIX[1] = nullptr;
+ ALL_NO_PREFIX[2] = nullptr;
+ XMLNS_PREFIX = new nsIAtom*[3];
+ XMLNS_PREFIX[0] = nullptr;
+ XMLNS_PREFIX[1] = nsHtml5Atoms::xmlns;
+ XMLNS_PREFIX[2] = nsHtml5Atoms::xmlns;
+ XLINK_PREFIX = new nsIAtom*[3];
+ XLINK_PREFIX[0] = nullptr;
+ XLINK_PREFIX[1] = nsHtml5Atoms::xlink;
+ XLINK_PREFIX[2] = nsHtml5Atoms::xlink;
+ XML_PREFIX = new nsIAtom*[3];
+ XML_PREFIX[0] = nullptr;
+ XML_PREFIX[1] = nsHtml5Atoms::xml;
+ XML_PREFIX[2] = nsHtml5Atoms::xml;
+ ATTR_ALT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::alt), ALL_NO_PREFIX);
+ ATTR_DIR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::dir), ALL_NO_PREFIX);
+ ATTR_DUR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::dur), ALL_NO_PREFIX);
+ ATTR_END = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::end), ALL_NO_PREFIX);
+ ATTR_FOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::for_), ALL_NO_PREFIX);
+ ATTR_IN2 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::in2), ALL_NO_PREFIX);
+ ATTR_LOW = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::low), ALL_NO_PREFIX);
+ ATTR_MIN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::min), ALL_NO_PREFIX);
+ ATTR_MAX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::max), ALL_NO_PREFIX);
+ ATTR_REL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rel), ALL_NO_PREFIX);
+ ATTR_REV = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rev), ALL_NO_PREFIX);
+ ATTR_SRC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::src), ALL_NO_PREFIX);
+ ATTR_D = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::d), ALL_NO_PREFIX);
+ ATTR_K = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::k), ALL_NO_PREFIX);
+ ATTR_R = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::r), ALL_NO_PREFIX);
+ ATTR_X = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::x), ALL_NO_PREFIX);
+ ATTR_Y = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::y), ALL_NO_PREFIX);
+ ATTR_Z = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::z), ALL_NO_PREFIX);
+ ATTR_CAP_HEIGHT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::cap_height), ALL_NO_PREFIX);
+ ATTR_G1 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::g1), ALL_NO_PREFIX);
+ ATTR_K1 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::k1), ALL_NO_PREFIX);
+ ATTR_U1 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::u1), ALL_NO_PREFIX);
+ ATTR_X1 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::x1), ALL_NO_PREFIX);
+ ATTR_Y1 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::y1), ALL_NO_PREFIX);
+ ATTR_G2 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::g2), ALL_NO_PREFIX);
+ ATTR_K2 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::k2), ALL_NO_PREFIX);
+ ATTR_U2 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::u2), ALL_NO_PREFIX);
+ ATTR_X2 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::x2), ALL_NO_PREFIX);
+ ATTR_Y2 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::y2), ALL_NO_PREFIX);
+ ATTR_K3 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::k3), ALL_NO_PREFIX);
+ ATTR_K4 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::k4), ALL_NO_PREFIX);
+ ATTR_XML_SPACE = new nsHtml5AttributeName(XML_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xml_space, nsHtml5Atoms::space), XML_PREFIX);
+ ATTR_XML_LANG = new nsHtml5AttributeName(XML_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xml_lang, nsHtml5Atoms::lang), XML_PREFIX);
+ ATTR_XML_BASE = new nsHtml5AttributeName(XML_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xml_base, nsHtml5Atoms::base), XML_PREFIX);
+ ATTR_ARIA_GRAB = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_grab), ALL_NO_PREFIX);
+ ATTR_ARIA_VALUEMAX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_valuemax), ALL_NO_PREFIX);
+ ATTR_ARIA_LABELLEDBY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_labelledby), ALL_NO_PREFIX);
+ ATTR_ARIA_DESCRIBEDBY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_describedby), ALL_NO_PREFIX);
+ ATTR_ARIA_DISABLED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_disabled), ALL_NO_PREFIX);
+ ATTR_ARIA_CHECKED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_checked), ALL_NO_PREFIX);
+ ATTR_ARIA_SELECTED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_selected), ALL_NO_PREFIX);
+ ATTR_ARIA_DROPEFFECT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_dropeffect), ALL_NO_PREFIX);
+ ATTR_ARIA_REQUIRED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_required), ALL_NO_PREFIX);
+ ATTR_ARIA_EXPANDED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_expanded), ALL_NO_PREFIX);
+ ATTR_ARIA_PRESSED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_pressed), ALL_NO_PREFIX);
+ ATTR_ARIA_LEVEL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_level), ALL_NO_PREFIX);
+ ATTR_ARIA_CHANNEL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_channel), ALL_NO_PREFIX);
+ ATTR_ARIA_HIDDEN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_hidden), ALL_NO_PREFIX);
+ ATTR_ARIA_SECRET = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_secret), ALL_NO_PREFIX);
+ ATTR_ARIA_POSINSET = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_posinset), ALL_NO_PREFIX);
+ ATTR_ARIA_ATOMIC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_atomic), ALL_NO_PREFIX);
+ ATTR_ARIA_INVALID = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_invalid), ALL_NO_PREFIX);
+ ATTR_ARIA_TEMPLATEID = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_templateid), ALL_NO_PREFIX);
+ ATTR_ARIA_VALUEMIN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_valuemin), ALL_NO_PREFIX);
+ ATTR_ARIA_MULTISELECTABLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_multiselectable), ALL_NO_PREFIX);
+ ATTR_ARIA_CONTROLS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_controls), ALL_NO_PREFIX);
+ ATTR_ARIA_MULTILINE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_multiline), ALL_NO_PREFIX);
+ ATTR_ARIA_READONLY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_readonly), ALL_NO_PREFIX);
+ ATTR_ARIA_OWNS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_owns), ALL_NO_PREFIX);
+ ATTR_ARIA_ACTIVEDESCENDANT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_activedescendant), ALL_NO_PREFIX);
+ ATTR_ARIA_RELEVANT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_relevant), ALL_NO_PREFIX);
+ ATTR_ARIA_DATATYPE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_datatype), ALL_NO_PREFIX);
+ ATTR_ARIA_VALUENOW = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_valuenow), ALL_NO_PREFIX);
+ ATTR_ARIA_SORT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_sort), ALL_NO_PREFIX);
+ ATTR_ARIA_AUTOCOMPLETE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_autocomplete), ALL_NO_PREFIX);
+ ATTR_ARIA_FLOWTO = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_flowto), ALL_NO_PREFIX);
+ ATTR_ARIA_BUSY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_busy), ALL_NO_PREFIX);
+ ATTR_ARIA_LIVE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_live), ALL_NO_PREFIX);
+ ATTR_ARIA_HASPOPUP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_haspopup), ALL_NO_PREFIX);
+ ATTR_ARIA_SETSIZE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::aria_setsize), ALL_NO_PREFIX);
+ ATTR_CLEAR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::clear), ALL_NO_PREFIX);
+ ATTR_DATAFORMATAS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::dataformatas), ALL_NO_PREFIX);
+ ATTR_DISABLED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::disabled), ALL_NO_PREFIX);
+ ATTR_DATAFLD = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::datafld), ALL_NO_PREFIX);
+ ATTR_DEFAULT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::default_), ALL_NO_PREFIX);
+ ATTR_DATASRC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::datasrc), ALL_NO_PREFIX);
+ ATTR_DATA = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::data), ALL_NO_PREFIX);
+ ATTR_EQUALCOLUMNS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::equalcolumns), ALL_NO_PREFIX);
+ ATTR_EQUALROWS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::equalrows), ALL_NO_PREFIX);
+ ATTR_HSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::hspace), ALL_NO_PREFIX);
+ ATTR_ISMAP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ismap), ALL_NO_PREFIX);
+ ATTR_LOCAL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::local), ALL_NO_PREFIX);
+ ATTR_LSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::lspace), ALL_NO_PREFIX);
+ ATTR_MOVABLELIMITS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::movablelimits), ALL_NO_PREFIX);
+ ATTR_NOTATION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::notation), ALL_NO_PREFIX);
+ ATTR_ONDATASETCHANGED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondatasetchanged), ALL_NO_PREFIX);
+ ATTR_ONDATAAVAILABLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondataavailable), ALL_NO_PREFIX);
+ ATTR_ONPASTE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onpaste), ALL_NO_PREFIX);
+ ATTR_ONDATASETCOMPLETE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondatasetcomplete), ALL_NO_PREFIX);
+ ATTR_RSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rspace), ALL_NO_PREFIX);
+ ATTR_ROWALIGN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rowalign), ALL_NO_PREFIX);
+ ATTR_ROTATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rotate), ALL_NO_PREFIX);
+ ATTR_SEPARATOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::separator), ALL_NO_PREFIX);
+ ATTR_SEPARATORS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::separators), ALL_NO_PREFIX);
+ ATTR_V_MATHEMATICAL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::v_mathematical), ALL_NO_PREFIX);
+ ATTR_VSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::vspace), ALL_NO_PREFIX);
+ ATTR_V_HANGING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::v_hanging), ALL_NO_PREFIX);
+ ATTR_XCHANNELSELECTOR = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::xchannelselector, nsHtml5Atoms::xChannelSelector), ALL_NO_PREFIX);
+ ATTR_YCHANNELSELECTOR = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::ychannelselector, nsHtml5Atoms::yChannelSelector), ALL_NO_PREFIX);
+ ATTR_ARABIC_FORM = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::arabic_form), ALL_NO_PREFIX);
+ ATTR_ENABLE_BACKGROUND = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::enable_background), ALL_NO_PREFIX);
+ ATTR_ONDBLCLICK = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondblclick), ALL_NO_PREFIX);
+ ATTR_ONABORT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onabort), ALL_NO_PREFIX);
+ ATTR_CALCMODE = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::calcmode, nsHtml5Atoms::calcMode), ALL_NO_PREFIX);
+ ATTR_CHECKED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::checked), ALL_NO_PREFIX);
+ ATTR_DESCENT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::descent), ALL_NO_PREFIX);
+ ATTR_FENCE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::fence), ALL_NO_PREFIX);
+ ATTR_ONSCROLL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onscroll), ALL_NO_PREFIX);
+ ATTR_ONACTIVATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onactivate), ALL_NO_PREFIX);
+ ATTR_OPACITY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::opacity), ALL_NO_PREFIX);
+ ATTR_SPACING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::spacing), ALL_NO_PREFIX);
+ ATTR_SPECULAREXPONENT = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::specularexponent, nsHtml5Atoms::specularExponent), ALL_NO_PREFIX);
+ ATTR_SPECULARCONSTANT = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::specularconstant, nsHtml5Atoms::specularConstant), ALL_NO_PREFIX);
+ ATTR_SPECIFICATION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::specification), ALL_NO_PREFIX);
+ ATTR_THICKMATHSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::thickmathspace), ALL_NO_PREFIX);
+ ATTR_UNICODE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::unicode_), ALL_NO_PREFIX);
+ ATTR_UNICODE_BIDI = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::unicode_bidi), ALL_NO_PREFIX);
+ ATTR_UNICODE_RANGE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::unicode_range), ALL_NO_PREFIX);
+ ATTR_BORDER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::border), ALL_NO_PREFIX);
+ ATTR_ID = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::id), ALL_NO_PREFIX);
+ ATTR_GRADIENTTRANSFORM = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::gradienttransform, nsHtml5Atoms::gradientTransform), ALL_NO_PREFIX);
+ ATTR_GRADIENTUNITS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::gradientunits, nsHtml5Atoms::gradientUnits), ALL_NO_PREFIX);
+ ATTR_HIDDEN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::hidden), ALL_NO_PREFIX);
+ ATTR_HEADERS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::headers), ALL_NO_PREFIX);
+ ATTR_READONLY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::readonly), ALL_NO_PREFIX);
+ ATTR_RENDERING_INTENT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rendering_intent), ALL_NO_PREFIX);
+ ATTR_SEED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::seed), ALL_NO_PREFIX);
+ ATTR_SRCDOC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::srcdoc), ALL_NO_PREFIX);
+ ATTR_STDDEVIATION = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::stddeviation, nsHtml5Atoms::stdDeviation), ALL_NO_PREFIX);
+ ATTR_SANDBOX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::sandbox), ALL_NO_PREFIX);
+ ATTR_V_IDEOGRAPHIC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::v_ideographic), ALL_NO_PREFIX);
+ ATTR_WORD_SPACING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::word_spacing), ALL_NO_PREFIX);
+ ATTR_ACCENTUNDER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::accentunder), ALL_NO_PREFIX);
+ ATTR_ACCEPT_CHARSET = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::accept_charset), ALL_NO_PREFIX);
+ ATTR_ACCESSKEY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::accesskey), ALL_NO_PREFIX);
+ ATTR_ACCENT_HEIGHT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::accent_height), ALL_NO_PREFIX);
+ ATTR_ACCENT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::accent), ALL_NO_PREFIX);
+ ATTR_ASCENT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ascent), ALL_NO_PREFIX);
+ ATTR_ACCEPT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::accept), ALL_NO_PREFIX);
+ ATTR_BEVELLED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::bevelled), ALL_NO_PREFIX);
+ ATTR_BASEFREQUENCY = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::basefrequency, nsHtml5Atoms::baseFrequency), ALL_NO_PREFIX);
+ ATTR_BASELINE_SHIFT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::baseline_shift), ALL_NO_PREFIX);
+ ATTR_BASEPROFILE = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::baseprofile, nsHtml5Atoms::baseProfile), ALL_NO_PREFIX);
+ ATTR_BASELINE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::baseline), ALL_NO_PREFIX);
+ ATTR_BASE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::base), ALL_NO_PREFIX);
+ ATTR_CODE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::code), ALL_NO_PREFIX);
+ ATTR_CODETYPE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::codetype), ALL_NO_PREFIX);
+ ATTR_CODEBASE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::codebase), ALL_NO_PREFIX);
+ ATTR_CITE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::cite), ALL_NO_PREFIX);
+ ATTR_DEFER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::defer), ALL_NO_PREFIX);
+ ATTR_DATETIME = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::datetime), ALL_NO_PREFIX);
+ ATTR_DIRECTION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::direction), ALL_NO_PREFIX);
+ ATTR_EDGEMODE = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::edgemode, nsHtml5Atoms::edgeMode), ALL_NO_PREFIX);
+ ATTR_EDGE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::edge), ALL_NO_PREFIX);
+ ATTR_FACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::face), ALL_NO_PREFIX);
+ ATTR_HIDEFOCUS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::hidefocus), ALL_NO_PREFIX);
+ ATTR_INDEX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::index), ALL_NO_PREFIX);
+ ATTR_IRRELEVANT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::irrelevant), ALL_NO_PREFIX);
+ ATTR_INTERCEPT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::intercept), ALL_NO_PREFIX);
+ ATTR_INTEGRITY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::integrity), ALL_NO_PREFIX);
+ ATTR_LINEBREAK = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::linebreak), ALL_NO_PREFIX);
+ ATTR_LABEL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::label), ALL_NO_PREFIX);
+ ATTR_LINETHICKNESS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::linethickness), ALL_NO_PREFIX);
+ ATTR_MODE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::mode), ALL_NO_PREFIX);
+ ATTR_NAME = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::name), ALL_NO_PREFIX);
+ ATTR_NORESIZE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::noresize), ALL_NO_PREFIX);
+ ATTR_ONBEFOREUNLOAD = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbeforeunload), ALL_NO_PREFIX);
+ ATTR_ONREPEAT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onrepeat), ALL_NO_PREFIX);
+ ATTR_OBJECT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::object), ALL_NO_PREFIX);
+ ATTR_ONSELECT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onselect), ALL_NO_PREFIX);
+ ATTR_ORDER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::order), ALL_NO_PREFIX);
+ ATTR_OTHER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::other), ALL_NO_PREFIX);
+ ATTR_ONRESET = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onreset), ALL_NO_PREFIX);
+ ATTR_ONCELLCHANGE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::oncellchange), ALL_NO_PREFIX);
+ ATTR_ONREADYSTATECHANGE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onreadystatechange), ALL_NO_PREFIX);
+ ATTR_ONMESSAGE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmessage), ALL_NO_PREFIX);
+ ATTR_ONBEGIN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbegin), ALL_NO_PREFIX);
+ ATTR_ONHELP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onhelp), ALL_NO_PREFIX);
+ ATTR_ONBEFOREPRINT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbeforeprint), ALL_NO_PREFIX);
+ ATTR_ORIENT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::orient), ALL_NO_PREFIX);
+ ATTR_ORIENTATION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::orientation), ALL_NO_PREFIX);
+ ATTR_ONBEFORECOPY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbeforecopy), ALL_NO_PREFIX);
+ ATTR_ONSELECTSTART = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onselectstart), ALL_NO_PREFIX);
+ ATTR_ONBEFOREPASTE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbeforepaste), ALL_NO_PREFIX);
+ ATTR_ONBEFOREUPDATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbeforeupdate), ALL_NO_PREFIX);
+ ATTR_ONDEACTIVATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondeactivate), ALL_NO_PREFIX);
+ ATTR_ONBEFOREACTIVATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbeforeactivate), ALL_NO_PREFIX);
+ ATTR_ONBEFORDEACTIVATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbefordeactivate), ALL_NO_PREFIX);
+ ATTR_ONKEYPRESS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onkeypress), ALL_NO_PREFIX);
+ ATTR_ONKEYUP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onkeyup), ALL_NO_PREFIX);
+ ATTR_ONBEFOREEDITFOCUS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbeforeeditfocus), ALL_NO_PREFIX);
+ ATTR_ONBEFORECUT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbeforecut), ALL_NO_PREFIX);
+ ATTR_ONKEYDOWN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onkeydown), ALL_NO_PREFIX);
+ ATTR_ONRESIZE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onresize), ALL_NO_PREFIX);
+ ATTR_REPEAT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::repeat), ALL_NO_PREFIX);
+ ATTR_REPEAT_MAX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::repeat_max), ALL_NO_PREFIX);
+ ATTR_REFERRERPOLICY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::referrerpolicy), ALL_NO_PREFIX);
+ ATTR_RULES = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rules), ALL_NO_PREFIX);
+ ATTR_REPEAT_MIN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::repeat_min), ALL_NO_PREFIX);
+ ATTR_ROLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::role), ALL_NO_PREFIX);
+ ATTR_REPEATCOUNT = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::repeatcount, nsHtml5Atoms::repeatCount), ALL_NO_PREFIX);
+ ATTR_REPEAT_START = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::repeat_start), ALL_NO_PREFIX);
+ ATTR_REPEAT_TEMPLATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::repeat_template), ALL_NO_PREFIX);
+ ATTR_REPEATDUR = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::repeatdur, nsHtml5Atoms::repeatDur), ALL_NO_PREFIX);
+ ATTR_SELECTED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::selected), ALL_NO_PREFIX);
+ ATTR_SPEED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::speed), ALL_NO_PREFIX);
+ ATTR_SIZES = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::sizes), ALL_NO_PREFIX);
+ ATTR_SUPERSCRIPTSHIFT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::superscriptshift), ALL_NO_PREFIX);
+ ATTR_STRETCHY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stretchy), ALL_NO_PREFIX);
+ ATTR_SCHEME = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::scheme), ALL_NO_PREFIX);
+ ATTR_SPREADMETHOD = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::spreadmethod, nsHtml5Atoms::spreadMethod), ALL_NO_PREFIX);
+ ATTR_SELECTION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::selection), ALL_NO_PREFIX);
+ ATTR_SIZE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::size), ALL_NO_PREFIX);
+ ATTR_TYPE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::type), ALL_NO_PREFIX);
+ ATTR_UNSELECTABLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::unselectable), ALL_NO_PREFIX);
+ ATTR_UNDERLINE_POSITION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::underline_position), ALL_NO_PREFIX);
+ ATTR_UNDERLINE_THICKNESS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::underline_thickness), ALL_NO_PREFIX);
+ ATTR_X_HEIGHT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::x_height), ALL_NO_PREFIX);
+ ATTR_DIFFUSECONSTANT = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::diffuseconstant, nsHtml5Atoms::diffuseConstant), ALL_NO_PREFIX);
+ ATTR_HREF = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::href), ALL_NO_PREFIX);
+ ATTR_HREFLANG = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::hreflang), ALL_NO_PREFIX);
+ ATTR_ONAFTERPRINT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onafterprint), ALL_NO_PREFIX);
+ ATTR_ONAFTERUPDATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onafterupdate), ALL_NO_PREFIX);
+ ATTR_PROFILE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::profile), ALL_NO_PREFIX);
+ ATTR_SURFACESCALE = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::surfacescale, nsHtml5Atoms::surfaceScale), ALL_NO_PREFIX);
+ ATTR_XREF = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::xref), ALL_NO_PREFIX);
+ ATTR_ALIGN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::align), ALL_NO_PREFIX);
+ ATTR_ALIGNMENT_BASELINE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::alignment_baseline), ALL_NO_PREFIX);
+ ATTR_ALIGNMENTSCOPE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::alignmentscope), ALL_NO_PREFIX);
+ ATTR_DRAGGABLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::draggable), ALL_NO_PREFIX);
+ ATTR_HEIGHT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::height), ALL_NO_PREFIX);
+ ATTR_HANGING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::hanging), ALL_NO_PREFIX);
+ ATTR_IMAGE_RENDERING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::image_rendering), ALL_NO_PREFIX);
+ ATTR_LANGUAGE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::language), ALL_NO_PREFIX);
+ ATTR_LANG = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::lang), ALL_NO_PREFIX);
+ ATTR_LARGEOP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::largeop), ALL_NO_PREFIX);
+ ATTR_LONGDESC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::longdesc), ALL_NO_PREFIX);
+ ATTR_LENGTHADJUST = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::lengthadjust, nsHtml5Atoms::lengthAdjust), ALL_NO_PREFIX);
+ ATTR_MARGINHEIGHT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::marginheight), ALL_NO_PREFIX);
+ ATTR_MARGINWIDTH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::marginwidth), ALL_NO_PREFIX);
+ ATTR_NARGS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::nargs), ALL_NO_PREFIX);
+ ATTR_ORIGIN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::origin), ALL_NO_PREFIX);
+ ATTR_PING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ping), ALL_NO_PREFIX);
+ ATTR_TARGET = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::target), ALL_NO_PREFIX);
+ ATTR_TARGETX = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::targetx, nsHtml5Atoms::targetX), ALL_NO_PREFIX);
+ ATTR_TARGETY = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::targety, nsHtml5Atoms::targetY), ALL_NO_PREFIX);
+ ATTR_ALPHABETIC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::alphabetic), ALL_NO_PREFIX);
+ ATTR_ARCHIVE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::archive), ALL_NO_PREFIX);
+ ATTR_HIGH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::high), ALL_NO_PREFIX);
+ ATTR_LIGHTING_COLOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::lighting_color), ALL_NO_PREFIX);
+ ATTR_MATHEMATICAL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::mathematical), ALL_NO_PREFIX);
+ ATTR_MATHBACKGROUND = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::mathbackground), ALL_NO_PREFIX);
+ ATTR_METHOD = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::method), ALL_NO_PREFIX);
+ ATTR_MATHVARIANT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::mathvariant), ALL_NO_PREFIX);
+ ATTR_MATHCOLOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::mathcolor), ALL_NO_PREFIX);
+ ATTR_MATHSIZE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::mathsize), ALL_NO_PREFIX);
+ ATTR_NOSHADE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::noshade), ALL_NO_PREFIX);
+ ATTR_ONCHANGE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onchange), ALL_NO_PREFIX);
+ ATTR_PATHLENGTH = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::pathlength, nsHtml5Atoms::pathLength), ALL_NO_PREFIX);
+ ATTR_PATH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::path), ALL_NO_PREFIX);
+ ATTR_ALTIMG = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::altimg), ALL_NO_PREFIX);
+ ATTR_ACTIONTYPE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::actiontype), ALL_NO_PREFIX);
+ ATTR_ACTION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::action), ALL_NO_PREFIX);
+ ATTR_ACTIVE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::active), ALL_NO_PREFIX);
+ ATTR_ADDITIVE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::additive), ALL_NO_PREFIX);
+ ATTR_BEGIN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::begin), ALL_NO_PREFIX);
+ ATTR_DOMINANT_BASELINE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::dominant_baseline), ALL_NO_PREFIX);
+ ATTR_DIVISOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::divisor), ALL_NO_PREFIX);
+ ATTR_DEFINITIONURL = new nsHtml5AttributeName(ALL_NO_NS, MATH_DIFFERENT(nsHtml5Atoms::definitionurl, nsHtml5Atoms::definitionURL), ALL_NO_PREFIX);
+ ATTR_HORIZ_ADV_X = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::horiz_adv_x), ALL_NO_PREFIX);
+ ATTR_HORIZ_ORIGIN_X = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::horiz_origin_x), ALL_NO_PREFIX);
+ ATTR_HORIZ_ORIGIN_Y = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::horiz_origin_y), ALL_NO_PREFIX);
+ ATTR_LIMITINGCONEANGLE = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::limitingconeangle, nsHtml5Atoms::limitingConeAngle), ALL_NO_PREFIX);
+ ATTR_MEDIUMMATHSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::mediummathspace), ALL_NO_PREFIX);
+ ATTR_MEDIA = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::media), ALL_NO_PREFIX);
+ ATTR_MANIFEST = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::manifest), ALL_NO_PREFIX);
+ ATTR_ONFILTERCHANGE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onfilterchange), ALL_NO_PREFIX);
+ ATTR_ONFINISH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onfinish), ALL_NO_PREFIX);
+ ATTR_OPTIMUM = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::optimum), ALL_NO_PREFIX);
+ ATTR_RADIOGROUP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::radiogroup), ALL_NO_PREFIX);
+ ATTR_RADIUS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::radius), ALL_NO_PREFIX);
+ ATTR_SCRIPTLEVEL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::scriptlevel), ALL_NO_PREFIX);
+ ATTR_SCRIPTSIZEMULTIPLIER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::scriptsizemultiplier), ALL_NO_PREFIX);
+ ATTR_STRING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::string), ALL_NO_PREFIX);
+ ATTR_STRIKETHROUGH_POSITION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::strikethrough_position), ALL_NO_PREFIX);
+ ATTR_STRIKETHROUGH_THICKNESS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::strikethrough_thickness), ALL_NO_PREFIX);
+ ATTR_SCRIPTMINSIZE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::scriptminsize), ALL_NO_PREFIX);
+ ATTR_TABINDEX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::tabindex), ALL_NO_PREFIX);
+ ATTR_VALIGN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::valign), ALL_NO_PREFIX);
+ ATTR_VISIBILITY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::visibility), ALL_NO_PREFIX);
+ ATTR_BACKGROUND = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::background), ALL_NO_PREFIX);
+ ATTR_LINK = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::link), ALL_NO_PREFIX);
+ ATTR_MARKER_MID = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::marker_mid), ALL_NO_PREFIX);
+ ATTR_MARKERHEIGHT = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::markerheight, nsHtml5Atoms::markerHeight), ALL_NO_PREFIX);
+ ATTR_MARKER_END = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::marker_end), ALL_NO_PREFIX);
+ ATTR_MASK = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::mask), ALL_NO_PREFIX);
+ ATTR_MARKER_START = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::marker_start), ALL_NO_PREFIX);
+ ATTR_MARKERWIDTH = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::markerwidth, nsHtml5Atoms::markerWidth), ALL_NO_PREFIX);
+ ATTR_MASKUNITS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::maskunits, nsHtml5Atoms::maskUnits), ALL_NO_PREFIX);
+ ATTR_MARKERUNITS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::markerunits, nsHtml5Atoms::markerUnits), ALL_NO_PREFIX);
+ ATTR_MASKCONTENTUNITS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::maskcontentunits, nsHtml5Atoms::maskContentUnits), ALL_NO_PREFIX);
+ ATTR_AMPLITUDE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::amplitude), ALL_NO_PREFIX);
+ ATTR_CELLSPACING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::cellspacing), ALL_NO_PREFIX);
+ ATTR_CELLPADDING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::cellpadding), ALL_NO_PREFIX);
+ ATTR_DECLARE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::declare), ALL_NO_PREFIX);
+ ATTR_FILL_RULE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::fill_rule), ALL_NO_PREFIX);
+ ATTR_FILL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::fill), ALL_NO_PREFIX);
+ ATTR_FILL_OPACITY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::fill_opacity), ALL_NO_PREFIX);
+ ATTR_MAXLENGTH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::maxlength), ALL_NO_PREFIX);
+ ATTR_ONCLICK = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onclick), ALL_NO_PREFIX);
+ ATTR_ONBLUR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onblur), ALL_NO_PREFIX);
+ ATTR_REPLACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::replace), ALL_NO_PREFIX);
+ ATTR_ROWLINES = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rowlines), ALL_NO_PREFIX);
+ ATTR_SCALE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::scale), ALL_NO_PREFIX);
+ ATTR_STYLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::style), ALL_NO_PREFIX);
+ ATTR_TABLEVALUES = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::tablevalues, nsHtml5Atoms::tableValues), ALL_NO_PREFIX);
+ ATTR_TITLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::title), ALL_NO_PREFIX);
+ ATTR_V_ALPHABETIC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::v_alphabetic), ALL_NO_PREFIX);
+ ATTR_AZIMUTH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::azimuth), ALL_NO_PREFIX);
+ ATTR_FORMAT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::format), ALL_NO_PREFIX);
+ ATTR_FRAMEBORDER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::frameborder), ALL_NO_PREFIX);
+ ATTR_FRAME = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::frame), ALL_NO_PREFIX);
+ ATTR_FRAMESPACING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::framespacing), ALL_NO_PREFIX);
+ ATTR_FROM = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::from), ALL_NO_PREFIX);
+ ATTR_FORM = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::form), ALL_NO_PREFIX);
+ ATTR_PROMPT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::prompt), ALL_NO_PREFIX);
+ ATTR_PRIMITIVEUNITS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::primitiveunits, nsHtml5Atoms::primitiveUnits), ALL_NO_PREFIX);
+ ATTR_SYMMETRIC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::symmetric), ALL_NO_PREFIX);
+ ATTR_STEMH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stemh), ALL_NO_PREFIX);
+ ATTR_STEMV = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stemv), ALL_NO_PREFIX);
+ ATTR_SEAMLESS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::seamless), ALL_NO_PREFIX);
+ ATTR_SUMMARY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::summary), ALL_NO_PREFIX);
+ ATTR_USEMAP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::usemap), ALL_NO_PREFIX);
+ ATTR_ZOOMANDPAN = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::zoomandpan, nsHtml5Atoms::zoomAndPan), ALL_NO_PREFIX);
+ ATTR_ASYNC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::async), ALL_NO_PREFIX);
+ ATTR_ALINK = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::alink), ALL_NO_PREFIX);
+ ATTR_IN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::in), ALL_NO_PREFIX);
+ ATTR_ICON = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::icon), ALL_NO_PREFIX);
+ ATTR_KERNELMATRIX = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::kernelmatrix, nsHtml5Atoms::kernelMatrix), ALL_NO_PREFIX);
+ ATTR_KERNING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::kerning), ALL_NO_PREFIX);
+ ATTR_KERNELUNITLENGTH = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::kernelunitlength, nsHtml5Atoms::kernelUnitLength), ALL_NO_PREFIX);
+ ATTR_ONUNLOAD = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onunload), ALL_NO_PREFIX);
+ ATTR_OPEN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::open), ALL_NO_PREFIX);
+ ATTR_ONINVALID = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::oninvalid), ALL_NO_PREFIX);
+ ATTR_ONEND = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onend), ALL_NO_PREFIX);
+ ATTR_ONINPUT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::oninput), ALL_NO_PREFIX);
+ ATTR_POINTER_EVENTS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::pointer_events), ALL_NO_PREFIX);
+ ATTR_POINTS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::points), ALL_NO_PREFIX);
+ ATTR_POINTSATX = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::pointsatx, nsHtml5Atoms::pointsAtX), ALL_NO_PREFIX);
+ ATTR_POINTSATY = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::pointsaty, nsHtml5Atoms::pointsAtY), ALL_NO_PREFIX);
+ ATTR_POINTSATZ = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::pointsatz, nsHtml5Atoms::pointsAtZ), ALL_NO_PREFIX);
+ ATTR_SPAN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::span), ALL_NO_PREFIX);
+ ATTR_STANDBY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::standby), ALL_NO_PREFIX);
+ ATTR_THINMATHSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::thinmathspace), ALL_NO_PREFIX);
+ ATTR_TRANSFORM = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::transform), ALL_NO_PREFIX);
+ ATTR_VLINK = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::vlink), ALL_NO_PREFIX);
+ ATTR_WHEN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::when), ALL_NO_PREFIX);
+ ATTR_XLINK_HREF = new nsHtml5AttributeName(XLINK_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xlink_href, nsHtml5Atoms::href), XLINK_PREFIX);
+ ATTR_XLINK_TITLE = new nsHtml5AttributeName(XLINK_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xlink_title, nsHtml5Atoms::title), XLINK_PREFIX);
+ ATTR_XLINK_ROLE = new nsHtml5AttributeName(XLINK_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xlink_role, nsHtml5Atoms::role), XLINK_PREFIX);
+ ATTR_XLINK_ARCROLE = new nsHtml5AttributeName(XLINK_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xlink_arcrole, nsHtml5Atoms::arcrole), XLINK_PREFIX);
+ ATTR_XMLNS_XLINK = new nsHtml5AttributeName(XMLNS_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xmlns_xlink, nsHtml5Atoms::xlink), XMLNS_PREFIX);
+ ATTR_XMLNS = new nsHtml5AttributeName(XMLNS_NS, SAME_LOCAL(nsHtml5Atoms::xmlns), ALL_NO_PREFIX);
+ ATTR_XLINK_TYPE = new nsHtml5AttributeName(XLINK_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xlink_type, nsHtml5Atoms::type), XLINK_PREFIX);
+ ATTR_XLINK_SHOW = new nsHtml5AttributeName(XLINK_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xlink_show, nsHtml5Atoms::show), XLINK_PREFIX);
+ ATTR_XLINK_ACTUATE = new nsHtml5AttributeName(XLINK_NS, COLONIFIED_LOCAL(nsHtml5Atoms::xlink_actuate, nsHtml5Atoms::actuate), XLINK_PREFIX);
+ ATTR_AUTOPLAY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::autoplay), ALL_NO_PREFIX);
+ ATTR_AUTOSUBMIT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::autosubmit), ALL_NO_PREFIX);
+ ATTR_AUTOCOMPLETE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::autocomplete), ALL_NO_PREFIX);
+ ATTR_AUTOFOCUS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::autofocus), ALL_NO_PREFIX);
+ ATTR_BGCOLOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::bgcolor), ALL_NO_PREFIX);
+ ATTR_COLOR_PROFILE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::color_profile), ALL_NO_PREFIX);
+ ATTR_COLOR_RENDERING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::color_rendering), ALL_NO_PREFIX);
+ ATTR_COLOR_INTERPOLATION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::color_interpolation), ALL_NO_PREFIX);
+ ATTR_COLOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::color), ALL_NO_PREFIX);
+ ATTR_COLOR_INTERPOLATION_FILTERS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::color_interpolation_filters), ALL_NO_PREFIX);
+ ATTR_ENCODING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::encoding), ALL_NO_PREFIX);
+ ATTR_EXPONENT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::exponent), ALL_NO_PREFIX);
+ ATTR_FLOOD_COLOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::flood_color), ALL_NO_PREFIX);
+ ATTR_FLOOD_OPACITY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::flood_opacity), ALL_NO_PREFIX);
+ ATTR_IDEOGRAPHIC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ideographic), ALL_NO_PREFIX);
+ ATTR_LQUOTE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::lquote), ALL_NO_PREFIX);
+ ATTR_PANOSE_1 = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::panose_1), ALL_NO_PREFIX);
+ ATTR_NUMOCTAVES = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::numoctaves, nsHtml5Atoms::numOctaves), ALL_NO_PREFIX);
+ ATTR_NOMODULE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::nomodule), ALL_NO_PREFIX);
+ ATTR_ONLOAD = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onload), ALL_NO_PREFIX);
+ ATTR_ONBOUNCE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onbounce), ALL_NO_PREFIX);
+ ATTR_ONCONTROLSELECT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::oncontrolselect), ALL_NO_PREFIX);
+ ATTR_ONROWSINSERTED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onrowsinserted), ALL_NO_PREFIX);
+ ATTR_ONMOUSEWHEEL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmousewheel), ALL_NO_PREFIX);
+ ATTR_ONROWENTER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onrowenter), ALL_NO_PREFIX);
+ ATTR_ONMOUSEENTER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmouseenter), ALL_NO_PREFIX);
+ ATTR_ONMOUSEOVER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmouseover), ALL_NO_PREFIX);
+ ATTR_ONFORMCHANGE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onformchange), ALL_NO_PREFIX);
+ ATTR_ONFOCUSIN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onfocusin), ALL_NO_PREFIX);
+ ATTR_ONROWEXIT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onrowexit), ALL_NO_PREFIX);
+ ATTR_ONMOVEEND = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmoveend), ALL_NO_PREFIX);
+ ATTR_ONCONTEXTMENU = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::oncontextmenu), ALL_NO_PREFIX);
+ ATTR_ONZOOM = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onzoom), ALL_NO_PREFIX);
+ ATTR_ONLOSECAPTURE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onlosecapture), ALL_NO_PREFIX);
+ ATTR_ONCOPY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::oncopy), ALL_NO_PREFIX);
+ ATTR_ONMOVESTART = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmovestart), ALL_NO_PREFIX);
+ ATTR_ONROWSDELETE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onrowsdelete), ALL_NO_PREFIX);
+ ATTR_ONMOUSELEAVE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmouseleave), ALL_NO_PREFIX);
+ ATTR_ONMOVE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmove), ALL_NO_PREFIX);
+ ATTR_ONMOUSEMOVE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmousemove), ALL_NO_PREFIX);
+ ATTR_ONMOUSEUP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmouseup), ALL_NO_PREFIX);
+ ATTR_ONFOCUS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onfocus), ALL_NO_PREFIX);
+ ATTR_ONMOUSEOUT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmouseout), ALL_NO_PREFIX);
+ ATTR_ONFORMINPUT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onforminput), ALL_NO_PREFIX);
+ ATTR_ONFOCUSOUT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onfocusout), ALL_NO_PREFIX);
+ ATTR_ONMOUSEDOWN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onmousedown), ALL_NO_PREFIX);
+ ATTR_TO = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::to), ALL_NO_PREFIX);
+ ATTR_RQUOTE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rquote), ALL_NO_PREFIX);
+ ATTR_STROKE_LINECAP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stroke_linecap), ALL_NO_PREFIX);
+ ATTR_SCROLLDELAY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::scrolldelay), ALL_NO_PREFIX);
+ ATTR_STROKE_DASHARRAY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stroke_dasharray), ALL_NO_PREFIX);
+ ATTR_STROKE_DASHOFFSET = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stroke_dashoffset), ALL_NO_PREFIX);
+ ATTR_STROKE_LINEJOIN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stroke_linejoin), ALL_NO_PREFIX);
+ ATTR_STROKE_MITERLIMIT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stroke_miterlimit), ALL_NO_PREFIX);
+ ATTR_STROKE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stroke), ALL_NO_PREFIX);
+ ATTR_SCROLLING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::scrolling), ALL_NO_PREFIX);
+ ATTR_STROKE_WIDTH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stroke_width), ALL_NO_PREFIX);
+ ATTR_STROKE_OPACITY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stroke_opacity), ALL_NO_PREFIX);
+ ATTR_COMPACT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::compact), ALL_NO_PREFIX);
+ ATTR_CLIP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::clip), ALL_NO_PREFIX);
+ ATTR_CLIP_RULE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::clip_rule), ALL_NO_PREFIX);
+ ATTR_CLIP_PATH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::clip_path), ALL_NO_PREFIX);
+ ATTR_CLIPPATHUNITS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::clippathunits, nsHtml5Atoms::clipPathUnits), ALL_NO_PREFIX);
+ ATTR_DISPLAY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::display), ALL_NO_PREFIX);
+ ATTR_DISPLAYSTYLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::displaystyle), ALL_NO_PREFIX);
+ ATTR_GLYPH_ORIENTATION_VERTICAL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::glyph_orientation_vertical), ALL_NO_PREFIX);
+ ATTR_GLYPH_ORIENTATION_HORIZONTAL = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::glyph_orientation_horizontal), ALL_NO_PREFIX);
+ ATTR_GLYPHREF = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::glyphref, nsHtml5Atoms::glyphRef), ALL_NO_PREFIX);
+ ATTR_GLYPH_NAME = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::glyph_name), ALL_NO_PREFIX);
+ ATTR_HTTP_EQUIV = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::http_equiv), ALL_NO_PREFIX);
+ ATTR_KEYPOINTS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::keypoints, nsHtml5Atoms::keyPoints), ALL_NO_PREFIX);
+ ATTR_LOOP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::loop), ALL_NO_PREFIX);
+ ATTR_PROPERTY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::property), ALL_NO_PREFIX);
+ ATTR_SCOPED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::scoped), ALL_NO_PREFIX);
+ ATTR_STEP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::step), ALL_NO_PREFIX);
+ ATTR_SHAPE_RENDERING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::shape_rendering), ALL_NO_PREFIX);
+ ATTR_SCOPE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::scope), ALL_NO_PREFIX);
+ ATTR_SHAPE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::shape), ALL_NO_PREFIX);
+ ATTR_SLOPE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::slope), ALL_NO_PREFIX);
+ ATTR_STOP_COLOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stop_color), ALL_NO_PREFIX);
+ ATTR_STOP_OPACITY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::stop_opacity), ALL_NO_PREFIX);
+ ATTR_TEMPLATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::template_), ALL_NO_PREFIX);
+ ATTR_WRAP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::wrap), ALL_NO_PREFIX);
+ ATTR_ABBR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::abbr), ALL_NO_PREFIX);
+ ATTR_ATTRIBUTENAME = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::attributename, nsHtml5Atoms::attributeName), ALL_NO_PREFIX);
+ ATTR_ATTRIBUTETYPE = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::attributetype, nsHtml5Atoms::attributeType), ALL_NO_PREFIX);
+ ATTR_CHAR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::char_), ALL_NO_PREFIX);
+ ATTR_COORDS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::coords), ALL_NO_PREFIX);
+ ATTR_CHAROFF = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::charoff), ALL_NO_PREFIX);
+ ATTR_CHARSET = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::charset), ALL_NO_PREFIX);
+ ATTR_MACROS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::macros), ALL_NO_PREFIX);
+ ATTR_NOWRAP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::nowrap), ALL_NO_PREFIX);
+ ATTR_NOHREF = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::nohref), ALL_NO_PREFIX);
+ ATTR_ONDRAG = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondrag), ALL_NO_PREFIX);
+ ATTR_ONDRAGENTER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondragenter), ALL_NO_PREFIX);
+ ATTR_ONDRAGOVER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondragover), ALL_NO_PREFIX);
+ ATTR_ONPROPERTYCHANGE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onpropertychange), ALL_NO_PREFIX);
+ ATTR_ONDRAGEND = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondragend), ALL_NO_PREFIX);
+ ATTR_ONDROP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondrop), ALL_NO_PREFIX);
+ ATTR_ONDRAGDROP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondragdrop), ALL_NO_PREFIX);
+ ATTR_OVERLINE_POSITION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::overline_position), ALL_NO_PREFIX);
+ ATTR_ONERROR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onerror), ALL_NO_PREFIX);
+ ATTR_OPERATOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::operator_), ALL_NO_PREFIX);
+ ATTR_OVERFLOW = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::overflow), ALL_NO_PREFIX);
+ ATTR_ONDRAGSTART = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondragstart), ALL_NO_PREFIX);
+ ATTR_ONERRORUPDATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onerrorupdate), ALL_NO_PREFIX);
+ ATTR_OVERLINE_THICKNESS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::overline_thickness), ALL_NO_PREFIX);
+ ATTR_ONDRAGLEAVE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ondragleave), ALL_NO_PREFIX);
+ ATTR_STARTOFFSET = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::startoffset, nsHtml5Atoms::startOffset), ALL_NO_PREFIX);
+ ATTR_START = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::start), ALL_NO_PREFIX);
+ ATTR_AXIS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::axis), ALL_NO_PREFIX);
+ ATTR_BIAS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::bias), ALL_NO_PREFIX);
+ ATTR_COLSPAN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::colspan), ALL_NO_PREFIX);
+ ATTR_CLASSID = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::classid), ALL_NO_PREFIX);
+ ATTR_CROSSORIGIN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::crossorigin), ALL_NO_PREFIX);
+ ATTR_COLS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::cols), ALL_NO_PREFIX);
+ ATTR_CURSOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::cursor), ALL_NO_PREFIX);
+ ATTR_CLOSURE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::closure), ALL_NO_PREFIX);
+ ATTR_CLOSE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::close), ALL_NO_PREFIX);
+ ATTR_CLASS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::class_), ALL_NO_PREFIX);
+ ATTR_IS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::is), ALL_NO_PREFIX);
+ ATTR_KEYSYSTEM = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::keysystem), ALL_NO_PREFIX);
+ ATTR_KEYSPLINES = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::keysplines, nsHtml5Atoms::keySplines), ALL_NO_PREFIX);
+ ATTR_LOWSRC = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::lowsrc), ALL_NO_PREFIX);
+ ATTR_MAXSIZE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::maxsize), ALL_NO_PREFIX);
+ ATTR_MINSIZE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::minsize), ALL_NO_PREFIX);
+ ATTR_OFFSET = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::offset), ALL_NO_PREFIX);
+ ATTR_PRESERVEALPHA = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::preservealpha, nsHtml5Atoms::preserveAlpha), ALL_NO_PREFIX);
+ ATTR_PRESERVEASPECTRATIO = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::preserveaspectratio, nsHtml5Atoms::preserveAspectRatio), ALL_NO_PREFIX);
+ ATTR_ROWSPAN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rowspan), ALL_NO_PREFIX);
+ ATTR_ROWSPACING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rowspacing), ALL_NO_PREFIX);
+ ATTR_ROWS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rows), ALL_NO_PREFIX);
+ ATTR_SRCSET = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::srcset), ALL_NO_PREFIX);
+ ATTR_SUBSCRIPTSHIFT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::subscriptshift), ALL_NO_PREFIX);
+ ATTR_VERSION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::version), ALL_NO_PREFIX);
+ ATTR_ALTTEXT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::alttext), ALL_NO_PREFIX);
+ ATTR_CONTENTEDITABLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::contenteditable), ALL_NO_PREFIX);
+ ATTR_CONTROLS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::controls), ALL_NO_PREFIX);
+ ATTR_CONTENT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::content), ALL_NO_PREFIX);
+ ATTR_CONTEXTMENU = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::contextmenu), ALL_NO_PREFIX);
+ ATTR_DEPTH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::depth), ALL_NO_PREFIX);
+ ATTR_ENCTYPE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::enctype), ALL_NO_PREFIX);
+ ATTR_FONT_STRETCH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::font_stretch), ALL_NO_PREFIX);
+ ATTR_FILTER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::filter), ALL_NO_PREFIX);
+ ATTR_FONTWEIGHT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::fontweight), ALL_NO_PREFIX);
+ ATTR_FONT_WEIGHT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::font_weight), ALL_NO_PREFIX);
+ ATTR_FONTSTYLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::fontstyle), ALL_NO_PREFIX);
+ ATTR_FONT_STYLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::font_style), ALL_NO_PREFIX);
+ ATTR_FONTFAMILY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::fontfamily), ALL_NO_PREFIX);
+ ATTR_FONT_FAMILY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::font_family), ALL_NO_PREFIX);
+ ATTR_FONT_VARIANT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::font_variant), ALL_NO_PREFIX);
+ ATTR_FONT_SIZE_ADJUST = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::font_size_adjust), ALL_NO_PREFIX);
+ ATTR_FILTERUNITS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::filterunits, nsHtml5Atoms::filterUnits), ALL_NO_PREFIX);
+ ATTR_FONTSIZE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::fontsize), ALL_NO_PREFIX);
+ ATTR_FONT_SIZE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::font_size), ALL_NO_PREFIX);
+ ATTR_KEYTIMES = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::keytimes, nsHtml5Atoms::keyTimes), ALL_NO_PREFIX);
+ ATTR_LETTER_SPACING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::letter_spacing), ALL_NO_PREFIX);
+ ATTR_LIST = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::list), ALL_NO_PREFIX);
+ ATTR_MULTIPLE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::multiple), ALL_NO_PREFIX);
+ ATTR_RT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rt), ALL_NO_PREFIX);
+ ATTR_ONSTOP = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onstop), ALL_NO_PREFIX);
+ ATTR_ONSTART = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onstart), ALL_NO_PREFIX);
+ ATTR_POSTER = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::poster), ALL_NO_PREFIX);
+ ATTR_PATTERNTRANSFORM = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::patterntransform, nsHtml5Atoms::patternTransform), ALL_NO_PREFIX);
+ ATTR_PATTERN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::pattern), ALL_NO_PREFIX);
+ ATTR_PATTERNUNITS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::patternunits, nsHtml5Atoms::patternUnits), ALL_NO_PREFIX);
+ ATTR_PATTERNCONTENTUNITS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::patterncontentunits, nsHtml5Atoms::patternContentUnits), ALL_NO_PREFIX);
+ ATTR_RESTART = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::restart), ALL_NO_PREFIX);
+ ATTR_STITCHTILES = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::stitchtiles, nsHtml5Atoms::stitchTiles), ALL_NO_PREFIX);
+ ATTR_SYSTEMLANGUAGE = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::systemlanguage, nsHtml5Atoms::systemLanguage), ALL_NO_PREFIX);
+ ATTR_TEXT_RENDERING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::text_rendering), ALL_NO_PREFIX);
+ ATTR_VERT_ORIGIN_X = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::vert_origin_x), ALL_NO_PREFIX);
+ ATTR_VERT_ADV_Y = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::vert_adv_y), ALL_NO_PREFIX);
+ ATTR_VERT_ORIGIN_Y = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::vert_origin_y), ALL_NO_PREFIX);
+ ATTR_TEXT_DECORATION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::text_decoration), ALL_NO_PREFIX);
+ ATTR_TEXT_ANCHOR = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::text_anchor), ALL_NO_PREFIX);
+ ATTR_TEXTLENGTH = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::textlength, nsHtml5Atoms::textLength), ALL_NO_PREFIX);
+ ATTR_TEXT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::text), ALL_NO_PREFIX);
+ ATTR_UNITS_PER_EM = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::units_per_em), ALL_NO_PREFIX);
+ ATTR_WRITING_MODE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::writing_mode), ALL_NO_PREFIX);
+ ATTR_WIDTHS = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::widths), ALL_NO_PREFIX);
+ ATTR_WIDTH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::width), ALL_NO_PREFIX);
+ ATTR_ACCUMULATE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::accumulate), ALL_NO_PREFIX);
+ ATTR_COLUMNSPAN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::columnspan), ALL_NO_PREFIX);
+ ATTR_COLUMNLINES = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::columnlines), ALL_NO_PREFIX);
+ ATTR_COLUMNALIGN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::columnalign), ALL_NO_PREFIX);
+ ATTR_COLUMNSPACING = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::columnspacing), ALL_NO_PREFIX);
+ ATTR_COLUMNWIDTH = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::columnwidth), ALL_NO_PREFIX);
+ ATTR_GROUPALIGN = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::groupalign), ALL_NO_PREFIX);
+ ATTR_INPUTMODE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::inputmode), ALL_NO_PREFIX);
+ ATTR_OCCURRENCE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::occurrence), ALL_NO_PREFIX);
+ ATTR_ONSUBMIT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::onsubmit), ALL_NO_PREFIX);
+ ATTR_ONCUT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::oncut), ALL_NO_PREFIX);
+ ATTR_REQUIRED = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::required), ALL_NO_PREFIX);
+ ATTR_REQUIREDFEATURES = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::requiredfeatures, nsHtml5Atoms::requiredFeatures), ALL_NO_PREFIX);
+ ATTR_RESULT = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::result), ALL_NO_PREFIX);
+ ATTR_REQUIREDEXTENSIONS = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::requiredextensions, nsHtml5Atoms::requiredExtensions), ALL_NO_PREFIX);
+ ATTR_VALUES = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::values), ALL_NO_PREFIX);
+ ATTR_VALUETYPE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::valuetype), ALL_NO_PREFIX);
+ ATTR_VALUE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::value), ALL_NO_PREFIX);
+ ATTR_ELEVATION = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::elevation), ALL_NO_PREFIX);
+ ATTR_VIEWTARGET = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::viewtarget, nsHtml5Atoms::viewTarget), ALL_NO_PREFIX);
+ ATTR_VIEWBOX = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::viewbox, nsHtml5Atoms::viewBox), ALL_NO_PREFIX);
+ ATTR_CX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::cx), ALL_NO_PREFIX);
+ ATTR_DX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::dx), ALL_NO_PREFIX);
+ ATTR_FX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::fx), ALL_NO_PREFIX);
+ ATTR_BBOX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::bbox), ALL_NO_PREFIX);
+ ATTR_RX = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::rx), ALL_NO_PREFIX);
+ ATTR_REFX = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::refx, nsHtml5Atoms::refX), ALL_NO_PREFIX);
+ ATTR_BY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::by), ALL_NO_PREFIX);
+ ATTR_CY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::cy), ALL_NO_PREFIX);
+ ATTR_DY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::dy), ALL_NO_PREFIX);
+ ATTR_FY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::fy), ALL_NO_PREFIX);
+ ATTR_RY = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::ry), ALL_NO_PREFIX);
+ ATTR_REFY = new nsHtml5AttributeName(ALL_NO_NS, SVG_DIFFERENT(nsHtml5Atoms::refy, nsHtml5Atoms::refY), ALL_NO_PREFIX);
+ ATTR_VERYTHINMATHSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::verythinmathspace), ALL_NO_PREFIX);
+ ATTR_VERYTHICKMATHSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::verythickmathspace), ALL_NO_PREFIX);
+ ATTR_VERYVERYTHINMATHSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::veryverythinmathspace), ALL_NO_PREFIX);
+ ATTR_VERYVERYTHICKMATHSPACE = new nsHtml5AttributeName(ALL_NO_NS, SAME_LOCAL(nsHtml5Atoms::veryverythickmathspace), ALL_NO_PREFIX);
+ ATTRIBUTE_NAMES = new nsHtml5AttributeName*[585];
+ ATTRIBUTE_NAMES[0] = ATTR_STEMV;
+ ATTRIBUTE_NAMES[1] = ATTR_REPEAT_START;
+ ATTRIBUTE_NAMES[2] = ATTR_NOWRAP;
+ ATTRIBUTE_NAMES[3] = ATTR_SRCDOC;
+ ATTRIBUTE_NAMES[4] = ATTR_BEGIN;
+ ATTRIBUTE_NAMES[5] = ATTR_ONFORMCHANGE;
+ ATTRIBUTE_NAMES[6] = ATTR_KEYTIMES;
+ ATTRIBUTE_NAMES[7] = ATTR_ARIA_SORT;
+ ATTRIBUTE_NAMES[8] = ATTR_ONSELECT;
+ ATTRIBUTE_NAMES[9] = ATTR_LANGUAGE;
+ ATTRIBUTE_NAMES[10] = ATTR_MARKERWIDTH;
+ ATTRIBUTE_NAMES[11] = ATTR_XMLNS_XLINK;
+ ATTRIBUTE_NAMES[12] = ATTR_CLIP;
+ ATTRIBUTE_NAMES[13] = ATTR_LOWSRC;
+ ATTRIBUTE_NAMES[14] = ATTR_COLUMNWIDTH;
+ ATTRIBUTE_NAMES[15] = ATTR_XML_SPACE;
+ ATTRIBUTE_NAMES[16] = ATTR_VSPACE;
+ ATTRIBUTE_NAMES[17] = ATTR_EDGE;
+ ATTRIBUTE_NAMES[18] = ATTR_ONDEACTIVATE;
+ ATTRIBUTE_NAMES[19] = ATTR_X_HEIGHT;
+ ATTRIBUTE_NAMES[20] = ATTR_LIGHTING_COLOR;
+ ATTRIBUTE_NAMES[21] = ATTR_SCRIPTLEVEL;
+ ATTRIBUTE_NAMES[22] = ATTR_SCALE;
+ ATTRIBUTE_NAMES[23] = ATTR_ONINPUT;
+ ATTRIBUTE_NAMES[24] = ATTR_EXPONENT;
+ ATTRIBUTE_NAMES[25] = ATTR_ONFORMINPUT;
+ ATTRIBUTE_NAMES[26] = ATTR_SHAPE_RENDERING;
+ ATTRIBUTE_NAMES[27] = ATTR_ONDRAGLEAVE;
+ ATTRIBUTE_NAMES[28] = ATTR_CONTEXTMENU;
+ ATTRIBUTE_NAMES[29] = ATTR_VERT_ORIGIN_X;
+ ATTRIBUTE_NAMES[30] = ATTR_CX;
+ ATTRIBUTE_NAMES[31] = ATTR_X;
+ ATTRIBUTE_NAMES[32] = ATTR_ARIA_HIDDEN;
+ ATTRIBUTE_NAMES[33] = ATTR_HSPACE;
+ ATTRIBUTE_NAMES[34] = ATTR_SPECULAREXPONENT;
+ ATTRIBUTE_NAMES[35] = ATTR_BASELINE;
+ ATTRIBUTE_NAMES[36] = ATTR_LABEL;
+ ATTRIBUTE_NAMES[37] = ATTR_ONHELP;
+ ATTRIBUTE_NAMES[38] = ATTR_ONRESIZE;
+ ATTRIBUTE_NAMES[39] = ATTR_SCHEME;
+ ATTRIBUTE_NAMES[40] = ATTR_XREF;
+ ATTRIBUTE_NAMES[41] = ATTR_ORIGIN;
+ ATTRIBUTE_NAMES[42] = ATTR_ONCHANGE;
+ ATTRIBUTE_NAMES[43] = ATTR_MEDIUMMATHSPACE;
+ ATTRIBUTE_NAMES[44] = ATTR_VISIBILITY;
+ ATTRIBUTE_NAMES[45] = ATTR_FILL_RULE;
+ ATTRIBUTE_NAMES[46] = ATTR_FRAME;
+ ATTRIBUTE_NAMES[47] = ATTR_ICON;
+ ATTRIBUTE_NAMES[48] = ATTR_THINMATHSPACE;
+ ATTRIBUTE_NAMES[49] = ATTR_AUTOFOCUS;
+ ATTRIBUTE_NAMES[50] = ATTR_ONLOAD;
+ ATTRIBUTE_NAMES[51] = ATTR_ONMOVESTART;
+ ATTRIBUTE_NAMES[52] = ATTR_STROKE_DASHOFFSET;
+ ATTRIBUTE_NAMES[53] = ATTR_GLYPHREF;
+ ATTRIBUTE_NAMES[54] = ATTR_ABBR;
+ ATTRIBUTE_NAMES[55] = ATTR_ONDRAGDROP;
+ ATTRIBUTE_NAMES[56] = ATTR_COLS;
+ ATTRIBUTE_NAMES[57] = ATTR_ROWS;
+ ATTRIBUTE_NAMES[58] = ATTR_FONT_STYLE;
+ ATTRIBUTE_NAMES[59] = ATTR_PATTERNTRANSFORM;
+ ATTRIBUTE_NAMES[60] = ATTR_WRITING_MODE;
+ ATTRIBUTE_NAMES[61] = ATTR_RESULT;
+ ATTRIBUTE_NAMES[62] = ATTR_DY;
+ ATTRIBUTE_NAMES[63] = ATTR_MIN;
+ ATTRIBUTE_NAMES[64] = ATTR_Y1;
+ ATTRIBUTE_NAMES[65] = ATTR_ARIA_CHECKED;
+ ATTRIBUTE_NAMES[66] = ATTR_ARIA_CONTROLS;
+ ATTRIBUTE_NAMES[67] = ATTR_DATAFORMATAS;
+ ATTRIBUTE_NAMES[68] = ATTR_ONPASTE;
+ ATTRIBUTE_NAMES[69] = ATTR_CALCMODE;
+ ATTRIBUTE_NAMES[70] = ATTR_ID;
+ ATTRIBUTE_NAMES[71] = ATTR_ACCENT_HEIGHT;
+ ATTRIBUTE_NAMES[72] = ATTR_DEFER;
+ ATTRIBUTE_NAMES[73] = ATTR_IRRELEVANT;
+ ATTRIBUTE_NAMES[74] = ATTR_NORESIZE;
+ ATTRIBUTE_NAMES[75] = ATTR_ONCELLCHANGE;
+ ATTRIBUTE_NAMES[76] = ATTR_ONBEFORECOPY;
+ ATTRIBUTE_NAMES[77] = ATTR_ONKEYUP;
+ ATTRIBUTE_NAMES[78] = ATTR_RULES;
+ ATTRIBUTE_NAMES[79] = ATTR_SPEED;
+ ATTRIBUTE_NAMES[80] = ATTR_TYPE;
+ ATTRIBUTE_NAMES[81] = ATTR_ONAFTERPRINT;
+ ATTRIBUTE_NAMES[82] = ATTR_DRAGGABLE;
+ ATTRIBUTE_NAMES[83] = ATTR_LENGTHADJUST;
+ ATTRIBUTE_NAMES[84] = ATTR_TARGETY;
+ ATTRIBUTE_NAMES[85] = ATTR_MATHVARIANT;
+ ATTRIBUTE_NAMES[86] = ATTR_ACTIONTYPE;
+ ATTRIBUTE_NAMES[87] = ATTR_HORIZ_ADV_X;
+ ATTRIBUTE_NAMES[88] = ATTR_ONFINISH;
+ ATTRIBUTE_NAMES[89] = ATTR_STRIKETHROUGH_THICKNESS;
+ ATTRIBUTE_NAMES[90] = ATTR_MARKERHEIGHT;
+ ATTRIBUTE_NAMES[91] = ATTR_AMPLITUDE;
+ ATTRIBUTE_NAMES[92] = ATTR_ONCLICK;
+ ATTRIBUTE_NAMES[93] = ATTR_V_ALPHABETIC;
+ ATTRIBUTE_NAMES[94] = ATTR_PROMPT;
+ ATTRIBUTE_NAMES[95] = ATTR_ZOOMANDPAN;
+ ATTRIBUTE_NAMES[96] = ATTR_ONUNLOAD;
+ ATTRIBUTE_NAMES[97] = ATTR_POINTSATY;
+ ATTRIBUTE_NAMES[98] = ATTR_XLINK_HREF;
+ ATTRIBUTE_NAMES[99] = ATTR_XLINK_ACTUATE;
+ ATTRIBUTE_NAMES[100] = ATTR_COLOR_INTERPOLATION;
+ ATTRIBUTE_NAMES[101] = ATTR_LQUOTE;
+ ATTRIBUTE_NAMES[102] = ATTR_ONMOUSEWHEEL;
+ ATTRIBUTE_NAMES[103] = ATTR_ONCONTEXTMENU;
+ ATTRIBUTE_NAMES[104] = ATTR_ONMOUSEMOVE;
+ ATTRIBUTE_NAMES[105] = ATTR_RQUOTE;
+ ATTRIBUTE_NAMES[106] = ATTR_SCROLLING;
+ ATTRIBUTE_NAMES[107] = ATTR_DISPLAY;
+ ATTRIBUTE_NAMES[108] = ATTR_LOOP;
+ ATTRIBUTE_NAMES[109] = ATTR_STOP_COLOR;
+ ATTRIBUTE_NAMES[110] = ATTR_COORDS;
+ ATTRIBUTE_NAMES[111] = ATTR_ONDRAGOVER;
+ ATTRIBUTE_NAMES[112] = ATTR_OVERFLOW;
+ ATTRIBUTE_NAMES[113] = ATTR_BIAS;
+ ATTRIBUTE_NAMES[114] = ATTR_CLASS;
+ ATTRIBUTE_NAMES[115] = ATTR_PRESERVEALPHA;
+ ATTRIBUTE_NAMES[116] = ATTR_ALTTEXT;
+ ATTRIBUTE_NAMES[117] = ATTR_FILTER;
+ ATTRIBUTE_NAMES[118] = ATTR_FONT_SIZE_ADJUST;
+ ATTRIBUTE_NAMES[119] = ATTR_RT;
+ ATTRIBUTE_NAMES[120] = ATTR_RESTART;
+ ATTRIBUTE_NAMES[121] = ATTR_TEXT_ANCHOR;
+ ATTRIBUTE_NAMES[122] = ATTR_COLUMNSPAN;
+ ATTRIBUTE_NAMES[123] = ATTR_ONSUBMIT;
+ ATTRIBUTE_NAMES[124] = ATTR_VALUE;
+ ATTRIBUTE_NAMES[125] = ATTR_RX;
+ ATTRIBUTE_NAMES[126] = ATTR_VERYTHINMATHSPACE;
+ ATTRIBUTE_NAMES[127] = ATTR_END;
+ ATTRIBUTE_NAMES[128] = ATTR_SRC;
+ ATTRIBUTE_NAMES[129] = ATTR_G1;
+ ATTRIBUTE_NAMES[130] = ATTR_X2;
+ ATTRIBUTE_NAMES[131] = ATTR_ARIA_VALUEMAX;
+ ATTRIBUTE_NAMES[132] = ATTR_ARIA_EXPANDED;
+ ATTRIBUTE_NAMES[133] = ATTR_ARIA_INVALID;
+ ATTRIBUTE_NAMES[134] = ATTR_ARIA_ACTIVEDESCENDANT;
+ ATTRIBUTE_NAMES[135] = ATTR_ARIA_LIVE;
+ ATTRIBUTE_NAMES[136] = ATTR_DATASRC;
+ ATTRIBUTE_NAMES[137] = ATTR_MOVABLELIMITS;
+ ATTRIBUTE_NAMES[138] = ATTR_ROTATE;
+ ATTRIBUTE_NAMES[139] = ATTR_ARABIC_FORM;
+ ATTRIBUTE_NAMES[140] = ATTR_ONSCROLL;
+ ATTRIBUTE_NAMES[141] = ATTR_UNICODE;
+ ATTRIBUTE_NAMES[142] = ATTR_HEADERS;
+ ATTRIBUTE_NAMES[143] = ATTR_WORD_SPACING;
+ ATTRIBUTE_NAMES[144] = ATTR_BEVELLED;
+ ATTRIBUTE_NAMES[145] = ATTR_CODEBASE;
+ ATTRIBUTE_NAMES[146] = ATTR_DIRECTION;
+ ATTRIBUTE_NAMES[147] = ATTR_HIDEFOCUS;
+ ATTRIBUTE_NAMES[148] = ATTR_INTEGRITY;
+ ATTRIBUTE_NAMES[149] = ATTR_MODE;
+ ATTRIBUTE_NAMES[150] = ATTR_ONREPEAT;
+ ATTRIBUTE_NAMES[151] = ATTR_OTHER;
+ ATTRIBUTE_NAMES[152] = ATTR_ONMESSAGE;
+ ATTRIBUTE_NAMES[153] = ATTR_ORIENT;
+ ATTRIBUTE_NAMES[154] = ATTR_ONBEFOREPASTE;
+ ATTRIBUTE_NAMES[155] = ATTR_ONBEFORDEACTIVATE;
+ ATTRIBUTE_NAMES[156] = ATTR_ONBEFORECUT;
+ ATTRIBUTE_NAMES[157] = ATTR_REPEAT_MAX;
+ ATTRIBUTE_NAMES[158] = ATTR_ROLE;
+ ATTRIBUTE_NAMES[159] = ATTR_REPEATDUR;
+ ATTRIBUTE_NAMES[160] = ATTR_SUPERSCRIPTSHIFT;
+ ATTRIBUTE_NAMES[161] = ATTR_SELECTION;
+ ATTRIBUTE_NAMES[162] = ATTR_UNDERLINE_POSITION;
+ ATTRIBUTE_NAMES[163] = ATTR_HREF;
+ ATTRIBUTE_NAMES[164] = ATTR_PROFILE;
+ ATTRIBUTE_NAMES[165] = ATTR_ALIGNMENT_BASELINE;
+ ATTRIBUTE_NAMES[166] = ATTR_HANGING;
+ ATTRIBUTE_NAMES[167] = ATTR_LARGEOP;
+ ATTRIBUTE_NAMES[168] = ATTR_MARGINWIDTH;
+ ATTRIBUTE_NAMES[169] = ATTR_TARGET;
+ ATTRIBUTE_NAMES[170] = ATTR_ARCHIVE;
+ ATTRIBUTE_NAMES[171] = ATTR_MATHBACKGROUND;
+ ATTRIBUTE_NAMES[172] = ATTR_MATHSIZE;
+ ATTRIBUTE_NAMES[173] = ATTR_PATH;
+ ATTRIBUTE_NAMES[174] = ATTR_ACTIVE;
+ ATTRIBUTE_NAMES[175] = ATTR_DIVISOR;
+ ATTRIBUTE_NAMES[176] = ATTR_HORIZ_ORIGIN_Y;
+ ATTRIBUTE_NAMES[177] = ATTR_MANIFEST;
+ ATTRIBUTE_NAMES[178] = ATTR_RADIOGROUP;
+ ATTRIBUTE_NAMES[179] = ATTR_STRING;
+ ATTRIBUTE_NAMES[180] = ATTR_TABINDEX;
+ ATTRIBUTE_NAMES[181] = ATTR_LINK;
+ ATTRIBUTE_NAMES[182] = ATTR_MASK;
+ ATTRIBUTE_NAMES[183] = ATTR_MARKERUNITS;
+ ATTRIBUTE_NAMES[184] = ATTR_CELLPADDING;
+ ATTRIBUTE_NAMES[185] = ATTR_FILL_OPACITY;
+ ATTRIBUTE_NAMES[186] = ATTR_REPLACE;
+ ATTRIBUTE_NAMES[187] = ATTR_TABLEVALUES;
+ ATTRIBUTE_NAMES[188] = ATTR_FORMAT;
+ ATTRIBUTE_NAMES[189] = ATTR_FROM;
+ ATTRIBUTE_NAMES[190] = ATTR_SYMMETRIC;
+ ATTRIBUTE_NAMES[191] = ATTR_SUMMARY;
+ ATTRIBUTE_NAMES[192] = ATTR_ALINK;
+ ATTRIBUTE_NAMES[193] = ATTR_KERNING;
+ ATTRIBUTE_NAMES[194] = ATTR_ONINVALID;
+ ATTRIBUTE_NAMES[195] = ATTR_POINTS;
+ ATTRIBUTE_NAMES[196] = ATTR_SPAN;
+ ATTRIBUTE_NAMES[197] = ATTR_VLINK;
+ ATTRIBUTE_NAMES[198] = ATTR_XLINK_ROLE;
+ ATTRIBUTE_NAMES[199] = ATTR_XLINK_TYPE;
+ ATTRIBUTE_NAMES[200] = ATTR_AUTOSUBMIT;
+ ATTRIBUTE_NAMES[201] = ATTR_COLOR_PROFILE;
+ ATTRIBUTE_NAMES[202] = ATTR_COLOR_INTERPOLATION_FILTERS;
+ ATTRIBUTE_NAMES[203] = ATTR_FLOOD_OPACITY;
+ ATTRIBUTE_NAMES[204] = ATTR_NUMOCTAVES;
+ ATTRIBUTE_NAMES[205] = ATTR_ONCONTROLSELECT;
+ ATTRIBUTE_NAMES[206] = ATTR_ONMOUSEENTER;
+ ATTRIBUTE_NAMES[207] = ATTR_ONROWEXIT;
+ ATTRIBUTE_NAMES[208] = ATTR_ONLOSECAPTURE;
+ ATTRIBUTE_NAMES[209] = ATTR_ONMOUSELEAVE;
+ ATTRIBUTE_NAMES[210] = ATTR_ONFOCUS;
+ ATTRIBUTE_NAMES[211] = ATTR_ONMOUSEDOWN;
+ ATTRIBUTE_NAMES[212] = ATTR_SCROLLDELAY;
+ ATTRIBUTE_NAMES[213] = ATTR_STROKE_MITERLIMIT;
+ ATTRIBUTE_NAMES[214] = ATTR_STROKE_OPACITY;
+ ATTRIBUTE_NAMES[215] = ATTR_CLIP_PATH;
+ ATTRIBUTE_NAMES[216] = ATTR_GLYPH_ORIENTATION_VERTICAL;
+ ATTRIBUTE_NAMES[217] = ATTR_HTTP_EQUIV;
+ ATTRIBUTE_NAMES[218] = ATTR_SCOPED;
+ ATTRIBUTE_NAMES[219] = ATTR_SHAPE;
+ ATTRIBUTE_NAMES[220] = ATTR_TEMPLATE;
+ ATTRIBUTE_NAMES[221] = ATTR_ATTRIBUTETYPE;
+ ATTRIBUTE_NAMES[222] = ATTR_CHARSET;
+ ATTRIBUTE_NAMES[223] = ATTR_ONDRAG;
+ ATTRIBUTE_NAMES[224] = ATTR_ONDRAGEND;
+ ATTRIBUTE_NAMES[225] = ATTR_ONERROR;
+ ATTRIBUTE_NAMES[226] = ATTR_ONERRORUPDATE;
+ ATTRIBUTE_NAMES[227] = ATTR_START;
+ ATTRIBUTE_NAMES[228] = ATTR_CLASSID;
+ ATTRIBUTE_NAMES[229] = ATTR_CLOSURE;
+ ATTRIBUTE_NAMES[230] = ATTR_KEYSYSTEM;
+ ATTRIBUTE_NAMES[231] = ATTR_MINSIZE;
+ ATTRIBUTE_NAMES[232] = ATTR_ROWSPAN;
+ ATTRIBUTE_NAMES[233] = ATTR_SUBSCRIPTSHIFT;
+ ATTRIBUTE_NAMES[234] = ATTR_CONTROLS;
+ ATTRIBUTE_NAMES[235] = ATTR_ENCTYPE;
+ ATTRIBUTE_NAMES[236] = ATTR_FONT_WEIGHT;
+ ATTRIBUTE_NAMES[237] = ATTR_FONT_FAMILY;
+ ATTRIBUTE_NAMES[238] = ATTR_FONTSIZE;
+ ATTRIBUTE_NAMES[239] = ATTR_LIST;
+ ATTRIBUTE_NAMES[240] = ATTR_ONSTART;
+ ATTRIBUTE_NAMES[241] = ATTR_PATTERNUNITS;
+ ATTRIBUTE_NAMES[242] = ATTR_SYSTEMLANGUAGE;
+ ATTRIBUTE_NAMES[243] = ATTR_VERT_ORIGIN_Y;
+ ATTRIBUTE_NAMES[244] = ATTR_TEXT;
+ ATTRIBUTE_NAMES[245] = ATTR_WIDTH;
+ ATTRIBUTE_NAMES[246] = ATTR_COLUMNALIGN;
+ ATTRIBUTE_NAMES[247] = ATTR_INPUTMODE;
+ ATTRIBUTE_NAMES[248] = ATTR_REQUIRED;
+ ATTRIBUTE_NAMES[249] = ATTR_VALUES;
+ ATTRIBUTE_NAMES[250] = ATTR_VIEWTARGET;
+ ATTRIBUTE_NAMES[251] = ATTR_FX;
+ ATTRIBUTE_NAMES[252] = ATTR_BY;
+ ATTRIBUTE_NAMES[253] = ATTR_RY;
+ ATTRIBUTE_NAMES[254] = ATTR_VERYVERYTHINMATHSPACE;
+ ATTRIBUTE_NAMES[255] = ATTR_DIR;
+ ATTRIBUTE_NAMES[256] = ATTR_IN2;
+ ATTRIBUTE_NAMES[257] = ATTR_REL;
+ ATTRIBUTE_NAMES[258] = ATTR_K;
+ ATTRIBUTE_NAMES[259] = ATTR_Z;
+ ATTRIBUTE_NAMES[260] = ATTR_U1;
+ ATTRIBUTE_NAMES[261] = ATTR_K2;
+ ATTRIBUTE_NAMES[262] = ATTR_K3;
+ ATTRIBUTE_NAMES[263] = ATTR_XML_BASE;
+ ATTRIBUTE_NAMES[264] = ATTR_ARIA_DESCRIBEDBY;
+ ATTRIBUTE_NAMES[265] = ATTR_ARIA_DROPEFFECT;
+ ATTRIBUTE_NAMES[266] = ATTR_ARIA_LEVEL;
+ ATTRIBUTE_NAMES[267] = ATTR_ARIA_POSINSET;
+ ATTRIBUTE_NAMES[268] = ATTR_ARIA_VALUEMIN;
+ ATTRIBUTE_NAMES[269] = ATTR_ARIA_READONLY;
+ ATTRIBUTE_NAMES[270] = ATTR_ARIA_DATATYPE;
+ ATTRIBUTE_NAMES[271] = ATTR_ARIA_FLOWTO;
+ ATTRIBUTE_NAMES[272] = ATTR_ARIA_SETSIZE;
+ ATTRIBUTE_NAMES[273] = ATTR_DATAFLD;
+ ATTRIBUTE_NAMES[274] = ATTR_EQUALCOLUMNS;
+ ATTRIBUTE_NAMES[275] = ATTR_LOCAL;
+ ATTRIBUTE_NAMES[276] = ATTR_ONDATASETCHANGED;
+ ATTRIBUTE_NAMES[277] = ATTR_RSPACE;
+ ATTRIBUTE_NAMES[278] = ATTR_SEPARATORS;
+ ATTRIBUTE_NAMES[279] = ATTR_XCHANNELSELECTOR;
+ ATTRIBUTE_NAMES[280] = ATTR_ONDBLCLICK;
+ ATTRIBUTE_NAMES[281] = ATTR_DESCENT;
+ ATTRIBUTE_NAMES[282] = ATTR_OPACITY;
+ ATTRIBUTE_NAMES[283] = ATTR_SPECIFICATION;
+ ATTRIBUTE_NAMES[284] = ATTR_UNICODE_RANGE;
+ ATTRIBUTE_NAMES[285] = ATTR_GRADIENTUNITS;
+ ATTRIBUTE_NAMES[286] = ATTR_RENDERING_INTENT;
+ ATTRIBUTE_NAMES[287] = ATTR_SANDBOX;
+ ATTRIBUTE_NAMES[288] = ATTR_ACCEPT_CHARSET;
+ ATTRIBUTE_NAMES[289] = ATTR_ASCENT;
+ ATTRIBUTE_NAMES[290] = ATTR_BASELINE_SHIFT;
+ ATTRIBUTE_NAMES[291] = ATTR_CODE;
+ ATTRIBUTE_NAMES[292] = ATTR_CITE;
+ ATTRIBUTE_NAMES[293] = ATTR_DATETIME;
+ ATTRIBUTE_NAMES[294] = ATTR_EDGEMODE;
+ ATTRIBUTE_NAMES[295] = ATTR_FACE;
+ ATTRIBUTE_NAMES[296] = ATTR_INDEX;
+ ATTRIBUTE_NAMES[297] = ATTR_INTERCEPT;
+ ATTRIBUTE_NAMES[298] = ATTR_LINEBREAK;
+ ATTRIBUTE_NAMES[299] = ATTR_LINETHICKNESS;
+ ATTRIBUTE_NAMES[300] = ATTR_NAME;
+ ATTRIBUTE_NAMES[301] = ATTR_ONBEFOREUNLOAD;
+ ATTRIBUTE_NAMES[302] = ATTR_OBJECT;
+ ATTRIBUTE_NAMES[303] = ATTR_ORDER;
+ ATTRIBUTE_NAMES[304] = ATTR_ONRESET;
+ ATTRIBUTE_NAMES[305] = ATTR_ONREADYSTATECHANGE;
+ ATTRIBUTE_NAMES[306] = ATTR_ONBEGIN;
+ ATTRIBUTE_NAMES[307] = ATTR_ONBEFOREPRINT;
+ ATTRIBUTE_NAMES[308] = ATTR_ORIENTATION;
+ ATTRIBUTE_NAMES[309] = ATTR_ONSELECTSTART;
+ ATTRIBUTE_NAMES[310] = ATTR_ONBEFOREUPDATE;
+ ATTRIBUTE_NAMES[311] = ATTR_ONBEFOREACTIVATE;
+ ATTRIBUTE_NAMES[312] = ATTR_ONKEYPRESS;
+ ATTRIBUTE_NAMES[313] = ATTR_ONBEFOREEDITFOCUS;
+ ATTRIBUTE_NAMES[314] = ATTR_ONKEYDOWN;
+ ATTRIBUTE_NAMES[315] = ATTR_REPEAT;
+ ATTRIBUTE_NAMES[316] = ATTR_REFERRERPOLICY;
+ ATTRIBUTE_NAMES[317] = ATTR_REPEAT_MIN;
+ ATTRIBUTE_NAMES[318] = ATTR_REPEATCOUNT;
+ ATTRIBUTE_NAMES[319] = ATTR_REPEAT_TEMPLATE;
+ ATTRIBUTE_NAMES[320] = ATTR_SELECTED;
+ ATTRIBUTE_NAMES[321] = ATTR_SIZES;
+ ATTRIBUTE_NAMES[322] = ATTR_STRETCHY;
+ ATTRIBUTE_NAMES[323] = ATTR_SPREADMETHOD;
+ ATTRIBUTE_NAMES[324] = ATTR_SIZE;
+ ATTRIBUTE_NAMES[325] = ATTR_UNSELECTABLE;
+ ATTRIBUTE_NAMES[326] = ATTR_UNDERLINE_THICKNESS;
+ ATTRIBUTE_NAMES[327] = ATTR_DIFFUSECONSTANT;
+ ATTRIBUTE_NAMES[328] = ATTR_HREFLANG;
+ ATTRIBUTE_NAMES[329] = ATTR_ONAFTERUPDATE;
+ ATTRIBUTE_NAMES[330] = ATTR_SURFACESCALE;
+ ATTRIBUTE_NAMES[331] = ATTR_ALIGN;
+ ATTRIBUTE_NAMES[332] = ATTR_ALIGNMENTSCOPE;
+ ATTRIBUTE_NAMES[333] = ATTR_HEIGHT;
+ ATTRIBUTE_NAMES[334] = ATTR_IMAGE_RENDERING;
+ ATTRIBUTE_NAMES[335] = ATTR_LANG;
+ ATTRIBUTE_NAMES[336] = ATTR_LONGDESC;
+ ATTRIBUTE_NAMES[337] = ATTR_MARGINHEIGHT;
+ ATTRIBUTE_NAMES[338] = ATTR_NARGS;
+ ATTRIBUTE_NAMES[339] = ATTR_PING;
+ ATTRIBUTE_NAMES[340] = ATTR_TARGETX;
+ ATTRIBUTE_NAMES[341] = ATTR_ALPHABETIC;
+ ATTRIBUTE_NAMES[342] = ATTR_HIGH;
+ ATTRIBUTE_NAMES[343] = ATTR_MATHEMATICAL;
+ ATTRIBUTE_NAMES[344] = ATTR_METHOD;
+ ATTRIBUTE_NAMES[345] = ATTR_MATHCOLOR;
+ ATTRIBUTE_NAMES[346] = ATTR_NOSHADE;
+ ATTRIBUTE_NAMES[347] = ATTR_PATHLENGTH;
+ ATTRIBUTE_NAMES[348] = ATTR_ALTIMG;
+ ATTRIBUTE_NAMES[349] = ATTR_ACTION;
+ ATTRIBUTE_NAMES[350] = ATTR_ADDITIVE;
+ ATTRIBUTE_NAMES[351] = ATTR_DOMINANT_BASELINE;
+ ATTRIBUTE_NAMES[352] = ATTR_DEFINITIONURL;
+ ATTRIBUTE_NAMES[353] = ATTR_HORIZ_ORIGIN_X;
+ ATTRIBUTE_NAMES[354] = ATTR_LIMITINGCONEANGLE;
+ ATTRIBUTE_NAMES[355] = ATTR_MEDIA;
+ ATTRIBUTE_NAMES[356] = ATTR_ONFILTERCHANGE;
+ ATTRIBUTE_NAMES[357] = ATTR_OPTIMUM;
+ ATTRIBUTE_NAMES[358] = ATTR_RADIUS;
+ ATTRIBUTE_NAMES[359] = ATTR_SCRIPTSIZEMULTIPLIER;
+ ATTRIBUTE_NAMES[360] = ATTR_STRIKETHROUGH_POSITION;
+ ATTRIBUTE_NAMES[361] = ATTR_SCRIPTMINSIZE;
+ ATTRIBUTE_NAMES[362] = ATTR_VALIGN;
+ ATTRIBUTE_NAMES[363] = ATTR_BACKGROUND;
+ ATTRIBUTE_NAMES[364] = ATTR_MARKER_MID;
+ ATTRIBUTE_NAMES[365] = ATTR_MARKER_END;
+ ATTRIBUTE_NAMES[366] = ATTR_MARKER_START;
+ ATTRIBUTE_NAMES[367] = ATTR_MASKUNITS;
+ ATTRIBUTE_NAMES[368] = ATTR_MASKCONTENTUNITS;
+ ATTRIBUTE_NAMES[369] = ATTR_CELLSPACING;
+ ATTRIBUTE_NAMES[370] = ATTR_DECLARE;
+ ATTRIBUTE_NAMES[371] = ATTR_FILL;
+ ATTRIBUTE_NAMES[372] = ATTR_MAXLENGTH;
+ ATTRIBUTE_NAMES[373] = ATTR_ONBLUR;
+ ATTRIBUTE_NAMES[374] = ATTR_ROWLINES;
+ ATTRIBUTE_NAMES[375] = ATTR_STYLE;
+ ATTRIBUTE_NAMES[376] = ATTR_TITLE;
+ ATTRIBUTE_NAMES[377] = ATTR_AZIMUTH;
+ ATTRIBUTE_NAMES[378] = ATTR_FRAMEBORDER;
+ ATTRIBUTE_NAMES[379] = ATTR_FRAMESPACING;
+ ATTRIBUTE_NAMES[380] = ATTR_FORM;
+ ATTRIBUTE_NAMES[381] = ATTR_PRIMITIVEUNITS;
+ ATTRIBUTE_NAMES[382] = ATTR_STEMH;
+ ATTRIBUTE_NAMES[383] = ATTR_SEAMLESS;
+ ATTRIBUTE_NAMES[384] = ATTR_USEMAP;
+ ATTRIBUTE_NAMES[385] = ATTR_ASYNC;
+ ATTRIBUTE_NAMES[386] = ATTR_IN;
+ ATTRIBUTE_NAMES[387] = ATTR_KERNELMATRIX;
+ ATTRIBUTE_NAMES[388] = ATTR_KERNELUNITLENGTH;
+ ATTRIBUTE_NAMES[389] = ATTR_OPEN;
+ ATTRIBUTE_NAMES[390] = ATTR_ONEND;
+ ATTRIBUTE_NAMES[391] = ATTR_POINTER_EVENTS;
+ ATTRIBUTE_NAMES[392] = ATTR_POINTSATX;
+ ATTRIBUTE_NAMES[393] = ATTR_POINTSATZ;
+ ATTRIBUTE_NAMES[394] = ATTR_STANDBY;
+ ATTRIBUTE_NAMES[395] = ATTR_TRANSFORM;
+ ATTRIBUTE_NAMES[396] = ATTR_WHEN;
+ ATTRIBUTE_NAMES[397] = ATTR_XLINK_TITLE;
+ ATTRIBUTE_NAMES[398] = ATTR_XLINK_ARCROLE;
+ ATTRIBUTE_NAMES[399] = ATTR_XMLNS;
+ ATTRIBUTE_NAMES[400] = ATTR_XLINK_SHOW;
+ ATTRIBUTE_NAMES[401] = ATTR_AUTOPLAY;
+ ATTRIBUTE_NAMES[402] = ATTR_AUTOCOMPLETE;
+ ATTRIBUTE_NAMES[403] = ATTR_BGCOLOR;
+ ATTRIBUTE_NAMES[404] = ATTR_COLOR_RENDERING;
+ ATTRIBUTE_NAMES[405] = ATTR_COLOR;
+ ATTRIBUTE_NAMES[406] = ATTR_ENCODING;
+ ATTRIBUTE_NAMES[407] = ATTR_FLOOD_COLOR;
+ ATTRIBUTE_NAMES[408] = ATTR_IDEOGRAPHIC;
+ ATTRIBUTE_NAMES[409] = ATTR_PANOSE_1;
+ ATTRIBUTE_NAMES[410] = ATTR_NOMODULE;
+ ATTRIBUTE_NAMES[411] = ATTR_ONBOUNCE;
+ ATTRIBUTE_NAMES[412] = ATTR_ONROWSINSERTED;
+ ATTRIBUTE_NAMES[413] = ATTR_ONROWENTER;
+ ATTRIBUTE_NAMES[414] = ATTR_ONMOUSEOVER;
+ ATTRIBUTE_NAMES[415] = ATTR_ONFOCUSIN;
+ ATTRIBUTE_NAMES[416] = ATTR_ONMOVEEND;
+ ATTRIBUTE_NAMES[417] = ATTR_ONZOOM;
+ ATTRIBUTE_NAMES[418] = ATTR_ONCOPY;
+ ATTRIBUTE_NAMES[419] = ATTR_ONROWSDELETE;
+ ATTRIBUTE_NAMES[420] = ATTR_ONMOVE;
+ ATTRIBUTE_NAMES[421] = ATTR_ONMOUSEUP;
+ ATTRIBUTE_NAMES[422] = ATTR_ONMOUSEOUT;
+ ATTRIBUTE_NAMES[423] = ATTR_ONFOCUSOUT;
+ ATTRIBUTE_NAMES[424] = ATTR_TO;
+ ATTRIBUTE_NAMES[425] = ATTR_STROKE_LINECAP;
+ ATTRIBUTE_NAMES[426] = ATTR_STROKE_DASHARRAY;
+ ATTRIBUTE_NAMES[427] = ATTR_STROKE_LINEJOIN;
+ ATTRIBUTE_NAMES[428] = ATTR_STROKE;
+ ATTRIBUTE_NAMES[429] = ATTR_STROKE_WIDTH;
+ ATTRIBUTE_NAMES[430] = ATTR_COMPACT;
+ ATTRIBUTE_NAMES[431] = ATTR_CLIP_RULE;
+ ATTRIBUTE_NAMES[432] = ATTR_CLIPPATHUNITS;
+ ATTRIBUTE_NAMES[433] = ATTR_DISPLAYSTYLE;
+ ATTRIBUTE_NAMES[434] = ATTR_GLYPH_ORIENTATION_HORIZONTAL;
+ ATTRIBUTE_NAMES[435] = ATTR_GLYPH_NAME;
+ ATTRIBUTE_NAMES[436] = ATTR_KEYPOINTS;
+ ATTRIBUTE_NAMES[437] = ATTR_PROPERTY;
+ ATTRIBUTE_NAMES[438] = ATTR_STEP;
+ ATTRIBUTE_NAMES[439] = ATTR_SCOPE;
+ ATTRIBUTE_NAMES[440] = ATTR_SLOPE;
+ ATTRIBUTE_NAMES[441] = ATTR_STOP_OPACITY;
+ ATTRIBUTE_NAMES[442] = ATTR_WRAP;
+ ATTRIBUTE_NAMES[443] = ATTR_ATTRIBUTENAME;
+ ATTRIBUTE_NAMES[444] = ATTR_CHAR;
+ ATTRIBUTE_NAMES[445] = ATTR_CHAROFF;
+ ATTRIBUTE_NAMES[446] = ATTR_MACROS;
+ ATTRIBUTE_NAMES[447] = ATTR_NOHREF;
+ ATTRIBUTE_NAMES[448] = ATTR_ONDRAGENTER;
+ ATTRIBUTE_NAMES[449] = ATTR_ONPROPERTYCHANGE;
+ ATTRIBUTE_NAMES[450] = ATTR_ONDROP;
+ ATTRIBUTE_NAMES[451] = ATTR_OVERLINE_POSITION;
+ ATTRIBUTE_NAMES[452] = ATTR_OPERATOR;
+ ATTRIBUTE_NAMES[453] = ATTR_ONDRAGSTART;
+ ATTRIBUTE_NAMES[454] = ATTR_OVERLINE_THICKNESS;
+ ATTRIBUTE_NAMES[455] = ATTR_STARTOFFSET;
+ ATTRIBUTE_NAMES[456] = ATTR_AXIS;
+ ATTRIBUTE_NAMES[457] = ATTR_COLSPAN;
+ ATTRIBUTE_NAMES[458] = ATTR_CROSSORIGIN;
+ ATTRIBUTE_NAMES[459] = ATTR_CURSOR;
+ ATTRIBUTE_NAMES[460] = ATTR_CLOSE;
+ ATTRIBUTE_NAMES[461] = ATTR_IS;
+ ATTRIBUTE_NAMES[462] = ATTR_KEYSPLINES;
+ ATTRIBUTE_NAMES[463] = ATTR_MAXSIZE;
+ ATTRIBUTE_NAMES[464] = ATTR_OFFSET;
+ ATTRIBUTE_NAMES[465] = ATTR_PRESERVEASPECTRATIO;
+ ATTRIBUTE_NAMES[466] = ATTR_ROWSPACING;
+ ATTRIBUTE_NAMES[467] = ATTR_SRCSET;
+ ATTRIBUTE_NAMES[468] = ATTR_VERSION;
+ ATTRIBUTE_NAMES[469] = ATTR_CONTENTEDITABLE;
+ ATTRIBUTE_NAMES[470] = ATTR_CONTENT;
+ ATTRIBUTE_NAMES[471] = ATTR_DEPTH;
+ ATTRIBUTE_NAMES[472] = ATTR_FONT_STRETCH;
+ ATTRIBUTE_NAMES[473] = ATTR_FONTWEIGHT;
+ ATTRIBUTE_NAMES[474] = ATTR_FONTSTYLE;
+ ATTRIBUTE_NAMES[475] = ATTR_FONTFAMILY;
+ ATTRIBUTE_NAMES[476] = ATTR_FONT_VARIANT;
+ ATTRIBUTE_NAMES[477] = ATTR_FILTERUNITS;
+ ATTRIBUTE_NAMES[478] = ATTR_FONT_SIZE;
+ ATTRIBUTE_NAMES[479] = ATTR_LETTER_SPACING;
+ ATTRIBUTE_NAMES[480] = ATTR_MULTIPLE;
+ ATTRIBUTE_NAMES[481] = ATTR_ONSTOP;
+ ATTRIBUTE_NAMES[482] = ATTR_POSTER;
+ ATTRIBUTE_NAMES[483] = ATTR_PATTERN;
+ ATTRIBUTE_NAMES[484] = ATTR_PATTERNCONTENTUNITS;
+ ATTRIBUTE_NAMES[485] = ATTR_STITCHTILES;
+ ATTRIBUTE_NAMES[486] = ATTR_TEXT_RENDERING;
+ ATTRIBUTE_NAMES[487] = ATTR_VERT_ADV_Y;
+ ATTRIBUTE_NAMES[488] = ATTR_TEXT_DECORATION;
+ ATTRIBUTE_NAMES[489] = ATTR_TEXTLENGTH;
+ ATTRIBUTE_NAMES[490] = ATTR_UNITS_PER_EM;
+ ATTRIBUTE_NAMES[491] = ATTR_WIDTHS;
+ ATTRIBUTE_NAMES[492] = ATTR_ACCUMULATE;
+ ATTRIBUTE_NAMES[493] = ATTR_COLUMNLINES;
+ ATTRIBUTE_NAMES[494] = ATTR_COLUMNSPACING;
+ ATTRIBUTE_NAMES[495] = ATTR_GROUPALIGN;
+ ATTRIBUTE_NAMES[496] = ATTR_OCCURRENCE;
+ ATTRIBUTE_NAMES[497] = ATTR_ONCUT;
+ ATTRIBUTE_NAMES[498] = ATTR_REQUIREDFEATURES;
+ ATTRIBUTE_NAMES[499] = ATTR_REQUIREDEXTENSIONS;
+ ATTRIBUTE_NAMES[500] = ATTR_VALUETYPE;
+ ATTRIBUTE_NAMES[501] = ATTR_ELEVATION;
+ ATTRIBUTE_NAMES[502] = ATTR_VIEWBOX;
+ ATTRIBUTE_NAMES[503] = ATTR_DX;
+ ATTRIBUTE_NAMES[504] = ATTR_BBOX;
+ ATTRIBUTE_NAMES[505] = ATTR_REFX;
+ ATTRIBUTE_NAMES[506] = ATTR_CY;
+ ATTRIBUTE_NAMES[507] = ATTR_FY;
+ ATTRIBUTE_NAMES[508] = ATTR_REFY;
+ ATTRIBUTE_NAMES[509] = ATTR_VERYTHICKMATHSPACE;
+ ATTRIBUTE_NAMES[510] = ATTR_VERYVERYTHICKMATHSPACE;
+ ATTRIBUTE_NAMES[511] = ATTR_ALT;
+ ATTRIBUTE_NAMES[512] = ATTR_DUR;
+ ATTRIBUTE_NAMES[513] = ATTR_FOR;
+ ATTRIBUTE_NAMES[514] = ATTR_LOW;
+ ATTRIBUTE_NAMES[515] = ATTR_MAX;
+ ATTRIBUTE_NAMES[516] = ATTR_REV;
+ ATTRIBUTE_NAMES[517] = ATTR_D;
+ ATTRIBUTE_NAMES[518] = ATTR_R;
+ ATTRIBUTE_NAMES[519] = ATTR_Y;
+ ATTRIBUTE_NAMES[520] = ATTR_CAP_HEIGHT;
+ ATTRIBUTE_NAMES[521] = ATTR_K1;
+ ATTRIBUTE_NAMES[522] = ATTR_X1;
+ ATTRIBUTE_NAMES[523] = ATTR_G2;
+ ATTRIBUTE_NAMES[524] = ATTR_U2;
+ ATTRIBUTE_NAMES[525] = ATTR_Y2;
+ ATTRIBUTE_NAMES[526] = ATTR_K4;
+ ATTRIBUTE_NAMES[527] = ATTR_XML_LANG;
+ ATTRIBUTE_NAMES[528] = ATTR_ARIA_GRAB;
+ ATTRIBUTE_NAMES[529] = ATTR_ARIA_LABELLEDBY;
+ ATTRIBUTE_NAMES[530] = ATTR_ARIA_DISABLED;
+ ATTRIBUTE_NAMES[531] = ATTR_ARIA_SELECTED;
+ ATTRIBUTE_NAMES[532] = ATTR_ARIA_REQUIRED;
+ ATTRIBUTE_NAMES[533] = ATTR_ARIA_PRESSED;
+ ATTRIBUTE_NAMES[534] = ATTR_ARIA_CHANNEL;
+ ATTRIBUTE_NAMES[535] = ATTR_ARIA_SECRET;
+ ATTRIBUTE_NAMES[536] = ATTR_ARIA_ATOMIC;
+ ATTRIBUTE_NAMES[537] = ATTR_ARIA_TEMPLATEID;
+ ATTRIBUTE_NAMES[538] = ATTR_ARIA_MULTISELECTABLE;
+ ATTRIBUTE_NAMES[539] = ATTR_ARIA_MULTILINE;
+ ATTRIBUTE_NAMES[540] = ATTR_ARIA_OWNS;
+ ATTRIBUTE_NAMES[541] = ATTR_ARIA_RELEVANT;
+ ATTRIBUTE_NAMES[542] = ATTR_ARIA_VALUENOW;
+ ATTRIBUTE_NAMES[543] = ATTR_ARIA_AUTOCOMPLETE;
+ ATTRIBUTE_NAMES[544] = ATTR_ARIA_BUSY;
+ ATTRIBUTE_NAMES[545] = ATTR_ARIA_HASPOPUP;
+ ATTRIBUTE_NAMES[546] = ATTR_CLEAR;
+ ATTRIBUTE_NAMES[547] = ATTR_DISABLED;
+ ATTRIBUTE_NAMES[548] = ATTR_DEFAULT;
+ ATTRIBUTE_NAMES[549] = ATTR_DATA;
+ ATTRIBUTE_NAMES[550] = ATTR_EQUALROWS;
+ ATTRIBUTE_NAMES[551] = ATTR_ISMAP;
+ ATTRIBUTE_NAMES[552] = ATTR_LSPACE;
+ ATTRIBUTE_NAMES[553] = ATTR_NOTATION;
+ ATTRIBUTE_NAMES[554] = ATTR_ONDATAAVAILABLE;
+ ATTRIBUTE_NAMES[555] = ATTR_ONDATASETCOMPLETE;
+ ATTRIBUTE_NAMES[556] = ATTR_ROWALIGN;
+ ATTRIBUTE_NAMES[557] = ATTR_SEPARATOR;
+ ATTRIBUTE_NAMES[558] = ATTR_V_MATHEMATICAL;
+ ATTRIBUTE_NAMES[559] = ATTR_V_HANGING;
+ ATTRIBUTE_NAMES[560] = ATTR_YCHANNELSELECTOR;
+ ATTRIBUTE_NAMES[561] = ATTR_ENABLE_BACKGROUND;
+ ATTRIBUTE_NAMES[562] = ATTR_ONABORT;
+ ATTRIBUTE_NAMES[563] = ATTR_CHECKED;
+ ATTRIBUTE_NAMES[564] = ATTR_FENCE;
+ ATTRIBUTE_NAMES[565] = ATTR_ONACTIVATE;
+ ATTRIBUTE_NAMES[566] = ATTR_SPACING;
+ ATTRIBUTE_NAMES[567] = ATTR_SPECULARCONSTANT;
+ ATTRIBUTE_NAMES[568] = ATTR_THICKMATHSPACE;
+ ATTRIBUTE_NAMES[569] = ATTR_UNICODE_BIDI;
+ ATTRIBUTE_NAMES[570] = ATTR_BORDER;
+ ATTRIBUTE_NAMES[571] = ATTR_GRADIENTTRANSFORM;
+ ATTRIBUTE_NAMES[572] = ATTR_HIDDEN;
+ ATTRIBUTE_NAMES[573] = ATTR_READONLY;
+ ATTRIBUTE_NAMES[574] = ATTR_SEED;
+ ATTRIBUTE_NAMES[575] = ATTR_STDDEVIATION;
+ ATTRIBUTE_NAMES[576] = ATTR_V_IDEOGRAPHIC;
+ ATTRIBUTE_NAMES[577] = ATTR_ACCENTUNDER;
+ ATTRIBUTE_NAMES[578] = ATTR_ACCESSKEY;
+ ATTRIBUTE_NAMES[579] = ATTR_ACCENT;
+ ATTRIBUTE_NAMES[580] = ATTR_ACCEPT;
+ ATTRIBUTE_NAMES[581] = ATTR_BASEFREQUENCY;
+ ATTRIBUTE_NAMES[582] = ATTR_BASEPROFILE;
+ ATTRIBUTE_NAMES[583] = ATTR_BASE;
+ ATTRIBUTE_NAMES[584] = ATTR_CODETYPE;
+}
+
+void
+nsHtml5AttributeName::releaseStatics()
+{
+ delete[] ALL_NO_NS;
+ delete[] XMLNS_NS;
+ delete[] XML_NS;
+ delete[] XLINK_NS;
+ delete[] ALL_NO_PREFIX;
+ delete[] XMLNS_PREFIX;
+ delete[] XLINK_PREFIX;
+ delete[] XML_PREFIX;
+ delete ATTR_ALT;
+ delete ATTR_DIR;
+ delete ATTR_DUR;
+ delete ATTR_END;
+ delete ATTR_FOR;
+ delete ATTR_IN2;
+ delete ATTR_LOW;
+ delete ATTR_MIN;
+ delete ATTR_MAX;
+ delete ATTR_REL;
+ delete ATTR_REV;
+ delete ATTR_SRC;
+ delete ATTR_D;
+ delete ATTR_K;
+ delete ATTR_R;
+ delete ATTR_X;
+ delete ATTR_Y;
+ delete ATTR_Z;
+ delete ATTR_CAP_HEIGHT;
+ delete ATTR_G1;
+ delete ATTR_K1;
+ delete ATTR_U1;
+ delete ATTR_X1;
+ delete ATTR_Y1;
+ delete ATTR_G2;
+ delete ATTR_K2;
+ delete ATTR_U2;
+ delete ATTR_X2;
+ delete ATTR_Y2;
+ delete ATTR_K3;
+ delete ATTR_K4;
+ delete ATTR_XML_SPACE;
+ delete ATTR_XML_LANG;
+ delete ATTR_XML_BASE;
+ delete ATTR_ARIA_GRAB;
+ delete ATTR_ARIA_VALUEMAX;
+ delete ATTR_ARIA_LABELLEDBY;
+ delete ATTR_ARIA_DESCRIBEDBY;
+ delete ATTR_ARIA_DISABLED;
+ delete ATTR_ARIA_CHECKED;
+ delete ATTR_ARIA_SELECTED;
+ delete ATTR_ARIA_DROPEFFECT;
+ delete ATTR_ARIA_REQUIRED;
+ delete ATTR_ARIA_EXPANDED;
+ delete ATTR_ARIA_PRESSED;
+ delete ATTR_ARIA_LEVEL;
+ delete ATTR_ARIA_CHANNEL;
+ delete ATTR_ARIA_HIDDEN;
+ delete ATTR_ARIA_SECRET;
+ delete ATTR_ARIA_POSINSET;
+ delete ATTR_ARIA_ATOMIC;
+ delete ATTR_ARIA_INVALID;
+ delete ATTR_ARIA_TEMPLATEID;
+ delete ATTR_ARIA_VALUEMIN;
+ delete ATTR_ARIA_MULTISELECTABLE;
+ delete ATTR_ARIA_CONTROLS;
+ delete ATTR_ARIA_MULTILINE;
+ delete ATTR_ARIA_READONLY;
+ delete ATTR_ARIA_OWNS;
+ delete ATTR_ARIA_ACTIVEDESCENDANT;
+ delete ATTR_ARIA_RELEVANT;
+ delete ATTR_ARIA_DATATYPE;
+ delete ATTR_ARIA_VALUENOW;
+ delete ATTR_ARIA_SORT;
+ delete ATTR_ARIA_AUTOCOMPLETE;
+ delete ATTR_ARIA_FLOWTO;
+ delete ATTR_ARIA_BUSY;
+ delete ATTR_ARIA_LIVE;
+ delete ATTR_ARIA_HASPOPUP;
+ delete ATTR_ARIA_SETSIZE;
+ delete ATTR_CLEAR;
+ delete ATTR_DATAFORMATAS;
+ delete ATTR_DISABLED;
+ delete ATTR_DATAFLD;
+ delete ATTR_DEFAULT;
+ delete ATTR_DATASRC;
+ delete ATTR_DATA;
+ delete ATTR_EQUALCOLUMNS;
+ delete ATTR_EQUALROWS;
+ delete ATTR_HSPACE;
+ delete ATTR_ISMAP;
+ delete ATTR_LOCAL;
+ delete ATTR_LSPACE;
+ delete ATTR_MOVABLELIMITS;
+ delete ATTR_NOTATION;
+ delete ATTR_ONDATASETCHANGED;
+ delete ATTR_ONDATAAVAILABLE;
+ delete ATTR_ONPASTE;
+ delete ATTR_ONDATASETCOMPLETE;
+ delete ATTR_RSPACE;
+ delete ATTR_ROWALIGN;
+ delete ATTR_ROTATE;
+ delete ATTR_SEPARATOR;
+ delete ATTR_SEPARATORS;
+ delete ATTR_V_MATHEMATICAL;
+ delete ATTR_VSPACE;
+ delete ATTR_V_HANGING;
+ delete ATTR_XCHANNELSELECTOR;
+ delete ATTR_YCHANNELSELECTOR;
+ delete ATTR_ARABIC_FORM;
+ delete ATTR_ENABLE_BACKGROUND;
+ delete ATTR_ONDBLCLICK;
+ delete ATTR_ONABORT;
+ delete ATTR_CALCMODE;
+ delete ATTR_CHECKED;
+ delete ATTR_DESCENT;
+ delete ATTR_FENCE;
+ delete ATTR_ONSCROLL;
+ delete ATTR_ONACTIVATE;
+ delete ATTR_OPACITY;
+ delete ATTR_SPACING;
+ delete ATTR_SPECULAREXPONENT;
+ delete ATTR_SPECULARCONSTANT;
+ delete ATTR_SPECIFICATION;
+ delete ATTR_THICKMATHSPACE;
+ delete ATTR_UNICODE;
+ delete ATTR_UNICODE_BIDI;
+ delete ATTR_UNICODE_RANGE;
+ delete ATTR_BORDER;
+ delete ATTR_ID;
+ delete ATTR_GRADIENTTRANSFORM;
+ delete ATTR_GRADIENTUNITS;
+ delete ATTR_HIDDEN;
+ delete ATTR_HEADERS;
+ delete ATTR_READONLY;
+ delete ATTR_RENDERING_INTENT;
+ delete ATTR_SEED;
+ delete ATTR_SRCDOC;
+ delete ATTR_STDDEVIATION;
+ delete ATTR_SANDBOX;
+ delete ATTR_V_IDEOGRAPHIC;
+ delete ATTR_WORD_SPACING;
+ delete ATTR_ACCENTUNDER;
+ delete ATTR_ACCEPT_CHARSET;
+ delete ATTR_ACCESSKEY;
+ delete ATTR_ACCENT_HEIGHT;
+ delete ATTR_ACCENT;
+ delete ATTR_ASCENT;
+ delete ATTR_ACCEPT;
+ delete ATTR_BEVELLED;
+ delete ATTR_BASEFREQUENCY;
+ delete ATTR_BASELINE_SHIFT;
+ delete ATTR_BASEPROFILE;
+ delete ATTR_BASELINE;
+ delete ATTR_BASE;
+ delete ATTR_CODE;
+ delete ATTR_CODETYPE;
+ delete ATTR_CODEBASE;
+ delete ATTR_CITE;
+ delete ATTR_DEFER;
+ delete ATTR_DATETIME;
+ delete ATTR_DIRECTION;
+ delete ATTR_EDGEMODE;
+ delete ATTR_EDGE;
+ delete ATTR_FACE;
+ delete ATTR_HIDEFOCUS;
+ delete ATTR_INDEX;
+ delete ATTR_IRRELEVANT;
+ delete ATTR_INTERCEPT;
+ delete ATTR_INTEGRITY;
+ delete ATTR_LINEBREAK;
+ delete ATTR_LABEL;
+ delete ATTR_LINETHICKNESS;
+ delete ATTR_MODE;
+ delete ATTR_NAME;
+ delete ATTR_NORESIZE;
+ delete ATTR_ONBEFOREUNLOAD;
+ delete ATTR_ONREPEAT;
+ delete ATTR_OBJECT;
+ delete ATTR_ONSELECT;
+ delete ATTR_ORDER;
+ delete ATTR_OTHER;
+ delete ATTR_ONRESET;
+ delete ATTR_ONCELLCHANGE;
+ delete ATTR_ONREADYSTATECHANGE;
+ delete ATTR_ONMESSAGE;
+ delete ATTR_ONBEGIN;
+ delete ATTR_ONHELP;
+ delete ATTR_ONBEFOREPRINT;
+ delete ATTR_ORIENT;
+ delete ATTR_ORIENTATION;
+ delete ATTR_ONBEFORECOPY;
+ delete ATTR_ONSELECTSTART;
+ delete ATTR_ONBEFOREPASTE;
+ delete ATTR_ONBEFOREUPDATE;
+ delete ATTR_ONDEACTIVATE;
+ delete ATTR_ONBEFOREACTIVATE;
+ delete ATTR_ONBEFORDEACTIVATE;
+ delete ATTR_ONKEYPRESS;
+ delete ATTR_ONKEYUP;
+ delete ATTR_ONBEFOREEDITFOCUS;
+ delete ATTR_ONBEFORECUT;
+ delete ATTR_ONKEYDOWN;
+ delete ATTR_ONRESIZE;
+ delete ATTR_REPEAT;
+ delete ATTR_REPEAT_MAX;
+ delete ATTR_REFERRERPOLICY;
+ delete ATTR_RULES;
+ delete ATTR_REPEAT_MIN;
+ delete ATTR_ROLE;
+ delete ATTR_REPEATCOUNT;
+ delete ATTR_REPEAT_START;
+ delete ATTR_REPEAT_TEMPLATE;
+ delete ATTR_REPEATDUR;
+ delete ATTR_SELECTED;
+ delete ATTR_SPEED;
+ delete ATTR_SIZES;
+ delete ATTR_SUPERSCRIPTSHIFT;
+ delete ATTR_STRETCHY;
+ delete ATTR_SCHEME;
+ delete ATTR_SPREADMETHOD;
+ delete ATTR_SELECTION;
+ delete ATTR_SIZE;
+ delete ATTR_TYPE;
+ delete ATTR_UNSELECTABLE;
+ delete ATTR_UNDERLINE_POSITION;
+ delete ATTR_UNDERLINE_THICKNESS;
+ delete ATTR_X_HEIGHT;
+ delete ATTR_DIFFUSECONSTANT;
+ delete ATTR_HREF;
+ delete ATTR_HREFLANG;
+ delete ATTR_ONAFTERPRINT;
+ delete ATTR_ONAFTERUPDATE;
+ delete ATTR_PROFILE;
+ delete ATTR_SURFACESCALE;
+ delete ATTR_XREF;
+ delete ATTR_ALIGN;
+ delete ATTR_ALIGNMENT_BASELINE;
+ delete ATTR_ALIGNMENTSCOPE;
+ delete ATTR_DRAGGABLE;
+ delete ATTR_HEIGHT;
+ delete ATTR_HANGING;
+ delete ATTR_IMAGE_RENDERING;
+ delete ATTR_LANGUAGE;
+ delete ATTR_LANG;
+ delete ATTR_LARGEOP;
+ delete ATTR_LONGDESC;
+ delete ATTR_LENGTHADJUST;
+ delete ATTR_MARGINHEIGHT;
+ delete ATTR_MARGINWIDTH;
+ delete ATTR_NARGS;
+ delete ATTR_ORIGIN;
+ delete ATTR_PING;
+ delete ATTR_TARGET;
+ delete ATTR_TARGETX;
+ delete ATTR_TARGETY;
+ delete ATTR_ALPHABETIC;
+ delete ATTR_ARCHIVE;
+ delete ATTR_HIGH;
+ delete ATTR_LIGHTING_COLOR;
+ delete ATTR_MATHEMATICAL;
+ delete ATTR_MATHBACKGROUND;
+ delete ATTR_METHOD;
+ delete ATTR_MATHVARIANT;
+ delete ATTR_MATHCOLOR;
+ delete ATTR_MATHSIZE;
+ delete ATTR_NOSHADE;
+ delete ATTR_ONCHANGE;
+ delete ATTR_PATHLENGTH;
+ delete ATTR_PATH;
+ delete ATTR_ALTIMG;
+ delete ATTR_ACTIONTYPE;
+ delete ATTR_ACTION;
+ delete ATTR_ACTIVE;
+ delete ATTR_ADDITIVE;
+ delete ATTR_BEGIN;
+ delete ATTR_DOMINANT_BASELINE;
+ delete ATTR_DIVISOR;
+ delete ATTR_DEFINITIONURL;
+ delete ATTR_HORIZ_ADV_X;
+ delete ATTR_HORIZ_ORIGIN_X;
+ delete ATTR_HORIZ_ORIGIN_Y;
+ delete ATTR_LIMITINGCONEANGLE;
+ delete ATTR_MEDIUMMATHSPACE;
+ delete ATTR_MEDIA;
+ delete ATTR_MANIFEST;
+ delete ATTR_ONFILTERCHANGE;
+ delete ATTR_ONFINISH;
+ delete ATTR_OPTIMUM;
+ delete ATTR_RADIOGROUP;
+ delete ATTR_RADIUS;
+ delete ATTR_SCRIPTLEVEL;
+ delete ATTR_SCRIPTSIZEMULTIPLIER;
+ delete ATTR_STRING;
+ delete ATTR_STRIKETHROUGH_POSITION;
+ delete ATTR_STRIKETHROUGH_THICKNESS;
+ delete ATTR_SCRIPTMINSIZE;
+ delete ATTR_TABINDEX;
+ delete ATTR_VALIGN;
+ delete ATTR_VISIBILITY;
+ delete ATTR_BACKGROUND;
+ delete ATTR_LINK;
+ delete ATTR_MARKER_MID;
+ delete ATTR_MARKERHEIGHT;
+ delete ATTR_MARKER_END;
+ delete ATTR_MASK;
+ delete ATTR_MARKER_START;
+ delete ATTR_MARKERWIDTH;
+ delete ATTR_MASKUNITS;
+ delete ATTR_MARKERUNITS;
+ delete ATTR_MASKCONTENTUNITS;
+ delete ATTR_AMPLITUDE;
+ delete ATTR_CELLSPACING;
+ delete ATTR_CELLPADDING;
+ delete ATTR_DECLARE;
+ delete ATTR_FILL_RULE;
+ delete ATTR_FILL;
+ delete ATTR_FILL_OPACITY;
+ delete ATTR_MAXLENGTH;
+ delete ATTR_ONCLICK;
+ delete ATTR_ONBLUR;
+ delete ATTR_REPLACE;
+ delete ATTR_ROWLINES;
+ delete ATTR_SCALE;
+ delete ATTR_STYLE;
+ delete ATTR_TABLEVALUES;
+ delete ATTR_TITLE;
+ delete ATTR_V_ALPHABETIC;
+ delete ATTR_AZIMUTH;
+ delete ATTR_FORMAT;
+ delete ATTR_FRAMEBORDER;
+ delete ATTR_FRAME;
+ delete ATTR_FRAMESPACING;
+ delete ATTR_FROM;
+ delete ATTR_FORM;
+ delete ATTR_PROMPT;
+ delete ATTR_PRIMITIVEUNITS;
+ delete ATTR_SYMMETRIC;
+ delete ATTR_STEMH;
+ delete ATTR_STEMV;
+ delete ATTR_SEAMLESS;
+ delete ATTR_SUMMARY;
+ delete ATTR_USEMAP;
+ delete ATTR_ZOOMANDPAN;
+ delete ATTR_ASYNC;
+ delete ATTR_ALINK;
+ delete ATTR_IN;
+ delete ATTR_ICON;
+ delete ATTR_KERNELMATRIX;
+ delete ATTR_KERNING;
+ delete ATTR_KERNELUNITLENGTH;
+ delete ATTR_ONUNLOAD;
+ delete ATTR_OPEN;
+ delete ATTR_ONINVALID;
+ delete ATTR_ONEND;
+ delete ATTR_ONINPUT;
+ delete ATTR_POINTER_EVENTS;
+ delete ATTR_POINTS;
+ delete ATTR_POINTSATX;
+ delete ATTR_POINTSATY;
+ delete ATTR_POINTSATZ;
+ delete ATTR_SPAN;
+ delete ATTR_STANDBY;
+ delete ATTR_THINMATHSPACE;
+ delete ATTR_TRANSFORM;
+ delete ATTR_VLINK;
+ delete ATTR_WHEN;
+ delete ATTR_XLINK_HREF;
+ delete ATTR_XLINK_TITLE;
+ delete ATTR_XLINK_ROLE;
+ delete ATTR_XLINK_ARCROLE;
+ delete ATTR_XMLNS_XLINK;
+ delete ATTR_XMLNS;
+ delete ATTR_XLINK_TYPE;
+ delete ATTR_XLINK_SHOW;
+ delete ATTR_XLINK_ACTUATE;
+ delete ATTR_AUTOPLAY;
+ delete ATTR_AUTOSUBMIT;
+ delete ATTR_AUTOCOMPLETE;
+ delete ATTR_AUTOFOCUS;
+ delete ATTR_BGCOLOR;
+ delete ATTR_COLOR_PROFILE;
+ delete ATTR_COLOR_RENDERING;
+ delete ATTR_COLOR_INTERPOLATION;
+ delete ATTR_COLOR;
+ delete ATTR_COLOR_INTERPOLATION_FILTERS;
+ delete ATTR_ENCODING;
+ delete ATTR_EXPONENT;
+ delete ATTR_FLOOD_COLOR;
+ delete ATTR_FLOOD_OPACITY;
+ delete ATTR_IDEOGRAPHIC;
+ delete ATTR_LQUOTE;
+ delete ATTR_PANOSE_1;
+ delete ATTR_NUMOCTAVES;
+ delete ATTR_NOMODULE;
+ delete ATTR_ONLOAD;
+ delete ATTR_ONBOUNCE;
+ delete ATTR_ONCONTROLSELECT;
+ delete ATTR_ONROWSINSERTED;
+ delete ATTR_ONMOUSEWHEEL;
+ delete ATTR_ONROWENTER;
+ delete ATTR_ONMOUSEENTER;
+ delete ATTR_ONMOUSEOVER;
+ delete ATTR_ONFORMCHANGE;
+ delete ATTR_ONFOCUSIN;
+ delete ATTR_ONROWEXIT;
+ delete ATTR_ONMOVEEND;
+ delete ATTR_ONCONTEXTMENU;
+ delete ATTR_ONZOOM;
+ delete ATTR_ONLOSECAPTURE;
+ delete ATTR_ONCOPY;
+ delete ATTR_ONMOVESTART;
+ delete ATTR_ONROWSDELETE;
+ delete ATTR_ONMOUSELEAVE;
+ delete ATTR_ONMOVE;
+ delete ATTR_ONMOUSEMOVE;
+ delete ATTR_ONMOUSEUP;
+ delete ATTR_ONFOCUS;
+ delete ATTR_ONMOUSEOUT;
+ delete ATTR_ONFORMINPUT;
+ delete ATTR_ONFOCUSOUT;
+ delete ATTR_ONMOUSEDOWN;
+ delete ATTR_TO;
+ delete ATTR_RQUOTE;
+ delete ATTR_STROKE_LINECAP;
+ delete ATTR_SCROLLDELAY;
+ delete ATTR_STROKE_DASHARRAY;
+ delete ATTR_STROKE_DASHOFFSET;
+ delete ATTR_STROKE_LINEJOIN;
+ delete ATTR_STROKE_MITERLIMIT;
+ delete ATTR_STROKE;
+ delete ATTR_SCROLLING;
+ delete ATTR_STROKE_WIDTH;
+ delete ATTR_STROKE_OPACITY;
+ delete ATTR_COMPACT;
+ delete ATTR_CLIP;
+ delete ATTR_CLIP_RULE;
+ delete ATTR_CLIP_PATH;
+ delete ATTR_CLIPPATHUNITS;
+ delete ATTR_DISPLAY;
+ delete ATTR_DISPLAYSTYLE;
+ delete ATTR_GLYPH_ORIENTATION_VERTICAL;
+ delete ATTR_GLYPH_ORIENTATION_HORIZONTAL;
+ delete ATTR_GLYPHREF;
+ delete ATTR_GLYPH_NAME;
+ delete ATTR_HTTP_EQUIV;
+ delete ATTR_KEYPOINTS;
+ delete ATTR_LOOP;
+ delete ATTR_PROPERTY;
+ delete ATTR_SCOPED;
+ delete ATTR_STEP;
+ delete ATTR_SHAPE_RENDERING;
+ delete ATTR_SCOPE;
+ delete ATTR_SHAPE;
+ delete ATTR_SLOPE;
+ delete ATTR_STOP_COLOR;
+ delete ATTR_STOP_OPACITY;
+ delete ATTR_TEMPLATE;
+ delete ATTR_WRAP;
+ delete ATTR_ABBR;
+ delete ATTR_ATTRIBUTENAME;
+ delete ATTR_ATTRIBUTETYPE;
+ delete ATTR_CHAR;
+ delete ATTR_COORDS;
+ delete ATTR_CHAROFF;
+ delete ATTR_CHARSET;
+ delete ATTR_MACROS;
+ delete ATTR_NOWRAP;
+ delete ATTR_NOHREF;
+ delete ATTR_ONDRAG;
+ delete ATTR_ONDRAGENTER;
+ delete ATTR_ONDRAGOVER;
+ delete ATTR_ONPROPERTYCHANGE;
+ delete ATTR_ONDRAGEND;
+ delete ATTR_ONDROP;
+ delete ATTR_ONDRAGDROP;
+ delete ATTR_OVERLINE_POSITION;
+ delete ATTR_ONERROR;
+ delete ATTR_OPERATOR;
+ delete ATTR_OVERFLOW;
+ delete ATTR_ONDRAGSTART;
+ delete ATTR_ONERRORUPDATE;
+ delete ATTR_OVERLINE_THICKNESS;
+ delete ATTR_ONDRAGLEAVE;
+ delete ATTR_STARTOFFSET;
+ delete ATTR_START;
+ delete ATTR_AXIS;
+ delete ATTR_BIAS;
+ delete ATTR_COLSPAN;
+ delete ATTR_CLASSID;
+ delete ATTR_CROSSORIGIN;
+ delete ATTR_COLS;
+ delete ATTR_CURSOR;
+ delete ATTR_CLOSURE;
+ delete ATTR_CLOSE;
+ delete ATTR_CLASS;
+ delete ATTR_IS;
+ delete ATTR_KEYSYSTEM;
+ delete ATTR_KEYSPLINES;
+ delete ATTR_LOWSRC;
+ delete ATTR_MAXSIZE;
+ delete ATTR_MINSIZE;
+ delete ATTR_OFFSET;
+ delete ATTR_PRESERVEALPHA;
+ delete ATTR_PRESERVEASPECTRATIO;
+ delete ATTR_ROWSPAN;
+ delete ATTR_ROWSPACING;
+ delete ATTR_ROWS;
+ delete ATTR_SRCSET;
+ delete ATTR_SUBSCRIPTSHIFT;
+ delete ATTR_VERSION;
+ delete ATTR_ALTTEXT;
+ delete ATTR_CONTENTEDITABLE;
+ delete ATTR_CONTROLS;
+ delete ATTR_CONTENT;
+ delete ATTR_CONTEXTMENU;
+ delete ATTR_DEPTH;
+ delete ATTR_ENCTYPE;
+ delete ATTR_FONT_STRETCH;
+ delete ATTR_FILTER;
+ delete ATTR_FONTWEIGHT;
+ delete ATTR_FONT_WEIGHT;
+ delete ATTR_FONTSTYLE;
+ delete ATTR_FONT_STYLE;
+ delete ATTR_FONTFAMILY;
+ delete ATTR_FONT_FAMILY;
+ delete ATTR_FONT_VARIANT;
+ delete ATTR_FONT_SIZE_ADJUST;
+ delete ATTR_FILTERUNITS;
+ delete ATTR_FONTSIZE;
+ delete ATTR_FONT_SIZE;
+ delete ATTR_KEYTIMES;
+ delete ATTR_LETTER_SPACING;
+ delete ATTR_LIST;
+ delete ATTR_MULTIPLE;
+ delete ATTR_RT;
+ delete ATTR_ONSTOP;
+ delete ATTR_ONSTART;
+ delete ATTR_POSTER;
+ delete ATTR_PATTERNTRANSFORM;
+ delete ATTR_PATTERN;
+ delete ATTR_PATTERNUNITS;
+ delete ATTR_PATTERNCONTENTUNITS;
+ delete ATTR_RESTART;
+ delete ATTR_STITCHTILES;
+ delete ATTR_SYSTEMLANGUAGE;
+ delete ATTR_TEXT_RENDERING;
+ delete ATTR_VERT_ORIGIN_X;
+ delete ATTR_VERT_ADV_Y;
+ delete ATTR_VERT_ORIGIN_Y;
+ delete ATTR_TEXT_DECORATION;
+ delete ATTR_TEXT_ANCHOR;
+ delete ATTR_TEXTLENGTH;
+ delete ATTR_TEXT;
+ delete ATTR_UNITS_PER_EM;
+ delete ATTR_WRITING_MODE;
+ delete ATTR_WIDTHS;
+ delete ATTR_WIDTH;
+ delete ATTR_ACCUMULATE;
+ delete ATTR_COLUMNSPAN;
+ delete ATTR_COLUMNLINES;
+ delete ATTR_COLUMNALIGN;
+ delete ATTR_COLUMNSPACING;
+ delete ATTR_COLUMNWIDTH;
+ delete ATTR_GROUPALIGN;
+ delete ATTR_INPUTMODE;
+ delete ATTR_OCCURRENCE;
+ delete ATTR_ONSUBMIT;
+ delete ATTR_ONCUT;
+ delete ATTR_REQUIRED;
+ delete ATTR_REQUIREDFEATURES;
+ delete ATTR_RESULT;
+ delete ATTR_REQUIREDEXTENSIONS;
+ delete ATTR_VALUES;
+ delete ATTR_VALUETYPE;
+ delete ATTR_VALUE;
+ delete ATTR_ELEVATION;
+ delete ATTR_VIEWTARGET;
+ delete ATTR_VIEWBOX;
+ delete ATTR_CX;
+ delete ATTR_DX;
+ delete ATTR_FX;
+ delete ATTR_BBOX;
+ delete ATTR_RX;
+ delete ATTR_REFX;
+ delete ATTR_BY;
+ delete ATTR_CY;
+ delete ATTR_DY;
+ delete ATTR_FY;
+ delete ATTR_RY;
+ delete ATTR_REFY;
+ delete ATTR_VERYTHINMATHSPACE;
+ delete ATTR_VERYTHICKMATHSPACE;
+ delete ATTR_VERYVERYTHINMATHSPACE;
+ delete ATTR_VERYVERYTHICKMATHSPACE;
+ delete[] ATTRIBUTE_NAMES;
+}
+
+
diff --git a/components/htmlfive/nsHtml5AttributeName.h b/components/htmlfive/nsHtml5AttributeName.h
new file mode 100644
index 000000000..8bb706408
--- /dev/null
+++ b/components/htmlfive/nsHtml5AttributeName.h
@@ -0,0 +1,776 @@
+/*
+ * Copyright (c) 2008-2017 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit AttributeName.java instead and regenerate.
+ */
+
+#ifndef nsHtml5AttributeName_h
+#define nsHtml5AttributeName_h
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+class nsHtml5StreamParser;
+
+class nsHtml5ElementName;
+class nsHtml5Tokenizer;
+class nsHtml5TreeBuilder;
+class nsHtml5MetaScanner;
+class nsHtml5UTF16Buffer;
+class nsHtml5StateSnapshot;
+class nsHtml5Portability;
+
+
+class nsHtml5AttributeName
+{
+ public:
+ static int32_t* ALL_NO_NS;
+ private:
+ static int32_t* XMLNS_NS;
+ static int32_t* XML_NS;
+ static int32_t* XLINK_NS;
+ public:
+ static nsIAtom** ALL_NO_PREFIX;
+ private:
+ static nsIAtom** XMLNS_PREFIX;
+ static nsIAtom** XLINK_PREFIX;
+ static nsIAtom** XML_PREFIX;
+ static nsIAtom** SVG_DIFFERENT(nsIAtom* name, nsIAtom* camel);
+ static nsIAtom** MATH_DIFFERENT(nsIAtom* name, nsIAtom* camel);
+ static nsIAtom** COLONIFIED_LOCAL(nsIAtom* name, nsIAtom* suffix);
+ public:
+ static nsIAtom** SAME_LOCAL(nsIAtom* name);
+ inline static int32_t levelOrderBinarySearch(jArray<int32_t,int32_t> data, int32_t key)
+ {
+ int32_t n = data.length;
+ int32_t i = 0;
+ while (i < n) {
+ int32_t val = data[i];
+ if (val < key) {
+ i = 2 * i + 2;
+ } else if (val > key) {
+ i = 2 * i + 1;
+ } else {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ inline static nsHtml5AttributeName* nameByBuffer(char16_t* buf, int32_t offset, int32_t length, nsHtml5AtomTable* interner)
+ {
+ uint32_t hash = nsHtml5AttributeName::bufToHash(buf, length);
+ jArray<int32_t,int32_t> hashes;
+ hashes = nsHtml5AttributeName::ATTRIBUTE_HASHES;
+ int32_t index = levelOrderBinarySearch(hashes, hash);
+ if (index < 0) {
+ return nullptr;
+ }
+ nsHtml5AttributeName* attributeName = nsHtml5AttributeName::ATTRIBUTE_NAMES[index];
+ nsIAtom* name = attributeName->getLocal(0);
+ if (!nsHtml5Portability::localEqualsBuffer(name, buf, offset, length)) {
+ return nullptr;
+ }
+ return attributeName;
+ }
+
+ private:
+ inline static uint32_t bufToHash(char16_t* buf, int32_t length)
+ {
+ uint32_t len = length;
+ uint32_t first = buf[0];
+ first <<= 19;
+ uint32_t second = 1 << 23;
+ uint32_t third = 0;
+ uint32_t fourth = 0;
+ uint32_t fifth = 0;
+ uint32_t sixth = 0;
+ if (length >= 4) {
+ second = buf[length - 4];
+ second <<= 4;
+ third = buf[1];
+ third <<= 9;
+ fourth = buf[length - 2];
+ fourth <<= 14;
+ fifth = buf[3];
+ fifth <<= 24;
+ sixth = buf[length - 1];
+ sixth <<= 11;
+ } else if (length == 3) {
+ second = buf[1];
+ second <<= 4;
+ third = buf[2];
+ third <<= 9;
+ } else if (length == 2) {
+ second = buf[1];
+ second <<= 24;
+ }
+ return len + first + second + third + fourth + fifth + sixth;
+ }
+
+ public:
+ static const int32_t HTML = 0;
+
+ static const int32_t MATHML = 1;
+
+ static const int32_t SVG = 2;
+
+ private:
+ int32_t* uri;
+ nsIAtom** local;
+ nsIAtom** prefix;
+ bool custom;
+ nsHtml5AttributeName(int32_t* uri, nsIAtom** local, nsIAtom** prefix);
+ public:
+ nsHtml5AttributeName();
+ inline bool isInterned()
+ {
+ return !custom;
+ }
+
+ inline void setNameForNonInterned(nsIAtom* name)
+ {
+ MOZ_ASSERT(custom);
+ local[0] = name;
+ local[1] = name;
+ local[2] = name;
+ }
+
+ static nsHtml5AttributeName* createAttributeName(nsIAtom* name);
+ ~nsHtml5AttributeName();
+ int32_t getUri(int32_t mode);
+ nsIAtom* getLocal(int32_t mode);
+ nsIAtom* getPrefix(int32_t mode);
+ bool equalsAnother(nsHtml5AttributeName* another);
+ static nsHtml5AttributeName* ATTR_ALT;
+ static nsHtml5AttributeName* ATTR_DIR;
+ static nsHtml5AttributeName* ATTR_DUR;
+ static nsHtml5AttributeName* ATTR_END;
+ static nsHtml5AttributeName* ATTR_FOR;
+ static nsHtml5AttributeName* ATTR_IN2;
+ static nsHtml5AttributeName* ATTR_LOW;
+ static nsHtml5AttributeName* ATTR_MIN;
+ static nsHtml5AttributeName* ATTR_MAX;
+ static nsHtml5AttributeName* ATTR_REL;
+ static nsHtml5AttributeName* ATTR_REV;
+ static nsHtml5AttributeName* ATTR_SRC;
+ static nsHtml5AttributeName* ATTR_D;
+ static nsHtml5AttributeName* ATTR_K;
+ static nsHtml5AttributeName* ATTR_R;
+ static nsHtml5AttributeName* ATTR_X;
+ static nsHtml5AttributeName* ATTR_Y;
+ static nsHtml5AttributeName* ATTR_Z;
+ static nsHtml5AttributeName* ATTR_CAP_HEIGHT;
+ static nsHtml5AttributeName* ATTR_G1;
+ static nsHtml5AttributeName* ATTR_K1;
+ static nsHtml5AttributeName* ATTR_U1;
+ static nsHtml5AttributeName* ATTR_X1;
+ static nsHtml5AttributeName* ATTR_Y1;
+ static nsHtml5AttributeName* ATTR_G2;
+ static nsHtml5AttributeName* ATTR_K2;
+ static nsHtml5AttributeName* ATTR_U2;
+ static nsHtml5AttributeName* ATTR_X2;
+ static nsHtml5AttributeName* ATTR_Y2;
+ static nsHtml5AttributeName* ATTR_K3;
+ static nsHtml5AttributeName* ATTR_K4;
+ static nsHtml5AttributeName* ATTR_XML_SPACE;
+ static nsHtml5AttributeName* ATTR_XML_LANG;
+ static nsHtml5AttributeName* ATTR_XML_BASE;
+ static nsHtml5AttributeName* ATTR_ARIA_GRAB;
+ static nsHtml5AttributeName* ATTR_ARIA_VALUEMAX;
+ static nsHtml5AttributeName* ATTR_ARIA_LABELLEDBY;
+ static nsHtml5AttributeName* ATTR_ARIA_DESCRIBEDBY;
+ static nsHtml5AttributeName* ATTR_ARIA_DISABLED;
+ static nsHtml5AttributeName* ATTR_ARIA_CHECKED;
+ static nsHtml5AttributeName* ATTR_ARIA_SELECTED;
+ static nsHtml5AttributeName* ATTR_ARIA_DROPEFFECT;
+ static nsHtml5AttributeName* ATTR_ARIA_REQUIRED;
+ static nsHtml5AttributeName* ATTR_ARIA_EXPANDED;
+ static nsHtml5AttributeName* ATTR_ARIA_PRESSED;
+ static nsHtml5AttributeName* ATTR_ARIA_LEVEL;
+ static nsHtml5AttributeName* ATTR_ARIA_CHANNEL;
+ static nsHtml5AttributeName* ATTR_ARIA_HIDDEN;
+ static nsHtml5AttributeName* ATTR_ARIA_SECRET;
+ static nsHtml5AttributeName* ATTR_ARIA_POSINSET;
+ static nsHtml5AttributeName* ATTR_ARIA_ATOMIC;
+ static nsHtml5AttributeName* ATTR_ARIA_INVALID;
+ static nsHtml5AttributeName* ATTR_ARIA_TEMPLATEID;
+ static nsHtml5AttributeName* ATTR_ARIA_VALUEMIN;
+ static nsHtml5AttributeName* ATTR_ARIA_MULTISELECTABLE;
+ static nsHtml5AttributeName* ATTR_ARIA_CONTROLS;
+ static nsHtml5AttributeName* ATTR_ARIA_MULTILINE;
+ static nsHtml5AttributeName* ATTR_ARIA_READONLY;
+ static nsHtml5AttributeName* ATTR_ARIA_OWNS;
+ static nsHtml5AttributeName* ATTR_ARIA_ACTIVEDESCENDANT;
+ static nsHtml5AttributeName* ATTR_ARIA_RELEVANT;
+ static nsHtml5AttributeName* ATTR_ARIA_DATATYPE;
+ static nsHtml5AttributeName* ATTR_ARIA_VALUENOW;
+ static nsHtml5AttributeName* ATTR_ARIA_SORT;
+ static nsHtml5AttributeName* ATTR_ARIA_AUTOCOMPLETE;
+ static nsHtml5AttributeName* ATTR_ARIA_FLOWTO;
+ static nsHtml5AttributeName* ATTR_ARIA_BUSY;
+ static nsHtml5AttributeName* ATTR_ARIA_LIVE;
+ static nsHtml5AttributeName* ATTR_ARIA_HASPOPUP;
+ static nsHtml5AttributeName* ATTR_ARIA_SETSIZE;
+ static nsHtml5AttributeName* ATTR_CLEAR;
+ static nsHtml5AttributeName* ATTR_DATAFORMATAS;
+ static nsHtml5AttributeName* ATTR_DISABLED;
+ static nsHtml5AttributeName* ATTR_DATAFLD;
+ static nsHtml5AttributeName* ATTR_DEFAULT;
+ static nsHtml5AttributeName* ATTR_DATASRC;
+ static nsHtml5AttributeName* ATTR_DATA;
+ static nsHtml5AttributeName* ATTR_EQUALCOLUMNS;
+ static nsHtml5AttributeName* ATTR_EQUALROWS;
+ static nsHtml5AttributeName* ATTR_HSPACE;
+ static nsHtml5AttributeName* ATTR_ISMAP;
+ static nsHtml5AttributeName* ATTR_LOCAL;
+ static nsHtml5AttributeName* ATTR_LSPACE;
+ static nsHtml5AttributeName* ATTR_MOVABLELIMITS;
+ static nsHtml5AttributeName* ATTR_NOTATION;
+ static nsHtml5AttributeName* ATTR_ONDATASETCHANGED;
+ static nsHtml5AttributeName* ATTR_ONDATAAVAILABLE;
+ static nsHtml5AttributeName* ATTR_ONPASTE;
+ static nsHtml5AttributeName* ATTR_ONDATASETCOMPLETE;
+ static nsHtml5AttributeName* ATTR_RSPACE;
+ static nsHtml5AttributeName* ATTR_ROWALIGN;
+ static nsHtml5AttributeName* ATTR_ROTATE;
+ static nsHtml5AttributeName* ATTR_SEPARATOR;
+ static nsHtml5AttributeName* ATTR_SEPARATORS;
+ static nsHtml5AttributeName* ATTR_V_MATHEMATICAL;
+ static nsHtml5AttributeName* ATTR_VSPACE;
+ static nsHtml5AttributeName* ATTR_V_HANGING;
+ static nsHtml5AttributeName* ATTR_XCHANNELSELECTOR;
+ static nsHtml5AttributeName* ATTR_YCHANNELSELECTOR;
+ static nsHtml5AttributeName* ATTR_ARABIC_FORM;
+ static nsHtml5AttributeName* ATTR_ENABLE_BACKGROUND;
+ static nsHtml5AttributeName* ATTR_ONDBLCLICK;
+ static nsHtml5AttributeName* ATTR_ONABORT;
+ static nsHtml5AttributeName* ATTR_CALCMODE;
+ static nsHtml5AttributeName* ATTR_CHECKED;
+ static nsHtml5AttributeName* ATTR_DESCENT;
+ static nsHtml5AttributeName* ATTR_FENCE;
+ static nsHtml5AttributeName* ATTR_ONSCROLL;
+ static nsHtml5AttributeName* ATTR_ONACTIVATE;
+ static nsHtml5AttributeName* ATTR_OPACITY;
+ static nsHtml5AttributeName* ATTR_SPACING;
+ static nsHtml5AttributeName* ATTR_SPECULAREXPONENT;
+ static nsHtml5AttributeName* ATTR_SPECULARCONSTANT;
+ static nsHtml5AttributeName* ATTR_SPECIFICATION;
+ static nsHtml5AttributeName* ATTR_THICKMATHSPACE;
+ static nsHtml5AttributeName* ATTR_UNICODE;
+ static nsHtml5AttributeName* ATTR_UNICODE_BIDI;
+ static nsHtml5AttributeName* ATTR_UNICODE_RANGE;
+ static nsHtml5AttributeName* ATTR_BORDER;
+ static nsHtml5AttributeName* ATTR_ID;
+ static nsHtml5AttributeName* ATTR_GRADIENTTRANSFORM;
+ static nsHtml5AttributeName* ATTR_GRADIENTUNITS;
+ static nsHtml5AttributeName* ATTR_HIDDEN;
+ static nsHtml5AttributeName* ATTR_HEADERS;
+ static nsHtml5AttributeName* ATTR_READONLY;
+ static nsHtml5AttributeName* ATTR_RENDERING_INTENT;
+ static nsHtml5AttributeName* ATTR_SEED;
+ static nsHtml5AttributeName* ATTR_SRCDOC;
+ static nsHtml5AttributeName* ATTR_STDDEVIATION;
+ static nsHtml5AttributeName* ATTR_SANDBOX;
+ static nsHtml5AttributeName* ATTR_V_IDEOGRAPHIC;
+ static nsHtml5AttributeName* ATTR_WORD_SPACING;
+ static nsHtml5AttributeName* ATTR_ACCENTUNDER;
+ static nsHtml5AttributeName* ATTR_ACCEPT_CHARSET;
+ static nsHtml5AttributeName* ATTR_ACCESSKEY;
+ static nsHtml5AttributeName* ATTR_ACCENT_HEIGHT;
+ static nsHtml5AttributeName* ATTR_ACCENT;
+ static nsHtml5AttributeName* ATTR_ASCENT;
+ static nsHtml5AttributeName* ATTR_ACCEPT;
+ static nsHtml5AttributeName* ATTR_BEVELLED;
+ static nsHtml5AttributeName* ATTR_BASEFREQUENCY;
+ static nsHtml5AttributeName* ATTR_BASELINE_SHIFT;
+ static nsHtml5AttributeName* ATTR_BASEPROFILE;
+ static nsHtml5AttributeName* ATTR_BASELINE;
+ static nsHtml5AttributeName* ATTR_BASE;
+ static nsHtml5AttributeName* ATTR_CODE;
+ static nsHtml5AttributeName* ATTR_CODETYPE;
+ static nsHtml5AttributeName* ATTR_CODEBASE;
+ static nsHtml5AttributeName* ATTR_CITE;
+ static nsHtml5AttributeName* ATTR_DEFER;
+ static nsHtml5AttributeName* ATTR_DATETIME;
+ static nsHtml5AttributeName* ATTR_DIRECTION;
+ static nsHtml5AttributeName* ATTR_EDGEMODE;
+ static nsHtml5AttributeName* ATTR_EDGE;
+ static nsHtml5AttributeName* ATTR_FACE;
+ static nsHtml5AttributeName* ATTR_HIDEFOCUS;
+ static nsHtml5AttributeName* ATTR_INDEX;
+ static nsHtml5AttributeName* ATTR_IRRELEVANT;
+ static nsHtml5AttributeName* ATTR_INTERCEPT;
+ static nsHtml5AttributeName* ATTR_INTEGRITY;
+ static nsHtml5AttributeName* ATTR_LINEBREAK;
+ static nsHtml5AttributeName* ATTR_LABEL;
+ static nsHtml5AttributeName* ATTR_LINETHICKNESS;
+ static nsHtml5AttributeName* ATTR_MODE;
+ static nsHtml5AttributeName* ATTR_NAME;
+ static nsHtml5AttributeName* ATTR_NORESIZE;
+ static nsHtml5AttributeName* ATTR_ONBEFOREUNLOAD;
+ static nsHtml5AttributeName* ATTR_ONREPEAT;
+ static nsHtml5AttributeName* ATTR_OBJECT;
+ static nsHtml5AttributeName* ATTR_ONSELECT;
+ static nsHtml5AttributeName* ATTR_ORDER;
+ static nsHtml5AttributeName* ATTR_OTHER;
+ static nsHtml5AttributeName* ATTR_ONRESET;
+ static nsHtml5AttributeName* ATTR_ONCELLCHANGE;
+ static nsHtml5AttributeName* ATTR_ONREADYSTATECHANGE;
+ static nsHtml5AttributeName* ATTR_ONMESSAGE;
+ static nsHtml5AttributeName* ATTR_ONBEGIN;
+ static nsHtml5AttributeName* ATTR_ONHELP;
+ static nsHtml5AttributeName* ATTR_ONBEFOREPRINT;
+ static nsHtml5AttributeName* ATTR_ORIENT;
+ static nsHtml5AttributeName* ATTR_ORIENTATION;
+ static nsHtml5AttributeName* ATTR_ONBEFORECOPY;
+ static nsHtml5AttributeName* ATTR_ONSELECTSTART;
+ static nsHtml5AttributeName* ATTR_ONBEFOREPASTE;
+ static nsHtml5AttributeName* ATTR_ONBEFOREUPDATE;
+ static nsHtml5AttributeName* ATTR_ONDEACTIVATE;
+ static nsHtml5AttributeName* ATTR_ONBEFOREACTIVATE;
+ static nsHtml5AttributeName* ATTR_ONBEFORDEACTIVATE;
+ static nsHtml5AttributeName* ATTR_ONKEYPRESS;
+ static nsHtml5AttributeName* ATTR_ONKEYUP;
+ static nsHtml5AttributeName* ATTR_ONBEFOREEDITFOCUS;
+ static nsHtml5AttributeName* ATTR_ONBEFORECUT;
+ static nsHtml5AttributeName* ATTR_ONKEYDOWN;
+ static nsHtml5AttributeName* ATTR_ONRESIZE;
+ static nsHtml5AttributeName* ATTR_REPEAT;
+ static nsHtml5AttributeName* ATTR_REPEAT_MAX;
+ static nsHtml5AttributeName* ATTR_REFERRERPOLICY;
+ static nsHtml5AttributeName* ATTR_RULES;
+ static nsHtml5AttributeName* ATTR_REPEAT_MIN;
+ static nsHtml5AttributeName* ATTR_ROLE;
+ static nsHtml5AttributeName* ATTR_REPEATCOUNT;
+ static nsHtml5AttributeName* ATTR_REPEAT_START;
+ static nsHtml5AttributeName* ATTR_REPEAT_TEMPLATE;
+ static nsHtml5AttributeName* ATTR_REPEATDUR;
+ static nsHtml5AttributeName* ATTR_SELECTED;
+ static nsHtml5AttributeName* ATTR_SPEED;
+ static nsHtml5AttributeName* ATTR_SIZES;
+ static nsHtml5AttributeName* ATTR_SUPERSCRIPTSHIFT;
+ static nsHtml5AttributeName* ATTR_STRETCHY;
+ static nsHtml5AttributeName* ATTR_SCHEME;
+ static nsHtml5AttributeName* ATTR_SPREADMETHOD;
+ static nsHtml5AttributeName* ATTR_SELECTION;
+ static nsHtml5AttributeName* ATTR_SIZE;
+ static nsHtml5AttributeName* ATTR_TYPE;
+ static nsHtml5AttributeName* ATTR_UNSELECTABLE;
+ static nsHtml5AttributeName* ATTR_UNDERLINE_POSITION;
+ static nsHtml5AttributeName* ATTR_UNDERLINE_THICKNESS;
+ static nsHtml5AttributeName* ATTR_X_HEIGHT;
+ static nsHtml5AttributeName* ATTR_DIFFUSECONSTANT;
+ static nsHtml5AttributeName* ATTR_HREF;
+ static nsHtml5AttributeName* ATTR_HREFLANG;
+ static nsHtml5AttributeName* ATTR_ONAFTERPRINT;
+ static nsHtml5AttributeName* ATTR_ONAFTERUPDATE;
+ static nsHtml5AttributeName* ATTR_PROFILE;
+ static nsHtml5AttributeName* ATTR_SURFACESCALE;
+ static nsHtml5AttributeName* ATTR_XREF;
+ static nsHtml5AttributeName* ATTR_ALIGN;
+ static nsHtml5AttributeName* ATTR_ALIGNMENT_BASELINE;
+ static nsHtml5AttributeName* ATTR_ALIGNMENTSCOPE;
+ static nsHtml5AttributeName* ATTR_DRAGGABLE;
+ static nsHtml5AttributeName* ATTR_HEIGHT;
+ static nsHtml5AttributeName* ATTR_HANGING;
+ static nsHtml5AttributeName* ATTR_IMAGE_RENDERING;
+ static nsHtml5AttributeName* ATTR_LANGUAGE;
+ static nsHtml5AttributeName* ATTR_LANG;
+ static nsHtml5AttributeName* ATTR_LARGEOP;
+ static nsHtml5AttributeName* ATTR_LONGDESC;
+ static nsHtml5AttributeName* ATTR_LENGTHADJUST;
+ static nsHtml5AttributeName* ATTR_MARGINHEIGHT;
+ static nsHtml5AttributeName* ATTR_MARGINWIDTH;
+ static nsHtml5AttributeName* ATTR_NARGS;
+ static nsHtml5AttributeName* ATTR_ORIGIN;
+ static nsHtml5AttributeName* ATTR_PING;
+ static nsHtml5AttributeName* ATTR_TARGET;
+ static nsHtml5AttributeName* ATTR_TARGETX;
+ static nsHtml5AttributeName* ATTR_TARGETY;
+ static nsHtml5AttributeName* ATTR_ALPHABETIC;
+ static nsHtml5AttributeName* ATTR_ARCHIVE;
+ static nsHtml5AttributeName* ATTR_HIGH;
+ static nsHtml5AttributeName* ATTR_LIGHTING_COLOR;
+ static nsHtml5AttributeName* ATTR_MATHEMATICAL;
+ static nsHtml5AttributeName* ATTR_MATHBACKGROUND;
+ static nsHtml5AttributeName* ATTR_METHOD;
+ static nsHtml5AttributeName* ATTR_MATHVARIANT;
+ static nsHtml5AttributeName* ATTR_MATHCOLOR;
+ static nsHtml5AttributeName* ATTR_MATHSIZE;
+ static nsHtml5AttributeName* ATTR_NOSHADE;
+ static nsHtml5AttributeName* ATTR_ONCHANGE;
+ static nsHtml5AttributeName* ATTR_PATHLENGTH;
+ static nsHtml5AttributeName* ATTR_PATH;
+ static nsHtml5AttributeName* ATTR_ALTIMG;
+ static nsHtml5AttributeName* ATTR_ACTIONTYPE;
+ static nsHtml5AttributeName* ATTR_ACTION;
+ static nsHtml5AttributeName* ATTR_ACTIVE;
+ static nsHtml5AttributeName* ATTR_ADDITIVE;
+ static nsHtml5AttributeName* ATTR_BEGIN;
+ static nsHtml5AttributeName* ATTR_DOMINANT_BASELINE;
+ static nsHtml5AttributeName* ATTR_DIVISOR;
+ static nsHtml5AttributeName* ATTR_DEFINITIONURL;
+ static nsHtml5AttributeName* ATTR_HORIZ_ADV_X;
+ static nsHtml5AttributeName* ATTR_HORIZ_ORIGIN_X;
+ static nsHtml5AttributeName* ATTR_HORIZ_ORIGIN_Y;
+ static nsHtml5AttributeName* ATTR_LIMITINGCONEANGLE;
+ static nsHtml5AttributeName* ATTR_MEDIUMMATHSPACE;
+ static nsHtml5AttributeName* ATTR_MEDIA;
+ static nsHtml5AttributeName* ATTR_MANIFEST;
+ static nsHtml5AttributeName* ATTR_ONFILTERCHANGE;
+ static nsHtml5AttributeName* ATTR_ONFINISH;
+ static nsHtml5AttributeName* ATTR_OPTIMUM;
+ static nsHtml5AttributeName* ATTR_RADIOGROUP;
+ static nsHtml5AttributeName* ATTR_RADIUS;
+ static nsHtml5AttributeName* ATTR_SCRIPTLEVEL;
+ static nsHtml5AttributeName* ATTR_SCRIPTSIZEMULTIPLIER;
+ static nsHtml5AttributeName* ATTR_STRING;
+ static nsHtml5AttributeName* ATTR_STRIKETHROUGH_POSITION;
+ static nsHtml5AttributeName* ATTR_STRIKETHROUGH_THICKNESS;
+ static nsHtml5AttributeName* ATTR_SCRIPTMINSIZE;
+ static nsHtml5AttributeName* ATTR_TABINDEX;
+ static nsHtml5AttributeName* ATTR_VALIGN;
+ static nsHtml5AttributeName* ATTR_VISIBILITY;
+ static nsHtml5AttributeName* ATTR_BACKGROUND;
+ static nsHtml5AttributeName* ATTR_LINK;
+ static nsHtml5AttributeName* ATTR_MARKER_MID;
+ static nsHtml5AttributeName* ATTR_MARKERHEIGHT;
+ static nsHtml5AttributeName* ATTR_MARKER_END;
+ static nsHtml5AttributeName* ATTR_MASK;
+ static nsHtml5AttributeName* ATTR_MARKER_START;
+ static nsHtml5AttributeName* ATTR_MARKERWIDTH;
+ static nsHtml5AttributeName* ATTR_MASKUNITS;
+ static nsHtml5AttributeName* ATTR_MARKERUNITS;
+ static nsHtml5AttributeName* ATTR_MASKCONTENTUNITS;
+ static nsHtml5AttributeName* ATTR_AMPLITUDE;
+ static nsHtml5AttributeName* ATTR_CELLSPACING;
+ static nsHtml5AttributeName* ATTR_CELLPADDING;
+ static nsHtml5AttributeName* ATTR_DECLARE;
+ static nsHtml5AttributeName* ATTR_FILL_RULE;
+ static nsHtml5AttributeName* ATTR_FILL;
+ static nsHtml5AttributeName* ATTR_FILL_OPACITY;
+ static nsHtml5AttributeName* ATTR_MAXLENGTH;
+ static nsHtml5AttributeName* ATTR_ONCLICK;
+ static nsHtml5AttributeName* ATTR_ONBLUR;
+ static nsHtml5AttributeName* ATTR_REPLACE;
+ static nsHtml5AttributeName* ATTR_ROWLINES;
+ static nsHtml5AttributeName* ATTR_SCALE;
+ static nsHtml5AttributeName* ATTR_STYLE;
+ static nsHtml5AttributeName* ATTR_TABLEVALUES;
+ static nsHtml5AttributeName* ATTR_TITLE;
+ static nsHtml5AttributeName* ATTR_V_ALPHABETIC;
+ static nsHtml5AttributeName* ATTR_AZIMUTH;
+ static nsHtml5AttributeName* ATTR_FORMAT;
+ static nsHtml5AttributeName* ATTR_FRAMEBORDER;
+ static nsHtml5AttributeName* ATTR_FRAME;
+ static nsHtml5AttributeName* ATTR_FRAMESPACING;
+ static nsHtml5AttributeName* ATTR_FROM;
+ static nsHtml5AttributeName* ATTR_FORM;
+ static nsHtml5AttributeName* ATTR_PROMPT;
+ static nsHtml5AttributeName* ATTR_PRIMITIVEUNITS;
+ static nsHtml5AttributeName* ATTR_SYMMETRIC;
+ static nsHtml5AttributeName* ATTR_STEMH;
+ static nsHtml5AttributeName* ATTR_STEMV;
+ static nsHtml5AttributeName* ATTR_SEAMLESS;
+ static nsHtml5AttributeName* ATTR_SUMMARY;
+ static nsHtml5AttributeName* ATTR_USEMAP;
+ static nsHtml5AttributeName* ATTR_ZOOMANDPAN;
+ static nsHtml5AttributeName* ATTR_ASYNC;
+ static nsHtml5AttributeName* ATTR_ALINK;
+ static nsHtml5AttributeName* ATTR_IN;
+ static nsHtml5AttributeName* ATTR_ICON;
+ static nsHtml5AttributeName* ATTR_KERNELMATRIX;
+ static nsHtml5AttributeName* ATTR_KERNING;
+ static nsHtml5AttributeName* ATTR_KERNELUNITLENGTH;
+ static nsHtml5AttributeName* ATTR_ONUNLOAD;
+ static nsHtml5AttributeName* ATTR_OPEN;
+ static nsHtml5AttributeName* ATTR_ONINVALID;
+ static nsHtml5AttributeName* ATTR_ONEND;
+ static nsHtml5AttributeName* ATTR_ONINPUT;
+ static nsHtml5AttributeName* ATTR_POINTER_EVENTS;
+ static nsHtml5AttributeName* ATTR_POINTS;
+ static nsHtml5AttributeName* ATTR_POINTSATX;
+ static nsHtml5AttributeName* ATTR_POINTSATY;
+ static nsHtml5AttributeName* ATTR_POINTSATZ;
+ static nsHtml5AttributeName* ATTR_SPAN;
+ static nsHtml5AttributeName* ATTR_STANDBY;
+ static nsHtml5AttributeName* ATTR_THINMATHSPACE;
+ static nsHtml5AttributeName* ATTR_TRANSFORM;
+ static nsHtml5AttributeName* ATTR_VLINK;
+ static nsHtml5AttributeName* ATTR_WHEN;
+ static nsHtml5AttributeName* ATTR_XLINK_HREF;
+ static nsHtml5AttributeName* ATTR_XLINK_TITLE;
+ static nsHtml5AttributeName* ATTR_XLINK_ROLE;
+ static nsHtml5AttributeName* ATTR_XLINK_ARCROLE;
+ static nsHtml5AttributeName* ATTR_XMLNS_XLINK;
+ static nsHtml5AttributeName* ATTR_XMLNS;
+ static nsHtml5AttributeName* ATTR_XLINK_TYPE;
+ static nsHtml5AttributeName* ATTR_XLINK_SHOW;
+ static nsHtml5AttributeName* ATTR_XLINK_ACTUATE;
+ static nsHtml5AttributeName* ATTR_AUTOPLAY;
+ static nsHtml5AttributeName* ATTR_AUTOSUBMIT;
+ static nsHtml5AttributeName* ATTR_AUTOCOMPLETE;
+ static nsHtml5AttributeName* ATTR_AUTOFOCUS;
+ static nsHtml5AttributeName* ATTR_BGCOLOR;
+ static nsHtml5AttributeName* ATTR_COLOR_PROFILE;
+ static nsHtml5AttributeName* ATTR_COLOR_RENDERING;
+ static nsHtml5AttributeName* ATTR_COLOR_INTERPOLATION;
+ static nsHtml5AttributeName* ATTR_COLOR;
+ static nsHtml5AttributeName* ATTR_COLOR_INTERPOLATION_FILTERS;
+ static nsHtml5AttributeName* ATTR_ENCODING;
+ static nsHtml5AttributeName* ATTR_EXPONENT;
+ static nsHtml5AttributeName* ATTR_FLOOD_COLOR;
+ static nsHtml5AttributeName* ATTR_FLOOD_OPACITY;
+ static nsHtml5AttributeName* ATTR_IDEOGRAPHIC;
+ static nsHtml5AttributeName* ATTR_LQUOTE;
+ static nsHtml5AttributeName* ATTR_PANOSE_1;
+ static nsHtml5AttributeName* ATTR_NUMOCTAVES;
+ static nsHtml5AttributeName* ATTR_NOMODULE;
+ static nsHtml5AttributeName* ATTR_ONLOAD;
+ static nsHtml5AttributeName* ATTR_ONBOUNCE;
+ static nsHtml5AttributeName* ATTR_ONCONTROLSELECT;
+ static nsHtml5AttributeName* ATTR_ONROWSINSERTED;
+ static nsHtml5AttributeName* ATTR_ONMOUSEWHEEL;
+ static nsHtml5AttributeName* ATTR_ONROWENTER;
+ static nsHtml5AttributeName* ATTR_ONMOUSEENTER;
+ static nsHtml5AttributeName* ATTR_ONMOUSEOVER;
+ static nsHtml5AttributeName* ATTR_ONFORMCHANGE;
+ static nsHtml5AttributeName* ATTR_ONFOCUSIN;
+ static nsHtml5AttributeName* ATTR_ONROWEXIT;
+ static nsHtml5AttributeName* ATTR_ONMOVEEND;
+ static nsHtml5AttributeName* ATTR_ONCONTEXTMENU;
+ static nsHtml5AttributeName* ATTR_ONZOOM;
+ static nsHtml5AttributeName* ATTR_ONLOSECAPTURE;
+ static nsHtml5AttributeName* ATTR_ONCOPY;
+ static nsHtml5AttributeName* ATTR_ONMOVESTART;
+ static nsHtml5AttributeName* ATTR_ONROWSDELETE;
+ static nsHtml5AttributeName* ATTR_ONMOUSELEAVE;
+ static nsHtml5AttributeName* ATTR_ONMOVE;
+ static nsHtml5AttributeName* ATTR_ONMOUSEMOVE;
+ static nsHtml5AttributeName* ATTR_ONMOUSEUP;
+ static nsHtml5AttributeName* ATTR_ONFOCUS;
+ static nsHtml5AttributeName* ATTR_ONMOUSEOUT;
+ static nsHtml5AttributeName* ATTR_ONFORMINPUT;
+ static nsHtml5AttributeName* ATTR_ONFOCUSOUT;
+ static nsHtml5AttributeName* ATTR_ONMOUSEDOWN;
+ static nsHtml5AttributeName* ATTR_TO;
+ static nsHtml5AttributeName* ATTR_RQUOTE;
+ static nsHtml5AttributeName* ATTR_STROKE_LINECAP;
+ static nsHtml5AttributeName* ATTR_SCROLLDELAY;
+ static nsHtml5AttributeName* ATTR_STROKE_DASHARRAY;
+ static nsHtml5AttributeName* ATTR_STROKE_DASHOFFSET;
+ static nsHtml5AttributeName* ATTR_STROKE_LINEJOIN;
+ static nsHtml5AttributeName* ATTR_STROKE_MITERLIMIT;
+ static nsHtml5AttributeName* ATTR_STROKE;
+ static nsHtml5AttributeName* ATTR_SCROLLING;
+ static nsHtml5AttributeName* ATTR_STROKE_WIDTH;
+ static nsHtml5AttributeName* ATTR_STROKE_OPACITY;
+ static nsHtml5AttributeName* ATTR_COMPACT;
+ static nsHtml5AttributeName* ATTR_CLIP;
+ static nsHtml5AttributeName* ATTR_CLIP_RULE;
+ static nsHtml5AttributeName* ATTR_CLIP_PATH;
+ static nsHtml5AttributeName* ATTR_CLIPPATHUNITS;
+ static nsHtml5AttributeName* ATTR_DISPLAY;
+ static nsHtml5AttributeName* ATTR_DISPLAYSTYLE;
+ static nsHtml5AttributeName* ATTR_GLYPH_ORIENTATION_VERTICAL;
+ static nsHtml5AttributeName* ATTR_GLYPH_ORIENTATION_HORIZONTAL;
+ static nsHtml5AttributeName* ATTR_GLYPHREF;
+ static nsHtml5AttributeName* ATTR_GLYPH_NAME;
+ static nsHtml5AttributeName* ATTR_HTTP_EQUIV;
+ static nsHtml5AttributeName* ATTR_KEYPOINTS;
+ static nsHtml5AttributeName* ATTR_LOOP;
+ static nsHtml5AttributeName* ATTR_PROPERTY;
+ static nsHtml5AttributeName* ATTR_SCOPED;
+ static nsHtml5AttributeName* ATTR_STEP;
+ static nsHtml5AttributeName* ATTR_SHAPE_RENDERING;
+ static nsHtml5AttributeName* ATTR_SCOPE;
+ static nsHtml5AttributeName* ATTR_SHAPE;
+ static nsHtml5AttributeName* ATTR_SLOPE;
+ static nsHtml5AttributeName* ATTR_STOP_COLOR;
+ static nsHtml5AttributeName* ATTR_STOP_OPACITY;
+ static nsHtml5AttributeName* ATTR_TEMPLATE;
+ static nsHtml5AttributeName* ATTR_WRAP;
+ static nsHtml5AttributeName* ATTR_ABBR;
+ static nsHtml5AttributeName* ATTR_ATTRIBUTENAME;
+ static nsHtml5AttributeName* ATTR_ATTRIBUTETYPE;
+ static nsHtml5AttributeName* ATTR_CHAR;
+ static nsHtml5AttributeName* ATTR_COORDS;
+ static nsHtml5AttributeName* ATTR_CHAROFF;
+ static nsHtml5AttributeName* ATTR_CHARSET;
+ static nsHtml5AttributeName* ATTR_MACROS;
+ static nsHtml5AttributeName* ATTR_NOWRAP;
+ static nsHtml5AttributeName* ATTR_NOHREF;
+ static nsHtml5AttributeName* ATTR_ONDRAG;
+ static nsHtml5AttributeName* ATTR_ONDRAGENTER;
+ static nsHtml5AttributeName* ATTR_ONDRAGOVER;
+ static nsHtml5AttributeName* ATTR_ONPROPERTYCHANGE;
+ static nsHtml5AttributeName* ATTR_ONDRAGEND;
+ static nsHtml5AttributeName* ATTR_ONDROP;
+ static nsHtml5AttributeName* ATTR_ONDRAGDROP;
+ static nsHtml5AttributeName* ATTR_OVERLINE_POSITION;
+ static nsHtml5AttributeName* ATTR_ONERROR;
+ static nsHtml5AttributeName* ATTR_OPERATOR;
+ static nsHtml5AttributeName* ATTR_OVERFLOW;
+ static nsHtml5AttributeName* ATTR_ONDRAGSTART;
+ static nsHtml5AttributeName* ATTR_ONERRORUPDATE;
+ static nsHtml5AttributeName* ATTR_OVERLINE_THICKNESS;
+ static nsHtml5AttributeName* ATTR_ONDRAGLEAVE;
+ static nsHtml5AttributeName* ATTR_STARTOFFSET;
+ static nsHtml5AttributeName* ATTR_START;
+ static nsHtml5AttributeName* ATTR_AXIS;
+ static nsHtml5AttributeName* ATTR_BIAS;
+ static nsHtml5AttributeName* ATTR_COLSPAN;
+ static nsHtml5AttributeName* ATTR_CLASSID;
+ static nsHtml5AttributeName* ATTR_CROSSORIGIN;
+ static nsHtml5AttributeName* ATTR_COLS;
+ static nsHtml5AttributeName* ATTR_CURSOR;
+ static nsHtml5AttributeName* ATTR_CLOSURE;
+ static nsHtml5AttributeName* ATTR_CLOSE;
+ static nsHtml5AttributeName* ATTR_CLASS;
+ static nsHtml5AttributeName* ATTR_IS;
+ static nsHtml5AttributeName* ATTR_KEYSYSTEM;
+ static nsHtml5AttributeName* ATTR_KEYSPLINES;
+ static nsHtml5AttributeName* ATTR_LOWSRC;
+ static nsHtml5AttributeName* ATTR_MAXSIZE;
+ static nsHtml5AttributeName* ATTR_MINSIZE;
+ static nsHtml5AttributeName* ATTR_OFFSET;
+ static nsHtml5AttributeName* ATTR_PRESERVEALPHA;
+ static nsHtml5AttributeName* ATTR_PRESERVEASPECTRATIO;
+ static nsHtml5AttributeName* ATTR_ROWSPAN;
+ static nsHtml5AttributeName* ATTR_ROWSPACING;
+ static nsHtml5AttributeName* ATTR_ROWS;
+ static nsHtml5AttributeName* ATTR_SRCSET;
+ static nsHtml5AttributeName* ATTR_SUBSCRIPTSHIFT;
+ static nsHtml5AttributeName* ATTR_VERSION;
+ static nsHtml5AttributeName* ATTR_ALTTEXT;
+ static nsHtml5AttributeName* ATTR_CONTENTEDITABLE;
+ static nsHtml5AttributeName* ATTR_CONTROLS;
+ static nsHtml5AttributeName* ATTR_CONTENT;
+ static nsHtml5AttributeName* ATTR_CONTEXTMENU;
+ static nsHtml5AttributeName* ATTR_DEPTH;
+ static nsHtml5AttributeName* ATTR_ENCTYPE;
+ static nsHtml5AttributeName* ATTR_FONT_STRETCH;
+ static nsHtml5AttributeName* ATTR_FILTER;
+ static nsHtml5AttributeName* ATTR_FONTWEIGHT;
+ static nsHtml5AttributeName* ATTR_FONT_WEIGHT;
+ static nsHtml5AttributeName* ATTR_FONTSTYLE;
+ static nsHtml5AttributeName* ATTR_FONT_STYLE;
+ static nsHtml5AttributeName* ATTR_FONTFAMILY;
+ static nsHtml5AttributeName* ATTR_FONT_FAMILY;
+ static nsHtml5AttributeName* ATTR_FONT_VARIANT;
+ static nsHtml5AttributeName* ATTR_FONT_SIZE_ADJUST;
+ static nsHtml5AttributeName* ATTR_FILTERUNITS;
+ static nsHtml5AttributeName* ATTR_FONTSIZE;
+ static nsHtml5AttributeName* ATTR_FONT_SIZE;
+ static nsHtml5AttributeName* ATTR_KEYTIMES;
+ static nsHtml5AttributeName* ATTR_LETTER_SPACING;
+ static nsHtml5AttributeName* ATTR_LIST;
+ static nsHtml5AttributeName* ATTR_MULTIPLE;
+ static nsHtml5AttributeName* ATTR_RT;
+ static nsHtml5AttributeName* ATTR_ONSTOP;
+ static nsHtml5AttributeName* ATTR_ONSTART;
+ static nsHtml5AttributeName* ATTR_POSTER;
+ static nsHtml5AttributeName* ATTR_PATTERNTRANSFORM;
+ static nsHtml5AttributeName* ATTR_PATTERN;
+ static nsHtml5AttributeName* ATTR_PATTERNUNITS;
+ static nsHtml5AttributeName* ATTR_PATTERNCONTENTUNITS;
+ static nsHtml5AttributeName* ATTR_RESTART;
+ static nsHtml5AttributeName* ATTR_STITCHTILES;
+ static nsHtml5AttributeName* ATTR_SYSTEMLANGUAGE;
+ static nsHtml5AttributeName* ATTR_TEXT_RENDERING;
+ static nsHtml5AttributeName* ATTR_VERT_ORIGIN_X;
+ static nsHtml5AttributeName* ATTR_VERT_ADV_Y;
+ static nsHtml5AttributeName* ATTR_VERT_ORIGIN_Y;
+ static nsHtml5AttributeName* ATTR_TEXT_DECORATION;
+ static nsHtml5AttributeName* ATTR_TEXT_ANCHOR;
+ static nsHtml5AttributeName* ATTR_TEXTLENGTH;
+ static nsHtml5AttributeName* ATTR_TEXT;
+ static nsHtml5AttributeName* ATTR_UNITS_PER_EM;
+ static nsHtml5AttributeName* ATTR_WRITING_MODE;
+ static nsHtml5AttributeName* ATTR_WIDTHS;
+ static nsHtml5AttributeName* ATTR_WIDTH;
+ static nsHtml5AttributeName* ATTR_ACCUMULATE;
+ static nsHtml5AttributeName* ATTR_COLUMNSPAN;
+ static nsHtml5AttributeName* ATTR_COLUMNLINES;
+ static nsHtml5AttributeName* ATTR_COLUMNALIGN;
+ static nsHtml5AttributeName* ATTR_COLUMNSPACING;
+ static nsHtml5AttributeName* ATTR_COLUMNWIDTH;
+ static nsHtml5AttributeName* ATTR_GROUPALIGN;
+ static nsHtml5AttributeName* ATTR_INPUTMODE;
+ static nsHtml5AttributeName* ATTR_OCCURRENCE;
+ static nsHtml5AttributeName* ATTR_ONSUBMIT;
+ static nsHtml5AttributeName* ATTR_ONCUT;
+ static nsHtml5AttributeName* ATTR_REQUIRED;
+ static nsHtml5AttributeName* ATTR_REQUIREDFEATURES;
+ static nsHtml5AttributeName* ATTR_RESULT;
+ static nsHtml5AttributeName* ATTR_REQUIREDEXTENSIONS;
+ static nsHtml5AttributeName* ATTR_VALUES;
+ static nsHtml5AttributeName* ATTR_VALUETYPE;
+ static nsHtml5AttributeName* ATTR_VALUE;
+ static nsHtml5AttributeName* ATTR_ELEVATION;
+ static nsHtml5AttributeName* ATTR_VIEWTARGET;
+ static nsHtml5AttributeName* ATTR_VIEWBOX;
+ static nsHtml5AttributeName* ATTR_CX;
+ static nsHtml5AttributeName* ATTR_DX;
+ static nsHtml5AttributeName* ATTR_FX;
+ static nsHtml5AttributeName* ATTR_BBOX;
+ static nsHtml5AttributeName* ATTR_RX;
+ static nsHtml5AttributeName* ATTR_REFX;
+ static nsHtml5AttributeName* ATTR_BY;
+ static nsHtml5AttributeName* ATTR_CY;
+ static nsHtml5AttributeName* ATTR_DY;
+ static nsHtml5AttributeName* ATTR_FY;
+ static nsHtml5AttributeName* ATTR_RY;
+ static nsHtml5AttributeName* ATTR_REFY;
+ static nsHtml5AttributeName* ATTR_VERYTHINMATHSPACE;
+ static nsHtml5AttributeName* ATTR_VERYTHICKMATHSPACE;
+ static nsHtml5AttributeName* ATTR_VERYVERYTHINMATHSPACE;
+ static nsHtml5AttributeName* ATTR_VERYVERYTHICKMATHSPACE;
+ private:
+ static nsHtml5AttributeName** ATTRIBUTE_NAMES;
+ static staticJArray<int32_t,int32_t> ATTRIBUTE_HASHES;
+ public:
+ static void initializeStatics();
+ static void releaseStatics();
+};
+
+#endif
+
diff --git a/components/htmlfive/nsHtml5ByteReadable.h b/components/htmlfive/nsHtml5ByteReadable.h
new file mode 100644
index 000000000..7b6103ccf
--- /dev/null
+++ b/components/htmlfive/nsHtml5ByteReadable.h
@@ -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/. */
+
+#ifndef nsHtml5ByteReadable_h
+#define nsHtml5ByteReadable_h
+
+/**
+ * A weak reference wrapper around a byte array.
+ */
+class nsHtml5ByteReadable
+{
+ public:
+
+ nsHtml5ByteReadable(const uint8_t* aCurrent, const uint8_t* aEnd)
+ : current(aCurrent),
+ end(aEnd)
+ {
+ }
+
+ inline int32_t read() {
+ if (current < end) {
+ return *(current++);
+ } else {
+ return -1;
+ }
+ }
+
+ private:
+ const uint8_t* current;
+ const uint8_t* end;
+};
+#endif
diff --git a/components/htmlfive/nsHtml5ContentCreatorFunction.h b/components/htmlfive/nsHtml5ContentCreatorFunction.h
new file mode 100644
index 000000000..f46246a41
--- /dev/null
+++ b/components/htmlfive/nsHtml5ContentCreatorFunction.h
@@ -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/. */
+
+#ifndef nsHtml5ContentCreatorFunction_h
+#define nsHtml5ContentCreatorFunction_h
+
+#include "nsGenericHTMLElement.h"
+#include "mozilla/dom/SVGElementFactory.h"
+
+union nsHtml5ContentCreatorFunction
+{
+ mozilla::dom::HTMLContentCreatorFunction html;
+ mozilla::dom::SVGContentCreatorFunction svg;
+};
+
+#endif // nsHtml5ContentCreatorFunction_h
diff --git a/components/htmlfive/nsHtml5DependentUTF16Buffer.cpp b/components/htmlfive/nsHtml5DependentUTF16Buffer.cpp
new file mode 100644
index 000000000..ed2211611
--- /dev/null
+++ b/components/htmlfive/nsHtml5DependentUTF16Buffer.cpp
@@ -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/. */
+
+#include "nsHtml5DependentUTF16Buffer.h"
+
+nsHtml5DependentUTF16Buffer::nsHtml5DependentUTF16Buffer(const nsAString& aToWrap)
+ : nsHtml5UTF16Buffer(const_cast<char16_t*> (aToWrap.BeginReading()),
+ aToWrap.Length())
+{
+ MOZ_COUNT_CTOR(nsHtml5DependentUTF16Buffer);
+}
+
+nsHtml5DependentUTF16Buffer::~nsHtml5DependentUTF16Buffer()
+{
+ MOZ_COUNT_DTOR(nsHtml5DependentUTF16Buffer);
+}
+
+already_AddRefed<nsHtml5OwningUTF16Buffer>
+nsHtml5DependentUTF16Buffer::FalliblyCopyAsOwningBuffer()
+{
+ int32_t newLength = getEnd() - getStart();
+ RefPtr<nsHtml5OwningUTF16Buffer> newObj =
+ nsHtml5OwningUTF16Buffer::FalliblyCreate(newLength);
+ if (!newObj) {
+ return nullptr;
+ }
+ newObj->setEnd(newLength);
+ memcpy(newObj->getBuffer(),
+ getBuffer() + getStart(),
+ newLength * sizeof(char16_t));
+ return newObj.forget();
+}
diff --git a/components/htmlfive/nsHtml5DependentUTF16Buffer.h b/components/htmlfive/nsHtml5DependentUTF16Buffer.h
new file mode 100644
index 000000000..038d331c0
--- /dev/null
+++ b/components/htmlfive/nsHtml5DependentUTF16Buffer.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/. */
+
+#ifndef nsHtml5DependentUTF16Buffer_h
+#define nsHtml5DependentUTF16Buffer_h
+
+#include "nscore.h"
+#include "nsHtml5OwningUTF16Buffer.h"
+
+class MOZ_STACK_CLASS nsHtml5DependentUTF16Buffer : public nsHtml5UTF16Buffer
+{
+ public:
+ /**
+ * Wraps a string without taking ownership of the buffer. aToWrap MUST NOT
+ * go away or be shortened while nsHtml5DependentUTF16Buffer is in use.
+ */
+ explicit nsHtml5DependentUTF16Buffer(const nsAString& aToWrap);
+
+ ~nsHtml5DependentUTF16Buffer();
+
+ /**
+ * Copies the currently unconsumed part of this buffer into a new
+ * heap-allocated nsHtml5OwningUTF16Buffer. The new object is allocated
+ * with a fallible allocator. If the allocation fails, nullptr is returned.
+ * @return heap-allocated copy or nullptr if memory allocation failed
+ */
+ already_AddRefed<nsHtml5OwningUTF16Buffer> FalliblyCopyAsOwningBuffer();
+};
+
+#endif // nsHtml5DependentUTF16Buffer_h
diff --git a/components/htmlfive/nsHtml5DocumentBuilder.cpp b/components/htmlfive/nsHtml5DocumentBuilder.cpp
new file mode 100644
index 000000000..459b0311d
--- /dev/null
+++ b/components/htmlfive/nsHtml5DocumentBuilder.cpp
@@ -0,0 +1,119 @@
+/* -*- 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 "nsHtml5DocumentBuilder.h"
+
+#include "nsIStyleSheetLinkingElement.h"
+#include "nsStyleLinkElement.h"
+#include "nsIHTMLDocument.h"
+#include "mozilla/dom/ScriptLoader.h"
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsHtml5DocumentBuilder, nsContentSink,
+ mOwnedElements)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsHtml5DocumentBuilder)
+NS_INTERFACE_MAP_END_INHERITING(nsContentSink)
+
+NS_IMPL_ADDREF_INHERITED(nsHtml5DocumentBuilder, nsContentSink)
+NS_IMPL_RELEASE_INHERITED(nsHtml5DocumentBuilder, nsContentSink)
+
+nsHtml5DocumentBuilder::nsHtml5DocumentBuilder(bool aRunsToCompletion)
+{
+ mRunsToCompletion = aRunsToCompletion;
+}
+
+nsresult
+nsHtml5DocumentBuilder::Init(nsIDocument* aDoc,
+ nsIURI* aURI,
+ nsISupports* aContainer,
+ nsIChannel* aChannel)
+{
+ return nsContentSink::Init(aDoc, aURI, aContainer, aChannel);
+}
+
+nsHtml5DocumentBuilder::~nsHtml5DocumentBuilder()
+{
+}
+
+nsresult
+nsHtml5DocumentBuilder::MarkAsBroken(nsresult aReason)
+{
+ mBroken = aReason;
+ return aReason;
+}
+
+void
+nsHtml5DocumentBuilder::SetDocumentCharsetAndSource(nsACString& aCharset, int32_t aCharsetSource)
+{
+ if (mDocument) {
+ mDocument->SetDocumentCharacterSetSource(aCharsetSource);
+ mDocument->SetDocumentCharacterSet(aCharset);
+ }
+}
+
+void
+nsHtml5DocumentBuilder::UpdateStyleSheet(nsIContent* aElement)
+{
+ // Break out of the doc update created by Flush() to zap a runnable
+ // waiting to call UpdateStyleSheet without the right observer
+ EndDocUpdate();
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // EndDocUpdate ran stuff that called nsIParser::Terminate()
+ return;
+ }
+
+ nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(aElement));
+ NS_ASSERTION(ssle, "Node didn't QI to style.");
+
+ ssle->SetEnableUpdates(true);
+
+ bool willNotify;
+ bool isAlternate;
+ nsresult rv = ssle->UpdateStyleSheet(mRunsToCompletion ? nullptr : this,
+ &willNotify,
+ &isAlternate);
+ if (NS_SUCCEEDED(rv) && willNotify && !isAlternate && !mRunsToCompletion) {
+ ++mPendingSheetCount;
+ mScriptLoader->AddParserBlockingScriptExecutionBlocker();
+ }
+
+ // Re-open update
+ BeginDocUpdate();
+}
+
+void
+nsHtml5DocumentBuilder::SetDocumentMode(nsHtml5DocumentMode m)
+{
+ nsCompatibility mode = eCompatibility_NavQuirks;
+ switch (m) {
+ case STANDARDS_MODE:
+ mode = eCompatibility_FullStandards;
+ break;
+ case ALMOST_STANDARDS_MODE:
+ mode = eCompatibility_AlmostStandards;
+ break;
+ case QUIRKS_MODE:
+ mode = eCompatibility_NavQuirks;
+ break;
+ }
+ nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(mDocument);
+ NS_ASSERTION(htmlDocument, "Document didn't QI into HTML document.");
+ htmlDocument->SetCompatibilityMode(mode);
+}
+
+// nsContentSink overrides
+
+void
+nsHtml5DocumentBuilder::UpdateChildCounts()
+{
+ // No-op
+}
+
+nsresult
+nsHtml5DocumentBuilder::FlushTags()
+{
+ return NS_OK;
+}
diff --git a/components/htmlfive/nsHtml5DocumentBuilder.h b/components/htmlfive/nsHtml5DocumentBuilder.h
new file mode 100644
index 000000000..82ad747b1
--- /dev/null
+++ b/components/htmlfive/nsHtml5DocumentBuilder.h
@@ -0,0 +1,129 @@
+/* -*- 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 nsHtml5DocumentBuilder_h
+#define nsHtml5DocumentBuilder_h
+
+#include "nsContentSink.h"
+#include "nsHtml5DocumentMode.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+
+typedef nsIContent* nsIContentPtr;
+
+enum eHtml5FlushState {
+ eNotFlushing = 0, // not flushing
+ eInFlush = 1, // the Flush() method is on the call stack
+ eInDocUpdate = 2, // inside an update batch on the document
+ eNotifying = 3 // flushing pending append notifications
+};
+
+class nsHtml5DocumentBuilder : public nsContentSink
+{
+public:
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHtml5DocumentBuilder,
+ nsContentSink)
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ inline void HoldElement(already_AddRefed<nsIContent> aContent)
+ {
+ *(mOwnedElements.AppendElement()) = aContent;
+ }
+
+ nsresult Init(nsIDocument* aDoc, nsIURI* aURI,
+ nsISupports* aContainer, nsIChannel* aChannel);
+
+ // Getters and setters for fields from nsContentSink
+ nsIDocument* GetDocument()
+ {
+ return mDocument;
+ }
+
+ nsNodeInfoManager* GetNodeInfoManager()
+ {
+ return mNodeInfoManager;
+ }
+
+ /**
+ * Marks this parser as broken and tells the stream parser (if any) to
+ * terminate.
+ *
+ * @return aReason for convenience
+ */
+ virtual nsresult MarkAsBroken(nsresult aReason);
+
+ /**
+ * Checks if this parser is broken. Returns a non-NS_OK (i.e. non-0)
+ * value if broken.
+ */
+ inline nsresult IsBroken()
+ {
+ return mBroken;
+ }
+
+ inline void BeginDocUpdate()
+ {
+ NS_PRECONDITION(mFlushState == eInFlush, "Tried to double-open update.");
+ NS_PRECONDITION(mParser, "Started update without parser.");
+ mFlushState = eInDocUpdate;
+ mDocument->BeginUpdate(UPDATE_CONTENT_MODEL);
+ }
+
+ inline void EndDocUpdate()
+ {
+ NS_PRECONDITION(mFlushState != eNotifying, "mFlushState out of sync");
+ if (mFlushState == eInDocUpdate) {
+ mFlushState = eInFlush;
+ mDocument->EndUpdate(UPDATE_CONTENT_MODEL);
+ }
+ }
+
+ bool IsInDocUpdate()
+ {
+ return mFlushState == eInDocUpdate;
+ }
+
+ void SetDocumentCharsetAndSource(nsACString& aCharset, int32_t aCharsetSource);
+
+ /**
+ * Sets up style sheet load / parse
+ */
+ void UpdateStyleSheet(nsIContent* aElement);
+
+ void SetDocumentMode(nsHtml5DocumentMode m);
+
+ void SetNodeInfoManager(nsNodeInfoManager* aManager)
+ {
+ mNodeInfoManager = aManager;
+ }
+
+ // nsContentSink methods
+ virtual void UpdateChildCounts() override;
+ virtual nsresult FlushTags() override;
+
+protected:
+
+ explicit nsHtml5DocumentBuilder(bool aRunsToCompletion);
+ virtual ~nsHtml5DocumentBuilder();
+
+protected:
+ AutoTArray<nsCOMPtr<nsIContent>, 32> mOwnedElements;
+ /**
+ * Non-NS_OK if this parser should refuse to process any more input.
+ * For example, the parser needs to be marked as broken if it drops some
+ * input due to a memory allocation failure. In such a case, the whole
+ * parser needs to be marked as broken, because some input has been lost
+ * and parsing more input could lead to a DOM where pieces of HTML source
+ * that weren't supposed to become scripts become scripts.
+ *
+ * Since NS_OK is actually 0, zeroing operator new takes care of
+ * initializing this.
+ */
+ nsresult mBroken;
+ eHtml5FlushState mFlushState;
+};
+
+#endif // nsHtml5DocumentBuilder_h
diff --git a/components/htmlfive/nsHtml5DocumentMode.h b/components/htmlfive/nsHtml5DocumentMode.h
new file mode 100644
index 000000000..b6acee5c9
--- /dev/null
+++ b/components/htmlfive/nsHtml5DocumentMode.h
@@ -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/. */
+
+#ifndef nsHtml5DocumentMode_h
+#define nsHtml5DocumentMode_h
+
+enum nsHtml5DocumentMode {
+ STANDARDS_MODE,
+ ALMOST_STANDARDS_MODE,
+ QUIRKS_MODE
+};
+
+#endif // nsHtml5DocumentMode_h
diff --git a/components/htmlfive/nsHtml5ElementName.cpp b/components/htmlfive/nsHtml5ElementName.cpp
new file mode 100644
index 000000000..af9436f08
--- /dev/null
+++ b/components/htmlfive/nsHtml5ElementName.cpp
@@ -0,0 +1,1702 @@
+/*
+ * Copyright (c) 2008-2017 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit ElementName.java instead and regenerate.
+ */
+
+#define nsHtml5ElementName_cpp__
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+#include "nsHtml5AttributeName.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5MetaScanner.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5UTF16Buffer.h"
+#include "nsHtml5StateSnapshot.h"
+#include "nsHtml5Portability.h"
+
+#include "nsHtml5ElementName.h"
+
+
+nsHtml5ElementName::nsHtml5ElementName(nsIAtom* name, nsIAtom* camelCaseName, mozilla::dom::HTMLContentCreatorFunction htmlCreator, mozilla::dom::SVGContentCreatorFunction svgCreator, int32_t flags)
+ : name(name),
+ camelCaseName(camelCaseName),
+ htmlCreator(htmlCreator),
+ svgCreator(svgCreator),
+ flags(flags)
+{
+ MOZ_COUNT_CTOR(nsHtml5ElementName);
+}
+
+
+nsHtml5ElementName::nsHtml5ElementName()
+ : name(nullptr),
+ camelCaseName(nullptr),
+ htmlCreator(NS_NewHTMLUnknownElement),
+ svgCreator(NS_NewSVGUnknownElement),
+ flags(nsHtml5TreeBuilder::OTHER | NOT_INTERNED)
+{
+ MOZ_COUNT_CTOR(nsHtml5ElementName);
+}
+
+
+nsHtml5ElementName::~nsHtml5ElementName()
+{
+ MOZ_COUNT_DTOR(nsHtml5ElementName);
+}
+
+nsHtml5ElementName* nsHtml5ElementName::ELT_ISINDEX = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ANNOTATION_XML = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_AND = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARG = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ABS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_BIG = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_BDO = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CSC = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_COL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_COS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_COT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DEL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DFN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DIR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DIV = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_EXP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_GCD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_GEQ = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_IMG = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_INS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_INT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_KBD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LOG = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LCM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LEQ = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MTD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MIN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MAP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MTR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MAX = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NEQ = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NOT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NAV = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PRE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_A = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_B = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_RTC = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_REM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SUB = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SEC = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SVG = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SUM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SIN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SEP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SUP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SET = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TAN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_USE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_VAR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_G = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_WBR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_XMP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_XOR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_I = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_P = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_Q = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_S = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_U = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_H1 = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_H2 = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_H3 = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_H4 = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_H5 = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_H6 = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_AREA = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DATA = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_EULERGAMMA = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEFUNCA = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LAMBDA = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_METADATA = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_META = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TEXTAREA = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEFUNCB = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MSUB = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_RB = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCSEC = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCCSC = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DEFINITION_SRC = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DESC = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FONT_FACE_SRC = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MFRAC = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_BGSOUND = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CARD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DISCARD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_EMBED = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEBLEND = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEFLOOD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_GRAD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_HEAD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LEGEND = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MFENCED = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MPADDED = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NOEMBED = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_THEAD = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ASIDE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARTICLE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ANIMATE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_BASE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_BLOCKQUOTE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CODE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CIRCLE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_COLOR_PROFILE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_COMPOSE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CONJUGATE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CITE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DIVERGENCE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DIVIDE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DEGREE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DECLARE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DATATEMPLATE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_EXPONENTIALE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ELLIPSE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FONT_FACE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FETURBULENCE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEMERGENODE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEIMAGE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEMERGE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FETILE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FONT_FACE_NAME = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FRAME = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FIGURE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FALSE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FECOMPOSITE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_IMAGE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_IFRAME = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_INVERSE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LINE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LOGBASE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MSPACE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MODE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MTABLE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MSTYLE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MENCLOSE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NONE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_OTHERWISE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PIECE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_POLYLINE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PICTURE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PIECEWISE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_RULE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SOURCE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_STRIKE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_STYLE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TABLE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TITLE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TIME = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TRANSPOSE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TEMPLATE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TRUE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_VARIANCE = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ALTGLYPHDEF = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DIFF = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FACTOROF = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_GLYPHREF = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PARTIALDIFF = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SETDIFF = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TREF = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CEILING = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DIALOG = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEFUNCG = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEDIFFUSELIGHTING = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FESPECULARLIGHTING = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LISTING = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_STRONG = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCSECH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCCSCH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCTANH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCSINH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ALTGLYPH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCCOSH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCCOTH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CSCH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_COSH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CLIPPATH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_COTH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_GLYPH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MGLYPH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MISSING_GLYPH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MATH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MPATH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PREFETCH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PATH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SECH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SWITCH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SINH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TANH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TEXTPATH = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CI = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FONT_FACE_URI = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LI = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_IMAGINARYI = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MI = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PI = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LINK = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MARK = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MALIGNMARK = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MASK = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TBREAK = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TRACK = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CSYMBOL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CURL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FACTORIAL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FORALL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_HTML = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_INTERVAL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_OL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LABEL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_UL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_REAL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SMALL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SYMBOL = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ALTGLYPHITEM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ANIMATETRANSFORM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ACRONYM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_EM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FORM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MENUITEM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MPHANTOM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PARAM = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCTAN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCSIN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ANIMATION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ANNOTATION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ANIMATEMOTION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_BUTTON = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CODOMAIN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CAPTION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CONDITION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DOMAIN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DOMAINOFAPPLICATION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_IN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FIGCAPTION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_HKERN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_KEYGEN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LAPLACIAN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MEAN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MEDIAN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MAIN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MACTION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NOTIN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_OPTION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_POLYGON = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PATTERN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_RELN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SPAN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SECTION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TSPAN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_UNION = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_VKERN = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_AUDIO = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MO = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TENDSTO = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_VIDEO = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_COLGROUP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEDISPLACEMENTMAP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_HGROUP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MALIGNGROUP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MSUBSUP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MSUP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_RP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_OPTGROUP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SAMP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_STOP = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_EQ = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_BR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ABBR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ANIMATECOLOR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_BVAR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CENTER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CURSOR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_HR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEFUNCR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FECOMPONENTTRANSFER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FILTER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FOOTER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FLOOR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEGAUSSIANBLUR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_HEADER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_HANDLER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_OR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LISTENER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MUNDER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MARKER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_METER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MOVER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MUNDEROVER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MERROR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MLABELEDTR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NOBR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NOTANUMBER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_POWER = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SOLIDCOLOR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SELECTOR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_VECTOR = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCCOS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ADDRESS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CANVAS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_COMPLEXES = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DEFS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DETAILS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_EXISTS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_IMPLIES = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_INTEGERS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MPRESCRIPTS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MMULTISCRIPTS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MINUS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NOFRAMES = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NATURALNUMBERS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PRIMES = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PROGRESS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PLUS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_REALS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_RATIONALS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SEMANTICS = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TIMES = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_APPLET = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ARCCOT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_BASEFONT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_CARTESIANPRODUCT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_GT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DETERMINANT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_DATALIST = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_EMPTYSET = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_EQUIVALENT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FONT_FACE_FORMAT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FOREIGNOBJECT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FIELDSET = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FRAMESET = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEOFFSET = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FESPOTLIGHT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEPOINTLIGHT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEDISTANTLIGHT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FONT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_INTERSECT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_IDENT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_INPUT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LIMIT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LOWLIMIT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LINEARGRADIENT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_LIST = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MOMENT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MROOT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MSQRT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MOMENTABOUT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MTEXT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NOTSUBSET = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NOTPRSUBSET = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NOSCRIPT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_NEST = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_RT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_OBJECT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_OUTERPRODUCT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_OUTPUT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PRODUCT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PRSUBSET = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_PLAINTEXT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_QUOTIENT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_RECT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_RADIALGRADIENT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_ROOT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SELECT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SCALARPRODUCT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SUBSET = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SLOT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SCRIPT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TFOOT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TEXT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_UPLIMIT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_VECTORPRODUCT = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MENU = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SDEV = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEDROPSHADOW = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MROW = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MATRIXROW = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_VIEW = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_APPROX = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FECOLORMATRIX = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FECONVOLVEMATRIX = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_MATRIX = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_APPLY = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_BODY = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_FEMORPHOLOGY = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_IMAGINARY = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_INFINITY = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_RUBY = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_SUMMARY = nullptr;
+nsHtml5ElementName* nsHtml5ElementName::ELT_TBODY = nullptr;
+nsHtml5ElementName** nsHtml5ElementName::ELEMENT_NAMES = 0;
+static int32_t const ELEMENT_HASHES_DATA[] = { 1914900309, 1753057319, 2001349704, 1681770564, 1818700314, 1982935782, 2007257240, 58773795, 1747176599, 1783210839, 1898130486, 1971457766, 1990969429, 2005181733, 2055514836, 54061139, 62390273, 1730150402, 1749395095, 1757137429, 1800730821, 1870135298, 1903302038, 1965115924, 1971981018, 1988486811, 1999745104, 2002882873, 2005925890, 2008340774, 2082727685, 51965171, 57200451, 60350803, 69730305, 1703292116, 1733890180, 1748355193, 1749813541, 1754894485, 1765431364, 1797544247, 1806799156, 1857653029, 1881613047, 1899272521, 1906087319, 1938172967, 1967795910, 1971467002, 1974775352, 1984294038, 1988972590, 1998585858, 2000825752, 2001392796, 2004557976, 2005543977, 2006560839, 2008125638, 2009706573, 2068523853, 2087049448, 51434643, 52488851, 56151587, 57210387, 59826259, 60354131, 63438849, 926941186, 1686489160, 1715300574, 1732381397, 1737099991, 1748100148, 1748642422, 1749715159, 1751288021, 1753479494, 1756098852, 1757268168, 1773295687, 1790207270, 1798417460, 1803929861, 1807599880, 1854228692, 1867061545, 1874053333, 1887743720, 1898753862, 1900845386, 1904412884, 1907661127, 1932928296, 1941178676, 1966386470, 1968836118, 1971465813, 1971703386, 1973420034, 1982106678, 1983533124, 1986351224, 1988502165, 1990037800, 1991350601, 1998883894, 2000439531, 2001281328, 2001349736, 2001495140, 2003183333, 2004719812, 2005279787, 2005719336, 2006036556, 2006896969, 2007781534, 2008165414, 2008994116, 2041712436, 2060065124, 2070023911, 2085266636, 2092255447, 50910499, 51957043, 52485715, 53012355, 55110883, 56680499, 57206291, 57732851, 59768833, 60345427, 60352083, 61395251, 62973651, 67633153, 893386754, 960495618, 1682547543, 1689922072, 1713515574, 1716349149, 1731545140, 1733076167, 1736576231, 1740181637, 1747814436, 1748228205, 1748607578, 1748879564, 1749656156, 1749801286, 1749917205, 1751493207, 1753343188, 1754031332, 1755148615, 1756600614, 1757157700, 1758044696, 1766992520, 1781815495, 1783388498, 1797368887, 1797628983, 1798686984, 1803876557, 1805502724, 1806981428, 1817013469, 1820327938, 1854245076, 1865714391, 1868312196, 1873281026, 1881288348, 1884120164, 1897398274, 1898223946, 1899170008, 1899796819, 1902116866, 1904283860, 1904946933, 1907085604, 1908709605, 1925049415, 1935549734, 1938817026, 1948778498, 1965634084, 1967760215, 1967957189, 1970798594, 1971461414, 1971466997, 1971628838, 1971938532, 1973040373, 1974771450, 1976348214, 1982173479, 1983002201, 1983633431, 1986140359, 1986527234, 1988486813, 1988763672, 1989812374, 1990074116, 1990969577, 1991909525, 1998724870, 1999397992, 2000158722, 2000525512, 2000965834, 2001309869, 2001349720, 2001392795, 2001392798, 2002780162, 2003062853, 2004557973, 2004635806, 2005160150, 2005231925, 2005324101, 2005543979, 2005766372, 2006028454, 2006329158, 2006592552, 2006974466, 2007601444, 2007803172, 2008133709, 2008325940, 2008851557, 2009276567, 2021937364, 2051837468, 2055515017, 2066000646, 2068523856, 2072193862, 2083120164, 2087012585, 2091479332, 2092557349, 50908899, 50916387, 51438659, 51961587, 51965683, 52486755, 52490899, 54054451, 55104723, 55111395, 56677619, 56682579, 57205395, 57207619, 57731155, 57733651, 59244545, 59821379, 60345171, 60347747, 60351123, 60352339, 60875283, 61925907, 62450211, 62974707, 67108865, 68681729, 876609538, 910163970, 943718402, 1679960596, 1682186266, 1685703382, 1686491348, 1699324759, 1703936002, 1713736758, 1715310660, 1719741029, 1730965751, 1732069431, 1733054663, 1733372532, 1736200310, 1736576583, 1738539010, 1747048757, 1747306711, 1747838298, 1748225318, 1748346119, 1748359220, 1748621670, 1748846791, 1749272732, 1749649513, 1749673195, 1749723735, 1749813486, 1749905526, 1749932347, 1751386406, 1752979652, 1753319686, 1753467414, 1753588936, 1754634617, 1755076808, 1755158905, 1756474198, 1756625221, 1757146773, 1757259017, 1757293380, 1763839627, 1766632184, 1771722827, 1773808452, 1782357526, 1783388497, 1786534215, 1797361975, 1797540167, 1797585096, 1797645367, 1798677556, 1798693940, 1803876550, 1803929812, 1805233752, 1805647874, 1806806678, 1807501636, 1813512194, 1818230786, 1818755074, 1853642948, 1854228698, 1857622310, 1864368130, 1865773108, 1867237670, 1868641064, 1870268949, 1873350948, 1874102998, 1881498736, 1881669634, 1887579800, 1889085973, 1897999926, 1898223945, 1898223949, 1898971138, 1899272519, 1899694294, 1900544002, 1901940917, 1902641154, 1903761465, 1904285766, 1904515399, 1905563974, 1906135367, 1907435316, 1907959605, 1909280949, 1919418370, 1925844629, 1934172497, 1938171179, 1938173140, 1939219752, 1941221172, 1963982850, 1965334268, 1966223078, 1967128578, 1967788867, 1967795958, 1968053806, 1968840263, 1970938456 };
+staticJArray<int32_t,int32_t> nsHtml5ElementName::ELEMENT_HASHES = { ELEMENT_HASHES_DATA, MOZ_ARRAY_LENGTH(ELEMENT_HASHES_DATA) };
+void
+nsHtml5ElementName::initializeStatics()
+{
+ ELT_ISINDEX = new nsHtml5ElementName(nsHtml5Atoms::isindex, nsHtml5Atoms::isindex, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ISINDEX | SPECIAL);
+ ELT_ANNOTATION_XML = new nsHtml5ElementName(nsHtml5Atoms::annotation_xml, nsHtml5Atoms::annotation_xml, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ANNOTATION_XML | SCOPING_AS_MATHML);
+ ELT_AND = new nsHtml5ElementName(nsHtml5Atoms::and_, nsHtml5Atoms::and_, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ARG = new nsHtml5ElementName(nsHtml5Atoms::arg, nsHtml5Atoms::arg, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ABS = new nsHtml5ElementName(nsHtml5Atoms::abs, nsHtml5Atoms::abs, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_BIG = new nsHtml5ElementName(nsHtml5Atoms::big, nsHtml5Atoms::big, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_BDO = new nsHtml5ElementName(nsHtml5Atoms::bdo, nsHtml5Atoms::bdo, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CSC = new nsHtml5ElementName(nsHtml5Atoms::csc, nsHtml5Atoms::csc, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_COL = new nsHtml5ElementName(nsHtml5Atoms::col, nsHtml5Atoms::col, NS_NewHTMLTableColElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::COL | SPECIAL);
+ ELT_COS = new nsHtml5ElementName(nsHtml5Atoms::cos, nsHtml5Atoms::cos, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_COT = new nsHtml5ElementName(nsHtml5Atoms::cot, nsHtml5Atoms::cot, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DEL = new nsHtml5ElementName(nsHtml5Atoms::del, nsHtml5Atoms::del, NS_NewHTMLModElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DFN = new nsHtml5ElementName(nsHtml5Atoms::dfn, nsHtml5Atoms::dfn, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DIR = new nsHtml5ElementName(nsHtml5Atoms::dir, nsHtml5Atoms::dir, NS_NewHTMLSharedElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_DIV = new nsHtml5ElementName(nsHtml5Atoms::div, nsHtml5Atoms::div, NS_NewHTMLDivElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL);
+ ELT_EXP = new nsHtml5ElementName(nsHtml5Atoms::exp, nsHtml5Atoms::exp, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_GCD = new nsHtml5ElementName(nsHtml5Atoms::gcd, nsHtml5Atoms::gcd, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_GEQ = new nsHtml5ElementName(nsHtml5Atoms::geq, nsHtml5Atoms::geq, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_IMG = new nsHtml5ElementName(nsHtml5Atoms::img, nsHtml5Atoms::img, NS_NewHTMLImageElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::IMG | SPECIAL);
+ ELT_INS = new nsHtml5ElementName(nsHtml5Atoms::ins, nsHtml5Atoms::ins, NS_NewHTMLModElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_INT = new nsHtml5ElementName(nsHtml5Atoms::int_, nsHtml5Atoms::int_, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_KBD = new nsHtml5ElementName(nsHtml5Atoms::kbd, nsHtml5Atoms::kbd, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LOG = new nsHtml5ElementName(nsHtml5Atoms::log, nsHtml5Atoms::log, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LCM = new nsHtml5ElementName(nsHtml5Atoms::lcm, nsHtml5Atoms::lcm, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LEQ = new nsHtml5ElementName(nsHtml5Atoms::leq, nsHtml5Atoms::leq, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MTD = new nsHtml5ElementName(nsHtml5Atoms::mtd, nsHtml5Atoms::mtd, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MIN = new nsHtml5ElementName(nsHtml5Atoms::min, nsHtml5Atoms::min, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MAP = new nsHtml5ElementName(nsHtml5Atoms::map, nsHtml5Atoms::map, NS_NewHTMLMapElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MTR = new nsHtml5ElementName(nsHtml5Atoms::mtr, nsHtml5Atoms::mtr, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MAX = new nsHtml5ElementName(nsHtml5Atoms::max, nsHtml5Atoms::max, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_NEQ = new nsHtml5ElementName(nsHtml5Atoms::neq, nsHtml5Atoms::neq, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_NOT = new nsHtml5ElementName(nsHtml5Atoms::not_, nsHtml5Atoms::not_, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_NAV = new nsHtml5ElementName(nsHtml5Atoms::nav, nsHtml5Atoms::nav, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_PRE = new nsHtml5ElementName(nsHtml5Atoms::pre, nsHtml5Atoms::pre, NS_NewHTMLPreElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::PRE_OR_LISTING | SPECIAL);
+ ELT_A = new nsHtml5ElementName(nsHtml5Atoms::a, nsHtml5Atoms::a, NS_NewHTMLAnchorElement, NS_NewSVGAElement, nsHtml5TreeBuilder::A);
+ ELT_B = new nsHtml5ElementName(nsHtml5Atoms::b, nsHtml5Atoms::b, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_RTC = new nsHtml5ElementName(nsHtml5Atoms::rtc, nsHtml5Atoms::rtc, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::RB_OR_RTC | OPTIONAL_END_TAG);
+ ELT_REM = new nsHtml5ElementName(nsHtml5Atoms::rem, nsHtml5Atoms::rem, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SUB = new nsHtml5ElementName(nsHtml5Atoms::sub, nsHtml5Atoms::sub, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR);
+ ELT_SEC = new nsHtml5ElementName(nsHtml5Atoms::sec, nsHtml5Atoms::sec, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SVG = new nsHtml5ElementName(nsHtml5Atoms::svg, nsHtml5Atoms::svg, NS_NewHTMLUnknownElement, NS_NewSVGSVGElement, nsHtml5TreeBuilder::SVG);
+ ELT_SUM = new nsHtml5ElementName(nsHtml5Atoms::sum, nsHtml5Atoms::sum, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SIN = new nsHtml5ElementName(nsHtml5Atoms::sin, nsHtml5Atoms::sin, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SEP = new nsHtml5ElementName(nsHtml5Atoms::sep, nsHtml5Atoms::sep, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SUP = new nsHtml5ElementName(nsHtml5Atoms::sup, nsHtml5Atoms::sup, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR);
+ ELT_SET = new nsHtml5ElementName(nsHtml5Atoms::set, nsHtml5Atoms::set, NS_NewHTMLUnknownElement, NS_NewSVGSetElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TAN = new nsHtml5ElementName(nsHtml5Atoms::tan, nsHtml5Atoms::tan, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_USE = new nsHtml5ElementName(nsHtml5Atoms::use, nsHtml5Atoms::use, NS_NewHTMLUnknownElement, NS_NewSVGUseElement, nsHtml5TreeBuilder::OTHER);
+ ELT_VAR = new nsHtml5ElementName(nsHtml5Atoms::var, nsHtml5Atoms::var, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR);
+ ELT_G = new nsHtml5ElementName(nsHtml5Atoms::g, nsHtml5Atoms::g, NS_NewHTMLUnknownElement, NS_NewSVGGElement, nsHtml5TreeBuilder::OTHER);
+ ELT_WBR = new nsHtml5ElementName(nsHtml5Atoms::wbr, nsHtml5Atoms::wbr, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::AREA_OR_WBR | SPECIAL);
+ ELT_XMP = new nsHtml5ElementName(nsHtml5Atoms::xmp, nsHtml5Atoms::xmp, NS_NewHTMLPreElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::XMP | SPECIAL);
+ ELT_XOR = new nsHtml5ElementName(nsHtml5Atoms::xor_, nsHtml5Atoms::xor_, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_I = new nsHtml5ElementName(nsHtml5Atoms::i, nsHtml5Atoms::i, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_P = new nsHtml5ElementName(nsHtml5Atoms::p, nsHtml5Atoms::p, NS_NewHTMLParagraphElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::P | SPECIAL | OPTIONAL_END_TAG);
+ ELT_Q = new nsHtml5ElementName(nsHtml5Atoms::q, nsHtml5Atoms::q, NS_NewHTMLSharedElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_S = new nsHtml5ElementName(nsHtml5Atoms::s, nsHtml5Atoms::s, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_U = new nsHtml5ElementName(nsHtml5Atoms::u, nsHtml5Atoms::u, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_H1 = new nsHtml5ElementName(nsHtml5Atoms::h1, nsHtml5Atoms::h1, NS_NewHTMLHeadingElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ ELT_H2 = new nsHtml5ElementName(nsHtml5Atoms::h2, nsHtml5Atoms::h2, NS_NewHTMLHeadingElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ ELT_H3 = new nsHtml5ElementName(nsHtml5Atoms::h3, nsHtml5Atoms::h3, NS_NewHTMLHeadingElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ ELT_H4 = new nsHtml5ElementName(nsHtml5Atoms::h4, nsHtml5Atoms::h4, NS_NewHTMLHeadingElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ ELT_H5 = new nsHtml5ElementName(nsHtml5Atoms::h5, nsHtml5Atoms::h5, NS_NewHTMLHeadingElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ ELT_H6 = new nsHtml5ElementName(nsHtml5Atoms::h6, nsHtml5Atoms::h6, NS_NewHTMLHeadingElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 | SPECIAL);
+ ELT_AREA = new nsHtml5ElementName(nsHtml5Atoms::area, nsHtml5Atoms::area, NS_NewHTMLAreaElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::AREA_OR_WBR | SPECIAL);
+ ELT_DATA = new nsHtml5ElementName(nsHtml5Atoms::data, nsHtml5Atoms::data, NS_NewHTMLDataElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_EULERGAMMA = new nsHtml5ElementName(nsHtml5Atoms::eulergamma, nsHtml5Atoms::eulergamma, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FEFUNCA = new nsHtml5ElementName(nsHtml5Atoms::fefunca, nsHtml5Atoms::feFuncA, NS_NewHTMLUnknownElement, NS_NewSVGFEFuncAElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LAMBDA = new nsHtml5ElementName(nsHtml5Atoms::lambda, nsHtml5Atoms::lambda, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_METADATA = new nsHtml5ElementName(nsHtml5Atoms::metadata, nsHtml5Atoms::metadata, NS_NewHTMLUnknownElement, NS_NewSVGMetadataElement, nsHtml5TreeBuilder::OTHER);
+ ELT_META = new nsHtml5ElementName(nsHtml5Atoms::meta, nsHtml5Atoms::meta, NS_NewHTMLMetaElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::META | SPECIAL);
+ ELT_TEXTAREA = new nsHtml5ElementName(nsHtml5Atoms::textarea, nsHtml5Atoms::textarea, NS_NewHTMLTextAreaElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::TEXTAREA | SPECIAL);
+ ELT_FEFUNCB = new nsHtml5ElementName(nsHtml5Atoms::fefuncb, nsHtml5Atoms::feFuncB, NS_NewHTMLUnknownElement, NS_NewSVGFEFuncBElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MSUB = new nsHtml5ElementName(nsHtml5Atoms::msub, nsHtml5Atoms::msub, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_RB = new nsHtml5ElementName(nsHtml5Atoms::rb, nsHtml5Atoms::rb, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::RB_OR_RTC | OPTIONAL_END_TAG);
+ ELT_ARCSEC = new nsHtml5ElementName(nsHtml5Atoms::arcsec, nsHtml5Atoms::arcsec, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ARCCSC = new nsHtml5ElementName(nsHtml5Atoms::arccsc, nsHtml5Atoms::arccsc, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DEFINITION_SRC = new nsHtml5ElementName(nsHtml5Atoms::definition_src, nsHtml5Atoms::definition_src, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DESC = new nsHtml5ElementName(nsHtml5Atoms::desc, nsHtml5Atoms::desc, NS_NewHTMLUnknownElement, NS_NewSVGDescElement, nsHtml5TreeBuilder::FOREIGNOBJECT_OR_DESC | SCOPING_AS_SVG);
+ ELT_FONT_FACE_SRC = new nsHtml5ElementName(nsHtml5Atoms::font_face_src, nsHtml5Atoms::font_face_src, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MFRAC = new nsHtml5ElementName(nsHtml5Atoms::mfrac, nsHtml5Atoms::mfrac, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DD = new nsHtml5ElementName(nsHtml5Atoms::dd, nsHtml5Atoms::dd, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::DD_OR_DT | SPECIAL | OPTIONAL_END_TAG);
+ ELT_BGSOUND = new nsHtml5ElementName(nsHtml5Atoms::bgsound, nsHtml5Atoms::bgsound, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::LINK_OR_BASEFONT_OR_BGSOUND | SPECIAL);
+ ELT_CARD = new nsHtml5ElementName(nsHtml5Atoms::card, nsHtml5Atoms::card, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DISCARD = new nsHtml5ElementName(nsHtml5Atoms::discard, nsHtml5Atoms::discard, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_EMBED = new nsHtml5ElementName(nsHtml5Atoms::embed, nsHtml5Atoms::embed, NS_NewHTMLSharedObjectElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::EMBED | SPECIAL);
+ ELT_FEBLEND = new nsHtml5ElementName(nsHtml5Atoms::feblend, nsHtml5Atoms::feBlend, NS_NewHTMLUnknownElement, NS_NewSVGFEBlendElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FEFLOOD = new nsHtml5ElementName(nsHtml5Atoms::feflood, nsHtml5Atoms::feFlood, NS_NewHTMLUnknownElement, NS_NewSVGFEFloodElement, nsHtml5TreeBuilder::OTHER);
+ ELT_GRAD = new nsHtml5ElementName(nsHtml5Atoms::grad, nsHtml5Atoms::grad, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_HEAD = new nsHtml5ElementName(nsHtml5Atoms::head, nsHtml5Atoms::head, NS_NewHTMLSharedElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::HEAD | SPECIAL | OPTIONAL_END_TAG);
+ ELT_LEGEND = new nsHtml5ElementName(nsHtml5Atoms::legend, nsHtml5Atoms::legend, NS_NewHTMLLegendElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MFENCED = new nsHtml5ElementName(nsHtml5Atoms::mfenced, nsHtml5Atoms::mfenced, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MPADDED = new nsHtml5ElementName(nsHtml5Atoms::mpadded, nsHtml5Atoms::mpadded, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_NOEMBED = new nsHtml5ElementName(nsHtml5Atoms::noembed, nsHtml5Atoms::noembed, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::NOEMBED | SPECIAL);
+ ELT_TD = new nsHtml5ElementName(nsHtml5Atoms::td, nsHtml5Atoms::td, NS_NewHTMLTableCellElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::TD_OR_TH | SPECIAL | SCOPING | OPTIONAL_END_TAG);
+ ELT_THEAD = new nsHtml5ElementName(nsHtml5Atoms::thead, nsHtml5Atoms::thead, NS_NewHTMLTableSectionElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::TBODY_OR_THEAD_OR_TFOOT | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG);
+ ELT_ASIDE = new nsHtml5ElementName(nsHtml5Atoms::aside, nsHtml5Atoms::aside, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_ARTICLE = new nsHtml5ElementName(nsHtml5Atoms::article, nsHtml5Atoms::article, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_ANIMATE = new nsHtml5ElementName(nsHtml5Atoms::animate, nsHtml5Atoms::animate, NS_NewHTMLUnknownElement, NS_NewSVGAnimateElement, nsHtml5TreeBuilder::OTHER);
+ ELT_BASE = new nsHtml5ElementName(nsHtml5Atoms::base, nsHtml5Atoms::base, NS_NewHTMLSharedElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::BASE | SPECIAL);
+ ELT_BLOCKQUOTE = new nsHtml5ElementName(nsHtml5Atoms::blockquote, nsHtml5Atoms::blockquote, NS_NewHTMLSharedElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL);
+ ELT_CODE = new nsHtml5ElementName(nsHtml5Atoms::code, nsHtml5Atoms::code, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_CIRCLE = new nsHtml5ElementName(nsHtml5Atoms::circle, nsHtml5Atoms::circle, NS_NewHTMLUnknownElement, NS_NewSVGCircleElement, nsHtml5TreeBuilder::OTHER);
+ ELT_COLOR_PROFILE = new nsHtml5ElementName(nsHtml5Atoms::color_profile, nsHtml5Atoms::color_profile, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_COMPOSE = new nsHtml5ElementName(nsHtml5Atoms::compose, nsHtml5Atoms::compose, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CONJUGATE = new nsHtml5ElementName(nsHtml5Atoms::conjugate, nsHtml5Atoms::conjugate, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CITE = new nsHtml5ElementName(nsHtml5Atoms::cite, nsHtml5Atoms::cite, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DIVERGENCE = new nsHtml5ElementName(nsHtml5Atoms::divergence, nsHtml5Atoms::divergence, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DIVIDE = new nsHtml5ElementName(nsHtml5Atoms::divide, nsHtml5Atoms::divide, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DEGREE = new nsHtml5ElementName(nsHtml5Atoms::degree, nsHtml5Atoms::degree, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DECLARE = new nsHtml5ElementName(nsHtml5Atoms::declare, nsHtml5Atoms::declare, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DATATEMPLATE = new nsHtml5ElementName(nsHtml5Atoms::datatemplate, nsHtml5Atoms::datatemplate, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_EXPONENTIALE = new nsHtml5ElementName(nsHtml5Atoms::exponentiale, nsHtml5Atoms::exponentiale, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ELLIPSE = new nsHtml5ElementName(nsHtml5Atoms::ellipse, nsHtml5Atoms::ellipse, NS_NewHTMLUnknownElement, NS_NewSVGEllipseElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FONT_FACE = new nsHtml5ElementName(nsHtml5Atoms::font_face, nsHtml5Atoms::font_face, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FETURBULENCE = new nsHtml5ElementName(nsHtml5Atoms::feturbulence, nsHtml5Atoms::feTurbulence, NS_NewHTMLUnknownElement, NS_NewSVGFETurbulenceElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FEMERGENODE = new nsHtml5ElementName(nsHtml5Atoms::femergenode, nsHtml5Atoms::feMergeNode, NS_NewHTMLUnknownElement, NS_NewSVGFEMergeNodeElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FEIMAGE = new nsHtml5ElementName(nsHtml5Atoms::feimage, nsHtml5Atoms::feImage, NS_NewHTMLUnknownElement, NS_NewSVGFEImageElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FEMERGE = new nsHtml5ElementName(nsHtml5Atoms::femerge, nsHtml5Atoms::feMerge, NS_NewHTMLUnknownElement, NS_NewSVGFEMergeElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FETILE = new nsHtml5ElementName(nsHtml5Atoms::fetile, nsHtml5Atoms::feTile, NS_NewHTMLUnknownElement, NS_NewSVGFETileElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FONT_FACE_NAME = new nsHtml5ElementName(nsHtml5Atoms::font_face_name, nsHtml5Atoms::font_face_name, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FRAME = new nsHtml5ElementName(nsHtml5Atoms::frame, nsHtml5Atoms::frame, NS_NewHTMLFrameElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::FRAME | SPECIAL);
+ ELT_FIGURE = new nsHtml5ElementName(nsHtml5Atoms::figure, nsHtml5Atoms::figure, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_FALSE = new nsHtml5ElementName(nsHtml5Atoms::false_, nsHtml5Atoms::false_, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FECOMPOSITE = new nsHtml5ElementName(nsHtml5Atoms::fecomposite, nsHtml5Atoms::feComposite, NS_NewHTMLUnknownElement, NS_NewSVGFECompositeElement, nsHtml5TreeBuilder::OTHER);
+ ELT_IMAGE = new nsHtml5ElementName(nsHtml5Atoms::image, nsHtml5Atoms::image, NS_NewHTMLElement, NS_NewSVGImageElement, nsHtml5TreeBuilder::IMAGE);
+ ELT_IFRAME = new nsHtml5ElementName(nsHtml5Atoms::iframe, nsHtml5Atoms::iframe, NS_NewHTMLIFrameElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::IFRAME | SPECIAL);
+ ELT_INVERSE = new nsHtml5ElementName(nsHtml5Atoms::inverse, nsHtml5Atoms::inverse, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LINE = new nsHtml5ElementName(nsHtml5Atoms::line, nsHtml5Atoms::line, NS_NewHTMLUnknownElement, NS_NewSVGLineElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LOGBASE = new nsHtml5ElementName(nsHtml5Atoms::logbase, nsHtml5Atoms::logbase, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MSPACE = new nsHtml5ElementName(nsHtml5Atoms::mspace, nsHtml5Atoms::mspace, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MODE = new nsHtml5ElementName(nsHtml5Atoms::mode, nsHtml5Atoms::mode, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MTABLE = new nsHtml5ElementName(nsHtml5Atoms::mtable, nsHtml5Atoms::mtable, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MSTYLE = new nsHtml5ElementName(nsHtml5Atoms::mstyle, nsHtml5Atoms::mstyle, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MENCLOSE = new nsHtml5ElementName(nsHtml5Atoms::menclose, nsHtml5Atoms::menclose, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_NONE = new nsHtml5ElementName(nsHtml5Atoms::none, nsHtml5Atoms::none, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_OTHERWISE = new nsHtml5ElementName(nsHtml5Atoms::otherwise, nsHtml5Atoms::otherwise, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PIECE = new nsHtml5ElementName(nsHtml5Atoms::piece, nsHtml5Atoms::piece, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_POLYLINE = new nsHtml5ElementName(nsHtml5Atoms::polyline, nsHtml5Atoms::polyline, NS_NewHTMLUnknownElement, NS_NewSVGPolylineElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PICTURE = new nsHtml5ElementName(nsHtml5Atoms::picture, nsHtml5Atoms::picture, NS_NewHTMLPictureElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PIECEWISE = new nsHtml5ElementName(nsHtml5Atoms::piecewise, nsHtml5Atoms::piecewise, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_RULE = new nsHtml5ElementName(nsHtml5Atoms::rule, nsHtml5Atoms::rule, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SOURCE = new nsHtml5ElementName(nsHtml5Atoms::source, nsHtml5Atoms::source, NS_NewHTMLSourceElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::PARAM_OR_SOURCE_OR_TRACK);
+ ELT_STRIKE = new nsHtml5ElementName(nsHtml5Atoms::strike, nsHtml5Atoms::strike, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_STYLE = new nsHtml5ElementName(nsHtml5Atoms::style, nsHtml5Atoms::style, NS_NewHTMLStyleElement, NS_NewSVGStyleElement, nsHtml5TreeBuilder::STYLE | SPECIAL);
+ ELT_TABLE = new nsHtml5ElementName(nsHtml5Atoms::table, nsHtml5Atoms::table, NS_NewHTMLTableElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::TABLE | SPECIAL | FOSTER_PARENTING | SCOPING);
+ ELT_TITLE = new nsHtml5ElementName(nsHtml5Atoms::title, nsHtml5Atoms::title, NS_NewHTMLTitleElement, NS_NewSVGTitleElement, nsHtml5TreeBuilder::TITLE | SPECIAL | SCOPING_AS_SVG);
+ ELT_TIME = new nsHtml5ElementName(nsHtml5Atoms::time, nsHtml5Atoms::time, NS_NewHTMLTimeElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TRANSPOSE = new nsHtml5ElementName(nsHtml5Atoms::transpose, nsHtml5Atoms::transpose, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TEMPLATE = new nsHtml5ElementName(nsHtml5Atoms::template_, nsHtml5Atoms::template_, NS_NewHTMLTemplateElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::TEMPLATE | SPECIAL | SCOPING);
+ ELT_TRUE = new nsHtml5ElementName(nsHtml5Atoms::true_, nsHtml5Atoms::true_, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_VARIANCE = new nsHtml5ElementName(nsHtml5Atoms::variance, nsHtml5Atoms::variance, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ALTGLYPHDEF = new nsHtml5ElementName(nsHtml5Atoms::altglyphdef, nsHtml5Atoms::altGlyphDef, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DIFF = new nsHtml5ElementName(nsHtml5Atoms::diff, nsHtml5Atoms::diff, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FACTOROF = new nsHtml5ElementName(nsHtml5Atoms::factorof, nsHtml5Atoms::factorof, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_GLYPHREF = new nsHtml5ElementName(nsHtml5Atoms::glyphref, nsHtml5Atoms::glyphRef, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PARTIALDIFF = new nsHtml5ElementName(nsHtml5Atoms::partialdiff, nsHtml5Atoms::partialdiff, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SETDIFF = new nsHtml5ElementName(nsHtml5Atoms::setdiff, nsHtml5Atoms::setdiff, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TREF = new nsHtml5ElementName(nsHtml5Atoms::tref, nsHtml5Atoms::tref, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CEILING = new nsHtml5ElementName(nsHtml5Atoms::ceiling, nsHtml5Atoms::ceiling, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DIALOG = new nsHtml5ElementName(nsHtml5Atoms::dialog, nsHtml5Atoms::dialog, NS_NewHTMLDialogElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_FEFUNCG = new nsHtml5ElementName(nsHtml5Atoms::fefuncg, nsHtml5Atoms::feFuncG, NS_NewHTMLUnknownElement, NS_NewSVGFEFuncGElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FEDIFFUSELIGHTING = new nsHtml5ElementName(nsHtml5Atoms::fediffuselighting, nsHtml5Atoms::feDiffuseLighting, NS_NewHTMLUnknownElement, NS_NewSVGFEDiffuseLightingElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FESPECULARLIGHTING = new nsHtml5ElementName(nsHtml5Atoms::fespecularlighting, nsHtml5Atoms::feSpecularLighting, NS_NewHTMLUnknownElement, NS_NewSVGFESpecularLightingElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LISTING = new nsHtml5ElementName(nsHtml5Atoms::listing, nsHtml5Atoms::listing, NS_NewHTMLPreElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::PRE_OR_LISTING | SPECIAL);
+ ELT_STRONG = new nsHtml5ElementName(nsHtml5Atoms::strong, nsHtml5Atoms::strong, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_ARCSECH = new nsHtml5ElementName(nsHtml5Atoms::arcsech, nsHtml5Atoms::arcsech, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ARCCSCH = new nsHtml5ElementName(nsHtml5Atoms::arccsch, nsHtml5Atoms::arccsch, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ARCTANH = new nsHtml5ElementName(nsHtml5Atoms::arctanh, nsHtml5Atoms::arctanh, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ARCSINH = new nsHtml5ElementName(nsHtml5Atoms::arcsinh, nsHtml5Atoms::arcsinh, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ALTGLYPH = new nsHtml5ElementName(nsHtml5Atoms::altglyph, nsHtml5Atoms::altGlyph, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ARCCOSH = new nsHtml5ElementName(nsHtml5Atoms::arccosh, nsHtml5Atoms::arccosh, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ARCCOTH = new nsHtml5ElementName(nsHtml5Atoms::arccoth, nsHtml5Atoms::arccoth, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CSCH = new nsHtml5ElementName(nsHtml5Atoms::csch, nsHtml5Atoms::csch, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_COSH = new nsHtml5ElementName(nsHtml5Atoms::cosh, nsHtml5Atoms::cosh, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CLIPPATH = new nsHtml5ElementName(nsHtml5Atoms::clippath, nsHtml5Atoms::clipPath, NS_NewHTMLUnknownElement, NS_NewSVGClipPathElement, nsHtml5TreeBuilder::OTHER);
+ ELT_COTH = new nsHtml5ElementName(nsHtml5Atoms::coth, nsHtml5Atoms::coth, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_GLYPH = new nsHtml5ElementName(nsHtml5Atoms::glyph, nsHtml5Atoms::glyph, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MGLYPH = new nsHtml5ElementName(nsHtml5Atoms::mglyph, nsHtml5Atoms::mglyph, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::MGLYPH_OR_MALIGNMARK);
+ ELT_MISSING_GLYPH = new nsHtml5ElementName(nsHtml5Atoms::missing_glyph, nsHtml5Atoms::missing_glyph, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MATH = new nsHtml5ElementName(nsHtml5Atoms::math, nsHtml5Atoms::math, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::MATH);
+ ELT_MPATH = new nsHtml5ElementName(nsHtml5Atoms::mpath, nsHtml5Atoms::mpath, NS_NewHTMLUnknownElement, NS_NewSVGMPathElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PREFETCH = new nsHtml5ElementName(nsHtml5Atoms::prefetch, nsHtml5Atoms::prefetch, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PATH = new nsHtml5ElementName(nsHtml5Atoms::path, nsHtml5Atoms::path, NS_NewHTMLUnknownElement, NS_NewSVGPathElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TH = new nsHtml5ElementName(nsHtml5Atoms::th, nsHtml5Atoms::th, NS_NewHTMLTableCellElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::TD_OR_TH | SPECIAL | SCOPING | OPTIONAL_END_TAG);
+ ELT_SECH = new nsHtml5ElementName(nsHtml5Atoms::sech, nsHtml5Atoms::sech, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SWITCH = new nsHtml5ElementName(nsHtml5Atoms::switch_, nsHtml5Atoms::switch_, NS_NewHTMLUnknownElement, NS_NewSVGSwitchElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SINH = new nsHtml5ElementName(nsHtml5Atoms::sinh, nsHtml5Atoms::sinh, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TANH = new nsHtml5ElementName(nsHtml5Atoms::tanh, nsHtml5Atoms::tanh, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TEXTPATH = new nsHtml5ElementName(nsHtml5Atoms::textpath, nsHtml5Atoms::textPath, NS_NewHTMLUnknownElement, NS_NewSVGTextPathElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CI = new nsHtml5ElementName(nsHtml5Atoms::ci, nsHtml5Atoms::ci, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FONT_FACE_URI = new nsHtml5ElementName(nsHtml5Atoms::font_face_uri, nsHtml5Atoms::font_face_uri, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LI = new nsHtml5ElementName(nsHtml5Atoms::li, nsHtml5Atoms::li, NS_NewHTMLLIElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::LI | SPECIAL | OPTIONAL_END_TAG);
+ ELT_IMAGINARYI = new nsHtml5ElementName(nsHtml5Atoms::imaginaryi, nsHtml5Atoms::imaginaryi, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MI = new nsHtml5ElementName(nsHtml5Atoms::mi, nsHtml5Atoms::mi, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML);
+ ELT_PI = new nsHtml5ElementName(nsHtml5Atoms::pi, nsHtml5Atoms::pi, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LINK = new nsHtml5ElementName(nsHtml5Atoms::link, nsHtml5Atoms::link, NS_NewHTMLLinkElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::LINK_OR_BASEFONT_OR_BGSOUND | SPECIAL);
+ ELT_MARK = new nsHtml5ElementName(nsHtml5Atoms::mark, nsHtml5Atoms::mark, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MALIGNMARK = new nsHtml5ElementName(nsHtml5Atoms::malignmark, nsHtml5Atoms::malignmark, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::MGLYPH_OR_MALIGNMARK);
+ ELT_MASK = new nsHtml5ElementName(nsHtml5Atoms::mask, nsHtml5Atoms::mask, NS_NewHTMLUnknownElement, NS_NewSVGMaskElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TBREAK = new nsHtml5ElementName(nsHtml5Atoms::tbreak, nsHtml5Atoms::tbreak, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TRACK = new nsHtml5ElementName(nsHtml5Atoms::track, nsHtml5Atoms::track, NS_NewHTMLTrackElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::PARAM_OR_SOURCE_OR_TRACK | SPECIAL);
+ ELT_DL = new nsHtml5ElementName(nsHtml5Atoms::dl, nsHtml5Atoms::dl, NS_NewHTMLSharedListElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::UL_OR_OL_OR_DL | SPECIAL);
+ ELT_CSYMBOL = new nsHtml5ElementName(nsHtml5Atoms::csymbol, nsHtml5Atoms::csymbol, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CURL = new nsHtml5ElementName(nsHtml5Atoms::curl, nsHtml5Atoms::curl, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FACTORIAL = new nsHtml5ElementName(nsHtml5Atoms::factorial, nsHtml5Atoms::factorial, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FORALL = new nsHtml5ElementName(nsHtml5Atoms::forall, nsHtml5Atoms::forall, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_HTML = new nsHtml5ElementName(nsHtml5Atoms::html, nsHtml5Atoms::html, NS_NewHTMLSharedElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::HTML | SPECIAL | SCOPING | OPTIONAL_END_TAG);
+ ELT_INTERVAL = new nsHtml5ElementName(nsHtml5Atoms::interval, nsHtml5Atoms::interval, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_OL = new nsHtml5ElementName(nsHtml5Atoms::ol, nsHtml5Atoms::ol, NS_NewHTMLSharedListElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::UL_OR_OL_OR_DL | SPECIAL);
+ ELT_LABEL = new nsHtml5ElementName(nsHtml5Atoms::label, nsHtml5Atoms::label, NS_NewHTMLLabelElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_UL = new nsHtml5ElementName(nsHtml5Atoms::ul, nsHtml5Atoms::ul, NS_NewHTMLSharedListElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::UL_OR_OL_OR_DL | SPECIAL);
+ ELT_REAL = new nsHtml5ElementName(nsHtml5Atoms::real, nsHtml5Atoms::real, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SMALL = new nsHtml5ElementName(nsHtml5Atoms::small_, nsHtml5Atoms::small_, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_SYMBOL = new nsHtml5ElementName(nsHtml5Atoms::symbol, nsHtml5Atoms::symbol, NS_NewHTMLUnknownElement, NS_NewSVGSymbolElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ALTGLYPHITEM = new nsHtml5ElementName(nsHtml5Atoms::altglyphitem, nsHtml5Atoms::altGlyphItem, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ANIMATETRANSFORM = new nsHtml5ElementName(nsHtml5Atoms::animatetransform, nsHtml5Atoms::animateTransform, NS_NewHTMLUnknownElement, NS_NewSVGAnimateTransformElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ACRONYM = new nsHtml5ElementName(nsHtml5Atoms::acronym, nsHtml5Atoms::acronym, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_EM = new nsHtml5ElementName(nsHtml5Atoms::em, nsHtml5Atoms::em, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_FORM = new nsHtml5ElementName(nsHtml5Atoms::form, nsHtml5Atoms::form, NS_NewHTMLFormElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::FORM | SPECIAL);
+ ELT_MENUITEM = new nsHtml5ElementName(nsHtml5Atoms::menuitem, nsHtml5Atoms::menuitem, NS_NewHTMLMenuItemElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::MENUITEM);
+ ELT_MPHANTOM = new nsHtml5ElementName(nsHtml5Atoms::mphantom, nsHtml5Atoms::mphantom, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PARAM = new nsHtml5ElementName(nsHtml5Atoms::param, nsHtml5Atoms::param, NS_NewHTMLSharedElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::PARAM_OR_SOURCE_OR_TRACK | SPECIAL);
+ ELT_CN = new nsHtml5ElementName(nsHtml5Atoms::cn, nsHtml5Atoms::cn, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ARCTAN = new nsHtml5ElementName(nsHtml5Atoms::arctan, nsHtml5Atoms::arctan, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ARCSIN = new nsHtml5ElementName(nsHtml5Atoms::arcsin, nsHtml5Atoms::arcsin, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ANIMATION = new nsHtml5ElementName(nsHtml5Atoms::animation, nsHtml5Atoms::animation, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ANNOTATION = new nsHtml5ElementName(nsHtml5Atoms::annotation, nsHtml5Atoms::annotation, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ANIMATEMOTION = new nsHtml5ElementName(nsHtml5Atoms::animatemotion, nsHtml5Atoms::animateMotion, NS_NewHTMLUnknownElement, NS_NewSVGAnimateMotionElement, nsHtml5TreeBuilder::OTHER);
+ ELT_BUTTON = new nsHtml5ElementName(nsHtml5Atoms::button, nsHtml5Atoms::button, NS_NewHTMLButtonElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::BUTTON | SPECIAL);
+ ELT_FN = new nsHtml5ElementName(nsHtml5Atoms::fn, nsHtml5Atoms::fn, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CODOMAIN = new nsHtml5ElementName(nsHtml5Atoms::codomain, nsHtml5Atoms::codomain, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CAPTION = new nsHtml5ElementName(nsHtml5Atoms::caption, nsHtml5Atoms::caption, NS_NewHTMLTableCaptionElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::CAPTION | SPECIAL | SCOPING);
+ ELT_CONDITION = new nsHtml5ElementName(nsHtml5Atoms::condition, nsHtml5Atoms::condition, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DOMAIN = new nsHtml5ElementName(nsHtml5Atoms::domain, nsHtml5Atoms::domain, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DOMAINOFAPPLICATION = new nsHtml5ElementName(nsHtml5Atoms::domainofapplication, nsHtml5Atoms::domainofapplication, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_IN = new nsHtml5ElementName(nsHtml5Atoms::in, nsHtml5Atoms::in, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FIGCAPTION = new nsHtml5ElementName(nsHtml5Atoms::figcaption, nsHtml5Atoms::figcaption, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_HKERN = new nsHtml5ElementName(nsHtml5Atoms::hkern, nsHtml5Atoms::hkern, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LN = new nsHtml5ElementName(nsHtml5Atoms::ln, nsHtml5Atoms::ln, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MN = new nsHtml5ElementName(nsHtml5Atoms::mn, nsHtml5Atoms::mn, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML);
+ ELT_KEYGEN = new nsHtml5ElementName(nsHtml5Atoms::keygen, nsHtml5Atoms::keygen, NS_NewHTMLSpanElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::KEYGEN);
+ ELT_LAPLACIAN = new nsHtml5ElementName(nsHtml5Atoms::laplacian, nsHtml5Atoms::laplacian, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MEAN = new nsHtml5ElementName(nsHtml5Atoms::mean, nsHtml5Atoms::mean, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MEDIAN = new nsHtml5ElementName(nsHtml5Atoms::median, nsHtml5Atoms::median, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MAIN = new nsHtml5ElementName(nsHtml5Atoms::main, nsHtml5Atoms::main, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_MACTION = new nsHtml5ElementName(nsHtml5Atoms::maction, nsHtml5Atoms::maction, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_NOTIN = new nsHtml5ElementName(nsHtml5Atoms::notin, nsHtml5Atoms::notin, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_OPTION = new nsHtml5ElementName(nsHtml5Atoms::option, nsHtml5Atoms::option, NS_NewHTMLOptionElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OPTION | OPTIONAL_END_TAG);
+ ELT_POLYGON = new nsHtml5ElementName(nsHtml5Atoms::polygon, nsHtml5Atoms::polygon, NS_NewHTMLUnknownElement, NS_NewSVGPolygonElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PATTERN = new nsHtml5ElementName(nsHtml5Atoms::pattern, nsHtml5Atoms::pattern, NS_NewHTMLUnknownElement, NS_NewSVGPatternElement, nsHtml5TreeBuilder::OTHER);
+ ELT_RELN = new nsHtml5ElementName(nsHtml5Atoms::reln, nsHtml5Atoms::reln, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SPAN = new nsHtml5ElementName(nsHtml5Atoms::span, nsHtml5Atoms::span, NS_NewHTMLSpanElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR);
+ ELT_SECTION = new nsHtml5ElementName(nsHtml5Atoms::section, nsHtml5Atoms::section, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_TSPAN = new nsHtml5ElementName(nsHtml5Atoms::tspan, nsHtml5Atoms::tspan, NS_NewHTMLUnknownElement, NS_NewSVGTSpanElement, nsHtml5TreeBuilder::OTHER);
+ ELT_UNION = new nsHtml5ElementName(nsHtml5Atoms::union_, nsHtml5Atoms::union_, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_VKERN = new nsHtml5ElementName(nsHtml5Atoms::vkern, nsHtml5Atoms::vkern, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_AUDIO = new nsHtml5ElementName(nsHtml5Atoms::audio, nsHtml5Atoms::audio, NS_NewHTMLAudioElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MO = new nsHtml5ElementName(nsHtml5Atoms::mo, nsHtml5Atoms::mo, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML);
+ ELT_TENDSTO = new nsHtml5ElementName(nsHtml5Atoms::tendsto, nsHtml5Atoms::tendsto, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_VIDEO = new nsHtml5ElementName(nsHtml5Atoms::video, nsHtml5Atoms::video, NS_NewHTMLVideoElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_COLGROUP = new nsHtml5ElementName(nsHtml5Atoms::colgroup, nsHtml5Atoms::colgroup, NS_NewHTMLTableColElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::COLGROUP | SPECIAL | OPTIONAL_END_TAG);
+ ELT_FEDISPLACEMENTMAP = new nsHtml5ElementName(nsHtml5Atoms::fedisplacementmap, nsHtml5Atoms::feDisplacementMap, NS_NewHTMLUnknownElement, NS_NewSVGFEDisplacementMapElement, nsHtml5TreeBuilder::OTHER);
+ ELT_HGROUP = new nsHtml5ElementName(nsHtml5Atoms::hgroup, nsHtml5Atoms::hgroup, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_MALIGNGROUP = new nsHtml5ElementName(nsHtml5Atoms::maligngroup, nsHtml5Atoms::maligngroup, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MSUBSUP = new nsHtml5ElementName(nsHtml5Atoms::msubsup, nsHtml5Atoms::msubsup, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MSUP = new nsHtml5ElementName(nsHtml5Atoms::msup, nsHtml5Atoms::msup, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_RP = new nsHtml5ElementName(nsHtml5Atoms::rp, nsHtml5Atoms::rp, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::RT_OR_RP | OPTIONAL_END_TAG);
+ ELT_OPTGROUP = new nsHtml5ElementName(nsHtml5Atoms::optgroup, nsHtml5Atoms::optgroup, NS_NewHTMLOptGroupElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OPTGROUP | OPTIONAL_END_TAG);
+ ELT_SAMP = new nsHtml5ElementName(nsHtml5Atoms::samp, nsHtml5Atoms::samp, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_STOP = new nsHtml5ElementName(nsHtml5Atoms::stop, nsHtml5Atoms::stop, NS_NewHTMLUnknownElement, NS_NewSVGStopElement, nsHtml5TreeBuilder::OTHER);
+ ELT_EQ = new nsHtml5ElementName(nsHtml5Atoms::eq, nsHtml5Atoms::eq, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_BR = new nsHtml5ElementName(nsHtml5Atoms::br, nsHtml5Atoms::br, NS_NewHTMLBRElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::BR | SPECIAL);
+ ELT_ABBR = new nsHtml5ElementName(nsHtml5Atoms::abbr, nsHtml5Atoms::abbr, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ANIMATECOLOR = new nsHtml5ElementName(nsHtml5Atoms::animatecolor, nsHtml5Atoms::animateColor, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_BVAR = new nsHtml5ElementName(nsHtml5Atoms::bvar, nsHtml5Atoms::bvar, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_CENTER = new nsHtml5ElementName(nsHtml5Atoms::center, nsHtml5Atoms::center, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL);
+ ELT_CURSOR = new nsHtml5ElementName(nsHtml5Atoms::cursor, nsHtml5Atoms::cursor, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_HR = new nsHtml5ElementName(nsHtml5Atoms::hr, nsHtml5Atoms::hr, NS_NewHTMLHRElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::HR | SPECIAL);
+ ELT_FEFUNCR = new nsHtml5ElementName(nsHtml5Atoms::fefuncr, nsHtml5Atoms::feFuncR, NS_NewHTMLUnknownElement, NS_NewSVGFEFuncRElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FECOMPONENTTRANSFER = new nsHtml5ElementName(nsHtml5Atoms::fecomponenttransfer, nsHtml5Atoms::feComponentTransfer, NS_NewHTMLUnknownElement, NS_NewSVGFEComponentTransferElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FILTER = new nsHtml5ElementName(nsHtml5Atoms::filter, nsHtml5Atoms::filter, NS_NewHTMLUnknownElement, NS_NewSVGFilterElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FOOTER = new nsHtml5ElementName(nsHtml5Atoms::footer, nsHtml5Atoms::footer, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_FLOOR = new nsHtml5ElementName(nsHtml5Atoms::floor, nsHtml5Atoms::floor, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FEGAUSSIANBLUR = new nsHtml5ElementName(nsHtml5Atoms::fegaussianblur, nsHtml5Atoms::feGaussianBlur, NS_NewHTMLUnknownElement, NS_NewSVGFEGaussianBlurElement, nsHtml5TreeBuilder::OTHER);
+ ELT_HEADER = new nsHtml5ElementName(nsHtml5Atoms::header, nsHtml5Atoms::header, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_HANDLER = new nsHtml5ElementName(nsHtml5Atoms::handler, nsHtml5Atoms::handler, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_OR = new nsHtml5ElementName(nsHtml5Atoms::or_, nsHtml5Atoms::or_, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LISTENER = new nsHtml5ElementName(nsHtml5Atoms::listener, nsHtml5Atoms::listener, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MUNDER = new nsHtml5ElementName(nsHtml5Atoms::munder, nsHtml5Atoms::munder, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MARKER = new nsHtml5ElementName(nsHtml5Atoms::marker, nsHtml5Atoms::marker, NS_NewHTMLUnknownElement, NS_NewSVGMarkerElement, nsHtml5TreeBuilder::OTHER);
+ ELT_METER = new nsHtml5ElementName(nsHtml5Atoms::meter, nsHtml5Atoms::meter, NS_NewHTMLMeterElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MOVER = new nsHtml5ElementName(nsHtml5Atoms::mover, nsHtml5Atoms::mover, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MUNDEROVER = new nsHtml5ElementName(nsHtml5Atoms::munderover, nsHtml5Atoms::munderover, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MERROR = new nsHtml5ElementName(nsHtml5Atoms::merror, nsHtml5Atoms::merror, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MLABELEDTR = new nsHtml5ElementName(nsHtml5Atoms::mlabeledtr, nsHtml5Atoms::mlabeledtr, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_NOBR = new nsHtml5ElementName(nsHtml5Atoms::nobr, nsHtml5Atoms::nobr, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::NOBR);
+ ELT_NOTANUMBER = new nsHtml5ElementName(nsHtml5Atoms::notanumber, nsHtml5Atoms::notanumber, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_POWER = new nsHtml5ElementName(nsHtml5Atoms::power, nsHtml5Atoms::power, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TR = new nsHtml5ElementName(nsHtml5Atoms::tr, nsHtml5Atoms::tr, NS_NewHTMLTableRowElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::TR | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG);
+ ELT_SOLIDCOLOR = new nsHtml5ElementName(nsHtml5Atoms::solidcolor, nsHtml5Atoms::solidcolor, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SELECTOR = new nsHtml5ElementName(nsHtml5Atoms::selector, nsHtml5Atoms::selector, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_VECTOR = new nsHtml5ElementName(nsHtml5Atoms::vector, nsHtml5Atoms::vector, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ARCCOS = new nsHtml5ElementName(nsHtml5Atoms::arccos, nsHtml5Atoms::arccos, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ADDRESS = new nsHtml5ElementName(nsHtml5Atoms::address, nsHtml5Atoms::address, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_CANVAS = new nsHtml5ElementName(nsHtml5Atoms::canvas, nsHtml5Atoms::canvas, NS_NewHTMLCanvasElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_COMPLEXES = new nsHtml5ElementName(nsHtml5Atoms::complexes, nsHtml5Atoms::complexes, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DEFS = new nsHtml5ElementName(nsHtml5Atoms::defs, nsHtml5Atoms::defs, NS_NewHTMLUnknownElement, NS_NewSVGDefsElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DETAILS = new nsHtml5ElementName(nsHtml5Atoms::details, nsHtml5Atoms::details, NS_NewHTMLDetailsElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_EXISTS = new nsHtml5ElementName(nsHtml5Atoms::exists, nsHtml5Atoms::exists, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_IMPLIES = new nsHtml5ElementName(nsHtml5Atoms::implies, nsHtml5Atoms::implies, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_INTEGERS = new nsHtml5ElementName(nsHtml5Atoms::integers, nsHtml5Atoms::integers, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MS = new nsHtml5ElementName(nsHtml5Atoms::ms, nsHtml5Atoms::ms, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML);
+ ELT_MPRESCRIPTS = new nsHtml5ElementName(nsHtml5Atoms::mprescripts, nsHtml5Atoms::mprescripts, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MMULTISCRIPTS = new nsHtml5ElementName(nsHtml5Atoms::mmultiscripts, nsHtml5Atoms::mmultiscripts, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MINUS = new nsHtml5ElementName(nsHtml5Atoms::minus, nsHtml5Atoms::minus, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_NOFRAMES = new nsHtml5ElementName(nsHtml5Atoms::noframes, nsHtml5Atoms::noframes, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::NOFRAMES | SPECIAL);
+ ELT_NATURALNUMBERS = new nsHtml5ElementName(nsHtml5Atoms::naturalnumbers, nsHtml5Atoms::naturalnumbers, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PRIMES = new nsHtml5ElementName(nsHtml5Atoms::primes, nsHtml5Atoms::primes, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PROGRESS = new nsHtml5ElementName(nsHtml5Atoms::progress, nsHtml5Atoms::progress, NS_NewHTMLProgressElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PLUS = new nsHtml5ElementName(nsHtml5Atoms::plus, nsHtml5Atoms::plus, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_REALS = new nsHtml5ElementName(nsHtml5Atoms::reals, nsHtml5Atoms::reals, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_RATIONALS = new nsHtml5ElementName(nsHtml5Atoms::rationals, nsHtml5Atoms::rationals, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SEMANTICS = new nsHtml5ElementName(nsHtml5Atoms::semantics, nsHtml5Atoms::semantics, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_TIMES = new nsHtml5ElementName(nsHtml5Atoms::times, nsHtml5Atoms::times, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DT = new nsHtml5ElementName(nsHtml5Atoms::dt, nsHtml5Atoms::dt, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::DD_OR_DT | SPECIAL | OPTIONAL_END_TAG);
+ ELT_APPLET = new nsHtml5ElementName(nsHtml5Atoms::applet, nsHtml5Atoms::applet, NS_NewHTMLSharedObjectElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::MARQUEE_OR_APPLET | SPECIAL | SCOPING);
+ ELT_ARCCOT = new nsHtml5ElementName(nsHtml5Atoms::arccot, nsHtml5Atoms::arccot, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_BASEFONT = new nsHtml5ElementName(nsHtml5Atoms::basefont, nsHtml5Atoms::basefont, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::LINK_OR_BASEFONT_OR_BGSOUND | SPECIAL);
+ ELT_CARTESIANPRODUCT = new nsHtml5ElementName(nsHtml5Atoms::cartesianproduct, nsHtml5Atoms::cartesianproduct, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_GT = new nsHtml5ElementName(nsHtml5Atoms::gt, nsHtml5Atoms::gt, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DETERMINANT = new nsHtml5ElementName(nsHtml5Atoms::determinant, nsHtml5Atoms::determinant, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_DATALIST = new nsHtml5ElementName(nsHtml5Atoms::datalist, nsHtml5Atoms::datalist, NS_NewHTMLDataListElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_EMPTYSET = new nsHtml5ElementName(nsHtml5Atoms::emptyset, nsHtml5Atoms::emptyset, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_EQUIVALENT = new nsHtml5ElementName(nsHtml5Atoms::equivalent, nsHtml5Atoms::equivalent, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FONT_FACE_FORMAT = new nsHtml5ElementName(nsHtml5Atoms::font_face_format, nsHtml5Atoms::font_face_format, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FOREIGNOBJECT = new nsHtml5ElementName(nsHtml5Atoms::foreignobject, nsHtml5Atoms::foreignObject, NS_NewHTMLUnknownElement, NS_NewSVGForeignObjectElement, nsHtml5TreeBuilder::FOREIGNOBJECT_OR_DESC | SCOPING_AS_SVG);
+ ELT_FIELDSET = new nsHtml5ElementName(nsHtml5Atoms::fieldset, nsHtml5Atoms::fieldset, NS_NewHTMLFieldSetElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::FIELDSET | SPECIAL);
+ ELT_FRAMESET = new nsHtml5ElementName(nsHtml5Atoms::frameset, nsHtml5Atoms::frameset, NS_NewHTMLFrameSetElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::FRAMESET | SPECIAL);
+ ELT_FEOFFSET = new nsHtml5ElementName(nsHtml5Atoms::feoffset, nsHtml5Atoms::feOffset, NS_NewHTMLUnknownElement, NS_NewSVGFEOffsetElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FESPOTLIGHT = new nsHtml5ElementName(nsHtml5Atoms::fespotlight, nsHtml5Atoms::feSpotLight, NS_NewHTMLUnknownElement, NS_NewSVGFESpotLightElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FEPOINTLIGHT = new nsHtml5ElementName(nsHtml5Atoms::fepointlight, nsHtml5Atoms::fePointLight, NS_NewHTMLUnknownElement, NS_NewSVGFEPointLightElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FEDISTANTLIGHT = new nsHtml5ElementName(nsHtml5Atoms::fedistantlight, nsHtml5Atoms::feDistantLight, NS_NewHTMLUnknownElement, NS_NewSVGFEDistantLightElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FONT = new nsHtml5ElementName(nsHtml5Atoms::font, nsHtml5Atoms::font, NS_NewHTMLFontElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::FONT);
+ ELT_LT = new nsHtml5ElementName(nsHtml5Atoms::lt, nsHtml5Atoms::lt, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_INTERSECT = new nsHtml5ElementName(nsHtml5Atoms::intersect, nsHtml5Atoms::intersect, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_IDENT = new nsHtml5ElementName(nsHtml5Atoms::ident, nsHtml5Atoms::ident, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_INPUT = new nsHtml5ElementName(nsHtml5Atoms::input, nsHtml5Atoms::input, NS_NewHTMLInputElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::INPUT | SPECIAL);
+ ELT_LIMIT = new nsHtml5ElementName(nsHtml5Atoms::limit, nsHtml5Atoms::limit, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LOWLIMIT = new nsHtml5ElementName(nsHtml5Atoms::lowlimit, nsHtml5Atoms::lowlimit, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LINEARGRADIENT = new nsHtml5ElementName(nsHtml5Atoms::lineargradient, nsHtml5Atoms::linearGradient, NS_NewHTMLUnknownElement, NS_NewSVGLinearGradientElement, nsHtml5TreeBuilder::OTHER);
+ ELT_LIST = new nsHtml5ElementName(nsHtml5Atoms::list, nsHtml5Atoms::list, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MOMENT = new nsHtml5ElementName(nsHtml5Atoms::moment, nsHtml5Atoms::moment, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MROOT = new nsHtml5ElementName(nsHtml5Atoms::mroot, nsHtml5Atoms::mroot, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MSQRT = new nsHtml5ElementName(nsHtml5Atoms::msqrt, nsHtml5Atoms::msqrt, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MOMENTABOUT = new nsHtml5ElementName(nsHtml5Atoms::momentabout, nsHtml5Atoms::momentabout, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MTEXT = new nsHtml5ElementName(nsHtml5Atoms::mtext, nsHtml5Atoms::mtext, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::MI_MO_MN_MS_MTEXT | SCOPING_AS_MATHML);
+ ELT_NOTSUBSET = new nsHtml5ElementName(nsHtml5Atoms::notsubset, nsHtml5Atoms::notsubset, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_NOTPRSUBSET = new nsHtml5ElementName(nsHtml5Atoms::notprsubset, nsHtml5Atoms::notprsubset, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_NOSCRIPT = new nsHtml5ElementName(nsHtml5Atoms::noscript, nsHtml5Atoms::noscript, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::NOSCRIPT | SPECIAL);
+ ELT_NEST = new nsHtml5ElementName(nsHtml5Atoms::nest, nsHtml5Atoms::nest, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_RT = new nsHtml5ElementName(nsHtml5Atoms::rt, nsHtml5Atoms::rt, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::RT_OR_RP | OPTIONAL_END_TAG);
+ ELT_OBJECT = new nsHtml5ElementName(nsHtml5Atoms::object, nsHtml5Atoms::object, NS_NewHTMLObjectElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OBJECT | SPECIAL | SCOPING);
+ ELT_OUTERPRODUCT = new nsHtml5ElementName(nsHtml5Atoms::outerproduct, nsHtml5Atoms::outerproduct, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_OUTPUT = new nsHtml5ElementName(nsHtml5Atoms::output, nsHtml5Atoms::output, NS_NewHTMLOutputElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OUTPUT);
+ ELT_PRODUCT = new nsHtml5ElementName(nsHtml5Atoms::product, nsHtml5Atoms::product, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PRSUBSET = new nsHtml5ElementName(nsHtml5Atoms::prsubset, nsHtml5Atoms::prsubset, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_PLAINTEXT = new nsHtml5ElementName(nsHtml5Atoms::plaintext, nsHtml5Atoms::plaintext, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::PLAINTEXT | SPECIAL);
+ ELT_TT = new nsHtml5ElementName(nsHtml5Atoms::tt, nsHtml5Atoms::tt, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U);
+ ELT_QUOTIENT = new nsHtml5ElementName(nsHtml5Atoms::quotient, nsHtml5Atoms::quotient, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_RECT = new nsHtml5ElementName(nsHtml5Atoms::rect, nsHtml5Atoms::rect, NS_NewHTMLUnknownElement, NS_NewSVGRectElement, nsHtml5TreeBuilder::OTHER);
+ ELT_RADIALGRADIENT = new nsHtml5ElementName(nsHtml5Atoms::radialgradient, nsHtml5Atoms::radialGradient, NS_NewHTMLUnknownElement, NS_NewSVGRadialGradientElement, nsHtml5TreeBuilder::OTHER);
+ ELT_ROOT = new nsHtml5ElementName(nsHtml5Atoms::root, nsHtml5Atoms::root, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SELECT = new nsHtml5ElementName(nsHtml5Atoms::select, nsHtml5Atoms::select, NS_NewHTMLSelectElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::SELECT | SPECIAL);
+ ELT_SCALARPRODUCT = new nsHtml5ElementName(nsHtml5Atoms::scalarproduct, nsHtml5Atoms::scalarproduct, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SUBSET = new nsHtml5ElementName(nsHtml5Atoms::subset, nsHtml5Atoms::subset, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SLOT = new nsHtml5ElementName(nsHtml5Atoms::slot, nsHtml5Atoms::slot, NS_NewHTMLSlotElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_SCRIPT = new nsHtml5ElementName(nsHtml5Atoms::script, nsHtml5Atoms::script, NS_NewHTMLScriptElement, NS_NewSVGScriptElement, nsHtml5TreeBuilder::SCRIPT | SPECIAL);
+ ELT_TFOOT = new nsHtml5ElementName(nsHtml5Atoms::tfoot, nsHtml5Atoms::tfoot, NS_NewHTMLTableSectionElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::TBODY_OR_THEAD_OR_TFOOT | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG);
+ ELT_TEXT = new nsHtml5ElementName(nsHtml5Atoms::text, nsHtml5Atoms::text, NS_NewHTMLUnknownElement, NS_NewSVGTextElement, nsHtml5TreeBuilder::OTHER);
+ ELT_UPLIMIT = new nsHtml5ElementName(nsHtml5Atoms::uplimit, nsHtml5Atoms::uplimit, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_VECTORPRODUCT = new nsHtml5ElementName(nsHtml5Atoms::vectorproduct, nsHtml5Atoms::vectorproduct, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MENU = new nsHtml5ElementName(nsHtml5Atoms::menu, nsHtml5Atoms::menu, NS_NewHTMLMenuElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU | SPECIAL);
+ ELT_SDEV = new nsHtml5ElementName(nsHtml5Atoms::sdev, nsHtml5Atoms::sdev, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FEDROPSHADOW = new nsHtml5ElementName(nsHtml5Atoms::fedropshadow, nsHtml5Atoms::feDropShadow, NS_NewHTMLUnknownElement, NS_NewSVGFEDropShadowElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MROW = new nsHtml5ElementName(nsHtml5Atoms::mrow, nsHtml5Atoms::mrow, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MATRIXROW = new nsHtml5ElementName(nsHtml5Atoms::matrixrow, nsHtml5Atoms::matrixrow, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_VIEW = new nsHtml5ElementName(nsHtml5Atoms::view, nsHtml5Atoms::view, NS_NewHTMLUnknownElement, NS_NewSVGViewElement, nsHtml5TreeBuilder::OTHER);
+ ELT_APPROX = new nsHtml5ElementName(nsHtml5Atoms::approx, nsHtml5Atoms::approx, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FECOLORMATRIX = new nsHtml5ElementName(nsHtml5Atoms::fecolormatrix, nsHtml5Atoms::feColorMatrix, NS_NewHTMLUnknownElement, NS_NewSVGFEColorMatrixElement, nsHtml5TreeBuilder::OTHER);
+ ELT_FECONVOLVEMATRIX = new nsHtml5ElementName(nsHtml5Atoms::feconvolvematrix, nsHtml5Atoms::feConvolveMatrix, NS_NewHTMLUnknownElement, NS_NewSVGFEConvolveMatrixElement, nsHtml5TreeBuilder::OTHER);
+ ELT_MATRIX = new nsHtml5ElementName(nsHtml5Atoms::matrix, nsHtml5Atoms::matrix, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_APPLY = new nsHtml5ElementName(nsHtml5Atoms::apply, nsHtml5Atoms::apply, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_BODY = new nsHtml5ElementName(nsHtml5Atoms::body, nsHtml5Atoms::body, NS_NewHTMLBodyElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::BODY | SPECIAL | OPTIONAL_END_TAG);
+ ELT_FEMORPHOLOGY = new nsHtml5ElementName(nsHtml5Atoms::femorphology, nsHtml5Atoms::feMorphology, NS_NewHTMLUnknownElement, NS_NewSVGFEMorphologyElement, nsHtml5TreeBuilder::OTHER);
+ ELT_IMAGINARY = new nsHtml5ElementName(nsHtml5Atoms::imaginary, nsHtml5Atoms::imaginary, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_INFINITY = new nsHtml5ElementName(nsHtml5Atoms::infinity, nsHtml5Atoms::infinity, NS_NewHTMLUnknownElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::OTHER);
+ ELT_RUBY = new nsHtml5ElementName(nsHtml5Atoms::ruby, nsHtml5Atoms::ruby, NS_NewHTMLElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR);
+ ELT_SUMMARY = new nsHtml5ElementName(nsHtml5Atoms::summary, nsHtml5Atoms::summary, NS_NewHTMLSummaryElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY | SPECIAL);
+ ELT_TBODY = new nsHtml5ElementName(nsHtml5Atoms::tbody, nsHtml5Atoms::tbody, NS_NewHTMLTableSectionElement, NS_NewSVGUnknownElement, nsHtml5TreeBuilder::TBODY_OR_THEAD_OR_TFOOT | SPECIAL | FOSTER_PARENTING | OPTIONAL_END_TAG);
+ ELEMENT_NAMES = new nsHtml5ElementName*[399];
+ ELEMENT_NAMES[0] = ELT_AUDIO;
+ ELEMENT_NAMES[1] = ELT_LOGBASE;
+ ELEMENT_NAMES[2] = ELT_FIELDSET;
+ ELEMENT_NAMES[3] = ELT_DATA;
+ ELEMENT_NAMES[4] = ELT_IMAGINARYI;
+ ELEMENT_NAMES[5] = ELT_CANVAS;
+ ELEMENT_NAMES[6] = ELT_QUOTIENT;
+ ELEMENT_NAMES[7] = ELT_PRE;
+ ELEMENT_NAMES[8] = ELT_ARTICLE;
+ ELEMENT_NAMES[9] = ELT_FEFUNCG;
+ ELEMENT_NAMES[10] = ELT_ARCSIN;
+ ELEMENT_NAMES[11] = ELT_MUNDER;
+ ELEMENT_NAMES[12] = ELT_REALS;
+ ELEMENT_NAMES[13] = ELT_MROOT;
+ ELEMENT_NAMES[14] = ELT_MROW;
+ ELEMENT_NAMES[15] = ELT_GEQ;
+ ELEMENT_NAMES[16] = ELT_G;
+ ELEMENT_NAMES[17] = ELT_DD;
+ ELEMENT_NAMES[18] = ELT_ELLIPSE;
+ ELEMENT_NAMES[19] = ELT_TABLE;
+ ELEMENT_NAMES[20] = ELT_GLYPH;
+ ELEMENT_NAMES[21] = ELT_OL;
+ ELEMENT_NAMES[22] = ELT_KEYGEN;
+ ELEMENT_NAMES[23] = ELT_ABBR;
+ ELEMENT_NAMES[24] = ELT_NOTANUMBER;
+ ELEMENT_NAMES[25] = ELT_MPRESCRIPTS;
+ ELEMENT_NAMES[26] = ELT_CARTESIANPRODUCT;
+ ELEMENT_NAMES[27] = ELT_INTERSECT;
+ ELEMENT_NAMES[28] = ELT_RT;
+ ELEMENT_NAMES[29] = ELT_SCRIPT;
+ ELEMENT_NAMES[30] = ELT_APPLY;
+ ELEMENT_NAMES[31] = ELT_COS;
+ ELEMENT_NAMES[32] = ELT_MTD;
+ ELEMENT_NAMES[33] = ELT_SUM;
+ ELEMENT_NAMES[34] = ELT_U;
+ ELEMENT_NAMES[35] = ELT_MSUB;
+ ELEMENT_NAMES[36] = ELT_HEAD;
+ ELEMENT_NAMES[37] = ELT_CONJUGATE;
+ ELEMENT_NAMES[38] = ELT_FRAME;
+ ELEMENT_NAMES[39] = ELT_PIECE;
+ ELEMENT_NAMES[40] = ELT_DIFF;
+ ELEMENT_NAMES[41] = ELT_ARCSINH;
+ ELEMENT_NAMES[42] = ELT_SECH;
+ ELEMENT_NAMES[43] = ELT_TRACK;
+ ELEMENT_NAMES[44] = ELT_ACRONYM;
+ ELEMENT_NAMES[45] = ELT_CONDITION;
+ ELEMENT_NAMES[46] = ELT_POLYGON;
+ ELEMENT_NAMES[47] = ELT_MSUBSUP;
+ ELEMENT_NAMES[48] = ELT_FILTER;
+ ELEMENT_NAMES[49] = ELT_MUNDEROVER;
+ ELEMENT_NAMES[50] = ELT_SELECTOR;
+ ELEMENT_NAMES[51] = ELT_EXISTS;
+ ELEMENT_NAMES[52] = ELT_NATURALNUMBERS;
+ ELEMENT_NAMES[53] = ELT_DT;
+ ELEMENT_NAMES[54] = ELT_EMPTYSET;
+ ELEMENT_NAMES[55] = ELT_FEPOINTLIGHT;
+ ELEMENT_NAMES[56] = ELT_LOWLIMIT;
+ ELEMENT_NAMES[57] = ELT_NOTSUBSET;
+ ELEMENT_NAMES[58] = ELT_PRODUCT;
+ ELEMENT_NAMES[59] = ELT_SELECT;
+ ELEMENT_NAMES[60] = ELT_VECTORPRODUCT;
+ ELEMENT_NAMES[61] = ELT_FECOLORMATRIX;
+ ELEMENT_NAMES[62] = ELT_INFINITY;
+ ELEMENT_NAMES[63] = ELT_BIG;
+ ELEMENT_NAMES[64] = ELT_DIR;
+ ELEMENT_NAMES[65] = ELT_KBD;
+ ELEMENT_NAMES[66] = ELT_MAX;
+ ELEMENT_NAMES[67] = ELT_REM;
+ ELEMENT_NAMES[68] = ELT_SET;
+ ELEMENT_NAMES[69] = ELT_I;
+ ELEMENT_NAMES[70] = ELT_H4;
+ ELEMENT_NAMES[71] = ELT_METADATA;
+ ELEMENT_NAMES[72] = ELT_DEFINITION_SRC;
+ ELEMENT_NAMES[73] = ELT_EMBED;
+ ELEMENT_NAMES[74] = ELT_NOEMBED;
+ ELEMENT_NAMES[75] = ELT_CODE;
+ ELEMENT_NAMES[76] = ELT_DEGREE;
+ ELEMENT_NAMES[77] = ELT_FEIMAGE;
+ ELEMENT_NAMES[78] = ELT_IMAGE;
+ ELEMENT_NAMES[79] = ELT_MSTYLE;
+ ELEMENT_NAMES[80] = ELT_RULE;
+ ELEMENT_NAMES[81] = ELT_TEMPLATE;
+ ELEMENT_NAMES[82] = ELT_SETDIFF;
+ ELEMENT_NAMES[83] = ELT_STRONG;
+ ELEMENT_NAMES[84] = ELT_CSCH;
+ ELEMENT_NAMES[85] = ELT_MPATH;
+ ELEMENT_NAMES[86] = ELT_TEXTPATH;
+ ELEMENT_NAMES[87] = ELT_MARK;
+ ELEMENT_NAMES[88] = ELT_FACTORIAL;
+ ELEMENT_NAMES[89] = ELT_SMALL;
+ ELEMENT_NAMES[90] = ELT_MPHANTOM;
+ ELEMENT_NAMES[91] = ELT_BUTTON;
+ ELEMENT_NAMES[92] = ELT_FIGCAPTION;
+ ELEMENT_NAMES[93] = ELT_MAIN;
+ ELEMENT_NAMES[94] = ELT_SECTION;
+ ELEMENT_NAMES[95] = ELT_COLGROUP;
+ ELEMENT_NAMES[96] = ELT_SAMP;
+ ELEMENT_NAMES[97] = ELT_CURSOR;
+ ELEMENT_NAMES[98] = ELT_HEADER;
+ ELEMENT_NAMES[99] = ELT_METER;
+ ELEMENT_NAMES[100] = ELT_MLABELEDTR;
+ ELEMENT_NAMES[101] = ELT_TR;
+ ELEMENT_NAMES[102] = ELT_ARCCOS;
+ ELEMENT_NAMES[103] = ELT_DEFS;
+ ELEMENT_NAMES[104] = ELT_INTEGERS;
+ ELEMENT_NAMES[105] = ELT_MINUS;
+ ELEMENT_NAMES[106] = ELT_PROGRESS;
+ ELEMENT_NAMES[107] = ELT_SEMANTICS;
+ ELEMENT_NAMES[108] = ELT_ARCCOT;
+ ELEMENT_NAMES[109] = ELT_DETERMINANT;
+ ELEMENT_NAMES[110] = ELT_FONT_FACE_FORMAT;
+ ELEMENT_NAMES[111] = ELT_FEOFFSET;
+ ELEMENT_NAMES[112] = ELT_FONT;
+ ELEMENT_NAMES[113] = ELT_INPUT;
+ ELEMENT_NAMES[114] = ELT_LIST;
+ ELEMENT_NAMES[115] = ELT_MOMENTABOUT;
+ ELEMENT_NAMES[116] = ELT_NOSCRIPT;
+ ELEMENT_NAMES[117] = ELT_OUTERPRODUCT;
+ ELEMENT_NAMES[118] = ELT_PLAINTEXT;
+ ELEMENT_NAMES[119] = ELT_RADIALGRADIENT;
+ ELEMENT_NAMES[120] = ELT_SUBSET;
+ ELEMENT_NAMES[121] = ELT_TEXT;
+ ELEMENT_NAMES[122] = ELT_SDEV;
+ ELEMENT_NAMES[123] = ELT_VIEW;
+ ELEMENT_NAMES[124] = ELT_ISINDEX;
+ ELEMENT_NAMES[125] = ELT_FEMORPHOLOGY;
+ ELEMENT_NAMES[126] = ELT_SUMMARY;
+ ELEMENT_NAMES[127] = ELT_ARG;
+ ELEMENT_NAMES[128] = ELT_CSC;
+ ELEMENT_NAMES[129] = ELT_DEL;
+ ELEMENT_NAMES[130] = ELT_EXP;
+ ELEMENT_NAMES[131] = ELT_INS;
+ ELEMENT_NAMES[132] = ELT_LCM;
+ ELEMENT_NAMES[133] = ELT_MAP;
+ ELEMENT_NAMES[134] = ELT_NOT;
+ ELEMENT_NAMES[135] = ELT_B;
+ ELEMENT_NAMES[136] = ELT_SEC;
+ ELEMENT_NAMES[137] = ELT_SEP;
+ ELEMENT_NAMES[138] = ELT_USE;
+ ELEMENT_NAMES[139] = ELT_XMP;
+ ELEMENT_NAMES[140] = ELT_Q;
+ ELEMENT_NAMES[141] = ELT_H2;
+ ELEMENT_NAMES[142] = ELT_H6;
+ ELEMENT_NAMES[143] = ELT_FEFUNCA;
+ ELEMENT_NAMES[144] = ELT_TEXTAREA;
+ ELEMENT_NAMES[145] = ELT_ARCSEC;
+ ELEMENT_NAMES[146] = ELT_FONT_FACE_SRC;
+ ELEMENT_NAMES[147] = ELT_CARD;
+ ELEMENT_NAMES[148] = ELT_FEFLOOD;
+ ELEMENT_NAMES[149] = ELT_MFENCED;
+ ELEMENT_NAMES[150] = ELT_THEAD;
+ ELEMENT_NAMES[151] = ELT_BASE;
+ ELEMENT_NAMES[152] = ELT_COLOR_PROFILE;
+ ELEMENT_NAMES[153] = ELT_DIVERGENCE;
+ ELEMENT_NAMES[154] = ELT_DATATEMPLATE;
+ ELEMENT_NAMES[155] = ELT_FETURBULENCE;
+ ELEMENT_NAMES[156] = ELT_FETILE;
+ ELEMENT_NAMES[157] = ELT_FALSE;
+ ELEMENT_NAMES[158] = ELT_INVERSE;
+ ELEMENT_NAMES[159] = ELT_MODE;
+ ELEMENT_NAMES[160] = ELT_NONE;
+ ELEMENT_NAMES[161] = ELT_PICTURE;
+ ELEMENT_NAMES[162] = ELT_STRIKE;
+ ELEMENT_NAMES[163] = ELT_TIME;
+ ELEMENT_NAMES[164] = ELT_VARIANCE;
+ ELEMENT_NAMES[165] = ELT_GLYPHREF;
+ ELEMENT_NAMES[166] = ELT_CEILING;
+ ELEMENT_NAMES[167] = ELT_FESPECULARLIGHTING;
+ ELEMENT_NAMES[168] = ELT_ARCCSCH;
+ ELEMENT_NAMES[169] = ELT_ARCCOSH;
+ ELEMENT_NAMES[170] = ELT_CLIPPATH;
+ ELEMENT_NAMES[171] = ELT_MISSING_GLYPH;
+ ELEMENT_NAMES[172] = ELT_PATH;
+ ELEMENT_NAMES[173] = ELT_SINH;
+ ELEMENT_NAMES[174] = ELT_FONT_FACE_URI;
+ ELEMENT_NAMES[175] = ELT_PI;
+ ELEMENT_NAMES[176] = ELT_MASK;
+ ELEMENT_NAMES[177] = ELT_CSYMBOL;
+ ELEMENT_NAMES[178] = ELT_HTML;
+ ELEMENT_NAMES[179] = ELT_UL;
+ ELEMENT_NAMES[180] = ELT_ALTGLYPHITEM;
+ ELEMENT_NAMES[181] = ELT_FORM;
+ ELEMENT_NAMES[182] = ELT_CN;
+ ELEMENT_NAMES[183] = ELT_ANNOTATION;
+ ELEMENT_NAMES[184] = ELT_CODOMAIN;
+ ELEMENT_NAMES[185] = ELT_DOMAINOFAPPLICATION;
+ ELEMENT_NAMES[186] = ELT_LN;
+ ELEMENT_NAMES[187] = ELT_MEAN;
+ ELEMENT_NAMES[188] = ELT_NOTIN;
+ ELEMENT_NAMES[189] = ELT_RELN;
+ ELEMENT_NAMES[190] = ELT_UNION;
+ ELEMENT_NAMES[191] = ELT_TENDSTO;
+ ELEMENT_NAMES[192] = ELT_HGROUP;
+ ELEMENT_NAMES[193] = ELT_RP;
+ ELEMENT_NAMES[194] = ELT_EQ;
+ ELEMENT_NAMES[195] = ELT_BVAR;
+ ELEMENT_NAMES[196] = ELT_FEFUNCR;
+ ELEMENT_NAMES[197] = ELT_FLOOR;
+ ELEMENT_NAMES[198] = ELT_OR;
+ ELEMENT_NAMES[199] = ELT_MARKER;
+ ELEMENT_NAMES[200] = ELT_MOVER;
+ ELEMENT_NAMES[201] = ELT_MERROR;
+ ELEMENT_NAMES[202] = ELT_NOBR;
+ ELEMENT_NAMES[203] = ELT_POWER;
+ ELEMENT_NAMES[204] = ELT_SOLIDCOLOR;
+ ELEMENT_NAMES[205] = ELT_VECTOR;
+ ELEMENT_NAMES[206] = ELT_ADDRESS;
+ ELEMENT_NAMES[207] = ELT_COMPLEXES;
+ ELEMENT_NAMES[208] = ELT_DETAILS;
+ ELEMENT_NAMES[209] = ELT_IMPLIES;
+ ELEMENT_NAMES[210] = ELT_MS;
+ ELEMENT_NAMES[211] = ELT_MMULTISCRIPTS;
+ ELEMENT_NAMES[212] = ELT_NOFRAMES;
+ ELEMENT_NAMES[213] = ELT_PRIMES;
+ ELEMENT_NAMES[214] = ELT_PLUS;
+ ELEMENT_NAMES[215] = ELT_RATIONALS;
+ ELEMENT_NAMES[216] = ELT_TIMES;
+ ELEMENT_NAMES[217] = ELT_APPLET;
+ ELEMENT_NAMES[218] = ELT_BASEFONT;
+ ELEMENT_NAMES[219] = ELT_GT;
+ ELEMENT_NAMES[220] = ELT_DATALIST;
+ ELEMENT_NAMES[221] = ELT_EQUIVALENT;
+ ELEMENT_NAMES[222] = ELT_FOREIGNOBJECT;
+ ELEMENT_NAMES[223] = ELT_FRAMESET;
+ ELEMENT_NAMES[224] = ELT_FESPOTLIGHT;
+ ELEMENT_NAMES[225] = ELT_FEDISTANTLIGHT;
+ ELEMENT_NAMES[226] = ELT_LT;
+ ELEMENT_NAMES[227] = ELT_IDENT;
+ ELEMENT_NAMES[228] = ELT_LIMIT;
+ ELEMENT_NAMES[229] = ELT_LINEARGRADIENT;
+ ELEMENT_NAMES[230] = ELT_MOMENT;
+ ELEMENT_NAMES[231] = ELT_MSQRT;
+ ELEMENT_NAMES[232] = ELT_MTEXT;
+ ELEMENT_NAMES[233] = ELT_NOTPRSUBSET;
+ ELEMENT_NAMES[234] = ELT_NEST;
+ ELEMENT_NAMES[235] = ELT_OBJECT;
+ ELEMENT_NAMES[236] = ELT_OUTPUT;
+ ELEMENT_NAMES[237] = ELT_PRSUBSET;
+ ELEMENT_NAMES[238] = ELT_TT;
+ ELEMENT_NAMES[239] = ELT_RECT;
+ ELEMENT_NAMES[240] = ELT_ROOT;
+ ELEMENT_NAMES[241] = ELT_SCALARPRODUCT;
+ ELEMENT_NAMES[242] = ELT_SLOT;
+ ELEMENT_NAMES[243] = ELT_TFOOT;
+ ELEMENT_NAMES[244] = ELT_UPLIMIT;
+ ELEMENT_NAMES[245] = ELT_MENU;
+ ELEMENT_NAMES[246] = ELT_FEDROPSHADOW;
+ ELEMENT_NAMES[247] = ELT_MATRIXROW;
+ ELEMENT_NAMES[248] = ELT_APPROX;
+ ELEMENT_NAMES[249] = ELT_FECONVOLVEMATRIX;
+ ELEMENT_NAMES[250] = ELT_MATRIX;
+ ELEMENT_NAMES[251] = ELT_BODY;
+ ELEMENT_NAMES[252] = ELT_IMAGINARY;
+ ELEMENT_NAMES[253] = ELT_RUBY;
+ ELEMENT_NAMES[254] = ELT_TBODY;
+ ELEMENT_NAMES[255] = ELT_AND;
+ ELEMENT_NAMES[256] = ELT_ABS;
+ ELEMENT_NAMES[257] = ELT_BDO;
+ ELEMENT_NAMES[258] = ELT_COL;
+ ELEMENT_NAMES[259] = ELT_COT;
+ ELEMENT_NAMES[260] = ELT_DFN;
+ ELEMENT_NAMES[261] = ELT_DIV;
+ ELEMENT_NAMES[262] = ELT_GCD;
+ ELEMENT_NAMES[263] = ELT_IMG;
+ ELEMENT_NAMES[264] = ELT_INT;
+ ELEMENT_NAMES[265] = ELT_LOG;
+ ELEMENT_NAMES[266] = ELT_LEQ;
+ ELEMENT_NAMES[267] = ELT_MIN;
+ ELEMENT_NAMES[268] = ELT_MTR;
+ ELEMENT_NAMES[269] = ELT_NEQ;
+ ELEMENT_NAMES[270] = ELT_NAV;
+ ELEMENT_NAMES[271] = ELT_A;
+ ELEMENT_NAMES[272] = ELT_RTC;
+ ELEMENT_NAMES[273] = ELT_SUB;
+ ELEMENT_NAMES[274] = ELT_SVG;
+ ELEMENT_NAMES[275] = ELT_SIN;
+ ELEMENT_NAMES[276] = ELT_SUP;
+ ELEMENT_NAMES[277] = ELT_TAN;
+ ELEMENT_NAMES[278] = ELT_VAR;
+ ELEMENT_NAMES[279] = ELT_WBR;
+ ELEMENT_NAMES[280] = ELT_XOR;
+ ELEMENT_NAMES[281] = ELT_P;
+ ELEMENT_NAMES[282] = ELT_S;
+ ELEMENT_NAMES[283] = ELT_H1;
+ ELEMENT_NAMES[284] = ELT_H3;
+ ELEMENT_NAMES[285] = ELT_H5;
+ ELEMENT_NAMES[286] = ELT_AREA;
+ ELEMENT_NAMES[287] = ELT_EULERGAMMA;
+ ELEMENT_NAMES[288] = ELT_LAMBDA;
+ ELEMENT_NAMES[289] = ELT_META;
+ ELEMENT_NAMES[290] = ELT_FEFUNCB;
+ ELEMENT_NAMES[291] = ELT_RB;
+ ELEMENT_NAMES[292] = ELT_ARCCSC;
+ ELEMENT_NAMES[293] = ELT_DESC;
+ ELEMENT_NAMES[294] = ELT_MFRAC;
+ ELEMENT_NAMES[295] = ELT_BGSOUND;
+ ELEMENT_NAMES[296] = ELT_DISCARD;
+ ELEMENT_NAMES[297] = ELT_FEBLEND;
+ ELEMENT_NAMES[298] = ELT_GRAD;
+ ELEMENT_NAMES[299] = ELT_LEGEND;
+ ELEMENT_NAMES[300] = ELT_MPADDED;
+ ELEMENT_NAMES[301] = ELT_TD;
+ ELEMENT_NAMES[302] = ELT_ASIDE;
+ ELEMENT_NAMES[303] = ELT_ANIMATE;
+ ELEMENT_NAMES[304] = ELT_BLOCKQUOTE;
+ ELEMENT_NAMES[305] = ELT_CIRCLE;
+ ELEMENT_NAMES[306] = ELT_COMPOSE;
+ ELEMENT_NAMES[307] = ELT_CITE;
+ ELEMENT_NAMES[308] = ELT_DIVIDE;
+ ELEMENT_NAMES[309] = ELT_DECLARE;
+ ELEMENT_NAMES[310] = ELT_EXPONENTIALE;
+ ELEMENT_NAMES[311] = ELT_FONT_FACE;
+ ELEMENT_NAMES[312] = ELT_FEMERGENODE;
+ ELEMENT_NAMES[313] = ELT_FEMERGE;
+ ELEMENT_NAMES[314] = ELT_FONT_FACE_NAME;
+ ELEMENT_NAMES[315] = ELT_FIGURE;
+ ELEMENT_NAMES[316] = ELT_FECOMPOSITE;
+ ELEMENT_NAMES[317] = ELT_IFRAME;
+ ELEMENT_NAMES[318] = ELT_LINE;
+ ELEMENT_NAMES[319] = ELT_MSPACE;
+ ELEMENT_NAMES[320] = ELT_MTABLE;
+ ELEMENT_NAMES[321] = ELT_MENCLOSE;
+ ELEMENT_NAMES[322] = ELT_OTHERWISE;
+ ELEMENT_NAMES[323] = ELT_POLYLINE;
+ ELEMENT_NAMES[324] = ELT_PIECEWISE;
+ ELEMENT_NAMES[325] = ELT_SOURCE;
+ ELEMENT_NAMES[326] = ELT_STYLE;
+ ELEMENT_NAMES[327] = ELT_TITLE;
+ ELEMENT_NAMES[328] = ELT_TRANSPOSE;
+ ELEMENT_NAMES[329] = ELT_TRUE;
+ ELEMENT_NAMES[330] = ELT_ALTGLYPHDEF;
+ ELEMENT_NAMES[331] = ELT_FACTOROF;
+ ELEMENT_NAMES[332] = ELT_PARTIALDIFF;
+ ELEMENT_NAMES[333] = ELT_TREF;
+ ELEMENT_NAMES[334] = ELT_DIALOG;
+ ELEMENT_NAMES[335] = ELT_FEDIFFUSELIGHTING;
+ ELEMENT_NAMES[336] = ELT_LISTING;
+ ELEMENT_NAMES[337] = ELT_ARCSECH;
+ ELEMENT_NAMES[338] = ELT_ARCTANH;
+ ELEMENT_NAMES[339] = ELT_ALTGLYPH;
+ ELEMENT_NAMES[340] = ELT_ARCCOTH;
+ ELEMENT_NAMES[341] = ELT_COSH;
+ ELEMENT_NAMES[342] = ELT_COTH;
+ ELEMENT_NAMES[343] = ELT_MGLYPH;
+ ELEMENT_NAMES[344] = ELT_MATH;
+ ELEMENT_NAMES[345] = ELT_PREFETCH;
+ ELEMENT_NAMES[346] = ELT_TH;
+ ELEMENT_NAMES[347] = ELT_SWITCH;
+ ELEMENT_NAMES[348] = ELT_TANH;
+ ELEMENT_NAMES[349] = ELT_CI;
+ ELEMENT_NAMES[350] = ELT_LI;
+ ELEMENT_NAMES[351] = ELT_MI;
+ ELEMENT_NAMES[352] = ELT_LINK;
+ ELEMENT_NAMES[353] = ELT_MALIGNMARK;
+ ELEMENT_NAMES[354] = ELT_TBREAK;
+ ELEMENT_NAMES[355] = ELT_DL;
+ ELEMENT_NAMES[356] = ELT_CURL;
+ ELEMENT_NAMES[357] = ELT_FORALL;
+ ELEMENT_NAMES[358] = ELT_INTERVAL;
+ ELEMENT_NAMES[359] = ELT_LABEL;
+ ELEMENT_NAMES[360] = ELT_REAL;
+ ELEMENT_NAMES[361] = ELT_SYMBOL;
+ ELEMENT_NAMES[362] = ELT_ANIMATETRANSFORM;
+ ELEMENT_NAMES[363] = ELT_EM;
+ ELEMENT_NAMES[364] = ELT_MENUITEM;
+ ELEMENT_NAMES[365] = ELT_PARAM;
+ ELEMENT_NAMES[366] = ELT_ARCTAN;
+ ELEMENT_NAMES[367] = ELT_ANIMATION;
+ ELEMENT_NAMES[368] = ELT_ANIMATEMOTION;
+ ELEMENT_NAMES[369] = ELT_FN;
+ ELEMENT_NAMES[370] = ELT_CAPTION;
+ ELEMENT_NAMES[371] = ELT_DOMAIN;
+ ELEMENT_NAMES[372] = ELT_IN;
+ ELEMENT_NAMES[373] = ELT_HKERN;
+ ELEMENT_NAMES[374] = ELT_MN;
+ ELEMENT_NAMES[375] = ELT_LAPLACIAN;
+ ELEMENT_NAMES[376] = ELT_MEDIAN;
+ ELEMENT_NAMES[377] = ELT_MACTION;
+ ELEMENT_NAMES[378] = ELT_OPTION;
+ ELEMENT_NAMES[379] = ELT_PATTERN;
+ ELEMENT_NAMES[380] = ELT_SPAN;
+ ELEMENT_NAMES[381] = ELT_TSPAN;
+ ELEMENT_NAMES[382] = ELT_VKERN;
+ ELEMENT_NAMES[383] = ELT_MO;
+ ELEMENT_NAMES[384] = ELT_VIDEO;
+ ELEMENT_NAMES[385] = ELT_FEDISPLACEMENTMAP;
+ ELEMENT_NAMES[386] = ELT_MALIGNGROUP;
+ ELEMENT_NAMES[387] = ELT_MSUP;
+ ELEMENT_NAMES[388] = ELT_OPTGROUP;
+ ELEMENT_NAMES[389] = ELT_STOP;
+ ELEMENT_NAMES[390] = ELT_BR;
+ ELEMENT_NAMES[391] = ELT_ANIMATECOLOR;
+ ELEMENT_NAMES[392] = ELT_CENTER;
+ ELEMENT_NAMES[393] = ELT_HR;
+ ELEMENT_NAMES[394] = ELT_FECOMPONENTTRANSFER;
+ ELEMENT_NAMES[395] = ELT_FOOTER;
+ ELEMENT_NAMES[396] = ELT_FEGAUSSIANBLUR;
+ ELEMENT_NAMES[397] = ELT_HANDLER;
+ ELEMENT_NAMES[398] = ELT_LISTENER;
+}
+
+void
+nsHtml5ElementName::releaseStatics()
+{
+ delete ELT_ISINDEX;
+ delete ELT_ANNOTATION_XML;
+ delete ELT_AND;
+ delete ELT_ARG;
+ delete ELT_ABS;
+ delete ELT_BIG;
+ delete ELT_BDO;
+ delete ELT_CSC;
+ delete ELT_COL;
+ delete ELT_COS;
+ delete ELT_COT;
+ delete ELT_DEL;
+ delete ELT_DFN;
+ delete ELT_DIR;
+ delete ELT_DIV;
+ delete ELT_EXP;
+ delete ELT_GCD;
+ delete ELT_GEQ;
+ delete ELT_IMG;
+ delete ELT_INS;
+ delete ELT_INT;
+ delete ELT_KBD;
+ delete ELT_LOG;
+ delete ELT_LCM;
+ delete ELT_LEQ;
+ delete ELT_MTD;
+ delete ELT_MIN;
+ delete ELT_MAP;
+ delete ELT_MTR;
+ delete ELT_MAX;
+ delete ELT_NEQ;
+ delete ELT_NOT;
+ delete ELT_NAV;
+ delete ELT_PRE;
+ delete ELT_A;
+ delete ELT_B;
+ delete ELT_RTC;
+ delete ELT_REM;
+ delete ELT_SUB;
+ delete ELT_SEC;
+ delete ELT_SVG;
+ delete ELT_SUM;
+ delete ELT_SIN;
+ delete ELT_SEP;
+ delete ELT_SUP;
+ delete ELT_SET;
+ delete ELT_TAN;
+ delete ELT_USE;
+ delete ELT_VAR;
+ delete ELT_G;
+ delete ELT_WBR;
+ delete ELT_XMP;
+ delete ELT_XOR;
+ delete ELT_I;
+ delete ELT_P;
+ delete ELT_Q;
+ delete ELT_S;
+ delete ELT_U;
+ delete ELT_H1;
+ delete ELT_H2;
+ delete ELT_H3;
+ delete ELT_H4;
+ delete ELT_H5;
+ delete ELT_H6;
+ delete ELT_AREA;
+ delete ELT_DATA;
+ delete ELT_EULERGAMMA;
+ delete ELT_FEFUNCA;
+ delete ELT_LAMBDA;
+ delete ELT_METADATA;
+ delete ELT_META;
+ delete ELT_TEXTAREA;
+ delete ELT_FEFUNCB;
+ delete ELT_MSUB;
+ delete ELT_RB;
+ delete ELT_ARCSEC;
+ delete ELT_ARCCSC;
+ delete ELT_DEFINITION_SRC;
+ delete ELT_DESC;
+ delete ELT_FONT_FACE_SRC;
+ delete ELT_MFRAC;
+ delete ELT_DD;
+ delete ELT_BGSOUND;
+ delete ELT_CARD;
+ delete ELT_DISCARD;
+ delete ELT_EMBED;
+ delete ELT_FEBLEND;
+ delete ELT_FEFLOOD;
+ delete ELT_GRAD;
+ delete ELT_HEAD;
+ delete ELT_LEGEND;
+ delete ELT_MFENCED;
+ delete ELT_MPADDED;
+ delete ELT_NOEMBED;
+ delete ELT_TD;
+ delete ELT_THEAD;
+ delete ELT_ASIDE;
+ delete ELT_ARTICLE;
+ delete ELT_ANIMATE;
+ delete ELT_BASE;
+ delete ELT_BLOCKQUOTE;
+ delete ELT_CODE;
+ delete ELT_CIRCLE;
+ delete ELT_COLOR_PROFILE;
+ delete ELT_COMPOSE;
+ delete ELT_CONJUGATE;
+ delete ELT_CITE;
+ delete ELT_DIVERGENCE;
+ delete ELT_DIVIDE;
+ delete ELT_DEGREE;
+ delete ELT_DECLARE;
+ delete ELT_DATATEMPLATE;
+ delete ELT_EXPONENTIALE;
+ delete ELT_ELLIPSE;
+ delete ELT_FONT_FACE;
+ delete ELT_FETURBULENCE;
+ delete ELT_FEMERGENODE;
+ delete ELT_FEIMAGE;
+ delete ELT_FEMERGE;
+ delete ELT_FETILE;
+ delete ELT_FONT_FACE_NAME;
+ delete ELT_FRAME;
+ delete ELT_FIGURE;
+ delete ELT_FALSE;
+ delete ELT_FECOMPOSITE;
+ delete ELT_IMAGE;
+ delete ELT_IFRAME;
+ delete ELT_INVERSE;
+ delete ELT_LINE;
+ delete ELT_LOGBASE;
+ delete ELT_MSPACE;
+ delete ELT_MODE;
+ delete ELT_MTABLE;
+ delete ELT_MSTYLE;
+ delete ELT_MENCLOSE;
+ delete ELT_NONE;
+ delete ELT_OTHERWISE;
+ delete ELT_PIECE;
+ delete ELT_POLYLINE;
+ delete ELT_PICTURE;
+ delete ELT_PIECEWISE;
+ delete ELT_RULE;
+ delete ELT_SOURCE;
+ delete ELT_STRIKE;
+ delete ELT_STYLE;
+ delete ELT_TABLE;
+ delete ELT_TITLE;
+ delete ELT_TIME;
+ delete ELT_TRANSPOSE;
+ delete ELT_TEMPLATE;
+ delete ELT_TRUE;
+ delete ELT_VARIANCE;
+ delete ELT_ALTGLYPHDEF;
+ delete ELT_DIFF;
+ delete ELT_FACTOROF;
+ delete ELT_GLYPHREF;
+ delete ELT_PARTIALDIFF;
+ delete ELT_SETDIFF;
+ delete ELT_TREF;
+ delete ELT_CEILING;
+ delete ELT_DIALOG;
+ delete ELT_FEFUNCG;
+ delete ELT_FEDIFFUSELIGHTING;
+ delete ELT_FESPECULARLIGHTING;
+ delete ELT_LISTING;
+ delete ELT_STRONG;
+ delete ELT_ARCSECH;
+ delete ELT_ARCCSCH;
+ delete ELT_ARCTANH;
+ delete ELT_ARCSINH;
+ delete ELT_ALTGLYPH;
+ delete ELT_ARCCOSH;
+ delete ELT_ARCCOTH;
+ delete ELT_CSCH;
+ delete ELT_COSH;
+ delete ELT_CLIPPATH;
+ delete ELT_COTH;
+ delete ELT_GLYPH;
+ delete ELT_MGLYPH;
+ delete ELT_MISSING_GLYPH;
+ delete ELT_MATH;
+ delete ELT_MPATH;
+ delete ELT_PREFETCH;
+ delete ELT_PATH;
+ delete ELT_TH;
+ delete ELT_SECH;
+ delete ELT_SWITCH;
+ delete ELT_SINH;
+ delete ELT_TANH;
+ delete ELT_TEXTPATH;
+ delete ELT_CI;
+ delete ELT_FONT_FACE_URI;
+ delete ELT_LI;
+ delete ELT_IMAGINARYI;
+ delete ELT_MI;
+ delete ELT_PI;
+ delete ELT_LINK;
+ delete ELT_MARK;
+ delete ELT_MALIGNMARK;
+ delete ELT_MASK;
+ delete ELT_TBREAK;
+ delete ELT_TRACK;
+ delete ELT_DL;
+ delete ELT_CSYMBOL;
+ delete ELT_CURL;
+ delete ELT_FACTORIAL;
+ delete ELT_FORALL;
+ delete ELT_HTML;
+ delete ELT_INTERVAL;
+ delete ELT_OL;
+ delete ELT_LABEL;
+ delete ELT_UL;
+ delete ELT_REAL;
+ delete ELT_SMALL;
+ delete ELT_SYMBOL;
+ delete ELT_ALTGLYPHITEM;
+ delete ELT_ANIMATETRANSFORM;
+ delete ELT_ACRONYM;
+ delete ELT_EM;
+ delete ELT_FORM;
+ delete ELT_MENUITEM;
+ delete ELT_MPHANTOM;
+ delete ELT_PARAM;
+ delete ELT_CN;
+ delete ELT_ARCTAN;
+ delete ELT_ARCSIN;
+ delete ELT_ANIMATION;
+ delete ELT_ANNOTATION;
+ delete ELT_ANIMATEMOTION;
+ delete ELT_BUTTON;
+ delete ELT_FN;
+ delete ELT_CODOMAIN;
+ delete ELT_CAPTION;
+ delete ELT_CONDITION;
+ delete ELT_DOMAIN;
+ delete ELT_DOMAINOFAPPLICATION;
+ delete ELT_IN;
+ delete ELT_FIGCAPTION;
+ delete ELT_HKERN;
+ delete ELT_LN;
+ delete ELT_MN;
+ delete ELT_KEYGEN;
+ delete ELT_LAPLACIAN;
+ delete ELT_MEAN;
+ delete ELT_MEDIAN;
+ delete ELT_MAIN;
+ delete ELT_MACTION;
+ delete ELT_NOTIN;
+ delete ELT_OPTION;
+ delete ELT_POLYGON;
+ delete ELT_PATTERN;
+ delete ELT_RELN;
+ delete ELT_SPAN;
+ delete ELT_SECTION;
+ delete ELT_TSPAN;
+ delete ELT_UNION;
+ delete ELT_VKERN;
+ delete ELT_AUDIO;
+ delete ELT_MO;
+ delete ELT_TENDSTO;
+ delete ELT_VIDEO;
+ delete ELT_COLGROUP;
+ delete ELT_FEDISPLACEMENTMAP;
+ delete ELT_HGROUP;
+ delete ELT_MALIGNGROUP;
+ delete ELT_MSUBSUP;
+ delete ELT_MSUP;
+ delete ELT_RP;
+ delete ELT_OPTGROUP;
+ delete ELT_SAMP;
+ delete ELT_STOP;
+ delete ELT_EQ;
+ delete ELT_BR;
+ delete ELT_ABBR;
+ delete ELT_ANIMATECOLOR;
+ delete ELT_BVAR;
+ delete ELT_CENTER;
+ delete ELT_CURSOR;
+ delete ELT_HR;
+ delete ELT_FEFUNCR;
+ delete ELT_FECOMPONENTTRANSFER;
+ delete ELT_FILTER;
+ delete ELT_FOOTER;
+ delete ELT_FLOOR;
+ delete ELT_FEGAUSSIANBLUR;
+ delete ELT_HEADER;
+ delete ELT_HANDLER;
+ delete ELT_OR;
+ delete ELT_LISTENER;
+ delete ELT_MUNDER;
+ delete ELT_MARKER;
+ delete ELT_METER;
+ delete ELT_MOVER;
+ delete ELT_MUNDEROVER;
+ delete ELT_MERROR;
+ delete ELT_MLABELEDTR;
+ delete ELT_NOBR;
+ delete ELT_NOTANUMBER;
+ delete ELT_POWER;
+ delete ELT_TR;
+ delete ELT_SOLIDCOLOR;
+ delete ELT_SELECTOR;
+ delete ELT_VECTOR;
+ delete ELT_ARCCOS;
+ delete ELT_ADDRESS;
+ delete ELT_CANVAS;
+ delete ELT_COMPLEXES;
+ delete ELT_DEFS;
+ delete ELT_DETAILS;
+ delete ELT_EXISTS;
+ delete ELT_IMPLIES;
+ delete ELT_INTEGERS;
+ delete ELT_MS;
+ delete ELT_MPRESCRIPTS;
+ delete ELT_MMULTISCRIPTS;
+ delete ELT_MINUS;
+ delete ELT_NOFRAMES;
+ delete ELT_NATURALNUMBERS;
+ delete ELT_PRIMES;
+ delete ELT_PROGRESS;
+ delete ELT_PLUS;
+ delete ELT_REALS;
+ delete ELT_RATIONALS;
+ delete ELT_SEMANTICS;
+ delete ELT_TIMES;
+ delete ELT_DT;
+ delete ELT_APPLET;
+ delete ELT_ARCCOT;
+ delete ELT_BASEFONT;
+ delete ELT_CARTESIANPRODUCT;
+ delete ELT_GT;
+ delete ELT_DETERMINANT;
+ delete ELT_DATALIST;
+ delete ELT_EMPTYSET;
+ delete ELT_EQUIVALENT;
+ delete ELT_FONT_FACE_FORMAT;
+ delete ELT_FOREIGNOBJECT;
+ delete ELT_FIELDSET;
+ delete ELT_FRAMESET;
+ delete ELT_FEOFFSET;
+ delete ELT_FESPOTLIGHT;
+ delete ELT_FEPOINTLIGHT;
+ delete ELT_FEDISTANTLIGHT;
+ delete ELT_FONT;
+ delete ELT_LT;
+ delete ELT_INTERSECT;
+ delete ELT_IDENT;
+ delete ELT_INPUT;
+ delete ELT_LIMIT;
+ delete ELT_LOWLIMIT;
+ delete ELT_LINEARGRADIENT;
+ delete ELT_LIST;
+ delete ELT_MOMENT;
+ delete ELT_MROOT;
+ delete ELT_MSQRT;
+ delete ELT_MOMENTABOUT;
+ delete ELT_MTEXT;
+ delete ELT_NOTSUBSET;
+ delete ELT_NOTPRSUBSET;
+ delete ELT_NOSCRIPT;
+ delete ELT_NEST;
+ delete ELT_RT;
+ delete ELT_OBJECT;
+ delete ELT_OUTERPRODUCT;
+ delete ELT_OUTPUT;
+ delete ELT_PRODUCT;
+ delete ELT_PRSUBSET;
+ delete ELT_PLAINTEXT;
+ delete ELT_TT;
+ delete ELT_QUOTIENT;
+ delete ELT_RECT;
+ delete ELT_RADIALGRADIENT;
+ delete ELT_ROOT;
+ delete ELT_SELECT;
+ delete ELT_SCALARPRODUCT;
+ delete ELT_SUBSET;
+ delete ELT_SLOT;
+ delete ELT_SCRIPT;
+ delete ELT_TFOOT;
+ delete ELT_TEXT;
+ delete ELT_UPLIMIT;
+ delete ELT_VECTORPRODUCT;
+ delete ELT_MENU;
+ delete ELT_SDEV;
+ delete ELT_FEDROPSHADOW;
+ delete ELT_MROW;
+ delete ELT_MATRIXROW;
+ delete ELT_VIEW;
+ delete ELT_APPROX;
+ delete ELT_FECOLORMATRIX;
+ delete ELT_FECONVOLVEMATRIX;
+ delete ELT_MATRIX;
+ delete ELT_APPLY;
+ delete ELT_BODY;
+ delete ELT_FEMORPHOLOGY;
+ delete ELT_IMAGINARY;
+ delete ELT_INFINITY;
+ delete ELT_RUBY;
+ delete ELT_SUMMARY;
+ delete ELT_TBODY;
+ delete[] ELEMENT_NAMES;
+}
+
+
diff --git a/components/htmlfive/nsHtml5ElementName.h b/components/htmlfive/nsHtml5ElementName.h
new file mode 100644
index 000000000..97d6d8d5e
--- /dev/null
+++ b/components/htmlfive/nsHtml5ElementName.h
@@ -0,0 +1,620 @@
+/*
+ * Copyright (c) 2008-2017 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit ElementName.java instead and regenerate.
+ */
+
+#ifndef nsHtml5ElementName_h
+#define nsHtml5ElementName_h
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+class nsHtml5StreamParser;
+
+class nsHtml5AttributeName;
+class nsHtml5Tokenizer;
+class nsHtml5TreeBuilder;
+class nsHtml5MetaScanner;
+class nsHtml5UTF16Buffer;
+class nsHtml5StateSnapshot;
+class nsHtml5Portability;
+
+
+class nsHtml5ElementName
+{
+ public:
+ static const int32_t GROUP_MASK = 127;
+
+ static const int32_t NOT_INTERNED = (1 << 30);
+
+ static const int32_t SPECIAL = (1 << 29);
+
+ static const int32_t FOSTER_PARENTING = (1 << 28);
+
+ static const int32_t SCOPING = (1 << 27);
+
+ static const int32_t SCOPING_AS_SVG = (1 << 26);
+
+ static const int32_t SCOPING_AS_MATHML = (1 << 25);
+
+ static const int32_t HTML_INTEGRATION_POINT = (1 << 24);
+
+ static const int32_t OPTIONAL_END_TAG = (1 << 23);
+
+ private:
+ nsIAtom* name;
+ nsIAtom* camelCaseName;
+ mozilla::dom::HTMLContentCreatorFunction htmlCreator;
+ mozilla::dom::SVGContentCreatorFunction svgCreator;
+ public:
+ int32_t flags;
+ inline nsIAtom* getName()
+ {
+ return name;
+ }
+
+ inline nsIAtom* getCamelCaseName()
+ {
+ return camelCaseName;
+ }
+
+ inline mozilla::dom::HTMLContentCreatorFunction getHtmlCreator()
+ {
+ return htmlCreator;
+ }
+
+ inline mozilla::dom::SVGContentCreatorFunction getSvgCreator()
+ {
+ return svgCreator;
+ }
+
+ inline int32_t getFlags()
+ {
+ return flags;
+ }
+
+ inline int32_t getGroup()
+ {
+ return flags & nsHtml5ElementName::GROUP_MASK;
+ }
+
+ inline bool isInterned()
+ {
+ return !(flags & nsHtml5ElementName::NOT_INTERNED);
+ }
+
+ inline static int32_t levelOrderBinarySearch(jArray<int32_t,int32_t> data, int32_t key)
+ {
+ int32_t n = data.length;
+ int32_t i = 0;
+ while (i < n) {
+ int32_t val = data[i];
+ if (val < key) {
+ i = 2 * i + 2;
+ } else if (val > key) {
+ i = 2 * i + 1;
+ } else {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ inline static nsHtml5ElementName* elementNameByBuffer(char16_t* buf, int32_t offset, int32_t length, nsHtml5AtomTable* interner)
+ {
+ uint32_t hash = nsHtml5ElementName::bufToHash(buf, length);
+ jArray<int32_t,int32_t> hashes;
+ hashes = nsHtml5ElementName::ELEMENT_HASHES;
+ int32_t index = levelOrderBinarySearch(hashes, hash);
+ if (index < 0) {
+ return nullptr;
+ } else {
+ nsHtml5ElementName* elementName = nsHtml5ElementName::ELEMENT_NAMES[index];
+ nsIAtom* name = elementName->name;
+ if (!nsHtml5Portability::localEqualsBuffer(name, buf, offset, length)) {
+ return nullptr;
+ }
+ return elementName;
+ }
+ }
+
+ private:
+ inline static uint32_t bufToHash(char16_t* buf, int32_t length)
+ {
+ uint32_t len = length;
+ uint32_t first = buf[0];
+ first <<= 19;
+ uint32_t second = 1 << 23;
+ uint32_t third = 0;
+ uint32_t fourth = 0;
+ uint32_t fifth = 0;
+ if (length >= 4) {
+ second = buf[length - 4];
+ second <<= 4;
+ third = buf[length - 3];
+ third <<= 9;
+ fourth = buf[length - 2];
+ fourth <<= 14;
+ fifth = buf[length - 1];
+ fifth <<= 24;
+ } else if (length == 3) {
+ second = buf[1];
+ second <<= 4;
+ third = buf[2];
+ third <<= 9;
+ } else if (length == 2) {
+ second = buf[1];
+ second <<= 24;
+ }
+ return len + first + second + third + fourth + fifth;
+ }
+
+ nsHtml5ElementName(nsIAtom* name, nsIAtom* camelCaseName, mozilla::dom::HTMLContentCreatorFunction htmlCreator, mozilla::dom::SVGContentCreatorFunction svgCreator, int32_t flags);
+ public:
+ nsHtml5ElementName();
+ ~nsHtml5ElementName();
+ inline void setNameForNonInterned(nsIAtom* name, bool custom)
+ {
+ this->name = name;
+ this->camelCaseName = name;
+ if (custom) {
+ this->htmlCreator = NS_NewCustomElement;
+ } else {
+ this->htmlCreator = NS_NewHTMLUnknownElement;
+ }
+ MOZ_ASSERT(this->flags == nsHtml5ElementName::NOT_INTERNED);
+ }
+
+ inline bool isCustom()
+ {
+ return this->htmlCreator == NS_NewCustomElement;
+ }
+
+ static nsHtml5ElementName* ELT_ISINDEX;
+ static nsHtml5ElementName* ELT_ANNOTATION_XML;
+ static nsHtml5ElementName* ELT_AND;
+ static nsHtml5ElementName* ELT_ARG;
+ static nsHtml5ElementName* ELT_ABS;
+ static nsHtml5ElementName* ELT_BIG;
+ static nsHtml5ElementName* ELT_BDO;
+ static nsHtml5ElementName* ELT_CSC;
+ static nsHtml5ElementName* ELT_COL;
+ static nsHtml5ElementName* ELT_COS;
+ static nsHtml5ElementName* ELT_COT;
+ static nsHtml5ElementName* ELT_DEL;
+ static nsHtml5ElementName* ELT_DFN;
+ static nsHtml5ElementName* ELT_DIR;
+ static nsHtml5ElementName* ELT_DIV;
+ static nsHtml5ElementName* ELT_EXP;
+ static nsHtml5ElementName* ELT_GCD;
+ static nsHtml5ElementName* ELT_GEQ;
+ static nsHtml5ElementName* ELT_IMG;
+ static nsHtml5ElementName* ELT_INS;
+ static nsHtml5ElementName* ELT_INT;
+ static nsHtml5ElementName* ELT_KBD;
+ static nsHtml5ElementName* ELT_LOG;
+ static nsHtml5ElementName* ELT_LCM;
+ static nsHtml5ElementName* ELT_LEQ;
+ static nsHtml5ElementName* ELT_MTD;
+ static nsHtml5ElementName* ELT_MIN;
+ static nsHtml5ElementName* ELT_MAP;
+ static nsHtml5ElementName* ELT_MTR;
+ static nsHtml5ElementName* ELT_MAX;
+ static nsHtml5ElementName* ELT_NEQ;
+ static nsHtml5ElementName* ELT_NOT;
+ static nsHtml5ElementName* ELT_NAV;
+ static nsHtml5ElementName* ELT_PRE;
+ static nsHtml5ElementName* ELT_A;
+ static nsHtml5ElementName* ELT_B;
+ static nsHtml5ElementName* ELT_RTC;
+ static nsHtml5ElementName* ELT_REM;
+ static nsHtml5ElementName* ELT_SUB;
+ static nsHtml5ElementName* ELT_SEC;
+ static nsHtml5ElementName* ELT_SVG;
+ static nsHtml5ElementName* ELT_SUM;
+ static nsHtml5ElementName* ELT_SIN;
+ static nsHtml5ElementName* ELT_SEP;
+ static nsHtml5ElementName* ELT_SUP;
+ static nsHtml5ElementName* ELT_SET;
+ static nsHtml5ElementName* ELT_TAN;
+ static nsHtml5ElementName* ELT_USE;
+ static nsHtml5ElementName* ELT_VAR;
+ static nsHtml5ElementName* ELT_G;
+ static nsHtml5ElementName* ELT_WBR;
+ static nsHtml5ElementName* ELT_XMP;
+ static nsHtml5ElementName* ELT_XOR;
+ static nsHtml5ElementName* ELT_I;
+ static nsHtml5ElementName* ELT_P;
+ static nsHtml5ElementName* ELT_Q;
+ static nsHtml5ElementName* ELT_S;
+ static nsHtml5ElementName* ELT_U;
+ static nsHtml5ElementName* ELT_H1;
+ static nsHtml5ElementName* ELT_H2;
+ static nsHtml5ElementName* ELT_H3;
+ static nsHtml5ElementName* ELT_H4;
+ static nsHtml5ElementName* ELT_H5;
+ static nsHtml5ElementName* ELT_H6;
+ static nsHtml5ElementName* ELT_AREA;
+ static nsHtml5ElementName* ELT_DATA;
+ static nsHtml5ElementName* ELT_EULERGAMMA;
+ static nsHtml5ElementName* ELT_FEFUNCA;
+ static nsHtml5ElementName* ELT_LAMBDA;
+ static nsHtml5ElementName* ELT_METADATA;
+ static nsHtml5ElementName* ELT_META;
+ static nsHtml5ElementName* ELT_TEXTAREA;
+ static nsHtml5ElementName* ELT_FEFUNCB;
+ static nsHtml5ElementName* ELT_MSUB;
+ static nsHtml5ElementName* ELT_RB;
+ static nsHtml5ElementName* ELT_ARCSEC;
+ static nsHtml5ElementName* ELT_ARCCSC;
+ static nsHtml5ElementName* ELT_DEFINITION_SRC;
+ static nsHtml5ElementName* ELT_DESC;
+ static nsHtml5ElementName* ELT_FONT_FACE_SRC;
+ static nsHtml5ElementName* ELT_MFRAC;
+ static nsHtml5ElementName* ELT_DD;
+ static nsHtml5ElementName* ELT_BGSOUND;
+ static nsHtml5ElementName* ELT_CARD;
+ static nsHtml5ElementName* ELT_DISCARD;
+ static nsHtml5ElementName* ELT_EMBED;
+ static nsHtml5ElementName* ELT_FEBLEND;
+ static nsHtml5ElementName* ELT_FEFLOOD;
+ static nsHtml5ElementName* ELT_GRAD;
+ static nsHtml5ElementName* ELT_HEAD;
+ static nsHtml5ElementName* ELT_LEGEND;
+ static nsHtml5ElementName* ELT_MFENCED;
+ static nsHtml5ElementName* ELT_MPADDED;
+ static nsHtml5ElementName* ELT_NOEMBED;
+ static nsHtml5ElementName* ELT_TD;
+ static nsHtml5ElementName* ELT_THEAD;
+ static nsHtml5ElementName* ELT_ASIDE;
+ static nsHtml5ElementName* ELT_ARTICLE;
+ static nsHtml5ElementName* ELT_ANIMATE;
+ static nsHtml5ElementName* ELT_BASE;
+ static nsHtml5ElementName* ELT_BLOCKQUOTE;
+ static nsHtml5ElementName* ELT_CODE;
+ static nsHtml5ElementName* ELT_CIRCLE;
+ static nsHtml5ElementName* ELT_COLOR_PROFILE;
+ static nsHtml5ElementName* ELT_COMPOSE;
+ static nsHtml5ElementName* ELT_CONJUGATE;
+ static nsHtml5ElementName* ELT_CITE;
+ static nsHtml5ElementName* ELT_DIVERGENCE;
+ static nsHtml5ElementName* ELT_DIVIDE;
+ static nsHtml5ElementName* ELT_DEGREE;
+ static nsHtml5ElementName* ELT_DECLARE;
+ static nsHtml5ElementName* ELT_DATATEMPLATE;
+ static nsHtml5ElementName* ELT_EXPONENTIALE;
+ static nsHtml5ElementName* ELT_ELLIPSE;
+ static nsHtml5ElementName* ELT_FONT_FACE;
+ static nsHtml5ElementName* ELT_FETURBULENCE;
+ static nsHtml5ElementName* ELT_FEMERGENODE;
+ static nsHtml5ElementName* ELT_FEIMAGE;
+ static nsHtml5ElementName* ELT_FEMERGE;
+ static nsHtml5ElementName* ELT_FETILE;
+ static nsHtml5ElementName* ELT_FONT_FACE_NAME;
+ static nsHtml5ElementName* ELT_FRAME;
+ static nsHtml5ElementName* ELT_FIGURE;
+ static nsHtml5ElementName* ELT_FALSE;
+ static nsHtml5ElementName* ELT_FECOMPOSITE;
+ static nsHtml5ElementName* ELT_IMAGE;
+ static nsHtml5ElementName* ELT_IFRAME;
+ static nsHtml5ElementName* ELT_INVERSE;
+ static nsHtml5ElementName* ELT_LINE;
+ static nsHtml5ElementName* ELT_LOGBASE;
+ static nsHtml5ElementName* ELT_MSPACE;
+ static nsHtml5ElementName* ELT_MODE;
+ static nsHtml5ElementName* ELT_MTABLE;
+ static nsHtml5ElementName* ELT_MSTYLE;
+ static nsHtml5ElementName* ELT_MENCLOSE;
+ static nsHtml5ElementName* ELT_NONE;
+ static nsHtml5ElementName* ELT_OTHERWISE;
+ static nsHtml5ElementName* ELT_PIECE;
+ static nsHtml5ElementName* ELT_POLYLINE;
+ static nsHtml5ElementName* ELT_PICTURE;
+ static nsHtml5ElementName* ELT_PIECEWISE;
+ static nsHtml5ElementName* ELT_RULE;
+ static nsHtml5ElementName* ELT_SOURCE;
+ static nsHtml5ElementName* ELT_STRIKE;
+ static nsHtml5ElementName* ELT_STYLE;
+ static nsHtml5ElementName* ELT_TABLE;
+ static nsHtml5ElementName* ELT_TITLE;
+ static nsHtml5ElementName* ELT_TIME;
+ static nsHtml5ElementName* ELT_TRANSPOSE;
+ static nsHtml5ElementName* ELT_TEMPLATE;
+ static nsHtml5ElementName* ELT_TRUE;
+ static nsHtml5ElementName* ELT_VARIANCE;
+ static nsHtml5ElementName* ELT_ALTGLYPHDEF;
+ static nsHtml5ElementName* ELT_DIFF;
+ static nsHtml5ElementName* ELT_FACTOROF;
+ static nsHtml5ElementName* ELT_GLYPHREF;
+ static nsHtml5ElementName* ELT_PARTIALDIFF;
+ static nsHtml5ElementName* ELT_SETDIFF;
+ static nsHtml5ElementName* ELT_TREF;
+ static nsHtml5ElementName* ELT_CEILING;
+ static nsHtml5ElementName* ELT_DIALOG;
+ static nsHtml5ElementName* ELT_FEFUNCG;
+ static nsHtml5ElementName* ELT_FEDIFFUSELIGHTING;
+ static nsHtml5ElementName* ELT_FESPECULARLIGHTING;
+ static nsHtml5ElementName* ELT_LISTING;
+ static nsHtml5ElementName* ELT_STRONG;
+ static nsHtml5ElementName* ELT_ARCSECH;
+ static nsHtml5ElementName* ELT_ARCCSCH;
+ static nsHtml5ElementName* ELT_ARCTANH;
+ static nsHtml5ElementName* ELT_ARCSINH;
+ static nsHtml5ElementName* ELT_ALTGLYPH;
+ static nsHtml5ElementName* ELT_ARCCOSH;
+ static nsHtml5ElementName* ELT_ARCCOTH;
+ static nsHtml5ElementName* ELT_CSCH;
+ static nsHtml5ElementName* ELT_COSH;
+ static nsHtml5ElementName* ELT_CLIPPATH;
+ static nsHtml5ElementName* ELT_COTH;
+ static nsHtml5ElementName* ELT_GLYPH;
+ static nsHtml5ElementName* ELT_MGLYPH;
+ static nsHtml5ElementName* ELT_MISSING_GLYPH;
+ static nsHtml5ElementName* ELT_MATH;
+ static nsHtml5ElementName* ELT_MPATH;
+ static nsHtml5ElementName* ELT_PREFETCH;
+ static nsHtml5ElementName* ELT_PATH;
+ static nsHtml5ElementName* ELT_TH;
+ static nsHtml5ElementName* ELT_SECH;
+ static nsHtml5ElementName* ELT_SWITCH;
+ static nsHtml5ElementName* ELT_SINH;
+ static nsHtml5ElementName* ELT_TANH;
+ static nsHtml5ElementName* ELT_TEXTPATH;
+ static nsHtml5ElementName* ELT_CI;
+ static nsHtml5ElementName* ELT_FONT_FACE_URI;
+ static nsHtml5ElementName* ELT_LI;
+ static nsHtml5ElementName* ELT_IMAGINARYI;
+ static nsHtml5ElementName* ELT_MI;
+ static nsHtml5ElementName* ELT_PI;
+ static nsHtml5ElementName* ELT_LINK;
+ static nsHtml5ElementName* ELT_MARK;
+ static nsHtml5ElementName* ELT_MALIGNMARK;
+ static nsHtml5ElementName* ELT_MASK;
+ static nsHtml5ElementName* ELT_TBREAK;
+ static nsHtml5ElementName* ELT_TRACK;
+ static nsHtml5ElementName* ELT_DL;
+ static nsHtml5ElementName* ELT_CSYMBOL;
+ static nsHtml5ElementName* ELT_CURL;
+ static nsHtml5ElementName* ELT_FACTORIAL;
+ static nsHtml5ElementName* ELT_FORALL;
+ static nsHtml5ElementName* ELT_HTML;
+ static nsHtml5ElementName* ELT_INTERVAL;
+ static nsHtml5ElementName* ELT_OL;
+ static nsHtml5ElementName* ELT_LABEL;
+ static nsHtml5ElementName* ELT_UL;
+ static nsHtml5ElementName* ELT_REAL;
+ static nsHtml5ElementName* ELT_SMALL;
+ static nsHtml5ElementName* ELT_SYMBOL;
+ static nsHtml5ElementName* ELT_ALTGLYPHITEM;
+ static nsHtml5ElementName* ELT_ANIMATETRANSFORM;
+ static nsHtml5ElementName* ELT_ACRONYM;
+ static nsHtml5ElementName* ELT_EM;
+ static nsHtml5ElementName* ELT_FORM;
+ static nsHtml5ElementName* ELT_MENUITEM;
+ static nsHtml5ElementName* ELT_MPHANTOM;
+ static nsHtml5ElementName* ELT_PARAM;
+ static nsHtml5ElementName* ELT_CN;
+ static nsHtml5ElementName* ELT_ARCTAN;
+ static nsHtml5ElementName* ELT_ARCSIN;
+ static nsHtml5ElementName* ELT_ANIMATION;
+ static nsHtml5ElementName* ELT_ANNOTATION;
+ static nsHtml5ElementName* ELT_ANIMATEMOTION;
+ static nsHtml5ElementName* ELT_BUTTON;
+ static nsHtml5ElementName* ELT_FN;
+ static nsHtml5ElementName* ELT_CODOMAIN;
+ static nsHtml5ElementName* ELT_CAPTION;
+ static nsHtml5ElementName* ELT_CONDITION;
+ static nsHtml5ElementName* ELT_DOMAIN;
+ static nsHtml5ElementName* ELT_DOMAINOFAPPLICATION;
+ static nsHtml5ElementName* ELT_IN;
+ static nsHtml5ElementName* ELT_FIGCAPTION;
+ static nsHtml5ElementName* ELT_HKERN;
+ static nsHtml5ElementName* ELT_LN;
+ static nsHtml5ElementName* ELT_MN;
+ static nsHtml5ElementName* ELT_KEYGEN;
+ static nsHtml5ElementName* ELT_LAPLACIAN;
+ static nsHtml5ElementName* ELT_MEAN;
+ static nsHtml5ElementName* ELT_MEDIAN;
+ static nsHtml5ElementName* ELT_MAIN;
+ static nsHtml5ElementName* ELT_MACTION;
+ static nsHtml5ElementName* ELT_NOTIN;
+ static nsHtml5ElementName* ELT_OPTION;
+ static nsHtml5ElementName* ELT_POLYGON;
+ static nsHtml5ElementName* ELT_PATTERN;
+ static nsHtml5ElementName* ELT_RELN;
+ static nsHtml5ElementName* ELT_SPAN;
+ static nsHtml5ElementName* ELT_SECTION;
+ static nsHtml5ElementName* ELT_TSPAN;
+ static nsHtml5ElementName* ELT_UNION;
+ static nsHtml5ElementName* ELT_VKERN;
+ static nsHtml5ElementName* ELT_AUDIO;
+ static nsHtml5ElementName* ELT_MO;
+ static nsHtml5ElementName* ELT_TENDSTO;
+ static nsHtml5ElementName* ELT_VIDEO;
+ static nsHtml5ElementName* ELT_COLGROUP;
+ static nsHtml5ElementName* ELT_FEDISPLACEMENTMAP;
+ static nsHtml5ElementName* ELT_HGROUP;
+ static nsHtml5ElementName* ELT_MALIGNGROUP;
+ static nsHtml5ElementName* ELT_MSUBSUP;
+ static nsHtml5ElementName* ELT_MSUP;
+ static nsHtml5ElementName* ELT_RP;
+ static nsHtml5ElementName* ELT_OPTGROUP;
+ static nsHtml5ElementName* ELT_SAMP;
+ static nsHtml5ElementName* ELT_STOP;
+ static nsHtml5ElementName* ELT_EQ;
+ static nsHtml5ElementName* ELT_BR;
+ static nsHtml5ElementName* ELT_ABBR;
+ static nsHtml5ElementName* ELT_ANIMATECOLOR;
+ static nsHtml5ElementName* ELT_BVAR;
+ static nsHtml5ElementName* ELT_CENTER;
+ static nsHtml5ElementName* ELT_CURSOR;
+ static nsHtml5ElementName* ELT_HR;
+ static nsHtml5ElementName* ELT_FEFUNCR;
+ static nsHtml5ElementName* ELT_FECOMPONENTTRANSFER;
+ static nsHtml5ElementName* ELT_FILTER;
+ static nsHtml5ElementName* ELT_FOOTER;
+ static nsHtml5ElementName* ELT_FLOOR;
+ static nsHtml5ElementName* ELT_FEGAUSSIANBLUR;
+ static nsHtml5ElementName* ELT_HEADER;
+ static nsHtml5ElementName* ELT_HANDLER;
+ static nsHtml5ElementName* ELT_OR;
+ static nsHtml5ElementName* ELT_LISTENER;
+ static nsHtml5ElementName* ELT_MUNDER;
+ static nsHtml5ElementName* ELT_MARKER;
+ static nsHtml5ElementName* ELT_METER;
+ static nsHtml5ElementName* ELT_MOVER;
+ static nsHtml5ElementName* ELT_MUNDEROVER;
+ static nsHtml5ElementName* ELT_MERROR;
+ static nsHtml5ElementName* ELT_MLABELEDTR;
+ static nsHtml5ElementName* ELT_NOBR;
+ static nsHtml5ElementName* ELT_NOTANUMBER;
+ static nsHtml5ElementName* ELT_POWER;
+ static nsHtml5ElementName* ELT_TR;
+ static nsHtml5ElementName* ELT_SOLIDCOLOR;
+ static nsHtml5ElementName* ELT_SELECTOR;
+ static nsHtml5ElementName* ELT_VECTOR;
+ static nsHtml5ElementName* ELT_ARCCOS;
+ static nsHtml5ElementName* ELT_ADDRESS;
+ static nsHtml5ElementName* ELT_CANVAS;
+ static nsHtml5ElementName* ELT_COMPLEXES;
+ static nsHtml5ElementName* ELT_DEFS;
+ static nsHtml5ElementName* ELT_DETAILS;
+ static nsHtml5ElementName* ELT_EXISTS;
+ static nsHtml5ElementName* ELT_IMPLIES;
+ static nsHtml5ElementName* ELT_INTEGERS;
+ static nsHtml5ElementName* ELT_MS;
+ static nsHtml5ElementName* ELT_MPRESCRIPTS;
+ static nsHtml5ElementName* ELT_MMULTISCRIPTS;
+ static nsHtml5ElementName* ELT_MINUS;
+ static nsHtml5ElementName* ELT_NOFRAMES;
+ static nsHtml5ElementName* ELT_NATURALNUMBERS;
+ static nsHtml5ElementName* ELT_PRIMES;
+ static nsHtml5ElementName* ELT_PROGRESS;
+ static nsHtml5ElementName* ELT_PLUS;
+ static nsHtml5ElementName* ELT_REALS;
+ static nsHtml5ElementName* ELT_RATIONALS;
+ static nsHtml5ElementName* ELT_SEMANTICS;
+ static nsHtml5ElementName* ELT_TIMES;
+ static nsHtml5ElementName* ELT_DT;
+ static nsHtml5ElementName* ELT_APPLET;
+ static nsHtml5ElementName* ELT_ARCCOT;
+ static nsHtml5ElementName* ELT_BASEFONT;
+ static nsHtml5ElementName* ELT_CARTESIANPRODUCT;
+ static nsHtml5ElementName* ELT_GT;
+ static nsHtml5ElementName* ELT_DETERMINANT;
+ static nsHtml5ElementName* ELT_DATALIST;
+ static nsHtml5ElementName* ELT_EMPTYSET;
+ static nsHtml5ElementName* ELT_EQUIVALENT;
+ static nsHtml5ElementName* ELT_FONT_FACE_FORMAT;
+ static nsHtml5ElementName* ELT_FOREIGNOBJECT;
+ static nsHtml5ElementName* ELT_FIELDSET;
+ static nsHtml5ElementName* ELT_FRAMESET;
+ static nsHtml5ElementName* ELT_FEOFFSET;
+ static nsHtml5ElementName* ELT_FESPOTLIGHT;
+ static nsHtml5ElementName* ELT_FEPOINTLIGHT;
+ static nsHtml5ElementName* ELT_FEDISTANTLIGHT;
+ static nsHtml5ElementName* ELT_FONT;
+ static nsHtml5ElementName* ELT_LT;
+ static nsHtml5ElementName* ELT_INTERSECT;
+ static nsHtml5ElementName* ELT_IDENT;
+ static nsHtml5ElementName* ELT_INPUT;
+ static nsHtml5ElementName* ELT_LIMIT;
+ static nsHtml5ElementName* ELT_LOWLIMIT;
+ static nsHtml5ElementName* ELT_LINEARGRADIENT;
+ static nsHtml5ElementName* ELT_LIST;
+ static nsHtml5ElementName* ELT_MOMENT;
+ static nsHtml5ElementName* ELT_MROOT;
+ static nsHtml5ElementName* ELT_MSQRT;
+ static nsHtml5ElementName* ELT_MOMENTABOUT;
+ static nsHtml5ElementName* ELT_MTEXT;
+ static nsHtml5ElementName* ELT_NOTSUBSET;
+ static nsHtml5ElementName* ELT_NOTPRSUBSET;
+ static nsHtml5ElementName* ELT_NOSCRIPT;
+ static nsHtml5ElementName* ELT_NEST;
+ static nsHtml5ElementName* ELT_RT;
+ static nsHtml5ElementName* ELT_OBJECT;
+ static nsHtml5ElementName* ELT_OUTERPRODUCT;
+ static nsHtml5ElementName* ELT_OUTPUT;
+ static nsHtml5ElementName* ELT_PRODUCT;
+ static nsHtml5ElementName* ELT_PRSUBSET;
+ static nsHtml5ElementName* ELT_PLAINTEXT;
+ static nsHtml5ElementName* ELT_TT;
+ static nsHtml5ElementName* ELT_QUOTIENT;
+ static nsHtml5ElementName* ELT_RECT;
+ static nsHtml5ElementName* ELT_RADIALGRADIENT;
+ static nsHtml5ElementName* ELT_ROOT;
+ static nsHtml5ElementName* ELT_SELECT;
+ static nsHtml5ElementName* ELT_SCALARPRODUCT;
+ static nsHtml5ElementName* ELT_SUBSET;
+ static nsHtml5ElementName* ELT_SLOT;
+ static nsHtml5ElementName* ELT_SCRIPT;
+ static nsHtml5ElementName* ELT_TFOOT;
+ static nsHtml5ElementName* ELT_TEXT;
+ static nsHtml5ElementName* ELT_UPLIMIT;
+ static nsHtml5ElementName* ELT_VECTORPRODUCT;
+ static nsHtml5ElementName* ELT_MENU;
+ static nsHtml5ElementName* ELT_SDEV;
+ static nsHtml5ElementName* ELT_FEDROPSHADOW;
+ static nsHtml5ElementName* ELT_MROW;
+ static nsHtml5ElementName* ELT_MATRIXROW;
+ static nsHtml5ElementName* ELT_VIEW;
+ static nsHtml5ElementName* ELT_APPROX;
+ static nsHtml5ElementName* ELT_FECOLORMATRIX;
+ static nsHtml5ElementName* ELT_FECONVOLVEMATRIX;
+ static nsHtml5ElementName* ELT_MATRIX;
+ static nsHtml5ElementName* ELT_APPLY;
+ static nsHtml5ElementName* ELT_BODY;
+ static nsHtml5ElementName* ELT_FEMORPHOLOGY;
+ static nsHtml5ElementName* ELT_IMAGINARY;
+ static nsHtml5ElementName* ELT_INFINITY;
+ static nsHtml5ElementName* ELT_RUBY;
+ static nsHtml5ElementName* ELT_SUMMARY;
+ static nsHtml5ElementName* ELT_TBODY;
+ private:
+ static nsHtml5ElementName** ELEMENT_NAMES;
+ static staticJArray<int32_t,int32_t> ELEMENT_HASHES;
+ public:
+ static void initializeStatics();
+ static void releaseStatics();
+};
+
+#endif
+
diff --git a/components/htmlfive/nsHtml5Highlighter.cpp b/components/htmlfive/nsHtml5Highlighter.cpp
new file mode 100644
index 000000000..b08179fbd
--- /dev/null
+++ b/components/htmlfive/nsHtml5Highlighter.cpp
@@ -0,0 +1,816 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsHtml5Highlighter.h"
+#include "nsDebug.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5AttributeName.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsHtml5ViewSourceUtils.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+
+// The old code had a limit of 16 tokens. 1300 is a number picked my measuring
+// the size of 16 tokens on cnn.com.
+#define NS_HTML5_HIGHLIGHTER_PRE_BREAK_THRESHOLD 1300
+
+char16_t nsHtml5Highlighter::sComment[] =
+ { 'c', 'o', 'm', 'm', 'e', 'n', 't', 0 };
+
+char16_t nsHtml5Highlighter::sCdata[] =
+ { 'c', 'd', 'a', 't', 'a', 0 };
+
+char16_t nsHtml5Highlighter::sEntity[] =
+ { 'e', 'n', 't', 'i', 't', 'y', 0 };
+
+char16_t nsHtml5Highlighter::sEndTag[] =
+ { 'e', 'n', 'd', '-', 't', 'a', 'g', 0 };
+
+char16_t nsHtml5Highlighter::sStartTag[] =
+ { 's', 't', 'a', 'r', 't', '-', 't', 'a', 'g', 0 };
+
+char16_t nsHtml5Highlighter::sAttributeName[] =
+ { 'a', 't', 't', 'r', 'i', 'b', 'u', 't', 'e', '-', 'n', 'a', 'm', 'e', 0 };
+
+char16_t nsHtml5Highlighter::sAttributeValue[] =
+ { 'a', 't', 't', 'r', 'i', 'b', 'u', 't', 'e', '-',
+ 'v', 'a', 'l', 'u', 'e', 0 };
+
+char16_t nsHtml5Highlighter::sDoctype[] =
+ { 'd', 'o', 'c', 't', 'y', 'p', 'e', 0 };
+
+char16_t nsHtml5Highlighter::sPi[] =
+ { 'p', 'i', 0 };
+
+nsHtml5Highlighter::nsHtml5Highlighter(nsAHtml5TreeOpSink* aOpSink)
+ : mState(nsHtml5Tokenizer::DATA)
+ , mCStart(INT32_MAX)
+ , mPos(0)
+ , mLineNumber(1)
+ , mInlinesOpen(0)
+ , mInCharacters(false)
+ , mBuffer(nullptr)
+ , mOpSink(aOpSink)
+ , mCurrentRun(nullptr)
+ , mAmpersand(nullptr)
+ , mSlash(nullptr)
+ , mHandles(MakeUnique<nsIContent*[]>(NS_HTML5_HIGHLIGHTER_HANDLE_ARRAY_LENGTH))
+ , mHandlesUsed(0)
+ , mSeenBase(false)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+}
+
+nsHtml5Highlighter::~nsHtml5Highlighter()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+}
+
+void
+nsHtml5Highlighter::Start(const nsAutoString& aTitle)
+{
+ // Doctype
+ mOpQueue.AppendElement()->Init(nsGkAtoms::html, EmptyString(), EmptyString());
+
+ mOpQueue.AppendElement()->Init(STANDARDS_MODE);
+
+ // <html> uses NS_NewHTMLSharedElement creator
+ nsIContent** root =
+ CreateElement(nsHtml5Atoms::html, nullptr, nullptr, NS_NewHTMLSharedElement);
+ mOpQueue.AppendElement()->Init(eTreeOpAppendToDocument, root);
+ mStack.AppendElement(root);
+
+ // <head> uses NS_NewHTMLSharedElement creator
+ Push(nsGkAtoms::head, nullptr, NS_NewHTMLSharedElement);
+
+ Push(nsGkAtoms::title, nullptr, NS_NewHTMLTitleElement);
+ // XUL will add the "Source of: " prefix.
+ uint32_t length = aTitle.Length();
+ if (length > INT32_MAX) {
+ length = INT32_MAX;
+ }
+ AppendCharacters(aTitle.BeginReading(), 0, (int32_t)length);
+ Pop(); // title
+
+ Push(nsGkAtoms::link,
+ nsHtml5ViewSourceUtils::NewLinkAttributes(),
+ NS_NewHTMLLinkElement);
+
+ mOpQueue.AppendElement()->Init(eTreeOpUpdateStyleSheet, CurrentNode());
+
+ Pop(); // link
+
+ Pop(); // head
+
+ Push(nsGkAtoms::body,
+ nsHtml5ViewSourceUtils::NewBodyAttributes(),
+ NS_NewHTMLBodyElement);
+
+ nsHtml5HtmlAttributes* preAttrs = new nsHtml5HtmlAttributes(0);
+ nsHtml5String preId = nsHtml5Portability::newStringFromLiteral("line1");
+ preAttrs->addAttribute(nsHtml5AttributeName::ATTR_ID, preId, -1);
+ Push(nsGkAtoms::pre, preAttrs, NS_NewHTMLPreElement);
+
+ StartCharacters();
+
+ mOpQueue.AppendElement()->Init(eTreeOpStartLayout);
+}
+
+int32_t
+nsHtml5Highlighter::Transition(int32_t aState, bool aReconsume, int32_t aPos)
+{
+ mPos = aPos;
+ switch (mState) {
+ case nsHtml5Tokenizer::SCRIPT_DATA:
+ case nsHtml5Tokenizer::RAWTEXT:
+ case nsHtml5Tokenizer::RCDATA:
+ case nsHtml5Tokenizer::DATA:
+ // We can transition on < and on &. Either way, we don't yet know the
+ // role of the token, so open a span without class.
+ if (aState == nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE) {
+ StartSpan();
+ // Start another span for highlighting the ampersand
+ StartSpan();
+ mAmpersand = CurrentNode();
+ } else {
+ EndCharactersAndStartMarkupRun();
+ }
+ break;
+ case nsHtml5Tokenizer::TAG_OPEN:
+ switch (aState) {
+ case nsHtml5Tokenizer::TAG_NAME:
+ StartSpan(sStartTag);
+ break;
+ case nsHtml5Tokenizer::DATA:
+ FinishTag(); // DATA
+ break;
+ case nsHtml5Tokenizer::PROCESSING_INSTRUCTION:
+ AddClass(sPi);
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::TAG_NAME:
+ switch (aState) {
+ case nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME:
+ EndSpanOrA(); // nsHtml5Tokenizer::TAG_NAME
+ break;
+ case nsHtml5Tokenizer::SELF_CLOSING_START_TAG:
+ EndSpanOrA(); // nsHtml5Tokenizer::TAG_NAME
+ StartSpan(); // for highlighting the slash
+ mSlash = CurrentNode();
+ break;
+ default:
+ FinishTag();
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME:
+ switch (aState) {
+ case nsHtml5Tokenizer::ATTRIBUTE_NAME:
+ StartSpan(sAttributeName);
+ break;
+ case nsHtml5Tokenizer::SELF_CLOSING_START_TAG:
+ StartSpan(); // for highlighting the slash
+ mSlash = CurrentNode();
+ break;
+ default:
+ FinishTag();
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::ATTRIBUTE_NAME:
+ switch (aState) {
+ case nsHtml5Tokenizer::AFTER_ATTRIBUTE_NAME:
+ case nsHtml5Tokenizer::BEFORE_ATTRIBUTE_VALUE:
+ EndSpanOrA(); // nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME
+ break;
+ case nsHtml5Tokenizer::SELF_CLOSING_START_TAG:
+ EndSpanOrA(); // nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME
+ StartSpan(); // for highlighting the slash
+ mSlash = CurrentNode();
+ break;
+ default:
+ FinishTag();
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::BEFORE_ATTRIBUTE_VALUE:
+ switch (aState) {
+ case nsHtml5Tokenizer::ATTRIBUTE_VALUE_DOUBLE_QUOTED:
+ case nsHtml5Tokenizer::ATTRIBUTE_VALUE_SINGLE_QUOTED:
+ FlushCurrent();
+ StartA();
+ break;
+ case nsHtml5Tokenizer::ATTRIBUTE_VALUE_UNQUOTED:
+ StartA();
+ break;
+ default:
+ FinishTag();
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::ATTRIBUTE_VALUE_DOUBLE_QUOTED:
+ case nsHtml5Tokenizer::ATTRIBUTE_VALUE_SINGLE_QUOTED:
+ switch (aState) {
+ case nsHtml5Tokenizer::AFTER_ATTRIBUTE_VALUE_QUOTED:
+ EndSpanOrA();
+ break;
+ case nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE:
+ StartSpan();
+ StartSpan(); // for ampersand itself
+ mAmpersand = CurrentNode();
+ break;
+ default:
+ NS_NOTREACHED("Impossible transition.");
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::AFTER_ATTRIBUTE_VALUE_QUOTED:
+ switch (aState) {
+ case nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME:
+ break;
+ case nsHtml5Tokenizer::SELF_CLOSING_START_TAG:
+ StartSpan(); // for highlighting the slash
+ mSlash = CurrentNode();
+ break;
+ default:
+ FinishTag();
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::SELF_CLOSING_START_TAG:
+ EndSpanOrA(); // end the slash highlight
+ switch (aState) {
+ case nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME:
+ break;
+ default:
+ FinishTag();
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::ATTRIBUTE_VALUE_UNQUOTED:
+ switch (aState) {
+ case nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME:
+ EndSpanOrA();
+ break;
+ case nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE:
+ StartSpan();
+ StartSpan(); // for ampersand itself
+ mAmpersand = CurrentNode();
+ break;
+ default:
+ FinishTag();
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::AFTER_ATTRIBUTE_NAME:
+ switch (aState) {
+ case nsHtml5Tokenizer::SELF_CLOSING_START_TAG:
+ StartSpan(); // for highlighting the slash
+ mSlash = CurrentNode();
+ break;
+ case nsHtml5Tokenizer::BEFORE_ATTRIBUTE_VALUE:
+ break;
+ case nsHtml5Tokenizer::ATTRIBUTE_NAME:
+ StartSpan(sAttributeName);
+ break;
+ default:
+ FinishTag();
+ break;
+ }
+ break;
+ // most comment states are omitted, because they don't matter to
+ // highlighting
+ case nsHtml5Tokenizer::COMMENT_START:
+ case nsHtml5Tokenizer::COMMENT_END:
+ case nsHtml5Tokenizer::COMMENT_END_BANG:
+ case nsHtml5Tokenizer::COMMENT_START_DASH:
+ case nsHtml5Tokenizer::BOGUS_COMMENT:
+ case nsHtml5Tokenizer::BOGUS_COMMENT_HYPHEN:
+ if (aState == nsHtml5Tokenizer::DATA) {
+ AddClass(sComment);
+ FinishTag();
+ }
+ break;
+ // most cdata states are omitted, because they don't matter to
+ // highlighting
+ case nsHtml5Tokenizer::CDATA_RSQB_RSQB:
+ if (aState == nsHtml5Tokenizer::DATA) {
+ AddClass(sCdata);
+ FinishTag();
+ }
+ break;
+ case nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE:
+ EndSpanOrA(); // the span for the ampersand
+ switch (aState) {
+ case nsHtml5Tokenizer::CONSUME_NCR:
+ case nsHtml5Tokenizer::CHARACTER_REFERENCE_HILO_LOOKUP:
+ break;
+ default:
+ // not actually a character reference
+ EndSpanOrA();
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::CHARACTER_REFERENCE_HILO_LOOKUP:
+ if (aState == nsHtml5Tokenizer::CHARACTER_REFERENCE_TAIL) {
+ break;
+ }
+ // not actually a character reference
+ EndSpanOrA();
+ break;
+ case nsHtml5Tokenizer::CHARACTER_REFERENCE_TAIL:
+ if (!aReconsume) {
+ FlushCurrent();
+ }
+ EndSpanOrA();
+ break;
+ case nsHtml5Tokenizer::DECIMAL_NRC_LOOP:
+ case nsHtml5Tokenizer::HEX_NCR_LOOP:
+ switch (aState) {
+ case nsHtml5Tokenizer::HANDLE_NCR_VALUE:
+ AddClass(sEntity);
+ FlushCurrent();
+ break;
+ case nsHtml5Tokenizer::HANDLE_NCR_VALUE_RECONSUME:
+ AddClass(sEntity);
+ break;
+ }
+ EndSpanOrA();
+ break;
+ case nsHtml5Tokenizer::CLOSE_TAG_OPEN:
+ switch (aState) {
+ case nsHtml5Tokenizer::DATA:
+ FinishTag();
+ break;
+ case nsHtml5Tokenizer::TAG_NAME:
+ StartSpan(sEndTag);
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::RAWTEXT_RCDATA_LESS_THAN_SIGN:
+ if (aState == nsHtml5Tokenizer::NON_DATA_END_TAG_NAME) {
+ FlushCurrent();
+ StartSpan(); // don't know if it is "end-tag" yet :-(
+ break;
+ }
+ EndSpanOrA();
+ StartCharacters();
+ break;
+ case nsHtml5Tokenizer::NON_DATA_END_TAG_NAME:
+ switch (aState) {
+ case nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME:
+ AddClass(sEndTag);
+ EndSpanOrA();
+ break;
+ case nsHtml5Tokenizer::SELF_CLOSING_START_TAG:
+ AddClass(sEndTag);
+ EndSpanOrA();
+ StartSpan(); // for highlighting the slash
+ mSlash = CurrentNode();
+ break;
+ case nsHtml5Tokenizer::DATA: // yes, as a result of emitting the token
+ AddClass(sEndTag);
+ FinishTag();
+ break;
+ default:
+ FinishTag();
+ break;
+ }
+ break;
+ case nsHtml5Tokenizer::SCRIPT_DATA_LESS_THAN_SIGN:
+ case nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN:
+ if (aState == nsHtml5Tokenizer::NON_DATA_END_TAG_NAME) {
+ FlushCurrent();
+ StartSpan(); // don't know if it is "end-tag" yet :-(
+ break;
+ }
+ FinishTag();
+ break;
+ case nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED_DASH_DASH:
+ case nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED:
+ case nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED_DASH:
+ if (aState == nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN) {
+ EndCharactersAndStartMarkupRun();
+ }
+ break;
+ // Lots of double escape states omitted, because they don't highlight.
+ // Likewise, only doctype states that can emit the doctype are of
+ // interest. Otherwise, the transition out of bogus comment deals.
+ case nsHtml5Tokenizer::BEFORE_DOCTYPE_NAME:
+ case nsHtml5Tokenizer::DOCTYPE_NAME:
+ case nsHtml5Tokenizer::AFTER_DOCTYPE_NAME:
+ case nsHtml5Tokenizer::AFTER_DOCTYPE_PUBLIC_KEYWORD:
+ case nsHtml5Tokenizer::BEFORE_DOCTYPE_PUBLIC_IDENTIFIER:
+ case nsHtml5Tokenizer::DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED:
+ case nsHtml5Tokenizer::AFTER_DOCTYPE_PUBLIC_IDENTIFIER:
+ case nsHtml5Tokenizer::BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS:
+ case nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED:
+ case nsHtml5Tokenizer::AFTER_DOCTYPE_SYSTEM_IDENTIFIER:
+ case nsHtml5Tokenizer::BOGUS_DOCTYPE:
+ case nsHtml5Tokenizer::AFTER_DOCTYPE_SYSTEM_KEYWORD:
+ case nsHtml5Tokenizer::BEFORE_DOCTYPE_SYSTEM_IDENTIFIER:
+ case nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED:
+ case nsHtml5Tokenizer::DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED:
+ if (aState == nsHtml5Tokenizer::DATA) {
+ AddClass(sDoctype);
+ FinishTag();
+ }
+ break;
+ case nsHtml5Tokenizer::PROCESSING_INSTRUCTION_QUESTION_MARK:
+ if (aState == nsHtml5Tokenizer::DATA) {
+ FinishTag();
+ }
+ break;
+ default:
+ break;
+ }
+ mState = aState;
+ return aState;
+}
+
+void
+nsHtml5Highlighter::End()
+{
+ switch (mState) {
+ case nsHtml5Tokenizer::COMMENT_END:
+ case nsHtml5Tokenizer::COMMENT_END_BANG:
+ case nsHtml5Tokenizer::COMMENT_START_DASH:
+ case nsHtml5Tokenizer::BOGUS_COMMENT:
+ case nsHtml5Tokenizer::BOGUS_COMMENT_HYPHEN:
+ AddClass(sComment);
+ break;
+ case nsHtml5Tokenizer::CDATA_RSQB_RSQB:
+ AddClass(sCdata);
+ break;
+ case nsHtml5Tokenizer::DECIMAL_NRC_LOOP:
+ case nsHtml5Tokenizer::HEX_NCR_LOOP:
+ // XXX need tokenizer help here
+ break;
+ case nsHtml5Tokenizer::BEFORE_DOCTYPE_NAME:
+ case nsHtml5Tokenizer::DOCTYPE_NAME:
+ case nsHtml5Tokenizer::AFTER_DOCTYPE_NAME:
+ case nsHtml5Tokenizer::AFTER_DOCTYPE_PUBLIC_KEYWORD:
+ case nsHtml5Tokenizer::BEFORE_DOCTYPE_PUBLIC_IDENTIFIER:
+ case nsHtml5Tokenizer::DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED:
+ case nsHtml5Tokenizer::AFTER_DOCTYPE_PUBLIC_IDENTIFIER:
+ case nsHtml5Tokenizer::BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS:
+ case nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED:
+ case nsHtml5Tokenizer::AFTER_DOCTYPE_SYSTEM_IDENTIFIER:
+ case nsHtml5Tokenizer::BOGUS_DOCTYPE:
+ case nsHtml5Tokenizer::AFTER_DOCTYPE_SYSTEM_KEYWORD:
+ case nsHtml5Tokenizer::BEFORE_DOCTYPE_SYSTEM_IDENTIFIER:
+ case nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED:
+ case nsHtml5Tokenizer::DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED:
+ AddClass(sDoctype);
+ break;
+ default:
+ break;
+ }
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpStreamEnded);
+ FlushOps();
+}
+
+void
+nsHtml5Highlighter::SetBuffer(nsHtml5UTF16Buffer* aBuffer)
+{
+ NS_PRECONDITION(!mBuffer, "Old buffer still here!");
+ mBuffer = aBuffer;
+ mCStart = aBuffer->getStart();
+}
+
+void
+nsHtml5Highlighter::DropBuffer(int32_t aPos)
+{
+ NS_PRECONDITION(mBuffer, "No buffer to drop!");
+ mPos = aPos;
+ FlushChars();
+ mBuffer = nullptr;
+}
+
+void
+nsHtml5Highlighter::StartSpan()
+{
+ FlushChars();
+ Push(nsGkAtoms::span, nullptr, NS_NewHTMLSpanElement);
+ ++mInlinesOpen;
+}
+
+void
+nsHtml5Highlighter::StartSpan(const char16_t* aClass)
+{
+ StartSpan();
+ AddClass(aClass);
+}
+
+void
+nsHtml5Highlighter::EndSpanOrA()
+{
+ FlushChars();
+ Pop();
+ --mInlinesOpen;
+}
+
+void
+nsHtml5Highlighter::StartCharacters()
+{
+ NS_PRECONDITION(!mInCharacters, "Already in characters!");
+ FlushChars();
+ Push(nsGkAtoms::span, nullptr, NS_NewHTMLSpanElement);
+ mCurrentRun = CurrentNode();
+ mInCharacters = true;
+}
+
+void
+nsHtml5Highlighter::EndCharactersAndStartMarkupRun()
+{
+ NS_PRECONDITION(mInCharacters, "Not in characters!");
+ FlushChars();
+ Pop();
+ mInCharacters = false;
+ // Now start markup run
+ StartSpan();
+ mCurrentRun = CurrentNode();
+}
+
+void
+nsHtml5Highlighter::StartA()
+{
+ FlushChars();
+ Push(nsGkAtoms::a, nullptr, NS_NewHTMLAnchorElement);
+ AddClass(sAttributeValue);
+ ++mInlinesOpen;
+}
+
+void
+nsHtml5Highlighter::FinishTag()
+{
+ while (mInlinesOpen > 1) {
+ EndSpanOrA();
+ }
+ FlushCurrent(); // >
+ EndSpanOrA(); // DATA
+ NS_ASSERTION(!mInlinesOpen, "mInlinesOpen got out of sync!");
+ StartCharacters();
+}
+
+void
+nsHtml5Highlighter::FlushChars()
+{
+ if (mCStart < mPos) {
+ char16_t* buf = mBuffer->getBuffer();
+ int32_t i = mCStart;
+ while (i < mPos) {
+ char16_t c = buf[i];
+ switch (c) {
+ case '\r':
+ // The input this code sees has been normalized so that there are
+ // CR breaks and LF breaks but no CRLF breaks. Overwrite CR with LF
+ // to show consistent LF line breaks to layout. It is OK to mutate
+ // the input data, because there are no reparses in the View Source
+ // case, so we won't need the original data in the buffer anymore.
+ buf[i] = '\n';
+ MOZ_FALLTHROUGH;
+ case '\n': {
+ ++i;
+ if (mCStart < i) {
+ int32_t len = i - mCStart;
+ AppendCharacters(buf, mCStart, len);
+ mCStart = i;
+ }
+ ++mLineNumber;
+ Push(nsGkAtoms::span, nullptr, NS_NewHTMLSpanElement);
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->InitAddLineNumberId(CurrentNode(), mLineNumber);
+ Pop();
+ break;
+ }
+ default:
+ ++i;
+ break;
+ }
+ }
+ if (mCStart < mPos) {
+ int32_t len = mPos - mCStart;
+ AppendCharacters(buf, mCStart, len);
+ mCStart = mPos;
+ }
+ }
+}
+
+void
+nsHtml5Highlighter::FlushCurrent()
+{
+ mPos++;
+ FlushChars();
+}
+
+bool
+nsHtml5Highlighter::FlushOps()
+{
+ bool hasOps = !mOpQueue.IsEmpty();
+ if (hasOps) {
+ mOpSink->MoveOpsFrom(mOpQueue);
+ }
+ return hasOps;
+}
+
+void
+nsHtml5Highlighter::MaybeLinkifyAttributeValue(nsHtml5AttributeName* aName,
+ nsHtml5String aValue)
+{
+ if (!(nsHtml5AttributeName::ATTR_HREF == aName ||
+ nsHtml5AttributeName::ATTR_SRC == aName ||
+ nsHtml5AttributeName::ATTR_ACTION == aName ||
+ nsHtml5AttributeName::ATTR_CITE == aName ||
+ nsHtml5AttributeName::ATTR_BACKGROUND == aName ||
+ nsHtml5AttributeName::ATTR_LONGDESC == aName ||
+ nsHtml5AttributeName::ATTR_XLINK_HREF == aName ||
+ nsHtml5AttributeName::ATTR_DEFINITIONURL == aName)) {
+ return;
+ }
+ AddViewSourceHref(aValue);
+}
+
+void
+nsHtml5Highlighter::CompletedNamedCharacterReference()
+{
+ AddClass(sEntity);
+}
+
+nsIContent**
+nsHtml5Highlighter::AllocateContentHandle()
+{
+ if (mHandlesUsed == NS_HTML5_HIGHLIGHTER_HANDLE_ARRAY_LENGTH) {
+ mOldHandles.AppendElement(Move(mHandles));
+ mHandles = MakeUnique<nsIContent*[]>(NS_HTML5_HIGHLIGHTER_HANDLE_ARRAY_LENGTH);
+ mHandlesUsed = 0;
+ }
+#ifdef DEBUG
+ mHandles[mHandlesUsed] = reinterpret_cast<nsIContent*>(uintptr_t(0xC0DEDBAD));
+#endif
+ return &mHandles[mHandlesUsed++];
+}
+
+nsIContent**
+nsHtml5Highlighter::CreateElement(nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ nsIContent** aIntendedParent,
+ mozilla::dom::HTMLContentCreatorFunction aCreator)
+{
+ NS_PRECONDITION(aName, "Got null name.");
+ nsHtml5ContentCreatorFunction creator;
+ creator.html = aCreator;
+ nsIContent** content = AllocateContentHandle();
+ mOpQueue.AppendElement()->Init(kNameSpaceID_XHTML,
+ aName,
+ aAttributes,
+ content,
+ aIntendedParent,
+ true,
+ creator);
+ return content;
+}
+
+nsIContent**
+nsHtml5Highlighter::CurrentNode()
+{
+ NS_PRECONDITION(mStack.Length() >= 1, "Must have something on stack.");
+ return mStack[mStack.Length() - 1];
+}
+
+void
+nsHtml5Highlighter::Push(nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ mozilla::dom::HTMLContentCreatorFunction aCreator)
+{
+ NS_PRECONDITION(mStack.Length() >= 1, "Pushing without root.");
+ nsIContent** elt = CreateElement(aName, aAttributes,
+ CurrentNode(),
+ aCreator); // Don't inline below!
+ mOpQueue.AppendElement()->Init(eTreeOpAppend, elt, CurrentNode());
+ mStack.AppendElement(elt);
+}
+
+void
+nsHtml5Highlighter::Pop()
+{
+ NS_PRECONDITION(mStack.Length() >= 2, "Popping when stack too short.");
+ mStack.RemoveElementAt(mStack.Length() - 1);
+}
+
+void
+nsHtml5Highlighter::AppendCharacters(const char16_t* aBuffer,
+ int32_t aStart,
+ int32_t aLength)
+{
+ NS_PRECONDITION(aBuffer, "Null buffer");
+
+ char16_t* bufferCopy = new char16_t[aLength];
+ memcpy(bufferCopy, aBuffer + aStart, aLength * sizeof(char16_t));
+
+ mOpQueue.AppendElement()->Init(eTreeOpAppendText,
+ bufferCopy,
+ aLength,
+ CurrentNode());
+}
+
+
+void
+nsHtml5Highlighter::AddClass(const char16_t* aClass)
+{
+ mOpQueue.AppendElement()->InitAddClass(CurrentNode(), aClass);
+}
+
+void
+nsHtml5Highlighter::AddViewSourceHref(nsHtml5String aValue)
+{
+ char16_t* bufferCopy = new char16_t[aValue.Length() + 1];
+ aValue.CopyToBuffer(bufferCopy);
+ bufferCopy[aValue.Length()] = 0;
+
+ mOpQueue.AppendElement()->Init(eTreeOpAddViewSourceHref,
+ bufferCopy,
+ aValue.Length(),
+ CurrentNode());
+}
+
+void
+nsHtml5Highlighter::AddBase(nsHtml5String aValue)
+{
+ if(mSeenBase) {
+ return;
+ }
+ mSeenBase = true;
+ char16_t* bufferCopy = new char16_t[aValue.Length() + 1];
+ aValue.CopyToBuffer(bufferCopy);
+ bufferCopy[aValue.Length()] = 0;
+
+ mOpQueue.AppendElement()->Init(eTreeOpAddViewSourceBase,
+ bufferCopy,
+ aValue.Length());
+}
+
+void
+nsHtml5Highlighter::AddErrorToCurrentNode(const char* aMsgId)
+{
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(CurrentNode(), aMsgId);
+}
+
+void
+nsHtml5Highlighter::AddErrorToCurrentRun(const char* aMsgId)
+{
+ NS_PRECONDITION(mCurrentRun, "Adding error to run without one!");
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(mCurrentRun, aMsgId);
+}
+
+void
+nsHtml5Highlighter::AddErrorToCurrentRun(const char* aMsgId,
+ nsIAtom* aName)
+{
+ NS_PRECONDITION(mCurrentRun, "Adding error to run without one!");
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(mCurrentRun, aMsgId, aName);
+}
+
+void
+nsHtml5Highlighter::AddErrorToCurrentRun(const char* aMsgId,
+ nsIAtom* aName,
+ nsIAtom* aOther)
+{
+ NS_PRECONDITION(mCurrentRun, "Adding error to run without one!");
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(mCurrentRun, aMsgId, aName, aOther);
+}
+
+void
+nsHtml5Highlighter::AddErrorToCurrentAmpersand(const char* aMsgId)
+{
+ NS_PRECONDITION(mAmpersand, "Adding error to ampersand without one!");
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(mAmpersand, aMsgId);
+}
+
+void
+nsHtml5Highlighter::AddErrorToCurrentSlash(const char* aMsgId)
+{
+ NS_PRECONDITION(mSlash, "Adding error to slash without one!");
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(mSlash, aMsgId);
+}
diff --git a/components/htmlfive/nsHtml5Highlighter.h b/components/htmlfive/nsHtml5Highlighter.h
new file mode 100644
index 000000000..c37b703b8
--- /dev/null
+++ b/components/htmlfive/nsHtml5Highlighter.h
@@ -0,0 +1,418 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsHtml5Highlighter_h
+#define nsHtml5Highlighter_h
+
+#include "nsCOMPtr.h"
+#include "nsHtml5TreeOperation.h"
+#include "nsHtml5UTF16Buffer.h"
+#include "nsHtml5TreeOperation.h"
+#include "nsAHtml5TreeOpSink.h"
+
+#define NS_HTML5_HIGHLIGHTER_HANDLE_ARRAY_LENGTH 512
+
+/**
+ * A state machine for generating HTML for display in View Source based on
+ * the transitions the tokenizer makes on the source being viewed.
+ */
+class nsHtml5Highlighter
+{
+ public:
+ /**
+ * The constructor.
+ *
+ * @param aOpSink the sink for the tree ops generated by this highlighter
+ */
+ explicit nsHtml5Highlighter(nsAHtml5TreeOpSink* aOpSink);
+
+ /**
+ * The destructor.
+ */
+ ~nsHtml5Highlighter();
+
+ /**
+ * Starts the generated document.
+ */
+ void Start(const nsAutoString& aTitle);
+
+ /**
+ * Report a tokenizer state transition.
+ *
+ * @param aState the state being transitioned to
+ * @param aReconsume whether this is a reconsuming transition
+ * @param aPos the tokenizer's current position into the buffer
+ */
+ int32_t Transition(int32_t aState, bool aReconsume, int32_t aPos);
+
+ /**
+ * Report end of file.
+ */
+ void End();
+
+ /**
+ * Set the current buffer being tokenized
+ */
+ void SetBuffer(nsHtml5UTF16Buffer* aBuffer);
+
+ /**
+ * Let go of the buffer being tokenized but first, flush text from it.
+ *
+ * @param aPos the first UTF-16 code unit not to flush
+ */
+ void DropBuffer(int32_t aPos);
+
+ /**
+ * Flush the tree ops into the sink.
+ *
+ * @return true if there were ops to flush
+ */
+ bool FlushOps();
+
+ /**
+ * Linkify the current attribute value if the attribute name is one of
+ * known URL attributes. (When executing tree ops, javascript: URLs will
+ * not be linkified, though.)
+ *
+ * @param aName the name of the attribute
+ * @param aValue the value of the attribute
+ */
+ void MaybeLinkifyAttributeValue(nsHtml5AttributeName* aName,
+ nsHtml5String aValue);
+
+ /**
+ * Inform the highlighter that the tokenizer successfully completed a
+ * named character reference.
+ */
+ void CompletedNamedCharacterReference();
+
+ /**
+ * Adds an error annotation to the node that's currently on top of
+ * mStack.
+ *
+ * @param aMsgId the id of the message in the property file
+ */
+ void AddErrorToCurrentNode(const char* aMsgId);
+
+ /**
+ * Adds an error annotation to the node that corresponds to the most
+ * recently opened markup declaration/tag span, character reference or
+ * run of text.
+ *
+ * @param aMsgId the id of the message in the property file
+ */
+ void AddErrorToCurrentRun(const char* aMsgId);
+
+ /**
+ * Adds an error annotation to the node that corresponds to the most
+ * recently opened markup declaration/tag span, character reference or
+ * run of text with one atom to use when formatting the message.
+ *
+ * @param aMsgId the id of the message in the property file
+ * @param aName the atom
+ */
+ void AddErrorToCurrentRun(const char* aMsgId, nsIAtom* aName);
+
+ /**
+ * Adds an error annotation to the node that corresponds to the most
+ * recently opened markup declaration/tag span, character reference or
+ * run of text with two atoms to use when formatting the message.
+ *
+ * @param aMsgId the id of the message in the property file
+ * @param aName the first atom
+ * @param aOther the second atom
+ */
+ void AddErrorToCurrentRun(const char* aMsgId,
+ nsIAtom* aName,
+ nsIAtom* aOther);
+
+ /**
+ * Adds an error annotation to the node that corresponds to the most
+ * recent potentially character reference-starting ampersand.
+ *
+ * @param aMsgId the id of the message in the property file
+ */
+ void AddErrorToCurrentAmpersand(const char* aMsgId);
+
+ /**
+ * Adds an error annotation to the node that corresponds to the most
+ * recent potentially self-closing slash.
+ *
+ * @param aMsgId the id of the message in the property file
+ */
+ void AddErrorToCurrentSlash(const char* aMsgId);
+
+ /**
+ * Enqueues a tree op for adding base to the urls with the view-source:
+ *
+ * @param aValue the base URL to add
+ */
+ void AddBase(nsHtml5String aValue);
+
+ private:
+
+ /**
+ * Starts a span with no class.
+ */
+ void StartSpan();
+
+ /**
+ * Starts a <span> and sets the class attribute on it.
+ *
+ * @param aClass the class to set (MUST be a static string that does not
+ * need to be released!)
+ */
+ void StartSpan(const char16_t* aClass);
+
+ /**
+ * End the current <span> or <a> in the highlighter output.
+ */
+ void EndSpanOrA();
+
+ /**
+ * Starts a wrapper around a run of characters.
+ */
+ void StartCharacters();
+
+ /**
+ * Ends a wrapper around a run of characters.
+ */
+ void EndCharactersAndStartMarkupRun();
+
+ /**
+ * Starts an <a>.
+ */
+ void StartA();
+
+ /**
+ * Flushes characters up to but not including the current one.
+ */
+ void FlushChars();
+
+ /**
+ * Flushes characters up to and including the current one.
+ */
+ void FlushCurrent();
+
+ /**
+ * Finishes highlighting a tag in the input data by closing the open
+ * <span> and <a> elements in the highlighter output and then starts
+ * another <span> for potentially highlighting characters potentially
+ * appearing next.
+ */
+ void FinishTag();
+
+ /**
+ * Adds a class attribute to the current node.
+ *
+ * @param aClass the class to set (MUST be a static string that does not
+ * need to be released!)
+ */
+ void AddClass(const char16_t* aClass);
+
+ /**
+ * Allocates a handle for an element.
+ *
+ * See the documentation for nsHtml5TreeBuilder::AllocateContentHandle()
+ * in nsHtml5TreeBuilderHSupplement.h.
+ *
+ * @return the handle
+ */
+ nsIContent** AllocateContentHandle();
+
+ /**
+ * Enqueues an element creation tree operation.
+ *
+ * @param aName the name of the element
+ * @param aAttributes the attribute holder (ownership will be taken) or
+ * nullptr for no attributes
+ * @param aIntendedParent the intended parent node for the created element
+ * @param aCreator the content creator function
+ * @return the handle for the element that will be created
+ */
+ nsIContent** CreateElement(nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ nsIContent** aIntendedParent,
+ mozilla::dom::HTMLContentCreatorFunction aCreator);
+
+ /**
+ * Gets the handle for the current node. May be called only after the
+ * root element has been set.
+ *
+ * @return the handle for the current node
+ */
+ nsIContent** CurrentNode();
+
+ /**
+ * Create an element and push it (its handle) on the stack.
+ *
+ * @param aName the name of the element
+ * @param aAttributes the attribute holder (ownership will be taken) or
+ * nullptr for no attributes
+ * @param aCreator the content creator function
+ */
+ void Push(nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ mozilla::dom::HTMLContentCreatorFunction aCreator);
+
+ /**
+ * Pops the current node off the stack.
+ */
+ void Pop();
+
+ /**
+ * Appends text content to the current node.
+ *
+ * @param aBuffer the buffer to copy from
+ * @param aStart the index of the first code unit to copy
+ * @param aLength the number of code units to copy
+ */
+ void AppendCharacters(const char16_t* aBuffer,
+ int32_t aStart,
+ int32_t aLength);
+
+ /**
+ * Enqueues a tree op for adding an href attribute with the view-source:
+ * URL scheme to the current node.
+ *
+ * @param aValue the (potentially relative) URL to link to
+ */
+ void AddViewSourceHref(nsHtml5String aValue);
+
+ /**
+ * The state we are transitioning away from.
+ */
+ int32_t mState;
+
+ /**
+ * The index of the first UTF-16 code unit in mBuffer that hasn't been
+ * flushed yet.
+ */
+ int32_t mCStart;
+
+ /**
+ * The position of the code unit in mBuffer that caused the current
+ * transition.
+ */
+ int32_t mPos;
+
+ /**
+ * The current line number.
+ */
+ int32_t mLineNumber;
+
+ /**
+ * The number of inline elements open inside the <pre> excluding the
+ * span potentially wrapping a run of characters.
+ */
+ int32_t mInlinesOpen;
+
+ /**
+ * Whether there's a span wrapping a run of characters (excluding CDATA
+ * section) open.
+ */
+ bool mInCharacters;
+
+ /**
+ * The current buffer being tokenized.
+ */
+ nsHtml5UTF16Buffer* mBuffer;
+
+ /**
+ * The outgoing tree op queue.
+ */
+ nsTArray<nsHtml5TreeOperation> mOpQueue;
+
+ /**
+ * The tree op stage for the tree op executor.
+ */
+ nsAHtml5TreeOpSink* mOpSink;
+
+ /**
+ * The most recently opened markup declaration/tag or run of characters.
+ */
+ nsIContent** mCurrentRun;
+
+ /**
+ * The most recent ampersand in a place where character references were
+ * allowed.
+ */
+ nsIContent** mAmpersand;
+
+ /**
+ * The most recent slash that might become a self-closing slash.
+ */
+ nsIContent** mSlash;
+
+ /**
+ * Memory for element handles.
+ */
+ mozilla::UniquePtr<nsIContent*[]> mHandles;
+
+ /**
+ * Number of handles used in mHandles
+ */
+ int32_t mHandlesUsed;
+
+ /**
+ * A holder for old contents of mHandles
+ */
+ nsTArray<mozilla::UniquePtr<nsIContent*[]>> mOldHandles;
+
+ /**
+ * The element stack.
+ */
+ nsTArray<nsIContent**> mStack;
+
+ /**
+ * The string "comment"
+ */
+ static char16_t sComment[];
+
+ /**
+ * The string "cdata"
+ */
+ static char16_t sCdata[];
+
+ /**
+ * The string "start-tag"
+ */
+ static char16_t sStartTag[];
+
+ /**
+ * The string "attribute-name"
+ */
+ static char16_t sAttributeName[];
+
+ /**
+ * The string "attribute-value"
+ */
+ static char16_t sAttributeValue[];
+
+ /**
+ * The string "end-tag"
+ */
+ static char16_t sEndTag[];
+
+ /**
+ * The string "doctype"
+ */
+ static char16_t sDoctype[];
+
+ /**
+ * The string "entity"
+ */
+ static char16_t sEntity[];
+
+ /**
+ * The string "pi"
+ */
+ static char16_t sPi[];
+
+ /**
+ * Whether base is already visited once.
+ */
+ bool mSeenBase;
+};
+
+#endif // nsHtml5Highlighter_h
diff --git a/components/htmlfive/nsHtml5HtmlAttributes.cpp b/components/htmlfive/nsHtml5HtmlAttributes.cpp
new file mode 100644
index 000000000..d8f59d22c
--- /dev/null
+++ b/components/htmlfive/nsHtml5HtmlAttributes.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2011 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#define nsHtml5HtmlAttributes_cpp__
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5MetaScanner.h"
+#include "nsHtml5AttributeName.h"
+#include "nsHtml5ElementName.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5UTF16Buffer.h"
+#include "nsHtml5StateSnapshot.h"
+#include "nsHtml5Portability.h"
+
+#include "nsHtml5HtmlAttributes.h"
+
+nsHtml5HtmlAttributes* nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES = nullptr;
+
+nsHtml5HtmlAttributes::nsHtml5HtmlAttributes(int32_t aMode)
+ : mMode(aMode)
+{
+ MOZ_COUNT_CTOR(nsHtml5HtmlAttributes);
+}
+
+
+nsHtml5HtmlAttributes::~nsHtml5HtmlAttributes()
+{
+ MOZ_COUNT_DTOR(nsHtml5HtmlAttributes);
+ clear(0);
+}
+
+int32_t
+nsHtml5HtmlAttributes::getIndex(nsHtml5AttributeName* aName)
+{
+ for (size_t i = 0; i < mStorage.Length(); i++) {
+ if (mStorage[i].GetLocal(nsHtml5AttributeName::HTML) ==
+ aName->getLocal(nsHtml5AttributeName::HTML)) {
+ // It's release asserted elsewhere that i can't be too large.
+ return i;
+ }
+ }
+ return -1;
+}
+
+nsHtml5String
+nsHtml5HtmlAttributes::getValue(nsHtml5AttributeName* aName)
+{
+ int32_t index = getIndex(aName);
+ if (index == -1) {
+ return nullptr;
+ } else {
+ return getValueNoBoundsCheck(index);
+ }
+}
+
+int32_t
+nsHtml5HtmlAttributes::getLength()
+{
+ return mStorage.Length();
+}
+
+nsIAtom*
+nsHtml5HtmlAttributes::getLocalNameNoBoundsCheck(int32_t aIndex)
+{
+ MOZ_ASSERT(aIndex < int32_t(mStorage.Length()) && aIndex >= 0,
+ "Index out of bounds");
+ return mStorage[aIndex].GetLocal(mMode);
+}
+
+int32_t
+nsHtml5HtmlAttributes::getURINoBoundsCheck(int32_t aIndex)
+{
+ MOZ_ASSERT(aIndex < int32_t(mStorage.Length()) && aIndex >= 0,
+ "Index out of bounds");
+ return mStorage[aIndex].GetUri(mMode);
+}
+
+nsIAtom*
+nsHtml5HtmlAttributes::getPrefixNoBoundsCheck(int32_t aIndex)
+{
+ MOZ_ASSERT(aIndex < int32_t(mStorage.Length()) && aIndex >= 0,
+ "Index out of bounds");
+ return mStorage[aIndex].GetPrefix(mMode);
+}
+
+nsHtml5String
+nsHtml5HtmlAttributes::getValueNoBoundsCheck(int32_t aIndex)
+{
+ MOZ_ASSERT(aIndex < int32_t(mStorage.Length()) && aIndex >= 0,
+ "Index out of bounds");
+ return mStorage[aIndex].GetValue();
+}
+
+int32_t
+nsHtml5HtmlAttributes::getLineNoBoundsCheck(int32_t aIndex)
+{
+ MOZ_ASSERT(aIndex < int32_t(mStorage.Length()) && aIndex >= 0,
+ "Index out of bounds");
+ return mStorage[aIndex].GetLine();
+}
+
+void
+nsHtml5HtmlAttributes::addAttribute(nsHtml5AttributeName* aName,
+ nsHtml5String aValue,
+ int32_t aLine)
+{
+ mStorage.AppendElement(nsHtml5AttributeEntry(aName, aValue, aLine));
+ MOZ_RELEASE_ASSERT(mStorage.Length() <= INT32_MAX,
+ "Can't handle this many attributes.");
+}
+
+// Isindex-only, so doesn't need to deal with SVG and MathML
+void
+nsHtml5HtmlAttributes::AddAttributeWithLocal(nsIAtom* aName,
+ nsHtml5String aValue,
+ int32_t aLine)
+{
+ mStorage.AppendElement(nsHtml5AttributeEntry(aName, aValue, aLine));
+ MOZ_RELEASE_ASSERT(mStorage.Length() <= INT32_MAX,
+ "Can't handle this many attributes.");
+}
+
+void
+nsHtml5HtmlAttributes::clear(int32_t aMode)
+{
+ for (nsHtml5AttributeEntry& entry : mStorage) {
+ entry.ReleaseValue();
+ }
+ mStorage.TruncateLength(0);
+ mMode = aMode;
+}
+
+void
+nsHtml5HtmlAttributes::releaseValue(int32_t aIndex)
+{
+ mStorage[aIndex].ReleaseValue();
+}
+
+void
+nsHtml5HtmlAttributes::clearWithoutReleasingContents()
+{
+ mStorage.TruncateLength(0);
+}
+
+bool
+nsHtml5HtmlAttributes::contains(nsHtml5AttributeName* aName)
+{
+ for (size_t i = 0; i < mStorage.Length(); i++) {
+ if (mStorage[i].GetLocal(nsHtml5AttributeName::HTML) ==
+ aName->getLocal(nsHtml5AttributeName::HTML)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+nsHtml5HtmlAttributes::adjustForMath()
+{
+ mMode = nsHtml5AttributeName::MATHML;
+}
+
+void
+nsHtml5HtmlAttributes::adjustForSvg()
+{
+ mMode = nsHtml5AttributeName::SVG;
+}
+
+nsHtml5HtmlAttributes*
+nsHtml5HtmlAttributes::cloneAttributes(nsHtml5AtomTable* aInterner)
+{
+ MOZ_ASSERT(mStorage.IsEmpty() || !mMode);
+ nsHtml5HtmlAttributes* clone =
+ new nsHtml5HtmlAttributes(nsHtml5AttributeName::HTML);
+ for (nsHtml5AttributeEntry& entry : mStorage) {
+ clone->AddEntry(entry.Clone(aInterner));
+ }
+ return clone;
+}
+
+bool
+nsHtml5HtmlAttributes::equalsAnother(nsHtml5HtmlAttributes* aOther)
+{
+ MOZ_ASSERT(!mMode, "Trying to compare attributes in foreign content.");
+ if (mStorage.Length() != aOther->mStorage.Length()) {
+ return false;
+ }
+ for (nsHtml5AttributeEntry& entry : mStorage) {
+ bool found = false;
+ nsIAtom* ownLocal = entry.GetLocal(nsHtml5AttributeName::HTML);
+ for (nsHtml5AttributeEntry& otherEntry : aOther->mStorage) {
+ if (ownLocal == otherEntry.GetLocal(nsHtml5AttributeName::HTML)) {
+ found = true;
+ if (!entry.GetValue().Equals(otherEntry.GetValue())) {
+ return false;
+ }
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void
+nsHtml5HtmlAttributes::AddEntry(nsHtml5AttributeEntry&& aEntry)
+{
+ mStorage.AppendElement(aEntry);
+}
+
+void
+nsHtml5HtmlAttributes::initializeStatics()
+{
+ EMPTY_ATTRIBUTES = new nsHtml5HtmlAttributes(nsHtml5AttributeName::HTML);
+}
+
+void
+nsHtml5HtmlAttributes::releaseStatics()
+{
+ delete EMPTY_ATTRIBUTES;
+}
+
+
diff --git a/components/htmlfive/nsHtml5HtmlAttributes.h b/components/htmlfive/nsHtml5HtmlAttributes.h
new file mode 100644
index 000000000..8dde05771
--- /dev/null
+++ b/components/htmlfive/nsHtml5HtmlAttributes.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2011 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef nsHtml5HtmlAttributes_h
+#define nsHtml5HtmlAttributes_h
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsTArray.h"
+#include "nsHtml5AttributeEntry.h"
+
+class nsHtml5StreamParser;
+
+class nsHtml5Tokenizer;
+class nsHtml5TreeBuilder;
+class nsHtml5MetaScanner;
+class nsHtml5AttributeName;
+class nsHtml5ElementName;
+class nsHtml5UTF16Buffer;
+class nsHtml5StateSnapshot;
+class nsHtml5Portability;
+
+
+class nsHtml5HtmlAttributes
+{
+ public:
+ static nsHtml5HtmlAttributes* EMPTY_ATTRIBUTES;
+ private:
+ AutoTArray<nsHtml5AttributeEntry, 5> mStorage;
+ int32_t mMode;
+ void AddEntry(nsHtml5AttributeEntry&& aEntry);
+
+ public:
+ explicit nsHtml5HtmlAttributes(int32_t aMode);
+ ~nsHtml5HtmlAttributes();
+
+ // Remove getIndex when removing isindex support
+ int32_t getIndex(nsHtml5AttributeName* aName);
+
+ nsHtml5String getValue(nsHtml5AttributeName* aName);
+ int32_t getLength();
+ nsIAtom* getLocalNameNoBoundsCheck(int32_t aIndex);
+ int32_t getURINoBoundsCheck(int32_t aIndex);
+ nsIAtom* getPrefixNoBoundsCheck(int32_t aIndex);
+ nsHtml5String getValueNoBoundsCheck(int32_t aIndex);
+ nsHtml5AttributeName* getAttributeNameNoBoundsCheck(int32_t aIndex);
+ int32_t getLineNoBoundsCheck(int32_t aIndex);
+ void addAttribute(nsHtml5AttributeName* aName,
+ nsHtml5String aValue,
+ int32_t aLine);
+ void AddAttributeWithLocal(nsIAtom* aName,
+ nsHtml5String aValue,
+ int32_t aLine);
+ void clear(int32_t aMode);
+ void releaseValue(int32_t aIndex);
+ void clearWithoutReleasingContents();
+ bool contains(nsHtml5AttributeName* aName);
+ void adjustForMath();
+ void adjustForSvg();
+ nsHtml5HtmlAttributes* cloneAttributes(nsHtml5AtomTable* aInterner);
+ bool equalsAnother(nsHtml5HtmlAttributes* aOther);
+ static void initializeStatics();
+ static void releaseStatics();
+};
+
+
+
+#endif
+
diff --git a/components/htmlfive/nsHtml5Macros.h b/components/htmlfive/nsHtml5Macros.h
new file mode 100644
index 000000000..5abc848b9
--- /dev/null
+++ b/components/htmlfive/nsHtml5Macros.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef nsHtml5Macros_h
+#define nsHtml5Macros_h
+
+#define NS_HTML5_CONTINUE(target) \
+ goto target
+
+#define NS_HTML5_BREAK(target) \
+ goto target ## _end
+
+#endif /* nsHtml5Macros_h */
diff --git a/components/htmlfive/nsHtml5MetaScanner.cpp b/components/htmlfive/nsHtml5MetaScanner.cpp
new file mode 100644
index 000000000..04c43717d
--- /dev/null
+++ b/components/htmlfive/nsHtml5MetaScanner.cpp
@@ -0,0 +1,815 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2015 Mozilla Foundation
+ * Copyright (c) 2018-2021 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit MetaScanner.java instead and regenerate.
+ */
+
+#define nsHtml5MetaScanner_cpp__
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+#include "nsHtml5AttributeName.h"
+#include "nsHtml5ElementName.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5UTF16Buffer.h"
+#include "nsHtml5StateSnapshot.h"
+#include "nsHtml5Portability.h"
+
+#include "nsHtml5MetaScanner.h"
+
+static char16_t const CHARSET_DATA[] = { 'h', 'a', 'r', 's', 'e', 't' };
+staticJArray<char16_t,int32_t> nsHtml5MetaScanner::CHARSET = { CHARSET_DATA, MOZ_ARRAY_LENGTH(CHARSET_DATA) };
+static char16_t const CONTENT_DATA[] = { 'o', 'n', 't', 'e', 'n', 't' };
+staticJArray<char16_t,int32_t> nsHtml5MetaScanner::CONTENT = { CONTENT_DATA, MOZ_ARRAY_LENGTH(CONTENT_DATA) };
+static char16_t const HTTP_EQUIV_DATA[] = { 't', 't', 'p', '-', 'e', 'q', 'u', 'i', 'v' };
+staticJArray<char16_t,int32_t> nsHtml5MetaScanner::HTTP_EQUIV = { HTTP_EQUIV_DATA, MOZ_ARRAY_LENGTH(HTTP_EQUIV_DATA) };
+static char16_t const CONTENT_TYPE_DATA[] = { 'c', 'o', 'n', 't', 'e', 'n', 't', '-', 't', 'y', 'p', 'e' };
+staticJArray<char16_t,int32_t> nsHtml5MetaScanner::CONTENT_TYPE = { CONTENT_TYPE_DATA, MOZ_ARRAY_LENGTH(CONTENT_TYPE_DATA) };
+
+nsHtml5MetaScanner::nsHtml5MetaScanner(nsHtml5TreeBuilder* tb)
+ : readable(nullptr),
+ metaState(NO),
+ contentIndex(INT32_MAX),
+ charsetIndex(INT32_MAX),
+ httpEquivIndex(INT32_MAX),
+ contentTypeIndex(INT32_MAX),
+ stateSave(DATA),
+ strBufLen(0),
+ strBuf(jArray<char16_t,int32_t>::newJArray(36)),
+ content(nullptr),
+ charset(nullptr),
+ httpEquivState(HTTP_EQUIV_NOT_SEEN),
+ treeBuilder(tb)
+{
+ MOZ_COUNT_CTOR(nsHtml5MetaScanner);
+}
+
+
+nsHtml5MetaScanner::~nsHtml5MetaScanner()
+{
+ MOZ_COUNT_DTOR(nsHtml5MetaScanner);
+ content.Release();
+ charset.Release();
+}
+
+void
+nsHtml5MetaScanner::stateLoop(int32_t state)
+{
+ int32_t c = -1;
+ bool reconsume = false;
+ stateloop: for (; ; ) {
+ switch(state) {
+ case DATA: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '<': {
+ state = nsHtml5MetaScanner::TAG_OPEN;
+ NS_HTML5_BREAK(dataloop);
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ dataloop_end: ;
+ }
+ case TAG_OPEN: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case 'm':
+ case 'M': {
+ metaState = M;
+ state = nsHtml5MetaScanner::TAG_NAME;
+ NS_HTML5_BREAK(tagopenloop);
+ }
+ case '!': {
+ state = nsHtml5MetaScanner::MARKUP_DECLARATION_OPEN;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\?':
+ case '/': {
+ state = nsHtml5MetaScanner::SCAN_UNTIL_GT;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
+ metaState = NO;
+ state = nsHtml5MetaScanner::TAG_NAME;
+ NS_HTML5_BREAK(tagopenloop);
+ }
+ state = nsHtml5MetaScanner::DATA;
+ reconsume = true;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ tagopenloop_end: ;
+ }
+ case TAG_NAME: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\f': {
+ state = nsHtml5MetaScanner::BEFORE_ATTRIBUTE_NAME;
+ NS_HTML5_BREAK(tagnameloop);
+ }
+ case '/': {
+ state = nsHtml5MetaScanner::SELF_CLOSING_START_TAG;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case 'e':
+ case 'E': {
+ if (metaState == M) {
+ metaState = E;
+ } else {
+ metaState = NO;
+ }
+ continue;
+ }
+ case 't':
+ case 'T': {
+ if (metaState == E) {
+ metaState = T;
+ } else {
+ metaState = NO;
+ }
+ continue;
+ }
+ case 'a':
+ case 'A': {
+ if (metaState == T) {
+ metaState = A;
+ } else {
+ metaState = NO;
+ }
+ continue;
+ }
+ default: {
+ metaState = NO;
+ continue;
+ }
+ }
+ }
+ tagnameloop_end: ;
+ }
+ case BEFORE_ATTRIBUTE_NAME: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\f': {
+ continue;
+ }
+ case '/': {
+ state = nsHtml5MetaScanner::SELF_CLOSING_START_TAG;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (handleTag()) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ state = DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case 'c':
+ case 'C': {
+ contentIndex = 0;
+ charsetIndex = 0;
+ httpEquivIndex = INT32_MAX;
+ contentTypeIndex = INT32_MAX;
+ state = nsHtml5MetaScanner::ATTRIBUTE_NAME;
+ NS_HTML5_BREAK(beforeattributenameloop);
+ }
+ case 'h':
+ case 'H': {
+ contentIndex = INT32_MAX;
+ charsetIndex = INT32_MAX;
+ httpEquivIndex = 0;
+ contentTypeIndex = INT32_MAX;
+ state = nsHtml5MetaScanner::ATTRIBUTE_NAME;
+ NS_HTML5_BREAK(beforeattributenameloop);
+ }
+ default: {
+ contentIndex = INT32_MAX;
+ charsetIndex = INT32_MAX;
+ httpEquivIndex = INT32_MAX;
+ contentTypeIndex = INT32_MAX;
+ state = nsHtml5MetaScanner::ATTRIBUTE_NAME;
+ NS_HTML5_BREAK(beforeattributenameloop);
+ }
+ }
+ }
+ beforeattributenameloop_end: ;
+ }
+ case ATTRIBUTE_NAME: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\f': {
+ state = nsHtml5MetaScanner::AFTER_ATTRIBUTE_NAME;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '/': {
+ state = nsHtml5MetaScanner::SELF_CLOSING_START_TAG;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '=': {
+ strBufLen = 0;
+ contentTypeIndex = 0;
+ state = nsHtml5MetaScanner::BEFORE_ATTRIBUTE_VALUE;
+ NS_HTML5_BREAK(attributenameloop);
+ }
+ case '>': {
+ if (handleTag()) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ if (metaState == A) {
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ if (contentIndex < CONTENT.length && c == CONTENT[contentIndex]) {
+ ++contentIndex;
+ } else {
+ contentIndex = INT32_MAX;
+ }
+ if (charsetIndex < CHARSET.length && c == CHARSET[charsetIndex]) {
+ ++charsetIndex;
+ } else {
+ charsetIndex = INT32_MAX;
+ }
+ if (httpEquivIndex < HTTP_EQUIV.length && c == HTTP_EQUIV[httpEquivIndex]) {
+ ++httpEquivIndex;
+ } else {
+ httpEquivIndex = INT32_MAX;
+ }
+ }
+ continue;
+ }
+ }
+ }
+ attributenameloop_end: ;
+ }
+ case BEFORE_ATTRIBUTE_VALUE: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\f': {
+ continue;
+ }
+ case '\"': {
+ state = nsHtml5MetaScanner::ATTRIBUTE_VALUE_DOUBLE_QUOTED;
+ NS_HTML5_BREAK(beforeattributevalueloop);
+ }
+ case '\'': {
+ state = nsHtml5MetaScanner::ATTRIBUTE_VALUE_SINGLE_QUOTED;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (handleTag()) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ handleCharInAttributeValue(c);
+ state = nsHtml5MetaScanner::ATTRIBUTE_VALUE_UNQUOTED;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ beforeattributevalueloop_end: ;
+ }
+ case ATTRIBUTE_VALUE_DOUBLE_QUOTED: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\"': {
+ handleAttributeValue();
+ state = nsHtml5MetaScanner::AFTER_ATTRIBUTE_VALUE_QUOTED;
+ NS_HTML5_BREAK(attributevaluedoublequotedloop);
+ }
+ default: {
+ handleCharInAttributeValue(c);
+ continue;
+ }
+ }
+ }
+ attributevaluedoublequotedloop_end: ;
+ }
+ case AFTER_ATTRIBUTE_VALUE_QUOTED: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\f': {
+ state = nsHtml5MetaScanner::BEFORE_ATTRIBUTE_NAME;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '/': {
+ state = nsHtml5MetaScanner::SELF_CLOSING_START_TAG;
+ NS_HTML5_BREAK(afterattributevaluequotedloop);
+ }
+ case '>': {
+ if (handleTag()) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ state = nsHtml5MetaScanner::BEFORE_ATTRIBUTE_NAME;
+ reconsume = true;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ afterattributevaluequotedloop_end: ;
+ }
+ case SELF_CLOSING_START_TAG: {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '>': {
+ if (handleTag()) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ state = nsHtml5MetaScanner::BEFORE_ATTRIBUTE_NAME;
+ reconsume = true;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ case ATTRIBUTE_VALUE_UNQUOTED: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\f': {
+ handleAttributeValue();
+ state = nsHtml5MetaScanner::BEFORE_ATTRIBUTE_NAME;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ handleAttributeValue();
+ if (handleTag()) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ handleCharInAttributeValue(c);
+ continue;
+ }
+ }
+ }
+ }
+ case AFTER_ATTRIBUTE_NAME: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\f': {
+ continue;
+ }
+ case '/': {
+ handleAttributeValue();
+ state = nsHtml5MetaScanner::SELF_CLOSING_START_TAG;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '=': {
+ strBufLen = 0;
+ contentTypeIndex = 0;
+ state = nsHtml5MetaScanner::BEFORE_ATTRIBUTE_VALUE;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ handleAttributeValue();
+ if (handleTag()) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case 'c':
+ case 'C': {
+ contentIndex = 0;
+ charsetIndex = 0;
+ state = nsHtml5MetaScanner::ATTRIBUTE_NAME;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ contentIndex = INT32_MAX;
+ charsetIndex = INT32_MAX;
+ state = nsHtml5MetaScanner::ATTRIBUTE_NAME;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ }
+ case MARKUP_DECLARATION_OPEN: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '-': {
+ state = nsHtml5MetaScanner::MARKUP_DECLARATION_HYPHEN;
+ NS_HTML5_BREAK(markupdeclarationopenloop);
+ }
+ default: {
+ state = nsHtml5MetaScanner::SCAN_UNTIL_GT;
+ reconsume = true;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ markupdeclarationopenloop_end: ;
+ }
+ case MARKUP_DECLARATION_HYPHEN: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '-': {
+ state = nsHtml5MetaScanner::COMMENT_START;
+ NS_HTML5_BREAK(markupdeclarationhyphenloop);
+ }
+ default: {
+ state = nsHtml5MetaScanner::SCAN_UNTIL_GT;
+ reconsume = true;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ markupdeclarationhyphenloop_end: ;
+ }
+ case COMMENT_START: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '-': {
+ state = nsHtml5MetaScanner::COMMENT_START_DASH;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ state = nsHtml5MetaScanner::COMMENT;
+ NS_HTML5_BREAK(commentstartloop);
+ }
+ }
+ }
+ commentstartloop_end: ;
+ }
+ case COMMENT: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '-': {
+ state = nsHtml5MetaScanner::COMMENT_END_DASH;
+ NS_HTML5_BREAK(commentloop);
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ commentloop_end: ;
+ }
+ case COMMENT_END_DASH: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '-': {
+ state = nsHtml5MetaScanner::COMMENT_END;
+ NS_HTML5_BREAK(commentenddashloop);
+ }
+ default: {
+ state = nsHtml5MetaScanner::COMMENT;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ commentenddashloop_end: ;
+ }
+ case COMMENT_END: {
+ for (; ; ) {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '>': {
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '-': {
+ continue;
+ }
+ default: {
+ state = nsHtml5MetaScanner::COMMENT;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ }
+ case COMMENT_START_DASH: {
+ c = read();
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '-': {
+ state = nsHtml5MetaScanner::COMMENT_END;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ state = nsHtml5MetaScanner::COMMENT;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ case ATTRIBUTE_VALUE_SINGLE_QUOTED: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\'': {
+ handleAttributeValue();
+ state = nsHtml5MetaScanner::AFTER_ATTRIBUTE_VALUE_QUOTED;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ handleCharInAttributeValue(c);
+ continue;
+ }
+ }
+ }
+ }
+ case SCAN_UNTIL_GT: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ c = read();
+ }
+ switch(c) {
+ case -1: {
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '>': {
+ state = nsHtml5MetaScanner::DATA;
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ }
+ }
+ }
+ stateloop_end: ;
+ stateSave = state;
+}
+
+void
+nsHtml5MetaScanner::handleCharInAttributeValue(int32_t c)
+{
+ if (metaState == A) {
+ if (contentIndex == CONTENT.length || charsetIndex == CHARSET.length) {
+ addToBuffer(c);
+ } else if (httpEquivIndex == HTTP_EQUIV.length) {
+ if (contentTypeIndex < CONTENT_TYPE.length && toAsciiLowerCase(c) == CONTENT_TYPE[contentTypeIndex]) {
+ ++contentTypeIndex;
+ } else {
+ contentTypeIndex = INT32_MAX;
+ }
+ }
+ }
+}
+
+void
+nsHtml5MetaScanner::addToBuffer(int32_t c)
+{
+ if (strBufLen == strBuf.length) {
+ jArray<char16_t,int32_t> newBuf = jArray<char16_t,int32_t>::newJArray(nsHtml5Portability::checkedAdd(strBuf.length, (strBuf.length << 1)));
+ nsHtml5ArrayCopy::arraycopy(strBuf, newBuf, strBuf.length);
+ strBuf = newBuf;
+ }
+ strBuf[strBufLen++] = (char16_t) c;
+}
+
+void
+nsHtml5MetaScanner::handleAttributeValue()
+{
+ if (metaState != A) {
+ return;
+ }
+ if (contentIndex == CONTENT.length && !content) {
+ content = nsHtml5Portability::newStringFromBuffer(strBuf, 0, strBufLen, treeBuilder, false);
+ return;
+ }
+ if (charsetIndex == CHARSET.length && !charset) {
+ charset = nsHtml5Portability::newStringFromBuffer(strBuf, 0, strBufLen, treeBuilder, false);
+ return;
+ }
+ if (httpEquivIndex == HTTP_EQUIV.length && httpEquivState == HTTP_EQUIV_NOT_SEEN) {
+ httpEquivState = (contentTypeIndex == CONTENT_TYPE.length) ? HTTP_EQUIV_CONTENT_TYPE : HTTP_EQUIV_OTHER;
+ return;
+ }
+}
+
+bool
+nsHtml5MetaScanner::handleTag()
+{
+ bool stop = handleTagInner();
+ content.Release();
+ content = nullptr;
+ charset.Release();
+ charset = nullptr;
+ httpEquivState = HTTP_EQUIV_NOT_SEEN;
+ return stop;
+}
+
+bool
+nsHtml5MetaScanner::handleTagInner()
+{
+ if (!!charset && tryCharset(charset)) {
+ return true;
+ }
+ if (!!content && httpEquivState == HTTP_EQUIV_CONTENT_TYPE) {
+ nsHtml5String extract = nsHtml5TreeBuilder::extractCharsetFromContent(content, treeBuilder);
+ if (!extract) {
+ return false;
+ }
+ bool success = tryCharset(extract);
+ extract.Release();
+ return success;
+ }
+ return false;
+}
+
+void
+nsHtml5MetaScanner::initializeStatics()
+{
+}
+
+void
+nsHtml5MetaScanner::releaseStatics()
+{
+}
+
+
+#include "nsHtml5MetaScannerCppSupplement.h"
+
diff --git a/components/htmlfive/nsHtml5MetaScanner.h b/components/htmlfive/nsHtml5MetaScanner.h
new file mode 100644
index 000000000..6af25efe6
--- /dev/null
+++ b/components/htmlfive/nsHtml5MetaScanner.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2008-2015 Mozilla Foundation
+ * Copyright (c) 2018-2021 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit MetaScanner.java instead and regenerate.
+ */
+
+#ifndef nsHtml5MetaScanner_h
+#define nsHtml5MetaScanner_h
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+class nsHtml5StreamParser;
+
+class nsHtml5AttributeName;
+class nsHtml5ElementName;
+class nsHtml5Tokenizer;
+class nsHtml5TreeBuilder;
+class nsHtml5UTF16Buffer;
+class nsHtml5StateSnapshot;
+class nsHtml5Portability;
+
+
+class nsHtml5MetaScanner
+{
+ private:
+ static staticJArray<char16_t,int32_t> CHARSET;
+ static staticJArray<char16_t,int32_t> CONTENT;
+ static staticJArray<char16_t,int32_t> HTTP_EQUIV;
+ static staticJArray<char16_t,int32_t> CONTENT_TYPE;
+ static const int32_t NO = 0;
+
+ static const int32_t M = 1;
+
+ static const int32_t E = 2;
+
+ static const int32_t T = 3;
+
+ static const int32_t A = 4;
+
+ static const int32_t DATA = 0;
+
+ static const int32_t TAG_OPEN = 1;
+
+ static const int32_t SCAN_UNTIL_GT = 2;
+
+ static const int32_t TAG_NAME = 3;
+
+ static const int32_t BEFORE_ATTRIBUTE_NAME = 4;
+
+ static const int32_t ATTRIBUTE_NAME = 5;
+
+ static const int32_t AFTER_ATTRIBUTE_NAME = 6;
+
+ static const int32_t BEFORE_ATTRIBUTE_VALUE = 7;
+
+ static const int32_t ATTRIBUTE_VALUE_DOUBLE_QUOTED = 8;
+
+ static const int32_t ATTRIBUTE_VALUE_SINGLE_QUOTED = 9;
+
+ static const int32_t ATTRIBUTE_VALUE_UNQUOTED = 10;
+
+ static const int32_t AFTER_ATTRIBUTE_VALUE_QUOTED = 11;
+
+ static const int32_t MARKUP_DECLARATION_OPEN = 13;
+
+ static const int32_t MARKUP_DECLARATION_HYPHEN = 14;
+
+ static const int32_t COMMENT_START = 15;
+
+ static const int32_t COMMENT_START_DASH = 16;
+
+ static const int32_t COMMENT = 17;
+
+ static const int32_t COMMENT_END_DASH = 18;
+
+ static const int32_t COMMENT_END = 19;
+
+ static const int32_t SELF_CLOSING_START_TAG = 20;
+
+ static const int32_t HTTP_EQUIV_NOT_SEEN = 0;
+
+ static const int32_t HTTP_EQUIV_CONTENT_TYPE = 1;
+
+ static const int32_t HTTP_EQUIV_OTHER = 2;
+
+ protected:
+ nsHtml5ByteReadable* readable;
+ private:
+ int32_t metaState;
+ int32_t contentIndex;
+ int32_t charsetIndex;
+ int32_t httpEquivIndex;
+ int32_t contentTypeIndex;
+ protected:
+ int32_t stateSave;
+ private:
+ int32_t strBufLen;
+ autoJArray<char16_t,int32_t> strBuf;
+ nsHtml5String content;
+ nsHtml5String charset;
+ int32_t httpEquivState;
+ nsHtml5TreeBuilder* treeBuilder;
+ public:
+ explicit nsHtml5MetaScanner(nsHtml5TreeBuilder* tb);
+ ~nsHtml5MetaScanner();
+ protected:
+ void stateLoop(int32_t state);
+ private:
+ void handleCharInAttributeValue(int32_t c);
+ inline int32_t toAsciiLowerCase(int32_t c)
+ {
+ if (c >= 'A' && c <= 'Z') {
+ return c + 0x20;
+ }
+ return c;
+ }
+
+ void addToBuffer(int32_t c);
+ void handleAttributeValue();
+ bool handleTag();
+ bool handleTagInner();
+ protected:
+ bool tryCharset(nsHtml5String encoding);
+ public:
+ static void initializeStatics();
+ static void releaseStatics();
+
+#include "nsHtml5MetaScannerHSupplement.h"
+};
+
+#endif
+
diff --git a/components/htmlfive/nsHtml5MetaScannerCppSupplement.h b/components/htmlfive/nsHtml5MetaScannerCppSupplement.h
new file mode 100644
index 000000000..9d2496361
--- /dev/null
+++ b/components/htmlfive/nsHtml5MetaScannerCppSupplement.h
@@ -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 "nsEncoderDecoderUtils.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+
+using mozilla::dom::EncodingUtils;
+
+void
+nsHtml5MetaScanner::sniff(nsHtml5ByteReadable* bytes, nsACString& charset)
+{
+ readable = bytes;
+ stateLoop(stateSave);
+ readable = nullptr;
+ charset.Assign(mCharset);
+}
+
+bool
+nsHtml5MetaScanner::tryCharset(nsHtml5String charset)
+{
+ // This code needs to stay in sync with
+ // nsHtml5StreamParser::internalEncodingDeclaration. Unfortunately, the
+ // trickery with member fields here leads to some copy-paste reuse. :-(
+ nsAutoCString label;
+ nsString charset16; // Not Auto, because using it to hold nsStringBuffer*
+ charset.ToString(charset16);
+ CopyUTF16toUTF8(charset16, label);
+ nsAutoCString encoding;
+ if (!EncodingUtils::FindEncodingForLabel(label, encoding)) {
+ return false;
+ }
+ if (encoding.EqualsLiteral("UTF-16BE") ||
+ encoding.EqualsLiteral("UTF-16LE")) {
+ mCharset.AssignLiteral("UTF-8");
+ return true;
+ }
+ if (encoding.EqualsLiteral("x-user-defined")) {
+ // WebKit/Blink hack for Indian and Armenian legacy sites
+ mCharset.AssignLiteral("windows-1252");
+ return true;
+ }
+ mCharset.Assign(encoding);
+ return true;
+}
diff --git a/components/htmlfive/nsHtml5MetaScannerHSupplement.h b/components/htmlfive/nsHtml5MetaScannerHSupplement.h
new file mode 100644
index 000000000..b1bcbb671
--- /dev/null
+++ b/components/htmlfive/nsHtml5MetaScannerHSupplement.h
@@ -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/. */
+
+private:
+ nsCString mCharset;
+ inline int32_t read()
+ {
+ return readable->read();
+ }
+public:
+ void sniff(nsHtml5ByteReadable* bytes, nsACString& charset);
diff --git a/components/htmlfive/nsHtml5Module.cpp b/components/htmlfive/nsHtml5Module.cpp
new file mode 100644
index 000000000..cc019d035
--- /dev/null
+++ b/components/htmlfive/nsHtml5Module.cpp
@@ -0,0 +1,137 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsHtml5AttributeName.h"
+#include "nsHtml5ElementName.h"
+#include "nsHtml5HtmlAttributes.h"
+#include "nsHtml5NamedCharacters.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5UTF16Buffer.h"
+#include "nsHtml5Module.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Attributes.h"
+
+using namespace mozilla;
+
+// static
+bool nsHtml5Module::sOffMainThread = true;
+nsIThread* nsHtml5Module::sStreamParserThread = nullptr;
+nsIThread* nsHtml5Module::sMainThread = nullptr;
+
+// static
+void
+nsHtml5Module::InitializeStatics()
+{
+ Preferences::AddBoolVarCache(&sOffMainThread, "html5.offmainthread");
+ nsHtml5AttributeName::initializeStatics();
+ nsHtml5ElementName::initializeStatics();
+ nsHtml5HtmlAttributes::initializeStatics();
+ nsHtml5NamedCharacters::initializeStatics();
+ nsHtml5Portability::initializeStatics();
+ nsHtml5StackNode::initializeStatics();
+ nsHtml5Tokenizer::initializeStatics();
+ nsHtml5TreeBuilder::initializeStatics();
+ nsHtml5UTF16Buffer::initializeStatics();
+ nsHtml5StreamParser::InitializeStatics();
+ nsHtml5TreeOpExecutor::InitializeStatics();
+#ifdef DEBUG
+ sNsHtml5ModuleInitialized = true;
+#endif
+}
+
+// static
+void
+nsHtml5Module::ReleaseStatics()
+{
+#ifdef DEBUG
+ sNsHtml5ModuleInitialized = false;
+#endif
+ nsHtml5AttributeName::releaseStatics();
+ nsHtml5ElementName::releaseStatics();
+ nsHtml5HtmlAttributes::releaseStatics();
+ nsHtml5NamedCharacters::releaseStatics();
+ nsHtml5Portability::releaseStatics();
+ nsHtml5StackNode::releaseStatics();
+ nsHtml5Tokenizer::releaseStatics();
+ nsHtml5TreeBuilder::releaseStatics();
+ nsHtml5UTF16Buffer::releaseStatics();
+ NS_IF_RELEASE(sStreamParserThread);
+ NS_IF_RELEASE(sMainThread);
+}
+
+// static
+already_AddRefed<nsIParser>
+nsHtml5Module::NewHtml5Parser()
+{
+ MOZ_ASSERT(sNsHtml5ModuleInitialized, "nsHtml5Module not initialized.");
+ nsCOMPtr<nsIParser> rv = new nsHtml5Parser();
+ return rv.forget();
+}
+
+// static
+nsresult
+nsHtml5Module::Initialize(nsIParser* aParser, nsIDocument* aDoc, nsIURI* aURI, nsISupports* aContainer, nsIChannel* aChannel)
+{
+ MOZ_ASSERT(sNsHtml5ModuleInitialized, "nsHtml5Module not initialized.");
+ nsHtml5Parser* parser = static_cast<nsHtml5Parser*> (aParser);
+ return parser->Initialize(aDoc, aURI, aContainer, aChannel);
+}
+
+class nsHtml5ParserThreadTerminator final : public nsIObserver
+{
+ public:
+ NS_DECL_ISUPPORTS
+ explicit nsHtml5ParserThreadTerminator(nsIThread* aThread)
+ : mThread(aThread)
+ {}
+ NS_IMETHOD Observe(nsISupports *, const char *topic, const char16_t *) override
+ {
+ NS_ASSERTION(!strcmp(topic, "xpcom-shutdown-threads"),
+ "Unexpected topic");
+ if (mThread) {
+ mThread->Shutdown();
+ mThread = nullptr;
+ }
+ return NS_OK;
+ }
+ private:
+ ~nsHtml5ParserThreadTerminator() {}
+
+ nsCOMPtr<nsIThread> mThread;
+};
+
+NS_IMPL_ISUPPORTS(nsHtml5ParserThreadTerminator, nsIObserver)
+
+// static
+nsIThread*
+nsHtml5Module::GetStreamParserThread()
+{
+ if (sOffMainThread) {
+ if (!sStreamParserThread) {
+ NS_NewNamedThread("HTML5 Parser", &sStreamParserThread);
+ NS_ASSERTION(sStreamParserThread, "Thread creation failed!");
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ASSERTION(os, "do_GetService failed");
+ os->AddObserver(new nsHtml5ParserThreadTerminator(sStreamParserThread),
+ "xpcom-shutdown-threads",
+ false);
+ }
+ return sStreamParserThread;
+ }
+ if (!sMainThread) {
+ NS_GetMainThread(&sMainThread);
+ NS_ASSERTION(sMainThread, "Main thread getter failed");
+ }
+ return sMainThread;
+}
+
+#ifdef DEBUG
+bool nsHtml5Module::sNsHtml5ModuleInitialized = false;
+#endif
diff --git a/components/htmlfive/nsHtml5Module.h b/components/htmlfive/nsHtml5Module.h
new file mode 100644
index 000000000..e667b0a2e
--- /dev/null
+++ b/components/htmlfive/nsHtml5Module.h
@@ -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/. */
+
+#ifndef nsHtml5Module_h
+#define nsHtml5Module_h
+
+#include "nsIParser.h"
+#include "nsIThread.h"
+
+class nsHtml5Module
+{
+ public:
+ static void InitializeStatics();
+ static void ReleaseStatics();
+ static already_AddRefed<nsIParser> NewHtml5Parser();
+ static nsresult Initialize(nsIParser* aParser, nsIDocument* aDoc, nsIURI* aURI, nsISupports* aContainer, nsIChannel* aChannel);
+ static nsIThread* GetStreamParserThread();
+ static bool sOffMainThread;
+ private:
+#ifdef DEBUG
+ static bool sNsHtml5ModuleInitialized;
+#endif
+ static nsIThread* sStreamParserThread;
+ static nsIThread* sMainThread;
+};
+
+#endif // nsHtml5Module_h
diff --git a/components/htmlfive/nsHtml5NamedCharacters.cpp b/components/htmlfive/nsHtml5NamedCharacters.cpp
new file mode 100644
index 000000000..302dbf5c9
--- /dev/null
+++ b/components/htmlfive/nsHtml5NamedCharacters.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#define nsHtml5NamedCharacters_cpp_
+#include "jArray.h"
+#include "nscore.h"
+#include "nsDebug.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Logging.h"
+
+#include "nsHtml5NamedCharacters.h"
+
+const char16_t nsHtml5NamedCharacters::VALUES[][2] = {
+#define NAMED_CHARACTER_REFERENCE(N, CHARS, LEN, FLAG, VALUE) \
+{ VALUE },
+#include "nsHtml5NamedCharactersInclude.h"
+#undef NAMED_CHARACTER_REFERENCE
+{0, 0} };
+
+char16_t** nsHtml5NamedCharacters::WINDOWS_1252;
+static char16_t const WINDOWS_1252_DATA[] = {
+ 0x20AC,
+ 0x0081,
+ 0x201A,
+ 0x0192,
+ 0x201E,
+ 0x2026,
+ 0x2020,
+ 0x2021,
+ 0x02C6,
+ 0x2030,
+ 0x0160,
+ 0x2039,
+ 0x0152,
+ 0x008D,
+ 0x017D,
+ 0x008F,
+ 0x0090,
+ 0x2018,
+ 0x2019,
+ 0x201C,
+ 0x201D,
+ 0x2022,
+ 0x2013,
+ 0x2014,
+ 0x02DC,
+ 0x2122,
+ 0x0161,
+ 0x203A,
+ 0x0153,
+ 0x009D,
+ 0x017E,
+ 0x0178
+};
+
+/**
+ * To avoid having lots of pointers in the |charData| array, below,
+ * which would cause us to have to do lots of relocations at library
+ * load time, store all the string data for the names in one big array.
+ * Then use tricks with enums to help us build an array that contains
+ * the positions of each within the big arrays.
+ */
+
+static const int8_t ALL_NAMES[] = {
+#define NAMED_CHARACTER_REFERENCE(N, CHARS, LEN, FLAG, VALUE) \
+CHARS ,
+#include "nsHtml5NamedCharactersInclude.h"
+#undef NAMED_CHARACTER_REFERENCE
+};
+
+enum NamePositions {
+ DUMMY_INITIAL_NAME_POSITION = 0,
+/* enums don't take up space, so generate _START and _END */
+#define NAMED_CHARACTER_REFERENCE(N, CHARS, LEN, FLAG, VALUE) \
+NAME_##N##_DUMMY, /* automatically one higher than previous */ \
+NAME_##N##_START = NAME_##N##_DUMMY - 1, \
+NAME_##N##_END = NAME_##N##_START + LEN + FLAG,
+#include "nsHtml5NamedCharactersInclude.h"
+#undef NAMED_CHARACTER_REFERENCE
+ DUMMY_FINAL_NAME_VALUE
+};
+
+static_assert(MOZ_ARRAY_LENGTH(ALL_NAMES) < 0x10000, "Start positions should fit in 16 bits");
+
+const nsHtml5CharacterName nsHtml5NamedCharacters::NAMES[] = {
+#ifdef DEBUG
+ #define NAMED_CHARACTER_REFERENCE(N, CHARS, LEN, FLAG, VALUE) \
+{ NAME_##N##_START, LEN, N },
+#else
+ #define NAMED_CHARACTER_REFERENCE(N, CHARS, LEN, FLAG, VALUE) \
+{ NAME_##N##_START, LEN, },
+#endif
+#include "nsHtml5NamedCharactersInclude.h"
+#undef NAMED_CHARACTER_REFERENCE
+};
+
+int32_t
+nsHtml5CharacterName::length() const
+{
+ return nameLen;
+}
+
+char16_t
+nsHtml5CharacterName::charAt(int32_t index) const
+{
+ return static_cast<char16_t> (ALL_NAMES[nameStart + index]);
+}
+
+void
+nsHtml5NamedCharacters::initializeStatics()
+{
+ WINDOWS_1252 = new char16_t*[32];
+ for (int32_t i = 0; i < 32; ++i) {
+ WINDOWS_1252[i] = (char16_t*)&(WINDOWS_1252_DATA[i]);
+ }
+}
+
+void
+nsHtml5NamedCharacters::releaseStatics()
+{
+ delete[] WINDOWS_1252;
+}
diff --git a/components/htmlfive/nsHtml5NamedCharacters.h b/components/htmlfive/nsHtml5NamedCharacters.h
new file mode 100644
index 000000000..9c6cc3a9b
--- /dev/null
+++ b/components/htmlfive/nsHtml5NamedCharacters.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef nsHtml5NamedCharacters_h
+#define nsHtml5NamedCharacters_h
+
+#include "jArray.h"
+#include "nscore.h"
+#include "nsDebug.h"
+#include "mozilla/Logging.h"
+#include "nsMemory.h"
+
+struct nsHtml5CharacterName {
+ uint16_t nameStart;
+ uint16_t nameLen;
+ #ifdef DEBUG
+ int32_t n;
+ #endif
+ int32_t length() const;
+ char16_t charAt(int32_t index) const;
+};
+
+class nsHtml5NamedCharacters
+{
+ public:
+ static const nsHtml5CharacterName NAMES[];
+ static const char16_t VALUES[][2];
+ static char16_t** WINDOWS_1252;
+ static void initializeStatics();
+ static void releaseStatics();
+};
+
+#endif // nsHtml5NamedCharacters_h
diff --git a/components/htmlfive/nsHtml5NamedCharactersAccel.cpp b/components/htmlfive/nsHtml5NamedCharactersAccel.cpp
new file mode 100644
index 000000000..e9c42d71b
--- /dev/null
+++ b/components/htmlfive/nsHtml5NamedCharactersAccel.cpp
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2004-2010 Apple Computer, Inc., Mozilla Foundation, and Opera
+ * Software ASA.
+ *
+ * You are granted a license to use, reproduce and create derivative works of
+ * this document.
+ */
+
+
+#include "nsHtml5NamedCharactersAccel.h"
+
+static int32_t const HILO_ACCEL_65[] = {
+0, 0, 0, 0, 0, 0, 0, 12386493, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40174181, 0, 0, 0, 0, 60162966, 0, 0, 0, 75367550, 0, 0, 0, 82183396, 0, 0, 0, 0, 0, 115148507, 0, 0, 135989275, 139397199, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_66[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28770743, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82248935, 0, 0, 0, 0, 0, 115214046, 0, 0, 0, 139528272, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_68[] = {
+0, 0, 0, 4980811, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38470219, 0, 0, 0, 0, 0, 0, 0, 0, 64553944, 0, 0, 0, 0, 0, 0, 0, 92145022, 0, 0, 0, 0, 0, 0, 0, 0, 139593810, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_69[] = {
+65536, 0, 0, 0, 0, 0, 0, 0, 13172937, 0, 0, 0, 0, 0, 25297282, 0, 0, 28901816, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71500866, 0, 0, 0, 0, 82380008, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_71[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94897574, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_72[] = {
+0, 0, 2555943, 0, 0, 0, 0, 0, 0, 0, 15532269, 0, 0, 0, 0, 0, 0, 0, 31785444, 34406924, 0, 0, 0, 0, 0, 40895088, 0, 0, 0, 60228503, 0, 0, 0, 0, 0, 0, 0, 82445546, 0, 0, 0, 0, 0, 115279583, 0, 0, 136054812, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_73[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40239718, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_74[] = {
+0, 0, 0, 5046349, 0, 0, 10944679, 0, 13238474, 0, 15597806, 16056565, 0, 20578618, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_76[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95225257, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_77[] = {
+196610, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_78[] = {
+0, 0, 0, 0, 8454273, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46072511, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_79[] = {
+0, 0, 2687016, 0, 0, 0, 0, 0, 13304011, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31850982, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_82[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34472462, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95290798, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_83[] = {
+0, 0, 0, 5111886, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34603535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105776718, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_84[] = {
+0, 0, 0, 0, 8585346, 0, 11075752, 0, 0, 0, 0, 16187638, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_85[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28508594, 0, 0, 0, 0, 0, 0, 0, 40305255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_86[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95421871, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_90[] = {
+0, 0, 0, 5177423, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_97[] = {
+327684, 1900571, 2949162, 5374032, 8716420, 0, 11206826, 12517566, 13435084, 0, 15663343, 16515320, 19988785, 20644155, 25428355, 27197855, 0, 29163962, 31916519, 34734609, 36045347, 0, 0, 0, 40436328, 40960625, 41615994, 46596800, 54264627, 60556184, 64750554, 68879387, 71763012, 75826303, 77268122, 0, 81462490, 83952875, 92865919, 96142769, 105973327, 110167691, 0, 116917984, 121833283, 132253665, 136251421, 140707923, 0, 0, 144574620, 145361066
+};
+
+static int32_t const HILO_ACCEL_98[] = {
+393222, 0, 0, 0, 0, 0, 11272364, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36176423, 38535756, 0, 0, 0, 0, 41681532, 46727880, 0, 60687261, 0, 0, 71828552, 75891846, 0, 0, 0, 84411650, 0, 96404924, 0, 0, 0, 117376761, 121898820, 132319203, 136382496, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_99[] = {
+589831, 1966110, 3276846, 5505107, 8978566, 10420383, 11468973, 12583104, 13631694, 15139046, 15794416, 16711933, 20054322, 20840764, 25624965, 27263392, 0, 29360574, 32244200, 34931219, 36373033, 38601293, 39584348, 0, 40567402, 41091698, 42205821, 46858954, 54723389, 60818335, 65143773, 68944924, 71959625, 75957383, 77530268, 80938194, 81593564, 84739337, 92997002, 96863680, 106235474, 110233234, 0, 117704448, 122816325, 132515812, 136579106, 140773476, 142149753, 143001732, 144705695, 145492139
+};
+
+static int32_t const HILO_ACCEL_100[] = {
+0, 0, 3342387, 0, 9044106, 0, 11534512, 0, 13697233, 0, 0, 0, 0, 0, 25690504, 0, 0, 0, 0, 0, 36438572, 38732366, 0, 0, 0, 41157236, 0, 46924492, 54788932, 61080481, 65209315, 0, 72025163, 0, 0, 0, 0, 85132558, 93062540, 96929223, 106563158, 0, 0, 118032133, 123012947, 132581351, 136775717, 140839013, 0, 143067271, 0, 145557677
+};
+
+static int32_t const HILO_ACCEL_101[] = {
+0, 2162719, 3473460, 5636181, 0, 0, 0, 0, 0, 0, 0, 18809088, 20185395, 21299519, 0, 0, 0, 29622721, 0, 0, 0, 39256656, 39649885, 0, 0, 41288309, 42336901, 47448781, 55182149, 61342629, 65274852, 69010461, 72811596, 76219528, 77726880, 0, 0, 86967572, 93128077, 97650120, 106628699, 110560915, 0, 118490890, 123733846, 132646888, 0, 141232230, 142411898, 0, 144836769, 145688750
+};
+
+static int32_t const HILO_ACCEL_102[] = {
+655370, 2228258, 3538998, 5701719, 9109643, 10485920, 11600049, 12648641, 13762770, 15204584, 15859954, 18874656, 20250933, 21365062, 25756041, 27328929, 28574132, 29688261, 32309741, 34996758, 36504109, 39322200, 39715422, 39912033, 40632940, 41353847, 42467975, 47514325, 55247691, 61473705, 65405925, 69272606, 72877144, 76285068, 77857955, 81003732, 81659102, 87164208, 93193614, 97715667, 106759772, 110626456, 114296528, 118687505, 123864929, 132712425, 136906792, 141297772, 142477438, 143132808, 144902307, 145754288
+};
+
+static int32_t const HILO_ACCEL_103[] = {
+786443, 0, 0, 0, 9240716, 0, 11665586, 0, 13893843, 0, 0, 0, 0, 0, 25887114, 0, 0, 0, 0, 0, 36635182, 0, 0, 0, 0, 0, 42599049, 0, 0, 0, 65733607, 0, 73008217, 0, 77989029, 0, 81724639, 87295283, 0, 98305492, 107021918, 0, 0, 0, 0, 0, 137037866, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_104[] = {
+0, 0, 3604535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27394466, 0, 29753798, 32571886, 35258903, 0, 0, 0, 0, 0, 0, 0, 0, 55509836, 61604779, 0, 0, 0, 0, 0, 0, 81790176, 87557429, 93259151, 98502109, 107152994, 110888601, 0, 119015188, 124323683, 133498858, 137234476, 0, 0, 143263881, 0, 145819825
+};
+
+static int32_t const HILO_ACCEL_105[] = {
+0, 0, 3866680, 6160472, 0, 10616993, 0, 12714178, 0, 0, 0, 0, 20316470, 0, 0, 27460003, 0, 31261127, 32637426, 35521051, 0, 0, 0, 39977570, 0, 0, 0, 48366294, 56492880, 62391213, 0, 69338146, 73073755, 0, 78316711, 0, 0, 0, 93980048, 98764256, 107218532, 111085213, 114362065, 119736089, 125241194, 133957622, 0, 0, 0, 143329419, 144967844, 145885362
+};
+
+static int32_t const HILO_ACCEL_106[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62456761, 0, 69403683, 73139292, 0, 78382252, 0, 81855713, 87622969, 0, 98829796, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_107[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48431843, 0, 0, 0, 0, 0, 76416141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_108[] = {
+851981, 0, 4063292, 0, 9306254, 0, 0, 0, 0, 0, 0, 19005729, 0, 0, 0, 27525540, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42795659, 49152740, 56623967, 62587834, 66061292, 69600292, 73401437, 0, 0, 0, 0, 87950650, 94111131, 99878373, 107546213, 112002720, 0, 119932708, 125306744, 0, 137496623, 141363309, 0, 143460492, 0, 0
+};
+
+static int32_t const HILO_ACCEL_109[] = {
+917518, 0, 0, 0, 9502863, 0, 0, 0, 14155989, 0, 0, 19071267, 0, 0, 26083724, 0, 0, 0, 32702963, 0, 36700720, 0, 0, 0, 0, 0, 43057806, 0, 0, 0, 66520049, 0, 0, 0, 78841005, 81069269, 0, 88147263, 0, 99943925, 107873898, 112068270, 0, 120063783, 125831033, 0, 137693235, 0, 0, 143526030, 0, 0
+};
+
+static int32_t const HILO_ACCEL_110[] = {
+983055, 0, 0, 0, 0, 0, 0, 0, 14483673, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37093937, 0, 0, 0, 0, 0, 44565138, 49349359, 0, 0, 66651128, 69665831, 73860193, 0, 79561908, 0, 0, 88606018, 94176669, 0, 0, 0, 0, 120129321, 0, 0, 0, 141494382, 0, 143591567, 0, 0
+};
+
+static int32_t const HILO_ACCEL_111[] = {
+1114128, 2293795, 4587583, 8257631, 9633938, 10813603, 11731123, 12845251, 14680286, 15270121, 15925491, 19661092, 20382007, 24969543, 26149263, 27656613, 28639669, 31392222, 32768500, 35586591, 37225015, 39387737, 39780959, 40043107, 40698477, 41419384, 44696233, 52495090, 57738081, 63439804, 66782202, 69927976, 73925736, 76809359, 79824063, 81134806, 81921250, 89785673, 94307742, 100795894, 107939439, 112330415, 114427602, 120588074, 126158721, 134416381, 137824310, 141559920, 142542975, 143853712, 145033381, 145950899
+};
+
+static int32_t const HILO_ACCEL_112[] = {
+1179666, 0, 0, 0, 9699476, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26280336, 0, 0, 0, 0, 0, 38076985, 0, 0, 0, 0, 0, 45220523, 52560674, 0, 0, 67175420, 69993516, 0, 0, 79889603, 0, 0, 89916763, 94373280, 101451267, 108136048, 0, 114493139, 120784689, 126355334, 134481924, 138414136, 141625457, 142608512, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_113[] = {
+0, 0, 0, 0, 9896085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33292789, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67830786, 0, 0, 0, 80020676, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127403913, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_114[] = {
+1310739, 2359332, 4653127, 0, 0, 0, 12189876, 0, 0, 0, 0, 0, 0, 0, 26345874, 28246439, 0, 31457760, 0, 35652128, 38142534, 0, 0, 0, 0, 0, 45351603, 52757283, 57869170, 63636425, 67961868, 71304237, 73991273, 0, 0, 0, 0, 90309981, 0, 101910029, 108988019, 114034355, 0, 120850228, 127469465, 135464965, 138741825, 141690994, 142739585, 143984788, 0, 0
+};
+
+static int32_t const HILO_ACCEL_115[] = {
+1441813, 2424869, 4718664, 8388735, 10027160, 10879142, 12255419, 12976325, 14745825, 15401194, 15991028, 19857709, 20447544, 25035134, 26542483, 28377520, 28705206, 31588833, 33358333, 35783201, 38208071, 39453274, 39846496, 40108644, 40764014, 41484921, 45613749, 53216038, 58196852, 63898572, 68158478, 71369793, 74253418, 77005973, 80479430, 81265879, 81986787, 90965347, 94504353, 103679508, 109250176, 114165453, 114558676, 121243445, 127731610, 135727124, 138807366, 142018675, 142805123, 144115862, 145098918, 146016436
+};
+
+static int32_t const HILO_ACCEL_116[] = {
+1572887, 0, 0, 0, 10092698, 0, 12320956, 0, 14811362, 0, 0, 19923248, 0, 25166207, 26739094, 0, 0, 0, 33423870, 0, 38273608, 0, 0, 0, 0, 0, 45744825, 0, 58262393, 64095184, 68355089, 0, 75170926, 0, 80610509, 0, 0, 91817325, 0, 104203823, 109512324, 0, 0, 121636667, 128059294, 0, 139069511, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_117[] = {
+1703961, 2490406, 4849737, 0, 10223771, 0, 0, 13107399, 15007971, 15466732, 0, 0, 20513081, 25231745, 26870169, 0, 0, 31654371, 34275839, 0, 38404681, 0, 0, 0, 40829551, 0, 45875899, 53609261, 59900794, 64226259, 68551700, 0, 0, 0, 80807119, 81331417, 0, 91948410, 94700963, 104465975, 109643400, 114230991, 114951893, 121702209, 131663779, 0, 139266123, 0, 0, 144246936, 145295527, 0
+};
+
+static int32_t const HILO_ACCEL_118[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27132315, 0, 0, 0, 0, 0, 0, 39518811, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75302012, 0, 0, 0, 0, 92079484, 0, 105383483, 109708938, 0, 0, 0, 0, 0, 0, 0, 0, 144312474, 0, 0
+};
+
+static int32_t const HILO_ACCEL_119[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46006973, 0, 60031891, 64291797, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105711177, 0, 0, 0, 0, 131991514, 135923736, 139331662, 0, 0, 144378011, 0, 146147509
+};
+
+static int32_t const HILO_ACCEL_120[] = {
+0, 0, 0, 0, 10354845, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68813847, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 121767746, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_121[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60097429, 0, 0, 0, 0, 77137048, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int32_t const HILO_ACCEL_122[] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64422870, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 132122591, 0, 0, 142084216, 0, 0, 0, 0
+};
+
+const int32_t* const nsHtml5NamedCharactersAccel::HILO_ACCEL[] = {
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ HILO_ACCEL_65,
+ HILO_ACCEL_66,
+ 0,
+ HILO_ACCEL_68,
+ HILO_ACCEL_69,
+ 0,
+ HILO_ACCEL_71,
+ HILO_ACCEL_72,
+ HILO_ACCEL_73,
+ HILO_ACCEL_74,
+ 0,
+ HILO_ACCEL_76,
+ HILO_ACCEL_77,
+ HILO_ACCEL_78,
+ HILO_ACCEL_79,
+ 0,
+ 0,
+ HILO_ACCEL_82,
+ HILO_ACCEL_83,
+ HILO_ACCEL_84,
+ HILO_ACCEL_85,
+ HILO_ACCEL_86,
+ 0,
+ 0,
+ 0,
+ HILO_ACCEL_90,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ HILO_ACCEL_97,
+ HILO_ACCEL_98,
+ HILO_ACCEL_99,
+ HILO_ACCEL_100,
+ HILO_ACCEL_101,
+ HILO_ACCEL_102,
+ HILO_ACCEL_103,
+ HILO_ACCEL_104,
+ HILO_ACCEL_105,
+ HILO_ACCEL_106,
+ HILO_ACCEL_107,
+ HILO_ACCEL_108,
+ HILO_ACCEL_109,
+ HILO_ACCEL_110,
+ HILO_ACCEL_111,
+ HILO_ACCEL_112,
+ HILO_ACCEL_113,
+ HILO_ACCEL_114,
+ HILO_ACCEL_115,
+ HILO_ACCEL_116,
+ HILO_ACCEL_117,
+ HILO_ACCEL_118,
+ HILO_ACCEL_119,
+ HILO_ACCEL_120,
+ HILO_ACCEL_121,
+ HILO_ACCEL_122
+};
+
diff --git a/components/htmlfive/nsHtml5NamedCharactersAccel.h b/components/htmlfive/nsHtml5NamedCharactersAccel.h
new file mode 100644
index 000000000..8e6df8ebb
--- /dev/null
+++ b/components/htmlfive/nsHtml5NamedCharactersAccel.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2004-2010 Apple Computer, Inc., Mozilla Foundation, and Opera
+ * Software ASA.
+ *
+ * You are granted a license to use, reproduce and create derivative works of
+ * this document.
+ */
+
+#ifndef nsHtml5NamedCharactersAccel_h
+#define nsHtml5NamedCharactersAccel_h
+
+#include "jArray.h"
+#include "nscore.h"
+#include "nsDebug.h"
+#include "mozilla/Logging.h"
+#include "nsMemory.h"
+
+class nsHtml5NamedCharactersAccel
+{
+ public:
+ static const int32_t* const HILO_ACCEL[];
+};
+
+#endif // nsHtml5NamedCharactersAccel_h
diff --git a/components/htmlfive/nsHtml5NamedCharactersInclude.h b/components/htmlfive/nsHtml5NamedCharactersInclude.h
new file mode 100644
index 000000000..0a6b0c11e
--- /dev/null
+++ b/components/htmlfive/nsHtml5NamedCharactersInclude.h
@@ -0,0 +1,2266 @@
+/*
+ * Copyright 2004-2010 Apple Computer, Inc., Mozilla Foundation, and Opera
+ * Software ASA.
+ *
+ * You are granted a license to use, reproduce and create derivative works of
+ * this document.
+ */
+
+/* Data generated from the table of named character references found at
+ *
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/named-character-references.html#named-character-references
+ *
+ * Files that #include this file must #define NAMED_CHARACTER_REFERENCE as a
+ * macro of four parameters:
+ *
+ * 1. a unique integer N identifying the Nth [0,1,..] macro expansion in this file,
+ * 2. a comma-separated sequence of characters comprising the character name,
+ * without the first two letters or 0 if the sequence would be empty.
+ * See Tokenizer.java.
+ * 3. the length of this sequence of characters,
+ * 4. placeholder flag (0 if argument #is not a placeholder and 1 if it is),
+ * 5. a comma-separated sequence of char16_t literals corresponding
+ * to the code-point(s) of the named character.
+ *
+ * The macro expansion doesn't have to refer to all or any of these parameters,
+ * but common sense dictates that it should involve at least one of them.
+ */
+
+// This #define allows the NAMED_CHARACTER_REFERENCE macro to accept comma-
+// separated sequences as single macro arguments. Using commas directly would
+// split the sequence into multiple macro arguments.
+#define _ ,
+
+NAMED_CHARACTER_REFERENCE(0, /* A E */ 'l' _ 'i' _ 'g', 3, 0, 0x00c6 _ 0)
+NAMED_CHARACTER_REFERENCE(1, /* A E */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0x00c6 _ 0)
+NAMED_CHARACTER_REFERENCE(2, /* A M */ 'P', 1, 0, 0x0026 _ 0)
+NAMED_CHARACTER_REFERENCE(3, /* A M */ 'P' _ ';', 2, 0, 0x0026 _ 0)
+NAMED_CHARACTER_REFERENCE(4, /* A a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00c1 _ 0)
+NAMED_CHARACTER_REFERENCE(5, /* A a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00c1 _ 0)
+NAMED_CHARACTER_REFERENCE(6, /* A b */ 'r' _ 'e' _ 'v' _ 'e' _ ';', 5, 0, 0x0102 _ 0)
+NAMED_CHARACTER_REFERENCE(7, /* A c */ 'i' _ 'r' _ 'c', 3, 0, 0x00c2 _ 0)
+NAMED_CHARACTER_REFERENCE(8, /* A c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x00c2 _ 0)
+NAMED_CHARACTER_REFERENCE(9, /* A c */ 'y' _ ';', 2, 0, 0x0410 _ 0)
+NAMED_CHARACTER_REFERENCE(10, /* A f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd04)
+NAMED_CHARACTER_REFERENCE(11, /* A g */ 'r' _ 'a' _ 'v' _ 'e', 4, 0, 0x00c0 _ 0)
+NAMED_CHARACTER_REFERENCE(12, /* A g */ 'r' _ 'a' _ 'v' _ 'e' _ ';', 5, 0, 0x00c0 _ 0)
+NAMED_CHARACTER_REFERENCE(13, /* A l */ 'p' _ 'h' _ 'a' _ ';', 4, 0, 0x0391 _ 0)
+NAMED_CHARACTER_REFERENCE(14, /* A m */ 'a' _ 'c' _ 'r' _ ';', 4, 0, 0x0100 _ 0)
+NAMED_CHARACTER_REFERENCE(15, /* A n */ 'd' _ ';', 2, 0, 0x2a53 _ 0)
+NAMED_CHARACTER_REFERENCE(16, /* A o */ 'g' _ 'o' _ 'n' _ ';', 4, 0, 0x0104 _ 0)
+NAMED_CHARACTER_REFERENCE(17, /* A o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd38)
+NAMED_CHARACTER_REFERENCE(18, /* A p */ 'p' _ 'l' _ 'y' _ 'F' _ 'u' _ 'n' _ 'c' _ 't' _ 'i' _ 'o' _ 'n' _ ';', 12, 0, 0x2061 _ 0)
+NAMED_CHARACTER_REFERENCE(19, /* A r */ 'i' _ 'n' _ 'g', 3, 0, 0x00c5 _ 0)
+NAMED_CHARACTER_REFERENCE(20, /* A r */ 'i' _ 'n' _ 'g' _ ';', 4, 0, 0x00c5 _ 0)
+NAMED_CHARACTER_REFERENCE(21, /* A s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdc9c)
+NAMED_CHARACTER_REFERENCE(22, /* A s */ 's' _ 'i' _ 'g' _ 'n' _ ';', 5, 0, 0x2254 _ 0)
+NAMED_CHARACTER_REFERENCE(23, /* A t */ 'i' _ 'l' _ 'd' _ 'e', 4, 0, 0x00c3 _ 0)
+NAMED_CHARACTER_REFERENCE(24, /* A t */ 'i' _ 'l' _ 'd' _ 'e' _ ';', 5, 0, 0x00c3 _ 0)
+NAMED_CHARACTER_REFERENCE(25, /* A u */ 'm' _ 'l', 2, 0, 0x00c4 _ 0)
+NAMED_CHARACTER_REFERENCE(26, /* A u */ 'm' _ 'l' _ ';', 3, 0, 0x00c4 _ 0)
+NAMED_CHARACTER_REFERENCE(27, /* B a */ 'c' _ 'k' _ 's' _ 'l' _ 'a' _ 's' _ 'h' _ ';', 8, 0, 0x2216 _ 0)
+NAMED_CHARACTER_REFERENCE(28, /* B a */ 'r' _ 'v' _ ';', 3, 0, 0x2ae7 _ 0)
+NAMED_CHARACTER_REFERENCE(29, /* B a */ 'r' _ 'w' _ 'e' _ 'd' _ ';', 5, 0, 0x2306 _ 0)
+NAMED_CHARACTER_REFERENCE(30, /* B c */ 'y' _ ';', 2, 0, 0x0411 _ 0)
+NAMED_CHARACTER_REFERENCE(31, /* B e */ 'c' _ 'a' _ 'u' _ 's' _ 'e' _ ';', 6, 0, 0x2235 _ 0)
+NAMED_CHARACTER_REFERENCE(32, /* B e */ 'r' _ 'n' _ 'o' _ 'u' _ 'l' _ 'l' _ 'i' _ 's' _ ';', 9, 0, 0x212c _ 0)
+NAMED_CHARACTER_REFERENCE(33, /* B e */ 't' _ 'a' _ ';', 3, 0, 0x0392 _ 0)
+NAMED_CHARACTER_REFERENCE(34, /* B f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd05)
+NAMED_CHARACTER_REFERENCE(35, /* B o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd39)
+NAMED_CHARACTER_REFERENCE(36, /* B r */ 'e' _ 'v' _ 'e' _ ';', 4, 0, 0x02d8 _ 0)
+NAMED_CHARACTER_REFERENCE(37, /* B s */ 'c' _ 'r' _ ';', 3, 0, 0x212c _ 0)
+NAMED_CHARACTER_REFERENCE(38, /* B u */ 'm' _ 'p' _ 'e' _ 'q' _ ';', 5, 0, 0x224e _ 0)
+NAMED_CHARACTER_REFERENCE(39, /* C H */ 'c' _ 'y' _ ';', 3, 0, 0x0427 _ 0)
+NAMED_CHARACTER_REFERENCE(40, /* C O */ 'P' _ 'Y', 2, 0, 0x00a9 _ 0)
+NAMED_CHARACTER_REFERENCE(41, /* C O */ 'P' _ 'Y' _ ';', 3, 0, 0x00a9 _ 0)
+NAMED_CHARACTER_REFERENCE(42, /* C a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x0106 _ 0)
+NAMED_CHARACTER_REFERENCE(43, /* C a */ 'p' _ ';', 2, 0, 0x22d2 _ 0)
+NAMED_CHARACTER_REFERENCE(44, /* C a */ 'p' _ 'i' _ 't' _ 'a' _ 'l' _ 'D' _ 'i' _ 'f' _ 'f' _ 'e' _ 'r' _ 'e' _ 'n' _ 't' _ 'i' _ 'a' _ 'l' _ 'D' _ ';', 19, 0, 0x2145 _ 0)
+NAMED_CHARACTER_REFERENCE(45, /* C a */ 'y' _ 'l' _ 'e' _ 'y' _ 's' _ ';', 6, 0, 0x212d _ 0)
+NAMED_CHARACTER_REFERENCE(46, /* C c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x010c _ 0)
+NAMED_CHARACTER_REFERENCE(47, /* C c */ 'e' _ 'd' _ 'i' _ 'l', 4, 0, 0x00c7 _ 0)
+NAMED_CHARACTER_REFERENCE(48, /* C c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x00c7 _ 0)
+NAMED_CHARACTER_REFERENCE(49, /* C c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x0108 _ 0)
+NAMED_CHARACTER_REFERENCE(50, /* C c */ 'o' _ 'n' _ 'i' _ 'n' _ 't' _ ';', 6, 0, 0x2230 _ 0)
+NAMED_CHARACTER_REFERENCE(51, /* C d */ 'o' _ 't' _ ';', 3, 0, 0x010a _ 0)
+NAMED_CHARACTER_REFERENCE(52, /* C e */ 'd' _ 'i' _ 'l' _ 'l' _ 'a' _ ';', 6, 0, 0x00b8 _ 0)
+NAMED_CHARACTER_REFERENCE(53, /* C e */ 'n' _ 't' _ 'e' _ 'r' _ 'D' _ 'o' _ 't' _ ';', 8, 0, 0x00b7 _ 0)
+NAMED_CHARACTER_REFERENCE(54, /* C f */ 'r' _ ';', 2, 0, 0x212d _ 0)
+NAMED_CHARACTER_REFERENCE(55, /* C h */ 'i' _ ';', 2, 0, 0x03a7 _ 0)
+NAMED_CHARACTER_REFERENCE(56, /* C i */ 'r' _ 'c' _ 'l' _ 'e' _ 'D' _ 'o' _ 't' _ ';', 8, 0, 0x2299 _ 0)
+NAMED_CHARACTER_REFERENCE(57, /* C i */ 'r' _ 'c' _ 'l' _ 'e' _ 'M' _ 'i' _ 'n' _ 'u' _ 's' _ ';', 10, 0, 0x2296 _ 0)
+NAMED_CHARACTER_REFERENCE(58, /* C i */ 'r' _ 'c' _ 'l' _ 'e' _ 'P' _ 'l' _ 'u' _ 's' _ ';', 9, 0, 0x2295 _ 0)
+NAMED_CHARACTER_REFERENCE(59, /* C i */ 'r' _ 'c' _ 'l' _ 'e' _ 'T' _ 'i' _ 'm' _ 'e' _ 's' _ ';', 10, 0, 0x2297 _ 0)
+NAMED_CHARACTER_REFERENCE(60, /* C l */ 'o' _ 'c' _ 'k' _ 'w' _ 'i' _ 's' _ 'e' _ 'C' _ 'o' _ 'n' _ 't' _ 'o' _ 'u' _ 'r' _ 'I' _ 'n' _ 't' _ 'e' _ 'g' _ 'r' _ 'a' _ 'l' _ ';', 23, 0, 0x2232 _ 0)
+NAMED_CHARACTER_REFERENCE(61, /* C l */ 'o' _ 's' _ 'e' _ 'C' _ 'u' _ 'r' _ 'l' _ 'y' _ 'D' _ 'o' _ 'u' _ 'b' _ 'l' _ 'e' _ 'Q' _ 'u' _ 'o' _ 't' _ 'e' _ ';', 20, 0, 0x201d _ 0)
+NAMED_CHARACTER_REFERENCE(62, /* C l */ 'o' _ 's' _ 'e' _ 'C' _ 'u' _ 'r' _ 'l' _ 'y' _ 'Q' _ 'u' _ 'o' _ 't' _ 'e' _ ';', 14, 0, 0x2019 _ 0)
+NAMED_CHARACTER_REFERENCE(63, /* C o */ 'l' _ 'o' _ 'n' _ ';', 4, 0, 0x2237 _ 0)
+NAMED_CHARACTER_REFERENCE(64, /* C o */ 'l' _ 'o' _ 'n' _ 'e' _ ';', 5, 0, 0x2a74 _ 0)
+NAMED_CHARACTER_REFERENCE(65, /* C o */ 'n' _ 'g' _ 'r' _ 'u' _ 'e' _ 'n' _ 't' _ ';', 8, 0, 0x2261 _ 0)
+NAMED_CHARACTER_REFERENCE(66, /* C o */ 'n' _ 'i' _ 'n' _ 't' _ ';', 5, 0, 0x222f _ 0)
+NAMED_CHARACTER_REFERENCE(67, /* C o */ 'n' _ 't' _ 'o' _ 'u' _ 'r' _ 'I' _ 'n' _ 't' _ 'e' _ 'g' _ 'r' _ 'a' _ 'l' _ ';', 14, 0, 0x222e _ 0)
+NAMED_CHARACTER_REFERENCE(68, /* C o */ 'p' _ 'f' _ ';', 3, 0, 0x2102 _ 0)
+NAMED_CHARACTER_REFERENCE(69, /* C o */ 'p' _ 'r' _ 'o' _ 'd' _ 'u' _ 'c' _ 't' _ ';', 8, 0, 0x2210 _ 0)
+NAMED_CHARACTER_REFERENCE(70, /* C o */ 'u' _ 'n' _ 't' _ 'e' _ 'r' _ 'C' _ 'l' _ 'o' _ 'c' _ 'k' _ 'w' _ 'i' _ 's' _ 'e' _ 'C' _ 'o' _ 'n' _ 't' _ 'o' _ 'u' _ 'r' _ 'I' _ 'n' _ 't' _ 'e' _ 'g' _ 'r' _ 'a' _ 'l' _ ';', 30, 0, 0x2233 _ 0)
+NAMED_CHARACTER_REFERENCE(71, /* C r */ 'o' _ 's' _ 's' _ ';', 4, 0, 0x2a2f _ 0)
+NAMED_CHARACTER_REFERENCE(72, /* C s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdc9e)
+NAMED_CHARACTER_REFERENCE(73, /* C u */ 'p' _ ';', 2, 0, 0x22d3 _ 0)
+NAMED_CHARACTER_REFERENCE(74, /* C u */ 'p' _ 'C' _ 'a' _ 'p' _ ';', 5, 0, 0x224d _ 0)
+NAMED_CHARACTER_REFERENCE(75, /* D D */ ';', 1, 0, 0x2145 _ 0)
+NAMED_CHARACTER_REFERENCE(76, /* D D */ 'o' _ 't' _ 'r' _ 'a' _ 'h' _ 'd' _ ';', 7, 0, 0x2911 _ 0)
+NAMED_CHARACTER_REFERENCE(77, /* D J */ 'c' _ 'y' _ ';', 3, 0, 0x0402 _ 0)
+NAMED_CHARACTER_REFERENCE(78, /* D S */ 'c' _ 'y' _ ';', 3, 0, 0x0405 _ 0)
+NAMED_CHARACTER_REFERENCE(79, /* D Z */ 'c' _ 'y' _ ';', 3, 0, 0x040f _ 0)
+NAMED_CHARACTER_REFERENCE(80, /* D a */ 'g' _ 'g' _ 'e' _ 'r' _ ';', 5, 0, 0x2021 _ 0)
+NAMED_CHARACTER_REFERENCE(81, /* D a */ 'r' _ 'r' _ ';', 3, 0, 0x21a1 _ 0)
+NAMED_CHARACTER_REFERENCE(82, /* D a */ 's' _ 'h' _ 'v' _ ';', 4, 0, 0x2ae4 _ 0)
+NAMED_CHARACTER_REFERENCE(83, /* D c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x010e _ 0)
+NAMED_CHARACTER_REFERENCE(84, /* D c */ 'y' _ ';', 2, 0, 0x0414 _ 0)
+NAMED_CHARACTER_REFERENCE(85, /* D e */ 'l' _ ';', 2, 0, 0x2207 _ 0)
+NAMED_CHARACTER_REFERENCE(86, /* D e */ 'l' _ 't' _ 'a' _ ';', 4, 0, 0x0394 _ 0)
+NAMED_CHARACTER_REFERENCE(87, /* D f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd07)
+NAMED_CHARACTER_REFERENCE(88, /* D i */ 'a' _ 'c' _ 'r' _ 'i' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'A' _ 'c' _ 'u' _ 't' _ 'e' _ ';', 15, 0, 0x00b4 _ 0)
+NAMED_CHARACTER_REFERENCE(89, /* D i */ 'a' _ 'c' _ 'r' _ 'i' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'D' _ 'o' _ 't' _ ';', 13, 0, 0x02d9 _ 0)
+NAMED_CHARACTER_REFERENCE(90, /* D i */ 'a' _ 'c' _ 'r' _ 'i' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'D' _ 'o' _ 'u' _ 'b' _ 'l' _ 'e' _ 'A' _ 'c' _ 'u' _ 't' _ 'e' _ ';', 21, 0, 0x02dd _ 0)
+NAMED_CHARACTER_REFERENCE(91, /* D i */ 'a' _ 'c' _ 'r' _ 'i' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'G' _ 'r' _ 'a' _ 'v' _ 'e' _ ';', 15, 0, 0x0060 _ 0)
+NAMED_CHARACTER_REFERENCE(92, /* D i */ 'a' _ 'c' _ 'r' _ 'i' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 15, 0, 0x02dc _ 0)
+NAMED_CHARACTER_REFERENCE(93, /* D i */ 'a' _ 'm' _ 'o' _ 'n' _ 'd' _ ';', 6, 0, 0x22c4 _ 0)
+NAMED_CHARACTER_REFERENCE(94, /* D i */ 'f' _ 'f' _ 'e' _ 'r' _ 'e' _ 'n' _ 't' _ 'i' _ 'a' _ 'l' _ 'D' _ ';', 12, 0, 0x2146 _ 0)
+NAMED_CHARACTER_REFERENCE(95, /* D o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd3b)
+NAMED_CHARACTER_REFERENCE(96, /* D o */ 't' _ ';', 2, 0, 0x00a8 _ 0)
+NAMED_CHARACTER_REFERENCE(97, /* D o */ 't' _ 'D' _ 'o' _ 't' _ ';', 5, 0, 0x20dc _ 0)
+NAMED_CHARACTER_REFERENCE(98, /* D o */ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 7, 0, 0x2250 _ 0)
+NAMED_CHARACTER_REFERENCE(99, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'C' _ 'o' _ 'n' _ 't' _ 'o' _ 'u' _ 'r' _ 'I' _ 'n' _ 't' _ 'e' _ 'g' _ 'r' _ 'a' _ 'l' _ ';', 20, 0, 0x222f _ 0)
+NAMED_CHARACTER_REFERENCE(100, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'D' _ 'o' _ 't' _ ';', 8, 0, 0x00a8 _ 0)
+NAMED_CHARACTER_REFERENCE(101, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'D' _ 'o' _ 'w' _ 'n' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 14, 0, 0x21d3 _ 0)
+NAMED_CHARACTER_REFERENCE(102, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'L' _ 'e' _ 'f' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 14, 0, 0x21d0 _ 0)
+NAMED_CHARACTER_REFERENCE(103, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'L' _ 'e' _ 'f' _ 't' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 19, 0, 0x21d4 _ 0)
+NAMED_CHARACTER_REFERENCE(104, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'L' _ 'e' _ 'f' _ 't' _ 'T' _ 'e' _ 'e' _ ';', 12, 0, 0x2ae4 _ 0)
+NAMED_CHARACTER_REFERENCE(105, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'L' _ 'o' _ 'n' _ 'g' _ 'L' _ 'e' _ 'f' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 18, 0, 0x27f8 _ 0)
+NAMED_CHARACTER_REFERENCE(106, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'L' _ 'o' _ 'n' _ 'g' _ 'L' _ 'e' _ 'f' _ 't' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 23, 0, 0x27fa _ 0)
+NAMED_CHARACTER_REFERENCE(107, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'L' _ 'o' _ 'n' _ 'g' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 19, 0, 0x27f9 _ 0)
+NAMED_CHARACTER_REFERENCE(108, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 15, 0, 0x21d2 _ 0)
+NAMED_CHARACTER_REFERENCE(109, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'T' _ 'e' _ 'e' _ ';', 13, 0, 0x22a8 _ 0)
+NAMED_CHARACTER_REFERENCE(110, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'U' _ 'p' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 12, 0, 0x21d1 _ 0)
+NAMED_CHARACTER_REFERENCE(111, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'U' _ 'p' _ 'D' _ 'o' _ 'w' _ 'n' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 16, 0, 0x21d5 _ 0)
+NAMED_CHARACTER_REFERENCE(112, /* D o */ 'u' _ 'b' _ 'l' _ 'e' _ 'V' _ 'e' _ 'r' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'B' _ 'a' _ 'r' _ ';', 16, 0, 0x2225 _ 0)
+NAMED_CHARACTER_REFERENCE(113, /* D o */ 'w' _ 'n' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 8, 0, 0x2193 _ 0)
+NAMED_CHARACTER_REFERENCE(114, /* D o */ 'w' _ 'n' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ 'B' _ 'a' _ 'r' _ ';', 11, 0, 0x2913 _ 0)
+NAMED_CHARACTER_REFERENCE(115, /* D o */ 'w' _ 'n' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ 'U' _ 'p' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 15, 0, 0x21f5 _ 0)
+NAMED_CHARACTER_REFERENCE(116, /* D o */ 'w' _ 'n' _ 'B' _ 'r' _ 'e' _ 'v' _ 'e' _ ';', 8, 0, 0x0311 _ 0)
+NAMED_CHARACTER_REFERENCE(117, /* D o */ 'w' _ 'n' _ 'L' _ 'e' _ 'f' _ 't' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 18, 0, 0x2950 _ 0)
+NAMED_CHARACTER_REFERENCE(118, /* D o */ 'w' _ 'n' _ 'L' _ 'e' _ 'f' _ 't' _ 'T' _ 'e' _ 'e' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 16, 0, 0x295e _ 0)
+NAMED_CHARACTER_REFERENCE(119, /* D o */ 'w' _ 'n' _ 'L' _ 'e' _ 'f' _ 't' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 13, 0, 0x21bd _ 0)
+NAMED_CHARACTER_REFERENCE(120, /* D o */ 'w' _ 'n' _ 'L' _ 'e' _ 'f' _ 't' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ 'B' _ 'a' _ 'r' _ ';', 16, 0, 0x2956 _ 0)
+NAMED_CHARACTER_REFERENCE(121, /* D o */ 'w' _ 'n' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'T' _ 'e' _ 'e' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 17, 0, 0x295f _ 0)
+NAMED_CHARACTER_REFERENCE(122, /* D o */ 'w' _ 'n' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 14, 0, 0x21c1 _ 0)
+NAMED_CHARACTER_REFERENCE(123, /* D o */ 'w' _ 'n' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ 'B' _ 'a' _ 'r' _ ';', 17, 0, 0x2957 _ 0)
+NAMED_CHARACTER_REFERENCE(124, /* D o */ 'w' _ 'n' _ 'T' _ 'e' _ 'e' _ ';', 6, 0, 0x22a4 _ 0)
+NAMED_CHARACTER_REFERENCE(125, /* D o */ 'w' _ 'n' _ 'T' _ 'e' _ 'e' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 11, 0, 0x21a7 _ 0)
+NAMED_CHARACTER_REFERENCE(126, /* D o */ 'w' _ 'n' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 8, 0, 0x21d3 _ 0)
+NAMED_CHARACTER_REFERENCE(127, /* D s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdc9f)
+NAMED_CHARACTER_REFERENCE(128, /* D s */ 't' _ 'r' _ 'o' _ 'k' _ ';', 5, 0, 0x0110 _ 0)
+NAMED_CHARACTER_REFERENCE(129, /* E N */ 'G' _ ';', 2, 0, 0x014a _ 0)
+NAMED_CHARACTER_REFERENCE(130, /* E T */ 'H', 1, 0, 0x00d0 _ 0)
+NAMED_CHARACTER_REFERENCE(131, /* E T */ 'H' _ ';', 2, 0, 0x00d0 _ 0)
+NAMED_CHARACTER_REFERENCE(132, /* E a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00c9 _ 0)
+NAMED_CHARACTER_REFERENCE(133, /* E a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00c9 _ 0)
+NAMED_CHARACTER_REFERENCE(134, /* E c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x011a _ 0)
+NAMED_CHARACTER_REFERENCE(135, /* E c */ 'i' _ 'r' _ 'c', 3, 0, 0x00ca _ 0)
+NAMED_CHARACTER_REFERENCE(136, /* E c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x00ca _ 0)
+NAMED_CHARACTER_REFERENCE(137, /* E c */ 'y' _ ';', 2, 0, 0x042d _ 0)
+NAMED_CHARACTER_REFERENCE(138, /* E d */ 'o' _ 't' _ ';', 3, 0, 0x0116 _ 0)
+NAMED_CHARACTER_REFERENCE(139, /* E f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd08)
+NAMED_CHARACTER_REFERENCE(140, /* E g */ 'r' _ 'a' _ 'v' _ 'e', 4, 0, 0x00c8 _ 0)
+NAMED_CHARACTER_REFERENCE(141, /* E g */ 'r' _ 'a' _ 'v' _ 'e' _ ';', 5, 0, 0x00c8 _ 0)
+NAMED_CHARACTER_REFERENCE(142, /* E l */ 'e' _ 'm' _ 'e' _ 'n' _ 't' _ ';', 6, 0, 0x2208 _ 0)
+NAMED_CHARACTER_REFERENCE(143, /* E m */ 'a' _ 'c' _ 'r' _ ';', 4, 0, 0x0112 _ 0)
+NAMED_CHARACTER_REFERENCE(144, /* E m */ 'p' _ 't' _ 'y' _ 'S' _ 'm' _ 'a' _ 'l' _ 'l' _ 'S' _ 'q' _ 'u' _ 'a' _ 'r' _ 'e' _ ';', 15, 0, 0x25fb _ 0)
+NAMED_CHARACTER_REFERENCE(145, /* E m */ 'p' _ 't' _ 'y' _ 'V' _ 'e' _ 'r' _ 'y' _ 'S' _ 'm' _ 'a' _ 'l' _ 'l' _ 'S' _ 'q' _ 'u' _ 'a' _ 'r' _ 'e' _ ';', 19, 0, 0x25ab _ 0)
+NAMED_CHARACTER_REFERENCE(146, /* E o */ 'g' _ 'o' _ 'n' _ ';', 4, 0, 0x0118 _ 0)
+NAMED_CHARACTER_REFERENCE(147, /* E o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd3c)
+NAMED_CHARACTER_REFERENCE(148, /* E p */ 's' _ 'i' _ 'l' _ 'o' _ 'n' _ ';', 6, 0, 0x0395 _ 0)
+NAMED_CHARACTER_REFERENCE(149, /* E q */ 'u' _ 'a' _ 'l' _ ';', 4, 0, 0x2a75 _ 0)
+NAMED_CHARACTER_REFERENCE(150, /* E q */ 'u' _ 'a' _ 'l' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 9, 0, 0x2242 _ 0)
+NAMED_CHARACTER_REFERENCE(151, /* E q */ 'u' _ 'i' _ 'l' _ 'i' _ 'b' _ 'r' _ 'i' _ 'u' _ 'm' _ ';', 10, 0, 0x21cc _ 0)
+NAMED_CHARACTER_REFERENCE(152, /* E s */ 'c' _ 'r' _ ';', 3, 0, 0x2130 _ 0)
+NAMED_CHARACTER_REFERENCE(153, /* E s */ 'i' _ 'm' _ ';', 3, 0, 0x2a73 _ 0)
+NAMED_CHARACTER_REFERENCE(154, /* E t */ 'a' _ ';', 2, 0, 0x0397 _ 0)
+NAMED_CHARACTER_REFERENCE(155, /* E u */ 'm' _ 'l', 2, 0, 0x00cb _ 0)
+NAMED_CHARACTER_REFERENCE(156, /* E u */ 'm' _ 'l' _ ';', 3, 0, 0x00cb _ 0)
+NAMED_CHARACTER_REFERENCE(157, /* E x */ 'i' _ 's' _ 't' _ 's' _ ';', 5, 0, 0x2203 _ 0)
+NAMED_CHARACTER_REFERENCE(158, /* E x */ 'p' _ 'o' _ 'n' _ 'e' _ 'n' _ 't' _ 'i' _ 'a' _ 'l' _ 'E' _ ';', 11, 0, 0x2147 _ 0)
+NAMED_CHARACTER_REFERENCE(159, /* F c */ 'y' _ ';', 2, 0, 0x0424 _ 0)
+NAMED_CHARACTER_REFERENCE(160, /* F f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd09)
+NAMED_CHARACTER_REFERENCE(161, /* F i */ 'l' _ 'l' _ 'e' _ 'd' _ 'S' _ 'm' _ 'a' _ 'l' _ 'l' _ 'S' _ 'q' _ 'u' _ 'a' _ 'r' _ 'e' _ ';', 16, 0, 0x25fc _ 0)
+NAMED_CHARACTER_REFERENCE(162, /* F i */ 'l' _ 'l' _ 'e' _ 'd' _ 'V' _ 'e' _ 'r' _ 'y' _ 'S' _ 'm' _ 'a' _ 'l' _ 'l' _ 'S' _ 'q' _ 'u' _ 'a' _ 'r' _ 'e' _ ';', 20, 0, 0x25aa _ 0)
+NAMED_CHARACTER_REFERENCE(163, /* F o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd3d)
+NAMED_CHARACTER_REFERENCE(164, /* F o */ 'r' _ 'A' _ 'l' _ 'l' _ ';', 5, 0, 0x2200 _ 0)
+NAMED_CHARACTER_REFERENCE(165, /* F o */ 'u' _ 'r' _ 'i' _ 'e' _ 'r' _ 't' _ 'r' _ 'f' _ ';', 9, 0, 0x2131 _ 0)
+NAMED_CHARACTER_REFERENCE(166, /* F s */ 'c' _ 'r' _ ';', 3, 0, 0x2131 _ 0)
+NAMED_CHARACTER_REFERENCE(167, /* G J */ 'c' _ 'y' _ ';', 3, 0, 0x0403 _ 0)
+NAMED_CHARACTER_REFERENCE(168, /* G T */ 0, 0, 1, 0x003e _ 0)
+NAMED_CHARACTER_REFERENCE(169, /* G T */ ';', 1, 0, 0x003e _ 0)
+NAMED_CHARACTER_REFERENCE(170, /* G a */ 'm' _ 'm' _ 'a' _ ';', 4, 0, 0x0393 _ 0)
+NAMED_CHARACTER_REFERENCE(171, /* G a */ 'm' _ 'm' _ 'a' _ 'd' _ ';', 5, 0, 0x03dc _ 0)
+NAMED_CHARACTER_REFERENCE(172, /* G b */ 'r' _ 'e' _ 'v' _ 'e' _ ';', 5, 0, 0x011e _ 0)
+NAMED_CHARACTER_REFERENCE(173, /* G c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x0122 _ 0)
+NAMED_CHARACTER_REFERENCE(174, /* G c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x011c _ 0)
+NAMED_CHARACTER_REFERENCE(175, /* G c */ 'y' _ ';', 2, 0, 0x0413 _ 0)
+NAMED_CHARACTER_REFERENCE(176, /* G d */ 'o' _ 't' _ ';', 3, 0, 0x0120 _ 0)
+NAMED_CHARACTER_REFERENCE(177, /* G f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd0a)
+NAMED_CHARACTER_REFERENCE(178, /* G g */ ';', 1, 0, 0x22d9 _ 0)
+NAMED_CHARACTER_REFERENCE(179, /* G o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd3e)
+NAMED_CHARACTER_REFERENCE(180, /* G r */ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 11, 0, 0x2265 _ 0)
+NAMED_CHARACTER_REFERENCE(181, /* G r */ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ 'L' _ 'e' _ 's' _ 's' _ ';', 15, 0, 0x22db _ 0)
+NAMED_CHARACTER_REFERENCE(182, /* G r */ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'F' _ 'u' _ 'l' _ 'l' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 15, 0, 0x2267 _ 0)
+NAMED_CHARACTER_REFERENCE(183, /* G r */ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ ';', 13, 0, 0x2aa2 _ 0)
+NAMED_CHARACTER_REFERENCE(184, /* G r */ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'L' _ 'e' _ 's' _ 's' _ ';', 10, 0, 0x2277 _ 0)
+NAMED_CHARACTER_REFERENCE(185, /* G r */ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'S' _ 'l' _ 'a' _ 'n' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 16, 0, 0x2a7e _ 0)
+NAMED_CHARACTER_REFERENCE(186, /* G r */ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 11, 0, 0x2273 _ 0)
+NAMED_CHARACTER_REFERENCE(187, /* G s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdca2)
+NAMED_CHARACTER_REFERENCE(188, /* G t */ ';', 1, 0, 0x226b _ 0)
+NAMED_CHARACTER_REFERENCE(189, /* H A */ 'R' _ 'D' _ 'c' _ 'y' _ ';', 5, 0, 0x042a _ 0)
+NAMED_CHARACTER_REFERENCE(190, /* H a */ 'c' _ 'e' _ 'k' _ ';', 4, 0, 0x02c7 _ 0)
+NAMED_CHARACTER_REFERENCE(191, /* H a */ 't' _ ';', 2, 0, 0x005e _ 0)
+NAMED_CHARACTER_REFERENCE(192, /* H c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x0124 _ 0)
+NAMED_CHARACTER_REFERENCE(193, /* H f */ 'r' _ ';', 2, 0, 0x210c _ 0)
+NAMED_CHARACTER_REFERENCE(194, /* H i */ 'l' _ 'b' _ 'e' _ 'r' _ 't' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 11, 0, 0x210b _ 0)
+NAMED_CHARACTER_REFERENCE(195, /* H o */ 'p' _ 'f' _ ';', 3, 0, 0x210d _ 0)
+NAMED_CHARACTER_REFERENCE(196, /* H o */ 'r' _ 'i' _ 'z' _ 'o' _ 'n' _ 't' _ 'a' _ 'l' _ 'L' _ 'i' _ 'n' _ 'e' _ ';', 13, 0, 0x2500 _ 0)
+NAMED_CHARACTER_REFERENCE(197, /* H s */ 'c' _ 'r' _ ';', 3, 0, 0x210b _ 0)
+NAMED_CHARACTER_REFERENCE(198, /* H s */ 't' _ 'r' _ 'o' _ 'k' _ ';', 5, 0, 0x0126 _ 0)
+NAMED_CHARACTER_REFERENCE(199, /* H u */ 'm' _ 'p' _ 'D' _ 'o' _ 'w' _ 'n' _ 'H' _ 'u' _ 'm' _ 'p' _ ';', 11, 0, 0x224e _ 0)
+NAMED_CHARACTER_REFERENCE(200, /* H u */ 'm' _ 'p' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 8, 0, 0x224f _ 0)
+NAMED_CHARACTER_REFERENCE(201, /* I E */ 'c' _ 'y' _ ';', 3, 0, 0x0415 _ 0)
+NAMED_CHARACTER_REFERENCE(202, /* I J */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0x0132 _ 0)
+NAMED_CHARACTER_REFERENCE(203, /* I O */ 'c' _ 'y' _ ';', 3, 0, 0x0401 _ 0)
+NAMED_CHARACTER_REFERENCE(204, /* I a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00cd _ 0)
+NAMED_CHARACTER_REFERENCE(205, /* I a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00cd _ 0)
+NAMED_CHARACTER_REFERENCE(206, /* I c */ 'i' _ 'r' _ 'c', 3, 0, 0x00ce _ 0)
+NAMED_CHARACTER_REFERENCE(207, /* I c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x00ce _ 0)
+NAMED_CHARACTER_REFERENCE(208, /* I c */ 'y' _ ';', 2, 0, 0x0418 _ 0)
+NAMED_CHARACTER_REFERENCE(209, /* I d */ 'o' _ 't' _ ';', 3, 0, 0x0130 _ 0)
+NAMED_CHARACTER_REFERENCE(210, /* I f */ 'r' _ ';', 2, 0, 0x2111 _ 0)
+NAMED_CHARACTER_REFERENCE(211, /* I g */ 'r' _ 'a' _ 'v' _ 'e', 4, 0, 0x00cc _ 0)
+NAMED_CHARACTER_REFERENCE(212, /* I g */ 'r' _ 'a' _ 'v' _ 'e' _ ';', 5, 0, 0x00cc _ 0)
+NAMED_CHARACTER_REFERENCE(213, /* I m */ ';', 1, 0, 0x2111 _ 0)
+NAMED_CHARACTER_REFERENCE(214, /* I m */ 'a' _ 'c' _ 'r' _ ';', 4, 0, 0x012a _ 0)
+NAMED_CHARACTER_REFERENCE(215, /* I m */ 'a' _ 'g' _ 'i' _ 'n' _ 'a' _ 'r' _ 'y' _ 'I' _ ';', 9, 0, 0x2148 _ 0)
+NAMED_CHARACTER_REFERENCE(216, /* I m */ 'p' _ 'l' _ 'i' _ 'e' _ 's' _ ';', 6, 0, 0x21d2 _ 0)
+NAMED_CHARACTER_REFERENCE(217, /* I n */ 't' _ ';', 2, 0, 0x222c _ 0)
+NAMED_CHARACTER_REFERENCE(218, /* I n */ 't' _ 'e' _ 'g' _ 'r' _ 'a' _ 'l' _ ';', 7, 0, 0x222b _ 0)
+NAMED_CHARACTER_REFERENCE(219, /* I n */ 't' _ 'e' _ 'r' _ 's' _ 'e' _ 'c' _ 't' _ 'i' _ 'o' _ 'n' _ ';', 11, 0, 0x22c2 _ 0)
+NAMED_CHARACTER_REFERENCE(220, /* I n */ 'v' _ 'i' _ 's' _ 'i' _ 'b' _ 'l' _ 'e' _ 'C' _ 'o' _ 'm' _ 'm' _ 'a' _ ';', 13, 0, 0x2063 _ 0)
+NAMED_CHARACTER_REFERENCE(221, /* I n */ 'v' _ 'i' _ 's' _ 'i' _ 'b' _ 'l' _ 'e' _ 'T' _ 'i' _ 'm' _ 'e' _ 's' _ ';', 13, 0, 0x2062 _ 0)
+NAMED_CHARACTER_REFERENCE(222, /* I o */ 'g' _ 'o' _ 'n' _ ';', 4, 0, 0x012e _ 0)
+NAMED_CHARACTER_REFERENCE(223, /* I o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd40)
+NAMED_CHARACTER_REFERENCE(224, /* I o */ 't' _ 'a' _ ';', 3, 0, 0x0399 _ 0)
+NAMED_CHARACTER_REFERENCE(225, /* I s */ 'c' _ 'r' _ ';', 3, 0, 0x2110 _ 0)
+NAMED_CHARACTER_REFERENCE(226, /* I t */ 'i' _ 'l' _ 'd' _ 'e' _ ';', 5, 0, 0x0128 _ 0)
+NAMED_CHARACTER_REFERENCE(227, /* I u */ 'k' _ 'c' _ 'y' _ ';', 4, 0, 0x0406 _ 0)
+NAMED_CHARACTER_REFERENCE(228, /* I u */ 'm' _ 'l', 2, 0, 0x00cf _ 0)
+NAMED_CHARACTER_REFERENCE(229, /* I u */ 'm' _ 'l' _ ';', 3, 0, 0x00cf _ 0)
+NAMED_CHARACTER_REFERENCE(230, /* J c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x0134 _ 0)
+NAMED_CHARACTER_REFERENCE(231, /* J c */ 'y' _ ';', 2, 0, 0x0419 _ 0)
+NAMED_CHARACTER_REFERENCE(232, /* J f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd0d)
+NAMED_CHARACTER_REFERENCE(233, /* J o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd41)
+NAMED_CHARACTER_REFERENCE(234, /* J s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdca5)
+NAMED_CHARACTER_REFERENCE(235, /* J s */ 'e' _ 'r' _ 'c' _ 'y' _ ';', 5, 0, 0x0408 _ 0)
+NAMED_CHARACTER_REFERENCE(236, /* J u */ 'k' _ 'c' _ 'y' _ ';', 4, 0, 0x0404 _ 0)
+NAMED_CHARACTER_REFERENCE(237, /* K H */ 'c' _ 'y' _ ';', 3, 0, 0x0425 _ 0)
+NAMED_CHARACTER_REFERENCE(238, /* K J */ 'c' _ 'y' _ ';', 3, 0, 0x040c _ 0)
+NAMED_CHARACTER_REFERENCE(239, /* K a */ 'p' _ 'p' _ 'a' _ ';', 4, 0, 0x039a _ 0)
+NAMED_CHARACTER_REFERENCE(240, /* K c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x0136 _ 0)
+NAMED_CHARACTER_REFERENCE(241, /* K c */ 'y' _ ';', 2, 0, 0x041a _ 0)
+NAMED_CHARACTER_REFERENCE(242, /* K f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd0e)
+NAMED_CHARACTER_REFERENCE(243, /* K o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd42)
+NAMED_CHARACTER_REFERENCE(244, /* K s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdca6)
+NAMED_CHARACTER_REFERENCE(245, /* L J */ 'c' _ 'y' _ ';', 3, 0, 0x0409 _ 0)
+NAMED_CHARACTER_REFERENCE(246, /* L T */ 0, 0, 1, 0x003c _ 0)
+NAMED_CHARACTER_REFERENCE(247, /* L T */ ';', 1, 0, 0x003c _ 0)
+NAMED_CHARACTER_REFERENCE(248, /* L a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x0139 _ 0)
+NAMED_CHARACTER_REFERENCE(249, /* L a */ 'm' _ 'b' _ 'd' _ 'a' _ ';', 5, 0, 0x039b _ 0)
+NAMED_CHARACTER_REFERENCE(250, /* L a */ 'n' _ 'g' _ ';', 3, 0, 0x27ea _ 0)
+NAMED_CHARACTER_REFERENCE(251, /* L a */ 'p' _ 'l' _ 'a' _ 'c' _ 'e' _ 't' _ 'r' _ 'f' _ ';', 9, 0, 0x2112 _ 0)
+NAMED_CHARACTER_REFERENCE(252, /* L a */ 'r' _ 'r' _ ';', 3, 0, 0x219e _ 0)
+NAMED_CHARACTER_REFERENCE(253, /* L c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x013d _ 0)
+NAMED_CHARACTER_REFERENCE(254, /* L c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x013b _ 0)
+NAMED_CHARACTER_REFERENCE(255, /* L c */ 'y' _ ';', 2, 0, 0x041b _ 0)
+NAMED_CHARACTER_REFERENCE(256, /* L e */ 'f' _ 't' _ 'A' _ 'n' _ 'g' _ 'l' _ 'e' _ 'B' _ 'r' _ 'a' _ 'c' _ 'k' _ 'e' _ 't' _ ';', 15, 0, 0x27e8 _ 0)
+NAMED_CHARACTER_REFERENCE(257, /* L e */ 'f' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 8, 0, 0x2190 _ 0)
+NAMED_CHARACTER_REFERENCE(258, /* L e */ 'f' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ 'B' _ 'a' _ 'r' _ ';', 11, 0, 0x21e4 _ 0)
+NAMED_CHARACTER_REFERENCE(259, /* L e */ 'f' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 18, 0, 0x21c6 _ 0)
+NAMED_CHARACTER_REFERENCE(260, /* L e */ 'f' _ 't' _ 'C' _ 'e' _ 'i' _ 'l' _ 'i' _ 'n' _ 'g' _ ';', 10, 0, 0x2308 _ 0)
+NAMED_CHARACTER_REFERENCE(261, /* L e */ 'f' _ 't' _ 'D' _ 'o' _ 'u' _ 'b' _ 'l' _ 'e' _ 'B' _ 'r' _ 'a' _ 'c' _ 'k' _ 'e' _ 't' _ ';', 16, 0, 0x27e6 _ 0)
+NAMED_CHARACTER_REFERENCE(262, /* L e */ 'f' _ 't' _ 'D' _ 'o' _ 'w' _ 'n' _ 'T' _ 'e' _ 'e' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 16, 0, 0x2961 _ 0)
+NAMED_CHARACTER_REFERENCE(263, /* L e */ 'f' _ 't' _ 'D' _ 'o' _ 'w' _ 'n' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 13, 0, 0x21c3 _ 0)
+NAMED_CHARACTER_REFERENCE(264, /* L e */ 'f' _ 't' _ 'D' _ 'o' _ 'w' _ 'n' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ 'B' _ 'a' _ 'r' _ ';', 16, 0, 0x2959 _ 0)
+NAMED_CHARACTER_REFERENCE(265, /* L e */ 'f' _ 't' _ 'F' _ 'l' _ 'o' _ 'o' _ 'r' _ ';', 8, 0, 0x230a _ 0)
+NAMED_CHARACTER_REFERENCE(266, /* L e */ 'f' _ 't' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x2194 _ 0)
+NAMED_CHARACTER_REFERENCE(267, /* L e */ 'f' _ 't' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 14, 0, 0x294e _ 0)
+NAMED_CHARACTER_REFERENCE(268, /* L e */ 'f' _ 't' _ 'T' _ 'e' _ 'e' _ ';', 6, 0, 0x22a3 _ 0)
+NAMED_CHARACTER_REFERENCE(269, /* L e */ 'f' _ 't' _ 'T' _ 'e' _ 'e' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 11, 0, 0x21a4 _ 0)
+NAMED_CHARACTER_REFERENCE(270, /* L e */ 'f' _ 't' _ 'T' _ 'e' _ 'e' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 12, 0, 0x295a _ 0)
+NAMED_CHARACTER_REFERENCE(271, /* L e */ 'f' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ ';', 11, 0, 0x22b2 _ 0)
+NAMED_CHARACTER_REFERENCE(272, /* L e */ 'f' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'B' _ 'a' _ 'r' _ ';', 14, 0, 0x29cf _ 0)
+NAMED_CHARACTER_REFERENCE(273, /* L e */ 'f' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 16, 0, 0x22b4 _ 0)
+NAMED_CHARACTER_REFERENCE(274, /* L e */ 'f' _ 't' _ 'U' _ 'p' _ 'D' _ 'o' _ 'w' _ 'n' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 15, 0, 0x2951 _ 0)
+NAMED_CHARACTER_REFERENCE(275, /* L e */ 'f' _ 't' _ 'U' _ 'p' _ 'T' _ 'e' _ 'e' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 14, 0, 0x2960 _ 0)
+NAMED_CHARACTER_REFERENCE(276, /* L e */ 'f' _ 't' _ 'U' _ 'p' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 11, 0, 0x21bf _ 0)
+NAMED_CHARACTER_REFERENCE(277, /* L e */ 'f' _ 't' _ 'U' _ 'p' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ 'B' _ 'a' _ 'r' _ ';', 14, 0, 0x2958 _ 0)
+NAMED_CHARACTER_REFERENCE(278, /* L e */ 'f' _ 't' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 9, 0, 0x21bc _ 0)
+NAMED_CHARACTER_REFERENCE(279, /* L e */ 'f' _ 't' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ 'B' _ 'a' _ 'r' _ ';', 12, 0, 0x2952 _ 0)
+NAMED_CHARACTER_REFERENCE(280, /* L e */ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 8, 0, 0x21d0 _ 0)
+NAMED_CHARACTER_REFERENCE(281, /* L e */ 'f' _ 't' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x21d4 _ 0)
+NAMED_CHARACTER_REFERENCE(282, /* L e */ 's' _ 's' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ ';', 15, 0, 0x22da _ 0)
+NAMED_CHARACTER_REFERENCE(283, /* L e */ 's' _ 's' _ 'F' _ 'u' _ 'l' _ 'l' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 12, 0, 0x2266 _ 0)
+NAMED_CHARACTER_REFERENCE(284, /* L e */ 's' _ 's' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ ';', 10, 0, 0x2276 _ 0)
+NAMED_CHARACTER_REFERENCE(285, /* L e */ 's' _ 's' _ 'L' _ 'e' _ 's' _ 's' _ ';', 7, 0, 0x2aa1 _ 0)
+NAMED_CHARACTER_REFERENCE(286, /* L e */ 's' _ 's' _ 'S' _ 'l' _ 'a' _ 'n' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 13, 0, 0x2a7d _ 0)
+NAMED_CHARACTER_REFERENCE(287, /* L e */ 's' _ 's' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 8, 0, 0x2272 _ 0)
+NAMED_CHARACTER_REFERENCE(288, /* L f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd0f)
+NAMED_CHARACTER_REFERENCE(289, /* L l */ ';', 1, 0, 0x22d8 _ 0)
+NAMED_CHARACTER_REFERENCE(290, /* L l */ 'e' _ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 9, 0, 0x21da _ 0)
+NAMED_CHARACTER_REFERENCE(291, /* L m */ 'i' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x013f _ 0)
+NAMED_CHARACTER_REFERENCE(292, /* L o */ 'n' _ 'g' _ 'L' _ 'e' _ 'f' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 12, 0, 0x27f5 _ 0)
+NAMED_CHARACTER_REFERENCE(293, /* L o */ 'n' _ 'g' _ 'L' _ 'e' _ 'f' _ 't' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 17, 0, 0x27f7 _ 0)
+NAMED_CHARACTER_REFERENCE(294, /* L o */ 'n' _ 'g' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x27f6 _ 0)
+NAMED_CHARACTER_REFERENCE(295, /* L o */ 'n' _ 'g' _ 'l' _ 'e' _ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 12, 0, 0x27f8 _ 0)
+NAMED_CHARACTER_REFERENCE(296, /* L o */ 'n' _ 'g' _ 'l' _ 'e' _ 'f' _ 't' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 17, 0, 0x27fa _ 0)
+NAMED_CHARACTER_REFERENCE(297, /* L o */ 'n' _ 'g' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x27f9 _ 0)
+NAMED_CHARACTER_REFERENCE(298, /* L o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd43)
+NAMED_CHARACTER_REFERENCE(299, /* L o */ 'w' _ 'e' _ 'r' _ 'L' _ 'e' _ 'f' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x2199 _ 0)
+NAMED_CHARACTER_REFERENCE(300, /* L o */ 'w' _ 'e' _ 'r' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 14, 0, 0x2198 _ 0)
+NAMED_CHARACTER_REFERENCE(301, /* L s */ 'c' _ 'r' _ ';', 3, 0, 0x2112 _ 0)
+NAMED_CHARACTER_REFERENCE(302, /* L s */ 'h' _ ';', 2, 0, 0x21b0 _ 0)
+NAMED_CHARACTER_REFERENCE(303, /* L s */ 't' _ 'r' _ 'o' _ 'k' _ ';', 5, 0, 0x0141 _ 0)
+NAMED_CHARACTER_REFERENCE(304, /* L t */ ';', 1, 0, 0x226a _ 0)
+NAMED_CHARACTER_REFERENCE(305, /* M a */ 'p' _ ';', 2, 0, 0x2905 _ 0)
+NAMED_CHARACTER_REFERENCE(306, /* M c */ 'y' _ ';', 2, 0, 0x041c _ 0)
+NAMED_CHARACTER_REFERENCE(307, /* M e */ 'd' _ 'i' _ 'u' _ 'm' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 10, 0, 0x205f _ 0)
+NAMED_CHARACTER_REFERENCE(308, /* M e */ 'l' _ 'l' _ 'i' _ 'n' _ 't' _ 'r' _ 'f' _ ';', 8, 0, 0x2133 _ 0)
+NAMED_CHARACTER_REFERENCE(309, /* M f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd10)
+NAMED_CHARACTER_REFERENCE(310, /* M i */ 'n' _ 'u' _ 's' _ 'P' _ 'l' _ 'u' _ 's' _ ';', 8, 0, 0x2213 _ 0)
+NAMED_CHARACTER_REFERENCE(311, /* M o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd44)
+NAMED_CHARACTER_REFERENCE(312, /* M s */ 'c' _ 'r' _ ';', 3, 0, 0x2133 _ 0)
+NAMED_CHARACTER_REFERENCE(313, /* M u */ ';', 1, 0, 0x039c _ 0)
+NAMED_CHARACTER_REFERENCE(314, /* N J */ 'c' _ 'y' _ ';', 3, 0, 0x040a _ 0)
+NAMED_CHARACTER_REFERENCE(315, /* N a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x0143 _ 0)
+NAMED_CHARACTER_REFERENCE(316, /* N c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x0147 _ 0)
+NAMED_CHARACTER_REFERENCE(317, /* N c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x0145 _ 0)
+NAMED_CHARACTER_REFERENCE(318, /* N c */ 'y' _ ';', 2, 0, 0x041d _ 0)
+NAMED_CHARACTER_REFERENCE(319, /* N e */ 'g' _ 'a' _ 't' _ 'i' _ 'v' _ 'e' _ 'M' _ 'e' _ 'd' _ 'i' _ 'u' _ 'm' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 18, 0, 0x200b _ 0)
+NAMED_CHARACTER_REFERENCE(320, /* N e */ 'g' _ 'a' _ 't' _ 'i' _ 'v' _ 'e' _ 'T' _ 'h' _ 'i' _ 'c' _ 'k' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 17, 0, 0x200b _ 0)
+NAMED_CHARACTER_REFERENCE(321, /* N e */ 'g' _ 'a' _ 't' _ 'i' _ 'v' _ 'e' _ 'T' _ 'h' _ 'i' _ 'n' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 16, 0, 0x200b _ 0)
+NAMED_CHARACTER_REFERENCE(322, /* N e */ 'g' _ 'a' _ 't' _ 'i' _ 'v' _ 'e' _ 'V' _ 'e' _ 'r' _ 'y' _ 'T' _ 'h' _ 'i' _ 'n' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 20, 0, 0x200b _ 0)
+NAMED_CHARACTER_REFERENCE(323, /* N e */ 's' _ 't' _ 'e' _ 'd' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ ';', 19, 0, 0x226b _ 0)
+NAMED_CHARACTER_REFERENCE(324, /* N e */ 's' _ 't' _ 'e' _ 'd' _ 'L' _ 'e' _ 's' _ 's' _ 'L' _ 'e' _ 's' _ 's' _ ';', 13, 0, 0x226a _ 0)
+NAMED_CHARACTER_REFERENCE(325, /* N e */ 'w' _ 'L' _ 'i' _ 'n' _ 'e' _ ';', 6, 0, 0x000a _ 0)
+NAMED_CHARACTER_REFERENCE(326, /* N f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd11)
+NAMED_CHARACTER_REFERENCE(327, /* N o */ 'B' _ 'r' _ 'e' _ 'a' _ 'k' _ ';', 6, 0, 0x2060 _ 0)
+NAMED_CHARACTER_REFERENCE(328, /* N o */ 'n' _ 'B' _ 'r' _ 'e' _ 'a' _ 'k' _ 'i' _ 'n' _ 'g' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 15, 0, 0x00a0 _ 0)
+NAMED_CHARACTER_REFERENCE(329, /* N o */ 'p' _ 'f' _ ';', 3, 0, 0x2115 _ 0)
+NAMED_CHARACTER_REFERENCE(330, /* N o */ 't' _ ';', 2, 0, 0x2aec _ 0)
+NAMED_CHARACTER_REFERENCE(331, /* N o */ 't' _ 'C' _ 'o' _ 'n' _ 'g' _ 'r' _ 'u' _ 'e' _ 'n' _ 't' _ ';', 11, 0, 0x2262 _ 0)
+NAMED_CHARACTER_REFERENCE(332, /* N o */ 't' _ 'C' _ 'u' _ 'p' _ 'C' _ 'a' _ 'p' _ ';', 8, 0, 0x226d _ 0)
+NAMED_CHARACTER_REFERENCE(333, /* N o */ 't' _ 'D' _ 'o' _ 'u' _ 'b' _ 'l' _ 'e' _ 'V' _ 'e' _ 'r' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'B' _ 'a' _ 'r' _ ';', 19, 0, 0x2226 _ 0)
+NAMED_CHARACTER_REFERENCE(334, /* N o */ 't' _ 'E' _ 'l' _ 'e' _ 'm' _ 'e' _ 'n' _ 't' _ ';', 9, 0, 0x2209 _ 0)
+NAMED_CHARACTER_REFERENCE(335, /* N o */ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 7, 0, 0x2260 _ 0)
+NAMED_CHARACTER_REFERENCE(336, /* N o */ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 12, 0, 0x2242 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(337, /* N o */ 't' _ 'E' _ 'x' _ 'i' _ 's' _ 't' _ 's' _ ';', 8, 0, 0x2204 _ 0)
+NAMED_CHARACTER_REFERENCE(338, /* N o */ 't' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ ';', 9, 0, 0x226f _ 0)
+NAMED_CHARACTER_REFERENCE(339, /* N o */ 't' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 14, 0, 0x2271 _ 0)
+NAMED_CHARACTER_REFERENCE(340, /* N o */ 't' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'F' _ 'u' _ 'l' _ 'l' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 18, 0, 0x2267 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(341, /* N o */ 't' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ ';', 16, 0, 0x226b _ 0x0338)
+NAMED_CHARACTER_REFERENCE(342, /* N o */ 't' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'L' _ 'e' _ 's' _ 's' _ ';', 13, 0, 0x2279 _ 0)
+NAMED_CHARACTER_REFERENCE(343, /* N o */ 't' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'S' _ 'l' _ 'a' _ 'n' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 19, 0, 0x2a7e _ 0x0338)
+NAMED_CHARACTER_REFERENCE(344, /* N o */ 't' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 14, 0, 0x2275 _ 0)
+NAMED_CHARACTER_REFERENCE(345, /* N o */ 't' _ 'H' _ 'u' _ 'm' _ 'p' _ 'D' _ 'o' _ 'w' _ 'n' _ 'H' _ 'u' _ 'm' _ 'p' _ ';', 14, 0, 0x224e _ 0x0338)
+NAMED_CHARACTER_REFERENCE(346, /* N o */ 't' _ 'H' _ 'u' _ 'm' _ 'p' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 11, 0, 0x224f _ 0x0338)
+NAMED_CHARACTER_REFERENCE(347, /* N o */ 't' _ 'L' _ 'e' _ 'f' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ ';', 14, 0, 0x22ea _ 0)
+NAMED_CHARACTER_REFERENCE(348, /* N o */ 't' _ 'L' _ 'e' _ 'f' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'B' _ 'a' _ 'r' _ ';', 17, 0, 0x29cf _ 0x0338)
+NAMED_CHARACTER_REFERENCE(349, /* N o */ 't' _ 'L' _ 'e' _ 'f' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 19, 0, 0x22ec _ 0)
+NAMED_CHARACTER_REFERENCE(350, /* N o */ 't' _ 'L' _ 'e' _ 's' _ 's' _ ';', 6, 0, 0x226e _ 0)
+NAMED_CHARACTER_REFERENCE(351, /* N o */ 't' _ 'L' _ 'e' _ 's' _ 's' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 11, 0, 0x2270 _ 0)
+NAMED_CHARACTER_REFERENCE(352, /* N o */ 't' _ 'L' _ 'e' _ 's' _ 's' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ ';', 13, 0, 0x2278 _ 0)
+NAMED_CHARACTER_REFERENCE(353, /* N o */ 't' _ 'L' _ 'e' _ 's' _ 's' _ 'L' _ 'e' _ 's' _ 's' _ ';', 10, 0, 0x226a _ 0x0338)
+NAMED_CHARACTER_REFERENCE(354, /* N o */ 't' _ 'L' _ 'e' _ 's' _ 's' _ 'S' _ 'l' _ 'a' _ 'n' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 16, 0, 0x2a7d _ 0x0338)
+NAMED_CHARACTER_REFERENCE(355, /* N o */ 't' _ 'L' _ 'e' _ 's' _ 's' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 11, 0, 0x2274 _ 0)
+NAMED_CHARACTER_REFERENCE(356, /* N o */ 't' _ 'N' _ 'e' _ 's' _ 't' _ 'e' _ 'd' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ 'G' _ 'r' _ 'e' _ 'a' _ 't' _ 'e' _ 'r' _ ';', 22, 0, 0x2aa2 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(357, /* N o */ 't' _ 'N' _ 'e' _ 's' _ 't' _ 'e' _ 'd' _ 'L' _ 'e' _ 's' _ 's' _ 'L' _ 'e' _ 's' _ 's' _ ';', 16, 0, 0x2aa1 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(358, /* N o */ 't' _ 'P' _ 'r' _ 'e' _ 'c' _ 'e' _ 'd' _ 'e' _ 's' _ ';', 10, 0, 0x2280 _ 0)
+NAMED_CHARACTER_REFERENCE(359, /* N o */ 't' _ 'P' _ 'r' _ 'e' _ 'c' _ 'e' _ 'd' _ 'e' _ 's' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 15, 0, 0x2aaf _ 0x0338)
+NAMED_CHARACTER_REFERENCE(360, /* N o */ 't' _ 'P' _ 'r' _ 'e' _ 'c' _ 'e' _ 'd' _ 'e' _ 's' _ 'S' _ 'l' _ 'a' _ 'n' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 20, 0, 0x22e0 _ 0)
+NAMED_CHARACTER_REFERENCE(361, /* N o */ 't' _ 'R' _ 'e' _ 'v' _ 'e' _ 'r' _ 's' _ 'e' _ 'E' _ 'l' _ 'e' _ 'm' _ 'e' _ 'n' _ 't' _ ';', 16, 0, 0x220c _ 0)
+NAMED_CHARACTER_REFERENCE(362, /* N o */ 't' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ ';', 15, 0, 0x22eb _ 0)
+NAMED_CHARACTER_REFERENCE(363, /* N o */ 't' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'B' _ 'a' _ 'r' _ ';', 18, 0, 0x29d0 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(364, /* N o */ 't' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 20, 0, 0x22ed _ 0)
+NAMED_CHARACTER_REFERENCE(365, /* N o */ 't' _ 'S' _ 'q' _ 'u' _ 'a' _ 'r' _ 'e' _ 'S' _ 'u' _ 'b' _ 's' _ 'e' _ 't' _ ';', 14, 0, 0x228f _ 0x0338)
+NAMED_CHARACTER_REFERENCE(366, /* N o */ 't' _ 'S' _ 'q' _ 'u' _ 'a' _ 'r' _ 'e' _ 'S' _ 'u' _ 'b' _ 's' _ 'e' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 19, 0, 0x22e2 _ 0)
+NAMED_CHARACTER_REFERENCE(367, /* N o */ 't' _ 'S' _ 'q' _ 'u' _ 'a' _ 'r' _ 'e' _ 'S' _ 'u' _ 'p' _ 'e' _ 'r' _ 's' _ 'e' _ 't' _ ';', 16, 0, 0x2290 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(368, /* N o */ 't' _ 'S' _ 'q' _ 'u' _ 'a' _ 'r' _ 'e' _ 'S' _ 'u' _ 'p' _ 'e' _ 'r' _ 's' _ 'e' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 21, 0, 0x22e3 _ 0)
+NAMED_CHARACTER_REFERENCE(369, /* N o */ 't' _ 'S' _ 'u' _ 'b' _ 's' _ 'e' _ 't' _ ';', 8, 0, 0x2282 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(370, /* N o */ 't' _ 'S' _ 'u' _ 'b' _ 's' _ 'e' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 13, 0, 0x2288 _ 0)
+NAMED_CHARACTER_REFERENCE(371, /* N o */ 't' _ 'S' _ 'u' _ 'c' _ 'c' _ 'e' _ 'e' _ 'd' _ 's' _ ';', 10, 0, 0x2281 _ 0)
+NAMED_CHARACTER_REFERENCE(372, /* N o */ 't' _ 'S' _ 'u' _ 'c' _ 'c' _ 'e' _ 'e' _ 'd' _ 's' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 15, 0, 0x2ab0 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(373, /* N o */ 't' _ 'S' _ 'u' _ 'c' _ 'c' _ 'e' _ 'e' _ 'd' _ 's' _ 'S' _ 'l' _ 'a' _ 'n' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 20, 0, 0x22e1 _ 0)
+NAMED_CHARACTER_REFERENCE(374, /* N o */ 't' _ 'S' _ 'u' _ 'c' _ 'c' _ 'e' _ 'e' _ 'd' _ 's' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 15, 0, 0x227f _ 0x0338)
+NAMED_CHARACTER_REFERENCE(375, /* N o */ 't' _ 'S' _ 'u' _ 'p' _ 'e' _ 'r' _ 's' _ 'e' _ 't' _ ';', 10, 0, 0x2283 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(376, /* N o */ 't' _ 'S' _ 'u' _ 'p' _ 'e' _ 'r' _ 's' _ 'e' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 15, 0, 0x2289 _ 0)
+NAMED_CHARACTER_REFERENCE(377, /* N o */ 't' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 7, 0, 0x2241 _ 0)
+NAMED_CHARACTER_REFERENCE(378, /* N o */ 't' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 12, 0, 0x2244 _ 0)
+NAMED_CHARACTER_REFERENCE(379, /* N o */ 't' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ 'F' _ 'u' _ 'l' _ 'l' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 16, 0, 0x2247 _ 0)
+NAMED_CHARACTER_REFERENCE(380, /* N o */ 't' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 12, 0, 0x2249 _ 0)
+NAMED_CHARACTER_REFERENCE(381, /* N o */ 't' _ 'V' _ 'e' _ 'r' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'B' _ 'a' _ 'r' _ ';', 13, 0, 0x2224 _ 0)
+NAMED_CHARACTER_REFERENCE(382, /* N s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdca9)
+NAMED_CHARACTER_REFERENCE(383, /* N t */ 'i' _ 'l' _ 'd' _ 'e', 4, 0, 0x00d1 _ 0)
+NAMED_CHARACTER_REFERENCE(384, /* N t */ 'i' _ 'l' _ 'd' _ 'e' _ ';', 5, 0, 0x00d1 _ 0)
+NAMED_CHARACTER_REFERENCE(385, /* N u */ ';', 1, 0, 0x039d _ 0)
+NAMED_CHARACTER_REFERENCE(386, /* O E */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0x0152 _ 0)
+NAMED_CHARACTER_REFERENCE(387, /* O a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00d3 _ 0)
+NAMED_CHARACTER_REFERENCE(388, /* O a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00d3 _ 0)
+NAMED_CHARACTER_REFERENCE(389, /* O c */ 'i' _ 'r' _ 'c', 3, 0, 0x00d4 _ 0)
+NAMED_CHARACTER_REFERENCE(390, /* O c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x00d4 _ 0)
+NAMED_CHARACTER_REFERENCE(391, /* O c */ 'y' _ ';', 2, 0, 0x041e _ 0)
+NAMED_CHARACTER_REFERENCE(392, /* O d */ 'b' _ 'l' _ 'a' _ 'c' _ ';', 5, 0, 0x0150 _ 0)
+NAMED_CHARACTER_REFERENCE(393, /* O f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd12)
+NAMED_CHARACTER_REFERENCE(394, /* O g */ 'r' _ 'a' _ 'v' _ 'e', 4, 0, 0x00d2 _ 0)
+NAMED_CHARACTER_REFERENCE(395, /* O g */ 'r' _ 'a' _ 'v' _ 'e' _ ';', 5, 0, 0x00d2 _ 0)
+NAMED_CHARACTER_REFERENCE(396, /* O m */ 'a' _ 'c' _ 'r' _ ';', 4, 0, 0x014c _ 0)
+NAMED_CHARACTER_REFERENCE(397, /* O m */ 'e' _ 'g' _ 'a' _ ';', 4, 0, 0x03a9 _ 0)
+NAMED_CHARACTER_REFERENCE(398, /* O m */ 'i' _ 'c' _ 'r' _ 'o' _ 'n' _ ';', 6, 0, 0x039f _ 0)
+NAMED_CHARACTER_REFERENCE(399, /* O o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd46)
+NAMED_CHARACTER_REFERENCE(400, /* O p */ 'e' _ 'n' _ 'C' _ 'u' _ 'r' _ 'l' _ 'y' _ 'D' _ 'o' _ 'u' _ 'b' _ 'l' _ 'e' _ 'Q' _ 'u' _ 'o' _ 't' _ 'e' _ ';', 19, 0, 0x201c _ 0)
+NAMED_CHARACTER_REFERENCE(401, /* O p */ 'e' _ 'n' _ 'C' _ 'u' _ 'r' _ 'l' _ 'y' _ 'Q' _ 'u' _ 'o' _ 't' _ 'e' _ ';', 13, 0, 0x2018 _ 0)
+NAMED_CHARACTER_REFERENCE(402, /* O r */ ';', 1, 0, 0x2a54 _ 0)
+NAMED_CHARACTER_REFERENCE(403, /* O s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcaa)
+NAMED_CHARACTER_REFERENCE(404, /* O s */ 'l' _ 'a' _ 's' _ 'h', 4, 0, 0x00d8 _ 0)
+NAMED_CHARACTER_REFERENCE(405, /* O s */ 'l' _ 'a' _ 's' _ 'h' _ ';', 5, 0, 0x00d8 _ 0)
+NAMED_CHARACTER_REFERENCE(406, /* O t */ 'i' _ 'l' _ 'd' _ 'e', 4, 0, 0x00d5 _ 0)
+NAMED_CHARACTER_REFERENCE(407, /* O t */ 'i' _ 'l' _ 'd' _ 'e' _ ';', 5, 0, 0x00d5 _ 0)
+NAMED_CHARACTER_REFERENCE(408, /* O t */ 'i' _ 'm' _ 'e' _ 's' _ ';', 5, 0, 0x2a37 _ 0)
+NAMED_CHARACTER_REFERENCE(409, /* O u */ 'm' _ 'l', 2, 0, 0x00d6 _ 0)
+NAMED_CHARACTER_REFERENCE(410, /* O u */ 'm' _ 'l' _ ';', 3, 0, 0x00d6 _ 0)
+NAMED_CHARACTER_REFERENCE(411, /* O v */ 'e' _ 'r' _ 'B' _ 'a' _ 'r' _ ';', 6, 0, 0x203e _ 0)
+NAMED_CHARACTER_REFERENCE(412, /* O v */ 'e' _ 'r' _ 'B' _ 'r' _ 'a' _ 'c' _ 'e' _ ';', 8, 0, 0x23de _ 0)
+NAMED_CHARACTER_REFERENCE(413, /* O v */ 'e' _ 'r' _ 'B' _ 'r' _ 'a' _ 'c' _ 'k' _ 'e' _ 't' _ ';', 10, 0, 0x23b4 _ 0)
+NAMED_CHARACTER_REFERENCE(414, /* O v */ 'e' _ 'r' _ 'P' _ 'a' _ 'r' _ 'e' _ 'n' _ 't' _ 'h' _ 'e' _ 's' _ 'i' _ 's' _ ';', 14, 0, 0x23dc _ 0)
+NAMED_CHARACTER_REFERENCE(415, /* P a */ 'r' _ 't' _ 'i' _ 'a' _ 'l' _ 'D' _ ';', 7, 0, 0x2202 _ 0)
+NAMED_CHARACTER_REFERENCE(416, /* P c */ 'y' _ ';', 2, 0, 0x041f _ 0)
+NAMED_CHARACTER_REFERENCE(417, /* P f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd13)
+NAMED_CHARACTER_REFERENCE(418, /* P h */ 'i' _ ';', 2, 0, 0x03a6 _ 0)
+NAMED_CHARACTER_REFERENCE(419, /* P i */ ';', 1, 0, 0x03a0 _ 0)
+NAMED_CHARACTER_REFERENCE(420, /* P l */ 'u' _ 's' _ 'M' _ 'i' _ 'n' _ 'u' _ 's' _ ';', 8, 0, 0x00b1 _ 0)
+NAMED_CHARACTER_REFERENCE(421, /* P o */ 'i' _ 'n' _ 'c' _ 'a' _ 'r' _ 'e' _ 'p' _ 'l' _ 'a' _ 'n' _ 'e' _ ';', 12, 0, 0x210c _ 0)
+NAMED_CHARACTER_REFERENCE(422, /* P o */ 'p' _ 'f' _ ';', 3, 0, 0x2119 _ 0)
+NAMED_CHARACTER_REFERENCE(423, /* P r */ ';', 1, 0, 0x2abb _ 0)
+NAMED_CHARACTER_REFERENCE(424, /* P r */ 'e' _ 'c' _ 'e' _ 'd' _ 'e' _ 's' _ ';', 7, 0, 0x227a _ 0)
+NAMED_CHARACTER_REFERENCE(425, /* P r */ 'e' _ 'c' _ 'e' _ 'd' _ 'e' _ 's' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 12, 0, 0x2aaf _ 0)
+NAMED_CHARACTER_REFERENCE(426, /* P r */ 'e' _ 'c' _ 'e' _ 'd' _ 'e' _ 's' _ 'S' _ 'l' _ 'a' _ 'n' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 17, 0, 0x227c _ 0)
+NAMED_CHARACTER_REFERENCE(427, /* P r */ 'e' _ 'c' _ 'e' _ 'd' _ 'e' _ 's' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 12, 0, 0x227e _ 0)
+NAMED_CHARACTER_REFERENCE(428, /* P r */ 'i' _ 'm' _ 'e' _ ';', 4, 0, 0x2033 _ 0)
+NAMED_CHARACTER_REFERENCE(429, /* P r */ 'o' _ 'd' _ 'u' _ 'c' _ 't' _ ';', 6, 0, 0x220f _ 0)
+NAMED_CHARACTER_REFERENCE(430, /* P r */ 'o' _ 'p' _ 'o' _ 'r' _ 't' _ 'i' _ 'o' _ 'n' _ ';', 9, 0, 0x2237 _ 0)
+NAMED_CHARACTER_REFERENCE(431, /* P r */ 'o' _ 'p' _ 'o' _ 'r' _ 't' _ 'i' _ 'o' _ 'n' _ 'a' _ 'l' _ ';', 11, 0, 0x221d _ 0)
+NAMED_CHARACTER_REFERENCE(432, /* P s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcab)
+NAMED_CHARACTER_REFERENCE(433, /* P s */ 'i' _ ';', 2, 0, 0x03a8 _ 0)
+NAMED_CHARACTER_REFERENCE(434, /* Q U */ 'O' _ 'T', 2, 0, 0x0022 _ 0)
+NAMED_CHARACTER_REFERENCE(435, /* Q U */ 'O' _ 'T' _ ';', 3, 0, 0x0022 _ 0)
+NAMED_CHARACTER_REFERENCE(436, /* Q f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd14)
+NAMED_CHARACTER_REFERENCE(437, /* Q o */ 'p' _ 'f' _ ';', 3, 0, 0x211a _ 0)
+NAMED_CHARACTER_REFERENCE(438, /* Q s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcac)
+NAMED_CHARACTER_REFERENCE(439, /* R B */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x2910 _ 0)
+NAMED_CHARACTER_REFERENCE(440, /* R E */ 'G', 1, 0, 0x00ae _ 0)
+NAMED_CHARACTER_REFERENCE(441, /* R E */ 'G' _ ';', 2, 0, 0x00ae _ 0)
+NAMED_CHARACTER_REFERENCE(442, /* R a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x0154 _ 0)
+NAMED_CHARACTER_REFERENCE(443, /* R a */ 'n' _ 'g' _ ';', 3, 0, 0x27eb _ 0)
+NAMED_CHARACTER_REFERENCE(444, /* R a */ 'r' _ 'r' _ ';', 3, 0, 0x21a0 _ 0)
+NAMED_CHARACTER_REFERENCE(445, /* R a */ 'r' _ 'r' _ 't' _ 'l' _ ';', 5, 0, 0x2916 _ 0)
+NAMED_CHARACTER_REFERENCE(446, /* R c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x0158 _ 0)
+NAMED_CHARACTER_REFERENCE(447, /* R c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x0156 _ 0)
+NAMED_CHARACTER_REFERENCE(448, /* R c */ 'y' _ ';', 2, 0, 0x0420 _ 0)
+NAMED_CHARACTER_REFERENCE(449, /* R e */ ';', 1, 0, 0x211c _ 0)
+NAMED_CHARACTER_REFERENCE(450, /* R e */ 'v' _ 'e' _ 'r' _ 's' _ 'e' _ 'E' _ 'l' _ 'e' _ 'm' _ 'e' _ 'n' _ 't' _ ';', 13, 0, 0x220b _ 0)
+NAMED_CHARACTER_REFERENCE(451, /* R e */ 'v' _ 'e' _ 'r' _ 's' _ 'e' _ 'E' _ 'q' _ 'u' _ 'i' _ 'l' _ 'i' _ 'b' _ 'r' _ 'i' _ 'u' _ 'm' _ ';', 17, 0, 0x21cb _ 0)
+NAMED_CHARACTER_REFERENCE(452, /* R e */ 'v' _ 'e' _ 'r' _ 's' _ 'e' _ 'U' _ 'p' _ 'E' _ 'q' _ 'u' _ 'i' _ 'l' _ 'i' _ 'b' _ 'r' _ 'i' _ 'u' _ 'm' _ ';', 19, 0, 0x296f _ 0)
+NAMED_CHARACTER_REFERENCE(453, /* R f */ 'r' _ ';', 2, 0, 0x211c _ 0)
+NAMED_CHARACTER_REFERENCE(454, /* R h */ 'o' _ ';', 2, 0, 0x03a1 _ 0)
+NAMED_CHARACTER_REFERENCE(455, /* R i */ 'g' _ 'h' _ 't' _ 'A' _ 'n' _ 'g' _ 'l' _ 'e' _ 'B' _ 'r' _ 'a' _ 'c' _ 'k' _ 'e' _ 't' _ ';', 16, 0, 0x27e9 _ 0)
+NAMED_CHARACTER_REFERENCE(456, /* R i */ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 9, 0, 0x2192 _ 0)
+NAMED_CHARACTER_REFERENCE(457, /* R i */ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ 'B' _ 'a' _ 'r' _ ';', 12, 0, 0x21e5 _ 0)
+NAMED_CHARACTER_REFERENCE(458, /* R i */ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ 'L' _ 'e' _ 'f' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 18, 0, 0x21c4 _ 0)
+NAMED_CHARACTER_REFERENCE(459, /* R i */ 'g' _ 'h' _ 't' _ 'C' _ 'e' _ 'i' _ 'l' _ 'i' _ 'n' _ 'g' _ ';', 11, 0, 0x2309 _ 0)
+NAMED_CHARACTER_REFERENCE(460, /* R i */ 'g' _ 'h' _ 't' _ 'D' _ 'o' _ 'u' _ 'b' _ 'l' _ 'e' _ 'B' _ 'r' _ 'a' _ 'c' _ 'k' _ 'e' _ 't' _ ';', 17, 0, 0x27e7 _ 0)
+NAMED_CHARACTER_REFERENCE(461, /* R i */ 'g' _ 'h' _ 't' _ 'D' _ 'o' _ 'w' _ 'n' _ 'T' _ 'e' _ 'e' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 17, 0, 0x295d _ 0)
+NAMED_CHARACTER_REFERENCE(462, /* R i */ 'g' _ 'h' _ 't' _ 'D' _ 'o' _ 'w' _ 'n' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 14, 0, 0x21c2 _ 0)
+NAMED_CHARACTER_REFERENCE(463, /* R i */ 'g' _ 'h' _ 't' _ 'D' _ 'o' _ 'w' _ 'n' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ 'B' _ 'a' _ 'r' _ ';', 17, 0, 0x2955 _ 0)
+NAMED_CHARACTER_REFERENCE(464, /* R i */ 'g' _ 'h' _ 't' _ 'F' _ 'l' _ 'o' _ 'o' _ 'r' _ ';', 9, 0, 0x230b _ 0)
+NAMED_CHARACTER_REFERENCE(465, /* R i */ 'g' _ 'h' _ 't' _ 'T' _ 'e' _ 'e' _ ';', 7, 0, 0x22a2 _ 0)
+NAMED_CHARACTER_REFERENCE(466, /* R i */ 'g' _ 'h' _ 't' _ 'T' _ 'e' _ 'e' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 12, 0, 0x21a6 _ 0)
+NAMED_CHARACTER_REFERENCE(467, /* R i */ 'g' _ 'h' _ 't' _ 'T' _ 'e' _ 'e' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 13, 0, 0x295b _ 0)
+NAMED_CHARACTER_REFERENCE(468, /* R i */ 'g' _ 'h' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ ';', 12, 0, 0x22b3 _ 0)
+NAMED_CHARACTER_REFERENCE(469, /* R i */ 'g' _ 'h' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'B' _ 'a' _ 'r' _ ';', 15, 0, 0x29d0 _ 0)
+NAMED_CHARACTER_REFERENCE(470, /* R i */ 'g' _ 'h' _ 't' _ 'T' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 17, 0, 0x22b5 _ 0)
+NAMED_CHARACTER_REFERENCE(471, /* R i */ 'g' _ 'h' _ 't' _ 'U' _ 'p' _ 'D' _ 'o' _ 'w' _ 'n' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 16, 0, 0x294f _ 0)
+NAMED_CHARACTER_REFERENCE(472, /* R i */ 'g' _ 'h' _ 't' _ 'U' _ 'p' _ 'T' _ 'e' _ 'e' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 15, 0, 0x295c _ 0)
+NAMED_CHARACTER_REFERENCE(473, /* R i */ 'g' _ 'h' _ 't' _ 'U' _ 'p' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 12, 0, 0x21be _ 0)
+NAMED_CHARACTER_REFERENCE(474, /* R i */ 'g' _ 'h' _ 't' _ 'U' _ 'p' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ 'B' _ 'a' _ 'r' _ ';', 15, 0, 0x2954 _ 0)
+NAMED_CHARACTER_REFERENCE(475, /* R i */ 'g' _ 'h' _ 't' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ ';', 10, 0, 0x21c0 _ 0)
+NAMED_CHARACTER_REFERENCE(476, /* R i */ 'g' _ 'h' _ 't' _ 'V' _ 'e' _ 'c' _ 't' _ 'o' _ 'r' _ 'B' _ 'a' _ 'r' _ ';', 13, 0, 0x2953 _ 0)
+NAMED_CHARACTER_REFERENCE(477, /* R i */ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 9, 0, 0x21d2 _ 0)
+NAMED_CHARACTER_REFERENCE(478, /* R o */ 'p' _ 'f' _ ';', 3, 0, 0x211d _ 0)
+NAMED_CHARACTER_REFERENCE(479, /* R o */ 'u' _ 'n' _ 'd' _ 'I' _ 'm' _ 'p' _ 'l' _ 'i' _ 'e' _ 's' _ ';', 11, 0, 0x2970 _ 0)
+NAMED_CHARACTER_REFERENCE(480, /* R r */ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 10, 0, 0x21db _ 0)
+NAMED_CHARACTER_REFERENCE(481, /* R s */ 'c' _ 'r' _ ';', 3, 0, 0x211b _ 0)
+NAMED_CHARACTER_REFERENCE(482, /* R s */ 'h' _ ';', 2, 0, 0x21b1 _ 0)
+NAMED_CHARACTER_REFERENCE(483, /* R u */ 'l' _ 'e' _ 'D' _ 'e' _ 'l' _ 'a' _ 'y' _ 'e' _ 'd' _ ';', 10, 0, 0x29f4 _ 0)
+NAMED_CHARACTER_REFERENCE(484, /* S H */ 'C' _ 'H' _ 'c' _ 'y' _ ';', 5, 0, 0x0429 _ 0)
+NAMED_CHARACTER_REFERENCE(485, /* S H */ 'c' _ 'y' _ ';', 3, 0, 0x0428 _ 0)
+NAMED_CHARACTER_REFERENCE(486, /* S O */ 'F' _ 'T' _ 'c' _ 'y' _ ';', 5, 0, 0x042c _ 0)
+NAMED_CHARACTER_REFERENCE(487, /* S a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x015a _ 0)
+NAMED_CHARACTER_REFERENCE(488, /* S c */ ';', 1, 0, 0x2abc _ 0)
+NAMED_CHARACTER_REFERENCE(489, /* S c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x0160 _ 0)
+NAMED_CHARACTER_REFERENCE(490, /* S c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x015e _ 0)
+NAMED_CHARACTER_REFERENCE(491, /* S c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x015c _ 0)
+NAMED_CHARACTER_REFERENCE(492, /* S c */ 'y' _ ';', 2, 0, 0x0421 _ 0)
+NAMED_CHARACTER_REFERENCE(493, /* S f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd16)
+NAMED_CHARACTER_REFERENCE(494, /* S h */ 'o' _ 'r' _ 't' _ 'D' _ 'o' _ 'w' _ 'n' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x2193 _ 0)
+NAMED_CHARACTER_REFERENCE(495, /* S h */ 'o' _ 'r' _ 't' _ 'L' _ 'e' _ 'f' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x2190 _ 0)
+NAMED_CHARACTER_REFERENCE(496, /* S h */ 'o' _ 'r' _ 't' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 14, 0, 0x2192 _ 0)
+NAMED_CHARACTER_REFERENCE(497, /* S h */ 'o' _ 'r' _ 't' _ 'U' _ 'p' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 11, 0, 0x2191 _ 0)
+NAMED_CHARACTER_REFERENCE(498, /* S i */ 'g' _ 'm' _ 'a' _ ';', 4, 0, 0x03a3 _ 0)
+NAMED_CHARACTER_REFERENCE(499, /* S m */ 'a' _ 'l' _ 'l' _ 'C' _ 'i' _ 'r' _ 'c' _ 'l' _ 'e' _ ';', 10, 0, 0x2218 _ 0)
+NAMED_CHARACTER_REFERENCE(500, /* S o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd4a)
+NAMED_CHARACTER_REFERENCE(501, /* S q */ 'r' _ 't' _ ';', 3, 0, 0x221a _ 0)
+NAMED_CHARACTER_REFERENCE(502, /* S q */ 'u' _ 'a' _ 'r' _ 'e' _ ';', 5, 0, 0x25a1 _ 0)
+NAMED_CHARACTER_REFERENCE(503, /* S q */ 'u' _ 'a' _ 'r' _ 'e' _ 'I' _ 'n' _ 't' _ 'e' _ 'r' _ 's' _ 'e' _ 'c' _ 't' _ 'i' _ 'o' _ 'n' _ ';', 17, 0, 0x2293 _ 0)
+NAMED_CHARACTER_REFERENCE(504, /* S q */ 'u' _ 'a' _ 'r' _ 'e' _ 'S' _ 'u' _ 'b' _ 's' _ 'e' _ 't' _ ';', 11, 0, 0x228f _ 0)
+NAMED_CHARACTER_REFERENCE(505, /* S q */ 'u' _ 'a' _ 'r' _ 'e' _ 'S' _ 'u' _ 'b' _ 's' _ 'e' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 16, 0, 0x2291 _ 0)
+NAMED_CHARACTER_REFERENCE(506, /* S q */ 'u' _ 'a' _ 'r' _ 'e' _ 'S' _ 'u' _ 'p' _ 'e' _ 'r' _ 's' _ 'e' _ 't' _ ';', 13, 0, 0x2290 _ 0)
+NAMED_CHARACTER_REFERENCE(507, /* S q */ 'u' _ 'a' _ 'r' _ 'e' _ 'S' _ 'u' _ 'p' _ 'e' _ 'r' _ 's' _ 'e' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 18, 0, 0x2292 _ 0)
+NAMED_CHARACTER_REFERENCE(508, /* S q */ 'u' _ 'a' _ 'r' _ 'e' _ 'U' _ 'n' _ 'i' _ 'o' _ 'n' _ ';', 10, 0, 0x2294 _ 0)
+NAMED_CHARACTER_REFERENCE(509, /* S s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcae)
+NAMED_CHARACTER_REFERENCE(510, /* S t */ 'a' _ 'r' _ ';', 3, 0, 0x22c6 _ 0)
+NAMED_CHARACTER_REFERENCE(511, /* S u */ 'b' _ ';', 2, 0, 0x22d0 _ 0)
+NAMED_CHARACTER_REFERENCE(512, /* S u */ 'b' _ 's' _ 'e' _ 't' _ ';', 5, 0, 0x22d0 _ 0)
+NAMED_CHARACTER_REFERENCE(513, /* S u */ 'b' _ 's' _ 'e' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 10, 0, 0x2286 _ 0)
+NAMED_CHARACTER_REFERENCE(514, /* S u */ 'c' _ 'c' _ 'e' _ 'e' _ 'd' _ 's' _ ';', 7, 0, 0x227b _ 0)
+NAMED_CHARACTER_REFERENCE(515, /* S u */ 'c' _ 'c' _ 'e' _ 'e' _ 'd' _ 's' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 12, 0, 0x2ab0 _ 0)
+NAMED_CHARACTER_REFERENCE(516, /* S u */ 'c' _ 'c' _ 'e' _ 'e' _ 'd' _ 's' _ 'S' _ 'l' _ 'a' _ 'n' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 17, 0, 0x227d _ 0)
+NAMED_CHARACTER_REFERENCE(517, /* S u */ 'c' _ 'c' _ 'e' _ 'e' _ 'd' _ 's' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 12, 0, 0x227f _ 0)
+NAMED_CHARACTER_REFERENCE(518, /* S u */ 'c' _ 'h' _ 'T' _ 'h' _ 'a' _ 't' _ ';', 7, 0, 0x220b _ 0)
+NAMED_CHARACTER_REFERENCE(519, /* S u */ 'm' _ ';', 2, 0, 0x2211 _ 0)
+NAMED_CHARACTER_REFERENCE(520, /* S u */ 'p' _ ';', 2, 0, 0x22d1 _ 0)
+NAMED_CHARACTER_REFERENCE(521, /* S u */ 'p' _ 'e' _ 'r' _ 's' _ 'e' _ 't' _ ';', 7, 0, 0x2283 _ 0)
+NAMED_CHARACTER_REFERENCE(522, /* S u */ 'p' _ 'e' _ 'r' _ 's' _ 'e' _ 't' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 12, 0, 0x2287 _ 0)
+NAMED_CHARACTER_REFERENCE(523, /* S u */ 'p' _ 's' _ 'e' _ 't' _ ';', 5, 0, 0x22d1 _ 0)
+NAMED_CHARACTER_REFERENCE(524, /* T H */ 'O' _ 'R' _ 'N', 3, 0, 0x00de _ 0)
+NAMED_CHARACTER_REFERENCE(525, /* T H */ 'O' _ 'R' _ 'N' _ ';', 4, 0, 0x00de _ 0)
+NAMED_CHARACTER_REFERENCE(526, /* T R */ 'A' _ 'D' _ 'E' _ ';', 4, 0, 0x2122 _ 0)
+NAMED_CHARACTER_REFERENCE(527, /* T S */ 'H' _ 'c' _ 'y' _ ';', 4, 0, 0x040b _ 0)
+NAMED_CHARACTER_REFERENCE(528, /* T S */ 'c' _ 'y' _ ';', 3, 0, 0x0426 _ 0)
+NAMED_CHARACTER_REFERENCE(529, /* T a */ 'b' _ ';', 2, 0, 0x0009 _ 0)
+NAMED_CHARACTER_REFERENCE(530, /* T a */ 'u' _ ';', 2, 0, 0x03a4 _ 0)
+NAMED_CHARACTER_REFERENCE(531, /* T c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x0164 _ 0)
+NAMED_CHARACTER_REFERENCE(532, /* T c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x0162 _ 0)
+NAMED_CHARACTER_REFERENCE(533, /* T c */ 'y' _ ';', 2, 0, 0x0422 _ 0)
+NAMED_CHARACTER_REFERENCE(534, /* T f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd17)
+NAMED_CHARACTER_REFERENCE(535, /* T h */ 'e' _ 'r' _ 'e' _ 'f' _ 'o' _ 'r' _ 'e' _ ';', 8, 0, 0x2234 _ 0)
+NAMED_CHARACTER_REFERENCE(536, /* T h */ 'e' _ 't' _ 'a' _ ';', 4, 0, 0x0398 _ 0)
+NAMED_CHARACTER_REFERENCE(537, /* T h */ 'i' _ 'c' _ 'k' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 9, 0, 0x205f _ 0x200a)
+NAMED_CHARACTER_REFERENCE(538, /* T h */ 'i' _ 'n' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 8, 0, 0x2009 _ 0)
+NAMED_CHARACTER_REFERENCE(539, /* T i */ 'l' _ 'd' _ 'e' _ ';', 4, 0, 0x223c _ 0)
+NAMED_CHARACTER_REFERENCE(540, /* T i */ 'l' _ 'd' _ 'e' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 9, 0, 0x2243 _ 0)
+NAMED_CHARACTER_REFERENCE(541, /* T i */ 'l' _ 'd' _ 'e' _ 'F' _ 'u' _ 'l' _ 'l' _ 'E' _ 'q' _ 'u' _ 'a' _ 'l' _ ';', 13, 0, 0x2245 _ 0)
+NAMED_CHARACTER_REFERENCE(542, /* T i */ 'l' _ 'd' _ 'e' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 9, 0, 0x2248 _ 0)
+NAMED_CHARACTER_REFERENCE(543, /* T o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd4b)
+NAMED_CHARACTER_REFERENCE(544, /* T r */ 'i' _ 'p' _ 'l' _ 'e' _ 'D' _ 'o' _ 't' _ ';', 8, 0, 0x20db _ 0)
+NAMED_CHARACTER_REFERENCE(545, /* T s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcaf)
+NAMED_CHARACTER_REFERENCE(546, /* T s */ 't' _ 'r' _ 'o' _ 'k' _ ';', 5, 0, 0x0166 _ 0)
+NAMED_CHARACTER_REFERENCE(547, /* U a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00da _ 0)
+NAMED_CHARACTER_REFERENCE(548, /* U a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00da _ 0)
+NAMED_CHARACTER_REFERENCE(549, /* U a */ 'r' _ 'r' _ ';', 3, 0, 0x219f _ 0)
+NAMED_CHARACTER_REFERENCE(550, /* U a */ 'r' _ 'r' _ 'o' _ 'c' _ 'i' _ 'r' _ ';', 7, 0, 0x2949 _ 0)
+NAMED_CHARACTER_REFERENCE(551, /* U b */ 'r' _ 'c' _ 'y' _ ';', 4, 0, 0x040e _ 0)
+NAMED_CHARACTER_REFERENCE(552, /* U b */ 'r' _ 'e' _ 'v' _ 'e' _ ';', 5, 0, 0x016c _ 0)
+NAMED_CHARACTER_REFERENCE(553, /* U c */ 'i' _ 'r' _ 'c', 3, 0, 0x00db _ 0)
+NAMED_CHARACTER_REFERENCE(554, /* U c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x00db _ 0)
+NAMED_CHARACTER_REFERENCE(555, /* U c */ 'y' _ ';', 2, 0, 0x0423 _ 0)
+NAMED_CHARACTER_REFERENCE(556, /* U d */ 'b' _ 'l' _ 'a' _ 'c' _ ';', 5, 0, 0x0170 _ 0)
+NAMED_CHARACTER_REFERENCE(557, /* U f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd18)
+NAMED_CHARACTER_REFERENCE(558, /* U g */ 'r' _ 'a' _ 'v' _ 'e', 4, 0, 0x00d9 _ 0)
+NAMED_CHARACTER_REFERENCE(559, /* U g */ 'r' _ 'a' _ 'v' _ 'e' _ ';', 5, 0, 0x00d9 _ 0)
+NAMED_CHARACTER_REFERENCE(560, /* U m */ 'a' _ 'c' _ 'r' _ ';', 4, 0, 0x016a _ 0)
+NAMED_CHARACTER_REFERENCE(561, /* U n */ 'd' _ 'e' _ 'r' _ 'B' _ 'a' _ 'r' _ ';', 7, 0, 0x005f _ 0)
+NAMED_CHARACTER_REFERENCE(562, /* U n */ 'd' _ 'e' _ 'r' _ 'B' _ 'r' _ 'a' _ 'c' _ 'e' _ ';', 9, 0, 0x23df _ 0)
+NAMED_CHARACTER_REFERENCE(563, /* U n */ 'd' _ 'e' _ 'r' _ 'B' _ 'r' _ 'a' _ 'c' _ 'k' _ 'e' _ 't' _ ';', 11, 0, 0x23b5 _ 0)
+NAMED_CHARACTER_REFERENCE(564, /* U n */ 'd' _ 'e' _ 'r' _ 'P' _ 'a' _ 'r' _ 'e' _ 'n' _ 't' _ 'h' _ 'e' _ 's' _ 'i' _ 's' _ ';', 15, 0, 0x23dd _ 0)
+NAMED_CHARACTER_REFERENCE(565, /* U n */ 'i' _ 'o' _ 'n' _ ';', 4, 0, 0x22c3 _ 0)
+NAMED_CHARACTER_REFERENCE(566, /* U n */ 'i' _ 'o' _ 'n' _ 'P' _ 'l' _ 'u' _ 's' _ ';', 8, 0, 0x228e _ 0)
+NAMED_CHARACTER_REFERENCE(567, /* U o */ 'g' _ 'o' _ 'n' _ ';', 4, 0, 0x0172 _ 0)
+NAMED_CHARACTER_REFERENCE(568, /* U o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd4c)
+NAMED_CHARACTER_REFERENCE(569, /* U p */ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 6, 0, 0x2191 _ 0)
+NAMED_CHARACTER_REFERENCE(570, /* U p */ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ 'B' _ 'a' _ 'r' _ ';', 9, 0, 0x2912 _ 0)
+NAMED_CHARACTER_REFERENCE(571, /* U p */ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ 'D' _ 'o' _ 'w' _ 'n' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 15, 0, 0x21c5 _ 0)
+NAMED_CHARACTER_REFERENCE(572, /* U p */ 'D' _ 'o' _ 'w' _ 'n' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 10, 0, 0x2195 _ 0)
+NAMED_CHARACTER_REFERENCE(573, /* U p */ 'E' _ 'q' _ 'u' _ 'i' _ 'l' _ 'i' _ 'b' _ 'r' _ 'i' _ 'u' _ 'm' _ ';', 12, 0, 0x296e _ 0)
+NAMED_CHARACTER_REFERENCE(574, /* U p */ 'T' _ 'e' _ 'e' _ ';', 4, 0, 0x22a5 _ 0)
+NAMED_CHARACTER_REFERENCE(575, /* U p */ 'T' _ 'e' _ 'e' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 9, 0, 0x21a5 _ 0)
+NAMED_CHARACTER_REFERENCE(576, /* U p */ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 6, 0, 0x21d1 _ 0)
+NAMED_CHARACTER_REFERENCE(577, /* U p */ 'd' _ 'o' _ 'w' _ 'n' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 10, 0, 0x21d5 _ 0)
+NAMED_CHARACTER_REFERENCE(578, /* U p */ 'p' _ 'e' _ 'r' _ 'L' _ 'e' _ 'f' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x2196 _ 0)
+NAMED_CHARACTER_REFERENCE(579, /* U p */ 'p' _ 'e' _ 'r' _ 'R' _ 'i' _ 'g' _ 'h' _ 't' _ 'A' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 14, 0, 0x2197 _ 0)
+NAMED_CHARACTER_REFERENCE(580, /* U p */ 's' _ 'i' _ ';', 3, 0, 0x03d2 _ 0)
+NAMED_CHARACTER_REFERENCE(581, /* U p */ 's' _ 'i' _ 'l' _ 'o' _ 'n' _ ';', 6, 0, 0x03a5 _ 0)
+NAMED_CHARACTER_REFERENCE(582, /* U r */ 'i' _ 'n' _ 'g' _ ';', 4, 0, 0x016e _ 0)
+NAMED_CHARACTER_REFERENCE(583, /* U s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcb0)
+NAMED_CHARACTER_REFERENCE(584, /* U t */ 'i' _ 'l' _ 'd' _ 'e' _ ';', 5, 0, 0x0168 _ 0)
+NAMED_CHARACTER_REFERENCE(585, /* U u */ 'm' _ 'l', 2, 0, 0x00dc _ 0)
+NAMED_CHARACTER_REFERENCE(586, /* U u */ 'm' _ 'l' _ ';', 3, 0, 0x00dc _ 0)
+NAMED_CHARACTER_REFERENCE(587, /* V D */ 'a' _ 's' _ 'h' _ ';', 4, 0, 0x22ab _ 0)
+NAMED_CHARACTER_REFERENCE(588, /* V b */ 'a' _ 'r' _ ';', 3, 0, 0x2aeb _ 0)
+NAMED_CHARACTER_REFERENCE(589, /* V c */ 'y' _ ';', 2, 0, 0x0412 _ 0)
+NAMED_CHARACTER_REFERENCE(590, /* V d */ 'a' _ 's' _ 'h' _ ';', 4, 0, 0x22a9 _ 0)
+NAMED_CHARACTER_REFERENCE(591, /* V d */ 'a' _ 's' _ 'h' _ 'l' _ ';', 5, 0, 0x2ae6 _ 0)
+NAMED_CHARACTER_REFERENCE(592, /* V e */ 'e' _ ';', 2, 0, 0x22c1 _ 0)
+NAMED_CHARACTER_REFERENCE(593, /* V e */ 'r' _ 'b' _ 'a' _ 'r' _ ';', 5, 0, 0x2016 _ 0)
+NAMED_CHARACTER_REFERENCE(594, /* V e */ 'r' _ 't' _ ';', 3, 0, 0x2016 _ 0)
+NAMED_CHARACTER_REFERENCE(595, /* V e */ 'r' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'B' _ 'a' _ 'r' _ ';', 10, 0, 0x2223 _ 0)
+NAMED_CHARACTER_REFERENCE(596, /* V e */ 'r' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'L' _ 'i' _ 'n' _ 'e' _ ';', 11, 0, 0x007c _ 0)
+NAMED_CHARACTER_REFERENCE(597, /* V e */ 'r' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'S' _ 'e' _ 'p' _ 'a' _ 'r' _ 'a' _ 't' _ 'o' _ 'r' _ ';', 16, 0, 0x2758 _ 0)
+NAMED_CHARACTER_REFERENCE(598, /* V e */ 'r' _ 't' _ 'i' _ 'c' _ 'a' _ 'l' _ 'T' _ 'i' _ 'l' _ 'd' _ 'e' _ ';', 12, 0, 0x2240 _ 0)
+NAMED_CHARACTER_REFERENCE(599, /* V e */ 'r' _ 'y' _ 'T' _ 'h' _ 'i' _ 'n' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 12, 0, 0x200a _ 0)
+NAMED_CHARACTER_REFERENCE(600, /* V f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd19)
+NAMED_CHARACTER_REFERENCE(601, /* V o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd4d)
+NAMED_CHARACTER_REFERENCE(602, /* V s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcb1)
+NAMED_CHARACTER_REFERENCE(603, /* V v */ 'd' _ 'a' _ 's' _ 'h' _ ';', 5, 0, 0x22aa _ 0)
+NAMED_CHARACTER_REFERENCE(604, /* W c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x0174 _ 0)
+NAMED_CHARACTER_REFERENCE(605, /* W e */ 'd' _ 'g' _ 'e' _ ';', 4, 0, 0x22c0 _ 0)
+NAMED_CHARACTER_REFERENCE(606, /* W f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd1a)
+NAMED_CHARACTER_REFERENCE(607, /* W o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd4e)
+NAMED_CHARACTER_REFERENCE(608, /* W s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcb2)
+NAMED_CHARACTER_REFERENCE(609, /* X f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd1b)
+NAMED_CHARACTER_REFERENCE(610, /* X i */ ';', 1, 0, 0x039e _ 0)
+NAMED_CHARACTER_REFERENCE(611, /* X o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd4f)
+NAMED_CHARACTER_REFERENCE(612, /* X s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcb3)
+NAMED_CHARACTER_REFERENCE(613, /* Y A */ 'c' _ 'y' _ ';', 3, 0, 0x042f _ 0)
+NAMED_CHARACTER_REFERENCE(614, /* Y I */ 'c' _ 'y' _ ';', 3, 0, 0x0407 _ 0)
+NAMED_CHARACTER_REFERENCE(615, /* Y U */ 'c' _ 'y' _ ';', 3, 0, 0x042e _ 0)
+NAMED_CHARACTER_REFERENCE(616, /* Y a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00dd _ 0)
+NAMED_CHARACTER_REFERENCE(617, /* Y a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00dd _ 0)
+NAMED_CHARACTER_REFERENCE(618, /* Y c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x0176 _ 0)
+NAMED_CHARACTER_REFERENCE(619, /* Y c */ 'y' _ ';', 2, 0, 0x042b _ 0)
+NAMED_CHARACTER_REFERENCE(620, /* Y f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd1c)
+NAMED_CHARACTER_REFERENCE(621, /* Y o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd50)
+NAMED_CHARACTER_REFERENCE(622, /* Y s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcb4)
+NAMED_CHARACTER_REFERENCE(623, /* Y u */ 'm' _ 'l' _ ';', 3, 0, 0x0178 _ 0)
+NAMED_CHARACTER_REFERENCE(624, /* Z H */ 'c' _ 'y' _ ';', 3, 0, 0x0416 _ 0)
+NAMED_CHARACTER_REFERENCE(625, /* Z a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x0179 _ 0)
+NAMED_CHARACTER_REFERENCE(626, /* Z c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x017d _ 0)
+NAMED_CHARACTER_REFERENCE(627, /* Z c */ 'y' _ ';', 2, 0, 0x0417 _ 0)
+NAMED_CHARACTER_REFERENCE(628, /* Z d */ 'o' _ 't' _ ';', 3, 0, 0x017b _ 0)
+NAMED_CHARACTER_REFERENCE(629, /* Z e */ 'r' _ 'o' _ 'W' _ 'i' _ 'd' _ 't' _ 'h' _ 'S' _ 'p' _ 'a' _ 'c' _ 'e' _ ';', 13, 0, 0x200b _ 0)
+NAMED_CHARACTER_REFERENCE(630, /* Z e */ 't' _ 'a' _ ';', 3, 0, 0x0396 _ 0)
+NAMED_CHARACTER_REFERENCE(631, /* Z f */ 'r' _ ';', 2, 0, 0x2128 _ 0)
+NAMED_CHARACTER_REFERENCE(632, /* Z o */ 'p' _ 'f' _ ';', 3, 0, 0x2124 _ 0)
+NAMED_CHARACTER_REFERENCE(633, /* Z s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcb5)
+NAMED_CHARACTER_REFERENCE(634, /* a a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00e1 _ 0)
+NAMED_CHARACTER_REFERENCE(635, /* a a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00e1 _ 0)
+NAMED_CHARACTER_REFERENCE(636, /* a b */ 'r' _ 'e' _ 'v' _ 'e' _ ';', 5, 0, 0x0103 _ 0)
+NAMED_CHARACTER_REFERENCE(637, /* a c */ ';', 1, 0, 0x223e _ 0)
+NAMED_CHARACTER_REFERENCE(638, /* a c */ 'E' _ ';', 2, 0, 0x223e _ 0x0333)
+NAMED_CHARACTER_REFERENCE(639, /* a c */ 'd' _ ';', 2, 0, 0x223f _ 0)
+NAMED_CHARACTER_REFERENCE(640, /* a c */ 'i' _ 'r' _ 'c', 3, 0, 0x00e2 _ 0)
+NAMED_CHARACTER_REFERENCE(641, /* a c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x00e2 _ 0)
+NAMED_CHARACTER_REFERENCE(642, /* a c */ 'u' _ 't' _ 'e', 3, 0, 0x00b4 _ 0)
+NAMED_CHARACTER_REFERENCE(643, /* a c */ 'u' _ 't' _ 'e' _ ';', 4, 0, 0x00b4 _ 0)
+NAMED_CHARACTER_REFERENCE(644, /* a c */ 'y' _ ';', 2, 0, 0x0430 _ 0)
+NAMED_CHARACTER_REFERENCE(645, /* a e */ 'l' _ 'i' _ 'g', 3, 0, 0x00e6 _ 0)
+NAMED_CHARACTER_REFERENCE(646, /* a e */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0x00e6 _ 0)
+NAMED_CHARACTER_REFERENCE(647, /* a f */ ';', 1, 0, 0x2061 _ 0)
+NAMED_CHARACTER_REFERENCE(648, /* a f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd1e)
+NAMED_CHARACTER_REFERENCE(649, /* a g */ 'r' _ 'a' _ 'v' _ 'e', 4, 0, 0x00e0 _ 0)
+NAMED_CHARACTER_REFERENCE(650, /* a g */ 'r' _ 'a' _ 'v' _ 'e' _ ';', 5, 0, 0x00e0 _ 0)
+NAMED_CHARACTER_REFERENCE(651, /* a l */ 'e' _ 'f' _ 's' _ 'y' _ 'm' _ ';', 6, 0, 0x2135 _ 0)
+NAMED_CHARACTER_REFERENCE(652, /* a l */ 'e' _ 'p' _ 'h' _ ';', 4, 0, 0x2135 _ 0)
+NAMED_CHARACTER_REFERENCE(653, /* a l */ 'p' _ 'h' _ 'a' _ ';', 4, 0, 0x03b1 _ 0)
+NAMED_CHARACTER_REFERENCE(654, /* a m */ 'a' _ 'c' _ 'r' _ ';', 4, 0, 0x0101 _ 0)
+NAMED_CHARACTER_REFERENCE(655, /* a m */ 'a' _ 'l' _ 'g' _ ';', 4, 0, 0x2a3f _ 0)
+NAMED_CHARACTER_REFERENCE(656, /* a m */ 'p', 1, 0, 0x0026 _ 0)
+NAMED_CHARACTER_REFERENCE(657, /* a m */ 'p' _ ';', 2, 0, 0x0026 _ 0)
+NAMED_CHARACTER_REFERENCE(658, /* a n */ 'd' _ ';', 2, 0, 0x2227 _ 0)
+NAMED_CHARACTER_REFERENCE(659, /* a n */ 'd' _ 'a' _ 'n' _ 'd' _ ';', 5, 0, 0x2a55 _ 0)
+NAMED_CHARACTER_REFERENCE(660, /* a n */ 'd' _ 'd' _ ';', 3, 0, 0x2a5c _ 0)
+NAMED_CHARACTER_REFERENCE(661, /* a n */ 'd' _ 's' _ 'l' _ 'o' _ 'p' _ 'e' _ ';', 7, 0, 0x2a58 _ 0)
+NAMED_CHARACTER_REFERENCE(662, /* a n */ 'd' _ 'v' _ ';', 3, 0, 0x2a5a _ 0)
+NAMED_CHARACTER_REFERENCE(663, /* a n */ 'g' _ ';', 2, 0, 0x2220 _ 0)
+NAMED_CHARACTER_REFERENCE(664, /* a n */ 'g' _ 'e' _ ';', 3, 0, 0x29a4 _ 0)
+NAMED_CHARACTER_REFERENCE(665, /* a n */ 'g' _ 'l' _ 'e' _ ';', 4, 0, 0x2220 _ 0)
+NAMED_CHARACTER_REFERENCE(666, /* a n */ 'g' _ 'm' _ 's' _ 'd' _ ';', 5, 0, 0x2221 _ 0)
+NAMED_CHARACTER_REFERENCE(667, /* a n */ 'g' _ 'm' _ 's' _ 'd' _ 'a' _ 'a' _ ';', 7, 0, 0x29a8 _ 0)
+NAMED_CHARACTER_REFERENCE(668, /* a n */ 'g' _ 'm' _ 's' _ 'd' _ 'a' _ 'b' _ ';', 7, 0, 0x29a9 _ 0)
+NAMED_CHARACTER_REFERENCE(669, /* a n */ 'g' _ 'm' _ 's' _ 'd' _ 'a' _ 'c' _ ';', 7, 0, 0x29aa _ 0)
+NAMED_CHARACTER_REFERENCE(670, /* a n */ 'g' _ 'm' _ 's' _ 'd' _ 'a' _ 'd' _ ';', 7, 0, 0x29ab _ 0)
+NAMED_CHARACTER_REFERENCE(671, /* a n */ 'g' _ 'm' _ 's' _ 'd' _ 'a' _ 'e' _ ';', 7, 0, 0x29ac _ 0)
+NAMED_CHARACTER_REFERENCE(672, /* a n */ 'g' _ 'm' _ 's' _ 'd' _ 'a' _ 'f' _ ';', 7, 0, 0x29ad _ 0)
+NAMED_CHARACTER_REFERENCE(673, /* a n */ 'g' _ 'm' _ 's' _ 'd' _ 'a' _ 'g' _ ';', 7, 0, 0x29ae _ 0)
+NAMED_CHARACTER_REFERENCE(674, /* a n */ 'g' _ 'm' _ 's' _ 'd' _ 'a' _ 'h' _ ';', 7, 0, 0x29af _ 0)
+NAMED_CHARACTER_REFERENCE(675, /* a n */ 'g' _ 'r' _ 't' _ ';', 4, 0, 0x221f _ 0)
+NAMED_CHARACTER_REFERENCE(676, /* a n */ 'g' _ 'r' _ 't' _ 'v' _ 'b' _ ';', 6, 0, 0x22be _ 0)
+NAMED_CHARACTER_REFERENCE(677, /* a n */ 'g' _ 'r' _ 't' _ 'v' _ 'b' _ 'd' _ ';', 7, 0, 0x299d _ 0)
+NAMED_CHARACTER_REFERENCE(678, /* a n */ 'g' _ 's' _ 'p' _ 'h' _ ';', 5, 0, 0x2222 _ 0)
+NAMED_CHARACTER_REFERENCE(679, /* a n */ 'g' _ 's' _ 't' _ ';', 4, 0, 0x00c5 _ 0)
+NAMED_CHARACTER_REFERENCE(680, /* a n */ 'g' _ 'z' _ 'a' _ 'r' _ 'r' _ ';', 6, 0, 0x237c _ 0)
+NAMED_CHARACTER_REFERENCE(681, /* a o */ 'g' _ 'o' _ 'n' _ ';', 4, 0, 0x0105 _ 0)
+NAMED_CHARACTER_REFERENCE(682, /* a o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd52)
+NAMED_CHARACTER_REFERENCE(683, /* a p */ ';', 1, 0, 0x2248 _ 0)
+NAMED_CHARACTER_REFERENCE(684, /* a p */ 'E' _ ';', 2, 0, 0x2a70 _ 0)
+NAMED_CHARACTER_REFERENCE(685, /* a p */ 'a' _ 'c' _ 'i' _ 'r' _ ';', 5, 0, 0x2a6f _ 0)
+NAMED_CHARACTER_REFERENCE(686, /* a p */ 'e' _ ';', 2, 0, 0x224a _ 0)
+NAMED_CHARACTER_REFERENCE(687, /* a p */ 'i' _ 'd' _ ';', 3, 0, 0x224b _ 0)
+NAMED_CHARACTER_REFERENCE(688, /* a p */ 'o' _ 's' _ ';', 3, 0, 0x0027 _ 0)
+NAMED_CHARACTER_REFERENCE(689, /* a p */ 'p' _ 'r' _ 'o' _ 'x' _ ';', 5, 0, 0x2248 _ 0)
+NAMED_CHARACTER_REFERENCE(690, /* a p */ 'p' _ 'r' _ 'o' _ 'x' _ 'e' _ 'q' _ ';', 7, 0, 0x224a _ 0)
+NAMED_CHARACTER_REFERENCE(691, /* a r */ 'i' _ 'n' _ 'g', 3, 0, 0x00e5 _ 0)
+NAMED_CHARACTER_REFERENCE(692, /* a r */ 'i' _ 'n' _ 'g' _ ';', 4, 0, 0x00e5 _ 0)
+NAMED_CHARACTER_REFERENCE(693, /* a s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcb6)
+NAMED_CHARACTER_REFERENCE(694, /* a s */ 't' _ ';', 2, 0, 0x002a _ 0)
+NAMED_CHARACTER_REFERENCE(695, /* a s */ 'y' _ 'm' _ 'p' _ ';', 4, 0, 0x2248 _ 0)
+NAMED_CHARACTER_REFERENCE(696, /* a s */ 'y' _ 'm' _ 'p' _ 'e' _ 'q' _ ';', 6, 0, 0x224d _ 0)
+NAMED_CHARACTER_REFERENCE(697, /* a t */ 'i' _ 'l' _ 'd' _ 'e', 4, 0, 0x00e3 _ 0)
+NAMED_CHARACTER_REFERENCE(698, /* a t */ 'i' _ 'l' _ 'd' _ 'e' _ ';', 5, 0, 0x00e3 _ 0)
+NAMED_CHARACTER_REFERENCE(699, /* a u */ 'm' _ 'l', 2, 0, 0x00e4 _ 0)
+NAMED_CHARACTER_REFERENCE(700, /* a u */ 'm' _ 'l' _ ';', 3, 0, 0x00e4 _ 0)
+NAMED_CHARACTER_REFERENCE(701, /* a w */ 'c' _ 'o' _ 'n' _ 'i' _ 'n' _ 't' _ ';', 7, 0, 0x2233 _ 0)
+NAMED_CHARACTER_REFERENCE(702, /* a w */ 'i' _ 'n' _ 't' _ ';', 4, 0, 0x2a11 _ 0)
+NAMED_CHARACTER_REFERENCE(703, /* b N */ 'o' _ 't' _ ';', 3, 0, 0x2aed _ 0)
+NAMED_CHARACTER_REFERENCE(704, /* b a */ 'c' _ 'k' _ 'c' _ 'o' _ 'n' _ 'g' _ ';', 7, 0, 0x224c _ 0)
+NAMED_CHARACTER_REFERENCE(705, /* b a */ 'c' _ 'k' _ 'e' _ 'p' _ 's' _ 'i' _ 'l' _ 'o' _ 'n' _ ';', 10, 0, 0x03f6 _ 0)
+NAMED_CHARACTER_REFERENCE(706, /* b a */ 'c' _ 'k' _ 'p' _ 'r' _ 'i' _ 'm' _ 'e' _ ';', 8, 0, 0x2035 _ 0)
+NAMED_CHARACTER_REFERENCE(707, /* b a */ 'c' _ 'k' _ 's' _ 'i' _ 'm' _ ';', 6, 0, 0x223d _ 0)
+NAMED_CHARACTER_REFERENCE(708, /* b a */ 'c' _ 'k' _ 's' _ 'i' _ 'm' _ 'e' _ 'q' _ ';', 8, 0, 0x22cd _ 0)
+NAMED_CHARACTER_REFERENCE(709, /* b a */ 'r' _ 'v' _ 'e' _ 'e' _ ';', 5, 0, 0x22bd _ 0)
+NAMED_CHARACTER_REFERENCE(710, /* b a */ 'r' _ 'w' _ 'e' _ 'd' _ ';', 5, 0, 0x2305 _ 0)
+NAMED_CHARACTER_REFERENCE(711, /* b a */ 'r' _ 'w' _ 'e' _ 'd' _ 'g' _ 'e' _ ';', 7, 0, 0x2305 _ 0)
+NAMED_CHARACTER_REFERENCE(712, /* b b */ 'r' _ 'k' _ ';', 3, 0, 0x23b5 _ 0)
+NAMED_CHARACTER_REFERENCE(713, /* b b */ 'r' _ 'k' _ 't' _ 'b' _ 'r' _ 'k' _ ';', 7, 0, 0x23b6 _ 0)
+NAMED_CHARACTER_REFERENCE(714, /* b c */ 'o' _ 'n' _ 'g' _ ';', 4, 0, 0x224c _ 0)
+NAMED_CHARACTER_REFERENCE(715, /* b c */ 'y' _ ';', 2, 0, 0x0431 _ 0)
+NAMED_CHARACTER_REFERENCE(716, /* b d */ 'q' _ 'u' _ 'o' _ ';', 4, 0, 0x201e _ 0)
+NAMED_CHARACTER_REFERENCE(717, /* b e */ 'c' _ 'a' _ 'u' _ 's' _ ';', 5, 0, 0x2235 _ 0)
+NAMED_CHARACTER_REFERENCE(718, /* b e */ 'c' _ 'a' _ 'u' _ 's' _ 'e' _ ';', 6, 0, 0x2235 _ 0)
+NAMED_CHARACTER_REFERENCE(719, /* b e */ 'm' _ 'p' _ 't' _ 'y' _ 'v' _ ';', 6, 0, 0x29b0 _ 0)
+NAMED_CHARACTER_REFERENCE(720, /* b e */ 'p' _ 's' _ 'i' _ ';', 4, 0, 0x03f6 _ 0)
+NAMED_CHARACTER_REFERENCE(721, /* b e */ 'r' _ 'n' _ 'o' _ 'u' _ ';', 5, 0, 0x212c _ 0)
+NAMED_CHARACTER_REFERENCE(722, /* b e */ 't' _ 'a' _ ';', 3, 0, 0x03b2 _ 0)
+NAMED_CHARACTER_REFERENCE(723, /* b e */ 't' _ 'h' _ ';', 3, 0, 0x2136 _ 0)
+NAMED_CHARACTER_REFERENCE(724, /* b e */ 't' _ 'w' _ 'e' _ 'e' _ 'n' _ ';', 6, 0, 0x226c _ 0)
+NAMED_CHARACTER_REFERENCE(725, /* b f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd1f)
+NAMED_CHARACTER_REFERENCE(726, /* b i */ 'g' _ 'c' _ 'a' _ 'p' _ ';', 5, 0, 0x22c2 _ 0)
+NAMED_CHARACTER_REFERENCE(727, /* b i */ 'g' _ 'c' _ 'i' _ 'r' _ 'c' _ ';', 6, 0, 0x25ef _ 0)
+NAMED_CHARACTER_REFERENCE(728, /* b i */ 'g' _ 'c' _ 'u' _ 'p' _ ';', 5, 0, 0x22c3 _ 0)
+NAMED_CHARACTER_REFERENCE(729, /* b i */ 'g' _ 'o' _ 'd' _ 'o' _ 't' _ ';', 6, 0, 0x2a00 _ 0)
+NAMED_CHARACTER_REFERENCE(730, /* b i */ 'g' _ 'o' _ 'p' _ 'l' _ 'u' _ 's' _ ';', 7, 0, 0x2a01 _ 0)
+NAMED_CHARACTER_REFERENCE(731, /* b i */ 'g' _ 'o' _ 't' _ 'i' _ 'm' _ 'e' _ 's' _ ';', 8, 0, 0x2a02 _ 0)
+NAMED_CHARACTER_REFERENCE(732, /* b i */ 'g' _ 's' _ 'q' _ 'c' _ 'u' _ 'p' _ ';', 7, 0, 0x2a06 _ 0)
+NAMED_CHARACTER_REFERENCE(733, /* b i */ 'g' _ 's' _ 't' _ 'a' _ 'r' _ ';', 6, 0, 0x2605 _ 0)
+NAMED_CHARACTER_REFERENCE(734, /* b i */ 'g' _ 't' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'd' _ 'o' _ 'w' _ 'n' _ ';', 14, 0, 0x25bd _ 0)
+NAMED_CHARACTER_REFERENCE(735, /* b i */ 'g' _ 't' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'u' _ 'p' _ ';', 12, 0, 0x25b3 _ 0)
+NAMED_CHARACTER_REFERENCE(736, /* b i */ 'g' _ 'u' _ 'p' _ 'l' _ 'u' _ 's' _ ';', 7, 0, 0x2a04 _ 0)
+NAMED_CHARACTER_REFERENCE(737, /* b i */ 'g' _ 'v' _ 'e' _ 'e' _ ';', 5, 0, 0x22c1 _ 0)
+NAMED_CHARACTER_REFERENCE(738, /* b i */ 'g' _ 'w' _ 'e' _ 'd' _ 'g' _ 'e' _ ';', 7, 0, 0x22c0 _ 0)
+NAMED_CHARACTER_REFERENCE(739, /* b k */ 'a' _ 'r' _ 'o' _ 'w' _ ';', 5, 0, 0x290d _ 0)
+NAMED_CHARACTER_REFERENCE(740, /* b l */ 'a' _ 'c' _ 'k' _ 'l' _ 'o' _ 'z' _ 'e' _ 'n' _ 'g' _ 'e' _ ';', 11, 0, 0x29eb _ 0)
+NAMED_CHARACTER_REFERENCE(741, /* b l */ 'a' _ 'c' _ 'k' _ 's' _ 'q' _ 'u' _ 'a' _ 'r' _ 'e' _ ';', 10, 0, 0x25aa _ 0)
+NAMED_CHARACTER_REFERENCE(742, /* b l */ 'a' _ 'c' _ 'k' _ 't' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ ';', 12, 0, 0x25b4 _ 0)
+NAMED_CHARACTER_REFERENCE(743, /* b l */ 'a' _ 'c' _ 'k' _ 't' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'd' _ 'o' _ 'w' _ 'n' _ ';', 16, 0, 0x25be _ 0)
+NAMED_CHARACTER_REFERENCE(744, /* b l */ 'a' _ 'c' _ 'k' _ 't' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'l' _ 'e' _ 'f' _ 't' _ ';', 16, 0, 0x25c2 _ 0)
+NAMED_CHARACTER_REFERENCE(745, /* b l */ 'a' _ 'c' _ 'k' _ 't' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ ';', 17, 0, 0x25b8 _ 0)
+NAMED_CHARACTER_REFERENCE(746, /* b l */ 'a' _ 'n' _ 'k' _ ';', 4, 0, 0x2423 _ 0)
+NAMED_CHARACTER_REFERENCE(747, /* b l */ 'k' _ '1' _ '2' _ ';', 4, 0, 0x2592 _ 0)
+NAMED_CHARACTER_REFERENCE(748, /* b l */ 'k' _ '1' _ '4' _ ';', 4, 0, 0x2591 _ 0)
+NAMED_CHARACTER_REFERENCE(749, /* b l */ 'k' _ '3' _ '4' _ ';', 4, 0, 0x2593 _ 0)
+NAMED_CHARACTER_REFERENCE(750, /* b l */ 'o' _ 'c' _ 'k' _ ';', 4, 0, 0x2588 _ 0)
+NAMED_CHARACTER_REFERENCE(751, /* b n */ 'e' _ ';', 2, 0, 0x003d _ 0x20e5)
+NAMED_CHARACTER_REFERENCE(752, /* b n */ 'e' _ 'q' _ 'u' _ 'i' _ 'v' _ ';', 6, 0, 0x2261 _ 0x20e5)
+NAMED_CHARACTER_REFERENCE(753, /* b n */ 'o' _ 't' _ ';', 3, 0, 0x2310 _ 0)
+NAMED_CHARACTER_REFERENCE(754, /* b o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd53)
+NAMED_CHARACTER_REFERENCE(755, /* b o */ 't' _ ';', 2, 0, 0x22a5 _ 0)
+NAMED_CHARACTER_REFERENCE(756, /* b o */ 't' _ 't' _ 'o' _ 'm' _ ';', 5, 0, 0x22a5 _ 0)
+NAMED_CHARACTER_REFERENCE(757, /* b o */ 'w' _ 't' _ 'i' _ 'e' _ ';', 5, 0, 0x22c8 _ 0)
+NAMED_CHARACTER_REFERENCE(758, /* b o */ 'x' _ 'D' _ 'L' _ ';', 4, 0, 0x2557 _ 0)
+NAMED_CHARACTER_REFERENCE(759, /* b o */ 'x' _ 'D' _ 'R' _ ';', 4, 0, 0x2554 _ 0)
+NAMED_CHARACTER_REFERENCE(760, /* b o */ 'x' _ 'D' _ 'l' _ ';', 4, 0, 0x2556 _ 0)
+NAMED_CHARACTER_REFERENCE(761, /* b o */ 'x' _ 'D' _ 'r' _ ';', 4, 0, 0x2553 _ 0)
+NAMED_CHARACTER_REFERENCE(762, /* b o */ 'x' _ 'H' _ ';', 3, 0, 0x2550 _ 0)
+NAMED_CHARACTER_REFERENCE(763, /* b o */ 'x' _ 'H' _ 'D' _ ';', 4, 0, 0x2566 _ 0)
+NAMED_CHARACTER_REFERENCE(764, /* b o */ 'x' _ 'H' _ 'U' _ ';', 4, 0, 0x2569 _ 0)
+NAMED_CHARACTER_REFERENCE(765, /* b o */ 'x' _ 'H' _ 'd' _ ';', 4, 0, 0x2564 _ 0)
+NAMED_CHARACTER_REFERENCE(766, /* b o */ 'x' _ 'H' _ 'u' _ ';', 4, 0, 0x2567 _ 0)
+NAMED_CHARACTER_REFERENCE(767, /* b o */ 'x' _ 'U' _ 'L' _ ';', 4, 0, 0x255d _ 0)
+NAMED_CHARACTER_REFERENCE(768, /* b o */ 'x' _ 'U' _ 'R' _ ';', 4, 0, 0x255a _ 0)
+NAMED_CHARACTER_REFERENCE(769, /* b o */ 'x' _ 'U' _ 'l' _ ';', 4, 0, 0x255c _ 0)
+NAMED_CHARACTER_REFERENCE(770, /* b o */ 'x' _ 'U' _ 'r' _ ';', 4, 0, 0x2559 _ 0)
+NAMED_CHARACTER_REFERENCE(771, /* b o */ 'x' _ 'V' _ ';', 3, 0, 0x2551 _ 0)
+NAMED_CHARACTER_REFERENCE(772, /* b o */ 'x' _ 'V' _ 'H' _ ';', 4, 0, 0x256c _ 0)
+NAMED_CHARACTER_REFERENCE(773, /* b o */ 'x' _ 'V' _ 'L' _ ';', 4, 0, 0x2563 _ 0)
+NAMED_CHARACTER_REFERENCE(774, /* b o */ 'x' _ 'V' _ 'R' _ ';', 4, 0, 0x2560 _ 0)
+NAMED_CHARACTER_REFERENCE(775, /* b o */ 'x' _ 'V' _ 'h' _ ';', 4, 0, 0x256b _ 0)
+NAMED_CHARACTER_REFERENCE(776, /* b o */ 'x' _ 'V' _ 'l' _ ';', 4, 0, 0x2562 _ 0)
+NAMED_CHARACTER_REFERENCE(777, /* b o */ 'x' _ 'V' _ 'r' _ ';', 4, 0, 0x255f _ 0)
+NAMED_CHARACTER_REFERENCE(778, /* b o */ 'x' _ 'b' _ 'o' _ 'x' _ ';', 5, 0, 0x29c9 _ 0)
+NAMED_CHARACTER_REFERENCE(779, /* b o */ 'x' _ 'd' _ 'L' _ ';', 4, 0, 0x2555 _ 0)
+NAMED_CHARACTER_REFERENCE(780, /* b o */ 'x' _ 'd' _ 'R' _ ';', 4, 0, 0x2552 _ 0)
+NAMED_CHARACTER_REFERENCE(781, /* b o */ 'x' _ 'd' _ 'l' _ ';', 4, 0, 0x2510 _ 0)
+NAMED_CHARACTER_REFERENCE(782, /* b o */ 'x' _ 'd' _ 'r' _ ';', 4, 0, 0x250c _ 0)
+NAMED_CHARACTER_REFERENCE(783, /* b o */ 'x' _ 'h' _ ';', 3, 0, 0x2500 _ 0)
+NAMED_CHARACTER_REFERENCE(784, /* b o */ 'x' _ 'h' _ 'D' _ ';', 4, 0, 0x2565 _ 0)
+NAMED_CHARACTER_REFERENCE(785, /* b o */ 'x' _ 'h' _ 'U' _ ';', 4, 0, 0x2568 _ 0)
+NAMED_CHARACTER_REFERENCE(786, /* b o */ 'x' _ 'h' _ 'd' _ ';', 4, 0, 0x252c _ 0)
+NAMED_CHARACTER_REFERENCE(787, /* b o */ 'x' _ 'h' _ 'u' _ ';', 4, 0, 0x2534 _ 0)
+NAMED_CHARACTER_REFERENCE(788, /* b o */ 'x' _ 'm' _ 'i' _ 'n' _ 'u' _ 's' _ ';', 7, 0, 0x229f _ 0)
+NAMED_CHARACTER_REFERENCE(789, /* b o */ 'x' _ 'p' _ 'l' _ 'u' _ 's' _ ';', 6, 0, 0x229e _ 0)
+NAMED_CHARACTER_REFERENCE(790, /* b o */ 'x' _ 't' _ 'i' _ 'm' _ 'e' _ 's' _ ';', 7, 0, 0x22a0 _ 0)
+NAMED_CHARACTER_REFERENCE(791, /* b o */ 'x' _ 'u' _ 'L' _ ';', 4, 0, 0x255b _ 0)
+NAMED_CHARACTER_REFERENCE(792, /* b o */ 'x' _ 'u' _ 'R' _ ';', 4, 0, 0x2558 _ 0)
+NAMED_CHARACTER_REFERENCE(793, /* b o */ 'x' _ 'u' _ 'l' _ ';', 4, 0, 0x2518 _ 0)
+NAMED_CHARACTER_REFERENCE(794, /* b o */ 'x' _ 'u' _ 'r' _ ';', 4, 0, 0x2514 _ 0)
+NAMED_CHARACTER_REFERENCE(795, /* b o */ 'x' _ 'v' _ ';', 3, 0, 0x2502 _ 0)
+NAMED_CHARACTER_REFERENCE(796, /* b o */ 'x' _ 'v' _ 'H' _ ';', 4, 0, 0x256a _ 0)
+NAMED_CHARACTER_REFERENCE(797, /* b o */ 'x' _ 'v' _ 'L' _ ';', 4, 0, 0x2561 _ 0)
+NAMED_CHARACTER_REFERENCE(798, /* b o */ 'x' _ 'v' _ 'R' _ ';', 4, 0, 0x255e _ 0)
+NAMED_CHARACTER_REFERENCE(799, /* b o */ 'x' _ 'v' _ 'h' _ ';', 4, 0, 0x253c _ 0)
+NAMED_CHARACTER_REFERENCE(800, /* b o */ 'x' _ 'v' _ 'l' _ ';', 4, 0, 0x2524 _ 0)
+NAMED_CHARACTER_REFERENCE(801, /* b o */ 'x' _ 'v' _ 'r' _ ';', 4, 0, 0x251c _ 0)
+NAMED_CHARACTER_REFERENCE(802, /* b p */ 'r' _ 'i' _ 'm' _ 'e' _ ';', 5, 0, 0x2035 _ 0)
+NAMED_CHARACTER_REFERENCE(803, /* b r */ 'e' _ 'v' _ 'e' _ ';', 4, 0, 0x02d8 _ 0)
+NAMED_CHARACTER_REFERENCE(804, /* b r */ 'v' _ 'b' _ 'a' _ 'r', 4, 0, 0x00a6 _ 0)
+NAMED_CHARACTER_REFERENCE(805, /* b r */ 'v' _ 'b' _ 'a' _ 'r' _ ';', 5, 0, 0x00a6 _ 0)
+NAMED_CHARACTER_REFERENCE(806, /* b s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcb7)
+NAMED_CHARACTER_REFERENCE(807, /* b s */ 'e' _ 'm' _ 'i' _ ';', 4, 0, 0x204f _ 0)
+NAMED_CHARACTER_REFERENCE(808, /* b s */ 'i' _ 'm' _ ';', 3, 0, 0x223d _ 0)
+NAMED_CHARACTER_REFERENCE(809, /* b s */ 'i' _ 'm' _ 'e' _ ';', 4, 0, 0x22cd _ 0)
+NAMED_CHARACTER_REFERENCE(810, /* b s */ 'o' _ 'l' _ ';', 3, 0, 0x005c _ 0)
+NAMED_CHARACTER_REFERENCE(811, /* b s */ 'o' _ 'l' _ 'b' _ ';', 4, 0, 0x29c5 _ 0)
+NAMED_CHARACTER_REFERENCE(812, /* b s */ 'o' _ 'l' _ 'h' _ 's' _ 'u' _ 'b' _ ';', 7, 0, 0x27c8 _ 0)
+NAMED_CHARACTER_REFERENCE(813, /* b u */ 'l' _ 'l' _ ';', 3, 0, 0x2022 _ 0)
+NAMED_CHARACTER_REFERENCE(814, /* b u */ 'l' _ 'l' _ 'e' _ 't' _ ';', 5, 0, 0x2022 _ 0)
+NAMED_CHARACTER_REFERENCE(815, /* b u */ 'm' _ 'p' _ ';', 3, 0, 0x224e _ 0)
+NAMED_CHARACTER_REFERENCE(816, /* b u */ 'm' _ 'p' _ 'E' _ ';', 4, 0, 0x2aae _ 0)
+NAMED_CHARACTER_REFERENCE(817, /* b u */ 'm' _ 'p' _ 'e' _ ';', 4, 0, 0x224f _ 0)
+NAMED_CHARACTER_REFERENCE(818, /* b u */ 'm' _ 'p' _ 'e' _ 'q' _ ';', 5, 0, 0x224f _ 0)
+NAMED_CHARACTER_REFERENCE(819, /* c a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x0107 _ 0)
+NAMED_CHARACTER_REFERENCE(820, /* c a */ 'p' _ ';', 2, 0, 0x2229 _ 0)
+NAMED_CHARACTER_REFERENCE(821, /* c a */ 'p' _ 'a' _ 'n' _ 'd' _ ';', 5, 0, 0x2a44 _ 0)
+NAMED_CHARACTER_REFERENCE(822, /* c a */ 'p' _ 'b' _ 'r' _ 'c' _ 'u' _ 'p' _ ';', 7, 0, 0x2a49 _ 0)
+NAMED_CHARACTER_REFERENCE(823, /* c a */ 'p' _ 'c' _ 'a' _ 'p' _ ';', 5, 0, 0x2a4b _ 0)
+NAMED_CHARACTER_REFERENCE(824, /* c a */ 'p' _ 'c' _ 'u' _ 'p' _ ';', 5, 0, 0x2a47 _ 0)
+NAMED_CHARACTER_REFERENCE(825, /* c a */ 'p' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x2a40 _ 0)
+NAMED_CHARACTER_REFERENCE(826, /* c a */ 'p' _ 's' _ ';', 3, 0, 0x2229 _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(827, /* c a */ 'r' _ 'e' _ 't' _ ';', 4, 0, 0x2041 _ 0)
+NAMED_CHARACTER_REFERENCE(828, /* c a */ 'r' _ 'o' _ 'n' _ ';', 4, 0, 0x02c7 _ 0)
+NAMED_CHARACTER_REFERENCE(829, /* c c */ 'a' _ 'p' _ 's' _ ';', 4, 0, 0x2a4d _ 0)
+NAMED_CHARACTER_REFERENCE(830, /* c c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x010d _ 0)
+NAMED_CHARACTER_REFERENCE(831, /* c c */ 'e' _ 'd' _ 'i' _ 'l', 4, 0, 0x00e7 _ 0)
+NAMED_CHARACTER_REFERENCE(832, /* c c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x00e7 _ 0)
+NAMED_CHARACTER_REFERENCE(833, /* c c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x0109 _ 0)
+NAMED_CHARACTER_REFERENCE(834, /* c c */ 'u' _ 'p' _ 's' _ ';', 4, 0, 0x2a4c _ 0)
+NAMED_CHARACTER_REFERENCE(835, /* c c */ 'u' _ 'p' _ 's' _ 's' _ 'm' _ ';', 6, 0, 0x2a50 _ 0)
+NAMED_CHARACTER_REFERENCE(836, /* c d */ 'o' _ 't' _ ';', 3, 0, 0x010b _ 0)
+NAMED_CHARACTER_REFERENCE(837, /* c e */ 'd' _ 'i' _ 'l', 3, 0, 0x00b8 _ 0)
+NAMED_CHARACTER_REFERENCE(838, /* c e */ 'd' _ 'i' _ 'l' _ ';', 4, 0, 0x00b8 _ 0)
+NAMED_CHARACTER_REFERENCE(839, /* c e */ 'm' _ 'p' _ 't' _ 'y' _ 'v' _ ';', 6, 0, 0x29b2 _ 0)
+NAMED_CHARACTER_REFERENCE(840, /* c e */ 'n' _ 't', 2, 0, 0x00a2 _ 0)
+NAMED_CHARACTER_REFERENCE(841, /* c e */ 'n' _ 't' _ ';', 3, 0, 0x00a2 _ 0)
+NAMED_CHARACTER_REFERENCE(842, /* c e */ 'n' _ 't' _ 'e' _ 'r' _ 'd' _ 'o' _ 't' _ ';', 8, 0, 0x00b7 _ 0)
+NAMED_CHARACTER_REFERENCE(843, /* c f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd20)
+NAMED_CHARACTER_REFERENCE(844, /* c h */ 'c' _ 'y' _ ';', 3, 0, 0x0447 _ 0)
+NAMED_CHARACTER_REFERENCE(845, /* c h */ 'e' _ 'c' _ 'k' _ ';', 4, 0, 0x2713 _ 0)
+NAMED_CHARACTER_REFERENCE(846, /* c h */ 'e' _ 'c' _ 'k' _ 'm' _ 'a' _ 'r' _ 'k' _ ';', 8, 0, 0x2713 _ 0)
+NAMED_CHARACTER_REFERENCE(847, /* c h */ 'i' _ ';', 2, 0, 0x03c7 _ 0)
+NAMED_CHARACTER_REFERENCE(848, /* c i */ 'r' _ ';', 2, 0, 0x25cb _ 0)
+NAMED_CHARACTER_REFERENCE(849, /* c i */ 'r' _ 'E' _ ';', 3, 0, 0x29c3 _ 0)
+NAMED_CHARACTER_REFERENCE(850, /* c i */ 'r' _ 'c' _ ';', 3, 0, 0x02c6 _ 0)
+NAMED_CHARACTER_REFERENCE(851, /* c i */ 'r' _ 'c' _ 'e' _ 'q' _ ';', 5, 0, 0x2257 _ 0)
+NAMED_CHARACTER_REFERENCE(852, /* c i */ 'r' _ 'c' _ 'l' _ 'e' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 'l' _ 'e' _ 'f' _ 't' _ ';', 14, 0, 0x21ba _ 0)
+NAMED_CHARACTER_REFERENCE(853, /* c i */ 'r' _ 'c' _ 'l' _ 'e' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ ';', 15, 0, 0x21bb _ 0)
+NAMED_CHARACTER_REFERENCE(854, /* c i */ 'r' _ 'c' _ 'l' _ 'e' _ 'd' _ 'R' _ ';', 7, 0, 0x00ae _ 0)
+NAMED_CHARACTER_REFERENCE(855, /* c i */ 'r' _ 'c' _ 'l' _ 'e' _ 'd' _ 'S' _ ';', 7, 0, 0x24c8 _ 0)
+NAMED_CHARACTER_REFERENCE(856, /* c i */ 'r' _ 'c' _ 'l' _ 'e' _ 'd' _ 'a' _ 's' _ 't' _ ';', 9, 0, 0x229b _ 0)
+NAMED_CHARACTER_REFERENCE(857, /* c i */ 'r' _ 'c' _ 'l' _ 'e' _ 'd' _ 'c' _ 'i' _ 'r' _ 'c' _ ';', 10, 0, 0x229a _ 0)
+NAMED_CHARACTER_REFERENCE(858, /* c i */ 'r' _ 'c' _ 'l' _ 'e' _ 'd' _ 'd' _ 'a' _ 's' _ 'h' _ ';', 10, 0, 0x229d _ 0)
+NAMED_CHARACTER_REFERENCE(859, /* c i */ 'r' _ 'e' _ ';', 3, 0, 0x2257 _ 0)
+NAMED_CHARACTER_REFERENCE(860, /* c i */ 'r' _ 'f' _ 'n' _ 'i' _ 'n' _ 't' _ ';', 7, 0, 0x2a10 _ 0)
+NAMED_CHARACTER_REFERENCE(861, /* c i */ 'r' _ 'm' _ 'i' _ 'd' _ ';', 5, 0, 0x2aef _ 0)
+NAMED_CHARACTER_REFERENCE(862, /* c i */ 'r' _ 's' _ 'c' _ 'i' _ 'r' _ ';', 6, 0, 0x29c2 _ 0)
+NAMED_CHARACTER_REFERENCE(863, /* c l */ 'u' _ 'b' _ 's' _ ';', 4, 0, 0x2663 _ 0)
+NAMED_CHARACTER_REFERENCE(864, /* c l */ 'u' _ 'b' _ 's' _ 'u' _ 'i' _ 't' _ ';', 7, 0, 0x2663 _ 0)
+NAMED_CHARACTER_REFERENCE(865, /* c o */ 'l' _ 'o' _ 'n' _ ';', 4, 0, 0x003a _ 0)
+NAMED_CHARACTER_REFERENCE(866, /* c o */ 'l' _ 'o' _ 'n' _ 'e' _ ';', 5, 0, 0x2254 _ 0)
+NAMED_CHARACTER_REFERENCE(867, /* c o */ 'l' _ 'o' _ 'n' _ 'e' _ 'q' _ ';', 6, 0, 0x2254 _ 0)
+NAMED_CHARACTER_REFERENCE(868, /* c o */ 'm' _ 'm' _ 'a' _ ';', 4, 0, 0x002c _ 0)
+NAMED_CHARACTER_REFERENCE(869, /* c o */ 'm' _ 'm' _ 'a' _ 't' _ ';', 5, 0, 0x0040 _ 0)
+NAMED_CHARACTER_REFERENCE(870, /* c o */ 'm' _ 'p' _ ';', 3, 0, 0x2201 _ 0)
+NAMED_CHARACTER_REFERENCE(871, /* c o */ 'm' _ 'p' _ 'f' _ 'n' _ ';', 5, 0, 0x2218 _ 0)
+NAMED_CHARACTER_REFERENCE(872, /* c o */ 'm' _ 'p' _ 'l' _ 'e' _ 'm' _ 'e' _ 'n' _ 't' _ ';', 9, 0, 0x2201 _ 0)
+NAMED_CHARACTER_REFERENCE(873, /* c o */ 'm' _ 'p' _ 'l' _ 'e' _ 'x' _ 'e' _ 's' _ ';', 8, 0, 0x2102 _ 0)
+NAMED_CHARACTER_REFERENCE(874, /* c o */ 'n' _ 'g' _ ';', 3, 0, 0x2245 _ 0)
+NAMED_CHARACTER_REFERENCE(875, /* c o */ 'n' _ 'g' _ 'd' _ 'o' _ 't' _ ';', 6, 0, 0x2a6d _ 0)
+NAMED_CHARACTER_REFERENCE(876, /* c o */ 'n' _ 'i' _ 'n' _ 't' _ ';', 5, 0, 0x222e _ 0)
+NAMED_CHARACTER_REFERENCE(877, /* c o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd54)
+NAMED_CHARACTER_REFERENCE(878, /* c o */ 'p' _ 'r' _ 'o' _ 'd' _ ';', 5, 0, 0x2210 _ 0)
+NAMED_CHARACTER_REFERENCE(879, /* c o */ 'p' _ 'y', 2, 0, 0x00a9 _ 0)
+NAMED_CHARACTER_REFERENCE(880, /* c o */ 'p' _ 'y' _ ';', 3, 0, 0x00a9 _ 0)
+NAMED_CHARACTER_REFERENCE(881, /* c o */ 'p' _ 'y' _ 's' _ 'r' _ ';', 5, 0, 0x2117 _ 0)
+NAMED_CHARACTER_REFERENCE(882, /* c r */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21b5 _ 0)
+NAMED_CHARACTER_REFERENCE(883, /* c r */ 'o' _ 's' _ 's' _ ';', 4, 0, 0x2717 _ 0)
+NAMED_CHARACTER_REFERENCE(884, /* c s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcb8)
+NAMED_CHARACTER_REFERENCE(885, /* c s */ 'u' _ 'b' _ ';', 3, 0, 0x2acf _ 0)
+NAMED_CHARACTER_REFERENCE(886, /* c s */ 'u' _ 'b' _ 'e' _ ';', 4, 0, 0x2ad1 _ 0)
+NAMED_CHARACTER_REFERENCE(887, /* c s */ 'u' _ 'p' _ ';', 3, 0, 0x2ad0 _ 0)
+NAMED_CHARACTER_REFERENCE(888, /* c s */ 'u' _ 'p' _ 'e' _ ';', 4, 0, 0x2ad2 _ 0)
+NAMED_CHARACTER_REFERENCE(889, /* c t */ 'd' _ 'o' _ 't' _ ';', 4, 0, 0x22ef _ 0)
+NAMED_CHARACTER_REFERENCE(890, /* c u */ 'd' _ 'a' _ 'r' _ 'r' _ 'l' _ ';', 6, 0, 0x2938 _ 0)
+NAMED_CHARACTER_REFERENCE(891, /* c u */ 'd' _ 'a' _ 'r' _ 'r' _ 'r' _ ';', 6, 0, 0x2935 _ 0)
+NAMED_CHARACTER_REFERENCE(892, /* c u */ 'e' _ 'p' _ 'r' _ ';', 4, 0, 0x22de _ 0)
+NAMED_CHARACTER_REFERENCE(893, /* c u */ 'e' _ 's' _ 'c' _ ';', 4, 0, 0x22df _ 0)
+NAMED_CHARACTER_REFERENCE(894, /* c u */ 'l' _ 'a' _ 'r' _ 'r' _ ';', 5, 0, 0x21b6 _ 0)
+NAMED_CHARACTER_REFERENCE(895, /* c u */ 'l' _ 'a' _ 'r' _ 'r' _ 'p' _ ';', 6, 0, 0x293d _ 0)
+NAMED_CHARACTER_REFERENCE(896, /* c u */ 'p' _ ';', 2, 0, 0x222a _ 0)
+NAMED_CHARACTER_REFERENCE(897, /* c u */ 'p' _ 'b' _ 'r' _ 'c' _ 'a' _ 'p' _ ';', 7, 0, 0x2a48 _ 0)
+NAMED_CHARACTER_REFERENCE(898, /* c u */ 'p' _ 'c' _ 'a' _ 'p' _ ';', 5, 0, 0x2a46 _ 0)
+NAMED_CHARACTER_REFERENCE(899, /* c u */ 'p' _ 'c' _ 'u' _ 'p' _ ';', 5, 0, 0x2a4a _ 0)
+NAMED_CHARACTER_REFERENCE(900, /* c u */ 'p' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x228d _ 0)
+NAMED_CHARACTER_REFERENCE(901, /* c u */ 'p' _ 'o' _ 'r' _ ';', 4, 0, 0x2a45 _ 0)
+NAMED_CHARACTER_REFERENCE(902, /* c u */ 'p' _ 's' _ ';', 3, 0, 0x222a _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(903, /* c u */ 'r' _ 'a' _ 'r' _ 'r' _ ';', 5, 0, 0x21b7 _ 0)
+NAMED_CHARACTER_REFERENCE(904, /* c u */ 'r' _ 'a' _ 'r' _ 'r' _ 'm' _ ';', 6, 0, 0x293c _ 0)
+NAMED_CHARACTER_REFERENCE(905, /* c u */ 'r' _ 'l' _ 'y' _ 'e' _ 'q' _ 'p' _ 'r' _ 'e' _ 'c' _ ';', 10, 0, 0x22de _ 0)
+NAMED_CHARACTER_REFERENCE(906, /* c u */ 'r' _ 'l' _ 'y' _ 'e' _ 'q' _ 's' _ 'u' _ 'c' _ 'c' _ ';', 10, 0, 0x22df _ 0)
+NAMED_CHARACTER_REFERENCE(907, /* c u */ 'r' _ 'l' _ 'y' _ 'v' _ 'e' _ 'e' _ ';', 7, 0, 0x22ce _ 0)
+NAMED_CHARACTER_REFERENCE(908, /* c u */ 'r' _ 'l' _ 'y' _ 'w' _ 'e' _ 'd' _ 'g' _ 'e' _ ';', 9, 0, 0x22cf _ 0)
+NAMED_CHARACTER_REFERENCE(909, /* c u */ 'r' _ 'r' _ 'e' _ 'n', 4, 0, 0x00a4 _ 0)
+NAMED_CHARACTER_REFERENCE(910, /* c u */ 'r' _ 'r' _ 'e' _ 'n' _ ';', 5, 0, 0x00a4 _ 0)
+NAMED_CHARACTER_REFERENCE(911, /* c u */ 'r' _ 'v' _ 'e' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 'l' _ 'e' _ 'f' _ 't' _ ';', 13, 0, 0x21b6 _ 0)
+NAMED_CHARACTER_REFERENCE(912, /* c u */ 'r' _ 'v' _ 'e' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ ';', 14, 0, 0x21b7 _ 0)
+NAMED_CHARACTER_REFERENCE(913, /* c u */ 'v' _ 'e' _ 'e' _ ';', 4, 0, 0x22ce _ 0)
+NAMED_CHARACTER_REFERENCE(914, /* c u */ 'w' _ 'e' _ 'd' _ ';', 4, 0, 0x22cf _ 0)
+NAMED_CHARACTER_REFERENCE(915, /* c w */ 'c' _ 'o' _ 'n' _ 'i' _ 'n' _ 't' _ ';', 7, 0, 0x2232 _ 0)
+NAMED_CHARACTER_REFERENCE(916, /* c w */ 'i' _ 'n' _ 't' _ ';', 4, 0, 0x2231 _ 0)
+NAMED_CHARACTER_REFERENCE(917, /* c y */ 'l' _ 'c' _ 't' _ 'y' _ ';', 5, 0, 0x232d _ 0)
+NAMED_CHARACTER_REFERENCE(918, /* d A */ 'r' _ 'r' _ ';', 3, 0, 0x21d3 _ 0)
+NAMED_CHARACTER_REFERENCE(919, /* d H */ 'a' _ 'r' _ ';', 3, 0, 0x2965 _ 0)
+NAMED_CHARACTER_REFERENCE(920, /* d a */ 'g' _ 'g' _ 'e' _ 'r' _ ';', 5, 0, 0x2020 _ 0)
+NAMED_CHARACTER_REFERENCE(921, /* d a */ 'l' _ 'e' _ 't' _ 'h' _ ';', 5, 0, 0x2138 _ 0)
+NAMED_CHARACTER_REFERENCE(922, /* d a */ 'r' _ 'r' _ ';', 3, 0, 0x2193 _ 0)
+NAMED_CHARACTER_REFERENCE(923, /* d a */ 's' _ 'h' _ ';', 3, 0, 0x2010 _ 0)
+NAMED_CHARACTER_REFERENCE(924, /* d a */ 's' _ 'h' _ 'v' _ ';', 4, 0, 0x22a3 _ 0)
+NAMED_CHARACTER_REFERENCE(925, /* d b */ 'k' _ 'a' _ 'r' _ 'o' _ 'w' _ ';', 6, 0, 0x290f _ 0)
+NAMED_CHARACTER_REFERENCE(926, /* d b */ 'l' _ 'a' _ 'c' _ ';', 4, 0, 0x02dd _ 0)
+NAMED_CHARACTER_REFERENCE(927, /* d c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x010f _ 0)
+NAMED_CHARACTER_REFERENCE(928, /* d c */ 'y' _ ';', 2, 0, 0x0434 _ 0)
+NAMED_CHARACTER_REFERENCE(929, /* d d */ ';', 1, 0, 0x2146 _ 0)
+NAMED_CHARACTER_REFERENCE(930, /* d d */ 'a' _ 'g' _ 'g' _ 'e' _ 'r' _ ';', 6, 0, 0x2021 _ 0)
+NAMED_CHARACTER_REFERENCE(931, /* d d */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21ca _ 0)
+NAMED_CHARACTER_REFERENCE(932, /* d d */ 'o' _ 't' _ 's' _ 'e' _ 'q' _ ';', 6, 0, 0x2a77 _ 0)
+NAMED_CHARACTER_REFERENCE(933, /* d e */ 'g', 1, 0, 0x00b0 _ 0)
+NAMED_CHARACTER_REFERENCE(934, /* d e */ 'g' _ ';', 2, 0, 0x00b0 _ 0)
+NAMED_CHARACTER_REFERENCE(935, /* d e */ 'l' _ 't' _ 'a' _ ';', 4, 0, 0x03b4 _ 0)
+NAMED_CHARACTER_REFERENCE(936, /* d e */ 'm' _ 'p' _ 't' _ 'y' _ 'v' _ ';', 6, 0, 0x29b1 _ 0)
+NAMED_CHARACTER_REFERENCE(937, /* d f */ 'i' _ 's' _ 'h' _ 't' _ ';', 5, 0, 0x297f _ 0)
+NAMED_CHARACTER_REFERENCE(938, /* d f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd21)
+NAMED_CHARACTER_REFERENCE(939, /* d h */ 'a' _ 'r' _ 'l' _ ';', 4, 0, 0x21c3 _ 0)
+NAMED_CHARACTER_REFERENCE(940, /* d h */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21c2 _ 0)
+NAMED_CHARACTER_REFERENCE(941, /* d i */ 'a' _ 'm' _ ';', 3, 0, 0x22c4 _ 0)
+NAMED_CHARACTER_REFERENCE(942, /* d i */ 'a' _ 'm' _ 'o' _ 'n' _ 'd' _ ';', 6, 0, 0x22c4 _ 0)
+NAMED_CHARACTER_REFERENCE(943, /* d i */ 'a' _ 'm' _ 'o' _ 'n' _ 'd' _ 's' _ 'u' _ 'i' _ 't' _ ';', 10, 0, 0x2666 _ 0)
+NAMED_CHARACTER_REFERENCE(944, /* d i */ 'a' _ 'm' _ 's' _ ';', 4, 0, 0x2666 _ 0)
+NAMED_CHARACTER_REFERENCE(945, /* d i */ 'e' _ ';', 2, 0, 0x00a8 _ 0)
+NAMED_CHARACTER_REFERENCE(946, /* d i */ 'g' _ 'a' _ 'm' _ 'm' _ 'a' _ ';', 6, 0, 0x03dd _ 0)
+NAMED_CHARACTER_REFERENCE(947, /* d i */ 's' _ 'i' _ 'n' _ ';', 4, 0, 0x22f2 _ 0)
+NAMED_CHARACTER_REFERENCE(948, /* d i */ 'v' _ ';', 2, 0, 0x00f7 _ 0)
+NAMED_CHARACTER_REFERENCE(949, /* d i */ 'v' _ 'i' _ 'd' _ 'e', 4, 0, 0x00f7 _ 0)
+NAMED_CHARACTER_REFERENCE(950, /* d i */ 'v' _ 'i' _ 'd' _ 'e' _ ';', 5, 0, 0x00f7 _ 0)
+NAMED_CHARACTER_REFERENCE(951, /* d i */ 'v' _ 'i' _ 'd' _ 'e' _ 'o' _ 'n' _ 't' _ 'i' _ 'm' _ 'e' _ 's' _ ';', 12, 0, 0x22c7 _ 0)
+NAMED_CHARACTER_REFERENCE(952, /* d i */ 'v' _ 'o' _ 'n' _ 'x' _ ';', 5, 0, 0x22c7 _ 0)
+NAMED_CHARACTER_REFERENCE(953, /* d j */ 'c' _ 'y' _ ';', 3, 0, 0x0452 _ 0)
+NAMED_CHARACTER_REFERENCE(954, /* d l */ 'c' _ 'o' _ 'r' _ 'n' _ ';', 5, 0, 0x231e _ 0)
+NAMED_CHARACTER_REFERENCE(955, /* d l */ 'c' _ 'r' _ 'o' _ 'p' _ ';', 5, 0, 0x230d _ 0)
+NAMED_CHARACTER_REFERENCE(956, /* d o */ 'l' _ 'l' _ 'a' _ 'r' _ ';', 5, 0, 0x0024 _ 0)
+NAMED_CHARACTER_REFERENCE(957, /* d o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd55)
+NAMED_CHARACTER_REFERENCE(958, /* d o */ 't' _ ';', 2, 0, 0x02d9 _ 0)
+NAMED_CHARACTER_REFERENCE(959, /* d o */ 't' _ 'e' _ 'q' _ ';', 4, 0, 0x2250 _ 0)
+NAMED_CHARACTER_REFERENCE(960, /* d o */ 't' _ 'e' _ 'q' _ 'd' _ 'o' _ 't' _ ';', 7, 0, 0x2251 _ 0)
+NAMED_CHARACTER_REFERENCE(961, /* d o */ 't' _ 'm' _ 'i' _ 'n' _ 'u' _ 's' _ ';', 7, 0, 0x2238 _ 0)
+NAMED_CHARACTER_REFERENCE(962, /* d o */ 't' _ 'p' _ 'l' _ 'u' _ 's' _ ';', 6, 0, 0x2214 _ 0)
+NAMED_CHARACTER_REFERENCE(963, /* d o */ 't' _ 's' _ 'q' _ 'u' _ 'a' _ 'r' _ 'e' _ ';', 8, 0, 0x22a1 _ 0)
+NAMED_CHARACTER_REFERENCE(964, /* d o */ 'u' _ 'b' _ 'l' _ 'e' _ 'b' _ 'a' _ 'r' _ 'w' _ 'e' _ 'd' _ 'g' _ 'e' _ ';', 13, 0, 0x2306 _ 0)
+NAMED_CHARACTER_REFERENCE(965, /* d o */ 'w' _ 'n' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 8, 0, 0x2193 _ 0)
+NAMED_CHARACTER_REFERENCE(966, /* d o */ 'w' _ 'n' _ 'd' _ 'o' _ 'w' _ 'n' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 's' _ ';', 13, 0, 0x21ca _ 0)
+NAMED_CHARACTER_REFERENCE(967, /* d o */ 'w' _ 'n' _ 'h' _ 'a' _ 'r' _ 'p' _ 'o' _ 'o' _ 'n' _ 'l' _ 'e' _ 'f' _ 't' _ ';', 14, 0, 0x21c3 _ 0)
+NAMED_CHARACTER_REFERENCE(968, /* d o */ 'w' _ 'n' _ 'h' _ 'a' _ 'r' _ 'p' _ 'o' _ 'o' _ 'n' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ ';', 15, 0, 0x21c2 _ 0)
+NAMED_CHARACTER_REFERENCE(969, /* d r */ 'b' _ 'k' _ 'a' _ 'r' _ 'o' _ 'w' _ ';', 7, 0, 0x2910 _ 0)
+NAMED_CHARACTER_REFERENCE(970, /* d r */ 'c' _ 'o' _ 'r' _ 'n' _ ';', 5, 0, 0x231f _ 0)
+NAMED_CHARACTER_REFERENCE(971, /* d r */ 'c' _ 'r' _ 'o' _ 'p' _ ';', 5, 0, 0x230c _ 0)
+NAMED_CHARACTER_REFERENCE(972, /* d s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcb9)
+NAMED_CHARACTER_REFERENCE(973, /* d s */ 'c' _ 'y' _ ';', 3, 0, 0x0455 _ 0)
+NAMED_CHARACTER_REFERENCE(974, /* d s */ 'o' _ 'l' _ ';', 3, 0, 0x29f6 _ 0)
+NAMED_CHARACTER_REFERENCE(975, /* d s */ 't' _ 'r' _ 'o' _ 'k' _ ';', 5, 0, 0x0111 _ 0)
+NAMED_CHARACTER_REFERENCE(976, /* d t */ 'd' _ 'o' _ 't' _ ';', 4, 0, 0x22f1 _ 0)
+NAMED_CHARACTER_REFERENCE(977, /* d t */ 'r' _ 'i' _ ';', 3, 0, 0x25bf _ 0)
+NAMED_CHARACTER_REFERENCE(978, /* d t */ 'r' _ 'i' _ 'f' _ ';', 4, 0, 0x25be _ 0)
+NAMED_CHARACTER_REFERENCE(979, /* d u */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21f5 _ 0)
+NAMED_CHARACTER_REFERENCE(980, /* d u */ 'h' _ 'a' _ 'r' _ ';', 4, 0, 0x296f _ 0)
+NAMED_CHARACTER_REFERENCE(981, /* d w */ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ ';', 6, 0, 0x29a6 _ 0)
+NAMED_CHARACTER_REFERENCE(982, /* d z */ 'c' _ 'y' _ ';', 3, 0, 0x045f _ 0)
+NAMED_CHARACTER_REFERENCE(983, /* d z */ 'i' _ 'g' _ 'r' _ 'a' _ 'r' _ 'r' _ ';', 7, 0, 0x27ff _ 0)
+NAMED_CHARACTER_REFERENCE(984, /* e D */ 'D' _ 'o' _ 't' _ ';', 4, 0, 0x2a77 _ 0)
+NAMED_CHARACTER_REFERENCE(985, /* e D */ 'o' _ 't' _ ';', 3, 0, 0x2251 _ 0)
+NAMED_CHARACTER_REFERENCE(986, /* e a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00e9 _ 0)
+NAMED_CHARACTER_REFERENCE(987, /* e a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00e9 _ 0)
+NAMED_CHARACTER_REFERENCE(988, /* e a */ 's' _ 't' _ 'e' _ 'r' _ ';', 5, 0, 0x2a6e _ 0)
+NAMED_CHARACTER_REFERENCE(989, /* e c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x011b _ 0)
+NAMED_CHARACTER_REFERENCE(990, /* e c */ 'i' _ 'r' _ ';', 3, 0, 0x2256 _ 0)
+NAMED_CHARACTER_REFERENCE(991, /* e c */ 'i' _ 'r' _ 'c', 3, 0, 0x00ea _ 0)
+NAMED_CHARACTER_REFERENCE(992, /* e c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x00ea _ 0)
+NAMED_CHARACTER_REFERENCE(993, /* e c */ 'o' _ 'l' _ 'o' _ 'n' _ ';', 5, 0, 0x2255 _ 0)
+NAMED_CHARACTER_REFERENCE(994, /* e c */ 'y' _ ';', 2, 0, 0x044d _ 0)
+NAMED_CHARACTER_REFERENCE(995, /* e d */ 'o' _ 't' _ ';', 3, 0, 0x0117 _ 0)
+NAMED_CHARACTER_REFERENCE(996, /* e e */ ';', 1, 0, 0x2147 _ 0)
+NAMED_CHARACTER_REFERENCE(997, /* e f */ 'D' _ 'o' _ 't' _ ';', 4, 0, 0x2252 _ 0)
+NAMED_CHARACTER_REFERENCE(998, /* e f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd22)
+NAMED_CHARACTER_REFERENCE(999, /* e g */ ';', 1, 0, 0x2a9a _ 0)
+NAMED_CHARACTER_REFERENCE(1000, /* e g */ 'r' _ 'a' _ 'v' _ 'e', 4, 0, 0x00e8 _ 0)
+NAMED_CHARACTER_REFERENCE(1001, /* e g */ 'r' _ 'a' _ 'v' _ 'e' _ ';', 5, 0, 0x00e8 _ 0)
+NAMED_CHARACTER_REFERENCE(1002, /* e g */ 's' _ ';', 2, 0, 0x2a96 _ 0)
+NAMED_CHARACTER_REFERENCE(1003, /* e g */ 's' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x2a98 _ 0)
+NAMED_CHARACTER_REFERENCE(1004, /* e l */ ';', 1, 0, 0x2a99 _ 0)
+NAMED_CHARACTER_REFERENCE(1005, /* e l */ 'i' _ 'n' _ 't' _ 'e' _ 'r' _ 's' _ ';', 7, 0, 0x23e7 _ 0)
+NAMED_CHARACTER_REFERENCE(1006, /* e l */ 'l' _ ';', 2, 0, 0x2113 _ 0)
+NAMED_CHARACTER_REFERENCE(1007, /* e l */ 's' _ ';', 2, 0, 0x2a95 _ 0)
+NAMED_CHARACTER_REFERENCE(1008, /* e l */ 's' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x2a97 _ 0)
+NAMED_CHARACTER_REFERENCE(1009, /* e m */ 'a' _ 'c' _ 'r' _ ';', 4, 0, 0x0113 _ 0)
+NAMED_CHARACTER_REFERENCE(1010, /* e m */ 'p' _ 't' _ 'y' _ ';', 4, 0, 0x2205 _ 0)
+NAMED_CHARACTER_REFERENCE(1011, /* e m */ 'p' _ 't' _ 'y' _ 's' _ 'e' _ 't' _ ';', 7, 0, 0x2205 _ 0)
+NAMED_CHARACTER_REFERENCE(1012, /* e m */ 'p' _ 't' _ 'y' _ 'v' _ ';', 5, 0, 0x2205 _ 0)
+NAMED_CHARACTER_REFERENCE(1013, /* e m */ 's' _ 'p' _ '1' _ '3' _ ';', 5, 0, 0x2004 _ 0)
+NAMED_CHARACTER_REFERENCE(1014, /* e m */ 's' _ 'p' _ '1' _ '4' _ ';', 5, 0, 0x2005 _ 0)
+NAMED_CHARACTER_REFERENCE(1015, /* e m */ 's' _ 'p' _ ';', 3, 0, 0x2003 _ 0)
+NAMED_CHARACTER_REFERENCE(1016, /* e n */ 'g' _ ';', 2, 0, 0x014b _ 0)
+NAMED_CHARACTER_REFERENCE(1017, /* e n */ 's' _ 'p' _ ';', 3, 0, 0x2002 _ 0)
+NAMED_CHARACTER_REFERENCE(1018, /* e o */ 'g' _ 'o' _ 'n' _ ';', 4, 0, 0x0119 _ 0)
+NAMED_CHARACTER_REFERENCE(1019, /* e o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd56)
+NAMED_CHARACTER_REFERENCE(1020, /* e p */ 'a' _ 'r' _ ';', 3, 0, 0x22d5 _ 0)
+NAMED_CHARACTER_REFERENCE(1021, /* e p */ 'a' _ 'r' _ 's' _ 'l' _ ';', 5, 0, 0x29e3 _ 0)
+NAMED_CHARACTER_REFERENCE(1022, /* e p */ 'l' _ 'u' _ 's' _ ';', 4, 0, 0x2a71 _ 0)
+NAMED_CHARACTER_REFERENCE(1023, /* e p */ 's' _ 'i' _ ';', 3, 0, 0x03b5 _ 0)
+NAMED_CHARACTER_REFERENCE(1024, /* e p */ 's' _ 'i' _ 'l' _ 'o' _ 'n' _ ';', 6, 0, 0x03b5 _ 0)
+NAMED_CHARACTER_REFERENCE(1025, /* e p */ 's' _ 'i' _ 'v' _ ';', 4, 0, 0x03f5 _ 0)
+NAMED_CHARACTER_REFERENCE(1026, /* e q */ 'c' _ 'i' _ 'r' _ 'c' _ ';', 5, 0, 0x2256 _ 0)
+NAMED_CHARACTER_REFERENCE(1027, /* e q */ 'c' _ 'o' _ 'l' _ 'o' _ 'n' _ ';', 6, 0, 0x2255 _ 0)
+NAMED_CHARACTER_REFERENCE(1028, /* e q */ 's' _ 'i' _ 'm' _ ';', 4, 0, 0x2242 _ 0)
+NAMED_CHARACTER_REFERENCE(1029, /* e q */ 's' _ 'l' _ 'a' _ 'n' _ 't' _ 'g' _ 't' _ 'r' _ ';', 9, 0, 0x2a96 _ 0)
+NAMED_CHARACTER_REFERENCE(1030, /* e q */ 's' _ 'l' _ 'a' _ 'n' _ 't' _ 'l' _ 'e' _ 's' _ 's' _ ';', 10, 0, 0x2a95 _ 0)
+NAMED_CHARACTER_REFERENCE(1031, /* e q */ 'u' _ 'a' _ 'l' _ 's' _ ';', 5, 0, 0x003d _ 0)
+NAMED_CHARACTER_REFERENCE(1032, /* e q */ 'u' _ 'e' _ 's' _ 't' _ ';', 5, 0, 0x225f _ 0)
+NAMED_CHARACTER_REFERENCE(1033, /* e q */ 'u' _ 'i' _ 'v' _ ';', 4, 0, 0x2261 _ 0)
+NAMED_CHARACTER_REFERENCE(1034, /* e q */ 'u' _ 'i' _ 'v' _ 'D' _ 'D' _ ';', 6, 0, 0x2a78 _ 0)
+NAMED_CHARACTER_REFERENCE(1035, /* e q */ 'v' _ 'p' _ 'a' _ 'r' _ 's' _ 'l' _ ';', 7, 0, 0x29e5 _ 0)
+NAMED_CHARACTER_REFERENCE(1036, /* e r */ 'D' _ 'o' _ 't' _ ';', 4, 0, 0x2253 _ 0)
+NAMED_CHARACTER_REFERENCE(1037, /* e r */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x2971 _ 0)
+NAMED_CHARACTER_REFERENCE(1038, /* e s */ 'c' _ 'r' _ ';', 3, 0, 0x212f _ 0)
+NAMED_CHARACTER_REFERENCE(1039, /* e s */ 'd' _ 'o' _ 't' _ ';', 4, 0, 0x2250 _ 0)
+NAMED_CHARACTER_REFERENCE(1040, /* e s */ 'i' _ 'm' _ ';', 3, 0, 0x2242 _ 0)
+NAMED_CHARACTER_REFERENCE(1041, /* e t */ 'a' _ ';', 2, 0, 0x03b7 _ 0)
+NAMED_CHARACTER_REFERENCE(1042, /* e t */ 'h', 1, 0, 0x00f0 _ 0)
+NAMED_CHARACTER_REFERENCE(1043, /* e t */ 'h' _ ';', 2, 0, 0x00f0 _ 0)
+NAMED_CHARACTER_REFERENCE(1044, /* e u */ 'm' _ 'l', 2, 0, 0x00eb _ 0)
+NAMED_CHARACTER_REFERENCE(1045, /* e u */ 'm' _ 'l' _ ';', 3, 0, 0x00eb _ 0)
+NAMED_CHARACTER_REFERENCE(1046, /* e u */ 'r' _ 'o' _ ';', 3, 0, 0x20ac _ 0)
+NAMED_CHARACTER_REFERENCE(1047, /* e x */ 'c' _ 'l' _ ';', 3, 0, 0x0021 _ 0)
+NAMED_CHARACTER_REFERENCE(1048, /* e x */ 'i' _ 's' _ 't' _ ';', 4, 0, 0x2203 _ 0)
+NAMED_CHARACTER_REFERENCE(1049, /* e x */ 'p' _ 'e' _ 'c' _ 't' _ 'a' _ 't' _ 'i' _ 'o' _ 'n' _ ';', 10, 0, 0x2130 _ 0)
+NAMED_CHARACTER_REFERENCE(1050, /* e x */ 'p' _ 'o' _ 'n' _ 'e' _ 'n' _ 't' _ 'i' _ 'a' _ 'l' _ 'e' _ ';', 11, 0, 0x2147 _ 0)
+NAMED_CHARACTER_REFERENCE(1051, /* f a */ 'l' _ 'l' _ 'i' _ 'n' _ 'g' _ 'd' _ 'o' _ 't' _ 's' _ 'e' _ 'q' _ ';', 12, 0, 0x2252 _ 0)
+NAMED_CHARACTER_REFERENCE(1052, /* f c */ 'y' _ ';', 2, 0, 0x0444 _ 0)
+NAMED_CHARACTER_REFERENCE(1053, /* f e */ 'm' _ 'a' _ 'l' _ 'e' _ ';', 5, 0, 0x2640 _ 0)
+NAMED_CHARACTER_REFERENCE(1054, /* f f */ 'i' _ 'l' _ 'i' _ 'g' _ ';', 5, 0, 0xfb03 _ 0)
+NAMED_CHARACTER_REFERENCE(1055, /* f f */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0xfb00 _ 0)
+NAMED_CHARACTER_REFERENCE(1056, /* f f */ 'l' _ 'l' _ 'i' _ 'g' _ ';', 5, 0, 0xfb04 _ 0)
+NAMED_CHARACTER_REFERENCE(1057, /* f f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd23)
+NAMED_CHARACTER_REFERENCE(1058, /* f i */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0xfb01 _ 0)
+NAMED_CHARACTER_REFERENCE(1059, /* f j */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0x0066 _ 0x006a)
+NAMED_CHARACTER_REFERENCE(1060, /* f l */ 'a' _ 't' _ ';', 3, 0, 0x266d _ 0)
+NAMED_CHARACTER_REFERENCE(1061, /* f l */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0xfb02 _ 0)
+NAMED_CHARACTER_REFERENCE(1062, /* f l */ 't' _ 'n' _ 's' _ ';', 4, 0, 0x25b1 _ 0)
+NAMED_CHARACTER_REFERENCE(1063, /* f n */ 'o' _ 'f' _ ';', 3, 0, 0x0192 _ 0)
+NAMED_CHARACTER_REFERENCE(1064, /* f o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd57)
+NAMED_CHARACTER_REFERENCE(1065, /* f o */ 'r' _ 'a' _ 'l' _ 'l' _ ';', 5, 0, 0x2200 _ 0)
+NAMED_CHARACTER_REFERENCE(1066, /* f o */ 'r' _ 'k' _ ';', 3, 0, 0x22d4 _ 0)
+NAMED_CHARACTER_REFERENCE(1067, /* f o */ 'r' _ 'k' _ 'v' _ ';', 4, 0, 0x2ad9 _ 0)
+NAMED_CHARACTER_REFERENCE(1068, /* f p */ 'a' _ 'r' _ 't' _ 'i' _ 'n' _ 't' _ ';', 7, 0, 0x2a0d _ 0)
+NAMED_CHARACTER_REFERENCE(1069, /* f r */ 'a' _ 'c' _ '1' _ '2', 4, 0, 0x00bd _ 0)
+NAMED_CHARACTER_REFERENCE(1070, /* f r */ 'a' _ 'c' _ '1' _ '2' _ ';', 5, 0, 0x00bd _ 0)
+NAMED_CHARACTER_REFERENCE(1071, /* f r */ 'a' _ 'c' _ '1' _ '3' _ ';', 5, 0, 0x2153 _ 0)
+NAMED_CHARACTER_REFERENCE(1072, /* f r */ 'a' _ 'c' _ '1' _ '4', 4, 0, 0x00bc _ 0)
+NAMED_CHARACTER_REFERENCE(1073, /* f r */ 'a' _ 'c' _ '1' _ '4' _ ';', 5, 0, 0x00bc _ 0)
+NAMED_CHARACTER_REFERENCE(1074, /* f r */ 'a' _ 'c' _ '1' _ '5' _ ';', 5, 0, 0x2155 _ 0)
+NAMED_CHARACTER_REFERENCE(1075, /* f r */ 'a' _ 'c' _ '1' _ '6' _ ';', 5, 0, 0x2159 _ 0)
+NAMED_CHARACTER_REFERENCE(1076, /* f r */ 'a' _ 'c' _ '1' _ '8' _ ';', 5, 0, 0x215b _ 0)
+NAMED_CHARACTER_REFERENCE(1077, /* f r */ 'a' _ 'c' _ '2' _ '3' _ ';', 5, 0, 0x2154 _ 0)
+NAMED_CHARACTER_REFERENCE(1078, /* f r */ 'a' _ 'c' _ '2' _ '5' _ ';', 5, 0, 0x2156 _ 0)
+NAMED_CHARACTER_REFERENCE(1079, /* f r */ 'a' _ 'c' _ '3' _ '4', 4, 0, 0x00be _ 0)
+NAMED_CHARACTER_REFERENCE(1080, /* f r */ 'a' _ 'c' _ '3' _ '4' _ ';', 5, 0, 0x00be _ 0)
+NAMED_CHARACTER_REFERENCE(1081, /* f r */ 'a' _ 'c' _ '3' _ '5' _ ';', 5, 0, 0x2157 _ 0)
+NAMED_CHARACTER_REFERENCE(1082, /* f r */ 'a' _ 'c' _ '3' _ '8' _ ';', 5, 0, 0x215c _ 0)
+NAMED_CHARACTER_REFERENCE(1083, /* f r */ 'a' _ 'c' _ '4' _ '5' _ ';', 5, 0, 0x2158 _ 0)
+NAMED_CHARACTER_REFERENCE(1084, /* f r */ 'a' _ 'c' _ '5' _ '6' _ ';', 5, 0, 0x215a _ 0)
+NAMED_CHARACTER_REFERENCE(1085, /* f r */ 'a' _ 'c' _ '5' _ '8' _ ';', 5, 0, 0x215d _ 0)
+NAMED_CHARACTER_REFERENCE(1086, /* f r */ 'a' _ 'c' _ '7' _ '8' _ ';', 5, 0, 0x215e _ 0)
+NAMED_CHARACTER_REFERENCE(1087, /* f r */ 'a' _ 's' _ 'l' _ ';', 4, 0, 0x2044 _ 0)
+NAMED_CHARACTER_REFERENCE(1088, /* f r */ 'o' _ 'w' _ 'n' _ ';', 4, 0, 0x2322 _ 0)
+NAMED_CHARACTER_REFERENCE(1089, /* f s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcbb)
+NAMED_CHARACTER_REFERENCE(1090, /* g E */ ';', 1, 0, 0x2267 _ 0)
+NAMED_CHARACTER_REFERENCE(1091, /* g E */ 'l' _ ';', 2, 0, 0x2a8c _ 0)
+NAMED_CHARACTER_REFERENCE(1092, /* g a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x01f5 _ 0)
+NAMED_CHARACTER_REFERENCE(1093, /* g a */ 'm' _ 'm' _ 'a' _ ';', 4, 0, 0x03b3 _ 0)
+NAMED_CHARACTER_REFERENCE(1094, /* g a */ 'm' _ 'm' _ 'a' _ 'd' _ ';', 5, 0, 0x03dd _ 0)
+NAMED_CHARACTER_REFERENCE(1095, /* g a */ 'p' _ ';', 2, 0, 0x2a86 _ 0)
+NAMED_CHARACTER_REFERENCE(1096, /* g b */ 'r' _ 'e' _ 'v' _ 'e' _ ';', 5, 0, 0x011f _ 0)
+NAMED_CHARACTER_REFERENCE(1097, /* g c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x011d _ 0)
+NAMED_CHARACTER_REFERENCE(1098, /* g c */ 'y' _ ';', 2, 0, 0x0433 _ 0)
+NAMED_CHARACTER_REFERENCE(1099, /* g d */ 'o' _ 't' _ ';', 3, 0, 0x0121 _ 0)
+NAMED_CHARACTER_REFERENCE(1100, /* g e */ ';', 1, 0, 0x2265 _ 0)
+NAMED_CHARACTER_REFERENCE(1101, /* g e */ 'l' _ ';', 2, 0, 0x22db _ 0)
+NAMED_CHARACTER_REFERENCE(1102, /* g e */ 'q' _ ';', 2, 0, 0x2265 _ 0)
+NAMED_CHARACTER_REFERENCE(1103, /* g e */ 'q' _ 'q' _ ';', 3, 0, 0x2267 _ 0)
+NAMED_CHARACTER_REFERENCE(1104, /* g e */ 'q' _ 's' _ 'l' _ 'a' _ 'n' _ 't' _ ';', 7, 0, 0x2a7e _ 0)
+NAMED_CHARACTER_REFERENCE(1105, /* g e */ 's' _ ';', 2, 0, 0x2a7e _ 0)
+NAMED_CHARACTER_REFERENCE(1106, /* g e */ 's' _ 'c' _ 'c' _ ';', 4, 0, 0x2aa9 _ 0)
+NAMED_CHARACTER_REFERENCE(1107, /* g e */ 's' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x2a80 _ 0)
+NAMED_CHARACTER_REFERENCE(1108, /* g e */ 's' _ 'd' _ 'o' _ 't' _ 'o' _ ';', 6, 0, 0x2a82 _ 0)
+NAMED_CHARACTER_REFERENCE(1109, /* g e */ 's' _ 'd' _ 'o' _ 't' _ 'o' _ 'l' _ ';', 7, 0, 0x2a84 _ 0)
+NAMED_CHARACTER_REFERENCE(1110, /* g e */ 's' _ 'l' _ ';', 3, 0, 0x22db _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(1111, /* g e */ 's' _ 'l' _ 'e' _ 's' _ ';', 5, 0, 0x2a94 _ 0)
+NAMED_CHARACTER_REFERENCE(1112, /* g f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd24)
+NAMED_CHARACTER_REFERENCE(1113, /* g g */ ';', 1, 0, 0x226b _ 0)
+NAMED_CHARACTER_REFERENCE(1114, /* g g */ 'g' _ ';', 2, 0, 0x22d9 _ 0)
+NAMED_CHARACTER_REFERENCE(1115, /* g i */ 'm' _ 'e' _ 'l' _ ';', 4, 0, 0x2137 _ 0)
+NAMED_CHARACTER_REFERENCE(1116, /* g j */ 'c' _ 'y' _ ';', 3, 0, 0x0453 _ 0)
+NAMED_CHARACTER_REFERENCE(1117, /* g l */ ';', 1, 0, 0x2277 _ 0)
+NAMED_CHARACTER_REFERENCE(1118, /* g l */ 'E' _ ';', 2, 0, 0x2a92 _ 0)
+NAMED_CHARACTER_REFERENCE(1119, /* g l */ 'a' _ ';', 2, 0, 0x2aa5 _ 0)
+NAMED_CHARACTER_REFERENCE(1120, /* g l */ 'j' _ ';', 2, 0, 0x2aa4 _ 0)
+NAMED_CHARACTER_REFERENCE(1121, /* g n */ 'E' _ ';', 2, 0, 0x2269 _ 0)
+NAMED_CHARACTER_REFERENCE(1122, /* g n */ 'a' _ 'p' _ ';', 3, 0, 0x2a8a _ 0)
+NAMED_CHARACTER_REFERENCE(1123, /* g n */ 'a' _ 'p' _ 'p' _ 'r' _ 'o' _ 'x' _ ';', 7, 0, 0x2a8a _ 0)
+NAMED_CHARACTER_REFERENCE(1124, /* g n */ 'e' _ ';', 2, 0, 0x2a88 _ 0)
+NAMED_CHARACTER_REFERENCE(1125, /* g n */ 'e' _ 'q' _ ';', 3, 0, 0x2a88 _ 0)
+NAMED_CHARACTER_REFERENCE(1126, /* g n */ 'e' _ 'q' _ 'q' _ ';', 4, 0, 0x2269 _ 0)
+NAMED_CHARACTER_REFERENCE(1127, /* g n */ 's' _ 'i' _ 'm' _ ';', 4, 0, 0x22e7 _ 0)
+NAMED_CHARACTER_REFERENCE(1128, /* g o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd58)
+NAMED_CHARACTER_REFERENCE(1129, /* g r */ 'a' _ 'v' _ 'e' _ ';', 4, 0, 0x0060 _ 0)
+NAMED_CHARACTER_REFERENCE(1130, /* g s */ 'c' _ 'r' _ ';', 3, 0, 0x210a _ 0)
+NAMED_CHARACTER_REFERENCE(1131, /* g s */ 'i' _ 'm' _ ';', 3, 0, 0x2273 _ 0)
+NAMED_CHARACTER_REFERENCE(1132, /* g s */ 'i' _ 'm' _ 'e' _ ';', 4, 0, 0x2a8e _ 0)
+NAMED_CHARACTER_REFERENCE(1133, /* g s */ 'i' _ 'm' _ 'l' _ ';', 4, 0, 0x2a90 _ 0)
+NAMED_CHARACTER_REFERENCE(1134, /* g t */ 0, 0, 1, 0x003e _ 0)
+NAMED_CHARACTER_REFERENCE(1135, /* g t */ ';', 1, 0, 0x003e _ 0)
+NAMED_CHARACTER_REFERENCE(1136, /* g t */ 'c' _ 'c' _ ';', 3, 0, 0x2aa7 _ 0)
+NAMED_CHARACTER_REFERENCE(1137, /* g t */ 'c' _ 'i' _ 'r' _ ';', 4, 0, 0x2a7a _ 0)
+NAMED_CHARACTER_REFERENCE(1138, /* g t */ 'd' _ 'o' _ 't' _ ';', 4, 0, 0x22d7 _ 0)
+NAMED_CHARACTER_REFERENCE(1139, /* g t */ 'l' _ 'P' _ 'a' _ 'r' _ ';', 5, 0, 0x2995 _ 0)
+NAMED_CHARACTER_REFERENCE(1140, /* g t */ 'q' _ 'u' _ 'e' _ 's' _ 't' _ ';', 6, 0, 0x2a7c _ 0)
+NAMED_CHARACTER_REFERENCE(1141, /* g t */ 'r' _ 'a' _ 'p' _ 'p' _ 'r' _ 'o' _ 'x' _ ';', 8, 0, 0x2a86 _ 0)
+NAMED_CHARACTER_REFERENCE(1142, /* g t */ 'r' _ 'a' _ 'r' _ 'r' _ ';', 5, 0, 0x2978 _ 0)
+NAMED_CHARACTER_REFERENCE(1143, /* g t */ 'r' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x22d7 _ 0)
+NAMED_CHARACTER_REFERENCE(1144, /* g t */ 'r' _ 'e' _ 'q' _ 'l' _ 'e' _ 's' _ 's' _ ';', 8, 0, 0x22db _ 0)
+NAMED_CHARACTER_REFERENCE(1145, /* g t */ 'r' _ 'e' _ 'q' _ 'q' _ 'l' _ 'e' _ 's' _ 's' _ ';', 9, 0, 0x2a8c _ 0)
+NAMED_CHARACTER_REFERENCE(1146, /* g t */ 'r' _ 'l' _ 'e' _ 's' _ 's' _ ';', 6, 0, 0x2277 _ 0)
+NAMED_CHARACTER_REFERENCE(1147, /* g t */ 'r' _ 's' _ 'i' _ 'm' _ ';', 5, 0, 0x2273 _ 0)
+NAMED_CHARACTER_REFERENCE(1148, /* g v */ 'e' _ 'r' _ 't' _ 'n' _ 'e' _ 'q' _ 'q' _ ';', 8, 0, 0x2269 _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(1149, /* g v */ 'n' _ 'E' _ ';', 3, 0, 0x2269 _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(1150, /* h A */ 'r' _ 'r' _ ';', 3, 0, 0x21d4 _ 0)
+NAMED_CHARACTER_REFERENCE(1151, /* h a */ 'i' _ 'r' _ 's' _ 'p' _ ';', 5, 0, 0x200a _ 0)
+NAMED_CHARACTER_REFERENCE(1152, /* h a */ 'l' _ 'f' _ ';', 3, 0, 0x00bd _ 0)
+NAMED_CHARACTER_REFERENCE(1153, /* h a */ 'm' _ 'i' _ 'l' _ 't' _ ';', 5, 0, 0x210b _ 0)
+NAMED_CHARACTER_REFERENCE(1154, /* h a */ 'r' _ 'd' _ 'c' _ 'y' _ ';', 5, 0, 0x044a _ 0)
+NAMED_CHARACTER_REFERENCE(1155, /* h a */ 'r' _ 'r' _ ';', 3, 0, 0x2194 _ 0)
+NAMED_CHARACTER_REFERENCE(1156, /* h a */ 'r' _ 'r' _ 'c' _ 'i' _ 'r' _ ';', 6, 0, 0x2948 _ 0)
+NAMED_CHARACTER_REFERENCE(1157, /* h a */ 'r' _ 'r' _ 'w' _ ';', 4, 0, 0x21ad _ 0)
+NAMED_CHARACTER_REFERENCE(1158, /* h b */ 'a' _ 'r' _ ';', 3, 0, 0x210f _ 0)
+NAMED_CHARACTER_REFERENCE(1159, /* h c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x0125 _ 0)
+NAMED_CHARACTER_REFERENCE(1160, /* h e */ 'a' _ 'r' _ 't' _ 's' _ ';', 5, 0, 0x2665 _ 0)
+NAMED_CHARACTER_REFERENCE(1161, /* h e */ 'a' _ 'r' _ 't' _ 's' _ 'u' _ 'i' _ 't' _ ';', 8, 0, 0x2665 _ 0)
+NAMED_CHARACTER_REFERENCE(1162, /* h e */ 'l' _ 'l' _ 'i' _ 'p' _ ';', 5, 0, 0x2026 _ 0)
+NAMED_CHARACTER_REFERENCE(1163, /* h e */ 'r' _ 'c' _ 'o' _ 'n' _ ';', 5, 0, 0x22b9 _ 0)
+NAMED_CHARACTER_REFERENCE(1164, /* h f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd25)
+NAMED_CHARACTER_REFERENCE(1165, /* h k */ 's' _ 'e' _ 'a' _ 'r' _ 'o' _ 'w' _ ';', 7, 0, 0x2925 _ 0)
+NAMED_CHARACTER_REFERENCE(1166, /* h k */ 's' _ 'w' _ 'a' _ 'r' _ 'o' _ 'w' _ ';', 7, 0, 0x2926 _ 0)
+NAMED_CHARACTER_REFERENCE(1167, /* h o */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21ff _ 0)
+NAMED_CHARACTER_REFERENCE(1168, /* h o */ 'm' _ 't' _ 'h' _ 't' _ ';', 5, 0, 0x223b _ 0)
+NAMED_CHARACTER_REFERENCE(1169, /* h o */ 'o' _ 'k' _ 'l' _ 'e' _ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 12, 0, 0x21a9 _ 0)
+NAMED_CHARACTER_REFERENCE(1170, /* h o */ 'o' _ 'k' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x21aa _ 0)
+NAMED_CHARACTER_REFERENCE(1171, /* h o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd59)
+NAMED_CHARACTER_REFERENCE(1172, /* h o */ 'r' _ 'b' _ 'a' _ 'r' _ ';', 5, 0, 0x2015 _ 0)
+NAMED_CHARACTER_REFERENCE(1173, /* h s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcbd)
+NAMED_CHARACTER_REFERENCE(1174, /* h s */ 'l' _ 'a' _ 's' _ 'h' _ ';', 5, 0, 0x210f _ 0)
+NAMED_CHARACTER_REFERENCE(1175, /* h s */ 't' _ 'r' _ 'o' _ 'k' _ ';', 5, 0, 0x0127 _ 0)
+NAMED_CHARACTER_REFERENCE(1176, /* h y */ 'b' _ 'u' _ 'l' _ 'l' _ ';', 5, 0, 0x2043 _ 0)
+NAMED_CHARACTER_REFERENCE(1177, /* h y */ 'p' _ 'h' _ 'e' _ 'n' _ ';', 5, 0, 0x2010 _ 0)
+NAMED_CHARACTER_REFERENCE(1178, /* i a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00ed _ 0)
+NAMED_CHARACTER_REFERENCE(1179, /* i a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00ed _ 0)
+NAMED_CHARACTER_REFERENCE(1180, /* i c */ ';', 1, 0, 0x2063 _ 0)
+NAMED_CHARACTER_REFERENCE(1181, /* i c */ 'i' _ 'r' _ 'c', 3, 0, 0x00ee _ 0)
+NAMED_CHARACTER_REFERENCE(1182, /* i c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x00ee _ 0)
+NAMED_CHARACTER_REFERENCE(1183, /* i c */ 'y' _ ';', 2, 0, 0x0438 _ 0)
+NAMED_CHARACTER_REFERENCE(1184, /* i e */ 'c' _ 'y' _ ';', 3, 0, 0x0435 _ 0)
+NAMED_CHARACTER_REFERENCE(1185, /* i e */ 'x' _ 'c' _ 'l', 3, 0, 0x00a1 _ 0)
+NAMED_CHARACTER_REFERENCE(1186, /* i e */ 'x' _ 'c' _ 'l' _ ';', 4, 0, 0x00a1 _ 0)
+NAMED_CHARACTER_REFERENCE(1187, /* i f */ 'f' _ ';', 2, 0, 0x21d4 _ 0)
+NAMED_CHARACTER_REFERENCE(1188, /* i f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd26)
+NAMED_CHARACTER_REFERENCE(1189, /* i g */ 'r' _ 'a' _ 'v' _ 'e', 4, 0, 0x00ec _ 0)
+NAMED_CHARACTER_REFERENCE(1190, /* i g */ 'r' _ 'a' _ 'v' _ 'e' _ ';', 5, 0, 0x00ec _ 0)
+NAMED_CHARACTER_REFERENCE(1191, /* i i */ ';', 1, 0, 0x2148 _ 0)
+NAMED_CHARACTER_REFERENCE(1192, /* i i */ 'i' _ 'i' _ 'n' _ 't' _ ';', 5, 0, 0x2a0c _ 0)
+NAMED_CHARACTER_REFERENCE(1193, /* i i */ 'i' _ 'n' _ 't' _ ';', 4, 0, 0x222d _ 0)
+NAMED_CHARACTER_REFERENCE(1194, /* i i */ 'n' _ 'f' _ 'i' _ 'n' _ ';', 5, 0, 0x29dc _ 0)
+NAMED_CHARACTER_REFERENCE(1195, /* i i */ 'o' _ 't' _ 'a' _ ';', 4, 0, 0x2129 _ 0)
+NAMED_CHARACTER_REFERENCE(1196, /* i j */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0x0133 _ 0)
+NAMED_CHARACTER_REFERENCE(1197, /* i m */ 'a' _ 'c' _ 'r' _ ';', 4, 0, 0x012b _ 0)
+NAMED_CHARACTER_REFERENCE(1198, /* i m */ 'a' _ 'g' _ 'e' _ ';', 4, 0, 0x2111 _ 0)
+NAMED_CHARACTER_REFERENCE(1199, /* i m */ 'a' _ 'g' _ 'l' _ 'i' _ 'n' _ 'e' _ ';', 7, 0, 0x2110 _ 0)
+NAMED_CHARACTER_REFERENCE(1200, /* i m */ 'a' _ 'g' _ 'p' _ 'a' _ 'r' _ 't' _ ';', 7, 0, 0x2111 _ 0)
+NAMED_CHARACTER_REFERENCE(1201, /* i m */ 'a' _ 't' _ 'h' _ ';', 4, 0, 0x0131 _ 0)
+NAMED_CHARACTER_REFERENCE(1202, /* i m */ 'o' _ 'f' _ ';', 3, 0, 0x22b7 _ 0)
+NAMED_CHARACTER_REFERENCE(1203, /* i m */ 'p' _ 'e' _ 'd' _ ';', 4, 0, 0x01b5 _ 0)
+NAMED_CHARACTER_REFERENCE(1204, /* i n */ ';', 1, 0, 0x2208 _ 0)
+NAMED_CHARACTER_REFERENCE(1205, /* i n */ 'c' _ 'a' _ 'r' _ 'e' _ ';', 5, 0, 0x2105 _ 0)
+NAMED_CHARACTER_REFERENCE(1206, /* i n */ 'f' _ 'i' _ 'n' _ ';', 4, 0, 0x221e _ 0)
+NAMED_CHARACTER_REFERENCE(1207, /* i n */ 'f' _ 'i' _ 'n' _ 't' _ 'i' _ 'e' _ ';', 7, 0, 0x29dd _ 0)
+NAMED_CHARACTER_REFERENCE(1208, /* i n */ 'o' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x0131 _ 0)
+NAMED_CHARACTER_REFERENCE(1209, /* i n */ 't' _ ';', 2, 0, 0x222b _ 0)
+NAMED_CHARACTER_REFERENCE(1210, /* i n */ 't' _ 'c' _ 'a' _ 'l' _ ';', 5, 0, 0x22ba _ 0)
+NAMED_CHARACTER_REFERENCE(1211, /* i n */ 't' _ 'e' _ 'g' _ 'e' _ 'r' _ 's' _ ';', 7, 0, 0x2124 _ 0)
+NAMED_CHARACTER_REFERENCE(1212, /* i n */ 't' _ 'e' _ 'r' _ 'c' _ 'a' _ 'l' _ ';', 7, 0, 0x22ba _ 0)
+NAMED_CHARACTER_REFERENCE(1213, /* i n */ 't' _ 'l' _ 'a' _ 'r' _ 'h' _ 'k' _ ';', 7, 0, 0x2a17 _ 0)
+NAMED_CHARACTER_REFERENCE(1214, /* i n */ 't' _ 'p' _ 'r' _ 'o' _ 'd' _ ';', 6, 0, 0x2a3c _ 0)
+NAMED_CHARACTER_REFERENCE(1215, /* i o */ 'c' _ 'y' _ ';', 3, 0, 0x0451 _ 0)
+NAMED_CHARACTER_REFERENCE(1216, /* i o */ 'g' _ 'o' _ 'n' _ ';', 4, 0, 0x012f _ 0)
+NAMED_CHARACTER_REFERENCE(1217, /* i o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd5a)
+NAMED_CHARACTER_REFERENCE(1218, /* i o */ 't' _ 'a' _ ';', 3, 0, 0x03b9 _ 0)
+NAMED_CHARACTER_REFERENCE(1219, /* i p */ 'r' _ 'o' _ 'd' _ ';', 4, 0, 0x2a3c _ 0)
+NAMED_CHARACTER_REFERENCE(1220, /* i q */ 'u' _ 'e' _ 's' _ 't', 4, 0, 0x00bf _ 0)
+NAMED_CHARACTER_REFERENCE(1221, /* i q */ 'u' _ 'e' _ 's' _ 't' _ ';', 5, 0, 0x00bf _ 0)
+NAMED_CHARACTER_REFERENCE(1222, /* i s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcbe)
+NAMED_CHARACTER_REFERENCE(1223, /* i s */ 'i' _ 'n' _ ';', 3, 0, 0x2208 _ 0)
+NAMED_CHARACTER_REFERENCE(1224, /* i s */ 'i' _ 'n' _ 'E' _ ';', 4, 0, 0x22f9 _ 0)
+NAMED_CHARACTER_REFERENCE(1225, /* i s */ 'i' _ 'n' _ 'd' _ 'o' _ 't' _ ';', 6, 0, 0x22f5 _ 0)
+NAMED_CHARACTER_REFERENCE(1226, /* i s */ 'i' _ 'n' _ 's' _ ';', 4, 0, 0x22f4 _ 0)
+NAMED_CHARACTER_REFERENCE(1227, /* i s */ 'i' _ 'n' _ 's' _ 'v' _ ';', 5, 0, 0x22f3 _ 0)
+NAMED_CHARACTER_REFERENCE(1228, /* i s */ 'i' _ 'n' _ 'v' _ ';', 4, 0, 0x2208 _ 0)
+NAMED_CHARACTER_REFERENCE(1229, /* i t */ ';', 1, 0, 0x2062 _ 0)
+NAMED_CHARACTER_REFERENCE(1230, /* i t */ 'i' _ 'l' _ 'd' _ 'e' _ ';', 5, 0, 0x0129 _ 0)
+NAMED_CHARACTER_REFERENCE(1231, /* i u */ 'k' _ 'c' _ 'y' _ ';', 4, 0, 0x0456 _ 0)
+NAMED_CHARACTER_REFERENCE(1232, /* i u */ 'm' _ 'l', 2, 0, 0x00ef _ 0)
+NAMED_CHARACTER_REFERENCE(1233, /* i u */ 'm' _ 'l' _ ';', 3, 0, 0x00ef _ 0)
+NAMED_CHARACTER_REFERENCE(1234, /* j c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x0135 _ 0)
+NAMED_CHARACTER_REFERENCE(1235, /* j c */ 'y' _ ';', 2, 0, 0x0439 _ 0)
+NAMED_CHARACTER_REFERENCE(1236, /* j f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd27)
+NAMED_CHARACTER_REFERENCE(1237, /* j m */ 'a' _ 't' _ 'h' _ ';', 4, 0, 0x0237 _ 0)
+NAMED_CHARACTER_REFERENCE(1238, /* j o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd5b)
+NAMED_CHARACTER_REFERENCE(1239, /* j s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcbf)
+NAMED_CHARACTER_REFERENCE(1240, /* j s */ 'e' _ 'r' _ 'c' _ 'y' _ ';', 5, 0, 0x0458 _ 0)
+NAMED_CHARACTER_REFERENCE(1241, /* j u */ 'k' _ 'c' _ 'y' _ ';', 4, 0, 0x0454 _ 0)
+NAMED_CHARACTER_REFERENCE(1242, /* k a */ 'p' _ 'p' _ 'a' _ ';', 4, 0, 0x03ba _ 0)
+NAMED_CHARACTER_REFERENCE(1243, /* k a */ 'p' _ 'p' _ 'a' _ 'v' _ ';', 5, 0, 0x03f0 _ 0)
+NAMED_CHARACTER_REFERENCE(1244, /* k c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x0137 _ 0)
+NAMED_CHARACTER_REFERENCE(1245, /* k c */ 'y' _ ';', 2, 0, 0x043a _ 0)
+NAMED_CHARACTER_REFERENCE(1246, /* k f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd28)
+NAMED_CHARACTER_REFERENCE(1247, /* k g */ 'r' _ 'e' _ 'e' _ 'n' _ ';', 5, 0, 0x0138 _ 0)
+NAMED_CHARACTER_REFERENCE(1248, /* k h */ 'c' _ 'y' _ ';', 3, 0, 0x0445 _ 0)
+NAMED_CHARACTER_REFERENCE(1249, /* k j */ 'c' _ 'y' _ ';', 3, 0, 0x045c _ 0)
+NAMED_CHARACTER_REFERENCE(1250, /* k o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd5c)
+NAMED_CHARACTER_REFERENCE(1251, /* k s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcc0)
+NAMED_CHARACTER_REFERENCE(1252, /* l A */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21da _ 0)
+NAMED_CHARACTER_REFERENCE(1253, /* l A */ 'r' _ 'r' _ ';', 3, 0, 0x21d0 _ 0)
+NAMED_CHARACTER_REFERENCE(1254, /* l A */ 't' _ 'a' _ 'i' _ 'l' _ ';', 5, 0, 0x291b _ 0)
+NAMED_CHARACTER_REFERENCE(1255, /* l B */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x290e _ 0)
+NAMED_CHARACTER_REFERENCE(1256, /* l E */ ';', 1, 0, 0x2266 _ 0)
+NAMED_CHARACTER_REFERENCE(1257, /* l E */ 'g' _ ';', 2, 0, 0x2a8b _ 0)
+NAMED_CHARACTER_REFERENCE(1258, /* l H */ 'a' _ 'r' _ ';', 3, 0, 0x2962 _ 0)
+NAMED_CHARACTER_REFERENCE(1259, /* l a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x013a _ 0)
+NAMED_CHARACTER_REFERENCE(1260, /* l a */ 'e' _ 'm' _ 'p' _ 't' _ 'y' _ 'v' _ ';', 7, 0, 0x29b4 _ 0)
+NAMED_CHARACTER_REFERENCE(1261, /* l a */ 'g' _ 'r' _ 'a' _ 'n' _ ';', 5, 0, 0x2112 _ 0)
+NAMED_CHARACTER_REFERENCE(1262, /* l a */ 'm' _ 'b' _ 'd' _ 'a' _ ';', 5, 0, 0x03bb _ 0)
+NAMED_CHARACTER_REFERENCE(1263, /* l a */ 'n' _ 'g' _ ';', 3, 0, 0x27e8 _ 0)
+NAMED_CHARACTER_REFERENCE(1264, /* l a */ 'n' _ 'g' _ 'd' _ ';', 4, 0, 0x2991 _ 0)
+NAMED_CHARACTER_REFERENCE(1265, /* l a */ 'n' _ 'g' _ 'l' _ 'e' _ ';', 5, 0, 0x27e8 _ 0)
+NAMED_CHARACTER_REFERENCE(1266, /* l a */ 'p' _ ';', 2, 0, 0x2a85 _ 0)
+NAMED_CHARACTER_REFERENCE(1267, /* l a */ 'q' _ 'u' _ 'o', 3, 0, 0x00ab _ 0)
+NAMED_CHARACTER_REFERENCE(1268, /* l a */ 'q' _ 'u' _ 'o' _ ';', 4, 0, 0x00ab _ 0)
+NAMED_CHARACTER_REFERENCE(1269, /* l a */ 'r' _ 'r' _ ';', 3, 0, 0x2190 _ 0)
+NAMED_CHARACTER_REFERENCE(1270, /* l a */ 'r' _ 'r' _ 'b' _ ';', 4, 0, 0x21e4 _ 0)
+NAMED_CHARACTER_REFERENCE(1271, /* l a */ 'r' _ 'r' _ 'b' _ 'f' _ 's' _ ';', 6, 0, 0x291f _ 0)
+NAMED_CHARACTER_REFERENCE(1272, /* l a */ 'r' _ 'r' _ 'f' _ 's' _ ';', 5, 0, 0x291d _ 0)
+NAMED_CHARACTER_REFERENCE(1273, /* l a */ 'r' _ 'r' _ 'h' _ 'k' _ ';', 5, 0, 0x21a9 _ 0)
+NAMED_CHARACTER_REFERENCE(1274, /* l a */ 'r' _ 'r' _ 'l' _ 'p' _ ';', 5, 0, 0x21ab _ 0)
+NAMED_CHARACTER_REFERENCE(1275, /* l a */ 'r' _ 'r' _ 'p' _ 'l' _ ';', 5, 0, 0x2939 _ 0)
+NAMED_CHARACTER_REFERENCE(1276, /* l a */ 'r' _ 'r' _ 's' _ 'i' _ 'm' _ ';', 6, 0, 0x2973 _ 0)
+NAMED_CHARACTER_REFERENCE(1277, /* l a */ 'r' _ 'r' _ 't' _ 'l' _ ';', 5, 0, 0x21a2 _ 0)
+NAMED_CHARACTER_REFERENCE(1278, /* l a */ 't' _ ';', 2, 0, 0x2aab _ 0)
+NAMED_CHARACTER_REFERENCE(1279, /* l a */ 't' _ 'a' _ 'i' _ 'l' _ ';', 5, 0, 0x2919 _ 0)
+NAMED_CHARACTER_REFERENCE(1280, /* l a */ 't' _ 'e' _ ';', 3, 0, 0x2aad _ 0)
+NAMED_CHARACTER_REFERENCE(1281, /* l a */ 't' _ 'e' _ 's' _ ';', 4, 0, 0x2aad _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(1282, /* l b */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x290c _ 0)
+NAMED_CHARACTER_REFERENCE(1283, /* l b */ 'b' _ 'r' _ 'k' _ ';', 4, 0, 0x2772 _ 0)
+NAMED_CHARACTER_REFERENCE(1284, /* l b */ 'r' _ 'a' _ 'c' _ 'e' _ ';', 5, 0, 0x007b _ 0)
+NAMED_CHARACTER_REFERENCE(1285, /* l b */ 'r' _ 'a' _ 'c' _ 'k' _ ';', 5, 0, 0x005b _ 0)
+NAMED_CHARACTER_REFERENCE(1286, /* l b */ 'r' _ 'k' _ 'e' _ ';', 4, 0, 0x298b _ 0)
+NAMED_CHARACTER_REFERENCE(1287, /* l b */ 'r' _ 'k' _ 's' _ 'l' _ 'd' _ ';', 6, 0, 0x298f _ 0)
+NAMED_CHARACTER_REFERENCE(1288, /* l b */ 'r' _ 'k' _ 's' _ 'l' _ 'u' _ ';', 6, 0, 0x298d _ 0)
+NAMED_CHARACTER_REFERENCE(1289, /* l c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x013e _ 0)
+NAMED_CHARACTER_REFERENCE(1290, /* l c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x013c _ 0)
+NAMED_CHARACTER_REFERENCE(1291, /* l c */ 'e' _ 'i' _ 'l' _ ';', 4, 0, 0x2308 _ 0)
+NAMED_CHARACTER_REFERENCE(1292, /* l c */ 'u' _ 'b' _ ';', 3, 0, 0x007b _ 0)
+NAMED_CHARACTER_REFERENCE(1293, /* l c */ 'y' _ ';', 2, 0, 0x043b _ 0)
+NAMED_CHARACTER_REFERENCE(1294, /* l d */ 'c' _ 'a' _ ';', 3, 0, 0x2936 _ 0)
+NAMED_CHARACTER_REFERENCE(1295, /* l d */ 'q' _ 'u' _ 'o' _ ';', 4, 0, 0x201c _ 0)
+NAMED_CHARACTER_REFERENCE(1296, /* l d */ 'q' _ 'u' _ 'o' _ 'r' _ ';', 5, 0, 0x201e _ 0)
+NAMED_CHARACTER_REFERENCE(1297, /* l d */ 'r' _ 'd' _ 'h' _ 'a' _ 'r' _ ';', 6, 0, 0x2967 _ 0)
+NAMED_CHARACTER_REFERENCE(1298, /* l d */ 'r' _ 'u' _ 's' _ 'h' _ 'a' _ 'r' _ ';', 7, 0, 0x294b _ 0)
+NAMED_CHARACTER_REFERENCE(1299, /* l d */ 's' _ 'h' _ ';', 3, 0, 0x21b2 _ 0)
+NAMED_CHARACTER_REFERENCE(1300, /* l e */ ';', 1, 0, 0x2264 _ 0)
+NAMED_CHARACTER_REFERENCE(1301, /* l e */ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 8, 0, 0x2190 _ 0)
+NAMED_CHARACTER_REFERENCE(1302, /* l e */ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 't' _ 'a' _ 'i' _ 'l' _ ';', 12, 0, 0x21a2 _ 0)
+NAMED_CHARACTER_REFERENCE(1303, /* l e */ 'f' _ 't' _ 'h' _ 'a' _ 'r' _ 'p' _ 'o' _ 'o' _ 'n' _ 'd' _ 'o' _ 'w' _ 'n' _ ';', 14, 0, 0x21bd _ 0)
+NAMED_CHARACTER_REFERENCE(1304, /* l e */ 'f' _ 't' _ 'h' _ 'a' _ 'r' _ 'p' _ 'o' _ 'o' _ 'n' _ 'u' _ 'p' _ ';', 12, 0, 0x21bc _ 0)
+NAMED_CHARACTER_REFERENCE(1305, /* l e */ 'f' _ 't' _ 'l' _ 'e' _ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 's' _ ';', 13, 0, 0x21c7 _ 0)
+NAMED_CHARACTER_REFERENCE(1306, /* l e */ 'f' _ 't' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x2194 _ 0)
+NAMED_CHARACTER_REFERENCE(1307, /* l e */ 'f' _ 't' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 's' _ ';', 14, 0, 0x21c6 _ 0)
+NAMED_CHARACTER_REFERENCE(1308, /* l e */ 'f' _ 't' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'h' _ 'a' _ 'r' _ 'p' _ 'o' _ 'o' _ 'n' _ 's' _ ';', 16, 0, 0x21cb _ 0)
+NAMED_CHARACTER_REFERENCE(1309, /* l e */ 'f' _ 't' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 's' _ 'q' _ 'u' _ 'i' _ 'g' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 18, 0, 0x21ad _ 0)
+NAMED_CHARACTER_REFERENCE(1310, /* l e */ 'f' _ 't' _ 't' _ 'h' _ 'r' _ 'e' _ 'e' _ 't' _ 'i' _ 'm' _ 'e' _ 's' _ ';', 13, 0, 0x22cb _ 0)
+NAMED_CHARACTER_REFERENCE(1311, /* l e */ 'g' _ ';', 2, 0, 0x22da _ 0)
+NAMED_CHARACTER_REFERENCE(1312, /* l e */ 'q' _ ';', 2, 0, 0x2264 _ 0)
+NAMED_CHARACTER_REFERENCE(1313, /* l e */ 'q' _ 'q' _ ';', 3, 0, 0x2266 _ 0)
+NAMED_CHARACTER_REFERENCE(1314, /* l e */ 'q' _ 's' _ 'l' _ 'a' _ 'n' _ 't' _ ';', 7, 0, 0x2a7d _ 0)
+NAMED_CHARACTER_REFERENCE(1315, /* l e */ 's' _ ';', 2, 0, 0x2a7d _ 0)
+NAMED_CHARACTER_REFERENCE(1316, /* l e */ 's' _ 'c' _ 'c' _ ';', 4, 0, 0x2aa8 _ 0)
+NAMED_CHARACTER_REFERENCE(1317, /* l e */ 's' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x2a7f _ 0)
+NAMED_CHARACTER_REFERENCE(1318, /* l e */ 's' _ 'd' _ 'o' _ 't' _ 'o' _ ';', 6, 0, 0x2a81 _ 0)
+NAMED_CHARACTER_REFERENCE(1319, /* l e */ 's' _ 'd' _ 'o' _ 't' _ 'o' _ 'r' _ ';', 7, 0, 0x2a83 _ 0)
+NAMED_CHARACTER_REFERENCE(1320, /* l e */ 's' _ 'g' _ ';', 3, 0, 0x22da _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(1321, /* l e */ 's' _ 'g' _ 'e' _ 's' _ ';', 5, 0, 0x2a93 _ 0)
+NAMED_CHARACTER_REFERENCE(1322, /* l e */ 's' _ 's' _ 'a' _ 'p' _ 'p' _ 'r' _ 'o' _ 'x' _ ';', 9, 0, 0x2a85 _ 0)
+NAMED_CHARACTER_REFERENCE(1323, /* l e */ 's' _ 's' _ 'd' _ 'o' _ 't' _ ';', 6, 0, 0x22d6 _ 0)
+NAMED_CHARACTER_REFERENCE(1324, /* l e */ 's' _ 's' _ 'e' _ 'q' _ 'g' _ 't' _ 'r' _ ';', 8, 0, 0x22da _ 0)
+NAMED_CHARACTER_REFERENCE(1325, /* l e */ 's' _ 's' _ 'e' _ 'q' _ 'q' _ 'g' _ 't' _ 'r' _ ';', 9, 0, 0x2a8b _ 0)
+NAMED_CHARACTER_REFERENCE(1326, /* l e */ 's' _ 's' _ 'g' _ 't' _ 'r' _ ';', 6, 0, 0x2276 _ 0)
+NAMED_CHARACTER_REFERENCE(1327, /* l e */ 's' _ 's' _ 's' _ 'i' _ 'm' _ ';', 6, 0, 0x2272 _ 0)
+NAMED_CHARACTER_REFERENCE(1328, /* l f */ 'i' _ 's' _ 'h' _ 't' _ ';', 5, 0, 0x297c _ 0)
+NAMED_CHARACTER_REFERENCE(1329, /* l f */ 'l' _ 'o' _ 'o' _ 'r' _ ';', 5, 0, 0x230a _ 0)
+NAMED_CHARACTER_REFERENCE(1330, /* l f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd29)
+NAMED_CHARACTER_REFERENCE(1331, /* l g */ ';', 1, 0, 0x2276 _ 0)
+NAMED_CHARACTER_REFERENCE(1332, /* l g */ 'E' _ ';', 2, 0, 0x2a91 _ 0)
+NAMED_CHARACTER_REFERENCE(1333, /* l h */ 'a' _ 'r' _ 'd' _ ';', 4, 0, 0x21bd _ 0)
+NAMED_CHARACTER_REFERENCE(1334, /* l h */ 'a' _ 'r' _ 'u' _ ';', 4, 0, 0x21bc _ 0)
+NAMED_CHARACTER_REFERENCE(1335, /* l h */ 'a' _ 'r' _ 'u' _ 'l' _ ';', 5, 0, 0x296a _ 0)
+NAMED_CHARACTER_REFERENCE(1336, /* l h */ 'b' _ 'l' _ 'k' _ ';', 4, 0, 0x2584 _ 0)
+NAMED_CHARACTER_REFERENCE(1337, /* l j */ 'c' _ 'y' _ ';', 3, 0, 0x0459 _ 0)
+NAMED_CHARACTER_REFERENCE(1338, /* l l */ ';', 1, 0, 0x226a _ 0)
+NAMED_CHARACTER_REFERENCE(1339, /* l l */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21c7 _ 0)
+NAMED_CHARACTER_REFERENCE(1340, /* l l */ 'c' _ 'o' _ 'r' _ 'n' _ 'e' _ 'r' _ ';', 7, 0, 0x231e _ 0)
+NAMED_CHARACTER_REFERENCE(1341, /* l l */ 'h' _ 'a' _ 'r' _ 'd' _ ';', 5, 0, 0x296b _ 0)
+NAMED_CHARACTER_REFERENCE(1342, /* l l */ 't' _ 'r' _ 'i' _ ';', 4, 0, 0x25fa _ 0)
+NAMED_CHARACTER_REFERENCE(1343, /* l m */ 'i' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x0140 _ 0)
+NAMED_CHARACTER_REFERENCE(1344, /* l m */ 'o' _ 'u' _ 's' _ 't' _ ';', 5, 0, 0x23b0 _ 0)
+NAMED_CHARACTER_REFERENCE(1345, /* l m */ 'o' _ 'u' _ 's' _ 't' _ 'a' _ 'c' _ 'h' _ 'e' _ ';', 9, 0, 0x23b0 _ 0)
+NAMED_CHARACTER_REFERENCE(1346, /* l n */ 'E' _ ';', 2, 0, 0x2268 _ 0)
+NAMED_CHARACTER_REFERENCE(1347, /* l n */ 'a' _ 'p' _ ';', 3, 0, 0x2a89 _ 0)
+NAMED_CHARACTER_REFERENCE(1348, /* l n */ 'a' _ 'p' _ 'p' _ 'r' _ 'o' _ 'x' _ ';', 7, 0, 0x2a89 _ 0)
+NAMED_CHARACTER_REFERENCE(1349, /* l n */ 'e' _ ';', 2, 0, 0x2a87 _ 0)
+NAMED_CHARACTER_REFERENCE(1350, /* l n */ 'e' _ 'q' _ ';', 3, 0, 0x2a87 _ 0)
+NAMED_CHARACTER_REFERENCE(1351, /* l n */ 'e' _ 'q' _ 'q' _ ';', 4, 0, 0x2268 _ 0)
+NAMED_CHARACTER_REFERENCE(1352, /* l n */ 's' _ 'i' _ 'm' _ ';', 4, 0, 0x22e6 _ 0)
+NAMED_CHARACTER_REFERENCE(1353, /* l o */ 'a' _ 'n' _ 'g' _ ';', 4, 0, 0x27ec _ 0)
+NAMED_CHARACTER_REFERENCE(1354, /* l o */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21fd _ 0)
+NAMED_CHARACTER_REFERENCE(1355, /* l o */ 'b' _ 'r' _ 'k' _ ';', 4, 0, 0x27e6 _ 0)
+NAMED_CHARACTER_REFERENCE(1356, /* l o */ 'n' _ 'g' _ 'l' _ 'e' _ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 12, 0, 0x27f5 _ 0)
+NAMED_CHARACTER_REFERENCE(1357, /* l o */ 'n' _ 'g' _ 'l' _ 'e' _ 'f' _ 't' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 17, 0, 0x27f7 _ 0)
+NAMED_CHARACTER_REFERENCE(1358, /* l o */ 'n' _ 'g' _ 'm' _ 'a' _ 'p' _ 's' _ 't' _ 'o' _ ';', 9, 0, 0x27fc _ 0)
+NAMED_CHARACTER_REFERENCE(1359, /* l o */ 'n' _ 'g' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 13, 0, 0x27f6 _ 0)
+NAMED_CHARACTER_REFERENCE(1360, /* l o */ 'o' _ 'p' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 'l' _ 'e' _ 'f' _ 't' _ ';', 12, 0, 0x21ab _ 0)
+NAMED_CHARACTER_REFERENCE(1361, /* l o */ 'o' _ 'p' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ ';', 13, 0, 0x21ac _ 0)
+NAMED_CHARACTER_REFERENCE(1362, /* l o */ 'p' _ 'a' _ 'r' _ ';', 4, 0, 0x2985 _ 0)
+NAMED_CHARACTER_REFERENCE(1363, /* l o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd5d)
+NAMED_CHARACTER_REFERENCE(1364, /* l o */ 'p' _ 'l' _ 'u' _ 's' _ ';', 5, 0, 0x2a2d _ 0)
+NAMED_CHARACTER_REFERENCE(1365, /* l o */ 't' _ 'i' _ 'm' _ 'e' _ 's' _ ';', 6, 0, 0x2a34 _ 0)
+NAMED_CHARACTER_REFERENCE(1366, /* l o */ 'w' _ 'a' _ 's' _ 't' _ ';', 5, 0, 0x2217 _ 0)
+NAMED_CHARACTER_REFERENCE(1367, /* l o */ 'w' _ 'b' _ 'a' _ 'r' _ ';', 5, 0, 0x005f _ 0)
+NAMED_CHARACTER_REFERENCE(1368, /* l o */ 'z' _ ';', 2, 0, 0x25ca _ 0)
+NAMED_CHARACTER_REFERENCE(1369, /* l o */ 'z' _ 'e' _ 'n' _ 'g' _ 'e' _ ';', 6, 0, 0x25ca _ 0)
+NAMED_CHARACTER_REFERENCE(1370, /* l o */ 'z' _ 'f' _ ';', 3, 0, 0x29eb _ 0)
+NAMED_CHARACTER_REFERENCE(1371, /* l p */ 'a' _ 'r' _ ';', 3, 0, 0x0028 _ 0)
+NAMED_CHARACTER_REFERENCE(1372, /* l p */ 'a' _ 'r' _ 'l' _ 't' _ ';', 5, 0, 0x2993 _ 0)
+NAMED_CHARACTER_REFERENCE(1373, /* l r */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21c6 _ 0)
+NAMED_CHARACTER_REFERENCE(1374, /* l r */ 'c' _ 'o' _ 'r' _ 'n' _ 'e' _ 'r' _ ';', 7, 0, 0x231f _ 0)
+NAMED_CHARACTER_REFERENCE(1375, /* l r */ 'h' _ 'a' _ 'r' _ ';', 4, 0, 0x21cb _ 0)
+NAMED_CHARACTER_REFERENCE(1376, /* l r */ 'h' _ 'a' _ 'r' _ 'd' _ ';', 5, 0, 0x296d _ 0)
+NAMED_CHARACTER_REFERENCE(1377, /* l r */ 'm' _ ';', 2, 0, 0x200e _ 0)
+NAMED_CHARACTER_REFERENCE(1378, /* l r */ 't' _ 'r' _ 'i' _ ';', 4, 0, 0x22bf _ 0)
+NAMED_CHARACTER_REFERENCE(1379, /* l s */ 'a' _ 'q' _ 'u' _ 'o' _ ';', 5, 0, 0x2039 _ 0)
+NAMED_CHARACTER_REFERENCE(1380, /* l s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcc1)
+NAMED_CHARACTER_REFERENCE(1381, /* l s */ 'h' _ ';', 2, 0, 0x21b0 _ 0)
+NAMED_CHARACTER_REFERENCE(1382, /* l s */ 'i' _ 'm' _ ';', 3, 0, 0x2272 _ 0)
+NAMED_CHARACTER_REFERENCE(1383, /* l s */ 'i' _ 'm' _ 'e' _ ';', 4, 0, 0x2a8d _ 0)
+NAMED_CHARACTER_REFERENCE(1384, /* l s */ 'i' _ 'm' _ 'g' _ ';', 4, 0, 0x2a8f _ 0)
+NAMED_CHARACTER_REFERENCE(1385, /* l s */ 'q' _ 'b' _ ';', 3, 0, 0x005b _ 0)
+NAMED_CHARACTER_REFERENCE(1386, /* l s */ 'q' _ 'u' _ 'o' _ ';', 4, 0, 0x2018 _ 0)
+NAMED_CHARACTER_REFERENCE(1387, /* l s */ 'q' _ 'u' _ 'o' _ 'r' _ ';', 5, 0, 0x201a _ 0)
+NAMED_CHARACTER_REFERENCE(1388, /* l s */ 't' _ 'r' _ 'o' _ 'k' _ ';', 5, 0, 0x0142 _ 0)
+NAMED_CHARACTER_REFERENCE(1389, /* l t */ 0, 0, 1, 0x003c _ 0)
+NAMED_CHARACTER_REFERENCE(1390, /* l t */ ';', 1, 0, 0x003c _ 0)
+NAMED_CHARACTER_REFERENCE(1391, /* l t */ 'c' _ 'c' _ ';', 3, 0, 0x2aa6 _ 0)
+NAMED_CHARACTER_REFERENCE(1392, /* l t */ 'c' _ 'i' _ 'r' _ ';', 4, 0, 0x2a79 _ 0)
+NAMED_CHARACTER_REFERENCE(1393, /* l t */ 'd' _ 'o' _ 't' _ ';', 4, 0, 0x22d6 _ 0)
+NAMED_CHARACTER_REFERENCE(1394, /* l t */ 'h' _ 'r' _ 'e' _ 'e' _ ';', 5, 0, 0x22cb _ 0)
+NAMED_CHARACTER_REFERENCE(1395, /* l t */ 'i' _ 'm' _ 'e' _ 's' _ ';', 5, 0, 0x22c9 _ 0)
+NAMED_CHARACTER_REFERENCE(1396, /* l t */ 'l' _ 'a' _ 'r' _ 'r' _ ';', 5, 0, 0x2976 _ 0)
+NAMED_CHARACTER_REFERENCE(1397, /* l t */ 'q' _ 'u' _ 'e' _ 's' _ 't' _ ';', 6, 0, 0x2a7b _ 0)
+NAMED_CHARACTER_REFERENCE(1398, /* l t */ 'r' _ 'P' _ 'a' _ 'r' _ ';', 5, 0, 0x2996 _ 0)
+NAMED_CHARACTER_REFERENCE(1399, /* l t */ 'r' _ 'i' _ ';', 3, 0, 0x25c3 _ 0)
+NAMED_CHARACTER_REFERENCE(1400, /* l t */ 'r' _ 'i' _ 'e' _ ';', 4, 0, 0x22b4 _ 0)
+NAMED_CHARACTER_REFERENCE(1401, /* l t */ 'r' _ 'i' _ 'f' _ ';', 4, 0, 0x25c2 _ 0)
+NAMED_CHARACTER_REFERENCE(1402, /* l u */ 'r' _ 'd' _ 's' _ 'h' _ 'a' _ 'r' _ ';', 7, 0, 0x294a _ 0)
+NAMED_CHARACTER_REFERENCE(1403, /* l u */ 'r' _ 'u' _ 'h' _ 'a' _ 'r' _ ';', 6, 0, 0x2966 _ 0)
+NAMED_CHARACTER_REFERENCE(1404, /* l v */ 'e' _ 'r' _ 't' _ 'n' _ 'e' _ 'q' _ 'q' _ ';', 8, 0, 0x2268 _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(1405, /* l v */ 'n' _ 'E' _ ';', 3, 0, 0x2268 _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(1406, /* m D */ 'D' _ 'o' _ 't' _ ';', 4, 0, 0x223a _ 0)
+NAMED_CHARACTER_REFERENCE(1407, /* m a */ 'c' _ 'r', 2, 0, 0x00af _ 0)
+NAMED_CHARACTER_REFERENCE(1408, /* m a */ 'c' _ 'r' _ ';', 3, 0, 0x00af _ 0)
+NAMED_CHARACTER_REFERENCE(1409, /* m a */ 'l' _ 'e' _ ';', 3, 0, 0x2642 _ 0)
+NAMED_CHARACTER_REFERENCE(1410, /* m a */ 'l' _ 't' _ ';', 3, 0, 0x2720 _ 0)
+NAMED_CHARACTER_REFERENCE(1411, /* m a */ 'l' _ 't' _ 'e' _ 's' _ 'e' _ ';', 6, 0, 0x2720 _ 0)
+NAMED_CHARACTER_REFERENCE(1412, /* m a */ 'p' _ ';', 2, 0, 0x21a6 _ 0)
+NAMED_CHARACTER_REFERENCE(1413, /* m a */ 'p' _ 's' _ 't' _ 'o' _ ';', 5, 0, 0x21a6 _ 0)
+NAMED_CHARACTER_REFERENCE(1414, /* m a */ 'p' _ 's' _ 't' _ 'o' _ 'd' _ 'o' _ 'w' _ 'n' _ ';', 9, 0, 0x21a7 _ 0)
+NAMED_CHARACTER_REFERENCE(1415, /* m a */ 'p' _ 's' _ 't' _ 'o' _ 'l' _ 'e' _ 'f' _ 't' _ ';', 9, 0, 0x21a4 _ 0)
+NAMED_CHARACTER_REFERENCE(1416, /* m a */ 'p' _ 's' _ 't' _ 'o' _ 'u' _ 'p' _ ';', 7, 0, 0x21a5 _ 0)
+NAMED_CHARACTER_REFERENCE(1417, /* m a */ 'r' _ 'k' _ 'e' _ 'r' _ ';', 5, 0, 0x25ae _ 0)
+NAMED_CHARACTER_REFERENCE(1418, /* m c */ 'o' _ 'm' _ 'm' _ 'a' _ ';', 5, 0, 0x2a29 _ 0)
+NAMED_CHARACTER_REFERENCE(1419, /* m c */ 'y' _ ';', 2, 0, 0x043c _ 0)
+NAMED_CHARACTER_REFERENCE(1420, /* m d */ 'a' _ 's' _ 'h' _ ';', 4, 0, 0x2014 _ 0)
+NAMED_CHARACTER_REFERENCE(1421, /* m e */ 'a' _ 's' _ 'u' _ 'r' _ 'e' _ 'd' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ ';', 12, 0, 0x2221 _ 0)
+NAMED_CHARACTER_REFERENCE(1422, /* m f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd2a)
+NAMED_CHARACTER_REFERENCE(1423, /* m h */ 'o' _ ';', 2, 0, 0x2127 _ 0)
+NAMED_CHARACTER_REFERENCE(1424, /* m i */ 'c' _ 'r' _ 'o', 3, 0, 0x00b5 _ 0)
+NAMED_CHARACTER_REFERENCE(1425, /* m i */ 'c' _ 'r' _ 'o' _ ';', 4, 0, 0x00b5 _ 0)
+NAMED_CHARACTER_REFERENCE(1426, /* m i */ 'd' _ ';', 2, 0, 0x2223 _ 0)
+NAMED_CHARACTER_REFERENCE(1427, /* m i */ 'd' _ 'a' _ 's' _ 't' _ ';', 5, 0, 0x002a _ 0)
+NAMED_CHARACTER_REFERENCE(1428, /* m i */ 'd' _ 'c' _ 'i' _ 'r' _ ';', 5, 0, 0x2af0 _ 0)
+NAMED_CHARACTER_REFERENCE(1429, /* m i */ 'd' _ 'd' _ 'o' _ 't', 4, 0, 0x00b7 _ 0)
+NAMED_CHARACTER_REFERENCE(1430, /* m i */ 'd' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x00b7 _ 0)
+NAMED_CHARACTER_REFERENCE(1431, /* m i */ 'n' _ 'u' _ 's' _ ';', 4, 0, 0x2212 _ 0)
+NAMED_CHARACTER_REFERENCE(1432, /* m i */ 'n' _ 'u' _ 's' _ 'b' _ ';', 5, 0, 0x229f _ 0)
+NAMED_CHARACTER_REFERENCE(1433, /* m i */ 'n' _ 'u' _ 's' _ 'd' _ ';', 5, 0, 0x2238 _ 0)
+NAMED_CHARACTER_REFERENCE(1434, /* m i */ 'n' _ 'u' _ 's' _ 'd' _ 'u' _ ';', 6, 0, 0x2a2a _ 0)
+NAMED_CHARACTER_REFERENCE(1435, /* m l */ 'c' _ 'p' _ ';', 3, 0, 0x2adb _ 0)
+NAMED_CHARACTER_REFERENCE(1436, /* m l */ 'd' _ 'r' _ ';', 3, 0, 0x2026 _ 0)
+NAMED_CHARACTER_REFERENCE(1437, /* m n */ 'p' _ 'l' _ 'u' _ 's' _ ';', 5, 0, 0x2213 _ 0)
+NAMED_CHARACTER_REFERENCE(1438, /* m o */ 'd' _ 'e' _ 'l' _ 's' _ ';', 5, 0, 0x22a7 _ 0)
+NAMED_CHARACTER_REFERENCE(1439, /* m o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd5e)
+NAMED_CHARACTER_REFERENCE(1440, /* m p */ ';', 1, 0, 0x2213 _ 0)
+NAMED_CHARACTER_REFERENCE(1441, /* m s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcc2)
+NAMED_CHARACTER_REFERENCE(1442, /* m s */ 't' _ 'p' _ 'o' _ 's' _ ';', 5, 0, 0x223e _ 0)
+NAMED_CHARACTER_REFERENCE(1443, /* m u */ ';', 1, 0, 0x03bc _ 0)
+NAMED_CHARACTER_REFERENCE(1444, /* m u */ 'l' _ 't' _ 'i' _ 'm' _ 'a' _ 'p' _ ';', 7, 0, 0x22b8 _ 0)
+NAMED_CHARACTER_REFERENCE(1445, /* m u */ 'm' _ 'a' _ 'p' _ ';', 4, 0, 0x22b8 _ 0)
+NAMED_CHARACTER_REFERENCE(1446, /* n G */ 'g' _ ';', 2, 0, 0x22d9 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1447, /* n G */ 't' _ ';', 2, 0, 0x226b _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1448, /* n G */ 't' _ 'v' _ ';', 3, 0, 0x226b _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1449, /* n L */ 'e' _ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 9, 0, 0x21cd _ 0)
+NAMED_CHARACTER_REFERENCE(1450, /* n L */ 'e' _ 'f' _ 't' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 14, 0, 0x21ce _ 0)
+NAMED_CHARACTER_REFERENCE(1451, /* n L */ 'l' _ ';', 2, 0, 0x22d8 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1452, /* n L */ 't' _ ';', 2, 0, 0x226a _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1453, /* n L */ 't' _ 'v' _ ';', 3, 0, 0x226a _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1454, /* n R */ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 10, 0, 0x21cf _ 0)
+NAMED_CHARACTER_REFERENCE(1455, /* n V */ 'D' _ 'a' _ 's' _ 'h' _ ';', 5, 0, 0x22af _ 0)
+NAMED_CHARACTER_REFERENCE(1456, /* n V */ 'd' _ 'a' _ 's' _ 'h' _ ';', 5, 0, 0x22ae _ 0)
+NAMED_CHARACTER_REFERENCE(1457, /* n a */ 'b' _ 'l' _ 'a' _ ';', 4, 0, 0x2207 _ 0)
+NAMED_CHARACTER_REFERENCE(1458, /* n a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x0144 _ 0)
+NAMED_CHARACTER_REFERENCE(1459, /* n a */ 'n' _ 'g' _ ';', 3, 0, 0x2220 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1460, /* n a */ 'p' _ ';', 2, 0, 0x2249 _ 0)
+NAMED_CHARACTER_REFERENCE(1461, /* n a */ 'p' _ 'E' _ ';', 3, 0, 0x2a70 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1462, /* n a */ 'p' _ 'i' _ 'd' _ ';', 4, 0, 0x224b _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1463, /* n a */ 'p' _ 'o' _ 's' _ ';', 4, 0, 0x0149 _ 0)
+NAMED_CHARACTER_REFERENCE(1464, /* n a */ 'p' _ 'p' _ 'r' _ 'o' _ 'x' _ ';', 6, 0, 0x2249 _ 0)
+NAMED_CHARACTER_REFERENCE(1465, /* n a */ 't' _ 'u' _ 'r' _ ';', 4, 0, 0x266e _ 0)
+NAMED_CHARACTER_REFERENCE(1466, /* n a */ 't' _ 'u' _ 'r' _ 'a' _ 'l' _ ';', 6, 0, 0x266e _ 0)
+NAMED_CHARACTER_REFERENCE(1467, /* n a */ 't' _ 'u' _ 'r' _ 'a' _ 'l' _ 's' _ ';', 7, 0, 0x2115 _ 0)
+NAMED_CHARACTER_REFERENCE(1468, /* n b */ 's' _ 'p', 2, 0, 0x00a0 _ 0)
+NAMED_CHARACTER_REFERENCE(1469, /* n b */ 's' _ 'p' _ ';', 3, 0, 0x00a0 _ 0)
+NAMED_CHARACTER_REFERENCE(1470, /* n b */ 'u' _ 'm' _ 'p' _ ';', 4, 0, 0x224e _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1471, /* n b */ 'u' _ 'm' _ 'p' _ 'e' _ ';', 5, 0, 0x224f _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1472, /* n c */ 'a' _ 'p' _ ';', 3, 0, 0x2a43 _ 0)
+NAMED_CHARACTER_REFERENCE(1473, /* n c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x0148 _ 0)
+NAMED_CHARACTER_REFERENCE(1474, /* n c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x0146 _ 0)
+NAMED_CHARACTER_REFERENCE(1475, /* n c */ 'o' _ 'n' _ 'g' _ ';', 4, 0, 0x2247 _ 0)
+NAMED_CHARACTER_REFERENCE(1476, /* n c */ 'o' _ 'n' _ 'g' _ 'd' _ 'o' _ 't' _ ';', 7, 0, 0x2a6d _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1477, /* n c */ 'u' _ 'p' _ ';', 3, 0, 0x2a42 _ 0)
+NAMED_CHARACTER_REFERENCE(1478, /* n c */ 'y' _ ';', 2, 0, 0x043d _ 0)
+NAMED_CHARACTER_REFERENCE(1479, /* n d */ 'a' _ 's' _ 'h' _ ';', 4, 0, 0x2013 _ 0)
+NAMED_CHARACTER_REFERENCE(1480, /* n e */ ';', 1, 0, 0x2260 _ 0)
+NAMED_CHARACTER_REFERENCE(1481, /* n e */ 'A' _ 'r' _ 'r' _ ';', 4, 0, 0x21d7 _ 0)
+NAMED_CHARACTER_REFERENCE(1482, /* n e */ 'a' _ 'r' _ 'h' _ 'k' _ ';', 5, 0, 0x2924 _ 0)
+NAMED_CHARACTER_REFERENCE(1483, /* n e */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x2197 _ 0)
+NAMED_CHARACTER_REFERENCE(1484, /* n e */ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 6, 0, 0x2197 _ 0)
+NAMED_CHARACTER_REFERENCE(1485, /* n e */ 'd' _ 'o' _ 't' _ ';', 4, 0, 0x2250 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1486, /* n e */ 'q' _ 'u' _ 'i' _ 'v' _ ';', 5, 0, 0x2262 _ 0)
+NAMED_CHARACTER_REFERENCE(1487, /* n e */ 's' _ 'e' _ 'a' _ 'r' _ ';', 5, 0, 0x2928 _ 0)
+NAMED_CHARACTER_REFERENCE(1488, /* n e */ 's' _ 'i' _ 'm' _ ';', 4, 0, 0x2242 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1489, /* n e */ 'x' _ 'i' _ 's' _ 't' _ ';', 5, 0, 0x2204 _ 0)
+NAMED_CHARACTER_REFERENCE(1490, /* n e */ 'x' _ 'i' _ 's' _ 't' _ 's' _ ';', 6, 0, 0x2204 _ 0)
+NAMED_CHARACTER_REFERENCE(1491, /* n f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd2b)
+NAMED_CHARACTER_REFERENCE(1492, /* n g */ 'E' _ ';', 2, 0, 0x2267 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1493, /* n g */ 'e' _ ';', 2, 0, 0x2271 _ 0)
+NAMED_CHARACTER_REFERENCE(1494, /* n g */ 'e' _ 'q' _ ';', 3, 0, 0x2271 _ 0)
+NAMED_CHARACTER_REFERENCE(1495, /* n g */ 'e' _ 'q' _ 'q' _ ';', 4, 0, 0x2267 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1496, /* n g */ 'e' _ 'q' _ 's' _ 'l' _ 'a' _ 'n' _ 't' _ ';', 8, 0, 0x2a7e _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1497, /* n g */ 'e' _ 's' _ ';', 3, 0, 0x2a7e _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1498, /* n g */ 's' _ 'i' _ 'm' _ ';', 4, 0, 0x2275 _ 0)
+NAMED_CHARACTER_REFERENCE(1499, /* n g */ 't' _ ';', 2, 0, 0x226f _ 0)
+NAMED_CHARACTER_REFERENCE(1500, /* n g */ 't' _ 'r' _ ';', 3, 0, 0x226f _ 0)
+NAMED_CHARACTER_REFERENCE(1501, /* n h */ 'A' _ 'r' _ 'r' _ ';', 4, 0, 0x21ce _ 0)
+NAMED_CHARACTER_REFERENCE(1502, /* n h */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21ae _ 0)
+NAMED_CHARACTER_REFERENCE(1503, /* n h */ 'p' _ 'a' _ 'r' _ ';', 4, 0, 0x2af2 _ 0)
+NAMED_CHARACTER_REFERENCE(1504, /* n i */ ';', 1, 0, 0x220b _ 0)
+NAMED_CHARACTER_REFERENCE(1505, /* n i */ 's' _ ';', 2, 0, 0x22fc _ 0)
+NAMED_CHARACTER_REFERENCE(1506, /* n i */ 's' _ 'd' _ ';', 3, 0, 0x22fa _ 0)
+NAMED_CHARACTER_REFERENCE(1507, /* n i */ 'v' _ ';', 2, 0, 0x220b _ 0)
+NAMED_CHARACTER_REFERENCE(1508, /* n j */ 'c' _ 'y' _ ';', 3, 0, 0x045a _ 0)
+NAMED_CHARACTER_REFERENCE(1509, /* n l */ 'A' _ 'r' _ 'r' _ ';', 4, 0, 0x21cd _ 0)
+NAMED_CHARACTER_REFERENCE(1510, /* n l */ 'E' _ ';', 2, 0, 0x2266 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1511, /* n l */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x219a _ 0)
+NAMED_CHARACTER_REFERENCE(1512, /* n l */ 'd' _ 'r' _ ';', 3, 0, 0x2025 _ 0)
+NAMED_CHARACTER_REFERENCE(1513, /* n l */ 'e' _ ';', 2, 0, 0x2270 _ 0)
+NAMED_CHARACTER_REFERENCE(1514, /* n l */ 'e' _ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 9, 0, 0x219a _ 0)
+NAMED_CHARACTER_REFERENCE(1515, /* n l */ 'e' _ 'f' _ 't' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 14, 0, 0x21ae _ 0)
+NAMED_CHARACTER_REFERENCE(1516, /* n l */ 'e' _ 'q' _ ';', 3, 0, 0x2270 _ 0)
+NAMED_CHARACTER_REFERENCE(1517, /* n l */ 'e' _ 'q' _ 'q' _ ';', 4, 0, 0x2266 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1518, /* n l */ 'e' _ 'q' _ 's' _ 'l' _ 'a' _ 'n' _ 't' _ ';', 8, 0, 0x2a7d _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1519, /* n l */ 'e' _ 's' _ ';', 3, 0, 0x2a7d _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1520, /* n l */ 'e' _ 's' _ 's' _ ';', 4, 0, 0x226e _ 0)
+NAMED_CHARACTER_REFERENCE(1521, /* n l */ 's' _ 'i' _ 'm' _ ';', 4, 0, 0x2274 _ 0)
+NAMED_CHARACTER_REFERENCE(1522, /* n l */ 't' _ ';', 2, 0, 0x226e _ 0)
+NAMED_CHARACTER_REFERENCE(1523, /* n l */ 't' _ 'r' _ 'i' _ ';', 4, 0, 0x22ea _ 0)
+NAMED_CHARACTER_REFERENCE(1524, /* n l */ 't' _ 'r' _ 'i' _ 'e' _ ';', 5, 0, 0x22ec _ 0)
+NAMED_CHARACTER_REFERENCE(1525, /* n m */ 'i' _ 'd' _ ';', 3, 0, 0x2224 _ 0)
+NAMED_CHARACTER_REFERENCE(1526, /* n o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd5f)
+NAMED_CHARACTER_REFERENCE(1527, /* n o */ 't', 1, 0, 0x00ac _ 0)
+NAMED_CHARACTER_REFERENCE(1528, /* n o */ 't' _ ';', 2, 0, 0x00ac _ 0)
+NAMED_CHARACTER_REFERENCE(1529, /* n o */ 't' _ 'i' _ 'n' _ ';', 4, 0, 0x2209 _ 0)
+NAMED_CHARACTER_REFERENCE(1530, /* n o */ 't' _ 'i' _ 'n' _ 'E' _ ';', 5, 0, 0x22f9 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1531, /* n o */ 't' _ 'i' _ 'n' _ 'd' _ 'o' _ 't' _ ';', 7, 0, 0x22f5 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1532, /* n o */ 't' _ 'i' _ 'n' _ 'v' _ 'a' _ ';', 6, 0, 0x2209 _ 0)
+NAMED_CHARACTER_REFERENCE(1533, /* n o */ 't' _ 'i' _ 'n' _ 'v' _ 'b' _ ';', 6, 0, 0x22f7 _ 0)
+NAMED_CHARACTER_REFERENCE(1534, /* n o */ 't' _ 'i' _ 'n' _ 'v' _ 'c' _ ';', 6, 0, 0x22f6 _ 0)
+NAMED_CHARACTER_REFERENCE(1535, /* n o */ 't' _ 'n' _ 'i' _ ';', 4, 0, 0x220c _ 0)
+NAMED_CHARACTER_REFERENCE(1536, /* n o */ 't' _ 'n' _ 'i' _ 'v' _ 'a' _ ';', 6, 0, 0x220c _ 0)
+NAMED_CHARACTER_REFERENCE(1537, /* n o */ 't' _ 'n' _ 'i' _ 'v' _ 'b' _ ';', 6, 0, 0x22fe _ 0)
+NAMED_CHARACTER_REFERENCE(1538, /* n o */ 't' _ 'n' _ 'i' _ 'v' _ 'c' _ ';', 6, 0, 0x22fd _ 0)
+NAMED_CHARACTER_REFERENCE(1539, /* n p */ 'a' _ 'r' _ ';', 3, 0, 0x2226 _ 0)
+NAMED_CHARACTER_REFERENCE(1540, /* n p */ 'a' _ 'r' _ 'a' _ 'l' _ 'l' _ 'e' _ 'l' _ ';', 8, 0, 0x2226 _ 0)
+NAMED_CHARACTER_REFERENCE(1541, /* n p */ 'a' _ 'r' _ 's' _ 'l' _ ';', 5, 0, 0x2afd _ 0x20e5)
+NAMED_CHARACTER_REFERENCE(1542, /* n p */ 'a' _ 'r' _ 't' _ ';', 4, 0, 0x2202 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1543, /* n p */ 'o' _ 'l' _ 'i' _ 'n' _ 't' _ ';', 6, 0, 0x2a14 _ 0)
+NAMED_CHARACTER_REFERENCE(1544, /* n p */ 'r' _ ';', 2, 0, 0x2280 _ 0)
+NAMED_CHARACTER_REFERENCE(1545, /* n p */ 'r' _ 'c' _ 'u' _ 'e' _ ';', 5, 0, 0x22e0 _ 0)
+NAMED_CHARACTER_REFERENCE(1546, /* n p */ 'r' _ 'e' _ ';', 3, 0, 0x2aaf _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1547, /* n p */ 'r' _ 'e' _ 'c' _ ';', 4, 0, 0x2280 _ 0)
+NAMED_CHARACTER_REFERENCE(1548, /* n p */ 'r' _ 'e' _ 'c' _ 'e' _ 'q' _ ';', 6, 0, 0x2aaf _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1549, /* n r */ 'A' _ 'r' _ 'r' _ ';', 4, 0, 0x21cf _ 0)
+NAMED_CHARACTER_REFERENCE(1550, /* n r */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x219b _ 0)
+NAMED_CHARACTER_REFERENCE(1551, /* n r */ 'a' _ 'r' _ 'r' _ 'c' _ ';', 5, 0, 0x2933 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1552, /* n r */ 'a' _ 'r' _ 'r' _ 'w' _ ';', 5, 0, 0x219d _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1553, /* n r */ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 10, 0, 0x219b _ 0)
+NAMED_CHARACTER_REFERENCE(1554, /* n r */ 't' _ 'r' _ 'i' _ ';', 4, 0, 0x22eb _ 0)
+NAMED_CHARACTER_REFERENCE(1555, /* n r */ 't' _ 'r' _ 'i' _ 'e' _ ';', 5, 0, 0x22ed _ 0)
+NAMED_CHARACTER_REFERENCE(1556, /* n s */ 'c' _ ';', 2, 0, 0x2281 _ 0)
+NAMED_CHARACTER_REFERENCE(1557, /* n s */ 'c' _ 'c' _ 'u' _ 'e' _ ';', 5, 0, 0x22e1 _ 0)
+NAMED_CHARACTER_REFERENCE(1558, /* n s */ 'c' _ 'e' _ ';', 3, 0, 0x2ab0 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1559, /* n s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcc3)
+NAMED_CHARACTER_REFERENCE(1560, /* n s */ 'h' _ 'o' _ 'r' _ 't' _ 'm' _ 'i' _ 'd' _ ';', 8, 0, 0x2224 _ 0)
+NAMED_CHARACTER_REFERENCE(1561, /* n s */ 'h' _ 'o' _ 'r' _ 't' _ 'p' _ 'a' _ 'r' _ 'a' _ 'l' _ 'l' _ 'e' _ 'l' _ ';', 13, 0, 0x2226 _ 0)
+NAMED_CHARACTER_REFERENCE(1562, /* n s */ 'i' _ 'm' _ ';', 3, 0, 0x2241 _ 0)
+NAMED_CHARACTER_REFERENCE(1563, /* n s */ 'i' _ 'm' _ 'e' _ ';', 4, 0, 0x2244 _ 0)
+NAMED_CHARACTER_REFERENCE(1564, /* n s */ 'i' _ 'm' _ 'e' _ 'q' _ ';', 5, 0, 0x2244 _ 0)
+NAMED_CHARACTER_REFERENCE(1565, /* n s */ 'm' _ 'i' _ 'd' _ ';', 4, 0, 0x2224 _ 0)
+NAMED_CHARACTER_REFERENCE(1566, /* n s */ 'p' _ 'a' _ 'r' _ ';', 4, 0, 0x2226 _ 0)
+NAMED_CHARACTER_REFERENCE(1567, /* n s */ 'q' _ 's' _ 'u' _ 'b' _ 'e' _ ';', 6, 0, 0x22e2 _ 0)
+NAMED_CHARACTER_REFERENCE(1568, /* n s */ 'q' _ 's' _ 'u' _ 'p' _ 'e' _ ';', 6, 0, 0x22e3 _ 0)
+NAMED_CHARACTER_REFERENCE(1569, /* n s */ 'u' _ 'b' _ ';', 3, 0, 0x2284 _ 0)
+NAMED_CHARACTER_REFERENCE(1570, /* n s */ 'u' _ 'b' _ 'E' _ ';', 4, 0, 0x2ac5 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1571, /* n s */ 'u' _ 'b' _ 'e' _ ';', 4, 0, 0x2288 _ 0)
+NAMED_CHARACTER_REFERENCE(1572, /* n s */ 'u' _ 'b' _ 's' _ 'e' _ 't' _ ';', 6, 0, 0x2282 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1573, /* n s */ 'u' _ 'b' _ 's' _ 'e' _ 't' _ 'e' _ 'q' _ ';', 8, 0, 0x2288 _ 0)
+NAMED_CHARACTER_REFERENCE(1574, /* n s */ 'u' _ 'b' _ 's' _ 'e' _ 't' _ 'e' _ 'q' _ 'q' _ ';', 9, 0, 0x2ac5 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1575, /* n s */ 'u' _ 'c' _ 'c' _ ';', 4, 0, 0x2281 _ 0)
+NAMED_CHARACTER_REFERENCE(1576, /* n s */ 'u' _ 'c' _ 'c' _ 'e' _ 'q' _ ';', 6, 0, 0x2ab0 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1577, /* n s */ 'u' _ 'p' _ ';', 3, 0, 0x2285 _ 0)
+NAMED_CHARACTER_REFERENCE(1578, /* n s */ 'u' _ 'p' _ 'E' _ ';', 4, 0, 0x2ac6 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1579, /* n s */ 'u' _ 'p' _ 'e' _ ';', 4, 0, 0x2289 _ 0)
+NAMED_CHARACTER_REFERENCE(1580, /* n s */ 'u' _ 'p' _ 's' _ 'e' _ 't' _ ';', 6, 0, 0x2283 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1581, /* n s */ 'u' _ 'p' _ 's' _ 'e' _ 't' _ 'e' _ 'q' _ ';', 8, 0, 0x2289 _ 0)
+NAMED_CHARACTER_REFERENCE(1582, /* n s */ 'u' _ 'p' _ 's' _ 'e' _ 't' _ 'e' _ 'q' _ 'q' _ ';', 9, 0, 0x2ac6 _ 0x0338)
+NAMED_CHARACTER_REFERENCE(1583, /* n t */ 'g' _ 'l' _ ';', 3, 0, 0x2279 _ 0)
+NAMED_CHARACTER_REFERENCE(1584, /* n t */ 'i' _ 'l' _ 'd' _ 'e', 4, 0, 0x00f1 _ 0)
+NAMED_CHARACTER_REFERENCE(1585, /* n t */ 'i' _ 'l' _ 'd' _ 'e' _ ';', 5, 0, 0x00f1 _ 0)
+NAMED_CHARACTER_REFERENCE(1586, /* n t */ 'l' _ 'g' _ ';', 3, 0, 0x2278 _ 0)
+NAMED_CHARACTER_REFERENCE(1587, /* n t */ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'l' _ 'e' _ 'f' _ 't' _ ';', 12, 0, 0x22ea _ 0)
+NAMED_CHARACTER_REFERENCE(1588, /* n t */ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'l' _ 'e' _ 'f' _ 't' _ 'e' _ 'q' _ ';', 14, 0, 0x22ec _ 0)
+NAMED_CHARACTER_REFERENCE(1589, /* n t */ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ ';', 13, 0, 0x22eb _ 0)
+NAMED_CHARACTER_REFERENCE(1590, /* n t */ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'e' _ 'q' _ ';', 15, 0, 0x22ed _ 0)
+NAMED_CHARACTER_REFERENCE(1591, /* n u */ ';', 1, 0, 0x03bd _ 0)
+NAMED_CHARACTER_REFERENCE(1592, /* n u */ 'm' _ ';', 2, 0, 0x0023 _ 0)
+NAMED_CHARACTER_REFERENCE(1593, /* n u */ 'm' _ 'e' _ 'r' _ 'o' _ ';', 5, 0, 0x2116 _ 0)
+NAMED_CHARACTER_REFERENCE(1594, /* n u */ 'm' _ 's' _ 'p' _ ';', 4, 0, 0x2007 _ 0)
+NAMED_CHARACTER_REFERENCE(1595, /* n v */ 'D' _ 'a' _ 's' _ 'h' _ ';', 5, 0, 0x22ad _ 0)
+NAMED_CHARACTER_REFERENCE(1596, /* n v */ 'H' _ 'a' _ 'r' _ 'r' _ ';', 5, 0, 0x2904 _ 0)
+NAMED_CHARACTER_REFERENCE(1597, /* n v */ 'a' _ 'p' _ ';', 3, 0, 0x224d _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1598, /* n v */ 'd' _ 'a' _ 's' _ 'h' _ ';', 5, 0, 0x22ac _ 0)
+NAMED_CHARACTER_REFERENCE(1599, /* n v */ 'g' _ 'e' _ ';', 3, 0, 0x2265 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1600, /* n v */ 'g' _ 't' _ ';', 3, 0, 0x003e _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1601, /* n v */ 'i' _ 'n' _ 'f' _ 'i' _ 'n' _ ';', 6, 0, 0x29de _ 0)
+NAMED_CHARACTER_REFERENCE(1602, /* n v */ 'l' _ 'A' _ 'r' _ 'r' _ ';', 5, 0, 0x2902 _ 0)
+NAMED_CHARACTER_REFERENCE(1603, /* n v */ 'l' _ 'e' _ ';', 3, 0, 0x2264 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1604, /* n v */ 'l' _ 't' _ ';', 3, 0, 0x003c _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1605, /* n v */ 'l' _ 't' _ 'r' _ 'i' _ 'e' _ ';', 6, 0, 0x22b4 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1606, /* n v */ 'r' _ 'A' _ 'r' _ 'r' _ ';', 5, 0, 0x2903 _ 0)
+NAMED_CHARACTER_REFERENCE(1607, /* n v */ 'r' _ 't' _ 'r' _ 'i' _ 'e' _ ';', 6, 0, 0x22b5 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1608, /* n v */ 's' _ 'i' _ 'm' _ ';', 4, 0, 0x223c _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(1609, /* n w */ 'A' _ 'r' _ 'r' _ ';', 4, 0, 0x21d6 _ 0)
+NAMED_CHARACTER_REFERENCE(1610, /* n w */ 'a' _ 'r' _ 'h' _ 'k' _ ';', 5, 0, 0x2923 _ 0)
+NAMED_CHARACTER_REFERENCE(1611, /* n w */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x2196 _ 0)
+NAMED_CHARACTER_REFERENCE(1612, /* n w */ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 6, 0, 0x2196 _ 0)
+NAMED_CHARACTER_REFERENCE(1613, /* n w */ 'n' _ 'e' _ 'a' _ 'r' _ ';', 5, 0, 0x2927 _ 0)
+NAMED_CHARACTER_REFERENCE(1614, /* o S */ ';', 1, 0, 0x24c8 _ 0)
+NAMED_CHARACTER_REFERENCE(1615, /* o a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00f3 _ 0)
+NAMED_CHARACTER_REFERENCE(1616, /* o a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00f3 _ 0)
+NAMED_CHARACTER_REFERENCE(1617, /* o a */ 's' _ 't' _ ';', 3, 0, 0x229b _ 0)
+NAMED_CHARACTER_REFERENCE(1618, /* o c */ 'i' _ 'r' _ ';', 3, 0, 0x229a _ 0)
+NAMED_CHARACTER_REFERENCE(1619, /* o c */ 'i' _ 'r' _ 'c', 3, 0, 0x00f4 _ 0)
+NAMED_CHARACTER_REFERENCE(1620, /* o c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x00f4 _ 0)
+NAMED_CHARACTER_REFERENCE(1621, /* o c */ 'y' _ ';', 2, 0, 0x043e _ 0)
+NAMED_CHARACTER_REFERENCE(1622, /* o d */ 'a' _ 's' _ 'h' _ ';', 4, 0, 0x229d _ 0)
+NAMED_CHARACTER_REFERENCE(1623, /* o d */ 'b' _ 'l' _ 'a' _ 'c' _ ';', 5, 0, 0x0151 _ 0)
+NAMED_CHARACTER_REFERENCE(1624, /* o d */ 'i' _ 'v' _ ';', 3, 0, 0x2a38 _ 0)
+NAMED_CHARACTER_REFERENCE(1625, /* o d */ 'o' _ 't' _ ';', 3, 0, 0x2299 _ 0)
+NAMED_CHARACTER_REFERENCE(1626, /* o d */ 's' _ 'o' _ 'l' _ 'd' _ ';', 5, 0, 0x29bc _ 0)
+NAMED_CHARACTER_REFERENCE(1627, /* o e */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0x0153 _ 0)
+NAMED_CHARACTER_REFERENCE(1628, /* o f */ 'c' _ 'i' _ 'r' _ ';', 4, 0, 0x29bf _ 0)
+NAMED_CHARACTER_REFERENCE(1629, /* o f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd2c)
+NAMED_CHARACTER_REFERENCE(1630, /* o g */ 'o' _ 'n' _ ';', 3, 0, 0x02db _ 0)
+NAMED_CHARACTER_REFERENCE(1631, /* o g */ 'r' _ 'a' _ 'v' _ 'e', 4, 0, 0x00f2 _ 0)
+NAMED_CHARACTER_REFERENCE(1632, /* o g */ 'r' _ 'a' _ 'v' _ 'e' _ ';', 5, 0, 0x00f2 _ 0)
+NAMED_CHARACTER_REFERENCE(1633, /* o g */ 't' _ ';', 2, 0, 0x29c1 _ 0)
+NAMED_CHARACTER_REFERENCE(1634, /* o h */ 'b' _ 'a' _ 'r' _ ';', 4, 0, 0x29b5 _ 0)
+NAMED_CHARACTER_REFERENCE(1635, /* o h */ 'm' _ ';', 2, 0, 0x03a9 _ 0)
+NAMED_CHARACTER_REFERENCE(1636, /* o i */ 'n' _ 't' _ ';', 3, 0, 0x222e _ 0)
+NAMED_CHARACTER_REFERENCE(1637, /* o l */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21ba _ 0)
+NAMED_CHARACTER_REFERENCE(1638, /* o l */ 'c' _ 'i' _ 'r' _ ';', 4, 0, 0x29be _ 0)
+NAMED_CHARACTER_REFERENCE(1639, /* o l */ 'c' _ 'r' _ 'o' _ 's' _ 's' _ ';', 6, 0, 0x29bb _ 0)
+NAMED_CHARACTER_REFERENCE(1640, /* o l */ 'i' _ 'n' _ 'e' _ ';', 4, 0, 0x203e _ 0)
+NAMED_CHARACTER_REFERENCE(1641, /* o l */ 't' _ ';', 2, 0, 0x29c0 _ 0)
+NAMED_CHARACTER_REFERENCE(1642, /* o m */ 'a' _ 'c' _ 'r' _ ';', 4, 0, 0x014d _ 0)
+NAMED_CHARACTER_REFERENCE(1643, /* o m */ 'e' _ 'g' _ 'a' _ ';', 4, 0, 0x03c9 _ 0)
+NAMED_CHARACTER_REFERENCE(1644, /* o m */ 'i' _ 'c' _ 'r' _ 'o' _ 'n' _ ';', 6, 0, 0x03bf _ 0)
+NAMED_CHARACTER_REFERENCE(1645, /* o m */ 'i' _ 'd' _ ';', 3, 0, 0x29b6 _ 0)
+NAMED_CHARACTER_REFERENCE(1646, /* o m */ 'i' _ 'n' _ 'u' _ 's' _ ';', 5, 0, 0x2296 _ 0)
+NAMED_CHARACTER_REFERENCE(1647, /* o o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd60)
+NAMED_CHARACTER_REFERENCE(1648, /* o p */ 'a' _ 'r' _ ';', 3, 0, 0x29b7 _ 0)
+NAMED_CHARACTER_REFERENCE(1649, /* o p */ 'e' _ 'r' _ 'p' _ ';', 4, 0, 0x29b9 _ 0)
+NAMED_CHARACTER_REFERENCE(1650, /* o p */ 'l' _ 'u' _ 's' _ ';', 4, 0, 0x2295 _ 0)
+NAMED_CHARACTER_REFERENCE(1651, /* o r */ ';', 1, 0, 0x2228 _ 0)
+NAMED_CHARACTER_REFERENCE(1652, /* o r */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21bb _ 0)
+NAMED_CHARACTER_REFERENCE(1653, /* o r */ 'd' _ ';', 2, 0, 0x2a5d _ 0)
+NAMED_CHARACTER_REFERENCE(1654, /* o r */ 'd' _ 'e' _ 'r' _ ';', 4, 0, 0x2134 _ 0)
+NAMED_CHARACTER_REFERENCE(1655, /* o r */ 'd' _ 'e' _ 'r' _ 'o' _ 'f' _ ';', 6, 0, 0x2134 _ 0)
+NAMED_CHARACTER_REFERENCE(1656, /* o r */ 'd' _ 'f', 2, 0, 0x00aa _ 0)
+NAMED_CHARACTER_REFERENCE(1657, /* o r */ 'd' _ 'f' _ ';', 3, 0, 0x00aa _ 0)
+NAMED_CHARACTER_REFERENCE(1658, /* o r */ 'd' _ 'm', 2, 0, 0x00ba _ 0)
+NAMED_CHARACTER_REFERENCE(1659, /* o r */ 'd' _ 'm' _ ';', 3, 0, 0x00ba _ 0)
+NAMED_CHARACTER_REFERENCE(1660, /* o r */ 'i' _ 'g' _ 'o' _ 'f' _ ';', 5, 0, 0x22b6 _ 0)
+NAMED_CHARACTER_REFERENCE(1661, /* o r */ 'o' _ 'r' _ ';', 3, 0, 0x2a56 _ 0)
+NAMED_CHARACTER_REFERENCE(1662, /* o r */ 's' _ 'l' _ 'o' _ 'p' _ 'e' _ ';', 6, 0, 0x2a57 _ 0)
+NAMED_CHARACTER_REFERENCE(1663, /* o r */ 'v' _ ';', 2, 0, 0x2a5b _ 0)
+NAMED_CHARACTER_REFERENCE(1664, /* o s */ 'c' _ 'r' _ ';', 3, 0, 0x2134 _ 0)
+NAMED_CHARACTER_REFERENCE(1665, /* o s */ 'l' _ 'a' _ 's' _ 'h', 4, 0, 0x00f8 _ 0)
+NAMED_CHARACTER_REFERENCE(1666, /* o s */ 'l' _ 'a' _ 's' _ 'h' _ ';', 5, 0, 0x00f8 _ 0)
+NAMED_CHARACTER_REFERENCE(1667, /* o s */ 'o' _ 'l' _ ';', 3, 0, 0x2298 _ 0)
+NAMED_CHARACTER_REFERENCE(1668, /* o t */ 'i' _ 'l' _ 'd' _ 'e', 4, 0, 0x00f5 _ 0)
+NAMED_CHARACTER_REFERENCE(1669, /* o t */ 'i' _ 'l' _ 'd' _ 'e' _ ';', 5, 0, 0x00f5 _ 0)
+NAMED_CHARACTER_REFERENCE(1670, /* o t */ 'i' _ 'm' _ 'e' _ 's' _ ';', 5, 0, 0x2297 _ 0)
+NAMED_CHARACTER_REFERENCE(1671, /* o t */ 'i' _ 'm' _ 'e' _ 's' _ 'a' _ 's' _ ';', 7, 0, 0x2a36 _ 0)
+NAMED_CHARACTER_REFERENCE(1672, /* o u */ 'm' _ 'l', 2, 0, 0x00f6 _ 0)
+NAMED_CHARACTER_REFERENCE(1673, /* o u */ 'm' _ 'l' _ ';', 3, 0, 0x00f6 _ 0)
+NAMED_CHARACTER_REFERENCE(1674, /* o v */ 'b' _ 'a' _ 'r' _ ';', 4, 0, 0x233d _ 0)
+NAMED_CHARACTER_REFERENCE(1675, /* p a */ 'r' _ ';', 2, 0, 0x2225 _ 0)
+NAMED_CHARACTER_REFERENCE(1676, /* p a */ 'r' _ 'a', 2, 0, 0x00b6 _ 0)
+NAMED_CHARACTER_REFERENCE(1677, /* p a */ 'r' _ 'a' _ ';', 3, 0, 0x00b6 _ 0)
+NAMED_CHARACTER_REFERENCE(1678, /* p a */ 'r' _ 'a' _ 'l' _ 'l' _ 'e' _ 'l' _ ';', 7, 0, 0x2225 _ 0)
+NAMED_CHARACTER_REFERENCE(1679, /* p a */ 'r' _ 's' _ 'i' _ 'm' _ ';', 5, 0, 0x2af3 _ 0)
+NAMED_CHARACTER_REFERENCE(1680, /* p a */ 'r' _ 's' _ 'l' _ ';', 4, 0, 0x2afd _ 0)
+NAMED_CHARACTER_REFERENCE(1681, /* p a */ 'r' _ 't' _ ';', 3, 0, 0x2202 _ 0)
+NAMED_CHARACTER_REFERENCE(1682, /* p c */ 'y' _ ';', 2, 0, 0x043f _ 0)
+NAMED_CHARACTER_REFERENCE(1683, /* p e */ 'r' _ 'c' _ 'n' _ 't' _ ';', 5, 0, 0x0025 _ 0)
+NAMED_CHARACTER_REFERENCE(1684, /* p e */ 'r' _ 'i' _ 'o' _ 'd' _ ';', 5, 0, 0x002e _ 0)
+NAMED_CHARACTER_REFERENCE(1685, /* p e */ 'r' _ 'm' _ 'i' _ 'l' _ ';', 5, 0, 0x2030 _ 0)
+NAMED_CHARACTER_REFERENCE(1686, /* p e */ 'r' _ 'p' _ ';', 3, 0, 0x22a5 _ 0)
+NAMED_CHARACTER_REFERENCE(1687, /* p e */ 'r' _ 't' _ 'e' _ 'n' _ 'k' _ ';', 6, 0, 0x2031 _ 0)
+NAMED_CHARACTER_REFERENCE(1688, /* p f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd2d)
+NAMED_CHARACTER_REFERENCE(1689, /* p h */ 'i' _ ';', 2, 0, 0x03c6 _ 0)
+NAMED_CHARACTER_REFERENCE(1690, /* p h */ 'i' _ 'v' _ ';', 3, 0, 0x03d5 _ 0)
+NAMED_CHARACTER_REFERENCE(1691, /* p h */ 'm' _ 'm' _ 'a' _ 't' _ ';', 5, 0, 0x2133 _ 0)
+NAMED_CHARACTER_REFERENCE(1692, /* p h */ 'o' _ 'n' _ 'e' _ ';', 4, 0, 0x260e _ 0)
+NAMED_CHARACTER_REFERENCE(1693, /* p i */ ';', 1, 0, 0x03c0 _ 0)
+NAMED_CHARACTER_REFERENCE(1694, /* p i */ 't' _ 'c' _ 'h' _ 'f' _ 'o' _ 'r' _ 'k' _ ';', 8, 0, 0x22d4 _ 0)
+NAMED_CHARACTER_REFERENCE(1695, /* p i */ 'v' _ ';', 2, 0, 0x03d6 _ 0)
+NAMED_CHARACTER_REFERENCE(1696, /* p l */ 'a' _ 'n' _ 'c' _ 'k' _ ';', 5, 0, 0x210f _ 0)
+NAMED_CHARACTER_REFERENCE(1697, /* p l */ 'a' _ 'n' _ 'c' _ 'k' _ 'h' _ ';', 6, 0, 0x210e _ 0)
+NAMED_CHARACTER_REFERENCE(1698, /* p l */ 'a' _ 'n' _ 'k' _ 'v' _ ';', 5, 0, 0x210f _ 0)
+NAMED_CHARACTER_REFERENCE(1699, /* p l */ 'u' _ 's' _ ';', 3, 0, 0x002b _ 0)
+NAMED_CHARACTER_REFERENCE(1700, /* p l */ 'u' _ 's' _ 'a' _ 'c' _ 'i' _ 'r' _ ';', 7, 0, 0x2a23 _ 0)
+NAMED_CHARACTER_REFERENCE(1701, /* p l */ 'u' _ 's' _ 'b' _ ';', 4, 0, 0x229e _ 0)
+NAMED_CHARACTER_REFERENCE(1702, /* p l */ 'u' _ 's' _ 'c' _ 'i' _ 'r' _ ';', 6, 0, 0x2a22 _ 0)
+NAMED_CHARACTER_REFERENCE(1703, /* p l */ 'u' _ 's' _ 'd' _ 'o' _ ';', 5, 0, 0x2214 _ 0)
+NAMED_CHARACTER_REFERENCE(1704, /* p l */ 'u' _ 's' _ 'd' _ 'u' _ ';', 5, 0, 0x2a25 _ 0)
+NAMED_CHARACTER_REFERENCE(1705, /* p l */ 'u' _ 's' _ 'e' _ ';', 4, 0, 0x2a72 _ 0)
+NAMED_CHARACTER_REFERENCE(1706, /* p l */ 'u' _ 's' _ 'm' _ 'n', 4, 0, 0x00b1 _ 0)
+NAMED_CHARACTER_REFERENCE(1707, /* p l */ 'u' _ 's' _ 'm' _ 'n' _ ';', 5, 0, 0x00b1 _ 0)
+NAMED_CHARACTER_REFERENCE(1708, /* p l */ 'u' _ 's' _ 's' _ 'i' _ 'm' _ ';', 6, 0, 0x2a26 _ 0)
+NAMED_CHARACTER_REFERENCE(1709, /* p l */ 'u' _ 's' _ 't' _ 'w' _ 'o' _ ';', 6, 0, 0x2a27 _ 0)
+NAMED_CHARACTER_REFERENCE(1710, /* p m */ ';', 1, 0, 0x00b1 _ 0)
+NAMED_CHARACTER_REFERENCE(1711, /* p o */ 'i' _ 'n' _ 't' _ 'i' _ 'n' _ 't' _ ';', 7, 0, 0x2a15 _ 0)
+NAMED_CHARACTER_REFERENCE(1712, /* p o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd61)
+NAMED_CHARACTER_REFERENCE(1713, /* p o */ 'u' _ 'n' _ 'd', 3, 0, 0x00a3 _ 0)
+NAMED_CHARACTER_REFERENCE(1714, /* p o */ 'u' _ 'n' _ 'd' _ ';', 4, 0, 0x00a3 _ 0)
+NAMED_CHARACTER_REFERENCE(1715, /* p r */ ';', 1, 0, 0x227a _ 0)
+NAMED_CHARACTER_REFERENCE(1716, /* p r */ 'E' _ ';', 2, 0, 0x2ab3 _ 0)
+NAMED_CHARACTER_REFERENCE(1717, /* p r */ 'a' _ 'p' _ ';', 3, 0, 0x2ab7 _ 0)
+NAMED_CHARACTER_REFERENCE(1718, /* p r */ 'c' _ 'u' _ 'e' _ ';', 4, 0, 0x227c _ 0)
+NAMED_CHARACTER_REFERENCE(1719, /* p r */ 'e' _ ';', 2, 0, 0x2aaf _ 0)
+NAMED_CHARACTER_REFERENCE(1720, /* p r */ 'e' _ 'c' _ ';', 3, 0, 0x227a _ 0)
+NAMED_CHARACTER_REFERENCE(1721, /* p r */ 'e' _ 'c' _ 'a' _ 'p' _ 'p' _ 'r' _ 'o' _ 'x' _ ';', 9, 0, 0x2ab7 _ 0)
+NAMED_CHARACTER_REFERENCE(1722, /* p r */ 'e' _ 'c' _ 'c' _ 'u' _ 'r' _ 'l' _ 'y' _ 'e' _ 'q' _ ';', 10, 0, 0x227c _ 0)
+NAMED_CHARACTER_REFERENCE(1723, /* p r */ 'e' _ 'c' _ 'e' _ 'q' _ ';', 5, 0, 0x2aaf _ 0)
+NAMED_CHARACTER_REFERENCE(1724, /* p r */ 'e' _ 'c' _ 'n' _ 'a' _ 'p' _ 'p' _ 'r' _ 'o' _ 'x' _ ';', 10, 0, 0x2ab9 _ 0)
+NAMED_CHARACTER_REFERENCE(1725, /* p r */ 'e' _ 'c' _ 'n' _ 'e' _ 'q' _ 'q' _ ';', 7, 0, 0x2ab5 _ 0)
+NAMED_CHARACTER_REFERENCE(1726, /* p r */ 'e' _ 'c' _ 'n' _ 's' _ 'i' _ 'm' _ ';', 7, 0, 0x22e8 _ 0)
+NAMED_CHARACTER_REFERENCE(1727, /* p r */ 'e' _ 'c' _ 's' _ 'i' _ 'm' _ ';', 6, 0, 0x227e _ 0)
+NAMED_CHARACTER_REFERENCE(1728, /* p r */ 'i' _ 'm' _ 'e' _ ';', 4, 0, 0x2032 _ 0)
+NAMED_CHARACTER_REFERENCE(1729, /* p r */ 'i' _ 'm' _ 'e' _ 's' _ ';', 5, 0, 0x2119 _ 0)
+NAMED_CHARACTER_REFERENCE(1730, /* p r */ 'n' _ 'E' _ ';', 3, 0, 0x2ab5 _ 0)
+NAMED_CHARACTER_REFERENCE(1731, /* p r */ 'n' _ 'a' _ 'p' _ ';', 4, 0, 0x2ab9 _ 0)
+NAMED_CHARACTER_REFERENCE(1732, /* p r */ 'n' _ 's' _ 'i' _ 'm' _ ';', 5, 0, 0x22e8 _ 0)
+NAMED_CHARACTER_REFERENCE(1733, /* p r */ 'o' _ 'd' _ ';', 3, 0, 0x220f _ 0)
+NAMED_CHARACTER_REFERENCE(1734, /* p r */ 'o' _ 'f' _ 'a' _ 'l' _ 'a' _ 'r' _ ';', 7, 0, 0x232e _ 0)
+NAMED_CHARACTER_REFERENCE(1735, /* p r */ 'o' _ 'f' _ 'l' _ 'i' _ 'n' _ 'e' _ ';', 7, 0, 0x2312 _ 0)
+NAMED_CHARACTER_REFERENCE(1736, /* p r */ 'o' _ 'f' _ 's' _ 'u' _ 'r' _ 'f' _ ';', 7, 0, 0x2313 _ 0)
+NAMED_CHARACTER_REFERENCE(1737, /* p r */ 'o' _ 'p' _ ';', 3, 0, 0x221d _ 0)
+NAMED_CHARACTER_REFERENCE(1738, /* p r */ 'o' _ 'p' _ 't' _ 'o' _ ';', 5, 0, 0x221d _ 0)
+NAMED_CHARACTER_REFERENCE(1739, /* p r */ 's' _ 'i' _ 'm' _ ';', 4, 0, 0x227e _ 0)
+NAMED_CHARACTER_REFERENCE(1740, /* p r */ 'u' _ 'r' _ 'e' _ 'l' _ ';', 5, 0, 0x22b0 _ 0)
+NAMED_CHARACTER_REFERENCE(1741, /* p s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcc5)
+NAMED_CHARACTER_REFERENCE(1742, /* p s */ 'i' _ ';', 2, 0, 0x03c8 _ 0)
+NAMED_CHARACTER_REFERENCE(1743, /* p u */ 'n' _ 'c' _ 's' _ 'p' _ ';', 5, 0, 0x2008 _ 0)
+NAMED_CHARACTER_REFERENCE(1744, /* q f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd2e)
+NAMED_CHARACTER_REFERENCE(1745, /* q i */ 'n' _ 't' _ ';', 3, 0, 0x2a0c _ 0)
+NAMED_CHARACTER_REFERENCE(1746, /* q o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd62)
+NAMED_CHARACTER_REFERENCE(1747, /* q p */ 'r' _ 'i' _ 'm' _ 'e' _ ';', 5, 0, 0x2057 _ 0)
+NAMED_CHARACTER_REFERENCE(1748, /* q s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcc6)
+NAMED_CHARACTER_REFERENCE(1749, /* q u */ 'a' _ 't' _ 'e' _ 'r' _ 'n' _ 'i' _ 'o' _ 'n' _ 's' _ ';', 10, 0, 0x210d _ 0)
+NAMED_CHARACTER_REFERENCE(1750, /* q u */ 'a' _ 't' _ 'i' _ 'n' _ 't' _ ';', 6, 0, 0x2a16 _ 0)
+NAMED_CHARACTER_REFERENCE(1751, /* q u */ 'e' _ 's' _ 't' _ ';', 4, 0, 0x003f _ 0)
+NAMED_CHARACTER_REFERENCE(1752, /* q u */ 'e' _ 's' _ 't' _ 'e' _ 'q' _ ';', 6, 0, 0x225f _ 0)
+NAMED_CHARACTER_REFERENCE(1753, /* q u */ 'o' _ 't', 2, 0, 0x0022 _ 0)
+NAMED_CHARACTER_REFERENCE(1754, /* q u */ 'o' _ 't' _ ';', 3, 0, 0x0022 _ 0)
+NAMED_CHARACTER_REFERENCE(1755, /* r A */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21db _ 0)
+NAMED_CHARACTER_REFERENCE(1756, /* r A */ 'r' _ 'r' _ ';', 3, 0, 0x21d2 _ 0)
+NAMED_CHARACTER_REFERENCE(1757, /* r A */ 't' _ 'a' _ 'i' _ 'l' _ ';', 5, 0, 0x291c _ 0)
+NAMED_CHARACTER_REFERENCE(1758, /* r B */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x290f _ 0)
+NAMED_CHARACTER_REFERENCE(1759, /* r H */ 'a' _ 'r' _ ';', 3, 0, 0x2964 _ 0)
+NAMED_CHARACTER_REFERENCE(1760, /* r a */ 'c' _ 'e' _ ';', 3, 0, 0x223d _ 0x0331)
+NAMED_CHARACTER_REFERENCE(1761, /* r a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x0155 _ 0)
+NAMED_CHARACTER_REFERENCE(1762, /* r a */ 'd' _ 'i' _ 'c' _ ';', 4, 0, 0x221a _ 0)
+NAMED_CHARACTER_REFERENCE(1763, /* r a */ 'e' _ 'm' _ 'p' _ 't' _ 'y' _ 'v' _ ';', 7, 0, 0x29b3 _ 0)
+NAMED_CHARACTER_REFERENCE(1764, /* r a */ 'n' _ 'g' _ ';', 3, 0, 0x27e9 _ 0)
+NAMED_CHARACTER_REFERENCE(1765, /* r a */ 'n' _ 'g' _ 'd' _ ';', 4, 0, 0x2992 _ 0)
+NAMED_CHARACTER_REFERENCE(1766, /* r a */ 'n' _ 'g' _ 'e' _ ';', 4, 0, 0x29a5 _ 0)
+NAMED_CHARACTER_REFERENCE(1767, /* r a */ 'n' _ 'g' _ 'l' _ 'e' _ ';', 5, 0, 0x27e9 _ 0)
+NAMED_CHARACTER_REFERENCE(1768, /* r a */ 'q' _ 'u' _ 'o', 3, 0, 0x00bb _ 0)
+NAMED_CHARACTER_REFERENCE(1769, /* r a */ 'q' _ 'u' _ 'o' _ ';', 4, 0, 0x00bb _ 0)
+NAMED_CHARACTER_REFERENCE(1770, /* r a */ 'r' _ 'r' _ ';', 3, 0, 0x2192 _ 0)
+NAMED_CHARACTER_REFERENCE(1771, /* r a */ 'r' _ 'r' _ 'a' _ 'p' _ ';', 5, 0, 0x2975 _ 0)
+NAMED_CHARACTER_REFERENCE(1772, /* r a */ 'r' _ 'r' _ 'b' _ ';', 4, 0, 0x21e5 _ 0)
+NAMED_CHARACTER_REFERENCE(1773, /* r a */ 'r' _ 'r' _ 'b' _ 'f' _ 's' _ ';', 6, 0, 0x2920 _ 0)
+NAMED_CHARACTER_REFERENCE(1774, /* r a */ 'r' _ 'r' _ 'c' _ ';', 4, 0, 0x2933 _ 0)
+NAMED_CHARACTER_REFERENCE(1775, /* r a */ 'r' _ 'r' _ 'f' _ 's' _ ';', 5, 0, 0x291e _ 0)
+NAMED_CHARACTER_REFERENCE(1776, /* r a */ 'r' _ 'r' _ 'h' _ 'k' _ ';', 5, 0, 0x21aa _ 0)
+NAMED_CHARACTER_REFERENCE(1777, /* r a */ 'r' _ 'r' _ 'l' _ 'p' _ ';', 5, 0, 0x21ac _ 0)
+NAMED_CHARACTER_REFERENCE(1778, /* r a */ 'r' _ 'r' _ 'p' _ 'l' _ ';', 5, 0, 0x2945 _ 0)
+NAMED_CHARACTER_REFERENCE(1779, /* r a */ 'r' _ 'r' _ 's' _ 'i' _ 'm' _ ';', 6, 0, 0x2974 _ 0)
+NAMED_CHARACTER_REFERENCE(1780, /* r a */ 'r' _ 'r' _ 't' _ 'l' _ ';', 5, 0, 0x21a3 _ 0)
+NAMED_CHARACTER_REFERENCE(1781, /* r a */ 'r' _ 'r' _ 'w' _ ';', 4, 0, 0x219d _ 0)
+NAMED_CHARACTER_REFERENCE(1782, /* r a */ 't' _ 'a' _ 'i' _ 'l' _ ';', 5, 0, 0x291a _ 0)
+NAMED_CHARACTER_REFERENCE(1783, /* r a */ 't' _ 'i' _ 'o' _ ';', 4, 0, 0x2236 _ 0)
+NAMED_CHARACTER_REFERENCE(1784, /* r a */ 't' _ 'i' _ 'o' _ 'n' _ 'a' _ 'l' _ 's' _ ';', 8, 0, 0x211a _ 0)
+NAMED_CHARACTER_REFERENCE(1785, /* r b */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x290d _ 0)
+NAMED_CHARACTER_REFERENCE(1786, /* r b */ 'b' _ 'r' _ 'k' _ ';', 4, 0, 0x2773 _ 0)
+NAMED_CHARACTER_REFERENCE(1787, /* r b */ 'r' _ 'a' _ 'c' _ 'e' _ ';', 5, 0, 0x007d _ 0)
+NAMED_CHARACTER_REFERENCE(1788, /* r b */ 'r' _ 'a' _ 'c' _ 'k' _ ';', 5, 0, 0x005d _ 0)
+NAMED_CHARACTER_REFERENCE(1789, /* r b */ 'r' _ 'k' _ 'e' _ ';', 4, 0, 0x298c _ 0)
+NAMED_CHARACTER_REFERENCE(1790, /* r b */ 'r' _ 'k' _ 's' _ 'l' _ 'd' _ ';', 6, 0, 0x298e _ 0)
+NAMED_CHARACTER_REFERENCE(1791, /* r b */ 'r' _ 'k' _ 's' _ 'l' _ 'u' _ ';', 6, 0, 0x2990 _ 0)
+NAMED_CHARACTER_REFERENCE(1792, /* r c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x0159 _ 0)
+NAMED_CHARACTER_REFERENCE(1793, /* r c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x0157 _ 0)
+NAMED_CHARACTER_REFERENCE(1794, /* r c */ 'e' _ 'i' _ 'l' _ ';', 4, 0, 0x2309 _ 0)
+NAMED_CHARACTER_REFERENCE(1795, /* r c */ 'u' _ 'b' _ ';', 3, 0, 0x007d _ 0)
+NAMED_CHARACTER_REFERENCE(1796, /* r c */ 'y' _ ';', 2, 0, 0x0440 _ 0)
+NAMED_CHARACTER_REFERENCE(1797, /* r d */ 'c' _ 'a' _ ';', 3, 0, 0x2937 _ 0)
+NAMED_CHARACTER_REFERENCE(1798, /* r d */ 'l' _ 'd' _ 'h' _ 'a' _ 'r' _ ';', 6, 0, 0x2969 _ 0)
+NAMED_CHARACTER_REFERENCE(1799, /* r d */ 'q' _ 'u' _ 'o' _ ';', 4, 0, 0x201d _ 0)
+NAMED_CHARACTER_REFERENCE(1800, /* r d */ 'q' _ 'u' _ 'o' _ 'r' _ ';', 5, 0, 0x201d _ 0)
+NAMED_CHARACTER_REFERENCE(1801, /* r d */ 's' _ 'h' _ ';', 3, 0, 0x21b3 _ 0)
+NAMED_CHARACTER_REFERENCE(1802, /* r e */ 'a' _ 'l' _ ';', 3, 0, 0x211c _ 0)
+NAMED_CHARACTER_REFERENCE(1803, /* r e */ 'a' _ 'l' _ 'i' _ 'n' _ 'e' _ ';', 6, 0, 0x211b _ 0)
+NAMED_CHARACTER_REFERENCE(1804, /* r e */ 'a' _ 'l' _ 'p' _ 'a' _ 'r' _ 't' _ ';', 7, 0, 0x211c _ 0)
+NAMED_CHARACTER_REFERENCE(1805, /* r e */ 'a' _ 'l' _ 's' _ ';', 4, 0, 0x211d _ 0)
+NAMED_CHARACTER_REFERENCE(1806, /* r e */ 'c' _ 't' _ ';', 3, 0, 0x25ad _ 0)
+NAMED_CHARACTER_REFERENCE(1807, /* r e */ 'g', 1, 0, 0x00ae _ 0)
+NAMED_CHARACTER_REFERENCE(1808, /* r e */ 'g' _ ';', 2, 0, 0x00ae _ 0)
+NAMED_CHARACTER_REFERENCE(1809, /* r f */ 'i' _ 's' _ 'h' _ 't' _ ';', 5, 0, 0x297d _ 0)
+NAMED_CHARACTER_REFERENCE(1810, /* r f */ 'l' _ 'o' _ 'o' _ 'r' _ ';', 5, 0, 0x230b _ 0)
+NAMED_CHARACTER_REFERENCE(1811, /* r f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd2f)
+NAMED_CHARACTER_REFERENCE(1812, /* r h */ 'a' _ 'r' _ 'd' _ ';', 4, 0, 0x21c1 _ 0)
+NAMED_CHARACTER_REFERENCE(1813, /* r h */ 'a' _ 'r' _ 'u' _ ';', 4, 0, 0x21c0 _ 0)
+NAMED_CHARACTER_REFERENCE(1814, /* r h */ 'a' _ 'r' _ 'u' _ 'l' _ ';', 5, 0, 0x296c _ 0)
+NAMED_CHARACTER_REFERENCE(1815, /* r h */ 'o' _ ';', 2, 0, 0x03c1 _ 0)
+NAMED_CHARACTER_REFERENCE(1816, /* r h */ 'o' _ 'v' _ ';', 3, 0, 0x03f1 _ 0)
+NAMED_CHARACTER_REFERENCE(1817, /* r i */ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 9, 0, 0x2192 _ 0)
+NAMED_CHARACTER_REFERENCE(1818, /* r i */ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 't' _ 'a' _ 'i' _ 'l' _ ';', 13, 0, 0x21a3 _ 0)
+NAMED_CHARACTER_REFERENCE(1819, /* r i */ 'g' _ 'h' _ 't' _ 'h' _ 'a' _ 'r' _ 'p' _ 'o' _ 'o' _ 'n' _ 'd' _ 'o' _ 'w' _ 'n' _ ';', 15, 0, 0x21c1 _ 0)
+NAMED_CHARACTER_REFERENCE(1820, /* r i */ 'g' _ 'h' _ 't' _ 'h' _ 'a' _ 'r' _ 'p' _ 'o' _ 'o' _ 'n' _ 'u' _ 'p' _ ';', 13, 0, 0x21c0 _ 0)
+NAMED_CHARACTER_REFERENCE(1821, /* r i */ 'g' _ 'h' _ 't' _ 'l' _ 'e' _ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 's' _ ';', 14, 0, 0x21c4 _ 0)
+NAMED_CHARACTER_REFERENCE(1822, /* r i */ 'g' _ 'h' _ 't' _ 'l' _ 'e' _ 'f' _ 't' _ 'h' _ 'a' _ 'r' _ 'p' _ 'o' _ 'o' _ 'n' _ 's' _ ';', 16, 0, 0x21cc _ 0)
+NAMED_CHARACTER_REFERENCE(1823, /* r i */ 'g' _ 'h' _ 't' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 's' _ ';', 15, 0, 0x21c9 _ 0)
+NAMED_CHARACTER_REFERENCE(1824, /* r i */ 'g' _ 'h' _ 't' _ 's' _ 'q' _ 'u' _ 'i' _ 'g' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 14, 0, 0x219d _ 0)
+NAMED_CHARACTER_REFERENCE(1825, /* r i */ 'g' _ 'h' _ 't' _ 't' _ 'h' _ 'r' _ 'e' _ 'e' _ 't' _ 'i' _ 'm' _ 'e' _ 's' _ ';', 14, 0, 0x22cc _ 0)
+NAMED_CHARACTER_REFERENCE(1826, /* r i */ 'n' _ 'g' _ ';', 3, 0, 0x02da _ 0)
+NAMED_CHARACTER_REFERENCE(1827, /* r i */ 's' _ 'i' _ 'n' _ 'g' _ 'd' _ 'o' _ 't' _ 's' _ 'e' _ 'q' _ ';', 11, 0, 0x2253 _ 0)
+NAMED_CHARACTER_REFERENCE(1828, /* r l */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21c4 _ 0)
+NAMED_CHARACTER_REFERENCE(1829, /* r l */ 'h' _ 'a' _ 'r' _ ';', 4, 0, 0x21cc _ 0)
+NAMED_CHARACTER_REFERENCE(1830, /* r l */ 'm' _ ';', 2, 0, 0x200f _ 0)
+NAMED_CHARACTER_REFERENCE(1831, /* r m */ 'o' _ 'u' _ 's' _ 't' _ ';', 5, 0, 0x23b1 _ 0)
+NAMED_CHARACTER_REFERENCE(1832, /* r m */ 'o' _ 'u' _ 's' _ 't' _ 'a' _ 'c' _ 'h' _ 'e' _ ';', 9, 0, 0x23b1 _ 0)
+NAMED_CHARACTER_REFERENCE(1833, /* r n */ 'm' _ 'i' _ 'd' _ ';', 4, 0, 0x2aee _ 0)
+NAMED_CHARACTER_REFERENCE(1834, /* r o */ 'a' _ 'n' _ 'g' _ ';', 4, 0, 0x27ed _ 0)
+NAMED_CHARACTER_REFERENCE(1835, /* r o */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21fe _ 0)
+NAMED_CHARACTER_REFERENCE(1836, /* r o */ 'b' _ 'r' _ 'k' _ ';', 4, 0, 0x27e7 _ 0)
+NAMED_CHARACTER_REFERENCE(1837, /* r o */ 'p' _ 'a' _ 'r' _ ';', 4, 0, 0x2986 _ 0)
+NAMED_CHARACTER_REFERENCE(1838, /* r o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd63)
+NAMED_CHARACTER_REFERENCE(1839, /* r o */ 'p' _ 'l' _ 'u' _ 's' _ ';', 5, 0, 0x2a2e _ 0)
+NAMED_CHARACTER_REFERENCE(1840, /* r o */ 't' _ 'i' _ 'm' _ 'e' _ 's' _ ';', 6, 0, 0x2a35 _ 0)
+NAMED_CHARACTER_REFERENCE(1841, /* r p */ 'a' _ 'r' _ ';', 3, 0, 0x0029 _ 0)
+NAMED_CHARACTER_REFERENCE(1842, /* r p */ 'a' _ 'r' _ 'g' _ 't' _ ';', 5, 0, 0x2994 _ 0)
+NAMED_CHARACTER_REFERENCE(1843, /* r p */ 'p' _ 'o' _ 'l' _ 'i' _ 'n' _ 't' _ ';', 7, 0, 0x2a12 _ 0)
+NAMED_CHARACTER_REFERENCE(1844, /* r r */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21c9 _ 0)
+NAMED_CHARACTER_REFERENCE(1845, /* r s */ 'a' _ 'q' _ 'u' _ 'o' _ ';', 5, 0, 0x203a _ 0)
+NAMED_CHARACTER_REFERENCE(1846, /* r s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcc7)
+NAMED_CHARACTER_REFERENCE(1847, /* r s */ 'h' _ ';', 2, 0, 0x21b1 _ 0)
+NAMED_CHARACTER_REFERENCE(1848, /* r s */ 'q' _ 'b' _ ';', 3, 0, 0x005d _ 0)
+NAMED_CHARACTER_REFERENCE(1849, /* r s */ 'q' _ 'u' _ 'o' _ ';', 4, 0, 0x2019 _ 0)
+NAMED_CHARACTER_REFERENCE(1850, /* r s */ 'q' _ 'u' _ 'o' _ 'r' _ ';', 5, 0, 0x2019 _ 0)
+NAMED_CHARACTER_REFERENCE(1851, /* r t */ 'h' _ 'r' _ 'e' _ 'e' _ ';', 5, 0, 0x22cc _ 0)
+NAMED_CHARACTER_REFERENCE(1852, /* r t */ 'i' _ 'm' _ 'e' _ 's' _ ';', 5, 0, 0x22ca _ 0)
+NAMED_CHARACTER_REFERENCE(1853, /* r t */ 'r' _ 'i' _ ';', 3, 0, 0x25b9 _ 0)
+NAMED_CHARACTER_REFERENCE(1854, /* r t */ 'r' _ 'i' _ 'e' _ ';', 4, 0, 0x22b5 _ 0)
+NAMED_CHARACTER_REFERENCE(1855, /* r t */ 'r' _ 'i' _ 'f' _ ';', 4, 0, 0x25b8 _ 0)
+NAMED_CHARACTER_REFERENCE(1856, /* r t */ 'r' _ 'i' _ 'l' _ 't' _ 'r' _ 'i' _ ';', 7, 0, 0x29ce _ 0)
+NAMED_CHARACTER_REFERENCE(1857, /* r u */ 'l' _ 'u' _ 'h' _ 'a' _ 'r' _ ';', 6, 0, 0x2968 _ 0)
+NAMED_CHARACTER_REFERENCE(1858, /* r x */ ';', 1, 0, 0x211e _ 0)
+NAMED_CHARACTER_REFERENCE(1859, /* s a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x015b _ 0)
+NAMED_CHARACTER_REFERENCE(1860, /* s b */ 'q' _ 'u' _ 'o' _ ';', 4, 0, 0x201a _ 0)
+NAMED_CHARACTER_REFERENCE(1861, /* s c */ ';', 1, 0, 0x227b _ 0)
+NAMED_CHARACTER_REFERENCE(1862, /* s c */ 'E' _ ';', 2, 0, 0x2ab4 _ 0)
+NAMED_CHARACTER_REFERENCE(1863, /* s c */ 'a' _ 'p' _ ';', 3, 0, 0x2ab8 _ 0)
+NAMED_CHARACTER_REFERENCE(1864, /* s c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x0161 _ 0)
+NAMED_CHARACTER_REFERENCE(1865, /* s c */ 'c' _ 'u' _ 'e' _ ';', 4, 0, 0x227d _ 0)
+NAMED_CHARACTER_REFERENCE(1866, /* s c */ 'e' _ ';', 2, 0, 0x2ab0 _ 0)
+NAMED_CHARACTER_REFERENCE(1867, /* s c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x015f _ 0)
+NAMED_CHARACTER_REFERENCE(1868, /* s c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x015d _ 0)
+NAMED_CHARACTER_REFERENCE(1869, /* s c */ 'n' _ 'E' _ ';', 3, 0, 0x2ab6 _ 0)
+NAMED_CHARACTER_REFERENCE(1870, /* s c */ 'n' _ 'a' _ 'p' _ ';', 4, 0, 0x2aba _ 0)
+NAMED_CHARACTER_REFERENCE(1871, /* s c */ 'n' _ 's' _ 'i' _ 'm' _ ';', 5, 0, 0x22e9 _ 0)
+NAMED_CHARACTER_REFERENCE(1872, /* s c */ 'p' _ 'o' _ 'l' _ 'i' _ 'n' _ 't' _ ';', 7, 0, 0x2a13 _ 0)
+NAMED_CHARACTER_REFERENCE(1873, /* s c */ 's' _ 'i' _ 'm' _ ';', 4, 0, 0x227f _ 0)
+NAMED_CHARACTER_REFERENCE(1874, /* s c */ 'y' _ ';', 2, 0, 0x0441 _ 0)
+NAMED_CHARACTER_REFERENCE(1875, /* s d */ 'o' _ 't' _ ';', 3, 0, 0x22c5 _ 0)
+NAMED_CHARACTER_REFERENCE(1876, /* s d */ 'o' _ 't' _ 'b' _ ';', 4, 0, 0x22a1 _ 0)
+NAMED_CHARACTER_REFERENCE(1877, /* s d */ 'o' _ 't' _ 'e' _ ';', 4, 0, 0x2a66 _ 0)
+NAMED_CHARACTER_REFERENCE(1878, /* s e */ 'A' _ 'r' _ 'r' _ ';', 4, 0, 0x21d8 _ 0)
+NAMED_CHARACTER_REFERENCE(1879, /* s e */ 'a' _ 'r' _ 'h' _ 'k' _ ';', 5, 0, 0x2925 _ 0)
+NAMED_CHARACTER_REFERENCE(1880, /* s e */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x2198 _ 0)
+NAMED_CHARACTER_REFERENCE(1881, /* s e */ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 6, 0, 0x2198 _ 0)
+NAMED_CHARACTER_REFERENCE(1882, /* s e */ 'c' _ 't', 2, 0, 0x00a7 _ 0)
+NAMED_CHARACTER_REFERENCE(1883, /* s e */ 'c' _ 't' _ ';', 3, 0, 0x00a7 _ 0)
+NAMED_CHARACTER_REFERENCE(1884, /* s e */ 'm' _ 'i' _ ';', 3, 0, 0x003b _ 0)
+NAMED_CHARACTER_REFERENCE(1885, /* s e */ 's' _ 'w' _ 'a' _ 'r' _ ';', 5, 0, 0x2929 _ 0)
+NAMED_CHARACTER_REFERENCE(1886, /* s e */ 't' _ 'm' _ 'i' _ 'n' _ 'u' _ 's' _ ';', 7, 0, 0x2216 _ 0)
+NAMED_CHARACTER_REFERENCE(1887, /* s e */ 't' _ 'm' _ 'n' _ ';', 4, 0, 0x2216 _ 0)
+NAMED_CHARACTER_REFERENCE(1888, /* s e */ 'x' _ 't' _ ';', 3, 0, 0x2736 _ 0)
+NAMED_CHARACTER_REFERENCE(1889, /* s f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd30)
+NAMED_CHARACTER_REFERENCE(1890, /* s f */ 'r' _ 'o' _ 'w' _ 'n' _ ';', 5, 0, 0x2322 _ 0)
+NAMED_CHARACTER_REFERENCE(1891, /* s h */ 'a' _ 'r' _ 'p' _ ';', 4, 0, 0x266f _ 0)
+NAMED_CHARACTER_REFERENCE(1892, /* s h */ 'c' _ 'h' _ 'c' _ 'y' _ ';', 5, 0, 0x0449 _ 0)
+NAMED_CHARACTER_REFERENCE(1893, /* s h */ 'c' _ 'y' _ ';', 3, 0, 0x0448 _ 0)
+NAMED_CHARACTER_REFERENCE(1894, /* s h */ 'o' _ 'r' _ 't' _ 'm' _ 'i' _ 'd' _ ';', 7, 0, 0x2223 _ 0)
+NAMED_CHARACTER_REFERENCE(1895, /* s h */ 'o' _ 'r' _ 't' _ 'p' _ 'a' _ 'r' _ 'a' _ 'l' _ 'l' _ 'e' _ 'l' _ ';', 12, 0, 0x2225 _ 0)
+NAMED_CHARACTER_REFERENCE(1896, /* s h */ 'y', 1, 0, 0x00ad _ 0)
+NAMED_CHARACTER_REFERENCE(1897, /* s h */ 'y' _ ';', 2, 0, 0x00ad _ 0)
+NAMED_CHARACTER_REFERENCE(1898, /* s i */ 'g' _ 'm' _ 'a' _ ';', 4, 0, 0x03c3 _ 0)
+NAMED_CHARACTER_REFERENCE(1899, /* s i */ 'g' _ 'm' _ 'a' _ 'f' _ ';', 5, 0, 0x03c2 _ 0)
+NAMED_CHARACTER_REFERENCE(1900, /* s i */ 'g' _ 'm' _ 'a' _ 'v' _ ';', 5, 0, 0x03c2 _ 0)
+NAMED_CHARACTER_REFERENCE(1901, /* s i */ 'm' _ ';', 2, 0, 0x223c _ 0)
+NAMED_CHARACTER_REFERENCE(1902, /* s i */ 'm' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x2a6a _ 0)
+NAMED_CHARACTER_REFERENCE(1903, /* s i */ 'm' _ 'e' _ ';', 3, 0, 0x2243 _ 0)
+NAMED_CHARACTER_REFERENCE(1904, /* s i */ 'm' _ 'e' _ 'q' _ ';', 4, 0, 0x2243 _ 0)
+NAMED_CHARACTER_REFERENCE(1905, /* s i */ 'm' _ 'g' _ ';', 3, 0, 0x2a9e _ 0)
+NAMED_CHARACTER_REFERENCE(1906, /* s i */ 'm' _ 'g' _ 'E' _ ';', 4, 0, 0x2aa0 _ 0)
+NAMED_CHARACTER_REFERENCE(1907, /* s i */ 'm' _ 'l' _ ';', 3, 0, 0x2a9d _ 0)
+NAMED_CHARACTER_REFERENCE(1908, /* s i */ 'm' _ 'l' _ 'E' _ ';', 4, 0, 0x2a9f _ 0)
+NAMED_CHARACTER_REFERENCE(1909, /* s i */ 'm' _ 'n' _ 'e' _ ';', 4, 0, 0x2246 _ 0)
+NAMED_CHARACTER_REFERENCE(1910, /* s i */ 'm' _ 'p' _ 'l' _ 'u' _ 's' _ ';', 6, 0, 0x2a24 _ 0)
+NAMED_CHARACTER_REFERENCE(1911, /* s i */ 'm' _ 'r' _ 'a' _ 'r' _ 'r' _ ';', 6, 0, 0x2972 _ 0)
+NAMED_CHARACTER_REFERENCE(1912, /* s l */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x2190 _ 0)
+NAMED_CHARACTER_REFERENCE(1913, /* s m */ 'a' _ 'l' _ 'l' _ 's' _ 'e' _ 't' _ 'm' _ 'i' _ 'n' _ 'u' _ 's' _ ';', 12, 0, 0x2216 _ 0)
+NAMED_CHARACTER_REFERENCE(1914, /* s m */ 'a' _ 's' _ 'h' _ 'p' _ ';', 5, 0, 0x2a33 _ 0)
+NAMED_CHARACTER_REFERENCE(1915, /* s m */ 'e' _ 'p' _ 'a' _ 'r' _ 's' _ 'l' _ ';', 7, 0, 0x29e4 _ 0)
+NAMED_CHARACTER_REFERENCE(1916, /* s m */ 'i' _ 'd' _ ';', 3, 0, 0x2223 _ 0)
+NAMED_CHARACTER_REFERENCE(1917, /* s m */ 'i' _ 'l' _ 'e' _ ';', 4, 0, 0x2323 _ 0)
+NAMED_CHARACTER_REFERENCE(1918, /* s m */ 't' _ ';', 2, 0, 0x2aaa _ 0)
+NAMED_CHARACTER_REFERENCE(1919, /* s m */ 't' _ 'e' _ ';', 3, 0, 0x2aac _ 0)
+NAMED_CHARACTER_REFERENCE(1920, /* s m */ 't' _ 'e' _ 's' _ ';', 4, 0, 0x2aac _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(1921, /* s o */ 'f' _ 't' _ 'c' _ 'y' _ ';', 5, 0, 0x044c _ 0)
+NAMED_CHARACTER_REFERENCE(1922, /* s o */ 'l' _ ';', 2, 0, 0x002f _ 0)
+NAMED_CHARACTER_REFERENCE(1923, /* s o */ 'l' _ 'b' _ ';', 3, 0, 0x29c4 _ 0)
+NAMED_CHARACTER_REFERENCE(1924, /* s o */ 'l' _ 'b' _ 'a' _ 'r' _ ';', 5, 0, 0x233f _ 0)
+NAMED_CHARACTER_REFERENCE(1925, /* s o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd64)
+NAMED_CHARACTER_REFERENCE(1926, /* s p */ 'a' _ 'd' _ 'e' _ 's' _ ';', 5, 0, 0x2660 _ 0)
+NAMED_CHARACTER_REFERENCE(1927, /* s p */ 'a' _ 'd' _ 'e' _ 's' _ 'u' _ 'i' _ 't' _ ';', 8, 0, 0x2660 _ 0)
+NAMED_CHARACTER_REFERENCE(1928, /* s p */ 'a' _ 'r' _ ';', 3, 0, 0x2225 _ 0)
+NAMED_CHARACTER_REFERENCE(1929, /* s q */ 'c' _ 'a' _ 'p' _ ';', 4, 0, 0x2293 _ 0)
+NAMED_CHARACTER_REFERENCE(1930, /* s q */ 'c' _ 'a' _ 'p' _ 's' _ ';', 5, 0, 0x2293 _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(1931, /* s q */ 'c' _ 'u' _ 'p' _ ';', 4, 0, 0x2294 _ 0)
+NAMED_CHARACTER_REFERENCE(1932, /* s q */ 'c' _ 'u' _ 'p' _ 's' _ ';', 5, 0, 0x2294 _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(1933, /* s q */ 's' _ 'u' _ 'b' _ ';', 4, 0, 0x228f _ 0)
+NAMED_CHARACTER_REFERENCE(1934, /* s q */ 's' _ 'u' _ 'b' _ 'e' _ ';', 5, 0, 0x2291 _ 0)
+NAMED_CHARACTER_REFERENCE(1935, /* s q */ 's' _ 'u' _ 'b' _ 's' _ 'e' _ 't' _ ';', 7, 0, 0x228f _ 0)
+NAMED_CHARACTER_REFERENCE(1936, /* s q */ 's' _ 'u' _ 'b' _ 's' _ 'e' _ 't' _ 'e' _ 'q' _ ';', 9, 0, 0x2291 _ 0)
+NAMED_CHARACTER_REFERENCE(1937, /* s q */ 's' _ 'u' _ 'p' _ ';', 4, 0, 0x2290 _ 0)
+NAMED_CHARACTER_REFERENCE(1938, /* s q */ 's' _ 'u' _ 'p' _ 'e' _ ';', 5, 0, 0x2292 _ 0)
+NAMED_CHARACTER_REFERENCE(1939, /* s q */ 's' _ 'u' _ 'p' _ 's' _ 'e' _ 't' _ ';', 7, 0, 0x2290 _ 0)
+NAMED_CHARACTER_REFERENCE(1940, /* s q */ 's' _ 'u' _ 'p' _ 's' _ 'e' _ 't' _ 'e' _ 'q' _ ';', 9, 0, 0x2292 _ 0)
+NAMED_CHARACTER_REFERENCE(1941, /* s q */ 'u' _ ';', 2, 0, 0x25a1 _ 0)
+NAMED_CHARACTER_REFERENCE(1942, /* s q */ 'u' _ 'a' _ 'r' _ 'e' _ ';', 5, 0, 0x25a1 _ 0)
+NAMED_CHARACTER_REFERENCE(1943, /* s q */ 'u' _ 'a' _ 'r' _ 'f' _ ';', 5, 0, 0x25aa _ 0)
+NAMED_CHARACTER_REFERENCE(1944, /* s q */ 'u' _ 'f' _ ';', 3, 0, 0x25aa _ 0)
+NAMED_CHARACTER_REFERENCE(1945, /* s r */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x2192 _ 0)
+NAMED_CHARACTER_REFERENCE(1946, /* s s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcc8)
+NAMED_CHARACTER_REFERENCE(1947, /* s s */ 'e' _ 't' _ 'm' _ 'n' _ ';', 5, 0, 0x2216 _ 0)
+NAMED_CHARACTER_REFERENCE(1948, /* s s */ 'm' _ 'i' _ 'l' _ 'e' _ ';', 5, 0, 0x2323 _ 0)
+NAMED_CHARACTER_REFERENCE(1949, /* s s */ 't' _ 'a' _ 'r' _ 'f' _ ';', 5, 0, 0x22c6 _ 0)
+NAMED_CHARACTER_REFERENCE(1950, /* s t */ 'a' _ 'r' _ ';', 3, 0, 0x2606 _ 0)
+NAMED_CHARACTER_REFERENCE(1951, /* s t */ 'a' _ 'r' _ 'f' _ ';', 4, 0, 0x2605 _ 0)
+NAMED_CHARACTER_REFERENCE(1952, /* s t */ 'r' _ 'a' _ 'i' _ 'g' _ 'h' _ 't' _ 'e' _ 'p' _ 's' _ 'i' _ 'l' _ 'o' _ 'n' _ ';', 14, 0, 0x03f5 _ 0)
+NAMED_CHARACTER_REFERENCE(1953, /* s t */ 'r' _ 'a' _ 'i' _ 'g' _ 'h' _ 't' _ 'p' _ 'h' _ 'i' _ ';', 10, 0, 0x03d5 _ 0)
+NAMED_CHARACTER_REFERENCE(1954, /* s t */ 'r' _ 'n' _ 's' _ ';', 4, 0, 0x00af _ 0)
+NAMED_CHARACTER_REFERENCE(1955, /* s u */ 'b' _ ';', 2, 0, 0x2282 _ 0)
+NAMED_CHARACTER_REFERENCE(1956, /* s u */ 'b' _ 'E' _ ';', 3, 0, 0x2ac5 _ 0)
+NAMED_CHARACTER_REFERENCE(1957, /* s u */ 'b' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x2abd _ 0)
+NAMED_CHARACTER_REFERENCE(1958, /* s u */ 'b' _ 'e' _ ';', 3, 0, 0x2286 _ 0)
+NAMED_CHARACTER_REFERENCE(1959, /* s u */ 'b' _ 'e' _ 'd' _ 'o' _ 't' _ ';', 6, 0, 0x2ac3 _ 0)
+NAMED_CHARACTER_REFERENCE(1960, /* s u */ 'b' _ 'm' _ 'u' _ 'l' _ 't' _ ';', 6, 0, 0x2ac1 _ 0)
+NAMED_CHARACTER_REFERENCE(1961, /* s u */ 'b' _ 'n' _ 'E' _ ';', 4, 0, 0x2acb _ 0)
+NAMED_CHARACTER_REFERENCE(1962, /* s u */ 'b' _ 'n' _ 'e' _ ';', 4, 0, 0x228a _ 0)
+NAMED_CHARACTER_REFERENCE(1963, /* s u */ 'b' _ 'p' _ 'l' _ 'u' _ 's' _ ';', 6, 0, 0x2abf _ 0)
+NAMED_CHARACTER_REFERENCE(1964, /* s u */ 'b' _ 'r' _ 'a' _ 'r' _ 'r' _ ';', 6, 0, 0x2979 _ 0)
+NAMED_CHARACTER_REFERENCE(1965, /* s u */ 'b' _ 's' _ 'e' _ 't' _ ';', 5, 0, 0x2282 _ 0)
+NAMED_CHARACTER_REFERENCE(1966, /* s u */ 'b' _ 's' _ 'e' _ 't' _ 'e' _ 'q' _ ';', 7, 0, 0x2286 _ 0)
+NAMED_CHARACTER_REFERENCE(1967, /* s u */ 'b' _ 's' _ 'e' _ 't' _ 'e' _ 'q' _ 'q' _ ';', 8, 0, 0x2ac5 _ 0)
+NAMED_CHARACTER_REFERENCE(1968, /* s u */ 'b' _ 's' _ 'e' _ 't' _ 'n' _ 'e' _ 'q' _ ';', 8, 0, 0x228a _ 0)
+NAMED_CHARACTER_REFERENCE(1969, /* s u */ 'b' _ 's' _ 'e' _ 't' _ 'n' _ 'e' _ 'q' _ 'q' _ ';', 9, 0, 0x2acb _ 0)
+NAMED_CHARACTER_REFERENCE(1970, /* s u */ 'b' _ 's' _ 'i' _ 'm' _ ';', 5, 0, 0x2ac7 _ 0)
+NAMED_CHARACTER_REFERENCE(1971, /* s u */ 'b' _ 's' _ 'u' _ 'b' _ ';', 5, 0, 0x2ad5 _ 0)
+NAMED_CHARACTER_REFERENCE(1972, /* s u */ 'b' _ 's' _ 'u' _ 'p' _ ';', 5, 0, 0x2ad3 _ 0)
+NAMED_CHARACTER_REFERENCE(1973, /* s u */ 'c' _ 'c' _ ';', 3, 0, 0x227b _ 0)
+NAMED_CHARACTER_REFERENCE(1974, /* s u */ 'c' _ 'c' _ 'a' _ 'p' _ 'p' _ 'r' _ 'o' _ 'x' _ ';', 9, 0, 0x2ab8 _ 0)
+NAMED_CHARACTER_REFERENCE(1975, /* s u */ 'c' _ 'c' _ 'c' _ 'u' _ 'r' _ 'l' _ 'y' _ 'e' _ 'q' _ ';', 10, 0, 0x227d _ 0)
+NAMED_CHARACTER_REFERENCE(1976, /* s u */ 'c' _ 'c' _ 'e' _ 'q' _ ';', 5, 0, 0x2ab0 _ 0)
+NAMED_CHARACTER_REFERENCE(1977, /* s u */ 'c' _ 'c' _ 'n' _ 'a' _ 'p' _ 'p' _ 'r' _ 'o' _ 'x' _ ';', 10, 0, 0x2aba _ 0)
+NAMED_CHARACTER_REFERENCE(1978, /* s u */ 'c' _ 'c' _ 'n' _ 'e' _ 'q' _ 'q' _ ';', 7, 0, 0x2ab6 _ 0)
+NAMED_CHARACTER_REFERENCE(1979, /* s u */ 'c' _ 'c' _ 'n' _ 's' _ 'i' _ 'm' _ ';', 7, 0, 0x22e9 _ 0)
+NAMED_CHARACTER_REFERENCE(1980, /* s u */ 'c' _ 'c' _ 's' _ 'i' _ 'm' _ ';', 6, 0, 0x227f _ 0)
+NAMED_CHARACTER_REFERENCE(1981, /* s u */ 'm' _ ';', 2, 0, 0x2211 _ 0)
+NAMED_CHARACTER_REFERENCE(1982, /* s u */ 'n' _ 'g' _ ';', 3, 0, 0x266a _ 0)
+NAMED_CHARACTER_REFERENCE(1983, /* s u */ 'p' _ '1', 2, 0, 0x00b9 _ 0)
+NAMED_CHARACTER_REFERENCE(1984, /* s u */ 'p' _ '1' _ ';', 3, 0, 0x00b9 _ 0)
+NAMED_CHARACTER_REFERENCE(1985, /* s u */ 'p' _ '2', 2, 0, 0x00b2 _ 0)
+NAMED_CHARACTER_REFERENCE(1986, /* s u */ 'p' _ '2' _ ';', 3, 0, 0x00b2 _ 0)
+NAMED_CHARACTER_REFERENCE(1987, /* s u */ 'p' _ '3', 2, 0, 0x00b3 _ 0)
+NAMED_CHARACTER_REFERENCE(1988, /* s u */ 'p' _ '3' _ ';', 3, 0, 0x00b3 _ 0)
+NAMED_CHARACTER_REFERENCE(1989, /* s u */ 'p' _ ';', 2, 0, 0x2283 _ 0)
+NAMED_CHARACTER_REFERENCE(1990, /* s u */ 'p' _ 'E' _ ';', 3, 0, 0x2ac6 _ 0)
+NAMED_CHARACTER_REFERENCE(1991, /* s u */ 'p' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x2abe _ 0)
+NAMED_CHARACTER_REFERENCE(1992, /* s u */ 'p' _ 'd' _ 's' _ 'u' _ 'b' _ ';', 6, 0, 0x2ad8 _ 0)
+NAMED_CHARACTER_REFERENCE(1993, /* s u */ 'p' _ 'e' _ ';', 3, 0, 0x2287 _ 0)
+NAMED_CHARACTER_REFERENCE(1994, /* s u */ 'p' _ 'e' _ 'd' _ 'o' _ 't' _ ';', 6, 0, 0x2ac4 _ 0)
+NAMED_CHARACTER_REFERENCE(1995, /* s u */ 'p' _ 'h' _ 's' _ 'o' _ 'l' _ ';', 6, 0, 0x27c9 _ 0)
+NAMED_CHARACTER_REFERENCE(1996, /* s u */ 'p' _ 'h' _ 's' _ 'u' _ 'b' _ ';', 6, 0, 0x2ad7 _ 0)
+NAMED_CHARACTER_REFERENCE(1997, /* s u */ 'p' _ 'l' _ 'a' _ 'r' _ 'r' _ ';', 6, 0, 0x297b _ 0)
+NAMED_CHARACTER_REFERENCE(1998, /* s u */ 'p' _ 'm' _ 'u' _ 'l' _ 't' _ ';', 6, 0, 0x2ac2 _ 0)
+NAMED_CHARACTER_REFERENCE(1999, /* s u */ 'p' _ 'n' _ 'E' _ ';', 4, 0, 0x2acc _ 0)
+NAMED_CHARACTER_REFERENCE(2000, /* s u */ 'p' _ 'n' _ 'e' _ ';', 4, 0, 0x228b _ 0)
+NAMED_CHARACTER_REFERENCE(2001, /* s u */ 'p' _ 'p' _ 'l' _ 'u' _ 's' _ ';', 6, 0, 0x2ac0 _ 0)
+NAMED_CHARACTER_REFERENCE(2002, /* s u */ 'p' _ 's' _ 'e' _ 't' _ ';', 5, 0, 0x2283 _ 0)
+NAMED_CHARACTER_REFERENCE(2003, /* s u */ 'p' _ 's' _ 'e' _ 't' _ 'e' _ 'q' _ ';', 7, 0, 0x2287 _ 0)
+NAMED_CHARACTER_REFERENCE(2004, /* s u */ 'p' _ 's' _ 'e' _ 't' _ 'e' _ 'q' _ 'q' _ ';', 8, 0, 0x2ac6 _ 0)
+NAMED_CHARACTER_REFERENCE(2005, /* s u */ 'p' _ 's' _ 'e' _ 't' _ 'n' _ 'e' _ 'q' _ ';', 8, 0, 0x228b _ 0)
+NAMED_CHARACTER_REFERENCE(2006, /* s u */ 'p' _ 's' _ 'e' _ 't' _ 'n' _ 'e' _ 'q' _ 'q' _ ';', 9, 0, 0x2acc _ 0)
+NAMED_CHARACTER_REFERENCE(2007, /* s u */ 'p' _ 's' _ 'i' _ 'm' _ ';', 5, 0, 0x2ac8 _ 0)
+NAMED_CHARACTER_REFERENCE(2008, /* s u */ 'p' _ 's' _ 'u' _ 'b' _ ';', 5, 0, 0x2ad4 _ 0)
+NAMED_CHARACTER_REFERENCE(2009, /* s u */ 'p' _ 's' _ 'u' _ 'p' _ ';', 5, 0, 0x2ad6 _ 0)
+NAMED_CHARACTER_REFERENCE(2010, /* s w */ 'A' _ 'r' _ 'r' _ ';', 4, 0, 0x21d9 _ 0)
+NAMED_CHARACTER_REFERENCE(2011, /* s w */ 'a' _ 'r' _ 'h' _ 'k' _ ';', 5, 0, 0x2926 _ 0)
+NAMED_CHARACTER_REFERENCE(2012, /* s w */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x2199 _ 0)
+NAMED_CHARACTER_REFERENCE(2013, /* s w */ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 6, 0, 0x2199 _ 0)
+NAMED_CHARACTER_REFERENCE(2014, /* s w */ 'n' _ 'w' _ 'a' _ 'r' _ ';', 5, 0, 0x292a _ 0)
+NAMED_CHARACTER_REFERENCE(2015, /* s z */ 'l' _ 'i' _ 'g', 3, 0, 0x00df _ 0)
+NAMED_CHARACTER_REFERENCE(2016, /* s z */ 'l' _ 'i' _ 'g' _ ';', 4, 0, 0x00df _ 0)
+NAMED_CHARACTER_REFERENCE(2017, /* t a */ 'r' _ 'g' _ 'e' _ 't' _ ';', 5, 0, 0x2316 _ 0)
+NAMED_CHARACTER_REFERENCE(2018, /* t a */ 'u' _ ';', 2, 0, 0x03c4 _ 0)
+NAMED_CHARACTER_REFERENCE(2019, /* t b */ 'r' _ 'k' _ ';', 3, 0, 0x23b4 _ 0)
+NAMED_CHARACTER_REFERENCE(2020, /* t c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x0165 _ 0)
+NAMED_CHARACTER_REFERENCE(2021, /* t c */ 'e' _ 'd' _ 'i' _ 'l' _ ';', 5, 0, 0x0163 _ 0)
+NAMED_CHARACTER_REFERENCE(2022, /* t c */ 'y' _ ';', 2, 0, 0x0442 _ 0)
+NAMED_CHARACTER_REFERENCE(2023, /* t d */ 'o' _ 't' _ ';', 3, 0, 0x20db _ 0)
+NAMED_CHARACTER_REFERENCE(2024, /* t e */ 'l' _ 'r' _ 'e' _ 'c' _ ';', 5, 0, 0x2315 _ 0)
+NAMED_CHARACTER_REFERENCE(2025, /* t f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd31)
+NAMED_CHARACTER_REFERENCE(2026, /* t h */ 'e' _ 'r' _ 'e' _ '4' _ ';', 5, 0, 0x2234 _ 0)
+NAMED_CHARACTER_REFERENCE(2027, /* t h */ 'e' _ 'r' _ 'e' _ 'f' _ 'o' _ 'r' _ 'e' _ ';', 8, 0, 0x2234 _ 0)
+NAMED_CHARACTER_REFERENCE(2028, /* t h */ 'e' _ 't' _ 'a' _ ';', 4, 0, 0x03b8 _ 0)
+NAMED_CHARACTER_REFERENCE(2029, /* t h */ 'e' _ 't' _ 'a' _ 's' _ 'y' _ 'm' _ ';', 7, 0, 0x03d1 _ 0)
+NAMED_CHARACTER_REFERENCE(2030, /* t h */ 'e' _ 't' _ 'a' _ 'v' _ ';', 5, 0, 0x03d1 _ 0)
+NAMED_CHARACTER_REFERENCE(2031, /* t h */ 'i' _ 'c' _ 'k' _ 'a' _ 'p' _ 'p' _ 'r' _ 'o' _ 'x' _ ';', 10, 0, 0x2248 _ 0)
+NAMED_CHARACTER_REFERENCE(2032, /* t h */ 'i' _ 'c' _ 'k' _ 's' _ 'i' _ 'm' _ ';', 7, 0, 0x223c _ 0)
+NAMED_CHARACTER_REFERENCE(2033, /* t h */ 'i' _ 'n' _ 's' _ 'p' _ ';', 5, 0, 0x2009 _ 0)
+NAMED_CHARACTER_REFERENCE(2034, /* t h */ 'k' _ 'a' _ 'p' _ ';', 4, 0, 0x2248 _ 0)
+NAMED_CHARACTER_REFERENCE(2035, /* t h */ 'k' _ 's' _ 'i' _ 'm' _ ';', 5, 0, 0x223c _ 0)
+NAMED_CHARACTER_REFERENCE(2036, /* t h */ 'o' _ 'r' _ 'n', 3, 0, 0x00fe _ 0)
+NAMED_CHARACTER_REFERENCE(2037, /* t h */ 'o' _ 'r' _ 'n' _ ';', 4, 0, 0x00fe _ 0)
+NAMED_CHARACTER_REFERENCE(2038, /* t i */ 'l' _ 'd' _ 'e' _ ';', 4, 0, 0x02dc _ 0)
+NAMED_CHARACTER_REFERENCE(2039, /* t i */ 'm' _ 'e' _ 's', 3, 0, 0x00d7 _ 0)
+NAMED_CHARACTER_REFERENCE(2040, /* t i */ 'm' _ 'e' _ 's' _ ';', 4, 0, 0x00d7 _ 0)
+NAMED_CHARACTER_REFERENCE(2041, /* t i */ 'm' _ 'e' _ 's' _ 'b' _ ';', 5, 0, 0x22a0 _ 0)
+NAMED_CHARACTER_REFERENCE(2042, /* t i */ 'm' _ 'e' _ 's' _ 'b' _ 'a' _ 'r' _ ';', 7, 0, 0x2a31 _ 0)
+NAMED_CHARACTER_REFERENCE(2043, /* t i */ 'm' _ 'e' _ 's' _ 'd' _ ';', 5, 0, 0x2a30 _ 0)
+NAMED_CHARACTER_REFERENCE(2044, /* t i */ 'n' _ 't' _ ';', 3, 0, 0x222d _ 0)
+NAMED_CHARACTER_REFERENCE(2045, /* t o */ 'e' _ 'a' _ ';', 3, 0, 0x2928 _ 0)
+NAMED_CHARACTER_REFERENCE(2046, /* t o */ 'p' _ ';', 2, 0, 0x22a4 _ 0)
+NAMED_CHARACTER_REFERENCE(2047, /* t o */ 'p' _ 'b' _ 'o' _ 't' _ ';', 5, 0, 0x2336 _ 0)
+NAMED_CHARACTER_REFERENCE(2048, /* t o */ 'p' _ 'c' _ 'i' _ 'r' _ ';', 5, 0, 0x2af1 _ 0)
+NAMED_CHARACTER_REFERENCE(2049, /* t o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd65)
+NAMED_CHARACTER_REFERENCE(2050, /* t o */ 'p' _ 'f' _ 'o' _ 'r' _ 'k' _ ';', 6, 0, 0x2ada _ 0)
+NAMED_CHARACTER_REFERENCE(2051, /* t o */ 's' _ 'a' _ ';', 3, 0, 0x2929 _ 0)
+NAMED_CHARACTER_REFERENCE(2052, /* t p */ 'r' _ 'i' _ 'm' _ 'e' _ ';', 5, 0, 0x2034 _ 0)
+NAMED_CHARACTER_REFERENCE(2053, /* t r */ 'a' _ 'd' _ 'e' _ ';', 4, 0, 0x2122 _ 0)
+NAMED_CHARACTER_REFERENCE(2054, /* t r */ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ ';', 7, 0, 0x25b5 _ 0)
+NAMED_CHARACTER_REFERENCE(2055, /* t r */ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'd' _ 'o' _ 'w' _ 'n' _ ';', 11, 0, 0x25bf _ 0)
+NAMED_CHARACTER_REFERENCE(2056, /* t r */ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'l' _ 'e' _ 'f' _ 't' _ ';', 11, 0, 0x25c3 _ 0)
+NAMED_CHARACTER_REFERENCE(2057, /* t r */ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'l' _ 'e' _ 'f' _ 't' _ 'e' _ 'q' _ ';', 13, 0, 0x22b4 _ 0)
+NAMED_CHARACTER_REFERENCE(2058, /* t r */ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'q' _ ';', 8, 0, 0x225c _ 0)
+NAMED_CHARACTER_REFERENCE(2059, /* t r */ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ ';', 12, 0, 0x25b9 _ 0)
+NAMED_CHARACTER_REFERENCE(2060, /* t r */ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'e' _ 'q' _ ';', 14, 0, 0x22b5 _ 0)
+NAMED_CHARACTER_REFERENCE(2061, /* t r */ 'i' _ 'd' _ 'o' _ 't' _ ';', 5, 0, 0x25ec _ 0)
+NAMED_CHARACTER_REFERENCE(2062, /* t r */ 'i' _ 'e' _ ';', 3, 0, 0x225c _ 0)
+NAMED_CHARACTER_REFERENCE(2063, /* t r */ 'i' _ 'm' _ 'i' _ 'n' _ 'u' _ 's' _ ';', 7, 0, 0x2a3a _ 0)
+NAMED_CHARACTER_REFERENCE(2064, /* t r */ 'i' _ 'p' _ 'l' _ 'u' _ 's' _ ';', 6, 0, 0x2a39 _ 0)
+NAMED_CHARACTER_REFERENCE(2065, /* t r */ 'i' _ 's' _ 'b' _ ';', 4, 0, 0x29cd _ 0)
+NAMED_CHARACTER_REFERENCE(2066, /* t r */ 'i' _ 't' _ 'i' _ 'm' _ 'e' _ ';', 6, 0, 0x2a3b _ 0)
+NAMED_CHARACTER_REFERENCE(2067, /* t r */ 'p' _ 'e' _ 'z' _ 'i' _ 'u' _ 'm' _ ';', 7, 0, 0x23e2 _ 0)
+NAMED_CHARACTER_REFERENCE(2068, /* t s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcc9)
+NAMED_CHARACTER_REFERENCE(2069, /* t s */ 'c' _ 'y' _ ';', 3, 0, 0x0446 _ 0)
+NAMED_CHARACTER_REFERENCE(2070, /* t s */ 'h' _ 'c' _ 'y' _ ';', 4, 0, 0x045b _ 0)
+NAMED_CHARACTER_REFERENCE(2071, /* t s */ 't' _ 'r' _ 'o' _ 'k' _ ';', 5, 0, 0x0167 _ 0)
+NAMED_CHARACTER_REFERENCE(2072, /* t w */ 'i' _ 'x' _ 't' _ ';', 4, 0, 0x226c _ 0)
+NAMED_CHARACTER_REFERENCE(2073, /* t w */ 'o' _ 'h' _ 'e' _ 'a' _ 'd' _ 'l' _ 'e' _ 'f' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 15, 0, 0x219e _ 0)
+NAMED_CHARACTER_REFERENCE(2074, /* t w */ 'o' _ 'h' _ 'e' _ 'a' _ 'd' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 16, 0, 0x21a0 _ 0)
+NAMED_CHARACTER_REFERENCE(2075, /* u A */ 'r' _ 'r' _ ';', 3, 0, 0x21d1 _ 0)
+NAMED_CHARACTER_REFERENCE(2076, /* u H */ 'a' _ 'r' _ ';', 3, 0, 0x2963 _ 0)
+NAMED_CHARACTER_REFERENCE(2077, /* u a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00fa _ 0)
+NAMED_CHARACTER_REFERENCE(2078, /* u a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00fa _ 0)
+NAMED_CHARACTER_REFERENCE(2079, /* u a */ 'r' _ 'r' _ ';', 3, 0, 0x2191 _ 0)
+NAMED_CHARACTER_REFERENCE(2080, /* u b */ 'r' _ 'c' _ 'y' _ ';', 4, 0, 0x045e _ 0)
+NAMED_CHARACTER_REFERENCE(2081, /* u b */ 'r' _ 'e' _ 'v' _ 'e' _ ';', 5, 0, 0x016d _ 0)
+NAMED_CHARACTER_REFERENCE(2082, /* u c */ 'i' _ 'r' _ 'c', 3, 0, 0x00fb _ 0)
+NAMED_CHARACTER_REFERENCE(2083, /* u c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x00fb _ 0)
+NAMED_CHARACTER_REFERENCE(2084, /* u c */ 'y' _ ';', 2, 0, 0x0443 _ 0)
+NAMED_CHARACTER_REFERENCE(2085, /* u d */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21c5 _ 0)
+NAMED_CHARACTER_REFERENCE(2086, /* u d */ 'b' _ 'l' _ 'a' _ 'c' _ ';', 5, 0, 0x0171 _ 0)
+NAMED_CHARACTER_REFERENCE(2087, /* u d */ 'h' _ 'a' _ 'r' _ ';', 4, 0, 0x296e _ 0)
+NAMED_CHARACTER_REFERENCE(2088, /* u f */ 'i' _ 's' _ 'h' _ 't' _ ';', 5, 0, 0x297e _ 0)
+NAMED_CHARACTER_REFERENCE(2089, /* u f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd32)
+NAMED_CHARACTER_REFERENCE(2090, /* u g */ 'r' _ 'a' _ 'v' _ 'e', 4, 0, 0x00f9 _ 0)
+NAMED_CHARACTER_REFERENCE(2091, /* u g */ 'r' _ 'a' _ 'v' _ 'e' _ ';', 5, 0, 0x00f9 _ 0)
+NAMED_CHARACTER_REFERENCE(2092, /* u h */ 'a' _ 'r' _ 'l' _ ';', 4, 0, 0x21bf _ 0)
+NAMED_CHARACTER_REFERENCE(2093, /* u h */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21be _ 0)
+NAMED_CHARACTER_REFERENCE(2094, /* u h */ 'b' _ 'l' _ 'k' _ ';', 4, 0, 0x2580 _ 0)
+NAMED_CHARACTER_REFERENCE(2095, /* u l */ 'c' _ 'o' _ 'r' _ 'n' _ ';', 5, 0, 0x231c _ 0)
+NAMED_CHARACTER_REFERENCE(2096, /* u l */ 'c' _ 'o' _ 'r' _ 'n' _ 'e' _ 'r' _ ';', 7, 0, 0x231c _ 0)
+NAMED_CHARACTER_REFERENCE(2097, /* u l */ 'c' _ 'r' _ 'o' _ 'p' _ ';', 5, 0, 0x230f _ 0)
+NAMED_CHARACTER_REFERENCE(2098, /* u l */ 't' _ 'r' _ 'i' _ ';', 4, 0, 0x25f8 _ 0)
+NAMED_CHARACTER_REFERENCE(2099, /* u m */ 'a' _ 'c' _ 'r' _ ';', 4, 0, 0x016b _ 0)
+NAMED_CHARACTER_REFERENCE(2100, /* u m */ 'l', 1, 0, 0x00a8 _ 0)
+NAMED_CHARACTER_REFERENCE(2101, /* u m */ 'l' _ ';', 2, 0, 0x00a8 _ 0)
+NAMED_CHARACTER_REFERENCE(2102, /* u o */ 'g' _ 'o' _ 'n' _ ';', 4, 0, 0x0173 _ 0)
+NAMED_CHARACTER_REFERENCE(2103, /* u o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd66)
+NAMED_CHARACTER_REFERENCE(2104, /* u p */ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 6, 0, 0x2191 _ 0)
+NAMED_CHARACTER_REFERENCE(2105, /* u p */ 'd' _ 'o' _ 'w' _ 'n' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ ';', 10, 0, 0x2195 _ 0)
+NAMED_CHARACTER_REFERENCE(2106, /* u p */ 'h' _ 'a' _ 'r' _ 'p' _ 'o' _ 'o' _ 'n' _ 'l' _ 'e' _ 'f' _ 't' _ ';', 12, 0, 0x21bf _ 0)
+NAMED_CHARACTER_REFERENCE(2107, /* u p */ 'h' _ 'a' _ 'r' _ 'p' _ 'o' _ 'o' _ 'n' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ ';', 13, 0, 0x21be _ 0)
+NAMED_CHARACTER_REFERENCE(2108, /* u p */ 'l' _ 'u' _ 's' _ ';', 4, 0, 0x228e _ 0)
+NAMED_CHARACTER_REFERENCE(2109, /* u p */ 's' _ 'i' _ ';', 3, 0, 0x03c5 _ 0)
+NAMED_CHARACTER_REFERENCE(2110, /* u p */ 's' _ 'i' _ 'h' _ ';', 4, 0, 0x03d2 _ 0)
+NAMED_CHARACTER_REFERENCE(2111, /* u p */ 's' _ 'i' _ 'l' _ 'o' _ 'n' _ ';', 6, 0, 0x03c5 _ 0)
+NAMED_CHARACTER_REFERENCE(2112, /* u p */ 'u' _ 'p' _ 'a' _ 'r' _ 'r' _ 'o' _ 'w' _ 's' _ ';', 9, 0, 0x21c8 _ 0)
+NAMED_CHARACTER_REFERENCE(2113, /* u r */ 'c' _ 'o' _ 'r' _ 'n' _ ';', 5, 0, 0x231d _ 0)
+NAMED_CHARACTER_REFERENCE(2114, /* u r */ 'c' _ 'o' _ 'r' _ 'n' _ 'e' _ 'r' _ ';', 7, 0, 0x231d _ 0)
+NAMED_CHARACTER_REFERENCE(2115, /* u r */ 'c' _ 'r' _ 'o' _ 'p' _ ';', 5, 0, 0x230e _ 0)
+NAMED_CHARACTER_REFERENCE(2116, /* u r */ 'i' _ 'n' _ 'g' _ ';', 4, 0, 0x016f _ 0)
+NAMED_CHARACTER_REFERENCE(2117, /* u r */ 't' _ 'r' _ 'i' _ ';', 4, 0, 0x25f9 _ 0)
+NAMED_CHARACTER_REFERENCE(2118, /* u s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcca)
+NAMED_CHARACTER_REFERENCE(2119, /* u t */ 'd' _ 'o' _ 't' _ ';', 4, 0, 0x22f0 _ 0)
+NAMED_CHARACTER_REFERENCE(2120, /* u t */ 'i' _ 'l' _ 'd' _ 'e' _ ';', 5, 0, 0x0169 _ 0)
+NAMED_CHARACTER_REFERENCE(2121, /* u t */ 'r' _ 'i' _ ';', 3, 0, 0x25b5 _ 0)
+NAMED_CHARACTER_REFERENCE(2122, /* u t */ 'r' _ 'i' _ 'f' _ ';', 4, 0, 0x25b4 _ 0)
+NAMED_CHARACTER_REFERENCE(2123, /* u u */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x21c8 _ 0)
+NAMED_CHARACTER_REFERENCE(2124, /* u u */ 'm' _ 'l', 2, 0, 0x00fc _ 0)
+NAMED_CHARACTER_REFERENCE(2125, /* u u */ 'm' _ 'l' _ ';', 3, 0, 0x00fc _ 0)
+NAMED_CHARACTER_REFERENCE(2126, /* u w */ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ ';', 6, 0, 0x29a7 _ 0)
+NAMED_CHARACTER_REFERENCE(2127, /* v A */ 'r' _ 'r' _ ';', 3, 0, 0x21d5 _ 0)
+NAMED_CHARACTER_REFERENCE(2128, /* v B */ 'a' _ 'r' _ ';', 3, 0, 0x2ae8 _ 0)
+NAMED_CHARACTER_REFERENCE(2129, /* v B */ 'a' _ 'r' _ 'v' _ ';', 4, 0, 0x2ae9 _ 0)
+NAMED_CHARACTER_REFERENCE(2130, /* v D */ 'a' _ 's' _ 'h' _ ';', 4, 0, 0x22a8 _ 0)
+NAMED_CHARACTER_REFERENCE(2131, /* v a */ 'n' _ 'g' _ 'r' _ 't' _ ';', 5, 0, 0x299c _ 0)
+NAMED_CHARACTER_REFERENCE(2132, /* v a */ 'r' _ 'e' _ 'p' _ 's' _ 'i' _ 'l' _ 'o' _ 'n' _ ';', 9, 0, 0x03f5 _ 0)
+NAMED_CHARACTER_REFERENCE(2133, /* v a */ 'r' _ 'k' _ 'a' _ 'p' _ 'p' _ 'a' _ ';', 7, 0, 0x03f0 _ 0)
+NAMED_CHARACTER_REFERENCE(2134, /* v a */ 'r' _ 'n' _ 'o' _ 't' _ 'h' _ 'i' _ 'n' _ 'g' _ ';', 9, 0, 0x2205 _ 0)
+NAMED_CHARACTER_REFERENCE(2135, /* v a */ 'r' _ 'p' _ 'h' _ 'i' _ ';', 5, 0, 0x03d5 _ 0)
+NAMED_CHARACTER_REFERENCE(2136, /* v a */ 'r' _ 'p' _ 'i' _ ';', 4, 0, 0x03d6 _ 0)
+NAMED_CHARACTER_REFERENCE(2137, /* v a */ 'r' _ 'p' _ 'r' _ 'o' _ 'p' _ 't' _ 'o' _ ';', 8, 0, 0x221d _ 0)
+NAMED_CHARACTER_REFERENCE(2138, /* v a */ 'r' _ 'r' _ ';', 3, 0, 0x2195 _ 0)
+NAMED_CHARACTER_REFERENCE(2139, /* v a */ 'r' _ 'r' _ 'h' _ 'o' _ ';', 5, 0, 0x03f1 _ 0)
+NAMED_CHARACTER_REFERENCE(2140, /* v a */ 'r' _ 's' _ 'i' _ 'g' _ 'm' _ 'a' _ ';', 7, 0, 0x03c2 _ 0)
+NAMED_CHARACTER_REFERENCE(2141, /* v a */ 'r' _ 's' _ 'u' _ 'b' _ 's' _ 'e' _ 't' _ 'n' _ 'e' _ 'q' _ ';', 11, 0, 0x228a _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(2142, /* v a */ 'r' _ 's' _ 'u' _ 'b' _ 's' _ 'e' _ 't' _ 'n' _ 'e' _ 'q' _ 'q' _ ';', 12, 0, 0x2acb _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(2143, /* v a */ 'r' _ 's' _ 'u' _ 'p' _ 's' _ 'e' _ 't' _ 'n' _ 'e' _ 'q' _ ';', 11, 0, 0x228b _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(2144, /* v a */ 'r' _ 's' _ 'u' _ 'p' _ 's' _ 'e' _ 't' _ 'n' _ 'e' _ 'q' _ 'q' _ ';', 12, 0, 0x2acc _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(2145, /* v a */ 'r' _ 't' _ 'h' _ 'e' _ 't' _ 'a' _ ';', 7, 0, 0x03d1 _ 0)
+NAMED_CHARACTER_REFERENCE(2146, /* v a */ 'r' _ 't' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'l' _ 'e' _ 'f' _ 't' _ ';', 14, 0, 0x22b2 _ 0)
+NAMED_CHARACTER_REFERENCE(2147, /* v a */ 'r' _ 't' _ 'r' _ 'i' _ 'a' _ 'n' _ 'g' _ 'l' _ 'e' _ 'r' _ 'i' _ 'g' _ 'h' _ 't' _ ';', 15, 0, 0x22b3 _ 0)
+NAMED_CHARACTER_REFERENCE(2148, /* v c */ 'y' _ ';', 2, 0, 0x0432 _ 0)
+NAMED_CHARACTER_REFERENCE(2149, /* v d */ 'a' _ 's' _ 'h' _ ';', 4, 0, 0x22a2 _ 0)
+NAMED_CHARACTER_REFERENCE(2150, /* v e */ 'e' _ ';', 2, 0, 0x2228 _ 0)
+NAMED_CHARACTER_REFERENCE(2151, /* v e */ 'e' _ 'b' _ 'a' _ 'r' _ ';', 5, 0, 0x22bb _ 0)
+NAMED_CHARACTER_REFERENCE(2152, /* v e */ 'e' _ 'e' _ 'q' _ ';', 4, 0, 0x225a _ 0)
+NAMED_CHARACTER_REFERENCE(2153, /* v e */ 'l' _ 'l' _ 'i' _ 'p' _ ';', 5, 0, 0x22ee _ 0)
+NAMED_CHARACTER_REFERENCE(2154, /* v e */ 'r' _ 'b' _ 'a' _ 'r' _ ';', 5, 0, 0x007c _ 0)
+NAMED_CHARACTER_REFERENCE(2155, /* v e */ 'r' _ 't' _ ';', 3, 0, 0x007c _ 0)
+NAMED_CHARACTER_REFERENCE(2156, /* v f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd33)
+NAMED_CHARACTER_REFERENCE(2157, /* v l */ 't' _ 'r' _ 'i' _ ';', 4, 0, 0x22b2 _ 0)
+NAMED_CHARACTER_REFERENCE(2158, /* v n */ 's' _ 'u' _ 'b' _ ';', 4, 0, 0x2282 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(2159, /* v n */ 's' _ 'u' _ 'p' _ ';', 4, 0, 0x2283 _ 0x20d2)
+NAMED_CHARACTER_REFERENCE(2160, /* v o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd67)
+NAMED_CHARACTER_REFERENCE(2161, /* v p */ 'r' _ 'o' _ 'p' _ ';', 4, 0, 0x221d _ 0)
+NAMED_CHARACTER_REFERENCE(2162, /* v r */ 't' _ 'r' _ 'i' _ ';', 4, 0, 0x22b3 _ 0)
+NAMED_CHARACTER_REFERENCE(2163, /* v s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdccb)
+NAMED_CHARACTER_REFERENCE(2164, /* v s */ 'u' _ 'b' _ 'n' _ 'E' _ ';', 5, 0, 0x2acb _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(2165, /* v s */ 'u' _ 'b' _ 'n' _ 'e' _ ';', 5, 0, 0x228a _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(2166, /* v s */ 'u' _ 'p' _ 'n' _ 'E' _ ';', 5, 0, 0x2acc _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(2167, /* v s */ 'u' _ 'p' _ 'n' _ 'e' _ ';', 5, 0, 0x228b _ 0xfe00)
+NAMED_CHARACTER_REFERENCE(2168, /* v z */ 'i' _ 'g' _ 'z' _ 'a' _ 'g' _ ';', 6, 0, 0x299a _ 0)
+NAMED_CHARACTER_REFERENCE(2169, /* w c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x0175 _ 0)
+NAMED_CHARACTER_REFERENCE(2170, /* w e */ 'd' _ 'b' _ 'a' _ 'r' _ ';', 5, 0, 0x2a5f _ 0)
+NAMED_CHARACTER_REFERENCE(2171, /* w e */ 'd' _ 'g' _ 'e' _ ';', 4, 0, 0x2227 _ 0)
+NAMED_CHARACTER_REFERENCE(2172, /* w e */ 'd' _ 'g' _ 'e' _ 'q' _ ';', 5, 0, 0x2259 _ 0)
+NAMED_CHARACTER_REFERENCE(2173, /* w e */ 'i' _ 'e' _ 'r' _ 'p' _ ';', 5, 0, 0x2118 _ 0)
+NAMED_CHARACTER_REFERENCE(2174, /* w f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd34)
+NAMED_CHARACTER_REFERENCE(2175, /* w o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd68)
+NAMED_CHARACTER_REFERENCE(2176, /* w p */ ';', 1, 0, 0x2118 _ 0)
+NAMED_CHARACTER_REFERENCE(2177, /* w r */ ';', 1, 0, 0x2240 _ 0)
+NAMED_CHARACTER_REFERENCE(2178, /* w r */ 'e' _ 'a' _ 't' _ 'h' _ ';', 5, 0, 0x2240 _ 0)
+NAMED_CHARACTER_REFERENCE(2179, /* w s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdccc)
+NAMED_CHARACTER_REFERENCE(2180, /* x c */ 'a' _ 'p' _ ';', 3, 0, 0x22c2 _ 0)
+NAMED_CHARACTER_REFERENCE(2181, /* x c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x25ef _ 0)
+NAMED_CHARACTER_REFERENCE(2182, /* x c */ 'u' _ 'p' _ ';', 3, 0, 0x22c3 _ 0)
+NAMED_CHARACTER_REFERENCE(2183, /* x d */ 't' _ 'r' _ 'i' _ ';', 4, 0, 0x25bd _ 0)
+NAMED_CHARACTER_REFERENCE(2184, /* x f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd35)
+NAMED_CHARACTER_REFERENCE(2185, /* x h */ 'A' _ 'r' _ 'r' _ ';', 4, 0, 0x27fa _ 0)
+NAMED_CHARACTER_REFERENCE(2186, /* x h */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x27f7 _ 0)
+NAMED_CHARACTER_REFERENCE(2187, /* x i */ ';', 1, 0, 0x03be _ 0)
+NAMED_CHARACTER_REFERENCE(2188, /* x l */ 'A' _ 'r' _ 'r' _ ';', 4, 0, 0x27f8 _ 0)
+NAMED_CHARACTER_REFERENCE(2189, /* x l */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x27f5 _ 0)
+NAMED_CHARACTER_REFERENCE(2190, /* x m */ 'a' _ 'p' _ ';', 3, 0, 0x27fc _ 0)
+NAMED_CHARACTER_REFERENCE(2191, /* x n */ 'i' _ 's' _ ';', 3, 0, 0x22fb _ 0)
+NAMED_CHARACTER_REFERENCE(2192, /* x o */ 'd' _ 'o' _ 't' _ ';', 4, 0, 0x2a00 _ 0)
+NAMED_CHARACTER_REFERENCE(2193, /* x o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd69)
+NAMED_CHARACTER_REFERENCE(2194, /* x o */ 'p' _ 'l' _ 'u' _ 's' _ ';', 5, 0, 0x2a01 _ 0)
+NAMED_CHARACTER_REFERENCE(2195, /* x o */ 't' _ 'i' _ 'm' _ 'e' _ ';', 5, 0, 0x2a02 _ 0)
+NAMED_CHARACTER_REFERENCE(2196, /* x r */ 'A' _ 'r' _ 'r' _ ';', 4, 0, 0x27f9 _ 0)
+NAMED_CHARACTER_REFERENCE(2197, /* x r */ 'a' _ 'r' _ 'r' _ ';', 4, 0, 0x27f6 _ 0)
+NAMED_CHARACTER_REFERENCE(2198, /* x s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdccd)
+NAMED_CHARACTER_REFERENCE(2199, /* x s */ 'q' _ 'c' _ 'u' _ 'p' _ ';', 5, 0, 0x2a06 _ 0)
+NAMED_CHARACTER_REFERENCE(2200, /* x u */ 'p' _ 'l' _ 'u' _ 's' _ ';', 5, 0, 0x2a04 _ 0)
+NAMED_CHARACTER_REFERENCE(2201, /* x u */ 't' _ 'r' _ 'i' _ ';', 4, 0, 0x25b3 _ 0)
+NAMED_CHARACTER_REFERENCE(2202, /* x v */ 'e' _ 'e' _ ';', 3, 0, 0x22c1 _ 0)
+NAMED_CHARACTER_REFERENCE(2203, /* x w */ 'e' _ 'd' _ 'g' _ 'e' _ ';', 5, 0, 0x22c0 _ 0)
+NAMED_CHARACTER_REFERENCE(2204, /* y a */ 'c' _ 'u' _ 't' _ 'e', 4, 0, 0x00fd _ 0)
+NAMED_CHARACTER_REFERENCE(2205, /* y a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x00fd _ 0)
+NAMED_CHARACTER_REFERENCE(2206, /* y a */ 'c' _ 'y' _ ';', 3, 0, 0x044f _ 0)
+NAMED_CHARACTER_REFERENCE(2207, /* y c */ 'i' _ 'r' _ 'c' _ ';', 4, 0, 0x0177 _ 0)
+NAMED_CHARACTER_REFERENCE(2208, /* y c */ 'y' _ ';', 2, 0, 0x044b _ 0)
+NAMED_CHARACTER_REFERENCE(2209, /* y e */ 'n', 1, 0, 0x00a5 _ 0)
+NAMED_CHARACTER_REFERENCE(2210, /* y e */ 'n' _ ';', 2, 0, 0x00a5 _ 0)
+NAMED_CHARACTER_REFERENCE(2211, /* y f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd36)
+NAMED_CHARACTER_REFERENCE(2212, /* y i */ 'c' _ 'y' _ ';', 3, 0, 0x0457 _ 0)
+NAMED_CHARACTER_REFERENCE(2213, /* y o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd6a)
+NAMED_CHARACTER_REFERENCE(2214, /* y s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdcce)
+NAMED_CHARACTER_REFERENCE(2215, /* y u */ 'c' _ 'y' _ ';', 3, 0, 0x044e _ 0)
+NAMED_CHARACTER_REFERENCE(2216, /* y u */ 'm' _ 'l', 2, 0, 0x00ff _ 0)
+NAMED_CHARACTER_REFERENCE(2217, /* y u */ 'm' _ 'l' _ ';', 3, 0, 0x00ff _ 0)
+NAMED_CHARACTER_REFERENCE(2218, /* z a */ 'c' _ 'u' _ 't' _ 'e' _ ';', 5, 0, 0x017a _ 0)
+NAMED_CHARACTER_REFERENCE(2219, /* z c */ 'a' _ 'r' _ 'o' _ 'n' _ ';', 5, 0, 0x017e _ 0)
+NAMED_CHARACTER_REFERENCE(2220, /* z c */ 'y' _ ';', 2, 0, 0x0437 _ 0)
+NAMED_CHARACTER_REFERENCE(2221, /* z d */ 'o' _ 't' _ ';', 3, 0, 0x017c _ 0)
+NAMED_CHARACTER_REFERENCE(2222, /* z e */ 'e' _ 't' _ 'r' _ 'f' _ ';', 5, 0, 0x2128 _ 0)
+NAMED_CHARACTER_REFERENCE(2223, /* z e */ 't' _ 'a' _ ';', 3, 0, 0x03b6 _ 0)
+NAMED_CHARACTER_REFERENCE(2224, /* z f */ 'r' _ ';', 2, 0, 0xd835 _ 0xdd37)
+NAMED_CHARACTER_REFERENCE(2225, /* z h */ 'c' _ 'y' _ ';', 3, 0, 0x0436 _ 0)
+NAMED_CHARACTER_REFERENCE(2226, /* z i */ 'g' _ 'r' _ 'a' _ 'r' _ 'r' _ ';', 6, 0, 0x21dd _ 0)
+NAMED_CHARACTER_REFERENCE(2227, /* z o */ 'p' _ 'f' _ ';', 3, 0, 0xd835 _ 0xdd6b)
+NAMED_CHARACTER_REFERENCE(2228, /* z s */ 'c' _ 'r' _ ';', 3, 0, 0xd835 _ 0xdccf)
+NAMED_CHARACTER_REFERENCE(2229, /* z w */ 'j' _ ';', 2, 0, 0x200d _ 0)
+NAMED_CHARACTER_REFERENCE(2230, /* z w */ 'n' _ 'j' _ ';', 3, 0, 0x200c _ 0)
+
+#undef _
diff --git a/components/htmlfive/nsHtml5OplessBuilder.cpp b/components/htmlfive/nsHtml5OplessBuilder.cpp
new file mode 100644
index 000000000..ccb730091
--- /dev/null
+++ b/components/htmlfive/nsHtml5OplessBuilder.cpp
@@ -0,0 +1,48 @@
+/* -*- 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 "nsHtml5OplessBuilder.h"
+
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "nsIDocShell.h"
+#include "nsIHTMLDocument.h"
+
+nsHtml5OplessBuilder::nsHtml5OplessBuilder()
+ : nsHtml5DocumentBuilder(true)
+{
+}
+
+nsHtml5OplessBuilder::~nsHtml5OplessBuilder()
+{
+}
+
+void
+nsHtml5OplessBuilder::Start()
+{
+ mFlushState = eInFlush;
+ BeginDocUpdate();
+}
+
+void
+nsHtml5OplessBuilder::Finish()
+{
+ EndDocUpdate();
+ DropParserAndPerfHint();
+ mScriptLoader = nullptr;
+ mDocument = nullptr;
+ mNodeInfoManager = nullptr;
+ mCSSLoader = nullptr;
+ mDocumentURI = nullptr;
+ mDocShell = nullptr;
+ mOwnedElements.Clear();
+ mFlushState = eNotFlushing;
+}
+
+void
+nsHtml5OplessBuilder::SetParser(nsParserBase* aParser)
+{
+ mParser = aParser;
+}
diff --git a/components/htmlfive/nsHtml5OplessBuilder.h b/components/htmlfive/nsHtml5OplessBuilder.h
new file mode 100644
index 000000000..0f063b76c
--- /dev/null
+++ b/components/htmlfive/nsHtml5OplessBuilder.h
@@ -0,0 +1,34 @@
+/* -*- 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 nsHtml5OplessBuilder_h
+#define nsHtml5OplessBuilder_h
+
+#include "nsHtml5DocumentBuilder.h"
+
+class nsParserBase;
+
+/**
+ * This class implements a minimal subclass of nsHtml5DocumentBuilder that
+ * works when tree operation queues that are part of the off-the-main-thread
+ * parsing machinery are not used and, therefore, nsHtml5TreeOpExecutor is
+ * not used.
+ *
+ * This class is mostly responsible for wrapping tree building in an update
+ * batch and resetting various fields in nsContentSink upon finishing.
+ */
+class nsHtml5OplessBuilder : public nsHtml5DocumentBuilder
+{
+public:
+ NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+
+ nsHtml5OplessBuilder();
+ ~nsHtml5OplessBuilder();
+ void Start();
+ void Finish();
+ void SetParser(nsParserBase* aParser);
+};
+
+#endif // nsHtml5OplessBuilder_h
diff --git a/components/htmlfive/nsHtml5OwningUTF16Buffer.cpp b/components/htmlfive/nsHtml5OwningUTF16Buffer.cpp
new file mode 100644
index 000000000..213e40209
--- /dev/null
+++ b/components/htmlfive/nsHtml5OwningUTF16Buffer.cpp
@@ -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/. */
+
+#include "nsHtml5OwningUTF16Buffer.h"
+
+nsHtml5OwningUTF16Buffer::nsHtml5OwningUTF16Buffer(char16_t* aBuffer)
+ : nsHtml5UTF16Buffer(aBuffer, 0),
+ next(nullptr),
+ key(nullptr)
+{
+ MOZ_COUNT_CTOR(nsHtml5OwningUTF16Buffer);
+}
+
+nsHtml5OwningUTF16Buffer::nsHtml5OwningUTF16Buffer(void* aKey)
+ : nsHtml5UTF16Buffer(nullptr, 0),
+ next(nullptr),
+ key(aKey)
+{
+ MOZ_COUNT_CTOR(nsHtml5OwningUTF16Buffer);
+}
+
+nsHtml5OwningUTF16Buffer::~nsHtml5OwningUTF16Buffer()
+{
+ MOZ_COUNT_DTOR(nsHtml5OwningUTF16Buffer);
+ DeleteBuffer();
+
+ // This is to avoid dtor recursion on 'next', bug 706932.
+ RefPtr<nsHtml5OwningUTF16Buffer> tail;
+ tail.swap(next);
+ while (tail && tail->mRefCnt == 1) {
+ RefPtr<nsHtml5OwningUTF16Buffer> tmp;
+ tmp.swap(tail->next);
+ tail.swap(tmp);
+ }
+}
+
+// static
+already_AddRefed<nsHtml5OwningUTF16Buffer>
+nsHtml5OwningUTF16Buffer::FalliblyCreate(int32_t aLength)
+{
+ char16_t* newBuf = new (mozilla::fallible) char16_t[aLength];
+ if (!newBuf) {
+ return nullptr;
+ }
+ RefPtr<nsHtml5OwningUTF16Buffer> newObj =
+ new (mozilla::fallible) nsHtml5OwningUTF16Buffer(newBuf);
+ if (!newObj) {
+ delete[] newBuf;
+ return nullptr;
+ }
+ return newObj.forget();
+}
+
+void
+nsHtml5OwningUTF16Buffer::Swap(nsHtml5OwningUTF16Buffer* aOther)
+{
+ nsHtml5UTF16Buffer::Swap(aOther);
+}
+
+
+// Not using macros for AddRef and Release in order to be able to refcount on
+// and create on different threads.
+
+nsrefcnt
+nsHtml5OwningUTF16Buffer::AddRef()
+{
+ NS_PRECONDITION(int32_t(mRefCnt) >= 0, "Illegal refcount.");
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "nsHtml5OwningUTF16Buffer", sizeof(*this));
+ return mRefCnt;
+}
+
+nsrefcnt
+nsHtml5OwningUTF16Buffer::Release()
+{
+ NS_PRECONDITION(0 != mRefCnt, "Release without AddRef.");
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "nsHtml5OwningUTF16Buffer");
+ if (mRefCnt == 0) {
+ mRefCnt = 1; /* stabilize */
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+}
diff --git a/components/htmlfive/nsHtml5OwningUTF16Buffer.h b/components/htmlfive/nsHtml5OwningUTF16Buffer.h
new file mode 100644
index 000000000..0d8777464
--- /dev/null
+++ b/components/htmlfive/nsHtml5OwningUTF16Buffer.h
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHtml5OwningUTF16Buffer_h
+#define nsHtml5OwningUTF16Buffer_h
+
+#include "nsHtml5UTF16Buffer.h"
+
+class nsHtml5OwningUTF16Buffer : public nsHtml5UTF16Buffer
+{
+ private:
+
+ /**
+ * Passes a buffer and its length to the superclass constructor.
+ */
+ explicit nsHtml5OwningUTF16Buffer(char16_t* aBuffer);
+
+ public:
+
+ /**
+ * Constructor for a parser key placeholder. (No actual buffer.)
+ * @param aKey a parser key
+ */
+ explicit nsHtml5OwningUTF16Buffer(void* aKey);
+
+protected:
+ /**
+ * Takes care of releasing the owned buffer.
+ */
+ ~nsHtml5OwningUTF16Buffer();
+
+public:
+ /**
+ * The next buffer in a queue.
+ */
+ RefPtr<nsHtml5OwningUTF16Buffer> next;
+
+ /**
+ * A parser key.
+ */
+ void* key;
+
+ static already_AddRefed<nsHtml5OwningUTF16Buffer>
+ FalliblyCreate(int32_t aLength);
+
+ /**
+ * Swap start, end and buffer fields with another object.
+ */
+ void Swap(nsHtml5OwningUTF16Buffer* aOther);
+
+ nsrefcnt AddRef();
+ nsrefcnt Release();
+ private:
+ mozilla::ThreadSafeAutoRefCnt mRefCnt;
+};
+
+#endif // nsHtml5OwningUTF16Buffer_h
diff --git a/components/htmlfive/nsHtml5Parser.cpp b/components/htmlfive/nsHtml5Parser.cpp
new file mode 100644
index 000000000..b95b0d647
--- /dev/null
+++ b/components/htmlfive/nsHtml5Parser.cpp
@@ -0,0 +1,753 @@
+/* -*- 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 "nsHtml5Parser.h"
+
+#include "mozilla/AutoRestore.h"
+#include "nsContentUtils.h" // for kLoadAsData
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5DependentUTF16Buffer.h"
+#include "nsNetUtil.h"
+
+NS_INTERFACE_TABLE_HEAD(nsHtml5Parser)
+ NS_INTERFACE_TABLE(nsHtml5Parser, nsIParser, nsISupportsWeakReference)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsHtml5Parser)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHtml5Parser)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHtml5Parser)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5Parser)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5Parser)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExecutor)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(GetStreamParser())
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5Parser)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mExecutor)
+ tmp->DropStreamParser();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+nsHtml5Parser::nsHtml5Parser()
+ : mFirstBuffer(new nsHtml5OwningUTF16Buffer((void*)nullptr))
+ , mLastBuffer(mFirstBuffer)
+ , mExecutor(new nsHtml5TreeOpExecutor())
+ , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor, nullptr))
+ , mTokenizer(new nsHtml5Tokenizer(mTreeBuilder, false))
+ , mRootContextLineNumber(1)
+{
+ mTokenizer->setInterner(&mAtomTable);
+ // There's a zeroing operator new for everything else
+}
+
+nsHtml5Parser::~nsHtml5Parser()
+{
+ mTokenizer->end();
+ if (mDocWriteSpeculativeTokenizer) {
+ mDocWriteSpeculativeTokenizer->end();
+ }
+}
+
+NS_IMETHODIMP_(void)
+nsHtml5Parser::SetContentSink(nsIContentSink* aSink)
+{
+ NS_ASSERTION(aSink == static_cast<nsIContentSink*> (mExecutor),
+ "Attempt to set a foreign sink.");
+}
+
+NS_IMETHODIMP_(nsIContentSink*)
+nsHtml5Parser::GetContentSink()
+{
+ return static_cast<nsIContentSink*> (mExecutor);
+}
+
+NS_IMETHODIMP_(void)
+nsHtml5Parser::GetCommand(nsCString& aCommand)
+{
+ aCommand.AssignLiteral("view");
+}
+
+NS_IMETHODIMP_(void)
+nsHtml5Parser::SetCommand(const char* aCommand)
+{
+ NS_ASSERTION(!strcmp(aCommand, "view") ||
+ !strcmp(aCommand, "view-source") ||
+ !strcmp(aCommand, "external-resource") ||
+ !strcmp(aCommand, "import") ||
+ !strcmp(aCommand, kLoadAsData),
+ "Unsupported parser command");
+}
+
+NS_IMETHODIMP_(void)
+nsHtml5Parser::SetCommand(eParserCommands aParserCommand)
+{
+ NS_ASSERTION(aParserCommand == eViewNormal,
+ "Parser command was not eViewNormal.");
+}
+
+NS_IMETHODIMP_(void)
+nsHtml5Parser::SetDocumentCharset(const nsACString& aCharset,
+ int32_t aCharsetSource)
+{
+ NS_PRECONDITION(!mExecutor->HasStarted(),
+ "Document charset set too late.");
+ NS_PRECONDITION(GetStreamParser(), "Setting charset on a script-only parser.");
+ nsAutoCString trimmed;
+ trimmed.Assign(aCharset);
+ trimmed.Trim(" \t\r\n\f");
+ GetStreamParser()->SetDocumentCharset(trimmed, aCharsetSource);
+ mExecutor->SetDocumentCharsetAndSource(trimmed,
+ aCharsetSource);
+}
+
+NS_IMETHODIMP
+nsHtml5Parser::GetChannel(nsIChannel** aChannel)
+{
+ if (GetStreamParser()) {
+ return GetStreamParser()->GetChannel(aChannel);
+ } else {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+}
+
+NS_IMETHODIMP
+nsHtml5Parser::GetDTD(nsIDTD** aDTD)
+{
+ *aDTD = nullptr;
+ return NS_OK;
+}
+
+nsIStreamListener*
+nsHtml5Parser::GetStreamListener()
+{
+ return mStreamListener;
+}
+
+NS_IMETHODIMP
+nsHtml5Parser::ContinueInterruptedParsing()
+{
+ NS_NOTREACHED("Don't call. For interface compat only.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(void)
+nsHtml5Parser::BlockParser()
+{
+ mBlocked = true;
+}
+
+NS_IMETHODIMP_(void)
+nsHtml5Parser::UnblockParser()
+{
+ mBlocked = false;
+ mExecutor->ContinueInterruptedParsingAsync();
+}
+
+NS_IMETHODIMP_(void)
+nsHtml5Parser::ContinueInterruptedParsingAsync()
+{
+ mExecutor->ContinueInterruptedParsingAsync();
+}
+
+NS_IMETHODIMP_(bool)
+nsHtml5Parser::IsParserEnabled()
+{
+ return !mBlocked;
+}
+
+NS_IMETHODIMP_(bool)
+nsHtml5Parser::IsComplete()
+{
+ return mExecutor->IsComplete();
+}
+
+NS_IMETHODIMP
+nsHtml5Parser::Parse(nsIURI* aURL,
+ nsIRequestObserver* aObserver,
+ void* aKey, // legacy; ignored
+ nsDTDMode aMode) // legacy; ignored
+{
+ /*
+ * Do NOT cause WillBuildModel to be called synchronously from here!
+ * The document won't be ready for it until OnStartRequest!
+ */
+ NS_PRECONDITION(!mExecutor->HasStarted(),
+ "Tried to start parse without initializing the parser.");
+ NS_PRECONDITION(GetStreamParser(),
+ "Can't call this Parse() variant on script-created parser");
+ GetStreamParser()->SetObserver(aObserver);
+ GetStreamParser()->SetViewSourceTitle(aURL); // In case we're viewing source
+ mExecutor->SetStreamParser(GetStreamParser());
+ mExecutor->SetParser(this);
+ return NS_OK;
+}
+
+nsresult
+nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
+ void* aKey,
+ const nsACString& aContentType,
+ bool aLastCall,
+ nsDTDMode aMode) // ignored
+{
+ nsresult rv;
+ if (NS_FAILED(rv = mExecutor->IsBroken())) {
+ return rv;
+ }
+ if (aSourceBuffer.Length() > INT32_MAX) {
+ return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // Maintain a reference to ourselves so we don't go away
+ // till we're completely done. The old parser grips itself in this method.
+ nsCOMPtr<nsIParser> kungFuDeathGrip(this);
+
+ // Gripping the other objects just in case, since the other old grip
+ // required grips to these, too.
+ RefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(GetStreamParser());
+ mozilla::Unused << streamKungFuDeathGrip; // Not used within function
+ RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor);
+
+ if (!executor->HasStarted()) {
+ NS_ASSERTION(!GetStreamParser(),
+ "Had stream parser but document.write started life cycle.");
+ // This is the first document.write() on a document.open()ed document
+ executor->SetParser(this);
+ mTreeBuilder->setScriptingEnabled(executor->IsScriptEnabled());
+
+ bool isSrcdoc = false;
+ nsCOMPtr<nsIChannel> channel;
+ rv = GetChannel(getter_AddRefs(channel));
+ if (NS_SUCCEEDED(rv)) {
+ isSrcdoc = NS_IsSrcdocChannel(channel);
+ }
+ mTreeBuilder->setIsSrcdocDocument(isSrcdoc);
+
+ mTokenizer->start();
+ executor->Start();
+ if (!aContentType.EqualsLiteral("text/html")) {
+ mTreeBuilder->StartPlainText();
+ mTokenizer->StartPlainText();
+ }
+ /*
+ * If you move the following line, be very careful not to cause
+ * WillBuildModel to be called before the document has had its
+ * script global object set.
+ */
+ rv = executor->WillBuildModel(eDTDMode_unknown);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Return early if the parser has processed EOF
+ if (executor->IsComplete()) {
+ return NS_OK;
+ }
+
+ if (aLastCall && aSourceBuffer.IsEmpty() && !aKey) {
+ // document.close()
+ NS_ASSERTION(!GetStreamParser(),
+ "Had stream parser but got document.close().");
+ if (mDocumentClosed) {
+ // already closed
+ return NS_OK;
+ }
+ mDocumentClosed = true;
+ if (!mBlocked && !mInDocumentWrite) {
+ return ParseUntilBlocked();
+ }
+ return NS_OK;
+ }
+
+ // If we got this far, we are dealing with a document.write or
+ // document.writeln call--not document.close().
+
+ NS_ASSERTION(IsInsertionPointDefined(),
+ "Doc.write reached parser with undefined insertion point.");
+
+ NS_ASSERTION(!(GetStreamParser() && !aKey),
+ "Got a null key in a non-script-created parser");
+
+ // XXX is this optimization bogus?
+ if (aSourceBuffer.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // This guard is here to prevent document.close from tokenizing synchronously
+ // while a document.write (that wrote the script that called document.close!)
+ // is still on the call stack.
+ mozilla::AutoRestore<bool> guard(mInDocumentWrite);
+ mInDocumentWrite = true;
+
+ // The script is identified by aKey. If there's nothing in the buffer
+ // chain for that key, we'll insert at the head of the queue.
+ // When the script leaves something in the queue, a zero-length
+ // key-holder "buffer" is inserted in the queue. If the same script
+ // leaves something in the chain again, it will be inserted immediately
+ // before the old key holder belonging to the same script.
+ //
+ // We don't do the actual data insertion yet in the hope that the data gets
+ // tokenized and there no data or less data to copy to the heap after
+ // tokenization. Also, this way, we avoid inserting one empty data buffer
+ // per document.write, which matters for performance when the parser isn't
+ // blocked and a badly-authored script calls document.write() once per
+ // input character. (As seen in a benchmark!)
+ //
+ // The insertion into the input stream happens conceptually before anything
+ // gets tokenized. To make sure multi-level document.write works right,
+ // it's necessary to establish the location of our parser key up front
+ // in case this is the first write with this key.
+ //
+ // In a document.open() case, the first write level has a null key, so that
+ // case is handled separately, because normal buffers containing data
+ // have null keys.
+
+ // These don't need to be owning references, because they always point to
+ // the buffer queue and buffers can't be removed from the buffer queue
+ // before document.write() returns. The buffer queue clean-up happens the
+ // next time ParseUntilBlocked() is called.
+ // However, they are made owning just in case the reasoning above is flawed
+ // and a flaw would lead to worse problems with plain pointers. If this
+ // turns out to be a perf problem, it's worthwhile to consider making
+ // prevSearchbuf a plain pointer again.
+ RefPtr<nsHtml5OwningUTF16Buffer> prevSearchBuf;
+ RefPtr<nsHtml5OwningUTF16Buffer> firstLevelMarker;
+
+ if (aKey) {
+ if (mFirstBuffer == mLastBuffer) {
+ nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey);
+ keyHolder->next = mLastBuffer;
+ mFirstBuffer = keyHolder;
+ } else if (mFirstBuffer->key != aKey) {
+ prevSearchBuf = mFirstBuffer;
+ for (;;) {
+ if (prevSearchBuf->next == mLastBuffer) {
+ // key was not found
+ nsHtml5OwningUTF16Buffer* keyHolder =
+ new nsHtml5OwningUTF16Buffer(aKey);
+ keyHolder->next = mFirstBuffer;
+ mFirstBuffer = keyHolder;
+ prevSearchBuf = nullptr;
+ break;
+ }
+ if (prevSearchBuf->next->key == aKey) {
+ // found a key holder
+ break;
+ }
+ prevSearchBuf = prevSearchBuf->next;
+ }
+ } // else mFirstBuffer is the keyholder
+
+ // prevSearchBuf is the previous buffer before the keyholder or null if
+ // there isn't one.
+ } else {
+ // We have a first-level write in the document.open() case. We insert before
+ // mLastBuffer, effectively, by making mLastBuffer be a new sentinel object
+ // and redesignating the previous mLastBuffer as our firstLevelMarker. We
+ // need to put a marker there, because otherwise additional document.writes
+ // from nested event loops would insert in the wrong place. Sigh.
+ mLastBuffer->next = new nsHtml5OwningUTF16Buffer((void*)nullptr);
+ firstLevelMarker = mLastBuffer;
+ mLastBuffer = mLastBuffer->next;
+ }
+
+ nsHtml5DependentUTF16Buffer stackBuffer(aSourceBuffer);
+
+ while (!mBlocked && stackBuffer.hasMore()) {
+ stackBuffer.adjust(mLastWasCR);
+ mLastWasCR = false;
+ if (stackBuffer.hasMore()) {
+ int32_t lineNumberSave;
+ bool inRootContext = (!GetStreamParser() && !aKey);
+ if (inRootContext) {
+ mTokenizer->setLineNumber(mRootContextLineNumber);
+ } else {
+ // we aren't the root context, so save the line number on the
+ // *stack* so that we can restore it.
+ lineNumberSave = mTokenizer->getLineNumber();
+ }
+
+ if (!mTokenizer->EnsureBufferSpace(stackBuffer.getLength())) {
+ return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+ }
+ mLastWasCR = mTokenizer->tokenizeBuffer(&stackBuffer);
+ if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
+ return executor->MarkAsBroken(rv);
+ }
+
+ if (inRootContext) {
+ mRootContextLineNumber = mTokenizer->getLineNumber();
+ } else {
+ mTokenizer->setLineNumber(lineNumberSave);
+ }
+
+ if (mTreeBuilder->HasScript()) {
+ mTreeBuilder->Flush(); // Move ops to the executor
+ rv = executor->FlushDocumentWrite(); // run the ops
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Flushing tree ops can cause all sorts of things.
+ // Return early if the parser got terminated.
+ if (executor->IsComplete()) {
+ return NS_OK;
+ }
+ }
+ // Ignore suspension requests
+ }
+ }
+
+ RefPtr<nsHtml5OwningUTF16Buffer> heapBuffer;
+ if (stackBuffer.hasMore()) {
+ // The buffer wasn't tokenized to completion. Create a copy of the tail
+ // on the heap.
+ heapBuffer = stackBuffer.FalliblyCopyAsOwningBuffer();
+ if (!heapBuffer) {
+ // Allocation failed. The parser is now broken.
+ return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+
+ if (heapBuffer) {
+ // We have something to insert before the keyholder holding in the non-null
+ // aKey case and we have something to swap into firstLevelMarker in the
+ // null aKey case.
+ if (aKey) {
+ NS_ASSERTION(mFirstBuffer != mLastBuffer,
+ "Where's the keyholder?");
+ // the key holder is still somewhere further down the list from
+ // prevSearchBuf (which may be null)
+ if (mFirstBuffer->key == aKey) {
+ NS_ASSERTION(!prevSearchBuf,
+ "Non-null prevSearchBuf when mFirstBuffer is the key holder?");
+ heapBuffer->next = mFirstBuffer;
+ mFirstBuffer = heapBuffer;
+ } else {
+ if (!prevSearchBuf) {
+ prevSearchBuf = mFirstBuffer;
+ }
+ // We created a key holder earlier, so we will find it without walking
+ // past the end of the list.
+ while (prevSearchBuf->next->key != aKey) {
+ prevSearchBuf = prevSearchBuf->next;
+ }
+ heapBuffer->next = prevSearchBuf->next;
+ prevSearchBuf->next = heapBuffer;
+ }
+ } else {
+ NS_ASSERTION(firstLevelMarker, "How come we don't have a marker.");
+ firstLevelMarker->Swap(heapBuffer);
+ }
+ }
+
+ if (!mBlocked) { // buffer was tokenized to completion
+ NS_ASSERTION(!stackBuffer.hasMore(),
+ "Buffer wasn't tokenized to completion?");
+ // Scripting semantics require a forced tree builder flush here
+ mTreeBuilder->Flush(); // Move ops to the executor
+ rv = executor->FlushDocumentWrite(); // run the ops
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (stackBuffer.hasMore()) {
+ // The buffer wasn't tokenized to completion. Tokenize the untokenized
+ // content in order to preload stuff. This content will be retokenized
+ // later for normal parsing.
+ if (!mDocWriteSpeculatorActive) {
+ mDocWriteSpeculatorActive = true;
+ if (!mDocWriteSpeculativeTreeBuilder) {
+ // Lazily initialize if uninitialized
+ mDocWriteSpeculativeTreeBuilder =
+ new nsHtml5TreeBuilder(nullptr, executor->GetStage());
+ mDocWriteSpeculativeTreeBuilder->setScriptingEnabled(
+ mTreeBuilder->isScriptingEnabled());
+ mDocWriteSpeculativeTokenizer =
+ new nsHtml5Tokenizer(mDocWriteSpeculativeTreeBuilder, false);
+ mDocWriteSpeculativeTokenizer->setInterner(&mAtomTable);
+ mDocWriteSpeculativeTokenizer->start();
+ }
+ mDocWriteSpeculativeTokenizer->resetToDataState();
+ mDocWriteSpeculativeTreeBuilder->loadState(mTreeBuilder, &mAtomTable);
+ mDocWriteSpeculativeLastWasCR = false;
+ }
+
+ // Note that with multilevel document.write if we didn't just activate the
+ // speculator, it's possible that the speculator is now in the wrong state.
+ // That's OK for the sake of simplicity. The worst that can happen is
+ // that the speculative loads aren't exactly right. The content will be
+ // reparsed anyway for non-preload purposes.
+
+ // The buffer position for subsequent non-speculative parsing now lives
+ // in heapBuffer, so it's ok to let the buffer position of stackBuffer
+ // to be overwritten and not restored below.
+ while (stackBuffer.hasMore()) {
+ stackBuffer.adjust(mDocWriteSpeculativeLastWasCR);
+ if (stackBuffer.hasMore()) {
+ if (!mDocWriteSpeculativeTokenizer->EnsureBufferSpace(
+ stackBuffer.getLength())) {
+ return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+ }
+ mDocWriteSpeculativeLastWasCR =
+ mDocWriteSpeculativeTokenizer->tokenizeBuffer(&stackBuffer);
+ nsresult rv;
+ if (NS_FAILED((rv = mDocWriteSpeculativeTreeBuilder->IsBroken()))) {
+ return executor->MarkAsBroken(rv);
+ }
+ }
+ }
+
+ mDocWriteSpeculativeTreeBuilder->Flush();
+ mDocWriteSpeculativeTreeBuilder->DropHandles();
+ executor->FlushSpeculativeLoads();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHtml5Parser::Terminate()
+{
+ // We should only call DidBuildModel once, so don't do anything if this is
+ // the second time that Terminate has been called.
+ if (mExecutor->IsComplete()) {
+ return NS_OK;
+ }
+ // XXX - [ until we figure out a way to break parser-sink circularity ]
+ // Hack - Hold a reference until we are completely done...
+ nsCOMPtr<nsIParser> kungFuDeathGrip(this);
+ RefPtr<nsHtml5StreamParser> streamParser(GetStreamParser());
+ RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor);
+ if (streamParser) {
+ streamParser->Terminate();
+ }
+ return executor->DidBuildModel(true);
+}
+
+NS_IMETHODIMP
+nsHtml5Parser::ParseFragment(const nsAString& aSourceBuffer,
+ nsTArray<nsString>& aTagStack)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHtml5Parser::BuildModel()
+{
+ NS_NOTREACHED("Don't call this!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHtml5Parser::CancelParsingEvents()
+{
+ NS_NOTREACHED("Don't call this!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+nsHtml5Parser::Reset()
+{
+ NS_NOTREACHED("Don't call this!");
+}
+
+bool
+nsHtml5Parser::IsInsertionPointDefined()
+{
+ return !mExecutor->IsFlushing() &&
+ (!GetStreamParser() || mInsertionPointPushLevel);
+}
+
+void
+nsHtml5Parser::PushDefinedInsertionPoint()
+{
+ ++mInsertionPointPushLevel;
+}
+
+void
+nsHtml5Parser::PopDefinedInsertionPoint()
+{
+ --mInsertionPointPushLevel;
+}
+
+void
+nsHtml5Parser::MarkAsNotScriptCreated(const char* aCommand)
+{
+ NS_PRECONDITION(!mStreamListener, "Must not call this twice.");
+ eParserMode mode = NORMAL;
+ if (!nsCRT::strcmp(aCommand, "view-source")) {
+ mode = VIEW_SOURCE_HTML;
+ } else if (!nsCRT::strcmp(aCommand, "view-source-xml")) {
+ mode = VIEW_SOURCE_XML;
+ } else if (!nsCRT::strcmp(aCommand, "view-source-plain")) {
+ mode = VIEW_SOURCE_PLAIN;
+ } else if (!nsCRT::strcmp(aCommand, "plain-text")) {
+ mode = PLAIN_TEXT;
+ } else if (!nsCRT::strcmp(aCommand, kLoadAsData)) {
+ mode = LOAD_AS_DATA;
+ }
+#ifdef DEBUG
+ else {
+ NS_ASSERTION(!nsCRT::strcmp(aCommand, "view") ||
+ !nsCRT::strcmp(aCommand, "external-resource") ||
+ !nsCRT::strcmp(aCommand, "import"),
+ "Unsupported parser command!");
+ }
+#endif
+ mStreamListener =
+ new nsHtml5StreamListener(new nsHtml5StreamParser(mExecutor, this, mode));
+}
+
+bool
+nsHtml5Parser::IsScriptCreated()
+{
+ return !GetStreamParser();
+}
+
+/* End nsIParser */
+
+// not from interface
+nsresult
+nsHtml5Parser::ParseUntilBlocked()
+{
+ nsresult rv = mExecutor->IsBroken();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mBlocked || mExecutor->IsComplete()) {
+ return NS_OK;
+ }
+ NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle.");
+ NS_ASSERTION(!mInDocumentWrite,
+ "ParseUntilBlocked entered while in doc.write!");
+
+ mDocWriteSpeculatorActive = false;
+
+ for (;;) {
+ if (!mFirstBuffer->hasMore()) {
+ if (mFirstBuffer == mLastBuffer) {
+ if (mExecutor->IsComplete()) {
+ // something like cache manisfests stopped the parse in mid-flight
+ return NS_OK;
+ }
+ if (mDocumentClosed) {
+ nsresult rv;
+ NS_ASSERTION(!GetStreamParser(),
+ "This should only happen with script-created parser.");
+ if (NS_SUCCEEDED((rv = mExecutor->IsBroken()))) {
+ mTokenizer->eof();
+ if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
+ mExecutor->MarkAsBroken(rv);
+ } else {
+ mTreeBuilder->StreamEnded();
+ }
+ }
+ mTreeBuilder->Flush();
+ mExecutor->FlushDocumentWrite();
+ // The below call does memory cleanup, so call it even if the
+ // parser has been marked as broken.
+ mTokenizer->end();
+ return rv;
+ }
+ // never release the last buffer.
+ NS_ASSERTION(!mLastBuffer->getStart() && !mLastBuffer->getEnd(),
+ "Sentinel buffer had its indeces changed.");
+ if (GetStreamParser()) {
+ if (mReturnToStreamParserPermitted &&
+ !mExecutor->IsScriptExecuting()) {
+ mTreeBuilder->Flush();
+ mReturnToStreamParserPermitted = false;
+ GetStreamParser()->ContinueAfterScripts(mTokenizer,
+ mTreeBuilder,
+ mLastWasCR);
+ }
+ } else {
+ // Script-created parser
+ mTreeBuilder->Flush();
+ // No need to flush the executor, because the executor is already
+ // in a flush
+ NS_ASSERTION(mExecutor->IsInFlushLoop(),
+ "How did we come here without being in the flush loop?");
+ }
+ return NS_OK; // no more data for now but expecting more
+ }
+ mFirstBuffer = mFirstBuffer->next;
+ continue;
+ }
+
+ if (mBlocked || mExecutor->IsComplete()) {
+ return NS_OK;
+ }
+
+ // now we have a non-empty buffer
+ mFirstBuffer->adjust(mLastWasCR);
+ mLastWasCR = false;
+ if (mFirstBuffer->hasMore()) {
+ bool inRootContext = (!GetStreamParser() && !mFirstBuffer->key);
+ if (inRootContext) {
+ mTokenizer->setLineNumber(mRootContextLineNumber);
+ }
+ if (!mTokenizer->EnsureBufferSpace(mFirstBuffer->getLength())) {
+ return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+ }
+ mLastWasCR = mTokenizer->tokenizeBuffer(mFirstBuffer);
+ nsresult rv;
+ if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
+ return mExecutor->MarkAsBroken(rv);
+ }
+ if (inRootContext) {
+ mRootContextLineNumber = mTokenizer->getLineNumber();
+ }
+ if (mTreeBuilder->HasScript()) {
+ mTreeBuilder->Flush();
+ rv = mExecutor->FlushDocumentWrite();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (mBlocked) {
+ return NS_OK;
+ }
+ }
+ continue;
+ }
+}
+
+nsresult
+nsHtml5Parser::Initialize(nsIDocument* aDoc,
+ nsIURI* aURI,
+ nsISupports* aContainer,
+ nsIChannel* aChannel)
+{
+ return mExecutor->Init(aDoc, aURI, aContainer, aChannel);
+}
+
+void
+nsHtml5Parser::StartTokenizer(bool aScriptingEnabled) {
+
+ bool isSrcdoc = false;
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = GetChannel(getter_AddRefs(channel));
+ if (NS_SUCCEEDED(rv)) {
+ isSrcdoc = NS_IsSrcdocChannel(channel);
+ }
+ mTreeBuilder->setIsSrcdocDocument(isSrcdoc);
+
+ mTreeBuilder->SetPreventScriptExecution(!aScriptingEnabled);
+ mTreeBuilder->setScriptingEnabled(aScriptingEnabled);
+ mTokenizer->start();
+}
+
+void
+nsHtml5Parser::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState,
+ int32_t aLine)
+{
+ mTokenizer->resetToDataState();
+ mTokenizer->setLineNumber(aLine);
+ mTreeBuilder->loadState(aState, &mAtomTable);
+ mLastWasCR = false;
+ mReturnToStreamParserPermitted = true;
+}
+
+void
+nsHtml5Parser::ContinueAfterFailedCharsetSwitch()
+{
+ NS_PRECONDITION(GetStreamParser(),
+ "Tried to continue after failed charset switch without a stream parser");
+ GetStreamParser()->ContinueAfterFailedCharsetSwitch();
+}
+
diff --git a/components/htmlfive/nsHtml5Parser.h b/components/htmlfive/nsHtml5Parser.h
new file mode 100644
index 000000000..9d88adb29
--- /dev/null
+++ b/components/htmlfive/nsHtml5Parser.h
@@ -0,0 +1,362 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef NS_HTML5_PARSER
+#define NS_HTML5_PARSER
+
+#include "nsAutoPtr.h"
+#include "nsIParser.h"
+#include "nsDeque.h"
+#include "nsIURL.h"
+#include "nsParserCIID.h"
+#include "nsITokenizer.h"
+#include "nsIContentSink.h"
+#include "nsIRequest.h"
+#include "nsIChannel.h"
+#include "nsCOMArray.h"
+#include "nsContentSink.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIInputStream.h"
+#include "nsDetectionConfident.h"
+#include "nsHtml5OwningUTF16Buffer.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "nsHtml5StreamParser.h"
+#include "nsHtml5AtomTable.h"
+#include "nsWeakReference.h"
+#include "nsHtml5StreamListener.h"
+
+class nsHtml5Parser final : public nsIParser,
+ public nsSupportsWeakReference
+{
+ public:
+ NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHtml5Parser, nsIParser)
+
+ nsHtml5Parser();
+
+ /* Start nsIParser */
+ /**
+ * No-op for backwards compat.
+ */
+ NS_IMETHOD_(void) SetContentSink(nsIContentSink* aSink) override;
+
+ /**
+ * Returns the tree op executor for backwards compat.
+ */
+ NS_IMETHOD_(nsIContentSink*) GetContentSink() override;
+
+ /**
+ * Always returns "view" for backwards compat.
+ */
+ NS_IMETHOD_(void) GetCommand(nsCString& aCommand) override;
+
+ /**
+ * No-op for backwards compat.
+ */
+ NS_IMETHOD_(void) SetCommand(const char* aCommand) override;
+
+ /**
+ * No-op for backwards compat.
+ */
+ NS_IMETHOD_(void) SetCommand(eParserCommands aParserCommand) override;
+
+ /**
+ * Call this method once you've created a parser, and want to instruct it
+ * about what charset to load
+ *
+ * @param aCharset the charset of a document
+ * @param aCharsetSource the source of the charset
+ */
+ NS_IMETHOD_(void) SetDocumentCharset(const nsACString& aCharset, int32_t aSource) override;
+
+ /**
+ * Don't call. For interface compat only.
+ */
+ NS_IMETHOD_(void) GetDocumentCharset(nsACString& aCharset, int32_t& aSource) override
+ {
+ NS_NOTREACHED("No one should call this.");
+ }
+
+ /**
+ * Get the channel associated with this parser
+ * @param aChannel out param that will contain the result
+ * @return NS_OK if successful or NS_NOT_AVAILABLE if not
+ */
+ NS_IMETHOD GetChannel(nsIChannel** aChannel) override;
+
+ /**
+ * Return |this| for backwards compat.
+ */
+ NS_IMETHOD GetDTD(nsIDTD** aDTD) override;
+
+ /**
+ * Get the stream parser for this parser
+ */
+ virtual nsIStreamListener* GetStreamListener() override;
+
+ /**
+ * Don't call. For interface compat only.
+ */
+ NS_IMETHOD ContinueInterruptedParsing() override;
+
+ /**
+ * Blocks the parser.
+ */
+ NS_IMETHOD_(void) BlockParser() override;
+
+ /**
+ * Unblocks the parser.
+ */
+ NS_IMETHOD_(void) UnblockParser() override;
+
+ /**
+ * Asynchronously continues parsing.
+ */
+ NS_IMETHOD_(void) ContinueInterruptedParsingAsync() override;
+
+ /**
+ * Query whether the parser is enabled (i.e. not blocked) or not.
+ */
+ NS_IMETHOD_(bool) IsParserEnabled() override;
+
+ /**
+ * Query whether the parser thinks it's done with parsing.
+ */
+ NS_IMETHOD_(bool) IsComplete() override;
+
+ /**
+ * Set up request observer.
+ *
+ * @param aURL used for View Source title
+ * @param aListener a listener to forward notifications to
+ * @param aKey the root context key (used for document.write)
+ * @param aMode ignored (for interface compat only)
+ */
+ NS_IMETHOD Parse(nsIURI* aURL,
+ nsIRequestObserver* aListener = nullptr,
+ void* aKey = 0,
+ nsDTDMode aMode = eDTDMode_autodetect) override;
+
+ /**
+ * document.write and document.close
+ *
+ * @param aSourceBuffer the argument of document.write (empty for .close())
+ * @param aKey a key unique to the script element that caused this call
+ * @param aContentType "text/html" for HTML mode, else text/plain mode
+ * @param aLastCall true if .close() false if .write()
+ * @param aMode ignored (for interface compat only)
+ */
+ nsresult Parse(const nsAString& aSourceBuffer,
+ void* aKey,
+ const nsACString& aContentType,
+ bool aLastCall,
+ nsDTDMode aMode = eDTDMode_autodetect);
+
+ /**
+ * Stops the parser prematurely
+ */
+ NS_IMETHOD Terminate() override;
+
+ /**
+ * Don't call. For interface backwards compat only.
+ */
+ NS_IMETHOD ParseFragment(const nsAString& aSourceBuffer,
+ nsTArray<nsString>& aTagStack) override;
+
+ /**
+ * Don't call. For interface compat only.
+ */
+ NS_IMETHOD BuildModel() override;
+
+ /**
+ * Don't call. For interface compat only.
+ */
+ NS_IMETHOD CancelParsingEvents() override;
+
+ /**
+ * Don't call. For interface compat only.
+ */
+ virtual void Reset() override;
+
+ /**
+ * True if the insertion point (per HTML5) is defined.
+ */
+ virtual bool IsInsertionPointDefined() override;
+
+ /**
+ * Call immediately before starting to evaluate a parser-inserted script or
+ * in general when the spec says to define an insertion point.
+ */
+ virtual void PushDefinedInsertionPoint() override;
+
+ /**
+ * Call immediately after having evaluated a parser-inserted script or
+ * generally want to restore to the state before the last
+ * PushDefinedInsertionPoint call.
+ */
+ virtual void PopDefinedInsertionPoint() override;
+
+ /**
+ * Marks the HTML5 parser as not a script-created parser: Prepares the
+ * parser to be able to read a stream.
+ *
+ * @param aCommand the parser command (Yeah, this is bad API design. Let's
+ * make this better when retiring nsIParser)
+ */
+ virtual void MarkAsNotScriptCreated(const char* aCommand) override;
+
+ /**
+ * True if this is a script-created HTML5 parser.
+ */
+ virtual bool IsScriptCreated() override;
+
+ /* End nsIParser */
+
+ // Not from an external interface
+ // Non-inherited methods
+
+ public:
+
+ /**
+ * Initializes the parser to load from a channel.
+ */
+ virtual nsresult Initialize(nsIDocument* aDoc,
+ nsIURI* aURI,
+ nsISupports* aContainer,
+ nsIChannel* aChannel);
+
+ inline nsHtml5Tokenizer* GetTokenizer() {
+ return mTokenizer;
+ }
+
+ void InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine);
+
+ void DropStreamParser()
+ {
+ if (GetStreamParser()) {
+ GetStreamParser()->DropTimer();
+ mStreamListener->DropDelegate();
+ mStreamListener = nullptr;
+ }
+ }
+
+ void StartTokenizer(bool aScriptingEnabled);
+
+ void ContinueAfterFailedCharsetSwitch();
+
+ nsHtml5StreamParser* GetStreamParser()
+ {
+ if (!mStreamListener) {
+ return nullptr;
+ }
+ return mStreamListener->GetDelegate();
+ }
+
+ /**
+ * Parse until pending data is exhausted or a script blocks the parser
+ */
+ nsresult ParseUntilBlocked();
+
+ private:
+
+ virtual ~nsHtml5Parser();
+
+ // State variables
+
+ /**
+ * Whether the last character tokenized was a carriage return (for CRLF)
+ */
+ bool mLastWasCR;
+
+ /**
+ * Whether the last character tokenized was a carriage return (for CRLF)
+ * when preparsing document.write.
+ */
+ bool mDocWriteSpeculativeLastWasCR;
+
+ /**
+ * The parser is blocking on a script
+ */
+ bool mBlocked;
+
+ /**
+ * Whether the document.write() speculator is already active.
+ */
+ bool mDocWriteSpeculatorActive;
+
+ /**
+ * The number of PushDefinedInsertionPoint calls we've seen without a
+ * matching PopDefinedInsertionPoint.
+ */
+ int32_t mInsertionPointPushLevel;
+
+ /**
+ * True if document.close() has been called.
+ */
+ bool mDocumentClosed;
+
+ bool mInDocumentWrite;
+
+ // Portable parser objects
+ /**
+ * The first buffer in the pending UTF-16 buffer queue
+ */
+ RefPtr<nsHtml5OwningUTF16Buffer> mFirstBuffer;
+
+ /**
+ * The last buffer in the pending UTF-16 buffer queue. Always points
+ * to a sentinel object with nullptr as its parser key.
+ */
+ nsHtml5OwningUTF16Buffer* mLastBuffer; // weak ref;
+
+ /**
+ * The tree operation executor
+ */
+ RefPtr<nsHtml5TreeOpExecutor> mExecutor;
+
+ /**
+ * The HTML5 tree builder
+ */
+ const nsAutoPtr<nsHtml5TreeBuilder> mTreeBuilder;
+
+ /**
+ * The HTML5 tokenizer
+ */
+ const nsAutoPtr<nsHtml5Tokenizer> mTokenizer;
+
+ /**
+ * Another HTML5 tree builder for preloading document.written content.
+ */
+ nsAutoPtr<nsHtml5TreeBuilder> mDocWriteSpeculativeTreeBuilder;
+
+ /**
+ * Another HTML5 tokenizer for preloading document.written content.
+ */
+ nsAutoPtr<nsHtml5Tokenizer> mDocWriteSpeculativeTokenizer;
+
+ /**
+ * The stream listener holding the stream parser.
+ */
+ RefPtr<nsHtml5StreamListener> mStreamListener;
+
+ /**
+ *
+ */
+ int32_t mRootContextLineNumber;
+
+ /**
+ * Whether it's OK to transfer parsing back to the stream parser
+ */
+ bool mReturnToStreamParserPermitted;
+
+ /**
+ * The scoped atom table
+ */
+ nsHtml5AtomTable mAtomTable;
+
+};
+#endif
diff --git a/components/htmlfive/nsHtml5PlainTextUtils.cpp b/components/htmlfive/nsHtml5PlainTextUtils.cpp
new file mode 100644
index 000000000..0d2933150
--- /dev/null
+++ b/components/htmlfive/nsHtml5PlainTextUtils.cpp
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsHtml5PlainTextUtils.h"
+#include "nsHtml5AttributeName.h"
+#include "nsHtml5Portability.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "mozilla/Preferences.h"
+#include "nsHtml5String.h"
+
+// static
+nsHtml5HtmlAttributes*
+nsHtml5PlainTextUtils::NewLinkAttributes()
+{
+ nsHtml5HtmlAttributes* linkAttrs = new nsHtml5HtmlAttributes(0);
+ nsHtml5String rel =
+ nsHtml5Portability::newStringFromLiteral("alternate stylesheet");
+ linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_REL, rel, -1);
+ nsHtml5String type = nsHtml5Portability::newStringFromLiteral("text/css");
+ linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_TYPE, type, -1);
+ nsHtml5String href = nsHtml5Portability::newStringFromLiteral(
+ "resource://gre-resources/plaintext.css");
+ linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_HREF, href, -1);
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv) && bundleService, "The bundle service could not be loaded");
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle("chrome://global/locale/browser.properties",
+ getter_AddRefs(bundle));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && bundle, "chrome://global/locale/browser.properties could not be loaded");
+ nsXPIDLString title;
+ if (bundle) {
+ bundle->GetStringFromName(u"plainText.wordWrap", getter_Copies(title));
+ }
+
+ linkAttrs->addAttribute(
+ nsHtml5AttributeName::ATTR_TITLE, nsHtml5String::FromString(title), -1);
+ return linkAttrs;
+}
diff --git a/components/htmlfive/nsHtml5PlainTextUtils.h b/components/htmlfive/nsHtml5PlainTextUtils.h
new file mode 100644
index 000000000..997702cff
--- /dev/null
+++ b/components/htmlfive/nsHtml5PlainTextUtils.h
@@ -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/. */
+
+#ifndef nsHtml5PlainTextUtils_h
+#define nsHtml5PlainTextUtils_h
+
+#include "nsHtml5HtmlAttributes.h"
+
+class nsHtml5PlainTextUtils
+{
+ public:
+ static nsHtml5HtmlAttributes* NewLinkAttributes();
+};
+
+#endif // nsHtml5PlainTextUtils_h
diff --git a/components/htmlfive/nsHtml5Portability.cpp b/components/htmlfive/nsHtml5Portability.cpp
new file mode 100644
index 000000000..613f4e530
--- /dev/null
+++ b/components/htmlfive/nsHtml5Portability.cpp
@@ -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/. */
+
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "jArray.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5TreeBuilder.h"
+#include "mozilla/CheckedInt.h"
+
+int32_t nsHtml5Portability::checkedAdd(int32_t a, int32_t b) {
+ mozilla::CheckedInt<int32_t> sum(a);
+ sum += b;
+ MOZ_RELEASE_ASSERT(sum.isValid(),
+ "HTML input too large for signed 32-bit integer.");
+ return sum.value();
+}
+
+nsIAtom*
+nsHtml5Portability::newLocalNameFromBuffer(char16_t* buf, int32_t offset, int32_t length, nsHtml5AtomTable* interner)
+{
+ NS_ASSERTION(!offset, "The offset should always be zero here.");
+ NS_ASSERTION(interner, "Didn't get an atom service.");
+ return interner->GetAtom(nsDependentSubstring(buf, buf + length));
+}
+
+static bool
+ContainsWhiteSpace(mozilla::Span<char16_t> aSpan)
+{
+ for (char16_t c : aSpan) {
+ if (nsContentUtils::IsHTMLWhitespace(c)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsHtml5String
+nsHtml5Portability::newStringFromBuffer(char16_t* buf,
+ int32_t offset,
+ int32_t length,
+ nsHtml5TreeBuilder* treeBuilder,
+ bool maybeAtomize)
+{
+ if (!length) {
+ return nsHtml5String::EmptyString();
+ }
+ if (maybeAtomize && !ContainsWhiteSpace(mozilla::MakeSpan(buf + offset, length))) {
+ return nsHtml5String::FromAtom(NS_AtomizeMainThread(nsDependentSubstring(buf + offset, length)));
+ }
+ return nsHtml5String::FromBuffer(buf + offset, length, treeBuilder);
+}
+
+nsHtml5String
+nsHtml5Portability::newEmptyString()
+{
+ return nsHtml5String::EmptyString();
+}
+
+nsHtml5String
+nsHtml5Portability::newStringFromLiteral(const char* literal)
+{
+ return nsHtml5String::FromLiteral(literal);
+}
+
+nsHtml5String
+nsHtml5Portability::newStringFromString(nsHtml5String string)
+{
+ return string.Clone();
+}
+
+jArray<char16_t,int32_t>
+nsHtml5Portability::newCharArrayFromLocal(nsIAtom* local)
+{
+ nsAutoString temp;
+ local->ToString(temp);
+ int32_t len = temp.Length();
+ jArray<char16_t,int32_t> arr = jArray<char16_t,int32_t>::newJArray(len);
+ memcpy(arr, temp.BeginReading(), len * sizeof(char16_t));
+ return arr;
+}
+
+jArray<char16_t, int32_t>
+nsHtml5Portability::newCharArrayFromString(nsHtml5String string)
+{
+ MOZ_RELEASE_ASSERT(string);
+ uint32_t len = string.Length();
+ MOZ_RELEASE_ASSERT(len < INT32_MAX);
+ jArray<char16_t,int32_t> arr = jArray<char16_t,int32_t>::newJArray(len);
+ string.CopyToBuffer(arr);
+ return arr;
+}
+
+nsIAtom*
+nsHtml5Portability::newLocalFromLocal(nsIAtom* local, nsHtml5AtomTable* interner)
+{
+ NS_PRECONDITION(local, "Atom was null.");
+ NS_PRECONDITION(interner, "Atom table was null");
+ if (!local->IsStaticAtom()) {
+ nsAutoString str;
+ local->ToString(str);
+ local = interner->GetAtom(str);
+ }
+ return local;
+}
+
+bool
+nsHtml5Portability::localEqualsBuffer(nsIAtom* local, char16_t* buf, int32_t offset, int32_t length)
+{
+ return local->Equals(buf + offset, length);
+}
+
+bool
+nsHtml5Portability::lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString(
+ const char* lowerCaseLiteral,
+ nsHtml5String string)
+{
+ return string.LowerCaseStartsWithASCII(lowerCaseLiteral);
+}
+
+bool
+nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ const char* lowerCaseLiteral,
+ nsHtml5String string)
+{
+ return string.LowerCaseEqualsASCII(lowerCaseLiteral);
+}
+
+bool
+nsHtml5Portability::literalEqualsString(const char* literal,
+ nsHtml5String string)
+{
+ return string.EqualsASCII(literal);
+}
+
+bool
+nsHtml5Portability::stringEqualsString(nsHtml5String one, nsHtml5String other)
+{
+ return one.Equals(other);
+}
+
+void
+nsHtml5Portability::initializeStatics()
+{
+}
+
+void
+nsHtml5Portability::releaseStatics()
+{
+}
diff --git a/components/htmlfive/nsHtml5Portability.h b/components/htmlfive/nsHtml5Portability.h
new file mode 100644
index 000000000..3421b410c
--- /dev/null
+++ b/components/htmlfive/nsHtml5Portability.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2008-2015 Mozilla Foundation
+ * Copyright (c) 2018-2021 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit Portability.java instead and regenerate.
+ */
+
+#ifndef nsHtml5Portability_h
+#define nsHtml5Portability_h
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+class nsHtml5StreamParser;
+
+class nsHtml5AttributeName;
+class nsHtml5ElementName;
+class nsHtml5Tokenizer;
+class nsHtml5TreeBuilder;
+class nsHtml5MetaScanner;
+class nsHtml5UTF16Buffer;
+class nsHtml5StateSnapshot;
+
+
+class nsHtml5Portability
+{
+ public:
+ static int32_t checkedAdd(int32_t a, int32_t b);
+ static nsIAtom* newLocalNameFromBuffer(char16_t* buf, int32_t offset, int32_t length, nsHtml5AtomTable* interner);
+ static nsHtml5String newStringFromBuffer(char16_t* buf, int32_t offset, int32_t length, nsHtml5TreeBuilder* treeBuilder, bool maybeAtomize);
+ static nsHtml5String newEmptyString();
+ static nsHtml5String newStringFromLiteral(const char* literal);
+ static nsHtml5String newStringFromString(nsHtml5String string);
+ static jArray<char16_t,int32_t> newCharArrayFromLocal(nsIAtom* local);
+ static jArray<char16_t,int32_t> newCharArrayFromString(nsHtml5String string);
+ static nsIAtom* newLocalFromLocal(nsIAtom* local, nsHtml5AtomTable* interner);
+ static bool localEqualsBuffer(nsIAtom* local, char16_t* buf, int32_t offset, int32_t length);
+ static bool lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString(const char* lowerCaseLiteral, nsHtml5String string);
+ static bool lowerCaseLiteralEqualsIgnoreAsciiCaseString(const char* lowerCaseLiteral, nsHtml5String string);
+ static bool literalEqualsString(const char* literal, nsHtml5String string);
+ static bool stringEqualsString(nsHtml5String one, nsHtml5String other);
+ static void initializeStatics();
+ static void releaseStatics();
+};
+
+#endif
+
diff --git a/components/htmlfive/nsHtml5RefPtr.h b/components/htmlfive/nsHtml5RefPtr.h
new file mode 100644
index 000000000..bc0477ba6
--- /dev/null
+++ b/components/htmlfive/nsHtml5RefPtr.h
@@ -0,0 +1,449 @@
+/* -*- 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 nsHtml5RefPtr_h
+#define nsHtml5RefPtr_h
+
+#include "nsThreadUtils.h"
+
+template <class T>
+class nsHtml5RefPtrReleaser : public mozilla::Runnable
+ {
+ private:
+ T* mPtr;
+ public:
+ explicit nsHtml5RefPtrReleaser(T* aPtr)
+ : mPtr(aPtr)
+ {}
+ NS_IMETHOD Run() override
+ {
+ mPtr->Release();
+ return NS_OK;
+ }
+ };
+
+// template <class T> class nsHtml5RefPtrGetterAddRefs;
+
+/**
+ * Like nsRefPtr except release is proxied to the main thread. Mostly copied
+ * from nsRefPtr.
+ */
+template <class T>
+class nsHtml5RefPtr
+ {
+ private:
+
+ void
+ assign_with_AddRef( T* rawPtr )
+ {
+ if ( rawPtr )
+ rawPtr->AddRef();
+ assign_assuming_AddRef(rawPtr);
+ }
+
+ void**
+ begin_assignment()
+ {
+ assign_assuming_AddRef(0);
+ return reinterpret_cast<void**>(&mRawPtr);
+ }
+
+ void
+ assign_assuming_AddRef( T* newPtr )
+ {
+ T* oldPtr = mRawPtr;
+ mRawPtr = newPtr;
+ if ( oldPtr )
+ release(oldPtr);
+ }
+
+ void
+ release( T* aPtr )
+ {
+ nsCOMPtr<nsIRunnable> releaser = new nsHtml5RefPtrReleaser<T>(aPtr);
+ if (NS_FAILED(NS_DispatchToMainThread(releaser)))
+ {
+ NS_WARNING("Failed to dispatch releaser event.");
+ }
+ }
+
+ private:
+ T* mRawPtr;
+
+ public:
+ typedef T element_type;
+
+ ~nsHtml5RefPtr()
+ {
+ if ( mRawPtr )
+ release(mRawPtr);
+ }
+
+ // Constructors
+
+ nsHtml5RefPtr()
+ : mRawPtr(0)
+ // default constructor
+ {
+ }
+
+ nsHtml5RefPtr( const nsHtml5RefPtr<T>& aSmartPtr )
+ : mRawPtr(aSmartPtr.mRawPtr)
+ // copy-constructor
+ {
+ if ( mRawPtr )
+ mRawPtr->AddRef();
+ }
+
+ explicit nsHtml5RefPtr( T* aRawPtr )
+ : mRawPtr(aRawPtr)
+ // construct from a raw pointer (of the right type)
+ {
+ if ( mRawPtr )
+ mRawPtr->AddRef();
+ }
+
+ explicit nsHtml5RefPtr( const already_AddRefed<T>& aSmartPtr )
+ : mRawPtr(aSmartPtr.mRawPtr)
+ // construct from |dont_AddRef(expr)|
+ {
+ }
+
+ // Assignment operators
+
+ nsHtml5RefPtr<T>&
+ operator=( const nsHtml5RefPtr<T>& rhs )
+ // copy assignment operator
+ {
+ assign_with_AddRef(rhs.mRawPtr);
+ return *this;
+ }
+
+ nsHtml5RefPtr<T>&
+ operator=( T* rhs )
+ // assign from a raw pointer (of the right type)
+ {
+ assign_with_AddRef(rhs);
+ return *this;
+ }
+
+ nsHtml5RefPtr<T>&
+ operator=( const already_AddRefed<T>& rhs )
+ // assign from |dont_AddRef(expr)|
+ {
+ assign_assuming_AddRef(rhs.mRawPtr);
+ return *this;
+ }
+
+ // Other pointer operators
+
+ void
+ swap( nsHtml5RefPtr<T>& rhs )
+ // ...exchange ownership with |rhs|; can save a pair of refcount operations
+ {
+ T* temp = rhs.mRawPtr;
+ rhs.mRawPtr = mRawPtr;
+ mRawPtr = temp;
+ }
+
+ void
+ swap( T*& rhs )
+ // ...exchange ownership with |rhs|; can save a pair of refcount operations
+ {
+ T* temp = rhs;
+ rhs = mRawPtr;
+ mRawPtr = temp;
+ }
+
+ already_AddRefed<T>
+ forget()
+ // return the value of mRawPtr and null out mRawPtr. Useful for
+ // already_AddRefed return values.
+ {
+ T* temp = 0;
+ swap(temp);
+ return temp;
+ }
+
+ template <typename I>
+ void
+ forget( I** rhs)
+ // Set the target of rhs to the value of mRawPtr and null out mRawPtr.
+ // Useful to avoid unnecessary AddRef/Release pairs with "out"
+ // parameters where rhs bay be a T** or an I** where I is a base class
+ // of T.
+ {
+ NS_ASSERTION(rhs, "Null pointer passed to forget!");
+ *rhs = mRawPtr;
+ mRawPtr = 0;
+ }
+
+ T*
+ get() const
+ /*
+ Prefer the implicit conversion provided automatically by |operator T*() const|.
+ Use |get()| to resolve ambiguity or to get a castable pointer.
+ */
+ {
+ return const_cast<T*>(mRawPtr);
+ }
+
+ operator T*() const
+ /*
+ ...makes an |nsHtml5RefPtr| act like its underlying raw pointer type whenever it
+ is used in a context where a raw pointer is expected. It is this operator
+ that makes an |nsHtml5RefPtr| substitutable for a raw pointer.
+
+ Prefer the implicit use of this operator to calling |get()|, except where
+ necessary to resolve ambiguity.
+ */
+ {
+ return get();
+ }
+
+ T*
+ operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN
+ {
+ NS_PRECONDITION(mRawPtr != 0, "You can't dereference a NULL nsHtml5RefPtr with operator->().");
+ return get();
+ }
+
+ nsHtml5RefPtr<T>*
+ get_address()
+ // This is not intended to be used by clients. See |address_of|
+ // below.
+ {
+ return this;
+ }
+
+ const nsHtml5RefPtr<T>*
+ get_address() const
+ // This is not intended to be used by clients. See |address_of|
+ // below.
+ {
+ return this;
+ }
+
+ public:
+ T&
+ operator*() const
+ {
+ NS_PRECONDITION(mRawPtr != 0, "You can't dereference a NULL nsHtml5RefPtr with operator*().");
+ return *get();
+ }
+
+ T**
+ StartAssignment()
+ {
+#ifndef NSCAP_FEATURE_INLINE_STARTASSIGNMENT
+ return reinterpret_cast<T**>(begin_assignment());
+#else
+ assign_assuming_AddRef(0);
+ return reinterpret_cast<T**>(&mRawPtr);
+#endif
+ }
+ };
+
+template <class T>
+inline
+nsHtml5RefPtr<T>*
+address_of( nsHtml5RefPtr<T>& aPtr )
+ {
+ return aPtr.get_address();
+ }
+
+template <class T>
+inline
+const nsHtml5RefPtr<T>*
+address_of( const nsHtml5RefPtr<T>& aPtr )
+ {
+ return aPtr.get_address();
+ }
+
+template <class T>
+class nsHtml5RefPtrGetterAddRefs
+ /*
+ ...
+
+ This class is designed to be used for anonymous temporary objects in the
+ argument list of calls that return COM interface pointers, e.g.,
+
+ nsHtml5RefPtr<IFoo> fooP;
+ ...->GetAddRefedPointer(getter_AddRefs(fooP))
+
+ DO NOT USE THIS TYPE DIRECTLY IN YOUR CODE. Use |getter_AddRefs()| instead.
+
+ When initialized with a |nsHtml5RefPtr|, as in the example above, it returns
+ a |void**|, a |T**|, or an |nsISupports**| as needed, that the
+ outer call (|GetAddRefedPointer| in this case) can fill in.
+
+ This type should be a nested class inside |nsHtml5RefPtr<T>|.
+ */
+ {
+ public:
+ explicit
+ nsHtml5RefPtrGetterAddRefs( nsHtml5RefPtr<T>& aSmartPtr )
+ : mTargetSmartPtr(aSmartPtr)
+ {
+ // nothing else to do
+ }
+
+ operator void**()
+ {
+ return reinterpret_cast<void**>(mTargetSmartPtr.StartAssignment());
+ }
+
+ operator T**()
+ {
+ return mTargetSmartPtr.StartAssignment();
+ }
+
+ T*&
+ operator*()
+ {
+ return *(mTargetSmartPtr.StartAssignment());
+ }
+
+ private:
+ nsHtml5RefPtr<T>& mTargetSmartPtr;
+ };
+
+template <class T>
+inline
+nsHtml5RefPtrGetterAddRefs<T>
+getter_AddRefs( nsHtml5RefPtr<T>& aSmartPtr )
+ /*
+ Used around a |nsHtml5RefPtr| when
+ ...makes the class |nsHtml5RefPtrGetterAddRefs<T>| invisible.
+ */
+ {
+ return nsHtml5RefPtrGetterAddRefs<T>(aSmartPtr);
+ }
+
+
+
+ // Comparing two |nsHtml5RefPtr|s
+
+template <class T, class U>
+inline
+bool
+operator==( const nsHtml5RefPtr<T>& lhs, const nsHtml5RefPtr<U>& rhs )
+ {
+ return static_cast<const T*>(lhs.get()) == static_cast<const U*>(rhs.get());
+ }
+
+
+template <class T, class U>
+inline
+bool
+operator!=( const nsHtml5RefPtr<T>& lhs, const nsHtml5RefPtr<U>& rhs )
+ {
+ return static_cast<const T*>(lhs.get()) != static_cast<const U*>(rhs.get());
+ }
+
+
+ // Comparing an |nsHtml5RefPtr| to a raw pointer
+
+template <class T, class U>
+inline
+bool
+operator==( const nsHtml5RefPtr<T>& lhs, const U* rhs )
+ {
+ return static_cast<const T*>(lhs.get()) == static_cast<const U*>(rhs);
+ }
+
+template <class T, class U>
+inline
+bool
+operator==( const U* lhs, const nsHtml5RefPtr<T>& rhs )
+ {
+ return static_cast<const U*>(lhs) == static_cast<const T*>(rhs.get());
+ }
+
+template <class T, class U>
+inline
+bool
+operator!=( const nsHtml5RefPtr<T>& lhs, const U* rhs )
+ {
+ return static_cast<const T*>(lhs.get()) != static_cast<const U*>(rhs);
+ }
+
+template <class T, class U>
+inline
+bool
+operator!=( const U* lhs, const nsHtml5RefPtr<T>& rhs )
+ {
+ return static_cast<const U*>(lhs) != static_cast<const T*>(rhs.get());
+ }
+
+template <class T, class U>
+inline
+bool
+operator==( const nsHtml5RefPtr<T>& lhs, U* rhs )
+ {
+ return static_cast<const T*>(lhs.get()) == const_cast<const U*>(rhs);
+ }
+
+template <class T, class U>
+inline
+bool
+operator==( U* lhs, const nsHtml5RefPtr<T>& rhs )
+ {
+ return const_cast<const U*>(lhs) == static_cast<const T*>(rhs.get());
+ }
+
+template <class T, class U>
+inline
+bool
+operator!=( const nsHtml5RefPtr<T>& lhs, U* rhs )
+ {
+ return static_cast<const T*>(lhs.get()) != const_cast<const U*>(rhs);
+ }
+
+template <class T, class U>
+inline
+bool
+operator!=( U* lhs, const nsHtml5RefPtr<T>& rhs )
+ {
+ return const_cast<const U*>(lhs) != static_cast<const T*>(rhs.get());
+ }
+
+
+
+ // Comparing an |nsHtml5RefPtr| to |0|
+
+template <class T>
+inline
+bool
+operator==( const nsHtml5RefPtr<T>& lhs, decltype(nullptr) )
+ {
+ return lhs.get() == nullptr;
+ }
+
+template <class T>
+inline
+bool
+operator==( decltype(nullptr), const nsHtml5RefPtr<T>& rhs )
+ {
+ return nullptr == rhs.get();
+ }
+
+template <class T>
+inline
+bool
+operator!=( const nsHtml5RefPtr<T>& lhs, decltype(nullptr) )
+ {
+ return lhs.get() != nullptr;
+ }
+
+template <class T>
+inline
+bool
+operator!=( decltype(nullptr), const nsHtml5RefPtr<T>& rhs )
+ {
+ return nullptr != rhs.get();
+ }
+
+#endif // !defined(nsHtml5RefPtr_h)
diff --git a/components/htmlfive/nsHtml5SVGLoadDispatcher.cpp b/components/htmlfive/nsHtml5SVGLoadDispatcher.cpp
new file mode 100644
index 000000000..b5c22d883
--- /dev/null
+++ b/components/htmlfive/nsHtml5SVGLoadDispatcher.cpp
@@ -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/. */
+
+#include "nsHtml5SVGLoadDispatcher.h"
+#include "nsPresContext.h"
+#include "nsIPresShell.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "nsIDocument.h"
+
+using namespace mozilla;
+
+nsHtml5SVGLoadDispatcher::nsHtml5SVGLoadDispatcher(nsIContent* aElement)
+ : mElement(aElement)
+ , mDocument(mElement->OwnerDoc())
+{
+ mDocument->BlockOnload();
+}
+
+NS_IMETHODIMP
+nsHtml5SVGLoadDispatcher::Run()
+{
+ WidgetEvent event(true, eSVGLoad);
+ event.mFlags.mBubbles = false;
+ // Do we care about forcing presshell creation if it hasn't happened yet?
+ // That is, should this code flush or something? Does it really matter?
+ // For that matter, do we really want to try getting the prescontext?
+ // Does this event ever want one?
+ RefPtr<nsPresContext> ctx;
+ nsCOMPtr<nsIPresShell> shell = mElement->OwnerDoc()->GetShell();
+ if (shell) {
+ ctx = shell->GetPresContext();
+ }
+ EventDispatcher::Dispatch(mElement, ctx, &event);
+ // Unblocking onload on the same document that it was blocked even if
+ // the element has moved between docs since blocking.
+ mDocument->UnblockOnload(false);
+ return NS_OK;
+}
diff --git a/components/htmlfive/nsHtml5SVGLoadDispatcher.h b/components/htmlfive/nsHtml5SVGLoadDispatcher.h
new file mode 100644
index 000000000..3f1b1cd04
--- /dev/null
+++ b/components/htmlfive/nsHtml5SVGLoadDispatcher.h
@@ -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/. */
+
+#ifndef nsHtml5SVGLoadDispatcher_h
+#define nsHtml5SVGLoadDispatcher_h
+
+#include "nsThreadUtils.h"
+#include "nsIContent.h"
+
+class nsHtml5SVGLoadDispatcher : public mozilla::Runnable
+{
+ private:
+ nsCOMPtr<nsIContent> mElement;
+ nsCOMPtr<nsIDocument> mDocument;
+ public:
+ explicit nsHtml5SVGLoadDispatcher(nsIContent* aElement);
+ NS_IMETHOD Run();
+};
+
+#endif // nsHtml5SVGLoadDispatcher_h
diff --git a/components/htmlfive/nsHtml5Speculation.cpp b/components/htmlfive/nsHtml5Speculation.cpp
new file mode 100644
index 000000000..f9b5fa38f
--- /dev/null
+++ b/components/htmlfive/nsHtml5Speculation.cpp
@@ -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/. */
+
+#include "nsHtml5Speculation.h"
+
+using namespace mozilla;
+
+nsHtml5Speculation::nsHtml5Speculation(nsHtml5OwningUTF16Buffer* aBuffer,
+ int32_t aStart,
+ int32_t aStartLineNumber,
+ nsAHtml5TreeBuilderState* aSnapshot)
+ : mBuffer(aBuffer)
+ , mStart(aStart)
+ , mStartLineNumber(aStartLineNumber)
+ , mSnapshot(aSnapshot)
+{
+ MOZ_COUNT_CTOR(nsHtml5Speculation);
+}
+
+nsHtml5Speculation::~nsHtml5Speculation()
+{
+ MOZ_COUNT_DTOR(nsHtml5Speculation);
+}
+
+void
+nsHtml5Speculation::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
+{
+ mOpQueue.AppendElements(Move(aOpQueue));
+}
+
+void
+nsHtml5Speculation::FlushToSink(nsAHtml5TreeOpSink* aSink)
+{
+ aSink->MoveOpsFrom(mOpQueue);
+}
diff --git a/components/htmlfive/nsHtml5Speculation.h b/components/htmlfive/nsHtml5Speculation.h
new file mode 100644
index 000000000..3104bd7f4
--- /dev/null
+++ b/components/htmlfive/nsHtml5Speculation.h
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsHtml5Speculation_h
+#define nsHtml5Speculation_h
+
+#include "nsHtml5OwningUTF16Buffer.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5TreeOperation.h"
+#include "nsAHtml5TreeOpSink.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsHtml5Speculation final : public nsAHtml5TreeOpSink
+{
+ public:
+ nsHtml5Speculation(nsHtml5OwningUTF16Buffer* aBuffer,
+ int32_t aStart,
+ int32_t aStartLineNumber,
+ nsAHtml5TreeBuilderState* aSnapshot);
+
+ ~nsHtml5Speculation();
+
+ nsHtml5OwningUTF16Buffer* GetBuffer()
+ {
+ return mBuffer;
+ }
+
+ int32_t GetStart()
+ {
+ return mStart;
+ }
+
+ int32_t GetStartLineNumber()
+ {
+ return mStartLineNumber;
+ }
+
+ nsAHtml5TreeBuilderState* GetSnapshot()
+ {
+ return mSnapshot;
+ }
+
+ /**
+ * Flush the operations from the tree operations from the argument
+ * queue unconditionally.
+ */
+ virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue);
+
+ void FlushToSink(nsAHtml5TreeOpSink* aSink);
+
+ private:
+ /**
+ * The first buffer in the pending UTF-16 buffer queue
+ */
+ RefPtr<nsHtml5OwningUTF16Buffer> mBuffer;
+
+ /**
+ * The start index of this speculation in the first buffer
+ */
+ int32_t mStart;
+
+ /**
+ * The current line number at the start of the speculation
+ */
+ int32_t mStartLineNumber;
+
+ nsAutoPtr<nsAHtml5TreeBuilderState> mSnapshot;
+
+ nsTArray<nsHtml5TreeOperation> mOpQueue;
+};
+
+#endif // nsHtml5Speculation_h
diff --git a/components/htmlfive/nsHtml5SpeculativeLoad.cpp b/components/htmlfive/nsHtml5SpeculativeLoad.cpp
new file mode 100644
index 000000000..35c11b739
--- /dev/null
+++ b/components/htmlfive/nsHtml5SpeculativeLoad.cpp
@@ -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/. */
+
+#include "nsHtml5SpeculativeLoad.h"
+#include "nsHtml5TreeOpExecutor.h"
+
+nsHtml5SpeculativeLoad::nsHtml5SpeculativeLoad()
+ :
+#ifdef DEBUG
+ mOpCode(eSpeculativeLoadUninitialized),
+#endif
+ mIsAsync(false),
+ mIsDefer(false)
+{
+ MOZ_COUNT_CTOR(nsHtml5SpeculativeLoad);
+}
+
+nsHtml5SpeculativeLoad::~nsHtml5SpeculativeLoad()
+{
+ MOZ_COUNT_DTOR(nsHtml5SpeculativeLoad);
+ NS_ASSERTION(mOpCode != eSpeculativeLoadUninitialized,
+ "Uninitialized speculative load.");
+}
+
+void
+nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor)
+{
+ switch (mOpCode) {
+ case eSpeculativeLoadBase:
+ aExecutor->SetSpeculationBase(mUrl);
+ break;
+ case eSpeculativeLoadCSP:
+ aExecutor->AddSpeculationCSP(mMetaCSP);
+ break;
+ case eSpeculativeLoadMetaReferrer:
+ aExecutor->SetSpeculationReferrerPolicy(mReferrerPolicy);
+ break;
+ case eSpeculativeLoadImage:
+ aExecutor->PreloadImage(mUrl, mCrossOrigin, mSrcset, mSizes, mReferrerPolicy);
+ break;
+ case eSpeculativeLoadOpenPicture:
+ aExecutor->PreloadOpenPicture();
+ break;
+ case eSpeculativeLoadEndPicture:
+ aExecutor->PreloadEndPicture();
+ break;
+ case eSpeculativeLoadPictureSource:
+ aExecutor->PreloadPictureSource(mSrcset, mSizes, mTypeOrCharsetSourceOrDocumentMode,
+ mMedia);
+ break;
+ case eSpeculativeLoadScript:
+ aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSourceOrDocumentMode,
+ mCrossOrigin, mIntegrity, false,
+ mIsAsync, mIsDefer, false);
+ break;
+ case eSpeculativeLoadScriptFromHead:
+ aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSourceOrDocumentMode,
+ mCrossOrigin, mIntegrity, true,
+ mIsAsync, mIsDefer, false);
+ break;
+ case eSpeculativeLoadNoModuleScript:
+ aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSourceOrDocumentMode,
+ mCrossOrigin, mIntegrity, false,
+ mIsAsync, mIsDefer, true);
+ break;
+ case eSpeculativeLoadNoModuleScriptFromHead:
+ aExecutor->PreloadScript(mUrl, mCharset, mTypeOrCharsetSourceOrDocumentMode,
+ mCrossOrigin, mIntegrity, true,
+ mIsAsync, mIsDefer, true);
+ break;
+ case eSpeculativeLoadStyle:
+ aExecutor->PreloadStyle(mUrl, mCharset, mCrossOrigin, mIntegrity);
+ break;
+ case eSpeculativeLoadManifest:
+ aExecutor->ProcessOfflineManifest(mUrl);
+ break;
+ case eSpeculativeLoadSetDocumentCharset: {
+ nsAutoCString narrowName;
+ CopyUTF16toUTF8(mCharset, narrowName);
+ NS_ASSERTION(mTypeOrCharsetSourceOrDocumentMode.Length() == 1,
+ "Unexpected charset source string");
+ int32_t intSource = (int32_t)mTypeOrCharsetSourceOrDocumentMode.First();
+ aExecutor->SetDocumentCharsetAndSource(narrowName,
+ intSource);
+ }
+ break;
+ case eSpeculativeLoadSetDocumentMode: {
+ NS_ASSERTION(mTypeOrCharsetSourceOrDocumentMode.Length() == 1,
+ "Unexpected document mode string");
+ nsHtml5DocumentMode mode =
+ (nsHtml5DocumentMode)mTypeOrCharsetSourceOrDocumentMode.First();
+ aExecutor->SetDocumentMode(mode);
+ }
+ break;
+ case eSpeculativeLoadPreconnect:
+ aExecutor->Preconnect(mUrl, mCrossOrigin);
+ break;
+ default:
+ NS_NOTREACHED("Bogus speculative load.");
+ break;
+ }
+}
diff --git a/components/htmlfive/nsHtml5SpeculativeLoad.h b/components/htmlfive/nsHtml5SpeculativeLoad.h
new file mode 100644
index 000000000..1f4a61741
--- /dev/null
+++ b/components/htmlfive/nsHtml5SpeculativeLoad.h
@@ -0,0 +1,295 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsHtml5SpeculativeLoad_h
+#define nsHtml5SpeculativeLoad_h
+
+#include "nsString.h"
+#include "nsContentUtils.h"
+
+class nsHtml5TreeOpExecutor;
+
+enum eHtml5SpeculativeLoad
+{
+#ifdef DEBUG
+ eSpeculativeLoadUninitialized,
+#endif
+ eSpeculativeLoadBase,
+ eSpeculativeLoadCSP,
+ eSpeculativeLoadMetaReferrer,
+ eSpeculativeLoadImage,
+ eSpeculativeLoadOpenPicture,
+ eSpeculativeLoadEndPicture,
+ eSpeculativeLoadPictureSource,
+ eSpeculativeLoadScript,
+ eSpeculativeLoadScriptFromHead,
+ eSpeculativeLoadNoModuleScript,
+ eSpeculativeLoadNoModuleScriptFromHead,
+ eSpeculativeLoadStyle,
+ eSpeculativeLoadManifest,
+ eSpeculativeLoadSetDocumentCharset,
+ eSpeculativeLoadSetDocumentMode,
+ eSpeculativeLoadPreconnect
+};
+
+class nsHtml5SpeculativeLoad {
+ public:
+ nsHtml5SpeculativeLoad();
+ ~nsHtml5SpeculativeLoad();
+
+ inline void InitBase(nsHtml5String aUrl)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadBase;
+ aUrl.ToString(mUrl);
+ }
+
+ inline void InitMetaCSP(nsHtml5String aCSP)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadCSP;
+ nsString csp; // Not Auto, because using it to hold nsStringBuffer*
+ aCSP.ToString(csp);
+ mMetaCSP.Assign(
+ nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(csp));
+ }
+
+ inline void InitMetaReferrerPolicy(nsHtml5String aReferrerPolicy)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadMetaReferrer;
+ nsString
+ referrerPolicy; // Not Auto, because using it to hold nsStringBuffer*
+ aReferrerPolicy.ToString(referrerPolicy);
+ mReferrerPolicy.Assign(
+ nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
+ referrerPolicy));
+ }
+
+ inline void InitImage(nsHtml5String aUrl,
+ nsHtml5String aCrossOrigin,
+ nsHtml5String aReferrerPolicy,
+ nsHtml5String aSrcset,
+ nsHtml5String aSizes)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadImage;
+ aUrl.ToString(mUrl);
+ aCrossOrigin.ToString(mCrossOrigin);
+ nsString
+ referrerPolicy; // Not Auto, because using it to hold nsStringBuffer*
+ aReferrerPolicy.ToString(referrerPolicy);
+ mReferrerPolicy.Assign(
+ nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
+ referrerPolicy));
+ aSrcset.ToString(mSrcset);
+ aSizes.ToString(mSizes);
+ }
+
+ // <picture> elements have multiple <source> nodes followed by an <img>,
+ // where we use the first valid source, which may be the img. Because we
+ // can't determine validity at this point without parsing CSS and getting
+ // main thread state, we push preload operations for picture pushed and
+ // popped, so that the target of the preload ops can determine what picture
+ // and nesting level each source/img from the main preloading code exists
+ // at.
+ inline void InitOpenPicture()
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadOpenPicture;
+ }
+
+ inline void InitEndPicture()
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadEndPicture;
+ }
+
+ inline void InitPictureSource(nsHtml5String aSrcset,
+ nsHtml5String aSizes,
+ nsHtml5String aType,
+ nsHtml5String aMedia)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadPictureSource;
+ aSrcset.ToString(mSrcset);
+ aSizes.ToString(mSizes);
+ aType.ToString(mTypeOrCharsetSourceOrDocumentMode);
+ aMedia.ToString(mMedia);
+ }
+
+ inline void InitScript(nsHtml5String aUrl,
+ nsHtml5String aCharset,
+ nsHtml5String aType,
+ nsHtml5String aCrossOrigin,
+ nsHtml5String aIntegrity,
+ bool aParserInHead,
+ bool aAsync,
+ bool aDefer,
+ bool aNoModule)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ if (aNoModule) {
+ mOpCode = aParserInHead ? eSpeculativeLoadNoModuleScriptFromHead
+ : eSpeculativeLoadNoModuleScript;
+ } else {
+ mOpCode = aParserInHead ? eSpeculativeLoadScriptFromHead
+ : eSpeculativeLoadScript;
+ }
+ aUrl.ToString(mUrl);
+ aCharset.ToString(mCharset);
+ aType.ToString(mTypeOrCharsetSourceOrDocumentMode);
+ aCrossOrigin.ToString(mCrossOrigin);
+ aIntegrity.ToString(mIntegrity);
+ mIsAsync = aAsync;
+ mIsDefer = aDefer;
+ }
+
+ inline void InitStyle(nsHtml5String aUrl,
+ nsHtml5String aCharset,
+ nsHtml5String aCrossOrigin,
+ nsHtml5String aIntegrity)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadStyle;
+ aUrl.ToString(mUrl);
+ aCharset.ToString(mCharset);
+ aCrossOrigin.ToString(mCrossOrigin);
+ aIntegrity.ToString(mIntegrity);
+ }
+
+ /**
+ * "Speculative" manifest loads aren't truly speculative--if a manifest
+ * gets loaded, we are committed to it. There can never be a <script>
+ * before the manifest, so the situation of having to undo a manifest due
+ * to document.write() never arises. The reason why a parser
+ * thread-discovered manifest gets loaded via the speculative load queue
+ * as opposed to tree operation queue is that the manifest must get
+ * processed before any actual speculative loads such as scripts. Thus,
+ * manifests seen by the parser thread have to maintain the queue order
+ * relative to true speculative loads. See bug 541079.
+ */
+ inline void InitManifest(nsHtml5String aUrl)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadManifest;
+ aUrl.ToString(mUrl);
+ }
+
+ /**
+ * "Speculative" charset setting isn't truly speculative. If the charset
+ * is set via this operation, we are committed to it unless chardet or
+ * a late meta cause a reload. The reason why a parser
+ * thread-discovered charset gets communicated via the speculative load
+ * queue as opposed to tree operation queue is that the charset change
+ * must get processed before any actual speculative loads such as style
+ * sheets. Thus, encoding decisions by the parser thread have to maintain
+ * the queue order relative to true speculative loads. See bug 675499.
+ */
+ inline void InitSetDocumentCharset(nsACString& aCharset,
+ int32_t aCharsetSource)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadSetDocumentCharset;
+ CopyUTF8toUTF16(aCharset, mCharset);
+ mTypeOrCharsetSourceOrDocumentMode.Assign((char16_t)aCharsetSource);
+ }
+
+ /**
+ * Speculative document mode setting isn't really speculative. Once it
+ * happens, we are committed to it. However, this information needs to
+ * travel in the speculation queue in order to have this information
+ * available before parsing the speculatively loaded style sheets.
+ */
+ inline void InitSetDocumentMode(nsHtml5DocumentMode aMode)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadSetDocumentMode;
+ mTypeOrCharsetSourceOrDocumentMode.Assign((char16_t)aMode);
+ }
+
+ inline void InitPreconnect(nsHtml5String aUrl, nsHtml5String aCrossOrigin)
+ {
+ NS_PRECONDITION(mOpCode == eSpeculativeLoadUninitialized,
+ "Trying to reinitialize a speculative load!");
+ mOpCode = eSpeculativeLoadPreconnect;
+ aUrl.ToString(mUrl);
+ aCrossOrigin.ToString(mCrossOrigin);
+ }
+
+ void Perform(nsHtml5TreeOpExecutor* aExecutor);
+
+ private:
+ eHtml5SpeculativeLoad mOpCode;
+
+ /**
+ * Whether the refering element has async and/or defer attributes.
+ */
+ bool mIsAsync;
+ bool mIsDefer;
+
+ nsString mUrl;
+ nsString mReferrerPolicy;
+ nsString mMetaCSP;
+
+ /**
+ * If mOpCode is eSpeculativeLoadStyle or eSpeculativeLoadScript[FromHead]
+ * then this is the value of the "charset" attribute. For
+ * eSpeculativeLoadSetDocumentCharset it is the charset that the
+ * document's charset is being set to. Otherwise it's empty.
+ */
+ nsString mCharset;
+ /**
+ * If mOpCode is eSpeculativeLoadSetDocumentCharset, this is a
+ * one-character string whose single character's code point is to be
+ * interpreted as a charset source integer. If mOpCode is
+ * eSpeculativeLoadSetDocumentMode, this is a one-character string whose
+ * single character's code point is to be interpreted as an
+ * nsHtml5DocumentMode. Otherwise, it is empty or the value of the type
+ * attribute.
+ */
+ nsString mTypeOrCharsetSourceOrDocumentMode;
+ /**
+ * If mOpCode is eSpeculativeLoadImage or eSpeculativeLoadScript[FromHead]
+ * or eSpeculativeLoadPreconnect this is the value of the "crossorigin"
+ * attribute. If the attribute is not set, this will be a void string.
+ */
+ nsString mCrossOrigin;
+ /**
+ * If mOpCode is eSpeculativeLoadImage or eSpeculativeLoadPictureSource,
+ * this is the value of "srcset" attribute. If the attribute is not set,
+ * this will be a void string.
+ */
+ nsString mSrcset;
+ /**
+ * If mOpCode is eSpeculativeLoadPictureSource, this is the value of "sizes"
+ * attribute. If the attribute is not set, this will be a void string.
+ */
+ nsString mSizes;
+ /**
+ * If mOpCode is eSpeculativeLoadPictureSource, this is the value of "media"
+ * attribute. If the attribute is not set, this will be a void string.
+ */
+ nsString mMedia;
+ /**
+ * If mOpCode is eSpeculativeLoadScript[FromHead], this is the value of the
+ * "integrity" attribute. If the attribute is not set, this will be a void
+ * string.
+ */
+ nsString mIntegrity;
+};
+
+#endif // nsHtml5SpeculativeLoad_h
diff --git a/components/htmlfive/nsHtml5StackNode.cpp b/components/htmlfive/nsHtml5StackNode.cpp
new file mode 100644
index 000000000..4c5058349
--- /dev/null
+++ b/components/htmlfive/nsHtml5StackNode.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2011 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit StackNode.java instead and regenerate.
+ */
+
+#define nsHtml5StackNode_cpp__
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+#include "nsHtml5AttributeName.h"
+#include "nsHtml5ElementName.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5MetaScanner.h"
+#include "nsHtml5UTF16Buffer.h"
+#include "nsHtml5StateSnapshot.h"
+#include "nsHtml5Portability.h"
+
+#include "nsHtml5StackNode.h"
+
+int32_t
+nsHtml5StackNode::getGroup()
+{
+ return flags & nsHtml5ElementName::GROUP_MASK;
+}
+
+bool
+nsHtml5StackNode::isScoping()
+{
+ return (flags & nsHtml5ElementName::SCOPING);
+}
+
+bool
+nsHtml5StackNode::isSpecial()
+{
+ return (flags & nsHtml5ElementName::SPECIAL);
+}
+
+bool
+nsHtml5StackNode::isFosterParenting()
+{
+ return (flags & nsHtml5ElementName::FOSTER_PARENTING);
+}
+
+bool
+nsHtml5StackNode::isHtmlIntegrationPoint()
+{
+ return (flags & nsHtml5ElementName::HTML_INTEGRATION_POINT);
+}
+
+mozilla::dom::HTMLContentCreatorFunction
+nsHtml5StackNode::getHtmlCreator()
+{
+ return htmlCreator;
+}
+
+
+nsHtml5StackNode::nsHtml5StackNode(int32_t flags, int32_t ns, nsIAtom* name, nsIContentHandle* node, nsIAtom* popName, nsHtml5HtmlAttributes* attributes, mozilla::dom::HTMLContentCreatorFunction htmlCreator)
+ : flags(flags),
+ name(name),
+ popName(popName),
+ ns(ns),
+ node(node),
+ attributes(attributes),
+ refcount(1),
+ htmlCreator(htmlCreator)
+{
+ MOZ_COUNT_CTOR(nsHtml5StackNode);
+}
+
+
+nsHtml5StackNode::nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node)
+ : flags(elementName->getFlags()),
+ name(elementName->getName()),
+ popName(elementName->getName()),
+ ns(kNameSpaceID_XHTML),
+ node(node),
+ attributes(nullptr),
+ refcount(1),
+ htmlCreator(nullptr)
+{
+ MOZ_COUNT_CTOR(nsHtml5StackNode);
+ MOZ_ASSERT(elementName->isInterned(), "Don't use this constructor for custom elements.");
+}
+
+
+nsHtml5StackNode::nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node, nsHtml5HtmlAttributes* attributes)
+ : flags(elementName->getFlags()),
+ name(elementName->getName()),
+ popName(elementName->getName()),
+ ns(kNameSpaceID_XHTML),
+ node(node),
+ attributes(attributes),
+ refcount(1),
+ htmlCreator(elementName->getHtmlCreator())
+{
+ MOZ_COUNT_CTOR(nsHtml5StackNode);
+ MOZ_ASSERT(elementName->isInterned(), "Don't use this constructor for custom elements.");
+}
+
+
+nsHtml5StackNode::nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node, nsIAtom* popName)
+ : flags(elementName->getFlags()),
+ name(elementName->getName()),
+ popName(popName),
+ ns(kNameSpaceID_XHTML),
+ node(node),
+ attributes(nullptr),
+ refcount(1),
+ htmlCreator(nullptr)
+{
+ MOZ_COUNT_CTOR(nsHtml5StackNode);
+}
+
+
+nsHtml5StackNode::nsHtml5StackNode(nsHtml5ElementName* elementName, nsIAtom* popName, nsIContentHandle* node)
+ : flags(prepareSvgFlags(elementName->getFlags())),
+ name(elementName->getName()),
+ popName(popName),
+ ns(kNameSpaceID_SVG),
+ node(node),
+ attributes(nullptr),
+ refcount(1),
+ htmlCreator(nullptr)
+{
+ MOZ_COUNT_CTOR(nsHtml5StackNode);
+}
+
+
+nsHtml5StackNode::nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node, nsIAtom* popName, bool markAsIntegrationPoint)
+ : flags(prepareMathFlags(elementName->getFlags(), markAsIntegrationPoint)),
+ name(elementName->getName()),
+ popName(popName),
+ ns(kNameSpaceID_MathML),
+ node(node),
+ attributes(nullptr),
+ refcount(1),
+ htmlCreator(nullptr)
+{
+ MOZ_COUNT_CTOR(nsHtml5StackNode);
+}
+
+int32_t
+nsHtml5StackNode::prepareSvgFlags(int32_t flags)
+{
+ flags &= ~(nsHtml5ElementName::FOSTER_PARENTING | nsHtml5ElementName::SCOPING | nsHtml5ElementName::SPECIAL | nsHtml5ElementName::OPTIONAL_END_TAG);
+ if ((flags & nsHtml5ElementName::SCOPING_AS_SVG)) {
+ flags |= (nsHtml5ElementName::SCOPING | nsHtml5ElementName::SPECIAL | nsHtml5ElementName::HTML_INTEGRATION_POINT);
+ }
+ return flags;
+}
+
+int32_t
+nsHtml5StackNode::prepareMathFlags(int32_t flags, bool markAsIntegrationPoint)
+{
+ flags &= ~(nsHtml5ElementName::FOSTER_PARENTING | nsHtml5ElementName::SCOPING | nsHtml5ElementName::SPECIAL | nsHtml5ElementName::OPTIONAL_END_TAG);
+ if ((flags & nsHtml5ElementName::SCOPING_AS_MATHML)) {
+ flags |= (nsHtml5ElementName::SCOPING | nsHtml5ElementName::SPECIAL);
+ }
+ if (markAsIntegrationPoint) {
+ flags |= nsHtml5ElementName::HTML_INTEGRATION_POINT;
+ }
+ return flags;
+}
+
+
+nsHtml5StackNode::~nsHtml5StackNode()
+{
+ MOZ_COUNT_DTOR(nsHtml5StackNode);
+ delete attributes;
+}
+
+void
+nsHtml5StackNode::dropAttributes()
+{
+ attributes = nullptr;
+}
+
+void
+nsHtml5StackNode::retain()
+{
+ refcount++;
+}
+
+void
+nsHtml5StackNode::release()
+{
+ refcount--;
+ if (!refcount) {
+ delete this;
+ }
+}
+
+void
+nsHtml5StackNode::initializeStatics()
+{
+}
+
+void
+nsHtml5StackNode::releaseStatics()
+{
+}
+
+
diff --git a/components/htmlfive/nsHtml5StackNode.h b/components/htmlfive/nsHtml5StackNode.h
new file mode 100644
index 000000000..c02b8c793
--- /dev/null
+++ b/components/htmlfive/nsHtml5StackNode.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2011 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit StackNode.java instead and regenerate.
+ */
+
+#ifndef nsHtml5StackNode_h
+#define nsHtml5StackNode_h
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+class nsHtml5StreamParser;
+
+class nsHtml5AttributeName;
+class nsHtml5ElementName;
+class nsHtml5Tokenizer;
+class nsHtml5TreeBuilder;
+class nsHtml5MetaScanner;
+class nsHtml5UTF16Buffer;
+class nsHtml5StateSnapshot;
+class nsHtml5Portability;
+
+
+class nsHtml5StackNode
+{
+ public:
+ int32_t flags;
+ nsIAtom* name;
+ nsIAtom* popName;
+ int32_t ns;
+ nsIContentHandle* node;
+ nsHtml5HtmlAttributes* attributes;
+ private:
+ int32_t refcount;
+ mozilla::dom::HTMLContentCreatorFunction htmlCreator;
+ public:
+ inline int32_t getFlags()
+ {
+ return flags;
+ }
+
+ int32_t getGroup();
+ bool isScoping();
+ bool isSpecial();
+ bool isFosterParenting();
+ bool isHtmlIntegrationPoint();
+ mozilla::dom::HTMLContentCreatorFunction getHtmlCreator();
+ nsHtml5StackNode(int32_t flags, int32_t ns, nsIAtom* name, nsIContentHandle* node, nsIAtom* popName, nsHtml5HtmlAttributes* attributes, mozilla::dom::HTMLContentCreatorFunction htmlCreator);
+ nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node);
+ nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node, nsHtml5HtmlAttributes* attributes);
+ nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node, nsIAtom* popName);
+ nsHtml5StackNode(nsHtml5ElementName* elementName, nsIAtom* popName, nsIContentHandle* node);
+ nsHtml5StackNode(nsHtml5ElementName* elementName, nsIContentHandle* node, nsIAtom* popName, bool markAsIntegrationPoint);
+ private:
+ static int32_t prepareSvgFlags(int32_t flags);
+ static int32_t prepareMathFlags(int32_t flags, bool markAsIntegrationPoint);
+ public:
+ ~nsHtml5StackNode();
+ void dropAttributes();
+ void retain();
+ void release();
+ static void initializeStatics();
+ static void releaseStatics();
+};
+
+#endif
+
diff --git a/components/htmlfive/nsHtml5StateSnapshot.cpp b/components/htmlfive/nsHtml5StateSnapshot.cpp
new file mode 100644
index 000000000..67f309a3e
--- /dev/null
+++ b/components/htmlfive/nsHtml5StateSnapshot.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2009-2010 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit StateSnapshot.java instead and regenerate.
+ */
+
+#define nsHtml5StateSnapshot_cpp__
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+#include "nsHtml5AttributeName.h"
+#include "nsHtml5ElementName.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5MetaScanner.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5UTF16Buffer.h"
+#include "nsHtml5Portability.h"
+
+#include "nsHtml5StateSnapshot.h"
+
+
+nsHtml5StateSnapshot::nsHtml5StateSnapshot(jArray<nsHtml5StackNode*,int32_t> stack, jArray<nsHtml5StackNode*,int32_t> listOfActiveFormattingElements, jArray<int32_t,int32_t> templateModeStack, nsIContentHandle* formPointer, nsIContentHandle* headPointer, nsIContentHandle* deepTreeSurrogateParent, int32_t mode, int32_t originalMode, bool framesetOk, bool needToDropLF, bool quirks)
+ : stack(stack),
+ listOfActiveFormattingElements(listOfActiveFormattingElements),
+ templateModeStack(templateModeStack),
+ formPointer(formPointer),
+ headPointer(headPointer),
+ deepTreeSurrogateParent(deepTreeSurrogateParent),
+ mode(mode),
+ originalMode(originalMode),
+ framesetOk(framesetOk),
+ needToDropLF(needToDropLF),
+ quirks(quirks)
+{
+ MOZ_COUNT_CTOR(nsHtml5StateSnapshot);
+}
+
+jArray<nsHtml5StackNode*,int32_t>
+nsHtml5StateSnapshot::getStack()
+{
+ return stack;
+}
+
+jArray<int32_t,int32_t>
+nsHtml5StateSnapshot::getTemplateModeStack()
+{
+ return templateModeStack;
+}
+
+jArray<nsHtml5StackNode*,int32_t>
+nsHtml5StateSnapshot::getListOfActiveFormattingElements()
+{
+ return listOfActiveFormattingElements;
+}
+
+nsIContentHandle*
+nsHtml5StateSnapshot::getFormPointer()
+{
+ return formPointer;
+}
+
+nsIContentHandle*
+nsHtml5StateSnapshot::getHeadPointer()
+{
+ return headPointer;
+}
+
+nsIContentHandle*
+nsHtml5StateSnapshot::getDeepTreeSurrogateParent()
+{
+ return deepTreeSurrogateParent;
+}
+
+int32_t
+nsHtml5StateSnapshot::getMode()
+{
+ return mode;
+}
+
+int32_t
+nsHtml5StateSnapshot::getOriginalMode()
+{
+ return originalMode;
+}
+
+bool
+nsHtml5StateSnapshot::isFramesetOk()
+{
+ return framesetOk;
+}
+
+bool
+nsHtml5StateSnapshot::isNeedToDropLF()
+{
+ return needToDropLF;
+}
+
+bool
+nsHtml5StateSnapshot::isQuirks()
+{
+ return quirks;
+}
+
+int32_t
+nsHtml5StateSnapshot::getListOfActiveFormattingElementsLength()
+{
+ return listOfActiveFormattingElements.length;
+}
+
+int32_t
+nsHtml5StateSnapshot::getStackLength()
+{
+ return stack.length;
+}
+
+int32_t
+nsHtml5StateSnapshot::getTemplateModeStackLength()
+{
+ return templateModeStack.length;
+}
+
+
+nsHtml5StateSnapshot::~nsHtml5StateSnapshot()
+{
+ MOZ_COUNT_DTOR(nsHtml5StateSnapshot);
+ for (int32_t i = 0; i < stack.length; i++) {
+ stack[i]->release();
+ }
+ for (int32_t i = 0; i < listOfActiveFormattingElements.length; i++) {
+ if (listOfActiveFormattingElements[i]) {
+ listOfActiveFormattingElements[i]->release();
+ }
+ }
+}
+
+void
+nsHtml5StateSnapshot::initializeStatics()
+{
+}
+
+void
+nsHtml5StateSnapshot::releaseStatics()
+{
+}
+
+
diff --git a/components/htmlfive/nsHtml5StateSnapshot.h b/components/htmlfive/nsHtml5StateSnapshot.h
new file mode 100644
index 000000000..b608a8410
--- /dev/null
+++ b/components/htmlfive/nsHtml5StateSnapshot.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2009-2010 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit StateSnapshot.java instead and regenerate.
+ */
+
+#ifndef nsHtml5StateSnapshot_h
+#define nsHtml5StateSnapshot_h
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+class nsHtml5StreamParser;
+
+class nsHtml5AttributeName;
+class nsHtml5ElementName;
+class nsHtml5Tokenizer;
+class nsHtml5TreeBuilder;
+class nsHtml5MetaScanner;
+class nsHtml5UTF16Buffer;
+class nsHtml5Portability;
+
+
+class nsHtml5StateSnapshot : public nsAHtml5TreeBuilderState
+{
+ private:
+ autoJArray<nsHtml5StackNode*,int32_t> stack;
+ autoJArray<nsHtml5StackNode*,int32_t> listOfActiveFormattingElements;
+ autoJArray<int32_t,int32_t> templateModeStack;
+ nsIContentHandle* formPointer;
+ nsIContentHandle* headPointer;
+ nsIContentHandle* deepTreeSurrogateParent;
+ int32_t mode;
+ int32_t originalMode;
+ bool framesetOk;
+ bool needToDropLF;
+ bool quirks;
+ public:
+ nsHtml5StateSnapshot(jArray<nsHtml5StackNode*,int32_t> stack, jArray<nsHtml5StackNode*,int32_t> listOfActiveFormattingElements, jArray<int32_t,int32_t> templateModeStack, nsIContentHandle* formPointer, nsIContentHandle* headPointer, nsIContentHandle* deepTreeSurrogateParent, int32_t mode, int32_t originalMode, bool framesetOk, bool needToDropLF, bool quirks);
+ jArray<nsHtml5StackNode*,int32_t> getStack();
+ jArray<int32_t,int32_t> getTemplateModeStack();
+ jArray<nsHtml5StackNode*,int32_t> getListOfActiveFormattingElements();
+ nsIContentHandle* getFormPointer();
+ nsIContentHandle* getHeadPointer();
+ nsIContentHandle* getDeepTreeSurrogateParent();
+ int32_t getMode();
+ int32_t getOriginalMode();
+ bool isFramesetOk();
+ bool isNeedToDropLF();
+ bool isQuirks();
+ int32_t getListOfActiveFormattingElementsLength();
+ int32_t getStackLength();
+ int32_t getTemplateModeStackLength();
+ ~nsHtml5StateSnapshot();
+ static void initializeStatics();
+ static void releaseStatics();
+};
+
+#endif
+
diff --git a/components/htmlfive/nsHtml5StreamListener.cpp b/components/htmlfive/nsHtml5StreamListener.cpp
new file mode 100644
index 000000000..a585ce8de
--- /dev/null
+++ b/components/htmlfive/nsHtml5StreamListener.cpp
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsHtml5StreamListener.h"
+
+NS_IMPL_ADDREF(nsHtml5StreamListener)
+NS_IMPL_RELEASE(nsHtml5StreamListener)
+
+NS_INTERFACE_MAP_BEGIN(nsHtml5StreamListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsHtml5StreamListener::nsHtml5StreamListener(nsHtml5StreamParser* aDelegate)
+ : mDelegate(aDelegate)
+{
+}
+
+nsHtml5StreamListener::~nsHtml5StreamListener()
+{
+}
+
+void
+nsHtml5StreamListener::DropDelegate()
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Must not call DropDelegate from non-main threads.");
+ mDelegate = nullptr;
+}
+
+NS_IMETHODIMP
+nsHtml5StreamListener::CheckListenerChain()
+{
+ if (MOZ_UNLIKELY(!mDelegate)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mDelegate->CheckListenerChain();
+}
+
+NS_IMETHODIMP
+nsHtml5StreamListener::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ if (MOZ_UNLIKELY(!mDelegate)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mDelegate->OnStartRequest(aRequest, aContext);
+}
+
+NS_IMETHODIMP
+nsHtml5StreamListener::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ if (MOZ_UNLIKELY(!mDelegate)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mDelegate->OnStopRequest(aRequest,
+ aContext,
+ aStatus);
+}
+
+NS_IMETHODIMP
+nsHtml5StreamListener::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ if (MOZ_UNLIKELY(!mDelegate)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mDelegate->OnDataAvailable(aRequest,
+ aContext,
+ aInStream,
+ aSourceOffset,
+ aLength);
+}
+
diff --git a/components/htmlfive/nsHtml5StreamListener.h b/components/htmlfive/nsHtml5StreamListener.h
new file mode 100644
index 000000000..966765fb5
--- /dev/null
+++ b/components/htmlfive/nsHtml5StreamListener.h
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHtml5StreamListener_h
+#define nsHtml5StreamListener_h
+
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsHtml5RefPtr.h"
+#include "nsHtml5StreamParser.h"
+
+/**
+ * The purpose of this class is to reconcile the problem that
+ * nsHtml5StreamParser is a cycle collection participant, which means that it
+ * can only be refcounted on the main thread, but
+ * nsIThreadRetargetableStreamListener can be refcounted from another thread,
+ * so nsHtml5StreamParser being an nsIThreadRetargetableStreamListener was
+ * a memory corruption problem.
+ *
+ * mDelegate is an nsHtml5RefPtr, which releases the object that it points
+ * to from a runnable on the main thread. DropDelegate() is only called on
+ * the main thread. This call will finish before the main-thread derefs the
+ * nsHtml5StreamListener itself, so there is no risk of another thread making
+ * the refcount of nsHtml5StreamListener go to zero and running the destructor
+ * concurrently. Other than that, the thread-safe nsISupports implementation
+ * takes care of the destructor not running concurrently from different
+ * threads, so there is no need to have a mutex around nsHtml5RefPtr to
+ * prevent it from double-releasing nsHtml5StreamParser.
+ */
+class nsHtml5StreamListener : public nsIStreamListener,
+ public nsIThreadRetargetableStreamListener
+{
+public:
+ explicit nsHtml5StreamListener(nsHtml5StreamParser* aDelegate);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ inline nsHtml5StreamParser* GetDelegate()
+ {
+ return mDelegate;
+ }
+
+ void DropDelegate();
+
+private:
+ virtual ~nsHtml5StreamListener();
+
+ nsHtml5RefPtr<nsHtml5StreamParser> mDelegate;
+};
+
+#endif // nsHtml5StreamListener_h
diff --git a/components/htmlfive/nsHtml5StreamParser.cpp b/components/htmlfive/nsHtml5StreamParser.cpp
new file mode 100644
index 000000000..9779aa194
--- /dev/null
+++ b/components/htmlfive/nsHtml5StreamParser.cpp
@@ -0,0 +1,1726 @@
+/* -*- 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/DebugOnly.h"
+
+#include "nsHtml5StreamParser.h"
+#include "nsContentUtils.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsIHttpChannel.h"
+#include "nsHtml5Parser.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5Module.h"
+#include "nsHtml5RefPtr.h"
+#include "nsIScriptError.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsHtml5Highlighter.h"
+#include "expat_config.h"
+#include "expat.h"
+#include "nsINestedURI.h"
+#include "nsCharsetSource.h"
+#include "nsIWyciwygChannel.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsPrintfCString.h"
+#include "nsNetUtil.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+
+using namespace mozilla;
+using mozilla::dom::EncodingUtils;
+
+int32_t nsHtml5StreamParser::sTimerInitialDelay = 120;
+int32_t nsHtml5StreamParser::sTimerSubsequentDelay = 120;
+
+// static
+void
+nsHtml5StreamParser::InitializeStatics()
+{
+ Preferences::AddIntVarCache(&sTimerInitialDelay,
+ "html5.flushtimer.initialdelay");
+ Preferences::AddIntVarCache(&sTimerSubsequentDelay,
+ "html5.flushtimer.subsequentdelay");
+}
+
+/*
+ * Note that nsHtml5StreamParser implements cycle collecting AddRef and
+ * Release. Therefore, nsHtml5StreamParser must never be refcounted from
+ * the parser thread!
+ *
+ * To work around this limitation, runnables posted by the main thread to the
+ * parser thread hold their reference to the stream parser in an
+ * nsHtml5RefPtr. Upon creation, nsHtml5RefPtr addrefs the object it holds
+ * just like a regular nsRefPtr. This is OK, since the creation of the
+ * runnable and the nsHtml5RefPtr happens on the main thread.
+ *
+ * When the runnable is done on the parser thread, the destructor of
+ * nsHtml5RefPtr runs there. It doesn't call Release on the held object
+ * directly. Instead, it posts another runnable back to the main thread where
+ * that runnable calls Release on the wrapped object.
+ *
+ * When posting runnables in the other direction, the runnables have to be
+ * created on the main thread when nsHtml5StreamParser is instantiated and
+ * held for the lifetime of the nsHtml5StreamParser. This works, because the
+ * same runnabled can be dispatched multiple times and currently runnables
+ * posted from the parser thread to main thread don't need to wrap any
+ * runnable-specific data. (In the other direction, the runnables most notably
+ * wrap the byte data of the stream.)
+ */
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHtml5StreamParser)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHtml5StreamParser)
+
+NS_INTERFACE_TABLE_HEAD(nsHtml5StreamParser)
+ NS_INTERFACE_TABLE(nsHtml5StreamParser,
+ nsICharsetDetectionObserver)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsHtml5StreamParser)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5StreamParser)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5StreamParser)
+ tmp->DropTimer();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mObserver)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRequest)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ tmp->mExecutorFlusher = nullptr;
+ tmp->mLoadFlusher = nullptr;
+ tmp->mExecutor = nullptr;
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChardet)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5StreamParser)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObserver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRequest)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ // hack: count the strongly owned edge wrapped in the runnable
+ if (tmp->mExecutorFlusher) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExecutorFlusher->mExecutor");
+ cb.NoteXPCOMChild(static_cast<nsIContentSink*> (tmp->mExecutor));
+ }
+ // hack: count the strongly owned edge wrapped in the runnable
+ if (tmp->mLoadFlusher) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mLoadFlusher->mExecutor");
+ cb.NoteXPCOMChild(static_cast<nsIContentSink*> (tmp->mExecutor));
+ }
+ // hack: count self if held by mChardet
+ if (tmp->mChardet) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mChardet->mObserver");
+ cb.NoteXPCOMChild(static_cast<nsICharsetDetectionObserver*>(tmp));
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+class nsHtml5ExecutorFlusher : public Runnable
+{
+ private:
+ RefPtr<nsHtml5TreeOpExecutor> mExecutor;
+ public:
+ explicit nsHtml5ExecutorFlusher(nsHtml5TreeOpExecutor* aExecutor)
+ : mExecutor(aExecutor)
+ {}
+ NS_IMETHOD Run() override
+ {
+ if (!mExecutor->isInList()) {
+ mExecutor->RunFlushLoop();
+ }
+ return NS_OK;
+ }
+};
+
+class nsHtml5LoadFlusher : public Runnable
+{
+ private:
+ RefPtr<nsHtml5TreeOpExecutor> mExecutor;
+ public:
+ explicit nsHtml5LoadFlusher(nsHtml5TreeOpExecutor* aExecutor)
+ : mExecutor(aExecutor)
+ {}
+ NS_IMETHOD Run() override
+ {
+ mExecutor->FlushSpeculativeLoads();
+ return NS_OK;
+ }
+};
+
+nsHtml5StreamParser::nsHtml5StreamParser(nsHtml5TreeOpExecutor* aExecutor,
+ nsHtml5Parser* aOwner,
+ eParserMode aMode)
+ : mFirstBuffer(nullptr) // Will be filled when starting
+ , mLastBuffer(nullptr) // Will be filled when starting
+ , mExecutor(aExecutor)
+ , mTreeBuilder(new nsHtml5TreeBuilder((aMode == VIEW_SOURCE_HTML ||
+ aMode == VIEW_SOURCE_XML) ?
+ nullptr : mExecutor->GetStage(),
+ aMode == NORMAL ?
+ mExecutor->GetStage() : nullptr))
+ , mTokenizer(new nsHtml5Tokenizer(mTreeBuilder, aMode == VIEW_SOURCE_XML))
+ , mTokenizerMutex("nsHtml5StreamParser mTokenizerMutex")
+ , mOwner(aOwner)
+ , mSpeculationMutex("nsHtml5StreamParser mSpeculationMutex")
+ , mTerminatedMutex("nsHtml5StreamParser mTerminatedMutex")
+ , mThread(nsHtml5Module::GetStreamParserThread())
+ , mExecutorFlusher(new nsHtml5ExecutorFlusher(aExecutor))
+ , mLoadFlusher(new nsHtml5LoadFlusher(aExecutor))
+ , mFlushTimer(do_CreateInstance("@mozilla.org/timer;1"))
+ , mMode(aMode)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ mFlushTimer->SetTarget(mThread);
+#ifdef DEBUG
+ mAtomTable.SetPermittedLookupThread(mThread);
+#endif
+ mTokenizer->setInterner(&mAtomTable);
+ mTokenizer->setEncodingDeclarationHandler(this);
+
+ if (aMode == VIEW_SOURCE_HTML || aMode == VIEW_SOURCE_XML) {
+ nsHtml5Highlighter* highlighter =
+ new nsHtml5Highlighter(mExecutor->GetStage());
+ mTokenizer->EnableViewSource(highlighter); // takes ownership
+ mTreeBuilder->EnableViewSource(highlighter); // doesn't own
+ }
+
+ // Chardet instantiation adapted from File.
+ // Chardet is initialized here even if it turns out to be useless
+ // to make the chardet refcount its observer (nsHtml5StreamParser)
+ // on the main thread.
+ const nsAdoptingCString& detectorName =
+ Preferences::GetLocalizedCString("intl.charset.detector");
+ if (!detectorName.IsEmpty()) {
+ nsAutoCString detectorContractID;
+ detectorContractID.AssignLiteral(NS_CHARSET_DETECTOR_CONTRACTID_BASE);
+ detectorContractID += detectorName;
+ if ((mChardet = do_CreateInstance(detectorContractID.get()))) {
+ (void) mChardet->Init(this);
+ mFeedChardet = true;
+ }
+ }
+
+ // There's a zeroing operator new for everything else
+}
+
+nsHtml5StreamParser::~nsHtml5StreamParser()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ mTokenizer->end();
+ NS_ASSERTION(!mFlushTimer, "Flush timer was not dropped before dtor!");
+#ifdef DEBUG
+ mRequest = nullptr;
+ mObserver = nullptr;
+ mUnicodeDecoder = nullptr;
+ mSniffingBuffer = nullptr;
+ mMetaScanner = nullptr;
+ mFirstBuffer = nullptr;
+ mExecutor = nullptr;
+ mTreeBuilder = nullptr;
+ mTokenizer = nullptr;
+ mOwner = nullptr;
+#endif
+}
+
+nsresult
+nsHtml5StreamParser::GetChannel(nsIChannel** aChannel)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ return mRequest ? CallQueryInterface(mRequest, aChannel) :
+ NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsHtml5StreamParser::Notify(const char* aCharset, nsDetectionConfident aConf)
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ if (aConf == eBestAnswer || aConf == eSureAnswer) {
+ mFeedChardet = false; // just in case
+ nsAutoCString encoding;
+ if (!EncodingUtils::FindEncodingForLabelNoReplacement(
+ nsDependentCString(aCharset), encoding)) {
+ return NS_OK;
+ }
+ if (HasDecoder()) {
+ if (mCharset.Equals(encoding)) {
+ NS_ASSERTION(mCharsetSource < kCharsetFromAutoDetection,
+ "Why are we running chardet at all?");
+ mCharsetSource = kCharsetFromAutoDetection;
+ mTreeBuilder->SetDocumentCharset(mCharset, mCharsetSource);
+ } else {
+ // We've already committed to a decoder. Request a reload from the
+ // docshell.
+ mTreeBuilder->NeedsCharsetSwitchTo(encoding,
+ kCharsetFromAutoDetection,
+ 0);
+ FlushTreeOpsAndDisarmTimer();
+ Interrupt();
+ }
+ } else {
+ // Got a confident answer from the sniffing buffer. That code will
+ // take care of setting up the decoder.
+ mCharset.Assign(encoding);
+ mCharsetSource = kCharsetFromAutoDetection;
+ mTreeBuilder->SetDocumentCharset(mCharset, mCharsetSource);
+ }
+ }
+ return NS_OK;
+}
+
+void
+nsHtml5StreamParser::SetViewSourceTitle(nsIURI* aURL)
+{
+ if (aURL) {
+ nsCOMPtr<nsIURI> temp;
+ bool isViewSource;
+ aURL->SchemeIs("view-source", &isViewSource);
+ if (isViewSource) {
+ nsCOMPtr<nsINestedURI> nested = do_QueryInterface(aURL);
+ nested->GetInnerURI(getter_AddRefs(temp));
+ } else {
+ temp = aURL;
+ }
+ bool isData;
+ temp->SchemeIs("data", &isData);
+ if (isData) {
+ // Avoid showing potentially huge data: URLs. The three last bytes are
+ // UTF-8 for an ellipsis.
+ mViewSourceTitle.AssignLiteral("data:\xE2\x80\xA6");
+ } else {
+ nsresult rv = temp->GetSpec(mViewSourceTitle);
+ if (NS_FAILED(rv)) {
+ mViewSourceTitle.AssignLiteral("\xE2\x80\xA6");
+ }
+ }
+ }
+}
+
+nsresult
+nsHtml5StreamParser::SetupDecodingAndWriteSniffingBufferAndCurrentSegment(const uint8_t* aFromSegment, // can be null
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ nsresult rv = NS_OK;
+ mUnicodeDecoder = EncodingUtils::DecoderForEncoding(mCharset);
+ if (mSniffingBuffer) {
+ uint32_t writeCount;
+ rv = WriteStreamBytes(mSniffingBuffer.get(), mSniffingLength, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSniffingBuffer = nullptr;
+ }
+ mMetaScanner = nullptr;
+ if (aFromSegment) {
+ rv = WriteStreamBytes(aFromSegment, aCount, aWriteCount);
+ }
+ return rv;
+}
+
+nsresult
+nsHtml5StreamParser::SetupDecodingFromBom(const char* aDecoderCharsetName)
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ mCharset.Assign(aDecoderCharsetName);
+ mUnicodeDecoder = EncodingUtils::DecoderForEncoding(mCharset);
+ mCharsetSource = kCharsetFromByteOrderMark;
+ mFeedChardet = false;
+ mTreeBuilder->SetDocumentCharset(mCharset, mCharsetSource);
+ mSniffingBuffer = nullptr;
+ mMetaScanner = nullptr;
+ mBomState = BOM_SNIFFING_OVER;
+ return NS_OK;
+}
+
+void
+nsHtml5StreamParser::SniffBOMlessUTF16BasicLatin(const uint8_t* aFromSegment,
+ uint32_t aCountToSniffingLimit)
+{
+ // Avoid underspecified heuristic craziness for XHR
+ if (mMode == LOAD_AS_DATA) {
+ return;
+ }
+ // Make sure there's enough data. Require room for "<title></title>"
+ if (mSniffingLength + aCountToSniffingLimit < 30) {
+ return;
+ }
+ // even-numbered bytes tracked at 0, odd-numbered bytes tracked at 1
+ bool byteZero[2] = { false, false };
+ bool byteNonZero[2] = { false, false };
+ uint32_t i = 0;
+ if (mSniffingBuffer) {
+ for (; i < mSniffingLength; ++i) {
+ if (mSniffingBuffer[i]) {
+ if (byteNonZero[1 - (i % 2)]) {
+ return;
+ }
+ byteNonZero[i % 2] = true;
+ } else {
+ if (byteZero[1 - (i % 2)]) {
+ return;
+ }
+ byteZero[i % 2] = true;
+ }
+ }
+ }
+ if (aFromSegment) {
+ for (uint32_t j = 0; j < aCountToSniffingLimit; ++j) {
+ if (aFromSegment[j]) {
+ if (byteNonZero[1 - ((i + j) % 2)]) {
+ return;
+ }
+ byteNonZero[(i + j) % 2] = true;
+ } else {
+ if (byteZero[1 - ((i + j) % 2)]) {
+ return;
+ }
+ byteZero[(i + j) % 2] = true;
+ }
+ }
+ }
+
+ if (byteNonZero[0]) {
+ mCharset.AssignLiteral("UTF-16LE");
+ } else {
+ mCharset.AssignLiteral("UTF-16BE");
+ }
+ mCharsetSource = kCharsetFromIrreversibleAutoDetection;
+ mTreeBuilder->SetDocumentCharset(mCharset, mCharsetSource);
+ mFeedChardet = false;
+ mTreeBuilder->MaybeComplainAboutCharset("EncBomlessUtf16",
+ true,
+ 0);
+
+}
+
+void
+nsHtml5StreamParser::SetEncodingFromExpat(const char16_t* aEncoding)
+{
+ if (aEncoding) {
+ nsDependentString utf16(aEncoding);
+ nsAutoCString utf8;
+ CopyUTF16toUTF8(utf16, utf8);
+ if (PreferredForInternalEncodingDecl(utf8)) {
+ mCharset.Assign(utf8);
+ mCharsetSource = kCharsetFromMetaTag; // closest for XML
+ return;
+ }
+ // else the page declared an encoding Gecko doesn't support and we'd
+ // end up defaulting to UTF-8 anyway. Might as well fall through here
+ // right away and let the encoding be set to UTF-8 which we'd default to
+ // anyway.
+ }
+ mCharset.AssignLiteral("UTF-8"); // XML defaults to UTF-8 without a BOM
+ mCharsetSource = kCharsetFromMetaTag; // means confident
+}
+
+// A separate user data struct is used instead of passing the
+// nsHtml5StreamParser instance as user data in order to avoid including
+// expat.h in nsHtml5StreamParser.h. Doing that would cause naming conflicts.
+// Using a separate user data struct also avoids bloating nsHtml5StreamParser
+// by one pointer.
+struct UserData {
+ XML_Parser mExpat;
+ nsHtml5StreamParser* mStreamParser;
+};
+
+// Using no-namespace handler callbacks to avoid including expat.h in
+// nsHtml5StreamParser.h, since doing so would cause naming conclicts.
+static void
+HandleXMLDeclaration(void* aUserData,
+ const XML_Char* aVersion,
+ const XML_Char* aEncoding,
+ int aStandalone)
+{
+ UserData* ud = static_cast<UserData*>(aUserData);
+ ud->mStreamParser->SetEncodingFromExpat(
+ reinterpret_cast<const char16_t*>(aEncoding));
+ XML_StopParser(ud->mExpat, false);
+}
+
+static void
+HandleStartElement(void* aUserData,
+ const XML_Char* aName,
+ const XML_Char **aAtts)
+{
+ UserData* ud = static_cast<UserData*>(aUserData);
+ XML_StopParser(ud->mExpat, false);
+}
+
+static void
+HandleEndElement(void* aUserData,
+ const XML_Char* aName)
+{
+ UserData* ud = static_cast<UserData*>(aUserData);
+ XML_StopParser(ud->mExpat, false);
+}
+
+static void
+HandleComment(void* aUserData,
+ const XML_Char* aName)
+{
+ UserData* ud = static_cast<UserData*>(aUserData);
+ XML_StopParser(ud->mExpat, false);
+}
+
+static void
+HandleProcessingInstruction(void* aUserData,
+ const XML_Char* aTarget,
+ const XML_Char* aData)
+{
+ UserData* ud = static_cast<UserData*>(aUserData);
+ XML_StopParser(ud->mExpat, false);
+}
+
+nsresult
+nsHtml5StreamParser::FinalizeSniffing(const uint8_t* aFromSegment, // can be null
+ uint32_t aCount,
+ uint32_t* aWriteCount,
+ uint32_t aCountToSniffingLimit)
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ NS_ASSERTION(mCharsetSource < kCharsetFromParentForced,
+ "Should not finalize sniffing when using forced charset.");
+ if (mMode == VIEW_SOURCE_XML) {
+ static const XML_Memory_Handling_Suite memsuite =
+ {
+ (void *(*)(size_t))moz_xmalloc,
+ (void *(*)(void *, size_t))moz_xrealloc,
+ free
+ };
+
+ static const char16_t kExpatSeparator[] = { 0xFFFF, '\0' };
+
+ static const char16_t kISO88591[] =
+ { 'I', 'S', 'O', '-', '8', '8', '5', '9', '-', '1', '\0' };
+
+ UserData ud;
+ ud.mStreamParser = this;
+
+ // If we got this far, the stream didn't have a BOM. UTF-16-encoded XML
+ // documents MUST begin with a BOM. We don't support EBCDIC and such.
+ // Thus, at this point, what we have is garbage or something encoded using
+ // a rough ASCII superset. ISO-8859-1 allows us to decode ASCII bytes
+ // without throwing errors when bytes have the most significant bit set
+ // and without triggering expat's unknown encoding code paths. This is
+ // enough to be able to use expat to parse the XML declaration in order
+ // to extract the encoding name from it.
+ ud.mExpat = XML_ParserCreate_MM(kISO88591, &memsuite, kExpatSeparator);
+ XML_SetXmlDeclHandler(ud.mExpat, HandleXMLDeclaration);
+ XML_SetElementHandler(ud.mExpat, HandleStartElement, HandleEndElement);
+ XML_SetCommentHandler(ud.mExpat, HandleComment);
+ XML_SetProcessingInstructionHandler(ud.mExpat, HandleProcessingInstruction);
+ XML_SetUserData(ud.mExpat, static_cast<void*>(&ud));
+
+ XML_Status status = XML_STATUS_OK;
+
+ // aFromSegment points to the data obtained from the current network
+ // event. mSniffingBuffer (if it exists) contains the data obtained before
+ // the current event. Thus, mSniffingLenth bytes of mSniffingBuffer
+ // followed by aCountToSniffingLimit bytes from aFromSegment are the
+ // first 1024 bytes of the file (or the file as a whole if the file is
+ // 1024 bytes long or shorter). Thus, we parse both buffers, but if the
+ // first call succeeds already, we skip parsing the second buffer.
+ if (mSniffingBuffer) {
+ status = XML_Parse(ud.mExpat,
+ reinterpret_cast<const char*>(mSniffingBuffer.get()),
+ mSniffingLength,
+ false);
+ }
+ if (status == XML_STATUS_OK &&
+ mCharsetSource < kCharsetFromMetaTag &&
+ aFromSegment) {
+ status = XML_Parse(ud.mExpat,
+ reinterpret_cast<const char*>(aFromSegment),
+ aCountToSniffingLimit,
+ false);
+ }
+ XML_ParserFree(ud.mExpat);
+
+ if (mCharsetSource < kCharsetFromMetaTag) {
+ // Failed to get an encoding from the XML declaration. XML defaults
+ // confidently to UTF-8 in this case.
+ // It is also possible that the document has an XML declaration that is
+ // longer than 1024 bytes, but that case is not worth worrying about.
+ mCharset.AssignLiteral("UTF-8");
+ mCharsetSource = kCharsetFromMetaTag; // means confident
+ }
+
+ return SetupDecodingAndWriteSniffingBufferAndCurrentSegment(aFromSegment,
+ aCount,
+ aWriteCount);
+ }
+
+ // meta scan failed.
+ if (mCharsetSource >= kCharsetFromHintPrevDoc) {
+ mFeedChardet = false;
+ return SetupDecodingAndWriteSniffingBufferAndCurrentSegment(aFromSegment, aCount, aWriteCount);
+ }
+ // Check for BOMless UTF-16 with Basic
+ // Latin content for compat with IE. See bug 631751.
+ SniffBOMlessUTF16BasicLatin(aFromSegment, aCountToSniffingLimit);
+ // the charset may have been set now
+ // maybe try chardet now;
+ if (mFeedChardet) {
+ bool dontFeed;
+ nsresult rv;
+ if (mSniffingBuffer) {
+ rv = mChardet->DoIt((const char*)mSniffingBuffer.get(), mSniffingLength, &dontFeed);
+ mFeedChardet = !dontFeed;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (mFeedChardet && aFromSegment) {
+ rv = mChardet->DoIt((const char*)aFromSegment,
+ // Avoid buffer boundary-dependent behavior when
+ // reparsing is forbidden. If reparse is forbidden,
+ // act as if we only saw the first 1024 bytes.
+ // When reparsing isn't forbidden, buffer boundaries
+ // can have an effect on whether the page is loaded
+ // once or twice. :-(
+ mReparseForbidden ? aCountToSniffingLimit : aCount,
+ &dontFeed);
+ mFeedChardet = !dontFeed;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (mFeedChardet && (!aFromSegment || mReparseForbidden)) {
+ // mReparseForbidden is checked so that we get to use the sniffing
+ // buffer with the best guess so far if we aren't allowed to guess
+ // better later.
+ mFeedChardet = false;
+ rv = mChardet->Done();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // fall thru; callback may have changed charset
+ }
+ if (mCharsetSource == kCharsetUninitialized) {
+ // Hopefully this case is never needed, but dealing with it anyway
+ mCharset.AssignLiteral("windows-1252");
+ mCharsetSource = kCharsetFromFallback;
+ mTreeBuilder->SetDocumentCharset(mCharset, mCharsetSource);
+ } else if (mMode == LOAD_AS_DATA &&
+ mCharsetSource == kCharsetFromFallback) {
+ NS_ASSERTION(mReparseForbidden, "Reparse should be forbidden for XHR");
+ NS_ASSERTION(!mFeedChardet, "Should not feed chardet for XHR");
+ NS_ASSERTION(mCharset.EqualsLiteral("UTF-8"),
+ "XHR should default to UTF-8");
+ // Now mark charset source as non-weak to signal that we have a decision
+ mCharsetSource = kCharsetFromDocTypeDefault;
+ mTreeBuilder->SetDocumentCharset(mCharset, mCharsetSource);
+ }
+ return SetupDecodingAndWriteSniffingBufferAndCurrentSegment(aFromSegment, aCount, aWriteCount);
+}
+
+nsresult
+nsHtml5StreamParser::SniffStreamBytes(const uint8_t* aFromSegment,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ nsresult rv = NS_OK;
+ uint32_t writeCount;
+
+ // mCharset and mCharsetSource potentially have come from channel or higher
+ // by now. If we find a BOM, SetupDecodingFromBom() will overwrite them.
+ // If we don't find a BOM, the previously set values of mCharset and
+ // mCharsetSource are not modified by the BOM sniffing here.
+ for (uint32_t i = 0; i < aCount && mBomState != BOM_SNIFFING_OVER; i++) {
+ switch (mBomState) {
+ case BOM_SNIFFING_NOT_STARTED:
+ NS_ASSERTION(i == 0, "Bad BOM sniffing state.");
+ switch (*aFromSegment) {
+ case 0xEF:
+ mBomState = SEEN_UTF_8_FIRST_BYTE;
+ break;
+ case 0xFF:
+ mBomState = SEEN_UTF_16_LE_FIRST_BYTE;
+ break;
+ case 0xFE:
+ mBomState = SEEN_UTF_16_BE_FIRST_BYTE;
+ break;
+ default:
+ mBomState = BOM_SNIFFING_OVER;
+ break;
+ }
+ break;
+ case SEEN_UTF_16_LE_FIRST_BYTE:
+ if (aFromSegment[i] == 0xFE) {
+ rv = SetupDecodingFromBom("UTF-16LE"); // upper case is the raw form
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t count = aCount - (i + 1);
+ rv = WriteStreamBytes(aFromSegment + (i + 1), count, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aWriteCount = writeCount + (i + 1);
+ return rv;
+ }
+ mBomState = BOM_SNIFFING_OVER;
+ break;
+ case SEEN_UTF_16_BE_FIRST_BYTE:
+ if (aFromSegment[i] == 0xFF) {
+ rv = SetupDecodingFromBom("UTF-16BE"); // upper case is the raw form
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t count = aCount - (i + 1);
+ rv = WriteStreamBytes(aFromSegment + (i + 1), count, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aWriteCount = writeCount + (i + 1);
+ return rv;
+ }
+ mBomState = BOM_SNIFFING_OVER;
+ break;
+ case SEEN_UTF_8_FIRST_BYTE:
+ if (aFromSegment[i] == 0xBB) {
+ mBomState = SEEN_UTF_8_SECOND_BYTE;
+ } else {
+ mBomState = BOM_SNIFFING_OVER;
+ }
+ break;
+ case SEEN_UTF_8_SECOND_BYTE:
+ if (aFromSegment[i] == 0xBF) {
+ rv = SetupDecodingFromBom("UTF-8"); // upper case is the raw form
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t count = aCount - (i + 1);
+ rv = WriteStreamBytes(aFromSegment + (i + 1), count, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aWriteCount = writeCount + (i + 1);
+ return rv;
+ }
+ mBomState = BOM_SNIFFING_OVER;
+ break;
+ default:
+ mBomState = BOM_SNIFFING_OVER;
+ break;
+ }
+ }
+ // if we get here, there either was no BOM or the BOM sniffing isn't complete
+ // yet
+
+ MOZ_ASSERT(mCharsetSource != kCharsetFromByteOrderMark,
+ "Should not come here if BOM was found.");
+ MOZ_ASSERT(mCharsetSource != kCharsetFromOtherComponent,
+ "kCharsetFromOtherComponent is for XSLT.");
+
+ if (mBomState == BOM_SNIFFING_OVER &&
+ mCharsetSource == kCharsetFromChannel) {
+ // There was no BOM and the charset came from channel. mCharset
+ // still contains the charset from the channel as set by an
+ // earlier call to SetDocumentCharset(), since we didn't find a BOM and
+ // overwrite mCharset. (Note that if the user has overridden the charset,
+ // we don't come here but check <meta> for XSS-dangerous charsets first.)
+ mFeedChardet = false;
+ mTreeBuilder->SetDocumentCharset(mCharset, mCharsetSource);
+ return SetupDecodingAndWriteSniffingBufferAndCurrentSegment(aFromSegment,
+ aCount, aWriteCount);
+ }
+
+ if (!mMetaScanner && (mMode == NORMAL ||
+ mMode == VIEW_SOURCE_HTML ||
+ mMode == LOAD_AS_DATA)) {
+ mMetaScanner = new nsHtml5MetaScanner(mTreeBuilder);
+ }
+
+ if (mSniffingLength + aCount >= NS_HTML5_STREAM_PARSER_SNIFFING_BUFFER_SIZE) {
+ // this is the last buffer
+ uint32_t countToSniffingLimit =
+ NS_HTML5_STREAM_PARSER_SNIFFING_BUFFER_SIZE - mSniffingLength;
+ if (mMode == NORMAL || mMode == VIEW_SOURCE_HTML || mMode == LOAD_AS_DATA) {
+ nsHtml5ByteReadable readable(aFromSegment, aFromSegment +
+ countToSniffingLimit);
+ nsAutoCString encoding;
+ mMetaScanner->sniff(&readable, encoding);
+ // Due to the way nsHtml5Portability reports OOM, ask the tree buider
+ nsresult rv;
+ if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
+ MarkAsBroken(rv);
+ return rv;
+ }
+ if (!encoding.IsEmpty()) {
+ // meta scan successful; honor overrides unless meta is XSS-dangerous
+ if ((mCharsetSource == kCharsetFromParentForced ||
+ mCharsetSource == kCharsetFromUserForced) &&
+ EncodingUtils::IsAsciiCompatible(encoding)) {
+ // Honor override
+ return SetupDecodingAndWriteSniffingBufferAndCurrentSegment(
+ aFromSegment, aCount, aWriteCount);
+ }
+ mCharset.Assign(encoding);
+ mCharsetSource = kCharsetFromMetaPrescan;
+ mFeedChardet = false;
+ mTreeBuilder->SetDocumentCharset(mCharset, mCharsetSource);
+ return SetupDecodingAndWriteSniffingBufferAndCurrentSegment(
+ aFromSegment, aCount, aWriteCount);
+ }
+ }
+ if (mCharsetSource == kCharsetFromParentForced ||
+ mCharsetSource == kCharsetFromUserForced) {
+ // meta not found, honor override
+ return SetupDecodingAndWriteSniffingBufferAndCurrentSegment(
+ aFromSegment, aCount, aWriteCount);
+ }
+ return FinalizeSniffing(aFromSegment, aCount, aWriteCount,
+ countToSniffingLimit);
+ }
+
+ // not the last buffer
+ if (mMode == NORMAL || mMode == VIEW_SOURCE_HTML || mMode == LOAD_AS_DATA) {
+ nsHtml5ByteReadable readable(aFromSegment, aFromSegment + aCount);
+ nsAutoCString encoding;
+ mMetaScanner->sniff(&readable, encoding);
+ // Due to the way nsHtml5Portability reports OOM, ask the tree buider
+ nsresult rv;
+ if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
+ MarkAsBroken(rv);
+ return rv;
+ }
+ if (!encoding.IsEmpty()) {
+ // meta scan successful; honor overrides unless meta is XSS-dangerous
+ if ((mCharsetSource == kCharsetFromParentForced ||
+ mCharsetSource == kCharsetFromUserForced) &&
+ EncodingUtils::IsAsciiCompatible(encoding)) {
+ // Honor override
+ return SetupDecodingAndWriteSniffingBufferAndCurrentSegment(aFromSegment,
+ aCount, aWriteCount);
+ }
+ mCharset.Assign(encoding);
+ mCharsetSource = kCharsetFromMetaPrescan;
+ mFeedChardet = false;
+ mTreeBuilder->SetDocumentCharset(mCharset, mCharsetSource);
+ return SetupDecodingAndWriteSniffingBufferAndCurrentSegment(aFromSegment,
+ aCount, aWriteCount);
+ }
+ }
+
+ if (!mSniffingBuffer) {
+ mSniffingBuffer =
+ MakeUniqueFallible<uint8_t[]>(NS_HTML5_STREAM_PARSER_SNIFFING_BUFFER_SIZE);
+ if (!mSniffingBuffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ memcpy(&mSniffingBuffer[mSniffingLength], aFromSegment, aCount);
+ mSniffingLength += aCount;
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+nsresult
+nsHtml5StreamParser::WriteStreamBytes(const uint8_t* aFromSegment,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ // mLastBuffer should always point to a buffer of the size
+ // NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE.
+ if (!mLastBuffer) {
+ NS_WARNING("mLastBuffer should not be null!");
+ MarkAsBroken(NS_ERROR_NULL_POINTER);
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (mLastBuffer->getEnd() == NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE) {
+ RefPtr<nsHtml5OwningUTF16Buffer> newBuf =
+ nsHtml5OwningUTF16Buffer::FalliblyCreate(
+ NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE);
+ if (!newBuf) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mLastBuffer = (mLastBuffer->next = newBuf.forget());
+ }
+ int32_t totalByteCount = 0;
+ for (;;) {
+ int32_t end = mLastBuffer->getEnd();
+ int32_t byteCount = aCount - totalByteCount;
+ int32_t utf16Count = NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE - end;
+
+ NS_ASSERTION(utf16Count, "Trying to convert into a buffer with no free space!");
+ // byteCount may be zero to force the decoder to output a pending surrogate
+ // pair.
+
+ nsresult convResult = mUnicodeDecoder->Convert((const char*)aFromSegment, &byteCount, mLastBuffer->getBuffer() + end, &utf16Count);
+ MOZ_ASSERT(NS_SUCCEEDED(convResult));
+
+ end += utf16Count;
+ mLastBuffer->setEnd(end);
+ totalByteCount += byteCount;
+ aFromSegment += byteCount;
+
+ NS_ASSERTION(end <= NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE,
+ "The Unicode decoder wrote too much data.");
+ NS_ASSERTION(byteCount >= -1, "The decoder consumed fewer than -1 bytes.");
+
+ if (convResult == NS_PARTIAL_MORE_OUTPUT) {
+ RefPtr<nsHtml5OwningUTF16Buffer> newBuf =
+ nsHtml5OwningUTF16Buffer::FalliblyCreate(
+ NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE);
+ if (!newBuf) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mLastBuffer = (mLastBuffer->next = newBuf.forget());
+ // All input may have been consumed if there is a pending surrogate pair
+ // that doesn't fit in the output buffer. Loop back to push a zero-length
+ // input to the decoder in that case.
+ } else {
+ NS_ASSERTION(totalByteCount == (int32_t)aCount,
+ "The Unicode decoder consumed the wrong number of bytes.");
+ *aWriteCount = (uint32_t)totalByteCount;
+ return NS_OK;
+ }
+ }
+}
+
+nsresult
+nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ NS_PRECONDITION(STREAM_NOT_STARTED == mStreamState,
+ "Got OnStartRequest when the stream had already started.");
+ NS_PRECONDITION(!mExecutor->HasStarted(),
+ "Got OnStartRequest at the wrong stage in the executor life cycle.");
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ if (mObserver) {
+ mObserver->OnStartRequest(aRequest, aContext);
+ }
+ mRequest = aRequest;
+
+ mStreamState = STREAM_BEING_READ;
+
+ if (mMode == VIEW_SOURCE_HTML || mMode == VIEW_SOURCE_XML) {
+ mTokenizer->StartViewSource(NS_ConvertUTF8toUTF16(mViewSourceTitle));
+ }
+
+ // For View Source, the parser should run with scripts "enabled" if a normal
+ // load would have scripts enabled.
+ bool scriptingEnabled = mMode == LOAD_AS_DATA ?
+ false : mExecutor->IsScriptEnabled();
+ mOwner->StartTokenizer(scriptingEnabled);
+
+ bool isSrcdoc = false;
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = GetChannel(getter_AddRefs(channel));
+ if (NS_SUCCEEDED(rv)) {
+ isSrcdoc = NS_IsSrcdocChannel(channel);
+ }
+ mTreeBuilder->setIsSrcdocDocument(isSrcdoc);
+ mTreeBuilder->setScriptingEnabled(scriptingEnabled);
+ mTreeBuilder->SetPreventScriptExecution(!((mMode == NORMAL) &&
+ scriptingEnabled));
+ mTokenizer->start();
+ mExecutor->Start();
+ mExecutor->StartReadingFromStage();
+
+ if (mMode == PLAIN_TEXT) {
+ mTreeBuilder->StartPlainText();
+ mTokenizer->StartPlainText();
+ } else if (mMode == VIEW_SOURCE_PLAIN) {
+ nsAutoString viewSourceTitle;
+ CopyUTF8toUTF16(mViewSourceTitle, viewSourceTitle);
+ mTreeBuilder->EnsureBufferSpace(viewSourceTitle.Length());
+ mTreeBuilder->StartPlainTextViewSource(viewSourceTitle);
+ mTokenizer->StartPlainText();
+ }
+
+ /*
+ * If you move the following line, be very careful not to cause
+ * WillBuildModel to be called before the document has had its
+ * script global object set.
+ */
+ rv = mExecutor->WillBuildModel(eDTDMode_unknown);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsHtml5OwningUTF16Buffer> newBuf =
+ nsHtml5OwningUTF16Buffer::FalliblyCreate(
+ NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE);
+ if (!newBuf) {
+ // marks this stream parser as terminated,
+ // which prevents entry to code paths that
+ // would use mFirstBuffer or mLastBuffer.
+ return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+ }
+ NS_ASSERTION(!mFirstBuffer, "How come we have the first buffer set?");
+ NS_ASSERTION(!mLastBuffer, "How come we have the last buffer set?");
+ mFirstBuffer = mLastBuffer = newBuf;
+
+ rv = NS_OK;
+
+ // The line below means that the encoding can end up being wrong if
+ // a view-source URL is loaded without having the encoding hint from a
+ // previous normal load in the history.
+ mReparseForbidden = !(mMode == NORMAL || mMode == PLAIN_TEXT);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mRequest, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString method;
+ httpChannel->GetRequestMethod(method);
+ // XXX does Necko have a way to renavigate POST, etc. without hitting
+ // the network?
+ if (!method.EqualsLiteral("GET")) {
+ // This is the old Gecko behavior but the HTML5 spec disagrees.
+ // Don't reparse on POST.
+ mReparseForbidden = true;
+ mFeedChardet = false; // can't restart anyway
+ }
+ }
+
+ // Attempt to retarget delivery of data (via OnDataAvailable) to the parser
+ // thread, rather than through the main thread.
+ nsCOMPtr<nsIThreadRetargetableRequest> threadRetargetableRequest =
+ do_QueryInterface(mRequest, &rv);
+ if (threadRetargetableRequest) {
+ rv = threadRetargetableRequest->RetargetDeliveryTo(mThread);
+ }
+
+ if (NS_FAILED(rv)) {
+ // for now skip warning if we're on child process, since we don't support
+ // off-main thread delivery there yet. This will change with bug 1015466
+ if (!XRE_IsContentProcess()) {
+ NS_WARNING("Failed to retarget HTML data delivery to the parser thread.");
+ }
+ }
+
+ if (mCharsetSource == kCharsetFromParentFrame) {
+ // Remember this in case chardet overwrites mCharsetSource
+ mInitialEncodingWasFromParentFrame = true;
+ }
+
+ if (mCharsetSource >= kCharsetFromAutoDetection) {
+ mFeedChardet = false;
+ }
+
+ nsCOMPtr<nsIWyciwygChannel> wyciwygChannel(do_QueryInterface(mRequest));
+ if (!wyciwygChannel) {
+ // we aren't ready to commit to an encoding yet
+ // leave converter uninstantiated for now
+ return NS_OK;
+ }
+
+ // We are reloading a document.open()ed doc.
+ mReparseForbidden = true;
+ mFeedChardet = false;
+
+ // Instantiate the converter here to avoid BOM sniffing.
+ mUnicodeDecoder = EncodingUtils::DecoderForEncoding(mCharset);
+ return NS_OK;
+}
+
+nsresult
+nsHtml5StreamParser::CheckListenerChain()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
+ if (!mObserver) {
+ return NS_OK;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable =
+ do_QueryInterface(mObserver, &rv);
+ if (NS_SUCCEEDED(rv) && retargetable) {
+ rv = retargetable->CheckListenerChain();
+ }
+ return rv;
+}
+
+void
+nsHtml5StreamParser::DoStopRequest()
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ NS_PRECONDITION(STREAM_BEING_READ == mStreamState,
+ "Stream ended without being open.");
+ mTokenizerMutex.AssertCurrentThreadOwns();
+
+ if (IsTerminated()) {
+ return;
+ }
+
+ mStreamState = STREAM_ENDED;
+
+ if (!mUnicodeDecoder) {
+ uint32_t writeCount;
+ nsresult rv;
+ if (NS_FAILED(rv = FinalizeSniffing(nullptr, 0, &writeCount, 0))) {
+ MarkAsBroken(rv);
+ return;
+ }
+ } else if (mFeedChardet) {
+ mChardet->Done();
+ }
+
+ if (IsTerminatedOrInterrupted()) {
+ return;
+ }
+
+ ParseAvailableData();
+}
+
+class nsHtml5RequestStopper : public Runnable
+{
+ private:
+ nsHtml5RefPtr<nsHtml5StreamParser> mStreamParser;
+ public:
+ explicit nsHtml5RequestStopper(nsHtml5StreamParser* aStreamParser)
+ : mStreamParser(aStreamParser)
+ {}
+ NS_IMETHOD Run() override
+ {
+ mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex);
+ mStreamParser->DoStopRequest();
+ return NS_OK;
+ }
+};
+
+nsresult
+nsHtml5StreamParser::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult status)
+{
+ NS_ASSERTION(mRequest == aRequest, "Got Stop on wrong stream.");
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ if (mObserver) {
+ mObserver->OnStopRequest(aRequest, aContext, status);
+ }
+ nsCOMPtr<nsIRunnable> stopper = new nsHtml5RequestStopper(this);
+ if (NS_FAILED(mThread->Dispatch(stopper, nsIThread::DISPATCH_NORMAL))) {
+ NS_WARNING("Dispatching StopRequest event failed.");
+ }
+ return NS_OK;
+}
+
+void
+nsHtml5StreamParser::DoDataAvailable(const uint8_t* aBuffer, uint32_t aLength)
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ NS_PRECONDITION(STREAM_BEING_READ == mStreamState,
+ "DoDataAvailable called when stream not open.");
+ mTokenizerMutex.AssertCurrentThreadOwns();
+
+ if (IsTerminated()) {
+ return;
+ }
+
+ uint32_t writeCount;
+ nsresult rv;
+ if (HasDecoder()) {
+ if (mFeedChardet) {
+ bool dontFeed;
+ mChardet->DoIt((const char*)aBuffer, aLength, &dontFeed);
+ mFeedChardet = !dontFeed;
+ }
+ rv = WriteStreamBytes(aBuffer, aLength, &writeCount);
+ } else {
+ rv = SniffStreamBytes(aBuffer, aLength, &writeCount);
+ }
+ if (NS_FAILED(rv)) {
+ MarkAsBroken(rv);
+ return;
+ }
+ NS_ASSERTION(writeCount == aLength, "Wrong number of stream bytes written/sniffed.");
+
+ if (IsTerminatedOrInterrupted()) {
+ return;
+ }
+
+ ParseAvailableData();
+
+ if (mFlushTimerArmed || mSpeculating) {
+ return;
+ }
+
+ mFlushTimer->InitWithFuncCallback(nsHtml5StreamParser::TimerCallback,
+ static_cast<void*> (this),
+ mFlushTimerEverFired ?
+ sTimerInitialDelay :
+ sTimerSubsequentDelay,
+ nsITimer::TYPE_ONE_SHOT);
+ mFlushTimerArmed = true;
+}
+
+class nsHtml5DataAvailable : public Runnable
+{
+ private:
+ nsHtml5RefPtr<nsHtml5StreamParser> mStreamParser;
+ UniquePtr<uint8_t[]> mData;
+ uint32_t mLength;
+ public:
+ nsHtml5DataAvailable(nsHtml5StreamParser* aStreamParser,
+ UniquePtr<uint8_t[]> aData,
+ uint32_t aLength)
+ : mStreamParser(aStreamParser)
+ , mData(Move(aData))
+ , mLength(aLength)
+ {}
+ NS_IMETHOD Run() override
+ {
+ mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex);
+ mStreamParser->DoDataAvailable(mData.get(), mLength);
+ return NS_OK;
+ }
+};
+
+nsresult
+nsHtml5StreamParser::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength)
+{
+ nsresult rv;
+
+ NS_ASSERTION(mRequest == aRequest, "Got data on wrong stream.");
+ uint32_t totalRead;
+ // Main thread to parser thread dispatch requires copying to buffer first.
+ if (NS_IsMainThread()) {
+ if (NS_FAILED(rv = mExecutor->IsBroken())) {
+ return rv;
+ }
+ auto data = MakeUniqueFallible<uint8_t[]>(aLength);
+ if (!data) {
+ return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+ }
+ rv = aInStream->Read(reinterpret_cast<char*>(data.get()),
+ aLength, &totalRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(totalRead <= aLength, "Read more bytes than were available?");
+
+ nsCOMPtr<nsIRunnable> dataAvailable = new nsHtml5DataAvailable(this,
+ Move(data),
+ totalRead);
+ if (NS_FAILED(mThread->Dispatch(dataAvailable, nsIThread::DISPATCH_NORMAL))) {
+ NS_WARNING("Dispatching DataAvailable event failed.");
+ }
+ return rv;
+ } else {
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ mozilla::MutexAutoLock autoLock(mTokenizerMutex);
+
+ if (NS_FAILED(rv = mTreeBuilder->IsBroken())) {
+ return rv;
+ }
+ // Read directly from response buffer.
+ rv = aInStream->ReadSegments(CopySegmentsToParser, this, aLength,
+ &totalRead);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed reading response data to parser");
+ return rv;
+ }
+ return NS_OK;
+ }
+}
+
+// Called under lock by function ptr
+/* static */ nsresult
+nsHtml5StreamParser::CopySegmentsToParser(nsIInputStream *aInStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ nsHtml5StreamParser* parser = static_cast<nsHtml5StreamParser*>(aClosure);
+
+ parser->DoDataAvailable((const uint8_t*)aFromSegment, aCount);
+ // Assume DoDataAvailable consumed all available bytes.
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+bool
+nsHtml5StreamParser::PreferredForInternalEncodingDecl(nsACString& aEncoding)
+{
+ nsAutoCString newEncoding;
+ if (!EncodingUtils::FindEncodingForLabel(aEncoding, newEncoding)) {
+ // the encoding name is bogus
+ mTreeBuilder->MaybeComplainAboutCharset("EncMetaUnsupported",
+ true,
+ mTokenizer->getLineNumber());
+ return false;
+ }
+
+ if (newEncoding.EqualsLiteral("UTF-16BE") ||
+ newEncoding.EqualsLiteral("UTF-16LE")) {
+ mTreeBuilder->MaybeComplainAboutCharset("EncMetaUtf16",
+ true,
+ mTokenizer->getLineNumber());
+ newEncoding.AssignLiteral("UTF-8");
+ }
+
+ if (newEncoding.EqualsLiteral("x-user-defined")) {
+ // WebKit/Blink hack for Indian and Armenian legacy sites
+ mTreeBuilder->MaybeComplainAboutCharset("EncMetaUserDefined",
+ true,
+ mTokenizer->getLineNumber());
+ newEncoding.AssignLiteral("windows-1252");
+ }
+
+ if (newEncoding.Equals(mCharset)) {
+ if (mCharsetSource < kCharsetFromMetaPrescan) {
+ if (mInitialEncodingWasFromParentFrame) {
+ mTreeBuilder->MaybeComplainAboutCharset("EncLateMetaFrame",
+ false,
+ mTokenizer->getLineNumber());
+ } else {
+ mTreeBuilder->MaybeComplainAboutCharset("EncLateMeta",
+ false,
+ mTokenizer->getLineNumber());
+ }
+ }
+ mCharsetSource = kCharsetFromMetaTag; // become confident
+ mFeedChardet = false; // don't feed chardet when confident
+ return false;
+ }
+
+ aEncoding.Assign(newEncoding);
+ return true;
+}
+
+bool
+nsHtml5StreamParser::internalEncodingDeclaration(nsHtml5String aEncoding)
+{
+ // This code needs to stay in sync with
+ // nsHtml5MetaScanner::tryCharset. Unfortunately, the
+ // trickery with member fields there leads to some copy-paste reuse. :-(
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ if (mCharsetSource >= kCharsetFromMetaTag) { // this threshold corresponds to "confident" in the HTML5 spec
+ return false;
+ }
+ nsString newEncoding16; // Not Auto, because using it to hold nsStringBuffer*
+ aEncoding.ToString(newEncoding16);
+ nsAutoCString newEncoding;
+ CopyUTF16toUTF8(newEncoding16, newEncoding);
+
+ if (!PreferredForInternalEncodingDecl(newEncoding)) {
+ return false;
+ }
+
+ if (mReparseForbidden) {
+ // This mReparseForbidden check happens after the call to
+ // PreferredForInternalEncodingDecl so that if that method calls
+ // MaybeComplainAboutCharset, its charset complaint wins over the one
+ // below.
+ mTreeBuilder->MaybeComplainAboutCharset("EncLateMetaTooLate",
+ true,
+ mTokenizer->getLineNumber());
+ return false; // not reparsing even if we wanted to
+ }
+
+ // Avoid having the chardet ask for another restart after this restart
+ // request.
+ mFeedChardet = false;
+ mTreeBuilder->NeedsCharsetSwitchTo(newEncoding,
+ kCharsetFromMetaTag,
+ mTokenizer->getLineNumber());
+ FlushTreeOpsAndDisarmTimer();
+ Interrupt();
+ // the tree op executor will cause the stream parser to terminate
+ // if the charset switch request is accepted or it'll uninterrupt
+ // if the request failed. Note that if the restart request fails,
+ // we don't bother trying to make chardet resume. Might as well
+ // assume that chardet-requested restarts would fail, too.
+ return true;
+}
+
+void
+nsHtml5StreamParser::FlushTreeOpsAndDisarmTimer()
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ if (mFlushTimerArmed) {
+ // avoid calling Cancel if the flush timer isn't armed to avoid acquiring
+ // a mutex
+ mFlushTimer->Cancel();
+ mFlushTimerArmed = false;
+ }
+ if (mMode == VIEW_SOURCE_HTML || mMode == VIEW_SOURCE_XML) {
+ mTokenizer->FlushViewSource();
+ }
+ mTreeBuilder->Flush();
+ if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
+ NS_WARNING("failed to dispatch executor flush event");
+ }
+}
+
+void
+nsHtml5StreamParser::ParseAvailableData()
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ mTokenizerMutex.AssertCurrentThreadOwns();
+
+ if (IsTerminatedOrInterrupted()) {
+ return;
+ }
+
+ if (mSpeculating && !IsSpeculationEnabled()) {
+ return;
+ }
+
+ for (;;) {
+ if (!mFirstBuffer->hasMore()) {
+ if (mFirstBuffer == mLastBuffer) {
+ switch (mStreamState) {
+ case STREAM_BEING_READ:
+ // never release the last buffer.
+ if (!mSpeculating) {
+ // reuse buffer space if not speculating
+ mFirstBuffer->setStart(0);
+ mFirstBuffer->setEnd(0);
+ }
+ mTreeBuilder->FlushLoads();
+ // Dispatch this runnable unconditionally, because the loads
+ // that need flushing may have been flushed earlier even if the
+ // flush right above here did nothing.
+ if (NS_FAILED(NS_DispatchToMainThread(mLoadFlusher))) {
+ NS_WARNING("failed to dispatch load flush event");
+ }
+ return; // no more data for now but expecting more
+ case STREAM_ENDED:
+ if (mAtEOF) {
+ return;
+ }
+ mAtEOF = true;
+ if (mCharsetSource < kCharsetFromMetaTag) {
+ if (mInitialEncodingWasFromParentFrame) {
+ // Unfortunately, this check doesn't take effect for
+ // cross-origin frames, so cross-origin ad frames that have
+ // no text and only an image or a Flash embed get the more
+ // severe message from the next if block. The message is
+ // technically accurate, though.
+ mTreeBuilder->MaybeComplainAboutCharset("EncNoDeclarationFrame",
+ false,
+ 0);
+ } else if (mMode == NORMAL) {
+ mTreeBuilder->MaybeComplainAboutCharset("EncNoDeclaration",
+ true,
+ 0);
+ } else if (mMode == PLAIN_TEXT) {
+ mTreeBuilder->MaybeComplainAboutCharset("EncNoDeclarationPlain",
+ true,
+ 0);
+ }
+ }
+ if (NS_SUCCEEDED(mTreeBuilder->IsBroken())) {
+ mTokenizer->eof();
+ nsresult rv;
+ if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
+ MarkAsBroken(rv);
+ } else {
+ mTreeBuilder->StreamEnded();
+ if (mMode == VIEW_SOURCE_HTML || mMode == VIEW_SOURCE_XML) {
+ mTokenizer->EndViewSource();
+ }
+ }
+ }
+ FlushTreeOpsAndDisarmTimer();
+ return; // no more data and not expecting more
+ default:
+ NS_NOTREACHED("It should be impossible to reach this.");
+ return;
+ }
+ }
+ mFirstBuffer = mFirstBuffer->next;
+ continue;
+ }
+
+ // now we have a non-empty buffer
+ mFirstBuffer->adjust(mLastWasCR);
+ mLastWasCR = false;
+ if (mFirstBuffer->hasMore()) {
+ if (!mTokenizer->EnsureBufferSpace(mFirstBuffer->getLength())) {
+ MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ mLastWasCR = mTokenizer->tokenizeBuffer(mFirstBuffer);
+ nsresult rv;
+ if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
+ MarkAsBroken(rv);
+ return;
+ }
+ // At this point, internalEncodingDeclaration() may have called
+ // Terminate, but that never happens together with script.
+ // Can't assert that here, though, because it's possible that the main
+ // thread has called Terminate() while this thread was parsing.
+ if (mTreeBuilder->HasScript()) {
+ // HasScript() cannot return true if the tree builder is preventing
+ // script execution.
+ MOZ_ASSERT(mMode == NORMAL);
+ mozilla::MutexAutoLock speculationAutoLock(mSpeculationMutex);
+ nsHtml5Speculation* speculation =
+ new nsHtml5Speculation(mFirstBuffer,
+ mFirstBuffer->getStart(),
+ mTokenizer->getLineNumber(),
+ mTreeBuilder->newSnapshot());
+ mTreeBuilder->AddSnapshotToScript(speculation->GetSnapshot(),
+ speculation->GetStartLineNumber());
+ FlushTreeOpsAndDisarmTimer();
+ mTreeBuilder->SetOpSink(speculation);
+ mSpeculations.AppendElement(speculation); // adopts the pointer
+ mSpeculating = true;
+ }
+ if (IsTerminatedOrInterrupted()) {
+ return;
+ }
+ }
+ continue;
+ }
+}
+
+class nsHtml5StreamParserContinuation : public Runnable
+{
+private:
+ nsHtml5RefPtr<nsHtml5StreamParser> mStreamParser;
+public:
+ explicit nsHtml5StreamParserContinuation(nsHtml5StreamParser* aStreamParser)
+ : mStreamParser(aStreamParser)
+ {}
+ NS_IMETHOD Run() override
+ {
+ mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex);
+ mStreamParser->Uninterrupt();
+ mStreamParser->ParseAvailableData();
+ return NS_OK;
+ }
+};
+
+void
+nsHtml5StreamParser::ContinueAfterScripts(nsHtml5Tokenizer* aTokenizer,
+ nsHtml5TreeBuilder* aTreeBuilder,
+ bool aLastWasCR)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ NS_ASSERTION(!(mMode == VIEW_SOURCE_HTML || mMode == VIEW_SOURCE_XML),
+ "ContinueAfterScripts called in view source mode!");
+ if (NS_FAILED(mExecutor->IsBroken())) {
+ return;
+ }
+ #ifdef DEBUG
+ mExecutor->AssertStageEmpty();
+ #endif
+ bool speculationFailed = false;
+ {
+ mozilla::MutexAutoLock speculationAutoLock(mSpeculationMutex);
+ if (mSpeculations.IsEmpty()) {
+ NS_NOTREACHED("ContinueAfterScripts called without speculations.");
+ return;
+ }
+ nsHtml5Speculation* speculation = mSpeculations.ElementAt(0);
+ if (aLastWasCR ||
+ !aTokenizer->isInDataState() ||
+ !aTreeBuilder->snapshotMatches(speculation->GetSnapshot())) {
+ speculationFailed = true;
+ // We've got a failed speculation :-(
+ MaybeDisableFutureSpeculation();
+ Interrupt(); // Make the parser thread release the tokenizer mutex sooner
+ // now fall out of the speculationAutoLock into the tokenizerAutoLock block
+ } else {
+ // We've got a successful speculation!
+ if (mSpeculations.Length() > 1) {
+ // the first speculation isn't the current speculation, so there's
+ // no need to bother the parser thread.
+ speculation->FlushToSink(mExecutor);
+ NS_ASSERTION(!mExecutor->IsScriptExecuting(),
+ "ParseUntilBlocked() was supposed to ensure we don't come "
+ "here when scripts are executing.");
+ NS_ASSERTION(mExecutor->IsInFlushLoop(), "How are we here if "
+ "RunFlushLoop() didn't call ParseUntilBlocked() which is the "
+ "only caller of this method?");
+ mSpeculations.RemoveElementAt(0);
+ return;
+ }
+ // else
+ Interrupt(); // Make the parser thread release the tokenizer mutex sooner
+
+ // now fall through
+ // the first speculation is the current speculation. Need to
+ // release the the speculation mutex and acquire the tokenizer
+ // mutex. (Just acquiring the other mutex here would deadlock)
+ }
+ }
+ {
+ mozilla::MutexAutoLock tokenizerAutoLock(mTokenizerMutex);
+ #ifdef DEBUG
+ {
+ nsCOMPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ mAtomTable.SetPermittedLookupThread(mainThread);
+ }
+ #endif
+ // In principle, the speculation mutex should be acquired here,
+ // but there's no point, because the parser thread only acquires it
+ // when it has also acquired the tokenizer mutex and we are already
+ // holding the tokenizer mutex.
+ if (speculationFailed) {
+ // Rewind the stream
+ mAtEOF = false;
+ nsHtml5Speculation* speculation = mSpeculations.ElementAt(0);
+ mFirstBuffer = speculation->GetBuffer();
+ mFirstBuffer->setStart(speculation->GetStart());
+ mTokenizer->setLineNumber(speculation->GetStartLineNumber());
+
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("DOM Events"),
+ mExecutor->GetDocument(),
+ nsContentUtils::eDOM_PROPERTIES,
+ "SpeculationFailed",
+ nullptr, 0,
+ nullptr,
+ EmptyString(),
+ speculation->GetStartLineNumber());
+
+ nsHtml5OwningUTF16Buffer* buffer = mFirstBuffer->next;
+ while (buffer) {
+ buffer->setStart(0);
+ buffer = buffer->next;
+ }
+
+ mSpeculations.Clear(); // potentially a huge number of destructors
+ // run here synchronously on the main thread...
+
+ mTreeBuilder->flushCharacters(); // empty the pending buffer
+ mTreeBuilder->ClearOps(); // now get rid of the failed ops
+
+ mTreeBuilder->SetOpSink(mExecutor->GetStage());
+ mExecutor->StartReadingFromStage();
+ mSpeculating = false;
+
+ // Copy state over
+ mLastWasCR = aLastWasCR;
+ mTokenizer->loadState(aTokenizer);
+ mTreeBuilder->loadState(aTreeBuilder, &mAtomTable);
+ } else {
+ // We've got a successful speculation and at least a moment ago it was
+ // the current speculation
+ mSpeculations.ElementAt(0)->FlushToSink(mExecutor);
+ NS_ASSERTION(!mExecutor->IsScriptExecuting(),
+ "ParseUntilBlocked() was supposed to ensure we don't come "
+ "here when scripts are executing.");
+ NS_ASSERTION(mExecutor->IsInFlushLoop(), "How are we here if "
+ "RunFlushLoop() didn't call ParseUntilBlocked() which is the "
+ "only caller of this method?");
+ mSpeculations.RemoveElementAt(0);
+ if (mSpeculations.IsEmpty()) {
+ // yes, it was still the only speculation. Now stop speculating
+ // However, before telling the executor to read from stage, flush
+ // any pending ops straight to the executor, because otherwise
+ // they remain unflushed until we get more data from the network.
+ mTreeBuilder->SetOpSink(mExecutor);
+ mTreeBuilder->Flush(true);
+ mTreeBuilder->SetOpSink(mExecutor->GetStage());
+ mExecutor->StartReadingFromStage();
+ mSpeculating = false;
+ }
+ }
+ nsCOMPtr<nsIRunnable> event = new nsHtml5StreamParserContinuation(this);
+ if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
+ NS_WARNING("Failed to dispatch nsHtml5StreamParserContinuation");
+ }
+ // A stream event might run before this event runs, but that's harmless.
+ #ifdef DEBUG
+ mAtomTable.SetPermittedLookupThread(mThread);
+ #endif
+ }
+}
+
+void
+nsHtml5StreamParser::ContinueAfterFailedCharsetSwitch()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ nsCOMPtr<nsIRunnable> event = new nsHtml5StreamParserContinuation(this);
+ if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
+ NS_WARNING("Failed to dispatch nsHtml5StreamParserContinuation");
+ }
+}
+
+class nsHtml5TimerKungFu : public Runnable
+{
+private:
+ nsHtml5RefPtr<nsHtml5StreamParser> mStreamParser;
+public:
+ explicit nsHtml5TimerKungFu(nsHtml5StreamParser* aStreamParser)
+ : mStreamParser(aStreamParser)
+ {}
+ NS_IMETHOD Run() override
+ {
+ if (mStreamParser->mFlushTimer) {
+ mStreamParser->mFlushTimer->Cancel();
+ mStreamParser->mFlushTimer = nullptr;
+ }
+ return NS_OK;
+ }
+};
+
+void
+nsHtml5StreamParser::DropTimer()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ /*
+ * Simply nulling out the timer wouldn't work, because if the timer is
+ * armed, it needs to be canceled first. Simply canceling it first wouldn't
+ * work, because nsTimerImpl::Cancel is not safe for calling from outside
+ * the thread where nsTimerImpl::Fire would run. It's not safe to
+ * dispatch a runnable to cancel the timer from the destructor of this
+ * class, because the timer has a weak (void*) pointer back to this instance
+ * of the stream parser and having the timer fire before the runnable
+ * cancels it would make the timer access a deleted object.
+ *
+ * This DropTimer method addresses these issues. This method must be called
+ * on the main thread before the destructor of this class is reached.
+ * The nsHtml5TimerKungFu object has an nsHtml5RefPtr that addrefs this
+ * stream parser object to keep it alive until the runnable is done.
+ * The runnable cancels the timer on the parser thread, drops the timer
+ * and lets nsHtml5RefPtr send a runnable back to the main thread to
+ * release the stream parser.
+ */
+ if (mFlushTimer) {
+ nsCOMPtr<nsIRunnable> event = new nsHtml5TimerKungFu(this);
+ if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
+ NS_WARNING("Failed to dispatch TimerKungFu event");
+ }
+ }
+}
+
+// Using a static, because the method name Notify is taken by the chardet
+// callback.
+void
+nsHtml5StreamParser::TimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ (static_cast<nsHtml5StreamParser*> (aClosure))->TimerFlush();
+}
+
+void
+nsHtml5StreamParser::TimerFlush()
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ mozilla::MutexAutoLock autoLock(mTokenizerMutex);
+
+ NS_ASSERTION(!mSpeculating, "Flush timer fired while speculating.");
+
+ // The timer fired if we got here. No need to cancel it. Mark it as
+ // not armed, though.
+ mFlushTimerArmed = false;
+
+ mFlushTimerEverFired = true;
+
+ if (IsTerminatedOrInterrupted()) {
+ return;
+ }
+
+ if (mMode == VIEW_SOURCE_HTML || mMode == VIEW_SOURCE_XML) {
+ mTreeBuilder->Flush(); // delete useless ops
+ if (mTokenizer->FlushViewSource()) {
+ if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
+ NS_WARNING("failed to dispatch executor flush event");
+ }
+ }
+ } else {
+ // we aren't speculating and we don't know when new data is
+ // going to arrive. Send data to the main thread.
+ if (mTreeBuilder->Flush(true)) {
+ if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
+ NS_WARNING("failed to dispatch executor flush event");
+ }
+ }
+ }
+}
+
+void
+nsHtml5StreamParser::MarkAsBroken(nsresult aRv)
+{
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ mTokenizerMutex.AssertCurrentThreadOwns();
+
+ Terminate();
+ mTreeBuilder->MarkAsBroken(aRv);
+ mozilla::DebugOnly<bool> hadOps = mTreeBuilder->Flush(false);
+ NS_ASSERTION(hadOps, "Should have had the markAsBroken op!");
+ if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
+ NS_WARNING("failed to dispatch executor flush event");
+ }
+}
diff --git a/components/htmlfive/nsHtml5StreamParser.h b/components/htmlfive/nsHtml5StreamParser.h
new file mode 100644
index 000000000..2560f84ab
--- /dev/null
+++ b/components/htmlfive/nsHtml5StreamParser.h
@@ -0,0 +1,579 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsHtml5StreamParser_h
+#define nsHtml5StreamParser_h
+
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsICharsetDetectionObserver.h"
+#include "nsHtml5MetaScanner.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "nsHtml5OwningUTF16Buffer.h"
+#include "nsIInputStream.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/UniquePtr.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5Speculation.h"
+#include "nsITimer.h"
+#include "nsICharsetDetector.h"
+
+class nsHtml5Parser;
+
+#define NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE 1024
+#define NS_HTML5_STREAM_PARSER_SNIFFING_BUFFER_SIZE 1024
+
+enum eParserMode {
+ /**
+ * Parse a document normally as HTML.
+ */
+ NORMAL,
+
+ /**
+ * View document as HTML source.
+ */
+ VIEW_SOURCE_HTML,
+
+ /**
+ * View document as XML source
+ */
+ VIEW_SOURCE_XML,
+
+ /**
+ * View document as plain text source
+ */
+ VIEW_SOURCE_PLAIN,
+
+ /**
+ * View document as plain text
+ */
+ PLAIN_TEXT,
+
+ /**
+ * Load as data (XHR)
+ */
+ LOAD_AS_DATA
+};
+
+enum eBomState {
+ /**
+ * BOM sniffing hasn't started.
+ */
+ BOM_SNIFFING_NOT_STARTED = 0,
+
+ /**
+ * BOM sniffing is ongoing, and the first byte of an UTF-16LE BOM has been
+ * seen.
+ */
+ SEEN_UTF_16_LE_FIRST_BYTE = 1,
+
+ /**
+ * BOM sniffing is ongoing, and the first byte of an UTF-16BE BOM has been
+ * seen.
+ */
+ SEEN_UTF_16_BE_FIRST_BYTE = 2,
+
+ /**
+ * BOM sniffing is ongoing, and the first byte of an UTF-8 BOM has been
+ * seen.
+ */
+ SEEN_UTF_8_FIRST_BYTE = 3,
+
+ /**
+ * BOM sniffing is ongoing, and the first and second bytes of an UTF-8 BOM
+ * have been seen.
+ */
+ SEEN_UTF_8_SECOND_BYTE = 4,
+
+ /**
+ * BOM sniffing was started but is now over for whatever reason.
+ */
+ BOM_SNIFFING_OVER = 5
+};
+
+enum eHtml5StreamState {
+ STREAM_NOT_STARTED = 0,
+ STREAM_BEING_READ = 1,
+ STREAM_ENDED = 2
+};
+
+class nsHtml5StreamParser : public nsICharsetDetectionObserver {
+
+ friend class nsHtml5RequestStopper;
+ friend class nsHtml5DataAvailable;
+ friend class nsHtml5StreamParserContinuation;
+ friend class nsHtml5TimerKungFu;
+
+ public:
+ NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHtml5StreamParser,
+ nsICharsetDetectionObserver)
+
+ static void InitializeStatics();
+
+ nsHtml5StreamParser(nsHtml5TreeOpExecutor* aExecutor,
+ nsHtml5Parser* aOwner,
+ eParserMode aMode);
+
+ // Methods that nsHtml5StreamListener calls
+ nsresult CheckListenerChain();
+
+ nsresult OnStartRequest(nsIRequest* aRequest, nsISupports* aContext);
+
+ nsresult OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInStream,
+ uint64_t aSourceOffset,
+ uint32_t aLength);
+
+ nsresult OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult status);
+
+ // nsICharsetDetectionObserver
+ /**
+ * Chardet calls this to report the detection result
+ */
+ NS_IMETHOD Notify(const char* aCharset, nsDetectionConfident aConf) override;
+
+ // EncodingDeclarationHandler
+ // http://hg.mozilla.org/projects/htmlparser/file/tip/src/nu/validator/htmlparser/common/EncodingDeclarationHandler.java
+ /**
+ * Tree builder uses this to report a late <meta charset>
+ */
+ bool internalEncodingDeclaration(nsHtml5String aEncoding);
+
+ // Not from an external interface
+
+ /**
+ * Call this method once you've created a parser, and want to instruct it
+ * about what charset to load
+ *
+ * @param aCharset the charset of a document
+ * @param aCharsetSource the source of the charset
+ */
+ inline void SetDocumentCharset(const nsACString& aCharset, int32_t aSource) {
+ NS_PRECONDITION(mStreamState == STREAM_NOT_STARTED,
+ "SetDocumentCharset called too late.");
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ mCharset = aCharset;
+ mCharsetSource = aSource;
+ }
+
+ inline void SetObserver(nsIRequestObserver* aObserver) {
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ mObserver = aObserver;
+ }
+
+ nsresult GetChannel(nsIChannel** aChannel);
+
+ /**
+ * The owner parser must call this after script execution
+ * when no scripts are executing and the document.written
+ * buffer has been exhausted.
+ */
+ void ContinueAfterScripts(nsHtml5Tokenizer* aTokenizer,
+ nsHtml5TreeBuilder* aTreeBuilder,
+ bool aLastWasCR);
+
+ /**
+ * Continues the stream parser if the charset switch failed.
+ */
+ void ContinueAfterFailedCharsetSwitch();
+
+ void Terminate()
+ {
+ mozilla::MutexAutoLock autoLock(mTerminatedMutex);
+ mTerminated = true;
+ }
+
+ void DropTimer();
+
+ /**
+ * Sets mCharset and mCharsetSource appropriately for the XML View Source
+ * case if aEncoding names a supported rough ASCII superset and sets
+ * the mCharset and mCharsetSource to the UTF-8 default otherwise.
+ */
+ void SetEncodingFromExpat(const char16_t* aEncoding);
+
+ /**
+ * Sets the URL for View Source title in case this parser ends up being
+ * used for View Source. If aURL is a view-source: URL, takes the inner
+ * URL. data: URLs are shown with an ellipsis instead of the actual data.
+ */
+ void SetViewSourceTitle(nsIURI* aURL);
+
+ private:
+ virtual ~nsHtml5StreamParser();
+
+#ifdef DEBUG
+ bool IsParserThread() {
+ bool ret;
+ mThread->IsOnCurrentThread(&ret);
+ return ret;
+ }
+#endif
+
+ void MarkAsBroken(nsresult aRv);
+
+ /**
+ * Marks the stream parser as interrupted. If you ever add calls to this
+ * method, be sure to review Uninterrupt usage very, very carefully to
+ * avoid having a previous in-flight runnable cancel your Interrupt()
+ * call on the other thread too soon.
+ */
+ void Interrupt()
+ {
+ mozilla::MutexAutoLock autoLock(mTerminatedMutex);
+ mInterrupted = true;
+ }
+
+ void Uninterrupt()
+ {
+ NS_ASSERTION(IsParserThread(), "Wrong thread!");
+ mTokenizerMutex.AssertCurrentThreadOwns();
+ // Not acquiring mTerminatedMutex because mTokenizerMutex is already
+ // held at this point and is already stronger.
+ mInterrupted = false;
+ }
+
+ /**
+ * Flushes the tree ops from the tree builder and disarms the flush
+ * timer.
+ */
+ void FlushTreeOpsAndDisarmTimer();
+
+ void ParseAvailableData();
+
+ void DoStopRequest();
+
+ void DoDataAvailable(const uint8_t* aBuffer, uint32_t aLength);
+
+ static nsresult CopySegmentsToParser(nsIInputStream *aInStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount);
+
+ bool IsTerminatedOrInterrupted()
+ {
+ mozilla::MutexAutoLock autoLock(mTerminatedMutex);
+ return mTerminated || mInterrupted;
+ }
+
+ bool IsTerminated()
+ {
+ mozilla::MutexAutoLock autoLock(mTerminatedMutex);
+ return mTerminated;
+ }
+
+ /**
+ * True when there is a Unicode decoder already
+ */
+ inline bool HasDecoder()
+ {
+ return !!mUnicodeDecoder;
+ }
+
+ /**
+ * Push bytes from network when there is no Unicode decoder yet
+ */
+ nsresult SniffStreamBytes(const uint8_t* aFromSegment,
+ uint32_t aCount,
+ uint32_t* aWriteCount);
+
+ /**
+ * Push bytes from network when there is a Unicode decoder already
+ */
+ nsresult WriteStreamBytes(const uint8_t* aFromSegment,
+ uint32_t aCount,
+ uint32_t* aWriteCount);
+
+ /**
+ * Check whether every other byte in the sniffing buffer is zero.
+ */
+ void SniffBOMlessUTF16BasicLatin(const uint8_t* aFromSegment,
+ uint32_t aCountToSniffingLimit);
+
+ /**
+ * <meta charset> scan failed. Try chardet if applicable. After this, the
+ * the parser will have some encoding even if a last resolt fallback.
+ *
+ * @param aFromSegment The current network buffer or null if the sniffing
+ * buffer is being flushed due to network stream ending.
+ * @param aCount The number of bytes in aFromSegment (ignored if
+ * aFromSegment is null)
+ * @param aWriteCount Return value for how many bytes got read from the
+ * buffer.
+ * @param aCountToSniffingLimit The number of unfilled slots in
+ * mSniffingBuffer
+ */
+ nsresult FinalizeSniffing(const uint8_t* aFromSegment,
+ uint32_t aCount,
+ uint32_t* aWriteCount,
+ uint32_t aCountToSniffingLimit);
+
+ /**
+ * Set up the Unicode decoder and write the sniffing buffer into it
+ * followed by the current network buffer.
+ *
+ * @param aFromSegment The current network buffer or null if the sniffing
+ * buffer is being flushed due to network stream ending.
+ * @param aCount The number of bytes in aFromSegment (ignored if
+ * aFromSegment is null)
+ * @param aWriteCount Return value for how many bytes got read from the
+ * buffer.
+ */
+ nsresult SetupDecodingAndWriteSniffingBufferAndCurrentSegment(const uint8_t* aFromSegment,
+ uint32_t aCount,
+ uint32_t* aWriteCount);
+
+ /**
+ * Initialize the Unicode decoder, mark the BOM as the source and
+ * drop the sniffer.
+ *
+ * @param aDecoderCharsetName The name for the decoder's charset
+ * (UTF-16BE, UTF-16LE or UTF-8; the BOM has
+ * been swallowed)
+ */
+ nsresult SetupDecodingFromBom(const char* aDecoderCharsetName);
+
+ /**
+ * Become confident or resolve and encoding name to its preferred form.
+ * @param aEncoding the value of an internal encoding decl. Acts as an
+ * out param, too, when the method returns true.
+ * @return true if the parser needs to start using the new value of
+ * aEncoding and false if the parser became confident or if
+ * the encoding name did not specify a usable encoding
+ */
+ bool PreferredForInternalEncodingDecl(nsACString& aEncoding);
+
+ /**
+ * Callback for mFlushTimer.
+ */
+ static void TimerCallback(nsITimer* aTimer, void* aClosure);
+
+ /**
+ * Parser thread entry point for (maybe) flushing the ops and posting
+ * a flush runnable back on the main thread.
+ */
+ void TimerFlush();
+
+ /**
+ * Called when speculation fails.
+ */
+ void MaybeDisableFutureSpeculation()
+ {
+ mSpeculationFailureCount++;
+ }
+
+ /**
+ * Used to check whether we're getting too many speculation failures and
+ * should just stop trying. The 100 is picked pretty randomly to be not too
+ * small (so most pages are not affected) but small enough that we don't end
+ * up with failed speculations over and over in pathological cases.
+ */
+ bool IsSpeculationEnabled()
+ {
+ return mSpeculationFailureCount < 100;
+ }
+
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIRequestObserver> mObserver;
+
+ /**
+ * The document title to use if this turns out to be a View Source parser.
+ */
+ nsCString mViewSourceTitle;
+
+ /**
+ * The Unicode decoder
+ */
+ nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
+
+ /**
+ * The buffer for sniffing the character encoding
+ */
+ mozilla::UniquePtr<uint8_t[]> mSniffingBuffer;
+
+ /**
+ * The number of meaningful bytes in mSniffingBuffer
+ */
+ uint32_t mSniffingLength;
+
+ /**
+ * BOM sniffing state
+ */
+ eBomState mBomState;
+
+ /**
+ * <meta> prescan implementation
+ */
+ nsAutoPtr<nsHtml5MetaScanner> mMetaScanner;
+
+ // encoding-related stuff
+ /**
+ * The source (confidence) of the character encoding in use
+ */
+ int32_t mCharsetSource;
+
+ /**
+ * The character encoding in use
+ */
+ nsCString mCharset;
+
+ /**
+ * Whether reparse is forbidden
+ */
+ bool mReparseForbidden;
+
+ // Portable parser objects
+ /**
+ * The first buffer in the pending UTF-16 buffer queue
+ */
+ RefPtr<nsHtml5OwningUTF16Buffer> mFirstBuffer;
+
+ /**
+ * The last buffer in the pending UTF-16 buffer queue
+ */
+ nsHtml5OwningUTF16Buffer* mLastBuffer; // weak ref; always points to
+ // a buffer of the size NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE
+
+ /**
+ * The tree operation executor
+ */
+ nsHtml5TreeOpExecutor* mExecutor;
+
+ /**
+ * The HTML5 tree builder
+ */
+ nsAutoPtr<nsHtml5TreeBuilder> mTreeBuilder;
+
+ /**
+ * The HTML5 tokenizer
+ */
+ nsAutoPtr<nsHtml5Tokenizer> mTokenizer;
+
+ /**
+ * Makes sure the main thread can't mess the tokenizer state while it's
+ * tokenizing. This mutex also protects the current speculation.
+ */
+ mozilla::Mutex mTokenizerMutex;
+
+ /**
+ * The scoped atom table
+ */
+ nsHtml5AtomTable mAtomTable;
+
+ /**
+ * The owner parser.
+ */
+ RefPtr<nsHtml5Parser> mOwner;
+
+ /**
+ * Whether the last character tokenized was a carriage return (for CRLF)
+ */
+ bool mLastWasCR;
+
+ /**
+ * For tracking stream life cycle
+ */
+ eHtml5StreamState mStreamState;
+
+ /**
+ * Whether we are speculating.
+ */
+ bool mSpeculating;
+
+ /**
+ * Whether the tokenizer has reached EOF. (Reset when stream rewinded.)
+ */
+ bool mAtEOF;
+
+ /**
+ * The speculations. The mutex protects the nsTArray itself.
+ * To access the queue of current speculation, mTokenizerMutex must be
+ * obtained.
+ * The current speculation is the last element
+ */
+ nsTArray<nsAutoPtr<nsHtml5Speculation> > mSpeculations;
+ mozilla::Mutex mSpeculationMutex;
+
+ /**
+ * Number of times speculation has failed for this parser.
+ */
+ uint32_t mSpeculationFailureCount;
+
+ /**
+ * True to terminate early; protected by mTerminatedMutex
+ */
+ bool mTerminated;
+ bool mInterrupted;
+ mozilla::Mutex mTerminatedMutex;
+
+ /**
+ * The thread this stream parser runs on.
+ */
+ nsCOMPtr<nsIThread> mThread;
+
+ nsCOMPtr<nsIRunnable> mExecutorFlusher;
+
+ nsCOMPtr<nsIRunnable> mLoadFlusher;
+
+ /**
+ * The chardet instance if chardet is enabled.
+ */
+ nsCOMPtr<nsICharsetDetector> mChardet;
+
+ /**
+ * If false, don't push data to chardet.
+ */
+ bool mFeedChardet;
+
+ /**
+ * Whether the initial charset source was kCharsetFromParentFrame
+ */
+ bool mInitialEncodingWasFromParentFrame;
+
+ /**
+ * Timer for flushing tree ops once in a while when not speculating.
+ */
+ nsCOMPtr<nsITimer> mFlushTimer;
+
+ /**
+ * Keeps track whether mFlushTimer has been armed. Unfortunately,
+ * nsITimer doesn't enable querying this from the timer itself.
+ */
+ bool mFlushTimerArmed;
+
+ /**
+ * False initially and true after the timer has fired at least once.
+ */
+ bool mFlushTimerEverFired;
+
+ /**
+ * Whether the parser is doing a normal parse, view source or plain text.
+ */
+ eParserMode mMode;
+
+ /**
+ * The pref html5.flushtimer.initialdelay: Time in milliseconds between
+ * the time a network buffer is seen and the timer firing when the
+ * timer hasn't fired previously in this parse.
+ */
+ static int32_t sTimerInitialDelay;
+
+ /**
+ * The pref html5.flushtimer.subsequentdelay: Time in milliseconds between
+ * the time a network buffer is seen and the timer firing when the
+ * timer has already fired previously in this parse.
+ */
+ static int32_t sTimerSubsequentDelay;
+};
+
+#endif // nsHtml5StreamParser_h
diff --git a/components/htmlfive/nsHtml5String.cpp b/components/htmlfive/nsHtml5String.cpp
new file mode 100644
index 000000000..e72798016
--- /dev/null
+++ b/components/htmlfive/nsHtml5String.cpp
@@ -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/. */
+
+#include "nsHtml5String.h"
+#include "nsCharTraits.h"
+#include "nsUTF8Utils.h"
+#include "nsHtml5TreeBuilder.h"
+
+void
+nsHtml5String::ToString(nsAString& aString)
+{
+ switch (GetKind()) {
+ case eStringBuffer:
+ return AsStringBuffer()->ToString(Length(), aString);
+ case eAtom:
+ return AsAtom()->ToString(aString);
+ case eEmpty:
+ aString.Truncate();
+ return;
+ default:
+ aString.Truncate();
+ aString.SetIsVoid(true);
+ return;
+ }
+}
+
+void
+nsHtml5String::CopyToBuffer(char16_t* aBuffer) const
+{
+ memcpy(aBuffer, AsPtr(), Length() * sizeof(char16_t));
+}
+
+bool
+nsHtml5String::LowerCaseEqualsASCII(const char* aLowerCaseLiteral) const
+{
+ return !nsCharTraits<char16_t>::compareLowerCaseToASCIINullTerminated(
+ AsPtr(), Length(), aLowerCaseLiteral);
+}
+
+bool
+nsHtml5String::EqualsASCII(const char* aLiteral) const
+{
+ return !nsCharTraits<char16_t>::compareASCIINullTerminated(
+ AsPtr(), Length(), aLiteral);
+}
+
+bool
+nsHtml5String::LowerCaseStartsWithASCII(const char* aLowerCaseLiteral) const
+{
+ const char* litPtr = aLowerCaseLiteral;
+ const char16_t* strPtr = AsPtr();
+ const char16_t* end = strPtr + Length();
+ char16_t litChar;
+ while ((litChar = *litPtr) && (strPtr != end)) {
+ MOZ_ASSERT(!(litChar >= 'A' && litChar <= 'Z'),
+ "Literal isn't in lower case.");
+ char16_t strChar = *strPtr;
+ if (strChar >= 'A' && strChar <= 'Z') {
+ strChar += 0x20;
+ }
+ if (litChar != strChar) {
+ return false;
+ }
+ ++litPtr;
+ ++strPtr;
+ }
+ return true;
+}
+
+bool
+nsHtml5String::Equals(nsHtml5String aOther) const
+{
+ MOZ_ASSERT(operator bool());
+ MOZ_ASSERT(aOther);
+ if (Length() != aOther.Length()) {
+ return false;
+ }
+ return !memcmp(
+ AsPtr(), aOther.AsPtr(), Length() * sizeof(char16_t));
+}
+
+nsHtml5String
+nsHtml5String::Clone()
+{
+ switch (GetKind()) {
+ case eStringBuffer:
+ AsStringBuffer()->AddRef();
+ break;
+ case eAtom:
+ AsAtom()->AddRef();
+ break;
+ default:
+ break;
+ }
+ return nsHtml5String(mBits);
+}
+
+void
+nsHtml5String::Release()
+{
+ switch (GetKind()) {
+ case eStringBuffer:
+ AsStringBuffer()->Release();
+ break;
+ case eAtom:
+ AsAtom()->Release();
+ break;
+ default:
+ break;
+ }
+ mBits = eNull;
+}
+
+// static
+nsHtml5String
+nsHtml5String::FromBuffer(char16_t* aBuffer,
+ int32_t aLength,
+ nsHtml5TreeBuilder* aTreeBuilder)
+{
+ if (!aLength) {
+ return nsHtml5String(eEmpty);
+ }
+ // Work with nsStringBuffer directly to make sure that storage is actually
+ // nsStringBuffer and to make sure the allocation strategy matches
+ // nsAttrValue::GetStringBuffer, so that it doesn't need to reallocate and
+ // copy.
+ RefPtr<nsStringBuffer> buffer(
+ nsStringBuffer::Alloc((aLength + 1) * sizeof(char16_t)));
+ if (!buffer) {
+ if (!aTreeBuilder) {
+ MOZ_CRASH("Out of memory.");
+ }
+ aTreeBuilder->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+ buffer = nsStringBuffer::Alloc(2 * sizeof(char16_t));
+ if (!buffer) {
+ MOZ_CRASH(
+ "Out of memory so badly that couldn't even allocate placeholder.");
+ }
+ char16_t* data = reinterpret_cast<char16_t*>(buffer->Data());
+ data[0] = 0xFFFD;
+ data[1] = 0;
+ return nsHtml5String(reinterpret_cast<uintptr_t>(buffer.forget().take()) | eStringBuffer);
+ }
+ char16_t* data = reinterpret_cast<char16_t*>(buffer->Data());
+ memcpy(data, aBuffer, aLength * sizeof(char16_t));
+ data[aLength] = 0;
+ return nsHtml5String(reinterpret_cast<uintptr_t>(buffer.forget().take()) | eStringBuffer);
+}
+
+// static
+nsHtml5String
+nsHtml5String::FromLiteral(const char* aLiteral)
+{
+ size_t length = std::strlen(aLiteral);
+ if (!length) {
+ return nsHtml5String(eEmpty);
+ }
+ // Work with nsStringBuffer directly to make sure that storage is actually
+ // nsStringBuffer and to make sure the allocation strategy matches
+ // nsAttrValue::GetStringBuffer, so that it doesn't need to reallocate and
+ // copy.
+ RefPtr<nsStringBuffer> buffer(
+ nsStringBuffer::Alloc((length + 1) * sizeof(char16_t)));
+ if (!buffer) {
+ MOZ_CRASH("Out of memory.");
+ }
+ char16_t* data = reinterpret_cast<char16_t*>(buffer->Data());
+ LossyConvertEncoding8to16 converter(data);
+ converter.write(aLiteral, length);
+ data[length] = 0;
+ return nsHtml5String(reinterpret_cast<uintptr_t>(buffer.forget().take()) | eStringBuffer);
+}
+
+// static
+nsHtml5String
+nsHtml5String::FromString(const nsAString& aString)
+{
+ auto length = aString.Length();
+ if (!length) {
+ return nsHtml5String(eEmpty);
+ }
+ RefPtr<nsStringBuffer> buffer = nsStringBuffer::FromString(aString);
+ if (buffer && (length == buffer->StorageSize()/sizeof(char16_t) - 1)) {
+ return nsHtml5String(reinterpret_cast<uintptr_t>(buffer.forget().take()) | eStringBuffer);
+ }
+ buffer = nsStringBuffer::Alloc((length + 1) * sizeof(char16_t));
+ if (!buffer) {
+ MOZ_CRASH("Out of memory.");
+ }
+ char16_t* data = reinterpret_cast<char16_t*>(buffer->Data());
+ memcpy(data, aString.BeginReading(), length * sizeof(char16_t));
+ data[length] = 0;
+ return nsHtml5String(reinterpret_cast<uintptr_t>(buffer.forget().take()) | eStringBuffer);
+}
+
+// static
+nsHtml5String
+nsHtml5String::FromAtom(already_AddRefed<nsIAtom> aAtom)
+{
+ return nsHtml5String(reinterpret_cast<uintptr_t>(aAtom.take()) | eAtom);
+}
+
+// static
+nsHtml5String
+nsHtml5String::EmptyString()
+{
+ return nsHtml5String(eEmpty);
+
+}
diff --git a/components/htmlfive/nsHtml5String.h b/components/htmlfive/nsHtml5String.h
new file mode 100644
index 000000000..54954fe1a
--- /dev/null
+++ b/components/htmlfive/nsHtml5String.h
@@ -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/. */
+
+#ifndef nsHtml5String_h
+#define nsHtml5String_h
+
+#include "nsString.h"
+#include "nsIAtom.h"
+
+class nsHtml5TreeBuilder;
+
+/**
+ * A pass-by-value type that can represent
+ * * nullptr
+ * * empty string
+ * * Non-empty string as exactly-sized (capacity is length) `nsStringBuffer*`
+ * * Non-empty string as an nsIAtom*
+ *
+ * Holding or passing this type is as unsafe as holding or passing
+ * `nsStringBuffer*`/`nsIAtom*`.
+ */
+class nsHtml5String final
+{
+private:
+
+ static const uintptr_t kKindMask = uintptr_t(3);
+
+ static const uintptr_t kPtrMask = ~kKindMask;
+
+ enum Kind : uintptr_t {
+ eNull = 0,
+ eEmpty = 1,
+ eStringBuffer = 2,
+ eAtom = 3,
+ };
+
+ inline Kind GetKind() const { return (Kind)(mBits & kKindMask); }
+
+ inline nsStringBuffer* AsStringBuffer() const
+ {
+ MOZ_ASSERT(GetKind() == eStringBuffer);
+ return reinterpret_cast<nsStringBuffer*>(mBits & kPtrMask);
+ }
+
+ inline nsIAtom* AsAtom() const
+ {
+ MOZ_ASSERT(GetKind() == eAtom);
+ return reinterpret_cast<nsIAtom*>(mBits & kPtrMask);
+ }
+
+ inline const char16_t* AsPtr() const
+ {
+ switch (GetKind()) {
+ case eStringBuffer:
+ return reinterpret_cast<char16_t*>(AsStringBuffer()->Data());
+ case eAtom:
+ return AsAtom()->GetUTF16String();
+ default:
+ return nullptr;
+ }
+ }
+
+public:
+ /**
+ * Default constructor.
+ */
+ inline nsHtml5String()
+ : nsHtml5String(nullptr)
+ {
+ }
+
+ /**
+ * Constructor from nullptr.
+ */
+ inline MOZ_IMPLICIT nsHtml5String(decltype(nullptr))
+ : mBits(eNull)
+ {
+ }
+
+ inline uint32_t Length() const
+ {
+ switch (GetKind()) {
+ case eStringBuffer:
+ return (AsStringBuffer()->StorageSize()/sizeof(char16_t) - 1);
+ case eAtom:
+ return AsAtom()->GetLength();
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * False iff the string is logically null
+ */
+ inline MOZ_IMPLICIT operator bool() const { return mBits; }
+
+ /**
+ * Get the underlying nsIAtom* or nullptr if this nsHtml5String
+ * does not hold an atom.
+ */
+ inline nsIAtom* MaybeAsAtom()
+ {
+ if (GetKind() == eAtom) {
+ return AsAtom();
+ }
+ return nullptr;
+ }
+
+ void ToString(nsAString& aString);
+
+ void CopyToBuffer(char16_t* aBuffer) const;
+
+ bool LowerCaseEqualsASCII(const char* aLowerCaseLiteral) const;
+
+ bool EqualsASCII(const char* aLiteral) const;
+
+ bool LowerCaseStartsWithASCII(const char* aLowerCaseLiteral) const;
+
+ bool Equals(nsHtml5String aOther) const;
+
+ nsHtml5String Clone();
+
+ void Release();
+
+ static nsHtml5String FromBuffer(char16_t* aBuffer,
+ int32_t aLength,
+ nsHtml5TreeBuilder* aTreeBuilder);
+
+ static nsHtml5String FromLiteral(const char* aLiteral);
+
+ static nsHtml5String FromString(const nsAString& aString);
+
+ static nsHtml5String FromAtom(already_AddRefed<nsIAtom> aAtom);
+
+ static nsHtml5String EmptyString();
+
+private:
+
+ /**
+ * Constructor from raw bits.
+ */
+ explicit nsHtml5String(uintptr_t aBits) : mBits(aBits) {};
+
+ /**
+ * Zero if null, one if empty, otherwise tagged pointer
+ * to either nsIAtom or nsStringBuffer. The two least-significant
+ * bits are tag bits.
+ */
+ uintptr_t mBits;
+};
+
+#endif // nsHtml5String_h
diff --git a/components/htmlfive/nsHtml5StringParser.cpp b/components/htmlfive/nsHtml5StringParser.cpp
new file mode 100644
index 000000000..d1eeca6b8
--- /dev/null
+++ b/components/htmlfive/nsHtml5StringParser.cpp
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsHtml5StringParser.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocumentFragment.h"
+#include "nsHtml5DependentUTF16Buffer.h"
+
+NS_IMPL_ISUPPORTS0(nsHtml5StringParser)
+
+nsHtml5StringParser::nsHtml5StringParser()
+ : mBuilder(new nsHtml5OplessBuilder())
+ , mTreeBuilder(new nsHtml5TreeBuilder(mBuilder))
+ , mTokenizer(new nsHtml5Tokenizer(mTreeBuilder, false))
+{
+ MOZ_COUNT_CTOR(nsHtml5StringParser);
+ mTokenizer->setInterner(&mAtomTable);
+}
+
+nsHtml5StringParser::~nsHtml5StringParser()
+{
+ MOZ_COUNT_DTOR(nsHtml5StringParser);
+}
+
+nsresult
+nsHtml5StringParser::ParseFragment(const nsAString& aSourceBuffer,
+ nsIContent* aTargetNode,
+ nsIAtom* aContextLocalName,
+ int32_t aContextNamespace,
+ bool aQuirks,
+ bool aPreventScriptExecution)
+{
+ NS_ENSURE_TRUE(aSourceBuffer.Length() <= INT32_MAX,
+ NS_ERROR_OUT_OF_MEMORY);
+
+ nsIDocument* doc = aTargetNode->OwnerDoc();
+ nsIURI* uri = doc->GetDocumentURI();
+ NS_ENSURE_TRUE(uri, NS_ERROR_NOT_AVAILABLE);
+
+ mTreeBuilder->setFragmentContext(aContextLocalName,
+ aContextNamespace,
+ aTargetNode,
+ aQuirks);
+
+#ifdef DEBUG
+ if (!aPreventScriptExecution) {
+ NS_ASSERTION(!aTargetNode->IsInUncomposedDoc(),
+ "If script execution isn't prevented, "
+ "the target node must not be in doc.");
+ nsCOMPtr<nsIDOMDocumentFragment> domFrag = do_QueryInterface(aTargetNode);
+ NS_ASSERTION(domFrag,
+ "If script execution isn't prevented, must parse to DOM fragment.");
+ }
+#endif
+
+ mTreeBuilder->SetPreventScriptExecution(aPreventScriptExecution);
+
+ return Tokenize(aSourceBuffer, doc, true);
+}
+
+nsresult
+nsHtml5StringParser::ParseDocument(const nsAString& aSourceBuffer,
+ nsIDocument* aTargetDoc,
+ bool aScriptingEnabledForNoscriptParsing)
+{
+ MOZ_ASSERT(!aTargetDoc->GetFirstChild());
+
+ NS_ENSURE_TRUE(aSourceBuffer.Length() <= INT32_MAX,
+ NS_ERROR_OUT_OF_MEMORY);
+
+ mTreeBuilder->setFragmentContext(nullptr,
+ kNameSpaceID_None,
+ nullptr,
+ false);
+
+ mTreeBuilder->SetPreventScriptExecution(true);
+
+ return Tokenize(aSourceBuffer, aTargetDoc, aScriptingEnabledForNoscriptParsing);
+}
+
+nsresult
+nsHtml5StringParser::Tokenize(const nsAString& aSourceBuffer,
+ nsIDocument* aDocument,
+ bool aScriptingEnabledForNoscriptParsing) {
+
+ nsIURI* uri = aDocument->GetDocumentURI();
+
+ mBuilder->Init(aDocument, uri, nullptr, nullptr);
+
+ mBuilder->SetParser(this);
+ mBuilder->SetNodeInfoManager(aDocument->NodeInfoManager());
+
+ // Mark the parser as *not* broken by passing NS_OK
+ nsresult rv = mBuilder->MarkAsBroken(NS_OK);
+
+ mTreeBuilder->setScriptingEnabled(aScriptingEnabledForNoscriptParsing);
+ mTreeBuilder->setIsSrcdocDocument(aDocument->IsSrcdocDocument());
+ mBuilder->Start();
+ mTokenizer->start();
+ if (!aSourceBuffer.IsEmpty()) {
+ bool lastWasCR = false;
+ nsHtml5DependentUTF16Buffer buffer(aSourceBuffer);
+ while (buffer.hasMore()) {
+ buffer.adjust(lastWasCR);
+ lastWasCR = false;
+ if (buffer.hasMore()) {
+ if (!mTokenizer->EnsureBufferSpace(buffer.getLength())) {
+ rv = mBuilder->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
+ break;
+ }
+ lastWasCR = mTokenizer->tokenizeBuffer(&buffer);
+ if (NS_FAILED(rv = mBuilder->IsBroken())) {
+ break;
+ }
+ }
+ }
+ }
+ if (NS_SUCCEEDED(rv)) {
+ mTokenizer->eof();
+ }
+ mTokenizer->end();
+ mBuilder->Finish();
+ mAtomTable.Clear();
+ return rv;
+}
diff --git a/components/htmlfive/nsHtml5StringParser.h b/components/htmlfive/nsHtml5StringParser.h
new file mode 100644
index 000000000..3f7322436
--- /dev/null
+++ b/components/htmlfive/nsHtml5StringParser.h
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsHtml5StringParser_h
+#define nsHtml5StringParser_h
+
+#include "nsHtml5AtomTable.h"
+#include "nsParserBase.h"
+
+class nsHtml5OplessBuilder;
+class nsHtml5TreeBuilder;
+class nsHtml5Tokenizer;
+class nsIContent;
+class nsIDocument;
+
+class nsHtml5StringParser : public nsParserBase
+{
+ public:
+
+ NS_DECL_ISUPPORTS
+
+ /**
+ * Constructor for use ONLY by nsContentUtils. Others, please call the
+ * nsContentUtils statics that wrap this.
+ */
+ nsHtml5StringParser();
+
+ /**
+ * Invoke the fragment parsing algorithm (innerHTML).
+ * DO NOT CALL from outside nsContentUtils.cpp.
+ *
+ * @param aSourceBuffer the string being set as innerHTML
+ * @param aTargetNode the target container
+ * @param aContextLocalName local name of context node
+ * @param aContextNamespace namespace of context node
+ * @param aQuirks true to make <table> not close <p>
+ * @param aPreventScriptExecution true to prevent scripts from executing;
+ * don't set to false when parsing into a target node that has been bound
+ * to tree.
+ */
+ nsresult ParseFragment(const nsAString& aSourceBuffer,
+ nsIContent* aTargetNode,
+ nsIAtom* aContextLocalName,
+ int32_t aContextNamespace,
+ bool aQuirks,
+ bool aPreventScriptExecution);
+
+ /**
+ * Parse an entire HTML document from a source string.
+ * DO NOT CALL from outside nsContentUtils.cpp.
+ *
+ */
+ nsresult ParseDocument(const nsAString& aSourceBuffer,
+ nsIDocument* aTargetDoc,
+ bool aScriptingEnabledForNoscriptParsing);
+
+ private:
+
+ virtual ~nsHtml5StringParser();
+
+ nsresult Tokenize(const nsAString& aSourceBuffer,
+ nsIDocument* aDocument,
+ bool aScriptingEnabledForNoscriptParsing);
+
+ /**
+ * The tree operation executor
+ */
+ RefPtr<nsHtml5OplessBuilder> mBuilder;
+
+ /**
+ * The HTML5 tree builder
+ */
+ const nsAutoPtr<nsHtml5TreeBuilder> mTreeBuilder;
+
+ /**
+ * The HTML5 tokenizer
+ */
+ const nsAutoPtr<nsHtml5Tokenizer> mTokenizer;
+
+ /**
+ * The scoped atom table
+ */
+ nsHtml5AtomTable mAtomTable;
+
+};
+
+#endif // nsHtml5StringParser_h
diff --git a/components/htmlfive/nsHtml5Tokenizer.cpp b/components/htmlfive/nsHtml5Tokenizer.cpp
new file mode 100644
index 000000000..4c6a32f73
--- /dev/null
+++ b/components/htmlfive/nsHtml5Tokenizer.cpp
@@ -0,0 +1,4125 @@
+/*
+ * Copyright (c) 2005-2007 Henri Sivonen
+ * Copyright (c) 2007-2015 Mozilla Foundation
+ * Copyright (c) 2018-2021 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ * Portions of comments Copyright 2004-2010 Apple Computer, Inc., Mozilla
+ * Foundation, and Opera Software ASA.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit Tokenizer.java instead and regenerate.
+ */
+
+#define nsHtml5Tokenizer_cpp__
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5DocumentMode.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsHtml5NamedCharacters.h"
+#include "nsHtml5NamedCharactersAccel.h"
+#include "nsHtml5Atoms.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Macros.h"
+#include "nsHtml5Highlighter.h"
+#include "nsHtml5TokenizerLoopPolicies.h"
+
+#include "nsHtml5AttributeName.h"
+#include "nsHtml5ElementName.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5MetaScanner.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5UTF16Buffer.h"
+#include "nsHtml5StateSnapshot.h"
+#include "nsHtml5Portability.h"
+
+#include "nsHtml5Tokenizer.h"
+
+char16_t nsHtml5Tokenizer::LT_GT[] = { '<', '>' };
+char16_t nsHtml5Tokenizer::LT_SOLIDUS[] = { '<', '/' };
+char16_t nsHtml5Tokenizer::RSQB_RSQB[] = { ']', ']' };
+char16_t nsHtml5Tokenizer::REPLACEMENT_CHARACTER[] = { 0xfffd };
+char16_t nsHtml5Tokenizer::LF[] = { '\n' };
+char16_t nsHtml5Tokenizer::CDATA_LSQB[] = { 'C', 'D', 'A', 'T', 'A', '[' };
+char16_t nsHtml5Tokenizer::OCTYPE[] = { 'o', 'c', 't', 'y', 'p', 'e' };
+char16_t nsHtml5Tokenizer::UBLIC[] = { 'u', 'b', 'l', 'i', 'c' };
+char16_t nsHtml5Tokenizer::YSTEM[] = { 'y', 's', 't', 'e', 'm' };
+static char16_t const TITLE_ARR_DATA[] = { 't', 'i', 't', 'l', 'e' };
+staticJArray<char16_t,int32_t> nsHtml5Tokenizer::TITLE_ARR = { TITLE_ARR_DATA, MOZ_ARRAY_LENGTH(TITLE_ARR_DATA) };
+static char16_t const SCRIPT_ARR_DATA[] = { 's', 'c', 'r', 'i', 'p', 't' };
+staticJArray<char16_t,int32_t> nsHtml5Tokenizer::SCRIPT_ARR = { SCRIPT_ARR_DATA, MOZ_ARRAY_LENGTH(SCRIPT_ARR_DATA) };
+static char16_t const STYLE_ARR_DATA[] = { 's', 't', 'y', 'l', 'e' };
+staticJArray<char16_t,int32_t> nsHtml5Tokenizer::STYLE_ARR = { STYLE_ARR_DATA, MOZ_ARRAY_LENGTH(STYLE_ARR_DATA) };
+static char16_t const PLAINTEXT_ARR_DATA[] = { 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't' };
+staticJArray<char16_t,int32_t> nsHtml5Tokenizer::PLAINTEXT_ARR = { PLAINTEXT_ARR_DATA, MOZ_ARRAY_LENGTH(PLAINTEXT_ARR_DATA) };
+static char16_t const XMP_ARR_DATA[] = { 'x', 'm', 'p' };
+staticJArray<char16_t,int32_t> nsHtml5Tokenizer::XMP_ARR = { XMP_ARR_DATA, MOZ_ARRAY_LENGTH(XMP_ARR_DATA) };
+static char16_t const TEXTAREA_ARR_DATA[] = { 't', 'e', 'x', 't', 'a', 'r', 'e', 'a' };
+staticJArray<char16_t,int32_t> nsHtml5Tokenizer::TEXTAREA_ARR = { TEXTAREA_ARR_DATA, MOZ_ARRAY_LENGTH(TEXTAREA_ARR_DATA) };
+static char16_t const IFRAME_ARR_DATA[] = { 'i', 'f', 'r', 'a', 'm', 'e' };
+staticJArray<char16_t,int32_t> nsHtml5Tokenizer::IFRAME_ARR = { IFRAME_ARR_DATA, MOZ_ARRAY_LENGTH(IFRAME_ARR_DATA) };
+static char16_t const NOEMBED_ARR_DATA[] = { 'n', 'o', 'e', 'm', 'b', 'e', 'd' };
+staticJArray<char16_t,int32_t> nsHtml5Tokenizer::NOEMBED_ARR = { NOEMBED_ARR_DATA, MOZ_ARRAY_LENGTH(NOEMBED_ARR_DATA) };
+static char16_t const NOSCRIPT_ARR_DATA[] = { 'n', 'o', 's', 'c', 'r', 'i', 'p', 't' };
+staticJArray<char16_t,int32_t> nsHtml5Tokenizer::NOSCRIPT_ARR = { NOSCRIPT_ARR_DATA, MOZ_ARRAY_LENGTH(NOSCRIPT_ARR_DATA) };
+static char16_t const NOFRAMES_ARR_DATA[] = { 'n', 'o', 'f', 'r', 'a', 'm', 'e', 's' };
+staticJArray<char16_t,int32_t> nsHtml5Tokenizer::NOFRAMES_ARR = { NOFRAMES_ARR_DATA, MOZ_ARRAY_LENGTH(NOFRAMES_ARR_DATA) };
+
+nsHtml5Tokenizer::nsHtml5Tokenizer(nsHtml5TreeBuilder* tokenHandler, bool viewingXmlSource)
+ : tokenHandler(tokenHandler),
+ encodingDeclarationHandler(nullptr),
+ charRefBuf(jArray<char16_t,int32_t>::newJArray(32)),
+ bmpChar(jArray<char16_t,int32_t>::newJArray(1)),
+ astralChar(jArray<char16_t,int32_t>::newJArray(2)),
+ containsHyphen(false),
+ tagName(nullptr),
+ nonInternedTagName(new nsHtml5ElementName()),
+ attributeName(nullptr),
+ nonInternedAttributeName(new nsHtml5AttributeName()),
+ doctypeName(nullptr),
+ publicIdentifier(nullptr),
+ systemIdentifier(nullptr),
+ attributes(tokenHandler->HasBuilder() ? new nsHtml5HtmlAttributes(0) : nullptr),
+ newAttributesEachTime(!tokenHandler->HasBuilder()),
+ viewingXmlSource(viewingXmlSource)
+{
+ MOZ_COUNT_CTOR(nsHtml5Tokenizer);
+}
+
+void
+nsHtml5Tokenizer::setInterner(nsHtml5AtomTable* interner)
+{
+ this->interner = interner;
+}
+
+void
+nsHtml5Tokenizer::initLocation(nsHtml5String newPublicId, nsHtml5String newSystemId)
+{
+ this->systemId = newSystemId;
+ this->publicId = newPublicId;
+}
+
+bool
+nsHtml5Tokenizer::isViewingXmlSource()
+{
+ return viewingXmlSource;
+}
+
+void
+nsHtml5Tokenizer::setState(int32_t specialTokenizerState)
+{
+ this->stateSave = specialTokenizerState;
+ this->endTagExpectation = nullptr;
+ this->endTagExpectationAsArray = nullptr;
+}
+
+void
+nsHtml5Tokenizer::setStateAndEndTagExpectation(int32_t specialTokenizerState, nsHtml5ElementName* endTagExpectation)
+{
+ this->stateSave = specialTokenizerState;
+ this->endTagExpectation = endTagExpectation;
+ endTagExpectationToArray();
+}
+
+void
+nsHtml5Tokenizer::endTagExpectationToArray()
+{
+ switch(endTagExpectation->getGroup()) {
+ case nsHtml5TreeBuilder::TITLE: {
+ endTagExpectationAsArray = TITLE_ARR;
+ return;
+ }
+ case nsHtml5TreeBuilder::SCRIPT: {
+ endTagExpectationAsArray = SCRIPT_ARR;
+ return;
+ }
+ case nsHtml5TreeBuilder::STYLE: {
+ endTagExpectationAsArray = STYLE_ARR;
+ return;
+ }
+ case nsHtml5TreeBuilder::PLAINTEXT: {
+ endTagExpectationAsArray = PLAINTEXT_ARR;
+ return;
+ }
+ case nsHtml5TreeBuilder::XMP: {
+ endTagExpectationAsArray = XMP_ARR;
+ return;
+ }
+ case nsHtml5TreeBuilder::TEXTAREA: {
+ endTagExpectationAsArray = TEXTAREA_ARR;
+ return;
+ }
+ case nsHtml5TreeBuilder::IFRAME: {
+ endTagExpectationAsArray = IFRAME_ARR;
+ return;
+ }
+ case nsHtml5TreeBuilder::NOEMBED: {
+ endTagExpectationAsArray = NOEMBED_ARR;
+ return;
+ }
+ case nsHtml5TreeBuilder::NOSCRIPT: {
+ endTagExpectationAsArray = NOSCRIPT_ARR;
+ return;
+ }
+ case nsHtml5TreeBuilder::NOFRAMES: {
+ endTagExpectationAsArray = NOFRAMES_ARR;
+ return;
+ }
+ default: {
+ MOZ_ASSERT(false, "Bad end tag expectation.");
+ return;
+ }
+ }
+}
+
+void
+nsHtml5Tokenizer::setLineNumber(int32_t line)
+{
+ this->attributeLine = line;
+ this->line = line;
+}
+
+nsHtml5HtmlAttributes*
+nsHtml5Tokenizer::emptyAttributes()
+{
+ return nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES;
+}
+
+void
+nsHtml5Tokenizer::emitOrAppendCharRefBuf(int32_t returnState)
+{
+ if ((returnState & DATA_AND_RCDATA_MASK)) {
+ appendCharRefBufToStrBuf();
+ } else {
+ if (charRefBufLen > 0) {
+ tokenHandler->characters(charRefBuf, 0, charRefBufLen);
+ charRefBufLen = 0;
+ }
+ }
+}
+
+nsHtml5String
+nsHtml5Tokenizer::strBufToString()
+{
+ nsHtml5String str = nsHtml5Portability::newStringFromBuffer(strBuf, 0, strBufLen, tokenHandler, !newAttributesEachTime && attributeName == nsHtml5AttributeName::ATTR_CLASS);
+ clearStrBufAfterUse();
+ return str;
+}
+
+void
+nsHtml5Tokenizer::strBufToDoctypeName()
+{
+ doctypeName = nsHtml5Portability::newLocalNameFromBuffer(strBuf, 0, strBufLen, interner);
+ clearStrBufAfterUse();
+}
+
+void
+nsHtml5Tokenizer::emitStrBuf()
+{
+ if (strBufLen > 0) {
+ tokenHandler->characters(strBuf, 0, strBufLen);
+ clearStrBufAfterUse();
+ }
+}
+
+void
+nsHtml5Tokenizer::appendStrBuf(char16_t* buffer, int32_t offset, int32_t length)
+{
+ int32_t newLen = nsHtml5Portability::checkedAdd(strBufLen, length);
+ MOZ_ASSERT(newLen <= strBuf.length, "Previous buffer length insufficient.");
+ if (MOZ_UNLIKELY(strBuf.length < newLen)) {
+ if (MOZ_UNLIKELY(!EnsureBufferSpace(length))) {
+ MOZ_CRASH("Unable to recover from buffer reallocation failure");
+ }
+ }
+ nsHtml5ArrayCopy::arraycopy(buffer, offset, strBuf, strBufLen, length);
+ strBufLen = newLen;
+}
+
+void
+nsHtml5Tokenizer::emitComment(int32_t provisionalHyphens, int32_t pos)
+{
+ tokenHandler->comment(strBuf, 0, strBufLen - provisionalHyphens);
+ clearStrBufAfterUse();
+ cstart = pos + 1;
+}
+
+void
+nsHtml5Tokenizer::flushChars(char16_t* buf, int32_t pos)
+{
+ if (pos > cstart) {
+ tokenHandler->characters(buf, cstart, pos - cstart);
+ }
+ cstart = INT32_MAX;
+}
+
+void
+nsHtml5Tokenizer::strBufToElementNameString()
+{
+ if (containsHyphen) {
+ nsIAtom* annotationName = nsHtml5ElementName::ELT_ANNOTATION_XML->getName();
+ if (nsHtml5Portability::localEqualsBuffer(annotationName, strBuf, 0, strBufLen)) {
+ tagName = nsHtml5ElementName::ELT_ANNOTATION_XML;
+ } else {
+ nonInternedTagName->setNameForNonInterned(nsHtml5Portability::newLocalNameFromBuffer(strBuf, 0, strBufLen, interner), true);
+ tagName = nonInternedTagName;
+ }
+ } else {
+ tagName = nsHtml5ElementName::elementNameByBuffer(strBuf, 0, strBufLen, interner);
+ if (!tagName) {
+ nonInternedTagName->setNameForNonInterned(nsHtml5Portability::newLocalNameFromBuffer(strBuf, 0, strBufLen, interner), false);
+ tagName = nonInternedTagName;
+ }
+ }
+ containsHyphen = false;
+ clearStrBufAfterUse();
+}
+
+int32_t
+nsHtml5Tokenizer::emitCurrentTagToken(bool selfClosing, int32_t pos)
+{
+ cstart = pos + 1;
+ maybeErrSlashInEndTag(selfClosing);
+ stateSave = nsHtml5Tokenizer::DATA;
+ nsHtml5HtmlAttributes* attrs = (!attributes ? nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES : attributes);
+ if (endTag) {
+ maybeErrAttributesOnEndTag(attrs);
+ if (!viewingXmlSource) {
+ tokenHandler->endTag(tagName);
+ }
+ if (newAttributesEachTime) {
+ delete attributes;
+ attributes = nullptr;
+ }
+ } else {
+ if (viewingXmlSource) {
+ MOZ_ASSERT(newAttributesEachTime);
+ delete attributes;
+ attributes = nullptr;
+ } else {
+ tokenHandler->startTag(tagName, attrs, selfClosing);
+ }
+ }
+ tagName = nullptr;
+ if (newAttributesEachTime) {
+ attributes = nullptr;
+ } else {
+ attributes->clear(0);
+ }
+ return stateSave;
+}
+
+void
+nsHtml5Tokenizer::attributeNameComplete()
+{
+ attributeName = nsHtml5AttributeName::nameByBuffer(strBuf, 0, strBufLen, interner);
+ if (!attributeName) {
+ nonInternedAttributeName->setNameForNonInterned(nsHtml5Portability::newLocalNameFromBuffer(strBuf, 0, strBufLen, interner));
+ attributeName = nonInternedAttributeName;
+ }
+ clearStrBufAfterUse();
+ if (!attributes) {
+ attributes = new nsHtml5HtmlAttributes(0);
+ }
+ if (attributes->contains(attributeName)) {
+ errDuplicateAttribute();
+ attributeName = nullptr;
+ }
+}
+
+void
+nsHtml5Tokenizer::addAttributeWithoutValue()
+{
+
+ if (attributeName) {
+ attributes->addAttribute(attributeName, nsHtml5Portability::newEmptyString(), attributeLine);
+ attributeName = nullptr;
+ } else {
+ clearStrBufAfterUse();
+ }
+}
+
+void
+nsHtml5Tokenizer::addAttributeWithValue()
+{
+ if (attributeName) {
+ nsHtml5String val = strBufToString();
+ if (mViewSource) {
+ mViewSource->MaybeLinkifyAttributeValue(attributeName, val);
+ }
+ attributes->addAttribute(attributeName, val, attributeLine);
+ attributeName = nullptr;
+ } else {
+ clearStrBufAfterUse();
+ }
+}
+
+void
+nsHtml5Tokenizer::start()
+{
+ initializeWithoutStarting();
+ tokenHandler->startTokenization(this);
+}
+
+bool
+nsHtml5Tokenizer::tokenizeBuffer(nsHtml5UTF16Buffer* buffer)
+{
+ int32_t state = stateSave;
+ int32_t returnState = returnStateSave;
+ char16_t c = '\0';
+ shouldSuspend = false;
+ lastCR = false;
+ int32_t start = buffer->getStart();
+ int32_t end = buffer->getEnd();
+ int32_t pos = start - 1;
+ switch(state) {
+ case DATA:
+ case RCDATA:
+ case SCRIPT_DATA:
+ case PLAINTEXT:
+ case RAWTEXT:
+ case CDATA_SECTION:
+ case SCRIPT_DATA_ESCAPED:
+ case SCRIPT_DATA_ESCAPE_START:
+ case SCRIPT_DATA_ESCAPE_START_DASH:
+ case SCRIPT_DATA_ESCAPED_DASH:
+ case SCRIPT_DATA_ESCAPED_DASH_DASH:
+ case SCRIPT_DATA_DOUBLE_ESCAPE_START:
+ case SCRIPT_DATA_DOUBLE_ESCAPED:
+ case SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN:
+ case SCRIPT_DATA_DOUBLE_ESCAPED_DASH:
+ case SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH:
+ case SCRIPT_DATA_DOUBLE_ESCAPE_END: {
+ cstart = start;
+ break;
+ }
+ default: {
+ cstart = INT32_MAX;
+ break;
+ }
+ }
+ if (mViewSource) {
+ mViewSource->SetBuffer(buffer);
+ pos = stateLoop<nsHtml5ViewSourcePolicy>(state, c, pos, buffer->getBuffer(), false, returnState, buffer->getEnd());
+ mViewSource->DropBuffer((pos == buffer->getEnd()) ? pos : pos + 1);
+ } else {
+ pos = stateLoop<nsHtml5SilentPolicy>(state, c, pos, buffer->getBuffer(), false, returnState, buffer->getEnd());
+ }
+ if (pos == end) {
+ buffer->setStart(pos);
+ } else {
+ buffer->setStart(pos + 1);
+ }
+ return lastCR;
+}
+
+template<class P>
+int32_t
+nsHtml5Tokenizer::stateLoop(int32_t state, char16_t c, int32_t pos, char16_t* buf, bool reconsume, int32_t returnState, int32_t endPos)
+{
+ stateloop: for (; ; ) {
+ switch(state) {
+ case DATA: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '&': {
+ flushChars(buf, pos);
+ MOZ_ASSERT(!charRefBufLen, "charRefBufLen not reset after previous use!");
+ appendCharRefBuf(c);
+ setAdditionalAndRememberAmpersandLocation('\0');
+ returnState = state;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '<': {
+ flushChars(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::TAG_OPEN, reconsume, pos);
+ NS_HTML5_BREAK(dataloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ continue;
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ dataloop_end: ;
+ }
+ case TAG_OPEN: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ if (c >= 'A' && c <= 'Z') {
+ endTag = false;
+ clearStrBufBeforeUse();
+ appendStrBuf((char16_t) (c + 0x20));
+ containsHyphen = false;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::TAG_NAME, reconsume, pos);
+ NS_HTML5_BREAK(tagopenloop);
+ } else if (c >= 'a' && c <= 'z') {
+ endTag = false;
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ containsHyphen = false;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::TAG_NAME, reconsume, pos);
+ NS_HTML5_BREAK(tagopenloop);
+ }
+ switch(c) {
+ case '!': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::MARKUP_DECLARATION_OPEN, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '/': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CLOSE_TAG_OPEN, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\?': {
+ if (viewingXmlSource) {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::PROCESSING_INSTRUCTION, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ if (P::reportErrors) {
+ errProcessingInstruction();
+ }
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errLtGt();
+ }
+ tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 2);
+ cstart = pos + 1;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ if (P::reportErrors) {
+ errBadCharAfterLt(c);
+ }
+ tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 1);
+ cstart = pos;
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ tagopenloop_end: ;
+ }
+ case TAG_NAME: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ strBufToElementNameString();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ strBufToElementNameString();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(tagnameloop);
+ }
+ case '/': {
+ strBufToElementNameString();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SELF_CLOSING_START_TAG, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ strBufToElementNameString();
+ state = P::transition(mViewSource, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ } else if (c == '-') {
+ containsHyphen = true;
+ }
+ appendStrBuf(c);
+ containsHyphen = false;
+ continue;
+ }
+ }
+ }
+ tagnameloop_end: ;
+ }
+ case BEFORE_ATTRIBUTE_NAME: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ continue;
+ }
+ case '/': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SELF_CLOSING_START_TAG, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ state = P::transition(mViewSource, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ case '\"':
+ case '\'':
+ case '<':
+ case '=': {
+ if (P::reportErrors) {
+ errBadCharBeforeAttributeNameOrNull(c);
+ }
+ }
+ default: {
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ attributeLine = line;
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(beforeattributenameloop);
+ }
+ }
+ }
+ beforeattributenameloop_end: ;
+ }
+ case ATTRIBUTE_NAME: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ attributeNameComplete();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ attributeNameComplete();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '/': {
+ attributeNameComplete();
+ addAttributeWithoutValue();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SELF_CLOSING_START_TAG, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '=': {
+ attributeNameComplete();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_VALUE, reconsume, pos);
+ NS_HTML5_BREAK(attributenameloop);
+ }
+ case '>': {
+ attributeNameComplete();
+ addAttributeWithoutValue();
+ state = P::transition(mViewSource, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ case '\"':
+ case '\'':
+ case '<': {
+ if (P::reportErrors) {
+ errQuoteOrLtInAttributeNameOrNull(c);
+ }
+ }
+ default: {
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+ attributenameloop_end: ;
+ }
+ case BEFORE_ATTRIBUTE_VALUE: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ continue;
+ }
+ case '\"': {
+ attributeLine = line;
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::ATTRIBUTE_VALUE_DOUBLE_QUOTED, reconsume, pos);
+ NS_HTML5_BREAK(beforeattributevalueloop);
+ }
+ case '&': {
+ attributeLine = line;
+ clearStrBufBeforeUse();
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::ATTRIBUTE_VALUE_UNQUOTED, reconsume, pos);
+
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\'': {
+ attributeLine = line;
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::ATTRIBUTE_VALUE_SINGLE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errAttributeValueMissing();
+ }
+ addAttributeWithoutValue();
+ state = P::transition(mViewSource, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ case '<':
+ case '=':
+ case '`': {
+ if (P::reportErrors) {
+ errLtOrEqualsOrGraveInUnquotedAttributeOrNull(c);
+ }
+ }
+ default: {
+ attributeLine = line;
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::ATTRIBUTE_VALUE_UNQUOTED, reconsume, pos);
+
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ beforeattributevalueloop_end: ;
+ }
+ case ATTRIBUTE_VALUE_DOUBLE_QUOTED: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '\"': {
+ addAttributeWithValue();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_ATTRIBUTE_VALUE_QUOTED, reconsume, pos);
+ NS_HTML5_BREAK(attributevaluedoublequotedloop);
+ }
+ case '&': {
+ MOZ_ASSERT(!charRefBufLen, "charRefBufLen not reset after previous use!");
+ appendCharRefBuf(c);
+ setAdditionalAndRememberAmpersandLocation('\"');
+ returnState = state;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ continue;
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+ attributevaluedoublequotedloop_end: ;
+ }
+ case AFTER_ATTRIBUTE_VALUE_QUOTED: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '/': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SELF_CLOSING_START_TAG, reconsume, pos);
+ NS_HTML5_BREAK(afterattributevaluequotedloop);
+ }
+ case '>': {
+ state = P::transition(mViewSource, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ if (P::reportErrors) {
+ errNoSpaceBetweenAttributes();
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ afterattributevaluequotedloop_end: ;
+ }
+ case SELF_CLOSING_START_TAG: {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '>': {
+ state = P::transition(mViewSource, emitCurrentTagToken(true, pos), reconsume, pos);
+ if (shouldSuspend) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ if (P::reportErrors) {
+ errSlashNotFollowedByGt();
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ case ATTRIBUTE_VALUE_UNQUOTED: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ addAttributeWithValue();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ addAttributeWithValue();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '&': {
+ MOZ_ASSERT(!charRefBufLen, "charRefBufLen not reset after previous use!");
+ appendCharRefBuf(c);
+ setAdditionalAndRememberAmpersandLocation('>');
+ returnState = state;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ addAttributeWithValue();
+ state = P::transition(mViewSource, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ case '<':
+ case '\"':
+ case '\'':
+ case '=':
+ case '`': {
+ if (P::reportErrors) {
+ errUnquotedAttributeValOrNull(c);
+ }
+ }
+ default: {
+
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+ }
+ case AFTER_ATTRIBUTE_NAME: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ continue;
+ }
+ case '/': {
+ addAttributeWithoutValue();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SELF_CLOSING_START_TAG, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '=': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_VALUE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ addAttributeWithoutValue();
+ state = P::transition(mViewSource, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ case '\"':
+ case '\'':
+ case '<': {
+ if (P::reportErrors) {
+ errQuoteOrLtInAttributeNameOrNull(c);
+ }
+ }
+ default: {
+ addAttributeWithoutValue();
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ }
+ case MARKUP_DECLARATION_OPEN: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::MARKUP_DECLARATION_HYPHEN, reconsume, pos);
+ NS_HTML5_BREAK(markupdeclarationopenloop);
+ }
+ case 'd':
+ case 'D': {
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ index = 0;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::MARKUP_DECLARATION_OCTYPE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '[': {
+ if (tokenHandler->cdataSectionAllowed()) {
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ index = 0;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CDATA_START, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ default: {
+ if (P::reportErrors) {
+ errBogusComment();
+ }
+ clearStrBufBeforeUse();
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ markupdeclarationopenloop_end: ;
+ }
+ case MARKUP_DECLARATION_HYPHEN: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ clearStrBufAfterOneHyphen();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT_START, reconsume, pos);
+ NS_HTML5_BREAK(markupdeclarationhyphenloop);
+ }
+ default: {
+ if (P::reportErrors) {
+ errBogusComment();
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ markupdeclarationhyphenloop_end: ;
+ }
+ case COMMENT_START: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT_START_DASH, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errPrematureEndOfComment();
+ }
+ emitComment(0, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_BREAK(commentstartloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_BREAK(commentstartloop);
+ }
+ }
+ }
+ commentstartloop_end: ;
+ }
+ case COMMENT: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT_END_DASH, reconsume, pos);
+ NS_HTML5_BREAK(commentloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ continue;
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+ commentloop_end: ;
+ }
+ case COMMENT_END_DASH: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT_END, reconsume, pos);
+ NS_HTML5_BREAK(commentenddashloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ commentenddashloop_end: ;
+ }
+ case COMMENT_END: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '>': {
+ emitComment(2, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '-': {
+ adjustDoubleHyphenAndAppendToStrBufAndErr(c);
+ continue;
+ }
+ case '\r': {
+ adjustDoubleHyphenAndAppendToStrBufCarriageReturn();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ adjustDoubleHyphenAndAppendToStrBufLineFeed();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '!': {
+ if (P::reportErrors) {
+ errHyphenHyphenBang();
+ }
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT_END_BANG, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ adjustDoubleHyphenAndAppendToStrBufAndErr(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+
+ }
+ case COMMENT_END_BANG: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '>': {
+ emitComment(3, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '-': {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT_END_DASH, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ continue;
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ }
+ case COMMENT_START_DASH: {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT_END, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errPrematureEndOfComment();
+ }
+ emitComment(1, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ case CDATA_START: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ if (index < 6) {
+ if (c == nsHtml5Tokenizer::CDATA_LSQB[index]) {
+ appendStrBuf(c);
+ } else {
+ if (P::reportErrors) {
+ errBogusComment();
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ index++;
+ continue;
+ } else {
+ clearStrBufAfterUse();
+ cstart = pos;
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CDATA_SECTION, reconsume, pos);
+ break;
+ }
+ }
+ }
+ case CDATA_SECTION: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case ']': {
+ flushChars(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CDATA_RSQB, reconsume, pos);
+ NS_HTML5_BREAK(cdatasectionloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ continue;
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ cdatasectionloop_end: ;
+ }
+ case CDATA_RSQB: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case ']': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CDATA_RSQB_RSQB, reconsume, pos);
+ NS_HTML5_BREAK(cdatarsqb);
+ }
+ default: {
+ tokenHandler->characters(nsHtml5Tokenizer::RSQB_RSQB, 0, 1);
+ cstart = pos;
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CDATA_SECTION, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ cdatarsqb_end: ;
+ }
+ case CDATA_RSQB_RSQB: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case ']': {
+ tokenHandler->characters(nsHtml5Tokenizer::RSQB_RSQB, 0, 1);
+ continue;
+ }
+ case '>': {
+ cstart = pos + 1;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ tokenHandler->characters(nsHtml5Tokenizer::RSQB_RSQB, 0, 2);
+ cstart = pos;
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CDATA_SECTION, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+
+ }
+ case ATTRIBUTE_VALUE_SINGLE_QUOTED: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '\'': {
+ addAttributeWithValue();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_ATTRIBUTE_VALUE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '&': {
+ MOZ_ASSERT(!charRefBufLen, "charRefBufLen not reset after previous use!");
+ appendCharRefBuf(c);
+ setAdditionalAndRememberAmpersandLocation('\'');
+ returnState = state;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, reconsume, pos);
+ NS_HTML5_BREAK(attributevaluesinglequotedloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ continue;
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+ attributevaluesinglequotedloop_end: ;
+ }
+ case CONSUME_CHARACTER_REFERENCE: {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\f':
+ case '<':
+ case '&':
+ case '\0': {
+ emitOrAppendCharRefBuf(returnState);
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '#': {
+ appendCharRefBuf('#');
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CONSUME_NCR, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ if (c == additional) {
+ emitOrAppendCharRefBuf(returnState);
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ if (c >= 'a' && c <= 'z') {
+ firstCharKey = c - 'a' + 26;
+ } else if (c >= 'A' && c <= 'Z') {
+ firstCharKey = c - 'A';
+ } else {
+ if (P::reportErrors) {
+ errNoNamedCharacterMatch();
+ }
+ emitOrAppendCharRefBuf(returnState);
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ appendCharRefBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CHARACTER_REFERENCE_HILO_LOOKUP, reconsume, pos);
+ }
+ }
+ }
+ case CHARACTER_REFERENCE_HILO_LOOKUP: {
+ {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ int32_t hilo = 0;
+ if (c <= 'z') {
+ const int32_t* row = nsHtml5NamedCharactersAccel::HILO_ACCEL[c];
+ if (row) {
+ hilo = row[firstCharKey];
+ }
+ }
+ if (!hilo) {
+ if (P::reportErrors) {
+ errNoNamedCharacterMatch();
+ }
+ emitOrAppendCharRefBuf(returnState);
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ appendCharRefBuf(c);
+ lo = hilo & 0xFFFF;
+ hi = hilo >> 16;
+ entCol = -1;
+ candidate = -1;
+ charRefBufMark = 0;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CHARACTER_REFERENCE_TAIL, reconsume, pos);
+ }
+ }
+ case CHARACTER_REFERENCE_TAIL: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ entCol++;
+ for (; ; ) {
+ if (hi < lo) {
+ NS_HTML5_BREAK(outer);
+ }
+ if (entCol == nsHtml5NamedCharacters::NAMES[lo].length()) {
+ candidate = lo;
+ charRefBufMark = charRefBufLen;
+ lo++;
+ } else if (entCol > nsHtml5NamedCharacters::NAMES[lo].length()) {
+ NS_HTML5_BREAK(outer);
+ } else if (c > nsHtml5NamedCharacters::NAMES[lo].charAt(entCol)) {
+ lo++;
+ } else {
+ NS_HTML5_BREAK(loloop);
+ }
+ }
+ loloop_end: ;
+ for (; ; ) {
+ if (hi < lo) {
+ NS_HTML5_BREAK(outer);
+ }
+ if (entCol == nsHtml5NamedCharacters::NAMES[hi].length()) {
+ NS_HTML5_BREAK(hiloop);
+ }
+ if (entCol > nsHtml5NamedCharacters::NAMES[hi].length()) {
+ NS_HTML5_BREAK(outer);
+ } else if (c < nsHtml5NamedCharacters::NAMES[hi].charAt(entCol)) {
+ hi--;
+ } else {
+ NS_HTML5_BREAK(hiloop);
+ }
+ }
+ hiloop_end: ;
+ if (c == ';') {
+ if (entCol + 1 == nsHtml5NamedCharacters::NAMES[lo].length()) {
+ candidate = lo;
+ charRefBufMark = charRefBufLen;
+ }
+ NS_HTML5_BREAK(outer);
+ }
+ if (hi < lo) {
+ NS_HTML5_BREAK(outer);
+ }
+ appendCharRefBuf(c);
+ continue;
+ }
+ outer_end: ;
+ if (candidate == -1) {
+ if (P::reportErrors) {
+ errNoNamedCharacterMatch();
+ }
+ emitOrAppendCharRefBuf(returnState);
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ } else {
+ const nsHtml5CharacterName& candidateName = nsHtml5NamedCharacters::NAMES[candidate];
+ if (!candidateName.length() || candidateName.charAt(candidateName.length() - 1) != ';') {
+ if ((returnState & DATA_AND_RCDATA_MASK)) {
+ char16_t ch;
+ if (charRefBufMark == charRefBufLen) {
+ ch = c;
+ } else {
+ ch = charRefBuf[charRefBufMark];
+ }
+ if (ch == '=' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
+ if (P::reportErrors) {
+ errNoNamedCharacterMatch();
+ }
+ appendCharRefBufToStrBuf();
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ if ((returnState & DATA_AND_RCDATA_MASK)) {
+ if (P::reportErrors) {
+ errUnescapedAmpersandInterpretedAsCharacterReference();
+ }
+ } else {
+ if (P::reportErrors) {
+ errNotSemicolonTerminated();
+ }
+ }
+ }
+ P::completedNamedCharacterReference(mViewSource);
+ const char16_t* val = nsHtml5NamedCharacters::VALUES[candidate];
+ if (!val[1]) {
+ emitOrAppendOne(val, returnState);
+ } else {
+ emitOrAppendTwo(val, returnState);
+ }
+ if (charRefBufMark < charRefBufLen) {
+ if ((returnState & DATA_AND_RCDATA_MASK)) {
+ appendStrBuf(charRefBuf, charRefBufMark, charRefBufLen - charRefBufMark);
+ } else {
+ tokenHandler->characters(charRefBuf, charRefBufMark, charRefBufLen - charRefBufMark);
+ }
+ }
+ bool earlyBreak = (c == ';' && charRefBufMark == charRefBufLen);
+ charRefBufLen = 0;
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = earlyBreak ? pos + 1 : pos;
+ }
+ reconsume = !earlyBreak;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ case CONSUME_NCR: {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ value = 0;
+ seenDigits = false;
+ switch(c) {
+ case 'x':
+ case 'X': {
+ appendCharRefBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::HEX_NCR_LOOP, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DECIMAL_NRC_LOOP, reconsume, pos);
+ }
+ }
+ }
+ case DECIMAL_NRC_LOOP: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ MOZ_ASSERT(value >= 0, "value must not become negative.");
+ if (c >= '0' && c <= '9') {
+ seenDigits = true;
+ if (value <= 0x10FFFF) {
+ value *= 10;
+ value += c - '0';
+ }
+ continue;
+ } else if (c == ';') {
+ if (seenDigits) {
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos + 1;
+ }
+ state = P::transition(mViewSource, nsHtml5Tokenizer::HANDLE_NCR_VALUE, reconsume, pos);
+ NS_HTML5_BREAK(decimalloop);
+ } else {
+ if (P::reportErrors) {
+ errNoDigitsInNCR();
+ }
+ appendCharRefBuf(';');
+ emitOrAppendCharRefBuf(returnState);
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos + 1;
+ }
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ } else {
+ if (!seenDigits) {
+ if (P::reportErrors) {
+ errNoDigitsInNCR();
+ }
+ emitOrAppendCharRefBuf(returnState);
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ } else {
+ if (P::reportErrors) {
+ errCharRefLacksSemicolon();
+ }
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::HANDLE_NCR_VALUE, reconsume, pos);
+ NS_HTML5_BREAK(decimalloop);
+ }
+ }
+ }
+ decimalloop_end: ;
+ }
+ case HANDLE_NCR_VALUE: {
+ charRefBufLen = 0;
+ handleNcrValue(returnState);
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case HEX_NCR_LOOP: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ MOZ_ASSERT(value >= 0, "value must not become negative.");
+ if (c >= '0' && c <= '9') {
+ seenDigits = true;
+ if (value <= 0x10FFFF) {
+ value *= 16;
+ value += c - '0';
+ }
+ continue;
+ } else if (c >= 'A' && c <= 'F') {
+ seenDigits = true;
+ if (value <= 0x10FFFF) {
+ value *= 16;
+ value += c - 'A' + 10;
+ }
+ continue;
+ } else if (c >= 'a' && c <= 'f') {
+ seenDigits = true;
+ if (value <= 0x10FFFF) {
+ value *= 16;
+ value += c - 'a' + 10;
+ }
+ continue;
+ } else if (c == ';') {
+ if (seenDigits) {
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos + 1;
+ }
+ state = P::transition(mViewSource, nsHtml5Tokenizer::HANDLE_NCR_VALUE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ } else {
+ if (P::reportErrors) {
+ errNoDigitsInNCR();
+ }
+ appendCharRefBuf(';');
+ emitOrAppendCharRefBuf(returnState);
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos + 1;
+ }
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ } else {
+ if (!seenDigits) {
+ if (P::reportErrors) {
+ errNoDigitsInNCR();
+ }
+ emitOrAppendCharRefBuf(returnState);
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ } else {
+ if (P::reportErrors) {
+ errCharRefLacksSemicolon();
+ }
+ if (!(returnState & DATA_AND_RCDATA_MASK)) {
+ cstart = pos;
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::HANDLE_NCR_VALUE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ }
+ case PLAINTEXT: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '\0': {
+ emitPlaintextReplacementCharacter(buf, pos);
+ continue;
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+
+ }
+ case CLOSE_TAG_OPEN: {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '>': {
+ if (P::reportErrors) {
+ errLtSlashGt();
+ }
+ cstart = pos + 1;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ silentCarriageReturn();
+ if (P::reportErrors) {
+ errGarbageAfterLtSlash();
+ }
+ clearStrBufBeforeUse();
+ appendStrBuf('\n');
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ if (P::reportErrors) {
+ errGarbageAfterLtSlash();
+ }
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ if (c >= 'a' && c <= 'z') {
+ endTag = true;
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::TAG_NAME, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ } else {
+ if (P::reportErrors) {
+ errGarbageAfterLtSlash();
+ }
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ }
+ case RCDATA: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '&': {
+ flushChars(buf, pos);
+ MOZ_ASSERT(!charRefBufLen, "charRefBufLen not reset after previous use!");
+ appendCharRefBuf(c);
+ setAdditionalAndRememberAmpersandLocation('\0');
+ returnState = state;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '<': {
+ flushChars(buf, pos);
+ returnState = state;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::RAWTEXT_RCDATA_LESS_THAN_SIGN, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ continue;
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+
+ }
+ case RAWTEXT: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '<': {
+ flushChars(buf, pos);
+ returnState = state;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::RAWTEXT_RCDATA_LESS_THAN_SIGN, reconsume, pos);
+ NS_HTML5_BREAK(rawtextloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ continue;
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ rawtextloop_end: ;
+ }
+ case RAWTEXT_RCDATA_LESS_THAN_SIGN: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '/': {
+ index = 0;
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::NON_DATA_END_TAG_NAME, reconsume, pos);
+ NS_HTML5_BREAK(rawtextrcdatalessthansignloop);
+ }
+ default: {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 1);
+ cstart = pos;
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ rawtextrcdatalessthansignloop_end: ;
+ }
+ case NON_DATA_END_TAG_NAME: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ if (!endTagExpectationAsArray) {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_SOLIDUS, 0, 2);
+ cstart = pos;
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ } else if (index < endTagExpectationAsArray.length) {
+ char16_t e = endTagExpectationAsArray[index];
+ char16_t folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded != e) {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_SOLIDUS, 0, 2);
+ emitStrBuf();
+ cstart = pos;
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ appendStrBuf(c);
+ index++;
+ continue;
+ } else {
+ endTag = true;
+ tagName = endTagExpectation;
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ clearStrBufAfterUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ clearStrBufAfterUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_ATTRIBUTE_NAME, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '/': {
+ clearStrBufAfterUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SELF_CLOSING_START_TAG, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ clearStrBufAfterUse();
+ state = P::transition(mViewSource, emitCurrentTagToken(false, pos), reconsume, pos);
+ if (shouldSuspend) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_SOLIDUS, 0, 2);
+ emitStrBuf();
+ cstart = pos;
+ reconsume = true;
+ state = P::transition(mViewSource, returnState, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ }
+ }
+ case BOGUS_COMMENT: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '>': {
+ emitComment(0, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '-': {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT_HYPHEN, reconsume, pos);
+ NS_HTML5_BREAK(boguscommentloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ continue;
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+ boguscommentloop_end: ;
+ }
+ case BOGUS_COMMENT_HYPHEN: {
+ boguscommenthyphenloop: for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '>': {
+ emitComment(0, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '-': {
+ appendSecondHyphenToBogusComment();
+ NS_HTML5_CONTINUE(boguscommenthyphenloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+
+ }
+ case SCRIPT_DATA: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '<': {
+ flushChars(buf, pos);
+ returnState = state;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_LESS_THAN_SIGN, reconsume, pos);
+ NS_HTML5_BREAK(scriptdataloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ continue;
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ scriptdataloop_end: ;
+ }
+ case SCRIPT_DATA_LESS_THAN_SIGN: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '/': {
+ index = 0;
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::NON_DATA_END_TAG_NAME, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '!': {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 1);
+ cstart = pos;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPE_START, reconsume, pos);
+ NS_HTML5_BREAK(scriptdatalessthansignloop);
+ }
+ default: {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 1);
+ cstart = pos;
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ scriptdatalessthansignloop_end: ;
+ }
+ case SCRIPT_DATA_ESCAPE_START: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPE_START_DASH, reconsume, pos);
+ NS_HTML5_BREAK(scriptdataescapestartloop);
+ }
+ default: {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ scriptdataescapestartloop_end: ;
+ }
+ case SCRIPT_DATA_ESCAPE_START_DASH: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED_DASH_DASH, reconsume, pos);
+ NS_HTML5_BREAK(scriptdataescapestartdashloop);
+ }
+ default: {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ scriptdataescapestartdashloop_end: ;
+ }
+ case SCRIPT_DATA_ESCAPED_DASH_DASH: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ continue;
+ }
+ case '<': {
+ flushChars(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_BREAK(scriptdataescapeddashdashloop);
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_BREAK(scriptdataescapeddashdashloop);
+ }
+ }
+ }
+ scriptdataescapeddashdashloop_end: ;
+ }
+ case SCRIPT_DATA_ESCAPED: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '-': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED_DASH, reconsume, pos);
+ NS_HTML5_BREAK(scriptdataescapedloop);
+ }
+ case '<': {
+ flushChars(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ continue;
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ scriptdataescapedloop_end: ;
+ }
+ case SCRIPT_DATA_ESCAPED_DASH: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED_DASH_DASH, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '<': {
+ flushChars(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ NS_HTML5_BREAK(scriptdataescapeddashloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ scriptdataescapeddashloop_end: ;
+ }
+ case SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '/': {
+ index = 0;
+ clearStrBufBeforeUse();
+ returnState = nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::NON_DATA_END_TAG_NAME, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case 'S':
+ case 's': {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 1);
+ cstart = pos;
+ index = 1;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPE_START, reconsume, pos);
+ NS_HTML5_BREAK(scriptdataescapedlessthanloop);
+ }
+ default: {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 1);
+ cstart = pos;
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ scriptdataescapedlessthanloop_end: ;
+ }
+ case SCRIPT_DATA_DOUBLE_ESCAPE_START: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ MOZ_ASSERT(index > 0);
+ if (index < 6) {
+ char16_t folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded != nsHtml5Tokenizer::SCRIPT_ARR[index]) {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ index++;
+ continue;
+ }
+ switch(c) {
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f':
+ case '/':
+ case '>': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_BREAK(scriptdatadoubleescapestartloop);
+ }
+ default: {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ scriptdatadoubleescapestartloop_end: ;
+ }
+ case SCRIPT_DATA_DOUBLE_ESCAPED: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '-': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED_DASH, reconsume, pos);
+ NS_HTML5_BREAK(scriptdatadoubleescapedloop);
+ }
+ case '<': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ continue;
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ scriptdatadoubleescapedloop_end: ;
+ }
+ case SCRIPT_DATA_DOUBLE_ESCAPED_DASH: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH, reconsume, pos);
+ NS_HTML5_BREAK(scriptdatadoubleescapeddashloop);
+ }
+ case '<': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ scriptdatadoubleescapeddashloop_end: ;
+ }
+ case SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '-': {
+ continue;
+ }
+ case '<': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN, reconsume, pos);
+ NS_HTML5_BREAK(scriptdatadoubleescapeddashdashloop);
+ }
+ case '>': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ emitReplacementCharacter(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ scriptdatadoubleescapeddashdashloop_end: ;
+ }
+ case SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '/': {
+ index = 0;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPE_END, reconsume, pos);
+ NS_HTML5_BREAK(scriptdatadoubleescapedlessthanloop);
+ }
+ default: {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ scriptdatadoubleescapedlessthanloop_end: ;
+ }
+ case SCRIPT_DATA_DOUBLE_ESCAPE_END: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ if (index < 6) {
+ char16_t folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded != nsHtml5Tokenizer::SCRIPT_ARR[index]) {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ index++;
+ continue;
+ }
+ switch(c) {
+ case '\r': {
+ emitCarriageReturn(buf, pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f':
+ case '/':
+ case '>': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::SCRIPT_DATA_DOUBLE_ESCAPED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+
+ }
+ case MARKUP_DECLARATION_OCTYPE: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ if (index < 6) {
+ char16_t folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded == nsHtml5Tokenizer::OCTYPE[index]) {
+ appendStrBuf(c);
+ } else {
+ if (P::reportErrors) {
+ errBogusComment();
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_COMMENT, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ index++;
+ continue;
+ } else {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE, reconsume, pos);
+ NS_HTML5_BREAK(markupdeclarationdoctypeloop);
+ }
+ }
+ markupdeclarationdoctypeloop_end: ;
+ }
+ case DOCTYPE: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ initDoctypeFields();
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_DOCTYPE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_DOCTYPE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(doctypeloop);
+ }
+ default: {
+ if (P::reportErrors) {
+ errMissingSpaceBeforeDoctypeName();
+ }
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_DOCTYPE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(doctypeloop);
+ }
+ }
+ }
+ doctypeloop_end: ;
+ }
+ case BEFORE_DOCTYPE_NAME: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ continue;
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errNamelessDoctype();
+ }
+ forceQuirks = true;
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x20;
+ }
+ clearStrBufBeforeUse();
+ appendStrBuf(c);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(beforedoctypenameloop);
+ }
+ }
+ }
+ beforedoctypenameloop_end: ;
+ }
+ case DOCTYPE_NAME: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ strBufToDoctypeName();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_DOCTYPE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ strBufToDoctypeName();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_DOCTYPE_NAME, reconsume, pos);
+ NS_HTML5_BREAK(doctypenameloop);
+ }
+ case '>': {
+ strBufToDoctypeName();
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ if (c >= 'A' && c <= 'Z') {
+ c += 0x0020;
+ }
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+ doctypenameloop_end: ;
+ }
+ case AFTER_DOCTYPE_NAME: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ continue;
+ }
+ case '>': {
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case 'p':
+ case 'P': {
+ index = 0;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_UBLIC, reconsume, pos);
+ NS_HTML5_BREAK(afterdoctypenameloop);
+ }
+ case 's':
+ case 'S': {
+ index = 0;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_YSTEM, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ bogusDoctype();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_DOCTYPE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ afterdoctypenameloop_end: ;
+ }
+ case DOCTYPE_UBLIC: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ if (index < 5) {
+ char16_t folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded != nsHtml5Tokenizer::UBLIC[index]) {
+ bogusDoctype();
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_DOCTYPE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ index++;
+ continue;
+ } else {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_DOCTYPE_PUBLIC_KEYWORD, reconsume, pos);
+ NS_HTML5_BREAK(doctypeublicloop);
+ }
+ }
+ doctypeublicloop_end: ;
+ }
+ case AFTER_DOCTYPE_PUBLIC_KEYWORD: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos);
+ NS_HTML5_BREAK(afterdoctypepublickeywordloop);
+ }
+ case '\"': {
+ if (P::reportErrors) {
+ errNoSpaceBetweenDoctypePublicKeywordAndQuote();
+ }
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\'': {
+ if (P::reportErrors) {
+ errNoSpaceBetweenDoctypePublicKeywordAndQuote();
+ }
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errExpectedPublicId();
+ }
+ forceQuirks = true;
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ bogusDoctype();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_DOCTYPE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ afterdoctypepublickeywordloop_end: ;
+ }
+ case BEFORE_DOCTYPE_PUBLIC_IDENTIFIER: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ continue;
+ }
+ case '\"': {
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ NS_HTML5_BREAK(beforedoctypepublicidentifierloop);
+ }
+ case '\'': {
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errExpectedPublicId();
+ }
+ forceQuirks = true;
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ bogusDoctype();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_DOCTYPE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ beforedoctypepublicidentifierloop_end: ;
+ }
+ case DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\"': {
+ publicIdentifier = strBufToString();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos);
+ NS_HTML5_BREAK(doctypepublicidentifierdoublequotedloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errGtInPublicId();
+ }
+ forceQuirks = true;
+ publicIdentifier = strBufToString();
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ continue;
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+ doctypepublicidentifierdoublequotedloop_end: ;
+ }
+ case AFTER_DOCTYPE_PUBLIC_IDENTIFIER: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS, reconsume, pos);
+ NS_HTML5_BREAK(afterdoctypepublicidentifierloop);
+ }
+ case '>': {
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\"': {
+ if (P::reportErrors) {
+ errNoSpaceBetweenPublicAndSystemIds();
+ }
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\'': {
+ if (P::reportErrors) {
+ errNoSpaceBetweenPublicAndSystemIds();
+ }
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ bogusDoctype();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_DOCTYPE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ afterdoctypepublicidentifierloop_end: ;
+ }
+ case BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ continue;
+ }
+ case '>': {
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\"': {
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ NS_HTML5_BREAK(betweendoctypepublicandsystemidentifiersloop);
+ }
+ case '\'': {
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ bogusDoctype();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_DOCTYPE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ betweendoctypepublicandsystemidentifiersloop_end: ;
+ }
+ case DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\"': {
+ systemIdentifier = strBufToString();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errGtInSystemId();
+ }
+ forceQuirks = true;
+ systemIdentifier = strBufToString();
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ continue;
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+
+ }
+ case AFTER_DOCTYPE_SYSTEM_IDENTIFIER: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ continue;
+ }
+ case '>': {
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ bogusDoctypeWithoutQuirks();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_DOCTYPE, reconsume, pos);
+ NS_HTML5_BREAK(afterdoctypesystemidentifierloop);
+ }
+ }
+ }
+ afterdoctypesystemidentifierloop_end: ;
+ }
+ case BOGUS_DOCTYPE: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '>': {
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ silentCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ }
+ case DOCTYPE_YSTEM: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ if (index < 5) {
+ char16_t folded = c;
+ if (c >= 'A' && c <= 'Z') {
+ folded += 0x20;
+ }
+ if (folded != nsHtml5Tokenizer::YSTEM[index]) {
+ bogusDoctype();
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_DOCTYPE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ index++;
+ NS_HTML5_CONTINUE(stateloop);
+ } else {
+ reconsume = true;
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_DOCTYPE_SYSTEM_KEYWORD, reconsume, pos);
+ NS_HTML5_BREAK(doctypeystemloop);
+ }
+ }
+ doctypeystemloop_end: ;
+ }
+ case AFTER_DOCTYPE_SYSTEM_KEYWORD: {
+ for (; ; ) {
+ if (reconsume) {
+ reconsume = false;
+ } else {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ }
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos);
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BEFORE_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos);
+ NS_HTML5_BREAK(afterdoctypesystemkeywordloop);
+ }
+ case '\"': {
+ if (P::reportErrors) {
+ errNoSpaceBetweenDoctypeSystemKeywordAndQuote();
+ }
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\'': {
+ if (P::reportErrors) {
+ errNoSpaceBetweenDoctypeSystemKeywordAndQuote();
+ }
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errExpectedPublicId();
+ }
+ forceQuirks = true;
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ bogusDoctype();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_DOCTYPE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ afterdoctypesystemkeywordloop_end: ;
+ }
+ case BEFORE_DOCTYPE_SYSTEM_IDENTIFIER: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\r': {
+ silentCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ silentLineFeed();
+ }
+ case ' ':
+ case '\t':
+ case '\f': {
+ continue;
+ }
+ case '\"': {
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\'': {
+ clearStrBufBeforeUse();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED, reconsume, pos);
+ NS_HTML5_BREAK(beforedoctypesystemidentifierloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errExpectedSystemId();
+ }
+ forceQuirks = true;
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ bogusDoctype();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::BOGUS_DOCTYPE, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ beforedoctypesystemidentifierloop_end: ;
+ }
+ case DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\'': {
+ systemIdentifier = strBufToString();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_DOCTYPE_SYSTEM_IDENTIFIER, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errGtInSystemId();
+ }
+ forceQuirks = true;
+ systemIdentifier = strBufToString();
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ continue;
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+ }
+ case DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\'': {
+ publicIdentifier = strBufToString();
+ state = P::transition(mViewSource, nsHtml5Tokenizer::AFTER_DOCTYPE_PUBLIC_IDENTIFIER, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '>': {
+ if (P::reportErrors) {
+ errGtInPublicId();
+ }
+ forceQuirks = true;
+ publicIdentifier = strBufToString();
+ emitDoctypeToken(pos);
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ case '\r': {
+ appendStrBufCarriageReturn();
+ NS_HTML5_BREAK(stateloop);
+ }
+ case '\n': {
+ appendStrBufLineFeed();
+ continue;
+ }
+ case '\0': {
+ c = 0xfffd;
+ }
+ default: {
+ appendStrBuf(c);
+ continue;
+ }
+ }
+ }
+ }
+ case PROCESSING_INSTRUCTION: {
+ for (; ; ) {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '\?': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::PROCESSING_INSTRUCTION_QUESTION_MARK, reconsume, pos);
+ NS_HTML5_BREAK(processinginstructionloop);
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ processinginstructionloop_end: ;
+ }
+ case PROCESSING_INSTRUCTION_QUESTION_MARK: {
+ if (++pos == endPos) {
+ NS_HTML5_BREAK(stateloop);
+ }
+ c = checkChar(buf, pos);
+ switch(c) {
+ case '>': {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::DATA, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ default: {
+ state = P::transition(mViewSource, nsHtml5Tokenizer::PROCESSING_INSTRUCTION, reconsume, pos);
+ NS_HTML5_CONTINUE(stateloop);
+ }
+ }
+ }
+ }
+ }
+ stateloop_end: ;
+ flushChars(buf, pos);
+ stateSave = state;
+ returnStateSave = returnState;
+ return pos;
+}
+
+void
+nsHtml5Tokenizer::initDoctypeFields()
+{
+ clearStrBufAfterUse();
+ doctypeName = nsHtml5Atoms::emptystring;
+ if (systemIdentifier) {
+ systemIdentifier.Release();
+ systemIdentifier = nullptr;
+ }
+ if (publicIdentifier) {
+ publicIdentifier.Release();
+ publicIdentifier = nullptr;
+ }
+ forceQuirks = false;
+}
+
+void
+nsHtml5Tokenizer::emitCarriageReturn(char16_t* buf, int32_t pos)
+{
+ silentCarriageReturn();
+ flushChars(buf, pos);
+ tokenHandler->characters(nsHtml5Tokenizer::LF, 0, 1);
+ cstart = INT32_MAX;
+}
+
+void
+nsHtml5Tokenizer::emitReplacementCharacter(char16_t* buf, int32_t pos)
+{
+ flushChars(buf, pos);
+ tokenHandler->zeroOriginatingReplacementCharacter();
+ cstart = pos + 1;
+}
+
+void
+nsHtml5Tokenizer::emitPlaintextReplacementCharacter(char16_t* buf, int32_t pos)
+{
+ flushChars(buf, pos);
+ tokenHandler->characters(REPLACEMENT_CHARACTER, 0, 1);
+ cstart = pos + 1;
+}
+
+void
+nsHtml5Tokenizer::setAdditionalAndRememberAmpersandLocation(char16_t add)
+{
+ additional = add;
+}
+
+void
+nsHtml5Tokenizer::bogusDoctype()
+{
+ errBogusDoctype();
+ forceQuirks = true;
+}
+
+void
+nsHtml5Tokenizer::bogusDoctypeWithoutQuirks()
+{
+ errBogusDoctype();
+ forceQuirks = false;
+}
+
+void
+nsHtml5Tokenizer::handleNcrValue(int32_t returnState)
+{
+ if (value <= 0xFFFF) {
+ if (value >= 0x80 && value <= 0x9f) {
+ errNcrInC1Range();
+ char16_t* val = nsHtml5NamedCharacters::WINDOWS_1252[value - 0x80];
+ emitOrAppendOne(val, returnState);
+ } else if (value == 0x0) {
+ errNcrZero();
+ emitOrAppendOne(nsHtml5Tokenizer::REPLACEMENT_CHARACTER, returnState);
+ } else if ((value & 0xF800) == 0xD800) {
+ errNcrSurrogate();
+ emitOrAppendOne(nsHtml5Tokenizer::REPLACEMENT_CHARACTER, returnState);
+ } else {
+ char16_t ch = (char16_t) value;
+ bmpChar[0] = ch;
+ emitOrAppendOne(bmpChar, returnState);
+ }
+ } else if (value <= 0x10FFFF) {
+ astralChar[0] = (char16_t) (nsHtml5Tokenizer::LEAD_OFFSET + (value >> 10));
+ astralChar[1] = (char16_t) (0xDC00 + (value & 0x3FF));
+ emitOrAppendTwo(astralChar, returnState);
+ } else {
+ errNcrOutOfRange();
+ emitOrAppendOne(nsHtml5Tokenizer::REPLACEMENT_CHARACTER, returnState);
+ }
+}
+
+void
+nsHtml5Tokenizer::eof()
+{
+ int32_t state = stateSave;
+ int32_t returnState = returnStateSave;
+ eofloop: for (; ; ) {
+ switch(state) {
+ case SCRIPT_DATA_LESS_THAN_SIGN:
+ case SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN: {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 1);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case TAG_OPEN: {
+ errEofAfterLt();
+ tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 1);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case RAWTEXT_RCDATA_LESS_THAN_SIGN: {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 1);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case NON_DATA_END_TAG_NAME: {
+ tokenHandler->characters(nsHtml5Tokenizer::LT_SOLIDUS, 0, 2);
+ emitStrBuf();
+ NS_HTML5_BREAK(eofloop);
+ }
+ case CLOSE_TAG_OPEN: {
+ errEofAfterLt();
+ tokenHandler->characters(nsHtml5Tokenizer::LT_SOLIDUS, 0, 2);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case TAG_NAME: {
+ errEofInTagName();
+ NS_HTML5_BREAK(eofloop);
+ }
+ case BEFORE_ATTRIBUTE_NAME:
+ case AFTER_ATTRIBUTE_VALUE_QUOTED:
+ case SELF_CLOSING_START_TAG: {
+ errEofWithoutGt();
+ NS_HTML5_BREAK(eofloop);
+ }
+ case ATTRIBUTE_NAME: {
+ errEofInAttributeName();
+ NS_HTML5_BREAK(eofloop);
+ }
+ case AFTER_ATTRIBUTE_NAME:
+ case BEFORE_ATTRIBUTE_VALUE: {
+ errEofWithoutGt();
+ NS_HTML5_BREAK(eofloop);
+ }
+ case ATTRIBUTE_VALUE_DOUBLE_QUOTED:
+ case ATTRIBUTE_VALUE_SINGLE_QUOTED:
+ case ATTRIBUTE_VALUE_UNQUOTED: {
+ errEofInAttributeValue();
+ NS_HTML5_BREAK(eofloop);
+ }
+ case BOGUS_COMMENT: {
+ emitComment(0, 0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case BOGUS_COMMENT_HYPHEN: {
+ emitComment(0, 0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case MARKUP_DECLARATION_OPEN: {
+ errBogusComment();
+ emitComment(0, 0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case MARKUP_DECLARATION_HYPHEN: {
+ errBogusComment();
+ emitComment(0, 0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case MARKUP_DECLARATION_OCTYPE: {
+ if (index < 6) {
+ errBogusComment();
+ emitComment(0, 0);
+ } else {
+ errEofInDoctype();
+ doctypeName = nsHtml5Atoms::emptystring;
+ if (systemIdentifier) {
+ systemIdentifier.Release();
+ systemIdentifier = nullptr;
+ }
+ if (publicIdentifier) {
+ publicIdentifier.Release();
+ publicIdentifier = nullptr;
+ }
+ forceQuirks = true;
+ emitDoctypeToken(0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ NS_HTML5_BREAK(eofloop);
+ }
+ case COMMENT_START:
+ case COMMENT: {
+ errEofInComment();
+ emitComment(0, 0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case COMMENT_END: {
+ errEofInComment();
+ emitComment(2, 0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case COMMENT_END_DASH:
+ case COMMENT_START_DASH: {
+ errEofInComment();
+ emitComment(1, 0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case COMMENT_END_BANG: {
+ errEofInComment();
+ emitComment(3, 0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case DOCTYPE:
+ case BEFORE_DOCTYPE_NAME: {
+ errEofInDoctype();
+ forceQuirks = true;
+ emitDoctypeToken(0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case DOCTYPE_NAME: {
+ errEofInDoctype();
+ strBufToDoctypeName();
+ forceQuirks = true;
+ emitDoctypeToken(0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case DOCTYPE_UBLIC:
+ case DOCTYPE_YSTEM:
+ case AFTER_DOCTYPE_NAME:
+ case AFTER_DOCTYPE_PUBLIC_KEYWORD:
+ case AFTER_DOCTYPE_SYSTEM_KEYWORD:
+ case BEFORE_DOCTYPE_PUBLIC_IDENTIFIER: {
+ errEofInDoctype();
+ forceQuirks = true;
+ emitDoctypeToken(0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED:
+ case DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED: {
+ errEofInPublicId();
+ forceQuirks = true;
+ publicIdentifier = strBufToString();
+ emitDoctypeToken(0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case AFTER_DOCTYPE_PUBLIC_IDENTIFIER:
+ case BEFORE_DOCTYPE_SYSTEM_IDENTIFIER:
+ case BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS: {
+ errEofInDoctype();
+ forceQuirks = true;
+ emitDoctypeToken(0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED:
+ case DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED: {
+ errEofInSystemId();
+ forceQuirks = true;
+ systemIdentifier = strBufToString();
+ emitDoctypeToken(0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case AFTER_DOCTYPE_SYSTEM_IDENTIFIER: {
+ errEofInDoctype();
+ forceQuirks = true;
+ emitDoctypeToken(0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case BOGUS_DOCTYPE: {
+ emitDoctypeToken(0);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case CONSUME_CHARACTER_REFERENCE: {
+ emitOrAppendCharRefBuf(returnState);
+ state = returnState;
+ continue;
+ }
+ case CHARACTER_REFERENCE_HILO_LOOKUP: {
+ errNoNamedCharacterMatch();
+ emitOrAppendCharRefBuf(returnState);
+ state = returnState;
+ continue;
+ }
+ case CHARACTER_REFERENCE_TAIL: {
+ for (; ; ) {
+ char16_t c = '\0';
+ entCol++;
+ for (; ; ) {
+ if (hi == -1) {
+ NS_HTML5_BREAK(hiloop);
+ }
+ if (entCol == nsHtml5NamedCharacters::NAMES[hi].length()) {
+ NS_HTML5_BREAK(hiloop);
+ }
+ if (entCol > nsHtml5NamedCharacters::NAMES[hi].length()) {
+ NS_HTML5_BREAK(outer);
+ } else if (c < nsHtml5NamedCharacters::NAMES[hi].charAt(entCol)) {
+ hi--;
+ } else {
+ NS_HTML5_BREAK(hiloop);
+ }
+ }
+ hiloop_end: ;
+ for (; ; ) {
+ if (hi < lo) {
+ NS_HTML5_BREAK(outer);
+ }
+ if (entCol == nsHtml5NamedCharacters::NAMES[lo].length()) {
+ candidate = lo;
+ charRefBufMark = charRefBufLen;
+ lo++;
+ } else if (entCol > nsHtml5NamedCharacters::NAMES[lo].length()) {
+ NS_HTML5_BREAK(outer);
+ } else if (c > nsHtml5NamedCharacters::NAMES[lo].charAt(entCol)) {
+ lo++;
+ } else {
+ NS_HTML5_BREAK(loloop);
+ }
+ }
+ loloop_end: ;
+ if (hi < lo) {
+ NS_HTML5_BREAK(outer);
+ }
+ continue;
+ }
+ outer_end: ;
+ if (candidate == -1) {
+ errNoNamedCharacterMatch();
+ emitOrAppendCharRefBuf(returnState);
+ state = returnState;
+ NS_HTML5_CONTINUE(eofloop);
+ } else {
+ const nsHtml5CharacterName& candidateName = nsHtml5NamedCharacters::NAMES[candidate];
+ if (!candidateName.length() || candidateName.charAt(candidateName.length() - 1) != ';') {
+ if ((returnState & DATA_AND_RCDATA_MASK)) {
+ char16_t ch;
+ if (charRefBufMark == charRefBufLen) {
+ ch = '\0';
+ } else {
+ ch = charRefBuf[charRefBufMark];
+ }
+ if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
+ errNoNamedCharacterMatch();
+ appendCharRefBufToStrBuf();
+ state = returnState;
+ NS_HTML5_CONTINUE(eofloop);
+ }
+ }
+ if ((returnState & DATA_AND_RCDATA_MASK)) {
+ errUnescapedAmpersandInterpretedAsCharacterReference();
+ } else {
+ errNotSemicolonTerminated();
+ }
+ }
+ const char16_t* val = nsHtml5NamedCharacters::VALUES[candidate];
+ if (!val[1]) {
+ emitOrAppendOne(val, returnState);
+ } else {
+ emitOrAppendTwo(val, returnState);
+ }
+ if (charRefBufMark < charRefBufLen) {
+ if ((returnState & DATA_AND_RCDATA_MASK)) {
+ appendStrBuf(charRefBuf, charRefBufMark, charRefBufLen - charRefBufMark);
+ } else {
+ tokenHandler->characters(charRefBuf, charRefBufMark, charRefBufLen - charRefBufMark);
+ }
+ }
+ charRefBufLen = 0;
+ state = returnState;
+ NS_HTML5_CONTINUE(eofloop);
+ }
+ }
+ case CONSUME_NCR:
+ case DECIMAL_NRC_LOOP:
+ case HEX_NCR_LOOP: {
+ if (!seenDigits) {
+ errNoDigitsInNCR();
+ emitOrAppendCharRefBuf(returnState);
+ state = returnState;
+ continue;
+ } else {
+ errCharRefLacksSemicolon();
+ }
+ handleNcrValue(returnState);
+ state = returnState;
+ continue;
+ }
+ case CDATA_RSQB: {
+ tokenHandler->characters(nsHtml5Tokenizer::RSQB_RSQB, 0, 1);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case CDATA_RSQB_RSQB: {
+ tokenHandler->characters(nsHtml5Tokenizer::RSQB_RSQB, 0, 2);
+ NS_HTML5_BREAK(eofloop);
+ }
+ case DATA:
+ default: {
+ NS_HTML5_BREAK(eofloop);
+ }
+ }
+ }
+ eofloop_end: ;
+ tokenHandler->eof();
+ return;
+}
+
+void
+nsHtml5Tokenizer::emitDoctypeToken(int32_t pos)
+{
+ cstart = pos + 1;
+ tokenHandler->doctype(doctypeName, publicIdentifier, systemIdentifier, forceQuirks);
+ doctypeName = nullptr;
+ publicIdentifier.Release();
+ publicIdentifier = nullptr;
+ systemIdentifier.Release();
+ systemIdentifier = nullptr;
+}
+
+bool
+nsHtml5Tokenizer::internalEncodingDeclaration(nsHtml5String internalCharset)
+{
+ if (encodingDeclarationHandler) {
+ return encodingDeclarationHandler->internalEncodingDeclaration(internalCharset);
+ }
+ return false;
+}
+
+void
+nsHtml5Tokenizer::emitOrAppendTwo(const char16_t* val, int32_t returnState)
+{
+ if ((returnState & DATA_AND_RCDATA_MASK)) {
+ appendStrBuf(val[0]);
+ appendStrBuf(val[1]);
+ } else {
+ tokenHandler->characters(val, 0, 2);
+ }
+}
+
+void
+nsHtml5Tokenizer::emitOrAppendOne(const char16_t* val, int32_t returnState)
+{
+ if ((returnState & DATA_AND_RCDATA_MASK)) {
+ appendStrBuf(val[0]);
+ } else {
+ tokenHandler->characters(val, 0, 1);
+ }
+}
+
+void
+nsHtml5Tokenizer::end()
+{
+ strBuf = nullptr;
+ doctypeName = nullptr;
+ if (systemIdentifier) {
+ systemIdentifier.Release();
+ systemIdentifier = nullptr;
+ }
+ if (publicIdentifier) {
+ publicIdentifier.Release();
+ publicIdentifier = nullptr;
+ }
+ tagName = nullptr;
+ nonInternedTagName->setNameForNonInterned(nullptr, false);
+ attributeName = nullptr;
+ nonInternedAttributeName->setNameForNonInterned(nullptr);
+ tokenHandler->endTokenization();
+ if (attributes) {
+ attributes->clear(0);
+ }
+}
+
+void
+nsHtml5Tokenizer::requestSuspension()
+{
+ shouldSuspend = true;
+}
+
+bool
+nsHtml5Tokenizer::isInDataState()
+{
+ return (stateSave == DATA);
+}
+
+void
+nsHtml5Tokenizer::resetToDataState()
+{
+ clearStrBufAfterUse();
+ charRefBufLen = 0;
+ stateSave = nsHtml5Tokenizer::DATA;
+ lastCR = false;
+ index = 0;
+ forceQuirks = false;
+ additional = '\0';
+ entCol = -1;
+ firstCharKey = -1;
+ lo = 0;
+ hi = 0;
+ candidate = -1;
+ charRefBufMark = 0;
+ value = 0;
+ seenDigits = false;
+ endTag = false;
+ shouldSuspend = false;
+ initDoctypeFields();
+ containsHyphen = false;
+ tagName = nullptr;
+ attributeName = nullptr;
+ if (newAttributesEachTime) {
+ if (attributes) {
+ delete attributes;
+ attributes = nullptr;
+ }
+ }
+}
+
+void
+nsHtml5Tokenizer::loadState(nsHtml5Tokenizer* other)
+{
+ strBufLen = other->strBufLen;
+ if (strBufLen > strBuf.length) {
+ strBuf = jArray<char16_t,int32_t>::newJArray(strBufLen);
+ }
+ nsHtml5ArrayCopy::arraycopy(other->strBuf, strBuf, strBufLen);
+ charRefBufLen = other->charRefBufLen;
+ nsHtml5ArrayCopy::arraycopy(other->charRefBuf, charRefBuf, charRefBufLen);
+ stateSave = other->stateSave;
+ returnStateSave = other->returnStateSave;
+ endTagExpectation = other->endTagExpectation;
+ endTagExpectationAsArray = other->endTagExpectationAsArray;
+ lastCR = other->lastCR;
+ index = other->index;
+ forceQuirks = other->forceQuirks;
+ additional = other->additional;
+ entCol = other->entCol;
+ firstCharKey = other->firstCharKey;
+ lo = other->lo;
+ hi = other->hi;
+ candidate = other->candidate;
+ charRefBufMark = other->charRefBufMark;
+ value = other->value;
+ seenDigits = other->seenDigits;
+ endTag = other->endTag;
+ shouldSuspend = false;
+ if (!other->doctypeName) {
+ doctypeName = nullptr;
+ } else {
+ doctypeName = nsHtml5Portability::newLocalFromLocal(other->doctypeName, interner);
+ }
+ systemIdentifier.Release();
+ if (!other->systemIdentifier) {
+ systemIdentifier = nullptr;
+ } else {
+ systemIdentifier = nsHtml5Portability::newStringFromString(other->systemIdentifier);
+ }
+ publicIdentifier.Release();
+ if (!other->publicIdentifier) {
+ publicIdentifier = nullptr;
+ } else {
+ publicIdentifier = nsHtml5Portability::newStringFromString(other->publicIdentifier);
+ }
+ containsHyphen = other->containsHyphen;
+ if (!other->tagName) {
+ tagName = nullptr;
+ } else if (other->tagName->isInterned()) {
+ tagName = other->tagName;
+ } else {
+ nonInternedTagName->setNameForNonInterned(nsHtml5Portability::newLocalFromLocal(other->tagName->getName(), interner), other->tagName->isCustom());
+ tagName = nonInternedTagName;
+ }
+ if (!other->attributeName) {
+ attributeName = nullptr;
+ } else if (other->attributeName->isInterned()) {
+ attributeName = other->attributeName;
+ } else {
+ nonInternedAttributeName->setNameForNonInterned(nsHtml5Portability::newLocalFromLocal(other->attributeName->getLocal(nsHtml5AttributeName::HTML), interner));
+ attributeName = nonInternedAttributeName;
+ }
+ delete attributes;
+ if (!other->attributes) {
+ attributes = nullptr;
+ } else {
+ attributes = other->attributes->cloneAttributes(interner);
+ }
+}
+
+void
+nsHtml5Tokenizer::initializeWithoutStarting()
+{
+ confident = false;
+ strBuf = nullptr;
+ line = 1;
+ attributeLine = 1;
+ resetToDataState();
+}
+
+void
+nsHtml5Tokenizer::setEncodingDeclarationHandler(nsHtml5StreamParser* encodingDeclarationHandler)
+{
+ this->encodingDeclarationHandler = encodingDeclarationHandler;
+}
+
+
+nsHtml5Tokenizer::~nsHtml5Tokenizer()
+{
+ MOZ_COUNT_DTOR(nsHtml5Tokenizer);
+ delete nonInternedTagName;
+ delete nonInternedAttributeName;
+ nonInternedTagName = nullptr;
+ delete attributes;
+ attributes = nullptr;
+}
+
+void
+nsHtml5Tokenizer::initializeStatics()
+{
+}
+
+void
+nsHtml5Tokenizer::releaseStatics()
+{
+}
+
+
+#include "nsHtml5TokenizerCppSupplement.h"
+
diff --git a/components/htmlfive/nsHtml5Tokenizer.h b/components/htmlfive/nsHtml5Tokenizer.h
new file mode 100644
index 000000000..0e36226e6
--- /dev/null
+++ b/components/htmlfive/nsHtml5Tokenizer.h
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2005-2007 Henri Sivonen
+ * Copyright (c) 2007-2015 Mozilla Foundation
+ * Copyright (c) 2018-2021 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ * Portions of comments Copyright 2004-2010 Apple Computer, Inc., Mozilla
+ * Foundation, and Opera Software ASA.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit Tokenizer.java instead and regenerate.
+ */
+
+#ifndef nsHtml5Tokenizer_h
+#define nsHtml5Tokenizer_h
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5DocumentMode.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsHtml5NamedCharacters.h"
+#include "nsHtml5NamedCharactersAccel.h"
+#include "nsHtml5Atoms.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Macros.h"
+#include "nsHtml5Highlighter.h"
+#include "nsHtml5TokenizerLoopPolicies.h"
+
+class nsHtml5StreamParser;
+
+class nsHtml5AttributeName;
+class nsHtml5ElementName;
+class nsHtml5TreeBuilder;
+class nsHtml5MetaScanner;
+class nsHtml5UTF16Buffer;
+class nsHtml5StateSnapshot;
+class nsHtml5Portability;
+
+
+class nsHtml5Tokenizer
+{
+ private:
+ static const int32_t DATA_AND_RCDATA_MASK = ~1;
+
+ public:
+ static const int32_t DATA = 0;
+
+ static const int32_t RCDATA = 1;
+
+ static const int32_t SCRIPT_DATA = 2;
+
+ static const int32_t RAWTEXT = 3;
+
+ static const int32_t SCRIPT_DATA_ESCAPED = 4;
+
+ static const int32_t ATTRIBUTE_VALUE_DOUBLE_QUOTED = 5;
+
+ static const int32_t ATTRIBUTE_VALUE_SINGLE_QUOTED = 6;
+
+ static const int32_t ATTRIBUTE_VALUE_UNQUOTED = 7;
+
+ static const int32_t PLAINTEXT = 8;
+
+ static const int32_t TAG_OPEN = 9;
+
+ static const int32_t CLOSE_TAG_OPEN = 10;
+
+ static const int32_t TAG_NAME = 11;
+
+ static const int32_t BEFORE_ATTRIBUTE_NAME = 12;
+
+ static const int32_t ATTRIBUTE_NAME = 13;
+
+ static const int32_t AFTER_ATTRIBUTE_NAME = 14;
+
+ static const int32_t BEFORE_ATTRIBUTE_VALUE = 15;
+
+ static const int32_t AFTER_ATTRIBUTE_VALUE_QUOTED = 16;
+
+ static const int32_t BOGUS_COMMENT = 17;
+
+ static const int32_t MARKUP_DECLARATION_OPEN = 18;
+
+ static const int32_t DOCTYPE = 19;
+
+ static const int32_t BEFORE_DOCTYPE_NAME = 20;
+
+ static const int32_t DOCTYPE_NAME = 21;
+
+ static const int32_t AFTER_DOCTYPE_NAME = 22;
+
+ static const int32_t BEFORE_DOCTYPE_PUBLIC_IDENTIFIER = 23;
+
+ static const int32_t DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED = 24;
+
+ static const int32_t DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED = 25;
+
+ static const int32_t AFTER_DOCTYPE_PUBLIC_IDENTIFIER = 26;
+
+ static const int32_t BEFORE_DOCTYPE_SYSTEM_IDENTIFIER = 27;
+
+ static const int32_t DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED = 28;
+
+ static const int32_t DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED = 29;
+
+ static const int32_t AFTER_DOCTYPE_SYSTEM_IDENTIFIER = 30;
+
+ static const int32_t BOGUS_DOCTYPE = 31;
+
+ static const int32_t COMMENT_START = 32;
+
+ static const int32_t COMMENT_START_DASH = 33;
+
+ static const int32_t COMMENT = 34;
+
+ static const int32_t COMMENT_END_DASH = 35;
+
+ static const int32_t COMMENT_END = 36;
+
+ static const int32_t COMMENT_END_BANG = 37;
+
+ static const int32_t NON_DATA_END_TAG_NAME = 38;
+
+ static const int32_t MARKUP_DECLARATION_HYPHEN = 39;
+
+ static const int32_t MARKUP_DECLARATION_OCTYPE = 40;
+
+ static const int32_t DOCTYPE_UBLIC = 41;
+
+ static const int32_t DOCTYPE_YSTEM = 42;
+
+ static const int32_t AFTER_DOCTYPE_PUBLIC_KEYWORD = 43;
+
+ static const int32_t BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS = 44;
+
+ static const int32_t AFTER_DOCTYPE_SYSTEM_KEYWORD = 45;
+
+ static const int32_t CONSUME_CHARACTER_REFERENCE = 46;
+
+ static const int32_t CONSUME_NCR = 47;
+
+ static const int32_t CHARACTER_REFERENCE_TAIL = 48;
+
+ static const int32_t HEX_NCR_LOOP = 49;
+
+ static const int32_t DECIMAL_NRC_LOOP = 50;
+
+ static const int32_t HANDLE_NCR_VALUE = 51;
+
+ static const int32_t HANDLE_NCR_VALUE_RECONSUME = 52;
+
+ static const int32_t CHARACTER_REFERENCE_HILO_LOOKUP = 53;
+
+ static const int32_t SELF_CLOSING_START_TAG = 54;
+
+ static const int32_t CDATA_START = 55;
+
+ static const int32_t CDATA_SECTION = 56;
+
+ static const int32_t CDATA_RSQB = 57;
+
+ static const int32_t CDATA_RSQB_RSQB = 58;
+
+ static const int32_t SCRIPT_DATA_LESS_THAN_SIGN = 59;
+
+ static const int32_t SCRIPT_DATA_ESCAPE_START = 60;
+
+ static const int32_t SCRIPT_DATA_ESCAPE_START_DASH = 61;
+
+ static const int32_t SCRIPT_DATA_ESCAPED_DASH = 62;
+
+ static const int32_t SCRIPT_DATA_ESCAPED_DASH_DASH = 63;
+
+ static const int32_t BOGUS_COMMENT_HYPHEN = 64;
+
+ static const int32_t RAWTEXT_RCDATA_LESS_THAN_SIGN = 65;
+
+ static const int32_t SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN = 66;
+
+ static const int32_t SCRIPT_DATA_DOUBLE_ESCAPE_START = 67;
+
+ static const int32_t SCRIPT_DATA_DOUBLE_ESCAPED = 68;
+
+ static const int32_t SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN = 69;
+
+ static const int32_t SCRIPT_DATA_DOUBLE_ESCAPED_DASH = 70;
+
+ static const int32_t SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH = 71;
+
+ static const int32_t SCRIPT_DATA_DOUBLE_ESCAPE_END = 72;
+
+ static const int32_t PROCESSING_INSTRUCTION = 73;
+
+ static const int32_t PROCESSING_INSTRUCTION_QUESTION_MARK = 74;
+
+ private:
+ static const int32_t LEAD_OFFSET = (0xD800 - (0x10000 >> 10));
+
+ static char16_t LT_GT[];
+ static char16_t LT_SOLIDUS[];
+ static char16_t RSQB_RSQB[];
+ static char16_t REPLACEMENT_CHARACTER[];
+ static char16_t LF[];
+ static char16_t CDATA_LSQB[];
+ static char16_t OCTYPE[];
+ static char16_t UBLIC[];
+ static char16_t YSTEM[];
+ static staticJArray<char16_t,int32_t> TITLE_ARR;
+ static staticJArray<char16_t,int32_t> SCRIPT_ARR;
+ static staticJArray<char16_t,int32_t> STYLE_ARR;
+ static staticJArray<char16_t,int32_t> PLAINTEXT_ARR;
+ static staticJArray<char16_t,int32_t> XMP_ARR;
+ static staticJArray<char16_t,int32_t> TEXTAREA_ARR;
+ static staticJArray<char16_t,int32_t> IFRAME_ARR;
+ static staticJArray<char16_t,int32_t> NOEMBED_ARR;
+ static staticJArray<char16_t,int32_t> NOSCRIPT_ARR;
+ static staticJArray<char16_t,int32_t> NOFRAMES_ARR;
+ protected:
+ nsHtml5TreeBuilder* tokenHandler;
+ nsHtml5StreamParser* encodingDeclarationHandler;
+ bool lastCR;
+ int32_t stateSave;
+ private:
+ int32_t returnStateSave;
+ protected:
+ int32_t index;
+ private:
+ bool forceQuirks;
+ char16_t additional;
+ int32_t entCol;
+ int32_t firstCharKey;
+ int32_t lo;
+ int32_t hi;
+ int32_t candidate;
+ int32_t charRefBufMark;
+ protected:
+ int32_t value;
+ private:
+ bool seenDigits;
+ protected:
+ int32_t cstart;
+ private:
+ nsHtml5String publicId;
+ nsHtml5String systemId;
+ autoJArray<char16_t,int32_t> strBuf;
+ int32_t strBufLen;
+ autoJArray<char16_t,int32_t> charRefBuf;
+ int32_t charRefBufLen;
+ autoJArray<char16_t,int32_t> bmpChar;
+ autoJArray<char16_t,int32_t> astralChar;
+ protected:
+ nsHtml5ElementName* endTagExpectation;
+ private:
+ jArray<char16_t,int32_t> endTagExpectationAsArray;
+ protected:
+ bool endTag;
+ private:
+ bool containsHyphen;
+ nsHtml5ElementName* tagName;
+ nsHtml5ElementName* nonInternedTagName;
+ protected:
+ nsHtml5AttributeName* attributeName;
+ private:
+ nsHtml5AttributeName* nonInternedAttributeName;
+ nsIAtom* doctypeName;
+ nsHtml5String publicIdentifier;
+ nsHtml5String systemIdentifier;
+ nsHtml5HtmlAttributes* attributes;
+ bool newAttributesEachTime;
+ bool shouldSuspend;
+ protected:
+ bool confident;
+ private:
+ int32_t line;
+ int32_t attributeLine;
+ nsHtml5AtomTable* interner;
+ bool viewingXmlSource;
+ public:
+ nsHtml5Tokenizer(nsHtml5TreeBuilder* tokenHandler, bool viewingXmlSource);
+ void setInterner(nsHtml5AtomTable* interner);
+ void initLocation(nsHtml5String newPublicId, nsHtml5String newSystemId);
+ bool isViewingXmlSource();
+ void setState(int32_t specialTokenizerState);
+ void setStateAndEndTagExpectation(int32_t specialTokenizerState, nsHtml5ElementName* endTagExpectation);
+ private:
+ void endTagExpectationToArray();
+ public:
+ void setLineNumber(int32_t line);
+ inline int32_t getLineNumber()
+ {
+ return line;
+ }
+
+ nsHtml5HtmlAttributes* emptyAttributes();
+ private:
+ inline void appendCharRefBuf(char16_t c)
+ {
+ MOZ_RELEASE_ASSERT(charRefBufLen < charRefBuf.length, "Attempted to overrun charRefBuf!");
+ charRefBuf[charRefBufLen++] = c;
+ }
+
+ void emitOrAppendCharRefBuf(int32_t returnState);
+ inline void clearStrBufAfterUse()
+ {
+ strBufLen = 0;
+ }
+
+ inline void clearStrBufBeforeUse()
+ {
+ MOZ_ASSERT(!strBufLen, "strBufLen not reset after previous use!");
+ strBufLen = 0;
+ }
+
+ inline void clearStrBufAfterOneHyphen()
+ {
+ MOZ_ASSERT(strBufLen == 1, "strBufLen length not one!");
+ MOZ_ASSERT(strBuf[0] == '-', "strBuf does not start with a hyphen!");
+ strBufLen = 0;
+ }
+
+ inline void appendStrBuf(char16_t c)
+ {
+ MOZ_ASSERT(strBufLen < strBuf.length, "Previous buffer length insufficient.");
+ if (MOZ_UNLIKELY(strBufLen == strBuf.length)) {
+ if (MOZ_UNLIKELY(!EnsureBufferSpace(1))) {
+ MOZ_CRASH("Unable to recover from buffer reallocation failure");
+ }
+ }
+ strBuf[strBufLen++] = c;
+ }
+
+ protected:
+ nsHtml5String strBufToString();
+ private:
+ void strBufToDoctypeName();
+ void emitStrBuf();
+ inline void appendSecondHyphenToBogusComment()
+ {
+ appendStrBuf('-');
+ }
+
+ inline void adjustDoubleHyphenAndAppendToStrBufAndErr(char16_t c)
+ {
+ errConsecutiveHyphens();
+ appendStrBuf(c);
+ }
+
+ void appendStrBuf(char16_t* buffer, int32_t offset, int32_t length);
+ inline void appendCharRefBufToStrBuf()
+ {
+ appendStrBuf(charRefBuf, 0, charRefBufLen);
+ charRefBufLen = 0;
+ }
+
+ void emitComment(int32_t provisionalHyphens, int32_t pos);
+ protected:
+ void flushChars(char16_t* buf, int32_t pos);
+ private:
+ void strBufToElementNameString();
+ int32_t emitCurrentTagToken(bool selfClosing, int32_t pos);
+ void attributeNameComplete();
+ void addAttributeWithoutValue();
+ void addAttributeWithValue();
+ public:
+ void start();
+ bool tokenizeBuffer(nsHtml5UTF16Buffer* buffer);
+ private:
+ template<class P> int32_t stateLoop(int32_t state, char16_t c, int32_t pos, char16_t* buf, bool reconsume, int32_t returnState, int32_t endPos);
+ void initDoctypeFields();
+ inline void adjustDoubleHyphenAndAppendToStrBufCarriageReturn()
+ {
+ silentCarriageReturn();
+ adjustDoubleHyphenAndAppendToStrBufAndErr('\n');
+ }
+
+ inline void adjustDoubleHyphenAndAppendToStrBufLineFeed()
+ {
+ silentLineFeed();
+ adjustDoubleHyphenAndAppendToStrBufAndErr('\n');
+ }
+
+ inline void appendStrBufLineFeed()
+ {
+ silentLineFeed();
+ appendStrBuf('\n');
+ }
+
+ inline void appendStrBufCarriageReturn()
+ {
+ silentCarriageReturn();
+ appendStrBuf('\n');
+ }
+
+ protected:
+ inline void silentCarriageReturn()
+ {
+ ++line;
+ lastCR = true;
+ }
+
+ inline void silentLineFeed()
+ {
+ ++line;
+ }
+
+ private:
+ void emitCarriageReturn(char16_t* buf, int32_t pos);
+ void emitReplacementCharacter(char16_t* buf, int32_t pos);
+ void emitPlaintextReplacementCharacter(char16_t* buf, int32_t pos);
+ void setAdditionalAndRememberAmpersandLocation(char16_t add);
+ void bogusDoctype();
+ void bogusDoctypeWithoutQuirks();
+ void handleNcrValue(int32_t returnState);
+ public:
+ void eof();
+ private:
+ void emitDoctypeToken(int32_t pos);
+ protected:
+ inline char16_t checkChar(char16_t* buf, int32_t pos)
+ {
+ return buf[pos];
+ }
+
+ public:
+ bool internalEncodingDeclaration(nsHtml5String internalCharset);
+ private:
+ void emitOrAppendTwo(const char16_t* val, int32_t returnState);
+ void emitOrAppendOne(const char16_t* val, int32_t returnState);
+ public:
+ void end();
+ void requestSuspension();
+ bool isInDataState();
+ void resetToDataState();
+ void loadState(nsHtml5Tokenizer* other);
+ void initializeWithoutStarting();
+ void setEncodingDeclarationHandler(nsHtml5StreamParser* encodingDeclarationHandler);
+ ~nsHtml5Tokenizer();
+ static void initializeStatics();
+ static void releaseStatics();
+
+#include "nsHtml5TokenizerHSupplement.h"
+};
+
+#endif
+
diff --git a/components/htmlfive/nsHtml5TokenizerCppSupplement.h b/components/htmlfive/nsHtml5TokenizerCppSupplement.h
new file mode 100644
index 000000000..343fceadd
--- /dev/null
+++ b/components/htmlfive/nsHtml5TokenizerCppSupplement.h
@@ -0,0 +1,585 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Likely.h"
+
+// INT32_MAX is (2^31)-1. Therefore, the highest power-of-two that fits
+// is 2^30. Note that this is counting char16_t units. The underlying
+// bytes will be twice that, but they fit even in 32-bit size_t even
+// if a contiguous chunk of memory of that size is pretty unlikely to
+// be available on a 32-bit system.
+#define MAX_POWER_OF_TWO_IN_INT32 0x40000000
+
+bool
+nsHtml5Tokenizer::EnsureBufferSpace(int32_t aLength)
+{
+ MOZ_RELEASE_ASSERT(aLength >= 0, "Negative length.");
+ if (aLength > MAX_POWER_OF_TWO_IN_INT32) {
+ // Can't happen when loading from network.
+ return false;
+ }
+ CheckedInt<int32_t> worstCase(strBufLen);
+ worstCase += aLength;
+ worstCase += charRefBufLen;
+ // Add 2 to account for emissions of LT_GT, LT_SOLIDUS and RSQB_RSQB.
+ // Adding to the general worst case instead of only the
+ // TreeBuilder-exposed worst case to avoid re-introducing a bug when
+ // unifying the tokenizer and tree builder buffers in the future.
+ worstCase += 2;
+ if (!worstCase.isValid()) {
+ return false;
+ }
+ if (worstCase.value() > MAX_POWER_OF_TWO_IN_INT32) {
+ return false;
+ }
+ // TODO: Unify nsHtml5Tokenizer::strBuf and nsHtml5TreeBuilder::charBuffer
+ // so that the call below becomes unnecessary.
+ if (!tokenHandler->EnsureBufferSpace(worstCase.value())) {
+ return false;
+ }
+ if (!strBuf) {
+ if (worstCase.value() < MAX_POWER_OF_TWO_IN_INT32) {
+ // Add one to round to the next power of two to avoid immediate
+ // reallocation once there are a few characters in the buffer.
+ worstCase += 1;
+ }
+ strBuf = jArray<char16_t,int32_t>::newFallibleJArray(mozilla::RoundUpPow2(worstCase.value()));
+ if (!strBuf) {
+ return false;
+ }
+ } else if (worstCase.value() > strBuf.length) {
+ jArray<char16_t,int32_t> newBuf = jArray<char16_t,int32_t>::newFallibleJArray(mozilla::RoundUpPow2(worstCase.value()));
+ if (!newBuf) {
+ return false;
+ }
+ memcpy(newBuf, strBuf, sizeof(char16_t) * size_t(strBufLen));
+ strBuf = newBuf;
+ }
+ return true;
+}
+
+void
+nsHtml5Tokenizer::StartPlainText()
+{
+ stateSave = nsHtml5Tokenizer::PLAINTEXT;
+}
+
+void
+nsHtml5Tokenizer::EnableViewSource(nsHtml5Highlighter* aHighlighter)
+{
+ mViewSource = aHighlighter;
+}
+
+bool
+nsHtml5Tokenizer::FlushViewSource()
+{
+ return mViewSource->FlushOps();
+}
+
+void
+nsHtml5Tokenizer::StartViewSource(const nsAutoString& aTitle)
+{
+ mViewSource->Start(aTitle);
+}
+
+void
+nsHtml5Tokenizer::EndViewSource()
+{
+ mViewSource->End();
+}
+
+void
+nsHtml5Tokenizer::errWarnLtSlashInRcdata()
+{
+}
+
+// The null checks below annotated MOZ_LIKELY are not actually necessary.
+
+void
+nsHtml5Tokenizer::errUnquotedAttributeValOrNull(char16_t c)
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ switch (c) {
+ case '<':
+ mViewSource->AddErrorToCurrentNode("errUnquotedAttributeLt");
+ return;
+ case '`':
+ mViewSource->AddErrorToCurrentNode("errUnquotedAttributeGrave");
+ return;
+ case '\'':
+ case '"':
+ mViewSource->AddErrorToCurrentNode("errUnquotedAttributeQuote");
+ return;
+ case '=':
+ mViewSource->AddErrorToCurrentNode("errUnquotedAttributeEquals");
+ return;
+ }
+ }
+}
+
+void
+nsHtml5Tokenizer::errLtOrEqualsOrGraveInUnquotedAttributeOrNull(char16_t c)
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ switch (c) {
+ case '=':
+ mViewSource->AddErrorToCurrentNode("errUnquotedAttributeStartEquals");
+ return;
+ case '<':
+ mViewSource->AddErrorToCurrentNode("errUnquotedAttributeStartLt");
+ return;
+ case '`':
+ mViewSource->AddErrorToCurrentNode("errUnquotedAttributeStartGrave");
+ return;
+ }
+ }
+}
+
+void
+nsHtml5Tokenizer::errBadCharBeforeAttributeNameOrNull(char16_t c)
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ if (c == '<') {
+ mViewSource->AddErrorToCurrentNode("errBadCharBeforeAttributeNameLt");
+ } else if (c == '=') {
+ errEqualsSignBeforeAttributeName();
+ } else if (c != 0xFFFD) {
+ errQuoteBeforeAttributeName(c);
+ }
+ }
+}
+
+void
+nsHtml5Tokenizer::errBadCharAfterLt(char16_t c)
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errBadCharAfterLt");
+ }
+}
+
+void
+nsHtml5Tokenizer::errQuoteOrLtInAttributeNameOrNull(char16_t c)
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ if (c == '<') {
+ mViewSource->AddErrorToCurrentNode("errLtInAttributeName");
+ } else if (c != 0xFFFD) {
+ mViewSource->AddErrorToCurrentNode("errQuoteInAttributeName");
+ }
+ }
+}
+
+void
+nsHtml5Tokenizer::maybeErrAttributesOnEndTag(nsHtml5HtmlAttributes* attrs)
+{
+ if (mViewSource && attrs->getLength() != 0) {
+ /*
+ * When an end tag token is emitted with attributes, that is a parse
+ * error.
+ */
+ mViewSource->AddErrorToCurrentRun("maybeErrAttributesOnEndTag");
+ }
+}
+
+void
+nsHtml5Tokenizer::maybeErrSlashInEndTag(bool selfClosing)
+{
+ if (mViewSource && selfClosing && endTag) {
+ mViewSource->AddErrorToCurrentSlash("maybeErrSlashInEndTag");
+ }
+}
+
+char16_t
+nsHtml5Tokenizer::errNcrNonCharacter(char16_t ch)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNcrNonCharacter");
+ }
+ return ch;
+}
+
+void
+nsHtml5Tokenizer::errAstralNonCharacter(int32_t ch)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNcrNonCharacter");
+ }
+}
+
+char16_t
+nsHtml5Tokenizer::errNcrControlChar(char16_t ch)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNcrControlChar");
+ }
+ return ch;
+}
+
+void
+nsHtml5Tokenizer::errGarbageAfterLtSlash()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errGarbageAfterLtSlash");
+ }
+}
+
+void
+nsHtml5Tokenizer::errLtSlashGt()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errLtSlashGt");
+ }
+}
+
+void
+nsHtml5Tokenizer::errCharRefLacksSemicolon()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errCharRefLacksSemicolon");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNoDigitsInNCR()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNoDigitsInNCR");
+ }
+}
+
+void
+nsHtml5Tokenizer::errGtInSystemId()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errGtInSystemId");
+ }
+}
+
+void
+nsHtml5Tokenizer::errGtInPublicId()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errGtInPublicId");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNamelessDoctype()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNamelessDoctype");
+ }
+}
+
+void
+nsHtml5Tokenizer::errConsecutiveHyphens()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errConsecutiveHyphens");
+ }
+}
+
+void
+nsHtml5Tokenizer::errPrematureEndOfComment()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errPrematureEndOfComment");
+ }
+}
+
+void
+nsHtml5Tokenizer::errBogusComment()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errBogusComment");
+ }
+}
+
+void
+nsHtml5Tokenizer::errSlashNotFollowedByGt()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentSlash("errSlashNotFollowedByGt");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNoSpaceBetweenAttributes()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNoSpaceBetweenAttributes");
+ }
+}
+
+void
+nsHtml5Tokenizer::errAttributeValueMissing()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errAttributeValueMissing");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEqualsSignBeforeAttributeName()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errEqualsSignBeforeAttributeName");
+ }
+}
+
+void
+nsHtml5Tokenizer::errLtGt()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errLtGt");
+ }
+}
+
+void
+nsHtml5Tokenizer::errProcessingInstruction()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errProcessingInstruction");
+ }
+}
+
+void
+nsHtml5Tokenizer::errUnescapedAmpersandInterpretedAsCharacterReference()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentAmpersand("errUnescapedAmpersandInterpretedAsCharacterReference");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNotSemicolonTerminated()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNotSemicolonTerminated");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNoNamedCharacterMatch()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentAmpersand("errNoNamedCharacterMatch");
+ }
+}
+
+void
+nsHtml5Tokenizer::errQuoteBeforeAttributeName(char16_t c)
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errQuoteBeforeAttributeName");
+ }
+}
+
+void
+nsHtml5Tokenizer::errExpectedPublicId()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errExpectedPublicId");
+ }
+}
+
+void
+nsHtml5Tokenizer::errBogusDoctype()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errBogusDoctype");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNcrSurrogate()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNcrSurrogate");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNcrCr()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNcrCr");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNcrInC1Range()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNcrInC1Range");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEofInPublicId()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEofInPublicId");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEofInComment()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEofInComment");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEofInDoctype()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEofInDoctype");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEofInAttributeValue()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEofInAttributeValue");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEofInAttributeName()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEofInAttributeName");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEofWithoutGt()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEofWithoutGt");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEofInTagName()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEofInTagName");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEofInEndTag()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEofInEndTag");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEofAfterLt()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEofAfterLt");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNcrOutOfRange()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNcrOutOfRange");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNcrUnassigned()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNcrUnassigned");
+ }
+}
+
+void
+nsHtml5Tokenizer::errDuplicateAttribute()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errDuplicateAttribute");
+ }
+}
+
+void
+nsHtml5Tokenizer::errEofInSystemId()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEofInSystemId");
+ }
+}
+
+void
+nsHtml5Tokenizer::errExpectedSystemId()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errExpectedSystemId");
+ }
+}
+
+void
+nsHtml5Tokenizer::errMissingSpaceBeforeDoctypeName()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errMissingSpaceBeforeDoctypeName");
+ }
+}
+
+void
+nsHtml5Tokenizer::errHyphenHyphenBang()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errHyphenHyphenBang");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNcrControlChar()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNcrControlChar");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNcrZero()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNcrZero");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNoSpaceBetweenDoctypeSystemKeywordAndQuote()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNoSpaceBetweenDoctypeSystemKeywordAndQuote");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNoSpaceBetweenPublicAndSystemIds()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNoSpaceBetweenPublicAndSystemIds");
+ }
+}
+
+void
+nsHtml5Tokenizer::errNoSpaceBetweenDoctypePublicKeywordAndQuote()
+{
+ if (MOZ_LIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentNode("errNoSpaceBetweenDoctypePublicKeywordAndQuote");
+ }
+}
diff --git a/components/htmlfive/nsHtml5TokenizerHSupplement.h b/components/htmlfive/nsHtml5TokenizerHSupplement.h
new file mode 100644
index 000000000..a899feec9
--- /dev/null
+++ b/components/htmlfive/nsHtml5TokenizerHSupplement.h
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+inline nsHtml5HtmlAttributes* GetAttributes()
+{
+ return attributes;
+}
+
+/**
+ * Makes sure the buffers are large enough to be able to tokenize aLength
+ * UTF-16 code units before having to make the buffers larger.
+ *
+ * @param aLength the number of UTF-16 code units to be tokenized before the
+ * next call to this method.
+ * @return true if successful; false if out of memory
+ */
+bool EnsureBufferSpace(int32_t aLength);
+
+nsAutoPtr<nsHtml5Highlighter> mViewSource;
+
+/**
+ * Starts handling text/plain. This is a one-way initialization. There is
+ * no corresponding EndPlainText() call.
+ */
+void StartPlainText();
+
+void EnableViewSource(nsHtml5Highlighter* aHighlighter);
+
+bool FlushViewSource();
+
+void StartViewSource(const nsAutoString& aTitle);
+
+void EndViewSource();
+
+void errGarbageAfterLtSlash();
+
+void errLtSlashGt();
+
+void errWarnLtSlashInRcdata();
+
+void errCharRefLacksSemicolon();
+
+void errNoDigitsInNCR();
+
+void errGtInSystemId();
+
+void errGtInPublicId();
+
+void errNamelessDoctype();
+
+void errConsecutiveHyphens();
+
+void errPrematureEndOfComment();
+
+void errBogusComment();
+
+void errUnquotedAttributeValOrNull(char16_t c);
+
+void errSlashNotFollowedByGt();
+
+void errNoSpaceBetweenAttributes();
+
+void errLtOrEqualsOrGraveInUnquotedAttributeOrNull(char16_t c);
+
+void errAttributeValueMissing();
+
+void errBadCharBeforeAttributeNameOrNull(char16_t c);
+
+void errEqualsSignBeforeAttributeName();
+
+void errBadCharAfterLt(char16_t c);
+
+void errLtGt();
+
+void errProcessingInstruction();
+
+void errUnescapedAmpersandInterpretedAsCharacterReference();
+
+void errNotSemicolonTerminated();
+
+void errNoNamedCharacterMatch();
+
+void errQuoteBeforeAttributeName(char16_t c);
+
+void errQuoteOrLtInAttributeNameOrNull(char16_t c);
+
+void errExpectedPublicId();
+
+void errBogusDoctype();
+
+void maybeErrAttributesOnEndTag(nsHtml5HtmlAttributes* attrs);
+
+void maybeErrSlashInEndTag(bool selfClosing);
+
+char16_t errNcrNonCharacter(char16_t ch);
+
+void errAstralNonCharacter(int32_t ch);
+
+void errNcrSurrogate();
+
+char16_t errNcrControlChar(char16_t ch);
+
+void errNcrCr();
+
+void errNcrInC1Range();
+
+void errEofInPublicId();
+
+void errEofInComment();
+
+void errEofInDoctype();
+
+void errEofInAttributeValue();
+
+void errEofInAttributeName();
+
+void errEofWithoutGt();
+
+void errEofInTagName();
+
+void errEofInEndTag();
+
+void errEofAfterLt();
+
+void errNcrOutOfRange();
+
+void errNcrUnassigned();
+
+void errDuplicateAttribute();
+
+void errEofInSystemId();
+
+void errExpectedSystemId();
+
+void errMissingSpaceBeforeDoctypeName();
+
+void errHyphenHyphenBang();
+
+void errNcrControlChar();
+
+void errNcrZero();
+
+void errNoSpaceBetweenDoctypeSystemKeywordAndQuote();
+
+void errNoSpaceBetweenPublicAndSystemIds();
+
+void errNoSpaceBetweenDoctypePublicKeywordAndQuote();
diff --git a/components/htmlfive/nsHtml5TokenizerLoopPolicies.h b/components/htmlfive/nsHtml5TokenizerLoopPolicies.h
new file mode 100644
index 000000000..8e5869a62
--- /dev/null
+++ b/components/htmlfive/nsHtml5TokenizerLoopPolicies.h
@@ -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/. */
+
+#ifndef nsHtml5TokenizerLoopPolicies_h
+#define nsHtml5TokenizerLoopPolicies_h
+
+/**
+ * This policy does not report tokenizer transitions anywhere. To be used
+ * when _not_ viewing source.
+ */
+struct nsHtml5SilentPolicy
+{
+ static const bool reportErrors = false;
+ static int32_t transition(nsHtml5Highlighter* aHighlighter,
+ int32_t aState,
+ bool aReconsume,
+ int32_t aPos)
+ {
+ return aState;
+ }
+ static void completedNamedCharacterReference(nsHtml5Highlighter* aHighlighter)
+ {
+ }
+};
+
+/**
+ * This policy reports the tokenizer transitions to a highlighter. To be used
+ * when viewing source.
+ */
+struct nsHtml5ViewSourcePolicy
+{
+ static const bool reportErrors = true;
+ static int32_t transition(nsHtml5Highlighter* aHighlighter,
+ int32_t aState,
+ bool aReconsume,
+ int32_t aPos)
+ {
+ return aHighlighter->Transition(aState, aReconsume, aPos);
+ }
+ static void completedNamedCharacterReference(nsHtml5Highlighter* aHighlighter)
+ {
+ aHighlighter->CompletedNamedCharacterReference();
+ }
+};
+
+#endif // nsHtml5TokenizerLoopPolicies_h
diff --git a/components/htmlfive/nsHtml5TreeBuilder.cpp b/components/htmlfive/nsHtml5TreeBuilder.cpp
new file mode 100644
index 000000000..76a2398f5
--- /dev/null
+++ b/components/htmlfive/nsHtml5TreeBuilder.cpp
@@ -0,0 +1,4522 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2015 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ * Portions of comments Copyright 2004-2008 Apple Computer, Inc., Mozilla
+ * Foundation, and Opera Software ASA.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit TreeBuilder.java instead and regenerate.
+ */
+
+#define nsHtml5TreeBuilder_cpp__
+
+#include "nsContentUtils.h"
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsITimer.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5DocumentMode.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsHtml5Parser.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5TreeOperation.h"
+#include "nsHtml5StateSnapshot.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "nsHtml5StreamParser.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Highlighter.h"
+#include "nsHtml5PlainTextUtils.h"
+#include "nsHtml5ViewSourceUtils.h"
+#include "mozilla/Likely.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5OplessBuilder.h"
+
+#include "nsHtml5AttributeName.h"
+#include "nsHtml5ElementName.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5MetaScanner.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5UTF16Buffer.h"
+#include "nsHtml5StateSnapshot.h"
+#include "nsHtml5Portability.h"
+
+#include "nsHtml5TreeBuilder.h"
+
+char16_t nsHtml5TreeBuilder::REPLACEMENT_CHARACTER[] = { 0xfffd };
+static const char* const QUIRKY_PUBLIC_IDS_DATA[] = { "+//silmaril//dtd html pro v0r11 19970101//", "-//advasoft ltd//dtd html 3.0 aswedit + extensions//", "-//as//dtd html 3.0 aswedit + extensions//", "-//ietf//dtd html 2.0 level 1//", "-//ietf//dtd html 2.0 level 2//", "-//ietf//dtd html 2.0 strict level 1//", "-//ietf//dtd html 2.0 strict level 2//", "-//ietf//dtd html 2.0 strict//", "-//ietf//dtd html 2.0//", "-//ietf//dtd html 2.1e//", "-//ietf//dtd html 3.0//", "-//ietf//dtd html 3.2 final//", "-//ietf//dtd html 3.2//", "-//ietf//dtd html 3//", "-//ietf//dtd html level 0//", "-//ietf//dtd html level 1//", "-//ietf//dtd html level 2//", "-//ietf//dtd html level 3//", "-//ietf//dtd html strict level 0//", "-//ietf//dtd html strict level 1//", "-//ietf//dtd html strict level 2//", "-//ietf//dtd html strict level 3//", "-//ietf//dtd html strict//", "-//ietf//dtd html//", "-//metrius//dtd metrius presentational//", "-//microsoft//dtd internet explorer 2.0 html strict//", "-//microsoft//dtd internet explorer 2.0 html//", "-//microsoft//dtd internet explorer 2.0 tables//", "-//microsoft//dtd internet explorer 3.0 html strict//", "-//microsoft//dtd internet explorer 3.0 html//", "-//microsoft//dtd internet explorer 3.0 tables//", "-//netscape comm. corp.//dtd html//", "-//netscape comm. corp.//dtd strict html//", "-//o'reilly and associates//dtd html 2.0//", "-//o'reilly and associates//dtd html extended 1.0//", "-//o'reilly and associates//dtd html extended relaxed 1.0//", "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", "-//spyglass//dtd html 2.0 extended//", "-//sq//dtd html 2.0 hotmetal + extensions//", "-//sun microsystems corp.//dtd hotjava html//", "-//sun microsystems corp.//dtd hotjava strict html//", "-//w3c//dtd html 3 1995-03-24//", "-//w3c//dtd html 3.2 draft//", "-//w3c//dtd html 3.2 final//", "-//w3c//dtd html 3.2//", "-//w3c//dtd html 3.2s draft//", "-//w3c//dtd html 4.0 frameset//", "-//w3c//dtd html 4.0 transitional//", "-//w3c//dtd html experimental 19960712//", "-//w3c//dtd html experimental 970421//", "-//w3c//dtd w3 html//", "-//w3o//dtd w3 html 3.0//", "-//webtechs//dtd mozilla html 2.0//", "-//webtechs//dtd mozilla html//" };
+staticJArray<const char*,int32_t> nsHtml5TreeBuilder::QUIRKY_PUBLIC_IDS = { QUIRKY_PUBLIC_IDS_DATA, MOZ_ARRAY_LENGTH(QUIRKY_PUBLIC_IDS_DATA) };
+void
+nsHtml5TreeBuilder::startTokenization(nsHtml5Tokenizer* self)
+{
+ tokenizer = self;
+ stack = jArray<nsHtml5StackNode*,int32_t>::newJArray(64);
+ templateModeStack = jArray<int32_t,int32_t>::newJArray(64);
+ listOfActiveFormattingElements = jArray<nsHtml5StackNode*,int32_t>::newJArray(64);
+ needToDropLF = false;
+ originalMode = INITIAL;
+ templateModePtr = -1;
+ currentPtr = -1;
+ listPtr = -1;
+ formPointer = nullptr;
+ headPointer = nullptr;
+ deepTreeSurrogateParent = nullptr;
+ start(fragment);
+ charBufferLen = 0;
+ charBuffer = nullptr;
+ framesetOk = true;
+ if (fragment) {
+ nsIContentHandle* elt;
+ if (contextNode) {
+ elt = contextNode;
+ } else {
+ elt = createHtmlElementSetAsRoot(tokenizer->emptyAttributes());
+ }
+ if (contextNamespace == kNameSpaceID_SVG) {
+ nsHtml5ElementName* elementName = nsHtml5ElementName::ELT_SVG;
+ if (nsHtml5Atoms::title == contextName || nsHtml5Atoms::desc == contextName || nsHtml5Atoms::foreignObject == contextName) {
+ elementName = nsHtml5ElementName::ELT_FOREIGNOBJECT;
+ }
+ nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elementName->getCamelCaseName(), elt);
+ currentPtr++;
+ stack[currentPtr] = node;
+ tokenizer->setState(nsHtml5Tokenizer::DATA);
+ mode = FRAMESET_OK;
+ } else if (contextNamespace == kNameSpaceID_MathML) {
+ nsHtml5ElementName* elementName = nsHtml5ElementName::ELT_MATH;
+ if (nsHtml5Atoms::mi == contextName || nsHtml5Atoms::mo == contextName || nsHtml5Atoms::mn == contextName || nsHtml5Atoms::ms == contextName || nsHtml5Atoms::mtext == contextName) {
+ elementName = nsHtml5ElementName::ELT_MTEXT;
+ } else if (nsHtml5Atoms::annotation_xml == contextName) {
+ elementName = nsHtml5ElementName::ELT_ANNOTATION_XML;
+ }
+ nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt, elementName->getName(), false);
+ currentPtr++;
+ stack[currentPtr] = node;
+ tokenizer->setState(nsHtml5Tokenizer::DATA);
+ mode = FRAMESET_OK;
+ } else {
+ nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_HTML, elt);
+ currentPtr++;
+ stack[currentPtr] = node;
+ if (nsHtml5Atoms::template_ == contextName) {
+ pushTemplateMode(IN_TEMPLATE);
+ }
+ resetTheInsertionMode();
+ formPointer = getFormPointerForContext(contextNode);
+ if (nsHtml5Atoms::title == contextName || nsHtml5Atoms::textarea == contextName) {
+ tokenizer->setState(nsHtml5Tokenizer::RCDATA);
+ } else if (nsHtml5Atoms::style == contextName || nsHtml5Atoms::xmp == contextName || nsHtml5Atoms::iframe == contextName || nsHtml5Atoms::noembed == contextName || nsHtml5Atoms::noframes == contextName || (scriptingEnabled && nsHtml5Atoms::noscript == contextName)) {
+ tokenizer->setState(nsHtml5Tokenizer::RAWTEXT);
+ } else if (nsHtml5Atoms::plaintext == contextName) {
+ tokenizer->setState(nsHtml5Tokenizer::PLAINTEXT);
+ } else if (nsHtml5Atoms::script == contextName) {
+ tokenizer->setState(nsHtml5Tokenizer::SCRIPT_DATA);
+ } else {
+ tokenizer->setState(nsHtml5Tokenizer::DATA);
+ }
+ }
+ } else {
+ mode = INITIAL;
+ if (tokenizer->isViewingXmlSource()) {
+ nsIContentHandle* elt = createElement(kNameSpaceID_SVG, nsHtml5Atoms::svg, tokenizer->emptyAttributes(), nullptr, svgCreator(NS_NewSVGSVGElement));
+ nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_SVG, nsHtml5Atoms::svg, elt);
+ currentPtr++;
+ stack[currentPtr] = node;
+ }
+ }
+}
+
+void
+nsHtml5TreeBuilder::doctype(nsIAtom* name, nsHtml5String publicIdentifier, nsHtml5String systemIdentifier, bool forceQuirks)
+{
+ needToDropLF = false;
+ if (!isInForeign() && mode == INITIAL) {
+ nsHtml5String emptyString = nsHtml5Portability::newEmptyString();
+ appendDoctypeToDocument(!name ? nsHtml5Atoms::emptystring : name, !publicIdentifier ? emptyString : publicIdentifier, !systemIdentifier ? emptyString : systemIdentifier);
+ emptyString.Release();
+ if (isQuirky(name, publicIdentifier, systemIdentifier, forceQuirks)) {
+ errQuirkyDoctype();
+ documentModeInternal(QUIRKS_MODE, publicIdentifier, systemIdentifier, false);
+ } else if (isAlmostStandards(publicIdentifier, systemIdentifier)) {
+ errAlmostStandardsDoctype();
+ documentModeInternal(ALMOST_STANDARDS_MODE, publicIdentifier, systemIdentifier, false);
+ } else {
+ documentModeInternal(STANDARDS_MODE, publicIdentifier, systemIdentifier, false);
+ }
+ mode = BEFORE_HTML;
+ return;
+ }
+ errStrayDoctype();
+ return;
+}
+
+void
+nsHtml5TreeBuilder::comment(char16_t* buf, int32_t start, int32_t length)
+{
+ needToDropLF = false;
+ if (!isInForeign()) {
+ switch(mode) {
+ case INITIAL:
+ case BEFORE_HTML:
+ case AFTER_AFTER_BODY:
+ case AFTER_AFTER_FRAMESET: {
+ appendCommentToDocument(buf, start, length);
+ return;
+ }
+ case AFTER_BODY: {
+ flushCharacters();
+ appendComment(stack[0]->node, buf, start, length);
+ return;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+ flushCharacters();
+ appendComment(stack[currentPtr]->node, buf, start, length);
+ return;
+}
+
+void
+nsHtml5TreeBuilder::characters(const char16_t* buf, int32_t start, int32_t length)
+{
+ if (tokenizer->isViewingXmlSource()) {
+ return;
+ }
+ if (needToDropLF) {
+ needToDropLF = false;
+ if (buf[start] == '\n') {
+ start++;
+ length--;
+ if (!length) {
+ return;
+ }
+ }
+ }
+ switch(mode) {
+ case IN_BODY:
+ case IN_CELL:
+ case IN_CAPTION: {
+ if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) {
+ reconstructTheActiveFormattingElements();
+ }
+ }
+ case TEXT: {
+ accumulateCharacters(buf, start, length);
+ return;
+ }
+ case IN_TABLE:
+ case IN_TABLE_BODY:
+ case IN_ROW: {
+ accumulateCharactersForced(buf, start, length);
+ return;
+ }
+ default: {
+ int32_t end = start + length;
+ for (int32_t i = start; i < end; i++) {
+ switch(buf[i]) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\f': {
+ switch(mode) {
+ case INITIAL:
+ case BEFORE_HTML:
+ case BEFORE_HEAD: {
+ start = i + 1;
+ continue;
+ }
+ case IN_HEAD:
+ case IN_HEAD_NOSCRIPT:
+ case AFTER_HEAD:
+ case IN_COLUMN_GROUP:
+ case IN_FRAMESET:
+ case AFTER_FRAMESET: {
+ continue;
+ }
+ case FRAMESET_OK:
+ case IN_TEMPLATE:
+ case IN_BODY:
+ case IN_CELL:
+ case IN_CAPTION: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ start = i;
+ }
+ if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) {
+ flushCharacters();
+ reconstructTheActiveFormattingElements();
+ }
+ NS_HTML5_BREAK(charactersloop);
+ }
+ case IN_SELECT:
+ case IN_SELECT_IN_TABLE: {
+ NS_HTML5_BREAK(charactersloop);
+ }
+ case IN_TABLE:
+ case IN_TABLE_BODY:
+ case IN_ROW: {
+ accumulateCharactersForced(buf, i, 1);
+ start = i + 1;
+ continue;
+ }
+ case AFTER_BODY:
+ case AFTER_AFTER_BODY:
+ case AFTER_AFTER_FRAMESET: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ start = i;
+ }
+ flushCharacters();
+ reconstructTheActiveFormattingElements();
+ continue;
+ }
+ }
+ }
+ default: {
+ switch(mode) {
+ case INITIAL: {
+ documentModeInternal(QUIRKS_MODE, nullptr, nullptr, false);
+ mode = BEFORE_HTML;
+ i--;
+ continue;
+ }
+ case BEFORE_HTML: {
+ appendHtmlElementToDocumentAndPush();
+ mode = BEFORE_HEAD;
+ i--;
+ continue;
+ }
+ case BEFORE_HEAD: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ start = i;
+ }
+ flushCharacters();
+ appendToCurrentNodeAndPushHeadElement(nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ mode = IN_HEAD;
+ i--;
+ continue;
+ }
+ case IN_HEAD: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ start = i;
+ }
+ flushCharacters();
+ pop();
+ mode = AFTER_HEAD;
+ i--;
+ continue;
+ }
+ case IN_HEAD_NOSCRIPT: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ start = i;
+ }
+ errNonSpaceInNoscriptInHead();
+ flushCharacters();
+ pop();
+ mode = IN_HEAD;
+ i--;
+ continue;
+ }
+ case AFTER_HEAD: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ start = i;
+ }
+ flushCharacters();
+ appendToCurrentNodeAndPushBodyElement();
+ mode = FRAMESET_OK;
+ i--;
+ continue;
+ }
+ case FRAMESET_OK: {
+ framesetOk = false;
+ mode = IN_BODY;
+ i--;
+ continue;
+ }
+ case IN_TEMPLATE:
+ case IN_BODY:
+ case IN_CELL:
+ case IN_CAPTION: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ start = i;
+ }
+ if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) {
+ flushCharacters();
+ reconstructTheActiveFormattingElements();
+ }
+ NS_HTML5_BREAK(charactersloop);
+ }
+ case IN_TABLE:
+ case IN_TABLE_BODY:
+ case IN_ROW: {
+ accumulateCharactersForced(buf, i, 1);
+ start = i + 1;
+ continue;
+ }
+ case IN_COLUMN_GROUP: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ start = i;
+ }
+ if (!currentPtr || stack[currentPtr]->getGroup() == nsHtml5TreeBuilder::TEMPLATE) {
+ errNonSpaceInColgroupInFragment();
+ start = i + 1;
+ continue;
+ }
+ flushCharacters();
+ pop();
+ mode = IN_TABLE;
+ i--;
+ continue;
+ }
+ case IN_SELECT:
+ case IN_SELECT_IN_TABLE: {
+ NS_HTML5_BREAK(charactersloop);
+ }
+ case AFTER_BODY: {
+ errNonSpaceAfterBody();
+
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ i--;
+ continue;
+ }
+ case IN_FRAMESET: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ }
+ errNonSpaceInFrameset();
+ start = i + 1;
+ continue;
+ }
+ case AFTER_FRAMESET: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ }
+ errNonSpaceAfterFrameset();
+ start = i + 1;
+ continue;
+ }
+ case AFTER_AFTER_BODY: {
+ errNonSpaceInTrailer();
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ i--;
+ continue;
+ }
+ case AFTER_AFTER_FRAMESET: {
+ if (start < i) {
+ accumulateCharacters(buf, start, i - start);
+ }
+ errNonSpaceInTrailer();
+ start = i + 1;
+ continue;
+ }
+ }
+ }
+ }
+ }
+ charactersloop_end: ;
+ if (start < end) {
+ accumulateCharacters(buf, start, end - start);
+ }
+ }
+ }
+}
+
+void
+nsHtml5TreeBuilder::zeroOriginatingReplacementCharacter()
+{
+ if (mode == TEXT) {
+ accumulateCharacters(REPLACEMENT_CHARACTER, 0, 1);
+ return;
+ }
+ if (currentPtr >= 0) {
+ if (isSpecialParentInForeign(stack[currentPtr])) {
+ return;
+ }
+ accumulateCharacters(REPLACEMENT_CHARACTER, 0, 1);
+ }
+}
+
+void
+nsHtml5TreeBuilder::eof()
+{
+ flushCharacters();
+ for (; ; ) {
+ switch(mode) {
+ case INITIAL: {
+ documentModeInternal(QUIRKS_MODE, nullptr, nullptr, false);
+ mode = BEFORE_HTML;
+ continue;
+ }
+ case BEFORE_HTML: {
+ appendHtmlElementToDocumentAndPush();
+ mode = BEFORE_HEAD;
+ continue;
+ }
+ case BEFORE_HEAD: {
+ appendToCurrentNodeAndPushHeadElement(nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ mode = IN_HEAD;
+ continue;
+ }
+ case IN_HEAD: {
+ while (currentPtr > 0) {
+ popOnEof();
+ }
+ mode = AFTER_HEAD;
+ continue;
+ }
+ case IN_HEAD_NOSCRIPT: {
+ while (currentPtr > 1) {
+ popOnEof();
+ }
+ mode = IN_HEAD;
+ continue;
+ }
+ case AFTER_HEAD: {
+ appendToCurrentNodeAndPushBodyElement();
+ mode = IN_BODY;
+ continue;
+ }
+ case IN_TABLE_BODY:
+ case IN_ROW:
+ case IN_TABLE:
+ case IN_SELECT_IN_TABLE:
+ case IN_SELECT:
+ case IN_COLUMN_GROUP:
+ case FRAMESET_OK:
+ case IN_CAPTION:
+ case IN_CELL:
+ case IN_BODY: {
+ if (isTemplateModeStackEmpty()) {
+ NS_HTML5_BREAK(eofloop);
+ }
+ }
+ case IN_TEMPLATE: {
+ int32_t eltPos = findLast(nsHtml5Atoms::template_);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ MOZ_ASSERT(fragment);
+ NS_HTML5_BREAK(eofloop);
+ }
+ if (MOZ_UNLIKELY(mViewSource)) {
+ errUnclosedElements(eltPos, nsHtml5Atoms::template_);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ popTemplateMode();
+ resetTheInsertionMode();
+ continue;
+ }
+ case TEXT: {
+ if (originalMode == AFTER_HEAD) {
+ popOnEof();
+ }
+ popOnEof();
+ mode = originalMode;
+ continue;
+ }
+ case IN_FRAMESET: {
+ NS_HTML5_BREAK(eofloop);
+ }
+ case AFTER_BODY:
+ case AFTER_FRAMESET:
+ case AFTER_AFTER_BODY:
+ case AFTER_AFTER_FRAMESET:
+ default: {
+ NS_HTML5_BREAK(eofloop);
+ }
+ }
+ }
+ eofloop_end: ;
+ while (currentPtr > 0) {
+ popOnEof();
+ }
+ if (!fragment) {
+ popOnEof();
+ }
+}
+
+void
+nsHtml5TreeBuilder::endTokenization()
+{
+ formPointer = nullptr;
+ headPointer = nullptr;
+ contextName = nullptr;
+ contextNode = nullptr;
+ deepTreeSurrogateParent = nullptr;
+ templateModeStack = nullptr;
+ if (stack) {
+ while (currentPtr > -1) {
+ stack[currentPtr]->release();
+ currentPtr--;
+ }
+ stack = nullptr;
+ }
+ if (listOfActiveFormattingElements) {
+ while (listPtr > -1) {
+ if (listOfActiveFormattingElements[listPtr]) {
+ listOfActiveFormattingElements[listPtr]->release();
+ }
+ listPtr--;
+ }
+ listOfActiveFormattingElements = nullptr;
+ }
+ charBuffer = nullptr;
+ end();
+}
+
+void
+nsHtml5TreeBuilder::startTag(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes, bool selfClosing)
+{
+ flushCharacters();
+ int32_t eltPos;
+ needToDropLF = false;
+ starttagloop: for (; ; ) {
+ int32_t group = elementName->getGroup();
+ nsIAtom* name = elementName->getName();
+ if (isInForeign()) {
+ nsHtml5StackNode* currentNode = stack[currentPtr];
+ int32_t currNs = currentNode->ns;
+ if (!(currentNode->isHtmlIntegrationPoint() || (currNs == kNameSpaceID_MathML && ((currentNode->getGroup() == MI_MO_MN_MS_MTEXT && group != MGLYPH_OR_MALIGNMARK) || (currentNode->getGroup() == ANNOTATION_XML && group == SVG))))) {
+ switch(group) {
+ case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
+ case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU:
+ case BODY:
+ case BR:
+ case RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR:
+ case DD_OR_DT:
+ case UL_OR_OL_OR_DL:
+ case EMBED:
+ case IMG:
+ case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6:
+ case HEAD:
+ case HR:
+ case LI:
+ case META:
+ case NOBR:
+ case P:
+ case PRE_OR_LISTING:
+ case TABLE:
+ case FONT: {
+ if (!(group == FONT && !(attributes->contains(nsHtml5AttributeName::ATTR_COLOR) || attributes->contains(nsHtml5AttributeName::ATTR_FACE) || attributes->contains(nsHtml5AttributeName::ATTR_SIZE)))) {
+ errHtmlStartTagInForeignContext(name);
+ if (!fragment) {
+ while (!isSpecialParentInForeign(stack[currentPtr])) {
+ pop();
+ }
+ NS_HTML5_CONTINUE(starttagloop);
+ }
+ }
+ }
+ default: {
+ if (kNameSpaceID_SVG == currNs) {
+ attributes->adjustForSvg();
+ if (selfClosing) {
+ appendVoidElementToCurrentMayFosterSVG(elementName, attributes);
+ selfClosing = false;
+ } else {
+ appendToCurrentNodeAndPushElementMayFosterSVG(elementName, attributes);
+ }
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ } else {
+ attributes->adjustForMath();
+ if (selfClosing) {
+ appendVoidElementToCurrentMayFosterMathML(elementName, attributes);
+ selfClosing = false;
+ } else {
+ appendToCurrentNodeAndPushElementMayFosterMathML(elementName, attributes);
+ }
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ }
+ }
+ }
+ switch(mode) {
+ case IN_TEMPLATE: {
+ switch(group) {
+ case COL: {
+ popTemplateMode();
+ pushTemplateMode(IN_COLUMN_GROUP);
+ mode = IN_COLUMN_GROUP;
+ continue;
+ }
+ case CAPTION:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT: {
+ popTemplateMode();
+ pushTemplateMode(IN_TABLE);
+ mode = IN_TABLE;
+ continue;
+ }
+ case TR: {
+ popTemplateMode();
+ pushTemplateMode(IN_TABLE_BODY);
+ mode = IN_TABLE_BODY;
+ continue;
+ }
+ case TD_OR_TH: {
+ popTemplateMode();
+ pushTemplateMode(IN_ROW);
+ mode = IN_ROW;
+ continue;
+ }
+ case META: {
+ checkMetaCharset(attributes);
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TITLE: {
+ startTagTitleInHead(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case BASE:
+ case LINK_OR_BASEFONT_OR_BGSOUND: {
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case SCRIPT: {
+ startTagScriptInHead(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case NOFRAMES:
+ case STYLE: {
+ startTagGenericRawText(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TEMPLATE: {
+ startTagTemplateInHead(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ popTemplateMode();
+ pushTemplateMode(IN_BODY);
+ mode = IN_BODY;
+ continue;
+ }
+ }
+ }
+ case IN_ROW: {
+ switch(group) {
+ case TD_OR_TH: {
+ clearStackBackTo(findLastOrRoot(nsHtml5TreeBuilder::TR));
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ mode = IN_CELL;
+ insertMarker();
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR: {
+ eltPos = findLastOrRoot(nsHtml5TreeBuilder::TR);
+ if (!eltPos) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errNoTableRowToClose();
+ NS_HTML5_BREAK(starttagloop);
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE_BODY;
+ continue;
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case IN_TABLE_BODY: {
+ switch(group) {
+ case TR: {
+ clearStackBackTo(findLastInTableScopeOrRootTemplateTbodyTheadTfoot());
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ mode = IN_ROW;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TD_OR_TH: {
+ errStartTagInTableBody(name);
+ clearStackBackTo(findLastInTableScopeOrRootTemplateTbodyTheadTfoot());
+ appendToCurrentNodeAndPushElement(nsHtml5ElementName::ELT_TR, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ mode = IN_ROW;
+ continue;
+ }
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT: {
+ eltPos = findLastInTableScopeOrRootTemplateTbodyTheadTfoot();
+ if (!eltPos || stack[eltPos]->getGroup() == TEMPLATE) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errStrayStartTag(name);
+ NS_HTML5_BREAK(starttagloop);
+ } else {
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE;
+ continue;
+ }
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case IN_TABLE: {
+ for (; ; ) {
+ switch(group) {
+ case CAPTION: {
+ clearStackBackTo(findLastOrRoot(nsHtml5TreeBuilder::TABLE));
+ insertMarker();
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ mode = IN_CAPTION;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case COLGROUP: {
+ clearStackBackTo(findLastOrRoot(nsHtml5TreeBuilder::TABLE));
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ mode = IN_COLUMN_GROUP;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case COL: {
+ clearStackBackTo(findLastOrRoot(nsHtml5TreeBuilder::TABLE));
+ appendToCurrentNodeAndPushElement(nsHtml5ElementName::ELT_COLGROUP, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ mode = IN_COLUMN_GROUP;
+ NS_HTML5_CONTINUE(starttagloop);
+ }
+ case TBODY_OR_THEAD_OR_TFOOT: {
+ clearStackBackTo(findLastOrRoot(nsHtml5TreeBuilder::TABLE));
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ mode = IN_TABLE_BODY;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TR:
+ case TD_OR_TH: {
+ clearStackBackTo(findLastOrRoot(nsHtml5TreeBuilder::TABLE));
+ appendToCurrentNodeAndPushElement(nsHtml5ElementName::ELT_TBODY, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ mode = IN_TABLE_BODY;
+ NS_HTML5_CONTINUE(starttagloop);
+ }
+ case TEMPLATE: {
+ NS_HTML5_BREAK(intableloop);
+ }
+ case TABLE: {
+ errTableSeenWhileTableOpen();
+ eltPos = findLastInTableScope(name);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ NS_HTML5_BREAK(starttagloop);
+ }
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(nsHtml5Atoms::table)) {
+ errNoCheckUnclosedElementsOnStack();
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ NS_HTML5_CONTINUE(starttagloop);
+ }
+ case SCRIPT: {
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::SCRIPT_DATA, elementName);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case STYLE: {
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::RAWTEXT, elementName);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case INPUT: {
+ errStartTagInTable(name);
+ if (!nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("hidden", attributes->getValue(nsHtml5AttributeName::ATTR_TYPE))) {
+ NS_HTML5_BREAK(intableloop);
+ }
+ appendVoidInputToCurrent(attributes, formPointer);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case FORM: {
+ if (!!formPointer || isTemplateContents()) {
+ errFormWhenFormOpen();
+ NS_HTML5_BREAK(starttagloop);
+ } else {
+ errStartTagInTable(name);
+ appendVoidFormToCurrent(attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ default: {
+ errStartTagInTable(name);
+ NS_HTML5_BREAK(intableloop);
+ }
+ }
+ }
+ intableloop_end: ;
+ }
+ case IN_CAPTION: {
+ switch(group) {
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ case TD_OR_TH: {
+ errStrayStartTag(name);
+ eltPos = findLastInTableScope(nsHtml5Atoms::caption);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ NS_HTML5_BREAK(starttagloop);
+ }
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && currentPtr != eltPos) {
+ errNoCheckUnclosedElementsOnStack();
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ mode = IN_TABLE;
+ continue;
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case IN_CELL: {
+ switch(group) {
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ case TD_OR_TH: {
+ eltPos = findLastInTableScopeTdTh();
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errNoCellToClose();
+ NS_HTML5_BREAK(starttagloop);
+ } else {
+ closeTheCell(eltPos);
+ continue;
+ }
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case FRAMESET_OK: {
+ switch(group) {
+ case FRAMESET: {
+ if (mode == FRAMESET_OK) {
+ if (!currentPtr || stack[1]->getGroup() != BODY) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errStrayStartTag(name);
+ NS_HTML5_BREAK(starttagloop);
+ } else {
+ errFramesetStart();
+ detachFromParent(stack[1]->node);
+ while (currentPtr > 0) {
+ pop();
+ }
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ mode = IN_FRAMESET;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ } else {
+ errStrayStartTag(name);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ case PRE_OR_LISTING:
+ case LI:
+ case DD_OR_DT:
+ case BUTTON:
+ case MARQUEE_OR_APPLET:
+ case OBJECT:
+ case TABLE:
+ case AREA_OR_WBR:
+ case BR:
+ case EMBED:
+ case IMG:
+ case INPUT:
+ case KEYGEN:
+ case HR:
+ case TEXTAREA:
+ case XMP:
+ case IFRAME:
+ case SELECT: {
+ if (mode == FRAMESET_OK && !(group == INPUT && nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("hidden", attributes->getValue(nsHtml5AttributeName::ATTR_TYPE)))) {
+ framesetOk = false;
+ mode = IN_BODY;
+ }
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case IN_BODY: {
+ for (; ; ) {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case BASE:
+ case LINK_OR_BASEFONT_OR_BGSOUND:
+ case META:
+ case STYLE:
+ case SCRIPT:
+ case TITLE:
+ case TEMPLATE: {
+ NS_HTML5_BREAK(inbodyloop);
+ }
+ case BODY: {
+ if (!currentPtr || stack[1]->getGroup() != BODY || isTemplateContents()) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errStrayStartTag(name);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ errFooSeenWhenFooOpen(name);
+ framesetOk = false;
+ if (mode == FRAMESET_OK) {
+ mode = IN_BODY;
+ }
+ if (addAttributesToBody(attributes)) {
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case P:
+ case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU:
+ case UL_OR_OL_OR_DL:
+ case ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY: {
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6: {
+ implicitlyCloseP();
+ if (stack[currentPtr]->getGroup() == H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6) {
+ errHeadingWhenHeadingOpen();
+ pop();
+ }
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case FIELDSET: {
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes, formPointer);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case PRE_OR_LISTING: {
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ needToDropLF = true;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case FORM: {
+ if (!!formPointer && !isTemplateContents()) {
+ errFormWhenFormOpen();
+ NS_HTML5_BREAK(starttagloop);
+ } else {
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushFormElementMayFoster(attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ case LI:
+ case DD_OR_DT: {
+ eltPos = currentPtr;
+ for (; ; ) {
+ nsHtml5StackNode* node = stack[eltPos];
+ if (node->getGroup() == group) {
+ generateImpliedEndTagsExceptFor(node->name);
+ if (!!MOZ_UNLIKELY(mViewSource) && eltPos != currentPtr) {
+ errUnclosedElementsImplied(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ break;
+ } else if (!eltPos || (node->isSpecial() && (node->ns != kNameSpaceID_XHTML || (node->name != nsHtml5Atoms::p && node->name != nsHtml5Atoms::address && node->name != nsHtml5Atoms::div)))) {
+ break;
+ }
+ eltPos--;
+ }
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case PLAINTEXT: {
+ implicitlyCloseP();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::PLAINTEXT, elementName);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case A: {
+ int32_t activeAPos = findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker(nsHtml5Atoms::a);
+ if (activeAPos != -1) {
+ errFooSeenWhenFooOpen(name);
+ nsHtml5StackNode* activeA = listOfActiveFormattingElements[activeAPos];
+ activeA->retain();
+ adoptionAgencyEndTag(nsHtml5Atoms::a);
+ removeFromStack(activeA);
+ activeAPos = findInListOfActiveFormattingElements(activeA);
+ if (activeAPos != -1) {
+ removeFromListOfActiveFormattingElements(activeAPos);
+ }
+ activeA->release();
+ }
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushFormattingElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
+ case FONT: {
+ reconstructTheActiveFormattingElements();
+ maybeForgetEarlierDuplicateFormattingElement(elementName->getName(), attributes);
+ appendToCurrentNodeAndPushFormattingElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case NOBR: {
+ reconstructTheActiveFormattingElements();
+ if (nsHtml5TreeBuilder::NOT_FOUND_ON_STACK != findLastInScope(nsHtml5Atoms::nobr)) {
+ errFooSeenWhenFooOpen(name);
+ adoptionAgencyEndTag(nsHtml5Atoms::nobr);
+ reconstructTheActiveFormattingElements();
+ }
+ appendToCurrentNodeAndPushFormattingElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case BUTTON: {
+ eltPos = findLastInScope(name);
+ if (eltPos != nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errFooSeenWhenFooOpen(name);
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) {
+ errUnclosedElementsImplied(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ NS_HTML5_CONTINUE(starttagloop);
+ } else {
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes, formPointer);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ case OBJECT: {
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes, formPointer);
+ insertMarker();
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case MARQUEE_OR_APPLET: {
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ insertMarker();
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TABLE: {
+ if (!quirks) {
+ implicitlyCloseP();
+ }
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ mode = IN_TABLE;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case BR:
+ case EMBED:
+ case AREA_OR_WBR: {
+ reconstructTheActiveFormattingElements();
+ }
+#ifdef ENABLE_VOID_MENUITEM
+ case MENUITEM:
+#endif
+ case PARAM_OR_SOURCE_OR_TRACK: {
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case HR: {
+ implicitlyCloseP();
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case IMAGE: {
+ errImage();
+ elementName = nsHtml5ElementName::ELT_IMG;
+ NS_HTML5_CONTINUE(starttagloop);
+ }
+ case IMG:
+ case KEYGEN:
+ case INPUT: {
+ reconstructTheActiveFormattingElements();
+ appendVoidElementToCurrentMayFoster(elementName, attributes, formPointer);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case ISINDEX: {
+ errIsindex();
+ if (!!formPointer && !isTemplateContents()) {
+ NS_HTML5_BREAK(starttagloop);
+ }
+ implicitlyCloseP();
+ nsHtml5HtmlAttributes* formAttrs = new nsHtml5HtmlAttributes(0);
+ int32_t actionIndex = attributes->getIndex(nsHtml5AttributeName::ATTR_ACTION);
+ if (actionIndex > -1) {
+ formAttrs->addAttribute(nsHtml5AttributeName::ATTR_ACTION, attributes->getValueNoBoundsCheck(actionIndex), attributes->getLineNoBoundsCheck(actionIndex));
+ }
+ appendToCurrentNodeAndPushFormElementMayFoster(formAttrs);
+ appendVoidElementToCurrentMayFoster(nsHtml5ElementName::ELT_HR, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ appendToCurrentNodeAndPushElementMayFoster(nsHtml5ElementName::ELT_LABEL, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ int32_t promptIndex = attributes->getIndex(nsHtml5AttributeName::ATTR_PROMPT);
+ if (promptIndex > -1) {
+ autoJArray<char16_t,int32_t> prompt = nsHtml5Portability::newCharArrayFromString(attributes->getValueNoBoundsCheck(promptIndex));
+ appendCharacters(stack[currentPtr]->node, prompt, 0, prompt.length);
+ } else {
+ appendIsindexPrompt(stack[currentPtr]->node);
+ }
+ nsHtml5HtmlAttributes* inputAttributes = new nsHtml5HtmlAttributes(0);
+ inputAttributes->addAttribute(nsHtml5AttributeName::ATTR_NAME, nsHtml5Portability::newStringFromLiteral("isindex"), tokenizer->getLineNumber());
+ for (int32_t i = 0; i < attributes->getLength(); i++) {
+ nsIAtom* attributeQName = attributes->getLocalNameNoBoundsCheck(i);
+ if (nsHtml5Atoms::name == attributeQName || nsHtml5Atoms::prompt == attributeQName) {
+ attributes->releaseValue(i);
+ } else if (nsHtml5Atoms::action != attributeQName) {
+ inputAttributes->AddAttributeWithLocal(attributeQName, attributes->getValueNoBoundsCheck(i), attributes->getLineNoBoundsCheck(i));
+ }
+ }
+ attributes->clearWithoutReleasingContents();
+ appendVoidElementToCurrentMayFoster(nsHtml5ElementName::ELT_INPUT, inputAttributes, formPointer);
+ pop();
+ appendVoidElementToCurrentMayFoster(nsHtml5ElementName::ELT_HR, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ pop();
+ if (!isTemplateContents()) {
+ formPointer = nullptr;
+ }
+ selfClosing = false;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TEXTAREA: {
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes, formPointer);
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::RCDATA, elementName);
+ originalMode = mode;
+ mode = TEXT;
+ needToDropLF = true;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case XMP: {
+ implicitlyCloseP();
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::RAWTEXT, elementName);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case NOSCRIPT: {
+ if (!scriptingEnabled) {
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ } else {
+ }
+ }
+ case NOFRAMES:
+ case IFRAME:
+ case NOEMBED: {
+ startTagGenericRawText(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case SELECT: {
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes, formPointer);
+ switch(mode) {
+ case IN_TABLE:
+ case IN_CAPTION:
+ case IN_COLUMN_GROUP:
+ case IN_TABLE_BODY:
+ case IN_ROW:
+ case IN_CELL: {
+ mode = IN_SELECT_IN_TABLE;
+ break;
+ }
+ default: {
+ mode = IN_SELECT;
+ break;
+ }
+ }
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case OPTGROUP:
+ case OPTION: {
+ if (isCurrent(nsHtml5Atoms::option)) {
+ pop();
+ }
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case RB_OR_RTC: {
+ eltPos = findLastInScope(nsHtml5Atoms::ruby);
+ if (eltPos != NOT_FOUND_ON_STACK) {
+ generateImpliedEndTags();
+ }
+ if (eltPos != currentPtr) {
+ if (eltPos == NOT_FOUND_ON_STACK) {
+ errStartTagSeenWithoutRuby(name);
+ } else {
+ errUnclosedChildrenInRuby();
+ }
+ }
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case RT_OR_RP: {
+ eltPos = findLastInScope(nsHtml5Atoms::ruby);
+ if (eltPos != NOT_FOUND_ON_STACK) {
+ generateImpliedEndTagsExceptFor(nsHtml5Atoms::rtc);
+ }
+ if (eltPos != currentPtr) {
+ if (!isCurrent(nsHtml5Atoms::rtc)) {
+ if (eltPos == NOT_FOUND_ON_STACK) {
+ errStartTagSeenWithoutRuby(name);
+ } else {
+ errUnclosedChildrenInRuby();
+ }
+ }
+ }
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case MATH: {
+ reconstructTheActiveFormattingElements();
+ attributes->adjustForMath();
+ if (selfClosing) {
+ appendVoidElementToCurrentMayFosterMathML(elementName, attributes);
+ selfClosing = false;
+ } else {
+ appendToCurrentNodeAndPushElementMayFosterMathML(elementName, attributes);
+ }
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case SVG: {
+ reconstructTheActiveFormattingElements();
+ attributes->adjustForSvg();
+ if (selfClosing) {
+ appendVoidElementToCurrentMayFosterSVG(elementName, attributes);
+ selfClosing = false;
+ } else {
+ appendToCurrentNodeAndPushElementMayFosterSVG(elementName, attributes);
+ }
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ case TD_OR_TH:
+ case FRAME:
+ case FRAMESET:
+ case HEAD: {
+ errStrayStartTag(name);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case OUTPUT: {
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes, formPointer);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ reconstructTheActiveFormattingElements();
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ }
+ inbodyloop_end: ;
+ }
+ case IN_HEAD: {
+ for (; ; ) {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case BASE:
+ case LINK_OR_BASEFONT_OR_BGSOUND: {
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case META: {
+ NS_HTML5_BREAK(inheadloop);
+ }
+ case TITLE: {
+ startTagTitleInHead(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case NOSCRIPT: {
+ if (scriptingEnabled) {
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::RAWTEXT, elementName);
+ } else {
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ mode = IN_HEAD_NOSCRIPT;
+ }
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case SCRIPT: {
+ startTagScriptInHead(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case STYLE:
+ case NOFRAMES: {
+ startTagGenericRawText(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case HEAD: {
+ errFooSeenWhenFooOpen(name);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TEMPLATE: {
+ startTagTemplateInHead(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ pop();
+ mode = AFTER_HEAD;
+ NS_HTML5_CONTINUE(starttagloop);
+ }
+ }
+ }
+ inheadloop_end: ;
+ }
+ case IN_HEAD_NOSCRIPT: {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case LINK_OR_BASEFONT_OR_BGSOUND: {
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case META: {
+ checkMetaCharset(attributes);
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case STYLE:
+ case NOFRAMES: {
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::RAWTEXT, elementName);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case HEAD: {
+ errFooSeenWhenFooOpen(name);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case NOSCRIPT: {
+ errFooSeenWhenFooOpen(name);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ errBadStartTagInHead(name);
+ pop();
+ mode = IN_HEAD;
+ continue;
+ }
+ }
+ }
+ case IN_COLUMN_GROUP: {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case COL: {
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TEMPLATE: {
+ startTagTemplateInHead(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ if (!currentPtr || stack[currentPtr]->getGroup() == TEMPLATE) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errGarbageInColgroup();
+ NS_HTML5_BREAK(starttagloop);
+ }
+ pop();
+ mode = IN_TABLE;
+ continue;
+ }
+ }
+ }
+ case IN_SELECT_IN_TABLE: {
+ switch(group) {
+ case CAPTION:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ case TD_OR_TH:
+ case TABLE: {
+ errStartTagWithSelectOpen(name);
+ eltPos = findLastInTableScope(nsHtml5Atoms::select);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ MOZ_ASSERT(fragment);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ continue;
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case IN_SELECT: {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case OPTION: {
+ if (isCurrent(nsHtml5Atoms::option)) {
+ pop();
+ }
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case OPTGROUP: {
+ if (isCurrent(nsHtml5Atoms::option)) {
+ pop();
+ }
+ if (isCurrent(nsHtml5Atoms::optgroup)) {
+ pop();
+ }
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case SELECT: {
+ errStartSelectWhereEndSelectExpected();
+ eltPos = findLastInTableScope(name);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ MOZ_ASSERT(fragment);
+ errNoSelectInTableScope();
+ NS_HTML5_BREAK(starttagloop);
+ } else {
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ case INPUT:
+ case TEXTAREA:
+ case KEYGEN: {
+ errStartTagWithSelectOpen(name);
+ eltPos = findLastInTableScope(nsHtml5Atoms::select);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ MOZ_ASSERT(fragment);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ continue;
+ }
+ case SCRIPT: {
+ startTagScriptInHead(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TEMPLATE: {
+ startTagTemplateInHead(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ errStrayStartTag(name);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ }
+ case AFTER_BODY: {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ errStrayStartTag(name);
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ continue;
+ }
+ }
+ }
+ case IN_FRAMESET: {
+ switch(group) {
+ case FRAMESET: {
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case FRAME: {
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case AFTER_FRAMESET: {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case NOFRAMES: {
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::RAWTEXT, elementName);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ errStrayStartTag(name);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ }
+ case INITIAL: {
+ errStartTagWithoutDoctype();
+ documentModeInternal(QUIRKS_MODE, nullptr, nullptr, false);
+ mode = BEFORE_HTML;
+ continue;
+ }
+ case BEFORE_HTML: {
+ switch(group) {
+ case HTML: {
+ if (attributes == nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES) {
+ appendHtmlElementToDocumentAndPush();
+ } else {
+ appendHtmlElementToDocumentAndPush(attributes);
+ }
+ mode = BEFORE_HEAD;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ appendHtmlElementToDocumentAndPush();
+ mode = BEFORE_HEAD;
+ continue;
+ }
+ }
+ }
+ case BEFORE_HEAD: {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case HEAD: {
+ appendToCurrentNodeAndPushHeadElement(attributes);
+ mode = IN_HEAD;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ appendToCurrentNodeAndPushHeadElement(nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ mode = IN_HEAD;
+ continue;
+ }
+ }
+ }
+ case AFTER_HEAD: {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case BODY: {
+ if (!attributes->getLength()) {
+ appendToCurrentNodeAndPushBodyElement();
+ } else {
+ appendToCurrentNodeAndPushBodyElement(attributes);
+ }
+ framesetOk = false;
+ mode = IN_BODY;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case FRAMESET: {
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ mode = IN_FRAMESET;
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TEMPLATE: {
+ errFooBetweenHeadAndBody(name);
+ pushHeadPointerOntoStack();
+ nsHtml5StackNode* headOnStack = stack[currentPtr];
+ startTagTemplateInHead(elementName, attributes);
+ removeFromStack(headOnStack);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case BASE:
+ case LINK_OR_BASEFONT_OR_BGSOUND: {
+ errFooBetweenHeadAndBody(name);
+ pushHeadPointerOntoStack();
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ pop();
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case META: {
+ errFooBetweenHeadAndBody(name);
+ checkMetaCharset(attributes);
+ pushHeadPointerOntoStack();
+ appendVoidElementToCurrentMayFoster(elementName, attributes);
+ selfClosing = false;
+ pop();
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case SCRIPT: {
+ errFooBetweenHeadAndBody(name);
+ pushHeadPointerOntoStack();
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::SCRIPT_DATA, elementName);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case STYLE:
+ case NOFRAMES: {
+ errFooBetweenHeadAndBody(name);
+ pushHeadPointerOntoStack();
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::RAWTEXT, elementName);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case TITLE: {
+ errFooBetweenHeadAndBody(name);
+ pushHeadPointerOntoStack();
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::RCDATA, elementName);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case HEAD: {
+ errStrayStartTag(name);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ appendToCurrentNodeAndPushBodyElement();
+ mode = FRAMESET_OK;
+ continue;
+ }
+ }
+ }
+ case AFTER_AFTER_BODY: {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ errStrayStartTag(name);
+
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ continue;
+ }
+ }
+ }
+ case AFTER_AFTER_FRAMESET: {
+ switch(group) {
+ case HTML: {
+ errStrayStartTag(name);
+ if (!fragment && !isTemplateContents()) {
+ addAttributesToHtml(attributes);
+ attributes = nullptr;
+ }
+ NS_HTML5_BREAK(starttagloop);
+ }
+ case NOFRAMES: {
+ startTagGenericRawText(elementName, attributes);
+ attributes = nullptr;
+ NS_HTML5_BREAK(starttagloop);
+ }
+ default: {
+ errStrayStartTag(name);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ }
+ case TEXT: {
+ MOZ_ASSERT(false);
+ NS_HTML5_BREAK(starttagloop);
+ }
+ }
+ }
+ starttagloop_end: ;
+ if (selfClosing) {
+ errSelfClosing();
+ }
+ if (!mBuilder && attributes != nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES) {
+ delete attributes;
+ }
+}
+
+void
+nsHtml5TreeBuilder::startTagTitleInHead(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::RCDATA, elementName);
+}
+
+void
+nsHtml5TreeBuilder::startTagGenericRawText(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::RAWTEXT, elementName);
+}
+
+void
+nsHtml5TreeBuilder::startTagScriptInHead(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ appendToCurrentNodeAndPushElementMayFoster(elementName, attributes);
+ originalMode = mode;
+ mode = TEXT;
+ tokenizer->setStateAndEndTagExpectation(nsHtml5Tokenizer::SCRIPT_DATA, elementName);
+}
+
+void
+nsHtml5TreeBuilder::startTagTemplateInHead(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ appendToCurrentNodeAndPushElement(elementName, attributes);
+ insertMarker();
+ framesetOk = false;
+ originalMode = mode;
+ mode = IN_TEMPLATE;
+ pushTemplateMode(IN_TEMPLATE);
+}
+
+bool
+nsHtml5TreeBuilder::isTemplateContents()
+{
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK != findLast(nsHtml5Atoms::template_);
+}
+
+bool
+nsHtml5TreeBuilder::isTemplateModeStackEmpty()
+{
+ return templateModePtr == -1;
+}
+
+bool
+nsHtml5TreeBuilder::isSpecialParentInForeign(nsHtml5StackNode* stackNode)
+{
+ int32_t ns = stackNode->ns;
+ return (kNameSpaceID_XHTML == ns) || (stackNode->isHtmlIntegrationPoint()) || ((kNameSpaceID_MathML == ns) && (stackNode->getGroup() == MI_MO_MN_MS_MTEXT));
+}
+
+nsHtml5String
+nsHtml5TreeBuilder::extractCharsetFromContent(nsHtml5String attributeValue, nsHtml5TreeBuilder* tb)
+{
+ int32_t charsetState = CHARSET_INITIAL;
+ int32_t start = -1;
+ int32_t end = -1;
+ autoJArray<char16_t,int32_t> buffer = nsHtml5Portability::newCharArrayFromString(attributeValue);
+ for (int32_t i = 0; i < buffer.length; i++) {
+ char16_t c = buffer[i];
+ switch(charsetState) {
+ case CHARSET_INITIAL: {
+ switch(c) {
+ case 'c':
+ case 'C': {
+ charsetState = CHARSET_C;
+ continue;
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ case CHARSET_C: {
+ switch(c) {
+ case 'h':
+ case 'H': {
+ charsetState = CHARSET_H;
+ continue;
+ }
+ default: {
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ }
+ }
+ case CHARSET_H: {
+ switch(c) {
+ case 'a':
+ case 'A': {
+ charsetState = CHARSET_A;
+ continue;
+ }
+ default: {
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ }
+ }
+ case CHARSET_A: {
+ switch(c) {
+ case 'r':
+ case 'R': {
+ charsetState = CHARSET_R;
+ continue;
+ }
+ default: {
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ }
+ }
+ case CHARSET_R: {
+ switch(c) {
+ case 's':
+ case 'S': {
+ charsetState = CHARSET_S;
+ continue;
+ }
+ default: {
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ }
+ }
+ case CHARSET_S: {
+ switch(c) {
+ case 'e':
+ case 'E': {
+ charsetState = CHARSET_E;
+ continue;
+ }
+ default: {
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ }
+ }
+ case CHARSET_E: {
+ switch(c) {
+ case 't':
+ case 'T': {
+ charsetState = CHARSET_T;
+ continue;
+ }
+ default: {
+ charsetState = CHARSET_INITIAL;
+ continue;
+ }
+ }
+ }
+ case CHARSET_T: {
+ switch(c) {
+ case '\t':
+ case '\n':
+ case '\f':
+ case '\r':
+ case ' ': {
+ continue;
+ }
+ case '=': {
+ charsetState = CHARSET_EQUALS;
+ continue;
+ }
+ default: {
+ return nullptr;
+ }
+ }
+ }
+ case CHARSET_EQUALS: {
+ switch(c) {
+ case '\t':
+ case '\n':
+ case '\f':
+ case '\r':
+ case ' ': {
+ continue;
+ }
+ case '\'': {
+ start = i + 1;
+ charsetState = CHARSET_SINGLE_QUOTED;
+ continue;
+ }
+ case '\"': {
+ start = i + 1;
+ charsetState = CHARSET_DOUBLE_QUOTED;
+ continue;
+ }
+ default: {
+ start = i;
+ charsetState = CHARSET_UNQUOTED;
+ continue;
+ }
+ }
+ }
+ case CHARSET_SINGLE_QUOTED: {
+ switch(c) {
+ case '\'': {
+ end = i;
+ NS_HTML5_BREAK(charsetloop);
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ case CHARSET_DOUBLE_QUOTED: {
+ switch(c) {
+ case '\"': {
+ end = i;
+ NS_HTML5_BREAK(charsetloop);
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ case CHARSET_UNQUOTED: {
+ switch(c) {
+ case '\t':
+ case '\n':
+ case '\f':
+ case '\r':
+ case ' ':
+ case ';': {
+ end = i;
+ NS_HTML5_BREAK(charsetloop);
+ }
+ default: {
+ continue;
+ }
+ }
+ }
+ }
+ }
+ charsetloop_end: ;
+ nsHtml5String charset = nullptr;
+ if (start != -1) {
+ if (end == -1) {
+ end = buffer.length;
+ }
+ charset = nsHtml5Portability::newStringFromBuffer(buffer, start, end - start, tb, false);
+ }
+ return charset;
+}
+
+void
+nsHtml5TreeBuilder::checkMetaCharset(nsHtml5HtmlAttributes* attributes)
+{
+ nsHtml5String charset = attributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
+ if (charset) {
+ if (tokenizer->internalEncodingDeclaration(charset)) {
+ requestSuspension();
+ return;
+ }
+ return;
+ }
+ if (!nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("content-type", attributes->getValue(nsHtml5AttributeName::ATTR_HTTP_EQUIV))) {
+ return;
+ }
+ nsHtml5String content = attributes->getValue(nsHtml5AttributeName::ATTR_CONTENT);
+ if (content) {
+ nsHtml5String extract = nsHtml5TreeBuilder::extractCharsetFromContent(content, this);
+ if (extract) {
+ if (tokenizer->internalEncodingDeclaration(extract)) {
+ requestSuspension();
+ }
+ }
+ extract.Release();
+ }
+}
+
+void
+nsHtml5TreeBuilder::endTag(nsHtml5ElementName* elementName)
+{
+ flushCharacters();
+ needToDropLF = false;
+ int32_t eltPos;
+ int32_t group = elementName->getGroup();
+ nsIAtom* name = elementName->getName();
+ for (; ; ) {
+ if (isInForeign()) {
+ if (stack[currentPtr]->name != name) {
+ if (!currentPtr) {
+ errStrayEndTag(name);
+ } else {
+ errEndTagDidNotMatchCurrentOpenElement(name, stack[currentPtr]->popName);
+ }
+ }
+ eltPos = currentPtr;
+ for (; ; ) {
+ if (!eltPos) {
+ MOZ_ASSERT(fragment, "We can get this close to the root of the stack in foreign content only in the fragment case.");
+ NS_HTML5_BREAK(endtagloop);
+ }
+ if (stack[eltPos]->name == name) {
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ NS_HTML5_BREAK(endtagloop);
+ }
+ if (stack[--eltPos]->ns == kNameSpaceID_XHTML) {
+ break;
+ }
+ }
+ }
+ switch(mode) {
+ case IN_TEMPLATE: {
+ switch(group) {
+ case TEMPLATE: {
+ break;
+ }
+ default: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ }
+ case IN_ROW: {
+ switch(group) {
+ case TR: {
+ eltPos = findLastOrRoot(nsHtml5TreeBuilder::TR);
+ if (!eltPos) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errNoTableRowToClose();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE_BODY;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case TABLE: {
+ eltPos = findLastOrRoot(nsHtml5TreeBuilder::TR);
+ if (!eltPos) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errNoTableRowToClose();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE_BODY;
+ continue;
+ }
+ case TBODY_OR_THEAD_OR_TFOOT: {
+ if (findLastInTableScope(name) == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ eltPos = findLastOrRoot(nsHtml5TreeBuilder::TR);
+ if (!eltPos) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errNoTableRowToClose();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE_BODY;
+ continue;
+ }
+ case BODY:
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case HTML:
+ case TD_OR_TH: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case IN_TABLE_BODY: {
+ switch(group) {
+ case TBODY_OR_THEAD_OR_TFOOT: {
+ eltPos = findLastOrRoot(name);
+ if (!eltPos) {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case TABLE: {
+ eltPos = findLastInTableScopeOrRootTemplateTbodyTheadTfoot();
+ if (!eltPos || stack[eltPos]->getGroup() == TEMPLATE) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ clearStackBackTo(eltPos);
+ pop();
+ mode = IN_TABLE;
+ continue;
+ }
+ case BODY:
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case HTML:
+ case TD_OR_TH:
+ case TR: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case IN_TABLE: {
+ switch(group) {
+ case TABLE: {
+ eltPos = findLast(nsHtml5Atoms::table);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case BODY:
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case HTML:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TD_OR_TH:
+ case TR: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case TEMPLATE: {
+ break;
+ }
+ default: {
+ errStrayEndTag(name);
+ }
+ }
+ }
+ case IN_CAPTION: {
+ switch(group) {
+ case CAPTION: {
+ eltPos = findLastInTableScope(nsHtml5Atoms::caption);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ NS_HTML5_BREAK(endtagloop);
+ }
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && currentPtr != eltPos) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ mode = IN_TABLE;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case TABLE: {
+ errTableClosedWhileCaptionOpen();
+ eltPos = findLastInTableScope(nsHtml5Atoms::caption);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ NS_HTML5_BREAK(endtagloop);
+ }
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && currentPtr != eltPos) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ mode = IN_TABLE;
+ continue;
+ }
+ case BODY:
+ case COL:
+ case COLGROUP:
+ case HTML:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TD_OR_TH:
+ case TR: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case IN_CELL: {
+ switch(group) {
+ case TD_OR_TH: {
+ eltPos = findLastInTableScope(name);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ mode = IN_ROW;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case TABLE:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR: {
+ if (findLastInTableScope(name) == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ MOZ_ASSERT(name == nsHtml5Atoms::tbody || name == nsHtml5Atoms::tfoot || name == nsHtml5Atoms::thead || fragment || isTemplateContents());
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ closeTheCell(findLastInTableScopeTdTh());
+ continue;
+ }
+ case BODY:
+ case CAPTION:
+ case COL:
+ case COLGROUP:
+ case HTML: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case FRAMESET_OK:
+ case IN_BODY: {
+ switch(group) {
+ case BODY: {
+ if (!isSecondOnStackBody()) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ MOZ_ASSERT(currentPtr >= 1);
+ if (MOZ_UNLIKELY(mViewSource)) {
+ for (int32_t i = 2; i <= currentPtr; i++) {
+ switch(stack[i]->getGroup()) {
+ case DD_OR_DT:
+ case LI:
+ case OPTGROUP:
+ case OPTION:
+ case P:
+ case RB_OR_RTC:
+ case RT_OR_RP:
+ case TD_OR_TH:
+ case TBODY_OR_THEAD_OR_TFOOT: {
+ break;
+ }
+ default: {
+ errEndWithUnclosedElements(name);
+ NS_HTML5_BREAK(uncloseloop1);
+ }
+ }
+ }
+ uncloseloop1_end: ;
+ }
+ mode = AFTER_BODY;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case HTML: {
+ if (!isSecondOnStackBody()) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ if (MOZ_UNLIKELY(mViewSource)) {
+ for (int32_t i = 0; i <= currentPtr; i++) {
+ switch(stack[i]->getGroup()) {
+ case DD_OR_DT:
+ case LI:
+ case P:
+ case RB_OR_RTC:
+ case RT_OR_RP:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TD_OR_TH:
+ case BODY:
+ case HTML: {
+ break;
+ }
+ default: {
+ errEndWithUnclosedElements(name);
+ NS_HTML5_BREAK(uncloseloop2);
+ }
+ }
+ }
+ uncloseloop2_end: ;
+ }
+ mode = AFTER_BODY;
+ continue;
+ }
+ case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU:
+ case UL_OR_OL_OR_DL:
+ case PRE_OR_LISTING:
+ case FIELDSET:
+ case BUTTON:
+ case ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY: {
+ eltPos = findLastInScope(name);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ } else {
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ }
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case FORM: {
+ if (!isTemplateContents()) {
+ if (!formPointer) {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ formPointer = nullptr;
+ eltPos = findLastInScope(name);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ removeFromStack(eltPos);
+ NS_HTML5_BREAK(endtagloop);
+ } else {
+ eltPos = findLastInScope(name);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ case P: {
+ eltPos = findLastInButtonScope(nsHtml5Atoms::p);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errNoElementToCloseButEndTagSeen(nsHtml5Atoms::p);
+ if (isInForeign()) {
+ errHtmlStartTagInForeignContext(name);
+ while (currentPtr >= 0 && stack[currentPtr]->ns != kNameSpaceID_XHTML) {
+ pop();
+ }
+ }
+ appendVoidElementToCurrentMayFoster(elementName, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ generateImpliedEndTagsExceptFor(nsHtml5Atoms::p);
+ MOZ_ASSERT(eltPos != nsHtml5TreeBuilder::NOT_FOUND_ON_STACK);
+ if (!!MOZ_UNLIKELY(mViewSource) && eltPos != currentPtr) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case LI: {
+ eltPos = findLastInListScope(name);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errNoElementToCloseButEndTagSeen(name);
+ } else {
+ generateImpliedEndTagsExceptFor(name);
+ if (!!MOZ_UNLIKELY(mViewSource) && eltPos != currentPtr) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ }
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case DD_OR_DT: {
+ eltPos = findLastInScope(name);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errNoElementToCloseButEndTagSeen(name);
+ } else {
+ generateImpliedEndTagsExceptFor(name);
+ if (!!MOZ_UNLIKELY(mViewSource) && eltPos != currentPtr) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ }
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6: {
+ eltPos = findLastInScopeHn();
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ } else {
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ }
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case OBJECT:
+ case MARQUEE_OR_APPLET: {
+ eltPos = findLastInScope(name);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errStrayEndTag(name);
+ } else {
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ }
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case BR: {
+ errEndTagBr();
+ if (isInForeign()) {
+ errHtmlStartTagInForeignContext(name);
+ while (currentPtr >= 0 && stack[currentPtr]->ns != kNameSpaceID_XHTML) {
+ pop();
+ }
+ }
+ reconstructTheActiveFormattingElements();
+ appendVoidElementToCurrentMayFoster(elementName, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case TEMPLATE: {
+ break;
+ }
+ case AREA_OR_WBR:
+#ifdef ENABLE_VOID_MENUITEM
+ case MENUITEM:
+#endif
+ case PARAM_OR_SOURCE_OR_TRACK:
+ case EMBED:
+ case IMG:
+ case IMAGE:
+ case INPUT:
+ case KEYGEN:
+ case HR:
+ case ISINDEX:
+ case IFRAME:
+ case NOEMBED:
+ case NOFRAMES:
+ case SELECT:
+ case TABLE:
+ case TEXTAREA: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case NOSCRIPT: {
+ if (scriptingEnabled) {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ } else {
+ }
+ }
+ case A:
+ case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U:
+ case FONT:
+ case NOBR: {
+ if (adoptionAgencyEndTag(name)) {
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ default: {
+ if (isCurrent(name)) {
+ pop();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ eltPos = currentPtr;
+ for (; ; ) {
+ nsHtml5StackNode* node = stack[eltPos];
+ if (node->ns == kNameSpaceID_XHTML && node->name == name) {
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) {
+ errUnclosedElements(eltPos, name);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ NS_HTML5_BREAK(endtagloop);
+ } else if (!eltPos || node->isSpecial()) {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ eltPos--;
+ }
+ }
+ }
+ }
+ case IN_HEAD: {
+ switch(group) {
+ case HEAD: {
+ pop();
+ mode = AFTER_HEAD;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case BR:
+ case HTML:
+ case BODY: {
+ pop();
+ mode = AFTER_HEAD;
+ continue;
+ }
+ case TEMPLATE: {
+ endTagTemplateInHead();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ default: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ }
+ case IN_HEAD_NOSCRIPT: {
+ switch(group) {
+ case NOSCRIPT: {
+ pop();
+ mode = IN_HEAD;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case BR: {
+ errStrayEndTag(name);
+ pop();
+ mode = IN_HEAD;
+ continue;
+ }
+ default: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ }
+ case IN_COLUMN_GROUP: {
+ switch(group) {
+ case COLGROUP: {
+ if (!currentPtr || stack[currentPtr]->getGroup() == nsHtml5TreeBuilder::TEMPLATE) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errGarbageInColgroup();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ pop();
+ mode = IN_TABLE;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case COL: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case TEMPLATE: {
+ endTagTemplateInHead();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ default: {
+ if (!currentPtr || stack[currentPtr]->getGroup() == nsHtml5TreeBuilder::TEMPLATE) {
+ MOZ_ASSERT(fragment || isTemplateContents());
+ errGarbageInColgroup();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ pop();
+ mode = IN_TABLE;
+ continue;
+ }
+ }
+ }
+ case IN_SELECT_IN_TABLE: {
+ switch(group) {
+ case CAPTION:
+ case TABLE:
+ case TBODY_OR_THEAD_OR_TFOOT:
+ case TR:
+ case TD_OR_TH: {
+ errEndTagSeenWithSelectOpen(name);
+ if (findLastInTableScope(name) != nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ eltPos = findLastInTableScope(nsHtml5Atoms::select);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ MOZ_ASSERT(fragment);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ continue;
+ } else {
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ default:
+ ; // fall through
+ }
+ }
+ case IN_SELECT: {
+ switch(group) {
+ case OPTION: {
+ if (isCurrent(nsHtml5Atoms::option)) {
+ pop();
+ NS_HTML5_BREAK(endtagloop);
+ } else {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ case OPTGROUP: {
+ if (isCurrent(nsHtml5Atoms::option) && nsHtml5Atoms::optgroup == stack[currentPtr - 1]->name) {
+ pop();
+ }
+ if (isCurrent(nsHtml5Atoms::optgroup)) {
+ pop();
+ } else {
+ errStrayEndTag(name);
+ }
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case SELECT: {
+ eltPos = findLastInTableScope(nsHtml5Atoms::select);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ MOZ_ASSERT(fragment);
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ resetTheInsertionMode();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case TEMPLATE: {
+ endTagTemplateInHead();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ default: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ }
+ case AFTER_BODY: {
+ switch(group) {
+ case HTML: {
+ if (fragment) {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ } else {
+ mode = AFTER_AFTER_BODY;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ default: {
+ errEndTagAfterBody();
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ continue;
+ }
+ }
+ }
+ case IN_FRAMESET: {
+ switch(group) {
+ case FRAMESET: {
+ if (!currentPtr) {
+ MOZ_ASSERT(fragment);
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ pop();
+ if ((!fragment) && !isCurrent(nsHtml5Atoms::frameset)) {
+ mode = AFTER_FRAMESET;
+ }
+ NS_HTML5_BREAK(endtagloop);
+ }
+ default: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ }
+ case AFTER_FRAMESET: {
+ switch(group) {
+ case HTML: {
+ mode = AFTER_AFTER_FRAMESET;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ default: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ }
+ case INITIAL: {
+ errEndTagSeenWithoutDoctype();
+ documentModeInternal(QUIRKS_MODE, nullptr, nullptr, false);
+ mode = BEFORE_HTML;
+ continue;
+ }
+ case BEFORE_HTML: {
+ switch(group) {
+ case HEAD:
+ case BR:
+ case HTML:
+ case BODY: {
+ appendHtmlElementToDocumentAndPush();
+ mode = BEFORE_HEAD;
+ continue;
+ }
+ default: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ }
+ case BEFORE_HEAD: {
+ switch(group) {
+ case HEAD:
+ case BR:
+ case HTML:
+ case BODY: {
+ appendToCurrentNodeAndPushHeadElement(nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES);
+ mode = IN_HEAD;
+ continue;
+ }
+ default: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ }
+ case AFTER_HEAD: {
+ switch(group) {
+ case TEMPLATE: {
+ endTagTemplateInHead();
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case HTML:
+ case BODY:
+ case BR: {
+ appendToCurrentNodeAndPushBodyElement();
+ mode = FRAMESET_OK;
+ continue;
+ }
+ default: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ }
+ case AFTER_AFTER_BODY: {
+ errStrayEndTag(name);
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ continue;
+ }
+ case AFTER_AFTER_FRAMESET: {
+ errStrayEndTag(name);
+ NS_HTML5_BREAK(endtagloop);
+ }
+ case TEXT: {
+ pop();
+ if (originalMode == AFTER_HEAD) {
+ silentPop();
+ }
+ mode = originalMode;
+ NS_HTML5_BREAK(endtagloop);
+ }
+ }
+ }
+ endtagloop_end: ;
+}
+
+void
+nsHtml5TreeBuilder::endTagTemplateInHead()
+{
+ int32_t eltPos = findLast(nsHtml5Atoms::template_);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ errStrayEndTag(nsHtml5Atoms::template_);
+ return;
+ }
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(nsHtml5Atoms::template_)) {
+ errUnclosedElements(eltPos, nsHtml5Atoms::template_);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ popTemplateMode();
+ resetTheInsertionMode();
+}
+
+int32_t
+nsHtml5TreeBuilder::findLastInTableScopeOrRootTemplateTbodyTheadTfoot()
+{
+ for (int32_t i = currentPtr; i > 0; i--) {
+ if (stack[i]->getGroup() == nsHtml5TreeBuilder::TBODY_OR_THEAD_OR_TFOOT || stack[i]->getGroup() == nsHtml5TreeBuilder::TEMPLATE) {
+ return i;
+ }
+ }
+ return 0;
+}
+
+int32_t
+nsHtml5TreeBuilder::findLast(nsIAtom* name)
+{
+ for (int32_t i = currentPtr; i > 0; i--) {
+ if (stack[i]->ns == kNameSpaceID_XHTML && stack[i]->name == name) {
+ return i;
+ }
+ }
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+}
+
+int32_t
+nsHtml5TreeBuilder::findLastInTableScope(nsIAtom* name)
+{
+ for (int32_t i = currentPtr; i > 0; i--) {
+ if (stack[i]->ns == kNameSpaceID_XHTML) {
+ if (stack[i]->name == name) {
+ return i;
+ } else if (stack[i]->name == nsHtml5Atoms::table || stack[i]->name == nsHtml5Atoms::template_) {
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+ }
+ }
+ }
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+}
+
+int32_t
+nsHtml5TreeBuilder::findLastInButtonScope(nsIAtom* name)
+{
+ for (int32_t i = currentPtr; i > 0; i--) {
+ if (stack[i]->ns == kNameSpaceID_XHTML) {
+ if (stack[i]->name == name) {
+ return i;
+ } else if (stack[i]->name == nsHtml5Atoms::button) {
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+ }
+ }
+ if (stack[i]->isScoping()) {
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+ }
+ }
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+}
+
+int32_t
+nsHtml5TreeBuilder::findLastInScope(nsIAtom* name)
+{
+ for (int32_t i = currentPtr; i > 0; i--) {
+ if (stack[i]->ns == kNameSpaceID_XHTML && stack[i]->name == name) {
+ return i;
+ } else if (stack[i]->isScoping()) {
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+ }
+ }
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+}
+
+int32_t
+nsHtml5TreeBuilder::findLastInListScope(nsIAtom* name)
+{
+ for (int32_t i = currentPtr; i > 0; i--) {
+ if (stack[i]->ns == kNameSpaceID_XHTML) {
+ if (stack[i]->name == name) {
+ return i;
+ } else if (stack[i]->name == nsHtml5Atoms::ul || stack[i]->name == nsHtml5Atoms::ol) {
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+ }
+ }
+ if (stack[i]->isScoping()) {
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+ }
+ }
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+}
+
+int32_t
+nsHtml5TreeBuilder::findLastInScopeHn()
+{
+ for (int32_t i = currentPtr; i > 0; i--) {
+ if (stack[i]->getGroup() == nsHtml5TreeBuilder::H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6) {
+ return i;
+ } else if (stack[i]->isScoping()) {
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+ }
+ }
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+}
+
+void
+nsHtml5TreeBuilder::generateImpliedEndTagsExceptFor(nsIAtom* name)
+{
+ for (; ; ) {
+ nsHtml5StackNode* node = stack[currentPtr];
+ switch(node->getGroup()) {
+ case P:
+ case LI:
+ case DD_OR_DT:
+ case OPTION:
+ case OPTGROUP:
+ case RB_OR_RTC:
+ case RT_OR_RP: {
+ if (node->ns == kNameSpaceID_XHTML && node->name == name) {
+ return;
+ }
+ pop();
+ continue;
+ }
+ default: {
+ return;
+ }
+ }
+ }
+}
+
+void
+nsHtml5TreeBuilder::generateImpliedEndTags()
+{
+ for (; ; ) {
+ switch(stack[currentPtr]->getGroup()) {
+ case P:
+ case LI:
+ case DD_OR_DT:
+ case OPTION:
+ case OPTGROUP:
+ case RB_OR_RTC:
+ case RT_OR_RP: {
+ pop();
+ continue;
+ }
+ default: {
+ return;
+ }
+ }
+ }
+}
+
+bool
+nsHtml5TreeBuilder::isSecondOnStackBody()
+{
+ return currentPtr >= 1 && stack[1]->getGroup() == nsHtml5TreeBuilder::BODY;
+}
+
+void
+nsHtml5TreeBuilder::documentModeInternal(nsHtml5DocumentMode m, nsHtml5String publicIdentifier, nsHtml5String systemIdentifier, bool html4SpecificAdditionalErrorChecks)
+{
+ if (isSrcdocDocument) {
+ quirks = false;
+ this->documentMode(STANDARDS_MODE);
+ return;
+ }
+ quirks = (m == QUIRKS_MODE);
+ this->documentMode(m);
+}
+
+bool
+nsHtml5TreeBuilder::isAlmostStandards(nsHtml5String publicIdentifier, nsHtml5String systemIdentifier)
+{
+ if (nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("-//w3c//dtd xhtml 1.0 transitional//en", publicIdentifier)) {
+ return true;
+ }
+ if (nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("-//w3c//dtd xhtml 1.0 frameset//en", publicIdentifier)) {
+ return true;
+ }
+ if (systemIdentifier) {
+ if (nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("-//w3c//dtd html 4.01 transitional//en", publicIdentifier)) {
+ return true;
+ }
+ if (nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("-//w3c//dtd html 4.01 frameset//en", publicIdentifier)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+nsHtml5TreeBuilder::isQuirky(nsIAtom* name, nsHtml5String publicIdentifier, nsHtml5String systemIdentifier, bool forceQuirks)
+{
+ if (forceQuirks) {
+ return true;
+ }
+ if (name != nsHtml5Atoms::html) {
+ return true;
+ }
+ if (publicIdentifier) {
+ for (int32_t i = 0; i < nsHtml5TreeBuilder::QUIRKY_PUBLIC_IDS.length; i++) {
+ if (nsHtml5Portability::lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString(nsHtml5TreeBuilder::QUIRKY_PUBLIC_IDS[i], publicIdentifier)) {
+ return true;
+ }
+ }
+ if (nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("-//w3o//dtd w3 html strict 3.0//en//", publicIdentifier) || nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("-/w3c/dtd html 4.0 transitional/en", publicIdentifier) || nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("html", publicIdentifier)) {
+ return true;
+ }
+ }
+ if (!systemIdentifier) {
+ if (nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("-//w3c//dtd html 4.01 transitional//en", publicIdentifier)) {
+ return true;
+ } else if (nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("-//w3c//dtd html 4.01 frameset//en", publicIdentifier)) {
+ return true;
+ }
+ } else if (nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd", systemIdentifier)) {
+ return true;
+ }
+ return false;
+}
+
+void
+nsHtml5TreeBuilder::closeTheCell(int32_t eltPos)
+{
+ generateImpliedEndTags();
+ if (!!MOZ_UNLIKELY(mViewSource) && eltPos != currentPtr) {
+ errUnclosedElementsCell(eltPos);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+ clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ mode = IN_ROW;
+ return;
+}
+
+int32_t
+nsHtml5TreeBuilder::findLastInTableScopeTdTh()
+{
+ for (int32_t i = currentPtr; i > 0; i--) {
+ nsIAtom* name = stack[i]->name;
+ if (stack[i]->ns == kNameSpaceID_XHTML) {
+ if (nsHtml5Atoms::td == name || nsHtml5Atoms::th == name) {
+ return i;
+ } else if (name == nsHtml5Atoms::table || name == nsHtml5Atoms::template_) {
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+ }
+ }
+ }
+ return nsHtml5TreeBuilder::NOT_FOUND_ON_STACK;
+}
+
+void
+nsHtml5TreeBuilder::clearStackBackTo(int32_t eltPos)
+{
+ int32_t eltGroup = stack[eltPos]->getGroup();
+ while (currentPtr > eltPos) {
+ if (stack[currentPtr]->ns == kNameSpaceID_XHTML && stack[currentPtr]->getGroup() == TEMPLATE && (eltGroup == TABLE || eltGroup == TBODY_OR_THEAD_OR_TFOOT || eltGroup == TR || !eltPos)) {
+ return;
+ }
+ pop();
+ }
+}
+
+void
+nsHtml5TreeBuilder::resetTheInsertionMode()
+{
+ nsHtml5StackNode* node;
+ nsIAtom* name;
+ int32_t ns;
+ for (int32_t i = currentPtr; i >= 0; i--) {
+ node = stack[i];
+ name = node->name;
+ ns = node->ns;
+ if (!i) {
+ if (!(contextNamespace == kNameSpaceID_XHTML && (contextName == nsHtml5Atoms::td || contextName == nsHtml5Atoms::th))) {
+ if (fragment) {
+ name = contextName;
+ ns = contextNamespace;
+ }
+ } else {
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ return;
+ }
+ }
+ if (nsHtml5Atoms::select == name) {
+ int32_t ancestorIndex = i;
+ while (ancestorIndex > 0) {
+ nsHtml5StackNode* ancestor = stack[ancestorIndex--];
+ if (kNameSpaceID_XHTML == ancestor->ns) {
+ if (nsHtml5Atoms::template_ == ancestor->name) {
+ break;
+ }
+ if (nsHtml5Atoms::table == ancestor->name) {
+ mode = IN_SELECT_IN_TABLE;
+ return;
+ }
+ }
+ }
+ mode = IN_SELECT;
+ return;
+ } else if (nsHtml5Atoms::td == name || nsHtml5Atoms::th == name) {
+ mode = IN_CELL;
+ return;
+ } else if (nsHtml5Atoms::tr == name) {
+ mode = IN_ROW;
+ return;
+ } else if (nsHtml5Atoms::tbody == name || nsHtml5Atoms::thead == name || nsHtml5Atoms::tfoot == name) {
+ mode = IN_TABLE_BODY;
+ return;
+ } else if (nsHtml5Atoms::caption == name) {
+ mode = IN_CAPTION;
+ return;
+ } else if (nsHtml5Atoms::colgroup == name) {
+ mode = IN_COLUMN_GROUP;
+ return;
+ } else if (nsHtml5Atoms::table == name) {
+ mode = IN_TABLE;
+ return;
+ } else if (kNameSpaceID_XHTML != ns) {
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ return;
+ } else if (nsHtml5Atoms::template_ == name) {
+ MOZ_ASSERT(templateModePtr >= 0);
+ mode = templateModeStack[templateModePtr];
+ return;
+ } else if (nsHtml5Atoms::head == name) {
+ if (name == contextName) {
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ } else {
+ mode = IN_HEAD;
+ }
+ return;
+ } else if (nsHtml5Atoms::body == name) {
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ return;
+ } else if (nsHtml5Atoms::frameset == name) {
+ mode = IN_FRAMESET;
+ return;
+ } else if (nsHtml5Atoms::html == name) {
+ if (!headPointer) {
+ mode = BEFORE_HEAD;
+ } else {
+ mode = AFTER_HEAD;
+ }
+ return;
+ } else if (!i) {
+ mode = framesetOk ? FRAMESET_OK : IN_BODY;
+ return;
+ }
+ }
+}
+
+void
+nsHtml5TreeBuilder::implicitlyCloseP()
+{
+ int32_t eltPos = findLastInButtonScope(nsHtml5Atoms::p);
+ if (eltPos == nsHtml5TreeBuilder::NOT_FOUND_ON_STACK) {
+ return;
+ }
+ generateImpliedEndTagsExceptFor(nsHtml5Atoms::p);
+ if (!!MOZ_UNLIKELY(mViewSource) && eltPos != currentPtr) {
+ errUnclosedElementsImplied(eltPos, nsHtml5Atoms::p);
+ }
+ while (currentPtr >= eltPos) {
+ pop();
+ }
+}
+
+bool
+nsHtml5TreeBuilder::debugOnlyClearLastStackSlot()
+{
+ stack[currentPtr] = nullptr;
+ return true;
+}
+
+bool
+nsHtml5TreeBuilder::debugOnlyClearLastListSlot()
+{
+ listOfActiveFormattingElements[listPtr] = nullptr;
+ return true;
+}
+
+void
+nsHtml5TreeBuilder::pushTemplateMode(int32_t mode)
+{
+ templateModePtr++;
+ if (templateModePtr == templateModeStack.length) {
+ jArray<int32_t,int32_t> newStack = jArray<int32_t,int32_t>::newJArray(templateModeStack.length + 64);
+ nsHtml5ArrayCopy::arraycopy(templateModeStack, newStack, templateModeStack.length);
+ templateModeStack = newStack;
+ }
+ templateModeStack[templateModePtr] = mode;
+}
+
+void
+nsHtml5TreeBuilder::push(nsHtml5StackNode* node)
+{
+ currentPtr++;
+ if (currentPtr == stack.length) {
+ jArray<nsHtml5StackNode*,int32_t> newStack = jArray<nsHtml5StackNode*,int32_t>::newJArray(stack.length + 64);
+ nsHtml5ArrayCopy::arraycopy(stack, newStack, stack.length);
+ stack = newStack;
+ }
+ stack[currentPtr] = node;
+ elementPushed(node->ns, node->popName, node->node);
+}
+
+void
+nsHtml5TreeBuilder::silentPush(nsHtml5StackNode* node)
+{
+ currentPtr++;
+ if (currentPtr == stack.length) {
+ jArray<nsHtml5StackNode*,int32_t> newStack = jArray<nsHtml5StackNode*,int32_t>::newJArray(stack.length + 64);
+ nsHtml5ArrayCopy::arraycopy(stack, newStack, stack.length);
+ stack = newStack;
+ }
+ stack[currentPtr] = node;
+}
+
+void
+nsHtml5TreeBuilder::append(nsHtml5StackNode* node)
+{
+ listPtr++;
+ if (listPtr == listOfActiveFormattingElements.length) {
+ jArray<nsHtml5StackNode*,int32_t> newList = jArray<nsHtml5StackNode*,int32_t>::newJArray(listOfActiveFormattingElements.length + 64);
+ nsHtml5ArrayCopy::arraycopy(listOfActiveFormattingElements, newList, listOfActiveFormattingElements.length);
+ listOfActiveFormattingElements = newList;
+ }
+ listOfActiveFormattingElements[listPtr] = node;
+}
+
+void
+nsHtml5TreeBuilder::clearTheListOfActiveFormattingElementsUpToTheLastMarker()
+{
+ while (listPtr > -1) {
+ if (!listOfActiveFormattingElements[listPtr]) {
+ --listPtr;
+ return;
+ }
+ listOfActiveFormattingElements[listPtr]->release();
+ --listPtr;
+ }
+}
+
+void
+nsHtml5TreeBuilder::removeFromStack(int32_t pos)
+{
+ if (currentPtr == pos) {
+ pop();
+ } else {
+
+ stack[pos]->release();
+ nsHtml5ArrayCopy::arraycopy(stack, pos + 1, pos, currentPtr - pos);
+ MOZ_ASSERT(debugOnlyClearLastStackSlot());
+ currentPtr--;
+ }
+}
+
+void
+nsHtml5TreeBuilder::removeFromStack(nsHtml5StackNode* node)
+{
+ if (stack[currentPtr] == node) {
+ pop();
+ } else {
+ int32_t pos = currentPtr - 1;
+ while (pos >= 0 && stack[pos] != node) {
+ pos--;
+ }
+ if (pos == -1) {
+ return;
+ }
+
+ node->release();
+ nsHtml5ArrayCopy::arraycopy(stack, pos + 1, pos, currentPtr - pos);
+ currentPtr--;
+ }
+}
+
+void
+nsHtml5TreeBuilder::removeFromListOfActiveFormattingElements(int32_t pos)
+{
+ MOZ_ASSERT(!!listOfActiveFormattingElements[pos]);
+ listOfActiveFormattingElements[pos]->release();
+ if (pos == listPtr) {
+ MOZ_ASSERT(debugOnlyClearLastListSlot());
+ listPtr--;
+ return;
+ }
+ MOZ_ASSERT(pos < listPtr);
+ nsHtml5ArrayCopy::arraycopy(listOfActiveFormattingElements, pos + 1, pos, listPtr - pos);
+ MOZ_ASSERT(debugOnlyClearLastListSlot());
+ listPtr--;
+}
+
+bool
+nsHtml5TreeBuilder::adoptionAgencyEndTag(nsIAtom* name)
+{
+ if (stack[currentPtr]->ns == kNameSpaceID_XHTML && stack[currentPtr]->name == name && findInListOfActiveFormattingElements(stack[currentPtr]) == -1) {
+ pop();
+ return true;
+ }
+ for (int32_t i = 0; i < 8; ++i) {
+ int32_t formattingEltListPos = listPtr;
+ while (formattingEltListPos > -1) {
+ nsHtml5StackNode* listNode = listOfActiveFormattingElements[formattingEltListPos];
+ if (!listNode) {
+ formattingEltListPos = -1;
+ break;
+ } else if (listNode->name == name) {
+ break;
+ }
+ formattingEltListPos--;
+ }
+ if (formattingEltListPos == -1) {
+ return false;
+ }
+ nsHtml5StackNode* formattingElt = listOfActiveFormattingElements[formattingEltListPos];
+ int32_t formattingEltStackPos = currentPtr;
+ bool inScope = true;
+ while (formattingEltStackPos > -1) {
+ nsHtml5StackNode* node = stack[formattingEltStackPos];
+ if (node == formattingElt) {
+ break;
+ } else if (node->isScoping()) {
+ inScope = false;
+ }
+ formattingEltStackPos--;
+ }
+ if (formattingEltStackPos == -1) {
+ errNoElementToCloseButEndTagSeen(name);
+ removeFromListOfActiveFormattingElements(formattingEltListPos);
+ return true;
+ }
+ if (!inScope) {
+ errNoElementToCloseButEndTagSeen(name);
+ return true;
+ }
+ if (formattingEltStackPos != currentPtr) {
+ errEndTagViolatesNestingRules(name);
+ }
+ int32_t furthestBlockPos = formattingEltStackPos + 1;
+ while (furthestBlockPos <= currentPtr) {
+ nsHtml5StackNode* node = stack[furthestBlockPos];
+ MOZ_ASSERT(furthestBlockPos > 0, "How is formattingEltStackPos + 1 not > 0?");
+ if (node->isSpecial()) {
+ break;
+ }
+ furthestBlockPos++;
+ }
+ if (furthestBlockPos > currentPtr) {
+ while (currentPtr >= formattingEltStackPos) {
+ pop();
+ }
+ removeFromListOfActiveFormattingElements(formattingEltListPos);
+ return true;
+ }
+ nsHtml5StackNode* commonAncestor = stack[formattingEltStackPos - 1];
+ nsHtml5StackNode* furthestBlock = stack[furthestBlockPos];
+ int32_t bookmark = formattingEltListPos;
+ int32_t nodePos = furthestBlockPos;
+ nsHtml5StackNode* lastNode = furthestBlock;
+ int32_t j = 0;
+ for (; ; ) {
+ ++j;
+ nodePos--;
+ if (nodePos == formattingEltStackPos) {
+ break;
+ }
+ nsHtml5StackNode* node = stack[nodePos];
+ int32_t nodeListPos = findInListOfActiveFormattingElements(node);
+ if (j > 3 && nodeListPos != -1) {
+ removeFromListOfActiveFormattingElements(nodeListPos);
+ if (nodeListPos <= formattingEltListPos) {
+ formattingEltListPos--;
+ }
+ if (nodeListPos <= bookmark) {
+ bookmark--;
+ }
+ nodeListPos = -1;
+ }
+ if (nodeListPos == -1) {
+ MOZ_ASSERT(formattingEltStackPos < nodePos);
+ MOZ_ASSERT(bookmark < nodePos);
+ MOZ_ASSERT(furthestBlockPos > nodePos);
+ removeFromStack(nodePos);
+ furthestBlockPos--;
+ continue;
+ }
+ if (nodePos == furthestBlockPos) {
+ bookmark = nodeListPos + 1;
+ }
+ MOZ_ASSERT(node == listOfActiveFormattingElements[nodeListPos]);
+ MOZ_ASSERT(node == stack[nodePos]);
+ nsIContentHandle* clone = createElement(kNameSpaceID_XHTML, node->name, node->attributes->cloneAttributes(nullptr), commonAncestor->node, htmlCreator(node->getHtmlCreator()));
+ nsHtml5StackNode* newNode = new nsHtml5StackNode(node->getFlags(), node->ns, node->name, clone, node->popName, node->attributes, node->getHtmlCreator());
+ node->dropAttributes();
+ stack[nodePos] = newNode;
+ newNode->retain();
+ listOfActiveFormattingElements[nodeListPos] = newNode;
+ node->release();
+ node->release();
+ node = newNode;
+ detachFromParent(lastNode->node);
+ appendElement(lastNode->node, node->node);
+ lastNode = node;
+ }
+ if (commonAncestor->isFosterParenting()) {
+
+ detachFromParent(lastNode->node);
+ insertIntoFosterParent(lastNode->node);
+ } else {
+ detachFromParent(lastNode->node);
+ appendElement(lastNode->node, commonAncestor->node);
+ }
+ nsIContentHandle* clone = createElement(kNameSpaceID_XHTML, formattingElt->name, formattingElt->attributes->cloneAttributes(nullptr), furthestBlock->node, htmlCreator(formattingElt->getHtmlCreator()));
+ nsHtml5StackNode* formattingClone = new nsHtml5StackNode(formattingElt->getFlags(), formattingElt->ns, formattingElt->name, clone, formattingElt->popName, formattingElt->attributes, formattingElt->getHtmlCreator());
+ formattingElt->dropAttributes();
+ appendChildrenToNewParent(furthestBlock->node, clone);
+ appendElement(clone, furthestBlock->node);
+ removeFromListOfActiveFormattingElements(formattingEltListPos);
+ insertIntoListOfActiveFormattingElements(formattingClone, bookmark);
+ MOZ_ASSERT(formattingEltStackPos < furthestBlockPos);
+ removeFromStack(formattingEltStackPos);
+ insertIntoStack(formattingClone, furthestBlockPos);
+ }
+ return true;
+}
+
+void
+nsHtml5TreeBuilder::insertIntoStack(nsHtml5StackNode* node, int32_t position)
+{
+ MOZ_ASSERT(currentPtr + 1 < stack.length);
+ MOZ_ASSERT(position <= currentPtr + 1);
+ if (position == currentPtr + 1) {
+ push(node);
+ } else {
+ nsHtml5ArrayCopy::arraycopy(stack, position, position + 1, (currentPtr - position) + 1);
+ currentPtr++;
+ stack[position] = node;
+ }
+}
+
+void
+nsHtml5TreeBuilder::insertIntoListOfActiveFormattingElements(nsHtml5StackNode* formattingClone, int32_t bookmark)
+{
+ formattingClone->retain();
+ MOZ_ASSERT(listPtr + 1 < listOfActiveFormattingElements.length);
+ if (bookmark <= listPtr) {
+ nsHtml5ArrayCopy::arraycopy(listOfActiveFormattingElements, bookmark, bookmark + 1, (listPtr - bookmark) + 1);
+ }
+ listPtr++;
+ listOfActiveFormattingElements[bookmark] = formattingClone;
+}
+
+int32_t
+nsHtml5TreeBuilder::findInListOfActiveFormattingElements(nsHtml5StackNode* node)
+{
+ for (int32_t i = listPtr; i >= 0; i--) {
+ if (node == listOfActiveFormattingElements[i]) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int32_t
+nsHtml5TreeBuilder::findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker(nsIAtom* name)
+{
+ for (int32_t i = listPtr; i >= 0; i--) {
+ nsHtml5StackNode* node = listOfActiveFormattingElements[i];
+ if (!node) {
+ return -1;
+ } else if (node->name == name) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void
+nsHtml5TreeBuilder::maybeForgetEarlierDuplicateFormattingElement(nsIAtom* name, nsHtml5HtmlAttributes* attributes)
+{
+ int32_t candidate = -1;
+ int32_t count = 0;
+ for (int32_t i = listPtr; i >= 0; i--) {
+ nsHtml5StackNode* node = listOfActiveFormattingElements[i];
+ if (!node) {
+ break;
+ }
+ if (node->name == name && node->attributes->equalsAnother(attributes)) {
+ candidate = i;
+ ++count;
+ }
+ }
+ if (count >= 3) {
+ removeFromListOfActiveFormattingElements(candidate);
+ }
+}
+
+int32_t
+nsHtml5TreeBuilder::findLastOrRoot(nsIAtom* name)
+{
+ for (int32_t i = currentPtr; i > 0; i--) {
+ if (stack[i]->ns == kNameSpaceID_XHTML && stack[i]->name == name) {
+ return i;
+ }
+ }
+ return 0;
+}
+
+int32_t
+nsHtml5TreeBuilder::findLastOrRoot(int32_t group)
+{
+ for (int32_t i = currentPtr; i > 0; i--) {
+ if (stack[i]->getGroup() == group) {
+ return i;
+ }
+ }
+ return 0;
+}
+
+bool
+nsHtml5TreeBuilder::addAttributesToBody(nsHtml5HtmlAttributes* attributes)
+{
+ if (currentPtr >= 1) {
+ nsHtml5StackNode* body = stack[1];
+ if (body->getGroup() == nsHtml5TreeBuilder::BODY) {
+ addAttributesToElement(body->node, attributes);
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+nsHtml5TreeBuilder::addAttributesToHtml(nsHtml5HtmlAttributes* attributes)
+{
+ addAttributesToElement(stack[0]->node, attributes);
+}
+
+void
+nsHtml5TreeBuilder::pushHeadPointerOntoStack()
+{
+ MOZ_ASSERT(!!headPointer);
+ MOZ_ASSERT(mode == AFTER_HEAD);
+
+ silentPush(new nsHtml5StackNode(nsHtml5ElementName::ELT_HEAD, headPointer));
+}
+
+void
+nsHtml5TreeBuilder::reconstructTheActiveFormattingElements()
+{
+ if (listPtr == -1) {
+ return;
+ }
+ nsHtml5StackNode* mostRecent = listOfActiveFormattingElements[listPtr];
+ if (!mostRecent || isInStack(mostRecent)) {
+ return;
+ }
+ int32_t entryPos = listPtr;
+ for (; ; ) {
+ entryPos--;
+ if (entryPos == -1) {
+ break;
+ }
+ if (!listOfActiveFormattingElements[entryPos]) {
+ break;
+ }
+ if (isInStack(listOfActiveFormattingElements[entryPos])) {
+ break;
+ }
+ }
+ while (entryPos < listPtr) {
+ entryPos++;
+ nsHtml5StackNode* entry = listOfActiveFormattingElements[entryPos];
+ nsHtml5StackNode* currentNode = stack[currentPtr];
+ nsIContentHandle* clone;
+ if (currentNode->isFosterParenting()) {
+ clone = createAndInsertFosterParentedElement(kNameSpaceID_XHTML, entry->name, entry->attributes->cloneAttributes(nullptr), htmlCreator(entry->getHtmlCreator()));
+ } else {
+ clone = createElement(kNameSpaceID_XHTML, entry->name, entry->attributes->cloneAttributes(nullptr), currentNode->node, htmlCreator(entry->getHtmlCreator()));
+ appendElement(clone, currentNode->node);
+ }
+ nsHtml5StackNode* entryClone = new nsHtml5StackNode(entry->getFlags(), entry->ns, entry->name, clone, entry->popName, entry->attributes, entry->getHtmlCreator());
+ entry->dropAttributes();
+ push(entryClone);
+ listOfActiveFormattingElements[entryPos] = entryClone;
+ entry->release();
+ entryClone->retain();
+ }
+}
+
+void
+nsHtml5TreeBuilder::insertIntoFosterParent(nsIContentHandle* child)
+{
+ int32_t tablePos = findLastOrRoot(nsHtml5TreeBuilder::TABLE);
+ int32_t templatePos = findLastOrRoot(nsHtml5TreeBuilder::TEMPLATE);
+ if (templatePos >= tablePos) {
+ appendElement(child, stack[templatePos]->node);
+ return;
+ }
+ nsHtml5StackNode* node = stack[tablePos];
+ insertFosterParentedChild(child, node->node, stack[tablePos - 1]->node);
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::createAndInsertFosterParentedElement(int32_t ns, nsIAtom* name, nsHtml5HtmlAttributes* attributes, nsHtml5ContentCreatorFunction creator)
+{
+ return createAndInsertFosterParentedElement(ns, name, attributes, nullptr, creator);
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::createAndInsertFosterParentedElement(int32_t ns, nsIAtom* name, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form, nsHtml5ContentCreatorFunction creator)
+{
+ int32_t tablePos = findLastOrRoot(nsHtml5TreeBuilder::TABLE);
+ int32_t templatePos = findLastOrRoot(nsHtml5TreeBuilder::TEMPLATE);
+ if (templatePos >= tablePos) {
+ nsIContentHandle* child = createElement(ns, name, attributes, form, stack[templatePos]->node, creator);
+ appendElement(child, stack[templatePos]->node);
+ return child;
+ }
+ nsHtml5StackNode* node = stack[tablePos];
+ return createAndInsertFosterParentedElement(ns, name, attributes, form, node->node, stack[tablePos - 1]->node, creator);
+}
+
+bool
+nsHtml5TreeBuilder::isInStack(nsHtml5StackNode* node)
+{
+ for (int32_t i = currentPtr; i >= 0; i--) {
+ if (stack[i] == node) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+nsHtml5TreeBuilder::popTemplateMode()
+{
+ templateModePtr--;
+}
+
+void
+nsHtml5TreeBuilder::pop()
+{
+ nsHtml5StackNode* node = stack[currentPtr];
+ MOZ_ASSERT(debugOnlyClearLastStackSlot());
+ currentPtr--;
+ elementPopped(node->ns, node->popName, node->node);
+ node->release();
+}
+
+void
+nsHtml5TreeBuilder::silentPop()
+{
+ nsHtml5StackNode* node = stack[currentPtr];
+ MOZ_ASSERT(debugOnlyClearLastStackSlot());
+ currentPtr--;
+ node->release();
+}
+
+void
+nsHtml5TreeBuilder::popOnEof()
+{
+ nsHtml5StackNode* node = stack[currentPtr];
+ MOZ_ASSERT(debugOnlyClearLastStackSlot());
+ currentPtr--;
+ markMalformedIfScript(node->node);
+ elementPopped(node->ns, node->popName, node->node);
+ node->release();
+}
+
+void
+nsHtml5TreeBuilder::appendHtmlElementToDocumentAndPush(nsHtml5HtmlAttributes* attributes)
+{
+ nsIContentHandle* elt = createHtmlElementSetAsRoot(attributes);
+ nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_HTML, elt);
+ push(node);
+}
+
+void
+nsHtml5TreeBuilder::appendHtmlElementToDocumentAndPush()
+{
+ appendHtmlElementToDocumentAndPush(tokenizer->emptyAttributes());
+}
+
+void
+nsHtml5TreeBuilder::appendToCurrentNodeAndPushHeadElement(nsHtml5HtmlAttributes* attributes)
+{
+ nsIContentHandle* currentNode = stack[currentPtr]->node;
+ nsIContentHandle* elt = createElement(kNameSpaceID_XHTML, nsHtml5Atoms::head, attributes, currentNode, htmlCreator(NS_NewHTMLSharedElement));
+ appendElement(elt, currentNode);
+ headPointer = elt;
+ nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_HEAD, elt);
+ push(node);
+}
+
+void
+nsHtml5TreeBuilder::appendToCurrentNodeAndPushBodyElement(nsHtml5HtmlAttributes* attributes)
+{
+ appendToCurrentNodeAndPushElement(nsHtml5ElementName::ELT_BODY, attributes);
+}
+
+void
+nsHtml5TreeBuilder::appendToCurrentNodeAndPushBodyElement()
+{
+ appendToCurrentNodeAndPushBodyElement(tokenizer->emptyAttributes());
+}
+
+void
+nsHtml5TreeBuilder::appendToCurrentNodeAndPushFormElementMayFoster(nsHtml5HtmlAttributes* attributes)
+{
+ nsIContentHandle* elt;
+ nsHtml5StackNode* current = stack[currentPtr];
+ if (current->isFosterParenting()) {
+
+ elt = createAndInsertFosterParentedElement(kNameSpaceID_XHTML, nsHtml5Atoms::form, attributes, htmlCreator(NS_NewHTMLFormElement));
+ } else {
+ elt = createElement(kNameSpaceID_XHTML, nsHtml5Atoms::form, attributes, current->node, htmlCreator(NS_NewHTMLFormElement));
+ appendElement(elt, current->node);
+ }
+ if (!isTemplateContents()) {
+ formPointer = elt;
+ }
+ nsHtml5StackNode* node = new nsHtml5StackNode(nsHtml5ElementName::ELT_FORM, elt);
+ push(node);
+}
+
+void
+nsHtml5TreeBuilder::appendToCurrentNodeAndPushFormattingElementMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ nsHtml5HtmlAttributes* clone = attributes->cloneAttributes(nullptr);
+ nsIContentHandle* elt;
+ nsHtml5StackNode* current = stack[currentPtr];
+ if (current->isFosterParenting()) {
+
+ elt = createAndInsertFosterParentedElement(kNameSpaceID_XHTML, elementName->getName(), attributes, htmlCreator(elementName->getHtmlCreator()));
+ } else {
+ elt = createElement(kNameSpaceID_XHTML, elementName->getName(), attributes, current->node, htmlCreator(elementName->getHtmlCreator()));
+ appendElement(elt, current->node);
+ }
+ nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt, clone);
+ push(node);
+ append(node);
+ node->retain();
+}
+
+void
+nsHtml5TreeBuilder::appendToCurrentNodeAndPushElement(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ nsIContentHandle* currentNode = stack[currentPtr]->node;
+ nsIContentHandle* elt = createElement(kNameSpaceID_XHTML, elementName->getName(), attributes, currentNode, htmlCreator(elementName->getHtmlCreator()));
+ appendElement(elt, currentNode);
+ if (nsHtml5ElementName::ELT_TEMPLATE == elementName) {
+ elt = getDocumentFragmentForTemplate(elt);
+ }
+ nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt);
+ push(node);
+}
+
+void
+nsHtml5TreeBuilder::appendToCurrentNodeAndPushElementMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ nsIAtom* popName = elementName->getName();
+ nsIContentHandle* elt;
+ nsHtml5StackNode* current = stack[currentPtr];
+ if (current->isFosterParenting()) {
+
+ elt = createAndInsertFosterParentedElement(kNameSpaceID_XHTML, popName, attributes, htmlCreator(elementName->getHtmlCreator()));
+ } else {
+ elt = createElement(kNameSpaceID_XHTML, popName, attributes, current->node, htmlCreator(elementName->getHtmlCreator()));
+ appendElement(elt, current->node);
+ }
+ nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt, popName);
+ push(node);
+}
+
+void
+nsHtml5TreeBuilder::appendToCurrentNodeAndPushElementMayFosterMathML(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ nsIAtom* popName = elementName->getName();
+ bool markAsHtmlIntegrationPoint = false;
+ if (nsHtml5ElementName::ELT_ANNOTATION_XML == elementName && annotationXmlEncodingPermitsHtml(attributes)) {
+ markAsHtmlIntegrationPoint = true;
+ }
+ nsIContentHandle* elt;
+ nsHtml5StackNode* current = stack[currentPtr];
+ if (current->isFosterParenting()) {
+
+ elt = createAndInsertFosterParentedElement(kNameSpaceID_MathML, popName, attributes, htmlCreator(nullptr));
+ } else {
+ elt = createElement(kNameSpaceID_MathML, popName, attributes, current->node, htmlCreator(nullptr));
+ appendElement(elt, current->node);
+ }
+ nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt, popName, markAsHtmlIntegrationPoint);
+ push(node);
+}
+
+bool
+nsHtml5TreeBuilder::annotationXmlEncodingPermitsHtml(nsHtml5HtmlAttributes* attributes)
+{
+ nsHtml5String encoding = attributes->getValue(nsHtml5AttributeName::ATTR_ENCODING);
+ if (!encoding) {
+ return false;
+ }
+ return nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("application/xhtml+xml", encoding) || nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString("text/html", encoding);
+}
+
+void
+nsHtml5TreeBuilder::appendToCurrentNodeAndPushElementMayFosterSVG(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ nsIAtom* popName = elementName->getCamelCaseName();
+ nsIContentHandle* elt;
+ nsHtml5StackNode* current = stack[currentPtr];
+ if (current->isFosterParenting()) {
+
+ elt = createAndInsertFosterParentedElement(kNameSpaceID_SVG, popName, attributes, svgCreator(elementName->getSvgCreator()));
+ } else {
+ elt = createElement(kNameSpaceID_SVG, popName, attributes, current->node, svgCreator(elementName->getSvgCreator()));
+ appendElement(elt, current->node);
+ }
+ nsHtml5StackNode* node = new nsHtml5StackNode(elementName, popName, elt);
+ push(node);
+}
+
+void
+nsHtml5TreeBuilder::appendToCurrentNodeAndPushElementMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form)
+{
+ nsIContentHandle* elt;
+ nsIContentHandle* formOwner = !form || fragment || isTemplateContents() ? nullptr : form;
+ nsHtml5StackNode* current = stack[currentPtr];
+ if (current->isFosterParenting()) {
+
+ elt = createAndInsertFosterParentedElement(kNameSpaceID_XHTML, elementName->getName(), attributes, formOwner, htmlCreator(elementName->getHtmlCreator()));
+ } else {
+ elt = createElement(kNameSpaceID_XHTML, elementName->getName(), attributes, formOwner, current->node, htmlCreator(elementName->getHtmlCreator()));
+ appendElement(elt, current->node);
+ }
+ nsHtml5StackNode* node = new nsHtml5StackNode(elementName, elt);
+ push(node);
+}
+
+void
+nsHtml5TreeBuilder::appendVoidElementToCurrentMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form)
+{
+ nsIAtom* name = elementName->getName();
+ nsIContentHandle* elt;
+ nsIContentHandle* formOwner = !form || fragment || isTemplateContents() ? nullptr : form;
+ nsHtml5StackNode* current = stack[currentPtr];
+ if (current->isFosterParenting()) {
+
+ elt = createAndInsertFosterParentedElement(kNameSpaceID_XHTML, name, attributes, formOwner, htmlCreator(elementName->getHtmlCreator()));
+ } else {
+ elt = createElement(kNameSpaceID_XHTML, name, attributes, formOwner, current->node, htmlCreator(elementName->getHtmlCreator()));
+ appendElement(elt, current->node);
+ }
+ elementPushed(kNameSpaceID_XHTML, name, elt);
+ elementPopped(kNameSpaceID_XHTML, name, elt);
+}
+
+void
+nsHtml5TreeBuilder::appendVoidElementToCurrentMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ nsIAtom* popName = elementName->getName();
+ nsIContentHandle* elt;
+ nsHtml5StackNode* current = stack[currentPtr];
+ if (current->isFosterParenting()) {
+
+ elt = createAndInsertFosterParentedElement(kNameSpaceID_XHTML, popName, attributes, htmlCreator(elementName->getHtmlCreator()));
+ } else {
+ elt = createElement(kNameSpaceID_XHTML, popName, attributes, current->node, htmlCreator(elementName->getHtmlCreator()));
+ appendElement(elt, current->node);
+ }
+ elementPushed(kNameSpaceID_XHTML, popName, elt);
+ elementPopped(kNameSpaceID_XHTML, popName, elt);
+}
+
+void
+nsHtml5TreeBuilder::appendVoidElementToCurrentMayFosterSVG(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ nsIAtom* popName = elementName->getCamelCaseName();
+ nsIContentHandle* elt;
+ nsHtml5StackNode* current = stack[currentPtr];
+ if (current->isFosterParenting()) {
+
+ elt = createAndInsertFosterParentedElement(kNameSpaceID_SVG, popName, attributes, svgCreator(elementName->getSvgCreator()));
+ } else {
+ elt = createElement(kNameSpaceID_SVG, popName, attributes, current->node, svgCreator(elementName->getSvgCreator()));
+ appendElement(elt, current->node);
+ }
+ elementPushed(kNameSpaceID_SVG, popName, elt);
+ elementPopped(kNameSpaceID_SVG, popName, elt);
+}
+
+void
+nsHtml5TreeBuilder::appendVoidElementToCurrentMayFosterMathML(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes)
+{
+ nsIAtom* popName = elementName->getName();
+ nsIContentHandle* elt;
+ nsHtml5StackNode* current = stack[currentPtr];
+ if (current->isFosterParenting()) {
+
+ elt = createAndInsertFosterParentedElement(kNameSpaceID_MathML, popName, attributes, htmlCreator(nullptr));
+ } else {
+ elt = createElement(kNameSpaceID_MathML, popName, attributes, current->node, htmlCreator(nullptr));
+ appendElement(elt, current->node);
+ }
+ elementPushed(kNameSpaceID_MathML, popName, elt);
+ elementPopped(kNameSpaceID_MathML, popName, elt);
+}
+
+void
+nsHtml5TreeBuilder::appendVoidInputToCurrent(nsHtml5HtmlAttributes* attributes, nsIContentHandle* form)
+{
+ nsIContentHandle* currentNode = stack[currentPtr]->node;
+ nsIContentHandle* elt = createElement(kNameSpaceID_XHTML, nsHtml5Atoms::input, attributes, !form || fragment || isTemplateContents() ? nullptr : form, currentNode, htmlCreator(NS_NewHTMLInputElement));
+ appendElement(elt, currentNode);
+ elementPushed(kNameSpaceID_XHTML, nsHtml5Atoms::input, elt);
+ elementPopped(kNameSpaceID_XHTML, nsHtml5Atoms::input, elt);
+}
+
+void
+nsHtml5TreeBuilder::appendVoidFormToCurrent(nsHtml5HtmlAttributes* attributes)
+{
+ nsIContentHandle* currentNode = stack[currentPtr]->node;
+ nsIContentHandle* elt = createElement(kNameSpaceID_XHTML, nsHtml5Atoms::form, attributes, currentNode, htmlCreator(NS_NewHTMLFormElement));
+ formPointer = elt;
+ appendElement(elt, currentNode);
+ elementPushed(kNameSpaceID_XHTML, nsHtml5Atoms::form, elt);
+ elementPopped(kNameSpaceID_XHTML, nsHtml5Atoms::form, elt);
+}
+
+void
+nsHtml5TreeBuilder::requestSuspension()
+{
+ tokenizer->requestSuspension();
+}
+
+;bool
+nsHtml5TreeBuilder::isInForeign()
+{
+ return currentPtr >= 0 && stack[currentPtr]->ns != kNameSpaceID_XHTML;
+}
+
+bool
+nsHtml5TreeBuilder::isInForeignButNotHtmlOrMathTextIntegrationPoint()
+{
+ if (currentPtr < 0) {
+ return false;
+ }
+ return !isSpecialParentInForeign(stack[currentPtr]);
+}
+
+void
+nsHtml5TreeBuilder::setFragmentContext(nsIAtom* context, int32_t ns, nsIContentHandle* node, bool quirks)
+{
+ this->contextName = context;
+ this->contextNamespace = ns;
+ this->contextNode = node;
+ this->fragment = (!!contextName);
+ this->quirks = quirks;
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::currentNode()
+{
+ return stack[currentPtr]->node;
+}
+
+bool
+nsHtml5TreeBuilder::isScriptingEnabled()
+{
+ return scriptingEnabled;
+}
+
+void
+nsHtml5TreeBuilder::setScriptingEnabled(bool scriptingEnabled)
+{
+ this->scriptingEnabled = scriptingEnabled;
+}
+
+void
+nsHtml5TreeBuilder::setIsSrcdocDocument(bool isSrcdocDocument)
+{
+ this->isSrcdocDocument = isSrcdocDocument;
+}
+
+void
+nsHtml5TreeBuilder::flushCharacters()
+{
+ if (charBufferLen > 0) {
+ if ((mode == IN_TABLE || mode == IN_TABLE_BODY || mode == IN_ROW) && charBufferContainsNonWhitespace()) {
+ errNonSpaceInTable();
+ reconstructTheActiveFormattingElements();
+ if (!stack[currentPtr]->isFosterParenting()) {
+ appendCharacters(currentNode(), charBuffer, 0, charBufferLen);
+ charBufferLen = 0;
+ return;
+ }
+ int32_t tablePos = findLastOrRoot(nsHtml5TreeBuilder::TABLE);
+ int32_t templatePos = findLastOrRoot(nsHtml5TreeBuilder::TEMPLATE);
+ if (templatePos >= tablePos) {
+ appendCharacters(stack[templatePos]->node, charBuffer, 0, charBufferLen);
+ charBufferLen = 0;
+ return;
+ }
+ nsHtml5StackNode* tableElt = stack[tablePos];
+ insertFosterParentedCharacters(charBuffer, 0, charBufferLen, tableElt->node, stack[tablePos - 1]->node);
+ charBufferLen = 0;
+ return;
+ }
+ appendCharacters(currentNode(), charBuffer, 0, charBufferLen);
+ charBufferLen = 0;
+ }
+}
+
+bool
+nsHtml5TreeBuilder::charBufferContainsNonWhitespace()
+{
+ for (int32_t i = 0; i < charBufferLen; i++) {
+ switch(charBuffer[i]) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\f': {
+ continue;
+ }
+ default: {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+nsAHtml5TreeBuilderState*
+nsHtml5TreeBuilder::newSnapshot()
+{
+ jArray<nsHtml5StackNode*,int32_t> listCopy = jArray<nsHtml5StackNode*,int32_t>::newJArray(listPtr + 1);
+ for (int32_t i = 0; i < listCopy.length; i++) {
+ nsHtml5StackNode* node = listOfActiveFormattingElements[i];
+ if (node) {
+ nsHtml5StackNode* newNode = new nsHtml5StackNode(node->getFlags(), node->ns, node->name, node->node, node->popName, node->attributes->cloneAttributes(nullptr), node->getHtmlCreator());
+ listCopy[i] = newNode;
+ } else {
+ listCopy[i] = nullptr;
+ }
+ }
+ jArray<nsHtml5StackNode*,int32_t> stackCopy = jArray<nsHtml5StackNode*,int32_t>::newJArray(currentPtr + 1);
+ for (int32_t i = 0; i < stackCopy.length; i++) {
+ nsHtml5StackNode* node = stack[i];
+ int32_t listIndex = findInListOfActiveFormattingElements(node);
+ if (listIndex == -1) {
+ nsHtml5StackNode* newNode = new nsHtml5StackNode(node->getFlags(), node->ns, node->name, node->node, node->popName, nullptr, node->getHtmlCreator());
+ stackCopy[i] = newNode;
+ } else {
+ stackCopy[i] = listCopy[listIndex];
+ stackCopy[i]->retain();
+ }
+ }
+ jArray<int32_t,int32_t> templateModeStackCopy = jArray<int32_t,int32_t>::newJArray(templateModePtr + 1);
+ nsHtml5ArrayCopy::arraycopy(templateModeStack, templateModeStackCopy, templateModeStackCopy.length);
+ return new nsHtml5StateSnapshot(stackCopy, listCopy, templateModeStackCopy, formPointer, headPointer, deepTreeSurrogateParent, mode, originalMode, framesetOk, needToDropLF, quirks);
+}
+
+bool
+nsHtml5TreeBuilder::snapshotMatches(nsAHtml5TreeBuilderState* snapshot)
+{
+ jArray<nsHtml5StackNode*,int32_t> stackCopy = snapshot->getStack();
+ int32_t stackLen = snapshot->getStackLength();
+ jArray<nsHtml5StackNode*,int32_t> listCopy = snapshot->getListOfActiveFormattingElements();
+ int32_t listLen = snapshot->getListOfActiveFormattingElementsLength();
+ jArray<int32_t,int32_t> templateModeStackCopy = snapshot->getTemplateModeStack();
+ int32_t templateModeStackLen = snapshot->getTemplateModeStackLength();
+ if (stackLen != currentPtr + 1 || listLen != listPtr + 1 || templateModeStackLen != templateModePtr + 1 || formPointer != snapshot->getFormPointer() || headPointer != snapshot->getHeadPointer() || deepTreeSurrogateParent != snapshot->getDeepTreeSurrogateParent() || mode != snapshot->getMode() || originalMode != snapshot->getOriginalMode() || framesetOk != snapshot->isFramesetOk() || needToDropLF != snapshot->isNeedToDropLF() || quirks != snapshot->isQuirks()) {
+ return false;
+ }
+ for (int32_t i = listLen - 1; i >= 0; i--) {
+ if (!listCopy[i] && !listOfActiveFormattingElements[i]) {
+ continue;
+ } else if (!listCopy[i] || !listOfActiveFormattingElements[i]) {
+ return false;
+ }
+ if (listCopy[i]->node != listOfActiveFormattingElements[i]->node) {
+ return false;
+ }
+ }
+ for (int32_t i = stackLen - 1; i >= 0; i--) {
+ if (stackCopy[i]->node != stack[i]->node) {
+ return false;
+ }
+ }
+ for (int32_t i = templateModeStackLen - 1; i >= 0; i--) {
+ if (templateModeStackCopy[i] != templateModeStack[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void
+nsHtml5TreeBuilder::loadState(nsAHtml5TreeBuilderState* snapshot, nsHtml5AtomTable* interner)
+{
+ jArray<nsHtml5StackNode*,int32_t> stackCopy = snapshot->getStack();
+ int32_t stackLen = snapshot->getStackLength();
+ jArray<nsHtml5StackNode*,int32_t> listCopy = snapshot->getListOfActiveFormattingElements();
+ int32_t listLen = snapshot->getListOfActiveFormattingElementsLength();
+ jArray<int32_t,int32_t> templateModeStackCopy = snapshot->getTemplateModeStack();
+ int32_t templateModeStackLen = snapshot->getTemplateModeStackLength();
+ for (int32_t i = 0; i <= listPtr; i++) {
+ if (listOfActiveFormattingElements[i]) {
+ listOfActiveFormattingElements[i]->release();
+ }
+ }
+ if (listOfActiveFormattingElements.length < listLen) {
+ listOfActiveFormattingElements = jArray<nsHtml5StackNode*,int32_t>::newJArray(listLen);
+ }
+ listPtr = listLen - 1;
+ for (int32_t i = 0; i <= currentPtr; i++) {
+ stack[i]->release();
+ }
+ if (stack.length < stackLen) {
+ stack = jArray<nsHtml5StackNode*,int32_t>::newJArray(stackLen);
+ }
+ currentPtr = stackLen - 1;
+ if (templateModeStack.length < templateModeStackLen) {
+ templateModeStack = jArray<int32_t,int32_t>::newJArray(templateModeStackLen);
+ }
+ templateModePtr = templateModeStackLen - 1;
+ for (int32_t i = 0; i < listLen; i++) {
+ nsHtml5StackNode* node = listCopy[i];
+ if (node) {
+ nsHtml5StackNode* newNode = new nsHtml5StackNode(node->getFlags(), node->ns, nsHtml5Portability::newLocalFromLocal(node->name, interner), node->node, nsHtml5Portability::newLocalFromLocal(node->popName, interner), node->attributes->cloneAttributes(nullptr), node->getHtmlCreator());
+ listOfActiveFormattingElements[i] = newNode;
+ } else {
+ listOfActiveFormattingElements[i] = nullptr;
+ }
+ }
+ for (int32_t i = 0; i < stackLen; i++) {
+ nsHtml5StackNode* node = stackCopy[i];
+ int32_t listIndex = findInArray(node, listCopy);
+ if (listIndex == -1) {
+ nsHtml5StackNode* newNode = new nsHtml5StackNode(node->getFlags(), node->ns, nsHtml5Portability::newLocalFromLocal(node->name, interner), node->node, nsHtml5Portability::newLocalFromLocal(node->popName, interner), nullptr, node->getHtmlCreator());
+ stack[i] = newNode;
+ } else {
+ stack[i] = listOfActiveFormattingElements[listIndex];
+ stack[i]->retain();
+ }
+ }
+ nsHtml5ArrayCopy::arraycopy(templateModeStackCopy, templateModeStack, templateModeStackLen);
+ formPointer = snapshot->getFormPointer();
+ headPointer = snapshot->getHeadPointer();
+ deepTreeSurrogateParent = snapshot->getDeepTreeSurrogateParent();
+ mode = snapshot->getMode();
+ originalMode = snapshot->getOriginalMode();
+ framesetOk = snapshot->isFramesetOk();
+ needToDropLF = snapshot->isNeedToDropLF();
+ quirks = snapshot->isQuirks();
+}
+
+int32_t
+nsHtml5TreeBuilder::findInArray(nsHtml5StackNode* node, jArray<nsHtml5StackNode*,int32_t> arr)
+{
+ for (int32_t i = listPtr; i >= 0; i--) {
+ if (node == arr[i]) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::getFormPointer()
+{
+ return formPointer;
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::getHeadPointer()
+{
+ return headPointer;
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::getDeepTreeSurrogateParent()
+{
+ return deepTreeSurrogateParent;
+}
+
+jArray<nsHtml5StackNode*,int32_t>
+nsHtml5TreeBuilder::getListOfActiveFormattingElements()
+{
+ return listOfActiveFormattingElements;
+}
+
+jArray<nsHtml5StackNode*,int32_t>
+nsHtml5TreeBuilder::getStack()
+{
+ return stack;
+}
+
+jArray<int32_t,int32_t>
+nsHtml5TreeBuilder::getTemplateModeStack()
+{
+ return templateModeStack;
+}
+
+int32_t
+nsHtml5TreeBuilder::getMode()
+{
+ return mode;
+}
+
+int32_t
+nsHtml5TreeBuilder::getOriginalMode()
+{
+ return originalMode;
+}
+
+bool
+nsHtml5TreeBuilder::isFramesetOk()
+{
+ return framesetOk;
+}
+
+bool
+nsHtml5TreeBuilder::isNeedToDropLF()
+{
+ return needToDropLF;
+}
+
+bool
+nsHtml5TreeBuilder::isQuirks()
+{
+ return quirks;
+}
+
+int32_t
+nsHtml5TreeBuilder::getListOfActiveFormattingElementsLength()
+{
+ return listPtr + 1;
+}
+
+int32_t
+nsHtml5TreeBuilder::getStackLength()
+{
+ return currentPtr + 1;
+}
+
+int32_t
+nsHtml5TreeBuilder::getTemplateModeStackLength()
+{
+ return templateModePtr + 1;
+}
+
+void
+nsHtml5TreeBuilder::initializeStatics()
+{
+}
+
+void
+nsHtml5TreeBuilder::releaseStatics()
+{
+}
+
+
+#include "nsHtml5TreeBuilderCppSupplement.h"
+
diff --git a/components/htmlfive/nsHtml5TreeBuilder.h b/components/htmlfive/nsHtml5TreeBuilder.h
new file mode 100644
index 000000000..4f484a104
--- /dev/null
+++ b/components/htmlfive/nsHtml5TreeBuilder.h
@@ -0,0 +1,503 @@
+/*
+ * Copyright (c) 2007 Henri Sivonen
+ * Copyright (c) 2007-2015 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ * Portions of comments Copyright 2004-2008 Apple Computer, Inc., Mozilla
+ * Foundation, and Opera Software ASA.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit TreeBuilder.java instead and regenerate.
+ */
+
+#ifndef nsHtml5TreeBuilder_h
+#define nsHtml5TreeBuilder_h
+
+#include "nsContentUtils.h"
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsITimer.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5DocumentMode.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsHtml5Parser.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5TreeOperation.h"
+#include "nsHtml5StateSnapshot.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "nsHtml5StreamParser.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Highlighter.h"
+#include "nsHtml5PlainTextUtils.h"
+#include "nsHtml5ViewSourceUtils.h"
+#include "mozilla/Likely.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5OplessBuilder.h"
+
+class nsHtml5StreamParser;
+
+class nsHtml5AttributeName;
+class nsHtml5ElementName;
+class nsHtml5Tokenizer;
+class nsHtml5MetaScanner;
+class nsHtml5UTF16Buffer;
+class nsHtml5StateSnapshot;
+class nsHtml5Portability;
+
+
+class nsHtml5TreeBuilder : public nsAHtml5TreeBuilderState
+{
+ private:
+ static char16_t REPLACEMENT_CHARACTER[];
+ public:
+ static const int32_t OTHER = 0;
+
+ static const int32_t A = 1;
+
+ static const int32_t BASE = 2;
+
+ static const int32_t BODY = 3;
+
+ static const int32_t BR = 4;
+
+ static const int32_t BUTTON = 5;
+
+ static const int32_t CAPTION = 6;
+
+ static const int32_t COL = 7;
+
+ static const int32_t COLGROUP = 8;
+
+ static const int32_t FORM = 9;
+
+ static const int32_t FRAME = 10;
+
+ static const int32_t FRAMESET = 11;
+
+ static const int32_t IMAGE = 12;
+
+ static const int32_t INPUT = 13;
+
+ static const int32_t ISINDEX = 14;
+
+ static const int32_t LI = 15;
+
+ static const int32_t LINK_OR_BASEFONT_OR_BGSOUND = 16;
+
+ static const int32_t MATH = 17;
+
+ static const int32_t META = 18;
+
+ static const int32_t SVG = 19;
+
+ static const int32_t HEAD = 20;
+
+ static const int32_t HR = 22;
+
+ static const int32_t HTML = 23;
+
+ static const int32_t NOBR = 24;
+
+ static const int32_t NOFRAMES = 25;
+
+ static const int32_t NOSCRIPT = 26;
+
+ static const int32_t OPTGROUP = 27;
+
+ static const int32_t OPTION = 28;
+
+ static const int32_t P = 29;
+
+ static const int32_t PLAINTEXT = 30;
+
+ static const int32_t SCRIPT = 31;
+
+ static const int32_t SELECT = 32;
+
+ static const int32_t STYLE = 33;
+
+ static const int32_t TABLE = 34;
+
+ static const int32_t TEXTAREA = 35;
+
+ static const int32_t TITLE = 36;
+
+ static const int32_t TR = 37;
+
+ static const int32_t XMP = 38;
+
+ static const int32_t TBODY_OR_THEAD_OR_TFOOT = 39;
+
+ static const int32_t TD_OR_TH = 40;
+
+ static const int32_t DD_OR_DT = 41;
+
+ static const int32_t H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 = 42;
+
+ static const int32_t MARQUEE_OR_APPLET = 43;
+
+ static const int32_t PRE_OR_LISTING = 44;
+
+ static const int32_t B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U = 45;
+
+ static const int32_t UL_OR_OL_OR_DL = 46;
+
+ static const int32_t IFRAME = 47;
+
+ static const int32_t EMBED = 48;
+
+ static const int32_t AREA_OR_WBR = 49;
+
+ static const int32_t DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU = 50;
+
+ static const int32_t ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIALOG_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY = 51;
+
+ static const int32_t RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR = 52;
+
+ static const int32_t RB_OR_RTC = 53;
+
+ static const int32_t PARAM_OR_SOURCE_OR_TRACK = 55;
+
+ static const int32_t MGLYPH_OR_MALIGNMARK = 56;
+
+ static const int32_t MI_MO_MN_MS_MTEXT = 57;
+
+ static const int32_t ANNOTATION_XML = 58;
+
+ static const int32_t FOREIGNOBJECT_OR_DESC = 59;
+
+ static const int32_t NOEMBED = 60;
+
+ static const int32_t FIELDSET = 61;
+
+ static const int32_t OUTPUT = 62;
+
+ static const int32_t OBJECT = 63;
+
+ static const int32_t FONT = 64;
+
+ static const int32_t KEYGEN = 65;
+
+ static const int32_t MENUITEM = 66;
+
+ static const int32_t TEMPLATE = 67;
+
+ static const int32_t IMG = 68;
+
+ static const int32_t RT_OR_RP = 69;
+
+ private:
+ static const int32_t IN_ROW = 0;
+
+ static const int32_t IN_TABLE_BODY = 1;
+
+ static const int32_t IN_TABLE = 2;
+
+ static const int32_t IN_CAPTION = 3;
+
+ static const int32_t IN_CELL = 4;
+
+ static const int32_t FRAMESET_OK = 5;
+
+ static const int32_t IN_BODY = 6;
+
+ static const int32_t IN_HEAD = 7;
+
+ static const int32_t IN_HEAD_NOSCRIPT = 8;
+
+ static const int32_t IN_COLUMN_GROUP = 9;
+
+ static const int32_t IN_SELECT_IN_TABLE = 10;
+
+ static const int32_t IN_SELECT = 11;
+
+ static const int32_t AFTER_BODY = 12;
+
+ static const int32_t IN_FRAMESET = 13;
+
+ static const int32_t AFTER_FRAMESET = 14;
+
+ static const int32_t INITIAL = 15;
+
+ static const int32_t BEFORE_HTML = 16;
+
+ static const int32_t BEFORE_HEAD = 17;
+
+ static const int32_t AFTER_HEAD = 18;
+
+ static const int32_t AFTER_AFTER_BODY = 19;
+
+ static const int32_t AFTER_AFTER_FRAMESET = 20;
+
+ static const int32_t TEXT = 21;
+
+ static const int32_t IN_TEMPLATE = 22;
+
+ static const int32_t CHARSET_INITIAL = 0;
+
+ static const int32_t CHARSET_C = 1;
+
+ static const int32_t CHARSET_H = 2;
+
+ static const int32_t CHARSET_A = 3;
+
+ static const int32_t CHARSET_R = 4;
+
+ static const int32_t CHARSET_S = 5;
+
+ static const int32_t CHARSET_E = 6;
+
+ static const int32_t CHARSET_T = 7;
+
+ static const int32_t CHARSET_EQUALS = 8;
+
+ static const int32_t CHARSET_SINGLE_QUOTED = 9;
+
+ static const int32_t CHARSET_DOUBLE_QUOTED = 10;
+
+ static const int32_t CHARSET_UNQUOTED = 11;
+
+ static staticJArray<const char*,int32_t> QUIRKY_PUBLIC_IDS;
+ static const int32_t NOT_FOUND_ON_STACK = INT32_MAX;
+
+ int32_t mode;
+ int32_t originalMode;
+ bool framesetOk;
+ protected:
+ nsHtml5Tokenizer* tokenizer;
+ private:
+ bool scriptingEnabled;
+ bool needToDropLF;
+ bool fragment;
+ nsIAtom* contextName;
+ int32_t contextNamespace;
+ nsIContentHandle* contextNode;
+ autoJArray<int32_t,int32_t> templateModeStack;
+ int32_t templateModePtr;
+ autoJArray<nsHtml5StackNode*,int32_t> stack;
+ int32_t currentPtr;
+ autoJArray<nsHtml5StackNode*,int32_t> listOfActiveFormattingElements;
+ int32_t listPtr;
+ nsIContentHandle* formPointer;
+ nsIContentHandle* headPointer;
+ nsIContentHandle* deepTreeSurrogateParent;
+ protected:
+ autoJArray<char16_t,int32_t> charBuffer;
+ int32_t charBufferLen;
+ private:
+ bool quirks;
+ bool isSrcdocDocument;
+ inline nsHtml5ContentCreatorFunction htmlCreator(mozilla::dom::HTMLContentCreatorFunction htmlCreator)
+ {
+ nsHtml5ContentCreatorFunction creator;
+ creator.html = htmlCreator;
+ return creator;
+ }
+
+ inline nsHtml5ContentCreatorFunction svgCreator(mozilla::dom::SVGContentCreatorFunction svgCreator)
+ {
+ nsHtml5ContentCreatorFunction creator;
+ creator.svg = svgCreator;
+ return creator;
+ }
+
+ public:
+ void startTokenization(nsHtml5Tokenizer* self);
+ void doctype(nsIAtom* name, nsHtml5String publicIdentifier, nsHtml5String systemIdentifier, bool forceQuirks);
+ void comment(char16_t* buf, int32_t start, int32_t length);
+ void characters(const char16_t* buf, int32_t start, int32_t length);
+ void zeroOriginatingReplacementCharacter();
+ void eof();
+ void endTokenization();
+ void startTag(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes, bool selfClosing);
+ private:
+ void startTagTitleInHead(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ void startTagGenericRawText(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ void startTagScriptInHead(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ void startTagTemplateInHead(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ bool isTemplateContents();
+ bool isTemplateModeStackEmpty();
+ bool isSpecialParentInForeign(nsHtml5StackNode* stackNode);
+ public:
+ static nsHtml5String extractCharsetFromContent(nsHtml5String attributeValue, nsHtml5TreeBuilder* tb);
+ private:
+ void checkMetaCharset(nsHtml5HtmlAttributes* attributes);
+ public:
+ void endTag(nsHtml5ElementName* elementName);
+ private:
+ void endTagTemplateInHead();
+ int32_t findLastInTableScopeOrRootTemplateTbodyTheadTfoot();
+ int32_t findLast(nsIAtom* name);
+ int32_t findLastInTableScope(nsIAtom* name);
+ int32_t findLastInButtonScope(nsIAtom* name);
+ int32_t findLastInScope(nsIAtom* name);
+ int32_t findLastInListScope(nsIAtom* name);
+ int32_t findLastInScopeHn();
+ void generateImpliedEndTagsExceptFor(nsIAtom* name);
+ void generateImpliedEndTags();
+ bool isSecondOnStackBody();
+ void documentModeInternal(nsHtml5DocumentMode m, nsHtml5String publicIdentifier, nsHtml5String systemIdentifier, bool html4SpecificAdditionalErrorChecks);
+ bool isAlmostStandards(nsHtml5String publicIdentifier, nsHtml5String systemIdentifier);
+ bool isQuirky(nsIAtom* name, nsHtml5String publicIdentifier, nsHtml5String systemIdentifier, bool forceQuirks);
+ void closeTheCell(int32_t eltPos);
+ int32_t findLastInTableScopeTdTh();
+ void clearStackBackTo(int32_t eltPos);
+ void resetTheInsertionMode();
+ void implicitlyCloseP();
+ bool debugOnlyClearLastStackSlot();
+ bool debugOnlyClearLastListSlot();
+ void pushTemplateMode(int32_t mode);
+ void push(nsHtml5StackNode* node);
+ void silentPush(nsHtml5StackNode* node);
+ void append(nsHtml5StackNode* node);
+ inline void insertMarker()
+ {
+ append(nullptr);
+ }
+
+ void clearTheListOfActiveFormattingElementsUpToTheLastMarker();
+ inline bool isCurrent(nsIAtom* name)
+ {
+ return stack[currentPtr]->ns == kNameSpaceID_XHTML && name == stack[currentPtr]->name;
+ }
+
+ void removeFromStack(int32_t pos);
+ void removeFromStack(nsHtml5StackNode* node);
+ void removeFromListOfActiveFormattingElements(int32_t pos);
+ bool adoptionAgencyEndTag(nsIAtom* name);
+ void insertIntoStack(nsHtml5StackNode* node, int32_t position);
+ void insertIntoListOfActiveFormattingElements(nsHtml5StackNode* formattingClone, int32_t bookmark);
+ int32_t findInListOfActiveFormattingElements(nsHtml5StackNode* node);
+ int32_t findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker(nsIAtom* name);
+ void maybeForgetEarlierDuplicateFormattingElement(nsIAtom* name, nsHtml5HtmlAttributes* attributes);
+ int32_t findLastOrRoot(nsIAtom* name);
+ int32_t findLastOrRoot(int32_t group);
+ bool addAttributesToBody(nsHtml5HtmlAttributes* attributes);
+ void addAttributesToHtml(nsHtml5HtmlAttributes* attributes);
+ void pushHeadPointerOntoStack();
+ void reconstructTheActiveFormattingElements();
+ void insertIntoFosterParent(nsIContentHandle* child);
+ nsIContentHandle* createAndInsertFosterParentedElement(int32_t ns, nsIAtom* name, nsHtml5HtmlAttributes* attributes, nsHtml5ContentCreatorFunction creator);
+ nsIContentHandle* createAndInsertFosterParentedElement(int32_t ns, nsIAtom* name, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form, nsHtml5ContentCreatorFunction creator);
+ bool isInStack(nsHtml5StackNode* node);
+ void popTemplateMode();
+ void pop();
+ void silentPop();
+ void popOnEof();
+ void appendHtmlElementToDocumentAndPush(nsHtml5HtmlAttributes* attributes);
+ void appendHtmlElementToDocumentAndPush();
+ void appendToCurrentNodeAndPushHeadElement(nsHtml5HtmlAttributes* attributes);
+ void appendToCurrentNodeAndPushBodyElement(nsHtml5HtmlAttributes* attributes);
+ void appendToCurrentNodeAndPushBodyElement();
+ void appendToCurrentNodeAndPushFormElementMayFoster(nsHtml5HtmlAttributes* attributes);
+ void appendToCurrentNodeAndPushFormattingElementMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ void appendToCurrentNodeAndPushElement(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ void appendToCurrentNodeAndPushElementMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ void appendToCurrentNodeAndPushElementMayFosterMathML(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ bool annotationXmlEncodingPermitsHtml(nsHtml5HtmlAttributes* attributes);
+ void appendToCurrentNodeAndPushElementMayFosterSVG(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ void appendToCurrentNodeAndPushElementMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form);
+ void appendVoidElementToCurrentMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form);
+ void appendVoidElementToCurrentMayFoster(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ void appendVoidElementToCurrentMayFosterSVG(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ void appendVoidElementToCurrentMayFosterMathML(nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes);
+ void appendVoidInputToCurrent(nsHtml5HtmlAttributes* attributes, nsIContentHandle* form);
+ void appendVoidFormToCurrent(nsHtml5HtmlAttributes* attributes);
+ protected:
+ void accumulateCharacters(const char16_t* buf, int32_t start, int32_t length);
+ void requestSuspension();
+ nsIContentHandle* createElement(int32_t ns, nsIAtom* name, nsHtml5HtmlAttributes* attributes, nsIContentHandle* intendedParent, nsHtml5ContentCreatorFunction creator);
+ nsIContentHandle* createElement(int32_t ns, nsIAtom* name, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form, nsIContentHandle* intendedParent, nsHtml5ContentCreatorFunction creator);
+ nsIContentHandle* createHtmlElementSetAsRoot(nsHtml5HtmlAttributes* attributes);
+ void detachFromParent(nsIContentHandle* element);
+ bool hasChildren(nsIContentHandle* element);
+ void appendElement(nsIContentHandle* child, nsIContentHandle* newParent);
+ void appendChildrenToNewParent(nsIContentHandle* oldParent, nsIContentHandle* newParent);
+ void insertFosterParentedChild(nsIContentHandle* child, nsIContentHandle* table, nsIContentHandle* stackParent);
+ nsIContentHandle* createAndInsertFosterParentedElement(int32_t ns, nsIAtom* name, nsHtml5HtmlAttributes* attributes, nsIContentHandle* form, nsIContentHandle* table, nsIContentHandle* stackParent, nsHtml5ContentCreatorFunction creator);
+ ;void insertFosterParentedCharacters(char16_t* buf, int32_t start, int32_t length, nsIContentHandle* table, nsIContentHandle* stackParent);
+ void appendCharacters(nsIContentHandle* parent, char16_t* buf, int32_t start, int32_t length);
+ void appendIsindexPrompt(nsIContentHandle* parent);
+ void appendComment(nsIContentHandle* parent, char16_t* buf, int32_t start, int32_t length);
+ void appendCommentToDocument(char16_t* buf, int32_t start, int32_t length);
+ void addAttributesToElement(nsIContentHandle* element, nsHtml5HtmlAttributes* attributes);
+ void markMalformedIfScript(nsIContentHandle* elt);
+ void start(bool fragmentMode);
+ void end();
+ void appendDoctypeToDocument(nsIAtom* name, nsHtml5String publicIdentifier, nsHtml5String systemIdentifier);
+ void elementPushed(int32_t ns, nsIAtom* name, nsIContentHandle* node);
+ void elementPopped(int32_t ns, nsIAtom* name, nsIContentHandle* node);
+ public:
+ inline bool cdataSectionAllowed()
+ {
+ return isInForeign();
+ }
+
+ private:
+ bool isInForeign();
+ bool isInForeignButNotHtmlOrMathTextIntegrationPoint();
+ public:
+ void setFragmentContext(nsIAtom* context, int32_t ns, nsIContentHandle* node, bool quirks);
+ protected:
+ nsIContentHandle* currentNode();
+ public:
+ bool isScriptingEnabled();
+ void setScriptingEnabled(bool scriptingEnabled);
+ void setIsSrcdocDocument(bool isSrcdocDocument);
+ void flushCharacters();
+ private:
+ bool charBufferContainsNonWhitespace();
+ public:
+ nsAHtml5TreeBuilderState* newSnapshot();
+ bool snapshotMatches(nsAHtml5TreeBuilderState* snapshot);
+ void loadState(nsAHtml5TreeBuilderState* snapshot, nsHtml5AtomTable* interner);
+ private:
+ int32_t findInArray(nsHtml5StackNode* node, jArray<nsHtml5StackNode*,int32_t> arr);
+ public:
+ nsIContentHandle* getFormPointer();
+ nsIContentHandle* getHeadPointer();
+ nsIContentHandle* getDeepTreeSurrogateParent();
+ jArray<nsHtml5StackNode*,int32_t> getListOfActiveFormattingElements();
+ jArray<nsHtml5StackNode*,int32_t> getStack();
+ jArray<int32_t,int32_t> getTemplateModeStack();
+ int32_t getMode();
+ int32_t getOriginalMode();
+ bool isFramesetOk();
+ bool isNeedToDropLF();
+ bool isQuirks();
+ int32_t getListOfActiveFormattingElementsLength();
+ int32_t getStackLength();
+ int32_t getTemplateModeStackLength();
+ static void initializeStatics();
+ static void releaseStatics();
+
+#include "nsHtml5TreeBuilderHSupplement.h"
+};
+
+#endif
+
diff --git a/components/htmlfive/nsHtml5TreeBuilderCppSupplement.h b/components/htmlfive/nsHtml5TreeBuilderCppSupplement.h
new file mode 100644
index 000000000..c973478e0
--- /dev/null
+++ b/components/htmlfive/nsHtml5TreeBuilderCppSupplement.h
@@ -0,0 +1,1724 @@
+/* -*- 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 "nsError.h"
+#include "nsIPresShell.h"
+#include "nsNodeUtils.h"
+#include "nsIFrame.h"
+#include "mozilla/Likely.h"
+#include "mozilla/UniquePtr.h"
+
+nsHtml5TreeBuilder::nsHtml5TreeBuilder(nsHtml5OplessBuilder* aBuilder)
+ : scriptingEnabled(false)
+ , fragment(false)
+ , contextName(nullptr)
+ , contextNamespace(kNameSpaceID_None)
+ , contextNode(nullptr)
+ , formPointer(nullptr)
+ , headPointer(nullptr)
+ , mBuilder(aBuilder)
+ , mViewSource(nullptr)
+ , mOpSink(nullptr)
+ , mHandles(nullptr)
+ , mHandlesUsed(0)
+ , mSpeculativeLoadStage(nullptr)
+ , mBroken(NS_OK)
+ , mCurrentHtmlScriptIsAsyncOrDefer(false)
+ , mPreventScriptExecution(false)
+#ifdef DEBUG
+ , mActive(false)
+#endif
+{
+ MOZ_COUNT_CTOR(nsHtml5TreeBuilder);
+}
+
+nsHtml5TreeBuilder::nsHtml5TreeBuilder(nsAHtml5TreeOpSink* aOpSink,
+ nsHtml5TreeOpStage* aStage)
+ : scriptingEnabled(false)
+ , fragment(false)
+ , contextName(nullptr)
+ , contextNamespace(kNameSpaceID_None)
+ , contextNode(nullptr)
+ , formPointer(nullptr)
+ , headPointer(nullptr)
+ , mBuilder(nullptr)
+ , mViewSource(nullptr)
+ , mOpSink(aOpSink)
+ , mHandles(new nsIContent*[NS_HTML5_TREE_BUILDER_HANDLE_ARRAY_LENGTH])
+ , mHandlesUsed(0)
+ , mSpeculativeLoadStage(aStage)
+ , mBroken(NS_OK)
+ , mCurrentHtmlScriptIsAsyncOrDefer(false)
+ , mPreventScriptExecution(false)
+#ifdef DEBUG
+ , mActive(false)
+#endif
+{
+ MOZ_COUNT_CTOR(nsHtml5TreeBuilder);
+}
+
+nsHtml5TreeBuilder::~nsHtml5TreeBuilder()
+{
+ MOZ_COUNT_DTOR(nsHtml5TreeBuilder);
+ NS_ASSERTION(!mActive, "nsHtml5TreeBuilder deleted without ever calling end() on it!");
+ mOpQueue.Clear();
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::createElement(int32_t aNamespace,
+ nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ nsIContentHandle* aIntendedParent,
+ nsHtml5ContentCreatorFunction aCreator)
+{
+ NS_PRECONDITION(aAttributes, "Got null attributes.");
+ NS_PRECONDITION(aName, "Got null name.");
+ NS_PRECONDITION(aNamespace == kNameSpaceID_XHTML ||
+ aNamespace == kNameSpaceID_SVG ||
+ aNamespace == kNameSpaceID_MathML,
+ "Bogus namespace.");
+
+ if (mBuilder) {
+ nsCOMPtr<nsIAtom> name = nsHtml5TreeOperation::Reget(aName);
+
+ nsIContent* intendedParent = aIntendedParent ?
+ static_cast<nsIContent*>(aIntendedParent) : nullptr;
+
+ // intendedParent == nullptr is a special case where the
+ // intended parent is the document.
+ nsNodeInfoManager* nodeInfoManager = intendedParent ?
+ intendedParent->OwnerDoc()->NodeInfoManager() :
+ mBuilder->GetNodeInfoManager();
+
+ nsIContent* elem;
+ if (aNamespace == kNameSpaceID_XHTML) {
+ elem = nsHtml5TreeOperation::CreateHTMLElement(
+ name,
+ aAttributes,
+ mozilla::dom::FROM_PARSER_FRAGMENT,
+ nodeInfoManager,
+ mBuilder,
+ aCreator.html);
+ } else if (aNamespace == kNameSpaceID_SVG) {
+ elem = nsHtml5TreeOperation::CreateSVGElement(
+ name,
+ aAttributes,
+ mozilla::dom::FROM_PARSER_FRAGMENT,
+ nodeInfoManager,
+ mBuilder,
+ aCreator.svg);
+ } else {
+ MOZ_ASSERT(aNamespace == kNameSpaceID_MathML);
+ elem = nsHtml5TreeOperation::CreateMathMLElement(
+ name, aAttributes, nodeInfoManager, mBuilder);
+ }
+ if (MOZ_UNLIKELY(aAttributes != tokenizer->GetAttributes() &&
+ aAttributes != nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES)) {
+ delete aAttributes;
+ }
+ return elem;
+ }
+
+ nsIContentHandle* content = AllocateContentHandle();
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(aNamespace,
+ aName,
+ aAttributes,
+ content,
+ aIntendedParent,
+ !!mSpeculativeLoadStage,
+ aCreator);
+ // mSpeculativeLoadStage is non-null only in the off-the-main-thread
+ // tree builder, which handles the network stream
+
+ // Start wall of code for speculative loading and line numbers
+
+ if (mSpeculativeLoadStage) {
+ switch (aNamespace) {
+ case kNameSpaceID_XHTML:
+ if (nsHtml5Atoms::img == aName) {
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_SRC);
+ nsHtml5String srcset =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_SRCSET);
+ nsHtml5String crossOrigin =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+ nsHtml5String referrerPolicy =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_REFERRERPOLICY);
+ nsHtml5String sizes =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_SIZES);
+ mSpeculativeLoadQueue.AppendElement()->InitImage(
+ url, crossOrigin, referrerPolicy, srcset, sizes);
+ } else if (nsHtml5Atoms::source == aName) {
+ nsHtml5String srcset =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_SRCSET);
+ // Sources without srcset cannot be selected. The source could also be
+ // for a media element, but in that context doesn't use srcset. See
+ // comments in nsHtml5SpeculativeLoad.h about <picture> preloading
+ if (srcset) {
+ nsHtml5String sizes =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_SIZES);
+ nsHtml5String type =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
+ nsHtml5String media =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_MEDIA);
+ mSpeculativeLoadQueue.AppendElement()->InitPictureSource(
+ srcset, sizes, type, media);
+ }
+ } else if (nsHtml5Atoms::script == aName) {
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber());
+
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_SRC);
+ if (url) {
+ nsHtml5String charset =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
+ nsHtml5String type =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
+ nsHtml5String crossOrigin =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+ nsHtml5String integrity =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
+ bool async =
+ aAttributes->contains(nsHtml5AttributeName::ATTR_ASYNC);
+ bool defer =
+ aAttributes->contains(nsHtml5AttributeName::ATTR_DEFER);
+ bool noModule =
+ aAttributes->contains(nsHtml5AttributeName::ATTR_NOMODULE);
+ mSpeculativeLoadQueue.AppendElement()->InitScript(
+ url,
+ charset,
+ type,
+ crossOrigin,
+ integrity,
+ mode == nsHtml5TreeBuilder::IN_HEAD,
+ async,
+ defer,
+ noModule);
+ mCurrentHtmlScriptIsAsyncOrDefer = async || defer;
+ }
+ } else if (nsHtml5Atoms::link == aName) {
+ nsHtml5String rel =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_REL);
+ // Not splitting on space here is bogus but the old parser didn't even
+ // do a case-insensitive check.
+ if (rel) {
+ if (rel.LowerCaseEqualsASCII("stylesheet")) {
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
+ if (url) {
+ nsHtml5String charset =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
+ nsHtml5String crossOrigin =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+ nsHtml5String integrity =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
+ mSpeculativeLoadQueue.AppendElement()->InitStyle(
+ url, charset, crossOrigin, integrity);
+ }
+ } else if (rel.LowerCaseEqualsASCII("preconnect")) {
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
+ if (url) {
+ nsHtml5String crossOrigin =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+ mSpeculativeLoadQueue.AppendElement()->InitPreconnect(
+ url, crossOrigin);
+ }
+ }
+ }
+ } else if (nsHtml5Atoms::video == aName) {
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_POSTER);
+ if (url) {
+ mSpeculativeLoadQueue.AppendElement()->InitImage(
+ url, nullptr, nullptr, nullptr, nullptr);
+ }
+ } else if (nsHtml5Atoms::style == aName) {
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpSetStyleLineNumber, content, tokenizer->getLineNumber());
+ } else if (nsHtml5Atoms::html == aName) {
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_MANIFEST);
+ mSpeculativeLoadQueue.AppendElement()->InitManifest(url);
+ } else if (nsHtml5Atoms::base == aName) {
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
+ if (url) {
+ mSpeculativeLoadQueue.AppendElement()->InitBase(url);
+ }
+ } else if (nsHtml5Atoms::meta == aName) {
+ if (nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "content-security-policy",
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_HTTP_EQUIV))) {
+ nsHtml5String csp =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_CONTENT);
+ if (csp) {
+ mSpeculativeLoadQueue.AppendElement()->InitMetaCSP(csp);
+ }
+ }
+ else if (nsHtml5Portability::lowerCaseLiteralEqualsIgnoreAsciiCaseString(
+ "referrer",
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_NAME))) {
+ nsHtml5String referrerPolicy =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_CONTENT);
+ if (referrerPolicy) {
+ mSpeculativeLoadQueue.AppendElement()->InitMetaReferrerPolicy(
+ referrerPolicy);
+ }
+ }
+ }
+ break;
+ case kNameSpaceID_SVG:
+ if (nsHtml5Atoms::image == aName) {
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
+ if (url) {
+ mSpeculativeLoadQueue.AppendElement()->InitImage(
+ url, nullptr, nullptr, nullptr, nullptr);
+ }
+ } else if (nsHtml5Atoms::script == aName) {
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber());
+
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
+ if (url) {
+ nsHtml5String type =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
+ nsHtml5String crossOrigin =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+ nsHtml5String integrity =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
+ mSpeculativeLoadQueue.AppendElement()->InitScript(
+ url,
+ nullptr,
+ type,
+ crossOrigin,
+ integrity,
+ mode == nsHtml5TreeBuilder::IN_HEAD,
+ false /* async */,
+ false /* defer */,
+ false /* noModule */);
+ }
+ } else if (nsHtml5Atoms::style == aName) {
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpSetStyleLineNumber, content, tokenizer->getLineNumber());
+
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
+ if (url) {
+ nsHtml5String crossOrigin =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_CROSSORIGIN);
+ nsHtml5String integrity =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_INTEGRITY);
+ mSpeculativeLoadQueue.AppendElement()->InitStyle(
+ url, nullptr, crossOrigin, integrity);
+ }
+ }
+ break;
+ }
+ } else if (aNamespace != kNameSpaceID_MathML) {
+ // No speculative loader--just line numbers and defer/async check
+ if (nsHtml5Atoms::style == aName) {
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpSetStyleLineNumber, content, tokenizer->getLineNumber());
+ } else if (nsHtml5Atoms::script == aName) {
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpSetScriptLineNumberAndFreeze, content, tokenizer->getLineNumber());
+ if (aNamespace == kNameSpaceID_XHTML) {
+ mCurrentHtmlScriptIsAsyncOrDefer =
+ aAttributes->contains(nsHtml5AttributeName::ATTR_SRC) &&
+ (aAttributes->contains(nsHtml5AttributeName::ATTR_ASYNC) ||
+ aAttributes->contains(nsHtml5AttributeName::ATTR_DEFER));
+ }
+ } else if (aNamespace == kNameSpaceID_XHTML) {
+ if (nsHtml5Atoms::html == aName) {
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_MANIFEST);
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ if (url) {
+ nsString
+ urlString; // Not Auto, because using it to hold nsStringBuffer*
+ url.ToString(urlString);
+ treeOp->Init(eTreeOpProcessOfflineManifest, urlString);
+ } else {
+ treeOp->Init(eTreeOpProcessOfflineManifest, EmptyString());
+ }
+ } else if (nsHtml5Atoms::base == aName && mViewSource) {
+ nsHtml5String url =
+ aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
+ if (url) {
+ mViewSource->AddBase(url);
+ }
+ }
+ }
+ }
+
+ // End wall of code for speculative loading
+
+ return content;
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::createElement(int32_t aNamespace,
+ nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ nsIContentHandle* aFormElement,
+ nsIContentHandle* aIntendedParent,
+ nsHtml5ContentCreatorFunction aCreator)
+{
+ nsIContentHandle* content =
+ createElement(aNamespace, aName, aAttributes, aIntendedParent, aCreator);
+ if (aFormElement) {
+ if (mBuilder) {
+ nsHtml5TreeOperation::SetFormElement(static_cast<nsIContent*>(content),
+ static_cast<nsIContent*>(aFormElement));
+ } else {
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpSetFormElement, content, aFormElement);
+ }
+ }
+ return content;
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::createHtmlElementSetAsRoot(nsHtml5HtmlAttributes* aAttributes)
+{
+ nsHtml5ContentCreatorFunction creator;
+ // <html> uses NS_NewHTMLSharedElement creator
+ creator.html = NS_NewHTMLSharedElement;
+ nsIContentHandle* content = createElement(
+ kNameSpaceID_XHTML, nsHtml5Atoms::html, aAttributes, nullptr, creator);
+ if (mBuilder) {
+ nsresult rv = nsHtml5TreeOperation::AppendToDocument(static_cast<nsIContent*>(content),
+ mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ } else {
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpAppendToDocument, content);
+ }
+ return content;
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::createAndInsertFosterParentedElement(int32_t aNamespace,
+ nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ nsIContentHandle* aFormElement,
+ nsIContentHandle* aTable,
+ nsIContentHandle* aStackParent,
+ nsHtml5ContentCreatorFunction aCreator)
+{
+ NS_PRECONDITION(aTable, "Null table");
+ NS_PRECONDITION(aStackParent, "Null stack parent");
+
+ if (mBuilder) {
+ // Get the foster parent to use as the intended parent when creating
+ // the child element.
+ nsIContent* fosterParent = nsHtml5TreeOperation::GetFosterParent(
+ static_cast<nsIContent*>(aTable),
+ static_cast<nsIContent*>(aStackParent));
+
+ nsIContentHandle* child = createElement(
+ aNamespace, aName, aAttributes, aFormElement, fosterParent, aCreator);
+
+ insertFosterParentedChild(child, aTable, aStackParent);
+
+ return child;
+ }
+
+ // Tree op to get the foster parent that we use as the intended parent
+ // when creating the child element.
+ nsHtml5TreeOperation* fosterParentTreeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(fosterParentTreeOp, "Tree op allocation failed.");
+ nsIContentHandle* fosterParentHandle = AllocateContentHandle();
+ fosterParentTreeOp->Init(eTreeOpGetFosterParent, aTable,
+ aStackParent, fosterParentHandle);
+
+ // Create the element with the correct intended parent.
+ nsIContentHandle* child = createElement(
+ aNamespace, aName, aAttributes, aFormElement, fosterParentHandle, aCreator);
+
+ // Insert the child into the foster parent.
+ insertFosterParentedChild(child, aTable, aStackParent);
+
+ return child;
+}
+
+void
+nsHtml5TreeBuilder::detachFromParent(nsIContentHandle* aElement)
+{
+ NS_PRECONDITION(aElement, "Null element");
+
+ if (mBuilder) {
+ nsHtml5TreeOperation::Detach(static_cast<nsIContent*>(aElement),
+ mBuilder);
+ return;
+ }
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpDetach, aElement);
+}
+
+void
+nsHtml5TreeBuilder::appendElement(nsIContentHandle* aChild, nsIContentHandle* aParent)
+{
+ NS_PRECONDITION(aChild, "Null child");
+ NS_PRECONDITION(aParent, "Null parent");
+ if (deepTreeSurrogateParent) {
+ return;
+ }
+
+ if (mBuilder) {
+ nsresult rv = nsHtml5TreeOperation::Append(static_cast<nsIContent*>(aChild),
+ static_cast<nsIContent*>(aParent),
+ mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ return;
+ }
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpAppend, aChild, aParent);
+}
+
+void
+nsHtml5TreeBuilder::appendChildrenToNewParent(nsIContentHandle* aOldParent, nsIContentHandle* aNewParent)
+{
+ NS_PRECONDITION(aOldParent, "Null old parent");
+ NS_PRECONDITION(aNewParent, "Null new parent");
+
+ if (mBuilder) {
+ nsresult rv = nsHtml5TreeOperation::AppendChildrenToNewParent(
+ static_cast<nsIContent*>(aOldParent),
+ static_cast<nsIContent*>(aNewParent),
+ mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ return;
+ }
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpAppendChildrenToNewParent, aOldParent, aNewParent);
+}
+
+void
+nsHtml5TreeBuilder::insertFosterParentedCharacters(char16_t* aBuffer, int32_t aStart, int32_t aLength, nsIContentHandle* aTable, nsIContentHandle* aStackParent)
+{
+ NS_PRECONDITION(aBuffer, "Null buffer");
+ NS_PRECONDITION(aTable, "Null table");
+ NS_PRECONDITION(aStackParent, "Null stack parent");
+ MOZ_ASSERT(!aStart, "aStart must always be zero.");
+
+ if (mBuilder) {
+ nsresult rv = nsHtml5TreeOperation::FosterParentText(
+ static_cast<nsIContent*>(aStackParent),
+ aBuffer, // XXX aStart always ignored???
+ aLength,
+ static_cast<nsIContent*>(aTable),
+ mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ return;
+ }
+
+ char16_t* bufferCopy = new (mozilla::fallible) char16_t[aLength];
+ if (!bufferCopy) {
+ // Just assigning mBroken instead of generating tree op. The caller
+ // of tokenizeBuffer() will call MarkAsBroken() as appropriate.
+ mBroken = NS_ERROR_OUT_OF_MEMORY;
+ requestSuspension();
+ return;
+ }
+
+ memcpy(bufferCopy, aBuffer, aLength * sizeof(char16_t));
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpFosterParentText, bufferCopy, aLength, aStackParent, aTable);
+}
+
+void
+nsHtml5TreeBuilder::insertFosterParentedChild(nsIContentHandle* aChild, nsIContentHandle* aTable, nsIContentHandle* aStackParent)
+{
+ NS_PRECONDITION(aChild, "Null child");
+ NS_PRECONDITION(aTable, "Null table");
+ NS_PRECONDITION(aStackParent, "Null stack parent");
+
+ if (mBuilder) {
+ nsresult rv = nsHtml5TreeOperation::FosterParent(
+ static_cast<nsIContent*>(aChild),
+ static_cast<nsIContent*>(aStackParent),
+ static_cast<nsIContent*>(aTable),
+ mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ return;
+ }
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpFosterParent, aChild, aStackParent, aTable);
+}
+
+void
+nsHtml5TreeBuilder::appendCharacters(nsIContentHandle* aParent, char16_t* aBuffer, int32_t aStart, int32_t aLength)
+{
+ NS_PRECONDITION(aBuffer, "Null buffer");
+ NS_PRECONDITION(aParent, "Null parent");
+ MOZ_ASSERT(!aStart, "aStart must always be zero.");
+
+ if (mBuilder) {
+ nsresult rv = nsHtml5TreeOperation::AppendText(
+ aBuffer, // XXX aStart always ignored???
+ aLength,
+ static_cast<nsIContent*>(deepTreeSurrogateParent ?
+ deepTreeSurrogateParent : aParent),
+ mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ return;
+ }
+
+ char16_t* bufferCopy = new (mozilla::fallible) char16_t[aLength];
+ if (!bufferCopy) {
+ // Just assigning mBroken instead of generating tree op. The caller
+ // of tokenizeBuffer() will call MarkAsBroken() as appropriate.
+ mBroken = NS_ERROR_OUT_OF_MEMORY;
+ requestSuspension();
+ return;
+ }
+
+ memcpy(bufferCopy, aBuffer, aLength * sizeof(char16_t));
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpAppendText, bufferCopy, aLength,
+ deepTreeSurrogateParent ? deepTreeSurrogateParent : aParent);
+}
+
+void
+nsHtml5TreeBuilder::appendIsindexPrompt(nsIContentHandle* aParent)
+{
+ NS_PRECONDITION(aParent, "Null parent");
+
+ if (mBuilder) {
+ nsresult rv = nsHtml5TreeOperation::AppendIsindexPrompt(
+ static_cast<nsIContent*>(aParent),
+ mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ return;
+ }
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpAppendIsindexPrompt, aParent);
+}
+
+void
+nsHtml5TreeBuilder::appendComment(nsIContentHandle* aParent, char16_t* aBuffer, int32_t aStart, int32_t aLength)
+{
+ NS_PRECONDITION(aBuffer, "Null buffer");
+ NS_PRECONDITION(aParent, "Null parent");
+ MOZ_ASSERT(!aStart, "aStart must always be zero.");
+
+ if (deepTreeSurrogateParent) {
+ return;
+ }
+
+ if (mBuilder) {
+ nsresult rv = nsHtml5TreeOperation::AppendComment(
+ static_cast<nsIContent*>(aParent),
+ aBuffer, // XXX aStart always ignored???
+ aLength,
+ mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ return;
+ }
+
+ char16_t* bufferCopy = new (mozilla::fallible) char16_t[aLength];
+ if (!bufferCopy) {
+ // Just assigning mBroken instead of generating tree op. The caller
+ // of tokenizeBuffer() will call MarkAsBroken() as appropriate.
+ mBroken = NS_ERROR_OUT_OF_MEMORY;
+ requestSuspension();
+ return;
+ }
+
+ memcpy(bufferCopy, aBuffer, aLength * sizeof(char16_t));
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpAppendComment, bufferCopy, aLength, aParent);
+}
+
+void
+nsHtml5TreeBuilder::appendCommentToDocument(char16_t* aBuffer, int32_t aStart, int32_t aLength)
+{
+ NS_PRECONDITION(aBuffer, "Null buffer");
+ MOZ_ASSERT(!aStart, "aStart must always be zero.");
+
+ if (mBuilder) {
+ nsresult rv = nsHtml5TreeOperation::AppendCommentToDocument(
+ aBuffer, // XXX aStart always ignored???
+ aLength,
+ mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ return;
+ }
+
+ char16_t* bufferCopy = new (mozilla::fallible) char16_t[aLength];
+ if (!bufferCopy) {
+ // Just assigning mBroken instead of generating tree op. The caller
+ // of tokenizeBuffer() will call MarkAsBroken() as appropriate.
+ mBroken = NS_ERROR_OUT_OF_MEMORY;
+ requestSuspension();
+ return;
+ }
+
+ memcpy(bufferCopy, aBuffer, aLength * sizeof(char16_t));
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpAppendCommentToDocument, bufferCopy, aLength);
+}
+
+void
+nsHtml5TreeBuilder::addAttributesToElement(nsIContentHandle* aElement, nsHtml5HtmlAttributes* aAttributes)
+{
+ NS_PRECONDITION(aElement, "Null element");
+ NS_PRECONDITION(aAttributes, "Null attributes");
+
+ if (aAttributes == nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES) {
+ return;
+ }
+
+ if (mBuilder) {
+ MOZ_ASSERT(aAttributes == tokenizer->GetAttributes(),
+ "Using attribute other than the tokenizer's to add to body or html.");
+ nsresult rv = nsHtml5TreeOperation::AddAttributes(
+ static_cast<nsIContent*>(aElement),
+ aAttributes,
+ mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ return;
+ }
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(aElement, aAttributes);
+}
+
+void
+nsHtml5TreeBuilder::markMalformedIfScript(nsIContentHandle* aElement)
+{
+ NS_PRECONDITION(aElement, "Null element");
+
+ if (mBuilder) {
+ nsHtml5TreeOperation::MarkMalformedIfScript(
+ static_cast<nsIContent*>(aElement));
+ return;
+ }
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpMarkMalformedIfScript, aElement);
+}
+
+void
+nsHtml5TreeBuilder::start(bool fragment)
+{
+ mCurrentHtmlScriptIsAsyncOrDefer = false;
+ deepTreeSurrogateParent = nullptr;
+#ifdef DEBUG
+ mActive = true;
+#endif
+}
+
+void
+nsHtml5TreeBuilder::end()
+{
+ mOpQueue.Clear();
+#ifdef DEBUG
+ mActive = false;
+#endif
+}
+
+void
+nsHtml5TreeBuilder::appendDoctypeToDocument(nsIAtom* aName,
+ nsHtml5String aPublicId,
+ nsHtml5String aSystemId)
+{
+ NS_PRECONDITION(aName, "Null name");
+ nsString publicId; // Not Auto, because using it to hold nsStringBuffer*
+ nsString systemId; // Not Auto, because using it to hold nsStringBuffer*
+ aPublicId.ToString(publicId);
+ aSystemId.ToString(systemId);
+ if (mBuilder) {
+ nsCOMPtr<nsIAtom> name = nsHtml5TreeOperation::Reget(aName);
+ nsresult rv = nsHtml5TreeOperation::AppendDoctypeToDocument(
+ name, publicId, systemId, mBuilder);
+ if (NS_FAILED(rv)) {
+ MarkAsBrokenAndRequestSuspension(rv);
+ }
+ return;
+ }
+
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(aName, publicId, systemId);
+ // nsXMLContentSink can flush here, but what's the point?
+ // It can also interrupt here, but we can't.
+}
+
+void
+nsHtml5TreeBuilder::elementPushed(int32_t aNamespace, nsIAtom* aName, nsIContentHandle* aElement)
+{
+ NS_ASSERTION(aNamespace == kNameSpaceID_XHTML || aNamespace == kNameSpaceID_SVG || aNamespace == kNameSpaceID_MathML, "Element isn't HTML, SVG or MathML!");
+ NS_ASSERTION(aName, "Element doesn't have local name!");
+ NS_ASSERTION(aElement, "No element!");
+ /*
+ * The frame constructor uses recursive algorithms, so it can't deal with
+ * arbitrarily deep trees. This is especially a problem on Windows where
+ * the permitted depth of the runtime stack is rather small.
+ *
+ * The following is a protection against author incompetence--not against
+ * malice. There are other ways to make the DOM deep anyway.
+ *
+ * The basic idea is that when the tree builder stack gets too deep,
+ * append operations no longer append to the node that the HTML parsing
+ * algorithm says they should but instead text nodes are append to the last
+ * element that was seen before a magic tree builder stack threshold was
+ * reached and element and comment nodes aren't appended to the DOM at all.
+ *
+ * However, for security reasons, non-child descendant text nodes inside an
+ * SVG script or style element should not become children. Also, non-cell
+ * table elements shouldn't be used as surrogate parents for user experience
+ * reasons.
+ */
+ if (!deepTreeSurrogateParent && currentPtr >= MAX_REFLOW_DEPTH &&
+ !(aName == nsHtml5Atoms::script ||
+ aName == nsHtml5Atoms::table ||
+ aName == nsHtml5Atoms::thead ||
+ aName == nsHtml5Atoms::tfoot ||
+ aName == nsHtml5Atoms::tbody ||
+ aName == nsHtml5Atoms::tr ||
+ aName == nsHtml5Atoms::colgroup ||
+ aName == nsHtml5Atoms::style)) {
+ deepTreeSurrogateParent = aElement;
+ }
+ if (aNamespace != kNameSpaceID_XHTML) {
+ return;
+ }
+ if (aName == nsHtml5Atoms::body || aName == nsHtml5Atoms::frameset) {
+ if (mBuilder) {
+ // InnerHTML and DOMParser shouldn't start layout anyway
+ return;
+ }
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpStartLayout);
+ return;
+ }
+ if (aName == nsHtml5Atoms::input ||
+ aName == nsHtml5Atoms::button) {
+ if (mBuilder) {
+ nsHtml5TreeOperation::DoneCreatingElement(static_cast<nsIContent*>(aElement));
+ } else {
+ mOpQueue.AppendElement()->Init(eTreeOpDoneCreatingElement, aElement);
+ }
+ return;
+ }
+ if (aName == nsHtml5Atoms::audio ||
+ aName == nsHtml5Atoms::video ||
+ aName == nsHtml5Atoms::menuitem) {
+ if (mBuilder) {
+ nsHtml5TreeOperation::DoneCreatingElement(static_cast<nsIContent*>(aElement));
+ } else {
+ mOpQueue.AppendElement()->Init(eTreeOpDoneCreatingElement, aElement);
+ }
+ return;
+ }
+ if (mSpeculativeLoadStage && aName == nsHtml5Atoms::picture) {
+ // mSpeculativeLoadStage is non-null only in the off-the-main-thread
+ // tree builder, which handles the network stream
+ //
+ // See comments in nsHtml5SpeculativeLoad.h about <picture> preloading
+ mSpeculativeLoadQueue.AppendElement()->InitOpenPicture();
+ }
+}
+
+void
+nsHtml5TreeBuilder::elementPopped(int32_t aNamespace, nsIAtom* aName, nsIContentHandle* aElement)
+{
+ NS_ASSERTION(aNamespace == kNameSpaceID_XHTML || aNamespace == kNameSpaceID_SVG || aNamespace == kNameSpaceID_MathML, "Element isn't HTML, SVG or MathML!");
+ NS_ASSERTION(aName, "Element doesn't have local name!");
+ NS_ASSERTION(aElement, "No element!");
+ if (deepTreeSurrogateParent && currentPtr <= MAX_REFLOW_DEPTH) {
+ deepTreeSurrogateParent = nullptr;
+ }
+ if (aNamespace == kNameSpaceID_MathML) {
+ return;
+ }
+ // we now have only SVG and HTML
+ if (aName == nsHtml5Atoms::script) {
+ if (mPreventScriptExecution) {
+ if (mBuilder) {
+ nsHtml5TreeOperation::PreventScriptExecution(static_cast<nsIContent*>(aElement));
+ return;
+ }
+ mOpQueue.AppendElement()->Init(eTreeOpPreventScriptExecution, aElement);
+ return;
+ }
+ if (mBuilder) {
+ return;
+ }
+ if (mCurrentHtmlScriptIsAsyncOrDefer) {
+ NS_ASSERTION(aNamespace == kNameSpaceID_XHTML,
+ "Only HTML scripts may be async/defer.");
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpRunScriptAsyncDefer, aElement);
+ mCurrentHtmlScriptIsAsyncOrDefer = false;
+ return;
+ }
+ requestSuspension();
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->InitScript(aElement);
+ return;
+ }
+ if (aName == nsHtml5Atoms::title) {
+ if (mBuilder) {
+ nsHtml5TreeOperation::DoneAddingChildren(static_cast<nsIContent*>(aElement));
+ return;
+ }
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpDoneAddingChildren, aElement);
+ return;
+ }
+ if (aName == nsHtml5Atoms::style || (aNamespace == kNameSpaceID_XHTML && aName == nsHtml5Atoms::link)) {
+ if (mBuilder) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
+ "Scripts must be blocked.");
+ mBuilder->UpdateStyleSheet(static_cast<nsIContent*>(aElement));
+ return;
+ }
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpUpdateStyleSheet, aElement);
+ return;
+ }
+ if (aNamespace == kNameSpaceID_SVG) {
+ if (aName == nsHtml5Atoms::svg) {
+ if (!scriptingEnabled || mPreventScriptExecution) {
+ return;
+ }
+ if (mBuilder) {
+ nsHtml5TreeOperation::SvgLoad(static_cast<nsIContent*>(aElement));
+ return;
+ }
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpSvgLoad, aElement);
+ }
+ return;
+ }
+ // we now have only HTML
+ // Some HTML nodes need DoneAddingChildren() called to initialize
+ // properly (e.g. form state restoration).
+ // XXX expose ElementName group here and do switch
+ if (aName == nsHtml5Atoms::object ||
+ aName == nsHtml5Atoms::applet ||
+ aName == nsHtml5Atoms::select ||
+ aName == nsHtml5Atoms::textarea ||
+ aName == nsHtml5Atoms::output) {
+ if (mBuilder) {
+ nsHtml5TreeOperation::DoneAddingChildren(static_cast<nsIContent*>(aElement));
+ return;
+ }
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpDoneAddingChildren, aElement);
+ return;
+ }
+ if (aName == nsHtml5Atoms::meta && !fragment && !mBuilder) {
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpProcessMeta, aElement);
+ return;
+ }
+ if (mSpeculativeLoadStage && aName == nsHtml5Atoms::picture) {
+ // mSpeculativeLoadStage is non-null only in the off-the-main-thread
+ // tree builder, which handles the network stream
+ //
+ // See comments in nsHtml5SpeculativeLoad.h about <picture> preloading
+ mSpeculativeLoadQueue.AppendElement()->InitEndPicture();
+ }
+ return;
+}
+
+void
+nsHtml5TreeBuilder::accumulateCharacters(const char16_t* aBuf, int32_t aStart, int32_t aLength)
+{
+ MOZ_RELEASE_ASSERT(charBufferLen + aLength <= charBuffer.length,
+ "About to memcpy past the end of the buffer!");
+ memcpy(charBuffer + charBufferLen, aBuf + aStart, sizeof(char16_t) * aLength);
+ charBufferLen += aLength;
+}
+
+// INT32_MAX is (2^31)-1. Therefore, the highest power-of-two that fits
+// is 2^30. Note that this is counting char16_t units. The underlying
+// bytes will be twice that, but they fit even in 32-bit size_t even
+// if a contiguous chunk of memory of that size is pretty unlikely to
+// be available on a 32-bit system.
+#define MAX_POWER_OF_TWO_IN_INT32 0x40000000
+
+bool
+nsHtml5TreeBuilder::EnsureBufferSpace(int32_t aLength)
+{
+ // TODO: Unify nsHtml5Tokenizer::strBuf and nsHtml5TreeBuilder::charBuffer
+ // so that this method becomes unnecessary.
+ CheckedInt<int32_t> worstCase(charBufferLen);
+ worstCase += aLength;
+ if (!worstCase.isValid()) {
+ return false;
+ }
+ if (worstCase.value() > MAX_POWER_OF_TWO_IN_INT32) {
+ return false;
+ }
+ if (!charBuffer) {
+ if (worstCase.value() < MAX_POWER_OF_TWO_IN_INT32) {
+ // Add one to round to the next power of two to avoid immediate
+ // reallocation once there are a few characters in the buffer.
+ worstCase += 1;
+ }
+ charBuffer = jArray<char16_t,int32_t>::newFallibleJArray(mozilla::RoundUpPow2(worstCase.value()));
+ if (!charBuffer) {
+ return false;
+ }
+ } else if (worstCase.value() > charBuffer.length) {
+ jArray<char16_t,int32_t> newBuf = jArray<char16_t,int32_t>::newFallibleJArray(mozilla::RoundUpPow2(worstCase.value()));
+ if (!newBuf) {
+ return false;
+ }
+ memcpy(newBuf, charBuffer, sizeof(char16_t) * size_t(charBufferLen));
+ charBuffer = newBuf;
+ }
+ return true;
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::AllocateContentHandle()
+{
+ if (MOZ_UNLIKELY(mBuilder)) {
+ MOZ_ASSERT_UNREACHABLE("Must never allocate a handle with builder.");
+ return nullptr;
+ }
+ if (mHandlesUsed == NS_HTML5_TREE_BUILDER_HANDLE_ARRAY_LENGTH) {
+ mOldHandles.AppendElement(Move(mHandles));
+ mHandles = MakeUnique<nsIContent*[]>(NS_HTML5_TREE_BUILDER_HANDLE_ARRAY_LENGTH);
+ mHandlesUsed = 0;
+ }
+#ifdef DEBUG
+ mHandles[mHandlesUsed] = reinterpret_cast<nsIContent*>(uintptr_t(0xC0DEDBAD));
+#endif
+ return &mHandles[mHandlesUsed++];
+}
+
+bool
+nsHtml5TreeBuilder::HasScript()
+{
+ uint32_t len = mOpQueue.Length();
+ if (!len) {
+ return false;
+ }
+ return mOpQueue.ElementAt(len - 1).IsRunScript();
+}
+
+bool
+nsHtml5TreeBuilder::Flush(bool aDiscretionary)
+{
+ if (MOZ_UNLIKELY(mBuilder)) {
+ MOZ_ASSERT_UNREACHABLE("Must never flush with builder.");
+ return false;
+ }
+ if (NS_SUCCEEDED(mBroken)) {
+ if (!aDiscretionary ||
+ !(charBufferLen &&
+ currentPtr >= 0 &&
+ stack[currentPtr]->isFosterParenting())) {
+ // Don't flush text on discretionary flushes if the current element on
+ // the stack is a foster-parenting element and there's pending text,
+ // because flushing in that case would make the tree shape dependent on
+ // where the flush points fall.
+ flushCharacters();
+ }
+ FlushLoads();
+ }
+ if (mOpSink) {
+ bool hasOps = !mOpQueue.IsEmpty();
+ if (hasOps) {
+ // If the builder is broken and mOpQueue is not empty, there must be
+ // one op and it must be eTreeOpMarkAsBroken.
+ if (NS_FAILED(mBroken)) {
+ MOZ_ASSERT(mOpQueue.Length() == 1,
+ "Tree builder is broken with a non-empty op queue whose length isn't 1.");
+ MOZ_ASSERT(mOpQueue[0].IsMarkAsBroken(),
+ "Tree builder is broken but the op in queue is not marked as broken.");
+ }
+ mOpSink->MoveOpsFrom(mOpQueue);
+ }
+ return hasOps;
+ }
+ // no op sink: throw away ops
+ mOpQueue.Clear();
+ return false;
+}
+
+void
+nsHtml5TreeBuilder::FlushLoads()
+{
+ if (MOZ_UNLIKELY(mBuilder)) {
+ MOZ_ASSERT_UNREACHABLE("Must never flush loads with builder.");
+ return;
+ }
+ if (!mSpeculativeLoadQueue.IsEmpty()) {
+ mSpeculativeLoadStage->MoveSpeculativeLoadsFrom(mSpeculativeLoadQueue);
+ }
+}
+
+void
+nsHtml5TreeBuilder::SetDocumentCharset(nsACString& aCharset,
+ int32_t aCharsetSource)
+{
+ if (mBuilder) {
+ mBuilder->SetDocumentCharsetAndSource(aCharset, aCharsetSource);
+ } else if (mSpeculativeLoadStage) {
+ mSpeculativeLoadQueue.AppendElement()->InitSetDocumentCharset(
+ aCharset, aCharsetSource);
+ } else {
+ mOpQueue.AppendElement()->Init(
+ eTreeOpSetDocumentCharset, aCharset, aCharsetSource);
+ }
+}
+
+void
+nsHtml5TreeBuilder::StreamEnded()
+{
+ MOZ_ASSERT(!mBuilder, "Must not call StreamEnded with builder.");
+ MOZ_ASSERT(!fragment, "Must not parse fragments off the main thread.");
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpStreamEnded);
+}
+
+void
+nsHtml5TreeBuilder::NeedsCharsetSwitchTo(const nsACString& aCharset,
+ int32_t aCharsetSource,
+ int32_t aLineNumber)
+{
+ if (MOZ_UNLIKELY(mBuilder)) {
+ MOZ_ASSERT_UNREACHABLE("Must never switch charset with builder.");
+ return;
+ }
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(eTreeOpNeedsCharsetSwitchTo,
+ aCharset,
+ aCharsetSource,
+ aLineNumber);
+}
+
+void
+nsHtml5TreeBuilder::MaybeComplainAboutCharset(const char* aMsgId,
+ bool aError,
+ int32_t aLineNumber)
+{
+ if (MOZ_UNLIKELY(mBuilder)) {
+ MOZ_ASSERT_UNREACHABLE("Must never complain about charset with builder.");
+ return;
+ }
+ mOpQueue.AppendElement()->Init(aMsgId, aError, aLineNumber);
+}
+
+void
+nsHtml5TreeBuilder::AddSnapshotToScript(nsAHtml5TreeBuilderState* aSnapshot, int32_t aLine)
+{
+ if (MOZ_UNLIKELY(mBuilder)) {
+ MOZ_ASSERT_UNREACHABLE("Must never use snapshots with builder.");
+ return;
+ }
+ NS_PRECONDITION(HasScript(), "No script to add a snapshot to!");
+ NS_PRECONDITION(aSnapshot, "Got null snapshot.");
+ mOpQueue.ElementAt(mOpQueue.Length() - 1).SetSnapshot(aSnapshot, aLine);
+}
+
+void
+nsHtml5TreeBuilder::DropHandles()
+{
+ MOZ_ASSERT(!mBuilder, "Must not drop handles with builder.");
+ mOldHandles.Clear();
+ mHandlesUsed = 0;
+}
+
+void
+nsHtml5TreeBuilder::MarkAsBroken(nsresult aRv)
+{
+ if (MOZ_UNLIKELY(mBuilder)) {
+ MOZ_ASSERT_UNREACHABLE("Must not call this with builder.");
+ return;
+ }
+ mBroken = aRv;
+ mOpQueue.Clear(); // Previous ops don't matter anymore
+ mOpQueue.AppendElement()->Init(aRv);
+}
+
+void
+nsHtml5TreeBuilder::MarkAsBrokenFromPortability(nsresult aRv)
+{
+ if (mBuilder) {
+ MarkAsBrokenAndRequestSuspension(aRv);
+ return;
+ }
+ mBroken = aRv;
+ requestSuspension();
+}
+
+void
+nsHtml5TreeBuilder::StartPlainTextViewSource(const nsAutoString& aTitle)
+{
+ MOZ_ASSERT(!mBuilder, "Must not view source with builder.");
+ startTag(nsHtml5ElementName::ELT_TITLE,
+ nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES,
+ false);
+
+ // XUL will add the "Source of: " prefix.
+ uint32_t length = aTitle.Length();
+ if (length > INT32_MAX) {
+ length = INT32_MAX;
+ }
+ characters(aTitle.get(), 0, (int32_t)length);
+ endTag(nsHtml5ElementName::ELT_TITLE);
+
+ startTag(nsHtml5ElementName::ELT_LINK,
+ nsHtml5ViewSourceUtils::NewLinkAttributes(),
+ false);
+
+ startTag(nsHtml5ElementName::ELT_BODY,
+ nsHtml5ViewSourceUtils::NewBodyAttributes(),
+ false);
+
+ StartPlainTextBody();
+}
+
+void
+nsHtml5TreeBuilder::StartPlainText()
+{
+ MOZ_ASSERT(!mBuilder, "Must not view source with builder.");
+ startTag(nsHtml5ElementName::ELT_LINK,
+ nsHtml5PlainTextUtils::NewLinkAttributes(),
+ false);
+
+ StartPlainTextBody();
+}
+
+void
+nsHtml5TreeBuilder::StartPlainTextBody()
+{
+ MOZ_ASSERT(!mBuilder, "Must not view source with builder.");
+ startTag(nsHtml5ElementName::ELT_PRE,
+ nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES,
+ false);
+ needToDropLF = false;
+}
+
+// DocumentModeHandler
+void
+nsHtml5TreeBuilder::documentMode(nsHtml5DocumentMode m)
+{
+ if (mBuilder) {
+ mBuilder->SetDocumentMode(m);
+ return;
+ }
+ if (mSpeculativeLoadStage) {
+ mSpeculativeLoadQueue.AppendElement()->InitSetDocumentMode(m);
+ return;
+ }
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ treeOp->Init(m);
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::getDocumentFragmentForTemplate(nsIContentHandle* aTemplate)
+{
+ if (mBuilder) {
+ return nsHtml5TreeOperation::GetDocumentFragmentForTemplate(static_cast<nsIContent*>(aTemplate));
+ }
+ nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
+ NS_ASSERTION(treeOp, "Tree op allocation failed.");
+ nsIContentHandle* fragHandle = AllocateContentHandle();
+ treeOp->Init(eTreeOpGetDocumentFragmentForTemplate, aTemplate, fragHandle);
+ return fragHandle;
+}
+
+nsIContentHandle*
+nsHtml5TreeBuilder::getFormPointerForContext(nsIContentHandle* aContext)
+{
+ MOZ_ASSERT(mBuilder, "Must have builder.");
+ if (!aContext) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // aContext must always be an element that already exists
+ // in the document.
+ nsIContent* contextNode = static_cast<nsIContent*>(aContext);
+ nsIContent* currentAncestor = contextNode;
+
+ // We traverse the ancestors of the context node to find the nearest
+ // form pointer. This traversal is why aContext must not be an emtpy handle.
+ nsIContent* nearestForm = nullptr;
+ while (currentAncestor) {
+ if (currentAncestor->IsHTMLElement(nsGkAtoms::form)) {
+ nearestForm = currentAncestor;
+ break;
+ }
+ currentAncestor = currentAncestor->GetParent();
+ }
+
+ if (!nearestForm) {
+ return nullptr;
+ }
+
+ return nearestForm;
+}
+
+// Error reporting
+
+void
+nsHtml5TreeBuilder::EnableViewSource(nsHtml5Highlighter* aHighlighter)
+{
+ MOZ_ASSERT(!mBuilder, "Must not view source with builder.");
+ mViewSource = aHighlighter;
+}
+
+void
+nsHtml5TreeBuilder::errStrayStartTag(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errStrayStartTag2", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errStrayEndTag(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errStrayEndTag", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errUnclosedElements(int32_t aIndex, nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errUnclosedElements", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errUnclosedElementsImplied(int32_t aIndex, nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errUnclosedElementsImplied",
+ aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errUnclosedElementsCell(int32_t aIndex)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errUnclosedElementsCell");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errStrayDoctype()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errStrayDoctype");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errAlmostStandardsDoctype()
+{
+ if (MOZ_UNLIKELY(mViewSource) && !isSrcdocDocument) {
+ mViewSource->AddErrorToCurrentRun("errAlmostStandardsDoctype");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errQuirkyDoctype()
+{
+ if (MOZ_UNLIKELY(mViewSource) && !isSrcdocDocument) {
+ mViewSource->AddErrorToCurrentRun("errQuirkyDoctype");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNonSpaceInTrailer()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errNonSpaceInTrailer");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNonSpaceAfterFrameset()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errNonSpaceAfterFrameset");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNonSpaceInFrameset()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errNonSpaceInFrameset");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNonSpaceAfterBody()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errNonSpaceAfterBody");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNonSpaceInColgroupInFragment()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errNonSpaceInColgroupInFragment");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNonSpaceInNoscriptInHead()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errNonSpaceInNoscriptInHead");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errFooBetweenHeadAndBody(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errFooBetweenHeadAndBody", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errStartTagWithoutDoctype()
+{
+ if (MOZ_UNLIKELY(mViewSource) && !isSrcdocDocument) {
+ mViewSource->AddErrorToCurrentRun("errStartTagWithoutDoctype");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNoSelectInTableScope()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errNoSelectInTableScope");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errStartSelectWhereEndSelectExpected()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun(
+ "errStartSelectWhereEndSelectExpected");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errStartTagWithSelectOpen(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errStartTagWithSelectOpen", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errBadStartTagInHead(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errBadStartTagInHead2", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errImage()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errImage");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errIsindex()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errIsindex");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errFooSeenWhenFooOpen(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errFooSeenWhenFooOpen", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errHeadingWhenHeadingOpen()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errHeadingWhenHeadingOpen");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errFramesetStart()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errFramesetStart");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNoCellToClose()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errNoCellToClose");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errStartTagInTable(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errStartTagInTable", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errFormWhenFormOpen()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errFormWhenFormOpen");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errTableSeenWhileTableOpen()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errTableSeenWhileTableOpen");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errStartTagInTableBody(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errStartTagInTableBody", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errEndTagSeenWithoutDoctype()
+{
+ if (MOZ_UNLIKELY(mViewSource) && !isSrcdocDocument) {
+ mViewSource->AddErrorToCurrentRun("errEndTagSeenWithoutDoctype");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errEndTagAfterBody()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEndTagAfterBody");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errEndTagSeenWithSelectOpen(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEndTagSeenWithSelectOpen",
+ aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errGarbageInColgroup()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errGarbageInColgroup");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errEndTagBr()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEndTagBr");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNoElementToCloseButEndTagSeen(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun(
+ "errNoElementToCloseButEndTagSeen", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errHtmlStartTagInForeignContext(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errHtmlStartTagInForeignContext",
+ aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errTableClosedWhileCaptionOpen()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errTableClosedWhileCaptionOpen");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNoTableRowToClose()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errNoTableRowToClose");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNonSpaceInTable()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errNonSpaceInTable");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errUnclosedChildrenInRuby()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errUnclosedChildrenInRuby");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errStartTagSeenWithoutRuby(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errStartTagSeenWithoutRuby",
+ aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errSelfClosing()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentSlash("errSelfClosing");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errNoCheckUnclosedElementsOnStack()
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun(
+ "errNoCheckUnclosedElementsOnStack");
+ }
+}
+
+void
+nsHtml5TreeBuilder::errEndTagDidNotMatchCurrentOpenElement(nsIAtom* aName,
+ nsIAtom* aOther)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun(
+ "errEndTagDidNotMatchCurrentOpenElement", aName, aOther);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errEndTagViolatesNestingRules(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEndTagViolatesNestingRules", aName);
+ }
+}
+
+void
+nsHtml5TreeBuilder::errEndWithUnclosedElements(nsIAtom* aName)
+{
+ if (MOZ_UNLIKELY(mViewSource)) {
+ mViewSource->AddErrorToCurrentRun("errEndWithUnclosedElements", aName);
+ }
+}
diff --git a/components/htmlfive/nsHtml5TreeBuilderHSupplement.h b/components/htmlfive/nsHtml5TreeBuilderHSupplement.h
new file mode 100644
index 000000000..afaa0b4a2
--- /dev/null
+++ b/components/htmlfive/nsHtml5TreeBuilderHSupplement.h
@@ -0,0 +1,248 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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_HTML5_TREE_BUILDER_HANDLE_ARRAY_LENGTH 512
+
+ private:
+ nsHtml5OplessBuilder* mBuilder;
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ // If mBuilder is not null, the tree op machinery is not in use and
+ // the fields below aren't in use, either.
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ nsHtml5Highlighter* mViewSource;
+ nsTArray<nsHtml5TreeOperation> mOpQueue;
+ nsTArray<nsHtml5SpeculativeLoad> mSpeculativeLoadQueue;
+ nsAHtml5TreeOpSink* mOpSink;
+ mozilla::UniquePtr<nsIContent*[]> mHandles;
+ int32_t mHandlesUsed;
+ nsTArray<mozilla::UniquePtr<nsIContent*[]>> mOldHandles;
+ nsHtml5TreeOpStage* mSpeculativeLoadStage;
+ nsresult mBroken;
+ bool mCurrentHtmlScriptIsAsyncOrDefer;
+ bool mPreventScriptExecution;
+#ifdef DEBUG
+ bool mActive;
+#endif
+
+ // DocumentModeHandler
+ /**
+ * Tree builder uses this to report quirkiness of the document
+ */
+ void documentMode(nsHtml5DocumentMode m);
+
+ nsIContentHandle* getDocumentFragmentForTemplate(nsIContentHandle* aTemplate);
+
+ nsIContentHandle* getFormPointerForContext(nsIContentHandle* aContext);
+
+ /**
+ * Using nsIContent** instead of nsIContent* is the parser deals with DOM
+ * nodes in a way that works off the main thread. Non-main-thread code
+ * can't refcount or otherwise touch nsIContent objects in any way.
+ * Yet, the off-the-main-thread code needs to have a way to hold onto a
+ * particular node and repeatedly operate on the same node.
+ *
+ * The way this works is that the off-the-main-thread code has an
+ * nsIContent** for each DOM node and a given nsIContent** is only ever
+ * actually dereferenced into an actual nsIContent* on the main thread.
+ * When the off-the-main-thread code requests a new node, it gets an
+ * nsIContent** immediately and a tree op is enqueued for later allocating
+ * an actual nsIContent object and writing a pointer to it into the memory
+ * location pointed to by the nsIContent**.
+ *
+ * Since tree ops are in a queue, the node creating tree op will always
+ * run before tree ops that try to further operate on the node that the
+ * nsIContent** is a handle to.
+ *
+ * On-the-main-thread parts of the parser use nsIContent* instead of
+ * nsIContent**. Since both cases share the same parser core, the parser
+ * core casts both to nsIContentHandle*.
+ */
+ nsIContentHandle* AllocateContentHandle();
+
+ void accumulateCharactersForced(const char16_t* aBuf, int32_t aStart, int32_t aLength)
+ {
+ accumulateCharacters(aBuf, aStart, aLength);
+ }
+
+ void MarkAsBrokenAndRequestSuspension(nsresult aRv)
+ {
+ mBuilder->MarkAsBroken(aRv);
+ requestSuspension();
+ }
+
+ void MarkAsBrokenFromPortability(nsresult aRv);
+
+ public:
+
+ explicit nsHtml5TreeBuilder(nsHtml5OplessBuilder* aBuilder);
+
+ nsHtml5TreeBuilder(nsAHtml5TreeOpSink* aOpSink,
+ nsHtml5TreeOpStage* aStage);
+
+ ~nsHtml5TreeBuilder();
+
+ void StartPlainTextViewSource(const nsAutoString& aTitle);
+
+ void StartPlainText();
+
+ void StartPlainTextBody();
+
+ bool HasScript();
+
+ void SetOpSink(nsAHtml5TreeOpSink* aOpSink)
+ {
+ mOpSink = aOpSink;
+ }
+
+ void ClearOps()
+ {
+ mOpQueue.Clear();
+ }
+
+ bool Flush(bool aDiscretionary = false);
+
+ void FlushLoads();
+
+ void SetDocumentCharset(nsACString& aCharset, int32_t aCharsetSource);
+
+ void StreamEnded();
+
+ void NeedsCharsetSwitchTo(const nsACString& aEncoding,
+ int32_t aSource,
+ int32_t aLineNumber);
+
+ void MaybeComplainAboutCharset(const char* aMsgId,
+ bool aError,
+ int32_t aLineNumber);
+
+ void AddSnapshotToScript(nsAHtml5TreeBuilderState* aSnapshot, int32_t aLine);
+
+ void DropHandles();
+
+ void SetPreventScriptExecution(bool aPrevent)
+ {
+ mPreventScriptExecution = aPrevent;
+ }
+
+ bool HasBuilder()
+ {
+ return mBuilder;
+ }
+
+ /**
+ * Makes sure the buffers are large enough to be able to tokenize aLength
+ * UTF-16 code units before having to make the buffers larger.
+ *
+ * @param aLength the number of UTF-16 code units to be tokenized before the
+ * next call to this method.
+ * @return true if successful; false if out of memory
+ */
+ bool EnsureBufferSpace(int32_t aLength);
+
+ void EnableViewSource(nsHtml5Highlighter* aHighlighter);
+
+ void errStrayStartTag(nsIAtom* aName);
+
+ void errStrayEndTag(nsIAtom* aName);
+
+ void errUnclosedElements(int32_t aIndex, nsIAtom* aName);
+
+ void errUnclosedElementsImplied(int32_t aIndex, nsIAtom* aName);
+
+ void errUnclosedElementsCell(int32_t aIndex);
+
+ void errStrayDoctype();
+
+ void errAlmostStandardsDoctype();
+
+ void errQuirkyDoctype();
+
+ void errNonSpaceInTrailer();
+
+ void errNonSpaceAfterFrameset();
+
+ void errNonSpaceInFrameset();
+
+ void errNonSpaceAfterBody();
+
+ void errNonSpaceInColgroupInFragment();
+
+ void errNonSpaceInNoscriptInHead();
+
+ void errFooBetweenHeadAndBody(nsIAtom* aName);
+
+ void errStartTagWithoutDoctype();
+
+ void errNoSelectInTableScope();
+
+ void errStartSelectWhereEndSelectExpected();
+
+ void errStartTagWithSelectOpen(nsIAtom* aName);
+
+ void errBadStartTagInHead(nsIAtom* aName);
+
+ void errImage();
+
+ void errIsindex();
+
+ void errFooSeenWhenFooOpen(nsIAtom* aName);
+
+ void errHeadingWhenHeadingOpen();
+
+ void errFramesetStart();
+
+ void errNoCellToClose();
+
+ void errStartTagInTable(nsIAtom* aName);
+
+ void errFormWhenFormOpen();
+
+ void errTableSeenWhileTableOpen();
+
+ void errStartTagInTableBody(nsIAtom* aName);
+
+ void errEndTagSeenWithoutDoctype();
+
+ void errEndTagAfterBody();
+
+ void errEndTagSeenWithSelectOpen(nsIAtom* aName);
+
+ void errGarbageInColgroup();
+
+ void errEndTagBr();
+
+ void errNoElementToCloseButEndTagSeen(nsIAtom* aName);
+
+ void errHtmlStartTagInForeignContext(nsIAtom* aName);
+
+ void errTableClosedWhileCaptionOpen();
+
+ void errNoTableRowToClose();
+
+ void errNonSpaceInTable();
+
+ void errUnclosedChildrenInRuby();
+
+ void errStartTagSeenWithoutRuby(nsIAtom* aName);
+
+ void errSelfClosing();
+
+ void errNoCheckUnclosedElementsOnStack();
+
+ void errEndTagDidNotMatchCurrentOpenElement(nsIAtom* aName, nsIAtom* aOther);
+
+ void errEndTagViolatesNestingRules(nsIAtom* aName);
+
+ void errEndWithUnclosedElements(nsIAtom* aName);
+
+ void MarkAsBroken(nsresult aRv);
+
+ /**
+ * Checks if this parser is broken. Returns a non-NS_OK (i.e. non-0)
+ * value if broken.
+ */
+ nsresult IsBroken()
+ {
+ return mBroken;
+ }
diff --git a/components/htmlfive/nsHtml5TreeOpExecutor.cpp b/components/htmlfive/nsHtml5TreeOpExecutor.cpp
new file mode 100644
index 000000000..a7a8f28f9
--- /dev/null
+++ b/components/htmlfive/nsHtml5TreeOpExecutor.cpp
@@ -0,0 +1,1101 @@
+/* -*- 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/DebugOnly.h"
+#include "mozilla/Likely.h"
+#include "mozilla/dom/nsCSPService.h"
+#include "mozilla/dom/ScriptLoader.h"
+
+#include "nsError.h"
+#include "nsHtml5TreeOpExecutor.h"
+#include "nsIContentViewer.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShell.h"
+#include "nsIDOMDocument.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIWebShellServices.h"
+#include "nsContentUtils.h"
+#include "mozAutoDocUpdate.h"
+#include "nsNetUtil.h"
+#include "nsHtml5Parser.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5StreamParser.h"
+#include "mozilla/css/Loader.h"
+#include "GeckoProfiler.h"
+#include "nsIScriptError.h"
+#include "nsIScriptContext.h"
+#include "mozilla/Preferences.h"
+#include "nsIHTMLDocument.h"
+#include "nsIViewSourceChannel.h"
+#include "xpcpublic.h"
+
+using namespace mozilla;
+
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor)
+ NS_INTERFACE_TABLE_INHERITED(nsHtml5TreeOpExecutor,
+ nsIContentSink)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsHtml5DocumentBuilder)
+
+NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
+
+NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
+
+class nsHtml5ExecutorReflusher : public Runnable
+{
+ private:
+ RefPtr<nsHtml5TreeOpExecutor> mExecutor;
+ public:
+ explicit nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
+ : mExecutor(aExecutor)
+ {}
+ NS_IMETHOD Run() override
+ {
+ mExecutor->RunFlushLoop();
+ return NS_OK;
+ }
+};
+
+static mozilla::LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr;
+static nsITimer* gFlushTimer = nullptr;
+
+nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
+ : nsHtml5DocumentBuilder(false)
+ , mPreloadedURLs(23) // Mean # of preloadable resources per page on dmoz
+ , mSpeculationReferrerPolicy(mozilla::net::RP_Default)
+{
+ // zeroing operator new for everything else
+}
+
+nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
+{
+ if (gBackgroundFlushList && isInList()) {
+ mOpQueue.Clear();
+ removeFrom(*gBackgroundFlushList);
+ if (gBackgroundFlushList->isEmpty()) {
+ delete gBackgroundFlushList;
+ gBackgroundFlushList = nullptr;
+ if (gFlushTimer) {
+ gFlushTimer->Cancel();
+ NS_RELEASE(gFlushTimer);
+ }
+ }
+ }
+ NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue.");
+}
+
+// nsIContentSink
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::WillParse()
+{
+ NS_NOTREACHED("No one should call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::WillBuildModel(nsDTDMode aDTDMode)
+{
+ mDocument->AddObserver(this);
+ WillBuildModelImpl();
+ GetDocument()->BeginLoad();
+ if (mDocShell && !GetDocument()->GetWindow() &&
+ !IsExternalViewSource()) {
+ // Not loading as data but script global object not ready
+ return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+ return NS_OK;
+}
+
+
+// This is called when the tree construction has ended
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated)
+{
+ if (!aTerminated) {
+ // This is needed to avoid unblocking loads too many times on one hand
+ // and on the other hand to avoid destroying the frame constructor from
+ // within an update batch. See bug 537683.
+ EndDocUpdate();
+
+ // If the above caused a call to nsIParser::Terminate(), let that call
+ // win.
+ if (!mParser) {
+ return NS_OK;
+ }
+ }
+
+ if (mRunsToCompletion) {
+ return NS_OK;
+ }
+
+ GetParser()->DropStreamParser();
+
+ // This comes from nsXMLContentSink and nsHTMLContentSink
+ // If this parser has been marked as broken, treat the end of parse as
+ // forced termination.
+ DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken()));
+
+ if (!mLayoutStarted) {
+ // We never saw the body, and layout never got started. Force
+ // layout *now*, to get an initial reflow.
+
+ // NOTE: only force the layout if we are NOT destroying the
+ // docshell. If we are destroying it, then starting layout will
+ // likely cause us to crash, or at best waste a lot of time as we
+ // are just going to tear it down anyway.
+ bool destroying = true;
+ if (mDocShell) {
+ mDocShell->IsBeingDestroyed(&destroying);
+ }
+
+ if (!destroying) {
+ nsContentSink::StartLayout(false);
+ }
+ }
+
+ ScrollToRef();
+ mDocument->RemoveObserver(this);
+ if (!mParser) {
+ // DidBuildModelImpl may cause mParser to be nulled out
+ // Return early to avoid unblocking the onload event too many times.
+ return NS_OK;
+ }
+
+ // We may not have called BeginLoad() if loading is terminated before
+ // OnStartRequest call.
+ if (mStarted) {
+ mDocument->EndLoad();
+ }
+ DropParserAndPerfHint();
+#ifdef GATHER_DOCWRITE_STATISTICS
+ printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites);
+ printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites);
+ printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites);
+#endif
+#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize);
+ if (sAppendBatchExaminations != 0) {
+ printf("AVERAGE SLOTS EXAMINED: %d\n", sAppendBatchSlotsExamined / sAppendBatchExaminations);
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::WillInterrupt()
+{
+ NS_NOTREACHED("Don't call. For interface compat only.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::WillResume()
+{
+ NS_NOTREACHED("Don't call. For interface compat only.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser)
+{
+ mParser = aParser;
+ return NS_OK;
+}
+
+void
+nsHtml5TreeOpExecutor::FlushPendingNotifications(mozFlushType aType)
+{
+ if (aType >= Flush_InterruptibleLayout) {
+ // Bug 577508 / 253951
+ nsContentSink::StartLayout(true);
+ }
+}
+
+nsISupports*
+nsHtml5TreeOpExecutor::GetTarget()
+{
+ return mDocument;
+}
+
+nsresult
+nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+ mBroken = aReason;
+ if (mStreamParser) {
+ mStreamParser->Terminate();
+ }
+ // We are under memory pressure, but let's hope the following allocation
+ // works out so that we get to terminate and clean up the parser from
+ // a safer point.
+ if (mParser) { // can mParser ever be null here?
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_DispatchToMainThread(NewRunnableMethod(GetParser(), &nsHtml5Parser::Terminate)));
+ }
+ return aReason;
+}
+
+void
+FlushTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ RefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst();
+ if (ex) {
+ ex->RunFlushLoop();
+ }
+ if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) {
+ delete gBackgroundFlushList;
+ gBackgroundFlushList = nullptr;
+ gFlushTimer->Cancel();
+ NS_RELEASE(gFlushTimer);
+ }
+}
+
+void
+nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync()
+{
+ if (!mDocument || !mDocument->IsInBackgroundWindow()) {
+ nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);
+ if (NS_FAILED(NS_DispatchToMainThread(flusher))) {
+ NS_WARNING("failed to dispatch executor flush event");
+ }
+ } else {
+ if (!gBackgroundFlushList) {
+ gBackgroundFlushList = new mozilla::LinkedList<nsHtml5TreeOpExecutor>();
+ }
+ if (!isInList()) {
+ gBackgroundFlushList->insertBack(this);
+ }
+ if (!gFlushTimer) {
+ nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1");
+ t.swap(gFlushTimer);
+ // The timer value 50 should not hopefully slow down background pages too
+ // much, yet lets event loop to process enough between ticks.
+ // See bug 734015.
+ gFlushTimer->InitWithNamedFuncCallback(FlushTimerCallback, nullptr,
+ 50, nsITimer::TYPE_REPEATING_SLACK,
+ "FlushTimerCallback");
+ }
+ }
+}
+
+void
+nsHtml5TreeOpExecutor::FlushSpeculativeLoads()
+{
+ nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
+ mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
+ const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
+ const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
+ for (nsHtml5SpeculativeLoad* iter = const_cast<nsHtml5SpeculativeLoad*>(start);
+ iter < end;
+ ++iter) {
+ if (MOZ_UNLIKELY(!mParser)) {
+ // An extension terminated the parser from a HTTP observer.
+ return;
+ }
+ iter->Perform(this);
+ }
+}
+
+class nsHtml5FlushLoopGuard
+{
+ private:
+ RefPtr<nsHtml5TreeOpExecutor> mExecutor;
+ #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ uint32_t mStartTime;
+ #endif
+ public:
+ explicit nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor)
+ : mExecutor(aExecutor)
+ #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow()))
+ #endif
+ {
+ mExecutor->mRunFlushLoopOnStack = true;
+ }
+ ~nsHtml5FlushLoopGuard()
+ {
+ #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ uint32_t timeOffTheEventLoop =
+ PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime;
+ if (timeOffTheEventLoop >
+ nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) {
+ nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop =
+ timeOffTheEventLoop;
+ }
+ printf("Longest time off the event loop: %d\n",
+ nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop);
+ #endif
+
+ mExecutor->mRunFlushLoopOnStack = false;
+ }
+};
+
+/**
+ * The purpose of the loop here is to avoid returning to the main event loop
+ */
+void
+nsHtml5TreeOpExecutor::RunFlushLoop()
+{
+ PROFILER_LABEL("nsHtml5TreeOpExecutor", "RunFlushLoop",
+ js::ProfileEntry::Category::OTHER);
+
+ if (mRunFlushLoopOnStack) {
+ // There's already a RunFlushLoop() on the call stack.
+ return;
+ }
+
+ nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu!
+
+ RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
+ RefPtr<nsHtml5StreamParser> streamParserGrip;
+ if (mParser) {
+ streamParserGrip = GetParser()->GetStreamParser();
+ }
+ mozilla::Unused
+ << streamParserGrip; // Intentionally not used within function
+
+ // Remember the entry time
+ (void) nsContentSink::WillParseImpl();
+
+ for (;;) {
+ if (!mParser) {
+ // Parse has terminated.
+ mOpQueue.Clear(); // clear in order to be able to assert in destructor
+ return;
+ }
+
+ if (NS_FAILED(IsBroken())) {
+ return;
+ }
+
+ if (!parserKungFuDeathGrip->IsParserEnabled()) {
+ // The parser is blocked.
+ return;
+ }
+
+ if (mFlushState != eNotFlushing) {
+ // XXX Can this happen? In case it can, let's avoid crashing.
+ return;
+ }
+
+ // If there are scripts executing, then the content sink is jumping the gun
+ // (probably due to a synchronous XMLHttpRequest) and will re-enable us
+ // later, see bug 460706.
+ if (IsScriptExecuting()) {
+ return;
+ }
+
+ if (mReadingFromStage) {
+ nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
+ mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue);
+ // Make sure speculative loads never start after the corresponding
+ // normal loads for the same URLs.
+ const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
+ const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
+ for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start;
+ iter < end;
+ ++iter) {
+ iter->Perform(this);
+ if (MOZ_UNLIKELY(!mParser)) {
+ // An extension terminated the parser from a HTTP observer.
+ mOpQueue.Clear(); // clear in order to be able to assert in destructor
+ return;
+ }
+ }
+ } else {
+ FlushSpeculativeLoads(); // Make sure speculative loads never start after
+ // the corresponding normal loads for the same
+ // URLs.
+ if (MOZ_UNLIKELY(!mParser)) {
+ // An extension terminated the parser from a HTTP observer.
+ mOpQueue.Clear(); // clear in order to be able to assert in destructor
+ return;
+ }
+ // Now parse content left in the document.write() buffer queue if any.
+ // This may generate tree ops on its own or dequeue a speculation.
+ nsresult rv = GetParser()->ParseUntilBlocked();
+ if (NS_FAILED(rv)) {
+ MarkAsBroken(rv);
+ return;
+ }
+ }
+
+ if (mOpQueue.IsEmpty()) {
+ // Avoid bothering the rest of the engine with a doc update if there's
+ // nothing to do.
+ return;
+ }
+
+ mFlushState = eInFlush;
+
+ nsIContent* scriptElement = nullptr;
+
+ BeginDocUpdate();
+
+ uint32_t numberOfOpsToFlush = mOpQueue.Length();
+
+ const nsHtml5TreeOperation* first = mOpQueue.Elements();
+ const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1;
+ for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) {
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The previous tree op caused a call to nsIParser::Terminate().
+ break;
+ }
+ NS_ASSERTION(mFlushState == eInDocUpdate,
+ "Tried to perform tree op outside update batch.");
+ nsresult rv = iter->Perform(this, &scriptElement);
+ if (NS_FAILED(rv)) {
+ MarkAsBroken(rv);
+ break;
+ }
+
+ // Be sure not to check the deadline if the last op was just performed.
+ if (MOZ_UNLIKELY(iter == last)) {
+ break;
+ } else if (MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
+ NS_ERROR_HTMLPARSER_INTERRUPTED)) {
+ mOpQueue.RemoveElementsAt(0, (iter - first) + 1);
+
+ EndDocUpdate();
+
+ mFlushState = eNotFlushing;
+
+ #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ printf("REFLUSH SCHEDULED (executing ops): %d\n",
+ ++sTimesFlushLoopInterrupted);
+ #endif
+ nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+ return;
+ }
+ ++iter;
+ }
+
+ mOpQueue.Clear();
+
+ EndDocUpdate();
+
+ mFlushState = eNotFlushing;
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The parse ended already.
+ return;
+ }
+
+ if (scriptElement) {
+ // must be tail call when mFlushState is eNotFlushing
+ RunScript(scriptElement);
+
+ // Always check the clock in nsContentSink right after a script
+ StopDeflecting();
+ if (nsContentSink::DidProcessATokenImpl() ==
+ NS_ERROR_HTMLPARSER_INTERRUPTED) {
+ #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ printf("REFLUSH SCHEDULED (after script): %d\n",
+ ++sTimesFlushLoopInterrupted);
+ #endif
+ nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+ return;
+ }
+ }
+ }
+}
+
+nsresult
+nsHtml5TreeOpExecutor::FlushDocumentWrite()
+{
+ nsresult rv = IsBroken();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ FlushSpeculativeLoads(); // Make sure speculative loads never start after the
+ // corresponding normal loads for the same URLs.
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The parse has ended.
+ mOpQueue.Clear(); // clear in order to be able to assert in destructor
+ return rv;
+ }
+
+ if (mFlushState != eNotFlushing) {
+ // XXX Can this happen? In case it can, let's avoid crashing.
+ return rv;
+ }
+
+ mFlushState = eInFlush;
+
+ // avoid crashing near EOF
+ RefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
+ RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
+ mozilla::Unused << parserKungFuDeathGrip; // Intentionally not used within function
+ RefPtr<nsHtml5StreamParser> streamParserGrip;
+ if (mParser) {
+ streamParserGrip = GetParser()->GetStreamParser();
+ }
+ mozilla::Unused
+ << streamParserGrip; // Intentionally not used within function
+
+ NS_ASSERTION(!mReadingFromStage,
+ "Got doc write flush when reading from stage");
+
+#ifdef DEBUG
+ mStage.AssertEmpty();
+#endif
+
+ nsIContent* scriptElement = nullptr;
+
+ BeginDocUpdate();
+
+ uint32_t numberOfOpsToFlush = mOpQueue.Length();
+
+ const nsHtml5TreeOperation* start = mOpQueue.Elements();
+ const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
+ for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start);
+ iter < end;
+ ++iter) {
+ if (MOZ_UNLIKELY(!mParser)) {
+ // The previous tree op caused a call to nsIParser::Terminate().
+ break;
+ }
+ NS_ASSERTION(mFlushState == eInDocUpdate,
+ "Tried to perform tree op outside update batch.");
+ rv = iter->Perform(this, &scriptElement);
+ if (NS_FAILED(rv)) {
+ MarkAsBroken(rv);
+ break;
+ }
+ }
+
+ mOpQueue.Clear();
+
+ EndDocUpdate();
+
+ mFlushState = eNotFlushing;
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // Ending the doc update caused a call to nsIParser::Terminate().
+ return rv;
+ }
+
+ if (scriptElement) {
+ // must be tail call when mFlushState is eNotFlushing
+ RunScript(scriptElement);
+ }
+ return rv;
+}
+
+// copied from HTML content sink
+bool
+nsHtml5TreeOpExecutor::IsScriptEnabled()
+{
+ // Note that if we have no document or no docshell or no global or whatnot we
+ // want to claim script _is_ enabled, so we don't parse the contents of
+ // <noscript> tags!
+ if (!mDocument || !mDocShell)
+ return true;
+ nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(mDocument->GetInnerWindow());
+ // Getting context is tricky if the document hasn't had its
+ // GlobalObject set yet
+ if (!globalObject) {
+ globalObject = mDocShell->GetScriptGlobalObject();
+ }
+ NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true);
+ return xpc::Scriptability::Get(globalObject->GetGlobalJSObject()).Allowed();
+}
+
+void
+nsHtml5TreeOpExecutor::StartLayout() {
+ if (mLayoutStarted || !mDocument) {
+ return;
+ }
+
+ EndDocUpdate();
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // got terminate
+ return;
+ }
+
+ nsContentSink::StartLayout(false);
+
+ BeginDocUpdate();
+}
+
+/**
+ * The reason why this code is here and not in the tree builder even in the
+ * main-thread case is to allow the control to return from the tokenizer
+ * before scripts run. This way, the tokenizer is not invoked re-entrantly
+ * although the parser is.
+ *
+ * The reason why this is called as a tail call when mFlushState is set to
+ * eNotFlushing is to allow re-entry to Flush() but only after the current
+ * Flush() has cleared the op queue and is otherwise done cleaning up after
+ * itself.
+ */
+void
+nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement)
+{
+ if (mRunsToCompletion) {
+ // We are in createContextualFragment() or in the upcoming document.parse().
+ // Do nothing. Let's not even mark scripts malformed here, because that
+ // could cause serialization weirdness later.
+ return;
+ }
+
+ NS_ASSERTION(aScriptElement, "No script to run");
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement);
+
+ if (!mParser) {
+ NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed.");
+ // We got here not because of an end tag but because the tree builder
+ // popped an incomplete script element on EOF. Returning here to avoid
+ // calling back into mParser anymore.
+ return;
+ }
+
+ if (sele->GetScriptDeferred() || sele->GetScriptAsync()) {
+ DebugOnly<bool> block = sele->AttemptToExecute();
+ NS_ASSERTION(!block, "Defer or async script tried to block.");
+ return;
+ }
+
+ NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing.");
+
+ mReadingFromStage = false;
+
+ sele->SetCreatorParser(GetParser());
+
+ // Copied from nsXMLContentSink
+ // Now tell the script that it's ready to go. This may execute the script
+ // or return true, or neither if the script doesn't need executing.
+ bool block = sele->AttemptToExecute();
+
+ // If the act of insertion evaluated the script, we're fine.
+ // Else, block the parser till the script has loaded.
+ if (block) {
+ if (mParser) {
+ GetParser()->BlockParser();
+ }
+ } else {
+ // mParser may have been nulled out by now, but the flusher deals
+
+ // If this event isn't needed, it doesn't do anything. It is sometimes
+ // necessary for the parse to continue after complex situations.
+ nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
+ }
+}
+
+void
+nsHtml5TreeOpExecutor::Start()
+{
+ NS_PRECONDITION(!mStarted, "Tried to start when already started.");
+ mStarted = true;
+}
+
+void
+nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding,
+ int32_t aSource,
+ uint32_t aLineNumber)
+{
+ EndDocUpdate();
+
+ if (MOZ_UNLIKELY(!mParser)) {
+ // got terminate
+ return;
+ }
+
+ nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell);
+ if (!wss) {
+ return;
+ }
+
+ // ask the webshellservice to load the URL
+ if (NS_SUCCEEDED(wss->StopDocumentLoad())) {
+ wss->ReloadDocument(aEncoding, aSource);
+ }
+ // if the charset switch was accepted, wss has called Terminate() on the
+ // parser by now
+
+ if (!mParser) {
+ // success
+ if (aSource == kCharsetFromMetaTag) {
+ MaybeComplainAboutCharset("EncLateMetaReload", false, aLineNumber);
+ }
+ return;
+ }
+
+ if (aSource == kCharsetFromMetaTag) {
+ MaybeComplainAboutCharset("EncLateMetaTooLate", true, aLineNumber);
+ }
+
+ GetParser()->ContinueAfterFailedCharsetSwitch();
+
+ BeginDocUpdate();
+}
+
+void
+nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId,
+ bool aError,
+ uint32_t aLineNumber)
+{
+ if (mAlreadyComplainedAboutCharset) {
+ return;
+ }
+ // The EncNoDeclaration case for advertising iframes is so common that it
+ // would result is way too many errors. The iframe case doesn't matter
+ // when the ad is an image or a Flash animation anyway. When the ad is
+ // textual, a misrendered ad probably isn't a huge loss for users.
+ // Let's suppress the message in this case.
+ // This means that errors about other different-origin iframes in mashups
+ // are lost as well, but generally, the site author isn't in control of
+ // the embedded different-origin pages anyway and can't fix problems even
+ // if alerted about them.
+ if (!strcmp(aMsgId, "EncNoDeclaration") && mDocShell) {
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ mDocShell->GetSameTypeParent(getter_AddRefs(parent));
+ if (parent) {
+ return;
+ }
+ }
+ mAlreadyComplainedAboutCharset = true;
+ nsContentUtils::ReportToConsole(aError ? nsIScriptError::errorFlag
+ : nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("HTML parser"),
+ mDocument,
+ nsContentUtils::eHTMLPARSER_PROPERTIES,
+ aMsgId,
+ nullptr,
+ 0,
+ nullptr,
+ EmptyString(),
+ aLineNumber);
+}
+
+void
+nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(nsIDocument* aDoc)
+{
+ NS_ASSERTION(!mAlreadyComplainedAboutCharset,
+ "How come we already managed to complain?");
+ mAlreadyComplainedAboutCharset = true;
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("HTML parser"),
+ aDoc,
+ nsContentUtils::eHTMLPARSER_PROPERTIES,
+ "EncProtocolUnsupported");
+}
+
+nsHtml5Parser*
+nsHtml5TreeOpExecutor::GetParser()
+{
+ MOZ_ASSERT(!mRunsToCompletion);
+ return static_cast<nsHtml5Parser*>(mParser.get());
+}
+
+void
+nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
+{
+ NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution.");
+ mOpQueue.AppendElements(Move(aOpQueue));
+}
+
+void
+nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine)
+{
+ GetParser()->InitializeDocWriteParserState(aState, aLine);
+}
+
+nsIURI*
+nsHtml5TreeOpExecutor::GetViewSourceBaseURI()
+{
+ if (!mViewSourceBaseURI) {
+
+ // We query the channel for the baseURI because in certain situations it
+ // cannot otherwise be determined. If this process fails, fall back to the
+ // standard method.
+ nsCOMPtr<nsIViewSourceChannel> vsc =
+ do_QueryInterface(mDocument->GetChannel());
+ if (vsc) {
+ nsresult rv = vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI));
+ if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) {
+ return mViewSourceBaseURI;
+ }
+ }
+
+ nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI();
+ bool isViewSource;
+ orig->SchemeIs("view-source", &isViewSource);
+ if (isViewSource) {
+ nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig);
+ NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!");
+ nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI));
+ } else {
+ // Fail gracefully if the base URL isn't a view-source: URL.
+ // Not sure if this can ever happen.
+ mViewSourceBaseURI = orig;
+ }
+ }
+ return mViewSourceBaseURI;
+}
+
+//static
+void
+nsHtml5TreeOpExecutor::InitializeStatics()
+{
+ mozilla::Preferences::AddBoolVarCache(&sExternalViewSource,
+ "view_source.editor.external");
+}
+
+bool
+nsHtml5TreeOpExecutor::IsExternalViewSource()
+{
+ if (!sExternalViewSource) {
+ return false;
+ }
+ bool isViewSource = false;
+ if (mDocumentURI) {
+ mDocumentURI->SchemeIs("view-source", &isViewSource);
+ }
+ return isViewSource;
+}
+
+// Speculative loading
+
+nsIURI*
+nsHtml5TreeOpExecutor::BaseURIForPreload()
+{
+ // The URL of the document without <base>
+ nsIURI* documentURI = mDocument->GetDocumentURI();
+ // The URL of the document with non-speculative <base>
+ nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
+
+ // If the two above are different, use documentBaseURI. If they are the same,
+ // the document object isn't aware of a <base>, so attempt to use the
+ // mSpeculationBaseURI or, failing, that, documentURI.
+ return (documentURI == documentBaseURI) ?
+ (mSpeculationBaseURI ?
+ mSpeculationBaseURI.get() : documentURI)
+ : documentBaseURI;
+}
+
+already_AddRefed<nsIURI>
+nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL)
+{
+ if (aURL.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsIURI* base = BaseURIForPreload();
+ const nsCString& charset = mDocument->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create a URI");
+ return nullptr;
+ }
+
+ if (ShouldPreloadURI(uri)) {
+ return uri.forget();
+ }
+
+ return nullptr;
+}
+
+bool
+nsHtml5TreeOpExecutor::ShouldPreloadURI(nsIURI *aURI)
+{
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (mPreloadedURLs.Contains(spec)) {
+ return false;
+ }
+ mPreloadedURLs.PutEntry(spec);
+ return true;
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
+ const nsAString& aCharset,
+ const nsAString& aType,
+ const nsAString& aCrossOrigin,
+ const nsAString& aIntegrity,
+ bool aScriptFromHead,
+ bool aAsync,
+ bool aDefer,
+ bool aNoModule)
+{
+ nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
+ if (!uri) {
+ return;
+ }
+ mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin,
+ aIntegrity, aScriptFromHead, aAsync,
+ aDefer, aNoModule,
+ mSpeculationReferrerPolicy);
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
+ const nsAString& aCharset,
+ const nsAString& aCrossOrigin,
+ const nsAString& aIntegrity)
+{
+ nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
+ if (!uri) {
+ return;
+ }
+ mDocument->PreloadStyle(uri, aCharset, aCrossOrigin,
+ mSpeculationReferrerPolicy, aIntegrity);
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
+ const nsAString& aCrossOrigin,
+ const nsAString& aSrcset,
+ const nsAString& aSizes,
+ const nsAString& aImageReferrerPolicy)
+{
+ nsCOMPtr<nsIURI> baseURI = BaseURIForPreload();
+ bool isImgSet = false;
+ nsCOMPtr<nsIURI> uri = mDocument->ResolvePreloadImage(baseURI, aURL, aSrcset,
+ aSizes, &isImgSet);
+ if (uri && ShouldPreloadURI(uri)) {
+ // use document wide referrer policy
+ mozilla::net::ReferrerPolicy referrerPolicy = mSpeculationReferrerPolicy;
+ // if enabled in preferences, use the referrer attribute from the image, if provided
+ bool referrerAttributeEnabled = Preferences::GetBool("network.http.enablePerElementReferrer", true);
+ if (referrerAttributeEnabled) {
+ mozilla::net::ReferrerPolicy imageReferrerPolicy =
+ mozilla::net::AttributeReferrerPolicyFromString(aImageReferrerPolicy);
+ if (imageReferrerPolicy != mozilla::net::RP_Unset) {
+ referrerPolicy = imageReferrerPolicy;
+ }
+ }
+
+ mDocument->MaybePreLoadImage(uri, aCrossOrigin, referrerPolicy, isImgSet);
+ }
+}
+
+// These calls inform the document of picture state and seen sources, such that
+// it can use them to inform ResolvePreLoadImage as necessary
+void
+nsHtml5TreeOpExecutor::PreloadPictureSource(const nsAString& aSrcset,
+ const nsAString& aSizes,
+ const nsAString& aType,
+ const nsAString& aMedia)
+{
+ mDocument->PreloadPictureImageSource(aSrcset, aSizes, aType, aMedia);
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadOpenPicture()
+{
+ mDocument->PreloadPictureOpened();
+}
+
+void
+nsHtml5TreeOpExecutor::PreloadEndPicture()
+{
+ mDocument->PreloadPictureClosed();
+}
+
+void
+nsHtml5TreeOpExecutor::AddBase(const nsAString& aURL)
+{
+ const nsCString& charset = mDocument->GetDocumentCharacterSet();
+ nsresult rv = NS_NewURI(getter_AddRefs(mViewSourceBaseURI), aURL,
+ charset.get(), GetViewSourceBaseURI());
+ if (NS_FAILED(rv)) {
+ mViewSourceBaseURI = nullptr;
+ }
+}
+void
+nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL)
+{
+ if (mSpeculationBaseURI) {
+ // the first one wins
+ return;
+ }
+ const nsCString& charset = mDocument->GetDocumentCharacterSet();
+ DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(mSpeculationBaseURI), aURL,
+ charset.get(), mDocument->GetDocumentURI());
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to create a URI");
+}
+
+void
+nsHtml5TreeOpExecutor::SetSpeculationReferrerPolicy(const nsAString& aReferrerPolicy)
+{
+ // Specs says:
+ // - Let value be the result of stripping leading and trailing whitespace from
+ // the value of element's content attribute.
+ // - If value is not the empty string, then:
+ if (aReferrerPolicy.IsEmpty()) {
+ return;
+ }
+
+ ReferrerPolicy policy = mozilla::net::ReferrerPolicyFromString(aReferrerPolicy);
+ // Specs says:
+ // - If policy is not the empty string, then set element's node document's
+ // referrer policy to policy
+ if (policy != mozilla::net::RP_Unset) {
+ SetSpeculationReferrerPolicy(policy);
+ }
+}
+
+void
+nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP)
+{
+ if (!CSPService::sCSPEnabled) {
+ return;
+ }
+
+ NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+ nsCOMPtr<nsIPrincipal> principal = mDocument->NodePrincipal();
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
+ nsresult rv = principal->EnsurePreloadCSP(domDoc, getter_AddRefs(preloadCsp));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (!preloadCsp) {
+ // XXX: System principals can't preload CSP. We're done here.
+ return;
+ }
+
+ // please note that meta CSPs and CSPs delivered through a header need
+ // to be joined together.
+ rv = preloadCsp->AppendPolicy(aCSP,
+ false, // csp via meta tag can not be report only
+ true); // delivered through the meta tag
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Record "speculated" referrer policy for preloads
+ bool hasReferrerPolicy = false;
+ uint32_t referrerPolicy = mozilla::net::RP_Default;
+ rv = preloadCsp->GetReferrerPolicy(&referrerPolicy, &hasReferrerPolicy);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ if (hasReferrerPolicy) {
+ SetSpeculationReferrerPolicy(static_cast<ReferrerPolicy>(referrerPolicy));
+ }
+
+ mDocument->ApplySettingsFromCSP(true);
+}
+
+void
+nsHtml5TreeOpExecutor::SetSpeculationReferrerPolicy(ReferrerPolicy aReferrerPolicy)
+{
+ // Record "speculated" referrer policy locally and thread through the
+ // speculation phase. The actual referrer policy will be set by
+ // HTMLMetaElement::BindToTree().
+ mSpeculationReferrerPolicy = aReferrerPolicy;
+}
+
+#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
+uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
+uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
+uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
+uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0;
+#endif
+bool nsHtml5TreeOpExecutor::sExternalViewSource = false;
diff --git a/components/htmlfive/nsHtml5TreeOpExecutor.h b/components/htmlfive/nsHtml5TreeOpExecutor.h
new file mode 100644
index 000000000..878f359c5
--- /dev/null
+++ b/components/htmlfive/nsHtml5TreeOpExecutor.h
@@ -0,0 +1,309 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsHtml5TreeOpExecutor_h
+#define nsHtml5TreeOpExecutor_h
+
+#include "nsIAtom.h"
+#include "nsTraceRefcnt.h"
+#include "nsHtml5TreeOperation.h"
+#include "nsHtml5SpeculativeLoad.h"
+#include "nsTArray.h"
+#include "nsContentSink.h"
+#include "nsNodeInfoManager.h"
+#include "nsHtml5DocumentMode.h"
+#include "nsIScriptElement.h"
+#include "nsIParser.h"
+#include "nsAHtml5TreeOpSink.h"
+#include "nsHtml5TreeOpStage.h"
+#include "nsIURI.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "mozilla/LinkedList.h"
+#include "nsHtml5DocumentBuilder.h"
+#include "mozilla/net/ReferrerPolicy.h"
+
+class nsHtml5Parser;
+class nsHtml5StreamParser;
+class nsIContent;
+class nsIDocument;
+
+class nsHtml5TreeOpExecutor final : public nsHtml5DocumentBuilder,
+ public nsIContentSink,
+ public nsAHtml5TreeOpSink,
+ public mozilla::LinkedListElement<nsHtml5TreeOpExecutor>
+{
+ friend class nsHtml5FlushLoopGuard;
+ typedef mozilla::net::ReferrerPolicy ReferrerPolicy;
+
+ public:
+ NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
+ NS_DECL_ISUPPORTS_INHERITED
+
+ private:
+ static bool sExternalViewSource;
+#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
+ static uint32_t sAppendBatchMaxSize;
+ static uint32_t sAppendBatchSlotsExamined;
+ static uint32_t sAppendBatchExaminations;
+ static uint32_t sLongestTimeOffTheEventLoop;
+ static uint32_t sTimesFlushLoopInterrupted;
+#endif
+
+ /**
+ * Whether EOF needs to be suppressed
+ */
+ bool mSuppressEOF;
+
+ bool mReadingFromStage;
+ nsTArray<nsHtml5TreeOperation> mOpQueue;
+ nsHtml5StreamParser* mStreamParser;
+
+ /**
+ * URLs already preloaded/preloading.
+ */
+ nsTHashtable<nsCStringHashKey> mPreloadedURLs;
+
+ nsCOMPtr<nsIURI> mSpeculationBaseURI;
+
+ /**
+ * Speculative referrer policy
+ */
+ ReferrerPolicy mSpeculationReferrerPolicy;
+
+ nsCOMPtr<nsIURI> mViewSourceBaseURI;
+
+ /**
+ * Whether the parser has started
+ */
+ bool mStarted;
+
+ nsHtml5TreeOpStage mStage;
+
+ bool mRunFlushLoopOnStack;
+
+ bool mCallContinueInterruptedParsingIfEnabled;
+
+ /**
+ * Whether this executor has already complained about matters related
+ * to character encoding declarations.
+ */
+ bool mAlreadyComplainedAboutCharset;
+
+ public:
+
+ nsHtml5TreeOpExecutor();
+
+ protected:
+
+ virtual ~nsHtml5TreeOpExecutor();
+
+ public:
+
+ // nsIContentSink
+
+ /**
+ * Unimplemented. For interface compat only.
+ */
+ NS_IMETHOD WillParse() override;
+
+ /**
+ *
+ */
+ NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override;
+
+ /**
+ * Emits EOF.
+ */
+ NS_IMETHOD DidBuildModel(bool aTerminated) override;
+
+ /**
+ * Forwards to nsContentSink
+ */
+ NS_IMETHOD WillInterrupt() override;
+
+ /**
+ * Unimplemented. For interface compat only.
+ */
+ NS_IMETHOD WillResume() override;
+
+ /**
+ * Sets the parser.
+ */
+ NS_IMETHOD SetParser(nsParserBase* aParser) override;
+
+ /**
+ * No-op for backwards compat.
+ */
+ virtual void FlushPendingNotifications(mozFlushType aType) override;
+
+ /**
+ * Don't call. For interface compat only.
+ */
+ NS_IMETHOD SetDocumentCharset(nsACString& aCharset) override {
+ NS_NOTREACHED("No one should call this.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ /**
+ * Returns the document.
+ */
+ virtual nsISupports *GetTarget() override;
+
+ virtual void ContinueInterruptedParsingAsync() override;
+
+ bool IsScriptExecuting() override
+ {
+ return IsScriptExecutingImpl();
+ }
+
+ // Not from interface
+
+ void SetStreamParser(nsHtml5StreamParser* aStreamParser)
+ {
+ mStreamParser = aStreamParser;
+ }
+
+ void InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine);
+
+ bool IsScriptEnabled();
+
+ virtual nsresult MarkAsBroken(nsresult aReason) override;
+
+ void StartLayout();
+
+ void FlushSpeculativeLoads();
+
+ void RunFlushLoop();
+
+ nsresult FlushDocumentWrite();
+
+ void MaybeSuspend();
+
+ void Start();
+
+ void NeedsCharsetSwitchTo(const char* aEncoding,
+ int32_t aSource,
+ uint32_t aLineNumber);
+
+ void MaybeComplainAboutCharset(const char* aMsgId,
+ bool aError,
+ uint32_t aLineNumber);
+
+ void ComplainAboutBogusProtocolCharset(nsIDocument* aDoc);
+
+ bool IsComplete()
+ {
+ return !mParser;
+ }
+
+ bool HasStarted()
+ {
+ return mStarted;
+ }
+
+ bool IsFlushing()
+ {
+ return mFlushState >= eInFlush;
+ }
+
+#ifdef DEBUG
+ bool IsInFlushLoop()
+ {
+ return mRunFlushLoopOnStack;
+ }
+#endif
+
+ void RunScript(nsIContent* aScriptElement);
+
+ /**
+ * Flush the operations from the tree operations from the argument
+ * queue unconditionally. (This is for the main thread case.)
+ */
+ virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue) override;
+
+ nsHtml5TreeOpStage* GetStage()
+ {
+ return &mStage;
+ }
+
+ void StartReadingFromStage()
+ {
+ mReadingFromStage = true;
+ }
+
+ void StreamEnded();
+
+#ifdef DEBUG
+ void AssertStageEmpty()
+ {
+ mStage.AssertEmpty();
+ }
+#endif
+
+ nsIURI* GetViewSourceBaseURI();
+
+ void PreloadScript(const nsAString& aURL,
+ const nsAString& aCharset,
+ const nsAString& aType,
+ const nsAString& aCrossOrigin,
+ const nsAString& aIntegrity,
+ bool aScriptFromHead,
+ bool aAsync,
+ bool aDefer,
+ bool aNoModule);
+
+ void PreloadStyle(const nsAString& aURL, const nsAString& aCharset,
+ const nsAString& aCrossOrigin,
+ const nsAString& aIntegrity);
+
+ void PreloadImage(const nsAString& aURL,
+ const nsAString& aCrossOrigin,
+ const nsAString& aSrcset,
+ const nsAString& aSizes,
+ const nsAString& aImageReferrerPolicy);
+
+ void PreloadOpenPicture();
+
+ void PreloadEndPicture();
+
+ void PreloadPictureSource(const nsAString& aSrcset,
+ const nsAString& aSizes,
+ const nsAString& aType,
+ const nsAString& aMedia);
+
+ void SetSpeculationBase(const nsAString& aURL);
+
+ void SetSpeculationReferrerPolicy(ReferrerPolicy aReferrerPolicy);
+ void SetSpeculationReferrerPolicy(const nsAString& aReferrerPolicy);
+
+ void AddSpeculationCSP(const nsAString& aCSP);
+
+ void AddBase(const nsAString& aURL);
+
+ static void InitializeStatics();
+
+ private:
+ nsHtml5Parser* GetParser();
+
+ bool IsExternalViewSource();
+
+ /**
+ * Get a nsIURI for an nsString if the URL hasn't been preloaded yet.
+ */
+ already_AddRefed<nsIURI> ConvertIfNotPreloadedYet(const nsAString& aURL);
+
+ /**
+ * The base URI we would use for current preload operations
+ */
+ nsIURI* BaseURIForPreload();
+
+ /**
+ * Returns true if we haven't preloaded this URI yet, and adds it to the
+ * list of preloaded URIs
+ */
+ bool ShouldPreloadURI(nsIURI *aURI);
+};
+
+#endif // nsHtml5TreeOpExecutor_h
diff --git a/components/htmlfive/nsHtml5TreeOpStage.cpp b/components/htmlfive/nsHtml5TreeOpStage.cpp
new file mode 100644
index 000000000..5f7fe7496
--- /dev/null
+++ b/components/htmlfive/nsHtml5TreeOpStage.cpp
@@ -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/. */
+
+#include "nsHtml5TreeOpStage.h"
+
+using namespace mozilla;
+
+nsHtml5TreeOpStage::nsHtml5TreeOpStage()
+ : mMutex("nsHtml5TreeOpStage mutex")
+{
+}
+
+nsHtml5TreeOpStage::~nsHtml5TreeOpStage()
+{
+}
+
+void
+nsHtml5TreeOpStage::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
+{
+ mozilla::MutexAutoLock autoLock(mMutex);
+ mOpQueue.AppendElements(Move(aOpQueue));
+}
+
+void
+nsHtml5TreeOpStage::MoveOpsAndSpeculativeLoadsTo(nsTArray<nsHtml5TreeOperation>& aOpQueue,
+ nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue)
+{
+ mozilla::MutexAutoLock autoLock(mMutex);
+ aOpQueue.AppendElements(Move(mOpQueue));
+ aSpeculativeLoadQueue.AppendElements(Move(mSpeculativeLoadQueue));
+}
+
+void
+nsHtml5TreeOpStage::MoveSpeculativeLoadsFrom(nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue)
+{
+ mozilla::MutexAutoLock autoLock(mMutex);
+ mSpeculativeLoadQueue.AppendElements(Move(aSpeculativeLoadQueue));
+}
+
+void
+nsHtml5TreeOpStage::MoveSpeculativeLoadsTo(nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue)
+{
+ mozilla::MutexAutoLock autoLock(mMutex);
+ aSpeculativeLoadQueue.AppendElements(Move(mSpeculativeLoadQueue));
+}
+
+#ifdef DEBUG
+void
+nsHtml5TreeOpStage::AssertEmpty()
+{
+ mozilla::MutexAutoLock autoLock(mMutex);
+ // This shouldn't really need the mutex
+ NS_ASSERTION(mOpQueue.IsEmpty(), "The stage was supposed to be empty.");
+}
+#endif
diff --git a/components/htmlfive/nsHtml5TreeOpStage.h b/components/htmlfive/nsHtml5TreeOpStage.h
new file mode 100644
index 000000000..10e0054ee
--- /dev/null
+++ b/components/htmlfive/nsHtml5TreeOpStage.h
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsHtml5TreeOpStage_h
+#define nsHtml5TreeOpStage_h
+
+#include "mozilla/Mutex.h"
+#include "nsHtml5TreeOperation.h"
+#include "nsTArray.h"
+#include "nsAHtml5TreeOpSink.h"
+#include "nsHtml5SpeculativeLoad.h"
+
+class nsHtml5TreeOpStage : public nsAHtml5TreeOpSink {
+ public:
+
+ nsHtml5TreeOpStage();
+
+ virtual ~nsHtml5TreeOpStage();
+
+ /**
+ * Flush the operations from the tree operations from the argument
+ * queue unconditionally.
+ */
+ virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue);
+
+ /**
+ * Retrieve the staged operations and speculative loads into the arguments.
+ */
+ void MoveOpsAndSpeculativeLoadsTo(nsTArray<nsHtml5TreeOperation>& aOpQueue,
+ nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue);
+
+ /**
+ * Move the speculative loads from the argument into the staging queue.
+ */
+ void MoveSpeculativeLoadsFrom(nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue);
+
+ /**
+ * Retrieve the staged speculative loads into the argument.
+ */
+ void MoveSpeculativeLoadsTo(nsTArray<nsHtml5SpeculativeLoad>& aSpeculativeLoadQueue);
+
+#ifdef DEBUG
+ void AssertEmpty();
+#endif
+
+ private:
+ nsTArray<nsHtml5TreeOperation> mOpQueue;
+ nsTArray<nsHtml5SpeculativeLoad> mSpeculativeLoadQueue;
+ mozilla::Mutex mMutex;
+
+};
+
+#endif /* nsHtml5TreeOpStage_h */
diff --git a/components/htmlfive/nsHtml5TreeOperation.cpp b/components/htmlfive/nsHtml5TreeOperation.cpp
new file mode 100644
index 000000000..236c1f108
--- /dev/null
+++ b/components/htmlfive/nsHtml5TreeOperation.cpp
@@ -0,0 +1,1237 @@
+/* -*- 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 "nsHtml5TreeOperation.h"
+#include "nsContentUtils.h"
+#include "nsDocElementCreatedNotificationRunner.h"
+#include "nsNodeUtils.h"
+#include "nsAttrName.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsIDOMMutationEvent.h"
+#include "mozAutoDocUpdate.h"
+#include "nsBindingManager.h"
+#include "nsXBLBinding.h"
+#include "nsHtml5DocumentMode.h"
+#include "nsHtml5HtmlAttributes.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsIScriptElement.h"
+#include "nsIDTD.h"
+#include "nsISupportsImpl.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsIFormControl.h"
+#include "nsIStyleSheetLinkingElement.h"
+#include "nsIDOMDocumentType.h"
+#include "DocGroup.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "nsIMutationObserver.h"
+#include "nsIFormProcessor.h"
+#include "nsIServiceManager.h"
+#include "nsEscape.h"
+#include "mozilla/dom/Comment.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "nsHtml5SVGLoadDispatcher.h"
+#include "nsIURI.h"
+#include "nsIProtocolHandler.h"
+#include "nsNetUtil.h"
+#include "nsIHTMLDocument.h"
+#include "mozilla/Likely.h"
+#include "nsTextNode.h"
+
+using namespace mozilla;
+
+static NS_DEFINE_CID(kFormProcessorCID, NS_FORMPROCESSOR_CID);
+
+/**
+ * Helper class that opens a notification batch if the current doc
+ * is different from the executor doc.
+ */
+class MOZ_STACK_CLASS nsHtml5OtherDocUpdate {
+ public:
+ nsHtml5OtherDocUpdate(nsIDocument* aCurrentDoc, nsIDocument* aExecutorDoc)
+ {
+ NS_PRECONDITION(aCurrentDoc, "Node has no doc?");
+ NS_PRECONDITION(aExecutorDoc, "Executor has no doc?");
+ if (MOZ_LIKELY(aCurrentDoc == aExecutorDoc)) {
+ mDocument = nullptr;
+ } else {
+ mDocument = aCurrentDoc;
+ aCurrentDoc->BeginUpdate(UPDATE_CONTENT_MODEL);
+ }
+ }
+
+ ~nsHtml5OtherDocUpdate()
+ {
+ if (MOZ_UNLIKELY(mDocument)) {
+ mDocument->EndUpdate(UPDATE_CONTENT_MODEL);
+ }
+ }
+ private:
+ nsCOMPtr<nsIDocument> mDocument;
+};
+
+/**
+ * Helper class to temporary break out of the document update batch. Use this
+ * with caution as this will cause blocked scripts to run.
+ */
+class MOZ_RAII mozAutoPauseContentUpdate final
+{
+public:
+ explicit mozAutoPauseContentUpdate(nsIDocument* aDocument)
+ : mDocument(aDocument)
+ {
+ MOZ_ASSERT(mDocument);
+ mDocument->EndUpdate(UPDATE_CONTENT_MODEL);
+ }
+
+ ~mozAutoPauseContentUpdate()
+ {
+ mDocument->BeginUpdate(UPDATE_CONTENT_MODEL);
+ }
+
+private:
+ nsCOMPtr<nsIDocument> mDocument;
+};
+
+nsHtml5TreeOperation::nsHtml5TreeOperation()
+ : mOpCode(eTreeOpUninitialized)
+{
+ MOZ_COUNT_CTOR(nsHtml5TreeOperation);
+}
+
+nsHtml5TreeOperation::~nsHtml5TreeOperation()
+{
+ MOZ_COUNT_DTOR(nsHtml5TreeOperation);
+ NS_ASSERTION(mOpCode != eTreeOpUninitialized, "Uninitialized tree op.");
+ switch(mOpCode) {
+ case eTreeOpAddAttributes:
+ delete mTwo.attributes;
+ break;
+ case eTreeOpCreateHTMLElementNetwork:
+ case eTreeOpCreateHTMLElementNotNetwork:
+ case eTreeOpCreateSVGElementNetwork:
+ case eTreeOpCreateSVGElementNotNetwork:
+ case eTreeOpCreateMathMLElement:
+ delete mThree.attributes;
+ break;
+ case eTreeOpAppendDoctypeToDocument:
+ delete mTwo.stringPair;
+ break;
+ case eTreeOpFosterParentText:
+ case eTreeOpAppendText:
+ case eTreeOpAppendComment:
+ case eTreeOpAppendCommentToDocument:
+ case eTreeOpAddViewSourceHref:
+ case eTreeOpAddViewSourceBase:
+ delete[] mTwo.unicharPtr;
+ break;
+ case eTreeOpSetDocumentCharset:
+ case eTreeOpNeedsCharsetSwitchTo:
+ delete[] mOne.charPtr;
+ break;
+ case eTreeOpProcessOfflineManifest:
+ free(mOne.unicharPtr);
+ break;
+ default: // keep the compiler happy
+ break;
+ }
+}
+
+nsresult
+nsHtml5TreeOperation::AppendTextToTextNode(const char16_t* aBuffer,
+ uint32_t aLength,
+ nsIContent* aTextNode,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ NS_PRECONDITION(aTextNode, "Got null text node.");
+ MOZ_ASSERT(aBuilder);
+ MOZ_ASSERT(aBuilder->IsInDocUpdate());
+ uint32_t oldLength = aTextNode->TextLength();
+ CharacterDataChangeInfo info = {
+ true,
+ oldLength,
+ oldLength,
+ aLength
+ };
+ nsNodeUtils::CharacterDataWillChange(aTextNode, &info);
+
+ nsresult rv = aTextNode->AppendText(aBuffer, aLength, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsNodeUtils::CharacterDataChanged(aTextNode, &info);
+ return rv;
+}
+
+
+nsresult
+nsHtml5TreeOperation::AppendText(const char16_t* aBuffer,
+ uint32_t aLength,
+ nsIContent* aParent,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ nsresult rv = NS_OK;
+ nsIContent* lastChild = aParent->GetLastChild();
+ if (lastChild && lastChild->IsNodeOfType(nsINode::eTEXT)) {
+ nsHtml5OtherDocUpdate update(aParent->OwnerDoc(),
+ aBuilder->GetDocument());
+ return AppendTextToTextNode(aBuffer,
+ aLength,
+ lastChild,
+ aBuilder);
+ }
+
+ nsNodeInfoManager* nodeInfoManager = aParent->OwnerDoc()->NodeInfoManager();
+ RefPtr<nsTextNode> text = new nsTextNode(nodeInfoManager);
+ NS_ASSERTION(text, "Infallible malloc failed?");
+ rv = text->SetText(aBuffer, aLength, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return Append(text, aParent, aBuilder);
+}
+
+nsresult
+nsHtml5TreeOperation::Append(nsIContent* aNode,
+ nsIContent* aParent,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ MOZ_ASSERT(aBuilder);
+ MOZ_ASSERT(aBuilder->IsInDocUpdate());
+ nsresult rv = NS_OK;
+ nsHtml5OtherDocUpdate update(aParent->OwnerDoc(),
+ aBuilder->GetDocument());
+ uint32_t childCount = aParent->GetChildCount();
+ rv = aParent->AppendChildTo(aNode, false);
+ if (NS_SUCCEEDED(rv)) {
+ aNode->SetParserHasNotified();
+ nsNodeUtils::ContentAppended(aParent, aNode, childCount);
+ }
+ return rv;
+}
+
+nsresult
+nsHtml5TreeOperation::AppendToDocument(nsIContent* aNode,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ MOZ_ASSERT(aBuilder);
+ MOZ_ASSERT(aBuilder->GetDocument() == aNode->OwnerDoc());
+ MOZ_ASSERT(aBuilder->IsInDocUpdate());
+ nsresult rv = NS_OK;
+
+ nsIDocument* doc = aBuilder->GetDocument();
+ uint32_t childCount = doc->GetChildCount();
+ rv = doc->AppendChildTo(aNode, false);
+ if (rv == NS_ERROR_DOM_HIERARCHY_REQUEST_ERR) {
+ aNode->SetParserHasNotified();
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ aNode->SetParserHasNotified();
+ nsNodeUtils::ContentInserted(doc, aNode, childCount);
+
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot to block scripts");
+ if (aNode->IsElement()) {
+ nsContentUtils::AddScriptRunner(
+ new nsDocElementCreatedNotificationRunner(doc));
+ }
+ return rv;
+}
+
+static bool
+IsElementOrTemplateContent(nsINode* aNode) {
+ if (aNode) {
+ if (aNode->IsElement()) {
+ return true;
+ } else if (aNode->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
+ // Check if the node is a template content.
+ mozilla::dom::DocumentFragment* frag =
+ static_cast<mozilla::dom::DocumentFragment*>(aNode);
+ nsIContent* fragHost = frag->GetHost();
+ if (fragHost && nsNodeUtils::IsTemplateElement(fragHost)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void
+nsHtml5TreeOperation::Detach(nsIContent* aNode, nsHtml5DocumentBuilder* aBuilder)
+{
+ MOZ_ASSERT(aBuilder);
+ MOZ_ASSERT(aBuilder->IsInDocUpdate());
+ nsCOMPtr<nsINode> parent = aNode->GetParentNode();
+ if (parent) {
+ nsHtml5OtherDocUpdate update(parent->OwnerDoc(),
+ aBuilder->GetDocument());
+ int32_t pos = parent->IndexOf(aNode);
+ NS_ASSERTION((pos >= 0), "Element not found as child of its parent");
+ parent->RemoveChildAt(pos, true);
+ }
+}
+
+nsresult
+nsHtml5TreeOperation::AppendChildrenToNewParent(nsIContent* aNode,
+ nsIContent* aParent,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ MOZ_ASSERT(aBuilder);
+ MOZ_ASSERT(aBuilder->IsInDocUpdate());
+ nsHtml5OtherDocUpdate update(aParent->OwnerDoc(),
+ aBuilder->GetDocument());
+
+ uint32_t childCount = aParent->GetChildCount();
+ bool didAppend = false;
+ while (aNode->HasChildren()) {
+ nsCOMPtr<nsIContent> child = aNode->GetFirstChild();
+ aNode->RemoveChildAt(0, true);
+ nsresult rv = aParent->AppendChildTo(child, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ didAppend = true;
+ }
+ if (didAppend) {
+ nsNodeUtils::ContentAppended(aParent, aParent->GetChildAt(childCount),
+ childCount);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsHtml5TreeOperation::FosterParent(nsIContent* aNode,
+ nsIContent* aParent,
+ nsIContent* aTable,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ MOZ_ASSERT(aBuilder);
+ MOZ_ASSERT(aBuilder->IsInDocUpdate());
+ nsIContent* foster = aTable->GetParent();
+
+ if (IsElementOrTemplateContent(foster)) {
+
+ nsHtml5OtherDocUpdate update(foster->OwnerDoc(),
+ aBuilder->GetDocument());
+
+ uint32_t pos = foster->IndexOf(aTable);
+ nsresult rv = foster->InsertChildAt(aNode, pos, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsNodeUtils::ContentInserted(foster, aNode, pos);
+ return rv;
+ }
+
+ return Append(aNode, aParent, aBuilder);
+}
+
+nsresult
+nsHtml5TreeOperation::AddAttributes(nsIContent* aNode,
+ nsHtml5HtmlAttributes* aAttributes,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ dom::Element* node = aNode->AsElement();
+ nsHtml5OtherDocUpdate update(node->OwnerDoc(),
+ aBuilder->GetDocument());
+
+ int32_t len = aAttributes->getLength();
+ for (int32_t i = len; i > 0;) {
+ --i;
+ // prefix doesn't need regetting. it is always null or a static atom
+ // local name is never null
+ nsCOMPtr<nsIAtom> localName =
+ Reget(aAttributes->getLocalNameNoBoundsCheck(i));
+ int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+ if (!node->HasAttr(nsuri, localName)) {
+ // prefix doesn't need regetting. it is always null or a static atom
+ // local name is never null
+ nsString value; // Not Auto, because using it to hold nsStringBuffer*
+ aAttributes->getValueNoBoundsCheck(i).ToString(value);
+ node->SetAttr(
+ nsuri, localName, aAttributes->getPrefixNoBoundsCheck(i), value, true);
+ // XXX what to do with nsresult?
+ }
+ }
+ return NS_OK;
+}
+
+void
+nsHtml5TreeOperation::SetHTMLElementAttributes(dom::Element* aElement,
+ nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes)
+{
+ int32_t len = aAttributes->getLength();
+ for (int32_t i = 0; i < len; i++) {
+ nsHtml5String val = aAttributes->getValueNoBoundsCheck(i);
+ nsIAtom* klass = val.MaybeAsAtom();
+ if (klass) {
+ aElement->SetSingleClassFromParser(klass);
+ } else {
+ // prefix doesn't need regetting. it is always null or a static atom
+ // local name is never null
+ RefPtr<nsIAtom> localName =
+ Reget(aAttributes->getLocalNameNoBoundsCheck(i));
+ RefPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i);
+ int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+
+ nsString value; // Not Auto, because using it to hold nsStringBuffer*
+ val.ToString(value);
+ if (nsGkAtoms::a == aName && nsGkAtoms::name == localName) {
+ // This is an HTML5-incompliant Geckoism.
+ // Remove when fixing bug 582361
+ NS_ConvertUTF16toUTF8 cname(value);
+ NS_ConvertUTF8toUTF16 uv(nsUnescape(cname.BeginWriting()));
+ aElement->SetAttr(nsuri,
+ localName,
+ prefix,
+ uv,
+ false);
+ } else {
+ aElement->SetAttr(nsuri,
+ localName,
+ prefix,
+ value,
+ false);
+ }
+ }
+ }
+}
+
+nsIContent*
+nsHtml5TreeOperation::CreateHTMLElement(
+ nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ mozilla::dom::FromParser aFromParser,
+ nsNodeInfoManager* aNodeInfoManager,
+ nsHtml5DocumentBuilder* aBuilder,
+ mozilla::dom::HTMLContentCreatorFunction aCreator)
+{
+ bool isKeygen = (aName == nsHtml5Atoms::keygen);
+ if (MOZ_UNLIKELY(isKeygen)) {
+ aName = nsHtml5Atoms::select;
+ aCreator = NS_NewHTMLSelectElement;
+ }
+
+ RefPtr<dom::NodeInfo> nodeInfo = aNodeInfoManager->GetNodeInfo(
+ aName, nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE);
+ NS_ASSERTION(nodeInfo, "Got null nodeinfo.");
+
+ dom::Element* newContent = nullptr;
+ nsIDocument* document = nodeInfo->GetDocument();
+ bool willExecuteScript = false;
+ bool isCustomElement = false;
+ nsString isValue;
+ dom::CustomElementDefinition* definition = nullptr;
+
+ // Avoid overhead by checking if custom elements pref is enabled or not.
+ if (nsContentUtils::IsCustomElementsEnabled()) {
+ if (aAttributes) {
+ nsHtml5String is = aAttributes->getValue(nsHtml5AttributeName::ATTR_IS);
+ if (is) {
+ is.ToString(isValue);
+ }
+ }
+
+ isCustomElement = (aCreator == NS_NewCustomElement || !isValue.IsEmpty());
+ if (isCustomElement && aFromParser != dom::FROM_PARSER_FRAGMENT) {
+ RefPtr<nsIAtom> tagAtom = nodeInfo->NameAtom();
+ RefPtr<nsIAtom> typeAtom =
+ isValue.IsEmpty() ? tagAtom : NS_Atomize(isValue);
+
+ MOZ_ASSERT(nodeInfo->NameAtom()->Equals(nodeInfo->LocalName()));
+ definition = nsContentUtils::LookupCustomElementDefinition(document,
+ nodeInfo->NameAtom(), nodeInfo->NamespaceID(), typeAtom);
+
+ if (definition) {
+ willExecuteScript = true;
+ }
+ }
+ }
+
+ if (willExecuteScript) { // This will cause custom element constructors to run
+ AutoSetThrowOnDynamicMarkupInsertionCounter
+ throwOnDynamicMarkupInsertionCounter(document);
+ mozAutoPauseContentUpdate autoPauseContentUpdate(document);
+ {
+ nsAutoMicroTask mt;
+ }
+ dom::AutoCEReaction
+ autoCEReaction(document->GetDocGroup()->CustomElementReactionsStack(),
+ nullptr);
+
+ nsCOMPtr<dom::Element> newElement;
+ NS_NewHTMLElement(getter_AddRefs(newElement), nodeInfo.forget(),
+ aFromParser, (isValue.IsEmpty() ? nullptr : &isValue),
+ definition);
+
+ MOZ_ASSERT(newElement, "Element creation created null pointer.");
+ newContent = newElement;
+ aBuilder->HoldElement(newElement.forget());
+
+ if (MOZ_UNLIKELY(aName == nsHtml5Atoms::style || aName == nsHtml5Atoms::link)) {
+ nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(newContent));
+ if (ssle) {
+ ssle->InitStyleLinkElement(false);
+ ssle->SetEnableUpdates(false);
+ }
+ }
+
+ if (!aAttributes) {
+ return newContent;
+ }
+
+ SetHTMLElementAttributes(newContent, aName, aAttributes);
+ } else {
+ nsCOMPtr<dom::Element> newElement;
+
+ if (isCustomElement) {
+ NS_NewHTMLElement(getter_AddRefs(newElement), nodeInfo.forget(),
+ aFromParser, (isValue.IsEmpty() ? nullptr : &isValue),
+ definition);
+ } else {
+ newElement = aCreator(nodeInfo.forget(), aFromParser);
+ }
+
+ MOZ_ASSERT(newElement, "Element creation created null pointer.");
+
+ newContent = newElement;
+ aBuilder->HoldElement(newElement.forget());
+
+ if (MOZ_UNLIKELY(aName == nsHtml5Atoms::style || aName == nsHtml5Atoms::link)) {
+ nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(newContent));
+ if (ssle) {
+ ssle->InitStyleLinkElement(false);
+ ssle->SetEnableUpdates(false);
+ }
+ } else if (MOZ_UNLIKELY(isKeygen)) {
+ // Adapted from CNavDTD
+ nsresult rv;
+ nsCOMPtr<nsIFormProcessor> theFormProcessor =
+ do_GetService(kFormProcessorCID, &rv);
+ if (NS_FAILED(rv)) {
+ return newContent;
+ }
+
+ nsTArray<nsString> theContent;
+ nsAutoString theAttribute;
+
+ (void) theFormProcessor->ProvideContent(NS_LITERAL_STRING("select"),
+ theContent,
+ theAttribute);
+
+ newContent->SetAttr(kNameSpaceID_None,
+ nsGkAtoms::moztype,
+ nullptr,
+ theAttribute,
+ false);
+
+ RefPtr<dom::NodeInfo> optionNodeInfo = aNodeInfoManager->GetNodeInfo(
+ nsHtml5Atoms::option, nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE);
+
+ for (uint32_t i = 0; i < theContent.Length(); ++i) {
+ RefPtr<dom::NodeInfo> ni = optionNodeInfo;
+ nsCOMPtr<dom::Element> optionElt =
+ NS_NewHTMLOptionElement(ni.forget(), aFromParser);
+ RefPtr<nsTextNode> optionText = new nsTextNode(aNodeInfoManager);
+ (void) optionText->SetText(theContent[i], false);
+ optionElt->AppendChildTo(optionText, false);
+ newContent->AppendChildTo(optionElt, false);
+ }
+ newContent->DoneAddingChildren(false);
+ }
+
+ if (!aAttributes) {
+ return newContent;
+ }
+
+ SetHTMLElementAttributes(newContent, aName, aAttributes);
+ }
+
+ return newContent;
+}
+
+nsIContent*
+nsHtml5TreeOperation::CreateSVGElement(
+ nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ mozilla::dom::FromParser aFromParser,
+ nsNodeInfoManager* aNodeInfoManager,
+ nsHtml5DocumentBuilder* aBuilder,
+ mozilla::dom::SVGContentCreatorFunction aCreator)
+{
+ nsCOMPtr<nsIContent> newElement;
+ RefPtr<dom::NodeInfo> nodeInfo = aNodeInfoManager->GetNodeInfo(
+ aName, nullptr, kNameSpaceID_SVG, nsIDOMNode::ELEMENT_NODE);
+ MOZ_ASSERT(nodeInfo, "Got null nodeinfo.");
+
+ mozilla::DebugOnly<nsresult> rv =
+ aCreator(getter_AddRefs(newElement), nodeInfo.forget(), aFromParser);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && newElement);
+
+ dom::Element* newContent = newElement->AsElement();
+ aBuilder->HoldElement(newElement.forget());
+
+ if (MOZ_UNLIKELY(aName == nsHtml5Atoms::style)) {
+ nsCOMPtr<nsIStyleSheetLinkingElement> ssle(do_QueryInterface(newContent));
+ if (ssle) {
+ ssle->InitStyleLinkElement(false);
+ ssle->SetEnableUpdates(false);
+ }
+ }
+
+ if (!aAttributes) {
+ return newContent;
+ }
+
+ int32_t len = aAttributes->getLength();
+ for (int32_t i = 0; i < len; i++) {
+ nsHtml5String val = aAttributes->getValueNoBoundsCheck(i);
+ nsIAtom* klass = val.MaybeAsAtom();
+ if (klass) {
+ newContent->SetSingleClassFromParser(klass);
+ } else {
+ // prefix doesn't need regetting. it is always null or a static atom
+ // local name is never null
+ nsCOMPtr<nsIAtom> localName =
+ Reget(aAttributes->getLocalNameNoBoundsCheck(i));
+ nsCOMPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i);
+ int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+
+ nsString value; // Not Auto, because using it to hold nsStringBuffer*
+ val.ToString(value);
+ newContent->SetAttr(nsuri, localName, prefix, value, false);
+ }
+ }
+ return newContent;
+}
+
+nsIContent*
+nsHtml5TreeOperation::CreateMathMLElement(nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ nsNodeInfoManager* aNodeInfoManager,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ nsCOMPtr<dom::Element> newElement;
+ RefPtr<dom::NodeInfo> nodeInfo = aNodeInfoManager->GetNodeInfo(
+ aName, nullptr, kNameSpaceID_MathML, nsIDOMNode::ELEMENT_NODE);
+ NS_ASSERTION(nodeInfo, "Got null nodeinfo.");
+
+ mozilla::DebugOnly<nsresult> rv =
+ NS_NewMathMLElement(getter_AddRefs(newElement), nodeInfo.forget());
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && newElement);
+
+ dom::Element* newContent = newElement;
+ aBuilder->HoldElement(newElement.forget());
+
+ if (!aAttributes) {
+ return newContent;
+ }
+
+ int32_t len = aAttributes->getLength();
+ for (int32_t i = 0; i < len; i++) {
+ nsHtml5String val = aAttributes->getValueNoBoundsCheck(i);
+ nsIAtom* klass = val.MaybeAsAtom();
+ if (klass) {
+ newContent->SetSingleClassFromParser(klass);
+ } else {
+ // prefix doesn't need regetting. it is always null or a static atom
+ // local name is never null
+ nsCOMPtr<nsIAtom> localName =
+ Reget(aAttributes->getLocalNameNoBoundsCheck(i));
+ nsCOMPtr<nsIAtom> prefix = aAttributes->getPrefixNoBoundsCheck(i);
+ int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+
+ nsString value; // Not Auto, because using it to hold nsStringBuffer*
+ val.ToString(value);
+ newContent->SetAttr(nsuri, localName, prefix, value, false);
+ }
+ }
+ return newContent;
+}
+
+void
+nsHtml5TreeOperation::SetFormElement(nsIContent* aNode, nsIContent* aParent)
+{
+ nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(aNode));
+ nsCOMPtr<nsIDOMHTMLImageElement> domImageElement = do_QueryInterface(aNode);
+ // NS_ASSERTION(formControl, "Form-associated element did not implement nsIFormControl.");
+ // TODO: uncomment the above line when <keygen> (bug 101019) is supported by Gecko
+ nsCOMPtr<nsIDOMHTMLFormElement> formElement(do_QueryInterface(aParent));
+ NS_ASSERTION(formElement, "The form element doesn't implement nsIDOMHTMLFormElement.");
+ // avoid crashing on <keygen>
+ if (formControl &&
+ !aNode->HasAttr(kNameSpaceID_None, nsGkAtoms::form)) {
+ formControl->SetForm(formElement);
+ } else if (domImageElement) {
+ RefPtr<dom::HTMLImageElement> imageElement =
+ static_cast<dom::HTMLImageElement*>(domImageElement.get());
+ MOZ_ASSERT(imageElement);
+ imageElement->SetForm(formElement);
+ }
+}
+
+nsresult
+nsHtml5TreeOperation::AppendIsindexPrompt(nsIContent* parent, nsHtml5DocumentBuilder* aBuilder)
+{
+ nsXPIDLString prompt;
+ nsresult rv =
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "IsIndexPromptWithSpace", prompt);
+ uint32_t len = prompt.Length();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!len) {
+ // Don't bother appending a zero-length text node.
+ return NS_OK;
+ }
+ return AppendText(prompt.BeginReading(), len, parent, aBuilder);
+}
+
+nsresult
+nsHtml5TreeOperation::FosterParentText(nsIContent* aStackParent,
+ char16_t* aBuffer,
+ uint32_t aLength,
+ nsIContent* aTable,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ MOZ_ASSERT(aBuilder);
+ MOZ_ASSERT(aBuilder->IsInDocUpdate());
+ nsresult rv = NS_OK;
+ nsIContent* foster = aTable->GetParent();
+
+ if (IsElementOrTemplateContent(foster)) {
+ nsHtml5OtherDocUpdate update(foster->OwnerDoc(),
+ aBuilder->GetDocument());
+
+ uint32_t pos = foster->IndexOf(aTable);
+
+ nsIContent* previousSibling = aTable->GetPreviousSibling();
+ if (previousSibling && previousSibling->IsNodeOfType(nsINode::eTEXT)) {
+ return AppendTextToTextNode(aBuffer,
+ aLength,
+ previousSibling,
+ aBuilder);
+ }
+
+ nsNodeInfoManager* nodeInfoManager = aStackParent->OwnerDoc()->NodeInfoManager();
+ RefPtr<nsTextNode> text = new nsTextNode(nodeInfoManager);
+ NS_ASSERTION(text, "Infallible malloc failed?");
+ rv = text->SetText(aBuffer, aLength, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = foster->InsertChildAt(text, pos, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsNodeUtils::ContentInserted(foster, text, pos);
+ return rv;
+ }
+
+ return AppendText(aBuffer, aLength, aStackParent, aBuilder);
+}
+
+nsresult
+nsHtml5TreeOperation::AppendComment(nsIContent* aParent,
+ char16_t* aBuffer,
+ int32_t aLength,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ nsNodeInfoManager* nodeInfoManager = aParent->OwnerDoc()->NodeInfoManager();
+ RefPtr<dom::Comment> comment = new dom::Comment(nodeInfoManager);
+ NS_ASSERTION(comment, "Infallible malloc failed?");
+ nsresult rv = comment->SetText(aBuffer, aLength, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return Append(comment, aParent, aBuilder);
+}
+
+nsresult
+nsHtml5TreeOperation::AppendCommentToDocument(char16_t* aBuffer,
+ int32_t aLength,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ RefPtr<dom::Comment> comment =
+ new dom::Comment(aBuilder->GetNodeInfoManager());
+ NS_ASSERTION(comment, "Infallible malloc failed?");
+ nsresult rv = comment->SetText(aBuffer, aLength, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return AppendToDocument(comment, aBuilder);
+}
+
+nsresult
+nsHtml5TreeOperation::AppendDoctypeToDocument(nsIAtom* aName,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId,
+ nsHtml5DocumentBuilder* aBuilder)
+{
+ // Adapted from nsXMLContentSink
+ // Create a new doctype node
+ nsCOMPtr<nsIDOMDocumentType> docType;
+ NS_NewDOMDocumentType(getter_AddRefs(docType),
+ aBuilder->GetNodeInfoManager(),
+ aName,
+ aPublicId,
+ aSystemId,
+ NullString());
+ NS_ASSERTION(docType, "Doctype creation failed.");
+ nsCOMPtr<nsIContent> asContent = do_QueryInterface(docType);
+ return AppendToDocument(asContent, aBuilder);
+}
+
+nsIContent*
+nsHtml5TreeOperation::GetDocumentFragmentForTemplate(nsIContent* aNode)
+{
+ dom::HTMLTemplateElement* tempElem =
+ static_cast<dom::HTMLTemplateElement*>(aNode);
+ RefPtr<dom::DocumentFragment> frag = tempElem->Content();
+ return frag;
+}
+
+nsIContent*
+nsHtml5TreeOperation::GetFosterParent(nsIContent* aTable, nsIContent* aStackParent)
+{
+ nsIContent* tableParent = aTable->GetParent();
+ return IsElementOrTemplateContent(tableParent) ? tableParent : aStackParent;
+}
+
+void
+nsHtml5TreeOperation::PreventScriptExecution(nsIContent* aNode)
+{
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aNode);
+ MOZ_ASSERT(sele);
+ sele->PreventExecution();
+}
+
+void
+nsHtml5TreeOperation::DoneAddingChildren(nsIContent* aNode)
+{
+ aNode->DoneAddingChildren(aNode->HasParserNotified());
+}
+
+void
+nsHtml5TreeOperation::DoneCreatingElement(nsIContent* aNode)
+{
+ aNode->DoneCreatingElement();
+}
+
+void
+nsHtml5TreeOperation::SvgLoad(nsIContent* aNode)
+{
+ nsCOMPtr<nsIRunnable> event = new nsHtml5SVGLoadDispatcher(aNode);
+ if (NS_FAILED(NS_DispatchToMainThread(event))) {
+ NS_WARNING("failed to dispatch svg load dispatcher");
+ }
+}
+
+void
+nsHtml5TreeOperation::MarkMalformedIfScript(nsIContent* aNode)
+{
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aNode);
+ if (sele) {
+ // Make sure to serialize this script correctly, for nice round tripping.
+ sele->SetIsMalformed();
+ }
+}
+
+nsresult
+nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder,
+ nsIContent** aScriptElement)
+{
+ switch(mOpCode) {
+ case eTreeOpUninitialized: {
+ MOZ_CRASH("eTreeOpUninitialized");
+ }
+ case eTreeOpAppend: {
+ nsIContent* node = *(mOne.node);
+ nsIContent* parent = *(mTwo.node);
+ return Append(node, parent, aBuilder);
+ }
+ case eTreeOpDetach: {
+ nsIContent* node = *(mOne.node);
+ Detach(node, aBuilder);
+ return NS_OK;
+ }
+ case eTreeOpAppendChildrenToNewParent: {
+ nsCOMPtr<nsIContent> node = *(mOne.node);
+ nsIContent* parent = *(mTwo.node);
+ return AppendChildrenToNewParent(node, parent, aBuilder);
+ }
+ case eTreeOpFosterParent: {
+ nsIContent* node = *(mOne.node);
+ nsIContent* parent = *(mTwo.node);
+ nsIContent* table = *(mThree.node);
+ return FosterParent(node, parent, table, aBuilder);
+ }
+ case eTreeOpAppendToDocument: {
+ nsIContent* node = *(mOne.node);
+ return AppendToDocument(node, aBuilder);
+ }
+ case eTreeOpAddAttributes: {
+ nsIContent* node = *(mOne.node);
+ nsHtml5HtmlAttributes* attributes = mTwo.attributes;
+ return AddAttributes(node, attributes, aBuilder);
+ }
+ case eTreeOpDocumentMode: {
+ aBuilder->SetDocumentMode(mOne.mode);
+ return NS_OK;
+ }
+ case eTreeOpCreateHTMLElementNetwork:
+ case eTreeOpCreateHTMLElementNotNetwork: {
+ nsIContent** target = mOne.node;
+ mozilla::dom::HTMLContentCreatorFunction creator = mFour.htmlCreator;
+ nsCOMPtr<nsIAtom> name = Reget(mTwo.atom);
+ nsHtml5HtmlAttributes* attributes = mThree.attributes;
+ nsIContent* intendedParent = mFive.node ? *(mFive.node) : nullptr;
+
+ // intendedParent == nullptr is a special case where the
+ // intended parent is the document.
+ nsNodeInfoManager* nodeInfoManager =
+ intendedParent ? intendedParent->OwnerDoc()->NodeInfoManager()
+ : aBuilder->GetNodeInfoManager();
+
+ *target = CreateHTMLElement(name,
+ attributes,
+ mOpCode == eTreeOpCreateHTMLElementNetwork
+ ? dom::FROM_PARSER_NETWORK
+ : dom::FROM_PARSER_DOCUMENT_WRITE,
+ nodeInfoManager,
+ aBuilder,
+ creator);
+ return NS_OK;
+ }
+ case eTreeOpCreateSVGElementNetwork:
+ case eTreeOpCreateSVGElementNotNetwork: {
+ nsIContent** target = mOne.node;
+ mozilla::dom::SVGContentCreatorFunction creator = mFour.svgCreator;
+ nsCOMPtr<nsIAtom> name = Reget(mTwo.atom);
+ nsHtml5HtmlAttributes* attributes = mThree.attributes;
+ nsIContent* intendedParent = mFive.node ? *(mFive.node) : nullptr;
+
+ // intendedParent == nullptr is a special case where the
+ // intended parent is the document.
+ nsNodeInfoManager* nodeInfoManager =
+ intendedParent ? intendedParent->OwnerDoc()->NodeInfoManager()
+ : aBuilder->GetNodeInfoManager();
+
+ *target = CreateSVGElement(name,
+ attributes,
+ mOpCode == eTreeOpCreateSVGElementNetwork
+ ? dom::FROM_PARSER_NETWORK
+ : dom::FROM_PARSER_DOCUMENT_WRITE,
+ nodeInfoManager,
+ aBuilder,
+ creator);
+ return NS_OK;
+ }
+ case eTreeOpCreateMathMLElement: {
+ nsIContent** target = mOne.node;
+ nsCOMPtr<nsIAtom> name = Reget(mTwo.atom);
+ nsHtml5HtmlAttributes* attributes = mThree.attributes;
+ nsIContent* intendedParent = mFive.node ? *(mFive.node) : nullptr;
+
+ // intendedParent == nullptr is a special case where the
+ // intended parent is the document.
+ nsNodeInfoManager* nodeInfoManager = intendedParent ?
+ intendedParent->OwnerDoc()->NodeInfoManager() :
+ aBuilder->GetNodeInfoManager();
+
+ *target =
+ CreateMathMLElement(name, attributes, nodeInfoManager, aBuilder);
+ return NS_OK;
+ }
+ case eTreeOpSetFormElement: {
+ nsIContent* node = *(mOne.node);
+ nsIContent* parent = *(mTwo.node);
+ SetFormElement(node, parent);
+ return NS_OK;
+ }
+ case eTreeOpAppendText: {
+ nsIContent* parent = *mOne.node;
+ char16_t* buffer = mTwo.unicharPtr;
+ uint32_t length = mFour.integer;
+ return AppendText(buffer, length, parent, aBuilder);
+ }
+ case eTreeOpAppendIsindexPrompt: {
+ nsIContent* parent = *mOne.node;
+ return AppendIsindexPrompt(parent, aBuilder);
+ }
+ case eTreeOpFosterParentText: {
+ nsIContent* stackParent = *mOne.node;
+ char16_t* buffer = mTwo.unicharPtr;
+ uint32_t length = mFour.integer;
+ nsIContent* table = *mThree.node;
+ return FosterParentText(stackParent, buffer, length, table, aBuilder);
+ }
+ case eTreeOpAppendComment: {
+ nsIContent* parent = *mOne.node;
+ char16_t* buffer = mTwo.unicharPtr;
+ int32_t length = mFour.integer;
+ return AppendComment(parent, buffer, length, aBuilder);
+ }
+ case eTreeOpAppendCommentToDocument: {
+ char16_t* buffer = mTwo.unicharPtr;
+ int32_t length = mFour.integer;
+ return AppendCommentToDocument(buffer, length, aBuilder);
+ }
+ case eTreeOpAppendDoctypeToDocument: {
+ nsCOMPtr<nsIAtom> name = Reget(mOne.atom);
+ nsHtml5TreeOperationStringPair* pair = mTwo.stringPair;
+ nsString publicId;
+ nsString systemId;
+ pair->Get(publicId, systemId);
+ return AppendDoctypeToDocument(name, publicId, systemId, aBuilder);
+ }
+ case eTreeOpGetDocumentFragmentForTemplate: {
+ nsIContent* node = *(mOne.node);
+ *mTwo.node = GetDocumentFragmentForTemplate(node);
+ return NS_OK;
+ }
+ case eTreeOpGetFosterParent: {
+ nsIContent* table = *(mOne.node);
+ nsIContent* stackParent = *(mTwo.node);
+ nsIContent* fosterParent = GetFosterParent(table, stackParent);
+ *mThree.node = fosterParent;
+ return NS_OK;
+ }
+ case eTreeOpMarkAsBroken: {
+ return mOne.result;
+ }
+ case eTreeOpRunScript: {
+ nsIContent* node = *(mOne.node);
+ nsAHtml5TreeBuilderState* snapshot = mTwo.state;
+ if (snapshot) {
+ aBuilder->InitializeDocWriteParserState(snapshot, mFour.integer);
+ }
+ *aScriptElement = node;
+ return NS_OK;
+ }
+ case eTreeOpRunScriptAsyncDefer: {
+ nsIContent* node = *(mOne.node);
+ aBuilder->RunScript(node);
+ return NS_OK;
+ }
+ case eTreeOpPreventScriptExecution: {
+ nsIContent* node = *(mOne.node);
+ PreventScriptExecution(node);
+ return NS_OK;
+ }
+ case eTreeOpDoneAddingChildren: {
+ nsIContent* node = *(mOne.node);
+ node->DoneAddingChildren(node->HasParserNotified());
+ return NS_OK;
+ }
+ case eTreeOpDoneCreatingElement: {
+ nsIContent* node = *(mOne.node);
+ DoneCreatingElement(node);
+ return NS_OK;
+ }
+ case eTreeOpSetDocumentCharset: {
+ char* str = mOne.charPtr;
+ int32_t charsetSource = mFour.integer;
+ nsDependentCString dependentString(str);
+ aBuilder->SetDocumentCharsetAndSource(dependentString, charsetSource);
+ return NS_OK;
+ }
+ case eTreeOpNeedsCharsetSwitchTo: {
+ char* str = mOne.charPtr;
+ int32_t charsetSource = mFour.integer;
+ int32_t lineNumber = mTwo.integer;
+ aBuilder->NeedsCharsetSwitchTo(str, charsetSource, (uint32_t)lineNumber);
+ return NS_OK;
+ }
+ case eTreeOpUpdateStyleSheet: {
+ nsIContent* node = *(mOne.node);
+ aBuilder->UpdateStyleSheet(node);
+ return NS_OK;
+ }
+ case eTreeOpProcessMeta: {
+ nsIContent* node = *(mOne.node);
+ return aBuilder->ProcessMETATag(node);
+ }
+ case eTreeOpProcessOfflineManifest: {
+ char16_t* str = mOne.unicharPtr;
+ nsDependentString dependentString(str);
+ aBuilder->ProcessOfflineManifest(dependentString);
+ return NS_OK;
+ }
+ case eTreeOpMarkMalformedIfScript: {
+ nsIContent* node = *(mOne.node);
+ MarkMalformedIfScript(node);
+ return NS_OK;
+ }
+ case eTreeOpStreamEnded: {
+ aBuilder->DidBuildModel(false); // this causes a notifications flush anyway
+ return NS_OK;
+ }
+ case eTreeOpSetStyleLineNumber: {
+ nsIContent* node = *(mOne.node);
+ nsCOMPtr<nsIStyleSheetLinkingElement> ssle = do_QueryInterface(node);
+ NS_ASSERTION(ssle, "Node didn't QI to style.");
+ ssle->SetLineNumber(mFour.integer);
+ return NS_OK;
+ }
+ case eTreeOpSetScriptLineNumberAndFreeze: {
+ nsIContent* node = *(mOne.node);
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(node);
+ NS_ASSERTION(sele, "Node didn't QI to script.");
+ sele->SetScriptLineNumber(mFour.integer);
+ sele->FreezeExecutionAttrs(node->OwnerDoc());
+ return NS_OK;
+ }
+ case eTreeOpSvgLoad: {
+ nsIContent* node = *(mOne.node);
+ SvgLoad(node);
+ return NS_OK;
+ }
+ case eTreeOpMaybeComplainAboutCharset: {
+ char* msgId = mOne.charPtr;
+ bool error = mTwo.integer;
+ int32_t lineNumber = mThree.integer;
+ aBuilder->MaybeComplainAboutCharset(msgId, error, (uint32_t)lineNumber);
+ return NS_OK;
+ }
+ case eTreeOpAddClass: {
+ nsIContent* node = *(mOne.node);
+ char16_t* str = mTwo.unicharPtr;
+ nsDependentString depStr(str);
+ // See viewsource.css for the possible classes
+ nsAutoString klass;
+ node->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, klass);
+ if (!klass.IsEmpty()) {
+ klass.Append(' ');
+ klass.Append(depStr);
+ node->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, klass, true);
+ } else {
+ node->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, depStr, true);
+ }
+ return NS_OK;
+ }
+ case eTreeOpAddViewSourceHref: {
+ nsIContent* node = *mOne.node;
+ char16_t* buffer = mTwo.unicharPtr;
+ int32_t length = mFour.integer;
+
+ nsDependentString relative(buffer, length);
+
+ nsIDocument* doc = aBuilder->GetDocument();
+
+ const nsCString& charset = doc->GetDocumentCharacterSet();
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri),
+ relative,
+ charset.get(),
+ aBuilder->GetViewSourceBaseURI());
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ // Reuse the fix for bug 467852
+ // URLs that execute script (e.g. "javascript:" URLs) should just be
+ // ignored. There's nothing reasonable we can do with them, and allowing
+ // them to execute in the context of the view-source window presents a
+ // security risk. Just return the empty string in this case.
+ bool openingExecutesScript = false;
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT,
+ &openingExecutesScript);
+ if (NS_FAILED(rv) || openingExecutesScript) {
+ return NS_OK;
+ }
+
+ nsAutoCString viewSourceUrl;
+
+ // URLs that return data (e.g. "http:" URLs) should be prefixed with
+ // "view-source:". URLs that don't return data should just be returned
+ // undecorated.
+ bool doesNotReturnData = false;
+ rv = NS_URIChainHasFlags(uri,
+ nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
+ &doesNotReturnData);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ if (!doesNotReturnData) {
+ viewSourceUrl.AssignLiteral("view-source:");
+ }
+
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ viewSourceUrl.Append(spec);
+
+ nsAutoString utf16;
+ CopyUTF8toUTF16(viewSourceUrl, utf16);
+
+ node->SetAttr(kNameSpaceID_None, nsGkAtoms::href, utf16, true);
+ return NS_OK;
+ }
+ case eTreeOpAddViewSourceBase: {
+ char16_t* buffer = mTwo.unicharPtr;
+ int32_t length = mFour.integer;
+ nsDependentString baseUrl(buffer, length);
+ aBuilder->AddBase(baseUrl);
+ return NS_OK;
+ }
+ case eTreeOpAddError: {
+ nsIContent* node = *(mOne.node);
+ char* msgId = mTwo.charPtr;
+ nsCOMPtr<nsIAtom> atom = Reget(mThree.atom);
+ nsCOMPtr<nsIAtom> otherAtom = Reget(mFour.atom);
+ // See viewsource.css for the possible classes in addition to "error".
+ nsAutoString klass;
+ node->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, klass);
+ if (!klass.IsEmpty()) {
+ klass.AppendLiteral(" error");
+ node->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, klass, true);
+ } else {
+ node->SetAttr(kNameSpaceID_None,
+ nsGkAtoms::_class,
+ NS_LITERAL_STRING("error"),
+ true);
+ }
+
+ nsresult rv;
+ nsXPIDLString message;
+ if (otherAtom) {
+ const char16_t* params[] = { atom->GetUTF16String(),
+ otherAtom->GetUTF16String() };
+ rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eHTMLPARSER_PROPERTIES, msgId, params, message);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ } else if (atom) {
+ const char16_t* params[] = { atom->GetUTF16String() };
+ rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eHTMLPARSER_PROPERTIES, msgId, params, message);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ } else {
+ rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eHTMLPARSER_PROPERTIES, msgId, message);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ }
+
+ nsAutoString title;
+ node->GetAttr(kNameSpaceID_None, nsGkAtoms::title, title);
+ if (!title.IsEmpty()) {
+ title.Append('\n');
+ title.Append(message);
+ node->SetAttr(kNameSpaceID_None, nsGkAtoms::title, title, true);
+ } else {
+ node->SetAttr(kNameSpaceID_None, nsGkAtoms::title, message, true);
+ }
+ return rv;
+ }
+ case eTreeOpAddLineNumberId: {
+ nsIContent* node = *(mOne.node);
+ int32_t lineNumber = mFour.integer;
+ nsAutoString val(NS_LITERAL_STRING("line"));
+ val.AppendInt(lineNumber);
+ node->SetAttr(kNameSpaceID_None, nsGkAtoms::id, val, true);
+ return NS_OK;
+ }
+ case eTreeOpStartLayout: {
+ aBuilder->StartLayout(); // this causes a notification flush anyway
+ return NS_OK;
+ }
+ default: {
+ MOZ_CRASH("Bogus tree op");
+ }
+ }
+ return NS_OK; // keep compiler happy
+}
diff --git a/components/htmlfive/nsHtml5TreeOperation.h b/components/htmlfive/nsHtml5TreeOperation.h
new file mode 100644
index 000000000..db6cc6397
--- /dev/null
+++ b/components/htmlfive/nsHtml5TreeOperation.h
@@ -0,0 +1,546 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsHtml5TreeOperation_h
+#define nsHtml5TreeOperation_h
+
+#include "nsHtml5DocumentMode.h"
+#include "nsHtml5HtmlAttributes.h"
+#include "nsXPCOMStrings.h"
+#include "mozilla/dom/FromParser.h"
+
+class nsIContent;
+class nsHtml5TreeOpExecutor;
+class nsHtml5DocumentBuilder;
+
+enum eHtml5TreeOperation
+{
+ eTreeOpUninitialized,
+ // main HTML5 ops
+ eTreeOpAppend,
+ eTreeOpDetach,
+ eTreeOpAppendChildrenToNewParent,
+ eTreeOpFosterParent,
+ eTreeOpAppendToDocument,
+ eTreeOpAddAttributes,
+ eTreeOpDocumentMode,
+ eTreeOpCreateHTMLElementNetwork,
+ eTreeOpCreateHTMLElementNotNetwork,
+ eTreeOpCreateSVGElementNetwork,
+ eTreeOpCreateSVGElementNotNetwork,
+ eTreeOpCreateMathMLElement,
+ eTreeOpSetFormElement,
+ eTreeOpAppendText,
+ eTreeOpAppendIsindexPrompt,
+ eTreeOpFosterParentText,
+ eTreeOpAppendComment,
+ eTreeOpAppendCommentToDocument,
+ eTreeOpAppendDoctypeToDocument,
+ eTreeOpGetDocumentFragmentForTemplate,
+ eTreeOpGetFosterParent,
+ // Gecko-specific on-pop ops
+ eTreeOpMarkAsBroken,
+ eTreeOpRunScript,
+ eTreeOpRunScriptAsyncDefer,
+ eTreeOpPreventScriptExecution,
+ eTreeOpDoneAddingChildren,
+ eTreeOpDoneCreatingElement,
+ eTreeOpSetDocumentCharset,
+ eTreeOpNeedsCharsetSwitchTo,
+ eTreeOpUpdateStyleSheet,
+ eTreeOpProcessMeta,
+ eTreeOpProcessOfflineManifest,
+ eTreeOpMarkMalformedIfScript,
+ eTreeOpStreamEnded,
+ eTreeOpSetStyleLineNumber,
+ eTreeOpSetScriptLineNumberAndFreeze,
+ eTreeOpSvgLoad,
+ eTreeOpMaybeComplainAboutCharset,
+ eTreeOpAddClass,
+ eTreeOpAddViewSourceHref,
+ eTreeOpAddViewSourceBase,
+ eTreeOpAddError,
+ eTreeOpAddLineNumberId,
+ eTreeOpStartLayout
+};
+
+class nsHtml5TreeOperationStringPair {
+ private:
+ nsString mPublicId;
+ nsString mSystemId;
+ public:
+ nsHtml5TreeOperationStringPair(const nsAString& aPublicId,
+ const nsAString& aSystemId)
+ : mPublicId(aPublicId)
+ , mSystemId(aSystemId)
+ {
+ MOZ_COUNT_CTOR(nsHtml5TreeOperationStringPair);
+ }
+
+ ~nsHtml5TreeOperationStringPair()
+ {
+ MOZ_COUNT_DTOR(nsHtml5TreeOperationStringPair);
+ }
+
+ inline void Get(nsAString& aPublicId, nsAString& aSystemId)
+ {
+ aPublicId.Assign(mPublicId);
+ aSystemId.Assign(mSystemId);
+ }
+};
+
+class nsHtml5TreeOperation {
+
+ public:
+ /**
+ * Atom is used inside the parser core are either static atoms that are
+ * the same as Gecko-wide static atoms or they are dynamic atoms scoped by
+ * both thread and parser to a particular nsHtml5AtomTable. In order to
+ * such scoped atoms coming into contact with the rest of Gecko, atoms
+ * that are about to exit the parser must go through this method which
+ * reobtains dynamic atoms from the Gecko-global atom table.
+ *
+ * @param aAtom a potentially parser-scoped atom
+ * @return an nsIAtom that's pointer comparable on the main thread with
+ * other not-parser atoms.
+ */
+ static inline already_AddRefed<nsIAtom> Reget(nsIAtom* aAtom)
+ {
+ if (!aAtom || aAtom->IsStaticAtom()) {
+ return dont_AddRef(aAtom);
+ }
+ nsAutoString str;
+ aAtom->ToString(str);
+ return NS_AtomizeMainThread(str);
+ }
+
+ static nsresult AppendTextToTextNode(const char16_t* aBuffer,
+ uint32_t aLength,
+ nsIContent* aTextNode,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static nsresult AppendText(const char16_t* aBuffer,
+ uint32_t aLength,
+ nsIContent* aParent,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static nsresult Append(nsIContent* aNode,
+ nsIContent* aParent,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static nsresult AppendToDocument(nsIContent* aNode,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static void Detach(nsIContent* aNode, nsHtml5DocumentBuilder* aBuilder);
+
+ static nsresult AppendChildrenToNewParent(nsIContent* aNode,
+ nsIContent* aParent,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static nsresult FosterParent(nsIContent* aNode,
+ nsIContent* aParent,
+ nsIContent* aTable,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static void SetHTMLElementAttributes(mozilla::dom::Element* aElement,
+ nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes);
+
+ static nsresult AddAttributes(nsIContent* aNode,
+ nsHtml5HtmlAttributes* aAttributes,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static nsIContent* CreateHTMLElement(
+ nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ mozilla::dom::FromParser aFromParser,
+ nsNodeInfoManager* aNodeInfoManager,
+ nsHtml5DocumentBuilder* aBuilder,
+ mozilla::dom::HTMLContentCreatorFunction aCreator);
+
+ static nsIContent* CreateSVGElement(
+ nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ mozilla::dom::FromParser aFromParser,
+ nsNodeInfoManager* aNodeInfoManager,
+ nsHtml5DocumentBuilder* aBuilder,
+ mozilla::dom::SVGContentCreatorFunction aCreator);
+
+ static nsIContent* CreateMathMLElement(nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ nsNodeInfoManager* aNodeInfoManager,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static void SetFormElement(nsIContent* aNode, nsIContent* aParent);
+
+ static nsresult AppendIsindexPrompt(nsIContent* parent,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static nsresult FosterParentText(nsIContent* aStackParent,
+ char16_t* aBuffer,
+ uint32_t aLength,
+ nsIContent* aTable,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static nsresult AppendComment(nsIContent* aParent,
+ char16_t* aBuffer,
+ int32_t aLength,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static nsresult AppendCommentToDocument(char16_t* aBuffer,
+ int32_t aLength,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static nsresult AppendDoctypeToDocument(nsIAtom* aName,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId,
+ nsHtml5DocumentBuilder* aBuilder);
+
+ static nsIContent* GetDocumentFragmentForTemplate(nsIContent* aNode);
+
+ static nsIContent* GetFosterParent(nsIContent* aTable, nsIContent* aStackParent);
+
+ static void PreventScriptExecution(nsIContent* aNode);
+
+ static void DoneAddingChildren(nsIContent* aNode);
+
+ static void DoneCreatingElement(nsIContent* aNode);
+
+ static void SvgLoad(nsIContent* aNode);
+
+ static void MarkMalformedIfScript(nsIContent* aNode);
+
+ nsHtml5TreeOperation();
+
+ ~nsHtml5TreeOperation();
+
+ inline void Init(eHtml5TreeOperation aOpCode)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ mOpCode = aOpCode;
+ }
+
+ inline void Init(eHtml5TreeOperation aOpCode, nsIContentHandle* aNode)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aNode, "Initialized tree op with null node.");
+ mOpCode = aOpCode;
+ mOne.node = static_cast<nsIContent**>(aNode);
+ }
+
+ inline void Init(eHtml5TreeOperation aOpCode,
+ nsIContentHandle* aNode,
+ nsIContentHandle* aParent)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aNode, "Initialized tree op with null node.");
+ NS_PRECONDITION(aParent, "Initialized tree op with null parent.");
+ mOpCode = aOpCode;
+ mOne.node = static_cast<nsIContent**>(aNode);
+ mTwo.node = static_cast<nsIContent**>(aParent);
+ }
+
+ inline void Init(eHtml5TreeOperation aOpCode,
+ const nsACString& aString,
+ int32_t aInt32)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+
+ int32_t len = aString.Length();
+ char* str = new char[len + 1];
+ const char* start = aString.BeginReading();
+ for (int32_t i = 0; i < len; ++i) {
+ str[i] = start[i];
+ }
+ str[len] = '\0';
+
+ mOpCode = aOpCode;
+ mOne.charPtr = str;
+ mFour.integer = aInt32;
+ }
+
+ inline void Init(eHtml5TreeOperation aOpCode,
+ const nsACString& aString,
+ int32_t aInt32,
+ int32_t aLineNumber)
+ {
+ Init(aOpCode, aString, aInt32);
+ mTwo.integer = aLineNumber;
+ }
+
+ inline void Init(eHtml5TreeOperation aOpCode,
+ nsIContentHandle* aNode,
+ nsIContentHandle* aParent,
+ nsIContentHandle* aTable)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aNode, "Initialized tree op with null node.");
+ NS_PRECONDITION(aParent, "Initialized tree op with null parent.");
+ NS_PRECONDITION(aTable, "Initialized tree op with null table.");
+ mOpCode = aOpCode;
+ mOne.node = static_cast<nsIContent**>(aNode);
+ mTwo.node = static_cast<nsIContent**>(aParent);
+ mThree.node = static_cast<nsIContent**>(aTable);
+ }
+
+ inline void Init(nsHtml5DocumentMode aMode)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ mOpCode = eTreeOpDocumentMode;
+ mOne.mode = aMode;
+ }
+
+ inline void InitScript(nsIContentHandle* aNode)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aNode, "Initialized tree op with null node.");
+ mOpCode = eTreeOpRunScript;
+ mOne.node = static_cast<nsIContent**>(aNode);
+ mTwo.state = nullptr;
+ }
+
+ inline void Init(int32_t aNamespace,
+ nsIAtom* aName,
+ nsHtml5HtmlAttributes* aAttributes,
+ nsIContentHandle* aTarget,
+ nsIContentHandle* aIntendedParent,
+ bool aFromNetwork,
+ nsHtml5ContentCreatorFunction aCreator)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aName, "Initialized tree op with null name.");
+ NS_PRECONDITION(aTarget, "Initialized tree op with null target node.");
+ if (aNamespace == kNameSpaceID_XHTML) {
+ mOpCode = aFromNetwork ? eTreeOpCreateHTMLElementNetwork
+ : eTreeOpCreateHTMLElementNotNetwork;
+ mFour.htmlCreator = aCreator.html;
+ } else if (aNamespace == kNameSpaceID_SVG) {
+ mOpCode = aFromNetwork ? eTreeOpCreateSVGElementNetwork
+ : eTreeOpCreateSVGElementNotNetwork;
+ mFour.svgCreator = aCreator.svg;
+ } else {
+ MOZ_ASSERT(aNamespace == kNameSpaceID_MathML);
+ mOpCode = eTreeOpCreateMathMLElement;
+ }
+ mFive.node = static_cast<nsIContent**>(aIntendedParent);
+ mOne.node = static_cast<nsIContent**>(aTarget);
+ mTwo.atom = aName;
+ if (aAttributes == nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES) {
+ mThree.attributes = nullptr;
+ } else {
+ mThree.attributes = aAttributes;
+ }
+ }
+
+ inline void Init(eHtml5TreeOperation aOpCode,
+ char16_t* aBuffer,
+ int32_t aLength,
+ nsIContentHandle* aStackParent,
+ nsIContentHandle* aTable)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aBuffer, "Initialized tree op with null buffer.");
+ mOpCode = aOpCode;
+ mOne.node = static_cast<nsIContent**>(aStackParent);
+ mTwo.unicharPtr = aBuffer;
+ mThree.node = static_cast<nsIContent**>(aTable);
+ mFour.integer = aLength;
+ }
+
+ inline void Init(eHtml5TreeOperation aOpCode,
+ char16_t* aBuffer,
+ int32_t aLength,
+ nsIContentHandle* aParent)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aBuffer, "Initialized tree op with null buffer.");
+ mOpCode = aOpCode;
+ mOne.node = static_cast<nsIContent**>(aParent);
+ mTwo.unicharPtr = aBuffer;
+ mFour.integer = aLength;
+ }
+
+ inline void Init(eHtml5TreeOperation aOpCode,
+ char16_t* aBuffer,
+ int32_t aLength)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aBuffer, "Initialized tree op with null buffer.");
+ mOpCode = aOpCode;
+ mTwo.unicharPtr = aBuffer;
+ mFour.integer = aLength;
+ }
+
+ inline void Init(nsIContentHandle* aElement,
+ nsHtml5HtmlAttributes* aAttributes)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aElement, "Initialized tree op with null element.");
+ mOpCode = eTreeOpAddAttributes;
+ mOne.node = static_cast<nsIContent**>(aElement);
+ mTwo.attributes = aAttributes;
+ }
+
+ inline void Init(nsIAtom* aName,
+ const nsAString& aPublicId,
+ const nsAString& aSystemId)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ mOpCode = eTreeOpAppendDoctypeToDocument;
+ mOne.atom = aName;
+ mTwo.stringPair = new nsHtml5TreeOperationStringPair(aPublicId, aSystemId);
+ }
+
+ inline void Init(nsIContentHandle* aElement,
+ const char* aMsgId,
+ nsIAtom* aAtom,
+ nsIAtom* aOtherAtom)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ mOpCode = eTreeOpAddError;
+ mOne.node = static_cast<nsIContent**>(aElement);
+ mTwo.charPtr = (char*)aMsgId;
+ mThree.atom = aAtom;
+ mFour.atom = aOtherAtom;
+ }
+
+ inline void Init(nsIContentHandle* aElement,
+ const char* aMsgId,
+ nsIAtom* aAtom)
+ {
+ Init(aElement, aMsgId, aAtom, nullptr);
+ }
+
+ inline void Init(nsIContentHandle* aElement,
+ const char* aMsgId)
+ {
+ Init(aElement, aMsgId, nullptr, nullptr);
+ }
+
+ inline void Init(const char* aMsgId,
+ bool aError,
+ int32_t aLineNumber)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ mOpCode = eTreeOpMaybeComplainAboutCharset;
+ mOne.charPtr = const_cast<char*>(aMsgId);
+ mTwo.integer = aError;
+ mThree.integer = aLineNumber;
+ }
+
+ inline void Init(eHtml5TreeOperation aOpCode, const nsAString& aString)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+
+ char16_t* str = NS_StringCloneData(aString);
+ mOpCode = aOpCode;
+ mOne.unicharPtr = str;
+ }
+
+ inline void Init(eHtml5TreeOperation aOpCode,
+ nsIContentHandle* aNode,
+ int32_t aInt)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aNode, "Initialized tree op with null node.");
+ mOpCode = aOpCode;
+ mOne.node = static_cast<nsIContent**>(aNode);
+ mFour.integer = aInt;
+ }
+
+ inline void Init(nsresult aRv)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(NS_FAILED(aRv), "Initialized tree op with non-failure.");
+ mOpCode = eTreeOpMarkAsBroken;
+ mOne.result = aRv;
+ }
+
+ inline void InitAddClass(nsIContentHandle* aNode, const char16_t* aClass)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aNode, "Initialized tree op with null node.");
+ NS_PRECONDITION(aClass, "Initialized tree op with null string.");
+ // aClass must be a literal string that does not need freeing
+ mOpCode = eTreeOpAddClass;
+ mOne.node = static_cast<nsIContent**>(aNode);
+ mTwo.unicharPtr = (char16_t*)aClass;
+ }
+
+ inline void InitAddLineNumberId(nsIContentHandle* aNode,
+ const int32_t aLineNumber)
+ {
+ NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
+ "Op code must be uninitialized when initializing.");
+ NS_PRECONDITION(aNode, "Initialized tree op with null node.");
+ NS_PRECONDITION(aLineNumber > 0, "Initialized tree op with line number.");
+ // aClass must be a literal string that does not need freeing
+ mOpCode = eTreeOpAddLineNumberId;
+ mOne.node = static_cast<nsIContent**>(aNode);
+ mFour.integer = aLineNumber;
+ }
+
+ inline bool IsRunScript()
+ {
+ return mOpCode == eTreeOpRunScript;
+ }
+
+ inline bool IsMarkAsBroken()
+ {
+ return mOpCode == eTreeOpMarkAsBroken;
+ }
+
+ inline void SetSnapshot(nsAHtml5TreeBuilderState* aSnapshot, int32_t aLine)
+ {
+ NS_ASSERTION(IsRunScript(),
+ "Setting a snapshot for a tree operation other than eTreeOpRunScript!");
+ NS_PRECONDITION(aSnapshot, "Initialized tree op with null snapshot.");
+ mTwo.state = aSnapshot;
+ mFour.integer = aLine;
+ }
+
+ nsresult Perform(nsHtml5TreeOpExecutor* aBuilder,
+ nsIContent** aScriptElement);
+
+ private:
+ // possible optimization:
+ // Make the queue take items the size of pointer and make the op code
+ // decide how many operands it dequeues after it.
+ eHtml5TreeOperation mOpCode;
+ union {
+ nsIContent** node;
+ nsIAtom* atom;
+ nsHtml5HtmlAttributes* attributes;
+ nsHtml5DocumentMode mode;
+ char16_t* unicharPtr;
+ char* charPtr;
+ nsHtml5TreeOperationStringPair* stringPair;
+ nsAHtml5TreeBuilderState* state;
+ int32_t integer;
+ nsresult result;
+ mozilla::dom::HTMLContentCreatorFunction htmlCreator;
+ mozilla::dom::SVGContentCreatorFunction svgCreator;
+ } mOne, mTwo, mThree, mFour, mFive;
+};
+
+#endif // nsHtml5TreeOperation_h
diff --git a/components/htmlfive/nsHtml5UTF16Buffer.cpp b/components/htmlfive/nsHtml5UTF16Buffer.cpp
new file mode 100644
index 000000000..40624a74a
--- /dev/null
+++ b/components/htmlfive/nsHtml5UTF16Buffer.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit UTF16Buffer.java instead and regenerate.
+ */
+
+#define nsHtml5UTF16Buffer_cpp__
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+#include "nsHtml5AttributeName.h"
+#include "nsHtml5ElementName.h"
+#include "nsHtml5Tokenizer.h"
+#include "nsHtml5TreeBuilder.h"
+#include "nsHtml5MetaScanner.h"
+#include "nsHtml5StackNode.h"
+#include "nsHtml5StateSnapshot.h"
+#include "nsHtml5Portability.h"
+
+#include "nsHtml5UTF16Buffer.h"
+
+int32_t
+nsHtml5UTF16Buffer::getStart()
+{
+ return start;
+}
+
+void
+nsHtml5UTF16Buffer::setStart(int32_t start)
+{
+ this->start = start;
+}
+
+char16_t*
+nsHtml5UTF16Buffer::getBuffer()
+{
+ return buffer;
+}
+
+int32_t
+nsHtml5UTF16Buffer::getEnd()
+{
+ return end;
+}
+
+bool
+nsHtml5UTF16Buffer::hasMore()
+{
+ return start < end;
+}
+
+int32_t
+nsHtml5UTF16Buffer::getLength()
+{
+ return end - start;
+}
+
+void
+nsHtml5UTF16Buffer::adjust(bool lastWasCR)
+{
+ if (lastWasCR && buffer[start] == '\n') {
+ start++;
+ }
+}
+
+void
+nsHtml5UTF16Buffer::setEnd(int32_t end)
+{
+ this->end = end;
+}
+
+void
+nsHtml5UTF16Buffer::initializeStatics()
+{
+}
+
+void
+nsHtml5UTF16Buffer::releaseStatics()
+{
+}
+
+
+#include "nsHtml5UTF16BufferCppSupplement.h"
+
diff --git a/components/htmlfive/nsHtml5UTF16Buffer.h b/components/htmlfive/nsHtml5UTF16Buffer.h
new file mode 100644
index 000000000..237fdf2e0
--- /dev/null
+++ b/components/htmlfive/nsHtml5UTF16Buffer.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2008-2010 Mozilla Foundation
+ * Copyright (c) 2018-2020 Moonchild Productions
+ * Copyright (c) 2020 Binary Outcast
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * THIS IS A GENERATED FILE. PLEASE DO NOT EDIT.
+ * Please edit UTF16Buffer.java instead and regenerate.
+ */
+
+#ifndef nsHtml5UTF16Buffer_h
+#define nsHtml5UTF16Buffer_h
+
+#include "nsIAtom.h"
+#include "nsHtml5AtomTable.h"
+#include "nsHtml5String.h"
+#include "nsNameSpaceManager.h"
+#include "nsIContent.h"
+#include "nsTraceRefcnt.h"
+#include "jArray.h"
+#include "nsHtml5ArrayCopy.h"
+#include "nsAHtml5TreeBuilderState.h"
+#include "nsHtml5Atoms.h"
+#include "nsHtml5ByteReadable.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsHtml5Macros.h"
+#include "nsIContentHandle.h"
+#include "nsHtml5Portability.h"
+#include "nsHtml5ContentCreatorFunction.h"
+
+class nsHtml5StreamParser;
+
+class nsHtml5AttributeName;
+class nsHtml5ElementName;
+class nsHtml5Tokenizer;
+class nsHtml5TreeBuilder;
+class nsHtml5MetaScanner;
+class nsHtml5StateSnapshot;
+class nsHtml5Portability;
+
+
+class nsHtml5UTF16Buffer
+{
+ private:
+ char16_t* buffer;
+ int32_t start;
+ int32_t end;
+ public:
+ int32_t getStart();
+ void setStart(int32_t start);
+ char16_t* getBuffer();
+ int32_t getEnd();
+ bool hasMore();
+ int32_t getLength();
+ void adjust(bool lastWasCR);
+ void setEnd(int32_t end);
+ static void initializeStatics();
+ static void releaseStatics();
+
+#include "nsHtml5UTF16BufferHSupplement.h"
+};
+
+#endif
+
diff --git a/components/htmlfive/nsHtml5UTF16BufferCppSupplement.h b/components/htmlfive/nsHtml5UTF16BufferCppSupplement.h
new file mode 100644
index 000000000..4c374fecc
--- /dev/null
+++ b/components/htmlfive/nsHtml5UTF16BufferCppSupplement.h
@@ -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/. */
+
+nsHtml5UTF16Buffer::nsHtml5UTF16Buffer(char16_t* aBuffer, int32_t aEnd)
+ : buffer(aBuffer)
+ , start(0)
+ , end(aEnd)
+{
+ MOZ_COUNT_CTOR(nsHtml5UTF16Buffer);
+}
+
+nsHtml5UTF16Buffer::~nsHtml5UTF16Buffer()
+{
+ MOZ_COUNT_DTOR(nsHtml5UTF16Buffer);
+}
+
+void
+nsHtml5UTF16Buffer::DeleteBuffer()
+{
+ delete[] buffer;
+}
+
+void
+nsHtml5UTF16Buffer::Swap(nsHtml5UTF16Buffer* aOther)
+{
+ char16_t* tempBuffer = buffer;
+ int32_t tempStart = start;
+ int32_t tempEnd = end;
+ buffer = aOther->buffer;
+ start = aOther->start;
+ end = aOther->end;
+ aOther->buffer = tempBuffer;
+ aOther->start = tempStart;
+ aOther->end = tempEnd;
+}
diff --git a/components/htmlfive/nsHtml5UTF16BufferHSupplement.h b/components/htmlfive/nsHtml5UTF16BufferHSupplement.h
new file mode 100644
index 000000000..938d164e8
--- /dev/null
+++ b/components/htmlfive/nsHtml5UTF16BufferHSupplement.h
@@ -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/. */
+
+protected:
+ nsHtml5UTF16Buffer(char16_t* aBuffer, int32_t aEnd);
+ ~nsHtml5UTF16Buffer();
+
+ /**
+ * For working around the privacy of |buffer| in the generated code.
+ */
+ void DeleteBuffer();
+
+ /**
+ * For working around the privacy of |buffer| in the generated code.
+ */
+ void Swap(nsHtml5UTF16Buffer* aOther);
diff --git a/components/htmlfive/nsHtml5ViewSourceUtils.cpp b/components/htmlfive/nsHtml5ViewSourceUtils.cpp
new file mode 100644
index 000000000..910a6691e
--- /dev/null
+++ b/components/htmlfive/nsHtml5ViewSourceUtils.cpp
@@ -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/. */
+
+
+#include "nsHtml5ViewSourceUtils.h"
+#include "nsHtml5AttributeName.h"
+#include "mozilla/Preferences.h"
+#include "nsHtml5String.h"
+
+// static
+nsHtml5HtmlAttributes*
+nsHtml5ViewSourceUtils::NewBodyAttributes()
+{
+ nsHtml5HtmlAttributes* bodyAttrs = new nsHtml5HtmlAttributes(0);
+ nsHtml5String id = nsHtml5Portability::newStringFromLiteral("viewsource");
+ bodyAttrs->addAttribute(nsHtml5AttributeName::ATTR_ID, id, -1);
+
+ nsString klass;
+ if (mozilla::Preferences::GetBool("view_source.wrap_long_lines", true)) {
+ klass.Append(NS_LITERAL_STRING("wrap "));
+ }
+ if (mozilla::Preferences::GetBool("view_source.syntax_highlight", true)) {
+ klass.Append(NS_LITERAL_STRING("highlight"));
+ }
+ if (!klass.IsEmpty()) {
+ bodyAttrs->addAttribute(
+ nsHtml5AttributeName::ATTR_CLASS, nsHtml5String::FromString(klass), -1);
+ }
+
+ int32_t tabSize = mozilla::Preferences::GetInt("view_source.tab_size", 4);
+ if (tabSize > 0) {
+ nsString style;
+ style.AssignASCII("tab-size: ");
+ style.AppendInt(tabSize);
+ bodyAttrs->addAttribute(
+ nsHtml5AttributeName::ATTR_STYLE, nsHtml5String::FromString(style), -1);
+ }
+
+ return bodyAttrs;
+}
+
+// static
+nsHtml5HtmlAttributes*
+nsHtml5ViewSourceUtils::NewLinkAttributes()
+{
+ nsHtml5HtmlAttributes* linkAttrs = new nsHtml5HtmlAttributes(0);
+ nsHtml5String rel = nsHtml5Portability::newStringFromLiteral("stylesheet");
+ linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_REL, rel, -1);
+ nsHtml5String type = nsHtml5Portability::newStringFromLiteral("text/css");
+ linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_TYPE, type, -1);
+ nsHtml5String href = nsHtml5Portability::newStringFromLiteral(
+ "resource://gre-resources/viewsource.css");
+ linkAttrs->addAttribute(nsHtml5AttributeName::ATTR_HREF, href, -1);
+ return linkAttrs;
+}
diff --git a/components/htmlfive/nsHtml5ViewSourceUtils.h b/components/htmlfive/nsHtml5ViewSourceUtils.h
new file mode 100644
index 000000000..1644801b6
--- /dev/null
+++ b/components/htmlfive/nsHtml5ViewSourceUtils.h
@@ -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/. */
+
+#ifndef nsHtml5ViewSourceUtils_h
+#define nsHtml5ViewSourceUtils_h
+
+#include "nsHtml5HtmlAttributes.h"
+
+class nsHtml5ViewSourceUtils
+{
+ public:
+ static nsHtml5HtmlAttributes* NewBodyAttributes();
+ static nsHtml5HtmlAttributes* NewLinkAttributes();
+};
+
+#endif // nsHtml5ViewSourceUtils_h
diff --git a/components/htmlfive/nsIContentHandle.h b/components/htmlfive/nsIContentHandle.h
new file mode 100644
index 000000000..b1cd6475f
--- /dev/null
+++ b/components/htmlfive/nsIContentHandle.h
@@ -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/. */
+
+typedef void nsIContentHandle;
diff --git a/components/htmlfive/nsIParserUtils.idl b/components/htmlfive/nsIParserUtils.idl
new file mode 100644
index 000000000..27306df25
--- /dev/null
+++ b/components/htmlfive/nsIParserUtils.idl
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIDOMDocumentFragment;
+interface nsIURI;
+
+/**
+ * Non-Web HTML parser functionality to extensions and applications.
+ * Don't use this from within Gecko--use nsContentUtils, nsTreeSanitizer, etc.
+ * directly instead.
+ */
+[scriptable, uuid(a1101145-0025-411e-8873-fdf57bf28128)]
+interface nsIParserUtils : nsISupports
+{
+
+ /**
+ * Flag for sanitizer: Allow comment nodes.
+ */
+ const unsigned long SanitizerAllowComments = (1 << 0);
+
+ /**
+ * Flag for sanitizer: Allow <style> and style="" (with contents sanitized
+ * in case of -moz-binding). Note! If -moz-binding is absent, properties
+ * that might be XSS risks in other Web engines are preserved!
+ */
+ const unsigned long SanitizerAllowStyle = (1 << 1);
+
+ /**
+ * Flag for sanitizer: Only allow cid: URLs for embedded content.
+ *
+ * At present, sanitizing CSS backgrounds, etc., is not supported, so setting
+ * this together with SanitizerAllowStyle doesn't make sense.
+ *
+ * At present, sanitizing CSS syntax in SVG presentational attributes is not
+ * supported, so this option flattens out SVG.
+ */
+ const unsigned long SanitizerCidEmbedsOnly = (1 << 2);
+
+ /**
+ * Flag for sanitizer: Drop non-CSS presentational HTML elements and
+ * attributes, such as <font>, <center> and bgcolor="".
+ */
+ const unsigned long SanitizerDropNonCSSPresentation = (1 << 3);
+
+ /**
+ * Flag for sanitizer: Drop forms and form controls (excluding
+ * fieldset/legend).
+ */
+ const unsigned long SanitizerDropForms = (1 << 4);
+
+ /**
+ * Flag for sanitizer: Drop <img>, <video>, <audio> and <source> and flatten
+ * out SVG.
+ */
+ const unsigned long SanitizerDropMedia = (1 << 5);
+
+ /**
+ * Parses a string into an HTML document, sanitizes the document and
+ * returns the result serialized to a string.
+ *
+ * The sanitizer is designed to protect against XSS when sanitized content
+ * is inserted into a different-origin context without an iframe-equivalent
+ * sandboxing mechanism.
+ *
+ * By default, the sanitizer doesn't try to avoid leaking information that
+ * the content was viewed to third parties. That is, by default, e.g.
+ * <img src> pointing to an HTTP server potentially controlled by a third
+ * party is not removed. To avoid ambient information leakage upon loading
+ * the sanitized content, use the SanitizerInternalEmbedsOnly flag. In that
+ * case, <a href> links (and similar) to other content are preserved, so an
+ * explicit user action (following a link) after the content has been loaded
+ * can still leak information.
+ *
+ * By default, non-dangerous non-CSS presentational HTML elements and
+ * attributes or forms are not removed. To remove these, use
+ * SanitizerDropNonCSSPresentation and/or SanitizerDropForms.
+ *
+ * By default, comments and CSS is removed. To preserve comments, use
+ * SanitizerAllowComments. To preserve <style> and style="", use
+ * SanitizerAllowStyle. -moz-binding is removed from <style> and style="" if
+ * present. In this case, properties that Gecko doesn't recognize can get
+ * removed as a side effect. Note! If -moz-binding is not present, <style>
+ * and style="" and SanitizerAllowStyle is specified, the sanitized content
+ * may still be XSS dangerous if loaded into a non-Gecko Web engine!
+ *
+ * @param src the HTML source to parse (C++ callers are allowed but not
+ * required to use the same string for the return value.)
+ * @param flags sanitization option flags defined above
+ */
+ AString sanitize(in AString src, in unsigned long flags);
+
+ /**
+ * Convert HTML to plain text.
+ *
+ * @param src the HTML source to parse (C++ callers are allowed but not
+ * required to use the same string for the return value.)
+ * @param flags conversion option flags defined in nsIDocumentEncoder
+ * @param wrapCol number of characters per line; 0 for no auto-wrapping
+ */
+ AString convertToPlainText(in AString src,
+ in unsigned long flags,
+ in unsigned long wrapCol);
+
+ /**
+ * Parses markup into a sanitized document fragment.
+ *
+ * @param fragment the input markup
+ * @param flags sanitization option flags defined above
+ * @param isXML true if |fragment| is XML and false if HTML
+ * @param baseURI the base URL for this fragment
+ * @param element the context node for the fragment parsing algorithm
+ */
+ nsIDOMDocumentFragment parseFragment(in AString fragment,
+ in unsigned long flags,
+ in boolean isXML,
+ in nsIURI baseURI,
+ in nsIDOMElement element);
+
+};
+
+%{ C++
+#define NS_PARSERUTILS_CONTRACTID \
+ "@mozilla.org/parserutils;1"
+#define NS_PARSERUTILS_CID \
+{ 0xaf7b24cb, 0x893f, 0x41bb, { 0x96, 0x1f, 0x5a, 0x69, 0x38, 0x8e, 0x27, 0xc3 } }
+%}
diff --git a/components/htmlfive/nsIScriptableUnescapeHTML.idl b/components/htmlfive/nsIScriptableUnescapeHTML.idl
new file mode 100644
index 000000000..238adf0dc
--- /dev/null
+++ b/components/htmlfive/nsIScriptableUnescapeHTML.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMElement;
+interface nsIDOMDocumentFragment;
+interface nsIURI;
+
+/**
+ * This interface is OBSOLETE and exists solely for legacy extensions.
+ */
+[scriptable, uuid(3ab244a9-f09d-44da-9e3f-ee4d67367f2d)]
+interface nsIScriptableUnescapeHTML : nsISupports
+{
+ /**
+ * Converts HTML to plain text. This is equivalent to calling
+ * nsIParserUtils::convertToPlainText(src,
+ * nsIDocumentEncoder::OutputSelectionOnly |
+ * nsIDocumentEncoder::OutputAbsoluteLinks, 0).
+ *
+ * You should call nsIParserUtils::convertToPlainText() instead of calling
+ * this method.
+ *
+ * @param src The HTML string to convert to plain text.
+ */
+ AString unescape(in AString src);
+
+ /**
+ * Parses markup into a sanitized document fragment. This is equivalent to
+ * calling nsIParserUtils::parseFragment(fragment, 0, isXML, baseURI,
+ * element).
+ *
+ * You should call nsIParserUtils::parseFragment() instead of calling this
+ * method.
+ * @param fragment the input markup
+ * @param isXML true if |fragment| is XML and false if HTML
+ * @param baseURI the base URL for this fragment
+ * @param element the context node for the fragment parsing algorithm
+ */
+ nsIDOMDocumentFragment parseFragment(in AString fragment,
+ in boolean isXML,
+ in nsIURI baseURI,
+ in nsIDOMElement element);
+};
+
+%{ C++
+#define NS_SCRIPTABLEUNESCAPEHTML_CONTRACTID \
+ "@mozilla.org/feed-unescapehtml;1"
+#define NS_SCRIPTABLEUNESCAPEHTML_CID \
+{ 0x10f2f5f0, 0xf103, 0x4901, { 0x98, 0x0f, 0xba, 0x11, 0xbd, 0x70, 0xd6, 0x0d} }
+%}
diff --git a/components/htmlfive/nsParserUtils.cpp b/components/htmlfive/nsParserUtils.cpp
new file mode 100644
index 000000000..2085cd149
--- /dev/null
+++ b/components/htmlfive/nsParserUtils.cpp
@@ -0,0 +1,232 @@
+/* -*- 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 "nsString.h"
+#include "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsXPIDLString.h"
+#include "nsEscape.h"
+#include "nsIParser.h"
+#include "nsIDTD.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsParserCIID.h"
+#include "nsContentUtils.h"
+#include "nsIContentSink.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIDOMDocumentFragment.h"
+#include "nsIFragmentContentSink.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMElement.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsAttrName.h"
+#include "nsHTMLParts.h"
+#include "nsContentCID.h"
+#include "nsIScriptableUnescapeHTML.h"
+#include "nsParserUtils.h"
+#include "nsAutoPtr.h"
+#include "nsTreeSanitizer.h"
+#include "nsHtml5Module.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "nsNullPrincipal.h"
+
+#define XHTML_DIV_TAG "div xmlns=\"http://www.w3.org/1999/xhtml\""
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsParserUtils,
+ nsIScriptableUnescapeHTML,
+ nsIParserUtils)
+
+NS_IMETHODIMP
+nsParserUtils::ConvertToPlainText(const nsAString& aFromStr,
+ uint32_t aFlags,
+ uint32_t aWrapCol,
+ nsAString& aToStr)
+{
+ return nsContentUtils::ConvertToPlainText(aFromStr,
+ aToStr,
+ aFlags,
+ aWrapCol);
+}
+
+NS_IMETHODIMP
+nsParserUtils::Unescape(const nsAString& aFromStr,
+ nsAString& aToStr)
+{
+ return nsContentUtils::ConvertToPlainText(aFromStr,
+ aToStr,
+ nsIDocumentEncoder::OutputSelectionOnly |
+ nsIDocumentEncoder::OutputAbsoluteLinks,
+ 0);
+}
+
+NS_IMETHODIMP
+nsParserUtils::Sanitize(const nsAString& aFromStr,
+ uint32_t aFlags,
+ nsAString& aToStr)
+{
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), "about:blank");
+ nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::Create();
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ nsresult rv = NS_NewDOMDocument(getter_AddRefs(domDocument),
+ EmptyString(),
+ EmptyString(),
+ nullptr,
+ uri,
+ uri,
+ principal,
+ true,
+ nullptr,
+ DocumentFlavorHTML);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocument> document = do_QueryInterface(domDocument);
+ rv = nsContentUtils::ParseDocumentHTML(aFromStr, document, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTreeSanitizer sanitizer(aFlags);
+ sanitizer.Sanitize(document);
+
+ nsCOMPtr<nsIDocumentEncoder> encoder =
+ do_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "text/html");
+
+ encoder->NativeInit(document,
+ NS_LITERAL_STRING("text/html"),
+ nsIDocumentEncoder::OutputDontRewriteEncodingDeclaration |
+ nsIDocumentEncoder::OutputNoScriptContent |
+ nsIDocumentEncoder::OutputEncodeBasicEntities |
+ nsIDocumentEncoder::OutputLFLineBreak |
+ nsIDocumentEncoder::OutputRaw);
+
+ return encoder->EncodeToString(aToStr);
+}
+
+NS_IMETHODIMP
+nsParserUtils::ParseFragment(const nsAString& aFragment,
+ bool aIsXML,
+ nsIURI* aBaseURI,
+ nsIDOMElement* aContextElement,
+ nsIDOMDocumentFragment** aReturn)
+{
+ return nsParserUtils::ParseFragment(aFragment,
+ 0,
+ aIsXML,
+ aBaseURI,
+ aContextElement,
+ aReturn);
+}
+
+NS_IMETHODIMP
+nsParserUtils::ParseFragment(const nsAString& aFragment,
+ uint32_t aFlags,
+ bool aIsXML,
+ nsIURI* aBaseURI,
+ nsIDOMElement* aContextElement,
+ nsIDOMDocumentFragment** aReturn)
+{
+ NS_ENSURE_ARG(aContextElement);
+ *aReturn = nullptr;
+
+ nsCOMPtr<nsIDocument> document;
+ nsCOMPtr<nsIDOMDocument> domDocument;
+ nsCOMPtr<nsIDOMNode> contextNode;
+ contextNode = do_QueryInterface(aContextElement);
+ contextNode->GetOwnerDocument(getter_AddRefs(domDocument));
+ document = do_QueryInterface(domDocument);
+ NS_ENSURE_TRUE(document, NS_ERROR_NOT_AVAILABLE);
+
+ nsAutoScriptBlockerSuppressNodeRemoved autoBlocker;
+
+ // stop scripts
+ RefPtr<ScriptLoader> loader;
+ bool scripts_enabled = false;
+ if (document) {
+ loader = document->ScriptLoader();
+ scripts_enabled = loader->GetEnabled();
+ }
+ if (scripts_enabled) {
+ loader->SetEnabled(false);
+ }
+
+ // Wrap things in a div or body for parsing, but it won't show up in
+ // the fragment.
+ nsresult rv = NS_OK;
+ AutoTArray<nsString, 2> tagStack;
+ nsAutoCString base, spec;
+ if (aIsXML) {
+ // XHTML
+ if (aBaseURI) {
+ base.AppendLiteral(XHTML_DIV_TAG);
+ base.AppendLiteral(" xml:base=\"");
+ rv = aBaseURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // nsEscapeHTML is good enough, because we only need to get
+ // quotes, ampersands, and angle brackets
+ char* escapedSpec = nsEscapeHTML(spec.get());
+ if (escapedSpec)
+ base += escapedSpec;
+ free(escapedSpec);
+ base.Append('"');
+ tagStack.AppendElement(NS_ConvertUTF8toUTF16(base));
+ } else {
+ tagStack.AppendElement(NS_LITERAL_STRING(XHTML_DIV_TAG));
+ }
+ }
+
+ nsCOMPtr<nsIContent> fragment;
+ if (aIsXML) {
+ rv = nsContentUtils::ParseFragmentXML(aFragment,
+ document,
+ tagStack,
+ true,
+ aReturn);
+ fragment = do_QueryInterface(*aReturn);
+ } else {
+ NS_ADDREF(*aReturn = new DocumentFragment(document->NodeInfoManager()));
+ fragment = do_QueryInterface(*aReturn);
+ rv = nsContentUtils::ParseFragmentHTML(aFragment,
+ fragment,
+ nsGkAtoms::body,
+ kNameSpaceID_XHTML,
+ false,
+ true);
+ // Now, set the base URI on all subtree roots.
+ if (aBaseURI) {
+ nsresult rv2 = aBaseURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ nsAutoString spec16;
+ CopyUTF8toUTF16(spec, spec16);
+ nsIContent* node = fragment->GetFirstChild();
+ while (node) {
+ if (node->IsElement()) {
+ node->SetAttr(kNameSpaceID_XML,
+ nsGkAtoms::base,
+ nsGkAtoms::xml,
+ spec16,
+ false);
+ }
+ node = node->GetNextSibling();
+ }
+ }
+ }
+ if (fragment) {
+ nsTreeSanitizer sanitizer(aFlags);
+ sanitizer.Sanitize(fragment);
+ }
+
+ if (scripts_enabled) {
+ loader->SetEnabled(true);
+ }
+
+ return rv;
+}
diff --git a/components/htmlfive/nsParserUtils.h b/components/htmlfive/nsParserUtils.h
new file mode 100644
index 000000000..4645f11b9
--- /dev/null
+++ b/components/htmlfive/nsParserUtils.h
@@ -0,0 +1,23 @@
+/* -*- 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/. */
+
+#ifndef nsParserUtils_h
+#define nsParserUtils_h
+
+#include "nsIScriptableUnescapeHTML.h"
+#include "nsIParserUtils.h"
+#include "mozilla/Attributes.h"
+
+class nsParserUtils final : public nsIScriptableUnescapeHTML,
+ public nsIParserUtils
+{
+ ~nsParserUtils() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCRIPTABLEUNESCAPEHTML
+ NS_DECL_NSIPARSERUTILS
+};
+
+#endif // nsParserUtils_h
diff --git a/components/htmlparser/moz.build b/components/htmlparser/moz.build
new file mode 100644
index 000000000..7d5881b90
--- /dev/null
+++ b/components/htmlparser/moz.build
@@ -0,0 +1,50 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsIExpatSink.idl',
+ 'public/nsIExtendedExpatSink.idl',
+]
+
+EXPORTS += [
+ 'src/nsCharsetSource.h',
+ 'src/nsElementTable.h',
+ 'src/nsHTMLTagList.h',
+ 'src/nsHTMLTags.h',
+ 'src/nsIContentSink.h',
+ 'src/nsIDTD.h',
+ 'src/nsIFragmentContentSink.h',
+ 'src/nsIHTMLContentSink.h',
+ 'src/nsIParser.h',
+ 'src/nsIParserService.h',
+ 'src/nsITokenizer.h',
+ 'src/nsParserBase.h',
+ 'src/nsParserCIID.h',
+ 'src/nsParserConstants.h',
+ 'src/nsScannerString.h',
+ 'src/nsToken.h',
+]
+
+SOURCES += [
+ 'src/CNavDTD.cpp',
+ 'src/CParserContext.cpp',
+ 'src/nsElementTable.cpp',
+ 'src/nsExpatDriver.cpp',
+ 'src/nsHTMLEntities.cpp',
+ 'src/nsHTMLTags.cpp',
+ 'src/nsHTMLTokenizer.cpp',
+ 'src/nsParser.cpp',
+ 'src/nsParserModule.cpp',
+ 'src/nsParserMsgUtils.cpp',
+ 'src/nsParserService.cpp',
+ 'src/nsScanner.cpp',
+ 'src/nsScannerString.cpp',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+XPIDL_MODULE = 'htmlparser'
+FINAL_LIBRARY = 'xul'
diff --git a/components/htmlparser/public/nsIExpatSink.idl b/components/htmlparser/public/nsIExpatSink.idl
new file mode 100644
index 000000000..df0b2d869
--- /dev/null
+++ b/components/htmlparser/public/nsIExpatSink.idl
@@ -0,0 +1,109 @@
+/* -*- 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 nsIScriptError;
+
+/**
+ * This interface should be implemented by any content sink that wants
+ * to get output from expat and do something with it; in other words,
+ * by any sink that handles some sort of XML dialect.
+ */
+
+[scriptable, uuid(01f681af-0f22-4725-a914-0d396114daf0)]
+interface nsIExpatSink : nsISupports
+{
+ /**
+ * Called to handle the opening tag of an element.
+ * @param aName the fully qualified tagname of the element
+ * @param aAtts the array of attribute names and values. There are
+ * aAttsCount/2 names and aAttsCount/2 values, so the total number of
+ * elements in the array is aAttsCount. The names and values
+ * alternate. Thus, if we number attributes starting with 0,
+ * aAtts[2*k] is the name of the k-th attribute and aAtts[2*k+1] is
+ * the value of that attribute Both explicitly specified attributes
+ * and attributes that are defined to have default values in a DTD are
+ * present in aAtts.
+ * @param aAttsCount the number of elements in aAtts.
+ * @param aLineNumber the line number of the start tag in the data stream.
+ */
+ void HandleStartElement(in wstring aName,
+ [array, size_is(aAttsCount)] in wstring aAtts,
+ in unsigned long aAttsCount,
+ in unsigned long aLineNumber);
+
+ /**
+ * Called to handle the closing tag of an element.
+ * @param aName the fully qualified tagname of the element
+ */
+ void HandleEndElement(in wstring aName);
+
+ /**
+ * Called to handle a comment
+ * @param aCommentText the text of the comment (not including the
+ * "<!--" and "-->")
+ */
+ void HandleComment(in wstring aCommentText);
+
+ /**
+ * Called to handle a CDATA section
+ * @param aData the text in the CDATA section. This is null-terminated.
+ * @param aLength the length of the aData string
+ */
+ void HandleCDataSection([size_is(aLength)] in wstring aData,
+ in unsigned long aLength);
+
+ /**
+ * Called to handle the doctype declaration
+ */
+ void HandleDoctypeDecl(in AString aSubset,
+ in AString aName,
+ in AString aSystemId,
+ in AString aPublicId,
+ in nsISupports aCatalogData);
+
+ /**
+ * Called to handle character data. Note that this does NOT get
+ * called for the contents of CDATA sections.
+ * @param aData the data to handle. aData is NOT NULL-TERMINATED.
+ * @param aLength the length of the aData string
+ */
+ void HandleCharacterData([size_is(aLength)] in wstring aData,
+ in unsigned long aLength);
+
+ /**
+ * Called to handle a processing instruction
+ * @param aTarget the PI target (e.g. xml-stylesheet)
+ * @param aData all the rest of the data in the PI
+ */
+ void HandleProcessingInstruction(in wstring aTarget,
+ in wstring aData);
+
+ /**
+ * Handle the XML Declaration.
+ *
+ * @param aVersion The version string, can be null if not specified.
+ * @param aEncoding The encoding string, can be null if not specified.
+ * @param aStandalone -1, 0, or 1 indicating respectively that there was no
+ * standalone parameter in the declaration, that it was
+ * given as no, or that it was given as yes.
+ */
+ void HandleXMLDeclaration(in wstring aVersion,
+ in wstring aEncoding,
+ in long aStandalone);
+
+ /**
+ * Ask the content sink if the expat driver should log an error to the console.
+ *
+ * @param aErrorText Error message to pass to content sink.
+ * @param aSourceText Source text of the document we're parsing.
+ * @param aError Script error object with line number & column number
+ *
+ * @retval True if the expat driver should report the error.
+ */
+ boolean ReportError(in wstring aErrorText,
+ in wstring aSourceText,
+ in nsIScriptError aError);
+};
diff --git a/components/htmlparser/public/nsIExtendedExpatSink.idl b/components/htmlparser/public/nsIExtendedExpatSink.idl
new file mode 100644
index 000000000..d88f0d974
--- /dev/null
+++ b/components/htmlparser/public/nsIExtendedExpatSink.idl
@@ -0,0 +1,72 @@
+/* -*- 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 "nsIExpatSink.idl"
+
+/**
+ * This interface provides notification of syntax-level events.
+ */
+[scriptable, uuid(5e3e4f0c-7b77-47ca-a7c5-a3d87f2a9c82)]
+interface nsIExtendedExpatSink : nsIExpatSink
+{
+ /**
+ * Called at the beginning of the DTD, before any entity or notation
+ * events.
+ * @param aDoctypeName The document type name.
+ * @param aSysid The declared system identifier for the external DTD subset,
+ * or null if none was declared.
+ * @param aPubid The declared public identifier for the external DTD subset,
+ * or null if none was declared.
+ */
+ void handleStartDTD(in wstring aDoctypeName,
+ in wstring aSysid,
+ in wstring aPubid);
+
+ /**
+ * Called when a prefix mapping starts to be in-scope, before any
+ * startElement events.
+ * @param aPrefix The Namespace prefix being declared. An empty string
+ * is used for the default element namespace, which has
+ * no prefix.
+ * @param aUri The Namespace URI the prefix is mapped to.
+ */
+ void handleStartNamespaceDecl(in wstring aPrefix,
+ in wstring aUri);
+
+ /**
+ * Called when a prefix mapping is no longer in-scope, after any
+ * endElement events.
+ * @param aPrefix The prefix that was being mapped. This is the empty string
+ * when a default mapping scope ends.
+ */
+ void handleEndNamespaceDecl(in wstring aPrefix);
+
+ /**
+ * This is called for a declaration of notation. The base argument is
+ * whatever was set by XML_SetBase. aNotationName will never be
+ * null. The other arguments can be.
+ * @param aNotationName The notation name.
+ * @param aSysId The notation's system identifier, or null if none was given.
+ * @param aPubId The notation's pubilc identifier, or null if none was given.
+ */
+ void handleNotationDecl(in wstring aNotationName,
+ in wstring aSysid,
+ in wstring aPubid);
+
+ /**
+ * This is called for a declaration of an unparsed (NDATA) entity.
+ * aName, aSysid and aNotationName arguments will never be
+ * null. The other arguments may be.
+ * @param aName The unparsed entity's name.
+ * @param aSysId The notation's system identifier.
+ * @param aPubId The notation's pubilc identifier, or null if none was given.
+ * @param aNotationName The name of the associated notation.
+ */
+ void handleUnparsedEntityDecl(in wstring aName,
+ in wstring aSysid,
+ in wstring aPubid,
+ in wstring aNotationName);
+
+};
diff --git a/components/htmlparser/src/CNavDTD.cpp b/components/htmlparser/src/CNavDTD.cpp
new file mode 100644
index 000000000..decc6a963
--- /dev/null
+++ b/components/htmlparser/src/CNavDTD.cpp
@@ -0,0 +1,90 @@
+/* -*- 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.h"
+#include "nsISupportsImpl.h"
+#include "nsIParser.h"
+#include "CNavDTD.h"
+#include "nsIHTMLContentSink.h"
+
+NS_IMPL_ISUPPORTS(CNavDTD, nsIDTD);
+
+CNavDTD::CNavDTD()
+{
+}
+
+CNavDTD::~CNavDTD()
+{
+}
+
+NS_IMETHODIMP
+CNavDTD::WillBuildModel(const CParserContext& aParserContext,
+ nsITokenizer* aTokenizer,
+ nsIContentSink* aSink)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CNavDTD::BuildModel(nsITokenizer* aTokenizer,
+ nsIContentSink* aSink)
+{
+ // NB: It is important to throw STOPPARSING if the sink is the wrong type in
+ // order to make sure nsParser cleans up properly after itself.
+ nsCOMPtr<nsIHTMLContentSink> sink = do_QueryInterface(aSink);
+ if (!sink) {
+ return NS_ERROR_HTMLPARSER_STOPPARSING;
+ }
+
+ nsresult rv = sink->OpenContainer(nsIHTMLContentSink::eHTML);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sink->OpenContainer(nsIHTMLContentSink::eBody);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = sink->CloseContainer(nsIHTMLContentSink::eBody);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = sink->CloseContainer(nsIHTMLContentSink::eHTML);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CNavDTD::DidBuildModel(nsresult anErrorCode)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+CNavDTD::Terminate()
+{
+}
+
+
+NS_IMETHODIMP_(int32_t)
+CNavDTD::GetType()
+{
+ return NS_IPARSER_FLAG_HTML;
+}
+
+NS_IMETHODIMP_(nsDTDMode)
+CNavDTD::GetMode() const
+{
+ return eDTDMode_quirks;
+}
+
+NS_IMETHODIMP_(bool)
+CNavDTD::CanContain(int32_t aParent,int32_t aChild) const
+{
+ MOZ_CRASH("nobody calls this");
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+CNavDTD::IsContainer(int32_t aTag) const
+{
+ MOZ_CRASH("nobody calls this");
+ return false;
+}
diff --git a/components/htmlparser/src/CNavDTD.h b/components/htmlparser/src/CNavDTD.h
new file mode 100644
index 000000000..b3c557e81
--- /dev/null
+++ b/components/htmlparser/src/CNavDTD.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+#ifndef NS_NAVHTMLDTD__
+#define NS_NAVHTMLDTD__
+
+#include "nsIDTD.h"
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+
+#ifdef _MSC_VER
+#pragma warning( disable : 4275 )
+#endif
+
+class CNavDTD : public nsIDTD
+{
+#ifdef _MSC_VER
+#pragma warning( default : 4275 )
+#endif
+
+ virtual ~CNavDTD();
+
+public:
+ CNavDTD();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDTD
+};
+
+#endif
+
+
+
diff --git a/components/htmlparser/src/CParserContext.cpp b/components/htmlparser/src/CParserContext.cpp
new file mode 100644
index 000000000..3b764d7e4
--- /dev/null
+++ b/components/htmlparser/src/CParserContext.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 "nsIAtom.h"
+#include "CParserContext.h"
+#include "nsToken.h"
+#include "prenv.h"
+#include "nsIHTMLContentSink.h"
+#include "nsHTMLTokenizer.h"
+#include "nsMimeTypes.h"
+#include "nsHTMLTokenizer.h"
+
+CParserContext::CParserContext(CParserContext* aPrevContext,
+ nsScanner* aScanner,
+ void *aKey,
+ eParserCommands aCommand,
+ nsIRequestObserver* aListener,
+ eAutoDetectResult aStatus,
+ bool aCopyUnused)
+ : mListener(aListener),
+ mKey(aKey),
+ mPrevContext(aPrevContext),
+ mScanner(aScanner),
+ mDTDMode(eDTDMode_unknown),
+ mStreamListenerState(eNone),
+ mContextType(eCTNone),
+ mAutoDetectStatus(aStatus),
+ mParserCommand(aCommand),
+ mMultipart(true),
+ mCopyUnused(aCopyUnused)
+{
+ MOZ_COUNT_CTOR(CParserContext);
+}
+
+CParserContext::~CParserContext()
+{
+ // It's ok to simply ingore the PrevContext.
+ MOZ_COUNT_DTOR(CParserContext);
+}
+
+void
+CParserContext::SetMimeType(const nsACString& aMimeType)
+{
+ mMimeType.Assign(aMimeType);
+
+ mDocType = ePlainText;
+
+ if (mMimeType.EqualsLiteral(TEXT_HTML))
+ mDocType = eHTML_Strict;
+ else if (mMimeType.EqualsLiteral(TEXT_XML) ||
+ mMimeType.EqualsLiteral(APPLICATION_XML) ||
+ mMimeType.EqualsLiteral(APPLICATION_XHTML_XML) ||
+ mMimeType.EqualsLiteral(TEXT_XUL) ||
+ mMimeType.EqualsLiteral(IMAGE_SVG_XML) ||
+ mMimeType.EqualsLiteral(APPLICATION_MATHML_XML) ||
+ mMimeType.EqualsLiteral(APPLICATION_RDF_XML) ||
+ mMimeType.EqualsLiteral(APPLICATION_WAPXHTML_XML) ||
+ mMimeType.EqualsLiteral(TEXT_RDF))
+ mDocType = eXML;
+}
+
+nsresult
+CParserContext::GetTokenizer(nsIDTD* aDTD,
+ nsIContentSink* aSink,
+ nsITokenizer*& aTokenizer)
+{
+ nsresult result = NS_OK;
+ int32_t type = aDTD ? aDTD->GetType() : NS_IPARSER_FLAG_HTML;
+
+ if (!mTokenizer) {
+ if (type == NS_IPARSER_FLAG_HTML || mParserCommand == eViewSource) {
+ mTokenizer = new nsHTMLTokenizer;
+ }
+ else if (type == NS_IPARSER_FLAG_XML) {
+ mTokenizer = do_QueryInterface(aDTD, &result);
+ }
+ }
+
+ aTokenizer = mTokenizer;
+
+ return result;
+}
diff --git a/components/htmlparser/src/CParserContext.h b/components/htmlparser/src/CParserContext.h
new file mode 100644
index 000000000..8850b83d5
--- /dev/null
+++ b/components/htmlparser/src/CParserContext.h
@@ -0,0 +1,70 @@
+/* -*- 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/. */
+
+/**
+ * MODULE NOTES:
+ * @update gess 4/1/98
+ *
+ */
+
+#ifndef __CParserContext
+#define __CParserContext
+
+#include "nsIParser.h"
+#include "nsIURL.h"
+#include "nsIDTD.h"
+#include "nsIStreamListener.h"
+#include "nsIRequest.h"
+#include "nsScanner.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+
+/**
+ * Note that the parser is given FULL access to all
+ * data in a parsercontext. Hey, that what it's for!
+ */
+
+class CParserContext {
+public:
+ enum eContextType {eCTNone,eCTURL,eCTString,eCTStream};
+
+ CParserContext(CParserContext* aPrevContext,
+ nsScanner* aScanner,
+ void* aKey = 0,
+ eParserCommands aCommand = eViewNormal,
+ nsIRequestObserver* aListener = 0,
+ eAutoDetectResult aStatus = eUnknownDetect,
+ bool aCopyUnused = false);
+
+ ~CParserContext();
+
+ nsresult GetTokenizer(nsIDTD* aDTD,
+ nsIContentSink* aSink,
+ nsITokenizer*& aTokenizer);
+ void SetMimeType(const nsACString& aMimeType);
+
+ nsCOMPtr<nsIRequest> mRequest; // provided by necko to differnciate different input streams
+ // why is mRequest strongly referenced? see bug 102376.
+ nsCOMPtr<nsIRequestObserver> mListener;
+ void* const mKey;
+ nsCOMPtr<nsITokenizer> mTokenizer;
+ CParserContext* const mPrevContext;
+ nsAutoPtr<nsScanner> mScanner;
+
+ nsCString mMimeType;
+ nsDTDMode mDTDMode;
+
+ eParserDocType mDocType;
+ eStreamState mStreamListenerState;
+ eContextType mContextType;
+ eAutoDetectResult mAutoDetectStatus;
+ eParserCommands mParserCommand;
+
+ bool mMultipart;
+ bool mCopyUnused;
+};
+
+#endif
diff --git a/components/htmlparser/src/nsCharsetSource.h b/components/htmlparser/src/nsCharsetSource.h
new file mode 100644
index 000000000..bd85bba10
--- /dev/null
+++ b/components/htmlparser/src/nsCharsetSource.h
@@ -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/. */
+
+#ifndef nsCharsetSource_h_
+#define nsCharsetSource_h_
+
+// note: the value order defines the priority; higher numbers take priority
+#define kCharsetUninitialized 0
+#define kCharsetFromFallback 1
+#define kCharsetFromTopLevelDomain 2
+#define kCharsetFromDocTypeDefault 3 // This and up confident for XHR
+#define kCharsetFromCache 4
+#define kCharsetFromParentFrame 5
+#define kCharsetFromAutoDetection 6
+#define kCharsetFromHintPrevDoc 7
+#define kCharsetFromMetaPrescan 8 // this one and smaller: HTML5 Tentative
+#define kCharsetFromMetaTag 9 // this one and greater: HTML5 Confident
+#define kCharsetFromIrreversibleAutoDetection 10
+#define kCharsetFromChannel 11
+#define kCharsetFromOtherComponent 12
+#define kCharsetFromParentForced 13 // propagates to child frames
+#define kCharsetFromUserForced 14 // propagates to child frames
+#define kCharsetFromByteOrderMark 15
+
+#endif /* nsCharsetSource_h_ */
diff --git a/components/htmlparser/src/nsElementTable.cpp b/components/htmlparser/src/nsElementTable.cpp
new file mode 100644
index 000000000..7ab4c48b1
--- /dev/null
+++ b/components/htmlparser/src/nsElementTable.cpp
@@ -0,0 +1,210 @@
+/* -*- 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 "nsElementTable.h"
+
+struct HTMLElement
+{
+#ifdef DEBUG
+ nsHTMLTag mTagID;
+#endif
+ bool mIsBlock;
+ bool mIsContainer;
+};
+
+#ifdef DEBUG
+#define ELEM(tag, block, container) { eHTMLTag_##tag, block, container },
+#else
+#define ELEM(tag, block, container) { block, container },
+#endif
+
+#define ____ false // This makes the table easier to read.
+
+// Note that the mIsBlock field disagrees with
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements for
+// the following elements: center, details, dialog, dir, dt, figcaption,
+// listing, menu, multicol, noscript, output, summary, tfoot, video.
+//
+// mrbkap thinks that the field values were pulled from the old HTML4 DTD and
+// then got modified in mostly random ways to make the old parser's behavior
+// compatible with the web. So it might make sense to change the mIsBlock
+// values for the abovementioned tags at some point.
+//
+
+static const HTMLElement gHTMLElements[] = {
+ ELEM(unknown, ____, ____)
+ ELEM(a, ____, true)
+ ELEM(abbr, ____, true)
+ ELEM(acronym, ____, true)
+ ELEM(address, true, true)
+ ELEM(applet, ____, true)
+ ELEM(area, ____, ____)
+ ELEM(article, true, true)
+ ELEM(aside, true, true)
+ ELEM(audio, ____, true)
+ ELEM(b, ____, true)
+ ELEM(base, ____, ____)
+ ELEM(basefont, ____, ____)
+ ELEM(bdo, ____, true)
+ ELEM(bgsound, ____, ____)
+ ELEM(big, ____, true)
+ ELEM(blockquote, true, true)
+ ELEM(body, ____, true)
+ ELEM(br, ____, ____)
+ ELEM(button, ____, true)
+ ELEM(canvas, ____, true)
+ ELEM(caption, ____, true)
+ ELEM(center, true, true)
+ ELEM(cite, ____, true)
+ ELEM(code, ____, true)
+ ELEM(col, ____, ____)
+ ELEM(colgroup, ____, true)
+ ELEM(data, ____, true)
+ ELEM(datalist, ____, true)
+ ELEM(dd, ____, true)
+ ELEM(del, ____, true)
+ ELEM(details, true, true)
+ ELEM(dfn, ____, true)
+ ELEM(dialog, true, true)
+ ELEM(dir, true, true)
+ ELEM(div, true, true)
+ ELEM(dl, true, true)
+ ELEM(dt, ____, true)
+ ELEM(em, ____, true)
+ ELEM(embed, ____, ____)
+ ELEM(fieldset, true, true)
+ ELEM(figcaption, ____, true)
+ ELEM(figure, true, true)
+ ELEM(font, ____, true)
+ ELEM(footer, true, true)
+ ELEM(form, true, true)
+ ELEM(frame, ____, ____)
+ ELEM(frameset, ____, true)
+ ELEM(h1, true, true)
+ ELEM(h2, true, true)
+ ELEM(h3, true, true)
+ ELEM(h4, true, true)
+ ELEM(h5, true, true)
+ ELEM(h6, true, true)
+ ELEM(head, ____, true)
+ ELEM(header, true, true)
+ ELEM(hgroup, true, true)
+ ELEM(hr, true, ____)
+ ELEM(html, ____, true)
+ ELEM(i, ____, true)
+ ELEM(iframe, ____, true)
+ ELEM(image, ____, ____)
+ ELEM(img, ____, ____)
+ ELEM(input, ____, ____)
+ ELEM(ins, ____, true)
+ ELEM(kbd, ____, true)
+ ELEM(keygen, ____, ____)
+ ELEM(label, ____, true)
+ ELEM(legend, ____, true)
+ ELEM(li, true, true)
+ ELEM(link, ____, ____)
+ ELEM(listing, true, true)
+ ELEM(main, true, true)
+ ELEM(map, ____, true)
+ ELEM(mark, ____, true)
+ ELEM(menu, true, true)
+ ELEM(menuitem, ____, true)
+ ELEM(meta, ____, ____)
+ ELEM(meter, ____, true)
+ ELEM(multicol, true, true)
+ ELEM(nav, true, true)
+ ELEM(nobr, ____, true)
+ ELEM(noembed, ____, true)
+ ELEM(noframes, ____, true)
+ ELEM(noscript, ____, true)
+ ELEM(object, ____, true)
+ ELEM(ol, true, true)
+ ELEM(optgroup, ____, true)
+ ELEM(option, ____, true)
+ ELEM(output, ____, true)
+ ELEM(p, true, true)
+ ELEM(param, ____, ____)
+ ELEM(picture, ____, true)
+ ELEM(plaintext, ____, true)
+ ELEM(pre, true, true)
+ ELEM(progress, ____, true)
+ ELEM(q, ____, true)
+ ELEM(rb, ____, true)
+ ELEM(rp, ____, true)
+ ELEM(rt, ____, true)
+ ELEM(rtc, ____, true)
+ ELEM(ruby, ____, true)
+ ELEM(s, ____, true)
+ ELEM(samp, ____, true)
+ ELEM(script, ____, true)
+ ELEM(section, true, true)
+ ELEM(select, ____, true)
+ ELEM(small, ____, true)
+ ELEM(slot, ____, true)
+ ELEM(source, ____, ____)
+ ELEM(span, ____, true)
+ ELEM(strike, ____, true)
+ ELEM(strong, ____, true)
+ ELEM(style, ____, true)
+ ELEM(sub, ____, true)
+ ELEM(summary, true, true)
+ ELEM(sup, ____, true)
+ ELEM(table, true, true)
+ ELEM(tbody, ____, true)
+ ELEM(td, ____, true)
+ ELEM(textarea, ____, true)
+ ELEM(tfoot, ____, true)
+ ELEM(th, ____, true)
+ ELEM(thead, ____, true)
+ ELEM(template, ____, true)
+ ELEM(time, ____, true)
+ ELEM(title, ____, true)
+ ELEM(tr, ____, true)
+ ELEM(track, ____, ____)
+ ELEM(tt, ____, true)
+ ELEM(u, ____, true)
+ ELEM(ul, true, true)
+ ELEM(var, ____, true)
+ ELEM(video, ____, true)
+ ELEM(wbr, ____, ____)
+ ELEM(xmp, ____, true)
+ ELEM(text, ____, ____)
+ ELEM(whitespace, ____, ____)
+ ELEM(newline, ____, ____)
+ ELEM(comment, ____, true)
+ ELEM(entity, ____, true)
+ ELEM(doctypeDecl, ____, true)
+ ELEM(markupDecl, ____, true)
+ ELEM(instruction, ____, true)
+ ELEM(userdefined, ____, true)
+};
+
+#undef ELEM
+#undef ____
+
+bool
+nsHTMLElement::IsContainer(nsHTMLTag aId)
+{
+ return gHTMLElements[aId].mIsContainer;
+}
+
+bool
+nsHTMLElement::IsBlock(nsHTMLTag aId)
+{
+ return gHTMLElements[aId].mIsBlock;
+}
+
+#ifdef DEBUG
+void
+CheckElementTable()
+{
+ for (nsHTMLTag t = eHTMLTag_unknown;
+ t <= eHTMLTag_userdefined;
+ t = nsHTMLTag(t + 1)) {
+ MOZ_ASSERT(gHTMLElements[t].mTagID == t,
+ "gHTMLElements entries does match tag list.");
+ }
+}
+#endif
diff --git a/components/htmlparser/src/nsElementTable.h b/components/htmlparser/src/nsElementTable.h
new file mode 100644
index 000000000..b456b5989
--- /dev/null
+++ b/components/htmlparser/src/nsElementTable.h
@@ -0,0 +1,21 @@
+/* -*- 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/. */
+
+#ifndef nsElementTable_h
+#define nsElementTable_h
+
+#include "nsHTMLTags.h"
+
+#ifdef DEBUG
+void CheckElementTable();
+#endif
+
+struct nsHTMLElement
+{
+ static bool IsContainer(nsHTMLTag aTag);
+ static bool IsBlock(nsHTMLTag aTag);
+};
+
+#endif // nsElementTable_h
diff --git a/components/htmlparser/src/nsExpatDriver.cpp b/components/htmlparser/src/nsExpatDriver.cpp
new file mode 100644
index 000000000..e35a1da25
--- /dev/null
+++ b/components/htmlparser/src/nsExpatDriver.cpp
@@ -0,0 +1,1412 @@
+/* -*- 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 "nsExpatDriver.h"
+#include "nsCOMPtr.h"
+#include "nsParserCIID.h"
+#include "CParserContext.h"
+#include "nsIExpatSink.h"
+#include "nsIExtendedExpatSink.h"
+#include "nsIContentSink.h"
+#include "nsParserMsgUtils.h"
+#include "nsIURL.h"
+#include "nsIUnicharInputStream.h"
+#include "nsIProtocolHandler.h"
+#include "nsNetUtil.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsTextFormatter.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsCRT.h"
+#include "nsIConsoleService.h"
+#include "nsIScriptError.h"
+#include "nsIContentPolicy.h"
+#include "nsContentPolicyUtils.h"
+#include "nsError.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsUnicharInputStream.h"
+#include "nsContentUtils.h"
+#include "nsNullPrincipal.h"
+
+#include "mozilla/IntegerTypeTraits.h"
+#include "mozilla/Logging.h"
+
+using mozilla::fallible;
+using mozilla::LogLevel;
+
+#define kExpatSeparatorChar 0xFFFF
+
+static const char16_t kUTF16[] = { 'U', 'T', 'F', '-', '1', '6', '\0' };
+
+static mozilla::LazyLogModule gExpatDriverLog("expatdriver");
+
+// The maximum tree depth used for XML-based files (xml/svg/etc.)
+static const uint16_t sMaxXMLDepth = 2048;
+
+/***************************** EXPAT CALL BACKS ******************************/
+// The callback handlers that get called from the expat parser.
+
+static void
+Driver_HandleXMLDeclaration(void *aUserData,
+ const XML_Char *aVersion,
+ const XML_Char *aEncoding,
+ int aStandalone)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ nsExpatDriver* driver = static_cast<nsExpatDriver*>(aUserData);
+ driver->HandleXMLDeclaration(aVersion, aEncoding, aStandalone);
+ }
+}
+
+static void
+Driver_HandleStartElement(void *aUserData,
+ const XML_Char *aName,
+ const XML_Char **aAtts)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->HandleStartElement(aName,
+ aAtts);
+ }
+}
+
+static void
+Driver_HandleEndElement(void *aUserData,
+ const XML_Char *aName)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->HandleEndElement(aName);
+ }
+}
+
+static void
+Driver_HandleCharacterData(void *aUserData,
+ const XML_Char *aData,
+ int aLength)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ nsExpatDriver* driver = static_cast<nsExpatDriver*>(aUserData);
+ driver->HandleCharacterData(aData, uint32_t(aLength));
+ }
+}
+
+static void
+Driver_HandleComment(void *aUserData,
+ const XML_Char *aName)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if(aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->HandleComment(aName);
+ }
+}
+
+static void
+Driver_HandleProcessingInstruction(void *aUserData,
+ const XML_Char *aTarget,
+ const XML_Char *aData)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ nsExpatDriver* driver = static_cast<nsExpatDriver*>(aUserData);
+ driver->HandleProcessingInstruction(aTarget, aData);
+ }
+}
+
+static void
+Driver_HandleDefault(void *aUserData,
+ const XML_Char *aData,
+ int aLength)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ nsExpatDriver* driver = static_cast<nsExpatDriver*>(aUserData);
+ driver->HandleDefault(aData, uint32_t(aLength));
+ }
+}
+
+static void
+Driver_HandleStartCdataSection(void *aUserData)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->HandleStartCdataSection();
+ }
+}
+
+static void
+Driver_HandleEndCdataSection(void *aUserData)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->HandleEndCdataSection();
+ }
+}
+
+static void
+Driver_HandleStartDoctypeDecl(void *aUserData,
+ const XML_Char *aDoctypeName,
+ const XML_Char *aSysid,
+ const XML_Char *aPubid,
+ int aHasInternalSubset)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->
+ HandleStartDoctypeDecl(aDoctypeName, aSysid, aPubid, !!aHasInternalSubset);
+ }
+}
+
+static void
+Driver_HandleEndDoctypeDecl(void *aUserData)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->HandleEndDoctypeDecl();
+ }
+}
+
+static int
+Driver_HandleExternalEntityRef(void *aExternalEntityRefHandler,
+ const XML_Char *aOpenEntityNames,
+ const XML_Char *aBase,
+ const XML_Char *aSystemId,
+ const XML_Char *aPublicId)
+{
+ NS_ASSERTION(aExternalEntityRefHandler, "expat driver should exist");
+ if (!aExternalEntityRefHandler) {
+ return 1;
+ }
+
+ nsExpatDriver* driver = static_cast<nsExpatDriver*>
+ (aExternalEntityRefHandler);
+
+ return driver->HandleExternalEntityRef(aOpenEntityNames, aBase, aSystemId,
+ aPublicId);
+}
+
+static void
+Driver_HandleStartNamespaceDecl(void *aUserData,
+ const XML_Char *aPrefix,
+ const XML_Char *aUri)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->
+ HandleStartNamespaceDecl(aPrefix, aUri);
+ }
+}
+
+static void
+Driver_HandleEndNamespaceDecl(void *aUserData,
+ const XML_Char *aPrefix)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->
+ HandleEndNamespaceDecl(aPrefix);
+ }
+}
+
+static void
+Driver_HandleNotationDecl(void *aUserData,
+ const XML_Char *aNotationName,
+ const XML_Char *aBase,
+ const XML_Char *aSysid,
+ const XML_Char *aPubid)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->
+ HandleNotationDecl(aNotationName, aBase, aSysid, aPubid);
+ }
+}
+
+static void
+Driver_HandleUnparsedEntityDecl(void *aUserData,
+ const XML_Char *aEntityName,
+ const XML_Char *aBase,
+ const XML_Char *aSysid,
+ const XML_Char *aPubid,
+ const XML_Char *aNotationName)
+{
+ NS_ASSERTION(aUserData, "expat driver should exist");
+ if (aUserData) {
+ static_cast<nsExpatDriver*>(aUserData)->
+ HandleUnparsedEntityDecl(aEntityName, aBase, aSysid, aPubid,
+ aNotationName);
+ }
+}
+
+
+/***************************** END CALL BACKS ********************************/
+
+/***************************** CATALOG UTILS *********************************/
+
+// Initially added for bug 113400 to switch from the remote "XHTML 1.0 plus
+// MathML 2.0" DTD to the the lightweight customized version that Mozilla uses.
+// Since Mozilla is not validating, no need to fetch a *huge* file at each
+// click.
+// XXX The cleanest solution here would be to fix Bug 98413: Implement XML
+// Catalogs.
+struct nsCatalogData {
+ const char* mPublicID;
+ const char* mLocalDTD;
+ const char* mAgentSheet;
+};
+
+// The order of this table is guestimated to be in the optimum order
+static const nsCatalogData kCatalogTable[] = {
+ { "-//W3C//DTD XHTML 1.0 Transitional//EN", "htmlmathml-f.ent", nullptr },
+ { "-//W3C//DTD XHTML 1.1//EN", "htmlmathml-f.ent", nullptr },
+ { "-//W3C//DTD XHTML 1.0 Strict//EN", "htmlmathml-f.ent", nullptr },
+ { "-//W3C//DTD XHTML 1.0 Frameset//EN", "htmlmathml-f.ent", nullptr },
+ { "-//W3C//DTD XHTML Basic 1.0//EN", "htmlmathml-f.ent", nullptr },
+ { "-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN", "htmlmathml-f.ent", nullptr },
+ { "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN", "htmlmathml-f.ent", nullptr },
+ { "-//W3C//DTD MathML 2.0//EN", "htmlmathml-f.ent", nullptr },
+ { "-//WAPFORUM//DTD XHTML Mobile 1.0//EN", "htmlmathml-f.ent", nullptr },
+ { nullptr, nullptr, nullptr }
+};
+
+static const nsCatalogData*
+LookupCatalogData(const char16_t* aPublicID)
+{
+ nsDependentString publicID(aPublicID);
+
+ // linear search for now since the number of entries is going to
+ // be negligible, and the fix for bug 98413 would get rid of this
+ // code anyway
+ const nsCatalogData* data = kCatalogTable;
+ while (data->mPublicID) {
+ if (publicID.EqualsASCII(data->mPublicID)) {
+ return data;
+ }
+ ++data;
+ }
+
+ return nullptr;
+}
+
+// This function provides a resource URI to a local DTD
+// in resource://gre/res/dtd/ which may or may not exist.
+// If aCatalogData is provided, it is used to remap the
+// DTD instead of taking the filename from the URI.
+static void
+GetLocalDTDURI(const nsCatalogData* aCatalogData, nsIURI* aDTD,
+ nsIURI** aResult)
+{
+ NS_ASSERTION(aDTD, "Null parameter.");
+
+ nsAutoCString fileName;
+ if (aCatalogData) {
+ // remap the DTD to a known local DTD
+ fileName.Assign(aCatalogData->mLocalDTD);
+ }
+
+ if (fileName.IsEmpty()) {
+ // Try to see if the user has installed the DTD file -- we extract the
+ // filename.ext of the DTD here. Hence, for any DTD for which we have
+ // no predefined mapping, users just have to copy the DTD file to our
+ // special DTD directory and it will be picked.
+ nsCOMPtr<nsIURL> dtdURL = do_QueryInterface(aDTD);
+ if (!dtdURL) {
+ return;
+ }
+
+ dtdURL->GetFileName(fileName);
+ if (fileName.IsEmpty()) {
+ return;
+ }
+ }
+
+ nsAutoCString respath("resource://gre/res/dtd/");
+ respath += fileName;
+ NS_NewURI(aResult, respath);
+}
+
+/***************************** END CATALOG UTILS *****************************/
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsExpatDriver)
+ NS_INTERFACE_MAP_ENTRY(nsITokenizer)
+ NS_INTERFACE_MAP_ENTRY(nsIDTD)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDTD)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsExpatDriver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsExpatDriver)
+
+NS_IMPL_CYCLE_COLLECTION(nsExpatDriver, mSink, mExtendedSink)
+
+nsExpatDriver::nsExpatDriver()
+ : mExpatParser(nullptr),
+ mInCData(false),
+ mInInternalSubset(false),
+ mInExternalDTD(false),
+ mMadeFinalCallToExpat(false),
+ mIsFinalChunk(false),
+ mTagDepth(0),
+ mInternalState(NS_OK),
+ mExpatBuffered(0),
+ mCatalogData(nullptr),
+ mInnerWindowID(0)
+{
+}
+
+nsExpatDriver::~nsExpatDriver()
+{
+ if (mExpatParser) {
+ XML_ParserFree(mExpatParser);
+ }
+}
+
+void
+nsExpatDriver::HandleStartElement(const char16_t *aValue,
+ const char16_t **aAtts)
+{
+ NS_ASSERTION(mSink, "content sink not found!");
+
+ // Calculate the total number of elements in aAtts.
+ // XML_GetSpecifiedAttributeCount will only give us the number of specified
+ // attrs (twice that number, actually), so we have to check for default attrs
+ // ourselves.
+ uint32_t attrArrayLength;
+ for (attrArrayLength = XML_GetSpecifiedAttributeCount(mExpatParser);
+ aAtts[attrArrayLength];
+ attrArrayLength += 2) {
+ // Just looping till we find out what the length is
+ }
+
+ if (mSink) {
+ // Sanity check: Make sure the limit fits in the type the tag depth tracker
+ // was declared as.
+ static_assert(sMaxXMLDepth <= mozilla::MaxValue<decltype(nsExpatDriver::mTagDepth)>::value,
+ "Maximum XML parsing depth type mismatch: value too large.");
+
+ if (++mTagDepth >= sMaxXMLDepth) {
+ MaybeStopParser(NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP);
+ return;
+ }
+
+ nsresult rv = mSink->
+ HandleStartElement(aValue, aAtts, attrArrayLength,
+ XML_GetCurrentLineNumber(mExpatParser));
+ MaybeStopParser(rv);
+ }
+}
+
+nsresult
+nsExpatDriver::HandleEndElement(const char16_t *aValue)
+{
+ NS_ASSERTION(mSink, "content sink not found!");
+ NS_ASSERTION(mInternalState != NS_ERROR_HTMLPARSER_BLOCK,
+ "Shouldn't block from HandleStartElement.");
+
+ if (mSink && mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) {
+ nsresult rv = mSink->HandleEndElement(aValue);
+ --mTagDepth;
+ MaybeStopParser(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleCharacterData(const char16_t *aValue,
+ const uint32_t aLength)
+{
+ NS_ASSERTION(mSink, "content sink not found!");
+
+ if (mInCData) {
+ if (!mCDataText.Append(aValue, aLength, fallible)) {
+ MaybeStopParser(NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ else if (mSink) {
+ nsresult rv = mSink->HandleCharacterData(aValue, aLength);
+ MaybeStopParser(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleComment(const char16_t *aValue)
+{
+ NS_ASSERTION(mSink, "content sink not found!");
+
+ if (mInExternalDTD) {
+ // Ignore comments from external DTDs
+ return NS_OK;
+ }
+
+ if (mInInternalSubset) {
+ mInternalSubset.AppendLiteral("<!--");
+ mInternalSubset.Append(aValue);
+ mInternalSubset.AppendLiteral("-->");
+ }
+ else if (mSink) {
+ nsresult rv = mSink->HandleComment(aValue);
+ MaybeStopParser(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleProcessingInstruction(const char16_t *aTarget,
+ const char16_t *aData)
+{
+ NS_ASSERTION(mSink, "content sink not found!");
+
+ if (mInExternalDTD) {
+ // Ignore PIs in external DTDs for now. Eventually we want to
+ // pass them to the sink in a way that doesn't put them in the DOM
+ return NS_OK;
+ }
+
+ if (mInInternalSubset) {
+ mInternalSubset.AppendLiteral("<?");
+ mInternalSubset.Append(aTarget);
+ mInternalSubset.Append(' ');
+ mInternalSubset.Append(aData);
+ mInternalSubset.AppendLiteral("?>");
+ }
+ else if (mSink) {
+ nsresult rv = mSink->HandleProcessingInstruction(aTarget, aData);
+ MaybeStopParser(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleXMLDeclaration(const char16_t *aVersion,
+ const char16_t *aEncoding,
+ int32_t aStandalone)
+{
+ if (mSink) {
+ nsresult rv = mSink->HandleXMLDeclaration(aVersion, aEncoding, aStandalone);
+ MaybeStopParser(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleDefault(const char16_t *aValue,
+ const uint32_t aLength)
+{
+ NS_ASSERTION(mSink, "content sink not found!");
+
+ if (mInExternalDTD) {
+ // Ignore newlines in external DTDs
+ return NS_OK;
+ }
+
+ if (mInInternalSubset) {
+ mInternalSubset.Append(aValue, aLength);
+ }
+ else if (mSink) {
+ uint32_t i;
+ nsresult rv = mInternalState;
+ for (i = 0; i < aLength && NS_SUCCEEDED(rv); ++i) {
+ if (aValue[i] == '\n' || aValue[i] == '\r') {
+ rv = mSink->HandleCharacterData(&aValue[i], 1);
+ }
+ }
+ MaybeStopParser(rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleStartCdataSection()
+{
+ mInCData = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleEndCdataSection()
+{
+ NS_ASSERTION(mSink, "content sink not found!");
+
+ mInCData = false;
+ if (mSink) {
+ nsresult rv = mSink->HandleCDataSection(mCDataText.get(),
+ mCDataText.Length());
+ MaybeStopParser(rv);
+ }
+ mCDataText.Truncate();
+
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleStartNamespaceDecl(const char16_t* aPrefix,
+ const char16_t* aUri)
+{
+ if (mExtendedSink) {
+ nsresult rv = mExtendedSink->HandleStartNamespaceDecl(aPrefix, aUri);
+ MaybeStopParser(rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleEndNamespaceDecl(const char16_t* aPrefix)
+{
+ if (mExtendedSink && mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) {
+ nsresult rv = mExtendedSink->HandleEndNamespaceDecl(aPrefix);
+ MaybeStopParser(rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleNotationDecl(const char16_t* aNotationName,
+ const char16_t* aBase,
+ const char16_t* aSysid,
+ const char16_t* aPubid)
+{
+ if (mExtendedSink) {
+ nsresult rv = mExtendedSink->HandleNotationDecl(aNotationName, aSysid,
+ aPubid);
+ MaybeStopParser(rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleUnparsedEntityDecl(const char16_t* aEntityName,
+ const char16_t* aBase,
+ const char16_t* aSysid,
+ const char16_t* aPubid,
+ const char16_t* aNotationName)
+{
+ if (mExtendedSink) {
+ nsresult rv = mExtendedSink->HandleUnparsedEntityDecl(aEntityName,
+ aSysid,
+ aPubid,
+ aNotationName);
+ MaybeStopParser(rv);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleStartDoctypeDecl(const char16_t* aDoctypeName,
+ const char16_t* aSysid,
+ const char16_t* aPubid,
+ bool aHasInternalSubset)
+{
+ mDoctypeName = aDoctypeName;
+ mSystemID = aSysid;
+ mPublicID = aPubid;
+
+ if (mExtendedSink) {
+ nsresult rv = mExtendedSink->HandleStartDTD(aDoctypeName, aSysid, aPubid);
+ MaybeStopParser(rv);
+ }
+
+ if (aHasInternalSubset) {
+ // Consuming a huge internal subset translates to numerous
+ // allocations. In an effort to avoid too many allocations
+ // setting mInternalSubset's capacity to be 1K ( just a guesstimate! ).
+ mInInternalSubset = true;
+ mInternalSubset.SetCapacity(1024);
+ } else {
+ // Distinguish missing internal subset from an empty one
+ mInternalSubset.SetIsVoid(true);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleEndDoctypeDecl()
+{
+ NS_ASSERTION(mSink, "content sink not found!");
+
+ mInInternalSubset = false;
+
+ if (mSink) {
+ // let the sink know any additional knowledge that we have about the
+ // document (currently, from bug 124570, we only expect to pass additional
+ // agent sheets needed to layout the XML vocabulary of the document)
+ nsCOMPtr<nsIURI> data;
+#if 0
+ if (mCatalogData && mCatalogData->mAgentSheet) {
+ NS_NewURI(getter_AddRefs(data), mCatalogData->mAgentSheet);
+ }
+#endif
+
+ // The unused support for "catalog style sheets" was removed. It doesn't
+ // look like we'll ever fix bug 98413 either.
+ MOZ_ASSERT(!mCatalogData || !mCatalogData->mAgentSheet,
+ "Need to add back support for catalog style sheets");
+
+ // Note: mInternalSubset already doesn't include the [] around it.
+ nsresult rv = mSink->HandleDoctypeDecl(mInternalSubset, mDoctypeName,
+ mSystemID, mPublicID, data);
+ MaybeStopParser(rv);
+ }
+
+ mInternalSubset.SetCapacity(0);
+
+ return NS_OK;
+}
+
+static nsresult
+ExternalDTDStreamReaderFunc(nsIUnicharInputStream* aIn,
+ void* aClosure,
+ const char16_t* aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ // Pass the buffer to expat for parsing.
+ if (XML_Parse((XML_Parser)aClosure, (const char *)aFromSegment,
+ aCount * sizeof(char16_t), 0) == XML_STATUS_OK) {
+ *aWriteCount = aCount;
+
+ return NS_OK;
+ }
+
+ *aWriteCount = 0;
+
+ return NS_ERROR_FAILURE;
+}
+
+int
+nsExpatDriver::HandleExternalEntityRef(const char16_t *openEntityNames,
+ const char16_t *base,
+ const char16_t *systemId,
+ const char16_t *publicId)
+{
+ if (mInInternalSubset && !mInExternalDTD && openEntityNames) {
+ mInternalSubset.Append(char16_t('%'));
+ mInternalSubset.Append(nsDependentString(openEntityNames));
+ mInternalSubset.Append(char16_t(';'));
+ }
+
+ // Load the external entity into a buffer.
+ nsCOMPtr<nsIInputStream> in;
+ nsAutoString absURL;
+ nsresult rv = OpenInputStreamFromExternalDTD(publicId, systemId, base,
+ getter_AddRefs(in), absURL);
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ nsCString message("Failed to open external DTD: publicId \"");
+ AppendUTF16toUTF8(publicId, message);
+ message += "\" systemId \"";
+ AppendUTF16toUTF8(systemId, message);
+ message += "\" base \"";
+ AppendUTF16toUTF8(base, message);
+ message += "\" URL \"";
+ AppendUTF16toUTF8(absURL, message);
+ message += "\"";
+ NS_WARNING(message.get());
+#endif
+ return 1;
+ }
+
+ nsCOMPtr<nsIUnicharInputStream> uniIn;
+ rv = NS_NewUnicharInputStream(in, getter_AddRefs(uniIn));
+ NS_ENSURE_SUCCESS(rv, 1);
+
+ int result = 1;
+ if (uniIn) {
+ XML_Parser entParser = XML_ExternalEntityParserCreate(mExpatParser, 0,
+ kUTF16);
+ if (entParser) {
+ XML_SetBase(entParser, absURL.get());
+
+ mInExternalDTD = true;
+
+ uint32_t totalRead;
+ do {
+ rv = uniIn->ReadSegments(ExternalDTDStreamReaderFunc, entParser,
+ uint32_t(-1), &totalRead);
+ } while (NS_SUCCEEDED(rv) && totalRead > 0);
+
+ result = XML_Parse(entParser, nullptr, 0, 1);
+
+ mInExternalDTD = false;
+
+ XML_ParserFree(entParser);
+ }
+ }
+
+ return result;
+}
+
+nsresult
+nsExpatDriver::OpenInputStreamFromExternalDTD(const char16_t* aFPIStr,
+ const char16_t* aURLStr,
+ const char16_t* aBaseURL,
+ nsIInputStream** aStream,
+ nsAString& aAbsURL)
+{
+ nsCOMPtr<nsIURI> baseURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(baseURI),
+ NS_ConvertUTF16toUTF8(aBaseURL));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), NS_ConvertUTF16toUTF8(aURLStr), nullptr,
+ baseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make sure the URI is allowed to be loaded in sync
+ bool isUIResource = false;
+ rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &isUIResource);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> localURI;
+ if (!isUIResource) {
+ // Check to see if we can map the DTD to a known local DTD, or if a DTD
+ // file of the same name exists in the special DTD directory
+ if (aFPIStr) {
+ // see if the Formal Public Identifier (FPI) maps to a catalog entry
+ mCatalogData = LookupCatalogData(aFPIStr);
+ GetLocalDTDURI(mCatalogData, uri, getter_AddRefs(localURI));
+ }
+ if (!localURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ if (localURI) {
+ localURI.swap(uri);
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_DTD);
+ }
+ else {
+ NS_ASSERTION(mSink == nsCOMPtr<nsIExpatSink>(do_QueryInterface(mOriginalSink)),
+ "In nsExpatDriver::OpenInputStreamFromExternalDTD: "
+ "mOriginalSink not the same object as mSink?");
+ nsCOMPtr<nsIPrincipal> loadingPrincipal;
+ if (mOriginalSink) {
+ nsCOMPtr<nsIDocument> doc;
+ doc = do_QueryInterface(mOriginalSink->GetTarget());
+ if (doc) {
+ loadingPrincipal = doc->NodePrincipal();
+ }
+ }
+ if (!loadingPrincipal) {
+ loadingPrincipal = nsNullPrincipal::Create();
+ }
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ loadingPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_ALLOW_CHROME,
+ nsIContentPolicy::TYPE_DTD);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString absURL;
+ rv = uri->GetSpec(absURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF8toUTF16(absURL, aAbsURL);
+
+ channel->SetContentType(NS_LITERAL_CSTRING("application/xml"));
+ return channel->Open2(aStream);
+}
+
+static nsresult
+CreateErrorText(const char16_t* aDescription,
+ const char16_t* aSourceURL,
+ const uint32_t aLineNumber,
+ const uint32_t aColNumber,
+ nsString& aErrorString)
+{
+ aErrorString.Truncate();
+
+ nsAutoString msg;
+ nsresult rv =
+ nsParserMsgUtils::GetLocalizedStringByName(XMLPARSER_PROPERTIES,
+ "XMLParsingError", msg);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XML Parsing Error: %1$S\nLocation: %2$S\nLine Number %3$u, Column %4$u:
+ char16_t *message = nsTextFormatter::smprintf(msg.get(), aDescription,
+ aSourceURL, aLineNumber,
+ aColNumber);
+ if (!message) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ aErrorString.Assign(message);
+ nsTextFormatter::smprintf_free(message);
+
+ return NS_OK;
+}
+
+static nsresult
+AppendErrorPointer(const int32_t aColNumber,
+ const char16_t *aSourceLine,
+ nsString& aSourceString)
+{
+ aSourceString.Append(char16_t('\n'));
+
+ // Last character will be '^'.
+ int32_t last = aColNumber - 1;
+ int32_t i;
+ uint32_t minuses = 0;
+ for (i = 0; i < last; ++i) {
+ if (aSourceLine[i] == '\t') {
+ // Since this uses |white-space: pre;| a tab stop equals 8 spaces.
+ uint32_t add = 8 - (minuses % 8);
+ aSourceString.AppendASCII("--------", add);
+ minuses += add;
+ }
+ else {
+ aSourceString.Append(char16_t('-'));
+ ++minuses;
+ }
+ }
+ aSourceString.Append(char16_t('^'));
+
+ return NS_OK;
+}
+
+nsresult
+nsExpatDriver::HandleError()
+{
+ int32_t code = XML_GetErrorCode(mExpatParser);
+ NS_ASSERTION(code > XML_ERROR_NONE, "unexpected XML error code");
+
+ // Map Expat error code to an error string
+ // XXX Deal with error returns.
+ nsAutoString description;
+ nsParserMsgUtils::GetLocalizedStringByID(XMLPARSER_PROPERTIES, code,
+ description);
+
+ if (code == XML_ERROR_TAG_MISMATCH) {
+ /**
+ * Expat can send the following:
+ * localName
+ * namespaceURI<separator>localName
+ * namespaceURI<separator>localName<separator>prefix
+ *
+ * and we use 0xFFFF for the <separator>.
+ *
+ */
+ const char16_t *mismatch = MOZ_XML_GetMismatchedTag(mExpatParser);
+ const char16_t *uriEnd = nullptr;
+ const char16_t *nameEnd = nullptr;
+ const char16_t *pos;
+ for (pos = mismatch; *pos; ++pos) {
+ if (*pos == kExpatSeparatorChar) {
+ if (uriEnd) {
+ nameEnd = pos;
+ }
+ else {
+ uriEnd = pos;
+ }
+ }
+ }
+
+ nsAutoString tagName;
+ if (uriEnd && nameEnd) {
+ // We have a prefix.
+ tagName.Append(nameEnd + 1, pos - nameEnd - 1);
+ tagName.Append(char16_t(':'));
+ }
+ const char16_t *nameStart = uriEnd ? uriEnd + 1 : mismatch;
+ tagName.Append(nameStart, (nameEnd ? nameEnd : pos) - nameStart);
+
+ nsAutoString msg;
+ nsParserMsgUtils::GetLocalizedStringByName(XMLPARSER_PROPERTIES,
+ "Expected", msg);
+
+ // . Expected: </%S>.
+ char16_t *message = nsTextFormatter::smprintf(msg.get(), tagName.get());
+ if (!message) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ description.Append(message);
+
+ nsTextFormatter::smprintf_free(message);
+ }
+
+ // Adjust the column number so that it is one based rather than zero based.
+ uint32_t colNumber = XML_GetCurrentColumnNumber(mExpatParser) + 1;
+ uint32_t lineNumber = XML_GetCurrentLineNumber(mExpatParser);
+
+ nsAutoString errorText;
+ CreateErrorText(description.get(), XML_GetBase(mExpatParser), lineNumber,
+ colNumber, errorText);
+
+ NS_ASSERTION(mSink, "no sink?");
+
+ nsAutoString sourceText(mLastLine);
+ AppendErrorPointer(colNumber, mLastLine.get(), sourceText);
+
+ // Try to create and initialize the script error.
+ nsCOMPtr<nsIScriptError> serr(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ nsresult rv = NS_ERROR_FAILURE;
+ if (serr) {
+ rv = serr->InitWithWindowID(errorText,
+ mURISpec,
+ mLastLine,
+ lineNumber, colNumber,
+ nsIScriptError::errorFlag, "malformed-xml",
+ mInnerWindowID);
+ }
+
+ // If it didn't initialize, we can't do any logging.
+ bool shouldReportError = NS_SUCCEEDED(rv);
+
+ if (mSink && shouldReportError) {
+ rv = mSink->ReportError(errorText.get(),
+ sourceText.get(),
+ serr,
+ &shouldReportError);
+ if (NS_FAILED(rv)) {
+ shouldReportError = true;
+ }
+ }
+
+ if (mOriginalSink) {
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(mOriginalSink->GetTarget());
+ if (doc && doc->SuppressParserErrorConsoleMessages()) {
+ shouldReportError = false;
+ }
+ }
+
+ if (shouldReportError) {
+ nsCOMPtr<nsIConsoleService> cs
+ (do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (cs) {
+ cs->LogMessage(serr);
+ }
+ }
+
+ return NS_ERROR_HTMLPARSER_STOPPARSING;
+}
+
+void
+nsExpatDriver::ParseBuffer(const char16_t *aBuffer,
+ uint32_t aLength,
+ bool aIsFinal,
+ uint32_t *aConsumed)
+{
+ NS_ASSERTION((aBuffer && aLength != 0) || (!aBuffer && aLength == 0), "?");
+ NS_ASSERTION(mInternalState != NS_OK || aIsFinal || aBuffer,
+ "Useless call, we won't call Expat");
+ NS_PRECONDITION(!BlockedOrInterrupted() || !aBuffer,
+ "Non-null buffer when resuming");
+ NS_PRECONDITION(XML_GetCurrentByteIndex(mExpatParser) % sizeof(char16_t) == 0,
+ "Consumed part of a char16_t?");
+
+ if (mExpatParser && (mInternalState == NS_OK || BlockedOrInterrupted())) {
+ int32_t parserBytesBefore = XML_GetCurrentByteIndex(mExpatParser);
+ NS_ASSERTION(parserBytesBefore >= 0, "Unexpected value");
+
+ XML_Status status;
+ if (BlockedOrInterrupted()) {
+ mInternalState = NS_OK; // Resume in case we're blocked.
+ status = XML_ResumeParser(mExpatParser);
+ }
+ else {
+ status = XML_Parse(mExpatParser,
+ reinterpret_cast<const char*>(aBuffer),
+ aLength * sizeof(char16_t), aIsFinal);
+ }
+
+ int32_t parserBytesConsumed = XML_GetCurrentByteIndex(mExpatParser);
+
+ NS_ASSERTION(parserBytesConsumed >= 0, "Unexpected value");
+ NS_ASSERTION(parserBytesConsumed >= parserBytesBefore,
+ "How'd this happen?");
+ NS_ASSERTION(parserBytesConsumed % sizeof(char16_t) == 0,
+ "Consumed part of a char16_t?");
+
+ // Consumed something.
+ *aConsumed = (parserBytesConsumed - parserBytesBefore) / sizeof(char16_t);
+ NS_ASSERTION(*aConsumed <= aLength + mExpatBuffered,
+ "Too many bytes consumed?");
+
+ NS_ASSERTION(status != XML_STATUS_SUSPENDED || BlockedOrInterrupted(),
+ "Inconsistent expat suspension state.");
+
+ if (status == XML_STATUS_ERROR) {
+ mInternalState = NS_ERROR_HTMLPARSER_STOPPARSING;
+ }
+ }
+ else {
+ *aConsumed = 0;
+ }
+}
+
+NS_IMETHODIMP
+nsExpatDriver::ConsumeToken(nsScanner& aScanner, bool& aFlushTokens)
+{
+ // We keep the scanner pointing to the position where Expat will start
+ // parsing.
+ nsScannerIterator currentExpatPosition;
+ aScanner.CurrentPosition(currentExpatPosition);
+
+ // This is the start of the first buffer that we need to pass to Expat.
+ nsScannerIterator start = currentExpatPosition;
+ start.advance(mExpatBuffered);
+
+ // This is the end of the last buffer (at this point, more data could come in
+ // later).
+ nsScannerIterator end;
+ aScanner.EndReading(end);
+
+ MOZ_LOG(gExpatDriverLog, LogLevel::Debug,
+ ("Remaining in expat's buffer: %i, remaining in scanner: %i.",
+ mExpatBuffered, Distance(start, end)));
+
+ // We want to call Expat if we have more buffers, or if we know there won't
+ // be more buffers (and so we want to flush the remaining data), or if we're
+ // currently blocked and there's data in Expat's buffer.
+ while (start != end || (mIsFinalChunk && !mMadeFinalCallToExpat) ||
+ (BlockedOrInterrupted() && mExpatBuffered > 0)) {
+ bool noMoreBuffers = start == end && mIsFinalChunk;
+ bool blocked = BlockedOrInterrupted();
+
+ const char16_t *buffer;
+ uint32_t length;
+ if (blocked || noMoreBuffers) {
+ // If we're blocked we just resume Expat so we don't need a buffer, if
+ // there aren't any more buffers we pass a null buffer to Expat.
+ buffer = nullptr;
+ length = 0;
+
+ if (blocked) {
+ MOZ_LOG(gExpatDriverLog, LogLevel::Debug,
+ ("Resuming Expat, will parse data remaining in Expat's "
+ "buffer.\nContent of Expat's buffer:\n-----\n%s\n-----\n",
+ NS_ConvertUTF16toUTF8(currentExpatPosition.get(),
+ mExpatBuffered).get()));
+ }
+ else {
+ NS_ASSERTION(mExpatBuffered == Distance(currentExpatPosition, end),
+ "Didn't pass all the data to Expat?");
+ MOZ_LOG(gExpatDriverLog, LogLevel::Debug,
+ ("Last call to Expat, will parse data remaining in Expat's "
+ "buffer.\nContent of Expat's buffer:\n-----\n%s\n-----\n",
+ NS_ConvertUTF16toUTF8(currentExpatPosition.get(),
+ mExpatBuffered).get()));
+ }
+ }
+ else {
+ buffer = start.get();
+ length = uint32_t(start.size_forward());
+
+ MOZ_LOG(gExpatDriverLog, LogLevel::Debug,
+ ("Calling Expat, will parse data remaining in Expat's buffer and "
+ "new data.\nContent of Expat's buffer:\n-----\n%s\n-----\nNew "
+ "data:\n-----\n%s\n-----\n",
+ NS_ConvertUTF16toUTF8(currentExpatPosition.get(),
+ mExpatBuffered).get(),
+ NS_ConvertUTF16toUTF8(start.get(), length).get()));
+ }
+
+ uint32_t consumed;
+ ParseBuffer(buffer, length, noMoreBuffers, &consumed);
+ if (consumed > 0) {
+ nsScannerIterator oldExpatPosition = currentExpatPosition;
+ currentExpatPosition.advance(consumed);
+
+ // We consumed some data, we want to store the last line of data that
+ // was consumed in case we run into an error (to show the line in which
+ // the error occurred).
+
+ // The length of the last line that Expat has parsed.
+ XML_Size lastLineLength = XML_GetCurrentColumnNumber(mExpatParser);
+
+ if (lastLineLength <= consumed) {
+ // The length of the last line was less than what expat consumed, so
+ // there was at least one line break in the consumed data. Store the
+ // last line until the point where we stopped parsing.
+ nsScannerIterator startLastLine = currentExpatPosition;
+ startLastLine.advance(-((ptrdiff_t)lastLineLength));
+ if (!CopyUnicodeTo(startLastLine, currentExpatPosition, mLastLine)) {
+ return (mInternalState = NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ else {
+ // There was no line break in the consumed data, append the consumed
+ // data.
+ if (!AppendUnicodeTo(oldExpatPosition,
+ currentExpatPosition,
+ mLastLine)) {
+ return (mInternalState = NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+ }
+
+ mExpatBuffered += length - consumed;
+
+ if (BlockedOrInterrupted()) {
+ MOZ_LOG(gExpatDriverLog, LogLevel::Debug,
+ ("Blocked or interrupted parser (probably for loading linked "
+ "stylesheets or scripts)."));
+
+ aScanner.SetPosition(currentExpatPosition, true);
+ aScanner.Mark();
+
+ return mInternalState;
+ }
+
+ if (noMoreBuffers && mExpatBuffered == 0) {
+ mMadeFinalCallToExpat = true;
+ }
+
+ if (NS_FAILED(mInternalState)) {
+ if (XML_GetErrorCode(mExpatParser) != XML_ERROR_NONE) {
+ NS_ASSERTION(mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING,
+ "Unexpected error");
+
+ // Look for the next newline after the last one we consumed
+ nsScannerIterator lastLine = currentExpatPosition;
+ while (lastLine != end) {
+ length = uint32_t(lastLine.size_forward());
+ uint32_t endOffset = 0;
+ const char16_t *buffer = lastLine.get();
+ while (endOffset < length && buffer[endOffset] != '\n' &&
+ buffer[endOffset] != '\r') {
+ ++endOffset;
+ }
+ mLastLine.Append(Substring(buffer, buffer + endOffset));
+ if (endOffset < length) {
+ // We found a newline.
+ break;
+ }
+
+ lastLine.advance(length);
+ }
+
+ HandleError();
+ }
+
+ return mInternalState;
+ }
+
+ // Either we have more buffers, or we were blocked (and we'll flush in the
+ // next iteration), or we should have emptied Expat's buffer.
+ NS_ASSERTION(!noMoreBuffers || blocked ||
+ (mExpatBuffered == 0 && currentExpatPosition == end),
+ "Unreachable data left in Expat's buffer");
+
+ start.advance(length);
+
+ // It's possible for start to have passed end if we received more data
+ // (e.g. if we spun the event loop in an inline script). Reload end now
+ // to compensate.
+ aScanner.EndReading(end);
+ }
+
+ aScanner.SetPosition(currentExpatPosition, true);
+ aScanner.Mark();
+
+ MOZ_LOG(gExpatDriverLog, LogLevel::Debug,
+ ("Remaining in expat's buffer: %i, remaining in scanner: %i.",
+ mExpatBuffered, Distance(currentExpatPosition, end)));
+
+ return NS_SUCCEEDED(mInternalState) ? kEOF : NS_OK;
+}
+
+NS_IMETHODIMP
+nsExpatDriver::WillBuildModel(const CParserContext& aParserContext,
+ nsITokenizer* aTokenizer,
+ nsIContentSink* aSink)
+{
+ mSink = do_QueryInterface(aSink);
+ if (!mSink) {
+ NS_ERROR("nsExpatDriver didn't get an nsIExpatSink");
+ // Make sure future calls to us bail out as needed
+ mInternalState = NS_ERROR_UNEXPECTED;
+ return mInternalState;
+ }
+
+ mOriginalSink = aSink;
+
+ static const XML_Memory_Handling_Suite memsuite =
+ {
+ (void *(*)(size_t))PR_Malloc,
+ (void *(*)(void *, size_t))PR_Realloc,
+ PR_Free
+ };
+
+ static const char16_t kExpatSeparator[] = { kExpatSeparatorChar, '\0' };
+
+ mExpatParser = XML_ParserCreate_MM(kUTF16, &memsuite, kExpatSeparator);
+ NS_ENSURE_TRUE(mExpatParser, NS_ERROR_FAILURE);
+
+ XML_SetReturnNSTriplet(mExpatParser, XML_TRUE);
+
+#ifdef XML_DTD
+ XML_SetParamEntityParsing(mExpatParser, XML_PARAM_ENTITY_PARSING_ALWAYS);
+#endif
+
+ mURISpec = aParserContext.mScanner->GetFilename();
+
+ XML_SetBase(mExpatParser, mURISpec.get());
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(mOriginalSink->GetTarget());
+ if (doc) {
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ nsCOMPtr<nsPIDOMWindowInner> inner;
+ if (win) {
+ inner = win->GetCurrentInnerWindow();
+ } else {
+ bool aHasHadScriptHandlingObject;
+ nsIScriptGlobalObject *global =
+ doc->GetScriptHandlingObject(aHasHadScriptHandlingObject);
+ if (global) {
+ inner = do_QueryInterface(global);
+ }
+ }
+ if (inner) {
+ mInnerWindowID = inner->WindowID();
+ }
+ }
+
+ // Set up the callbacks
+ XML_SetXmlDeclHandler(mExpatParser, Driver_HandleXMLDeclaration);
+ XML_SetElementHandler(mExpatParser, Driver_HandleStartElement,
+ Driver_HandleEndElement);
+ XML_SetCharacterDataHandler(mExpatParser, Driver_HandleCharacterData);
+ XML_SetProcessingInstructionHandler(mExpatParser,
+ Driver_HandleProcessingInstruction);
+ XML_SetDefaultHandlerExpand(mExpatParser, Driver_HandleDefault);
+ XML_SetExternalEntityRefHandler(mExpatParser,
+ (XML_ExternalEntityRefHandler)
+ Driver_HandleExternalEntityRef);
+ XML_SetExternalEntityRefHandlerArg(mExpatParser, this);
+ XML_SetCommentHandler(mExpatParser, Driver_HandleComment);
+ XML_SetCdataSectionHandler(mExpatParser, Driver_HandleStartCdataSection,
+ Driver_HandleEndCdataSection);
+
+ XML_SetParamEntityParsing(mExpatParser,
+ XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE);
+ XML_SetDoctypeDeclHandler(mExpatParser, Driver_HandleStartDoctypeDecl,
+ Driver_HandleEndDoctypeDecl);
+
+ // If the sink is an nsIExtendedExpatSink,
+ // register some addtional handlers.
+ mExtendedSink = do_QueryInterface(mSink);
+ if (mExtendedSink) {
+ XML_SetNamespaceDeclHandler(mExpatParser,
+ Driver_HandleStartNamespaceDecl,
+ Driver_HandleEndNamespaceDecl);
+ XML_SetUnparsedEntityDeclHandler(mExpatParser,
+ Driver_HandleUnparsedEntityDecl);
+ XML_SetNotationDeclHandler(mExpatParser,
+ Driver_HandleNotationDecl);
+ }
+
+ // Set up the user data.
+ XML_SetUserData(mExpatParser, this);
+
+ return mInternalState;
+}
+
+NS_IMETHODIMP
+nsExpatDriver::BuildModel(nsITokenizer* aTokenizer, nsIContentSink* aSink)
+{
+ return mInternalState;
+}
+
+NS_IMETHODIMP
+nsExpatDriver::DidBuildModel(nsresult anErrorCode)
+{
+ mOriginalSink = nullptr;
+ mSink = nullptr;
+ mExtendedSink = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsExpatDriver::WillTokenize(bool aIsFinalChunk)
+{
+ mIsFinalChunk = aIsFinalChunk;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsExpatDriver::Terminate()
+{
+ // XXX - not sure what happens to the unparsed data.
+ if (mExpatParser) {
+ XML_StopParser(mExpatParser, XML_FALSE);
+ }
+ mInternalState = NS_ERROR_HTMLPARSER_STOPPARSING;
+}
+
+NS_IMETHODIMP_(int32_t)
+nsExpatDriver::GetType()
+{
+ return NS_IPARSER_FLAG_XML;
+}
+
+NS_IMETHODIMP_(nsDTDMode)
+nsExpatDriver::GetMode() const
+{
+ return eDTDMode_full_standards;
+}
+
+/*************************** Unused methods **********************************/
+
+NS_IMETHODIMP_(bool)
+nsExpatDriver::IsContainer(int32_t aTag) const
+{
+ return true;
+}
+
+NS_IMETHODIMP_(bool)
+nsExpatDriver::CanContain(int32_t aParent,int32_t aChild) const
+{
+ return true;
+}
+
+void
+nsExpatDriver::MaybeStopParser(nsresult aState)
+{
+ if (NS_FAILED(aState)) {
+ // If we had a failure we want to override NS_ERROR_HTMLPARSER_INTERRUPTED
+ // and we want to override NS_ERROR_HTMLPARSER_BLOCK but not with
+ // NS_ERROR_HTMLPARSER_INTERRUPTED.
+ if (NS_SUCCEEDED(mInternalState) ||
+ mInternalState == NS_ERROR_HTMLPARSER_INTERRUPTED ||
+ (mInternalState == NS_ERROR_HTMLPARSER_BLOCK &&
+ aState != NS_ERROR_HTMLPARSER_INTERRUPTED)) {
+ mInternalState = (aState == NS_ERROR_HTMLPARSER_INTERRUPTED ||
+ aState == NS_ERROR_HTMLPARSER_BLOCK) ?
+ aState :
+ NS_ERROR_HTMLPARSER_STOPPARSING;
+ }
+
+ // If we get an error then we need to stop Expat (by calling XML_StopParser
+ // with false as the last argument). If the parser should be blocked or
+ // interrupted we need to pause Expat (by calling XML_StopParser with
+ // true as the last argument).
+ XML_StopParser(mExpatParser, BlockedOrInterrupted());
+ }
+ else if (NS_SUCCEEDED(mInternalState)) {
+ // Only clobber mInternalState with the success code if we didn't block or
+ // interrupt before.
+ mInternalState = aState;
+ }
+}
diff --git a/components/htmlparser/src/nsExpatDriver.h b/components/htmlparser/src/nsExpatDriver.h
new file mode 100644
index 000000000..988409cfe
--- /dev/null
+++ b/components/htmlparser/src/nsExpatDriver.h
@@ -0,0 +1,145 @@
+/* -*- 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 NS_EXPAT_DRIVER__
+#define NS_EXPAT_DRIVER__
+
+#include "expat_config.h"
+#include "expat.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIDTD.h"
+#include "nsITokenizer.h"
+#include "nsIInputStream.h"
+#include "nsIParser.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsIExpatSink;
+class nsIExtendedExpatSink;
+struct nsCatalogData;
+
+class nsExpatDriver : public nsIDTD,
+ public nsITokenizer
+{
+ virtual ~nsExpatDriver();
+
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIDTD
+ NS_DECL_NSITOKENIZER
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsExpatDriver, nsIDTD)
+
+ nsExpatDriver();
+
+ int HandleExternalEntityRef(const char16_t *aOpenEntityNames,
+ const char16_t *aBase,
+ const char16_t *aSystemId,
+ const char16_t *aPublicId);
+ void HandleStartElement(const char16_t *aName, const char16_t **aAtts);
+ nsresult HandleEndElement(const char16_t *aName);
+ nsresult HandleCharacterData(const char16_t *aCData, const uint32_t aLength);
+ nsresult HandleComment(const char16_t *aName);
+ nsresult HandleProcessingInstruction(const char16_t *aTarget,
+ const char16_t *aData);
+ nsresult HandleXMLDeclaration(const char16_t *aVersion,
+ const char16_t *aEncoding,
+ int32_t aStandalone);
+ nsresult HandleDefault(const char16_t *aData, const uint32_t aLength);
+ nsresult HandleStartCdataSection();
+ nsresult HandleEndCdataSection();
+ nsresult HandleStartDoctypeDecl(const char16_t* aDoctypeName,
+ const char16_t* aSysid,
+ const char16_t* aPubid,
+ bool aHasInternalSubset);
+ nsresult HandleEndDoctypeDecl();
+ nsresult HandleStartNamespaceDecl(const char16_t* aPrefix,
+ const char16_t* aUri);
+ nsresult HandleEndNamespaceDecl(const char16_t* aPrefix);
+ nsresult HandleNotationDecl(const char16_t* aNotationName,
+ const char16_t* aBase,
+ const char16_t* aSysid,
+ const char16_t* aPubid);
+ nsresult HandleUnparsedEntityDecl(const char16_t* aEntityName,
+ const char16_t* aBase,
+ const char16_t* aSysid,
+ const char16_t* aPubid,
+ const char16_t* aNotationName);
+
+private:
+ // Load up an external stream to get external entity information
+ nsresult OpenInputStreamFromExternalDTD(const char16_t* aFPIStr,
+ const char16_t* aURLStr,
+ const char16_t* aBaseURL,
+ nsIInputStream** aStream,
+ nsAString& aAbsURL);
+
+ /**
+ * Pass a buffer to Expat. If Expat is blocked aBuffer should be null and
+ * aLength should be 0. The result of the call will be stored in
+ * mInternalState. Expat will parse as much of the buffer as it can and store
+ * the rest in its internal buffer.
+ *
+ * @param aBuffer the buffer to pass to Expat. May be null.
+ * @param aLength the length of the buffer to pass to Expat (in number of
+ * char16_t's). Must be 0 if aBuffer is null and > 0 if
+ * aBuffer is not null.
+ * @param aIsFinal whether there will definitely not be any more new buffers
+ * passed in to ParseBuffer
+ * @param aConsumed [out] the number of PRUnichars that Expat consumed. This
+ * doesn't include the PRUnichars that Expat stored in
+ * its buffer but didn't parse yet.
+ */
+ void ParseBuffer(const char16_t *aBuffer, uint32_t aLength, bool aIsFinal,
+ uint32_t *aConsumed);
+ nsresult HandleError();
+
+ void MaybeStopParser(nsresult aState);
+
+ bool BlockedOrInterrupted()
+ {
+ return mInternalState == NS_ERROR_HTMLPARSER_BLOCK ||
+ mInternalState == NS_ERROR_HTMLPARSER_INTERRUPTED;
+ }
+
+ XML_Parser mExpatParser;
+ nsString mLastLine;
+ nsString mCDataText;
+ // Various parts of a doctype
+ nsString mDoctypeName;
+ nsString mSystemID;
+ nsString mPublicID;
+ nsString mInternalSubset;
+ bool mInCData;
+ bool mInInternalSubset;
+ bool mInExternalDTD;
+ bool mMadeFinalCallToExpat;
+
+ // Whether we're sure that we won't be getting more buffers to parse from
+ // Necko
+ bool mIsFinalChunk;
+
+ // The depth of nested parsing we are currently at
+ uint16_t mTagDepth;
+
+ nsresult mInternalState;
+
+ // The length of the data in Expat's buffer (in number of PRUnichars).
+ uint32_t mExpatBuffered;
+
+ // These sinks all refer the same conceptual object. mOriginalSink is
+ // identical with the nsIContentSink* passed to WillBuildModel, and exists
+ // only to avoid QI-ing back to nsIContentSink*.
+ nsCOMPtr<nsIContentSink> mOriginalSink;
+ nsCOMPtr<nsIExpatSink> mSink;
+ nsCOMPtr<nsIExtendedExpatSink> mExtendedSink;
+
+ const nsCatalogData* mCatalogData; // weak
+ nsString mURISpec;
+
+ // Used for error reporting.
+ uint64_t mInnerWindowID;
+};
+
+#endif
diff --git a/components/htmlparser/src/nsHTMLEntities.cpp b/components/htmlparser/src/nsHTMLEntities.cpp
new file mode 100644
index 000000000..e8365c21f
--- /dev/null
+++ b/components/htmlparser/src/nsHTMLEntities.cpp
@@ -0,0 +1,205 @@
+/* -*- 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 "nsHTMLEntities.h"
+
+#include "nsString.h"
+#include "nsCRT.h"
+#include "PLDHashTable.h"
+
+using namespace mozilla;
+
+struct EntityNode {
+ const char* mStr; // never owns buffer
+ int32_t mUnicode;
+};
+
+struct EntityNodeEntry : public PLDHashEntryHdr
+{
+ const EntityNode* node;
+};
+
+static bool matchNodeString(const PLDHashEntryHdr* aHdr, const void* key)
+{
+ const EntityNodeEntry* entry = static_cast<const EntityNodeEntry*>(aHdr);
+ const char* str = static_cast<const char*>(key);
+ return (nsCRT::strcmp(entry->node->mStr, str) == 0);
+}
+
+static bool matchNodeUnicode(const PLDHashEntryHdr* aHdr, const void* key)
+{
+ const EntityNodeEntry* entry = static_cast<const EntityNodeEntry*>(aHdr);
+ const int32_t ucode = NS_PTR_TO_INT32(key);
+ return (entry->node->mUnicode == ucode);
+}
+
+static PLDHashNumber hashUnicodeValue(const void* key)
+{
+ // key is actually the unicode value
+ return PLDHashNumber(NS_PTR_TO_INT32(key));
+}
+
+
+static const PLDHashTableOps EntityToUnicodeOps = {
+ PLDHashTable::HashStringKey,
+ matchNodeString,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr,
+};
+
+static const PLDHashTableOps UnicodeToEntityOps = {
+ hashUnicodeValue,
+ matchNodeUnicode,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr,
+};
+
+static PLDHashTable* gEntityToUnicode;
+static PLDHashTable* gUnicodeToEntity;
+static nsrefcnt gTableRefCnt = 0;
+
+#define HTML_ENTITY(_name, _value) { #_name, _value },
+static const EntityNode gEntityArray[] = {
+#include "nsHTMLEntityList.h"
+};
+#undef HTML_ENTITY
+
+#define NS_HTML_ENTITY_COUNT ((int32_t)ArrayLength(gEntityArray))
+
+nsresult
+nsHTMLEntities::AddRefTable(void)
+{
+ if (!gTableRefCnt) {
+ gEntityToUnicode = new PLDHashTable(&EntityToUnicodeOps,
+ sizeof(EntityNodeEntry),
+ NS_HTML_ENTITY_COUNT);
+ gUnicodeToEntity = new PLDHashTable(&UnicodeToEntityOps,
+ sizeof(EntityNodeEntry),
+ NS_HTML_ENTITY_COUNT);
+ for (const EntityNode *node = gEntityArray,
+ *node_end = ArrayEnd(gEntityArray);
+ node < node_end; ++node) {
+
+ // add to Entity->Unicode table
+ auto entry = static_cast<EntityNodeEntry*>
+ (gEntityToUnicode->Add(node->mStr, fallible));
+ NS_ASSERTION(entry, "Error adding an entry");
+ // Prefer earlier entries when we have duplication.
+ if (!entry->node)
+ entry->node = node;
+
+ // add to Unicode->Entity table
+ entry = static_cast<EntityNodeEntry*>
+ (gUnicodeToEntity->Add(NS_INT32_TO_PTR(node->mUnicode),
+ fallible));
+ NS_ASSERTION(entry, "Error adding an entry");
+ // Prefer earlier entries when we have duplication.
+ if (!entry->node)
+ entry->node = node;
+ }
+#ifdef DEBUG
+ gUnicodeToEntity->MarkImmutable();
+ gEntityToUnicode->MarkImmutable();
+#endif
+ }
+ ++gTableRefCnt;
+ return NS_OK;
+}
+
+void
+nsHTMLEntities::ReleaseTable(void)
+{
+ if (--gTableRefCnt != 0) {
+ return;
+ }
+
+ delete gEntityToUnicode;
+ delete gUnicodeToEntity;
+ gEntityToUnicode = nullptr;
+ gUnicodeToEntity = nullptr;
+}
+
+int32_t
+nsHTMLEntities::EntityToUnicode(const nsCString& aEntity)
+{
+ NS_ASSERTION(gEntityToUnicode, "no lookup table, needs addref");
+ if (!gEntityToUnicode) {
+ return -1;
+ }
+
+ //this little piece of code exists because entities may or may not have the terminating ';'.
+ //if we see it, strip if off for this test...
+
+ if(';'==aEntity.Last()) {
+ nsAutoCString temp(aEntity);
+ temp.Truncate(aEntity.Length()-1);
+ return EntityToUnicode(temp);
+ }
+
+ auto entry =
+ static_cast<EntityNodeEntry*>(gEntityToUnicode->Search(aEntity.get()));
+
+ return entry ? entry->node->mUnicode : -1;
+}
+
+
+int32_t
+nsHTMLEntities::EntityToUnicode(const nsAString& aEntity) {
+ nsAutoCString theEntity; theEntity.AssignWithConversion(aEntity);
+ if(';'==theEntity.Last()) {
+ theEntity.Truncate(theEntity.Length()-1);
+ }
+
+ return EntityToUnicode(theEntity);
+}
+
+
+const char*
+nsHTMLEntities::UnicodeToEntity(int32_t aUnicode)
+{
+ NS_ASSERTION(gUnicodeToEntity, "no lookup table, needs addref");
+ auto entry =
+ static_cast<EntityNodeEntry*>
+ (gUnicodeToEntity->Search(NS_INT32_TO_PTR(aUnicode)));
+
+ return entry ? entry->node->mStr : nullptr;
+}
+
+#ifdef DEBUG
+#include <stdio.h>
+
+class nsTestEntityTable {
+public:
+ nsTestEntityTable() {
+ int32_t value;
+ nsHTMLEntities::AddRefTable();
+
+ // Make sure we can find everything we are supposed to
+ for (int i = 0; i < NS_HTML_ENTITY_COUNT; ++i) {
+ nsAutoString entity; entity.AssignWithConversion(gEntityArray[i].mStr);
+
+ value = nsHTMLEntities::EntityToUnicode(entity);
+ NS_ASSERTION(value != -1, "can't find entity");
+ NS_ASSERTION(value == gEntityArray[i].mUnicode, "bad unicode value");
+
+ entity.AssignWithConversion(nsHTMLEntities::UnicodeToEntity(value));
+ NS_ASSERTION(entity.EqualsASCII(gEntityArray[i].mStr), "bad entity name");
+ }
+
+ // Make sure we don't find things that aren't there
+ value = nsHTMLEntities::EntityToUnicode(nsAutoCString("@"));
+ NS_ASSERTION(value == -1, "found @");
+ value = nsHTMLEntities::EntityToUnicode(nsAutoCString("zzzzz"));
+ NS_ASSERTION(value == -1, "found zzzzz");
+ nsHTMLEntities::ReleaseTable();
+ }
+};
+//nsTestEntityTable validateEntityTable;
+#endif
+
diff --git a/components/htmlparser/src/nsHTMLEntities.h b/components/htmlparser/src/nsHTMLEntities.h
new file mode 100644
index 000000000..f38856bfa
--- /dev/null
+++ b/components/htmlparser/src/nsHTMLEntities.h
@@ -0,0 +1,35 @@
+/* -*- 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 nsHTMLEntities_h___
+#define nsHTMLEntities_h___
+
+#include "nsString.h"
+
+class nsHTMLEntities {
+public:
+
+ static nsresult AddRefTable(void);
+ static void ReleaseTable(void);
+
+/**
+ * Translate an entity string into it's unicode value. This call
+ * returns -1 if the entity cannot be mapped. Note that the string
+ * passed in must NOT have the leading "&" nor the trailing ";"
+ * in it.
+ */
+ static int32_t EntityToUnicode(const nsAString& aEntity);
+ static int32_t EntityToUnicode(const nsCString& aEntity);
+
+/**
+ * Translate a unicode value into an entity string. This call
+ * returns null if the entity cannot be mapped.
+ * Note that the string returned DOES NOT have the leading "&" nor
+ * the trailing ";" in it.
+ */
+ static const char* UnicodeToEntity(int32_t aUnicode);
+};
+
+
+#endif /* nsHTMLEntities_h___ */
diff --git a/components/htmlparser/src/nsHTMLEntityList.h b/components/htmlparser/src/nsHTMLEntityList.h
new file mode 100644
index 000000000..fa05382bf
--- /dev/null
+++ b/components/htmlparser/src/nsHTMLEntityList.h
@@ -0,0 +1,303 @@
+/* -*- 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/. */
+
+/******
+
+ This file contains the list of all HTML entities
+ See nsHTMLEntities.h for access to the enum values for entities
+
+ It is designed to be used as inline input to nsHTMLEntities.cpp *only*
+ through the magic of C preprocessing.
+
+ All entries must be enclosed in the macro HTML_ENTITY which will have cruel
+ and unusual things done to it
+
+ It is recommended (but not strictly necessary) to keep all entries
+ in alphabetical order
+
+ The first argument to HTML_ENTITY is the string value of the entity
+ The second argument it HTML_ENTITY is the unicode value of the entity
+
+ ******/
+
+// ISO 8859-1 entities.
+// See the HTML4.0 spec for this list in it's DTD form
+HTML_ENTITY(nbsp, 160)
+HTML_ENTITY(iexcl, 161)
+HTML_ENTITY(cent, 162)
+HTML_ENTITY(pound, 163)
+HTML_ENTITY(curren, 164)
+HTML_ENTITY(yen, 165)
+HTML_ENTITY(brvbar, 166)
+HTML_ENTITY(sect, 167)
+HTML_ENTITY(uml, 168)
+HTML_ENTITY(copy, 169)
+HTML_ENTITY(ordf, 170)
+HTML_ENTITY(laquo, 171)
+HTML_ENTITY(not, 172)
+HTML_ENTITY(shy, 173)
+HTML_ENTITY(reg, 174)
+HTML_ENTITY(macr, 175)
+HTML_ENTITY(deg, 176)
+HTML_ENTITY(plusmn, 177)
+HTML_ENTITY(sup2, 178)
+HTML_ENTITY(sup3, 179)
+HTML_ENTITY(acute, 180)
+HTML_ENTITY(micro, 181)
+HTML_ENTITY(para, 182)
+HTML_ENTITY(middot, 183)
+HTML_ENTITY(cedil, 184)
+HTML_ENTITY(sup1, 185)
+HTML_ENTITY(ordm, 186)
+HTML_ENTITY(raquo, 187)
+HTML_ENTITY(frac14, 188)
+HTML_ENTITY(frac12, 189)
+HTML_ENTITY(frac34, 190)
+HTML_ENTITY(iquest, 191)
+HTML_ENTITY(Agrave, 192)
+HTML_ENTITY(Aacute, 193)
+HTML_ENTITY(Acirc, 194)
+HTML_ENTITY(Atilde, 195)
+HTML_ENTITY(Auml, 196)
+HTML_ENTITY(Aring, 197)
+HTML_ENTITY(AElig, 198)
+HTML_ENTITY(Ccedil, 199)
+HTML_ENTITY(Egrave, 200)
+HTML_ENTITY(Eacute, 201)
+HTML_ENTITY(Ecirc, 202)
+HTML_ENTITY(Euml, 203)
+HTML_ENTITY(Igrave, 204)
+HTML_ENTITY(Iacute, 205)
+HTML_ENTITY(Icirc, 206)
+HTML_ENTITY(Iuml, 207)
+HTML_ENTITY(ETH, 208)
+HTML_ENTITY(Ntilde, 209)
+HTML_ENTITY(Ograve, 210)
+HTML_ENTITY(Oacute, 211)
+HTML_ENTITY(Ocirc, 212)
+HTML_ENTITY(Otilde, 213)
+HTML_ENTITY(Ouml, 214)
+HTML_ENTITY(times, 215)
+HTML_ENTITY(Oslash, 216)
+HTML_ENTITY(Ugrave, 217)
+HTML_ENTITY(Uacute, 218)
+HTML_ENTITY(Ucirc, 219)
+HTML_ENTITY(Uuml, 220)
+HTML_ENTITY(Yacute, 221)
+HTML_ENTITY(THORN, 222)
+HTML_ENTITY(szlig, 223)
+HTML_ENTITY(agrave, 224)
+HTML_ENTITY(aacute, 225)
+HTML_ENTITY(acirc, 226)
+HTML_ENTITY(atilde, 227)
+HTML_ENTITY(auml, 228)
+HTML_ENTITY(aring, 229)
+HTML_ENTITY(aelig, 230)
+HTML_ENTITY(ccedil, 231)
+HTML_ENTITY(egrave, 232)
+HTML_ENTITY(eacute, 233)
+HTML_ENTITY(ecirc, 234)
+HTML_ENTITY(euml, 235)
+HTML_ENTITY(igrave, 236)
+HTML_ENTITY(iacute, 237)
+HTML_ENTITY(icirc, 238)
+HTML_ENTITY(iuml, 239)
+HTML_ENTITY(eth, 240)
+HTML_ENTITY(ntilde, 241)
+HTML_ENTITY(ograve, 242)
+HTML_ENTITY(oacute, 243)
+HTML_ENTITY(ocirc, 244)
+HTML_ENTITY(otilde, 245)
+HTML_ENTITY(ouml, 246)
+HTML_ENTITY(divide, 247)
+HTML_ENTITY(oslash, 248)
+HTML_ENTITY(ugrave, 249)
+HTML_ENTITY(uacute, 250)
+HTML_ENTITY(ucirc, 251)
+HTML_ENTITY(uuml, 252)
+HTML_ENTITY(yacute, 253)
+HTML_ENTITY(thorn, 254)
+HTML_ENTITY(yuml, 255)
+
+// Symbols, mathematical symbols and Greek letters
+// See the HTML4.0 spec for this list in it's DTD form
+HTML_ENTITY(fnof, 402)
+HTML_ENTITY(Alpha, 913)
+HTML_ENTITY(Beta, 914)
+HTML_ENTITY(Gamma, 915)
+HTML_ENTITY(Delta, 916)
+HTML_ENTITY(Epsilon, 917)
+HTML_ENTITY(Zeta, 918)
+HTML_ENTITY(Eta, 919)
+HTML_ENTITY(Theta, 920)
+HTML_ENTITY(Iota, 921)
+HTML_ENTITY(Kappa, 922)
+HTML_ENTITY(Lambda, 923)
+HTML_ENTITY(Mu, 924)
+HTML_ENTITY(Nu, 925)
+HTML_ENTITY(Xi, 926)
+HTML_ENTITY(Omicron, 927)
+HTML_ENTITY(Pi, 928)
+HTML_ENTITY(Rho, 929)
+HTML_ENTITY(Sigma, 931)
+HTML_ENTITY(Tau, 932)
+HTML_ENTITY(Upsilon, 933)
+HTML_ENTITY(Phi, 934)
+HTML_ENTITY(Chi, 935)
+HTML_ENTITY(Psi, 936)
+HTML_ENTITY(Omega, 937)
+HTML_ENTITY(alpha, 945)
+HTML_ENTITY(beta, 946)
+HTML_ENTITY(gamma, 947)
+HTML_ENTITY(delta, 948)
+HTML_ENTITY(epsilon, 949)
+HTML_ENTITY(zeta, 950)
+HTML_ENTITY(eta, 951)
+HTML_ENTITY(theta, 952)
+HTML_ENTITY(iota, 953)
+HTML_ENTITY(kappa, 954)
+HTML_ENTITY(lambda, 955)
+HTML_ENTITY(mu, 956)
+HTML_ENTITY(nu, 957)
+HTML_ENTITY(xi, 958)
+HTML_ENTITY(omicron, 959)
+HTML_ENTITY(pi, 960)
+HTML_ENTITY(rho, 961)
+HTML_ENTITY(sigmaf, 962)
+HTML_ENTITY(sigma, 963)
+HTML_ENTITY(tau, 964)
+HTML_ENTITY(upsilon, 965)
+HTML_ENTITY(phi, 966)
+HTML_ENTITY(chi, 967)
+HTML_ENTITY(psi, 968)
+HTML_ENTITY(omega, 969)
+HTML_ENTITY(thetasym, 977)
+HTML_ENTITY(upsih, 978)
+HTML_ENTITY(piv, 982)
+HTML_ENTITY(bull, 8226)
+HTML_ENTITY(hellip, 8230)
+HTML_ENTITY(prime, 8242)
+HTML_ENTITY(Prime, 8243)
+HTML_ENTITY(oline, 8254)
+HTML_ENTITY(frasl, 8260)
+HTML_ENTITY(weierp, 8472)
+HTML_ENTITY(image, 8465)
+HTML_ENTITY(real, 8476)
+HTML_ENTITY(trade, 8482)
+HTML_ENTITY(alefsym, 8501)
+HTML_ENTITY(larr, 8592)
+HTML_ENTITY(uarr, 8593)
+HTML_ENTITY(rarr, 8594)
+HTML_ENTITY(darr, 8595)
+HTML_ENTITY(harr, 8596)
+HTML_ENTITY(crarr, 8629)
+HTML_ENTITY(lArr, 8656)
+HTML_ENTITY(uArr, 8657)
+HTML_ENTITY(rArr, 8658)
+HTML_ENTITY(dArr, 8659)
+HTML_ENTITY(hArr, 8660)
+HTML_ENTITY(forall, 8704)
+HTML_ENTITY(part, 8706)
+HTML_ENTITY(exist, 8707)
+HTML_ENTITY(empty, 8709)
+HTML_ENTITY(nabla, 8711)
+HTML_ENTITY(isin, 8712)
+HTML_ENTITY(notin, 8713)
+HTML_ENTITY(ni, 8715)
+HTML_ENTITY(prod, 8719)
+HTML_ENTITY(sum, 8721)
+HTML_ENTITY(minus, 8722)
+HTML_ENTITY(lowast, 8727)
+HTML_ENTITY(radic, 8730)
+HTML_ENTITY(prop, 8733)
+HTML_ENTITY(infin, 8734)
+HTML_ENTITY(ang, 8736)
+HTML_ENTITY(and, 8743)
+HTML_ENTITY(or, 8744)
+HTML_ENTITY(cap, 8745)
+HTML_ENTITY(cup, 8746)
+HTML_ENTITY(int, 8747)
+HTML_ENTITY(there4, 8756)
+HTML_ENTITY(sim, 8764)
+HTML_ENTITY(cong, 8773)
+HTML_ENTITY(asymp, 8776)
+HTML_ENTITY(ne, 8800)
+HTML_ENTITY(equiv, 8801)
+HTML_ENTITY(le, 8804)
+HTML_ENTITY(ge, 8805)
+HTML_ENTITY(sub, 8834)
+HTML_ENTITY(sup, 8835)
+HTML_ENTITY(nsub, 8836)
+HTML_ENTITY(sube, 8838)
+HTML_ENTITY(supe, 8839)
+HTML_ENTITY(oplus, 8853)
+HTML_ENTITY(otimes, 8855)
+HTML_ENTITY(perp, 8869)
+HTML_ENTITY(sdot, 8901)
+HTML_ENTITY(lceil, 8968)
+HTML_ENTITY(rceil, 8969)
+HTML_ENTITY(lfloor, 8970)
+HTML_ENTITY(rfloor, 8971)
+// Bug 603716: expansions of &lang; and &rang; have been modified in HTML5.
+// See http://www.w3.org/2003/entities/2007/htmlmathml-f.ent
+HTML_ENTITY(lang, 0x27E8)
+HTML_ENTITY(rang, 0x27E9)
+HTML_ENTITY(loz, 9674)
+HTML_ENTITY(spades, 9824)
+HTML_ENTITY(clubs, 9827)
+HTML_ENTITY(hearts, 9829)
+HTML_ENTITY(diams, 9830)
+
+// Markup-significant and internationalization characters
+// See the HTML4.0 spec for this list in it's DTD form
+HTML_ENTITY(quot, 34)
+HTML_ENTITY(amp, 38)
+HTML_ENTITY(lt, 60)
+HTML_ENTITY(gt, 62)
+HTML_ENTITY(OElig, 338)
+HTML_ENTITY(oelig, 339)
+HTML_ENTITY(Scaron, 352)
+HTML_ENTITY(scaron, 353)
+HTML_ENTITY(Yuml, 376)
+HTML_ENTITY(circ, 710)
+HTML_ENTITY(tilde, 732)
+HTML_ENTITY(ensp, 8194)
+HTML_ENTITY(emsp, 8195)
+HTML_ENTITY(thinsp, 8201)
+HTML_ENTITY(zwnj, 8204)
+HTML_ENTITY(zwj, 8205)
+HTML_ENTITY(lrm, 8206)
+HTML_ENTITY(rlm, 8207)
+HTML_ENTITY(ndash, 8211)
+HTML_ENTITY(mdash, 8212)
+HTML_ENTITY(lsquo, 8216)
+HTML_ENTITY(rsquo, 8217)
+HTML_ENTITY(sbquo, 8218)
+HTML_ENTITY(ldquo, 8220)
+HTML_ENTITY(rdquo, 8221)
+HTML_ENTITY(bdquo, 8222)
+HTML_ENTITY(dagger, 8224)
+HTML_ENTITY(Dagger, 8225)
+HTML_ENTITY(permil, 8240)
+HTML_ENTITY(lsaquo, 8249)
+HTML_ENTITY(rsaquo, 8250)
+HTML_ENTITY(euro, 8364)
+
+// Navigator entity extensions
+// This block of entities needs to be at the bottom of the list since it
+// contains duplicate Unicode codepoints. The codepoint to entity name
+// mapping (used by Composer) must ignores them, which occurs only
+// because they are listed later.
+
+// apos is from XML
+HTML_ENTITY(apos, 39)
+// The capitalized versions are required to handle non-standard input.
+HTML_ENTITY(AMP, 38)
+HTML_ENTITY(COPY, 169)
+HTML_ENTITY(GT, 62)
+HTML_ENTITY(LT, 60)
+HTML_ENTITY(QUOT, 34)
+HTML_ENTITY(REG, 174)
+
diff --git a/components/htmlparser/src/nsHTMLTagList.h b/components/htmlparser/src/nsHTMLTagList.h
new file mode 100644
index 000000000..4cb2a61e0
--- /dev/null
+++ b/components/htmlparser/src/nsHTMLTagList.h
@@ -0,0 +1,197 @@
+/* -*- 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/. */
+
+// IWYU pragma: private, include "nsHTMLTags.h"
+
+/******
+
+ This file contains the list of all HTML tags.
+ See nsHTMLTags.h for access to the enum values for tags.
+
+ It is designed to be used as input to various places that will define the
+ HTML_TAG macro in useful ways through the magic of C preprocessing.
+ Additionally, it is consumed by the self-regeneration code in
+ ElementName.java from which nsHtml5ElementName.cpp/h is translated.
+ See parser/html/java/README.txt.
+
+ If you edit this list, you need to re-run ElementName.java
+ self-regeneration and the HTML parser Java to C++ translation.
+
+ All entries must be enclosed in the macro HTML_TAG which will have cruel
+ and unusual things done to it.
+
+ It is recommended (but not strictly necessary) to keep all entries
+ in alphabetical order.
+
+ The first argument to HTML_TAG is the tag name. The second argument is the
+ "creator" method of the form NS_New$TAGNAMEElement, that will be used by
+ nsHTMLContentSink.cpp to create a content object for a tag of that
+ type. Use NOTUSED, if the particular tag has a non-standard creator.
+ The third argument is the interface name specified for this element
+ in the HTML specification. It can be empty if the relevant interface name
+ is "HTMLElement".
+
+ The HTML_OTHER macro is for values in the nsHTMLTag enum that are
+ not strictly tags.
+
+ Entries *must* use only lowercase characters.
+
+ Don't forget to update /editor/libeditor/HTMLEditUtils.cpp as well.
+
+ ** Break these invariants and bad things will happen. **
+
+ ******/
+#define HTML_HTMLELEMENT_TAG(_tag) HTML_TAG(_tag, , )
+
+HTML_TAG(a, Anchor, Anchor)
+HTML_HTMLELEMENT_TAG(abbr)
+HTML_HTMLELEMENT_TAG(acronym)
+HTML_HTMLELEMENT_TAG(address)
+HTML_TAG(applet, SharedObject, Applet)
+HTML_TAG(area, Area, Area)
+HTML_HTMLELEMENT_TAG(article)
+HTML_HTMLELEMENT_TAG(aside)
+HTML_TAG(audio, Audio, Audio)
+HTML_HTMLELEMENT_TAG(b)
+HTML_TAG(base, Shared, Base)
+HTML_HTMLELEMENT_TAG(basefont)
+HTML_HTMLELEMENT_TAG(bdo)
+HTML_TAG(bgsound, Unknown, Unknown)
+HTML_HTMLELEMENT_TAG(big)
+HTML_TAG(blockquote, Shared, Quote)
+HTML_TAG(body, Body, Body)
+HTML_TAG(br, BR, BR)
+HTML_TAG(button, Button, Button)
+HTML_TAG(canvas, Canvas, Canvas)
+HTML_TAG(caption, TableCaption, TableCaption)
+HTML_HTMLELEMENT_TAG(center)
+HTML_HTMLELEMENT_TAG(cite)
+HTML_HTMLELEMENT_TAG(code)
+HTML_TAG(col, TableCol, TableCol)
+HTML_TAG(colgroup, TableCol, TableCol)
+HTML_TAG(data, Data, Data)
+HTML_TAG(datalist, DataList, DataList)
+HTML_HTMLELEMENT_TAG(dd)
+HTML_TAG(del, Mod, Mod)
+HTML_TAG(details, Details, Details)
+HTML_HTMLELEMENT_TAG(dfn)
+HTML_TAG(dialog, Dialog, Dialog)
+HTML_TAG(dir, Shared, Directory)
+HTML_TAG(div, Div, Div)
+HTML_TAG(dl, SharedList, DList)
+HTML_HTMLELEMENT_TAG(dt)
+HTML_HTMLELEMENT_TAG(em)
+HTML_TAG(embed, SharedObject, Embed)
+HTML_TAG(fieldset, FieldSet, FieldSet)
+HTML_HTMLELEMENT_TAG(figcaption)
+HTML_HTMLELEMENT_TAG(figure)
+HTML_TAG(font, Font, Font)
+HTML_HTMLELEMENT_TAG(footer)
+HTML_TAG(form, Form, Form)
+HTML_TAG(frame, Frame, Frame)
+HTML_TAG(frameset, FrameSet, FrameSet)
+HTML_TAG(h1, Heading, Heading)
+HTML_TAG(h2, Heading, Heading)
+HTML_TAG(h3, Heading, Heading)
+HTML_TAG(h4, Heading, Heading)
+HTML_TAG(h5, Heading, Heading)
+HTML_TAG(h6, Heading, Heading)
+HTML_TAG(head, Shared, Head)
+HTML_HTMLELEMENT_TAG(header)
+HTML_HTMLELEMENT_TAG(hgroup)
+HTML_TAG(hr, HR, HR)
+HTML_TAG(html, Shared, Html)
+HTML_HTMLELEMENT_TAG(i)
+HTML_TAG(iframe, IFrame, IFrame)
+HTML_HTMLELEMENT_TAG(image)
+HTML_TAG(img, Image, Image)
+HTML_TAG(input, Input, Input)
+HTML_TAG(ins, Mod, Mod)
+HTML_HTMLELEMENT_TAG(kbd)
+HTML_TAG(keygen, Span, Span)
+HTML_TAG(label, Label, Label)
+HTML_TAG(legend, Legend, Legend)
+HTML_TAG(li, LI, LI)
+HTML_TAG(link, Link, Link)
+HTML_TAG(listing, Pre, Pre)
+HTML_HTMLELEMENT_TAG(main)
+HTML_TAG(map, Map, Map)
+HTML_HTMLELEMENT_TAG(mark)
+HTML_TAG(menu, Menu, Menu)
+HTML_TAG(menuitem, MenuItem, MenuItem)
+HTML_TAG(meta, Meta, Meta)
+HTML_TAG(meter, Meter, Meter)
+HTML_TAG(multicol, Unknown, Unknown)
+HTML_HTMLELEMENT_TAG(nav)
+HTML_HTMLELEMENT_TAG(nobr)
+HTML_HTMLELEMENT_TAG(noembed)
+HTML_HTMLELEMENT_TAG(noframes)
+HTML_HTMLELEMENT_TAG(noscript)
+HTML_TAG(object, Object, Object)
+HTML_TAG(ol, SharedList, OList)
+HTML_TAG(optgroup, OptGroup, OptGroup)
+HTML_TAG(option, Option, Option)
+HTML_TAG(output, Output, Output)
+HTML_TAG(p, Paragraph, Paragraph)
+HTML_TAG(param, Shared, Param)
+HTML_TAG(picture, Picture, Picture)
+HTML_HTMLELEMENT_TAG(plaintext)
+HTML_TAG(pre, Pre, Pre)
+HTML_TAG(progress, Progress, Progress)
+HTML_TAG(q, Shared, Quote)
+HTML_HTMLELEMENT_TAG(rb)
+HTML_HTMLELEMENT_TAG(rp)
+HTML_HTMLELEMENT_TAG(rt)
+HTML_HTMLELEMENT_TAG(rtc)
+HTML_HTMLELEMENT_TAG(ruby)
+HTML_HTMLELEMENT_TAG(s)
+HTML_HTMLELEMENT_TAG(samp)
+HTML_TAG(script, Script, Script)
+HTML_HTMLELEMENT_TAG(section)
+HTML_TAG(select, Select, Select)
+HTML_HTMLELEMENT_TAG(small)
+HTML_TAG(slot, Slot, Slot)
+HTML_TAG(source, Source, Source)
+HTML_TAG(span, Span, Span)
+HTML_HTMLELEMENT_TAG(strike)
+HTML_HTMLELEMENT_TAG(strong)
+HTML_TAG(style, Style, Style)
+HTML_HTMLELEMENT_TAG(sub)
+HTML_TAG(summary, Summary, )
+HTML_HTMLELEMENT_TAG(sup)
+HTML_TAG(table, Table, Table)
+HTML_TAG(tbody, TableSection, TableSection)
+HTML_TAG(td, TableCell, TableCell)
+HTML_TAG(textarea, TextArea, TextArea)
+HTML_TAG(tfoot, TableSection, TableSection)
+HTML_TAG(th, TableCell, TableCell)
+HTML_TAG(thead, TableSection, TableSection)
+HTML_TAG(template, Template, Template)
+HTML_TAG(time, Time, Time)
+HTML_TAG(title, Title, Title)
+HTML_TAG(tr, TableRow, TableRow)
+HTML_TAG(track, Track, Track)
+HTML_HTMLELEMENT_TAG(tt)
+HTML_HTMLELEMENT_TAG(u)
+HTML_TAG(ul, SharedList, UList)
+HTML_HTMLELEMENT_TAG(var)
+HTML_TAG(video, Video, Video)
+HTML_HTMLELEMENT_TAG(wbr)
+HTML_TAG(xmp, Pre, Pre)
+
+
+/* These are not for tags. But they will be included in the nsHTMLTag
+ enum anyway */
+
+HTML_OTHER(text)
+HTML_OTHER(whitespace)
+HTML_OTHER(newline)
+HTML_OTHER(comment)
+HTML_OTHER(entity)
+HTML_OTHER(doctypeDecl)
+HTML_OTHER(markupDecl)
+HTML_OTHER(instruction)
+
+#undef HTML_HTMLELEMENT_TAG
diff --git a/components/htmlparser/src/nsHTMLTags.cpp b/components/htmlparser/src/nsHTMLTags.cpp
new file mode 100644
index 000000000..681c37489
--- /dev/null
+++ b/components/htmlparser/src/nsHTMLTags.cpp
@@ -0,0 +1,259 @@
+/* -*- 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 "nsHTMLTags.h"
+#include "nsCRT.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsStaticAtom.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/HashFunctions.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+// static array of unicode tag names
+#define HTML_TAG(_tag, _classname, _interfacename) (u"" #_tag),
+#define HTML_OTHER(_tag)
+const char16_t* const nsHTMLTags::sTagUnicodeTable[] = {
+#include "nsHTMLTagList.h"
+};
+#undef HTML_TAG
+#undef HTML_OTHER
+
+// static array of tag atoms
+nsIAtom* nsHTMLTags::sTagAtomTable[eHTMLTag_userdefined - 1];
+
+int32_t nsHTMLTags::gTableRefCount;
+PLHashTable* nsHTMLTags::gTagTable;
+PLHashTable* nsHTMLTags::gTagAtomTable;
+
+
+// char16_t* -> id hash
+static PLHashNumber
+HTMLTagsHashCodeUCPtr(const void *key)
+{
+ return HashString(static_cast<const char16_t*>(key));
+}
+
+static int
+HTMLTagsKeyCompareUCPtr(const void *key1, const void *key2)
+{
+ const char16_t *str1 = (const char16_t *)key1;
+ const char16_t *str2 = (const char16_t *)key2;
+
+ return nsCRT::strcmp(str1, str2) == 0;
+}
+
+// nsIAtom* -> id hash
+static PLHashNumber
+HTMLTagsHashCodeAtom(const void *key)
+{
+ return NS_PTR_TO_INT32(key) >> 2;
+}
+
+#define NS_HTMLTAG_NAME_MAX_LENGTH 10
+
+// static
+void
+nsHTMLTags::RegisterAtoms(void)
+{
+#define HTML_TAG(_tag, _classname, _interfacename) NS_STATIC_ATOM_BUFFER(Atombuffer_##_tag, #_tag)
+#define HTML_OTHER(_tag)
+#include "nsHTMLTagList.h"
+#undef HTML_TAG
+#undef HTML_OTHER
+
+// static array of tag StaticAtom structs
+#define HTML_TAG(_tag, _classname, _interfacename) NS_STATIC_ATOM(Atombuffer_##_tag, &nsHTMLTags::sTagAtomTable[eHTMLTag_##_tag - 1]),
+#define HTML_OTHER(_tag)
+ static const nsStaticAtom sTagAtoms_info[] = {
+#include "nsHTMLTagList.h"
+ };
+#undef HTML_TAG
+#undef HTML_OTHER
+
+ // Fill in our static atom pointers
+ NS_RegisterStaticAtoms(sTagAtoms_info);
+
+
+#if defined(DEBUG)
+ {
+ // let's verify that all names in the the table are lowercase...
+ for (int32_t i = 0; i < NS_HTML_TAG_MAX; ++i) {
+ nsAutoString temp1((char16_t*)sTagAtoms_info[i].mStringBuffer->Data());
+ nsAutoString temp2((char16_t*)sTagAtoms_info[i].mStringBuffer->Data());
+ ToLowerCase(temp1);
+ NS_ASSERTION(temp1.Equals(temp2), "upper case char in table");
+ }
+
+ // let's verify that all names in the unicode strings above are
+ // correct.
+ for (int32_t i = 0; i < NS_HTML_TAG_MAX; ++i) {
+ nsAutoString temp1(sTagUnicodeTable[i]);
+ nsAutoString temp2((char16_t*)sTagAtoms_info[i].mStringBuffer->Data());
+ NS_ASSERTION(temp1.Equals(temp2), "Bad unicode tag name!");
+ }
+
+ // let's verify that NS_HTMLTAG_NAME_MAX_LENGTH is correct
+ uint32_t maxTagNameLength = 0;
+ for (int32_t i = 0; i < NS_HTML_TAG_MAX; ++i) {
+ uint32_t len = NS_strlen(sTagUnicodeTable[i]);
+ maxTagNameLength = std::max(len, maxTagNameLength);
+ }
+ NS_ASSERTION(maxTagNameLength == NS_HTMLTAG_NAME_MAX_LENGTH,
+ "NS_HTMLTAG_NAME_MAX_LENGTH not set correctly!");
+ }
+#endif
+}
+
+// static
+nsresult
+nsHTMLTags::AddRefTable(void)
+{
+ if (gTableRefCount++ == 0) {
+ NS_ASSERTION(!gTagTable && !gTagAtomTable, "pre existing hash!");
+
+ gTagTable = PL_NewHashTable(64, HTMLTagsHashCodeUCPtr,
+ HTMLTagsKeyCompareUCPtr, PL_CompareValues,
+ nullptr, nullptr);
+ NS_ENSURE_TRUE(gTagTable, NS_ERROR_OUT_OF_MEMORY);
+
+ gTagAtomTable = PL_NewHashTable(64, HTMLTagsHashCodeAtom,
+ PL_CompareValues, PL_CompareValues,
+ nullptr, nullptr);
+ NS_ENSURE_TRUE(gTagAtomTable, NS_ERROR_OUT_OF_MEMORY);
+
+ // Fill in gTagTable with the above static char16_t strings as
+ // keys and the value of the corresponding enum as the value in
+ // the table.
+
+ int32_t i;
+ for (i = 0; i < NS_HTML_TAG_MAX; ++i) {
+ PL_HashTableAdd(gTagTable, sTagUnicodeTable[i],
+ NS_INT32_TO_PTR(i + 1));
+
+ PL_HashTableAdd(gTagAtomTable, sTagAtomTable[i],
+ NS_INT32_TO_PTR(i + 1));
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+void
+nsHTMLTags::ReleaseTable(void)
+{
+ if (0 == --gTableRefCount) {
+ if (gTagTable) {
+ // Nothing to delete/free in this table, just destroy the table.
+
+ PL_HashTableDestroy(gTagTable);
+ PL_HashTableDestroy(gTagAtomTable);
+ gTagTable = nullptr;
+ gTagAtomTable = nullptr;
+ }
+ }
+}
+
+// static
+nsHTMLTag
+nsHTMLTags::StringTagToId(const nsAString& aTagName)
+{
+ uint32_t length = aTagName.Length();
+
+ if (length > NS_HTMLTAG_NAME_MAX_LENGTH) {
+ return eHTMLTag_userdefined;
+ }
+
+ char16_t buf[NS_HTMLTAG_NAME_MAX_LENGTH + 1];
+
+ nsAString::const_iterator iter;
+ uint32_t i = 0;
+ char16_t c;
+
+ aTagName.BeginReading(iter);
+
+ // Fast lowercasing-while-copying of ASCII characters into a
+ // char16_t buffer
+
+ while (i < length) {
+ c = *iter;
+
+ if (c <= 'Z' && c >= 'A') {
+ c |= 0x20; // Lowercase the ASCII character.
+ }
+
+ buf[i] = c; // Copy ASCII character.
+
+ ++i;
+ ++iter;
+ }
+
+ buf[i] = 0;
+
+ return CaseSensitiveStringTagToId(buf);
+}
+
+#ifdef DEBUG
+void
+nsHTMLTags::TestTagTable()
+{
+ const char16_t *tag;
+ nsHTMLTag id;
+ nsCOMPtr<nsIAtom> atom;
+
+ nsHTMLTags::AddRefTable();
+ // Make sure we can find everything we are supposed to
+ for (int i = 0; i < NS_HTML_TAG_MAX; ++i) {
+ tag = sTagUnicodeTable[i];
+ id = StringTagToId(nsDependentString(tag));
+ NS_ASSERTION(id != eHTMLTag_userdefined, "can't find tag id");
+ const char16_t* check = GetStringValue(id);
+ NS_ASSERTION(0 == nsCRT::strcmp(check, tag), "can't map id back to tag");
+
+ nsAutoString uname(tag);
+ ToUpperCase(uname);
+ NS_ASSERTION(id == StringTagToId(uname), "wrong id");
+
+ NS_ASSERTION(id == CaseSensitiveStringTagToId(tag), "wrong id");
+
+ atom = NS_Atomize(tag);
+ NS_ASSERTION(id == CaseSensitiveAtomTagToId(atom), "wrong id");
+ NS_ASSERTION(atom == GetAtom(id), "can't map id back to atom");
+ }
+
+ // Make sure we don't find things that aren't there
+ id = StringTagToId(NS_LITERAL_STRING("@"));
+ NS_ASSERTION(id == eHTMLTag_userdefined, "found @");
+ id = StringTagToId(NS_LITERAL_STRING("zzzzz"));
+ NS_ASSERTION(id == eHTMLTag_userdefined, "found zzzzz");
+
+ atom = NS_Atomize("@");
+ id = CaseSensitiveAtomTagToId(atom);
+ NS_ASSERTION(id == eHTMLTag_userdefined, "found @");
+ atom = NS_Atomize("zzzzz");
+ id = CaseSensitiveAtomTagToId(atom);
+ NS_ASSERTION(id == eHTMLTag_userdefined, "found zzzzz");
+
+ tag = GetStringValue((nsHTMLTag) 0);
+ NS_ASSERTION(!tag, "found enum 0");
+ tag = GetStringValue((nsHTMLTag) -1);
+ NS_ASSERTION(!tag, "found enum -1");
+ tag = GetStringValue((nsHTMLTag) (NS_HTML_TAG_MAX + 1));
+ NS_ASSERTION(!tag, "found past max enum");
+
+ atom = GetAtom((nsHTMLTag) 0);
+ NS_ASSERTION(!atom, "found enum 0");
+ atom = GetAtom((nsHTMLTag) -1);
+ NS_ASSERTION(!atom, "found enum -1");
+ atom = GetAtom((nsHTMLTag) (NS_HTML_TAG_MAX + 1));
+ NS_ASSERTION(!atom, "found past max enum");
+
+ ReleaseTable();
+}
+
+#endif // DEBUG
diff --git a/components/htmlparser/src/nsHTMLTags.h b/components/htmlparser/src/nsHTMLTags.h
new file mode 100644
index 000000000..b21df55f8
--- /dev/null
+++ b/components/htmlparser/src/nsHTMLTags.h
@@ -0,0 +1,100 @@
+/* -*- 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 nsHTMLTags_h___
+#define nsHTMLTags_h___
+
+#include "nsIAtom.h"
+#include "nsString.h"
+#include "plhash.h"
+
+class nsIAtom;
+
+/*
+ Declare the enum list using the magic of preprocessing
+ enum values are "eHTMLTag_foo" (where foo is the tag)
+
+ To change the list of tags, see nsHTMLTagList.h
+
+ These enum values are used as the index of array in various places.
+ If we change the structure of the enum by adding entries to it or removing
+ entries from it _directly_, not via nsHTMLTagList.h, don't forget to update
+ dom/bindings/BindingUtils.cpp and dom/html/nsHTMLContentSink.cpp as well.
+ */
+#define HTML_TAG(_tag, _classname, _interfacename) eHTMLTag_##_tag,
+#define HTML_OTHER(_tag) eHTMLTag_##_tag,
+enum nsHTMLTag {
+ /* this enum must be first and must be zero */
+ eHTMLTag_unknown = 0,
+#include "nsHTMLTagList.h"
+
+ /* can't be moved into nsHTMLTagList since gcc3.4 doesn't like a
+ comma at the end of enum list*/
+ eHTMLTag_userdefined
+};
+#undef HTML_TAG
+#undef HTML_OTHER
+
+// All tags before eHTMLTag_text are HTML tags
+#define NS_HTML_TAG_MAX int32_t(eHTMLTag_text - 1)
+
+class nsHTMLTags {
+public:
+ static void RegisterAtoms(void);
+ static nsresult AddRefTable(void);
+ static void ReleaseTable(void);
+
+ // Functions for converting string or atom to id
+ static nsHTMLTag StringTagToId(const nsAString& aTagName);
+ static nsHTMLTag AtomTagToId(nsIAtom* aTagName)
+ {
+ return StringTagToId(nsDependentAtomString(aTagName));
+ }
+
+ static nsHTMLTag CaseSensitiveStringTagToId(const char16_t* aTagName)
+ {
+ NS_ASSERTION(gTagTable, "no lookup table, needs addref");
+ NS_ASSERTION(aTagName, "null tagname!");
+
+ void* tag = PL_HashTableLookupConst(gTagTable, aTagName);
+
+ return tag ? (nsHTMLTag)NS_PTR_TO_INT32(tag) : eHTMLTag_userdefined;
+ }
+ static nsHTMLTag CaseSensitiveAtomTagToId(nsIAtom* aTagName)
+ {
+ NS_ASSERTION(gTagAtomTable, "no lookup table, needs addref");
+ NS_ASSERTION(aTagName, "null tagname!");
+
+ void* tag = PL_HashTableLookupConst(gTagAtomTable, aTagName);
+
+ return tag ? (nsHTMLTag)NS_PTR_TO_INT32(tag) : eHTMLTag_userdefined;
+ }
+
+ // Functions for converting an id to a string or atom
+ static const char16_t *GetStringValue(nsHTMLTag aEnum)
+ {
+ return aEnum <= eHTMLTag_unknown || aEnum > NS_HTML_TAG_MAX ?
+ nullptr : sTagUnicodeTable[aEnum - 1];
+ }
+ static nsIAtom *GetAtom(nsHTMLTag aEnum)
+ {
+ return aEnum <= eHTMLTag_unknown || aEnum > NS_HTML_TAG_MAX ?
+ nullptr : sTagAtomTable[aEnum - 1];
+ }
+
+#ifdef DEBUG
+ static void TestTagTable();
+#endif
+
+private:
+ static nsIAtom* sTagAtomTable[eHTMLTag_userdefined - 1];
+ static const char16_t* const sTagUnicodeTable[];
+
+ static int32_t gTableRefCount;
+ static PLHashTable* gTagTable;
+ static PLHashTable* gTagAtomTable;
+};
+
+#endif /* nsHTMLTags_h___ */
diff --git a/components/htmlparser/src/nsHTMLTokenizer.cpp b/components/htmlparser/src/nsHTMLTokenizer.cpp
new file mode 100644
index 000000000..a40e11f0e
--- /dev/null
+++ b/components/htmlparser/src/nsHTMLTokenizer.cpp
@@ -0,0 +1,59 @@
+/* -*- 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/. */
+
+
+/**
+ * @file nsHTMLTokenizer.cpp
+ * This is an implementation of the nsITokenizer interface.
+ * This file contains the implementation of a tokenizer to tokenize an HTML
+ * document. It attempts to do so, making tradeoffs between compatibility with
+ * older parsers and the SGML specification. Note that most of the real
+ * "tokenization" takes place in nsHTMLTokens.cpp.
+ */
+
+#include "nsHTMLTokenizer.h"
+#include "nsIParser.h"
+#include "nsParserConstants.h"
+
+/************************************************************************
+ And now for the main class -- nsHTMLTokenizer...
+ ************************************************************************/
+
+/**
+ * Satisfy the nsISupports interface.
+ */
+NS_IMPL_ISUPPORTS(nsHTMLTokenizer, nsITokenizer)
+
+/**
+ * Default constructor
+ */
+nsHTMLTokenizer::nsHTMLTokenizer()
+{
+ // TODO Assert about:blank-ness.
+}
+
+nsresult
+nsHTMLTokenizer::WillTokenize(bool aIsFinalChunk)
+{
+ return NS_OK;
+}
+
+/**
+ * This method is repeatedly called by the tokenizer.
+ * Each time, we determine the kind of token we're about to
+ * read, and then we call the appropriate method to handle
+ * that token type.
+ *
+ * @param aScanner The source of our input.
+ * @param aFlushTokens An OUT parameter to tell the caller whether it should
+ * process our queued tokens up to now (e.g., when we
+ * reach a <script>).
+ * @return Success or error
+ */
+nsresult
+nsHTMLTokenizer::ConsumeToken(nsScanner& aScanner, bool& aFlushTokens)
+{
+ return kEOF;
+}
diff --git a/components/htmlparser/src/nsHTMLTokenizer.h b/components/htmlparser/src/nsHTMLTokenizer.h
new file mode 100644
index 000000000..0d2940c5e
--- /dev/null
+++ b/components/htmlparser/src/nsHTMLTokenizer.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+
+/**
+ * MODULE NOTES:
+ * @update gess 4/1/98
+ *
+ */
+
+#ifndef __NSHTMLTOKENIZER
+#define __NSHTMLTOKENIZER
+
+#include "mozilla/Attributes.h"
+#include "nsISupports.h"
+#include "nsITokenizer.h"
+
+#ifdef _MSC_VER
+#pragma warning( disable : 4275 )
+#endif
+
+class nsHTMLTokenizer final : public nsITokenizer {
+ ~nsHTMLTokenizer() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITOKENIZER
+ nsHTMLTokenizer();
+};
+
+#endif
+
+
diff --git a/components/htmlparser/src/nsIContentSink.h b/components/htmlparser/src/nsIContentSink.h
new file mode 100644
index 000000000..56c70a1b4
--- /dev/null
+++ b/components/htmlparser/src/nsIContentSink.h
@@ -0,0 +1,132 @@
+/* -*- 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 nsIContentSink_h___
+#define nsIContentSink_h___
+
+/**
+ * MODULE NOTES:
+ * @update gess 4/1/98
+ *
+ * This pure virtual interface is used as the "glue" that connects the parsing
+ * process to the content model construction process.
+ *
+ * The icontentsink interface is a very lightweight wrapper that represents the
+ * content-sink model building process. There is another one that you may care
+ * about more, which is the IHTMLContentSink interface. (See that file for details).
+ */
+#include "nsISupports.h"
+#include "nsString.h"
+#include "mozFlushType.h"
+#include "nsIDTD.h"
+
+class nsParserBase;
+
+#define NS_ICONTENT_SINK_IID \
+{ 0xcf9a7cbb, 0xfcbc, 0x4e13, \
+ { 0x8e, 0xf5, 0x18, 0xef, 0x2d, 0x3d, 0x58, 0x29 } }
+
+class nsIContentSink : public nsISupports {
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENT_SINK_IID)
+
+ /**
+ * This method is called by the parser when it is entered from
+ * the event loop. The content sink wants to know how long the
+ * parser has been active since we last processed events on the
+ * main event loop and this call calibrates that measurement.
+ */
+ NS_IMETHOD WillParse(void)=0;
+
+ /**
+ * This method gets called when the parser begins the process
+ * of building the content model via the content sink.
+ *
+ * Default implementation provided since the sink should have the option of
+ * doing nothing in response to this call.
+ *
+ * @update 5/7/98 gess
+ */
+ NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) {
+ return NS_OK;
+ }
+
+ /**
+ * This method gets called when the parser concludes the process
+ * of building the content model via the content sink.
+ *
+ * Default implementation provided since the sink should have the option of
+ * doing nothing in response to this call.
+ *
+ * @update 5/7/98 gess
+ */
+ NS_IMETHOD DidBuildModel(bool aTerminated) {
+ return NS_OK;
+ }
+
+ /**
+ * This method gets called when the parser gets i/o blocked,
+ * and wants to notify the sink that it may be a while before
+ * more data is available.
+ *
+ * @update 5/7/98 gess
+ */
+ NS_IMETHOD WillInterrupt(void)=0;
+
+ /**
+ * This method gets called when the parser i/o gets unblocked,
+ * and we're about to start dumping content again to the sink.
+ *
+ * @update 5/7/98 gess
+ */
+ NS_IMETHOD WillResume(void)=0;
+
+ /**
+ * This method gets called by the parser so that the content
+ * sink can retain a reference to the parser. The expectation
+ * is that the content sink will drop the reference when it
+ * gets the DidBuildModel notification i.e. when parsing is done.
+ */
+ NS_IMETHOD SetParser(nsParserBase* aParser)=0;
+
+ /**
+ * Flush content so that the content model is in sync with the state
+ * of the sink.
+ *
+ * @param aType the type of flush to perform
+ */
+ virtual void FlushPendingNotifications(mozFlushType aType)=0;
+
+ /**
+ * Set the document character set. This should be passed on to the
+ * document itself.
+ */
+ NS_IMETHOD SetDocumentCharset(nsACString& aCharset)=0;
+
+ /**
+ * Returns the target object (often a document object) into which
+ * the content built by this content sink is being added, if any
+ * (IOW, may return null).
+ */
+ virtual nsISupports *GetTarget()=0;
+
+ /**
+ * Returns true if there's currently script executing that we need to hold
+ * parsing for.
+ */
+ virtual bool IsScriptExecuting()
+ {
+ return false;
+ }
+
+ /**
+ * Posts a runnable that continues parsing.
+ */
+ virtual void ContinueInterruptedParsingAsync() {}
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIContentSink, NS_ICONTENT_SINK_IID)
+
+#endif /* nsIContentSink_h___ */
diff --git a/components/htmlparser/src/nsIDTD.h b/components/htmlparser/src/nsIDTD.h
new file mode 100644
index 000000000..cbae4d507
--- /dev/null
+++ b/components/htmlparser/src/nsIDTD.h
@@ -0,0 +1,136 @@
+/* -*- 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/. */
+
+#ifndef nsIDTD_h___
+#define nsIDTD_h___
+
+/**
+ * MODULE NOTES:
+ * @update gess 7/20/98
+ *
+ * This interface defines standard interface for DTD's. Note that this
+ * isn't HTML specific. DTD's have several functions within the parser
+ * system:
+ * 1) To coordinate the consumption of an input stream via the
+ * parser
+ * 2) To serve as proxy to represent the containment rules of the
+ * underlying document
+ * 3) To offer autodetection services to the parser (mainly for doc
+ * conversion)
+ * */
+
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsITokenizer.h"
+
+#define NS_IDTD_IID \
+{ 0x3de05873, 0xefa7, 0x410d, \
+ { 0xa4, 0x61, 0x80, 0x33, 0xaf, 0xd9, 0xe3, 0x26 } }
+
+enum eAutoDetectResult {
+ eUnknownDetect,
+ eValidDetect,
+ ePrimaryDetect,
+ eInvalidDetect
+};
+
+enum nsDTDMode {
+ eDTDMode_unknown = 0,
+ eDTDMode_quirks, //pre 4.0 versions
+ eDTDMode_almost_standards,
+ eDTDMode_full_standards,
+ eDTDMode_autodetect,
+ eDTDMode_fragment
+};
+
+
+class nsIContentSink;
+class CParserContext;
+
+class nsIDTD : public nsISupports
+{
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDTD_IID)
+
+ NS_IMETHOD WillBuildModel(const CParserContext& aParserContext,
+ nsITokenizer* aTokenizer,
+ nsIContentSink* aSink) = 0;
+
+ /**
+ * Called by the parser after the parsing process has concluded
+ * @update gess5/18/98
+ * @param anErrorCode - contains error code resulting from parse process
+ * @return
+ */
+ NS_IMETHOD DidBuildModel(nsresult anErrorCode) = 0;
+
+ /**
+ * Called (possibly repeatedly) by the parser to parse tokens and construct
+ * the document model via the sink provided to WillBuildModel.
+ *
+ * @param aTokenizer - tokenizer providing the token stream to be parsed
+ * @param aCountLines - informs the DTD whether to count newlines
+ * (not wanted, e.g., when handling document.write)
+ * @param aCharsetPtr - address of an nsCString containing the charset
+ * that the DTD should use (pointer in case the DTD
+ * opts to ignore this parameter)
+ */
+ NS_IMETHOD BuildModel(nsITokenizer* aTokenizer, nsIContentSink* aSink) = 0;
+
+ /**
+ * This method is called to determine whether or not a tag of one
+ * type can contain a tag of another type.
+ *
+ * @update gess 3/25/98
+ * @param aParent -- int tag of parent container
+ * @param aChild -- int tag of child container
+ * @return true if parent can contain child
+ */
+ NS_IMETHOD_(bool) CanContain(int32_t aParent,int32_t aChild) const = 0;
+
+ /**
+ * This method gets called to determine whether a given
+ * tag is itself a container
+ *
+ * @update gess 3/25/98
+ * @param aTag -- tag to test for containership
+ * @return true if given tag can contain other tags
+ */
+ NS_IMETHOD_(bool) IsContainer(int32_t aTag) const = 0;
+
+ /**
+ * Use this id you want to stop the building content model
+ * --------------[ Sets DTD to STOP mode ]----------------
+ * It's recommended to use this method in accordance with
+ * the parser's terminate() method.
+ *
+ * @update harishd 07/22/99
+ * @param
+ * @return
+ */
+ NS_IMETHOD_(void) Terminate() = 0;
+
+ NS_IMETHOD_(int32_t) GetType() = 0;
+
+ /**
+ * Call this method after calling WillBuildModel to determine what mode the
+ * DTD actually is using, as it may differ from aParserContext.mDTDMode.
+ */
+ NS_IMETHOD_(nsDTDMode) GetMode() const = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIDTD, NS_IDTD_IID)
+
+#define NS_DECL_NSIDTD \
+ NS_IMETHOD WillBuildModel( const CParserContext& aParserContext, nsITokenizer* aTokenizer, nsIContentSink* aSink) override;\
+ NS_IMETHOD DidBuildModel(nsresult anErrorCode) override;\
+ NS_IMETHOD BuildModel(nsITokenizer* aTokenizer, nsIContentSink* aSink) override;\
+ NS_IMETHOD_(bool) CanContain(int32_t aParent,int32_t aChild) const override;\
+ NS_IMETHOD_(bool) IsContainer(int32_t aTag) const override;\
+ NS_IMETHOD_(void) Terminate() override;\
+ NS_IMETHOD_(int32_t) GetType() override;\
+ NS_IMETHOD_(nsDTDMode) GetMode() const override;
+#endif /* nsIDTD_h___ */
diff --git a/components/htmlparser/src/nsIFragmentContentSink.h b/components/htmlparser/src/nsIFragmentContentSink.h
new file mode 100644
index 000000000..8d547ed66
--- /dev/null
+++ b/components/htmlparser/src/nsIFragmentContentSink.h
@@ -0,0 +1,77 @@
+/* -*- 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 nsIFragmentContentSink_h___
+#define nsIFragmentContentSink_h___
+
+#include "nsISupports.h"
+
+class nsIDOMDocumentFragment;
+class nsIDocument;
+
+#define NS_I_FRAGMENT_CONTENT_SINK_IID \
+ { 0x1a8ce30b, 0x63fc, 0x441a, \
+ { 0xa3, 0xaa, 0xf7, 0x16, 0xc0, 0xfe, 0x96, 0x69 } }
+
+/**
+ * The fragment sink allows a client to parse a fragment of sink, possibly
+ * surrounded in context. Also see nsIParser::ParseFragment().
+ * Note: once you've parsed a fragment, the fragment sink must be re-set on
+ * the parser in order to parse another fragment.
+ */
+class nsIFragmentContentSink : public nsISupports {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_I_FRAGMENT_CONTENT_SINK_IID)
+ /**
+ * This method is used to obtain the fragment created by
+ * a fragment content sink and to release resources held by the parser.
+ *
+ * The sink drops its reference to the fragment.
+ */
+ NS_IMETHOD FinishFragmentParsing(nsIDOMDocumentFragment** aFragment) = 0;
+
+ /**
+ * This method is used to set the target document for this fragment
+ * sink. This document's nodeinfo manager will be used to create
+ * the content objects. This MUST be called before the sink is used.
+ *
+ * @param aDocument the document the new nodes will belong to
+ * (should not be null)
+ */
+ NS_IMETHOD SetTargetDocument(nsIDocument* aDocument) = 0;
+
+ /**
+ * This method is used to indicate to the sink that we're done building
+ * the context and should start paying attention to the incoming content
+ */
+ NS_IMETHOD WillBuildContent() = 0;
+
+ /**
+ * This method is used to indicate to the sink that we're done building
+ * The real content. This is useful if you want to parse additional context
+ * (such as an end context).
+ */
+ NS_IMETHOD DidBuildContent() = 0;
+
+ /**
+ * This method is a total hack to help with parsing fragments. It is called to
+ * tell the fragment sink that a container from the context will be delivered
+ * after the call to WillBuildContent(). This is only relevent for HTML
+ * fragments that use nsHTMLTokenizer/CNavDTD.
+ */
+ NS_IMETHOD IgnoreFirstContainer() = 0;
+
+ /**
+ * Sets whether scripts elements are marked as unexecutable.
+ */
+ NS_IMETHOD SetPreventScriptExecution(bool aPreventScriptExecution) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIFragmentContentSink,
+ NS_I_FRAGMENT_CONTENT_SINK_IID)
+
+nsresult
+NS_NewXMLFragmentContentSink(nsIFragmentContentSink** aInstancePtrResult);
+
+#endif
diff --git a/components/htmlparser/src/nsIHTMLContentSink.h b/components/htmlparser/src/nsIHTMLContentSink.h
new file mode 100644
index 000000000..bf08c4b5e
--- /dev/null
+++ b/components/htmlparser/src/nsIHTMLContentSink.h
@@ -0,0 +1,89 @@
+/* -*- 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 nsIHTMLContentSink_h___
+#define nsIHTMLContentSink_h___
+
+/**
+ * This interface is OBSOLETE and in the process of being REMOVED.
+ * Do NOT implement!
+ *
+ * This file declares the concrete HTMLContentSink class.
+ * This class is used during the parsing process as the
+ * primary interface between the parser and the content
+ * model.
+ *
+ * After the tokenizer completes, the parser iterates over
+ * the known token list. As the parser identifies valid
+ * elements, it calls the contentsink interface to notify
+ * the content model that a new node or child node is being
+ * created and added to the content model.
+ *
+ * The HTMLContentSink interface assumes 4 underlying
+ * containers: HTML, HEAD, BODY and FRAMESET. Before
+ * accessing any these, the parser will call the appropriate
+ * OpennsIHTMLContentSink method: OpenHTML,OpenHead,OpenBody,OpenFrameSet;
+ * likewise, the ClosensIHTMLContentSink version will be called when the
+ * parser is done with a given section.
+ *
+ * IMPORTANT: The parser may Open each container more than
+ * once! This is due to the irregular nature of HTML files.
+ * For example, it is possible to encounter plain text at
+ * the start of an HTML document (that precedes the HTML tag).
+ * Such text is treated as if it were part of the body.
+ * In such cases, the parser will Open the body, pass the text-
+ * node in and then Close the body. The body will likely be
+ * re-Opened later when the actual <BODY> tag has been seen.
+ *
+ * Containers within the body are Opened and Closed
+ * using the OpenContainer(...) and CloseContainer(...) calls.
+ * It is assumed that the document or contentSink is
+ * maintaining its state to manage where new content should
+ * be added to the underlying document.
+ *
+ * NOTE: OpenHTML() and OpenBody() may get called multiple times
+ * in the same document. That's fine, and it doesn't mean
+ * that we have multiple bodies or HTML's.
+ *
+ * NOTE: I haven't figured out how sub-documents (non-frames)
+ * are going to be handled. Stay tuned.
+ */
+#include "nsIContentSink.h"
+#include "nsHTMLTags.h"
+
+#define NS_IHTML_CONTENT_SINK_IID \
+ {0xefc5af86, 0x5cfd, 0x4918, {0x9d, 0xd3, 0x5f, 0x7a, 0xb2, 0x88, 0xb2, 0x68}}
+
+/**
+ * This interface is OBSOLETE and in the process of being REMOVED.
+ * Do NOT implement!
+ */
+class nsIHTMLContentSink : public nsIContentSink
+{
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IHTML_CONTENT_SINK_IID)
+
+ enum ElementType { eHTML, eBody };
+
+ /**
+ * This method is used to open a generic container in the sink.
+ *
+ * @update 4/1/98 gess
+ */
+ NS_IMETHOD OpenContainer(ElementType aNodeType) = 0;
+
+ /**
+ * This method gets called by the parser when a close
+ * container tag has been consumed and needs to be closed.
+ *
+ * @param aTag - The tag to be closed.
+ */
+ NS_IMETHOD CloseContainer(ElementType aTag) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIHTMLContentSink, NS_IHTML_CONTENT_SINK_IID)
+
+#endif /* nsIHTMLContentSink_h___ */
+
diff --git a/components/htmlparser/src/nsIParser.h b/components/htmlparser/src/nsIParser.h
new file mode 100644
index 000000000..4bf0b3370
--- /dev/null
+++ b/components/htmlparser/src/nsIParser.h
@@ -0,0 +1,272 @@
+/* -*- 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/. */
+#ifndef NS_IPARSER___
+#define NS_IPARSER___
+
+
+ /**
+ * This GECKO-INTERNAL interface is on track to being REMOVED (or refactored
+ * to the point of being near-unrecognizable).
+ *
+ * Please DO NOT #include this file in comm-central code, in your XUL
+ * app or binary extensions.
+ *
+ * Please DO NOT #include this into new files even inside Gecko. It is more
+ * likely than not that #including this header is the wrong thing to do.
+ */
+
+#include "nsISupports.h"
+#include "nsIStreamListener.h"
+#include "nsIDTD.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIAtom.h"
+#include "nsParserBase.h"
+
+#define NS_IPARSER_IID \
+{ 0x2c4ad90a, 0x740e, 0x4212, \
+ { 0xba, 0x3f, 0xfe, 0xac, 0xda, 0x4b, 0x92, 0x9e } }
+
+// {41421C60-310A-11d4-816F-000064657374}
+#define NS_IDEBUG_DUMP_CONTENT_IID \
+{ 0x41421c60, 0x310a, 0x11d4, \
+{ 0x81, 0x6f, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } }
+
+class nsIContentSink;
+class nsIRequestObserver;
+class nsString;
+class nsIURI;
+class nsIChannel;
+class nsIContent;
+
+enum eParserCommands {
+ eViewNormal,
+ eViewSource,
+ eViewFragment,
+ eViewErrors
+};
+
+enum eParserDocType {
+ ePlainText = 0,
+ eXML,
+ eHTML_Quirks,
+ eHTML_Strict
+};
+
+enum eStreamState {eNone,eOnStart,eOnDataAvail,eOnStop};
+
+/**
+ * This GECKO-INTERNAL interface is on track to being REMOVED (or refactored
+ * to the point of being near-unrecognizable).
+ *
+ * Please DO NOT #include this file in comm-central code, in your XUL
+ * app or binary extensions.
+ *
+ * Please DO NOT #include this into new files even inside Gecko. It is more
+ * likely than not that #including this header is the wrong thing to do.
+ */
+class nsIParser : public nsParserBase {
+ public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IPARSER_IID)
+
+ /**
+ * Select given content sink into parser for parser output
+ * @update gess5/11/98
+ * @param aSink is the new sink to be used by parser
+ * @return
+ */
+ NS_IMETHOD_(void) SetContentSink(nsIContentSink* aSink)=0;
+
+
+ /**
+ * retrieve the sink set into the parser
+ * @update gess5/11/98
+ * @return current sink
+ */
+ NS_IMETHOD_(nsIContentSink*) GetContentSink(void)=0;
+
+ /**
+ * Call this method once you've created a parser, and want to instruct it
+ * about the command which caused the parser to be constructed. For example,
+ * this allows us to select a DTD which can do, say, view-source.
+ *
+ * @update gess 3/25/98
+ * @param aCommand -- ptrs to string that contains command
+ * @return nada
+ */
+ NS_IMETHOD_(void) GetCommand(nsCString& aCommand)=0;
+ NS_IMETHOD_(void) SetCommand(const char* aCommand)=0;
+ NS_IMETHOD_(void) SetCommand(eParserCommands aParserCommand)=0;
+
+ /**
+ * Call this method once you've created a parser, and want to instruct it
+ * about what charset to load
+ *
+ * @update ftang 4/23/99
+ * @param aCharset- the charest of a document
+ * @param aCharsetSource- the soure of the chares
+ * @return nada
+ */
+ NS_IMETHOD_(void) SetDocumentCharset(const nsACString& aCharset, int32_t aSource)=0;
+ NS_IMETHOD_(void) GetDocumentCharset(nsACString& oCharset, int32_t& oSource)=0;
+
+ /**
+ * Get the channel associated with this parser
+ * @update harishd,gagan 07/17/01
+ * @param aChannel out param that will contain the result
+ * @return NS_OK if successful
+ */
+ NS_IMETHOD GetChannel(nsIChannel** aChannel) override = 0;
+
+ /**
+ * Get the DTD associated with this parser
+ * @update vidur 9/29/99
+ * @param aDTD out param that will contain the result
+ * @return NS_OK if successful, NS_ERROR_FAILURE for runtime error
+ */
+ NS_IMETHOD GetDTD(nsIDTD** aDTD) = 0;
+
+ /**
+ * Get the nsIStreamListener for this parser
+ */
+ virtual nsIStreamListener* GetStreamListener() = 0;
+
+ /**************************************************************************
+ * Parse methods always begin with an input source, and perform
+ * conversions until you wind up being emitted to the given contentsink
+ * (which may or may not be a proxy for the NGLayout content model).
+ ************************************************************************/
+
+ // Call this method to resume the parser from an unblocked state.
+ // This can happen, for example, if parsing was interrupted and then the
+ // consumer needed to restart the parser without waiting for more data.
+ // This also happens after loading scripts, which unblock the parser in
+ // order to process the output of document.write() and then need to
+ // continue on with the page load on an enabled parser.
+ NS_IMETHOD ContinueInterruptedParsing() = 0;
+
+ // Stops parsing temporarily.
+ NS_IMETHOD_(void) BlockParser() = 0;
+
+ // Open up the parser for tokenization, building up content
+ // model..etc. However, this method does not resume parsing
+ // automatically. It's the callers' responsibility to restart
+ // the parsing engine.
+ NS_IMETHOD_(void) UnblockParser() = 0;
+
+ /**
+ * Asynchronously continues parsing.
+ */
+ NS_IMETHOD_(void) ContinueInterruptedParsingAsync() = 0;
+
+ NS_IMETHOD_(bool) IsParserEnabled() override = 0;
+ NS_IMETHOD_(bool) IsComplete() = 0;
+
+ NS_IMETHOD Parse(nsIURI* aURL,
+ nsIRequestObserver* aListener = nullptr,
+ void* aKey = 0,
+ nsDTDMode aMode = eDTDMode_autodetect) = 0;
+
+ NS_IMETHOD Terminate(void) = 0;
+
+ /**
+ * This method gets called when you want to parse a fragment of HTML or XML
+ * surrounded by the context |aTagStack|. It requires that the parser have
+ * been given a fragment content sink.
+ *
+ * @param aSourceBuffer The XML or HTML that hasn't been parsed yet.
+ * @param aTagStack The context of the source buffer.
+ * @return Success or failure.
+ */
+ NS_IMETHOD ParseFragment(const nsAString& aSourceBuffer,
+ nsTArray<nsString>& aTagStack) = 0;
+
+ /**
+ * This method gets called when the tokens have been consumed, and it's time
+ * to build the model via the content sink.
+ * @update gess5/11/98
+ * @return error code -- 0 if model building went well .
+ */
+ NS_IMETHOD BuildModel(void) = 0;
+
+ /**
+ * Call this method to cancel any pending parsing events.
+ * Parsing events may be pending if all of the document's content
+ * has been passed to the parser but the parser has been interrupted
+ * because processing the tokens took too long.
+ *
+ * @update kmcclusk 05/18/01
+ * @return NS_OK if succeeded else ERROR.
+ */
+
+ NS_IMETHOD CancelParsingEvents() = 0;
+
+ virtual void Reset() = 0;
+
+ /**
+ * True if the insertion point (per HTML5) is defined.
+ */
+ virtual bool IsInsertionPointDefined() = 0;
+
+ /**
+ * Call immediately before starting to evaluate a parser-inserted script or
+ * in general when the spec says to define an insertion point.
+ */
+ virtual void PushDefinedInsertionPoint() = 0;
+
+ /**
+ * Call immediately after having evaluated a parser-inserted script or
+ * generally want to restore to the state before the last
+ * PushDefinedInsertionPoint call.
+ */
+ virtual void PopDefinedInsertionPoint() = 0;
+
+ /**
+ * Marks the HTML5 parser as not a script-created parser.
+ */
+ virtual void MarkAsNotScriptCreated(const char* aCommand) = 0;
+
+ /**
+ * True if this is a script-created HTML5 parser.
+ */
+ virtual bool IsScriptCreated() = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIParser, NS_IPARSER_IID)
+
+/* ===========================================================*
+ Some useful constants...
+ * ===========================================================*/
+
+#include "nsError.h"
+
+const nsresult kEOF = NS_ERROR_HTMLPARSER_EOF;
+const nsresult kUnknownError = NS_ERROR_HTMLPARSER_UNKNOWN;
+const nsresult kCantPropagate = NS_ERROR_HTMLPARSER_CANTPROPAGATE;
+const nsresult kContextMismatch = NS_ERROR_HTMLPARSER_CONTEXTMISMATCH;
+const nsresult kBadFilename = NS_ERROR_HTMLPARSER_BADFILENAME;
+const nsresult kBadURL = NS_ERROR_HTMLPARSER_BADURL;
+const nsresult kInvalidParserContext = NS_ERROR_HTMLPARSER_INVALIDPARSERCONTEXT;
+const nsresult kBlocked = NS_ERROR_HTMLPARSER_BLOCK;
+const nsresult kBadStringLiteral = NS_ERROR_HTMLPARSER_UNTERMINATEDSTRINGLITERAL;
+const nsresult kHierarchyTooDeep = NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP;
+const nsresult kFakeEndTag = NS_ERROR_HTMLPARSER_FAKE_ENDTAG;
+const nsresult kNotAComment = NS_ERROR_HTMLPARSER_INVALID_COMMENT;
+
+#define NS_IPARSER_FLAG_UNKNOWN_MODE 0x00000000
+#define NS_IPARSER_FLAG_QUIRKS_MODE 0x00000002
+#define NS_IPARSER_FLAG_STRICT_MODE 0x00000004
+#define NS_IPARSER_FLAG_AUTO_DETECT_MODE 0x00000010
+#define NS_IPARSER_FLAG_VIEW_NORMAL 0x00000020
+#define NS_IPARSER_FLAG_VIEW_SOURCE 0x00000040
+#define NS_IPARSER_FLAG_VIEW_ERRORS 0x00000080
+#define NS_IPARSER_FLAG_PLAIN_TEXT 0x00000100
+#define NS_IPARSER_FLAG_XML 0x00000200
+#define NS_IPARSER_FLAG_HTML 0x00000400
+#define NS_IPARSER_FLAG_SCRIPT_ENABLED 0x00000800
+#define NS_IPARSER_FLAG_FRAMES_ENABLED 0x00001000
+
+#endif
diff --git a/components/htmlparser/src/nsIParserService.h b/components/htmlparser/src/nsIParserService.h
new file mode 100644
index 000000000..2906974e9
--- /dev/null
+++ b/components/htmlparser/src/nsIParserService.h
@@ -0,0 +1,98 @@
+/* -*- 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 nsIParserService_h__
+#define nsIParserService_h__
+
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsHTMLTags.h"
+
+class nsIParser;
+
+#define NS_PARSERSERVICE_CONTRACTID "@mozilla.org/parser/parser-service;1"
+
+// {90a92e37-abd6-441b-9b39-4064d98e1ede}
+#define NS_IPARSERSERVICE_IID \
+{ 0x90a92e37, 0xabd6, 0x441b, { 0x9b, 0x39, 0x40, 0x64, 0xd9, 0x8e, 0x1e, 0xde } }
+
+class nsIParserService : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IPARSERSERVICE_IID)
+
+ /**
+ * Looks up the nsHTMLTag enum value corresponding to the tag in aAtom. The
+ * lookup happens case insensitively.
+ *
+ * @param aAtom The tag to look up.
+ *
+ * @return int32_t The nsHTMLTag enum value corresponding to the tag in aAtom
+ * or eHTMLTag_userdefined if the tag does not correspond to
+ * any of the tag nsHTMLTag enum values.
+ */
+ virtual int32_t HTMLAtomTagToId(nsIAtom* aAtom) const = 0;
+
+ /**
+ * Looks up the nsHTMLTag enum value corresponding to the tag in aAtom.
+ *
+ * @param aAtom The tag to look up.
+ *
+ * @return int32_t The nsHTMLTag enum value corresponding to the tag in aAtom
+ * or eHTMLTag_userdefined if the tag does not correspond to
+ * any of the tag nsHTMLTag enum values.
+ */
+ virtual int32_t HTMLCaseSensitiveAtomTagToId(nsIAtom* aAtom) const = 0;
+
+ /**
+ * Looks up the nsHTMLTag enum value corresponding to the tag in aTag. The
+ * lookup happens case insensitively.
+ *
+ * @param aTag The tag to look up.
+ *
+ * @return int32_t The nsHTMLTag enum value corresponding to the tag in aTag
+ * or eHTMLTag_userdefined if the tag does not correspond to
+ * any of the tag nsHTMLTag enum values.
+ */
+ virtual int32_t HTMLStringTagToId(const nsAString& aTag) const = 0;
+
+ /**
+ * Gets the tag corresponding to the nsHTMLTag enum value in aId. The
+ * returned tag will be in lowercase.
+ *
+ * @param aId The nsHTMLTag enum value to get the tag for.
+ *
+ * @return const char16_t* The tag corresponding to the nsHTMLTag enum
+ * value, or nullptr if the enum value doesn't
+ * correspond to a tag (eHTMLTag_unknown,
+ * eHTMLTag_userdefined, eHTMLTag_text, ...).
+ */
+ virtual const char16_t *HTMLIdToStringTag(int32_t aId) const = 0;
+
+ /**
+ * Gets the tag corresponding to the nsHTMLTag enum value in aId. The
+ * returned tag will be in lowercase.
+ *
+ * @param aId The nsHTMLTag enum value to get the tag for.
+ *
+ * @return nsIAtom* The tag corresponding to the nsHTMLTag enum value, or
+ * nullptr if the enum value doesn't correspond to a tag
+ * (eHTMLTag_unknown, eHTMLTag_userdefined, eHTMLTag_text,
+ * ...).
+ */
+ virtual nsIAtom *HTMLIdToAtomTag(int32_t aId) const = 0;
+
+ NS_IMETHOD HTMLConvertEntityToUnicode(const nsAString& aEntity,
+ int32_t* aUnicode) const = 0;
+
+ NS_IMETHOD HTMLConvertUnicodeToEntity(int32_t aUnicode,
+ nsCString& aEntity) const = 0;
+
+ NS_IMETHOD IsContainer(int32_t aId, bool& aIsContainer) const = 0;
+ NS_IMETHOD IsBlock(int32_t aId, bool& aIsBlock) const = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIParserService, NS_IPARSERSERVICE_IID)
+
+#endif // nsIParserService_h__
diff --git a/components/htmlparser/src/nsITokenizer.h b/components/htmlparser/src/nsITokenizer.h
new file mode 100644
index 000000000..2ed09d410
--- /dev/null
+++ b/components/htmlparser/src/nsITokenizer.h
@@ -0,0 +1,44 @@
+/* -*- 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/. */
+
+
+/**
+ * MODULE NOTES:
+ * @update gess 4/1/98
+ *
+ */
+
+#ifndef __NSITOKENIZER__
+#define __NSITOKENIZER__
+
+#include "nsISupports.h"
+
+class nsScanner;
+
+#define NS_ITOKENIZER_IID \
+{ 0Xae98a348, 0X5e91, 0X41a8, \
+ { 0Xa5, 0Xb4, 0Xd2, 0X20, 0Xf3, 0X1f, 0Xc4, 0Xab } }
+
+/***************************************************************
+ Notes:
+ ***************************************************************/
+
+
+class nsITokenizer : public nsISupports {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITOKENIZER_IID)
+
+ NS_IMETHOD WillTokenize(bool aIsFinalChunk)=0;
+ NS_IMETHOD ConsumeToken(nsScanner& aScanner,bool& aFlushTokens)=0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITokenizer, NS_ITOKENIZER_IID)
+
+#define NS_DECL_NSITOKENIZER \
+ NS_IMETHOD WillTokenize(bool aIsFinalChunk) override;\
+ NS_IMETHOD ConsumeToken(nsScanner& aScanner,bool& aFlushTokens) override;\
+
+
+#endif
diff --git a/components/htmlparser/src/nsParser.cpp b/components/htmlparser/src/nsParser.cpp
new file mode 100644
index 000000000..791ccf772
--- /dev/null
+++ b/components/htmlparser/src/nsParser.cpp
@@ -0,0 +1,1599 @@
+/* -*- 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 "nsIAtom.h"
+#include "nsParser.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsScanner.h"
+#include "plstr.h"
+#include "nsIStringStream.h"
+#include "nsIChannel.h"
+#include "nsICachingChannel.h"
+#include "nsIInputStream.h"
+#include "CNavDTD.h"
+#include "prenv.h"
+#include "prlock.h"
+#include "prcvar.h"
+#include "nsParserCIID.h"
+#include "nsReadableUtils.h"
+#include "nsCOMPtr.h"
+#include "nsExpatDriver.h"
+#include "nsIServiceManager.h"
+#include "nsICategoryManager.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIFragmentContentSink.h"
+#include "nsStreamUtils.h"
+#include "nsHTMLTokenizer.h"
+#include "nsDataHashtable.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsMimeTypes.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/Mutex.h"
+#include "nsParserConstants.h"
+#include "nsCharsetSource.h"
+#include "nsContentUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIHTMLContentSink.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/BinarySearch.h"
+
+using namespace mozilla;
+using mozilla::dom::EncodingUtils;
+
+#define NS_PARSER_FLAG_PARSER_ENABLED 0x00000002
+#define NS_PARSER_FLAG_OBSERVERS_ENABLED 0x00000004
+#define NS_PARSER_FLAG_PENDING_CONTINUE_EVENT 0x00000008
+#define NS_PARSER_FLAG_FLUSH_TOKENS 0x00000020
+#define NS_PARSER_FLAG_CAN_TOKENIZE 0x00000040
+
+//-------------- Begin ParseContinue Event Definition ------------------------
+/*
+The parser can be explicitly interrupted by passing a return value of
+NS_ERROR_HTMLPARSER_INTERRUPTED from BuildModel on the DTD. This will cause
+the parser to stop processing and allow the application to return to the event
+loop. The data which was left at the time of interruption will be processed
+the next time OnDataAvailable is called. If the parser has received its final
+chunk of data then OnDataAvailable will no longer be called by the networking
+module, so the parser will schedule a nsParserContinueEvent which will call
+the parser to process the remaining data after returning to the event loop.
+If the parser is interrupted while processing the remaining data it will
+schedule another ParseContinueEvent. The processing of data followed by
+scheduling of the continue events will proceed until either:
+
+ 1) All of the remaining data can be processed without interrupting
+ 2) The parser has been cancelled.
+
+
+This capability is currently used in CNavDTD and nsHTMLContentSink. The
+nsHTMLContentSink is notified by CNavDTD when a chunk of tokens is going to be
+processed and when each token is processed. The nsHTML content sink records
+the time when the chunk has started processing and will return
+NS_ERROR_HTMLPARSER_INTERRUPTED if the token processing time has exceeded a
+threshold called max tokenizing processing time. This allows the content sink
+to limit how much data is processed in a single chunk which in turn gates how
+much time is spent away from the event loop. Processing smaller chunks of data
+also reduces the time spent in subsequent reflows.
+
+This capability is most apparent when loading large documents. If the maximum
+token processing time is set small enough the application will remain
+responsive during document load.
+
+A side-effect of this capability is that document load is not complete when
+the last chunk of data is passed to OnDataAvailable since the parser may have
+been interrupted when the last chunk of data arrived. The document is complete
+when all of the document has been tokenized and there aren't any pending
+nsParserContinueEvents. This can cause problems if the application assumes
+that it can monitor the load requests to determine when the document load has
+been completed. This is what happens in Mozilla. The document is considered
+completely loaded when all of the load requests have been satisfied. To delay
+the document load until all of the parsing has been completed the
+nsHTMLContentSink adds a dummy parser load request which is not removed until
+the nsHTMLContentSink's DidBuildModel is called. The CNavDTD will not call
+DidBuildModel until the final chunk of data has been passed to the parser
+through the OnDataAvailable and there aren't any pending
+nsParserContineEvents.
+
+Currently the parser is ignores requests to be interrupted during the
+processing of script. This is because a document.write followed by JavaScript
+calls to manipulate the DOM may fail if the parser was interrupted during the
+document.write.
+
+For more details @see bugzilla bug 76722
+*/
+
+
+class nsParserContinueEvent : public Runnable
+{
+public:
+ RefPtr<nsParser> mParser;
+
+ explicit nsParserContinueEvent(nsParser* aParser)
+ : mParser(aParser)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ mParser->HandleParserContinueEvent(this);
+ return NS_OK;
+ }
+};
+
+//-------------- End ParseContinue Event Definition ------------------------
+
+/**
+ * default constructor
+ */
+nsParser::nsParser()
+{
+ Initialize(true);
+}
+
+nsParser::~nsParser()
+{
+ Cleanup();
+}
+
+void
+nsParser::Initialize(bool aConstructor)
+{
+ if (aConstructor) {
+ // Raw pointer
+ mParserContext = 0;
+ }
+ else {
+ // nsCOMPtrs
+ mObserver = nullptr;
+ mUnusedInput.Truncate();
+ }
+
+ mContinueEvent = nullptr;
+ mCharsetSource = kCharsetUninitialized;
+ mCharset.AssignLiteral("ISO-8859-1");
+ mInternalState = NS_OK;
+ mStreamStatus = NS_OK;
+ mCommand = eViewNormal;
+ mFlags = NS_PARSER_FLAG_OBSERVERS_ENABLED |
+ NS_PARSER_FLAG_PARSER_ENABLED |
+ NS_PARSER_FLAG_CAN_TOKENIZE;
+
+ mProcessingNetworkData = false;
+ mIsAboutBlank = false;
+}
+
+void
+nsParser::Cleanup()
+{
+#ifdef DEBUG
+ if (mParserContext && mParserContext->mPrevContext) {
+ NS_WARNING("Extra parser contexts still on the parser stack");
+ }
+#endif
+
+ while (mParserContext) {
+ CParserContext *pc = mParserContext->mPrevContext;
+ delete mParserContext;
+ mParserContext = pc;
+ }
+
+ // It should not be possible for this flag to be set when we are getting
+ // destroyed since this flag implies a pending nsParserContinueEvent, which
+ // has an owning reference to |this|.
+ NS_ASSERTION(!(mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT), "bad");
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsParser)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsParser)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDTD)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSink)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mObserver)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsParser)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDTD)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSink)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObserver)
+ CParserContext *pc = tmp->mParserContext;
+ while (pc) {
+ cb.NoteXPCOMChild(pc->mTokenizer);
+ pc = pc->mPrevContext;
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsParser)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsParser)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsParser)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIParser)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIParser)
+NS_INTERFACE_MAP_END
+
+// The parser continue event is posted only if
+// all of the data to parse has been passed to ::OnDataAvailable
+// and the parser has been interrupted by the content sink
+// because the processing of tokens took too long.
+
+nsresult
+nsParser::PostContinueEvent()
+{
+ if (!(mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT)) {
+ // If this flag isn't set, then there shouldn't be a live continue event!
+ NS_ASSERTION(!mContinueEvent, "bad");
+
+ // This creates a reference cycle between this and the event that is
+ // broken when the event fires.
+ nsCOMPtr<nsIRunnable> event = new nsParserContinueEvent(this);
+ if (NS_FAILED(NS_DispatchToCurrentThread(event))) {
+ NS_WARNING("failed to dispatch parser continuation event");
+ } else {
+ mFlags |= NS_PARSER_FLAG_PENDING_CONTINUE_EVENT;
+ mContinueEvent = event;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsParser::GetCommand(nsCString& aCommand)
+{
+ aCommand = mCommandStr;
+}
+
+/**
+ * Call this method once you've created a parser, and want to instruct it
+ * about the command which caused the parser to be constructed. For example,
+ * this allows us to select a DTD which can do, say, view-source.
+ *
+ * @param aCommand the command string to set
+ */
+NS_IMETHODIMP_(void)
+nsParser::SetCommand(const char* aCommand)
+{
+ mCommandStr.Assign(aCommand);
+ if (mCommandStr.EqualsLiteral("view-source")) {
+ mCommand = eViewSource;
+ } else if (mCommandStr.EqualsLiteral("view-fragment")) {
+ mCommand = eViewFragment;
+ } else {
+ mCommand = eViewNormal;
+ }
+}
+
+/**
+ * Call this method once you've created a parser, and want to instruct it
+ * about the command which caused the parser to be constructed. For example,
+ * this allows us to select a DTD which can do, say, view-source.
+ *
+ * @param aParserCommand the command to set
+ */
+NS_IMETHODIMP_(void)
+nsParser::SetCommand(eParserCommands aParserCommand)
+{
+ mCommand = aParserCommand;
+}
+
+/**
+ * Call this method once you've created a parser, and want to instruct it
+ * about what charset to load
+ *
+ * @param aCharset- the charset of a document
+ * @param aCharsetSource- the source of the charset
+ */
+NS_IMETHODIMP_(void)
+nsParser::SetDocumentCharset(const nsACString& aCharset, int32_t aCharsetSource)
+{
+ mCharset = aCharset;
+ mCharsetSource = aCharsetSource;
+ if (mParserContext && mParserContext->mScanner) {
+ mParserContext->mScanner->SetDocumentCharset(aCharset, aCharsetSource);
+ }
+}
+
+void
+nsParser::SetSinkCharset(nsACString& aCharset)
+{
+ if (mSink) {
+ mSink->SetDocumentCharset(aCharset);
+ }
+}
+
+/**
+ * This method gets called in order to set the content
+ * sink for this parser to dump nodes to.
+ *
+ * @param nsIContentSink interface for node receiver
+ */
+NS_IMETHODIMP_(void)
+nsParser::SetContentSink(nsIContentSink* aSink)
+{
+ NS_PRECONDITION(aSink, "sink cannot be null!");
+ mSink = aSink;
+
+ if (mSink) {
+ mSink->SetParser(this);
+ nsCOMPtr<nsIHTMLContentSink> htmlSink = do_QueryInterface(mSink);
+ if (htmlSink) {
+ mIsAboutBlank = true;
+ }
+ }
+}
+
+/**
+ * retrieve the sink set into the parser
+ * @return current sink
+ */
+NS_IMETHODIMP_(nsIContentSink*)
+nsParser::GetContentSink()
+{
+ return mSink;
+}
+
+static nsIDTD*
+FindSuitableDTD(CParserContext& aParserContext)
+{
+ // We always find a DTD.
+ aParserContext.mAutoDetectStatus = ePrimaryDetect;
+
+ // Quick check for view source.
+ MOZ_ASSERT(aParserContext.mParserCommand != eViewSource,
+ "The old parser is not supposed to be used for View Source "
+ "anymore.");
+
+ // Now see if we're parsing HTML (which, as far as we're concerned, simply
+ // means "not XML").
+ if (aParserContext.mDocType != eXML) {
+ return new CNavDTD();
+ }
+
+ // If we're here, then we'd better be parsing XML.
+ NS_ASSERTION(aParserContext.mDocType == eXML, "What are you trying to send me, here?");
+ return new nsExpatDriver();
+}
+
+NS_IMETHODIMP
+nsParser::CancelParsingEvents()
+{
+ if (mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT) {
+ NS_ASSERTION(mContinueEvent, "mContinueEvent is null");
+ // Revoke the pending continue parsing event
+ mContinueEvent = nullptr;
+ mFlags &= ~NS_PARSER_FLAG_PENDING_CONTINUE_EVENT;
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+/**
+ * Evalutes EXPR1 and EXPR2 exactly once each, in that order. Stores the value
+ * of EXPR2 in RV is EXPR2 fails, otherwise RV contains the result of EXPR1
+ * (which could be success or failure).
+ *
+ * To understand the motivation for this construct, consider these example
+ * methods:
+ *
+ * nsresult nsSomething::DoThatThing(nsIWhatever* obj) {
+ * nsresult rv = NS_OK;
+ * ...
+ * return obj->DoThatThing();
+ * NS_ENSURE_SUCCESS(rv, rv);
+ * ...
+ * return rv;
+ * }
+ *
+ * void nsCaller::MakeThingsHappen() {
+ * return mSomething->DoThatThing(mWhatever);
+ * }
+ *
+ * Suppose, for whatever reason*, we want to shift responsibility for calling
+ * mWhatever->DoThatThing() from nsSomething::DoThatThing up to
+ * nsCaller::MakeThingsHappen. We might rewrite the two methods as follows:
+ *
+ * nsresult nsSomething::DoThatThing() {
+ * nsresult rv = NS_OK;
+ * ...
+ * ...
+ * return rv;
+ * }
+ *
+ * void nsCaller::MakeThingsHappen() {
+ * nsresult rv;
+ * PREFER_LATTER_ERROR_CODE(mSomething->DoThatThing(),
+ * mWhatever->DoThatThing(),
+ * rv);
+ * return rv;
+ * }
+ *
+ * *Possible reasons include: nsCaller doesn't want to give mSomething access
+ * to mWhatever, nsCaller wants to guarantee that mWhatever->DoThatThing() will
+ * be called regardless of how nsSomething::DoThatThing behaves, &c.
+ */
+#define PREFER_LATTER_ERROR_CODE(EXPR1, EXPR2, RV) { \
+ nsresult RV##__temp = EXPR1; \
+ RV = EXPR2; \
+ if (NS_FAILED(RV)) { \
+ RV = RV##__temp; \
+ } \
+}
+
+/**
+ * This gets called just prior to the model actually
+ * being constructed. It's important to make this the
+ * last thing that happens right before parsing, so we
+ * can delay until the last moment the resolution of
+ * which DTD to use (unless of course we're assigned one).
+ */
+nsresult
+nsParser::WillBuildModel(nsString& aFilename)
+{
+ if (!mParserContext)
+ return kInvalidParserContext;
+
+ if (eUnknownDetect != mParserContext->mAutoDetectStatus)
+ return NS_OK;
+
+ if (eDTDMode_unknown == mParserContext->mDTDMode ||
+ eDTDMode_autodetect == mParserContext->mDTDMode) {
+ if (mIsAboutBlank) {
+ mParserContext->mDTDMode = eDTDMode_quirks;
+ mParserContext->mDocType = eHTML_Quirks;
+ } else {
+ mParserContext->mDTDMode = eDTDMode_full_standards;
+ mParserContext->mDocType = eXML;
+ }
+ } // else XML fragment with nested parser context
+
+ NS_ASSERTION(!mDTD || !mParserContext->mPrevContext,
+ "Clobbering DTD for non-root parser context!");
+ mDTD = FindSuitableDTD(*mParserContext);
+ NS_ENSURE_TRUE(mDTD, NS_ERROR_OUT_OF_MEMORY);
+
+ nsITokenizer* tokenizer;
+ nsresult rv = mParserContext->GetTokenizer(mDTD, mSink, tokenizer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDTD->WillBuildModel(*mParserContext, tokenizer, mSink);
+ nsresult sinkResult = mSink->WillBuildModel(mDTD->GetMode());
+ // nsIDTD::WillBuildModel used to be responsible for calling
+ // nsIContentSink::WillBuildModel, but that obligation isn't expressible
+ // in the nsIDTD interface itself, so it's sounder and simpler to give that
+ // responsibility back to the parser. The former behavior of the DTD was to
+ // NS_ENSURE_SUCCESS the sink WillBuildModel call, so if the sink returns
+ // failure we should use sinkResult instead of rv, to preserve the old error
+ // handling behavior of the DTD:
+ return NS_FAILED(sinkResult) ? sinkResult : rv;
+}
+
+/**
+ * This gets called when the parser is done with its input.
+ * Note that the parser may have been called recursively, so we
+ * have to check for a prev. context before closing out the DTD/sink.
+ */
+nsresult
+nsParser::DidBuildModel(nsresult anErrorCode)
+{
+ nsresult result = anErrorCode;
+
+ if (IsComplete()) {
+ if (mParserContext && !mParserContext->mPrevContext) {
+ // Let sink know if we're about to end load because we've been terminated.
+ // In that case we don't want it to run deferred scripts.
+ bool terminated = mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING;
+ if (mDTD && mSink) {
+ nsresult dtdResult = mDTD->DidBuildModel(anErrorCode),
+ sinkResult = mSink->DidBuildModel(terminated);
+ // nsIDTD::DidBuildModel used to be responsible for calling
+ // nsIContentSink::DidBuildModel, but that obligation isn't expressible
+ // in the nsIDTD interface itself, so it's sounder and simpler to give
+ // that responsibility back to the parser. The former behavior of the
+ // DTD was to NS_ENSURE_SUCCESS the sink DidBuildModel call, so if the
+ // sink returns failure we should use sinkResult instead of dtdResult,
+ // to preserve the old error handling behavior of the DTD:
+ result = NS_FAILED(sinkResult) ? sinkResult : dtdResult;
+ }
+
+ //Ref. to bug 61462.
+ mParserContext->mRequest = nullptr;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * This method adds a new parser context to the list,
+ * pushing the current one to the next position.
+ *
+ * @param ptr to new context
+ */
+void
+nsParser::PushContext(CParserContext& aContext)
+{
+ NS_ASSERTION(aContext.mPrevContext == mParserContext,
+ "Trying to push a context whose previous context differs from "
+ "the current parser context.");
+ mParserContext = &aContext;
+}
+
+/**
+ * This method pops the topmost context off the stack,
+ * returning it to the user. The next context (if any)
+ * becomes the current context.
+ * @update gess7/22/98
+ * @return prev. context
+ */
+CParserContext*
+nsParser::PopContext()
+{
+ CParserContext* oldContext = mParserContext;
+ if (oldContext) {
+ mParserContext = oldContext->mPrevContext;
+ if (mParserContext) {
+ // If the old context was blocked, propagate the blocked state
+ // back to the new one. Also, propagate the stream listener state
+ // but don't override onStop state to guarantee the call to DidBuildModel().
+ if (mParserContext->mStreamListenerState != eOnStop) {
+ mParserContext->mStreamListenerState = oldContext->mStreamListenerState;
+ }
+ }
+ }
+ return oldContext;
+}
+
+/**
+ * Call this when you want control whether or not the parser will parse
+ * and tokenize input (TRUE), or whether it just caches input to be
+ * parsed later (FALSE).
+ *
+ * @param aState determines whether we parse/tokenize or just cache.
+ * @return current state
+ */
+void
+nsParser::SetUnusedInput(nsString& aBuffer)
+{
+ mUnusedInput = aBuffer;
+}
+
+/**
+ * Call this when you want to *force* the parser to terminate the
+ * parsing process altogether. This is binary -- so once you terminate
+ * you can't resume without restarting altogether.
+ */
+NS_IMETHODIMP
+nsParser::Terminate(void)
+{
+ // We should only call DidBuildModel once, so don't do anything if this is
+ // the second time that Terminate has been called.
+ if (mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING) {
+ return NS_OK;
+ }
+
+ nsresult result = NS_OK;
+ // XXX - [ until we figure out a way to break parser-sink circularity ]
+ // Hack - Hold a reference until we are completely done...
+ nsCOMPtr<nsIParser> kungFuDeathGrip(this);
+ mInternalState = result = NS_ERROR_HTMLPARSER_STOPPARSING;
+
+ // CancelParsingEvents must be called to avoid leaking the nsParser object
+ // @see bug 108049
+ // If NS_PARSER_FLAG_PENDING_CONTINUE_EVENT is set then CancelParsingEvents
+ // will reset it so DidBuildModel will call DidBuildModel on the DTD. Note:
+ // The IsComplete() call inside of DidBuildModel looks at the pendingContinueEvents flag.
+ CancelParsingEvents();
+
+ // If we got interrupted in the middle of a document.write, then we might
+ // have more than one parser context on our parsercontext stack. This has
+ // the effect of making DidBuildModel a no-op, meaning that we never call
+ // our sink's DidBuildModel and break the reference cycle, causing a leak.
+ // Since we're getting terminated, we manually clean up our context stack.
+ while (mParserContext && mParserContext->mPrevContext) {
+ CParserContext *prev = mParserContext->mPrevContext;
+ delete mParserContext;
+ mParserContext = prev;
+ }
+
+ if (mDTD) {
+ mDTD->Terminate();
+ DidBuildModel(result);
+ } else if (mSink) {
+ // We have no parser context or no DTD yet (so we got terminated before we
+ // got any data). Manually break the reference cycle with the sink.
+ result = mSink->DidBuildModel(true);
+ NS_ENSURE_SUCCESS(result, result);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParser::ContinueInterruptedParsing()
+{
+ // If there are scripts executing, then the content sink is jumping the gun
+ // (probably due to a synchronous XMLHttpRequest) and will re-enable us
+ // later, see bug 460706.
+ if (!IsOkToProcessNetworkData()) {
+ return NS_OK;
+ }
+
+ // If the stream has already finished, there's a good chance
+ // that we might start closing things down when the parser
+ // is reenabled. To make sure that we're not deleted across
+ // the reenabling process, hold a reference to ourselves.
+ nsresult result=NS_OK;
+ nsCOMPtr<nsIParser> kungFuDeathGrip(this);
+ nsCOMPtr<nsIContentSink> sinkDeathGrip(mSink);
+
+#ifdef DEBUG
+ if (!(mFlags & NS_PARSER_FLAG_PARSER_ENABLED)) {
+ NS_WARNING("Don't call ContinueInterruptedParsing on a blocked parser.");
+ }
+#endif
+
+ bool isFinalChunk = mParserContext &&
+ mParserContext->mStreamListenerState == eOnStop;
+
+ mProcessingNetworkData = true;
+ if (sinkDeathGrip) {
+ sinkDeathGrip->WillParse();
+ }
+ result = ResumeParse(true, isFinalChunk); // Ref. bug 57999
+ mProcessingNetworkData = false;
+
+ if (result != NS_OK) {
+ result=mInternalState;
+ }
+
+ return result;
+}
+
+/**
+ * Stops parsing temporarily. That's it will prevent the
+ * parser from building up content model.
+ */
+NS_IMETHODIMP_(void)
+nsParser::BlockParser()
+{
+ mFlags &= ~NS_PARSER_FLAG_PARSER_ENABLED;
+}
+
+/**
+ * Open up the parser for tokenization, building up content
+ * model..etc. However, this method does not resume parsing
+ * automatically. It's the callers' responsibility to restart
+ * the parsing engine.
+ */
+NS_IMETHODIMP_(void)
+nsParser::UnblockParser()
+{
+ if (!(mFlags & NS_PARSER_FLAG_PARSER_ENABLED)) {
+ mFlags |= NS_PARSER_FLAG_PARSER_ENABLED;
+ } else {
+ NS_WARNING("Trying to unblock an unblocked parser.");
+ }
+}
+
+NS_IMETHODIMP_(void)
+nsParser::ContinueInterruptedParsingAsync()
+{
+ mSink->ContinueInterruptedParsingAsync();
+}
+
+/**
+ * Call this to query whether the parser is enabled or not.
+ */
+NS_IMETHODIMP_(bool)
+nsParser::IsParserEnabled()
+{
+ return (mFlags & NS_PARSER_FLAG_PARSER_ENABLED) != 0;
+}
+
+/**
+ * Call this to query whether the parser thinks it's done with parsing.
+ */
+NS_IMETHODIMP_(bool)
+nsParser::IsComplete()
+{
+ return !(mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT);
+}
+
+
+void nsParser::HandleParserContinueEvent(nsParserContinueEvent *ev)
+{
+ // Ignore any revoked continue events...
+ if (mContinueEvent != ev)
+ return;
+
+ mFlags &= ~NS_PARSER_FLAG_PENDING_CONTINUE_EVENT;
+ mContinueEvent = nullptr;
+
+ NS_ASSERTION(IsOkToProcessNetworkData(),
+ "Interrupted in the middle of a script?");
+ ContinueInterruptedParsing();
+}
+
+bool
+nsParser::IsInsertionPointDefined()
+{
+ return false;
+}
+
+void
+nsParser::PushDefinedInsertionPoint()
+{
+}
+
+void
+nsParser::PopDefinedInsertionPoint()
+{
+}
+
+void
+nsParser::MarkAsNotScriptCreated(const char* aCommand)
+{
+}
+
+bool
+nsParser::IsScriptCreated()
+{
+ return false;
+}
+
+/**
+ * This is the main controlling routine in the parsing process.
+ * Note that it may get called multiple times for the same scanner,
+ * since this is a pushed based system, and all the tokens may
+ * not have been consumed by the scanner during a given invocation
+ * of this method.
+ */
+NS_IMETHODIMP
+nsParser::Parse(nsIURI* aURL,
+ nsIRequestObserver* aListener,
+ void* aKey,
+ nsDTDMode aMode)
+{
+
+ NS_PRECONDITION(aURL, "Error: Null URL given");
+
+ nsresult result=kBadURL;
+ mObserver = aListener;
+
+ if (aURL) {
+ nsAutoCString spec;
+ nsresult rv = aURL->GetSpec(spec);
+ if (rv != NS_OK) {
+ return rv;
+ }
+ NS_ConvertUTF8toUTF16 theName(spec);
+
+ nsScanner* theScanner = new nsScanner(theName, false);
+ CParserContext* pc = new CParserContext(mParserContext, theScanner, aKey,
+ mCommand, aListener);
+ if (pc && theScanner) {
+ pc->mMultipart = true;
+ pc->mContextType = CParserContext::eCTURL;
+ pc->mDTDMode = aMode;
+ PushContext(*pc);
+
+ result = NS_OK;
+ } else {
+ result = mInternalState = NS_ERROR_HTMLPARSER_BADCONTEXT;
+ }
+ }
+ return result;
+}
+
+/**
+ * Used by XML fragment parsing below.
+ *
+ * @param aSourceBuffer contains a string-full of real content
+ */
+nsresult
+nsParser::Parse(const nsAString& aSourceBuffer,
+ void* aKey,
+ bool aLastCall)
+{
+ nsresult result = NS_OK;
+
+ // Don't bother if we're never going to parse this.
+ if (mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING) {
+ return result;
+ }
+
+ if (!aLastCall && aSourceBuffer.IsEmpty()) {
+ // Nothing is being passed to the parser so return
+ // immediately. mUnusedInput will get processed when
+ // some data is actually passed in.
+ // But if this is the last call, make sure to finish up
+ // stuff correctly.
+ return result;
+ }
+
+ // Maintain a reference to ourselves so we don't go away
+ // till we're completely done.
+ nsCOMPtr<nsIParser> kungFuDeathGrip(this);
+
+ if (aLastCall || !aSourceBuffer.IsEmpty() || !mUnusedInput.IsEmpty()) {
+ // Note: The following code will always find the parser context associated
+ // with the given key, even if that context has been suspended (e.g., for
+ // another document.write call). This doesn't appear to be exactly what IE
+ // does in the case where this happens, but this makes more sense.
+ CParserContext* pc = mParserContext;
+ while (pc && pc->mKey != aKey) {
+ pc = pc->mPrevContext;
+ }
+
+ if (!pc) {
+ // Only make a new context if we don't have one, OR if we do, but has a
+ // different context key.
+ nsScanner* theScanner = new nsScanner(mUnusedInput);
+ NS_ENSURE_TRUE(theScanner, NS_ERROR_OUT_OF_MEMORY);
+
+ eAutoDetectResult theStatus = eUnknownDetect;
+
+ if (mParserContext &&
+ mParserContext->mMimeType.EqualsLiteral("application/xml")) {
+ // Ref. Bug 90379
+ NS_ASSERTION(mDTD, "How come the DTD is null?");
+
+ if (mParserContext) {
+ theStatus = mParserContext->mAutoDetectStatus;
+ // Added this to fix bug 32022.
+ }
+ }
+
+ pc = new CParserContext(mParserContext, theScanner, aKey, mCommand,
+ 0, theStatus, aLastCall);
+ NS_ENSURE_TRUE(pc, NS_ERROR_OUT_OF_MEMORY);
+
+ PushContext(*pc);
+
+ pc->mMultipart = !aLastCall; // By default
+ if (pc->mPrevContext) {
+ pc->mMultipart |= pc->mPrevContext->mMultipart;
+ }
+
+ // Start fix bug 40143
+ if (pc->mMultipart) {
+ pc->mStreamListenerState = eOnDataAvail;
+ if (pc->mScanner) {
+ pc->mScanner->SetIncremental(true);
+ }
+ } else {
+ pc->mStreamListenerState = eOnStop;
+ if (pc->mScanner) {
+ pc->mScanner->SetIncremental(false);
+ }
+ }
+ // end fix for 40143
+
+ pc->mContextType=CParserContext::eCTString;
+ pc->SetMimeType(NS_LITERAL_CSTRING("application/xml"));
+ pc->mDTDMode = eDTDMode_full_standards;
+
+ mUnusedInput.Truncate();
+
+ pc->mScanner->Append(aSourceBuffer);
+ // Do not interrupt document.write() - bug 95487
+ result = ResumeParse(false, false, false);
+ } else {
+ pc->mScanner->Append(aSourceBuffer);
+ if (!pc->mPrevContext) {
+ // Set stream listener state to eOnStop, on the final context - Fix 68160,
+ // to guarantee DidBuildModel() call - Fix 36148
+ if (aLastCall) {
+ pc->mStreamListenerState = eOnStop;
+ pc->mScanner->SetIncremental(false);
+ }
+
+ if (pc == mParserContext) {
+ // If pc is not mParserContext, then this call to ResumeParse would
+ // do the wrong thing and try to continue parsing using
+ // mParserContext. We need to wait to actually resume parsing on pc.
+ ResumeParse(false, false, false);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+nsParser::ParseFragment(const nsAString& aSourceBuffer,
+ nsTArray<nsString>& aTagStack)
+{
+ nsresult result = NS_OK;
+ nsAutoString theContext;
+ uint32_t theCount = aTagStack.Length();
+ uint32_t theIndex = 0;
+
+ // Disable observers for fragments
+ mFlags &= ~NS_PARSER_FLAG_OBSERVERS_ENABLED;
+
+ for (theIndex = 0; theIndex < theCount; theIndex++) {
+ theContext.Append('<');
+ theContext.Append(aTagStack[theCount - theIndex - 1]);
+ theContext.Append('>');
+ }
+
+ if (theCount == 0) {
+ // Ensure that the buffer is not empty. Because none of the DTDs care
+ // about leading whitespace, this doesn't change the result.
+ theContext.Assign(' ');
+ }
+
+ // First, parse the context to build up the DTD's tag stack. Note that we
+ // pass false for the aLastCall parameter.
+ result = Parse(theContext,
+ (void*)&theContext,
+ false);
+ if (NS_FAILED(result)) {
+ mFlags |= NS_PARSER_FLAG_OBSERVERS_ENABLED;
+ return result;
+ }
+
+ if (!mSink) {
+ // Parse must have failed in the XML case and so the sink was killed.
+ return NS_ERROR_HTMLPARSER_STOPPARSING;
+ }
+
+ nsCOMPtr<nsIFragmentContentSink> fragSink = do_QueryInterface(mSink);
+ NS_ASSERTION(fragSink, "ParseFragment requires a fragment content sink");
+
+ fragSink->WillBuildContent();
+ // Now, parse the actual content. Note that this is the last call
+ // for HTML content, but for XML, we will want to build and parse
+ // the end tags. However, if tagStack is empty, it's the last call
+ // for XML as well.
+ if (theCount == 0) {
+ result = Parse(aSourceBuffer,
+ &theContext,
+ true);
+ fragSink->DidBuildContent();
+ } else {
+ // Add an end tag chunk, so expat will read the whole source buffer,
+ // and not worry about ']]' etc.
+ result = Parse(aSourceBuffer + NS_LITERAL_STRING("</"),
+ &theContext,
+ false);
+ fragSink->DidBuildContent();
+
+ if (NS_SUCCEEDED(result)) {
+ nsAutoString endContext;
+ for (theIndex = 0; theIndex < theCount; theIndex++) {
+ // we already added an end tag chunk above
+ if (theIndex > 0) {
+ endContext.AppendLiteral("</");
+ }
+
+ nsString& thisTag = aTagStack[theIndex];
+ // was there an xmlns=?
+ int32_t endOfTag = thisTag.FindChar(char16_t(' '));
+ if (endOfTag == -1) {
+ endContext.Append(thisTag);
+ } else {
+ endContext.Append(Substring(thisTag,0,endOfTag));
+ }
+
+ endContext.Append('>');
+ }
+
+ result = Parse(endContext,
+ &theContext,
+ true);
+ }
+ }
+
+ mFlags |= NS_PARSER_FLAG_OBSERVERS_ENABLED;
+
+ return result;
+}
+
+/**
+ * This routine is called to cause the parser to continue parsing its
+ * underlying stream. This call allows the parse process to happen in
+ * chunks, such as when the content is push based, and we need to parse in
+ * pieces.
+ *
+ * An interesting change in how the parser gets used has led us to add extra
+ * processing to this method. The case occurs when the parser is blocked in
+ * one context, and gets a parse(string) call in another context. In this
+ * case, the parserContexts are linked. No problem.
+ *
+ * The problem is that Parse(string) assumes that it can proceed unabated,
+ * but if the parser is already blocked that assumption is false. So we
+ * needed to add a mechanism here to allow the parser to continue to process
+ * (the pop and free) contexts until 1) it get's blocked again; 2) it runs
+ * out of contexts.
+ *
+ *
+ * @param allowItertion : set to true if non-script resumption is requested
+ * @param aIsFinalChunk : tells us when the last chunk of data is provided.
+ * @return error code -- 0 if ok, non-zero if error.
+ */
+nsresult
+nsParser::ResumeParse(bool allowIteration, bool aIsFinalChunk,
+ bool aCanInterrupt)
+{
+ nsresult result = NS_OK;
+
+ if ((mFlags & NS_PARSER_FLAG_PARSER_ENABLED) &&
+ mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) {
+
+ result = WillBuildModel(mParserContext->mScanner->GetFilename());
+ if (NS_FAILED(result)) {
+ mFlags &= ~NS_PARSER_FLAG_CAN_TOKENIZE;
+ return result;
+ }
+
+ if (mDTD) {
+ mSink->WillResume();
+ bool theIterationIsOk = true;
+
+ while (result == NS_OK && theIterationIsOk) {
+ if (!mUnusedInput.IsEmpty() && mParserContext->mScanner) {
+ // -- Ref: Bug# 22485 --
+ // Insert the unused input into the source buffer
+ // as if it was read from the input stream.
+ // Adding UngetReadable() per vidur!!
+ mParserContext->mScanner->UngetReadable(mUnusedInput);
+ mUnusedInput.Truncate(0);
+ }
+
+ // Only allow parsing to be interrupted in the subsequent call to
+ // build model.
+ nsresult theTokenizerResult = (mFlags & NS_PARSER_FLAG_CAN_TOKENIZE)
+ ? Tokenize(aIsFinalChunk)
+ : NS_OK;
+ result = BuildModel();
+
+ if (result == NS_ERROR_HTMLPARSER_INTERRUPTED && aIsFinalChunk) {
+ PostContinueEvent();
+ }
+
+ theIterationIsOk = theTokenizerResult != kEOF &&
+ result != NS_ERROR_HTMLPARSER_INTERRUPTED;
+
+ // Make sure not to stop parsing too early. Therefore, before shutting
+ // down the parser, it's important to check whether the input buffer
+ // has been scanned to completion (theTokenizerResult should be kEOF).
+ // kEOF -> End of buffer.
+
+ // If we're told to block the parser, we disable all further parsing
+ // (and cache any data coming in) until the parser is re-enabled.
+ if (NS_ERROR_HTMLPARSER_BLOCK == result) {
+ mSink->WillInterrupt();
+ if (mFlags & NS_PARSER_FLAG_PARSER_ENABLED) {
+ // If we were blocked by a recursive invocation, don't re-block.
+ BlockParser();
+ }
+ return NS_OK;
+ }
+ if (NS_ERROR_HTMLPARSER_STOPPARSING == result) {
+ // Note: Parser Terminate() calls DidBuildModel.
+ if (mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) {
+ DidBuildModel(mStreamStatus);
+ mInternalState = result;
+ }
+
+ return NS_OK;
+ }
+ if ((NS_OK == result && theTokenizerResult == kEOF) ||
+ result == NS_ERROR_HTMLPARSER_INTERRUPTED) {
+ bool theContextIsStringBased =
+ CParserContext::eCTString == mParserContext->mContextType;
+
+ if (mParserContext->mStreamListenerState == eOnStop ||
+ !mParserContext->mMultipart || theContextIsStringBased) {
+ if (!mParserContext->mPrevContext) {
+ if (mParserContext->mStreamListenerState == eOnStop) {
+ DidBuildModel(mStreamStatus);
+ return NS_OK;
+ }
+ } else {
+ CParserContext* theContext = PopContext();
+ if (theContext) {
+ theIterationIsOk = allowIteration && theContextIsStringBased;
+ if (theContext->mCopyUnused) {
+ if (!theContext->mScanner->CopyUnusedData(mUnusedInput)) {
+ mInternalState = NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ delete theContext;
+ }
+
+ result = mInternalState;
+ aIsFinalChunk = mParserContext &&
+ mParserContext->mStreamListenerState == eOnStop;
+ // ...then intentionally fall through to mSink->WillInterrupt()...
+ }
+ }
+ }
+
+ if (theTokenizerResult == kEOF ||
+ result == NS_ERROR_HTMLPARSER_INTERRUPTED) {
+ result = (result == NS_ERROR_HTMLPARSER_INTERRUPTED) ? NS_OK : result;
+ mSink->WillInterrupt();
+ }
+ }
+ } else {
+ mInternalState = result = NS_ERROR_HTMLPARSER_UNRESOLVEDDTD;
+ }
+ }
+
+ return (result == NS_ERROR_HTMLPARSER_INTERRUPTED) ? NS_OK : result;
+}
+
+/**
+ * This is where we loop over the tokens created in the
+ * tokenization phase, and try to make sense out of them.
+ */
+nsresult
+nsParser::BuildModel()
+{
+ nsITokenizer* theTokenizer = nullptr;
+
+ nsresult result = NS_OK;
+ if (mParserContext) {
+ result = mParserContext->GetTokenizer(mDTD, mSink, theTokenizer);
+ }
+
+ if (NS_SUCCEEDED(result)) {
+ if (mDTD) {
+ result = mDTD->BuildModel(theTokenizer, mSink);
+ }
+ } else {
+ mInternalState = result = NS_ERROR_HTMLPARSER_BADTOKENIZER;
+ }
+ return result;
+}
+
+/*******************************************************************
+ These methods are used to talk to the netlib system...
+ *******************************************************************/
+
+nsresult
+nsParser::OnStartRequest(nsIRequest *request, nsISupports* aContext)
+{
+ NS_PRECONDITION(eNone == mParserContext->mStreamListenerState,
+ "Parser's nsIStreamListener API was not setup "
+ "correctly in constructor.");
+ if (mObserver) {
+ mObserver->OnStartRequest(request, aContext);
+ }
+ mParserContext->mStreamListenerState = eOnStart;
+ mParserContext->mAutoDetectStatus = eUnknownDetect;
+ mParserContext->mRequest = request;
+
+ NS_ASSERTION(!mParserContext->mPrevContext,
+ "Clobbering DTD for non-root parser context!");
+ mDTD = nullptr;
+
+ nsresult rv;
+ nsAutoCString contentType;
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ rv = channel->GetContentType(contentType);
+ if (NS_SUCCEEDED(rv)) {
+ mParserContext->SetMimeType(contentType);
+ }
+ }
+
+ rv = NS_OK;
+
+ return rv;
+}
+
+static bool
+ExtractCharsetFromXmlDeclaration(const unsigned char* aBytes, int32_t aLen,
+ nsCString& oCharset)
+{
+ // This code is rather pointless to have. Might as well reuse expat as
+ // seen in nsHtml5StreamParser. -- hsivonen
+ oCharset.Truncate();
+ if ((aLen >= 5) &&
+ ('<' == aBytes[0]) &&
+ ('?' == aBytes[1]) &&
+ ('x' == aBytes[2]) &&
+ ('m' == aBytes[3]) &&
+ ('l' == aBytes[4])) {
+ int32_t i;
+ bool versionFound = false, encodingFound = false;
+ for (i = 6; i < aLen && !encodingFound; ++i) {
+ // end of XML declaration?
+ if ((((char*) aBytes)[i] == '?') &&
+ ((i + 1) < aLen) &&
+ (((char*) aBytes)[i + 1] == '>')) {
+ break;
+ }
+ // Version is required.
+ if (!versionFound) {
+ // Want to avoid string comparisons, hence looking for 'n'
+ // and only if found check the string leading to it. Not
+ // foolproof, but fast.
+ // The shortest string allowed before this is (strlen==13):
+ // <?xml version
+ if ((((char*) aBytes)[i] == 'n') &&
+ (i >= 12) &&
+ (0 == PL_strncmp("versio", (char*) (aBytes + i - 6), 6))) {
+ // Fast forward through version
+ char q = 0;
+ for (++i; i < aLen; ++i) {
+ char qi = ((char*) aBytes)[i];
+ if (qi == '\'' || qi == '"') {
+ if (q && q == qi) {
+ // ending quote
+ versionFound = true;
+ break;
+ } else {
+ // Starting quote
+ q = qi;
+ }
+ }
+ }
+ }
+ } else {
+ // encoding must follow version
+ // Want to avoid string comparisons, hence looking for 'g'
+ // and only if found check the string leading to it. Not
+ // foolproof, but fast.
+ // The shortest allowed string before this (strlen==26):
+ // <?xml version="1" encoding
+ if ((((char*) aBytes)[i] == 'g') && (i >= 25) && (0 == PL_strncmp(
+ "encodin", (char*) (aBytes + i - 7), 7))) {
+ int32_t encStart = 0;
+ char q = 0;
+ for (++i; i < aLen; ++i) {
+ char qi = ((char*) aBytes)[i];
+ if (qi == '\'' || qi == '"') {
+ if (q && q == qi) {
+ int32_t count = i - encStart;
+ // encoding value is invalid if it is UTF-16
+ if (count > 0 && PL_strncasecmp("UTF-16",
+ (char*) (aBytes + encStart), count)) {
+ oCharset.Assign((char*) (aBytes + encStart), count);
+ }
+ encodingFound = true;
+ break;
+ } else {
+ encStart = i + 1;
+ q = qi;
+ }
+ }
+ }
+ }
+ } // if (!versionFound)
+ } // for
+ }
+ return !oCharset.IsEmpty();
+}
+
+inline char
+GetNextChar(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd)
+{
+ NS_ASSERTION(aStart != aEnd, "end of buffer");
+ return (++aStart != aEnd) ? *aStart : '\0';
+}
+
+static nsresult
+NoOpParserWriteFunc(nsIInputStream* in,
+ void* closure,
+ const char* fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount)
+{
+ *writeCount = count;
+ return NS_OK;
+}
+
+typedef struct {
+ bool mNeedCharsetCheck;
+ nsParser* mParser;
+ nsScanner* mScanner;
+ nsIRequest* mRequest;
+} ParserWriteStruct;
+
+/*
+ * This function is invoked as a result of a call to a stream's
+ * ReadSegments() method. It is called for each contiguous buffer
+ * of data in the underlying stream or pipe. Using ReadSegments
+ * allows us to avoid copying data to read out of the stream.
+ */
+static nsresult
+ParserWriteFunc(nsIInputStream* in,
+ void* closure,
+ const char* fromRawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t *writeCount)
+{
+ nsresult result;
+ ParserWriteStruct* pws = static_cast<ParserWriteStruct*>(closure);
+ const unsigned char* buf =
+ reinterpret_cast<const unsigned char*> (fromRawSegment);
+ uint32_t theNumRead = count;
+
+ if (!pws) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (pws->mNeedCharsetCheck) {
+ pws->mNeedCharsetCheck = false;
+ int32_t source;
+ nsAutoCString preferred;
+ nsAutoCString maybePrefer;
+ pws->mParser->GetDocumentCharset(preferred, source);
+
+ // This code was bogus when I found it. It expects the BOM or the XML
+ // declaration to be entirely in the first network buffer. -- hsivonen
+ if (nsContentUtils::CheckForBOM(buf, count, maybePrefer)) {
+ // The decoder will swallow the BOM. The UTF-16 will re-sniff for
+ // endianness. The value of preferred is now either "UTF-8" or "UTF-16".
+ preferred.Assign(maybePrefer);
+ source = kCharsetFromByteOrderMark;
+ } else if (source < kCharsetFromChannel) {
+ nsAutoCString declCharset;
+
+ if (ExtractCharsetFromXmlDeclaration(buf, count, declCharset)) {
+ if (EncodingUtils::FindEncodingForLabel(declCharset, maybePrefer)) {
+ preferred.Assign(maybePrefer);
+ source = kCharsetFromMetaTag;
+ }
+ }
+ }
+
+ pws->mParser->SetDocumentCharset(preferred, source);
+ pws->mParser->SetSinkCharset(preferred);
+
+ }
+
+ result = pws->mScanner->Append(fromRawSegment, theNumRead);
+ if (NS_SUCCEEDED(result)) {
+ *writeCount = count;
+ }
+
+ return result;
+}
+
+nsresult
+nsParser::OnDataAvailable(nsIRequest *request, nsISupports* aContext,
+ nsIInputStream *pIStream, uint64_t sourceOffset,
+ uint32_t aLength)
+{
+ NS_PRECONDITION((eOnStart == mParserContext->mStreamListenerState ||
+ eOnDataAvail == mParserContext->mStreamListenerState),
+ "Error: OnStartRequest() must be called before OnDataAvailable()");
+ NS_PRECONDITION(NS_InputStreamIsBuffered(pIStream),
+ "Must have a buffered input stream");
+
+ nsresult rv = NS_OK;
+
+ if (mIsAboutBlank) {
+ MOZ_ASSERT(false, "Must not get OnDataAvailable for about:blank");
+ // ... but if an extension tries to feed us data for about:blank in a
+ // release build, silently ignore the data.
+ uint32_t totalRead;
+ rv = pIStream->ReadSegments(NoOpParserWriteFunc,
+ nullptr,
+ aLength,
+ &totalRead);
+ return rv;
+ }
+
+ CParserContext *theContext = mParserContext;
+
+ while (theContext && theContext->mRequest != request) {
+ theContext = theContext->mPrevContext;
+ }
+
+ if (theContext) {
+ theContext->mStreamListenerState = eOnDataAvail;
+
+ if (eInvalidDetect == theContext->mAutoDetectStatus) {
+ if (theContext->mScanner) {
+ nsScannerIterator iter;
+ theContext->mScanner->EndReading(iter);
+ theContext->mScanner->SetPosition(iter, true);
+ }
+ }
+
+ uint32_t totalRead;
+ ParserWriteStruct pws;
+ pws.mNeedCharsetCheck = true;
+ pws.mParser = this;
+ pws.mScanner = theContext->mScanner;
+ pws.mRequest = request;
+
+ rv = pIStream->ReadSegments(ParserWriteFunc, &pws, aLength, &totalRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (IsOkToProcessNetworkData()) {
+ nsCOMPtr<nsIParser> kungFuDeathGrip(this);
+ nsCOMPtr<nsIContentSink> sinkDeathGrip(mSink);
+ mProcessingNetworkData = true;
+ if (sinkDeathGrip) {
+ sinkDeathGrip->WillParse();
+ }
+ rv = ResumeParse();
+ mProcessingNetworkData = false;
+ }
+ } else {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ return rv;
+}
+
+/**
+ * This is called by the networking library once the last block of data
+ * has been collected from the net.
+ */
+nsresult
+nsParser::OnStopRequest(nsIRequest *request, nsISupports* aContext,
+ nsresult status)
+{
+ nsresult rv = NS_OK;
+
+ CParserContext *pc = mParserContext;
+ while (pc) {
+ if (pc->mRequest == request) {
+ pc->mStreamListenerState = eOnStop;
+ pc->mScanner->SetIncremental(false);
+ break;
+ }
+
+ pc = pc->mPrevContext;
+ }
+
+ mStreamStatus = status;
+
+ if (IsOkToProcessNetworkData() && NS_SUCCEEDED(rv)) {
+ mProcessingNetworkData = true;
+ if (mSink) {
+ mSink->WillParse();
+ }
+ rv = ResumeParse(true, true);
+ mProcessingNetworkData = false;
+ }
+
+ // If the parser isn't enabled, we don't finish parsing till
+ // it is reenabled.
+
+
+ // XXX Should we wait to notify our observers as well if the
+ // parser isn't yet enabled?
+ if (mObserver) {
+ mObserver->OnStopRequest(request, aContext, status);
+ }
+
+ return rv;
+}
+
+
+/*******************************************************************
+ Here come the tokenization methods...
+ *******************************************************************/
+
+
+/**
+ * Part of the code sandwich, this gets called right before
+ * the tokenization process begins. The main reason for
+ * this call is to allow the delegate to do initialization.
+ */
+bool
+nsParser::WillTokenize(bool aIsFinalChunk)
+{
+ if (!mParserContext) {
+ return true;
+ }
+
+ nsITokenizer* theTokenizer;
+ nsresult result = mParserContext->GetTokenizer(mDTD, mSink, theTokenizer);
+ NS_ENSURE_SUCCESS(result, false);
+ return NS_SUCCEEDED(theTokenizer->WillTokenize(aIsFinalChunk));
+}
+
+
+/**
+ * This is the primary control routine to consume tokens.
+ * It iteratively consumes tokens until an error occurs or
+ * you run out of data.
+ */
+nsresult nsParser::Tokenize(bool aIsFinalChunk)
+{
+ nsITokenizer* theTokenizer;
+
+ nsresult result = NS_ERROR_NOT_AVAILABLE;
+ if (mParserContext) {
+ result = mParserContext->GetTokenizer(mDTD, mSink, theTokenizer);
+ }
+
+ if (NS_SUCCEEDED(result)) {
+ bool flushTokens = false;
+
+ bool killSink = false;
+
+ WillTokenize(aIsFinalChunk);
+ while (NS_SUCCEEDED(result)) {
+ mParserContext->mScanner->Mark();
+ result = theTokenizer->ConsumeToken(*mParserContext->mScanner,
+ flushTokens);
+ if (NS_FAILED(result)) {
+ mParserContext->mScanner->RewindToMark();
+ if (kEOF == result){
+ break;
+ }
+ if (NS_ERROR_HTMLPARSER_STOPPARSING == result) {
+ killSink = true;
+ result = Terminate();
+ break;
+ }
+ } else if (flushTokens && (mFlags & NS_PARSER_FLAG_OBSERVERS_ENABLED)) {
+ // I added the extra test of NS_PARSER_FLAG_OBSERVERS_ENABLED to fix Bug# 23931.
+ // Flush tokens on seeing </SCRIPT> -- Ref: Bug# 22485 --
+ // Also remember to update the marked position.
+ mFlags |= NS_PARSER_FLAG_FLUSH_TOKENS;
+ mParserContext->mScanner->Mark();
+ break;
+ }
+ }
+
+ if (killSink) {
+ mSink = nullptr;
+ }
+ } else {
+ result = mInternalState = NS_ERROR_HTMLPARSER_BADTOKENIZER;
+ }
+
+ return result;
+}
+
+/**
+ * Get the channel associated with this parser
+ *
+ * @param aChannel out param that will contain the result
+ * @return NS_OK if successful
+ */
+NS_IMETHODIMP
+nsParser::GetChannel(nsIChannel** aChannel)
+{
+ nsresult result = NS_ERROR_NOT_AVAILABLE;
+ if (mParserContext && mParserContext->mRequest) {
+ result = CallQueryInterface(mParserContext->mRequest, aChannel);
+ }
+ return result;
+}
+
+/**
+ * Get the DTD associated with this parser
+ */
+NS_IMETHODIMP
+nsParser::GetDTD(nsIDTD** aDTD)
+{
+ if (mParserContext) {
+ NS_IF_ADDREF(*aDTD = mDTD);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Get this as nsIStreamListener
+ */
+nsIStreamListener*
+nsParser::GetStreamListener()
+{
+ return this;
+}
diff --git a/components/htmlparser/src/nsParser.h b/components/htmlparser/src/nsParser.h
new file mode 100644
index 000000000..39bfe03b8
--- /dev/null
+++ b/components/htmlparser/src/nsParser.h
@@ -0,0 +1,398 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/**
+ * MODULE NOTES:
+ *
+ * This class does two primary jobs:
+ * 1) It iterates the tokens provided during the
+ * tokenization process, identifing where elements
+ * begin and end (doing validation and normalization).
+ * 2) It controls and coordinates with an instance of
+ * the IContentSink interface, to coordinate the
+ * the production of the content model.
+ *
+ * The basic operation of this class assumes that an HTML
+ * document is non-normalized. Therefore, we don't process
+ * the document in a normalized way. Don't bother to look
+ * for methods like: doHead() or doBody().
+ *
+ * Instead, in order to be backward compatible, we must
+ * scan the set of tokens and perform this basic set of
+ * operations:
+ * 1) Determine the token type (easy, since the tokens know)
+ * 2) Determine the appropriate section of the HTML document
+ * each token belongs in (HTML,HEAD,BODY,FRAMESET).
+ * 3) Insert content into our document (via the sink) into
+ * the correct section.
+ * 4) In the case of tags that belong in the BODY, we must
+ * ensure that our underlying document state reflects
+ * the appropriate context for our tag.
+ *
+ * For example,if we see a <TR>, we must ensure our
+ * document contains a table into which the row can
+ * be placed. This may result in "implicit containers"
+ * created to ensure a well-formed document.
+ *
+ */
+
+#ifndef NS_PARSER__
+#define NS_PARSER__
+
+#include "nsIParser.h"
+#include "nsDeque.h"
+#include "nsIURL.h"
+#include "CParserContext.h"
+#include "nsParserCIID.h"
+#include "nsITokenizer.h"
+#include "nsHTMLTags.h"
+#include "nsIContentSink.h"
+#include "nsCOMArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWeakReference.h"
+
+class nsIDTD;
+class nsIRunnable;
+
+#ifdef _MSC_VER
+#pragma warning( disable : 4275 )
+#endif
+
+
+class nsParser final : public nsIParser,
+ public nsIStreamListener,
+ public nsSupportsWeakReference
+{
+ /**
+ * Destructor
+ * @update gess5/11/98
+ */
+ virtual ~nsParser();
+
+ public:
+ /**
+ * Called on module init
+ */
+ static nsresult Init();
+
+ /**
+ * Called on module shutdown
+ */
+ static void Shutdown();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsParser, nsIParser)
+
+ /**
+ * default constructor
+ * @update gess5/11/98
+ */
+ nsParser();
+
+ /**
+ * Select given content sink into parser for parser output
+ * @update gess5/11/98
+ * @param aSink is the new sink to be used by parser
+ * @return old sink, or nullptr
+ */
+ NS_IMETHOD_(void) SetContentSink(nsIContentSink* aSink) override;
+
+ /**
+ * retrive the sink set into the parser
+ * @update gess5/11/98
+ * @param aSink is the new sink to be used by parser
+ * @return old sink, or nullptr
+ */
+ NS_IMETHOD_(nsIContentSink*) GetContentSink(void) override;
+
+ /**
+ * Call this method once you've created a parser, and want to instruct it
+ * about the command which caused the parser to be constructed. For example,
+ * this allows us to select a DTD which can do, say, view-source.
+ *
+ * @update gess 3/25/98
+ * @param aCommand -- ptrs to string that contains command
+ * @return nada
+ */
+ NS_IMETHOD_(void) GetCommand(nsCString& aCommand) override;
+ NS_IMETHOD_(void) SetCommand(const char* aCommand) override;
+ NS_IMETHOD_(void) SetCommand(eParserCommands aParserCommand) override;
+
+ /**
+ * Call this method once you've created a parser, and want to instruct it
+ * about what charset to load
+ *
+ * @update ftang 4/23/99
+ * @param aCharset- the charset of a document
+ * @param aCharsetSource- the source of the charset
+ * @return nada
+ */
+ NS_IMETHOD_(void) SetDocumentCharset(const nsACString& aCharset, int32_t aSource) override;
+
+ NS_IMETHOD_(void) GetDocumentCharset(nsACString& aCharset, int32_t& aSource) override
+ {
+ aCharset = mCharset;
+ aSource = mCharsetSource;
+ }
+
+ /**
+ * Cause parser to parse input from given URL
+ * @update gess5/11/98
+ * @param aURL is a descriptor for source document
+ * @param aListener is a listener to forward notifications to
+ * @return TRUE if all went well -- FALSE otherwise
+ */
+ NS_IMETHOD Parse(nsIURI* aURL,
+ nsIRequestObserver* aListener = nullptr,
+ void* aKey = 0,
+ nsDTDMode aMode = eDTDMode_autodetect) override;
+
+ /**
+ * This method needs documentation
+ */
+ NS_IMETHOD ParseFragment(const nsAString& aSourceBuffer,
+ nsTArray<nsString>& aTagStack) override;
+
+ /**
+ * This method gets called when the tokens have been consumed, and it's time
+ * to build the model via the content sink.
+ * @update gess5/11/98
+ * @return YES if model building went well -- NO otherwise.
+ */
+ NS_IMETHOD BuildModel(void) override;
+
+ NS_IMETHOD ContinueInterruptedParsing() override;
+ NS_IMETHOD_(void) BlockParser() override;
+ NS_IMETHOD_(void) UnblockParser() override;
+ NS_IMETHOD_(void) ContinueInterruptedParsingAsync() override;
+ NS_IMETHOD Terminate(void) override;
+
+ /**
+ * Call this to query whether the parser is enabled or not.
+ *
+ * @update vidur 4/12/99
+ * @return current state
+ */
+ NS_IMETHOD_(bool) IsParserEnabled() override;
+
+ /**
+ * Call this to query whether the parser thinks it's done with parsing.
+ *
+ * @update rickg 5/12/01
+ * @return complete state
+ */
+ NS_IMETHOD_(bool) IsComplete() override;
+
+ /**
+ * This rather arcane method (hack) is used as a signal between the
+ * DTD and the parser. It allows the DTD to tell the parser that content
+ * that comes through (parser::parser(string)) but not consumed should
+ * propagate into the next string based parse call.
+ *
+ * @update gess 9/1/98
+ * @param aState determines whether we propagate unused string content.
+ * @return current state
+ */
+ void SetUnusedInput(nsString& aBuffer);
+
+ /**
+ * This method gets called (automatically) during incremental parsing
+ * @update gess5/11/98
+ * @return TRUE if all went well, otherwise FALSE
+ */
+ virtual nsresult ResumeParse(bool allowIteration = true,
+ bool aIsFinalChunk = false,
+ bool aCanInterrupt = true);
+
+ //*********************************************
+ // These methods are callback methods used by
+ // net lib to let us know about our inputstream.
+ //*********************************************
+ // nsIRequestObserver methods:
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // nsIStreamListener methods:
+ NS_DECL_NSISTREAMLISTENER
+
+ void PushContext(CParserContext& aContext);
+ CParserContext* PopContext();
+ CParserContext* PeekContext() {return mParserContext;}
+
+ /**
+ * Get the channel associated with this parser
+ * @update harishd,gagan 07/17/01
+ * @param aChannel out param that will contain the result
+ * @return NS_OK if successful
+ */
+ NS_IMETHOD GetChannel(nsIChannel** aChannel) override;
+
+ /**
+ * Get the DTD associated with this parser
+ * @update vidur 9/29/99
+ * @param aDTD out param that will contain the result
+ * @return NS_OK if successful, NS_ERROR_FAILURE for runtime error
+ */
+ NS_IMETHOD GetDTD(nsIDTD** aDTD) override;
+
+ /**
+ * Get the nsIStreamListener for this parser
+ */
+ virtual nsIStreamListener* GetStreamListener() override;
+
+ void SetSinkCharset(nsACString& aCharset);
+
+ /**
+ * Removes continue parsing events
+ * @update kmcclusk 5/18/98
+ */
+
+ NS_IMETHOD CancelParsingEvents() override;
+
+ /**
+ * Return true.
+ */
+ virtual bool IsInsertionPointDefined() override;
+
+ /**
+ * No-op.
+ */
+ virtual void PushDefinedInsertionPoint() override;
+
+ /**
+ * No-op.
+ */
+ virtual void PopDefinedInsertionPoint() override;
+
+ /**
+ * No-op.
+ */
+ virtual void MarkAsNotScriptCreated(const char* aCommand) override;
+
+ /**
+ * Always false.
+ */
+ virtual bool IsScriptCreated() override;
+
+ /**
+ * Set to parser state to indicate whether parsing tokens can be interrupted
+ * @param aCanInterrupt true if parser can be interrupted, false if it can not be interrupted.
+ * @update kmcclusk 5/18/98
+ */
+ void SetCanInterrupt(bool aCanInterrupt);
+
+ /**
+ * This is called when the final chunk has been
+ * passed to the parser and the content sink has
+ * interrupted token processing. It schedules
+ * a ParserContinue PL_Event which will ask the parser
+ * to HandleParserContinueEvent when it is handled.
+ * @update kmcclusk6/1/2001
+ */
+ nsresult PostContinueEvent();
+
+ /**
+ * Fired when the continue parse event is triggered.
+ * @update kmcclusk 5/18/98
+ */
+ void HandleParserContinueEvent(class nsParserContinueEvent *);
+
+ virtual void Reset() override {
+ Cleanup();
+ Initialize();
+ }
+
+ bool IsScriptExecuting() {
+ return mSink && mSink->IsScriptExecuting();
+ }
+
+ bool IsOkToProcessNetworkData() {
+ return !IsScriptExecuting() && !mProcessingNetworkData;
+ }
+
+ protected:
+
+ void Initialize(bool aConstructor = false);
+ void Cleanup();
+
+ /**
+ *
+ * @update gess5/18/98
+ * @param
+ * @return
+ */
+ nsresult WillBuildModel(nsString& aFilename);
+
+ /**
+ *
+ * @update gess5/18/98
+ * @param
+ * @return
+ */
+ nsresult DidBuildModel(nsresult anErrorCode);
+
+private:
+
+ /*******************************************
+ These are the tokenization methods...
+ *******************************************/
+
+ /**
+ * Part of the code sandwich, this gets called right before
+ * the tokenization process begins. The main reason for
+ * this call is to allow the delegate to do initialization.
+ *
+ * @update gess 3/25/98
+ * @param
+ * @return TRUE if it's ok to proceed
+ */
+ bool WillTokenize(bool aIsFinalChunk = false);
+
+
+ /**
+ * This is the primary control routine. It iteratively
+ * consumes tokens until an error occurs or you run out
+ * of data.
+ *
+ * @update gess 3/25/98
+ * @return error code
+ */
+ nsresult Tokenize(bool aIsFinalChunk = false);
+
+ /**
+ * Pushes XML fragment parsing data to expat without an input stream.
+ */
+ nsresult Parse(const nsAString& aSourceBuffer,
+ void* aKey,
+ bool aLastCall);
+
+protected:
+ //*********************************************
+ // And now, some data members...
+ //*********************************************
+
+
+ CParserContext* mParserContext;
+ nsCOMPtr<nsIDTD> mDTD;
+ nsCOMPtr<nsIRequestObserver> mObserver;
+ nsCOMPtr<nsIContentSink> mSink;
+ nsIRunnable* mContinueEvent; // weak ref
+
+ eParserCommands mCommand;
+ nsresult mInternalState;
+ nsresult mStreamStatus;
+ int32_t mCharsetSource;
+
+ uint16_t mFlags;
+
+ nsString mUnusedInput;
+ nsCString mCharset;
+ nsCString mCommandStr;
+
+ bool mProcessingNetworkData;
+ bool mIsAboutBlank;
+};
+
+#endif
+
diff --git a/components/htmlparser/src/nsParserBase.h b/components/htmlparser/src/nsParserBase.h
new file mode 100644
index 000000000..83b68c554
--- /dev/null
+++ b/components/htmlparser/src/nsParserBase.h
@@ -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/. */
+
+#ifndef nsParserBase_h_
+#define nsParserBase_h_
+
+#include "nsIChannel.h"
+
+class nsParserBase : public nsISupports
+{
+ public:
+ NS_IMETHOD_(bool) IsParserEnabled() { return true; }
+ NS_IMETHOD GetChannel(nsIChannel** aChannel) {
+ *aChannel = nullptr;
+ return NS_OK;
+ }
+};
+
+#endif // nsParserBase_h_
diff --git a/components/htmlparser/src/nsParserCIID.h b/components/htmlparser/src/nsParserCIID.h
new file mode 100644
index 000000000..4a2b7b1ad
--- /dev/null
+++ b/components/htmlparser/src/nsParserCIID.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsParserCIID_h__
+#define nsParserCIID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+// {2ce606b0-bee6-11d1-aad9-00805f8a3e14}
+#define NS_PARSER_CID \
+{ 0x2ce606b0, 0xbee6, 0x11d1, { 0xaa, 0xd9, 0x0, 0x80, 0x5f, 0x8a, 0x3e, 0x14 } }
+
+// XXX: This object should not be exposed outside of the parser.
+// Remove when CNavDTD subclasses do not need access
+#define NS_PARSER_NODE_IID \
+ {0x9039c670, 0x2717, 0x11d2, \
+ {0x92, 0x46, 0x00, 0x80, 0x5f, 0x8a, 0x7a, 0xb6}}
+
+// {a6cf9107-15b3-11d2-932e-00805f8add32}
+#define NS_CNAVDTD_CID \
+{ 0xa6cf9107, 0x15b3, 0x11d2, { 0x93, 0x2e, 0x0, 0x80, 0x5f, 0x8a, 0xdd, 0x32 } }
+
+// {FFF4FBE9-528A-4b37-819D-FC18F3A401A7}
+#define NS_EXPAT_DRIVER_CID \
+{ 0xfff4fbe9, 0x528a, 0x4b37, { 0x81, 0x9d, 0xfc, 0x18, 0xf3, 0xa4, 0x1, 0xa7 } }
+
+// {a6cf910f-15b3-11d2-932e-00805f8add32}
+#define NS_HTMLCONTENTSINKSTREAM_CID \
+{ 0xa6cf910f, 0x15b3, 0x11d2, { 0x93, 0x2e, 0x0, 0x80, 0x5f, 0x8a, 0xdd, 0x32 } }
+
+// {a6cf9112-15b3-11d2-932e-00805f8add32}
+#define NS_PARSERSERVICE_CID \
+{ 0xa6cf9112, 0x15b3, 0x11d2, { 0x93, 0x2e, 0x0, 0x80, 0x5f, 0x8a, 0xdd, 0x32 } }
+
+#endif
diff --git a/components/htmlparser/src/nsParserConstants.h b/components/htmlparser/src/nsParserConstants.h
new file mode 100644
index 000000000..2f2373c7f
--- /dev/null
+++ b/components/htmlparser/src/nsParserConstants.h
@@ -0,0 +1,38 @@
+/* -*- 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/. */
+
+#ifndef nsParserConstants_h_
+#define nsParserConstants_h_
+const char16_t kNewLine = '\n';
+const char16_t kCR = '\r';
+const char16_t kLF = '\n';
+const char16_t kTab = '\t';
+const char16_t kSpace = ' ';
+const char16_t kQuote = '"';
+const char16_t kApostrophe = '\'';
+const char16_t kLessThan = '<';
+const char16_t kGreaterThan = '>';
+const char16_t kAmpersand = '&';
+const char16_t kForwardSlash = '/';
+const char16_t kBackSlash = '\\';
+const char16_t kEqual = '=';
+const char16_t kMinus = '-';
+const char16_t kPlus = '+';
+const char16_t kExclamation = '!';
+const char16_t kSemicolon = ';';
+const char16_t kHashsign = '#';
+const char16_t kAsterisk = '*';
+const char16_t kUnderbar = '_';
+const char16_t kComma = ',';
+const char16_t kLeftParen = '(';
+const char16_t kRightParen = ')';
+const char16_t kLeftBrace = '{';
+const char16_t kRightBrace = '}';
+const char16_t kQuestionMark = '?';
+const char16_t kLeftSquareBracket = '[';
+const char16_t kRightSquareBracket = ']';
+const char16_t kNullCh = '\0';
+
+#endif // nsParserConstants_h_
diff --git a/components/htmlparser/src/nsParserModule.cpp b/components/htmlparser/src/nsParserModule.cpp
new file mode 100644
index 000000000..00c2d6c56
--- /dev/null
+++ b/components/htmlparser/src/nsParserModule.cpp
@@ -0,0 +1,107 @@
+/* -*- 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 "nsIAtom.h"
+#include "nsString.h"
+#include "nspr.h"
+#include "nsCOMPtr.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsParserCIID.h"
+#include "nsParser.h"
+#include "CNavDTD.h"
+#include "nsHTMLEntities.h"
+#include "nsHTMLTokenizer.h"
+//#include "nsTextTokenizer.h"
+#include "nsElementTable.h"
+#include "nsParserService.h"
+#include "nsSAXAttributes.h"
+#include "nsSAXLocator.h"
+#include "nsSAXXMLReader.h"
+
+#if defined(DEBUG)
+#include "nsExpatDriver.h"
+#endif
+
+//----------------------------------------------------------------------
+
+#if defined(DEBUG)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsExpatDriver)
+#endif
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsParser)
+NS_GENERIC_FACTORY_CONSTRUCTOR(CNavDTD)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsParserService)
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSAXAttributes)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSAXXMLReader)
+
+#if defined(DEBUG)
+NS_DEFINE_NAMED_CID(NS_EXPAT_DRIVER_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_PARSER_CID);
+NS_DEFINE_NAMED_CID(NS_CNAVDTD_CID);
+NS_DEFINE_NAMED_CID(NS_PARSERSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SAXATTRIBUTES_CID);
+NS_DEFINE_NAMED_CID(NS_SAXXMLREADER_CID);
+
+static const mozilla::Module::CIDEntry kParserCIDs[] = {
+#if defined(DEBUG)
+ { &kNS_EXPAT_DRIVER_CID, false, nullptr, nsExpatDriverConstructor },
+#endif
+ { &kNS_PARSER_CID, false, nullptr, nsParserConstructor },
+ { &kNS_CNAVDTD_CID, false, nullptr, CNavDTDConstructor },
+ { &kNS_PARSERSERVICE_CID, false, nullptr, nsParserServiceConstructor },
+ { &kNS_SAXATTRIBUTES_CID, false, nullptr, nsSAXAttributesConstructor },
+ { &kNS_SAXXMLREADER_CID, false, nullptr, nsSAXXMLReaderConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kParserContracts[] = {
+ { NS_PARSERSERVICE_CONTRACTID, &kNS_PARSERSERVICE_CID },
+ { NS_SAXATTRIBUTES_CONTRACTID, &kNS_SAXATTRIBUTES_CID },
+ { NS_SAXXMLREADER_CONTRACTID, &kNS_SAXXMLREADER_CID },
+ { nullptr }
+};
+
+static nsresult
+Initialize()
+{
+ nsresult rv = nsHTMLTags::AddRefTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsHTMLEntities::AddRefTable();
+ if (NS_FAILED(rv)) {
+ nsHTMLTags::ReleaseTable();
+ return rv;
+ }
+#ifdef DEBUG
+ CheckElementTable();
+#endif
+
+#ifdef DEBUG
+ nsHTMLTags::TestTagTable();
+#endif
+
+ return rv;
+}
+
+static void
+Shutdown()
+{
+ nsHTMLTags::ReleaseTable();
+ nsHTMLEntities::ReleaseTable();
+}
+
+static mozilla::Module kParserModule = {
+ mozilla::Module::kVersion,
+ kParserCIDs,
+ kParserContracts,
+ nullptr,
+ nullptr,
+ Initialize,
+ Shutdown
+};
+
+NSMODULE_DEFN(nsParserModule) = &kParserModule;
diff --git a/components/htmlparser/src/nsParserMsgUtils.cpp b/components/htmlparser/src/nsParserMsgUtils.cpp
new file mode 100644
index 000000000..627f57a0e
--- /dev/null
+++ b/components/htmlparser/src/nsParserMsgUtils.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsXPIDLString.h"
+#include "nsParserMsgUtils.h"
+#include "nsNetCID.h"
+#include "mozilla/Services.h"
+
+static nsresult GetBundle(const char * aPropFileName, nsIStringBundle **aBundle)
+{
+ NS_ENSURE_ARG_POINTER(aPropFileName);
+ NS_ENSURE_ARG_POINTER(aBundle);
+
+ // Create a bundle for the localization
+
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (!stringService)
+ return NS_ERROR_FAILURE;
+
+ return stringService->CreateBundle(aPropFileName, aBundle);
+}
+
+nsresult
+nsParserMsgUtils::GetLocalizedStringByName(const char * aPropFileName, const char* aKey, nsString& oVal)
+{
+ oVal.Truncate();
+
+ NS_ENSURE_ARG_POINTER(aKey);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = GetBundle(aPropFileName,getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle) {
+ nsXPIDLString valUni;
+ nsAutoString key; key.AssignWithConversion(aKey);
+ rv = bundle->GetStringFromName(key.get(), getter_Copies(valUni));
+ if (NS_SUCCEEDED(rv) && valUni) {
+ oVal.Assign(valUni);
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsParserMsgUtils::GetLocalizedStringByID(const char * aPropFileName, uint32_t aID, nsString& oVal)
+{
+ oVal.Truncate();
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = GetBundle(aPropFileName,getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle) {
+ nsXPIDLString valUni;
+ rv = bundle->GetStringFromID(aID, getter_Copies(valUni));
+ if (NS_SUCCEEDED(rv) && valUni) {
+ oVal.Assign(valUni);
+ }
+ }
+
+ return rv;
+}
diff --git a/components/htmlparser/src/nsParserMsgUtils.h b/components/htmlparser/src/nsParserMsgUtils.h
new file mode 100644
index 000000000..adf3fda8a
--- /dev/null
+++ b/components/htmlparser/src/nsParserMsgUtils.h
@@ -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/. */
+
+#ifndef nsParserMsgUtils_h
+#define nsParserMsgUtils_h
+
+#include "nsString.h"
+
+#define XMLPARSER_PROPERTIES "chrome://global/locale/layout/xmlparser.properties"
+
+class nsParserMsgUtils {
+ nsParserMsgUtils(); // Currently this is not meant to be created, use the static methods
+ ~nsParserMsgUtils(); // If perf required, change this to cache values etc.
+public:
+ static nsresult GetLocalizedStringByName(const char * aPropFileName, const char* aKey, nsString& aVal);
+ static nsresult GetLocalizedStringByID(const char * aPropFileName, uint32_t aID, nsString& aVal);
+};
+
+#endif
diff --git a/components/htmlparser/src/nsParserService.cpp b/components/htmlparser/src/nsParserService.cpp
new file mode 100644
index 000000000..5893f19a9
--- /dev/null
+++ b/components/htmlparser/src/nsParserService.cpp
@@ -0,0 +1,90 @@
+/* -*- 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 "nsError.h"
+#include "nsIAtom.h"
+#include "nsParserService.h"
+#include "nsHTMLEntities.h"
+#include "nsElementTable.h"
+#include "nsICategoryManager.h"
+#include "nsCategoryManagerUtils.h"
+
+nsParserService::nsParserService()
+{
+}
+
+nsParserService::~nsParserService()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsParserService, nsIParserService)
+
+int32_t
+nsParserService::HTMLAtomTagToId(nsIAtom* aAtom) const
+{
+ return nsHTMLTags::StringTagToId(nsDependentAtomString(aAtom));
+}
+
+int32_t
+nsParserService::HTMLCaseSensitiveAtomTagToId(nsIAtom* aAtom) const
+{
+ return nsHTMLTags::CaseSensitiveAtomTagToId(aAtom);
+}
+
+int32_t
+nsParserService::HTMLStringTagToId(const nsAString& aTag) const
+{
+ return nsHTMLTags::StringTagToId(aTag);
+}
+
+const char16_t*
+nsParserService::HTMLIdToStringTag(int32_t aId) const
+{
+ return nsHTMLTags::GetStringValue((nsHTMLTag)aId);
+}
+
+nsIAtom*
+nsParserService::HTMLIdToAtomTag(int32_t aId) const
+{
+ return nsHTMLTags::GetAtom((nsHTMLTag)aId);
+}
+
+NS_IMETHODIMP
+nsParserService::HTMLConvertEntityToUnicode(const nsAString& aEntity,
+ int32_t* aUnicode) const
+{
+ *aUnicode = nsHTMLEntities::EntityToUnicode(aEntity);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParserService::HTMLConvertUnicodeToEntity(int32_t aUnicode,
+ nsCString& aEntity) const
+{
+ const char* str = nsHTMLEntities::UnicodeToEntity(aUnicode);
+ if (str) {
+ aEntity.Assign(str);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParserService::IsContainer(int32_t aId, bool& aIsContainer) const
+{
+ aIsContainer = nsHTMLElement::IsContainer((nsHTMLTag)aId);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParserService::IsBlock(int32_t aId, bool& aIsBlock) const
+{
+ aIsBlock = nsHTMLElement::IsBlock((nsHTMLTag)aId);
+
+ return NS_OK;
+}
diff --git a/components/htmlparser/src/nsParserService.h b/components/htmlparser/src/nsParserService.h
new file mode 100644
index 000000000..0ea7ec98c
--- /dev/null
+++ b/components/htmlparser/src/nsParserService.h
@@ -0,0 +1,58 @@
+/* -*- 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 NS_PARSERSERVICE_H__
+#define NS_PARSERSERVICE_H__
+
+#include "nsIParserService.h"
+
+extern "C" int MOZ_XMLIsLetter(const char* ptr);
+extern "C" int MOZ_XMLIsNCNameChar(const char* ptr);
+/**
+ * Decodes an entity into the UTF-16 encoding of a Unicode character. If a ';'
+ * is found between `ptr` and `end` it will try to decode the entity and set
+ * `*next` to point to the character after the ;. The resulting UTF-16 code
+ * units will be written in `*result`, so if the entity is a valid numeric
+ * entity there needs to be space for at least two char16_t at the location
+ * `result` points to.
+ *
+ * @param ptr pointer to the ampersand.
+ * @param end pointer to the position after the last character of the
+ * string.
+ * @param next [out] will be set to the character after the ';' or null if
+ * the decoding was unsuccessful.
+ * @param result the buffer to write the resulting UTF-16 character in.
+ * @return the number of char16_t written to `*result`.
+ */
+extern "C" int MOZ_XMLTranslateEntity(const char* ptr, const char* end,
+ const char** next, char16_t* result);
+
+class nsParserService : public nsIParserService {
+ virtual ~nsParserService();
+
+public:
+ nsParserService();
+
+ NS_DECL_ISUPPORTS
+
+ int32_t HTMLAtomTagToId(nsIAtom* aAtom) const override;
+
+ int32_t HTMLCaseSensitiveAtomTagToId(nsIAtom* aAtom) const override;
+
+ int32_t HTMLStringTagToId(const nsAString& aTag) const override;
+
+ const char16_t *HTMLIdToStringTag(int32_t aId) const override;
+
+ nsIAtom *HTMLIdToAtomTag(int32_t aId) const override;
+
+ NS_IMETHOD HTMLConvertEntityToUnicode(const nsAString& aEntity,
+ int32_t* aUnicode) const override;
+ NS_IMETHOD HTMLConvertUnicodeToEntity(int32_t aUnicode,
+ nsCString& aEntity) const override;
+ NS_IMETHOD IsContainer(int32_t aId, bool& aIsContainer) const override;
+ NS_IMETHOD IsBlock(int32_t aId, bool& aIsBlock) const override;
+};
+
+#endif
diff --git a/components/htmlparser/src/nsScanner.cpp b/components/htmlparser/src/nsScanner.cpp
new file mode 100644
index 000000000..0fa8e43c6
--- /dev/null
+++ b/components/htmlparser/src/nsScanner.cpp
@@ -0,0 +1,408 @@
+/* -*- 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 __INCREMENTAL 1
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+
+#include "nsScanner.h"
+#include "nsDebug.h"
+#include "nsReadableUtils.h"
+#include "nsIInputStream.h"
+#include "nsIFile.h"
+#include "nsUTF8Utils.h" // for LossyConvertEncoding
+#include "nsCRT.h"
+#include "nsParser.h"
+#include "nsCharsetSource.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+
+using mozilla::dom::EncodingUtils;
+
+nsReadEndCondition::nsReadEndCondition(const char16_t* aTerminateChars) :
+ mChars(aTerminateChars), mFilter(char16_t(~0)) // All bits set
+{
+ // Build filter that will be used to filter out characters with
+ // bits that none of the terminal chars have. This works very well
+ // because terminal chars often have only the last 4-6 bits set and
+ // normal ascii letters have bit 7 set. Other letters have even higher
+ // bits set.
+
+ // Calculate filter
+ const char16_t *current = aTerminateChars;
+ char16_t terminalChar = *current;
+ while (terminalChar) {
+ mFilter &= ~terminalChar;
+ ++current;
+ terminalChar = *current;
+ }
+}
+
+/**
+ * Use this constructor if you want i/o to be based on
+ * a single string you hand in during construction.
+ * This short cut was added for Javascript.
+ *
+ * @update gess 5/12/98
+ * @param aMode represents the parser mode (nav, other)
+ * @return
+ */
+nsScanner::nsScanner(const nsAString& anHTMLString)
+{
+ MOZ_COUNT_CTOR(nsScanner);
+
+ mSlidingBuffer = nullptr;
+ if (AppendToBuffer(anHTMLString)) {
+ mSlidingBuffer->BeginReading(mCurrentPosition);
+ } else {
+ /* XXX see hack below, re: bug 182067 */
+ memset(&mCurrentPosition, 0, sizeof(mCurrentPosition));
+ mEndPosition = mCurrentPosition;
+ }
+ mMarkPosition = mCurrentPosition;
+ mIncremental = false;
+ mUnicodeDecoder = nullptr;
+ mCharsetSource = kCharsetUninitialized;
+}
+
+/**
+ * Use this constructor if you want i/o to be based on strings
+ * the scanner receives. If you pass a null filename, you
+ * can still provide data to the scanner via append.
+ */
+nsScanner::nsScanner(nsString& aFilename, bool aCreateStream)
+ : mFilename(aFilename)
+{
+ MOZ_COUNT_CTOR(nsScanner);
+ NS_ASSERTION(!aCreateStream, "This is always true.");
+
+ mSlidingBuffer = nullptr;
+
+ // XXX This is a big hack. We need to initialize the iterators to something.
+ // What matters is that mCurrentPosition == mEndPosition, so that our methods
+ // believe that we are at EOF (see bug 182067). We null out mCurrentPosition
+ // so that we have some hope of catching null pointer dereferences associated
+ // with this hack. --darin
+ memset(&mCurrentPosition, 0, sizeof(mCurrentPosition));
+ mMarkPosition = mCurrentPosition;
+ mEndPosition = mCurrentPosition;
+
+ mIncremental = true;
+
+ mUnicodeDecoder = nullptr;
+ mCharsetSource = kCharsetUninitialized;
+ // XML defaults to UTF-8 and about:blank is UTF-8, too.
+ SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"), kCharsetFromDocTypeDefault);
+}
+
+nsresult nsScanner::SetDocumentCharset(const nsACString& aCharset , int32_t aSource)
+{
+ if (aSource < mCharsetSource) // priority is lower than the current one
+ return NS_OK;
+
+ mCharsetSource = aSource;
+
+ nsCString charsetName;
+ mozilla::DebugOnly<bool> valid =
+ EncodingUtils::FindEncodingForLabel(aCharset, charsetName);
+ MOZ_ASSERT(valid, "Should never call with a bogus aCharset.");
+
+ if (!mCharset.IsEmpty() && charsetName.Equals(mCharset)) {
+ return NS_OK; // no difference, don't change it
+ }
+
+ // different, need to change it
+
+ mCharset.Assign(charsetName);
+
+ mUnicodeDecoder = EncodingUtils::DecoderForEncoding(mCharset);
+ mUnicodeDecoder->SetInputErrorBehavior(nsIUnicodeDecoder::kOnError_Signal);
+
+ return NS_OK;
+}
+
+
+/**
+ * default destructor
+ *
+ * @update gess 3/25/98
+ * @param
+ * @return
+ */
+nsScanner::~nsScanner() {
+
+ delete mSlidingBuffer;
+
+ MOZ_COUNT_DTOR(nsScanner);
+}
+
+/**
+ * Resets current offset position of input stream to marked position.
+ * This allows us to back up to this point if the need should arise,
+ * such as when tokenization gets interrupted.
+ * NOTE: IT IS REALLY BAD FORM TO CALL RELEASE WITHOUT CALLING MARK FIRST!
+ *
+ * @update gess 5/12/98
+ * @param
+ * @return
+ */
+void nsScanner::RewindToMark(void){
+ if (mSlidingBuffer) {
+ mCurrentPosition = mMarkPosition;
+ }
+}
+
+
+/**
+ * Records current offset position in input stream. This allows us
+ * to back up to this point if the need should arise, such as when
+ * tokenization gets interrupted.
+ *
+ * @update gess 7/29/98
+ * @param
+ * @return
+ */
+int32_t nsScanner::Mark() {
+ int32_t distance = 0;
+ if (mSlidingBuffer) {
+ nsScannerIterator oldStart;
+ mSlidingBuffer->BeginReading(oldStart);
+
+ distance = Distance(oldStart, mCurrentPosition);
+
+ mSlidingBuffer->DiscardPrefix(mCurrentPosition);
+ mSlidingBuffer->BeginReading(mCurrentPosition);
+ mMarkPosition = mCurrentPosition;
+ }
+
+ return distance;
+}
+
+/**
+ * Insert data to our underlying input buffer as
+ * if it were read from an input stream.
+ *
+ * @update harishd 01/12/99
+ * @return error code
+ */
+bool nsScanner::UngetReadable(const nsAString& aBuffer) {
+ if (!mSlidingBuffer) {
+ return false;
+ }
+
+ mSlidingBuffer->UngetReadable(aBuffer,mCurrentPosition);
+ mSlidingBuffer->BeginReading(mCurrentPosition); // Insertion invalidated our iterators
+ mSlidingBuffer->EndReading(mEndPosition);
+
+ return true;
+}
+
+/**
+ * Append data to our underlying input buffer as
+ * if it were read from an input stream.
+ *
+ * @update gess4/3/98
+ * @return error code
+ */
+nsresult nsScanner::Append(const nsAString& aBuffer) {
+ if (!AppendToBuffer(aBuffer))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+/**
+ *
+ *
+ * @update gess 5/21/98
+ * @param
+ * @return
+ */
+nsresult nsScanner::Append(const char* aBuffer, uint32_t aLen)
+{
+ nsresult res = NS_OK;
+ if (mUnicodeDecoder) {
+ int32_t unicharBufLen = 0;
+
+ nsresult rv = mUnicodeDecoder->GetMaxLength(aBuffer, aLen, &unicharBufLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsScannerString::Buffer* buffer = nsScannerString::AllocBuffer(unicharBufLen + 1);
+ NS_ENSURE_TRUE(buffer,NS_ERROR_OUT_OF_MEMORY);
+ char16_t *unichars = buffer->DataStart();
+
+ int32_t totalChars = 0;
+ int32_t unicharLength = unicharBufLen;
+
+ do {
+ int32_t srcLength = aLen;
+ res = mUnicodeDecoder->Convert(aBuffer, &srcLength, unichars, &unicharLength);
+
+ totalChars += unicharLength;
+ // Continuation of failure case
+ if(NS_FAILED(res)) {
+ // if we failed, we consume one byte, replace it with the replacement
+ // character and try the conversion again.
+
+ // This is only needed because some decoders don't follow the
+ // nsIUnicodeDecoder contract: they return a failure when *aDestLength
+ // is 0 rather than the correct NS_OK_UDEC_MOREOUTPUT. See bug 244177
+ if ((unichars + unicharLength) >= buffer->DataEnd()) {
+ NS_ERROR("Unexpected end of destination buffer");
+ break;
+ }
+
+ // Since about:blank is empty, this line runs only for XML. Use a
+ // character that's illegal in XML instead of U+FFFD in order to make
+ // expat flag the error.
+ unichars[unicharLength++] = 0xFFFF;
+
+ unichars = unichars + unicharLength;
+ unicharLength = unicharBufLen - (++totalChars);
+
+ mUnicodeDecoder->Reset();
+
+ if(((uint32_t) (srcLength + 1)) > aLen) {
+ srcLength = aLen;
+ }
+ else {
+ ++srcLength;
+ }
+
+ aBuffer += srcLength;
+ aLen -= srcLength;
+ }
+ } while (NS_FAILED(res) && (aLen > 0));
+
+ buffer->SetDataLength(totalChars);
+ // Don't propagate return code of unicode decoder
+ // since it doesn't reflect on our success or failure
+ // - Ref. bug 87110
+ res = NS_OK;
+ if (!AppendToBuffer(buffer))
+ res = NS_ERROR_OUT_OF_MEMORY;
+ }
+ else {
+ NS_WARNING("No decoder found.");
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+/**
+ * retrieve next char from scanners internal input stream
+ *
+ * @update gess 3/25/98
+ * @param
+ * @return error code reflecting read status
+ */
+nsresult nsScanner::GetChar(char16_t& aChar) {
+ if (!mSlidingBuffer || mCurrentPosition == mEndPosition) {
+ aChar = 0;
+ return kEOF;
+ }
+
+ aChar = *mCurrentPosition++;
+
+ return NS_OK;
+}
+
+
+void nsScanner::BindSubstring(nsScannerSubstring& aSubstring, const nsScannerIterator& aStart, const nsScannerIterator& aEnd)
+{
+ aSubstring.Rebind(*mSlidingBuffer, aStart, aEnd);
+}
+
+void nsScanner::CurrentPosition(nsScannerIterator& aPosition)
+{
+ aPosition = mCurrentPosition;
+}
+
+void nsScanner::EndReading(nsScannerIterator& aPosition)
+{
+ aPosition = mEndPosition;
+}
+
+void nsScanner::SetPosition(nsScannerIterator& aPosition, bool aTerminate)
+{
+ if (mSlidingBuffer) {
+ mCurrentPosition = aPosition;
+ if (aTerminate && (mCurrentPosition == mEndPosition)) {
+ mMarkPosition = mCurrentPosition;
+ mSlidingBuffer->DiscardPrefix(mCurrentPosition);
+ }
+ }
+}
+
+bool nsScanner::AppendToBuffer(nsScannerString::Buffer* aBuf)
+{
+ if (!mSlidingBuffer) {
+ mSlidingBuffer = new nsScannerString(aBuf);
+ if (!mSlidingBuffer)
+ return false;
+ mSlidingBuffer->BeginReading(mCurrentPosition);
+ mMarkPosition = mCurrentPosition;
+ mSlidingBuffer->EndReading(mEndPosition);
+ }
+ else {
+ mSlidingBuffer->AppendBuffer(aBuf);
+ if (mCurrentPosition == mEndPosition) {
+ mSlidingBuffer->BeginReading(mCurrentPosition);
+ }
+ mSlidingBuffer->EndReading(mEndPosition);
+ }
+
+ return true;
+}
+
+/**
+ * call this to copy bytes out of the scanner that have not yet been consumed
+ * by the tokenization process.
+ *
+ * @update gess 5/12/98
+ * @param aCopyBuffer is where the scanner buffer will be copied to
+ * @return true if OK or false on OOM
+ */
+bool nsScanner::CopyUnusedData(nsString& aCopyBuffer) {
+ if (!mSlidingBuffer) {
+ aCopyBuffer.Truncate();
+ return true;
+ }
+
+ nsScannerIterator start, end;
+ start = mCurrentPosition;
+ end = mEndPosition;
+
+ return CopyUnicodeTo(start, end, aCopyBuffer);
+}
+
+/**
+ * Retrieve the name of the file that the scanner is reading from.
+ * In some cases, it's just a given name, because the scanner isn't
+ * really reading from a file.
+ *
+ * @update gess 5/12/98
+ * @return
+ */
+nsString& nsScanner::GetFilename(void) {
+ return mFilename;
+}
+
+/**
+ * Conduct self test. Actually, selftesting for this class
+ * occurs in the parser selftest.
+ *
+ * @update gess 3/25/98
+ * @param
+ * @return
+ */
+
+void nsScanner::SelfTest(void) {
+#ifdef _DEBUG
+#endif
+}
diff --git a/components/htmlparser/src/nsScanner.h b/components/htmlparser/src/nsScanner.h
new file mode 100644
index 000000000..88edcf74e
--- /dev/null
+++ b/components/htmlparser/src/nsScanner.h
@@ -0,0 +1,190 @@
+/* -*- 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/. */
+
+
+/**
+ * MODULE NOTES:
+ * @update gess 4/1/98
+ *
+ * The scanner is a low-level service class that knows
+ * how to consume characters out of an (internal) stream.
+ * This class also offers a series of utility methods
+ * that most tokenizers want, such as readUntil()
+ * and SkipWhitespace().
+ */
+
+
+#ifndef SCANNER
+#define SCANNER
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIParser.h"
+#include "nsIUnicodeDecoder.h"
+#include "nsScannerString.h"
+#include "mozilla/CheckedInt.h"
+
+class nsReadEndCondition {
+public:
+ const char16_t *mChars;
+ char16_t mFilter;
+ explicit nsReadEndCondition(const char16_t* aTerminateChars);
+private:
+ nsReadEndCondition(const nsReadEndCondition& aOther); // No copying
+ void operator=(const nsReadEndCondition& aOther); // No assigning
+};
+
+class nsScanner {
+ public:
+
+ /**
+ * Use this constructor for the XML fragment parsing case
+ */
+ explicit nsScanner(const nsAString& anHTMLString);
+
+ /**
+ * Use this constructor if you want i/o to be based on
+ * a file (therefore a stream) or just data you provide via Append().
+ */
+ nsScanner(nsString& aFilename, bool aCreateStream);
+
+ ~nsScanner();
+
+ /**
+ * retrieve next char from internal input stream
+ *
+ * @update gess 3/25/98
+ * @param ch is the char to accept new value
+ * @return error code reflecting read status
+ */
+ nsresult GetChar(char16_t& ch);
+
+ /**
+ * Records current offset position in input stream. This allows us
+ * to back up to this point if the need should arise, such as when
+ * tokenization gets interrupted.
+ *
+ * @update gess 5/12/98
+ * @param
+ * @return
+ */
+ int32_t Mark(void);
+
+ /**
+ * Resets current offset position of input stream to marked position.
+ * This allows us to back up to this point if the need should arise,
+ * such as when tokenization gets interrupted.
+ * NOTE: IT IS REALLY BAD FORM TO CALL RELEASE WITHOUT CALLING MARK FIRST!
+ *
+ * @update gess 5/12/98
+ * @param
+ * @return
+ */
+ void RewindToMark(void);
+
+
+ /**
+ *
+ *
+ * @update harishd 01/12/99
+ * @param
+ * @return
+ */
+ bool UngetReadable(const nsAString& aBuffer);
+
+ /**
+ *
+ *
+ * @update gess 5/13/98
+ * @param
+ * @return
+ */
+ nsresult Append(const nsAString& aBuffer);
+
+ /**
+ *
+ *
+ * @update gess 5/21/98
+ * @param
+ * @return
+ */
+ nsresult Append(const char* aBuffer, uint32_t aLen);
+
+ /**
+ * Call this to copy bytes out of the scanner that have not yet been consumed
+ * by the tokenization process.
+ *
+ * @update gess 5/12/98
+ * @param aCopyBuffer is where the scanner buffer will be copied to
+ * @return true if OK or false on OOM
+ */
+ bool CopyUnusedData(nsString& aCopyBuffer);
+
+ /**
+ * Retrieve the name of the file that the scanner is reading from.
+ * In some cases, it's just a given name, because the scanner isn't
+ * really reading from a file.
+ *
+ * @update gess 5/12/98
+ * @return
+ */
+ nsString& GetFilename(void);
+
+ static void SelfTest();
+
+ /**
+ * Use this setter to change the scanner's unicode decoder
+ *
+ * @update ftang 3/02/99
+ * @param aCharset a normalized (alias resolved) charset name
+ * @param aCharsetSource- where the charset info came from
+ * @return
+ */
+ nsresult SetDocumentCharset(const nsACString& aCharset, int32_t aSource);
+
+ void BindSubstring(nsScannerSubstring& aSubstring, const nsScannerIterator& aStart, const nsScannerIterator& aEnd);
+ void CurrentPosition(nsScannerIterator& aPosition);
+ void EndReading(nsScannerIterator& aPosition);
+ void SetPosition(nsScannerIterator& aPosition,
+ bool aTruncate = false);
+
+ /**
+ * Internal method used to cause the internal buffer to
+ * be filled with data.
+ *
+ * @update gess4/3/98
+ */
+ bool IsIncremental(void) {return mIncremental;}
+ void SetIncremental(bool anIncrValue) {mIncremental=anIncrValue;}
+
+ protected:
+
+ bool AppendToBuffer(nsScannerString::Buffer* aBuffer);
+ bool AppendToBuffer(const nsAString& aStr)
+ {
+ nsScannerString::Buffer* buf = nsScannerString::AllocBufferFromString(aStr);
+ if (!buf)
+ return false;
+ AppendToBuffer(buf);
+ return true;
+ }
+
+ nsScannerString* mSlidingBuffer;
+ nsScannerIterator mCurrentPosition; // The position we will next read from in the scanner buffer
+ nsScannerIterator mMarkPosition; // The position last marked (we may rewind to here)
+ nsScannerIterator mEndPosition; // The current end of the scanner buffer
+ nsString mFilename;
+ bool mIncremental;
+ int32_t mCharsetSource;
+ nsCString mCharset;
+ nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
+
+ private:
+ nsScanner &operator =(const nsScanner &); // Not implemented.
+};
+
+#endif
+
+
diff --git a/components/htmlparser/src/nsScannerString.cpp b/components/htmlparser/src/nsScannerString.cpp
new file mode 100644
index 000000000..53ac117f1
--- /dev/null
+++ b/components/htmlparser/src/nsScannerString.cpp
@@ -0,0 +1,650 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <stdlib.h>
+#include "nsScannerString.h"
+#include "mozilla/CheckedInt.h"
+
+
+ /**
+ * nsScannerBufferList
+ */
+
+#define MAX_CAPACITY ((UINT32_MAX / sizeof(char16_t)) - \
+ (sizeof(Buffer) + sizeof(char16_t)))
+
+nsScannerBufferList::Buffer*
+nsScannerBufferList::AllocBufferFromString( const nsAString& aString )
+ {
+ uint32_t len = aString.Length();
+ Buffer* buf = AllocBuffer(len);
+
+ if (buf)
+ {
+ nsAString::const_iterator source;
+ aString.BeginReading(source);
+ nsCharTraits<char16_t>::copy(buf->DataStart(), source.get(), len);
+ }
+ return buf;
+ }
+
+nsScannerBufferList::Buffer*
+nsScannerBufferList::AllocBuffer( uint32_t capacity )
+ {
+ if (capacity > MAX_CAPACITY)
+ return nullptr;
+
+ void* ptr = malloc(sizeof(Buffer) + (capacity + 1) * sizeof(char16_t));
+ if (!ptr)
+ return nullptr;
+
+ Buffer* buf = new (ptr) Buffer();
+
+ buf->mUsageCount = 0;
+ buf->mDataEnd = buf->DataStart() + capacity;
+
+ // XXX null terminate. this shouldn't be required, but we do it because
+ // nsScanner erroneously thinks it can dereference DataEnd :-(
+ *buf->mDataEnd = char16_t(0);
+ return buf;
+ }
+
+void
+nsScannerBufferList::ReleaseAll()
+ {
+ while (!mBuffers.isEmpty())
+ {
+ Buffer* node = mBuffers.popFirst();
+ //printf(">>> freeing buffer @%p\n", node);
+ free(node);
+ }
+ }
+
+void
+nsScannerBufferList::SplitBuffer( const Position& pos )
+ {
+ // splitting to the right keeps the work string and any extant token
+ // pointing to and holding a reference count on the same buffer.
+
+ Buffer* bufferToSplit = pos.mBuffer;
+ NS_ASSERTION(bufferToSplit, "null pointer");
+
+ uint32_t splitOffset = pos.mPosition - bufferToSplit->DataStart();
+ NS_ASSERTION(pos.mPosition >= bufferToSplit->DataStart() &&
+ splitOffset <= bufferToSplit->DataLength(),
+ "split offset is outside buffer");
+
+ uint32_t len = bufferToSplit->DataLength() - splitOffset;
+ Buffer* new_buffer = AllocBuffer(len);
+ if (new_buffer)
+ {
+ nsCharTraits<char16_t>::copy(new_buffer->DataStart(),
+ bufferToSplit->DataStart() + splitOffset,
+ len);
+ InsertAfter(new_buffer, bufferToSplit);
+ bufferToSplit->SetDataLength(splitOffset);
+ }
+ }
+
+void
+nsScannerBufferList::DiscardUnreferencedPrefix( Buffer* aBuf )
+ {
+ if (aBuf == Head())
+ {
+ while (!mBuffers.isEmpty() && !Head()->IsInUse())
+ {
+ Buffer* buffer = Head();
+ buffer->remove();
+ free(buffer);
+ }
+ }
+ }
+
+size_t
+nsScannerBufferList::Position::Distance( const Position& aStart, const Position& aEnd )
+ {
+ size_t result = 0;
+ if (aStart.mBuffer == aEnd.mBuffer)
+ {
+ result = aEnd.mPosition - aStart.mPosition;
+ }
+ else
+ {
+ result = aStart.mBuffer->DataEnd() - aStart.mPosition;
+ for (Buffer* b = aStart.mBuffer->Next(); b != aEnd.mBuffer; b = b->Next())
+ result += b->DataLength();
+ result += aEnd.mPosition - aEnd.mBuffer->DataStart();
+ }
+ return result;
+ }
+
+
+/**
+ * nsScannerSubstring
+ */
+
+nsScannerSubstring::nsScannerSubstring()
+ : mStart(nullptr, nullptr)
+ , mEnd(nullptr, nullptr)
+ , mBufferList(nullptr)
+ , mLength(0)
+ , mIsDirty(true)
+ {
+ }
+
+nsScannerSubstring::nsScannerSubstring( const nsAString& s )
+ : mBufferList(nullptr)
+ , mIsDirty(true)
+ {
+ Rebind(s);
+ }
+
+nsScannerSubstring::~nsScannerSubstring()
+ {
+ release_ownership_of_buffer_list();
+ }
+
+int32_t
+nsScannerSubstring::CountChar( char16_t c ) const
+ {
+ /*
+ re-write this to use a counting sink
+ */
+
+ size_type result = 0;
+ size_type lengthToExamine = Length();
+
+ nsScannerIterator iter;
+ for ( BeginReading(iter); ; )
+ {
+ int32_t lengthToExamineInThisFragment = iter.size_forward();
+ const char16_t* fromBegin = iter.get();
+ result += size_type(NS_COUNT(fromBegin, fromBegin+lengthToExamineInThisFragment, c));
+ if ( !(lengthToExamine -= lengthToExamineInThisFragment) )
+ return result;
+ iter.advance(lengthToExamineInThisFragment);
+ }
+ // never reached; quiets warnings
+ return 0;
+ }
+
+void
+nsScannerSubstring::Rebind( const nsScannerSubstring& aString,
+ const nsScannerIterator& aStart,
+ const nsScannerIterator& aEnd )
+ {
+ // allow for the case where &aString == this
+
+ aString.acquire_ownership_of_buffer_list();
+ release_ownership_of_buffer_list();
+
+ mStart = aStart;
+ mEnd = aEnd;
+ mBufferList = aString.mBufferList;
+ mLength = Distance(aStart, aEnd);
+ mIsDirty = true;
+ }
+
+void
+nsScannerSubstring::Rebind( const nsAString& aString )
+ {
+ release_ownership_of_buffer_list();
+
+ mBufferList = new nsScannerBufferList(AllocBufferFromString(aString));
+ mIsDirty = true;
+
+ init_range_from_buffer_list();
+ acquire_ownership_of_buffer_list();
+ }
+
+const nsSubstring&
+nsScannerSubstring::AsString() const
+ {
+ if (mIsDirty)
+ {
+ nsScannerSubstring* mutable_this = const_cast<nsScannerSubstring*>(this);
+
+ if (mStart.mBuffer == mEnd.mBuffer) {
+ // We only have a single fragment to deal with, so just return it
+ // as a substring.
+ mutable_this->mFlattenedRep.Rebind(mStart.mPosition, mEnd.mPosition);
+ } else {
+ // Otherwise, we need to copy the data into a flattened buffer.
+ nsScannerIterator start, end;
+ CopyUnicodeTo(BeginReading(start), EndReading(end), mutable_this->mFlattenedRep);
+ }
+
+ mutable_this->mIsDirty = false;
+ }
+
+ return mFlattenedRep;
+ }
+
+nsScannerIterator&
+nsScannerSubstring::BeginReading( nsScannerIterator& iter ) const
+ {
+ iter.mOwner = this;
+
+ iter.mFragment.mBuffer = mStart.mBuffer;
+ iter.mFragment.mFragmentStart = mStart.mPosition;
+ if (mStart.mBuffer == mEnd.mBuffer)
+ iter.mFragment.mFragmentEnd = mEnd.mPosition;
+ else
+ iter.mFragment.mFragmentEnd = mStart.mBuffer->DataEnd();
+
+ iter.mPosition = mStart.mPosition;
+ iter.normalize_forward();
+ return iter;
+ }
+
+nsScannerIterator&
+nsScannerSubstring::EndReading( nsScannerIterator& iter ) const
+ {
+ iter.mOwner = this;
+
+ iter.mFragment.mBuffer = mEnd.mBuffer;
+ iter.mFragment.mFragmentEnd = mEnd.mPosition;
+ if (mStart.mBuffer == mEnd.mBuffer)
+ iter.mFragment.mFragmentStart = mStart.mPosition;
+ else
+ iter.mFragment.mFragmentStart = mEnd.mBuffer->DataStart();
+
+ iter.mPosition = mEnd.mPosition;
+ // must not |normalize_backward| as that would likely invalidate tests like |while ( first != last )|
+ return iter;
+ }
+
+bool
+nsScannerSubstring::GetNextFragment( nsScannerFragment& frag ) const
+ {
+ // check to see if we are at the end of the buffer list
+ if (frag.mBuffer == mEnd.mBuffer)
+ return false;
+
+ frag.mBuffer = frag.mBuffer->getNext();
+
+ if (frag.mBuffer == mStart.mBuffer)
+ frag.mFragmentStart = mStart.mPosition;
+ else
+ frag.mFragmentStart = frag.mBuffer->DataStart();
+
+ if (frag.mBuffer == mEnd.mBuffer)
+ frag.mFragmentEnd = mEnd.mPosition;
+ else
+ frag.mFragmentEnd = frag.mBuffer->DataEnd();
+
+ return true;
+ }
+
+bool
+nsScannerSubstring::GetPrevFragment( nsScannerFragment& frag ) const
+ {
+ // check to see if we are at the beginning of the buffer list
+ if (frag.mBuffer == mStart.mBuffer)
+ return false;
+
+ frag.mBuffer = frag.mBuffer->getPrevious();
+
+ if (frag.mBuffer == mStart.mBuffer)
+ frag.mFragmentStart = mStart.mPosition;
+ else
+ frag.mFragmentStart = frag.mBuffer->DataStart();
+
+ if (frag.mBuffer == mEnd.mBuffer)
+ frag.mFragmentEnd = mEnd.mPosition;
+ else
+ frag.mFragmentEnd = frag.mBuffer->DataEnd();
+
+ return true;
+ }
+
+
+ /**
+ * nsScannerString
+ */
+
+nsScannerString::nsScannerString( Buffer* aBuf )
+ {
+ mBufferList = new nsScannerBufferList(aBuf);
+
+ init_range_from_buffer_list();
+ acquire_ownership_of_buffer_list();
+ }
+
+void
+nsScannerString::AppendBuffer( Buffer* aBuf )
+ {
+ mBufferList->Append(aBuf);
+ mLength += aBuf->DataLength();
+
+ mEnd.mBuffer = aBuf;
+ mEnd.mPosition = aBuf->DataEnd();
+
+ mIsDirty = true;
+ }
+
+void
+nsScannerString::DiscardPrefix( const nsScannerIterator& aIter )
+ {
+ Position old_start(mStart);
+ mStart = aIter;
+ mLength -= Position::Distance(old_start, mStart);
+
+ mStart.mBuffer->IncrementUsageCount();
+ old_start.mBuffer->DecrementUsageCount();
+
+ mBufferList->DiscardUnreferencedPrefix(old_start.mBuffer);
+
+ mIsDirty = true;
+ }
+
+void
+nsScannerString::UngetReadable( const nsAString& aReadable, const nsScannerIterator& aInsertPoint )
+ /*
+ * Warning: this routine manipulates the shared buffer list in an unexpected way.
+ * The original design did not really allow for insertions, but this call promises
+ * that if called for a point after the end of all extant token strings, that no token string
+ * or the work string will be invalidated.
+ *
+ * This routine is protected because it is the responsibility of the derived class to keep those promises.
+ */
+ {
+ Position insertPos(aInsertPoint);
+
+ mBufferList->SplitBuffer(insertPos);
+ // splitting to the right keeps the work string and any extant token pointing to and
+ // holding a reference count on the same buffer
+
+ Buffer* new_buffer = AllocBufferFromString(aReadable);
+ // make a new buffer with all the data to insert...
+ // BULLSHIT ALERT: we may have empty space to re-use in the split buffer, measure the cost
+ // of this and decide if we should do the work to fill it
+
+ Buffer* buffer_to_split = insertPos.mBuffer;
+ mBufferList->InsertAfter(new_buffer, buffer_to_split);
+ mLength += aReadable.Length();
+
+ mEnd.mBuffer = mBufferList->Tail();
+ mEnd.mPosition = mEnd.mBuffer->DataEnd();
+
+ mIsDirty = true;
+ }
+
+ /**
+ * nsScannerSharedSubstring
+ */
+
+void
+nsScannerSharedSubstring::Rebind(const nsScannerIterator &aStart,
+ const nsScannerIterator &aEnd)
+{
+ // If the start and end positions are inside the same buffer, we must
+ // acquire ownership of the buffer. If not, we can optimize by not holding
+ // onto it.
+
+ Buffer *buffer = const_cast<Buffer*>(aStart.buffer());
+ bool sameBuffer = buffer == aEnd.buffer();
+
+ nsScannerBufferList *bufferList;
+
+ if (sameBuffer) {
+ bufferList = aStart.mOwner->mBufferList;
+ bufferList->AddRef();
+ buffer->IncrementUsageCount();
+ }
+
+ if (mBufferList)
+ ReleaseBuffer();
+
+ if (sameBuffer) {
+ mBuffer = buffer;
+ mBufferList = bufferList;
+ mString.Rebind(aStart.mPosition, aEnd.mPosition);
+ } else {
+ mBuffer = nullptr;
+ mBufferList = nullptr;
+ CopyUnicodeTo(aStart, aEnd, mString);
+ }
+}
+
+void
+nsScannerSharedSubstring::ReleaseBuffer()
+{
+ NS_ASSERTION(mBufferList, "Should only be called with non-null mBufferList");
+ mBuffer->DecrementUsageCount();
+ mBufferList->DiscardUnreferencedPrefix(mBuffer);
+ mBufferList->Release();
+}
+
+void
+nsScannerSharedSubstring::MakeMutable()
+{
+ nsString temp(mString); // this will force a copy of the data
+ mString.Assign(temp); // mString will now share the just-allocated buffer
+
+ ReleaseBuffer();
+
+ mBuffer = nullptr;
+ mBufferList = nullptr;
+}
+
+ /**
+ * utils -- based on code from nsReadableUtils.cpp
+ */
+
+// private helper function
+static inline
+nsAString::iterator&
+copy_multifragment_string( nsScannerIterator& first, const nsScannerIterator& last, nsAString::iterator& result )
+ {
+ typedef nsCharSourceTraits<nsScannerIterator> source_traits;
+ typedef nsCharSinkTraits<nsAString::iterator> sink_traits;
+
+ while ( first != last )
+ {
+ uint32_t distance = source_traits::readable_distance(first, last);
+ sink_traits::write(result, source_traits::read(first), distance);
+ NS_ASSERTION(distance > 0, "|copy_multifragment_string| will never terminate");
+ source_traits::advance(first, distance);
+ }
+
+ return result;
+ }
+
+bool
+CopyUnicodeTo( const nsScannerIterator& aSrcStart,
+ const nsScannerIterator& aSrcEnd,
+ nsAString& aDest )
+ {
+ nsAString::iterator writer;
+
+ mozilla::CheckedInt<nsAString::size_type> distance(Distance(aSrcStart, aSrcEnd));
+ if (!distance.isValid()) {
+ return false; // overflow detected
+ }
+
+ if (!aDest.SetLength(distance.value(), mozilla::fallible)) {
+ aDest.Truncate();
+ return false; // out of memory
+ }
+ aDest.BeginWriting(writer);
+ nsScannerIterator fromBegin(aSrcStart);
+
+ copy_multifragment_string(fromBegin, aSrcEnd, writer);
+ return true;
+ }
+
+bool
+AppendUnicodeTo( const nsScannerIterator& aSrcStart,
+ const nsScannerIterator& aSrcEnd,
+ nsScannerSharedSubstring& aDest )
+ {
+ // Check whether we can just create a dependent string.
+ if (aDest.str().IsEmpty()) {
+ // We can just make |aDest| point to the buffer.
+ // This will take care of copying if the buffer spans fragments.
+ aDest.Rebind(aSrcStart, aSrcEnd);
+ return true;
+ }
+ // The dest string is not empty, so it can't be a dependent substring.
+ return AppendUnicodeTo(aSrcStart, aSrcEnd, aDest.writable());
+ }
+
+bool
+AppendUnicodeTo( const nsScannerIterator& aSrcStart,
+ const nsScannerIterator& aSrcEnd,
+ nsAString& aDest )
+ {
+ nsAString::iterator writer;
+ const nsAString::size_type oldLength = aDest.Length();
+ mozilla::CheckedInt<nsAString::size_type> newLen(Distance(aSrcStart, aSrcEnd));
+ newLen += oldLength;
+ if (!newLen.isValid()) {
+ return false; // overflow detected
+ }
+
+ if (!aDest.SetLength(newLen.value(), mozilla::fallible))
+ return false; // out of memory
+ aDest.BeginWriting(writer).advance(oldLength);
+ nsScannerIterator fromBegin(aSrcStart);
+
+ copy_multifragment_string(fromBegin, aSrcEnd, writer);
+ return true;
+ }
+
+bool
+FindCharInReadable( char16_t aChar,
+ nsScannerIterator& aSearchStart,
+ const nsScannerIterator& aSearchEnd )
+ {
+ while ( aSearchStart != aSearchEnd )
+ {
+ int32_t fragmentLength;
+ if ( SameFragment(aSearchStart, aSearchEnd) )
+ fragmentLength = aSearchEnd.get() - aSearchStart.get();
+ else
+ fragmentLength = aSearchStart.size_forward();
+
+ const char16_t* charFoundAt = nsCharTraits<char16_t>::find(aSearchStart.get(), fragmentLength, aChar);
+ if ( charFoundAt ) {
+ aSearchStart.advance( charFoundAt - aSearchStart.get() );
+ return true;
+ }
+
+ aSearchStart.advance(fragmentLength);
+ }
+
+ return false;
+ }
+
+bool
+FindInReadable( const nsAString& aPattern,
+ nsScannerIterator& aSearchStart,
+ nsScannerIterator& aSearchEnd,
+ const nsStringComparator& compare )
+ {
+ bool found_it = false;
+
+ // only bother searching at all if we're given a non-empty range to search
+ if ( aSearchStart != aSearchEnd )
+ {
+ nsAString::const_iterator aPatternStart, aPatternEnd;
+ aPattern.BeginReading(aPatternStart);
+ aPattern.EndReading(aPatternEnd);
+
+ // outer loop keeps searching till we find it or run out of string to search
+ while ( !found_it )
+ {
+ // fast inner loop (that's what it's called, not what it is) looks for a potential match
+ while ( aSearchStart != aSearchEnd &&
+ compare(aPatternStart.get(), aSearchStart.get(), 1, 1) )
+ ++aSearchStart;
+
+ // if we broke out of the `fast' loop because we're out of string ... we're done: no match
+ if ( aSearchStart == aSearchEnd )
+ break;
+
+ // otherwise, we're at a potential match, let's see if we really hit one
+ nsAString::const_iterator testPattern(aPatternStart);
+ nsScannerIterator testSearch(aSearchStart);
+
+ // slow inner loop verifies the potential match (found by the `fast' loop) at the current position
+ for(;;)
+ {
+ // we already compared the first character in the outer loop,
+ // so we'll advance before the next comparison
+ ++testPattern;
+ ++testSearch;
+
+ // if we verified all the way to the end of the pattern, then we found it!
+ if ( testPattern == aPatternEnd )
+ {
+ found_it = true;
+ aSearchEnd = testSearch; // return the exact found range through the parameters
+ break;
+ }
+
+ // if we got to end of the string we're searching before we hit the end of the
+ // pattern, we'll never find what we're looking for
+ if ( testSearch == aSearchEnd )
+ {
+ aSearchStart = aSearchEnd;
+ break;
+ }
+
+ // else if we mismatched ... it's time to advance to the next search position
+ // and get back into the `fast' loop
+ if ( compare(testPattern.get(), testSearch.get(), 1, 1) )
+ {
+ ++aSearchStart;
+ break;
+ }
+ }
+ }
+ }
+
+ return found_it;
+ }
+
+ /**
+ * This implementation is simple, but does too much work.
+ * It searches the entire string from left to right, and returns the last match found, if any.
+ * This implementation will be replaced when I get |reverse_iterator|s working.
+ */
+bool
+RFindInReadable( const nsAString& aPattern,
+ nsScannerIterator& aSearchStart,
+ nsScannerIterator& aSearchEnd,
+ const nsStringComparator& aComparator )
+ {
+ bool found_it = false;
+
+ nsScannerIterator savedSearchEnd(aSearchEnd);
+ nsScannerIterator searchStart(aSearchStart), searchEnd(aSearchEnd);
+
+ while ( searchStart != searchEnd )
+ {
+ if ( FindInReadable(aPattern, searchStart, searchEnd, aComparator) )
+ {
+ found_it = true;
+
+ // this is the best match so far, so remember it
+ aSearchStart = searchStart;
+ aSearchEnd = searchEnd;
+
+ // ...and get ready to search some more
+ // (it's tempting to set |searchStart=searchEnd| ... but that misses overlapping patterns)
+ ++searchStart;
+ searchEnd = savedSearchEnd;
+ }
+ }
+
+ // if we never found it, return an empty range
+ if ( !found_it )
+ aSearchStart = aSearchEnd;
+
+ return found_it;
+ }
diff --git a/components/htmlparser/src/nsScannerString.h b/components/htmlparser/src/nsScannerString.h
new file mode 100644
index 000000000..247c04c04
--- /dev/null
+++ b/components/htmlparser/src/nsScannerString.h
@@ -0,0 +1,604 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsScannerString_h___
+#define nsScannerString_h___
+
+#include "nsString.h"
+#include "nsUnicharUtils.h" // for nsCaseInsensitiveStringComparator
+#include "mozilla/LinkedList.h"
+#include <algorithm>
+
+
+ /**
+ * NOTE: nsScannerString (and the other classes defined in this file) are
+ * not related to nsAString or any of the other xpcom/string classes.
+ *
+ * nsScannerString is based on the nsSlidingString implementation that used
+ * to live in xpcom/string. Now that nsAString is limited to representing
+ * only single fragment strings, nsSlidingString can no longer be used.
+ *
+ * An advantage to this design is that it does not employ any virtual
+ * functions.
+ *
+ * This file uses SCC-style indenting in deference to the nsSlidingString
+ * code from which this code is derived ;-)
+ */
+
+class nsScannerIterator;
+class nsScannerSubstring;
+class nsScannerString;
+
+
+ /**
+ * nsScannerBufferList
+ *
+ * This class maintains a list of heap-allocated Buffer objects. The buffers
+ * are maintained in a circular linked list. Each buffer has a usage count
+ * that is decremented by the owning nsScannerSubstring.
+ *
+ * The buffer list itself is reference counted. This allows the buffer list
+ * to be shared by multiple nsScannerSubstring objects. The reference
+ * counting is not threadsafe, which is not at all a requirement.
+ *
+ * When a nsScannerSubstring releases its reference to a buffer list, it
+ * decrements the usage count of the first buffer in the buffer list that it
+ * was referencing. It informs the buffer list that it can discard buffers
+ * starting at that prefix. The buffer list will do so if the usage count of
+ * that buffer is 0 and if it is the first buffer in the list. It will
+ * continue to prune buffers starting from the front of the buffer list until
+ * it finds a buffer that has a usage count that is non-zero.
+ */
+class nsScannerBufferList
+ {
+ public:
+
+ /**
+ * Buffer objects are directly followed by a data segment. The start
+ * of the data segment is determined by increment the |this| pointer
+ * by 1 unit.
+ */
+ class Buffer : public mozilla::LinkedListElement<Buffer>
+ {
+ public:
+
+ void IncrementUsageCount() { ++mUsageCount; }
+ void DecrementUsageCount() { --mUsageCount; }
+
+ bool IsInUse() const { return mUsageCount != 0; }
+
+ const char16_t* DataStart() const { return (const char16_t*) (this+1); }
+ char16_t* DataStart() { return ( char16_t*) (this+1); }
+
+ const char16_t* DataEnd() const { return mDataEnd; }
+ char16_t* DataEnd() { return mDataEnd; }
+
+ const Buffer* Next() const { return getNext(); }
+ Buffer* Next() { return getNext(); }
+
+ const Buffer* Prev() const { return getPrevious(); }
+ Buffer* Prev() { return getPrevious(); }
+
+ uint32_t DataLength() const { return mDataEnd - DataStart(); }
+ void SetDataLength(uint32_t len) { mDataEnd = DataStart() + len; }
+
+ private:
+
+ friend class nsScannerBufferList;
+
+ int32_t mUsageCount;
+ char16_t* mDataEnd;
+ };
+
+ /**
+ * Position objects serve as lightweight pointers into a buffer list.
+ * The mPosition member must be contained with mBuffer->DataStart()
+ * and mBuffer->DataEnd().
+ */
+ class Position
+ {
+ public:
+
+ Position() {}
+
+ Position( Buffer* buffer, char16_t* position )
+ : mBuffer(buffer)
+ , mPosition(position)
+ {}
+
+ inline
+ explicit Position( const nsScannerIterator& aIter );
+
+ inline
+ Position& operator=( const nsScannerIterator& aIter );
+
+ static size_t Distance( const Position& p1, const Position& p2 );
+
+ Buffer* mBuffer;
+ char16_t* mPosition;
+ };
+
+ static Buffer* AllocBufferFromString( const nsAString& );
+ static Buffer* AllocBuffer( uint32_t capacity ); // capacity = number of chars
+
+ explicit nsScannerBufferList( Buffer* buf )
+ : mRefCnt(0)
+ {
+ mBuffers.insertBack(buf);
+ }
+
+ void AddRef() { ++mRefCnt; }
+ void Release() { if (--mRefCnt == 0) delete this; }
+
+ void Append( Buffer* buf ) { mBuffers.insertBack(buf); }
+ void InsertAfter( Buffer* buf, Buffer* prev ) { prev->setNext(buf); }
+ void SplitBuffer( const Position& );
+ void DiscardUnreferencedPrefix( Buffer* );
+
+ Buffer* Head() { return mBuffers.getFirst(); }
+ const Buffer* Head() const { return mBuffers.getFirst(); }
+
+ Buffer* Tail() { return mBuffers.getLast(); }
+ const Buffer* Tail() const { return mBuffers.getLast(); }
+
+ private:
+
+ friend class nsScannerSubstring;
+
+ ~nsScannerBufferList() { ReleaseAll(); }
+ void ReleaseAll();
+
+ int32_t mRefCnt;
+ mozilla::LinkedList<Buffer> mBuffers;
+ };
+
+
+ /**
+ * nsScannerFragment represents a "slice" of a Buffer object.
+ */
+struct nsScannerFragment
+ {
+ typedef nsScannerBufferList::Buffer Buffer;
+
+ const Buffer* mBuffer;
+ const char16_t* mFragmentStart;
+ const char16_t* mFragmentEnd;
+ };
+
+
+ /**
+ * nsScannerSubstring is the base class for nsScannerString. It provides
+ * access to iterators and methods to bind the substring to another
+ * substring or nsAString instance.
+ *
+ * This class owns the buffer list.
+ */
+class nsScannerSubstring
+ {
+ public:
+ typedef nsScannerBufferList::Buffer Buffer;
+ typedef nsScannerBufferList::Position Position;
+ typedef uint32_t size_type;
+
+ nsScannerSubstring();
+ explicit nsScannerSubstring( const nsAString& s );
+
+ ~nsScannerSubstring();
+
+ nsScannerIterator& BeginReading( nsScannerIterator& iter ) const;
+ nsScannerIterator& EndReading( nsScannerIterator& iter ) const;
+
+ size_type Length() const { return mLength; }
+
+ int32_t CountChar( char16_t ) const;
+
+ void Rebind( const nsScannerSubstring&, const nsScannerIterator&, const nsScannerIterator& );
+ void Rebind( const nsAString& );
+
+ const nsSubstring& AsString() const;
+
+ bool GetNextFragment( nsScannerFragment& ) const;
+ bool GetPrevFragment( nsScannerFragment& ) const;
+
+ static inline Buffer* AllocBufferFromString( const nsAString& aStr ) { return nsScannerBufferList::AllocBufferFromString(aStr); }
+ static inline Buffer* AllocBuffer( size_type aCapacity ) { return nsScannerBufferList::AllocBuffer(aCapacity); }
+
+ protected:
+
+ void acquire_ownership_of_buffer_list() const
+ {
+ mBufferList->AddRef();
+ mStart.mBuffer->IncrementUsageCount();
+ }
+
+ void release_ownership_of_buffer_list()
+ {
+ if (mBufferList)
+ {
+ mStart.mBuffer->DecrementUsageCount();
+ mBufferList->DiscardUnreferencedPrefix(mStart.mBuffer);
+ mBufferList->Release();
+ }
+ }
+
+ void init_range_from_buffer_list()
+ {
+ mStart.mBuffer = mBufferList->Head();
+ mStart.mPosition = mStart.mBuffer->DataStart();
+
+ mEnd.mBuffer = mBufferList->Tail();
+ mEnd.mPosition = mEnd.mBuffer->DataEnd();
+
+ mLength = Position::Distance(mStart, mEnd);
+ }
+
+ Position mStart;
+ Position mEnd;
+ nsScannerBufferList *mBufferList;
+ size_type mLength;
+
+ // these fields are used to implement AsString
+ nsDependentSubstring mFlattenedRep;
+ bool mIsDirty;
+
+ friend class nsScannerSharedSubstring;
+ };
+
+
+ /**
+ * nsScannerString provides methods to grow and modify a buffer list.
+ */
+class nsScannerString : public nsScannerSubstring
+ {
+ public:
+
+ explicit nsScannerString( Buffer* );
+
+ // you are giving ownership to the string, it takes and keeps your
+ // buffer, deleting it when done.
+ // Use AllocBuffer or AllocBufferFromString to create a Buffer object
+ // for use with this function.
+ void AppendBuffer( Buffer* );
+
+ void DiscardPrefix( const nsScannerIterator& );
+ // any other way you want to do this?
+
+ void UngetReadable(const nsAString& aReadable, const nsScannerIterator& aCurrentPosition);
+ };
+
+
+ /**
+ * nsScannerSharedSubstring implements copy-on-write semantics for
+ * nsScannerSubstring. When you call .writable(), it will copy the data
+ * and return a mutable string object. This class also manages releasing
+ * the reference to the scanner buffer when it is no longer needed.
+ */
+
+class nsScannerSharedSubstring
+ {
+ public:
+ nsScannerSharedSubstring()
+ : mBuffer(nullptr), mBufferList(nullptr) { }
+
+ ~nsScannerSharedSubstring()
+ {
+ if (mBufferList)
+ ReleaseBuffer();
+ }
+
+ // Acquire a copy-on-write reference to the given substring.
+ void Rebind(const nsScannerIterator& aStart,
+ const nsScannerIterator& aEnd);
+
+ // Get a mutable reference to this string
+ nsSubstring& writable()
+ {
+ if (mBufferList)
+ MakeMutable();
+
+ return mString;
+ }
+
+ // Get a const reference to this string
+ const nsSubstring& str() const { return mString; }
+
+ private:
+ typedef nsScannerBufferList::Buffer Buffer;
+
+ void ReleaseBuffer();
+ void MakeMutable();
+
+ nsDependentSubstring mString;
+ Buffer *mBuffer;
+ nsScannerBufferList *mBufferList;
+ };
+
+ /**
+ * nsScannerIterator works just like nsReadingIterator<CharT> except that
+ * it knows how to iterate over a list of scanner buffers.
+ */
+class nsScannerIterator
+ {
+ public:
+ typedef nsScannerIterator self_type;
+ typedef ptrdiff_t difference_type;
+ typedef char16_t value_type;
+ typedef const char16_t* pointer;
+ typedef const char16_t& reference;
+ typedef nsScannerSubstring::Buffer Buffer;
+
+ protected:
+
+ nsScannerFragment mFragment;
+ const char16_t* mPosition;
+ const nsScannerSubstring* mOwner;
+
+ friend class nsScannerSubstring;
+ friend class nsScannerSharedSubstring;
+
+ public:
+ // nsScannerIterator(); // auto-generate default constructor is OK
+ // nsScannerIterator( const nsScannerIterator& ); // auto-generated copy-constructor OK
+ // nsScannerIterator& operator=( const nsScannerIterator& ); // auto-generated copy-assignment operator OK
+
+ inline void normalize_forward();
+ inline void normalize_backward();
+
+ pointer get() const
+ {
+ return mPosition;
+ }
+
+ char16_t operator*() const
+ {
+ return *get();
+ }
+
+ const nsScannerFragment& fragment() const
+ {
+ return mFragment;
+ }
+
+ const Buffer* buffer() const
+ {
+ return mFragment.mBuffer;
+ }
+
+ self_type& operator++()
+ {
+ ++mPosition;
+ normalize_forward();
+ return *this;
+ }
+
+ self_type operator++( int )
+ {
+ self_type result(*this);
+ ++mPosition;
+ normalize_forward();
+ return result;
+ }
+
+ self_type& operator--()
+ {
+ normalize_backward();
+ --mPosition;
+ return *this;
+ }
+
+ self_type operator--( int )
+ {
+ self_type result(*this);
+ normalize_backward();
+ --mPosition;
+ return result;
+ }
+
+ difference_type size_forward() const
+ {
+ return mFragment.mFragmentEnd - mPosition;
+ }
+
+ difference_type size_backward() const
+ {
+ return mPosition - mFragment.mFragmentStart;
+ }
+
+ self_type& advance( difference_type n )
+ {
+ while ( n > 0 )
+ {
+ difference_type one_hop = std::min(n, size_forward());
+
+ NS_ASSERTION(one_hop>0, "Infinite loop: can't advance a reading iterator beyond the end of a string");
+ // perhaps I should |break| if |!one_hop|?
+
+ mPosition += one_hop;
+ normalize_forward();
+ n -= one_hop;
+ }
+
+ while ( n < 0 )
+ {
+ normalize_backward();
+ difference_type one_hop = std::max(n, -size_backward());
+
+ NS_ASSERTION(one_hop<0, "Infinite loop: can't advance (backward) a reading iterator beyond the end of a string");
+ // perhaps I should |break| if |!one_hop|?
+
+ mPosition += one_hop;
+ n -= one_hop;
+ }
+
+ return *this;
+ }
+ };
+
+
+inline
+bool
+SameFragment( const nsScannerIterator& a, const nsScannerIterator& b )
+ {
+ return a.fragment().mFragmentStart == b.fragment().mFragmentStart;
+ }
+
+
+ /**
+ * this class is needed in order to make use of the methods in nsAlgorithm.h
+ */
+template <>
+struct nsCharSourceTraits<nsScannerIterator>
+ {
+ typedef nsScannerIterator::difference_type difference_type;
+
+ static
+ uint32_t
+ readable_distance( const nsScannerIterator& first, const nsScannerIterator& last )
+ {
+ return uint32_t(SameFragment(first, last) ? last.get() - first.get() : first.size_forward());
+ }
+
+ static
+ const nsScannerIterator::value_type*
+ read( const nsScannerIterator& iter )
+ {
+ return iter.get();
+ }
+
+ static
+ void
+ advance( nsScannerIterator& s, difference_type n )
+ {
+ s.advance(n);
+ }
+ };
+
+
+ /**
+ * inline methods follow
+ */
+
+inline
+void
+nsScannerIterator::normalize_forward()
+ {
+ while (mPosition == mFragment.mFragmentEnd && mOwner->GetNextFragment(mFragment))
+ mPosition = mFragment.mFragmentStart;
+ }
+
+inline
+void
+nsScannerIterator::normalize_backward()
+ {
+ while (mPosition == mFragment.mFragmentStart && mOwner->GetPrevFragment(mFragment))
+ mPosition = mFragment.mFragmentEnd;
+ }
+
+inline
+bool
+operator==( const nsScannerIterator& lhs, const nsScannerIterator& rhs )
+ {
+ return lhs.get() == rhs.get();
+ }
+
+inline
+bool
+operator!=( const nsScannerIterator& lhs, const nsScannerIterator& rhs )
+ {
+ return lhs.get() != rhs.get();
+ }
+
+
+inline
+nsScannerBufferList::Position::Position(const nsScannerIterator& aIter)
+ : mBuffer(const_cast<Buffer*>(aIter.buffer()))
+ , mPosition(const_cast<char16_t*>(aIter.get()))
+ {}
+
+inline
+nsScannerBufferList::Position&
+nsScannerBufferList::Position::operator=(const nsScannerIterator& aIter)
+ {
+ mBuffer = const_cast<Buffer*>(aIter.buffer());
+ mPosition = const_cast<char16_t*>(aIter.get());
+ return *this;
+ }
+
+
+ /**
+ * scanner string utils
+ *
+ * These methods mimic the API provided by nsReadableUtils in xpcom/string.
+ * Here we provide only the methods that the htmlparser module needs.
+ */
+
+inline
+size_t
+Distance( const nsScannerIterator& aStart, const nsScannerIterator& aEnd )
+ {
+ typedef nsScannerBufferList::Position Position;
+ return Position::Distance(Position(aStart), Position(aEnd));
+ }
+
+bool
+CopyUnicodeTo( const nsScannerIterator& aSrcStart,
+ const nsScannerIterator& aSrcEnd,
+ nsAString& aDest );
+
+inline
+bool
+CopyUnicodeTo( const nsScannerSubstring& aSrc, nsAString& aDest )
+ {
+ nsScannerIterator begin, end;
+ return CopyUnicodeTo(aSrc.BeginReading(begin), aSrc.EndReading(end), aDest);
+ }
+
+bool
+AppendUnicodeTo( const nsScannerIterator& aSrcStart,
+ const nsScannerIterator& aSrcEnd,
+ nsAString& aDest );
+
+inline
+bool
+AppendUnicodeTo( const nsScannerSubstring& aSrc, nsAString& aDest )
+ {
+ nsScannerIterator begin, end;
+ return AppendUnicodeTo(aSrc.BeginReading(begin), aSrc.EndReading(end), aDest);
+ }
+
+bool
+AppendUnicodeTo( const nsScannerIterator& aSrcStart,
+ const nsScannerIterator& aSrcEnd,
+ nsScannerSharedSubstring& aDest );
+
+bool
+FindCharInReadable( char16_t aChar,
+ nsScannerIterator& aStart,
+ const nsScannerIterator& aEnd );
+
+bool
+FindInReadable( const nsAString& aPattern,
+ nsScannerIterator& aStart,
+ nsScannerIterator& aEnd,
+ const nsStringComparator& = nsDefaultStringComparator() );
+
+bool
+RFindInReadable( const nsAString& aPattern,
+ nsScannerIterator& aStart,
+ nsScannerIterator& aEnd,
+ const nsStringComparator& = nsDefaultStringComparator() );
+
+inline
+bool
+CaseInsensitiveFindInReadable( const nsAString& aPattern,
+ nsScannerIterator& aStart,
+ nsScannerIterator& aEnd )
+ {
+ return FindInReadable(aPattern, aStart, aEnd,
+ nsCaseInsensitiveStringComparator());
+ }
+
+#endif // !defined(nsScannerString_h___)
diff --git a/components/htmlparser/src/nsToken.h b/components/htmlparser/src/nsToken.h
new file mode 100644
index 000000000..6221aca57
--- /dev/null
+++ b/components/htmlparser/src/nsToken.h
@@ -0,0 +1,19 @@
+/* -*- 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 CTOKEN__
+#define CTOKEN__
+
+enum eHTMLTokenTypes {
+ eToken_unknown=0,
+ eToken_start=1, eToken_end, eToken_comment, eToken_entity,
+ eToken_whitespace, eToken_newline, eToken_text, eToken_attribute,
+ eToken_instruction, eToken_cdatasection, eToken_doctypeDecl, eToken_markupDecl,
+ eToken_last //make sure this stays the last token...
+};
+
+#endif
+
+
diff --git a/components/jar/appnote.txt b/components/jar/appnote.txt
new file mode 100644
index 000000000..7b96643ca
--- /dev/null
+++ b/components/jar/appnote.txt
@@ -0,0 +1,1192 @@
+Revised: 03/01/1999
+
+Disclaimer
+----------
+
+Although PKWARE will attempt to supply current and accurate
+information relating to its file formats, algorithms, and the
+subject programs, the possibility of error can not be eliminated.
+PKWARE therefore expressly disclaims any warranty that the
+information contained in the associated materials relating to the
+subject programs and/or the format of the files created or
+accessed by the subject programs and/or the algorithms used by
+the subject programs, or any other matter, is current, correct or
+accurate as delivered. Any risk of damage due to any possible
+inaccurate information is assumed by the user of the information.
+Furthermore, the information relating to the subject programs
+and/or the file formats created or accessed by the subject
+programs and/or the algorithms used by the subject programs is
+subject to change without notice.
+
+General Format of a ZIP file
+----------------------------
+
+ Files stored in arbitrary order. Large zipfiles can span multiple
+ diskette media.
+
+ Overall zipfile format:
+
+ [local file header + file data + data_descriptor] . . .
+ [central directory] end of central directory record
+
+
+ A. Local file header:
+
+ local file header signature 4 bytes (0x04034b50)
+ version needed to extract 2 bytes
+ general purpose bit flag 2 bytes
+ compression method 2 bytes
+ last mod file time 2 bytes
+ last mod file date 2 bytes
+ crc-32 4 bytes
+ compressed size 4 bytes
+ uncompressed size 4 bytes
+ filename length 2 bytes
+ extra field length 2 bytes
+
+ filename (variable size)
+ extra field (variable size)
+
+ B. Data descriptor:
+
+ crc-32 4 bytes
+ compressed size 4 bytes
+ uncompressed size 4 bytes
+
+ This descriptor exists only if bit 3 of the general
+ purpose bit flag is set (see below). It is byte aligned
+ and immediately follows the last byte of compressed data.
+ This descriptor is used only when it was not possible to
+ seek in the output zip file, e.g., when the output zip file
+ was standard output or a non seekable device.
+
+ C. Central directory structure:
+
+ [file header] . . . end of central dir record
+
+ File header:
+
+ central file header signature 4 bytes (0x02014b50)
+ version made by 2 bytes
+ version needed to extract 2 bytes
+ general purpose bit flag 2 bytes
+ compression method 2 bytes
+ last mod file time 2 bytes
+ last mod file date 2 bytes
+ crc-32 4 bytes
+ compressed size 4 bytes
+ uncompressed size 4 bytes
+ filename length 2 bytes
+ extra field length 2 bytes
+ file comment length 2 bytes
+ disk number start 2 bytes
+ internal file attributes 2 bytes
+ external file attributes 4 bytes
+ relative offset of local header 4 bytes
+
+ filename (variable size)
+ extra field (variable size)
+ file comment (variable size)
+
+ End of central dir record:
+
+ end of central dir signature 4 bytes (0x06054b50)
+ number of this disk 2 bytes
+ number of the disk with the
+ start of the central directory 2 bytes
+ total number of entries in
+ the central dir on this disk 2 bytes
+ total number of entries in
+ the central dir 2 bytes
+ size of the central directory 4 bytes
+ offset of start of central
+ directory with respect to
+ the starting disk number 4 bytes
+ zipfile comment length 2 bytes
+ zipfile comment (variable size)
+
+ D. Explanation of fields:
+
+ version made by (2 bytes)
+
+ The upper byte indicates the compatibility of the file
+ attribute information. If the external file attributes
+ are compatible with MS-DOS and can be read by PKZIP for
+ DOS version 2.04g then this value will be zero. If these
+ attributes are not compatible, then this value will
+ identify the host system on which the attributes are
+ compatible. Software can use this information to determine
+ the line record format for text files etc. The current
+ mappings are:
+
+ 0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)
+ 1 - Amiga 2 - VAX/VMS
+ 3 - Unix 4 - VM/CMS
+ 5 - Atari ST 6 - OS/2 H.P.F.S.
+ 7 - Macintosh 8 - Z-System
+ 9 - CP/M 10 - Windows NTFS
+ 11 thru 255 - unused
+
+ The lower byte indicates the version number of the
+ software used to encode the file. The value/10
+ indicates the major version number, and the value
+ mod 10 is the minor version number.
+
+ version needed to extract (2 bytes)
+
+ The minimum software version needed to extract the
+ file, mapped as above.
+
+ general purpose bit flag: (2 bytes)
+
+ Bit 0: If set, indicates that the file is encrypted.
+
+ (For Method 6 - Imploding)
+ Bit 1: If the compression method used was type 6,
+ Imploding, then this bit, if set, indicates
+ an 8K sliding dictionary was used. If clear,
+ then a 4K sliding dictionary was used.
+ Bit 2: If the compression method used was type 6,
+ Imploding, then this bit, if set, indicates
+ 3 Shannon-Fano trees were used to encode the
+ sliding dictionary output. If clear, then 2
+ Shannon-Fano trees were used.
+
+ (For Method 8 - Deflating)
+ Bit 2 Bit 1
+ 0 0 Normal (-en) compression option was used.
+ 0 1 Maximum (-ex) compression option was used.
+ 1 0 Fast (-ef) compression option was used.
+ 1 1 Super Fast (-es) compression option was used.
+
+ Note: Bits 1 and 2 are undefined if the compression
+ method is any other.
+
+ Bit 3: If this bit is set, the fields crc-32, compressed
+ size and uncompressed size are set to zero in the
+ local header. The correct values are put in the
+ data descriptor immediately following the compressed
+ data. (Note: PKZIP version 2.04g for DOS only
+ recognizes this bit for method 8 compression, newer
+ versions of PKZIP recognize this bit for any
+ compression method.)
+
+ Bit 4: Reserved for use with method 8, for enhanced
+ deflating.
+
+ Bit 5: If this bit is set, this indicates that the file is
+ compressed patched data. (Note: Requires PKZIP
+ version 2.70 or greater)
+
+ Bit 6: Currently unused.
+
+ Bit 7: Currently unused.
+
+ Bit 8: Currently unused.
+
+ Bit 9: Currently unused.
+
+ Bit 10: Currently unused.
+
+ Bit 11: Currently unused.
+
+ Bit 12: Reserved by PKWARE for enhanced compression.
+
+ Bit 13: Reserved by PKWARE.
+
+ Bit 14: Reserved by PKWARE.
+
+ Bit 15: Reserved by PKWARE.
+
+ compression method: (2 bytes)
+
+ (see accompanying documentation for algorithm
+ descriptions)
+
+ 0 - The file is stored (no compression)
+ 1 - The file is Shrunk
+ 2 - The file is Reduced with compression factor 1
+ 3 - The file is Reduced with compression factor 2
+ 4 - The file is Reduced with compression factor 3
+ 5 - The file is Reduced with compression factor 4
+ 6 - The file is Imploded
+ 7 - Reserved for Tokenizing compression algorithm
+ 8 - The file is Deflated
+ 9 - Reserved for enhanced Deflating
+ 10 - PKWARE Date Compression Library Imploding
+
+ date and time fields: (2 bytes each)
+
+ The date and time are encoded in standard MS-DOS format.
+ If input came from standard input, the date and time are
+ those at which compression was started for this data.
+
+ CRC-32: (4 bytes)
+
+ The CRC-32 algorithm was generously contributed by
+ David Schwaderer and can be found in his excellent
+ book "C Programmers Guide to NetBIOS" published by
+ Howard W. Sams & Co. Inc. The 'magic number' for
+ the CRC is 0xdebb20e3. The proper CRC pre and post
+ conditioning is used, meaning that the CRC register
+ is pre-conditioned with all ones (a starting value
+ of 0xffffffff) and the value is post-conditioned by
+ taking the one's complement of the CRC residual.
+ If bit 3 of the general purpose flag is set, this
+ field is set to zero in the local header and the correct
+ value is put in the data descriptor and in the central
+ directory.
+
+ compressed size: (4 bytes)
+ uncompressed size: (4 bytes)
+
+ The size of the file compressed and uncompressed,
+ respectively. If bit 3 of the general purpose bit flag
+ is set, these fields are set to zero in the local header
+ and the correct values are put in the data descriptor and
+ in the central directory.
+
+ filename length: (2 bytes)
+ extra field length: (2 bytes)
+ file comment length: (2 bytes)
+
+ The length of the filename, extra field, and comment
+ fields respectively. The combined length of any
+ directory record and these three fields should not
+ generally exceed 65,535 bytes. If input came from standard
+ input, the filename length is set to zero.
+
+ disk number start: (2 bytes)
+
+ The number of the disk on which this file begins.
+
+ internal file attributes: (2 bytes)
+
+ The lowest bit of this field indicates, if set, that
+ the file is apparently an ASCII or text file. If not
+ set, that the file apparently contains binary data.
+ The remaining bits are unused in version 1.0.
+
+ Bits 1 and 2 are reserved for use by PKWARE.
+
+ external file attributes: (4 bytes)
+
+ The mapping of the external attributes is
+ host-system dependent (see 'version made by'). For
+ MS-DOS, the low order byte is the MS-DOS directory
+ attribute byte. If input came from standard input, this
+ field is set to zero.
+
+ relative offset of local header: (4 bytes)
+
+ This is the offset from the start of the first disk on
+ which this file appears, to where the local header should
+ be found.
+
+ filename: (Variable)
+
+ The name of the file, with optional relative path.
+ The path stored should not contain a drive or
+ device letter, or a leading slash. All slashes
+ should be forward slashes '/' as opposed to
+ backwards slashes '\' for compatibility with Amiga
+ and Unix file systems etc. If input came from standard
+ input, there is no filename field.
+
+ extra field: (Variable)
+
+ This is for future expansion. If additional information
+ needs to be stored in the future, it should be stored
+ here. Earlier versions of the software can then safely
+ skip this file, and find the next file or header. This
+ field will be 0 length in version 1.0.
+
+ In order to allow different programs and different types
+ of information to be stored in the 'extra' field in .ZIP
+ files, the following structure should be used for all
+ programs storing data in this field:
+
+ header1+data1 + header2+data2 . . .
+
+ Each header should consist of:
+
+ Header ID - 2 bytes
+ Data Size - 2 bytes
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ The Header ID field indicates the type of data that is in
+ the following data block.
+
+ Header ID's of 0 thru 31 are reserved for use by PKWARE.
+ The remaining ID's can be used by third party vendors for
+ proprietary usage.
+
+ The current Header ID mappings defined by PKWARE are:
+
+ 0x0007 AV Info
+ 0x0009 OS/2
+ 0x000a NTFS
+ 0x000c VAX/VMS
+ 0x000d Unix
+ 0x000f Patch Descriptor
+
+ Several third party mappings commonly used are:
+
+ 0x4b46 FWKCS MD5 (see below)
+ 0x07c8 Macintosh
+ 0x4341 Acorn/SparkFS
+ 0x4453 Windows NT security descriptor (binary ACL)
+ 0x4704 VM/CMS
+ 0x470f MVS
+ 0x4c41 OS/2 access control list (text ACL)
+ 0x4d49 Info-ZIP VMS (VAX or Alpha)
+ 0x5455 extended timestamp
+ 0x5855 Info-ZIP Unix (original, also OS/2, NT, etc)
+ 0x6542 BeOS/BeBox
+ 0x756e ASi Unix
+ 0x7855 Info-ZIP Unix (new)
+ 0xfd4a SMS/QDOS
+
+ The Data Size field indicates the size of the following
+ data block. Programs can use this value to skip to the
+ next header block, passing over any data blocks that are
+ not of interest.
+
+ Note: As stated above, the size of the entire .ZIP file
+ header, including the filename, comment, and extra
+ field should not exceed 64K in size.
+
+ In case two different programs should appropriate the same
+ Header ID value, it is strongly recommended that each
+ program place a unique signature of at least two bytes in
+ size (and preferably 4 bytes or bigger) at the start of
+ each data area. Every program should verify that its
+ unique signature is present, in addition to the Header ID
+ value being correct, before assuming that it is a block of
+ known type.
+
+ -OS/2 Extra Field:
+
+ The following is the layout of the OS/2 attributes "extra"
+ block. (Last Revision 09/05/95)
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (OS/2) 0x0009 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size for the following data block
+ BSize 4 bytes Uncompressed Block Size
+ CType 2 bytes Compression type
+ EACRC 4 bytes CRC value for uncompress block
+ (var) variable Compressed block
+
+ The OS/2 extended attribute structure (FEA2LIST) is
+ compressed and then stored in it's entirety within this
+ structure. There will only ever be one "block" of data in
+ VarFields[].
+
+ -UNIX Extra Field:
+
+ The following is the layout of the Unix "extra" block.
+ Note: all fields are stored in Intel low-byte/high-byte
+ order.
+
+ Value Size Description
+ ----- ---- -----------
+ (UNIX) 0x000d 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size for the following data block
+ Atime 4 bytes File last access time
+ Mtime 4 bytes File last modification time
+ Uid 2 bytes File user ID
+ Gid 2 bytes File group ID
+ (var) variable Variable length data field
+
+ The variable length data field will contain file type
+ specific data. Currently the only values allowed are
+ the original "linked to" file names for hard or symbolic
+ links.
+
+ -VAX/VMS Extra Field:
+
+ The following is the layout of the VAX/VMS attributes
+ "extra" block.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (VMS) 0x000c 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the total "extra" block
+ CRC 4 bytes 32-bit CRC for remainder of the block
+ Tag1 2 bytes VMS attribute tag value #1
+ Size1 2 bytes Size of attribute #1, in bytes
+ (var.) Size1 Attribute #1 data
+ .
+ .
+ .
+ TagN 2 bytes VMS attribute tage value #N
+ SizeN 2 bytes Size of attribute #N, in bytes
+ (var.) SizeN Attribute #N data
+
+ Rules:
+
+ 1. There will be one or more of attributes present, which
+ will each be preceded by the above TagX & SizeX values.
+ These values are identical to the ATR$C_XXXX and
+ ATR$S_XXXX constants which are defined in ATR.H under
+ VMS C. Neither of these values will ever be zero.
+
+ 2. No word alignment or padding is performed.
+
+ 3. A well-behaved PKZIP/VMS program should never produce
+ more than one sub-block with the same TagX value. Also,
+ there will never be more than one "extra" block of type
+ 0x000c in a particular directory record.
+
+ -NTFS Extra Field:
+
+ The following is the layout of the NTFS attributes
+ "extra" block.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (NTFS) 0x000a 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the total "extra" block
+ Reserved 4 bytes Reserved for future use
+ Tag1 2 bytes NTFS attribute tag value #1
+ Size1 2 bytes Size of attribute #1, in bytes
+ (var.) Size1 Attribute #1 data
+ .
+ .
+ .
+ TagN 2 bytes NTFS attribute tage value #N
+ SizeN 2 bytes Size of attribute #N, in bytes
+ (var.) SizeN Attribute #N data
+
+ For NTFS, values for Tag1 through TagN are as follows:
+ (currently only one set of attributes is defined for NTFS)
+
+ Tag Size Description
+ ----- ---- -----------
+ 0x0001 2 bytes Tag for attribute #1
+ Size1 2 bytes Size of attribute #1, in bytes
+ Mtime 8 bytes File last modification time
+ Atime 8 bytes File last access time
+ Ctime 8 bytes File creation time
+
+ -PATCH Descriptor Extra Field:
+
+ The following is the layout of the Patch Descriptor "extra"
+ block.
+
+ Note: all fields stored in Intel low-byte/high-byte order.
+
+ Value Size Description
+ ----- ---- -----------
+ (Patch) 0x000f 2 bytes Tag for this "extra" block type
+ TSize 2 bytes Size of the total "extra" block
+ Version 2 bytes Version of the descriptor
+ Flags 4 bytes Actions and reactions (see below)
+ OldSize 4 bytes Size of the file about to be patched
+ OldCRC 4 bytes 32-bit CRC of the file to be patched
+ NewSize 4 bytes Size of the resulting file
+ NewCRC 4 bytes 32-bit CRC of the resulting file
+
+ Actions and reactions
+
+ Bits Description
+ ---- ----------------
+ 0 Use for autodetection
+ 1 Treat as selfpatch
+ 2-3 RESERVED
+ 4-5 Action (see below)
+ 6-7 RESERVED
+ 8-9 Reaction (see below) to absent file
+ 10-11 Reaction (see below) to newer file
+ 12-13 Reaction (see below) to unknown file
+ 14-15 RESERVED
+ 16-31 RESERVED
+
+ Actions
+
+ Action Value
+ ------ -----
+ none 0
+ add 1
+ delete 2
+ patch 3
+
+ Reactions
+
+ Reaction Value
+ -------- -----
+ ask 0
+ skip 1
+ ignore 2
+ fail 3
+
+ - FWKCS MD5 Extra Field:
+
+ The FWKCS Contents_Signature System, used in
+ automatically identifying files independent of filename,
+ optionally adds and uses an extra field to support the
+ rapid creation of an enhanced contents_signature:
+
+ Header ID = 0x4b46
+ Data Size = 0x0013
+ Preface = 'M','D','5'
+ followed by 16 bytes containing the uncompressed file's
+ 128_bit MD5 hash(1), low byte first.
+
+ When FWKCS revises a zipfile central directory to add
+ this extra field for a file, it also replaces the
+ central directory entry for that file's uncompressed
+ filelength with a measured value.
+
+ FWKCS provides an option to strip this extra field, if
+ present, from a zipfile central directory. In adding
+ this extra field, FWKCS preserves Zipfile Authenticity
+ Verification; if stripping this extra field, FWKCS
+ preserves all versions of AV through PKZIP version 2.04g.
+
+ FWKCS, and FWKCS Contents_Signature System, are
+ trademarks of Frederick W. Kantor.
+
+ (1) R. Rivest, RFC1321.TXT, MIT Laboratory for Computer
+ Science and RSA Data Security, Inc., April 1992.
+ ll.76-77: "The MD5 algorithm is being placed in the
+ public domain for review and possible adoption as a
+ standard."
+
+ file comment: (Variable)
+
+ The comment for this file.
+
+ number of this disk: (2 bytes)
+
+ The number of this disk, which contains central
+ directory end record.
+
+ number of the disk with the start of the central
+ directory: (2 bytes)
+
+ The number of the disk on which the central
+ directory starts.
+
+ total number of entries in the central dir on
+ this disk: (2 bytes)
+
+ The number of central directory entries on this disk.
+
+ total number of entries in the central dir: (2 bytes)
+
+ The total number of files in the zipfile.
+
+ size of the central directory: (4 bytes)
+
+ The size (in bytes) of the entire central directory.
+
+ offset of start of central directory with respect to
+ the starting disk number: (4 bytes)
+
+ Offset of the start of the central directory on the
+ disk on which the central directory starts.
+
+ zipfile comment length: (2 bytes)
+
+ The length of the comment for this zipfile.
+
+ zipfile comment: (Variable)
+
+ The comment for this zipfile.
+
+ D. General notes:
+
+ 1) All fields unless otherwise noted are unsigned and stored
+ in Intel low-byte:high-byte, low-word:high-word order.
+
+ 2) String fields are not null terminated, since the
+ length is given explicitly.
+
+ 3) Local headers should not span disk boundaries. Also, even
+ though the central directory can span disk boundaries, no
+ single record in the central directory should be split
+ across disks.
+
+ 4) The entries in the central directory may not necessarily
+ be in the same order that files appear in the zipfile.
+
+UnShrinking - Method 1
+----------------------
+
+Shrinking is a Dynamic Ziv-Lempel-Welch compression algorithm
+with partial clearing. The initial code size is 9 bits, and
+the maximum code size is 13 bits. Shrinking differs from
+conventional Dynamic Ziv-Lempel-Welch implementations in several
+respects:
+
+1) The code size is controlled by the compressor, and is not
+ automatically increased when codes larger than the current
+ code size are created (but not necessarily used). When
+ the decompressor encounters the code sequence 256
+ (decimal) followed by 1, it should increase the code size
+ read from the input stream to the next bit size. No
+ blocking of the codes is performed, so the next code at
+ the increased size should be read from the input stream
+ immediately after where the previous code at the smaller
+ bit size was read. Again, the decompressor should not
+ increase the code size used until the sequence 256,1 is
+ encountered.
+
+2) When the table becomes full, total clearing is not
+ performed. Rather, when the compressor emits the code
+ sequence 256,2 (decimal), the decompressor should clear
+ all leaf nodes from the Ziv-Lempel tree, and continue to
+ use the current code size. The nodes that are cleared
+ from the Ziv-Lempel tree are then re-used, with the lowest
+ code value re-used first, and the highest code value
+ re-used last. The compressor can emit the sequence 256,2
+ at any time.
+
+Expanding - Methods 2-5
+-----------------------
+
+The Reducing algorithm is actually a combination of two
+distinct algorithms. The first algorithm compresses repeated
+byte sequences, and the second algorithm takes the compressed
+stream from the first algorithm and applies a probabilistic
+compression method.
+
+The probabilistic compression stores an array of 'follower
+sets' S(j), for j=0 to 255, corresponding to each possible
+ASCII character. Each set contains between 0 and 32
+characters, to be denoted as S(j)[0],...,S(j)[m], where m<32.
+The sets are stored at the beginning of the data area for a
+Reduced file, in reverse order, with S(255) first, and S(0)
+last.
+
+The sets are encoded as { N(j), S(j)[0],...,S(j)[N(j)-1] },
+where N(j) is the size of set S(j). N(j) can be 0, in which
+case the follower set for S(j) is empty. Each N(j) value is
+encoded in 6 bits, followed by N(j) eight bit character values
+corresponding to S(j)[0] to S(j)[N(j)-1] respectively. If
+N(j) is 0, then no values for S(j) are stored, and the value
+for N(j-1) immediately follows.
+
+Immediately after the follower sets, is the compressed data
+stream. The compressed data stream can be interpreted for the
+probabilistic decompression as follows:
+
+let Last-Character <- 0.
+loop until done
+ if the follower set S(Last-Character) is empty then
+ read 8 bits from the input stream, and copy this
+ value to the output stream.
+ otherwise if the follower set S(Last-Character) is non-empty then
+ read 1 bit from the input stream.
+ if this bit is not zero then
+ read 8 bits from the input stream, and copy this
+ value to the output stream.
+ otherwise if this bit is zero then
+ read B(N(Last-Character)) bits from the input
+ stream, and assign this value to I.
+ Copy the value of S(Last-Character)[I] to the
+ output stream.
+
+ assign the last value placed on the output stream to
+ Last-Character.
+end loop
+
+B(N(j)) is defined as the minimal number of bits required to
+encode the value N(j)-1.
+
+The decompressed stream from above can then be expanded to
+re-create the original file as follows:
+
+let State <- 0.
+
+loop until done
+ read 8 bits from the input stream into C.
+ case State of
+ 0: if C is not equal to DLE (144 decimal) then
+ copy C to the output stream.
+ otherwise if C is equal to DLE then
+ let State <- 1.
+
+ 1: if C is non-zero then
+ let V <- C.
+ let Len <- L(V)
+ let State <- F(Len).
+ otherwise if C is zero then
+ copy the value 144 (decimal) to the output stream.
+ let State <- 0
+
+ 2: let Len <- Len + C
+ let State <- 3.
+
+ 3: move backwards D(V,C) bytes in the output stream
+ (if this position is before the start of the output
+ stream, then assume that all the data before the
+ start of the output stream is filled with zeros).
+ copy Len+3 bytes from this position to the output stream.
+ let State <- 0.
+ end case
+end loop
+
+The functions F,L, and D are dependent on the 'compression
+factor', 1 through 4, and are defined as follows:
+
+For compression factor 1:
+ L(X) equals the lower 7 bits of X.
+ F(X) equals 2 if X equals 127 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 1 bit of X) * 256 + Y + 1.
+For compression factor 2:
+ L(X) equals the lower 6 bits of X.
+ F(X) equals 2 if X equals 63 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 2 bits of X) * 256 + Y + 1.
+For compression factor 3:
+ L(X) equals the lower 5 bits of X.
+ F(X) equals 2 if X equals 31 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 3 bits of X) * 256 + Y + 1.
+For compression factor 4:
+ L(X) equals the lower 4 bits of X.
+ F(X) equals 2 if X equals 15 otherwise F(X) equals 3.
+ D(X,Y) equals the (upper 4 bits of X) * 256 + Y + 1.
+
+Imploding - Method 6
+--------------------
+
+The Imploding algorithm is actually a combination of two distinct
+algorithms. The first algorithm compresses repeated byte
+sequences using a sliding dictionary. The second algorithm is
+used to compress the encoding of the sliding dictionary output,
+using multiple Shannon-Fano trees.
+
+The Imploding algorithm can use a 4K or 8K sliding dictionary
+size. The dictionary size used can be determined by bit 1 in the
+general purpose flag word; a 0 bit indicates a 4K dictionary
+while a 1 bit indicates an 8K dictionary.
+
+The Shannon-Fano trees are stored at the start of the compressed
+file. The number of trees stored is defined by bit 2 in the
+general purpose flag word; a 0 bit indicates two trees stored, a
+1 bit indicates three trees are stored. If 3 trees are stored,
+the first Shannon-Fano tree represents the encoding of the
+Literal characters, the second tree represents the encoding of
+the Length information, the third represents the encoding of the
+Distance information. When 2 Shannon-Fano trees are stored, the
+Length tree is stored first, followed by the Distance tree.
+
+The Literal Shannon-Fano tree, if present is used to represent
+the entire ASCII character set, and contains 256 values. This
+tree is used to compress any data not compressed by the sliding
+dictionary algorithm. When this tree is present, the Minimum
+Match Length for the sliding dictionary is 3. If this tree is
+not present, the Minimum Match Length is 2.
+
+The Length Shannon-Fano tree is used to compress the Length part
+of the (length,distance) pairs from the sliding dictionary
+output. The Length tree contains 64 values, ranging from the
+Minimum Match Length, to 63 plus the Minimum Match Length.
+
+The Distance Shannon-Fano tree is used to compress the Distance
+part of the (length,distance) pairs from the sliding dictionary
+output. The Distance tree contains 64 values, ranging from 0 to
+63, representing the upper 6 bits of the distance value. The
+distance values themselves will be between 0 and the sliding
+dictionary size, either 4K or 8K.
+
+The Shannon-Fano trees themselves are stored in a compressed
+format. The first byte of the tree data represents the number of
+bytes of data representing the (compressed) Shannon-Fano tree
+minus 1. The remaining bytes represent the Shannon-Fano tree
+data encoded as:
+
+ High 4 bits: Number of values at this bit length + 1. (1 - 16)
+ Low 4 bits: Bit Length needed to represent value + 1. (1 - 16)
+
+The Shannon-Fano codes can be constructed from the bit lengths
+using the following algorithm:
+
+1) Sort the Bit Lengths in ascending order, while retaining the
+ order of the original lengths stored in the file.
+
+2) Generate the Shannon-Fano trees:
+
+ Code <- 0
+ CodeIncrement <- 0
+ LastBitLength <- 0
+ i <- number of Shannon-Fano codes - 1 (either 255 or 63)
+
+ loop while i >= 0
+ Code = Code + CodeIncrement
+ if BitLength(i) <> LastBitLength then
+ LastBitLength=BitLength(i)
+ CodeIncrement = 1 shifted left (16 - LastBitLength)
+ ShannonCode(i) = Code
+ i <- i - 1
+ end loop
+
+3) Reverse the order of all the bits in the above ShannonCode()
+ vector, so that the most significant bit becomes the least
+ significant bit. For example, the value 0x1234 (hex) would
+ become 0x2C48 (hex).
+
+4) Restore the order of Shannon-Fano codes as originally stored
+ within the file.
+
+Example:
+
+ This example will show the encoding of a Shannon-Fano tree
+ of size 8. Notice that the actual Shannon-Fano trees used
+ for Imploding are either 64 or 256 entries in size.
+
+Example: 0x02, 0x42, 0x01, 0x13
+
+ The first byte indicates 3 values in this table. Decoding the
+ bytes:
+ 0x42 = 5 codes of 3 bits long
+ 0x01 = 1 code of 2 bits long
+ 0x13 = 2 codes of 4 bits long
+
+ This would generate the original bit length array of:
+ (3, 3, 3, 3, 3, 2, 4, 4)
+
+ There are 8 codes in this table for the values 0 thru 7. Using
+ the algorithm to obtain the Shannon-Fano codes produces:
+
+ Reversed Order Original
+Val Sorted Constructed Code Value Restored Length
+--- ------ ----------------- -------- -------- ------
+0: 2 1100000000000000 11 101 3
+1: 3 1010000000000000 101 001 3
+2: 3 1000000000000000 001 110 3
+3: 3 0110000000000000 110 010 3
+4: 3 0100000000000000 010 100 3
+5: 3 0010000000000000 100 11 2
+6: 4 0001000000000000 1000 1000 4
+7: 4 0000000000000000 0000 0000 4
+
+The values in the Val, Order Restored and Original Length columns
+now represent the Shannon-Fano encoding tree that can be used for
+decoding the Shannon-Fano encoded data. How to parse the
+variable length Shannon-Fano values from the data stream is beyond
+the scope of this document. (See the references listed at the end of
+this document for more information.) However, traditional decoding
+schemes used for Huffman variable length decoding, such as the
+Greenlaw algorithm, can be successfully applied.
+
+The compressed data stream begins immediately after the
+compressed Shannon-Fano data. The compressed data stream can be
+interpreted as follows:
+
+loop until done
+ read 1 bit from input stream.
+
+ if this bit is non-zero then (encoded data is literal data)
+ if Literal Shannon-Fano tree is present
+ read and decode character using Literal Shannon-Fano tree.
+ otherwise
+ read 8 bits from input stream.
+ copy character to the output stream.
+ otherwise (encoded data is sliding dictionary match)
+ if 8K dictionary size
+ read 7 bits for offset Distance (lower 7 bits of offset).
+ otherwise
+ read 6 bits for offset Distance (lower 6 bits of offset).
+
+ using the Distance Shannon-Fano tree, read and decode the
+ upper 6 bits of the Distance value.
+
+ using the Length Shannon-Fano tree, read and decode
+ the Length value.
+
+ Length <- Length + Minimum Match Length
+
+ if Length = 63 + Minimum Match Length
+ read 8 bits from the input stream,
+ add this value to Length.
+
+ move backwards Distance+1 bytes in the output stream, and
+ copy Length characters from this position to the output
+ stream. (if this position is before the start of the output
+ stream, then assume that all the data before the start of
+ the output stream is filled with zeros).
+end loop
+
+Tokenizing - Method 7
+--------------------
+
+This method is not used by PKZIP.
+
+Deflating - Method 8
+-----------------
+
+The Deflate algorithm is similar to the Implode algorithm using
+a sliding dictionary of up to 32K with secondary compression
+from Huffman/Shannon-Fano codes.
+
+The compressed data is stored in blocks with a header describing
+the block and the Huffman codes used in the data block. The header
+format is as follows:
+
+ Bit 0: Last Block bit This bit is set to 1 if this is the last
+ compressed block in the data.
+ Bits 1-2: Block type
+ 00 (0) - Block is stored - All stored data is byte aligned.
+ Skip bits until next byte, then next word = block
+ length, followed by the ones compliment of the block
+ length word. Remaining data in block is the stored
+ data.
+
+ 01 (1) - Use fixed Huffman codes for literal and distance codes.
+ Lit Code Bits Dist Code Bits
+ --------- ---- --------- ----
+ 0 - 143 8 0 - 31 5
+ 144 - 255 9
+ 256 - 279 7
+ 280 - 287 8
+
+ Literal codes 286-287 and distance codes 30-31 are
+ never used but participate in the huffman construction.
+
+ 10 (2) - Dynamic Huffman codes. (See expanding Huffman codes)
+
+ 11 (3) - Reserved - Flag a "Error in compressed data" if seen.
+
+Expanding Huffman Codes
+-----------------------
+If the data block is stored with dynamic Huffman codes, the Huffman
+codes are sent in the following compressed format:
+
+ 5 Bits: # of Literal codes sent - 256 (256 - 286)
+ All other codes are never sent.
+ 5 Bits: # of Dist codes - 1 (1 - 32)
+ 4 Bits: # of Bit Length codes - 3 (3 - 19)
+
+The Huffman codes are sent as bit lengths and the codes are built as
+described in the implode algorithm. The bit lengths themselves are
+compressed with Huffman codes. There are 19 bit length codes:
+
+ 0 - 15: Represent bit lengths of 0 - 15
+ 16: Copy the previous bit length 3 - 6 times.
+ The next 2 bits indicate repeat length (0 = 3, ... ,3 = 6)
+ Example: Codes 8, 16 (+2 bits 11), 16 (+2 bits 10) will
+ expand to 12 bit lengths of 8 (1 + 6 + 5)
+ 17: Repeat a bit length of 0 for 3 - 10 times. (3 bits of length)
+ 18: Repeat a bit length of 0 for 11 - 138 times (7 bits of length)
+
+The lengths of the bit length codes are sent packed 3 bits per value
+(0 - 7) in the following order:
+
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+
+The Huffman codes should be built as described in the Implode algorithm
+except codes are assigned starting at the shortest bit length, i.e. the
+shortest code should be all 0's rather than all 1's. Also, codes with
+a bit length of zero do not participate in the tree construction. The
+codes are then used to decode the bit lengths for the literal and
+distance tables.
+
+The bit lengths for the literal tables are sent first with the number
+of entries sent described by the 5 bits sent earlier. There are up
+to 286 literal characters; the first 256 represent the respective 8
+bit character, code 256 represents the End-Of-Block code, the remaining
+29 codes represent copy lengths of 3 thru 258. There are up to 30
+distance codes representing distances from 1 thru 32k as described
+below.
+
+ Length Codes
+ ------------
+ Extra Extra Extra Extra
+ Code Bits Length Code Bits Lengths Code Bits Lengths Code Bits Length(s)
+ ---- ---- ------ ---- ---- ------- ---- ---- ------- ---- ---- ---------
+ 257 0 3 265 1 11,12 273 3 35-42 281 5 131-162
+ 258 0 4 266 1 13,14 274 3 43-50 282 5 163-194
+ 259 0 5 267 1 15,16 275 3 51-58 283 5 195-226
+ 260 0 6 268 1 17,18 276 3 59-66 284 5 227-257
+ 261 0 7 269 2 19-22 277 4 67-82 285 0 258
+ 262 0 8 270 2 23-26 278 4 83-98
+ 263 0 9 271 2 27-30 279 4 99-114
+ 264 0 10 272 2 31-34 280 4 115-130
+
+ Distance Codes
+ --------------
+ Extra Extra Extra Extra
+ Code Bits Dist Code Bits Dist Code Bits Distance Code Bits Distance
+ ---- ---- ---- ---- ---- ------ ---- ---- -------- ---- ---- --------
+ 0 0 1 8 3 17-24 16 7 257-384 24 11 4097-6144
+ 1 0 2 9 3 25-32 17 7 385-512 25 11 6145-8192
+ 2 0 3 10 4 33-48 18 8 513-768 26 12 8193-12288
+ 3 0 4 11 4 49-64 19 8 769-1024 27 12 12289-16384
+ 4 1 5,6 12 5 65-96 20 9 1025-1536 28 13 16385-24576
+ 5 1 7,8 13 5 97-128 21 9 1537-2048 29 13 24577-32768
+ 6 2 9-12 14 6 129-192 22 10 2049-3072
+ 7 2 13-16 15 6 193-256 23 10 3073-4096
+
+The compressed data stream begins immediately after the
+compressed header data. The compressed data stream can be
+interpreted as follows:
+
+do
+ read header from input stream.
+
+ if stored block
+ skip bits until byte aligned
+ read count and 1's compliment of count
+ copy count bytes data block
+ otherwise
+ loop until end of block code sent
+ decode literal character from input stream
+ if literal < 256
+ copy character to the output stream
+ otherwise
+ if literal = end of block
+ break from loop
+ otherwise
+ decode distance from input stream
+
+ move backwards distance bytes in the output stream, and
+ copy length characters from this position to the output
+ stream.
+ end loop
+while not last block
+
+if data descriptor exists
+ skip bits until byte aligned
+ read crc and sizes
+endif
+
+Decryption
+----------
+
+The encryption used in PKZIP was generously supplied by Roger
+Schlafly. PKWARE is grateful to Mr. Schlafly for his expert
+help and advice in the field of data encryption.
+
+PKZIP encrypts the compressed data stream. Encrypted files must
+be decrypted before they can be extracted.
+
+Each encrypted file has an extra 12 bytes stored at the start of
+the data area defining the encryption header for that file. The
+encryption header is originally set to random values, and then
+itself encrypted, using three, 32-bit keys. The key values are
+initialized using the supplied encryption password. After each byte
+is encrypted, the keys are then updated using pseudo-random number
+generation techniques in combination with the same CRC-32 algorithm
+used in PKZIP and described elsewhere in this document.
+
+The following is the basic steps required to decrypt a file:
+
+1) Initialize the three 32-bit keys with the password.
+2) Read and decrypt the 12-byte encryption header, further
+ initializing the encryption keys.
+3) Read and decrypt the compressed data stream using the
+ encryption keys.
+
+Step 1 - Initializing the encryption keys
+-----------------------------------------
+
+Key(0) <- 305419896
+Key(1) <- 591751049
+Key(2) <- 878082192
+
+loop for i <- 0 to length(password)-1
+ update_keys(password(i))
+end loop
+
+Where update_keys() is defined as:
+
+update_keys(char):
+ Key(0) <- crc32(key(0),char)
+ Key(1) <- Key(1) + (Key(0) & 000000ffH)
+ Key(1) <- Key(1) * 134775813 + 1
+ Key(2) <- crc32(key(2),key(1) >> 24)
+end update_keys
+
+Where crc32(old_crc,char) is a routine that given a CRC value and a
+character, returns an updated CRC value after applying the CRC-32
+algorithm described elsewhere in this document.
+
+Step 2 - Decrypting the encryption header
+-----------------------------------------
+
+The purpose of this step is to further initialize the encryption
+keys, based on random data, to render a plaintext attack on the
+data ineffective.
+
+Read the 12-byte encryption header into Buffer, in locations
+Buffer(0) thru Buffer(11).
+
+loop for i <- 0 to 11
+ C <- buffer(i) ^ decrypt_byte()
+ update_keys(C)
+ buffer(i) <- C
+end loop
+
+Where decrypt_byte() is defined as:
+
+unsigned char decrypt_byte()
+ local unsigned short temp
+ temp <- Key(2) | 2
+ decrypt_byte <- (temp * (temp ^ 1)) >> 8
+end decrypt_byte
+
+After the header is decrypted, the last 1 or 2 bytes in Buffer
+should be the high-order word/byte of the CRC for the file being
+decrypted, stored in Intel low-byte/high-byte order. Versions of
+PKZIP prior to 2.0 used a 2 byte CRC check; a 1 byte CRC check is
+used on versions after 2.0. This can be used to test if the password
+supplied is correct or not.
+
+Step 3 - Decrypting the compressed data stream
+----------------------------------------------
+
+The compressed data stream can be decrypted as follows:
+
+loop until done
+ read a character into C
+ Temp <- C ^ decrypt_byte()
+ update_keys(temp)
+ output Temp
+end loop
+
+In addition to the above mentioned contributors to PKZIP and PKUNZIP,
+I would like to extend special thanks to Robert Mahoney for suggesting
+the extension .ZIP for this software.
+
+References:
+
+ Fiala, Edward R., and Greene, Daniel H., "Data compression with
+ finite windows", Communications of the ACM, Volume 32, Number 4,
+ April 1989, pages 490-505.
+
+ Held, Gilbert, "Data Compression, Techniques and Applications,
+ Hardware and Software Considerations", John Wiley & Sons, 1987.
+
+ Huffman, D.A., "A method for the construction of minimum-redundancy
+ codes", Proceedings of the IRE, Volume 40, Number 9, September 1952,
+ pages 1098-1101.
+
+ Nelson, Mark, "LZW Data Compression", Dr. Dobbs Journal, Volume 14,
+ Number 10, October 1989, pages 29-37.
+
+ Nelson, Mark, "The Data Compression Book", M&T Books, 1991.
+
+ Storer, James A., "Data Compression, Methods and Theory",
+ Computer Science Press, 1988
+
+ Welch, Terry, "A Technique for High-Performance Data Compression",
+ IEEE Computer, Volume 17, Number 6, June 1984, pages 8-19.
+
+ Ziv, J. and Lempel, A., "A universal algorithm for sequential data
+ compression", Communications of the ACM, Volume 30, Number 6,
+ June 1987, pages 520-540.
+
+ Ziv, J. and Lempel, A., "Compression of individual sequences via
+ variable-rate coding", IEEE Transactions on Information Theory,
+ Volume 24, Number 5, September 1978, pages 530-536.
diff --git a/components/jar/moz.build b/components/jar/moz.build
new file mode 100644
index 000000000..e7aae241a
--- /dev/null
+++ b/components/jar/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+XPIDL_SOURCES += [
+ 'public/nsIJARChannel.idl',
+ 'public/nsIJARProtocolHandler.idl',
+ 'public/nsIJARURI.idl',
+ 'public/nsIZipReader.idl',
+ 'public/nsIZipWriter.idl',
+]
+
+EXPORTS += [
+ 'src/nsJARURI.h',
+ 'src/nsZipArchive.h',
+ 'src/zipstruct.h',
+]
+
+SOURCES += [
+ 'src/nsDeflateConverter.cpp',
+ 'src/nsJAR.cpp',
+ 'src/nsJARChannel.cpp',
+ 'src/nsJARFactory.cpp',
+ 'src/nsJARInputStream.cpp',
+ 'src/nsJARProtocolHandler.cpp',
+ 'src/nsJARURI.cpp',
+ 'src/nsZipArchive.cpp',
+ 'src/nsZipDataStream.cpp',
+ 'src/nsZipHeader.cpp',
+ 'src/nsZipWriter.cpp',
+ 'src/StreamFunctions.cpp',
+ 'src/ZipWriterModule.cpp',
+]
+
+XPIDL_MODULE = 'jar'
+FINAL_LIBRARY = 'xul'
diff --git a/components/jar/public/nsIJARChannel.idl b/components/jar/public/nsIJARChannel.idl
new file mode 100644
index 000000000..4348717d2
--- /dev/null
+++ b/components/jar/public/nsIJARChannel.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; 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 "nsIChannel.idl"
+
+interface nsIFile;
+interface nsIZipEntry;
+
+[scriptable, builtinclass, uuid(e72b179b-d5df-4d87-b5de-fd73a65c60f6)]
+interface nsIJARChannel : nsIChannel
+{
+ /**
+ * Returns TRUE if the JAR file is not safe (if the content type reported
+ * by the server for a remote JAR is not of an expected type). Scripting,
+ * redirects, and plugins should be disabled when loading from this
+ * channel.
+ */
+ [infallible] readonly attribute boolean isUnsafe;
+
+ /**
+ * Returns the JAR file. May be null if the jar is remote.
+ */
+ readonly attribute nsIFile jarFile;
+
+ /**
+ * Returns the zip entry if the file is synchronously accessible.
+ * This will work even without opening the channel.
+ */
+ readonly attribute nsIZipEntry zipEntry;
+};
diff --git a/components/jar/public/nsIJARProtocolHandler.idl b/components/jar/public/nsIJARProtocolHandler.idl
new file mode 100644
index 000000000..a00239a48
--- /dev/null
+++ b/components/jar/public/nsIJARProtocolHandler.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsIProtocolHandler.idl"
+
+interface nsIZipReaderCache;
+
+[scriptable, uuid(92c3b42c-98c4-11d3-8cd9-0060b0fc14a3)]
+interface nsIJARProtocolHandler : nsIProtocolHandler {
+
+ /**
+ * JARCache contains the collection of open jar files.
+ */
+ readonly attribute nsIZipReaderCache JARCache;
+};
diff --git a/components/jar/public/nsIJARURI.idl b/components/jar/public/nsIJARURI.idl
new file mode 100644
index 000000000..68aefa9db
--- /dev/null
+++ b/components/jar/public/nsIJARURI.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: IDL; 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 "nsIURL.idl"
+
+/**
+ * JAR URLs have the following syntax
+ *
+ * jar:<jar-file-uri>!/<jar-entry>
+ *
+ * EXAMPLE: jar:http://www.big.com/blue.jar!/ocean.html
+ *
+ * The nsIURL methods operate on the <jar-entry> part of the spec.
+ */
+[scriptable, uuid(646a508c-f786-4e14-be6d-8dda2a633c60)]
+interface nsIJARURI : nsIURL {
+
+ /**
+ * Returns the root URI (the one for the actual JAR file) for this JAR
+ * (e.g., http://www.big.com/blue.jar).
+ */
+ readonly attribute nsIURI JARFile;
+
+ /**
+ * Returns the entry specified for this JAR URI (e.g., "ocean.html"). This
+ * value may contain %-escaped byte sequences.
+ */
+ attribute AUTF8String JAREntry;
+
+ /**
+ * Create a clone of the JAR URI with a new root URI (the URI for the
+ * actual JAR file).
+ */
+ nsIJARURI cloneWithJARFile(in nsIURI jarFile);
+};
diff --git a/components/jar/public/nsIZipReader.idl b/components/jar/public/nsIZipReader.idl
new file mode 100644
index 000000000..9ca111a3a
--- /dev/null
+++ b/components/jar/public/nsIZipReader.idl
@@ -0,0 +1,273 @@
+/* -*- Mode: IDL; 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 "nsISupports.idl"
+
+%{C++
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescStar(PRFileDesc);
+
+interface nsIUTF8StringEnumerator;
+interface nsIInputStream;
+interface nsIFile;
+interface nsIX509Cert;
+
+[scriptable, uuid(fad6f72f-13d8-4e26-9173-53007a4afe71)]
+interface nsIZipEntry : nsISupports
+{
+ /**
+ * The type of compression used for the item. The possible values and
+ * their meanings are defined in the zip file specification at
+ * http://www.pkware.com/business_and_developers/developer/appnote/
+ */
+ readonly attribute unsigned short compression;
+ /**
+ * The compressed size of the data in the item.
+ */
+ readonly attribute unsigned long size;
+ /**
+ * The uncompressed size of the data in the item.
+ */
+ readonly attribute unsigned long realSize;
+ /**
+ * The CRC-32 hash of the file in the entry.
+ */
+ readonly attribute unsigned long CRC32;
+ /**
+ * True if the name of the entry ends with '/' and false otherwise.
+ */
+ readonly attribute boolean isDirectory;
+ /**
+ * The time at which this item was last modified.
+ */
+ readonly attribute PRTime lastModifiedTime;
+ /**
+ * Use this attribute to determine whether this item is an actual zip entry
+ * or is one synthesized for part of a real entry's path. A synthesized
+ * entry represents a directory within the zip file which has no
+ * corresponding entry within the zip file. For example, the entry for the
+ * directory foo/ in a zip containing exactly one entry for foo/bar.txt
+ * is synthetic. If the zip file contains an actual entry for a directory,
+ * this attribute will be false for the nsIZipEntry for that directory.
+ * It is impossible for a file to be synthetic.
+ */
+ readonly attribute boolean isSynthetic;
+ /**
+ * The UNIX style file permissions of this item.
+ */
+ readonly attribute unsigned long permissions;
+};
+
+[scriptable, uuid(9ba4ef54-e0a0-4f65-9d23-128482448885)]
+interface nsIZipReader : nsISupports
+{
+ /**
+ * Opens a zip file for reading.
+ * It is allowed to open with another file,
+ * but it needs to be closed first with close().
+ */
+ void open(in nsIFile zipFile);
+
+ /**
+ * Opens a zip file inside a zip file for reading.
+ */
+ void openInner(in nsIZipReader zipReader, in AUTF8String zipEntry);
+
+ /**
+ * Opens a zip file stored in memory; the file attribute will be null.
+ *
+ * The ZipReader does not copy or take ownership of this memory; the
+ * caller must ensure that it is valid and unmodified until the
+ * ZipReader is closed or destroyed, and must free the memory as
+ * appropriate afterwards.
+ */
+ void openMemory(in voidPtr aData, in unsigned long aLength);
+
+ /**
+ * The file that represents the zip with which this zip reader was
+ * initialized. This will be null if there is no underlying file.
+ */
+ readonly attribute nsIFile file;
+
+ /**
+ * Closes a zip reader. Subsequent attempts to extract files or read from
+ * its input stream will result in an error.
+ *
+ * Subsequent attempts to access a nsIZipEntry obtained from this zip
+ * reader will cause unspecified behavior.
+ */
+ void close();
+
+ /**
+ * Tests the integrity of the archive by performing a CRC check
+ * on each item expanded into memory. If an entry is specified
+ * the integrity of only that item is tested. If null (javascript)
+ * or EmptyCString() (c++) is passed in the integrity of all items
+ * in the archive are tested.
+ */
+ void test(in AUTF8String aEntryName);
+
+ /**
+ * Extracts a zip entry into a local file specified by outFile.
+ * The entry must be stored in the zip in either uncompressed or
+ * DEFLATE-compressed format for the extraction to be successful.
+ * If the entry is a directory, the directory will be extracted
+ * non-recursively.
+ */
+ void extract(in AUTF8String zipEntry, in nsIFile outFile);
+
+ /**
+ * Returns a nsIZipEntry describing a specified zip entry.
+ */
+ nsIZipEntry getEntry(in AUTF8String zipEntry);
+
+ /**
+ * Checks whether the zipfile contains an entry specified by entryName.
+ */
+ boolean hasEntry(in AUTF8String zipEntry);
+
+ /**
+ * Returns a string enumerator containing the matching entry names.
+ *
+ * @param aPattern
+ * A regular expression used to find matching entries in the zip file.
+ * Set this parameter to null (javascript) or EmptyCString() (c++) or "*"
+ * to get all entries; otherwise, use the
+ * following syntax:
+ *
+ * o * matches anything
+ * o ? matches one character
+ * o $ matches the end of the string
+ * o [abc] matches one occurrence of a, b, or c. The only character that
+ * must be escaped inside the brackets is ]. ^ and - must never
+ * appear in the first and second positions within the brackets,
+ * respectively. (In the former case, the behavior specified for
+ * '[^az]' will happen.)
+ * o [a-z] matches any character between a and z. The characters a and z
+ * must either both be letters or both be numbers, with the
+ * character represented by 'a' having a lower ASCII value than
+ * the character represented by 'z'.
+ * o [^az] matches any character except a or z. If ] is to appear inside
+ * the brackets as a character to not match, it must be escaped.
+ * o pat~pat2 returns matches to the pattern 'pat' which do not also match
+ * the pattern 'pat2'. This may be used to perform filtering
+ * upon the results of one pattern to remove all matches which
+ * also match another pattern. For example, because '*'
+ * matches any string and '*z*' matches any string containing a
+ * 'z', '*~*z*' will match all strings except those containing
+ * a 'z'. Note that a pattern may not use '~' multiple times,
+ * so a string such as '*~*z*~*y*' is not a valid pattern.
+ * o (foo|bar) will match either the pattern foo or the pattern bar.
+ * Neither of the patterns foo or bar may use the 'pat~pat2'
+ * syntax described immediately above.
+ * o \ will escape a special character. Escaping is required for all
+ * special characters unless otherwise specified.
+ * o All other characters match case-sensitively.
+ *
+ * An aPattern not conforming to this syntax has undefined behavior.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE on many but not all invalid aPattern
+ * values.
+ */
+ nsIUTF8StringEnumerator findEntries(in AUTF8String aPattern);
+
+ /**
+ * Returns an input stream containing the contents of the specified zip
+ * entry.
+ * @param zipEntry the name of the entry to open the stream from
+ */
+ nsIInputStream getInputStream(in AUTF8String zipEntry);
+
+ /**
+ * Returns an input stream containing the contents of the specified zip
+ * entry. If the entry refers to a directory (ends with '/'), a directory stream
+ * is opened, otherwise the contents of the file entry is returned.
+ * @param aJarSpec the Spec of the URI for the JAR (only used for directory streams)
+ * @param zipEntry the name of the entry to open the stream from
+ */
+ nsIInputStream getInputStreamWithSpec(in AUTF8String aJarSpec, in AUTF8String zipEntry);
+
+ /**
+ * Returns an object describing the entity which signed
+ * an entry. parseManifest must be called first. If aEntryName is an
+ * entry in the jar, getInputStream must be called after parseManifest.
+ * If aEntryName is an external file which has meta-information
+ * stored in the jar, verifyExternalFile (not yet implemented) must
+ * be called before getPrincipal.
+ */
+ nsIX509Cert getSigningCert(in AUTF8String aEntryName);
+
+ readonly attribute uint32_t manifestEntriesCount;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIZipReaderCache
+
+[scriptable, uuid(31179807-9fcd-46c4-befa-2ade209a394b)]
+interface nsIZipReaderCache : nsISupports
+{
+ /**
+ * Initializes a new zip reader cache.
+ * @param cacheSize - the number of released entries to maintain before
+ * beginning to throw some out (note that the number of outstanding
+ * entries can be much greater than this number -- this is the count
+ * for those otherwise unused entries)
+ */
+ void init(in unsigned long cacheSize);
+
+ /**
+ * Returns a (possibly shared) nsIZipReader for an nsIFile.
+ *
+ * If the zip reader for given file is not in the cache, a new zip reader
+ * is created, initialized, and opened (see nsIZipReader::init and
+ * nsIZipReader::open). Otherwise the previously created zip reader is
+ * returned.
+ *
+ * @note If someone called close() on the shared nsIZipReader, this method
+ * will return the closed zip reader.
+ */
+ nsIZipReader getZip(in nsIFile zipFile);
+
+ /**
+ * returns true if this zipreader already has this file cached
+ */
+ bool isCached(in nsIFile zipFile);
+
+ /**
+ * Returns a (possibly shared) nsIZipReader for a zip inside another zip
+ *
+ * See getZip
+ */
+ nsIZipReader getInnerZip(in nsIFile zipFile, in AUTF8String zipEntry);
+
+ /**
+ * Returns the cached NSPR file descriptor of the file.
+ * Note: currently not supported on Windows platform.
+ */
+ PRFileDescStar getFd(in nsIFile zipFile);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+%{C++
+
+#define NS_ZIPREADER_CID \
+{ /* 88e2fd0b-f7f4-480c-9483-7846b00e8dad */ \
+ 0x88e2fd0b, 0xf7f4, 0x480c, \
+ { 0x94, 0x83, 0x78, 0x46, 0xb0, 0x0e, 0x8d, 0xad } \
+}
+
+#define NS_ZIPREADERCACHE_CID \
+{ /* 608b7f6f-4b60-40d6-87ed-d933bf53d8c1 */ \
+ 0x608b7f6f, 0x4b60, 0x40d6, \
+ { 0x87, 0xed, 0xd9, 0x33, 0xbf, 0x53, 0xd8, 0xc1 } \
+}
+
+%}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/components/jar/public/nsIZipWriter.idl b/components/jar/public/nsIZipWriter.idl
new file mode 100644
index 000000000..bcea5aaf7
--- /dev/null
+++ b/components/jar/public/nsIZipWriter.idl
@@ -0,0 +1,220 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIChannel;
+interface nsIInputStream;
+interface nsIRequestObserver;
+interface nsIFile;
+interface nsIZipEntry;
+
+/**
+ * nsIZipWriter
+ *
+ * An interface for a zip archiver that can be used from script.
+ *
+ * The interface supports both a synchronous method of archiving data and a
+ * queueing system to allow operations to be prepared then run in sequence
+ * with notification after completion.
+ *
+ * Operations added to the queue do not get performed until performQueue is
+ * called at which point they will be performed in the order that they were
+ * added to the queue.
+ *
+ * Operations performed on the queue will throw any errors out to the
+ * observer.
+ *
+ * An attempt to perform a synchronous operation while the background queue
+ * is in progress will throw NS_ERROR_IN_PROGRESS.
+ *
+ * Entry names should use /'s as path separators and should not start with
+ * a /.
+ *
+ * It is not generally necessary to add directory entries in order to add file
+ * entries within them, however it is possible that some zip programs may
+ * experience problems what that.
+ */
+[scriptable, uuid(3ca10750-797e-4a22-bcfe-66170b5e96dd)]
+interface nsIZipWriter : nsISupports
+{
+ /**
+ * Some predefined compression levels
+ */
+ const uint32_t COMPRESSION_NONE = 0;
+ const uint32_t COMPRESSION_FASTEST = 1;
+ const uint32_t COMPRESSION_DEFAULT = 6;
+ const uint32_t COMPRESSION_BEST = 9;
+
+ /**
+ * Gets or sets the comment associated with the open zip file.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ */
+ attribute ACString comment;
+
+ /**
+ * Indicates that operations on the background queue are being performed.
+ */
+ readonly attribute boolean inQueue;
+
+ /**
+ * The file that the zipwriter is writing to.
+ */
+ readonly attribute nsIFile file;
+
+ /**
+ * Opens a zip file.
+ *
+ * @param aFile the zip file to open
+ * @param aIoFlags the open flags for the zip file from prio.h
+ *
+ * @throws NS_ERROR_ALREADY_INITIALIZED if a zip file is already open
+ * @throws NS_ERROR_INVALID_ARG if aFile is null
+ * @throws NS_ERROR_FILE_NOT_FOUND if aFile does not exist and flags did
+ * not allow for creation
+ * @throws NS_ERROR_FILE_CORRUPTED if the file does not contain zip markers
+ * @throws <other-error> on failure to open zip file (most likely corrupt
+ * or unsupported form)
+ */
+ void open(in nsIFile aFile, in int32_t aIoFlags);
+
+ /**
+ * Returns a nsIZipEntry describing a specified zip entry or null if there
+ * is no such entry in the zip file
+ *
+ * @param aZipEntry the path of the entry
+ */
+ nsIZipEntry getEntry(in AUTF8String aZipEntry);
+
+ /**
+ * Checks whether the zipfile contains an entry specified by zipEntry.
+ *
+ * @param aZipEntry the path of the entry
+ */
+ boolean hasEntry(in AUTF8String aZipEntry);
+
+ /**
+ * Adds a new directory entry to the zip file. If aZipEntry does not end with
+ * "/" then it will be added.
+ *
+ * @param aZipEntry the path of the directory entry
+ * @param aModTime the modification time of the entry in microseconds
+ * @param aQueue adds the operation to the background queue. Will be
+ * performed when processQueue is called.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the
+ * file
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ */
+ void addEntryDirectory(in AUTF8String aZipEntry, in PRTime aModTime,
+ in boolean aQueue);
+
+ /**
+ * Adds a new file or directory to the zip file. If the specified file is
+ * a directory then this will be equivalent to a call to
+ * addEntryDirectory(aZipEntry, aFile.lastModifiedTime, aQueue)
+ *
+ * @param aZipEntry the path of the file entry
+ * @param aCompression the compression level, 0 is no compression, 9 is best
+ * @param aFile the file to get the data and modification time from
+ * @param aQueue adds the operation to the background queue. Will be
+ * performed when processQueue is called.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the zip
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ * @throws NS_ERROR_FILE_NOT_FOUND if file does not exist
+ */
+ void addEntryFile(in AUTF8String aZipEntry,
+ in int32_t aCompression, in nsIFile aFile,
+ in boolean aQueue);
+
+ /**
+ * Adds data from a channel to the zip file. If the operation is performed
+ * on the queue then the channel will be opened asynchronously, otherwise
+ * the channel must support being opened synchronously.
+ *
+ * @param aZipEntry the path of the file entry
+ * @param aModTime the modification time of the entry in microseconds
+ * @param aCompression the compression level, 0 is no compression, 9 is best
+ * @param aChannel the channel to get the data from
+ * @param aQueue adds the operation to the background queue. Will be
+ * performed when processQueue is called.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the zip
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ */
+ void addEntryChannel(in AUTF8String aZipEntry, in PRTime aModTime,
+ in int32_t aCompression, in nsIChannel aChannel,
+ in boolean aQueue);
+
+ /**
+ * Adds data from an input stream to the zip file.
+ *
+ * @param aZipEntry the path of the file entry
+ * @param aModTime the modification time of the entry in microseconds
+ * @param aCompression the compression level, 0 is no compression, 9 is best
+ * @param aStream the input stream to get the data from
+ * @param aQueue adds the operation to the background queue. Will be
+ * performed when processQueue is called.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_FILE_ALREADY_EXISTS if the path already exists in the zip
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ */
+ void addEntryStream(in AUTF8String aZipEntry, in PRTime aModTime,
+ in int32_t aCompression, in nsIInputStream aStream,
+ in boolean aQueue);
+
+ /**
+ * Removes an existing entry from the zip file.
+ *
+ * @param aZipEntry the path of the entry to be removed
+ * @param aQueue adds the operation to the background queue. Will be
+ * performed when processQueue is called.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ * @throws NS_ERROR_FILE_NOT_FOUND if no entry with the given path exists
+ * @throws <other-error> on failure to update the zip file
+ */
+ void removeEntry(in AUTF8String aZipEntry, in boolean aQueue);
+
+ /**
+ * Processes all queued items until complete or some error occurs. The
+ * observer will be notified when the first operation starts and when the
+ * last operation completes. Any failures will be passed to the observer.
+ * The zip writer will be busy until the queue is complete or some error
+ * halted processing of the queue early. In the event of an early failure,
+ * remaining items will stay in the queue and calling processQueue will
+ * continue.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_IN_PROGRESS if the queue is already in progress
+ */
+ void processQueue(in nsIRequestObserver aObserver, in nsISupports aContext);
+
+ /**
+ * Closes the zip file.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED if no zip file has been opened
+ * @throws NS_ERROR_IN_PROGRESS if another operation is currently in progress
+ * @throws <other-error> on failure to complete the zip file
+ */
+ void close();
+
+ /**
+ * Make all stored(uncompressed) files align to given alignment size.
+ *
+ * @param aAlignSize is the alignment size, valid values from 2 to 32768, and
+ must be power of 2.
+ *
+ * @throws NS_ERROR_INVALID_ARG if aAlignSize is invalid
+ * @throws <other-error> on failure to update the zip file
+ */
+ void alignStoredFiles(in uint16_t aAlignSize);
+};
diff --git a/components/jar/src/StreamFunctions.cpp b/components/jar/src/StreamFunctions.cpp
new file mode 100644
index 000000000..9a38f2dca
--- /dev/null
+++ b/components/jar/src/StreamFunctions.cpp
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "nscore.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+
+/*
+ * Fully reads the required amount of data. Keeps reading until all the
+ * data is retrieved or an error is hit.
+ */
+nsresult ZW_ReadData(nsIInputStream *aStream, char *aBuffer, uint32_t aCount)
+{
+ while (aCount > 0) {
+ uint32_t read;
+ nsresult rv = aStream->Read(aBuffer, aCount, &read);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aCount -= read;
+ aBuffer += read;
+ // If we hit EOF before reading the data we need then throw.
+ if (read == 0 && aCount > 0)
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Fully writes the required amount of data. Keeps writing until all the
+ * data is written or an error is hit.
+ */
+nsresult ZW_WriteData(nsIOutputStream *aStream, const char *aBuffer,
+ uint32_t aCount)
+{
+ while (aCount > 0) {
+ uint32_t written;
+ nsresult rv = aStream->Write(aBuffer, aCount, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (written <= 0)
+ return NS_ERROR_FAILURE;
+ aCount -= written;
+ aBuffer += written;
+ }
+
+ return NS_OK;
+}
diff --git a/components/jar/src/StreamFunctions.h b/components/jar/src/StreamFunctions.h
new file mode 100644
index 000000000..012fce986
--- /dev/null
+++ b/components/jar/src/StreamFunctions.h
@@ -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/.
+ */
+
+#ifndef _nsStreamFunctions_h_
+#define _nsStreamFunctions_h_
+
+#include "nscore.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+
+/*
+ * ZIP file data is stored little-endian. These are helper functions to read and
+ * write little endian data to/from a char buffer.
+ * The off argument, where present, is incremented according to the number of
+ * bytes consumed from the buffer.
+ */
+inline void WRITE8(uint8_t* buf, uint32_t* off, uint8_t val)
+{
+ buf[(*off)++] = val;
+}
+
+inline void WRITE16(uint8_t* buf, uint32_t* off, uint16_t val)
+{
+ WRITE8(buf, off, val & 0xff);
+ WRITE8(buf, off, (val >> 8) & 0xff);
+}
+
+inline void WRITE32(uint8_t* buf, uint32_t* off, uint32_t val)
+{
+ WRITE16(buf, off, val & 0xffff);
+ WRITE16(buf, off, (val >> 16) & 0xffff);
+}
+
+inline uint8_t READ8(const uint8_t* buf, uint32_t* off)
+{
+ return buf[(*off)++];
+}
+
+inline uint16_t READ16(const uint8_t* buf, uint32_t* off)
+{
+ uint16_t val = READ8(buf, off);
+ val |= READ8(buf, off) << 8;
+ return val;
+}
+
+inline uint32_t READ32(const uint8_t* buf, uint32_t* off)
+{
+ uint32_t val = READ16(buf, off);
+ val |= READ16(buf, off) << 16;
+ return val;
+}
+
+inline uint32_t PEEK32(const uint8_t* buf)
+{
+ return (uint32_t)( (buf [0] ) |
+ (buf [1] << 8) |
+ (buf [2] << 16) |
+ (buf [3] << 24) );
+}
+
+nsresult ZW_ReadData(nsIInputStream *aStream, char *aBuffer, uint32_t aCount);
+
+nsresult ZW_WriteData(nsIOutputStream *aStream, const char *aBuffer,
+ uint32_t aCount);
+
+#endif
diff --git a/components/jar/src/ZipWriterModule.cpp b/components/jar/src/ZipWriterModule.cpp
new file mode 100644
index 000000000..2891da58d
--- /dev/null
+++ b/components/jar/src/ZipWriterModule.cpp
@@ -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/.
+ */
+
+#include "mozilla/ModuleUtils.h"
+#include "nsDeflateConverter.h"
+#include "nsZipWriter.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeflateConverter)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsZipWriter)
+
+NS_DEFINE_NAMED_CID(DEFLATECONVERTER_CID);
+NS_DEFINE_NAMED_CID(ZIPWRITER_CID);
+
+static const mozilla::Module::CIDEntry kZipWriterCIDs[] = {
+ { &kDEFLATECONVERTER_CID, false, nullptr, nsDeflateConverterConstructor },
+ { &kZIPWRITER_CID, false, nullptr, nsZipWriterConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kZipWriterContracts[] = {
+ { "@mozilla.org/streamconv;1?from=uncompressed&to=deflate", &kDEFLATECONVERTER_CID },
+ { "@mozilla.org/streamconv;1?from=uncompressed&to=gzip", &kDEFLATECONVERTER_CID },
+ { "@mozilla.org/streamconv;1?from=uncompressed&to=x-gzip", &kDEFLATECONVERTER_CID },
+ { "@mozilla.org/streamconv;1?from=uncompressed&to=rawdeflate", &kDEFLATECONVERTER_CID },
+ { ZIPWRITER_CONTRACTID, &kZIPWRITER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kZipWriterModule = {
+ mozilla::Module::kVersion,
+ kZipWriterCIDs,
+ kZipWriterContracts
+};
+
+NSMODULE_DEFN(ZipWriterModule) = &kZipWriterModule;
diff --git a/components/jar/src/nsDeflateConverter.cpp b/components/jar/src/nsDeflateConverter.cpp
new file mode 100644
index 000000000..d51cb2ceb
--- /dev/null
+++ b/components/jar/src/nsDeflateConverter.cpp
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "StreamFunctions.h"
+#include "nsDeflateConverter.h"
+#include "nsStringStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "plstr.h"
+#include "mozilla/UniquePtr.h"
+
+#define ZLIB_TYPE "deflate"
+#define GZIP_TYPE "gzip"
+#define X_GZIP_TYPE "x-gzip"
+
+using namespace mozilla;
+
+/**
+ * nsDeflateConverter is a stream converter applies the deflate compression
+ * method to the data.
+ */
+NS_IMPL_ISUPPORTS(nsDeflateConverter, nsIStreamConverter,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+nsresult nsDeflateConverter::Init()
+{
+ int zerr;
+
+ mOffset = 0;
+
+ mZstream.zalloc = Z_NULL;
+ mZstream.zfree = Z_NULL;
+ mZstream.opaque = Z_NULL;
+
+ int32_t window = MAX_WBITS;
+ switch (mWrapMode) {
+ case WRAP_NONE:
+ window = -window;
+ break;
+ case WRAP_GZIP:
+ window += 16;
+ break;
+ default:
+ break;
+ }
+
+ zerr = deflateInit2(&mZstream, mLevel, Z_DEFLATED, window, 8,
+ Z_DEFAULT_STRATEGY);
+ if (zerr != Z_OK) return NS_ERROR_OUT_OF_MEMORY;
+
+ mZstream.next_out = mWriteBuffer;
+ mZstream.avail_out = sizeof(mWriteBuffer);
+
+ // mark the input buffer as empty.
+ mZstream.avail_in = 0;
+ mZstream.next_in = Z_NULL;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDeflateConverter::Convert(nsIInputStream *aFromStream,
+ const char *aFromType,
+ const char *aToType,
+ nsISupports *aCtxt,
+ nsIInputStream **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsDeflateConverter::AsyncConvertData(const char *aFromType,
+ const char *aToType,
+ nsIStreamListener *aListener,
+ nsISupports *aCtxt)
+{
+ if (mListener)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ if (!PL_strncasecmp(aToType, ZLIB_TYPE, sizeof(ZLIB_TYPE)-1))
+ mWrapMode = WRAP_ZLIB;
+ else if (!PL_strcasecmp(aToType, GZIP_TYPE) ||
+ !PL_strcasecmp(aToType, X_GZIP_TYPE))
+ mWrapMode = WRAP_GZIP;
+ else
+ mWrapMode = WRAP_NONE;
+
+ nsresult rv = Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mListener = aListener;
+ mContext = aCtxt;
+ return rv;
+}
+
+NS_IMETHODIMP nsDeflateConverter::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (!mListener)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ auto buffer = MakeUnique<char[]>(aCount);
+ NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = ZW_ReadData(aInputStream, buffer.get(), aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make sure we aren't reading too much
+ mZstream.avail_in = aCount;
+ mZstream.next_in = (unsigned char*)buffer.get();
+
+ int zerr = Z_OK;
+ // deflate loop
+ while (mZstream.avail_in > 0 && zerr == Z_OK) {
+ zerr = deflate(&mZstream, Z_NO_FLUSH);
+
+ while (mZstream.avail_out == 0) {
+ // buffer is full, push the data out to the listener
+ rv = PushAvailableData(aRequest, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ zerr = deflate(&mZstream, Z_NO_FLUSH);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDeflateConverter::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ if (!mListener)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mListener->OnStartRequest(aRequest, mContext);
+}
+
+NS_IMETHODIMP nsDeflateConverter::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ if (!mListener)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ int zerr;
+ do {
+ zerr = deflate(&mZstream, Z_FINISH);
+ rv = PushAvailableData(aRequest, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (zerr == Z_OK);
+
+ deflateEnd(&mZstream);
+
+ return mListener->OnStopRequest(aRequest, mContext, aStatusCode);
+}
+
+nsresult nsDeflateConverter::PushAvailableData(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ uint32_t bytesToWrite = sizeof(mWriteBuffer) - mZstream.avail_out;
+ // We don't need to do anything if there isn't any data
+ if (bytesToWrite == 0)
+ return NS_OK;
+
+ MOZ_ASSERT(bytesToWrite <= INT32_MAX);
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ (char*)mWriteBuffer, bytesToWrite);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mListener->OnDataAvailable(aRequest, mContext, stream, mOffset,
+ bytesToWrite);
+
+ // now set the state for 'deflate'
+ mZstream.next_out = mWriteBuffer;
+ mZstream.avail_out = sizeof(mWriteBuffer);
+
+ mOffset += bytesToWrite;
+ return rv;
+}
diff --git a/components/jar/src/nsDeflateConverter.h b/components/jar/src/nsDeflateConverter.h
new file mode 100644
index 000000000..9678af87d
--- /dev/null
+++ b/components/jar/src/nsDeflateConverter.h
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef _nsDeflateConverter_h_
+#define _nsDeflateConverter_h_
+
+#include "nsIStreamConverter.h"
+#include "nsCOMPtr.h"
+#include "nsIPipe.h"
+#include "zlib.h"
+#include "mozilla/Attributes.h"
+
+#define DEFLATECONVERTER_CID { 0x461cd5dd, 0x73c6, 0x47a4, \
+ { 0x8c, 0xc3, 0x60, 0x3b, 0x37, 0xd8, 0x4a, 0x61 } }
+
+#define ZIP_BUFLEN (4 * 1024 - 1)
+
+class nsDeflateConverter final : public nsIStreamConverter
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSISTREAMCONVERTER
+
+ nsDeflateConverter()
+ {
+ // 6 is Z_DEFAULT_COMPRESSION but we need the actual value
+ mLevel = 6;
+ }
+
+ explicit nsDeflateConverter(int32_t level)
+ {
+ mLevel = level;
+ }
+
+private:
+
+ ~nsDeflateConverter()
+ {
+ }
+
+ enum WrapMode {
+ WRAP_ZLIB,
+ WRAP_GZIP,
+ WRAP_NONE
+ };
+
+ WrapMode mWrapMode;
+ uint64_t mOffset;
+ int32_t mLevel;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mContext;
+ z_stream mZstream;
+ unsigned char mWriteBuffer[ZIP_BUFLEN];
+
+ nsresult Init();
+ nsresult PushAvailableData(nsIRequest *aRequest, nsISupports *aContext);
+};
+
+#endif
diff --git a/components/jar/src/nsIJARFactory.h b/components/jar/src/nsIJARFactory.h
new file mode 100644
index 000000000..a5b48280e
--- /dev/null
+++ b/components/jar/src/nsIJARFactory.h
@@ -0,0 +1,10 @@
+/* -*- Mode: C; 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/. */
+
+
+#ifndef nsIJARFactory_h__
+#define nsIJARFactory_h__
+
+#endif // nsIJARFactory_h__
diff --git a/components/jar/src/nsJAR.cpp b/components/jar/src/nsJAR.cpp
new file mode 100644
index 000000000..205649e8d
--- /dev/null
+++ b/components/jar/src/nsJAR.cpp
@@ -0,0 +1,1396 @@
+/* -*- 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 <string.h>
+#include "nsJARInputStream.h"
+#include "nsJAR.h"
+#include "nsIFile.h"
+#include "nsIX509Cert.h"
+#include "nsIConsoleService.h"
+#include "nsICryptoHash.h"
+#include "nsIDataSignatureVerifier.h"
+#include "prprf.h"
+#include "mozilla/Omnijar.h"
+
+#ifdef XP_UNIX
+ #include <sys/stat.h>
+#elif defined (XP_WIN)
+ #include <io.h>
+#endif
+
+using namespace mozilla;
+
+//----------------------------------------------
+// nsJARManifestItem declaration
+//----------------------------------------------
+/*
+ * nsJARManifestItem contains meta-information pertaining
+ * to an individual JAR entry, taken from the
+ * META-INF/MANIFEST.MF and META-INF/ *.SF files.
+ * This is security-critical information, defined here so it is not
+ * accessible from anywhere else.
+ */
+typedef enum
+{
+ JAR_INVALID = 1,
+ JAR_INTERNAL = 2,
+ JAR_EXTERNAL = 3
+} JARManifestItemType;
+
+class nsJARManifestItem
+{
+public:
+ JARManifestItemType mType;
+
+ // True if the second step of verification (VerifyEntry)
+ // has taken place:
+ bool entryVerified;
+
+ // Not signed, valid, or failure code
+ int16_t status;
+
+ // Internal storage of digests
+ nsCString calculatedSectionDigest;
+ nsCString storedEntryDigest;
+
+ nsJARManifestItem();
+ virtual ~nsJARManifestItem();
+};
+
+//-------------------------------------------------
+// nsJARManifestItem constructors and destructor
+//-------------------------------------------------
+nsJARManifestItem::nsJARManifestItem(): mType(JAR_INTERNAL),
+ entryVerified(false),
+ status(JAR_NOT_SIGNED)
+{
+}
+
+nsJARManifestItem::~nsJARManifestItem()
+{
+}
+
+//----------------------------------------------
+// nsJAR constructor/destructor
+//----------------------------------------------
+
+// The following initialization makes a guess of 10 entries per jarfile.
+nsJAR::nsJAR(): mZip(new nsZipArchive()),
+ mManifestData(8),
+ mParsedManifest(false),
+ mGlobalStatus(JAR_MANIFEST_NOT_PARSED),
+ mReleaseTime(PR_INTERVAL_NO_TIMEOUT),
+ mCache(nullptr),
+ mLock("nsJAR::mLock"),
+ mMtime(0),
+ mTotalItemsInManifest(0),
+ mOpened(false),
+ mIsOmnijar(false)
+{
+}
+
+nsJAR::~nsJAR()
+{
+ Close();
+}
+
+NS_IMPL_QUERY_INTERFACE(nsJAR, nsIZipReader)
+NS_IMPL_ADDREF(nsJAR)
+
+// Custom Release method works with nsZipReaderCache...
+// Release might be called from multi-thread, we have to
+// take this function carefully to avoid delete-after-use.
+MozExternalRefCountType nsJAR::Release(void)
+{
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+
+ RefPtr<nsZipReaderCache> cache;
+ if (mRefCnt == 2) { // don't use a lock too frequently
+ // Use a mutex here to guarantee mCache is not racing and the target instance
+ // is still valid to increase ref-count.
+ MutexAutoLock lock(mLock);
+ cache = mCache;
+ mCache = nullptr;
+ }
+ if (cache) {
+ DebugOnly<nsresult> rv = cache->ReleaseZip(this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to release zip file");
+ }
+
+ count = --mRefCnt; // don't access any member variable after this line
+ NS_LOG_RELEASE(this, count, "nsJAR");
+ if (0 == count) {
+ mRefCnt = 1; /* stabilize */
+ /* enable this to find non-threadsafe destructors: */
+ /* NS_ASSERT_OWNINGTHREAD(nsJAR); */
+ delete this;
+ return 0;
+ }
+
+ return count;
+}
+
+//----------------------------------------------
+// nsIZipReader implementation
+//----------------------------------------------
+
+NS_IMETHODIMP
+nsJAR::Open(nsIFile* zipFile)
+{
+ NS_ENSURE_ARG_POINTER(zipFile);
+ if (mOpened) return NS_ERROR_FAILURE; // Already open!
+
+ mZipFile = zipFile;
+ mOuterZipEntry.Truncate();
+ mOpened = true;
+
+ // The omnijar is special, it is opened early on and closed late
+ // this avoids reopening it
+ RefPtr<nsZipArchive> zip = mozilla::Omnijar::GetReader(zipFile);
+ if (zip) {
+ mZip = zip;
+ mIsOmnijar = true;
+ return NS_OK;
+ }
+ return mZip->OpenArchive(zipFile);
+}
+
+NS_IMETHODIMP
+nsJAR::OpenInner(nsIZipReader *aZipReader, const nsACString &aZipEntry)
+{
+ NS_ENSURE_ARG_POINTER(aZipReader);
+ if (mOpened) return NS_ERROR_FAILURE; // Already open!
+
+ bool exist;
+ nsresult rv = aZipReader->HasEntry(aZipEntry, &exist);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(exist, NS_ERROR_FILE_NOT_FOUND);
+
+ rv = aZipReader->GetFile(getter_AddRefs(mZipFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOpened = true;
+
+ mOuterZipEntry.Assign(aZipEntry);
+
+ RefPtr<nsZipHandle> handle;
+ rv = nsZipHandle::Init(static_cast<nsJAR*>(aZipReader)->mZip.get(), PromiseFlatCString(aZipEntry).get(),
+ getter_AddRefs(handle));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return mZip->OpenArchive(handle);
+}
+
+NS_IMETHODIMP
+nsJAR::OpenMemory(void* aData, uint32_t aLength)
+{
+ NS_ENSURE_ARG_POINTER(aData);
+ if (mOpened) return NS_ERROR_FAILURE; // Already open!
+
+ mOpened = true;
+
+ RefPtr<nsZipHandle> handle;
+ nsresult rv = nsZipHandle::Init(static_cast<uint8_t*>(aData), aLength,
+ getter_AddRefs(handle));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return mZip->OpenArchive(handle);
+}
+
+NS_IMETHODIMP
+nsJAR::GetFile(nsIFile* *result)
+{
+ *result = mZipFile;
+ NS_IF_ADDREF(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::Close()
+{
+ if (!mOpened) {
+ return NS_ERROR_FAILURE; // Never opened or already closed.
+ }
+
+ mOpened = false;
+ mParsedManifest = false;
+ mManifestData.Clear();
+ mGlobalStatus = JAR_MANIFEST_NOT_PARSED;
+ mTotalItemsInManifest = 0;
+
+ if (mIsOmnijar) {
+ // Reset state, but don't close the omnijar because we did not open it.
+ mIsOmnijar = false;
+ mZip = new nsZipArchive();
+ return NS_OK;
+ }
+
+ return mZip->CloseArchive();
+}
+
+NS_IMETHODIMP
+nsJAR::Test(const nsACString &aEntryName)
+{
+ return mZip->Test(aEntryName.IsEmpty()? nullptr : PromiseFlatCString(aEntryName).get());
+}
+
+NS_IMETHODIMP
+nsJAR::Extract(const nsACString &aEntryName, nsIFile* outFile)
+{
+ // nsZipArchive and zlib are not thread safe
+ // we need to use a lock to prevent bug #51267
+ MutexAutoLock lock(mLock);
+
+ nsZipItem *item = mZip->GetItem(PromiseFlatCString(aEntryName).get());
+ NS_ENSURE_TRUE(item, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
+
+ // Remove existing file or directory so we set permissions correctly.
+ // If it's a directory that already exists and contains files, throw
+ // an exception and return.
+
+ nsresult rv = outFile->Remove(false);
+ if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY ||
+ rv == NS_ERROR_FAILURE)
+ return rv;
+
+ if (item->IsDirectory())
+ {
+ rv = outFile->Create(nsIFile::DIRECTORY_TYPE, item->Mode());
+ //XXX Do this in nsZipArchive? It would be nice to keep extraction
+ //XXX code completely there, but that would require a way to get a
+ //XXX PRDir from outFile.
+ }
+ else
+ {
+ PRFileDesc* fd;
+ rv = outFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->Mode(), &fd);
+ if (NS_FAILED(rv)) return rv;
+
+ // ExtractFile also closes the fd handle and resolves the symlink if needed
+ rv = mZip->ExtractFile(item, outFile, fd);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ // nsIFile needs milliseconds, while prtime is in microseconds.
+ // non-fatal if this fails, ignore errors
+ outFile->SetLastModifiedTime(item->LastModTime() / PR_USEC_PER_MSEC);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::GetEntry(const nsACString &aEntryName, nsIZipEntry* *result)
+{
+ nsZipItem* zipItem = mZip->GetItem(PromiseFlatCString(aEntryName).get());
+ NS_ENSURE_TRUE(zipItem, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
+
+ nsJARItem* jarItem = new nsJARItem(zipItem);
+
+ NS_ADDREF(*result = jarItem);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::HasEntry(const nsACString &aEntryName, bool *result)
+{
+ *result = mZip->GetItem(PromiseFlatCString(aEntryName).get()) != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::FindEntries(const nsACString &aPattern, nsIUTF8StringEnumerator **result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsZipFind *find;
+ nsresult rv = mZip->FindInit(aPattern.IsEmpty()? nullptr : PromiseFlatCString(aPattern).get(), &find);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIUTF8StringEnumerator *zipEnum = new nsJAREnumerator(find);
+
+ NS_ADDREF(*result = zipEnum);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::GetInputStream(const nsACString &aFilename, nsIInputStream** result)
+{
+ return GetInputStreamWithSpec(EmptyCString(), aFilename, result);
+}
+
+NS_IMETHODIMP
+nsJAR::GetInputStreamWithSpec(const nsACString& aJarDirSpec,
+ const nsACString &aEntryName, nsIInputStream** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ // Watch out for the jar:foo.zip!/ (aDir is empty) top-level special case!
+ nsZipItem *item = nullptr;
+ const nsCString& entry = PromiseFlatCString(aEntryName);
+ if (*entry.get()) {
+ // First check if item exists in jar
+ item = mZip->GetItem(entry.get());
+ if (!item) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+ }
+ nsJARInputStream* jis = new nsJARInputStream();
+ // addref now so we can call InitFile/InitDirectory()
+ NS_ADDREF(*result = jis);
+
+ nsresult rv = NS_OK;
+ if (!item || item->IsDirectory()) {
+ rv = jis->InitDirectory(this, aJarDirSpec, entry.get());
+ } else {
+ rv = jis->InitFile(this, item);
+ }
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(*result);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJAR::GetSigningCert(const nsACString& aFilename, nsIX509Cert** aSigningCert)
+{
+ //-- Parameter check
+ if (!aSigningCert) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aSigningCert = nullptr;
+
+ // Don't check signatures in the omnijar - this is only
+ // interesting for extensions/XPIs.
+ RefPtr<nsZipArchive> greOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
+ RefPtr<nsZipArchive> appOmni = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
+
+ if (mZip == greOmni || mZip == appOmni)
+ return NS_OK;
+
+ //-- Parse the manifest
+ nsresult rv = ParseManifest();
+ if (NS_FAILED(rv)) return rv;
+ if (mGlobalStatus == JAR_NO_MANIFEST)
+ return NS_OK;
+
+ int16_t requestedStatus;
+ if (!aFilename.IsEmpty())
+ {
+ //-- Find the item
+ nsJARManifestItem* manItem = mManifestData.Get(aFilename);
+ if (!manItem)
+ return NS_OK;
+ //-- Verify the item against the manifest
+ if (!manItem->entryVerified)
+ {
+ nsXPIDLCString entryData;
+ uint32_t entryDataLen;
+ rv = LoadEntry(aFilename, getter_Copies(entryData), &entryDataLen);
+ if (NS_FAILED(rv)) return rv;
+ rv = VerifyEntry(manItem, entryData, entryDataLen);
+ if (NS_FAILED(rv)) return rv;
+ }
+ requestedStatus = manItem->status;
+ }
+ else // User wants identity of signer w/o verifying any entries
+ requestedStatus = mGlobalStatus;
+
+ if (requestedStatus != JAR_VALID_MANIFEST) {
+ ReportError(aFilename, requestedStatus);
+ } else { // Valid signature
+ *aSigningCert = mSigningCert;
+ NS_IF_ADDREF(*aSigningCert);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJAR::GetManifestEntriesCount(uint32_t* count)
+{
+ *count = mTotalItemsInManifest;
+ return NS_OK;
+}
+
+nsresult
+nsJAR::GetJarPath(nsACString& aResult)
+{
+ NS_ENSURE_ARG_POINTER(mZipFile);
+
+ return mZipFile->GetPersistentDescriptor(aResult);
+}
+
+nsresult
+nsJAR::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc)
+{
+ if (!aNSPRFileDesc) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ *aNSPRFileDesc = nullptr;
+
+ if (!mZip) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsZipHandle> handle = mZip->GetFD();
+ if (!handle) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return handle->GetNSPRFileDesc(aNSPRFileDesc);
+}
+
+//----------------------------------------------
+// nsJAR private implementation
+//----------------------------------------------
+nsresult
+nsJAR::LoadEntry(const nsACString &aFilename, char** aBuf, uint32_t* aBufLen)
+{
+ //-- Get a stream for reading the file
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> manifestStream;
+ rv = GetInputStream(aFilename, getter_AddRefs(manifestStream));
+ if (NS_FAILED(rv)) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+
+ //-- Read the manifest file into memory
+ char* buf;
+ uint64_t len64;
+ rv = manifestStream->Available(&len64);
+ if (NS_FAILED(rv)) return rv;
+ if (len64 >= UINT32_MAX) { // bug 164695
+ nsZipArchive::sFileCorruptedReason = "nsJAR: invalid manifest size";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ uint32_t len = (uint32_t)len64;
+ buf = (char*)malloc(len+1);
+ if (!buf) return NS_ERROR_OUT_OF_MEMORY;
+ uint32_t bytesRead;
+ rv = manifestStream->Read(buf, len, &bytesRead);
+ if (bytesRead != len) {
+ nsZipArchive::sFileCorruptedReason = "nsJAR: manifest too small";
+ rv = NS_ERROR_FILE_CORRUPTED;
+ }
+ if (NS_FAILED(rv)) {
+ free(buf);
+ return rv;
+ }
+ buf[len] = '\0'; //Null-terminate the buffer
+ *aBuf = buf;
+ if (aBufLen)
+ *aBufLen = len;
+ return NS_OK;
+}
+
+
+int32_t
+nsJAR::ReadLine(const char** src)
+{
+ if (!*src) {
+ return 0;
+ }
+
+ //--Moves pointer to beginning of next line and returns line length
+ // not including CR/LF.
+ int32_t length;
+ char* eol = PL_strpbrk(*src, "\r\n");
+
+ if (eol == nullptr) // Probably reached end of file before newline
+ {
+ length = strlen(*src);
+ if (length == 0) // immediate end-of-file
+ *src = nullptr;
+ else // some data left on this line
+ *src += length;
+ }
+ else
+ {
+ length = eol - *src;
+ if (eol[0] == '\r' && eol[1] == '\n') // CR LF, so skip 2
+ *src = eol+2;
+ else // Either CR or LF, so skip 1
+ *src = eol+1;
+ }
+ return length;
+}
+
+//-- The following #defines are used by ParseManifest()
+// and ParseOneFile(). The header strings are defined in the JAR specification.
+#define JAR_MF 1
+#define JAR_SF 2
+#define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
+#define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
+#define JAR_MF_HEADER (const char*)"Manifest-Version: 1.0"
+#define JAR_SF_HEADER (const char*)"Signature-Version: 1.0"
+
+nsresult
+nsJAR::ParseManifest()
+{
+ //-- Verification Step 1
+ if (mParsedManifest)
+ return NS_OK;
+ //-- (1)Manifest (MF) file
+ nsCOMPtr<nsIUTF8StringEnumerator> files;
+ nsresult rv = FindEntries(nsDependentCString(JAR_MF_SEARCH_STRING), getter_AddRefs(files));
+ if (!files) rv = NS_ERROR_FAILURE;
+ if (NS_FAILED(rv)) return rv;
+
+ //-- Load the file into memory
+ bool more;
+ rv = files->HasMore(&more);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!more)
+ {
+ mGlobalStatus = JAR_NO_MANIFEST;
+ mParsedManifest = true;
+ return NS_OK;
+ }
+
+ nsAutoCString manifestFilename;
+ rv = files->GetNext(manifestFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if there is more than one manifest, if so then error!
+ rv = files->HasMore(&more);
+ if (NS_FAILED(rv)) return rv;
+ if (more)
+ {
+ mParsedManifest = true;
+ nsZipArchive::sFileCorruptedReason = "nsJAR: duplicate manifests";
+ return NS_ERROR_FILE_CORRUPTED; // More than one MF file
+ }
+
+ nsXPIDLCString manifestBuffer;
+ uint32_t manifestLen;
+ rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
+ if (NS_FAILED(rv)) return rv;
+
+ //-- Parse it
+ rv = ParseOneFile(manifestBuffer, JAR_MF);
+ if (NS_FAILED(rv)) return rv;
+
+ //-- (2)Signature (SF) file
+ // If there are multiple signatures, we select one.
+ rv = FindEntries(nsDependentCString(JAR_SF_SEARCH_STRING), getter_AddRefs(files));
+ if (!files) rv = NS_ERROR_FAILURE;
+ if (NS_FAILED(rv)) return rv;
+ //-- Get an SF file
+ rv = files->HasMore(&more);
+ if (NS_FAILED(rv)) return rv;
+ if (!more)
+ {
+ mGlobalStatus = JAR_NO_MANIFEST;
+ mParsedManifest = true;
+ return NS_OK;
+ }
+ rv = files->GetNext(manifestFilename);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = LoadEntry(manifestFilename, getter_Copies(manifestBuffer), &manifestLen);
+ if (NS_FAILED(rv)) return rv;
+
+ //-- Get its corresponding signature file
+ nsAutoCString sigFilename(manifestFilename);
+ int32_t extension = sigFilename.RFindChar('.') + 1;
+ NS_ASSERTION(extension != 0, "Manifest Parser: Missing file extension.");
+ (void)sigFilename.Cut(extension, 2);
+ nsXPIDLCString sigBuffer;
+ uint32_t sigLen;
+ {
+ nsAutoCString tempFilename(sigFilename); tempFilename.Append("rsa", 3);
+ rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
+ }
+ if (NS_FAILED(rv))
+ {
+ nsAutoCString tempFilename(sigFilename); tempFilename.Append("RSA", 3);
+ rv = LoadEntry(tempFilename, getter_Copies(sigBuffer), &sigLen);
+ }
+ if (NS_FAILED(rv))
+ {
+ mGlobalStatus = JAR_NO_MANIFEST;
+ mParsedManifest = true;
+ return NS_OK;
+ }
+
+ //-- Get the signature verifier service
+ nsCOMPtr<nsIDataSignatureVerifier> verifier(
+ do_GetService("@mozilla.org/security/datasignatureverifier;1", &rv));
+ if (NS_FAILED(rv)) // No signature verifier available
+ {
+ mGlobalStatus = JAR_NO_MANIFEST;
+ mParsedManifest = true;
+ return NS_OK;
+ }
+
+ //-- Verify that the signature file is a valid signature of the SF file
+ int32_t verifyError;
+ rv = verifier->VerifySignature(sigBuffer, sigLen, manifestBuffer, manifestLen,
+ &verifyError, getter_AddRefs(mSigningCert));
+ if (NS_FAILED(rv)) return rv;
+ if (mSigningCert && verifyError == nsIDataSignatureVerifier::VERIFY_OK) {
+ mGlobalStatus = JAR_VALID_MANIFEST;
+ } else if (verifyError == nsIDataSignatureVerifier::VERIFY_ERROR_UNKNOWN_ISSUER) {
+ mGlobalStatus = JAR_INVALID_UNKNOWN_CA;
+ } else {
+ mGlobalStatus = JAR_INVALID_SIG;
+ }
+
+ //-- Parse the SF file. If the verification above failed, principal
+ // is null, and ParseOneFile will mark the relevant entries as invalid.
+ // if ParseOneFile fails, then it has no effect, and we can safely
+ // continue to the next SF file, or return.
+ ParseOneFile(manifestBuffer, JAR_SF);
+ mParsedManifest = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsJAR::ParseOneFile(const char* filebuf, int16_t aFileType)
+{
+ //-- Check file header
+ const char* nextLineStart = filebuf;
+ nsAutoCString curLine;
+ int32_t linelen;
+ linelen = ReadLine(&nextLineStart);
+ curLine.Assign(filebuf, linelen);
+
+ if ( ((aFileType == JAR_MF) && !curLine.Equals(JAR_MF_HEADER) ) ||
+ ((aFileType == JAR_SF) && !curLine.Equals(JAR_SF_HEADER) ) ) {
+ nsZipArchive::sFileCorruptedReason = "nsJAR: invalid manifest header";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ //-- Skip header section
+ do {
+ linelen = ReadLine(&nextLineStart);
+ } while (linelen > 0);
+
+ //-- Set up parsing variables
+ const char* curPos;
+ const char* sectionStart = nextLineStart;
+
+ nsJARManifestItem* curItemMF = nullptr;
+ bool foundName = false;
+ if (aFileType == JAR_MF) {
+ curItemMF = new nsJARManifestItem();
+ }
+
+ nsAutoCString curItemName;
+ nsAutoCString storedSectionDigest;
+
+ for(;;)
+ {
+ curPos = nextLineStart;
+ linelen = ReadLine(&nextLineStart);
+ curLine.Assign(curPos, linelen);
+ if (linelen == 0)
+ // end of section (blank line or end-of-file)
+ {
+ if (aFileType == JAR_MF)
+ {
+ mTotalItemsInManifest++;
+ if (curItemMF->mType != JAR_INVALID)
+ {
+ //-- Did this section have a name: line?
+ if(!foundName)
+ curItemMF->mType = JAR_INVALID;
+ else
+ {
+ //-- If it's an internal item, it must correspond
+ // to a valid jar entry
+ if (curItemMF->mType == JAR_INTERNAL)
+ {
+ bool exists;
+ nsresult rv = HasEntry(curItemName, &exists);
+ if (NS_FAILED(rv) || !exists)
+ curItemMF->mType = JAR_INVALID;
+ }
+ //-- Check for duplicates
+ if (mManifestData.Contains(curItemName)) {
+ curItemMF->mType = JAR_INVALID;
+ }
+ }
+ }
+
+ if (curItemMF->mType == JAR_INVALID)
+ delete curItemMF;
+ else //-- calculate section digest
+ {
+ uint32_t sectionLength = curPos - sectionStart;
+ CalculateDigest(sectionStart, sectionLength,
+ curItemMF->calculatedSectionDigest);
+ //-- Save item in the hashtable
+ mManifestData.Put(curItemName, curItemMF);
+ }
+ if (nextLineStart == nullptr) // end-of-file
+ break;
+
+ sectionStart = nextLineStart;
+ curItemMF = new nsJARManifestItem();
+ } // (aFileType == JAR_MF)
+ else
+ //-- file type is SF, compare digest with calculated
+ // section digests from MF file.
+ {
+ if (foundName)
+ {
+ nsJARManifestItem* curItemSF = mManifestData.Get(curItemName);
+ if(curItemSF)
+ {
+ NS_ASSERTION(curItemSF->status == JAR_NOT_SIGNED,
+ "SECURITY ERROR: nsJARManifestItem not correctly initialized");
+ curItemSF->status = mGlobalStatus;
+ if (curItemSF->status == JAR_VALID_MANIFEST)
+ { // Compare digests
+ if (storedSectionDigest.IsEmpty())
+ curItemSF->status = JAR_NOT_SIGNED;
+ else
+ {
+ if (!storedSectionDigest.Equals(curItemSF->calculatedSectionDigest))
+ curItemSF->status = JAR_INVALID_MANIFEST;
+ curItemSF->calculatedSectionDigest.Truncate();
+ storedSectionDigest.Truncate();
+ }
+ } // (aPrincipal != nullptr)
+ } // if(curItemSF)
+ } // if(foundName)
+
+ if(nextLineStart == nullptr) // end-of-file
+ break;
+ } // aFileType == JAR_SF
+ foundName = false;
+ continue;
+ } // if(linelen == 0)
+
+ //-- Look for continuations (beginning with a space) on subsequent lines
+ // and append them to the current line.
+ while(*nextLineStart == ' ')
+ {
+ curPos = nextLineStart;
+ int32_t continuationLen = ReadLine(&nextLineStart) - 1;
+ nsAutoCString continuation(curPos+1, continuationLen);
+ curLine += continuation;
+ linelen += continuationLen;
+ }
+
+ //-- Find colon in current line, this separates name from value
+ int32_t colonPos = curLine.FindChar(':');
+ if (colonPos == -1) // No colon on line, ignore line
+ continue;
+ //-- Break down the line
+ nsAutoCString lineName;
+ curLine.Left(lineName, colonPos);
+ nsAutoCString lineData;
+ curLine.Mid(lineData, colonPos+2, linelen - (colonPos+2));
+
+ //-- Lines to look for:
+ // (1) Digest:
+ if (lineName.LowerCaseEqualsLiteral("sha1-digest"))
+ //-- This is a digest line, save the data in the appropriate place
+ {
+ if(aFileType == JAR_MF)
+ curItemMF->storedEntryDigest = lineData;
+ else
+ storedSectionDigest = lineData;
+ continue;
+ }
+
+ // (2) Name: associates this manifest section with a file in the jar.
+ if (!foundName && lineName.LowerCaseEqualsLiteral("name"))
+ {
+ curItemName = lineData;
+ foundName = true;
+ continue;
+ }
+
+ // (3) Magic: this may be an inline Javascript.
+ // We can't do any other kind of magic.
+ if (aFileType == JAR_MF && lineName.LowerCaseEqualsLiteral("magic"))
+ {
+ if (lineData.LowerCaseEqualsLiteral("javascript"))
+ curItemMF->mType = JAR_EXTERNAL;
+ else
+ curItemMF->mType = JAR_INVALID;
+ continue;
+ }
+
+ } // for (;;)
+ return NS_OK;
+} //ParseOneFile()
+
+nsresult
+nsJAR::VerifyEntry(nsJARManifestItem* aManItem, const char* aEntryData,
+ uint32_t aLen)
+{
+ if (aManItem->status == JAR_VALID_MANIFEST)
+ {
+ if (aManItem->storedEntryDigest.IsEmpty())
+ // No entry digests in manifest file. Entry is unsigned.
+ aManItem->status = JAR_NOT_SIGNED;
+ else
+ { //-- Calculate and compare digests
+ nsCString calculatedEntryDigest;
+ nsresult rv = CalculateDigest(aEntryData, aLen, calculatedEntryDigest);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+ if (!aManItem->storedEntryDigest.Equals(calculatedEntryDigest))
+ aManItem->status = JAR_INVALID_ENTRY;
+ aManItem->storedEntryDigest.Truncate();
+ }
+ }
+ aManItem->entryVerified = true;
+ return NS_OK;
+}
+
+void nsJAR::ReportError(const nsACString &aFilename, int16_t errorCode)
+{
+ //-- Generate error message
+ nsAutoString message;
+ message.AssignLiteral("Signature Verification Error: the signature on ");
+ if (!aFilename.IsEmpty())
+ AppendASCIItoUTF16(aFilename, message);
+ else
+ message.AppendLiteral("this .jar archive");
+ message.AppendLiteral(" is invalid because ");
+ switch(errorCode)
+ {
+ case JAR_NOT_SIGNED:
+ message.AppendLiteral("the archive did not contain a valid PKCS7 signature.");
+ break;
+ case JAR_INVALID_SIG:
+ message.AppendLiteral("the digital signature (*.RSA) file is not a valid signature of the signature instruction file (*.SF).");
+ break;
+ case JAR_INVALID_UNKNOWN_CA:
+ message.AppendLiteral("the certificate used to sign this file has an unrecognized issuer.");
+ break;
+ case JAR_INVALID_MANIFEST:
+ message.AppendLiteral("the signature instruction file (*.SF) does not contain a valid hash of the MANIFEST.MF file.");
+ break;
+ case JAR_INVALID_ENTRY:
+ message.AppendLiteral("the MANIFEST.MF file does not contain a valid hash of the file being verified.");
+ break;
+ case JAR_NO_MANIFEST:
+ message.AppendLiteral("the archive did not contain a manifest.");
+ break;
+ default:
+ message.AppendLiteral("of an unknown problem.");
+ }
+
+ // Report error in JS console
+ nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
+ if (console)
+ {
+ console->LogStringMessage(message.get());
+ }
+#ifdef DEBUG
+ char* messageCstr = ToNewCString(message);
+ if (!messageCstr) return;
+ fprintf(stderr, "%s\n", messageCstr);
+ free(messageCstr);
+#endif
+}
+
+
+nsresult nsJAR::CalculateDigest(const char* aInBuf, uint32_t aLen,
+ nsCString& digest)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICryptoHash> hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = hasher->Init(nsICryptoHash::SHA1);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = hasher->Update((const uint8_t*) aInBuf, aLen);
+ if (NS_FAILED(rv)) return rv;
+
+ return hasher->Finish(true, digest);
+}
+
+NS_IMPL_ISUPPORTS(nsJAREnumerator, nsIUTF8StringEnumerator)
+
+//----------------------------------------------
+// nsJAREnumerator::HasMore
+//----------------------------------------------
+NS_IMETHODIMP
+nsJAREnumerator::HasMore(bool* aResult)
+{
+ // try to get the next element
+ if (!mName) {
+ NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
+ nsresult rv = mFind->FindNext( &mName, &mNameLen );
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ *aResult = false; // No more matches available
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); // no error translation
+ }
+
+ *aResult = true;
+ return NS_OK;
+}
+
+//----------------------------------------------
+// nsJAREnumerator::GetNext
+//----------------------------------------------
+NS_IMETHODIMP
+nsJAREnumerator::GetNext(nsACString& aResult)
+{
+ // check if the current item is "stale"
+ if (!mName) {
+ bool bMore;
+ nsresult rv = HasMore(&bMore);
+ if (NS_FAILED(rv) || !bMore)
+ return NS_ERROR_FAILURE; // no error translation
+ }
+ aResult.Assign(mName, mNameLen);
+ mName = 0; // we just gave this one away
+ return NS_OK;
+}
+
+
+NS_IMPL_ISUPPORTS(nsJARItem, nsIZipEntry)
+
+nsJARItem::nsJARItem(nsZipItem* aZipItem)
+ : mSize(aZipItem->Size()),
+ mRealsize(aZipItem->RealSize()),
+ mCrc32(aZipItem->CRC32()),
+ mLastModTime(aZipItem->LastModTime()),
+ mCompression(aZipItem->Compression()),
+ mPermissions(aZipItem->Mode()),
+ mIsDirectory(aZipItem->IsDirectory()),
+ mIsSynthetic(aZipItem->isSynthetic)
+{
+}
+
+//------------------------------------------
+// nsJARItem::GetCompression
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetCompression(uint16_t *aCompression)
+{
+ NS_ENSURE_ARG_POINTER(aCompression);
+
+ *aCompression = mCompression;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetSize
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetSize(uint32_t *aSize)
+{
+ NS_ENSURE_ARG_POINTER(aSize);
+
+ *aSize = mSize;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetRealSize
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetRealSize(uint32_t *aRealsize)
+{
+ NS_ENSURE_ARG_POINTER(aRealsize);
+
+ *aRealsize = mRealsize;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetCrc32
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetCRC32(uint32_t *aCrc32)
+{
+ NS_ENSURE_ARG_POINTER(aCrc32);
+
+ *aCrc32 = mCrc32;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetIsDirectory
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetIsDirectory(bool *aIsDirectory)
+{
+ NS_ENSURE_ARG_POINTER(aIsDirectory);
+
+ *aIsDirectory = mIsDirectory;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetIsSynthetic
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetIsSynthetic(bool *aIsSynthetic)
+{
+ NS_ENSURE_ARG_POINTER(aIsSynthetic);
+
+ *aIsSynthetic = mIsSynthetic;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetLastModifiedTime
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetLastModifiedTime(PRTime* aLastModTime)
+{
+ NS_ENSURE_ARG_POINTER(aLastModTime);
+
+ *aLastModTime = mLastModTime;
+ return NS_OK;
+}
+
+//------------------------------------------
+// nsJARItem::GetPermissions
+//------------------------------------------
+NS_IMETHODIMP
+nsJARItem::GetPermissions(uint32_t* aPermissions)
+{
+ NS_ENSURE_ARG_POINTER(aPermissions);
+
+ *aPermissions = mPermissions;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIZipReaderCache
+
+NS_IMPL_ISUPPORTS(nsZipReaderCache, nsIZipReaderCache, nsIObserver, nsISupportsWeakReference)
+
+nsZipReaderCache::nsZipReaderCache()
+ : mLock("nsZipReaderCache.mLock")
+ , mCacheSize(0)
+ , mZips()
+#ifdef ZIP_CACHE_HIT_RATE
+ ,
+ mZipCacheLookups(0),
+ mZipCacheHits(0),
+ mZipCacheFlushes(0),
+ mZipSyncMisses(0)
+#endif
+{
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::Init(uint32_t cacheSize)
+{
+ mCacheSize = cacheSize;
+
+// Register as a memory pressure observer
+ nsCOMPtr<nsIObserverService> os =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (os)
+ {
+ os->AddObserver(this, "memory-pressure", true);
+ os->AddObserver(this, "chrome-flush-caches", true);
+ os->AddObserver(this, "flush-cache-entry", true);
+ }
+// ignore failure of the observer registration.
+
+ return NS_OK;
+}
+
+nsZipReaderCache::~nsZipReaderCache()
+{
+ for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
+ iter.UserData()->SetZipReaderCache(nullptr);
+ }
+
+#ifdef ZIP_CACHE_HIT_RATE
+ printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n",
+ mCacheSize, mZipCacheHits, mZipCacheLookups,
+ (float)mZipCacheHits / mZipCacheLookups,
+ mZipCacheFlushes, mZipSyncMisses);
+#endif
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult)
+{
+ NS_ENSURE_ARG_POINTER(zipFile);
+ nsresult rv;
+ MutexAutoLock lock(mLock);
+
+ nsAutoCString uri;
+ rv = zipFile->GetPersistentDescriptor(uri);
+ if (NS_FAILED(rv))
+ return rv;
+
+ uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+
+ *aResult = mZips.Contains(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
+{
+ NS_ENSURE_ARG_POINTER(zipFile);
+ nsresult rv;
+ MutexAutoLock lock(mLock);
+
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheLookups++;
+#endif
+
+ nsAutoCString uri;
+ rv = zipFile->GetPersistentDescriptor(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+
+ RefPtr<nsJAR> zip;
+ mZips.Get(uri, getter_AddRefs(zip));
+ if (zip) {
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheHits++;
+#endif
+ zip->ClearReleaseTime();
+ } else {
+ zip = new nsJAR();
+ zip->SetZipReaderCache(this);
+ rv = zip->Open(zipFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MOZ_ASSERT(!mZips.Contains(uri));
+ mZips.Put(uri, zip);
+ }
+ zip.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::GetInnerZip(nsIFile* zipFile, const nsACString &entry,
+ nsIZipReader* *result)
+{
+ NS_ENSURE_ARG_POINTER(zipFile);
+
+ nsCOMPtr<nsIZipReader> outerZipReader;
+ nsresult rv = GetZip(zipFile, getter_AddRefs(outerZipReader));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheLookups++;
+#endif
+
+ nsAutoCString uri;
+ rv = zipFile->GetPersistentDescriptor(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
+ uri.AppendLiteral("!/");
+ uri.Append(entry);
+
+ RefPtr<nsJAR> zip;
+ mZips.Get(uri, getter_AddRefs(zip));
+ if (zip) {
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheHits++;
+#endif
+ zip->ClearReleaseTime();
+ } else {
+ zip = new nsJAR();
+ zip->SetZipReaderCache(this);
+
+ rv = zip->OpenInner(outerZipReader, entry);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MOZ_ASSERT(!mZips.Contains(uri));
+ mZips.Put(uri, zip);
+ }
+ zip.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::GetFd(nsIFile* zipFile, PRFileDesc** aRetVal)
+{
+#if defined(XP_WIN)
+ MOZ_CRASH("Not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ if (!zipFile) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ nsAutoCString uri;
+ rv = zipFile->GetPersistentDescriptor(uri);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+
+ MutexAutoLock lock(mLock);
+ RefPtr<nsJAR> zip;
+ mZips.Get(uri, getter_AddRefs(zip));
+ if (!zip) {
+ return NS_ERROR_FAILURE;
+ }
+
+ zip->ClearReleaseTime();
+ rv = zip->GetNSPRFileDesc(aRetVal);
+ // Do this to avoid possible deadlock on mLock with ReleaseZip().
+ MutexAutoUnlock unlock(mLock);
+ RefPtr<nsJAR> zipTemp = zip.forget();
+ return rv;
+#endif /* XP_WIN */
+}
+
+nsresult
+nsZipReaderCache::ReleaseZip(nsJAR* zip)
+{
+ nsresult rv;
+ MutexAutoLock lock(mLock);
+
+ // It is possible that two thread compete for this zip. The dangerous
+ // case is where one thread Releases the zip and discovers that the ref
+ // count has gone to one. Before it can call this ReleaseZip method
+ // another thread calls our GetZip method. The ref count goes to two. That
+ // second thread then Releases the zip and the ref count goes to one. It
+ // then tries to enter this ReleaseZip method and blocks while the first
+ // thread is still here. The first thread continues and remove the zip from
+ // the cache and calls its Release method sending the ref count to 0 and
+ // deleting the zip. However, the second thread is still blocked at the
+ // start of ReleaseZip, but the 'zip' param now hold a reference to a
+ // deleted zip!
+ //
+ // So, we are going to try safeguarding here by searching our hashtable while
+ // locked here for the zip. We return fast if it is not found.
+
+ bool found = false;
+ for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
+ if (zip == iter.UserData()) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipSyncMisses++;
+#endif
+ return NS_OK;
+ }
+
+ zip->SetReleaseTime();
+
+ if (mZips.Count() <= mCacheSize)
+ return NS_OK;
+
+ // Find the oldest zip.
+ nsJAR* oldest = nullptr;
+ for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
+ nsJAR* current = iter.UserData();
+ PRIntervalTime currentReleaseTime = current->GetReleaseTime();
+ if (currentReleaseTime != PR_INTERVAL_NO_TIMEOUT) {
+ if (oldest == nullptr ||
+ currentReleaseTime < oldest->GetReleaseTime()) {
+ oldest = current;
+ }
+ }
+ }
+
+ // Because of the craziness above it is possible that there is no zip that
+ // needs removing.
+ if (!oldest)
+ return NS_OK;
+
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheFlushes++;
+#endif
+
+ // remove from hashtable
+ nsAutoCString uri;
+ rv = oldest->GetJarPath(uri);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (oldest->mOuterZipEntry.IsEmpty()) {
+ uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+ } else {
+ uri.Insert(NS_LITERAL_CSTRING("jar:"), 0);
+ uri.AppendLiteral("!/");
+ uri.Append(oldest->mOuterZipEntry);
+ }
+
+ // Retrieving and removing the JAR must be done without an extra AddRef
+ // and Release, or we'll trigger nsJAR::Release's magic refcount 1 case
+ // an extra time and trigger a deadlock.
+ RefPtr<nsJAR> removed;
+ mZips.Remove(uri, getter_AddRefs(removed));
+ NS_ASSERTION(removed, "botched");
+ NS_ASSERTION(oldest == removed, "removed wrong entry");
+
+ if (removed)
+ removed->SetZipReaderCache(nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsZipReaderCache::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aSomeData)
+{
+ if (strcmp(aTopic, "memory-pressure") == 0) {
+ MutexAutoLock lock(mLock);
+ for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<nsJAR>& current = iter.Data();
+ if (current->GetReleaseTime() != PR_INTERVAL_NO_TIMEOUT) {
+ current->SetZipReaderCache(nullptr);
+ iter.Remove();
+ }
+ }
+ }
+ else if (strcmp(aTopic, "chrome-flush-caches") == 0) {
+ MutexAutoLock lock(mLock);
+ for (auto iter = mZips.Iter(); !iter.Done(); iter.Next()) {
+ iter.UserData()->SetZipReaderCache(nullptr);
+ }
+ mZips.Clear();
+ }
+ else if (strcmp(aTopic, "flush-cache-entry") == 0) {
+ nsCOMPtr<nsIFile> file = do_QueryInterface(aSubject);
+ if (!file)
+ return NS_OK;
+
+ nsAutoCString uri;
+ if (NS_FAILED(file->GetPersistentDescriptor(uri)))
+ return NS_OK;
+
+ uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
+
+ MutexAutoLock lock(mLock);
+
+ RefPtr<nsJAR> zip;
+ mZips.Get(uri, getter_AddRefs(zip));
+ if (!zip)
+ return NS_OK;
+
+#ifdef ZIP_CACHE_HIT_RATE
+ mZipCacheFlushes++;
+#endif
+
+ zip->SetZipReaderCache(nullptr);
+
+ mZips.Remove(uri);
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/components/jar/src/nsJAR.h b/components/jar/src/nsJAR.h
new file mode 100644
index 000000000..2a982a1b5
--- /dev/null
+++ b/components/jar/src/nsJAR.h
@@ -0,0 +1,224 @@
+/* -*- Mode: C; 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/. */
+
+#ifndef nsJAR_h_
+#define nsJAR_h_
+
+#include "nscore.h"
+#include "prio.h"
+#include "plstr.h"
+#include "mozilla/Logging.h"
+#include "prinrval.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Mutex.h"
+#include "nsIComponentManager.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "nsStringEnumerator.h"
+#include "nsHashKeys.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTHashtable.h"
+#include "nsIZipReader.h"
+#include "nsZipArchive.h"
+#include "nsIObserverService.h"
+#include "nsWeakReference.h"
+#include "nsIObserver.h"
+#include "mozilla/Attributes.h"
+
+class nsIX509Cert;
+class nsJARManifestItem;
+class nsZipReaderCache;
+
+/* For mManifestStatus */
+typedef enum
+{
+ JAR_MANIFEST_NOT_PARSED = 0,
+ JAR_VALID_MANIFEST = 1,
+ JAR_INVALID_SIG = 2,
+ JAR_INVALID_UNKNOWN_CA = 3,
+ JAR_INVALID_MANIFEST = 4,
+ JAR_INVALID_ENTRY = 5,
+ JAR_NO_MANIFEST = 6,
+ JAR_NOT_SIGNED = 7
+} JARManifestStatusType;
+
+/*-------------------------------------------------------------------------
+ * Class nsJAR declaration.
+ * nsJAR serves as an XPCOM wrapper for nsZipArchive with the addition of
+ * JAR manifest file parsing.
+ *------------------------------------------------------------------------*/
+class nsJAR final : public nsIZipReader
+{
+ // Allows nsJARInputStream to call the verification functions
+ friend class nsJARInputStream;
+ // Allows nsZipReaderCache to access mOuterZipEntry
+ friend class nsZipReaderCache;
+
+ private:
+
+ virtual ~nsJAR();
+
+ public:
+
+ nsJAR();
+
+ NS_DEFINE_STATIC_CID_ACCESSOR( NS_ZIPREADER_CID )
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIZIPREADER
+
+ nsresult GetJarPath(nsACString& aResult);
+
+ PRIntervalTime GetReleaseTime() {
+ return mReleaseTime;
+ }
+
+ bool IsReleased() {
+ return mReleaseTime != PR_INTERVAL_NO_TIMEOUT;
+ }
+
+ void SetReleaseTime() {
+ mReleaseTime = PR_IntervalNow();
+ }
+
+ void ClearReleaseTime() {
+ mReleaseTime = PR_INTERVAL_NO_TIMEOUT;
+ }
+
+ void SetZipReaderCache(nsZipReaderCache* aCache) {
+ mozilla::MutexAutoLock lock(mLock);
+ mCache = aCache;
+ }
+
+ nsresult GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc);
+
+ protected:
+ typedef nsClassHashtable<nsCStringHashKey, nsJARManifestItem> ManifestDataHashtable;
+
+ //-- Private data members
+ nsCOMPtr<nsIFile> mZipFile; // The zip/jar file on disk
+ nsCString mOuterZipEntry; // The entry in the zip this zip is reading from
+ RefPtr<nsZipArchive> mZip; // The underlying zip archive
+ ManifestDataHashtable mManifestData; // Stores metadata for each entry
+ bool mParsedManifest; // True if manifest has been parsed
+ nsCOMPtr<nsIX509Cert> mSigningCert; // The entity which signed this file
+ int16_t mGlobalStatus; // Global signature verification status
+ PRIntervalTime mReleaseTime; // used by nsZipReaderCache for flushing entries
+ nsZipReaderCache* mCache; // if cached, this points to the cache it's contained in
+ mozilla::Mutex mLock; // protect mCache and mZip
+ int64_t mMtime;
+ int32_t mTotalItemsInManifest;
+ bool mOpened;
+ bool mIsOmnijar;
+
+ nsresult ParseManifest();
+ void ReportError(const nsACString &aFilename, int16_t errorCode);
+ nsresult LoadEntry(const nsACString &aFilename, char** aBuf,
+ uint32_t* aBufLen = nullptr);
+ int32_t ReadLine(const char** src);
+ nsresult ParseOneFile(const char* filebuf, int16_t aFileType);
+ nsresult VerifyEntry(nsJARManifestItem* aEntry, const char* aEntryData,
+ uint32_t aLen);
+
+ nsresult CalculateDigest(const char* aInBuf, uint32_t aInBufLen,
+ nsCString& digest);
+};
+
+/**
+ * nsJARItem
+ *
+ * An individual JAR entry. A set of nsJARItems matching a
+ * supplied pattern are returned in a nsJAREnumerator.
+ */
+class nsJARItem : public nsIZipEntry
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIZIPENTRY
+
+ explicit nsJARItem(nsZipItem* aZipItem);
+
+private:
+ virtual ~nsJARItem() {}
+
+ uint32_t mSize; /* size in original file */
+ uint32_t mRealsize; /* inflated size */
+ uint32_t mCrc32;
+ PRTime mLastModTime;
+ uint16_t mCompression;
+ uint32_t mPermissions;
+ bool mIsDirectory;
+ bool mIsSynthetic;
+};
+
+/**
+ * nsJAREnumerator
+ *
+ * Enumerates a list of files in a zip archive
+ * (based on a pattern match in its member nsZipFind).
+ */
+class nsJAREnumerator final : public nsIUTF8StringEnumerator
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ explicit nsJAREnumerator(nsZipFind *aFind)
+ : mFind(aFind), mName(nullptr), mNameLen(0) {
+ NS_ASSERTION(mFind, "nsJAREnumerator: Missing zipFind.");
+ }
+
+private:
+ nsZipFind *mFind;
+ const char* mName; // pointer to an name owned by mArchive -- DON'T delete
+ uint16_t mNameLen;
+
+ ~nsJAREnumerator() { delete mFind; }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#if defined(DEBUG_warren) || defined(DEBUG_jband)
+#define ZIP_CACHE_HIT_RATE
+#endif
+
+class nsZipReaderCache : public nsIZipReaderCache, public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIZIPREADERCACHE
+ NS_DECL_NSIOBSERVER
+
+ nsZipReaderCache();
+
+ nsresult ReleaseZip(nsJAR* reader);
+
+ typedef nsRefPtrHashtable<nsCStringHashKey, nsJAR> ZipsHashtable;
+
+protected:
+
+ virtual ~nsZipReaderCache();
+
+ mozilla::Mutex mLock;
+ uint32_t mCacheSize;
+ ZipsHashtable mZips;
+
+#ifdef ZIP_CACHE_HIT_RATE
+ uint32_t mZipCacheLookups;
+ uint32_t mZipCacheHits;
+ uint32_t mZipCacheFlushes;
+ uint32_t mZipSyncMisses;
+#endif
+
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif /* nsJAR_h_ */
diff --git a/components/jar/src/nsJARChannel.cpp b/components/jar/src/nsJARChannel.cpp
new file mode 100644
index 000000000..65c034779
--- /dev/null
+++ b/components/jar/src/nsJARChannel.cpp
@@ -0,0 +1,1087 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsJAR.h"
+#include "nsJARChannel.h"
+#include "nsJARProtocolHandler.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsEscape.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIViewSourceChannel.h"
+#include "nsContentUtils.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+
+#include "nsIScriptSecurityManager.h"
+#include "nsIPrincipal.h"
+#include "nsIFileURL.h"
+
+#include "mozilla/Preferences.h"
+#include "nsITabChild.h"
+#include "private/pprio.h"
+#include "nsInputStreamPump.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
+
+// the entry for a directory will either be empty (in the case of the
+// top-level directory) or will end with a slash
+#define ENTRY_IS_DIRECTORY(_entry) \
+ ((_entry).IsEmpty() || '/' == (_entry).Last())
+
+//-----------------------------------------------------------------------------
+
+// Ignore any LOG macro that we inherit from arbitrary headers. (We define our
+// own LOG macro below.)
+#ifdef LOG
+#undef LOG
+#endif
+
+//
+// set NSPR_LOG_MODULES=nsJarProtocol:5
+//
+static LazyLogModule gJarProtocolLog("nsJarProtocol");
+
+#define LOG(args) MOZ_LOG(gJarProtocolLog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(gJarProtocolLog, mozilla::LogLevel::Debug)
+
+//-----------------------------------------------------------------------------
+// nsJARInputThunk
+//
+// this class allows us to do some extra work on the stream transport thread.
+//-----------------------------------------------------------------------------
+
+class nsJARInputThunk : public nsIInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ nsJARInputThunk(nsIZipReader *zipReader,
+ nsIURI* fullJarURI,
+ const nsACString &jarEntry,
+ bool usingJarCache)
+ : mUsingJarCache(usingJarCache)
+ , mJarReader(zipReader)
+ , mJarEntry(jarEntry)
+ , mContentLength(-1)
+ {
+ if (fullJarURI) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ fullJarURI->GetAsciiSpec(mJarDirSpec);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "this shouldn't fail");
+ }
+ }
+
+ int64_t GetContentLength()
+ {
+ return mContentLength;
+ }
+
+ nsresult Init();
+
+private:
+
+ virtual ~nsJARInputThunk()
+ {
+ Close();
+ }
+
+ bool mUsingJarCache;
+ nsCOMPtr<nsIZipReader> mJarReader;
+ nsCString mJarDirSpec;
+ nsCOMPtr<nsIInputStream> mJarStream;
+ nsCString mJarEntry;
+ int64_t mContentLength;
+};
+
+NS_IMPL_ISUPPORTS(nsJARInputThunk, nsIInputStream)
+
+nsresult
+nsJARInputThunk::Init()
+{
+ nsresult rv;
+ if (ENTRY_IS_DIRECTORY(mJarEntry)) {
+ // A directory stream also needs the Spec of the FullJarURI
+ // because is included in the stream data itself.
+
+ NS_ENSURE_STATE(!mJarDirSpec.IsEmpty());
+
+ rv = mJarReader->GetInputStreamWithSpec(mJarDirSpec,
+ mJarEntry,
+ getter_AddRefs(mJarStream));
+ }
+ else {
+ rv = mJarReader->GetInputStream(mJarEntry,
+ getter_AddRefs(mJarStream));
+ }
+ if (NS_FAILED(rv)) {
+ // convert to the proper result if the entry wasn't found
+ // so that error pages work
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ return rv;
+ }
+
+ // ask the JarStream for the content length
+ uint64_t avail;
+ rv = mJarStream->Available((uint64_t *) &avail);
+ if (NS_FAILED(rv)) return rv;
+
+ mContentLength = avail < INT64_MAX ? (int64_t) avail : -1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARInputThunk::Close()
+{
+ nsresult rv = NS_OK;
+
+ if (mJarStream)
+ rv = mJarStream->Close();
+
+ if (!mUsingJarCache && mJarReader)
+ mJarReader->Close();
+
+ mJarReader = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARInputThunk::Available(uint64_t *avail)
+{
+ return mJarStream->Available(avail);
+}
+
+NS_IMETHODIMP
+nsJARInputThunk::Read(char *buf, uint32_t count, uint32_t *countRead)
+{
+ return mJarStream->Read(buf, count, countRead);
+}
+
+NS_IMETHODIMP
+nsJARInputThunk::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *countRead)
+{
+ // stream transport does only calls Read()
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsJARInputThunk::IsNonBlocking(bool *nonBlocking)
+{
+ *nonBlocking = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsJARChannel
+//-----------------------------------------------------------------------------
+
+
+nsJARChannel::nsJARChannel()
+ : mOpened(false)
+ , mContentDisposition(0)
+ , mContentLength(-1)
+ , mLoadFlags(LOAD_NORMAL)
+ , mStatus(NS_OK)
+ , mIsPending(false)
+ , mIsUnsafe(true)
+ , mBlockRemoteFiles(false)
+{
+ mBlockRemoteFiles = Preferences::GetBool("network.jar.block-remote-files", false);
+
+ // hold an owning reference to the jar handler
+ NS_ADDREF(gJarHandler);
+}
+
+nsJARChannel::~nsJARChannel()
+{
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+
+ // release owning reference to the jar handler
+ nsJARProtocolHandler *handler = gJarHandler;
+ NS_RELEASE(handler); // nullptr parameter
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel,
+ nsHashPropertyBag,
+ nsIRequest,
+ nsIChannel,
+ nsIStreamListener,
+ nsIRequestObserver,
+ nsIThreadRetargetableRequest,
+ nsIThreadRetargetableStreamListener,
+ nsIJARChannel)
+
+nsresult
+nsJARChannel::Init(nsIURI *uri)
+{
+ nsresult rv;
+ mJarURI = do_QueryInterface(uri, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mOriginalURI = mJarURI;
+
+ // Prevent loading jar:javascript URIs (see bug 290982).
+ nsCOMPtr<nsIURI> innerURI;
+ rv = mJarURI->GetJARFile(getter_AddRefs(innerURI));
+ if (NS_FAILED(rv))
+ return rv;
+ bool isJS;
+ rv = innerURI->SchemeIs("javascript", &isJS);
+ if (NS_FAILED(rv))
+ return rv;
+ if (isJS) {
+ NS_WARNING("blocking jar:javascript:");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mJarURI->GetSpec(mSpec);
+ return rv;
+}
+
+nsresult
+nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resultInput)
+{
+ MOZ_ASSERT(resultInput);
+ MOZ_ASSERT(mJarFile || mTempMem);
+
+ // important to pass a clone of the file since the nsIFile impl is not
+ // necessarily MT-safe
+ nsCOMPtr<nsIFile> clonedFile;
+ nsresult rv = NS_OK;
+ if (mJarFile) {
+ rv = mJarFile->Clone(getter_AddRefs(clonedFile));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ nsCOMPtr<nsIZipReader> reader;
+ if (jarCache) {
+ MOZ_ASSERT(mJarFile);
+ if (mInnerJarEntry.IsEmpty())
+ rv = jarCache->GetZip(clonedFile, getter_AddRefs(reader));
+ else
+ rv = jarCache->GetInnerZip(clonedFile, mInnerJarEntry,
+ getter_AddRefs(reader));
+ } else {
+ // create an uncached jar reader
+ nsCOMPtr<nsIZipReader> outerReader = do_CreateInstance(kZipReaderCID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mJarFile) {
+ rv = outerReader->Open(clonedFile);
+ } else {
+ rv = outerReader->OpenMemory(mTempMem->Elements(),
+ mTempMem->Length());
+ }
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (mInnerJarEntry.IsEmpty())
+ reader = outerReader;
+ else {
+ reader = do_CreateInstance(kZipReaderCID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = reader->OpenInner(outerReader, mInnerJarEntry);
+ }
+ }
+ if (NS_FAILED(rv))
+ return rv;
+
+ RefPtr<nsJARInputThunk> input = new nsJARInputThunk(reader,
+ mJarURI,
+ mJarEntry,
+ jarCache != nullptr
+ );
+ rv = input->Init();
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Make GetContentLength meaningful
+ mContentLength = input->GetContentLength();
+
+ input.forget(resultInput);
+ return NS_OK;
+}
+
+nsresult
+nsJARChannel::LookupFile(bool aAllowAsync)
+{
+ LOG(("nsJARChannel::LookupFile [this=%x %s]\n", this, mSpec.get()));
+
+ if (mJarFile)
+ return NS_OK;
+
+ nsresult rv;
+
+ rv = mJarURI->GetJARFile(getter_AddRefs(mJarBaseURI));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = mJarURI->GetJAREntry(mJarEntry);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // The name of the JAR entry must not contain URL-escaped characters:
+ // we're moving from URL domain to a filename domain here. nsStandardURL
+ // does basic escaping by default, which breaks reading zipped files which
+ // have e.g. spaces in their filenames.
+ NS_UnescapeURL(mJarEntry);
+
+ // try to get a nsIFile directly from the url, which will often succeed.
+ {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mJarBaseURI);
+ if (fileURL)
+ fileURL->GetFile(getter_AddRefs(mJarFile));
+ }
+
+ // try to handle a nested jar
+ if (!mJarFile) {
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(mJarBaseURI);
+ if (jarURI) {
+ nsCOMPtr<nsIFileURL> fileURL;
+ nsCOMPtr<nsIURI> innerJarURI;
+ rv = jarURI->GetJARFile(getter_AddRefs(innerJarURI));
+ if (NS_SUCCEEDED(rv))
+ fileURL = do_QueryInterface(innerJarURI);
+ if (fileURL) {
+ fileURL->GetFile(getter_AddRefs(mJarFile));
+ jarURI->GetJAREntry(mInnerJarEntry);
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsJARChannel::OpenLocalFile()
+{
+ MOZ_ASSERT(mIsPending);
+
+ // Local files are always considered safe.
+ mIsUnsafe = false;
+
+ RefPtr<nsJARInputThunk> input;
+ nsresult rv = CreateJarInput(gJarHandler->JarCache(),
+ getter_AddRefs(input));
+ if (NS_SUCCEEDED(rv)) {
+ // Create input stream pump and call AsyncRead as a block.
+ rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
+ if (NS_SUCCEEDED(rv))
+ rv = mPump->AsyncRead(this, nullptr);
+ }
+
+ return rv;
+}
+
+void
+nsJARChannel::NotifyError(nsresult aError)
+{
+ MOZ_ASSERT(NS_FAILED(aError));
+
+ mStatus = aError;
+
+ OnStartRequest(nullptr, nullptr);
+ OnStopRequest(nullptr, nullptr, aError);
+}
+
+void
+nsJARChannel::FireOnProgress(uint64_t aProgress)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mProgressSink);
+
+ mProgressSink->OnProgress(this, nullptr, aProgress, mContentLength);
+}
+
+//-----------------------------------------------------------------------------
+// nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsJARChannel::GetName(nsACString &result)
+{
+ return mJarURI->GetSpec(result);
+}
+
+NS_IMETHODIMP
+nsJARChannel::IsPending(bool *result)
+{
+ *result = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetStatus(nsresult *status)
+{
+ if (mPump && NS_SUCCEEDED(mStatus))
+ mPump->GetStatus(status);
+ else
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::Cancel(nsresult status)
+{
+ mStatus = status;
+ if (mPump)
+ return mPump->Cancel(status);
+
+ NS_ASSERTION(!mIsPending, "need to implement cancel when downloading");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::Suspend()
+{
+ if (mPump)
+ return mPump->Suspend();
+
+ NS_ASSERTION(!mIsPending, "need to implement suspend when downloading");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::Resume()
+{
+ if (mPump)
+ return mPump->Resume();
+
+ NS_ASSERTION(!mIsPending, "need to implement resume when downloading");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
+{
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
+{
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsJARChannel::GetOriginalURI(nsIURI **aURI)
+{
+ *aURI = mOriginalURI;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetOriginalURI(nsIURI *aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetURI(nsIURI **aURI)
+{
+ NS_IF_ADDREF(*aURI = mJarURI);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetOwner(nsISupports **aOwner)
+{
+ // JAR signatures are not processed to avoid main-thread network I/O (bug 726125)
+ *aOwner = mOwner;
+ NS_IF_ADDREF(*aOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetOwner(nsISupports *aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
+{
+ NS_IF_ADDREF(*aCallbacks = mCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
+{
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
+{
+ NS_PRECONDITION(aSecurityInfo, "Null out param");
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentType(nsACString &result)
+{
+ // If the Jar file has not been open yet,
+ // We return application/x-unknown-content-type
+ if (!mOpened) {
+ result.Assign(UNKNOWN_CONTENT_TYPE);
+ return NS_OK;
+ }
+
+ if (mContentType.IsEmpty()) {
+
+ //
+ // generate content type and set it
+ //
+ const char *ext = nullptr, *fileName = mJarEntry.get();
+ int32_t len = mJarEntry.Length();
+
+ // check if we're displaying a directory
+ // mJarEntry will be empty if we're trying to display
+ // the topmost directory in a zip, e.g. jar:foo.zip!/
+ if (ENTRY_IS_DIRECTORY(mJarEntry)) {
+ mContentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT);
+ }
+ else {
+ // not a directory, take a guess by its extension
+ for (int32_t i = len-1; i >= 0; i--) {
+ if (fileName[i] == '.') {
+ ext = &fileName[i + 1];
+ break;
+ }
+ }
+ if (ext) {
+ nsIMIMEService *mimeServ = gJarHandler->MimeService();
+ if (mimeServ)
+ mimeServ->GetTypeFromExtension(nsDependentCString(ext), mContentType);
+ }
+ if (mContentType.IsEmpty())
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ }
+ }
+ result = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetContentType(const nsACString &aContentType)
+{
+ // If someone gives us a type hint we should just use that type instead of
+ // doing our guessing. So we don't care when this is being called.
+
+ // mContentCharset is unchanged if not parsed
+ NS_ParseResponseContentType(aContentType, mContentType, mContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ // If someone gives us a charset hint we should just use that charset.
+ // So we don't care when this is being called.
+ aContentCharset = mContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ mContentCharset = aContentCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ if (mContentDispositionHeader.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *aContentDisposition = mContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ if (mContentDispositionHeader.IsEmpty())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ aContentDispositionHeader = mContentDispositionHeader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetContentLength(int64_t *result)
+{
+ *result = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::SetContentLength(int64_t aContentLength)
+{
+ // XXX does this really make any sense at all?
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::Open(nsIInputStream **stream)
+{
+ LOG(("nsJARChannel::Open [this=%x]\n", this));
+
+ NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+
+ mJarFile = nullptr;
+ mIsUnsafe = true;
+
+ nsresult rv = LookupFile(false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // If mJarInput was not set by LookupFile, the JAR is a remote jar.
+ if (!mJarFile) {
+ NS_NOTREACHED("need sync downloader");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<nsJARInputThunk> input;
+ rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input));
+ if (NS_FAILED(rv))
+ return rv;
+
+ input.forget(stream);
+ mOpened = true;
+ // local files are always considered safe
+ mIsUnsafe = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+NS_IMETHODIMP
+nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ LOG(("nsJARChannel::AsyncOpen [this=%x]\n", this));
+
+ NS_ENSURE_ARG_POINTER(listener);
+ NS_ENSURE_TRUE(!mOpened, NS_ERROR_IN_PROGRESS);
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+
+ mJarFile = nullptr;
+ mIsUnsafe = true;
+
+ // Initialize mProgressSink
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink);
+
+ mListener = listener;
+ mListenerContext = ctx;
+ mIsPending = true;
+
+ nsresult rv = LookupFile(true);
+ if (NS_FAILED(rv)) {
+ mIsPending = false;
+ mListenerContext = nullptr;
+ mListener = nullptr;
+ return rv;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+
+ if (!mJarFile) {
+ // Not a local file...
+
+ // Check preferences to see if all remote jar support should be disabled
+ if (mBlockRemoteFiles) {
+ mIsUnsafe = true;
+ return NS_ERROR_UNSAFE_CONTENT_TYPE;
+ }
+
+ // kick off an async download of the base URI...
+ nsCOMPtr<nsIStreamListener> downloader = new MemoryDownloader(this);
+ uint32_t loadFlags =
+ mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS);
+ rv = NS_NewChannelInternal(getter_AddRefs(channel),
+ mJarBaseURI,
+ mLoadInfo,
+ mLoadGroup,
+ mCallbacks,
+ loadFlags);
+ if (NS_FAILED(rv)) {
+ mIsPending = false;
+ mListenerContext = nullptr;
+ mListener = nullptr;
+ return rv;
+ }
+ if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
+ rv = channel->AsyncOpen2(downloader);
+ }
+ else {
+ rv = channel->AsyncOpen(downloader, nullptr);
+ }
+ }
+ else {
+ rv = OpenLocalFile();
+ }
+
+ if (NS_FAILED(rv)) {
+ mIsPending = false;
+ mListenerContext = nullptr;
+ mListener = nullptr;
+ return rv;
+ }
+
+ if (mLoadGroup)
+ mLoadGroup->AddRequest(this, nullptr);
+
+ mOpened = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+//-----------------------------------------------------------------------------
+// nsIJARChannel
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsJARChannel::GetIsUnsafe(bool *isUnsafe)
+{
+ *isUnsafe = mIsUnsafe;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetJarFile(nsIFile **aFile)
+{
+ NS_IF_ADDREF(*aFile = mJarFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::GetZipEntry(nsIZipEntry **aZipEntry)
+{
+ nsresult rv = LookupFile(false);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!mJarFile)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIZipReader> reader;
+ rv = gJarHandler->JarCache()->GetZip(mJarFile, getter_AddRefs(reader));
+ if (NS_FAILED(rv))
+ return rv;
+
+ return reader->GetEntry(mJarEntry, aZipEntry);
+}
+
+//-----------------------------------------------------------------------------
+// mozilla::net::MemoryDownloader::IObserver
+//-----------------------------------------------------------------------------
+
+void
+nsJARChannel::OnDownloadComplete(MemoryDownloader* aDownloader,
+ nsIRequest *request,
+ nsISupports *context,
+ nsresult status,
+ MemoryDownloader::Data aData)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
+ if (channel) {
+ uint32_t loadFlags;
+ channel->GetLoadFlags(&loadFlags);
+ if (loadFlags & LOAD_REPLACE) {
+ mLoadFlags |= LOAD_REPLACE;
+
+ if (!mOriginalURI) {
+ SetOriginalURI(mJarURI);
+ }
+
+ nsCOMPtr<nsIURI> innerURI;
+ rv = channel->GetURI(getter_AddRefs(innerURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIJARURI> newURI;
+ rv = mJarURI->CloneWithJARFile(innerURI,
+ getter_AddRefs(newURI));
+ if (NS_SUCCEEDED(rv)) {
+ mJarURI = newURI;
+ }
+ }
+ if (NS_SUCCEEDED(status)) {
+ status = rv;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(status) && channel) {
+ // Grab the security info from our base channel
+ channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ // We only want to run scripts if the server really intended to
+ // send us a JAR file. Check the server-supplied content type for
+ // a JAR type.
+ nsAutoCString header;
+ httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"),
+ header);
+ nsAutoCString contentType;
+ nsAutoCString charset;
+ NS_ParseResponseContentType(header, contentType, charset);
+ nsAutoCString channelContentType;
+ channel->GetContentType(channelContentType);
+ mIsUnsafe = !(contentType.Equals(channelContentType) &&
+ (contentType.EqualsLiteral("application/java-archive") ||
+ contentType.EqualsLiteral("application/x-jar")));
+ } else {
+ nsCOMPtr<nsIJARChannel> innerJARChannel(do_QueryInterface(channel));
+ if (innerJARChannel) {
+ mIsUnsafe = innerJARChannel->GetIsUnsafe();
+ }
+ }
+
+ channel->GetContentDispositionHeader(mContentDispositionHeader);
+ mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
+ }
+
+ // This is a defense-in-depth check for the preferences to see if all remote jar
+ // support should be disabled. This check may not be needed.
+ MOZ_RELEASE_ASSERT(!mBlockRemoteFiles);
+
+ if (NS_SUCCEEDED(status) && mIsUnsafe &&
+ !Preferences::GetBool("network.jar.open-unsafe-types", false)) {
+ status = NS_ERROR_UNSAFE_CONTENT_TYPE;
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ // Refuse to unpack view-source: jars even if open-unsafe-types is set.
+ nsCOMPtr<nsIViewSourceChannel> viewSource = do_QueryInterface(channel);
+ if (viewSource) {
+ status = NS_ERROR_UNSAFE_CONTENT_TYPE;
+ }
+ }
+
+ if (NS_SUCCEEDED(status)) {
+ mTempMem = Move(aData);
+
+ RefPtr<nsJARInputThunk> input;
+ rv = CreateJarInput(nullptr, getter_AddRefs(input));
+ if (NS_SUCCEEDED(rv)) {
+ // create input stream pump
+ rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
+ if (NS_SUCCEEDED(rv))
+ rv = mPump->AsyncRead(this, nullptr);
+ }
+ status = rv;
+ }
+
+ if (NS_FAILED(status)) {
+ NotifyError(status);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsJARChannel::OnStartRequest(nsIRequest *req, nsISupports *ctx)
+{
+ LOG(("nsJARChannel::OnStartRequest [this=%x %s]\n", this, mSpec.get()));
+
+ mRequest = req;
+ nsresult rv = mListener->OnStartRequest(this, mListenerContext);
+ mRequest = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARChannel::OnStopRequest(nsIRequest *req, nsISupports *ctx, nsresult status)
+{
+ LOG(("nsJARChannel::OnStopRequest [this=%x %s status=%x]\n",
+ this, mSpec.get(), status));
+
+ if (NS_SUCCEEDED(mStatus))
+ mStatus = status;
+
+ if (mListener) {
+ mListener->OnStopRequest(this, mListenerContext, status);
+ mListener = nullptr;
+ mListenerContext = nullptr;
+ }
+
+ if (mLoadGroup)
+ mLoadGroup->RemoveRequest(this, nullptr, status);
+
+ mPump = nullptr;
+ mIsPending = false;
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+
+ #if !defined(XP_WIN)
+ // To deallocate file descriptor by RemoteOpenFileChild destructor.
+ mJarFile = nullptr;
+ #endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARChannel::OnDataAvailable(nsIRequest *req, nsISupports *ctx,
+ nsIInputStream *stream,
+ uint64_t offset, uint32_t count)
+{
+ LOG(("nsJARChannel::OnDataAvailable [this=%x %s]\n", this, mSpec.get()));
+
+ nsresult rv;
+
+ rv = mListener->OnDataAvailable(this, mListenerContext, stream, offset, count);
+
+ // simply report progress here instead of hooking ourselves up as a
+ // nsITransportEventSink implementation.
+ // XXX do the 64-bit stuff for real
+ if (mProgressSink && NS_SUCCEEDED(rv)) {
+ if (NS_IsMainThread()) {
+ FireOnProgress(offset + count);
+ } else {
+ NS_DispatchToMainThread(NewRunnableMethod
+ <uint64_t>(this,
+ &nsJARChannel::FireOnProgress,
+ offset + count));
+ }
+ }
+
+ return rv; // let the pump cancel on failure
+}
+
+NS_IMETHODIMP
+nsJARChannel::RetargetDeliveryTo(nsIEventTarget* aEventTarget)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIThreadRetargetableRequest> request = do_QueryInterface(mRequest);
+ if (!request) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return request->RetargetDeliveryTo(aEventTarget);
+}
+
+NS_IMETHODIMP
+nsJARChannel::CheckListenerChain()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
+ do_QueryInterface(mListener);
+ if (!listener) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return listener->CheckListenerChain();
+}
diff --git a/components/jar/src/nsJARChannel.h b/components/jar/src/nsJARChannel.h
new file mode 100644
index 000000000..5328d586a
--- /dev/null
+++ b/components/jar/src/nsJARChannel.h
@@ -0,0 +1,109 @@
+/* -*- 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/. */
+
+#ifndef nsJARChannel_h__
+#define nsJARChannel_h__
+
+#include "mozilla/net/MemoryDownloader.h"
+#include "nsIJARChannel.h"
+#include "nsIJARURI.h"
+#include "nsIInputStreamPump.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIProgressEventSink.h"
+#include "nsIStreamListener.h"
+#include "nsIZipReader.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsHashPropertyBag.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/Logging.h"
+
+class nsJARInputThunk;
+class nsInputStreamPump;
+
+//-----------------------------------------------------------------------------
+
+class nsJARChannel final : public nsIJARChannel
+ , public mozilla::net::MemoryDownloader::IObserver
+ , public nsIStreamListener
+ , public nsIThreadRetargetableRequest
+ , public nsIThreadRetargetableStreamListener
+ , public nsHashPropertyBag
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIJARCHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsJARChannel();
+
+ nsresult Init(nsIURI *uri);
+
+private:
+ virtual ~nsJARChannel();
+
+ nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **);
+ nsresult LookupFile(bool aAllowAsync);
+ nsresult OpenLocalFile();
+ void NotifyError(nsresult aError);
+ void FireOnProgress(uint64_t aProgress);
+ virtual void OnDownloadComplete(mozilla::net::MemoryDownloader* aDownloader,
+ nsIRequest* aRequest,
+ nsISupports* aCtxt,
+ nsresult aStatus,
+ mozilla::net::MemoryDownloader::Data aData)
+ override;
+
+ nsCString mSpec;
+
+ bool mOpened;
+
+ nsCOMPtr<nsIJARURI> mJarURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsISupports> mListenerContext;
+ nsCString mContentType;
+ nsCString mContentCharset;
+ nsCString mContentDispositionHeader;
+ /* mContentDisposition is uninitialized if mContentDispositionHeader is
+ * empty */
+ uint32_t mContentDisposition;
+ int64_t mContentLength;
+ uint32_t mLoadFlags;
+ nsresult mStatus;
+ bool mIsPending;
+ bool mIsUnsafe;
+
+ mozilla::net::MemoryDownloader::Data mTempMem;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ // mRequest is only non-null during OnStartRequest, so we'll have a pointer
+ // to the request if we get called back via RetargetDeliveryTo.
+ nsCOMPtr<nsIRequest> mRequest;
+ nsCOMPtr<nsIFile> mJarFile;
+ nsCOMPtr<nsIURI> mJarBaseURI;
+ nsCString mJarEntry;
+ nsCString mInnerJarEntry;
+
+ // True if this channel should not download any remote files.
+ bool mBlockRemoteFiles;
+};
+
+#endif // nsJARChannel_h__
diff --git a/components/jar/src/nsJARFactory.cpp b/components/jar/src/nsJARFactory.cpp
new file mode 100644
index 000000000..01d8d73f3
--- /dev/null
+++ b/components/jar/src/nsJARFactory.cpp
@@ -0,0 +1,64 @@
+/* -*- 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 <string.h>
+
+#include "nscore.h"
+
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsIJARFactory.h"
+#include "nsJARProtocolHandler.h"
+#include "nsJARURI.h"
+#include "nsJAR.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsJAR)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsZipReaderCache)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsJARProtocolHandler,
+ nsJARProtocolHandler::GetSingleton)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsJARURI)
+
+NS_DEFINE_NAMED_CID(NS_ZIPREADER_CID);
+NS_DEFINE_NAMED_CID(NS_ZIPREADERCACHE_CID);
+NS_DEFINE_NAMED_CID(NS_JARPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_JARURI_CID);
+
+static const mozilla::Module::CIDEntry kJARCIDs[] = {
+ { &kNS_ZIPREADER_CID, false, nullptr, nsJARConstructor },
+ { &kNS_ZIPREADERCACHE_CID, false, nullptr, nsZipReaderCacheConstructor },
+ { &kNS_JARPROTOCOLHANDLER_CID, false, nullptr, nsJARProtocolHandlerConstructor },
+ { &kNS_JARURI_CID, false, nullptr, nsJARURIConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kJARContracts[] = {
+ { "@mozilla.org/libjar/zip-reader;1", &kNS_ZIPREADER_CID },
+ { "@mozilla.org/libjar/zip-reader-cache;1", &kNS_ZIPREADERCACHE_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "jar", &kNS_JARPROTOCOLHANDLER_CID },
+ { nullptr }
+};
+
+// Jar module shutdown hook
+static void nsJarShutdown()
+{
+ // Make sure to not null out gJarHandler here, because we may have
+ // still-live nsJARChannels that will want to release it.
+ nsJARProtocolHandler *handler = gJarHandler;
+ NS_IF_RELEASE(handler);
+}
+
+static const mozilla::Module kJARModule = {
+ mozilla::Module::kVersion,
+ kJARCIDs,
+ kJARContracts,
+ nullptr,
+ nullptr,
+ nullptr,
+ nsJarShutdown
+};
+
+NSMODULE_DEFN(nsJarModule) = &kJARModule;
diff --git a/components/jar/src/nsJARInputStream.cpp b/components/jar/src/nsJARInputStream.cpp
new file mode 100644
index 000000000..a1f6c1d56
--- /dev/null
+++ b/components/jar/src/nsJARInputStream.cpp
@@ -0,0 +1,416 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* nsJARInputStream.cpp
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsJARInputStream.h"
+#include "zipstruct.h" // defines ZIP compression codes
+#include "brotli/decode.h"
+#include "nsZipArchive.h"
+
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsDebug.h"
+#include <algorithm>
+#if defined(XP_WIN)
+#include <windows.h>
+#endif
+
+/*---------------------------------------------
+ * nsISupports implementation
+ *--------------------------------------------*/
+
+NS_IMPL_ISUPPORTS(nsJARInputStream, nsIInputStream)
+
+/*----------------------------------------------------------
+ * nsJARInputStream implementation
+ *--------------------------------------------------------*/
+
+nsresult
+nsJARInputStream::InitFile(nsJAR *aJar, nsZipItem *item)
+{
+ nsresult rv = NS_OK;
+ MOZ_ASSERT(aJar, "Argument may not be null");
+ MOZ_ASSERT(item, "Argument may not be null");
+
+ // Mark it as closed, in case something fails in initialisation
+ mMode = MODE_CLOSED;
+ //-- prepare for the compression type
+ switch (item->Compression()) {
+ case STORED:
+ mMode = MODE_COPY;
+ break;
+
+ case DEFLATED:
+ rv = gZlibInit(&mZs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mMode = MODE_INFLATE;
+ mInCrc = item->CRC32();
+ mOutCrc = crc32(0L, Z_NULL, 0);
+ break;
+
+ case MOZ_JAR_BROTLI:
+ mBrotliState = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
+ mMode = MODE_BROTLI;
+ mInCrc = item->CRC32();
+ mOutCrc = crc32(0L, Z_NULL, 0);
+ break;
+
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // Must keep handle to filepointer and mmap structure as long as we need access to the mmapped data
+ mFd = aJar->mZip->GetFD();
+ mZs.next_in = (Bytef *)aJar->mZip->GetData(item);
+ if (!mZs.next_in) {
+ nsZipArchive::sFileCorruptedReason = "nsJARInputStream: !mZs.next_in";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ mZs.avail_in = item->Size();
+ mOutSize = item->RealSize();
+ mZs.total_out = 0;
+ return NS_OK;
+}
+
+nsresult
+nsJARInputStream::InitDirectory(nsJAR* aJar,
+ const nsACString& aJarDirSpec,
+ const char* aDir)
+{
+ MOZ_ASSERT(aJar, "Argument may not be null");
+ MOZ_ASSERT(aDir, "Argument may not be null");
+
+ // Mark it as closed, in case something fails in initialisation
+ mMode = MODE_CLOSED;
+
+ // Keep the zipReader for getting the actual zipItems
+ mJar = aJar;
+ nsZipFind *find;
+ nsresult rv;
+ // We can get aDir's contents as strings via FindEntries
+ // with the following pattern (see nsIZipReader.findEntries docs)
+ // assuming dirName is properly escaped:
+ //
+ // dirName + "?*~" + dirName + "?*/?*"
+ nsDependentCString dirName(aDir);
+ mNameLen = dirName.Length();
+
+ // iterate through dirName and copy it to escDirName, escaping chars
+ // which are special at the "top" level of the regexp so FindEntries
+ // works correctly
+ nsAutoCString escDirName;
+ const char* curr = dirName.BeginReading();
+ const char* end = dirName.EndReading();
+ while (curr != end) {
+ switch (*curr) {
+ case '*':
+ case '?':
+ case '$':
+ case '[':
+ case ']':
+ case '^':
+ case '~':
+ case '(':
+ case ')':
+ case '\\':
+ escDirName.Append('\\');
+ MOZ_FALLTHROUGH;
+ default:
+ escDirName.Append(*curr);
+ }
+ ++curr;
+ }
+ nsAutoCString pattern = escDirName + NS_LITERAL_CSTRING("?*~") +
+ escDirName + NS_LITERAL_CSTRING("?*/?*");
+ rv = mJar->mZip->FindInit(pattern.get(), &find);
+ if (NS_FAILED(rv)) return rv;
+
+ const char *name;
+ uint16_t nameLen;
+ while ((rv = find->FindNext( &name, &nameLen )) == NS_OK) {
+ // Must copy, to make it zero-terminated
+ mArray.AppendElement(nsCString(name,nameLen));
+ }
+ delete find;
+
+ if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE; // no error translation
+ }
+
+ // Sort it
+ mArray.Sort();
+
+ mBuffer.AssignLiteral("300: ");
+ mBuffer.Append(aJarDirSpec);
+ mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n");
+
+ // Open for reading
+ mMode = MODE_DIRECTORY;
+ mZs.total_out = 0;
+ mArrPos = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARInputStream::Available(uint64_t *_retval)
+{
+ // A lot of callers don't check the error code.
+ // They just use the _retval value.
+ *_retval = 0;
+
+ switch (mMode) {
+ case MODE_NOTINITED:
+ break;
+
+ case MODE_CLOSED:
+ return NS_BASE_STREAM_CLOSED;
+
+ case MODE_DIRECTORY:
+ *_retval = mBuffer.Length();
+ break;
+
+ case MODE_INFLATE:
+ case MODE_BROTLI:
+ case MODE_COPY:
+ *_retval = mOutSize - mZs.total_out;
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead)
+{
+ NS_ENSURE_ARG_POINTER(aBuffer);
+ NS_ENSURE_ARG_POINTER(aBytesRead);
+
+ *aBytesRead = 0;
+
+ nsresult rv = NS_OK;
+MOZ_WIN_MEM_TRY_BEGIN
+ switch (mMode) {
+ case MODE_NOTINITED:
+ return NS_OK;
+
+ case MODE_CLOSED:
+ return NS_BASE_STREAM_CLOSED;
+
+ case MODE_DIRECTORY:
+ return ReadDirectory(aBuffer, aCount, aBytesRead);
+
+ case MODE_INFLATE:
+ case MODE_BROTLI:
+ if (mZs.total_out < mOutSize) {
+ rv = ContinueInflate(aBuffer, aCount, aBytesRead);
+ }
+ // be aggressive about releasing the file!
+ // note that sometimes, we will release mFd before we've finished
+ // deflating - this is because zlib buffers the input
+ if (mZs.avail_in == 0) {
+ mFd = nullptr;
+ }
+ break;
+
+ case MODE_COPY:
+ if (mFd) {
+ uint32_t count = std::min(aCount, mOutSize - uint32_t(mZs.total_out));
+ if (count) {
+ memcpy(aBuffer, mZs.next_in + mZs.total_out, count);
+ mZs.total_out += count;
+ }
+ *aBytesRead = count;
+ }
+ // be aggressive about releasing the file!
+ // note that sometimes, we will release mFd before we've finished copying.
+ if (mZs.total_out >= mOutSize) {
+ mFd = nullptr;
+ }
+ break;
+ }
+MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE)
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval)
+{
+ // don't have a buffer to read from, so this better not be called!
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsJARInputStream::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARInputStream::Close()
+{
+ if (mMode == MODE_INFLATE) {
+ inflateEnd(&mZs);
+ }
+ if (mMode == MODE_BROTLI) {
+ BrotliDecoderDestroyInstance(mBrotliState);
+ }
+ mMode = MODE_CLOSED;
+ mFd = nullptr;
+ return NS_OK;
+}
+
+nsresult
+nsJARInputStream::ContinueInflate(char* aBuffer, uint32_t aCount,
+ uint32_t* aBytesRead)
+{
+ bool finished = false;
+
+ // No need to check the args, ::Read did that, but assert them at least
+ NS_ASSERTION(aBuffer,"aBuffer parameter must not be null");
+ NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null");
+
+ // Keep old total_out count
+ const uint32_t oldTotalOut = mZs.total_out;
+
+ // make sure we aren't reading too much
+ mZs.avail_out = std::min(aCount, (mOutSize-oldTotalOut));
+ mZs.next_out = (unsigned char*)aBuffer;
+
+ if (mMode == MODE_INFLATE) {
+ // now inflate
+ int zerr = inflate(&mZs, Z_SYNC_FLUSH);
+ if ((zerr != Z_OK) && (zerr != Z_STREAM_END)) {
+ nsZipArchive::sFileCorruptedReason = "nsJARInputStream: error while inflating";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ finished = (zerr == Z_STREAM_END);
+ } else {
+ MOZ_ASSERT(mMode == MODE_BROTLI);
+ /* The brotli library wants size_t, but z_stream only contains
+ * unsigned int for avail_* and unsigned long for total_*.
+ * So use temporary stack values. */
+ size_t avail_in = mZs.avail_in;
+ size_t avail_out = mZs.avail_out;
+ size_t total_out = mZs.total_out;
+ BrotliDecoderResult result = BrotliDecoderDecompressStream(
+ mBrotliState,
+ &avail_in, const_cast<const unsigned char**>(&mZs.next_in),
+ &avail_out, &mZs.next_out, &total_out);
+ /* We don't need to update avail_out, it's not used outside this
+ * function. */
+ mZs.total_out = total_out;
+ mZs.avail_in = avail_in;
+ if (result == BROTLI_DECODER_RESULT_ERROR) {
+ nsZipArchive::sFileCorruptedReason = "nsJARInputStream: brotli decompression error";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ finished = (result == BROTLI_DECODER_RESULT_SUCCESS);
+ }
+
+ *aBytesRead = (mZs.total_out - oldTotalOut);
+
+ // Calculate the CRC on the output
+ mOutCrc = crc32(mOutCrc, (unsigned char*)aBuffer, *aBytesRead);
+
+ // be aggressive about ending the inflation
+ // for some reason we don't always get Z_STREAM_END
+ if (finished || mZs.total_out == mOutSize) {
+ if (mMode == MODE_INFLATE) {
+ inflateEnd(&mZs);
+ }
+
+ // stop returning valid data as soon as we know we have a bad CRC
+ if (mOutCrc != mInCrc) {
+ nsZipArchive::sFileCorruptedReason = "nsJARInputStream: crc mismatch";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsJARInputStream::ReadDirectory(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead)
+{
+ // No need to check the args, ::Read did that, but assert them at least
+ NS_ASSERTION(aBuffer,"aBuffer parameter must not be null");
+ NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null");
+
+ // If the buffer contains data, copy what's there up to the desired amount
+ uint32_t numRead = CopyDataToBuffer(aBuffer, aCount);
+
+ if (aCount > 0) {
+ // empty the buffer and start writing directory entry lines to it
+ mBuffer.Truncate();
+ mCurPos = 0;
+ const uint32_t arrayLen = mArray.Length();
+
+ for ( ;aCount > mBuffer.Length(); mArrPos++) {
+ // have we consumed all the directory contents?
+ if (arrayLen <= mArrPos)
+ break;
+
+ const char * entryName = mArray[mArrPos].get();
+ uint32_t entryNameLen = mArray[mArrPos].Length();
+ nsZipItem* ze = mJar->mZip->GetItem(entryName);
+ NS_ENSURE_TRUE(ze, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST);
+
+ // Last Modified Time
+ PRExplodedTime tm;
+ PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm);
+ char itemLastModTime[65];
+ PR_FormatTimeUSEnglish(itemLastModTime,
+ sizeof(itemLastModTime),
+ " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ",
+ &tm);
+
+ // write a 201: line to the buffer for this item
+ // 200: filename content-length last-modified file-type
+ mBuffer.AppendLiteral("201: ");
+
+ // Names must be escaped and relative, so use the pre-calculated length
+ // of the directory name as the offset into the string
+ // NS_EscapeURL adds the escaped URL to the give string buffer
+ NS_EscapeURL(entryName + mNameLen,
+ entryNameLen - mNameLen,
+ esc_Minimal | esc_AlwaysCopy,
+ mBuffer);
+
+ mBuffer.Append(' ');
+ mBuffer.AppendInt(ze->RealSize(), 10);
+ mBuffer.Append(itemLastModTime); // starts/ends with ' '
+ if (ze->IsDirectory())
+ mBuffer.AppendLiteral("DIRECTORY\n");
+ else
+ mBuffer.AppendLiteral("FILE\n");
+ }
+
+ // Copy up to the desired amount of data to buffer
+ numRead += CopyDataToBuffer(aBuffer, aCount);
+ }
+
+ *aBytesRead = numRead;
+ return NS_OK;
+}
+
+uint32_t
+nsJARInputStream::CopyDataToBuffer(char* &aBuffer, uint32_t &aCount)
+{
+ const uint32_t writeLength = std::min(aCount, mBuffer.Length() - mCurPos);
+
+ if (writeLength > 0) {
+ memcpy(aBuffer, mBuffer.get() + mCurPos, writeLength);
+ mCurPos += writeLength;
+ aCount -= writeLength;
+ aBuffer += writeLength;
+ }
+
+ // return number of bytes copied to the buffer so the
+ // Read method can return the number of bytes copied
+ return writeLength;
+}
diff --git a/components/jar/src/nsJARInputStream.h b/components/jar/src/nsJARInputStream.h
new file mode 100644
index 000000000..60c3d763e
--- /dev/null
+++ b/components/jar/src/nsJARInputStream.h
@@ -0,0 +1,83 @@
+/* nsJARInputStream.h
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsJARINPUTSTREAM_h__
+#define nsJARINPUTSTREAM_h__
+
+#include "nsIInputStream.h"
+#include "nsJAR.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+struct BrotliDecoderStateStruct;
+
+/*-------------------------------------------------------------------------
+ * Class nsJARInputStream declaration. This class defines the type of the
+ * object returned by calls to nsJAR::GetInputStream(filename) for the
+ * purpose of reading a file item out of a JAR file.
+ *------------------------------------------------------------------------*/
+class nsJARInputStream final : public nsIInputStream
+{
+ public:
+ nsJARInputStream()
+ : mOutSize(0)
+ , mInCrc(0)
+ , mOutCrc(0)
+ , mBrotliState(nullptr)
+ , mNameLen(0)
+ , mCurPos(0)
+ , mArrPos(0)
+ , mMode(MODE_NOTINITED)
+ {
+ memset(&mZs, 0, sizeof(z_stream));
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ // takes ownership of |fd|, even on failure
+ nsresult InitFile(nsJAR *aJar, nsZipItem *item);
+
+ nsresult InitDirectory(nsJAR *aJar,
+ const nsACString& aJarDirSpec,
+ const char* aDir);
+
+ private:
+ ~nsJARInputStream() { Close(); }
+
+ RefPtr<nsZipHandle> mFd; // handle for reading
+ uint32_t mOutSize; // inflated size
+ uint32_t mInCrc; // CRC as provided by the zipentry
+ uint32_t mOutCrc; // CRC as calculated by me
+ z_stream mZs; // zip data structure
+ BrotliDecoderStateStruct* mBrotliState; // Brotli decoder state
+
+ /* For directory reading */
+ RefPtr<nsJAR> mJar; // string reference to zipreader
+ uint32_t mNameLen; // length of dirname
+ nsCString mBuffer; // storage for generated text of stream
+ uint32_t mCurPos; // Current position in buffer
+ uint32_t mArrPos; // current position within mArray
+ nsTArray<nsCString> mArray; // array of names in (zip) directory
+
+ typedef enum {
+ MODE_NOTINITED,
+ MODE_CLOSED,
+ MODE_DIRECTORY,
+ MODE_INFLATE,
+ MODE_BROTLI,
+ MODE_COPY
+ } JISMode;
+
+ JISMode mMode; // Modus of the stream
+
+ nsresult ContinueInflate(char* aBuf, uint32_t aCount, uint32_t* aBytesRead);
+ nsresult ReadDirectory(char* aBuf, uint32_t aCount, uint32_t* aBytesRead);
+ uint32_t CopyDataToBuffer(char* &aBuffer, uint32_t &aCount);
+};
+
+#endif /* nsJARINPUTSTREAM_h__ */
+
diff --git a/components/jar/src/nsJARProtocolHandler.cpp b/components/jar/src/nsJARProtocolHandler.cpp
new file mode 100644
index 000000000..38de73f14
--- /dev/null
+++ b/components/jar/src/nsJARProtocolHandler.cpp
@@ -0,0 +1,188 @@
+/* -*- 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 "nsAutoPtr.h"
+#include "nsJARProtocolHandler.h"
+#include "nsIIOService.h"
+#include "nsCRT.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsJARURI.h"
+#include "nsIURL.h"
+#include "nsJARChannel.h"
+#include "nsXPIDLString.h"
+#include "nsString.h"
+#include "nsNetCID.h"
+#include "nsIMIMEService.h"
+#include "nsMimeTypes.h"
+#include "nsThreadUtils.h"
+
+static NS_DEFINE_CID(kZipReaderCacheCID, NS_ZIPREADERCACHE_CID);
+
+#define NS_JAR_CACHE_SIZE 32
+
+//-----------------------------------------------------------------------------
+
+nsJARProtocolHandler *gJarHandler = nullptr;
+
+nsJARProtocolHandler::nsJARProtocolHandler()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+nsJARProtocolHandler::~nsJARProtocolHandler()
+{
+ MOZ_ASSERT(gJarHandler == this);
+ gJarHandler = nullptr;
+}
+
+nsresult
+nsJARProtocolHandler::Init()
+{
+ nsresult rv;
+
+ mJARCache = do_CreateInstance(kZipReaderCacheCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mJARCache->Init(NS_JAR_CACHE_SIZE);
+ return rv;
+}
+
+nsIMIMEService *
+nsJARProtocolHandler::MimeService()
+{
+ if (!mMimeService)
+ mMimeService = do_GetService("@mozilla.org/mime;1");
+
+ return mMimeService.get();
+}
+
+NS_IMPL_ISUPPORTS(nsJARProtocolHandler,
+ nsIJARProtocolHandler,
+ nsIProtocolHandler,
+ nsISupportsWeakReference)
+
+nsJARProtocolHandler*
+nsJARProtocolHandler::GetSingleton()
+{
+ if (!gJarHandler) {
+ gJarHandler = new nsJARProtocolHandler();
+ if (!gJarHandler)
+ return nullptr;
+
+ NS_ADDREF(gJarHandler);
+ nsresult rv = gJarHandler->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(gJarHandler);
+ return nullptr;
+ }
+ }
+ NS_ADDREF(gJarHandler);
+ return gJarHandler;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::GetJARCache(nsIZipReaderCache* *result)
+{
+ *result = mJARCache;
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsJARProtocolHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral("jar");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ *result = -1; // no port for JAR: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ // URI_LOADABLE_BY_ANYONE, since it's our inner URI that will matter
+ // anyway.
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_ANYONE;
+ /* Although jar uris have their own concept of relative urls
+ it is very different from the standard behaviour, so we
+ have to say norelative here! */
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+ nsresult rv = NS_OK;
+
+ RefPtr<nsJARURI> jarURI = new nsJARURI();
+ if (!jarURI)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = jarURI->Init(aCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = jarURI->SetSpecWithBase(aSpec, aBaseURI);
+ if (NS_FAILED(rv))
+ return rv;
+
+ NS_ADDREF(*result = jarURI);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::NewChannel2(nsIURI* uri,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result)
+{
+ nsJARChannel *chan = new nsJARChannel();
+ if (!chan)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(chan);
+
+ nsresult rv = chan->Init(uri);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(chan);
+ return rv;
+ }
+
+ // set the loadInfo on the new channel
+ rv = chan->SetLoadInfo(aLoadInfo);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(chan);
+ return rv;
+ }
+
+ *result = chan;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARProtocolHandler::NewChannel(nsIURI *uri, nsIChannel **result)
+{
+ return NewChannel2(uri, nullptr, result);
+}
+
+
+NS_IMETHODIMP
+nsJARProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/components/jar/src/nsJARProtocolHandler.h b/components/jar/src/nsJARProtocolHandler.h
new file mode 100644
index 000000000..62a4f7ac2
--- /dev/null
+++ b/components/jar/src/nsJARProtocolHandler.h
@@ -0,0 +1,52 @@
+/* -*- 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/. */
+
+#ifndef nsJARProtocolHandler_h__
+#define nsJARProtocolHandler_h__
+
+#include "nsIJARProtocolHandler.h"
+#include "nsIProtocolHandler.h"
+#include "nsIJARURI.h"
+#include "nsIZipReader.h"
+#include "nsIMIMEService.h"
+#include "nsWeakReference.h"
+#include "nsCOMPtr.h"
+
+class nsJARProtocolHandler final : public nsIJARProtocolHandler
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIJARPROTOCOLHANDLER
+
+ // nsJARProtocolHandler methods:
+ nsJARProtocolHandler();
+
+ static nsJARProtocolHandler *GetSingleton();
+
+ nsresult Init();
+
+ // returns non addref'ed pointer.
+ nsIMIMEService *MimeService();
+ nsIZipReaderCache *JarCache() { return mJARCache; }
+protected:
+ virtual ~nsJARProtocolHandler();
+
+ nsCOMPtr<nsIZipReaderCache> mJARCache;
+ nsCOMPtr<nsIMIMEService> mMimeService;
+};
+
+extern nsJARProtocolHandler *gJarHandler;
+
+#define NS_JARPROTOCOLHANDLER_CID \
+{ /* 0xc7e410d4-0x85f2-11d3-9f63-006008a6efe9 */ \
+ 0xc7e410d4, \
+ 0x85f2, \
+ 0x11d3, \
+ {0x9f, 0x63, 0x00, 0x60, 0x08, 0xa6, 0xef, 0xe9} \
+}
+
+#endif // !nsJARProtocolHandler_h__
diff --git a/components/jar/src/nsJARURI.cpp b/components/jar/src/nsJARURI.cpp
new file mode 100644
index 000000000..d1e4b5a59
--- /dev/null
+++ b/components/jar/src/nsJARURI.cpp
@@ -0,0 +1,912 @@
+/* -*- 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 "base/basictypes.h"
+
+#include "nsJARURI.h"
+#include "nsNetUtil.h"
+#include "nsIIOService.h"
+#include "nsIStandardURL.h"
+#include "nsCRT.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsIZipReader.h"
+#include "nsReadableUtils.h"
+#include "nsAutoPtr.h"
+#include "nsNetCID.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "mozilla/ipc/URIUtils.h"
+
+using namespace mozilla::ipc;
+
+static NS_DEFINE_CID(kJARURICID, NS_JARURI_CID);
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsJARURI::nsJARURI()
+{
+}
+
+nsJARURI::~nsJARURI()
+{
+}
+
+// XXX Why is this threadsafe?
+NS_IMPL_ADDREF(nsJARURI)
+NS_IMPL_RELEASE(nsJARURI)
+NS_INTERFACE_MAP_BEGIN(nsJARURI)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJARURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY(nsIJARURI)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
+ NS_INTERFACE_MAP_ENTRY(nsINestedURI)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableURI)
+ // see nsJARURI::Equals
+ if (aIID.Equals(NS_GET_IID(nsJARURI)))
+ foundInterface = reinterpret_cast<nsISupports*>(this);
+ else
+NS_INTERFACE_MAP_END
+
+nsresult
+nsJARURI::Init(const char *charsetHint)
+{
+ mCharsetHint = charsetHint;
+ return NS_OK;
+}
+
+#define NS_JAR_SCHEME NS_LITERAL_CSTRING("jar:")
+#define NS_JAR_DELIMITER NS_LITERAL_CSTRING("!/")
+#define NS_BOGUS_ENTRY_SCHEME NS_LITERAL_CSTRING("x:///")
+
+// FormatSpec takes the entry spec (including the "x:///" at the
+// beginning) and gives us a full JAR spec.
+nsresult
+nsJARURI::FormatSpec(const nsACString &entrySpec, nsACString &result,
+ bool aIncludeScheme)
+{
+ // The entrySpec MUST start with "x:///"
+ NS_ASSERTION(StringBeginsWith(entrySpec, NS_BOGUS_ENTRY_SCHEME),
+ "bogus entry spec");
+
+ nsAutoCString fileSpec;
+ nsresult rv = mJARFile->GetSpec(fileSpec);
+ if (NS_FAILED(rv)) return rv;
+
+ if (aIncludeScheme)
+ result = NS_JAR_SCHEME;
+ else
+ result.Truncate();
+
+ result.Append(fileSpec + NS_JAR_DELIMITER +
+ Substring(entrySpec, 5, entrySpec.Length() - 5));
+ return NS_OK;
+}
+
+nsresult
+nsJARURI::CreateEntryURL(const nsACString& entryFilename,
+ const char* charset,
+ nsIURL** url)
+{
+ *url = nullptr;
+
+ nsCOMPtr<nsIStandardURL> stdURL(do_CreateInstance(NS_STANDARDURL_CONTRACTID));
+ if (!stdURL) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Flatten the concatenation, just in case. See bug 128288
+ nsAutoCString spec(NS_BOGUS_ENTRY_SCHEME + entryFilename);
+ nsresult rv = stdURL->Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1,
+ spec, charset, nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return CallQueryInterface(stdURL, url);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsISerializable methods:
+
+NS_IMETHODIMP
+nsJARURI::Read(nsIObjectInputStream* aInputStream)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsISupports> supports;
+ rv = aInputStream->ReadObject(true, getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mJARFile = do_QueryInterface(supports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aInputStream->ReadObject(true, getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mJAREntry = do_QueryInterface(supports);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aInputStream->ReadCString(mCharsetHint);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARURI::Write(nsIObjectOutputStream* aOutputStream)
+{
+ nsresult rv;
+
+ rv = aOutputStream->WriteCompoundObject(mJARFile, NS_GET_IID(nsIURI),
+ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aOutputStream->WriteCompoundObject(mJAREntry, NS_GET_IID(nsIURL),
+ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aOutputStream->WriteStringZ(mCharsetHint.get());
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIClassInfo methods:
+
+NS_IMETHODIMP
+nsJARURI::GetInterfaces(uint32_t *count, nsIID * **array)
+{
+ *count = 0;
+ *array = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetScriptableHelper(nsIXPCScriptable **_retval)
+{
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetContractID(char * *aContractID)
+{
+ *aContractID = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetClassDescription(char * *aClassDescription)
+{
+ *aClassDescription = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetClassID(nsCID * *aClassID)
+{
+ *aClassID = (nsCID*) moz_xmalloc(sizeof(nsCID));
+ if (!*aClassID)
+ return NS_ERROR_OUT_OF_MEMORY;
+ return GetClassIDNoAlloc(*aClassID);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetFlags(uint32_t *aFlags)
+{
+ // XXX We implement THREADSAFE addref/release, but probably shouldn't.
+ *aFlags = nsIClassInfo::MAIN_THREAD_ONLY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
+{
+ *aClassIDNoAlloc = kJARURICID;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIURI methods:
+
+NS_IMETHODIMP
+nsJARURI::GetSpec(nsACString &aSpec)
+{
+ nsAutoCString entrySpec;
+ mJAREntry->GetSpec(entrySpec);
+ return FormatSpec(entrySpec, aSpec);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetSpecIgnoringRef(nsACString &aSpec)
+{
+ nsAutoCString entrySpec;
+ mJAREntry->GetSpecIgnoringRef(entrySpec);
+ return FormatSpec(entrySpec, aSpec);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetHasRef(bool *result)
+{
+ return mJAREntry->GetHasRef(result);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetSpec(const nsACString& aSpec)
+{
+ return SetSpecWithBase(aSpec, nullptr);
+}
+
+nsresult
+nsJARURI::SetSpecWithBase(const nsACString &aSpec, nsIURI* aBaseURL)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> ioServ(do_GetIOService(&rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString scheme;
+ rv = ioServ->ExtractScheme(aSpec, scheme);
+ if (NS_FAILED(rv)) {
+ // not an absolute URI
+ if (!aBaseURL)
+ return NS_ERROR_MALFORMED_URI;
+
+ RefPtr<nsJARURI> otherJAR;
+ aBaseURL->QueryInterface(NS_GET_IID(nsJARURI), getter_AddRefs(otherJAR));
+ NS_ENSURE_TRUE(otherJAR, NS_NOINTERFACE);
+
+ mJARFile = otherJAR->mJARFile;
+
+ nsCOMPtr<nsIStandardURL> entry(do_CreateInstance(NS_STANDARDURL_CONTRACTID));
+ if (!entry)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = entry->Init(nsIStandardURL::URLTYPE_NO_AUTHORITY, -1,
+ aSpec, mCharsetHint.get(), otherJAR->mJAREntry);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mJAREntry = do_QueryInterface(entry);
+ if (!mJAREntry)
+ return NS_NOINTERFACE;
+
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(scheme.EqualsLiteral("jar"), NS_ERROR_MALFORMED_URI);
+
+ nsACString::const_iterator begin, end;
+ aSpec.BeginReading(begin);
+ aSpec.EndReading(end);
+
+ while (begin != end && *begin != ':')
+ ++begin;
+
+ ++begin; // now we're past the "jar:"
+
+ // Search backward from the end for the "!/" delimiter. Remember, jar URLs
+ // can nest, e.g.:
+ // jar:jar:http://www.foo.com/bar.jar!/a.jar!/b.html
+ // This gets the b.html document from out of the a.jar file, that's
+ // contained within the bar.jar file.
+ // Also, the outermost "inner" URI may be a relative URI:
+ // jar:../relative.jar!/a.html
+
+ nsACString::const_iterator delim_begin (begin),
+ delim_end (end);
+
+ if (!RFindInReadable(NS_JAR_DELIMITER, delim_begin, delim_end))
+ return NS_ERROR_MALFORMED_URI;
+
+ rv = ioServ->NewURI(Substring(begin, delim_begin), mCharsetHint.get(),
+ aBaseURL, getter_AddRefs(mJARFile));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_TryToSetImmutable(mJARFile);
+
+ // skip over any extra '/' chars
+ while (*delim_end == '/')
+ ++delim_end;
+
+ return SetJAREntry(Substring(delim_end, end));
+}
+
+NS_IMETHODIMP
+nsJARURI::GetPrePath(nsACString &prePath)
+{
+ prePath = NS_JAR_SCHEME;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetScheme(nsACString &aScheme)
+{
+ aScheme = "jar";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetScheme(const nsACString &aScheme)
+{
+ // doesn't make sense to set the scheme of a jar: URL
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetUserPass(nsACString &aUserPass)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetUserPass(const nsACString &aUserPass)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetUsername(nsACString &aUsername)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetUsername(const nsACString &aUsername)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetPassword(nsACString &aPassword)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetPassword(const nsACString &aPassword)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetHostPort(nsACString &aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetHostPort(const nsACString &aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetHostAndPort(const nsACString &aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetHost(nsACString &aHost)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetHost(const nsACString &aHost)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetPort(int32_t *aPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetPort(int32_t aPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetPath(nsACString &aPath)
+{
+ nsAutoCString entrySpec;
+ mJAREntry->GetSpec(entrySpec);
+ return FormatSpec(entrySpec, aPath, false);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetPath(const nsACString &aPath)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetAsciiSpec(nsACString &aSpec)
+{
+ // XXX Shouldn't this like... make sure it returns ASCII or something?
+ return GetSpec(aSpec);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetAsciiHostPort(nsACString &aHostPort)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetAsciiHost(nsACString &aHost)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetOriginCharset(nsACString &aOriginCharset)
+{
+ aOriginCharset = mCharsetHint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::Equals(nsIURI *other, bool *result)
+{
+ return EqualsInternal(other, eHonorRef, result);
+}
+
+NS_IMETHODIMP
+nsJARURI::EqualsExceptRef(nsIURI *other, bool *result)
+{
+ return EqualsInternal(other, eIgnoreRef, result);
+}
+
+// Helper method:
+/* virtual */ nsresult
+nsJARURI::EqualsInternal(nsIURI *other,
+ nsJARURI::RefHandlingEnum refHandlingMode,
+ bool *result)
+{
+ *result = false;
+
+ if (!other)
+ return NS_OK; // not equal
+
+ RefPtr<nsJARURI> otherJAR;
+ other->QueryInterface(NS_GET_IID(nsJARURI), getter_AddRefs(otherJAR));
+ if (!otherJAR)
+ return NS_OK; // not equal
+
+ bool equal;
+ nsresult rv = mJARFile->Equals(otherJAR->mJARFile, &equal);
+ if (NS_FAILED(rv) || !equal) {
+ return rv; // not equal
+ }
+
+ return refHandlingMode == eHonorRef ?
+ mJAREntry->Equals(otherJAR->mJAREntry, result) :
+ mJAREntry->EqualsExceptRef(otherJAR->mJAREntry, result);
+}
+
+NS_IMETHODIMP
+nsJARURI::SchemeIs(const char *i_Scheme, bool *o_Equals)
+{
+ NS_ENSURE_ARG_POINTER(o_Equals);
+ if (!i_Scheme) return NS_ERROR_INVALID_ARG;
+
+ if (*i_Scheme == 'j' || *i_Scheme == 'J') {
+ *o_Equals = PL_strcasecmp("jar", i_Scheme) ? false : true;
+ } else {
+ *o_Equals = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::Clone(nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIJARURI> uri;
+ rv = CloneWithJARFileInternal(mJARFile, eHonorRef, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::CloneIgnoringRef(nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIJARURI> uri;
+ rv = CloneWithJARFileInternal(mJARFile, eIgnoreRef, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::CloneWithNewRef(const nsACString& newRef, nsIURI **result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIJARURI> uri;
+ rv = CloneWithJARFileInternal(mJARFile, eReplaceRef, newRef,
+ getter_AddRefs(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ uri.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::Resolve(const nsACString &relativePath, nsACString &result)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> ioServ(do_GetIOService(&rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString scheme;
+ rv = ioServ->ExtractScheme(relativePath, scheme);
+ if (NS_SUCCEEDED(rv)) {
+ // then aSpec is absolute
+ result = relativePath;
+ return NS_OK;
+ }
+
+ nsAutoCString resolvedPath;
+ mJAREntry->Resolve(relativePath, resolvedPath);
+
+ return FormatSpec(resolvedPath, result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIURL methods:
+
+NS_IMETHODIMP
+nsJARURI::GetFilePath(nsACString& filePath)
+{
+ return mJAREntry->GetFilePath(filePath);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetFilePath(const nsACString& filePath)
+{
+ return mJAREntry->SetFilePath(filePath);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetQuery(nsACString& query)
+{
+ return mJAREntry->GetQuery(query);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetQuery(const nsACString& query)
+{
+ return mJAREntry->SetQuery(query);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetRef(nsACString& ref)
+{
+ return mJAREntry->GetRef(ref);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetRef(const nsACString& ref)
+{
+ return mJAREntry->SetRef(ref);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetDirectory(nsACString& directory)
+{
+ return mJAREntry->GetDirectory(directory);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetDirectory(const nsACString& directory)
+{
+ return mJAREntry->SetDirectory(directory);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetFileName(nsACString& fileName)
+{
+ return mJAREntry->GetFileName(fileName);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetFileName(const nsACString& fileName)
+{
+ return mJAREntry->SetFileName(fileName);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetFileBaseName(nsACString& fileBaseName)
+{
+ return mJAREntry->GetFileBaseName(fileBaseName);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetFileBaseName(const nsACString& fileBaseName)
+{
+ return mJAREntry->SetFileBaseName(fileBaseName);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetFileExtension(nsACString& fileExtension)
+{
+ return mJAREntry->GetFileExtension(fileExtension);
+}
+
+NS_IMETHODIMP
+nsJARURI::SetFileExtension(const nsACString& fileExtension)
+{
+ return mJAREntry->SetFileExtension(fileExtension);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetCommonBaseSpec(nsIURI* uriToCompare, nsACString& commonSpec)
+{
+ commonSpec.Truncate();
+
+ NS_ENSURE_ARG_POINTER(uriToCompare);
+
+ commonSpec.Truncate();
+ nsCOMPtr<nsIJARURI> otherJARURI(do_QueryInterface(uriToCompare));
+ if (!otherJARURI) {
+ // Nothing in common
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> otherJARFile;
+ nsresult rv = otherJARURI->GetJARFile(getter_AddRefs(otherJARFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool equal;
+ rv = mJARFile->Equals(otherJARFile, &equal);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!equal) {
+ // See what the JAR file URIs have in common
+ nsCOMPtr<nsIURL> ourJARFileURL(do_QueryInterface(mJARFile));
+ if (!ourJARFileURL) {
+ // Not a URL, so nothing in common
+ return NS_OK;
+ }
+ nsAutoCString common;
+ rv = ourJARFileURL->GetCommonBaseSpec(otherJARFile, common);
+ if (NS_FAILED(rv)) return rv;
+
+ commonSpec = NS_JAR_SCHEME + common;
+ return NS_OK;
+
+ }
+
+ // At this point we have the same JAR file. Compare the JAREntrys
+ nsAutoCString otherEntry;
+ rv = otherJARURI->GetJAREntry(otherEntry);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString otherCharset;
+ rv = uriToCompare->GetOriginCharset(otherCharset);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURL> url;
+ rv = CreateEntryURL(otherEntry, otherCharset.get(), getter_AddRefs(url));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString common;
+ rv = mJAREntry->GetCommonBaseSpec(url, common);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = FormatSpec(common, commonSpec);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJARURI::GetRelativeSpec(nsIURI* uriToCompare, nsACString& relativeSpec)
+{
+ GetSpec(relativeSpec);
+
+ NS_ENSURE_ARG_POINTER(uriToCompare);
+
+ nsCOMPtr<nsIJARURI> otherJARURI(do_QueryInterface(uriToCompare));
+ if (!otherJARURI) {
+ // Nothing in common
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> otherJARFile;
+ nsresult rv = otherJARURI->GetJARFile(getter_AddRefs(otherJARFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool equal;
+ rv = mJARFile->Equals(otherJARFile, &equal);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!equal) {
+ // We live in different JAR files. Nothing in common.
+ return rv;
+ }
+
+ // Same JAR file. Compare the JAREntrys
+ nsAutoCString otherEntry;
+ rv = otherJARURI->GetJAREntry(otherEntry);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString otherCharset;
+ rv = uriToCompare->GetOriginCharset(otherCharset);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURL> url;
+ rv = CreateEntryURL(otherEntry, otherCharset.get(), getter_AddRefs(url));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString relativeEntrySpec;
+ rv = mJAREntry->GetRelativeSpec(url, relativeEntrySpec);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!StringBeginsWith(relativeEntrySpec, NS_BOGUS_ENTRY_SCHEME)) {
+ // An actual relative spec!
+ relativeSpec = relativeEntrySpec;
+ }
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIJARURI methods:
+
+NS_IMETHODIMP
+nsJARURI::GetJARFile(nsIURI* *jarFile)
+{
+ return GetInnerURI(jarFile);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetJAREntry(nsACString &entryPath)
+{
+ nsAutoCString filePath;
+ mJAREntry->GetFilePath(filePath);
+ NS_ASSERTION(filePath.Length() > 0, "path should never be empty!");
+ // Trim off the leading '/'
+ entryPath = Substring(filePath, 1, filePath.Length() - 1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJARURI::SetJAREntry(const nsACString &entryPath)
+{
+ return CreateEntryURL(entryPath, mCharsetHint.get(),
+ getter_AddRefs(mJAREntry));
+}
+
+NS_IMETHODIMP
+nsJARURI::CloneWithJARFile(nsIURI *jarFile, nsIJARURI **result)
+{
+ return CloneWithJARFileInternal(jarFile, eHonorRef, result);
+}
+
+nsresult
+nsJARURI::CloneWithJARFileInternal(nsIURI *jarFile,
+ nsJARURI::RefHandlingEnum refHandlingMode,
+ nsIJARURI **result)
+{
+ return CloneWithJARFileInternal(jarFile, refHandlingMode, EmptyCString(), result);
+}
+
+nsresult
+nsJARURI::CloneWithJARFileInternal(nsIURI *jarFile,
+ nsJARURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef,
+ nsIJARURI **result)
+{
+ if (!jarFile) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> newJARFile;
+ rv = jarFile->Clone(getter_AddRefs(newJARFile));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_TryToSetImmutable(newJARFile);
+
+ nsCOMPtr<nsIURI> newJAREntryURI;
+ if (refHandlingMode == eHonorRef) {
+ rv = mJAREntry->Clone(getter_AddRefs(newJAREntryURI));
+ } else if (refHandlingMode == eReplaceRef) {
+ rv = mJAREntry->CloneWithNewRef(newRef, getter_AddRefs(newJAREntryURI));
+ } else {
+ rv = mJAREntry->CloneIgnoringRef(getter_AddRefs(newJAREntryURI));
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURL> newJAREntry(do_QueryInterface(newJAREntryURI));
+ NS_ASSERTION(newJAREntry, "This had better QI to nsIURL!");
+
+ nsJARURI* uri = new nsJARURI();
+ NS_ADDREF(uri);
+ uri->mJARFile = newJARFile;
+ uri->mJAREntry = newJAREntry;
+ *result = uri;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsJARURI::GetInnerURI(nsIURI **uri)
+{
+ return NS_EnsureSafeToReturn(mJARFile, uri);
+}
+
+NS_IMETHODIMP
+nsJARURI::GetInnermostURI(nsIURI** uri)
+{
+ return NS_ImplGetInnermostURI(this, uri);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIIPCSerializableURI methods:
+
+void
+nsJARURI::Serialize(URIParams& aParams)
+{
+ JARURIParams params;
+
+ SerializeURI(mJARFile, params.jarFile());
+ SerializeURI(mJAREntry, params.jarEntry());
+ params.charset() = mCharsetHint;
+
+ aParams = params;
+}
+
+bool
+nsJARURI::Deserialize(const URIParams& aParams)
+{
+ if (aParams.type() != URIParams::TJARURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const JARURIParams& params = aParams.get_JARURIParams();
+
+ nsCOMPtr<nsIURI> file = DeserializeURI(params.jarFile());
+ if (!file) {
+ NS_ERROR("Couldn't deserialize jar file URI!");
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> entry = DeserializeURI(params.jarEntry());
+ if (!entry) {
+ NS_ERROR("Couldn't deserialize jar entry URI!");
+ return false;
+ }
+
+ nsCOMPtr<nsIURL> entryURL = do_QueryInterface(entry);
+ if (!entryURL) {
+ NS_ERROR("Couldn't QI jar entry URI to nsIURL!");
+ return false;
+ }
+
+ mJARFile.swap(file);
+ mJAREntry.swap(entryURL);
+ mCharsetHint = params.charset();
+
+ return true;
+}
diff --git a/components/jar/src/nsJARURI.h b/components/jar/src/nsJARURI.h
new file mode 100644
index 000000000..d2608a5c6
--- /dev/null
+++ b/components/jar/src/nsJARURI.h
@@ -0,0 +1,96 @@
+/* -*- 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/. */
+
+#ifndef nsJARURI_h__
+#define nsJARURI_h__
+
+#include "nsIJARURI.h"
+#include "nsISerializable.h"
+#include "nsIClassInfo.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsINestedURI.h"
+#include "nsIIPCSerializableURI.h"
+
+#define NS_THIS_JARURI_IMPL_CID \
+{ /* 9a55f629-730b-4d08-b75b-fa7d9570a691 */ \
+ 0x9a55f629, \
+ 0x730b, \
+ 0x4d08, \
+ {0xb7, 0x5b, 0xfa, 0x7d, 0x95, 0x70, 0xa6, 0x91} \
+}
+
+#define NS_JARURI_CID \
+{ /* 245abae2-b947-4ded-a46d-9829d3cca462 */ \
+ 0x245abae2, \
+ 0xb947, \
+ 0x4ded, \
+ {0xa4, 0x6d, 0x98, 0x29, 0xd3, 0xcc, 0xa4, 0x62} \
+}
+
+
+class nsJARURI final : public nsIJARURI,
+ public nsISerializable,
+ public nsIClassInfo,
+ public nsINestedURI,
+ public nsIIPCSerializableURI
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIURL
+ NS_DECL_NSIJARURI
+ NS_DECL_NSISERIALIZABLE
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSINESTEDURI
+ NS_DECL_NSIIPCSERIALIZABLEURI
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_THIS_JARURI_IMPL_CID)
+
+ // nsJARURI
+ nsJARURI();
+
+ nsresult Init(const char *charsetHint);
+ nsresult FormatSpec(const nsACString &entryPath, nsACString &result,
+ bool aIncludeScheme = true);
+ nsresult CreateEntryURL(const nsACString& entryFilename,
+ const char* charset,
+ nsIURL** url);
+ nsresult SetSpecWithBase(const nsACString& aSpec, nsIURI* aBaseURL);
+
+protected:
+ virtual ~nsJARURI();
+
+ // enum used in a few places to specify how .ref attribute should be handled
+ enum RefHandlingEnum {
+ eIgnoreRef,
+ eHonorRef,
+ eReplaceRef
+ };
+
+ // Helper to share code between Equals methods.
+ virtual nsresult EqualsInternal(nsIURI* other,
+ RefHandlingEnum refHandlingMode,
+ bool* result);
+
+ // Helpers to share code between Clone methods.
+ nsresult CloneWithJARFileInternal(nsIURI *jarFile,
+ RefHandlingEnum refHandlingMode,
+ nsIJARURI **result);
+ nsresult CloneWithJARFileInternal(nsIURI *jarFile,
+ RefHandlingEnum refHandlingMode,
+ const nsACString& newRef,
+ nsIJARURI **result);
+ nsCOMPtr<nsIURI> mJARFile;
+ // mJarEntry stored as a URL so that we can easily access things
+ // like extensions, refs, etc.
+ nsCOMPtr<nsIURL> mJAREntry;
+ nsCString mCharsetHint;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsJARURI, NS_THIS_JARURI_IMPL_CID)
+
+#endif // nsJARURI_h__
diff --git a/components/jar/src/nsZipArchive.cpp b/components/jar/src/nsZipArchive.cpp
new file mode 100644
index 000000000..8006ee56c
--- /dev/null
+++ b/components/jar/src/nsZipArchive.cpp
@@ -0,0 +1,1336 @@
+/* -*- Mode: C++; 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/. */
+
+/*
+ * This module implements a simple archive extractor.
+ *
+ * The underlying nsZipArchive is NOT thread-safe. Do not pass references
+ * or pointers to it across thread boundaries.
+ */
+
+// This must be the first include in the file in order for the
+// PL_ARENA_CONST_ALIGN_MASK macro to be effective.
+#define PL_ARENA_CONST_ALIGN_MASK (sizeof(void*)-1)
+#include "plarena.h"
+
+#define READTYPE int32_t
+#include "zlib.h"
+#include "brotli/decode.h"
+#include "nsISupportsUtils.h"
+#include "prio.h"
+#include "plstr.h"
+#include "mozilla/Logging.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "stdlib.h"
+#include "nsWildCard.h"
+#include "nsZipArchive.h"
+#include "nsString.h"
+#include "prenv.h"
+#if defined(XP_WIN)
+#include <windows.h>
+#endif
+
+// For placement new used for arena allocations of zip file list
+#include <new>
+#define ZIP_ARENABLOCKSIZE (1*1024)
+
+#ifdef XP_UNIX
+ #include <sys/mman.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <limits.h>
+ #include <unistd.h>
+#elif defined(XP_WIN)
+ #include <io.h>
+#endif
+
+#ifdef __SYMBIAN32__
+ #include <sys/syslimits.h>
+#endif /*__SYMBIAN32__*/
+
+
+#ifndef XP_UNIX /* we need some constants defined in limits.h and unistd.h */
+# ifndef S_IFMT
+# define S_IFMT 0170000
+# endif
+# ifndef S_IFLNK
+# define S_IFLNK 0120000
+# endif
+# ifndef PATH_MAX
+# define PATH_MAX 1024
+# endif
+#endif /* XP_UNIX */
+
+#ifdef XP_WIN
+#include "private/pprio.h" // To get PR_ImportFile
+#endif
+
+using namespace mozilla;
+
+static const uint32_t kMaxNameLength = PATH_MAX; /* Maximum name length */
+// For synthetic zip entries. Date/time corresponds to 1980-01-01 00:00.
+static const uint16_t kSyntheticTime = 0;
+static const uint16_t kSyntheticDate = (1 + (1 << 5) + (0 << 9));
+
+static uint16_t xtoint(const uint8_t *ii);
+static uint32_t xtolong(const uint8_t *ll);
+static uint32_t HashName(const char* aName, uint16_t nameLen);
+#ifdef XP_UNIX
+static nsresult ResolveSymlink(const char *path);
+#endif
+
+class ZipArchiveLogger {
+public:
+ void Write(const nsACString &zip, const char *entry) const {
+ if (!fd) {
+ char *env = PR_GetEnv("MOZ_JAR_LOG_FILE");
+ if (!env)
+ return;
+
+ nsCOMPtr<nsIFile> logFile;
+ nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, getter_AddRefs(logFile));
+ if (NS_FAILED(rv))
+ return;
+
+ // Create the log file and its parent directory (in case it doesn't exist)
+ rv = logFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
+ if (NS_FAILED(rv))
+ return;
+
+ PRFileDesc* file;
+#ifdef XP_WIN
+ // PR_APPEND is racy on Windows, so open a handle ourselves with flags that
+ // will work, and use PR_ImportFile to make it a PRFileDesc.
+ // This can go away when bug 840435 is fixed.
+ nsAutoString path;
+ logFile->GetPath(path);
+ if (path.IsEmpty())
+ return;
+ HANDLE handle = CreateFileW(path.get(), FILE_APPEND_DATA, FILE_SHARE_WRITE,
+ nullptr, OPEN_ALWAYS, 0, nullptr);
+ if (handle == INVALID_HANDLE_VALUE)
+ return;
+ file = PR_ImportFile((PROsfd)handle);
+ if (!file)
+ return;
+#else
+ rv = logFile->OpenNSPRFileDesc(PR_WRONLY|PR_CREATE_FILE|PR_APPEND, 0644, &file);
+ if (NS_FAILED(rv))
+ return;
+#endif
+ fd = file;
+ }
+ nsCString buf(zip);
+ buf.Append(' ');
+ buf.Append(entry);
+ buf.Append('\n');
+ PR_Write(fd, buf.get(), buf.Length());
+ }
+
+ void AddRef() {
+ MOZ_ASSERT(refCnt >= 0);
+ ++refCnt;
+ }
+
+ void Release() {
+ MOZ_ASSERT(refCnt > 0);
+ if ((0 == --refCnt) && fd) {
+ PR_Close(fd);
+ fd = nullptr;
+ }
+ }
+private:
+ int refCnt;
+ mutable PRFileDesc *fd;
+};
+
+static ZipArchiveLogger zipLog;
+
+//***********************************************************
+// For every inflation the following allocations are done:
+// malloc(1 * 9520)
+// malloc(32768 * 1)
+//***********************************************************
+
+nsresult gZlibInit(z_stream *zs)
+{
+ memset(zs, 0, sizeof(z_stream));
+ int zerr = inflateInit2(zs, -MAX_WBITS);
+ if (zerr != Z_OK) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+nsZipHandle::nsZipHandle()
+ : mFileData(nullptr)
+ , mLen(0)
+ , mMap(nullptr)
+ , mRefCnt(0)
+ , mFileStart(nullptr)
+ , mTotalLen(0)
+{
+ MOZ_COUNT_CTOR(nsZipHandle);
+}
+
+NS_IMPL_ADDREF(nsZipHandle)
+NS_IMPL_RELEASE(nsZipHandle)
+
+nsresult nsZipHandle::Init(nsIFile *file, nsZipHandle **ret,
+ PRFileDesc **aFd)
+{
+ mozilla::AutoFDClose fd;
+ int32_t flags = PR_RDONLY;
+#if defined(XP_WIN)
+ flags |= nsIFile::OS_READAHEAD;
+#endif
+ nsresult rv = file->OpenNSPRFileDesc(flags, 0000, &fd.rwget());
+ if (NS_FAILED(rv))
+ return rv;
+
+ int64_t size = PR_Available64(fd);
+ if (size >= INT32_MAX)
+ return NS_ERROR_FILE_TOO_BIG;
+
+ PRFileMap *map = PR_CreateFileMap(fd, size, PR_PROT_READONLY);
+ if (!map)
+ return NS_ERROR_FAILURE;
+
+ uint8_t *buf = (uint8_t*) PR_MemMap(map, 0, (uint32_t) size);
+ // Bug 525755: PR_MemMap fails when fd points at something other than a normal file.
+ if (!buf) {
+ PR_CloseFileMap(map);
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsZipHandle> handle = new nsZipHandle();
+ if (!handle) {
+ PR_MemUnmap(buf, (uint32_t) size);
+ PR_CloseFileMap(map);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+#if defined(XP_WIN)
+ if (aFd) {
+ *aFd = fd.forget();
+ }
+#else
+ handle->mNSPRFileDesc = fd.forget();
+#endif
+ handle->mFile.Init(file);
+ handle->mTotalLen = (uint32_t) size;
+ handle->mFileStart = buf;
+ rv = handle->findDataStart();
+ if (NS_FAILED(rv)) {
+ PR_MemUnmap(buf, (uint32_t) size);
+ handle->mFileStart = nullptr;
+ PR_CloseFileMap(map);
+ return rv;
+ }
+ handle->mMap = map;
+ handle.forget(ret);
+ return NS_OK;
+}
+
+nsresult nsZipHandle::Init(nsZipArchive *zip, const char *entry,
+ nsZipHandle **ret)
+{
+ RefPtr<nsZipHandle> handle = new nsZipHandle();
+ if (!handle)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ handle->mBuf = new nsZipItemPtr<uint8_t>(zip, entry);
+ if (!handle->mBuf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (!handle->mBuf->Buffer())
+ return NS_ERROR_UNEXPECTED;
+
+ handle->mMap = nullptr;
+ handle->mFile.Init(zip, entry);
+ handle->mTotalLen = handle->mBuf->Length();
+ handle->mFileStart = handle->mBuf->Buffer();
+ nsresult rv = handle->findDataStart();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ handle.forget(ret);
+ return NS_OK;
+}
+
+nsresult nsZipHandle::Init(const uint8_t* aData, uint32_t aLen,
+ nsZipHandle **aRet)
+{
+ RefPtr<nsZipHandle> handle = new nsZipHandle();
+
+ handle->mFileStart = aData;
+ handle->mTotalLen = aLen;
+ nsresult rv = handle->findDataStart();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ handle.forget(aRet);
+ return NS_OK;
+}
+
+// This function finds the start of the ZIP data. If the file is a regular ZIP,
+// this is just the start of the file. If the file is a CRX file, the start of
+// the data is after the CRX header.
+// CRX header reference: (CRX version 2)
+// Header requires little-endian byte ordering with 4-byte alignment.
+// 32 bits : magicNumber - Defined as a |char m[] = "Cr24"|.
+// Equivilant to |uint32_t m = 0x34327243|.
+// 32 bits : version - Unsigned integer representing the CRX file
+// format version. Currently equal to 2.
+// 32 bits : pubKeyLength - Unsigned integer representing the length
+// of the public key in bytes.
+// 32 bits : sigLength - Unsigned integer representing the length
+// of the signature in bytes.
+// pubKeyLength : publicKey - Contents of the author's public key.
+// sigLength : signature - Signature of the ZIP content.
+// Signature is created using the RSA
+// algorighm with the SHA-1 hash function.
+nsresult nsZipHandle::findDataStart()
+{
+ // In the CRX header, integers are 32 bits. Our pointer to the file is of
+ // type |uint8_t|, which is guaranteed to be 8 bits.
+ const uint32_t CRXIntSize = 4;
+
+MOZ_WIN_MEM_TRY_BEGIN
+ if (mTotalLen > CRXIntSize * 4 && xtolong(mFileStart) == kCRXMagic) {
+ const uint8_t* headerData = mFileStart;
+ headerData += CRXIntSize * 2; // Skip magic number and version number
+ uint32_t pubKeyLength = xtolong(headerData);
+ headerData += CRXIntSize;
+ uint32_t sigLength = xtolong(headerData);
+ uint32_t headerSize = CRXIntSize * 4 + pubKeyLength + sigLength;
+ if (mTotalLen > headerSize) {
+ mLen = mTotalLen - headerSize;
+ mFileData = mFileStart + headerSize;
+ return NS_OK;
+ }
+ }
+ mLen = mTotalLen;
+ mFileData = mFileStart;
+MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
+ return NS_OK;
+}
+
+int64_t nsZipHandle::SizeOfMapping()
+{
+ return mTotalLen;
+}
+
+nsresult nsZipHandle::GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc)
+{
+ if (!aNSPRFileDesc) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ *aNSPRFileDesc = mNSPRFileDesc;
+ if (!mNSPRFileDesc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+nsZipHandle::~nsZipHandle()
+{
+ if (mMap) {
+ PR_MemUnmap((void *)mFileStart, mTotalLen);
+ PR_CloseFileMap(mMap);
+ }
+ mFileStart = nullptr;
+ mFileData = nullptr;
+ mMap = nullptr;
+ mBuf = nullptr;
+ MOZ_COUNT_DTOR(nsZipHandle);
+}
+
+//***********************************************************
+// nsZipArchive -- public methods
+//***********************************************************
+
+//---------------------------------------------
+// nsZipArchive::OpenArchive
+//---------------------------------------------
+nsresult nsZipArchive::OpenArchive(nsZipHandle *aZipHandle, PRFileDesc *aFd)
+{
+ mFd = aZipHandle;
+
+ // Initialize our arena
+ PL_INIT_ARENA_POOL(&mArena, "ZipArena", ZIP_ARENABLOCKSIZE);
+
+ //-- get table of contents for archive
+ nsresult rv = BuildFileList(aFd);
+ if (NS_SUCCEEDED(rv)) {
+ if (aZipHandle->mFile)
+ aZipHandle->mFile.GetURIString(mURI);
+ }
+ return rv;
+}
+
+nsresult nsZipArchive::OpenArchive(nsIFile *aFile)
+{
+ RefPtr<nsZipHandle> handle;
+#if defined(XP_WIN)
+ mozilla::AutoFDClose fd;
+ nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle),
+ &fd.rwget());
+#else
+ nsresult rv = nsZipHandle::Init(aFile, getter_AddRefs(handle));
+#endif
+ if (NS_FAILED(rv))
+ return rv;
+
+#if defined(XP_WIN)
+ return OpenArchive(handle, fd.get());
+#else
+ return OpenArchive(handle);
+#endif
+}
+
+//---------------------------------------------
+// nsZipArchive::Test
+//---------------------------------------------
+nsresult nsZipArchive::Test(const char *aEntryName)
+{
+ nsZipItem* currItem;
+
+ if (aEntryName) // only test specified item
+ {
+ currItem = GetItem(aEntryName);
+ if (!currItem)
+ return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+ //-- don't test (synthetic) directory items
+ if (currItem->IsDirectory())
+ return NS_OK;
+ return ExtractFile(currItem, 0, 0);
+ }
+
+ // test all items in archive
+ for (int i = 0; i < ZIP_TABSIZE; i++) {
+ for (currItem = mFiles[i]; currItem; currItem = currItem->next) {
+ //-- don't test (synthetic) directory items
+ if (currItem->IsDirectory())
+ continue;
+ nsresult rv = ExtractFile(currItem, 0, 0);
+ if (rv != NS_OK)
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+//---------------------------------------------
+// nsZipArchive::CloseArchive
+//---------------------------------------------
+nsresult nsZipArchive::CloseArchive()
+{
+ if (mFd) {
+ PL_FinishArenaPool(&mArena);
+ mFd = nullptr;
+ }
+
+ // CAUTION:
+ // We don't need to delete each of the nsZipItem as the memory for
+ // the zip item and the filename it holds are both allocated from the Arena.
+ // Hence, destroying the Arena is like destroying all the memory
+ // for all the nsZipItem in one shot. But if the ~nsZipItem is doing
+ // anything more than cleaning up memory, we should start calling it.
+ // Let us also cleanup the mFiles table for re-use on the next 'open' call
+ memset(mFiles, 0, sizeof(mFiles));
+ mBuiltSynthetics = false;
+ return NS_OK;
+}
+
+//---------------------------------------------
+// nsZipArchive::GetItem
+//---------------------------------------------
+nsZipItem* nsZipArchive::GetItem(const char * aEntryName)
+{
+ if (aEntryName) {
+ uint32_t len = strlen(aEntryName);
+ //-- If the request is for a directory, make sure that synthetic entries
+ //-- are created for the directories without their own entry.
+ if (!mBuiltSynthetics) {
+ if ((len > 0) && (aEntryName[len-1] == '/')) {
+ if (BuildSynthetics() != NS_OK)
+ return 0;
+ }
+ }
+MOZ_WIN_MEM_TRY_BEGIN
+ nsZipItem* item = mFiles[ HashName(aEntryName, len) ];
+ while (item) {
+ if ((len == item->nameLength) &&
+ (!memcmp(aEntryName, item->Name(), len))) {
+
+ // Successful GetItem() is a good indicator that the file is about to be read
+ zipLog.Write(mURI, aEntryName);
+ return item; //-- found it
+ }
+ item = item->next;
+ }
+MOZ_WIN_MEM_TRY_CATCH(return nullptr)
+ }
+ return nullptr;
+}
+
+//---------------------------------------------
+// nsZipArchive::ExtractFile
+// This extracts the item to the filehandle provided.
+// If 'aFd' is null, it only tests the extraction.
+// On extraction error(s) it removes the file.
+// When needed, it also resolves the symlink.
+//---------------------------------------------
+nsresult nsZipArchive::ExtractFile(nsZipItem *item, nsIFile* outFile,
+ PRFileDesc* aFd)
+{
+ if (!item)
+ return NS_ERROR_ILLEGAL_VALUE;
+ if (!mFd)
+ return NS_ERROR_FAILURE;
+
+ // Directory extraction is handled in nsJAR::Extract,
+ // so the item to be extracted should never be a directory
+ MOZ_ASSERT(!item->IsDirectory());
+
+ Bytef outbuf[ZIP_BUFLEN];
+
+ nsZipCursor cursor(item, this, outbuf, ZIP_BUFLEN, true);
+
+ nsresult rv = NS_OK;
+
+ while (true) {
+ uint32_t count = 0;
+ uint8_t* buf = cursor.Read(&count);
+ if (!buf) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: Read() failed to return a buffer";
+ rv = NS_ERROR_FILE_CORRUPTED;
+ break;
+ } else if (count == 0) {
+ break;
+ }
+
+ if (aFd && PR_Write(aFd, buf, count) < (READTYPE)count) {
+ rv = NS_ERROR_FILE_DISK_FULL;
+ break;
+ }
+ }
+
+ //-- delete the file on errors, or resolve symlink if needed
+ if (aFd) {
+ PR_Close(aFd);
+ if (NS_FAILED(rv) && outFile) {
+ outFile->Remove(false);
+ }
+#ifdef XP_UNIX
+ else if (item->IsSymlink()) {
+ nsAutoCString path;
+ rv = outFile->GetNativePath(path);
+ if (NS_FAILED(rv)) return rv;
+ rv = ResolveSymlink(path.get());
+ }
+#endif
+ }
+
+ return rv;
+}
+
+//---------------------------------------------
+// nsZipArchive::FindInit
+//---------------------------------------------
+nsresult
+nsZipArchive::FindInit(const char * aPattern, nsZipFind **aFind)
+{
+ if (!aFind)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ // null out param in case an error happens
+ *aFind = nullptr;
+
+ bool regExp = false;
+ char* pattern = 0;
+
+ // Create synthetic directory entries on demand
+ nsresult rv = BuildSynthetics();
+ if (rv != NS_OK)
+ return rv;
+
+ // validate the pattern
+ if (aPattern)
+ {
+ switch (NS_WildCardValid((char*)aPattern))
+ {
+ case INVALID_SXP:
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ case NON_SXP:
+ regExp = false;
+ break;
+
+ case VALID_SXP:
+ regExp = true;
+ break;
+
+ default:
+ // undocumented return value from RegExpValid!
+ PR_ASSERT(false);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ pattern = PL_strdup(aPattern);
+ if (!pattern)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ *aFind = new nsZipFind(this, pattern, regExp);
+ if (!*aFind) {
+ PL_strfree(pattern);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+
+
+//---------------------------------------------
+// nsZipFind::FindNext
+//---------------------------------------------
+nsresult nsZipFind::FindNext(const char ** aResult, uint16_t *aNameLen)
+{
+ if (!mArchive || !aResult || !aNameLen)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ *aResult = 0;
+ *aNameLen = 0;
+MOZ_WIN_MEM_TRY_BEGIN
+ // we start from last match, look for next
+ while (mSlot < ZIP_TABSIZE)
+ {
+ // move to next in current chain, or move to new slot
+ mItem = mItem ? mItem->next : mArchive->mFiles[mSlot];
+
+ bool found = false;
+ if (!mItem)
+ ++mSlot; // no more in this chain, move to next slot
+ else if (!mPattern)
+ found = true; // always match
+ else if (mRegExp)
+ {
+ char buf[kMaxNameLength+1];
+ memcpy(buf, mItem->Name(), mItem->nameLength);
+ buf[mItem->nameLength]='\0';
+ found = (NS_WildCardMatch(buf, mPattern, false) == MATCH);
+ }
+ else
+ found = ((mItem->nameLength == strlen(mPattern)) &&
+ (memcmp(mItem->Name(), mPattern, mItem->nameLength) == 0));
+ if (found) {
+ // Need also to return the name length, as it is NOT zero-terminatdd...
+ *aResult = mItem->Name();
+ *aNameLen = mItem->nameLength;
+ return NS_OK;
+ }
+ }
+MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
+ return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+}
+
+#ifdef XP_UNIX
+//---------------------------------------------
+// ResolveSymlink
+//---------------------------------------------
+static nsresult ResolveSymlink(const char *path)
+{
+ PRFileDesc * fIn = PR_Open(path, PR_RDONLY, 0000);
+ if (!fIn)
+ return NS_ERROR_FILE_DISK_FULL;
+
+ char buf[PATH_MAX+1];
+ int32_t length = PR_Read(fIn, (void*)buf, PATH_MAX);
+ PR_Close(fIn);
+
+ if ( (length <= 0)
+ || ((buf[length] = 0, PR_Delete(path)) != 0)
+ || (symlink(buf, path) != 0))
+ {
+ return NS_ERROR_FILE_DISK_FULL;
+ }
+ return NS_OK;
+}
+#endif
+
+//***********************************************************
+// nsZipArchive -- private implementation
+//***********************************************************
+
+//---------------------------------------------
+// nsZipArchive::CreateZipItem
+//---------------------------------------------
+nsZipItem* nsZipArchive::CreateZipItem()
+{
+ // Arena allocate the nsZipItem
+ void *mem;
+ PL_ARENA_ALLOCATE(mem, &mArena, sizeof(nsZipItem));
+ return (nsZipItem*)mem;
+}
+
+//---------------------------------------------
+// nsZipArchive::BuildFileList
+//---------------------------------------------
+nsresult nsZipArchive::BuildFileList(PRFileDesc *aFd)
+{
+ // Get archive size using end pos
+ const uint8_t* buf;
+ const uint8_t* startp = mFd->mFileData;
+ const uint8_t* endp = startp + mFd->mLen;
+MOZ_WIN_MEM_TRY_BEGIN
+ uint32_t centralOffset = 4;
+ if (mFd->mLen > ZIPCENTRAL_SIZE && xtolong(startp + centralOffset) == CENTRALSIG) {
+ // Success means optimized jar layout from bug 559961 is in effect
+ uint32_t readaheadLength = xtolong(startp);
+ if (readaheadLength) {
+#ifdef XP_SOLARIS
+ posix_madvise(const_cast<uint8_t*>(startp), readaheadLength, POSIX_MADV_WILLNEED);
+#elif defined(XP_UNIX)
+ madvise(const_cast<uint8_t*>(startp), readaheadLength, MADV_WILLNEED);
+#elif defined(XP_WIN)
+ if (aFd) {
+ HANDLE hFile = (HANDLE) PR_FileDesc2NativeHandle(aFd);
+ mozilla::ReadAhead(hFile, 0, readaheadLength);
+ }
+#endif
+ }
+ } else {
+ for (buf = endp - ZIPEND_SIZE; buf > startp; buf--)
+ {
+ if (xtolong(buf) == ENDSIG) {
+ centralOffset = xtolong(((ZipEnd *)buf)->offset_central_dir);
+ break;
+ }
+ }
+ }
+
+ if (!centralOffset) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: no central offset";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ buf = startp + centralOffset;
+
+ // avoid overflow of startp + centralOffset.
+ if (buf < startp) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: overflow looking for central directory";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ //-- Read the central directory headers
+ uint32_t sig = 0;
+ while ((buf + int32_t(sizeof(uint32_t)) > buf) &&
+ (buf + int32_t(sizeof(uint32_t)) <= endp) &&
+ ((sig = xtolong(buf)) == CENTRALSIG)) {
+ // Make sure there is enough data available.
+ if ((buf > endp) || (endp - buf < ZIPCENTRAL_SIZE)) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: central directory too small";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Read the fixed-size data.
+ ZipCentral* central = (ZipCentral*)buf;
+
+ uint16_t namelen = xtoint(central->filename_len);
+ uint16_t extralen = xtoint(central->extrafield_len);
+ uint16_t commentlen = xtoint(central->commentfield_len);
+ uint32_t diff = ZIPCENTRAL_SIZE + namelen + extralen + commentlen;
+
+ // Sanity check variable sizes and refuse to deal with
+ // anything too big: it's likely a corrupt archive.
+ if (namelen < 1 ||
+ namelen > kMaxNameLength) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: namelen out of range";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ if (buf >= buf + diff || // No overflow
+ buf >= endp - diff) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: overflow looking for next item";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Point to the next item at the top of loop
+ buf += diff;
+
+ nsZipItem* item = CreateZipItem();
+ if (!item)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ item->central = central;
+ item->nameLength = namelen;
+ item->isSynthetic = false;
+
+ // Add item to file table
+ uint32_t hash = HashName(item->Name(), namelen);
+ item->next = mFiles[hash];
+ mFiles[hash] = item;
+
+ sig = 0;
+ } /* while reading central directory records */
+
+ if (sig != ENDSIG) {
+ nsZipArchive::sFileCorruptedReason = "nsZipArchive: unexpected sig";
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Make the comment available for consumers.
+ if ((endp >= buf) && (endp - buf >= ZIPEND_SIZE)) {
+ ZipEnd *zipend = (ZipEnd *)buf;
+
+ buf += ZIPEND_SIZE;
+ uint16_t commentlen = xtoint(zipend->commentfield_len);
+ if (endp - buf >= commentlen) {
+ mCommentPtr = (const char *)buf;
+ mCommentLen = commentlen;
+ }
+ }
+
+MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
+ return NS_OK;
+}
+
+//---------------------------------------------
+// nsZipArchive::BuildSynthetics
+//---------------------------------------------
+nsresult nsZipArchive::BuildSynthetics()
+{
+ if (mBuiltSynthetics)
+ return NS_OK;
+ mBuiltSynthetics = true;
+
+MOZ_WIN_MEM_TRY_BEGIN
+ // Create synthetic entries for any missing directories.
+ // Do this when all ziptable has scanned to prevent double entries.
+ for (int i = 0; i < ZIP_TABSIZE; ++i)
+ {
+ for (nsZipItem* item = mFiles[i]; item != nullptr; item = item->next)
+ {
+ if (item->isSynthetic)
+ continue;
+
+ //-- add entries for directories in the current item's path
+ //-- go from end to beginning, because then we can stop trying
+ //-- to create diritems if we find that the diritem we want to
+ //-- create already exists
+ //-- start just before the last char so as to not add the item
+ //-- twice if it's a directory
+ uint16_t namelen = item->nameLength;
+ MOZ_ASSERT(namelen > 0, "Attempt to build synthetic for zero-length entry name!");
+ const char *name = item->Name();
+ for (uint16_t dirlen = namelen - 1; dirlen > 0; dirlen--)
+ {
+ if (name[dirlen-1] != '/')
+ continue;
+
+ // The character before this is '/', so if this is also '/' then we
+ // have an empty path component. Skip it.
+ if (name[dirlen] == '/')
+ continue;
+
+ // Is the directory already in the file table?
+ uint32_t hash = HashName(item->Name(), dirlen);
+ bool found = false;
+ for (nsZipItem* zi = mFiles[hash]; zi != nullptr; zi = zi->next)
+ {
+ if ((dirlen == zi->nameLength) &&
+ (0 == memcmp(item->Name(), zi->Name(), dirlen)))
+ {
+ // we've already added this dir and all its parents
+ found = true;
+ break;
+ }
+ }
+ // if the directory was found, break out of the directory
+ // creation loop now that we know all implicit directories
+ // are there -- otherwise, start creating the zip item
+ if (found)
+ break;
+
+ nsZipItem* diritem = CreateZipItem();
+ if (!diritem)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Point to the central record of the original item for the name part.
+ diritem->central = item->central;
+ diritem->nameLength = dirlen;
+ diritem->isSynthetic = true;
+
+ // add diritem to the file table
+ diritem->next = mFiles[hash];
+ mFiles[hash] = diritem;
+ } /* end processing of dirs in item's name */
+ }
+ }
+MOZ_WIN_MEM_TRY_CATCH(return NS_ERROR_FAILURE)
+ return NS_OK;
+}
+
+nsZipHandle* nsZipArchive::GetFD()
+{
+ if (!mFd)
+ return nullptr;
+ return mFd.get();
+}
+
+//---------------------------------------------
+// nsZipArchive::GetDataOffset
+//---------------------------------------------
+uint32_t nsZipArchive::GetDataOffset(nsZipItem* aItem)
+{
+ MOZ_ASSERT(aItem);
+MOZ_WIN_MEM_TRY_BEGIN
+ //-- read local header to get variable length values and calculate
+ //-- the real data offset
+ uint32_t len = mFd->mLen;
+ const uint8_t* data = mFd->mFileData;
+ uint32_t offset = aItem->LocalOffset();
+ if (len < ZIPLOCAL_SIZE || offset > len - ZIPLOCAL_SIZE)
+ return 0;
+
+ // -- check signature before using the structure, in case the zip file is corrupt
+ ZipLocal* Local = (ZipLocal*)(data + offset);
+ if ((xtolong(Local->signature) != LOCALSIG))
+ return 0;
+
+ //-- NOTE: extralen is different in central header and local header
+ //-- for archives created using the Unix "zip" utility. To set
+ //-- the offset accurately we need the _local_ extralen.
+ offset += ZIPLOCAL_SIZE +
+ xtoint(Local->filename_len) +
+ xtoint(Local->extrafield_len);
+
+ return offset;
+MOZ_WIN_MEM_TRY_CATCH(return 0)
+}
+
+//---------------------------------------------
+// nsZipArchive::GetData
+//---------------------------------------------
+const uint8_t* nsZipArchive::GetData(nsZipItem* aItem)
+{
+ MOZ_ASSERT(aItem);
+MOZ_WIN_MEM_TRY_BEGIN
+ uint32_t offset = GetDataOffset(aItem);
+
+ // -- check if there is enough source data in the file
+ if (!offset ||
+ mFd->mLen < aItem->Size() ||
+ offset > mFd->mLen - aItem->Size() ||
+ (aItem->Compression() == STORED && aItem->Size() != aItem->RealSize())) {
+ return nullptr;
+ }
+
+ return mFd->mFileData + offset;
+MOZ_WIN_MEM_TRY_CATCH(return nullptr)
+}
+
+// nsZipArchive::GetComment
+bool nsZipArchive::GetComment(nsACString &aComment)
+{
+MOZ_WIN_MEM_TRY_BEGIN
+ aComment.Assign(mCommentPtr, mCommentLen);
+MOZ_WIN_MEM_TRY_CATCH(return false)
+ return true;
+}
+
+//---------------------------------------------
+// nsZipArchive::SizeOfMapping
+//---------------------------------------------
+int64_t nsZipArchive::SizeOfMapping()
+{
+ return mFd ? mFd->SizeOfMapping() : 0;
+}
+
+//------------------------------------------
+// nsZipArchive constructor and destructor
+//------------------------------------------
+
+nsZipArchive::nsZipArchive()
+ : mRefCnt(0)
+ , mCommentPtr(nullptr)
+ , mCommentLen(0)
+ , mBuiltSynthetics(false)
+{
+ zipLog.AddRef();
+
+ MOZ_COUNT_CTOR(nsZipArchive);
+
+ // initialize the table to nullptr
+ memset(mFiles, 0, sizeof(mFiles));
+}
+
+NS_IMPL_ADDREF(nsZipArchive)
+NS_IMPL_RELEASE(nsZipArchive)
+
+nsZipArchive::~nsZipArchive()
+{
+ CloseArchive();
+
+ MOZ_COUNT_DTOR(nsZipArchive);
+
+ zipLog.Release();
+}
+
+
+//------------------------------------------
+// nsZipFind constructor and destructor
+//------------------------------------------
+
+nsZipFind::nsZipFind(nsZipArchive* aZip, char* aPattern, bool aRegExp)
+ : mArchive(aZip)
+ , mPattern(aPattern)
+ , mItem(nullptr)
+ , mSlot(0)
+ , mRegExp(aRegExp)
+{
+ MOZ_COUNT_CTOR(nsZipFind);
+}
+
+nsZipFind::~nsZipFind()
+{
+ PL_strfree(mPattern);
+
+ MOZ_COUNT_DTOR(nsZipFind);
+}
+
+//------------------------------------------
+// helper functions
+//------------------------------------------
+
+/*
+ * HashName
+ *
+ * returns a hash key for the entry name
+ */
+static uint32_t HashName(const char* aName, uint16_t len)
+{
+ MOZ_ASSERT(aName != 0);
+
+ const uint8_t* p = (const uint8_t*)aName;
+ const uint8_t* endp = p + len;
+ uint32_t val = 0;
+ while (p != endp) {
+ val = val*37 + *p++;
+ }
+
+ return (val % ZIP_TABSIZE);
+}
+
+/*
+ * x t o i n t
+ *
+ * Converts a two byte ugly endianed integer
+ * to our platform's integer.
+ */
+static uint16_t xtoint (const uint8_t *ii)
+{
+ return (uint16_t) ((ii [0]) | (ii [1] << 8));
+}
+
+/*
+ * x t o l o n g
+ *
+ * Converts a four byte ugly endianed integer
+ * to our platform's integer.
+ */
+static uint32_t xtolong (const uint8_t *ll)
+{
+ return (uint32_t)( (ll [0] << 0) |
+ (ll [1] << 8) |
+ (ll [2] << 16) |
+ (ll [3] << 24) );
+}
+
+/*
+ * GetModTime
+ *
+ * returns last modification time in microseconds
+ */
+static PRTime GetModTime(uint16_t aDate, uint16_t aTime)
+{
+ // Note that on DST shift we can't handle correctly the hour that is valid
+ // in both DST zones
+ PRExplodedTime time;
+
+ time.tm_usec = 0;
+
+ time.tm_hour = (aTime >> 11) & 0x1F;
+ time.tm_min = (aTime >> 5) & 0x3F;
+ time.tm_sec = (aTime & 0x1F) * 2;
+
+ time.tm_year = (aDate >> 9) + 1980;
+ time.tm_month = ((aDate >> 5) & 0x0F) - 1;
+ time.tm_mday = aDate & 0x1F;
+
+ time.tm_params.tp_gmt_offset = 0;
+ time.tm_params.tp_dst_offset = 0;
+
+ PR_NormalizeTime(&time, PR_GMTParameters);
+ time.tm_params.tp_gmt_offset = PR_LocalTimeParameters(&time).tp_gmt_offset;
+ PR_NormalizeTime(&time, PR_GMTParameters);
+ time.tm_params.tp_dst_offset = PR_LocalTimeParameters(&time).tp_dst_offset;
+
+ return PR_ImplodeTime(&time);
+}
+
+nsZipItem::nsZipItem()
+ : next(nullptr)
+ , central(nullptr)
+ , nameLength(0)
+ , isSynthetic(false)
+{}
+
+uint32_t nsZipItem::LocalOffset()
+{
+ return xtolong(central->localhdr_offset);
+}
+
+uint32_t nsZipItem::Size()
+{
+ return isSynthetic ? 0 : xtolong(central->size);
+}
+
+uint32_t nsZipItem::RealSize()
+{
+ return isSynthetic ? 0 : xtolong(central->orglen);
+}
+
+uint32_t nsZipItem::CRC32()
+{
+ return isSynthetic ? 0 : xtolong(central->crc32);
+}
+
+uint16_t nsZipItem::Date()
+{
+ return isSynthetic ? kSyntheticDate : xtoint(central->date);
+}
+
+uint16_t nsZipItem::Time()
+{
+ return isSynthetic ? kSyntheticTime : xtoint(central->time);
+}
+
+uint16_t nsZipItem::Compression()
+{
+ return isSynthetic ? STORED : xtoint(central->method);
+}
+
+bool nsZipItem::IsDirectory()
+{
+ return isSynthetic || ((nameLength > 0) && ('/' == Name()[nameLength - 1]));
+}
+
+uint16_t nsZipItem::Mode()
+{
+ if (isSynthetic) return 0755;
+ return ((uint16_t)(central->external_attributes[2]) | 0x100);
+}
+
+const uint8_t * nsZipItem::GetExtraField(uint16_t aTag, uint16_t *aBlockSize)
+{
+ if (isSynthetic) return nullptr;
+MOZ_WIN_MEM_TRY_BEGIN
+ const unsigned char *buf = ((const unsigned char*)central) + ZIPCENTRAL_SIZE +
+ nameLength;
+ uint32_t buflen = (uint32_t)xtoint(central->extrafield_len);
+ uint32_t pos = 0;
+ uint16_t tag, blocksize;
+
+ while (buf && (pos + 4) <= buflen) {
+ tag = xtoint(buf + pos);
+ blocksize = xtoint(buf + pos + 2);
+
+ if (aTag == tag && (pos + 4 + blocksize) <= buflen) {
+ *aBlockSize = blocksize;
+ return buf + pos;
+ }
+
+ pos += blocksize + 4;
+ }
+
+MOZ_WIN_MEM_TRY_CATCH(return nullptr)
+ return nullptr;
+}
+
+
+PRTime nsZipItem::LastModTime()
+{
+ if (isSynthetic) return GetModTime(kSyntheticDate, kSyntheticTime);
+
+ // Try to read timestamp from extra field
+ uint16_t blocksize;
+ const uint8_t *tsField = GetExtraField(EXTENDED_TIMESTAMP_FIELD, &blocksize);
+ if (tsField && blocksize >= 5 && tsField[4] & EXTENDED_TIMESTAMP_MODTIME) {
+ return (PRTime)(xtolong(tsField + 5)) * PR_USEC_PER_SEC;
+ }
+
+ return GetModTime(Date(), Time());
+}
+
+#ifdef XP_UNIX
+bool nsZipItem::IsSymlink()
+{
+ if (isSynthetic) return false;
+ return (xtoint(central->external_attributes+2) & S_IFMT) == S_IFLNK;
+}
+#endif
+
+nsZipCursor::nsZipCursor(nsZipItem *item, nsZipArchive *aZip, uint8_t* aBuf,
+ uint32_t aBufSize, bool doCRC)
+ : mItem(item)
+ , mBuf(aBuf)
+ , mBufSize(aBufSize)
+ , mBrotliState(nullptr)
+ , mCRC(0)
+ , mDoCRC(doCRC)
+{
+ if (mItem->Compression() == DEFLATED) {
+#ifdef DEBUG
+ nsresult status =
+#endif
+ gZlibInit(&mZs);
+ NS_ASSERTION(status == NS_OK, "Zlib failed to initialize");
+ NS_ASSERTION(aBuf, "Must pass in a buffer for DEFLATED nsZipItem");
+ }
+
+ mZs.avail_in = item->Size();
+ mZs.next_in = (Bytef*)aZip->GetData(item);
+
+ if (mItem->Compression() == MOZ_JAR_BROTLI) {
+ mBrotliState = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
+ }
+
+ if (doCRC)
+ mCRC = crc32(0L, Z_NULL, 0);
+}
+
+nsZipCursor::~nsZipCursor()
+{
+ if (mItem->Compression() == DEFLATED) {
+ inflateEnd(&mZs);
+ }
+ if (mItem->Compression() == MOZ_JAR_BROTLI) {
+ BrotliDecoderDestroyInstance(mBrotliState);
+ }
+}
+
+uint8_t* nsZipCursor::ReadOrCopy(uint32_t *aBytesRead, bool aCopy) {
+ int zerr;
+ uint8_t *buf = nullptr;
+ bool verifyCRC = true;
+
+ if (!mZs.next_in)
+ return nullptr;
+MOZ_WIN_MEM_TRY_BEGIN
+ switch (mItem->Compression()) {
+ case STORED:
+ if (!aCopy) {
+ *aBytesRead = mZs.avail_in;
+ buf = mZs.next_in;
+ mZs.next_in += mZs.avail_in;
+ mZs.avail_in = 0;
+ } else {
+ *aBytesRead = mZs.avail_in > mBufSize ? mBufSize : mZs.avail_in;
+ memcpy(mBuf, mZs.next_in, *aBytesRead);
+ mZs.avail_in -= *aBytesRead;
+ mZs.next_in += *aBytesRead;
+ }
+ break;
+ case DEFLATED:
+ buf = mBuf;
+ mZs.next_out = buf;
+ mZs.avail_out = mBufSize;
+
+ zerr = inflate(&mZs, Z_PARTIAL_FLUSH);
+ if (zerr != Z_OK && zerr != Z_STREAM_END)
+ return nullptr;
+
+ *aBytesRead = mZs.next_out - buf;
+ verifyCRC = (zerr == Z_STREAM_END);
+ break;
+ case MOZ_JAR_BROTLI: {
+ buf = mBuf;
+ mZs.next_out = buf;
+ /* The brotli library wants size_t, but z_stream only contains
+ * unsigned int for avail_*. So use temporary stack values. */
+ size_t avail_out = mBufSize;
+ size_t avail_in = mZs.avail_in;
+ BrotliDecoderResult result = BrotliDecoderDecompressStream(
+ mBrotliState,
+ &avail_in, const_cast<const unsigned char**>(&mZs.next_in),
+ &avail_out, &mZs.next_out, nullptr);
+ /* We don't need to update avail_out, it's not used outside this
+ * function. */
+ mZs.avail_in = avail_in;
+
+ if (result == BROTLI_DECODER_RESULT_ERROR) {
+ return nullptr;
+ }
+
+ *aBytesRead = mZs.next_out - buf;
+ verifyCRC = (result == BROTLI_DECODER_RESULT_SUCCESS);
+ break;
+ }
+ default:
+ return nullptr;
+ }
+
+ if (mDoCRC) {
+ mCRC = crc32(mCRC, (const unsigned char*)buf, *aBytesRead);
+ if (verifyCRC && mCRC != mItem->CRC32())
+ return nullptr;
+ }
+MOZ_WIN_MEM_TRY_CATCH(return nullptr)
+ return buf;
+}
+
+nsZipItemPtr_base::nsZipItemPtr_base(nsZipArchive *aZip,
+ const char * aEntryName, bool doCRC)
+ : mReturnBuf(nullptr)
+ , mReadlen(0)
+{
+ // make sure the ziparchive hangs around
+ mZipHandle = aZip->GetFD();
+
+ nsZipItem* item = aZip->GetItem(aEntryName);
+ if (!item)
+ return;
+
+ uint32_t size = 0;
+ bool compressed = (item->Compression() == DEFLATED) ||
+ (item->Compression() == MOZ_JAR_BROTLI);
+ if (compressed) {
+ size = item->RealSize();
+ mAutoBuf = MakeUniqueFallible<uint8_t[]>(size);
+ if (!mAutoBuf) {
+ return;
+ }
+ }
+
+ nsZipCursor cursor(item, aZip, mAutoBuf.get(), size, doCRC);
+ mReturnBuf = cursor.Read(&mReadlen);
+ if (!mReturnBuf) {
+ return;
+ }
+
+ if (mReadlen != item->RealSize()) {
+ NS_ASSERTION(mReadlen == item->RealSize(), "nsZipCursor underflow");
+ mReturnBuf = nullptr;
+ return;
+ }
+}
+
+/* static */ const char*
+nsZipArchive::sFileCorruptedReason = nullptr;
diff --git a/components/jar/src/nsZipArchive.h b/components/jar/src/nsZipArchive.h
new file mode 100644
index 000000000..278f41c5f
--- /dev/null
+++ b/components/jar/src/nsZipArchive.h
@@ -0,0 +1,438 @@
+/* -*- 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 nsZipArchive_h_
+#define nsZipArchive_h_
+
+#include "mozilla/Attributes.h"
+
+#define ZIP_TABSIZE 256
+#define ZIP_BUFLEN (4*1024) /* Used as output buffer when deflating items to a file */
+
+#include "plarena.h"
+#include "zlib.h"
+#include "zipstruct.h"
+#include "nsAutoPtr.h"
+#include "nsIFile.h"
+#include "nsISupportsImpl.h" // For mozilla::ThreadSafeAutoRefCnt
+#include "mozilla/FileUtils.h"
+#include "mozilla/FileLocation.h"
+#include "mozilla/UniquePtr.h"
+
+#ifdef HAVE_SEH_EXCEPTIONS
+#define MOZ_WIN_MEM_TRY_BEGIN __try {
+#define MOZ_WIN_MEM_TRY_CATCH(cmd) } \
+ __except(GetExceptionCode()==EXCEPTION_IN_PAGE_ERROR ? \
+ EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) \
+ { \
+ NS_WARNING("unexpected EXCEPTION_IN_PAGE_ERROR"); \
+ cmd; \
+ }
+#else
+#define MOZ_WIN_MEM_TRY_BEGIN {
+#define MOZ_WIN_MEM_TRY_CATCH(cmd) }
+#endif
+
+class nsZipFind;
+struct PRFileDesc;
+struct BrotliDecoderStateStruct;
+
+/**
+ * This file defines some of the basic structures used by libjar to
+ * read Zip files. It makes use of zlib in order to do the decompression.
+ *
+ * A few notes on the classes/structs:
+ * nsZipArchive represents a single Zip file, and maintains an index
+ * of all the items in the file.
+ * nsZipItem represents a single item (file) in the Zip archive.
+ * nsZipFind represents the metadata involved in doing a search,
+ * and current state of the iteration of found objects.
+ * 'MT''safe' reading from the zipfile is performed through JARInputStream,
+ * which maintains its own file descriptor, allowing for multiple reads
+ * concurrently from the same zip file.
+ */
+
+/**
+ * nsZipItem -- a helper struct for nsZipArchive
+ *
+ * each nsZipItem represents one file in the archive and all the
+ * information needed to manipulate it.
+ */
+class nsZipItem final
+{
+public:
+ nsZipItem();
+
+ const char* Name() { return ((const char*)central) + ZIPCENTRAL_SIZE; }
+
+ uint32_t LocalOffset();
+ uint32_t Size();
+ uint32_t RealSize();
+ uint32_t CRC32();
+ uint16_t Date();
+ uint16_t Time();
+ uint16_t Compression();
+ bool IsDirectory();
+ uint16_t Mode();
+ const uint8_t* GetExtraField(uint16_t aTag, uint16_t *aBlockSize);
+ PRTime LastModTime();
+
+#ifdef XP_UNIX
+ bool IsSymlink();
+#endif
+
+ nsZipItem* next;
+ const ZipCentral* central;
+ uint16_t nameLength;
+ bool isSynthetic;
+};
+
+class nsZipHandle;
+
+/**
+ * nsZipArchive -- a class for reading the PKZIP file format.
+ *
+ */
+class nsZipArchive final
+{
+ friend class nsZipFind;
+
+ /** destructing the object closes the archive */
+ ~nsZipArchive();
+
+public:
+ static const char* sFileCorruptedReason;
+
+ /** constructing does not open the archive. See OpenArchive() */
+ nsZipArchive();
+
+ /**
+ * OpenArchive
+ *
+ * It's an error to call this more than once on the same nsZipArchive
+ * object. If we were allowed to use exceptions this would have been
+ * part of the constructor
+ *
+ * @param aZipHandle The nsZipHandle used to access the zip
+ * @param aFd Optional PRFileDesc for Windows readahead optimization
+ * @return status code
+ */
+ nsresult OpenArchive(nsZipHandle *aZipHandle, PRFileDesc *aFd = nullptr);
+
+ /**
+ * OpenArchive
+ *
+ * Convenience function that generates nsZipHandle
+ *
+ * @param aFile The file used to access the zip
+ * @return status code
+ */
+ nsresult OpenArchive(nsIFile *aFile);
+
+ /**
+ * Test the integrity of items in this archive by running
+ * a CRC check after extracting each item into a memory
+ * buffer. If an entry name is supplied only the
+ * specified item is tested. Else, if null is supplied
+ * then all the items in the archive are tested.
+ *
+ * @return status code
+ */
+ nsresult Test(const char *aEntryName);
+
+ /**
+ * Closes an open archive.
+ */
+ nsresult CloseArchive();
+
+ /**
+ * GetItem
+ * @param aEntryName Name of file in the archive
+ * @return pointer to nsZipItem
+ */
+ nsZipItem* GetItem(const char * aEntryName);
+
+ /**
+ * ExtractFile
+ *
+ * @param zipEntry Name of file in archive to extract
+ * @param outFD Filedescriptor to write contents to
+ * @param outname Name of file to write to
+ * @return status code
+ */
+ nsresult ExtractFile(nsZipItem * zipEntry, nsIFile* outFile, PRFileDesc * outFD);
+
+ /**
+ * FindInit
+ *
+ * Initializes a search for files in the archive. FindNext() returns
+ * the actual matches. The nsZipFind must be deleted when you're done
+ *
+ * @param aPattern a string or RegExp pattern to search for
+ * (may be nullptr to find all files in archive)
+ * @param aFind a pointer to a pointer to a structure used
+ * in FindNext. In the case of an error this
+ * will be set to nullptr.
+ * @return status code
+ */
+ nsresult FindInit(const char * aPattern, nsZipFind** aFind);
+
+ /*
+ * Gets an undependent handle to the mapped file.
+ */
+ nsZipHandle* GetFD();
+
+ /**
+ * Gets the data offset.
+ * @param aItem Pointer to nsZipItem
+ * returns 0 on failure.
+ */
+ uint32_t GetDataOffset(nsZipItem* aItem);
+
+ /**
+ * Get pointer to the data of the item.
+ * @param aItem Pointer to nsZipItem
+ * reutrns null when zip file is corrupt.
+ */
+ const uint8_t* GetData(nsZipItem* aItem);
+
+ bool GetComment(nsACString &aComment);
+
+ /**
+ * Gets the amount of memory taken up by the archive's mapping.
+ * @return the size
+ */
+ int64_t SizeOfMapping();
+
+ /*
+ * Refcounting
+ */
+ NS_METHOD_(MozExternalRefCountType) AddRef(void);
+ NS_METHOD_(MozExternalRefCountType) Release(void);
+
+private:
+ //--- private members ---
+ mozilla::ThreadSafeAutoRefCnt mRefCnt; /* ref count */
+ NS_DECL_OWNINGTHREAD
+
+ nsZipItem* mFiles[ZIP_TABSIZE];
+ PLArenaPool mArena;
+
+ const char* mCommentPtr;
+ uint16_t mCommentLen;
+
+ // Whether we synthesized the directory entries
+ bool mBuiltSynthetics;
+
+ // file handle
+ RefPtr<nsZipHandle> mFd;
+
+ // file URI, for logging
+ nsCString mURI;
+
+private:
+ //--- private methods ---
+ nsZipItem* CreateZipItem();
+ nsresult BuildFileList(PRFileDesc *aFd = nullptr);
+ nsresult BuildSynthetics();
+
+ nsZipArchive& operator=(const nsZipArchive& rhs) = delete;
+ nsZipArchive(const nsZipArchive& rhs) = delete;
+};
+
+/**
+ * nsZipFind
+ *
+ * a helper class for nsZipArchive, representing a search
+ */
+class nsZipFind final
+{
+public:
+ nsZipFind(nsZipArchive* aZip, char* aPattern, bool regExp);
+ ~nsZipFind();
+
+ nsresult FindNext(const char** aResult, uint16_t* aNameLen);
+
+private:
+ RefPtr<nsZipArchive> mArchive;
+ char* mPattern;
+ nsZipItem* mItem;
+ uint16_t mSlot;
+ bool mRegExp;
+
+ nsZipFind& operator=(const nsZipFind& rhs) = delete;
+ nsZipFind(const nsZipFind& rhs) = delete;
+};
+
+/**
+ * nsZipCursor -- a low-level class for reading the individual items in a zip.
+ */
+class nsZipCursor final
+{
+public:
+ /**
+ * Initializes the cursor
+ *
+ * @param aItem Item of interest
+ * @param aZip Archive
+ * @param aBuf Buffer used for decompression.
+ * This determines the maximum Read() size in the compressed case.
+ * @param aBufSize Buffer size
+ * @param doCRC When set to true Read() will check crc
+ */
+ nsZipCursor(nsZipItem *aItem, nsZipArchive *aZip, uint8_t* aBuf = nullptr, uint32_t aBufSize = 0, bool doCRC = false);
+
+ ~nsZipCursor();
+
+ /**
+ * Performs reads. In the compressed case it uses aBuf(passed in constructor), for stored files
+ * it returns a zero-copy buffer.
+ *
+ * @param aBytesRead Outparam for number of bytes read.
+ * @return data read or nullptr if item is corrupted.
+ */
+ uint8_t* Read(uint32_t *aBytesRead) {
+ return ReadOrCopy(aBytesRead, false);
+ }
+
+ /**
+ * Performs a copy. It always uses aBuf(passed in constructor).
+ *
+ * @param aBytesRead Outparam for number of bytes read.
+ * @return data read or nullptr if item is corrupted.
+ */
+ uint8_t* Copy(uint32_t *aBytesRead) {
+ return ReadOrCopy(aBytesRead, true);
+ }
+
+private:
+ /* Actual implementation for both Read and Copy above */
+ uint8_t* ReadOrCopy(uint32_t *aBytesRead, bool aCopy);
+
+ nsZipItem *mItem;
+ uint8_t *mBuf;
+ uint32_t mBufSize;
+ z_stream mZs;
+ BrotliDecoderStateStruct* mBrotliState;
+ uint32_t mCRC;
+ bool mDoCRC;
+};
+
+/**
+ * nsZipItemPtr - a RAII convenience class for reading the individual items in a zip.
+ * It reads whole files and does zero-copy IO for stored files. A buffer is allocated
+ * for decompression.
+ * Do not use when the file may be very large.
+ */
+class nsZipItemPtr_base
+{
+public:
+ /**
+ * Initializes the reader
+ *
+ * @param aZip Archive
+ * @param aEntryName Archive membername
+ * @param doCRC When set to true Read() will check crc
+ */
+ nsZipItemPtr_base(nsZipArchive *aZip, const char *aEntryName, bool doCRC);
+
+ uint32_t Length() const {
+ return mReadlen;
+ }
+
+protected:
+ RefPtr<nsZipHandle> mZipHandle;
+ mozilla::UniquePtr<uint8_t[]> mAutoBuf;
+ uint8_t *mReturnBuf;
+ uint32_t mReadlen;
+};
+
+template <class T>
+class nsZipItemPtr final : public nsZipItemPtr_base
+{
+ static_assert(sizeof(T) == sizeof(char),
+ "This class cannot be used with larger T without re-examining"
+ " a number of assumptions.");
+
+public:
+ nsZipItemPtr(nsZipArchive *aZip, const char *aEntryName, bool doCRC = false) : nsZipItemPtr_base(aZip, aEntryName, doCRC) { }
+ /**
+ * @return buffer containing the whole zip member or nullptr on error.
+ * The returned buffer is owned by nsZipItemReader.
+ */
+ const T* Buffer() const {
+ return (const T*)mReturnBuf;
+ }
+
+ operator const T*() const {
+ return Buffer();
+ }
+
+ /**
+ * Relinquish ownership of zip member if compressed.
+ * Copy member into a new buffer if uncompressed.
+ * @return a buffer with whole zip member. It is caller's responsibility to free() it.
+ */
+ mozilla::UniquePtr<T[]> Forget() {
+ if (!mReturnBuf)
+ return nullptr;
+ // In uncompressed mmap case, give up buffer
+ if (mAutoBuf.get() == mReturnBuf) {
+ mReturnBuf = nullptr;
+ return mozilla::UniquePtr<T[]>(reinterpret_cast<T*>(mAutoBuf.release()));
+ }
+ auto ret = mozilla::MakeUnique<T[]>(Length());
+ memcpy(ret.get(), mReturnBuf, Length());
+ mReturnBuf = nullptr;
+ return ret;
+ }
+};
+
+class nsZipHandle final
+{
+friend class nsZipArchive;
+friend class mozilla::FileLocation;
+public:
+ static nsresult Init(nsIFile *file, nsZipHandle **ret,
+ PRFileDesc **aFd = nullptr);
+ static nsresult Init(nsZipArchive *zip, const char *entry,
+ nsZipHandle **ret);
+ static nsresult Init(const uint8_t* aData, uint32_t aLen,
+ nsZipHandle **aRet);
+
+ NS_METHOD_(MozExternalRefCountType) AddRef(void);
+ NS_METHOD_(MozExternalRefCountType) Release(void);
+
+ int64_t SizeOfMapping();
+
+ nsresult GetNSPRFileDesc(PRFileDesc** aNSPRFileDesc);
+
+protected:
+ const uint8_t * mFileData; /* pointer to zip data */
+ uint32_t mLen; /* length of zip data */
+ mozilla::FileLocation mFile; /* source file if any, for logging */
+
+private:
+ nsZipHandle();
+ ~nsZipHandle();
+
+ nsresult findDataStart();
+
+ PRFileMap * mMap; /* nspr datastructure for mmap */
+ mozilla::AutoFDClose mNSPRFileDesc;
+ nsAutoPtr<nsZipItemPtr<uint8_t> > mBuf;
+ mozilla::ThreadSafeAutoRefCnt mRefCnt; /* ref count */
+ NS_DECL_OWNINGTHREAD
+
+ const uint8_t * mFileStart; /* pointer to mmaped file */
+ uint32_t mTotalLen; /* total length of the mmaped file */
+
+ /* Magic number for CRX type expressed in Big Endian since it is a literal */
+ static const uint32_t kCRXMagic = 0x34327243;
+};
+
+nsresult gZlibInit(z_stream *zs);
+
+#endif /* nsZipArchive_h_ */
diff --git a/components/jar/src/nsZipDataStream.cpp b/components/jar/src/nsZipDataStream.cpp
new file mode 100644
index 000000000..4a7b785b7
--- /dev/null
+++ b/components/jar/src/nsZipDataStream.cpp
@@ -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/.
+ */
+
+#include "StreamFunctions.h"
+#include "nsZipDataStream.h"
+#include "nsStringStream.h"
+#include "nsISeekableStream.h"
+#include "nsDeflateConverter.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+
+#define ZIP_METHOD_STORE 0
+#define ZIP_METHOD_DEFLATE 8
+
+using namespace mozilla;
+
+/**
+ * nsZipDataStream handles the writing an entry's into the zip file.
+ * It is set up to wither write the data as is, or in the event that compression
+ * has been requested to pass it through a stream converter.
+ * Currently only the deflate compression method is supported.
+ * The CRC checksum for the entry's data is also generated here.
+ */
+NS_IMPL_ISUPPORTS(nsZipDataStream, nsIStreamListener,
+ nsIRequestObserver)
+
+nsresult nsZipDataStream::Init(nsZipWriter *aWriter,
+ nsIOutputStream *aStream,
+ nsZipHeader *aHeader,
+ int32_t aCompression)
+{
+ mWriter = aWriter;
+ mHeader = aHeader;
+ mStream = aStream;
+ mHeader->mCRC = crc32(0L, Z_NULL, 0);
+
+ nsresult rv = NS_NewSimpleStreamListener(getter_AddRefs(mOutput), aStream,
+ nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aCompression > 0) {
+ mHeader->mMethod = ZIP_METHOD_DEFLATE;
+ nsCOMPtr<nsIStreamConverter> converter =
+ new nsDeflateConverter(aCompression);
+ NS_ENSURE_TRUE(converter, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = converter->AsyncConvertData("uncompressed", "rawdeflate", mOutput,
+ nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOutput = do_QueryInterface(converter, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ mHeader->mMethod = ZIP_METHOD_STORE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipDataStream::OnDataAvailable(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsIInputStream *aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (!mOutput)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ auto buffer = MakeUnique<char[]>(aCount);
+ NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = ZW_ReadData(aInputStream, buffer.get(), aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return ProcessData(aRequest, aContext, buffer.get(), aOffset, aCount);
+}
+
+NS_IMETHODIMP nsZipDataStream::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ if (!mOutput)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return mOutput->OnStartRequest(aRequest, aContext);
+}
+
+NS_IMETHODIMP nsZipDataStream::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ if (!mOutput)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mOutput->OnStopRequest(aRequest, aContext, aStatusCode);
+ mOutput = nullptr;
+ if (NS_FAILED(rv)) {
+ mWriter->EntryCompleteCallback(mHeader, rv);
+ }
+ else {
+ rv = CompleteEntry();
+ rv = mWriter->EntryCompleteCallback(mHeader, rv);
+ }
+
+ mStream = nullptr;
+ mWriter = nullptr;
+ mHeader = nullptr;
+
+ return rv;
+}
+
+inline nsresult nsZipDataStream::CompleteEntry()
+{
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t pos;
+ rv = seekable->Tell(&pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHeader->mCSize = pos - mHeader->mOffset - mHeader->GetFileHeaderLength();
+ mHeader->mWriteOnClose = true;
+ return NS_OK;
+}
+
+nsresult nsZipDataStream::ProcessData(nsIRequest *aRequest,
+ nsISupports *aContext, char *aBuffer,
+ uint64_t aOffset, uint32_t aCount)
+{
+ mHeader->mCRC = crc32(mHeader->mCRC,
+ reinterpret_cast<const unsigned char*>(aBuffer),
+ aCount);
+
+ MOZ_ASSERT(aCount <= INT32_MAX);
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ aBuffer, aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mOutput->OnDataAvailable(aRequest, aContext, stream, aOffset, aCount);
+ mHeader->mUSize += aCount;
+
+ return rv;
+}
+
+nsresult nsZipDataStream::ReadStream(nsIInputStream *aStream)
+{
+ if (!mOutput)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = OnStartRequest(nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto buffer = MakeUnique<char[]>(4096);
+ NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t read = 0;
+ uint32_t offset = 0;
+ do
+ {
+ rv = aStream->Read(buffer.get(), 4096, &read);
+ if (NS_FAILED(rv)) {
+ OnStopRequest(nullptr, nullptr, rv);
+ return rv;
+ }
+
+ if (read > 0) {
+ rv = ProcessData(nullptr, nullptr, buffer.get(), offset, read);
+ if (NS_FAILED(rv)) {
+ OnStopRequest(nullptr, nullptr, rv);
+ return rv;
+ }
+ offset += read;
+ }
+ } while (read > 0);
+
+ return OnStopRequest(nullptr, nullptr, NS_OK);
+}
diff --git a/components/jar/src/nsZipDataStream.h b/components/jar/src/nsZipDataStream.h
new file mode 100644
index 000000000..ffa455274
--- /dev/null
+++ b/components/jar/src/nsZipDataStream.h
@@ -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/.
+ */
+
+#ifndef _nsZipDataStream_h_
+#define _nsZipDataStream_h_
+
+#include "nsZipWriter.h"
+#include "nsIOutputStream.h"
+#include "nsIStreamListener.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsZipDataStream final : public nsIStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsZipDataStream()
+ {
+ }
+
+ nsresult Init(nsZipWriter *aWriter, nsIOutputStream *aStream,
+ nsZipHeader *aHeader, int32_t aCompression);
+
+ nsresult ReadStream(nsIInputStream *aStream);
+
+private:
+
+ ~nsZipDataStream() {}
+
+ nsCOMPtr<nsIStreamListener> mOutput;
+ nsCOMPtr<nsIOutputStream> mStream;
+ RefPtr<nsZipWriter> mWriter;
+ RefPtr<nsZipHeader> mHeader;
+
+ nsresult CompleteEntry();
+ nsresult ProcessData(nsIRequest *aRequest, nsISupports *aContext,
+ char *aBuffer, uint64_t aOffset, uint32_t aCount);
+};
+
+#endif
diff --git a/components/jar/src/nsZipHeader.cpp b/components/jar/src/nsZipHeader.cpp
new file mode 100644
index 000000000..af2ee0335
--- /dev/null
+++ b/components/jar/src/nsZipHeader.cpp
@@ -0,0 +1,389 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "StreamFunctions.h"
+#include "nsZipHeader.h"
+#include "nsMemory.h"
+#include "prtime.h"
+
+#define ZIP_FILE_HEADER_SIGNATURE 0x04034b50
+#define ZIP_FILE_HEADER_SIZE 30
+#define ZIP_CDS_HEADER_SIGNATURE 0x02014b50
+#define ZIP_CDS_HEADER_SIZE 46
+
+#define FLAGS_IS_UTF8 0x800
+
+#define ZIP_EXTENDED_TIMESTAMP_FIELD 0x5455
+#define ZIP_EXTENDED_TIMESTAMP_MODTIME 0x01
+
+using namespace mozilla;
+
+/**
+ * nsZipHeader represents an entry from a zip file.
+ */
+NS_IMPL_ISUPPORTS(nsZipHeader, nsIZipEntry)
+
+NS_IMETHODIMP nsZipHeader::GetCompression(uint16_t *aCompression)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ *aCompression = mMethod;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetSize(uint32_t *aSize)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ *aSize = mCSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetRealSize(uint32_t *aRealSize)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ *aRealSize = mUSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetCRC32(uint32_t *aCRC32)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ *aCRC32 = mCRC;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetIsDirectory(bool *aIsDirectory)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ if (mName.Last() == '/')
+ *aIsDirectory = true;
+ else
+ *aIsDirectory = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetLastModifiedTime(PRTime *aLastModifiedTime)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ // Try to read timestamp from extra field
+ uint16_t blocksize;
+ const uint8_t *tsField = GetExtraField(ZIP_EXTENDED_TIMESTAMP_FIELD, false, &blocksize);
+ if (tsField && blocksize >= 5) {
+ uint32_t pos = 4;
+ uint8_t flags;
+ flags = READ8(tsField, &pos);
+ if (flags & ZIP_EXTENDED_TIMESTAMP_MODTIME) {
+ *aLastModifiedTime = (PRTime)(READ32(tsField, &pos))
+ * PR_USEC_PER_SEC;
+ return NS_OK;
+ }
+ }
+
+ // Use DOS date/time fields
+ // Note that on DST shift we can't handle correctly the hour that is valid
+ // in both DST zones
+ PRExplodedTime time;
+
+ time.tm_usec = 0;
+
+ time.tm_hour = (mTime >> 11) & 0x1F;
+ time.tm_min = (mTime >> 5) & 0x3F;
+ time.tm_sec = (mTime & 0x1F) * 2;
+
+ time.tm_year = (mDate >> 9) + 1980;
+ time.tm_month = ((mDate >> 5) & 0x0F) - 1;
+ time.tm_mday = mDate & 0x1F;
+
+ time.tm_params.tp_gmt_offset = 0;
+ time.tm_params.tp_dst_offset = 0;
+
+ PR_NormalizeTime(&time, PR_GMTParameters);
+ time.tm_params.tp_gmt_offset = PR_LocalTimeParameters(&time).tp_gmt_offset;
+ PR_NormalizeTime(&time, PR_GMTParameters);
+ time.tm_params.tp_dst_offset = PR_LocalTimeParameters(&time).tp_dst_offset;
+
+ *aLastModifiedTime = PR_ImplodeTime(&time);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetIsSynthetic(bool *aIsSynthetic)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ *aIsSynthetic = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipHeader::GetPermissions(uint32_t *aPermissions)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ // Always give user read access at least, this matches nsIZipReader's behaviour
+ *aPermissions = ((mEAttr >> 16) & 0xfff) | 0x100;
+ return NS_OK;
+}
+
+void nsZipHeader::Init(const nsACString & aPath, PRTime aDate, uint32_t aAttr,
+ uint32_t aOffset)
+{
+ NS_ASSERTION(!mInited, "Already initalised");
+
+ PRExplodedTime time;
+ PR_ExplodeTime(aDate, PR_LocalTimeParameters, &time);
+
+ mTime = time.tm_sec / 2 + (time.tm_min << 5) + (time.tm_hour << 11);
+ mDate = time.tm_mday + ((time.tm_month + 1) << 5) +
+ ((time.tm_year - 1980) << 9);
+
+ // Store modification timestamp as extra field
+ // First fill CDS extra field
+ mFieldLength = 9;
+ mExtraField = MakeUnique<uint8_t[]>(mFieldLength);
+ if (!mExtraField) {
+ mFieldLength = 0;
+ } else {
+ uint32_t pos = 0;
+ WRITE16(mExtraField.get(), &pos, ZIP_EXTENDED_TIMESTAMP_FIELD);
+ WRITE16(mExtraField.get(), &pos, 5);
+ WRITE8(mExtraField.get(), &pos, ZIP_EXTENDED_TIMESTAMP_MODTIME);
+ WRITE32(mExtraField.get(), &pos, aDate / PR_USEC_PER_SEC);
+
+ // Fill local extra field
+ mLocalExtraField = MakeUnique<uint8_t[]>(mFieldLength);
+ if (mLocalExtraField) {
+ mLocalFieldLength = mFieldLength;
+ memcpy(mLocalExtraField.get(), mExtraField.get(), mLocalFieldLength);
+ }
+ }
+
+ mEAttr = aAttr;
+ mOffset = aOffset;
+ mName = aPath;
+ mComment = NS_LITERAL_CSTRING("");
+ // Claim a UTF-8 path in case it needs it.
+ mFlags |= FLAGS_IS_UTF8;
+ mInited = true;
+}
+
+uint32_t nsZipHeader::GetFileHeaderLength()
+{
+ return ZIP_FILE_HEADER_SIZE + mName.Length() + mLocalFieldLength;
+}
+
+nsresult nsZipHeader::WriteFileHeader(nsIOutputStream *aStream)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ uint8_t buf[ZIP_FILE_HEADER_SIZE];
+ uint32_t pos = 0;
+ WRITE32(buf, &pos, ZIP_FILE_HEADER_SIGNATURE);
+ WRITE16(buf, &pos, mVersionNeeded);
+ WRITE16(buf, &pos, mFlags);
+ WRITE16(buf, &pos, mMethod);
+ WRITE16(buf, &pos, mTime);
+ WRITE16(buf, &pos, mDate);
+ WRITE32(buf, &pos, mCRC);
+ WRITE32(buf, &pos, mCSize);
+ WRITE32(buf, &pos, mUSize);
+ WRITE16(buf, &pos, mName.Length());
+ WRITE16(buf, &pos, mLocalFieldLength);
+
+ nsresult rv = ZW_WriteData(aStream, (const char *)buf, pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ZW_WriteData(aStream, mName.get(), mName.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mLocalFieldLength)
+ {
+ rv = ZW_WriteData(aStream, (const char *)mLocalExtraField.get(), mLocalFieldLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+uint32_t nsZipHeader::GetCDSHeaderLength()
+{
+ return ZIP_CDS_HEADER_SIZE + mName.Length() + mComment.Length() +
+ mFieldLength;
+}
+
+nsresult nsZipHeader::WriteCDSHeader(nsIOutputStream *aStream)
+{
+ NS_ASSERTION(mInited, "Not initalised");
+
+ uint8_t buf[ZIP_CDS_HEADER_SIZE];
+ uint32_t pos = 0;
+ WRITE32(buf, &pos, ZIP_CDS_HEADER_SIGNATURE);
+ WRITE16(buf, &pos, mVersionMade);
+ WRITE16(buf, &pos, mVersionNeeded);
+ WRITE16(buf, &pos, mFlags);
+ WRITE16(buf, &pos, mMethod);
+ WRITE16(buf, &pos, mTime);
+ WRITE16(buf, &pos, mDate);
+ WRITE32(buf, &pos, mCRC);
+ WRITE32(buf, &pos, mCSize);
+ WRITE32(buf, &pos, mUSize);
+ WRITE16(buf, &pos, mName.Length());
+ WRITE16(buf, &pos, mFieldLength);
+ WRITE16(buf, &pos, mComment.Length());
+ WRITE16(buf, &pos, mDisk);
+ WRITE16(buf, &pos, mIAttr);
+ WRITE32(buf, &pos, mEAttr);
+ WRITE32(buf, &pos, mOffset);
+
+ nsresult rv = ZW_WriteData(aStream, (const char *)buf, pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ZW_WriteData(aStream, mName.get(), mName.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mExtraField) {
+ rv = ZW_WriteData(aStream, (const char *)mExtraField.get(), mFieldLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return ZW_WriteData(aStream, mComment.get(), mComment.Length());
+}
+
+nsresult nsZipHeader::ReadCDSHeader(nsIInputStream *stream)
+{
+ NS_ASSERTION(!mInited, "Already initalised");
+
+ uint8_t buf[ZIP_CDS_HEADER_SIZE];
+
+ nsresult rv = ZW_ReadData(stream, (char *)buf, ZIP_CDS_HEADER_SIZE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t pos = 0;
+ uint32_t signature = READ32(buf, &pos);
+ if (signature != ZIP_CDS_HEADER_SIGNATURE)
+ return NS_ERROR_FILE_CORRUPTED;
+
+ mVersionMade = READ16(buf, &pos);
+ mVersionNeeded = READ16(buf, &pos);
+ mFlags = READ16(buf, &pos);
+ mMethod = READ16(buf, &pos);
+ mTime = READ16(buf, &pos);
+ mDate = READ16(buf, &pos);
+ mCRC = READ32(buf, &pos);
+ mCSize = READ32(buf, &pos);
+ mUSize = READ32(buf, &pos);
+ uint16_t namelength = READ16(buf, &pos);
+ mFieldLength = READ16(buf, &pos);
+ uint16_t commentlength = READ16(buf, &pos);
+ mDisk = READ16(buf, &pos);
+ mIAttr = READ16(buf, &pos);
+ mEAttr = READ32(buf, &pos);
+ mOffset = READ32(buf, &pos);
+
+ if (namelength > 0) {
+ auto field = MakeUnique<char[]>(namelength);
+ NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
+ rv = ZW_ReadData(stream, field.get(), namelength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mName.Assign(field.get(), namelength);
+ }
+ else
+ mName = NS_LITERAL_CSTRING("");
+
+ if (mFieldLength > 0) {
+ mExtraField = MakeUnique<uint8_t[]>(mFieldLength);
+ NS_ENSURE_TRUE(mExtraField, NS_ERROR_OUT_OF_MEMORY);
+ rv = ZW_ReadData(stream, (char *)mExtraField.get(), mFieldLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (commentlength > 0) {
+ auto field = MakeUnique<char[]>(commentlength);
+ NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
+ rv = ZW_ReadData(stream, field.get(), commentlength);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mComment.Assign(field.get(), commentlength);
+ }
+ else
+ mComment = NS_LITERAL_CSTRING("");
+
+ mInited = true;
+ return NS_OK;
+}
+
+const uint8_t * nsZipHeader::GetExtraField(uint16_t aTag, bool aLocal, uint16_t *aBlockSize)
+{
+ const uint8_t *buf = aLocal ? mLocalExtraField.get() : mExtraField.get();
+ uint32_t buflen = aLocal ? mLocalFieldLength : mFieldLength;
+ uint32_t pos = 0;
+ uint16_t tag, blocksize;
+
+ while (buf && (pos + 4) <= buflen) {
+ tag = READ16(buf, &pos);
+ blocksize = READ16(buf, &pos);
+
+ if (aTag == tag && (pos + blocksize) <= buflen) {
+ *aBlockSize = blocksize;
+ return buf + pos - 4;
+ }
+
+ pos += blocksize;
+ }
+
+ return nullptr;
+}
+
+/*
+ * Pad extra field to align data starting position to specified size.
+ */
+nsresult nsZipHeader::PadExtraField(uint32_t aOffset, uint16_t aAlignSize)
+{
+ uint32_t pad_size;
+ uint32_t pa_offset;
+ uint32_t pa_end;
+
+ // Check for range and power of 2.
+ if (aAlignSize < 2 || aAlignSize > 32768 ||
+ (aAlignSize & (aAlignSize - 1)) != 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Point to current starting data position.
+ aOffset += ZIP_FILE_HEADER_SIZE + mName.Length() + mLocalFieldLength;
+
+ // Calculate aligned offset.
+ pa_offset = aOffset & ~(aAlignSize - 1);
+ pa_end = pa_offset + aAlignSize;
+ pad_size = pa_end - aOffset;
+ if (pad_size == 0) {
+ return NS_OK;
+ }
+
+ // Leave enough room(at least 4 bytes) for valid values in extra field.
+ while (pad_size < 4) {
+ pad_size += aAlignSize;
+ }
+ // Extra field length is 2 bytes.
+ if (mLocalFieldLength + pad_size > 65535) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<uint8_t[]> field = Move(mLocalExtraField);
+ uint32_t pos = mLocalFieldLength;
+
+ mLocalExtraField = MakeUnique<uint8_t[]>(mLocalFieldLength + pad_size);
+ memcpy(mLocalExtraField.get(), field.get(), mLocalFieldLength);
+ // Use 0xFFFF as tag ID to avoid conflict with other IDs.
+ // For more information, please read "Extensible data fields" section in:
+ // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+ WRITE16(mLocalExtraField.get(), &pos, 0xFFFF);
+ WRITE16(mLocalExtraField.get(), &pos, pad_size - 4);
+ memset(mLocalExtraField.get() + pos, 0, pad_size - 4);
+ mLocalFieldLength += pad_size;
+
+ return NS_OK;
+}
diff --git a/components/jar/src/nsZipHeader.h b/components/jar/src/nsZipHeader.h
new file mode 100644
index 000000000..f09aa2090
--- /dev/null
+++ b/components/jar/src/nsZipHeader.h
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 _nsZipHeader_h_
+#define _nsZipHeader_h_
+
+#include "nsString.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsIZipReader.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+// High word is S_IFREG, low word is DOS file attribute
+#define ZIP_ATTRS_FILE 0x80000000
+// High word is S_IFDIR, low word is DOS dir attribute
+#define ZIP_ATTRS_DIRECTORY 0x40000010
+#define PERMISSIONS_FILE 0644
+#define PERMISSIONS_DIR 0755
+
+// Combine file type attributes with unix style permissions
+#define ZIP_ATTRS(p, a) ((p & 0xfff) << 16) | a
+
+class nsZipHeader final : public nsIZipEntry
+{
+ ~nsZipHeader()
+ {
+ mExtraField = nullptr;
+ mLocalExtraField = nullptr;
+ }
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIZIPENTRY
+
+ nsZipHeader() :
+ mCRC(0),
+ mCSize(0),
+ mUSize(0),
+ mEAttr(0),
+ mOffset(0),
+ mFieldLength(0),
+ mLocalFieldLength(0),
+ mVersionMade(0x0300 + 23), // Generated on Unix by v2.3 (matches infozip)
+ mVersionNeeded(20), // Requires v2.0 to extract
+ mFlags(0),
+ mMethod(0),
+ mTime(0),
+ mDate(0),
+ mDisk(0),
+ mIAttr(0),
+ mInited(false),
+ mWriteOnClose(false),
+ mExtraField(nullptr),
+ mLocalExtraField(nullptr)
+ {
+ }
+
+ uint32_t mCRC;
+ uint32_t mCSize;
+ uint32_t mUSize;
+ uint32_t mEAttr;
+ uint32_t mOffset;
+ uint32_t mFieldLength;
+ uint32_t mLocalFieldLength;
+ uint16_t mVersionMade;
+ uint16_t mVersionNeeded;
+ uint16_t mFlags;
+ uint16_t mMethod;
+ uint16_t mTime;
+ uint16_t mDate;
+ uint16_t mDisk;
+ uint16_t mIAttr;
+ bool mInited;
+ bool mWriteOnClose;
+ nsCString mName;
+ nsCString mComment;
+ mozilla::UniquePtr<uint8_t[]> mExtraField;
+ mozilla::UniquePtr<uint8_t[]> mLocalExtraField;
+
+ void Init(const nsACString & aPath, PRTime aDate, uint32_t aAttr,
+ uint32_t aOffset);
+ uint32_t GetFileHeaderLength();
+ nsresult WriteFileHeader(nsIOutputStream *aStream);
+ uint32_t GetCDSHeaderLength();
+ nsresult WriteCDSHeader(nsIOutputStream *aStream);
+ nsresult ReadCDSHeader(nsIInputStream *aStream);
+ const uint8_t * GetExtraField(uint16_t aTag, bool aLocal, uint16_t *aBlockSize);
+ nsresult PadExtraField(uint32_t aOffset, uint16_t aAlignSize);
+};
+
+#endif
diff --git a/components/jar/src/nsZipWriter.cpp b/components/jar/src/nsZipWriter.cpp
new file mode 100644
index 000000000..25231fac0
--- /dev/null
+++ b/components/jar/src/nsZipWriter.cpp
@@ -0,0 +1,1133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsZipWriter.h"
+
+#include <algorithm>
+
+#include "StreamFunctions.h"
+#include "nsZipDataStream.h"
+#include "nsISeekableStream.h"
+#include "nsIAsyncStreamCopier.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStreamPump.h"
+#include "nsILoadInfo.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "nsError.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsIFile.h"
+#include "prio.h"
+
+#define ZIP_EOCDR_HEADER_SIZE 22
+#define ZIP_EOCDR_HEADER_SIGNATURE 0x06054b50
+
+using namespace mozilla;
+
+/**
+ * nsZipWriter is used to create and add to zip files.
+ * It is based on the spec available at
+ * http://www.pkware.com/documents/casestudies/APPNOTE.TXT.
+ *
+ * The basic structure of a zip file created is slightly simpler than that
+ * illustrated in the spec because certain features of the zip format are
+ * unsupported:
+ *
+ * [local file header 1]
+ * [file data 1]
+ * .
+ * .
+ * .
+ * [local file header n]
+ * [file data n]
+ * [central directory]
+ * [end of central directory record]
+ */
+NS_IMPL_ISUPPORTS(nsZipWriter, nsIZipWriter,
+ nsIRequestObserver)
+
+nsZipWriter::nsZipWriter()
+ : mCDSOffset(0)
+ , mCDSDirty(false)
+ , mInQueue(false)
+{}
+
+nsZipWriter::~nsZipWriter()
+{
+ if (mStream && !mInQueue)
+ Close();
+}
+
+NS_IMETHODIMP nsZipWriter::GetComment(nsACString & aComment)
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ aComment = mComment;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::SetComment(const nsACString & aComment)
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ mComment = aComment;
+ mCDSDirty = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::GetInQueue(bool *aInQueue)
+{
+ *aInQueue = mInQueue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::GetFile(nsIFile **aFile)
+{
+ if (!mFile)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = mFile->Clone(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aFile = file);
+ return NS_OK;
+}
+
+/*
+ * Reads file entries out of an existing zip file.
+ */
+nsresult nsZipWriter::ReadFile(nsIFile *aFile)
+{
+ int64_t size;
+ nsresult rv = aFile->GetFileSize(&size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the file is too short, it cannot be a valid archive, thus we fail
+ // without even attempting to open it
+ NS_ENSURE_TRUE(size > ZIP_EOCDR_HEADER_SIZE, NS_ERROR_FILE_CORRUPTED);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint8_t buf[1024];
+ int64_t seek = size - 1024;
+ uint32_t length = 1024;
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(inputStream);
+
+ while (true) {
+ if (seek < 0) {
+ length += (int32_t)seek;
+ seek = 0;
+ }
+
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, seek);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ rv = ZW_ReadData(inputStream, (char *)buf, length);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ /*
+ * We have to backtrack from the end of the file until we find the
+ * CDS signature
+ */
+ // We know it's at least this far from the end
+ for (uint32_t pos = length - ZIP_EOCDR_HEADER_SIZE;
+ (int32_t)pos >= 0; pos--) {
+ uint32_t sig = PEEK32(buf + pos);
+ if (sig == ZIP_EOCDR_HEADER_SIGNATURE) {
+ // Skip down to entry count
+ pos += 10;
+ uint32_t entries = READ16(buf, &pos);
+ // Skip past CDS size
+ pos += 4;
+ mCDSOffset = READ32(buf, &pos);
+ uint32_t commentlen = READ16(buf, &pos);
+
+ if (commentlen == 0)
+ mComment.Truncate();
+ else if (pos + commentlen <= length)
+ mComment.Assign((const char *)buf + pos, commentlen);
+ else {
+ if ((seek + pos + commentlen) > size) {
+ inputStream->Close();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+ auto field = MakeUnique<char[]>(commentlen);
+ NS_ENSURE_TRUE(field, NS_ERROR_OUT_OF_MEMORY);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ seek + pos);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ rv = ZW_ReadData(inputStream, field.get(), length);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ mComment.Assign(field.get(), commentlen);
+ }
+
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ mCDSOffset);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ for (uint32_t entry = 0; entry < entries; entry++) {
+ nsZipHeader* header = new nsZipHeader();
+ if (!header) {
+ inputStream->Close();
+ mEntryHash.Clear();
+ mHeaders.Clear();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = header->ReadCDSHeader(inputStream);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ mEntryHash.Clear();
+ mHeaders.Clear();
+ return rv;
+ }
+ mEntryHash.Put(header->mName, mHeaders.Count());
+ if (!mHeaders.AppendObject(header))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return inputStream->Close();
+ }
+ }
+
+ if (seek == 0) {
+ // We've reached the start with no signature found. Corrupt.
+ inputStream->Close();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Overlap by the size of the end of cdr
+ seek -= (1024 - ZIP_EOCDR_HEADER_SIZE);
+ }
+ // Will never reach here in reality
+ NS_NOTREACHED("Loop should never complete");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP nsZipWriter::Open(nsIFile *aFile, int32_t aIoFlags)
+{
+ if (mStream)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ // Need to be able to write to the file
+ if (aIoFlags & PR_RDONLY)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = aFile->Clone(getter_AddRefs(mFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = mFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists && !(aIoFlags & PR_CREATE_FILE))
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ if (exists && !(aIoFlags & (PR_TRUNCATE | PR_WRONLY))) {
+ rv = ReadFile(mFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCDSDirty = false;
+ }
+ else {
+ mCDSOffset = 0;
+ mCDSDirty = true;
+ mComment.Truncate();
+ }
+
+ // Silently drop PR_APPEND
+ aIoFlags &= 0xef;
+
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), mFile, aIoFlags);
+ if (NS_FAILED(rv)) {
+ mHeaders.Clear();
+ mEntryHash.Clear();
+ return rv;
+ }
+
+ rv = NS_NewBufferedOutputStream(getter_AddRefs(mStream), stream, 64 * 1024);
+ if (NS_FAILED(rv)) {
+ stream->Close();
+ mHeaders.Clear();
+ mEntryHash.Clear();
+ return rv;
+ }
+
+ if (mCDSOffset > 0) {
+ rv = SeekCDS();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::GetEntry(const nsACString & aZipEntry,
+ nsIZipEntry **_retval)
+{
+ int32_t pos;
+ if (mEntryHash.Get(aZipEntry, &pos))
+ NS_ADDREF(*_retval = mHeaders[pos]);
+ else
+ *_retval = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::HasEntry(const nsACString & aZipEntry,
+ bool *_retval)
+{
+ *_retval = mEntryHash.Get(aZipEntry, nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::AddEntryDirectory(const nsACString & aZipEntry,
+ PRTime aModTime, bool aQueue)
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (aQueue) {
+ nsZipQueueItem item;
+ item.mOperation = OPERATION_ADD;
+ item.mZipEntry = aZipEntry;
+ item.mModTime = aModTime;
+ item.mPermissions = PERMISSIONS_DIR;
+ if (!mQueue.AppendElement(item))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+ }
+
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+ return InternalAddEntryDirectory(aZipEntry, aModTime, PERMISSIONS_DIR);
+}
+
+NS_IMETHODIMP nsZipWriter::AddEntryFile(const nsACString & aZipEntry,
+ int32_t aCompression, nsIFile *aFile,
+ bool aQueue)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ if (aQueue) {
+ nsZipQueueItem item;
+ item.mOperation = OPERATION_ADD;
+ item.mZipEntry = aZipEntry;
+ item.mCompression = aCompression;
+ rv = aFile->Clone(getter_AddRefs(item.mFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mQueue.AppendElement(item))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+ }
+
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+
+ bool exists;
+ rv = aFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isdir;
+ rv = aFile->IsDirectory(&isdir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRTime modtime;
+ rv = aFile->GetLastModifiedTime(&modtime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ modtime *= PR_USEC_PER_MSEC;
+
+ uint32_t permissions;
+ rv = aFile->GetPermissions(&permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isdir)
+ return InternalAddEntryDirectory(aZipEntry, modtime, permissions);
+
+ if (mEntryHash.Get(aZipEntry, nullptr))
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddEntryStream(aZipEntry, modtime, aCompression, inputStream,
+ false, permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return inputStream->Close();
+}
+
+NS_IMETHODIMP nsZipWriter::AddEntryChannel(const nsACString & aZipEntry,
+ PRTime aModTime,
+ int32_t aCompression,
+ nsIChannel *aChannel,
+ bool aQueue)
+{
+ NS_ENSURE_ARG_POINTER(aChannel);
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (aQueue) {
+ nsZipQueueItem item;
+ item.mOperation = OPERATION_ADD;
+ item.mZipEntry = aZipEntry;
+ item.mModTime = aModTime;
+ item.mCompression = aCompression;
+ item.mPermissions = PERMISSIONS_FILE;
+ item.mChannel = aChannel;
+ if (!mQueue.AppendElement(item))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+ }
+
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+ if (mEntryHash.Get(aZipEntry, nullptr))
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_MaybeOpenChannelUsingOpen2(aChannel,
+ getter_AddRefs(inputStream));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddEntryStream(aZipEntry, aModTime, aCompression, inputStream,
+ false, PERMISSIONS_FILE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return inputStream->Close();
+}
+
+NS_IMETHODIMP nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
+ PRTime aModTime,
+ int32_t aCompression,
+ nsIInputStream *aStream,
+ bool aQueue)
+{
+ return AddEntryStream(aZipEntry, aModTime, aCompression, aStream, aQueue,
+ PERMISSIONS_FILE);
+}
+
+nsresult nsZipWriter::AddEntryStream(const nsACString & aZipEntry,
+ PRTime aModTime,
+ int32_t aCompression,
+ nsIInputStream *aStream,
+ bool aQueue,
+ uint32_t aPermissions)
+{
+ NS_ENSURE_ARG_POINTER(aStream);
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (aQueue) {
+ nsZipQueueItem item;
+ item.mOperation = OPERATION_ADD;
+ item.mZipEntry = aZipEntry;
+ item.mModTime = aModTime;
+ item.mCompression = aCompression;
+ item.mPermissions = aPermissions;
+ item.mStream = aStream;
+ if (!mQueue.AppendElement(item))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+ }
+
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+ if (mEntryHash.Get(aZipEntry, nullptr))
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+
+ RefPtr<nsZipHeader> header = new nsZipHeader();
+ NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
+ header->Init(aZipEntry, aModTime, ZIP_ATTRS(aPermissions, ZIP_ATTRS_FILE),
+ mCDSOffset);
+ nsresult rv = header->WriteFileHeader(mStream);
+ if (NS_FAILED(rv)) {
+ SeekCDS();
+ return rv;
+ }
+
+ RefPtr<nsZipDataStream> stream = new nsZipDataStream();
+ if (!stream) {
+ SeekCDS();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = stream->Init(this, mStream, header, aCompression);
+ if (NS_FAILED(rv)) {
+ SeekCDS();
+ return rv;
+ }
+
+ rv = stream->ReadStream(aStream);
+ if (NS_FAILED(rv))
+ SeekCDS();
+ return rv;
+}
+
+NS_IMETHODIMP nsZipWriter::RemoveEntry(const nsACString & aZipEntry,
+ bool aQueue)
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (aQueue) {
+ nsZipQueueItem item;
+ item.mOperation = OPERATION_REMOVE;
+ item.mZipEntry = aZipEntry;
+ if (!mQueue.AppendElement(item))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+ }
+
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+
+ int32_t pos;
+ if (mEntryHash.Get(aZipEntry, &pos)) {
+ // Flush any remaining data before we seek.
+ nsresult rv = mStream->Flush();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (pos < mHeaders.Count() - 1) {
+ // This is not the last entry, pull back the data.
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ mHeaders[pos]->mOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ mFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ seekable = do_QueryInterface(inputStream);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ mHeaders[pos + 1]->mOffset);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ uint32_t count = mCDSOffset - mHeaders[pos + 1]->mOffset;
+ uint32_t read = 0;
+ char buf[4096];
+ while (count > 0) {
+ read = std::min(count, (uint32_t) sizeof(buf));
+
+ rv = inputStream->Read(buf, read, &read);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ Cleanup();
+ return rv;
+ }
+
+ rv = ZW_WriteData(mStream, buf, read);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ Cleanup();
+ return rv;
+ }
+
+ count -= read;
+ }
+ inputStream->Close();
+
+ // Rewrite header offsets and update hash
+ uint32_t shift = (mHeaders[pos + 1]->mOffset -
+ mHeaders[pos]->mOffset);
+ mCDSOffset -= shift;
+ int32_t pos2 = pos + 1;
+ while (pos2 < mHeaders.Count()) {
+ mEntryHash.Put(mHeaders[pos2]->mName, pos2-1);
+ mHeaders[pos2]->mOffset -= shift;
+ pos2++;
+ }
+ }
+ else {
+ // Remove the last entry is just a case of moving the CDS
+ mCDSOffset = mHeaders[pos]->mOffset;
+ rv = SeekCDS();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mEntryHash.Remove(mHeaders[pos]->mName);
+ mHeaders.RemoveObjectAt(pos);
+ mCDSDirty = true;
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_FILE_NOT_FOUND;
+}
+
+NS_IMETHODIMP nsZipWriter::ProcessQueue(nsIRequestObserver *aObserver,
+ nsISupports *aContext)
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+
+ mProcessObserver = aObserver;
+ mProcessContext = aContext;
+ mInQueue = true;
+
+ if (mProcessObserver)
+ mProcessObserver->OnStartRequest(nullptr, mProcessContext);
+
+ BeginProcessingNextItem();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::Close()
+{
+ if (!mStream)
+ return NS_ERROR_NOT_INITIALIZED;
+ if (mInQueue)
+ return NS_ERROR_IN_PROGRESS;
+
+ if (mCDSDirty) {
+ uint32_t size = 0;
+ for (int32_t i = 0; i < mHeaders.Count(); i++) {
+ nsresult rv = mHeaders[i]->WriteCDSHeader(mStream);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+ size += mHeaders[i]->GetCDSHeaderLength();
+ }
+
+ uint8_t buf[ZIP_EOCDR_HEADER_SIZE];
+ uint32_t pos = 0;
+ WRITE32(buf, &pos, ZIP_EOCDR_HEADER_SIGNATURE);
+ WRITE16(buf, &pos, 0);
+ WRITE16(buf, &pos, 0);
+ WRITE16(buf, &pos, mHeaders.Count());
+ WRITE16(buf, &pos, mHeaders.Count());
+ WRITE32(buf, &pos, size);
+ WRITE32(buf, &pos, mCDSOffset);
+ WRITE16(buf, &pos, mComment.Length());
+
+ nsresult rv = ZW_WriteData(mStream, (const char *)buf, pos);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ rv = ZW_WriteData(mStream, mComment.get(), mComment.Length());
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ rv = seekable->SetEOF();
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ // Go back and rewrite the file headers
+ for (int32_t i = 0; i < mHeaders.Count(); i++) {
+ nsZipHeader *header = mHeaders[i];
+ if (!header->mWriteOnClose)
+ continue;
+
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, header->mOffset);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+ rv = header->WriteFileHeader(mStream);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+ }
+ }
+
+ nsresult rv = mStream->Close();
+ mStream = nullptr;
+ mHeaders.Clear();
+ mEntryHash.Clear();
+ mQueue.Clear();
+
+ return rv;
+}
+
+// Our nsIRequestObserver monitors removal operations performed on the queue
+NS_IMETHODIMP nsZipWriter::OnStartRequest(nsIRequest *aRequest,
+ nsISupports *aContext)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsZipWriter::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aContext,
+ nsresult aStatusCode)
+{
+ if (NS_FAILED(aStatusCode)) {
+ FinishQueue(aStatusCode);
+ Cleanup();
+ }
+
+ nsresult rv = mStream->Flush();
+ if (NS_FAILED(rv)) {
+ FinishQueue(rv);
+ Cleanup();
+ return rv;
+ }
+ rv = SeekCDS();
+ if (NS_FAILED(rv)) {
+ FinishQueue(rv);
+ return rv;
+ }
+
+ BeginProcessingNextItem();
+
+ return NS_OK;
+}
+
+/*
+ * Make all stored(uncompressed) files align to given alignment size.
+ */
+NS_IMETHODIMP nsZipWriter::AlignStoredFiles(uint16_t aAlignSize)
+{
+ nsresult rv;
+
+ // Check for range and power of 2.
+ if (aAlignSize < 2 || aAlignSize > 32768 ||
+ (aAlignSize & (aAlignSize - 1)) != 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (int i = 0; i < mHeaders.Count(); i++) {
+ nsZipHeader *header = mHeaders[i];
+
+ // Check whether this entry is file and compression method is stored.
+ bool isdir;
+ rv = header->GetIsDirectory(&isdir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (isdir || header->mMethod != 0) {
+ continue;
+ }
+ // Pad extra field to align data starting position to specified size.
+ uint32_t old_len = header->mLocalFieldLength;
+ rv = header->PadExtraField(header->mOffset, aAlignSize);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ // No padding means data already aligned.
+ uint32_t shift = header->mLocalFieldLength - old_len;
+ if (shift == 0) {
+ continue;
+ }
+
+ // Flush any remaining data before we start.
+ rv = mStream->Flush();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Open zip file for reading.
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISeekableStream> in_seekable = do_QueryInterface(inputStream);
+ nsCOMPtr<nsISeekableStream> out_seekable = do_QueryInterface(mStream);
+
+ uint32_t data_offset = header->mOffset + header->GetFileHeaderLength() - shift;
+ uint32_t count = mCDSOffset - data_offset;
+ uint32_t read;
+ char buf[4096];
+
+ // Shift data to aligned postion.
+ while (count > 0) {
+ read = std::min(count, (uint32_t) sizeof(buf));
+
+ rv = in_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ data_offset + count - read);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ rv = inputStream->Read(buf, read, &read);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ rv = out_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ data_offset + count - read + shift);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ rv = ZW_WriteData(mStream, buf, read);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ count -= read;
+ }
+ inputStream->Close();
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ // Update current header
+ rv = out_seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ header->mOffset);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+ rv = header->WriteFileHeader(mStream);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ // Update offset of all other headers
+ int pos = i + 1;
+ while (pos < mHeaders.Count()) {
+ mHeaders[pos]->mOffset += shift;
+ pos++;
+ }
+ mCDSOffset += shift;
+ rv = SeekCDS();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mCDSDirty = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsZipWriter::InternalAddEntryDirectory(const nsACString & aZipEntry,
+ PRTime aModTime,
+ uint32_t aPermissions)
+{
+ RefPtr<nsZipHeader> header = new nsZipHeader();
+ NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t zipAttributes = ZIP_ATTRS(aPermissions, ZIP_ATTRS_DIRECTORY);
+
+ if (aZipEntry.Last() != '/') {
+ nsCString dirPath;
+ dirPath.Assign(aZipEntry + NS_LITERAL_CSTRING("/"));
+ header->Init(dirPath, aModTime, zipAttributes, mCDSOffset);
+ }
+ else
+ header->Init(aZipEntry, aModTime, zipAttributes, mCDSOffset);
+
+ if (mEntryHash.Get(header->mName, nullptr))
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+
+ nsresult rv = header->WriteFileHeader(mStream);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+
+ mCDSDirty = true;
+ mCDSOffset += header->GetFileHeaderLength();
+ mEntryHash.Put(header->mName, mHeaders.Count());
+
+ if (!mHeaders.AppendObject(header)) {
+ Cleanup();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Recovering from an error while adding a new entry is simply a case of
+ * seeking back to the CDS. If we fail trying to do that though then cleanup
+ * and bail out.
+ */
+nsresult nsZipWriter::SeekCDS()
+{
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream, &rv);
+ if (NS_FAILED(rv)) {
+ Cleanup();
+ return rv;
+ }
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET, mCDSOffset);
+ if (NS_FAILED(rv))
+ Cleanup();
+ return rv;
+}
+
+/*
+ * In a bad error condition this essentially closes down the component as best
+ * it can.
+ */
+void nsZipWriter::Cleanup()
+{
+ mHeaders.Clear();
+ mEntryHash.Clear();
+ if (mStream)
+ mStream->Close();
+ mStream = nullptr;
+ mFile = nullptr;
+}
+
+/*
+ * Called when writing a file to the zip is complete.
+ */
+nsresult nsZipWriter::EntryCompleteCallback(nsZipHeader* aHeader,
+ nsresult aStatus)
+{
+ if (NS_SUCCEEDED(aStatus)) {
+ mEntryHash.Put(aHeader->mName, mHeaders.Count());
+ if (!mHeaders.AppendObject(aHeader)) {
+ mEntryHash.Remove(aHeader->mName);
+ SeekCDS();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mCDSDirty = true;
+ mCDSOffset += aHeader->mCSize + aHeader->GetFileHeaderLength();
+
+ if (mInQueue)
+ BeginProcessingNextItem();
+
+ return NS_OK;
+ }
+
+ nsresult rv = SeekCDS();
+ if (mInQueue)
+ FinishQueue(aStatus);
+ return rv;
+}
+
+inline nsresult nsZipWriter::BeginProcessingAddition(nsZipQueueItem* aItem,
+ bool* complete)
+{
+ if (aItem->mFile) {
+ bool exists;
+ nsresult rv = aItem->mFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isdir;
+ rv = aItem->mFile->IsDirectory(&isdir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aItem->mFile->GetLastModifiedTime(&aItem->mModTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aItem->mModTime *= PR_USEC_PER_MSEC;
+
+ rv = aItem->mFile->GetPermissions(&aItem->mPermissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isdir) {
+ // Set up for fall through to stream reader
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(aItem->mStream),
+ aItem->mFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // If a dir then this will fall through to the plain dir addition
+ }
+
+ uint32_t zipAttributes = ZIP_ATTRS(aItem->mPermissions, ZIP_ATTRS_FILE);
+
+ if (aItem->mStream || aItem->mChannel) {
+ RefPtr<nsZipHeader> header = new nsZipHeader();
+ NS_ENSURE_TRUE(header, NS_ERROR_OUT_OF_MEMORY);
+
+ header->Init(aItem->mZipEntry, aItem->mModTime, zipAttributes,
+ mCDSOffset);
+ nsresult rv = header->WriteFileHeader(mStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsZipDataStream> stream = new nsZipDataStream();
+ NS_ENSURE_TRUE(stream, NS_ERROR_OUT_OF_MEMORY);
+ rv = stream->Init(this, mStream, header, aItem->mCompression);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aItem->mStream) {
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), aItem->mStream,
+ -1, -1, 0, 0, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = pump->AsyncRead(stream, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ rv = NS_MaybeOpenChannelUsingAsyncOpen2(aItem->mChannel, stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+ }
+
+ // Must be plain directory addition
+ *complete = true;
+ return InternalAddEntryDirectory(aItem->mZipEntry, aItem->mModTime,
+ aItem->mPermissions);
+}
+
+inline nsresult nsZipWriter::BeginProcessingRemoval(int32_t aPos)
+{
+ // Open the zip file for reading
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
+ mFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), inputStream, -1, -1, 0,
+ 0, true);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = NS_NewSimpleStreamListener(getter_AddRefs(listener), mStream, this);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mStream);
+ rv = seekable->Seek(nsISeekableStream::NS_SEEK_SET,
+ mHeaders[aPos]->mOffset);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+
+ uint32_t shift = (mHeaders[aPos + 1]->mOffset -
+ mHeaders[aPos]->mOffset);
+ mCDSOffset -= shift;
+ int32_t pos2 = aPos + 1;
+ while (pos2 < mHeaders.Count()) {
+ mEntryHash.Put(mHeaders[pos2]->mName, pos2 - 1);
+ mHeaders[pos2]->mOffset -= shift;
+ pos2++;
+ }
+
+ mEntryHash.Remove(mHeaders[aPos]->mName);
+ mHeaders.RemoveObjectAt(aPos);
+ mCDSDirty = true;
+
+ rv = pump->AsyncRead(listener, nullptr);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ Cleanup();
+ return rv;
+ }
+ return NS_OK;
+}
+
+/*
+ * Starts processing on the next item in the queue.
+ */
+void nsZipWriter::BeginProcessingNextItem()
+{
+ while (!mQueue.IsEmpty()) {
+
+ nsZipQueueItem next = mQueue[0];
+ mQueue.RemoveElementAt(0);
+
+ if (next.mOperation == OPERATION_REMOVE) {
+ int32_t pos = -1;
+ if (mEntryHash.Get(next.mZipEntry, &pos)) {
+ if (pos < mHeaders.Count() - 1) {
+ nsresult rv = BeginProcessingRemoval(pos);
+ if (NS_FAILED(rv)) FinishQueue(rv);
+ return;
+ }
+
+ mCDSOffset = mHeaders[pos]->mOffset;
+ nsresult rv = SeekCDS();
+ if (NS_FAILED(rv)) {
+ FinishQueue(rv);
+ return;
+ }
+ mEntryHash.Remove(mHeaders[pos]->mName);
+ mHeaders.RemoveObjectAt(pos);
+ }
+ else {
+ FinishQueue(NS_ERROR_FILE_NOT_FOUND);
+ return;
+ }
+ }
+ else if (next.mOperation == OPERATION_ADD) {
+ if (mEntryHash.Get(next.mZipEntry, nullptr)) {
+ FinishQueue(NS_ERROR_FILE_ALREADY_EXISTS);
+ return;
+ }
+
+ bool complete = false;
+ nsresult rv = BeginProcessingAddition(&next, &complete);
+ if (NS_FAILED(rv)) {
+ SeekCDS();
+ FinishQueue(rv);
+ return;
+ }
+ if (!complete)
+ return;
+ }
+ }
+
+ FinishQueue(NS_OK);
+}
+
+/*
+ * Ends processing with the given status.
+ */
+void nsZipWriter::FinishQueue(nsresult aStatus)
+{
+ nsCOMPtr<nsIRequestObserver> observer = mProcessObserver;
+ nsCOMPtr<nsISupports> context = mProcessContext;
+ // Clean up everything first in case the observer decides to queue more
+ // things
+ mProcessObserver = nullptr;
+ mProcessContext = nullptr;
+ mInQueue = false;
+
+ if (observer)
+ observer->OnStopRequest(nullptr, context, aStatus);
+}
diff --git a/components/jar/src/nsZipWriter.h b/components/jar/src/nsZipWriter.h
new file mode 100644
index 000000000..f7fa1b163
--- /dev/null
+++ b/components/jar/src/nsZipWriter.h
@@ -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/.
+ */
+
+#ifndef _nsZipWriter_h_
+#define _nsZipWriter_h_
+
+#include "nsIZipWriter.h"
+#include "nsIFileStreams.h"
+#include "nsIBufferedStreams.h"
+#include "nsIRequestObserver.h"
+#include "nsZipHeader.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "nsDataHashtable.h"
+#include "mozilla/Attributes.h"
+
+#define ZIPWRITER_CONTRACTID "@mozilla.org/zipwriter;1"
+#define ZIPWRITER_CID { 0x430d416c, 0xa722, 0x4ad1, \
+ { 0xbe, 0x98, 0xd9, 0xa4, 0x45, 0xf8, 0x5e, 0x3f } }
+
+#define OPERATION_ADD 0
+#define OPERATION_REMOVE 1
+struct nsZipQueueItem
+{
+public:
+ uint32_t mOperation;
+ nsCString mZipEntry;
+ nsCOMPtr<nsIFile> mFile;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIInputStream> mStream;
+ PRTime mModTime;
+ int32_t mCompression;
+ uint32_t mPermissions;
+};
+
+class nsZipWriter final : public nsIZipWriter,
+ public nsIRequestObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIZIPWRITER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ nsZipWriter();
+ nsresult EntryCompleteCallback(nsZipHeader *aHeader, nsresult aStatus);
+
+private:
+ ~nsZipWriter();
+
+ uint32_t mCDSOffset;
+ bool mCDSDirty;
+ bool mInQueue;
+
+ nsCOMPtr<nsIFile> mFile;
+ nsCOMPtr<nsIRequestObserver> mProcessObserver;
+ nsCOMPtr<nsISupports> mProcessContext;
+ nsCOMPtr<nsIOutputStream> mStream;
+ nsCOMArray<nsZipHeader> mHeaders;
+ nsTArray<nsZipQueueItem> mQueue;
+ nsDataHashtable<nsCStringHashKey, int32_t> mEntryHash;
+ nsCString mComment;
+
+ nsresult SeekCDS();
+ void Cleanup();
+ nsresult ReadFile(nsIFile *aFile);
+ nsresult InternalAddEntryDirectory(const nsACString & aZipEntry,
+ PRTime aModTime, uint32_t aPermissions);
+ nsresult BeginProcessingAddition(nsZipQueueItem* aItem, bool* complete);
+ nsresult BeginProcessingRemoval(int32_t aPos);
+ nsresult AddEntryStream(const nsACString & aZipEntry, PRTime aModTime,
+ int32_t aCompression, nsIInputStream *aStream,
+ bool aQueue, uint32_t aPermissions);
+ void BeginProcessingNextItem();
+ void FinishQueue(nsresult aStatus);
+};
+
+#endif
diff --git a/components/jar/src/zipstruct.h b/components/jar/src/zipstruct.h
new file mode 100644
index 000000000..f7393128a
--- /dev/null
+++ b/components/jar/src/zipstruct.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C; 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/. */
+
+#ifndef _zipstruct_h
+#define _zipstruct_h
+
+
+/*
+ * Certain constants and structures for
+ * the Phil Katz ZIP archive format.
+ *
+ */
+
+typedef struct ZipLocal_
+ {
+ unsigned char signature [4];
+ unsigned char word [2];
+ unsigned char bitflag [2];
+ unsigned char method [2];
+ unsigned char time [2];
+ unsigned char date [2];
+ unsigned char crc32 [4];
+ unsigned char size [4];
+ unsigned char orglen [4];
+ unsigned char filename_len [2];
+ unsigned char extrafield_len [2];
+} ZipLocal;
+
+/*
+ * 'sizeof(struct XXX)' includes padding on ARM (see bug 87965)
+ * As the internals of a jar/zip file must not depend on the target
+ * architecture (i386, ppc, ARM, ...), use a fixed value instead.
+ */
+#define ZIPLOCAL_SIZE (4+2+2+2+2+2+4+4+4+2+2)
+
+typedef struct ZipCentral_
+ {
+ unsigned char signature [4];
+ unsigned char version_made_by [2];
+ unsigned char version [2];
+ unsigned char bitflag [2];
+ unsigned char method [2];
+ unsigned char time [2];
+ unsigned char date [2];
+ unsigned char crc32 [4];
+ unsigned char size [4];
+ unsigned char orglen [4];
+ unsigned char filename_len [2];
+ unsigned char extrafield_len [2];
+ unsigned char commentfield_len [2];
+ unsigned char diskstart_number [2];
+ unsigned char internal_attributes [2];
+ unsigned char external_attributes [4];
+ unsigned char localhdr_offset [4];
+} ZipCentral;
+
+/*
+ * 'sizeof(struct XXX)' includes padding on ARM (see bug 87965)
+ * As the internals of a jar/zip file must not depend on the target
+ * architecture (i386, ppc, ARM, ...), use a fixed value instead.
+ */
+#define ZIPCENTRAL_SIZE (4+2+2+2+2+2+2+4+4+4+2+2+2+2+2+4+4)
+
+typedef struct ZipEnd_
+ {
+ unsigned char signature [4];
+ unsigned char disk_nr [2];
+ unsigned char start_central_dir [2];
+ unsigned char total_entries_disk [2];
+ unsigned char total_entries_archive [2];
+ unsigned char central_dir_size [4];
+ unsigned char offset_central_dir [4];
+ unsigned char commentfield_len [2];
+} ZipEnd;
+
+/*
+ * 'sizeof(struct XXX)' includes padding on ARM (see bug 87965)
+ * As the internals of a jar/zip file must not depend on the target
+ * architecture (i386, ppc, ARM, ...), use a fixed value instead.
+ */
+#define ZIPEND_SIZE (4+2+2+2+2+4+4+2)
+
+/* signatures */
+#define LOCALSIG 0x04034B50l
+#define CENTRALSIG 0x02014B50l
+#define ENDSIG 0x06054B50l
+
+/* extra fields */
+#define EXTENDED_TIMESTAMP_FIELD 0x5455
+#define EXTENDED_TIMESTAMP_MODTIME 0x01
+
+/* compression methods */
+#define STORED 0
+#define SHRUNK 1
+#define REDUCED1 2
+#define REDUCED2 3
+#define REDUCED3 4
+#define REDUCED4 5
+#define IMPLODED 6
+#define TOKENIZED 7
+#define DEFLATED 8
+#define UNSUPPORTED 0xFF
+/* non-standard extension */
+#define MOZ_JAR_BROTLI 0x81
+
+#endif /* _zipstruct_h */
diff --git a/components/jetpack/app-extension/application.ini b/components/jetpack/app-extension/application.ini
new file mode 100644
index 000000000..6cec69a16
--- /dev/null
+++ b/components/jetpack/app-extension/application.ini
@@ -0,0 +1,11 @@
+[App]
+Vendor=Varma
+Name=Test App
+Version=1.0
+BuildID=20060101
+Copyright=Copyright (c) 2009 Atul Varma
+ID=xulapp@toolness.com
+
+[Gecko]
+MinVersion=1.9.2.0
+MaxVersion=2.0.*
diff --git a/components/jetpack/app-extension/bootstrap.js b/components/jetpack/app-extension/bootstrap.js
new file mode 100644
index 000000000..c2207c75f
--- /dev/null
+++ b/components/jetpack/app-extension/bootstrap.js
@@ -0,0 +1,362 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// @see http://dxr.mozilla.org/mozilla-central/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp
+
+'use strict';
+
+// IMPORTANT: Avoid adding any initialization tasks here, if you need to do
+// something before add-on is loaded consider addon/runner module instead!
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
+ results: Cr, manager: Cm } = Components;
+const ioService = Cc['@mozilla.org/network/io-service;1'].
+ getService(Ci.nsIIOService);
+const resourceHandler = ioService.getProtocolHandler('resource').
+ QueryInterface(Ci.nsIResProtocolHandler);
+const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
+const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
+ getService(Ci.mozIJSSubScriptLoader);
+const prefService = Cc['@mozilla.org/preferences-service;1'].
+ getService(Ci.nsIPrefService).
+ QueryInterface(Ci.nsIPrefBranch);
+const appInfo = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo);
+const vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
+ getService(Ci.nsIVersionComparator);
+
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+
+const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports;
+
+
+const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable',
+ 'install', 'uninstall', 'upgrade', 'downgrade' ];
+
+const bind = Function.call.bind(Function.bind);
+
+var loader = null;
+var unload = null;
+var cuddlefishSandbox = null;
+var nukeTimer = null;
+
+var resourceDomains = [];
+function setResourceSubstitution(domain, uri) {
+ resourceDomains.push(domain);
+ resourceHandler.setSubstitution(domain, uri);
+}
+
+// Utility function that synchronously reads local resource from the given
+// `uri` and returns content string.
+function readURI(uri) {
+ let channel = NetUtil.newChannel({
+ uri: NetUtil.newURI(uri, 'UTF-8'),
+ loadUsingSystemPrincipal: true
+ });
+
+ let stream = channel.open2();
+
+ let cstream = Cc['@mozilla.org/intl/converter-input-stream;1'].
+ createInstance(Ci.nsIConverterInputStream);
+ cstream.init(stream, 'UTF-8', 0, 0);
+
+ let str = {};
+ let data = '';
+ let read = 0;
+ do {
+ read = cstream.readString(0xffffffff, str);
+ data += str.value;
+ } while (read != 0);
+
+ cstream.close();
+
+ return data;
+}
+
+// We don't do anything on install & uninstall yet, but in a future
+// we should allow add-ons to cleanup after uninstall.
+function install(data, reason) {}
+function uninstall(data, reason) {}
+
+function startup(data, reasonCode) {
+ try {
+ let reason = REASON[reasonCode];
+ // URI for the root of the XPI file.
+ // 'jar:' URI if the addon is packed, 'file:' URI otherwise.
+ // (Used by l10n module in order to fetch `locale` folder)
+ let rootURI = data.resourceURI.spec;
+
+ // TODO: Maybe we should perform read harness-options.json asynchronously,
+ // since we can't do anything until 'sessionstore-windows-restored' anyway.
+ let options = JSON.parse(readURI(rootURI + './harness-options.json'));
+
+ let id = options.jetpackID;
+ let name = options.name;
+
+ // Clean the metadata
+ options.metadata[name]['permissions'] = options.metadata[name]['permissions'] || {};
+
+ // freeze the permissionss
+ Object.freeze(options.metadata[name]['permissions']);
+ // freeze the metadata
+ Object.freeze(options.metadata[name]);
+
+ // Register a new resource 'domain' for this addon which is mapping to
+ // XPI's `resources` folder.
+ // Generate the domain name by using jetpack ID, which is the extension ID
+ // by stripping common characters that doesn't work as a domain name:
+ let uuidRe =
+ /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
+
+ let domain = id.
+ toLowerCase().
+ replace(/@/g, '-at-').
+ replace(/\./g, '-dot-').
+ replace(uuidRe, '$1');
+
+ let prefixURI = 'resource://' + domain + '/';
+ let resourcesURI = ioService.newURI(rootURI + '/resources/', null, null);
+ setResourceSubstitution(domain, resourcesURI);
+
+ // Create path to URLs mapping supported by loader.
+ let paths = {
+ // Relative modules resolve to add-on package lib
+ './': prefixURI + name + '/lib/',
+ './tests/': prefixURI + name + '/tests/',
+ '': 'resource://gre/modules/commonjs/'
+ };
+
+ // Maps addon lib and tests ressource folders for each package
+ paths = Object.keys(options.metadata).reduce(function(result, name) {
+ result[name + '/'] = prefixURI + name + '/lib/'
+ result[name + '/tests/'] = prefixURI + name + '/tests/'
+ return result;
+ }, paths);
+
+ // We need to map tests folder when we run sdk tests whose package name
+ // is stripped
+ if (name == 'addon-sdk')
+ paths['tests/'] = prefixURI + name + '/tests/';
+
+ let useBundledSDK = options['force-use-bundled-sdk'];
+ if (!useBundledSDK) {
+ try {
+ useBundledSDK = prefService.getBoolPref("extensions.addon-sdk.useBundledSDK");
+ }
+ catch (e) {
+ // Pref doesn't exist, allow using Firefox shipped SDK
+ }
+ }
+
+ // Starting with Firefox 21.0a1, we start using modules shipped into firefox
+ // Still allow using modules from the xpi if the manifest tell us to do so.
+ // And only try to look for sdk modules in xpi if the xpi actually ship them
+ if (options['is-sdk-bundled'] &&
+ (vc.compare(appInfo.version, '21.0a1') < 0 || useBundledSDK)) {
+ // Maps sdk module folders to their resource folder
+ paths[''] = prefixURI + 'addon-sdk/lib/';
+ // test.js is usually found in root commonjs or SDK_ROOT/lib/ folder,
+ // so that it isn't shipped in the xpi. Keep a copy of it in sdk/ folder
+ // until we no longer support SDK modules in XPI:
+ paths['test'] = prefixURI + 'addon-sdk/lib/sdk/test.js';
+ }
+
+ // Retrieve list of module folder overloads based on preferences in order to
+ // eventually used a local modules instead of files shipped into Firefox.
+ let branch = prefService.getBranch('extensions.modules.' + id + '.path');
+ paths = branch.getChildList('', {}).reduce(function (result, name) {
+ // Allows overloading of any sub folder by replacing . by / in pref name
+ let path = name.substr(1).split('.').join('/');
+ // Only accept overloading folder by ensuring always ending with `/`
+ if (path) path += '/';
+ let fileURI = branch.getCharPref(name);
+
+ // On mobile, file URI has to end with a `/` otherwise, setSubstitution
+ // takes the parent folder instead.
+ if (fileURI[fileURI.length-1] !== '/')
+ fileURI += '/';
+
+ // Maps the given file:// URI to a resource:// in order to avoid various
+ // failure that happens with file:// URI and be close to production env
+ let resourcesURI = ioService.newURI(fileURI, null, null);
+ let resName = 'extensions.modules.' + domain + '.commonjs.path' + name;
+ setResourceSubstitution(resName, resourcesURI);
+
+ result[path] = 'resource://' + resName + '/';
+ return result;
+ }, paths);
+
+ // Make version 2 of the manifest
+ let manifest = options.manifest;
+
+ // Import `cuddlefish.js` module using a Sandbox and bootstrap loader.
+ let cuddlefishPath = 'loader/cuddlefish.js';
+ let cuddlefishURI = 'resource://gre/modules/commonjs/sdk/' + cuddlefishPath;
+ if (paths['sdk/']) { // sdk folder has been overloaded
+ // (from pref, or cuddlefish is still in the xpi)
+ cuddlefishURI = paths['sdk/'] + cuddlefishPath;
+ }
+ else if (paths['']) { // root modules folder has been overloaded
+ cuddlefishURI = paths[''] + 'sdk/' + cuddlefishPath;
+ }
+
+ cuddlefishSandbox = loadSandbox(cuddlefishURI);
+ let cuddlefish = cuddlefishSandbox.exports;
+
+ // Normalize `options.mainPath` so that it looks like one that will come
+ // in a new version of linker.
+ let main = options.mainPath;
+
+ unload = cuddlefish.unload;
+ loader = cuddlefish.Loader({
+ paths: paths,
+ // modules manifest.
+ manifest: manifest,
+
+ // Add-on ID used by different APIs as a unique identifier.
+ id: id,
+ // Add-on name.
+ name: name,
+ // Add-on version.
+ version: options.metadata[name].version,
+ // Add-on package descriptor.
+ metadata: options.metadata[name],
+ // Add-on load reason.
+ loadReason: reason,
+
+ prefixURI: prefixURI,
+ // Add-on URI.
+ rootURI: rootURI,
+ // options used by system module.
+ // File to write 'OK' or 'FAIL' (exit code emulation).
+ resultFile: options.resultFile,
+ // Arguments passed as --static-args
+ staticArgs: options.staticArgs,
+
+ // Option to prevent automatic kill of firefox during tests
+ noQuit: options.no_quit,
+
+ // Add-on preferences branch name
+ preferencesBranch: options.preferencesBranch,
+
+ // Arguments related to test runner.
+ modules: {
+ '@test/options': {
+ iterations: options.iterations,
+ filter: options.filter,
+ profileMemory: options.profileMemory,
+ stopOnError: options.stopOnError,
+ verbose: options.verbose,
+ parseable: options.parseable,
+ checkMemory: options.check_memory,
+ }
+ }
+ });
+
+ let module = cuddlefish.Module('sdk/loader/cuddlefish', cuddlefishURI);
+ let require = cuddlefish.Require(loader, module);
+
+ // Init the 'sdk/webextension' module from the bootstrap addon parameter.
+ require("sdk/webextension").initFromBootstrapAddonParam(data);
+
+ require('sdk/addon/runner').startup(reason, {
+ loader: loader,
+ main: main,
+ prefsURI: rootURI + 'defaults/preferences/prefs.js'
+ });
+ } catch (error) {
+ dump('Bootstrap error: ' +
+ (error.message ? error.message : String(error)) + '\n' +
+ (error.stack || error.fileName + ': ' + error.lineNumber) + '\n');
+ throw error;
+ }
+};
+
+function loadSandbox(uri) {
+ let proto = {
+ sandboxPrototype: {
+ loadSandbox: loadSandbox,
+ ChromeWorker: ChromeWorker
+ }
+ };
+ let sandbox = Cu.Sandbox(systemPrincipal, proto);
+ // Create a fake commonjs environnement just to enable loading loader.js
+ // correctly
+ sandbox.exports = {};
+ sandbox.module = { uri: uri, exports: sandbox.exports };
+ sandbox.require = function (id) {
+ if (id !== "chrome")
+ throw new Error("Bootstrap sandbox `require` method isn't implemented.");
+
+ return Object.freeze({ Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
+ CC: bind(CC, Components), components: Components,
+ ChromeWorker: ChromeWorker });
+ };
+ scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
+ return sandbox;
+}
+
+function unloadSandbox(sandbox) {
+ if (Cu.getClassName(sandbox, true) == "Sandbox")
+ Cu.nukeSandbox(sandbox);
+}
+
+function setTimeout(callback, delay) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback({ notify: callback }, delay,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+}
+
+function shutdown(data, reasonCode) {
+ let reason = REASON[reasonCode];
+ if (loader) {
+ unload(loader, reason);
+ unload = null;
+
+ // Don't waste time cleaning up if the application is shutting down
+ if (reason != "shutdown") {
+ // Avoid leaking all modules when something goes wrong with one particular
+ // module. Do not clean it up immediatly in order to allow executing some
+ // actions on addon disabling.
+ // We need to keep a reference to the timer, otherwise it is collected
+ // and won't ever fire.
+ nukeTimer = setTimeout(nukeModules, 1000);
+
+ // Bug 944951 - bootstrap.js must remove the added resource: URIs on unload
+ resourceDomains.forEach(domain => {
+ resourceHandler.setSubstitution(domain, null);
+ })
+ }
+ }
+};
+
+function nukeModules() {
+ nukeTimer = null;
+ // module objects store `exports` which comes from sandboxes
+ // We should avoid keeping link to these object to avoid leaking sandboxes
+ for (let key in loader.modules) {
+ delete loader.modules[key];
+ }
+ // Direct links to sandboxes should be removed too
+ for (let key in loader.sandboxes) {
+ let sandbox = loader.sandboxes[key];
+ delete loader.sandboxes[key];
+ // Bug 775067: From FF17 we can kill all CCW from a given sandbox
+ unloadSandbox(sandbox);
+ }
+ unloadSandbox(loader.sharedGlobalSandbox);
+ loader = null;
+
+ // both `toolkit/loader` and `system/xul-app` are loaded as JSM's via
+ // `cuddlefish.js`, and needs to be unloaded to avoid memory leaks, when
+ // the addon is unload.
+
+ unloadSandbox(cuddlefishSandbox.loaderSandbox);
+
+ // Bug 764840: We need to unload cuddlefish otherwise it will stay alive
+ // and keep a reference to this compartment.
+ unloadSandbox(cuddlefishSandbox);
+ cuddlefishSandbox = null;
+}
diff --git a/components/jetpack/app-extension/install.rdf b/components/jetpack/app-extension/install.rdf
new file mode 100644
index 000000000..641d1cc21
--- /dev/null
+++ b/components/jetpack/app-extension/install.rdf
@@ -0,0 +1,33 @@
+<?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:em="http://www.mozilla.org/2004/em-rdf#">
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>xulapp@toolness.com</em:id>
+ <em:version>1.0</em:version>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:unpack>false</em:unpack>
+
+ <!-- Firefox -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>26.0</em:minVersion>
+ <em:maxVersion>30.0</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>Test App</em:name>
+ <em:description>Harness for tests.</em:description>
+ <em:creator>Mozilla Corporation</em:creator>
+ <em:homepageURL></em:homepageURL>
+ <em:optionsType></em:optionsType>
+ <em:optionsURL></em:optionsURL>
+ <em:updateURL></em:updateURL>
+ </Description>
+</RDF>
diff --git a/components/jetpack/dev/debuggee.js b/components/jetpack/dev/debuggee.js
new file mode 100644
index 000000000..0ca0bd37a
--- /dev/null
+++ b/components/jetpack/dev/debuggee.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cu } = require("chrome");
+const { Class } = require("../sdk/core/heritage");
+const { MessagePort, MessageChannel } = require("../sdk/messaging");
+const { require: devtoolsRequire } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { DebuggerServer } = devtoolsRequire("devtools/server/main");
+
+const outputs = new WeakMap();
+const inputs = new WeakMap();
+const targets = new WeakMap();
+const transports = new WeakMap();
+
+const inputFor = port => inputs.get(port);
+const outputFor = port => outputs.get(port);
+const transportFor = port => transports.get(port);
+
+const fromTarget = target => {
+ const debuggee = new Debuggee();
+ const { port1, port2 } = new MessageChannel();
+ inputs.set(debuggee, port1);
+ outputs.set(debuggee, port2);
+ targets.set(debuggee, target);
+
+ return debuggee;
+};
+exports.fromTarget = fromTarget;
+
+const Debuggee = Class({
+ extends: MessagePort.prototype,
+ close: function() {
+ const server = transportFor(this);
+ if (server) {
+ transports.delete(this);
+ server.close();
+ }
+ outputFor(this).close();
+ },
+ start: function() {
+ const target = targets.get(this);
+ if (target.isLocalTab) {
+ // Since a remote protocol connection will be made, let's start the
+ // DebuggerServer here, once and for all tools.
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ transports.set(this, DebuggerServer.connectPipe());
+ }
+ // TODO: Implement support for remote connections (See Bug 980421)
+ else {
+ throw Error("Remote targets are not yet supported");
+ }
+
+ // pipe messages send to the debuggee to an actual
+ // server via remote debugging protocol transport.
+ inputFor(this).addEventListener("message", ({data}) =>
+ transportFor(this).send(data));
+
+ // pipe messages received from the remote debugging
+ // server transport onto the this debuggee.
+ transportFor(this).hooks = {
+ onPacket: packet => inputFor(this).postMessage(packet),
+ onClosed: () => inputFor(this).close()
+ };
+
+ inputFor(this).start();
+ outputFor(this).start();
+ },
+ postMessage: function(data) {
+ return outputFor(this).postMessage(data);
+ },
+ get onmessage() {
+ return outputFor(this).onmessage;
+ },
+ set onmessage(onmessage) {
+ outputFor(this).onmessage = onmessage;
+ },
+ addEventListener: function(...args) {
+ return outputFor(this).addEventListener(...args);
+ },
+ removeEventListener: function(...args) {
+ return outputFor(this).removeEventListener(...args);
+ }
+});
+exports.Debuggee = Debuggee;
diff --git a/components/jetpack/dev/frame-script.js b/components/jetpack/dev/frame-script.js
new file mode 100644
index 000000000..33a197419
--- /dev/null
+++ b/components/jetpack/dev/frame-script.js
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+(function({content, sendSyncMessage, addMessageListener, sendAsyncMessage}) {
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const observerService = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+
+const channels = new Map();
+const handles = new WeakMap();
+
+// Takes remote port handle and creates a local one.
+// also set's up a messaging channel between them.
+// This is temporary workaround until Bug 914974 is fixed
+// and port can be transfered through message manager.
+const demarshal = (handle) => {
+ if (handle.type === "MessagePort") {
+ if (!channels.has(handle.id)) {
+ const channel = new content.MessageChannel();
+ channels.set(handle.id, channel);
+ handles.set(channel.port1, handle);
+ channel.port1.onmessage = onOutPort;
+ }
+ return channels.get(handle.id).port2;
+ }
+ return null;
+};
+
+const onOutPort = event => {
+ const handle = handles.get(event.target);
+ sendAsyncMessage("sdk/port/message", {
+ port: handle,
+ message: event.data
+ });
+};
+
+const onInPort = ({data}) => {
+ const channel = channels.get(data.port.id);
+ if (channel)
+ channel.port1.postMessage(data.message);
+};
+
+const onOutEvent = event =>
+ sendSyncMessage("sdk/event/" + event.type,
+ { type: event.type,
+ data: event.data });
+
+const onInMessage = (message) => {
+ const {type, data, origin, bubbles, cancelable, ports} = message.data;
+
+ const event = new content.MessageEvent(type, {
+ bubbles: bubbles,
+ cancelable: cancelable,
+ data: data,
+ origin: origin,
+ target: content,
+ source: content,
+ ports: ports.map(demarshal)
+ });
+ content.dispatchEvent(event);
+};
+
+const onReady = event => {
+ channels.clear();
+};
+
+addMessageListener("sdk/event/message", onInMessage);
+addMessageListener("sdk/port/message", onInPort);
+
+const observer = {
+ handleEvent: ({target, type}) => {
+ observer.observe(target, type);
+ },
+ observe: (document, topic, data) => {
+ // When frame associated with message manager is removed from document `docShell`
+ // is set to `null` but observer is still kept alive. At this point accesing
+ // `content.document` throws "can't access dead object" exceptions. In order to
+ // avoid leaking observer and logged errors observer is going to be removed when
+ // `docShell` is set to `null`.
+ if (!docShell) {
+ observerService.removeObserver(observer, topic);
+ }
+ else if (document === content.document) {
+ if (topic.endsWith("-document-interactive")) {
+ sendAsyncMessage("sdk/event/ready", {
+ type: "ready",
+ readyState: document.readyState,
+ uri: document.documentURI
+ });
+ }
+ if (topic.endsWith("-document-loaded")) {
+ sendAsyncMessage("sdk/event/load", {
+ type: "load",
+ readyState: document.readyState,
+ uri: document.documentURI
+ });
+ }
+ if (topic === "unload") {
+ channels.clear();
+ sendAsyncMessage("sdk/event/unload", {
+ type: "unload",
+ readyState: "uninitialized",
+ uri: document.documentURI
+ });
+ }
+ }
+ }
+};
+
+observerService.addObserver(observer, "content-document-interactive", false);
+observerService.addObserver(observer, "content-document-loaded", false);
+observerService.addObserver(observer, "chrome-document-interactive", false);
+observerService.addObserver(observer, "chrome-document-loaded", false);
+addEventListener("unload", observer, false);
+
+})(this);
diff --git a/components/jetpack/dev/panel.js b/components/jetpack/dev/panel.js
new file mode 100644
index 000000000..1ef6a303a
--- /dev/null
+++ b/components/jetpack/dev/panel.js
@@ -0,0 +1,259 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cu } = require("chrome");
+const { Class } = require("../sdk/core/heritage");
+const { curry } = require("../sdk/lang/functional");
+const { EventTarget } = require("../sdk/event/target");
+const { Disposable, setup, dispose } = require("../sdk/core/disposable");
+const { emit, off, setListeners } = require("../sdk/event/core");
+const { when } = require("../sdk/event/utils");
+const { getFrameElement } = require("../sdk/window/utils");
+const { contract, validate } = require("../sdk/util/contract");
+const { data: { url: resolve }} = require("../sdk/self");
+const { identify } = require("../sdk/ui/id");
+const { isLocalURL, URL } = require("../sdk/url");
+const { encode } = require("../sdk/base64");
+const { marshal, demarshal } = require("./ports");
+const { fromTarget } = require("./debuggee");
+const { removed } = require("../sdk/dom/events");
+const { id: addonID } = require("../sdk/self");
+const { viewFor } = require("../sdk/view/core");
+const { createView } = require("./panel/view");
+
+const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html");
+const FRAME_SCRIPT = module.uri.replace("/panel.js", "/frame-script.js");
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+const makeID = name =>
+ ("dev-panel-" + addonID + "-" + name).
+ split("/").join("-").
+ split(".").join("-").
+ split(" ").join("-").
+ replace(/[^A-Za-z0-9_\-]/g, "");
+
+
+// Weak mapping between `Panel` instances and their frame's
+// `nsIMessageManager`.
+const managers = new WeakMap();
+// Return `nsIMessageManager` for the given `Panel` instance.
+const managerFor = x => managers.get(x);
+
+// Weak mappinging between iframe's and their owner
+// `Panel` instances.
+const panels = new WeakMap();
+const panelFor = frame => panels.get(frame);
+
+// Weak mapping between panels and debugees they're targeting.
+const debuggees = new WeakMap();
+const debuggeeFor = panel => debuggees.get(panel);
+
+const frames = new WeakMap();
+const frameFor = panel => frames.get(panel);
+
+const setAttributes = (node, attributes) => {
+ for (var key in attributes)
+ node.setAttribute(key, attributes[key]);
+};
+
+const onStateChange = ({target, data}) => {
+ const panel = panelFor(target);
+ panel.readyState = data.readyState;
+ emit(panel, data.type, { target: panel, type: data.type });
+};
+
+// port event listener on the message manager that demarshalls
+// and forwards to the actual receiver. This is a workaround
+// until Bug 914974 is fixed.
+const onPortMessage = ({data, target}) => {
+ const port = demarshal(target, data.port);
+ if (port)
+ port.postMessage(data.message);
+};
+
+// When frame is removed from the toolbox destroy panel
+// associated with it to release all the resources.
+const onFrameRemove = frame => {
+ panelFor(frame).destroy();
+};
+
+const onFrameInited = frame => {
+ frame.style.visibility = "visible";
+}
+
+const inited = frame => new Promise(resolve => {
+ const { messageManager } = frame.frameLoader;
+ const listener = message => {
+ messageManager.removeMessageListener("sdk/event/ready", listener);
+ resolve(frame);
+ };
+ messageManager.addMessageListener("sdk/event/ready", listener);
+});
+
+const getTarget = ({target}) => target;
+
+const Panel = Class({
+ extends: Disposable,
+ implements: [EventTarget],
+ get id() {
+ return makeID(this.name || this.label);
+ },
+ readyState: "uninitialized",
+ ready: function() {
+ const { readyState } = this;
+ const isReady = readyState === "complete" ||
+ readyState === "interactive";
+ return isReady ? Promise.resolve(this) :
+ when(this, "ready").then(getTarget);
+ },
+ loaded: function() {
+ const { readyState } = this;
+ const isLoaded = readyState === "complete";
+ return isLoaded ? Promise.resolve(this) :
+ when(this, "load").then(getTarget);
+ },
+ unloaded: function() {
+ const { readyState } = this;
+ const isUninitialized = readyState === "uninitialized";
+ return isUninitialized ? Promise.resolve(this) :
+ when(this, "unload").then(getTarget);
+ },
+ postMessage: function(data, ports=[]) {
+ const manager = managerFor(this);
+ manager.sendAsyncMessage("sdk/event/message", {
+ type: "message",
+ bubbles: false,
+ cancelable: false,
+ data: data,
+ origin: this.url,
+ ports: ports.map(marshal(manager))
+ });
+ }
+});
+exports.Panel = Panel;
+
+validate.define(Panel, contract({
+ label: {
+ is: ["string"],
+ msg: "The `option.label` must be a provided"
+ },
+ tooltip: {
+ is: ["string", "undefined"],
+ msg: "The `option.tooltip` must be a string"
+ },
+ icon: {
+ is: ["string"],
+ map: x => x && resolve(x),
+ ok: x => isLocalURL(x),
+ msg: "The `options.icon` must be a valid local URI."
+ },
+ url: {
+ map: x => resolve(x.toString()),
+ is: ["string"],
+ ok: x => isLocalURL(x),
+ msg: "The `options.url` must be a valid local URI."
+ },
+ invertIconForLightTheme: {
+ is: ["boolean", "undefined"],
+ msg: "The `options.invertIconForLightTheme` must be a boolean."
+ },
+ invertIconForDarkTheme: {
+ is: ["boolean", "undefined"],
+ msg: "The `options.invertIconForDarkTheme` must be a boolean."
+ }
+}));
+
+setup.define(Panel, (panel, {window, toolbox, url}) => {
+ // Hack: Given that iframe created by devtools API is no good for us,
+ // we obtain original iframe and replace it with the one that has
+ // desired configuration.
+ const original = getFrameElement(window);
+ const container = original.parentNode;
+ original.remove();
+ const frame = createView(panel, container.ownerDocument);
+
+ // Following modifications are a temporary workaround until Bug 1049188
+ // is fixed.
+ // Enforce certain iframe customizations regardless of users request.
+ setAttributes(frame, {
+ "id": original.id,
+ "src": url,
+ "flex": 1,
+ "forceOwnRefreshDriver": "",
+ "tooltip": "aHTMLTooltip"
+ });
+ frame.style.visibility = "hidden";
+ frame.classList.add("toolbox-panel-iframe");
+ // Inject iframe into designated node until add-on author decides
+ // to inject it elsewhere instead.
+ if (!frame.parentNode)
+ container.appendChild(frame);
+
+ // associate view with a panel
+ frames.set(panel, frame);
+
+ // associate panel model with a frame view.
+ panels.set(frame, panel);
+
+ const debuggee = fromTarget(toolbox.target);
+ // associate debuggee with a panel.
+ debuggees.set(panel, debuggee);
+
+
+ // Setup listeners for the frame message manager.
+ const { messageManager } = frame.frameLoader;
+ messageManager.addMessageListener("sdk/event/ready", onStateChange);
+ messageManager.addMessageListener("sdk/event/load", onStateChange);
+ messageManager.addMessageListener("sdk/event/unload", onStateChange);
+ messageManager.addMessageListener("sdk/port/message", onPortMessage);
+ messageManager.loadFrameScript(FRAME_SCRIPT, false);
+
+ managers.set(panel, messageManager);
+
+ // destroy panel if frame is removed.
+ removed(frame).then(onFrameRemove);
+ // show frame when it is initialized.
+ inited(frame).then(onFrameInited);
+
+
+ // set listeners if there are ones defined on the prototype.
+ setListeners(panel, Object.getPrototypeOf(panel));
+
+
+ panel.setup({ debuggee: debuggee });
+});
+
+createView.define(Panel, (panel, document) => {
+ const frame = document.createElement("iframe");
+ setAttributes(frame, {
+ "sandbox": "allow-scripts",
+ // We end up using chrome iframe with forced message manager
+ // as fixing a swapFrameLoader seemed like a giant task (see
+ // Bug 1075490).
+ "type": "chrome",
+ "forcemessagemanager": true,
+ "transparent": true,
+ "seamless": "seamless",
+ });
+ return frame;
+});
+
+dispose.define(Panel, function(panel) {
+ debuggeeFor(panel).close();
+
+ debuggees.delete(panel);
+ managers.delete(panel);
+ frames.delete(panel);
+ panel.readyState = "destroyed";
+ panel.dispose();
+});
+
+viewFor.define(Panel, frameFor);
diff --git a/components/jetpack/dev/panel/view.js b/components/jetpack/dev/panel/view.js
new file mode 100644
index 000000000..41cf9c221
--- /dev/null
+++ b/components/jetpack/dev/panel/view.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { method } = require("method/core");
+
+const createView = method("dev/panel/view#createView");
+exports.createView = createView;
diff --git a/components/jetpack/dev/ports.js b/components/jetpack/dev/ports.js
new file mode 100644
index 000000000..a41f59eb7
--- /dev/null
+++ b/components/jetpack/dev/ports.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+// This module provides `marshal` and `demarshal` functions
+// that can be used to send MessagePort's over `nsIFrameMessageManager`
+// until Bug 914974 is fixed.
+
+const { add, iterator } = require("../sdk/lang/weak-set");
+const { curry } = require("../sdk/lang/functional");
+
+var id = 0;
+const ports = new WeakMap();
+
+// Takes `nsIFrameMessageManager` and `MessagePort` instances
+// and returns a handle representing given `port`. Messages
+// received on given `port` will be forwarded to a message
+// manager under `sdk/port/message` and messages like:
+// { port: { type: "MessagePort", id: 2}, data: data }
+// Where id is an identifier associated with a given `port`
+// and `data` is an `event.data` received on port.
+const marshal = curry((manager, port) => {
+ if (!ports.has(port)) {
+ id = id + 1;
+ const handle = {type: "MessagePort", id: id};
+ // Bind id to the given port
+ ports.set(port, handle);
+
+ // Obtain a weak reference to a port.
+ add(exports, port);
+
+ port.onmessage = event => {
+ manager.sendAsyncMessage("sdk/port/message", {
+ port: handle,
+ message: event.data
+ });
+ };
+
+ return handle;
+ }
+ return ports.get(port);
+});
+exports.marshal = marshal;
+
+// Takes `nsIFrameMessageManager` instance and a handle returned
+// `marshal(manager, port)` returning a `port` that was passed
+// to it. Note that `port` may be GC-ed in which case returned
+// value will be `null`.
+const demarshal = curry((manager, {type, id}) => {
+ if (type === "MessagePort") {
+ for (let port of iterator(exports)) {
+ if (id === ports.get(port).id)
+ return port;
+ }
+ }
+ return null;
+});
+exports.demarshal = demarshal;
diff --git a/components/jetpack/dev/theme.js b/components/jetpack/dev/theme.js
new file mode 100644
index 000000000..05930a502
--- /dev/null
+++ b/components/jetpack/dev/theme.js
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Class } = require("../sdk/core/heritage");
+const { EventTarget } = require("../sdk/event/target");
+const { Disposable, setup, dispose } = require("../sdk/core/disposable");
+const { contract, validate } = require("../sdk/util/contract");
+const { id: addonID } = require("../sdk/self");
+const { onEnable, onDisable } = require("dev/theme/hooks");
+const { isString, instanceOf, isFunction } = require("sdk/lang/type");
+const { add } = require("sdk/util/array");
+const { data } = require("../sdk/self");
+const { isLocalURL } = require("../sdk/url");
+
+const makeID = name =>
+ ("dev-theme-" + addonID + (name ? "-" + name : "")).
+ split(/[ . /]/).join("-").
+ replace(/[^A-Za-z0-9_\-]/g, "");
+
+const Theme = Class({
+ extends: Disposable,
+ implements: [EventTarget],
+
+ initialize: function(options) {
+ this.name = options.name;
+ this.label = options.label;
+ this.styles = options.styles;
+
+ // Event handlers
+ this.onEnable = options.onEnable;
+ this.onDisable = options.onDisable;
+ },
+ get id() {
+ return makeID(this.name || this.label);
+ },
+ setup: function() {
+ // Any initialization steps done at the registration time.
+ },
+ getStyles: function() {
+ if (!this.styles) {
+ return [];
+ }
+
+ if (isString(this.styles)) {
+ if (isLocalURL(this.styles)) {
+ return [data.url(this.styles)];
+ }
+ }
+
+ let result = [];
+ for (let style of this.styles) {
+ if (isString(style)) {
+ if (isLocalURL(style)) {
+ style = data.url(style);
+ }
+ add(result, style);
+ } else if (instanceOf(style, Theme)) {
+ result = result.concat(style.getStyles());
+ }
+ }
+ return result;
+ },
+ getClassList: function() {
+ let result = [];
+ for (let style of this.styles) {
+ if (instanceOf(style, Theme)) {
+ result = result.concat(style.getClassList());
+ }
+ }
+
+ if (this.name) {
+ add(result, this.name);
+ }
+
+ return result;
+ }
+});
+
+exports.Theme = Theme;
+
+// Initialization & dispose
+
+setup.define(Theme, (theme) => {
+ theme.classList = [];
+ theme.setup();
+});
+
+dispose.define(Theme, function(theme) {
+ theme.dispose();
+});
+
+// Validation
+
+validate.define(Theme, contract({
+ label: {
+ is: ["string"],
+ msg: "The `option.label` must be a provided"
+ },
+}));
+
+// Support theme events: apply and unapply the theme.
+
+onEnable.define(Theme, (theme, {window, oldTheme}) => {
+ if (isFunction(theme.onEnable)) {
+ theme.onEnable(window, oldTheme);
+ }
+});
+
+onDisable.define(Theme, (theme, {window, newTheme}) => {
+ if (isFunction(theme.onDisable)) {
+ theme.onDisable(window, newTheme);
+ }
+});
+
+// Support for built-in themes
+
+const LightTheme = Theme({
+ name: "theme-light",
+ styles: "chrome://devtools/skin/light-theme.css",
+});
+
+const DarkTheme = Theme({
+ name: "theme-dark",
+ styles: "chrome://devtools/skin/dark-theme.css",
+});
+
+exports.LightTheme = LightTheme;
+exports.DarkTheme = DarkTheme;
diff --git a/components/jetpack/dev/theme/hooks.js b/components/jetpack/dev/theme/hooks.js
new file mode 100644
index 000000000..9987f853b
--- /dev/null
+++ b/components/jetpack/dev/theme/hooks.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { method } = require("method/core");
+
+const onEnable = method("dev/theme/hooks#onEnable");
+const onDisable = method("dev/theme/hooks#onDisable");
+
+exports.onEnable = onEnable;
+exports.onDisable = onDisable;
diff --git a/components/jetpack/dev/toolbox.js b/components/jetpack/dev/toolbox.js
new file mode 100644
index 000000000..43f37759f
--- /dev/null
+++ b/components/jetpack/dev/toolbox.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cu, Cc, Ci } = require("chrome");
+const { Class } = require("../sdk/core/heritage");
+const { Disposable, setup } = require("../sdk/core/disposable");
+const { contract, validate } = require("../sdk/util/contract");
+const { each, pairs, values } = require("../sdk/util/sequence");
+const { onEnable, onDisable } = require("../dev/theme/hooks");
+
+const { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
+
+// This is temporary workaround to allow loading of the developer tools client - volcan
+// into a toolbox panel, this hack won't be necessary as soon as devtools patch will be
+// shipped in nightly, after which it can be removed. Bug 1038517
+const registerSDKURI = () => {
+ const ioService = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+ const resourceHandler = ioService.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+
+ const uri = module.uri.replace("dev/toolbox.js", "");
+ resourceHandler.setSubstitution("sdk", ioService.newURI(uri, null, null));
+};
+
+registerSDKURI();
+
+const Tool = Class({
+ extends: Disposable,
+ setup: function(params={}) {
+ const { panels } = validate(this, params);
+ const { themes } = validate(this, params);
+
+ this.panels = panels;
+ this.themes = themes;
+
+ each(([key, Panel]) => {
+ const { url, label, tooltip, icon, invertIconForLightTheme,
+ invertIconForDarkTheme } = validate(Panel.prototype);
+ const { id } = Panel.prototype;
+
+ gDevTools.registerTool({
+ id: id,
+ url: "about:blank",
+ label: label,
+ tooltip: tooltip,
+ icon: icon,
+ invertIconForLightTheme: invertIconForLightTheme,
+ invertIconForDarkTheme: invertIconForDarkTheme,
+ isTargetSupported: target => target.isLocalTab,
+ build: (window, toolbox) => {
+ const panel = new Panel();
+ setup(panel, { window: window,
+ toolbox: toolbox,
+ url: url });
+
+ return panel.ready();
+ }
+ });
+ }, pairs(panels));
+
+ each(([key, theme]) => {
+ validate(theme);
+ setup(theme);
+
+ gDevTools.registerTheme({
+ id: theme.id,
+ label: theme.label,
+ stylesheets: theme.getStyles(),
+ classList: theme.getClassList(),
+ onApply: (window, oldTheme) => {
+ onEnable(theme, { window: window,
+ oldTheme: oldTheme });
+ },
+ onUnapply: (window, newTheme) => {
+ onDisable(theme, { window: window,
+ newTheme: newTheme });
+ }
+ });
+ }, pairs(themes));
+ },
+ dispose: function() {
+ each(Panel => gDevTools.unregisterTool(Panel.prototype.id),
+ values(this.panels));
+
+ each(Theme => gDevTools.unregisterTheme(Theme.prototype.id),
+ values(this.themes));
+ }
+});
+
+validate.define(Tool, contract({
+ panels: {
+ is: ["object", "undefined"]
+ },
+ themes: {
+ is: ["object", "undefined"]
+ }
+}));
+
+exports.Tool = Tool;
diff --git a/components/jetpack/dev/utils.js b/components/jetpack/dev/utils.js
new file mode 100644
index 000000000..48db39e04
--- /dev/null
+++ b/components/jetpack/dev/utils.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/. */
+
+"use strict";
+
+const { Cu } = require("chrome");
+const { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
+const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+const { getActiveTab } = require("../sdk/tabs/utils");
+const { getMostRecentBrowserWindow } = require("../sdk/window/utils");
+
+const targetFor = target => {
+ target = target || getActiveTab(getMostRecentBrowserWindow());
+ return devtools.TargetFactory.forTab(target);
+};
+
+const getId = id => ((id.prototype && id.prototype.id) || id.id || id);
+
+const getCurrentPanel = toolbox => toolbox.getCurrentPanel();
+exports.getCurrentPanel = getCurrentPanel;
+
+const openToolbox = (id, tab) => {
+ id = getId(id);
+ return gDevTools.showToolbox(targetFor(tab), id);
+};
+exports.openToolbox = openToolbox;
+
+const closeToolbox = tab => gDevTools.closeToolbox(targetFor(tab));
+exports.closeToolbox = closeToolbox;
+
+const getToolbox = tab => gDevTools.getToolbox(targetFor(tab));
+exports.getToolbox = getToolbox;
+
+const openToolboxPanel = (id, tab) => {
+ id = getId(id);
+ return gDevTools.showToolbox(targetFor(tab), id).then(getCurrentPanel);
+};
+exports.openToolboxPanel = openToolboxPanel;
diff --git a/components/jetpack/dev/volcan.js b/components/jetpack/dev/volcan.js
new file mode 100644
index 000000000..7ec208eec
--- /dev/null
+++ b/components/jetpack/dev/volcan.js
@@ -0,0 +1,3848 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.volcan=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
+"use strict";
+
+var Client = _dereq_("../client").Client;
+
+function connect(port) {
+ var client = new Client();
+ return client.connect(port);
+}
+exports.connect = connect;
+
+},{"../client":4}],2:[function(_dereq_,module,exports){
+"use strict";
+
+exports.Promise = Promise;
+
+},{}],3:[function(_dereq_,module,exports){
+"use strict";
+
+var describe = Object.getOwnPropertyDescriptor;
+var Class = function(fields) {
+ var names = Object.keys(fields);
+ var constructor = names.indexOf("constructor") >= 0 ? fields.constructor :
+ function() {};
+ var ancestor = fields.extends || Object;
+
+ var descriptor = names.reduce(function(descriptor, key) {
+ descriptor[key] = describe(fields, key);
+ return descriptor;
+ }, {});
+
+ var prototype = Object.create(ancestor.prototype, descriptor);
+
+ constructor.prototype = prototype;
+ prototype.constructor = constructor;
+
+ return constructor;
+};
+exports.Class = Class;
+
+},{}],4:[function(_dereq_,module,exports){
+"use strict";
+
+var Class = _dereq_("./class").Class;
+var TypeSystem = _dereq_("./type-system").TypeSystem;
+var values = _dereq_("./util").values;
+var Promise = _dereq_("es6-promise").Promise;
+var MessageEvent = _dereq_("./event").MessageEvent;
+
+var specification = _dereq_("./specification/core.json");
+
+function recoverActorDescriptions(error) {
+ console.warn("Failed to fetch protocol specification (see reason below). " +
+ "Using a fallback protocal specification!",
+ error);
+ return _dereq_("./specification/protocol.json");
+}
+
+// Type to represent superviser actor relations to actors they supervise
+// in terms of lifetime management.
+var Supervisor = Class({
+ constructor: function(id) {
+ this.id = id;
+ this.workers = [];
+ }
+});
+
+var Telemetry = Class({
+ add: function(id, ms) {
+ console.log("telemetry::", id, ms)
+ }
+});
+
+// Consider making client a root actor.
+
+var Client = Class({
+ constructor: function() {
+ this.root = null;
+ this.telemetry = new Telemetry();
+
+ this.setupConnection();
+ this.setupLifeManagement();
+ this.setupTypeSystem();
+ },
+
+ setupConnection: function() {
+ this.requests = [];
+ },
+ setupLifeManagement: function() {
+ this.cache = Object.create(null);
+ this.graph = Object.create(null);
+ this.get = this.get.bind(this);
+ this.release = this.release.bind(this);
+ },
+ setupTypeSystem: function() {
+ this.typeSystem = new TypeSystem(this);
+ this.typeSystem.registerTypes(specification);
+ },
+
+ connect: function(port) {
+ var client = this;
+ return new Promise(function(resolve, reject) {
+ client.port = port;
+ port.onmessage = client.receive.bind(client);
+ client.onReady = resolve;
+ client.onFail = reject;
+
+ port.start();
+ });
+ },
+ send: function(packet) {
+ this.port.postMessage(packet);
+ },
+ request: function(packet) {
+ var client = this;
+ return new Promise(function(resolve, reject) {
+ client.requests.push(packet.to, { resolve: resolve, reject: reject });
+ client.send(packet);
+ });
+ },
+
+ receive: function(event) {
+ var packet = event.data;
+ if (!this.root) {
+ if (packet.from !== "root")
+ throw Error("Initial packet must be from root");
+ if (!("applicationType" in packet))
+ throw Error("Initial packet must contain applicationType field");
+
+ this.root = this.typeSystem.read("root", null, "root");
+ this.root
+ .protocolDescription()
+ .catch(recoverActorDescriptions)
+ .then(this.typeSystem.registerTypes.bind(this.typeSystem))
+ .then(this.onReady.bind(this, this.root), this.onFail);
+ } else {
+ var actor = this.get(packet.from) || this.root;
+ event = actor.events[packet.type];
+ if (event) {
+ var message = new MessageEvent(packet.type, {
+ data: event.read(packet)
+ });
+ actor.dispatchEvent(message);
+ } else {
+ var index = this.requests.indexOf(actor.id);
+ if (index >= 0) {
+ var request = this.requests.splice(index, 2).pop();
+ if (packet.error)
+ request.reject(packet);
+ else
+ request.resolve(packet);
+ } else {
+ console.error(Error("Unexpected packet " + JSON.stringify(packet, 2, 2)),
+ packet,
+ this.requests.slice(0));
+ }
+ }
+ }
+ },
+
+ get: function(id) {
+ return this.cache[id];
+ },
+ supervisorOf: function(actor) {
+ for (var id in this.graph) {
+ if (this.graph[id].indexOf(actor.id) >= 0) {
+ return id;
+ }
+ }
+ },
+ workersOf: function(actor) {
+ return this.graph[actor.id];
+ },
+ supervise: function(actor, worker) {
+ var workers = this.workersOf(actor)
+ if (workers.indexOf(worker.id) < 0) {
+ workers.push(worker.id);
+ }
+ },
+ unsupervise: function(actor, worker) {
+ var workers = this.workersOf(actor);
+ var index = workers.indexOf(worker.id)
+ if (index >= 0) {
+ workers.splice(index, 1)
+ }
+ },
+
+ register: function(actor) {
+ var registered = this.get(actor.id);
+ if (!registered) {
+ this.cache[actor.id] = actor;
+ this.graph[actor.id] = [];
+ } else if (registered !== actor) {
+ throw new Error("Different actor with same id is already registered");
+ }
+ },
+ unregister: function(actor) {
+ if (this.get(actor.id)) {
+ delete this.cache[actor.id];
+ delete this.graph[actor.id];
+ }
+ },
+
+ release: function(actor) {
+ var supervisor = this.supervisorOf(actor);
+ if (supervisor)
+ this.unsupervise(supervisor, actor);
+
+ var workers = this.workersOf(actor)
+
+ if (workers) {
+ workers.map(this.get).forEach(this.release)
+ }
+ this.unregister(actor);
+ }
+});
+exports.Client = Client;
+
+},{"./class":3,"./event":5,"./specification/core.json":23,"./specification/protocol.json":24,"./type-system":25,"./util":26,"es6-promise":2}],5:[function(_dereq_,module,exports){
+"use strict";
+
+var Symbol = _dereq_("es6-symbol")
+var EventEmitter = _dereq_("events").EventEmitter;
+var Class = _dereq_("./class").Class;
+
+var $bound = Symbol("EventTarget/handleEvent");
+var $emitter = Symbol("EventTarget/emitter");
+
+function makeHandler(handler) {
+ return function(event) {
+ handler.handleEvent(event);
+ }
+}
+
+var EventTarget = Class({
+ constructor: function() {
+ Object.defineProperty(this, $emitter, {
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ value: new EventEmitter()
+ });
+ },
+ addEventListener: function(type, handler) {
+ if (typeof(handler) === "function") {
+ this[$emitter].on(type, handler);
+ }
+ else if (handler && typeof(handler) === "object") {
+ if (!handler[$bound]) handler[$bound] = makeHandler(handler);
+ this[$emitter].on(type, handler[$bound]);
+ }
+ },
+ removeEventListener: function(type, handler) {
+ if (typeof(handler) === "function")
+ this[$emitter].removeListener(type, handler);
+ else if (handler && handler[$bound])
+ this[$emitter].removeListener(type, handler[$bound]);
+ },
+ dispatchEvent: function(event) {
+ event.target = this;
+ this[$emitter].emit(event.type, event);
+ }
+});
+exports.EventTarget = EventTarget;
+
+var MessageEvent = Class({
+ constructor: function(type, options) {
+ options = options || {};
+ this.type = type;
+ this.data = options.data === void(0) ? null : options.data;
+
+ this.lastEventId = options.lastEventId || "";
+ this.origin = options.origin || "";
+ this.bubbles = options.bubbles || false;
+ this.cancelable = options.cancelable || false;
+ },
+ source: null,
+ ports: null,
+ preventDefault: function() {
+ },
+ stopPropagation: function() {
+ },
+ stopImmediatePropagation: function() {
+ }
+});
+exports.MessageEvent = MessageEvent;
+
+},{"./class":3,"es6-symbol":7,"events":6}],6:[function(_dereq_,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+function EventEmitter() {
+ this._events = this._events || {};
+ this._maxListeners = this._maxListeners || undefined;
+}
+module.exports = EventEmitter;
+
+// Backwards-compat with node 0.10.x
+EventEmitter.EventEmitter = EventEmitter;
+
+EventEmitter.prototype._events = undefined;
+EventEmitter.prototype._maxListeners = undefined;
+
+// By default EventEmitters will print a warning if more than 10 listeners are
+// added to it. This is a useful default which helps finding memory leaks.
+EventEmitter.defaultMaxListeners = 10;
+
+// Obviously not all Emitters should be limited to 10. This function allows
+// that to be increased. Set to zero for unlimited.
+EventEmitter.prototype.setMaxListeners = function(n) {
+ if (!isNumber(n) || n < 0 || isNaN(n))
+ throw TypeError('n must be a positive number');
+ this._maxListeners = n;
+ return this;
+};
+
+EventEmitter.prototype.emit = function(type) {
+ var er, handler, len, args, i, listeners;
+
+ if (!this._events)
+ this._events = {};
+
+ // If there is no 'error' event listener then throw.
+ if (type === 'error') {
+ if (!this._events.error ||
+ (isObject(this._events.error) && !this._events.error.length)) {
+ er = arguments[1];
+ if (er instanceof Error) {
+ throw er; // Unhandled 'error' event
+ } else {
+ throw TypeError('Uncaught, unspecified "error" event.');
+ }
+ return false;
+ }
+ }
+
+ handler = this._events[type];
+
+ if (isUndefined(handler))
+ return false;
+
+ if (isFunction(handler)) {
+ switch (arguments.length) {
+ // fast cases
+ case 1:
+ handler.call(this);
+ break;
+ case 2:
+ handler.call(this, arguments[1]);
+ break;
+ case 3:
+ handler.call(this, arguments[1], arguments[2]);
+ break;
+ // slower
+ default:
+ len = arguments.length;
+ args = new Array(len - 1);
+ for (i = 1; i < len; i++)
+ args[i - 1] = arguments[i];
+ handler.apply(this, args);
+ }
+ } else if (isObject(handler)) {
+ len = arguments.length;
+ args = new Array(len - 1);
+ for (i = 1; i < len; i++)
+ args[i - 1] = arguments[i];
+
+ listeners = handler.slice();
+ len = listeners.length;
+ for (i = 0; i < len; i++)
+ listeners[i].apply(this, args);
+ }
+
+ return true;
+};
+
+EventEmitter.prototype.addListener = function(type, listener) {
+ var m;
+
+ if (!isFunction(listener))
+ throw TypeError('listener must be a function');
+
+ if (!this._events)
+ this._events = {};
+
+ // To avoid recursion in the case that type === "newListener"! Before
+ // adding it to the listeners, first emit "newListener".
+ if (this._events.newListener)
+ this.emit('newListener', type,
+ isFunction(listener.listener) ?
+ listener.listener : listener);
+
+ if (!this._events[type])
+ // Optimize the case of one listener. Don't need the extra array object.
+ this._events[type] = listener;
+ else if (isObject(this._events[type]))
+ // If we've already got an array, just append.
+ this._events[type].push(listener);
+ else
+ // Adding the second element, need to change to array.
+ this._events[type] = [this._events[type], listener];
+
+ // Check for listener leak
+ if (isObject(this._events[type]) && !this._events[type].warned) {
+ var m;
+ if (!isUndefined(this._maxListeners)) {
+ m = this._maxListeners;
+ } else {
+ m = EventEmitter.defaultMaxListeners;
+ }
+
+ if (m && m > 0 && this._events[type].length > m) {
+ this._events[type].warned = true;
+ console.error('(node) warning: possible EventEmitter memory ' +
+ 'leak detected. %d listeners added. ' +
+ 'Use emitter.setMaxListeners() to increase limit.',
+ this._events[type].length);
+ if (typeof console.trace === 'function') {
+ // not supported in IE 10
+ console.trace();
+ }
+ }
+ }
+
+ return this;
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.once = function(type, listener) {
+ if (!isFunction(listener))
+ throw TypeError('listener must be a function');
+
+ var fired = false;
+
+ function g() {
+ this.removeListener(type, g);
+
+ if (!fired) {
+ fired = true;
+ listener.apply(this, arguments);
+ }
+ }
+
+ g.listener = listener;
+ this.on(type, g);
+
+ return this;
+};
+
+// emits a 'removeListener' event iff the listener was removed
+EventEmitter.prototype.removeListener = function(type, listener) {
+ var list, position, length, i;
+
+ if (!isFunction(listener))
+ throw TypeError('listener must be a function');
+
+ if (!this._events || !this._events[type])
+ return this;
+
+ list = this._events[type];
+ length = list.length;
+ position = -1;
+
+ if (list === listener ||
+ (isFunction(list.listener) && list.listener === listener)) {
+ delete this._events[type];
+ if (this._events.removeListener)
+ this.emit('removeListener', type, listener);
+
+ } else if (isObject(list)) {
+ for (i = length; i-- > 0;) {
+ if (list[i] === listener ||
+ (list[i].listener && list[i].listener === listener)) {
+ position = i;
+ break;
+ }
+ }
+
+ if (position < 0)
+ return this;
+
+ if (list.length === 1) {
+ list.length = 0;
+ delete this._events[type];
+ } else {
+ list.splice(position, 1);
+ }
+
+ if (this._events.removeListener)
+ this.emit('removeListener', type, listener);
+ }
+
+ return this;
+};
+
+EventEmitter.prototype.removeAllListeners = function(type) {
+ var key, listeners;
+
+ if (!this._events)
+ return this;
+
+ // not listening for removeListener, no need to emit
+ if (!this._events.removeListener) {
+ if (arguments.length === 0)
+ this._events = {};
+ else if (this._events[type])
+ delete this._events[type];
+ return this;
+ }
+
+ // emit removeListener for all listeners on all events
+ if (arguments.length === 0) {
+ for (key in this._events) {
+ if (key === 'removeListener') continue;
+ this.removeAllListeners(key);
+ }
+ this.removeAllListeners('removeListener');
+ this._events = {};
+ return this;
+ }
+
+ listeners = this._events[type];
+
+ if (isFunction(listeners)) {
+ this.removeListener(type, listeners);
+ } else {
+ // LIFO order
+ while (listeners.length)
+ this.removeListener(type, listeners[listeners.length - 1]);
+ }
+ delete this._events[type];
+
+ return this;
+};
+
+EventEmitter.prototype.listeners = function(type) {
+ var ret;
+ if (!this._events || !this._events[type])
+ ret = [];
+ else if (isFunction(this._events[type]))
+ ret = [this._events[type]];
+ else
+ ret = this._events[type].slice();
+ return ret;
+};
+
+EventEmitter.listenerCount = function(emitter, type) {
+ var ret;
+ if (!emitter._events || !emitter._events[type])
+ ret = 0;
+ else if (isFunction(emitter._events[type]))
+ ret = 1;
+ else
+ ret = emitter._events[type].length;
+ return ret;
+};
+
+function isFunction(arg) {
+ return typeof arg === 'function';
+}
+
+function isNumber(arg) {
+ return typeof arg === 'number';
+}
+
+function isObject(arg) {
+ return typeof arg === 'object' && arg !== null;
+}
+
+function isUndefined(arg) {
+ return arg === void 0;
+}
+
+},{}],7:[function(_dereq_,module,exports){
+'use strict';
+
+module.exports = _dereq_('./is-implemented')() ? Symbol : _dereq_('./polyfill');
+
+},{"./is-implemented":8,"./polyfill":22}],8:[function(_dereq_,module,exports){
+'use strict';
+
+module.exports = function () {
+ var symbol;
+ if (typeof Symbol !== 'function') return false;
+ symbol = Symbol('test symbol');
+ try {
+ if (String(symbol) !== 'Symbol (test symbol)') return false;
+ } catch (e) { return false; }
+ if (typeof Symbol.iterator === 'symbol') return true;
+
+ // Return 'true' for polyfills
+ if (typeof Symbol.isConcatSpreadable !== 'object') return false;
+ if (typeof Symbol.isRegExp !== 'object') return false;
+ if (typeof Symbol.iterator !== 'object') return false;
+ if (typeof Symbol.toPrimitive !== 'object') return false;
+ if (typeof Symbol.toStringTag !== 'object') return false;
+ if (typeof Symbol.unscopables !== 'object') return false;
+
+ return true;
+};
+
+},{}],9:[function(_dereq_,module,exports){
+'use strict';
+
+var assign = _dereq_('es5-ext/object/assign')
+ , normalizeOpts = _dereq_('es5-ext/object/normalize-options')
+ , isCallable = _dereq_('es5-ext/object/is-callable')
+ , contains = _dereq_('es5-ext/string/#/contains')
+
+ , d;
+
+d = module.exports = function (dscr, value/*, options*/) {
+ var c, e, w, options, desc;
+ if ((arguments.length < 2) || (typeof dscr !== 'string')) {
+ options = value;
+ value = dscr;
+ dscr = null;
+ } else {
+ options = arguments[2];
+ }
+ if (dscr == null) {
+ c = w = true;
+ e = false;
+ } else {
+ c = contains.call(dscr, 'c');
+ e = contains.call(dscr, 'e');
+ w = contains.call(dscr, 'w');
+ }
+
+ desc = { value: value, configurable: c, enumerable: e, writable: w };
+ return !options ? desc : assign(normalizeOpts(options), desc);
+};
+
+d.gs = function (dscr, get, set/*, options*/) {
+ var c, e, options, desc;
+ if (typeof dscr !== 'string') {
+ options = set;
+ set = get;
+ get = dscr;
+ dscr = null;
+ } else {
+ options = arguments[3];
+ }
+ if (get == null) {
+ get = undefined;
+ } else if (!isCallable(get)) {
+ options = get;
+ get = set = undefined;
+ } else if (set == null) {
+ set = undefined;
+ } else if (!isCallable(set)) {
+ options = set;
+ set = undefined;
+ }
+ if (dscr == null) {
+ c = true;
+ e = false;
+ } else {
+ c = contains.call(dscr, 'c');
+ e = contains.call(dscr, 'e');
+ }
+
+ desc = { get: get, set: set, configurable: c, enumerable: e };
+ return !options ? desc : assign(normalizeOpts(options), desc);
+};
+
+},{"es5-ext/object/assign":10,"es5-ext/object/is-callable":13,"es5-ext/object/normalize-options":17,"es5-ext/string/#/contains":19}],10:[function(_dereq_,module,exports){
+'use strict';
+
+module.exports = _dereq_('./is-implemented')()
+ ? Object.assign
+ : _dereq_('./shim');
+
+},{"./is-implemented":11,"./shim":12}],11:[function(_dereq_,module,exports){
+'use strict';
+
+module.exports = function () {
+ var assign = Object.assign, obj;
+ if (typeof assign !== 'function') return false;
+ obj = { foo: 'raz' };
+ assign(obj, { bar: 'dwa' }, { trzy: 'trzy' });
+ return (obj.foo + obj.bar + obj.trzy) === 'razdwatrzy';
+};
+
+},{}],12:[function(_dereq_,module,exports){
+'use strict';
+
+var keys = _dereq_('../keys')
+ , value = _dereq_('../valid-value')
+
+ , max = Math.max;
+
+module.exports = function (dest, src/*, …srcn*/) {
+ var error, i, l = max(arguments.length, 2), assign;
+ dest = Object(value(dest));
+ assign = function (key) {
+ try { dest[key] = src[key]; } catch (e) {
+ if (!error) error = e;
+ }
+ };
+ for (i = 1; i < l; ++i) {
+ src = arguments[i];
+ keys(src).forEach(assign);
+ }
+ if (error !== undefined) throw error;
+ return dest;
+};
+
+},{"../keys":14,"../valid-value":18}],13:[function(_dereq_,module,exports){
+// Deprecated
+
+'use strict';
+
+module.exports = function (obj) { return typeof obj === 'function'; };
+
+},{}],14:[function(_dereq_,module,exports){
+'use strict';
+
+module.exports = _dereq_('./is-implemented')()
+ ? Object.keys
+ : _dereq_('./shim');
+
+},{"./is-implemented":15,"./shim":16}],15:[function(_dereq_,module,exports){
+'use strict';
+
+module.exports = function () {
+ try {
+ Object.keys('primitive');
+ return true;
+ } catch (e) { return false; }
+};
+
+},{}],16:[function(_dereq_,module,exports){
+'use strict';
+
+var keys = Object.keys;
+
+module.exports = function (object) {
+ return keys(object == null ? object : Object(object));
+};
+
+},{}],17:[function(_dereq_,module,exports){
+'use strict';
+
+var assign = _dereq_('./assign')
+
+ , forEach = Array.prototype.forEach
+ , create = Object.create, getPrototypeOf = Object.getPrototypeOf
+
+ , process;
+
+process = function (src, obj) {
+ var proto = getPrototypeOf(src);
+ return assign(proto ? process(proto, obj) : obj, src);
+};
+
+module.exports = function (options/*, …options*/) {
+ var result = create(null);
+ forEach.call(arguments, function (options) {
+ if (options == null) return;
+ process(Object(options), result);
+ });
+ return result;
+};
+
+},{"./assign":10}],18:[function(_dereq_,module,exports){
+'use strict';
+
+module.exports = function (value) {
+ if (value == null) throw new TypeError("Cannot use null or undefined");
+ return value;
+};
+
+},{}],19:[function(_dereq_,module,exports){
+'use strict';
+
+module.exports = _dereq_('./is-implemented')()
+ ? String.prototype.contains
+ : _dereq_('./shim');
+
+},{"./is-implemented":20,"./shim":21}],20:[function(_dereq_,module,exports){
+'use strict';
+
+var str = 'razdwatrzy';
+
+module.exports = function () {
+ if (typeof str.contains !== 'function') return false;
+ return ((str.contains('dwa') === true) && (str.contains('foo') === false));
+};
+
+},{}],21:[function(_dereq_,module,exports){
+'use strict';
+
+var indexOf = String.prototype.indexOf;
+
+module.exports = function (searchString/*, position*/) {
+ return indexOf.call(this, searchString, arguments[1]) > -1;
+};
+
+},{}],22:[function(_dereq_,module,exports){
+'use strict';
+
+var d = _dereq_('d')
+
+ , create = Object.create, defineProperties = Object.defineProperties
+ , generateName, Symbol;
+
+generateName = (function () {
+ var created = create(null);
+ return function (desc) {
+ var postfix = 0;
+ while (created[desc + (postfix || '')]) ++postfix;
+ desc += (postfix || '');
+ created[desc] = true;
+ return '@@' + desc;
+ };
+}());
+
+module.exports = Symbol = function (description) {
+ var symbol;
+ if (this instanceof Symbol) {
+ throw new TypeError('TypeError: Symbol is not a constructor');
+ }
+ symbol = create(Symbol.prototype);
+ description = (description === undefined ? '' : String(description));
+ return defineProperties(symbol, {
+ __description__: d('', description),
+ __name__: d('', generateName(description))
+ });
+};
+
+Object.defineProperties(Symbol, {
+ create: d('', Symbol('create')),
+ hasInstance: d('', Symbol('hasInstance')),
+ isConcatSpreadable: d('', Symbol('isConcatSpreadable')),
+ isRegExp: d('', Symbol('isRegExp')),
+ iterator: d('', Symbol('iterator')),
+ toPrimitive: d('', Symbol('toPrimitive')),
+ toStringTag: d('', Symbol('toStringTag')),
+ unscopables: d('', Symbol('unscopables'))
+});
+
+defineProperties(Symbol.prototype, {
+ properToString: d(function () {
+ return 'Symbol (' + this.__description__ + ')';
+ }),
+ toString: d('', function () { return this.__name__; })
+});
+Object.defineProperty(Symbol.prototype, Symbol.toPrimitive, d('',
+ function (hint) {
+ throw new TypeError("Conversion of symbol objects is not allowed");
+ }));
+Object.defineProperty(Symbol.prototype, Symbol.toStringTag, d('c', 'Symbol'));
+
+},{"d":9}],23:[function(_dereq_,module,exports){
+module.exports={
+ "types": {
+ "root": {
+ "category": "actor",
+ "typeName": "root",
+ "methods": [
+ {
+ "name": "echo",
+ "request": {
+ "string": { "_arg": 0, "type": "string" }
+ },
+ "response": {
+ "string": { "_retval": "string" }
+ }
+ },
+ {
+ "name": "listTabs",
+ "request": {},
+ "response": { "_retval": "tablist" }
+ },
+ {
+ "name": "protocolDescription",
+ "request": {},
+ "response": { "_retval": "json" }
+ }
+ ],
+ "events": {
+ "tabListChanged": {}
+ }
+ },
+ "tablist": {
+ "category": "dict",
+ "typeName": "tablist",
+ "specializations": {
+ "selected": "number",
+ "tabs": "array:tab",
+ "url": "string",
+ "consoleActor": "console",
+ "inspectorActor": "inspector",
+ "styleSheetsActor": "stylesheets",
+ "styleEditorActor": "styleeditor",
+ "memoryActor": "memory",
+ "eventLoopLagActor": "eventLoopLag",
+ "preferenceActor": "preference",
+ "deviceActor": "device",
+
+ "profilerActor": "profiler",
+ "chromeDebugger": "chromeDebugger",
+ "webappsActor": "webapps"
+ }
+ },
+ "tab": {
+ "category": "actor",
+ "typeName": "tab",
+ "fields": {
+ "title": "string",
+ "url": "string",
+ "outerWindowID": "number",
+ "inspectorActor": "inspector",
+ "callWatcherActor": "call-watcher",
+ "canvasActor": "canvas",
+ "webglActor": "webgl",
+ "webaudioActor": "webaudio",
+ "storageActor": "storage",
+ "gcliActor": "gcli",
+ "memoryActor": "memory",
+ "eventLoopLag": "eventLoopLag",
+ "styleSheetsActor": "stylesheets",
+ "styleEditorActor": "styleeditor",
+
+ "consoleActor": "console",
+ "traceActor": "trace"
+ },
+ "methods": [
+ {
+ "name": "attach",
+ "request": {},
+ "response": { "_retval": "json" }
+ }
+ ],
+ "events": {
+ "tabNavigated": {
+ "typeName": "tabNavigated"
+ }
+ }
+ },
+ "console": {
+ "category": "actor",
+ "typeName": "console",
+ "methods": [
+ {
+ "name": "evaluateJS",
+ "request": {
+ "text": {
+ "_option": 0,
+ "type": "string"
+ },
+ "url": {
+ "_option": 1,
+ "type": "string"
+ },
+ "bindObjectActor": {
+ "_option": 2,
+ "type": "nullable:string"
+ },
+ "frameActor": {
+ "_option": 2,
+ "type": "nullable:string"
+ },
+ "selectedNodeActor": {
+ "_option": 2,
+ "type": "nullable:string"
+ }
+ },
+ "response": {
+ "_retval": "evaluatejsresponse"
+ }
+ }
+ ],
+ "events": {}
+ },
+ "evaluatejsresponse": {
+ "category": "dict",
+ "typeName": "evaluatejsresponse",
+ "specializations": {
+ "result": "object",
+ "exception": "object",
+ "exceptionMessage": "string",
+ "input": "string"
+ }
+ },
+ "object": {
+ "category": "actor",
+ "typeName": "object",
+ "methods": [
+ {
+ "name": "property",
+ "request": {
+ "name": {
+ "_arg": 0,
+ "type": "string"
+ }
+ },
+ "response": {
+ "descriptor": {
+ "_retval": "json"
+ }
+ }
+ }
+ ]
+ }
+ }
+}
+
+},{}],24:[function(_dereq_,module,exports){
+module.exports={
+ "types": {
+ "longstractor": {
+ "category": "actor",
+ "typeName": "longstractor",
+ "methods": [
+ {
+ "name": "substring",
+ "request": {
+ "type": "substring",
+ "start": {
+ "_arg": 0,
+ "type": "primitive"
+ },
+ "end": {
+ "_arg": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "substring": {
+ "_retval": "primitive"
+ }
+ }
+ },
+ {
+ "name": "release",
+ "release": true,
+ "request": {
+ "type": "release"
+ },
+ "response": {}
+ }
+ ],
+ "events": {}
+ },
+ "stylesheet": {
+ "category": "actor",
+ "typeName": "stylesheet",
+ "methods": [
+ {
+ "name": "toggleDisabled",
+ "request": {
+ "type": "toggleDisabled"
+ },
+ "response": {
+ "disabled": {
+ "_retval": "boolean"
+ }
+ }
+ },
+ {
+ "name": "getText",
+ "request": {
+ "type": "getText"
+ },
+ "response": {
+ "text": {
+ "_retval": "longstring"
+ }
+ }
+ },
+ {
+ "name": "getOriginalSources",
+ "request": {
+ "type": "getOriginalSources"
+ },
+ "response": {
+ "originalSources": {
+ "_retval": "nullable:array:originalsource"
+ }
+ }
+ },
+ {
+ "name": "getOriginalLocation",
+ "request": {
+ "type": "getOriginalLocation",
+ "line": {
+ "_arg": 0,
+ "type": "number"
+ },
+ "column": {
+ "_arg": 1,
+ "type": "number"
+ }
+ },
+ "response": {
+ "_retval": "originallocationresponse"
+ }
+ },
+ {
+ "name": "update",
+ "request": {
+ "type": "update",
+ "text": {
+ "_arg": 0,
+ "type": "string"
+ },
+ "transition": {
+ "_arg": 1,
+ "type": "boolean"
+ }
+ },
+ "response": {}
+ }
+ ],
+ "events": {
+ "property-change": {
+ "type": "propertyChange",
+ "property": {
+ "_arg": 0,
+ "type": "string"
+ },
+ "value": {
+ "_arg": 1,
+ "type": "json"
+ }
+ },
+ "style-applied": {
+ "type": "styleApplied"
+ }
+ }
+ },
+ "originalsource": {
+ "category": "actor",
+ "typeName": "originalsource",
+ "methods": [
+ {
+ "name": "getText",
+ "request": {
+ "type": "getText"
+ },
+ "response": {
+ "text": {
+ "_retval": "longstring"
+ }
+ }
+ }
+ ],
+ "events": {}
+ },
+ "stylesheets": {
+ "category": "actor",
+ "typeName": "stylesheets",
+ "methods": [
+ {
+ "name": "getStyleSheets",
+ "request": {
+ "type": "getStyleSheets"
+ },
+ "response": {
+ "styleSheets": {
+ "_retval": "array:stylesheet"
+ }
+ }
+ },
+ {
+ "name": "addStyleSheet",
+ "request": {
+ "type": "addStyleSheet",
+ "text": {
+ "_arg": 0,
+ "type": "string"
+ }
+ },
+ "response": {
+ "styleSheet": {
+ "_retval": "stylesheet"
+ }
+ }
+ }
+ ],
+ "events": {}
+ },
+ "originallocationresponse": {
+ "category": "dict",
+ "typeName": "originallocationresponse",
+ "specializations": {
+ "source": "string",
+ "line": "number",
+ "column": "number"
+ }
+ },
+ "domnode": {
+ "category": "actor",
+ "typeName": "domnode",
+ "methods": [
+ {
+ "name": "getNodeValue",
+ "request": {
+ "type": "getNodeValue"
+ },
+ "response": {
+ "value": {
+ "_retval": "longstring"
+ }
+ }
+ },
+ {
+ "name": "setNodeValue",
+ "request": {
+ "type": "setNodeValue",
+ "value": {
+ "_arg": 0,
+ "type": "primitive"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "getImageData",
+ "request": {
+ "type": "getImageData",
+ "maxDim": {
+ "_arg": 0,
+ "type": "nullable:number"
+ }
+ },
+ "response": {
+ "_retval": "imageData"
+ }
+ },
+ {
+ "name": "modifyAttributes",
+ "request": {
+ "type": "modifyAttributes",
+ "modifications": {
+ "_arg": 0,
+ "type": "array:json"
+ }
+ },
+ "response": {}
+ }
+ ],
+ "events": {}
+ },
+ "appliedstyle": {
+ "category": "dict",
+ "typeName": "appliedstyle",
+ "specializations": {
+ "rule": "domstylerule#actorid",
+ "inherited": "nullable:domnode#actorid"
+ }
+ },
+ "matchedselector": {
+ "category": "dict",
+ "typeName": "matchedselector",
+ "specializations": {
+ "rule": "domstylerule#actorid",
+ "selector": "string",
+ "value": "string",
+ "status": "number"
+ }
+ },
+ "matchedselectorresponse": {
+ "category": "dict",
+ "typeName": "matchedselectorresponse",
+ "specializations": {
+ "rules": "array:domstylerule",
+ "sheets": "array:stylesheet",
+ "matched": "array:matchedselector"
+ }
+ },
+ "appliedStylesReturn": {
+ "category": "dict",
+ "typeName": "appliedStylesReturn",
+ "specializations": {
+ "entries": "array:appliedstyle",
+ "rules": "array:domstylerule",
+ "sheets": "array:stylesheet"
+ }
+ },
+ "pagestyle": {
+ "category": "actor",
+ "typeName": "pagestyle",
+ "methods": [
+ {
+ "name": "getComputed",
+ "request": {
+ "type": "getComputed",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "markMatched": {
+ "_option": 1,
+ "type": "boolean"
+ },
+ "onlyMatched": {
+ "_option": 1,
+ "type": "boolean"
+ },
+ "filter": {
+ "_option": 1,
+ "type": "string"
+ }
+ },
+ "response": {
+ "computed": {
+ "_retval": "json"
+ }
+ }
+ },
+ {
+ "name": "getMatchedSelectors",
+ "request": {
+ "type": "getMatchedSelectors",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "property": {
+ "_arg": 1,
+ "type": "string"
+ },
+ "filter": {
+ "_option": 2,
+ "type": "string"
+ }
+ },
+ "response": {
+ "_retval": "matchedselectorresponse"
+ }
+ },
+ {
+ "name": "getApplied",
+ "request": {
+ "type": "getApplied",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "inherited": {
+ "_option": 1,
+ "type": "boolean"
+ },
+ "matchedSelectors": {
+ "_option": 1,
+ "type": "boolean"
+ },
+ "filter": {
+ "_option": 1,
+ "type": "string"
+ }
+ },
+ "response": {
+ "_retval": "appliedStylesReturn"
+ }
+ },
+ {
+ "name": "getLayout",
+ "request": {
+ "type": "getLayout",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "autoMargins": {
+ "_option": 1,
+ "type": "boolean"
+ }
+ },
+ "response": {
+ "_retval": "json"
+ }
+ }
+ ],
+ "events": {}
+ },
+ "domstylerule": {
+ "category": "actor",
+ "typeName": "domstylerule",
+ "methods": [
+ {
+ "name": "modifyProperties",
+ "request": {
+ "type": "modifyProperties",
+ "modifications": {
+ "_arg": 0,
+ "type": "array:json"
+ }
+ },
+ "response": {
+ "rule": {
+ "_retval": "domstylerule"
+ }
+ }
+ }
+ ],
+ "events": {}
+ },
+ "highlighter": {
+ "category": "actor",
+ "typeName": "highlighter",
+ "methods": [
+ {
+ "name": "showBoxModel",
+ "request": {
+ "type": "showBoxModel",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "region": {
+ "_option": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "hideBoxModel",
+ "request": {
+ "type": "hideBoxModel"
+ },
+ "response": {}
+ },
+ {
+ "name": "pick",
+ "request": {
+ "type": "pick"
+ },
+ "response": {}
+ },
+ {
+ "name": "cancelPick",
+ "request": {
+ "type": "cancelPick"
+ },
+ "response": {}
+ }
+ ],
+ "events": {}
+ },
+ "imageData": {
+ "category": "dict",
+ "typeName": "imageData",
+ "specializations": {
+ "data": "nullable:longstring",
+ "size": "json"
+ }
+ },
+ "disconnectedNode": {
+ "category": "dict",
+ "typeName": "disconnectedNode",
+ "specializations": {
+ "node": "domnode",
+ "newParents": "array:domnode"
+ }
+ },
+ "disconnectedNodeArray": {
+ "category": "dict",
+ "typeName": "disconnectedNodeArray",
+ "specializations": {
+ "nodes": "array:domnode",
+ "newParents": "array:domnode"
+ }
+ },
+ "dommutation": {
+ "category": "dict",
+ "typeName": "dommutation",
+ "specializations": {}
+ },
+ "domnodelist": {
+ "category": "actor",
+ "typeName": "domnodelist",
+ "methods": [
+ {
+ "name": "item",
+ "request": {
+ "type": "item",
+ "item": {
+ "_arg": 0,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "_retval": "disconnectedNode"
+ }
+ },
+ {
+ "name": "items",
+ "request": {
+ "type": "items",
+ "start": {
+ "_arg": 0,
+ "type": "nullable:number"
+ },
+ "end": {
+ "_arg": 1,
+ "type": "nullable:number"
+ }
+ },
+ "response": {
+ "_retval": "disconnectedNodeArray"
+ }
+ },
+ {
+ "name": "release",
+ "release": true,
+ "request": {
+ "type": "release"
+ },
+ "response": {}
+ }
+ ],
+ "events": {}
+ },
+ "domtraversalarray": {
+ "category": "dict",
+ "typeName": "domtraversalarray",
+ "specializations": {
+ "nodes": "array:domnode"
+ }
+ },
+ "domwalker": {
+ "category": "actor",
+ "typeName": "domwalker",
+ "methods": [
+ {
+ "name": "release",
+ "release": true,
+ "request": {
+ "type": "release"
+ },
+ "response": {}
+ },
+ {
+ "name": "pick",
+ "request": {
+ "type": "pick"
+ },
+ "response": {
+ "_retval": "disconnectedNode"
+ }
+ },
+ {
+ "name": "cancelPick",
+ "request": {
+ "type": "cancelPick"
+ },
+ "response": {}
+ },
+ {
+ "name": "highlight",
+ "request": {
+ "type": "highlight",
+ "node": {
+ "_arg": 0,
+ "type": "nullable:domnode"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "document",
+ "request": {
+ "type": "document",
+ "node": {
+ "_arg": 0,
+ "type": "nullable:domnode"
+ }
+ },
+ "response": {
+ "node": {
+ "_retval": "domnode"
+ }
+ }
+ },
+ {
+ "name": "documentElement",
+ "request": {
+ "type": "documentElement",
+ "node": {
+ "_arg": 0,
+ "type": "nullable:domnode"
+ }
+ },
+ "response": {
+ "node": {
+ "_retval": "domnode"
+ }
+ }
+ },
+ {
+ "name": "parents",
+ "request": {
+ "type": "parents",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "sameDocument": {
+ "_option": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "nodes": {
+ "_retval": "array:domnode"
+ }
+ }
+ },
+ {
+ "name": "retainNode",
+ "request": {
+ "type": "retainNode",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "unretainNode",
+ "request": {
+ "type": "unretainNode",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "releaseNode",
+ "request": {
+ "type": "releaseNode",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "force": {
+ "_option": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "children",
+ "request": {
+ "type": "children",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "maxNodes": {
+ "_option": 1,
+ "type": "primitive"
+ },
+ "center": {
+ "_option": 1,
+ "type": "domnode"
+ },
+ "start": {
+ "_option": 1,
+ "type": "domnode"
+ },
+ "whatToShow": {
+ "_option": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "_retval": "domtraversalarray"
+ }
+ },
+ {
+ "name": "siblings",
+ "request": {
+ "type": "siblings",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "maxNodes": {
+ "_option": 1,
+ "type": "primitive"
+ },
+ "center": {
+ "_option": 1,
+ "type": "domnode"
+ },
+ "start": {
+ "_option": 1,
+ "type": "domnode"
+ },
+ "whatToShow": {
+ "_option": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "_retval": "domtraversalarray"
+ }
+ },
+ {
+ "name": "nextSibling",
+ "request": {
+ "type": "nextSibling",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "whatToShow": {
+ "_option": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "node": {
+ "_retval": "nullable:domnode"
+ }
+ }
+ },
+ {
+ "name": "previousSibling",
+ "request": {
+ "type": "previousSibling",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "whatToShow": {
+ "_option": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "node": {
+ "_retval": "nullable:domnode"
+ }
+ }
+ },
+ {
+ "name": "querySelector",
+ "request": {
+ "type": "querySelector",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "selector": {
+ "_arg": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "_retval": "disconnectedNode"
+ }
+ },
+ {
+ "name": "querySelectorAll",
+ "request": {
+ "type": "querySelectorAll",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "selector": {
+ "_arg": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "list": {
+ "_retval": "domnodelist"
+ }
+ }
+ },
+ {
+ "name": "getSuggestionsForQuery",
+ "request": {
+ "type": "getSuggestionsForQuery",
+ "query": {
+ "_arg": 0,
+ "type": "primitive"
+ },
+ "completing": {
+ "_arg": 1,
+ "type": "primitive"
+ },
+ "selectorState": {
+ "_arg": 2,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "list": {
+ "_retval": "array:array:string"
+ }
+ }
+ },
+ {
+ "name": "addPseudoClassLock",
+ "request": {
+ "type": "addPseudoClassLock",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "pseudoClass": {
+ "_arg": 1,
+ "type": "primitive"
+ },
+ "parents": {
+ "_option": 2,
+ "type": "primitive"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "hideNode",
+ "request": {
+ "type": "hideNode",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "unhideNode",
+ "request": {
+ "type": "unhideNode",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "removePseudoClassLock",
+ "request": {
+ "type": "removePseudoClassLock",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "pseudoClass": {
+ "_arg": 1,
+ "type": "primitive"
+ },
+ "parents": {
+ "_option": 2,
+ "type": "primitive"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "clearPseudoClassLocks",
+ "request": {
+ "type": "clearPseudoClassLocks",
+ "node": {
+ "_arg": 0,
+ "type": "nullable:domnode"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "innerHTML",
+ "request": {
+ "type": "innerHTML",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ }
+ },
+ "response": {
+ "value": {
+ "_retval": "longstring"
+ }
+ }
+ },
+ {
+ "name": "outerHTML",
+ "request": {
+ "type": "outerHTML",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ }
+ },
+ "response": {
+ "value": {
+ "_retval": "longstring"
+ }
+ }
+ },
+ {
+ "name": "setOuterHTML",
+ "request": {
+ "type": "setOuterHTML",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "value": {
+ "_arg": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "removeNode",
+ "request": {
+ "type": "removeNode",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ }
+ },
+ "response": {
+ "nextSibling": {
+ "_retval": "nullable:domnode"
+ }
+ }
+ },
+ {
+ "name": "insertBefore",
+ "request": {
+ "type": "insertBefore",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ },
+ "parent": {
+ "_arg": 1,
+ "type": "domnode"
+ },
+ "sibling": {
+ "_arg": 2,
+ "type": "nullable:domnode"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "getMutations",
+ "request": {
+ "type": "getMutations",
+ "cleanup": {
+ "_option": 0,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "mutations": {
+ "_retval": "array:dommutation"
+ }
+ }
+ },
+ {
+ "name": "isInDOMTree",
+ "request": {
+ "type": "isInDOMTree",
+ "node": {
+ "_arg": 0,
+ "type": "domnode"
+ }
+ },
+ "response": {
+ "attached": {
+ "_retval": "boolean"
+ }
+ }
+ },
+ {
+ "name": "getNodeActorFromObjectActor",
+ "request": {
+ "type": "getNodeActorFromObjectActor",
+ "objectActorID": {
+ "_arg": 0,
+ "type": "string"
+ }
+ },
+ "response": {
+ "nodeFront": {
+ "_retval": "nullable:disconnectedNode"
+ }
+ }
+ }
+ ],
+ "events": {
+ "new-mutations": {
+ "type": "newMutations"
+ },
+ "picker-node-picked": {
+ "type": "pickerNodePicked",
+ "node": {
+ "_arg": 0,
+ "type": "disconnectedNode"
+ }
+ },
+ "picker-node-hovered": {
+ "type": "pickerNodeHovered",
+ "node": {
+ "_arg": 0,
+ "type": "disconnectedNode"
+ }
+ },
+ "highlighter-ready": {
+ "type": "highlighter-ready"
+ },
+ "highlighter-hide": {
+ "type": "highlighter-hide"
+ }
+ }
+ },
+ "inspector": {
+ "category": "actor",
+ "typeName": "inspector",
+ "methods": [
+ {
+ "name": "getWalker",
+ "request": {
+ "type": "getWalker"
+ },
+ "response": {
+ "walker": {
+ "_retval": "domwalker"
+ }
+ }
+ },
+ {
+ "name": "getPageStyle",
+ "request": {
+ "type": "getPageStyle"
+ },
+ "response": {
+ "pageStyle": {
+ "_retval": "pagestyle"
+ }
+ }
+ },
+ {
+ "name": "getHighlighter",
+ "request": {
+ "type": "getHighlighter",
+ "autohide": {
+ "_arg": 0,
+ "type": "boolean"
+ }
+ },
+ "response": {
+ "highligter": {
+ "_retval": "highlighter"
+ }
+ }
+ },
+ {
+ "name": "getImageDataFromURL",
+ "request": {
+ "type": "getImageDataFromURL",
+ "url": {
+ "_arg": 0,
+ "type": "primitive"
+ },
+ "maxDim": {
+ "_arg": 1,
+ "type": "nullable:number"
+ }
+ },
+ "response": {
+ "_retval": "imageData"
+ }
+ }
+ ],
+ "events": {}
+ },
+ "call-stack-item": {
+ "category": "dict",
+ "typeName": "call-stack-item",
+ "specializations": {
+ "name": "string",
+ "file": "string",
+ "line": "number"
+ }
+ },
+ "call-details": {
+ "category": "dict",
+ "typeName": "call-details",
+ "specializations": {
+ "type": "number",
+ "name": "string",
+ "stack": "array:call-stack-item"
+ }
+ },
+ "function-call": {
+ "category": "actor",
+ "typeName": "function-call",
+ "methods": [
+ {
+ "name": "getDetails",
+ "request": {
+ "type": "getDetails"
+ },
+ "response": {
+ "info": {
+ "_retval": "call-details"
+ }
+ }
+ }
+ ],
+ "events": {}
+ },
+ "call-watcher": {
+ "category": "actor",
+ "typeName": "call-watcher",
+ "methods": [
+ {
+ "name": "setup",
+ "oneway": true,
+ "request": {
+ "type": "setup",
+ "tracedGlobals": {
+ "_option": 0,
+ "type": "nullable:array:string"
+ },
+ "tracedFunctions": {
+ "_option": 0,
+ "type": "nullable:array:string"
+ },
+ "startRecording": {
+ "_option": 0,
+ "type": "boolean"
+ },
+ "performReload": {
+ "_option": 0,
+ "type": "boolean"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "finalize",
+ "oneway": true,
+ "request": {
+ "type": "finalize"
+ },
+ "response": {}
+ },
+ {
+ "name": "isRecording",
+ "request": {
+ "type": "isRecording"
+ },
+ "response": {
+ "_retval": "boolean"
+ }
+ },
+ {
+ "name": "resumeRecording",
+ "request": {
+ "type": "resumeRecording"
+ },
+ "response": {}
+ },
+ {
+ "name": "pauseRecording",
+ "request": {
+ "type": "pauseRecording"
+ },
+ "response": {
+ "calls": {
+ "_retval": "array:function-call"
+ }
+ }
+ },
+ {
+ "name": "eraseRecording",
+ "request": {
+ "type": "eraseRecording"
+ },
+ "response": {}
+ }
+ ],
+ "events": {}
+ },
+ "snapshot-image": {
+ "category": "dict",
+ "typeName": "snapshot-image",
+ "specializations": {
+ "index": "number",
+ "width": "number",
+ "height": "number",
+ "flipped": "boolean",
+ "pixels": "uint32-array"
+ }
+ },
+ "snapshot-overview": {
+ "category": "dict",
+ "typeName": "snapshot-overview",
+ "specializations": {
+ "calls": "array:function-call",
+ "thumbnails": "array:snapshot-image",
+ "screenshot": "snapshot-image"
+ }
+ },
+ "frame-snapshot": {
+ "category": "actor",
+ "typeName": "frame-snapshot",
+ "methods": [
+ {
+ "name": "getOverview",
+ "request": {
+ "type": "getOverview"
+ },
+ "response": {
+ "overview": {
+ "_retval": "snapshot-overview"
+ }
+ }
+ },
+ {
+ "name": "generateScreenshotFor",
+ "request": {
+ "type": "generateScreenshotFor",
+ "call": {
+ "_arg": 0,
+ "type": "function-call"
+ }
+ },
+ "response": {
+ "screenshot": {
+ "_retval": "snapshot-image"
+ }
+ }
+ }
+ ],
+ "events": {}
+ },
+ "canvas": {
+ "category": "actor",
+ "typeName": "canvas",
+ "methods": [
+ {
+ "name": "setup",
+ "oneway": true,
+ "request": {
+ "type": "setup",
+ "reload": {
+ "_option": 0,
+ "type": "boolean"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "finalize",
+ "oneway": true,
+ "request": {
+ "type": "finalize"
+ },
+ "response": {}
+ },
+ {
+ "name": "isInitialized",
+ "request": {
+ "type": "isInitialized"
+ },
+ "response": {
+ "initialized": {
+ "_retval": "boolean"
+ }
+ }
+ },
+ {
+ "name": "recordAnimationFrame",
+ "request": {
+ "type": "recordAnimationFrame"
+ },
+ "response": {
+ "snapshot": {
+ "_retval": "frame-snapshot"
+ }
+ }
+ }
+ ],
+ "events": {}
+ },
+ "gl-shader": {
+ "category": "actor",
+ "typeName": "gl-shader",
+ "methods": [
+ {
+ "name": "getText",
+ "request": {
+ "type": "getText"
+ },
+ "response": {
+ "text": {
+ "_retval": "string"
+ }
+ }
+ },
+ {
+ "name": "compile",
+ "request": {
+ "type": "compile",
+ "text": {
+ "_arg": 0,
+ "type": "string"
+ }
+ },
+ "response": {
+ "error": {
+ "_retval": "nullable:json"
+ }
+ }
+ }
+ ],
+ "events": {}
+ },
+ "gl-program": {
+ "category": "actor",
+ "typeName": "gl-program",
+ "methods": [
+ {
+ "name": "getVertexShader",
+ "request": {
+ "type": "getVertexShader"
+ },
+ "response": {
+ "shader": {
+ "_retval": "gl-shader"
+ }
+ }
+ },
+ {
+ "name": "getFragmentShader",
+ "request": {
+ "type": "getFragmentShader"
+ },
+ "response": {
+ "shader": {
+ "_retval": "gl-shader"
+ }
+ }
+ },
+ {
+ "name": "highlight",
+ "oneway": true,
+ "request": {
+ "type": "highlight",
+ "tint": {
+ "_arg": 0,
+ "type": "array:number"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "unhighlight",
+ "oneway": true,
+ "request": {
+ "type": "unhighlight"
+ },
+ "response": {}
+ },
+ {
+ "name": "blackbox",
+ "oneway": true,
+ "request": {
+ "type": "blackbox"
+ },
+ "response": {}
+ },
+ {
+ "name": "unblackbox",
+ "oneway": true,
+ "request": {
+ "type": "unblackbox"
+ },
+ "response": {}
+ }
+ ],
+ "events": {}
+ },
+ "webgl": {
+ "category": "actor",
+ "typeName": "webgl",
+ "methods": [
+ {
+ "name": "setup",
+ "oneway": true,
+ "request": {
+ "type": "setup",
+ "reload": {
+ "_option": 0,
+ "type": "boolean"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "finalize",
+ "oneway": true,
+ "request": {
+ "type": "finalize"
+ },
+ "response": {}
+ },
+ {
+ "name": "getPrograms",
+ "request": {
+ "type": "getPrograms"
+ },
+ "response": {
+ "programs": {
+ "_retval": "array:gl-program"
+ }
+ }
+ }
+ ],
+ "events": {
+ "program-linked": {
+ "type": "programLinked",
+ "program": {
+ "_arg": 0,
+ "type": "gl-program"
+ }
+ }
+ }
+ },
+ "audionode": {
+ "category": "actor",
+ "typeName": "audionode",
+ "methods": [
+ {
+ "name": "getType",
+ "request": {
+ "type": "getType"
+ },
+ "response": {
+ "type": {
+ "_retval": "string"
+ }
+ }
+ },
+ {
+ "name": "isSource",
+ "request": {
+ "type": "isSource"
+ },
+ "response": {
+ "source": {
+ "_retval": "boolean"
+ }
+ }
+ },
+ {
+ "name": "setParam",
+ "request": {
+ "type": "setParam",
+ "param": {
+ "_arg": 0,
+ "type": "string"
+ },
+ "value": {
+ "_arg": 1,
+ "type": "nullable:primitive"
+ }
+ },
+ "response": {
+ "error": {
+ "_retval": "nullable:json"
+ }
+ }
+ },
+ {
+ "name": "getParam",
+ "request": {
+ "type": "getParam",
+ "param": {
+ "_arg": 0,
+ "type": "string"
+ }
+ },
+ "response": {
+ "text": {
+ "_retval": "nullable:primitive"
+ }
+ }
+ },
+ {
+ "name": "getParamFlags",
+ "request": {
+ "type": "getParamFlags",
+ "param": {
+ "_arg": 0,
+ "type": "string"
+ }
+ },
+ "response": {
+ "flags": {
+ "_retval": "nullable:primitive"
+ }
+ }
+ },
+ {
+ "name": "getParams",
+ "request": {
+ "type": "getParams"
+ },
+ "response": {
+ "params": {
+ "_retval": "json"
+ }
+ }
+ }
+ ],
+ "events": {}
+ },
+ "webaudio": {
+ "category": "actor",
+ "typeName": "webaudio",
+ "methods": [
+ {
+ "name": "setup",
+ "oneway": true,
+ "request": {
+ "type": "setup",
+ "reload": {
+ "_option": 0,
+ "type": "boolean"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "finalize",
+ "oneway": true,
+ "request": {
+ "type": "finalize"
+ },
+ "response": {}
+ }
+ ],
+ "events": {
+ "start-context": {
+ "type": "startContext"
+ },
+ "connect-node": {
+ "type": "connectNode",
+ "source": {
+ "_option": 0,
+ "type": "audionode"
+ },
+ "dest": {
+ "_option": 0,
+ "type": "audionode"
+ }
+ },
+ "disconnect-node": {
+ "type": "disconnectNode",
+ "source": {
+ "_arg": 0,
+ "type": "audionode"
+ }
+ },
+ "connect-param": {
+ "type": "connectParam",
+ "source": {
+ "_arg": 0,
+ "type": "audionode"
+ },
+ "param": {
+ "_arg": 1,
+ "type": "string"
+ }
+ },
+ "change-param": {
+ "type": "changeParam",
+ "source": {
+ "_option": 0,
+ "type": "audionode"
+ },
+ "param": {
+ "_option": 0,
+ "type": "string"
+ },
+ "value": {
+ "_option": 0,
+ "type": "string"
+ }
+ },
+ "create-node": {
+ "type": "createNode",
+ "source": {
+ "_arg": 0,
+ "type": "audionode"
+ }
+ }
+ }
+ },
+ "old-stylesheet": {
+ "category": "actor",
+ "typeName": "old-stylesheet",
+ "methods": [
+ {
+ "name": "toggleDisabled",
+ "request": {
+ "type": "toggleDisabled"
+ },
+ "response": {
+ "disabled": {
+ "_retval": "boolean"
+ }
+ }
+ },
+ {
+ "name": "fetchSource",
+ "request": {
+ "type": "fetchSource"
+ },
+ "response": {}
+ },
+ {
+ "name": "update",
+ "request": {
+ "type": "update",
+ "text": {
+ "_arg": 0,
+ "type": "string"
+ },
+ "transition": {
+ "_arg": 1,
+ "type": "boolean"
+ }
+ },
+ "response": {}
+ }
+ ],
+ "events": {
+ "property-change": {
+ "type": "propertyChange",
+ "property": {
+ "_arg": 0,
+ "type": "string"
+ },
+ "value": {
+ "_arg": 1,
+ "type": "json"
+ }
+ },
+ "source-load": {
+ "type": "sourceLoad",
+ "source": {
+ "_arg": 0,
+ "type": "string"
+ }
+ },
+ "style-applied": {
+ "type": "styleApplied"
+ }
+ }
+ },
+ "styleeditor": {
+ "category": "actor",
+ "typeName": "styleeditor",
+ "methods": [
+ {
+ "name": "newDocument",
+ "request": {
+ "type": "newDocument"
+ },
+ "response": {}
+ },
+ {
+ "name": "newStyleSheet",
+ "request": {
+ "type": "newStyleSheet",
+ "text": {
+ "_arg": 0,
+ "type": "string"
+ }
+ },
+ "response": {
+ "styleSheet": {
+ "_retval": "old-stylesheet"
+ }
+ }
+ }
+ ],
+ "events": {
+ "document-load": {
+ "type": "documentLoad",
+ "styleSheets": {
+ "_arg": 0,
+ "type": "array:old-stylesheet"
+ }
+ }
+ }
+ },
+ "cookieobject": {
+ "category": "dict",
+ "typeName": "cookieobject",
+ "specializations": {
+ "name": "string",
+ "value": "longstring",
+ "path": "nullable:string",
+ "host": "string",
+ "isDomain": "boolean",
+ "isSecure": "boolean",
+ "isHttpOnly": "boolean",
+ "creationTime": "number",
+ "lastAccessed": "number",
+ "expires": "number"
+ }
+ },
+ "cookiestoreobject": {
+ "category": "dict",
+ "typeName": "cookiestoreobject",
+ "specializations": {
+ "total": "number",
+ "offset": "number",
+ "data": "array:nullable:cookieobject"
+ }
+ },
+ "storageobject": {
+ "category": "dict",
+ "typeName": "storageobject",
+ "specializations": {
+ "name": "string",
+ "value": "longstring"
+ }
+ },
+ "storagestoreobject": {
+ "category": "dict",
+ "typeName": "storagestoreobject",
+ "specializations": {
+ "total": "number",
+ "offset": "number",
+ "data": "array:nullable:storageobject"
+ }
+ },
+ "idbobject": {
+ "category": "dict",
+ "typeName": "idbobject",
+ "specializations": {
+ "name": "nullable:string",
+ "db": "nullable:string",
+ "objectStore": "nullable:string",
+ "origin": "nullable:string",
+ "version": "nullable:number",
+ "objectStores": "nullable:number",
+ "keyPath": "nullable:string",
+ "autoIncrement": "nullable:boolean",
+ "indexes": "nullable:string",
+ "value": "nullable:longstring"
+ }
+ },
+ "idbstoreobject": {
+ "category": "dict",
+ "typeName": "idbstoreobject",
+ "specializations": {
+ "total": "number",
+ "offset": "number",
+ "data": "array:nullable:idbobject"
+ }
+ },
+ "storeUpdateObject": {
+ "category": "dict",
+ "typeName": "storeUpdateObject",
+ "specializations": {
+ "changed": "nullable:json",
+ "deleted": "nullable:json",
+ "added": "nullable:json"
+ }
+ },
+ "cookies": {
+ "category": "actor",
+ "typeName": "cookies",
+ "methods": [
+ {
+ "name": "getStoreObjects",
+ "request": {
+ "type": "getStoreObjects",
+ "host": {
+ "_arg": 0,
+ "type": "primitive"
+ },
+ "names": {
+ "_arg": 1,
+ "type": "nullable:array:string"
+ },
+ "options": {
+ "_arg": 2,
+ "type": "nullable:json"
+ }
+ },
+ "response": {
+ "_retval": "cookiestoreobject"
+ }
+ }
+ ],
+ "events": {}
+ },
+ "localStorage": {
+ "category": "actor",
+ "typeName": "localStorage",
+ "methods": [
+ {
+ "name": "getStoreObjects",
+ "request": {
+ "type": "getStoreObjects",
+ "host": {
+ "_arg": 0,
+ "type": "primitive"
+ },
+ "names": {
+ "_arg": 1,
+ "type": "nullable:array:string"
+ },
+ "options": {
+ "_arg": 2,
+ "type": "nullable:json"
+ }
+ },
+ "response": {
+ "_retval": "storagestoreobject"
+ }
+ }
+ ],
+ "events": {}
+ },
+ "sessionStorage": {
+ "category": "actor",
+ "typeName": "sessionStorage",
+ "methods": [
+ {
+ "name": "getStoreObjects",
+ "request": {
+ "type": "getStoreObjects",
+ "host": {
+ "_arg": 0,
+ "type": "primitive"
+ },
+ "names": {
+ "_arg": 1,
+ "type": "nullable:array:string"
+ },
+ "options": {
+ "_arg": 2,
+ "type": "nullable:json"
+ }
+ },
+ "response": {
+ "_retval": "storagestoreobject"
+ }
+ }
+ ],
+ "events": {}
+ },
+ "indexedDB": {
+ "category": "actor",
+ "typeName": "indexedDB",
+ "methods": [
+ {
+ "name": "getStoreObjects",
+ "request": {
+ "type": "getStoreObjects",
+ "host": {
+ "_arg": 0,
+ "type": "primitive"
+ },
+ "names": {
+ "_arg": 1,
+ "type": "nullable:array:string"
+ },
+ "options": {
+ "_arg": 2,
+ "type": "nullable:json"
+ }
+ },
+ "response": {
+ "_retval": "idbstoreobject"
+ }
+ }
+ ],
+ "events": {}
+ },
+ "storelist": {
+ "category": "dict",
+ "typeName": "storelist",
+ "specializations": {
+ "cookies": "cookies",
+ "localStorage": "localStorage",
+ "sessionStorage": "sessionStorage",
+ "indexedDB": "indexedDB"
+ }
+ },
+ "storage": {
+ "category": "actor",
+ "typeName": "storage",
+ "methods": [
+ {
+ "name": "listStores",
+ "request": {
+ "type": "listStores"
+ },
+ "response": {
+ "_retval": "storelist"
+ }
+ }
+ ],
+ "events": {
+ "stores-update": {
+ "type": "storesUpdate",
+ "data": {
+ "_arg": 0,
+ "type": "storeUpdateObject"
+ }
+ },
+ "stores-cleared": {
+ "type": "storesCleared",
+ "data": {
+ "_arg": 0,
+ "type": "json"
+ }
+ },
+ "stores-reloaded": {
+ "type": "storesRelaoded",
+ "data": {
+ "_arg": 0,
+ "type": "json"
+ }
+ }
+ }
+ },
+ "gcli": {
+ "category": "actor",
+ "typeName": "gcli",
+ "methods": [
+ {
+ "name": "specs",
+ "request": {
+ "type": "specs"
+ },
+ "response": {
+ "_retval": "json"
+ }
+ },
+ {
+ "name": "execute",
+ "request": {
+ "type": "execute",
+ "typed": {
+ "_arg": 0,
+ "type": "string"
+ }
+ },
+ "response": {
+ "_retval": "json"
+ }
+ },
+ {
+ "name": "state",
+ "request": {
+ "type": "state",
+ "typed": {
+ "_arg": 0,
+ "type": "string"
+ },
+ "start": {
+ "_arg": 1,
+ "type": "number"
+ },
+ "rank": {
+ "_arg": 2,
+ "type": "number"
+ }
+ },
+ "response": {
+ "_retval": "json"
+ }
+ },
+ {
+ "name": "typeparse",
+ "request": {
+ "type": "typeparse",
+ "typed": {
+ "_arg": 0,
+ "type": "string"
+ },
+ "param": {
+ "_arg": 1,
+ "type": "string"
+ }
+ },
+ "response": {
+ "_retval": "json"
+ }
+ },
+ {
+ "name": "typeincrement",
+ "request": {
+ "type": "typeincrement",
+ "typed": {
+ "_arg": 0,
+ "type": "string"
+ },
+ "param": {
+ "_arg": 1,
+ "type": "string"
+ }
+ },
+ "response": {
+ "_retval": "string"
+ }
+ },
+ {
+ "name": "typedecrement",
+ "request": {
+ "type": "typedecrement",
+ "typed": {
+ "_arg": 0,
+ "type": "string"
+ },
+ "param": {
+ "_arg": 1,
+ "type": "string"
+ }
+ },
+ "response": {
+ "_retval": "string"
+ }
+ },
+ {
+ "name": "selectioninfo",
+ "request": {
+ "type": "selectioninfo",
+ "typed": {
+ "_arg": 0,
+ "type": "string"
+ },
+ "param": {
+ "_arg": 1,
+ "type": "string"
+ },
+ "action": {
+ "_arg": 1,
+ "type": "string"
+ }
+ },
+ "response": {
+ "_retval": "json"
+ }
+ }
+ ],
+ "events": {}
+ },
+ "memory": {
+ "category": "actor",
+ "typeName": "memory",
+ "methods": [
+ {
+ "name": "measure",
+ "request": {
+ "type": "measure"
+ },
+ "response": {
+ "_retval": "json"
+ }
+ }
+ ],
+ "events": {}
+ },
+ "eventLoopLag": {
+ "category": "actor",
+ "typeName": "eventLoopLag",
+ "methods": [
+ {
+ "name": "start",
+ "request": {
+ "type": "start"
+ },
+ "response": {
+ "success": {
+ "_retval": "number"
+ }
+ }
+ },
+ {
+ "name": "stop",
+ "request": {
+ "type": "stop"
+ },
+ "response": {}
+ }
+ ],
+ "events": {
+ "event-loop-lag": {
+ "type": "event-loop-lag",
+ "time": {
+ "_arg": 0,
+ "type": "number"
+ }
+ }
+ }
+ },
+ "preference": {
+ "category": "actor",
+ "typeName": "preference",
+ "methods": [
+ {
+ "name": "getBoolPref",
+ "request": {
+ "type": "getBoolPref",
+ "value": {
+ "_arg": 0,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "value": {
+ "_retval": "boolean"
+ }
+ }
+ },
+ {
+ "name": "getCharPref",
+ "request": {
+ "type": "getCharPref",
+ "value": {
+ "_arg": 0,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "value": {
+ "_retval": "string"
+ }
+ }
+ },
+ {
+ "name": "getIntPref",
+ "request": {
+ "type": "getIntPref",
+ "value": {
+ "_arg": 0,
+ "type": "primitive"
+ }
+ },
+ "response": {
+ "value": {
+ "_retval": "number"
+ }
+ }
+ },
+ {
+ "name": "getAllPrefs",
+ "request": {
+ "type": "getAllPrefs"
+ },
+ "response": {
+ "value": {
+ "_retval": "json"
+ }
+ }
+ },
+ {
+ "name": "setBoolPref",
+ "request": {
+ "type": "setBoolPref",
+ "name": {
+ "_arg": 0,
+ "type": "primitive"
+ },
+ "value": {
+ "_arg": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "setCharPref",
+ "request": {
+ "type": "setCharPref",
+ "name": {
+ "_arg": 0,
+ "type": "primitive"
+ },
+ "value": {
+ "_arg": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "setIntPref",
+ "request": {
+ "type": "setIntPref",
+ "name": {
+ "_arg": 0,
+ "type": "primitive"
+ },
+ "value": {
+ "_arg": 1,
+ "type": "primitive"
+ }
+ },
+ "response": {}
+ },
+ {
+ "name": "clearUserPref",
+ "request": {
+ "type": "clearUserPref",
+ "name": {
+ "_arg": 0,
+ "type": "primitive"
+ }
+ },
+ "response": {}
+ }
+ ],
+ "events": {}
+ },
+ "device": {
+ "category": "actor",
+ "typeName": "device",
+ "methods": [
+ {
+ "name": "getDescription",
+ "request": {
+ "type": "getDescription"
+ },
+ "response": {
+ "value": {
+ "_retval": "json"
+ }
+ }
+ },
+ {
+ "name": "getWallpaper",
+ "request": {
+ "type": "getWallpaper"
+ },
+ "response": {
+ "value": {
+ "_retval": "longstring"
+ }
+ }
+ },
+ {
+ "name": "screenshotToDataURL",
+ "request": {
+ "type": "screenshotToDataURL"
+ },
+ "response": {
+ "value": {
+ "_retval": "longstring"
+ }
+ }
+ },
+ {
+ "name": "getRawPermissionsTable",
+ "request": {
+ "type": "getRawPermissionsTable"
+ },
+ "response": {
+ "value": {
+ "_retval": "json"
+ }
+ }
+ }
+ ],
+ "events": {}
+ }
+ },
+ "from": "root"
+}
+
+},{}],25:[function(_dereq_,module,exports){
+"use strict";
+
+var Class = _dereq_("./class").Class;
+var util = _dereq_("./util");
+var keys = util.keys;
+var values = util.values;
+var pairs = util.pairs;
+var query = util.query;
+var findPath = util.findPath;
+var EventTarget = _dereq_("./event").EventTarget;
+
+var TypeSystem = Class({
+ constructor: function(client) {
+ var types = Object.create(null);
+ var specification = Object.create(null);
+
+ this.specification = specification;
+ this.types = types;
+
+ var typeFor = function typeFor(typeName) {
+ typeName = typeName || "primitive";
+ if (!types[typeName]) {
+ defineType(typeName);
+ }
+
+ return types[typeName];
+ };
+ this.typeFor = typeFor;
+
+ var defineType = function(descriptor) {
+ var type = void(0);
+ if (typeof(descriptor) === "string") {
+ if (descriptor.indexOf(":") > 0)
+ type = makeCompoundType(descriptor);
+ else if (descriptor.indexOf("#") > 0)
+ type = new ActorDetail(descriptor);
+ else if (specification[descriptor])
+ type = makeCategoryType(specification[descriptor]);
+ } else {
+ type = makeCategoryType(descriptor);
+ }
+
+ if (type)
+ types[type.name] = type;
+ else
+ throw TypeError("Invalid type: " + descriptor);
+ };
+ this.defineType = defineType;
+
+
+ var makeCompoundType = function(name) {
+ var index = name.indexOf(":");
+ var baseType = name.slice(0, index);
+ var subType = name.slice(index + 1);
+
+ return baseType === "array" ? new ArrayOf(subType) :
+ baseType === "nullable" ? new Maybe(subType) :
+ null;
+ };
+
+ var makeCategoryType = function(descriptor) {
+ var category = descriptor.category;
+ return category === "dict" ? new Dictionary(descriptor) :
+ category === "actor" ? new Actor(descriptor) :
+ null;
+ };
+
+ var read = function(input, context, typeName) {
+ return typeFor(typeName).read(input, context);
+ }
+ this.read = read;
+
+ var write = function(input, context, typeName) {
+ return typeFor(typeName).write(input);
+ };
+ this.write = write;
+
+
+ var Type = Class({
+ constructor: function() {
+ },
+ get name() {
+ return this.category ? this.category + ":" + this.type :
+ this.type;
+ },
+ read: function(input, context) {
+ throw new TypeError("`Type` subclass must implement `read`");
+ },
+ write: function(input, context) {
+ throw new TypeError("`Type` subclass must implement `write`");
+ }
+ });
+
+ var Primitve = Class({
+ extends: Type,
+ constuctor: function(type) {
+ this.type = type;
+ },
+ read: function(input, context) {
+ return input;
+ },
+ write: function(input, context) {
+ return input;
+ }
+ });
+
+ var Maybe = Class({
+ extends: Type,
+ category: "nullable",
+ constructor: function(type) {
+ this.type = type;
+ },
+ read: function(input, context) {
+ return input === null ? null :
+ input === void(0) ? void(0) :
+ read(input, context, this.type);
+ },
+ write: function(input, context) {
+ return input === null ? null :
+ input === void(0) ? void(0) :
+ write(input, context, this.type);
+ }
+ });
+
+ var ArrayOf = Class({
+ extends: Type,
+ category: "array",
+ constructor: function(type) {
+ this.type = type;
+ },
+ read: function(input, context) {
+ var type = this.type;
+ return input.map(function($) { return read($, context, type) });
+ },
+ write: function(input, context) {
+ var type = this.type;
+ return input.map(function($) { return write($, context, type) });
+ }
+ });
+
+ var makeField = function makeField(name, type) {
+ return {
+ enumerable: true,
+ configurable: true,
+ get: function() {
+ Object.defineProperty(this, name, {
+ configurable: false,
+ value: read(this.state[name], this.context, type)
+ });
+ return this[name];
+ }
+ }
+ };
+
+ var makeFields = function(descriptor) {
+ return pairs(descriptor).reduce(function(fields, pair) {
+ var name = pair[0], type = pair[1];
+ fields[name] = makeField(name, type);
+ return fields;
+ }, {});
+ }
+
+ var DictionaryType = Class({});
+
+ var Dictionary = Class({
+ extends: Type,
+ category: "dict",
+ get name() { return this.type; },
+ constructor: function(descriptor) {
+ this.type = descriptor.typeName;
+ this.types = descriptor.specializations;
+
+ var proto = Object.defineProperties({
+ extends: DictionaryType,
+ constructor: function(state, context) {
+ Object.defineProperties(this, {
+ state: {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: state
+ },
+ context: {
+ enumerable: false,
+ writable: false,
+ configurable: true,
+ value: context
+ }
+ });
+ }
+ }, makeFields(this.types));
+
+ this.class = new Class(proto);
+ },
+ read: function(input, context) {
+ return new this.class(input, context);
+ },
+ write: function(input, context) {
+ var output = {};
+ for (var key in input) {
+ output[key] = write(value, context, types[key]);
+ }
+ return output;
+ }
+ });
+
+ var makeMethods = function(descriptors) {
+ return descriptors.reduce(function(methods, descriptor) {
+ methods[descriptor.name] = {
+ enumerable: true,
+ configurable: true,
+ writable: false,
+ value: makeMethod(descriptor)
+ };
+ return methods;
+ }, {});
+ };
+
+ var makeEvents = function(descriptors) {
+ return pairs(descriptors).reduce(function(events, pair) {
+ var name = pair[0], descriptor = pair[1];
+ var event = new Event(name, descriptor);
+ events[event.eventType] = event;
+ return events;
+ }, Object.create(null));
+ };
+
+ var Actor = Class({
+ extends: Type,
+ category: "actor",
+ get name() { return this.type; },
+ constructor: function(descriptor) {
+ this.type = descriptor.typeName;
+
+ var events = makeEvents(descriptor.events || {});
+ var fields = makeFields(descriptor.fields || {});
+ var methods = makeMethods(descriptor.methods || []);
+
+
+ var proto = {
+ extends: Front,
+ constructor: function() {
+ Front.apply(this, arguments);
+ },
+ events: events
+ };
+ Object.defineProperties(proto, fields);
+ Object.defineProperties(proto, methods);
+
+ this.class = Class(proto);
+ },
+ read: function(input, context, detail) {
+ var state = typeof(input) === "string" ? { actor: input } : input;
+
+ var actor = client.get(state.actor) || new this.class(state, context);
+ actor.form(state, detail, context);
+
+ return actor;
+ },
+ write: function(input, context, detail) {
+ return input.id;
+ }
+ });
+ exports.Actor = Actor;
+
+
+ var ActorDetail = Class({
+ extends: Actor,
+ constructor: function(name) {
+ var parts = name.split("#")
+ this.actorType = parts[0]
+ this.detail = parts[1];
+ },
+ read: function(input, context) {
+ return typeFor(this.actorType).read(input, context, this.detail);
+ },
+ write: function(input, context) {
+ return typeFor(this.actorType).write(input, context, this.detail);
+ }
+ });
+ exports.ActorDetail = ActorDetail;
+
+ var Method = Class({
+ extends: Type,
+ constructor: function(descriptor) {
+ this.type = descriptor.name;
+ this.path = findPath(descriptor.response, "_retval");
+ this.responseType = this.path && query(descriptor.response, this.path)._retval;
+ this.requestType = descriptor.request.type;
+
+ var params = [];
+ for (var key in descriptor.request) {
+ if (key !== "type") {
+ var param = descriptor.request[key];
+ var index = "_arg" in param ? param._arg : param._option;
+ var isParam = param._option === index;
+ var isArgument = param._arg === index;
+ params[index] = {
+ type: param.type,
+ key: key,
+ index: index,
+ isParam: isParam,
+ isArgument: isArgument
+ };
+ }
+ }
+ this.params = params;
+ },
+ read: function(input, context) {
+ return read(query(input, this.path), context, this.responseType);
+ },
+ write: function(input, context) {
+ return this.params.reduce(function(result, param) {
+ result[param.key] = write(input[param.index], context, param.type);
+ return result;
+ }, {type: this.type});
+ }
+ });
+ exports.Method = Method;
+
+ var profiler = function(method, id) {
+ return function() {
+ var start = new Date();
+ return method.apply(this, arguments).then(function(result) {
+ var end = new Date();
+ client.telemetry.add(id, +end - start);
+ return result;
+ });
+ };
+ };
+
+ var destructor = function(method) {
+ return function() {
+ return method.apply(this, arguments).then(function(result) {
+ client.release(this);
+ return result;
+ });
+ };
+ };
+
+ function makeMethod(descriptor) {
+ var type = new Method(descriptor);
+ var method = descriptor.oneway ? makeUnidirecationalMethod(descriptor, type) :
+ makeBidirectionalMethod(descriptor, type);
+
+ if (descriptor.telemetry)
+ method = profiler(method);
+ if (descriptor.release)
+ method = destructor(method);
+
+ return method;
+ }
+
+ var makeUnidirecationalMethod = function(descriptor, type) {
+ return function() {
+ var packet = type.write(arguments, this);
+ packet.to = this.id;
+ client.send(packet);
+ return Promise.resolve(void(0));
+ };
+ };
+
+ var makeBidirectionalMethod = function(descriptor, type) {
+ return function() {
+ var context = this.context;
+ var packet = type.write(arguments, context);
+ var context = this.context;
+ packet.to = this.id;
+ return client.request(packet).then(function(packet) {
+ return type.read(packet, context);
+ });
+ };
+ };
+
+ var Event = Class({
+ constructor: function(name, descriptor) {
+ this.name = descriptor.type || name;
+ this.eventType = descriptor.type || name;
+ this.types = Object.create(null);
+
+ var types = this.types;
+ for (var key in descriptor) {
+ if (key === "type") {
+ types[key] = "string";
+ } else {
+ types[key] = descriptor[key].type;
+ }
+ }
+ },
+ read: function(input, context) {
+ var output = {};
+ var types = this.types;
+ for (var key in input) {
+ output[key] = read(input[key], context, types[key]);
+ }
+ return output;
+ },
+ write: function(input, context) {
+ var output = {};
+ var types = this.types;
+ for (var key in this.types) {
+ output[key] = write(input[key], context, types[key]);
+ }
+ return output;
+ }
+ });
+
+ var Front = Class({
+ extends: EventTarget,
+ EventTarget: EventTarget,
+ constructor: function(state) {
+ this.EventTarget();
+ Object.defineProperties(this, {
+ state: {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: state
+ }
+ });
+
+ client.register(this);
+ },
+ get id() {
+ return this.state.actor;
+ },
+ get context() {
+ return this;
+ },
+ form: function(state, detail, context) {
+ if (this.state !== state) {
+ if (detail) {
+ this.state[detail] = state[detail];
+ } else {
+ pairs(state).forEach(function(pair) {
+ var key = pair[0], value = pair[1];
+ this.state[key] = value;
+ }, this);
+ }
+ }
+
+ if (context) {
+ client.supervise(context, this);
+ }
+ },
+ requestTypes: function() {
+ return client.request({
+ to: this.id,
+ type: "requestTypes"
+ }).then(function(packet) {
+ return packet.requestTypes;
+ });
+ }
+ });
+ types.primitive = new Primitve("primitive");
+ types.string = new Primitve("string");
+ types.number = new Primitve("number");
+ types.boolean = new Primitve("boolean");
+ types.json = new Primitve("json");
+ types.array = new Primitve("array");
+ },
+ registerTypes: function(descriptor) {
+ var specification = this.specification;
+ values(descriptor.types).forEach(function(descriptor) {
+ specification[descriptor.typeName] = descriptor;
+ });
+ }
+});
+exports.TypeSystem = TypeSystem;
+
+},{"./class":3,"./event":5,"./util":26}],26:[function(_dereq_,module,exports){
+"use strict";
+
+var keys = Object.keys;
+exports.keys = keys;
+
+// Returns array of values for the given object.
+var values = function(object) {
+ return keys(object).map(function(key) {
+ return object[key]
+ });
+};
+exports.values = values;
+
+// Returns [key, value] pairs for the given object.
+var pairs = function(object) {
+ return keys(object).map(function(key) {
+ return [key, object[key]]
+ });
+};
+exports.pairs = pairs;
+
+
+// Queries an object for the field nested with in it.
+var query = function(object, path) {
+ return path.reduce(function(object, entry) {
+ return object && object[entry]
+ }, object);
+};
+exports.query = query;
+
+var isObject = function(x) {
+ return x && typeof(x) === "object"
+}
+
+var findPath = function(object, key) {
+ var path = void(0);
+ if (object && typeof(object) === "object") {
+ var names = keys(object);
+ if (names.indexOf(key) >= 0) {
+ path = [];
+ } else {
+ var index = 0;
+ var count = names.length;
+ while (index < count && !path){
+ var head = names[index];
+ var tail = findPath(object[head], key);
+ path = tail ? [head].concat(tail) : tail;
+ index = index + 1
+ }
+ }
+ }
+ return path;
+};
+exports.findPath = findPath;
+
+},{}]},{},[1])
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlcyI6WyIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvYnJvd3NlcmlmeS9ub2RlX21vZHVsZXMvYnJvd3Nlci1wYWNrL19wcmVsdWRlLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vYnJvd3Nlci9pbmRleC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL2Jyb3dzZXIvcHJvbWlzZS5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL2NsYXNzLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vY2xpZW50LmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vZXZlbnQuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvYnJvd3NlcmlmeS9ub2RlX21vZHVsZXMvZXZlbnRzL2V2ZW50cy5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL2luZGV4LmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvaXMtaW1wbGVtZW50ZWQuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZC9pbmRleC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL25vZGVfbW9kdWxlcy9lczUtZXh0L29iamVjdC9hc3NpZ24vaW5kZXguanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3QvYXNzaWduL2lzLWltcGxlbWVudGVkLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvb2JqZWN0L2Fzc2lnbi9zaGltLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvb2JqZWN0L2lzLWNhbGxhYmxlLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvb2JqZWN0L2tleXMvaW5kZXguanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3Qva2V5cy9pcy1pbXBsZW1lbnRlZC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL25vZGVfbW9kdWxlcy9lczUtZXh0L29iamVjdC9rZXlzL3NoaW0uanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3Qvbm9ybWFsaXplLW9wdGlvbnMuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9vYmplY3QvdmFsaWQtdmFsdWUuanMiLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9ub2RlX21vZHVsZXMvZXM2LXN5bWJvbC9ub2RlX21vZHVsZXMvZXM1LWV4dC9zdHJpbmcvIy9jb250YWlucy9pbmRleC5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL25vZGVfbW9kdWxlcy9lczUtZXh0L3N0cmluZy8jL2NvbnRhaW5zL2lzLWltcGxlbWVudGVkLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vbm9kZV9tb2R1bGVzL2VzNi1zeW1ib2wvbm9kZV9tb2R1bGVzL2VzNS1leHQvc3RyaW5nLyMvY29udGFpbnMvc2hpbS5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL25vZGVfbW9kdWxlcy9lczYtc3ltYm9sL3BvbHlmaWxsLmpzIiwiL1VzZXJzL2dvemFsYS9Qcm9qZWN0cy92b2xjYW4vc3BlY2lmaWNhdGlvbi9jb3JlLmpzb24iLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi9zcGVjaWZpY2F0aW9uL3Byb3RvY29sLmpzb24iLCIvVXNlcnMvZ296YWxhL1Byb2plY3RzL3ZvbGNhbi90eXBlLXN5c3RlbS5qcyIsIi9Vc2Vycy9nb3phbGEvUHJvamVjdHMvdm9sY2FuL3V0aWwuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNUQTtBQUNBO0FBQ0E7QUFDQTs7QUNIQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3RCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaExBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQy9TQTtBQUNBO0FBQ0E7QUFDQTs7QUNIQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNMQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNUQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3RCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDTEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ0xBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNSQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1BBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ05BO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNMQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDUkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNQQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDckRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pKQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMzdUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcmRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlc0NvbnRlbnQiOlsiKGZ1bmN0aW9uIGUodCxuLHIpe2Z1bmN0aW9uIHMobyx1KXtpZighbltvXSl7aWYoIXRbb10pe3ZhciBhPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7aWYoIXUmJmEpcmV0dXJuIGEobywhMCk7aWYoaSlyZXR1cm4gaShvLCEwKTt0aHJvdyBuZXcgRXJyb3IoXCJDYW5ub3QgZmluZCBtb2R1bGUgJ1wiK28rXCInXCIpfXZhciBmPW5bb109e2V4cG9ydHM6e319O3Rbb11bMF0uY2FsbChmLmV4cG9ydHMsZnVuY3Rpb24oZSl7dmFyIG49dFtvXVsxXVtlXTtyZXR1cm4gcyhuP246ZSl9LGYsZi5leHBvcnRzLGUsdCxuLHIpfXJldHVybiBuW29dLmV4cG9ydHN9dmFyIGk9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtmb3IodmFyIG89MDtvPHIubGVuZ3RoO28rKylzKHJbb10pO3JldHVybiBzfSkiLCJcInVzZSBzdHJpY3RcIjtcblxudmFyIENsaWVudCA9IHJlcXVpcmUoXCIuLi9jbGllbnRcIikuQ2xpZW50O1xuXG5mdW5jdGlvbiBjb25uZWN0KHBvcnQpIHtcbiAgdmFyIGNsaWVudCA9IG5ldyBDbGllbnQoKTtcbiAgcmV0dXJuIGNsaWVudC5jb25uZWN0KHBvcnQpO1xufVxuZXhwb3J0cy5jb25uZWN0ID0gY29ubmVjdDtcbiIsIlwidXNlIHN0cmljdFwiO1xuXG5leHBvcnRzLlByb21pc2UgPSBQcm9taXNlO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbnZhciBkZXNjcmliZSA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3I7XG52YXIgQ2xhc3MgPSBmdW5jdGlvbihmaWVsZHMpIHtcbiAgdmFyIG5hbWVzID0gT2JqZWN0LmtleXMoZmllbGRzKTtcbiAgdmFyIGNvbnN0cnVjdG9yID0gbmFtZXMuaW5kZXhPZihcImNvbnN0cnVjdG9yXCIpID49IDAgPyBmaWVsZHMuY29uc3RydWN0b3IgOlxuICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbigpIHt9O1xuICB2YXIgYW5jZXN0b3IgPSBmaWVsZHMuZXh0ZW5kcyB8fCBPYmplY3Q7XG5cbiAgdmFyIGRlc2NyaXB0b3IgPSBuYW1lcy5yZWR1Y2UoZnVuY3Rpb24oZGVzY3JpcHRvciwga2V5KSB7XG4gICAgZGVzY3JpcHRvcltrZXldID0gZGVzY3JpYmUoZmllbGRzLCBrZXkpO1xuICAgIHJldHVybiBkZXNjcmlwdG9yO1xuICB9LCB7fSk7XG5cbiAgdmFyIHByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoYW5jZXN0b3IucHJvdG90eXBlLCBkZXNjcmlwdG9yKTtcblxuICBjb25zdHJ1Y3Rvci5wcm90b3R5cGUgPSBwcm90b3R5cGU7XG4gIHByb3RvdHlwZS5jb25zdHJ1Y3RvciA9IGNvbnN0cnVjdG9yO1xuXG4gIHJldHVybiBjb25zdHJ1Y3Rvcjtcbn07XG5leHBvcnRzLkNsYXNzID0gQ2xhc3M7XG4iLCJcInVzZSBzdHJpY3RcIjtcblxudmFyIENsYXNzID0gcmVxdWlyZShcIi4vY2xhc3NcIikuQ2xhc3M7XG52YXIgVHlwZVN5c3RlbSA9IHJlcXVpcmUoXCIuL3R5cGUtc3lzdGVtXCIpLlR5cGVTeXN0ZW07XG52YXIgdmFsdWVzID0gcmVxdWlyZShcIi4vdXRpbFwiKS52YWx1ZXM7XG52YXIgUHJvbWlzZSA9IHJlcXVpcmUoXCJlczYtcHJvbWlzZVwiKS5Qcm9taXNlO1xudmFyIE1lc3NhZ2VFdmVudCA9IHJlcXVpcmUoXCIuL2V2ZW50XCIpLk1lc3NhZ2VFdmVudDtcblxudmFyIHNwZWNpZmljYXRpb24gPSByZXF1aXJlKFwiLi9zcGVjaWZpY2F0aW9uL2NvcmUuanNvblwiKTtcblxuZnVuY3Rpb24gcmVjb3ZlckFjdG9yRGVzY3JpcHRpb25zKGVycm9yKSB7XG4gIGNvbnNvbGUud2FybihcIkZhaWxlZCB0byBmZXRjaCBwcm90b2NvbCBzcGVjaWZpY2F0aW9uIChzZWUgcmVhc29uIGJlbG93KS4gXCIgK1xuICAgICAgICAgICAgICAgXCJVc2luZyBhIGZhbGxiYWNrIHByb3RvY2FsIHNwZWNpZmljYXRpb24hXCIsXG4gICAgICAgICAgICAgICBlcnJvcik7XG4gIHJldHVybiByZXF1aXJlKFwiLi9zcGVjaWZpY2F0aW9uL3Byb3RvY29sLmpzb25cIik7XG59XG5cbi8vIFR5cGUgdG8gcmVwcmVzZW50IHN1cGVydmlzZXIgYWN0b3IgcmVsYXRpb25zIHRvIGFjdG9ycyB0aGV5IHN1cGVydmlzZVxuLy8gaW4gdGVybXMgb2YgbGlmZXRpbWUgbWFuYWdlbWVudC5cbnZhciBTdXBlcnZpc29yID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oaWQpIHtcbiAgICB0aGlzLmlkID0gaWQ7XG4gICAgdGhpcy53b3JrZXJzID0gW107XG4gIH1cbn0pO1xuXG52YXIgVGVsZW1ldHJ5ID0gQ2xhc3Moe1xuICBhZGQ6IGZ1bmN0aW9uKGlkLCBtcykge1xuICAgIGNvbnNvbGUubG9nKFwidGVsZW1ldHJ5OjpcIiwgaWQsIG1zKVxuICB9XG59KTtcblxuLy8gQ29uc2lkZXIgbWFraW5nIGNsaWVudCBhIHJvb3QgYWN0b3IuXG5cbnZhciBDbGllbnQgPSBDbGFzcyh7XG4gIGNvbnN0cnVjdG9yOiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLnJvb3QgPSBudWxsO1xuICAgIHRoaXMudGVsZW1ldHJ5ID0gbmV3IFRlbGVtZXRyeSgpO1xuXG4gICAgdGhpcy5zZXR1cENvbm5lY3Rpb24oKTtcbiAgICB0aGlzLnNldHVwTGlmZU1hbmFnZW1lbnQoKTtcbiAgICB0aGlzLnNldHVwVHlwZVN5c3RlbSgpO1xuICB9LFxuXG4gIHNldHVwQ29ubmVjdGlvbjogZnVuY3Rpb24oKSB7XG4gICAgdGhpcy5yZXF1ZXN0cyA9IFtdO1xuICB9LFxuICBzZXR1cExpZmVNYW5hZ2VtZW50OiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLmNhY2hlID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLmdyYXBoID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB0aGlzLmdldCA9IHRoaXMuZ2V0LmJpbmQodGhpcyk7XG4gICAgdGhpcy5yZWxlYXNlID0gdGhpcy5yZWxlYXNlLmJpbmQodGhpcyk7XG4gIH0sXG4gIHNldHVwVHlwZVN5c3RlbTogZnVuY3Rpb24oKSB7XG4gICAgdGhpcy50eXBlU3lzdGVtID0gbmV3IFR5cGVTeXN0ZW0odGhpcyk7XG4gICAgdGhpcy50eXBlU3lzdGVtLnJlZ2lzdGVyVHlwZXMoc3BlY2lmaWNhdGlvbik7XG4gIH0sXG5cbiAgY29ubmVjdDogZnVuY3Rpb24ocG9ydCkge1xuICAgIHZhciBjbGllbnQgPSB0aGlzO1xuICAgIHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbihyZXNvbHZlLCByZWplY3QpIHtcbiAgICAgIGNsaWVudC5wb3J0ID0gcG9ydDtcbiAgICAgIHBvcnQub25tZXNzYWdlID0gY2xpZW50LnJlY2VpdmUuYmluZChjbGllbnQpO1xuICAgICAgY2xpZW50Lm9uUmVhZHkgPSByZXNvbHZlO1xuICAgICAgY2xpZW50Lm9uRmFpbCA9IHJlamVjdDtcblxuICAgICAgcG9ydC5zdGFydCgpO1xuICAgIH0pO1xuICB9LFxuICBzZW5kOiBmdW5jdGlvbihwYWNrZXQpIHtcbiAgICB0aGlzLnBvcnQucG9zdE1lc3NhZ2UocGFja2V0KTtcbiAgfSxcbiAgcmVxdWVzdDogZnVuY3Rpb24ocGFja2V0KSB7XG4gICAgdmFyIGNsaWVudCA9IHRoaXM7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKHJlc29sdmUsIHJlamVjdCkge1xuICAgICAgY2xpZW50LnJlcXVlc3RzLnB1c2gocGFja2V0LnRvLCB7IHJlc29sdmU6IHJlc29sdmUsIHJlamVjdDogcmVqZWN0IH0pO1xuICAgICAgY2xpZW50LnNlbmQocGFja2V0KTtcbiAgICB9KTtcbiAgfSxcblxuICByZWNlaXZlOiBmdW5jdGlvbihldmVudCkge1xuICAgIHZhciBwYWNrZXQgPSBldmVudC5kYXRhO1xuICAgIGlmICghdGhpcy5yb290KSB7XG4gICAgICBpZiAocGFja2V0LmZyb20gIT09IFwicm9vdFwiKVxuICAgICAgICB0aHJvdyBFcnJvcihcIkluaXRpYWwgcGFja2V0IG11c3QgYmUgZnJvbSByb290XCIpO1xuICAgICAgaWYgKCEoXCJhcHBsaWNhdGlvblR5cGVcIiBpbiBwYWNrZXQpKVxuICAgICAgICB0aHJvdyBFcnJvcihcIkluaXRpYWwgcGFja2V0IG11c3QgY29udGFpbiBhcHBsaWNhdGlvblR5cGUgZmllbGRcIik7XG5cbiAgICAgIHRoaXMucm9vdCA9IHRoaXMudHlwZVN5c3RlbS5yZWFkKFwicm9vdFwiLCBudWxsLCBcInJvb3RcIik7XG4gICAgICB0aGlzLnJvb3RcbiAgICAgICAgICAucHJvdG9jb2xEZXNjcmlwdGlvbigpXG4gICAgICAgICAgLmNhdGNoKHJlY292ZXJBY3RvckRlc2NyaXB0aW9ucylcbiAgICAgICAgICAudGhlbih0aGlzLnR5cGVTeXN0ZW0ucmVnaXN0ZXJUeXBlcy5iaW5kKHRoaXMudHlwZVN5c3RlbSkpXG4gICAgICAgICAgLnRoZW4odGhpcy5vblJlYWR5LmJpbmQodGhpcywgdGhpcy5yb290KSwgdGhpcy5vbkZhaWwpO1xuICAgIH0gZWxzZSB7XG4gICAgICB2YXIgYWN0b3IgPSB0aGlzLmdldChwYWNrZXQuZnJvbSkgfHwgdGhpcy5yb290O1xuICAgICAgdmFyIGV2ZW50ID0gYWN0b3IuZXZlbnRzW3BhY2tldC50eXBlXTtcbiAgICAgIGlmIChldmVudCkge1xuICAgICAgICB2YXIgbWVzc2FnZSA9IG5ldyBNZXNzYWdlRXZlbnQocGFja2V0LnR5cGUsIHtcbiAgICAgICAgICBkYXRhOiBldmVudC5yZWFkKHBhY2tldClcbiAgICAgICAgfSk7XG4gICAgICAgIGFjdG9yLmRpc3BhdGNoRXZlbnQobWVzc2FnZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB2YXIgaW5kZXggPSB0aGlzLnJlcXVlc3RzLmluZGV4T2YoYWN0b3IuaWQpO1xuICAgICAgICBpZiAoaW5kZXggPj0gMCkge1xuICAgICAgICAgIHZhciByZXF1ZXN0ID0gdGhpcy5yZXF1ZXN0cy5zcGxpY2UoaW5kZXgsIDIpLnBvcCgpO1xuICAgICAgICAgIGlmIChwYWNrZXQuZXJyb3IpXG4gICAgICAgICAgICByZXF1ZXN0LnJlamVjdChwYWNrZXQpO1xuICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgIHJlcXVlc3QucmVzb2x2ZShwYWNrZXQpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGNvbnNvbGUuZXJyb3IoRXJyb3IoXCJVbmV4cGVjdGVkIHBhY2tldCBcIiArIEpTT04uc3RyaW5naWZ5KHBhY2tldCwgMiwgMikpLFxuICAgICAgICAgICAgICAgICAgICAgICAgcGFja2V0LFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5yZXF1ZXN0cy5zbGljZSgwKSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH0sXG5cbiAgZ2V0OiBmdW5jdGlvbihpZCkge1xuICAgIHJldHVybiB0aGlzLmNhY2hlW2lkXTtcbiAgfSxcbiAgc3VwZXJ2aXNvck9mOiBmdW5jdGlvbihhY3Rvcikge1xuICAgIGZvciAodmFyIGlkIGluIHRoaXMuZ3JhcGgpIHtcbiAgICAgIGlmICh0aGlzLmdyYXBoW2lkXS5pbmRleE9mKGFjdG9yLmlkKSA+PSAwKSB7XG4gICAgICAgIHJldHVybiBpZDtcbiAgICAgIH1cbiAgICB9XG4gIH0sXG4gIHdvcmtlcnNPZjogZnVuY3Rpb24oYWN0b3IpIHtcbiAgICByZXR1cm4gdGhpcy5ncmFwaFthY3Rvci5pZF07XG4gIH0sXG4gIHN1cGVydmlzZTogZnVuY3Rpb24oYWN0b3IsIHdvcmtlcikge1xuICAgIHZhciB3b3JrZXJzID0gdGhpcy53b3JrZXJzT2YoYWN0b3IpXG4gICAgaWYgKHdvcmtlcnMuaW5kZXhPZih3b3JrZXIuaWQpIDwgMCkge1xuICAgICAgd29ya2Vycy5wdXNoKHdvcmtlci5pZCk7XG4gICAgfVxuICB9LFxuICB1bnN1cGVydmlzZTogZnVuY3Rpb24oYWN0b3IsIHdvcmtlcikge1xuICAgIHZhciB3b3JrZXJzID0gdGhpcy53b3JrZXJzT2YoYWN0b3IpO1xuICAgIHZhciBpbmRleCA9IHdvcmtlcnMuaW5kZXhPZih3b3JrZXIuaWQpXG4gICAgaWYgKGluZGV4ID49IDApIHtcbiAgICAgIHdvcmtlcnMuc3BsaWNlKGluZGV4LCAxKVxuICAgIH1cbiAgfSxcblxuICByZWdpc3RlcjogZnVuY3Rpb24oYWN0b3IpIHtcbiAgICB2YXIgcmVnaXN0ZXJlZCA9IHRoaXMuZ2V0KGFjdG9yLmlkKTtcbiAgICBpZiAoIXJlZ2lzdGVyZWQpIHtcbiAgICAgIHRoaXMuY2FjaGVbYWN0b3IuaWRdID0gYWN0b3I7XG4gICAgICB0aGlzLmdyYXBoW2FjdG9yLmlkXSA9IFtdO1xuICAgIH0gZWxzZSBpZiAocmVnaXN0ZXJlZCAhPT0gYWN0b3IpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIkRpZmZlcmVudCBhY3RvciB3aXRoIHNhbWUgaWQgaXMgYWxyZWFkeSByZWdpc3RlcmVkXCIpO1xuICAgIH1cbiAgfSxcbiAgdW5yZWdpc3RlcjogZnVuY3Rpb24oYWN0b3IpIHtcbiAgICBpZiAodGhpcy5nZXQoYWN0b3IuaWQpKSB7XG4gICAgICBkZWxldGUgdGhpcy5jYWNoZVthY3Rvci5pZF07XG4gICAgICBkZWxldGUgdGhpcy5ncmFwaFthY3Rvci5pZF07XG4gICAgfVxuICB9LFxuXG4gIHJlbGVhc2U6IGZ1bmN0aW9uKGFjdG9yKSB7XG4gICAgdmFyIHN1cGVydmlzb3IgPSB0aGlzLnN1cGVydmlzb3JPZihhY3Rvcik7XG4gICAgaWYgKHN1cGVydmlzb3IpXG4gICAgICB0aGlzLnVuc3VwZXJ2aXNlKHN1cGVydmlzb3IsIGFjdG9yKTtcblxuICAgIHZhciB3b3JrZXJzID0gdGhpcy53b3JrZXJzT2YoYWN0b3IpXG5cbiAgICBpZiAod29ya2Vycykge1xuICAgICAgd29ya2Vycy5tYXAodGhpcy5nZXQpLmZvckVhY2godGhpcy5yZWxlYXNlKVxuICAgIH1cbiAgICB0aGlzLnVucmVnaXN0ZXIoYWN0b3IpO1xuICB9XG59KTtcbmV4cG9ydHMuQ2xpZW50ID0gQ2xpZW50O1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbnZhciBTeW1ib2wgPSByZXF1aXJlKFwiZXM2LXN5bWJvbFwiKVxudmFyIEV2ZW50RW1pdHRlciA9IHJlcXVpcmUoXCJldmVudHNcIikuRXZlbnRFbWl0dGVyO1xudmFyIENsYXNzID0gcmVxdWlyZShcIi4vY2xhc3NcIikuQ2xhc3M7XG5cbnZhciAkYm91bmQgPSBTeW1ib2woXCJFdmVudFRhcmdldC9oYW5kbGVFdmVudFwiKTtcbnZhciAkZW1pdHRlciA9IFN5bWJvbChcIkV2ZW50VGFyZ2V0L2VtaXR0ZXJcIik7XG5cbmZ1bmN0aW9uIG1ha2VIYW5kbGVyKGhhbmRsZXIpIHtcbiAgcmV0dXJuIGZ1bmN0aW9uKGV2ZW50KSB7XG4gICAgaGFuZGxlci5oYW5kbGVFdmVudChldmVudCk7XG4gIH1cbn1cblxudmFyIEV2ZW50VGFyZ2V0ID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KHRoaXMsICRlbWl0dGVyLCB7XG4gICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICAgIHdyaXRhYmxlOiB0cnVlLFxuICAgICAgdmFsdWU6IG5ldyBFdmVudEVtaXR0ZXIoKVxuICAgIH0pO1xuICB9LFxuICBhZGRFdmVudExpc3RlbmVyOiBmdW5jdGlvbih0eXBlLCBoYW5kbGVyKSB7XG4gICAgaWYgKHR5cGVvZihoYW5kbGVyKSA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICB0aGlzWyRlbWl0dGVyXS5vbih0eXBlLCBoYW5kbGVyKTtcbiAgICB9XG4gICAgZWxzZSBpZiAoaGFuZGxlciAmJiB0eXBlb2YoaGFuZGxlcikgPT09IFwib2JqZWN0XCIpIHtcbiAgICAgIGlmICghaGFuZGxlclskYm91bmRdKSBoYW5kbGVyWyRib3VuZF0gPSBtYWtlSGFuZGxlcihoYW5kbGVyKTtcbiAgICAgIHRoaXNbJGVtaXR0ZXJdLm9uKHR5cGUsIGhhbmRsZXJbJGJvdW5kXSk7XG4gICAgfVxuICB9LFxuICByZW1vdmVFdmVudExpc3RlbmVyOiBmdW5jdGlvbih0eXBlLCBoYW5kbGVyKSB7XG4gICAgaWYgKHR5cGVvZihoYW5kbGVyKSA9PT0gXCJmdW5jdGlvblwiKVxuICAgICAgdGhpc1skZW1pdHRlcl0ucmVtb3ZlTGlzdGVuZXIodHlwZSwgaGFuZGxlcik7XG4gICAgZWxzZSBpZiAoaGFuZGxlciAmJiBoYW5kbGVyWyRib3VuZF0pXG4gICAgICB0aGlzWyRlbWl0dGVyXS5yZW1vdmVMaXN0ZW5lcih0eXBlLCBoYW5kbGVyWyRib3VuZF0pO1xuICB9LFxuICBkaXNwYXRjaEV2ZW50OiBmdW5jdGlvbihldmVudCkge1xuICAgIGV2ZW50LnRhcmdldCA9IHRoaXM7XG4gICAgdGhpc1skZW1pdHRlcl0uZW1pdChldmVudC50eXBlLCBldmVudCk7XG4gIH1cbn0pO1xuZXhwb3J0cy5FdmVudFRhcmdldCA9IEV2ZW50VGFyZ2V0O1xuXG52YXIgTWVzc2FnZUV2ZW50ID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24odHlwZSwgb3B0aW9ucykge1xuICAgIG9wdGlvbnMgPSBvcHRpb25zIHx8IHt9O1xuICAgIHRoaXMudHlwZSA9IHR5cGU7XG4gICAgdGhpcy5kYXRhID0gb3B0aW9ucy5kYXRhID09PSB2b2lkKDApID8gbnVsbCA6IG9wdGlvbnMuZGF0YTtcblxuICAgIHRoaXMubGFzdEV2ZW50SWQgPSBvcHRpb25zLmxhc3RFdmVudElkIHx8IFwiXCI7XG4gICAgdGhpcy5vcmlnaW4gPSBvcHRpb25zLm9yaWdpbiB8fCBcIlwiO1xuICAgIHRoaXMuYnViYmxlcyA9IG9wdGlvbnMuYnViYmxlcyB8fCBmYWxzZTtcbiAgICB0aGlzLmNhbmNlbGFibGUgPSBvcHRpb25zLmNhbmNlbGFibGUgfHwgZmFsc2U7XG4gIH0sXG4gIHNvdXJjZTogbnVsbCxcbiAgcG9ydHM6IG51bGwsXG4gIHByZXZlbnREZWZhdWx0OiBmdW5jdGlvbigpIHtcbiAgfSxcbiAgc3RvcFByb3BhZ2F0aW9uOiBmdW5jdGlvbigpIHtcbiAgfSxcbiAgc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uOiBmdW5jdGlvbigpIHtcbiAgfVxufSk7XG5leHBvcnRzLk1lc3NhZ2VFdmVudCA9IE1lc3NhZ2VFdmVudDtcbiIsIi8vIENvcHlyaWdodCBKb3llbnQsIEluYy4gYW5kIG90aGVyIE5vZGUgY29udHJpYnV0b3JzLlxuLy9cbi8vIFBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhXG4vLyBjb3B5IG9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlXG4vLyBcIlNvZnR3YXJlXCIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmdcbi8vIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzIHRvIHVzZSwgY29weSwgbW9kaWZ5LCBtZXJnZSwgcHVibGlzaCxcbi8vIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsIGNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXRcbi8vIHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZVxuLy8gZm9sbG93aW5nIGNvbmRpdGlvbnM6XG4vL1xuLy8gVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWRcbi8vIGluIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1Ncbi8vIE9SIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0Zcbi8vIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU5cbi8vIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLFxuLy8gREFNQUdFUyBPUiBPVEhFUiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SXG4vLyBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFXG4vLyBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLlxuXG5mdW5jdGlvbiBFdmVudEVtaXR0ZXIoKSB7XG4gIHRoaXMuX2V2ZW50cyA9IHRoaXMuX2V2ZW50cyB8fCB7fTtcbiAgdGhpcy5fbWF4TGlzdGVuZXJzID0gdGhpcy5fbWF4TGlzdGVuZXJzIHx8IHVuZGVmaW5lZDtcbn1cbm1vZHVsZS5leHBvcnRzID0gRXZlbnRFbWl0dGVyO1xuXG4vLyBCYWNrd2FyZHMtY29tcGF0IHdpdGggbm9kZSAwLjEwLnhcbkV2ZW50RW1pdHRlci5FdmVudEVtaXR0ZXIgPSBFdmVudEVtaXR0ZXI7XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX2V2ZW50cyA9IHVuZGVmaW5lZDtcbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuX21heExpc3RlbmVycyA9IHVuZGVmaW5lZDtcblxuLy8gQnkgZGVmYXVsdCBFdmVudEVtaXR0ZXJzIHdpbGwgcHJpbnQgYSB3YXJuaW5nIGlmIG1vcmUgdGhhbiAxMCBsaXN0ZW5lcnMgYXJlXG4vLyBhZGRlZCB0byBpdC4gVGhpcyBpcyBhIHVzZWZ1bCBkZWZhdWx0IHdoaWNoIGhlbHBzIGZpbmRpbmcgbWVtb3J5IGxlYWtzLlxuRXZlbnRFbWl0dGVyLmRlZmF1bHRNYXhMaXN0ZW5lcnMgPSAxMDtcblxuLy8gT2J2aW91c2x5IG5vdCBhbGwgRW1pdHRlcnMgc2hvdWxkIGJlIGxpbWl0ZWQgdG8gMTAuIFRoaXMgZnVuY3Rpb24gYWxsb3dzXG4vLyB0aGF0IHRvIGJlIGluY3JlYXNlZC4gU2V0IHRvIHplcm8gZm9yIHVubGltaXRlZC5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuc2V0TWF4TGlzdGVuZXJzID0gZnVuY3Rpb24obikge1xuICBpZiAoIWlzTnVtYmVyKG4pIHx8IG4gPCAwIHx8IGlzTmFOKG4pKVxuICAgIHRocm93IFR5cGVFcnJvcignbiBtdXN0IGJlIGEgcG9zaXRpdmUgbnVtYmVyJyk7XG4gIHRoaXMuX21heExpc3RlbmVycyA9IG47XG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5lbWl0ID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIgZXIsIGhhbmRsZXIsIGxlbiwgYXJncywgaSwgbGlzdGVuZXJzO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzKVxuICAgIHRoaXMuX2V2ZW50cyA9IHt9O1xuXG4gIC8vIElmIHRoZXJlIGlzIG5vICdlcnJvcicgZXZlbnQgbGlzdGVuZXIgdGhlbiB0aHJvdy5cbiAgaWYgKHR5cGUgPT09ICdlcnJvcicpIHtcbiAgICBpZiAoIXRoaXMuX2V2ZW50cy5lcnJvciB8fFxuICAgICAgICAoaXNPYmplY3QodGhpcy5fZXZlbnRzLmVycm9yKSAmJiAhdGhpcy5fZXZlbnRzLmVycm9yLmxlbmd0aCkpIHtcbiAgICAgIGVyID0gYXJndW1lbnRzWzFdO1xuICAgICAgaWYgKGVyIGluc3RhbmNlb2YgRXJyb3IpIHtcbiAgICAgICAgdGhyb3cgZXI7IC8vIFVuaGFuZGxlZCAnZXJyb3InIGV2ZW50XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aHJvdyBUeXBlRXJyb3IoJ1VuY2F1Z2h0LCB1bnNwZWNpZmllZCBcImVycm9yXCIgZXZlbnQuJyk7XG4gICAgICB9XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICB9XG5cbiAgaGFuZGxlciA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICBpZiAoaXNVbmRlZmluZWQoaGFuZGxlcikpXG4gICAgcmV0dXJuIGZhbHNlO1xuXG4gIGlmIChpc0Z1bmN0aW9uKGhhbmRsZXIpKSB7XG4gICAgc3dpdGNoIChhcmd1bWVudHMubGVuZ3RoKSB7XG4gICAgICAvLyBmYXN0IGNhc2VzXG4gICAgICBjYXNlIDE6XG4gICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIDI6XG4gICAgICAgIGhhbmRsZXIuY2FsbCh0aGlzLCBhcmd1bWVudHNbMV0pO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgMzpcbiAgICAgICAgaGFuZGxlci5jYWxsKHRoaXMsIGFyZ3VtZW50c1sxXSwgYXJndW1lbnRzWzJdKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICAvLyBzbG93ZXJcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIGxlbiA9IGFyZ3VtZW50cy5sZW5ndGg7XG4gICAgICAgIGFyZ3MgPSBuZXcgQXJyYXkobGVuIC0gMSk7XG4gICAgICAgIGZvciAoaSA9IDE7IGkgPCBsZW47IGkrKylcbiAgICAgICAgICBhcmdzW2kgLSAxXSA9IGFyZ3VtZW50c1tpXTtcbiAgICAgICAgaGFuZGxlci5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICB9XG4gIH0gZWxzZSBpZiAoaXNPYmplY3QoaGFuZGxlcikpIHtcbiAgICBsZW4gPSBhcmd1bWVudHMubGVuZ3RoO1xuICAgIGFyZ3MgPSBuZXcgQXJyYXkobGVuIC0gMSk7XG4gICAgZm9yIChpID0gMTsgaSA8IGxlbjsgaSsrKVxuICAgICAgYXJnc1tpIC0gMV0gPSBhcmd1bWVudHNbaV07XG5cbiAgICBsaXN0ZW5lcnMgPSBoYW5kbGVyLnNsaWNlKCk7XG4gICAgbGVuID0gbGlzdGVuZXJzLmxlbmd0aDtcbiAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspXG4gICAgICBsaXN0ZW5lcnNbaV0uYXBwbHkodGhpcywgYXJncyk7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUuYWRkTGlzdGVuZXIgPSBmdW5jdGlvbih0eXBlLCBsaXN0ZW5lcikge1xuICB2YXIgbTtcblxuICBpZiAoIWlzRnVuY3Rpb24obGlzdGVuZXIpKVxuICAgIHRocm93IFR5cGVFcnJvcignbGlzdGVuZXIgbXVzdCBiZSBhIGZ1bmN0aW9uJyk7XG5cbiAgaWYgKCF0aGlzLl9ldmVudHMpXG4gICAgdGhpcy5fZXZlbnRzID0ge307XG5cbiAgLy8gVG8gYXZvaWQgcmVjdXJzaW9uIGluIHRoZSBjYXNlIHRoYXQgdHlwZSA9PT0gXCJuZXdMaXN0ZW5lclwiISBCZWZvcmVcbiAgLy8gYWRkaW5nIGl0IHRvIHRoZSBsaXN0ZW5lcnMsIGZpcnN0IGVtaXQgXCJuZXdMaXN0ZW5lclwiLlxuICBpZiAodGhpcy5fZXZlbnRzLm5ld0xpc3RlbmVyKVxuICAgIHRoaXMuZW1pdCgnbmV3TGlzdGVuZXInLCB0eXBlLFxuICAgICAgICAgICAgICBpc0Z1bmN0aW9uKGxpc3RlbmVyLmxpc3RlbmVyKSA/XG4gICAgICAgICAgICAgIGxpc3RlbmVyLmxpc3RlbmVyIDogbGlzdGVuZXIpO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgIC8vIE9wdGltaXplIHRoZSBjYXNlIG9mIG9uZSBsaXN0ZW5lci4gRG9uJ3QgbmVlZCB0aGUgZXh0cmEgYXJyYXkgb2JqZWN0LlxuICAgIHRoaXMuX2V2ZW50c1t0eXBlXSA9IGxpc3RlbmVyO1xuICBlbHNlIGlmIChpc09iamVjdCh0aGlzLl9ldmVudHNbdHlwZV0pKVxuICAgIC8vIElmIHdlJ3ZlIGFscmVhZHkgZ290IGFuIGFycmF5LCBqdXN0IGFwcGVuZC5cbiAgICB0aGlzLl9ldmVudHNbdHlwZV0ucHVzaChsaXN0ZW5lcik7XG4gIGVsc2VcbiAgICAvLyBBZGRpbmcgdGhlIHNlY29uZCBlbGVtZW50LCBuZWVkIHRvIGNoYW5nZSB0byBhcnJheS5cbiAgICB0aGlzLl9ldmVudHNbdHlwZV0gPSBbdGhpcy5fZXZlbnRzW3R5cGVdLCBsaXN0ZW5lcl07XG5cbiAgLy8gQ2hlY2sgZm9yIGxpc3RlbmVyIGxlYWtcbiAgaWYgKGlzT2JqZWN0KHRoaXMuX2V2ZW50c1t0eXBlXSkgJiYgIXRoaXMuX2V2ZW50c1t0eXBlXS53YXJuZWQpIHtcbiAgICB2YXIgbTtcbiAgICBpZiAoIWlzVW5kZWZpbmVkKHRoaXMuX21heExpc3RlbmVycykpIHtcbiAgICAgIG0gPSB0aGlzLl9tYXhMaXN0ZW5lcnM7XG4gICAgfSBlbHNlIHtcbiAgICAgIG0gPSBFdmVudEVtaXR0ZXIuZGVmYXVsdE1heExpc3RlbmVycztcbiAgICB9XG5cbiAgICBpZiAobSAmJiBtID4gMCAmJiB0aGlzLl9ldmVudHNbdHlwZV0ubGVuZ3RoID4gbSkge1xuICAgICAgdGhpcy5fZXZlbnRzW3R5cGVdLndhcm5lZCA9IHRydWU7XG4gICAgICBjb25zb2xlLmVycm9yKCcobm9kZSkgd2FybmluZzogcG9zc2libGUgRXZlbnRFbWl0dGVyIG1lbW9yeSAnICtcbiAgICAgICAgICAgICAgICAgICAgJ2xlYWsgZGV0ZWN0ZWQuICVkIGxpc3RlbmVycyBhZGRlZC4gJyArXG4gICAgICAgICAgICAgICAgICAgICdVc2UgZW1pdHRlci5zZXRNYXhMaXN0ZW5lcnMoKSB0byBpbmNyZWFzZSBsaW1pdC4nLFxuICAgICAgICAgICAgICAgICAgICB0aGlzLl9ldmVudHNbdHlwZV0ubGVuZ3RoKTtcbiAgICAgIGlmICh0eXBlb2YgY29uc29sZS50cmFjZSA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICAvLyBub3Qgc3VwcG9ydGVkIGluIElFIDEwXG4gICAgICAgIGNvbnNvbGUudHJhY2UoKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICByZXR1cm4gdGhpcztcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUub24gPSBFdmVudEVtaXR0ZXIucHJvdG90eXBlLmFkZExpc3RlbmVyO1xuXG5FdmVudEVtaXR0ZXIucHJvdG90eXBlLm9uY2UgPSBmdW5jdGlvbih0eXBlLCBsaXN0ZW5lcikge1xuICBpZiAoIWlzRnVuY3Rpb24obGlzdGVuZXIpKVxuICAgIHRocm93IFR5cGVFcnJvcignbGlzdGVuZXIgbXVzdCBiZSBhIGZ1bmN0aW9uJyk7XG5cbiAgdmFyIGZpcmVkID0gZmFsc2U7XG5cbiAgZnVuY3Rpb24gZygpIHtcbiAgICB0aGlzLnJlbW92ZUxpc3RlbmVyKHR5cGUsIGcpO1xuXG4gICAgaWYgKCFmaXJlZCkge1xuICAgICAgZmlyZWQgPSB0cnVlO1xuICAgICAgbGlzdGVuZXIuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICB9XG4gIH1cblxuICBnLmxpc3RlbmVyID0gbGlzdGVuZXI7XG4gIHRoaXMub24odHlwZSwgZyk7XG5cbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vLyBlbWl0cyBhICdyZW1vdmVMaXN0ZW5lcicgZXZlbnQgaWZmIHRoZSBsaXN0ZW5lciB3YXMgcmVtb3ZlZFxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5yZW1vdmVMaXN0ZW5lciA9IGZ1bmN0aW9uKHR5cGUsIGxpc3RlbmVyKSB7XG4gIHZhciBsaXN0LCBwb3NpdGlvbiwgbGVuZ3RoLCBpO1xuXG4gIGlmICghaXNGdW5jdGlvbihsaXN0ZW5lcikpXG4gICAgdGhyb3cgVHlwZUVycm9yKCdsaXN0ZW5lciBtdXN0IGJlIGEgZnVuY3Rpb24nKTtcblxuICBpZiAoIXRoaXMuX2V2ZW50cyB8fCAhdGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgIHJldHVybiB0aGlzO1xuXG4gIGxpc3QgPSB0aGlzLl9ldmVudHNbdHlwZV07XG4gIGxlbmd0aCA9IGxpc3QubGVuZ3RoO1xuICBwb3NpdGlvbiA9IC0xO1xuXG4gIGlmIChsaXN0ID09PSBsaXN0ZW5lciB8fFxuICAgICAgKGlzRnVuY3Rpb24obGlzdC5saXN0ZW5lcikgJiYgbGlzdC5saXN0ZW5lciA9PT0gbGlzdGVuZXIpKSB7XG4gICAgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTtcbiAgICBpZiAodGhpcy5fZXZlbnRzLnJlbW92ZUxpc3RlbmVyKVxuICAgICAgdGhpcy5lbWl0KCdyZW1vdmVMaXN0ZW5lcicsIHR5cGUsIGxpc3RlbmVyKTtcblxuICB9IGVsc2UgaWYgKGlzT2JqZWN0KGxpc3QpKSB7XG4gICAgZm9yIChpID0gbGVuZ3RoOyBpLS0gPiAwOykge1xuICAgICAgaWYgKGxpc3RbaV0gPT09IGxpc3RlbmVyIHx8XG4gICAgICAgICAgKGxpc3RbaV0ubGlzdGVuZXIgJiYgbGlzdFtpXS5saXN0ZW5lciA9PT0gbGlzdGVuZXIpKSB7XG4gICAgICAgIHBvc2l0aW9uID0gaTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHBvc2l0aW9uIDwgMClcbiAgICAgIHJldHVybiB0aGlzO1xuXG4gICAgaWYgKGxpc3QubGVuZ3RoID09PSAxKSB7XG4gICAgICBsaXN0Lmxlbmd0aCA9IDA7XG4gICAgICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuICAgIH0gZWxzZSB7XG4gICAgICBsaXN0LnNwbGljZShwb3NpdGlvbiwgMSk7XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuX2V2ZW50cy5yZW1vdmVMaXN0ZW5lcilcbiAgICAgIHRoaXMuZW1pdCgncmVtb3ZlTGlzdGVuZXInLCB0eXBlLCBsaXN0ZW5lcik7XG4gIH1cblxuICByZXR1cm4gdGhpcztcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUucmVtb3ZlQWxsTGlzdGVuZXJzID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIga2V5LCBsaXN0ZW5lcnM7XG5cbiAgaWYgKCF0aGlzLl9ldmVudHMpXG4gICAgcmV0dXJuIHRoaXM7XG5cbiAgLy8gbm90IGxpc3RlbmluZyBmb3IgcmVtb3ZlTGlzdGVuZXIsIG5vIG5lZWQgdG8gZW1pdFxuICBpZiAoIXRoaXMuX2V2ZW50cy5yZW1vdmVMaXN0ZW5lcikge1xuICAgIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAwKVxuICAgICAgdGhpcy5fZXZlbnRzID0ge307XG4gICAgZWxzZSBpZiAodGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgICAgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTtcbiAgICByZXR1cm4gdGhpcztcbiAgfVxuXG4gIC8vIGVtaXQgcmVtb3ZlTGlzdGVuZXIgZm9yIGFsbCBsaXN0ZW5lcnMgb24gYWxsIGV2ZW50c1xuICBpZiAoYXJndW1lbnRzLmxlbmd0aCA9PT0gMCkge1xuICAgIGZvciAoa2V5IGluIHRoaXMuX2V2ZW50cykge1xuICAgICAgaWYgKGtleSA9PT0gJ3JlbW92ZUxpc3RlbmVyJykgY29udGludWU7XG4gICAgICB0aGlzLnJlbW92ZUFsbExpc3RlbmVycyhrZXkpO1xuICAgIH1cbiAgICB0aGlzLnJlbW92ZUFsbExpc3RlbmVycygncmVtb3ZlTGlzdGVuZXInKTtcbiAgICB0aGlzLl9ldmVudHMgPSB7fTtcbiAgICByZXR1cm4gdGhpcztcbiAgfVxuXG4gIGxpc3RlbmVycyA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICBpZiAoaXNGdW5jdGlvbihsaXN0ZW5lcnMpKSB7XG4gICAgdGhpcy5yZW1vdmVMaXN0ZW5lcih0eXBlLCBsaXN0ZW5lcnMpO1xuICB9IGVsc2Uge1xuICAgIC8vIExJRk8gb3JkZXJcbiAgICB3aGlsZSAobGlzdGVuZXJzLmxlbmd0aClcbiAgICAgIHRoaXMucmVtb3ZlTGlzdGVuZXIodHlwZSwgbGlzdGVuZXJzW2xpc3RlbmVycy5sZW5ndGggLSAxXSk7XG4gIH1cbiAgZGVsZXRlIHRoaXMuX2V2ZW50c1t0eXBlXTtcblxuICByZXR1cm4gdGhpcztcbn07XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUubGlzdGVuZXJzID0gZnVuY3Rpb24odHlwZSkge1xuICB2YXIgcmV0O1xuICBpZiAoIXRoaXMuX2V2ZW50cyB8fCAhdGhpcy5fZXZlbnRzW3R5cGVdKVxuICAgIHJldCA9IFtdO1xuICBlbHNlIGlmIChpc0Z1bmN0aW9uKHRoaXMuX2V2ZW50c1t0eXBlXSkpXG4gICAgcmV0ID0gW3RoaXMuX2V2ZW50c1t0eXBlXV07XG4gIGVsc2VcbiAgICByZXQgPSB0aGlzLl9ldmVudHNbdHlwZV0uc2xpY2UoKTtcbiAgcmV0dXJuIHJldDtcbn07XG5cbkV2ZW50RW1pdHRlci5saXN0ZW5lckNvdW50ID0gZnVuY3Rpb24oZW1pdHRlciwgdHlwZSkge1xuICB2YXIgcmV0O1xuICBpZiAoIWVtaXR0ZXIuX2V2ZW50cyB8fCAhZW1pdHRlci5fZXZlbnRzW3R5cGVdKVxuICAgIHJldCA9IDA7XG4gIGVsc2UgaWYgKGlzRnVuY3Rpb24oZW1pdHRlci5fZXZlbnRzW3R5cGVdKSlcbiAgICByZXQgPSAxO1xuICBlbHNlXG4gICAgcmV0ID0gZW1pdHRlci5fZXZlbnRzW3R5cGVdLmxlbmd0aDtcbiAgcmV0dXJuIHJldDtcbn07XG5cbmZ1bmN0aW9uIGlzRnVuY3Rpb24oYXJnKSB7XG4gIHJldHVybiB0eXBlb2YgYXJnID09PSAnZnVuY3Rpb24nO1xufVxuXG5mdW5jdGlvbiBpc051bWJlcihhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdudW1iZXInO1xufVxuXG5mdW5jdGlvbiBpc09iamVjdChhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdvYmplY3QnICYmIGFyZyAhPT0gbnVsbDtcbn1cblxuZnVuY3Rpb24gaXNVbmRlZmluZWQoYXJnKSB7XG4gIHJldHVybiBhcmcgPT09IHZvaWQgMDtcbn1cbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL2lzLWltcGxlbWVudGVkJykoKSA/IFN5bWJvbCA6IHJlcXVpcmUoJy4vcG9seWZpbGwnKTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoKSB7XG5cdHZhciBzeW1ib2w7XG5cdGlmICh0eXBlb2YgU3ltYm9sICE9PSAnZnVuY3Rpb24nKSByZXR1cm4gZmFsc2U7XG5cdHN5bWJvbCA9IFN5bWJvbCgndGVzdCBzeW1ib2wnKTtcblx0dHJ5IHtcblx0XHRpZiAoU3RyaW5nKHN5bWJvbCkgIT09ICdTeW1ib2wgKHRlc3Qgc3ltYm9sKScpIHJldHVybiBmYWxzZTtcblx0fSBjYXRjaCAoZSkgeyByZXR1cm4gZmFsc2U7IH1cblx0aWYgKHR5cGVvZiBTeW1ib2wuaXRlcmF0b3IgPT09ICdzeW1ib2wnKSByZXR1cm4gdHJ1ZTtcblxuXHQvLyBSZXR1cm4gJ3RydWUnIGZvciBwb2x5ZmlsbHNcblx0aWYgKHR5cGVvZiBTeW1ib2wuaXNDb25jYXRTcHJlYWRhYmxlICE9PSAnb2JqZWN0JykgcmV0dXJuIGZhbHNlO1xuXHRpZiAodHlwZW9mIFN5bWJvbC5pc1JlZ0V4cCAhPT0gJ29iamVjdCcpIHJldHVybiBmYWxzZTtcblx0aWYgKHR5cGVvZiBTeW1ib2wuaXRlcmF0b3IgIT09ICdvYmplY3QnKSByZXR1cm4gZmFsc2U7XG5cdGlmICh0eXBlb2YgU3ltYm9sLnRvUHJpbWl0aXZlICE9PSAnb2JqZWN0JykgcmV0dXJuIGZhbHNlO1xuXHRpZiAodHlwZW9mIFN5bWJvbC50b1N0cmluZ1RhZyAhPT0gJ29iamVjdCcpIHJldHVybiBmYWxzZTtcblx0aWYgKHR5cGVvZiBTeW1ib2wudW5zY29wYWJsZXMgIT09ICdvYmplY3QnKSByZXR1cm4gZmFsc2U7XG5cblx0cmV0dXJuIHRydWU7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgYXNzaWduICAgICAgICA9IHJlcXVpcmUoJ2VzNS1leHQvb2JqZWN0L2Fzc2lnbicpXG4gICwgbm9ybWFsaXplT3B0cyA9IHJlcXVpcmUoJ2VzNS1leHQvb2JqZWN0L25vcm1hbGl6ZS1vcHRpb25zJylcbiAgLCBpc0NhbGxhYmxlICAgID0gcmVxdWlyZSgnZXM1LWV4dC9vYmplY3QvaXMtY2FsbGFibGUnKVxuICAsIGNvbnRhaW5zICAgICAgPSByZXF1aXJlKCdlczUtZXh0L3N0cmluZy8jL2NvbnRhaW5zJylcblxuICAsIGQ7XG5cbmQgPSBtb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIChkc2NyLCB2YWx1ZS8qLCBvcHRpb25zKi8pIHtcblx0dmFyIGMsIGUsIHcsIG9wdGlvbnMsIGRlc2M7XG5cdGlmICgoYXJndW1lbnRzLmxlbmd0aCA8IDIpIHx8ICh0eXBlb2YgZHNjciAhPT0gJ3N0cmluZycpKSB7XG5cdFx0b3B0aW9ucyA9IHZhbHVlO1xuXHRcdHZhbHVlID0gZHNjcjtcblx0XHRkc2NyID0gbnVsbDtcblx0fSBlbHNlIHtcblx0XHRvcHRpb25zID0gYXJndW1lbnRzWzJdO1xuXHR9XG5cdGlmIChkc2NyID09IG51bGwpIHtcblx0XHRjID0gdyA9IHRydWU7XG5cdFx0ZSA9IGZhbHNlO1xuXHR9IGVsc2Uge1xuXHRcdGMgPSBjb250YWlucy5jYWxsKGRzY3IsICdjJyk7XG5cdFx0ZSA9IGNvbnRhaW5zLmNhbGwoZHNjciwgJ2UnKTtcblx0XHR3ID0gY29udGFpbnMuY2FsbChkc2NyLCAndycpO1xuXHR9XG5cblx0ZGVzYyA9IHsgdmFsdWU6IHZhbHVlLCBjb25maWd1cmFibGU6IGMsIGVudW1lcmFibGU6IGUsIHdyaXRhYmxlOiB3IH07XG5cdHJldHVybiAhb3B0aW9ucyA/IGRlc2MgOiBhc3NpZ24obm9ybWFsaXplT3B0cyhvcHRpb25zKSwgZGVzYyk7XG59O1xuXG5kLmdzID0gZnVuY3Rpb24gKGRzY3IsIGdldCwgc2V0LyosIG9wdGlvbnMqLykge1xuXHR2YXIgYywgZSwgb3B0aW9ucywgZGVzYztcblx0aWYgKHR5cGVvZiBkc2NyICE9PSAnc3RyaW5nJykge1xuXHRcdG9wdGlvbnMgPSBzZXQ7XG5cdFx0c2V0ID0gZ2V0O1xuXHRcdGdldCA9IGRzY3I7XG5cdFx0ZHNjciA9IG51bGw7XG5cdH0gZWxzZSB7XG5cdFx0b3B0aW9ucyA9IGFyZ3VtZW50c1szXTtcblx0fVxuXHRpZiAoZ2V0ID09IG51bGwpIHtcblx0XHRnZXQgPSB1bmRlZmluZWQ7XG5cdH0gZWxzZSBpZiAoIWlzQ2FsbGFibGUoZ2V0KSkge1xuXHRcdG9wdGlvbnMgPSBnZXQ7XG5cdFx0Z2V0ID0gc2V0ID0gdW5kZWZpbmVkO1xuXHR9IGVsc2UgaWYgKHNldCA9PSBudWxsKSB7XG5cdFx0c2V0ID0gdW5kZWZpbmVkO1xuXHR9IGVsc2UgaWYgKCFpc0NhbGxhYmxlKHNldCkpIHtcblx0XHRvcHRpb25zID0gc2V0O1xuXHRcdHNldCA9IHVuZGVmaW5lZDtcblx0fVxuXHRpZiAoZHNjciA9PSBudWxsKSB7XG5cdFx0YyA9IHRydWU7XG5cdFx0ZSA9IGZhbHNlO1xuXHR9IGVsc2Uge1xuXHRcdGMgPSBjb250YWlucy5jYWxsKGRzY3IsICdjJyk7XG5cdFx0ZSA9IGNvbnRhaW5zLmNhbGwoZHNjciwgJ2UnKTtcblx0fVxuXG5cdGRlc2MgPSB7IGdldDogZ2V0LCBzZXQ6IHNldCwgY29uZmlndXJhYmxlOiBjLCBlbnVtZXJhYmxlOiBlIH07XG5cdHJldHVybiAhb3B0aW9ucyA/IGRlc2MgOiBhc3NpZ24obm9ybWFsaXplT3B0cyhvcHRpb25zKSwgZGVzYyk7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG5tb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoJy4vaXMtaW1wbGVtZW50ZWQnKSgpXG5cdD8gT2JqZWN0LmFzc2lnblxuXHQ6IHJlcXVpcmUoJy4vc2hpbScpO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uICgpIHtcblx0dmFyIGFzc2lnbiA9IE9iamVjdC5hc3NpZ24sIG9iajtcblx0aWYgKHR5cGVvZiBhc3NpZ24gIT09ICdmdW5jdGlvbicpIHJldHVybiBmYWxzZTtcblx0b2JqID0geyBmb286ICdyYXonIH07XG5cdGFzc2lnbihvYmosIHsgYmFyOiAnZHdhJyB9LCB7IHRyenk6ICd0cnp5JyB9KTtcblx0cmV0dXJuIChvYmouZm9vICsgb2JqLmJhciArIG9iai50cnp5KSA9PT0gJ3JhemR3YXRyenknO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIGtleXMgID0gcmVxdWlyZSgnLi4va2V5cycpXG4gICwgdmFsdWUgPSByZXF1aXJlKCcuLi92YWxpZC12YWx1ZScpXG5cbiAgLCBtYXggPSBNYXRoLm1heDtcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoZGVzdCwgc3JjLyosIOKApnNyY24qLykge1xuXHR2YXIgZXJyb3IsIGksIGwgPSBtYXgoYXJndW1lbnRzLmxlbmd0aCwgMiksIGFzc2lnbjtcblx0ZGVzdCA9IE9iamVjdCh2YWx1ZShkZXN0KSk7XG5cdGFzc2lnbiA9IGZ1bmN0aW9uIChrZXkpIHtcblx0XHR0cnkgeyBkZXN0W2tleV0gPSBzcmNba2V5XTsgfSBjYXRjaCAoZSkge1xuXHRcdFx0aWYgKCFlcnJvcikgZXJyb3IgPSBlO1xuXHRcdH1cblx0fTtcblx0Zm9yIChpID0gMTsgaSA8IGw7ICsraSkge1xuXHRcdHNyYyA9IGFyZ3VtZW50c1tpXTtcblx0XHRrZXlzKHNyYykuZm9yRWFjaChhc3NpZ24pO1xuXHR9XG5cdGlmIChlcnJvciAhPT0gdW5kZWZpbmVkKSB0aHJvdyBlcnJvcjtcblx0cmV0dXJuIGRlc3Q7XG59O1xuIiwiLy8gRGVwcmVjYXRlZFxuXG4ndXNlIHN0cmljdCc7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKG9iaikgeyByZXR1cm4gdHlwZW9mIG9iaiA9PT0gJ2Z1bmN0aW9uJzsgfTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL2lzLWltcGxlbWVudGVkJykoKVxuXHQ/IE9iamVjdC5rZXlzXG5cdDogcmVxdWlyZSgnLi9zaGltJyk7XG4iLCIndXNlIHN0cmljdCc7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKCkge1xuXHR0cnkge1xuXHRcdE9iamVjdC5rZXlzKCdwcmltaXRpdmUnKTtcblx0XHRyZXR1cm4gdHJ1ZTtcblx0fSBjYXRjaCAoZSkgeyByZXR1cm4gZmFsc2U7IH1cbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBrZXlzID0gT2JqZWN0LmtleXM7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKG9iamVjdCkge1xuXHRyZXR1cm4ga2V5cyhvYmplY3QgPT0gbnVsbCA/IG9iamVjdCA6IE9iamVjdChvYmplY3QpKTtcbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBhc3NpZ24gPSByZXF1aXJlKCcuL2Fzc2lnbicpXG5cbiAgLCBmb3JFYWNoID0gQXJyYXkucHJvdG90eXBlLmZvckVhY2hcbiAgLCBjcmVhdGUgPSBPYmplY3QuY3JlYXRlLCBnZXRQcm90b3R5cGVPZiA9IE9iamVjdC5nZXRQcm90b3R5cGVPZlxuXG4gICwgcHJvY2VzcztcblxucHJvY2VzcyA9IGZ1bmN0aW9uIChzcmMsIG9iaikge1xuXHR2YXIgcHJvdG8gPSBnZXRQcm90b3R5cGVPZihzcmMpO1xuXHRyZXR1cm4gYXNzaWduKHByb3RvID8gcHJvY2Vzcyhwcm90bywgb2JqKSA6IG9iaiwgc3JjKTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKG9wdGlvbnMvKiwg4oCmb3B0aW9ucyovKSB7XG5cdHZhciByZXN1bHQgPSBjcmVhdGUobnVsbCk7XG5cdGZvckVhY2guY2FsbChhcmd1bWVudHMsIGZ1bmN0aW9uIChvcHRpb25zKSB7XG5cdFx0aWYgKG9wdGlvbnMgPT0gbnVsbCkgcmV0dXJuO1xuXHRcdHByb2Nlc3MoT2JqZWN0KG9wdGlvbnMpLCByZXN1bHQpO1xuXHR9KTtcblx0cmV0dXJuIHJlc3VsdDtcbn07XG4iLCIndXNlIHN0cmljdCc7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKHZhbHVlKSB7XG5cdGlmICh2YWx1ZSA9PSBudWxsKSB0aHJvdyBuZXcgVHlwZUVycm9yKFwiQ2Fubm90IHVzZSBudWxsIG9yIHVuZGVmaW5lZFwiKTtcblx0cmV0dXJuIHZhbHVlO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKCcuL2lzLWltcGxlbWVudGVkJykoKVxuXHQ/IFN0cmluZy5wcm90b3R5cGUuY29udGFpbnNcblx0OiByZXF1aXJlKCcuL3NoaW0nKTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIHN0ciA9ICdyYXpkd2F0cnp5JztcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoKSB7XG5cdGlmICh0eXBlb2Ygc3RyLmNvbnRhaW5zICE9PSAnZnVuY3Rpb24nKSByZXR1cm4gZmFsc2U7XG5cdHJldHVybiAoKHN0ci5jb250YWlucygnZHdhJykgPT09IHRydWUpICYmIChzdHIuY29udGFpbnMoJ2ZvbycpID09PSBmYWxzZSkpO1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIGluZGV4T2YgPSBTdHJpbmcucHJvdG90eXBlLmluZGV4T2Y7XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gKHNlYXJjaFN0cmluZy8qLCBwb3NpdGlvbiovKSB7XG5cdHJldHVybiBpbmRleE9mLmNhbGwodGhpcywgc2VhcmNoU3RyaW5nLCBhcmd1bWVudHNbMV0pID4gLTE7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgZCA9IHJlcXVpcmUoJ2QnKVxuXG4gICwgY3JlYXRlID0gT2JqZWN0LmNyZWF0ZSwgZGVmaW5lUHJvcGVydGllcyA9IE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzXG4gICwgZ2VuZXJhdGVOYW1lLCBTeW1ib2w7XG5cbmdlbmVyYXRlTmFtZSA9IChmdW5jdGlvbiAoKSB7XG5cdHZhciBjcmVhdGVkID0gY3JlYXRlKG51bGwpO1xuXHRyZXR1cm4gZnVuY3Rpb24gKGRlc2MpIHtcblx0XHR2YXIgcG9zdGZpeCA9IDA7XG5cdFx0d2hpbGUgKGNyZWF0ZWRbZGVzYyArIChwb3N0Zml4IHx8ICcnKV0pICsrcG9zdGZpeDtcblx0XHRkZXNjICs9IChwb3N0Zml4IHx8ICcnKTtcblx0XHRjcmVhdGVkW2Rlc2NdID0gdHJ1ZTtcblx0XHRyZXR1cm4gJ0BAJyArIGRlc2M7XG5cdH07XG59KCkpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IFN5bWJvbCA9IGZ1bmN0aW9uIChkZXNjcmlwdGlvbikge1xuXHR2YXIgc3ltYm9sO1xuXHRpZiAodGhpcyBpbnN0YW5jZW9mIFN5bWJvbCkge1xuXHRcdHRocm93IG5ldyBUeXBlRXJyb3IoJ1R5cGVFcnJvcjogU3ltYm9sIGlzIG5vdCBhIGNvbnN0cnVjdG9yJyk7XG5cdH1cblx0c3ltYm9sID0gY3JlYXRlKFN5bWJvbC5wcm90b3R5cGUpO1xuXHRkZXNjcmlwdGlvbiA9IChkZXNjcmlwdGlvbiA9PT0gdW5kZWZpbmVkID8gJycgOiBTdHJpbmcoZGVzY3JpcHRpb24pKTtcblx0cmV0dXJuIGRlZmluZVByb3BlcnRpZXMoc3ltYm9sLCB7XG5cdFx0X19kZXNjcmlwdGlvbl9fOiBkKCcnLCBkZXNjcmlwdGlvbiksXG5cdFx0X19uYW1lX186IGQoJycsIGdlbmVyYXRlTmFtZShkZXNjcmlwdGlvbikpXG5cdH0pO1xufTtcblxuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoU3ltYm9sLCB7XG5cdGNyZWF0ZTogZCgnJywgU3ltYm9sKCdjcmVhdGUnKSksXG5cdGhhc0luc3RhbmNlOiBkKCcnLCBTeW1ib2woJ2hhc0luc3RhbmNlJykpLFxuXHRpc0NvbmNhdFNwcmVhZGFibGU6IGQoJycsIFN5bWJvbCgnaXNDb25jYXRTcHJlYWRhYmxlJykpLFxuXHRpc1JlZ0V4cDogZCgnJywgU3ltYm9sKCdpc1JlZ0V4cCcpKSxcblx0aXRlcmF0b3I6IGQoJycsIFN5bWJvbCgnaXRlcmF0b3InKSksXG5cdHRvUHJpbWl0aXZlOiBkKCcnLCBTeW1ib2woJ3RvUHJpbWl0aXZlJykpLFxuXHR0b1N0cmluZ1RhZzogZCgnJywgU3ltYm9sKCd0b1N0cmluZ1RhZycpKSxcblx0dW5zY29wYWJsZXM6IGQoJycsIFN5bWJvbCgndW5zY29wYWJsZXMnKSlcbn0pO1xuXG5kZWZpbmVQcm9wZXJ0aWVzKFN5bWJvbC5wcm90b3R5cGUsIHtcblx0cHJvcGVyVG9TdHJpbmc6IGQoZnVuY3Rpb24gKCkge1xuXHRcdHJldHVybiAnU3ltYm9sICgnICsgdGhpcy5fX2Rlc2NyaXB0aW9uX18gKyAnKSc7XG5cdH0pLFxuXHR0b1N0cmluZzogZCgnJywgZnVuY3Rpb24gKCkgeyByZXR1cm4gdGhpcy5fX25hbWVfXzsgfSlcbn0pO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KFN5bWJvbC5wcm90b3R5cGUsIFN5bWJvbC50b1ByaW1pdGl2ZSwgZCgnJyxcblx0ZnVuY3Rpb24gKGhpbnQpIHtcblx0XHR0aHJvdyBuZXcgVHlwZUVycm9yKFwiQ29udmVyc2lvbiBvZiBzeW1ib2wgb2JqZWN0cyBpcyBub3QgYWxsb3dlZFwiKTtcblx0fSkpO1xuT2JqZWN0LmRlZmluZVByb3BlcnR5KFN5bWJvbC5wcm90b3R5cGUsIFN5bWJvbC50b1N0cmluZ1RhZywgZCgnYycsICdTeW1ib2wnKSk7XG4iLCJtb2R1bGUuZXhwb3J0cz17XG4gIFwidHlwZXNcIjoge1xuICAgIFwicm9vdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJyb290XCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZWNob1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInN0cmluZ1wiOiB7IFwiX2FyZ1wiOiAwLCBcInR5cGVcIjogXCJzdHJpbmdcIiB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwic3RyaW5nXCI6IHsgXCJfcmV0dmFsXCI6IFwic3RyaW5nXCIgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImxpc3RUYWJzXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHt9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjogeyBcIl9yZXR2YWxcIjogXCJ0YWJsaXN0XCIgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicHJvdG9jb2xEZXNjcmlwdGlvblwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7fSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHsgXCJfcmV0dmFsXCI6IFwianNvblwiIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJ0YWJMaXN0Q2hhbmdlZFwiOiB7fVxuICAgICAgfVxuICAgIH0sXG4gICAgXCJ0YWJsaXN0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwidGFibGlzdFwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcInNlbGVjdGVkXCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwidGFic1wiOiBcImFycmF5OnRhYlwiLFxuICAgICAgICBcInVybFwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcImNvbnNvbGVBY3RvclwiOiBcImNvbnNvbGVcIixcbiAgICAgICAgXCJpbnNwZWN0b3JBY3RvclwiOiBcImluc3BlY3RvclwiLFxuICAgICAgICBcInN0eWxlU2hlZXRzQWN0b3JcIjogXCJzdHlsZXNoZWV0c1wiLFxuICAgICAgICBcInN0eWxlRWRpdG9yQWN0b3JcIjogXCJzdHlsZWVkaXRvclwiLFxuICAgICAgICBcIm1lbW9yeUFjdG9yXCI6IFwibWVtb3J5XCIsXG4gICAgICAgIFwiZXZlbnRMb29wTGFnQWN0b3JcIjogXCJldmVudExvb3BMYWdcIixcbiAgICAgICAgXCJwcmVmZXJlbmNlQWN0b3JcIjogXCJwcmVmZXJlbmNlXCIsXG4gICAgICAgIFwiZGV2aWNlQWN0b3JcIjogXCJkZXZpY2VcIixcblxuICAgICAgICBcInByb2ZpbGVyQWN0b3JcIjogXCJwcm9maWxlclwiLFxuICAgICAgICBcImNocm9tZURlYnVnZ2VyXCI6IFwiY2hyb21lRGVidWdnZXJcIixcbiAgICAgICAgXCJ3ZWJhcHBzQWN0b3JcIjogXCJ3ZWJhcHBzXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwidGFiXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInRhYlwiLFxuICAgICAgXCJmaWVsZHNcIjoge1xuICAgICAgICBcInRpdGxlXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwidXJsXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwib3V0ZXJXaW5kb3dJRFwiOiBcIm51bWJlclwiLFxuICAgICAgICBcImluc3BlY3RvckFjdG9yXCI6IFwiaW5zcGVjdG9yXCIsXG4gICAgICAgIFwiY2FsbFdhdGNoZXJBY3RvclwiOiBcImNhbGwtd2F0Y2hlclwiLFxuICAgICAgICBcImNhbnZhc0FjdG9yXCI6IFwiY2FudmFzXCIsXG4gICAgICAgIFwid2ViZ2xBY3RvclwiOiBcIndlYmdsXCIsXG4gICAgICAgIFwid2ViYXVkaW9BY3RvclwiOiBcIndlYmF1ZGlvXCIsXG4gICAgICAgIFwic3RvcmFnZUFjdG9yXCI6IFwic3RvcmFnZVwiLFxuICAgICAgICBcImdjbGlBY3RvclwiOiBcImdjbGlcIixcbiAgICAgICAgXCJtZW1vcnlBY3RvclwiOiBcIm1lbW9yeVwiLFxuICAgICAgICBcImV2ZW50TG9vcExhZ1wiOiBcImV2ZW50TG9vcExhZ1wiLFxuICAgICAgICBcInN0eWxlU2hlZXRzQWN0b3JcIjogXCJzdHlsZXNoZWV0c1wiLFxuICAgICAgICBcInN0eWxlRWRpdG9yQWN0b3JcIjogXCJzdHlsZWVkaXRvclwiLFxuXG4gICAgICAgIFwiY29uc29sZUFjdG9yXCI6IFwiY29uc29sZVwiLFxuICAgICAgICBcInRyYWNlQWN0b3JcIjogXCJ0cmFjZVwiXG4gICAgICB9LFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJhdHRhY2hcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge30sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7IFwiX3JldHZhbFwiOiBcImpzb25cIiB9XG4gICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge1xuICAgICAgICBcInRhYk5hdmlnYXRlZFwiOiB7XG4gICAgICAgICAgIFwidHlwZU5hbWVcIjogXCJ0YWJOYXZpZ2F0ZWRcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImNvbnNvbGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiY29uc29sZVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImV2YWx1YXRlSlNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0ZXh0XCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJ1cmxcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImJpbmRPYmplY3RBY3RvclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwiZnJhbWVBY3RvclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwic2VsZWN0ZWROb2RlQWN0b3JcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6c3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZXZhbHVhdGVqc3Jlc3BvbnNlXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJldmFsdWF0ZWpzcmVzcG9uc2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJldmFsdWF0ZWpzcmVzcG9uc2VcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJyZXN1bHRcIjogXCJvYmplY3RcIixcbiAgICAgICAgXCJleGNlcHRpb25cIjogXCJvYmplY3RcIixcbiAgICAgICAgXCJleGNlcHRpb25NZXNzYWdlXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwiaW5wdXRcIjogXCJzdHJpbmdcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJvYmplY3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwib2JqZWN0XCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICAge1xuICAgICAgICAgICBcIm5hbWVcIjogXCJwcm9wZXJ0eVwiLFxuICAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgICBcIm5hbWVcIjoge1xuICAgICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgfSxcbiAgICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICAgIFwiZGVzY3JpcHRvclwiOiB7XG4gICAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgfVxuICAgICAgICAgfVxuICAgICAgXVxuICAgIH1cbiAgfVxufVxuIiwibW9kdWxlLmV4cG9ydHM9e1xuICBcInR5cGVzXCI6IHtcbiAgICBcImxvbmdzdHJhY3RvclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJsb25nc3RyYWN0b3JcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzdWJzdHJpbmdcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3Vic3RyaW5nXCIsXG4gICAgICAgICAgICBcInN0YXJ0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJlbmRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdWJzdHJpbmdcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInJlbGVhc2VcIixcbiAgICAgICAgICBcInJlbGVhc2VcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVsZWFzZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJzdHlsZXNoZWV0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInN0eWxlc2hlZXRcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ0b2dnbGVEaXNhYmxlZFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ0b2dnbGVEaXNhYmxlZFwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiZGlzYWJsZWRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUZXh0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFRleHRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRPcmlnaW5hbFNvdXJjZXNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0T3JpZ2luYWxTb3VyY2VzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJvcmlnaW5hbFNvdXJjZXNcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJudWxsYWJsZTphcnJheTpvcmlnaW5hbHNvdXJjZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0T3JpZ2luYWxMb2NhdGlvblwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRPcmlnaW5hbExvY2F0aW9uXCIsXG4gICAgICAgICAgICBcImxpbmVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVtYmVyXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImNvbHVtblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudW1iZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJvcmlnaW5hbGxvY2F0aW9ucmVzcG9uc2VcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInVwZGF0ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ1cGRhdGVcIixcbiAgICAgICAgICAgIFwidGV4dFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidHJhbnNpdGlvblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJwcm9wZXJ0eS1jaGFuZ2VcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInByb3BlcnR5Q2hhbmdlXCIsXG4gICAgICAgICAgXCJwcm9wZXJ0eVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBcInN0eWxlLWFwcGxpZWRcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInN0eWxlQXBwbGllZFwiXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuICAgIFwib3JpZ2luYWxzb3VyY2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwib3JpZ2luYWxzb3VyY2VcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUZXh0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFRleHRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJzdHlsZXNoZWV0c1wiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJzdHlsZXNoZWV0c1wiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFN0eWxlU2hlZXRzXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFN0eWxlU2hlZXRzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdHlsZVNoZWV0c1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFycmF5OnN0eWxlc2hlZXRcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImFkZFN0eWxlU2hlZXRcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYWRkU3R5bGVTaGVldFwiLFxuICAgICAgICAgICAgXCJ0ZXh0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwic3R5bGVTaGVldFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInN0eWxlc2hlZXRcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcIm9yaWdpbmFsbG9jYXRpb25yZXNwb25zZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcIm9yaWdpbmFsbG9jYXRpb25yZXNwb25zZVwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcInNvdXJjZVwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcImxpbmVcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJjb2x1bW5cIjogXCJudW1iZXJcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJkb21ub2RlXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImRvbW5vZGVcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXROb2RlVmFsdWVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0Tm9kZVZhbHVlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImxvbmdzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldE5vZGVWYWx1ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXROb2RlVmFsdWVcIixcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldEltYWdlRGF0YVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRJbWFnZURhdGFcIixcbiAgICAgICAgICAgIFwibWF4RGltXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOm51bWJlclwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImltYWdlRGF0YVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibW9kaWZ5QXR0cmlidXRlc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJtb2RpZnlBdHRyaWJ1dGVzXCIsXG4gICAgICAgICAgICBcIm1vZGlmaWNhdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXJyYXk6anNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJhcHBsaWVkc3R5bGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJhcHBsaWVkc3R5bGVcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJydWxlXCI6IFwiZG9tc3R5bGVydWxlI2FjdG9yaWRcIixcbiAgICAgICAgXCJpbmhlcml0ZWRcIjogXCJudWxsYWJsZTpkb21ub2RlI2FjdG9yaWRcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJtYXRjaGVkc2VsZWN0b3JcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJtYXRjaGVkc2VsZWN0b3JcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJydWxlXCI6IFwiZG9tc3R5bGVydWxlI2FjdG9yaWRcIixcbiAgICAgICAgXCJzZWxlY3RvclwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcInZhbHVlXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwic3RhdHVzXCI6IFwibnVtYmVyXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwibWF0Y2hlZHNlbGVjdG9ycmVzcG9uc2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJtYXRjaGVkc2VsZWN0b3JyZXNwb25zZVwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcInJ1bGVzXCI6IFwiYXJyYXk6ZG9tc3R5bGVydWxlXCIsXG4gICAgICAgIFwic2hlZXRzXCI6IFwiYXJyYXk6c3R5bGVzaGVldFwiLFxuICAgICAgICBcIm1hdGNoZWRcIjogXCJhcnJheTptYXRjaGVkc2VsZWN0b3JcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJhcHBsaWVkU3R5bGVzUmV0dXJuXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiYXBwbGllZFN0eWxlc1JldHVyblwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcImVudHJpZXNcIjogXCJhcnJheTphcHBsaWVkc3R5bGVcIixcbiAgICAgICAgXCJydWxlc1wiOiBcImFycmF5OmRvbXN0eWxlcnVsZVwiLFxuICAgICAgICBcInNoZWV0c1wiOiBcImFycmF5OnN0eWxlc2hlZXRcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJwYWdlc3R5bGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwicGFnZXN0eWxlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0Q29tcHV0ZWRcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0Q29tcHV0ZWRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm1hcmtNYXRjaGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwib25seU1hdGNoZWRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJmaWx0ZXJcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJjb21wdXRlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldE1hdGNoZWRTZWxlY3RvcnNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0TWF0Y2hlZFNlbGVjdG9yc1wiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicHJvcGVydHlcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImZpbHRlclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJtYXRjaGVkc2VsZWN0b3JyZXNwb25zZVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0QXBwbGllZFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRBcHBsaWVkXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJpbmhlcml0ZWRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJtYXRjaGVkU2VsZWN0b3JzXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwiZmlsdGVyXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFwcGxpZWRTdHlsZXNSZXR1cm5cIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldExheW91dFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRMYXlvdXRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImF1dG9NYXJnaW5zXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJkb21zdHlsZXJ1bGVcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZG9tc3R5bGVydWxlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibW9kaWZ5UHJvcGVydGllc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJtb2RpZnlQcm9wZXJ0aWVzXCIsXG4gICAgICAgICAgICBcIm1vZGlmaWNhdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXJyYXk6anNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwicnVsZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRvbXN0eWxlcnVsZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiaGlnaGxpZ2h0ZXJcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiaGlnaGxpZ2h0ZXJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzaG93Qm94TW9kZWxcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2hvd0JveE1vZGVsXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJyZWdpb25cIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImhpZGVCb3hNb2RlbFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJoaWRlQm94TW9kZWxcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicGlja1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwaWNrXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNhbmNlbFBpY2tcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiY2FuY2VsUGlja1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJpbWFnZURhdGFcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJpbWFnZURhdGFcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJkYXRhXCI6IFwibnVsbGFibGU6bG9uZ3N0cmluZ1wiLFxuICAgICAgICBcInNpemVcIjogXCJqc29uXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZGlzY29ubmVjdGVkTm9kZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJub2RlXCI6IFwiZG9tbm9kZVwiLFxuICAgICAgICBcIm5ld1BhcmVudHNcIjogXCJhcnJheTpkb21ub2RlXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZGlzY29ubmVjdGVkTm9kZUFycmF5XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZGlzY29ubmVjdGVkTm9kZUFycmF5XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwibm9kZXNcIjogXCJhcnJheTpkb21ub2RlXCIsXG4gICAgICAgIFwibmV3UGFyZW50c1wiOiBcImFycmF5OmRvbW5vZGVcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJkb21tdXRhdGlvblwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImRvbW11dGF0aW9uXCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7fVxuICAgIH0sXG4gICAgXCJkb21ub2RlbGlzdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJkb21ub2RlbGlzdFwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIml0ZW1cIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaXRlbVwiLFxuICAgICAgICAgICAgXCJpdGVtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIml0ZW1zXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcIml0ZW1zXCIsXG4gICAgICAgICAgICBcInN0YXJ0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOm51bWJlclwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJlbmRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6bnVtYmVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZGlzY29ubmVjdGVkTm9kZUFycmF5XCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZWxlYXNlXCIsXG4gICAgICAgICAgXCJyZWxlYXNlXCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbGVhc2VcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiZG9tdHJhdmVyc2FsYXJyYXlcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJkb210cmF2ZXJzYWxhcnJheVwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcIm5vZGVzXCI6IFwiYXJyYXk6ZG9tbm9kZVwiXG4gICAgICB9XG4gICAgfSxcbiAgICBcImRvbXdhbGtlclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJkb213YWxrZXJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZWxlYXNlXCIsXG4gICAgICAgICAgXCJyZWxlYXNlXCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbGVhc2VcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicGlja1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwaWNrXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZGlzY29ubmVjdGVkTm9kZVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiY2FuY2VsUGlja1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJjYW5jZWxQaWNrXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImhpZ2hsaWdodFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJoaWdobGlnaHRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImRvY3VtZW50XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImRvY3VtZW50XCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6ZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImRvY3VtZW50RWxlbWVudFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJkb2N1bWVudEVsZW1lbnRcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicGFyZW50c1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwYXJlbnRzXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzYW1lRG9jdW1lbnRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJub2Rlc1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFycmF5OmRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInJldGFpbk5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmV0YWluTm9kZVwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwidW5yZXRhaW5Ob2RlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInVucmV0YWluTm9kZVwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicmVsZWFzZU5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVsZWFzZU5vZGVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImZvcmNlXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJjaGlsZHJlblwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJjaGlsZHJlblwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwibWF4Tm9kZXNcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImNlbnRlclwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInN0YXJ0XCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwid2hhdFRvU2hvd1wiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJkb210cmF2ZXJzYWxhcnJheVwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2libGluZ3NcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2libGluZ3NcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm1heE5vZGVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJjZW50ZXJcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzdGFydFwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIndoYXRUb1Nob3dcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZG9tdHJhdmVyc2FsYXJyYXlcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIm5leHRTaWJsaW5nXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcIm5leHRTaWJsaW5nXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJ3aGF0VG9TaG93XCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInByZXZpb3VzU2libGluZ1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJwcmV2aW91c1NpYmxpbmdcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIndoYXRUb1Nob3dcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwibnVsbGFibGU6ZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicXVlcnlTZWxlY3RvclwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJxdWVyeVNlbGVjdG9yXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzZWxlY3RvclwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJkaXNjb25uZWN0ZWROb2RlXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJxdWVyeVNlbGVjdG9yQWxsXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInF1ZXJ5U2VsZWN0b3JBbGxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInNlbGVjdG9yXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwibGlzdFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImRvbW5vZGVsaXN0XCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdWdnZXN0aW9uc0ZvclF1ZXJ5XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFN1Z2dlc3Rpb25zRm9yUXVlcnlcIixcbiAgICAgICAgICAgIFwicXVlcnlcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImNvbXBsZXRpbmdcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInNlbGVjdG9yU3RhdGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJsaXN0XCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiYXJyYXk6YXJyYXk6c3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJhZGRQc2V1ZG9DbGFzc0xvY2tcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYWRkUHNldWRvQ2xhc3NMb2NrXCIsXG4gICAgICAgICAgICBcIm5vZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZG9tbm9kZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJwc2V1ZG9DbGFzc1wiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicGFyZW50c1wiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiaGlkZU5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaGlkZU5vZGVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInVuaGlkZU5vZGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwidW5oaWRlTm9kZVwiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicmVtb3ZlUHNldWRvQ2xhc3NMb2NrXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbW92ZVBzZXVkb0NsYXNzTG9ja1wiLFxuICAgICAgICAgICAgXCJub2RlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImRvbW5vZGVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicHNldWRvQ2xhc3NcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInBhcmVudHNcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNsZWFyUHNldWRvQ2xhc3NMb2Nrc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJjbGVhclBzZXVkb0NsYXNzTG9ja3NcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudWxsYWJsZTpkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImlubmVySFRNTFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJpbm5lckhUTUxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImxvbmdzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcIm91dGVySFRNTFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJvdXRlckhUTUxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImxvbmdzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldE91dGVySFRNTFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXRPdXRlckhUTUxcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZW1vdmVOb2RlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInJlbW92ZU5vZGVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJuZXh0U2libGluZ1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmRvbW5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImluc2VydEJlZm9yZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJpbnNlcnRCZWZvcmVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInBhcmVudFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInNpYmxpbmdcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6ZG9tbm9kZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRNdXRhdGlvbnNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0TXV0YXRpb25zXCIsXG4gICAgICAgICAgICBcImNsZWFudXBcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJtdXRhdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJhcnJheTpkb21tdXRhdGlvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiaXNJbkRPTVRyZWVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaXNJbkRPTVRyZWVcIixcbiAgICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJkb21ub2RlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJhdHRhY2hlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldE5vZGVBY3RvckZyb21PYmplY3RBY3RvclwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXROb2RlQWN0b3JGcm9tT2JqZWN0QWN0b3JcIixcbiAgICAgICAgICAgIFwib2JqZWN0QWN0b3JJRFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIm5vZGVGcm9udFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJuZXctbXV0YXRpb25zXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJuZXdNdXRhdGlvbnNcIlxuICAgICAgICB9LFxuICAgICAgICBcInBpY2tlci1ub2RlLXBpY2tlZFwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwicGlja2VyTm9kZVBpY2tlZFwiLFxuICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJwaWNrZXItbm9kZS1ob3ZlcmVkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJwaWNrZXJOb2RlSG92ZXJlZFwiLFxuICAgICAgICAgIFwibm9kZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImRpc2Nvbm5lY3RlZE5vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJoaWdobGlnaHRlci1yZWFkeVwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwiaGlnaGxpZ2h0ZXItcmVhZHlcIlxuICAgICAgICB9LFxuICAgICAgICBcImhpZ2hsaWdodGVyLWhpZGVcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcImhpZ2hsaWdodGVyLWhpZGVcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImluc3BlY3RvclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJpbnNwZWN0b3JcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRXYWxrZXJcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0V2Fsa2VyXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ3YWxrZXJcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJkb213YWxrZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFBhZ2VTdHlsZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRQYWdlU3R5bGVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInBhZ2VTdHlsZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInBhZ2VzdHlsZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0SGlnaGxpZ2h0ZXJcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0SGlnaGxpZ2h0ZXJcIixcbiAgICAgICAgICAgIFwiYXV0b2hpZGVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiaGlnaGxpZ3RlclwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImhpZ2hsaWdodGVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRJbWFnZURhdGFGcm9tVVJMXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEltYWdlRGF0YUZyb21VUkxcIixcbiAgICAgICAgICAgIFwidXJsXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJtYXhEaW1cIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6bnVtYmVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiaW1hZ2VEYXRhXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJjYWxsLXN0YWNrLWl0ZW1cIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJjYWxsLXN0YWNrLWl0ZW1cIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJuYW1lXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwiZmlsZVwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcImxpbmVcIjogXCJudW1iZXJcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjYWxsLWRldGFpbHNcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJjYWxsLWRldGFpbHNcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJ0eXBlXCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwibmFtZVwiOiBcInN0cmluZ1wiLFxuICAgICAgICBcInN0YWNrXCI6IFwiYXJyYXk6Y2FsbC1zdGFjay1pdGVtXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZnVuY3Rpb24tY2FsbFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJmdW5jdGlvbi1jYWxsXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0RGV0YWlsc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXREZXRhaWxzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJpbmZvXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiY2FsbC1kZXRhaWxzXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJjYWxsLXdhdGNoZXJcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiY2FsbC13YXRjaGVyXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2V0dXBcIixcbiAgICAgICAgICBcIm9uZXdheVwiOiB0cnVlLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXR1cFwiLFxuICAgICAgICAgICAgXCJ0cmFjZWRHbG9iYWxzXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJ0cmFjZWRGdW5jdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6YXJyYXk6c3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInN0YXJ0UmVjb3JkaW5nXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicGVyZm9ybVJlbG9hZFwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImZpbmFsaXplXCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZmluYWxpemVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiaXNSZWNvcmRpbmdcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiaXNSZWNvcmRpbmdcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJyZXN1bWVSZWNvcmRpbmdcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVzdW1lUmVjb3JkaW5nXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInBhdXNlUmVjb3JkaW5nXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInBhdXNlUmVjb3JkaW5nXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJjYWxsc1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImFycmF5OmZ1bmN0aW9uLWNhbGxcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImVyYXNlUmVjb3JkaW5nXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImVyYXNlUmVjb3JkaW5nXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcInNuYXBzaG90LWltYWdlXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic25hcHNob3QtaW1hZ2VcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJpbmRleFwiOiBcIm51bWJlclwiLFxuICAgICAgICBcIndpZHRoXCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwiaGVpZ2h0XCI6IFwibnVtYmVyXCIsXG4gICAgICAgIFwiZmxpcHBlZFwiOiBcImJvb2xlYW5cIixcbiAgICAgICAgXCJwaXhlbHNcIjogXCJ1aW50MzItYXJyYXlcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJzbmFwc2hvdC1vdmVydmlld1wiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInNuYXBzaG90LW92ZXJ2aWV3XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwiY2FsbHNcIjogXCJhcnJheTpmdW5jdGlvbi1jYWxsXCIsXG4gICAgICAgIFwidGh1bWJuYWlsc1wiOiBcImFycmF5OnNuYXBzaG90LWltYWdlXCIsXG4gICAgICAgIFwic2NyZWVuc2hvdFwiOiBcInNuYXBzaG90LWltYWdlXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwiZnJhbWUtc25hcHNob3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZnJhbWUtc25hcHNob3RcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRPdmVydmlld1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRPdmVydmlld1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwib3ZlcnZpZXdcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzbmFwc2hvdC1vdmVydmlld1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2VuZXJhdGVTY3JlZW5zaG90Rm9yXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdlbmVyYXRlU2NyZWVuc2hvdEZvclwiLFxuICAgICAgICAgICAgXCJjYWxsXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImZ1bmN0aW9uLWNhbGxcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInNjcmVlbnNob3RcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzbmFwc2hvdC1pbWFnZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiY2FudmFzXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImNhbnZhc1wiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldHVwXCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2V0dXBcIixcbiAgICAgICAgICAgIFwicmVsb2FkXCI6IHtcbiAgICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZmluYWxpemVcIixcbiAgICAgICAgICBcIm9uZXdheVwiOiB0cnVlLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJmaW5hbGl6ZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJpc0luaXRpYWxpemVkXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImlzSW5pdGlhbGl6ZWRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcImluaXRpYWxpemVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwicmVjb3JkQW5pbWF0aW9uRnJhbWVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwicmVjb3JkQW5pbWF0aW9uRnJhbWVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInNuYXBzaG90XCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiZnJhbWUtc25hcHNob3RcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcImdsLXNoYWRlclwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJnbC1zaGFkZXJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUZXh0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFRleHRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNvbXBpbGVcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiY29tcGlsZVwiLFxuICAgICAgICAgICAgXCJ0ZXh0XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiZXJyb3JcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJudWxsYWJsZTpqc29uXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJnbC1wcm9ncmFtXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImdsLXByb2dyYW1cIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRWZXJ0ZXhTaGFkZXJcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0VmVydGV4U2hhZGVyXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzaGFkZXJcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJnbC1zaGFkZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldEZyYWdtZW50U2hhZGVyXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEZyYWdtZW50U2hhZGVyXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzaGFkZXJcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJnbC1zaGFkZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImhpZ2hsaWdodFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImhpZ2hsaWdodFwiLFxuICAgICAgICAgICAgXCJ0aW50XCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcImFycmF5Om51bWJlclwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ1bmhpZ2hsaWdodFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInVuaGlnaGxpZ2h0XCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImJsYWNrYm94XCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYmxhY2tib3hcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwidW5ibGFja2JveFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInVuYmxhY2tib3hcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwid2ViZ2xcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwid2ViZ2xcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzZXR1cFwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNldHVwXCIsXG4gICAgICAgICAgICBcInJlbG9hZFwiOiB7XG4gICAgICAgICAgICAgIFwiX29wdGlvblwiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImZpbmFsaXplXCIsXG4gICAgICAgICAgXCJvbmV3YXlcIjogdHJ1ZSxcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZmluYWxpemVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0UHJvZ3JhbXNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0UHJvZ3JhbXNcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInByb2dyYW1zXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwiYXJyYXk6Z2wtcHJvZ3JhbVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge1xuICAgICAgICBcInByb2dyYW0tbGlua2VkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJwcm9ncmFtTGlua2VkXCIsXG4gICAgICAgICAgXCJwcm9ncmFtXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2wtcHJvZ3JhbVwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImF1ZGlvbm9kZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJhdWRpb25vZGVcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRUeXBlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFR5cGVcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImlzU291cmNlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImlzU291cmNlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzZXRQYXJhbVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXRQYXJhbVwiLFxuICAgICAgICAgICAgXCJwYXJhbVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6cHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJlcnJvclwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFBhcmFtXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFBhcmFtXCIsXG4gICAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwidGV4dFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bGxhYmxlOnByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0UGFyYW1GbGFnc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRQYXJhbUZsYWdzXCIsXG4gICAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiZmxhZ3NcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJudWxsYWJsZTpwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFBhcmFtc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRQYXJhbXNcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInBhcmFtc1wiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcIndlYmF1ZGlvXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcIndlYmF1ZGlvXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2V0dXBcIixcbiAgICAgICAgICBcIm9uZXdheVwiOiB0cnVlLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJzZXR1cFwiLFxuICAgICAgICAgICAgXCJyZWxvYWRcIjoge1xuICAgICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYm9vbGVhblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJmaW5hbGl6ZVwiLFxuICAgICAgICAgIFwib25ld2F5XCI6IHRydWUsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImZpbmFsaXplXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJzdGFydC1jb250ZXh0XCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJzdGFydENvbnRleHRcIlxuICAgICAgICB9LFxuICAgICAgICBcImNvbm5lY3Qtbm9kZVwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwiY29ubmVjdE5vZGVcIixcbiAgICAgICAgICBcInNvdXJjZVwiOiB7XG4gICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImF1ZGlvbm9kZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcImRlc3RcIjoge1xuICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJhdWRpb25vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJkaXNjb25uZWN0LW5vZGVcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcImRpc2Nvbm5lY3ROb2RlXCIsXG4gICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJhdWRpb25vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJjb25uZWN0LXBhcmFtXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJjb25uZWN0UGFyYW1cIixcbiAgICAgICAgICBcInNvdXJjZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImF1ZGlvbm9kZVwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIFwiY2hhbmdlLXBhcmFtXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJjaGFuZ2VQYXJhbVwiLFxuICAgICAgICAgIFwic291cmNlXCI6IHtcbiAgICAgICAgICAgIFwiX29wdGlvblwiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXVkaW9ub2RlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicGFyYW1cIjoge1xuICAgICAgICAgICAgXCJfb3B0aW9uXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICBcIl9vcHRpb25cIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBcImNyZWF0ZS1ub2RlXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJjcmVhdGVOb2RlXCIsXG4gICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJhdWRpb25vZGVcIlxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG4gICAgXCJvbGQtc3R5bGVzaGVldFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJvbGQtc3R5bGVzaGVldFwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInRvZ2dsZURpc2FibGVkXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInRvZ2dsZURpc2FibGVkXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJkaXNhYmxlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImJvb2xlYW5cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImZldGNoU291cmNlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImZldGNoU291cmNlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInVwZGF0ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ1cGRhdGVcIixcbiAgICAgICAgICAgIFwidGV4dFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidHJhbnNpdGlvblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJwcm9wZXJ0eS1jaGFuZ2VcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInByb3BlcnR5Q2hhbmdlXCIsXG4gICAgICAgICAgXCJwcm9wZXJ0eVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBcInNvdXJjZS1sb2FkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJzb3VyY2VMb2FkXCIsXG4gICAgICAgICAgXCJzb3VyY2VcIjoge1xuICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJzdHlsZS1hcHBsaWVkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJzdHlsZUFwcGxpZWRcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcInN0eWxlZWRpdG9yXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInN0eWxlZWRpdG9yXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibmV3RG9jdW1lbnRcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwibmV3RG9jdW1lbnRcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibmV3U3R5bGVTaGVldFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJuZXdTdHlsZVNoZWV0XCIsXG4gICAgICAgICAgICBcInRleHRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdHlsZVNoZWV0XCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwib2xkLXN0eWxlc2hlZXRcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHtcbiAgICAgICAgXCJkb2N1bWVudC1sb2FkXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJkb2N1bWVudExvYWRcIixcbiAgICAgICAgICBcInN0eWxlU2hlZXRzXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiYXJyYXk6b2xkLXN0eWxlc2hlZXRcIlxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjb29raWVvYmplY3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJjb29raWVvYmplY3RcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJuYW1lXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwidmFsdWVcIjogXCJsb25nc3RyaW5nXCIsXG4gICAgICAgIFwicGF0aFwiOiBcIm51bGxhYmxlOnN0cmluZ1wiLFxuICAgICAgICBcImhvc3RcIjogXCJzdHJpbmdcIixcbiAgICAgICAgXCJpc0RvbWFpblwiOiBcImJvb2xlYW5cIixcbiAgICAgICAgXCJpc1NlY3VyZVwiOiBcImJvb2xlYW5cIixcbiAgICAgICAgXCJpc0h0dHBPbmx5XCI6IFwiYm9vbGVhblwiLFxuICAgICAgICBcImNyZWF0aW9uVGltZVwiOiBcIm51bWJlclwiLFxuICAgICAgICBcImxhc3RBY2Nlc3NlZFwiOiBcIm51bWJlclwiLFxuICAgICAgICBcImV4cGlyZXNcIjogXCJudW1iZXJcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjb29raWVzdG9yZW9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImNvb2tpZXN0b3Jlb2JqZWN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwidG90YWxcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJvZmZzZXRcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJkYXRhXCI6IFwiYXJyYXk6bnVsbGFibGU6Y29va2llb2JqZWN0XCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmFnZW9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcInN0b3JhZ2VvYmplY3RcIixcbiAgICAgIFwic3BlY2lhbGl6YXRpb25zXCI6IHtcbiAgICAgICAgXCJuYW1lXCI6IFwic3RyaW5nXCIsXG4gICAgICAgIFwidmFsdWVcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmFnZXN0b3Jlb2JqZWN0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic3RvcmFnZXN0b3Jlb2JqZWN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwidG90YWxcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJvZmZzZXRcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJkYXRhXCI6IFwiYXJyYXk6bnVsbGFibGU6c3RvcmFnZW9iamVjdFwiXG4gICAgICB9XG4gICAgfSxcbiAgICBcImlkYm9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImlkYm9iamVjdFwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcIm5hbWVcIjogXCJudWxsYWJsZTpzdHJpbmdcIixcbiAgICAgICAgXCJkYlwiOiBcIm51bGxhYmxlOnN0cmluZ1wiLFxuICAgICAgICBcIm9iamVjdFN0b3JlXCI6IFwibnVsbGFibGU6c3RyaW5nXCIsXG4gICAgICAgIFwib3JpZ2luXCI6IFwibnVsbGFibGU6c3RyaW5nXCIsXG4gICAgICAgIFwidmVyc2lvblwiOiBcIm51bGxhYmxlOm51bWJlclwiLFxuICAgICAgICBcIm9iamVjdFN0b3Jlc1wiOiBcIm51bGxhYmxlOm51bWJlclwiLFxuICAgICAgICBcImtleVBhdGhcIjogXCJudWxsYWJsZTpzdHJpbmdcIixcbiAgICAgICAgXCJhdXRvSW5jcmVtZW50XCI6IFwibnVsbGFibGU6Ym9vbGVhblwiLFxuICAgICAgICBcImluZGV4ZXNcIjogXCJudWxsYWJsZTpzdHJpbmdcIixcbiAgICAgICAgXCJ2YWx1ZVwiOiBcIm51bGxhYmxlOmxvbmdzdHJpbmdcIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJpZGJzdG9yZW9iamVjdFwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiZGljdFwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImlkYnN0b3Jlb2JqZWN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwidG90YWxcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJvZmZzZXRcIjogXCJudW1iZXJcIixcbiAgICAgICAgXCJkYXRhXCI6IFwiYXJyYXk6bnVsbGFibGU6aWRib2JqZWN0XCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmVVcGRhdGVPYmplY3RcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImRpY3RcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJzdG9yZVVwZGF0ZU9iamVjdFwiLFxuICAgICAgXCJzcGVjaWFsaXphdGlvbnNcIjoge1xuICAgICAgICBcImNoYW5nZWRcIjogXCJudWxsYWJsZTpqc29uXCIsXG4gICAgICAgIFwiZGVsZXRlZFwiOiBcIm51bGxhYmxlOmpzb25cIixcbiAgICAgICAgXCJhZGRlZFwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgfVxuICAgIH0sXG4gICAgXCJjb29raWVzXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImNvb2tpZXNcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0U3RvcmVPYmplY3RzXCIsXG4gICAgICAgICAgICBcImhvc3RcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm5hbWVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJvcHRpb25zXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDIsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJjb29raWVzdG9yZW9iamVjdFwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwibG9jYWxTdG9yYWdlXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImxvY2FsU3RvcmFnZVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldFN0b3JlT2JqZWN0c1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICAgIFwiaG9zdFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwibmFtZXNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6YXJyYXk6c3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm9wdGlvbnNcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMixcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwibnVsbGFibGU6anNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInN0b3JhZ2VzdG9yZW9iamVjdFwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwic2Vzc2lvblN0b3JhZ2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic2Vzc2lvblN0b3JhZ2VcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0U3RvcmVPYmplY3RzXCIsXG4gICAgICAgICAgICBcImhvc3RcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm5hbWVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJvcHRpb25zXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDIsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdG9yYWdlc3RvcmVvYmplY3RcIlxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfSxcbiAgICBcImluZGV4ZWREQlwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJpbmRleGVkREJcIixcbiAgICAgIFwibWV0aG9kc1wiOiBbXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRTdG9yZU9iamVjdHNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0U3RvcmVPYmplY3RzXCIsXG4gICAgICAgICAgICBcImhvc3RcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcIm5hbWVzXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmFycmF5OnN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJvcHRpb25zXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDIsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bGxhYmxlOmpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJpZGJzdG9yZW9iamVjdFwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwic3RvcmVsaXN0XCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJkaWN0XCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwic3RvcmVsaXN0XCIsXG4gICAgICBcInNwZWNpYWxpemF0aW9uc1wiOiB7XG4gICAgICAgIFwiY29va2llc1wiOiBcImNvb2tpZXNcIixcbiAgICAgICAgXCJsb2NhbFN0b3JhZ2VcIjogXCJsb2NhbFN0b3JhZ2VcIixcbiAgICAgICAgXCJzZXNzaW9uU3RvcmFnZVwiOiBcInNlc3Npb25TdG9yYWdlXCIsXG4gICAgICAgIFwiaW5kZXhlZERCXCI6IFwiaW5kZXhlZERCXCJcbiAgICAgIH1cbiAgICB9LFxuICAgIFwic3RvcmFnZVwiOiB7XG4gICAgICBcImNhdGVnb3J5XCI6IFwiYWN0b3JcIixcbiAgICAgIFwidHlwZU5hbWVcIjogXCJzdG9yYWdlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibGlzdFN0b3Jlc1wiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJsaXN0U3RvcmVzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwic3RvcmVsaXN0XCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7XG4gICAgICAgIFwic3RvcmVzLXVwZGF0ZVwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwic3RvcmVzVXBkYXRlXCIsXG4gICAgICAgICAgXCJkYXRhXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RvcmVVcGRhdGVPYmplY3RcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJzdG9yZXMtY2xlYXJlZFwiOiB7XG4gICAgICAgICAgXCJ0eXBlXCI6IFwic3RvcmVzQ2xlYXJlZFwiLFxuICAgICAgICAgIFwiZGF0YVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImpzb25cIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgXCJzdG9yZXMtcmVsb2FkZWRcIjoge1xuICAgICAgICAgIFwidHlwZVwiOiBcInN0b3Jlc1JlbGFvZGVkXCIsXG4gICAgICAgICAgXCJkYXRhXCI6IHtcbiAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgXCJ0eXBlXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcImdjbGlcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZ2NsaVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNwZWNzXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNwZWNzXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZXhlY3V0ZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJleGVjdXRlXCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInN0YXRlXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0YXRlXCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJzdGFydFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudW1iZXJcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicmFua1wiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAyLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJudW1iZXJcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ0eXBlcGFyc2VcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwidHlwZXBhcnNlXCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJwYXJhbVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJ0eXBlaW5jcmVtZW50XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInR5cGVpbmNyZW1lbnRcIixcbiAgICAgICAgICAgIFwidHlwZWRcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInBhcmFtXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwidHlwZWRlY3JlbWVudFwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJ0eXBlZGVjcmVtZW50XCIsXG4gICAgICAgICAgICBcInR5cGVkXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInN0cmluZ1wiXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgXCJwYXJhbVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJzdHJpbmdcIlxuICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNlbGVjdGlvbmluZm9cIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2VsZWN0aW9uaW5mb1wiLFxuICAgICAgICAgICAgXCJ0eXBlZFwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwicGFyYW1cIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcImFjdGlvblwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAxLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJzdHJpbmdcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJqc29uXCJcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJtZW1vcnlcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwibWVtb3J5XCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwibWVhc3VyZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJtZWFzdXJlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge31cbiAgICB9LFxuICAgIFwiZXZlbnRMb29wTGFnXCI6IHtcbiAgICAgIFwiY2F0ZWdvcnlcIjogXCJhY3RvclwiLFxuICAgICAgXCJ0eXBlTmFtZVwiOiBcImV2ZW50TG9vcExhZ1wiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInN0YXJ0XCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0YXJ0XCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJzdWNjZXNzXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwibnVtYmVyXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzdG9wXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInN0b3BcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7fVxuICAgICAgICB9XG4gICAgICBdLFxuICAgICAgXCJldmVudHNcIjoge1xuICAgICAgICBcImV2ZW50LWxvb3AtbGFnXCI6IHtcbiAgICAgICAgICBcInR5cGVcIjogXCJldmVudC1sb29wLWxhZ1wiLFxuICAgICAgICAgIFwidGltZVwiOiB7XG4gICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgIFwidHlwZVwiOiBcIm51bWJlclwiXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSxcbiAgICBcInByZWZlcmVuY2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwicHJlZmVyZW5jZVwiLFxuICAgICAgXCJtZXRob2RzXCI6IFtcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImdldEJvb2xQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEJvb2xQcmVmXCIsXG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJib29sZWFuXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRDaGFyUHJlZlwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRDaGFyUHJlZlwiLFxuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwic3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJnZXRJbnRQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldEludFByZWZcIixcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcIm51bWJlclwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0QWxsUHJlZnNcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0QWxsUHJlZnNcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwic2V0Qm9vbFByZWZcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwic2V0Qm9vbFByZWZcIixcbiAgICAgICAgICAgIFwibmFtZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcInNldENoYXJQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNldENoYXJQcmVmXCIsXG4gICAgICAgICAgICBcIm5hbWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMCxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDEsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzZXRJbnRQcmVmXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNldEludFByZWZcIixcbiAgICAgICAgICAgIFwibmFtZVwiOiB7XG4gICAgICAgICAgICAgIFwiX2FyZ1wiOiAwLFxuICAgICAgICAgICAgICBcInR5cGVcIjogXCJwcmltaXRpdmVcIlxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9hcmdcIjogMSxcbiAgICAgICAgICAgICAgXCJ0eXBlXCI6IFwicHJpbWl0aXZlXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge31cbiAgICAgICAgfSxcbiAgICAgICAge1xuICAgICAgICAgIFwibmFtZVwiOiBcImNsZWFyVXNlclByZWZcIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiY2xlYXJVc2VyUHJlZlwiLFxuICAgICAgICAgICAgXCJuYW1lXCI6IHtcbiAgICAgICAgICAgICAgXCJfYXJnXCI6IDAsXG4gICAgICAgICAgICAgIFwidHlwZVwiOiBcInByaW1pdGl2ZVwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHt9XG4gICAgICAgIH1cbiAgICAgIF0sXG4gICAgICBcImV2ZW50c1wiOiB7fVxuICAgIH0sXG4gICAgXCJkZXZpY2VcIjoge1xuICAgICAgXCJjYXRlZ29yeVwiOiBcImFjdG9yXCIsXG4gICAgICBcInR5cGVOYW1lXCI6IFwiZGV2aWNlXCIsXG4gICAgICBcIm1ldGhvZHNcIjogW1xuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0RGVzY3JpcHRpb25cIixcbiAgICAgICAgICBcInJlcXVlc3RcIjoge1xuICAgICAgICAgICAgXCJ0eXBlXCI6IFwiZ2V0RGVzY3JpcHRpb25cIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwianNvblwiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0V2FsbHBhcGVyXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcImdldFdhbGxwYXBlclwiXG4gICAgICAgICAgfSxcbiAgICAgICAgICBcInJlc3BvbnNlXCI6IHtcbiAgICAgICAgICAgIFwidmFsdWVcIjoge1xuICAgICAgICAgICAgICBcIl9yZXR2YWxcIjogXCJsb25nc3RyaW5nXCJcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIHtcbiAgICAgICAgICBcIm5hbWVcIjogXCJzY3JlZW5zaG90VG9EYXRhVVJMXCIsXG4gICAgICAgICAgXCJyZXF1ZXN0XCI6IHtcbiAgICAgICAgICAgIFwidHlwZVwiOiBcInNjcmVlbnNob3RUb0RhdGFVUkxcIlxuICAgICAgICAgIH0sXG4gICAgICAgICAgXCJyZXNwb25zZVwiOiB7XG4gICAgICAgICAgICBcInZhbHVlXCI6IHtcbiAgICAgICAgICAgICAgXCJfcmV0dmFsXCI6IFwibG9uZ3N0cmluZ1wiXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICB7XG4gICAgICAgICAgXCJuYW1lXCI6IFwiZ2V0UmF3UGVybWlzc2lvbnNUYWJsZVwiLFxuICAgICAgICAgIFwicmVxdWVzdFwiOiB7XG4gICAgICAgICAgICBcInR5cGVcIjogXCJnZXRSYXdQZXJtaXNzaW9uc1RhYmxlXCJcbiAgICAgICAgICB9LFxuICAgICAgICAgIFwicmVzcG9uc2VcIjoge1xuICAgICAgICAgICAgXCJ2YWx1ZVwiOiB7XG4gICAgICAgICAgICAgIFwiX3JldHZhbFwiOiBcImpzb25cIlxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgXSxcbiAgICAgIFwiZXZlbnRzXCI6IHt9XG4gICAgfVxuICB9LFxuICBcImZyb21cIjogXCJyb290XCJcbn1cbiIsIlwidXNlIHN0cmljdFwiO1xuXG52YXIgQ2xhc3MgPSByZXF1aXJlKFwiLi9jbGFzc1wiKS5DbGFzcztcbnZhciB1dGlsID0gcmVxdWlyZShcIi4vdXRpbFwiKTtcbnZhciBrZXlzID0gdXRpbC5rZXlzO1xudmFyIHZhbHVlcyA9IHV0aWwudmFsdWVzO1xudmFyIHBhaXJzID0gdXRpbC5wYWlycztcbnZhciBxdWVyeSA9IHV0aWwucXVlcnk7XG52YXIgZmluZFBhdGggPSB1dGlsLmZpbmRQYXRoO1xudmFyIEV2ZW50VGFyZ2V0ID0gcmVxdWlyZShcIi4vZXZlbnRcIikuRXZlbnRUYXJnZXQ7XG5cbnZhciBUeXBlU3lzdGVtID0gQ2xhc3Moe1xuICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oY2xpZW50KSB7XG4gICAgdmFyIHR5cGVzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcbiAgICB2YXIgc3BlY2lmaWNhdGlvbiA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG5cbiAgICB0aGlzLnNwZWNpZmljYXRpb24gPSBzcGVjaWZpY2F0aW9uO1xuICAgIHRoaXMudHlwZXMgPSB0eXBlcztcblxuICAgIHZhciB0eXBlRm9yID0gZnVuY3Rpb24gdHlwZUZvcih0eXBlTmFtZSkge1xuICAgICAgdHlwZU5hbWUgPSB0eXBlTmFtZSB8fCBcInByaW1pdGl2ZVwiO1xuICAgICAgaWYgKCF0eXBlc1t0eXBlTmFtZV0pIHtcbiAgICAgICAgZGVmaW5lVHlwZSh0eXBlTmFtZSk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB0eXBlc1t0eXBlTmFtZV07XG4gICAgfTtcbiAgICB0aGlzLnR5cGVGb3IgPSB0eXBlRm9yO1xuXG4gICAgdmFyIGRlZmluZVR5cGUgPSBmdW5jdGlvbihkZXNjcmlwdG9yKSB7XG4gICAgICB2YXIgdHlwZSA9IHZvaWQoMCk7XG4gICAgICBpZiAodHlwZW9mKGRlc2NyaXB0b3IpID09PSBcInN0cmluZ1wiKSB7XG4gICAgICAgIGlmIChkZXNjcmlwdG9yLmluZGV4T2YoXCI6XCIpID4gMClcbiAgICAgICAgICB0eXBlID0gbWFrZUNvbXBvdW5kVHlwZShkZXNjcmlwdG9yKTtcbiAgICAgICAgZWxzZSBpZiAoZGVzY3JpcHRvci5pbmRleE9mKFwiI1wiKSA+IDApXG4gICAgICAgICAgdHlwZSA9IG5ldyBBY3RvckRldGFpbChkZXNjcmlwdG9yKTtcbiAgICAgICAgICBlbHNlIGlmIChzcGVjaWZpY2F0aW9uW2Rlc2NyaXB0b3JdKVxuICAgICAgICAgICAgdHlwZSA9IG1ha2VDYXRlZ29yeVR5cGUoc3BlY2lmaWNhdGlvbltkZXNjcmlwdG9yXSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0eXBlID0gbWFrZUNhdGVnb3J5VHlwZShkZXNjcmlwdG9yKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHR5cGUpXG4gICAgICAgIHR5cGVzW3R5cGUubmFtZV0gPSB0eXBlO1xuICAgICAgZWxzZVxuICAgICAgICB0aHJvdyBUeXBlRXJyb3IoXCJJbnZhbGlkIHR5cGU6IFwiICsgZGVzY3JpcHRvcik7XG4gICAgfTtcbiAgICB0aGlzLmRlZmluZVR5cGUgPSBkZWZpbmVUeXBlO1xuXG5cbiAgICB2YXIgbWFrZUNvbXBvdW5kVHlwZSA9IGZ1bmN0aW9uKG5hbWUpIHtcbiAgICAgIHZhciBpbmRleCA9IG5hbWUuaW5kZXhPZihcIjpcIik7XG4gICAgICB2YXIgYmFzZVR5cGUgPSBuYW1lLnNsaWNlKDAsIGluZGV4KTtcbiAgICAgIHZhciBzdWJUeXBlID0gbmFtZS5zbGljZShpbmRleCArIDEpO1xuXG4gICAgICByZXR1cm4gYmFzZVR5cGUgPT09IFwiYXJyYXlcIiA/IG5ldyBBcnJheU9mKHN1YlR5cGUpIDpcbiAgICAgIGJhc2VUeXBlID09PSBcIm51bGxhYmxlXCIgPyBuZXcgTWF5YmUoc3ViVHlwZSkgOlxuICAgICAgbnVsbDtcbiAgICB9O1xuXG4gICAgdmFyIG1ha2VDYXRlZ29yeVR5cGUgPSBmdW5jdGlvbihkZXNjcmlwdG9yKSB7XG4gICAgICB2YXIgY2F0ZWdvcnkgPSBkZXNjcmlwdG9yLmNhdGVnb3J5O1xuICAgICAgcmV0dXJuIGNhdGVnb3J5ID09PSBcImRpY3RcIiA/IG5ldyBEaWN0aW9uYXJ5KGRlc2NyaXB0b3IpIDpcbiAgICAgIGNhdGVnb3J5ID09PSBcImFjdG9yXCIgPyBuZXcgQWN0b3IoZGVzY3JpcHRvcikgOlxuICAgICAgbnVsbDtcbiAgICB9O1xuXG4gICAgdmFyIHJlYWQgPSBmdW5jdGlvbihpbnB1dCwgY29udGV4dCwgdHlwZU5hbWUpIHtcbiAgICAgIHJldHVybiB0eXBlRm9yKHR5cGVOYW1lKS5yZWFkKGlucHV0LCBjb250ZXh0KTtcbiAgICB9XG4gICAgdGhpcy5yZWFkID0gcmVhZDtcblxuICAgIHZhciB3cml0ZSA9IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0LCB0eXBlTmFtZSkge1xuICAgICAgcmV0dXJuIHR5cGVGb3IodHlwZU5hbWUpLndyaXRlKGlucHV0KTtcbiAgICB9O1xuICAgIHRoaXMud3JpdGUgPSB3cml0ZTtcblxuXG4gICAgdmFyIFR5cGUgPSBDbGFzcyh7XG4gICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oKSB7XG4gICAgICB9LFxuICAgICAgZ2V0IG5hbWUoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmNhdGVnb3J5ID8gdGhpcy5jYXRlZ29yeSArIFwiOlwiICsgdGhpcy50eXBlIDpcbiAgICAgICAgdGhpcy50eXBlO1xuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJgVHlwZWAgc3ViY2xhc3MgbXVzdCBpbXBsZW1lbnQgYHJlYWRgXCIpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiYFR5cGVgIHN1YmNsYXNzIG11c3QgaW1wbGVtZW50IGB3cml0ZWBcIik7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgUHJpbWl0dmUgPSBDbGFzcyh7XG4gICAgICBleHRlbmRzOiBUeXBlLFxuICAgICAgY29uc3R1Y3RvcjogZnVuY3Rpb24odHlwZSkge1xuICAgICAgICB0aGlzLnR5cGUgPSB0eXBlO1xuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHJldHVybiBpbnB1dDtcbiAgICAgIH0sXG4gICAgICB3cml0ZTogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQpIHtcbiAgICAgICAgcmV0dXJuIGlucHV0O1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdmFyIE1heWJlID0gQ2xhc3Moe1xuICAgICAgZXh0ZW5kczogVHlwZSxcbiAgICAgIGNhdGVnb3J5OiBcIm51bGxhYmxlXCIsXG4gICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24odHlwZSkge1xuICAgICAgICB0aGlzLnR5cGUgPSB0eXBlO1xuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHJldHVybiBpbnB1dCA9PT0gbnVsbCA/IG51bGwgOlxuICAgICAgICBpbnB1dCA9PT0gdm9pZCgwKSA/IHZvaWQoMCkgOlxuICAgICAgICByZWFkKGlucHV0LCBjb250ZXh0LCB0aGlzLnR5cGUpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gaW5wdXQgPT09IG51bGwgPyBudWxsIDpcbiAgICAgICAgaW5wdXQgPT09IHZvaWQoMCkgPyB2b2lkKDApIDpcbiAgICAgICAgd3JpdGUoaW5wdXQsIGNvbnRleHQsIHRoaXMudHlwZSk7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgQXJyYXlPZiA9IENsYXNzKHtcbiAgICAgIGV4dGVuZHM6IFR5cGUsXG4gICAgICBjYXRlZ29yeTogXCJhcnJheVwiLFxuICAgICAgY29uc3RydWN0b3I6IGZ1bmN0aW9uKHR5cGUpIHtcbiAgICAgICAgdGhpcy50eXBlID0gdHlwZTtcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICB2YXIgdHlwZSA9IHRoaXMudHlwZTtcbiAgICAgICAgcmV0dXJuIGlucHV0Lm1hcChmdW5jdGlvbigkKSB7IHJldHVybiByZWFkKCQsIGNvbnRleHQsIHR5cGUpIH0pO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICB2YXIgdHlwZSA9IHRoaXMudHlwZTtcbiAgICAgICAgcmV0dXJuIGlucHV0Lm1hcChmdW5jdGlvbigkKSB7IHJldHVybiB3cml0ZSgkLCBjb250ZXh0LCB0eXBlKSB9KTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIHZhciBtYWtlRmllbGQgPSBmdW5jdGlvbiBtYWtlRmllbGQobmFtZSwgdHlwZSkge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgICAgICBnZXQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCBuYW1lLCB7XG4gICAgICAgICAgICBjb25maWd1cmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgdmFsdWU6IHJlYWQodGhpcy5zdGF0ZVtuYW1lXSwgdGhpcy5jb250ZXh0LCB0eXBlKVxuICAgICAgICAgIH0pO1xuICAgICAgICAgIHJldHVybiB0aGlzW25hbWVdO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfTtcblxuICAgIHZhciBtYWtlRmllbGRzID0gZnVuY3Rpb24oZGVzY3JpcHRvcikge1xuICAgICAgcmV0dXJuIHBhaXJzKGRlc2NyaXB0b3IpLnJlZHVjZShmdW5jdGlvbihmaWVsZHMsIHBhaXIpIHtcbiAgICAgICAgdmFyIG5hbWUgPSBwYWlyWzBdLCB0eXBlID0gcGFpclsxXTtcbiAgICAgICAgZmllbGRzW25hbWVdID0gbWFrZUZpZWxkKG5hbWUsIHR5cGUpO1xuICAgICAgICByZXR1cm4gZmllbGRzO1xuICAgICAgfSwge30pO1xuICAgIH1cblxuICAgIHZhciBEaWN0aW9uYXJ5VHlwZSA9IENsYXNzKHt9KTtcblxuICAgIHZhciBEaWN0aW9uYXJ5ID0gQ2xhc3Moe1xuICAgICAgZXh0ZW5kczogVHlwZSxcbiAgICAgIGNhdGVnb3J5OiBcImRpY3RcIixcbiAgICAgIGdldCBuYW1lKCkgeyByZXR1cm4gdGhpcy50eXBlOyB9LFxuICAgICAgY29uc3RydWN0b3I6IGZ1bmN0aW9uKGRlc2NyaXB0b3IpIHtcbiAgICAgICAgdGhpcy50eXBlID0gZGVzY3JpcHRvci50eXBlTmFtZTtcbiAgICAgICAgdGhpcy50eXBlcyA9IGRlc2NyaXB0b3Iuc3BlY2lhbGl6YXRpb25zO1xuXG4gICAgICAgIHZhciBwcm90byA9IE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHtcbiAgICAgICAgICBleHRlbmRzOiBEaWN0aW9uYXJ5VHlwZSxcbiAgICAgICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oc3RhdGUsIGNvbnRleHQpIHtcbiAgICAgICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHRoaXMsIHtcbiAgICAgICAgICAgICAgc3RhdGU6IHtcbiAgICAgICAgICAgICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB3cml0YWJsZTogdHJ1ZSxcbiAgICAgICAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgICAgICAgdmFsdWU6IHN0YXRlXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIGNvbnRleHQ6IHtcbiAgICAgICAgICAgICAgICBlbnVtZXJhYmxlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICB3cml0YWJsZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgICAgICAgICAgICAgIHZhbHVlOiBjb250ZXh0XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSwgbWFrZUZpZWxkcyh0aGlzLnR5cGVzKSk7XG5cbiAgICAgICAgdGhpcy5jbGFzcyA9IG5ldyBDbGFzcyhwcm90byk7XG4gICAgICB9LFxuICAgICAgcmVhZDogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQpIHtcbiAgICAgICAgcmV0dXJuIG5ldyB0aGlzLmNsYXNzKGlucHV0LCBjb250ZXh0KTtcbiAgICAgIH0sXG4gICAgICB3cml0ZTogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQpIHtcbiAgICAgICAgdmFyIG91dHB1dCA9IHt9O1xuICAgICAgICBmb3IgKHZhciBrZXkgaW4gaW5wdXQpIHtcbiAgICAgICAgICBvdXRwdXRba2V5XSA9IHdyaXRlKHZhbHVlLCBjb250ZXh0LCB0eXBlc1trZXldKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gb3V0cHV0O1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgdmFyIG1ha2VNZXRob2RzID0gZnVuY3Rpb24oZGVzY3JpcHRvcnMpIHtcbiAgICAgIHJldHVybiBkZXNjcmlwdG9ycy5yZWR1Y2UoZnVuY3Rpb24obWV0aG9kcywgZGVzY3JpcHRvcikge1xuICAgICAgICBtZXRob2RzW2Rlc2NyaXB0b3IubmFtZV0gPSB7XG4gICAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgd3JpdGFibGU6IGZhbHNlLFxuICAgICAgICAgIHZhbHVlOiBtYWtlTWV0aG9kKGRlc2NyaXB0b3IpXG4gICAgICAgIH07XG4gICAgICAgIHJldHVybiBtZXRob2RzO1xuICAgICAgfSwge30pO1xuICAgIH07XG5cbiAgICB2YXIgbWFrZUV2ZW50cyA9IGZ1bmN0aW9uKGRlc2NyaXB0b3JzKSB7XG4gICAgICByZXR1cm4gcGFpcnMoZGVzY3JpcHRvcnMpLnJlZHVjZShmdW5jdGlvbihldmVudHMsIHBhaXIpIHtcbiAgICAgICAgdmFyIG5hbWUgPSBwYWlyWzBdLCBkZXNjcmlwdG9yID0gcGFpclsxXTtcbiAgICAgICAgdmFyIGV2ZW50ID0gbmV3IEV2ZW50KG5hbWUsIGRlc2NyaXB0b3IpO1xuICAgICAgICBldmVudHNbZXZlbnQuZXZlbnRUeXBlXSA9IGV2ZW50O1xuICAgICAgICByZXR1cm4gZXZlbnRzO1xuICAgICAgfSwgT2JqZWN0LmNyZWF0ZShudWxsKSk7XG4gICAgfTtcblxuICAgIHZhciBBY3RvciA9IENsYXNzKHtcbiAgICAgIGV4dGVuZHM6IFR5cGUsXG4gICAgICBjYXRlZ29yeTogXCJhY3RvclwiLFxuICAgICAgZ2V0IG5hbWUoKSB7IHJldHVybiB0aGlzLnR5cGU7IH0sXG4gICAgICBjb25zdHJ1Y3RvcjogZnVuY3Rpb24oZGVzY3JpcHRvcikge1xuICAgICAgICB0aGlzLnR5cGUgPSBkZXNjcmlwdG9yLnR5cGVOYW1lO1xuXG4gICAgICAgIHZhciBldmVudHMgPSBtYWtlRXZlbnRzKGRlc2NyaXB0b3IuZXZlbnRzIHx8IHt9KTtcbiAgICAgICAgdmFyIGZpZWxkcyA9IG1ha2VGaWVsZHMoZGVzY3JpcHRvci5maWVsZHMgfHwge30pO1xuICAgICAgICB2YXIgbWV0aG9kcyA9IG1ha2VNZXRob2RzKGRlc2NyaXB0b3IubWV0aG9kcyB8fCBbXSk7XG5cblxuICAgICAgICB2YXIgcHJvdG8gPSB7XG4gICAgICAgICAgZXh0ZW5kczogRnJvbnQsXG4gICAgICAgICAgY29uc3RydWN0b3I6IGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgRnJvbnQuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICAgICAgICB9LFxuICAgICAgICAgIGV2ZW50czogZXZlbnRzXG4gICAgICAgIH07XG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHByb3RvLCBmaWVsZHMpO1xuICAgICAgICBPYmplY3QuZGVmaW5lUHJvcGVydGllcyhwcm90bywgbWV0aG9kcyk7XG5cbiAgICAgICAgdGhpcy5jbGFzcyA9IENsYXNzKHByb3RvKTtcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCwgZGV0YWlsKSB7XG4gICAgICAgIHZhciBzdGF0ZSA9IHR5cGVvZihpbnB1dCkgPT09IFwic3RyaW5nXCIgPyB7IGFjdG9yOiBpbnB1dCB9IDogaW5wdXQ7XG5cbiAgICAgICAgdmFyIGFjdG9yID0gY2xpZW50LmdldChzdGF0ZS5hY3RvcikgfHwgbmV3IHRoaXMuY2xhc3Moc3RhdGUsIGNvbnRleHQpO1xuICAgICAgICBhY3Rvci5mb3JtKHN0YXRlLCBkZXRhaWwsIGNvbnRleHQpO1xuXG4gICAgICAgIHJldHVybiBhY3RvcjtcbiAgICAgIH0sXG4gICAgICB3cml0ZTogZnVuY3Rpb24oaW5wdXQsIGNvbnRleHQsIGRldGFpbCkge1xuICAgICAgICByZXR1cm4gaW5wdXQuaWQ7XG4gICAgICB9XG4gICAgfSk7XG4gICAgZXhwb3J0cy5BY3RvciA9IEFjdG9yO1xuXG5cbiAgICB2YXIgQWN0b3JEZXRhaWwgPSBDbGFzcyh7XG4gICAgICBleHRlbmRzOiBBY3RvcixcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihuYW1lKSB7XG4gICAgICAgIHZhciBwYXJ0cyA9IG5hbWUuc3BsaXQoXCIjXCIpXG4gICAgICAgIHRoaXMuYWN0b3JUeXBlID0gcGFydHNbMF1cbiAgICAgICAgdGhpcy5kZXRhaWwgPSBwYXJ0c1sxXTtcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gdHlwZUZvcih0aGlzLmFjdG9yVHlwZSkucmVhZChpbnB1dCwgY29udGV4dCwgdGhpcy5kZXRhaWwpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gdHlwZUZvcih0aGlzLmFjdG9yVHlwZSkud3JpdGUoaW5wdXQsIGNvbnRleHQsIHRoaXMuZGV0YWlsKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICBleHBvcnRzLkFjdG9yRGV0YWlsID0gQWN0b3JEZXRhaWw7XG5cbiAgICB2YXIgTWV0aG9kID0gQ2xhc3Moe1xuICAgICAgZXh0ZW5kczogVHlwZSxcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihkZXNjcmlwdG9yKSB7XG4gICAgICAgIHRoaXMudHlwZSA9IGRlc2NyaXB0b3IubmFtZTtcbiAgICAgICAgdGhpcy5wYXRoID0gZmluZFBhdGgoZGVzY3JpcHRvci5yZXNwb25zZSwgXCJfcmV0dmFsXCIpO1xuICAgICAgICB0aGlzLnJlc3BvbnNlVHlwZSA9IHRoaXMucGF0aCAmJiBxdWVyeShkZXNjcmlwdG9yLnJlc3BvbnNlLCB0aGlzLnBhdGgpLl9yZXR2YWw7XG4gICAgICAgIHRoaXMucmVxdWVzdFR5cGUgPSBkZXNjcmlwdG9yLnJlcXVlc3QudHlwZTtcblxuICAgICAgICB2YXIgcGFyYW1zID0gW107XG4gICAgICAgIGZvciAodmFyIGtleSBpbiBkZXNjcmlwdG9yLnJlcXVlc3QpIHtcbiAgICAgICAgICBpZiAoa2V5ICE9PSBcInR5cGVcIikge1xuICAgICAgICAgICAgdmFyIHBhcmFtID0gZGVzY3JpcHRvci5yZXF1ZXN0W2tleV07XG4gICAgICAgICAgICB2YXIgaW5kZXggPSBcIl9hcmdcIiBpbiBwYXJhbSA/IHBhcmFtLl9hcmcgOiBwYXJhbS5fb3B0aW9uO1xuICAgICAgICAgICAgdmFyIGlzUGFyYW0gPSBwYXJhbS5fb3B0aW9uID09PSBpbmRleDtcbiAgICAgICAgICAgIHZhciBpc0FyZ3VtZW50ID0gcGFyYW0uX2FyZyA9PT0gaW5kZXg7XG4gICAgICAgICAgICBwYXJhbXNbaW5kZXhdID0ge1xuICAgICAgICAgICAgICB0eXBlOiBwYXJhbS50eXBlLFxuICAgICAgICAgICAgICBrZXk6IGtleSxcbiAgICAgICAgICAgICAgaW5kZXg6IGluZGV4LFxuICAgICAgICAgICAgICBpc1BhcmFtOiBpc1BhcmFtLFxuICAgICAgICAgICAgICBpc0FyZ3VtZW50OiBpc0FyZ3VtZW50XG4gICAgICAgICAgICB9O1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICB0aGlzLnBhcmFtcyA9IHBhcmFtcztcbiAgICAgIH0sXG4gICAgICByZWFkOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gcmVhZChxdWVyeShpbnB1dCwgdGhpcy5wYXRoKSwgY29udGV4dCwgdGhpcy5yZXNwb25zZVR5cGUpO1xuICAgICAgfSxcbiAgICAgIHdyaXRlOiBmdW5jdGlvbihpbnB1dCwgY29udGV4dCkge1xuICAgICAgICByZXR1cm4gdGhpcy5wYXJhbXMucmVkdWNlKGZ1bmN0aW9uKHJlc3VsdCwgcGFyYW0pIHtcbiAgICAgICAgICByZXN1bHRbcGFyYW0ua2V5XSA9IHdyaXRlKGlucHV0W3BhcmFtLmluZGV4XSwgY29udGV4dCwgcGFyYW0udHlwZSk7XG4gICAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICAgICAgfSwge3R5cGU6IHRoaXMudHlwZX0pO1xuICAgICAgfVxuICAgIH0pO1xuICAgIGV4cG9ydHMuTWV0aG9kID0gTWV0aG9kO1xuXG4gICAgdmFyIHByb2ZpbGVyID0gZnVuY3Rpb24obWV0aG9kLCBpZCkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgc3RhcnQgPSBuZXcgRGF0ZSgpO1xuICAgICAgICByZXR1cm4gbWV0aG9kLmFwcGx5KHRoaXMsIGFyZ3VtZW50cykudGhlbihmdW5jdGlvbihyZXN1bHQpIHtcbiAgICAgICAgICB2YXIgZW5kID0gbmV3IERhdGUoKTtcbiAgICAgICAgICBjbGllbnQudGVsZW1ldHJ5LmFkZChpZCwgK2VuZCAtIHN0YXJ0KTtcbiAgICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgICB9KTtcbiAgICAgIH07XG4gICAgfTtcblxuICAgIHZhciBkZXN0cnVjdG9yID0gZnVuY3Rpb24obWV0aG9kKSB7XG4gICAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICAgIHJldHVybiBtZXRob2QuYXBwbHkodGhpcywgYXJndW1lbnRzKS50aGVuKGZ1bmN0aW9uKHJlc3VsdCkge1xuICAgICAgICAgIGNsaWVudC5yZWxlYXNlKHRoaXMpO1xuICAgICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgICAgIH0pO1xuICAgICAgfTtcbiAgICB9O1xuXG4gICAgZnVuY3Rpb24gbWFrZU1ldGhvZChkZXNjcmlwdG9yKSB7XG4gICAgICB2YXIgdHlwZSA9IG5ldyBNZXRob2QoZGVzY3JpcHRvcik7XG4gICAgICB2YXIgbWV0aG9kID0gZGVzY3JpcHRvci5vbmV3YXkgPyBtYWtlVW5pZGlyZWNhdGlvbmFsTWV0aG9kKGRlc2NyaXB0b3IsIHR5cGUpIDpcbiAgICAgICAgICAgICAgICAgICBtYWtlQmlkaXJlY3Rpb25hbE1ldGhvZChkZXNjcmlwdG9yLCB0eXBlKTtcblxuICAgICAgaWYgKGRlc2NyaXB0b3IudGVsZW1ldHJ5KVxuICAgICAgICBtZXRob2QgPSBwcm9maWxlcihtZXRob2QpO1xuICAgICAgaWYgKGRlc2NyaXB0b3IucmVsZWFzZSlcbiAgICAgICAgbWV0aG9kID0gZGVzdHJ1Y3RvcihtZXRob2QpO1xuXG4gICAgICByZXR1cm4gbWV0aG9kO1xuICAgIH1cblxuICAgIHZhciBtYWtlVW5pZGlyZWNhdGlvbmFsTWV0aG9kID0gZnVuY3Rpb24oZGVzY3JpcHRvciwgdHlwZSkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgcGFja2V0ID0gdHlwZS53cml0ZShhcmd1bWVudHMsIHRoaXMpO1xuICAgICAgICBwYWNrZXQudG8gPSB0aGlzLmlkO1xuICAgICAgICBjbGllbnQuc2VuZChwYWNrZXQpO1xuICAgICAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKHZvaWQoMCkpO1xuICAgICAgfTtcbiAgICB9O1xuXG4gICAgdmFyIG1ha2VCaWRpcmVjdGlvbmFsTWV0aG9kID0gZnVuY3Rpb24oZGVzY3JpcHRvciwgdHlwZSkge1xuICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgY29udGV4dCA9IHRoaXMuY29udGV4dDtcbiAgICAgICAgdmFyIHBhY2tldCA9IHR5cGUud3JpdGUoYXJndW1lbnRzLCBjb250ZXh0KTtcbiAgICAgICAgdmFyIGNvbnRleHQgPSB0aGlzLmNvbnRleHQ7XG4gICAgICAgIHBhY2tldC50byA9IHRoaXMuaWQ7XG4gICAgICAgIHJldHVybiBjbGllbnQucmVxdWVzdChwYWNrZXQpLnRoZW4oZnVuY3Rpb24ocGFja2V0KSB7XG4gICAgICAgICAgcmV0dXJuIHR5cGUucmVhZChwYWNrZXQsIGNvbnRleHQpO1xuICAgICAgICB9KTtcbiAgICAgIH07XG4gICAgfTtcblxuICAgIHZhciBFdmVudCA9IENsYXNzKHtcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihuYW1lLCBkZXNjcmlwdG9yKSB7XG4gICAgICAgIHRoaXMubmFtZSA9IGRlc2NyaXB0b3IudHlwZSB8fCBuYW1lO1xuICAgICAgICB0aGlzLmV2ZW50VHlwZSA9IGRlc2NyaXB0b3IudHlwZSB8fCBuYW1lO1xuICAgICAgICB0aGlzLnR5cGVzID0gT2JqZWN0LmNyZWF0ZShudWxsKTtcblxuICAgICAgICB2YXIgdHlwZXMgPSB0aGlzLnR5cGVzO1xuICAgICAgICBmb3IgKHZhciBrZXkgaW4gZGVzY3JpcHRvcikge1xuICAgICAgICAgIGlmIChrZXkgPT09IFwidHlwZVwiKSB7XG4gICAgICAgICAgICB0eXBlc1trZXldID0gXCJzdHJpbmdcIjtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdHlwZXNba2V5XSA9IGRlc2NyaXB0b3Jba2V5XS50eXBlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIHJlYWQ6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHZhciBvdXRwdXQgPSB7fTtcbiAgICAgICAgdmFyIHR5cGVzID0gdGhpcy50eXBlcztcbiAgICAgICAgZm9yICh2YXIga2V5IGluIGlucHV0KSB7XG4gICAgICAgICAgb3V0cHV0W2tleV0gPSByZWFkKGlucHV0W2tleV0sIGNvbnRleHQsIHR5cGVzW2tleV0pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvdXRwdXQ7XG4gICAgICB9LFxuICAgICAgd3JpdGU6IGZ1bmN0aW9uKGlucHV0LCBjb250ZXh0KSB7XG4gICAgICAgIHZhciBvdXRwdXQgPSB7fTtcbiAgICAgICAgdmFyIHR5cGVzID0gdGhpcy50eXBlcztcbiAgICAgICAgZm9yICh2YXIga2V5IGluIHRoaXMudHlwZXMpIHtcbiAgICAgICAgICBvdXRwdXRba2V5XSA9IHdyaXRlKGlucHV0W2tleV0sIGNvbnRleHQsIHR5cGVzW2tleV0pO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBvdXRwdXQ7XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICB2YXIgRnJvbnQgPSBDbGFzcyh7XG4gICAgICBleHRlbmRzOiBFdmVudFRhcmdldCxcbiAgICAgIEV2ZW50VGFyZ2V0OiBFdmVudFRhcmdldCxcbiAgICAgIGNvbnN0cnVjdG9yOiBmdW5jdGlvbihzdGF0ZSkge1xuICAgICAgICB0aGlzLkV2ZW50VGFyZ2V0KCk7XG4gICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKHRoaXMsICB7XG4gICAgICAgICAgc3RhdGU6IHtcbiAgICAgICAgICAgIGVudW1lcmFibGU6IGZhbHNlLFxuICAgICAgICAgICAgd3JpdGFibGU6IHRydWUsXG4gICAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgICB2YWx1ZTogc3RhdGVcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuXG4gICAgICAgIGNsaWVudC5yZWdpc3Rlcih0aGlzKTtcbiAgICAgIH0sXG4gICAgICBnZXQgaWQoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnN0YXRlLmFjdG9yO1xuICAgICAgfSxcbiAgICAgIGdldCBjb250ZXh0KCkge1xuICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgIH0sXG4gICAgICBmb3JtOiBmdW5jdGlvbihzdGF0ZSwgZGV0YWlsLCBjb250ZXh0KSB7XG4gICAgICAgIGlmICh0aGlzLnN0YXRlICE9PSBzdGF0ZSkge1xuICAgICAgICAgIGlmIChkZXRhaWwpIHtcbiAgICAgICAgICAgIHRoaXMuc3RhdGVbZGV0YWlsXSA9IHN0YXRlW2RldGFpbF07XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHBhaXJzKHN0YXRlKS5mb3JFYWNoKGZ1bmN0aW9uKHBhaXIpIHtcbiAgICAgICAgICAgICAgdmFyIGtleSA9IHBhaXJbMF0sIHZhbHVlID0gcGFpclsxXTtcbiAgICAgICAgICAgICAgdGhpcy5zdGF0ZVtrZXldID0gdmFsdWU7XG4gICAgICAgICAgICB9LCB0aGlzKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoY29udGV4dCkge1xuICAgICAgICAgIGNsaWVudC5zdXBlcnZpc2UoY29udGV4dCwgdGhpcyk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICByZXF1ZXN0VHlwZXM6IGZ1bmN0aW9uKCkge1xuICAgICAgICByZXR1cm4gY2xpZW50LnJlcXVlc3Qoe1xuICAgICAgICAgIHRvOiB0aGlzLmlkLFxuICAgICAgICAgIHR5cGU6IFwicmVxdWVzdFR5cGVzXCJcbiAgICAgICAgfSkudGhlbihmdW5jdGlvbihwYWNrZXQpIHtcbiAgICAgICAgICByZXR1cm4gcGFja2V0LnJlcXVlc3RUeXBlcztcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfSk7XG4gICAgdHlwZXMucHJpbWl0aXZlID0gbmV3IFByaW1pdHZlKFwicHJpbWl0aXZlXCIpO1xuICAgIHR5cGVzLnN0cmluZyA9IG5ldyBQcmltaXR2ZShcInN0cmluZ1wiKTtcbiAgICB0eXBlcy5udW1iZXIgPSBuZXcgUHJpbWl0dmUoXCJudW1iZXJcIik7XG4gICAgdHlwZXMuYm9vbGVhbiA9IG5ldyBQcmltaXR2ZShcImJvb2xlYW5cIik7XG4gICAgdHlwZXMuanNvbiA9IG5ldyBQcmltaXR2ZShcImpzb25cIik7XG4gICAgdHlwZXMuYXJyYXkgPSBuZXcgUHJpbWl0dmUoXCJhcnJheVwiKTtcbiAgfSxcbiAgcmVnaXN0ZXJUeXBlczogZnVuY3Rpb24oZGVzY3JpcHRvcikge1xuICAgIHZhciBzcGVjaWZpY2F0aW9uID0gdGhpcy5zcGVjaWZpY2F0aW9uO1xuICAgIHZhbHVlcyhkZXNjcmlwdG9yLnR5cGVzKS5mb3JFYWNoKGZ1bmN0aW9uKGRlc2NyaXB0b3IpIHtcbiAgICAgIHNwZWNpZmljYXRpb25bZGVzY3JpcHRvci50eXBlTmFtZV0gPSBkZXNjcmlwdG9yO1xuICAgIH0pO1xuICB9XG59KTtcbmV4cG9ydHMuVHlwZVN5c3RlbSA9IFR5cGVTeXN0ZW07XG4iLCJcInVzZSBzdHJpY3RcIjtcblxudmFyIGtleXMgPSBPYmplY3Qua2V5cztcbmV4cG9ydHMua2V5cyA9IGtleXM7XG5cbi8vIFJldHVybnMgYXJyYXkgb2YgdmFsdWVzIGZvciB0aGUgZ2l2ZW4gb2JqZWN0LlxudmFyIHZhbHVlcyA9IGZ1bmN0aW9uKG9iamVjdCkge1xuICByZXR1cm4ga2V5cyhvYmplY3QpLm1hcChmdW5jdGlvbihrZXkpIHtcbiAgICByZXR1cm4gb2JqZWN0W2tleV1cbiAgfSk7XG59O1xuZXhwb3J0cy52YWx1ZXMgPSB2YWx1ZXM7XG5cbi8vIFJldHVybnMgW2tleSwgdmFsdWVdIHBhaXJzIGZvciB0aGUgZ2l2ZW4gb2JqZWN0LlxudmFyIHBhaXJzID0gZnVuY3Rpb24ob2JqZWN0KSB7XG4gIHJldHVybiBrZXlzKG9iamVjdCkubWFwKGZ1bmN0aW9uKGtleSkge1xuICAgIHJldHVybiBba2V5LCBvYmplY3Rba2V5XV1cbiAgfSk7XG59O1xuZXhwb3J0cy5wYWlycyA9IHBhaXJzO1xuXG5cbi8vIFF1ZXJpZXMgYW4gb2JqZWN0IGZvciB0aGUgZmllbGQgbmVzdGVkIHdpdGggaW4gaXQuXG52YXIgcXVlcnkgPSBmdW5jdGlvbihvYmplY3QsIHBhdGgpIHtcbiAgcmV0dXJuIHBhdGgucmVkdWNlKGZ1bmN0aW9uKG9iamVjdCwgZW50cnkpIHtcbiAgICByZXR1cm4gb2JqZWN0ICYmIG9iamVjdFtlbnRyeV1cbiAgfSwgb2JqZWN0KTtcbn07XG5leHBvcnRzLnF1ZXJ5ID0gcXVlcnk7XG5cbnZhciBpc09iamVjdCA9IGZ1bmN0aW9uKHgpIHtcbiAgcmV0dXJuIHggJiYgdHlwZW9mKHgpID09PSBcIm9iamVjdFwiXG59XG5cbnZhciBmaW5kUGF0aCA9IGZ1bmN0aW9uKG9iamVjdCwga2V5KSB7XG4gIHZhciBwYXRoID0gdm9pZCgwKTtcbiAgaWYgKG9iamVjdCAmJiB0eXBlb2Yob2JqZWN0KSA9PT0gXCJvYmplY3RcIikge1xuICAgIHZhciBuYW1lcyA9IGtleXMob2JqZWN0KTtcbiAgICBpZiAobmFtZXMuaW5kZXhPZihrZXkpID49IDApIHtcbiAgICAgIHBhdGggPSBbXTtcbiAgICB9IGVsc2Uge1xuICAgICAgdmFyIGluZGV4ID0gMDtcbiAgICAgIHZhciBjb3VudCA9IG5hbWVzLmxlbmd0aDtcbiAgICAgIHdoaWxlIChpbmRleCA8IGNvdW50ICYmICFwYXRoKXtcbiAgICAgICAgdmFyIGhlYWQgPSBuYW1lc1tpbmRleF07XG4gICAgICAgIHZhciB0YWlsID0gZmluZFBhdGgob2JqZWN0W2hlYWRdLCBrZXkpO1xuICAgICAgICBwYXRoID0gdGFpbCA/IFtoZWFkXS5jb25jYXQodGFpbCkgOiB0YWlsO1xuICAgICAgICBpbmRleCA9IGluZGV4ICsgMVxuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gcGF0aDtcbn07XG5leHBvcnRzLmZpbmRQYXRoID0gZmluZFBhdGg7XG4iXX0=
+(1)
+});
diff --git a/components/jetpack/diffpatcher/.travis.yml b/components/jetpack/diffpatcher/.travis.yml
new file mode 100644
index 000000000..780731a47
--- /dev/null
+++ b/components/jetpack/diffpatcher/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+ - 0.4
+ - 0.5
+ - 0.6
diff --git a/components/jetpack/diffpatcher/History.md b/components/jetpack/diffpatcher/History.md
new file mode 100644
index 000000000..d38978805
--- /dev/null
+++ b/components/jetpack/diffpatcher/History.md
@@ -0,0 +1,14 @@
+# Changes
+
+## 1.0.1 / 2013-05-01
+
+ - Update method library version.
+
+## 1.0.0 / 2012-11-09
+
+ - Test integration for browsers.
+ - New method library.
+
+## 0.0.1 / 2012-10-22
+
+ - Initial release
diff --git a/components/jetpack/diffpatcher/License.md b/components/jetpack/diffpatcher/License.md
new file mode 100644
index 000000000..ed76489a3
--- /dev/null
+++ b/components/jetpack/diffpatcher/License.md
@@ -0,0 +1,18 @@
+Copyright 2012 Irakli Gozalishvili. All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/components/jetpack/diffpatcher/Readme.md b/components/jetpack/diffpatcher/Readme.md
new file mode 100644
index 000000000..1520b1c37
--- /dev/null
+++ b/components/jetpack/diffpatcher/Readme.md
@@ -0,0 +1,70 @@
+# diffpatcher
+
+[![Build Status](https://secure.travis-ci.org/Gozala/diffpatcher.png)](http://travis-ci.org/Gozala/diffpatcher)
+
+[![Browser support](https://ci.testling.com/Gozala/diffpatcher.png)](http://ci.testling.com/Gozala/diffpatcher)
+
+Diffpatcher is a small library that lets you treat hashes as if they were
+git repositories.
+
+## diff
+
+Diff function that takes two hashes and returns delta hash.
+
+```js
+var diff = require("diffpatcher/diff")
+
+diff({ a: { b: 1 }, c: { d: 2 } }, // hash#1
+ { a: { e: 3 }, c: { d: 4 } }) // hash#2
+
+// => { // delta
+// a: {
+// b: null, // -
+// e: 3 // +
+// },
+// c: {
+// d: 4 // ±
+// }
+// }
+```
+
+As you can see from the example above `delta` makes no real distinction between
+proprety upadate and property addition. Try to think of additions as an update
+from `undefined` to whatever it's being updated to.
+
+## patch
+
+Patch fuction takes a `hash` and a `delta` and returns a new `hash` which is
+just like orginial but with delta applied to it. Let's apply delta from the
+previous example to the first hash from the same example
+
+
+```js
+var patch = require("diffpatcher/patch")
+
+patch({ a: { b: 1 }, c: { d: 2 } }, // hash#1
+ { // delta
+ a: {
+ b: null, // -
+ e: 3 // +
+ },
+ c: {
+ d: 4 // ±
+ }
+ })
+
+// => { a: { e: 3 }, c: { d: 4 } } // hash#2
+```
+
+That's about it really, just diffing hashes and applying thes diffs on them.
+
+
+### rebase
+
+And as Linus mentioned everything in git can be expressed with `rebase`, that
+also pretty much the case for `diffpatcher`. `rebase` takes `target` hash,
+and rebases `parent` onto it with `diff` applied.
+
+## Install
+
+ npm install diffpatcher
diff --git a/components/jetpack/diffpatcher/diff.js b/components/jetpack/diffpatcher/diff.js
new file mode 100644
index 000000000..967c137e6
--- /dev/null
+++ b/components/jetpack/diffpatcher/diff.js
@@ -0,0 +1,45 @@
+"use strict";
+
+var method = require("../method/core")
+
+// Method is designed to work with data structures representing application
+// state. Calling it with a state should return object representing `delta`
+// that has being applied to a previous state to get to a current state.
+//
+// Example
+//
+// diff(state) // => { "item-id-1": { title: "some title" } "item-id-2": null }
+var diff = method("diff@diffpatcher")
+
+// diff between `null` / `undefined` to any hash is a hash itself.
+diff.define(null, function(from, to) { return to })
+diff.define(undefined, function(from, to) { return to })
+diff.define(Object, function(from, to) {
+ return calculate(from, to || {}) || {}
+})
+
+function calculate(from, to) {
+ var diff = {}
+ var changes = 0
+ Object.keys(from).forEach(function(key) {
+ changes = changes + 1
+ if (!(key in to) && from[key] != null) diff[key] = null
+ else changes = changes - 1
+ })
+ Object.keys(to).forEach(function(key) {
+ changes = changes + 1
+ var previous = from[key]
+ var current = to[key]
+ if (previous === current) return (changes = changes - 1)
+ if (typeof(current) !== "object") return diff[key] = current
+ if (typeof(previous) !== "object") return diff[key] = current
+ var delta = calculate(previous, current)
+ if (delta) diff[key] = delta
+ else changes = changes - 1
+ })
+ return changes ? diff : null
+}
+
+diff.calculate = calculate
+
+module.exports = diff
diff --git a/components/jetpack/diffpatcher/index.js b/components/jetpack/diffpatcher/index.js
new file mode 100644
index 000000000..91ddba425
--- /dev/null
+++ b/components/jetpack/diffpatcher/index.js
@@ -0,0 +1,5 @@
+"use strict";
+
+exports.diff = require("./diff")
+exports.patch = require("./patch")
+exports.rebase = require("./rebase")
diff --git a/components/jetpack/diffpatcher/package.json b/components/jetpack/diffpatcher/package.json
new file mode 100644
index 000000000..54e085d2e
--- /dev/null
+++ b/components/jetpack/diffpatcher/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "diffpatcher",
+ "id": "diffpatcher",
+ "version": "1.2.0",
+ "description": "Utilities for diff-ing & patch-ing hashes",
+ "keywords": [
+ "diff", "patch", "rebase", "hash", "changes", "versions"
+ ],
+ "author": "Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com)",
+ "homepage": "https://github.com/Gozala/diffpatcher",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Gozala/diffpatcher.git",
+ "web": "https://github.com/Gozala/diffpatcher"
+ },
+ "bugs": {
+ "url": "http://github.com/Gozala/diffpatcher/issues/"
+ },
+ "dependencies": {
+ "method": "~2.0.0"
+ },
+ "devDependencies": {
+ "test": "~0.x.0",
+ "phantomify": "~0.x.0",
+ "retape": "~0.x.0",
+ "tape": "~0.1.5"
+ },
+ "main": "./index.js",
+ "scripts": {
+ "test": "npm run test-node && npm run test-browser",
+ "test-browser": "node ./node_modules/phantomify/bin/cmd.js ./test/common.js",
+ "test-node": "node ./test/common.js",
+ "test-tap": "node ./test/tap.js"
+ },
+ "testling": {
+ "files": "test/tap.js",
+ "browsers": [
+ "ie/9..latest",
+ "chrome/25..latest",
+ "firefox/20..latest",
+ "safari/6..latest",
+ "opera/11.0..latest",
+ "iphone/6..latest",
+ "ipad/6..latest",
+ "android-browser/4.2..latest"
+ ]
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "https://github.com/Gozala/diffpatcher/License.md"
+ }
+ ]
+}
diff --git a/components/jetpack/diffpatcher/patch.js b/components/jetpack/diffpatcher/patch.js
new file mode 100644
index 000000000..9271e8893
--- /dev/null
+++ b/components/jetpack/diffpatcher/patch.js
@@ -0,0 +1,21 @@
+"use strict";
+
+var method = require("../method/core")
+var rebase = require("./rebase")
+
+// Method is designed to work with data structures representing application
+// state. Calling it with a state and delta should return object representing
+// new state, with changes in `delta` being applied to previous.
+//
+// ## Example
+//
+// patch(state, {
+// "item-id-1": { completed: false }, // update
+// "item-id-2": null // delete
+// })
+var patch = method("patch@diffpatcher")
+patch.define(Object, function patch(hash, delta) {
+ return rebase({}, hash, delta)
+})
+
+module.exports = patch
diff --git a/components/jetpack/diffpatcher/rebase.js b/components/jetpack/diffpatcher/rebase.js
new file mode 100644
index 000000000..03c756fee
--- /dev/null
+++ b/components/jetpack/diffpatcher/rebase.js
@@ -0,0 +1,36 @@
+"use strict";
+
+var nil = {}
+var owns = ({}).hasOwnProperty
+
+function rebase(result, parent, delta) {
+ var key, current, previous, update
+ for (key in parent) {
+ if (owns.call(parent, key)) {
+ previous = parent[key]
+ update = owns.call(delta, key) ? delta[key] : nil
+ if (previous === null) continue
+ else if (previous === void(0)) continue
+ else if (update === null) continue
+ else if (update === void(0)) continue
+ else result[key] = previous
+ }
+ }
+ for (key in delta) {
+ if (owns.call(delta, key)) {
+ update = delta[key]
+ current = owns.call(result, key) ? result[key] : nil
+ if (current === update) continue
+ else if (update === null) continue
+ else if (update === void(0)) continue
+ else if (current === nil) result[key] = update
+ else if (typeof(update) !== "object") result[key] = update
+ else if (typeof(current) !== "object") result[key] = update
+ else result[key]= rebase({}, current, update)
+ }
+ }
+
+ return result
+}
+
+module.exports = rebase
diff --git a/components/jetpack/diffpatcher/test/common.js b/components/jetpack/diffpatcher/test/common.js
new file mode 100644
index 000000000..dbc79013c
--- /dev/null
+++ b/components/jetpack/diffpatcher/test/common.js
@@ -0,0 +1,3 @@
+"use strict";
+
+require("test").run(require("./index"))
diff --git a/components/jetpack/diffpatcher/test/diff.js b/components/jetpack/diffpatcher/test/diff.js
new file mode 100644
index 000000000..d1d674005
--- /dev/null
+++ b/components/jetpack/diffpatcher/test/diff.js
@@ -0,0 +1,59 @@
+"use strict";
+
+var diff = require("../diff")
+
+exports["test diff from null"] = function(assert) {
+ var to = { a: 1, b: 2 }
+ assert.equal(diff(null, to), to, "diff null to x returns x")
+ assert.equal(diff(void(0), to), to, "diff undefined to x returns x")
+
+}
+
+exports["test diff to null"] = function(assert) {
+ var from = { a: 1, b: 2 }
+ assert.deepEqual(diff({ a: 1, b: 2 }, null),
+ { a: null, b: null },
+ "diff x null returns x with all properties nullified")
+}
+
+exports["test diff identical"] = function(assert) {
+ assert.deepEqual(diff({}, {}), {}, "diff on empty objects is {}")
+
+ assert.deepEqual(diff({ a: 1, b: 2 }, { a: 1, b: 2 }), {},
+ "if properties match diff is {}")
+
+ assert.deepEqual(diff({ a: 1, b: { c: { d: 3, e: 4 } } },
+ { a: 1, b: { c: { d: 3, e: 4 } } }), {},
+ "diff between identical nested hashes is {}")
+
+}
+
+exports["test diff delete"] = function(assert) {
+ assert.deepEqual(diff({ a: 1, b: 2 }, { b: 2 }), { a: null },
+ "missing property is deleted")
+ assert.deepEqual(diff({ a: 1, b: 2 }, { a: 2 }), { a: 2, b: null },
+ "missing property is deleted another updated")
+ assert.deepEqual(diff({ a: 1, b: 2 }, {}), { a: null, b: null },
+ "missing propertes are deleted")
+ assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, {}),
+ { a: null, b: null },
+ "missing deep propertes are deleted")
+ assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, { b: { c: {} } }),
+ { a: null, b: { c: { d: null } } },
+ "missing nested propertes are deleted")
+}
+
+exports["test add update"] = function(assert) {
+ assert.deepEqual(diff({ a: 1, b: 2 }, { b: 2, c: 3 }), { a: null, c: 3 },
+ "delete and add")
+ assert.deepEqual(diff({ a: 1, b: 2 }, { a: 2, c: 3 }), { a: 2, b: null, c: 3 },
+ "delete and adds")
+ assert.deepEqual(diff({}, { a: 1, b: 2 }), { a: 1, b: 2 },
+ "diff on empty objcet returns equivalen of to")
+ assert.deepEqual(diff({ a: 1, b: { c: { d: 2 } } }, { d: 3 }),
+ { a: null, b: null, d: 3 },
+ "missing deep propertes are deleted")
+ assert.deepEqual(diff({ b: { c: {} }, d: null }, { a: 1, b: { c: { d: 2 } } }),
+ { a: 1, b: { c: { d: 2 } } },
+ "missing nested propertes are deleted")
+}
diff --git a/components/jetpack/diffpatcher/test/index.js b/components/jetpack/diffpatcher/test/index.js
new file mode 100644
index 000000000..c06407e7c
--- /dev/null
+++ b/components/jetpack/diffpatcher/test/index.js
@@ -0,0 +1,14 @@
+"use strict";
+
+var diff = require("../diff")
+var patch = require("../patch")
+
+exports["test diff"] = require("./diff")
+exports["test patch"] = require("./patch")
+
+exports["test patch(a, diff(a, b)) => b"] = function(assert) {
+ var a = { a: { b: 1 }, c: { d: 2 } }
+ var b = { a: { e: 3 }, c: { d: 4 } }
+
+ assert.deepEqual(patch(a, diff(a, b)), b, "patch(a, diff(a, b)) => b")
+}
diff --git a/components/jetpack/diffpatcher/test/patch.js b/components/jetpack/diffpatcher/test/patch.js
new file mode 100644
index 000000000..dc2e38229
--- /dev/null
+++ b/components/jetpack/diffpatcher/test/patch.js
@@ -0,0 +1,83 @@
+"use strict";
+
+var patch = require("../patch")
+
+exports["test patch delete"] = function(assert) {
+ var hash = { a: 1, b: 2 }
+
+ assert.deepEqual(patch(hash, { a: null }), { b: 2 }, "null removes property")
+}
+
+exports["test patch delete with void"] = function(assert) {
+ var hash = { a: 1, b: 2 }
+
+ assert.deepEqual(patch(hash, { a: void(0) }), { b: 2 },
+ "void(0) removes property")
+}
+
+exports["test patch delete missing"] = function(assert) {
+ assert.deepEqual(patch({ a: 1, b: 2 }, { c: null }),
+ { a: 1, b: 2 },
+ "null removes property if exists");
+
+ assert.deepEqual(patch({ a: 1, b: 2 }, { c: void(0) }),
+ { a: 1, b: 2 },
+ "void removes property if exists");
+}
+
+exports["test delete deleted"] = function(assert) {
+ assert.deepEqual(patch({ a: null, b: 2, c: 3, d: void(0)},
+ { a: void(0), b: null, d: null }),
+ {c: 3},
+ "removed all existing and non existing");
+}
+
+exports["test update deleted"] = function(assert) {
+ assert.deepEqual(patch({ a: null, b: void(0), c: 3},
+ { a: { b: 2 } }),
+ { a: { b: 2 }, c: 3 },
+ "replace deleted");
+}
+
+exports["test patch delete with void"] = function(assert) {
+ var hash = { a: 1, b: 2 }
+
+ assert.deepEqual(patch(hash, { a: void(0) }), { b: 2 },
+ "void(0) removes property")
+}
+
+
+exports["test patch addition"] = function(assert) {
+ var hash = { a: 1, b: 2 }
+
+ assert.deepEqual(patch(hash, { c: 3 }), { a: 1, b: 2, c: 3 },
+ "new properties are added")
+}
+
+exports["test patch addition"] = function(assert) {
+ var hash = { a: 1, b: 2 }
+
+ assert.deepEqual(patch(hash, { c: 3 }), { a: 1, b: 2, c: 3 },
+ "new properties are added")
+}
+
+exports["test hash on itself"] = function(assert) {
+ var hash = { a: 1, b: 2 }
+
+ assert.deepEqual(patch(hash, hash), hash,
+ "applying hash to itself returns hash itself")
+}
+
+exports["test patch with empty delta"] = function(assert) {
+ var hash = { a: 1, b: 2 }
+
+ assert.deepEqual(patch(hash, {}), hash,
+ "applying empty delta results in no changes")
+}
+
+exports["test patch nested data"] = function(assert) {
+ assert.deepEqual(patch({ a: { b: 1 }, c: { d: 2 } },
+ { a: { b: null, e: 3 }, c: { d: 4 } }),
+ { a: { e: 3 }, c: { d: 4 } },
+ "nested structures can also be patched")
+}
diff --git a/components/jetpack/diffpatcher/test/tap.js b/components/jetpack/diffpatcher/test/tap.js
new file mode 100644
index 000000000..e550b82f5
--- /dev/null
+++ b/components/jetpack/diffpatcher/test/tap.js
@@ -0,0 +1,3 @@
+"use strict";
+
+require("retape")(require("./index"))
diff --git a/components/jetpack/framescript/FrameScriptManager.jsm b/components/jetpack/framescript/FrameScriptManager.jsm
new file mode 100644
index 000000000..1ce6ceb07
--- /dev/null
+++ b/components/jetpack/framescript/FrameScriptManager.jsm
@@ -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/. */
+"use strict";
+
+const globalMM = Components.classes["@mozilla.org/globalmessagemanager;1"].
+ getService(Components.interfaces.nsIMessageListenerManager);
+
+// Load frame scripts from the same dir as this module.
+// Since this JSM will be loaded using require(), PATH will be
+// overridden while running tests, just like any other module.
+const PATH = __URI__.replace('framescript/FrameScriptManager.jsm', '');
+
+// Builds a unique loader ID for this runtime. We prefix with the SDK path so
+// overriden versions of the SDK don't conflict
+var LOADER_ID = 0;
+this.getNewLoaderID = () => {
+ return PATH + ":" + LOADER_ID++;
+}
+
+const frame_script = function(contentFrame, PATH) {
+ let { registerContentFrame } = Components.utils.import(PATH + 'framescript/content.jsm', {});
+ registerContentFrame(contentFrame);
+}
+globalMM.loadFrameScript("data:,(" + frame_script.toString() + ")(this, " + JSON.stringify(PATH) + ");", true);
+
+this.EXPORTED_SYMBOLS = ['getNewLoaderID'];
diff --git a/components/jetpack/framescript/content.jsm b/components/jetpack/framescript/content.jsm
new file mode 100644
index 000000000..eaee26be3
--- /dev/null
+++ b/components/jetpack/framescript/content.jsm
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { utils: Cu, classes: Cc, interfaces: Ci } = Components;
+const { Services } = Cu.import('resource://gre/modules/Services.jsm');
+
+const cpmm = Cc['@mozilla.org/childprocessmessagemanager;1'].
+ getService(Ci.nsISyncMessageSender);
+
+this.EXPORTED_SYMBOLS = ["registerContentFrame"];
+
+// This may be an overriden version of the SDK so use the PATH as a key for the
+// initial messages before we have a loaderID.
+const PATH = __URI__.replace('framescript/content.jsm', '');
+
+const { Loader } = Cu.import(PATH + 'toolkit/loader.js', {});
+
+// one Loader instance per addon (per @loader/options to be precise)
+var addons = new Map();
+
+// Tell the parent that a new process is ready
+cpmm.sendAsyncMessage('sdk/remote/process/start', {
+ modulePath: PATH
+});
+
+// Load a child process module loader with the given loader options
+cpmm.addMessageListener('sdk/remote/process/load', ({ data: { modulePath, loaderID, options, reason } }) => {
+ if (modulePath != PATH)
+ return;
+
+ // During startup races can mean we get a second load message
+ if (addons.has(loaderID))
+ return;
+
+ options.waiveInterposition = true;
+
+ let loader = Loader.Loader(options);
+ let addon = {
+ loader,
+ require: Loader.Require(loader, { id: 'LoaderHelper' }),
+ }
+ addons.set(loaderID, addon);
+
+ cpmm.sendAsyncMessage('sdk/remote/process/attach', {
+ loaderID,
+ processID: Services.appinfo.processID,
+ isRemote: Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+ });
+
+ addon.child = addon.require('sdk/remote/child');
+
+ for (let contentFrame of frames.values())
+ addon.child.registerContentFrame(contentFrame);
+});
+
+// Unload a child process loader
+cpmm.addMessageListener('sdk/remote/process/unload', ({ data: { loaderID, reason } }) => {
+ if (!addons.has(loaderID))
+ return;
+
+ let addon = addons.get(loaderID);
+ Loader.unload(addon.loader, reason);
+
+ // We want to drop the reference to the loader but never allow creating a new
+ // loader with the same ID
+ addons.set(loaderID, {});
+})
+
+
+var frames = new Set();
+
+this.registerContentFrame = contentFrame => {
+ contentFrame.addEventListener("unload", () => {
+ unregisterContentFrame(contentFrame);
+ }, false);
+
+ frames.add(contentFrame);
+
+ for (let addon of addons.values()) {
+ if ("child" in addon)
+ addon.child.registerContentFrame(contentFrame);
+ }
+};
+
+function unregisterContentFrame(contentFrame) {
+ frames.delete(contentFrame);
+
+ for (let addon of addons.values()) {
+ if ("child" in addon)
+ addon.child.unregisterContentFrame(contentFrame);
+ }
+}
diff --git a/components/jetpack/framescript/context-menu.js b/components/jetpack/framescript/context-menu.js
new file mode 100644
index 000000000..3915b7cd8
--- /dev/null
+++ b/components/jetpack/framescript/context-menu.js
@@ -0,0 +1,215 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { query, constant, cache } = require("sdk/lang/functional");
+const { pairs, each, map, object } = require("sdk/util/sequence");
+const { nodeToMessageManager } = require("./util");
+
+// Decorator function that takes `f` function and returns one that attempts
+// to run `f` with given arguments. In case of exception error is logged
+// and `fallback` is returned instead.
+const Try = (fn, fallback=null) => (...args) => {
+ try {
+ return fn(...args);
+ } catch(error) {
+ console.error(error);
+ return fallback;
+ }
+};
+
+// Decorator funciton that takes `f` function and returns one that returns
+// JSON cloned result of whatever `f` returns for given arguments.
+const JSONReturn = f => (...args) => JSON.parse(JSON.stringify(f(...args)));
+
+const Null = constant(null);
+
+// Table of readers mapped to field names they're going to be reading.
+const readers = Object.create(null);
+// Read function takes "contextmenu" event target `node` and returns table of
+// read field names mapped to appropriate values. Read uses above defined read
+// table to read data for all registered readers.
+const read = node =>
+ object(...map(([id, read]) => [id, read(node, id)], pairs(readers)));
+
+// Table of built-in readers, each takes a descriptor and returns a reader:
+// descriptor -> node -> JSON
+const parsers = Object.create(null)
+// Function takes a descriptor of the remotely defined reader and parsese it
+// to construct a local reader that's going to read out data from context menu
+// target.
+const parse = descriptor => {
+ const parser = parsers[descriptor.category];
+ if (!parser) {
+ console.error("Unknown reader descriptor was received", descriptor, `"${descriptor.category}"`);
+ return Null
+ }
+ return Try(parser(descriptor));
+}
+
+// TODO: Test how chrome's mediaType behaves to try and match it's behavior.
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const SVG_NS = "http://www.w3.org/2000/svg";
+
+// 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.
+// Source: https://github.com/mozilla/gecko-dev/blob/28c2fca3753c5371643843fc2f2f205146b083b7/browser/base/content/nsContextMenu.js#L632-L637
+const isVideoLoadingAudio = node =>
+ node.readyState >= node.HAVE_METADATA &&
+ (node.videoWidth == 0 || node.videoHeight == 0)
+
+const isVideo = node =>
+ node instanceof node.ownerDocument.defaultView.HTMLVideoElement &&
+ !isVideoLoadingAudio(node);
+
+const isAudio = node => {
+ const {HTMLVideoElement, HTMLAudioElement} = node.ownerDocument.defaultView;
+ return node instanceof HTMLAudioElement ? true :
+ node instanceof HTMLVideoElement ? isVideoLoadingAudio(node) :
+ false;
+};
+
+const isImage = ({namespaceURI, localName}) =>
+ namespaceURI === HTML_NS && localName === "img" ? true :
+ namespaceURI === XUL_NS && localName === "image" ? true :
+ namespaceURI === SVG_NS && localName === "image" ? true :
+ false;
+
+parsers["reader/MediaType()"] = constant(node =>
+ isImage(node) ? "image" :
+ isAudio(node) ? "audio" :
+ isVideo(node) ? "video" :
+ null);
+
+
+const readLink = node =>
+ node.namespaceURI === HTML_NS && node.localName === "a" ? node.href :
+ readLink(node.parentNode);
+
+parsers["reader/LinkURL()"] = constant(node =>
+ node.matches("a, a *") ? readLink(node) : null);
+
+// Reader that reads out `true` if "contextmenu" `event.target` matches
+// `descriptor.selector` and `false` if it does not.
+parsers["reader/SelectorMatch()"] = ({selector}) =>
+ node => node.matches(selector);
+
+// Accessing `selectionStart` and `selectionEnd` properties on non
+// editable input nodes throw exceptions, there for we need this util
+// function to guard us against them.
+const getInputSelection = node => {
+ try {
+ if ("selectionStart" in node && "selectionEnd" in node) {
+ const {selectionStart, selectionEnd} = node;
+ return {selectionStart, selectionEnd}
+ }
+ }
+ catch(_) {}
+
+ return null;
+}
+
+// Selection reader does not really cares about descriptor so it is
+// a constant function returning selection reader. Selection reader
+// returns string of the selected text or `null` if there is no selection.
+parsers["reader/Selection()"] = constant(node => {
+ const selection = node.ownerDocument.getSelection();
+ if (!selection.isCollapsed) {
+ return selection.toString();
+ }
+ // If target node is editable (text, input, textarea, etc..) document does
+ // not really handles selections there. There for we fallback to checking
+ // `selectionStart` `selectionEnd` properties and if they are present we
+ // extract selections manually from the `node.value`.
+ else {
+ const selection = getInputSelection(node);
+ const isSelected = selection &&
+ Number.isInteger(selection.selectionStart) &&
+ Number.isInteger(selection.selectionEnd) &&
+ selection.selectionStart !== selection.selectionEnd;
+ return isSelected ? node.value.substring(selection.selectionStart,
+ selection.selectionEnd) :
+ null;
+ }
+});
+
+// Query reader just reads out properties from the node, so we just use `query`
+// utility function.
+parsers["reader/Query()"] = ({path}) => JSONReturn(query(path));
+// Attribute reader just reads attribute of the event target node.
+parsers["reader/Attribute()"] = ({name}) => node => node.getAttribute(name);
+
+// Extractor reader defines generates a reader out of serialized function, who's
+// return value is JSON cloned. Note: We do know source will evaluate to function
+// as that's what we serialized on the other end, it's also ok if generated function
+// is going to throw as registered readers are wrapped in try catch to avoid breakting
+// unrelated readers.
+parsers["reader/Extractor()"] = ({source}) =>
+ JSONReturn(new Function("return (" + source + ")")());
+
+// If the context-menu target node or any of its ancestors is one of these,
+// Firefox uses a tailored context menu, and so the page context doesn't apply.
+// There for `reader/isPage()` will read `false` in that case otherwise it's going
+// to read `true`.
+const nonPageElements = ["a", "applet", "area", "button", "canvas", "object",
+ "embed", "img", "input", "map", "video", "audio", "menu",
+ "option", "select", "textarea", "[contenteditable=true]"];
+const nonPageSelector = nonPageElements.
+ concat(nonPageElements.map(tag => `${tag} *`)).
+ join(", ");
+
+// Note: isPageContext implementation could have actually used SelectorMatch reader,
+// but old implementation was also checked for collapsed selection there for to keep
+// the behavior same we end up implementing a new reader.
+parsers["reader/isPage()"] = constant(node =>
+ node.ownerDocument.defaultView.getSelection().isCollapsed &&
+ !node.matches(nonPageSelector));
+
+// Reads `true` if node is in an iframe otherwise returns true.
+parsers["reader/isFrame()"] = constant(node =>
+ !!node.ownerDocument.defaultView.frameElement);
+
+parsers["reader/isEditable()"] = constant(node => {
+ const selection = getInputSelection(node);
+ return selection ? !node.readOnly && !node.disabled : node.isContentEditable;
+});
+
+
+// TODO: Add some reader to read out tab id.
+
+const onReadersUpdate = message => {
+ each(([id, descriptor]) => {
+ if (descriptor) {
+ readers[id] = parse(descriptor);
+ }
+ else {
+ delete readers[id];
+ }
+ }, pairs(message.data));
+};
+exports.onReadersUpdate = onReadersUpdate;
+
+
+const onContextMenu = event => {
+ if (!event.defaultPrevented) {
+ const manager = nodeToMessageManager(event.target);
+ manager.sendSyncMessage("sdk/context-menu/read", read(event.target), readers);
+ }
+};
+exports.onContextMenu = onContextMenu;
+
+
+const onContentFrame = (frame) => {
+ // Listen for contextmenu events in on this frame.
+ frame.addEventListener("contextmenu", onContextMenu);
+ // Listen to registered reader changes and update registry.
+ frame.addMessageListener("sdk/context-menu/readers", onReadersUpdate);
+
+ // Request table of readers (if this is loaded in a new process some table
+ // changes may be missed, this is way to sync up).
+ frame.sendAsyncMessage("sdk/context-menu/readers?");
+};
+exports.onContentFrame = onContentFrame;
diff --git a/components/jetpack/framescript/manager.js b/components/jetpack/framescript/manager.js
new file mode 100644
index 000000000..1f261e1fa
--- /dev/null
+++ b/components/jetpack/framescript/manager.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const mime = "application/javascript";
+const requireURI = module.uri.replace("framescript/manager.js",
+ "toolkit/require.js");
+
+const requireLoadURI = `data:${mime},this["Components"].utils.import("${requireURI}")`
+
+// Loads module with given `id` into given `messageManager` via shared module loader. If `init`
+// string is passed, will call module export with that name and pass frame script environment
+// of the `messageManager` into it. Since module will load only once per process (which is
+// once for chrome proces & second for content process) it is useful to have an init function
+// to setup event listeners on each content frame.
+const loadModule = (messageManager, id, allowDelayed, init) => {
+ const moduleLoadURI = `${requireLoadURI}.require("${id}")`
+ const uri = init ? `${moduleLoadURI}.${init}(this)` : moduleLoadURI;
+ messageManager.loadFrameScript(uri, allowDelayed);
+};
+exports.loadModule = loadModule;
diff --git a/components/jetpack/framescript/util.js b/components/jetpack/framescript/util.js
new file mode 100644
index 000000000..fb6834608
--- /dev/null
+++ b/components/jetpack/framescript/util.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+
+const { Ci } = require("chrome");
+
+const windowToMessageManager = window =>
+ window.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDocShell).
+ sameTypeRootTreeItem.
+ QueryInterface(Ci.nsIDocShell).
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIContentFrameMessageManager);
+exports.windowToMessageManager = windowToMessageManager;
+
+const nodeToMessageManager = node =>
+ windowToMessageManager(node.ownerDocument.defaultView);
+exports.nodeToMessageManager = nodeToMessageManager;
diff --git a/components/jetpack/index.js b/components/jetpack/index.js
new file mode 100644
index 000000000..e0032240a
--- /dev/null
+++ b/components/jetpack/index.js
@@ -0,0 +1,3 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/components/jetpack/jetpack-id/index.js b/components/jetpack/jetpack-id/index.js
new file mode 100644
index 000000000..6c1493f1d
--- /dev/null
+++ b/components/jetpack/jetpack-id/index.js
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Takes parsed `package.json` manifest and returns
+ * valid add-on id for it.
+ */
+function getID(manifest) {
+ manifest = manifest || {};
+
+ if (manifest.id) {
+
+ if (typeof manifest.id !== "string") {
+ return null;
+ }
+
+ // If manifest.id is already valid (as domain or GUID), use it
+ if (isValidAOMName(manifest.id)) {
+ return manifest.id;
+ }
+ // Otherwise, this ID is invalid so return `null`
+ return null;
+ }
+
+ // If no `id` defined, turn `name` into a domain ID,
+ // as we transition to `name` being an id, similar to node/npm, but
+ // append a '@' to make it compatible with Firefox requirements
+ if (manifest.name) {
+
+ if (typeof manifest.name !== "string") {
+ return null;
+ }
+
+ var modifiedName = "@" + manifest.name;
+ return isValidAOMName(modifiedName) ? modifiedName : null;
+ }
+
+ // If no `id` or `name` property, return null as this manifest
+ // is invalid
+ return null;
+}
+
+module.exports = getID;
+
+/**
+ * Regex taken from XPIProvider.jsm in the Addon Manager to validate proper
+ * IDs that are able to be used.
+ * http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm#209
+ */
+function isValidAOMName (s) {
+ return /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i.test(s || "");
+}
diff --git a/components/jetpack/jetpack-id/package.json b/components/jetpack/jetpack-id/package.json
new file mode 100644
index 000000000..62a1c73ba
--- /dev/null
+++ b/components/jetpack/jetpack-id/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "jetpack-id",
+ "version": "1.0.0",
+ "description": "Creates an ID from a Firefox Jetpack manifest",
+ "main": "index.js",
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/jsantell/jetpack-id"
+ },
+ "author": {
+ "name": "Jordan Santell",
+ "url": "http://github.com/jsantell"
+ },
+ "license": "MPL-2.0",
+ "scripts": {
+ "test": "./node_modules/.bin/mocha --reporter spec --ui bdd"
+ },
+ "keywords": [
+ "jetpack",
+ "addon",
+ "mozilla",
+ "firefox"
+ ],
+ "devDependencies": {
+ "mocha": "*",
+ "chai": "*"
+ }
+}
diff --git a/components/jetpack/method/.travis.yml b/components/jetpack/method/.travis.yml
new file mode 100644
index 000000000..780731a47
--- /dev/null
+++ b/components/jetpack/method/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+ - 0.4
+ - 0.5
+ - 0.6
diff --git a/components/jetpack/method/History.md b/components/jetpack/method/History.md
new file mode 100644
index 000000000..95258c45f
--- /dev/null
+++ b/components/jetpack/method/History.md
@@ -0,0 +1,55 @@
+# Changes
+
+## 1.0.2 / 2012-12-26
+
+ - Delegate to polymorphic methods from `.define` and `.implement` so, they
+ can be overidden.
+
+## 1.0.1 / 2012-11-11
+
+ - Fix issues with different `Error` types as they all inherit from
+ `Error`.
+
+## 1.0.0 / 2012-11-09
+
+ - Add browser test integration.
+ - Fix cross-browser incompatibilities & test failures.
+ - Add support for host objects.
+ - Add optional `hint` argument for method to ease debugging.
+ - Remove default implementation at definition time.
+
+## 0.1.1 / 2012-10-15
+
+ - Fix regression causing custom type implementation to be stored on objects.
+
+## 0.1.0 / 2012-10-15
+
+ - Remove dependency on name module.
+ - Implement fallback for engines that do not support ES5.
+ - Add support for built-in type extensions without extending their prototypes.
+ - Make API for default definitions more intuitive.
+ Skipping type argument now defines default:
+
+ isFoo.define(function(value) {
+ return false
+ })
+
+ - Make exposed `define` and `implement` polymorphic.
+ - Removed dev dependency on swank-js.
+ - Primitive types `string, number, boolean` no longer inherit method
+ implementations from `Object`.
+
+## 0.0.3 / 2012-07-17
+
+ - Remove module boilerplate
+
+## 0.0.2 / 2012-06-26
+
+ - Name changes to make it less conflicting with other library conventions.
+ - Expose function version of `define` & `implement` methods.
+ - Expose `Null` and `Undefined` object holding implementations for an
+ associated types.
+
+## 0.0.1 / 2012-06-25
+
+ - Initial release
diff --git a/components/jetpack/method/License.md b/components/jetpack/method/License.md
new file mode 100644
index 000000000..ed76489a3
--- /dev/null
+++ b/components/jetpack/method/License.md
@@ -0,0 +1,18 @@
+Copyright 2012 Irakli Gozalishvili. All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/components/jetpack/method/Readme.md b/components/jetpack/method/Readme.md
new file mode 100644
index 000000000..9584c9160
--- /dev/null
+++ b/components/jetpack/method/Readme.md
@@ -0,0 +1,117 @@
+# method
+
+[![Build Status](https://secure.travis-ci.org/Gozala/method.png)](http://travis-ci.org/Gozala/method)
+
+Library provides an API for defining polymorphic methods that dispatch on the
+first argument type. This provides a powerful way for decouple abstraction
+interface definition from an actual implementation per type, without risks
+of interference with other libraries.
+
+### Motivation
+
+ - Provide a high-performance, dynamic polymorphism construct as an
+ alternative to existing object methods that does not provides any
+ mechanics for guarding against name conflicts.
+ - Allow independent extension of types, and implementations of methods
+ on types, by different parties.
+
+## Install
+
+ npm install method
+
+## Use
+
+```js
+var method = require("method")
+
+// Define `isWatchable` method that can be implemented for any type.
+var isWatchable = method("isWatchable")
+
+// If you call it on any object it will
+// throw as nothing implements that method yet.
+//isWatchable({}) // => Exception: method is not implemented
+
+// If you define private method on `Object.prototype`
+// all objects will inherit it.
+Object.prototype[isWatchable] = function() {
+ return false;
+}
+
+isWatchable({}) // => false
+
+
+// Although `isWatchable` property above will be enumerable and there for
+// may damage some assumbtions made by other libraries. There for it"s
+// recomended to use built-in helpers methods that will define extension
+// without breaking assumbtions made by other libraries:
+
+isWatchable.define(Object, function() { return false })
+
+
+// There are primitive types in JS that won"t inherit methods from Object:
+isWatchable(null) // => Exception: method is not implemented
+
+// One could either implement methods for such types:
+isWatchable.define(null, function() { return false })
+isWatchable.define(undefined, function() { return false })
+
+// Or simply define default implementation:
+isWatchable.define(function() { return false })
+
+// Alternatively default implementation may be provided at creation:
+isWatchable = method(function() { return false })
+
+// Method dispatches on an first argument type. That allows us to create
+// new types with an alternative implementations:
+function Watchable() {}
+isWatchable.define(Watchable, function() { return true })
+
+// This will make all `Watchable` instances watchable!
+isWatchable(new Watchable()) // => true
+
+// Arbitrary objects can also be extended to implement given method. For example
+// any object can simply made watchable:
+function watchable(object) {
+ return isWatchable.implement(objct, function() { return true })
+}
+
+isWatchable(watchable({})) // => true
+
+// Full protocols can be defined with such methods:
+var observers = "observers@" + module.filename
+var watchers = method("watchers")
+var watch = method("watch")
+var unwatch = method("unwatch")
+
+watchers.define(Watchable, function(target) {
+ return target[observers] || (target[observers] = [])
+})
+
+watch.define(Watchable, function(target, watcher) {
+ var observers = watchers(target)
+ if (observers.indexOf(watcher) < 0) observers.push(watcher)
+ return target
+})
+unwatch.define(Watchable, function(target, watcher) {
+ var observers = watchers(target)
+ var index = observers.indexOf(watcher)
+ if (observers.indexOf(watcher) >= 0) observers.unshift(watcher)
+ return target
+})
+
+// Define type Port that inherits form Watchable
+
+function Port() {}
+Port.prototype = Object.create(Watchable.prototype)
+
+var emit = method("emit")
+emit.define(Port, function(port, message) {
+ watchers(port).slice().forEach(function(watcher) {
+ watcher(message)
+ })
+})
+
+var p = new Port()
+watch(p, console.log)
+emit(p, "hello world") // => info: "hello world"
+```
diff --git a/components/jetpack/method/core.js b/components/jetpack/method/core.js
new file mode 100644
index 000000000..a6a5261e6
--- /dev/null
+++ b/components/jetpack/method/core.js
@@ -0,0 +1,225 @@
+"use strict";
+
+var defineProperty = Object.defineProperty || function(object, name, property) {
+ object[name] = property.value
+ return object
+}
+
+// Shortcut for `Object.prototype.toString` for faster access.
+var typefy = Object.prototype.toString
+
+// Map to for jumping from typeof(value) to associated type prefix used
+// as a hash in the map of builtin implementations.
+var types = { "function": "Object", "object": "Object" }
+
+// Array is used to save method implementations for the host objects in order
+// to avoid extending them with non-primitive values that could cause leaks.
+var host = []
+// Hash map is used to save method implementations for builtin types in order
+// to avoid extending their prototypes. This also allows to share method
+// implementations for types across diff contexts / frames / compartments.
+var builtin = {}
+
+function Primitive() {}
+function ObjectType() {}
+ObjectType.prototype = new Primitive()
+function ErrorType() {}
+ErrorType.prototype = new ObjectType()
+
+var Default = builtin.Default = Primitive.prototype
+var Null = builtin.Null = new Primitive()
+var Void = builtin.Void = new Primitive()
+builtin.String = new Primitive()
+builtin.Number = new Primitive()
+builtin.Boolean = new Primitive()
+
+builtin.Object = ObjectType.prototype
+builtin.Error = ErrorType.prototype
+
+builtin.EvalError = new ErrorType()
+builtin.InternalError = new ErrorType()
+builtin.RangeError = new ErrorType()
+builtin.ReferenceError = new ErrorType()
+builtin.StopIteration = new ErrorType()
+builtin.SyntaxError = new ErrorType()
+builtin.TypeError = new ErrorType()
+builtin.URIError = new ErrorType()
+
+
+function Method(hint) {
+ /**
+ Private Method is a callable private name that dispatches on the first
+ arguments same named Method:
+
+ method(object, ...rest) => object[method](...rest)
+
+ Optionally hint string may be provided that will be used in generated names
+ to ease debugging.
+
+ ## Example
+
+ var foo = Method()
+
+ // Implementation for any types
+ foo.define(function(value, arg1, arg2) {
+ // ...
+ })
+
+ // Implementation for a specific type
+ foo.define(BarType, function(bar, arg1, arg2) {
+ // ...
+ })
+ **/
+
+ // Create an internal unique name if `hint` is provided it is used to
+ // prefix name to ease debugging.
+ var name = (hint || "") + "#" + Math.random().toString(32).substr(2)
+
+ function dispatch(value) {
+ // Method dispatches on type of the first argument.
+ // If first argument is `null` or `void` associated implementation is
+ // looked up in the `builtin` hash where implementations for built-ins
+ // are stored.
+ var type = null
+ var method = value === null ? Null[name] :
+ value === void(0) ? Void[name] :
+ // Otherwise attempt to use method with a generated private
+ // `name` that is supposedly in the prototype chain of the
+ // `target`.
+ value[name] ||
+ // Otherwise assume it's one of the built-in type instances,
+ // in which case implementation is stored in a `builtin` hash.
+ // Attempt to find a implementation for the given built-in
+ // via constructor name and method name.
+ ((type = builtin[(value.constructor || "").name]) &&
+ type[name]) ||
+ // Otherwise assume it's a host object. For host objects
+ // actual method implementations are stored in the `host`
+ // array and only index for the implementation is stored
+ // in the host object's prototype chain. This avoids memory
+ // leaks that otherwise could happen when saving JS objects
+ // on host object.
+ host[value["!" + name] || void(0)] ||
+ // Otherwise attempt to lookup implementation for builtins by
+ // a type of the value. This basically makes sure that all
+ // non primitive values will delegate to an `Object`.
+ ((type = builtin[types[typeof(value)]]) && type[name])
+
+
+ // If method implementation for the type is still not found then
+ // just fallback for default implementation.
+ method = method || Default[name]
+
+
+ // If implementation is still not found (which also means there is no
+ // default) just throw an error with a descriptive message.
+ if (!method) throw TypeError("Type does not implements method: " + name)
+
+ // If implementation was found then just delegate.
+ return method.apply(method, arguments)
+ }
+
+ // Make `toString` of the dispatch return a private name, this enables
+ // method definition without sugar:
+ //
+ // var method = Method()
+ // object[method] = function() { /***/ }
+ dispatch.toString = function toString() { return name }
+
+ // Copy utility methods for convenient API.
+ dispatch.implement = implementMethod
+ dispatch.define = defineMethod
+
+ return dispatch
+}
+
+// Create method shortcuts form functions.
+var defineMethod = function defineMethod(Type, lambda) {
+ return define(this, Type, lambda)
+}
+var implementMethod = function implementMethod(object, lambda) {
+ return implement(this, object, lambda)
+}
+
+// Define `implement` and `define` polymorphic methods to allow definitions
+// and implementations through them.
+var implement = Method("implement")
+var define = Method("define")
+
+
+function _implement(method, object, lambda) {
+ /**
+ Implements `Method` for the given `object` with a provided `implementation`.
+ Calling `Method` with `object` as a first argument will dispatch on provided
+ implementation.
+ **/
+ return defineProperty(object, method.toString(), {
+ enumerable: false,
+ configurable: false,
+ writable: false,
+ value: lambda
+ })
+}
+
+function _define(method, Type, lambda) {
+ /**
+ Defines `Method` for the given `Type` with a provided `implementation`.
+ Calling `Method` with a first argument of this `Type` will dispatch on
+ provided `implementation`. If `Type` is a `Method` default implementation
+ is defined. If `Type` is a `null` or `undefined` `Method` is implemented
+ for that value type.
+ **/
+
+ // Attempt to guess a type via `Object.prototype.toString.call` hack.
+ var type = Type && typefy.call(Type.prototype)
+
+ // If only two arguments are passed then `Type` is actually an implementation
+ // for a default type.
+ if (!lambda) Default[method] = Type
+ // If `Type` is `null` or `void` store implementation accordingly.
+ else if (Type === null) Null[method] = lambda
+ else if (Type === void(0)) Void[method] = lambda
+ // If `type` hack indicates built-in type and type has a name us it to
+ // store a implementation into associated hash. If hash for this type does
+ // not exists yet create one.
+ else if (type !== "[object Object]" && Type.name) {
+ var Bulitin = builtin[Type.name] || (builtin[Type.name] = new ObjectType())
+ Bulitin[method] = lambda
+ }
+ // If `type` hack indicates an object, that may be either object or any
+ // JS defined "Class". If name of the constructor is `Object`, assume it's
+ // built-in `Object` and store implementation accordingly.
+ else if (Type.name === "Object")
+ builtin.Object[method] = lambda
+ // Host objects are pain!!! Every browser does some crazy stuff for them
+ // So far all browser seem to not implement `call` method for host object
+ // constructors. If that is a case here, assume it's a host object and
+ // store implementation in a `host` array and store `index` in the array
+ // in a `Type.prototype` itself. This avoids memory leaks that could be
+ // caused by storing JS objects on a host objects.
+ else if (Type.call === void(0)) {
+ var index = host.indexOf(lambda)
+ if (index < 0) index = host.push(lambda) - 1
+ // Prefix private name with `!` so it can be dispatched from the method
+ // without type checks.
+ implement("!" + method, Type.prototype, index)
+ }
+ // If Got that far `Type` is user defined JS `Class`. Define private name
+ // as hidden property on it's prototype.
+ else
+ implement(method, Type.prototype, lambda)
+}
+
+// And provided implementations for a polymorphic equivalents.
+_define(define, _define)
+_define(implement, _implement)
+
+// Define exports on `Method` as it's only thing being exported.
+Method.implement = implement
+Method.define = define
+Method.Method = Method
+Method.method = Method
+Method.builtin = builtin
+Method.host = host
+
+module.exports = Method
diff --git a/components/jetpack/method/package.json b/components/jetpack/method/package.json
new file mode 100644
index 000000000..7bb004e28
--- /dev/null
+++ b/components/jetpack/method/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "method",
+ "id": "method",
+ "version": "1.0.2",
+ "description": "Functional polymorphic method dispatch",
+ "keywords": [
+ "method",
+ "dispatch",
+ "protocol",
+ "polymorphism",
+ "type dispatch"
+ ],
+ "author": "Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com)",
+ "homepage": "https://github.com/Gozala/method",
+ "main": "./core.js",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Gozala/method.git",
+ "web": "https://github.com/Gozala/method"
+ },
+ "bugs": {
+ "url": "http://github.com/Gozala/method/issues/"
+ },
+ "devDependencies": {
+ "test": "~0.x.0",
+ "repl-utils": "~2.0.1",
+ "phantomify": "~0.1.0"
+ },
+ "scripts": {
+ "test": "npm run test-node && npm run test-browser",
+ "test-browser": "node ./node_modules/phantomify/bin/cmd.js ./test/browser.js",
+ "test-node": "node ./test/common.js",
+ "repl": "node node_modules/repl-utils"
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "https://github.com/Gozala/method/License.md"
+ }
+ ]
+}
diff --git a/components/jetpack/method/test/browser.js b/components/jetpack/method/test/browser.js
new file mode 100644
index 000000000..7c8e6cd52
--- /dev/null
+++ b/components/jetpack/method/test/browser.js
@@ -0,0 +1,20 @@
+"use strict";
+
+exports["test common"] = require("./common")
+
+var Method = require("../core")
+
+exports["test host objects"] = function(assert) {
+ var isElement = Method("is-element")
+ isElement.define(function() { return false })
+
+ isElement.define(Element, function() { return true })
+
+ assert.notDeepEqual(typeof(Element.prototype[isElement]), "number",
+ "Host object's prototype is extended with a number value")
+
+ assert.ok(!isElement({}), "object is not an Element")
+ assert.ok(document.createElement("div"), "Element is an element")
+}
+
+require("test").run(exports)
diff --git a/components/jetpack/method/test/common.js b/components/jetpack/method/test/common.js
new file mode 100644
index 000000000..0418c3a23
--- /dev/null
+++ b/components/jetpack/method/test/common.js
@@ -0,0 +1,272 @@
+"use strict";
+
+var Method = require("../core")
+
+function type(value) {
+ return Object.prototype.toString.call(value).
+ split(" ").
+ pop().
+ split("]").
+ shift().
+ toLowerCase()
+}
+
+var values = [
+ null, // 0
+ undefined, // 1
+ Infinity, // 2
+ NaN, // 3
+ 5, // 4
+ {}, // 5
+ Object.create({}), // 6
+ Object.create(null), // 7
+ [], // 8
+ /foo/, // 9
+ new Date(), // 10
+ Function, // 11
+ function() {}, // 12
+ true, // 13
+ false, // 14
+ "string" // 15
+]
+
+function True() { return true }
+function False() { return false }
+
+var trues = values.map(True)
+var falses = values.map(False)
+
+exports["test throws if not implemented"] = function(assert) {
+ var method = Method("nope")
+
+ assert.throws(function() {
+ method({})
+ }, /not implement/i, "method throws if not implemented")
+
+ assert.throws(function() {
+ method(null)
+ }, /not implement/i, "method throws on null")
+}
+
+exports["test all types inherit from default"] = function(assert) {
+ var isImplemented = Method("isImplemented")
+ isImplemented.define(function() { return true })
+
+ values.forEach(function(value) {
+ assert.ok(isImplemented(value),
+ type(value) + " inherits deafult implementation")
+ })
+}
+
+exports["test default can be implemented later"] = function(assert) {
+ var isImplemented = Method("isImplemented")
+ isImplemented.define(function() {
+ return true
+ })
+
+ values.forEach(function(value) {
+ assert.ok(isImplemented(value),
+ type(value) + " inherits deafult implementation")
+ })
+}
+
+exports["test dispatch not-implemented"] = function(assert) {
+ var isDefault = Method("isDefault")
+ values.forEach(function(value) {
+ assert.throws(function() {
+ isDefault(value)
+ }, /not implement/, type(value) + " throws if not implemented")
+ })
+}
+
+exports["test dispatch default"] = function(assert) {
+ var isDefault = Method("isDefault")
+
+ // Implement default
+ isDefault.define(True)
+ assert.deepEqual(values.map(isDefault), trues,
+ "all implementation inherit from default")
+
+}
+
+exports["test dispatch null"] = function(assert) {
+ var isNull = Method("isNull")
+
+ // Implement default
+ isNull.define(False)
+ isNull.define(null, True)
+ assert.deepEqual(values.map(isNull),
+ [ true ].
+ concat(falses.slice(1)),
+ "only null gets methods defined for null")
+}
+
+exports["test dispatch undefined"] = function(assert) {
+ var isUndefined = Method("isUndefined")
+
+ // Implement default
+ isUndefined.define(False)
+ isUndefined.define(undefined, True)
+ assert.deepEqual(values.map(isUndefined),
+ [ false, true ].
+ concat(falses.slice(2)),
+ "only undefined gets methods defined for undefined")
+}
+
+exports["test dispatch object"] = function(assert) {
+ var isObject = Method("isObject")
+
+ // Implement default
+ isObject.define(False)
+ isObject.define(Object, True)
+ assert.deepEqual(values.map(isObject),
+ [ false, false, false, false, false ].
+ concat(trues.slice(5, 13)).
+ concat([false, false, false]),
+ "all values except primitives inherit Object methods")
+
+}
+
+exports["test dispatch number"] = function(assert) {
+ var isNumber = Method("isNumber")
+ isNumber.define(False)
+ isNumber.define(Number, True)
+
+ assert.deepEqual(values.map(isNumber),
+ falses.slice(0, 2).
+ concat(true, true, true).
+ concat(falses.slice(5)),
+ "all numbers inherit from Number method")
+}
+
+exports["test dispatch string"] = function(assert) {
+ var isString = Method("isString")
+ isString.define(False)
+ isString.define(String, True)
+
+ assert.deepEqual(values.map(isString),
+ falses.slice(0, 15).
+ concat(true),
+ "all strings inherit from String method")
+}
+
+exports["test dispatch function"] = function(assert) {
+ var isFunction = Method("isFunction")
+ isFunction.define(False)
+ isFunction.define(Function, True)
+
+ assert.deepEqual(values.map(isFunction),
+ falses.slice(0, 11).
+ concat(true, true).
+ concat(falses.slice(13)),
+ "all functions inherit from Function method")
+}
+
+exports["test dispatch date"] = function(assert) {
+ var isDate = Method("isDate")
+ isDate.define(False)
+ isDate.define(Date, True)
+
+ assert.deepEqual(values.map(isDate),
+ falses.slice(0, 10).
+ concat(true).
+ concat(falses.slice(11)),
+ "all dates inherit from Date method")
+}
+
+exports["test dispatch RegExp"] = function(assert) {
+ var isRegExp = Method("isRegExp")
+ isRegExp.define(False)
+ isRegExp.define(RegExp, True)
+
+ assert.deepEqual(values.map(isRegExp),
+ falses.slice(0, 9).
+ concat(true).
+ concat(falses.slice(10)),
+ "all regexps inherit from RegExp method")
+}
+
+exports["test redefine for descendant"] = function(assert) {
+ var isFoo = Method("isFoo")
+ var ancestor = {}
+ isFoo.implement(ancestor, function() { return true })
+ var descendant = Object.create(ancestor)
+ isFoo.implement(descendant, function() { return false })
+
+ assert.ok(isFoo(ancestor), "defined on ancestor")
+ assert.ok(!isFoo(descendant), "overrided for descendant")
+}
+
+exports["test on custom types"] = function(assert) {
+ function Bar() {}
+ var isBar = Method("isBar")
+
+ isBar.define(function() { return false })
+ isBar.define(Bar, function() { return true })
+
+ assert.ok(!isBar({}), "object is get's default implementation")
+ assert.ok(isBar(new Bar()), "Foo type objects get own implementation")
+
+ var isObject = Method("isObject")
+ isObject.define(function() { return false })
+ isObject.define(Object, function() { return true })
+
+ assert.ok(isObject(new Bar()), "foo inherits implementation from object")
+
+
+ isObject.define(Bar, function() { return false })
+
+ assert.ok(!isObject(new Bar()),
+ "implementation inherited form object can be overrided")
+}
+
+
+exports["test error types"] = function(assert) {
+ var isError = Method("isError")
+ isError.define(function() { return false })
+ isError.define(Error, function() { return true })
+
+ assert.ok(isError(Error("boom")), "error is error")
+ assert.ok(isError(TypeError("boom")), "type error is an error")
+ assert.ok(isError(EvalError("boom")), "eval error is an error")
+ assert.ok(isError(RangeError("boom")), "range error is an error")
+ assert.ok(isError(ReferenceError("boom")), "reference error is an error")
+ assert.ok(isError(SyntaxError("boom")), "syntax error is an error")
+ assert.ok(isError(URIError("boom")), "URI error is an error")
+}
+
+exports["test override define polymorphic method"] = function(assert) {
+ var define = Method.define
+ var implement = Method.implement
+
+ var fn = Method("fn")
+ var methods = {}
+ implement(define, fn, function(method, label, implementation) {
+ methods[label] = implementation
+ })
+
+ function foo() {}
+
+ define(fn, "foo-case", foo)
+
+ assert.equal(methods["foo-case"], foo, "define set property")
+}
+
+exports["test override define via method API"] = function(assert) {
+ var define = Method.define
+ var implement = Method.implement
+
+ var fn = Method("fn")
+ var methods = {}
+ define.implement(fn, function(method, label, implementation) {
+ methods[label] = implementation
+ })
+
+ function foo() {}
+
+ define(fn, "foo-case", foo)
+
+ assert.equal(methods["foo-case"], foo, "define set property")
+}
+
+require("test").run(exports)
diff --git a/components/jetpack/modules/system/Startup.js b/components/jetpack/modules/system/Startup.js
new file mode 100644
index 000000000..89eeac3bc
--- /dev/null
+++ b/components/jetpack/modules/system/Startup.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+var EXPORTED_SYMBOLS = ["Startup"];
+
+var { utils: Cu, interfaces: Ci, classes: Cc } = Components;
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { defer } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
+
+const { XulApp } = Cu.import("resource://gre/modules/commonjs/sdk/system/xul-app.jsm", {});
+
+const appStartupSrv = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+
+const NAME2TOPIC = {
+ 'Palemoon': 'sessionstore-windows-restored',
+ 'Firefox': 'sessionstore-windows-restored',
+ 'Fennec': 'sessionstore-windows-restored',
+ 'SeaMonkey': 'sessionstore-windows-restored',
+ 'Thunderbird': 'mail-startup-done',
+ 'Instantbird': 'xul-window-visible'
+};
+
+var Startup = {
+ initialized: !appStartupSrv.startingUp
+};
+var exports = Startup;
+
+var gOnceInitializedDeferred = defer();
+exports.onceInitialized = gOnceInitializedDeferred.promise;
+
+// Set 'final-ui-startup' as default topic for unknown applications
+var appStartup = 'final-ui-startup';
+
+if (Startup.initialized) {
+ gOnceInitializedDeferred.resolve()
+}
+else {
+ // Gets the topic that fit best as application startup event, in according with
+ // the current application (e.g. Firefox, Fennec, Thunderbird...)
+ for (let name of Object.keys(NAME2TOPIC)) {
+ if (XulApp.is(name)) {
+ appStartup = NAME2TOPIC[name];
+ break;
+ }
+ }
+
+ let listener = function (subject, topic) {
+ Services.obs.removeObserver(this, topic);
+ Startup.initialized = true;
+ Services.tm.currentThread.dispatch(() => gOnceInitializedDeferred.resolve(),
+ Ci.nsIThread.DISPATCH_NORMAL);
+ }
+
+ Services.obs.addObserver(listener, appStartup, false);
+}
diff --git a/components/jetpack/modules/system/moz.build b/components/jetpack/modules/system/moz.build
new file mode 100644
index 000000000..b15ad453a
--- /dev/null
+++ b/components/jetpack/modules/system/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_JS_MODULES.sdk.system += [
+ 'Startup.js',
+]
diff --git a/components/jetpack/moz.build b/components/jetpack/moz.build
new file mode 100644
index 000000000..f07319ad9
--- /dev/null
+++ b/components/jetpack/moz.build
@@ -0,0 +1,486 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_JS_MODULES.sdk += [
+ 'app-extension/bootstrap.js',
+]
+
+EXTRA_JS_MODULES.sdk.system += [
+ 'modules/system/Startup.js',
+]
+
+EXTRA_JS_MODULES.commonjs.method.test += [
+ 'method/test/browser.js',
+ 'method/test/common.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.deprecated += [
+ 'sdk/deprecated/api-utils.js',
+ 'sdk/deprecated/sync-worker.js',
+ 'sdk/deprecated/unit-test-finder.js',
+ 'sdk/deprecated/unit-test.js',
+ 'sdk/deprecated/window-utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.frame += [
+ 'sdk/frame/hidden-frame.js',
+ 'sdk/frame/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.panel += [
+ 'sdk/panel/events.js',
+ 'sdk/panel/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.places += [
+ 'sdk/places/bookmarks.js',
+ 'sdk/places/contract.js',
+ 'sdk/places/events.js',
+ 'sdk/places/favicon.js',
+ 'sdk/places/history.js',
+ 'sdk/places/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.places.host += [
+ 'sdk/places/host/host-bookmarks.js',
+ 'sdk/places/host/host-query.js',
+ 'sdk/places/host/host-tags.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.tabs += [
+ 'sdk/tabs/common.js',
+ 'sdk/tabs/events.js',
+ 'sdk/tabs/helpers.js',
+ 'sdk/tabs/namespace.js',
+ 'sdk/tabs/observer.js',
+ 'sdk/tabs/tab-fennec.js',
+ 'sdk/tabs/tab-firefox.js',
+ 'sdk/tabs/tab.js',
+ 'sdk/tabs/tabs-firefox.js',
+ 'sdk/tabs/utils.js',
+ 'sdk/tabs/worker.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.test += [
+ 'sdk/test/assert.js',
+ 'sdk/test/harness.js',
+ 'sdk/test/httpd.js',
+ 'sdk/test/loader.js',
+ 'sdk/test/memory.js',
+ 'sdk/test/options.js',
+ 'sdk/test/runner.js',
+ 'sdk/test/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.ui += [
+ 'sdk/ui/component.js',
+ 'sdk/ui/frame.js',
+ 'sdk/ui/id.js',
+ 'sdk/ui/sidebar.js',
+ 'sdk/ui/state.js',
+ 'sdk/ui/toolbar.js',
+]
+
+if CONFIG['MC_PALEMOON']:
+ EXTRA_JS_MODULES.commonjs.sdk.ui += [
+ 'sdk/ui/buttons.js',
+ ]
+
+EXTRA_JS_MODULES.commonjs.sdk.ui.button += [
+ 'sdk/ui/button/action.js',
+ 'sdk/ui/button/contract.js',
+ 'sdk/ui/button/toggle.js',
+]
+
+EXTRA_PP_JS_MODULES.commonjs.sdk.ui.button += [
+ 'sdk/ui/button/view.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.ui.sidebar += [
+ 'sdk/ui/sidebar/actions.js',
+ 'sdk/ui/sidebar/contract.js',
+ 'sdk/ui/sidebar/namespace.js',
+ 'sdk/ui/sidebar/utils.js',
+ 'sdk/ui/sidebar/view.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.window += [
+ 'sdk/window/browser.js',
+ 'sdk/window/events.js',
+ 'sdk/window/helpers.js',
+ 'sdk/window/namespace.js',
+ 'sdk/window/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.windows += [
+ 'sdk/windows/fennec.js',
+ 'sdk/windows/firefox.js',
+ 'sdk/windows/observer.js',
+ 'sdk/windows/tabs-fennec.js',
+]
+
+EXTRA_JS_MODULES.commonjs += [
+ 'index.js',
+ 'test.js',
+]
+
+EXTRA_JS_MODULES.commonjs.dev += [
+ 'dev/debuggee.js',
+ 'dev/frame-script.js',
+ 'dev/panel.js',
+ 'dev/ports.js',
+ 'dev/theme.js',
+ 'dev/toolbox.js',
+ 'dev/utils.js',
+ 'dev/volcan.js',
+]
+
+EXTRA_JS_MODULES.commonjs.dev.panel += [
+ 'dev/panel/view.js',
+]
+
+EXTRA_JS_MODULES.commonjs.dev.theme += [
+ 'dev/theme/hooks.js',
+]
+
+EXTRA_JS_MODULES.commonjs.diffpatcher += [
+ 'diffpatcher/diff.js',
+ 'diffpatcher/index.js',
+ 'diffpatcher/patch.js',
+ 'diffpatcher/rebase.js',
+]
+
+EXTRA_JS_MODULES.commonjs.diffpatcher.test += [
+ 'diffpatcher/test/common.js',
+ 'diffpatcher/test/diff.js',
+ 'diffpatcher/test/index.js',
+ 'diffpatcher/test/patch.js',
+ 'diffpatcher/test/tap.js',
+]
+
+EXTRA_JS_MODULES.commonjs.framescript += [
+ 'framescript/content.jsm',
+ 'framescript/context-menu.js',
+ 'framescript/FrameScriptManager.jsm',
+ 'framescript/manager.js',
+ 'framescript/util.js',
+]
+
+EXTRA_JS_MODULES.commonjs['jetpack-id'] += [
+ 'jetpack-id/index.js',
+]
+
+EXTRA_JS_MODULES.commonjs.method += [
+ 'method/core.js',
+]
+
+EXTRA_JS_MODULES.commonjs['mozilla-toolkit-versioning'] += [
+ 'mozilla-toolkit-versioning/index.js',
+]
+
+EXTRA_JS_MODULES.commonjs['mozilla-toolkit-versioning'].lib += [
+ 'mozilla-toolkit-versioning/lib/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.node += [
+ 'node/os.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk += [
+ 'sdk/base64.js',
+ 'sdk/clipboard.js',
+ 'sdk/context-menu.js',
+ 'sdk/context-menu@2.js',
+ 'sdk/hotkeys.js',
+ 'sdk/indexed-db.js',
+ 'sdk/l10n.js',
+ 'sdk/messaging.js',
+ 'sdk/notifications.js',
+ 'sdk/page-mod.js',
+ 'sdk/page-worker.js',
+ 'sdk/panel.js',
+ 'sdk/passwords.js',
+ 'sdk/private-browsing.js',
+ 'sdk/querystring.js',
+ 'sdk/request.js',
+ 'sdk/selection.js',
+ 'sdk/self.js',
+ 'sdk/simple-prefs.js',
+ 'sdk/simple-storage.js',
+ 'sdk/system.js',
+ 'sdk/tabs.js',
+ 'sdk/test.js',
+ 'sdk/timers.js',
+ 'sdk/url.js',
+ 'sdk/windows.js',
+]
+
+EXTRA_PP_JS_MODULES.commonjs.sdk += [
+ 'sdk/ui.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.addon += [
+ 'sdk/addon/bootstrap.js',
+ 'sdk/addon/events.js',
+ 'sdk/addon/host.js',
+ 'sdk/addon/installer.js',
+ 'sdk/addon/manager.js',
+ 'sdk/addon/runner.js',
+ 'sdk/addon/window.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.browser += [
+ 'sdk/browser/events.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.console += [
+ 'sdk/console/plain-text.js',
+ 'sdk/console/traceback.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.content += [
+ 'sdk/content/content-worker.js',
+ 'sdk/content/content.js',
+ 'sdk/content/context-menu.js',
+ 'sdk/content/events.js',
+ 'sdk/content/l10n-html.js',
+ 'sdk/content/loader.js',
+ 'sdk/content/mod.js',
+ 'sdk/content/page-mod.js',
+ 'sdk/content/page-worker.js',
+ 'sdk/content/sandbox.js',
+ 'sdk/content/tab-events.js',
+ 'sdk/content/thumbnail.js',
+ 'sdk/content/utils.js',
+ 'sdk/content/worker-child.js',
+ 'sdk/content/worker.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.content.sandbox += [
+ 'sdk/content/sandbox/events.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk['context-menu'] += [
+ 'sdk/context-menu/context.js',
+ 'sdk/context-menu/core.js',
+ 'sdk/context-menu/readers.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.core += [
+ 'sdk/core/disposable.js',
+ 'sdk/core/heritage.js',
+ 'sdk/core/namespace.js',
+ 'sdk/core/observer.js',
+ 'sdk/core/promise.js',
+ 'sdk/core/reference.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.deprecated.events += [
+ 'sdk/deprecated/events/assembler.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.dom += [
+ 'sdk/dom/events-shimmed.js',
+ 'sdk/dom/events.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.dom.events += [
+ 'sdk/dom/events/keys.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.event += [
+ 'sdk/event/chrome.js',
+ 'sdk/event/core.js',
+ 'sdk/event/dom.js',
+ 'sdk/event/target.js',
+ 'sdk/event/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.fs += [
+ 'sdk/fs/path.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.input += [
+ 'sdk/input/browser.js',
+ 'sdk/input/customizable-ui.js',
+ 'sdk/input/frame.js',
+ 'sdk/input/system.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.io += [
+ 'sdk/io/buffer.js',
+ 'sdk/io/byte-streams.js',
+ 'sdk/io/file.js',
+ 'sdk/io/fs.js',
+ 'sdk/io/stream.js',
+ 'sdk/io/text-streams.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.keyboard += [
+ 'sdk/keyboard/hotkeys.js',
+ 'sdk/keyboard/observer.js',
+ 'sdk/keyboard/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.l10n += [
+ 'sdk/l10n/core.js',
+ 'sdk/l10n/html.js',
+ 'sdk/l10n/loader.js',
+ 'sdk/l10n/locale.js',
+ 'sdk/l10n/plural-rules.js',
+ 'sdk/l10n/prefs.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.l10n.json += [
+ 'sdk/l10n/json/core.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.l10n.properties += [
+ 'sdk/l10n/properties/core.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.lang += [
+ 'sdk/lang/functional.js',
+ 'sdk/lang/type.js',
+ 'sdk/lang/weak-set.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.lang.functional += [
+ 'sdk/lang/functional/concurrent.js',
+ 'sdk/lang/functional/core.js',
+ 'sdk/lang/functional/helpers.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.loader += [
+ 'sdk/loader/cuddlefish.js',
+ 'sdk/loader/sandbox.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.model += [
+ 'sdk/model/core.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.net += [
+ 'sdk/net/url.js',
+ 'sdk/net/xhr.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.output += [
+ 'sdk/output/system.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk['page-mod'] += [
+ 'sdk/page-mod/match-pattern.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.passwords += [
+ 'sdk/passwords/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.platform += [
+ 'sdk/platform/xpcom.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.preferences += [
+ 'sdk/preferences/event-target.js',
+ 'sdk/preferences/native-options.js',
+ 'sdk/preferences/service.js',
+ 'sdk/preferences/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk['private-browsing'] += [
+ 'sdk/private-browsing/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.remote += [
+ 'sdk/remote/child.js',
+ 'sdk/remote/core.js',
+ 'sdk/remote/parent.js',
+ 'sdk/remote/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.stylesheet += [
+ 'sdk/stylesheet/style.js',
+ 'sdk/stylesheet/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.system += [
+ 'sdk/system/child_process.js',
+ 'sdk/system/environment.js',
+ 'sdk/system/events-shimmed.js',
+ 'sdk/system/events.js',
+ 'sdk/system/globals.js',
+ 'sdk/system/process.js',
+ 'sdk/system/runtime.js',
+ 'sdk/system/unload.js',
+ 'sdk/system/xul-app.js',
+ 'sdk/system/xul-app.jsm',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.system.child_process += [
+ 'sdk/system/child_process/subprocess.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.tab += [
+ 'sdk/tab/events.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.ui.button.view += [
+ 'sdk/ui/button/view/events.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.ui.frame += [
+ 'sdk/ui/frame/model.js',
+ 'sdk/ui/frame/view.html',
+ 'sdk/ui/frame/view.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.ui.state += [
+ 'sdk/ui/state/events.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.ui.toolbar += [
+ 'sdk/ui/toolbar/model.js',
+ 'sdk/ui/toolbar/view.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.uri += [
+ 'sdk/uri/resource.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.url += [
+ 'sdk/url/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.util += [
+ 'sdk/util/array.js',
+ 'sdk/util/collection.js',
+ 'sdk/util/contract.js',
+ 'sdk/util/deprecate.js',
+ 'sdk/util/dispatcher.js',
+ 'sdk/util/list.js',
+ 'sdk/util/match-pattern.js',
+ 'sdk/util/object.js',
+ 'sdk/util/rules.js',
+ 'sdk/util/sequence.js',
+ 'sdk/util/uuid.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.view += [
+ 'sdk/view/core.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.worker += [
+ 'sdk/worker/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.sdk.zip += [
+ 'sdk/zip/utils.js',
+]
+
+EXTRA_JS_MODULES.commonjs.toolkit += [
+ 'toolkit/loader.js',
+ 'toolkit/require.js',
+]
diff --git a/components/jetpack/mozilla-toolkit-versioning/index.js b/components/jetpack/mozilla-toolkit-versioning/index.js
new file mode 100644
index 000000000..2f607c880
--- /dev/null
+++ b/components/jetpack/mozilla-toolkit-versioning/index.js
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 versionParse = require('./lib/utils').versionParse;
+
+var COMPARATORS = ['>=', '<=', '>', '<', '=', '~', '^'];
+
+exports.parse = function (input) {
+ input = input || '';
+ input = input.trim();
+ if (!input)
+ throw new Error('`parse` argument must be a populated string.');
+
+ // Handle the "*" case
+ if (input === "*") {
+ return { min: undefined, max: undefined };
+ }
+
+ var inputs = input.split(' ');
+ var min;
+ var max;
+
+ // 1.2.3 - 2.3.4
+ if (inputs.length === 3 && inputs[1] === '-') {
+ return { min: inputs[0], max: inputs[2] };
+ }
+
+ inputs.forEach(function (input) {
+ var parsed = parseExpression(input);
+ var version = parsed.version;
+ var comparator = parsed.comparator;
+
+ // 1.2.3
+ if (inputs.length === 1 && !comparator)
+ min = max = version;
+
+ // Parse min
+ if (~comparator.indexOf('>')) {
+ if (~comparator.indexOf('='))
+ min = version; // >=1.2.3
+ else
+ min = increment(version); // >1.2.3
+ }
+ else if (~comparator.indexOf('<')) {
+ if (~comparator.indexOf('='))
+ max = version; // <=1.2.3
+ else
+ max = decrement(version); // <1.2.3
+ }
+ });
+
+ return {
+ min: min,
+ max : max
+ };
+};
+
+function parseExpression (input) {
+ for (var i = 0; i < COMPARATORS.length; i++)
+ if (~input.indexOf(COMPARATORS[i]))
+ return {
+ comparator: COMPARATORS[i],
+ version: input.substr(COMPARATORS[i].length)
+ };
+ return { version: input, comparator: '' };
+}
+
+/**
+ * Takes a version string ('1.2.3') and returns a version string
+ * that'll parse as one less than the input string ('1.2.3.-1').
+ *
+ * @param {String} vString
+ * @return {String}
+ */
+function decrement (vString) {
+ return vString + (vString.charAt(vString.length - 1) === '.' ? '' : '.') + '-1';
+}
+exports.decrement = decrement;
+
+/**
+ * Takes a version string ('1.2.3') and returns a version string
+ * that'll parse as greater than the input string by the smallest margin
+ * possible ('1.2.3.1').
+ * listed as number-A, string-B, number-C, string-D in
+ * Mozilla's Toolkit Format.
+ * https://developer.mozilla.org/en-US/docs/Toolkit_version_format
+ *
+ * @param {String} vString
+ * @return {String}
+ */
+function increment (vString) {
+ var match = versionParse(vString);
+ var a = match[1];
+ var b = match[2];
+ var c = match[3];
+ var d = match[4];
+ var lastPos = vString.length - 1;
+ var lastChar = vString.charAt(lastPos);
+
+ if (!b) {
+ return vString + (lastChar === '.' ? '' : '.') + '1';
+ }
+ if (!c) {
+ return vString + '1';
+ }
+ if (!d) {
+ return vString.substr(0, lastPos) + (++lastChar);
+ }
+ return vString.substr(0, lastPos) + String.fromCharCode(lastChar.charCodeAt(0) + 1);
+}
+exports.increment = increment;
diff --git a/components/jetpack/mozilla-toolkit-versioning/lib/utils.js b/components/jetpack/mozilla-toolkit-versioning/lib/utils.js
new file mode 100644
index 000000000..e068085c0
--- /dev/null
+++ b/components/jetpack/mozilla-toolkit-versioning/lib/utils.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/. */
+
+/**
+ * Breaks up a version string into the 4 components
+ * defined in:
+ * https://developer.mozilla.org/en-US/docs/Toolkit_version_format
+ * @params {String} val
+ * @return {String}
+ */
+function versionParse (val) {
+ return val.match(/^([0-9\.]*)([a-zA-Z]*)([0-9\.]*)([a-zA-Z]*)$/);
+}
+exports.versionParse = versionParse;
diff --git a/components/jetpack/mozilla-toolkit-versioning/package.json b/components/jetpack/mozilla-toolkit-versioning/package.json
new file mode 100644
index 000000000..d9b0424e5
--- /dev/null
+++ b/components/jetpack/mozilla-toolkit-versioning/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "mozilla-toolkit-versioning",
+ "version": "0.0.2",
+ "description": "Parser for Mozilla's toolkit version format",
+ "main": "index.js",
+ "scripts": {
+ "test": "./node_modules/.bin/mocha --reporter spec --ui bdd"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/jsantell/mozilla-toolkit-versioning.git"
+ },
+ "author": "Jordan Santell",
+ "license": "MIT",
+ "dependencies": {},
+ "devDependencies": {
+ "mocha": "^1.21.4",
+ "chai": "^1.9.1",
+ "mozilla-version-comparator": "^1.0.2"
+ }
+}
diff --git a/components/jetpack/node/os.js b/components/jetpack/node/os.js
new file mode 100644
index 000000000..1c41a9246
--- /dev/null
+++ b/components/jetpack/node/os.js
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci } = require('chrome');
+const system = require('../sdk/system');
+const runtime = require('../sdk/system/runtime');
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+const isWindows = system.platform === 'win32';
+const endianness = ((new Uint32Array((new Uint8Array([1,2,3,4])).buffer))[0] === 0x04030201) ? 'LE' : 'BE';
+
+XPCOMUtils.defineLazyGetter(this, "oscpu", () => {
+ try {
+ return Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
+ } catch (e) {
+ return "";
+ }
+});
+
+XPCOMUtils.defineLazyGetter(this, "hostname", () => {
+ try {
+ // On some platforms (Linux according to try), this service does not exist and fails.
+ return Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService).myHostName;
+ } catch (e) {
+ return "";
+ }
+});
+
+/**
+ * Returns a path to a temp directory
+ */
+exports.tmpdir = () => system.pathFor('TmpD');
+
+/**
+ * Returns the endianness of the architecture: either 'LE' or 'BE'
+ */
+exports.endianness = () => endianness;
+
+/**
+ * Returns hostname of the machine
+ */
+exports.hostname = () => hostname;
+
+/**
+ * Name of the OS type
+ * Possible values:
+ * https://developer.mozilla.org/en/OS_TARGET
+ */
+exports.type = () => runtime.OS;
+
+/**
+ * Name of the OS Platform in lower case string.
+ * Possible values:
+ * https://developer.mozilla.org/en/OS_TARGET
+ */
+exports.platform = () => system.platform;
+
+/**
+ * Type of processor architecture running:
+ * 'arm', 'ia32', 'x86', 'x64'
+ */
+exports.arch = () => system.architecture;
+
+/**
+ * Returns the operating system release.
+ */
+exports.release = () => {
+ let match = oscpu.match(/(\d[\.\d]*)/);
+ return match && match.length > 1 ? match[1] : oscpu;
+};
+
+/**
+ * Returns EOL character for the OS
+ */
+exports.EOL = isWindows ? '\r\n' : '\n';
+
+/**
+ * Returns [0, 0, 0], as this is not implemented.
+ */
+exports.loadavg = () => [0, 0, 0];
+
+['uptime', 'totalmem', 'freemem', 'cpus'].forEach(method => {
+ exports[method] = () => { throw new Error('os.' + method + ' is not supported.'); };
+});
diff --git a/components/jetpack/sdk/addon/bootstrap.js b/components/jetpack/sdk/addon/bootstrap.js
new file mode 100644
index 000000000..a6055bd10
--- /dev/null
+++ b/components/jetpack/sdk/addon/bootstrap.js
@@ -0,0 +1,179 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Cu } = require("chrome");
+const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
+const { Task: { spawn } } = require("resource://gre/modules/Task.jsm");
+const { readURI } = require("sdk/net/url");
+const { mount, unmount } = require("sdk/uri/resource");
+const { setTimeout } = require("sdk/timers");
+const { Loader, Require, Module, main, unload } = require("toolkit/loader");
+const prefs = require("sdk/preferences/service");
+
+// load below now, so that it can be used by sdk/addon/runner
+// see bug https://bugzilla.mozilla.org/show_bug.cgi?id=1042239
+const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {});
+
+const REASON = [ "unknown", "startup", "shutdown", "enable", "disable",
+ "install", "uninstall", "upgrade", "downgrade" ];
+
+const UUID_PATTERN = /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
+// Takes add-on ID and normalizes it to a domain name so that add-on
+// can be mapped to resource://domain/
+const readDomain = id =>
+ // If only `@` character is the first one, than just substract it,
+ // otherwise fallback to legacy normalization code path. Note: `.`
+ // is valid character for resource substitutaiton & we intend to
+ // make add-on URIs intuitive, so it's best to just stick to an
+ // add-on author typed input.
+ id.lastIndexOf("@") === 0 ? id.substr(1).toLowerCase() :
+ id.toLowerCase().
+ replace(/@/g, "-at-").
+ replace(/\./g, "-dot-").
+ replace(UUID_PATTERN, "$1");
+
+const readPaths = id => {
+ const base = `extensions.modules.${id}.path.`;
+ const domain = readDomain(id);
+ return prefs.keys(base).reduce((paths, key) => {
+ const value = prefs.get(key);
+ const name = key.replace(base, "");
+ const path = name.split(".").join("/");
+ const prefix = path.length ? `${path}/` : path;
+ const uri = value.endsWith("/") ? value : `${value}/`;
+ const root = `extensions.modules.${domain}.commonjs.path.${name}`;
+
+ mount(root, uri);
+
+ paths[prefix] = `resource://${root}/`;
+ return paths;
+ }, {});
+};
+
+const Bootstrap = function(mountURI) {
+ this.mountURI = mountURI;
+ this.install = this.install.bind(this);
+ this.uninstall = this.uninstall.bind(this);
+ this.startup = this.startup.bind(this);
+ this.shutdown = this.shutdown.bind(this);
+};
+Bootstrap.prototype = {
+ constructor: Bootstrap,
+ mount(domain, rootURI) {
+ mount(domain, rootURI);
+ this.domain = domain;
+ },
+ unmount() {
+ if (this.domain) {
+ unmount(this.domain);
+ this.domain = null;
+ }
+ },
+ install(addon, reason) {
+ return new Promise(resolve => resolve());
+ },
+ uninstall(addon, reason) {
+ return new Promise(resolve => {
+ const {id} = addon;
+
+ prefs.reset(`extensions.${id}.sdk.domain`);
+ prefs.reset(`extensions.${id}.sdk.version`);
+ prefs.reset(`extensions.${id}.sdk.rootURI`);
+ prefs.reset(`extensions.${id}.sdk.baseURI`);
+ prefs.reset(`extensions.${id}.sdk.load.reason`);
+
+ resolve();
+ });
+ },
+ startup(addon, reasonCode) {
+ const { id, version, resourceURI: { spec: addonURI } } = addon;
+ const rootURI = this.mountURI || addonURI;
+ const reason = REASON[reasonCode];
+ const self = this;
+
+ return spawn(function*() {
+ const metadata = JSON.parse(yield readURI(`${rootURI}package.json`));
+ const domain = readDomain(id);
+ const baseURI = `resource://${domain}/`;
+
+ this.mount(domain, rootURI);
+
+ prefs.set(`extensions.${id}.sdk.domain`, domain);
+ prefs.set(`extensions.${id}.sdk.version`, version);
+ prefs.set(`extensions.${id}.sdk.rootURI`, rootURI);
+ prefs.set(`extensions.${id}.sdk.baseURI`, baseURI);
+ prefs.set(`extensions.${id}.sdk.load.reason`, reason);
+
+ const command = prefs.get(`extensions.${id}.sdk.load.command`);
+
+ const loader = Loader({
+ id,
+ isNative: true,
+ checkCompatibility: true,
+ prefixURI: baseURI,
+ rootURI: baseURI,
+ name: metadata.name,
+ paths: Object.assign({
+ "": "resource://gre/modules/commonjs/",
+ "devtools/": "resource://devtools/",
+ "./": baseURI
+ }, readPaths(id)),
+ manifest: metadata,
+ metadata: metadata,
+ modules: {
+ "@test/options": {},
+ },
+ noQuit: prefs.get(`extensions.${id}.sdk.test.no-quit`, false)
+ });
+ self.loader = loader;
+
+ const module = Module("package.json", `${baseURI}package.json`);
+ const require = Require(loader, module);
+ const main = command === "test" ? "sdk/test/runner" : null;
+ const prefsURI = `${baseURI}defaults/preferences/prefs.js`;
+
+ const { startup } = require("sdk/addon/runner");
+ startup(reason, {loader, main, prefsURI});
+ }.bind(this)).catch(error => {
+ console.error(`Failed to start ${id} addon`, error);
+ throw error;
+ });
+ },
+ shutdown(addon, code) {
+ this.unmount();
+ return this.unload(REASON[code]);
+ },
+ unload(reason) {
+ return new Promise(resolve => {
+ const { loader } = this;
+ if (loader) {
+ this.loader = null;
+ unload(loader, reason);
+
+ setTimeout(() => {
+ for (let uri of Object.keys(loader.sandboxes)) {
+ let sandbox = loader.sandboxes[uri];
+ if (Cu.getClassName(sandbox, true) == "Sandbox")
+ Cu.nukeSandbox(sandbox);
+ delete loader.sandboxes[uri];
+ delete loader.modules[uri];
+ }
+
+ try {
+ Cu.nukeSandbox(loader.sharedGlobalSandbox);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+
+ resolve();
+ }, 1000);
+ }
+ else {
+ resolve();
+ }
+ });
+ }
+};
+exports.Bootstrap = Bootstrap;
diff --git a/components/jetpack/sdk/addon/events.js b/components/jetpack/sdk/addon/events.js
new file mode 100644
index 000000000..45bada6e1
--- /dev/null
+++ b/components/jetpack/sdk/addon/events.js
@@ -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/. */
+
+'use strict';
+
+module.metadata = {
+ 'stability': 'experimental'
+};
+
+var { request: hostReq, response: hostRes } = require('./host');
+var { defer: async } = require('../lang/functional');
+var { defer } = require('../core/promise');
+var { emit: emitSync, on, off } = require('../event/core');
+var { uuid } = require('../util/uuid');
+var emit = async(emitSync);
+
+// Map of IDs to deferreds
+var requests = new Map();
+
+// May not be necessary to wrap this in `async`
+// once promises are async via bug 881047
+var receive = async(function ({data, id, error}) {
+ let request = requests.get(id);
+ if (request) {
+ if (error) request.reject(error);
+ else request.resolve(clone(data));
+ requests.delete(id);
+ }
+});
+on(hostRes, 'data', receive);
+
+/*
+ * Send is a helper to be used in client APIs to send
+ * a request to host
+ */
+function send (eventName, data) {
+ let id = uuid();
+ let deferred = defer();
+ requests.set(id, deferred);
+ emit(hostReq, 'data', {
+ id: id,
+ data: clone(data),
+ event: eventName
+ });
+ return deferred.promise;
+}
+exports.send = send;
+
+/*
+ * Implement internal structured cloning algorithm in the future?
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#internal-structured-cloning-algorithm
+ */
+function clone (obj) {
+ return JSON.parse(JSON.stringify(obj || {}));
+}
diff --git a/components/jetpack/sdk/addon/host.js b/components/jetpack/sdk/addon/host.js
new file mode 100644
index 000000000..91aa0e869
--- /dev/null
+++ b/components/jetpack/sdk/addon/host.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+exports.request = {};
+exports.response = {};
diff --git a/components/jetpack/sdk/addon/installer.js b/components/jetpack/sdk/addon/installer.js
new file mode 100644
index 000000000..bb8cf8d16
--- /dev/null
+++ b/components/jetpack/sdk/addon/installer.js
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci, Cu } = require("chrome");
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
+const { defer } = require("../core/promise");
+const { setTimeout } = require("../timers");
+
+/**
+ * `install` method error codes:
+ *
+ * https://developer.mozilla.org/en/Addons/Add-on_Manager/AddonManager#AddonInstall_errors
+ */
+exports.ERROR_NETWORK_FAILURE = AddonManager.ERROR_NETWORK_FAILURE;
+exports.ERROR_INCORRECT_HASH = AddonManager.ERROR_INCORRECT_HASH;
+exports.ERROR_CORRUPT_FILE = AddonManager.ERROR_CORRUPT_FILE;
+exports.ERROR_FILE_ACCESS = AddonManager.ERROR_FILE_ACCESS;
+
+/**
+ * Immediatly install an addon.
+ *
+ * @param {String} xpiPath
+ * file path to an xpi file to install
+ * @return {Promise}
+ * A promise resolved when the addon is finally installed.
+ * Resolved with addon id as value or rejected with an error code.
+ */
+exports.install = function install(xpiPath) {
+ let { promise, resolve, reject } = defer();
+
+ // Create nsIFile for the xpi file
+ let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+ try {
+ file.initWithPath(xpiPath);
+ }
+ catch(e) {
+ reject(exports.ERROR_FILE_ACCESS);
+ return promise;
+ }
+
+ // Listen for installation end
+ let listener = {
+ onInstallEnded: function(aInstall, aAddon) {
+ aInstall.removeListener(listener);
+ // Bug 749745: on FF14+, onInstallEnded is called just before `startup()`
+ // is called, but we expect to resolve the promise only after it.
+ // As startup is called synchronously just after onInstallEnded,
+ // a simple setTimeout(0) is enough
+ setTimeout(resolve, 0, aAddon.id);
+ },
+ onInstallFailed: function (aInstall) {
+ aInstall.removeListener(listener);
+ reject(aInstall.error);
+ },
+ onDownloadFailed: function(aInstall) {
+ this.onInstallFailed(aInstall);
+ }
+ };
+
+ // Order AddonManager to install the addon
+ AddonManager.getInstallForFile(file, function(install) {
+ if (install.error == 0) {
+ install.addListener(listener);
+ install.install();
+ } else {
+ reject(install.error);
+ }
+ });
+
+ return promise;
+};
+
+exports.uninstall = function uninstall(addonId) {
+ let { promise, resolve, reject } = defer();
+
+ // Listen for uninstallation end
+ let listener = {
+ onUninstalled: function onUninstalled(aAddon) {
+ if (aAddon.id != addonId)
+ return;
+ AddonManager.removeAddonListener(listener);
+ resolve();
+ }
+ };
+ AddonManager.addAddonListener(listener);
+
+ // Order Addonmanager to uninstall the addon
+ getAddon(addonId).then(addon => addon.uninstall(), reject);
+
+ return promise;
+};
+
+exports.disable = function disable(addonId) {
+ return getAddon(addonId).then(addon => {
+ addon.userDisabled = true;
+ return addonId;
+ });
+};
+
+exports.enable = function enabled(addonId) {
+ return getAddon(addonId).then(addon => {
+ addon.userDisabled = false;
+ return addonId;
+ });
+};
+
+exports.isActive = function isActive(addonId) {
+ return getAddon(addonId).then(addon => addon.isActive && !addon.appDisabled);
+};
+
+const getAddon = function getAddon (id) {
+ let { promise, resolve, reject } = defer();
+ AddonManager.getAddonByID(id, addon => addon ? resolve(addon) : reject());
+ return promise;
+}
+exports.getAddon = getAddon;
diff --git a/components/jetpack/sdk/addon/manager.js b/components/jetpack/sdk/addon/manager.js
new file mode 100644
index 000000000..7ac0a7d6e
--- /dev/null
+++ b/components/jetpack/sdk/addon/manager.js
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
+const { defer } = require("../core/promise");
+
+function getAddonByID(id) {
+ let { promise, resolve } = defer();
+ AddonManager.getAddonByID(id, resolve);
+ return promise;
+}
+exports.getAddonByID = getAddonByID;
diff --git a/components/jetpack/sdk/addon/runner.js b/components/jetpack/sdk/addon/runner.js
new file mode 100644
index 000000000..3977a04e4
--- /dev/null
+++ b/components/jetpack/sdk/addon/runner.js
@@ -0,0 +1,180 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci, Cu } = require('chrome');
+const { rootURI, metadata, isNative } = require('@loader/options');
+const { id, loadReason } = require('../self');
+const { descriptor, Sandbox, evaluate, main, resolveURI } = require('toolkit/loader');
+const { once } = require('../system/events');
+const { exit, env, staticArgs } = require('../system');
+const { when: unload } = require('../system/unload');
+const globals = require('../system/globals');
+const xulApp = require('../system/xul-app');
+const { get } = require('../preferences/service');
+const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+const { preferences } = metadata;
+
+const Startup = Cu.import("resource://gre/modules/sdk/system/Startup.js", {}).exports;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () {
+ return Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}).
+ BrowserToolboxProcess;
+});
+
+// Initializes default preferences
+function setDefaultPrefs(prefsURI) {
+ const prefs = Cc['@mozilla.org/preferences-service;1'].
+ getService(Ci.nsIPrefService).
+ QueryInterface(Ci.nsIPrefBranch2);
+ const branch = prefs.getDefaultBranch('');
+ const sandbox = Sandbox({
+ name: prefsURI,
+ prototype: {
+ pref: function(key, val) {
+ switch (typeof val) {
+ case 'boolean':
+ branch.setBoolPref(key, val);
+ break;
+ case 'number':
+ if (val % 1 == 0) // number must be a integer, otherwise ignore it
+ branch.setIntPref(key, val);
+ break;
+ case 'string':
+ branch.setCharPref(key, val);
+ break;
+ }
+ }
+ }
+ });
+ // load preferences.
+ evaluate(sandbox, prefsURI);
+}
+
+function definePseudo(loader, id, exports) {
+ let uri = resolveURI(id, loader.mapping);
+ loader.modules[uri] = { exports: exports };
+}
+
+function startup(reason, options) {
+ return Startup.onceInitialized.then(() => {
+ // Inject globals ASAP in order to have console API working ASAP
+ Object.defineProperties(options.loader.globals, descriptor(globals));
+
+ // NOTE: Module is intentionally required only now because it relies
+ // on existence of hidden window, which does not exists until startup.
+ let { ready } = require('../addon/window');
+ // Load localization manifest and .properties files.
+ // Run the addon even in case of error (best effort approach)
+ require('../l10n/loader').
+ load(rootURI).
+ then(null, function failure(error) {
+ if (!isNative)
+ console.info("Error while loading localization: " + error.message);
+ }).
+ then(function onLocalizationReady(data) {
+ // Exports data to a pseudo module so that api-utils/l10n/core
+ // can get access to it
+ definePseudo(options.loader, '@l10n/data', data ? data : null);
+ return ready;
+ }).then(function() {
+ run(options);
+ }).then(null, console.exception);
+ return void 0; // otherwise we raise a warning, see bug 910304
+ });
+}
+
+function run(options) {
+ try {
+ // Try initializing HTML localization before running main module. Just print
+ // an exception in case of error, instead of preventing addon to be run.
+ try {
+ // Do not enable HTML localization while running test as it is hard to
+ // disable. Because unit tests are evaluated in a another Loader who
+ // doesn't have access to this current loader.
+ if (options.main !== 'sdk/test/runner') {
+ require('../l10n/html').enable();
+ }
+ }
+ catch(error) {
+ console.exception(error);
+ }
+
+ // native-options does stuff directly with preferences key from package.json
+ if (preferences && preferences.length > 0) {
+ try {
+ require('../preferences/native-options').
+ enable({ preferences: preferences, id: id }).
+ catch(console.exception);
+ }
+ catch (error) {
+ console.exception(error);
+ }
+ }
+ else {
+ // keeping support for addons packaged with older SDK versions,
+ // when cfx didn't include the 'preferences' key in @loader/options
+
+ // Initialize inline options localization, without preventing addon to be
+ // run in case of error
+ try {
+ require('../l10n/prefs').enable();
+ }
+ catch(error) {
+ console.exception(error);
+ }
+
+ // TODO: When bug 564675 is implemented this will no longer be needed
+ // Always set the default prefs, because they disappear on restart
+ if (options.prefsURI) {
+ // Only set if `prefsURI` specified
+ try {
+ setDefaultPrefs(options.prefsURI);
+ }
+ catch (err) {
+ // cfx bootstrap always passes prefsURI, even in addons without prefs
+ }
+ }
+ }
+
+ // this is where the addon's main.js finally run.
+ let program = main(options.loader, options.main);
+
+ if (typeof(program.onUnload) === 'function')
+ unload(program.onUnload);
+
+ if (typeof(program.main) === 'function') {
+ program.main({
+ loadReason: loadReason,
+ staticArgs: staticArgs
+ }, {
+ print: function print(_) { dump(_ + '\n') },
+ quit: exit
+ });
+ }
+
+ if (get("extensions." + id + ".sdk.debug.show", false)) {
+ BrowserToolboxProcess.init({ addonID: id });
+ }
+ } catch (error) {
+ console.exception(error);
+ throw error;
+ }
+}
+exports.startup = startup;
+
+// If add-on is lunched via `cfx run` we need to use `system.exit` to let
+// cfx know we're done (`cfx test` will take care of exit so we don't do
+// anything here).
+if (env.CFX_COMMAND === 'run') {
+ unload(function(reason) {
+ if (reason === 'shutdown')
+ exit(0);
+ });
+}
diff --git a/components/jetpack/sdk/addon/window.js b/components/jetpack/sdk/addon/window.js
new file mode 100644
index 000000000..93ed1d8dc
--- /dev/null
+++ b/components/jetpack/sdk/addon/window.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Ci, Cc } = require("chrome");
+const { make: makeWindow, getHiddenWindow } = require("../window/utils");
+const { create: makeFrame, getDocShell } = require("../frame/utils");
+const { defer } = require("../core/promise");
+const { when: unload } = require("../system/unload");
+const cfxArgs = require("../test/options");
+
+var addonPrincipal = Cc["@mozilla.org/systemprincipal;1"].
+ createInstance(Ci.nsIPrincipal);
+
+var hiddenWindow = getHiddenWindow();
+
+if (cfxArgs.parseable) {
+ console.info("hiddenWindow document.documentURI:" +
+ hiddenWindow.document.documentURI);
+ console.info("hiddenWindow document.readyState:" +
+ hiddenWindow.document.readyState);
+}
+
+// Once Bug 565388 is fixed and shipped we'll be able to make invisible,
+// permanent docShells. Meanwhile we create hidden top level window and
+// use it's docShell.
+var frame = makeFrame(hiddenWindow.document, {
+ nodeName: "iframe",
+ namespaceURI: "http://www.w3.org/1999/xhtml",
+ allowJavascript: true,
+ allowPlugins: true
+})
+var docShell = getDocShell(frame);
+var eventTarget = docShell.chromeEventHandler;
+
+// We need to grant docShell system principals in order to load XUL document
+// from data URI into it.
+docShell.createAboutBlankContentViewer(addonPrincipal);
+
+// Get a reference to the DOM window of the given docShell and load
+// such document into that would allow us to create XUL iframes, that
+// are necessary for hidden frames etc..
+var window = docShell.contentViewer.DOMDocument.defaultView;
+window.location = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window/>";
+
+// Create a promise that is delivered once add-on window is interactive,
+// used by add-on runner to defer add-on loading until window is ready.
+var { promise, resolve } = defer();
+eventTarget.addEventListener("DOMContentLoaded", function handler(event) {
+ eventTarget.removeEventListener("DOMContentLoaded", handler, false);
+ resolve();
+}, false);
+
+exports.ready = promise;
+exports.window = window;
+
+// Still close window on unload to claim memory back early.
+unload(function() {
+ window.close()
+ frame.parentNode.removeChild(frame);
+});
diff --git a/components/jetpack/sdk/base64.js b/components/jetpack/sdk/base64.js
new file mode 100644
index 000000000..a07b302e0
--- /dev/null
+++ b/components/jetpack/sdk/base64.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cu } = require("chrome");
+
+// Passing an empty object as second argument to avoid scope's pollution
+// (devtools loader injects these symbols as global and prevent using
+// const here)
+var { atob, btoa } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+function isUTF8(charset) {
+ let type = typeof charset;
+
+ if (type === "undefined")
+ return false;
+
+ if (type === "string" && charset.toLowerCase() === "utf-8")
+ return true;
+
+ throw new Error("The charset argument can be only 'utf-8'");
+}
+
+function toOctetChar(c) {
+ return String.fromCharCode(c.charCodeAt(0) & 0xFF);
+}
+
+exports.decode = function (data, charset) {
+ if (isUTF8(charset))
+ return decodeURIComponent(escape(atob(data)))
+
+ return atob(data);
+}
+
+exports.encode = function (data, charset) {
+ if (isUTF8(charset))
+ return btoa(unescape(encodeURIComponent(data)))
+
+ data = data.replace(/[^\x00-\xFF]/g, toOctetChar);
+ return btoa(data);
+}
diff --git a/components/jetpack/sdk/browser/events.js b/components/jetpack/sdk/browser/events.js
new file mode 100644
index 000000000..f91119031
--- /dev/null
+++ b/components/jetpack/sdk/browser/events.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { events } = require("../window/events");
+const { filter } = require("../event/utils");
+const { isBrowser } = require("../window/utils");
+
+// TODO: `isBrowser` detects weather window is a browser by checking
+// `windowtype` attribute, which means that all 'open' events will be
+// filtered out since document is not loaded yet. Maybe we can find a better
+// implementation for `isBrowser`. Either way it's not really needed yet
+// neither window tracker provides this event.
+
+exports.events = filter(events, ({target}) => isBrowser(target));
diff --git a/components/jetpack/sdk/clipboard.js b/components/jetpack/sdk/clipboard.js
new file mode 100644
index 000000000..c6b3c46fe
--- /dev/null
+++ b/components/jetpack/sdk/clipboard.js
@@ -0,0 +1,338 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "stable",
+ "engines": {
+ // TODO Fennec Support 789757
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*",
+ "Thunderbird": "*"
+ }
+};
+
+const { Cc, Ci } = require("chrome");
+const { DataURL } = require("./url");
+const apiUtils = require("./deprecated/api-utils");
+/*
+While these data flavors resemble Internet media types, they do
+no directly map to them.
+*/
+const kAllowableFlavors = [
+ "text/unicode",
+ "text/html",
+ "image/png"
+ /* CURRENTLY UNSUPPORTED FLAVORS
+ "text/plain",
+ "image/jpg",
+ "image/jpeg",
+ "image/gif",
+ "text/x-moz-text-internal",
+ "AOLMAIL",
+ "application/x-moz-file",
+ "text/x-moz-url",
+ "text/x-moz-url-data",
+ "text/x-moz-url-desc",
+ "text/x-moz-url-priv",
+ "application/x-moz-nativeimage",
+ "application/x-moz-nativehtml",
+ "application/x-moz-file-promise-url",
+ "application/x-moz-file-promise-dest-filename",
+ "application/x-moz-file-promise",
+ "application/x-moz-file-promise-dir"
+ */
+];
+
+/*
+Aliases for common flavors. Not all flavors will
+get an alias. New aliases must be approved by a
+Jetpack API druid.
+*/
+const kFlavorMap = [
+ { short: "text", long: "text/unicode" },
+ { short: "html", long: "text/html" },
+ { short: "image", long: "image/png" }
+];
+
+var clipboardService = Cc["@mozilla.org/widget/clipboard;1"].
+ getService(Ci.nsIClipboard);
+
+var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+
+var imageTools = Cc["@mozilla.org/image/tools;1"].
+ getService(Ci.imgITools);
+
+exports.set = function(aData, aDataType) {
+
+ let options = {
+ data: aData,
+ datatype: aDataType || "text"
+ };
+
+ // If `aDataType` is not given or if it's "image", the data is parsed as
+ // data URL to detect a better datatype
+ if (aData && (!aDataType || aDataType === "image")) {
+ try {
+ let dataURL = new DataURL(aData);
+
+ options.datatype = dataURL.mimeType;
+ options.data = dataURL.data;
+ }
+ catch (e) {
+ // Ignore invalid URIs
+ if (e.name !== "URIError") {
+ throw e;
+ }
+ }
+ }
+
+ options = apiUtils.validateOptions(options, {
+ data: {
+ is: ["string"]
+ },
+ datatype: {
+ is: ["string"]
+ }
+ });
+
+ let flavor = fromJetpackFlavor(options.datatype);
+
+ if (!flavor)
+ throw new Error("Invalid flavor for " + options.datatype);
+
+ // Additional checks for using the simple case
+ if (flavor == "text/unicode") {
+ clipboardHelper.copyString(options.data);
+ return true;
+ }
+
+ // Below are the more complex cases where we actually have to work with a
+ // nsITransferable object
+ var xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ if (!xferable)
+ throw new Error("Couldn't set the clipboard due to an internal error " +
+ "(couldn't create a Transferable object).");
+ // Bug 769440: Starting with FF16, transferable have to be inited
+ if ("init" in xferable)
+ xferable.init(null);
+
+ switch (flavor) {
+ case "text/html":
+ // add text/html flavor
+ let str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+
+ str.data = options.data;
+ xferable.addDataFlavor(flavor);
+ xferable.setTransferData(flavor, str, str.data.length * 2);
+
+ // add a text/unicode flavor (html converted to plain text)
+ str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ let converter = Cc["@mozilla.org/feed-textconstruct;1"].
+ createInstance(Ci.nsIFeedTextConstruct);
+
+ converter.type = "html";
+ converter.text = options.data;
+ str.data = converter.plainText();
+ xferable.addDataFlavor("text/unicode");
+ xferable.setTransferData("text/unicode", str, str.data.length * 2);
+ break;
+
+ // Set images to the clipboard is not straightforward, to have an idea how
+ // it works on platform side, see:
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsCopySupport.cpp?rev=7857c5bff017#530
+ case "image/png":
+ let image = options.data;
+
+ let container = {};
+
+ try {
+ let input = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+
+ input.setData(image, image.length);
+
+ imageTools.decodeImageData(input, flavor, container);
+ }
+ catch (e) {
+ throw new Error("Unable to decode data given in a valid image.");
+ }
+
+ // Store directly the input stream makes the cliboard's data available
+ // for Firefox but not to the others application or to the OS. Therefore,
+ // a `nsISupportsInterfacePointer` object that reference an `imgIContainer`
+ // with the image is needed.
+ var imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"].
+ createInstance(Ci.nsISupportsInterfacePointer);
+
+ imgPtr.data = container.value;
+
+ xferable.addDataFlavor(flavor);
+ xferable.setTransferData(flavor, imgPtr, -1);
+
+ break;
+ default:
+ throw new Error("Unable to handle the flavor " + flavor + ".");
+ }
+
+ // TODO: Not sure if this will ever actually throw. -zpao
+ try {
+ clipboardService.setData(
+ xferable,
+ null,
+ clipboardService.kGlobalClipboard
+ );
+ } catch (e) {
+ throw new Error("Couldn't set clipboard data due to an internal error: " + e);
+ }
+ return true;
+};
+
+
+exports.get = function(aDataType) {
+ let options = {
+ datatype: aDataType
+ };
+
+ // Figure out the best data type for the clipboard's data, if omitted
+ if (!aDataType) {
+ if (~currentFlavors().indexOf("image"))
+ options.datatype = "image";
+ else
+ options.datatype = "text";
+ }
+
+ options = apiUtils.validateOptions(options, {
+ datatype: {
+ is: ["string"]
+ }
+ });
+
+ var xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ if (!xferable)
+ throw new Error("Couldn't set the clipboard due to an internal error " +
+ "(couldn't create a Transferable object).");
+ // Bug 769440: Starting with FF16, transferable have to be inited
+ if ("init" in xferable)
+ xferable.init(null);
+
+ var flavor = fromJetpackFlavor(options.datatype);
+
+ // Ensure that the user hasn't requested a flavor that we don't support.
+ if (!flavor)
+ throw new Error("Getting the clipboard with the flavor '" + flavor +
+ "' is not supported.");
+
+ // TODO: Check for matching flavor first? Probably not worth it.
+
+ xferable.addDataFlavor(flavor);
+ // Get the data into our transferable.
+ clipboardService.getData(
+ xferable,
+ clipboardService.kGlobalClipboard
+ );
+
+ var data = {};
+ var dataLen = {};
+ try {
+ xferable.getTransferData(flavor, data, dataLen);
+ } catch (e) {
+ // Clipboard doesn't contain data in flavor, return null.
+ return null;
+ }
+
+ // There's no data available, return.
+ if (data.value === null)
+ return null;
+
+ // TODO: Add flavors here as we support more in kAllowableFlavors.
+ switch (flavor) {
+ case "text/unicode":
+ case "text/html":
+ data = data.value.QueryInterface(Ci.nsISupportsString).data;
+ break;
+ case "image/png":
+ let dataURL = new DataURL();
+
+ dataURL.mimeType = flavor;
+ dataURL.base64 = true;
+
+ let image = data.value;
+
+ // Due to the differences in how images could be stored in the clipboard
+ // the checks below are needed. The clipboard could already provide the
+ // image as byte streams, but also as pointer, or as image container.
+ // If it's not possible obtain a byte stream, the function returns `null`.
+ if (image instanceof Ci.nsISupportsInterfacePointer)
+ image = image.data;
+
+ if (image instanceof Ci.imgIContainer)
+ image = imageTools.encodeImage(image, flavor);
+
+ if (image instanceof Ci.nsIInputStream) {
+ let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+
+ binaryStream.setInputStream(image);
+
+ dataURL.data = binaryStream.readBytes(binaryStream.available());
+
+ data = dataURL.toString();
+ }
+ else
+ data = null;
+
+ break;
+ default:
+ data = null;
+ }
+
+ return data;
+};
+
+function currentFlavors() {
+ // Loop over kAllowableFlavors, calling hasDataMatchingFlavors for each.
+ // This doesn't seem like the most efficient way, but we can't get
+ // confirmation for specific flavors any other way. This is supposed to be
+ // an inexpensive call, so performance shouldn't be impacted (much).
+ var currentFlavors = [];
+ for (var flavor of kAllowableFlavors) {
+ var matches = clipboardService.hasDataMatchingFlavors(
+ [flavor],
+ 1,
+ clipboardService.kGlobalClipboard
+ );
+ if (matches)
+ currentFlavors.push(toJetpackFlavor(flavor));
+ }
+ return currentFlavors;
+};
+
+Object.defineProperty(exports, "currentFlavors", { get : currentFlavors });
+
+// SUPPORT FUNCTIONS ////////////////////////////////////////////////////////
+
+function toJetpackFlavor(aFlavor) {
+ for (let flavorMap of kFlavorMap)
+ if (flavorMap.long == aFlavor)
+ return flavorMap.short;
+ // Return null in the case where we don't match
+ return null;
+}
+
+function fromJetpackFlavor(aJetpackFlavor) {
+ // TODO: Handle proper flavors better
+ for (let flavorMap of kFlavorMap)
+ if (flavorMap.short == aJetpackFlavor || flavorMap.long == aJetpackFlavor)
+ return flavorMap.long;
+ // Return null in the case where we don't match.
+ return null;
+}
diff --git a/components/jetpack/sdk/console/plain-text.js b/components/jetpack/sdk/console/plain-text.js
new file mode 100644
index 000000000..0e44cf106
--- /dev/null
+++ b/components/jetpack/sdk/console/plain-text.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, Cu, Cr } = require("chrome");
+const self = require("../self");
+const prefs = require("../preferences/service");
+const { merge } = require("../util/object");
+const { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+
+const DEFAULT_LOG_LEVEL = "error";
+const ADDON_LOG_LEVEL_PREF = "extensions." + self.id + ".sdk.console.logLevel";
+const SDK_LOG_LEVEL_PREF = "extensions.sdk.console.logLevel";
+
+var logLevel = DEFAULT_LOG_LEVEL;
+function setLogLevel() {
+ logLevel = prefs.get(ADDON_LOG_LEVEL_PREF,
+ prefs.get(SDK_LOG_LEVEL_PREF,
+ DEFAULT_LOG_LEVEL));
+}
+setLogLevel();
+
+var logLevelObserver = {
+ QueryInterface: function(iid) {
+ if (!iid.equals(Ci.nsIObserver) &&
+ !iid.equals(Ci.nsISupportsWeakReference) &&
+ !iid.equals(Ci.nsISupports))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+ observe: function(subject, topic, data) {
+ setLogLevel();
+ }
+};
+var branch = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(null);
+branch.addObserver(ADDON_LOG_LEVEL_PREF, logLevelObserver, true);
+branch.addObserver(SDK_LOG_LEVEL_PREF, logLevelObserver, true);
+
+function PlainTextConsole(print, innerID) {
+
+ let consoleOptions = {
+ prefix: self.name,
+ maxLogLevel: logLevel,
+ dump: print,
+ innerID: innerID,
+ consoleID: "addon/" + self.id
+ };
+ let console = new ConsoleAPI(consoleOptions);
+
+ // As we freeze the console object, we can't modify this property afterward
+ Object.defineProperty(console, "maxLogLevel", {
+ get: function() {
+ return logLevel;
+ }
+ });
+
+ // We defined the `__exposedProps__` in our console chrome object.
+ //
+ // Meanwhile we're investigating with the platform team if `__exposedProps__`
+ // are needed, or are just a left-over.
+
+ console.__exposedProps__ = Object.keys(ConsoleAPI.prototype).reduce(function(exposed, prop) {
+ exposed[prop] = "r";
+ return exposed;
+ }, {});
+
+ Object.freeze(console);
+ return console;
+};
+exports.PlainTextConsole = PlainTextConsole;
diff --git a/components/jetpack/sdk/console/traceback.js b/components/jetpack/sdk/console/traceback.js
new file mode 100644
index 000000000..be0fb7b94
--- /dev/null
+++ b/components/jetpack/sdk/console/traceback.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Ci, components } = require("chrome");
+const { parseStack, sourceURI } = require("toolkit/loader");
+const { readURISync } = require("../net/url");
+
+function safeGetFileLine(path, line) {
+ try {
+ var scheme = require("../url").URL(path).scheme;
+ // TODO: There should be an easier, more accurate way to figure out
+ // what's the case here.
+ if (!(scheme == "http" || scheme == "https"))
+ return readURISync(path).split("\n")[line - 1];
+ } catch (e) {}
+ return null;
+}
+
+function nsIStackFramesToJSON(frame) {
+ var stack = [];
+
+ while (frame) {
+ if (frame.filename) {
+ stack.unshift({
+ fileName: sourceURI(frame.filename),
+ lineNumber: frame.lineNumber,
+ name: frame.name
+ });
+ }
+ frame = frame.caller;
+ }
+
+ return stack;
+};
+
+var fromException = exports.fromException = function fromException(e) {
+ if (e instanceof Ci.nsIException)
+ return nsIStackFramesToJSON(e.location);
+ if (e.stack && e.stack.length)
+ return parseStack(e.stack);
+ if (e.fileName && typeof(e.lineNumber == "number"))
+ return [{fileName: sourceURI(e.fileName),
+ lineNumber: e.lineNumber,
+ name: null}];
+ return [];
+};
+
+var get = exports.get = function get() {
+ return nsIStackFramesToJSON(components.stack.caller);
+};
+
+var format = exports.format = function format(tbOrException) {
+ if (tbOrException === undefined) {
+ tbOrException = get();
+ tbOrException.pop();
+ }
+
+ var tb;
+ if (typeof(tbOrException) == "object" &&
+ tbOrException.constructor.name == "Array")
+ tb = tbOrException;
+ else
+ tb = fromException(tbOrException);
+
+ var lines = ["Traceback (most recent call last):"];
+
+ tb.forEach(
+ function(frame) {
+ if (!(frame.fileName || frame.lineNumber || frame.name))
+ return;
+
+ lines.push(' File "' + frame.fileName + '", line ' +
+ frame.lineNumber + ', in ' + frame.name);
+ var sourceLine = safeGetFileLine(frame.fileName, frame.lineNumber);
+ if (sourceLine)
+ lines.push(' ' + sourceLine.trim());
+ });
+
+ return lines.join("\n");
+};
diff --git a/components/jetpack/sdk/content/content-worker.js b/components/jetpack/sdk/content/content-worker.js
new file mode 100644
index 000000000..0a8225733
--- /dev/null
+++ b/components/jetpack/sdk/content/content-worker.js
@@ -0,0 +1,305 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Object.freeze({
+ // TODO: Bug 727854 Use same implementation than common JS modules,
+ // i.e. EventEmitter module
+
+ /**
+ * Create an EventEmitter instance.
+ */
+ createEventEmitter: function createEventEmitter(emit) {
+ let listeners = Object.create(null);
+ let eventEmitter = Object.freeze({
+ emit: emit,
+ on: function on(name, callback) {
+ if (typeof callback !== "function")
+ return this;
+ if (!(name in listeners))
+ listeners[name] = [];
+ listeners[name].push(callback);
+ return this;
+ },
+ once: function once(name, callback) {
+ eventEmitter.on(name, function onceCallback() {
+ eventEmitter.removeListener(name, onceCallback);
+ callback.apply(callback, arguments);
+ });
+ },
+ removeListener: function removeListener(name, callback) {
+ if (!(name in listeners))
+ return;
+ let index = listeners[name].indexOf(callback);
+ if (index == -1)
+ return;
+ listeners[name].splice(index, 1);
+ }
+ });
+ function onEvent(name) {
+ if (!(name in listeners))
+ return [];
+ let args = Array.slice(arguments, 1);
+ let results = [];
+ for (let callback of listeners[name]) {
+ results.push(callback.apply(null, args));
+ }
+ return results;
+ }
+ return {
+ eventEmitter: eventEmitter,
+ emit: onEvent
+ };
+ },
+
+ /**
+ * Create an EventEmitter instance to communicate with chrome module
+ * by passing only strings between compartments.
+ * This function expects `emitToChrome` function, that allows to send
+ * events to the chrome module. It returns the EventEmitter as `pipe`
+ * attribute, and, `onChromeEvent` a function that allows chrome module
+ * to send event into the EventEmitter.
+ *
+ * pipe.emit --> emitToChrome
+ * onChromeEvent --> callback registered through pipe.on
+ */
+ createPipe: function createPipe(emitToChrome) {
+ let ContentWorker = this;
+ function onEvent(type, ...args) {
+ // JSON.stringify is buggy with cross-sandbox values,
+ // it may return "{}" on functions. Use a replacer to match them correctly.
+ let replacer = (k, v) =>
+ typeof(v) === "function"
+ ? (type === "console" ? Function.toString.call(v) : void(0))
+ : v;
+
+ let str = JSON.stringify([type, ...args], replacer);
+ emitToChrome(str);
+ }
+
+ let { eventEmitter, emit } =
+ ContentWorker.createEventEmitter(onEvent);
+
+ return {
+ pipe: eventEmitter,
+ onChromeEvent: function onChromeEvent(array) {
+ // We either receive a stringified array, or a real array.
+ // We still allow to pass an array of objects, in WorkerSandbox.emitSync
+ // in order to allow sending DOM node reference between content script
+ // and modules (only used for context-menu API)
+ let args = typeof array == "string" ? JSON.parse(array) : array;
+ return emit.apply(null, args);
+ }
+ };
+ },
+
+ injectConsole: function injectConsole(exports, pipe) {
+ exports.console = Object.freeze({
+ log: pipe.emit.bind(null, "console", "log"),
+ info: pipe.emit.bind(null, "console", "info"),
+ warn: pipe.emit.bind(null, "console", "warn"),
+ error: pipe.emit.bind(null, "console", "error"),
+ debug: pipe.emit.bind(null, "console", "debug"),
+ exception: pipe.emit.bind(null, "console", "exception"),
+ trace: pipe.emit.bind(null, "console", "trace"),
+ time: pipe.emit.bind(null, "console", "time"),
+ timeEnd: pipe.emit.bind(null, "console", "timeEnd")
+ });
+ },
+
+ injectTimers: function injectTimers(exports, chromeAPI, pipe, console) {
+ // wrapped functions from `'timer'` module.
+ // Wrapper adds `try catch` blocks to the callbacks in order to
+ // emit `error` event if exception is thrown in
+ // the Worker global scope.
+ // @see http://www.w3.org/TR/workers/#workerutils
+
+ // List of all living timeouts/intervals
+ let _timers = Object.create(null);
+
+ // Keep a reference to original timeout functions
+ let {
+ setTimeout: chromeSetTimeout,
+ setInterval: chromeSetInterval,
+ clearTimeout: chromeClearTimeout,
+ clearInterval: chromeClearInterval
+ } = chromeAPI.timers;
+
+ function registerTimer(timer) {
+ let registerMethod = null;
+ if (timer.kind == "timeout")
+ registerMethod = chromeSetTimeout;
+ else if (timer.kind == "interval")
+ registerMethod = chromeSetInterval;
+ else
+ throw new Error("Unknown timer kind: " + timer.kind);
+
+ if (typeof timer.fun == 'string') {
+ let code = timer.fun;
+ timer.fun = () => chromeAPI.sandbox.evaluate(exports, code);
+ } else if (typeof timer.fun != 'function') {
+ throw new Error('Unsupported callback type' + typeof timer.fun);
+ }
+
+ let id = registerMethod(onFire, timer.delay);
+ function onFire() {
+ try {
+ if (timer.kind == "timeout")
+ delete _timers[id];
+ timer.fun.apply(null, timer.args);
+ } catch(e) {
+ console.exception(e);
+ let wrapper = {
+ instanceOfError: instanceOf(e, Error),
+ value: e,
+ };
+ if (wrapper.instanceOfError) {
+ wrapper.value = {
+ message: e.message,
+ fileName: e.fileName,
+ lineNumber: e.lineNumber,
+ stack: e.stack,
+ name: e.name,
+ };
+ }
+ pipe.emit('error', wrapper);
+ }
+ }
+ _timers[id] = timer;
+ return id;
+ }
+
+ // copied from sdk/lang/type.js since modules are not available here
+ function instanceOf(value, Type) {
+ var isConstructorNameSame;
+ var isConstructorSourceSame;
+
+ // If `instanceof` returned `true` we know result right away.
+ var isInstanceOf = value instanceof Type;
+
+ // If `instanceof` returned `false` we do ducktype check since `Type` may be
+ // from a different sandbox. If a constructor of the `value` or a constructor
+ // of the value's prototype has same name and source we assume that it's an
+ // instance of the Type.
+ if (!isInstanceOf && value) {
+ isConstructorNameSame = value.constructor.name === Type.name;
+ isConstructorSourceSame = String(value.constructor) == String(Type);
+ isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
+ instanceOf(Object.getPrototypeOf(value), Type);
+ }
+ return isInstanceOf;
+ }
+
+ function unregisterTimer(id) {
+ if (!(id in _timers))
+ return;
+ let { kind } = _timers[id];
+ delete _timers[id];
+ if (kind == "timeout")
+ chromeClearTimeout(id);
+ else if (kind == "interval")
+ chromeClearInterval(id);
+ else
+ throw new Error("Unknown timer kind: " + kind);
+ }
+
+ function disableAllTimers() {
+ Object.keys(_timers).forEach(unregisterTimer);
+ }
+
+ exports.setTimeout = function ContentScriptSetTimeout(callback, delay) {
+ return registerTimer({
+ kind: "timeout",
+ fun: callback,
+ delay: delay,
+ args: Array.slice(arguments, 2)
+ });
+ };
+ exports.clearTimeout = function ContentScriptClearTimeout(id) {
+ unregisterTimer(id);
+ };
+
+ exports.setInterval = function ContentScriptSetInterval(callback, delay) {
+ return registerTimer({
+ kind: "interval",
+ fun: callback,
+ delay: delay,
+ args: Array.slice(arguments, 2)
+ });
+ };
+ exports.clearInterval = function ContentScriptClearInterval(id) {
+ unregisterTimer(id);
+ };
+
+ // On page-hide, save a list of all existing timers before disabling them,
+ // in order to be able to restore them on page-show.
+ // These events are fired when the page goes in/out of bfcache.
+ // https://developer.mozilla.org/En/Working_with_BFCache
+ let frozenTimers = [];
+ pipe.on("pageshow", function onPageShow() {
+ frozenTimers.forEach(registerTimer);
+ });
+ pipe.on("pagehide", function onPageHide() {
+ frozenTimers = [];
+ for (let id in _timers)
+ frozenTimers.push(_timers[id]);
+ disableAllTimers();
+ // Some other pagehide listeners may register some timers that won't be
+ // frozen as this particular pagehide listener is called first.
+ // So freeze these timers on next cycle.
+ chromeSetTimeout(function () {
+ for (let id in _timers)
+ frozenTimers.push(_timers[id]);
+ disableAllTimers();
+ }, 0);
+ });
+
+ // Unregister all timers when the page is destroyed
+ // (i.e. when it is removed from bfcache)
+ pipe.on("detach", function clearTimeouts() {
+ disableAllTimers();
+ _timers = {};
+ frozenTimers = [];
+ });
+ },
+
+ injectMessageAPI: function injectMessageAPI(exports, pipe, console) {
+
+ let ContentWorker = this;
+ let { eventEmitter: port, emit : portEmit } =
+ ContentWorker.createEventEmitter(pipe.emit.bind(null, "event"));
+ pipe.on("event", portEmit);
+
+ let self = {
+ port: port,
+ postMessage: pipe.emit.bind(null, "message"),
+ on: pipe.on.bind(null),
+ once: pipe.once.bind(null),
+ removeListener: pipe.removeListener.bind(null),
+ };
+ Object.defineProperty(exports, "self", {
+ value: self
+ });
+ },
+
+ injectOptions: function (exports, options) {
+ Object.defineProperty( exports.self, "options", { value: JSON.parse( options ) });
+ },
+
+ inject: function (exports, chromeAPI, emitToChrome, options) {
+ let ContentWorker = this;
+ let { pipe, onChromeEvent } =
+ ContentWorker.createPipe(emitToChrome);
+
+ ContentWorker.injectConsole(exports, pipe);
+ ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console);
+ ContentWorker.injectMessageAPI(exports, pipe, exports.console);
+ if ( options !== undefined ) {
+ ContentWorker.injectOptions(exports, options);
+ }
+
+ Object.freeze( exports.self );
+
+ return onChromeEvent;
+ }
+});
diff --git a/components/jetpack/sdk/content/content.js b/components/jetpack/sdk/content/content.js
new file mode 100644
index 000000000..9655223a3
--- /dev/null
+++ b/components/jetpack/sdk/content/content.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "deprecated"
+};
+
+const { deprecateUsage } = require('../util/deprecate');
+
+Object.defineProperty(exports, "Worker", {
+ get: function() {
+ deprecateUsage('`sdk/content/content` is deprecated. Please use `sdk/content/worker` directly.');
+ return require('./worker').Worker;
+ }
+});
diff --git a/components/jetpack/sdk/content/context-menu.js b/components/jetpack/sdk/content/context-menu.js
new file mode 100644
index 000000000..2955e2f09
--- /dev/null
+++ b/components/jetpack/sdk/content/context-menu.js
@@ -0,0 +1,408 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { Class } = require("../core/heritage");
+const self = require("../self");
+const { WorkerChild } = require("./worker-child");
+const { getInnerId } = require("../window/utils");
+const { Ci } = require("chrome");
+const { Services } = require("resource://gre/modules/Services.jsm");
+const system = require('../system/events');
+const { process } = require('../remote/child');
+
+// These functions are roughly copied from sdk/selection which doesn't work
+// in the content process
+function getElementWithSelection(window) {
+ let element = Services.focus.getFocusedElementForWindow(window, false, {});
+ if (!element)
+ return null;
+
+ try {
+ // Accessing selectionStart and selectionEnd on e.g. a button
+ // results in an exception thrown as per the HTML5 spec. See
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection
+
+ let { value, selectionStart, selectionEnd } = element;
+
+ let hasSelection = typeof value === "string" &&
+ !isNaN(selectionStart) &&
+ !isNaN(selectionEnd) &&
+ selectionStart !== selectionEnd;
+
+ return hasSelection ? element : null;
+ }
+ catch (err) {
+ console.exception(err);
+ return null;
+ }
+}
+
+function safeGetRange(selection, rangeNumber) {
+ try {
+ let { rangeCount } = selection;
+ let range = null;
+
+ for (let rangeNumber = 0; rangeNumber < rangeCount; rangeNumber++ ) {
+ range = selection.getRangeAt(rangeNumber);
+
+ if (range && range.toString())
+ break;
+
+ range = null;
+ }
+
+ return range;
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+function getSelection(window) {
+ let selection = window.getSelection();
+ let range = safeGetRange(selection);
+ if (range)
+ return range.toString();
+
+ let node = getElementWithSelection(window);
+ if (!node)
+ return null;
+
+ return node.value.substring(node.selectionStart, node.selectionEnd);
+}
+
+//These are used by PageContext.isCurrent below. If the popupNode or any of
+//its ancestors is one of these, Firefox uses a tailored context menu, and so
+//the page context doesn't apply.
+const NON_PAGE_CONTEXT_ELTS = [
+ Ci.nsIDOMHTMLAnchorElement,
+ Ci.nsIDOMHTMLAppletElement,
+ Ci.nsIDOMHTMLAreaElement,
+ Ci.nsIDOMHTMLButtonElement,
+ Ci.nsIDOMHTMLCanvasElement,
+ Ci.nsIDOMHTMLEmbedElement,
+ Ci.nsIDOMHTMLImageElement,
+ Ci.nsIDOMHTMLInputElement,
+ Ci.nsIDOMHTMLMapElement,
+ Ci.nsIDOMHTMLMediaElement,
+ Ci.nsIDOMHTMLMenuElement,
+ Ci.nsIDOMHTMLObjectElement,
+ Ci.nsIDOMHTMLOptionElement,
+ Ci.nsIDOMHTMLSelectElement,
+ Ci.nsIDOMHTMLTextAreaElement,
+];
+
+// List all editable types of inputs. Or is it better to have a list
+// of non-editable inputs?
+var editableInputs = {
+ email: true,
+ number: true,
+ password: true,
+ search: true,
+ tel: true,
+ text: true,
+ textarea: true,
+ url: true
+};
+
+var CONTEXTS = {};
+
+var Context = Class({
+ initialize: function(id) {
+ this.id = id;
+ },
+
+ adjustPopupNode: function adjustPopupNode(popupNode) {
+ return popupNode;
+ },
+
+ // Gets state to pass through to the parent process for the node the user
+ // clicked on
+ getState: function(popupNode) {
+ return false;
+ }
+});
+
+// Matches when the context-clicked node doesn't have any of
+// NON_PAGE_CONTEXT_ELTS in its ancestors
+CONTEXTS.PageContext = Class({
+ extends: Context,
+
+ getState: function(popupNode) {
+ // If there is a selection in the window then this context does not match
+ if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed)
+ return false;
+
+ // If the clicked node or any of its ancestors is one of the blocked
+ // NON_PAGE_CONTEXT_ELTS then this context does not match
+ while (!(popupNode instanceof Ci.nsIDOMDocument)) {
+ if (NON_PAGE_CONTEXT_ELTS.some(type => popupNode instanceof type))
+ return false;
+
+ popupNode = popupNode.parentNode;
+ }
+
+ return true;
+ }
+});
+
+// Matches when there is an active selection in the window
+CONTEXTS.SelectionContext = Class({
+ extends: Context,
+
+ getState: function(popupNode) {
+ if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed)
+ return true;
+
+ try {
+ // The node may be a text box which has selectionStart and selectionEnd
+ // properties. If not this will throw.
+ let { selectionStart, selectionEnd } = popupNode;
+ return !isNaN(selectionStart) && !isNaN(selectionEnd) &&
+ selectionStart !== selectionEnd;
+ }
+ catch (e) {
+ return false;
+ }
+ }
+});
+
+// Matches when the context-clicked node or any of its ancestors matches the
+// selector given
+CONTEXTS.SelectorContext = Class({
+ extends: Context,
+
+ initialize: function initialize(id, selector) {
+ Context.prototype.initialize.call(this, id);
+ this.selector = selector;
+ },
+
+ adjustPopupNode: function adjustPopupNode(popupNode) {
+ let selector = this.selector;
+
+ while (!(popupNode instanceof Ci.nsIDOMDocument)) {
+ if (popupNode.matches(selector))
+ return popupNode;
+
+ popupNode = popupNode.parentNode;
+ }
+
+ return null;
+ },
+
+ getState: function(popupNode) {
+ return !!this.adjustPopupNode(popupNode);
+ }
+});
+
+// Matches when the page url matches any of the patterns given
+CONTEXTS.URLContext = Class({
+ extends: Context,
+
+ getState: function(popupNode) {
+ return popupNode.ownerDocument.URL;
+ }
+});
+
+// Matches when the user-supplied predicate returns true
+CONTEXTS.PredicateContext = Class({
+ extends: Context,
+
+ getState: function(node) {
+ let window = node.ownerDocument.defaultView;
+ let data = {};
+
+ data.documentType = node.ownerDocument.contentType;
+
+ data.documentURL = node.ownerDocument.location.href;
+ data.targetName = node.nodeName.toLowerCase();
+ data.targetID = node.id || null ;
+
+ if ((data.targetName === 'input' && editableInputs[node.type]) ||
+ data.targetName === 'textarea') {
+ data.isEditable = !node.readOnly && !node.disabled;
+ }
+ else {
+ data.isEditable = node.isContentEditable;
+ }
+
+ data.selectionText = getSelection(window, "TEXT");
+
+ data.srcURL = node.src || null;
+ data.value = node.value || null;
+
+ while (!data.linkURL && node) {
+ data.linkURL = node.href || null;
+ node = node.parentNode;
+ }
+
+ return data;
+ },
+});
+
+function instantiateContext({ id, type, args }) {
+ if (!(type in CONTEXTS)) {
+ console.error("Attempt to use unknown context " + type);
+ return;
+ }
+ return new CONTEXTS[type](id, ...args);
+}
+
+var ContextWorker = Class({
+ implements: [ WorkerChild ],
+
+ // Calls the context workers context listeners and returns the first result
+ // that is either a string or a value that evaluates to true. If all of the
+ // listeners returned false then returns false. If there are no listeners,
+ // returns true (show the menu item by default).
+ getMatchedContext: function getCurrentContexts(popupNode) {
+ let results = this.sandbox.emitSync("context", popupNode);
+ if (!results.length)
+ return true;
+ return results.reduce((val, result) => val || result);
+ },
+
+ // Emits a click event in the worker's port. popupNode is the node that was
+ // context-clicked, and clickedItemData is the data of the item that was
+ // clicked.
+ fireClick: function fireClick(popupNode, clickedItemData) {
+ this.sandbox.emitSync("click", popupNode, clickedItemData);
+ }
+});
+
+// Gets the item's content script worker for a window, creating one if necessary
+// Once created it will be automatically destroyed when the window unloads.
+// If there is not content scripts for the item then null will be returned.
+function getItemWorkerForWindow(item, window) {
+ if (!item.contentScript && !item.contentScriptFile)
+ return null;
+
+ let id = getInnerId(window);
+ let worker = item.workerMap.get(id);
+
+ if (worker)
+ return worker;
+
+ worker = ContextWorker({
+ id: item.id,
+ window,
+ manager: item.manager,
+ contentScript: item.contentScript,
+ contentScriptFile: item.contentScriptFile,
+ onDetach: function() {
+ item.workerMap.delete(id);
+ }
+ });
+
+ item.workerMap.set(id, worker);
+
+ return worker;
+}
+
+// A very simple remote proxy for every item. It's job is to provide data for
+// the main process to use to determine visibility state and to call into
+// content scripts when clicked.
+var RemoteItem = Class({
+ initialize: function(options, manager) {
+ this.id = options.id;
+ this.contexts = options.contexts.map(instantiateContext);
+ this.contentScript = options.contentScript;
+ this.contentScriptFile = options.contentScriptFile;
+
+ this.manager = manager;
+
+ this.workerMap = new Map();
+ keepAlive.set(this.id, this);
+ },
+
+ destroy: function() {
+ for (let worker of this.workerMap.values()) {
+ worker.destroy();
+ }
+ keepAlive.delete(this.id);
+ },
+
+ activate: function(popupNode, data) {
+ let worker = getItemWorkerForWindow(this, popupNode.ownerDocument.defaultView);
+ if (!worker)
+ return;
+
+ for (let context of this.contexts)
+ popupNode = context.adjustPopupNode(popupNode);
+
+ worker.fireClick(popupNode, data);
+ },
+
+ // Fills addonInfo with state data to send through to the main process
+ getContextState: function(popupNode, addonInfo) {
+ if (!(self.id in addonInfo)) {
+ addonInfo[self.id] = {
+ processID: process.id,
+ items: {}
+ };
+ }
+
+ let worker = getItemWorkerForWindow(this, popupNode.ownerDocument.defaultView);
+ let contextStates = {};
+ for (let context of this.contexts)
+ contextStates[context.id] = context.getState(popupNode);
+
+ addonInfo[self.id].items[this.id] = {
+ // It isn't ideal to create a PageContext for every item but there isn't
+ // a good shared place to do it.
+ pageContext: (new CONTEXTS.PageContext()).getState(popupNode),
+ contextStates,
+ hasWorker: !!worker,
+ workerContext: worker ? worker.getMatchedContext(popupNode) : true
+ }
+ }
+});
+exports.RemoteItem = RemoteItem;
+
+// Holds remote items for this frame.
+var keepAlive = new Map();
+
+// Called to create remote proxies for items. If they already exist we destroy
+// and recreate. This can happen if the item changes in some way or in odd
+// timing cases where the frame script is create around the same time as the
+// item is created in the main process
+process.port.on('sdk/contextmenu/createitems', (process, items) => {
+ for (let itemoptions of items) {
+ let oldItem = keepAlive.get(itemoptions.id);
+ if (oldItem) {
+ oldItem.destroy();
+ }
+
+ let item = new RemoteItem(itemoptions, this);
+ }
+});
+
+process.port.on('sdk/contextmenu/destroyitems', (process, items) => {
+ for (let id of items) {
+ let item = keepAlive.get(id);
+ item.destroy();
+ }
+});
+
+var lastPopupNode = null;
+
+system.on('content-contextmenu', ({ subject }) => {
+ let { event: { target: popupNode }, addonInfo } = subject.wrappedJSObject;
+ lastPopupNode = popupNode;
+
+ for (let item of keepAlive.values()) {
+ item.getContextState(popupNode, addonInfo);
+ }
+}, true);
+
+process.port.on('sdk/contextmenu/activateitems', (process, items, data) => {
+ for (let id of items) {
+ let item = keepAlive.get(id);
+ if (!item)
+ continue;
+
+ item.activate(lastPopupNode, data);
+ }
+});
diff --git a/components/jetpack/sdk/content/events.js b/components/jetpack/sdk/content/events.js
new file mode 100644
index 000000000..c085b6179
--- /dev/null
+++ b/components/jetpack/sdk/content/events.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Ci } = require("chrome");
+const { open } = require("../event/dom");
+const { observe } = require("../event/chrome");
+const { filter, merge, map, expand } = require("../event/utils");
+const { windows } = require("../window/utils");
+const { events: windowEvents } = require("sdk/window/events");
+
+// Note: Please note that even though pagehide event is included
+// it's not observable reliably since it's not always triggered
+// when closing tabs. Implementation can be imrpoved once that
+// event will be necessary.
+var TYPES = ["DOMContentLoaded", "load", "pageshow", "pagehide"];
+
+var insert = observe("document-element-inserted");
+var windowCreate = merge([
+ observe("content-document-global-created"),
+ observe("chrome-document-global-created")
+]);
+var create = map(windowCreate, function({target, data, type}) {
+ return { target: target.document, type: type, data: data }
+});
+
+function streamEventsFrom({document}) {
+ // Map supported event types to a streams of those events on the given
+ // `window` for the inserted document and than merge these streams into
+ // single form stream off all window state change events.
+ let stateChanges = TYPES.map(function(type) {
+ return open(document, type, { capture: true });
+ });
+
+ // Since load events on document occur for every loded resource
+ return filter(merge(stateChanges), function({target}) {
+ return target instanceof Ci.nsIDOMDocument
+ })
+}
+exports.streamEventsFrom = streamEventsFrom;
+
+var opened = windows(null, { includePrivate: true });
+var state = merge(opened.map(streamEventsFrom));
+
+
+var futureReady = filter(windowEvents, ({type}) =>
+ type === "DOMContentLoaded");
+var futureWindows = map(futureReady, ({target}) => target);
+var futureState = expand(futureWindows, streamEventsFrom);
+
+exports.events = merge([insert, create, state, futureState]);
diff --git a/components/jetpack/sdk/content/l10n-html.js b/components/jetpack/sdk/content/l10n-html.js
new file mode 100644
index 000000000..f324623dc
--- /dev/null
+++ b/components/jetpack/sdk/content/l10n-html.js
@@ -0,0 +1,133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Ci, Cc, Cu } = require("chrome");
+const core = require("../l10n/core");
+const { loadSheet, removeSheet } = require("../stylesheet/utils");
+const { process, frames } = require("../remote/child");
+var observerService = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
+const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
+const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
+
+const assetsURI = require('../self').data.url();
+
+const hideSheetUri = "data:text/css,:root {visibility: hidden !important;}";
+
+function translateElementAttributes(element) {
+ // Translateable attributes
+ const attrList = ['title', 'accesskey', 'alt', 'label', 'placeholder'];
+ const ariaAttrMap = {
+ 'ariaLabel': 'aria-label',
+ 'ariaValueText': 'aria-valuetext',
+ 'ariaMozHint': 'aria-moz-hint'
+ };
+ const attrSeparator = '.';
+
+ // Try to translate each of the attributes
+ for (let attribute of attrList) {
+ const data = core.get(element.dataset.l10nId + attrSeparator + attribute);
+ if (data)
+ element.setAttribute(attribute, data);
+ }
+
+ // Look for the aria attribute translations that match fxOS's aliases
+ for (let attrAlias in ariaAttrMap) {
+ const data = core.get(element.dataset.l10nId + attrSeparator + attrAlias);
+ if (data)
+ element.setAttribute(ariaAttrMap[attrAlias], data);
+ }
+}
+
+// Taken from Gaia:
+// https://github.com/andreasgal/gaia/blob/04fde2640a7f40314643016a5a6c98bf3755f5fd/webapi.js#L1470
+function translateElement(element) {
+ element = element || document;
+
+ // check all translatable children (= w/ a `data-l10n-id' attribute)
+ var children = element.querySelectorAll('*[data-l10n-id]');
+ var elementCount = children.length;
+ for (var i = 0; i < elementCount; i++) {
+ var child = children[i];
+
+ // translate the child
+ var key = child.dataset.l10nId;
+ var data = core.get(key);
+ if (data)
+ child.textContent = data;
+
+ translateElementAttributes(child);
+ }
+}
+exports.translateElement = translateElement;
+
+function onDocumentReady2Translate(event) {
+ let document = event.target;
+ document.removeEventListener("DOMContentLoaded", onDocumentReady2Translate,
+ false);
+
+ translateElement(document);
+
+ try {
+ // Finally display document when we finished replacing all text content
+ if (document.defaultView)
+ removeSheet(document.defaultView, hideSheetUri, 'user');
+ }
+ catch(e) {
+ console.exception(e);
+ }
+}
+
+function onContentWindow(document) {
+ // Accept only HTML documents
+ if (!(document instanceof Ci.nsIDOMHTMLDocument))
+ return;
+
+ // Bug 769483: data:URI documents instanciated with nsIDOMParser
+ // have a null `location` attribute at this time
+ if (!document.location)
+ return;
+
+ // Accept only document from this addon
+ if (document.location.href.indexOf(assetsURI) !== 0)
+ return;
+
+ try {
+ // First hide content of the document in order to have content blinking
+ // between untranslated and translated states
+ loadSheet(document.defaultView, hideSheetUri, 'user');
+ }
+ catch(e) {
+ console.exception(e);
+ }
+ // Wait for DOM tree to be built before applying localization
+ document.addEventListener("DOMContentLoaded", onDocumentReady2Translate,
+ false);
+}
+
+// Listen to creation of content documents in order to translate them as soon
+// as possible in their loading process
+const ON_CONTENT = "document-element-inserted";
+let enabled = false;
+function enable() {
+ if (enabled)
+ return;
+ addObserver(onContentWindow, ON_CONTENT, false);
+ enabled = true;
+}
+process.port.on("sdk/l10n/html/enable", enable);
+
+function disable() {
+ if (!enabled)
+ return;
+ removeObserver(onContentWindow, ON_CONTENT);
+ enabled = false;
+}
+process.port.on("sdk/l10n/html/disable", disable);
diff --git a/components/jetpack/sdk/content/loader.js b/components/jetpack/sdk/content/loader.js
new file mode 100644
index 000000000..e4f0dd2aa
--- /dev/null
+++ b/components/jetpack/sdk/content/loader.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { isValidURI, isLocalURL, URL } = require('../url');
+const { contract } = require('../util/contract');
+const { isString, isNil, instanceOf, isJSONable } = require('../lang/type');
+const { validateOptions,
+ string, array, object, either, required } = require('../deprecated/api-utils');
+
+const isValidScriptFile = (value) =>
+ (isString(value) || instanceOf(value, URL)) && isLocalURL(value);
+
+// map of property validations
+const valid = {
+ contentURL: {
+ is: either(string, object),
+ ok: url => isNil(url) || isLocalURL(url) || isValidURI(url),
+ msg: 'The `contentURL` option must be a valid URL.'
+ },
+ contentScriptFile: {
+ is: either(string, object, array),
+ ok: value => isNil(value) || [].concat(value).every(isValidScriptFile),
+ msg: 'The `contentScriptFile` option must be a local URL or an array of URLs.'
+ },
+ contentScript: {
+ is: either(string, array),
+ ok: value => isNil(value) || [].concat(value).every(isString),
+ msg: 'The `contentScript` option must be a string or an array of strings.'
+ },
+ contentScriptWhen: {
+ is: required(string),
+ map: value => value || 'end',
+ ok: value => ~['start', 'ready', 'end'].indexOf(value),
+ msg: 'The `contentScriptWhen` option must be either "start", "ready" or "end".'
+ },
+ contentScriptOptions: {
+ ok: value => isNil(value) || isJSONable(value),
+ msg: 'The contentScriptOptions should be a jsonable value.'
+ }
+};
+exports.validationAttributes = valid;
+
+/**
+ * Shortcut function to validate property with validation.
+ * @param {Object|Number|String} suspect
+ * value to validate
+ * @param {Object} validation
+ * validation rule passed to `api-utils`
+ */
+function validate(suspect, validation) {
+ return validateOptions(
+ { $: suspect },
+ { $: validation }
+ ).$;
+}
+
+function Allow(script) {
+ return {
+ get script() {
+ return script;
+ },
+ set script(value) {
+ script = !!value;
+ }
+ };
+}
+
+exports.contract = contract(valid);
diff --git a/components/jetpack/sdk/content/mod.js b/components/jetpack/sdk/content/mod.js
new file mode 100644
index 000000000..81fe9ee42
--- /dev/null
+++ b/components/jetpack/sdk/content/mod.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Ci } = require("chrome");
+const { dispatcher } = require("../util/dispatcher");
+const { add, remove, iterator } = require("../lang/weak-set");
+
+var getTargetWindow = dispatcher("getTargetWindow");
+
+getTargetWindow.define(function (target) {
+ if (target instanceof Ci.nsIDOMWindow)
+ return target;
+ if (target instanceof Ci.nsIDOMDocument)
+ return target.defaultView || null;
+
+ return null;
+});
+
+exports.getTargetWindow = getTargetWindow;
+
+var attachTo = dispatcher("attachTo");
+exports.attachTo = attachTo;
+
+var detachFrom = dispatcher("detatchFrom");
+exports.detachFrom = detachFrom;
+
+function attach(modification, target) {
+ if (!modification)
+ return;
+
+ let window = getTargetWindow(target);
+
+ attachTo(modification, window);
+
+ // modification are stored per content; `window` reference can still be the
+ // same even if the content is changed, therefore `document` is used instead.
+ add(modification, window.document);
+}
+exports.attach = attach;
+
+function detach(modification, target) {
+ if (!modification)
+ return;
+
+ if (target) {
+ let window = getTargetWindow(target);
+ detachFrom(modification, window);
+ remove(modification, window.document);
+ }
+ else {
+ let documents = iterator(modification);
+ for (let document of documents) {
+ let window = document.defaultView;
+ // The window might have already gone away
+ if (!window)
+ continue;
+ detachFrom(modification, document.defaultView);
+ remove(modification, document);
+ }
+ }
+}
+exports.detach = detach;
diff --git a/components/jetpack/sdk/content/page-mod.js b/components/jetpack/sdk/content/page-mod.js
new file mode 100644
index 000000000..8ff9b1e7b
--- /dev/null
+++ b/components/jetpack/sdk/content/page-mod.js
@@ -0,0 +1,236 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { getAttachEventType } = require('../content/utils');
+const { Class } = require('../core/heritage');
+const { Disposable } = require('../core/disposable');
+const { WeakReference } = require('../core/reference');
+const { WorkerChild } = require('./worker-child');
+const { EventTarget } = require('../event/target');
+const { on, emit, once, setListeners } = require('../event/core');
+const { on: domOn, removeListener: domOff } = require('../dom/events');
+const { isRegExp, isUndefined } = require('../lang/type');
+const { merge } = require('../util/object');
+const { isBrowser, getFrames } = require('../window/utils');
+const { getTabs, getURI: getTabURI } = require('../tabs/utils');
+const { ignoreWindow } = require('../private-browsing/utils');
+const { Style } = require("../stylesheet/style");
+const { attach, detach } = require("../content/mod");
+const { has, hasAny } = require("../util/array");
+const { Rules } = require("../util/rules");
+const { List, addListItem, removeListItem } = require('../util/list');
+const { when } = require("../system/unload");
+const { uuid } = require('../util/uuid');
+const { frames, process } = require('../remote/child');
+
+const pagemods = new Map();
+const styles = new WeakMap();
+var styleFor = (mod) => styles.get(mod);
+
+// Helper functions
+var modMatchesURI = (mod, uri) => mod.include.matchesAny(uri) && !mod.exclude.matchesAny(uri);
+
+/**
+ * PageMod constructor (exported below).
+ * @constructor
+ */
+const ChildPageMod = Class({
+ implements: [
+ EventTarget,
+ Disposable,
+ ],
+ setup: function PageMod(model) {
+ merge(this, model);
+
+ // Set listeners on {PageMod} itself, not the underlying worker,
+ // like `onMessage`, as it'll get piped.
+ setListeners(this, model);
+
+ function deserializeRules(rules) {
+ for (let rule of rules) {
+ yield rule.type == "string" ? rule.value
+ : new RegExp(rule.pattern, rule.flags);
+ }
+ }
+
+ let include = [...deserializeRules(this.include)];
+ this.include = Rules();
+ this.include.add.apply(this.include, include);
+
+ let exclude = [...deserializeRules(this.exclude)];
+ this.exclude = Rules();
+ this.exclude.add.apply(this.exclude, exclude);
+
+ if (this.contentStyle || this.contentStyleFile) {
+ styles.set(this, Style({
+ uri: this.contentStyleFile,
+ source: this.contentStyle
+ }));
+ }
+
+ pagemods.set(this.id, this);
+ this.seenDocuments = new WeakMap();
+
+ // `applyOnExistingDocuments` has to be called after `pagemods.add()`
+ // otherwise its calls to `onContent` method won't do anything.
+ if (has(this.attachTo, 'existing'))
+ applyOnExistingDocuments(this);
+ },
+
+ dispose: function() {
+ let style = styleFor(this);
+ if (style)
+ detach(style);
+
+ for (let i in this.include)
+ this.include.remove(this.include[i]);
+
+ pagemods.delete(this.id);
+ }
+});
+
+function onContentWindow({ target: document }) {
+ // Return if we have no pagemods
+ if (pagemods.size === 0)
+ return;
+
+ let window = document.defaultView;
+ // XML documents don't have windows, and we don't yet support them.
+ if (!window)
+ return;
+
+ // Frame event listeners are bound to the frame the event came from by default
+ let frame = this;
+ // We apply only on documents in tabs of Firefox
+ if (!frame.isTab)
+ return;
+
+ // When the tab is private, only addons with 'private-browsing' flag in
+ // their package.json can apply content script to private documents
+ if (ignoreWindow(window))
+ return;
+
+ for (let pagemod of pagemods.values()) {
+ if (modMatchesURI(pagemod, window.location.href))
+ onContent(pagemod, window);
+ }
+}
+frames.addEventListener("DOMDocElementInserted", onContentWindow, true);
+
+function applyOnExistingDocuments (mod) {
+ for (let frame of frames) {
+ // Fake a newly created document
+ let window = frame.content;
+ // on startup with e10s, contentWindow might not exist yet,
+ // in which case we will get notified by "document-element-inserted".
+ if (!window || !window.frames)
+ return;
+ let uri = window.location.href;
+ if (has(mod.attachTo, "top") && modMatchesURI(mod, uri))
+ onContent(mod, window);
+ if (has(mod.attachTo, "frame"))
+ getFrames(window).
+ filter(iframe => modMatchesURI(mod, iframe.location.href)).
+ forEach(frame => onContent(mod, frame));
+ }
+}
+
+function createWorker(mod, window) {
+ let workerId = String(uuid());
+
+ // Instruct the parent to connect to this worker. Do this first so the parent
+ // side is connected before the worker attempts to send any messages there
+ let frame = frames.getFrameForWindow(window.top);
+ frame.port.emit('sdk/page-mod/worker-create', mod.id, {
+ id: workerId,
+ url: window.location.href
+ });
+
+ // Create a child worker and notify the parent
+ let worker = WorkerChild({
+ id: workerId,
+ window: window,
+ contentScript: mod.contentScript,
+ contentScriptFile: mod.contentScriptFile,
+ contentScriptOptions: mod.contentScriptOptions
+ });
+
+ once(worker, 'detach', () => worker.destroy());
+}
+
+function onContent (mod, window) {
+ let isTopDocument = window.top === window;
+ // Is a top level document and `top` is not set, ignore
+ if (isTopDocument && !has(mod.attachTo, "top"))
+ return;
+ // Is a frame document and `frame` is not set, ignore
+ if (!isTopDocument && !has(mod.attachTo, "frame"))
+ return;
+
+ // ensure we attach only once per document
+ let seen = mod.seenDocuments;
+ if (seen.has(window.document))
+ return;
+ seen.set(window.document, true);
+
+ let style = styleFor(mod);
+ if (style)
+ attach(style, window);
+
+ // Immediately evaluate content script if the document state is already
+ // matching contentScriptWhen expectations
+ if (isMatchingAttachState(mod, window)) {
+ createWorker(mod, window);
+ return;
+ }
+
+ let eventName = getAttachEventType(mod) || 'load';
+ domOn(window, eventName, function onReady (e) {
+ if (e.target.defaultView !== window)
+ return;
+ domOff(window, eventName, onReady, true);
+ createWorker(mod, window);
+
+ // Attaching is asynchronous so if the document is already loaded we will
+ // miss the pageshow event so send a synthetic one.
+ if (window.document.readyState == "complete") {
+ mod.on('attach', worker => {
+ try {
+ worker.send('pageshow');
+ emit(worker, 'pageshow');
+ }
+ catch (e) {
+ // This can fail if an earlier attach listener destroyed the worker
+ }
+ });
+ }
+ }, true);
+}
+
+function isMatchingAttachState (mod, window) {
+ let state = window.document.readyState;
+ return 'start' === mod.contentScriptWhen ||
+ // Is `load` event already dispatched?
+ 'complete' === state ||
+ // Is DOMContentLoaded already dispatched and waiting for it?
+ ('ready' === mod.contentScriptWhen && state === 'interactive')
+}
+
+process.port.on('sdk/page-mod/create', (process, model) => {
+ if (pagemods.has(model.id))
+ return;
+
+ new ChildPageMod(model);
+});
+
+process.port.on('sdk/page-mod/destroy', (process, id) => {
+ let mod = pagemods.get(id);
+ if (mod)
+ mod.destroy();
+});
diff --git a/components/jetpack/sdk/content/page-worker.js b/components/jetpack/sdk/content/page-worker.js
new file mode 100644
index 000000000..e9e741120
--- /dev/null
+++ b/components/jetpack/sdk/content/page-worker.js
@@ -0,0 +1,154 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { frames } = require("../remote/child");
+const { Class } = require("../core/heritage");
+const { Disposable } = require('../core/disposable');
+const { data } = require("../self");
+const { once } = require("../dom/events");
+const { getAttachEventType } = require("./utils");
+const { Rules } = require('../util/rules');
+const { uuid } = require('../util/uuid');
+const { WorkerChild } = require("./worker-child");
+const { Cc, Ci, Cu } = require("chrome");
+const { observe } = require("../event/chrome");
+const { on } = require("../event/core");
+
+const appShell = Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci.nsIAppShellService);
+
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+
+const pages = new Map();
+
+const DOC_INSERTED = "document-element-inserted";
+
+function isValidURL(page, url) {
+ return !page.rules || page.rules.matchesAny(url);
+}
+
+const ChildPage = Class({
+ implements: [ Disposable ],
+ setup: function(frame, id, options) {
+ this.id = id;
+ this.frame = frame;
+ this.options = options;
+
+ this.webNav = appShell.createWindowlessBrowser(false);
+ this.docShell.allowJavascript = this.options.allow.script;
+
+ // Accessing the browser's window forces the initial about:blank document to
+ // be created before we start listening for notifications
+ this.contentWindow;
+
+ this.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
+
+ pages.set(this.id, this);
+
+ this.contentURL = options.contentURL;
+
+ if (options.include) {
+ this.rules = Rules();
+ this.rules.add.apply(this.rules, [].concat(options.include));
+ }
+ },
+
+ dispose: function() {
+ pages.delete(this.id);
+ this.webProgress.removeProgressListener(this);
+ this.webNav.close();
+ this.webNav = null;
+ },
+
+ attachWorker: function() {
+ if (!isValidURL(this, this.contentWindow.location.href))
+ return;
+
+ this.options.id = uuid().toString();
+ this.options.window = this.contentWindow;
+ this.frame.port.emit("sdk/frame/connect", this.id, {
+ id: this.options.id,
+ url: this.contentWindow.document.documentURIObject.spec
+ });
+ new WorkerChild(this.options);
+ },
+
+ get docShell() {
+ return this.webNav.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+ },
+
+ get webProgress() {
+ return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress);
+ },
+
+ get contentWindow() {
+ return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ },
+
+ get contentURL() {
+ return this.options.contentURL;
+ },
+ set contentURL(url) {
+ this.options.contentURL = url;
+
+ url = this.options.contentURL ? data.url(this.options.contentURL) : "about:blank";
+ this.webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
+ },
+
+ onLocationChange: function(progress, request, location, flags) {
+ // Ignore inner-frame events
+ if (progress != this.webProgress)
+ return;
+ // Ignore events that don't change the document
+ if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
+ return;
+
+ let event = getAttachEventType(this.options);
+ // Attaching at the start of the load is handled by the
+ // document-element-inserted listener.
+ if (event == DOC_INSERTED)
+ return;
+
+ once(this.contentWindow, event, () => {
+ this.attachWorker();
+ }, false);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"])
+});
+
+on(observe(DOC_INSERTED), "data", ({ target }) => {
+ let page = Array.from(pages.values()).find(p => p.contentWindow.document === target);
+ if (!page)
+ return;
+
+ if (getAttachEventType(page.options) == DOC_INSERTED)
+ page.attachWorker();
+});
+
+frames.port.on("sdk/frame/create", (frame, id, options) => {
+ new ChildPage(frame, id, options);
+});
+
+frames.port.on("sdk/frame/set", (frame, id, params) => {
+ let page = pages.get(id);
+ if (!page)
+ return;
+
+ if ("allowScript" in params)
+ page.docShell.allowJavascript = params.allowScript;
+ if ("contentURL" in params)
+ page.contentURL = params.contentURL;
+});
+
+frames.port.on("sdk/frame/destroy", (frame, id) => {
+ let page = pages.get(id);
+ if (!page)
+ return;
+
+ page.destroy();
+});
diff --git a/components/jetpack/sdk/content/sandbox.js b/components/jetpack/sdk/content/sandbox.js
new file mode 100644
index 000000000..096ba5c87
--- /dev/null
+++ b/components/jetpack/sdk/content/sandbox.js
@@ -0,0 +1,426 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const { Class } = require('../core/heritage');
+const { EventTarget } = require('../event/target');
+const { on, off, emit } = require('../event/core');
+const { events } = require('./sandbox/events');
+const { requiresAddonGlobal } = require('./utils');
+const { delay: async } = require('../lang/functional');
+const { Ci, Cu, Cc } = require('chrome');
+const timer = require('../timers');
+const { URL } = require('../url');
+const { sandbox, evaluate, load } = require('../loader/sandbox');
+const { merge } = require('../util/object');
+const { getTabForContentWindowNoShim } = require('../tabs/utils');
+const { getInnerId } = require('../window/utils');
+const { PlainTextConsole } = require('../console/plain-text');
+const { data } = require('../self');const { isChildLoader } = require('../remote/core');
+// WeakMap of sandboxes so we can access private values
+const sandboxes = new WeakMap();
+
+/* Trick the linker in order to ensure shipping these files in the XPI.
+ require('./content-worker.js');
+ Then, retrieve URL of these files in the XPI:
+*/
+var prefix = module.uri.split('sandbox.js')[0];
+const CONTENT_WORKER_URL = prefix + 'content-worker.js';
+const metadata = require('@loader/options').metadata;
+
+// Fetch additional list of domains to authorize access to for each content
+// script. It is stored in manifest `metadata` field which contains
+// package.json data. This list is originaly defined by authors in
+// `permissions` attribute of their package.json addon file.
+const permissions = (metadata && metadata['permissions']) || {};
+const EXPANDED_PRINCIPALS = permissions['cross-domain-content'] || [];
+
+const waiveSecurityMembrane = !!permissions['unsafe-content-script'];
+
+const nsIScriptSecurityManager = Ci.nsIScriptSecurityManager;
+const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+
+const JS_VERSION = '1.8';
+
+// Tests whether this window is loaded in a tab
+function isWindowInTab(window) {
+ if (isChildLoader) {
+ let { frames } = require('../remote/child');
+ let frame = frames.getFrameForWindow(window.top);
+ return frame && frame.isTab;
+ }
+ else {
+ // The deprecated sync worker API still does everything in the main process
+ return getTabForContentWindowNoShim(window);
+ }
+}
+
+const WorkerSandbox = Class({
+ implements: [ EventTarget ],
+
+ /**
+ * Emit a message to the worker content sandbox
+ */
+ emit: function emit(type, ...args) {
+ // JSON.stringify is buggy with cross-sandbox values,
+ // it may return "{}" on functions. Use a replacer to match them correctly.
+ let replacer = (k, v) =>
+ typeof(v) === "function"
+ ? (type === "console" ? Function.toString.call(v) : void(0))
+ : v;
+
+ // Ensure having an asynchronous behavior
+ async(() =>
+ emitToContent(this, JSON.stringify([type, ...args], replacer))
+ );
+ },
+
+ /**
+ * Synchronous version of `emit`.
+ * /!\ Should only be used when it is strictly mandatory /!\
+ * Doesn't ensure passing only JSON values.
+ * Mainly used by context-menu in order to avoid breaking it.
+ */
+ emitSync: function emitSync(...args) {
+ // because the arguments could be also non JSONable values,
+ // we need to ensure the array instance is created from
+ // the content's sandbox
+ return emitToContent(this, new modelFor(this).sandbox.Array(...args));
+ },
+
+ /**
+ * Configures sandbox and loads content scripts into it.
+ * @param {Worker} worker
+ * content worker
+ */
+ initialize: function WorkerSandbox(worker, window) {
+ let model = {};
+ sandboxes.set(this, model);
+ model.worker = worker;
+ // We receive a wrapped window, that may be an xraywrapper if it's content
+ let proto = window;
+
+ // TODO necessary?
+ // Ensure that `emit` has always the right `this`
+ this.emit = this.emit.bind(this);
+ this.emitSync = this.emitSync.bind(this);
+
+ // Use expanded principal for content-script if the content is a
+ // regular web content for better isolation.
+ // (This behavior can be turned off for now with the unsafe-content-script
+ // flag to give addon developers time for making the necessary changes)
+ // But prevent it when the Worker isn't used for a content script but for
+ // injecting `addon` object into a Panel scope, for example.
+ // That's because:
+ // 1/ It is useless to use multiple domains as the worker is only used
+ // to communicate with the addon,
+ // 2/ By using it it would prevent the document to have access to any JS
+ // value of the worker. As JS values coming from multiple domain principals
+ // can't be accessed by 'mono-principals' (principal with only one domain).
+ // Even if this principal is for a domain that is specified in the multiple
+ // domain principal.
+ let principals = window;
+ let wantGlobalProperties = [];
+ let isSystemPrincipal = secMan.isSystemPrincipal(
+ window.document.nodePrincipal);
+ if (!isSystemPrincipal && !requiresAddonGlobal(worker)) {
+ if (EXPANDED_PRINCIPALS.length > 0) {
+ // We have to replace XHR constructor of the content document
+ // with a custom cross origin one, automagically added by platform code:
+ delete proto.XMLHttpRequest;
+ wantGlobalProperties.push('XMLHttpRequest');
+ }
+ if (!waiveSecurityMembrane)
+ principals = EXPANDED_PRINCIPALS.concat(window);
+ }
+
+ // Create the sandbox and bind it to window in order for content scripts to
+ // have access to all standard globals (window, document, ...)
+ let content = sandbox(principals, {
+ sandboxPrototype: proto,
+ wantXrays: !requiresAddonGlobal(worker),
+ wantGlobalProperties: wantGlobalProperties,
+ wantExportHelpers: true,
+ sameZoneAs: window,
+ metadata: {
+ SDKContentScript: true,
+ 'inner-window-id': getInnerId(window)
+ }
+ });
+ model.sandbox = content;
+
+ // We have to ensure that window.top and window.parent are the exact same
+ // object than window object, i.e. the sandbox global object. But not
+ // always, in case of iframes, top and parent are another window object.
+ let top = window.top === window ? content : content.top;
+ let parent = window.parent === window ? content : content.parent;
+ merge(content, {
+ // We need 'this === window === top' to be true in toplevel scope:
+ get window() {
+ return content;
+ },
+ get top() {
+ return top;
+ },
+ get parent() {
+ return parent;
+ }
+ });
+
+ // Use the Greasemonkey naming convention to provide access to the
+ // unwrapped window object so the content script can access document
+ // JavaScript values.
+ // NOTE: this functionality is experimental and may change or go away
+ // at any time!
+ //
+ // Note that because waivers aren't propagated between origins, we
+ // need the unsafeWindow getter to live in the sandbox.
+ var unsafeWindowGetter =
+ new content.Function('return window.wrappedJSObject || window;');
+ Object.defineProperty(content, 'unsafeWindow', {get: unsafeWindowGetter});
+
+ // Load trusted code that will inject content script API.
+ let ContentWorker = load(content, CONTENT_WORKER_URL);
+
+ // prepare a clean `self.options`
+ let options = 'contentScriptOptions' in worker ?
+ JSON.stringify(worker.contentScriptOptions) :
+ undefined;
+
+ // Then call `inject` method and communicate with this script
+ // by trading two methods that allow to send events to the other side:
+ // - `onEvent` called by content script
+ // - `result.emitToContent` called by addon script
+ let onEvent = Cu.exportFunction(onContentEvent.bind(null, this), ContentWorker);
+ let chromeAPI = createChromeAPI(ContentWorker);
+ let result = Cu.waiveXrays(ContentWorker).inject(content, chromeAPI, onEvent, options);
+
+ // Merge `emitToContent` into our private model of the
+ // WorkerSandbox so we can communicate with content script
+ model.emitToContent = result;
+
+ let console = new PlainTextConsole(null, getInnerId(window));
+
+ // Handle messages send by this script:
+ setListeners(this, console);
+
+ // Inject `addon` global into target document if document is trusted,
+ // `addon` in document is equivalent to `self` in content script.
+ if (requiresAddonGlobal(worker)) {
+ Object.defineProperty(getUnsafeWindow(window), 'addon', {
+ value: content.self,
+ configurable: true
+ }
+ );
+ }
+
+ // Inject our `console` into target document if worker doesn't have a tab
+ // (e.g Panel, PageWorker).
+ // `worker.tab` can't be used because bug 804935.
+ if (!isWindowInTab(window)) {
+ let win = getUnsafeWindow(window);
+
+ // export our chrome console to content window, as described here:
+ // https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn
+ let con = Cu.createObjectIn(win);
+
+ let genPropDesc = function genPropDesc(fun) {
+ return { enumerable: true, configurable: true, writable: true,
+ value: console[fun] };
+ }
+
+ const properties = {
+ log: genPropDesc('log'),
+ info: genPropDesc('info'),
+ warn: genPropDesc('warn'),
+ error: genPropDesc('error'),
+ debug: genPropDesc('debug'),
+ trace: genPropDesc('trace'),
+ dir: genPropDesc('dir'),
+ group: genPropDesc('group'),
+ groupCollapsed: genPropDesc('groupCollapsed'),
+ groupEnd: genPropDesc('groupEnd'),
+ time: genPropDesc('time'),
+ timeEnd: genPropDesc('timeEnd'),
+ profile: genPropDesc('profile'),
+ profileEnd: genPropDesc('profileEnd'),
+ exception: genPropDesc('exception'),
+ assert: genPropDesc('assert'),
+ count: genPropDesc('count'),
+ table: genPropDesc('table'),
+ clear: genPropDesc('clear'),
+ dirxml: genPropDesc('dirxml'),
+ markTimeline: genPropDesc('markTimeline'),
+ timeline: genPropDesc('timeline'),
+ timelineEnd: genPropDesc('timelineEnd'),
+ timeStamp: genPropDesc('timeStamp'),
+ };
+
+ Object.defineProperties(con, properties);
+ Cu.makeObjectPropsNormal(con);
+
+ win.console = con;
+ };
+
+ emit(events, "content-script-before-inserted", {
+ window: window,
+ worker: worker
+ });
+
+ // The order of `contentScriptFile` and `contentScript` evaluation is
+ // intentional, so programs can load libraries like jQuery from script URLs
+ // and use them in scripts.
+ let contentScriptFile = ('contentScriptFile' in worker)
+ ? worker.contentScriptFile
+ : null,
+ contentScript = ('contentScript' in worker)
+ ? worker.contentScript
+ : null;
+
+ if (contentScriptFile)
+ importScripts.apply(null, [this].concat(contentScriptFile));
+
+ if (contentScript) {
+ evaluateIn(
+ this,
+ Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript
+ );
+ }
+ },
+ destroy: function destroy(reason) {
+ if (typeof reason != 'string')
+ reason = '';
+ this.emitSync('event', 'detach', reason);
+ let model = modelFor(this);
+ model.sandbox = null
+ model.worker = null;
+ },
+
+});
+
+exports.WorkerSandbox = WorkerSandbox;
+
+/**
+ * Imports scripts to the sandbox by reading files under urls and
+ * evaluating its source. If exception occurs during evaluation
+ * `'error'` event is emitted on the worker.
+ * This is actually an analog to the `importScript` method in web
+ * workers but in our case it's not exposed even though content
+ * scripts may be able to do it synchronously since IO operation
+ * takes place in the UI process.
+ */
+function importScripts (workerSandbox, ...urls) {
+ let { worker, sandbox } = modelFor(workerSandbox);
+ for (let i in urls) {
+ let contentScriptFile = data.url(urls[i]);
+
+ try {
+ let uri = URL(contentScriptFile);
+ if (uri.scheme === 'resource')
+ load(sandbox, String(uri));
+ else
+ throw Error('Unsupported `contentScriptFile` url: ' + String(uri));
+ }
+ catch(e) {
+ emit(worker, 'error', e);
+ }
+ }
+}
+
+function setListeners (workerSandbox, console) {
+ let { worker } = modelFor(workerSandbox);
+ // console.xxx calls
+ workerSandbox.on('console', function consoleListener (kind, ...args) {
+ console[kind].apply(console, args);
+ });
+
+ // self.postMessage calls
+ workerSandbox.on('message', function postMessage(data) {
+ // destroyed?
+ if (worker)
+ emit(worker, 'message', data);
+ });
+
+ // self.port.emit calls
+ workerSandbox.on('event', function portEmit (...eventArgs) {
+ // If not destroyed, emit event information to worker
+ // `eventArgs` has the event name as first element,
+ // and remaining elements are additional arguments to pass
+ if (worker)
+ emit.apply(null, [worker.port].concat(eventArgs));
+ });
+
+ // unwrap, recreate and propagate async Errors thrown from content-script
+ workerSandbox.on('error', function onError({instanceOfError, value}) {
+ if (worker) {
+ let error = value;
+ if (instanceOfError) {
+ error = new Error(value.message, value.fileName, value.lineNumber);
+ error.stack = value.stack;
+ error.name = value.name;
+ }
+ emit(worker, 'error', error);
+ }
+ });
+}
+
+/**
+ * Evaluates code in the sandbox.
+ * @param {String} code
+ * JavaScript source to evaluate.
+ * @param {String} [filename='javascript:' + code]
+ * Name of the file
+ */
+function evaluateIn (workerSandbox, code, filename) {
+ let { worker, sandbox } = modelFor(workerSandbox);
+ try {
+ evaluate(sandbox, code, filename || 'javascript:' + code);
+ }
+ catch(e) {
+ emit(worker, 'error', e);
+ }
+}
+
+/**
+ * Method called by the worker sandbox when it needs to send a message
+ */
+function onContentEvent (workerSandbox, args) {
+ // As `emit`, we ensure having an asynchronous behavior
+ async(function () {
+ // We emit event to chrome/addon listeners
+ emit.apply(null, [workerSandbox].concat(JSON.parse(args)));
+ });
+}
+
+
+function modelFor (workerSandbox) {
+ return sandboxes.get(workerSandbox);
+}
+
+function getUnsafeWindow (win) {
+ return win.wrappedJSObject || win;
+}
+
+function emitToContent (workerSandbox, args) {
+ return modelFor(workerSandbox).emitToContent(args);
+}
+
+function createChromeAPI (scope) {
+ return Cu.cloneInto({
+ timers: {
+ setTimeout: timer.setTimeout.bind(timer),
+ setInterval: timer.setInterval.bind(timer),
+ clearTimeout: timer.clearTimeout.bind(timer),
+ clearInterval: timer.clearInterval.bind(timer),
+ },
+ sandbox: {
+ evaluate: evaluate,
+ },
+ }, scope, {cloneFunctions: true});
+}
diff --git a/components/jetpack/sdk/content/sandbox/events.js b/components/jetpack/sdk/content/sandbox/events.js
new file mode 100644
index 000000000..d6f7eb004
--- /dev/null
+++ b/components/jetpack/sdk/content/sandbox/events.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const events = {};
+exports.events = events;
diff --git a/components/jetpack/sdk/content/tab-events.js b/components/jetpack/sdk/content/tab-events.js
new file mode 100644
index 000000000..9e244a853
--- /dev/null
+++ b/components/jetpack/sdk/content/tab-events.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Ci } = require('chrome');
+const system = require('sdk/system/events');
+const { frames } = require('sdk/remote/child');
+const { WorkerChild } = require('sdk/content/worker-child');
+
+// map observer topics to tab event names
+const EVENTS = {
+ 'content-document-global-created': 'create',
+ 'chrome-document-global-created': 'create',
+ 'content-document-interactive': 'ready',
+ 'chrome-document-interactive': 'ready',
+ 'content-document-loaded': 'load',
+ 'chrome-document-loaded': 'load',
+// 'content-page-shown': 'pageshow', // bug 1024105
+}
+
+function topicListener({ subject, type }) {
+ // NOTE detect the window from the subject:
+ // - on *-global-created the subject is the window
+ // - in the other cases it is the document object
+ let window = subject instanceof Ci.nsIDOMWindow ? subject : subject.defaultView;
+ if (!window){
+ return;
+ }
+ let frame = frames.getFrameForWindow(window);
+ if (frame) {
+ let readyState = frame.content.document.readyState;
+ frame.port.emit('sdk/tab/event', EVENTS[type], { readyState });
+ }
+}
+
+for (let topic in EVENTS)
+ system.on(topic, topicListener, true);
+
+// bug 1024105 - content-page-shown notification doesn't pass persisted param
+function eventListener({target, type, persisted}) {
+ let frame = this;
+ if (target === frame.content.document) {
+ frame.port.emit('sdk/tab/event', type, persisted);
+ }
+}
+frames.addEventListener('pageshow', eventListener, true);
+
+frames.port.on('sdk/tab/attach', (frame, options) => {
+ options.window = frame.content;
+ new WorkerChild(options);
+});
+
+// Forward the existent frames's readyState.
+for (let frame of frames) {
+ let readyState = frame.content.document.readyState;
+ frame.port.emit('sdk/tab/event', 'init', { readyState });
+}
diff --git a/components/jetpack/sdk/content/thumbnail.js b/components/jetpack/sdk/content/thumbnail.js
new file mode 100644
index 000000000..783615fc6
--- /dev/null
+++ b/components/jetpack/sdk/content/thumbnail.js
@@ -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/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const { Cc, Ci, Cu } = require('chrome');
+const AppShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+
+const NS = 'http://www.w3.org/1999/xhtml';
+const COLOR = 'rgb(255,255,255)';
+
+/**
+ * Creates canvas element with a thumbnail of the passed window.
+ * @param {Window} window
+ * @returns {Element}
+ */
+function getThumbnailCanvasForWindow(window) {
+ let aspectRatio = 0.5625; // 16:9
+ let thumbnail = AppShellService.hiddenDOMWindow.document
+ .createElementNS(NS, 'canvas');
+ thumbnail.mozOpaque = true;
+ thumbnail.width = Math.ceil(window.screen.availWidth / 5.75);
+ thumbnail.height = Math.round(thumbnail.width * aspectRatio);
+ let ctx = thumbnail.getContext('2d');
+ let snippetWidth = window.innerWidth * .6;
+ let scale = thumbnail.width / snippetWidth;
+ ctx.scale(scale, scale);
+ ctx.drawWindow(window, window.scrollX, window.scrollY, snippetWidth,
+ snippetWidth * aspectRatio, COLOR);
+ return thumbnail;
+}
+exports.getThumbnailCanvasForWindow = getThumbnailCanvasForWindow;
+
+/**
+ * Creates Base64 encoded data URI of the thumbnail for the passed window.
+ * @param {Window} window
+ * @returns {String}
+ */
+exports.getThumbnailURIForWindow = function getThumbnailURIForWindow(window) {
+ return getThumbnailCanvasForWindow(window).toDataURL()
+};
+
+// default 80x45 blank when not available
+exports.BLANK = 'data:image/png;base64,' +
+ 'iVBORw0KGgoAAAANSUhEUgAAAFAAAAAtCAYAAAA5reyyAAAAJElEQVRoge3BAQ'+
+ 'EAAACCIP+vbkhAAQAAAAAAAAAAAAAAAADXBjhtAAGQ0AF/AAAAAElFTkSuQmCC';
diff --git a/components/jetpack/sdk/content/utils.js b/components/jetpack/sdk/content/utils.js
new file mode 100644
index 000000000..90995a614
--- /dev/null
+++ b/components/jetpack/sdk/content/utils.js
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+var { merge } = require('../util/object');
+var { data } = require('../self');
+var assetsURI = data.url();
+var isArray = Array.isArray;
+var method = require('../../method/core');
+var { uuid } = require('../util/uuid');
+
+const isAddonContent = ({ contentURL }) =>
+ contentURL && data.url(contentURL).startsWith(assetsURI);
+
+exports.isAddonContent = isAddonContent;
+
+function hasContentScript({ contentScript, contentScriptFile }) {
+ return (isArray(contentScript) ? contentScript.length > 0 :
+ !!contentScript) ||
+ (isArray(contentScriptFile) ? contentScriptFile.length > 0 :
+ !!contentScriptFile);
+}
+exports.hasContentScript = hasContentScript;
+
+function requiresAddonGlobal(model) {
+ return model.injectInDocument || (isAddonContent(model) && !hasContentScript(model));
+}
+exports.requiresAddonGlobal = requiresAddonGlobal;
+
+function getAttachEventType(model) {
+ if (!model) return null;
+ let when = model.contentScriptWhen;
+ return requiresAddonGlobal(model) ? 'document-element-inserted' :
+ when === 'start' ? 'document-element-inserted' :
+ when === 'ready' ? 'DOMContentLoaded' :
+ when === 'end' ? 'load' :
+ null;
+}
+exports.getAttachEventType = getAttachEventType;
+
+var attach = method('worker-attach');
+exports.attach = attach;
+
+var connect = method('worker-connect');
+exports.connect = connect;
+
+var detach = method('worker-detach');
+exports.detach = detach;
+
+var destroy = method('worker-destroy');
+exports.destroy = destroy;
+
+function WorkerHost (workerFor) {
+ // Define worker properties that just proxy to underlying worker
+ return ['postMessage', 'port', 'url', 'tab'].reduce(function(proto, name) {
+ // Use descriptor properties instead so we can call
+ // the worker function in the context of the worker so we
+ // don't have to create new functions with `fn.bind(worker)`
+ let descriptorProp = {
+ value: function (...args) {
+ let worker = workerFor(this);
+ return worker[name].apply(worker, args);
+ }
+ };
+
+ let accessorProp = {
+ get: function () { return workerFor(this)[name]; },
+ set: function (value) { workerFor(this)[name] = value; }
+ };
+
+ Object.defineProperty(proto, name, merge({
+ enumerable: true,
+ configurable: false,
+ }, isDescriptor(name) ? descriptorProp : accessorProp));
+ return proto;
+ }, {});
+
+ function isDescriptor (prop) {
+ return ~['postMessage'].indexOf(prop);
+ }
+}
+exports.WorkerHost = WorkerHost;
+
+function makeChildOptions(options) {
+ function makeStringArray(arrayOrValue) {
+ if (!arrayOrValue)
+ return [];
+ return [].concat(arrayOrValue).map(String);
+ }
+
+ return {
+ id: String(uuid()),
+ contentScript: makeStringArray(options.contentScript),
+ contentScriptFile: makeStringArray(options.contentScriptFile),
+ contentScriptOptions: options.contentScriptOptions ?
+ JSON.stringify(options.contentScriptOptions) :
+ null,
+ }
+}
+exports.makeChildOptions = makeChildOptions;
diff --git a/components/jetpack/sdk/content/worker-child.js b/components/jetpack/sdk/content/worker-child.js
new file mode 100644
index 000000000..dbf65a933
--- /dev/null
+++ b/components/jetpack/sdk/content/worker-child.js
@@ -0,0 +1,158 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { merge } = require('../util/object');
+const { Class } = require('../core/heritage');
+const { emit } = require('../event/core');
+const { EventTarget } = require('../event/target');
+const { getInnerId, getByInnerId } = require('../window/utils');
+const { instanceOf, isObject } = require('../lang/type');
+const system = require('../system/events');
+const { when } = require('../system/unload');
+const { WorkerSandbox } = require('./sandbox');
+const { Ci } = require('chrome');
+const { process, frames } = require('../remote/child');
+
+const EVENTS = {
+ 'chrome-page-shown': 'pageshow',
+ 'content-page-shown': 'pageshow',
+ 'chrome-page-hidden': 'pagehide',
+ 'content-page-hidden': 'pagehide',
+ 'inner-window-destroyed': 'detach',
+}
+
+// The parent Worker must have been created (or an async message sent to spawn
+// its creation) before creating the WorkerChild or messages from the content
+// script to the parent will get lost.
+const WorkerChild = Class({
+ implements: [EventTarget],
+
+ initialize(options) {
+ merge(this, options);
+ keepAlive.set(this.id, this);
+
+ this.windowId = getInnerId(this.window);
+ if (this.contentScriptOptions)
+ this.contentScriptOptions = JSON.parse(this.contentScriptOptions);
+
+ this.port = EventTarget();
+ this.port.on('*', this.send.bind(this, 'event'));
+ this.on('*', this.send.bind(this));
+
+ this.observe = this.observe.bind(this);
+
+ for (let topic in EVENTS)
+ system.on(topic, this.observe);
+
+ this.receive = this.receive.bind(this);
+ process.port.on('sdk/worker/message', this.receive);
+
+ this.sandbox = WorkerSandbox(this, this.window);
+
+ // If the document has an unexpected readyState, its worker-child instance is initialized
+ // as frozen until one of the known readyState is reached.
+ let initialDocumentReadyState = this.window.document.readyState;
+ this.frozen = [
+ "loading", "interactive", "complete"
+ ].includes(initialDocumentReadyState) ? false : true;
+
+ if (this.frozen) {
+ console.warn("SDK worker-child started as frozen on unexpected initial document.readyState", {
+ initialDocumentReadyState, windowLocation: this.window.location.href,
+ });
+ }
+
+ this.frozenMessages = [];
+ this.on('pageshow', () => {
+ this.frozen = false;
+ this.frozenMessages.forEach(args => this.sandbox.emit(...args));
+ this.frozenMessages = [];
+ });
+ this.on('pagehide', () => {
+ this.frozen = true;
+ });
+ },
+
+ // messages
+ receive(process, id, args) {
+ if (id !== this.id)
+ return;
+ args = JSON.parse(args);
+
+ if (this.frozen)
+ this.frozenMessages.push(args);
+ else
+ this.sandbox.emit(...args);
+
+ if (args[0] === 'detach')
+ this.destroy(args[1]);
+ },
+
+ send(...args) {
+ process.port.emit('sdk/worker/event', this.id, JSON.stringify(args, exceptions));
+ },
+
+ // notifications
+ observe({ type, subject }) {
+ if (!this.sandbox)
+ return;
+
+ if (subject.defaultView && getInnerId(subject.defaultView) === this.windowId) {
+ this.sandbox.emitSync(EVENTS[type]);
+ emit(this, EVENTS[type]);
+ }
+
+ if (type === 'inner-window-destroyed' &&
+ subject.QueryInterface(Ci.nsISupportsPRUint64).data === this.windowId) {
+ this.destroy();
+ }
+ },
+
+ get frame() {
+ return frames.getFrameForWindow(this.window.top);
+ },
+
+ // detach/destroy: unload and release the sandbox
+ destroy(reason) {
+ if (!this.sandbox)
+ return;
+
+ for (let topic in EVENTS)
+ system.off(topic, this.observe);
+ process.port.off('sdk/worker/message', this.receive);
+
+ this.sandbox.destroy(reason);
+ this.sandbox = null;
+ keepAlive.delete(this.id);
+
+ this.send('detach');
+ }
+})
+exports.WorkerChild = WorkerChild;
+
+// Error instances JSON poorly
+function exceptions(key, value) {
+ if (!isObject(value) || !instanceOf(value, Error))
+ return value;
+ let _errorType = value.constructor.name;
+ let { message, fileName, lineNumber, stack, name } = value;
+ return { _errorType, message, fileName, lineNumber, stack, name };
+}
+
+// workers for windows in this tab
+var keepAlive = new Map();
+
+process.port.on('sdk/worker/create', (process, options, cpows) => {
+ options.window = cpows.window;
+ let worker = new WorkerChild(options);
+
+ let frame = frames.getFrameForWindow(options.window.top);
+ frame.port.emit('sdk/worker/connect', options.id, options.window.location.href);
+});
+
+when(reason => {
+ for (let worker of keepAlive.values())
+ worker.destroy(reason);
+});
diff --git a/components/jetpack/sdk/content/worker.js b/components/jetpack/sdk/content/worker.js
new file mode 100644
index 000000000..39b940a88
--- /dev/null
+++ b/components/jetpack/sdk/content/worker.js
@@ -0,0 +1,180 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { emit } = require('../event/core');
+const { omit, merge } = require('../util/object');
+const { Class } = require('../core/heritage');
+const { method } = require('../lang/functional');
+const { getInnerId } = require('../window/utils');
+const { EventTarget } = require('../event/target');
+const { isPrivate } = require('../private-browsing/utils');
+const { getTabForBrowser, getTabForContentWindowNoShim, getBrowserForTab } = require('../tabs/utils');
+const { attach, connect, detach, destroy, makeChildOptions } = require('./utils');
+const { ensure } = require('../system/unload');
+const { on: observe } = require('../system/events');
+const { Ci, Cu } = require('chrome');
+const { modelFor: tabFor } = require('sdk/model/core');
+const { remoteRequire, processes, frames } = require('../remote/parent');
+remoteRequire('sdk/content/worker-child');
+
+const workers = new WeakMap();
+var modelFor = (worker) => workers.get(worker);
+
+const ERR_DESTROYED = "Couldn't find the worker to receive this message. " +
+ "The script may not be initialized yet, or may already have been unloaded.";
+
+// a handle for communication between content script and addon code
+const Worker = Class({
+ implements: [EventTarget],
+
+ initialize(options = {}) {
+ ensure(this, 'detach');
+
+ let model = {
+ attached: false,
+ destroyed: false,
+ earlyEvents: [], // fired before worker was attached
+ frozen: true, // document is not yet active
+ options,
+ };
+ workers.set(this, model);
+
+ this.on('detach', this.detach);
+ EventTarget.prototype.initialize.call(this, options);
+
+ this.receive = this.receive.bind(this);
+
+ this.port = EventTarget();
+ this.port.emit = this.send.bind(this, 'event');
+ this.postMessage = this.send.bind(this, 'message');
+
+ if ('window' in options) {
+ let window = options.window;
+ delete options.window;
+ attach(this, window);
+ }
+ },
+
+ // messages
+ receive(process, id, args) {
+ let model = modelFor(this);
+ if (id !== model.id || !model.attached)
+ return;
+ args = JSON.parse(args);
+ if (model.destroyed && args[0] != 'detach')
+ return;
+
+ if (args[0] === 'event')
+ emit(this.port, ...args.slice(1))
+ else
+ emit(this, ...args);
+ },
+
+ send(...args) {
+ let model = modelFor(this);
+ if (model.destroyed && args[0] !== 'detach')
+ throw new Error(ERR_DESTROYED);
+
+ if (!model.attached) {
+ model.earlyEvents.push(args);
+ return;
+ }
+
+ processes.port.emit('sdk/worker/message', model.id, JSON.stringify(args));
+ },
+
+ // properties
+ get url() {
+ let { url } = modelFor(this);
+ return url;
+ },
+
+ get contentURL() {
+ return this.url;
+ },
+
+ get tab() {
+ require('sdk/tabs');
+ let { frame } = modelFor(this);
+ if (!frame)
+ return null;
+ let rawTab = getTabForBrowser(frame.frameElement);
+ return rawTab && tabFor(rawTab);
+ },
+
+ toString: () => '[object Worker]',
+
+ detach: method(detach),
+ destroy: method(destroy),
+})
+exports.Worker = Worker;
+
+attach.define(Worker, function(worker, window) {
+ let model = modelFor(worker);
+ if (model.attached)
+ detach(worker);
+
+ let childOptions = makeChildOptions(model.options);
+ processes.port.emitCPOW('sdk/worker/create', [childOptions], { window });
+
+ let listener = (frame, id, url) => {
+ if (id != childOptions.id)
+ return;
+ frames.port.off('sdk/worker/connect', listener);
+ connect(worker, frame, { id, url });
+ };
+ frames.port.on('sdk/worker/connect', listener);
+});
+
+connect.define(Worker, function(worker, frame, { id, url }) {
+ let model = modelFor(worker);
+ if (model.attached)
+ detach(worker);
+
+ model.id = id;
+ model.frame = frame;
+ model.url = url;
+
+ // Messages from content -> chrome come through the process message manager
+ // since that lives longer than the frame message manager
+ processes.port.on('sdk/worker/event', worker.receive);
+
+ model.attached = true;
+ model.destroyed = false;
+ model.frozen = false;
+
+ model.earlyEvents.forEach(args => worker.send(...args));
+ model.earlyEvents = [];
+ emit(worker, 'attach');
+});
+
+// unload and release the child worker, release window reference
+detach.define(Worker, function(worker) {
+ let model = modelFor(worker);
+ if (!model.attached)
+ return;
+
+ processes.port.off('sdk/worker/event', worker.receive);
+ model.attached = false;
+ model.destroyed = true;
+ emit(worker, 'detach');
+});
+
+isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
+
+// Something in the parent side has destroyed the worker, tell the child to
+// detach, the child will respond when it has detached
+destroy.define(Worker, function(worker, reason) {
+ let model = modelFor(worker);
+ model.destroyed = true;
+ if (!model.attached)
+ return;
+
+ worker.send('detach', reason);
+});
diff --git a/components/jetpack/sdk/context-menu.js b/components/jetpack/sdk/context-menu.js
new file mode 100644
index 000000000..e00f41d79
--- /dev/null
+++ b/components/jetpack/sdk/context-menu.js
@@ -0,0 +1,1189 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "stable",
+ "engines": {
+ // TODO Fennec support Bug 788334
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+const { Class, mix } = require("./core/heritage");
+const { addCollectionProperty } = require("./util/collection");
+const { ns } = require("./core/namespace");
+const { validateOptions, getTypeOf } = require("./deprecated/api-utils");
+const { URL, isValidURI } = require("./url");
+const { WindowTracker, browserWindowIterator } = require("./deprecated/window-utils");
+const { isBrowser, getInnerId } = require("./window/utils");
+const { MatchPattern } = require("./util/match-pattern");
+const { EventTarget } = require("./event/target");
+const { emit } = require('./event/core');
+const { when } = require('./system/unload');
+const { contract: loaderContract } = require('./content/loader');
+const { omit } = require('./util/object');
+const self = require('./self')
+const { remoteRequire, processes } = require('./remote/parent');
+remoteRequire('sdk/content/context-menu');
+
+// All user items we add have this class.
+const ITEM_CLASS = "addon-context-menu-item";
+
+// Items in the top-level context menu also have this class.
+const TOPLEVEL_ITEM_CLASS = "addon-context-menu-item-toplevel";
+
+// Items in the overflow submenu also have this class.
+const OVERFLOW_ITEM_CLASS = "addon-context-menu-item-overflow";
+
+// The class of the menu separator that separates standard context menu items
+// from our user items.
+const SEPARATOR_CLASS = "addon-context-menu-separator";
+
+// If more than this number of items are added to the context menu, all items
+// overflow into a "Jetpack" submenu.
+const OVERFLOW_THRESH_DEFAULT = 10;
+const OVERFLOW_THRESH_PREF =
+ "extensions.addon-sdk.context-menu.overflowThreshold";
+
+// The label of the overflow sub-xul:menu.
+//
+// TODO: Localize these.
+const OVERFLOW_MENU_LABEL = "Add-ons";
+const OVERFLOW_MENU_ACCESSKEY = "A";
+
+// The class of the overflow sub-xul:menu.
+const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu";
+
+// The class of the overflow submenu's xul:menupopup.
+const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup";
+
+// Holds private properties for API objects
+var internal = ns();
+
+// A little hacky but this is the last process ID that last opened the context
+// menu
+var lastContextProcessId = null;
+
+var uuidModule = require('./util/uuid');
+function uuid() {
+ return uuidModule.uuid().toString();
+}
+
+function getScheme(spec) {
+ try {
+ return URL(spec).scheme;
+ }
+ catch(e) {
+ return null;
+ }
+}
+
+var Context = Class({
+ initialize: function() {
+ internal(this).id = uuid();
+ },
+
+ // Returns the node that made this context current
+ adjustPopupNode: function adjustPopupNode(popupNode) {
+ return popupNode;
+ },
+
+ // Returns whether this context is current for the current node
+ isCurrent: function isCurrent(state) {
+ return state;
+ }
+});
+
+// Matches when the context-clicked node doesn't have any of
+// NON_PAGE_CONTEXT_ELTS in its ancestors
+var PageContext = Class({
+ extends: Context,
+
+ serialize: function() {
+ return {
+ id: internal(this).id,
+ type: "PageContext",
+ args: []
+ }
+ }
+});
+exports.PageContext = PageContext;
+
+// Matches when there is an active selection in the window
+var SelectionContext = Class({
+ extends: Context,
+
+ serialize: function() {
+ return {
+ id: internal(this).id,
+ type: "SelectionContext",
+ args: []
+ }
+ }
+});
+exports.SelectionContext = SelectionContext;
+
+// Matches when the context-clicked node or any of its ancestors matches the
+// selector given
+var SelectorContext = Class({
+ extends: Context,
+
+ initialize: function initialize(selector) {
+ Context.prototype.initialize.call(this);
+ let options = validateOptions({ selector: selector }, {
+ selector: {
+ is: ["string"],
+ msg: "selector must be a string."
+ }
+ });
+ internal(this).selector = options.selector;
+ },
+
+ serialize: function() {
+ return {
+ id: internal(this).id,
+ type: "SelectorContext",
+ args: [internal(this).selector]
+ }
+ }
+});
+exports.SelectorContext = SelectorContext;
+
+// Matches when the page url matches any of the patterns given
+var URLContext = Class({
+ extends: Context,
+
+ initialize: function initialize(patterns) {
+ Context.prototype.initialize.call(this);
+ patterns = Array.isArray(patterns) ? patterns : [patterns];
+
+ try {
+ internal(this).patterns = patterns.map(p => new MatchPattern(p));
+ }
+ catch (err) {
+ throw new Error("Patterns must be a string, regexp or an array of " +
+ "strings or regexps: " + err);
+ }
+ },
+
+ isCurrent: function isCurrent(url) {
+ return internal(this).patterns.some(p => p.test(url));
+ },
+
+ serialize: function() {
+ return {
+ id: internal(this).id,
+ type: "URLContext",
+ args: []
+ }
+ }
+});
+exports.URLContext = URLContext;
+
+// Matches when the user-supplied predicate returns true
+var PredicateContext = Class({
+ extends: Context,
+
+ initialize: function initialize(predicate) {
+ Context.prototype.initialize.call(this);
+ let options = validateOptions({ predicate: predicate }, {
+ predicate: {
+ is: ["function"],
+ msg: "predicate must be a function."
+ }
+ });
+ internal(this).predicate = options.predicate;
+ },
+
+ isCurrent: function isCurrent(state) {
+ return internal(this).predicate(state);
+ },
+
+ serialize: function() {
+ return {
+ id: internal(this).id,
+ type: "PredicateContext",
+ args: []
+ }
+ }
+});
+exports.PredicateContext = PredicateContext;
+
+function removeItemFromArray(array, item) {
+ return array.filter(i => i !== item);
+}
+
+// Converts anything that isn't false, null or undefined into a string
+function stringOrNull(val) {
+ return val ? String(val) : val;
+}
+
+// Shared option validation rules for Item, Menu, and Separator
+var baseItemRules = {
+ parentMenu: {
+ is: ["object", "undefined"],
+ ok: function (v) {
+ if (!v)
+ return true;
+ return (v instanceof ItemContainer) || (v instanceof Menu);
+ },
+ msg: "parentMenu must be a Menu or not specified."
+ },
+ context: {
+ is: ["undefined", "object", "array"],
+ ok: function (v) {
+ if (!v)
+ return true;
+ let arr = Array.isArray(v) ? v : [v];
+ return arr.every(o => o instanceof Context);
+ },
+ msg: "The 'context' option must be a Context object or an array of " +
+ "Context objects."
+ },
+ onMessage: {
+ is: ["function", "undefined"]
+ },
+ contentScript: loaderContract.rules.contentScript,
+ contentScriptFile: loaderContract.rules.contentScriptFile
+};
+
+var labelledItemRules = mix(baseItemRules, {
+ label: {
+ map: stringOrNull,
+ is: ["string"],
+ ok: v => !!v,
+ msg: "The item must have a non-empty string label."
+ },
+ accesskey: {
+ map: stringOrNull,
+ is: ["string", "undefined", "null"],
+ ok: (v) => {
+ if (!v) {
+ return true;
+ }
+ return typeof v == "string" && v.length === 1;
+ },
+ msg: "The item must have a single character accesskey, or no accesskey."
+ },
+ image: {
+ map: stringOrNull,
+ is: ["string", "undefined", "null"],
+ ok: function (url) {
+ if (!url)
+ return true;
+ return isValidURI(url);
+ },
+ msg: "Image URL validation failed"
+ }
+});
+
+// Additional validation rules for Item
+var itemRules = mix(labelledItemRules, {
+ data: {
+ map: stringOrNull,
+ is: ["string", "undefined", "null"]
+ }
+});
+
+// Additional validation rules for Menu
+var menuRules = mix(labelledItemRules, {
+ items: {
+ is: ["array", "undefined"],
+ ok: function (v) {
+ if (!v)
+ return true;
+ return v.every(function (item) {
+ return item instanceof BaseItem;
+ });
+ },
+ msg: "items must be an array, and each element in the array must be an " +
+ "Item, Menu, or Separator."
+ }
+});
+
+// Returns true if any contexts match. If there are no contexts then a
+// PageContext is tested instead
+function hasMatchingContext(contexts, addonInfo) {
+ for (let context of contexts) {
+ if (!(internal(context).id in addonInfo.contextStates)) {
+ console.error("Missing state for context " + internal(context).id + " this is an error in the SDK modules.");
+ return false;
+ }
+ if (!context.isCurrent(addonInfo.contextStates[internal(context).id]))
+ return false;
+ }
+
+ return true;
+}
+
+// Tests whether an item should be visible or not based on its contexts and
+// content scripts
+function isItemVisible(item, addonInfo, usePageWorker) {
+ if (!item.context.length) {
+ if (!addonInfo.hasWorker)
+ return usePageWorker ? addonInfo.pageContext : true;
+ }
+
+ if (!hasMatchingContext(item.context, addonInfo))
+ return false;
+
+ let context = addonInfo.workerContext;
+ if (typeof(context) === "string" && context != "")
+ item.label = context;
+
+ return !!context;
+}
+
+// Called when an item is clicked to send out click events to the content
+// scripts
+function itemActivated(item, clickedNode) {
+ let items = [internal(item).id];
+ let data = item.data;
+
+ while (item.parentMenu) {
+ item = item.parentMenu;
+ items.push(internal(item).id);
+ }
+
+ let process = processes.getById(lastContextProcessId);
+ if (process)
+ process.port.emit('sdk/contextmenu/activateitems', items, data);
+}
+
+function serializeItem(item) {
+ return {
+ id: internal(item).id,
+ contexts: item.context.map(c => c.serialize()),
+ contentScript: item.contentScript,
+ contentScriptFile: item.contentScriptFile,
+ };
+}
+
+// All things that appear in the context menu extend this
+var BaseItem = Class({
+ initialize: function initialize() {
+ internal(this).id = uuid();
+
+ internal(this).contexts = [];
+ if ("context" in internal(this).options && internal(this).options.context) {
+ let contexts = internal(this).options.context;
+ if (Array.isArray(contexts)) {
+ for (let context of contexts)
+ internal(this).contexts.push(context);
+ }
+ else {
+ internal(this).contexts.push(contexts);
+ }
+ }
+
+ let parentMenu = internal(this).options.parentMenu;
+ if (!parentMenu)
+ parentMenu = contentContextMenu;
+
+ parentMenu.addItem(this);
+
+ Object.defineProperty(this, "contentScript", {
+ enumerable: true,
+ value: internal(this).options.contentScript
+ });
+
+ // Resolve URIs here as tests may have overriden self
+ let files = internal(this).options.contentScriptFile;
+ if (files) {
+ if (!Array.isArray(files))
+ files = [files];
+ files = files.map(self.data.url);
+ }
+ internal(this).options.contentScriptFile = files;
+ Object.defineProperty(this, "contentScriptFile", {
+ enumerable: true,
+ value: internal(this).options.contentScriptFile
+ });
+
+ // Notify all frames of this new item
+ sendItems([serializeItem(this)]);
+ },
+
+ destroy: function destroy() {
+ if (internal(this).destroyed)
+ return;
+
+ // Tell all existing frames that this item has been destroyed
+ processes.port.emit("sdk/contextmenu/destroyitems", [internal(this).id]);
+
+ if (this.parentMenu)
+ this.parentMenu.removeItem(this);
+
+ internal(this).destroyed = true;
+ },
+
+ get context() {
+ let contexts = internal(this).contexts.slice(0);
+ contexts.add = (context) => {
+ internal(this).contexts.push(context);
+ // Notify all frames that this item has changed
+ sendItems([serializeItem(this)]);
+ };
+ contexts.remove = (context) => {
+ internal(this).contexts = internal(this).contexts.filter(c => {
+ return c != context;
+ });
+ // Notify all frames that this item has changed
+ sendItems([serializeItem(this)]);
+ };
+ return contexts;
+ },
+
+ set context(val) {
+ internal(this).contexts = val.slice(0);
+ // Notify all frames that this item has changed
+ sendItems([serializeItem(this)]);
+ },
+
+ get parentMenu() {
+ return internal(this).parentMenu;
+ },
+});
+
+function workerMessageReceived(process, id, args) {
+ if (internal(this).id != id)
+ return;
+
+ emit(this, ...JSON.parse(args));
+}
+
+// All things that have a label on the context menu extend this
+var LabelledItem = Class({
+ extends: BaseItem,
+ implements: [ EventTarget ],
+
+ initialize: function initialize(options) {
+ BaseItem.prototype.initialize.call(this);
+ EventTarget.prototype.initialize.call(this, options);
+
+ internal(this).messageListener = workerMessageReceived.bind(this);
+ processes.port.on('sdk/worker/event', internal(this).messageListener);
+ },
+
+ destroy: function destroy() {
+ if (internal(this).destroyed)
+ return;
+
+ processes.port.off('sdk/worker/event', internal(this).messageListener);
+
+ BaseItem.prototype.destroy.call(this);
+ },
+
+ get label() {
+ return internal(this).options.label;
+ },
+
+ set label(val) {
+ internal(this).options.label = val;
+
+ MenuManager.updateItem(this);
+ },
+
+ get accesskey() {
+ return internal(this).options.accesskey;
+ },
+
+ set accesskey(val) {
+ internal(this).options.accesskey = val;
+
+ MenuManager.updateItem(this);
+ },
+
+ get image() {
+ return internal(this).options.image;
+ },
+
+ set image(val) {
+ internal(this).options.image = val;
+
+ MenuManager.updateItem(this);
+ },
+
+ get data() {
+ return internal(this).options.data;
+ },
+
+ set data(val) {
+ internal(this).options.data = val;
+ }
+});
+
+var Item = Class({
+ extends: LabelledItem,
+
+ initialize: function initialize(options) {
+ internal(this).options = validateOptions(options, itemRules);
+
+ LabelledItem.prototype.initialize.call(this, options);
+ },
+
+ toString: function toString() {
+ return "[object Item \"" + this.label + "\"]";
+ },
+
+ get data() {
+ return internal(this).options.data;
+ },
+
+ set data(val) {
+ internal(this).options.data = val;
+
+ MenuManager.updateItem(this);
+ },
+});
+exports.Item = Item;
+
+var ItemContainer = Class({
+ initialize: function initialize() {
+ internal(this).children = [];
+ },
+
+ destroy: function destroy() {
+ // Destroys the entire hierarchy
+ for (let item of internal(this).children)
+ item.destroy();
+ },
+
+ addItem: function addItem(item) {
+ let oldParent = item.parentMenu;
+
+ // Don't just call removeItem here as that would remove the corresponding
+ // UI element which is more costly than just moving it to the right place
+ if (oldParent)
+ internal(oldParent).children = removeItemFromArray(internal(oldParent).children, item);
+
+ let after = null;
+ let children = internal(this).children;
+ if (children.length > 0)
+ after = children[children.length - 1];
+
+ children.push(item);
+ internal(item).parentMenu = this;
+
+ // If there was an old parent then we just have to move the item, otherwise
+ // it needs to be created
+ if (oldParent)
+ MenuManager.moveItem(item, after);
+ else
+ MenuManager.createItem(item, after);
+ },
+
+ removeItem: function removeItem(item) {
+ // If the item isn't a child of this menu then ignore this call
+ if (item.parentMenu !== this)
+ return;
+
+ MenuManager.removeItem(item);
+
+ internal(this).children = removeItemFromArray(internal(this).children, item);
+ internal(item).parentMenu = null;
+ },
+
+ get items() {
+ return internal(this).children.slice(0);
+ },
+
+ set items(val) {
+ // Validate the arguments before making any changes
+ if (!Array.isArray(val))
+ throw new Error(menuOptionRules.items.msg);
+
+ for (let item of val) {
+ if (!(item instanceof BaseItem))
+ throw new Error(menuOptionRules.items.msg);
+ }
+
+ // Remove the old items and add the new ones
+ for (let item of internal(this).children)
+ this.removeItem(item);
+
+ for (let item of val)
+ this.addItem(item);
+ },
+});
+
+var Menu = Class({
+ extends: LabelledItem,
+ implements: [ItemContainer],
+
+ initialize: function initialize(options) {
+ internal(this).options = validateOptions(options, menuRules);
+
+ LabelledItem.prototype.initialize.call(this, options);
+ ItemContainer.prototype.initialize.call(this);
+
+ if (internal(this).options.items) {
+ for (let item of internal(this).options.items)
+ this.addItem(item);
+ }
+ },
+
+ destroy: function destroy() {
+ ItemContainer.prototype.destroy.call(this);
+ LabelledItem.prototype.destroy.call(this);
+ },
+
+ toString: function toString() {
+ return "[object Menu \"" + this.label + "\"]";
+ },
+});
+exports.Menu = Menu;
+
+var Separator = Class({
+ extends: BaseItem,
+
+ initialize: function initialize(options) {
+ internal(this).options = validateOptions(options, baseItemRules);
+
+ BaseItem.prototype.initialize.call(this);
+ },
+
+ toString: function toString() {
+ return "[object Separator]";
+ }
+});
+exports.Separator = Separator;
+
+// Holds items for the content area context menu
+var contentContextMenu = ItemContainer();
+exports.contentContextMenu = contentContextMenu;
+
+function getContainerItems(container) {
+ let items = [];
+ for (let item of internal(container).children) {
+ items.push(serializeItem(item));
+ if (item instanceof Menu)
+ items = items.concat(getContainerItems(item));
+ }
+ return items;
+}
+
+// Notify all frames of these new or changed items
+function sendItems(items) {
+ processes.port.emit("sdk/contextmenu/createitems", items);
+}
+
+// Called when a new process is created and needs to get the current list of items
+function remoteItemRequest(process) {
+ let items = getContainerItems(contentContextMenu);
+ if (items.length == 0)
+ return;
+
+ process.port.emit("sdk/contextmenu/createitems", items);
+}
+processes.forEvery(remoteItemRequest);
+
+when(function() {
+ contentContextMenu.destroy();
+});
+
+// App specific UI code lives here, it should handle populating the context
+// menu and passing clicks etc. through to the items.
+
+function countVisibleItems(nodes) {
+ return Array.reduce(nodes, function(sum, node) {
+ return node.hidden ? sum : sum + 1;
+ }, 0);
+}
+
+var MenuWrapper = Class({
+ initialize: function initialize(winWrapper, items, contextMenu) {
+ this.winWrapper = winWrapper;
+ this.window = winWrapper.window;
+ this.items = items;
+ this.contextMenu = contextMenu;
+ this.populated = false;
+ this.menuMap = new Map();
+
+ // updateItemVisibilities will run first, updateOverflowState will run after
+ // all other instances of this module have run updateItemVisibilities
+ this._updateItemVisibilities = this.updateItemVisibilities.bind(this);
+ this.contextMenu.addEventListener("popupshowing", this._updateItemVisibilities, true);
+ this._updateOverflowState = this.updateOverflowState.bind(this);
+ this.contextMenu.addEventListener("popupshowing", this._updateOverflowState, false);
+ },
+
+ destroy: function destroy() {
+ this.contextMenu.removeEventListener("popupshowing", this._updateOverflowState, false);
+ this.contextMenu.removeEventListener("popupshowing", this._updateItemVisibilities, true);
+
+ if (!this.populated)
+ return;
+
+ // If we're getting unloaded at runtime then we must remove all the
+ // generated XUL nodes
+ let oldParent = null;
+ for (let item of internal(this.items).children) {
+ let xulNode = this.getXULNodeForItem(item);
+ oldParent = xulNode.parentNode;
+ oldParent.removeChild(xulNode);
+ }
+
+ if (oldParent)
+ this.onXULRemoved(oldParent);
+ },
+
+ get separator() {
+ return this.contextMenu.querySelector("." + SEPARATOR_CLASS);
+ },
+
+ get overflowMenu() {
+ return this.contextMenu.querySelector("." + OVERFLOW_MENU_CLASS);
+ },
+
+ get overflowPopup() {
+ return this.contextMenu.querySelector("." + OVERFLOW_POPUP_CLASS);
+ },
+
+ get topLevelItems() {
+ return this.contextMenu.querySelectorAll("." + TOPLEVEL_ITEM_CLASS);
+ },
+
+ get overflowItems() {
+ return this.contextMenu.querySelectorAll("." + OVERFLOW_ITEM_CLASS);
+ },
+
+ getXULNodeForItem: function getXULNodeForItem(item) {
+ return this.menuMap.get(item);
+ },
+
+ // Recurses through the item hierarchy creating XUL nodes for everything
+ populate: function populate(menu) {
+ for (let i = 0; i < internal(menu).children.length; i++) {
+ let item = internal(menu).children[i];
+ let after = i === 0 ? null : internal(menu).children[i - 1];
+ this.createItem(item, after);
+
+ if (item instanceof Menu)
+ this.populate(item);
+ }
+ },
+
+ // Recurses through the menu setting the visibility of items. Returns true
+ // if any of the items in this menu were visible
+ setVisibility: function setVisibility(menu, addonInfo, usePageWorker) {
+ let anyVisible = false;
+
+ for (let item of internal(menu).children) {
+ let visible = isItemVisible(item, addonInfo[internal(item).id], usePageWorker);
+
+ // Recurse through Menus, if none of the sub-items were visible then the
+ // menu is hidden too.
+ if (visible && (item instanceof Menu))
+ visible = this.setVisibility(item, addonInfo, false);
+
+ let xulNode = this.getXULNodeForItem(item);
+ xulNode.hidden = !visible;
+
+ anyVisible = anyVisible || visible;
+ }
+
+ return anyVisible;
+ },
+
+ // Works out where to insert a XUL node for an item in a browser window
+ insertIntoXUL: function insertIntoXUL(item, node, after) {
+ let menupopup = null;
+ let before = null;
+
+ let menu = item.parentMenu;
+ if (menu === this.items) {
+ // Insert into the overflow popup if it exists, otherwise the normal
+ // context menu
+ menupopup = this.overflowPopup;
+ if (!menupopup)
+ menupopup = this.contextMenu;
+ }
+ else {
+ let xulNode = this.getXULNodeForItem(menu);
+ menupopup = xulNode.firstChild;
+ }
+
+ if (after) {
+ let afterNode = this.getXULNodeForItem(after);
+ before = afterNode.nextSibling;
+ }
+ else if (menupopup === this.contextMenu) {
+ let topLevel = this.topLevelItems;
+ if (topLevel.length > 0)
+ before = topLevel[topLevel.length - 1].nextSibling;
+ else
+ before = this.separator.nextSibling;
+ }
+
+ menupopup.insertBefore(node, before);
+ },
+
+ // Sets the right class for XUL nodes
+ updateXULClass: function updateXULClass(xulNode) {
+ if (xulNode.parentNode == this.contextMenu)
+ xulNode.classList.add(TOPLEVEL_ITEM_CLASS);
+ else
+ xulNode.classList.remove(TOPLEVEL_ITEM_CLASS);
+
+ if (xulNode.parentNode == this.overflowPopup)
+ xulNode.classList.add(OVERFLOW_ITEM_CLASS);
+ else
+ xulNode.classList.remove(OVERFLOW_ITEM_CLASS);
+ },
+
+ // Creates a XUL node for an item
+ createItem: function createItem(item, after) {
+ if (!this.populated)
+ return;
+
+ // Create the separator if it doesn't already exist
+ if (!this.separator) {
+ let separator = this.window.document.createElement("menuseparator");
+ separator.setAttribute("class", SEPARATOR_CLASS);
+
+ // Insert before the separator created by the old context-menu if it
+ // exists to avoid bug 832401
+ let oldSeparator = this.window.document.getElementById("jetpack-context-menu-separator");
+ if (oldSeparator && oldSeparator.parentNode != this.contextMenu)
+ oldSeparator = null;
+ this.contextMenu.insertBefore(separator, oldSeparator);
+ }
+
+ let type = "menuitem";
+ if (item instanceof Menu)
+ type = "menu";
+ else if (item instanceof Separator)
+ type = "menuseparator";
+
+ let xulNode = this.window.document.createElement(type);
+ xulNode.setAttribute("class", ITEM_CLASS);
+ if (item instanceof LabelledItem) {
+ xulNode.setAttribute("label", item.label);
+ if (item.accesskey)
+ xulNode.setAttribute("accesskey", item.accesskey);
+ if (item.image) {
+ xulNode.setAttribute("image", item.image);
+ if (item instanceof Menu)
+ xulNode.classList.add("menu-iconic");
+ else
+ xulNode.classList.add("menuitem-iconic");
+ }
+ if (item.data)
+ xulNode.setAttribute("value", item.data);
+
+ let self = this;
+ xulNode.addEventListener("command", function(event) {
+ // Only care about clicks directly on this item
+ if (event.target !== xulNode)
+ return;
+
+ itemActivated(item, xulNode);
+ }, false);
+ }
+
+ this.insertIntoXUL(item, xulNode, after);
+ this.updateXULClass(xulNode);
+ xulNode.data = item.data;
+
+ if (item instanceof Menu) {
+ let menupopup = this.window.document.createElement("menupopup");
+ xulNode.appendChild(menupopup);
+ }
+
+ this.menuMap.set(item, xulNode);
+ },
+
+ // Updates the XUL node for an item in this window
+ updateItem: function updateItem(item) {
+ if (!this.populated)
+ return;
+
+ let xulNode = this.getXULNodeForItem(item);
+
+ // TODO figure out why this requires setAttribute
+ xulNode.setAttribute("label", item.label);
+ xulNode.setAttribute("accesskey", item.accesskey || "");
+
+ if (item.image) {
+ xulNode.setAttribute("image", item.image);
+ if (item instanceof Menu)
+ xulNode.classList.add("menu-iconic");
+ else
+ xulNode.classList.add("menuitem-iconic");
+ }
+ else {
+ xulNode.removeAttribute("image");
+ xulNode.classList.remove("menu-iconic");
+ xulNode.classList.remove("menuitem-iconic");
+ }
+
+ if (item.data)
+ xulNode.setAttribute("value", item.data);
+ else
+ xulNode.removeAttribute("value");
+ },
+
+ // Moves the XUL node for an item in this window to its new place in the
+ // hierarchy
+ moveItem: function moveItem(item, after) {
+ if (!this.populated)
+ return;
+
+ let xulNode = this.getXULNodeForItem(item);
+ let oldParent = xulNode.parentNode;
+
+ this.insertIntoXUL(item, xulNode, after);
+ this.updateXULClass(xulNode);
+ this.onXULRemoved(oldParent);
+ },
+
+ // Removes the XUL nodes for an item in every window we've ever populated.
+ removeItem: function removeItem(item) {
+ if (!this.populated)
+ return;
+
+ let xulItem = this.getXULNodeForItem(item);
+
+ let oldParent = xulItem.parentNode;
+
+ oldParent.removeChild(xulItem);
+ this.menuMap.delete(item);
+
+ this.onXULRemoved(oldParent);
+ },
+
+ // Called when any XUL nodes have been removed from a menupopup. This handles
+ // making sure the separator and overflow are correct
+ onXULRemoved: function onXULRemoved(parent) {
+ if (parent == this.contextMenu) {
+ let toplevel = this.topLevelItems;
+
+ // If there are no more items then remove the separator
+ if (toplevel.length == 0) {
+ let separator = this.separator;
+ if (separator)
+ separator.parentNode.removeChild(separator);
+ }
+ }
+ else if (parent == this.overflowPopup) {
+ // If there are no more items then remove the overflow menu and separator
+ if (parent.childNodes.length == 0) {
+ let separator = this.separator;
+ separator.parentNode.removeChild(separator);
+ this.contextMenu.removeChild(parent.parentNode);
+ }
+ }
+ },
+
+ // Recurses through all the items owned by this module and sets their hidden
+ // state
+ updateItemVisibilities: function updateItemVisibilities(event) {
+ try {
+ if (event.type != "popupshowing")
+ return;
+ if (event.target != this.contextMenu)
+ return;
+
+ if (internal(this.items).children.length == 0)
+ return;
+
+ if (!this.populated) {
+ this.populated = true;
+ this.populate(this.items);
+ }
+
+ let mainWindow = event.target.ownerDocument.defaultView;
+ this.contextMenuContentData = mainWindow.gContextMenuContentData
+ if (!(self.id in this.contextMenuContentData.addonInfo)) {
+ console.warn("No context menu state data was provided.");
+ return;
+ }
+ let addonInfo = this.contextMenuContentData.addonInfo[self.id];
+ lastContextProcessId = addonInfo.processID;
+ this.setVisibility(this.items, addonInfo.items, true);
+ }
+ catch (e) {
+ console.exception(e);
+ }
+ },
+
+ // Counts the number of visible items across all modules and makes sure they
+ // are in the right place between the top level context menu and the overflow
+ // menu
+ updateOverflowState: function updateOverflowState(event) {
+ try {
+ if (event.type != "popupshowing")
+ return;
+ if (event.target != this.contextMenu)
+ return;
+
+ // The main items will be in either the top level context menu or the
+ // overflow menu at this point. Count the visible ones and if they are in
+ // the wrong place move them
+ let toplevel = this.topLevelItems;
+ let overflow = this.overflowItems;
+ let visibleCount = countVisibleItems(toplevel) +
+ countVisibleItems(overflow);
+
+ if (visibleCount == 0) {
+ let separator = this.separator;
+ if (separator)
+ separator.hidden = true;
+ let overflowMenu = this.overflowMenu;
+ if (overflowMenu)
+ overflowMenu.hidden = true;
+ }
+ else if (visibleCount > MenuManager.overflowThreshold) {
+ this.separator.hidden = false;
+ let overflowPopup = this.overflowPopup;
+ if (overflowPopup)
+ overflowPopup.parentNode.hidden = false;
+
+ if (toplevel.length > 0) {
+ // The overflow menu shouldn't exist here but let's play it safe
+ if (!overflowPopup) {
+ let overflowMenu = this.window.document.createElement("menu");
+ overflowMenu.setAttribute("class", OVERFLOW_MENU_CLASS);
+ overflowMenu.setAttribute("label", OVERFLOW_MENU_LABEL);
+ overflowMenu.setAttribute("accesskey", OVERFLOW_MENU_ACCESSKEY);
+ this.contextMenu.insertBefore(overflowMenu, this.separator.nextSibling);
+
+ overflowPopup = this.window.document.createElement("menupopup");
+ overflowPopup.setAttribute("class", OVERFLOW_POPUP_CLASS);
+ overflowMenu.appendChild(overflowPopup);
+ }
+
+ for (let xulNode of toplevel) {
+ overflowPopup.appendChild(xulNode);
+ this.updateXULClass(xulNode);
+ }
+ }
+ }
+ else {
+ this.separator.hidden = false;
+
+ if (overflow.length > 0) {
+ // Move all the overflow nodes out of the overflow menu and position
+ // them immediately before it
+ for (let xulNode of overflow) {
+ this.contextMenu.insertBefore(xulNode, xulNode.parentNode.parentNode);
+ this.updateXULClass(xulNode);
+ }
+ this.contextMenu.removeChild(this.overflowMenu);
+ }
+ }
+ }
+ catch (e) {
+ console.exception(e);
+ }
+ }
+});
+
+// This wraps every window that we've seen
+var WindowWrapper = Class({
+ initialize: function initialize(window) {
+ this.window = window;
+ this.menus = [
+ new MenuWrapper(this, contentContextMenu, window.document.getElementById("contentAreaContextMenu")),
+ ];
+ },
+
+ destroy: function destroy() {
+ for (let menuWrapper of this.menus)
+ menuWrapper.destroy();
+ },
+
+ getMenuWrapperForItem: function getMenuWrapperForItem(item) {
+ let root = item.parentMenu;
+ while (root.parentMenu)
+ root = root.parentMenu;
+
+ for (let wrapper of this.menus) {
+ if (wrapper.items === root)
+ return wrapper;
+ }
+
+ return null;
+ }
+});
+
+var MenuManager = {
+ windowMap: new Map(),
+
+ get overflowThreshold() {
+ let prefs = require("./preferences/service");
+ return prefs.get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT);
+ },
+
+ // When a new window is added start watching it for context menu shows
+ onTrack: function onTrack(window) {
+ if (!isBrowser(window))
+ return;
+
+ // Generally shouldn't happen, but just in case
+ if (this.windowMap.has(window)) {
+ console.warn("Already seen this window");
+ return;
+ }
+
+ let winWrapper = WindowWrapper(window);
+ this.windowMap.set(window, winWrapper);
+ },
+
+ onUntrack: function onUntrack(window) {
+ if (!isBrowser(window))
+ return;
+
+ let winWrapper = this.windowMap.get(window);
+ // This shouldn't happen but protect against it anyway
+ if (!winWrapper)
+ return;
+ winWrapper.destroy();
+
+ this.windowMap.delete(window);
+ },
+
+ // Creates a XUL node for an item in every window we've already populated
+ createItem: function createItem(item, after) {
+ for (let [window, winWrapper] of this.windowMap) {
+ let menuWrapper = winWrapper.getMenuWrapperForItem(item);
+ if (menuWrapper)
+ menuWrapper.createItem(item, after);
+ }
+ },
+
+ // Updates the XUL node for an item in every window we've already populated
+ updateItem: function updateItem(item) {
+ for (let [window, winWrapper] of this.windowMap) {
+ let menuWrapper = winWrapper.getMenuWrapperForItem(item);
+ if (menuWrapper)
+ menuWrapper.updateItem(item);
+ }
+ },
+
+ // Moves the XUL node for an item in every window we've ever populated to its
+ // new place in the hierarchy
+ moveItem: function moveItem(item, after) {
+ for (let [window, winWrapper] of this.windowMap) {
+ let menuWrapper = winWrapper.getMenuWrapperForItem(item);
+ if (menuWrapper)
+ menuWrapper.moveItem(item, after);
+ }
+ },
+
+ // Removes the XUL nodes for an item in every window we've ever populated.
+ removeItem: function removeItem(item) {
+ for (let [window, winWrapper] of this.windowMap) {
+ let menuWrapper = winWrapper.getMenuWrapperForItem(item);
+ if (menuWrapper)
+ menuWrapper.removeItem(item);
+ }
+ }
+};
+
+WindowTracker(MenuManager);
diff --git a/components/jetpack/sdk/context-menu/context.js b/components/jetpack/sdk/context-menu/context.js
new file mode 100644
index 000000000..fc5aea500
--- /dev/null
+++ b/components/jetpack/sdk/context-menu/context.js
@@ -0,0 +1,147 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { Class } = require("../core/heritage");
+const { extend } = require("../util/object");
+const { MatchPattern } = require("../util/match-pattern");
+const readers = require("./readers");
+
+// Context class is required to implement a single `isCurrent(target)` method
+// that must return boolean value indicating weather given target matches a
+// context or not. Most context implementations below will have an associated
+// reader that way context implementation can setup a reader to extract necessary
+// information to make decision if target is matching a context.
+const Context = Class({
+ isRequired: false,
+ isCurrent(target) {
+ throw Error("Context class must implement isCurrent(target) method");
+ },
+ get required() {
+ Object.defineProperty(this, "required", {
+ value: Object.assign(Object.create(Object.getPrototypeOf(this)),
+ this,
+ {isRequired: true})
+ });
+ return this.required;
+ }
+});
+Context.required = function(...params) {
+ return Object.assign(new this(...params), {isRequired: true});
+};
+exports.Context = Context;
+
+
+// Next few context implementations use an associated reader to extract info
+// from the context target and story it to a private symbol associtaed with
+// a context implementation. That way name collisions are avoided while required
+// information is still carried along.
+const isPage = Symbol("context/page?")
+const PageContext = Class({
+ extends: Context,
+ read: {[isPage]: new readers.isPage()},
+ isCurrent: target => target[isPage]
+});
+exports.Page = PageContext;
+
+const isFrame = Symbol("context/frame?");
+const FrameContext = Class({
+ extends: Context,
+ read: {[isFrame]: new readers.isFrame()},
+ isCurrent: target => target[isFrame]
+});
+exports.Frame = FrameContext;
+
+const selection = Symbol("context/selection")
+const SelectionContext = Class({
+ read: {[selection]: new readers.Selection()},
+ isCurrent: target => !!target[selection]
+});
+exports.Selection = SelectionContext;
+
+const link = Symbol("context/link");
+const LinkContext = Class({
+ extends: Context,
+ read: {[link]: new readers.LinkURL()},
+ isCurrent: target => !!target[link]
+});
+exports.Link = LinkContext;
+
+const isEditable = Symbol("context/editable?")
+const EditableContext = Class({
+ extends: Context,
+ read: {[isEditable]: new readers.isEditable()},
+ isCurrent: target => target[isEditable]
+});
+exports.Editable = EditableContext;
+
+
+const mediaType = Symbol("context/mediaType")
+
+const ImageContext = Class({
+ extends: Context,
+ read: {[mediaType]: new readers.MediaType()},
+ isCurrent: target => target[mediaType] === "image"
+});
+exports.Image = ImageContext;
+
+
+const VideoContext = Class({
+ extends: Context,
+ read: {[mediaType]: new readers.MediaType()},
+ isCurrent: target => target[mediaType] === "video"
+});
+exports.Video = VideoContext;
+
+
+const AudioContext = Class({
+ extends: Context,
+ read: {[mediaType]: new readers.MediaType()},
+ isCurrent: target => target[mediaType] === "audio"
+});
+exports.Audio = AudioContext;
+
+const isSelectorMatch = Symbol("context/selector/mathches?")
+const SelectorContext = Class({
+ extends: Context,
+ initialize(selector) {
+ this.selector = selector;
+ // Each instance of selector context will need to store read
+ // data into different field, so that case with multilpe selector
+ // contexts won't cause a conflicts.
+ this[isSelectorMatch] = Symbol(selector);
+ this.read = {[this[isSelectorMatch]]: new readers.SelectorMatch(selector)};
+ },
+ isCurrent(target) {
+ return target[this[isSelectorMatch]];
+ }
+});
+exports.Selector = SelectorContext;
+
+const url = Symbol("context/url");
+const URLContext = Class({
+ extends: Context,
+ initialize(pattern) {
+ this.pattern = new MatchPattern(pattern);
+ },
+ read: {[url]: new readers.PageURL()},
+ isCurrent(target) {
+ return this.pattern.test(target[url]);
+ }
+});
+exports.URL = URLContext;
+
+var PredicateContext = Class({
+ extends: Context,
+ initialize(isMatch) {
+ if (typeof(isMatch) !== "function") {
+ throw TypeError("Predicate context mus be passed a function");
+ }
+
+ this.isMatch = isMatch
+ },
+ isCurrent(target) {
+ return this.isMatch(target);
+ }
+});
+exports.Predicate = PredicateContext;
diff --git a/components/jetpack/sdk/context-menu/core.js b/components/jetpack/sdk/context-menu/core.js
new file mode 100644
index 000000000..c64cddfe8
--- /dev/null
+++ b/components/jetpack/sdk/context-menu/core.js
@@ -0,0 +1,384 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Contexts = require("./context");
+const Readers = require("./readers");
+const Component = require("../ui/component");
+const { Class } = require("../core/heritage");
+const { map, filter, object, reduce, keys, symbols,
+ pairs, values, each, some, isEvery, count } = require("../util/sequence");
+const { loadModule } = require("framescript/manager");
+const { Cu, Cc, Ci } = require("chrome");
+const prefs = require("sdk/preferences/service");
+
+const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+const preferencesService = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(null);
+
+
+const readTable = Symbol("context-menu/read-table");
+const nameTable = Symbol("context-menu/name-table");
+const onContext = Symbol("context-menu/on-context");
+const isMatching = Symbol("context-menu/matching-handler?");
+
+exports.onContext = onContext;
+exports.readTable = readTable;
+exports.nameTable = nameTable;
+
+
+const propagateOnContext = (item, data) =>
+ each(child => child[onContext](data), item.state.children);
+
+const isContextMatch = item => !item[isMatching] || item[isMatching]();
+
+// For whatever reason addWeakMessageListener does not seems to work as our
+// instance seems to dropped even though it's alive. This is simple workaround
+// to avoid dead object excetptions.
+const WeakMessageListener = function(receiver, handler="receiveMessage") {
+ this.receiver = receiver
+ this.handler = handler
+};
+WeakMessageListener.prototype = {
+ constructor: WeakMessageListener,
+ receiveMessage(message) {
+ if (Cu.isDeadWrapper(this.receiver)) {
+ message.target.messageManager.removeMessageListener(message.name, this);
+ }
+ else {
+ this.receiver[this.handler](message);
+ }
+ }
+};
+
+const OVERFLOW_THRESH = "extensions.addon-sdk.context-menu.overflowThreshold";
+const onMessage = Symbol("context-menu/message-listener");
+const onPreferceChange = Symbol("context-menu/preference-change");
+const ContextMenuExtension = Class({
+ extends: Component,
+ initialize: Component,
+ setup() {
+ const messageListener = new WeakMessageListener(this, onMessage);
+ loadModule(globalMessageManager, "framescript/context-menu", true, "onContentFrame");
+ globalMessageManager.addMessageListener("sdk/context-menu/read", messageListener);
+ globalMessageManager.addMessageListener("sdk/context-menu/readers?", messageListener);
+
+ preferencesService.addObserver(OVERFLOW_THRESH, this, false);
+ },
+ observe(_, __, name) {
+ if (name === OVERFLOW_THRESH) {
+ const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
+ this[Component.patch]({overflowThreshold});
+ }
+ },
+ [onMessage]({name, data, target}) {
+ if (name === "sdk/context-menu/read")
+ this[onContext]({target, data});
+ if (name === "sdk/context-menu/readers?")
+ target.messageManager.sendAsyncMessage("sdk/context-menu/readers",
+ JSON.parse(JSON.stringify(this.state.readers)));
+ },
+ [Component.initial](options={}, children) {
+ const element = options.element || null;
+ const target = options.target || null;
+ const readers = Object.create(null);
+ const users = Object.create(null);
+ const registry = new WeakSet();
+ const overflowThreshold = prefs.get(OVERFLOW_THRESH, 10);
+
+ return { target, children: [], readers, users, element,
+ registry, overflowThreshold };
+ },
+ [Component.isUpdated](before, after) {
+ // Update only if target changed, since there is no point in re-rendering
+ // when children are. Also new items added won't be in sync with a latest
+ // context target so we should really just render before drawing context
+ // menu.
+ return before.target !== after.target;
+ },
+ [Component.render]({element, children, overflowThreshold}) {
+ if (!element) return null;
+
+ const items = children.filter(isContextMatch);
+ const body = items.length === 0 ? items :
+ items.length < overflowThreshold ? [new Separator(),
+ ...items] :
+ [{tagName: "menu",
+ className: "sdk-context-menu-overflow-menu",
+ label: "Add-ons",
+ accesskey: "A",
+ children: [{tagName: "menupopup",
+ children: items}]}];
+ return {
+ element: element,
+ tagName: "menugroup",
+ style: "-moz-box-orient: vertical;",
+ className: "sdk-context-menu-extension",
+ children: body
+ }
+ },
+ // Adds / remove child to it's own list.
+ add(item) {
+ this[Component.patch]({children: this.state.children.concat(item)});
+ },
+ remove(item) {
+ this[Component.patch]({
+ children: this.state.children.filter(x => x !== item)
+ });
+ },
+ register(item) {
+ const { users, registry } = this.state;
+ if (registry.has(item)) return;
+ registry.add(item);
+
+ // Each (ContextHandler) item has a readTable that is a
+ // map of keys to readers extracting them from the content.
+ // During the registraction we update intrnal record of unique
+ // readers and users per reader. Most context will have a reader
+ // shared across all instances there for map of users per reader
+ // is stored separately from the reader so that removing reader
+ // will occur only when no users remain.
+ const table = item[readTable];
+ // Context readers store data in private symbols so we need to
+ // collect both table keys and private symbols.
+ const names = [...keys(table), ...symbols(table)];
+ const readers = map(name => table[name], names);
+ // Create delta for registered readers that will be merged into
+ // internal readers table.
+ const added = filter(x => !users[x.id], readers);
+ const delta = object(...map(x => [x.id, x], added));
+
+ const update = reduce((update, reader) => {
+ const n = update[reader.id] || 0;
+ update[reader.id] = n + 1;
+ return update;
+ }, Object.assign({}, users), readers);
+
+ // Patch current state with a changes that registered item caused.
+ this[Component.patch]({users: update,
+ readers: Object.assign(this.state.readers, delta)});
+
+ if (count(added)) {
+ globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
+ JSON.parse(JSON.stringify(delta)));
+ }
+ },
+ unregister(item) {
+ const { users, registry } = this.state;
+ if (!registry.has(item)) return;
+ registry.delete(item);
+
+ const table = item[readTable];
+ const names = [...keys(table), ...symbols(table)];
+ const readers = map(name => table[name], names);
+ const update = reduce((update, reader) => {
+ update[reader.id] = update[reader.id] - 1;
+ return update;
+ }, Object.assign({}, users), readers);
+ const removed = filter(id => !update[id], keys(update));
+ const delta = object(...map(x => [x, null], removed));
+
+ this[Component.patch]({users: update,
+ readers: Object.assign(this.state.readers, delta)});
+
+ if (count(removed)) {
+ globalMessageManager.broadcastAsyncMessage("sdk/context-menu/readers",
+ JSON.parse(JSON.stringify(delta)));
+ }
+ },
+
+ [onContext]({data, target}) {
+ propagateOnContext(this, data);
+ const document = target.ownerDocument;
+ const element = document.getElementById("contentAreaContextMenu");
+
+ this[Component.patch]({target: data, element: element});
+ }
+});this,
+exports.ContextMenuExtension = ContextMenuExtension;
+
+// Takes an item options and
+const makeReadTable = ({context, read}) => {
+ // Result of this function is a tuple of all readers &
+ // name, reader id pairs.
+
+ // Filter down to contexts that have a reader associated.
+ const contexts = filter(context => context.read, context);
+ // Merge all contexts read maps to a single hash, note that there should be
+ // no name collisions as context implementations expect to use private
+ // symbols for storing it's read data.
+ return Object.assign({}, ...map(({read}) => read, contexts), read);
+}
+
+const readTarget = (nameTable, data) =>
+ object(...map(([name, id]) => [name, data[id]], nameTable))
+
+const ContextHandler = Class({
+ extends: Component,
+ initialize: Component,
+ get context() {
+ return this.state.options.context;
+ },
+ get read() {
+ return this.state.options.read;
+ },
+ [Component.initial](options) {
+ return {
+ table: makeReadTable(options),
+ requiredContext: filter(context => context.isRequired, options.context),
+ optionalContext: filter(context => !context.isRequired, options.context)
+ }
+ },
+ [isMatching]() {
+ const {target, requiredContext, optionalContext} = this.state;
+ return isEvery(context => context.isCurrent(target), requiredContext) &&
+ (count(optionalContext) === 0 ||
+ some(context => context.isCurrent(target), optionalContext));
+ },
+ setup() {
+ const table = makeReadTable(this.state.options);
+ this[readTable] = table;
+ this[nameTable] = [...map(symbol => [symbol, table[symbol].id], symbols(table)),
+ ...map(name => [name, table[name].id], keys(table))];
+
+
+ contextMenu.register(this);
+
+ each(child => contextMenu.remove(child), this.state.children);
+ contextMenu.add(this);
+ },
+ dispose() {
+ contextMenu.remove(this);
+
+ each(child => contextMenu.unregister(child), this.state.children);
+ contextMenu.unregister(this);
+ },
+ // Internal `Symbol("onContext")` method is invoked when "contextmenu" event
+ // occurs in content process. Context handles with children delegate to each
+ // child and patch it's internal state to reflect new contextmenu target.
+ [onContext](data) {
+ propagateOnContext(this, data);
+ this[Component.patch]({target: readTarget(this[nameTable], data)});
+ }
+});
+const isContextHandler = item => item instanceof ContextHandler;
+
+exports.ContextHandler = ContextHandler;
+
+const Menu = Class({
+ extends: ContextHandler,
+ [isMatching]() {
+ return ContextHandler.prototype[isMatching].call(this) &&
+ this.state.children.filter(isContextHandler)
+ .some(isContextMatch);
+ },
+ [Component.render]({children, options}) {
+ const items = children.filter(isContextMatch);
+ return {tagName: "menu",
+ className: "sdk-context-menu menu-iconic",
+ label: options.label,
+ accesskey: options.accesskey,
+ image: options.icon,
+ children: [{tagName: "menupopup",
+ children: items}]};
+ }
+});
+exports.Menu = Menu;
+
+const onCommand = Symbol("context-menu/item/onCommand");
+const Item = Class({
+ extends: ContextHandler,
+ get onClick() {
+ return this.state.options.onClick;
+ },
+ [Component.render]({options}) {
+ const {label, icon, accesskey} = options;
+ return {tagName: "menuitem",
+ className: "sdk-context-menu-item menuitem-iconic",
+ label,
+ accesskey,
+ image: icon,
+ oncommand: this};
+ },
+ handleEvent(event) {
+ if (this.onClick)
+ this.onClick(this.state.target);
+ }
+});
+exports.Item = Item;
+
+var Separator = Class({
+ extends: Component,
+ initialize: Component,
+ [Component.render]() {
+ return {tagName: "menuseparator",
+ className: "sdk-context-menu-separator"}
+ },
+ [onContext]() {
+
+ }
+});
+exports.Separator = Separator;
+
+exports.Contexts = Contexts;
+exports.Readers = Readers;
+
+const createElement = (vnode, {document}) => {
+ const node = vnode.namespace ?
+ document.createElementNS(vnode.namespace, vnode.tagName) :
+ document.createElement(vnode.tagName);
+
+ node.setAttribute("data-component-path", vnode[Component.path]);
+
+ each(([key, value]) => {
+ if (key === "tagName") {
+ return;
+ }
+ if (key === "children") {
+ return;
+ }
+
+ if (key.startsWith("on")) {
+ node.addEventListener(key.substr(2), value)
+ return;
+ }
+
+ if (typeof(value) !== "object" &&
+ typeof(value) !== "function" &&
+ value !== void(0) &&
+ value !== null)
+ {
+ if (key === "className") {
+ node[key] = value;
+ }
+ else {
+ node.setAttribute(key, value);
+ }
+ return;
+ }
+ }, pairs(vnode));
+
+ each(child => node.appendChild(createElement(child, {document})), vnode.children);
+ return node;
+};
+
+const htmlWriter = tree => {
+ if (tree !== null) {
+ const root = tree.element;
+ const node = createElement(tree, {document: root.ownerDocument});
+ const before = root.querySelector("[data-component-path='/']");
+ if (before) {
+ root.replaceChild(node, before);
+ } else {
+ root.appendChild(node);
+ }
+ }
+};
+
+
+const contextMenu = ContextMenuExtension();
+exports.contextMenu = contextMenu;
+Component.mount(contextMenu, htmlWriter);
diff --git a/components/jetpack/sdk/context-menu/readers.js b/components/jetpack/sdk/context-menu/readers.js
new file mode 100644
index 000000000..5078f8f29
--- /dev/null
+++ b/components/jetpack/sdk/context-menu/readers.js
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { Class } = require("../core/heritage");
+const { extend } = require("../util/object");
+const { memoize, method, identity } = require("../lang/functional");
+
+const serializeCategory = ({type}) => ({ category: `reader/${type}()` });
+
+const Reader = Class({
+ initialize() {
+ this.id = `reader/${this.type}()`
+ },
+ toJSON() {
+ return serializeCategory(this);
+ }
+});
+
+
+const MediaTypeReader = Class({ extends: Reader, type: "MediaType" });
+exports.MediaType = MediaTypeReader;
+
+const LinkURLReader = Class({ extends: Reader, type: "LinkURL" });
+exports.LinkURL = LinkURLReader;
+
+const SelectionReader = Class({ extends: Reader, type: "Selection" });
+exports.Selection = SelectionReader;
+
+const isPageReader = Class({ extends: Reader, type: "isPage" });
+exports.isPage = isPageReader;
+
+const isFrameReader = Class({ extends: Reader, type: "isFrame" });
+exports.isFrame = isFrameReader;
+
+const isEditable = Class({ extends: Reader, type: "isEditable"});
+exports.isEditable = isEditable;
+
+
+
+const ParameterizedReader = Class({
+ extends: Reader,
+ readParameter: function(value) {
+ return value;
+ },
+ toJSON: function() {
+ var json = serializeCategory(this);
+ json[this.parameter] = this[this.parameter];
+ return json;
+ },
+ initialize(...params) {
+ if (params.length) {
+ this[this.parameter] = this.readParameter(...params);
+ }
+ this.id = `reader/${this.type}(${JSON.stringify(this[this.parameter])})`;
+ }
+});
+exports.ParameterizedReader = ParameterizedReader;
+
+
+const QueryReader = Class({
+ extends: ParameterizedReader,
+ type: "Query",
+ parameter: "path"
+});
+exports.Query = QueryReader;
+
+
+const AttributeReader = Class({
+ extends: ParameterizedReader,
+ type: "Attribute",
+ parameter: "name"
+});
+exports.Attribute = AttributeReader;
+
+const SrcURLReader = Class({
+ extends: AttributeReader,
+ name: "src",
+});
+exports.SrcURL = SrcURLReader;
+
+const PageURLReader = Class({
+ extends: QueryReader,
+ path: "ownerDocument.URL",
+});
+exports.PageURL = PageURLReader;
+
+const SelectorMatchReader = Class({
+ extends: ParameterizedReader,
+ type: "SelectorMatch",
+ parameter: "selector"
+});
+exports.SelectorMatch = SelectorMatchReader;
+
+const extractors = new WeakMap();
+extractors.id = 0;
+
+
+var Extractor = Class({
+ extends: ParameterizedReader,
+ type: "Extractor",
+ parameter: "source",
+ initialize: function(f) {
+ this[this.parameter] = String(f);
+ if (!extractors.has(f)) {
+ extractors.id = extractors.id + 1;
+ extractors.set(f, extractors.id);
+ }
+
+ this.id = `reader/${this.type}.for(${extractors.get(f)})`
+ }
+});
+exports.Extractor = Extractor;
diff --git a/components/jetpack/sdk/context-menu@2.js b/components/jetpack/sdk/context-menu@2.js
new file mode 100644
index 000000000..45ad804e9
--- /dev/null
+++ b/components/jetpack/sdk/context-menu@2.js
@@ -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/. */
+"use strict";
+
+const shared = require("toolkit/require");
+const { Item, Separator, Menu, Contexts, Readers } = shared.require("sdk/context-menu/core");
+const { setupDisposable, disposeDisposable, Disposable } = require("sdk/core/disposable")
+const { Class } = require("sdk/core/heritage")
+
+const makeDisposable = Type => Class({
+ extends: Type,
+ implements: [Disposable],
+ initialize: Type.prototype.initialize,
+ setup(...params) {
+ Type.prototype.setup.call(this, ...params);
+ setupDisposable(this);
+ },
+ dispose(...params) {
+ disposeDisposable(this);
+ Type.prototype.dispose.call(this, ...params);
+ }
+});
+
+exports.Separator = Separator;
+exports.Contexts = Contexts;
+exports.Readers = Readers;
+
+// Subclass Item & Menu shared classes so their items
+// will be unloaded when add-on is unloaded.
+exports.Item = makeDisposable(Item);
+exports.Menu = makeDisposable(Menu);
diff --git a/components/jetpack/sdk/core/disposable.js b/components/jetpack/sdk/core/disposable.js
new file mode 100644
index 000000000..19f7eaa9f
--- /dev/null
+++ b/components/jetpack/sdk/core/disposable.js
@@ -0,0 +1,186 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Class } = require("./heritage");
+const { Observer, subscribe, unsubscribe, observe } = require("./observer");
+const { isWeak } = require("./reference");
+const SDKWeakSet = require("../lang/weak-set");
+
+const method = require("../../method/core");
+
+const unloadSubject = require('@loader/unload');
+const addonUnloadTopic = "sdk:loader:destroy";
+
+const uninstall = method("disposable/uninstall");
+exports.uninstall = uninstall;
+
+const shutdown = method("disposable/shutdown");
+exports.shutdown = shutdown;
+
+const disable = method("disposable/disable");
+exports.disable = disable;
+
+const upgrade = method("disposable/upgrade");
+exports.upgrade = upgrade;
+
+const downgrade = method("disposable/downgrade");
+exports.downgrade = downgrade;
+
+const unload = method("disposable/unload");
+exports.unload = unload;
+
+const dispose = method("disposable/dispose");
+exports.dispose = dispose;
+dispose.define(Object, object => object.dispose());
+
+const setup = method("disposable/setup");
+exports.setup = setup;
+setup.define(Object, (object, ...args) => object.setup(...args));
+
+// DisposablesUnloadObserver is the class which subscribe the
+// Observer Service to be notified when the add-on loader is
+// unloading to be able to dispose all the existent disposables.
+const DisposablesUnloadObserver = Class({
+ implements: [Observer],
+ initialize: function(...args) {
+ // Set of the non-weak disposables registered to be disposed.
+ this.disposables = new Set();
+ // Target of the weak disposables registered to be disposed
+ // (and tracked on this target using the SDK weak-set module).
+ this.weakDisposables = {};
+ },
+ subscribe(disposable) {
+ if (isWeak(disposable)) {
+ SDKWeakSet.add(this.weakDisposables, disposable);
+ } else {
+ this.disposables.add(disposable);
+ }
+ },
+ unsubscribe(disposable) {
+ if (isWeak(disposable)) {
+ SDKWeakSet.remove(this.weakDisposables, disposable);
+ } else {
+ this.disposables.delete(disposable);
+ }
+ },
+ tryUnloadDisposable(disposable) {
+ try {
+ if (disposable) {
+ unload(disposable);
+ }
+ } catch(e) {
+ console.error("Error unloading a",
+ isWeak(disposable) ? "weak disposable" : "disposable",
+ disposable, e);
+ }
+ },
+ unloadAll() {
+ // Remove all the subscribed disposables.
+ for (let disposable of this.disposables) {
+ this.tryUnloadDisposable(disposable);
+ }
+
+ this.disposables.clear();
+
+ // Remove all the subscribed weak disposables.
+ for (let disposable of SDKWeakSet.iterator(this.weakDisposables)) {
+ this.tryUnloadDisposable(disposable);
+ }
+
+ SDKWeakSet.clear(this.weakDisposables);
+ }
+});
+const disposablesUnloadObserver = new DisposablesUnloadObserver();
+
+// The DisposablesUnloadObserver instance is the only object which subscribes
+// the Observer Service directly, it observes add-on unload notifications in
+// order to trigger `unload` on all its subscribed disposables.
+observe.define(DisposablesUnloadObserver, (obj, subject, topic, data) => {
+ const isUnloadTopic = topic === addonUnloadTopic;
+ const isUnloadSubject = subject.wrappedJSObject === unloadSubject;
+ if (isUnloadTopic && isUnloadSubject) {
+ unsubscribe(disposablesUnloadObserver, addonUnloadTopic);
+ disposablesUnloadObserver.unloadAll();
+ }
+});
+
+subscribe(disposablesUnloadObserver, addonUnloadTopic, false);
+
+// Set's up disposable instance.
+const setupDisposable = disposable => {
+ disposablesUnloadObserver.subscribe(disposable);
+};
+exports.setupDisposable = setupDisposable;
+
+// Tears down disposable instance.
+const disposeDisposable = disposable => {
+ disposablesUnloadObserver.unsubscribe(disposable);
+};
+exports.disposeDisposable = disposeDisposable;
+
+// Base type that takes care of disposing it's instances on add-on unload.
+// Also makes sure to remove unload listener if it's already being disposed.
+const Disposable = Class({
+ initialize: function(...args) {
+ // First setup instance before initializing it's disposal. If instance
+ // fails to initialize then there is no instance to be disposed at the
+ // unload.
+ setup(this, ...args);
+ setupDisposable(this);
+ },
+ destroy: function(reason) {
+ // Destroying disposable removes unload handler so that attempt to dispose
+ // won't be made at unload & delegates to dispose.
+ disposeDisposable(this);
+ unload(this, reason);
+ },
+ setup: function() {
+ // Implement your initialize logic here.
+ },
+ dispose: function() {
+ // Implement your cleanup logic here.
+ }
+});
+exports.Disposable = Disposable;
+
+const unloaders = {
+ destroy: dispose,
+ uninstall: uninstall,
+ shutdown: shutdown,
+ disable: disable,
+ upgrade: upgrade,
+ downgrade: downgrade
+};
+
+const unloaded = new WeakMap();
+unload.define(Disposable, (disposable, reason) => {
+ if (!unloaded.get(disposable)) {
+ unloaded.set(disposable, true);
+ // Pick an unload handler associated with an unload
+ // reason (falling back to destroy if not found) and
+ // delegate unloading to it.
+ const unload = unloaders[reason] || unloaders.destroy;
+ unload(disposable);
+ }
+});
+
+// If add-on is disabled manually, it's being upgraded, downgraded
+// or uninstalled `dispose` is invoked to undo any changes that
+// has being done by it in this session.
+disable.define(Disposable, dispose);
+downgrade.define(Disposable, dispose);
+upgrade.define(Disposable, dispose);
+uninstall.define(Disposable, dispose);
+
+// If application is shut down no dispose is invoked as undo-ing
+// changes made by instance is likely to just waste of resources &
+// increase shutdown time. Although specefic components may choose
+// to implement shutdown handler that does something better.
+shutdown.define(Disposable, disposable => {});
diff --git a/components/jetpack/sdk/core/heritage.js b/components/jetpack/sdk/core/heritage.js
new file mode 100644
index 000000000..fc87ba1f5
--- /dev/null
+++ b/components/jetpack/sdk/core/heritage.js
@@ -0,0 +1,184 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+var getPrototypeOf = Object.getPrototypeOf;
+var getNames = x => [...Object.getOwnPropertyNames(x),
+ ...Object.getOwnPropertySymbols(x)];
+var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+var create = Object.create;
+var freeze = Object.freeze;
+var unbind = Function.call.bind(Function.bind, Function.call);
+
+// This shortcut makes sure that we do perform desired operations, even if
+// associated methods have being overridden on the used object.
+var owns = unbind(Object.prototype.hasOwnProperty);
+var apply = unbind(Function.prototype.apply);
+var slice = Array.slice || unbind(Array.prototype.slice);
+var reduce = Array.reduce || unbind(Array.prototype.reduce);
+var map = Array.map || unbind(Array.prototype.map);
+var concat = Array.concat || unbind(Array.prototype.concat);
+
+// Utility function to get own properties descriptor map.
+function getOwnPropertyDescriptors(object) {
+ return reduce(getNames(object), function(descriptor, name) {
+ descriptor[name] = getOwnPropertyDescriptor(object, name);
+ return descriptor;
+ }, {});
+}
+
+function isDataProperty(property) {
+ var value = property.value;
+ var type = typeof(property.value);
+ return "value" in property &&
+ (type !== "object" || value === null) &&
+ type !== "function";
+}
+
+function getDataProperties(object) {
+ var properties = getOwnPropertyDescriptors(object);
+ return getNames(properties).reduce(function(result, name) {
+ var property = properties[name];
+ if (isDataProperty(property)) {
+ result[name] = {
+ value: property.value,
+ writable: true,
+ configurable: true,
+ enumerable: false
+ };
+ }
+ return result;
+ }, {})
+}
+
+/**
+ * Takes `source` object as an argument and returns identical object
+ * with the difference that all own properties will be non-enumerable
+ */
+function obscure(source) {
+ var descriptor = reduce(getNames(source), function(descriptor, name) {
+ var property = getOwnPropertyDescriptor(source, name);
+ property.enumerable = false;
+ descriptor[name] = property;
+ return descriptor;
+ }, {});
+ return create(getPrototypeOf(source), descriptor);
+}
+exports.obscure = obscure;
+
+/**
+ * Takes arbitrary number of source objects and returns fresh one, that
+ * inherits from the same prototype as a first argument and implements all
+ * own properties of all argument objects. If two or more argument objects
+ * have own properties with the same name, the property is overridden, with
+ * precedence from right to left, implying, that properties of the object on
+ * the left are overridden by a same named property of the object on the right.
+ */
+var mix = function(source) {
+ var descriptor = reduce(slice(arguments), function(descriptor, source) {
+ return reduce(getNames(source), function(descriptor, name) {
+ descriptor[name] = getOwnPropertyDescriptor(source, name);
+ return descriptor;
+ }, descriptor);
+ }, {});
+
+ return create(getPrototypeOf(source), descriptor);
+};
+exports.mix = mix;
+
+/**
+ * Returns a frozen object with that inherits from the given `prototype` and
+ * implements all own properties of the given `properties` object.
+ */
+function extend(prototype, properties) {
+ return create(prototype, getOwnPropertyDescriptors(properties));
+}
+exports.extend = extend;
+
+/**
+ * Returns a constructor function with a proper `prototype` setup. Returned
+ * constructor's `prototype` inherits from a given `options.extends` or
+ * `Class.prototype` if omitted and implements all the properties of the
+ * given `option`. If `options.implemens` array is passed, it's elements
+ * will be mixed into prototype as well. Also, `options.extends` can be
+ * a function or a prototype. If function than it's prototype is used as
+ * an ancestor of the prototype, if it's an object that it's used directly.
+ * Also `options.implements` may contain functions or objects, in case of
+ * functions their prototypes are used for mixing.
+ */
+var Class = new function() {
+ function prototypeOf(input) {
+ return typeof(input) === 'function' ? input.prototype : input;
+ }
+ var none = freeze([]);
+
+ return function Class(options) {
+ // Create descriptor with normalized `options.extends` and
+ // `options.implements`.
+ var descriptor = {
+ // Normalize extends property of `options.extends` to a prototype object
+ // in case it's constructor. If property is missing that fallback to
+ // `Type.prototype`.
+ extends: owns(options, 'extends') ?
+ prototypeOf(options.extends) : Class.prototype,
+ // Normalize `options.implements` to make sure that it's array of
+ // prototype objects instead of constructor functions.
+ implements: owns(options, 'implements') ?
+ freeze(map(options.implements, prototypeOf)) : none
+ };
+
+ // Create array of property descriptors who's properties will be defined
+ // on the resulting prototype. Note: Using reflection `concat` instead of
+ // method as it may be overridden.
+ var descriptors = concat(descriptor.implements, options, descriptor, {
+ constructor: constructor
+ });
+
+ // Note: we use reflection `apply` in the constructor instead of method
+ // call since later may be overridden.
+ function constructor() {
+ var instance = create(prototype, attributes);
+ if (initialize) apply(initialize, instance, arguments);
+ return instance;
+ }
+ // Create `prototype` that inherits from given ancestor passed as
+ // `options.extends`, falling back to `Type.prototype`, implementing all
+ // properties of given `options.implements` and `options` itself.
+ var prototype = extend(descriptor.extends, mix.apply(mix, descriptors));
+ var initialize = prototype.initialize;
+
+ // Combine ancestor attributes with prototype's attributes so that
+ // ancestors attributes also become initializeable.
+ var attributes = mix(descriptor.extends.constructor.attributes || {},
+ getDataProperties(prototype));
+
+ constructor.attributes = attributes;
+ Object.defineProperty(constructor, 'prototype', {
+ configurable: false,
+ writable: false,
+ value: prototype
+ });
+ return constructor;
+ };
+}
+Class.prototype = extend(null, obscure({
+ constructor: function constructor() {
+ this.initialize.apply(this, arguments);
+ return this;
+ },
+ initialize: function initialize() {
+ // Do your initialization logic here
+ },
+ // Copy useful properties from `Object.prototype`.
+ toString: Object.prototype.toString,
+ toLocaleString: Object.prototype.toLocaleString,
+ toSource: Object.prototype.toSource,
+ valueOf: Object.prototype.valueOf,
+ isPrototypeOf: Object.prototype.isPrototypeOf
+}));
+exports.Class = freeze(Class);
diff --git a/components/jetpack/sdk/core/namespace.js b/components/jetpack/sdk/core/namespace.js
new file mode 100644
index 000000000..3ceb73b72
--- /dev/null
+++ b/components/jetpack/sdk/core/namespace.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const create = Object.create;
+const prototypeOf = Object.getPrototypeOf;
+
+/**
+ * Returns a new namespace, function that may can be used to access an
+ * namespaced object of the argument argument. Namespaced object are associated
+ * with owner objects via weak references. Namespaced objects inherit from the
+ * owners ancestor namespaced object. If owner's ancestor is `null` then
+ * namespaced object inherits from given `prototype`. Namespaces can be used
+ * to define internal APIs that can be shared via enclosing `namespace`
+ * function.
+ * @examples
+ * const internals = ns();
+ * internals(object).secret = secret;
+ */
+function ns() {
+ const map = new WeakMap();
+ return function namespace(target) {
+ if (!target) // If `target` is not an object return `target` itself.
+ return target;
+ // If target has no namespaced object yet, create one that inherits from
+ // the target prototype's namespaced object.
+ if (!map.has(target))
+ map.set(target, create(namespace(prototypeOf(target) || null)));
+
+ return map.get(target);
+ };
+};
+
+// `Namespace` is a e4x function in the scope, so we export the function also as
+// `ns` as alias to avoid clashing.
+exports.ns = ns;
+exports.Namespace = ns;
diff --git a/components/jetpack/sdk/core/observer.js b/components/jetpack/sdk/core/observer.js
new file mode 100644
index 000000000..7e11bf8f9
--- /dev/null
+++ b/components/jetpack/sdk/core/observer.js
@@ -0,0 +1,89 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+
+const { Cc, Ci, Cr, Cu } = require("chrome");
+const { Class } = require("./heritage");
+const { isWeak } = require("./reference");
+const method = require("../../method/core");
+
+const observerService = Cc['@mozilla.org/observer-service;1'].
+ getService(Ci.nsIObserverService);
+
+const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
+const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
+const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
+
+// This is a method that will be invoked when notification observer
+// subscribed to occurs.
+const observe = method("observer/observe");
+exports.observe = observe;
+
+// Method to subscribe to the observer notification.
+const subscribe = method("observe/subscribe");
+exports.subscribe = subscribe;
+
+
+// Method to unsubscribe from the observer notifications.
+const unsubscribe = method("observer/unsubscribe");
+exports.unsubscribe = unsubscribe;
+
+
+// This is wrapper class that takes a `delegate` and produces
+// instance of `nsIObserver` which will delegate to a given
+// object when observer notification occurs.
+const ObserverDelegee = Class({
+ initialize: function(delegate) {
+ this.delegate = delegate;
+ },
+ QueryInterface: function(iid) {
+ if (!iid.equals(Ci.nsIObserver) &&
+ !iid.equals(Ci.nsISupportsWeakReference) &&
+ !iid.equals(Ci.nsISupports))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ },
+ observe: function(subject, topic, data) {
+ observe(this.delegate, subject, topic, data);
+ }
+});
+
+
+// Class that can be either mixed in or inherited from in
+// order to subscribe / unsubscribe for observer notifications.
+const Observer = Class({});
+exports.Observer = Observer;
+
+// Weak maps that associates instance of `ObserverDelegee` with
+// an actual observer. It ensures that `ObserverDelegee` instance
+// won't be GC-ed until given `observer` is.
+const subscribers = new WeakMap();
+
+// Implementation of `subscribe` for `Observer` type just registers
+// observer for an observer service. If `isWeak(observer)` is `true`
+// observer service won't hold strong reference to a given `observer`.
+subscribe.define(Observer, (observer, topic) => {
+ if (!subscribers.has(observer)) {
+ const delegee = new ObserverDelegee(observer);
+ subscribers.set(observer, delegee);
+ addObserver(delegee, topic, isWeak(observer));
+ }
+});
+
+// Unsubscribes `observer` from observer notifications for the
+// given `topic`.
+unsubscribe.define(Observer, (observer, topic) => {
+ const delegee = subscribers.get(observer);
+ if (delegee) {
+ subscribers.delete(observer);
+ removeObserver(delegee, topic);
+ }
+});
diff --git a/components/jetpack/sdk/core/promise.js b/components/jetpack/sdk/core/promise.js
new file mode 100644
index 000000000..f4bd7b0f5
--- /dev/null
+++ b/components/jetpack/sdk/core/promise.js
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+/*
+ * Uses `Promise.jsm` as a core implementation, with additional sugar
+ * from previous implementation, with inspiration from `Q` and `when`
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm
+ * https://github.com/cujojs/when
+ * https://github.com/kriskowal/q
+ */
+const PROMISE_URI = 'resource://gre/modules/Promise.jsm';
+
+getEnvironment.call(this, function ({ require, exports, module, Cu }) {
+
+const Promise = Cu.import(PROMISE_URI, {}).Promise;
+const { Debugging, defer, resolve, all, reject, race } = Promise;
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+var promised = (function() {
+ // Note: Define shortcuts and utility functions here in order to avoid
+ // slower property accesses and unnecessary closure creations on each
+ // call of this popular function.
+
+ var call = Function.call;
+ var concat = Array.prototype.concat;
+
+ // Utility function that does following:
+ // execute([ f, self, args...]) => f.apply(self, args)
+ function execute (args) {
+ return call.apply(call, args);
+ }
+
+ // Utility function that takes promise of `a` array and maybe promise `b`
+ // as arguments and returns promise for `a.concat(b)`.
+ function promisedConcat(promises, unknown) {
+ return promises.then(function (values) {
+ return resolve(unknown)
+ .then(value => values.concat([value]));
+ });
+ }
+
+ return function promised(f, prototype) {
+ /**
+ Returns a wrapped `f`, which when called returns a promise that resolves to
+ `f(...)` passing all the given arguments to it, which by the way may be
+ promises. Optionally second `prototype` argument may be provided to be used
+ a prototype for a returned promise.
+
+ ## Example
+
+ var promise = promised(Array)(1, promise(2), promise(3))
+ promise.then(console.log) // => [ 1, 2, 3 ]
+ **/
+
+ return function promised(...args) {
+ // create array of [ f, this, args... ]
+ return [f, this, ...args].
+ // reduce it via `promisedConcat` to get promised array of fulfillments
+ reduce(promisedConcat, resolve([], prototype)).
+ // finally map that to promise of `f.apply(this, args...)`
+ then(execute);
+ };
+ };
+})();
+
+exports.promised = promised;
+exports.all = all;
+exports.defer = defer;
+exports.resolve = resolve;
+exports.reject = reject;
+exports.race = race;
+exports.Promise = Promise;
+exports.Debugging = Debugging;
+});
+
+function getEnvironment (callback) {
+ let Cu, _exports, _module, _require;
+
+ // CommonJS / SDK
+ if (typeof(require) === 'function') {
+ Cu = require('chrome').Cu;
+ _exports = exports;
+ _module = module;
+ _require = require;
+ }
+ // JSM
+ else if (String(this).indexOf('BackstagePass') >= 0) {
+ Cu = this['Components'].utils;
+ _exports = this.Promise = {};
+ _module = { uri: __URI__, id: 'promise/core' };
+ _require = uri => {
+ let imports = {};
+ Cu.import(uri, imports);
+ return imports;
+ };
+ this.EXPORTED_SYMBOLS = ['Promise'];
+ // mozIJSSubScriptLoader.loadSubscript
+ } else if (~String(this).indexOf('Sandbox')) {
+ Cu = this['Components'].utils;
+ _exports = this;
+ _module = { id: 'promise/core' };
+ _require = uri => {};
+ }
+
+ callback({
+ Cu: Cu,
+ exports: _exports,
+ module: _module,
+ require: _require
+ });
+}
+
diff --git a/components/jetpack/sdk/core/reference.js b/components/jetpack/sdk/core/reference.js
new file mode 100644
index 000000000..04549cd0f
--- /dev/null
+++ b/components/jetpack/sdk/core/reference.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const method = require("../../method/core");
+const { Class } = require("./heritage");
+
+// Object that inherit or mix WeakRefence inn will register
+// weak observes for system notifications.
+const WeakReference = Class({});
+exports.WeakReference = WeakReference;
+
+
+// If `isWeak(object)` is `true` observer installed
+// for such `object` will be weak, meaning that it will
+// be GC-ed if nothing else but observer is observing it.
+// By default everything except `WeakReference` will return
+// `false`.
+const isWeak = method("reference/weak?");
+exports.isWeak = isWeak;
+
+isWeak.define(Object, _ => false);
+isWeak.define(WeakReference, _ => true);
diff --git a/components/jetpack/sdk/deprecated/api-utils.js b/components/jetpack/sdk/deprecated/api-utils.js
new file mode 100644
index 000000000..856fc50cb
--- /dev/null
+++ b/components/jetpack/sdk/deprecated/api-utils.js
@@ -0,0 +1,197 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "deprecated"
+};
+
+const { merge } = require("../util/object");
+const { union } = require("../util/array");
+const { isNil, isRegExp } = require("../lang/type");
+
+// The possible return values of getTypeOf.
+const VALID_TYPES = [
+ "array",
+ "boolean",
+ "function",
+ "null",
+ "number",
+ "object",
+ "string",
+ "undefined",
+ "regexp"
+];
+
+const { isArray } = Array;
+
+/**
+ * Returns a validated options dictionary given some requirements. If any of
+ * the requirements are not met, an exception is thrown.
+ *
+ * @param options
+ * An object, the options dictionary to validate. It's not modified.
+ * If it's null or otherwise falsey, an empty object is assumed.
+ * @param requirements
+ * An object whose keys are the expected keys in options. Any key in
+ * options that is not present in requirements is ignored. Each value
+ * in requirements is itself an object describing the requirements of
+ * its key. There are four optional keys in this object:
+ * map: A function that's passed the value of the key in options.
+ * map's return value is taken as the key's value in the final
+ * validated options, is, and ok. If map throws an exception
+ * it's caught and discarded, and the key's value is its value in
+ * options.
+ * is: An array containing any number of the typeof type names. If
+ * the key's value is none of these types, it fails validation.
+ * Arrays, null and regexps are identified by the special type names
+ * "array", "null", "regexp"; "object" will not match either. No type
+ * coercion is done.
+ * ok: A function that's passed the key's value. If it returns
+ * false, the value fails validation.
+ * msg: If the key's value fails validation, an exception is thrown.
+ * This string will be used as its message. If undefined, a
+ * generic message is used, unless is is defined, in which case
+ * the message will state that the value needs to be one of the
+ * given types.
+ * @return An object whose keys are those keys in requirements that are also in
+ * options and whose values are the corresponding return values of map
+ * or the corresponding values in options. Note that any keys not
+ * shared by both requirements and options are not in the returned
+ * object.
+ */
+exports.validateOptions = function validateOptions(options, requirements) {
+ options = options || {};
+ let validatedOptions = {};
+
+ for (let key in requirements) {
+ let isOptional = false;
+ let mapThrew = false;
+ let req = requirements[key];
+ let [optsVal, keyInOpts] = (key in options) ?
+ [options[key], true] :
+ [undefined, false];
+ if (req.map) {
+ try {
+ optsVal = req.map(optsVal);
+ }
+ catch (err) {
+ if (err instanceof RequirementError)
+ throw err;
+
+ mapThrew = true;
+ }
+ }
+ if (req.is) {
+ let types = req.is;
+
+ if (!isArray(types) && isArray(types.is))
+ types = types.is;
+
+ if (isArray(types)) {
+ isOptional = ['undefined', 'null'].every(v => ~types.indexOf(v));
+
+ // Sanity check the caller's type names.
+ types.forEach(function (typ) {
+ if (VALID_TYPES.indexOf(typ) < 0) {
+ let msg = 'Internal error: invalid requirement type "' + typ + '".';
+ throw new Error(msg);
+ }
+ });
+ if (types.indexOf(getTypeOf(optsVal)) < 0)
+ throw new RequirementError(key, req);
+ }
+ }
+
+ if (req.ok && ((!isOptional || !isNil(optsVal)) && !req.ok(optsVal)))
+ throw new RequirementError(key, req);
+
+ if (keyInOpts || (req.map && !mapThrew && optsVal !== undefined))
+ validatedOptions[key] = optsVal;
+ }
+
+ return validatedOptions;
+};
+
+exports.addIterator = function addIterator(obj, keysValsGenerator) {
+ obj.__iterator__ = function(keysOnly, keysVals) {
+ let keysValsIterator = keysValsGenerator.call(this);
+
+ // "for (.. in ..)" gets only keys, "for each (.. in ..)" gets values,
+ // and "for (.. in Iterator(..))" gets [key, value] pairs.
+ let index = keysOnly ? 0 : 1;
+ while (true)
+ yield keysVals ? keysValsIterator.next() : keysValsIterator.next()[index];
+ };
+};
+
+// Similar to typeof, except arrays, null and regexps are identified by "array" and
+// "null" and "regexp", not "object".
+var getTypeOf = exports.getTypeOf = function getTypeOf(val) {
+ let typ = typeof(val);
+ if (typ === "object") {
+ if (!val)
+ return "null";
+ if (isArray(val))
+ return "array";
+ if (isRegExp(val))
+ return "regexp";
+ }
+ return typ;
+}
+
+function RequirementError(key, requirement) {
+ Error.call(this);
+
+ this.name = "RequirementError";
+
+ let msg = requirement.msg;
+ if (!msg) {
+ msg = 'The option "' + key + '" ';
+ msg += requirement.is ?
+ "must be one of the following types: " + requirement.is.join(", ") :
+ "is invalid.";
+ }
+
+ this.message = msg;
+}
+RequirementError.prototype = Object.create(Error.prototype);
+
+var string = { is: ['string', 'undefined', 'null'] };
+exports.string = string;
+
+var number = { is: ['number', 'undefined', 'null'] };
+exports.number = number;
+
+var boolean = { is: ['boolean', 'undefined', 'null'] };
+exports.boolean = boolean;
+
+var object = { is: ['object', 'undefined', 'null'] };
+exports.object = object;
+
+var array = { is: ['array', 'undefined', 'null'] };
+exports.array = array;
+
+var isTruthyType = type => !(type === 'undefined' || type === 'null');
+var findTypes = v => { while (!isArray(v) && v.is) v = v.is; return v };
+
+function required(req) {
+ let types = (findTypes(req) || VALID_TYPES).filter(isTruthyType);
+
+ return merge({}, req, {is: types});
+}
+exports.required = required;
+
+function optional(req) {
+ req = merge({is: []}, req);
+ req.is = findTypes(req).filter(isTruthyType).concat('undefined', 'null');
+
+ return req;
+}
+exports.optional = optional;
+
+function either(...types) {
+ return union.apply(null, types.map(findTypes));
+}
+exports.either = either;
diff --git a/components/jetpack/sdk/deprecated/events/assembler.js b/components/jetpack/sdk/deprecated/events/assembler.js
new file mode 100644
index 000000000..bb297c24f
--- /dev/null
+++ b/components/jetpack/sdk/deprecated/events/assembler.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Class } = require("../../core/heritage");
+const { removeListener, on } = require("../../dom/events");
+
+/**
+ * Event targets
+ * can be added / removed by calling `observe / ignore` methods. Composer should
+ * provide array of event types it wishes to handle as property
+ * `supportedEventsTypes` and function for handling all those events as
+ * `handleEvent` property.
+ */
+exports.DOMEventAssembler = Class({
+ /**
+ * Function that is supposed to handle all the supported events (that are
+ * present in the `supportedEventsTypes`) from all the observed
+ * `eventTargets`.
+ * @param {Event} event
+ * Event being dispatched.
+ */
+ handleEvent() {
+ throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` method");
+ },
+ /**
+ * Array of supported event names.
+ * @type {String[]}
+ */
+ get supportedEventsTypes() {
+ throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` field");
+ },
+ /**
+ * Adds `eventTarget` to the list of observed `eventTarget`s. Listeners for
+ * supported events will be registered on the given `eventTarget`.
+ * @param {EventTarget} eventTarget
+ */
+ observe: function observe(eventTarget) {
+ this.supportedEventsTypes.forEach(function(eventType) {
+ on(eventTarget, eventType, this);
+ }, this);
+ },
+ /**
+ * Removes `eventTarget` from the list of observed `eventTarget`s. Listeners
+ * for all supported events will be unregistered from the given `eventTarget`.
+ * @param {EventTarget} eventTarget
+ */
+ ignore: function ignore(eventTarget) {
+ this.supportedEventsTypes.forEach(function(eventType) {
+ removeListener(eventTarget, eventType, this);
+ }, this);
+ }
+});
diff --git a/components/jetpack/sdk/deprecated/sync-worker.js b/components/jetpack/sdk/deprecated/sync-worker.js
new file mode 100644
index 000000000..71cadac36
--- /dev/null
+++ b/components/jetpack/sdk/deprecated/sync-worker.js
@@ -0,0 +1,288 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ *
+ * `deprecated/sync-worker` was previously `content/worker`, that was
+ * incompatible with e10s. we are in the process of switching to the new
+ * asynchronous `Worker`, which behaves slightly differently in some edge
+ * cases, so we are keeping this one around for a short period.
+ * try to switch to the new one as soon as possible..
+ *
+ */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Class } = require('../core/heritage');
+const { EventTarget } = require('../event/target');
+const { on, off, emit, setListeners } = require('../event/core');
+const {
+ attach, detach, destroy
+} = require('../content/utils');
+const { method } = require('../lang/functional');
+const { Ci, Cu, Cc } = require('chrome');
+const unload = require('../system/unload');
+const events = require('../system/events');
+const { getInnerId } = require("../window/utils");
+const { WorkerSandbox } = require('../content/sandbox');
+const { isPrivate } = require('../private-browsing/utils');
+
+// A weak map of workers to hold private attributes that
+// should not be exposed
+const workers = new WeakMap();
+
+var modelFor = (worker) => workers.get(worker);
+
+const ERR_DESTROYED =
+ "Couldn't find the worker to receive this message. " +
+ "The script may not be initialized yet, or may already have been unloaded.";
+
+const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
+ "until it is visible again.";
+
+/**
+ * Message-passing facility for communication between code running
+ * in the content and add-on process.
+ * @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker
+ */
+const Worker = Class({
+ implements: [EventTarget],
+ initialize: function WorkerConstructor (options) {
+ // Save model in weak map to not expose properties
+ let model = createModel();
+ workers.set(this, model);
+
+ options = options || {};
+
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScriptOptions' in options)
+ this.contentScriptOptions = options.contentScriptOptions;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('injectInDocument' in options)
+ this.injectInDocument = !!options.injectInDocument;
+
+ setListeners(this, options);
+
+ unload.ensure(this, "destroy");
+
+ // Ensure that worker.port is initialized for contentWorker to be able
+ // to send events during worker initialization.
+ this.port = createPort(this);
+
+ model.documentUnload = documentUnload.bind(this);
+ model.pageShow = pageShow.bind(this);
+ model.pageHide = pageHide.bind(this);
+
+ if ('window' in options)
+ attach(this, options.window);
+ },
+
+ /**
+ * Sends a message to the worker's global scope. Method takes single
+ * argument, which represents data to be sent to the worker. The data may
+ * be any primitive type value or `JSON`. Call of this method asynchronously
+ * emits `message` event with data value in the global scope of this
+ * worker.
+ *
+ * `message` event listeners can be set either by calling
+ * `self.on` with a first argument string `"message"` or by
+ * implementing `onMessage` function in the global scope of this worker.
+ * @param {Number|String|JSON} data
+ */
+ postMessage: function (...data) {
+ let model = modelFor(this);
+ let args = ['message'].concat(data);
+ if (!model.inited) {
+ model.earlyEvents.push(args);
+ return;
+ }
+ processMessage.apply(null, [this].concat(args));
+ },
+
+ get url () {
+ let model = modelFor(this);
+ // model.window will be null after detach
+ return model.window ? model.window.document.location.href : null;
+ },
+
+ get contentURL () {
+ let model = modelFor(this);
+ return model.window ? model.window.document.URL : null;
+ },
+
+ // Implemented to provide some of the previous features of exposing sandbox
+ // so that Worker can be extended
+ getSandbox: function () {
+ return modelFor(this).contentWorker;
+ },
+
+ toString: function () { return '[object Worker]'; },
+ attach: method(attach),
+ detach: method(detach),
+ destroy: method(destroy)
+});
+exports.Worker = Worker;
+
+attach.define(Worker, function (worker, window) {
+ let model = modelFor(worker);
+ model.window = window;
+ // Track document unload to destroy this worker.
+ // We can't watch for unload event on page's window object as it
+ // prevents bfcache from working:
+ // https://developer.mozilla.org/En/Working_with_BFCache
+ model.windowID = getInnerId(model.window);
+ events.on("inner-window-destroyed", model.documentUnload);
+
+ // will set model.contentWorker pointing to the private API:
+ model.contentWorker = WorkerSandbox(worker, model.window);
+
+ // Listen to pagehide event in order to freeze the content script
+ // while the document is frozen in bfcache:
+ model.window.addEventListener("pageshow", model.pageShow, true);
+ model.window.addEventListener("pagehide", model.pageHide, true);
+
+ // Mainly enable worker.port.emit to send event to the content worker
+ model.inited = true;
+ model.frozen = false;
+
+ // Fire off `attach` event
+ emit(worker, 'attach', window);
+
+ // Process all events and messages that were fired before the
+ // worker was initialized.
+ model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
+});
+
+/**
+ * Remove all internal references to the attached document
+ * Tells _port to unload itself and removes all the references from itself.
+ */
+detach.define(Worker, function (worker, reason) {
+ let model = modelFor(worker);
+
+ // maybe unloaded before content side is created
+ if (model.contentWorker) {
+ model.contentWorker.destroy(reason);
+ }
+
+ model.contentWorker = null;
+ if (model.window) {
+ model.window.removeEventListener("pageshow", model.pageShow, true);
+ model.window.removeEventListener("pagehide", model.pageHide, true);
+ }
+ model.window = null;
+ // This method may be called multiple times,
+ // avoid dispatching `detach` event more than once
+ if (model.windowID) {
+ model.windowID = null;
+ events.off("inner-window-destroyed", model.documentUnload);
+ model.earlyEvents.length = 0;
+ emit(worker, 'detach');
+ }
+ model.inited = false;
+});
+
+isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
+
+/**
+ * Tells content worker to unload itself and
+ * removes all the references from itself.
+ */
+destroy.define(Worker, function (worker, reason) {
+ detach(worker, reason);
+ modelFor(worker).inited = true;
+ // Specifying no type or listener removes all listeners
+ // from target
+ off(worker);
+ off(worker.port);
+});
+
+/**
+ * Events fired by workers
+ */
+function documentUnload ({ subject, data }) {
+ let model = modelFor(this);
+ let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (innerWinID != model.windowID) return false;
+ detach(this);
+ return true;
+}
+
+function pageShow () {
+ let model = modelFor(this);
+ model.contentWorker.emitSync('pageshow');
+ emit(this, 'pageshow');
+ model.frozen = false;
+}
+
+function pageHide () {
+ let model = modelFor(this);
+ model.contentWorker.emitSync('pagehide');
+ emit(this, 'pagehide');
+ model.frozen = true;
+}
+
+/**
+ * Fired from postMessage and emitEventToContent, or from the earlyMessage
+ * queue when fired before the content is loaded. Sends arguments to
+ * contentWorker if able
+ */
+
+function processMessage (worker, ...args) {
+ let model = modelFor(worker) || {};
+ if (!model.contentWorker)
+ throw new Error(ERR_DESTROYED);
+ if (model.frozen)
+ throw new Error(ERR_FROZEN);
+ model.contentWorker.emit.apply(null, args);
+}
+
+function createModel () {
+ return {
+ // List of messages fired before worker is initialized
+ earlyEvents: [],
+ // Is worker connected to the content worker sandbox ?
+ inited: false,
+ // Is worker being frozen? i.e related document is frozen in bfcache.
+ // Content script should not be reachable if frozen.
+ frozen: true,
+ /**
+ * Reference to the content side of the worker.
+ * @type {WorkerGlobalScope}
+ */
+ contentWorker: null,
+ /**
+ * Reference to the window that is accessible from
+ * the content scripts.
+ * @type {Object}
+ */
+ window: null
+ };
+}
+
+function createPort (worker) {
+ let port = EventTarget();
+ port.emit = emitEventToContent.bind(null, worker);
+ return port;
+}
+
+/**
+ * Emit a custom event to the content script,
+ * i.e. emit this event on `self.port`
+ */
+function emitEventToContent (worker, ...eventArgs) {
+ let model = modelFor(worker);
+ let args = ['event'].concat(eventArgs);
+ if (!model.inited) {
+ model.earlyEvents.push(args);
+ return;
+ }
+ processMessage.apply(null, [worker].concat(args));
+}
diff --git a/components/jetpack/sdk/deprecated/unit-test-finder.js b/components/jetpack/sdk/deprecated/unit-test-finder.js
new file mode 100644
index 000000000..e38629f45
--- /dev/null
+++ b/components/jetpack/sdk/deprecated/unit-test-finder.js
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "deprecated"
+};
+
+const file = require("../io/file");
+const { Loader } = require("../test/loader");
+
+const { isNative } = require('@loader/options');
+
+const cuddlefish = isNative ? require("toolkit/loader") : require("../loader/cuddlefish");
+
+const { defer, resolve } = require("../core/promise");
+const { getAddon } = require("../addon/installer");
+const { id } = require("sdk/self");
+const { newURI } = require('sdk/url/utils');
+const { getZipReader } = require("../zip/utils");
+
+const { Cc, Ci, Cu } = require("chrome");
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+
+const CFX_TEST_REGEX = /(([^\/]+\/)(?:lib\/)?)?(tests?\/test-[^\.\/]+)\.js$/;
+const JPM_TEST_REGEX = /^()(tests?\/test-[^\.\/]+)\.js$/;
+
+const { mapcat, map, filter, fromEnumerator } = require("sdk/util/sequence");
+
+const toFile = x => x.QueryInterface(Ci.nsIFile);
+const isTestFile = ({leafName}) => leafName.substr(0, 5) == "test-" && leafName.substr(-3, 3) == ".js";
+const getFileURI = x => ios.newFileURI(x).spec;
+
+const getDirectoryEntries = file => map(toFile, fromEnumerator(_ => file.directoryEntries));
+const getTestFiles = directory => filter(isTestFile, getDirectoryEntries(directory));
+const getTestURIs = directory => map(getFileURI, getTestFiles(directory));
+
+const isDirectory = x => x.isDirectory();
+const getTestEntries = directory => mapcat(entry =>
+ /^tests?$/.test(entry.leafName) ? getTestURIs(entry) : getTestEntries(entry),
+ filter(isDirectory, getDirectoryEntries(directory)));
+
+const removeDups = (array) => array.reduce((result, value) => {
+ if (value != result[result.length - 1]) {
+ result.push(value);
+ }
+ return result;
+}, []);
+
+const getSuites = function getSuites({ id, filter }) {
+ const TEST_REGEX = isNative ? JPM_TEST_REGEX : CFX_TEST_REGEX;
+
+ return getAddon(id).then(addon => {
+ let fileURI = addon.getResourceURI("tests/");
+ let isPacked = fileURI.scheme == "jar";
+ let xpiURI = addon.getResourceURI();
+ let file = xpiURI.QueryInterface(Ci.nsIFileURL).file;
+ let suites = [];
+ let addEntry = (entry) => {
+ if (filter(entry) && TEST_REGEX.test(entry)) {
+ let suite = (isNative ? "./" : "") + (RegExp.$2 || "") + RegExp.$3;
+ suites.push(suite);
+ }
+ }
+
+ if (isPacked) {
+ return getZipReader(file).then(zip => {
+ let entries = zip.findEntries(null);
+ while (entries.hasMore()) {
+ let entry = entries.getNext();
+ addEntry(entry);
+ }
+ zip.close();
+
+ // sort and remove dups
+ suites = removeDups(suites.sort());
+ return suites;
+ })
+ }
+ else {
+ let tests = [...getTestEntries(file)];
+ let rootURI = addon.getResourceURI("/");
+ tests.forEach((entry) => {
+ addEntry(entry.replace(rootURI.spec, ""));
+ });
+ }
+
+ // sort and remove dups
+ suites = removeDups(suites.sort());
+ return suites;
+ });
+}
+exports.getSuites = getSuites;
+
+const makeFilters = function makeFilters(options) {
+ options = options || {};
+
+ // A filter string is {fileNameRegex}[:{testNameRegex}] - ie, a colon
+ // optionally separates a regex for the test fileName from a regex for the
+ // testName.
+ if (options.filter) {
+ let colonPos = options.filter.indexOf(':');
+ let filterFileRegex, filterNameRegex;
+
+ if (colonPos === -1) {
+ filterFileRegex = new RegExp(options.filter);
+ filterNameRegex = { test: () => true }
+ }
+ else {
+ filterFileRegex = new RegExp(options.filter.substr(0, colonPos));
+ filterNameRegex = new RegExp(options.filter.substr(colonPos + 1));
+ }
+
+ return {
+ fileFilter: (name) => filterFileRegex.test(name),
+ testFilter: (name) => filterNameRegex.test(name)
+ }
+ }
+
+ return {
+ fileFilter: () => true,
+ testFilter: () => true
+ };
+}
+exports.makeFilters = makeFilters;
+
+var loader = Loader(module);
+const NOT_TESTS = ['setup', 'teardown'];
+
+var TestFinder = exports.TestFinder = function TestFinder(options) {
+ this.filter = options.filter;
+ this.testInProcess = options.testInProcess === false ? false : true;
+ this.testOutOfProcess = options.testOutOfProcess === true ? true : false;
+};
+
+TestFinder.prototype = {
+ findTests: function findTests() {
+ let { fileFilter, testFilter } = makeFilters({ filter: this.filter });
+
+ return getSuites({ id: id, filter: fileFilter }).then(suites => {
+ let testsRemaining = [];
+
+ let getNextTest = () => {
+ if (testsRemaining.length) {
+ return testsRemaining.shift();
+ }
+
+ if (!suites.length) {
+ return null;
+ }
+
+ let suite = suites.shift();
+
+ // Load each test file as a main module in its own loader instance
+ // `suite` is defined by cuddlefish/manifest.py:ManifestBuilder.build
+ let suiteModule;
+
+ try {
+ suiteModule = cuddlefish.main(loader, suite);
+ }
+ catch (e) {
+ if (/Unsupported Application/i.test(e.message)) {
+ // If `Unsupported Application` error thrown during test,
+ // skip the test suite
+ suiteModule = {
+ 'test suite skipped': assert => assert.pass(e.message)
+ };
+ }
+ else {
+ console.exception(e);
+ throw e;
+ }
+ }
+
+ if (this.testInProcess) {
+ for (let name of Object.keys(suiteModule).sort()) {
+ if (NOT_TESTS.indexOf(name) === -1 && testFilter(name)) {
+ testsRemaining.push({
+ setup: suiteModule.setup,
+ teardown: suiteModule.teardown,
+ testFunction: suiteModule[name],
+ name: suite + "." + name
+ });
+ }
+ }
+ }
+
+ return getNextTest();
+ };
+
+ return {
+ getNext: () => resolve(getNextTest())
+ };
+ });
+ }
+};
diff --git a/components/jetpack/sdk/deprecated/unit-test.js b/components/jetpack/sdk/deprecated/unit-test.js
new file mode 100644
index 000000000..32bba8f6b
--- /dev/null
+++ b/components/jetpack/sdk/deprecated/unit-test.js
@@ -0,0 +1,584 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "deprecated"
+};
+
+const timer = require("../timers");
+const cfxArgs = require("../test/options");
+const { getTabs, closeTab, getURI, getTabId, getSelectedTab } = require("../tabs/utils");
+const { windows, isBrowser, getMostRecentBrowserWindow } = require("../window/utils");
+const { defer, all, Debugging: PromiseDebugging, resolve } = require("../core/promise");
+const { getInnerId } = require("../window/utils");
+const { cleanUI } = require("../test/utils");
+
+const findAndRunTests = function findAndRunTests(options) {
+ var TestFinder = require("./unit-test-finder").TestFinder;
+ var finder = new TestFinder({
+ filter: options.filter,
+ testInProcess: options.testInProcess,
+ testOutOfProcess: options.testOutOfProcess
+ });
+ var runner = new TestRunner({fs: options.fs});
+ finder.findTests().then(tests => {
+ runner.startMany({
+ tests: tests,
+ stopOnError: options.stopOnError,
+ onDone: options.onDone
+ });
+ });
+};
+exports.findAndRunTests = findAndRunTests;
+
+var runnerWindows = new WeakMap();
+var runnerTabs = new WeakMap();
+
+const TestRunner = function TestRunner(options) {
+ options = options || {};
+
+ // remember the id's for the open window and tab
+ let window = getMostRecentBrowserWindow();
+ runnerWindows.set(this, getInnerId(window));
+ runnerTabs.set(this, getTabId(getSelectedTab(window)));
+
+ this.fs = options.fs;
+ this.console = options.console || console;
+ this.passed = 0;
+ this.failed = 0;
+ this.testRunSummary = [];
+ this.expectFailNesting = 0;
+ this.done = TestRunner.prototype.done.bind(this);
+};
+
+TestRunner.prototype = {
+ toString: function toString() {
+ return "[object TestRunner]";
+ },
+
+ DEFAULT_PAUSE_TIMEOUT: (cfxArgs.parseable ? 300000 : 15000), //Five minutes (5*60*1000ms)
+ PAUSE_DELAY: 500,
+
+ _logTestFailed: function _logTestFailed(why) {
+ if (!(why in this.test.errors))
+ this.test.errors[why] = 0;
+ this.test.errors[why]++;
+ },
+
+ _uncaughtErrorObserver: function({message, date, fileName, stack, lineNumber}) {
+ this.fail("There was an uncaught Promise rejection: " + message + " @ " +
+ fileName + ":" + lineNumber + "\n" + stack);
+ },
+
+ pass: function pass(message) {
+ if(!this.expectFailure) {
+ if ("testMessage" in this.console)
+ this.console.testMessage(true, true, this.test.name, message);
+ else
+ this.console.info("pass:", message);
+ this.passed++;
+ this.test.passed++;
+ this.test.last = message;
+ }
+ else {
+ this.expectFailure = false;
+ this._logTestFailed("failure");
+ if ("testMessage" in this.console) {
+ this.console.testMessage(true, false, this.test.name, message);
+ }
+ else {
+ this.console.error("fail:", 'Failure Expected: ' + message)
+ this.console.trace();
+ }
+ this.failed++;
+ this.test.failed++;
+ }
+ },
+
+ fail: function fail(message) {
+ if(!this.expectFailure) {
+ this._logTestFailed("failure");
+ if ("testMessage" in this.console) {
+ this.console.testMessage(false, false, this.test.name, message);
+ }
+ else {
+ this.console.error("fail:", message)
+ this.console.trace();
+ }
+ this.failed++;
+ this.test.failed++;
+ }
+ else {
+ this.expectFailure = false;
+ if ("testMessage" in this.console)
+ this.console.testMessage(false, true, this.test.name, message);
+ else
+ this.console.info("pass:", message);
+ this.passed++;
+ this.test.passed++;
+ this.test.last = message;
+ }
+ },
+
+ expectFail: function(callback) {
+ this.expectFailure = true;
+ callback();
+ this.expectFailure = false;
+ },
+
+ exception: function exception(e) {
+ this._logTestFailed("exception");
+ if (cfxArgs.parseable)
+ this.console.print("TEST-UNEXPECTED-FAIL | " + this.test.name + " | " + e + "\n");
+ this.console.exception(e);
+ this.failed++;
+ this.test.failed++;
+ },
+
+ assertMatches: function assertMatches(string, regexp, message) {
+ if (regexp.test(string)) {
+ if (!message)
+ message = uneval(string) + " matches " + uneval(regexp);
+ this.pass(message);
+ } else {
+ var no = uneval(string) + " doesn't match " + uneval(regexp);
+ if (!message)
+ message = no;
+ else
+ message = message + " (" + no + ")";
+ this.fail(message);
+ }
+ },
+
+ assertRaises: function assertRaises(func, predicate, message) {
+ try {
+ func();
+ if (message)
+ this.fail(message + " (no exception thrown)");
+ else
+ this.fail("function failed to throw exception");
+ } catch (e) {
+ var errorMessage;
+ if (typeof(e) == "string")
+ errorMessage = e;
+ else
+ errorMessage = e.message;
+ if (typeof(predicate) == "string")
+ this.assertEqual(errorMessage, predicate, message);
+ else
+ this.assertMatches(errorMessage, predicate, message);
+ }
+ },
+
+ assert: function assert(a, message) {
+ if (!a) {
+ if (!message)
+ message = "assertion failed, value is " + a;
+ this.fail(message);
+ } else
+ this.pass(message || "assertion successful");
+ },
+
+ assertNotEqual: function assertNotEqual(a, b, message) {
+ if (a != b) {
+ if (!message)
+ message = "a != b != " + uneval(a);
+ this.pass(message);
+ } else {
+ var equality = uneval(a) + " == " + uneval(b);
+ if (!message)
+ message = equality;
+ else
+ message += " (" + equality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertEqual: function assertEqual(a, b, message) {
+ if (a == b) {
+ if (!message)
+ message = "a == b == " + uneval(a);
+ this.pass(message);
+ } else {
+ var inequality = uneval(a) + " != " + uneval(b);
+ if (!message)
+ message = inequality;
+ else
+ message += " (" + inequality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertNotStrictEqual: function assertNotStrictEqual(a, b, message) {
+ if (a !== b) {
+ if (!message)
+ message = "a !== b !== " + uneval(a);
+ this.pass(message);
+ } else {
+ var equality = uneval(a) + " === " + uneval(b);
+ if (!message)
+ message = equality;
+ else
+ message += " (" + equality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertStrictEqual: function assertStrictEqual(a, b, message) {
+ if (a === b) {
+ if (!message)
+ message = "a === b === " + uneval(a);
+ this.pass(message);
+ } else {
+ var inequality = uneval(a) + " !== " + uneval(b);
+ if (!message)
+ message = inequality;
+ else
+ message += " (" + inequality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertFunction: function assertFunction(a, message) {
+ this.assertStrictEqual('function', typeof a, message);
+ },
+
+ assertUndefined: function(a, message) {
+ this.assertStrictEqual('undefined', typeof a, message);
+ },
+
+ assertNotUndefined: function(a, message) {
+ this.assertNotStrictEqual('undefined', typeof a, message);
+ },
+
+ assertNull: function(a, message) {
+ this.assertStrictEqual(null, a, message);
+ },
+
+ assertNotNull: function(a, message) {
+ this.assertNotStrictEqual(null, a, message);
+ },
+
+ assertObject: function(a, message) {
+ this.assertStrictEqual('[object Object]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertString: function(a, message) {
+ this.assertStrictEqual('[object String]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertArray: function(a, message) {
+ this.assertStrictEqual('[object Array]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertNumber: function(a, message) {
+ this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message);
+ },
+
+ done: function done() {
+ if (this.isDone) {
+ return resolve();
+ }
+
+ this.isDone = true;
+ this.pass("This test is done.");
+
+ if (this.test.teardown) {
+ this.test.teardown(this);
+ }
+
+ if (this.waitTimeout !== null) {
+ timer.clearTimeout(this.waitTimeout);
+ this.waitTimeout = null;
+ }
+
+ // Do not leave any callback set when calling to `waitUntil`
+ this.waitUntilCallback = null;
+ if (this.test.passed == 0 && this.test.failed == 0) {
+ this._logTestFailed("empty test");
+
+ if ("testMessage" in this.console) {
+ this.console.testMessage(false, false, this.test.name, "Empty test");
+ }
+ else {
+ this.console.error("fail:", "Empty test")
+ }
+
+ this.failed++;
+ this.test.failed++;
+ }
+
+ let wins = windows(null, { includePrivate: true });
+ let winPromises = wins.map(win => {
+ return new Promise(resolve => {
+ if (["interactive", "complete"].indexOf(win.document.readyState) >= 0) {
+ resolve()
+ }
+ else {
+ win.addEventListener("DOMContentLoaded", function onLoad() {
+ win.removeEventListener("DOMContentLoaded", onLoad, false);
+ resolve();
+ }, false);
+ }
+ });
+ });
+
+ PromiseDebugging.flushUncaughtErrors();
+ PromiseDebugging.removeUncaughtErrorObserver(this._uncaughtErrorObserver);
+
+
+ return all(winPromises).then(() => {
+ let browserWins = wins.filter(isBrowser);
+ let tabs = browserWins.reduce((tabs, window) => tabs.concat(getTabs(window)), []);
+ let newTabID = getTabId(getSelectedTab(wins[0]));
+ let oldTabID = runnerTabs.get(this);
+ let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
+ let failure = false;
+
+ if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this)) {
+ failure = true;
+ this.fail("Should not be any unexpected windows open");
+ }
+ else if (hasMoreTabsOpen) {
+ failure = true;
+ this.fail("Should not be any unexpected tabs open");
+ }
+ else if (oldTabID != newTabID) {
+ failure = true;
+ runnerTabs.set(this, newTabID);
+ this.fail("Should not be any new tabs left open, old id: " + oldTabID + " new id: " + newTabID);
+ }
+
+ if (failure) {
+ console.log("Windows open:");
+ for (let win of wins) {
+ if (isBrowser(win)) {
+ tabs = getTabs(win);
+ console.log(win.location + " - " + tabs.map(getURI).join(", "));
+ }
+ else {
+ console.log(win.location);
+ }
+ }
+ }
+
+ return failure;
+ }).
+ then(failure => {
+ if (!failure) {
+ this.pass("There was a clean UI.");
+ return null;
+ }
+ return cleanUI().then(() => {
+ this.pass("There is a clean UI.");
+ });
+ }).
+ then(() => {
+ this.testRunSummary.push({
+ name: this.test.name,
+ passed: this.test.passed,
+ failed: this.test.failed,
+ errors: Object.keys(this.test.errors).join(", ")
+ });
+
+ if (this.onDone !== null) {
+ let onDone = this.onDone;
+ this.onDone = null;
+ timer.setTimeout(_ => onDone(this));
+ }
+ }).
+ catch(console.exception);
+ },
+
+ // Set of assertion functions to wait for an assertion to become true
+ // These functions take the same arguments as the TestRunner.assert* methods.
+ waitUntil: function waitUntil() {
+ return this._waitUntil(this.assert, arguments);
+ },
+
+ waitUntilNotEqual: function waitUntilNotEqual() {
+ return this._waitUntil(this.assertNotEqual, arguments);
+ },
+
+ waitUntilEqual: function waitUntilEqual() {
+ return this._waitUntil(this.assertEqual, arguments);
+ },
+
+ waitUntilMatches: function waitUntilMatches() {
+ return this._waitUntil(this.assertMatches, arguments);
+ },
+
+ /**
+ * Internal function that waits for an assertion to become true.
+ * @param {Function} assertionMethod
+ * Reference to a TestRunner assertion method like test.assert,
+ * test.assertEqual, ...
+ * @param {Array} args
+ * List of arguments to give to the previous assertion method.
+ * All functions in this list are going to be called to retrieve current
+ * assertion values.
+ */
+ _waitUntil: function waitUntil(assertionMethod, args) {
+ let { promise, resolve } = defer();
+ let count = 0;
+ let maxCount = this.DEFAULT_PAUSE_TIMEOUT / this.PAUSE_DELAY;
+
+ // We need to ensure that test is asynchronous
+ if (!this.waitTimeout)
+ this.waitUntilDone(this.DEFAULT_PAUSE_TIMEOUT);
+
+ let finished = false;
+ let test = this;
+
+ // capture a traceback before we go async.
+ let traceback = require("../console/traceback");
+ let stack = traceback.get();
+ stack.splice(-2, 2);
+ let currentWaitStack = traceback.format(stack);
+ let timeout = null;
+
+ function loop(stopIt) {
+ timeout = null;
+
+ // Build a mockup object to fake TestRunner API and intercept calls to
+ // pass and fail methods, in order to retrieve nice error messages
+ // and assertion result
+ let mock = {
+ pass: function (msg) {
+ test.pass(msg);
+ test.waitUntilCallback = null;
+ if (!stopIt)
+ resolve();
+ },
+ fail: function (msg) {
+ // If we are called on test timeout, we stop the loop
+ // and print which test keeps failing:
+ if (stopIt) {
+ test.console.error("test assertion never became true:\n",
+ msg + "\n",
+ currentWaitStack);
+ if (timeout)
+ timer.clearTimeout(timeout);
+ return;
+ }
+ timeout = timer.setTimeout(loop, test.PAUSE_DELAY);
+ }
+ };
+
+ // Automatically call args closures in order to build arguments for
+ // assertion function
+ let appliedArgs = [];
+ for (let i = 0, l = args.length; i < l; i++) {
+ let a = args[i];
+ if (typeof a == "function") {
+ try {
+ a = a();
+ }
+ catch(e) {
+ test.fail("Exception when calling asynchronous assertion: " + e +
+ "\n" + e.stack);
+ return resolve();
+ }
+ }
+ appliedArgs.push(a);
+ }
+
+ // Finally call assertion function with current assertion values
+ assertionMethod.apply(mock, appliedArgs);
+ }
+ loop();
+ this.waitUntilCallback = loop;
+
+ return promise;
+ },
+
+ waitUntilDone: function waitUntilDone(ms) {
+ if (ms === undefined)
+ ms = this.DEFAULT_PAUSE_TIMEOUT;
+
+ var self = this;
+
+ function tiredOfWaiting() {
+ self._logTestFailed("timed out");
+ if ("testMessage" in self.console) {
+ self.console.testMessage(false, false, self.test.name,
+ `Test timed out (after: ${self.test.last})`);
+ }
+ else {
+ self.console.error("fail:", `Timed out (after: ${self.test.last})`)
+ }
+ if (self.waitUntilCallback) {
+ self.waitUntilCallback(true);
+ self.waitUntilCallback = null;
+ }
+ self.failed++;
+ self.test.failed++;
+ self.done();
+ }
+
+ // We may already have registered a timeout callback
+ if (this.waitTimeout)
+ timer.clearTimeout(this.waitTimeout);
+
+ this.waitTimeout = timer.setTimeout(tiredOfWaiting, ms);
+ },
+
+ startMany: function startMany(options) {
+ function runNextTest(self) {
+ let { tests, onDone } = options;
+
+ return tests.getNext().then((test) => {
+ if (options.stopOnError && self.test && self.test.failed) {
+ self.console.error("aborted: test failed and --stop-on-error was specified");
+ onDone(self);
+ }
+ else if (test) {
+ self.start({test: test, onDone: runNextTest});
+ }
+ else {
+ onDone(self);
+ }
+ });
+ }
+
+ return runNextTest(this).catch(console.exception);
+ },
+
+ start: function start(options) {
+ this.test = options.test;
+ this.test.passed = 0;
+ this.test.failed = 0;
+ this.test.errors = {};
+ this.test.last = 'START';
+ PromiseDebugging.clearUncaughtErrorObservers();
+ this._uncaughtErrorObserver = this._uncaughtErrorObserver.bind(this);
+ PromiseDebugging.addUncaughtErrorObserver(this._uncaughtErrorObserver);
+
+ this.isDone = false;
+ this.onDone = function(self) {
+ if (cfxArgs.parseable)
+ self.console.print("TEST-END | " + self.test.name + "\n");
+ options.onDone(self);
+ }
+ this.waitTimeout = null;
+
+ try {
+ if (cfxArgs.parseable)
+ this.console.print("TEST-START | " + this.test.name + "\n");
+ else
+ this.console.info("executing '" + this.test.name + "'");
+
+ if(this.test.setup) {
+ this.test.setup(this);
+ }
+ this.test.testFunction(this);
+ } catch (e) {
+ this.exception(e);
+ }
+ if (this.waitTimeout === null)
+ this.done();
+ }
+};
+exports.TestRunner = TestRunner;
diff --git a/components/jetpack/sdk/deprecated/window-utils.js b/components/jetpack/sdk/deprecated/window-utils.js
new file mode 100644
index 000000000..93c0ab7b8
--- /dev/null
+++ b/components/jetpack/sdk/deprecated/window-utils.js
@@ -0,0 +1,193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'deprecated'
+};
+
+const { Cc, Ci } = require('chrome');
+const events = require('../system/events');
+const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser,
+ getMostRecentBrowserWindow, getToplevelWindow, getMostRecentWindow } = require('../window/utils');
+const { deprecateFunction } = require('../util/deprecate');
+const { ignoreWindow } = require('sdk/private-browsing/utils');
+const { isPrivateBrowsingSupported } = require('../self');
+
+const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
+ getService(Ci.nsIWindowWatcher);
+const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+
+// Bug 834961: ignore private windows when they are not supported
+function getWindows() {
+ return windows(null, { includePrivate: isPrivateBrowsingSupported });
+}
+
+/**
+ * An iterator for XUL windows currently in the application.
+ *
+ * @return A generator that yields XUL windows exposing the
+ * nsIDOMWindow interface.
+ */
+function windowIterator() {
+ // Bug 752631: We only pass already loaded window in order to avoid
+ // breaking XUL windows DOM. DOM is broken when some JS code try
+ // to access DOM during "uninitialized" state of the related document.
+ let list = getWindows().filter(isDocumentLoaded);
+ for (let i = 0, l = list.length; i < l; i++) {
+ yield list[i];
+ }
+};
+exports.windowIterator = windowIterator;
+
+/**
+ * An iterator for browser windows currently open in the application.
+ * @returns {Function}
+ * A generator that yields browser windows exposing the `nsIDOMWindow`
+ * interface.
+ */
+function browserWindowIterator() {
+ for (let window of windowIterator()) {
+ if (isBrowser(window))
+ yield window;
+ }
+}
+exports.browserWindowIterator = browserWindowIterator;
+
+function WindowTracker(delegate) {
+ if (!(this instanceof WindowTracker)) {
+ return new WindowTracker(delegate);
+ }
+
+ this._delegate = delegate;
+
+ for (let window of getWindows())
+ this._regWindow(window);
+ windowWatcher.registerNotification(this);
+ this._onToplevelWindowReady = this._onToplevelWindowReady.bind(this);
+ events.on('toplevel-window-ready', this._onToplevelWindowReady);
+
+ require('../system/unload').ensure(this);
+
+ return this;
+};
+
+WindowTracker.prototype = {
+ _regLoadingWindow: function _regLoadingWindow(window) {
+ // Bug 834961: ignore private windows when they are not supported
+ if (ignoreWindow(window))
+ return;
+
+ window.addEventListener('load', this, true);
+ },
+
+ _unregLoadingWindow: function _unregLoadingWindow(window) {
+ // This may have no effect if we ignored the window in _regLoadingWindow().
+ window.removeEventListener('load', this, true);
+ },
+
+ _regWindow: function _regWindow(window) {
+ // Bug 834961: ignore private windows when they are not supported
+ if (ignoreWindow(window))
+ return;
+
+ if (window.document.readyState == 'complete') {
+ this._unregLoadingWindow(window);
+ this._delegate.onTrack(window);
+ } else
+ this._regLoadingWindow(window);
+ },
+
+ _unregWindow: function _unregWindow(window) {
+ if (window.document.readyState == 'complete') {
+ if (this._delegate.onUntrack)
+ this._delegate.onUntrack(window);
+ } else {
+ this._unregLoadingWindow(window);
+ }
+ },
+
+ unload: function unload() {
+ windowWatcher.unregisterNotification(this);
+ events.off('toplevel-window-ready', this._onToplevelWindowReady);
+ for (let window of getWindows())
+ this._unregWindow(window);
+ },
+
+ handleEvent: function handleEvent(event) {
+ try {
+ if (event.type == 'load' && event.target) {
+ var window = event.target.defaultView;
+ if (window)
+ this._regWindow(getToplevelWindow(window));
+ }
+ }
+ catch(e) {
+ console.exception(e);
+ }
+ },
+
+ _onToplevelWindowReady: function _onToplevelWindowReady({subject}) {
+ let window = getToplevelWindow(subject);
+ // ignore private windows if they are not supported
+ if (ignoreWindow(window))
+ return;
+ this._regWindow(window);
+ },
+
+ observe: function observe(subject, topic, data) {
+ try {
+ var window = subject.QueryInterface(Ci.nsIDOMWindow);
+ // ignore private windows if they are not supported
+ if (ignoreWindow(window))
+ return;
+ if (topic == 'domwindowclosed')
+ this._unregWindow(window);
+ }
+ catch(e) {
+ console.exception(e);
+ }
+ }
+};
+exports.WindowTracker = WindowTracker;
+
+Object.defineProperties(exports, {
+ activeWindow: {
+ enumerable: true,
+ get: function() {
+ return getMostRecentWindow(null);
+ },
+ set: function(window) {
+ try {
+ window.focus();
+ } catch (e) {}
+ }
+ },
+ activeBrowserWindow: {
+ enumerable: true,
+ get: getMostRecentBrowserWindow
+ }
+});
+
+
+/**
+ * Returns the ID of the window's current inner window.
+ */
+exports.getInnerId = deprecateFunction(getInnerId,
+ 'require("window-utils").getInnerId is deprecated, ' +
+ 'please use require("sdk/window/utils").getInnerId instead'
+);
+
+exports.getOuterId = deprecateFunction(getOuterId,
+ 'require("window-utils").getOuterId is deprecated, ' +
+ 'please use require("sdk/window/utils").getOuterId instead'
+);
+
+exports.isBrowser = deprecateFunction(isBrowser,
+ 'require("window-utils").isBrowser is deprecated, ' +
+ 'please use require("sdk/window/utils").isBrowser instead'
+);
+
+exports.hiddenWindow = appShellService.hiddenDOMWindow;
diff --git a/components/jetpack/sdk/dom/events-shimmed.js b/components/jetpack/sdk/dom/events-shimmed.js
new file mode 100644
index 000000000..7a1727681
--- /dev/null
+++ b/components/jetpack/sdk/dom/events-shimmed.js
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const events = require('./events.js');
+
+exports.emit = (element, type, obj) => events.emit(element, type, obj, true);
+exports.on = (element, type, listener, capture) => events.on(element, type, listener, capture, true);
+exports.once = (element, type, listener, capture) => events.once(element, type, listener, capture, true);
+exports.removeListener = (element, type, listener, capture) => events.removeListener(element, type, listener, capture, true);
+exports.removed = events.removed;
+exports.when = (element, eventName, capture) => events.when(element, eventName, capture ? capture : false, true);
diff --git a/components/jetpack/sdk/dom/events.js b/components/jetpack/sdk/dom/events.js
new file mode 100644
index 000000000..502d2350f
--- /dev/null
+++ b/components/jetpack/sdk/dom/events.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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cu } = require("chrome");
+const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
+
+// Utility function that returns copy of the given `text` with last character
+// removed if it is `"s"`.
+function singularify(text) {
+ return text[text.length - 1] === "s" ? text.substr(0, text.length - 1) : text;
+}
+
+// Utility function that takes event type, argument is passed to
+// `document.createEvent` and returns name of the initializer method of the
+// given event. Please note that there are some event types whose initializer
+// methods can't be guessed by this function. For more details see following
+// link: https://developer.mozilla.org/En/DOM/Document.createEvent
+function getInitializerName(category) {
+ return "init" + singularify(category);
+}
+
+/**
+ * Registers an event `listener` on a given `element`, that will be called
+ * when events of specified `type` is dispatched on the `element`.
+ * @param {Element} element
+ * Dom element to register listener on.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type) to
+ * listen for.
+ * @param {Function} listener
+ * Function that is called whenever an event of the specified `type`
+ * occurs.
+ * @param {Boolean} capture
+ * If true, indicates that the user wishes to initiate capture. After
+ * initiating capture, all events of the specified type will be dispatched
+ * to the registered listener before being dispatched to any `EventTarget`s
+ * beneath it in the DOM tree. Events which are bubbling upward through
+ * the tree will not trigger a listener designated to use capture.
+ * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
+ * for a detailed explanation.
+ */
+function on(element, type, listener, capture, shimmed = false) {
+ // `capture` defaults to `false`.
+ capture = capture || false;
+ if (shimmed) {
+ element.addEventListener(type, listener, capture);
+ } else {
+ ShimWaiver.getProperty(element, "addEventListener")(type, listener, capture);
+ }
+}
+exports.on = on;
+
+/**
+ * Registers an event `listener` on a given `element`, that will be called
+ * only once, next time event of specified `type` is dispatched on the
+ * `element`.
+ * @param {Element} element
+ * Dom element to register listener on.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type) to
+ * listen for.
+ * @param {Function} listener
+ * Function that is called whenever an event of the specified `type`
+ * occurs.
+ * @param {Boolean} capture
+ * If true, indicates that the user wishes to initiate capture. After
+ * initiating capture, all events of the specified type will be dispatched
+ * to the registered listener before being dispatched to any `EventTarget`s
+ * beneath it in the DOM tree. Events which are bubbling upward through
+ * the tree will not trigger a listener designated to use capture.
+ * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
+ * for a detailed explanation.
+ */
+function once(element, type, listener, capture, shimmed = false) {
+ on(element, type, function selfRemovableListener(event) {
+ removeListener(element, type, selfRemovableListener, capture, shimmed);
+ listener.apply(this, arguments);
+ }, capture, shimmed);
+}
+exports.once = once;
+
+/**
+ * Unregisters an event `listener` on a given `element` for the events of the
+ * specified `type`.
+ *
+ * @param {Element} element
+ * Dom element to unregister listener from.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type) to
+ * listen for.
+ * @param {Function} listener
+ * Function that is called whenever an event of the specified `type`
+ * occurs.
+ * @param {Boolean} capture
+ * If true, indicates that the user wishes to initiate capture. After
+ * initiating capture, all events of the specified type will be dispatched
+ * to the registered listener before being dispatched to any `EventTarget`s
+ * beneath it in the DOM tree. Events which are bubbling upward through
+ * the tree will not trigger a listener designated to use capture.
+ * See [DOM Level 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/#event-flow)
+ * for a detailed explanation.
+ */
+function removeListener(element, type, listener, capture, shimmed = false) {
+ if (shimmed) {
+ element.removeEventListener(type, listener, capture);
+ } else {
+ ShimWaiver.getProperty(element, "removeEventListener")(type, listener, capture);
+ }
+}
+exports.removeListener = removeListener;
+
+/**
+ * Emits event of the specified `type` and `category` on the given `element`.
+ * Specified `settings` are used to initialize event before dispatching it.
+ * @param {Element} element
+ * Dom element to dispatch event on.
+ * @param {String} type
+ * A string representing the
+ * [event type](https://developer.mozilla.org/en/DOM/event.type).
+ * @param {Object} options
+ * Options object containing following properties:
+ * - `category`: String passed to the `document.createEvent`. Option is
+ * optional and defaults to "UIEvents".
+ * - `initializer`: If passed it will be used as name of the method used
+ * to initialize event. If omitted name will be generated from the
+ * `category` field by prefixing it with `"init"` and removing last
+ * character if it matches `"s"`.
+ * - `settings`: Array of settings that are forwarded to the event
+ * initializer after firs `type` argument.
+ * @see https://developer.mozilla.org/En/DOM/Document.createEvent
+ */
+function emit(element, type, { category, initializer, settings }, shimmed = false) {
+ category = category || "UIEvents";
+ initializer = initializer || getInitializerName(category);
+ let document = element.ownerDocument;
+ let event = document.createEvent(category);
+ event[initializer].apply(event, [type].concat(settings));
+ if (shimmed) {
+ element.dispatchEvent(event);
+ } else {
+ ShimWaiver.getProperty(element, "dispatchEvent")(event);
+ }
+};
+exports.emit = emit;
+
+// Takes DOM `element` and returns promise which is resolved
+// when given element is removed from it's parent node.
+const removed = element => {
+ return new Promise(resolve => {
+ const { MutationObserver } = element.ownerDocument.defaultView;
+ const observer = new MutationObserver(mutations => {
+ for (let mutation of mutations) {
+ for (let node of mutation.removedNodes || []) {
+ if (node === element) {
+ observer.disconnect();
+ resolve(element);
+ }
+ }
+ }
+ });
+ observer.observe(element.parentNode, {childList: true});
+ });
+};
+exports.removed = removed;
+
+const when = (element, eventName, capture=false, shimmed=false) => new Promise(resolve => {
+ const listener = event => {
+ if (shimmed) {
+ element.removeEventListener(eventName, listener, capture);
+ } else {
+ ShimWaiver.getProperty(element, "removeEventListener")(eventName, listener, capture);
+ }
+ resolve(event);
+ };
+
+ if (shimmed) {
+ element.addEventListener(eventName, listener, capture);
+ } else {
+ ShimWaiver.getProperty(element, "addEventListener")(eventName, listener, capture);
+ }
+});
+exports.when = when;
diff --git a/components/jetpack/sdk/dom/events/keys.js b/components/jetpack/sdk/dom/events/keys.js
new file mode 100644
index 000000000..e6f1483a2
--- /dev/null
+++ b/components/jetpack/sdk/dom/events/keys.js
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { emit } = require("../events");
+const { getCodeForKey, toJSON } = require("../../keyboard/utils");
+const { has } = require("../../util/array");
+const { isString } = require("../../lang/type");
+
+const INITIALIZER = "initKeyEvent";
+const CATEGORY = "KeyboardEvent";
+
+function Options(options) {
+ if (!isString(options))
+ return options;
+
+ var { key, modifiers } = toJSON(options);
+ return {
+ key: key,
+ control: has(modifiers, "control"),
+ alt: has(modifiers, "alt"),
+ shift: has(modifiers, "shift"),
+ meta: has(modifiers, "meta")
+ };
+}
+
+var keyEvent = exports.keyEvent = function keyEvent(element, type, options) {
+
+ emit(element, type, {
+ initializer: INITIALIZER,
+ category: CATEGORY,
+ settings: [
+ !("bubbles" in options) || options.bubbles !== false,
+ !("cancelable" in options) || options.cancelable !== false,
+ "window" in options && options.window ? options.window : null,
+ "control" in options && !!options.control,
+ "alt" in options && !!options.alt,
+ "shift" in options && !!options.shift,
+ "meta" in options && !!options.meta,
+ getCodeForKey(options.key) || 0,
+ options.key.length === 1 ? options.key.charCodeAt(0) : 0
+ ]
+ });
+}
+
+exports.keyDown = function keyDown(element, options) {
+ keyEvent(element, "keydown", Options(options));
+};
+
+exports.keyUp = function keyUp(element, options) {
+ keyEvent(element, "keyup", Options(options));
+};
+
+exports.keyPress = function keyPress(element, options) {
+ keyEvent(element, "keypress", Options(options));
+};
+
diff --git a/components/jetpack/sdk/event/chrome.js b/components/jetpack/sdk/event/chrome.js
new file mode 100644
index 000000000..9044fef99
--- /dev/null
+++ b/components/jetpack/sdk/event/chrome.js
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, Cr, Cu } = require("chrome");
+const { emit, on, off } = require("./core");
+var observerService = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+
+const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
+const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
+const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
+
+const { when: unload } = require("../system/unload");
+
+// Simple class that can be used to instantiate event channel that
+// implements `nsIObserver` interface. It's will is used by `observe`
+// function as observer + event target. It basically proxies observer
+// notifications as to it's registered listeners.
+function ObserverChannel() {}
+Object.freeze(Object.defineProperties(ObserverChannel.prototype, {
+ QueryInterface: {
+ value: function(iid) {
+ if (!iid.equals(Ci.nsIObserver) &&
+ !iid.equals(Ci.nsISupportsWeakReference) &&
+ !iid.equals(Ci.nsISupports))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+ },
+ observe: {
+ value: function(subject, topic, data) {
+ emit(this, "data", {
+ type: topic,
+ target: subject,
+ data: data
+ });
+ }
+ }
+}));
+
+function observe(topic) {
+ let observerChannel = new ObserverChannel();
+
+ // Note: `nsIObserverService` will not hold a weak reference to a
+ // observerChannel (since third argument is `true`). There for if it
+ // will be GC-ed with all it's event listeners once no other references
+ // will be held.
+ addObserver(observerChannel, topic, true);
+
+ // We need to remove any observer added once the add-on is unloaded;
+ // otherwise we'll get a "dead object" exception.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833
+ unload(() => removeObserver(observerChannel, topic));
+
+ return observerChannel;
+}
+
+exports.observe = observe;
diff --git a/components/jetpack/sdk/event/core.js b/components/jetpack/sdk/event/core.js
new file mode 100644
index 000000000..c16dd2df5
--- /dev/null
+++ b/components/jetpack/sdk/event/core.js
@@ -0,0 +1,193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const UNCAUGHT_ERROR = 'An error event was emitted for which there was no listener.';
+const BAD_LISTENER = 'The event listener must be a function.';
+
+const { ns } = require('../core/namespace');
+
+const event = ns();
+
+const EVENT_TYPE_PATTERN = /^on([A-Z]\w+$)/;
+exports.EVENT_TYPE_PATTERN = EVENT_TYPE_PATTERN;
+
+// Utility function to access given event `target` object's event listeners for
+// the specific event `type`. If listeners for this type does not exists they
+// will be created.
+const observers = function observers(target, type) {
+ if (!target) throw TypeError("Event target must be an object");
+ let listeners = event(target);
+ return type in listeners ? listeners[type] : listeners[type] = [];
+};
+
+/**
+ * Registers an event `listener` that is called every time events of
+ * specified `type` is emitted on the given event `target`.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+function on(target, type, listener) {
+ if (typeof(listener) !== 'function')
+ throw new Error(BAD_LISTENER);
+
+ let listeners = observers(target, type);
+ if (!~listeners.indexOf(listener))
+ listeners.push(listener);
+}
+exports.on = on;
+
+
+var onceWeakMap = new WeakMap();
+
+
+/**
+ * Registers an event `listener` that is called only the next time an event
+ * of the specified `type` is emitted on the given event `target`.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of the event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+function once(target, type, listener) {
+ let replacement = function observer(...args) {
+ off(target, type, observer);
+ onceWeakMap.delete(listener);
+ listener.apply(target, args);
+ };
+ onceWeakMap.set(listener, replacement);
+ on(target, type, replacement);
+}
+exports.once = once;
+
+/**
+ * Execute each of the listeners in order with the supplied arguments.
+ * All the exceptions that are thrown by listeners during the emit
+ * are caught and can be handled by listeners of 'error' event. Thrown
+ * exceptions are passed as an argument to an 'error' event listener.
+ * If no 'error' listener is registered exception will be logged into an
+ * error console.
+ * @param {Object} target
+ * Event target object.
+ * @param {String} type
+ * The type of event.
+ * @params {Object|Number|String|Boolean} args
+ * Arguments that will be passed to listeners.
+ */
+function emit (target, type, ...args) {
+ emitOnObject(target, type, target, ...args);
+}
+exports.emit = emit;
+
+/**
+ * A variant of emit that allows setting the this property for event listeners
+ */
+function emitOnObject(target, type, thisArg, ...args) {
+ let all = observers(target, '*').length;
+ let state = observers(target, type);
+ let listeners = state.slice();
+ let count = listeners.length;
+ let index = 0;
+
+ // If error event and there are no handlers (explicit or catch-all)
+ // then print error message to the console.
+ if (count === 0 && type === 'error' && all === 0)
+ console.exception(args[0]);
+ while (index < count) {
+ try {
+ let listener = listeners[index];
+ // Dispatch only if listener is still registered.
+ if (~state.indexOf(listener))
+ listener.apply(thisArg, args);
+ }
+ catch (error) {
+ // If exception is not thrown by a error listener and error listener is
+ // registered emit `error` event. Otherwise dump exception to the console.
+ if (type !== 'error') emit(target, 'error', error);
+ else console.exception(error);
+ }
+ index++;
+ }
+ // Also emit on `"*"` so that one could listen for all events.
+ if (type !== '*') emit(target, '*', type, ...args);
+}
+exports.emitOnObject = emitOnObject;
+
+/**
+ * Removes an event `listener` for the given event `type` on the given event
+ * `target`. If no `listener` is passed removes all listeners of the given
+ * `type`. If `type` is not passed removes all the listeners of the given
+ * event `target`.
+ * @param {Object} target
+ * The event target object.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+function off(target, type, listener) {
+ let length = arguments.length;
+ if (length === 3) {
+ if (onceWeakMap.has(listener)) {
+ listener = onceWeakMap.get(listener);
+ onceWeakMap.delete(listener);
+ }
+
+ let listeners = observers(target, type);
+ let index = listeners.indexOf(listener);
+ if (~index)
+ listeners.splice(index, 1);
+ }
+ else if (length === 2) {
+ observers(target, type).splice(0);
+ }
+ else if (length === 1) {
+ let listeners = event(target);
+ Object.keys(listeners).forEach(type => delete listeners[type]);
+ }
+}
+exports.off = off;
+
+/**
+ * Returns a number of event listeners registered for the given event `type`
+ * on the given event `target`.
+ */
+function count(target, type) {
+ return observers(target, type).length;
+}
+exports.count = count;
+
+/**
+ * Registers listeners on the given event `target` from the given `listeners`
+ * dictionary. Iterates over the listeners and if property name matches name
+ * pattern `onEventType` and property is a function, then registers it as
+ * an `eventType` listener on `target`.
+ *
+ * @param {Object} target
+ * The type of event.
+ * @param {Object} listeners
+ * Dictionary of listeners.
+ */
+function setListeners(target, listeners) {
+ Object.keys(listeners || {}).forEach(key => {
+ let match = EVENT_TYPE_PATTERN.exec(key);
+ let type = match && match[1].toLowerCase();
+ if (!type) return;
+
+ let listener = listeners[key];
+ if (typeof(listener) === 'function')
+ on(target, type, listener);
+ });
+}
+exports.setListeners = setListeners;
diff --git a/components/jetpack/sdk/event/dom.js b/components/jetpack/sdk/event/dom.js
new file mode 100644
index 000000000..da99dec7a
--- /dev/null
+++ b/components/jetpack/sdk/event/dom.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Ci } = require("chrome");
+
+var { emit } = require("./core");
+var { when: unload } = require("../system/unload");
+var listeners = new WeakMap();
+
+const { Cu } = require("chrome");
+const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
+const { ThreadSafeChromeUtils } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+var getWindowFrom = x =>
+ x instanceof Ci.nsIDOMWindow ? x :
+ x instanceof Ci.nsIDOMDocument ? x.defaultView :
+ x instanceof Ci.nsIDOMNode ? x.ownerDocument.defaultView :
+ null;
+
+function removeFromListeners() {
+ ShimWaiver.getProperty(this, "removeEventListener")("DOMWindowClose", removeFromListeners);
+ for (let cleaner of listeners.get(this))
+ cleaner();
+
+ listeners.delete(this);
+}
+
+// Simple utility function takes event target, event type and optional
+// `options.capture` and returns node style event stream that emits "data"
+// events every time event of that type occurs on the given `target`.
+function open(target, type, options) {
+ let output = {};
+ let capture = options && options.capture ? true : false;
+ let listener = (event) => emit(output, "data", event);
+
+ // `open` is currently used only on DOM Window objects, however it was made
+ // to be used to any kind of `target` that supports `addEventListener`,
+ // therefore is safer get the `window` from the `target` instead assuming
+ // that `target` is the `window`.
+ let window = getWindowFrom(target);
+
+ // If we're not able to get a `window` from `target`, there is something
+ // wrong. We cannot add listeners that can leak later, or results in
+ // "dead object" exception.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1001833
+ if (!window)
+ throw new Error("Unable to obtain the owner window from the target given.");
+
+ let cleaners = listeners.get(window);
+ if (!cleaners) {
+ cleaners = [];
+ listeners.set(window, cleaners);
+
+ // We need to remove from our map the `window` once is closed, to prevent
+ // memory leak
+ ShimWaiver.getProperty(window, "addEventListener")("DOMWindowClose", removeFromListeners);
+ }
+
+ cleaners.push(() => ShimWaiver.getProperty(target, "removeEventListener")(type, listener, capture));
+ ShimWaiver.getProperty(target, "addEventListener")(type, listener, capture);
+
+ return output;
+}
+
+unload(() => {
+ let keys = ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(listeners)
+ for (let window of keys)
+ removeFromListeners.call(window);
+});
+
+exports.open = open;
diff --git a/components/jetpack/sdk/event/target.js b/components/jetpack/sdk/event/target.js
new file mode 100644
index 000000000..3a1f5e5f0
--- /dev/null
+++ b/components/jetpack/sdk/event/target.js
@@ -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/. */
+'use strict';
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { on, once, off, setListeners } = require('./core');
+const { method, chainable } = require('../lang/functional/core');
+const { Class } = require('../core/heritage');
+
+/**
+ * `EventTarget` is an exemplar for creating an objects that can be used to
+ * add / remove event listeners on them. Events on these objects may be emitted
+ * via `emit` function exported by 'event/core' module.
+ */
+const EventTarget = Class({
+ /**
+ * Method initializes `this` event source. It goes through properties of a
+ * given `options` and registers listeners for the ones that look like an
+ * event listeners.
+ */
+ /**
+ * Method initializes `this` event source. It goes through properties of a
+ * given `options` and registers listeners for the ones that look like an
+ * event listeners.
+ */
+ initialize: function initialize(options) {
+ setListeners(this, options);
+ },
+ /**
+ * Registers an event `listener` that is called every time events of
+ * specified `type` are emitted.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ * @example
+ * worker.on('message', function (data) {
+ * console.log('data received: ' + data)
+ * })
+ */
+ on: chainable(method(on)),
+ /**
+ * Registers an event `listener` that is called once the next time an event
+ * of the specified `type` is emitted.
+ * @param {String} type
+ * The type of the event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ once: chainable(method(once)),
+ /**
+ * Removes an event `listener` for the given event `type`.
+ * @param {String} type
+ * The type of event.
+ * @param {Function} listener
+ * The listener function that processes the event.
+ */
+ removeListener: function removeListener(type, listener) {
+ // Note: We can't just wrap `off` in `method` as we do it for other methods
+ // cause skipping a second or third argument will behave very differently
+ // than intended. This way we make sure all arguments are passed and only
+ // one listener is removed at most.
+ off(this, type, listener);
+ return this;
+ },
+ // but we can wrap `off` here, as the semantics are the same
+ off: chainable(method(off))
+
+});
+exports.EventTarget = EventTarget;
diff --git a/components/jetpack/sdk/event/utils.js b/components/jetpack/sdk/event/utils.js
new file mode 100644
index 000000000..f193b6785
--- /dev/null
+++ b/components/jetpack/sdk/event/utils.js
@@ -0,0 +1,328 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+var { emit, on, once, off, EVENT_TYPE_PATTERN } = require("./core");
+const { Cu } = require("chrome");
+
+// This module provides set of high order function for working with event
+// streams (streams in a NodeJS style that dispatch data, end and error
+// events).
+
+// Function takes a `target` object and returns set of implicit references
+// (non property references) it keeps. This basically allows defining
+// references between objects without storing the explicitly. See transform for
+// more details.
+var refs = (function() {
+ let refSets = new WeakMap();
+ return function refs(target) {
+ if (!refSets.has(target)) refSets.set(target, new Set());
+ return refSets.get(target);
+ };
+})();
+
+function transform(input, f) {
+ let output = new Output();
+
+ // Since event listeners don't prevent `input` to be GC-ed we wanna presrve
+ // it until `output` can be GC-ed. There for we add implicit reference which
+ // is removed once `input` ends.
+ refs(output).add(input);
+
+ const next = data => receive(output, data);
+ once(output, "start", () => start(input));
+ on(input, "error", error => emit(output, "error", error));
+ on(input, "end", function() {
+ refs(output).delete(input);
+ end(output);
+ });
+ on(input, "data", data => f(data, next));
+ return output;
+}
+
+// High order event transformation function that takes `input` event channel
+// and returns transformation containing only events on which `p` predicate
+// returns `true`.
+function filter(input, predicate) {
+ return transform(input, function(data, next) {
+ if (predicate(data))
+ next(data);
+ });
+}
+exports.filter = filter;
+
+// High order function that takes `input` and returns input of it's values
+// mapped via given `f` function.
+const map = (input, f) => transform(input, (data, next) => next(f(data)));
+exports.map = map;
+
+// High order function that takes `input` stream of streams and merges them
+// into single event stream. Like flatten but time based rather than order
+// based.
+function merge(inputs) {
+ let output = new Output();
+ let open = 1;
+ let state = [];
+ output.state = state;
+ refs(output).add(inputs);
+
+ function end(input) {
+ open = open - 1;
+ refs(output).delete(input);
+ if (open === 0) emit(output, "end");
+ }
+ const error = e => emit(output, "error", e);
+ function forward(input) {
+ state.push(input);
+ open = open + 1;
+ on(input, "end", () => end(input));
+ on(input, "error", error);
+ on(input, "data", data => emit(output, "data", data));
+ }
+
+ // If `inputs` is an array treat it as a stream.
+ if (Array.isArray(inputs)) {
+ inputs.forEach(forward);
+ end(inputs);
+ }
+ else {
+ on(inputs, "end", () => end(inputs));
+ on(inputs, "error", error);
+ on(inputs, "data", forward);
+ }
+
+ return output;
+}
+exports.merge = merge;
+
+const expand = (inputs, f) => merge(map(inputs, f));
+exports.expand = expand;
+
+const pipe = (from, to) => on(from, "*", emit.bind(emit, to));
+exports.pipe = pipe;
+
+
+// Shim signal APIs so other modules can be used as is.
+const receive = (input, message) => {
+ if (input[receive])
+ input[receive](input, message);
+ else
+ emit(input, "data", message);
+
+ // Ideally our input will extend Input and already provide a weak value
+ // getter. If not, opportunistically shim the weak value getter on
+ // other types passed as the input.
+ if (!("value" in input)) {
+ Object.defineProperty(input, "value", WeakValueGetterSetter);
+ }
+ input.value = message;
+};
+receive.toString = () => "@@receive";
+exports.receive = receive;
+exports.send = receive;
+
+const end = input => {
+ if (input[end])
+ input[end](input);
+ else
+ emit(input, "end", input);
+};
+end.toString = () => "@@end";
+exports.end = end;
+
+const stop = input => {
+ if (input[stop])
+ input[stop](input);
+ else
+ emit(input, "stop", input);
+};
+stop.toString = () => "@@stop";
+exports.stop = stop;
+
+const start = input => {
+ if (input[start])
+ input[start](input);
+ else
+ emit(input, "start", input);
+};
+start.toString = () => "@@start";
+exports.start = start;
+
+const lift = (step, ...inputs) => {
+ let args = null;
+ let opened = inputs.length;
+ let started = false;
+ const output = new Output();
+ const init = () => {
+ args = [...inputs.map(input => input.value)];
+ output.value = step(...args);
+ };
+
+ inputs.forEach((input, index) => {
+ on(input, "data", data => {
+ args[index] = data;
+ receive(output, step(...args));
+ });
+ on(input, "end", () => {
+ opened = opened - 1;
+ if (opened <= 0)
+ end(output);
+ });
+ });
+
+ once(output, "start", () => {
+ inputs.forEach(start);
+ init();
+ });
+
+ init();
+
+ return output;
+};
+exports.lift = lift;
+
+const merges = inputs => {
+ let opened = inputs.length;
+ let output = new Output();
+ output.value = inputs[0].value;
+ inputs.forEach((input, index) => {
+ on(input, "data", data => receive(output, data));
+ on(input, "end", () => {
+ opened = opened - 1;
+ if (opened <= 0)
+ end(output);
+ });
+ });
+
+ once(output, "start", () => {
+ inputs.forEach(start);
+ output.value = inputs[0].value;
+ });
+
+ return output;
+};
+exports.merges = merges;
+
+const foldp = (step, initial, input) => {
+ let output = map(input, x => step(output.value, x));
+ output.value = initial;
+ return output;
+};
+exports.foldp = foldp;
+
+const keepIf = (p, base, input) => {
+ let output = filter(input, p);
+ output.value = base;
+ return output;
+};
+exports.keepIf = keepIf;
+
+function Input() {}
+Input.start = input => emit(input, "start", input);
+Input.prototype.start = Input.start;
+
+Input.end = input => {
+ emit(input, "end", input);
+ stop(input);
+};
+Input.prototype[end] = Input.end;
+
+// The event channel system caches the last event seen as input.value.
+// Unfortunately, if the last event is a DOM object this is a great way
+// leak windows. Mitigate this by storing input.value using a weak
+// reference. This allows the system to work for normal event processing
+// while also allowing the objects to be reclaimed. It means, however,
+// input.value cannot be accessed long after the event was dispatched.
+const WeakValueGetterSetter = {
+ get: function() {
+ return this._weakValue ? this._weakValue.get() : this._simpleValue
+ },
+ set: function(v) {
+ if (v && typeof v === "object") {
+ try {
+ // Try to set a weak reference. This can throw for some values.
+ // For example, if the value is a native object that does not
+ // implement nsISupportsWeakReference.
+ this._weakValue = Cu.getWeakReference(v)
+ this._simpleValue = undefined;
+ return;
+ } catch (e) {
+ // Do nothing. Fall through to setting _simpleValue below.
+ }
+ }
+ this._simpleValue = v;
+ this._weakValue = undefined;
+ },
+}
+Object.defineProperty(Input.prototype, "value", WeakValueGetterSetter);
+
+exports.Input = Input;
+
+// Define an Output type with a weak value getter for the transformation
+// functions that produce new channels.
+function Output() { }
+Object.defineProperty(Output.prototype, "value", WeakValueGetterSetter);
+exports.Output = Output;
+
+const $source = "@@source";
+const $outputs = "@@outputs";
+exports.outputs = $outputs;
+
+// NOTE: Passing DOM objects through a Reactor can cause them to leak
+// when they get cached in this.value. We cannot use a weak reference
+// in this case because the Reactor design expects to always have both the
+// past and present value. If we allow past values to be collected the
+// system breaks.
+
+function Reactor(options={}) {
+ const {onStep, onStart, onEnd} = options;
+ if (onStep)
+ this.onStep = onStep;
+ if (onStart)
+ this.onStart = onStart;
+ if (onEnd)
+ this.onEnd = onEnd;
+}
+Reactor.prototype.onStep = _ => void(0);
+Reactor.prototype.onStart = _ => void(0);
+Reactor.prototype.onEnd = _ => void(0);
+Reactor.prototype.onNext = function(present, past) {
+ this.value = present;
+ this.onStep(present, past);
+};
+Reactor.prototype.run = function(input) {
+ on(input, "data", message => this.onNext(message, input.value));
+ on(input, "end", () => this.onEnd(input.value));
+ start(input);
+ this.value = input.value;
+ this.onStart(input.value);
+};
+exports.Reactor = Reactor;
+
+/**
+ * Takes an object used as options with potential keys like 'onMessage',
+ * used to be called `require('sdk/event/core').setListeners` on.
+ * This strips all keys that would trigger a listener to be set.
+ *
+ * @params {Object} object
+ * @return {Object}
+ */
+
+function stripListeners (object) {
+ return Object.keys(object || {}).reduce((agg, key) => {
+ if (!EVENT_TYPE_PATTERN.test(key))
+ agg[key] = object[key];
+ return agg;
+ }, {});
+}
+exports.stripListeners = stripListeners;
+
+const when = (target, type) => new Promise(resolve => {
+ once(target, type, resolve);
+});
+exports.when = when;
diff --git a/components/jetpack/sdk/frame/hidden-frame.js b/components/jetpack/sdk/frame/hidden-frame.js
new file mode 100644
index 000000000..97e0b7974
--- /dev/null
+++ b/components/jetpack/sdk/frame/hidden-frame.js
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci } = require("chrome");
+const { Class } = require("../core/heritage");
+const { List, addListItem, removeListItem } = require("../util/list");
+const { EventTarget } = require("../event/target");
+const { emit } = require("../event/core");
+const { create: makeFrame } = require("./utils");
+const { defer } = require("../core/promise");
+const { when: unload } = require("../system/unload");
+const { validateOptions, getTypeOf } = require("../deprecated/api-utils");
+const { window } = require("../addon/window");
+const { fromIterator } = require("../util/array");
+
+// This cache is used to access friend properties between functions
+// without exposing them on the public API.
+var cache = new Set();
+var elements = new WeakMap();
+
+function contentLoaded(target) {
+ var deferred = defer();
+ target.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
+ // "DOMContentLoaded" events from nested frames propagate up to target,
+ // ignore events unless it's DOMContentLoaded for the given target.
+ if (event.target === target || event.target === target.contentDocument) {
+ target.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
+ deferred.resolve(target);
+ }
+ }, false);
+ return deferred.promise;
+}
+
+function FrameOptions(options) {
+ options = options || {}
+ return validateOptions(options, FrameOptions.validator);
+}
+FrameOptions.validator = {
+ onReady: {
+ is: ["undefined", "function", "array"],
+ ok: function(v) {
+ if (getTypeOf(v) === "array") {
+ // make sure every item is a function
+ return v.every(item => typeof(item) === "function")
+ }
+ return true;
+ }
+ },
+ onUnload: {
+ is: ["undefined", "function"]
+ }
+};
+
+var HiddenFrame = Class({
+ extends: EventTarget,
+ initialize: function initialize(options) {
+ options = FrameOptions(options);
+ EventTarget.prototype.initialize.call(this, options);
+ },
+ get element() {
+ return elements.get(this);
+ },
+ toString: function toString() {
+ return "[object Frame]"
+ }
+});
+exports.HiddenFrame = HiddenFrame
+
+function addHidenFrame(frame) {
+ if (!(frame instanceof HiddenFrame))
+ throw Error("The object to be added must be a HiddenFrame.");
+
+ // This instance was already added.
+ if (cache.has(frame)) return frame;
+ else cache.add(frame);
+
+ let element = makeFrame(window.document, {
+ nodeName: "iframe",
+ type: "content",
+ allowJavascript: true,
+ allowPlugins: true,
+ allowAuth: true,
+ });
+ elements.set(frame, element);
+
+ contentLoaded(element).then(function onFrameReady(element) {
+ emit(frame, "ready");
+ }, console.exception);
+
+ return frame;
+}
+exports.add = addHidenFrame
+
+function removeHiddenFrame(frame) {
+ if (!(frame instanceof HiddenFrame))
+ throw Error("The object to be removed must be a HiddenFrame.");
+
+ if (!cache.has(frame)) return;
+
+ // Remove from cache before calling in order to avoid loop
+ cache.delete(frame);
+ emit(frame, "unload")
+ let element = frame.element
+ if (element) element.parentNode.removeChild(element)
+}
+exports.remove = removeHiddenFrame;
+
+unload(() => fromIterator(cache).forEach(removeHiddenFrame));
diff --git a/components/jetpack/sdk/frame/utils.js b/components/jetpack/sdk/frame/utils.js
new file mode 100644
index 000000000..d9fccec4d
--- /dev/null
+++ b/components/jetpack/sdk/frame/utils.js
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Ci } = require("chrome");
+const XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+
+function eventTarget(frame) {
+ return getDocShell(frame).chromeEventHandler;
+}
+exports.eventTarget = eventTarget;
+
+function getDocShell(frame) {
+ let { frameLoader } = frame.QueryInterface(Ci.nsIFrameLoaderOwner);
+ return frameLoader && frameLoader.docShell;
+}
+exports.getDocShell = getDocShell;
+
+/**
+ * Creates a XUL `browser` element in a privileged document.
+ * @params {nsIDOMDocument} document
+ * @params {String} options.type
+ * By default is 'content' for possible values see:
+ * https://developer.mozilla.org/en/XUL/iframe#a-browser.type
+ * @params {String} options.uri
+ * URI of the document to be loaded into created frame.
+ * @params {Boolean} options.remote
+ * If `true` separate process will be used for this frame, also in such
+ * case all the following options are ignored.
+ * @params {Boolean} options.allowAuth
+ * Whether to allow auth dialogs. Defaults to `false`.
+ * @params {Boolean} options.allowJavascript
+ * Whether to allow Javascript execution. Defaults to `false`.
+ * @params {Boolean} options.allowPlugins
+ * Whether to allow plugin execution. Defaults to `false`.
+ */
+function create(target, options) {
+ target = target instanceof Ci.nsIDOMDocument ? target.documentElement :
+ target instanceof Ci.nsIDOMWindow ? target.document.documentElement :
+ target;
+ options = options || {};
+ let remote = options.remote || false;
+ let namespaceURI = options.namespaceURI || XUL;
+ let isXUL = namespaceURI === XUL;
+ let nodeName = isXUL && options.browser ? 'browser' : 'iframe';
+ let document = target.ownerDocument;
+
+ let frame = document.createElementNS(namespaceURI, nodeName);
+ // Type="content" is mandatory to enable stuff here:
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1776
+ frame.setAttribute('type', options.type || 'content');
+ frame.setAttribute('src', options.uri || 'about:blank');
+
+ // Must set the remote attribute before attaching the frame to the document
+ if (remote && isXUL) {
+ // We remove XBL binding to avoid execution of code that is not going to
+ // work because browser has no docShell attribute in remote mode
+ // (for example)
+ frame.setAttribute('style', '-moz-binding: none;');
+ frame.setAttribute('remote', 'true');
+ }
+
+ target.appendChild(frame);
+
+ // Load in separate process if `options.remote` is `true`.
+ // http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1347
+ if (remote && !isXUL) {
+ frame.QueryInterface(Ci.nsIMozBrowserFrame);
+ frame.createRemoteFrameLoader(null);
+ }
+
+ // If browser is remote it won't have a `docShell`.
+ if (!remote) {
+ let docShell = getDocShell(frame);
+ docShell.allowAuth = options.allowAuth || false;
+ docShell.allowJavascript = options.allowJavascript || false;
+ docShell.allowPlugins = options.allowPlugins || false;
+ docShell.allowWindowControl = options.allowWindowControl || false;
+ }
+
+ return frame;
+}
+exports.create = create;
+
+function swapFrameLoaders(from, to) {
+ return from.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(to);
+}
+exports.swapFrameLoaders = swapFrameLoaders;
diff --git a/components/jetpack/sdk/fs/path.js b/components/jetpack/sdk/fs/path.js
new file mode 100644
index 000000000..4474b2b4a
--- /dev/null
+++ b/components/jetpack/sdk/fs/path.js
@@ -0,0 +1,500 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Adapted version of:
+// https://github.com/joyent/node/blob/v0.11.3/lib/path.js
+
+// Shim process global from node.
+var process = Object.create(require('../system'));
+process.cwd = process.pathFor.bind(process, 'CurProcD');
+
+// Update original check in node `process.platform === 'win32'` since in SDK it's `winnt`.
+var isWindows = process.platform.indexOf('win') === 0;
+
+
+
+// resolves . and .. elements in a path array with directory names there
+// must be no slashes, empty elements, or device names (c:\) in the array
+// (so also no leading and trailing slashes - it does not distinguish
+// relative and absolute paths)
+function normalizeArray(parts, allowAboveRoot) {
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = parts.length - 1; i >= 0; i--) {
+ var last = parts[i];
+ if (last === '.') {
+ parts.splice(i, 1);
+ } else if (last === '..') {
+ parts.splice(i, 1);
+ up++;
+ } else if (up) {
+ parts.splice(i, 1);
+ up--;
+ }
+ }
+
+ // if the path is allowed to go above the root, restore leading ..s
+ if (allowAboveRoot) {
+ for (; up--; up) {
+ parts.unshift('..');
+ }
+ }
+
+ return parts;
+}
+
+
+if (isWindows) {
+ // Regex to split a windows path into three parts: [*, device, slash,
+ // tail] windows-only
+ var splitDeviceRe =
+ /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;
+
+ // Regex to split the tail part of the above into [*, dir, basename, ext]
+ var splitTailRe =
+ /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/;
+
+ // Function to split a filename into [root, dir, basename, ext]
+ // windows version
+ var splitPath = function(filename) {
+ // Separate device+slash from tail
+ var result = splitDeviceRe.exec(filename),
+ device = (result[1] || '') + (result[2] || ''),
+ tail = result[3] || '';
+ // Split the tail into dir, basename and extension
+ var result2 = splitTailRe.exec(tail),
+ dir = result2[1],
+ basename = result2[2],
+ ext = result2[3];
+ return [device, dir, basename, ext];
+ };
+
+ var normalizeUNCRoot = function(device) {
+ return '\\\\' + device.replace(/^[\\\/]+/, '').replace(/[\\\/]+/g, '\\');
+ };
+
+ // path.resolve([from ...], to)
+ // windows version
+ exports.resolve = function() {
+ var resolvedDevice = '',
+ resolvedTail = '',
+ resolvedAbsolute = false;
+
+ for (var i = arguments.length - 1; i >= -1; i--) {
+ var path;
+ if (i >= 0) {
+ path = arguments[i];
+ } else if (!resolvedDevice) {
+ path = process.cwd();
+ } else {
+ // Windows has the concept of drive-specific current working
+ // directories. If we've resolved a drive letter but not yet an
+ // absolute path, get cwd for that drive. We're sure the device is not
+ // an unc path at this points, because unc paths are always absolute.
+ path = process.env['=' + resolvedDevice];
+ // Verify that a drive-local cwd was found and that it actually points
+ // to our drive. If not, default to the drive's root.
+ if (!path || path.substr(0, 3).toLowerCase() !==
+ resolvedDevice.toLowerCase() + '\\') {
+ path = resolvedDevice + '\\';
+ }
+ }
+
+ // Skip empty and invalid entries
+ if (typeof path !== 'string') {
+ throw new TypeError('Arguments to path.resolve must be strings');
+ } else if (!path) {
+ continue;
+ }
+
+ var result = splitDeviceRe.exec(path),
+ device = result[1] || '',
+ isUnc = device && device.charAt(1) !== ':',
+ isAbsolute = exports.isAbsolute(path),
+ tail = result[3];
+
+ if (device &&
+ resolvedDevice &&
+ device.toLowerCase() !== resolvedDevice.toLowerCase()) {
+ // This path points to another device so it is not applicable
+ continue;
+ }
+
+ if (!resolvedDevice) {
+ resolvedDevice = device;
+ }
+ if (!resolvedAbsolute) {
+ resolvedTail = tail + '\\' + resolvedTail;
+ resolvedAbsolute = isAbsolute;
+ }
+
+ if (resolvedDevice && resolvedAbsolute) {
+ break;
+ }
+ }
+
+ // Convert slashes to backslashes when `resolvedDevice` points to an UNC
+ // root. Also squash multiple slashes into a single one where appropriate.
+ if (isUnc) {
+ resolvedDevice = normalizeUNCRoot(resolvedDevice);
+ }
+
+ // At this point the path should be resolved to a full absolute path,
+ // but handle relative paths to be safe (might happen when process.cwd()
+ // fails)
+
+ // Normalize the tail path
+
+ function f(p) {
+ return !!p;
+ }
+
+ resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f),
+ !resolvedAbsolute).join('\\');
+
+ return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
+ '.';
+ };
+
+ // windows version
+ exports.normalize = function(path) {
+ var result = splitDeviceRe.exec(path),
+ device = result[1] || '',
+ isUnc = device && device.charAt(1) !== ':',
+ isAbsolute = exports.isAbsolute(path),
+ tail = result[3],
+ trailingSlash = /[\\\/]$/.test(tail);
+
+ // If device is a drive letter, we'll normalize to lower case.
+ if (device && device.charAt(1) === ':') {
+ device = device[0].toLowerCase() + device.substr(1);
+ }
+
+ // Normalize the tail path
+ tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) {
+ return !!p;
+ }), !isAbsolute).join('\\');
+
+ if (!tail && !isAbsolute) {
+ tail = '.';
+ }
+ if (tail && trailingSlash) {
+ tail += '\\';
+ }
+
+ // Convert slashes to backslashes when `device` points to an UNC root.
+ // Also squash multiple slashes into a single one where appropriate.
+ if (isUnc) {
+ device = normalizeUNCRoot(device);
+ }
+
+ return device + (isAbsolute ? '\\' : '') + tail;
+ };
+
+ // windows version
+ exports.isAbsolute = function(path) {
+ var result = splitDeviceRe.exec(path),
+ device = result[1] || '',
+ isUnc = device && device.charAt(1) !== ':';
+ // UNC paths are always absolute
+ return !!result[2] || isUnc;
+ };
+
+ // windows version
+ exports.join = function() {
+ function f(p) {
+ if (typeof p !== 'string') {
+ throw new TypeError('Arguments to path.join must be strings');
+ }
+ return p;
+ }
+
+ var paths = Array.prototype.filter.call(arguments, f);
+ var joined = paths.join('\\');
+
+ // Make sure that the joined path doesn't start with two slashes, because
+ // normalize() will mistake it for an UNC path then.
+ //
+ // This step is skipped when it is very clear that the user actually
+ // intended to point at an UNC path. This is assumed when the first
+ // non-empty string arguments starts with exactly two slashes followed by
+ // at least one more non-slash character.
+ //
+ // Note that for normalize() to treat a path as an UNC path it needs to
+ // have at least 2 components, so we don't filter for that here.
+ // This means that the user can use join to construct UNC paths from
+ // a server name and a share name; for example:
+ // path.join('//server', 'share') -> '\\\\server\\share\')
+ if (!/^[\\\/]{2}[^\\\/]/.test(paths[0])) {
+ joined = joined.replace(/^[\\\/]{2,}/, '\\');
+ }
+
+ return exports.normalize(joined);
+ };
+
+ // path.relative(from, to)
+ // it will solve the relative path from 'from' to 'to', for instance:
+ // from = 'C:\\orandea\\test\\aaa'
+ // to = 'C:\\orandea\\impl\\bbb'
+ // The output of the function should be: '..\\..\\impl\\bbb'
+ // windows version
+ exports.relative = function(from, to) {
+ from = exports.resolve(from);
+ to = exports.resolve(to);
+
+ // windows is not case sensitive
+ var lowerFrom = from.toLowerCase();
+ var lowerTo = to.toLowerCase();
+
+ function trim(arr) {
+ var start = 0;
+ for (; start < arr.length; start++) {
+ if (arr[start] !== '') break;
+ }
+
+ var end = arr.length - 1;
+ for (; end >= 0; end--) {
+ if (arr[end] !== '') break;
+ }
+
+ if (start > end) return [];
+ return arr.slice(start, end - start + 1);
+ }
+
+ var toParts = trim(to.split('\\'));
+
+ var lowerFromParts = trim(lowerFrom.split('\\'));
+ var lowerToParts = trim(lowerTo.split('\\'));
+
+ var length = Math.min(lowerFromParts.length, lowerToParts.length);
+ var samePartsLength = length;
+ for (var i = 0; i < length; i++) {
+ if (lowerFromParts[i] !== lowerToParts[i]) {
+ samePartsLength = i;
+ break;
+ }
+ }
+
+ if (samePartsLength == 0) {
+ return to;
+ }
+
+ var outputParts = [];
+ for (var i = samePartsLength; i < lowerFromParts.length; i++) {
+ outputParts.push('..');
+ }
+
+ outputParts = outputParts.concat(toParts.slice(samePartsLength));
+
+ return outputParts.join('\\');
+ };
+
+ exports.sep = '\\';
+ exports.delimiter = ';';
+
+} else /* posix */ {
+
+ // Split a filename into [root, dir, basename, ext], unix version
+ // 'root' is just a slash, or nothing.
+ var splitPathRe =
+ /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
+ var splitPath = function(filename) {
+ return splitPathRe.exec(filename).slice(1);
+ };
+
+ // path.resolve([from ...], to)
+ // posix version
+ exports.resolve = function() {
+ var resolvedPath = '',
+ resolvedAbsolute = false;
+
+ for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+ var path = (i >= 0) ? arguments[i] : process.cwd();
+
+ // Skip empty and invalid entries
+ if (typeof path !== 'string') {
+ throw new TypeError('Arguments to path.resolve must be strings');
+ } else if (!path) {
+ continue;
+ }
+
+ resolvedPath = path + '/' + resolvedPath;
+ resolvedAbsolute = path.charAt(0) === '/';
+ }
+
+ // At this point the path should be resolved to a full absolute path, but
+ // handle relative paths to be safe (might happen when process.cwd() fails)
+
+ // Normalize the path
+ resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
+ return !!p;
+ }), !resolvedAbsolute).join('/');
+
+ return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+ };
+
+ // path.normalize(path)
+ // posix version
+ exports.normalize = function(path) {
+ var isAbsolute = exports.isAbsolute(path),
+ trailingSlash = path.substr(-1) === '/';
+
+ // Normalize the path
+ path = normalizeArray(path.split('/').filter(function(p) {
+ return !!p;
+ }), !isAbsolute).join('/');
+
+ if (!path && !isAbsolute) {
+ path = '.';
+ }
+ if (path && trailingSlash) {
+ path += '/';
+ }
+
+ return (isAbsolute ? '/' : '') + path;
+ };
+
+ // posix version
+ exports.isAbsolute = function(path) {
+ return path.charAt(0) === '/';
+ };
+
+ // posix version
+ exports.join = function() {
+ var paths = Array.prototype.slice.call(arguments, 0);
+ return exports.normalize(paths.filter(function(p, index) {
+ if (typeof p !== 'string') {
+ throw new TypeError('Arguments to path.join must be strings');
+ }
+ return p;
+ }).join('/'));
+ };
+
+
+ // path.relative(from, to)
+ // posix version
+ exports.relative = function(from, to) {
+ from = exports.resolve(from).substr(1);
+ to = exports.resolve(to).substr(1);
+
+ function trim(arr) {
+ var start = 0;
+ for (; start < arr.length; start++) {
+ if (arr[start] !== '') break;
+ }
+
+ var end = arr.length - 1;
+ for (; end >= 0; end--) {
+ if (arr[end] !== '') break;
+ }
+
+ if (start > end) return [];
+ return arr.slice(start, end - start + 1);
+ }
+
+ var fromParts = trim(from.split('/'));
+ var toParts = trim(to.split('/'));
+
+ var length = Math.min(fromParts.length, toParts.length);
+ var samePartsLength = length;
+ for (var i = 0; i < length; i++) {
+ if (fromParts[i] !== toParts[i]) {
+ samePartsLength = i;
+ break;
+ }
+ }
+
+ var outputParts = [];
+ for (var i = samePartsLength; i < fromParts.length; i++) {
+ outputParts.push('..');
+ }
+
+ outputParts = outputParts.concat(toParts.slice(samePartsLength));
+
+ return outputParts.join('/');
+ };
+
+ exports.sep = '/';
+ exports.delimiter = ':';
+}
+
+exports.dirname = function(path) {
+ var result = splitPath(path),
+ root = result[0],
+ dir = result[1];
+
+ if (!root && !dir) {
+ // No dirname whatsoever
+ return '.';
+ }
+
+ if (dir) {
+ // It has a dirname, strip trailing slash
+ dir = dir.substr(0, dir.length - 1);
+ }
+
+ return root + dir;
+};
+
+
+exports.basename = function(path, ext) {
+ var f = splitPath(path)[2];
+ // TODO: make this comparison case-insensitive on windows?
+ if (ext && f.substr(-1 * ext.length) === ext) {
+ f = f.substr(0, f.length - ext.length);
+ }
+ return f;
+};
+
+
+exports.extname = function(path) {
+ return splitPath(path)[3];
+};
+
+if (isWindows) {
+ exports._makeLong = function(path) {
+ // Note: this will *probably* throw somewhere.
+ if (typeof path !== 'string')
+ return path;
+
+ if (!path) {
+ return '';
+ }
+
+ var resolvedPath = exports.resolve(path);
+
+ if (/^[a-zA-Z]\:\\/.test(resolvedPath)) {
+ // path is local filesystem path, which needs to be converted
+ // to long UNC path.
+ return '\\\\?\\' + resolvedPath;
+ } else if (/^\\\\[^?.]/.test(resolvedPath)) {
+ // path is network UNC path, which needs to be converted
+ // to long UNC path.
+ return '\\\\?\\UNC\\' + resolvedPath.substring(2);
+ }
+
+ return path;
+ };
+} else {
+ exports._makeLong = function(path) {
+ return path;
+ };
+} \ No newline at end of file
diff --git a/components/jetpack/sdk/hotkeys.js b/components/jetpack/sdk/hotkeys.js
new file mode 100644
index 000000000..00081455e
--- /dev/null
+++ b/components/jetpack/sdk/hotkeys.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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const INVALID_HOTKEY = "Hotkey must have at least one modifier.";
+
+const { toJSON: jsonify, toString: stringify,
+ isFunctionKey } = require("./keyboard/utils");
+const { register, unregister } = require("./keyboard/hotkeys");
+
+const Hotkey = exports.Hotkey = function Hotkey(options) {
+ if (!(this instanceof Hotkey))
+ return new Hotkey(options);
+
+ // Parsing key combination string.
+ let hotkey = jsonify(options.combo);
+ if (!isFunctionKey(hotkey.key) && !hotkey.modifiers.length) {
+ throw new TypeError(INVALID_HOTKEY);
+ }
+
+ this.onPress = options.onPress && options.onPress.bind(this);
+ this.toString = stringify.bind(null, hotkey);
+ // Registering listener on keyboard combination enclosed by this hotkey.
+ // Please note that `this.toString()` is a normalized version of
+ // `options.combination` where order of modifiers is sorted and `accel` is
+ // replaced with platform specific key.
+ register(this.toString(), this.onPress);
+ // We freeze instance before returning it in order to make it's properties
+ // read-only.
+ return Object.freeze(this);
+};
+Hotkey.prototype.destroy = function destroy() {
+ unregister(this.toString(), this.onPress);
+};
diff --git a/components/jetpack/sdk/indexed-db.js b/components/jetpack/sdk/indexed-db.js
new file mode 100644
index 000000000..d4d166c02
--- /dev/null
+++ b/components/jetpack/sdk/indexed-db.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci } = require("chrome");
+const { id } = require("./self");
+
+// placeholder, copied from bootstrap.js
+var sanitizeId = function(id){
+ let uuidRe =
+ /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
+
+ let domain = id.
+ toLowerCase().
+ replace(/@/g, "-at-").
+ replace(/\./g, "-dot-").
+ replace(uuidRe, "$1");
+
+ return domain
+};
+
+const PSEUDOURI = "indexeddb://" + sanitizeId(id) // https://bugzilla.mozilla.org/show_bug.cgi?id=779197
+
+// Use XPCOM because `require("./url").URL` doesn't expose the raw uri object.
+var principaluri = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService).
+ newURI(PSEUDOURI, null, null);
+
+var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+var principal = ssm.createCodebasePrincipal(principaluri, {});
+
+function toArray(args) {
+ return Array.prototype.slice.call(args);
+}
+
+function openInternal(args, forPrincipal, deleting) {
+ if (forPrincipal) {
+ args = toArray(args);
+ } else {
+ args = [principal].concat(toArray(args));
+ }
+ if (args.length == 2) {
+ args.push({ storage: "persistent" });
+ } else if (!deleting && args.length >= 3 && typeof args[2] === "number") {
+ args[2] = { version: args[2], storage: "persistent" };
+ }
+
+ if (deleting) {
+ return indexedDB.deleteForPrincipal.apply(indexedDB, args);
+ }
+
+ return indexedDB.openForPrincipal.apply(indexedDB, args);
+}
+
+exports.indexedDB = Object.freeze({
+ open: function () {
+ return openInternal(arguments, false, false);
+ },
+ deleteDatabase: function () {
+ return openInternal(arguments, false, true);
+ },
+ openForPrincipal: function () {
+ return openInternal(arguments, true, false);
+ },
+ deleteForPrincipal: function () {
+ return openInternal(arguments, true, true);
+ },
+ cmp: indexedDB.cmp.bind(indexedDB)
+});
+
+exports.IDBKeyRange = IDBKeyRange;
+exports.DOMException = Ci.nsIDOMDOMException;
diff --git a/components/jetpack/sdk/input/browser.js b/components/jetpack/sdk/input/browser.js
new file mode 100644
index 000000000..daea875bf
--- /dev/null
+++ b/components/jetpack/sdk/input/browser.js
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { windows, isBrowser, isInteractive, isDocumentLoaded,
+ getOuterId } = require("../window/utils");
+const { InputPort } = require("./system");
+const { lift, merges, foldp, keepIf, start, Input } = require("../event/utils");
+const { patch } = require("diffpatcher/index");
+const { Sequence, seq, filter, object, pairs } = require("../util/sequence");
+
+
+// Create lazy iterators from the regular arrays, although
+// once https://github.com/mozilla/addon-sdk/pull/1314 lands
+// `windows` will be transforme to lazy iterators.
+// When iterated over belowe sequences items will represent
+// state of windows at the time of iteration.
+const opened = seq(function*() {
+ const items = windows("navigator:browser", {includePrivate: true});
+ for (let item of items) {
+ yield [getOuterId(item), item];
+ }
+});
+const interactive = filter(([_, window]) => isInteractive(window), opened);
+const loaded = filter(([_, window]) => isDocumentLoaded(window), opened);
+
+// Helper function that converts given argument to a delta.
+const Update = window => window && object([getOuterId(window), window]);
+const Delete = window => window && object([getOuterId(window), null]);
+
+
+// Signal represents delta for last top level window close.
+const LastClosed = lift(Delete,
+ keepIf(isBrowser, null,
+ new InputPort({topic: "domwindowclosed"})));
+exports.LastClosed = LastClosed;
+
+const windowFor = document => document && document.defaultView;
+
+// Signal represent delta for last top level window document becoming interactive.
+const InteractiveDoc = new InputPort({topic: "chrome-document-interactive"});
+const InteractiveWin = lift(windowFor, InteractiveDoc);
+const LastInteractive = lift(Update, keepIf(isBrowser, null, InteractiveWin));
+exports.LastInteractive = LastInteractive;
+
+// Signal represent delta for last top level window loaded.
+const LoadedDoc = new InputPort({topic: "chrome-document-loaded"});
+const LoadedWin = lift(windowFor, LoadedDoc);
+const LastLoaded = lift(Update, keepIf(isBrowser, null, LoadedWin));
+exports.LastLoaded = LastLoaded;
+
+
+const initialize = input => {
+ if (!input.initialized) {
+ input.value = object(...input.value);
+ Input.start(input);
+ input.initialized = true;
+ }
+};
+
+// Signal represents set of top level interactive windows, updated any
+// time new window becomes interactive or one get's closed.
+const Interactive = foldp(patch, interactive, merges([LastInteractive,
+ LastClosed]));
+Interactive[start] = initialize;
+exports.Interactive = Interactive;
+
+// Signal represents set of top level loaded window, updated any time
+// new window becomes interactive or one get's closed.
+const Loaded = foldp(patch, loaded, merges([LastLoaded, LastClosed]));
+Loaded[start] = initialize;
+exports.Loaded = Loaded;
diff --git a/components/jetpack/sdk/input/customizable-ui.js b/components/jetpack/sdk/input/customizable-ui.js
new file mode 100644
index 000000000..a41d0971a
--- /dev/null
+++ b/components/jetpack/sdk/input/customizable-ui.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Cu } = require("chrome");
+const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
+const { receive } = require("../event/utils");
+const { InputPort } = require("./system");
+const { object} = require("../util/sequence");
+const { getOuterId } = require("../window/utils");
+
+const Input = function() {};
+Input.prototype = Object.create(InputPort.prototype);
+
+Input.prototype.onCustomizeStart = function (window) {
+ receive(this, object([getOuterId(window), true]));
+}
+
+Input.prototype.onCustomizeEnd = function (window) {
+ receive(this, object([getOuterId(window), null]));
+}
+
+Input.prototype.addListener = input => CustomizableUI.addListener(input);
+
+Input.prototype.removeListener = input => CustomizableUI.removeListener(input);
+
+exports.CustomizationInput = Input;
diff --git a/components/jetpack/sdk/input/frame.js b/components/jetpack/sdk/input/frame.js
new file mode 100644
index 000000000..50efaa745
--- /dev/null
+++ b/components/jetpack/sdk/input/frame.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Ci } = require("chrome");
+const { InputPort } = require("./system");
+const { getFrameElement, getOuterId,
+ getOwnerBrowserWindow } = require("../window/utils");
+const { isnt } = require("../lang/functional");
+const { foldp, lift, merges, keepIf } = require("../event/utils");
+const { object } = require("../util/sequence");
+const { compose } = require("../lang/functional");
+const { LastClosed } = require("./browser");
+const { patch } = require("diffpatcher/index");
+
+const Document = Ci.nsIDOMDocument;
+
+const isntNull = isnt(null);
+
+const frameID = frame => frame.id;
+const browserID = compose(getOuterId, getOwnerBrowserWindow);
+
+const isInnerFrame = frame =>
+ frame && frame.hasAttribute("data-is-sdk-inner-frame");
+
+// Utility function that given content window loaded in our frame views returns
+// an actual frame. This basically takes care of fact that actual frame document
+// is loaded in the nested iframe. If content window is not loaded in the nested
+// frame of the frame view it returs null.
+const getFrame = document =>
+ document && document.defaultView && getFrameElement(document.defaultView);
+
+const FrameInput = function(options) {
+ const input = keepIf(isInnerFrame, null,
+ lift(getFrame, new InputPort(options)));
+ return lift(frame => {
+ if (!frame) return frame;
+ const [id, owner] = [frameID(frame), browserID(frame)];
+ return object([id, {owners: object([owner, options.update])}]);
+ }, input);
+};
+
+const LastLoading = new FrameInput({topic: "document-element-inserted",
+ update: {readyState: "loading"}});
+exports.LastLoading = LastLoading;
+
+const LastInteractive = new FrameInput({topic: "content-document-interactive",
+ update: {readyState: "interactive"}});
+exports.LastInteractive = LastInteractive;
+
+const LastLoaded = new FrameInput({topic: "content-document-loaded",
+ update: {readyState: "complete"}});
+exports.LastLoaded = LastLoaded;
+
+const LastUnloaded = new FrameInput({topic: "content-page-hidden",
+ update: null});
+exports.LastUnloaded = LastUnloaded;
+
+// Represents state of SDK frames in form of data structure:
+// {"frame#1": {"id": "frame#1",
+// "inbox": {"data": "ping",
+// "target": {"id": "frame#1", "owner": "outerWindowID#2"},
+// "source": {"id": "frame#1"}}
+// "url": "resource://addon-1/data/index.html",
+// "owners": {"outerWindowID#1": {"readyState": "loading"},
+// "outerWindowID#2": {"readyState": "complete"}}
+//
+//
+// frame#2: {"id": "frame#2",
+// "url": "resource://addon-1/data/main.html",
+// "outbox": {"data": "pong",
+// "source": {"id": "frame#2", "owner": "outerWindowID#1"}
+// "target": {"id": "frame#2"}}
+// "owners": {outerWindowID#1: {readyState: "interacitve"}}}}
+const Frames = foldp(patch, {}, merges([
+ LastLoading,
+ LastInteractive,
+ LastLoaded,
+ LastUnloaded,
+ new InputPort({ id: "frame-mailbox" }),
+ new InputPort({ id: "frame-change" }),
+ new InputPort({ id: "frame-changed" })
+]));
+exports.Frames = Frames;
diff --git a/components/jetpack/sdk/input/system.js b/components/jetpack/sdk/input/system.js
new file mode 100644
index 000000000..66bc6daec
--- /dev/null
+++ b/components/jetpack/sdk/input/system.js
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, Ci, Cr, Cu } = require("chrome");
+const { Input, start, stop, end, receive, outputs } = require("../event/utils");
+const { once, off } = require("../event/core");
+const { id: addonID } = require("../self");
+
+const unloadMessage = require("@loader/unload");
+const observerService = Cc['@mozilla.org/observer-service;1'].
+ getService(Ci.nsIObserverService);
+const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
+const addObserver = ShimWaiver.getProperty(observerService, "addObserver");
+const removeObserver = ShimWaiver.getProperty(observerService, "removeObserver");
+
+
+const addonUnloadTopic = "sdk:loader:destroy";
+
+const isXrayWrapper = Cu.isXrayWrapper;
+// In the past SDK used to double-wrap notifications dispatched, which
+// made them awkward to use outside of SDK. At present they no longer
+// do that, although we still supported for legacy reasons.
+const isLegacyWrapper = x =>
+ x && x.wrappedJSObject &&
+ "observersModuleSubjectWrapper" in x.wrappedJSObject;
+
+const unwrapLegacy = x => x.wrappedJSObject.object;
+
+// `InputPort` provides a way to create a signal out of the observer
+// notification subject's for the given `topic`. If `options.initial`
+// is provided it is used as initial value otherwise `null` is used.
+// Constructor can be given `options.id` that will be used to create
+// a `topic` which is namespaced to an add-on (this avoids conflicts
+// when multiple add-on are used, although in a future host probably
+// should just be shared across add-ons). It is also possible to
+// specify a specific `topic` via `options.topic` which is used as
+// without namespacing. Created signal ends whenever add-on is
+// unloaded.
+const InputPort = function InputPort({id, topic, initial}) {
+ this.id = id || topic;
+ this.topic = topic || "sdk:" + addonID + ":" + id;
+ this.value = initial === void(0) ? null : initial;
+ this.observing = false;
+ this[outputs] = [];
+};
+
+// InputPort type implements `Input` signal interface.
+InputPort.prototype = new Input();
+InputPort.prototype.constructor = InputPort;
+
+// When port is started (which is when it's subgraph get's
+// first subscriber) actual observer is registered.
+InputPort.start = input => {
+ input.addListener(input);
+ // Also register add-on unload observer to end this signal
+ // when that happens.
+ addObserver(input, addonUnloadTopic, false);
+};
+InputPort.prototype[start] = InputPort.start;
+
+InputPort.addListener = input => addObserver(input, input.topic, false);
+InputPort.prototype.addListener = InputPort.addListener;
+
+// When port is stopped (which is when it's subgraph has no
+// no subcribers left) an actual observer unregistered.
+// Note that port stopped once it ends as well (which is when
+// add-on is unloaded).
+InputPort.stop = input => {
+ input.removeListener(input);
+ removeObserver(input, addonUnloadTopic);
+};
+InputPort.prototype[stop] = InputPort.stop;
+
+InputPort.removeListener = input => removeObserver(input, input.topic);
+InputPort.prototype.removeListener = InputPort.removeListener;
+
+// `InputPort` also implements `nsIObserver` interface and
+// `nsISupportsWeakReference` interfaces as it's going to be used as such.
+InputPort.prototype.QueryInterface = function(iid) {
+ if (!iid.equals(Ci.nsIObserver) && !iid.equals(Ci.nsISupportsWeakReference))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+
+ return this;
+};
+
+// `InputPort` instances implement `observe` method, which is invoked when
+// observer notifications are dispatched. The `subject` of that notification
+// are received on this signal.
+InputPort.prototype.observe = function(subject, topic, data) {
+ // Unwrap message from the subject. SDK used to have it's own version of
+ // wrappedJSObjects which take precedence, if subject has `wrappedJSObject`
+ // and it's not an XrayWrapper use it as message. Otherwise use subject as
+ // is.
+ const message = subject === null ? null :
+ isLegacyWrapper(subject) ? unwrapLegacy(subject) :
+ isXrayWrapper(subject) ? subject :
+ subject.wrappedJSObject ? subject.wrappedJSObject :
+ subject;
+
+ // If observer topic matches topic of the input port receive a message.
+ if (topic === this.topic) {
+ receive(this, message);
+ }
+
+ // If observe topic is add-on unload topic we create an end message.
+ if (topic === addonUnloadTopic && message === unloadMessage) {
+ end(this);
+ }
+};
+
+exports.InputPort = InputPort;
diff --git a/components/jetpack/sdk/io/buffer.js b/components/jetpack/sdk/io/buffer.js
new file mode 100644
index 000000000..5ea169402
--- /dev/null
+++ b/components/jetpack/sdk/io/buffer.js
@@ -0,0 +1,351 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'experimental'
+};
+
+/*
+ * Encodings supported by TextEncoder/Decoder:
+ * utf-8, utf-16le, utf-16be
+ * http://encoding.spec.whatwg.org/#interface-textencoder
+ *
+ * Node however supports the following encodings:
+ * ascii, utf-8, utf-16le, usc2, base64, hex
+ */
+
+const { Cu } = require('chrome');
+const { isNumber } = require('sdk/lang/type');
+const { TextEncoder, TextDecoder } = Cu.import('resource://gre/modules/commonjs/toolkit/loader.js', {});
+
+exports.TextEncoder = TextEncoder;
+exports.TextDecoder = TextDecoder;
+
+/**
+ * Use WeakMaps to work around Bug 929146, which prevents us from adding
+ * getters or values to typed arrays
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=929146
+ */
+const parents = new WeakMap();
+const views = new WeakMap();
+
+function Buffer(subject, encoding /*, bufferLength */) {
+
+ // Allow invocation without `new` constructor
+ if (!(this instanceof Buffer))
+ return new Buffer(subject, encoding, arguments[2]);
+
+ var type = typeof(subject);
+
+ switch (type) {
+ case 'number':
+ // Create typed array of the given size if number.
+ try {
+ let buffer = new Uint8Array(subject > 0 ? Math.floor(subject) : 0);
+ return buffer;
+ } catch (e) {
+ if (/size and count too large/.test(e.message) ||
+ /invalid arguments/.test(e.message))
+ throw new RangeError('Could not instantiate buffer: size of buffer may be too large');
+ else
+ throw new Error('Could not instantiate buffer');
+ }
+ break;
+ case 'string':
+ // If string encode it and use buffer for the returned Uint8Array
+ // to create a local patched version that acts like node buffer.
+ encoding = encoding || 'utf8';
+ return new Uint8Array(new TextEncoder(encoding).encode(subject).buffer);
+ case 'object':
+ // This form of the constructor uses the form of
+ // new Uint8Array(buffer, offset, length);
+ // So we can instantiate a typed array within the constructor
+ // to inherit the appropriate properties, where both the
+ // `subject` and newly instantiated buffer share the same underlying
+ // data structure.
+ if (arguments.length === 3)
+ return new Uint8Array(subject, encoding, arguments[2]);
+ // If array or alike just make a copy with a local patched prototype.
+ else
+ return new Uint8Array(subject);
+ default:
+ throw new TypeError('must start with number, buffer, array or string');
+ }
+}
+exports.Buffer = Buffer;
+
+// Tests if `value` is a Buffer.
+Buffer.isBuffer = value => value instanceof Buffer
+
+// Returns true if the encoding is a valid encoding argument & false otherwise
+Buffer.isEncoding = function (encoding) {
+ if (!encoding) return false;
+ try {
+ new TextDecoder(encoding);
+ } catch(e) {
+ return false;
+ }
+ return true;
+}
+
+// Gives the actual byte length of a string. encoding defaults to 'utf8'.
+// This is not the same as String.prototype.length since that returns the
+// number of characters in a string.
+Buffer.byteLength = (value, encoding = 'utf8') =>
+ new TextEncoder(encoding).encode(value).byteLength
+
+// Direct copy of the nodejs's buffer implementation:
+// https://github.com/joyent/node/blob/b255f4c10a80343f9ce1cee56d0288361429e214/lib/buffer.js#L146-L177
+Buffer.concat = function(list, length) {
+ if (!Array.isArray(list))
+ throw new TypeError('Usage: Buffer.concat(list[, length])');
+
+ if (typeof length === 'undefined') {
+ length = 0;
+ for (var i = 0; i < list.length; i++)
+ length += list[i].length;
+ } else {
+ length = ~~length;
+ }
+
+ if (length < 0)
+ length = 0;
+
+ if (list.length === 0)
+ return new Buffer(0);
+ else if (list.length === 1)
+ return list[0];
+
+ if (length < 0)
+ throw new RangeError('length is not a positive number');
+
+ var buffer = new Buffer(length);
+ var pos = 0;
+ for (var i = 0; i < list.length; i++) {
+ var buf = list[i];
+ buf.copy(buffer, pos);
+ pos += buf.length;
+ }
+
+ return buffer;
+};
+
+// Node buffer is very much like Uint8Array although it has bunch of methods
+// that typically can be used in combination with `DataView` while preserving
+// access by index. Since in SDK each module has it's own set of bult-ins it
+// ok to patch ours to make it nodejs Buffer compatible.
+const Uint8ArraySet = Uint8Array.prototype.set
+Buffer.prototype = Uint8Array.prototype;
+Object.defineProperties(Buffer.prototype, {
+ parent: {
+ get: function() { return parents.get(this, undefined); }
+ },
+ view: {
+ get: function () {
+ let view = views.get(this, undefined);
+ if (view) return view;
+ view = new DataView(this.buffer);
+ views.set(this, view);
+ return view;
+ }
+ },
+ toString: {
+ value: function(encoding, start, end) {
+ encoding = !!encoding ? (encoding + '').toLowerCase() : 'utf8';
+ start = Math.max(0, ~~start);
+ end = Math.min(this.length, end === void(0) ? this.length : ~~end);
+ return new TextDecoder(encoding).decode(this.subarray(start, end));
+ }
+ },
+ toJSON: {
+ value: function() {
+ return { type: 'Buffer', data: Array.slice(this, 0) };
+ }
+ },
+ get: {
+ value: function(offset) {
+ return this[offset];
+ }
+ },
+ set: {
+ value: function(offset, value) { this[offset] = value; }
+ },
+ copy: {
+ value: function(target, offset, start, end) {
+ let length = this.length;
+ let targetLength = target.length;
+ offset = isNumber(offset) ? offset : 0;
+ start = isNumber(start) ? start : 0;
+
+ if (start < 0)
+ throw new RangeError('sourceStart is outside of valid range');
+ if (end < 0)
+ throw new RangeError('sourceEnd is outside of valid range');
+
+ // If sourceStart > sourceEnd, or targetStart > targetLength,
+ // zero bytes copied
+ if (start > end ||
+ offset > targetLength
+ )
+ return 0;
+
+ // If `end` is not defined, or if it is defined
+ // but would overflow `target`, redefine `end`
+ // so we can copy as much as we can
+ if (end - start > targetLength - offset ||
+ end == null) {
+ let remainingTarget = targetLength - offset;
+ let remainingSource = length - start;
+ if (remainingSource <= remainingTarget)
+ end = length;
+ else
+ end = start + remainingTarget;
+ }
+
+ Uint8ArraySet.call(target, this.subarray(start, end), offset);
+ return end - start;
+ }
+ },
+ slice: {
+ value: function(start, end) {
+ let length = this.length;
+ start = ~~start;
+ end = end != null ? end : length;
+
+ if (start < 0) {
+ start += length;
+ if (start < 0) start = 0;
+ } else if (start > length)
+ start = length;
+
+ if (end < 0) {
+ end += length;
+ if (end < 0) end = 0;
+ } else if (end > length)
+ end = length;
+
+ if (end < start)
+ end = start;
+
+ // This instantiation uses the new Uint8Array(buffer, offset, length) version
+ // of construction to share the same underling data structure
+ let buffer = new Buffer(this.buffer, start, end - start);
+
+ // If buffer has a value, assign its parent value to the
+ // buffer it shares its underlying structure with. If a slice of
+ // a slice, then use the root structure
+ if (buffer.length > 0)
+ parents.set(buffer, this.parent || this);
+
+ return buffer;
+ }
+ },
+ write: {
+ value: function(string, offset, length, encoding = 'utf8') {
+ // write(string, encoding);
+ if (typeof(offset) === 'string' && Number.isNaN(parseInt(offset))) {
+ [offset, length, encoding] = [0, null, offset];
+ }
+ // write(string, offset, encoding);
+ else if (typeof(length) === 'string')
+ [length, encoding] = [null, length];
+
+ if (offset < 0 || offset > this.length)
+ throw new RangeError('offset is outside of valid range');
+
+ offset = ~~offset;
+
+ // Clamp length if it would overflow buffer, or if its
+ // undefined
+ if (length == null || length + offset > this.length)
+ length = this.length - offset;
+
+ let buffer = new TextEncoder(encoding).encode(string);
+ let result = Math.min(buffer.length, length);
+ if (buffer.length !== length)
+ buffer = buffer.subarray(0, length);
+
+ Uint8ArraySet.call(this, buffer, offset);
+ return result;
+ }
+ },
+ fill: {
+ value: function fill(value, start, end) {
+ let length = this.length;
+ value = value || 0;
+ start = start || 0;
+ end = end || length;
+
+ if (typeof(value) === 'string')
+ value = value.charCodeAt(0);
+ if (typeof(value) !== 'number' || isNaN(value))
+ throw TypeError('value is not a number');
+ if (end < start)
+ throw new RangeError('end < start');
+
+ // Fill 0 bytes; we're done
+ if (end === start)
+ return 0;
+ if (length == 0)
+ return 0;
+
+ if (start < 0 || start >= length)
+ throw RangeError('start out of bounds');
+
+ if (end < 0 || end > length)
+ throw RangeError('end out of bounds');
+
+ let index = start;
+ while (index < end) this[index++] = value;
+ }
+ }
+});
+
+// Define nodejs Buffer's getter and setter functions that just proxy
+// to internal DataView's equivalent methods.
+
+// TODO do we need to check architecture to see if it's default big/little endian?
+[['readUInt16LE', 'getUint16', true],
+ ['readUInt16BE', 'getUint16', false],
+ ['readInt16LE', 'getInt16', true],
+ ['readInt16BE', 'getInt16', false],
+ ['readUInt32LE', 'getUint32', true],
+ ['readUInt32BE', 'getUint32', false],
+ ['readInt32LE', 'getInt32', true],
+ ['readInt32BE', 'getInt32', false],
+ ['readFloatLE', 'getFloat32', true],
+ ['readFloatBE', 'getFloat32', false],
+ ['readDoubleLE', 'getFloat64', true],
+ ['readDoubleBE', 'getFloat64', false],
+ ['readUInt8', 'getUint8'],
+ ['readInt8', 'getInt8']].forEach(([alias, name, littleEndian]) => {
+ Object.defineProperty(Buffer.prototype, alias, {
+ value: function(offset) {
+ return this.view[name](offset, littleEndian);
+ }
+ });
+});
+
+[['writeUInt16LE', 'setUint16', true],
+ ['writeUInt16BE', 'setUint16', false],
+ ['writeInt16LE', 'setInt16', true],
+ ['writeInt16BE', 'setInt16', false],
+ ['writeUInt32LE', 'setUint32', true],
+ ['writeUInt32BE', 'setUint32', false],
+ ['writeInt32LE', 'setInt32', true],
+ ['writeInt32BE', 'setInt32', false],
+ ['writeFloatLE', 'setFloat32', true],
+ ['writeFloatBE', 'setFloat32', false],
+ ['writeDoubleLE', 'setFloat64', true],
+ ['writeDoubleBE', 'setFloat64', false],
+ ['writeUInt8', 'setUint8'],
+ ['writeInt8', 'setInt8']].forEach(([alias, name, littleEndian]) => {
+ Object.defineProperty(Buffer.prototype, alias, {
+ value: function(value, offset) {
+ return this.view[name](offset, value, littleEndian);
+ }
+ });
+});
diff --git a/components/jetpack/sdk/io/byte-streams.js b/components/jetpack/sdk/io/byte-streams.js
new file mode 100644
index 000000000..6afab4369
--- /dev/null
+++ b/components/jetpack/sdk/io/byte-streams.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+exports.ByteReader = ByteReader;
+exports.ByteWriter = ByteWriter;
+
+const {Cc, Ci} = require("chrome");
+
+// This just controls the maximum number of bytes we read in at one time.
+const BUFFER_BYTE_LEN = 0x8000;
+
+function ByteReader(inputStream) {
+ const self = this;
+
+ let stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(inputStream);
+
+ let manager = new StreamManager(this, stream);
+
+ this.read = function ByteReader_read(numBytes) {
+ manager.ensureOpened();
+ if (typeof(numBytes) !== "number")
+ numBytes = Infinity;
+
+ let data = "";
+ let read = 0;
+ try {
+ while (true) {
+ let avail = stream.available();
+ let toRead = Math.min(numBytes - read, avail, BUFFER_BYTE_LEN);
+ if (toRead <= 0)
+ break;
+ data += stream.readBytes(toRead);
+ read += toRead;
+ }
+ }
+ catch (err) {
+ throw new Error("Error reading from stream: " + err);
+ }
+
+ return data;
+ };
+}
+
+function ByteWriter(outputStream) {
+ const self = this;
+
+ let stream = Cc["@mozilla.org/binaryoutputstream;1"].
+ createInstance(Ci.nsIBinaryOutputStream);
+ stream.setOutputStream(outputStream);
+
+ let manager = new StreamManager(this, stream);
+
+ this.write = function ByteWriter_write(str) {
+ manager.ensureOpened();
+ try {
+ stream.writeBytes(str, str.length);
+ }
+ catch (err) {
+ throw new Error("Error writing to stream: " + err);
+ }
+ };
+}
+
+
+// This manages the lifetime of stream, a ByteReader or ByteWriter. It defines
+// closed and close() on stream and registers an unload listener that closes
+// rawStream if it's still opened. It also provides ensureOpened(), which
+// throws an exception if the stream is closed.
+function StreamManager(stream, rawStream) {
+ const self = this;
+ this.rawStream = rawStream;
+ this.opened = true;
+
+ stream.__defineGetter__("closed", function stream_closed() {
+ return !self.opened;
+ });
+
+ stream.close = function stream_close() {
+ self.ensureOpened();
+ self.unload();
+ };
+
+ require("../system/unload").ensure(this);
+}
+
+StreamManager.prototype = {
+ ensureOpened: function StreamManager_ensureOpened() {
+ if (!this.opened)
+ throw new Error("The stream is closed and cannot be used.");
+ },
+ unload: function StreamManager_unload() {
+ this.rawStream.close();
+ this.opened = false;
+ }
+};
diff --git a/components/jetpack/sdk/io/file.js b/components/jetpack/sdk/io/file.js
new file mode 100644
index 000000000..47467df87
--- /dev/null
+++ b/components/jetpack/sdk/io/file.js
@@ -0,0 +1,196 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "deprecated"
+};
+
+const {Cc,Ci,Cr} = require("chrome");
+const byteStreams = require("./byte-streams");
+const textStreams = require("./text-streams");
+
+// Flags passed when opening a file. See nsprpub/pr/include/prio.h.
+const OPEN_FLAGS = {
+ RDONLY: parseInt("0x01"),
+ WRONLY: parseInt("0x02"),
+ CREATE_FILE: parseInt("0x08"),
+ APPEND: parseInt("0x10"),
+ TRUNCATE: parseInt("0x20"),
+ EXCL: parseInt("0x80")
+};
+
+var dirsvc = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+
+function MozFile(path) {
+ var file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ return file;
+}
+
+function ensureReadable(file) {
+ if (!file.isReadable())
+ throw new Error("path is not readable: " + file.path);
+}
+
+function ensureDir(file) {
+ ensureExists(file);
+ if (!file.isDirectory())
+ throw new Error("path is not a directory: " + file.path);
+}
+
+function ensureFile(file) {
+ ensureExists(file);
+ if (!file.isFile())
+ throw new Error("path is not a file: " + file.path);
+}
+
+function ensureExists(file) {
+ if (!file.exists())
+ throw friendlyError(Cr.NS_ERROR_FILE_NOT_FOUND, file.path);
+}
+
+function friendlyError(errOrResult, filename) {
+ var isResult = typeof(errOrResult) === "number";
+ var result = isResult ? errOrResult : errOrResult.result;
+ switch (result) {
+ case Cr.NS_ERROR_FILE_NOT_FOUND:
+ return new Error("path does not exist: " + filename);
+ }
+ return isResult ? new Error("XPCOM error code: " + errOrResult) : errOrResult;
+}
+
+exports.exists = function exists(filename) {
+ return MozFile(filename).exists();
+};
+
+exports.isFile = function isFile(filename) {
+ return MozFile(filename).isFile();
+};
+
+exports.read = function read(filename, mode) {
+ if (typeof(mode) !== "string")
+ mode = "";
+
+ // Ensure mode is read-only.
+ mode = /b/.test(mode) ? "b" : "";
+
+ var stream = exports.open(filename, mode);
+ try {
+ var str = stream.read();
+ }
+ finally {
+ stream.close();
+ }
+
+ return str;
+};
+
+exports.join = function join(base) {
+ if (arguments.length < 2)
+ throw new Error("need at least 2 args");
+ base = MozFile(base);
+ for (var i = 1; i < arguments.length; i++)
+ base.append(arguments[i]);
+ return base.path;
+};
+
+exports.dirname = function dirname(path) {
+ var parent = MozFile(path).parent;
+ return parent ? parent.path : "";
+};
+
+exports.basename = function basename(path) {
+ var leafName = MozFile(path).leafName;
+
+ // On Windows, leafName when the path is a volume letter and colon ("c:") is
+ // the path itself. But such a path has no basename, so we want the empty
+ // string.
+ return leafName == path ? "" : leafName;
+};
+
+exports.list = function list(path) {
+ var file = MozFile(path);
+ ensureDir(file);
+ ensureReadable(file);
+
+ var entries = file.directoryEntries;
+ var entryNames = [];
+ while(entries.hasMoreElements()) {
+ var entry = entries.getNext();
+ entry.QueryInterface(Ci.nsIFile);
+ entryNames.push(entry.leafName);
+ }
+ return entryNames;
+};
+
+exports.open = function open(filename, mode) {
+ var file = MozFile(filename);
+ if (typeof(mode) !== "string")
+ mode = "";
+
+ // File opened for write only.
+ if (/w/.test(mode)) {
+ if (file.exists())
+ ensureFile(file);
+ var stream = Cc['@mozilla.org/network/file-output-stream;1'].
+ createInstance(Ci.nsIFileOutputStream);
+ var openFlags = OPEN_FLAGS.WRONLY |
+ OPEN_FLAGS.CREATE_FILE |
+ OPEN_FLAGS.TRUNCATE;
+ var permFlags = 0o644; // u+rw go+r
+ try {
+ stream.init(file, openFlags, permFlags, 0);
+ }
+ catch (err) {
+ throw friendlyError(err, filename);
+ }
+ return /b/.test(mode) ?
+ new byteStreams.ByteWriter(stream) :
+ new textStreams.TextWriter(stream);
+ }
+
+ // File opened for read only, the default.
+ ensureFile(file);
+ stream = Cc['@mozilla.org/network/file-input-stream;1'].
+ createInstance(Ci.nsIFileInputStream);
+ try {
+ stream.init(file, OPEN_FLAGS.RDONLY, 0, 0);
+ }
+ catch (err) {
+ throw friendlyError(err, filename);
+ }
+ return /b/.test(mode) ?
+ new byteStreams.ByteReader(stream) :
+ new textStreams.TextReader(stream);
+};
+
+exports.remove = function remove(path) {
+ var file = MozFile(path);
+ ensureFile(file);
+ file.remove(false);
+};
+
+exports.mkpath = function mkpath(path) {
+ var file = MozFile(path);
+ if (!file.exists())
+ file.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); // u+rwx go+rx
+ else if (!file.isDirectory())
+ throw new Error("The path already exists and is not a directory: " + path);
+};
+
+exports.rmdir = function rmdir(path) {
+ var file = MozFile(path);
+ ensureDir(file);
+ try {
+ file.remove(false);
+ }
+ catch (err) {
+ // Bug 566950 explains why we're not catching a specific exception here.
+ throw new Error("The directory is not empty: " + path);
+ }
+};
diff --git a/components/jetpack/sdk/io/fs.js b/components/jetpack/sdk/io/fs.js
new file mode 100644
index 000000000..860a884a5
--- /dev/null
+++ b/components/jetpack/sdk/io/fs.js
@@ -0,0 +1,984 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci, CC } = require("chrome");
+
+const { setTimeout } = require("../timers");
+const { Stream, InputStream, OutputStream } = require("./stream");
+const { emit, on } = require("../event/core");
+const { Buffer } = require("./buffer");
+const { ns } = require("../core/namespace");
+const { Class } = require("../core/heritage");
+
+
+const nsILocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile",
+ "initWithPath");
+const FileOutputStream = CC("@mozilla.org/network/file-output-stream;1",
+ "nsIFileOutputStream", "init");
+const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
+ "nsIFileInputStream", "init");
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream", "setInputStream");
+const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream", "setOutputStream");
+const StreamPump = CC("@mozilla.org/network/input-stream-pump;1",
+ "nsIInputStreamPump", "init");
+
+const { createOutputTransport, createInputTransport } =
+ Cc["@mozilla.org/network/stream-transport-service;1"].
+ getService(Ci.nsIStreamTransportService);
+
+const { OPEN_UNBUFFERED } = Ci.nsITransport;
+
+
+const { REOPEN_ON_REWIND, DEFER_OPEN } = Ci.nsIFileInputStream;
+const { DIRECTORY_TYPE, NORMAL_FILE_TYPE } = Ci.nsIFile;
+const { NS_SEEK_SET, NS_SEEK_CUR, NS_SEEK_END } = Ci.nsISeekableStream;
+
+const FILE_PERMISSION = 0o666;
+const PR_UINT32_MAX = 0xfffffff;
+// Values taken from:
+// http://mxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prio.h#615
+const PR_RDONLY = 0x01;
+const PR_WRONLY = 0x02;
+const PR_RDWR = 0x04;
+const PR_CREATE_FILE = 0x08;
+const PR_APPEND = 0x10;
+const PR_TRUNCATE = 0x20;
+const PR_SYNC = 0x40;
+const PR_EXCL = 0x80;
+
+const FLAGS = {
+ "r": PR_RDONLY,
+ "r+": PR_RDWR,
+ "w": PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY,
+ "w+": PR_CREATE_FILE | PR_TRUNCATE | PR_RDWR,
+ "a": PR_APPEND | PR_CREATE_FILE | PR_WRONLY,
+ "a+": PR_APPEND | PR_CREATE_FILE | PR_RDWR
+};
+
+function accessor() {
+ let map = new WeakMap();
+ return function(fd, value) {
+ if (value === null) map.delete(fd);
+ if (value !== undefined) map.set(fd, value);
+ return map.get(fd);
+ }
+}
+
+var nsIFile = accessor();
+var nsIFileInputStream = accessor();
+var nsIFileOutputStream = accessor();
+var nsIBinaryInputStream = accessor();
+var nsIBinaryOutputStream = accessor();
+
+// Just a contstant object used to signal that all of the file
+// needs to be read.
+const ALL = new String("Read all of the file");
+
+function isWritable(mode) {
+ return !!(mode & PR_WRONLY || mode & PR_RDWR);
+}
+function isReadable(mode) {
+ return !!(mode & PR_RDONLY || mode & PR_RDWR);
+}
+
+function isString(value) {
+ return typeof(value) === "string";
+}
+function isFunction(value) {
+ return typeof(value) === "function";
+}
+
+function toArray(enumerator) {
+ let value = [];
+ while(enumerator.hasMoreElements())
+ value.push(enumerator.getNext())
+ return value
+}
+
+function getFileName(file) {
+ return file.QueryInterface(Ci.nsIFile).leafName;
+}
+
+
+function remove(path, recursive) {
+ let fd = new nsILocalFile(path)
+ if (fd.exists()) {
+ fd.remove(recursive || false);
+ }
+ else {
+ throw FSError("remove", "ENOENT", 34, path);
+ }
+}
+
+/**
+ * Utility function to convert either an octal number or string
+ * into an octal number
+ * 0777 => 0o777
+ * "0644" => 0o644
+ */
+function Mode(mode, fallback) {
+ return isString(mode) ? parseInt(mode, 8) : mode || fallback;
+}
+function Flags(flag) {
+ return !isString(flag) ? flag :
+ FLAGS[flag] || Error("Unknown file open flag: " + flag);
+}
+
+
+function FSError(op, code, errno, path, file, line) {
+ let error = Error(code + ", " + op + " " + path, file, line);
+ error.code = code;
+ error.path = path;
+ error.errno = errno;
+ return error;
+}
+
+const ReadStream = Class({
+ extends: InputStream,
+ initialize: function initialize(path, options) {
+ this.position = -1;
+ this.length = -1;
+ this.flags = "r";
+ this.mode = FILE_PERMISSION;
+ this.bufferSize = 64 * 1024;
+
+ options = options || {};
+
+ if ("flags" in options && options.flags)
+ this.flags = options.flags;
+ if ("bufferSize" in options && options.bufferSize)
+ this.bufferSize = options.bufferSize;
+ if ("length" in options && options.length)
+ this.length = options.length;
+ if ("position" in options && options.position !== undefined)
+ this.position = options.position;
+
+ let { flags, mode, position, length } = this;
+ let fd = isString(path) ? openSync(path, flags, mode) : path;
+ this.fd = fd;
+
+ let input = nsIFileInputStream(fd);
+ // Setting a stream position, unless it"s `-1` which means current position.
+ if (position >= 0)
+ input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
+ // We use `nsIStreamTransportService` service to transform blocking
+ // file input stream into a fully asynchronous stream that can be written
+ // without blocking the main thread.
+ let transport = createInputTransport(input, position, length, false);
+ // Open an input stream on a transport. We don"t pass flags to guarantee
+ // non-blocking stream semantics. Also we use defaults for segment size &
+ // count.
+ InputStream.prototype.initialize.call(this, {
+ asyncInputStream: transport.openInputStream(null, 0, 0)
+ });
+
+ // Close file descriptor on end and destroy the stream.
+ on(this, "end", _ => {
+ this.destroy();
+ emit(this, "close");
+ });
+
+ this.read();
+ },
+ destroy: function() {
+ closeSync(this.fd);
+ InputStream.prototype.destroy.call(this);
+ }
+});
+exports.ReadStream = ReadStream;
+exports.createReadStream = function createReadStream(path, options) {
+ return new ReadStream(path, options);
+};
+
+const WriteStream = Class({
+ extends: OutputStream,
+ initialize: function initialize(path, options) {
+ this.drainable = true;
+ this.flags = "w";
+ this.position = -1;
+ this.mode = FILE_PERMISSION;
+
+ options = options || {};
+
+ if ("flags" in options && options.flags)
+ this.flags = options.flags;
+ if ("mode" in options && options.mode)
+ this.mode = options.mode;
+ if ("position" in options && options.position !== undefined)
+ this.position = options.position;
+
+ let { position, flags, mode } = this;
+ // If pass was passed we create a file descriptor out of it. Otherwise
+ // we just use given file descriptor.
+ let fd = isString(path) ? openSync(path, flags, mode) : path;
+ this.fd = fd;
+
+ let output = nsIFileOutputStream(fd);
+ // Setting a stream position, unless it"s `-1` which means current position.
+ if (position >= 0)
+ output.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
+ // We use `nsIStreamTransportService` service to transform blocking
+ // file output stream into a fully asynchronous stream that can be written
+ // without blocking the main thread.
+ let transport = createOutputTransport(output, position, -1, false);
+ // Open an output stream on a transport. We don"t pass flags to guarantee
+ // non-blocking stream semantics. Also we use defaults for segment size &
+ // count.
+ OutputStream.prototype.initialize.call(this, {
+ asyncOutputStream: transport.openOutputStream(OPEN_UNBUFFERED, 0, 0),
+ output: output
+ });
+
+ // For write streams "finish" basically means close.
+ on(this, "finish", _ => {
+ this.destroy();
+ emit(this, "close");
+ });
+ },
+ destroy: function() {
+ OutputStream.prototype.destroy.call(this);
+ closeSync(this.fd);
+ }
+});
+exports.WriteStream = WriteStream;
+exports.createWriteStream = function createWriteStream(path, options) {
+ return new WriteStream(path, options);
+};
+
+const Stats = Class({
+ initialize: function initialize(path) {
+ let file = new nsILocalFile(path);
+ if (!file.exists()) throw FSError("stat", "ENOENT", 34, path);
+ nsIFile(this, file);
+ },
+ isDirectory: function() {
+ return nsIFile(this).isDirectory();
+ },
+ isFile: function() {
+ return nsIFile(this).isFile();
+ },
+ isSymbolicLink: function() {
+ return nsIFile(this).isSymlink();
+ },
+ get mode() {
+ return nsIFile(this).permissions;
+ },
+ get size() {
+ return nsIFile(this).fileSize;
+ },
+ get mtime() {
+ return nsIFile(this).lastModifiedTime;
+ },
+ isBlockDevice: function() {
+ return nsIFile(this).isSpecial();
+ },
+ isCharacterDevice: function() {
+ return nsIFile(this).isSpecial();
+ },
+ isFIFO: function() {
+ return nsIFile(this).isSpecial();
+ },
+ isSocket: function() {
+ return nsIFile(this).isSpecial();
+ },
+ // non standard
+ get exists() {
+ return nsIFile(this).exists();
+ },
+ get hidden() {
+ return nsIFile(this).isHidden();
+ },
+ get writable() {
+ return nsIFile(this).isWritable();
+ },
+ get readable() {
+ return nsIFile(this).isReadable();
+ }
+});
+exports.Stats = Stats;
+
+const LStats = Class({
+ extends: Stats,
+ get size() {
+ return this.isSymbolicLink() ? nsIFile(this).fileSizeOfLink :
+ nsIFile(this).fileSize;
+ },
+ get mtime() {
+ return this.isSymbolicLink() ? nsIFile(this).lastModifiedTimeOfLink :
+ nsIFile(this).lastModifiedTime;
+ },
+ // non standard
+ get permissions() {
+ return this.isSymbolicLink() ? nsIFile(this).permissionsOfLink :
+ nsIFile(this).permissions;
+ }
+});
+
+const FStat = Class({
+ extends: Stats,
+ initialize: function initialize(fd) {
+ nsIFile(this, nsIFile(fd));
+ }
+});
+
+function noop() {}
+function Async(wrapped) {
+ return function (path, callback) {
+ let args = Array.slice(arguments);
+ callback = args.pop();
+ // If node is not given a callback argument
+ // it just does not calls it.
+ if (typeof(callback) !== "function") {
+ args.push(callback);
+ callback = noop;
+ }
+ setTimeout(function() {
+ try {
+ var result = wrapped.apply(this, args);
+ if (result === undefined) callback(null);
+ else callback(null, result);
+ } catch (error) {
+ callback(error);
+ }
+ }, 0);
+ }
+}
+
+
+/**
+ * Synchronous rename(2)
+ */
+function renameSync(oldPath, newPath) {
+ let source = new nsILocalFile(oldPath);
+ let target = new nsILocalFile(newPath);
+ if (!source.exists()) throw FSError("rename", "ENOENT", 34, oldPath);
+ return source.moveTo(target.parent, target.leafName);
+};
+exports.renameSync = renameSync;
+
+/**
+ * Asynchronous rename(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+var rename = Async(renameSync);
+exports.rename = rename;
+
+/**
+ * Test whether or not the given path exists by checking with the file system.
+ */
+function existsSync(path) {
+ return new nsILocalFile(path).exists();
+}
+exports.existsSync = existsSync;
+
+var exists = Async(existsSync);
+exports.exists = exists;
+
+/**
+ * Synchronous ftruncate(2).
+ */
+function truncateSync(path, length) {
+ let fd = openSync(path, "w");
+ ftruncateSync(fd, length);
+ closeSync(fd);
+}
+exports.truncateSync = truncateSync;
+
+/**
+ * Asynchronous ftruncate(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+function truncate(path, length, callback) {
+ open(path, "w", function(error, fd) {
+ if (error) return callback(error);
+ ftruncate(fd, length, function(error) {
+ if (error) {
+ closeSync(fd);
+ callback(error);
+ }
+ else {
+ close(fd, callback);
+ }
+ });
+ });
+}
+exports.truncate = truncate;
+
+function ftruncate(fd, length, callback) {
+ write(fd, new Buffer(length), 0, length, 0, function(error) {
+ callback(error);
+ });
+}
+exports.ftruncate = ftruncate;
+
+function ftruncateSync(fd, length = 0) {
+ writeSync(fd, new Buffer(length), 0, length, 0);
+}
+exports.ftruncateSync = ftruncateSync;
+
+function chownSync(path, uid, gid) {
+ throw Error("Not implemented yet!!");
+}
+exports.chownSync = chownSync;
+
+var chown = Async(chownSync);
+exports.chown = chown;
+
+function lchownSync(path, uid, gid) {
+ throw Error("Not implemented yet!!");
+}
+exports.lchownSync = chownSync;
+
+var lchown = Async(lchown);
+exports.lchown = lchown;
+
+/**
+ * Synchronous chmod(2).
+ */
+function chmodSync (path, mode) {
+ let file;
+ try {
+ file = new nsILocalFile(path);
+ } catch(e) {
+ throw FSError("chmod", "ENOENT", 34, path);
+ }
+
+ file.permissions = Mode(mode);
+}
+exports.chmodSync = chmodSync;
+/**
+ * Asynchronous chmod(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+var chmod = Async(chmodSync);
+exports.chmod = chmod;
+
+/**
+ * Synchronous chmod(2).
+ */
+function fchmodSync(fd, mode) {
+ throw Error("Not implemented yet!!");
+};
+exports.fchmodSync = fchmodSync;
+/**
+ * Asynchronous chmod(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+var fchmod = Async(fchmodSync);
+exports.fchmod = fchmod;
+
+
+/**
+ * Synchronous stat(2). Returns an instance of `fs.Stats`
+ */
+function statSync(path) {
+ return new Stats(path);
+};
+exports.statSync = statSync;
+
+/**
+ * Asynchronous stat(2). The callback gets two arguments (err, stats) where
+ * stats is a `fs.Stats` object. It looks like this:
+ */
+var stat = Async(statSync);
+exports.stat = stat;
+
+/**
+ * Synchronous lstat(2). Returns an instance of `fs.Stats`.
+ */
+function lstatSync(path) {
+ return new LStats(path);
+};
+exports.lstatSync = lstatSync;
+
+/**
+ * Asynchronous lstat(2). The callback gets two arguments (err, stats) where
+ * stats is a fs.Stats object. lstat() is identical to stat(), except that if
+ * path is a symbolic link, then the link itself is stat-ed, not the file that
+ * it refers to.
+ */
+var lstat = Async(lstatSync);
+exports.lstat = lstat;
+
+/**
+ * Synchronous fstat(2). Returns an instance of `fs.Stats`.
+ */
+function fstatSync(fd) {
+ return new FStat(fd);
+};
+exports.fstatSync = fstatSync;
+
+/**
+ * Asynchronous fstat(2). The callback gets two arguments (err, stats) where
+ * stats is a fs.Stats object.
+ */
+var fstat = Async(fstatSync);
+exports.fstat = fstat;
+
+/**
+ * Synchronous link(2).
+ */
+function linkSync(source, target) {
+ throw Error("Not implemented yet!!");
+};
+exports.linkSync = linkSync;
+
+/**
+ * Asynchronous link(2). No arguments other than a possible exception are given
+ * to the completion callback.
+ */
+var link = Async(linkSync);
+exports.link = link;
+
+/**
+ * Synchronous symlink(2).
+ */
+function symlinkSync(source, target) {
+ throw Error("Not implemented yet!!");
+};
+exports.symlinkSync = symlinkSync;
+
+/**
+ * Asynchronous symlink(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+var symlink = Async(symlinkSync);
+exports.symlink = symlink;
+
+/**
+ * Synchronous readlink(2). Returns the resolved path.
+ */
+function readlinkSync(path) {
+ return new nsILocalFile(path).target;
+};
+exports.readlinkSync = readlinkSync;
+
+/**
+ * Asynchronous readlink(2). The callback gets two arguments
+ * `(error, resolvedPath)`.
+ */
+var readlink = Async(readlinkSync);
+exports.readlink = readlink;
+
+/**
+ * Synchronous realpath(2). Returns the resolved path.
+ */
+function realpathSync(path) {
+ return new nsILocalFile(path).path;
+};
+exports.realpathSync = realpathSync;
+
+/**
+ * Asynchronous realpath(2). The callback gets two arguments
+ * `(err, resolvedPath)`.
+ */
+var realpath = Async(realpathSync);
+exports.realpath = realpath;
+
+/**
+ * Synchronous unlink(2).
+ */
+var unlinkSync = remove;
+exports.unlinkSync = unlinkSync;
+
+/**
+ * Asynchronous unlink(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+var unlink = Async(remove);
+exports.unlink = unlink;
+
+/**
+ * Synchronous rmdir(2).
+ */
+var rmdirSync = remove;
+exports.rmdirSync = rmdirSync;
+
+/**
+ * Asynchronous rmdir(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+var rmdir = Async(rmdirSync);
+exports.rmdir = rmdir;
+
+/**
+ * Synchronous mkdir(2).
+ */
+function mkdirSync(path, mode) {
+ try {
+ return nsILocalFile(path).create(DIRECTORY_TYPE, Mode(mode));
+ } catch (error) {
+ // Adjust exception thorw to match ones thrown by node.
+ if (error.name === "NS_ERROR_FILE_ALREADY_EXISTS") {
+ let { fileName, lineNumber } = error;
+ error = FSError("mkdir", "EEXIST", 47, path, fileName, lineNumber);
+ }
+ throw error;
+ }
+};
+exports.mkdirSync = mkdirSync;
+
+/**
+ * Asynchronous mkdir(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+var mkdir = Async(mkdirSync);
+exports.mkdir = mkdir;
+
+/**
+ * Synchronous readdir(3). Returns an array of filenames excluding `"."` and
+ * `".."`.
+ */
+function readdirSync(path) {
+ try {
+ return toArray(new nsILocalFile(path).directoryEntries).map(getFileName);
+ }
+ catch (error) {
+ // Adjust exception thorw to match ones thrown by node.
+ if (error.name === "NS_ERROR_FILE_TARGET_DOES_NOT_EXIST" ||
+ error.name === "NS_ERROR_FILE_NOT_FOUND")
+ {
+ let { fileName, lineNumber } = error;
+ error = FSError("readdir", "ENOENT", 34, path, fileName, lineNumber);
+ }
+ throw error;
+ }
+};
+exports.readdirSync = readdirSync;
+
+/**
+ * Asynchronous readdir(3). Reads the contents of a directory. The callback
+ * gets two arguments `(error, files)` where `files` is an array of the names
+ * of the files in the directory excluding `"."` and `".."`.
+ */
+var readdir = Async(readdirSync);
+exports.readdir = readdir;
+
+/**
+ * Synchronous close(2).
+ */
+ function closeSync(fd) {
+ let input = nsIFileInputStream(fd);
+ let output = nsIFileOutputStream(fd);
+
+ // Closing input stream and removing reference.
+ if (input) input.close();
+ // Closing output stream and removing reference.
+ if (output) output.close();
+
+ nsIFile(fd, null);
+ nsIFileInputStream(fd, null);
+ nsIFileOutputStream(fd, null);
+ nsIBinaryInputStream(fd, null);
+ nsIBinaryOutputStream(fd, null);
+};
+exports.closeSync = closeSync;
+/**
+ * Asynchronous close(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+var close = Async(closeSync);
+exports.close = close;
+
+/**
+ * Synchronous open(2).
+ */
+function openSync(aPath, aFlag, aMode) {
+ let [ fd, flags, mode, file ] =
+ [ { path: aPath }, Flags(aFlag), Mode(aMode), nsILocalFile(aPath) ];
+
+ nsIFile(fd, file);
+
+ // If trying to open file for just read that does not exists
+ // need to throw exception as node does.
+ if (!file.exists() && !isWritable(flags))
+ throw FSError("open", "ENOENT", 34, aPath);
+
+ // If we want to open file in read mode we initialize input stream.
+ if (isReadable(flags)) {
+ let input = FileInputStream(file, flags, mode, DEFER_OPEN);
+ nsIFileInputStream(fd, input);
+ }
+
+ // If we want to open file in write mode we initialize output stream for it.
+ if (isWritable(flags)) {
+ let output = FileOutputStream(file, flags, mode, DEFER_OPEN);
+ nsIFileOutputStream(fd, output);
+ }
+
+ return fd;
+}
+exports.openSync = openSync;
+/**
+ * Asynchronous file open. See open(2). Flags can be
+ * `"r", "r+", "w", "w+", "a"`, or `"a+"`. mode defaults to `0666`.
+ * The callback gets two arguments `(error, fd).
+ */
+var open = Async(openSync);
+exports.open = open;
+
+/**
+ * Synchronous version of buffer-based fs.write(). Returns the number of bytes
+ * written.
+ */
+function writeSync(fd, buffer, offset, length, position) {
+ if (length + offset > buffer.length) {
+ throw Error("Length is extends beyond buffer");
+ }
+ else if (length + offset !== buffer.length) {
+ buffer = buffer.slice(offset, offset + length);
+ }
+
+ let output = BinaryOutputStream(nsIFileOutputStream(fd));
+ nsIBinaryOutputStream(fd, output);
+ // We write content as a byte array as this will avoid any transcoding
+ // if content was a buffer.
+ output.writeByteArray(buffer.valueOf(), buffer.length);
+ output.flush();
+};
+exports.writeSync = writeSync;
+
+/**
+ * Write buffer to the file specified by fd.
+ *
+ * `offset` and `length` determine the part of the buffer to be written.
+ *
+ * `position` refers to the offset from the beginning of the file where this
+ * data should be written. If `position` is `null`, the data will be written
+ * at the current position. See pwrite(2).
+ *
+ * The callback will be given three arguments `(error, written, buffer)` where
+ * written specifies how many bytes were written into buffer.
+ *
+ * Note that it is unsafe to use `fs.write` multiple times on the same file
+ * without waiting for the callback.
+ */
+function write(fd, buffer, offset, length, position, callback) {
+ if (!Buffer.isBuffer(buffer)) {
+ // (fd, data, position, encoding, callback)
+ let encoding = null;
+ [ position, encoding, callback ] = Array.slice(arguments, 1);
+ buffer = new Buffer(String(buffer), encoding);
+ offset = 0;
+ } else if (length + offset > buffer.length) {
+ throw Error("Length is extends beyond buffer");
+ } else if (length + offset !== buffer.length) {
+ buffer = buffer.slice(offset, offset + length);
+ }
+
+ let writeStream = new WriteStream(fd, { position: position,
+ length: length });
+ writeStream.on("error", callback);
+ writeStream.write(buffer, function onEnd() {
+ writeStream.destroy();
+ if (callback)
+ callback(null, buffer.length, buffer);
+ });
+};
+exports.write = write;
+
+/**
+ * Synchronous version of string-based fs.read. Returns the number of
+ * bytes read.
+ */
+function readSync(fd, buffer, offset, length, position) {
+ let input = nsIFileInputStream(fd);
+ // Setting a stream position, unless it"s `-1` which means current position.
+ if (position >= 0)
+ input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
+ // We use `nsIStreamTransportService` service to transform blocking
+ // file input stream into a fully asynchronous stream that can be written
+ // without blocking the main thread.
+ let binaryInputStream = BinaryInputStream(input);
+ let count = length === ALL ? binaryInputStream.available() : length;
+ if (offset === 0) binaryInputStream.readArrayBuffer(count, buffer.buffer);
+ else {
+ let chunk = new Buffer(count);
+ binaryInputStream.readArrayBuffer(count, chunk.buffer);
+ chunk.copy(buffer, offset);
+ }
+
+ return buffer.slice(offset, offset + count);
+};
+exports.readSync = readSync;
+
+/**
+ * Read data from the file specified by `fd`.
+ *
+ * `buffer` is the buffer that the data will be written to.
+ * `offset` is offset within the buffer where writing will start.
+ *
+ * `length` is an integer specifying the number of bytes to read.
+ *
+ * `position` is an integer specifying where to begin reading from in the file.
+ * If `position` is `null`, data will be read from the current file position.
+ *
+ * The callback is given the three arguments, `(error, bytesRead, buffer)`.
+ */
+function read(fd, buffer, offset, length, position, callback) {
+ let bytesRead = 0;
+ let readStream = new ReadStream(fd, { position: position, length: length });
+ readStream.on("data", function onData(data) {
+ data.copy(buffer, offset + bytesRead);
+ bytesRead += data.length;
+ });
+ readStream.on("end", function onEnd() {
+ callback(null, bytesRead, buffer);
+ readStream.destroy();
+ });
+};
+exports.read = read;
+
+/**
+ * Asynchronously reads the entire contents of a file.
+ * The callback is passed two arguments `(error, data)`, where data is the
+ * contents of the file.
+ */
+function readFile(path, encoding, callback) {
+ if (isFunction(encoding)) {
+ callback = encoding
+ encoding = null
+ }
+
+ let buffer = null;
+ try {
+ let readStream = new ReadStream(path);
+ readStream.on("data", function(data) {
+ if (!buffer) buffer = data;
+ else buffer = Buffer.concat([buffer, data], 2);
+ });
+ readStream.on("error", function onError(error) {
+ callback(error);
+ });
+ readStream.on("end", function onEnd() {
+ // Note: Need to destroy before invoking a callback
+ // so that file descriptor is released.
+ readStream.destroy();
+ callback(null, buffer);
+ });
+ }
+ catch (error) {
+ setTimeout(callback, 0, error);
+ }
+};
+exports.readFile = readFile;
+
+/**
+ * Synchronous version of `fs.readFile`. Returns the contents of the path.
+ * If encoding is specified then this function returns a string.
+ * Otherwise it returns a buffer.
+ */
+function readFileSync(path, encoding) {
+ let fd = openSync(path, "r");
+ let size = fstatSync(fd).size;
+ let buffer = new Buffer(size);
+ try {
+ readSync(fd, buffer, 0, ALL, 0);
+ }
+ finally {
+ closeSync(fd);
+ }
+ return buffer;
+};
+exports.readFileSync = readFileSync;
+
+/**
+ * Asynchronously writes data to a file, replacing the file if it already
+ * exists. data can be a string or a buffer.
+ */
+function writeFile(path, content, encoding, callback) {
+ if (!isString(path))
+ throw new TypeError('path must be a string');
+
+ try {
+ if (isFunction(encoding)) {
+ callback = encoding
+ encoding = null
+ }
+ if (isString(content))
+ content = new Buffer(content, encoding);
+
+ let writeStream = new WriteStream(path);
+ let error = null;
+
+ writeStream.end(content, function() {
+ writeStream.destroy();
+ callback(error);
+ });
+
+ writeStream.on("error", function onError(reason) {
+ error = reason;
+ writeStream.destroy();
+ });
+ } catch (error) {
+ callback(error);
+ }
+};
+exports.writeFile = writeFile;
+
+/**
+ * The synchronous version of `fs.writeFile`.
+ */
+function writeFileSync(filename, data, encoding) {
+ // TODO: Implement this in bug 1148209 https://bugzilla.mozilla.org/show_bug.cgi?id=1148209
+ throw Error("Not implemented");
+};
+exports.writeFileSync = writeFileSync;
+
+
+function utimesSync(path, atime, mtime) {
+ throw Error("Not implemented");
+}
+exports.utimesSync = utimesSync;
+
+var utimes = Async(utimesSync);
+exports.utimes = utimes;
+
+function futimesSync(fd, atime, mtime, callback) {
+ throw Error("Not implemented");
+}
+exports.futimesSync = futimesSync;
+
+var futimes = Async(futimesSync);
+exports.futimes = futimes;
+
+function fsyncSync(fd, atime, mtime, callback) {
+ throw Error("Not implemented");
+}
+exports.fsyncSync = fsyncSync;
+
+var fsync = Async(fsyncSync);
+exports.fsync = fsync;
+
+
+/**
+ * Watch for changes on filename. The callback listener will be called each
+ * time the file is accessed.
+ *
+ * The second argument is optional. The options if provided should be an object
+ * containing two members a boolean, persistent, and interval, a polling value
+ * in milliseconds. The default is { persistent: true, interval: 0 }.
+ */
+function watchFile(path, options, listener) {
+ throw Error("Not implemented");
+};
+exports.watchFile = watchFile;
+
+
+function unwatchFile(path, listener) {
+ throw Error("Not implemented");
+}
+exports.unwatchFile = unwatchFile;
+
+function watch(path, options, listener) {
+ throw Error("Not implemented");
+}
+exports.watch = watch;
diff --git a/components/jetpack/sdk/io/stream.js b/components/jetpack/sdk/io/stream.js
new file mode 100644
index 000000000..0698b8e32
--- /dev/null
+++ b/components/jetpack/sdk/io/stream.js
@@ -0,0 +1,440 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { CC, Cc, Ci, Cu, Cr, components } = require("chrome");
+const { EventTarget } = require("../event/target");
+const { emit } = require("../event/core");
+const { Buffer } = require("./buffer");
+const { Class } = require("../core/heritage");
+const { setTimeout } = require("../timers");
+
+
+const MultiplexInputStream = CC("@mozilla.org/io/multiplex-input-stream;1",
+ "nsIMultiplexInputStream");
+const AsyncStreamCopier = CC("@mozilla.org/network/async-stream-copier;1",
+ "nsIAsyncStreamCopier", "init");
+const StringInputStream = CC("@mozilla.org/io/string-input-stream;1",
+ "nsIStringInputStream");
+const ArrayBufferInputStream = CC("@mozilla.org/io/arraybuffer-input-stream;1",
+ "nsIArrayBufferInputStream");
+
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream", "setInputStream");
+const InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
+ "nsIInputStreamPump", "init");
+
+const threadManager = Cc["@mozilla.org/thread-manager;1"].
+ getService(Ci.nsIThreadManager);
+
+const eventTarget = Cc["@mozilla.org/network/stream-transport-service;1"].
+ getService(Ci.nsIEventTarget);
+
+var isFunction = value => typeof(value) === "function"
+
+function accessor() {
+ let map = new WeakMap();
+ return function(target, value) {
+ if (value)
+ map.set(target, value);
+ return map.get(target);
+ }
+}
+
+const Stream = Class({
+ extends: EventTarget,
+ initialize: function() {
+ this.readable = false;
+ this.writable = false;
+ this.encoding = null;
+ },
+ setEncoding: function setEncoding(encoding) {
+ this.encoding = String(encoding).toUpperCase();
+ },
+ pipe: function pipe(target, options) {
+ let source = this;
+ function onData(chunk) {
+ if (target.writable) {
+ if (false === target.write(chunk))
+ source.pause();
+ }
+ }
+ function onDrain() {
+ if (source.readable)
+ source.resume();
+ }
+ function onEnd() {
+ target.end();
+ }
+ function onPause() {
+ source.pause();
+ }
+ function onResume() {
+ if (source.readable)
+ source.resume();
+ }
+
+ function cleanup() {
+ source.removeListener("data", onData);
+ target.removeListener("drain", onDrain);
+ source.removeListener("end", onEnd);
+
+ target.removeListener("pause", onPause);
+ target.removeListener("resume", onResume);
+
+ source.removeListener("end", cleanup);
+ source.removeListener("close", cleanup);
+
+ target.removeListener("end", cleanup);
+ target.removeListener("close", cleanup);
+ }
+
+ if (!options || options.end !== false)
+ target.on("end", onEnd);
+
+ source.on("data", onData);
+ target.on("drain", onDrain);
+ target.on("resume", onResume);
+ target.on("pause", onPause);
+
+ source.on("end", cleanup);
+ source.on("close", cleanup);
+
+ target.on("end", cleanup);
+ target.on("close", cleanup);
+
+ emit(target, "pipe", source);
+ },
+ pause: function pause() {
+ emit(this, "pause");
+ },
+ resume: function resume() {
+ emit(this, "resume");
+ },
+ destroySoon: function destroySoon() {
+ this.destroy();
+ }
+});
+exports.Stream = Stream;
+
+
+var nsIStreamListener = accessor();
+var nsIInputStreamPump = accessor();
+var nsIAsyncInputStream = accessor();
+var nsIBinaryInputStream = accessor();
+
+const StreamListener = Class({
+ initialize: function(stream) {
+ this.stream = stream;
+ },
+
+ // Next three methods are part of `nsIStreamListener` interface and are
+ // invoked by `nsIInputStreamPump.asyncRead`.
+ onDataAvailable: function(request, context, input, offset, count) {
+ let stream = this.stream;
+ let buffer = new ArrayBuffer(count);
+ nsIBinaryInputStream(stream).readArrayBuffer(count, buffer);
+ emit(stream, "data", new Buffer(buffer));
+ },
+
+ // Next two methods implement `nsIRequestObserver` interface and are invoked
+ // by `nsIInputStreamPump.asyncRead`.
+ onStartRequest: function() {},
+ // Called to signify the end of an asynchronous request. We only care to
+ // discover errors.
+ onStopRequest: function(request, context, status) {
+ let stream = this.stream;
+ stream.readable = false;
+ if (!components.isSuccessCode(status))
+ emit(stream, "error", status);
+ else
+ emit(stream, "end");
+ }
+});
+
+
+const InputStream = Class({
+ extends: Stream,
+ readable: false,
+ paused: false,
+ initialize: function initialize(options) {
+ let { asyncInputStream } = options;
+
+ this.readable = true;
+
+ let binaryInputStream = new BinaryInputStream(asyncInputStream);
+ let inputStreamPump = new InputStreamPump(asyncInputStream,
+ -1, -1, 0, 0, false);
+ let streamListener = new StreamListener(this);
+
+ nsIAsyncInputStream(this, asyncInputStream);
+ nsIInputStreamPump(this, inputStreamPump);
+ nsIBinaryInputStream(this, binaryInputStream);
+ nsIStreamListener(this, streamListener);
+
+ this.asyncInputStream = asyncInputStream;
+ this.inputStreamPump = inputStreamPump;
+ this.binaryInputStream = binaryInputStream;
+ },
+ get status() {
+ return nsIInputStreamPump(this).status;
+ },
+ read: function() {
+ nsIInputStreamPump(this).asyncRead(nsIStreamListener(this), null);
+ },
+ pause: function pause() {
+ this.paused = true;
+ nsIInputStreamPump(this).suspend();
+ emit(this, "paused");
+ },
+ resume: function resume() {
+ this.paused = false;
+ if (nsIInputStreamPump(this).isPending()) {
+ nsIInputStreamPump(this).resume();
+ emit(this, "resume");
+ }
+ },
+ close: function close() {
+ this.readable = false;
+ nsIInputStreamPump(this).cancel(Cr.NS_OK);
+ nsIBinaryInputStream(this).close();
+ nsIAsyncInputStream(this).close();
+ },
+ destroy: function destroy() {
+ this.close();
+
+ nsIInputStreamPump(this);
+ nsIAsyncInputStream(this);
+ nsIBinaryInputStream(this);
+ nsIStreamListener(this);
+ }
+});
+exports.InputStream = InputStream;
+
+
+
+var nsIRequestObserver = accessor();
+var nsIAsyncOutputStream = accessor();
+var nsIAsyncStreamCopier = accessor();
+var nsIMultiplexInputStream = accessor();
+
+const RequestObserver = Class({
+ initialize: function(stream) {
+ this.stream = stream;
+ },
+ // Method is part of `nsIRequestObserver` interface that is
+ // invoked by `nsIAsyncStreamCopier.asyncCopy`.
+ onStartRequest: function() {},
+ // Method is part of `nsIRequestObserver` interface that is
+ // invoked by `nsIAsyncStreamCopier.asyncCopy`.
+ onStopRequest: function(request, context, status) {
+ let stream = this.stream;
+ stream.drained = true;
+
+ // Remove copied chunk.
+ let multiplexInputStream = nsIMultiplexInputStream(stream);
+ multiplexInputStream.removeStream(0);
+
+ // If there was an error report.
+ if (!components.isSuccessCode(status))
+ emit(stream, "error", status);
+
+ // If there more chunks in queue then flush them.
+ else if (multiplexInputStream.count)
+ stream.flush();
+
+ // If stream is still writable notify that queue has drained.
+ else if (stream.writable)
+ emit(stream, "drain");
+
+ // If stream is no longer writable close it.
+ else {
+ nsIAsyncStreamCopier(stream).cancel(Cr.NS_OK);
+ nsIMultiplexInputStream(stream).close();
+ nsIAsyncOutputStream(stream).close();
+ nsIAsyncOutputStream(stream).flush();
+ }
+ }
+});
+
+const OutputStreamCallback = Class({
+ initialize: function(stream) {
+ this.stream = stream;
+ },
+ // Method is part of `nsIOutputStreamCallback` interface that
+ // is invoked by `nsIAsyncOutputStream.asyncWait`. It is registered
+ // with `WAIT_CLOSURE_ONLY` flag that overrides the default behavior,
+ // causing the `onOutputStreamReady` notification to be suppressed until
+ // the stream becomes closed.
+ onOutputStreamReady: function(nsIAsyncOutputStream) {
+ emit(this.stream, "finish");
+ }
+});
+
+const OutputStream = Class({
+ extends: Stream,
+ writable: false,
+ drained: true,
+ get bufferSize() {
+ let multiplexInputStream = nsIMultiplexInputStream(this);
+ return multiplexInputStream && multiplexInputStream.available();
+ },
+ initialize: function initialize(options) {
+ let { asyncOutputStream, output } = options;
+ this.writable = true;
+
+ // Ensure that `nsIAsyncOutputStream` was provided.
+ asyncOutputStream.QueryInterface(Ci.nsIAsyncOutputStream);
+
+ // Create a `nsIMultiplexInputStream` and `nsIAsyncStreamCopier`. Former
+ // is used to queue written data chunks that `asyncStreamCopier` will
+ // asynchronously drain into `asyncOutputStream`.
+ let multiplexInputStream = MultiplexInputStream();
+ let asyncStreamCopier = AsyncStreamCopier(multiplexInputStream,
+ output || asyncOutputStream,
+ eventTarget,
+ // nsIMultiplexInputStream
+ // implemnts .readSegments()
+ true,
+ // nsIOutputStream may or
+ // may not implemnet
+ // .writeSegments().
+ false,
+ // Use default buffer size.
+ null,
+ // Should not close an input.
+ false,
+ // Should not close an output.
+ false);
+
+ // Create `requestObserver` implementing `nsIRequestObserver` interface
+ // in the constructor that's gonna be reused across several flushes.
+ let requestObserver = RequestObserver(this);
+
+
+ // Create observer that implements `nsIOutputStreamCallback` and register
+ // using `WAIT_CLOSURE_ONLY` flag. That way it will be notfied once
+ // `nsIAsyncOutputStream` is closed.
+ asyncOutputStream.asyncWait(OutputStreamCallback(this),
+ asyncOutputStream.WAIT_CLOSURE_ONLY,
+ 0,
+ threadManager.currentThread);
+
+ nsIRequestObserver(this, requestObserver);
+ nsIAsyncOutputStream(this, asyncOutputStream);
+ nsIMultiplexInputStream(this, multiplexInputStream);
+ nsIAsyncStreamCopier(this, asyncStreamCopier);
+
+ this.asyncOutputStream = asyncOutputStream;
+ this.multiplexInputStream = multiplexInputStream;
+ this.asyncStreamCopier = asyncStreamCopier;
+ },
+ write: function write(content, encoding, callback) {
+ if (isFunction(encoding)) {
+ callback = encoding;
+ encoding = callback;
+ }
+
+ // If stream is not writable we throw an error.
+ if (!this.writable) throw Error("stream is not writable");
+
+ let chunk = null;
+
+ // If content is not a buffer then we create one out of it.
+ if (Buffer.isBuffer(content)) {
+ chunk = new ArrayBufferInputStream();
+ chunk.setData(content.buffer, 0, content.length);
+ }
+ else {
+ chunk = new StringInputStream();
+ chunk.setData(content, content.length);
+ }
+
+ if (callback)
+ this.once("drain", callback);
+
+ // Queue up chunk to be copied to output sync.
+ nsIMultiplexInputStream(this).appendStream(chunk);
+ this.flush();
+
+ return this.drained;
+ },
+ flush: function() {
+ if (this.drained) {
+ this.drained = false;
+ nsIAsyncStreamCopier(this).asyncCopy(nsIRequestObserver(this), null);
+ }
+ },
+ end: function end(content, encoding, callback) {
+ if (isFunction(content)) {
+ callback = content
+ content = callback
+ }
+ if (isFunction(encoding)) {
+ callback = encoding
+ encoding = callback
+ }
+
+ // Setting a listener to "finish" event if passed.
+ if (isFunction(callback))
+ this.once("finish", callback);
+
+
+ if (content)
+ this.write(content, encoding);
+ this.writable = false;
+
+ // Close `asyncOutputStream` only if output has drained. If it's
+ // not drained than `asyncStreamCopier` is busy writing, so let
+ // it finish. Note that since `this.writable` is false copier will
+ // close `asyncOutputStream` once output drains.
+ if (this.drained)
+ nsIAsyncOutputStream(this).close();
+ },
+ destroy: function destroy() {
+ nsIAsyncOutputStream(this).close();
+ nsIAsyncOutputStream(this);
+ nsIMultiplexInputStream(this);
+ nsIAsyncStreamCopier(this);
+ nsIRequestObserver(this);
+ }
+});
+exports.OutputStream = OutputStream;
+
+const DuplexStream = Class({
+ extends: Stream,
+ implements: [InputStream, OutputStream],
+ allowHalfOpen: true,
+ initialize: function initialize(options) {
+ options = options || {};
+ let { readable, writable, allowHalfOpen } = options;
+
+ InputStream.prototype.initialize.call(this, options);
+ OutputStream.prototype.initialize.call(this, options);
+
+ if (readable === false)
+ this.readable = false;
+
+ if (writable === false)
+ this.writable = false;
+
+ if (allowHalfOpen === false)
+ this.allowHalfOpen = false;
+
+ // If in a half open state and it's disabled enforce end.
+ this.once("end", () => {
+ if (!this.allowHalfOpen && (!this.readable || !this.writable))
+ this.end();
+ });
+ },
+ destroy: function destroy(error) {
+ InputStream.prototype.destroy.call(this);
+ OutputStream.prototype.destroy.call(this);
+ }
+});
+exports.DuplexStream = DuplexStream;
diff --git a/components/jetpack/sdk/io/text-streams.js b/components/jetpack/sdk/io/text-streams.js
new file mode 100644
index 000000000..ed4ec4972
--- /dev/null
+++ b/components/jetpack/sdk/io/text-streams.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci, Cu, components } = require("chrome");
+const { ensure } = require("../system/unload");
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+
+// NetUtil.asyncCopy() uses this buffer length, and since we call it, for best
+// performance we use it, too.
+const BUFFER_BYTE_LEN = 0x8000;
+const PR_UINT32_MAX = 0xffffffff;
+const DEFAULT_CHARSET = "UTF-8";
+
+
+/**
+ * An input stream that reads text from a backing stream using a given text
+ * encoding.
+ *
+ * @param inputStream
+ * The stream is backed by this nsIInputStream. It must already be
+ * opened.
+ * @param charset
+ * Text in inputStream is expected to be in this character encoding. If
+ * not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl for
+ * documentation on how to determine other valid values for this.
+ */
+function TextReader(inputStream, charset) {
+ charset = checkCharset(charset);
+
+ let stream = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ stream.init(inputStream, charset, BUFFER_BYTE_LEN,
+ Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+
+ let manager = new StreamManager(this, stream);
+
+ /**
+ * Reads a string from the stream. If the stream is closed, an exception is
+ * thrown.
+ *
+ * @param numChars
+ * The number of characters to read. If not given, the remainder of
+ * the stream is read.
+ * @return The string read. If the stream is already at EOS, returns the
+ * empty string.
+ */
+ this.read = function TextReader_read(numChars) {
+ manager.ensureOpened();
+
+ let readAll = false;
+ if (typeof(numChars) === "number")
+ numChars = Math.max(numChars, 0);
+ else
+ readAll = true;
+
+ let str = "";
+ let totalRead = 0;
+ let chunkRead = 1;
+
+ // Read in numChars or until EOS, whichever comes first. Note that the
+ // units here are characters, not bytes.
+ while (true) {
+ let chunk = {};
+ let toRead = readAll ?
+ PR_UINT32_MAX :
+ Math.min(numChars - totalRead, PR_UINT32_MAX);
+ if (toRead <= 0 || chunkRead <= 0)
+ break;
+
+ // The converter stream reads in at most BUFFER_BYTE_LEN bytes in a call
+ // to readString, enough to fill its byte buffer. chunkRead will be the
+ // number of characters encoded by the bytes in that buffer.
+ chunkRead = stream.readString(toRead, chunk);
+ str += chunk.value;
+ totalRead += chunkRead;
+ }
+
+ return str;
+ };
+}
+exports.TextReader = TextReader;
+
+/**
+ * A buffered output stream that writes text to a backing stream using a given
+ * text encoding.
+ *
+ * @param outputStream
+ * The stream is backed by this nsIOutputStream. It must already be
+ * opened.
+ * @param charset
+ * Text will be written to outputStream using this character encoding.
+ * If not given, "UTF-8" is assumed. See nsICharsetConverterManager.idl
+ * for documentation on how to determine other valid values for this.
+ */
+function TextWriter(outputStream, charset) {
+ charset = checkCharset(charset);
+
+ let stream = outputStream;
+
+ // Buffer outputStream if it's not already.
+ let ioUtils = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
+ if (!ioUtils.outputStreamIsBuffered(outputStream)) {
+ stream = Cc["@mozilla.org/network/buffered-output-stream;1"].
+ createInstance(Ci.nsIBufferedOutputStream);
+ stream.init(outputStream, BUFFER_BYTE_LEN);
+ }
+
+ // I'd like to use nsIConverterOutputStream. But NetUtil.asyncCopy(), which
+ // we use below in writeAsync(), naturally expects its sink to be an instance
+ // of nsIOutputStream, which nsIConverterOutputStream's only implementation is
+ // not. So we use uconv and manually convert all strings before writing to
+ // outputStream.
+ let uconv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ uconv.charset = charset;
+
+ let manager = new StreamManager(this, stream);
+
+ /**
+ * Flushes the backing stream's buffer.
+ */
+ this.flush = function TextWriter_flush() {
+ manager.ensureOpened();
+ stream.flush();
+ };
+
+ /**
+ * Writes a string to the stream. If the stream is closed, an exception is
+ * thrown.
+ *
+ * @param str
+ * The string to write.
+ */
+ this.write = function TextWriter_write(str) {
+ manager.ensureOpened();
+ let istream = uconv.convertToInputStream(str);
+ let len = istream.available();
+ while (len > 0) {
+ stream.writeFrom(istream, len);
+ len = istream.available();
+ }
+ istream.close();
+ };
+
+ /**
+ * Writes a string on a background thread. After the write completes, the
+ * backing stream's buffer is flushed, and both the stream and the backing
+ * stream are closed, also on the background thread. If the stream is already
+ * closed, an exception is thrown immediately.
+ *
+ * @param str
+ * The string to write.
+ * @param callback
+ * An optional function. If given, it's called as callback(error) when
+ * the write completes. error is an Error object or undefined if there
+ * was no error. Inside callback, |this| is the stream object.
+ */
+ this.writeAsync = function TextWriter_writeAsync(str, callback) {
+ manager.ensureOpened();
+ let istream = uconv.convertToInputStream(str);
+ NetUtil.asyncCopy(istream, stream, (result) => {
+ let err = components.isSuccessCode(result) ? undefined :
+ new Error("An error occured while writing to the stream: " + result);
+ if (err)
+ console.error(err);
+
+ // asyncCopy() closes its output (and input) stream.
+ manager.opened = false;
+
+ if (typeof(callback) === "function") {
+ try {
+ callback.call(this, err);
+ }
+ catch (exc) {
+ console.exception(exc);
+ }
+ }
+ });
+ };
+}
+exports.TextWriter = TextWriter;
+
+// This manages the lifetime of stream, a TextReader or TextWriter. It defines
+// closed and close() on stream and registers an unload listener that closes
+// rawStream if it's still opened. It also provides ensureOpened(), which
+// throws an exception if the stream is closed.
+function StreamManager(stream, rawStream) {
+ this.rawStream = rawStream;
+ this.opened = true;
+
+ /**
+ * True iff the stream is closed.
+ */
+ stream.__defineGetter__("closed", () => !this.opened);
+
+ /**
+ * Closes both the stream and its backing stream. If the stream is already
+ * closed, an exception is thrown. For TextWriters, this first flushes the
+ * backing stream's buffer.
+ */
+ stream.close = () => {
+ this.ensureOpened();
+ this.unload();
+ };
+
+ ensure(this);
+}
+
+StreamManager.prototype = {
+ ensureOpened: function StreamManager_ensureOpened() {
+ if (!this.opened)
+ throw new Error("The stream is closed and cannot be used.");
+ },
+ unload: function StreamManager_unload() {
+ // TextWriter.writeAsync() causes rawStream to close and therefore sets
+ // opened to false, so check that we're still opened.
+ if (this.opened) {
+ // Calling close() on both an nsIUnicharInputStream and
+ // nsIBufferedOutputStream closes their backing streams. It also forces
+ // nsIOutputStreams to flush first.
+ this.rawStream.close();
+ this.opened = false;
+ }
+ }
+};
+
+function checkCharset(charset) {
+ return typeof(charset) === "string" ? charset : DEFAULT_CHARSET;
+}
diff --git a/components/jetpack/sdk/keyboard/hotkeys.js b/components/jetpack/sdk/keyboard/hotkeys.js
new file mode 100644
index 000000000..a179502b8
--- /dev/null
+++ b/components/jetpack/sdk/keyboard/hotkeys.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { observer: keyboardObserver } = require("./observer");
+const { getKeyForCode, normalize, isFunctionKey,
+ MODIFIERS } = require("./utils");
+
+/**
+ * Register a global `hotkey` that executes `listener` when the key combination
+ * in `hotkey` is pressed. If more then one `listener` is registered on the same
+ * key combination only last one will be executed.
+ *
+ * @param {string} hotkey
+ * Key combination in the format of 'modifier key'.
+ *
+ * Examples:
+ *
+ * "accel s"
+ * "meta shift i"
+ * "control alt d"
+ *
+ * Modifier keynames:
+ *
+ * - **shift**: The Shift key.
+ * - **alt**: The Alt key. On the Macintosh, this is the Option key. On
+ * Macintosh this can only be used in conjunction with another modifier,
+ * since `Alt+Letter` combinations are reserved for entering special
+ * characters in text.
+ * - **meta**: The Meta key. On the Macintosh, this is the Command key.
+ * - **control**: The Control key.
+ * - **accel**: The key used for keyboard shortcuts on the user's platform,
+ * which is Control on Windows and Linux, and Command on Mac. Usually, this
+ * would be the value you would use.
+ *
+ * @param {function} listener
+ * Function to execute when the `hotkey` is executed.
+ */
+exports.register = function register(hotkey, listener) {
+ hotkey = normalize(hotkey);
+ hotkeys[hotkey] = listener;
+};
+
+/**
+ * Unregister a global `hotkey`. If passed `listener` is not the one registered
+ * for the given `hotkey`, the call to this function will be ignored.
+ *
+ * @param {string} hotkey
+ * Key combination in the format of 'modifier key'.
+ * @param {function} listener
+ * Function that will be invoked when the `hotkey` is pressed.
+ */
+exports.unregister = function unregister(hotkey, listener) {
+ hotkey = normalize(hotkey);
+ if (hotkeys[hotkey] === listener)
+ delete hotkeys[hotkey];
+};
+
+/**
+ * Map of hotkeys and associated functions.
+ */
+const hotkeys = exports.hotkeys = {};
+
+keyboardObserver.on("keydown", function onKeypress(event, window) {
+ let key, modifiers = [];
+ let isChar = "isChar" in event && event.isChar;
+ let which = "which" in event ? event.which : null;
+ let keyCode = "keyCode" in event ? event.keyCode : null;
+
+ if ("shiftKey" in event && event.shiftKey)
+ modifiers.push("shift");
+ if ("altKey" in event && event.altKey)
+ modifiers.push("alt");
+ if ("ctrlKey" in event && event.ctrlKey)
+ modifiers.push("control");
+ if ("metaKey" in event && event.metaKey)
+ modifiers.push("meta");
+
+ // If it's not a printable character then we fall back to a human readable
+ // equivalent of one of the following constants.
+ // http://dxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl
+ key = getKeyForCode(keyCode);
+
+ // If only non-function (f1 - f24) key or only modifiers are pressed we don't
+ // have a valid combination so we return immediately (Also, sometimes
+ // `keyCode` may be one for the modifier which means we do not have a
+ // modifier).
+ if (!key || (!isFunctionKey(key) && !modifiers.length) || key in MODIFIERS)
+ return;
+
+ let combination = normalize({ key: key, modifiers: modifiers });
+ let hotkey = hotkeys[combination];
+
+ if (hotkey) {
+ try {
+ hotkey();
+ } catch (exception) {
+ console.exception(exception);
+ } finally {
+ // Work around bug 582052 by preventing the (nonexistent) default action.
+ event.preventDefault();
+ }
+ }
+});
diff --git a/components/jetpack/sdk/keyboard/observer.js b/components/jetpack/sdk/keyboard/observer.js
new file mode 100644
index 000000000..b8e32b95c
--- /dev/null
+++ b/components/jetpack/sdk/keyboard/observer.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Class } = require("../core/heritage");
+const { EventTarget } = require("../event/target");
+const { emit } = require("../event/core");
+const { DOMEventAssembler } = require("../deprecated/events/assembler");
+const { browserWindowIterator } = require('../deprecated/window-utils');
+const { isBrowser } = require('../window/utils');
+const { observer: windowObserver } = require("../windows/observer");
+
+// Event emitter objects used to register listeners and emit events on them
+// when they occur.
+const Observer = Class({
+ implements: [DOMEventAssembler, EventTarget],
+ initialize() {
+ // Adding each opened window to a list of observed windows.
+ windowObserver.on("open", window => {
+ if (isBrowser(window))
+ this.observe(window);
+ });
+
+ // Removing each closed window form the list of observed windows.
+ windowObserver.on("close", window => {
+ if (isBrowser(window))
+ this.ignore(window);
+ });
+
+ // Making observer aware of already opened windows.
+ for (let window of browserWindowIterator()) {
+ this.observe(window);
+ }
+ },
+ /**
+ * Events that are supported and emitted by the module.
+ */
+ supportedEventsTypes: [ "keydown", "keyup", "keypress" ],
+ /**
+ * Function handles all the supported events on all the windows that are
+ * observed. Method is used to proxy events to the listeners registered on
+ * this event emitter.
+ * @param {Event} event
+ * Keyboard event being emitted.
+ */
+ handleEvent(event) {
+ emit(this, event.type, event, event.target.ownerDocument ? event.target.ownerDocument.defaultView
+ : undefined);
+ }
+});
+
+exports.observer = new Observer();
diff --git a/components/jetpack/sdk/keyboard/utils.js b/components/jetpack/sdk/keyboard/utils.js
new file mode 100644
index 000000000..1b7df4ce3
--- /dev/null
+++ b/components/jetpack/sdk/keyboard/utils.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci } = require("chrome");
+const runtime = require("../system/runtime");
+const { isString } = require("../lang/type");
+const array = require("../util/array");
+
+
+const SWP = "{{SEPARATOR}}";
+const SEPARATOR = "-"
+const INVALID_COMBINATION = "Hotkey key combination must contain one or more " +
+ "modifiers and only one key";
+
+// Map of modifier key mappings.
+const MODIFIERS = exports.MODIFIERS = {
+ 'accel': runtime.OS === "Darwin" ? 'meta' : 'control',
+ 'meta': 'meta',
+ 'control': 'control',
+ 'ctrl': 'control',
+ 'option': 'alt',
+ 'command': 'meta',
+ 'alt': 'alt',
+ 'shift': 'shift'
+};
+
+// Hash of key:code pairs for all the chars supported by `nsIDOMKeyEvent`.
+// This is just a copy of the `nsIDOMKeyEvent` hash with normalized names.
+// @See: http://dxr.mozilla.org/mozilla-central/source/dom/interfaces/events/nsIDOMKeyEvent.idl
+const CODES = exports.CODES = new function Codes() {
+ let nsIDOMKeyEvent = Ci.nsIDOMKeyEvent;
+ // Names that will be substituted with a shorter analogs.
+ let aliases = {
+ 'subtract': '-',
+ 'add': '+',
+ 'equals': '=',
+ 'slash': '/',
+ 'backslash': '\\',
+ 'openbracket': '[',
+ 'closebracket': ']',
+ 'quote': '\'',
+ 'backquote': '`',
+ 'period': '.',
+ 'semicolon': ';',
+ 'comma': ','
+ };
+
+ // Normalizing keys and copying values to `this` object.
+ Object.keys(nsIDOMKeyEvent).filter(function(key) {
+ // Filter out only key codes.
+ return key.indexOf('DOM_VK') === 0;
+ }).map(function(key) {
+ // Map to key:values
+ return [ key, nsIDOMKeyEvent[key] ];
+ }).map(function([key, value]) {
+ return [ key.replace('DOM_VK_', '').replace('_', '').toLowerCase(), value ];
+ }).forEach(function ([ key, value ]) {
+ this[aliases[key] || key] = value;
+ }, this);
+};
+
+// Inverted `CODES` hash of `code:key`.
+const KEYS = exports.KEYS = new function Keys() {
+ Object.keys(CODES).forEach(function(key) {
+ this[CODES[key]] = key;
+ }, this)
+}
+
+exports.getKeyForCode = function getKeyForCode(code) {
+ return (code in KEYS) && KEYS[code];
+};
+exports.getCodeForKey = function getCodeForKey(key) {
+ return (key in CODES) && CODES[key];
+};
+
+/**
+ * Utility function that takes string or JSON that defines a `hotkey` and
+ * returns normalized string version of it.
+ * @param {JSON|String} hotkey
+ * @param {String} [separator=" "]
+ * Optional string that represents separator used to concatenate keys in the
+ * given `hotkey`.
+ * @returns {String}
+ * @examples
+ *
+ * require("keyboard/hotkeys").normalize("b Shift accel");
+ * // 'control shift b' -> on windows & linux
+ * // 'meta shift b' -> on mac
+ * require("keyboard/hotkeys").normalize("alt-d-shift", "-");
+ * // 'alt shift d'
+ */
+var normalize = exports.normalize = function normalize(hotkey, separator) {
+ if (!isString(hotkey))
+ hotkey = toString(hotkey, separator);
+ return toString(toJSON(hotkey, separator), separator);
+};
+
+/*
+ * Utility function that splits a string of characters that defines a `hotkey`
+ * into modifier keys and the defining key.
+ * @param {String} hotkey
+ * @param {String} [separator=" "]
+ * Optional string that represents separator used to concatenate keys in the
+ * given `hotkey`.
+ * @returns {JSON}
+ * @examples
+ *
+ * require("keyboard/hotkeys").toJSON("accel shift b");
+ * // { key: 'b', modifiers: [ 'control', 'shift' ] } -> on windows & linux
+ * // { key: 'b', modifiers: [ 'meta', 'shift' ] } -> on mac
+ *
+ * require("keyboard/hotkeys").normalize("alt-d-shift", "-");
+ * // { key: 'd', modifiers: [ 'alt', 'shift' ] }
+ */
+var toJSON = exports.toJSON = function toJSON(hotkey, separator) {
+ separator = separator || SEPARATOR;
+ // Since default separator is `-`, combination may take form of `alt--`. To
+ // avoid misbehavior we replace `--` with `-{{SEPARATOR}}` where
+ // `{{SEPARATOR}}` can be swapped later.
+ hotkey = hotkey.toLowerCase().replace(separator + separator, separator + SWP);
+
+ let value = {};
+ let modifiers = [];
+ let keys = hotkey.split(separator);
+ keys.forEach(function(name) {
+ // If name is `SEPARATOR` than we swap it back.
+ if (name === SWP)
+ name = separator;
+ if (name in MODIFIERS) {
+ array.add(modifiers, MODIFIERS[name]);
+ } else {
+ if (!value.key)
+ value.key = name;
+ else
+ throw new TypeError(INVALID_COMBINATION);
+ }
+ });
+
+ if (!value.key)
+ throw new TypeError(INVALID_COMBINATION);
+
+ value.modifiers = modifiers.sort();
+ return value;
+};
+
+/**
+ * Utility function that takes object that defines a `hotkey` and returns
+ * string representation of it.
+ *
+ * _Please note that this function does not validates data neither it normalizes
+ * it, if you are unsure that data is well formed use `normalize` function
+ * instead.
+ *
+ * @param {JSON} hotkey
+ * @param {String} [separator=" "]
+ * Optional string that represents separator used to concatenate keys in the
+ * given `hotkey`.
+ * @returns {String}
+ * @examples
+ *
+ * require("keyboard/hotkeys").toString({
+ * key: 'b',
+ * modifiers: [ 'control', 'shift' ]
+ * }, '+');
+ * // 'control+shift+b
+ *
+ */
+var toString = exports.toString = function toString(hotkey, separator) {
+ let keys = hotkey.modifiers.slice();
+ keys.push(hotkey.key);
+ return keys.join(separator || SEPARATOR);
+};
+
+/**
+ * Utility function takes `key` name and returns `true` if it's function key
+ * (F1, ..., F24) and `false` if it's not.
+ */
+var isFunctionKey = exports.isFunctionKey = function isFunctionKey(key) {
+ var $
+ return key[0].toLowerCase() === 'f' &&
+ ($ = parseInt(key.substr(1)), 0 < $ && $ < 25);
+};
diff --git a/components/jetpack/sdk/l10n.js b/components/jetpack/sdk/l10n.js
new file mode 100644
index 000000000..db5a9d7b6
--- /dev/null
+++ b/components/jetpack/sdk/l10n.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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const json = require("./l10n/json/core");
+const { get: getKey } = require("./l10n/core");
+const properties = require("./l10n/properties/core");
+const { getRulesForLocale } = require("./l10n/plural-rules");
+
+// Retrieve the plural mapping function
+var pluralMappingFunction = getRulesForLocale(json.language()) ||
+ getRulesForLocale("en");
+
+exports.get = function get(k) {
+ // For now, we only accept a "string" as first argument
+ // TODO: handle plural forms in gettext pattern
+ if (typeof k !== "string")
+ throw new Error("First argument of localization method should be a string");
+ let n = arguments[1];
+
+ // Get translation from big hashmap or default to hard coded string:
+ let localized = getKey(k, n) || k;
+
+ // # Simplest usecase:
+ // // String hard coded in source code:
+ // _("Hello world")
+ // // Identifier of a key stored in properties file
+ // _("helloString")
+ if (arguments.length <= 1)
+ return localized;
+
+ let args = Array.slice(arguments);
+ let placeholders = [null, ...args.slice(typeof(n) === "number" ? 2 : 1)];
+
+ if (typeof localized == "object" && "other" in localized) {
+ // # Plural form:
+ // // Strings hard coded in source code:
+ // _(["One download", "%d downloads"], 10);
+ // // Identifier of a key stored in properties file
+ // _("downloadNumber", 0);
+ let n = arguments[1];
+
+ // First handle simple universal forms that may not be mandatory
+ // for each language, (i.e. not different than 'other' form,
+ // but still usefull for better phrasing)
+ // For example 0 in english is the same form than 'other'
+ // but we accept 'zero' form if specified in localization file
+ if (n === 0 && "zero" in localized)
+ localized = localized["zero"];
+ else if (n === 1 && "one" in localized)
+ localized = localized["one"];
+ else if (n === 2 && "two" in localized)
+ localized = localized["two"];
+ else {
+ let pluralForm = pluralMappingFunction(n);
+ if (pluralForm in localized)
+ localized = localized[pluralForm];
+ else // Fallback in case of error: missing plural form
+ localized = localized["other"];
+ }
+
+ // Simulate a string with one placeholder:
+ args = [null, n];
+ }
+
+ // # String with placeholders:
+ // // Strings hard coded in source code:
+ // _("Hello %s", username)
+ // // Identifier of a key stored in properties file
+ // _("helloString", username)
+ // * We supports `%1s`, `%2s`, ... pattern in order to change arguments order
+ // in translation.
+ // * In case of plural form, we has `%d` instead of `%s`.
+ let offset = 1;
+ if (placeholders.length > 1) {
+ args = placeholders;
+ }
+
+ localized = localized.replace(/%(\d*)[sd]/g, (v, n) => {
+ let rv = args[n != "" ? n : offset];
+ offset++;
+ return rv;
+ });
+
+ return localized;
+}
diff --git a/components/jetpack/sdk/l10n/core.js b/components/jetpack/sdk/l10n/core.js
new file mode 100644
index 000000000..2f8f84c04
--- /dev/null
+++ b/components/jetpack/sdk/l10n/core.js
@@ -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/. */
+"use strict";
+
+const json = require("./json/core");
+const properties = require("./properties/core");
+
+exports.get = json.usingJSON ? json.get : properties.get;
diff --git a/components/jetpack/sdk/l10n/html.js b/components/jetpack/sdk/l10n/html.js
new file mode 100644
index 000000000..fa2cf9cf0
--- /dev/null
+++ b/components/jetpack/sdk/l10n/html.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { processes, remoteRequire } = require("../remote/parent");
+remoteRequire("sdk/content/l10n-html");
+
+var enabled = false;
+function enable() {
+ if (!enabled) {
+ processes.port.emit("sdk/l10n/html/enable");
+ enabled = true;
+ }
+}
+exports.enable = enable;
+
+function disable() {
+ if (enabled) {
+ processes.port.emit("sdk/l10n/html/disable");
+ enabled = false;
+ }
+}
+exports.disable = disable;
+
+processes.forEvery(process => {
+ process.port.emit(enabled ? "sdk/l10n/html/enable" : "sdk/l10n/html/disable");
+});
diff --git a/components/jetpack/sdk/l10n/json/core.js b/components/jetpack/sdk/l10n/json/core.js
new file mode 100644
index 000000000..af52f956f
--- /dev/null
+++ b/components/jetpack/sdk/l10n/json/core.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ "use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+var usingJSON = false;
+var hash = {}, bestMatchingLocale = null;
+try {
+ let data = require("@l10n/data");
+ hash = data.hash;
+ bestMatchingLocale = data.bestMatchingLocale;
+ usingJSON = true;
+}
+catch(e) {}
+
+exports.usingJSON = usingJSON;
+
+// Returns the translation for a given key, if available.
+exports.get = function get(k) {
+ return k in hash ? hash[k] : null;
+}
+
+// Returns the full length locale code: ja-JP-mac, en-US or fr
+exports.locale = function locale() {
+ return bestMatchingLocale;
+}
+
+// Returns the short locale code: ja, en, fr
+exports.language = function language() {
+ return bestMatchingLocale ? bestMatchingLocale.split("-")[0].toLowerCase()
+ : "en";
+}
diff --git a/components/jetpack/sdk/l10n/loader.js b/components/jetpack/sdk/l10n/loader.js
new file mode 100644
index 000000000..60e219e44
--- /dev/null
+++ b/components/jetpack/sdk/l10n/loader.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci } = require("chrome");
+const { getPreferedLocales, findClosestLocale } = require("./locale");
+const { readURI } = require("../net/url");
+const { resolve } = require("../core/promise");
+
+function parseJsonURI(uri) {
+ return readURI(uri).
+ then(JSON.parse).
+ then(null, function (error) {
+ throw Error("Failed to parse locale file:\n" + uri + "\n" + error);
+ });
+}
+
+// Returns the array stored in `locales.json` manifest that list available
+// locales files
+function getAvailableLocales(rootURI) {
+ let uri = rootURI + "locales.json";
+ return parseJsonURI(uri).then(function (manifest) {
+ return "locales" in manifest &&
+ Array.isArray(manifest.locales) ?
+ manifest.locales : [];
+ });
+}
+
+// Returns URI of the best locales file to use from the XPI
+function getBestLocale(rootURI) {
+ // Read localization manifest file that contains list of available languages
+ return getAvailableLocales(rootURI).then(function (availableLocales) {
+ // Retrieve list of prefered locales to use
+ let preferedLocales = getPreferedLocales();
+
+ // Compute the most preferable locale to use by using these two lists
+ return findClosestLocale(availableLocales, preferedLocales);
+ });
+}
+
+/**
+ * Read localization files and returns a promise of data to put in `@l10n/data`
+ * pseudo module, in order to allow l10n/json/core to fetch it.
+ */
+exports.load = function load(rootURI) {
+ // First, search for a locale file:
+ return getBestLocale(rootURI).then(function (bestMatchingLocale) {
+ // It may be null if the addon doesn't have any locale file
+ if (!bestMatchingLocale)
+ return resolve(null);
+
+ let localeURI = rootURI + "locale/" + bestMatchingLocale + ".json";
+
+ // Locale files only contains one big JSON object that is used as
+ // an hashtable of: "key to translate" => "translated key"
+ // TODO: We are likely to change this in order to be able to overload
+ // a specific key translation. For a specific package, module or line?
+ return parseJsonURI(localeURI).then(function (json) {
+ return {
+ hash: json,
+ bestMatchingLocale: bestMatchingLocale
+ };
+ });
+ });
+}
diff --git a/components/jetpack/sdk/l10n/locale.js b/components/jetpack/sdk/l10n/locale.js
new file mode 100644
index 000000000..950b33b20
--- /dev/null
+++ b/components/jetpack/sdk/l10n/locale.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const prefs = require("../preferences/service");
+const { Cu, Cc, Ci } = require("chrome");
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Gets the currently selected locale for display.
+ * Gets all usable locale that we can use sorted by priority of relevance
+ * @return Array of locales, begins with highest priority
+ */
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+const PREF_ACCEPT_LANGUAGES = "intl.accept_languages";
+
+function getPreferedLocales(caseSensitve) {
+ let locales = [];
+ function addLocale(locale) {
+ locale = locale.trim();
+ if (!caseSensitve)
+ locale = locale.toLowerCase();
+ if (locales.indexOf(locale) === -1)
+ locales.push(locale);
+ }
+
+ // Most important locale is OS one. But we use it, only if
+ // "intl.locale.matchOS" pref is set to `true`.
+ // Currently only used for multi-locales mobile builds.
+ // http://mxr.mozilla.org/mozilla-central/source/mobile/android/installer/Makefile.in#46
+ if (prefs.get(PREF_MATCH_OS_LOCALE, false)) {
+ let localeService = Cc["@mozilla.org/intl/nslocaleservice;1"].
+ getService(Ci.nsILocaleService);
+ let osLocale = localeService.getLocaleComponentForUserAgent();
+ addLocale(osLocale);
+ }
+
+ // In some cases, mainly on Fennec and on Linux version,
+ // `general.useragent.locale` is a special 'localized' value, like:
+ // "chrome://global/locale/intl.properties"
+ let browserUiLocale = prefs.getLocalized(PREF_SELECTED_LOCALE, "") ||
+ prefs.get(PREF_SELECTED_LOCALE, "");
+ if (browserUiLocale)
+ addLocale(browserUiLocale);
+
+ // Third priority is the list of locales used for web content
+ let contentLocales = prefs.getLocalized(PREF_ACCEPT_LANGUAGES, "") ||
+ prefs.get(PREF_ACCEPT_LANGUAGES, "");
+ if (contentLocales) {
+ // This list is a string of locales seperated by commas.
+ // There is spaces after commas, so strip each item
+ for (let locale of contentLocales.split(","))
+ addLocale(locale.replace(/(^\s+)|(\s+$)/g, ""));
+ }
+
+ // Finally, we ensure that en-US is the final fallback if it wasn't added
+ addLocale("en-US");
+
+ return locales;
+}
+exports.getPreferedLocales = getPreferedLocales;
+
+/**
+ * Selects the closest matching locale from a list of locales.
+ *
+ * @param aLocales
+ * An array of available locales
+ * @param aMatchLocales
+ * An array of prefered locales, ordered by priority. Most wanted first.
+ * Locales have to be in lowercase.
+ * If null, uses getPreferedLocales() results
+ * @return the best match for the currently selected locale
+ *
+ * Stolen from http://dxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+ */
+exports.findClosestLocale = function findClosestLocale(aLocales, aMatchLocales) {
+ aMatchLocales = aMatchLocales || getPreferedLocales();
+
+ // Holds the best matching localized resource
+ let bestmatch = null;
+ // The number of locale parts it matched with
+ let bestmatchcount = 0;
+ // The number of locale parts in the match
+ let bestpartcount = 0;
+
+ for (let locale of aMatchLocales) {
+ let lparts = locale.split("-");
+ for (let localized of aLocales) {
+ let found = localized.toLowerCase();
+ // Exact match is returned immediately
+ if (locale == found)
+ return localized;
+
+ let fparts = found.split("-");
+ /* If we have found a possible match and this one isn't any longer
+ then we dont need to check further. */
+ if (bestmatch && fparts.length < bestmatchcount)
+ continue;
+
+ // Count the number of parts that match
+ let maxmatchcount = Math.min(fparts.length, lparts.length);
+ let matchcount = 0;
+ while (matchcount < maxmatchcount &&
+ fparts[matchcount] == lparts[matchcount])
+ matchcount++;
+
+ /* If we matched more than the last best match or matched the same and
+ this locale is less specific than the last best match. */
+ if (matchcount > bestmatchcount ||
+ (matchcount == bestmatchcount && fparts.length < bestpartcount)) {
+ bestmatch = localized;
+ bestmatchcount = matchcount;
+ bestpartcount = fparts.length;
+ }
+ }
+ // If we found a valid match for this locale return it
+ if (bestmatch)
+ return bestmatch;
+ }
+ return null;
+}
diff --git a/components/jetpack/sdk/l10n/plural-rules.js b/components/jetpack/sdk/l10n/plural-rules.js
new file mode 100644
index 000000000..a3ef48a5e
--- /dev/null
+++ b/components/jetpack/sdk/l10n/plural-rules.js
@@ -0,0 +1,407 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 automatically generated with /python-lib/plural-rules-generator.py
+// Fetching data from: http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml
+
+// Mapping of short locale name == to == > rule index in following list
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const LOCALES_TO_RULES = {
+ "af": 3,
+ "ak": 4,
+ "am": 4,
+ "ar": 1,
+ "asa": 3,
+ "az": 0,
+ "be": 11,
+ "bem": 3,
+ "bez": 3,
+ "bg": 3,
+ "bh": 4,
+ "bm": 0,
+ "bn": 3,
+ "bo": 0,
+ "br": 20,
+ "brx": 3,
+ "bs": 11,
+ "ca": 3,
+ "cgg": 3,
+ "chr": 3,
+ "cs": 12,
+ "cy": 17,
+ "da": 3,
+ "de": 3,
+ "dv": 3,
+ "dz": 0,
+ "ee": 3,
+ "el": 3,
+ "en": 3,
+ "eo": 3,
+ "es": 3,
+ "et": 3,
+ "eu": 3,
+ "fa": 0,
+ "ff": 5,
+ "fi": 3,
+ "fil": 4,
+ "fo": 3,
+ "fr": 5,
+ "fur": 3,
+ "fy": 3,
+ "ga": 8,
+ "gd": 24,
+ "gl": 3,
+ "gsw": 3,
+ "gu": 3,
+ "guw": 4,
+ "gv": 23,
+ "ha": 3,
+ "haw": 3,
+ "he": 2,
+ "hi": 4,
+ "hr": 11,
+ "hu": 0,
+ "id": 0,
+ "ig": 0,
+ "ii": 0,
+ "is": 3,
+ "it": 3,
+ "iu": 7,
+ "ja": 0,
+ "jmc": 3,
+ "jv": 0,
+ "ka": 0,
+ "kab": 5,
+ "kaj": 3,
+ "kcg": 3,
+ "kde": 0,
+ "kea": 0,
+ "kk": 3,
+ "kl": 3,
+ "km": 0,
+ "kn": 0,
+ "ko": 0,
+ "ksb": 3,
+ "ksh": 21,
+ "ku": 3,
+ "kw": 7,
+ "lag": 18,
+ "lb": 3,
+ "lg": 3,
+ "ln": 4,
+ "lo": 0,
+ "lt": 10,
+ "lv": 6,
+ "mas": 3,
+ "mg": 4,
+ "mk": 16,
+ "ml": 3,
+ "mn": 3,
+ "mo": 9,
+ "mr": 3,
+ "ms": 0,
+ "mt": 15,
+ "my": 0,
+ "nah": 3,
+ "naq": 7,
+ "nb": 3,
+ "nd": 3,
+ "ne": 3,
+ "nl": 3,
+ "nn": 3,
+ "no": 3,
+ "nr": 3,
+ "nso": 4,
+ "ny": 3,
+ "nyn": 3,
+ "om": 3,
+ "or": 3,
+ "pa": 3,
+ "pap": 3,
+ "pl": 13,
+ "ps": 3,
+ "pt": 3,
+ "rm": 3,
+ "ro": 9,
+ "rof": 3,
+ "ru": 11,
+ "rwk": 3,
+ "sah": 0,
+ "saq": 3,
+ "se": 7,
+ "seh": 3,
+ "ses": 0,
+ "sg": 0,
+ "sh": 11,
+ "shi": 19,
+ "sk": 12,
+ "sl": 14,
+ "sma": 7,
+ "smi": 7,
+ "smj": 7,
+ "smn": 7,
+ "sms": 7,
+ "sn": 3,
+ "so": 3,
+ "sq": 3,
+ "sr": 11,
+ "ss": 3,
+ "ssy": 3,
+ "st": 3,
+ "sv": 3,
+ "sw": 3,
+ "syr": 3,
+ "ta": 3,
+ "te": 3,
+ "teo": 3,
+ "th": 0,
+ "ti": 4,
+ "tig": 3,
+ "tk": 3,
+ "tl": 4,
+ "tn": 3,
+ "to": 0,
+ "tr": 0,
+ "ts": 3,
+ "tzm": 22,
+ "uk": 11,
+ "ur": 3,
+ "ve": 3,
+ "vi": 0,
+ "vun": 3,
+ "wa": 4,
+ "wae": 3,
+ "wo": 0,
+ "xh": 3,
+ "xog": 3,
+ "yo": 0,
+ "zh": 0,
+ "zu": 3
+};
+
+// Utility functions for plural rules methods
+function isIn(n, list) {
+ return list.indexOf(n) !== -1;
+}
+function isBetween(n, start, end) {
+ return start <= n && n <= end;
+}
+
+// List of all plural rules methods, that maps an integer to the plural form name to use
+const RULES = {
+ "0": function (n) {
+
+ return "other"
+ },
+ "1": function (n) {
+ if ((isBetween((n % 100), 3, 10)))
+ return "few";
+ if (n == 0)
+ return "zero";
+ if ((isBetween((n % 100), 11, 99)))
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "2": function (n) {
+ if (n != 0 && (n % 10) == 0)
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "3": function (n) {
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "4": function (n) {
+ if ((isBetween(n, 0, 1)))
+ return "one";
+ return "other"
+ },
+ "5": function (n) {
+ if ((isBetween(n, 0, 2)) && n != 2)
+ return "one";
+ return "other"
+ },
+ "6": function (n) {
+ if (n == 0)
+ return "zero";
+ if ((n % 10) == 1 && (n % 100) != 11)
+ return "one";
+ return "other"
+ },
+ "7": function (n) {
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "8": function (n) {
+ if ((isBetween(n, 3, 6)))
+ return "few";
+ if ((isBetween(n, 7, 10)))
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "9": function (n) {
+ if (n == 0 || n != 1 && (isBetween((n % 100), 1, 19)))
+ return "few";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "10": function (n) {
+ if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
+ return "few";
+ if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
+ return "one";
+ return "other"
+ },
+ "11": function (n) {
+ if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
+ return "few";
+ if ((n % 10) == 0 || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 11, 14)))
+ return "many";
+ if ((n % 10) == 1 && (n % 100) != 11)
+ return "one";
+ return "other"
+ },
+ "12": function (n) {
+ if ((isBetween(n, 2, 4)))
+ return "few";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "13": function (n) {
+ if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
+ return "few";
+ if (n != 1 && (isBetween((n % 10), 0, 1)) || (isBetween((n % 10), 5, 9)) || (isBetween((n % 100), 12, 14)))
+ return "many";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "14": function (n) {
+ if ((isBetween((n % 100), 3, 4)))
+ return "few";
+ if ((n % 100) == 2)
+ return "two";
+ if ((n % 100) == 1)
+ return "one";
+ return "other"
+ },
+ "15": function (n) {
+ if (n == 0 || (isBetween((n % 100), 2, 10)))
+ return "few";
+ if ((isBetween((n % 100), 11, 19)))
+ return "many";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "16": function (n) {
+ if ((n % 10) == 1 && n != 11)
+ return "one";
+ return "other"
+ },
+ "17": function (n) {
+ if (n == 3)
+ return "few";
+ if (n == 0)
+ return "zero";
+ if (n == 6)
+ return "many";
+ if (n == 2)
+ return "two";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "18": function (n) {
+ if (n == 0)
+ return "zero";
+ if ((isBetween(n, 0, 2)) && n != 0 && n != 2)
+ return "one";
+ return "other"
+ },
+ "19": function (n) {
+ if ((isBetween(n, 2, 10)))
+ return "few";
+ if ((isBetween(n, 0, 1)))
+ return "one";
+ return "other"
+ },
+ "20": function (n) {
+ if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(isBetween((n % 100), 10, 19) || isBetween((n % 100), 70, 79) || isBetween((n % 100), 90, 99)))
+ return "few";
+ if ((n % 1000000) == 0 && n != 0)
+ return "many";
+ if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
+ return "two";
+ if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
+ return "one";
+ return "other"
+ },
+ "21": function (n) {
+ if (n == 0)
+ return "zero";
+ if (n == 1)
+ return "one";
+ return "other"
+ },
+ "22": function (n) {
+ if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
+ return "one";
+ return "other"
+ },
+ "23": function (n) {
+ if ((isBetween((n % 10), 1, 2)) || (n % 20) == 0)
+ return "one";
+ return "other"
+ },
+ "24": function (n) {
+ if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
+ return "few";
+ if (isIn(n, [2, 12]))
+ return "two";
+ if (isIn(n, [1, 11]))
+ return "one";
+ return "other"
+ },
+};
+
+/**
+ * Return a function that gives the plural form name for a given integer
+ * for the specified `locale`
+ * let fun = getRulesForLocale('en');
+ * fun(1) -> 'one'
+ * fun(0) -> 'other'
+ * fun(1000) -> 'other'
+ */
+exports.getRulesForLocale = function getRulesForLocale(locale) {
+ let index = LOCALES_TO_RULES[locale];
+ if (!(index in RULES)) {
+ console.warn('Plural form unknown for locale "' + locale + '"');
+ return function () { return "other"; };
+ }
+ return RULES[index];
+}
+
diff --git a/components/jetpack/sdk/l10n/prefs.js b/components/jetpack/sdk/l10n/prefs.js
new file mode 100644
index 000000000..8ee26fc5b
--- /dev/null
+++ b/components/jetpack/sdk/l10n/prefs.js
@@ -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/. */
+"use strict";
+
+const { on } = require("../system/events");
+const core = require("./core");
+const { id: jetpackId } = require('../self');
+
+const OPTIONS_DISPLAYED = "addon-options-displayed";
+
+function enable() {
+ on(OPTIONS_DISPLAYED, onOptionsDisplayed);
+}
+exports.enable = enable;
+
+function onOptionsDisplayed({ subject: document, data: addonId }) {
+ if (addonId !== jetpackId)
+ return;
+ localizeInlineOptions(document);
+}
+
+function localizeInlineOptions(document) {
+ let query = 'setting[data-jetpack-id="' + jetpackId + '"][pref-name], ' +
+ 'button[data-jetpack-id="' + jetpackId + '"][pref-name]';
+ let nodes = document.querySelectorAll(query);
+ for (let node of nodes) {
+ let name = node.getAttribute("pref-name");
+ if (node.tagName == "setting") {
+ let desc = core.get(name + "_description");
+ if (desc)
+ node.setAttribute("desc", desc);
+ let title = core.get(name + "_title");
+ if (title)
+ node.setAttribute("title", title);
+
+ for (let item of node.querySelectorAll("menuitem, radio")) {
+ let key = name + "_options." + item.getAttribute("label");
+ let label = core.get(key);
+ if (label)
+ item.setAttribute("label", label);
+ }
+ }
+ else if (node.tagName == "button") {
+ let label = core.get(name + "_label");
+ if (label)
+ node.setAttribute("label", label);
+ }
+ }
+}
+exports.localizeInlineOptions = localizeInlineOptions;
diff --git a/components/jetpack/sdk/l10n/properties/core.js b/components/jetpack/sdk/l10n/properties/core.js
new file mode 100644
index 000000000..7a9081d0b
--- /dev/null
+++ b/components/jetpack/sdk/l10n/properties/core.js
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { Cu } = require("chrome");
+const { newURI } = require('../../url/utils')
+const { getRulesForLocale } = require("../plural-rules");
+const { getPreferedLocales } = require('../locale');
+const { rootURI } = require("@loader/options");
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+const baseURI = rootURI + "locale/";
+const preferedLocales = getPreferedLocales(true);
+
+// Make sure we don't get stale data after an update
+// (See Bug 1300735 for rationale).
+Services.strings.flushBundles();
+
+function getLocaleURL(locale) {
+ // if the locale is a valid chrome URI, return it
+ try {
+ let uri = newURI(locale);
+ if (uri.scheme == 'chrome')
+ return uri.spec;
+ }
+ catch(_) {}
+ // otherwise try to construct the url
+ return baseURI + locale + ".properties";
+}
+
+function getKey(locale, key) {
+ let bundle = Services.strings.createBundle(getLocaleURL(locale));
+ try {
+ return bundle.GetStringFromName(key) + "";
+ }
+ catch (_) {}
+ return undefined;
+}
+
+function get(key, n, locales) {
+ // try this locale
+ let locale = locales.shift();
+ let localized;
+
+ if (typeof n == 'number') {
+ if (n == 0) {
+ localized = getKey(locale, key + '[zero]');
+ }
+ else if (n == 1) {
+ localized = getKey(locale, key + '[one]');
+ }
+ else if (n == 2) {
+ localized = getKey(locale, key + '[two]');
+ }
+
+ if (!localized) {
+ // Retrieve the plural mapping function
+ let pluralForm = (getRulesForLocale(locale.split("-")[0].toLowerCase()) ||
+ getRulesForLocale("en"))(n);
+ localized = getKey(locale, key + '[' + pluralForm + ']');
+ }
+
+ if (!localized) {
+ localized = getKey(locale, key + '[other]');
+ }
+ }
+
+ if (!localized) {
+ localized = getKey(locale, key);
+ }
+
+ if (!localized) {
+ localized = getKey(locale, key + '[other]');
+ }
+
+ if (localized) {
+ return localized;
+ }
+
+ // try next locale
+ if (locales.length)
+ return get(key, n, locales);
+
+ return undefined;
+}
+exports.get = (k, n) => get(k, n, Array.slice(preferedLocales));
diff --git a/components/jetpack/sdk/lang/functional.js b/components/jetpack/sdk/lang/functional.js
new file mode 100644
index 000000000..66e30edfa
--- /dev/null
+++ b/components/jetpack/sdk/lang/functional.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Disclaimer: Some of the functions in this module implement APIs from
+// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
+// those goes to him.
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { defer, remit, delay, debounce,
+ throttle } = require("./functional/concurrent");
+const { method, invoke, partial, curry, compose, wrap, identity, memoize, once,
+ cache, complement, constant, when, apply, flip, field, query,
+ isInstance, chainable, is, isnt } = require("./functional/core");
+
+exports.defer = defer;
+exports.remit = remit;
+exports.delay = delay;
+exports.debounce = debounce;
+exports.throttle = throttle;
+
+exports.method = method;
+exports.invoke = invoke;
+exports.partial = partial;
+exports.curry = curry;
+exports.compose = compose;
+exports.wrap = wrap;
+exports.identity = identity;
+exports.memoize = memoize;
+exports.once = once;
+exports.cache = cache;
+exports.complement = complement;
+exports.constant = constant;
+exports.when = when;
+exports.apply = apply;
+exports.flip = flip;
+exports.field = field;
+exports.query = query;
+exports.isInstance = isInstance;
+exports.chainable = chainable;
+exports.is = is;
+exports.isnt = isnt;
diff --git a/components/jetpack/sdk/lang/functional/concurrent.js b/components/jetpack/sdk/lang/functional/concurrent.js
new file mode 100644
index 000000000..85e8cff46
--- /dev/null
+++ b/components/jetpack/sdk/lang/functional/concurrent.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Disclaimer: Some of the functions in this module implement APIs from
+// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
+// those goes to him.
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { arity, name, derive, invoke } = require("./helpers");
+const { setTimeout, clearTimeout, setImmediate } = require("../../timers");
+
+/**
+ * Takes a function and returns a wrapped one instead, calling which will call
+ * original function in the next turn of event loop. This is basically utility
+ * to do `setImmediate(function() { ... })`, with a difference that returned
+ * function is reused, instead of creating a new one each time. This also allows
+ * to use this functions as event listeners.
+ */
+const defer = f => derive(function(...args) {
+ setImmediate(invoke, f, args, this);
+}, f);
+exports.defer = defer;
+// Exporting `remit` alias as `defer` may conflict with promises.
+exports.remit = defer;
+
+/**
+ * Much like setTimeout, invokes function after wait milliseconds. If you pass
+ * the optional arguments, they will be forwarded on to the function when it is
+ * invoked.
+ */
+const delay = function delay(f, ms, ...args) {
+ setTimeout(() => f.apply(this, args), ms);
+};
+exports.delay = delay;
+
+/**
+ * From underscore's `_.debounce`
+ * http://underscorejs.org
+ * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Underscore may be freely distributed under the MIT license.
+ */
+const debounce = function debounce (fn, wait) {
+ let timeout, args, context, timestamp, result;
+
+ let later = function () {
+ let last = Date.now() - timestamp;
+ if (last < wait) {
+ timeout = setTimeout(later, wait - last);
+ } else {
+ timeout = null;
+ result = fn.apply(context, args);
+ context = args = null;
+ }
+ };
+
+ return function (...aArgs) {
+ context = this;
+ args = aArgs;
+ timestamp = Date.now();
+ if (!timeout) {
+ timeout = setTimeout(later, wait);
+ }
+
+ return result;
+ };
+};
+exports.debounce = debounce;
+
+/**
+ * From underscore's `_.throttle`
+ * http://underscorejs.org
+ * (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Underscore may be freely distributed under the MIT license.
+ */
+const throttle = function throttle (func, wait, options) {
+ let context, args, result;
+ let timeout = null;
+ let previous = 0;
+ options || (options = {});
+ let later = function() {
+ previous = options.leading === false ? 0 : Date.now();
+ timeout = null;
+ result = func.apply(context, args);
+ context = args = null;
+ };
+ return function() {
+ let now = Date.now();
+ if (!previous && options.leading === false) previous = now;
+ let remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(context, args);
+ context = args = null;
+ } else if (!timeout && options.trailing !== false) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+};
+exports.throttle = throttle;
diff --git a/components/jetpack/sdk/lang/functional/core.js b/components/jetpack/sdk/lang/functional/core.js
new file mode 100644
index 000000000..0d9143364
--- /dev/null
+++ b/components/jetpack/sdk/lang/functional/core.js
@@ -0,0 +1,290 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Disclaimer: Some of the functions in this module implement APIs from
+// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
+// those goes to him.
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+}
+const { arity, name, derive, invoke } = require("./helpers");
+
+/**
+ * Takes variadic numeber of functions and returns composed one.
+ * Returned function pushes `this` pseudo-variable to the head
+ * of the passed arguments and invokes all the functions from
+ * left to right passing same arguments to them. Composite function
+ * returns return value of the right most funciton.
+ */
+const method = (...lambdas) => {
+ return function method(...args) {
+ args.unshift(this);
+ return lambdas.reduce((_, lambda) => lambda.apply(this, args),
+ void(0));
+ };
+};
+exports.method = method;
+
+/**
+ * Invokes `callee` by passing `params` as an arguments and `self` as `this`
+ * pseudo-variable. Returns value that is returned by a callee.
+ * @param {Function} callee
+ * Function to invoke.
+ * @param {Array} params
+ * Arguments to invoke function with.
+ * @param {Object} self
+ * Object to be passed as a `this` pseudo variable.
+ */
+exports.invoke = invoke;
+
+/**
+ * Takes a function and bind values to one or more arguments, returning a new
+ * function of smaller arity.
+ *
+ * @param {Function} fn
+ * The function to partial
+ *
+ * @returns The new function with binded values
+ */
+const partial = (f, ...curried) => {
+ if (typeof(f) !== "function")
+ throw new TypeError(String(f) + " is not a function");
+
+ let fn = derive(function(...args) {
+ return f.apply(this, curried.concat(args));
+ }, f);
+ fn.arity = arity(f) - curried.length;
+ return fn;
+};
+exports.partial = partial;
+
+/**
+ * Returns function with implicit currying, which will continue currying until
+ * expected number of argument is collected. Expected number of arguments is
+ * determined by `fn.length`. Using this with variadic functions is stupid,
+ * so don't do it.
+ *
+ * @examples
+ *
+ * var sum = curry(function(a, b) {
+ * return a + b
+ * })
+ * console.log(sum(2, 2)) // 4
+ * console.log(sum(2)(4)) // 6
+ */
+const curry = new function() {
+ const currier = (fn, arity, params) => {
+ // Function either continues to curry arguments or executes function
+ // if desired arguments have being collected.
+ const curried = function(...input) {
+ // Prepend all curried arguments to the given arguments.
+ if (params) input.unshift.apply(input, params);
+ // If expected number of arguments has being collected invoke fn,
+ // othrewise return curried version Otherwise continue curried.
+ return (input.length >= arity) ? fn.apply(this, input) :
+ currier(fn, arity, input);
+ };
+ curried.arity = arity - (params ? params.length : 0);
+
+ return curried;
+ };
+
+ return fn => currier(fn, arity(fn));
+};
+exports.curry = curry;
+
+/**
+ * Returns the composition of a list of functions, where each function consumes
+ * the return value of the function that follows. In math terms, composing the
+ * functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+ * @example
+ *
+ * var greet = function(name) { return "hi: " + name; };
+ * var exclaim = function(statement) { return statement + "!"; };
+ * var welcome = compose(exclaim, greet);
+ *
+ * welcome('moe'); // => 'hi: moe!'
+ */
+function compose(...lambdas) {
+ return function composed(...args) {
+ let index = lambdas.length;
+ while (0 <= --index)
+ args = [lambdas[index].apply(this, args)];
+
+ return args[0];
+ };
+}
+exports.compose = compose;
+
+/*
+ * Returns the first function passed as an argument to the second,
+ * allowing you to adjust arguments, run code before and after, and
+ * conditionally execute the original function.
+ * @example
+ *
+ * var hello = function(name) { return "hello: " + name; };
+ * hello = wrap(hello, function(f) {
+ * return "before, " + f("moe") + ", after";
+ * });
+ *
+ * hello(); // => 'before, hello: moe, after'
+ */
+const wrap = (f, wrapper) => derive(function wrapped(...args) {
+ return wrapper.apply(this, [f].concat(args));
+}, f);
+exports.wrap = wrap;
+
+/**
+ * Returns the same value that is used as the argument. In math: f(x) = x
+ */
+const identity = value => value;
+exports.identity = identity;
+
+/**
+ * Memoizes a given function by caching the computed result. Useful for
+ * speeding up slow-running computations. If passed an optional hashFunction,
+ * it will be used to compute the hash key for storing the result, based on
+ * the arguments to the original function. The default hashFunction just uses
+ * the first argument to the memoized function as the key.
+ */
+const memoize = (f, hasher) => {
+ let memo = Object.create(null);
+ let cache = new WeakMap();
+ hasher = hasher || identity;
+ return derive(function memoizer(...args) {
+ const key = hasher.apply(this, args);
+ const type = typeof(key);
+ if (key && (type === "object" || type === "function")) {
+ if (!cache.has(key))
+ cache.set(key, f.apply(this, args));
+ return cache.get(key);
+ }
+ else {
+ if (!(key in memo))
+ memo[key] = f.apply(this, args);
+ return memo[key];
+ }
+ }, f);
+};
+exports.memoize = memoize;
+
+/*
+ * Creates a version of the function that can only be called one time. Repeated
+ * calls to the modified function will have no effect, returning the value from
+ * the original call. Useful for initialization functions, instead of having to
+ * set a boolean flag and then check it later.
+ */
+const once = f => {
+ let ran = false, cache;
+ return derive(function(...args) {
+ return ran ? cache : (ran = true, cache = f.apply(this, args));
+ }, f);
+};
+exports.once = once;
+// export cache as once will may be conflicting with event once a lot.
+exports.cache = once;
+
+// Takes a `f` function and returns a function that takes the same
+// arguments as `f`, has the same effects, if any, and returns the
+// opposite truth value.
+const complement = f => derive(function(...args) {
+ return args.length < arity(f) ? complement(partial(f, ...args)) :
+ !f.apply(this, args);
+}, f);
+exports.complement = complement;
+
+// Constructs function that returns `x` no matter what is it
+// invoked with.
+const constant = x => _ => x;
+exports.constant = constant;
+
+// Takes `p` predicate, `consequent` function and an optional
+// `alternate` function and composes function that returns
+// application of arguments over `consequent` if application over
+// `p` is `true` otherwise returns application over `alternate`.
+// If `alternate` is not a function returns `undefined`.
+const when = (p, consequent, alternate) => {
+ if (typeof(alternate) !== "function" && alternate !== void(0))
+ throw TypeError("alternate must be a function");
+ if (typeof(consequent) !== "function")
+ throw TypeError("consequent must be a function");
+
+ return function(...args) {
+ return p.apply(this, args) ?
+ consequent.apply(this, args) :
+ alternate && alternate.apply(this, args);
+ };
+};
+exports.when = when;
+
+// Apply function that behaves as `apply` does in lisp:
+// apply(f, x, [y, z]) => f.apply(f, [x, y, z])
+// apply(f, x) => f.apply(f, [x])
+const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop()));
+exports.apply = apply;
+
+// Returns function identical to given `f` but with flipped order
+// of arguments.
+const flip = f => derive(function(...args) {
+ return f.apply(this, args.reverse());
+}, f);
+exports.flip = flip;
+
+// Takes field `name` and `target` and returns value of that field.
+// If `target` is `null` or `undefined` it would be returned back
+// instead of attempt to access it's field. Function is implicitly
+// curried, this allows accessor function generation by calling it
+// with only `name` argument.
+const field = curry((name, target) =>
+ // Note: Permisive `==` is intentional.
+ target == null ? target : target[name]);
+exports.field = field;
+
+// Takes `.` delimited string representing `path` to a nested field
+// and a `target` to get it from. For convinience function is
+// implicitly curried, there for accessors can be created by invoking
+// it with just a `path` argument.
+const query = curry((path, target) => {
+ const names = path.split(".");
+ const count = names.length;
+ let index = 0;
+ let result = target;
+ // Note: Permisive `!=` is intentional.
+ while (result != null && index < count) {
+ result = result[names[index]];
+ index = index + 1;
+ }
+ return result;
+});
+exports.query = query;
+
+// Takes `Type` (constructor function) and a `value` and returns
+// `true` if `value` is instance of the given `Type`. Function is
+// implicitly curried this allows predicate generation by calling
+// function with just first argument.
+const isInstance = curry((Type, value) => value instanceof Type);
+exports.isInstance = isInstance;
+
+/*
+ * Takes a funtion and returns a wrapped function that returns `this`
+ */
+const chainable = f => derive(function(...args) {
+ f.apply(this, args);
+ return this;
+}, f);
+exports.chainable = chainable;
+
+// Functions takes `expected` and `actual` values and returns `true` if
+// `expected === actual`. Returns curried function if called with less then
+// two arguments.
+//
+// [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
+const is = curry((expected, actual) => actual === expected);
+exports.is = is;
+
+const isnt = complement(is);
+exports.isnt = isnt;
diff --git a/components/jetpack/sdk/lang/functional/helpers.js b/components/jetpack/sdk/lang/functional/helpers.js
new file mode 100644
index 000000000..60f4e3300
--- /dev/null
+++ b/components/jetpack/sdk/lang/functional/helpers.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Disclaimer: Some of the functions in this module implement APIs from
+// Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
+// those goes to him.
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+}
+
+const arity = f => f.arity || f.length;
+exports.arity = arity;
+
+const name = f => f.displayName || f.name;
+exports.name = name;
+
+const derive = (f, source) => {
+ f.displayName = name(source);
+ f.arity = arity(source);
+ return f;
+};
+exports.derive = derive;
+
+const invoke = (callee, params, self) => callee.apply(self, params);
+exports.invoke = invoke;
diff --git a/components/jetpack/sdk/lang/type.js b/components/jetpack/sdk/lang/type.js
new file mode 100644
index 000000000..b50e6be4c
--- /dev/null
+++ b/components/jetpack/sdk/lang/type.js
@@ -0,0 +1,388 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+/**
+ * Returns `true` if `value` is `undefined`.
+ * @examples
+ * var foo; isUndefined(foo); // true
+ * isUndefined(0); // false
+ */
+function isUndefined(value) {
+ return value === undefined;
+}
+exports.isUndefined = isUndefined;
+
+/**
+ * Returns `true` if value is `null`.
+ * @examples
+ * isNull(null); // true
+ * isNull(undefined); // false
+ */
+function isNull(value) {
+ return value === null;
+}
+exports.isNull = isNull;
+
+/**
+ * Returns `true` if value is `null` or `undefined`.
+ * It's equivalent to `== null`, but resolve the ambiguity of the writer
+ * intention, makes clear that he's clearly checking both `null` and `undefined`
+ * values, and it's not a typo for `=== null`.
+ */
+function isNil(value) {
+ return value === null || value === undefined;
+}
+exports.isNil = isNil;
+
+function isBoolean(value) {
+ return typeof value === "boolean";
+}
+exports.isBoolean = isBoolean;
+/**
+ * Returns `true` if value is a string.
+ * @examples
+ * isString("moe"); // true
+ */
+function isString(value) {
+ return typeof value === "string";
+}
+exports.isString = isString;
+
+/**
+ * Returns `true` if `value` is a number.
+ * @examples
+ * isNumber(8.4 * 5); // true
+ */
+function isNumber(value) {
+ return typeof value === "number";
+}
+exports.isNumber = isNumber;
+
+/**
+ * Returns `true` if `value` is a `RegExp`.
+ * @examples
+ * isRegExp(/moe/); // true
+ */
+function isRegExp(value) {
+ return isObject(value) && instanceOf(value, RegExp);
+}
+exports.isRegExp = isRegExp;
+
+/**
+ * Returns true if `value` is a `Date`.
+ * @examples
+ * isDate(new Date()); // true
+ */
+function isDate(value) {
+ return isObject(value) && instanceOf(value, Date);
+}
+exports.isDate = isDate;
+
+/**
+ * Returns true if object is a Function.
+ * @examples
+ * isFunction(function foo(){}) // true
+ */
+function isFunction(value) {
+ return typeof value === "function";
+}
+exports.isFunction = isFunction;
+
+/**
+ * Returns `true` if `value` is an object (please note that `null` is considered
+ * to be an atom and not an object).
+ * @examples
+ * isObject({}) // true
+ * isObject(null) // false
+ */
+function isObject(value) {
+ return typeof value === "object" && value !== null;
+}
+exports.isObject = isObject;
+
+/**
+ * Detect whether a value is a generator.
+ *
+ * @param aValue
+ * The value to identify.
+ * @return A boolean indicating whether the value is a generator.
+ */
+function isGenerator(aValue) {
+ return !!(aValue && aValue.isGenerator && aValue.isGenerator());
+}
+exports.isGenerator = isGenerator;
+
+/**
+ * Returns true if `value` is an Array.
+ * @examples
+ * isArray([1, 2, 3]) // true
+ * isArray({ 0: 'foo', length: 1 }) // false
+ */
+var isArray = Array.isArray;
+exports.isArray = isArray;
+
+/**
+ * Returns `true` if `value` is an Arguments object.
+ * @examples
+ * (function(){ return isArguments(arguments); })(1, 2, 3); // true
+ * isArguments([1,2,3]); // false
+ */
+function isArguments(value) {
+ return Object.prototype.toString.call(value) === "[object Arguments]";
+}
+exports.isArguments = isArguments;
+
+var isMap = value => Object.prototype.toString.call(value) === "[object Map]"
+exports.isMap = isMap;
+
+var isSet = value => Object.prototype.toString.call(value) === "[object Set]"
+exports.isSet = isSet;
+
+/**
+ * Returns true if it is a primitive `value`. (null, undefined, number,
+ * boolean, string)
+ * @examples
+ * isPrimitive(3) // true
+ * isPrimitive('foo') // true
+ * isPrimitive({ bar: 3 }) // false
+ */
+function isPrimitive(value) {
+ return !isFunction(value) && !isObject(value);
+}
+exports.isPrimitive = isPrimitive;
+
+/**
+ * Returns `true` if given `object` is flat (it is direct decedent of
+ * `Object.prototype` or `null`).
+ * @examples
+ * isFlat({}) // true
+ * isFlat(new Type()) // false
+ */
+function isFlat(object) {
+ return isObject(object) && (isNull(Object.getPrototypeOf(object)) ||
+ isNull(Object.getPrototypeOf(
+ Object.getPrototypeOf(object))));
+}
+exports.isFlat = isFlat;
+
+/**
+ * Returns `true` if object contains no values.
+ */
+function isEmpty(object) {
+ if (isObject(object)) {
+ for (var key in object)
+ return false;
+ return true;
+ }
+ return false;
+}
+exports.isEmpty = isEmpty;
+
+/**
+ * Returns `true` if `value` is an array / flat object containing only atomic
+ * values and other flat objects.
+ */
+function isJSON(value, visited) {
+ // Adding value to array of visited values.
+ (visited || (visited = [])).push(value);
+ // If `value` is an atom return `true` cause it's valid JSON.
+ return isPrimitive(value) ||
+ // If `value` is an array of JSON values that has not been visited
+ // yet.
+ (isArray(value) && value.every(function(element) {
+ return isJSON(element, visited);
+ })) ||
+ // If `value` is a plain object containing properties with a JSON
+ // values it's a valid JSON.
+ (isFlat(value) && Object.keys(value).every(function(key) {
+ var $ = Object.getOwnPropertyDescriptor(value, key);
+ // Check every proprety of a plain object to verify that
+ // it's neither getter nor setter, but a JSON value, that
+ // has not been visited yet.
+ return ((!isObject($.value) || !~visited.indexOf($.value)) &&
+ !('get' in $) && !('set' in $) &&
+ isJSON($.value, visited));
+ }));
+}
+exports.isJSON = function (value) {
+ return isJSON(value);
+};
+
+/**
+ * Returns `true` if `value` is JSONable
+ */
+const isJSONable = (value) => {
+ try {
+ JSON.parse(JSON.stringify(value));
+ }
+ catch (e) {
+ return false;
+ }
+ return true;
+};
+exports.isJSONable = isJSONable;
+
+/**
+ * Returns if `value` is an instance of a given `Type`. This is exactly same as
+ * `value instanceof Type` with a difference that `Type` can be from a scope
+ * that has a different top level object. (Like in case where `Type` is a
+ * function from different iframe / jetpack module / sandbox).
+ */
+function instanceOf(value, Type) {
+ var isConstructorNameSame;
+ var isConstructorSourceSame;
+
+ // If `instanceof` returned `true` we know result right away.
+ var isInstanceOf = value instanceof Type;
+
+ // If `instanceof` returned `false` we do ducktype check since `Type` may be
+ // from a different sandbox. If a constructor of the `value` or a constructor
+ // of the value's prototype has same name and source we assume that it's an
+ // instance of the Type.
+ if (!isInstanceOf && value) {
+ isConstructorNameSame = value.constructor.name === Type.name;
+ isConstructorSourceSame = String(value.constructor) == String(Type);
+ isInstanceOf = (isConstructorNameSame && isConstructorSourceSame) ||
+ instanceOf(Object.getPrototypeOf(value), Type);
+ }
+ return isInstanceOf;
+}
+exports.instanceOf = instanceOf;
+
+/**
+ * Function returns textual representation of a value passed to it. Function
+ * takes additional `indent` argument that is used for indentation. Also
+ * optional `limit` argument may be passed to limit amount of detail returned.
+ * @param {Object} value
+ * @param {String} [indent=" "]
+ * @param {Number} [limit]
+ */
+function source(value, indent, limit, offset, visited) {
+ var result;
+ var names;
+ var nestingIndex;
+ var isCompact = !isUndefined(limit);
+
+ indent = indent || " ";
+ offset = (offset || "");
+ result = "";
+ visited = visited || [];
+
+ if (isUndefined(value)) {
+ result += "undefined";
+ }
+ else if (isNull(value)) {
+ result += "null";
+ }
+ else if (isString(value)) {
+ result += '"' + value + '"';
+ }
+ else if (isFunction(value)) {
+ value = String(value).split("\n");
+ if (isCompact && value.length > 2) {
+ value = value.splice(0, 2);
+ value.push("...}");
+ }
+ result += value.join("\n" + offset);
+ }
+ else if (isArray(value)) {
+ if ((nestingIndex = (visited.indexOf(value) + 1))) {
+ result = "#" + nestingIndex + "#";
+ }
+ else {
+ visited.push(value);
+
+ if (isCompact)
+ value = value.slice(0, limit);
+
+ result += "[\n";
+ result += value.map(function(value) {
+ return offset + indent + source(value, indent, limit, offset + indent,
+ visited);
+ }).join(",\n");
+ result += isCompact && value.length > limit ?
+ ",\n" + offset + "...]" : "\n" + offset + "]";
+ }
+ }
+ else if (isObject(value)) {
+ if ((nestingIndex = (visited.indexOf(value) + 1))) {
+ result = "#" + nestingIndex + "#"
+ }
+ else {
+ visited.push(value)
+
+ names = Object.keys(value);
+
+ result += "{ // " + value + "\n";
+ result += (isCompact ? names.slice(0, limit) : names).map(function(name) {
+ var _limit = isCompact ? limit - 1 : limit;
+ var descriptor = Object.getOwnPropertyDescriptor(value, name);
+ var result = offset + indent + "// ";
+ var accessor;
+ if (0 <= name.indexOf(" "))
+ name = '"' + name + '"';
+
+ if (descriptor.writable)
+ result += "writable ";
+ if (descriptor.configurable)
+ result += "configurable ";
+ if (descriptor.enumerable)
+ result += "enumerable ";
+
+ result += "\n";
+ if ("value" in descriptor) {
+ result += offset + indent + name + ": ";
+ result += source(descriptor.value, indent, _limit, indent + offset,
+ visited);
+ }
+ else {
+
+ if (descriptor.get) {
+ result += offset + indent + "get " + name + " ";
+ accessor = source(descriptor.get, indent, _limit, indent + offset,
+ visited);
+ result += accessor.substr(accessor.indexOf("{"));
+ }
+
+ if (descriptor.set) {
+ result += offset + indent + "set " + name + " ";
+ accessor = source(descriptor.set, indent, _limit, indent + offset,
+ visited);
+ result += accessor.substr(accessor.indexOf("{"));
+ }
+ }
+ return result;
+ }).join(",\n");
+
+ if (isCompact) {
+ if (names.length > limit && limit > 0) {
+ result += ",\n" + offset + indent + "//...";
+ }
+ }
+ else {
+ if (names.length)
+ result += ",";
+
+ result += "\n" + offset + indent + '"__proto__": ';
+ result += source(Object.getPrototypeOf(value), indent, 0,
+ offset + indent);
+ }
+
+ result += "\n" + offset + "}";
+ }
+ }
+ else {
+ result += String(value);
+ }
+ return result;
+}
+exports.source = function (value, indentation, limit) {
+ return source(value, indentation, limit);
+};
diff --git a/components/jetpack/sdk/lang/weak-set.js b/components/jetpack/sdk/lang/weak-set.js
new file mode 100644
index 000000000..0b81a395b
--- /dev/null
+++ b/components/jetpack/sdk/lang/weak-set.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cu } = require("chrome");
+
+function makeGetterFor(Type) {
+ let cache = new WeakMap();
+
+ return {
+ getFor(target) {
+ if (!cache.has(target))
+ cache.set(target, new Type());
+
+ return cache.get(target);
+ },
+ clearFor(target) {
+ return cache.delete(target)
+ }
+ }
+}
+
+var {getFor: getLookupFor, clearFor: clearLookupFor} = makeGetterFor(WeakMap);
+var {getFor: getRefsFor, clearFor: clearRefsFor} = makeGetterFor(Set);
+
+function add(target, value) {
+ if (has(target, value))
+ return;
+
+ getLookupFor(target).set(value, true);
+ getRefsFor(target).add(Cu.getWeakReference(value));
+}
+exports.add = add;
+
+function remove(target, value) {
+ getLookupFor(target).delete(value);
+}
+exports.remove = remove;
+
+function has(target, value) {
+ return getLookupFor(target).has(value);
+}
+exports.has = has;
+
+function clear(target) {
+ clearLookupFor(target);
+ clearRefsFor(target);
+}
+exports.clear = clear;
+
+function iterator(target) {
+ let refs = getRefsFor(target);
+
+ for (let ref of refs) {
+ let value = ref.get();
+
+ // If `value` is already gc'ed, it would be `null`.
+ // The `has` function is using a WeakMap as lookup table, so passing `null`
+ // would raise an exception because WeakMap accepts as value only non-null
+ // object.
+ // Plus, if `value` is already gc'ed, we do not have to take it in account
+ // during the iteration, and remove it from the references.
+ if (value !== null && has(target, value))
+ yield value;
+ else
+ refs.delete(ref);
+ }
+}
+exports.iterator = iterator;
diff --git a/components/jetpack/sdk/loader/cuddlefish.js b/components/jetpack/sdk/loader/cuddlefish.js
new file mode 100644
index 000000000..6ba19157b
--- /dev/null
+++ b/components/jetpack/sdk/loader/cuddlefish.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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+// This module is manually loaded by bootstrap.js in a sandbox and immediatly
+// put in module cache so that it is never loaded in any other way.
+
+/* Workarounds to include dependencies in the manifest
+require('chrome') // Otherwise CFX will complain about Components
+require('toolkit/loader') // Otherwise CFX will stip out loader.js
+require('sdk/addon/runner') // Otherwise CFX will stip out addon/runner.js
+*/
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+// `loadSandbox` is exposed by bootstrap.js
+const loaderURI = module.uri.replace("sdk/loader/cuddlefish.js",
+ "toolkit/loader.js");
+const xulappURI = module.uri.replace("loader/cuddlefish.js",
+ "system/xul-app.jsm");
+// We need to keep a reference to the sandbox in order to unload it in
+// bootstrap.js
+
+var loaderSandbox = loadSandbox(loaderURI);
+const loaderModule = loaderSandbox.exports;
+
+const { incompatibility } = Cu.import(xulappURI, {}).XulApp;
+
+const { override, load } = loaderModule;
+
+function CuddlefishLoader(options) {
+ let { manifest } = options;
+
+ options = override(options, {
+ // Put `api-utils/loader` and `api-utils/cuddlefish` loaded as JSM to module
+ // cache to avoid subsequent loads via `require`.
+ modules: override({
+ 'toolkit/loader': loaderModule,
+ 'sdk/loader/cuddlefish': exports
+ }, options.modules),
+ resolve: function resolve(id, requirer) {
+ let entry = requirer && requirer in manifest && manifest[requirer];
+ let uri = null;
+
+ // If manifest entry for this requirement is present we follow manifest.
+ // Note: Standard library modules like 'panel' will be present in
+ // manifest unless they were moved to platform.
+ if (entry) {
+ let requirement = entry.requirements[id];
+ // If requirer entry is in manifest and it's requirement is not, than
+ // it has no authority to load since linker was not able to find it.
+ if (!requirement)
+ throw Error('Module: ' + requirer + ' has no authority to load: '
+ + id, requirer);
+
+ uri = requirement;
+ } else {
+ // If requirer is off manifest than it's a system module and we allow it
+ // to go off manifest by resolving a relative path.
+ uri = loaderModule.resolve(id, requirer);
+ }
+ return uri;
+ },
+ load: function(loader, module) {
+ let result;
+ let error;
+
+ // In order to get the module's metadata, we need to load the module.
+ // if an exception is raised here, it could be that is due to application
+ // incompatibility. Therefore the exception is stored, and thrown again
+ // only if the module seems be compatible with the application currently
+ // running. Otherwise the incompatibility message takes the precedence.
+ try {
+ result = load(loader, module);
+ }
+ catch (e) {
+ error = e;
+ }
+
+ error = incompatibility(module) || error;
+
+ if (error)
+ throw error;
+
+ return result;
+ }
+ });
+
+ let loader = loaderModule.Loader(options);
+ // Hack to allow loading from `toolkit/loader`.
+ loader.modules[loaderURI] = loaderSandbox;
+ return loader;
+}
+
+exports = override(loaderModule, {
+ Loader: CuddlefishLoader
+});
diff --git a/components/jetpack/sdk/loader/sandbox.js b/components/jetpack/sdk/loader/sandbox.js
new file mode 100644
index 000000000..791dbc086
--- /dev/null
+++ b/components/jetpack/sdk/loader/sandbox.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci, CC, Cu } = require('chrome');
+const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
+const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
+ getService(Ci.mozIJSSubScriptLoader);
+const self = require('sdk/self');
+const { getTabId } = require('../tabs/utils');
+const { getInnerId } = require('../window/utils');
+
+const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { require: devtoolsRequire } = devtools;
+const { addContentGlobal, removeContentGlobal } = devtoolsRequire("devtools/server/content-globals");
+
+/**
+ * Make a new sandbox that inherits given `source`'s principals. Source can be
+ * URI string, DOMWindow or `null` for system principals.
+ */
+function sandbox(target, options) {
+ options = options || {};
+ options.metadata = options.metadata ? options.metadata : {};
+ options.metadata.addonID = options.metadata.addonID ?
+ options.metadata.addonID : self.id;
+
+ let sandbox = Cu.Sandbox(target || systemPrincipal, options);
+ Cu.setSandboxMetadata(sandbox, options.metadata);
+ let innerWindowID = options.metadata['inner-window-id']
+ if (innerWindowID) {
+ addContentGlobal({
+ global: sandbox,
+ 'inner-window-id': innerWindowID
+ });
+ }
+ return sandbox;
+}
+exports.sandbox = sandbox;
+
+/**
+ * Evaluates given `source` in a given `sandbox` and returns result.
+ */
+function evaluate(sandbox, code, uri, line, version) {
+ return Cu.evalInSandbox(code, sandbox, version || '1.8', uri || '', line || 1);
+}
+exports.evaluate = evaluate;
+
+/**
+ * Evaluates code under the given `uri` in the given `sandbox`.
+ *
+ * @param {String} uri
+ * The URL pointing to the script to load.
+ * It must be a local chrome:, resource:, file: or data: URL.
+ */
+function load(sandbox, uri) {
+ if (uri.indexOf('data:') === 0) {
+ let source = uri.substr(uri.indexOf(',') + 1);
+
+ return evaluate(sandbox, decodeURIComponent(source), '1.8', uri, 0);
+ } else {
+ return scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
+ }
+}
+exports.load = load;
+
+/**
+ * Forces the given `sandbox` to be freed immediately.
+ */
+exports.nuke = Cu.nukeSandbox
diff --git a/components/jetpack/sdk/messaging.js b/components/jetpack/sdk/messaging.js
new file mode 100644
index 000000000..07580eb33
--- /dev/null
+++ b/components/jetpack/sdk/messaging.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { window } = require("sdk/addon/window");
+exports.MessageChannel = window.MessageChannel;
+exports.MessagePort = window.MessagePort;
diff --git a/components/jetpack/sdk/model/core.js b/components/jetpack/sdk/model/core.js
new file mode 100644
index 000000000..315f8b1cd
--- /dev/null
+++ b/components/jetpack/sdk/model/core.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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { dispatcher } = require("../util/dispatcher");
+
+
+// Define `modelFor` accessor function that can be implemented
+// for different types of views. Since view's we'll be dealing
+// with types that don't really play well with `instanceof`
+// operator we're gonig to use `dispatcher` that is slight
+// extension over polymorphic dispatch provided by method.
+// This allows models to extend implementations of this by
+// providing predicates:
+//
+// modelFor.when($ => $ && $.nodeName === "tab", findTabById($.id))
+const modelFor = dispatcher("modelFor");
+exports.modelFor = modelFor;
diff --git a/components/jetpack/sdk/net/url.js b/components/jetpack/sdk/net/url.js
new file mode 100644
index 000000000..5502171ee
--- /dev/null
+++ b/components/jetpack/sdk/net/url.js
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Ci, Cu, components } = require("chrome");
+
+const { defer } = require("../core/promise");
+const { merge } = require("../util/object");
+
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+/**
+ * Reads a URI and returns a promise.
+ *
+ * @param uri {string} The URI to read
+ * @param [options] {object} This parameter can have any or all of the following
+ * fields: `charset`. By default the `charset` is set to 'UTF-8'.
+ *
+ * @returns {promise} The promise that will be resolved with the content of the
+ * URL given.
+ *
+ * @example
+ * let promise = readURI('resource://gre/modules/NetUtil.jsm', {
+ * charset: 'US-ASCII'
+ * });
+ */
+function readURI(uri, options) {
+ options = options || {};
+ let charset = options.charset || 'UTF-8';
+
+ let channel = NetUtil.newChannel({
+ uri: NetUtil.newURI(uri, charset),
+ loadUsingSystemPrincipal: true});
+
+ let { promise, resolve, reject } = defer();
+
+ try {
+ NetUtil.asyncFetch(channel, function (stream, result) {
+ if (components.isSuccessCode(result)) {
+ let count = stream.available();
+ let data = NetUtil.readInputStreamToString(stream, count, { charset : charset });
+
+ resolve(data);
+ } else {
+ reject("Failed to read: '" + uri + "' (Error Code: " + result + ")");
+ }
+ });
+ }
+ catch (e) {
+ reject("Failed to read: '" + uri + "' (Error: " + e.message + ")");
+ }
+
+ return promise;
+}
+
+exports.readURI = readURI;
+
+/**
+ * Reads a URI synchronously.
+ * This function is intentionally undocumented to favorites the `readURI` usage.
+ *
+ * @param uri {string} The URI to read
+ * @param [charset] {string} The character set to use when read the content of
+ * the `uri` given. By default is set to 'UTF-8'.
+ *
+ * @returns {string} The content of the URI given.
+ *
+ * @example
+ * let data = readURISync('resource://gre/modules/NetUtil.jsm');
+ */
+function readURISync(uri, charset) {
+ charset = typeof charset === "string" ? charset : "UTF-8";
+
+ let channel = NetUtil.newChannel({
+ uri: NetUtil.newURI(uri, charset),
+ loadUsingSystemPrincipal: true});
+ let stream = channel.open2();
+
+ let count = stream.available();
+ let data = NetUtil.readInputStreamToString(stream, count, { charset : charset });
+
+ stream.close();
+
+ return data;
+}
+
+exports.readURISync = readURISync;
diff --git a/components/jetpack/sdk/net/xhr.js b/components/jetpack/sdk/net/xhr.js
new file mode 100644
index 000000000..415b9cbf4
--- /dev/null
+++ b/components/jetpack/sdk/net/xhr.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { deprecateFunction } = require("../util/deprecate");
+const { Cc, Ci } = require("chrome");
+const XMLHttpRequest = require("../addon/window").window.XMLHttpRequest;
+
+Object.defineProperties(XMLHttpRequest.prototype, {
+ mozBackgroundRequest: {
+ value: true,
+ },
+ forceAllowThirdPartyCookie: {
+ configurable: true,
+ value: deprecateFunction(function() {
+ forceAllowThirdPartyCookie(this);
+
+ }, "`xhr.forceAllowThirdPartyCookie()` is deprecated, please use" +
+ "`require('sdk/net/xhr').forceAllowThirdPartyCookie(request)` instead")
+ }
+});
+exports.XMLHttpRequest = XMLHttpRequest;
+
+function forceAllowThirdPartyCookie(xhr) {
+ if (xhr.channel instanceof Ci.nsIHttpChannelInternal)
+ xhr.channel.forceAllowThirdPartyCookie = true;
+}
+exports.forceAllowThirdPartyCookie = forceAllowThirdPartyCookie;
+
+// No need to handle add-on unloads as addon/window is closed at unload
+// and it will take down all the associated requests.
diff --git a/components/jetpack/sdk/notifications.js b/components/jetpack/sdk/notifications.js
new file mode 100644
index 000000000..752e08fb1
--- /dev/null
+++ b/components/jetpack/sdk/notifications.js
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { Cc, Ci, Cr } = require("chrome");
+const apiUtils = require("./deprecated/api-utils");
+const { isString, isUndefined, instanceOf } = require('./lang/type');
+const { URL, isLocalURL } = require('./url');
+const { data } = require('./self');
+
+const NOTIFICATION_DIRECTIONS = ["auto", "ltr", "rtl"];
+
+try {
+ let alertServ = Cc["@mozilla.org/alerts-service;1"].
+ getService(Ci.nsIAlertsService);
+
+ // The unit test sets this to a mock notification function.
+ var notify = alertServ.showAlertNotification.bind(alertServ);
+}
+catch (err) {
+ // An exception will be thrown if the platform doesn't provide an alert
+ // service, e.g., if Growl is not installed on OS X. In that case, use a
+ // mock notification function that just logs to the console.
+ notify = notifyUsingConsole;
+}
+
+exports.notify = function notifications_notify(options) {
+ let valOpts = validateOptions(options);
+ let clickObserver = !valOpts.onClick ? null : {
+ observe: (subject, topic, data) => {
+ if (topic === "alertclickcallback") {
+ try {
+ valOpts.onClick.call(exports, valOpts.data);
+ }
+ catch(e) {
+ console.exception(e);
+ }
+ }
+ }
+ };
+ function notifyWithOpts(notifyFn) {
+ let { iconURL } = valOpts;
+ iconURL = iconURL && isLocalURL(iconURL) ? data.url(iconURL) : iconURL;
+
+ notifyFn(iconURL, valOpts.title, valOpts.text, !!clickObserver,
+ valOpts.data, clickObserver, valOpts.tag, valOpts.dir, valOpts.lang);
+ }
+ try {
+ notifyWithOpts(notify);
+ }
+ catch (err) {
+ if (err instanceof Ci.nsIException && err.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
+ console.warn("The notification icon named by " + iconURL +
+ " does not exist. A default icon will be used instead.");
+ delete valOpts.iconURL;
+ notifyWithOpts(notify);
+ }
+ else {
+ notifyWithOpts(notifyUsingConsole);
+ }
+ }
+};
+
+function notifyUsingConsole(iconURL, title, text) {
+ title = title ? "[" + title + "]" : "";
+ text = text || "";
+ let str = [title, text].filter(s => s).join(" ");
+ console.log(str);
+}
+
+function validateOptions(options) {
+ return apiUtils.validateOptions(options, {
+ data: {
+ is: ["string", "undefined"]
+ },
+ iconURL: {
+ is: ["string", "undefined", "object"],
+ ok: function(value) {
+ return isUndefined(value) || isString(value) || (value instanceof URL);
+ },
+ msg: "`iconURL` must be a string or an URL instance."
+ },
+ onClick: {
+ is: ["function", "undefined"]
+ },
+ text: {
+ is: ["string", "undefined", "number"]
+ },
+ title: {
+ is: ["string", "undefined", "number"]
+ },
+ tag: {
+ is: ["string", "undefined", "number"]
+ },
+ dir: {
+ is: ["string", "undefined"],
+ ok: function(value) {
+ return isUndefined(value) || ~NOTIFICATION_DIRECTIONS.indexOf(value);
+ },
+ msg: '`dir` option must be one of: "auto", "ltr" or "rtl".'
+ },
+ lang: {
+ is: ["string", "undefined"]
+ }
+ });
+}
diff --git a/components/jetpack/sdk/output/system.js b/components/jetpack/sdk/output/system.js
new file mode 100644
index 000000000..4fb16dcd5
--- /dev/null
+++ b/components/jetpack/sdk/output/system.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/. */
+"use strict";
+
+const { Cc, Ci, Cr } = require("chrome");
+const { Input, start, stop, receive, outputs } = require("../event/utils");
+const { id: addonID } = require("../self");
+const { setImmediate } = require("../timers");
+const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
+ getService(Ci.nsIObserverService);
+
+const NOT_AN_INPUT = "OutputPort can be used only for sending messages";
+
+// `OutputPort` creates a port to which messages can be send. Those
+// messages are actually disptached as `subject`'s of the observer
+// notifications. This is handy for communicating between different
+// components of the SDK. By default messages are dispatched
+// asynchronously, although `options.sync` can be used to make them
+// synchronous. If `options.id` is given `topic` for observer
+// notifications is generated by namespacing it, to avoid spamming
+// other SDK add-ons. It's also possible to provide `options.topic`
+// to use excat `topic` without namespacing it.
+//
+// Note: Symmetric `new InputPort({ id: "x" })` instances can be used to
+// receive messages send to the instances of `new OutputPort({ id: "x" })`.
+const OutputPort = function({id, topic, sync}) {
+ this.id = id || topic;
+ this.sync = !!sync;
+ this.topic = topic || "sdk:" + addonID + ":" + id;
+};
+// OutputPort extends base signal type to implement same message
+// receiving interface.
+OutputPort.prototype = new Input();
+OutputPort.constructor = OutputPort;
+
+// OutputPort can not be consumed there for starting or stopping it
+// is not supported.
+OutputPort.prototype[start] = _ => { throw TypeError(NOT_AN_INPUT); };
+OutputPort.prototype[stop] = _ => { throw TypeError(NOT_AN_INPUT); };
+
+// Port reecives message send to it, which will be dispatched via
+// observer notification service.
+OutputPort.receive = ({topic, sync}, message) => {
+ const type = typeof(message);
+ const supported = message === null ||
+ type === "object" ||
+ type === "function";
+
+ // There is no sensible way to wrap JS primitives that would make sense
+ // for general observer notification users. It's also probably not very
+ // useful to dispatch JS primitives as subject of observer service, there
+ // for we do not support those use cases.
+ if (!supported)
+ throw new TypeError("Unsupproted message type: `" + type + "`");
+
+ // Normalize `message` to create a valid observer notification `subject`.
+ // If `message` is `null`, implements `nsISupports` interface or already
+ // represents wrapped JS object use it as is. Otherwise create a wrapped
+ // object so that observers could receive it.
+ const subject = message === null ? null :
+ message instanceof Ci.nsISupports ? message :
+ message.wrappedJSObject ? message :
+ {wrappedJSObject: message};
+ if (sync)
+ notifyObservers(subject, topic, null);
+ else
+ setImmediate(notifyObservers, subject, topic, null);
+};
+OutputPort.prototype[receive] = OutputPort.receive;
+exports.OutputPort = OutputPort;
diff --git a/components/jetpack/sdk/page-mod.js b/components/jetpack/sdk/page-mod.js
new file mode 100644
index 000000000..538be2732
--- /dev/null
+++ b/components/jetpack/sdk/page-mod.js
@@ -0,0 +1,190 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { contract: loaderContract } = require('./content/loader');
+const { contract } = require('./util/contract');
+const { WorkerHost, connect } = require('./content/utils');
+const { Class } = require('./core/heritage');
+const { Disposable } = require('./core/disposable');
+const { Worker } = require('./content/worker');
+const { EventTarget } = require('./event/target');
+const { on, emit, once, setListeners } = require('./event/core');
+const { isRegExp, isUndefined } = require('./lang/type');
+const { merge, omit } = require('./util/object');
+const { remove, has, hasAny } = require("./util/array");
+const { Rules } = require("./util/rules");
+const { processes, frames, remoteRequire } = require('./remote/parent');
+remoteRequire('sdk/content/page-mod');
+
+const pagemods = new Map();
+const workers = new Map();
+const models = new WeakMap();
+var modelFor = (mod) => models.get(mod);
+var workerFor = (mod) => workers.get(mod)[0];
+
+// Helper functions
+var isRegExpOrString = (v) => isRegExp(v) || typeof v === 'string';
+
+var PAGEMOD_ID = 0;
+
+// Validation Contracts
+const modOptions = {
+ // contentStyle* / contentScript* are sharing the same validation constraints,
+ // so they can be mostly reused, except for the messages.
+ contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
+ msg: 'The `contentStyle` option must be a string or an array of strings.'
+ }),
+ contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
+ msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
+ }),
+ include: {
+ is: ['string', 'array', 'regexp'],
+ ok: (rule) => {
+ if (isRegExpOrString(rule))
+ return true;
+ if (Array.isArray(rule) && rule.length > 0)
+ return rule.every(isRegExpOrString);
+ return false;
+ },
+ msg: 'The `include` option must always contain atleast one rule as a string, regular expression, or an array of strings and regular expressions.'
+ },
+ exclude: {
+ is: ['string', 'array', 'regexp', 'undefined'],
+ ok: (rule) => {
+ if (isRegExpOrString(rule) || isUndefined(rule))
+ return true;
+ if (Array.isArray(rule) && rule.length > 0)
+ return rule.every(isRegExpOrString);
+ return false;
+ },
+ msg: 'If set, the `exclude` option must always contain at least one ' +
+ 'rule as a string, regular expression, or an array of strings and ' +
+ 'regular expressions.'
+ },
+ attachTo: {
+ is: ['string', 'array', 'undefined'],
+ map: function (attachTo) {
+ if (!attachTo) return ['top', 'frame'];
+ if (typeof attachTo === 'string') return [attachTo];
+ return attachTo;
+ },
+ ok: function (attachTo) {
+ return hasAny(attachTo, ['top', 'frame']) &&
+ attachTo.every(has.bind(null, ['top', 'frame', 'existing']));
+ },
+ msg: 'The `attachTo` option must be a string or an array of strings. ' +
+ 'The only valid options are "existing", "top" and "frame", and must ' +
+ 'contain at least "top" or "frame" values.'
+ },
+};
+
+const modContract = contract(merge({}, loaderContract.rules, modOptions));
+
+/**
+ * PageMod constructor (exported below).
+ * @constructor
+ */
+const PageMod = Class({
+ implements: [
+ modContract.properties(modelFor),
+ EventTarget,
+ Disposable,
+ ],
+ extends: WorkerHost(workerFor),
+ setup: function PageMod(options) {
+ let mod = this;
+ let model = modContract(options);
+ models.set(this, model);
+ model.id = PAGEMOD_ID++;
+
+ let include = model.include;
+ model.include = Rules();
+ model.include.add.apply(model.include, [].concat(include));
+
+ let exclude = isUndefined(model.exclude) ? [] : model.exclude;
+ model.exclude = Rules();
+ model.exclude.add.apply(model.exclude, [].concat(exclude));
+
+ // Set listeners on {PageMod} itself, not the underlying worker,
+ // like `onMessage`, as it'll get piped.
+ setListeners(this, options);
+
+ pagemods.set(model.id, this);
+ workers.set(this, []);
+
+ function serializeRules(rules) {
+ for (let rule of rules) {
+ yield isRegExp(rule) ? { type: "regexp", pattern: rule.source, flags: rule.flags }
+ : { type: "string", value: rule };
+ }
+ }
+
+ model.childOptions = omit(model, ["include", "exclude", "contentScriptOptions"]);
+ model.childOptions.include = [...serializeRules(model.include)];
+ model.childOptions.exclude = [...serializeRules(model.exclude)];
+ model.childOptions.contentScriptOptions = model.contentScriptOptions ?
+ JSON.stringify(model.contentScriptOptions) :
+ null;
+
+ processes.port.emit('sdk/page-mod/create', model.childOptions);
+ },
+
+ dispose: function(reason) {
+ processes.port.emit('sdk/page-mod/destroy', modelFor(this).id);
+ pagemods.delete(modelFor(this).id);
+ workers.delete(this);
+ },
+
+ destroy: function(reason) {
+ // Explicit destroy call, i.e. not via unload so destroy the workers
+ let list = workers.get(this);
+ if (!list)
+ return;
+
+ // Triggers dispose which will cause the child page-mod to be destroyed
+ Disposable.prototype.destroy.call(this, reason);
+
+ // Destroy any active workers
+ for (let worker of list)
+ worker.destroy(reason);
+ }
+});
+exports.PageMod = PageMod;
+
+// Whenever a new process starts send over the list of page-mods
+processes.forEvery(process => {
+ for (let mod of pagemods.values())
+ process.port.emit('sdk/page-mod/create', modelFor(mod).childOptions);
+});
+
+frames.port.on('sdk/page-mod/worker-create', (frame, modId, workerOptions) => {
+ let mod = pagemods.get(modId);
+ if (!mod)
+ return;
+
+ // Attach the parent side of the worker to the child
+ let worker = Worker();
+
+ workers.get(mod).unshift(worker);
+ worker.on('*', (event, ...args) => {
+ // page-mod's "attach" event needs to be passed a worker
+ if (event === 'attach')
+ emit(mod, event, worker)
+ else
+ emit(mod, event, ...args);
+ });
+
+ worker.on('detach', () => {
+ let array = workers.get(mod);
+ if (array)
+ remove(array, worker);
+ });
+
+ connect(worker, frame, workerOptions);
+});
diff --git a/components/jetpack/sdk/page-mod/match-pattern.js b/components/jetpack/sdk/page-mod/match-pattern.js
new file mode 100644
index 000000000..afbbd401e
--- /dev/null
+++ b/components/jetpack/sdk/page-mod/match-pattern.js
@@ -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/. */
+ "use strict";
+
+var { deprecateUsage } = require("../util/deprecate");
+
+deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead");
+
+module.exports = require("../util/match-pattern");
diff --git a/components/jetpack/sdk/page-worker.js b/components/jetpack/sdk/page-worker.js
new file mode 100644
index 000000000..837cf774b
--- /dev/null
+++ b/components/jetpack/sdk/page-worker.js
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { Class } = require('./core/heritage');
+const { ns } = require('./core/namespace');
+const { pipe, stripListeners } = require('./event/utils');
+const { connect, destroy, WorkerHost } = require('./content/utils');
+const { Worker } = require('./content/worker');
+const { Disposable } = require('./core/disposable');
+const { EventTarget } = require('./event/target');
+const { setListeners } = require('./event/core');
+const { window } = require('./addon/window');
+const { create: makeFrame, getDocShell } = require('./frame/utils');
+const { contract } = require('./util/contract');
+const { contract: loaderContract } = require('./content/loader');
+const { Rules } = require('./util/rules');
+const { merge } = require('./util/object');
+const { uuid } = require('./util/uuid');
+const { useRemoteProcesses, remoteRequire, frames } = require("./remote/parent");
+remoteRequire("sdk/content/page-worker");
+
+const workers = new WeakMap();
+const pages = new Map();
+
+const internal = ns();
+
+let workerFor = (page) => workers.get(page);
+let isDisposed = (page) => !pages.has(internal(page).id);
+
+// The frame is used to ensure we have a remote process to load workers in
+let remoteFrame = null;
+let framePromise = null;
+function getFrame() {
+ if (framePromise)
+ return framePromise;
+
+ framePromise = new Promise(resolve => {
+ let view = makeFrame(window.document, {
+ namespaceURI: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ nodeName: "iframe",
+ type: "content",
+ remote: useRemoteProcesses,
+ uri: "about:blank"
+ });
+
+ // Wait for the remote side to connect
+ let listener = (frame) => {
+ if (frame.frameElement != view)
+ return;
+ frames.off("attach", listener);
+ remoteFrame = frame;
+ resolve(frame);
+ }
+ frames.on("attach", listener);
+ });
+ return framePromise;
+}
+
+var pageContract = contract(merge({
+ allow: {
+ is: ['object', 'undefined', 'null'],
+ map: function (allow) { return { script: !allow || allow.script !== false }}
+ },
+ onMessage: {
+ is: ['function', 'undefined']
+ },
+ include: {
+ is: ['string', 'array', 'regexp', 'undefined']
+ },
+ contentScriptWhen: {
+ is: ['string', 'undefined'],
+ map: (when) => when || "end"
+ }
+}, loaderContract.rules));
+
+function enableScript (page) {
+ getDocShell(viewFor(page)).allowJavascript = true;
+}
+
+function disableScript (page) {
+ getDocShell(viewFor(page)).allowJavascript = false;
+}
+
+function Allow (page) {
+ return {
+ get script() {
+ return internal(page).options.allow.script;
+ },
+ set script(value) {
+ internal(page).options.allow.script = value;
+
+ if (isDisposed(page))
+ return;
+
+ remoteFrame.port.emit("sdk/frame/set", internal(page).id, { allowScript: value });
+ }
+ };
+}
+
+function isValidURL(page, url) {
+ return !page.rules || page.rules.matchesAny(url);
+}
+
+const Page = Class({
+ implements: [
+ EventTarget,
+ Disposable
+ ],
+ extends: WorkerHost(workerFor),
+ setup: function Page(options) {
+ options = pageContract(options);
+ // Sanitize the options
+ if ("contentScriptOptions" in options)
+ options.contentScriptOptions = JSON.stringify(options.contentScriptOptions);
+
+ internal(this).id = uuid().toString();
+ internal(this).options = options;
+
+ for (let prop of ['contentScriptFile', 'contentScript', 'contentScriptWhen']) {
+ this[prop] = options[prop];
+ }
+
+ pages.set(internal(this).id, this);
+
+ // Set listeners on the {Page} object itself, not the underlying worker,
+ // like `onMessage`, as it gets piped
+ setListeners(this, options);
+ let worker = new Worker(stripListeners(options));
+ workers.set(this, worker);
+ pipe(worker, this);
+
+ if (options.include) {
+ this.rules = Rules();
+ this.rules.add.apply(this.rules, [].concat(options.include));
+ }
+
+ getFrame().then(frame => {
+ if (isDisposed(this))
+ return;
+
+ frame.port.emit("sdk/frame/create", internal(this).id, stripListeners(options));
+ });
+ },
+ get allow() { return Allow(this); },
+ set allow(value) {
+ if (isDisposed(this))
+ return;
+ this.allow.script = pageContract({ allow: value }).allow.script;
+ },
+ get contentURL() {
+ return internal(this).options.contentURL;
+ },
+ set contentURL(value) {
+ if (!isValidURL(this, value))
+ return;
+ internal(this).options.contentURL = value;
+ if (isDisposed(this))
+ return;
+
+ remoteFrame.port.emit("sdk/frame/set", internal(this).id, { contentURL: value });
+ },
+ dispose: function () {
+ if (isDisposed(this))
+ return;
+ pages.delete(internal(this).id);
+ let worker = workerFor(this);
+ if (worker)
+ destroy(worker);
+ remoteFrame.port.emit("sdk/frame/destroy", internal(this).id);
+
+ // Destroy the remote frame if all the pages have been destroyed
+ if (pages.size == 0) {
+ framePromise = null;
+ remoteFrame.frameElement.remove();
+ remoteFrame = null;
+ }
+ },
+ toString: function () { return '[object Page]' }
+});
+
+exports.Page = Page;
+
+frames.port.on("sdk/frame/connect", (frame, id, params) => {
+ let page = pages.get(id);
+ if (!page)
+ return;
+ connect(workerFor(page), frame, params);
+});
diff --git a/components/jetpack/sdk/panel.js b/components/jetpack/sdk/panel.js
new file mode 100644
index 000000000..34cde2edd
--- /dev/null
+++ b/components/jetpack/sdk/panel.js
@@ -0,0 +1,428 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+// The panel module currently supports only Firefox and SeaMonkey.
+// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
+module.metadata = {
+ "stability": "stable",
+ "engines": {
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+const { Cu, Ci } = require("chrome");
+const { setTimeout } = require('./timers');
+const { Class } = require("./core/heritage");
+const { merge } = require("./util/object");
+const { WorkerHost } = require("./content/utils");
+const { Worker } = require("./deprecated/sync-worker");
+const { Disposable } = require("./core/disposable");
+const { WeakReference } = require('./core/reference');
+const { contract: loaderContract } = require("./content/loader");
+const { contract } = require("./util/contract");
+const { on, off, emit, setListeners } = require("./event/core");
+const { EventTarget } = require("./event/target");
+const domPanel = require("./panel/utils");
+const { getDocShell } = require('./frame/utils');
+const { events } = require("./panel/events");
+const systemEvents = require("./system/events");
+const { filter, pipe, stripListeners } = require("./event/utils");
+const { getNodeView, getActiveView } = require("./view/core");
+const { isNil, isObject, isNumber } = require("./lang/type");
+const { getAttachEventType } = require("./content/utils");
+const { number, boolean, object } = require('./deprecated/api-utils');
+const { Style } = require("./stylesheet/style");
+const { attach, detach } = require("./content/mod");
+
+var isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
+ some(value => isNumber(value) && !isNaN(value));
+
+var isSDKObj = obj => obj instanceof Class;
+
+var rectContract = contract({
+ top: number,
+ right: number,
+ bottom: number,
+ left: number
+});
+
+var position = {
+ is: object,
+ map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v),
+ ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)),
+ msg: 'The option "position" must be a SDK object registered as anchor; ' +
+ 'or an object with one or more of the following keys set to numeric ' +
+ 'values: top, right, bottom, left.'
+}
+
+var displayContract = contract({
+ width: number,
+ height: number,
+ focus: boolean,
+ position: position
+});
+
+var panelContract = contract(merge({
+ // contentStyle* / contentScript* are sharing the same validation constraints,
+ // so they can be mostly reused, except for the messages.
+ contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
+ msg: 'The `contentStyle` option must be a string or an array of strings.'
+ }),
+ contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
+ msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
+ }),
+ contextMenu: boolean,
+ allow: {
+ is: ['object', 'undefined', 'null'],
+ map: function (allow) { return { script: !allow || allow.script !== false }}
+ },
+}, displayContract.rules, loaderContract.rules));
+
+function Allow(panel) {
+ return {
+ get script() { return getDocShell(viewFor(panel).backgroundFrame).allowJavascript; },
+ set script(value) { return setScriptState(panel, value); },
+ };
+}
+
+function setScriptState(panel, value) {
+ let view = viewFor(panel);
+ getDocShell(view.backgroundFrame).allowJavascript = value;
+ getDocShell(view.viewFrame).allowJavascript = value;
+ view.setAttribute("sdkscriptenabled", "" + value);
+}
+
+function isDisposed(panel) {
+ return !views.has(panel);
+}
+
+var panels = new WeakMap();
+var models = new WeakMap();
+var views = new WeakMap();
+var workers = new WeakMap();
+var styles = new WeakMap();
+
+const viewFor = (panel) => views.get(panel);
+const modelFor = (panel) => models.get(panel);
+const panelFor = (view) => panels.get(view);
+const workerFor = (panel) => workers.get(panel);
+const styleFor = (panel) => styles.get(panel);
+
+function getPanelFromWeakRef(weakRef) {
+ if (!weakRef) {
+ return null;
+ }
+ let panel = weakRef.get();
+ if (!panel) {
+ return null;
+ }
+ if (isDisposed(panel)) {
+ return null;
+ }
+ return panel;
+}
+
+var SinglePanelManager = {
+ visiblePanel: null,
+ enqueuedPanel: null,
+ enqueuedPanelCallback: null,
+ // Calls |callback| with no arguments when the panel may be shown.
+ requestOpen: function(panelToOpen, callback) {
+ let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
+ if (currentPanel || SinglePanelManager.enqueuedPanel) {
+ SinglePanelManager.enqueuedPanel = Cu.getWeakReference(panelToOpen);
+ SinglePanelManager.enqueuedPanelCallback = callback;
+ if (currentPanel && currentPanel.isShowing) {
+ currentPanel.hide();
+ }
+ } else {
+ SinglePanelManager.notifyPanelCanOpen(panelToOpen, callback);
+ }
+ },
+ notifyPanelCanOpen: function(panel, callback) {
+ let view = viewFor(panel);
+ // Can't pass an arrow function as the event handler because we need to be
+ // able to call |removeEventListener| later.
+ view.addEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
+ view.addEventListener("popupshown", SinglePanelManager.onVisiblePanelShown, false);
+ SinglePanelManager.enqueuedPanel = null;
+ SinglePanelManager.enqueuedPanelCallback = null;
+ SinglePanelManager.visiblePanel = Cu.getWeakReference(panel);
+ callback();
+ },
+ onVisiblePanelShown: function(event) {
+ let panel = panelFor(event.target);
+ if (SinglePanelManager.enqueuedPanel) {
+ // Another panel started waiting for |panel| to close before |panel| was
+ // even done opening.
+ panel.hide();
+ }
+ },
+ onVisiblePanelHidden: function(event) {
+ let view = event.target;
+ let panel = panelFor(view);
+ let currentPanel = getPanelFromWeakRef(SinglePanelManager.visiblePanel);
+ if (currentPanel && currentPanel != panel) {
+ return;
+ }
+ SinglePanelManager.visiblePanel = null;
+ view.removeEventListener("popuphidden", SinglePanelManager.onVisiblePanelHidden, true);
+ view.removeEventListener("popupshown", SinglePanelManager.onVisiblePanelShown, false);
+ let nextPanel = getPanelFromWeakRef(SinglePanelManager.enqueuedPanel);
+ let nextPanelCallback = SinglePanelManager.enqueuedPanelCallback;
+ if (nextPanel) {
+ SinglePanelManager.notifyPanelCanOpen(nextPanel, nextPanelCallback);
+ }
+ }
+};
+
+const Panel = Class({
+ implements: [
+ // Generate accessors for the validated properties that update model on
+ // set and return values from model on get.
+ panelContract.properties(modelFor),
+ EventTarget,
+ Disposable,
+ WeakReference
+ ],
+ extends: WorkerHost(workerFor),
+ setup: function setup(options) {
+ let model = merge({
+ defaultWidth: 320,
+ defaultHeight: 240,
+ focus: true,
+ position: Object.freeze({}),
+ contextMenu: false
+ }, panelContract(options));
+ model.ready = false;
+ models.set(this, model);
+
+ if (model.contentStyle || model.contentStyleFile) {
+ styles.set(this, Style({
+ uri: model.contentStyleFile,
+ source: model.contentStyle
+ }));
+ }
+
+ // Setup view
+ let viewOptions = {allowJavascript: !model.allow || (model.allow.script !== false)};
+ let view = domPanel.make(null, viewOptions);
+ panels.set(view, this);
+ views.set(this, view);
+
+ // Load panel content.
+ domPanel.setURL(view, model.contentURL);
+
+ // Allow context menu
+ domPanel.allowContextMenu(view, model.contextMenu);
+
+ // Setup listeners.
+ setListeners(this, options);
+ let worker = new Worker(stripListeners(options));
+ workers.set(this, worker);
+
+ // pipe events from worker to a panel.
+ pipe(worker, this);
+ },
+ dispose: function dispose() {
+ this.hide();
+ off(this);
+
+ workerFor(this).destroy();
+ detach(styleFor(this));
+
+ domPanel.dispose(viewFor(this));
+
+ // Release circular reference between view and panel instance. This
+ // way view will be GC-ed. And panel as well once all the other refs
+ // will be removed from it.
+ views.delete(this);
+ },
+ /* Public API: Panel.width */
+ get width() {
+ return modelFor(this).width;
+ },
+ set width(value) {
+ this.resize(value, this.height);
+ },
+ /* Public API: Panel.height */
+ get height() {
+ return modelFor(this).height;
+ },
+ set height(value) {
+ this.resize(this.width, value);
+ },
+
+ /* Public API: Panel.focus */
+ get focus() {
+ return modelFor(this).focus;
+ },
+
+ /* Public API: Panel.position */
+ get position() {
+ return modelFor(this).position;
+ },
+
+ /* Public API: Panel.contextMenu */
+ get contextMenu() {
+ return modelFor(this).contextMenu;
+ },
+ set contextMenu(allow) {
+ let model = modelFor(this);
+ model.contextMenu = panelContract({ contextMenu: allow }).contextMenu;
+ domPanel.allowContextMenu(viewFor(this), model.contextMenu);
+ },
+
+ get contentURL() {
+ return modelFor(this).contentURL;
+ },
+ set contentURL(value) {
+ let model = modelFor(this);
+ model.contentURL = panelContract({ contentURL: value }).contentURL;
+ domPanel.setURL(viewFor(this), model.contentURL);
+ // Detach worker so that messages send will be queued until it's
+ // reatached once panel content is ready.
+ workerFor(this).detach();
+ },
+
+ get allow() { return Allow(this); },
+ set allow(value) {
+ let allowJavascript = panelContract({ allow: value }).allow.script;
+ return setScriptState(this, value);
+ },
+
+ /* Public API: Panel.isShowing */
+ get isShowing() {
+ return !isDisposed(this) && domPanel.isOpen(viewFor(this));
+ },
+
+ /* Public API: Panel.show */
+ show: function show(options={}, anchor) {
+ SinglePanelManager.requestOpen(this, () => {
+ if (options instanceof Ci.nsIDOMElement) {
+ [anchor, options] = [options, null];
+ }
+
+ if (anchor instanceof Ci.nsIDOMElement) {
+ console.warn(
+ "Passing a DOM node to Panel.show() method is an unsupported " +
+ "feature that will be soon replaced. " +
+ "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877"
+ );
+ }
+
+ let model = modelFor(this);
+ let view = viewFor(this);
+ let anchorView = getNodeView(anchor || options.position || model.position);
+
+ options = merge({
+ position: model.position,
+ width: model.width,
+ height: model.height,
+ defaultWidth: model.defaultWidth,
+ defaultHeight: model.defaultHeight,
+ focus: model.focus,
+ contextMenu: model.contextMenu
+ }, displayContract(options));
+
+ if (!isDisposed(this)) {
+ domPanel.show(view, options, anchorView);
+ }
+ });
+ return this;
+ },
+
+ /* Public API: Panel.hide */
+ hide: function hide() {
+ // Quit immediately if panel is disposed or there is no state change.
+ domPanel.close(viewFor(this));
+
+ return this;
+ },
+
+ /* Public API: Panel.resize */
+ resize: function resize(width, height) {
+ let model = modelFor(this);
+ let view = viewFor(this);
+ let change = panelContract({
+ width: width || model.width || model.defaultWidth,
+ height: height || model.height || model.defaultHeight
+ });
+
+ model.width = change.width
+ model.height = change.height
+
+ domPanel.resize(view, model.width, model.height);
+
+ return this;
+ }
+});
+exports.Panel = Panel;
+
+// Note must be defined only after value to `Panel` is assigned.
+getActiveView.define(Panel, viewFor);
+
+// Filter panel events to only panels that are create by this module.
+var panelEvents = filter(events, ({target}) => panelFor(target));
+
+// Panel events emitted after panel has being shown.
+var shows = filter(panelEvents, ({type}) => type === "popupshown");
+
+// Panel events emitted after panel became hidden.
+var hides = filter(panelEvents, ({type}) => type === "popuphidden");
+
+// Panel events emitted after content inside panel is ready. For different
+// panels ready may mean different state based on `contentScriptWhen` attribute.
+// Weather given event represents readyness is detected by `getAttachEventType`
+// helper function.
+var ready = filter(panelEvents, ({type, target}) =>
+ getAttachEventType(modelFor(panelFor(target))) === type);
+
+// Panel event emitted when the contents of the panel has been loaded.
+var readyToShow = filter(panelEvents, ({type}) => type === "DOMContentLoaded");
+
+// Styles should be always added as soon as possible, and doesn't makes them
+// depends on `contentScriptWhen`
+var start = filter(panelEvents, ({type}) => type === "document-element-inserted");
+
+// Forward panel show / hide events to panel's own event listeners.
+on(shows, "data", ({target}) => {
+ let panel = panelFor(target);
+ if (modelFor(panel).ready)
+ emit(panel, "show");
+});
+
+on(hides, "data", ({target}) => {
+ let panel = panelFor(target);
+ if (modelFor(panel).ready)
+ emit(panel, "hide");
+});
+
+on(ready, "data", ({target}) => {
+ let panel = panelFor(target);
+ let window = domPanel.getContentDocument(target).defaultView;
+
+ workerFor(panel).attach(window);
+});
+
+on(readyToShow, "data", ({target}) => {
+ let panel = panelFor(target);
+
+ if (!modelFor(panel).ready) {
+ modelFor(panel).ready = true;
+
+ if (viewFor(panel).state == "open")
+ emit(panel, "show");
+ }
+});
+
+on(start, "data", ({target}) => {
+ let panel = panelFor(target);
+ let window = domPanel.getContentDocument(target).defaultView;
+
+ attach(styleFor(panel), window);
+});
diff --git a/components/jetpack/sdk/panel/events.js b/components/jetpack/sdk/panel/events.js
new file mode 100644
index 000000000..f3040a11d
--- /dev/null
+++ b/components/jetpack/sdk/panel/events.js
@@ -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/. */
+
+"use strict";
+
+// This module basically translates system/events to a SDK standard events
+// so that `map`, `filter` and other utilities could be used with them.
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const events = require("../system/events");
+const { emit } = require("../event/core");
+
+var channel = {};
+
+function forward({ subject, type, data }) {
+ return emit(channel, "data", { target: subject, type: type, data: data });
+}
+
+["popupshowing", "popuphiding", "popupshown", "popuphidden",
+"document-element-inserted", "DOMContentLoaded", "load"
+].forEach(type => events.on(type, forward));
+
+exports.events = channel;
diff --git a/components/jetpack/sdk/panel/utils.js b/components/jetpack/sdk/panel/utils.js
new file mode 100644
index 000000000..c85b274bc
--- /dev/null
+++ b/components/jetpack/sdk/panel/utils.js
@@ -0,0 +1,451 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci } = require("chrome");
+const { Services } = require("resource://gre/modules/Services.jsm");
+const { setTimeout } = require("../timers");
+const { platform } = require("../system");
+const { getMostRecentBrowserWindow, getOwnerBrowserWindow,
+ getHiddenWindow, getScreenPixelsPerCSSPixel } = require("../window/utils");
+
+const { create: createFrame, swapFrameLoaders, getDocShell } = require("../frame/utils");
+const { window: addonWindow } = require("../addon/window");
+const { isNil } = require("../lang/type");
+const { data } = require('../self');
+
+const events = require("../system/events");
+
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function calculateRegion({ position, width, height, defaultWidth, defaultHeight }, rect) {
+ position = position || {};
+
+ let x, y;
+
+ let hasTop = !isNil(position.top);
+ let hasRight = !isNil(position.right);
+ let hasBottom = !isNil(position.bottom);
+ let hasLeft = !isNil(position.left);
+ let hasWidth = !isNil(width);
+ let hasHeight = !isNil(height);
+
+ // if width is not specified by constructor or show's options, then get
+ // the default width
+ if (!hasWidth)
+ width = defaultWidth;
+
+ // if height is not specified by constructor or show's options, then get
+ // the default height
+ if (!hasHeight)
+ height = defaultHeight;
+
+ // default position is centered
+ x = (rect.right - width) / 2;
+ y = (rect.top + rect.bottom - height) / 2;
+
+ if (hasTop) {
+ y = rect.top + position.top;
+
+ if (hasBottom && !hasHeight)
+ height = rect.bottom - position.bottom - y;
+ }
+ else if (hasBottom) {
+ y = rect.bottom - position.bottom - height;
+ }
+
+ if (hasLeft) {
+ x = position.left;
+
+ if (hasRight && !hasWidth)
+ width = rect.right - position.right - x;
+ }
+ else if (hasRight) {
+ x = rect.right - width - position.right;
+ }
+
+ return {x: x, y: y, width: width, height: height};
+}
+
+function open(panel, options, anchor) {
+ // Wait for the XBL binding to be constructed
+ if (!panel.openPopup) setTimeout(open, 50, panel, options, anchor);
+ else display(panel, options, anchor);
+}
+exports.open = open;
+
+function isOpen(panel) {
+ return panel.state === "open"
+}
+exports.isOpen = isOpen;
+
+function isOpening(panel) {
+ return panel.state === "showing"
+}
+exports.isOpening = isOpening
+
+function close(panel) {
+ // Sometimes "TypeError: panel.hidePopup is not a function" is thrown
+ // when quitting the host application while a panel is visible. To suppress
+ // these errors, check for "hidePopup" in panel before calling it.
+ // It's not clear if there's an issue or it's expected behavior.
+ // See Bug 1151796.
+
+ return panel.hidePopup && panel.hidePopup();
+}
+exports.close = close
+
+
+function resize(panel, width, height) {
+ // Resize the iframe instead of using panel.sizeTo
+ // because sizeTo doesn't work with arrow panels
+ if (panel.firstChild) {
+ panel.firstChild.style.width = width + "px";
+ panel.firstChild.style.height = height + "px";
+ }
+}
+exports.resize = resize
+
+function display(panel, options, anchor) {
+ let document = panel.ownerDocument;
+
+ let x, y;
+ let { width, height, defaultWidth, defaultHeight } = options;
+
+ let popupPosition = null;
+
+ // Panel XBL has some SDK incompatible styling decisions. We shim panel
+ // instances until proper fix for Bug 859504 is shipped.
+ shimDefaultStyle(panel);
+
+ if (!anchor) {
+ // The XUL Panel doesn't have an arrow, so the margin needs to be reset
+ // in order to, be positioned properly
+ panel.style.margin = "0";
+
+ let viewportRect = document.defaultView.gBrowser.getBoundingClientRect();
+
+ ({x, y, width, height} = calculateRegion(options, viewportRect));
+ }
+ else {
+ // The XUL Panel has an arrow, so the margin needs to be reset
+ // to the default value.
+ panel.style.margin = "";
+ let { CustomizableUI, window } = anchor.ownerDocument.defaultView;
+
+ // In Australis, widgets may be positioned in an overflow panel or the
+ // menu panel.
+ // In such cases clicking this widget will hide the overflow/menu panel,
+ // and the widget's panel will show instead.
+ // If `CustomizableUI` is not available, it means the anchor is not in a
+ // chrome browser window, and therefore there is no need for this check.
+ if (CustomizableUI) {
+ let node = anchor;
+ ({anchor} = CustomizableUI.getWidget(anchor.id).forWindow(window));
+
+ // if `node` is not the `anchor` itself, it means the widget is
+ // positioned in a panel, therefore we have to hide it before show
+ // the widget's panel in the same anchor
+ if (node !== anchor)
+ CustomizableUI.hidePanelForNode(anchor);
+ }
+
+ width = width || defaultWidth;
+ height = height || defaultHeight;
+
+ // Open the popup by the anchor.
+ let rect = anchor.getBoundingClientRect();
+
+ let zoom = getScreenPixelsPerCSSPixel(window);
+ let screenX = rect.left + window.mozInnerScreenX * zoom;
+ let screenY = rect.top + window.mozInnerScreenY * zoom;
+
+ // Set up the vertical position of the popup relative to the anchor
+ // (always display the arrow on anchor center)
+ let horizontal, vertical;
+ if (screenY > window.screen.availHeight / 2 + height)
+ vertical = "top";
+ else
+ vertical = "bottom";
+
+ if (screenY > window.screen.availWidth / 2 + width)
+ horizontal = "left";
+ else
+ horizontal = "right";
+
+ let verticalInverse = vertical == "top" ? "bottom" : "top";
+ popupPosition = vertical + "center " + verticalInverse + horizontal;
+
+ // Allow panel to flip itself if the panel can't be displayed at the
+ // specified position (useful if we compute a bad position or if the
+ // user moves the window and panel remains visible)
+ panel.setAttribute("flip", "both");
+ }
+
+ if (!panel.viewFrame) {
+ panel.viewFrame = document.importNode(panel.backgroundFrame, false);
+ panel.appendChild(panel.viewFrame);
+
+ let {privateBrowsingId} = getDocShell(panel.viewFrame).getOriginAttributes();
+ let principal = Services.scriptSecurityManager.createNullPrincipal({privateBrowsingId});
+ getDocShell(panel.viewFrame).createAboutBlankContentViewer(principal);
+ }
+
+ // Resize the iframe instead of using panel.sizeTo
+ // because sizeTo doesn't work with arrow panels
+ panel.firstChild.style.width = width + "px";
+ panel.firstChild.style.height = height + "px";
+
+ panel.openPopup(anchor, popupPosition, x, y);
+}
+exports.display = display;
+
+// This utility function is just a workaround until Bug 859504 has shipped.
+function shimDefaultStyle(panel) {
+ let document = panel.ownerDocument;
+ // Please note that `panel` needs to be part of document in order to reach
+ // it's anonymous nodes. One of the anonymous node has a big padding which
+ // doesn't work well since panel frame needs to fill all of the panel.
+ // XBL binding is a not the best option as it's applied asynchronously, and
+ // makes injected frames behave in strange way. Also this feels a lot
+ // cheaper to do.
+ ["panel-inner-arrowcontent", "panel-arrowcontent"].forEach(function(value) {
+ let node = document.getAnonymousElementByAttribute(panel, "class", value);
+ if (node) node.style.padding = 0;
+ });
+}
+
+function show(panel, options, anchor) {
+ // Prevent the panel from getting focus when showing up
+ // if focus is set to false
+ panel.setAttribute("noautofocus", !options.focus);
+
+ let window = anchor && getOwnerBrowserWindow(anchor);
+ let { document } = window ? window : getMostRecentBrowserWindow();
+ attach(panel, document);
+
+ open(panel, options, anchor);
+}
+exports.show = show
+
+function onPanelClick(event) {
+ let { target, metaKey, ctrlKey, shiftKey, button } = event;
+ let accel = platform === "darwin" ? metaKey : ctrlKey;
+ let isLeftClick = button === 0;
+ let isMiddleClick = button === 1;
+
+ if ((isLeftClick && (accel || shiftKey)) || isMiddleClick) {
+ let link = target.closest('a');
+
+ if (link && link.href)
+ getMostRecentBrowserWindow().openUILink(link.href, event)
+ }
+}
+
+function setupPanelFrame(frame) {
+ frame.setAttribute("flex", 1);
+ frame.setAttribute("transparent", "transparent");
+ frame.setAttribute("autocompleteenabled", true);
+ frame.setAttribute("tooltip", "aHTMLTooltip");
+ if (platform === "darwin") {
+ frame.style.borderRadius = "var(--arrowpanel-border-radius, 3.5px)";
+ frame.style.padding = "1px";
+ }
+}
+
+function make(document, options) {
+ document = document || getMostRecentBrowserWindow().document;
+ let panel = document.createElementNS(XUL_NS, "panel");
+ panel.setAttribute("type", "arrow");
+ panel.setAttribute("sdkscriptenabled", options.allowJavascript);
+
+ // The panel needs to be attached to a browser window in order for us
+ // to copy browser styles to the content document when it loads.
+ attach(panel, document);
+
+ let frameOptions = {
+ allowJavascript: options.allowJavascript,
+ allowPlugins: true,
+ allowAuth: true,
+ allowWindowControl: false,
+ // Need to override `nodeName` to use `iframe` as `browsers` save session
+ // history and in consequence do not dispatch "inner-window-destroyed"
+ // notifications.
+ browser: false,
+ };
+
+ let backgroundFrame = createFrame(addonWindow, frameOptions);
+ setupPanelFrame(backgroundFrame);
+
+ getDocShell(backgroundFrame).inheritPrivateBrowsingId = false;
+
+ function onPopupShowing({type, target}) {
+ if (target === this) {
+ let attrs = getDocShell(backgroundFrame).getOriginAttributes();
+ getDocShell(panel.viewFrame).setOriginAttributes(attrs);
+
+ swapFrameLoaders(backgroundFrame, panel.viewFrame);
+ }
+ }
+
+ function onPopupHiding({type, target}) {
+ if (target === this) {
+ swapFrameLoaders(backgroundFrame, panel.viewFrame);
+
+ panel.viewFrame.remove();
+ panel.viewFrame = null;
+ }
+ }
+
+ function onContentReady({target, type}) {
+ if (target === getContentDocument(panel)) {
+ style(panel);
+ events.emit(type, { subject: panel });
+ }
+ }
+
+ function onContentLoad({target, type}) {
+ if (target === getContentDocument(panel))
+ events.emit(type, { subject: panel });
+ }
+
+ function onContentChange({subject: document, type}) {
+ if (document === getContentDocument(panel) && document.defaultView)
+ events.emit(type, { subject: panel });
+ }
+
+ function onPanelStateChange({target, type}) {
+ if (target === this)
+ events.emit(type, { subject: panel })
+ }
+
+ panel.addEventListener("popupshowing", onPopupShowing);
+ panel.addEventListener("popuphiding", onPopupHiding);
+ for (let event of ["popupshowing", "popuphiding", "popupshown", "popuphidden"])
+ panel.addEventListener(event, onPanelStateChange);
+
+ panel.addEventListener("click", onPanelClick, false);
+
+ // Panel content document can be either in panel `viewFrame` or in
+ // a `backgroundFrame` depending on panel state. Listeners are set
+ // on both to avoid setting and removing listeners on panel state changes.
+
+ panel.addEventListener("DOMContentLoaded", onContentReady, true);
+ backgroundFrame.addEventListener("DOMContentLoaded", onContentReady, true);
+
+ panel.addEventListener("load", onContentLoad, true);
+ backgroundFrame.addEventListener("load", onContentLoad, true);
+
+ events.on("document-element-inserted", onContentChange);
+
+ panel.backgroundFrame = backgroundFrame;
+ panel.viewFrame = null;
+
+ // Store event listener on the panel instance so that it won't be GC-ed
+ // while panel is alive.
+ panel.onContentChange = onContentChange;
+
+ return panel;
+}
+exports.make = make;
+
+function attach(panel, document) {
+ document = document || getMostRecentBrowserWindow().document;
+ let container = document.getElementById("mainPopupSet");
+ if (container !== panel.parentNode) {
+ detach(panel);
+ document.getElementById("mainPopupSet").appendChild(panel);
+ }
+}
+exports.attach = attach;
+
+function detach(panel) {
+ if (panel.parentNode) panel.parentNode.removeChild(panel);
+}
+exports.detach = detach;
+
+function dispose(panel) {
+ panel.backgroundFrame.remove();
+ panel.backgroundFrame = null;
+ events.off("document-element-inserted", panel.onContentChange);
+ panel.onContentChange = null;
+ detach(panel);
+}
+exports.dispose = dispose;
+
+function style(panel) {
+ /**
+ Injects default OS specific panel styles into content document that is loaded
+ into given panel. Optionally `document` of the browser window can be
+ given to inherit styles from it, by default it will use either panel owner
+ document or an active browser's document. It should not matter though unless
+ Firefox decides to style windows differently base on profile or mode like
+ chrome for example.
+ **/
+
+ try {
+ let document = panel.ownerDocument;
+ let contentDocument = getContentDocument(panel);
+ let window = document.defaultView;
+ let node = document.getAnonymousElementByAttribute(panel, "class",
+ "panel-arrowcontent");
+
+ let { color, fontFamily, fontSize, fontWeight } = window.getComputedStyle(node);
+
+ let style = contentDocument.createElement("style");
+ style.id = "sdk-panel-style";
+ style.textContent = "body { " +
+ "color: " + color + ";" +
+ "font-family: " + fontFamily + ";" +
+ "font-weight: " + fontWeight + ";" +
+ "font-size: " + fontSize + ";" +
+ "}";
+
+ let container = contentDocument.head ? contentDocument.head :
+ contentDocument.documentElement;
+
+ if (container.firstChild)
+ container.insertBefore(style, container.firstChild);
+ else
+ container.appendChild(style);
+ }
+ catch (error) {
+ console.error("Unable to apply panel style");
+ console.exception(error);
+ }
+}
+exports.style = style;
+
+var getContentFrame = panel => panel.viewFrame || panel.backgroundFrame;
+exports.getContentFrame = getContentFrame;
+
+function getContentDocument(panel) {
+ return getContentFrame(panel).contentDocument;
+}
+exports.getContentDocument = getContentDocument;
+
+function setURL(panel, url) {
+ let frame = getContentFrame(panel);
+ let webNav = getDocShell(frame).QueryInterface(Ci.nsIWebNavigation);
+
+ webNav.loadURI(url ? data.url(url) : "about:blank", 0, null, null, null);
+}
+
+exports.setURL = setURL;
+
+function allowContextMenu(panel, allow) {
+ if (allow) {
+ panel.setAttribute("context", "contentAreaContextMenu");
+ }
+ else {
+ panel.removeAttribute("context");
+ }
+}
+exports.allowContextMenu = allowContextMenu;
diff --git a/components/jetpack/sdk/passwords.js b/components/jetpack/sdk/passwords.js
new file mode 100644
index 000000000..70f0aa4da
--- /dev/null
+++ b/components/jetpack/sdk/passwords.js
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { search, remove, store } = require("./passwords/utils");
+const { defer, delay } = require("./lang/functional");
+
+/**
+ * Utility function that returns `onComplete` and `onError` callbacks form the
+ * given `options` objects. Also properties are removed from the passed
+ * `options` objects.
+ * @param {Object} options
+ * Object that is passed to the exported functions of this module.
+ * @returns {Function[]}
+ * Array with two elements `onComplete` and `onError` functions.
+ */
+function getCallbacks(options) {
+ let value = [
+ 'onComplete' in options ? options.onComplete : null,
+ 'onError' in options ? defer(options.onError) : console.exception
+ ];
+
+ delete options.onComplete;
+ delete options.onError;
+
+ return value;
+};
+
+/**
+ * Creates a wrapper function that tries to call `onComplete` with a return
+ * value of the wrapped function or falls back to `onError` if wrapped function
+ * throws an exception.
+ */
+function createWrapperMethod(wrapped) {
+ return function (options) {
+ let [ onComplete, onError ] = getCallbacks(options);
+ try {
+ let value = wrapped(options);
+ if (onComplete) {
+ delay(function() {
+ try {
+ onComplete(value);
+ } catch (exception) {
+ onError(exception);
+ }
+ });
+ }
+ } catch (exception) {
+ onError(exception);
+ }
+ };
+}
+
+exports.search = createWrapperMethod(search);
+exports.store = createWrapperMethod(store);
+exports.remove = createWrapperMethod(remove);
diff --git a/components/jetpack/sdk/passwords/utils.js b/components/jetpack/sdk/passwords/utils.js
new file mode 100644
index 000000000..334efa490
--- /dev/null
+++ b/components/jetpack/sdk/passwords/utils.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, CC } = require("chrome");
+const { uri: ADDON_URI } = require("../self");
+const loginManager = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+const { URL: parseURL } = require("../url");
+const LoginInfo = CC("@mozilla.org/login-manager/loginInfo;1",
+ "nsILoginInfo", "init");
+
+function filterMatchingLogins(loginInfo) {
+ return Object.keys(this).every(key => loginInfo[key] === this[key], this);
+}
+
+/**
+ * Removes `user`, `password` and `path` fields from the given `url` if it's
+ * 'http', 'https' or 'ftp'. All other URLs are returned unchanged.
+ * @example
+ * http://user:pass@www.site.com/foo/?bar=baz#bang -> http://www.site.com
+ */
+function normalizeURL(url) {
+ let { scheme, host, port } = parseURL(url);
+ // We normalize URL only if it's `http`, `https` or `ftp`. All other types of
+ // URLs (`resource`, `chrome`, etc..) should not be normalized as they are
+ // used with add-on associated credentials path.
+ return scheme === "http" || scheme === "https" || scheme === "ftp" ?
+ scheme + "://" + (host || "") + (port ? ":" + port : "") :
+ url
+}
+
+function Login(options) {
+ let login = Object.create(Login.prototype);
+ Object.keys(options || {}).forEach(function(key) {
+ if (key === 'url')
+ login.hostname = normalizeURL(options.url);
+ else if (key === 'formSubmitURL')
+ login.formSubmitURL = options.formSubmitURL ?
+ normalizeURL(options.formSubmitURL) : null;
+ else if (key === 'realm')
+ login.httpRealm = options.realm;
+ else
+ login[key] = options[key];
+ });
+
+ return login;
+}
+Login.prototype.toJSON = function toJSON() {
+ return {
+ url: this.hostname || ADDON_URI,
+ realm: this.httpRealm || null,
+ formSubmitURL: this.formSubmitURL || null,
+ username: this.username || null,
+ password: this.password || null,
+ usernameField: this.usernameField || '',
+ passwordField: this.passwordField || '',
+ }
+};
+Login.prototype.toLoginInfo = function toLoginInfo() {
+ let { url, realm, formSubmitURL, username, password, usernameField,
+ passwordField } = this.toJSON();
+
+ return new LoginInfo(url, formSubmitURL, realm, username, password,
+ usernameField, passwordField);
+};
+
+function loginToJSON(value) {
+ return Login(value).toJSON();
+}
+
+/**
+ * Returns array of `nsILoginInfo` objects that are stored in the login manager
+ * and have all the properties with matching values as a given `options` object.
+ * @param {Object} options
+ * @returns {nsILoginInfo[]}
+ */
+exports.search = function search(options) {
+ return loginManager.getAllLogins()
+ .filter(filterMatchingLogins, Login(options))
+ .map(loginToJSON);
+};
+
+/**
+ * Stores login info created from the given `options` to the applications
+ * built-in login management system.
+ * @param {Object} options.
+ */
+exports.store = function store(options) {
+ loginManager.addLogin(Login(options).toLoginInfo());
+};
+
+/**
+ * Removes login info from the applications built-in login management system.
+ * _Please note: When removing a login info the specified properties must
+ * exactly match to the one that is already stored or exception will be thrown._
+ * @param {Object} options.
+ */
+exports.remove = function remove(options) {
+ loginManager.removeLogin(Login(options).toLoginInfo());
+};
diff --git a/components/jetpack/sdk/places/bookmarks.js b/components/jetpack/sdk/places/bookmarks.js
new file mode 100644
index 000000000..e8fdae4f4
--- /dev/null
+++ b/components/jetpack/sdk/places/bookmarks.js
@@ -0,0 +1,396 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable",
+ "engines": {
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+/*
+ * Requiring hosts so they can subscribe to client messages
+ */
+require('./host/host-bookmarks');
+require('./host/host-tags');
+require('./host/host-query');
+
+const { Cc, Ci } = require('chrome');
+const { Class } = require('../core/heritage');
+const { send } = require('../addon/events');
+const { defer, reject, all, resolve, promised } = require('../core/promise');
+const { EventTarget } = require('../event/target');
+const { emit } = require('../event/core');
+const { identity, defer:async } = require('../lang/functional');
+const { extend, merge } = require('../util/object');
+const { fromIterator } = require('../util/array');
+const {
+ constructTree, fetchItem, createQuery,
+ isRootGroup, createQueryOptions
+} = require('./utils');
+const {
+ bookmarkContract, groupContract, separatorContract
+} = require('./contract');
+const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Ci.nsINavBookmarksService);
+
+/*
+ * Mapping of uncreated bookmarks with their created
+ * counterparts
+ */
+const itemMap = new WeakMap();
+
+/*
+ * Constant used by nsIHistoryQuery; 1 is a bookmark query
+ * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
+ */
+const BOOKMARK_QUERY = 1;
+
+/*
+ * Bookmark Item classes
+ */
+
+const Bookmark = Class({
+ extends: [
+ bookmarkContract.properties(identity)
+ ],
+ initialize: function initialize (options) {
+ merge(this, bookmarkContract(extend(defaults, options)));
+ },
+ type: 'bookmark',
+ toString: () => '[object Bookmark]'
+});
+exports.Bookmark = Bookmark;
+
+const Group = Class({
+ extends: [
+ groupContract.properties(identity)
+ ],
+ initialize: function initialize (options) {
+ // Don't validate if root group
+ if (isRootGroup(options))
+ merge(this, options);
+ else
+ merge(this, groupContract(extend(defaults, options)));
+ },
+ type: 'group',
+ toString: () => '[object Group]'
+});
+exports.Group = Group;
+
+const Separator = Class({
+ extends: [
+ separatorContract.properties(identity)
+ ],
+ initialize: function initialize (options) {
+ merge(this, separatorContract(extend(defaults, options)));
+ },
+ type: 'separator',
+ toString: () => '[object Separator]'
+});
+exports.Separator = Separator;
+
+/*
+ * Functions
+ */
+
+function save (items, options) {
+ items = [].concat(items);
+ options = options || {};
+ let emitter = EventTarget();
+ let results = [];
+ let errors = [];
+ let root = constructTree(items);
+ let cache = new Map();
+
+ let isExplicitSave = item => !!~items.indexOf(item);
+ // `walk` returns an aggregate promise indicating the completion
+ // of the `commitItem` on each node, not whether or not that
+ // commit was successful
+
+ // Force this to be async, as if a ducktype fails validation,
+ // the promise implementation will fire an error event, which will
+ // not trigger the handler as it's not yet bound
+ //
+ // Can remove after `Promise.jsm` is implemented in Bug 881047,
+ // which will guarantee next tick execution
+ async(() => root.walk(preCommitItem).then(commitComplete))();
+
+ function preCommitItem ({value:item}) {
+ // Do nothing if tree root, default group (unsavable),
+ // or if it's a dependency and not explicitly saved (in the list
+ // of items to be saved), and not needed to be saved
+ if (item === null || // node is the tree root
+ isRootGroup(item) ||
+ (getId(item) && !isExplicitSave(item)))
+ return;
+
+ return promised(validate)(item)
+ .then(() => commitItem(item, options))
+ .then(data => construct(data, cache))
+ .then(savedItem => {
+ // If item was just created, make a map between
+ // the creation object and created object,
+ // so we can reference the item that doesn't have an id
+ if (!getId(item))
+ saveId(item, savedItem.id);
+
+ // Emit both the processed item, and original item
+ // so a mapping can be understood in handler
+ emit(emitter, 'data', savedItem, item);
+
+ // Push to results iff item was explicitly saved
+ if (isExplicitSave(item))
+ results[items.indexOf(item)] = savedItem;
+ }, reason => {
+ // Force reason to be a string for consistency
+ reason = reason + '';
+ // Emit both the reason, and original item
+ // so a mapping can be understood in handler
+ emit(emitter, 'error', reason + '', item);
+ // Store unsaved item in results list
+ results[items.indexOf(item)] = item;
+ errors.push(reason);
+ });
+ }
+
+ // Called when traversal of the node tree is completed and all
+ // items have been committed
+ function commitComplete () {
+ emit(emitter, 'end', results);
+ }
+
+ return emitter;
+}
+exports.save = save;
+
+function search (queries, options) {
+ queries = [].concat(queries);
+ let emitter = EventTarget();
+ let cache = new Map();
+ let queryObjs = queries.map(createQuery.bind(null, BOOKMARK_QUERY));
+ let optionsObj = createQueryOptions(BOOKMARK_QUERY, options);
+
+ // Can remove after `Promise.jsm` is implemented in Bug 881047,
+ // which will guarantee next tick execution
+ async(() => {
+ send('sdk-places-query', { queries: queryObjs, options: optionsObj })
+ .then(handleQueryResponse);
+ })();
+
+ function handleQueryResponse (data) {
+ let deferreds = data.map(item => {
+ return construct(item, cache).then(bookmark => {
+ emit(emitter, 'data', bookmark);
+ return bookmark;
+ }, reason => {
+ emit(emitter, 'error', reason);
+ errors.push(reason);
+ });
+ });
+
+ all(deferreds).then(data => {
+ emit(emitter, 'end', data);
+ }, () => emit(emitter, 'end', []));
+ }
+
+ return emitter;
+}
+exports.search = search;
+
+function remove (items) {
+ return [].concat(items).map(item => {
+ item.remove = true;
+ return item;
+ });
+}
+
+exports.remove = remove;
+
+/*
+ * Internal Utilities
+ */
+
+function commitItem (item, options) {
+ // Get the item's ID, or getId it's saved version if it exists
+ let id = getId(item);
+ let data = normalize(item);
+ let promise;
+
+ data.id = id;
+
+ if (!id) {
+ promise = send('sdk-places-bookmarks-create', data);
+ } else if (item.remove) {
+ promise = send('sdk-places-bookmarks-remove', { id: id });
+ } else {
+ promise = send('sdk-places-bookmarks-last-updated', {
+ id: id
+ }).then(function (updated) {
+ // If attempting to save an item that is not the
+ // latest snapshot of a bookmark item, execute
+ // the resolution function
+ if (updated !== item.updated && options.resolve)
+ return fetchItem(id)
+ .then(options.resolve.bind(null, data));
+ else
+ return data;
+ }).then(send.bind(null, 'sdk-places-bookmarks-save'));
+ }
+
+ return promise;
+}
+
+/*
+ * Turns a bookmark item into a plain object,
+ * converts `tags` from Set to Array, group instance to an id
+ */
+function normalize (item) {
+ let data = merge({}, item);
+ // Circumvent prototype property of `type`
+ delete data.type;
+ data.type = item.type;
+ data.tags = [];
+ if (item.tags) {
+ data.tags = fromIterator(item.tags);
+ }
+ data.group = getId(data.group) || exports.UNSORTED.id;
+
+ return data;
+}
+
+/*
+ * Takes a data object and constructs a BookmarkItem instance
+ * of it, recursively generating parent instances as well.
+ *
+ * Pass in a `cache` Map to reuse instances of
+ * bookmark items to reduce overhead;
+ * The cache object is a map of id to a deferred with a
+ * promise that resolves to the bookmark item.
+ */
+function construct (object, cache, forced) {
+ let item = instantiate(object);
+ let deferred = defer();
+
+ // Item could not be instantiated
+ if (!item)
+ return resolve(null);
+
+ // Return promise for item if found in the cache,
+ // and not `forced`. `forced` indicates that this is the construct
+ // call that should not read from cache, but should actually perform
+ // the construction, as it was set before several async calls
+ if (cache.has(item.id) && !forced)
+ return cache.get(item.id).promise;
+ else if (cache.has(item.id))
+ deferred = cache.get(item.id);
+ else
+ cache.set(item.id, deferred);
+
+ // When parent group is found in cache, use
+ // the same deferred value
+ if (item.group && cache.has(item.group)) {
+ cache.get(item.group).promise.then(group => {
+ item.group = group;
+ deferred.resolve(item);
+ });
+
+ // If not in the cache, and a root group, return
+ // the premade instance
+ } else if (rootGroups.get(item.group)) {
+ item.group = rootGroups.get(item.group);
+ deferred.resolve(item);
+
+ // If not in the cache or a root group, fetch the parent
+ } else {
+ cache.set(item.group, defer());
+ fetchItem(item.group).then(group => {
+ return construct(group, cache, true);
+ }).then(group => {
+ item.group = group;
+ deferred.resolve(item);
+ }, deferred.reject);
+ }
+
+ return deferred.promise;
+}
+
+function instantiate (object) {
+ if (object.type === 'bookmark')
+ return Bookmark(object);
+ if (object.type === 'group')
+ return Group(object);
+ if (object.type === 'separator')
+ return Separator(object);
+ return null;
+}
+
+/**
+ * Validates a bookmark item; will throw an error if ininvalid,
+ * to be used with `promised`. As bookmark items check on their class,
+ * this only checks ducktypes
+ */
+function validate (object) {
+ if (!isDuckType(object)) return true;
+ let contract = object.type === 'bookmark' ? bookmarkContract :
+ object.type === 'group' ? groupContract :
+ object.type === 'separator' ? separatorContract :
+ null;
+ if (!contract) {
+ throw Error('No type specified');
+ }
+
+ // If object has a property set, and undefined,
+ // manually override with default as it'll fail otherwise
+ let withDefaults = Object.keys(defaults).reduce((obj, prop) => {
+ if (obj[prop] == null) obj[prop] = defaults[prop];
+ return obj;
+ }, extend(object));
+
+ contract(withDefaults);
+}
+
+function isDuckType (item) {
+ return !(item instanceof Bookmark) &&
+ !(item instanceof Group) &&
+ !(item instanceof Separator);
+}
+
+function saveId (unsaved, id) {
+ itemMap.set(unsaved, id);
+}
+
+// Fetches an item's ID from itself, or from the mapped items
+function getId (item) {
+ return typeof item === 'number' ? item :
+ item ? item.id || itemMap.get(item) :
+ null;
+}
+
+/*
+ * Set up the default, root groups
+ */
+
+var defaultGroupMap = {
+ MENU: bmsrv.bookmarksMenuFolder,
+ TOOLBAR: bmsrv.toolbarFolder,
+ UNSORTED: bmsrv.unfiledBookmarksFolder
+};
+
+var rootGroups = new Map();
+
+for (let i in defaultGroupMap) {
+ let group = Object.freeze(Group({ title: i, id: defaultGroupMap[i] }));
+ rootGroups.set(defaultGroupMap[i], group);
+ exports[i] = group;
+}
+
+var defaults = {
+ group: exports.UNSORTED,
+ index: -1
+};
diff --git a/components/jetpack/sdk/places/contract.js b/components/jetpack/sdk/places/contract.js
new file mode 100644
index 000000000..a3541c34d
--- /dev/null
+++ b/components/jetpack/sdk/places/contract.js
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci } = require('chrome');
+const { isValidURI, URL } = require('../url');
+const { contract } = require('../util/contract');
+const { extend } = require('../util/object');
+
+// map of property validations
+const validItem = {
+ id: {
+ is: ['number', 'undefined', 'null'],
+ },
+ group: {
+ is: ['object', 'number', 'undefined', 'null'],
+ ok: function (value) {
+ return value &&
+ (value.toString && value.toString() === '[object Group]') ||
+ typeof value === 'number' ||
+ value.type === 'group';
+ },
+ msg: 'The `group` property must be a valid Group object'
+ },
+ index: {
+ is: ['undefined', 'null', 'number'],
+ map: value => value == null ? -1 : value,
+ msg: 'The `index` property must be a number.'
+ },
+ updated: {
+ is: ['number', 'undefined']
+ }
+};
+
+const validTitle = {
+ title: {
+ is: ['string'],
+ msg: 'The `title` property must be defined.'
+ }
+};
+
+const validURL = {
+ url: {
+ is: ['string'],
+ ok: isValidURI,
+ msg: 'The `url` property must be a valid URL.'
+ }
+};
+
+const validTags = {
+ tags: {
+ is: ['object'],
+ ok: tags => tags instanceof Set,
+ map: function (tags) {
+ if (Array.isArray(tags))
+ return new Set(tags);
+ if (tags == null)
+ return new Set();
+ return tags;
+ },
+ msg: 'The `tags` property must be a Set, or an array'
+ }
+};
+
+exports.bookmarkContract = contract(
+ extend(validItem, validTitle, validURL, validTags));
+exports.separatorContract = contract(validItem);
+exports.groupContract = contract(extend(validItem, validTitle));
diff --git a/components/jetpack/sdk/places/events.js b/components/jetpack/sdk/places/events.js
new file mode 100644
index 000000000..c5e728039
--- /dev/null
+++ b/components/jetpack/sdk/places/events.js
@@ -0,0 +1,129 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'experimental',
+ 'engines': {
+ 'Palemoon': '*',
+ 'Firefox': '*',
+ "SeaMonkey": '*'
+ }
+};
+
+const { Cc, Ci } = require('chrome');
+const { Unknown } = require('../platform/xpcom');
+const { Class } = require('../core/heritage');
+const { merge } = require('../util/object');
+const bookmarkService = Cc['@mozilla.org/browser/nav-bookmarks-service;1']
+ .getService(Ci.nsINavBookmarksService);
+const historyService = Cc['@mozilla.org/browser/nav-history-service;1']
+ .getService(Ci.nsINavHistoryService);
+const { mapBookmarkItemType } = require('./utils');
+const { EventTarget } = require('../event/target');
+const { emit } = require('../event/core');
+const { when } = require('../system/unload');
+
+const emitter = EventTarget();
+
+var HISTORY_ARGS = {
+ onBeginUpdateBatch: [],
+ onEndUpdateBatch: [],
+ onClearHistory: [],
+ onDeleteURI: ['url'],
+ onDeleteVisits: ['url', 'visitTime'],
+ onPageChanged: ['url', 'property', 'value'],
+ onTitleChanged: ['url', 'title'],
+ onVisit: [
+ 'url', 'visitId', 'time', 'sessionId', 'referringId', 'transitionType'
+ ]
+};
+
+var HISTORY_EVENTS = {
+ onBeginUpdateBatch: 'history-start-batch',
+ onEndUpdateBatch: 'history-end-batch',
+ onClearHistory: 'history-start-clear',
+ onDeleteURI: 'history-delete-url',
+ onDeleteVisits: 'history-delete-visits',
+ onPageChanged: 'history-page-changed',
+ onTitleChanged: 'history-title-changed',
+ onVisit: 'history-visit'
+};
+
+var BOOKMARK_ARGS = {
+ onItemAdded: [
+ 'id', 'parentId', 'index', 'type', 'url', 'title', 'dateAdded'
+ ],
+ onItemChanged: [
+ 'id', 'property', null, 'value', 'lastModified', 'type', 'parentId'
+ ],
+ onItemMoved: [
+ 'id', 'previousParentId', 'previousIndex', 'currentParentId',
+ 'currentIndex', 'type'
+ ],
+ onItemRemoved: ['id', 'parentId', 'index', 'type', 'url'],
+ onItemVisited: ['id', 'visitId', 'time', 'transitionType', 'url', 'parentId']
+};
+
+var BOOKMARK_EVENTS = {
+ onItemAdded: 'bookmark-item-added',
+ onItemChanged: 'bookmark-item-changed',
+ onItemMoved: 'bookmark-item-moved',
+ onItemRemoved: 'bookmark-item-removed',
+ onItemVisited: 'bookmark-item-visited',
+};
+
+function createHandler (type, propNames) {
+ propNames = propNames || [];
+ return function (...args) {
+ let data = propNames.reduce((acc, prop, i) => {
+ if (prop)
+ acc[prop] = formatValue(prop, args[i]);
+ return acc;
+ }, {});
+
+ emit(emitter, 'data', {
+ type: type,
+ data: data
+ });
+ };
+}
+
+/*
+ * Creates an observer, creating handlers based off of
+ * the `events` names, and ordering arguments from `propNames` hash
+ */
+function createObserverInstance (events, propNames) {
+ let definition = Object.keys(events).reduce((prototype, eventName) => {
+ prototype[eventName] = createHandler(events[eventName], propNames[eventName]);
+ return prototype;
+ }, {});
+
+ return Class(merge(definition, { extends: Unknown }))();
+}
+
+/*
+ * Formats `data` based off of the value of `type`
+ */
+function formatValue (type, data) {
+ if (type === 'type')
+ return mapBookmarkItemType(data);
+ if (type === 'url' && data)
+ return data.spec;
+ return data;
+}
+
+var historyObserver = createObserverInstance(HISTORY_EVENTS, HISTORY_ARGS);
+historyService.addObserver(historyObserver, false);
+
+var bookmarkObserver = createObserverInstance(BOOKMARK_EVENTS, BOOKMARK_ARGS);
+bookmarkService.addObserver(bookmarkObserver, false);
+
+when(() => {
+ historyService.removeObserver(historyObserver);
+ bookmarkService.removeObserver(bookmarkObserver);
+});
+
+exports.events = emitter;
diff --git a/components/jetpack/sdk/places/favicon.js b/components/jetpack/sdk/places/favicon.js
new file mode 100644
index 000000000..7a74aa517
--- /dev/null
+++ b/components/jetpack/sdk/places/favicon.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable",
+ "engines": {
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+const { Cc, Ci, Cu } = require("chrome");
+const { defer, reject } = require("../core/promise");
+const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
+ getService(Ci.nsIFaviconService);
+const AsyncFavicons = FaviconService.QueryInterface(Ci.mozIAsyncFavicons);
+const { isValidURI } = require("../url");
+const { newURI, getURL } = require("../url/utils");
+
+/**
+ * Takes an object of several possible types and
+ * returns a promise that resolves to the page's favicon URI.
+ * @param {String|Tab} object
+ * @param {Function} (callback)
+ * @returns {Promise}
+ */
+
+function getFavicon (object, callback) {
+ let url = getURL(object);
+ let deferred = defer();
+
+ if (url && isValidURI(url)) {
+ AsyncFavicons.getFaviconURLForPage(newURI(url), function (aURI) {
+ if (aURI && aURI.spec)
+ deferred.resolve(aURI.spec.toString());
+ else
+ deferred.reject(null);
+ });
+ } else {
+ deferred.reject(null);
+ }
+
+ if (callback) deferred.promise.then(callback, callback);
+ return deferred.promise;
+}
+exports.getFavicon = getFavicon;
diff --git a/components/jetpack/sdk/places/history.js b/components/jetpack/sdk/places/history.js
new file mode 100644
index 000000000..f7fc3ed57
--- /dev/null
+++ b/components/jetpack/sdk/places/history.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable",
+ "engines": {
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+/*
+ * Requiring hosts so they can subscribe to client messages
+ */
+require('./host/host-bookmarks');
+require('./host/host-tags');
+require('./host/host-query');
+
+const { Cc, Ci } = require('chrome');
+const { Class } = require('../core/heritage');
+const { events, send } = require('../addon/events');
+const { defer, reject, all } = require('../core/promise');
+const { uuid } = require('../util/uuid');
+const { flatten } = require('../util/array');
+const { has, extend, merge, pick } = require('../util/object');
+const { emit } = require('../event/core');
+const { defer: async } = require('../lang/functional');
+const { EventTarget } = require('../event/target');
+const {
+ urlQueryParser, createQuery, createQueryOptions
+} = require('./utils');
+
+/*
+ * Constant used by nsIHistoryQuery; 0 is a history query
+ * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
+ */
+const HISTORY_QUERY = 0;
+
+var search = function query (queries, options) {
+ queries = [].concat(queries);
+ let emitter = EventTarget();
+ let queryObjs = queries.map(createQuery.bind(null, HISTORY_QUERY));
+ let optionsObj = createQueryOptions(HISTORY_QUERY, options);
+
+ // Can remove after `Promise.jsm` is implemented in Bug 881047,
+ // which will guarantee next tick execution
+ async(() => {
+ send('sdk-places-query', {
+ query: queryObjs,
+ options: optionsObj
+ }).then(results => {
+ results.map(item => emit(emitter, 'data', item));
+ emit(emitter, 'end', results);
+ }, reason => {
+ emit(emitter, 'error', reason);
+ emit(emitter, 'end', []);
+ });
+ })();
+
+ return emitter;
+};
+exports.search = search;
diff --git a/components/jetpack/sdk/places/host/host-bookmarks.js b/components/jetpack/sdk/places/host/host-bookmarks.js
new file mode 100644
index 000000000..f6dec4069
--- /dev/null
+++ b/components/jetpack/sdk/places/host/host-bookmarks.js
@@ -0,0 +1,239 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+const { Cc, Ci } = require('chrome');
+const browserHistory = Cc["@mozilla.org/browser/nav-history-service;1"].
+ getService(Ci.nsIBrowserHistory);
+const asyncHistory = Cc["@mozilla.org/browser/history;1"].
+ getService(Ci.mozIAsyncHistory);
+const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Ci.nsINavBookmarksService);
+const taggingService = Cc["@mozilla.org/browser/tagging-service;1"].
+ getService(Ci.nsITaggingService);
+const ios = Cc['@mozilla.org/network/io-service;1'].
+ getService(Ci.nsIIOService);
+const { query } = require('./host-query');
+const {
+ defer, all, resolve, promised, reject
+} = require('../../core/promise');
+const { request, response } = require('../../addon/host');
+const { send } = require('../../addon/events');
+const { on, emit } = require('../../event/core');
+const { filter } = require('../../event/utils');
+const { URL, isValidURI } = require('../../url');
+const { newURI } = require('../../url/utils');
+
+const DEFAULT_INDEX = bmsrv.DEFAULT_INDEX;
+const UNSORTED_ID = bmsrv.unfiledBookmarksFolder;
+const ROOT_FOLDERS = [
+ bmsrv.unfiledBookmarksFolder, bmsrv.toolbarFolder,
+ bmsrv.tagsFolder, bmsrv.bookmarksMenuFolder
+];
+
+const EVENT_MAP = {
+ 'sdk-places-bookmarks-create': createBookmarkItem,
+ 'sdk-places-bookmarks-save': saveBookmarkItem,
+ 'sdk-places-bookmarks-last-updated': getBookmarkLastUpdated,
+ 'sdk-places-bookmarks-get': getBookmarkItem,
+ 'sdk-places-bookmarks-remove': removeBookmarkItem,
+ 'sdk-places-bookmarks-get-all': getAllBookmarks,
+ 'sdk-places-bookmarks-get-children': getChildren
+};
+
+function typeMap (type) {
+ if (typeof type === 'number') {
+ if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark';
+ if (bmsrv.TYPE_FOLDER === type) return 'group';
+ if (bmsrv.TYPE_SEPARATOR === type) return 'separator';
+ } else {
+ if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK;
+ if ('group' === type) return bmsrv.TYPE_FOLDER;
+ if ('separator' === type) return bmsrv.TYPE_SEPARATOR;
+ }
+}
+
+function getBookmarkLastUpdated ({id}) {
+ return resolve(bmsrv.getItemLastModified(id));
+}
+exports.getBookmarkLastUpdated;
+
+function createBookmarkItem (data) {
+ let error;
+
+ if (data.group == null) data.group = UNSORTED_ID;
+ if (data.index == null) data.index = DEFAULT_INDEX;
+
+ if (data.type === 'group')
+ data.id = bmsrv.createFolder(
+ data.group, data.title, data.index
+ );
+ else if (data.type === 'separator')
+ data.id = bmsrv.insertSeparator(
+ data.group, data.index
+ );
+ else
+ data.id = bmsrv.insertBookmark(
+ data.group, newURI(data.url), data.index, data.title
+ );
+
+ // In the event where default or no index is provided (-1),
+ // query the actual index for the response
+ if (data.index === -1)
+ data.index = bmsrv.getItemIndex(data.id);
+
+ try {
+ data.updated = bmsrv.getItemLastModified(data.id);
+ }
+ catch (e) {
+ console.exception(e);
+ }
+
+ return tag(data, true).then(() => data);
+}
+exports.createBookmarkItem = createBookmarkItem;
+
+function saveBookmarkItem (data) {
+ let id = data.id;
+ if (!id)
+ reject('Item is missing id');
+
+ let group = bmsrv.getFolderIdForItem(id);
+ let index = bmsrv.getItemIndex(id);
+ let type = bmsrv.getItemType(id);
+ let title = typeMap(type) !== 'separator' ?
+ bmsrv.getItemTitle(id) :
+ undefined;
+ let url = typeMap(type) === 'bookmark' ?
+ bmsrv.getBookmarkURI(id).spec :
+ undefined;
+
+ if (url != data.url)
+ bmsrv.changeBookmarkURI(id, newURI(data.url));
+ else if (typeMap(type) === 'bookmark')
+ data.url = url;
+
+ if (title != data.title)
+ bmsrv.setItemTitle(id, data.title);
+ else if (typeMap(type) !== 'separator')
+ data.title = title;
+
+ if (data.group && data.group !== group)
+ bmsrv.moveItem(id, data.group, data.index || -1);
+ else if (data.index != null && data.index !== index) {
+ // We use moveItem here instead of setItemIndex
+ // so we don't have to manage the indicies of the siblings
+ bmsrv.moveItem(id, group, data.index);
+ } else if (data.index == null)
+ data.index = index;
+
+ data.updated = bmsrv.getItemLastModified(data.id);
+
+ return tag(data).then(() => data);
+}
+exports.saveBookmarkItem = saveBookmarkItem;
+
+function removeBookmarkItem (data) {
+ let id = data.id;
+
+ if (!id)
+ reject('Item is missing id');
+
+ bmsrv.removeItem(id);
+ return resolve(null);
+}
+exports.removeBookmarkItem = removeBookmarkItem;
+
+function getBookmarkItem (data) {
+ let id = data.id;
+
+ if (!id)
+ reject('Item is missing id');
+
+ let type = bmsrv.getItemType(id);
+
+ data.type = typeMap(type);
+
+ if (type === bmsrv.TYPE_BOOKMARK || type === bmsrv.TYPE_FOLDER)
+ data.title = bmsrv.getItemTitle(id);
+
+ if (type === bmsrv.TYPE_BOOKMARK) {
+ data.url = bmsrv.getBookmarkURI(id).spec;
+ // Should be moved into host-tags as a method
+ data.tags = taggingService.getTagsForURI(newURI(data.url), {});
+ }
+
+ data.group = bmsrv.getFolderIdForItem(id);
+ data.index = bmsrv.getItemIndex(id);
+ data.updated = bmsrv.getItemLastModified(data.id);
+
+ return resolve(data);
+}
+exports.getBookmarkItem = getBookmarkItem;
+
+function getAllBookmarks () {
+ return query({}, { queryType: 1 }).then(bookmarks =>
+ all(bookmarks.map(getBookmarkItem)));
+}
+exports.getAllBookmarks = getAllBookmarks;
+
+function getChildren ({ id }) {
+ if (typeMap(bmsrv.getItemType(id)) !== 'group') return [];
+ let ids = [];
+ for (let i = 0; ids[ids.length - 1] !== -1; i++)
+ ids.push(bmsrv.getIdForItemAt(id, i));
+ ids.pop();
+ return all(ids.map(id => getBookmarkItem({ id: id })));
+}
+exports.getChildren = getChildren;
+
+/*
+ * Hook into host
+ */
+
+var reqStream = filter(request, (data) => /sdk-places-bookmarks/.test(data.event));
+on(reqStream, 'data', ({ event, id, data }) => {
+ if (!EVENT_MAP[event]) return;
+
+ let resData = { id: id, event: event };
+
+ promised(EVENT_MAP[event])(data).
+ then(res => resData.data = res, e => resData.error = e).
+ then(() => emit(response, 'data', resData));
+});
+
+function tag (data, isNew) {
+ // If a new item, we can skip checking what other tags
+ // are on the item
+ if (data.type !== 'bookmark') {
+ return resolve();
+ }
+ else if (!isNew) {
+ return send('sdk-places-tags-get-tags-by-url', { url: data.url })
+ .then(tags => {
+ return send('sdk-places-tags-untag', {
+ tags: tags.filter(tag => !~data.tags.indexOf(tag)),
+ url: data.url
+ });
+ }).then(() => send('sdk-places-tags-tag', {
+ url: data.url, tags: data.tags
+ }));
+ }
+ else if (data.tags && data.tags.length) {
+ return send('sdk-places-tags-tag', { url: data.url, tags: data.tags });
+ }
+ else
+ return resolve();
+}
+
diff --git a/components/jetpack/sdk/places/host/host-query.js b/components/jetpack/sdk/places/host/host-query.js
new file mode 100644
index 000000000..a2cd4cd35
--- /dev/null
+++ b/components/jetpack/sdk/places/host/host-query.js
@@ -0,0 +1,180 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+const { Cc, Ci } = require('chrome');
+const { all } = require('../../core/promise');
+const { safeMerge, omit } = require('../../util/object');
+const historyService = Cc['@mozilla.org/browser/nav-history-service;1']
+ .getService(Ci.nsINavHistoryService);
+const bookmarksService = Cc['@mozilla.org/browser/nav-bookmarks-service;1']
+ .getService(Ci.nsINavBookmarksService);
+const { request, response } = require('../../addon/host');
+const { newURI } = require('../../url/utils');
+const { send } = require('../../addon/events');
+const { on, emit } = require('../../event/core');
+const { filter } = require('../../event/utils');
+
+const ROOT_FOLDERS = [
+ bookmarksService.unfiledBookmarksFolder, bookmarksService.toolbarFolder,
+ bookmarksService.bookmarksMenuFolder
+];
+
+const EVENT_MAP = {
+ 'sdk-places-query': queryReceiver
+};
+
+// Properties that need to be manually
+// copied into a nsINavHistoryQuery object
+const MANUAL_QUERY_PROPERTIES = [
+ 'uri', 'folder', 'tags', 'url', 'folder'
+];
+
+const PLACES_PROPERTIES = [
+ 'uri', 'title', 'accessCount', 'time'
+];
+
+function execute (queries, options) {
+ return new Promise(resolve => {
+ let root = historyService
+ .executeQueries(queries, queries.length, options).root;
+ // Let's extract an eventual uri wildcard, if both domain and uri are set.
+ // See utils.js::urlQueryParser() for more details.
+ // In case of multiple queries, we only retain the first found wildcard.
+ let uriWildcard = queries.reduce((prev, query) => {
+ if (query.uri && query.domain) {
+ if (!prev)
+ prev = query.uri.spec;
+ query.uri = null;
+ }
+ return prev;
+ }, "");
+ resolve(collect([], root, uriWildcard));
+ });
+}
+
+function collect (acc, node, uriWildcard) {
+ node.containerOpen = true;
+ for (let i = 0; i < node.childCount; i++) {
+ let child = node.getChild(i);
+
+ if (!uriWildcard || child.uri.startsWith(uriWildcard)) {
+ acc.push(child);
+ }
+ if (child.type === child.RESULT_TYPE_FOLDER) {
+ let container = child.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ collect(acc, container, uriWildcard);
+ }
+ }
+ node.containerOpen = false;
+ return acc;
+}
+
+function query (queries, options) {
+ return new Promise((resolve, reject) => {
+ queries = queries || [];
+ options = options || {};
+ let optionsObj, queryObjs;
+
+ optionsObj = historyService.getNewQueryOptions();
+ queryObjs = [].concat(queries).map(createQuery);
+ if (!queryObjs.length) {
+ queryObjs = [historyService.getNewQuery()];
+ }
+ safeMerge(optionsObj, options);
+
+ /*
+ * Currently `places:` queries are not supported
+ */
+ optionsObj.excludeQueries = true;
+
+ execute(queryObjs, optionsObj).then((results) => {
+ if (optionsObj.queryType === 0) {
+ return results.map(normalize);
+ }
+ else if (optionsObj.queryType === 1) {
+ // Formats query results into more standard
+ // data structures for returning
+ return all(results.map(({itemId}) =>
+ send('sdk-places-bookmarks-get', { id: itemId })));
+ }
+ }).then(resolve, reject);
+ });
+}
+exports.query = query;
+
+function createQuery (query) {
+ query = query || {};
+ let queryObj = historyService.getNewQuery();
+
+ safeMerge(queryObj, omit(query, MANUAL_QUERY_PROPERTIES));
+
+ if (query.tags && Array.isArray(query.tags))
+ queryObj.tags = query.tags;
+ if (query.uri || query.url)
+ queryObj.uri = newURI(query.uri || query.url);
+ if (query.folder)
+ queryObj.setFolders([query.folder], 1);
+ return queryObj;
+}
+
+function queryReceiver (message) {
+ let queries = message.data.queries || message.data.query;
+ let options = message.data.options;
+ let resData = {
+ id: message.id,
+ event: message.event
+ };
+
+ query(queries, options).then(results => {
+ resData.data = results;
+ respond(resData);
+ }, reason => {
+ resData.error = reason;
+ respond(resData);
+ });
+}
+
+/*
+ * Converts a nsINavHistoryResultNode into a plain object
+ *
+ * https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryResultNode
+ */
+function normalize (historyObj) {
+ return PLACES_PROPERTIES.reduce((obj, prop) => {
+ if (prop === 'uri')
+ obj.url = historyObj.uri;
+ else if (prop === 'time') {
+ // Cast from microseconds to milliseconds
+ obj.time = Math.floor(historyObj.time / 1000)
+ }
+ else if (prop === 'accessCount')
+ obj.visitCount = historyObj[prop];
+ else
+ obj[prop] = historyObj[prop];
+ return obj;
+ }, {});
+}
+
+/*
+ * Hook into host
+ */
+
+var reqStream = filter(request, data => /sdk-places-query/.test(data.event));
+on(reqStream, 'data', function (e) {
+ if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e);
+});
+
+function respond (data) {
+ emit(response, 'data', data);
+}
diff --git a/components/jetpack/sdk/places/host/host-tags.js b/components/jetpack/sdk/places/host/host-tags.js
new file mode 100644
index 000000000..b94342549
--- /dev/null
+++ b/components/jetpack/sdk/places/host/host-tags.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+const { Cc, Ci } = require('chrome');
+const taggingService = Cc["@mozilla.org/browser/tagging-service;1"].
+ getService(Ci.nsITaggingService);
+const ios = Cc['@mozilla.org/network/io-service;1'].
+ getService(Ci.nsIIOService);
+const { URL } = require('../../url');
+const { newURI } = require('../../url/utils');
+const { request, response } = require('../../addon/host');
+const { on, emit } = require('../../event/core');
+const { filter } = require('../../event/utils');
+
+const EVENT_MAP = {
+ 'sdk-places-tags-tag': tag,
+ 'sdk-places-tags-untag': untag,
+ 'sdk-places-tags-get-tags-by-url': getTagsByURL,
+ 'sdk-places-tags-get-urls-by-tag': getURLsByTag
+};
+
+function tag (message) {
+ let data = message.data;
+ let resData = {
+ id: message.id,
+ event: message.event
+ };
+
+ resData.data = taggingService.tagURI(newURI(data.url), data.tags);
+ respond(resData);
+}
+
+function untag (message) {
+ let data = message.data;
+ let resData = {
+ id: message.id,
+ event: message.event
+ };
+
+ resData.data = taggingService.untagURI(newURI(data.url), data.tags);
+ respond(resData);
+}
+
+function getURLsByTag (message) {
+ let data = message.data;
+ let resData = {
+ id: message.id,
+ event: message.event
+ };
+
+ resData.data = taggingService
+ .getURIsForTag(data.tag).map(uri => uri.spec);
+ respond(resData);
+}
+
+function getTagsByURL (message) {
+ let data = message.data;
+ let resData = {
+ id: message.id,
+ event: message.event
+ };
+
+ resData.data = taggingService.getTagsForURI(newURI(data.url), {});
+ respond(resData);
+}
+
+/*
+ * Hook into host
+ */
+
+var reqStream = filter(request, function (data) {
+ return /sdk-places-tags/.test(data.event);
+});
+
+on(reqStream, 'data', function (e) {
+ if (EVENT_MAP[e.event]) EVENT_MAP[e.event](e);
+});
+
+function respond (data) {
+ emit(response, 'data', data);
+}
diff --git a/components/jetpack/sdk/places/utils.js b/components/jetpack/sdk/places/utils.js
new file mode 100644
index 000000000..fe928c4ea
--- /dev/null
+++ b/components/jetpack/sdk/places/utils.js
@@ -0,0 +1,269 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+const { Cc, Ci, Cu } = require('chrome');
+const { Class } = require('../core/heritage');
+const { method } = require('../lang/functional');
+const { defer, promised, all } = require('../core/promise');
+const { send } = require('../addon/events');
+const { EventTarget } = require('../event/target');
+const { merge } = require('../util/object');
+const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Ci.nsINavBookmarksService);
+
+Cu.importGlobalProperties(["URL"]);
+
+/*
+ * TreeNodes are used to construct dependency trees
+ * for BookmarkItems
+ */
+var TreeNode = Class({
+ initialize: function (value) {
+ this.value = value;
+ this.children = [];
+ },
+ add: function (values) {
+ [].concat(values).forEach(value => {
+ this.children.push(value instanceof TreeNode ? value : TreeNode(value));
+ });
+ },
+ get length () {
+ let count = 0;
+ this.walk(() => count++);
+ // Do not count the current node
+ return --count;
+ },
+ get: method(get),
+ walk: method(walk),
+ toString: () => '[object TreeNode]'
+});
+exports.TreeNode = TreeNode;
+
+/*
+ * Descends down from `node` applying `fn` to each in order.
+ * `fn` can return values or promises -- if promise returned,
+ * children are not processed until resolved. `fn` is passed
+ * one argument, the current node, `curr`.
+ */
+function walk (curr, fn) {
+ return promised(fn)(curr).then(val => {
+ return all(curr.children.map(child => walk(child, fn)));
+ });
+}
+
+/*
+ * Descends from the TreeNode `node`, returning
+ * the node with value `value` if found or `null`
+ * otherwise
+ */
+function get (node, value) {
+ if (node.value === value) return node;
+ for (let child of node.children) {
+ let found = get(child, value);
+ if (found) return found;
+ }
+ return null;
+}
+
+/*
+ * Constructs a tree of bookmark nodes
+ * returning the root (value: null);
+ */
+
+function constructTree (items) {
+ let root = TreeNode(null);
+ items.forEach(treeify.bind(null, root));
+
+ function treeify (root, item) {
+ // If node already exists, skip
+ let node = root.get(item);
+ if (node) return node;
+ node = TreeNode(item);
+
+ let parentNode = item.group ? treeify(root, item.group) : root;
+ parentNode.add(node);
+
+ return node;
+ }
+
+ return root;
+}
+exports.constructTree = constructTree;
+
+/*
+ * Shortcut for converting an id, or an object with an id, into
+ * an object with corresponding bookmark data
+ */
+function fetchItem (item) {
+ return send('sdk-places-bookmarks-get', { id: item.id || item });
+}
+exports.fetchItem = fetchItem;
+
+/*
+ * Takes an ID or an object with ID and checks it against
+ * the root bookmark folders
+ */
+function isRootGroup (id) {
+ id = id && id.id;
+ return ~[bmsrv.bookmarksMenuFolder, bmsrv.toolbarFolder,
+ bmsrv.unfiledBookmarksFolder
+ ].indexOf(id);
+}
+exports.isRootGroup = isRootGroup;
+
+/*
+ * Merges appropriate options into query based off of url
+ * 4 scenarios:
+ *
+ * 'moz.com' // domain: moz.com, domainIsHost: true
+ * --> 'http://moz.com', 'http://moz.com/thunderbird'
+ * '*.moz.com' // domain: moz.com, domainIsHost: false
+ * --> 'http://moz.com', 'http://moz.com/index', 'http://ff.moz.com/test'
+ * 'http://moz.com' // uri: http://moz.com/
+ * --> 'http://moz.com/'
+ * 'http://moz.com/*' // uri: http://moz.com/, domain: moz.com, domainIsHost: true
+ * --> 'http://moz.com/', 'http://moz.com/thunderbird'
+ */
+
+function urlQueryParser (query, url) {
+ if (!url) return;
+ if (/^https?:\/\//.test(url)) {
+ query.uri = url.charAt(url.length - 1) === '/' ? url : url + '/';
+ if (/\*$/.test(url)) {
+ // Wildcard searches on URIs are not supported, so try to extract a
+ // domain and filter the data later.
+ url = url.replace(/\*$/, '');
+ try {
+ query.domain = new URL(url).hostname;
+ query.domainIsHost = true;
+ // Unfortunately here we cannot use an expando to store the wildcard,
+ // cause the query is a wrapped native XPCOM object, so we reuse uri.
+ // We clearly don't want to query for both uri and domain, thus we'll
+ // have to handle this in host-query.js::execute()
+ query.uri = url;
+ } catch (ex) {
+ // Cannot extract an host cause it's not a valid uri, the query will
+ // just return nothing.
+ }
+ }
+ } else {
+ if (/^\*/.test(url)) {
+ query.domain = url.replace(/^\*\./, '');
+ query.domainIsHost = false;
+ } else {
+ query.domain = url;
+ query.domainIsHost = true;
+ }
+ }
+}
+exports.urlQueryParser = urlQueryParser;
+
+/*
+ * Takes an EventEmitter and returns a promise that
+ * aggregates results and handles a bulk resolve and reject
+ */
+
+function promisedEmitter (emitter) {
+ let { promise, resolve, reject } = defer();
+ let errors = [];
+ emitter.on('error', error => errors.push(error));
+ emitter.on('end', (items) => {
+ if (errors.length) reject(errors[0]);
+ else resolve(items);
+ });
+ return promise;
+}
+exports.promisedEmitter = promisedEmitter;
+
+
+// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
+function createQuery (type, query) {
+ query = query || {};
+ let qObj = {
+ searchTerms: query.query
+ };
+
+ urlQueryParser(qObj, query.url);
+
+ // 0 === history
+ if (type === 0) {
+ // PRTime used by query is in microseconds, not milliseconds
+ qObj.beginTime = (query.from || 0) * 1000;
+ qObj.endTime = (query.to || new Date()) * 1000;
+
+ // Set reference time to Epoch
+ qObj.beginTimeReference = 0;
+ qObj.endTimeReference = 0;
+ }
+ // 1 === bookmarks
+ else if (type === 1) {
+ qObj.tags = query.tags;
+ qObj.folder = query.group && query.group.id;
+ }
+ // 2 === unified (not implemented on platform)
+ else if (type === 2) {
+
+ }
+
+ return qObj;
+}
+exports.createQuery = createQuery;
+
+// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsINavHistoryQueryOptions
+
+const SORT_MAP = {
+ title: 1,
+ date: 3, // sort by visit date
+ url: 5,
+ visitCount: 7,
+ // keywords currently unsupported
+ // keyword: 9,
+ dateAdded: 11, // bookmarks only
+ lastModified: 13 // bookmarks only
+};
+
+function createQueryOptions (type, options) {
+ options = options || {};
+ let oObj = {};
+ oObj.sortingMode = SORT_MAP[options.sort] || 0;
+ if (options.descending && options.sort)
+ oObj.sortingMode++;
+
+ // Resolve to default sort if ineligible based on query type
+ if (type === 0 && // history
+ (options.sort === 'dateAdded' || options.sort === 'lastModified'))
+ oObj.sortingMode = 0;
+
+ oObj.maxResults = typeof options.count === 'number' ? options.count : 0;
+
+ oObj.queryType = type;
+
+ return oObj;
+}
+exports.createQueryOptions = createQueryOptions;
+
+
+function mapBookmarkItemType (type) {
+ if (typeof type === 'number') {
+ if (bmsrv.TYPE_BOOKMARK === type) return 'bookmark';
+ if (bmsrv.TYPE_FOLDER === type) return 'group';
+ if (bmsrv.TYPE_SEPARATOR === type) return 'separator';
+ } else {
+ if ('bookmark' === type) return bmsrv.TYPE_BOOKMARK;
+ if ('group' === type) return bmsrv.TYPE_FOLDER;
+ if ('separator' === type) return bmsrv.TYPE_SEPARATOR;
+ }
+}
+exports.mapBookmarkItemType = mapBookmarkItemType;
diff --git a/components/jetpack/sdk/platform/xpcom.js b/components/jetpack/sdk/platform/xpcom.js
new file mode 100644
index 000000000..383baf67a
--- /dev/null
+++ b/components/jetpack/sdk/platform/xpcom.js
@@ -0,0 +1,241 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, Cr, Cm, components: { classesByID } } = require('chrome');
+const { registerFactory, unregisterFactory, isCIDRegistered } =
+ Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+const { merge } = require('../util/object');
+const { Class, extend, mix } = require('../core/heritage');
+const { uuid } = require('../util/uuid');
+
+// This is a base prototype, that provides bare bones of XPCOM. JS based
+// components can be easily implement by extending it.
+const Unknown = new function() {
+ function hasInterface(component, iid) {
+ return component && component.interfaces &&
+ ( component.interfaces.some(id => iid.equals(Ci[id])) ||
+ component.implements.some($ => hasInterface($, iid)) ||
+ hasInterface(Object.getPrototypeOf(component), iid));
+ }
+
+ return Class({
+ /**
+ * The `QueryInterface` method provides runtime type discovery used by XPCOM.
+ * This method return queried instance of `this` if given `iid` is listed in
+ * the `interfaces` property or in equivalent properties of objects in it's
+ * prototype chain. In addition it will look up in the prototypes under
+ * `implements` array property, this ways compositions made via `Class`
+ * utility will carry interfaces implemented by composition components.
+ */
+ QueryInterface: function QueryInterface(iid) {
+ // For some reason there are cases when `iid` is `null`. In such cases we
+ // just return `this`. Otherwise we verify that component implements given
+ // `iid` interface. This will be no longer necessary once Bug 748003 is
+ // fixed.
+ if (iid && !hasInterface(this, iid))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ },
+ /**
+ * Array of `XPCOM` interfaces (as strings) implemented by this component.
+ * All components implement `nsISupports` by default which is default value
+ * here. Provide array of interfaces implemented by an object when
+ * extending, to append them to this list (Please note that there is no
+ * need to repeat interfaces implemented by super as they will be added
+ * automatically).
+ */
+ interfaces: Object.freeze([ 'nsISupports' ])
+ });
+}
+exports.Unknown = Unknown;
+
+// Base exemplar for creating instances implementing `nsIFactory` interface,
+// that maybe registered into runtime via `register` function. Instances of
+// this factory create instances of enclosed component on `createInstance`.
+const Factory = Class({
+ extends: Unknown,
+ interfaces: [ 'nsIFactory' ],
+ /**
+ * All the descendants will get auto generated `id` (also known as `classID`
+ * in XPCOM world) unless one is manually provided.
+ */
+ get id() { throw Error('Factory must implement `id` property') },
+ /**
+ * XPCOM `contractID` may optionally be provided to associate this factory
+ * with it. `contract` is a unique string that has a following format:
+ * '@vendor.com/unique/id;1'.
+ */
+ contract: null,
+ /**
+ * Class description that is being registered. This value is intended as a
+ * human-readable description for the given class and does not needs to be
+ * globally unique.
+ */
+ description: 'Jetpack generated factory',
+ /**
+ * This method is required by `nsIFactory` interfaces, but as in most
+ * implementations it does nothing interesting.
+ */
+ lockFactory: function lockFactory(lock) {
+ return undefined;
+ },
+ /**
+ * If property is `true` XPCOM service / factory will be registered
+ * automatically on creation.
+ */
+ register: true,
+ /**
+ * If property is `true` XPCOM factory will be unregistered prior to add-on
+ * unload.
+ */
+ unregister: true,
+ /**
+ * Method is called on `Service.new(options)` passing given `options` to
+ * it. Options is expected to have `component` property holding XPCOM
+ * component implementation typically decedent of `Unknown` or any custom
+ * implementation with a `new` method and optional `register`, `unregister`
+ * flags. Unless `register` is `false` Service / Factory will be
+ * automatically registered. Unless `unregister` is `false` component will
+ * be automatically unregistered on add-on unload.
+ */
+ initialize: function initialize(options) {
+ merge(this, {
+ id: 'id' in options ? options.id : uuid(),
+ register: 'register' in options ? options.register : this.register,
+ unregister: 'unregister' in options ? options.unregister : this.unregister,
+ contract: 'contract' in options ? options.contract : null,
+ Component: options.Component
+ });
+
+ // If service / factory has auto registration enabled then register.
+ if (this.register)
+ register(this);
+ },
+ /**
+ * Creates an instance of the class associated with this factory.
+ */
+ createInstance: function createInstance(outer, iid) {
+ try {
+ if (outer)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return this.create().QueryInterface(iid);
+ }
+ catch (error) {
+ throw error instanceof Ci.nsIException ? error : Cr.NS_ERROR_FAILURE;
+ }
+ },
+ create: function create() {
+ return this.Component();
+ }
+});
+exports.Factory = Factory;
+
+// Exemplar for creating services that implement `nsIFactory` interface, that
+// can be registered into runtime via call to `register`. This services return
+// enclosed `component` on `getService`.
+const Service = Class({
+ extends: Factory,
+ initialize: function initialize(options) {
+ this.component = options.Component();
+ Factory.prototype.initialize.call(this, options);
+ },
+ description: 'Jetpack generated service',
+ /**
+ * Creates an instance of the class associated with this factory.
+ */
+ create: function create() {
+ return this.component;
+ }
+});
+exports.Service = Service;
+
+function isRegistered({ id }) {
+ return isCIDRegistered(id);
+}
+exports.isRegistered = isRegistered;
+
+/**
+ * Registers given `component` object to be used to instantiate a particular
+ * class identified by `component.id`, and creates an association of class
+ * name and `component.contract` with the class.
+ */
+function register(factory) {
+ if (!(factory instanceof Factory)) {
+ throw new Error("xpcom.register() expect a Factory instance.\n" +
+ "Please refactor your code to new xpcom module if you" +
+ " are repacking an addon from SDK <= 1.5:\n" +
+ "https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/platform_xpcom");
+ }
+
+ registerFactory(factory.id, factory.description, factory.contract, factory);
+
+ if (factory.unregister)
+ require('../system/unload').when(unregister.bind(null, factory));
+}
+exports.register = register;
+
+/**
+ * Unregister a factory associated with a particular class identified by
+ * `factory.classID`.
+ */
+function unregister(factory) {
+ if (isRegistered(factory))
+ unregisterFactory(factory.id, factory);
+}
+exports.unregister = unregister;
+
+function autoRegister(path) {
+ // TODO: This assumes that the url points to a directory
+ // that contains subdirectories corresponding to OS/ABI and then
+ // further subdirectories corresponding to Gecko platform version.
+ // we should probably either behave intelligently here or allow
+ // the caller to pass-in more options if e.g. there aren't
+ // Gecko-specific binaries for a component (which will be the case
+ // if only frozen interfaces are used).
+
+ var runtime = require("../system/runtime");
+ var osDirName = runtime.OS + "_" + runtime.XPCOMABI;
+ var platformVersion = require("../system/xul-app").platformVersion.substring(0, 5);
+
+ var file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ file.append(osDirName);
+ file.append(platformVersion);
+
+ if (!(file.exists() && file.isDirectory()))
+ throw new Error("component not available for OS/ABI " +
+ osDirName + " and platform " + platformVersion);
+
+ Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ Cm.autoRegister(file);
+}
+exports.autoRegister = autoRegister;
+
+/**
+ * Returns registered factory that has a given `id` or `null` if not found.
+ */
+function factoryByID(id) {
+ return classesByID[id] || null;
+}
+exports.factoryByID = factoryByID;
+
+/**
+ * Returns factory registered with a given `contract` or `null` if not found.
+ * In contrast to `Cc[contract]` that does ignores new factory registration
+ * with a given `contract` this will return a factory currently associated
+ * with a `contract`.
+ */
+function factoryByContract(contract) {
+ return factoryByID(Cm.contractIDToCID(contract));
+}
+exports.factoryByContract = factoryByContract;
diff --git a/components/jetpack/sdk/preferences/event-target.js b/components/jetpack/sdk/preferences/event-target.js
new file mode 100644
index 000000000..b64ba303c
--- /dev/null
+++ b/components/jetpack/sdk/preferences/event-target.js
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci } = require('chrome');
+const { Class } = require('../core/heritage');
+const { EventTarget } = require('../event/target');
+const { Branch } = require('./service');
+const { emit, off } = require('../event/core');
+const { when: unload } = require('../system/unload');
+
+const prefTargetNS = require('../core/namespace').ns();
+
+const PrefsTarget = Class({
+ extends: EventTarget,
+ initialize: function(options) {
+ options = options || {};
+ EventTarget.prototype.initialize.call(this, options);
+
+ let branchName = options.branchName || '';
+ let branch = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(branchName).
+ QueryInterface(Ci.nsIPrefBranch2);
+ prefTargetNS(this).branch = branch;
+
+ // provides easy access to preference values
+ this.prefs = Branch(branchName);
+
+ // start listening to preference changes
+ let observer = prefTargetNS(this).observer = onChange.bind(this);
+ branch.addObserver('', observer, false);
+
+ // Make sure to destroy this on unload
+ unload(destroy.bind(this));
+ }
+});
+exports.PrefsTarget = PrefsTarget;
+
+/* HELPERS */
+
+function onChange(subject, topic, name) {
+ if (topic === 'nsPref:changed') {
+ emit(this, name, name);
+ emit(this, '', name);
+ }
+}
+
+function destroy() {
+ off(this);
+
+ // stop listening to preference changes
+ let branch = prefTargetNS(this).branch;
+ branch.removeObserver('', prefTargetNS(this).observer, false);
+ prefTargetNS(this).observer = null;
+}
diff --git a/components/jetpack/sdk/preferences/native-options.js b/components/jetpack/sdk/preferences/native-options.js
new file mode 100644
index 000000000..840997df9
--- /dev/null
+++ b/components/jetpack/sdk/preferences/native-options.js
@@ -0,0 +1,193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, Cu } = require('chrome');
+const { on } = require('../system/events');
+const { id, preferencesBranch } = require('../self');
+const { localizeInlineOptions } = require('../l10n/prefs');
+const { Services } = require("resource://gre/modules/Services.jsm");
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
+const { defer } = require("sdk/core/promise");
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";;
+const DEFAULT_OPTIONS_URL = 'data:text/xml,<placeholder/>';
+
+const VALID_PREF_TYPES = ['bool', 'boolint', 'integer', 'string', 'color',
+ 'file', 'directory', 'control', 'menulist', 'radio'];
+
+const isFennec = require("sdk/system/xul-app").is("Fennec");
+
+function enable({ preferences, id }) {
+ let enabled = defer();
+
+ validate(preferences);
+
+ setDefaults(preferences, preferencesBranch);
+
+ // allow the use of custom options.xul
+ AddonManager.getAddonByID(id, (addon) => {
+ on('addon-options-displayed', onAddonOptionsDisplayed, true);
+ enabled.resolve({ id: id });
+ });
+
+ function onAddonOptionsDisplayed({ subject: doc, data }) {
+ if (data === id) {
+ let parent;
+
+ if (isFennec) {
+ parent = doc.querySelector('.options-box');
+
+ // NOTE: This disable the CSS rule that makes the options invisible
+ let item = doc.querySelector('#addons-details .addon-item');
+ item.removeAttribute("optionsURL");
+ } else {
+ parent = doc.getElementById('detail-downloads').parentNode;
+ }
+
+ if (parent) {
+ injectOptions({
+ preferences: preferences,
+ preferencesBranch: preferencesBranch,
+ document: doc,
+ parent: parent,
+ id: id
+ });
+ localizeInlineOptions(doc);
+ } else {
+ throw Error("Preferences parent node not found in Addon Details. The configured custom preferences will not be visible.");
+ }
+ }
+ }
+
+ return enabled.promise;
+}
+exports.enable = enable;
+
+// centralized sanity checks
+function validate(preferences) {
+ for (let { name, title, type, label, options } of preferences) {
+ // make sure the title is set and non-empty
+ if (!title)
+ throw Error("The '" + name + "' pref requires a title");
+
+ // make sure that pref type is a valid inline option type
+ if (!~VALID_PREF_TYPES.indexOf(type))
+ throw Error("The '" + name + "' pref must be of valid type");
+
+ // if it's a control, make sure it has a label
+ if (type === 'control' && !label)
+ throw Error("The '" + name + "' control requires a label");
+
+ // if it's a menulist or radio, make sure it has options
+ if (type === 'menulist' || type === 'radio') {
+ if (!options)
+ throw Error("The '" + name + "' pref requires options");
+
+ // make sure each option has a value and a label
+ for (let item of options) {
+ if (!('value' in item) || !('label' in item))
+ throw Error("Each option requires both a value and a label");
+ }
+ }
+
+ // TODO: check that pref type matches default value type
+ }
+}
+exports.validate = validate;
+
+// initializes default preferences, emulates defaults/prefs.js
+function setDefaults(preferences, preferencesBranch) {
+ const branch = Cc['@mozilla.org/preferences-service;1'].
+ getService(Ci.nsIPrefService).
+ getDefaultBranch('extensions.' + preferencesBranch + '.');
+ for (let { name, value } of preferences) {
+ switch (typeof value) {
+ case 'boolean':
+ branch.setBoolPref(name, value);
+ break;
+ case 'number':
+ // must be integer, ignore otherwise
+ if (value % 1 === 0) {
+ branch.setIntPref(name, value);
+ }
+ break;
+ case 'string':
+ let str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ str.data = value;
+ branch.setComplexValue(name, Ci.nsISupportsString, str);
+ break;
+ }
+ }
+}
+exports.setDefaults = setDefaults;
+
+// dynamically injects inline options into about:addons page at runtime
+// NOTE: on Firefox Desktop the about:addons page is a xul page document,
+// on Firefox for Android the about:addons page is an xhtml page, to support both
+// the XUL xml namespace have to be enforced.
+function injectOptions({ preferences, preferencesBranch, document, parent, id }) {
+ preferences.forEach(({name, type, hidden, title, description, label, options, on, off}) => {
+ if (hidden) {
+ return;
+ }
+
+ let setting = document.createElementNS(XUL_NS, 'setting');
+ setting.setAttribute('pref-name', name);
+ setting.setAttribute('data-jetpack-id', id);
+ setting.setAttribute('pref', 'extensions.' + preferencesBranch + '.' + name);
+ setting.setAttribute('type', type);
+ setting.setAttribute('title', title);
+ if (description)
+ setting.setAttribute('desc', description);
+
+ if (type === 'file' || type === 'directory') {
+ setting.setAttribute('fullpath', 'true');
+ }
+ else if (type === 'control') {
+ let button = document.createElementNS(XUL_NS, 'button');
+ button.setAttribute('pref-name', name);
+ button.setAttribute('data-jetpack-id', id);
+ button.setAttribute('label', label);
+ button.addEventListener('command', function() {
+ Services.obs.notifyObservers(null, `${id}-cmdPressed`, name);
+ }, true);
+ setting.appendChild(button);
+ }
+ else if (type === 'boolint') {
+ setting.setAttribute('on', on);
+ setting.setAttribute('off', off);
+ }
+ else if (type === 'menulist') {
+ let menulist = document.createElementNS(XUL_NS, 'menulist');
+ let menupopup = document.createElementNS(XUL_NS, 'menupopup');
+ for (let { value, label } of options) {
+ let menuitem = document.createElementNS(XUL_NS, 'menuitem');
+ menuitem.setAttribute('value', value);
+ menuitem.setAttribute('label', label);
+ menupopup.appendChild(menuitem);
+ }
+ menulist.appendChild(menupopup);
+ setting.appendChild(menulist);
+ }
+ else if (type === 'radio') {
+ let radiogroup = document.createElementNS(XUL_NS, 'radiogroup');
+ for (let { value, label } of options) {
+ let radio = document.createElementNS(XUL_NS, 'radio');
+ radio.setAttribute('value', value);
+ radio.setAttribute('label', label);
+ radiogroup.appendChild(radio);
+ }
+ setting.appendChild(radiogroup);
+ }
+
+ parent.appendChild(setting);
+ });
+}
+exports.injectOptions = injectOptions;
diff --git a/components/jetpack/sdk/preferences/service.js b/components/jetpack/sdk/preferences/service.js
new file mode 100644
index 000000000..231cd8e14
--- /dev/null
+++ b/components/jetpack/sdk/preferences/service.js
@@ -0,0 +1,137 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+// The minimum and maximum integers that can be set as preferences.
+// The range of valid values is narrower than the range of valid JS values
+// because the native preferences code treats integers as NSPR PRInt32s,
+// which are 32-bit signed integers on all platforms.
+const MAX_INT = 0x7FFFFFFF;
+const MIN_INT = -0x80000000;
+
+const {Cc,Ci,Cr} = require("chrome");
+
+const prefService = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+const prefSvc = prefService.getBranch(null);
+const defaultBranch = prefService.getDefaultBranch(null);
+
+const { Preferences } = require("resource://gre/modules/Preferences.jsm");
+const prefs = new Preferences({});
+
+const branchKeys = branchName =>
+ keys(branchName).map($ => $.replace(branchName, ""));
+
+const Branch = function(branchName) {
+ return new Proxy(Branch.prototype, {
+ getOwnPropertyDescriptor(target, name, receiver) {
+ return {
+ configurable: true,
+ enumerable: true,
+ writable: false,
+ value: this.get(target, name, receiver)
+ };
+ },
+ ownKeys(target) {
+ return branchKeys(branchName);
+ },
+ get(target, name, receiver) {
+ return get(`${branchName}${name}`);
+ },
+ set(target, name, value, receiver) {
+ set(`${branchName}${name}`, value);
+ return true;
+ },
+ has(target, name) {
+ return this.hasOwn(target, name);
+ },
+ hasOwn(target, name) {
+ return has(`${branchName}${name}`);
+ },
+ deleteProperty(target, name) {
+ reset(`${branchName}${name}`);
+ return true;
+ }
+ });
+}
+
+
+function get(name, defaultValue) {
+ return prefs.get(name, defaultValue);
+}
+exports.get = get;
+
+
+function set(name, value) {
+ var prefType;
+ if (typeof value != "undefined" && value != null)
+ prefType = value.constructor.name;
+
+ switch (prefType) {
+ case "Number":
+ if (value % 1 != 0)
+ throw new Error("cannot store non-integer number: " + value);
+ }
+
+ prefs.set(name, value);
+}
+exports.set = set;
+
+const has = prefs.has.bind(prefs)
+exports.has = has;
+
+function keys(root) {
+ return prefSvc.getChildList(root);
+}
+exports.keys = keys;
+
+const isSet = prefs.isSet.bind(prefs);
+exports.isSet = isSet;
+
+function reset(name) {
+ try {
+ prefSvc.clearUserPref(name);
+ }
+ catch (e) {
+ // The pref service throws NS_ERROR_UNEXPECTED when the caller tries
+ // to reset a pref that doesn't exist or is already set to its default
+ // value. This interface fails silently in those cases, so callers
+ // can unconditionally reset a pref without having to check if it needs
+ // resetting first or trap exceptions after the fact. It passes through
+ // other exceptions, however, so callers know about them, since we don't
+ // know what other exceptions might be thrown and what they might mean.
+ if (e.result != Cr.NS_ERROR_UNEXPECTED) {
+ throw e;
+ }
+ }
+}
+exports.reset = reset;
+
+function getLocalized(name, defaultValue) {
+ let value = null;
+ try {
+ value = prefSvc.getComplexValue(name, Ci.nsIPrefLocalizedString).data;
+ }
+ finally {
+ return value || defaultValue;
+ }
+}
+exports.getLocalized = getLocalized;
+
+function setLocalized(name, value) {
+ // We can't use `prefs.set` here as we have to use `getDefaultBranch`
+ // (instead of `getBranch`) in order to have `mIsDefault` set to true, here:
+ // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#233
+ // Otherwise, we do not enter into this expected condition:
+ // http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/nsPrefBranch.cpp#244
+ defaultBranch.setCharPref(name, value);
+}
+exports.setLocalized = setLocalized;
+
+exports.Branch = Branch;
+
diff --git a/components/jetpack/sdk/preferences/utils.js b/components/jetpack/sdk/preferences/utils.js
new file mode 100644
index 000000000..1d5769c37
--- /dev/null
+++ b/components/jetpack/sdk/preferences/utils.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { openTab, getBrowserForTab, getTabId } = require("sdk/tabs/utils");
+const { on, off } = require("sdk/system/events");
+const { getMostRecentBrowserWindow } = require('../window/utils');
+
+// Opens about:addons in a new tab, then displays the inline
+// preferences of the provided add-on
+const open = ({ id }) => new Promise((resolve, reject) => {
+ // opening the about:addons page in a new tab
+ let tab = openTab(getMostRecentBrowserWindow(), "about:addons");
+ let browser = getBrowserForTab(tab);
+
+ // waiting for the about:addons page to load
+ browser.addEventListener("load", function onPageLoad() {
+ browser.removeEventListener("load", onPageLoad, true);
+ let window = browser.contentWindow;
+
+ // wait for the add-on's "addon-options-displayed"
+ on("addon-options-displayed", function onPrefDisplayed({ subject: doc, data }) {
+ if (data === id) {
+ off("addon-options-displayed", onPrefDisplayed);
+ resolve({
+ id: id,
+ tabId: getTabId(tab),
+ "document": doc
+ });
+ }
+ }, true);
+
+ // display the add-on inline preferences page
+ window.gViewController.commands.cmd_showItemDetails.doCommand({ id: id }, true);
+ }, true);
+});
+exports.open = open;
diff --git a/components/jetpack/sdk/private-browsing.js b/components/jetpack/sdk/private-browsing.js
new file mode 100644
index 000000000..29ca16185
--- /dev/null
+++ b/components/jetpack/sdk/private-browsing.js
@@ -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/. */
+'use strict';
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { isPrivate } = require('./private-browsing/utils');
+
+exports.isPrivate = isPrivate;
diff --git a/components/jetpack/sdk/private-browsing/utils.js b/components/jetpack/sdk/private-browsing/utils.js
new file mode 100644
index 000000000..8b012f0ce
--- /dev/null
+++ b/components/jetpack/sdk/private-browsing/utils.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, Cu } = require('chrome');
+const { is } = require('../system/xul-app');
+const { isWindowPrivate } = require('../window/utils');
+const { isPrivateBrowsingSupported } = require('../self');
+const { dispatcher } = require("../util/dispatcher");
+
+var PrivateBrowsingUtils;
+
+// Private browsing is only supported in Fx
+try {
+ PrivateBrowsingUtils = Cu.import('resource://gre/modules/PrivateBrowsingUtils.jsm', {}).PrivateBrowsingUtils;
+}
+catch (e) {}
+
+exports.isGlobalPBSupported = false;
+
+// checks that per-window private browsing is implemented
+var isWindowPBSupported = exports.isWindowPBSupported =
+ !!PrivateBrowsingUtils && is('Firefox');
+
+// checks that per-tab private browsing is implemented
+var isTabPBSupported = exports.isTabPBSupported =
+ !!PrivateBrowsingUtils && is('Fennec');
+
+function isPermanentPrivateBrowsing() {
+ return !!(PrivateBrowsingUtils && PrivateBrowsingUtils.permanentPrivateBrowsing);
+}
+exports.isPermanentPrivateBrowsing = isPermanentPrivateBrowsing;
+
+function ignoreWindow(window) {
+ return !isPrivateBrowsingSupported && isWindowPrivate(window);
+}
+exports.ignoreWindow = ignoreWindow;
+
+var getMode = function getMode(chromeWin) {
+ return (chromeWin !== undefined && isWindowPrivate(chromeWin));
+};
+exports.getMode = getMode;
+
+const isPrivate = dispatcher("isPrivate");
+isPrivate.when(isPermanentPrivateBrowsing, _ => true);
+isPrivate.when(x => x instanceof Ci.nsIDOMWindow, isWindowPrivate);
+isPrivate.when(x => Ci.nsIPrivateBrowsingChannel && x instanceof Ci.nsIPrivateBrowsingChannel, x => x.isChannelPrivate);
+isPrivate.define(() => false);
+exports.isPrivate = isPrivate;
diff --git a/components/jetpack/sdk/querystring.js b/components/jetpack/sdk/querystring.js
new file mode 100644
index 000000000..9982a00ab
--- /dev/null
+++ b/components/jetpack/sdk/querystring.js
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+var unescape = decodeURIComponent;
+exports.unescape = unescape;
+
+// encodes a string safely for application/x-www-form-urlencoded
+// adheres to RFC 3986
+// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURIComponent
+function escape(query) {
+ return encodeURIComponent(query).
+ replace(/%20/g, '+').
+ replace(/!/g, '%21').
+ replace(/'/g, '%27').
+ replace(/\(/g, '%28').
+ replace(/\)/g, '%29').
+ replace(/\*/g, '%2A');
+}
+exports.escape = escape;
+
+// Converts an object of unordered key-vals to a string that can be passed
+// as part of a request
+function stringify(options, separator, assigner) {
+ separator = separator || '&';
+ assigner = assigner || '=';
+ // Explicitly return null if we have null, and empty string, or empty object.
+ if (!options)
+ return '';
+
+ // If content is already a string, just return it as is.
+ if (typeof(options) == 'string')
+ return options;
+
+ // At this point we have a k:v object. Iterate over it and encode each value.
+ // Arrays and nested objects will get encoded as needed. For example...
+ //
+ // { foo: [1, 2, { omg: 'bbq', 'all your base!': 'are belong to us' }], bar: 'baz' }
+ //
+ // will be encoded as
+ //
+ // foo[0]=1&foo[1]=2&foo[2][omg]=bbq&foo[2][all+your+base!]=are+belong+to+us&bar=baz
+ //
+ // Keys (including '[' and ']') and values will be encoded with
+ // `escape` before returning.
+ //
+ // Execution was inspired by jQuery, but some details have changed and numeric
+ // array keys are included (whereas they are not in jQuery).
+
+ let encodedContent = [];
+ function add(key, val) {
+ encodedContent.push(escape(key) + assigner + escape(val));
+ }
+
+ function make(key, value) {
+ if (value && typeof(value) === 'object')
+ Object.keys(value).forEach(function(name) {
+ make(key + '[' + name + ']', value[name]);
+ });
+ else
+ add(key, value);
+ }
+
+ Object.keys(options).forEach(function(name) { make(name, options[name]); });
+ return encodedContent.join(separator);
+
+ //XXXzpao In theory, we can just use a FormData object on 1.9.3, but I had
+ // trouble getting that working. It would also be nice to stay
+ // backwards-compat as long as possible. Keeping this in for now...
+ // let formData = Cc['@mozilla.org/files/formdata;1'].
+ // createInstance(Ci.nsIDOMFormData);
+ // for ([k, v] in Iterator(content)) {
+ // formData.append(k, v);
+ // }
+ // return formData;
+}
+exports.stringify = stringify;
+
+// Exporting aliases that nodejs implements just for the sake of
+// interoperability.
+exports.encode = stringify;
+exports.serialize = stringify;
+
+// Note: That `stringify` and `parse` aren't bijective as we use `stringify`
+// as it was implement in request module, but implement `parse` to match nodejs
+// behavior.
+// TODO: Make `stringify` implement API as in nodejs and figure out backwards
+// compatibility.
+function parse(query, separator, assigner) {
+ separator = separator || '&';
+ assigner = assigner || '=';
+ let result = {};
+
+ if (typeof query !== 'string' || query.length === 0)
+ return result;
+
+ query.split(separator).forEach(function(chunk) {
+ let pair = chunk.split(assigner);
+ let key = unescape(pair[0]);
+ let value = unescape(pair.slice(1).join(assigner));
+
+ if (!(key in result))
+ result[key] = value;
+ else if (Array.isArray(result[key]))
+ result[key].push(value);
+ else
+ result[key] = [result[key], value];
+ });
+
+ return result;
+};
+exports.parse = parse;
+// Exporting aliases that nodejs implements just for the sake of
+// interoperability.
+exports.decode = parse;
diff --git a/components/jetpack/sdk/remote/child.js b/components/jetpack/sdk/remote/child.js
new file mode 100644
index 000000000..4ccfa661a
--- /dev/null
+++ b/components/jetpack/sdk/remote/child.js
@@ -0,0 +1,284 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { isChildLoader } = require('./core');
+if (!isChildLoader)
+ throw new Error("Cannot load sdk/remote/child in a main process loader.");
+
+const { Ci, Cc, Cu } = require('chrome');
+const runtime = require('../system/runtime');
+const { Class } = require('../core/heritage');
+const { Namespace } = require('../core/namespace');
+const { omit } = require('../util/object');
+const { when } = require('../system/unload');
+const { EventTarget } = require('../event/target');
+const { emit } = require('../event/core');
+const { Disposable } = require('../core/disposable');
+const { EventParent } = require('./utils');
+const { addListItem, removeListItem } = require('../util/list');
+
+const loaderID = require('@loader/options').loaderID;
+
+const MAIN_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+
+const mm = Cc['@mozilla.org/childprocessmessagemanager;1'].
+ getService(Ci.nsISyncMessageSender);
+
+const ns = Namespace();
+
+const process = {
+ port: new EventTarget(),
+ get id() {
+ return runtime.processID;
+ },
+ get isRemote() {
+ return runtime.processType != MAIN_PROCESS;
+ }
+};
+exports.process = process;
+
+function definePort(obj, name) {
+ obj.port.emit = (event, ...args) => {
+ let manager = ns(obj).messageManager;
+ if (!manager)
+ return;
+
+ manager.sendAsyncMessage(name, { loaderID, event, args });
+ };
+}
+
+function messageReceived({ data, objects }) {
+ // Ignore messages from other loaders
+ if (data.loaderID != loaderID)
+ return;
+
+ let keys = Object.keys(objects);
+ if (keys.length) {
+ // If any objects are CPOWs then ignore this message. We don't want child
+ // processes interracting with CPOWs
+ if (!keys.every(name => !Cu.isCrossProcessWrapper(objects[name])))
+ return;
+
+ data.args.push(objects);
+ }
+
+ emit(this.port, data.event, this, ...data.args);
+}
+
+ns(process).messageManager = mm;
+definePort(process, 'sdk/remote/process/message');
+let processMessageReceived = messageReceived.bind(process);
+mm.addMessageListener('sdk/remote/process/message', processMessageReceived);
+
+when(() => {
+ mm.removeMessageListener('sdk/remote/process/message', processMessageReceived);
+ frames = null;
+});
+
+process.port.on('sdk/remote/require', (process, uri) => {
+ require(uri);
+});
+
+function listenerEquals(a, b) {
+ for (let prop of ["type", "callback", "isCapturing"]) {
+ if (a[prop] != b[prop])
+ return false;
+ }
+ return true;
+}
+
+function listenerFor(type, callback, isCapturing = false) {
+ return {
+ type,
+ callback,
+ isCapturing,
+ registeredCallback: undefined,
+ get args() {
+ return [
+ this.type,
+ this.registeredCallback ? this.registeredCallback : this.callback,
+ this.isCapturing
+ ];
+ }
+ };
+}
+
+function removeListenerFromArray(array, listener) {
+ let index = array.findIndex(l => listenerEquals(l, listener));
+ if (index < 0)
+ return;
+ array.splice(index, 1);
+}
+
+function getListenerFromArray(array, listener) {
+ return array.find(l => listenerEquals(l, listener));
+}
+
+function arrayContainsListener(array, listener) {
+ return !!getListenerFromArray(array, listener);
+}
+
+function makeFrameEventListener(frame, callback) {
+ return callback.bind(frame);
+}
+
+var FRAME_ID = 0;
+var tabMap = new Map();
+
+const Frame = Class({
+ implements: [ Disposable ],
+ extends: EventTarget,
+ setup: function(contentFrame) {
+ // This ID should be unique for this loader across all processes
+ let priv = ns(this);
+
+ priv.id = runtime.processID + ":" + FRAME_ID++;
+
+ priv.contentFrame = contentFrame;
+ priv.messageManager = contentFrame;
+ priv.domListeners = [];
+
+ tabMap.set(contentFrame.docShell, this);
+
+ priv.messageReceived = messageReceived.bind(this);
+ priv.messageManager.addMessageListener('sdk/remote/frame/message', priv.messageReceived);
+
+ this.port = new EventTarget();
+ definePort(this, 'sdk/remote/frame/message');
+
+ priv.messageManager.sendAsyncMessage('sdk/remote/frame/attach', {
+ loaderID,
+ frameID: priv.id,
+ processID: runtime.processID
+ });
+
+ frames.attachItem(this);
+ },
+
+ dispose: function() {
+ let priv = ns(this);
+
+ emit(this, 'detach', this);
+
+ for (let listener of priv.domListeners)
+ priv.contentFrame.removeEventListener(...listener.args);
+
+ priv.messageManager.removeMessageListener('sdk/remote/frame/message', priv.messageReceived);
+ tabMap.delete(priv.contentFrame.docShell);
+ priv.contentFrame = null;
+ },
+
+ get content() {
+ return ns(this).contentFrame.content;
+ },
+
+ get isTab() {
+ let docShell = ns(this).contentFrame.docShell;
+ if (process.isRemote) {
+ // We don't want to roundtrip to the main process to get this property.
+ // This hack relies on the host app having defined webBrowserChrome only
+ // in frames that are part of the tabs. Since only Firefox has remote
+ // processes right now and does this this works.
+ let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsITabChild);
+ return !!tabchild.webBrowserChrome;
+ }
+ else {
+ // This is running in the main process so we can break out to the browser
+ // And check we can find a tab for the browser element directly.
+ let browser = docShell.chromeEventHandler;
+ let tab = require('../tabs/utils').getTabForBrowser(browser);
+ return !!tab;
+ }
+ },
+
+ addEventListener: function(...args) {
+ let priv = ns(this);
+
+ let listener = listenerFor(...args);
+ if (arrayContainsListener(priv.domListeners, listener))
+ return;
+
+ listener.registeredCallback = makeFrameEventListener(this, listener.callback);
+
+ priv.domListeners.push(listener);
+ priv.contentFrame.addEventListener(...listener.args);
+ },
+
+ removeEventListener: function(...args) {
+ let priv = ns(this);
+
+ let listener = getListenerFromArray(priv.domListeners, listenerFor(...args));
+ if (!listener)
+ return;
+
+ removeListenerFromArray(priv.domListeners, listener);
+ priv.contentFrame.removeEventListener(...listener.args);
+ }
+});
+
+const FrameList = Class({
+ implements: [ EventParent, Disposable ],
+ extends: EventTarget,
+ setup: function() {
+ EventParent.prototype.initialize.call(this);
+
+ this.port = new EventTarget();
+ ns(this).domListeners = [];
+
+ this.on('attach', frame => {
+ for (let listener of ns(this).domListeners)
+ frame.addEventListener(...listener.args);
+ });
+ },
+
+ dispose: function() {
+ // The only case where we get destroyed is when the loader is unloaded in
+ // which case each frame will clean up its own event listeners.
+ ns(this).domListeners = null;
+ },
+
+ getFrameForWindow: function(window) {
+ let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+
+ return tabMap.get(docShell) || null;
+ },
+
+ addEventListener: function(...args) {
+ let listener = listenerFor(...args);
+ if (arrayContainsListener(ns(this).domListeners, listener))
+ return;
+
+ ns(this).domListeners.push(listener);
+ for (let frame of this)
+ frame.addEventListener(...listener.args);
+ },
+
+ removeEventListener: function(...args) {
+ let listener = listenerFor(...args);
+ if (!arrayContainsListener(ns(this).domListeners, listener))
+ return;
+
+ removeListenerFromArray(ns(this).domListeners, listener);
+ for (let frame of this)
+ frame.removeEventListener(...listener.args);
+ }
+});
+var frames = exports.frames = new FrameList();
+
+function registerContentFrame(contentFrame) {
+ let frame = new Frame(contentFrame);
+}
+exports.registerContentFrame = registerContentFrame;
+
+function unregisterContentFrame(contentFrame) {
+ let frame = tabMap.get(contentFrame.docShell);
+ if (!frame)
+ return;
+
+ frame.destroy();
+}
+exports.unregisterContentFrame = unregisterContentFrame;
diff --git a/components/jetpack/sdk/remote/core.js b/components/jetpack/sdk/remote/core.js
new file mode 100644
index 000000000..78bb673fd
--- /dev/null
+++ b/components/jetpack/sdk/remote/core.js
@@ -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/. */
+"use strict";
+
+const options = require("@loader/options");
+
+exports.isChildLoader = options.childLoader;
diff --git a/components/jetpack/sdk/remote/parent.js b/components/jetpack/sdk/remote/parent.js
new file mode 100644
index 000000000..f110fe3f6
--- /dev/null
+++ b/components/jetpack/sdk/remote/parent.js
@@ -0,0 +1,338 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { isChildLoader } = require('./core');
+if (isChildLoader)
+ throw new Error("Cannot load sdk/remote/parent in a child loader.");
+
+const { Cu, Ci, Cc } = require('chrome');
+const runtime = require('../system/runtime');
+
+const MAIN_PROCESS = Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+
+if (runtime.processType != MAIN_PROCESS) {
+ throw new Error('Cannot use sdk/remote/parent in a child process.');
+}
+
+const { Class } = require('../core/heritage');
+const { Namespace } = require('../core/namespace');
+const { Disposable } = require('../core/disposable');
+const { omit } = require('../util/object');
+const { when } = require('../system/unload');
+const { EventTarget } = require('../event/target');
+const { emit } = require('../event/core');
+const system = require('../system/events');
+const { EventParent } = require('./utils');
+const options = require('@loader/options');
+const loaderModule = require('toolkit/loader');
+const { getTabForBrowser } = require('../tabs/utils');
+
+const appInfo = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULRuntime);
+
+exports.useRemoteProcesses = appInfo.browserTabsRemoteAutostart;
+
+// Chose the right function for resolving relative a module id
+var moduleResolve;
+if (options.isNative) {
+ moduleResolve = (id, requirer) => loaderModule.nodeResolve(id, requirer, { rootURI: options.rootURI });
+}
+else {
+ moduleResolve = loaderModule.resolve;
+}
+// Build the sorted path mapping structure that resolveURI requires
+var pathMapping = Object.keys(options.paths)
+ .sort((a, b) => b.length - a.length)
+ .map(p => [p, options.paths[p]]);
+
+// Load the scripts in the child processes
+var { getNewLoaderID } = require('../../framescript/FrameScriptManager.jsm');
+var PATH = options.paths[''];
+
+const childOptions = omit(options, ['modules', 'globals', 'resolve', 'load']);
+childOptions.modules = {};
+// @l10n/data is just JSON data and can be safely sent across to the child loader
+try {
+ childOptions.modules["@l10n/data"] = require("@l10n/data");
+}
+catch (e) {
+ // There may be no l10n data
+}
+const loaderID = getNewLoaderID();
+childOptions.loaderID = loaderID;
+childOptions.childLoader = true;
+
+const ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1'].
+ getService(Ci.nsIMessageBroadcaster);
+const gmm = Cc['@mozilla.org/globalmessagemanager;1'].
+ getService(Ci.nsIMessageBroadcaster);
+
+const ns = Namespace();
+
+var processMap = new Map();
+
+function definePort(obj, name) {
+ obj.port.emitCPOW = (event, args, cpows = {}) => {
+ let manager = ns(obj).messageManager;
+ if (!manager)
+ return;
+
+ let method = manager instanceof Ci.nsIMessageBroadcaster ?
+ "broadcastAsyncMessage" : "sendAsyncMessage";
+
+ manager[method](name, { loaderID, event, args }, cpows);
+ };
+
+ obj.port.emit = (event, ...args) => obj.port.emitCPOW(event, args);
+}
+
+function messageReceived({ target, data }) {
+ // Ignore messages from other loaders
+ if (data.loaderID != loaderID)
+ return;
+
+ emit(this.port, data.event, this, ...data.args);
+}
+
+// Process represents a gecko process that can load webpages. Each process
+// contains a number of Frames. This class is used to send and receive messages
+// from a single process.
+const Process = Class({
+ implements: [ Disposable ],
+ extends: EventTarget,
+ setup: function(id, messageManager, isRemote) {
+ ns(this).id = id;
+ ns(this).isRemote = isRemote;
+ ns(this).messageManager = messageManager;
+ ns(this).messageReceived = messageReceived.bind(this);
+ this.destroy = this.destroy.bind(this);
+ ns(this).messageManager.addMessageListener('sdk/remote/process/message', ns(this).messageReceived);
+ ns(this).messageManager.addMessageListener('child-process-shutdown', this.destroy);
+
+ this.port = new EventTarget();
+ definePort(this, 'sdk/remote/process/message');
+
+ // Load any remote modules
+ for (let module of remoteModules.values())
+ this.port.emit('sdk/remote/require', module);
+
+ processMap.set(ns(this).id, this);
+ processes.attachItem(this);
+ },
+
+ dispose: function() {
+ emit(this, 'detach', this);
+ processMap.delete(ns(this).id);
+ ns(this).messageManager.removeMessageListener('sdk/remote/process/message', ns(this).messageReceived);
+ ns(this).messageManager.removeMessageListener('child-process-shutdown', this.destroy);
+ ns(this).messageManager = null;
+ },
+
+ // Returns true if this process is a child process
+ get isRemote() {
+ return ns(this).isRemote;
+ }
+});
+
+// Processes gives an API for enumerating an sending and receiving messages from
+// all processes as well as detecting when a new process starts.
+const Processes = Class({
+ implements: [ EventParent ],
+ extends: EventTarget,
+ initialize: function() {
+ EventParent.prototype.initialize.call(this);
+ ns(this).messageManager = ppmm;
+
+ this.port = new EventTarget();
+ definePort(this, 'sdk/remote/process/message');
+ },
+
+ getById: function(id) {
+ return processMap.get(id);
+ }
+});
+var processes = exports.processes = new Processes();
+
+var frameMap = new Map();
+
+function setFrameProcess(frame, process) {
+ ns(frame).process = process;
+ frames.attachItem(frame);
+}
+
+// Frames display webpages in a process. In the main process every Frame is
+// linked with a <browser> or <iframe> element.
+const Frame = Class({
+ implements: [ Disposable ],
+ extends: EventTarget,
+ setup: function(id, node) {
+ ns(this).id = id;
+ ns(this).node = node;
+
+ let frameLoader = node.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+ ns(this).messageManager = frameLoader.messageManager;
+
+ ns(this).messageReceived = messageReceived.bind(this);
+ ns(this).messageManager.addMessageListener('sdk/remote/frame/message', ns(this).messageReceived);
+
+ this.port = new EventTarget();
+ definePort(this, 'sdk/remote/frame/message');
+
+ frameMap.set(ns(this).messageManager, this);
+ },
+
+ dispose: function() {
+ emit(this, 'detach', this);
+ ns(this).messageManager.removeMessageListener('sdk/remote/frame/message', ns(this).messageReceived);
+
+ frameMap.delete(ns(this).messageManager);
+ ns(this).messageManager = null;
+ },
+
+ // Returns the browser or iframe element this frame displays in
+ get frameElement() {
+ return ns(this).node;
+ },
+
+ // Returns the process that this frame loads in
+ get process() {
+ return ns(this).process;
+ },
+
+ // Returns true if this frame is a tab in a main browser window
+ get isTab() {
+ let tab = getTabForBrowser(ns(this).node);
+ return !!tab;
+ }
+});
+
+function managerDisconnected({ subject: manager }) {
+ let frame = frameMap.get(manager);
+ if (frame)
+ frame.destroy();
+}
+system.on('message-manager-disconnect', managerDisconnected);
+
+// Provides an API for enumerating and sending and receiving messages from all
+// Frames
+const FrameList = Class({
+ implements: [ EventParent ],
+ extends: EventTarget,
+ initialize: function() {
+ EventParent.prototype.initialize.call(this);
+ ns(this).messageManager = gmm;
+
+ this.port = new EventTarget();
+ definePort(this, 'sdk/remote/frame/message');
+ },
+
+ // Returns the frame for a browser element
+ getFrameForBrowser: function(browser) {
+ for (let frame of this) {
+ if (frame.frameElement == browser)
+ return frame;
+ }
+ return null;
+ },
+});
+var frames = exports.frames = new FrameList();
+
+// Create the module loader in any existing processes
+ppmm.broadcastAsyncMessage('sdk/remote/process/load', {
+ modulePath: PATH,
+ loaderID,
+ options: childOptions,
+ reason: "broadcast"
+});
+
+// A loader has started in a remote process
+function processLoaderStarted({ target, data }) {
+ if (data.loaderID != loaderID)
+ return;
+
+ if (processMap.has(data.processID)) {
+ console.error("Saw the same process load the same loader twice. This is a bug in the SDK.");
+ return;
+ }
+
+ let process = new Process(data.processID, target, data.isRemote);
+
+ if (pendingFrames.has(data.processID)) {
+ for (let frame of pendingFrames.get(data.processID))
+ setFrameProcess(frame, process);
+ pendingFrames.delete(data.processID);
+ }
+}
+
+// A new process has started
+function processStarted({ target, data: { modulePath } }) {
+ if (modulePath != PATH)
+ return;
+
+ // Have it load a loader if it hasn't already
+ target.sendAsyncMessage('sdk/remote/process/load', {
+ modulePath,
+ loaderID,
+ options: childOptions,
+ reason: "response"
+ });
+}
+
+var pendingFrames = new Map();
+
+// A new frame has been created in the remote process
+function frameAttached({ target, data }) {
+ if (data.loaderID != loaderID)
+ return;
+
+ let frame = new Frame(data.frameID, target);
+
+ let process = processMap.get(data.processID);
+ if (process) {
+ setFrameProcess(frame, process);
+ return;
+ }
+
+ // In some cases frame messages can arrive earlier than process messages
+ // causing us to see a new frame appear before its process. In this case
+ // cache the frame data until we see the process. See bug 1131375.
+ if (!pendingFrames.has(data.processID))
+ pendingFrames.set(data.processID, [frame]);
+ else
+ pendingFrames.get(data.processID).push(frame);
+}
+
+// Wait for new processes and frames
+ppmm.addMessageListener('sdk/remote/process/attach', processLoaderStarted);
+ppmm.addMessageListener('sdk/remote/process/start', processStarted);
+gmm.addMessageListener('sdk/remote/frame/attach', frameAttached);
+
+when(reason => {
+ ppmm.removeMessageListener('sdk/remote/process/attach', processLoaderStarted);
+ ppmm.removeMessageListener('sdk/remote/process/start', processStarted);
+ gmm.removeMessageListener('sdk/remote/frame/attach', frameAttached);
+
+ ppmm.broadcastAsyncMessage('sdk/remote/process/unload', { loaderID, reason });
+});
+
+var remoteModules = new Set();
+
+// Ensures a module is loaded in every child process. It is safe to send
+// messages to this module immediately after calling this.
+// Pass a module to resolve the id relatively.
+function remoteRequire(id, module = null) {
+ // Resolve relative to calling module if passed
+ if (module)
+ id = moduleResolve(id, module.id);
+ let uri = loaderModule.resolveURI(id, pathMapping);
+
+ // Don't reload the same module
+ if (remoteModules.has(uri))
+ return;
+
+ remoteModules.add(uri);
+ processes.port.emit('sdk/remote/require', uri);
+}
+exports.remoteRequire = remoteRequire;
diff --git a/components/jetpack/sdk/remote/utils.js b/components/jetpack/sdk/remote/utils.js
new file mode 100644
index 000000000..5a5e39198
--- /dev/null
+++ b/components/jetpack/sdk/remote/utils.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { Class } = require('../core/heritage');
+const { List, addListItem, removeListItem } = require('../util/list');
+const { emit } = require('../event/core');
+const { pipe } = require('../event/utils');
+
+// A helper class that maintains a list of EventTargets. Any events emitted
+// to an EventTarget are also emitted by the EventParent. Likewise for an
+// EventTarget's port property.
+const EventParent = Class({
+ implements: [ List ],
+
+ attachItem: function(item) {
+ addListItem(this, item);
+
+ pipe(item.port, this.port);
+ pipe(item, this);
+
+ item.once('detach', () => {
+ removeListItem(this, item);
+ })
+
+ emit(this, 'attach', item);
+ },
+
+ // Calls listener for every object already in the list and every object
+ // subsequently added to the list.
+ forEvery: function(listener) {
+ for (let item of this)
+ listener(item);
+
+ this.on('attach', listener);
+ }
+});
+exports.EventParent = EventParent;
diff --git a/components/jetpack/sdk/request.js b/components/jetpack/sdk/request.js
new file mode 100644
index 000000000..96bb1e6d7
--- /dev/null
+++ b/components/jetpack/sdk/request.js
@@ -0,0 +1,248 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { ns } = require("./core/namespace");
+const { emit } = require("./event/core");
+const { merge } = require("./util/object");
+const { stringify } = require("./querystring");
+const { EventTarget } = require("./event/target");
+const { Class } = require("./core/heritage");
+const { XMLHttpRequest, forceAllowThirdPartyCookie } = require("./net/xhr");
+const apiUtils = require("./deprecated/api-utils");
+const { isValidURI } = require("./url.js");
+
+const response = ns();
+const request = ns();
+
+// Instead of creating a new validator for each request, just make one and
+// reuse it.
+const { validateOptions, validateSingleOption } = new OptionsValidator({
+ url: {
+ // Also converts a URL instance to string, bug 857902
+ map: url => url.toString(),
+ ok: isValidURI
+ },
+ headers: {
+ map: v => v || {},
+ is: ["object"],
+ },
+ content: {
+ map: v => v || null,
+ is: ["string", "object", "null"],
+ },
+ contentType: {
+ map: v => v || "application/x-www-form-urlencoded",
+ is: ["string"],
+ },
+ overrideMimeType: {
+ map: v => v || null,
+ is: ["string", "null"],
+ },
+ anonymous: {
+ map: v => v || false,
+ is: ["boolean", "null"],
+ }
+});
+
+const REUSE_ERROR = "This request object has been used already. You must " +
+ "create a new one to make a new request."
+
+// Utility function to prep the request since it's the same between
+// request types
+function runRequest(mode, target) {
+ let source = request(target)
+ let { xhr, url, content, contentType, headers, overrideMimeType, anonymous } = source;
+
+ let isGetOrHead = (mode == "GET" || mode == "HEAD");
+
+ // If this request has already been used, then we can't reuse it.
+ // Throw an error.
+ if (xhr)
+ throw new Error(REUSE_ERROR);
+
+ xhr = source.xhr = new XMLHttpRequest({
+ mozAnon: anonymous
+ });
+
+ // Build the data to be set. For GET or HEAD requests, we want to append that
+ // to the URL before opening the request.
+ let data = stringify(content);
+ // If the URL already has ? in it, then we want to just use &
+ if (isGetOrHead && data)
+ url = url + (/\?/.test(url) ? "&" : "?") + data;
+
+ // open the request
+ xhr.open(mode, url);
+
+
+ forceAllowThirdPartyCookie(xhr);
+
+ // request header must be set after open, but before send
+ xhr.setRequestHeader("Content-Type", contentType);
+
+ // set other headers
+ Object.keys(headers).forEach(function(name) {
+ xhr.setRequestHeader(name, headers[name]);
+ });
+
+ // set overrideMimeType
+ if (overrideMimeType)
+ xhr.overrideMimeType(overrideMimeType);
+
+ // handle the readystate, create the response, and call the callback
+ xhr.onreadystatechange = function onreadystatechange() {
+ if (xhr.readyState === 4) {
+ let response = Response(xhr);
+ source.response = response;
+ emit(target, 'complete', response);
+ }
+ };
+
+ // actually send the request.
+ // We don't want to send data on GET or HEAD requests.
+ xhr.send(!isGetOrHead ? data : null);
+}
+
+const Request = Class({
+ extends: EventTarget,
+ initialize: function initialize(options) {
+ // `EventTarget.initialize` will set event listeners that are named
+ // like `onEvent` in this case `onComplete` listener will be set to
+ // `complete` event.
+ EventTarget.prototype.initialize.call(this, options);
+
+ // Copy normalized options.
+ merge(request(this), validateOptions(options));
+ },
+ get url() { return request(this).url; },
+ set url(value) { request(this).url = validateSingleOption('url', value); },
+ get headers() { return request(this).headers; },
+ set headers(value) {
+ return request(this).headers = validateSingleOption('headers', value);
+ },
+ get content() { return request(this).content; },
+ set content(value) {
+ request(this).content = validateSingleOption('content', value);
+ },
+ get contentType() { return request(this).contentType; },
+ set contentType(value) {
+ request(this).contentType = validateSingleOption('contentType', value);
+ },
+ get anonymous() { return request(this).anonymous; },
+ get response() { return request(this).response; },
+ delete: function() {
+ runRequest('DELETE', this);
+ return this;
+ },
+ get: function() {
+ runRequest('GET', this);
+ return this;
+ },
+ post: function() {
+ runRequest('POST', this);
+ return this;
+ },
+ put: function() {
+ runRequest('PUT', this);
+ return this;
+ },
+ head: function() {
+ runRequest('HEAD', this);
+ return this;
+ }
+});
+exports.Request = Request;
+
+const Response = Class({
+ initialize: function initialize(request) {
+ response(this).request = request;
+ },
+ // more about responseURL: https://bugzilla.mozilla.org/show_bug.cgi?id=998076
+ get url() {
+ return response(this).request.responseURL;
+ },
+ get text() {
+ return response(this).request.responseText;
+ },
+ get xml() {
+ throw new Error("Sorry, the 'xml' property is no longer available. " +
+ "see bug 611042 for more information.");
+ },
+ get status() {
+ return response(this).request.status;
+ },
+ get statusText() {
+ return response(this).request.statusText;
+ },
+ get json() {
+ try {
+ return JSON.parse(this.text);
+ } catch(error) {
+ return null;
+ }
+ },
+ get headers() {
+ let headers = {}, lastKey;
+ // Since getAllResponseHeaders() will return null if there are no headers,
+ // defend against it by defaulting to ""
+ let rawHeaders = response(this).request.getAllResponseHeaders() || "";
+ rawHeaders.split("\n").forEach(function (h) {
+ // According to the HTTP spec, the header string is terminated by an empty
+ // line, so we can just skip it.
+ if (!h.length) {
+ return;
+ }
+
+ let index = h.indexOf(":");
+ // The spec allows for leading spaces, so instead of assuming a single
+ // leading space, just trim the values.
+ let key = h.substring(0, index).trim(),
+ val = h.substring(index + 1).trim();
+
+ // For empty keys, that means that the header value spanned multiple lines.
+ // In that case we should append the value to the value of lastKey with a
+ // new line. We'll assume lastKey will be set because there should never
+ // be an empty key on the first pass.
+ if (key) {
+ headers[key] = val;
+ lastKey = key;
+ }
+ else {
+ headers[lastKey] += "\n" + val;
+ }
+ });
+ return headers;
+ },
+ get anonymous() {
+ return response(this).request.mozAnon;
+ }
+});
+
+// apiUtils.validateOptions doesn't give the ability to easily validate single
+// options, so this is a wrapper that provides that ability.
+function OptionsValidator(rules) {
+ return {
+ validateOptions: function (options) {
+ return apiUtils.validateOptions(options, rules);
+ },
+ validateSingleOption: function (field, value) {
+ // We need to create a single rule object from our listed rules. To avoid
+ // JavaScript String warnings, check for the field & default to an empty object.
+ let singleRule = {};
+ if (field in rules) {
+ singleRule[field] = rules[field];
+ }
+ let singleOption = {};
+ singleOption[field] = value;
+ // This should throw if it's invalid, which will bubble up & out.
+ return apiUtils.validateOptions(singleOption, singleRule)[field];
+ }
+ };
+}
diff --git a/components/jetpack/sdk/selection.js b/components/jetpack/sdk/selection.js
new file mode 100644
index 000000000..e393aae06
--- /dev/null
+++ b/components/jetpack/sdk/selection.js
@@ -0,0 +1,471 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "stable",
+ "engines": {
+ "Palemoon": "*",
+ "Firefox": "*",
+ "SeaMonkey": "*"
+ }
+};
+
+const { Ci, Cc } = require("chrome"),
+ { setTimeout } = require("./timers"),
+ { emit, off } = require("./event/core"),
+ { Class, obscure } = require("./core/heritage"),
+ { EventTarget } = require("./event/target"),
+ { ns } = require("./core/namespace"),
+ { when: unload } = require("./system/unload"),
+ { ignoreWindow } = require('./private-browsing/utils'),
+ { getTabs, getTabForContentWindow,
+ getAllTabContentWindows } = require('./tabs/utils'),
+ winUtils = require("./window/utils"),
+ events = require("./system/events");
+
+// The selection types
+const HTML = 0x01,
+ TEXT = 0x02,
+ DOM = 0x03; // internal use only
+
+// A more developer-friendly message than the caught exception when is not
+// possible change a selection.
+const ERR_CANNOT_CHANGE_SELECTION =
+ "It isn't possible to change the selection, as there isn't currently a selection";
+
+const selections = ns();
+
+const Selection = Class({
+ /**
+ * Creates an object from which a selection can be set, get, etc. Each
+ * object has an associated with a range number. Range numbers are the
+ * 0-indexed counter of selection ranges as explained at
+ * https://developer.mozilla.org/en/DOM/Selection.
+ *
+ * @param rangeNumber
+ * The zero-based range index into the selection
+ */
+ initialize: function initialize(rangeNumber) {
+ // In order to hide the private `rangeNumber` argument from API consumers
+ // while still enabling Selection getters/setters to access it, we define
+ // it as non enumerable, non configurable property. While consumers still
+ // may discover it they won't be able to do any harm which is good enough
+ // in this case.
+ Object.defineProperties(this, {
+ rangeNumber: {
+ enumerable: false,
+ configurable: false,
+ value: rangeNumber
+ }
+ });
+ },
+ get text() { return getSelection(TEXT, this.rangeNumber); },
+ set text(value) { setSelection(TEXT, value, this.rangeNumber); },
+ get html() { return getSelection(HTML, this.rangeNumber); },
+ set html(value) { setSelection(HTML, value, this.rangeNumber); },
+ get isContiguous() {
+
+ // If there are multiple non empty ranges, the selection is definitely
+ // discontiguous. It returns `false` also if there are no valid selection.
+ let count = 0;
+ for (let sel in selectionIterator)
+ if (++count > 1)
+ break;
+
+ return count === 1;
+ }
+});
+
+const selectionListener = {
+ notifySelectionChanged: function (document, selection, reason) {
+ if (!["SELECTALL", "KEYPRESS", "MOUSEUP"].some(type => reason &
+ Ci.nsISelectionListener[type + "_REASON"]) || selection.toString() == "")
+ return;
+
+ this.onSelect();
+ },
+
+ onSelect: function() {
+ emit(module.exports, "select");
+ }
+}
+
+/**
+ * Defines iterators so that discontiguous selections can be iterated.
+ * Empty selections are skipped - see `safeGetRange` for further details.
+ *
+ * If discontiguous selections are in a text field, only the first one
+ * is returned because the text field selection APIs doesn't support
+ * multiple selections.
+ */
+function* forOfIterator() {
+ let selection = getSelection(DOM);
+ let count = 0;
+
+ if (selection)
+ count = selection.rangeCount || (getElementWithSelection() ? 1 : 0);
+
+ for (let i = 0; i < count; i++) {
+ let sel = Selection(i);
+
+ if (sel.text)
+ yield Selection(i);
+ }
+}
+
+const selectionIteratorOptions = {
+ __iterator__: function() {
+ for (let item of this)
+ yield item;
+ }
+}
+selectionIteratorOptions[Symbol.iterator] = forOfIterator;
+const selectionIterator = obscure(selectionIteratorOptions);
+
+/**
+ * Returns the most recent focused window.
+ * if private browsing window is most recent and not supported,
+ * then ignore it and return `null`, because the focused window
+ * can't be targeted.
+ */
+function getFocusedWindow() {
+ let window = winUtils.getFocusedWindow();
+
+ return ignoreWindow(window) ? null : window;
+}
+
+/**
+ * Returns the focused element in the most recent focused window
+ * if private browsing window is most recent and not supported,
+ * then ignore it and return `null`, because the focused element
+ * can't be targeted.
+ */
+function getFocusedElement() {
+ let element = winUtils.getFocusedElement();
+
+ if (!element || ignoreWindow(element.ownerDocument.defaultView))
+ return null;
+
+ return element;
+}
+
+/**
+ * Returns the current selection from most recent content window. Depending on
+ * the specified |type|, the value returned can be a string of text, stringified
+ * HTML, or a DOM selection object as described at
+ * https://developer.mozilla.org/en/DOM/Selection.
+ *
+ * @param type
+ * Specifies the return type of the selection. Valid values are the one
+ * of the constants HTML, TEXT, or DOM.
+ *
+ * @param rangeNumber
+ * Specifies the zero-based range index of the returned selection.
+ */
+function getSelection(type, rangeNumber) {
+ let window, selection;
+ try {
+ window = getFocusedWindow();
+ selection = window.getSelection();
+ }
+ catch (e) {
+ return null;
+ }
+
+ // Get the selected content as the specified type
+ if (type == DOM) {
+ return selection;
+ }
+ else if (type == TEXT) {
+ let range = safeGetRange(selection, rangeNumber);
+
+ if (range)
+ return range.toString();
+
+ let node = getElementWithSelection();
+
+ if (!node)
+ return null;
+
+ return node.value.substring(node.selectionStart, node.selectionEnd);
+ }
+ else if (type == HTML) {
+ let range = safeGetRange(selection, rangeNumber);
+ // Another way, but this includes the xmlns attribute for all elements in
+ // Gecko 1.9.2+ :
+ // return Cc["@mozilla.org/xmlextras/xmlserializer;1"].
+ // createInstance(Ci.nsIDOMSerializer).serializeToSTring(range.
+ // cloneContents());
+ if (!range)
+ return null;
+
+ let node = window.document.createElement("span");
+ node.appendChild(range.cloneContents());
+ return node.innerHTML;
+ }
+
+ throw new Error("Type " + type + " is unrecognized.");
+}
+
+/**
+ * Sets the current selection of the most recent content document by changing
+ * the existing selected text/HTML range to the specified value.
+ *
+ * @param val
+ * The value for the new selection
+ *
+ * @param rangeNumber
+ * The zero-based range index of the selection to be set
+ *
+ */
+function setSelection(type, val, rangeNumber) {
+ // Make sure we have a window context & that there is a current selection.
+ // Selection cannot be set unless there is an existing selection.
+ let window, selection;
+
+ try {
+ window = getFocusedWindow();
+ selection = window.getSelection();
+ }
+ catch (e) {
+ throw new Error(ERR_CANNOT_CHANGE_SELECTION);
+ }
+
+ let range = safeGetRange(selection, rangeNumber);
+
+ if (range) {
+ let fragment;
+
+ if (type === HTML)
+ fragment = range.createContextualFragment(val);
+ else {
+ fragment = range.createContextualFragment("");
+ fragment.textContent = val;
+ }
+
+ range.deleteContents();
+ range.insertNode(fragment);
+ }
+ else {
+ let node = getElementWithSelection();
+
+ if (!node)
+ throw new Error(ERR_CANNOT_CHANGE_SELECTION);
+
+ let { value, selectionStart, selectionEnd } = node;
+
+ let newSelectionEnd = selectionStart + val.length;
+
+ node.value = value.substring(0, selectionStart) +
+ val +
+ value.substring(selectionEnd, value.length);
+
+ node.setSelectionRange(selectionStart, newSelectionEnd);
+ }
+}
+
+/**
+ * Returns the specified range in a selection without throwing an exception.
+ *
+ * @param selection
+ * A selection object as described at
+ * https://developer.mozilla.org/en/DOM/Selection
+ *
+ * @param [rangeNumber]
+ * Specifies the zero-based range index of the returned selection.
+ * If it's not provided the function will return the first non empty
+ * range, if any.
+ */
+function safeGetRange(selection, rangeNumber) {
+ try {
+ let { rangeCount } = selection;
+ let range = null;
+
+ if (typeof rangeNumber === "undefined")
+ rangeNumber = 0;
+ else
+ rangeCount = rangeNumber + 1;
+
+ for (; rangeNumber < rangeCount; rangeNumber++ ) {
+ range = selection.getRangeAt(rangeNumber);
+
+ if (range && range.toString())
+ break;
+
+ range = null;
+ }
+
+ return range;
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+/**
+ * Returns a reference of the DOM's active element for the window given, if it
+ * supports the text field selection API and has a text selected.
+ *
+ * Note:
+ * we need this method because window.getSelection doesn't return a selection
+ * for text selected in a form field (see bug 85686)
+ */
+function getElementWithSelection() {
+ let element = getFocusedElement();
+
+ if (!element)
+ return null;
+
+ try {
+ // Accessing selectionStart and selectionEnd on e.g. a button
+ // results in an exception thrown as per the HTML5 spec. See
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection
+
+ let { value, selectionStart, selectionEnd } = element;
+
+ let hasSelection = typeof value === "string" &&
+ !isNaN(selectionStart) &&
+ !isNaN(selectionEnd) &&
+ selectionStart !== selectionEnd;
+
+ return hasSelection ? element : null;
+ }
+ catch (err) {
+ return null;
+ }
+
+}
+
+/**
+ * Adds the Selection Listener to the content's window given
+ */
+function addSelectionListener(window) {
+ let selection = window.getSelection();
+
+ // Don't add the selection's listener more than once to the same window,
+ // if the selection object is the same
+ if ("selection" in selections(window) && selections(window).selection === selection)
+ return;
+
+ // We ensure that the current selection is an instance of
+ // `nsISelectionPrivate` before working on it, in case is `null`.
+ //
+ // If it's `null` it's likely too early to add the listener, and we demand
+ // that operation to `document-shown` - it can easily happens for frames
+ if (selection instanceof Ci.nsISelectionPrivate)
+ selection.addSelectionListener(selectionListener);
+
+ // nsISelectionListener implementation seems not fire a notification if
+ // a selection is in a text field, therefore we need to add a listener to
+ // window.onselect, that is fired only for text fields.
+ // For consistency, we add it only when the nsISelectionListener is added.
+ //
+ // https://developer.mozilla.org/en/DOM/window.onselect
+ window.addEventListener("select", selectionListener.onSelect, true);
+
+ selections(window).selection = selection;
+};
+
+/**
+ * Removes the Selection Listener to the content's window given
+ */
+function removeSelectionListener(window) {
+ // Don't remove the selection's listener to a window that wasn't handled.
+ if (!("selection" in selections(window)))
+ return;
+
+ let selection = window.getSelection();
+ let isSameSelection = selection === selections(window).selection;
+
+ // Before remove the listener, we ensure that the current selection is an
+ // instance of `nsISelectionPrivate` (it could be `null`), and that is still
+ // the selection we managed for this window (it could be detached).
+ if (selection instanceof Ci.nsISelectionPrivate && isSameSelection)
+ selection.removeSelectionListener(selectionListener);
+
+ window.removeEventListener("select", selectionListener.onSelect, true);
+
+ delete selections(window).selection;
+};
+
+function onContent(event) {
+ let window = event.subject.defaultView;
+
+ // We are not interested in documents without valid defaultView (e.g. XML)
+ // that aren't in a tab (e.g. Panel); or in private windows
+ if (window && getTabForContentWindow(window) && !ignoreWindow(window)) {
+ addSelectionListener(window);
+ }
+}
+
+// Adds Selection listener to new documents
+// Note that strong reference is needed for documents that are loading slowly or
+// where the server didn't close the connection (e.g. "comet").
+events.on("document-element-inserted", onContent, true);
+
+// Adds Selection listeners to existing documents
+getAllTabContentWindows().forEach(addSelectionListener);
+
+// When a document is not visible anymore the selection object is detached, and
+// a new selection object is created when it becomes visible again.
+// That makes the previous selection's listeners added previously totally
+// useless – the listeners are not notified anymore.
+// To fix that we're listening for `document-shown` event in order to add
+// the listeners to the new selection object created.
+//
+// See bug 665386 for further details.
+
+function onShown(event) {
+ let window = event.subject.defaultView;
+
+ // We are not interested in documents without valid defaultView.
+ // For example XML documents don't have windows and we don't yet support them.
+ if (!window)
+ return;
+
+ // We want to handle only the windows where we added selection's listeners
+ if ("selection" in selections(window)) {
+ let currentSelection = window.getSelection();
+ let { selection } = selections(window);
+
+ // If the current selection for the window given is different from the one
+ // stored in the namespace, we need to add the listeners again, and replace
+ // the previous selection in our list with the new one.
+ //
+ // Notice that we don't have to remove the listeners from the old selection,
+ // because is detached. An attempt to remove the listener, will raise an
+ // error (see http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsSelection.cpp#5343 )
+ //
+ // We ensure that the current selection is an instance of
+ // `nsISelectionPrivate` before working on it, in case is `null`.
+ if (currentSelection instanceof Ci.nsISelectionPrivate &&
+ currentSelection !== selection) {
+
+ window.addEventListener("select", selectionListener.onSelect, true);
+ currentSelection.addSelectionListener(selectionListener);
+ selections(window).selection = currentSelection;
+ }
+ }
+}
+
+events.on("document-shown", onShown, true);
+
+// Removes Selection listeners when the add-on is unloaded
+unload(function(){
+ getAllTabContentWindows().forEach(removeSelectionListener);
+
+ events.off("document-element-inserted", onContent);
+ events.off("document-shown", onShown);
+
+ off(exports);
+});
+
+const selection = Class({
+ extends: EventTarget,
+ implements: [ Selection, selectionIterator ]
+})();
+
+module.exports = selection;
diff --git a/components/jetpack/sdk/self.js b/components/jetpack/sdk/self.js
new file mode 100644
index 000000000..c2114a926
--- /dev/null
+++ b/components/jetpack/sdk/self.js
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { CC } = require('chrome');
+const options = require('@loader/options');
+
+const { get } = require("./preferences/service");
+const { readURISync } = require('./net/url');
+
+const id = options.id;
+
+const readPref = key => get("extensions." + id + ".sdk." + key);
+
+const name = readPref("name") || options.name;
+const version = readPref("version") || options.version;
+const loadReason = readPref("load.reason") || options.loadReason;
+const rootURI = readPref("rootURI") || options.rootURI || "";
+const baseURI = readPref("baseURI") || options.prefixURI + name + "/"
+const addonDataURI = baseURI + "data/";
+const metadata = options.metadata || {};
+const permissions = metadata.permissions || {};
+const isPacked = rootURI && rootURI.indexOf("jar:") === 0;
+
+const isPrivateBrowsingSupported = 'private-browsing' in permissions &&
+ permissions['private-browsing'] === true;
+
+const uri = (path="") =>
+ path.includes(":") ? path : addonDataURI + path.replace(/^\.\//, "");
+
+var preferencesBranch = ("preferences-branch" in metadata)
+ ? metadata["preferences-branch"]
+ : options.preferencesBranch
+
+if (/[^\w{@}.-]/.test(preferencesBranch)) {
+ preferencesBranch = id;
+ console.warn("Ignoring preferences-branch (not a valid branch name)");
+}
+
+// Some XPCOM APIs require valid URIs as an argument for certain operations
+// (see `nsILoginManager` for example). This property represents add-on
+// associated unique URI string that can be used for that.
+exports.uri = 'addon:' + id;
+exports.id = id;
+exports.preferencesBranch = preferencesBranch || id;
+exports.name = name;
+exports.loadReason = loadReason;
+exports.version = version;
+exports.packed = isPacked;
+exports.data = Object.freeze({
+ url: uri,
+ load: function read(path) {
+ return readURISync(uri(path));
+ }
+});
+exports.isPrivateBrowsingSupported = isPrivateBrowsingSupported;
diff --git a/components/jetpack/sdk/simple-prefs.js b/components/jetpack/sdk/simple-prefs.js
new file mode 100644
index 000000000..3472f4418
--- /dev/null
+++ b/components/jetpack/sdk/simple-prefs.js
@@ -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/. */
+'use strict';
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { emit, off } = require("./event/core");
+const { PrefsTarget } = require("./preferences/event-target");
+const { preferencesBranch, id } = require("./self");
+const { on } = require("./system/events");
+
+const ADDON_BRANCH = "extensions." + preferencesBranch + ".";
+const BUTTON_PRESSED = id + "-cmdPressed";
+
+const target = PrefsTarget({ branchName: ADDON_BRANCH });
+
+// Listen to clicks on buttons
+function buttonClick({ data }) {
+ emit(target, data);
+}
+on(BUTTON_PRESSED, buttonClick);
+
+module.exports = target;
diff --git a/components/jetpack/sdk/simple-storage.js b/components/jetpack/sdk/simple-storage.js
new file mode 100644
index 000000000..bcf9b1cb9
--- /dev/null
+++ b/components/jetpack/sdk/simple-storage.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { Cc, Ci } = require("chrome");
+const file = require("./io/file");
+const prefs = require("./preferences/service");
+const jpSelf = require("./self");
+const timer = require("./timers");
+const unload = require("./system/unload");
+const { emit, on, off } = require("./event/core");
+
+const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
+const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes
+
+const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
+const QUOTA_DEFAULT = 5242880; // 5 MiB
+
+const JETPACK_DIR_BASENAME = "jetpack";
+
+Object.defineProperties(exports, {
+ storage: {
+ enumerable: true,
+ get: function() { return manager.root; },
+ set: function(value) { manager.root = value; }
+ },
+ quotaUsage: {
+ get: function() { return manager.quotaUsage; }
+ }
+});
+
+// A generic JSON store backed by a file on disk. This should be isolated
+// enough to move to its own module if need be...
+function JsonStore(options) {
+ this.filename = options.filename;
+ this.quota = options.quota;
+ this.writePeriod = options.writePeriod;
+ this.onOverQuota = options.onOverQuota;
+ this.onWrite = options.onWrite;
+
+ unload.ensure(this);
+
+ this.writeTimer = timer.setInterval(this.write.bind(this),
+ this.writePeriod);
+}
+
+JsonStore.prototype = {
+ // The store's root.
+ get root() {
+ return this.isRootInited ? this._root : {};
+ },
+
+ // Performs some type checking.
+ set root(val) {
+ let types = ["array", "boolean", "null", "number", "object", "string"];
+ if (types.indexOf(typeof(val)) < 0) {
+ throw new Error("storage must be one of the following types: " +
+ types.join(", "));
+ }
+ this._root = val;
+ return val;
+ },
+
+ // True if the root has ever been set (either via the root setter or by the
+ // backing file's having been read).
+ get isRootInited() {
+ return this._root !== undefined;
+ },
+
+ // Percentage of quota used, as a number [0, Inf). > 1 implies over quota.
+ // Undefined if there is no quota.
+ get quotaUsage() {
+ return this.quota > 0 ?
+ JSON.stringify(this.root).length / this.quota :
+ undefined;
+ },
+
+ // Removes the backing file and all empty subdirectories.
+ purge: function JsonStore_purge() {
+ try {
+ // This'll throw if the file doesn't exist.
+ file.remove(this.filename);
+ let parentPath = this.filename;
+ do {
+ parentPath = file.dirname(parentPath);
+ // This'll throw if the dir isn't empty.
+ file.rmdir(parentPath);
+ } while (file.basename(parentPath) !== JETPACK_DIR_BASENAME);
+ }
+ catch (err) {}
+ },
+
+ // Initializes the root by reading the backing file.
+ read: function JsonStore_read() {
+ try {
+ let str = file.read(this.filename);
+
+ // Ideally we'd log the parse error with console.error(), but logged
+ // errors cause tests to fail. Supporting "known" errors in the test
+ // harness appears to be non-trivial. Maybe later.
+ this.root = JSON.parse(str);
+ }
+ catch (err) {
+ this.root = {};
+ }
+ },
+
+ // If the store is under quota, writes the root to the backing file.
+ // Otherwise quota observers are notified and nothing is written.
+ write: function JsonStore_write() {
+ if (this.quotaUsage > 1)
+ this.onOverQuota(this);
+ else
+ this._write();
+ },
+
+ // Cleans up on unload. If unloading because of uninstall, the store is
+ // purged; otherwise it's written.
+ unload: function JsonStore_unload(reason) {
+ timer.clearInterval(this.writeTimer);
+ this.writeTimer = null;
+
+ if (reason === "uninstall")
+ this.purge();
+ else
+ this._write();
+ },
+
+ // True if the root is an empty object.
+ get _isEmpty() {
+ if (this.root && typeof(this.root) === "object") {
+ let empty = true;
+ for (let key in this.root) {
+ empty = false;
+ break;
+ }
+ return empty;
+ }
+ return false;
+ },
+
+ // Writes the root to the backing file, notifying write observers when
+ // complete. If the store is over quota or if it's empty and the store has
+ // never been written, nothing is written and write observers aren't notified.
+ _write: function JsonStore__write() {
+ // Don't write if the root is uninitialized or if the store is empty and the
+ // backing file doesn't yet exist.
+ if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename)))
+ return;
+
+ // If the store is over quota, don't write. The current under-quota state
+ // should persist.
+ if (this.quotaUsage > 1)
+ return;
+
+ // Finally, write.
+ let stream = file.open(this.filename, "w");
+ try {
+ stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) {
+ if (err)
+ console.error("Error writing simple storage file: " + this.filename);
+ else if (this.onWrite)
+ this.onWrite(this);
+ }.bind(this));
+ }
+ catch (err) {
+ // writeAsync closes the stream after it's done, so only close on error.
+ stream.close();
+ }
+ }
+};
+
+
+// This manages a JsonStore singleton and tailors its use to simple storage.
+// The root of the JsonStore is lazy-loaded: The backing file is only read the
+// first time the root's gotten.
+var manager = ({
+ jsonStore: null,
+
+ // The filename of the store, based on the profile dir and extension ID.
+ get filename() {
+ let storeFile = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("ProfD", Ci.nsIFile);
+ storeFile.append(JETPACK_DIR_BASENAME);
+ storeFile.append(jpSelf.id);
+ storeFile.append("simple-storage");
+ file.mkpath(storeFile.path);
+ storeFile.append("store.json");
+ return storeFile.path;
+ },
+
+ get quotaUsage() {
+ return this.jsonStore.quotaUsage;
+ },
+
+ get root() {
+ if (!this.jsonStore.isRootInited)
+ this.jsonStore.read();
+ return this.jsonStore.root;
+ },
+
+ set root(val) {
+ return this.jsonStore.root = val;
+ },
+
+ unload: function manager_unload() {
+ off(this);
+ },
+
+ new: function manager_constructor() {
+ let manager = Object.create(this);
+ unload.ensure(manager);
+
+ manager.jsonStore = new JsonStore({
+ filename: manager.filename,
+ writePeriod: prefs.get(WRITE_PERIOD_PREF, WRITE_PERIOD_DEFAULT),
+ quota: prefs.get(QUOTA_PREF, QUOTA_DEFAULT),
+ onOverQuota: emit.bind(null, exports, "OverQuota")
+ });
+
+ return manager;
+ }
+}).new();
+
+exports.on = on.bind(null, exports);
+exports.removeListener = function(type, listener) {
+ off(exports, type, listener);
+};
diff --git a/components/jetpack/sdk/stylesheet/style.js b/components/jetpack/sdk/stylesheet/style.js
new file mode 100644
index 000000000..7ec0787e1
--- /dev/null
+++ b/components/jetpack/sdk/stylesheet/style.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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci } = require("chrome");
+const { Class } = require("../core/heritage");
+const { URL, isLocalURL } = require('../url');
+const events = require("../system/events");
+const { loadSheet, removeSheet, isTypeValid } = require("./utils");
+const { isString } = require("../lang/type");
+const { attachTo, detachFrom } = require("../content/mod");
+const { data } = require('../self');
+
+const { freeze, create } = Object;
+
+function Style({ source, uri, type }) {
+ source = source == null ? null : freeze([].concat(source));
+ uri = uri == null ? null : freeze([].concat(uri));
+ type = type == null ? "author" : type;
+
+ if (source && !source.every(isString))
+ throw new Error('Style.source must be a string or an array of strings.');
+
+ if (uri && !uri.every(isLocalURL))
+ throw new Error('Style.uri must be a local URL or an array of local URLs');
+
+ if (type && !isTypeValid(type))
+ throw new Error('Style.type must be "agent", "user" or "author"');
+
+ return freeze(create(Style.prototype, {
+ "source": { value: source, enumerable: true },
+ "uri": { value: uri, enumerable: true },
+ "type": { value: type, enumerable: true }
+ }));
+};
+
+exports.Style = Style;
+
+attachTo.define(Style, function (style, window) {
+ if (style.uri) {
+ for (let uri of style.uri)
+ loadSheet(window, data.url(uri), style.type);
+ }
+
+ if (style.source) {
+ let uri = "data:text/css;charset=utf-8,";
+
+ uri += encodeURIComponent(style.source.join(""));
+
+ loadSheet(window, uri, style.type);
+ }
+});
+
+detachFrom.define(Style, function (style, window) {
+ if (style.uri)
+ for (let uri of style.uri)
+ removeSheet(window, data.url(uri));
+
+ if (style.source) {
+ let uri = "data:text/css;charset=utf-8,";
+
+ uri += encodeURIComponent(style.source.join(""));
+
+ removeSheet(window, uri, style.type);
+ }
+});
diff --git a/components/jetpack/sdk/stylesheet/utils.js b/components/jetpack/sdk/stylesheet/utils.js
new file mode 100644
index 000000000..844996bf3
--- /dev/null
+++ b/components/jetpack/sdk/stylesheet/utils.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Ci } = require("chrome");
+
+const SHEET_TYPE = {
+ "agent": "AGENT_SHEET",
+ "user": "USER_SHEET",
+ "author": "AUTHOR_SHEET"
+};
+
+function getDOMWindowUtils(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils);
+};
+
+/**
+ * Synchronously loads a style sheet from `uri` and adds it to the list of
+ * additional style sheets of the document.
+ * The sheets added takes effect immediately, and only on the document of the
+ * `window` given.
+ */
+function loadSheet(window, url, type) {
+ if (!(type && type in SHEET_TYPE))
+ type = "author";
+
+ type = SHEET_TYPE[type];
+
+ if (url instanceof Ci.nsIURI)
+ url = url.spec;
+
+ let winUtils = getDOMWindowUtils(window);
+ try {
+ winUtils.loadSheetUsingURIString(url, winUtils[type]);
+ }
+ catch (e) {};
+};
+exports.loadSheet = loadSheet;
+
+/**
+ * Remove the document style sheet at `sheetURI` from the list of additional
+ * style sheets of the document. The removal takes effect immediately.
+ */
+function removeSheet(window, url, type) {
+ if (!(type && type in SHEET_TYPE))
+ type = "author";
+
+ type = SHEET_TYPE[type];
+
+ if (url instanceof Ci.nsIURI)
+ url = url.spec;
+
+ let winUtils = getDOMWindowUtils(window);
+
+ try {
+ winUtils.removeSheetUsingURIString(url, winUtils[type]);
+ }
+ catch (e) {};
+};
+exports.removeSheet = removeSheet;
+
+/**
+ * Returns `true` if the `type` given is valid, otherwise `false`.
+ * The values currently accepted are: "agent", "user" and "author".
+ */
+function isTypeValid(type) {
+ return type in SHEET_TYPE;
+}
+exports.isTypeValid = isTypeValid;
diff --git a/components/jetpack/sdk/system.js b/components/jetpack/sdk/system.js
new file mode 100644
index 000000000..1acfe8c8c
--- /dev/null
+++ b/components/jetpack/sdk/system.js
@@ -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/. */
+'use strict';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, CC } = require('chrome');
+const options = require('@loader/options');
+const runtime = require("./system/runtime");
+const { when: unload } = require("./system/unload");
+
+const appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
+ getService(Ci.nsIAppStartup);
+const appInfo = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo);
+const directoryService = Cc['@mozilla.org/file/directory_service;1'].
+ getService(Ci.nsIProperties);
+
+const PR_WRONLY = parseInt("0x02");
+const PR_CREATE_FILE = parseInt("0x08");
+const PR_APPEND = parseInt("0x10");
+const PR_TRUNCATE = parseInt("0x20");
+
+function openFile(path, mode) {
+ let file = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, mode, -1, 0);
+ return stream
+}
+
+const { eAttemptQuit: E_ATTEMPT, eForceQuit: E_FORCE } = appStartup;
+
+/**
+ * Parsed JSON object that was passed via `cfx --static-args "{ foo: 'bar' }"`
+ */
+exports.staticArgs = options.staticArgs;
+
+/**
+ * Environment variables. Environment variables are non-enumerable properties
+ * of this object (key is name and value is value).
+ */
+exports.env = require('./system/environment').env;
+
+/**
+ * Ends the process with the specified `code`. If omitted, exit uses the
+ * 'success' code 0. To exit with failure use `1`.
+ * TODO: Improve platform to actually quit with an exit code.
+ */
+var forcedExit = false;
+exports.exit = function exit(code) {
+ if (forcedExit) {
+ // a forced exit was already tried
+ // NOTE: exit(0) is called twice sometimes (ex when using cfx testaddons)
+ return;
+ }
+
+ let resultsFile = 'resultFile' in options && options.resultFile;
+ function unloader() {
+ if (!options.resultFile) {
+ return;
+ }
+
+ // This is used by 'cfx' to find out exit code.
+ let mode = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
+ let stream = openFile(options.resultFile, mode);
+ let status = code ? 'FAIL' : 'OK';
+ stream.write(status, status.length);
+ stream.flush();
+ stream.close();
+ return;
+ }
+
+ if (code == 0) {
+ forcedExit = true;
+ }
+
+ // Bug 856999: Prevent automatic kill of Firefox when running tests
+ if (options.noQuit) {
+ return unload(unloader);
+ }
+
+ unloader();
+ appStartup.quit(code ? E_ATTEMPT : E_FORCE);
+};
+
+// Adapter for nodejs's stdout & stderr:
+// http://nodejs.org/api/process.html#process_process_stdout
+var stdout = Object.freeze({ write: dump, end: dump });
+exports.stdout = stdout;
+exports.stderr = stdout;
+
+/**
+ * Returns a path of the system's or application's special directory / file
+ * associated with a given `id`. For list of possible `id`s please see:
+ * https://developer.mozilla.org/en-US/docs/Code_snippets/File_I_O#Getting_files_in_special_directories
+ * http://dxr.mozilla.org/mozilla-central/source/xpcom/io/nsAppDirectoryServiceDefs.h
+ * @example
+ *
+ * // get firefox profile path
+ * let profilePath = require('system').pathFor('ProfD');
+ * // get OS temp files directory (/tmp)
+ * let temps = require('system').pathFor('TmpD');
+ * // get OS desktop path for an active user (~/Desktop on linux
+ * // or C:\Documents and Settings\username\Desktop on windows).
+ * let desktopPath = require('system').pathFor('Desk');
+ */
+exports.pathFor = function pathFor(id) {
+ return directoryService.get(id, Ci.nsIFile).path;
+};
+
+/**
+ * What platform you're running on (all lower case string).
+ * For possible values see:
+ * https://developer.mozilla.org/en/OS_TARGET
+ */
+exports.platform = runtime.OS.toLowerCase();
+
+const [, architecture, compiler] = runtime.XPCOMABI ?
+ runtime.XPCOMABI.match(/^([^-]*)-(.*)$/) :
+ [, null, null];
+
+/**
+ * What processor architecture you're running on:
+ * `'arm', 'ia32', or 'x64'`.
+ */
+exports.architecture = architecture;
+
+/**
+ * What compiler used for build:
+ * `'msvc', 'n32', 'gcc2', 'gcc3', 'sunc', 'ibmc'...`
+ */
+exports.compiler = compiler;
+
+/**
+ * The application's build ID/date, for example "2004051604".
+ */
+exports.build = appInfo.appBuildID;
+
+/**
+ * The XUL application's UUID.
+ * This has traditionally been in the form
+ * `{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}` but for some applications it may
+ * be: "appname@vendor.tld".
+ */
+exports.id = appInfo.ID;
+
+/**
+ * The name of the application.
+ */
+exports.name = appInfo.name;
+
+/**
+ * The XUL application's version, for example "0.8.0+" or "3.7a1pre".
+ */
+exports.version = appInfo.version;
+
+/**
+ * XULRunner version.
+ */
+exports.platformVersion = appInfo.platformVersion;
+
+
+/**
+ * The name of the application vendor, for example "Mozilla".
+ */
+exports.vendor = appInfo.vendor;
diff --git a/components/jetpack/sdk/system/child_process.js b/components/jetpack/sdk/system/child_process.js
new file mode 100644
index 000000000..8ea1f4f80
--- /dev/null
+++ b/components/jetpack/sdk/system/child_process.js
@@ -0,0 +1,332 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'experimental'
+};
+
+var { Ci } = require('chrome');
+var subprocess = require('./child_process/subprocess');
+var { EventTarget } = require('../event/target');
+var { Stream } = require('../io/stream');
+var { on, emit, off } = require('../event/core');
+var { Class } = require('../core/heritage');
+var { platform } = require('../system');
+var { isFunction, isArray } = require('../lang/type');
+var { delay } = require('../lang/functional');
+var { merge } = require('../util/object');
+var { setTimeout, clearTimeout } = require('../timers');
+var isWindows = platform.indexOf('win') === 0;
+
+var processes = new WeakMap();
+
+
+/**
+ * The `Child` class wraps a subprocess command, exposes
+ * the stdio streams, and methods to manipulate the subprocess
+ */
+var Child = Class({
+ implements: [EventTarget],
+ initialize: function initialize (options) {
+ let child = this;
+ let proc;
+
+ this.killed = false;
+ this.exitCode = undefined;
+ this.signalCode = undefined;
+
+ this.stdin = Stream();
+ this.stdout = Stream();
+ this.stderr = Stream();
+
+ try {
+ proc = subprocess.call({
+ command: options.file,
+ arguments: options.cmdArgs,
+ environment: serializeEnv(options.env),
+ workdir: options.cwd,
+ charset: options.encoding,
+ stdout: data => emit(child.stdout, 'data', data),
+ stderr: data => emit(child.stderr, 'data', data),
+ stdin: stream => {
+ child.stdin.on('data', pumpStdin);
+ child.stdin.on('end', function closeStdin () {
+ child.stdin.off('data', pumpStdin);
+ child.stdin.off('end', closeStdin);
+ stream.close();
+ });
+ function pumpStdin (data) {
+ stream.write(data);
+ }
+ },
+ done: function (result, error) {
+ if (error)
+ return handleError(error);
+
+ // Only emit if child is not killed; otherwise,
+ // the `kill` method will handle this
+ if (!child.killed) {
+ child.exitCode = result.exitCode;
+ child.signalCode = null;
+
+ // If process exits with < 0, there was an error
+ if (child.exitCode < 0) {
+ handleError(new Error('Process exited with exit code ' + child.exitCode));
+ }
+ else {
+ // Also do 'exit' event as there's not much of
+ // a difference in our implementation as we're not using
+ // node streams
+ emit(child, 'exit', child.exitCode, child.signalCode);
+ }
+
+ // Emit 'close' event with exit code and signal,
+ // which is `null`, as it was not a killed process
+ emit(child, 'close', child.exitCode, child.signalCode);
+ }
+ }
+ });
+ processes.set(child, proc);
+ } catch (e) {
+ // Delay the error handling so an error handler can be set
+ // during the same tick that the Child was created
+ delay(() => handleError(e));
+ }
+
+ // `handleError` is called when process could not even
+ // be spawned
+ function handleError (e) {
+ // If error is an nsIObject, make a fresh error object
+ // so we're not exposing nsIObjects, and we can modify it
+ // with additional process information, like node
+ let error = e;
+ if (e instanceof Ci.nsISupports) {
+ error = new Error(e.message, e.filename, e.lineNumber);
+ }
+ emit(child, 'error', error);
+ child.exitCode = -1;
+ child.signalCode = null;
+ emit(child, 'close', child.exitCode, child.signalCode);
+ }
+ },
+ kill: function kill (signal) {
+ let proc = processes.get(this);
+ proc.kill(signal);
+ this.killed = true;
+ this.exitCode = null;
+ this.signalCode = signal;
+ emit(this, 'exit', this.exitCode, this.signalCode);
+ emit(this, 'close', this.exitCode, this.signalCode);
+ },
+ get pid() { return processes.get(this, {}).pid || -1; }
+});
+
+function spawn (file, ...args) {
+ let cmdArgs = [];
+ // Default options
+ let options = {
+ cwd: null,
+ env: null,
+ encoding: 'UTF-8'
+ };
+
+ if (args[1]) {
+ merge(options, args[1]);
+ cmdArgs = args[0];
+ }
+ else {
+ if (isArray(args[0]))
+ cmdArgs = args[0];
+ else
+ merge(options, args[0]);
+ }
+
+ if ('gid' in options)
+ console.warn('`gid` option is not yet supported for `child_process`');
+ if ('uid' in options)
+ console.warn('`uid` option is not yet supported for `child_process`');
+ if ('detached' in options)
+ console.warn('`detached` option is not yet supported for `child_process`');
+
+ options.file = file;
+ options.cmdArgs = cmdArgs;
+
+ return Child(options);
+}
+
+exports.spawn = spawn;
+
+/**
+ * exec(command, options, callback)
+ */
+function exec (cmd, ...args) {
+ let file, cmdArgs, callback, options = {};
+
+ if (isFunction(args[0]))
+ callback = args[0];
+ else {
+ merge(options, args[0]);
+ callback = args[1];
+ }
+
+ if (isWindows) {
+ file = 'C:\\Windows\\System32\\cmd.exe';
+ cmdArgs = ['/S/C', cmd || ''];
+ }
+ else {
+ file = '/bin/sh';
+ cmdArgs = ['-c', cmd];
+ }
+
+ // Undocumented option from node being able to specify shell
+ if (options && options.shell)
+ file = options.shell;
+
+ return execFile(file, cmdArgs, options, callback);
+}
+exports.exec = exec;
+/**
+ * execFile (file, args, options, callback)
+ */
+function execFile (file, ...args) {
+ let cmdArgs = [], callback;
+ // Default options
+ let options = {
+ cwd: null,
+ env: null,
+ encoding: 'utf8',
+ timeout: 0,
+ maxBuffer: 204800, //200 KB (200*1024 bytes)
+ killSignal: 'SIGTERM'
+ };
+
+ if (isFunction(args[args.length - 1]))
+ callback = args[args.length - 1];
+
+ if (isArray(args[0])) {
+ cmdArgs = args[0];
+ merge(options, args[1]);
+ } else if (!isFunction(args[0]))
+ merge(options, args[0]);
+
+ let child = spawn(file, cmdArgs, options);
+ let exited = false;
+ let stdout = '';
+ let stderr = '';
+ let error = null;
+ let timeoutId = null;
+
+ child.stdout.setEncoding(options.encoding);
+ child.stderr.setEncoding(options.encoding);
+
+ on(child.stdout, 'data', pumpStdout);
+ on(child.stderr, 'data', pumpStderr);
+ on(child, 'close', exitHandler);
+ on(child, 'error', errorHandler);
+
+ if (options.timeout > 0) {
+ setTimeout(() => {
+ kill();
+ timeoutId = null;
+ }, options.timeout);
+ }
+
+ function exitHandler (code, signal) {
+
+ // Return if exitHandler called previously, occurs
+ // when multiple maxBuffer errors thrown and attempt to kill multiple
+ // times
+ if (exited) return;
+ exited = true;
+
+ if (!isFunction(callback)) return;
+
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ timeoutId = null;
+ }
+
+ if (!error && (code !== 0 || signal !== null))
+ error = createProcessError(new Error('Command failed: ' + stderr), {
+ code: code,
+ signal: signal,
+ killed: !!child.killed
+ });
+
+ callback(error, stdout, stderr);
+
+ off(child.stdout, 'data', pumpStdout);
+ off(child.stderr, 'data', pumpStderr);
+ off(child, 'close', exitHandler);
+ off(child, 'error', errorHandler);
+ }
+
+ function errorHandler (e) {
+ error = e;
+ exitHandler();
+ }
+
+ function kill () {
+ try {
+ child.kill(options.killSignal);
+ } catch (e) {
+ // In the scenario where the kill signal happens when
+ // the process is already closing, just abort the kill fail
+ if (/library is not open/.test(e))
+ return;
+ error = e;
+ exitHandler(-1, options.killSignal);
+ }
+ }
+
+ function pumpStdout (data) {
+ stdout += data;
+ if (stdout.length > options.maxBuffer) {
+ error = new Error('stdout maxBuffer exceeded');
+ kill();
+ }
+ }
+
+ function pumpStderr (data) {
+ stderr += data;
+ if (stderr.length > options.maxBuffer) {
+ error = new Error('stderr maxBuffer exceeded');
+ kill();
+ }
+ }
+
+ return child;
+}
+exports.execFile = execFile;
+
+exports.fork = function fork () {
+ throw new Error("child_process#fork is not currently supported");
+};
+
+function serializeEnv (obj) {
+ return Object.keys(obj || {}).map(prop => prop + '=' + obj[prop]);
+}
+
+function createProcessError (err, options = {}) {
+ // If code and signal look OK, this was probably a failure
+ // attempting to spawn the process (like ENOENT in node) -- use
+ // the code from the error message
+ if (!options.code && !options.signal) {
+ let match = err.message.match(/(NS_ERROR_\w*)/);
+ if (match && match.length > 1)
+ err.code = match[1];
+ else {
+ // If no good error message found, use the passed in exit code;
+ // this occurs when killing a process that's already closing,
+ // where we want both a valid exit code (0) and the error
+ err.code = options.code != null ? options.code : null;
+ }
+ }
+ else
+ err.code = options.code != null ? options.code : null;
+ err.signal = options.signal || null;
+ err.killed = options.killed || false;
+ return err;
+}
diff --git a/components/jetpack/sdk/system/child_process/subprocess.js b/components/jetpack/sdk/system/child_process/subprocess.js
new file mode 100644
index 000000000..e3454e95b
--- /dev/null
+++ b/components/jetpack/sdk/system/child_process/subprocess.js
@@ -0,0 +1,186 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { Ci, Cu } = require("chrome");
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Subprocess.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+const Runtime = require("sdk/system/runtime");
+const Environment = require("sdk/system/environment").env;
+const DEFAULT_ENVIRONMENT = [];
+if (Runtime.OS == "Linux" && "DISPLAY" in Environment) {
+ DEFAULT_ENVIRONMENT.push("DISPLAY=" + Environment.DISPLAY);
+}
+
+function awaitPromise(promise) {
+ let value;
+ let resolved = null;
+ promise.then(val => {
+ resolved = true;
+ value = val;
+ }, val => {
+ resolved = false;
+ value = val;
+ });
+
+ while (resolved === null)
+ Services.tm.mainThread.processNextEvent(true);
+
+ if (resolved === true)
+ return value;
+ throw value;
+}
+
+let readAllData = Task.async(function* (pipe, read, callback) {
+ let string;
+ while (string = yield read(pipe))
+ callback(string);
+});
+
+let write = (pipe, data) => {
+ let buffer = new Uint8Array(Array.from(data, c => c.charCodeAt(0)));
+ return pipe.write(data);
+};
+
+var subprocess = {
+ call: function(options) {
+ var result;
+
+ let procPromise = Task.spawn(function*() {
+ let opts = {};
+
+ if (options.mergeStderr) {
+ opts.stderr = "stdout"
+ } else if (options.stderr) {
+ opts.stderr = "pipe";
+ }
+
+ if (options.command instanceof Ci.nsIFile) {
+ opts.command = options.command.path;
+ } else {
+ opts.command = yield Subprocess.pathSearch(options.command);
+ }
+
+ if (options.workdir) {
+ opts.workdir = options.workdir;
+ }
+
+ opts.arguments = options.arguments || [];
+
+
+ // Set up environment
+
+ let envVars = options.environment || DEFAULT_ENVIRONMENT;
+ if (envVars.length) {
+ let environment = {};
+ for (let val of envVars) {
+ let idx = val.indexOf("=");
+ if (idx >= 0)
+ environment[val.slice(0, idx)] = val.slice(idx + 1);
+ }
+
+ opts.environment = environment;
+ }
+
+
+ let proc = yield Subprocess.call(opts);
+
+ Object.defineProperty(result, "pid", {
+ value: proc.pid,
+ enumerable: true,
+ configurable: true,
+ });
+
+
+ let promises = [];
+
+ // Set up IO handlers.
+
+ let read = pipe => pipe.readString();
+ if (options.charset === null) {
+ read = pipe => {
+ return pipe.read().then(buffer => {
+ return String.fromCharCode(...buffer);
+ });
+ };
+ }
+
+ if (options.stdout)
+ promises.push(readAllData(proc.stdout, read, options.stdout));
+
+ if (options.stderr && proc.stderr)
+ promises.push(readAllData(proc.stderr, read, options.stderr));
+
+ // Process stdin
+
+ if (typeof options.stdin === "string") {
+ write(proc.stdin, options.stdin);
+ proc.stdin.close();
+ }
+
+
+ // Handle process completion
+
+ if (options.done)
+ Promise.all(promises)
+ .then(() => proc.wait())
+ .then(options.done);
+
+ return proc;
+ });
+
+ procPromise.catch(e => {
+ if (options.done)
+ options.done({exitCode: -1}, e);
+ else
+ Cu.reportError(e instanceof Error ? e : e.message || e);
+ });
+
+ if (typeof options.stdin === "function") {
+ // Unfortunately, some callers (child_process.js) depend on this
+ // being called synchronously.
+ options.stdin({
+ write(val) {
+ procPromise.then(proc => {
+ write(proc.stdin, val);
+ });
+ },
+
+ close() {
+ procPromise.then(proc => {
+ proc.stdin.close();
+ });
+ },
+ });
+ }
+
+ result = {
+ get pid() {
+ return awaitPromise(procPromise.then(proc => {
+ return proc.pid;
+ }));
+ },
+
+ wait() {
+ return awaitPromise(procPromise.then(proc => {
+ return proc.wait().then(({exitCode}) => exitCode);
+ }));
+ },
+
+ kill(hard = false) {
+ procPromise.then(proc => {
+ proc.kill(hard ? 0 : undefined);
+ });
+ },
+ };
+
+ return result;
+ },
+};
+
+module.exports = subprocess;
diff --git a/components/jetpack/sdk/system/environment.js b/components/jetpack/sdk/system/environment.js
new file mode 100644
index 000000000..13621a696
--- /dev/null
+++ b/components/jetpack/sdk/system/environment.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { Cc, Ci } = require('chrome');
+const { get, set, exists } = Cc['@mozilla.org/process/environment;1'].
+ getService(Ci.nsIEnvironment);
+
+exports.env = new Proxy({}, {
+ deleteProperty(target, property) {
+ set(property, null);
+ return true;
+ },
+
+ get(target, property, receiver) {
+ return get(property) || undefined;
+ },
+
+ has(target, property) {
+ return exists(property);
+ },
+
+ set(target, property, value, receiver) {
+ set(property, value);
+ return true;
+ }
+});
diff --git a/components/jetpack/sdk/system/events-shimmed.js b/components/jetpack/sdk/system/events-shimmed.js
new file mode 100644
index 000000000..14496f1f0
--- /dev/null
+++ b/components/jetpack/sdk/system/events-shimmed.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/. */
+
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const events = require('./events.js');
+
+exports.emit = (type, event) => events.emit(type, event, true);
+exports.on = (type, listener, strong) => events.on(type, listener, strong, true);
+exports.once = (type, listener) => events.once(type, listener, true);
+exports.off = (type, listener) => events.off(type, listener, true);
diff --git a/components/jetpack/sdk/system/events.js b/components/jetpack/sdk/system/events.js
new file mode 100644
index 000000000..0cf525aa1
--- /dev/null
+++ b/components/jetpack/sdk/system/events.js
@@ -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/. */
+
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const { Cc, Ci, Cu } = require('chrome');
+const { Unknown } = require('../platform/xpcom');
+const { Class } = require('../core/heritage');
+const { ns } = require('../core/namespace');
+const observerService =
+ Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
+const { addObserver, removeObserver, notifyObservers } = observerService;
+const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
+const addObserverNoShim = ShimWaiver.getProperty(observerService, "addObserver");
+const removeObserverNoShim = ShimWaiver.getProperty(observerService, "removeObserver");
+const notifyObserversNoShim = ShimWaiver.getProperty(observerService, "notifyObservers");
+const unloadSubject = require('@loader/unload');
+
+const Subject = Class({
+ extends: Unknown,
+ initialize: function initialize(object) {
+ // Double-wrap the object and set a property identifying the
+ // wrappedJSObject as one of our wrappers to distinguish between
+ // subjects that are one of our wrappers (which we should unwrap
+ // when notifying our observers) and those that are real JS XPCOM
+ // components (which we should pass through unaltered).
+ this.wrappedJSObject = {
+ observersModuleSubjectWrapper: true,
+ object: object
+ };
+ },
+ getScriptableHelper: function() {},
+ getInterfaces: function() {}
+});
+
+function emit(type, event, shimmed = false) {
+ // From bug 910599
+ // We must test to see if 'subject' or 'data' is a defined property
+ // of the event object, but also allow primitives to be passed in,
+ // which the `in` operator breaks, yet `null` is an object, hence
+ // the long conditional
+ let subject = event && typeof event === 'object' && 'subject' in event ?
+ Subject(event.subject) :
+ null;
+ let data = event && typeof event === 'object' ?
+ // An object either returns its `data` property or null
+ ('data' in event ? event.data : null) :
+ // All other types return themselves (and cast to strings/null
+ // via observer service)
+ event;
+ if (shimmed) {
+ notifyObservers(subject, type, data);
+ } else {
+ notifyObserversNoShim(subject, type, data);
+ }
+}
+exports.emit = emit;
+
+const Observer = Class({
+ extends: Unknown,
+ initialize: function initialize(listener) {
+ this.listener = listener;
+ },
+ interfaces: [ 'nsIObserver', 'nsISupportsWeakReference' ],
+ observe: function(subject, topic, data) {
+ // Extract the wrapped object for subjects that are one of our
+ // wrappers around a JS object. This way we support both wrapped
+ // subjects created using this module and those that are real
+ // XPCOM components.
+ if (subject && typeof(subject) == 'object' &&
+ ('wrappedJSObject' in subject) &&
+ ('observersModuleSubjectWrapper' in subject.wrappedJSObject))
+ subject = subject.wrappedJSObject.object;
+
+ try {
+ this.listener({
+ type: topic,
+ subject: subject,
+ data: data
+ });
+ }
+ catch (error) {
+ console.exception(error);
+ }
+ }
+});
+
+const subscribers = ns();
+
+function on(type, listener, strong, shimmed = false) {
+ // Unless last optional argument is `true` we use a weak reference to a
+ // listener.
+ let weak = !strong;
+ // Take list of observers associated with given `listener` function.
+ let observers = subscribers(listener);
+ // If `observer` for the given `type` is not registered yet, then
+ // associate an `observer` and register it.
+ if (!(type in observers)) {
+ let observer = Observer(listener);
+ observers[type] = observer;
+ if (shimmed) {
+ addObserver(observer, type, weak);
+ } else {
+ addObserverNoShim(observer, type, weak);
+ }
+ // WeakRef gymnastics to remove all alive observers on unload
+ let ref = Cu.getWeakReference(observer);
+ weakRefs.set(observer, ref);
+ stillAlive.set(ref, type);
+ wasShimmed.set(ref, shimmed);
+ }
+}
+exports.on = on;
+
+function once(type, listener, shimmed = false) {
+ // Note: this code assumes order in which listeners are called, which is fine
+ // as long as dispatch happens in same order as listener registration which
+ // is the case now. That being said we should be aware that this may break
+ // in a future if order will change.
+ on(type, listener, shimmed);
+ on(type, function cleanup() {
+ off(type, listener, shimmed);
+ off(type, cleanup, shimmed);
+ }, true, shimmed);
+}
+exports.once = once;
+
+function off(type, listener, shimmed = false) {
+ // Take list of observers as with the given `listener`.
+ let observers = subscribers(listener);
+ // If `observer` for the given `type` is registered, then
+ // remove it & unregister.
+ if (type in observers) {
+ let observer = observers[type];
+ delete observers[type];
+ if (shimmed) {
+ removeObserver(observer, type);
+ } else {
+ removeObserverNoShim(observer, type);
+ }
+ stillAlive.delete(weakRefs.get(observer));
+ wasShimmed.delete(weakRefs.get(observer));
+ }
+}
+exports.off = off;
+
+// must use WeakMap to keep reference to all the WeakRefs (!), see bug 986115
+var weakRefs = new WeakMap();
+
+// and we're out of beta, we're releasing on time!
+var stillAlive = new Map();
+
+var wasShimmed = new Map();
+
+on('sdk:loader:destroy', function onunload({ subject, data: reason }) {
+ // using logic from ./unload, to avoid a circular module reference
+ if (subject.wrappedJSObject === unloadSubject) {
+ off('sdk:loader:destroy', onunload, false);
+
+ // don't bother
+ if (reason === 'shutdown')
+ return;
+
+ stillAlive.forEach( (type, ref) => {
+ let observer = ref.get();
+ if (observer) {
+ if (wasShimmed.get(ref)) {
+ removeObserver(observer, type);
+ } else {
+ removeObserverNoShim(observer, type);
+ }
+ }
+ })
+ }
+ // a strong reference
+}, true, false);
diff --git a/components/jetpack/sdk/system/globals.js b/components/jetpack/sdk/system/globals.js
new file mode 100644
index 000000000..a1a6cf9a2
--- /dev/null
+++ b/components/jetpack/sdk/system/globals.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+var { Cc, Ci, CC } = require('chrome');
+var { PlainTextConsole } = require('../console/plain-text');
+var { stdout } = require('../system');
+var ScriptError = CC('@mozilla.org/scripterror;1', 'nsIScriptError');
+var consoleService = Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService);
+
+// On windows dump does not writes into stdout so cfx can't read thous dumps.
+// To workaround this issue we write to a special file from which cfx will
+// read and print to the console.
+// For more details see: bug-673383
+exports.dump = stdout.write;
+
+exports.console = new PlainTextConsole();
+
+// Provide CommonJS `define` to allow authoring modules in a format that can be
+// loaded both into jetpack and into browser via AMD loaders.
+Object.defineProperty(exports, 'define', {
+ // `define` is provided as a lazy getter that binds below defined `define`
+ // function to the module scope, so that require, exports and module
+ // variables remain accessible.
+ configurable: true,
+ get: function() {
+ let sandbox = this;
+ return function define(factory) {
+ factory = Array.slice(arguments).pop();
+ factory.call(sandbox, sandbox.require, sandbox.exports, sandbox.module);
+ }
+ },
+ set: function(value) {
+ Object.defineProperty(this, 'define', {
+ configurable: true,
+ enumerable: true,
+ value,
+ });
+ },
+});
diff --git a/components/jetpack/sdk/system/process.js b/components/jetpack/sdk/system/process.js
new file mode 100644
index 000000000..f44a36658
--- /dev/null
+++ b/components/jetpack/sdk/system/process.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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const {
+ exit, version, stdout, stderr, platform, architecture
+} = require("../system");
+
+/**
+ * Supported
+ */
+
+exports.stdout = stdout;
+exports.stderr = stderr;
+exports.version = version;
+exports.versions = {};
+exports.config = {};
+exports.arch = architecture;
+exports.platform = platform;
+exports.exit = exit;
+
+/**
+ * Partial support
+ */
+
+// An alias to `setTimeout(fn, 0)`, which isn't the same as node's `nextTick`,
+// but atleast ensures it'll occur asynchronously
+exports.nextTick = (callback) => setTimeout(callback, 0);
+
+/**
+ * Unsupported
+ */
+
+exports.maxTickDepth = 1000;
+exports.pid = 0;
+exports.title = "";
+exports.stdin = {};
+exports.argv = [];
+exports.execPath = "";
+exports.execArgv = [];
+exports.abort = function () {};
+exports.chdir = function () {};
+exports.cwd = function () {};
+exports.env = {};
+exports.getgid = function () {};
+exports.setgid = function () {};
+exports.getuid = function () {};
+exports.setuid = function () {};
+exports.getgroups = function () {};
+exports.setgroups = function () {};
+exports.initgroups = function () {};
+exports.kill = function () {};
+exports.memoryUsage = function () {};
+exports.umask = function () {};
+exports.uptime = function () {};
+exports.hrtime = function () {};
diff --git a/components/jetpack/sdk/system/runtime.js b/components/jetpack/sdk/system/runtime.js
new file mode 100644
index 000000000..9a70f142d
--- /dev/null
+++ b/components/jetpack/sdk/system/runtime.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci } = require("chrome");
+const runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+
+exports.inSafeMode = runtime.inSafeMode;
+exports.OS = runtime.OS;
+exports.processType = runtime.processType;
+exports.widgetToolkit = runtime.widgetToolkit;
+exports.processID = runtime.processID;
+
+// Attempt to access `XPCOMABI` may throw exception, in which case exported
+// `XPCOMABI` will be set to `null`.
+// https://mxr.mozilla.org/mozilla-central/source/toolkit/xre/nsAppRunner.cpp#732
+try {
+ exports.XPCOMABI = runtime.XPCOMABI;
+}
+catch (error) {
+ exports.XPCOMABI = null;
+}
diff --git a/components/jetpack/sdk/system/unload.js b/components/jetpack/sdk/system/unload.js
new file mode 100644
index 000000000..98ab5f8f3
--- /dev/null
+++ b/components/jetpack/sdk/system/unload.js
@@ -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/. */
+
+// Parts of this module were taken from narwhal:
+//
+// http://narwhaljs.org
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cu } = require('chrome');
+const { on, off } = require('./events');
+const unloadSubject = require('@loader/unload');
+
+const observers = [];
+const unloaders = [];
+
+function WeakObserver(inner) {
+ this._inner = Cu.getWeakReference(inner);
+}
+
+Object.defineProperty(WeakObserver.prototype, 'value', {
+ get: function() { this._inner.get() }
+});
+
+var when = exports.when = function when(observer, opts) {
+ opts = opts || {};
+ for (var i = 0; i < observers.length; ++i) {
+ if (observers[i] === observer || observers[i].value === observer) {
+ return;
+ }
+ }
+ if (opts.weak) {
+ observers.unshift(new WeakObserver(observer));
+ } else {
+ observers.unshift(observer);
+ }
+};
+
+var ensure = exports.ensure = function ensure(obj, destructorName) {
+ if (!destructorName)
+ destructorName = "unload";
+ if (!(destructorName in obj))
+ throw new Error("object has no '" + destructorName + "' property");
+
+ let called = false;
+ let originalDestructor = obj[destructorName];
+
+ function unloadWrapper(reason) {
+ if (!called) {
+ called = true;
+ let index = unloaders.indexOf(unloadWrapper);
+ if (index == -1)
+ throw new Error("internal error: unloader not found");
+ unloaders.splice(index, 1);
+ originalDestructor.call(obj, reason);
+ originalDestructor = null;
+ destructorName = null;
+ obj = null;
+ }
+ };
+
+ // TODO: Find out why the order is inverted here. It seems that
+ // it may be causing issues!
+ unloaders.push(unloadWrapper);
+
+ obj[destructorName] = unloadWrapper;
+};
+
+function unload(reason) {
+ observers.forEach(function(observer) {
+ try {
+ if (observer instanceof WeakObserver) {
+ observer = observer.value;
+ }
+ if (typeof observer === 'function') {
+ observer(reason);
+ }
+ }
+ catch (error) {
+ console.exception(error);
+ }
+ });
+}
+
+when(function(reason) {
+ unloaders.slice().forEach(function(unloadWrapper) {
+ unloadWrapper(reason);
+ });
+});
+
+on('sdk:loader:destroy', function onunload({ subject, data: reason }) {
+ // If this loader is unload then `subject.wrappedJSObject` will be
+ // `destructor`.
+ if (subject.wrappedJSObject === unloadSubject) {
+ off('sdk:loader:destroy', onunload);
+ unload(reason);
+ }
+// Note that we use strong reference to listener here to make sure it's not
+// GC-ed, which may happen otherwise since nothing keeps reference to `onunolad`
+// function.
+}, true);
diff --git a/components/jetpack/sdk/system/xul-app.js b/components/jetpack/sdk/system/xul-app.js
new file mode 100644
index 000000000..612386f77
--- /dev/null
+++ b/components/jetpack/sdk/system/xul-app.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { XulApp } = require("./xul-app.jsm");
+
+Object.keys(XulApp).forEach(k => exports[k] = XulApp[k]);
diff --git a/components/jetpack/sdk/system/xul-app.jsm b/components/jetpack/sdk/system/xul-app.jsm
new file mode 100644
index 000000000..74a0cd52c
--- /dev/null
+++ b/components/jetpack/sdk/system/xul-app.jsm
@@ -0,0 +1,244 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [ "XulApp" ];
+
+var { classes: Cc, interfaces: Ci } = Components;
+
+var exports = {};
+this.XulApp = exports;
+
+var appInfo;
+
+// NOTE: below is required to avoid failing xpcshell tests,
+// which do not implement nsIXULAppInfo
+// See Bug 1114752 https://bugzilla.mozilla.org/show_bug.cgi?id=1114752
+try {
+ appInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo);
+}
+catch (e) {
+ // xpcshell test case
+ appInfo = {};
+}
+var vc = Cc["@mozilla.org/xpcom/version-comparator;1"]
+ .getService(Ci.nsIVersionComparator);
+
+var ID = exports.ID = appInfo.ID;
+var name = exports.name = appInfo.name;
+var version = exports.version = appInfo.version;
+var platformVersion = exports.platformVersion = appInfo.platformVersion;
+
+// The following mapping of application names to GUIDs was taken from:
+//
+// https://addons.mozilla.org/en-US/firefox/pages/appversions
+//
+// Using the GUID instead of the app's name is preferable because sometimes
+// re-branded versions of a product have different names: for instance,
+// Firefox, Minefield, Iceweasel, and Shiretoko all have the same
+// GUID.
+
+var ids = exports.ids = {
+ Palemoon: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+ PaleMoon: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+ Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+ Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
+ SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
+ Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
+ Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}",
+ Instantbird: "{33cb9019-c295-46dd-be21-8c4936574bee}"
+};
+
+function is(name) {
+ if (!(name in ids))
+ throw new Error("Unkown Mozilla Application: " + name);
+ return ID == ids[name];
+};
+exports.is = is;
+
+function isOneOf(names) {
+ for (var i = 0; i < names.length; i++)
+ if (is(names[i]))
+ return true;
+ return false;
+};
+exports.isOneOf = isOneOf;
+
+/**
+ * Use this to check whether the given version (e.g. xulApp.platformVersion)
+ * is in the given range. Versions must be in version comparator-compatible
+ * format. See MDC for details:
+ * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
+ */
+var versionInRange = exports.versionInRange =
+function versionInRange(version, lowInclusive, highExclusive) {
+ return (vc.compare(version, lowInclusive) >= 0) &&
+ (vc.compare(version, highExclusive) < 0);
+}
+
+const reVersionRange = /^((?:<|>)?=?)?\s*((?:\d+[\S]*)|\*)(?:\s+((?:<|>)=?)?(\d+[\S]+))?$/;
+const reOnlyInifinity = /^[<>]?=?\s*[*x]$/;
+const reSubInfinity = /\.[*x]/g;
+const reHyphenRange = /^(\d+.*?)\s*-\s*(\d+.*?)$/;
+const reRangeSeparator = /\s*\|\|\s*/;
+
+const compares = {
+ "=": function (c) { return c === 0 },
+ ">=": function (c) { return c >= 0 },
+ "<=": function (c) { return c <= 0},
+ "<": function (c) { return c < 0 },
+ ">": function (c) { return c > 0 }
+}
+
+function normalizeRange(range) {
+ return range
+ .replace(reOnlyInifinity, "")
+ .replace(reSubInfinity, ".*")
+ .replace(reHyphenRange, ">=$1 <=$2")
+}
+
+/**
+ * Compare the versions given, using the comparison operator provided.
+ * Internal use only.
+ *
+ * @example
+ * compareVersion("1.2", "<=", "1.*") // true
+ *
+ * @param {String} version
+ * A version to compare
+ *
+ * @param {String} comparison
+ * The comparison operator
+ *
+ * @param {String} compareVersion
+ * A version to compare
+ */
+function compareVersion(version, comparison, compareVersion) {
+ let hasWildcard = compareVersion.indexOf("*") !== -1;
+
+ comparison = comparison || "=";
+
+ if (hasWildcard) {
+ switch (comparison) {
+ case "=":
+ let zeroVersion = compareVersion.replace(reSubInfinity, ".0");
+ return versionInRange(version, zeroVersion, compareVersion);
+ case ">=":
+ compareVersion = compareVersion.replace(reSubInfinity, ".0");
+ break;
+ }
+ }
+
+ let compare = compares[comparison];
+
+ return typeof compare === "function" && compare(vc.compare(version, compareVersion));
+}
+
+/**
+ * Returns `true` if `version` satisfies the `versionRange` given.
+ * If only an argument is passed, is used as `versionRange` and compared against
+ * `xulApp.platformVersion`.
+ *
+ * `versionRange` is either a string which has one or more space-separated
+ * descriptors, or a range like "fromVersion - toVersion".
+ * Version range descriptors may be any of the following styles:
+ *
+ * - "version" Must match `version` exactly
+ * - "=version" Same as just `version`
+ * - ">version" Must be greater than `version`
+ * - ">=version" Must be greater or equal than `version`
+ * - "<version" Must be less than `version`
+ * - "<=version" Must be less or equal than `version`
+ * - "1.2.x" or "1.2.*" See 'X version ranges' below
+ * - "*" or "" (just an empty string) Matches any version
+ * - "version1 - version2" Same as ">=version1 <=version2"
+ * - "range1 || range2" Passes if either `range1` or `range2` are satisfied
+ *
+ * For example, these are all valid:
+ * - "1.0.0 - 2.9999.9999"
+ * - ">=1.0.2 <2.1.2"
+ * - ">1.0.2 <=2.3.4"
+ * - "2.0.1"
+ * - "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
+ * - "2.x" (equivalent to "2.*")
+ * - "1.2.x" (equivalent to "1.2.*" and ">=1.2.0 <1.3.0")
+ */
+function satisfiesVersion(version, versionRange) {
+ if (arguments.length === 1) {
+ versionRange = version;
+ version = appInfo.version;
+ }
+
+ let ranges = versionRange.trim().split(reRangeSeparator);
+
+ return ranges.some(function(range) {
+ range = normalizeRange(range);
+
+ // No versions' range specified means that any version satisfies the
+ // requirements.
+ if (range === "")
+ return true;
+
+ let matches = range.match(reVersionRange);
+
+ if (!matches)
+ return false;
+
+ let [, lowMod, lowVer, highMod, highVer] = matches;
+
+ return compareVersion(version, lowMod, lowVer) && (highVer !== undefined
+ ? compareVersion(version, highMod, highVer)
+ : true);
+ });
+}
+exports.satisfiesVersion = satisfiesVersion;
+
+/**
+ * Ensure the current application satisfied the requirements specified in the
+ * module given. If not, an exception related to the incompatibility is
+ * returned; `null` otherwise.
+ *
+ * @param {Object} module
+ * The module to check
+ * @returns {Error}
+ */
+function incompatibility(module) {
+ let { metadata, id } = module;
+
+ // if metadata or engines are not specified we assume compatibility is not
+ // an issue.
+ if (!metadata || !("engines" in metadata))
+ return null;
+
+ let { engines } = metadata;
+
+ if (engines === null || typeof(engines) !== "object")
+ return new Error("Malformed engines' property in metadata");
+
+ let applications = Object.keys(engines);
+
+ let versionRange;
+ applications.forEach(function(name) {
+ if (is(name)) {
+ versionRange = engines[name];
+ // Continue iteration. We want to ensure the module doesn't
+ // contain a typo in the applications' name or some unknown
+ // application - `is` function throws an exception in that case.
+ }
+ });
+
+ if (typeof(versionRange) === "string") {
+ if (satisfiesVersion(versionRange))
+ return null;
+
+ return new Error("Unsupported Application version: The module " + id +
+ " currently supports only version " + versionRange + " of " +
+ name + ".");
+ }
+
+ return new Error("Unsupported Application: The module " + id +
+ " currently supports only " + applications.join(", ") + ".")
+}
+exports.incompatibility = incompatibility;
diff --git a/components/jetpack/sdk/tab/events.js b/components/jetpack/sdk/tab/events.js
new file mode 100644
index 000000000..e431cc9d2
--- /dev/null
+++ b/components/jetpack/sdk/tab/events.js
@@ -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/. */
+
+"use strict";
+
+// This module provides temporary shim until Bug 843901 is shipped.
+// It basically registers tab event listeners on all windows that get
+// opened and forwards them through observer notifications.
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Ci } = require("chrome");
+const { windows, isInteractive } = require("../window/utils");
+const { events } = require("../browser/events");
+const { open } = require("../event/dom");
+const { filter, map, merge, expand } = require("../event/utils");
+const isFennec = require("sdk/system/xul-app").is("Fennec");
+
+// Module provides event stream (in nodejs style) that emits data events
+// for all the tab events that happen in running firefox. At the moment
+// it does it by registering listeners on all browser windows and then
+// forwarding events when they occur to a stream. This will become obsolete
+// once Bug 843901 is fixed, and we'll just leverage observer notifications.
+
+// Set of tab events that this module going to aggregate and expose.
+const TYPES = ["TabOpen","TabClose","TabSelect","TabMove","TabPinned",
+ "TabUnpinned"];
+
+// Utility function that given a browser `window` returns stream of above
+// defined tab events for all tabs on the given window.
+function tabEventsFor(window) {
+ // Map supported event types to a streams of those events on the given
+ // `window` and than merge these streams into single form stream off
+ // all events.
+ let channels = TYPES.map(type => open(window, type));
+ return merge(channels);
+}
+
+// Create our event channels. We do this in a separate function to
+// minimize the chance of leaking intermediate objects on the global.
+function makeEvents() {
+ // Filter DOMContentLoaded events from all the browser events.
+ var readyEvents = filter(events, e => e.type === "DOMContentLoaded");
+ // Map DOMContentLoaded events to it's target browser windows.
+ var futureWindows = map(readyEvents, e => e.target);
+ // Expand all browsers that will become interactive to supported tab events
+ // on these windows. Result will be a tab events from all tabs of all windows
+ // that will become interactive.
+ var eventsFromFuture = expand(futureWindows, tabEventsFor);
+
+ // Above covers only windows that will become interactive in a future, but some
+ // windows may already be interactive so we pick those and expand to supported
+ // tab events for them too.
+ var interactiveWindows = windows("navigator:browser", { includePrivate: true }).
+ filter(isInteractive);
+ var eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));
+
+
+ // Finally merge stream of tab events from future windows and current windows
+ // to cover all tab events on all windows that will open.
+ return merge([eventsFromInteractive, eventsFromFuture]);
+}
+
+// Map events to Fennec format if necessary
+exports.events = map(makeEvents(), function (event) {
+ return !isFennec ? event : {
+ type: event.type,
+ target: event.target.ownerDocument.defaultView.BrowserApp
+ .getTabForBrowser(event.target)
+ };
+});
diff --git a/components/jetpack/sdk/tabs.js b/components/jetpack/sdk/tabs.js
new file mode 100644
index 000000000..f61cad478
--- /dev/null
+++ b/components/jetpack/sdk/tabs.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+if (require("./system/xul-app").is("Fennec")) {
+ module.exports = require("./windows/tabs-fennec").tabs;
+}
+else {
+ module.exports = require("./tabs/tabs-firefox");
+}
+
+const tabs = module.exports;
diff --git a/components/jetpack/sdk/tabs/common.js b/components/jetpack/sdk/tabs/common.js
new file mode 100644
index 000000000..9ee512a7b
--- /dev/null
+++ b/components/jetpack/sdk/tabs/common.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { validateOptions } = require("../deprecated/api-utils");
+const { data } = require("../self");
+
+function Options(options) {
+ if ('string' === typeof options)
+ options = { url: options };
+
+ return validateOptions(options, {
+ url: {
+ is: ["string"],
+ map: (v) => v ? data.url(v) : v
+ },
+ inBackground: {
+ map: Boolean,
+ is: ["undefined", "boolean"]
+ },
+ isPinned: { is: ["undefined", "boolean"] },
+ isPrivate: { is: ["undefined", "boolean"] },
+ inNewWindow: { is: ["undefined", "boolean"] },
+ onOpen: { is: ["undefined", "function"] },
+ onClose: { is: ["undefined", "function"] },
+ onReady: { is: ["undefined", "function"] },
+ onLoad: { is: ["undefined", "function"] },
+ onPageShow: { is: ["undefined", "function"] },
+ onActivate: { is: ["undefined", "function"] },
+ onDeactivate: { is: ["undefined", "function"] }
+ });
+}
+exports.Options = Options;
diff --git a/components/jetpack/sdk/tabs/events.js b/components/jetpack/sdk/tabs/events.js
new file mode 100644
index 000000000..65650f9dc
--- /dev/null
+++ b/components/jetpack/sdk/tabs/events.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const ON_PREFIX = "on";
+const TAB_PREFIX = "Tab";
+
+const EVENTS = {
+ ready: "DOMContentLoaded",
+ load: "load", // Used for non-HTML content
+ pageshow: "pageshow", // Used for cached content
+ open: "TabOpen",
+ close: "TabClose",
+ activate: "TabSelect",
+ deactivate: null,
+ pinned: "TabPinned",
+ unpinned: "TabUnpinned"
+}
+exports.EVENTS = EVENTS;
+
+Object.keys(EVENTS).forEach(function(name) {
+ EVENTS[name] = {
+ name: name,
+ listener: createListenerName(name),
+ dom: EVENTS[name]
+ }
+});
+
+function createListenerName (name) {
+ if (name === 'pageshow')
+ return 'onPageShow';
+ else
+ return ON_PREFIX + name.charAt(0).toUpperCase() + name.substr(1);
+}
diff --git a/components/jetpack/sdk/tabs/helpers.js b/components/jetpack/sdk/tabs/helpers.js
new file mode 100644
index 000000000..b2c8aa013
--- /dev/null
+++ b/components/jetpack/sdk/tabs/helpers.js
@@ -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/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+
+// NOTE: This file should only export Tab instances
+
+
+const { getTabForBrowser: getRawTabForBrowser } = require('./utils');
+const { modelFor } = require('../model/core');
+
+exports.getTabForRawTab = modelFor;
+
+function getTabForBrowser(browser) {
+ return modelFor(getRawTabForBrowser(browser)) || null;
+}
+exports.getTabForBrowser = getTabForBrowser;
diff --git a/components/jetpack/sdk/tabs/namespace.js b/components/jetpack/sdk/tabs/namespace.js
new file mode 100644
index 000000000..3553b1a99
--- /dev/null
+++ b/components/jetpack/sdk/tabs/namespace.js
@@ -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/. */
+'use strict';
+
+var { ns } = require('../core/namespace');
+
+exports.tabsNS = ns();
+exports.tabNS = ns();
+exports.rawTabNS = ns();
diff --git a/components/jetpack/sdk/tabs/observer.js b/components/jetpack/sdk/tabs/observer.js
new file mode 100644
index 000000000..4e935cd62
--- /dev/null
+++ b/components/jetpack/sdk/tabs/observer.js
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { EventTarget } = require("../event/target");
+const { emit } = require("../event/core");
+const { DOMEventAssembler } = require("../deprecated/events/assembler");
+const { Class } = require("../core/heritage");
+const { getActiveTab, getTabs } = require("./utils");
+const { browserWindowIterator } = require("../deprecated/window-utils");
+const { isBrowser, windows, getMostRecentBrowserWindow } = require("../window/utils");
+const { observer: windowObserver } = require("../windows/observer");
+const { when } = require("../system/unload");
+
+const EVENTS = {
+ "TabOpen": "open",
+ "TabClose": "close",
+ "TabSelect": "select",
+ "TabMove": "move",
+ "TabPinned": "pinned",
+ "TabUnpinned": "unpinned"
+};
+
+const selectedTab = Symbol("observer/state/selectedTab");
+
+// Event emitter objects used to register listeners and emit events on them
+// when they occur.
+const Observer = Class({
+ implements: [EventTarget, DOMEventAssembler],
+ initialize() {
+ this[selectedTab] = null;
+ // Currently Gecko does not dispatch any event on the previously selected
+ // tab before / after "TabSelect" is dispatched. In order to work around this
+ // limitation we keep track of selected tab and emit "deactivate" event with
+ // that before emitting "activate" on selected tab.
+ this.on("select", tab => {
+ const selected = this[selectedTab];
+ if (selected !== tab) {
+ if (selected) {
+ emit(this, 'deactivate', selected);
+ }
+
+ if (tab) {
+ this[selectedTab] = tab;
+ emit(this, 'activate', this[selectedTab]);
+ }
+ }
+ });
+
+
+ // We also observe opening / closing windows in order to add / remove it's
+ // containers to the observed list.
+ windowObserver.on("open", chromeWindow => {
+ if (isBrowser(chromeWindow)) {
+ this.observe(chromeWindow);
+ }
+ });
+
+ windowObserver.on("close", chromeWindow => {
+ if (isBrowser(chromeWindow)) {
+ // Bug 751546: Emit `deactivate` event on window close immediatly
+ // Otherwise we are going to face "dead object" exception on `select` event
+ if (getActiveTab(chromeWindow) === this[selectedTab]) {
+ emit(this, "deactivate", this[selectedTab]);
+ this[selectedTab] = null;
+ }
+ this.ignore(chromeWindow);
+ }
+ });
+
+
+ // Currently gecko does not dispatches "TabSelect" events when different
+ // window gets activated. To work around this limitation we emulate "select"
+ // event for this case.
+ windowObserver.on("activate", chromeWindow => {
+ if (isBrowser(chromeWindow)) {
+ emit(this, "select", getActiveTab(chromeWindow));
+ }
+ });
+
+ // We should synchronize state, since probably we already have at least one
+ // window open.
+ for (let chromeWindow of browserWindowIterator()) {
+ this.observe(chromeWindow);
+ }
+
+ when(_ => {
+ // Don't dispatch a deactivate event during unload.
+ this[selectedTab] = null;
+ });
+ },
+ /**
+ * Events that are supported and emitted by the module.
+ */
+ supportedEventsTypes: Object.keys(EVENTS),
+ /**
+ * Function handles all the supported events on all the windows that are
+ * observed. Method is used to proxy events to the listeners registered on
+ * this event emitter.
+ * @param {Event} event
+ * Keyboard event being emitted.
+ */
+ handleEvent: function handleEvent(event) {
+ emit(this, EVENTS[event.type], event.target, event);
+ }
+});
+
+exports.observer = new Observer();
diff --git a/components/jetpack/sdk/tabs/tab-fennec.js b/components/jetpack/sdk/tabs/tab-fennec.js
new file mode 100644
index 000000000..3927337f6
--- /dev/null
+++ b/components/jetpack/sdk/tabs/tab-fennec.js
@@ -0,0 +1,249 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, Ci } = require('chrome');
+const { Class } = require('../core/heritage');
+const { tabNS, rawTabNS } = require('./namespace');
+const { EventTarget } = require('../event/target');
+const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL,
+ getTabContentWindow, getTabForBrowser, setTabURL, getOwnerWindow,
+ getTabContentDocument, getTabContentType, getTabId, isTab } = require('./utils');
+const { emit } = require('../event/core');
+const { isPrivate } = require('../private-browsing/utils');
+const { isWindowPrivate } = require('../window/utils');
+const { when: unload } = require('../system/unload');
+const { BLANK } = require('../content/thumbnail');
+const { viewFor } = require('../view/core');
+const { EVENTS } = require('./events');
+const { modelFor } = require('../model/core');
+
+const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';
+
+const Tab = Class({
+ extends: EventTarget,
+ initialize: function initialize(options) {
+ options = options.tab ? options : { tab: options };
+ let tab = options.tab;
+
+ EventTarget.prototype.initialize.call(this, options);
+ let tabInternals = tabNS(this);
+ rawTabNS(tab).tab = this;
+
+ let window = tabInternals.window = options.window || getOwnerWindow(tab);
+ tabInternals.tab = tab;
+
+ // TabReady
+ let onReady = tabInternals.onReady = onTabReady.bind(this);
+ tab.browser.addEventListener(EVENTS.ready.dom, onReady, false);
+
+ // TabPageShow
+ let onPageShow = tabInternals.onPageShow = onTabPageShow.bind(this);
+ tab.browser.addEventListener(EVENTS.pageshow.dom, onPageShow, false);
+
+ // TabLoad
+ let onLoad = tabInternals.onLoad = onTabLoad.bind(this);
+ tab.browser.addEventListener(EVENTS.load.dom, onLoad, true);
+
+ // TabClose
+ let onClose = tabInternals.onClose = onTabClose.bind(this);
+ window.BrowserApp.deck.addEventListener(EVENTS.close.dom, onClose, false);
+
+ unload(cleanupTab.bind(null, this));
+ },
+
+ /**
+ * The title of the page currently loaded in the tab.
+ * Changing this property changes an actual title.
+ * @type {String}
+ */
+ get title() {
+ return getTabTitle(tabNS(this).tab);
+ },
+ set title(title) {
+ setTabTitle(tabNS(this).tab, title);
+ },
+
+ /**
+ * Location of the page currently loaded in this tab.
+ * Changing this property will loads page under under the specified location.
+ * @type {String}
+ */
+ get url() {
+ return tabNS(this).closed ? undefined : getTabURL(tabNS(this).tab);
+ },
+ set url(url) {
+ setTabURL(tabNS(this).tab, url);
+ },
+
+ getThumbnail: function() {
+ // TODO: implement!
+ console.error(ERR_FENNEC_MSG);
+
+ // return 80x45 blank default
+ return BLANK;
+ },
+
+ /**
+ * tab's document readyState, or 'uninitialized' if it doesn't even exist yet.
+ */
+ get readyState() {
+ let doc = getTabContentDocument(tabNS(this).tab);
+ return doc && doc.readyState || 'uninitialized';
+ },
+
+ get id() {
+ return getTabId(tabNS(this).tab);
+ },
+
+ /**
+ * The index of the tab relative to other tabs in the application window.
+ * Changing this property will change order of the actual position of the tab.
+ * @type {Number}
+ */
+ get index() {
+ if (tabNS(this).closed) return undefined;
+
+ let tabs = tabNS(this).window.BrowserApp.tabs;
+ let tab = tabNS(this).tab;
+ for (var i = tabs.length; i >= 0; i--) {
+ if (tabs[i] === tab)
+ return i;
+ }
+ return null;
+ },
+ set index(value) {
+ console.error(ERR_FENNEC_MSG); // TODO
+ },
+
+ /**
+ * Whether or not tab is pinned (Is an app-tab).
+ * @type {Boolean}
+ */
+ get isPinned() {
+ console.error(ERR_FENNEC_MSG); // TODO
+ return false; // TODO
+ },
+ pin: function pin() {
+ console.error(ERR_FENNEC_MSG); // TODO
+ },
+ unpin: function unpin() {
+ console.error(ERR_FENNEC_MSG); // TODO
+ },
+
+ /**
+ * Returns the MIME type that the document loaded in the tab is being
+ * rendered as.
+ * @type {String}
+ */
+ get contentType() {
+ return getTabContentType(tabNS(this).tab);
+ },
+
+ /**
+ * Create a worker for this tab, first argument is options given to Worker.
+ * @type {Worker}
+ */
+ attach: function attach(options) {
+ // BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946
+ // TODO: fix this circular dependency
+ let { Worker } = require('./worker');
+ return Worker(options, getTabContentWindow(tabNS(this).tab));
+ },
+
+ /**
+ * Make this tab active.
+ */
+ activate: function activate() {
+ activateTab(tabNS(this).tab, tabNS(this).window);
+ },
+
+ /**
+ * Close the tab
+ */
+ close: function close(callback) {
+ let tab = this;
+ this.once(EVENTS.close.name, function () {
+ tabNS(tab).closed = true;
+ if (callback) callback();
+ });
+
+ closeTab(tabNS(this).tab);
+ },
+
+ /**
+ * Reload the tab
+ */
+ reload: function reload() {
+ tabNS(this).tab.browser.reload();
+ }
+});
+exports.Tab = Tab;
+
+// Implement `viewFor` polymorphic function for the Tab
+// instances.
+viewFor.define(Tab, x => tabNS(x).tab);
+
+function cleanupTab(tab) {
+ let tabInternals = tabNS(tab);
+ if (!tabInternals.tab)
+ return;
+
+ if (tabInternals.tab.browser) {
+ tabInternals.tab.browser.removeEventListener(EVENTS.ready.dom, tabInternals.onReady, false);
+ tabInternals.tab.browser.removeEventListener(EVENTS.pageshow.dom, tabInternals.onPageShow, false);
+ tabInternals.tab.browser.removeEventListener(EVENTS.load.dom, tabInternals.onLoad, true);
+ }
+ tabInternals.onReady = null;
+ tabInternals.onPageShow = null;
+ tabInternals.onLoad = null;
+ tabInternals.window.BrowserApp.deck.removeEventListener(EVENTS.close.dom, tabInternals.onClose, false);
+ tabInternals.onClose = null;
+ rawTabNS(tabInternals.tab).tab = null;
+ tabInternals.tab = null;
+ tabInternals.window = null;
+}
+
+function onTabReady(event) {
+ let win = event.target.defaultView;
+
+ // ignore frames
+ if (win === win.top) {
+ emit(this, 'ready', this);
+ }
+}
+
+function onTabLoad (event) {
+ let win = event.target.defaultView;
+
+ // ignore frames
+ if (win === win.top) {
+ emit(this, 'load', this);
+ }
+}
+
+function onTabPageShow(event) {
+ let win = event.target.defaultView;
+ if (win === win.top)
+ emit(this, 'pageshow', this, event.persisted);
+}
+
+// TabClose
+function onTabClose(event) {
+ let rawTab = getTabForBrowser(event.target);
+ if (tabNS(this).tab !== rawTab)
+ return;
+
+ emit(this, EVENTS.close.name, this);
+ cleanupTab(this);
+};
+
+isPrivate.implement(Tab, tab => {
+ return isWindowPrivate(getTabContentWindow(tabNS(tab).tab));
+});
+
+// Implement `modelFor` function for the Tab instances.
+modelFor.when(isTab, rawTab => {
+ return rawTabNS(rawTab).tab;
+});
diff --git a/components/jetpack/sdk/tabs/tab-firefox.js b/components/jetpack/sdk/tabs/tab-firefox.js
new file mode 100644
index 000000000..f1da92379
--- /dev/null
+++ b/components/jetpack/sdk/tabs/tab-firefox.js
@@ -0,0 +1,353 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { Class } = require('../core/heritage');
+const { observer } = require('./observer');
+const { observer: windowObserver } = require('../windows/observer');
+const { addListItem, removeListItem } = require('../util/list');
+const { viewFor } = require('../view/core');
+const { modelFor } = require('../model/core');
+const { emit, setListeners } = require('../event/core');
+const { EventTarget } = require('../event/target');
+const { getBrowserForTab, setTabURL, getTabId, getTabURL, getTabForBrowser,
+ getTabs, getTabTitle, setTabTitle, getIndex, closeTab, reload, move,
+ activateTab, pin, unpin, isTab } = require('./utils');
+const { isBrowser, getInnerId, isWindowPrivate } = require('../window/utils');
+const { getThumbnailURIForWindow, BLANK } = require("../content/thumbnail");
+const { when } = require('../system/unload');
+const { ignoreWindow, isPrivate } = require('../private-browsing/utils')
+const { defer } = require('../lang/functional');
+const { getURL } = require('../url/utils');
+const { frames, remoteRequire } = require('../remote/parent');
+remoteRequire('sdk/content/tab-events');
+
+const modelsFor = new WeakMap();
+const viewsFor = new WeakMap();
+const destroyed = new WeakMap();
+
+const tabEvents = {};
+exports.tabEvents = tabEvents;
+
+function browser(tab) {
+ return getBrowserForTab(viewsFor.get(tab));
+}
+
+function isDestroyed(tab) {
+ return destroyed.has(tab);
+}
+
+function isClosed(tab) {
+ if (!viewsFor.has(tab))
+ return true;
+ return viewsFor.get(tab).closing;
+}
+
+// private tab attribute where the remote cached value is stored
+const remoteReadyStateCached = Symbol("remoteReadyStateCached");
+
+const Tab = Class({
+ implements: [EventTarget],
+ initialize: function(tabElement, options = null) {
+ modelsFor.set(tabElement, this);
+ viewsFor.set(this, tabElement);
+
+ if (options) {
+ EventTarget.prototype.initialize.call(this, options);
+
+ if (options.isPinned)
+ this.pin();
+
+ // Note that activate is defered and so will run after any open event
+ // is sent out
+ if (!options.inBackground)
+ this.activate();
+ }
+
+ getURL.implement(this, tab => tab.url);
+ isPrivate.implement(this, tab => {
+ return isWindowPrivate(viewsFor.get(tab).ownerDocument.defaultView);
+ });
+ },
+
+ get id() {
+ return isDestroyed(this) ? undefined : getTabId(viewsFor.get(this));
+ },
+
+ get title() {
+ return isDestroyed(this) ? undefined : getTabTitle(viewsFor.get(this));
+ },
+
+ set title(val) {
+ if (isDestroyed(this))
+ return;
+
+ setTabTitle(viewsFor.get(this), val);
+ },
+
+ get url() {
+ return isDestroyed(this) ? undefined : getTabURL(viewsFor.get(this));
+ },
+
+ set url(val) {
+ if (isDestroyed(this))
+ return;
+
+ setTabURL(viewsFor.get(this), val);
+ },
+
+ get contentType() {
+ return isDestroyed(this) ? undefined : browser(this).documentContentType;
+ },
+
+ get index() {
+ return isDestroyed(this) ? undefined : getIndex(viewsFor.get(this));
+ },
+
+ set index(val) {
+ if (isDestroyed(this))
+ return;
+
+ move(viewsFor.get(this), val);
+ },
+
+ get isPinned() {
+ return isDestroyed(this) ? undefined : viewsFor.get(this).pinned;
+ },
+
+ get window() {
+ if (isClosed(this))
+ return undefined;
+
+ // TODO: Remove the dependency on the windows module, see bug 792670
+ require('../windows');
+ let tabElement = viewsFor.get(this);
+ let domWindow = tabElement.ownerDocument.defaultView;
+ return modelFor(domWindow);
+ },
+
+ get readyState() {
+ return isDestroyed(this) ? undefined : this[remoteReadyStateCached] || "uninitialized";
+ },
+
+ pin: function() {
+ if (isDestroyed(this))
+ return;
+
+ pin(viewsFor.get(this));
+ },
+
+ unpin: function() {
+ if (isDestroyed(this))
+ return;
+
+ unpin(viewsFor.get(this));
+ },
+
+ close: function(callback) {
+ let tabElement = viewsFor.get(this);
+
+ if (isDestroyed(this) || !tabElement || !tabElement.parentNode) {
+ if (callback)
+ callback();
+ return;
+ }
+
+ this.once('close', () => {
+ this.destroy();
+ if (callback)
+ callback();
+ });
+
+ closeTab(tabElement);
+ },
+
+ reload: function() {
+ if (isDestroyed(this))
+ return;
+
+ reload(viewsFor.get(this));
+ },
+
+ activate: defer(function() {
+ if (isDestroyed(this))
+ return;
+
+ activateTab(viewsFor.get(this));
+ }),
+
+ getThumbnail: function() {
+ if (isDestroyed(this))
+ return BLANK;
+
+ // TODO: This is unimplemented in e10s: bug 1148601
+ if (browser(this).isRemoteBrowser) {
+ console.error('This method is not supported with E10S');
+ return BLANK;
+ }
+ return getThumbnailURIForWindow(browser(this).contentWindow);
+ },
+
+ attach: function(options) {
+ if (isDestroyed(this))
+ return;
+
+ let { Worker } = require('../content/worker');
+ let { connect, makeChildOptions } = require('../content/utils');
+
+ let worker = Worker(options);
+ worker.once("detach", () => {
+ worker.destroy();
+ });
+
+ let attach = frame => {
+ let childOptions = makeChildOptions(options);
+ frame.port.emit("sdk/tab/attach", childOptions);
+ connect(worker, frame, { id: childOptions.id, url: this.url });
+ };
+
+ // Do this synchronously if possible
+ let frame = frames.getFrameForBrowser(browser(this));
+ if (frame) {
+ attach(frame);
+ }
+ else {
+ let listener = (frame) => {
+ if (frame.frameElement != browser(this))
+ return;
+
+ frames.off("attach", listener);
+ attach(frame);
+ };
+ frames.on("attach", listener);
+ }
+
+ return worker;
+ },
+
+ destroy: function() {
+ if (isDestroyed(this))
+ return;
+
+ destroyed.set(this, true);
+ }
+});
+exports.Tab = Tab;
+
+viewFor.define(Tab, tab => viewsFor.get(tab));
+
+// Returns the high-level window for this DOM window if the windows module has
+// ever been loaded otherwise returns null
+function maybeWindowFor(domWindow) {
+ try {
+ return modelFor(domWindow);
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+function tabEmit(tab, event, ...args) {
+ // Don't emit events for destroyed tabs
+ if (isDestroyed(tab))
+ return;
+
+ // If the windows module was never loaded this will return null. We don't need
+ // to emit to the window.tabs object in this case as nothing can be listening.
+ let tabElement = viewsFor.get(tab);
+ let window = maybeWindowFor(tabElement.ownerDocument.defaultView);
+ if (window)
+ emit(window.tabs, event, tab, ...args);
+
+ emit(tabEvents, event, tab, ...args);
+ emit(tab, event, tab, ...args);
+}
+
+function windowClosed(domWindow) {
+ if (!isBrowser(domWindow))
+ return;
+
+ for (let tabElement of getTabs(domWindow)) {
+ tabEventListener("close", tabElement);
+ }
+}
+windowObserver.on('close', windowClosed);
+
+// Don't want to send close events after unloaded
+when(_ => {
+ windowObserver.off('close', windowClosed);
+});
+
+// Listen for tabbrowser events
+function tabEventListener(event, tabElement, ...args) {
+ let domWindow = tabElement.ownerDocument.defaultView;
+
+ if (ignoreWindow(domWindow))
+ return;
+
+ // Don't send events for tabs that are already closing
+ if (event != "close" && (tabElement.closing || !tabElement.parentNode))
+ return;
+
+ let tab = modelsFor.get(tabElement);
+ if (!tab)
+ tab = new Tab(tabElement);
+
+ let window = maybeWindowFor(domWindow);
+
+ if (event == "open") {
+ // Note, add to the window tabs first because if this is the first access to
+ // window.tabs it will be prefilling itself with everything from tabs
+ if (window)
+ addListItem(window.tabs, tab);
+ // The tabs module will take care of adding to its internal list
+ }
+ else if (event == "close") {
+ if (window)
+ removeListItem(window.tabs, tab);
+ // The tabs module will take care of removing from its internal list
+ }
+ else if (event == "init" || event == "create" || event == "ready" || event == "load") {
+ // Ignore load events from before browser windows have fully loaded, these
+ // are for about:blank in the initial tab
+ if (isBrowser(domWindow) && !domWindow.gBrowserInit.delayedStartupFinished)
+ return;
+
+ // update the cached remote readyState value
+ let { readyState } = args[0] || {};
+ tab[remoteReadyStateCached] = readyState;
+ }
+
+ if (event == "init") {
+ // Do not emit events for the detected existent tabs, we only need to cache
+ // their current document.readyState value.
+ return;
+ }
+
+ tabEmit(tab, event, ...args);
+
+ // The tab object shouldn't be reachable after closed
+ if (event == "close") {
+ viewsFor.delete(tab);
+ modelsFor.delete(tabElement);
+ }
+}
+observer.on('*', tabEventListener);
+
+// Listen for tab events from content
+frames.port.on('sdk/tab/event', (frame, event, ...args) => {
+ if (!frame.isTab)
+ return;
+
+ let tabElement = getTabForBrowser(frame.frameElement);
+ if (!tabElement)
+ return;
+
+ tabEventListener(event, tabElement, ...args);
+});
+
+// Implement `modelFor` function for the Tab instances..
+modelFor.when(isTab, view => {
+ return modelsFor.get(view);
+});
diff --git a/components/jetpack/sdk/tabs/tab.js b/components/jetpack/sdk/tabs/tab.js
new file mode 100644
index 000000000..fa2272494
--- /dev/null
+++ b/components/jetpack/sdk/tabs/tab.js
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const { getTargetWindow } = require("../content/mod");
+const { getTabContentWindow, isTab } = require("./utils");
+const { viewFor } = require("../view/core");
+
+if (require('../system/xul-app').name == 'Fennec') {
+ module.exports = require('./tab-fennec');
+}
+else {
+ module.exports = require('./tab-firefox');
+}
+
+getTargetWindow.when(isTab, tab => getTabContentWindow(tab));
+
+getTargetWindow.when(x => x instanceof module.exports.Tab,
+ tab => getTabContentWindow(viewFor(tab)));
diff --git a/components/jetpack/sdk/tabs/tabs-firefox.js b/components/jetpack/sdk/tabs/tabs-firefox.js
new file mode 100644
index 000000000..1eefecb4c
--- /dev/null
+++ b/components/jetpack/sdk/tabs/tabs-firefox.js
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { Class } = require('../core/heritage');
+const { Tab, tabEvents } = require('./tab');
+const { EventTarget } = require('../event/target');
+const { emit, setListeners } = require('../event/core');
+const { pipe } = require('../event/utils');
+const { observer: windowObserver } = require('../windows/observer');
+const { List, addListItem, removeListItem } = require('../util/list');
+const { modelFor } = require('../model/core');
+const { viewFor } = require('../view/core');
+const { getTabs, getSelectedTab } = require('./utils');
+const { getMostRecentBrowserWindow, isBrowser } = require('../window/utils');
+const { Options } = require('./common');
+const { isPrivate } = require('../private-browsing');
+const { ignoreWindow, isWindowPBSupported } = require('../private-browsing/utils')
+const { isPrivateBrowsingSupported } = require('sdk/self');
+
+const supportPrivateTabs = isPrivateBrowsingSupported && isWindowPBSupported;
+
+const Tabs = Class({
+ implements: [EventTarget],
+ extends: List,
+ initialize: function() {
+ List.prototype.initialize.call(this);
+
+ // We must do the list manipulation here where the object is extensible
+ this.on("open", tab => {
+ addListItem(this, tab);
+ });
+
+ this.on("close", tab => {
+ removeListItem(this, tab);
+ });
+ },
+
+ get activeTab() {
+ let activeDomWin = getMostRecentBrowserWindow();
+ if (!activeDomWin)
+ return null;
+ return modelFor(getSelectedTab(activeDomWin));
+ },
+
+ open: function(options) {
+ options = Options(options);
+
+ // TODO: Remove the dependency on the windows module: bug 792670
+ let windows = require('../windows').browserWindows;
+ let activeWindow = windows.activeWindow;
+
+ let privateState = supportPrivateTabs && options.isPrivate;
+ // When no isPrivate option was passed use the private state of the active
+ // window
+ if (activeWindow && privateState === undefined)
+ privateState = isPrivate(activeWindow);
+
+ function getWindow(privateState) {
+ for (let window of windows) {
+ if (privateState === isPrivate(window)) {
+ return window;
+ }
+ }
+ return null;
+ }
+
+ function openNewWindowWithTab() {
+ windows.open({
+ url: options.url,
+ isPrivate: privateState,
+ onOpen: function(newWindow) {
+ let tab = newWindow.tabs[0];
+ setListeners(tab, options);
+
+ if (options.isPinned)
+ tab.pin();
+
+ // We don't emit the open event for the first tab in a new window so
+ // do it now the listeners are attached
+ emit(tab, "open", tab);
+ }
+ });
+ }
+
+ if (options.inNewWindow)
+ return openNewWindowWithTab();
+
+ // if the active window is in the state that we need then use it
+ if (activeWindow && (privateState === isPrivate(activeWindow)))
+ return activeWindow.tabs.open(options);
+
+ // find a window in the state that we need
+ let window = getWindow(privateState);
+ if (window)
+ return window.tabs.open(options);
+
+ return openNewWindowWithTab();
+ }
+});
+
+const allTabs = new Tabs();
+// Export a new object with allTabs as the prototype, otherwise allTabs becomes
+// frozen and addListItem and removeListItem don't work correctly.
+module.exports = Object.create(allTabs);
+pipe(tabEvents, module.exports);
+
+function addWindowTab(window, tabElement) {
+ let tab = new Tab(tabElement);
+ if (window)
+ addListItem(window.tabs, tab);
+ addListItem(allTabs, tab);
+ emit(allTabs, "open", tab);
+}
+
+// Find tabs in already open windows
+for (let tabElement of getTabs())
+ addWindowTab(null, tabElement);
+
+// Detect tabs in new windows
+windowObserver.on('open', domWindow => {
+ if (!isBrowser(domWindow) || ignoreWindow(domWindow))
+ return;
+
+ let window = null;
+ try {
+ modelFor(domWindow);
+ }
+ catch (e) { }
+
+ for (let tabElement of getTabs(domWindow)) {
+ addWindowTab(window, tabElement);
+ }
+});
diff --git a/components/jetpack/sdk/tabs/utils.js b/components/jetpack/sdk/tabs/utils.js
new file mode 100644
index 000000000..eae3d41fe
--- /dev/null
+++ b/components/jetpack/sdk/tabs/utils.js
@@ -0,0 +1,370 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+
+// NOTE: This file should only deal with xul/native tabs
+
+
+const { Ci, Cu } = require('chrome');
+const { defer } = require("../lang/functional");
+const { windows, isBrowser } = require('../window/utils');
+const { isPrivateBrowsingSupported } = require('../self');
+const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm");
+
+// Bug 834961: ignore private windows when they are not supported
+function getWindows() {
+ return windows(null, { includePrivate: isPrivateBrowsingSupported });
+}
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+// Define predicate functions that can be used to detech weather
+// we deal with fennec tabs or firefox tabs.
+
+// Predicate to detect whether tab is XUL "Tab" node.
+const isXULTab = tab =>
+ tab instanceof Ci.nsIDOMNode &&
+ tab.nodeName === "tab" &&
+ tab.namespaceURI === XUL_NS;
+exports.isXULTab = isXULTab;
+
+// Predicate to detecet whether given tab is a fettec tab.
+// Unfortunately we have to guess via duck typinng of:
+// http://mxr.mozilla.org/mozilla-central/source/mobile/android/chrome/content/browser.js#2583
+const isFennecTab = tab =>
+ tab &&
+ tab.QueryInterface &&
+ Ci.nsIBrowserTab &&
+ tab.QueryInterface(Ci.nsIBrowserTab) === tab;
+exports.isFennecTab = isFennecTab;
+
+const isTab = x => isXULTab(x) || isFennecTab(x);
+exports.isTab = isTab;
+
+function activateTab(tab, window) {
+ let gBrowser = getTabBrowserForTab(tab);
+
+ // normal case
+ if (gBrowser) {
+ gBrowser.selectedTab = tab;
+ }
+ // fennec ?
+ else if (window && window.BrowserApp) {
+ window.BrowserApp.selectTab(tab);
+ }
+ return null;
+}
+exports.activateTab = activateTab;
+
+function getTabBrowser(window) {
+ // bug 1009938 - may be null in SeaMonkey
+ return window.gBrowser || window.getBrowser();
+}
+exports.getTabBrowser = getTabBrowser;
+
+function getTabContainer(window) {
+ return getTabBrowser(window).tabContainer;
+}
+exports.getTabContainer = getTabContainer;
+
+/**
+ * Returns the tabs for the `window` if given, or the tabs
+ * across all the browser's windows otherwise.
+ *
+ * @param {nsIWindow} [window]
+ * A reference to a window
+ *
+ * @returns {Array} an array of Tab objects
+ */
+function getTabs(window) {
+ if (arguments.length === 0) {
+ return getWindows().
+ filter(isBrowser).
+ reduce((tabs, window) => tabs.concat(getTabs(window)), []);
+ }
+
+ // fennec
+ if (window.BrowserApp)
+ return window.BrowserApp.tabs;
+
+ // firefox - default
+ return Array.filter(getTabContainer(window).children, t => !t.closing);
+}
+exports.getTabs = getTabs;
+
+function getActiveTab(window) {
+ return getSelectedTab(window);
+}
+exports.getActiveTab = getActiveTab;
+
+function getOwnerWindow(tab) {
+ // normal case
+ if (tab.ownerDocument)
+ return tab.ownerDocument.defaultView;
+
+ // try fennec case
+ return getWindowHoldingTab(tab);
+}
+exports.getOwnerWindow = getOwnerWindow;
+
+// fennec
+function getWindowHoldingTab(rawTab) {
+ for (let window of getWindows()) {
+ // this function may be called when not using fennec,
+ // but BrowserApp is only defined on Fennec
+ if (!window.BrowserApp)
+ continue;
+
+ for (let tab of window.BrowserApp.tabs) {
+ if (tab === rawTab)
+ return window;
+ }
+ }
+
+ return null;
+}
+
+function openTab(window, url, options) {
+ options = options || {};
+
+ // fennec?
+ if (window.BrowserApp) {
+ return window.BrowserApp.addTab(url, {
+ selected: options.inBackground ? false : true,
+ pinned: options.isPinned || false,
+ isPrivate: options.isPrivate || false,
+ parentId: window.BrowserApp.selectedTab.id
+ });
+ }
+
+ // firefox
+ let newTab = window.gBrowser.addTab(url);
+ if (!options.inBackground) {
+ activateTab(newTab);
+ }
+ return newTab;
+};
+exports.openTab = openTab;
+
+function isTabOpen(tab) {
+ // try normal case then fennec case
+ return !!((tab.linkedBrowser) || getWindowHoldingTab(tab));
+}
+exports.isTabOpen = isTabOpen;
+
+function closeTab(tab) {
+ let gBrowser = getTabBrowserForTab(tab);
+ // normal case?
+ if (gBrowser) {
+ // Bug 699450: the tab may already have been detached
+ if (!tab.parentNode)
+ return;
+ return gBrowser.removeTab(tab);
+ }
+
+ let window = getWindowHoldingTab(tab);
+ // fennec?
+ if (window && window.BrowserApp) {
+ // Bug 699450: the tab may already have been detached
+ if (!tab.browser)
+ return;
+ return window.BrowserApp.closeTab(tab);
+ }
+ return null;
+}
+exports.closeTab = closeTab;
+
+function getURI(tab) {
+ if (tab.browser) // fennec
+ return tab.browser.currentURI.spec;
+ return tab.linkedBrowser.currentURI.spec;
+}
+exports.getURI = getURI;
+
+function getTabBrowserForTab(tab) {
+ let outerWin = getOwnerWindow(tab);
+ if (outerWin)
+ return getOwnerWindow(tab).gBrowser;
+ return null;
+}
+exports.getTabBrowserForTab = getTabBrowserForTab;
+
+function getBrowserForTab(tab) {
+ if (tab.browser) // fennec
+ return tab.browser;
+
+ return tab.linkedBrowser;
+}
+exports.getBrowserForTab = getBrowserForTab;
+
+function getTabId(tab) {
+ if (tab.browser) // fennec
+ return tab.id
+
+ return String.split(tab.linkedPanel, 'panel').pop();
+}
+exports.getTabId = getTabId;
+
+function getTabForId(id) {
+ return getTabs().find(tab => getTabId(tab) === id) || null;
+}
+exports.getTabForId = getTabForId;
+
+function getTabTitle(tab) {
+ return getBrowserForTab(tab).contentTitle || tab.label || "";
+}
+exports.getTabTitle = getTabTitle;
+
+function setTabTitle(tab, title) {
+ title = String(title);
+ if (tab.browser) {
+ // Fennec
+ tab.browser.contentDocument.title = title;
+ }
+ else {
+ let browser = getBrowserForTab(tab);
+ // Note that we aren't actually setting the document title in e10s, just
+ // the title the browser thinks the content has
+ if (browser.isRemoteBrowser)
+ browser._contentTitle = title;
+ else
+ browser.contentDocument.title = title;
+ }
+ tab.label = String(title);
+}
+exports.setTabTitle = setTabTitle;
+
+function getTabContentDocument(tab) {
+ return getBrowserForTab(tab).contentDocument;
+}
+exports.getTabContentDocument = getTabContentDocument;
+
+function getTabContentWindow(tab) {
+ return getBrowserForTab(tab).contentWindow;
+}
+exports.getTabContentWindow = getTabContentWindow;
+
+/**
+ * Returns all tabs' content windows across all the browsers' windows
+ */
+function getAllTabContentWindows() {
+ return getTabs().map(getTabContentWindow);
+}
+exports.getAllTabContentWindows = getAllTabContentWindows;
+
+// gets the tab containing the provided window
+function getTabForContentWindow(window) {
+ return getTabs().find(tab => getTabContentWindow(tab) === window.top) || null;
+}
+exports.getTabForContentWindow = getTabForContentWindow;
+
+// only sdk/selection.js is relying on shims
+function getTabForContentWindowNoShim(window) {
+ function getTabContentWindowNoShim(tab) {
+ let browser = getBrowserForTab(tab);
+ return ShimWaiver.getProperty(browser, "contentWindow");
+ }
+ return getTabs().find(tab => getTabContentWindowNoShim(tab) === window.top) || null;
+}
+exports.getTabForContentWindowNoShim = getTabForContentWindowNoShim;
+
+function getTabURL(tab) {
+ return String(getBrowserForTab(tab).currentURI.spec);
+}
+exports.getTabURL = getTabURL;
+
+function setTabURL(tab, url) {
+ let browser = getBrowserForTab(tab);
+ browser.loadURI(String(url));
+}
+// "TabOpen" event is fired when it's still "about:blank" is loaded in the
+// changing `location` property of the `contentDocument` has no effect since
+// seems to be either ignored or overridden by internal listener, there for
+// location change is enqueued for the next turn of event loop.
+exports.setTabURL = defer(setTabURL);
+
+function getTabContentType(tab) {
+ return getBrowserForTab(tab).contentDocument.contentType;
+}
+exports.getTabContentType = getTabContentType;
+
+function getSelectedTab(window) {
+ if (window.BrowserApp) // fennec?
+ return window.BrowserApp.selectedTab;
+ if (window.gBrowser)
+ return window.gBrowser.selectedTab;
+ return null;
+}
+exports.getSelectedTab = getSelectedTab;
+
+
+function getTabForBrowser(browser) {
+ for (let window of getWindows()) {
+ // this function may be called when not using fennec
+ if (!window.BrowserApp)
+ continue;
+
+ for (let tab of window.BrowserApp.tabs) {
+ if (tab.browser === browser)
+ return tab;
+ }
+ }
+
+ let tabbrowser = browser.getTabBrowser && browser.getTabBrowser()
+ return !!tabbrowser && tabbrowser.getTabForBrowser(browser);
+}
+exports.getTabForBrowser = getTabForBrowser;
+
+function pin(tab) {
+ let gBrowser = getTabBrowserForTab(tab);
+ // TODO: Implement Fennec support
+ if (gBrowser) gBrowser.pinTab(tab);
+}
+exports.pin = pin;
+
+function unpin(tab) {
+ let gBrowser = getTabBrowserForTab(tab);
+ // TODO: Implement Fennec support
+ if (gBrowser) gBrowser.unpinTab(tab);
+}
+exports.unpin = unpin;
+
+function isPinned(tab) {
+ return !!tab.pinned;
+}
+exports.isPinned = isPinned;
+
+function reload(tab) {
+ getBrowserForTab(tab).reload();
+}
+exports.reload = reload
+
+function getIndex(tab) {
+ let gBrowser = getTabBrowserForTab(tab);
+ // Firefox
+ if (gBrowser) {
+ return tab._tPos;
+ }
+ // Fennec
+ else {
+ let window = getWindowHoldingTab(tab)
+ let tabs = window.BrowserApp.tabs;
+ for (let i = tabs.length; i >= 0; i--)
+ if (tabs[i] === tab) return i;
+ }
+}
+exports.getIndex = getIndex;
+
+function move(tab, index) {
+ let gBrowser = getTabBrowserForTab(tab);
+ // Firefox
+ if (gBrowser) gBrowser.moveTabTo(tab, index);
+ // TODO: Implement fennec support
+}
+exports.move = move;
diff --git a/components/jetpack/sdk/tabs/worker.js b/components/jetpack/sdk/tabs/worker.js
new file mode 100644
index 000000000..d2ba33696
--- /dev/null
+++ b/components/jetpack/sdk/tabs/worker.js
@@ -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/. */
+'use strict';
+
+const ContentWorker = require('../content/worker').Worker;
+
+function Worker(options, window) {
+ options.window = window;
+
+ let worker = ContentWorker(options);
+ worker.once("detach", function detach() {
+ worker.destroy();
+ });
+ return worker;
+}
+exports.Worker = Worker; \ No newline at end of file
diff --git a/components/jetpack/sdk/test.js b/components/jetpack/sdk/test.js
new file mode 100644
index 000000000..e7e3df840
--- /dev/null
+++ b/components/jetpack/sdk/test.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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cu } = require("chrome");
+const { Task } = require("resource://gre/modules/Task.jsm", {});
+const { defer } = require("sdk/core/promise");
+const BaseAssert = require("sdk/test/assert").Assert;
+const { isFunction, isObject, isGenerator } = require("sdk/lang/type");
+const { extend } = require("sdk/util/object");
+
+exports.Assert = BaseAssert;
+
+/**
+ * Function takes test `suite` object in CommonJS format and defines all of the
+ * tests from that suite and nested suites in a jetpack format on a given
+ * `target` object. Optionally third argument `prefix` can be passed to prefix
+ * all the test names.
+ */
+function defineTestSuite(target, suite, prefix) {
+ prefix = prefix || "";
+ // If suite defines `Assert` that's what `assert` object have to be created
+ // from and passed to a test function (This allows custom assertion functions)
+ // See for details: http://wiki.commonjs.org/wiki/Unit_Testing/1.1
+ let Assert = suite.Assert || BaseAssert;
+ // Going through each item in the test suite and wrapping it into a
+ // Jetpack test format.
+ Object.keys(suite).forEach(function(key) {
+ // If name starts with test then it's a test function or suite.
+ if (key.indexOf("test") === 0) {
+ let test = suite[key];
+
+ // For each test function so we create a wrapper test function in a
+ // jetpack format and copy that to a `target` exports.
+ if (isFunction(test)) {
+
+ // Since names of the test may match across suites we use full object
+ // path as a name to avoid overriding same function.
+ target[prefix + key] = function(options) {
+
+ // Creating `assert` functions for this test.
+ let assert = Assert(options);
+ assert.end = () => options.done();
+
+ // If test function is a generator use a task JS to allow yield-ing
+ // style test runs.
+ if (isGenerator(test)) {
+ options.waitUntilDone();
+ Task.spawn(test.bind(null, assert)).
+ catch(assert.fail).
+ then(assert.end);
+ }
+
+ // If CommonJS test function expects more than one argument
+ // it means that test is async and second argument is a callback
+ // to notify that test is finished.
+ else if (1 < test.length) {
+ // Letting test runner know that test is executed async and
+ // creating a callback function that CommonJS tests will call
+ // once it's done.
+ options.waitUntilDone();
+ test(assert, function() {
+ options.done();
+ });
+ }
+
+ // Otherwise CommonJS test is synchronous so we call it only with
+ // one argument.
+ else {
+ test(assert);
+ }
+ }
+ }
+
+ // If it's an object then it's a test suite containing test function
+ // and / or nested test suites. In that case we just extend prefix used
+ // and call this function to copy and wrap tests from nested suite.
+ else if (isObject(test)) {
+ // We need to clone `tests` instead of modifying it, since it's very
+ // likely that it is frozen (usually test suites imported modules).
+ test = extend(Object.prototype, test, {
+ Assert: test.Assert || Assert
+ });
+ defineTestSuite(target, test, prefix + key + ".");
+ }
+ }
+ });
+}
+
+/**
+ * This function is a CommonJS test runner function, but since Jetpack test
+ * runner and test format is different from CommonJS this function shims given
+ * `exports` with all its tests into a Jetpack test format so that the built-in
+ * test runner will be able to run CommonJS test without manual changes.
+ */
+exports.run = function run(exports) {
+ // We can't leave old properties on exports since those are test in a CommonJS
+ // format that why we move everything to a new `suite` object.
+ let suite = {};
+ Object.keys(exports).forEach(function(key) {
+ suite[key] = exports[key];
+ delete exports[key];
+ });
+
+ // Now we wrap all the CommonJS tests to a Jetpack format and define
+ // those to a given `exports` object since that where jetpack test runner
+ // will look for them.
+ defineTestSuite(exports, suite);
+};
diff --git a/components/jetpack/sdk/test/assert.js b/components/jetpack/sdk/test/assert.js
new file mode 100644
index 000000000..8478c8414
--- /dev/null
+++ b/components/jetpack/sdk/test/assert.js
@@ -0,0 +1,366 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { isFunction, isNull, isObject, isString,
+ isRegExp, isArray, isDate, isPrimitive,
+ isUndefined, instanceOf, source } = require("../lang/type");
+
+/**
+ * The `AssertionError` is defined in assert.
+ * @extends Error
+ * @example
+ * new assert.AssertionError({
+ * message: message,
+ * actual: actual,
+ * expected: expected
+ * })
+ */
+function AssertionError(options) {
+ let assertionError = Object.create(AssertionError.prototype);
+
+ if (isString(options))
+ options = { message: options };
+ if ("actual" in options)
+ assertionError.actual = options.actual;
+ if ("expected" in options)
+ assertionError.expected = options.expected;
+ if ("operator" in options)
+ assertionError.operator = options.operator;
+
+ assertionError.message = options.message;
+ assertionError.stack = new Error().stack;
+ return assertionError;
+}
+AssertionError.prototype = Object.create(Error.prototype, {
+ constructor: { value: AssertionError },
+ name: { value: "AssertionError", enumerable: true },
+ toString: { value: function toString() {
+ let value;
+ if (this.message) {
+ value = this.name + " : " + this.message;
+ }
+ else {
+ value = [
+ this.name + " : ",
+ source(this.expected),
+ this.operator,
+ source(this.actual)
+ ].join(" ");
+ }
+ return value;
+ }}
+});
+exports.AssertionError = AssertionError;
+
+function Assert(logger) {
+ let assert = Object.create(Assert.prototype, { _log: { value: logger }});
+
+ assert.fail = assert.fail.bind(assert);
+ assert.pass = assert.pass.bind(assert);
+
+ return assert;
+}
+
+Assert.prototype = {
+ fail: function fail(e) {
+ if (!e || typeof(e) !== 'object') {
+ this._log.fail(e);
+ return;
+ }
+ let message = e.message;
+ try {
+ if ('operator' in e) {
+ message += [
+ " -",
+ source(e.actual),
+ e.operator,
+ source(e.expected)
+ ].join(" ");
+ }
+ }
+ catch(e) {}
+ this._log.fail(message);
+ },
+ pass: function pass(message) {
+ this._log.pass(message);
+ return true;
+ },
+ error: function error(e) {
+ this._log.exception(e);
+ },
+ ok: function ok(value, message) {
+ if (!!!value) {
+ this.fail({
+ actual: value,
+ expected: true,
+ message: message,
+ operator: "=="
+ });
+ return false;
+ }
+
+ this.pass(message);
+ return true;
+ },
+
+ /**
+ * The equality assertion tests shallow, coercive equality with `==`.
+ * @example
+ * assert.equal(1, 1, "one is one");
+ */
+ equal: function equal(actual, expected, message) {
+ if (actual == expected) {
+ this.pass(message);
+ return true;
+ }
+
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "=="
+ });
+ return false;
+ },
+
+ /**
+ * The non-equality assertion tests for whether two objects are not equal
+ * with `!=`.
+ * @example
+ * assert.notEqual(1, 2, "one is not two");
+ */
+ notEqual: function notEqual(actual, expected, message) {
+ if (actual != expected) {
+ this.pass(message);
+ return true;
+ }
+
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "!=",
+ });
+ return false;
+ },
+
+ /**
+ * The equivalence assertion tests a deep (with `===`) equality relation.
+ * @example
+ * assert.deepEqual({ a: "foo" }, { a: "foo" }, "equivalent objects")
+ */
+ deepEqual: function deepEqual(actual, expected, message) {
+ if (isDeepEqual(actual, expected)) {
+ this.pass(message);
+ return true;
+ }
+
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "deepEqual"
+ });
+ return false;
+ },
+
+ /**
+ * The non-equivalence assertion tests for any deep (with `===`) inequality.
+ * @example
+ * assert.notDeepEqual({ a: "foo" }, Object.create({ a: "foo" }),
+ * "object's inherit from different prototypes");
+ */
+ notDeepEqual: function notDeepEqual(actual, expected, message) {
+ if (!isDeepEqual(actual, expected)) {
+ this.pass(message);
+ return true;
+ }
+
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "notDeepEqual"
+ });
+ return false;
+ },
+
+ /**
+ * The strict equality assertion tests strict equality, as determined by
+ * `===`.
+ * @example
+ * assert.strictEqual(null, null, "`null` is `null`")
+ */
+ strictEqual: function strictEqual(actual, expected, message) {
+ if (actual === expected) {
+ this.pass(message);
+ return true;
+ }
+
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "==="
+ });
+ return false;
+ },
+
+ /**
+ * The strict non-equality assertion tests for strict inequality, as
+ * determined by `!==`.
+ * @example
+ * assert.notStrictEqual(null, undefined, "`null` is not `undefined`");
+ */
+ notStrictEqual: function notStrictEqual(actual, expected, message) {
+ if (actual !== expected) {
+ this.pass(message);
+ return true;
+ }
+
+ this.fail({
+ actual: actual,
+ expected: expected,
+ message: message,
+ operator: "!=="
+ });
+ return false;
+ },
+
+ /**
+ * The assertion whether or not given `block` throws an exception. If optional
+ * `Error` argument is provided and it's type of function thrown error is
+ * asserted to be an instance of it, if type of `Error` is string then message
+ * of throw exception is asserted to contain it.
+ * @param {Function} block
+ * Function that is expected to throw.
+ * @param {Error|RegExp} [Error]
+ * Error constructor that is expected to be thrown or a string that
+ * must be contained by a message of the thrown exception, or a RegExp
+ * matching a message of the thrown exception.
+ * @param {String} message
+ * Description message
+ *
+ * @examples
+ *
+ * assert.throws(function block() {
+ * doSomething(4)
+ * }, "Object is expected", "Incorrect argument is passed");
+ *
+ * assert.throws(function block() {
+ * Object.create(5)
+ * }, TypeError, "TypeError is thrown");
+ */
+ throws: function throws(block, Error, message) {
+ let threw = false;
+ let exception = null;
+
+ // If third argument is not provided and second argument is a string it
+ // means that optional `Error` argument was not passed, so we shift
+ // arguments.
+ if (isString(Error) && isUndefined(message)) {
+ message = Error;
+ Error = undefined;
+ }
+
+ // Executing given `block`.
+ try {
+ block();
+ }
+ catch (e) {
+ threw = true;
+ exception = e;
+ }
+
+ // If exception was thrown and `Error` argument was not passed assert is
+ // passed.
+ if (threw && (isUndefined(Error) ||
+ // If passed `Error` is RegExp using it's test method to
+ // assert thrown exception message.
+ (isRegExp(Error) && (Error.test(exception.message) || Error.test(exception.toString()))) ||
+ // If passed `Error` is a constructor function testing if
+ // thrown exception is an instance of it.
+ (isFunction(Error) && instanceOf(exception, Error))))
+ {
+ this.pass(message);
+ return true;
+ }
+
+ // Otherwise we report assertion failure.
+ let failure = {
+ message: message,
+ operator: "matches"
+ };
+
+ if (exception) {
+ failure.actual = exception.message || exception.toString();
+ }
+
+ if (Error) {
+ failure.expected = Error.toString();
+ }
+
+ this.fail(failure);
+ return false;
+ }
+};
+exports.Assert = Assert;
+
+function isDeepEqual(actual, expected) {
+ // 7.1. All identical values are equivalent, as determined by ===.
+ if (actual === expected) {
+ return true;
+ }
+
+ // 7.2. If the expected value is a Date object, the actual value is
+ // equivalent if it is also a Date object that refers to the same time.
+ else if (isDate(actual) && isDate(expected)) {
+ return actual.getTime() === expected.getTime();
+ }
+
+ // XXX specification bug: this should be specified
+ else if (isPrimitive(actual) || isPrimitive(expected)) {
+ return expected === actual;
+ }
+
+ // 7.3. Other pairs that do not both pass typeof value == "object",
+ // equivalence is determined by ==.
+ else if (!isObject(actual) && !isObject(expected)) {
+ return actual == expected;
+ }
+
+ // 7.4. For all other Object pairs, including Array objects, equivalence is
+ // determined by having the same number of owned properties (as verified
+ // with Object.prototype.hasOwnProperty.call), the same set of keys
+ // (although not necessarily the same order), equivalent values for every
+ // corresponding key, and an identical "prototype" property. Note: this
+ // accounts for both named and indexed properties on Arrays.
+ else {
+ return actual.prototype === expected.prototype &&
+ isEquivalent(actual, expected);
+ }
+}
+
+function isEquivalent(a, b, stack) {
+ let aKeys = Object.keys(a);
+ let bKeys = Object.keys(b);
+
+ return aKeys.length === bKeys.length &&
+ isArrayEquivalent(aKeys.sort(), bKeys.sort()) &&
+ aKeys.every(function(key) {
+ return isDeepEqual(a[key], b[key], stack)
+ });
+}
+
+function isArrayEquivalent(a, b, stack) {
+ return isArray(a) && isArray(b) &&
+ a.every(function(value, index) {
+ return isDeepEqual(value, b[index]);
+ });
+}
diff --git a/components/jetpack/sdk/test/harness.js b/components/jetpack/sdk/test/harness.js
new file mode 100644
index 000000000..1b31a1c79
--- /dev/null
+++ b/components/jetpack/sdk/test/harness.js
@@ -0,0 +1,645 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci, Cu } = require("chrome");
+const { Loader } = require('./loader');
+const { serializeStack, parseStack } = require("toolkit/loader");
+const { setTimeout } = require('../timers');
+const { PlainTextConsole } = require("../console/plain-text");
+const { when: unload } = require("../system/unload");
+const { format, fromException } = require("../console/traceback");
+const system = require("../system");
+const { gc: gcPromise } = require('./memory');
+const { defer } = require('../core/promise');
+const { extend } = require('../core/heritage');
+
+// Trick manifest builder to make it think we need these modules ?
+const unit = require("../deprecated/unit-test");
+const test = require("../../test");
+const url = require("../url");
+
+function emptyPromise() {
+ let { promise, resolve } = defer();
+ resolve();
+ return promise;
+}
+
+var cService = Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService);
+
+// The console used to log messages
+var testConsole;
+
+// Cuddlefish loader in which we load and execute tests.
+var loader;
+
+// Function to call when we're done running tests.
+var onDone;
+
+// Function to print text to a console, w/o CR at the end.
+var print;
+
+// How many more times to run all tests.
+var iterationsLeft;
+
+// Whether to report memory profiling information.
+var profileMemory;
+
+// Whether we should stop as soon as a test reports a failure.
+var stopOnError;
+
+// Function to call to retrieve a list of tests to execute
+var findAndRunTests;
+
+// Combined information from all test runs.
+var results;
+
+// A list of the compartments and windows loaded after startup
+var startLeaks;
+
+// JSON serialization of last memory usage stats; we keep it stringified
+// so we don't actually change the memory usage stats (in terms of objects)
+// of the JSRuntime we're profiling.
+var lastMemoryUsage;
+
+function analyzeRawProfilingData(data) {
+ var graph = data.graph;
+ var shapes = {};
+
+ // Convert keys in the graph from strings to ints.
+ // TODO: Can we get rid of this ridiculousness?
+ var newGraph = {};
+ for (id in graph) {
+ newGraph[parseInt(id)] = graph[id];
+ }
+ graph = newGraph;
+
+ var modules = 0;
+ var moduleIds = [];
+ var moduleObjs = {UNKNOWN: 0};
+ for (let name in data.namedObjects) {
+ moduleObjs[name] = 0;
+ moduleIds[data.namedObjects[name]] = name;
+ modules++;
+ }
+
+ var count = 0;
+ for (id in graph) {
+ var parent = graph[id].parent;
+ while (parent) {
+ if (parent in moduleIds) {
+ var name = moduleIds[parent];
+ moduleObjs[name]++;
+ break;
+ }
+ if (!(parent in graph)) {
+ moduleObjs.UNKNOWN++;
+ break;
+ }
+ parent = graph[parent].parent;
+ }
+ count++;
+ }
+
+ print("\nobject count is " + count + " in " + modules + " modules" +
+ " (" + data.totalObjectCount + " across entire JS runtime)\n");
+ if (lastMemoryUsage) {
+ var last = JSON.parse(lastMemoryUsage);
+ var diff = {
+ moduleObjs: dictDiff(last.moduleObjs, moduleObjs),
+ totalObjectClasses: dictDiff(last.totalObjectClasses,
+ data.totalObjectClasses)
+ };
+
+ for (let name in diff.moduleObjs)
+ print(" " + diff.moduleObjs[name] + " in " + name + "\n");
+ for (let name in diff.totalObjectClasses)
+ print(" " + diff.totalObjectClasses[name] + " instances of " +
+ name + "\n");
+ }
+ lastMemoryUsage = JSON.stringify(
+ {moduleObjs: moduleObjs,
+ totalObjectClasses: data.totalObjectClasses}
+ );
+}
+
+function dictDiff(last, curr) {
+ var diff = {};
+
+ for (let name in last) {
+ var result = (curr[name] || 0) - last[name];
+ if (result)
+ diff[name] = (result > 0 ? "+" : "") + result;
+ }
+ for (let name in curr) {
+ var result = curr[name] - (last[name] || 0);
+ if (result)
+ diff[name] = (result > 0 ? "+" : "") + result;
+ }
+ return diff;
+}
+
+function reportMemoryUsage() {
+ if (!profileMemory) {
+ return emptyPromise();
+ }
+
+ return gcPromise().then((() => {
+ var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
+ .getService(Ci.nsIMemoryReporterManager);
+ let count = 0;
+ function logReporter(process, path, kind, units, amount, description) {
+ print(((++count == 1) ? "\n" : "") + description + ": " + amount + "\n");
+ }
+ mgr.getReportsForThisProcess(logReporter, null, /* anonymize = */ false);
+ }));
+}
+
+var gWeakrefInfo;
+
+function checkMemory() {
+ return gcPromise().then(_ => {
+ let leaks = getPotentialLeaks();
+
+ let compartmentURLs = Object.keys(leaks.compartments).filter(function(url) {
+ return !(url in startLeaks.compartments);
+ });
+
+ let windowURLs = Object.keys(leaks.windows).filter(function(url) {
+ return !(url in startLeaks.windows);
+ });
+
+ for (let url of compartmentURLs)
+ console.warn("LEAKED", leaks.compartments[url]);
+
+ for (let url of windowURLs)
+ console.warn("LEAKED", leaks.windows[url]);
+ }).then(showResults);
+}
+
+function showResults() {
+ let { promise, resolve } = defer();
+
+ if (gWeakrefInfo) {
+ gWeakrefInfo.forEach(
+ function(info) {
+ var ref = info.weakref.get();
+ if (ref !== null) {
+ var data = ref.__url__ ? ref.__url__ : ref;
+ var warning = data == "[object Object]"
+ ? "[object " + data.constructor.name + "(" +
+ Object.keys(data).join(", ") + ")]"
+ : data;
+ console.warn("LEAK", warning, info.bin);
+ }
+ }
+ );
+ }
+
+ onDone(results);
+
+ resolve();
+ return promise;
+}
+
+function cleanup() {
+ let coverObject = {};
+ try {
+ loader.unload();
+
+ if (loader.globals.console.errorsLogged && !results.failed) {
+ results.failed++;
+ console.error("warnings and/or errors were logged.");
+ }
+
+ if (consoleListener.errorsLogged && !results.failed) {
+ console.warn(consoleListener.errorsLogged + " " +
+ "warnings or errors were logged to the " +
+ "platform's nsIConsoleService, which could " +
+ "be of no consequence; however, they could also " +
+ "be indicative of aberrant behavior.");
+ }
+
+ // read the code coverage object, if it exists, from CoverJS-moz
+ if (typeof loader.globals.global == "object") {
+ coverObject = loader.globals.global['__$coverObject'] || {};
+ }
+
+ consoleListener.errorsLogged = 0;
+ loader = null;
+
+ consoleListener.unregister();
+
+ Cu.forceGC();
+ }
+ catch (e) {
+ results.failed++;
+ console.error("unload.send() threw an exception.");
+ console.exception(e);
+ };
+
+ setTimeout(require("./options").checkMemory ? checkMemory : showResults, 1);
+
+ // dump the coverobject
+ if (Object.keys(coverObject).length){
+ const self = require('sdk/self');
+ const {pathFor} = require("sdk/system");
+ let file = require('sdk/io/file');
+ const {env} = require('sdk/system/environment');
+ console.log("CWD:", env.PWD);
+ let out = file.join(env.PWD,'coverstats-'+self.id+'.json');
+ console.log('coverstats:', out);
+ let outfh = file.open(out,'w');
+ outfh.write(JSON.stringify(coverObject,null,2));
+ outfh.flush();
+ outfh.close();
+ }
+}
+
+function getPotentialLeaks() {
+ Cu.forceGC();
+
+ // Things we can assume are part of the platform and so aren't leaks
+ let GOOD_BASE_URLS = [
+ "chrome://",
+ "resource:///",
+ "resource://app/",
+ "resource://gre/",
+ "resource://gre-resources/",
+ "resource://pdf.js/",
+ "resource://pdf.js.components/",
+ "resource://services-common/",
+ "resource://services-crypto/",
+ "resource://services-sync/"
+ ];
+
+ let ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ let uri = ioService.newURI("chrome://global/content/", "UTF-8", null);
+ let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Ci.nsIChromeRegistry);
+ uri = chromeReg.convertChromeURL(uri);
+ let spec = uri.spec;
+ let pos = spec.indexOf("!/");
+ GOOD_BASE_URLS.push(spec.substring(0, pos + 2));
+
+ let zoneRegExp = new RegExp("^explicit/js-non-window/zones/zone[^/]+/compartment\\((.+)\\)");
+ let compartmentRegexp = new RegExp("^explicit/js-non-window/compartments/non-window-global/compartment\\((.+)\\)/");
+ let compartmentDetails = new RegExp("^([^,]+)(?:, (.+?))?(?: \\(from: (.*)\\))?$");
+ let windowRegexp = new RegExp("^explicit/window-objects/top\\((.*)\\)/active");
+ let windowDetails = new RegExp("^(.*), id=.*$");
+
+ function isPossibleLeak(item) {
+ if (!item.location)
+ return false;
+
+ for (let url of GOOD_BASE_URLS) {
+ if (item.location.substring(0, url.length) == url) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ let compartments = {};
+ let windows = {};
+ function logReporter(process, path, kind, units, amount, description) {
+ let matches;
+
+ if ((matches = compartmentRegexp.exec(path)) || (matches = zoneRegExp.exec(path))) {
+ if (matches[1] in compartments)
+ return;
+
+ let details = compartmentDetails.exec(matches[1]);
+ if (!details) {
+ console.error("Unable to parse compartment detail " + matches[1]);
+ return;
+ }
+
+ let item = {
+ path: matches[1],
+ principal: details[1],
+ location: details[2] ? details[2].replace(/\\/g, "/") : undefined,
+ source: details[3] ? details[3].split(" -> ").reverse() : undefined,
+ toString: function() {
+ return this.location;
+ }
+ };
+
+ if (!isPossibleLeak(item))
+ return;
+
+ compartments[matches[1]] = item;
+ return;
+ }
+
+ if ((matches = windowRegexp.exec(path))) {
+ if (matches[1] in windows)
+ return;
+
+ let details = windowDetails.exec(matches[1]);
+ if (!details) {
+ console.error("Unable to parse window detail " + matches[1]);
+ return;
+ }
+
+ let item = {
+ path: matches[1],
+ location: details[1].replace(/\\/g, "/"),
+ source: [details[1].replace(/\\/g, "/")],
+ toString: function() {
+ return this.location;
+ }
+ };
+
+ if (!isPossibleLeak(item))
+ return;
+
+ windows[matches[1]] = item;
+ }
+ }
+
+ Cc["@mozilla.org/memory-reporter-manager;1"]
+ .getService(Ci.nsIMemoryReporterManager)
+ .getReportsForThisProcess(logReporter, null, /* anonymize = */ false);
+
+ return { compartments: compartments, windows: windows };
+}
+
+function nextIteration(tests) {
+ if (tests) {
+ results.passed += tests.passed;
+ results.failed += tests.failed;
+
+ reportMemoryUsage().then(_ => {
+ let testRun = [];
+ for (let test of tests.testRunSummary) {
+ let testCopy = {};
+ for (let info in test) {
+ testCopy[info] = test[info];
+ }
+ testRun.push(testCopy);
+ }
+
+ results.testRuns.push(testRun);
+ iterationsLeft--;
+
+ checkForEnd();
+ })
+ }
+ else {
+ checkForEnd();
+ }
+}
+
+function checkForEnd() {
+ if (iterationsLeft && (!stopOnError || results.failed == 0)) {
+ // Pass the loader which has a hooked console that doesn't dispatch
+ // errors to the JS console and avoid firing false alarm in our
+ // console listener
+ findAndRunTests(loader, nextIteration);
+ }
+ else {
+ setTimeout(cleanup, 0);
+ }
+}
+
+var POINTLESS_ERRORS = [
+ 'Invalid chrome URI:',
+ 'OpenGL LayerManager Initialized Succesfully.',
+ '[JavaScript Error: "TelemetryStopwatch:',
+ 'reference to undefined property',
+ '[JavaScript Error: "The character encoding of the HTML document was ' +
+ 'not declared.',
+ '[Javascript Warning: "Error: Failed to preserve wrapper of wrapped ' +
+ 'native weak map key',
+ '[JavaScript Warning: "Duplicate resource declaration for',
+ 'file: "chrome://browser/content/',
+ 'file: "chrome://global/content/',
+ '[JavaScript Warning: "The character encoding of a framed document was ' +
+ 'not declared.',
+ 'file: "chrome://browser/skin/'
+];
+
+// These are messages that will cause a test to fail if logged through the
+// console service
+var IMPORTANT_ERRORS = [
+ 'Sending message that cannot be cloned. Are you trying to send an XPCOM object?',
+];
+
+var consoleListener = {
+ registered: false,
+
+ register: function() {
+ if (this.registered)
+ return;
+ cService.registerListener(this);
+ this.registered = true;
+ },
+
+ unregister: function() {
+ if (!this.registered)
+ return;
+ cService.unregisterListener(this);
+ this.registered = false;
+ },
+
+ errorsLogged: 0,
+
+ observe: function(object) {
+ if (!(object instanceof Ci.nsIScriptError))
+ return;
+ this.errorsLogged++;
+ var message = object.QueryInterface(Ci.nsIConsoleMessage).message;
+ if (IMPORTANT_ERRORS.find(msg => message.indexOf(msg) >= 0)) {
+ testConsole.error(message);
+ return;
+ }
+ var pointless = POINTLESS_ERRORS.filter(err => message.indexOf(err) >= 0);
+ if (pointless.length == 0 && message)
+ testConsole.log(message);
+ }
+};
+
+function TestRunnerConsole(base, options) {
+ let proto = extend(base, {
+ errorsLogged: 0,
+ warn: function warn() {
+ this.errorsLogged++;
+ base.warn.apply(base, arguments);
+ },
+ error: function error() {
+ this.errorsLogged++;
+ base.error.apply(base, arguments);
+ },
+ info: function info(first) {
+ if (options.verbose)
+ base.info.apply(base, arguments);
+ else
+ if (first == "pass:")
+ print(".");
+ },
+ });
+ return Object.create(proto);
+}
+
+function stringify(arg) {
+ try {
+ return String(arg);
+ }
+ catch(ex) {
+ return "<toString() error>";
+ }
+}
+
+function stringifyArgs(args) {
+ return Array.map(args, stringify).join(" ");
+}
+
+function TestRunnerTinderboxConsole(base, options) {
+ this.base = base;
+ this.print = options.print;
+ this.verbose = options.verbose;
+ this.errorsLogged = 0;
+
+ // Binding all the public methods to an instance so that they can be used
+ // as callback / listener functions straightaway.
+ this.log = this.log.bind(this);
+ this.info = this.info.bind(this);
+ this.warn = this.warn.bind(this);
+ this.error = this.error.bind(this);
+ this.debug = this.debug.bind(this);
+ this.exception = this.exception.bind(this);
+ this.trace = this.trace.bind(this);
+};
+
+TestRunnerTinderboxConsole.prototype = {
+ testMessage: function testMessage(pass, expected, test, message) {
+ let type = "TEST-";
+ if (expected) {
+ if (pass)
+ type += "PASS";
+ else
+ type += "KNOWN-FAIL";
+ }
+ else {
+ this.errorsLogged++;
+ if (pass)
+ type += "UNEXPECTED-PASS";
+ else
+ type += "UNEXPECTED-FAIL";
+ }
+
+ this.print(type + " | " + test + " | " + message + "\n");
+ if (!expected)
+ this.trace();
+ },
+
+ log: function log() {
+ this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
+ },
+
+ info: function info(first) {
+ this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
+ },
+
+ warn: function warn() {
+ this.errorsLogged++;
+ this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n");
+ },
+
+ error: function error() {
+ this.errorsLogged++;
+ this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n");
+ this.base.error.apply(this.base, arguments);
+ },
+
+ debug: function debug() {
+ this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
+ },
+
+ exception: function exception(e) {
+ this.print("An exception occurred.\n" +
+ require("../console/traceback").format(e) + "\n" + e + "\n");
+ },
+
+ trace: function trace() {
+ var traceback = require("../console/traceback");
+ var stack = traceback.get();
+ stack.splice(-1, 1);
+ this.print("TEST-INFO | " + stringify(traceback.format(stack)) + "\n");
+ }
+};
+
+var runTests = exports.runTests = function runTests(options) {
+ iterationsLeft = options.iterations;
+ profileMemory = options.profileMemory;
+ stopOnError = options.stopOnError;
+ onDone = options.onDone;
+ print = options.print;
+ findAndRunTests = options.findAndRunTests;
+
+ results = {
+ passed: 0,
+ failed: 0,
+ testRuns: []
+ };
+
+ try {
+ consoleListener.register();
+ print("Running tests on " + system.name + " " + system.version +
+ "/Gecko " + system.platformVersion + " (Build " +
+ system.build + ") (" + system.id + ") under " +
+ system.platform + "/" + system.architecture + ".\n");
+
+ if (options.parseable)
+ testConsole = new TestRunnerTinderboxConsole(new PlainTextConsole(), options);
+ else
+ testConsole = new TestRunnerConsole(new PlainTextConsole(), options);
+
+ loader = Loader(module, {
+ console: testConsole,
+ global: {} // useful for storing things like coverage testing.
+ });
+
+ // Load these before getting initial leak stats as they will still be in
+ // memory when we check later
+ require("../deprecated/unit-test");
+ require("../deprecated/unit-test-finder");
+ if (profileMemory)
+ startLeaks = getPotentialLeaks();
+
+ nextIteration();
+ } catch (e) {
+ let frames = fromException(e).reverse().reduce(function(frames, frame) {
+ if (frame.fileName.split("/").pop() === "unit-test-finder.js")
+ frames.done = true
+ if (!frames.done) frames.push(frame)
+
+ return frames
+ }, [])
+
+ let prototype = typeof(e) === "object" ? e.constructor.prototype :
+ Error.prototype;
+ let stack = serializeStack(frames.reverse());
+
+ let error = Object.create(prototype, {
+ message: { value: e.message, writable: true, configurable: true },
+ fileName: { value: e.fileName, writable: true, configurable: true },
+ lineNumber: { value: e.lineNumber, writable: true, configurable: true },
+ stack: { value: stack, writable: true, configurable: true },
+ toString: { value: () => String(e), writable: true, configurable: true },
+ });
+
+ print("Error: " + error + " \n " + format(error));
+ onDone({passed: 0, failed: 1});
+ }
+};
+
+unload(_ => consoleListener.unregister());
diff --git a/components/jetpack/sdk/test/httpd.js b/components/jetpack/sdk/test/httpd.js
new file mode 100644
index 000000000..218493924
--- /dev/null
+++ b/components/jetpack/sdk/test/httpd.js
@@ -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/. */
+
+throw new Error(`This file was removed. A copy can be obtained from:
+ https://github.com/mozilla/addon-sdk/blob/master/test/lib/httpd.js`);
diff --git a/components/jetpack/sdk/test/loader.js b/components/jetpack/sdk/test/loader.js
new file mode 100644
index 000000000..33ba2ca5a
--- /dev/null
+++ b/components/jetpack/sdk/test/loader.js
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { resolveURI, Require,
+ unload, override, descriptor } = require('../../toolkit/loader');
+const { ensure } = require('../system/unload');
+const addonWindow = require('../addon/window');
+const { PlainTextConsole } = require('sdk/console/plain-text');
+
+var defaultGlobals = override(require('../system/globals'), {
+ console: console
+});
+
+function CustomLoader(module, globals, packaging, overrides={}) {
+ let options = packaging || require("@loader/options");
+ options = override(options, {
+ id: overrides.id || options.id,
+ globals: override(defaultGlobals, globals || {}),
+ modules: override(override(options.modules || {}, overrides.modules || {}), {
+ 'sdk/addon/window': addonWindow
+ })
+ });
+
+ let loaderModule = options.isNative ? '../../toolkit/loader' : '../loader/cuddlefish';
+ let { Loader } = require(loaderModule);
+ let loader = Loader(options);
+ let wrapper = Object.create(loader, descriptor({
+ require: Require(loader, module),
+ sandbox: function(id) {
+ let requirement = loader.resolve(id, module.id);
+ if (!requirement)
+ requirement = id;
+ let uri = resolveURI(requirement, loader.mapping);
+ return loader.sandboxes[uri];
+ },
+ unload: function(reason) {
+ unload(loader, reason);
+ }
+ }));
+ ensure(wrapper);
+ return wrapper;
+};
+exports.Loader = CustomLoader;
+
+function HookedPlainTextConsole(hook, print, innerID) {
+ this.log = hook.bind(null, "log", innerID);
+ this.info = hook.bind(null, "info", innerID);
+ this.warn = hook.bind(null, "warn", innerID);
+ this.error = hook.bind(null, "error", innerID);
+ this.debug = hook.bind(null, "debug", innerID);
+ this.exception = hook.bind(null, "exception", innerID);
+ this.time = hook.bind(null, "time", innerID);
+ this.timeEnd = hook.bind(null, "timeEnd", innerID);
+
+ this.__exposedProps__ = {
+ log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
+ exception: "rw", time: "rw", timeEnd: "rw"
+ };
+}
+
+// Creates a custom loader instance whose console module is hooked in order
+// to avoid printing messages to the console, and instead, expose them in the
+// returned `messages` array attribute
+exports.LoaderWithHookedConsole = function (module, callback) {
+ let messages = [];
+ function hook(type, innerID, msg) {
+ messages.push({ type: type, msg: msg, innerID: innerID });
+ if (callback)
+ callback(type, msg, innerID);
+ }
+
+ return {
+ loader: CustomLoader(module, {
+ console: new HookedPlainTextConsole(hook, null, null)
+ }, null, {
+ modules: {
+ 'sdk/console/plain-text': {
+ PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
+ }
+ }
+ }),
+ messages: messages
+ };
+}
+
+// Same than LoaderWithHookedConsole with lower level, instead we get what is
+// actually printed to the command line console
+exports.LoaderWithHookedConsole2 = function (module, callback) {
+ let messages = [];
+ return {
+ loader: CustomLoader(module, {
+ console: new PlainTextConsole(function (msg) {
+ messages.push(msg);
+ if (callback)
+ callback(msg);
+ })
+ }),
+ messages: messages
+ };
+}
+
+// Creates a custom loader with a filtered console. The callback is passed every
+// console message type and message and if it returns false the message will
+// not be logged normally
+exports.LoaderWithFilteredConsole = function (module, callback) {
+ function hook(type, innerID, msg) {
+ if (callback && callback(type, msg, innerID) == false)
+ return;
+ console[type](msg);
+ }
+
+ return CustomLoader(module, {
+ console: new HookedPlainTextConsole(hook, null, null)
+ }, null, {
+ modules: {
+ 'sdk/console/plain-text': {
+ PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
+ }
+ }
+ });
+}
diff --git a/components/jetpack/sdk/test/memory.js b/components/jetpack/sdk/test/memory.js
new file mode 100644
index 000000000..bd1198bfe
--- /dev/null
+++ b/components/jetpack/sdk/test/memory.js
@@ -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/. */
+'use strict';
+
+const { Cu } = require("chrome");
+
+function gc() {
+ return new Promise(resolve => Cu.schedulePreciseGC(resolve));
+}
+exports.gc = gc;
diff --git a/components/jetpack/sdk/test/options.js b/components/jetpack/sdk/test/options.js
new file mode 100644
index 000000000..9bc611ca5
--- /dev/null
+++ b/components/jetpack/sdk/test/options.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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const options = require("@test/options");
+const { id } = require("../self");
+const { get } = require("../preferences/service");
+
+const readPref = (key) => get("extensions." + id + ".sdk." + key);
+
+exports.iterations = readPref("test.iterations") || options.iterations;
+exports.filter = readPref("test.filter") || options.filter;
+exports.profileMemory = readPref("profile.memory") || options.profileMemory;
+exports.stopOnError = readPref("test.stop") || options.stopOnError;
+exports.keepOpen = readPref("test.keepOpen") || false;
+exports.verbose = (readPref("output.logLevel") == "verbose") || options.verbose;
+exports.parseable = (readPref("output.format") == "tbpl") || options.parseable;
+exports.checkMemory = readPref("profile.leaks") || options.check_memory;
diff --git a/components/jetpack/sdk/test/runner.js b/components/jetpack/sdk/test/runner.js
new file mode 100644
index 000000000..ea37ac84f
--- /dev/null
+++ b/components/jetpack/sdk/test/runner.js
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+var { exit, stdout } = require("../system");
+var cfxArgs = require("../test/options");
+var events = require("../system/events");
+const { resolve } = require("../core/promise");
+
+function runTests(findAndRunTests) {
+ var harness = require("./harness");
+
+ function onDone(tests) {
+ stdout.write("\n");
+ var total = tests.passed + tests.failed;
+ stdout.write(tests.passed + " of " + total + " tests passed.\n");
+
+ events.emit("sdk:test:results", { data: JSON.stringify(tests) });
+
+ if (tests.failed == 0) {
+ if (tests.passed === 0)
+ stdout.write("No tests were run\n");
+ if (!cfxArgs.keepOpen)
+ exit(0);
+ } else {
+ if (cfxArgs.verbose || cfxArgs.parseable)
+ printFailedTests(tests, stdout.write);
+ if (!cfxArgs.keepOpen)
+ exit(1);
+ }
+ };
+
+ // We may have to run test on next cycle, otherwise XPCOM components
+ // are not correctly updated.
+ // For ex: nsIFocusManager.getFocusedElementForWindow may throw
+ // NS_ERROR_ILLEGAL_VALUE exception.
+ require("../timers").setTimeout(_ => harness.runTests({
+ findAndRunTests: findAndRunTests,
+ iterations: cfxArgs.iterations || 1,
+ filter: cfxArgs.filter,
+ profileMemory: cfxArgs.profileMemory,
+ stopOnError: cfxArgs.stopOnError,
+ verbose: cfxArgs.verbose,
+ parseable: cfxArgs.parseable,
+ print: stdout.write,
+ onDone: onDone
+ }));
+}
+
+function printFailedTests(tests, print) {
+ let iterationNumber = 0;
+ let singleIteration = (tests.testRuns || []).length == 1;
+ let padding = singleIteration ? "" : " ";
+
+ print("\nThe following tests failed:\n");
+
+ for (let testRun of tests.testRuns) {
+ iterationNumber++;
+
+ if (!singleIteration)
+ print(" Iteration " + iterationNumber + ":\n");
+
+ for (let test of testRun) {
+ if (test.failed > 0) {
+ print(padding + " " + test.name + ": " + test.errors +"\n");
+ }
+ }
+ print("\n");
+ }
+}
+
+function main() {
+ var testsStarted = false;
+
+ if (!testsStarted) {
+ testsStarted = true;
+ runTests(function findAndRunTests(loader, nextIteration) {
+ loader.require("../deprecated/unit-test").findAndRunTests({
+ testOutOfProcess: false,
+ testInProcess: true,
+ stopOnError: cfxArgs.stopOnError,
+ filter: cfxArgs.filter,
+ onDone: nextIteration
+ });
+ });
+ }
+};
+
+if (require.main === module)
+ main();
+
+exports.runTestsFromModule = function runTestsFromModule(module) {
+ let id = module.id;
+ // Make a copy of exports as it may already be frozen by module loader
+ let exports = {};
+ Object.keys(module.exports).forEach(key => {
+ exports[key] = module.exports[key];
+ });
+
+ runTests(function findAndRunTests(loader, nextIteration) {
+ // Consider that all these tests are CommonJS ones
+ loader.require('../../test').run(exports);
+
+ // Reproduce what is done in sdk/deprecated/unit-test-finder.findTests()
+ let tests = [];
+ for (let name of Object.keys(exports).sort()) {
+ tests.push({
+ setup: exports.setup,
+ teardown: exports.teardown,
+ testFunction: exports[name],
+ name: id + "." + name
+ });
+ }
+
+ // Reproduce what is done by unit-test.findAndRunTests()
+ var { TestRunner } = loader.require("../deprecated/unit-test");
+ var runner = new TestRunner();
+ runner.startMany({
+ tests: {
+ getNext: () => resolve(tests.shift())
+ },
+ stopOnError: cfxArgs.stopOnError,
+ onDone: nextIteration
+ });
+ });
+}
diff --git a/components/jetpack/sdk/test/utils.js b/components/jetpack/sdk/test/utils.js
new file mode 100644
index 000000000..b01df67d4
--- /dev/null
+++ b/components/jetpack/sdk/test/utils.js
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const { defer } = require('../core/promise');
+const { setInterval, clearInterval } = require('../timers');
+const { getTabs, closeTab } = require("../tabs/utils");
+const { windows: getWindows } = require("../window/utils");
+const { close: closeWindow } = require("../window/helpers");
+const { isGenerator } = require("../lang/type");
+const { env } = require("../system/environment");
+const { Task } = require("resource://gre/modules/Task.jsm");
+
+const getTestNames = (exports) =>
+ Object.keys(exports).filter(name => /^test/.test(name));
+
+const isTestAsync = ({length}) => length > 1;
+const isHelperAsync = ({length}) => length > 2;
+
+/*
+ * Takes an `exports` object of a test file and a function `beforeFn`
+ * to be run before each test. `beforeFn` is called with a `name` string
+ * as the first argument of the test name, and may specify a second
+ * argument function `done` to indicate that this function should
+ * resolve asynchronously
+ */
+function before (exports, beforeFn) {
+ getTestNames(exports).map(name => {
+ let testFn = exports[name];
+
+ // GENERATOR TESTS
+ if (isGenerator(testFn) && isGenerator(beforeFn)) {
+ exports[name] = function*(assert) {
+ yield Task.spawn(beforeFn.bind(null, name, assert));
+ yield Task.spawn(testFn.bind(null, assert));
+ }
+ }
+ else if (isGenerator(testFn) && !isHelperAsync(beforeFn)) {
+ exports[name] = function*(assert) {
+ beforeFn(name, assert);
+ yield Task.spawn(testFn.bind(null, assert));
+ }
+ }
+ else if (isGenerator(testFn) && isHelperAsync(beforeFn)) {
+ exports[name] = function*(assert) {
+ yield new Promise(resolve => beforeFn(name, assert, resolve));
+ yield Task.spawn(testFn.bind(null, assert));
+ }
+ }
+ // SYNC TESTS
+ else if (!isTestAsync(testFn) && isGenerator(beforeFn)) {
+ exports[name] = function*(assert) {
+ yield Task.spawn(beforeFn.bind(null, name, assert));
+ testFn(assert);
+ };
+ }
+ else if (!isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
+ exports[name] = function (assert) {
+ beforeFn(name, assert);
+ testFn(assert);
+ };
+ }
+ else if (!isTestAsync(testFn) && isHelperAsync(beforeFn)) {
+ exports[name] = function (assert, done) {
+ beforeFn(name, assert, () => {
+ testFn(assert);
+ done();
+ });
+ };
+ }
+ // ASYNC TESTS
+ else if (isTestAsync(testFn) && isGenerator(beforeFn)) {
+ exports[name] = function*(assert) {
+ yield Task.spawn(beforeFn.bind(null, name, assert));
+ yield new Promise(resolve => testFn(assert, resolve));
+ };
+ }
+ else if (isTestAsync(testFn) && !isHelperAsync(beforeFn)) {
+ exports[name] = function (assert, done) {
+ beforeFn(name, assert);
+ testFn(assert, done);
+ };
+ }
+ else if (isTestAsync(testFn) && isHelperAsync(beforeFn)) {
+ exports[name] = function (assert, done) {
+ beforeFn(name, assert, () => {
+ testFn(assert, done);
+ });
+ };
+ }
+ });
+}
+exports.before = before;
+
+/*
+ * Takes an `exports` object of a test file and a function `afterFn`
+ * to be run after each test. `afterFn` is called with a `name` string
+ * as the first argument of the test name, and may specify a second
+ * argument function `done` to indicate that this function should
+ * resolve asynchronously
+ */
+function after (exports, afterFn) {
+ getTestNames(exports).map(name => {
+ let testFn = exports[name];
+
+ // GENERATOR TESTS
+ if (isGenerator(testFn) && isGenerator(afterFn)) {
+ exports[name] = function*(assert) {
+ yield Task.spawn(testFn.bind(null, assert));
+ yield Task.spawn(afterFn.bind(null, name, assert));
+ }
+ }
+ else if (isGenerator(testFn) && !isHelperAsync(afterFn)) {
+ exports[name] = function*(assert) {
+ yield Task.spawn(testFn.bind(null, assert));
+ afterFn(name, assert);
+ }
+ }
+ else if (isGenerator(testFn) && isHelperAsync(afterFn)) {
+ exports[name] = function*(assert) {
+ yield Task.spawn(testFn.bind(null, assert));
+ yield new Promise(resolve => afterFn(name, assert, resolve));
+ }
+ }
+ // SYNC TESTS
+ else if (!isTestAsync(testFn) && isGenerator(afterFn)) {
+ exports[name] = function*(assert) {
+ testFn(assert);
+ yield Task.spawn(afterFn.bind(null, name, assert));
+ };
+ }
+ else if (!isTestAsync(testFn) && !isHelperAsync(afterFn)) {
+ exports[name] = function (assert) {
+ testFn(assert);
+ afterFn(name, assert);
+ };
+ }
+ else if (!isTestAsync(testFn) && isHelperAsync(afterFn)) {
+ exports[name] = function (assert, done) {
+ testFn(assert);
+ afterFn(name, assert, done);
+ };
+ }
+ // ASYNC TESTS
+ else if (isTestAsync(testFn) && isGenerator(afterFn)) {
+ exports[name] = function*(assert) {
+ yield new Promise(resolve => testFn(assert, resolve));
+ yield Task.spawn(afterFn.bind(null, name, assert));
+ };
+ }
+ else if (isTestAsync(testFn) && !isHelperAsync(afterFn)) {
+ exports[name] = function*(assert) {
+ yield new Promise(resolve => testFn(assert, resolve));
+ afterFn(name, assert);
+ };
+ }
+ else if (isTestAsync(testFn) && isHelperAsync(afterFn)) {
+ exports[name] = function*(assert) {
+ yield new Promise(resolve => testFn(assert, resolve));
+ yield new Promise(resolve => afterFn(name, assert, resolve));
+ };
+ }
+ });
+}
+exports.after = after;
+
+function waitUntil (predicate, delay) {
+ let { promise, resolve } = defer();
+ let interval = setInterval(() => {
+ if (!predicate()) return;
+ clearInterval(interval);
+ resolve();
+ }, delay || 10);
+ return promise;
+}
+exports.waitUntil = waitUntil;
+
+var cleanUI = function cleanUI() {
+ let { promise, resolve } = defer();
+
+ let windows = getWindows(null, { includePrivate: true });
+ if (windows.length > 1) {
+ return closeWindow(windows[1]).then(cleanUI);
+ }
+
+ getTabs(windows[0]).slice(1).forEach(closeTab);
+
+ resolve();
+
+ return promise;
+}
+exports.cleanUI = cleanUI;
+
+exports.isTravisCI = ("TRAVIS" in env && "CI" in env);
diff --git a/components/jetpack/sdk/timers.js b/components/jetpack/sdk/timers.js
new file mode 100644
index 000000000..e97db01f2
--- /dev/null
+++ b/components/jetpack/sdk/timers.js
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ "stability": "stable"
+};
+
+const { CC, Cc, Ci } = require("chrome");
+const { when: unload } = require("./system/unload");
+
+const { TYPE_ONE_SHOT, TYPE_REPEATING_SLACK } = Ci.nsITimer;
+const Timer = CC("@mozilla.org/timer;1", "nsITimer");
+const timers = Object.create(null);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].
+ getService(Ci.nsIThreadManager);
+const prefBranch = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ QueryInterface(Ci.nsIPrefBranch);
+
+var MIN_DELAY = 4;
+// Try to get min timeout delay used by browser.
+try { MIN_DELAY = prefBranch.getIntPref("dom.min_timeout_value"); } finally {}
+
+
+// Last timer id.
+var lastID = 0;
+
+// Sets typer either by timeout or by interval
+// depending on a given type.
+function setTimer(type, callback, delay, ...args) {
+ let id = ++ lastID;
+ let timer = timers[id] = Timer();
+ timer.initWithCallback({
+ notify: function notify() {
+ try {
+ if (type === TYPE_ONE_SHOT)
+ delete timers[id];
+ callback.apply(null, args);
+ }
+ catch(error) {
+ console.exception(error);
+ }
+ }
+ }, Math.max(delay || MIN_DELAY), type);
+ return id;
+}
+
+function unsetTimer(id) {
+ let timer = timers[id];
+ delete timers[id];
+ if (timer) timer.cancel();
+}
+
+var immediates = new Map();
+
+var dispatcher = _ => {
+ // Allow scheduling of a new dispatch loop.
+ dispatcher.scheduled = false;
+ // Take a snapshot of timer `id`'s that have being present before
+ // starting a dispatch loop, in order to ignore timers registered
+ // in side effect to dispatch while also skipping immediates that
+ // were removed in side effect.
+ let ids = [...immediates.keys()];
+ for (let id of ids) {
+ let immediate = immediates.get(id);
+ if (immediate) {
+ immediates.delete(id);
+ try { immediate(); }
+ catch (error) { console.exception(error); }
+ }
+ }
+}
+
+function setImmediate(callback, ...params) {
+ let id = ++ lastID;
+ // register new immediate timer with curried params.
+ immediates.set(id, _ => callback.apply(callback, params));
+ // if dispatch loop is not scheduled schedule one. Own scheduler
+ if (!dispatcher.scheduled) {
+ dispatcher.scheduled = true;
+ threadManager.currentThread.dispatch(dispatcher,
+ Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ return id;
+}
+
+function clearImmediate(id) {
+ immediates.delete(id);
+}
+
+// Bind timers so that toString-ing them looks same as on native timers.
+exports.setImmediate = setImmediate.bind(null);
+exports.clearImmediate = clearImmediate.bind(null);
+exports.setTimeout = setTimer.bind(null, TYPE_ONE_SHOT);
+exports.setInterval = setTimer.bind(null, TYPE_REPEATING_SLACK);
+exports.clearTimeout = unsetTimer.bind(null);
+exports.clearInterval = unsetTimer.bind(null);
+
+// all timers are cleared out on unload.
+unload(function() {
+ immediates.clear();
+ Object.keys(timers).forEach(unsetTimer)
+});
diff --git a/components/jetpack/sdk/ui.js b/components/jetpack/sdk/ui.js
new file mode 100644
index 000000000..d1ff7ceb8
--- /dev/null
+++ b/components/jetpack/sdk/ui.js
@@ -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/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'experimental',
+ 'engines': {
+ 'Palemoon': '> 27',
+ 'Firefox': '> 28'
+ }
+};
+
+exports.ActionButton = require('./ui/button/action').ActionButton;
+exports.ToggleButton = require('./ui/button/toggle').ToggleButton;
+#ifndef MC_PALEMOON
+exports.Sidebar = require('./ui/sidebar').Sidebar;
+exports.Frame = require('./ui/frame').Frame;
+exports.Toolbar = require('./ui/toolbar').Toolbar;
+#endif
diff --git a/components/jetpack/sdk/ui/button/action.js b/components/jetpack/sdk/ui/button/action.js
new file mode 100644
index 000000000..5355705be
--- /dev/null
+++ b/components/jetpack/sdk/ui/button/action.js
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'experimental',
+ 'engines': {
+ 'Palemoon': '> 27',
+ 'Firefox': '> 28'
+ }
+};
+
+const { Class } = require('../../core/heritage');
+const { merge } = require('../../util/object');
+const { Disposable } = require('../../core/disposable');
+const { on, off, emit, setListeners } = require('../../event/core');
+const { EventTarget } = require('../../event/target');
+const { getNodeView } = require('../../view/core');
+
+const view = require('./view');
+const { buttonContract, stateContract } = require('./contract');
+const { properties, render, state, register, unregister,
+ getDerivedStateFor } = require('../state');
+const { events: stateEvents } = require('../state/events');
+const { events: viewEvents } = require('./view/events');
+const events = require('../../event/utils');
+
+const { getActiveTab } = require('../../tabs/utils');
+
+const { id: addonID } = require('../../self');
+const { identify } = require('../id');
+
+const buttons = new Map();
+
+const toWidgetId = id =>
+ ('action-button--' + addonID.toLowerCase()+ '-' + id).
+ replace(/[^a-z0-9_-]/g, '');
+
+const ActionButton = Class({
+ extends: EventTarget,
+ implements: [
+ properties(stateContract),
+ state(stateContract),
+ Disposable
+ ],
+ setup: function setup(options) {
+ let state = merge({
+ disabled: false
+ }, buttonContract(options));
+
+ let id = toWidgetId(options.id);
+
+ register(this, state);
+
+ // Setup listeners.
+ setListeners(this, options);
+
+ buttons.set(id, this);
+
+ view.create(merge({}, state, { id: id }));
+ },
+
+ dispose: function dispose() {
+ let id = toWidgetId(this.id);
+ buttons.delete(id);
+
+ off(this);
+
+ view.dispose(id);
+
+ unregister(this);
+ },
+
+ get id() {
+ return this.state().id;
+ },
+
+ click: function click() { view.click(toWidgetId(this.id)) }
+});
+exports.ActionButton = ActionButton;
+
+identify.define(ActionButton, ({id}) => toWidgetId(id));
+
+getNodeView.define(ActionButton, button =>
+ view.nodeFor(toWidgetId(button.id))
+);
+
+var actionButtonStateEvents = events.filter(stateEvents,
+ e => e.target instanceof ActionButton);
+
+var actionButtonViewEvents = events.filter(viewEvents,
+ e => buttons.has(e.target));
+
+var clickEvents = events.filter(actionButtonViewEvents, e => e.type === 'click');
+var updateEvents = events.filter(actionButtonViewEvents, e => e.type === 'update');
+
+on(clickEvents, 'data', ({target: id, window}) => {
+ let button = buttons.get(id);
+ let state = getDerivedStateFor(button, getActiveTab(window));
+
+ emit(button, 'click', state);
+});
+
+on(updateEvents, 'data', ({target: id, window}) => {
+ render(buttons.get(id), window);
+});
+
+on(actionButtonStateEvents, 'data', ({target, window, state}) => {
+ let id = toWidgetId(target.id);
+ view.setIcon(id, window, state.icon);
+ view.setLabel(id, window, state.label);
+ view.setDisabled(id, window, state.disabled);
+ view.setBadge(id, window, state.badge, state.badgeColor);
+});
diff --git a/components/jetpack/sdk/ui/button/contract.js b/components/jetpack/sdk/ui/button/contract.js
new file mode 100644
index 000000000..ce6e33d95
--- /dev/null
+++ b/components/jetpack/sdk/ui/button/contract.js
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { contract } = require('../../util/contract');
+const { isLocalURL } = require('../../url');
+const { isNil, isObject, isString } = require('../../lang/type');
+const { required, either, string, boolean, object, number } = require('../../deprecated/api-utils');
+const { merge } = require('../../util/object');
+const { freeze } = Object;
+
+const isIconSet = (icons) =>
+ Object.keys(icons).
+ every(size => String(size >>> 0) === size && isLocalURL(icons[size]));
+
+var iconSet = {
+ is: either(object, string),
+ map: v => isObject(v) ? freeze(merge({}, v)) : v,
+ ok: v => (isString(v) && isLocalURL(v)) || (isObject(v) && isIconSet(v)),
+ msg: 'The option "icon" must be a local URL or an object with ' +
+ 'numeric keys / local URL values pair.'
+}
+
+var id = {
+ is: string,
+ ok: v => /^[a-z-_][a-z0-9-_]*$/i.test(v),
+ msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' +
+ 'underscores are allowed).'
+};
+
+var label = {
+ is: string,
+ ok: v => isNil(v) || v.trim().length > 0,
+ msg: 'The option "label" must be a non empty string'
+}
+
+var badge = {
+ is: either(string, number),
+ msg: 'The option "badge" must be a string or a number'
+}
+
+var badgeColor = {
+ is: string,
+ msg: 'The option "badgeColor" must be a string'
+}
+
+var stateContract = contract({
+ label: label,
+ icon: iconSet,
+ disabled: boolean,
+ badge: badge,
+ badgeColor: badgeColor
+});
+
+exports.stateContract = stateContract;
+
+var buttonContract = contract(merge({}, stateContract.rules, {
+ id: required(id),
+ label: required(label),
+ icon: required(iconSet)
+}));
+
+exports.buttonContract = buttonContract;
+
+exports.toggleStateContract = contract(merge({
+ checked: boolean
+}, stateContract.rules));
+
+exports.toggleButtonContract = contract(merge({
+ checked: boolean
+}, buttonContract.rules));
+
diff --git a/components/jetpack/sdk/ui/button/toggle.js b/components/jetpack/sdk/ui/button/toggle.js
new file mode 100644
index 000000000..d8a3d1758
--- /dev/null
+++ b/components/jetpack/sdk/ui/button/toggle.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/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'experimental',
+ 'engines': {
+ 'Palemoon': '> 27',
+ 'Firefox': '> 28'
+ }
+};
+
+const { Class } = require('../../core/heritage');
+const { merge } = require('../../util/object');
+const { Disposable } = require('../../core/disposable');
+const { on, off, emit, setListeners } = require('../../event/core');
+const { EventTarget } = require('../../event/target');
+const { getNodeView } = require('../../view/core');
+
+const view = require('./view');
+const { toggleButtonContract, toggleStateContract } = require('./contract');
+const { properties, render, state, register, unregister,
+ setStateFor, getStateFor, getDerivedStateFor } = require('../state');
+const { events: stateEvents } = require('../state/events');
+const { events: viewEvents } = require('./view/events');
+const events = require('../../event/utils');
+
+const { getActiveTab } = require('../../tabs/utils');
+
+const { id: addonID } = require('../../self');
+const { identify } = require('../id');
+
+const buttons = new Map();
+
+const toWidgetId = id =>
+ ('toggle-button--' + addonID.toLowerCase()+ '-' + id).
+ replace(/[^a-z0-9_-]/g, '');
+
+const ToggleButton = Class({
+ extends: EventTarget,
+ implements: [
+ properties(toggleStateContract),
+ state(toggleStateContract),
+ Disposable
+ ],
+ setup: function setup(options) {
+ let state = merge({
+ disabled: false,
+ checked: false
+ }, toggleButtonContract(options));
+
+ let id = toWidgetId(options.id);
+
+ register(this, state);
+
+ // Setup listeners.
+ setListeners(this, options);
+
+ buttons.set(id, this);
+
+ view.create(merge({ type: 'checkbox' }, state, { id: id }));
+ },
+
+ dispose: function dispose() {
+ let id = toWidgetId(this.id);
+ buttons.delete(id);
+
+ off(this);
+
+ view.dispose(id);
+
+ unregister(this);
+ },
+
+ get id() {
+ return this.state().id;
+ },
+
+ click: function click() {
+ return view.click(toWidgetId(this.id));
+ }
+});
+exports.ToggleButton = ToggleButton;
+
+identify.define(ToggleButton, ({id}) => toWidgetId(id));
+
+getNodeView.define(ToggleButton, button =>
+ view.nodeFor(toWidgetId(button.id))
+);
+
+var toggleButtonStateEvents = events.filter(stateEvents,
+ e => e.target instanceof ToggleButton);
+
+var toggleButtonViewEvents = events.filter(viewEvents,
+ e => buttons.has(e.target));
+
+var clickEvents = events.filter(toggleButtonViewEvents, e => e.type === 'click');
+var updateEvents = events.filter(toggleButtonViewEvents, e => e.type === 'update');
+
+on(toggleButtonStateEvents, 'data', ({target, window, state}) => {
+ let id = toWidgetId(target.id);
+
+ view.setIcon(id, window, state.icon);
+ view.setLabel(id, window, state.label);
+ view.setDisabled(id, window, state.disabled);
+ view.setChecked(id, window, state.checked);
+ view.setBadge(id, window, state.badge, state.badgeColor);
+});
+
+on(clickEvents, 'data', ({target: id, window, checked }) => {
+ let button = buttons.get(id);
+ let windowState = getStateFor(button, window);
+
+ let newWindowState = merge({}, windowState, { checked: checked });
+
+ setStateFor(button, window, newWindowState);
+
+ let state = getDerivedStateFor(button, getActiveTab(window));
+
+ emit(button, 'click', state);
+
+ emit(button, 'change', state);
+});
+
+on(updateEvents, 'data', ({target: id, window}) => {
+ render(buttons.get(id), window);
+});
diff --git a/components/jetpack/sdk/ui/button/view.js b/components/jetpack/sdk/ui/button/view.js
new file mode 100644
index 000000000..dcc3be59d
--- /dev/null
+++ b/components/jetpack/sdk/ui/button/view.js
@@ -0,0 +1,286 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'experimental',
+ 'engines': {
+ 'Palemoon': '> 27',
+ 'Firefox': '> 28'
+ }
+};
+
+const { Cu } = require('chrome');
+const { on, off, emit } = require('../../event/core');
+
+const { data } = require('sdk/self');
+
+const { isObject, isNil } = require('../../lang/type');
+
+const { getMostRecentBrowserWindow } = require('../../window/utils');
+const { ignoreWindow } = require('../../private-browsing/utils');
+#ifdef MC_PALEMOON
+const { buttons } = require('../buttons');
+#else
+const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
+const { AREA_PANEL, AREA_NAVBAR } = CustomizableUI;
+#endif
+
+const { events: viewEvents } = require('./view/events');
+
+const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+
+const views = new Map();
+#ifndef MC_PALEMOON
+const customizedWindows = new WeakMap();
+
+const buttonListener = {
+ onCustomizeStart: window => {
+ for (let [id, view] of views) {
+ setIcon(id, window, view.icon);
+ setLabel(id, window, view.label);
+ }
+
+ customizedWindows.set(window, true);
+ },
+ onCustomizeEnd: window => {
+ customizedWindows.delete(window);
+
+ for (let [id, ] of views) {
+ let placement = CustomizableUI.getPlacementOfWidget(id);
+
+ if (placement)
+ emit(viewEvents, 'data', { type: 'update', target: id, window: window });
+ }
+ },
+ onWidgetAfterDOMChange: (node, nextNode, container) => {
+ let { id } = node;
+ let view = views.get(id);
+ let window = node.ownerDocument.defaultView;
+
+ if (view) {
+ emit(viewEvents, 'data', { type: 'update', target: id, window: window });
+ }
+ }
+};
+
+CustomizableUI.addListener(buttonListener);
+
+require('../../system/unload').when( _ =>
+ CustomizableUI.removeListener(buttonListener)
+);
+#endif
+
+function getNode(id, window) {
+ return !views.has(id) || ignoreWindow(window)
+ ? null
+#ifdef MC_PALEMOON
+ : buttons.getNode(id, window);
+#else
+ : CustomizableUI.getWidget(id).forWindow(window).node
+#endif
+};
+
+#ifndef MC_PALEMOON
+function isInToolbar(id) {
+ let placement = CustomizableUI.getPlacementOfWidget(id);
+
+ return placement && CustomizableUI.getAreaType(placement.area) === 'toolbar';
+}
+#endif
+
+function getImage(icon, isInToolbar, pixelRatio) {
+ let targetSize = (isInToolbar ? 18 : 32) * pixelRatio;
+ let bestSize = 0;
+ let image = icon;
+
+ if (isObject(icon)) {
+ for (let size of Object.keys(icon)) {
+ size = +size;
+ let offset = targetSize - size;
+
+ if (offset === 0) {
+ bestSize = size;
+ break;
+ }
+
+ let delta = Math.abs(offset) - Math.abs(targetSize - bestSize);
+
+ if (delta < 0)
+ bestSize = size;
+ }
+
+ image = icon[bestSize];
+ }
+
+ if (image.indexOf('./') === 0)
+ return data.url(image.substr(2));
+
+ return image;
+}
+
+function nodeFor(id, window=getMostRecentBrowserWindow()) {
+#ifdef MC_PALEMOON
+ return getNode(id, window);
+#else
+ return customizedWindows.has(window) ? null : getNode(id, window);
+#endif
+};
+exports.nodeFor = nodeFor;
+
+function create(options) {
+ let { id, label, icon, type, badge } = options;
+
+ if (views.has(id))
+ throw new Error('The ID "' + id + '" seems already used.');
+
+#ifdef MC_PALEMOON
+ buttons.createButton({
+#else
+ CustomizableUI.createWidget({
+#endif
+ id: id,
+ type: 'custom',
+#ifdef MC_PALEMOON
+
+ onBuild: function(document, _id) {
+#else
+ removable: true,
+ defaultArea: AREA_NAVBAR,
+ allowedAreas: [ AREA_PANEL, AREA_NAVBAR ],
+
+ onBuild: function(document) {
+#endif
+ let window = document.defaultView;
+
+ let node = document.createElementNS(XUL_NS, 'toolbarbutton');
+
+ let image = getImage(icon, true, window.devicePixelRatio);
+
+ if (ignoreWindow(window))
+ node.style.display = 'none';
+
+#ifdef MC_PALEMOON
+ node.setAttribute('id', _id);
+#else
+ node.setAttribute('id', this.id);
+#endif
+ node.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional badged-button');
+ node.setAttribute('type', type);
+ node.setAttribute('label', label);
+ node.setAttribute('tooltiptext', label);
+ node.setAttribute('image', image);
+#ifdef MC_PALEMOON
+ node.setAttribute('pmkit-button', 'true');
+#else
+ node.setAttribute('constrain-size', 'true');
+#endif
+
+ views.set(id, {
+#ifndef MC_PALEMOON
+ area: this.currentArea,
+#endif
+ icon: icon,
+ label: label
+ });
+
+ node.addEventListener('command', function(event) {
+ if (views.has(id)) {
+ emit(viewEvents, 'data', {
+ type: 'click',
+ target: id,
+ window: event.view,
+ checked: node.checked
+ });
+ }
+ });
+
+ return node;
+ }
+ });
+};
+exports.create = create;
+
+function dispose(id) {
+ if (!views.has(id)) return;
+
+ views.delete(id);
+#ifdef MC_PALEMOON
+ buttons.destroyButton(id);
+#else
+ CustomizableUI.destroyWidget(id);
+#endif
+}
+exports.dispose = dispose;
+
+function setIcon(id, window, icon) {
+ let node = getNode(id, window);
+
+ if (node) {
+#ifdef MC_PALEMOON
+ let image = getImage(icon, true, window.devicePixelRatio);
+#else
+ icon = customizedWindows.has(window) ? views.get(id).icon : icon;
+ let image = getImage(icon, isInToolbar(id), window.devicePixelRatio);
+#endif
+
+ node.setAttribute('image', image);
+ }
+}
+exports.setIcon = setIcon;
+
+function setLabel(id, window, label) {
+ let node = nodeFor(id, window);
+
+ if (node) {
+ node.setAttribute('label', label);
+ node.setAttribute('tooltiptext', label);
+ }
+}
+exports.setLabel = setLabel;
+
+function setDisabled(id, window, disabled) {
+ let node = nodeFor(id, window);
+
+ if (node)
+ node.disabled = disabled;
+}
+exports.setDisabled = setDisabled;
+
+function setChecked(id, window, checked) {
+ let node = nodeFor(id, window);
+
+ if (node)
+ node.checked = checked;
+}
+exports.setChecked = setChecked;
+
+function setBadge(id, window, badge, color) {
+ let node = nodeFor(id, window);
+
+ if (node) {
+ // `Array.from` is needed to handle unicode symbol properly:
+ // 'ð€ð'.length is 4 where Array.from('ð€ð').length is 2
+ let text = isNil(badge)
+ ? ''
+ : Array.from(String(badge)).slice(0, 4).join('');
+
+ node.setAttribute('badge', text);
+
+ let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
+ 'class', 'toolbarbutton-badge');
+
+ if (badgeNode)
+ badgeNode.style.backgroundColor = isNil(color) ? '' : color;
+ }
+}
+exports.setBadge = setBadge;
+
+function click(id) {
+ let node = nodeFor(id);
+
+ if (node)
+ node.click();
+}
+exports.click = click;
diff --git a/components/jetpack/sdk/ui/button/view/events.js b/components/jetpack/sdk/ui/button/view/events.js
new file mode 100644
index 000000000..34d14be3a
--- /dev/null
+++ b/components/jetpack/sdk/ui/button/view/events.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+module.metadata = {
+ 'stability': 'experimental',
+ 'engines': {
+ 'Palemoon': '*',
+ 'Firefox': '*',
+ 'SeaMonkey': '*',
+ 'Thunderbird': '*'
+ }
+};
+
+var channel = {};
+
+exports.events = channel;
diff --git a/components/jetpack/sdk/ui/buttons.js b/components/jetpack/sdk/ui/buttons.js
new file mode 100644
index 000000000..450584ea7
--- /dev/null
+++ b/components/jetpack/sdk/ui/buttons.js
@@ -0,0 +1,217 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * PMkit shim for 'sdk/ui/button', (c) JustOff, 2017 */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Palemoon": "> 27"
+ }
+};
+
+const { Ci, Cc } = require('chrome');
+const prefs = require('../preferences/service');
+
+const buttonsList = new Map();
+const LOCATION_PREF_ROOT = "extensions.sdk-button-location.";
+
+let gWindowListener;
+
+function getLocation(id) {
+ let toolbarId = "nav-bar", nextItemId = "";
+ let location = prefs.get(LOCATION_PREF_ROOT + id);
+ if (location && location.indexOf(",") !== -1) {
+ [toolbarId, nextItemId] = location.split(",");
+ }
+ return [toolbarId, nextItemId];
+}
+
+function saveLocation(id, toolbarId, nextItemId) {
+ let _toolbarId = toolbarId || "";
+ let _nextItemId = nextItemId || "";
+ prefs.set(LOCATION_PREF_ROOT + id, [_toolbarId, _nextItemId].join(","));
+}
+
+// Insert button into window
+function insertButton(aWindow, id, onBuild) {
+ // Build button and save reference to it
+ let doc = aWindow.document;
+ let b = onBuild(doc, id);
+ aWindow[id] = b;
+
+ // Add to the customization palette
+ let toolbox = doc.getElementById("navigator-toolbox");
+ toolbox.palette.appendChild(b);
+
+ // Retrieve button location from preferences
+ let [toolbarId, nextItemId] = getLocation(id);
+ let toolbar = toolbarId != "" && doc.getElementById(toolbarId);
+
+ if (toolbar) {
+ // Handle special items with dynamic ids
+ let match = /^(separator|spacer|spring)\[(\d+)\]$/.exec(nextItemId);
+ if (match !== null) {
+ let dynItems = toolbar.querySelectorAll("toolbar" + match[1]);
+ if (match[2] < dynItems.length) {
+ nextItemId = dynItems[match[2]].id;
+ }
+ }
+ let nextItem = nextItemId != "" && doc.getElementById(nextItemId);
+ // If nextItem not in toolbar then retrieve it by reading currentset attribute
+ if (!(nextItem && nextItem.parentNode && nextItem.parentNode.id == toolbarId)) {
+ nextItem = null;
+ let currentSet = toolbar.getAttribute("currentset");
+ let ids = (currentSet == "__empty") ? [] : currentSet.split(",");
+ let idx = ids.indexOf(id);
+ if (idx != -1) {
+ for (let i = idx; i < ids.length; i++) {
+ nextItem = doc.getElementById(ids[i]);
+ if (nextItem)
+ break;
+ }
+ }
+ }
+ // Finally insert button in the right toolbar and in the right position
+ toolbar.insertItem(id, nextItem, null, false);
+ }
+}
+
+// Remove button from window
+function removeButton(aWindow, id) {
+ let b = aWindow[id];
+ b.parentNode.removeChild(b);
+ delete aWindow[id];
+}
+
+// Save locations of buttons after customization
+function afterCustomize(e) {
+ for (let [id] of buttonsList) {
+ let toolbox = e.target;
+ let b = toolbox.parentNode.querySelector("#" + id);
+ let toolbarId = null, nextItem = null, nextItemId = null;
+ if (b) {
+ let parent = b.parentNode;
+ nextItem = b.nextSibling;
+ if (parent && parent.localName == "toolbar") {
+ toolbarId = parent.id;
+ nextItemId = nextItem && nextItem.id;
+ }
+ }
+ // Handle special items with dynamic ids
+ let match = /^(separator|spacer|spring)\d+$/.exec(nextItemId);
+ if (match !== null) {
+ let dynItems = nextItem.parentNode.querySelectorAll("toolbar" + match[1]);
+ for (let i = 0; i < dynItems.length; i++) {
+ if (dynItems[i].id == nextItemId) {
+ nextItemId = match[1] + "[" + i + "]";
+ break;
+ }
+ }
+ }
+ saveLocation(id, toolbarId, nextItemId);
+ }
+}
+
+// Global window observer
+function browserWindowObserver(handlers) {
+ this.handlers = handlers;
+}
+
+browserWindowObserver.prototype = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ aSubject.QueryInterface(Ci.nsIDOMWindow).addEventListener("load", this, false);
+ } else if (aTopic == "domwindowclosed") {
+ if (aSubject.document.documentElement.getAttribute("windowtype") == "navigator:browser") {
+ this.handlers.onShutdown(aSubject);
+ }
+ }
+ },
+
+ handleEvent: function(aEvent) {
+ let aWindow = aEvent.currentTarget;
+ aWindow.removeEventListener(aEvent.type, this, false);
+
+ if (aWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser") {
+ this.handlers.onStartup(aWindow);
+ }
+ }
+};
+
+// Run on every window startup
+function browserWindowStartup(aWindow) {
+ for (let [id, onBuild] of buttonsList) {
+ insertButton(aWindow, id, onBuild);
+ }
+ aWindow.addEventListener("aftercustomization", afterCustomize, false);
+};
+
+// Run on every window shutdown
+function browserWindowShutdown(aWindow) {
+ for (let [id, onBuild] of buttonsList) {
+ removeButton(aWindow, id);
+ }
+ aWindow.removeEventListener("aftercustomization", afterCustomize, false);
+}
+
+// Main object
+const buttons = {
+ createButton: function(aProperties) {
+ // If no buttons were inserted yet, setup global window observer
+ if (buttonsList.size == 0) {
+ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
+ gWindowListener = new browserWindowObserver({
+ onStartup: browserWindowStartup,
+ onShutdown: browserWindowShutdown
+ });
+ ww.registerNotification(gWindowListener);
+ }
+
+ // Add button to list
+ buttonsList.set(aProperties.id, aProperties.onBuild);
+
+ // Inster button to all open windows
+ let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+ let winenu = wm.getEnumerator("navigator:browser");
+ while (winenu.hasMoreElements()) {
+ let win = winenu.getNext();
+ insertButton(win, aProperties.id, aProperties.onBuild);
+ // When first button inserted, add afterCustomize listener
+ if (buttonsList.size == 1) {
+ win.addEventListener("aftercustomization", afterCustomize, false);
+ }
+ }
+ },
+
+ destroyButton: function(id) {
+ // Remove button from list
+ buttonsList.delete(id);
+
+ // If no more buttons exist, remove global window observer
+ if (buttonsList.size == 0) {
+ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
+ ww.unregisterNotification(gWindowListener);
+ gWindowListener = null;
+ }
+
+ // Remove button from all open windows
+ let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+ let winenu = wm.getEnumerator("navigator:browser");
+ while (winenu.hasMoreElements()) {
+ let win = winenu.getNext();
+ removeButton(win, id);
+ // If no more buttons exist, remove afterCustomize listener
+ if (buttonsList.size == 0) {
+ win.removeEventListener("aftercustomization", afterCustomize, false);
+ }
+ }
+ },
+
+ getNode: function(id, window) {
+ return window[id];
+ }
+};
+
+exports.buttons = buttons;
diff --git a/components/jetpack/sdk/ui/component.js b/components/jetpack/sdk/ui/component.js
new file mode 100644
index 000000000..d1f12c95e
--- /dev/null
+++ b/components/jetpack/sdk/ui/component.js
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+// Internal properties not exposed to the public.
+const cache = Symbol("component/cache");
+const writer = Symbol("component/writer");
+const isFirstWrite = Symbol("component/writer/first-write?");
+const currentState = Symbol("component/state/current");
+const pendingState = Symbol("component/state/pending");
+const isWriting = Symbol("component/writing?");
+
+const isntNull = x => x !== null;
+
+const Component = function(options, children) {
+ this[currentState] = null;
+ this[pendingState] = null;
+ this[writer] = null;
+ this[cache] = null;
+ this[isFirstWrite] = true;
+
+ this[Component.construct](options, children);
+}
+Component.Component = Component;
+// Constructs component.
+Component.construct = Symbol("component/construct");
+// Called with `options` and `children` and must return
+// initial state back.
+Component.initial = Symbol("component/initial");
+
+// Function patches current `state` with a given update.
+Component.patch = Symbol("component/patch");
+// Function that replaces current `state` with a passed state.
+Component.reset = Symbol("component/reset");
+
+// Function that must return render tree from passed state.
+Component.render = Symbol("component/render");
+
+// Path of the component with in the mount point.
+Component.path = Symbol("component/path");
+
+Component.isMounted = component => !!component[writer];
+Component.isWriting = component => !!component[isWriting];
+
+// Internal method that mounts component to a writer.
+// Mounts component to a writer.
+Component.mount = (component, write) => {
+ if (Component.isMounted(component)) {
+ throw Error("Can not mount already mounted component");
+ }
+
+ component[writer] = write;
+ Component.write(component);
+
+ if (component[Component.mounted]) {
+ component[Component.mounted]();
+ }
+}
+
+// Unmounts component from a writer.
+Component.unmount = (component) => {
+ if (Component.isMounted(component)) {
+ component[writer] = null;
+ if (component[Component.unmounted]) {
+ component[Component.unmounted]();
+ }
+ } else {
+ console.warn("Unmounting component that is not mounted is redundant");
+ }
+};
+ // Method invoked once after inital write occurs.
+Component.mounted = Symbol("component/mounted");
+// Internal method that unmounts component from the writer.
+Component.unmounted = Symbol("component/unmounted");
+// Function that must return true if component is changed
+Component.isUpdated = Symbol("component/updated?");
+Component.update = Symbol("component/update");
+Component.updated = Symbol("component/updated");
+
+const writeChild = base => (child, index) => Component.write(child, base, index)
+Component.write = (component, base, index) => {
+ if (component === null) {
+ return component;
+ }
+
+ if (!(component instanceof Component)) {
+ const path = base ? `${base}${component.key || index}/` : `/`;
+ return Object.assign({}, component, {
+ [Component.path]: path,
+ children: component.children && component.children.
+ map(writeChild(path)).
+ filter(isntNull)
+ });
+ }
+
+ component[isWriting] = true;
+
+ try {
+
+ const current = component[currentState];
+ const pending = component[pendingState] || current;
+ const isUpdated = component[Component.isUpdated];
+ const isInitial = component[isFirstWrite];
+
+ if (isUpdated(current, pending) || isInitial) {
+ if (!isInitial && component[Component.update]) {
+ component[Component.update](pending, current)
+ }
+
+ // Note: [Component.update] could have caused more updates so can't use
+ // `pending` as `component[pendingState]` may have changed.
+ component[currentState] = component[pendingState] || current;
+ component[pendingState] = null;
+
+ const tree = component[Component.render](component[currentState]);
+ component[cache] = Component.write(tree, base, index);
+ if (component[writer]) {
+ component[writer].call(null, component[cache]);
+ }
+
+ if (!isInitial && component[Component.updated]) {
+ component[Component.updated](current, pending);
+ }
+ }
+
+ component[isFirstWrite] = false;
+
+ return component[cache];
+ } finally {
+ component[isWriting] = false;
+ }
+};
+
+Component.prototype = Object.freeze({
+ constructor: Component,
+
+ [Component.mounted]: null,
+ [Component.unmounted]: null,
+ [Component.update]: null,
+ [Component.updated]: null,
+
+ get state() {
+ return this[pendingState] || this[currentState];
+ },
+
+
+ [Component.construct](settings, items) {
+ const initial = this[Component.initial];
+ const base = initial(settings, items);
+ const options = Object.assign(Object.create(null), base.options, settings);
+ const children = base.children || items || null;
+ const state = Object.assign(Object.create(null), base, {options, children});
+ this[currentState] = state;
+
+ if (this.setup) {
+ this.setup(state);
+ }
+ },
+ [Component.initial](options, children) {
+ return Object.create(null);
+ },
+ [Component.patch](update) {
+ this[Component.reset](Object.assign({}, this.state, update));
+ },
+ [Component.reset](state) {
+ this[pendingState] = state;
+ if (Component.isMounted(this) && !Component.isWriting(this)) {
+ Component.write(this);
+ }
+ },
+
+ [Component.isUpdated](before, after) {
+ return before != after
+ },
+
+ [Component.render](state) {
+ throw Error("Component must implement [Component.render] member");
+ }
+});
+
+module.exports = Component;
diff --git a/components/jetpack/sdk/ui/frame.js b/components/jetpack/sdk/ui/frame.js
new file mode 100644
index 000000000..566353cdf
--- /dev/null
+++ b/components/jetpack/sdk/ui/frame.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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Firefox": "> 28"
+ }
+};
+
+require("./frame/view");
+const { Frame } = require("./frame/model");
+
+exports.Frame = Frame;
diff --git a/components/jetpack/sdk/ui/frame/model.js b/components/jetpack/sdk/ui/frame/model.js
new file mode 100644
index 000000000..627310874
--- /dev/null
+++ b/components/jetpack/sdk/ui/frame/model.js
@@ -0,0 +1,154 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Firefox": "> 28"
+ }
+};
+
+const { Class } = require("../../core/heritage");
+const { EventTarget } = require("../../event/target");
+const { emit, off, setListeners } = require("../../event/core");
+const { Reactor, foldp, send, merges } = require("../../event/utils");
+const { Disposable } = require("../../core/disposable");
+const { OutputPort } = require("../../output/system");
+const { InputPort } = require("../../input/system");
+const { identify } = require("../id");
+const { pairs, object, map, each } = require("../../util/sequence");
+const { patch, diff } = require("diffpatcher/index");
+const { isLocalURL } = require("../../url");
+const { compose } = require("../../lang/functional");
+const { contract } = require("../../util/contract");
+const { id: addonID, data: { url: resolve }} = require("../../self");
+const { Frames } = require("../../input/frame");
+
+
+const output = new OutputPort({ id: "frame-change" });
+const mailbox = new OutputPort({ id: "frame-mailbox" });
+const input = Frames;
+
+
+const makeID = url =>
+ ("frame-" + addonID + "-" + url).
+ split("/").join("-").
+ split(".").join("-").
+ replace(/[^A-Za-z0-9_\-]/g, "");
+
+const validate = contract({
+ name: {
+ is: ["string", "undefined"],
+ ok: x => /^[a-z][a-z0-9-_]+$/i.test(x),
+ msg: "The `option.name` must be a valid alphanumeric string (hyphens and " +
+ "underscores are allowed) starting with letter."
+ },
+ url: {
+ map: x => x.toString(),
+ is: ["string"],
+ ok: x => isLocalURL(x),
+ msg: "The `options.url` must be a valid local URI."
+ }
+});
+
+const Source = function({id, ownerID}) {
+ this.id = id;
+ this.ownerID = ownerID;
+};
+Source.postMessage = ({id, ownerID}, data, origin) => {
+ send(mailbox, object([id, {
+ inbox: {
+ target: {id: id, ownerID: ownerID},
+ timeStamp: Date.now(),
+ data: data,
+ origin: origin
+ }
+ }]));
+};
+Source.prototype.postMessage = function(data, origin) {
+ Source.postMessage(this, data, origin);
+};
+
+const Message = function({type, data, source, origin, timeStamp}) {
+ this.type = type;
+ this.data = data;
+ this.origin = origin;
+ this.timeStamp = timeStamp;
+ this.source = new Source(source);
+};
+
+
+const frames = new Map();
+const sources = new Map();
+
+const Frame = Class({
+ extends: EventTarget,
+ implements: [Disposable, Source],
+ initialize: function(params={}) {
+ const options = validate(params);
+ const id = makeID(options.name || options.url);
+
+ if (frames.has(id))
+ throw Error("Frame with this id already exists: " + id);
+
+ const initial = { id: id, url: resolve(options.url) };
+ this.id = id;
+
+ setListeners(this, params);
+
+ frames.set(this.id, this);
+
+ send(output, object([id, initial]));
+ },
+ get url() {
+ const state = reactor.value[this.id];
+ return state && state.url;
+ },
+ destroy: function() {
+ send(output, object([this.id, null]));
+ frames.delete(this.id);
+ off(this);
+ },
+ // `JSON.stringify` serializes objects based of the return
+ // value of this method. For convinienc we provide this method
+ // to serialize actual state data.
+ toJSON: function() {
+ return { id: this.id, url: this.url };
+ }
+});
+identify.define(Frame, frame => frame.id);
+
+exports.Frame = Frame;
+
+const reactor = new Reactor({
+ onStep: (present, past) => {
+ const delta = diff(past, present);
+
+ each(([id, update]) => {
+ const frame = frames.get(id);
+ if (update) {
+ if (!past[id])
+ emit(frame, "register");
+
+ if (update.outbox)
+ emit(frame, "message", new Message(present[id].outbox));
+
+ each(([ownerID, state]) => {
+ const readyState = state ? state.readyState : "detach";
+ const type = readyState === "loading" ? "attach" :
+ readyState === "interactive" ? "ready" :
+ readyState === "complete" ? "load" :
+ readyState;
+
+ // TODO: Cache `Source` instances somewhere to preserve
+ // identity.
+ emit(frame, type, {type: type,
+ source: new Source({id: id, ownerID: ownerID})});
+ }, pairs(update.owners));
+ }
+ }, pairs(delta));
+ }
+});
+reactor.run(input);
diff --git a/components/jetpack/sdk/ui/frame/view.html b/components/jetpack/sdk/ui/frame/view.html
new file mode 100644
index 000000000..2a405b583
--- /dev/null
+++ b/components/jetpack/sdk/ui/frame/view.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script>
+ // HACK: This is not an ideal way to deliver chrome messages
+ // to an inner frame content but seems only way that would
+ // make `event.source` this (outer frame) window.
+ window.onmessage = function(event) {
+ var frame = document.querySelector("iframe");
+ var content = frame.contentWindow;
+ // If message is posted from chrome it has no `event.source`.
+ if (event.source === null)
+ content.postMessage(event.data, "*");
+ };
+ </script>
+ </head>
+ <body style="overflow: hidden"></body>
+</html>
diff --git a/components/jetpack/sdk/ui/frame/view.js b/components/jetpack/sdk/ui/frame/view.js
new file mode 100644
index 000000000..2eb4df2b7
--- /dev/null
+++ b/components/jetpack/sdk/ui/frame/view.js
@@ -0,0 +1,150 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Firefox": "> 28"
+ }
+};
+
+const { Cu, Ci } = require("chrome");
+const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
+const { subscribe, send, Reactor, foldp, lift, merges, keepIf } = require("../../event/utils");
+const { InputPort } = require("../../input/system");
+const { OutputPort } = require("../../output/system");
+const { LastClosed } = require("../../input/browser");
+const { pairs, keys, object, each } = require("../../util/sequence");
+const { curry, compose } = require("../../lang/functional");
+const { getFrameElement, getOuterId,
+ getByOuterId, getOwnerBrowserWindow } = require("../../window/utils");
+const { patch, diff } = require("diffpatcher/index");
+const { encode } = require("../../base64");
+const { Frames } = require("../../input/frame");
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const OUTER_FRAME_URI = module.uri.replace(/\.js$/, ".html");
+
+const mailbox = new OutputPort({ id: "frame-mailbox" });
+
+const frameID = frame => frame.id.replace("outer-", "");
+const windowID = compose(getOuterId, getOwnerBrowserWindow);
+
+const getOuterFrame = (windowID, frameID) =>
+ getByOuterId(windowID).document.getElementById("outer-" + frameID);
+
+const listener = ({target, source, data, origin, timeStamp}) => {
+ // And sent received message to outbox so that frame API model
+ // will deal with it.
+ if (source && source !== target) {
+ const frame = getFrameElement(target);
+ const id = frameID(frame);
+ send(mailbox, object([id, {
+ outbox: {type: "message",
+ source: {id: id, ownerID: windowID(frame)},
+ data: data,
+ origin: origin,
+ timeStamp: timeStamp}}]));
+ }
+};
+
+// Utility function used to create frame with a given `state` and
+// inject it into given `window`.
+const registerFrame = ({id, url}) => {
+ CustomizableUI.createWidget({
+ id: id,
+ type: "custom",
+ removable: true,
+ onBuild: document => {
+ let view = document.createElementNS(XUL_NS, "toolbaritem");
+ view.setAttribute("id", id);
+ view.setAttribute("flex", 2);
+
+ let outerFrame = document.createElementNS(XUL_NS, "iframe");
+ outerFrame.setAttribute("src", OUTER_FRAME_URI);
+ outerFrame.setAttribute("id", "outer-" + id);
+ outerFrame.setAttribute("data-is-sdk-outer-frame", true);
+ outerFrame.setAttribute("type", "content");
+ outerFrame.setAttribute("transparent", true);
+ outerFrame.setAttribute("flex", 2);
+ outerFrame.setAttribute("style", "overflow: hidden;");
+ outerFrame.setAttribute("scrolling", "no");
+ outerFrame.setAttribute("disablehistory", true);
+ outerFrame.setAttribute("seamless", "seamless");
+ outerFrame.addEventListener("load", function onload() {
+ outerFrame.removeEventListener("load", onload, true);
+
+ let doc = outerFrame.contentDocument;
+
+ let innerFrame = doc.createElementNS(HTML_NS, "iframe");
+ innerFrame.setAttribute("id", id);
+ innerFrame.setAttribute("src", url);
+ innerFrame.setAttribute("seamless", "seamless");
+ innerFrame.setAttribute("sandbox", "allow-scripts");
+ innerFrame.setAttribute("scrolling", "no");
+ innerFrame.setAttribute("data-is-sdk-inner-frame", true);
+ innerFrame.setAttribute("style", [ "border:none",
+ "position:absolute", "width:100%", "top: 0",
+ "left: 0", "overflow: hidden"].join(";"));
+
+ doc.body.appendChild(innerFrame);
+ }, true);
+
+ view.appendChild(outerFrame);
+
+ return view;
+ }
+ });
+};
+
+const unregisterFrame = CustomizableUI.destroyWidget;
+
+const deliverMessage = curry((frameID, data, windowID) => {
+ const frame = getOuterFrame(windowID, frameID);
+ const content = frame && frame.contentWindow;
+
+ if (content)
+ content.postMessage(data, content.location.origin);
+});
+
+const updateFrame = (id, {inbox, owners}, present) => {
+ if (inbox) {
+ const { data, target:{ownerID}, source } = present[id].inbox;
+ if (ownerID)
+ deliverMessage(id, data, ownerID);
+ else
+ each(deliverMessage(id, data), keys(present[id].owners));
+ }
+
+ each(setupView(id), pairs(owners));
+};
+
+const setupView = curry((frameID, [windowID, state]) => {
+ if (state && state.readyState === "loading") {
+ const frame = getOuterFrame(windowID, frameID);
+ // Setup a message listener on contentWindow.
+ frame.contentWindow.addEventListener("message", listener);
+ }
+});
+
+
+const reactor = new Reactor({
+ onStep: (present, past) => {
+ const delta = diff(past, present);
+
+ // Apply frame changes
+ each(([id, update]) => {
+ if (update === null)
+ unregisterFrame(id);
+ else if (past[id])
+ updateFrame(id, update, present);
+ else
+ registerFrame(update);
+ }, pairs(delta));
+ },
+ onEnd: state => each(unregisterFrame, keys(state))
+});
+reactor.run(Frames);
diff --git a/components/jetpack/sdk/ui/id.js b/components/jetpack/sdk/ui/id.js
new file mode 100644
index 000000000..d17eb0a4e
--- /dev/null
+++ b/components/jetpack/sdk/ui/id.js
@@ -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/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'experimental'
+};
+
+const method = require('../../method/core');
+const { uuid } = require('../util/uuid');
+
+// NOTE: use lang/functional memoize when it is updated to use WeakMap
+function memoize(f) {
+ const memo = new WeakMap();
+
+ return function memoizer(o) {
+ let key = o;
+ if (!memo.has(key))
+ memo.set(key, f.apply(this, arguments));
+ return memo.get(key);
+ };
+}
+
+var identify = method('identify');
+identify.define(Object, memoize(function() { return uuid(); }));
+exports.identify = identify;
diff --git a/components/jetpack/sdk/ui/sidebar.js b/components/jetpack/sdk/ui/sidebar.js
new file mode 100644
index 000000000..59e35ea11
--- /dev/null
+++ b/components/jetpack/sdk/ui/sidebar.js
@@ -0,0 +1,311 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'experimental',
+ 'engines': {
+ 'Firefox': '*'
+ }
+};
+
+const { Class } = require('../core/heritage');
+const { merge } = require('../util/object');
+const { Disposable } = require('../core/disposable');
+const { off, emit, setListeners } = require('../event/core');
+const { EventTarget } = require('../event/target');
+const { URL } = require('../url');
+const { add, remove, has, clear, iterator } = require('../lang/weak-set');
+const { id: addonID, data } = require('../self');
+const { WindowTracker } = require('../deprecated/window-utils');
+const { isShowing } = require('./sidebar/utils');
+const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../window/utils');
+const { ns } = require('../core/namespace');
+const { remove: removeFromArray } = require('../util/array');
+const { show, hide, toggle } = require('./sidebar/actions');
+const { Worker } = require('../deprecated/sync-worker');
+const { contract: sidebarContract } = require('./sidebar/contract');
+const { create, dispose, updateTitle, updateURL, isSidebarShowing, showSidebar, hideSidebar } = require('./sidebar/view');
+const { defer } = require('../core/promise');
+const { models, views, viewsFor, modelFor } = require('./sidebar/namespace');
+const { isLocalURL } = require('../url');
+const { ensure } = require('../system/unload');
+const { identify } = require('./id');
+const { uuid } = require('../util/uuid');
+const { viewFor } = require('../view/core');
+
+const resolveURL = (url) => url ? data.url(url) : url;
+
+const sidebarNS = ns();
+
+const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
+
+var sidebars = {};
+
+const Sidebar = Class({
+ implements: [ Disposable ],
+ extends: EventTarget,
+ setup: function(options) {
+ // inital validation for the model information
+ let model = sidebarContract(options);
+
+ // save the model information
+ models.set(this, model);
+
+ // generate an id if one was not provided
+ model.id = model.id || addonID + '-' + uuid();
+
+ // further validation for the title and url
+ validateTitleAndURLCombo({}, this.title, this.url);
+
+ const self = this;
+ const internals = sidebarNS(self);
+ const windowNS = internals.windowNS = ns();
+
+ // see bug https://bugzilla.mozilla.org/show_bug.cgi?id=886148
+ ensure(this, 'destroy');
+
+ setListeners(this, options);
+
+ let bars = [];
+ internals.tracker = WindowTracker({
+ onTrack: function(window) {
+ if (!isBrowser(window))
+ return;
+
+ let sidebar = window.document.getElementById('sidebar');
+ let sidebarBox = window.document.getElementById('sidebar-box');
+
+ let bar = create(window, {
+ id: self.id,
+ title: self.title,
+ sidebarurl: self.url
+ });
+ bars.push(bar);
+ windowNS(window).bar = bar;
+
+ bar.addEventListener('command', function() {
+ if (isSidebarShowing(window, self)) {
+ hideSidebar(window, self).catch(() => {});
+ return;
+ }
+
+ showSidebar(window, self);
+ }, false);
+
+ function onSidebarLoad() {
+ // check if the sidebar is ready
+ let isReady = sidebar.docShell && sidebar.contentDocument;
+ if (!isReady)
+ return;
+
+ // check if it is a web panel
+ let panelBrowser = sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
+ if (!panelBrowser) {
+ bar.removeAttribute('checked');
+ return;
+ }
+
+ let sbTitle = window.document.getElementById('sidebar-title');
+ function onWebPanelSidebarCreated() {
+ if (panelBrowser.contentWindow.location != resolveURL(model.url) ||
+ sbTitle.value != model.title) {
+ return;
+ }
+
+ let worker = windowNS(window).worker = Worker({
+ window: panelBrowser.contentWindow,
+ injectInDocument: true
+ });
+
+ function onWebPanelSidebarUnload() {
+ windowNS(window).onWebPanelSidebarUnload = null;
+
+ // uncheck the associated menuitem
+ bar.setAttribute('checked', 'false');
+
+ emit(self, 'hide', {});
+ emit(self, 'detach', worker);
+ windowNS(window).worker = null;
+ }
+ windowNS(window).onWebPanelSidebarUnload = onWebPanelSidebarUnload;
+ panelBrowser.contentWindow.addEventListener('unload', onWebPanelSidebarUnload, true);
+
+ // check the associated menuitem
+ bar.setAttribute('checked', 'true');
+
+ function onWebPanelSidebarReady() {
+ panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', onWebPanelSidebarReady, false);
+ windowNS(window).onWebPanelSidebarReady = null;
+
+ emit(self, 'ready', worker);
+ }
+ windowNS(window).onWebPanelSidebarReady = onWebPanelSidebarReady;
+ panelBrowser.contentWindow.addEventListener('DOMContentLoaded', onWebPanelSidebarReady, false);
+
+ function onWebPanelSidebarLoad() {
+ panelBrowser.contentWindow.removeEventListener('load', onWebPanelSidebarLoad, true);
+ windowNS(window).onWebPanelSidebarLoad = null;
+
+ // TODO: decide if returning worker is acceptable..
+ //emit(self, 'show', { worker: worker });
+ emit(self, 'show', {});
+ }
+ windowNS(window).onWebPanelSidebarLoad = onWebPanelSidebarLoad;
+ panelBrowser.contentWindow.addEventListener('load', onWebPanelSidebarLoad, true);
+
+ emit(self, 'attach', worker);
+ }
+ windowNS(window).onWebPanelSidebarCreated = onWebPanelSidebarCreated;
+ panelBrowser.addEventListener('DOMWindowCreated', onWebPanelSidebarCreated, true);
+ }
+ windowNS(window).onSidebarLoad = onSidebarLoad;
+ sidebar.addEventListener('load', onSidebarLoad, true); // removed properly
+ },
+ onUntrack: function(window) {
+ if (!isBrowser(window))
+ return;
+
+ // hide the sidebar if it is showing
+ hideSidebar(window, self).catch(() => {});
+
+ // kill the menu item
+ let { bar } = windowNS(window);
+ if (bar) {
+ removeFromArray(viewsFor(self), bar);
+ dispose(bar);
+ }
+
+ // kill listeners
+ let sidebar = window.document.getElementById('sidebar');
+
+ if (windowNS(window).onSidebarLoad) {
+ sidebar && sidebar.removeEventListener('load', windowNS(window).onSidebarLoad, true)
+ windowNS(window).onSidebarLoad = null;
+ }
+
+ let panelBrowser = sidebar && sidebar.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
+ if (windowNS(window).onWebPanelSidebarCreated) {
+ panelBrowser && panelBrowser.removeEventListener('DOMWindowCreated', windowNS(window).onWebPanelSidebarCreated, true);
+ windowNS(window).onWebPanelSidebarCreated = null;
+ }
+
+ if (windowNS(window).onWebPanelSidebarReady) {
+ panelBrowser && panelBrowser.contentWindow.removeEventListener('DOMContentLoaded', windowNS(window).onWebPanelSidebarReady, false);
+ windowNS(window).onWebPanelSidebarReady = null;
+ }
+
+ if (windowNS(window).onWebPanelSidebarLoad) {
+ panelBrowser && panelBrowser.contentWindow.removeEventListener('load', windowNS(window).onWebPanelSidebarLoad, true);
+ windowNS(window).onWebPanelSidebarLoad = null;
+ }
+
+ if (windowNS(window).onWebPanelSidebarUnload) {
+ panelBrowser && panelBrowser.contentWindow.removeEventListener('unload', windowNS(window).onWebPanelSidebarUnload, true);
+ windowNS(window).onWebPanelSidebarUnload();
+ }
+ }
+ });
+
+ views.set(this, bars);
+
+ add(sidebars, this);
+ },
+ get id() {
+ return (modelFor(this) || {}).id;
+ },
+ get title() {
+ return (modelFor(this) || {}).title;
+ },
+ set title(v) {
+ // destroyed?
+ if (!modelFor(this))
+ return;
+ // validation
+ if (typeof v != 'string')
+ throw Error('title must be a string');
+ validateTitleAndURLCombo(this, v, this.url);
+ // do update
+ updateTitle(this, v);
+ return modelFor(this).title = v;
+ },
+ get url() {
+ return (modelFor(this) || {}).url;
+ },
+ set url(v) {
+ // destroyed?
+ if (!modelFor(this))
+ return;
+
+ // validation
+ if (!isLocalURL(v))
+ throw Error('the url must be a valid local url');
+
+ validateTitleAndURLCombo(this, this.title, v);
+
+ // do update
+ updateURL(this, v);
+ modelFor(this).url = v;
+ },
+ show: function(window) {
+ return showSidebar(viewFor(window), this);
+ },
+ hide: function(window) {
+ return hideSidebar(viewFor(window), this);
+ },
+ dispose: function() {
+ const internals = sidebarNS(this);
+
+ off(this);
+
+ remove(sidebars, this);
+
+ // stop tracking windows
+ if (internals.tracker) {
+ internals.tracker.unload();
+ }
+
+ internals.tracker = null;
+ internals.windowNS = null;
+
+ views.delete(this);
+ models.delete(this);
+ }
+});
+exports.Sidebar = Sidebar;
+
+function validateTitleAndURLCombo(sidebar, title, url) {
+ url = resolveURL(url);
+
+ if (sidebar.title == title && sidebar.url == url) {
+ return false;
+ }
+
+ for (let window of windows(null, { includePrivate: true })) {
+ let sidebar = window.document.querySelector('menuitem[sidebarurl="' + url + '"][label="' + title + '"]');
+ if (sidebar) {
+ throw Error('The provided title and url combination is invalid (already used).');
+ }
+ }
+
+ return false;
+}
+
+isShowing.define(Sidebar, isSidebarShowing.bind(null, null));
+show.define(Sidebar, showSidebar.bind(null, null));
+hide.define(Sidebar, hideSidebar.bind(null, null));
+
+identify.define(Sidebar, function(sidebar) {
+ return sidebar.id;
+});
+
+function toggleSidebar(window, sidebar) {
+ // TODO: make sure this is not private
+ window = window || getMostRecentBrowserWindow();
+ if (isSidebarShowing(window, sidebar)) {
+ return hideSidebar(window, sidebar);
+ }
+ return showSidebar(window, sidebar);
+}
+toggle.define(Sidebar, toggleSidebar.bind(null, null));
diff --git a/components/jetpack/sdk/ui/sidebar/actions.js b/components/jetpack/sdk/ui/sidebar/actions.js
new file mode 100644
index 000000000..4a52984c9
--- /dev/null
+++ b/components/jetpack/sdk/ui/sidebar/actions.js
@@ -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/. */
+'use strict';
+
+const method = require('../../../method/core');
+
+exports.show = method('show');
+exports.hide = method('hide');
+exports.toggle = method('toggle');
diff --git a/components/jetpack/sdk/ui/sidebar/contract.js b/components/jetpack/sdk/ui/sidebar/contract.js
new file mode 100644
index 000000000..b59c37c0b
--- /dev/null
+++ b/components/jetpack/sdk/ui/sidebar/contract.js
@@ -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/. */
+'use strict';
+
+const { contract } = require('../../util/contract');
+const { isValidURI, URL, isLocalURL } = require('../../url');
+const { isNil, isObject, isString } = require('../../lang/type');
+
+exports.contract = contract({
+ id: {
+ is: [ 'string', 'undefined' ],
+ ok: v => /^[a-z0-9-_]+$/i.test(v),
+ msg: 'The option "id" must be a valid alphanumeric id (hyphens and ' +
+ 'underscores are allowed).'
+ },
+ title: {
+ is: [ 'string' ],
+ ok: v => v.length
+ },
+ url: {
+ is: [ 'string' ],
+ ok: v => isLocalURL(v),
+ map: v => v.toString(),
+ msg: 'The option "url" must be a valid local URI.'
+ }
+});
diff --git a/components/jetpack/sdk/ui/sidebar/namespace.js b/components/jetpack/sdk/ui/sidebar/namespace.js
new file mode 100644
index 000000000..d79725d1a
--- /dev/null
+++ b/components/jetpack/sdk/ui/sidebar/namespace.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/. */
+'use strict';
+
+const models = exports.models = new WeakMap();
+const views = exports.views = new WeakMap();
+exports.buttons = new WeakMap();
+
+exports.viewsFor = function viewsFor(sidebar) {
+ return views.get(sidebar);
+};
+exports.modelFor = function modelFor(sidebar) {
+ return models.get(sidebar);
+};
diff --git a/components/jetpack/sdk/ui/sidebar/utils.js b/components/jetpack/sdk/ui/sidebar/utils.js
new file mode 100644
index 000000000..d6145c32e
--- /dev/null
+++ b/components/jetpack/sdk/ui/sidebar/utils.js
@@ -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/. */
+'use strict';
+
+const method = require('../../../method/core');
+
+exports.isShowing = method('isShowing');
diff --git a/components/jetpack/sdk/ui/sidebar/view.js b/components/jetpack/sdk/ui/sidebar/view.js
new file mode 100644
index 000000000..c91e69d3d
--- /dev/null
+++ b/components/jetpack/sdk/ui/sidebar/view.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/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'unstable',
+ 'engines': {
+ 'Firefox': '*'
+ }
+};
+
+const { models, buttons, views, viewsFor, modelFor } = require('./namespace');
+const { isBrowser, getMostRecentBrowserWindow, windows, isWindowPrivate } = require('../../window/utils');
+const { setStateFor } = require('../state');
+const { defer } = require('../../core/promise');
+const { isPrivateBrowsingSupported, data } = require('../../self');
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const WEB_PANEL_BROWSER_ID = 'web-panels-browser';
+
+const resolveURL = (url) => url ? data.url(url) : url;
+
+function create(window, details) {
+ let id = makeID(details.id);
+ let { document } = window;
+
+ if (document.getElementById(id))
+ throw new Error('The ID "' + details.id + '" seems already used.');
+
+ let menuitem = document.createElementNS(XUL_NS, 'menuitem');
+ menuitem.setAttribute('id', id);
+ menuitem.setAttribute('label', details.title);
+ menuitem.setAttribute('sidebarurl', resolveURL(details.sidebarurl));
+ menuitem.setAttribute('checked', 'false');
+ menuitem.setAttribute('type', 'checkbox');
+ menuitem.setAttribute('group', 'sidebar');
+ menuitem.setAttribute('autoCheck', 'false');
+
+ document.getElementById('viewSidebarMenu').appendChild(menuitem);
+
+ return menuitem;
+}
+exports.create = create;
+
+function dispose(menuitem) {
+ menuitem.parentNode.removeChild(menuitem);
+}
+exports.dispose = dispose;
+
+function updateTitle(sidebar, title) {
+ let button = buttons.get(sidebar);
+
+ for (let window of windows(null, { includePrivate: true })) {
+ let { document } = window;
+
+ // update the button
+ if (button) {
+ setStateFor(button, window, { label: title });
+ }
+
+ // update the menuitem
+ let mi = document.getElementById(makeID(sidebar.id));
+ if (mi) {
+ mi.setAttribute('label', title)
+ }
+
+ // update sidebar, if showing
+ if (isSidebarShowing(window, sidebar)) {
+ document.getElementById('sidebar-title').setAttribute('value', title);
+ }
+ }
+}
+exports.updateTitle = updateTitle;
+
+function updateURL(sidebar, url) {
+ let eleID = makeID(sidebar.id);
+
+ url = resolveURL(url);
+
+ for (let window of windows(null, { includePrivate: true })) {
+ // update the menuitem
+ let mi = window.document.getElementById(eleID);
+ if (mi) {
+ mi.setAttribute('sidebarurl', url)
+ }
+
+ // update sidebar, if showing
+ if (isSidebarShowing(window, sidebar)) {
+ showSidebar(window, sidebar, url);
+ }
+ }
+}
+exports.updateURL = updateURL;
+
+function isSidebarShowing(window, sidebar) {
+ let win = window || getMostRecentBrowserWindow();
+
+ // make sure there is a window
+ if (!win) {
+ return false;
+ }
+
+ // make sure there is a sidebar for the window
+ let sb = win.document.getElementById('sidebar');
+ let sidebarTitle = win.document.getElementById('sidebar-title');
+ if (!(sb && sidebarTitle)) {
+ return false;
+ }
+
+ // checks if the sidebar box is hidden
+ let sbb = win.document.getElementById('sidebar-box');
+ if (!sbb || sbb.hidden) {
+ return false;
+ }
+
+ if (sidebarTitle.value == modelFor(sidebar).title) {
+ let url = resolveURL(modelFor(sidebar).url);
+
+ // checks if the sidebar is loading
+ if (win.gWebPanelURI == url) {
+ return true;
+ }
+
+ // checks if the sidebar loaded already
+ let ele = sb.contentDocument && sb.contentDocument.getElementById(WEB_PANEL_BROWSER_ID);
+ if (!ele) {
+ return false;
+ }
+
+ if (ele.getAttribute('cachedurl') == url) {
+ return true;
+ }
+
+ if (ele && ele.contentWindow && ele.contentWindow.location == url) {
+ return true;
+ }
+ }
+
+ // default
+ return false;
+}
+exports.isSidebarShowing = isSidebarShowing;
+
+function showSidebar(window, sidebar, newURL) {
+ window = window || getMostRecentBrowserWindow();
+
+ let { promise, resolve, reject } = defer();
+ let model = modelFor(sidebar);
+
+ if (!newURL && isSidebarShowing(window, sidebar)) {
+ resolve({});
+ }
+ else if (!isPrivateBrowsingSupported && isWindowPrivate(window)) {
+ reject(Error('You cannot show a sidebar on private windows'));
+ }
+ else {
+ sidebar.once('show', resolve);
+
+ let menuitem = window.document.getElementById(makeID(model.id));
+ menuitem.setAttribute('checked', true);
+
+ window.openWebPanel(model.title, resolveURL(newURL || model.url));
+ }
+
+ return promise;
+}
+exports.showSidebar = showSidebar;
+
+
+function hideSidebar(window, sidebar) {
+ window = window || getMostRecentBrowserWindow();
+
+ let { promise, resolve, reject } = defer();
+
+ if (!isSidebarShowing(window, sidebar)) {
+ reject(Error('The sidebar is already hidden'));
+ }
+ else {
+ sidebar.once('hide', resolve);
+
+ // Below was taken from http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#4775
+ // the code for window.todggleSideBar()..
+ let { document } = window;
+ let sidebarEle = document.getElementById('sidebar');
+ let sidebarTitle = document.getElementById('sidebar-title');
+ let sidebarBox = document.getElementById('sidebar-box');
+ let sidebarSplitter = document.getElementById('sidebar-splitter');
+ let commandID = sidebarBox.getAttribute('sidebarcommand');
+ let sidebarBroadcaster = document.getElementById(commandID);
+
+ sidebarBox.hidden = true;
+ sidebarSplitter.hidden = true;
+
+ sidebarEle.setAttribute('src', 'about:blank');
+ //sidebarEle.docShell.createAboutBlankContentViewer(null);
+
+ sidebarBroadcaster.removeAttribute('checked');
+ sidebarBox.setAttribute('sidebarcommand', '');
+ sidebarTitle.value = '';
+ sidebarBox.hidden = true;
+ sidebarSplitter.hidden = true;
+
+ // TODO: perhaps this isn't necessary if the window is not most recent?
+ window.gBrowser.selectedBrowser.focus();
+ }
+
+ return promise;
+}
+exports.hideSidebar = hideSidebar;
+
+function makeID(id) {
+ return 'jetpack-sidebar-' + id;
+}
diff --git a/components/jetpack/sdk/ui/state.js b/components/jetpack/sdk/ui/state.js
new file mode 100644
index 000000000..c90d4283d
--- /dev/null
+++ b/components/jetpack/sdk/ui/state.js
@@ -0,0 +1,240 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+// The Button module currently supports only Firefox.
+// See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
+module.metadata = {
+ 'stability': 'experimental',
+ 'engines': {
+ 'Palemoon': '*',
+ 'Firefox': '*',
+ 'SeaMonkey': '*',
+ 'Thunderbird': '*'
+ }
+};
+
+const { Ci } = require('chrome');
+
+const events = require('../event/utils');
+const { events: browserEvents } = require('../browser/events');
+const { events: tabEvents } = require('../tab/events');
+const { events: stateEvents } = require('./state/events');
+
+const { windows, isInteractive, getFocusedBrowser } = require('../window/utils');
+const { getActiveTab, getOwnerWindow } = require('../tabs/utils');
+
+const { ignoreWindow } = require('../private-browsing/utils');
+
+const { freeze } = Object;
+const { merge } = require('../util/object');
+const { on, off, emit } = require('../event/core');
+
+const { add, remove, has, clear, iterator } = require('../lang/weak-set');
+const { isNil } = require('../lang/type');
+
+const { viewFor } = require('../view/core');
+
+const components = new WeakMap();
+
+const ERR_UNREGISTERED = 'The state cannot be set or get. ' +
+ 'The object may be not be registered, or may already have been unloaded.';
+
+const ERR_INVALID_TARGET = 'The state cannot be set or get for this target.' +
+ 'Only window, tab and registered component are valid targets.';
+
+const isWindow = thing => thing instanceof Ci.nsIDOMWindow;
+const isTab = thing => thing.tagName && thing.tagName.toLowerCase() === 'tab';
+const isActiveTab = thing => isTab(thing) && thing === getActiveTab(getOwnerWindow(thing));
+const isEnumerable = window => !ignoreWindow(window);
+const browsers = _ =>
+ windows('navigator:browser', { includePrivate: true }).filter(isInteractive);
+const getMostRecentTab = _ => getActiveTab(getFocusedBrowser());
+
+function getStateFor(component, target) {
+ if (!isRegistered(component))
+ throw new Error(ERR_UNREGISTERED);
+
+ if (!components.has(component))
+ return null;
+
+ let states = components.get(component);
+
+ if (target) {
+ if (isTab(target) || isWindow(target) || target === component)
+ return states.get(target) || null;
+ else
+ throw new Error(ERR_INVALID_TARGET);
+ }
+
+ return null;
+}
+exports.getStateFor = getStateFor;
+
+function getDerivedStateFor(component, target) {
+ if (!isRegistered(component))
+ throw new Error(ERR_UNREGISTERED);
+
+ if (!components.has(component))
+ return null;
+
+ let states = components.get(component);
+
+ let componentState = states.get(component);
+ let windowState = null;
+ let tabState = null;
+
+ if (target) {
+ // has a target
+ if (isTab(target)) {
+ windowState = states.get(getOwnerWindow(target), null);
+
+ if (states.has(target)) {
+ // we have a tab state
+ tabState = states.get(target);
+ }
+ }
+ else if (isWindow(target) && states.has(target)) {
+ // we have a window state
+ windowState = states.get(target);
+ }
+ }
+
+ return freeze(merge({}, componentState, windowState, tabState));
+}
+exports.getDerivedStateFor = getDerivedStateFor;
+
+function setStateFor(component, target, state) {
+ if (!isRegistered(component))
+ throw new Error(ERR_UNREGISTERED);
+
+ let isComponentState = target === component;
+ let targetWindows = isWindow(target) ? [target] :
+ isActiveTab(target) ? [getOwnerWindow(target)] :
+ isComponentState ? browsers() :
+ isTab(target) ? [] :
+ null;
+
+ if (!targetWindows)
+ throw new Error(ERR_INVALID_TARGET);
+
+ // initialize the state's map
+ if (!components.has(component))
+ components.set(component, new WeakMap());
+
+ let states = components.get(component);
+
+ if (state === null && !isComponentState) // component state can't be deleted
+ states.delete(target);
+ else {
+ let base = isComponentState ? states.get(target) : null;
+ states.set(target, freeze(merge({}, base, state)));
+ }
+
+ render(component, targetWindows);
+}
+exports.setStateFor = setStateFor;
+
+function render(component, targetWindows) {
+ targetWindows = targetWindows ? [].concat(targetWindows) : browsers();
+
+ for (let window of targetWindows.filter(isEnumerable)) {
+ let tabState = getDerivedStateFor(component, getActiveTab(window));
+
+ emit(stateEvents, 'data', {
+ type: 'render',
+ target: component,
+ window: window,
+ state: tabState
+ });
+
+ }
+}
+exports.render = render;
+
+function properties(contract) {
+ let { rules } = contract;
+ let descriptor = Object.keys(rules).reduce(function(descriptor, name) {
+ descriptor[name] = {
+ get: function() { return getDerivedStateFor(this)[name] },
+ set: function(value) {
+ let changed = {};
+ changed[name] = value;
+
+ setStateFor(this, this, contract(changed));
+ }
+ }
+ return descriptor;
+ }, {});
+
+ return Object.create(Object.prototype, descriptor);
+}
+exports.properties = properties;
+
+function state(contract) {
+ return {
+ state: function state(target, state) {
+ let nativeTarget = target === 'window' ? getFocusedBrowser()
+ : target === 'tab' ? getMostRecentTab()
+ : target === this ? null
+ : viewFor(target);
+
+ if (!nativeTarget && target !== this && !isNil(target))
+ throw new Error(ERR_INVALID_TARGET);
+
+ target = nativeTarget || target;
+
+ // jquery style
+ return arguments.length < 2
+ ? getDerivedStateFor(this, target)
+ : setStateFor(this, target, contract(state))
+ }
+ }
+}
+exports.state = state;
+
+const register = (component, state) => {
+ add(components, component);
+ setStateFor(component, component, state);
+}
+exports.register = register;
+
+const unregister = component => {
+ remove(components, component);
+}
+exports.unregister = unregister;
+
+const isRegistered = component => has(components, component);
+exports.isRegistered = isRegistered;
+
+var tabSelect = events.filter(tabEvents, e => e.type === 'TabSelect');
+var tabClose = events.filter(tabEvents, e => e.type === 'TabClose');
+var windowOpen = events.filter(browserEvents, e => e.type === 'load');
+var windowClose = events.filter(browserEvents, e => e.type === 'close');
+
+var close = events.merge([tabClose, windowClose]);
+var activate = events.merge([windowOpen, tabSelect]);
+
+on(activate, 'data', ({target}) => {
+ let [window, tab] = isWindow(target)
+ ? [target, getActiveTab(target)]
+ : [getOwnerWindow(target), target];
+
+ if (ignoreWindow(window)) return;
+
+ for (let component of iterator(components)) {
+ emit(stateEvents, 'data', {
+ type: 'render',
+ target: component,
+ window: window,
+ state: getDerivedStateFor(component, tab)
+ });
+ }
+});
+
+on(close, 'data', function({target}) {
+ for (let component of iterator(components)) {
+ components.get(component).delete(target);
+ }
+});
diff --git a/components/jetpack/sdk/ui/state/events.js b/components/jetpack/sdk/ui/state/events.js
new file mode 100644
index 000000000..34d14be3a
--- /dev/null
+++ b/components/jetpack/sdk/ui/state/events.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+module.metadata = {
+ 'stability': 'experimental',
+ 'engines': {
+ 'Palemoon': '*',
+ 'Firefox': '*',
+ 'SeaMonkey': '*',
+ 'Thunderbird': '*'
+ }
+};
+
+var channel = {};
+
+exports.events = channel;
diff --git a/components/jetpack/sdk/ui/toolbar.js b/components/jetpack/sdk/ui/toolbar.js
new file mode 100644
index 000000000..c1becab2d
--- /dev/null
+++ b/components/jetpack/sdk/ui/toolbar.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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Firefox": "> 28"
+ }
+};
+
+const { Toolbar } = require("./toolbar/model");
+require("./toolbar/view");
+
+exports.Toolbar = Toolbar;
diff --git a/components/jetpack/sdk/ui/toolbar/model.js b/components/jetpack/sdk/ui/toolbar/model.js
new file mode 100644
index 000000000..5c5428606
--- /dev/null
+++ b/components/jetpack/sdk/ui/toolbar/model.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Firefox": "> 28"
+ }
+};
+
+const { Class } = require("../../core/heritage");
+const { EventTarget } = require("../../event/target");
+const { off, setListeners, emit } = require("../../event/core");
+const { Reactor, foldp, merges, send } = require("../../event/utils");
+const { Disposable } = require("../../core/disposable");
+const { InputPort } = require("../../input/system");
+const { OutputPort } = require("../../output/system");
+const { identify } = require("../id");
+const { pairs, object, map, each } = require("../../util/sequence");
+const { patch, diff } = require("diffpatcher/index");
+const { contract } = require("../../util/contract");
+const { id: addonID } = require("../../self");
+
+// Input state is accumulated from the input received form the toolbar
+// view code & local output. Merging local output reflects local state
+// changes without complete roundloop.
+const input = foldp(patch, {}, new InputPort({ id: "toolbar-changed" }));
+const output = new OutputPort({ id: "toolbar-change" });
+
+// Takes toolbar title and normalizes is to an
+// identifier, also prefixes with add-on id.
+const titleToId = title =>
+ ("toolbar-" + addonID + "-" + title).
+ toLowerCase().
+ replace(/\s/g, "-").
+ replace(/[^A-Za-z0-9_\-]/g, "");
+
+const validate = contract({
+ title: {
+ is: ["string"],
+ ok: x => x.length > 0,
+ msg: "The `option.title` string must be provided"
+ },
+ items: {
+ is:["undefined", "object", "array"],
+ msg: "The `options.items` must be iterable sequence of items"
+ },
+ hidden: {
+ is: ["boolean", "undefined"],
+ msg: "The `options.hidden` must be boolean"
+ }
+});
+
+// Toolbars is a mapping between `toolbar.id` & `toolbar` instances,
+// which is used to find intstance for dispatching events.
+var toolbars = new Map();
+
+const Toolbar = Class({
+ extends: EventTarget,
+ implements: [Disposable],
+ initialize: function(params={}) {
+ const options = validate(params);
+ const id = titleToId(options.title);
+
+ if (toolbars.has(id))
+ throw Error("Toolbar with this id already exists: " + id);
+
+ // Set of the items in the toolbar isn't mutable, as a matter of fact
+ // it just defines desired set of items, actual set is under users
+ // control. Conver test to an array and freeze to make sure users won't
+ // try mess with it.
+ const items = Object.freeze(options.items ? [...options.items] : []);
+
+ const initial = {
+ id: id,
+ title: options.title,
+ // By default toolbars are visible when add-on is installed, unless
+ // add-on authors decides it should be hidden. From that point on
+ // user is in control.
+ collapsed: !!options.hidden,
+ // In terms of state only identifiers of items matter.
+ items: items.map(identify)
+ };
+
+ this.id = id;
+ this.items = items;
+
+ toolbars.set(id, this);
+ setListeners(this, params);
+
+ // Send initial state to the host so it can reflect it
+ // into a user interface.
+ send(output, object([id, initial]));
+ },
+
+ get title() {
+ const state = reactor.value[this.id];
+ return state && state.title;
+ },
+ get hidden() {
+ const state = reactor.value[this.id];
+ return state && state.collapsed;
+ },
+
+ destroy: function() {
+ send(output, object([this.id, null]));
+ },
+ // `JSON.stringify` serializes objects based of the return
+ // value of this method. For convinienc we provide this method
+ // to serialize actual state data. Note: items will also be
+ // serialized so they should probably implement `toJSON`.
+ toJSON: function() {
+ return {
+ id: this.id,
+ title: this.title,
+ hidden: this.hidden,
+ items: this.items
+ };
+ }
+});
+exports.Toolbar = Toolbar;
+identify.define(Toolbar, toolbar => toolbar.id);
+
+const dispose = toolbar => {
+ toolbars.delete(toolbar.id);
+ emit(toolbar, "detach");
+ off(toolbar);
+};
+
+const reactor = new Reactor({
+ onStep: (present, past) => {
+ const delta = diff(past, present);
+
+ each(([id, update]) => {
+ const toolbar = toolbars.get(id);
+
+ // Remove
+ if (!update)
+ dispose(toolbar);
+ // Add
+ else if (!past[id])
+ emit(toolbar, "attach");
+ // Update
+ else
+ emit(toolbar, update.collapsed ? "hide" : "show", toolbar);
+ }, pairs(delta));
+ }
+});
+reactor.run(input);
diff --git a/components/jetpack/sdk/ui/toolbar/view.js b/components/jetpack/sdk/ui/toolbar/view.js
new file mode 100644
index 000000000..4ef0c3d46
--- /dev/null
+++ b/components/jetpack/sdk/ui/toolbar/view.js
@@ -0,0 +1,248 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental",
+ "engines": {
+ "Firefox": "> 28"
+ }
+};
+
+const { Cu } = require("chrome");
+const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
+const { subscribe, send, Reactor, foldp, lift, merges } = require("../../event/utils");
+const { InputPort } = require("../../input/system");
+const { OutputPort } = require("../../output/system");
+const { Interactive } = require("../../input/browser");
+const { CustomizationInput } = require("../../input/customizable-ui");
+const { pairs, map, isEmpty, object,
+ each, keys, values } = require("../../util/sequence");
+const { curry, flip } = require("../../lang/functional");
+const { patch, diff } = require("diffpatcher/index");
+const prefs = require("../../preferences/service");
+const { getByOuterId } = require("../../window/utils");
+const { ignoreWindow } = require('../../private-browsing/utils');
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const PREF_ROOT = "extensions.sdk-toolbar-collapsed.";
+
+
+// There are two output ports one for publishing changes that occured
+// and the other for change requests. Later is synchronous and is only
+// consumed here. Note: it needs to be synchronous to avoid race conditions
+// when `collapsed` attribute changes are caused by user interaction and
+// toolbar is destroyed between the ticks.
+const output = new OutputPort({ id: "toolbar-changed" });
+const syncoutput = new OutputPort({ id: "toolbar-change", sync: true });
+
+// Merge disptached changes and recevied changes from models to keep state up to
+// date.
+const Toolbars = foldp(patch, {}, merges([new InputPort({ id: "toolbar-changed" }),
+ new InputPort({ id: "toolbar-change" })]));
+const State = lift((toolbars, windows, customizable) =>
+ ({windows: windows, toolbars: toolbars, customizable: customizable}),
+ Toolbars, Interactive, new CustomizationInput());
+
+// Shared event handler that makes `event.target.parent` collapsed.
+// Used as toolbar's close buttons click handler.
+const collapseToolbar = event => {
+ const toolbar = event.target.parentNode;
+ toolbar.collapsed = true;
+};
+
+const parseAttribute = x =>
+ x === "true" ? true :
+ x === "false" ? false :
+ x === "" ? null :
+ x;
+
+// Shared mutation observer that is used to observe `toolbar` node's
+// attribute mutations. Mutations are aggregated in the `delta` hash
+// and send to `ToolbarStateChanged` channel to let model know state
+// has changed.
+const attributesChanged = mutations => {
+ const delta = mutations.reduce((changes, {attributeName, target}) => {
+ const id = target.id;
+ const field = attributeName === "toolbarname" ? "title" : attributeName;
+ let change = changes[id] || (changes[id] = {});
+ change[field] = parseAttribute(target.getAttribute(attributeName));
+ return changes;
+ }, {});
+
+ // Calculate what are the updates from the current state and if there are
+ // any send them.
+ const updates = diff(reactor.value, patch(reactor.value, delta));
+
+ if (!isEmpty(pairs(updates))) {
+ // TODO: Consider sending sync to make sure that there won't be a new
+ // update doing a delete in the meantime.
+ send(syncoutput, updates);
+ }
+};
+
+
+// Utility function creates `toolbar` with a "close" button and returns
+// it back. In addition it set's up a listener and observer to communicate
+// state changes.
+const addView = curry((options, {document, window}) => {
+ if (ignoreWindow(window))
+ return;
+
+ let view = document.createElementNS(XUL_NS, "toolbar");
+ view.setAttribute("id", options.id);
+ view.setAttribute("collapsed", options.collapsed);
+ view.setAttribute("toolbarname", options.title);
+ view.setAttribute("pack", "end");
+ view.setAttribute("customizable", "false");
+ view.setAttribute("style", "padding: 2px 0; max-height: 40px;");
+ view.setAttribute("mode", "icons");
+ view.setAttribute("iconsize", "small");
+ view.setAttribute("context", "toolbar-context-menu");
+ view.setAttribute("class", "chromeclass-toolbar");
+
+ let label = document.createElementNS(XUL_NS, "label");
+ label.setAttribute("value", options.title);
+ label.setAttribute("collapsed", "true");
+ view.appendChild(label);
+
+ let closeButton = document.createElementNS(XUL_NS, "toolbarbutton");
+ closeButton.setAttribute("id", "close-" + options.id);
+ closeButton.setAttribute("class", "close-icon");
+ closeButton.setAttribute("customizable", false);
+ closeButton.addEventListener("command", collapseToolbar);
+
+ view.appendChild(closeButton);
+
+ // In order to have a close button not costumizable, aligned on the right,
+ // leaving the customizable capabilities of Australis, we need to create
+ // a toolbar inside a toolbar.
+ // This is should be a temporary hack, we should have a proper XBL for toolbar
+ // instead. See:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=982005
+ let toolbar = document.createElementNS(XUL_NS, "toolbar");
+ toolbar.setAttribute("id", "inner-" + options.id);
+ toolbar.setAttribute("defaultset", options.items.join(","));
+ toolbar.setAttribute("customizable", "true");
+ toolbar.setAttribute("style", "-moz-appearance: none; overflow: hidden");
+ toolbar.setAttribute("mode", "icons");
+ toolbar.setAttribute("iconsize", "small");
+ toolbar.setAttribute("context", "toolbar-context-menu");
+ toolbar.setAttribute("flex", "1");
+
+ view.insertBefore(toolbar, closeButton);
+
+ const observer = new document.defaultView.MutationObserver(attributesChanged);
+ observer.observe(view, { attributes: true,
+ attributeFilter: ["collapsed", "toolbarname"] });
+
+ const toolbox = document.getElementById("navigator-toolbox");
+ toolbox.appendChild(view);
+});
+const viewAdd = curry(flip(addView));
+
+const removeView = curry((id, {document}) => {
+ const view = document.getElementById(id);
+ if (view) view.remove();
+});
+
+const updateView = curry((id, {title, collapsed, isCustomizing}, {document}) => {
+ const view = document.getElementById(id);
+
+ if (!view)
+ return;
+
+ if (title)
+ view.setAttribute("toolbarname", title);
+
+ if (collapsed !== void(0))
+ view.setAttribute("collapsed", Boolean(collapsed));
+
+ if (isCustomizing !== void(0)) {
+ view.querySelector("label").collapsed = !isCustomizing;
+ view.querySelector("toolbar").style.visibility = isCustomizing
+ ? "hidden" : "visible";
+ }
+});
+
+const viewUpdate = curry(flip(updateView));
+
+// Utility function used to register toolbar into CustomizableUI.
+const registerToolbar = state => {
+ // If it's first additon register toolbar as customizableUI component.
+ CustomizableUI.registerArea("inner-" + state.id, {
+ type: CustomizableUI.TYPE_TOOLBAR,
+ legacy: true,
+ defaultPlacements: [...state.items]
+ });
+};
+// Utility function used to unregister toolbar from the CustomizableUI.
+const unregisterToolbar = CustomizableUI.unregisterArea;
+
+const reactor = new Reactor({
+ onStep: (present, past) => {
+ const delta = diff(past, present);
+
+ each(([id, update]) => {
+ // If update is `null` toolbar is removed, in such case
+ // we unregister toolbar and remove it from each window
+ // it was added to.
+ if (update === null) {
+ unregisterToolbar("inner-" + id);
+ each(removeView(id), values(past.windows));
+
+ send(output, object([id, null]));
+ }
+ else if (past.toolbars[id]) {
+ // If `collapsed` state for toolbar was updated, persist
+ // it for a future sessions.
+ if (update.collapsed !== void(0))
+ prefs.set(PREF_ROOT + id, update.collapsed);
+
+ // Reflect update in each window it was added to.
+ each(updateView(id, update), values(past.windows));
+
+ send(output, object([id, update]));
+ }
+ // Hack: Mutation observers are invoked async, which means that if
+ // client does `hide(toolbar)` & then `toolbar.destroy()` by the
+ // time we'll get update for `collapsed` toolbar will be removed.
+ // For now we check if `update.id` is present which will be undefined
+ // in such cases.
+ else if (update.id) {
+ // If it is a new toolbar we create initial state by overriding
+ // `collapsed` filed with value persisted in previous sessions.
+ const state = patch(update, {
+ collapsed: prefs.get(PREF_ROOT + id, update.collapsed),
+ });
+
+ // Register toolbar and add it each window known in the past
+ // (note that new windows if any will be handled in loop below).
+ registerToolbar(state);
+ each(addView(state), values(past.windows));
+
+ send(output, object([state.id, state]));
+ }
+ }, pairs(delta.toolbars));
+
+ // Add views to every window that was added.
+ each(window => {
+ if (window)
+ each(viewAdd(window), values(past.toolbars));
+ }, values(delta.windows));
+
+ each(([id, isCustomizing]) => {
+ each(viewUpdate(getByOuterId(id), {isCustomizing: !!isCustomizing}),
+ keys(present.toolbars));
+
+ }, pairs(delta.customizable))
+ },
+ onEnd: state => {
+ each(id => {
+ unregisterToolbar("inner-" + id);
+ each(removeView(id), values(state.windows));
+ }, keys(state.toolbars));
+ }
+});
+reactor.run(State);
diff --git a/components/jetpack/sdk/uri/resource.js b/components/jetpack/sdk/uri/resource.js
new file mode 100644
index 000000000..8a1dcbf2c
--- /dev/null
+++ b/components/jetpack/sdk/uri/resource.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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const {Cc, Ci} = require("chrome");
+const ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+const resourceHandler = ioService.getProtocolHandler("resource").
+ QueryInterface(Ci.nsIResProtocolHandler);
+
+const URI = (uri, base=null) =>
+ ioService.newURI(uri, null, base && URI(base))
+
+const mount = (domain, uri) =>
+ resourceHandler.setSubstitution(domain, ioService.newURI(uri, null, null));
+exports.mount = mount;
+
+const unmount = (domain, uri) =>
+ resourceHandler.setSubstitution(domain, null);
+exports.unmount = unmount;
+
+const domain = 1;
+const path = 2;
+const resolve = (uri) => {
+ const match = /resource\:\/\/([^\/]+)\/{0,1}([\s\S]*)/.exec(uri);
+ const domain = match && match[1];
+ const path = match && match[2];
+ return !match ? null :
+ !resourceHandler.hasSubstitution(domain) ? null :
+ resourceHandler.resolveURI(URI(`/${path}`, `resource://${domain}/`));
+}
+exports.resolve = resolve;
diff --git a/components/jetpack/sdk/url.js b/components/jetpack/sdk/url.js
new file mode 100644
index 000000000..ae16ac4a8
--- /dev/null
+++ b/components/jetpack/sdk/url.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci, Cr, Cu } = require("chrome");
+
+const { Class } = require("./core/heritage");
+const base64 = require("./base64");
+var tlds = Cc["@mozilla.org/network/effective-tld-service;1"]
+ .getService(Ci.nsIEffectiveTLDService);
+
+var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+
+var resProt = ios.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler);
+
+var URLParser = Cc["@mozilla.org/network/url-parser;1?auth=no"]
+ .getService(Ci.nsIURLParser);
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+
+function newURI(uriStr, base) {
+ try {
+ let baseURI = base ? ios.newURI(base, null, null) : null;
+ return ios.newURI(uriStr, null, baseURI);
+ }
+ catch (e) {
+ if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
+ throw new Error("malformed URI: " + uriStr);
+ }
+ if (e.result == Cr.NS_ERROR_FAILURE ||
+ e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
+ throw new Error("invalid URI: " + uriStr);
+ }
+ }
+}
+
+function resolveResourceURI(uri) {
+ var resolved;
+ try {
+ resolved = resProt.resolveURI(uri);
+ }
+ catch (e) {
+ if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw new Error("resource does not exist: " + uri.spec);
+ }
+ }
+ return resolved;
+}
+
+var fromFilename = exports.fromFilename = function fromFilename(path) {
+ var file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ return ios.newFileURI(file).spec;
+};
+
+var toFilename = exports.toFilename = function toFilename(url) {
+ var uri = newURI(url);
+ if (uri.scheme == "resource")
+ uri = newURI(resolveResourceURI(uri));
+ if (uri.scheme == "chrome") {
+ var channel = ios.newChannelFromURI2(uri,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ try {
+ channel = channel.QueryInterface(Ci.nsIFileChannel);
+ return channel.file.path;
+ }
+ catch (e) {
+ if (e.result == Cr.NS_NOINTERFACE) {
+ throw new Error("chrome url isn't on filesystem: " + url);
+ }
+ }
+ }
+ if (uri.scheme == "file") {
+ var file = uri.QueryInterface(Ci.nsIFileURL).file;
+ return file.path;
+ }
+ throw new Error("cannot map to filename: " + url);
+};
+
+function URL(url, base) {
+ if (!(this instanceof URL)) {
+ return new URL(url, base);
+ }
+
+ var uri = newURI(url, base);
+
+ var userPass = null;
+ try {
+ userPass = uri.userPass ? uri.userPass : null;
+ }
+ catch (e) {
+ if (e.result != Cr.NS_ERROR_FAILURE) {
+ throw e;
+ }
+ }
+
+ var host = null;
+ try {
+ host = uri.host;
+ }
+ catch (e) {
+ if (e.result != Cr.NS_ERROR_FAILURE) {
+ throw e;
+ }
+ }
+
+ var port = null;
+ try {
+ port = uri.port == -1 ? null : uri.port;
+ }
+ catch (e) {
+ if (e.result != Cr.NS_ERROR_FAILURE) {
+ throw e;
+ }
+ }
+
+ let fileName = "/";
+ try {
+ fileName = uri.QueryInterface(Ci.nsIURL).fileName;
+ } catch (e) {
+ if (e.result != Cr.NS_NOINTERFACE) {
+ throw e;
+ }
+ }
+
+ let uriData = [uri.path, uri.path.length, {}, {}, {}, {}, {}, {}];
+ URLParser.parsePath.apply(URLParser, uriData);
+ let [{ value: filepathPos }, { value: filepathLen },
+ { value: queryPos }, { value: queryLen },
+ { value: refPos }, { value: refLen }] = uriData.slice(2);
+
+ let hash = uri.ref ? "#" + uri.ref : "";
+ let pathname = uri.path.substr(filepathPos, filepathLen);
+ let search = uri.path.substr(queryPos, queryLen);
+ search = search ? "?" + search : "";
+
+ this.__defineGetter__("fileName", () => fileName);
+ this.__defineGetter__("scheme", () => uri.scheme);
+ this.__defineGetter__("userPass", () => userPass);
+ this.__defineGetter__("host", () => host);
+ this.__defineGetter__("hostname", () => host);
+ this.__defineGetter__("port", () => port);
+ this.__defineGetter__("path", () => uri.path);
+ this.__defineGetter__("pathname", () => pathname);
+ this.__defineGetter__("hash", () => hash);
+ this.__defineGetter__("href", () => uri.spec);
+ this.__defineGetter__("origin", () => uri.prePath);
+ this.__defineGetter__("protocol", () => uri.scheme + ":");
+ this.__defineGetter__("search", () => search);
+
+ Object.defineProperties(this, {
+ toString: {
+ value() {
+ return new String(uri.spec).toString();
+ },
+ enumerable: false
+ },
+ valueOf: {
+ value() {
+ return new String(uri.spec).valueOf();
+ },
+ enumerable: false
+ },
+ toSource: {
+ value() {
+ return new String(uri.spec).toSource();
+ },
+ enumerable: false
+ },
+ // makes more sense to flatten to string, easier to travel across JSON
+ toJSON: {
+ value() {
+ return new String(uri.spec).toString();
+ },
+ enumerable: false
+ }
+ });
+
+ return this;
+};
+
+URL.prototype = Object.create(String.prototype);
+exports.URL = URL;
+
+/**
+ * Parse and serialize a Data URL.
+ *
+ * See: http://tools.ietf.org/html/rfc2397
+ *
+ * Note: Could be extended in the future to decode / encode automatically binary
+ * data.
+ */
+const DataURL = Class({
+
+ get base64 () {
+ return "base64" in this.parameters;
+ },
+
+ set base64 (value) {
+ if (value)
+ this.parameters["base64"] = "";
+ else
+ delete this.parameters["base64"];
+ },
+ /**
+ * Initialize the Data URL object. If a uri is given, it will be parsed.
+ *
+ * @param {String} [uri] The uri to parse
+ *
+ * @throws {URIError} if the Data URL is malformed
+ */
+ initialize: function(uri) {
+ // Due to bug 751834 it is not possible document and define these
+ // properties in the prototype.
+
+ /**
+ * An hashmap that contains the parameters of the Data URL. By default is
+ * empty, that accordingly to RFC is equivalent to {"charset" : "US-ASCII"}
+ */
+ this.parameters = {};
+
+ /**
+ * The MIME type of the data. By default is empty, that accordingly to RFC
+ * is equivalent to "text/plain"
+ */
+ this.mimeType = "";
+
+ /**
+ * The string that represent the data in the Data URL
+ */
+ this.data = "";
+
+ if (typeof uri === "undefined")
+ return;
+
+ uri = String(uri);
+
+ let matches = uri.match(/^data:([^,]*),(.*)$/i);
+
+ if (!matches)
+ throw new URIError("Malformed Data URL: " + uri);
+
+ let mediaType = matches[1].trim();
+
+ this.data = decodeURIComponent(matches[2].trim());
+
+ if (!mediaType)
+ return;
+
+ let parametersList = mediaType.split(";");
+
+ this.mimeType = parametersList.shift().trim();
+
+ for (let parameter, i = 0; parameter = parametersList[i++];) {
+ let pairs = parameter.split("=");
+ let name = pairs[0].trim();
+ let value = pairs.length > 1 ? decodeURIComponent(pairs[1].trim()) : "";
+
+ this.parameters[name] = value;
+ }
+
+ if (this.base64)
+ this.data = base64.decode(this.data);
+
+ },
+
+ /**
+ * Returns the object as a valid Data URL string
+ *
+ * @returns {String} The Data URL
+ */
+ toString : function() {
+ let parametersList = [];
+
+ for (let name in this.parameters) {
+ let encodedParameter = encodeURIComponent(name);
+ let value = this.parameters[name];
+
+ if (value)
+ encodedParameter += "=" + encodeURIComponent(value);
+
+ parametersList.push(encodedParameter);
+ }
+
+ // If there is at least a parameter, add an empty string in order
+ // to start with a `;` on join call.
+ if (parametersList.length > 0)
+ parametersList.unshift("");
+
+ let data = this.base64 ? base64.encode(this.data) : this.data;
+
+ return "data:" +
+ this.mimeType +
+ parametersList.join(";") + "," +
+ encodeURIComponent(data);
+ }
+});
+
+exports.DataURL = DataURL;
+
+var getTLD = exports.getTLD = function getTLD (url) {
+ let uri = newURI(url.toString());
+ let tld = null;
+ try {
+ tld = tlds.getPublicSuffix(uri);
+ }
+ catch (e) {
+ if (e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS &&
+ e.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
+ throw e;
+ }
+ }
+ return tld;
+};
+
+var isValidURI = exports.isValidURI = function (uri) {
+ try {
+ newURI(uri);
+ }
+ catch(e) {
+ return false;
+ }
+ return true;
+}
+
+function isLocalURL(url) {
+ if (String.indexOf(url, './') === 0)
+ return true;
+
+ try {
+ return ['resource', 'data', 'chrome'].indexOf(URL(url).scheme) > -1;
+ }
+ catch(e) {}
+
+ return false;
+}
+exports.isLocalURL = isLocalURL;
diff --git a/components/jetpack/sdk/url/utils.js b/components/jetpack/sdk/url/utils.js
new file mode 100644
index 000000000..aa5759204
--- /dev/null
+++ b/components/jetpack/sdk/url/utils.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Cc, Ci, Cr } = require("chrome");
+const IOService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+const { isValidURI } = require("../url");
+const { method } = require("../../method/core");
+
+function newURI (uri) {
+ if (!isValidURI(uri))
+ throw new Error("malformed URI: " + uri);
+ return IOService.newURI(uri, null, null);
+}
+exports.newURI = newURI;
+
+var getURL = method('sdk/url:getURL');
+getURL.define(String, url => url);
+getURL.define(function (object) {
+ return null;
+});
+exports.getURL = getURL;
diff --git a/components/jetpack/sdk/util/array.js b/components/jetpack/sdk/util/array.js
new file mode 100644
index 000000000..1d61a973e
--- /dev/null
+++ b/components/jetpack/sdk/util/array.js
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+/**
+ * Returns `true` if given `array` contain given `element` or `false`
+ * otherwise.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element being looked up.
+ * @returns {Boolean}
+ */
+var has = exports.has = function has(array, element) {
+ // shorter and faster equivalent of `array.indexOf(element) >= 0`
+ return !!~array.indexOf(element);
+};
+var hasAny = exports.hasAny = function hasAny(array, elements) {
+ if (arguments.length < 2)
+ return false;
+ if (!Array.isArray(elements))
+ elements = [ elements ];
+ return array.some(function (element) {
+ return has(elements, element);
+ });
+};
+
+/**
+ * Adds given `element` to the given `array` if it does not contain it yet.
+ * `true` is returned if element was added otherwise `false` is returned.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element to be added.
+ * @returns {Boolean}
+ */
+var add = exports.add = function add(array, element) {
+ var result;
+ if ((result = !has(array, element)))
+ array.push(element);
+
+ return result;
+};
+
+/**
+ * Removes first occurrence of the given `element` from the given `array`. If
+ * `array` does not contain given `element` `false` is returned otherwise
+ * `true` is returned.
+ * @param {Array} array
+ * Target array.
+ * @param {Object|String|Number|Boolean} element
+ * Element to be removed.
+ * @returns {Boolean}
+ */
+exports.remove = function remove(array, element) {
+ var result;
+ if ((result = has(array, element)))
+ array.splice(array.indexOf(element), 1);
+
+ return result;
+};
+
+/**
+ * Produces a duplicate-free version of the given `array`.
+ * @param {Array} array
+ * Source array.
+ * @returns {Array}
+ */
+function unique(array) {
+ return array.reduce(function(result, item) {
+ add(result, item);
+ return result;
+ }, []);
+};
+exports.unique = unique;
+
+/**
+ * Produce an array that contains the union: each distinct element from all
+ * of the passed-in arrays.
+ */
+function union() {
+ return unique(Array.concat.apply(null, arguments));
+};
+exports.union = union;
+
+exports.flatten = function flatten(array){
+ var flat = [];
+ for (var i = 0, l = array.length; i < l; i++) {
+ flat = flat.concat(Array.isArray(array[i]) ? flatten(array[i]) : array[i]);
+ }
+ return flat;
+};
+
+function fromIterator(iterator) {
+ let array = [];
+ if (iterator.__iterator__) {
+ for (let item of iterator)
+ array.push(item);
+ }
+ else {
+ for (let item of iterator)
+ array.push(item);
+ }
+ return array;
+}
+exports.fromIterator = fromIterator;
+
+function find(array, predicate, fallback) {
+ var index = 0;
+ var count = array.length;
+ while (index < count) {
+ var value = array[index];
+ if (predicate(value)) return value;
+ else index = index + 1;
+ }
+ return fallback;
+}
+exports.find = find;
diff --git a/components/jetpack/sdk/util/collection.js b/components/jetpack/sdk/util/collection.js
new file mode 100644
index 000000000..194a29470
--- /dev/null
+++ b/components/jetpack/sdk/util/collection.js
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+exports.Collection = Collection;
+
+/**
+ * Adds a collection property to the given object. Setting the property to a
+ * scalar value empties the collection and adds the value. Setting it to an
+ * array empties the collection and adds all the items in the array.
+ *
+ * @param obj
+ * The property will be defined on this object.
+ * @param propName
+ * The name of the property.
+ * @param array
+ * If given, this will be used as the collection's backing array.
+ */
+exports.addCollectionProperty = function addCollProperty(obj, propName, array) {
+ array = array || [];
+ let publicIface = new Collection(array);
+
+ Object.defineProperty(obj, propName, {
+ configurable: true,
+ enumerable: true,
+
+ set: function set(itemOrItems) {
+ array.splice(0, array.length);
+ publicIface.add(itemOrItems);
+ },
+
+ get: function get() {
+ return publicIface;
+ }
+ });
+};
+
+/**
+ * A collection is ordered, like an array, but its items are unique, like a set.
+ *
+ * @param array
+ * The collection is backed by an array. If this is given, it will be
+ * used as the backing array. This way the caller can fully control the
+ * collection. Otherwise a new empty array will be used, and no one but
+ * the collection will have access to it.
+ */
+function Collection(array) {
+ array = array || [];
+
+ /**
+ * Provides iteration over the collection. Items are yielded in the order
+ * they were added.
+ */
+ this.__iterator__ = function Collection___iterator__() {
+ let items = array.slice();
+ for (let i = 0; i < items.length; i++)
+ yield items[i];
+ };
+
+ /**
+ * The number of items in the collection.
+ */
+ this.__defineGetter__("length", function Collection_get_length() {
+ return array.length;
+ });
+
+ /**
+ * Adds a single item or an array of items to the collection. Any items
+ * already contained in the collection are ignored.
+ *
+ * @param itemOrItems
+ * An item or array of items.
+ * @return The collection.
+ */
+ this.add = function Collection_add(itemOrItems) {
+ let items = toArray(itemOrItems);
+ for (let i = 0; i < items.length; i++) {
+ let item = items[i];
+ if (array.indexOf(item) < 0)
+ array.push(item);
+ }
+ return this;
+ };
+
+ /**
+ * Removes a single item or an array of items from the collection. Any items
+ * not contained in the collection are ignored.
+ *
+ * @param itemOrItems
+ * An item or array of items.
+ * @return The collection.
+ */
+ this.remove = function Collection_remove(itemOrItems) {
+ let items = toArray(itemOrItems);
+ for (let i = 0; i < items.length; i++) {
+ let idx = array.indexOf(items[i]);
+ if (idx >= 0)
+ array.splice(idx, 1);
+ }
+ return this;
+ };
+};
+
+function toArray(itemOrItems) {
+ let isArr = itemOrItems &&
+ itemOrItems.constructor &&
+ itemOrItems.constructor.name === "Array";
+ return isArr ? itemOrItems : [itemOrItems];
+}
diff --git a/components/jetpack/sdk/util/contract.js b/components/jetpack/sdk/util/contract.js
new file mode 100644
index 000000000..c689ea601
--- /dev/null
+++ b/components/jetpack/sdk/util/contract.js
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { validateOptions: valid } = require("../deprecated/api-utils");
+const method = require("method/core");
+
+// Function takes property validation rules and returns function that given
+// an `options` object will return validated / normalized options back. If
+// option(s) are invalid validator will throw exception described by rules.
+// Returned will also have contain `rules` property with a given validation
+// rules and `properties` function that can be used to generate validated
+// property getter and setters can be mixed into prototype. For more details
+// see `properties` function below.
+function contract(rules) {
+ const validator = (instance, options) => {
+ return valid(options || instance || {}, rules);
+ };
+ validator.rules = rules
+ validator.properties = function(modelFor) {
+ return properties(modelFor, rules);
+ }
+ return validator;
+}
+exports.contract = contract
+
+// Function takes `modelFor` instance state model accessor functions and
+// a property validation rules and generates object with getters and setters
+// that can be mixed into prototype. Property accessors update model for the
+// given instance. If you wish to react to property updates you can always
+// override setters to put specific logic.
+function properties(modelFor, rules) {
+ let descriptor = Object.keys(rules).reduce(function(descriptor, name) {
+ descriptor[name] = {
+ get: function() { return modelFor(this)[name] },
+ set: function(value) {
+ let change = {};
+ change[name] = value;
+ modelFor(this)[name] = valid(change, rules)[name];
+ }
+ }
+ return descriptor
+ }, {});
+ return Object.create(Object.prototype, descriptor);
+}
+exports.properties = properties;
+
+const validate = method("contract/validate");
+exports.validate = validate;
diff --git a/components/jetpack/sdk/util/deprecate.js b/components/jetpack/sdk/util/deprecate.js
new file mode 100644
index 000000000..40f236de5
--- /dev/null
+++ b/components/jetpack/sdk/util/deprecate.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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { get, format } = require("../console/traceback");
+const { get: getPref } = require("../preferences/service");
+const PREFERENCE = "devtools.errorconsole.deprecation_warnings";
+
+function deprecateUsage(msg) {
+ // Print caller stacktrace in order to help figuring out which code
+ // does use deprecated thing
+ let stack = get().slice(2);
+
+ if (getPref(PREFERENCE))
+ console.error("DEPRECATED: " + msg + "\n" + format(stack));
+}
+exports.deprecateUsage = deprecateUsage;
+
+function deprecateFunction(fun, msg) {
+ return function deprecated() {
+ deprecateUsage(msg);
+ return fun.apply(this, arguments);
+ };
+}
+exports.deprecateFunction = deprecateFunction;
+
+function deprecateEvent(fun, msg, evtTypes) {
+ return function deprecateEvent(evtType) {
+ if (evtTypes.indexOf(evtType) >= 0)
+ deprecateUsage(msg);
+ return fun.apply(this, arguments);
+ };
+}
+exports.deprecateEvent = deprecateEvent;
diff --git a/components/jetpack/sdk/util/dispatcher.js b/components/jetpack/sdk/util/dispatcher.js
new file mode 100644
index 000000000..67d29dfed
--- /dev/null
+++ b/components/jetpack/sdk/util/dispatcher.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const method = require("method/core");
+
+// Utility function that is just an enhancement over `method` to
+// allow predicate based dispatch in addition to polymorphic
+// dispatch. Unfortunately polymorphic dispatch does not quite
+// cuts it in the world of XPCOM where no types / classes exist
+// and all the XUL nodes share same type / prototype.
+// Probably this is more generic and belongs some place else, but
+// we can move it later once this will be relevant.
+var dispatcher = hint => {
+ const base = method(hint);
+ // Make a map for storing predicate, implementation mappings.
+ let implementations = new Map();
+
+ // Dispatcher function goes through `predicate, implementation`
+ // pairs to find predicate that matches first argument and
+ // returns application of arguments on the associated
+ // `implementation`. If no matching predicate is found delegates
+ // to a `base` polymorphic function.
+ let dispatch = (value, ...rest) => {
+ for (let [predicate, implementation] of implementations) {
+ if (predicate(value))
+ return implementation(value, ...rest);
+ }
+
+ return base(value, ...rest);
+ };
+
+ // Expose base API.
+ dispatch.define = base.define;
+ dispatch.implement = base.implement;
+ dispatch.toString = base.toString;
+
+ // Add a `when` function to allow extending function via
+ // predicates.
+ dispatch.when = (predicate, implementation) => {
+ if (implementations.has(predicate))
+ throw TypeError("Already implemented for the given predicate");
+ implementations.set(predicate, implementation);
+ };
+
+ return dispatch;
+};
+
+exports.dispatcher = dispatcher;
diff --git a/components/jetpack/sdk/util/list.js b/components/jetpack/sdk/util/list.js
new file mode 100644
index 000000000..6d7d2dea9
--- /dev/null
+++ b/components/jetpack/sdk/util/list.js
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+const { Class } = require('../core/heritage');
+const listNS = require('../core/namespace').ns();
+
+const listOptions = {
+ /**
+ * List constructor can take any number of element to populate itself.
+ * @params {Object|String|Number} element
+ * @example
+ * List(1,2,3).length == 3 // true
+ */
+ initialize: function List() {
+ listNS(this).keyValueMap = [];
+
+ for (let i = 0, ii = arguments.length; i < ii; i++)
+ addListItem(this, arguments[i]);
+ },
+ /**
+ * Number of elements in this list.
+ * @type {Number}
+ */
+ get length() {
+ return listNS(this).keyValueMap.length;
+ },
+ /**
+ * Returns a string representing this list.
+ * @returns {String}
+ */
+ toString: function toString() {
+ return 'List(' + listNS(this).keyValueMap + ')';
+ },
+ /**
+ * Custom iterator providing `List`s enumeration behavior.
+ * We cant reuse `_iterator` that is defined by `Iterable` since it provides
+ * iteration in an arbitrary order.
+ * @see https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in
+ * @param {Boolean} onKeys
+ */
+ __iterator__: function __iterator__(onKeys, onKeyValue) {
+ let array = listNS(this).keyValueMap.slice(0),
+ i = -1;
+ for (let element of array)
+ yield onKeyValue ? [++i, element] : onKeys ? ++i : element;
+ },
+};
+listOptions[Symbol.iterator] = function iterator() {
+ return listNS(this).keyValueMap.slice(0)[Symbol.iterator]();
+};
+const List = Class(listOptions);
+exports.List = List;
+
+function addListItem(that, value) {
+ let list = listNS(that).keyValueMap,
+ index = list.indexOf(value);
+
+ if (-1 === index) {
+ try {
+ that[that.length] = value;
+ }
+ catch (e) {}
+ list.push(value);
+ }
+}
+exports.addListItem = addListItem;
+
+function removeListItem(that, element) {
+ let list = listNS(that).keyValueMap,
+ index = list.indexOf(element);
+
+ if (0 <= index) {
+ list.splice(index, 1);
+ try {
+ for (let length = list.length; index < length; index++)
+ that[index] = list[index];
+ that[list.length] = undefined;
+ }
+ catch(e){}
+ }
+}
+exports.removeListItem = removeListItem;
+
+exports.listNS = listNS;
diff --git a/components/jetpack/sdk/util/match-pattern.js b/components/jetpack/sdk/util/match-pattern.js
new file mode 100644
index 000000000..a0eb88b49
--- /dev/null
+++ b/components/jetpack/sdk/util/match-pattern.js
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { URL } = require('../url');
+const cache = {};
+
+function MatchPattern(pattern) {
+ if (cache[pattern]) return cache[pattern];
+
+ if (typeof pattern.test == "function") {
+ // For compatibility with -moz-document rules, we require the RegExp's
+ // global, ignoreCase, and multiline flags to be set to false.
+ if (pattern.global) {
+ throw new Error("A RegExp match pattern cannot be set to `global` " +
+ "(i.e. //g).");
+ }
+ if (pattern.multiline) {
+ throw new Error("A RegExp match pattern cannot be set to `multiline` " +
+ "(i.e. //m).");
+ }
+
+ this.regexp = pattern;
+ }
+ else {
+ let firstWildcardPosition = pattern.indexOf("*");
+ let lastWildcardPosition = pattern.lastIndexOf("*");
+ if (firstWildcardPosition != lastWildcardPosition)
+ throw new Error("There can be at most one '*' character in a wildcard.");
+
+ if (firstWildcardPosition == 0) {
+ if (pattern.length == 1)
+ this.anyWebPage = true;
+ else if (pattern[1] != ".")
+ throw new Error("Expected a *.<domain name> string, got: " + pattern);
+ else
+ this.domain = pattern.substr(2);
+ }
+ else {
+ if (pattern.indexOf(":") == -1) {
+ throw new Error("When not using *.example.org wildcard, the string " +
+ "supplied is expected to be either an exact URL to " +
+ "match or a URL prefix. The provided string ('" +
+ pattern + "') is unlikely to match any pages.");
+ }
+
+ if (firstWildcardPosition == -1)
+ this.exactURL = pattern;
+ else if (firstWildcardPosition == pattern.length - 1)
+ this.urlPrefix = pattern.substr(0, pattern.length - 1);
+ else {
+ throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
+ "in an unexpected position. It is expected to be the " +
+ "first or the last character in the wildcard.");
+ }
+ }
+ }
+
+ cache[pattern] = this;
+}
+
+MatchPattern.prototype = {
+ test: function MatchPattern_test(urlStr) {
+ try {
+ var url = URL(urlStr);
+ }
+ catch (err) {
+ return false;
+ }
+
+ // Test the URL against a RegExp pattern. For compatibility with
+ // -moz-document rules, we require the RegExp to match the entire URL,
+ // so we not only test for a match, we also make sure the matched string
+ // is the entire URL string.
+ //
+ // Assuming most URLs don't match most match patterns, we call `test` for
+ // speed when determining whether or not the URL matches, then call `exec`
+ // for the small subset that match to make sure the entire URL matches.
+ if (this.regexp && this.regexp.test(urlStr) &&
+ this.regexp.exec(urlStr)[0] == urlStr)
+ return true;
+
+ if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
+ return true;
+
+ if (this.exactURL && this.exactURL == urlStr)
+ return true;
+
+ // Tests the urlStr against domain and check if
+ // wildcard submitted (*.domain.com), it only allows
+ // subdomains (sub.domain.com) or from the root (http://domain.com)
+ // and reject non-matching domains (otherdomain.com)
+ // bug 856913
+ if (this.domain && url.host &&
+ (url.host === this.domain ||
+ url.host.slice(-this.domain.length - 1) === "." + this.domain))
+ return true;
+
+ if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
+ return true;
+
+ return false;
+ },
+
+ toString: () => '[object MatchPattern]'
+};
+
+exports.MatchPattern = MatchPattern;
diff --git a/components/jetpack/sdk/util/object.js b/components/jetpack/sdk/util/object.js
new file mode 100644
index 000000000..9d202bb51
--- /dev/null
+++ b/components/jetpack/sdk/util/object.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { flatten } = require('./array');
+
+/**
+ * Merges all the properties of all arguments into first argument. If two or
+ * more argument objects have own properties with the same name, the property
+ * is overridden, with precedence from right to left, implying, that properties
+ * of the object on the left are overridden by a same named property of the
+ * object on the right.
+ *
+ * Any argument given with "falsy" value - commonly `null` and `undefined` in
+ * case of objects - are skipped.
+ *
+ * @examples
+ * var a = { bar: 0, a: 'a' }
+ * var b = merge(a, { foo: 'foo', bar: 1 }, { foo: 'bar', name: 'b' });
+ * b === a // true
+ * b.a // 'a'
+ * b.foo // 'bar'
+ * b.bar // 1
+ * b.name // 'b'
+ */
+function merge(source) {
+ let descriptor = {};
+
+ // `Boolean` converts the first parameter to a boolean value. Any object is
+ // converted to `true` where `null` and `undefined` becames `false`. Therefore
+ // the `filter` method will keep only objects that are defined and not null.
+ Array.slice(arguments, 1).filter(Boolean).forEach(function onEach(properties) {
+ getOwnPropertyIdentifiers(properties).forEach(function(name) {
+ descriptor[name] = Object.getOwnPropertyDescriptor(properties, name);
+ });
+ });
+ return Object.defineProperties(source, descriptor);
+}
+exports.merge = merge;
+
+/**
+ * Returns an object that inherits from the first argument and contains all the
+ * properties from all following arguments.
+ * `extend(source1, source2, source3)` is equivalent of
+ * `merge(Object.create(source1), source2, source3)`.
+ */
+function extend(source) {
+ let rest = Array.slice(arguments, 1);
+ rest.unshift(Object.create(source));
+ return merge.apply(null, rest);
+}
+exports.extend = extend;
+
+function has(obj, key) {
+ return obj.hasOwnProperty(key);
+}
+exports.has = has;
+
+function each(obj, fn) {
+ for (let key in obj) has(obj, key) && fn(obj[key], key, obj);
+}
+exports.each = each;
+
+/**
+ * Like `merge`, except no property descriptors are manipulated, for use
+ * with platform objects. Identical to underscore's `extend`. Useful for
+ * merging XPCOM objects
+ */
+function safeMerge(source) {
+ Array.slice(arguments, 1).forEach(function onEach (obj) {
+ for (let prop in obj) source[prop] = obj[prop];
+ });
+ return source;
+}
+exports.safeMerge = safeMerge;
+
+/*
+ * Returns a copy of the object without omitted properties
+ */
+function omit(source, ...values) {
+ let copy = {};
+ let keys = flatten(values);
+ for (let prop in source)
+ if (!~keys.indexOf(prop))
+ copy[prop] = source[prop];
+ return copy;
+}
+exports.omit = omit;
+
+// get object's own property Symbols and/or Names, including nonEnumerables by default
+function getOwnPropertyIdentifiers(object, options = { names: true, symbols: true, nonEnumerables: true }) {
+ const symbols = !options.symbols ? [] :
+ Object.getOwnPropertySymbols(object);
+ const names = !options.names ? [] :
+ options.nonEnumerables ? Object.getOwnPropertyNames(object) :
+ Object.keys(object);
+ return [...names, ...symbols];
+}
+exports.getOwnPropertyIdentifiers = getOwnPropertyIdentifiers;
diff --git a/components/jetpack/sdk/util/rules.js b/components/jetpack/sdk/util/rules.js
new file mode 100644
index 000000000..98e3109b0
--- /dev/null
+++ b/components/jetpack/sdk/util/rules.js
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Class } = require('../core/heritage');
+const { MatchPattern } = require('./match-pattern');
+const { emit } = require('../event/core');
+const { EventTarget } = require('../event/target');
+const { List, addListItem, removeListItem } = require('./list');
+
+// Should deprecate usage of EventEmitter/compose
+const Rules = Class({
+ implements: [
+ EventTarget,
+ List
+ ],
+ add: function(...rules) {
+ return [].concat(rules).forEach(function onAdd(rule) {
+ addListItem(this, rule);
+ emit(this, 'add', rule);
+ }, this);
+ },
+ remove: function(...rules) {
+ return [].concat(rules).forEach(function onRemove(rule) {
+ removeListItem(this, rule);
+ emit(this, 'remove', rule);
+ }, this);
+ },
+ get: function(rule) {
+ let found = false;
+ for (let i in this) if (this[i] === rule) found = true;
+ return found;
+ },
+ // Returns true if uri matches atleast one stored rule
+ matchesAny: function(uri) {
+ return !!filterMatches(this, uri).length;
+ },
+ toString: () => '[object Rules]'
+});
+exports.Rules = Rules;
+
+function filterMatches(instance, uri) {
+ let matches = [];
+ for (let i in instance) {
+ if (new MatchPattern(instance[i]).test(uri)) matches.push(instance[i]);
+ }
+ return matches;
+}
diff --git a/components/jetpack/sdk/util/sequence.js b/components/jetpack/sdk/util/sequence.js
new file mode 100644
index 000000000..28e3de255
--- /dev/null
+++ b/components/jetpack/sdk/util/sequence.js
@@ -0,0 +1,593 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+module.metadata = {
+ "stability": "experimental"
+};
+
+// Disclamer:
+// In this module we'll have some common argument / variable names
+// to hint their type or behavior.
+//
+// - `f` stands for "function" that is intended to be side effect
+// free.
+// - `p` stands for "predicate" that is function which returns logical
+// true or false and is intended to be side effect free.
+// - `x` / `y` single item of the sequence.
+// - `xs` / `ys` sequence of `x` / `y` items where `x` / `y` signifies
+// type of the items in sequence, so sequence is not of the same item.
+// - `_` used for argument(s) or variable(s) who's values are ignored.
+
+const { complement, flip, identity } = require("../lang/functional");
+const { isArray, isArguments, isMap, isSet, isGenerator,
+ isString, isBoolean, isNumber } = require("../lang/type");
+
+const Sequence = function Sequence(iterator) {
+ if (!isGenerator(iterator)) {
+ throw TypeError("Expected generator argument");
+ }
+
+ this[Symbol.iterator] = iterator;
+};
+exports.Sequence = Sequence;
+
+const polymorphic = dispatch => x =>
+ x === null ? dispatch.null(null) :
+ x === void(0) ? dispatch.void(void(0)) :
+ isArray(x) ? (dispatch.array || dispatch.indexed)(x) :
+ isString(x) ? (dispatch.string || dispatch.indexed)(x) :
+ isArguments(x) ? (dispatch.arguments || dispatch.indexed)(x) :
+ isMap(x) ? dispatch.map(x) :
+ isSet(x) ? dispatch.set(x) :
+ isNumber(x) ? dispatch.number(x) :
+ isBoolean(x) ? dispatch.boolean(x) :
+ dispatch.default(x);
+
+const nogen = function*() {};
+const empty = () => new Sequence(nogen);
+exports.empty = empty;
+
+const seq = polymorphic({
+ null: empty,
+ void: empty,
+ array: identity,
+ string: identity,
+ arguments: identity,
+ map: identity,
+ set: identity,
+ default: x => x instanceof Sequence ? x : new Sequence(x)
+});
+exports.seq = seq;
+
+// Function to cast seq to string.
+const string = (...etc) => "".concat(...etc);
+exports.string = string;
+
+// Function for casting seq to plain object.
+const object = (...pairs) => {
+ let result = {};
+ for (let [key, value] of pairs)
+ result[key] = value;
+
+ return result;
+};
+exports.object = object;
+
+// Takes `getEnumerator` function that returns `nsISimpleEnumerator`
+// and creates lazy sequence of it's items. Note that function does
+// not take `nsISimpleEnumerator` itslef because that would allow
+// single iteration, which would not be consistent with rest of the
+// lazy sequences.
+const fromEnumerator = getEnumerator => seq(function* () {
+ const enumerator = getEnumerator();
+ while (enumerator.hasMoreElements())
+ yield enumerator.getNext();
+});
+exports.fromEnumerator = fromEnumerator;
+
+// Takes `object` and returns lazy sequence of own `[key, value]`
+// pairs (does not include inherited and non enumerable keys).
+const pairs = polymorphic({
+ null: empty,
+ void: empty,
+ map: identity,
+ indexed: indexed => seq(function* () {
+ const count = indexed.length;
+ let index = 0;
+ while (index < count) {
+ yield [index, indexed[index]];
+ index = index + 1;
+ }
+ }),
+ default: object => seq(function* () {
+ for (let key of Object.keys(object))
+ yield [key, object[key]];
+ })
+});
+exports.pairs = pairs;
+
+const names = polymorphic({
+ null: empty,
+ void: empty,
+ default: object => seq(function*() {
+ for (let name of Object.getOwnPropertyNames(object)) {
+ yield name;
+ }
+ })
+});
+exports.names = names;
+
+const symbols = polymorphic({
+ null: empty,
+ void: empty,
+ default: object => seq(function* () {
+ for (let symbol of Object.getOwnPropertySymbols(object)) {
+ yield symbol;
+ }
+ })
+});
+exports.symbols = symbols;
+
+const keys = polymorphic({
+ null: empty,
+ void: empty,
+ indexed: indexed => seq(function* () {
+ const count = indexed.length;
+ let index = 0;
+ while (index < count) {
+ yield index;
+ index = index + 1;
+ }
+ }),
+ map: map => seq(function* () {
+ for (let [key, _] of map)
+ yield key;
+ }),
+ default: object => seq(function* () {
+ for (let key of Object.keys(object))
+ yield key;
+ })
+});
+exports.keys = keys;
+
+
+const values = polymorphic({
+ null: empty,
+ void: empty,
+ set: identity,
+ indexed: indexed => seq(function* () {
+ const count = indexed.length;
+ let index = 0;
+ while (index < count) {
+ yield indexed[index];
+ index = index + 1;
+ }
+ }),
+ map: map => seq(function* () {
+ for (let [_, value] of map) yield value;
+ }),
+ default: object => seq(function* () {
+ for (let key of Object.keys(object)) yield object[key];
+ })
+});
+exports.values = values;
+
+
+
+// Returns a lazy sequence of `x`, `f(x)`, `f(f(x))` etc.
+// `f` must be free of side-effects. Note that returned
+// sequence is infinite so it must be consumed partially.
+//
+// Implements clojure iterate:
+// http://clojuredocs.org/clojure_core/clojure.core/iterate
+const iterate = (f, x) => seq(function* () {
+ let state = x;
+ while (true) {
+ yield state;
+ state = f(state);
+ }
+});
+exports.iterate = iterate;
+
+// Returns a lazy sequence of the items in sequence for which `p(item)`
+// returns `true`. `p` must be free of side-effects.
+//
+// Implements clojure filter:
+// http://clojuredocs.org/clojure_core/clojure.core/filter
+const filter = (p, sequence) => seq(function* () {
+ if (sequence !== null && sequence !== void(0)) {
+ for (let item of sequence) {
+ if (p(item))
+ yield item;
+ }
+ }
+});
+exports.filter = filter;
+
+// Returns a lazy sequence consisting of the result of applying `f` to the
+// set of first items of each sequence, followed by applying f to the set
+// of second items in each sequence, until any one of the sequences is
+// exhausted. Any remaining items in other sequences are ignored. Function
+// `f` should accept number-of-sequences arguments.
+//
+// Implements clojure map:
+// http://clojuredocs.org/clojure_core/clojure.core/map
+const map = (f, ...sequences) => seq(function* () {
+ const count = sequences.length;
+ // Optimize a single sequence case
+ if (count === 1) {
+ let [sequence] = sequences;
+ if (sequence !== null && sequence !== void(0)) {
+ for (let item of sequence)
+ yield f(item);
+ }
+ }
+ else {
+ // define args array that will be recycled on each
+ // step to aggregate arguments to be passed to `f`.
+ let args = [];
+ // define inputs to contain started generators.
+ let inputs = [];
+
+ let index = 0;
+ while (index < count) {
+ inputs[index] = sequences[index][Symbol.iterator]();
+ index = index + 1;
+ }
+
+ // Run loop yielding of applying `f` to the set of
+ // items at each step until one of the `inputs` is
+ // exhausted.
+ let done = false;
+ while (!done) {
+ let index = 0;
+ let value = void(0);
+ while (index < count && !done) {
+ ({ done, value } = inputs[index].next());
+
+ // If input is not exhausted yet store value in args.
+ if (!done) {
+ args[index] = value;
+ index = index + 1;
+ }
+ }
+
+ // If none of the inputs is exhasted yet, `args` contain items
+ // from each input so we yield application of `f` over them.
+ if (!done)
+ yield f(...args);
+ }
+ }
+});
+exports.map = map;
+
+// Returns a lazy sequence of the intermediate values of the reduction (as
+// per reduce) of sequence by `f`, starting with `initial` value if provided.
+//
+// Implements clojure reductions:
+// http://clojuredocs.org/clojure_core/clojure.core/reductions
+const reductions = (...params) => {
+ const count = params.length;
+ let hasInitial = false;
+ let f, initial, source;
+ if (count === 2) {
+ [f, source] = params;
+ }
+ else if (count === 3) {
+ [f, initial, source] = params;
+ hasInitial = true;
+ }
+ else {
+ throw Error("Invoked with wrong number of arguments: " + count);
+ }
+
+ const sequence = seq(source);
+
+ return seq(function* () {
+ let started = hasInitial;
+ let result = void(0);
+
+ // If initial is present yield it.
+ if (hasInitial)
+ yield (result = initial);
+
+ // For each item of the sequence accumulate new result.
+ for (let item of sequence) {
+ // If nothing has being yield yet set result to first
+ // item and yield it.
+ if (!started) {
+ started = true;
+ yield (result = item);
+ }
+ // Otherwise accumulate new result and yield it.
+ else {
+ yield (result = f(result, item));
+ }
+ }
+
+ // If nothing has being yield yet it's empty sequence and no
+ // `initial` was provided in which case we need to yield `f()`.
+ if (!started)
+ yield f();
+ });
+};
+exports.reductions = reductions;
+
+// `f` should be a function of 2 arguments. If `initial` is not supplied,
+// returns the result of applying `f` to the first 2 items in sequence, then
+// applying `f` to that result and the 3rd item, etc. If sequence contains no
+// items, `f` must accept no arguments as well, and reduce returns the
+// result of calling f with no arguments. If sequence has only 1 item, it
+// is returned and `f` is not called. If `initial` is supplied, returns the
+// result of applying `f` to `initial` and the first item in sequence, then
+// applying `f` to that result and the 2nd item, etc. If sequence contains no
+// items, returns `initial` and `f` is not called.
+//
+// Implements clojure reduce:
+// http://clojuredocs.org/clojure_core/clojure.core/reduce
+const reduce = (...args) => {
+ const xs = reductions(...args);
+ let x;
+ for (x of xs) void(0);
+ return x;
+};
+exports.reduce = reduce;
+
+const each = (f, sequence) => {
+ for (let x of seq(sequence)) void(f(x));
+};
+exports.each = each;
+
+
+const inc = x => x + 1;
+// Returns the number of items in the sequence. `count(null)` && `count()`
+// returns `0`. Also works on strings, arrays, Maps & Sets.
+
+// Implements clojure count:
+// http://clojuredocs.org/clojure_core/clojure.core/count
+const count = polymorphic({
+ null: _ => 0,
+ void: _ => 0,
+ indexed: indexed => indexed.length,
+ map: map => map.size,
+ set: set => set.size,
+ default: xs => reduce(inc, 0, xs)
+});
+exports.count = count;
+
+// Returns `true` if sequence has no items.
+
+// Implements clojure empty?:
+// http://clojuredocs.org/clojure_core/clojure.core/empty_q
+const isEmpty = sequence => {
+ // Treat `null` and `undefined` as empty sequences.
+ if (sequence === null || sequence === void(0))
+ return true;
+
+ // If contains any item non empty so return `false`.
+ for (let _ of sequence)
+ return false;
+
+ // If has not returned yet, there was nothing to iterate
+ // so it's empty.
+ return true;
+};
+exports.isEmpty = isEmpty;
+
+const and = (a, b) => a && b;
+
+// Returns true if `p(x)` is logical `true` for every `x` in sequence, else
+// `false`.
+//
+// Implements clojure every?:
+// http://clojuredocs.org/clojure_core/clojure.core/every_q
+const isEvery = (p, sequence) => {
+ if (sequence !== null && sequence !== void(0)) {
+ for (let item of sequence) {
+ if (!p(item))
+ return false;
+ }
+ }
+ return true;
+};
+exports.isEvery = isEvery;
+
+// Returns the first logical true value of (p x) for any x in sequence,
+// else `null`.
+//
+// Implements clojure some:
+// http://clojuredocs.org/clojure_core/clojure.core/some
+const some = (p, sequence) => {
+ if (sequence !== null && sequence !== void(0)) {
+ for (let item of sequence) {
+ if (p(item))
+ return true;
+ }
+ }
+ return null;
+};
+exports.some = some;
+
+// Returns a lazy sequence of the first `n` items in sequence, or all items if
+// there are fewer than `n`.
+//
+// Implements clojure take:
+// http://clojuredocs.org/clojure_core/clojure.core/take
+const take = (n, sequence) => n <= 0 ? empty() : seq(function* () {
+ let count = n;
+ for (let item of sequence) {
+ yield item;
+ count = count - 1;
+ if (count === 0) break;
+ }
+});
+exports.take = take;
+
+// Returns a lazy sequence of successive items from sequence while
+// `p(item)` returns `true`. `p` must be free of side-effects.
+//
+// Implements clojure take-while:
+// http://clojuredocs.org/clojure_core/clojure.core/take-while
+const takeWhile = (p, sequence) => seq(function* () {
+ for (let item of sequence) {
+ if (!p(item))
+ break;
+
+ yield item;
+ }
+});
+exports.takeWhile = takeWhile;
+
+// Returns a lazy sequence of all but the first `n` items in
+// sequence.
+//
+// Implements clojure drop:
+// http://clojuredocs.org/clojure_core/clojure.core/drop
+const drop = (n, sequence) => seq(function* () {
+ if (sequence !== null && sequence !== void(0)) {
+ let count = n;
+ for (let item of sequence) {
+ if (count > 0)
+ count = count - 1;
+ else
+ yield item;
+ }
+ }
+});
+exports.drop = drop;
+
+// Returns a lazy sequence of the items in sequence starting from the
+// first item for which `p(item)` returns falsy value.
+//
+// Implements clojure drop-while:
+// http://clojuredocs.org/clojure_core/clojure.core/drop-while
+const dropWhile = (p, sequence) => seq(function* () {
+ let keep = false;
+ for (let item of sequence) {
+ keep = keep || !p(item);
+ if (keep) yield item;
+ }
+});
+exports.dropWhile = dropWhile;
+
+// Returns a lazy sequence representing the concatenation of the
+// suplied sequences.
+//
+// Implements clojure conact:
+// http://clojuredocs.org/clojure_core/clojure.core/concat
+const concat = (...sequences) => seq(function* () {
+ for (let sequence of sequences)
+ for (let item of sequence)
+ yield item;
+});
+exports.concat = concat;
+
+// Returns the first item in the sequence.
+//
+// Implements clojure first:
+// http://clojuredocs.org/clojure_core/clojure.core/first
+const first = sequence => {
+ if (sequence !== null && sequence !== void(0)) {
+ for (let item of sequence)
+ return item;
+ }
+ return null;
+};
+exports.first = first;
+
+// Returns a possibly empty sequence of the items after the first.
+//
+// Implements clojure rest:
+// http://clojuredocs.org/clojure_core/clojure.core/rest
+const rest = sequence => drop(1, sequence);
+exports.rest = rest;
+
+// Returns the value at the index. Returns `notFound` or `undefined`
+// if index is out of bounds.
+const nth = (xs, n, notFound) => {
+ if (n >= 0) {
+ if (isArray(xs) || isArguments(xs) || isString(xs)) {
+ return n < xs.length ? xs[n] : notFound;
+ }
+ else if (xs !== null && xs !== void(0)) {
+ let count = n;
+ for (let x of xs) {
+ if (count <= 0)
+ return x;
+
+ count = count - 1;
+ }
+ }
+ }
+ return notFound;
+};
+exports.nth = nth;
+
+// Return the last item in sequence, in linear time.
+// If `sequence` is an array or string or arguments
+// returns in constant time.
+// Implements clojure last:
+// http://clojuredocs.org/clojure_core/clojure.core/last
+const last = polymorphic({
+ null: _ => null,
+ void: _ => null,
+ indexed: indexed => indexed[indexed.length - 1],
+ map: xs => reduce((_, x) => x, xs),
+ set: xs => reduce((_, x) => x, xs),
+ default: xs => reduce((_, x) => x, xs)
+});
+exports.last = last;
+
+// Return a lazy sequence of all but the last `n` (default 1) items
+// from the give `xs`.
+//
+// Implements clojure drop-last:
+// http://clojuredocs.org/clojure_core/clojure.core/drop-last
+const dropLast = flip((xs, n=1) => seq(function* () {
+ let ys = [];
+ for (let x of xs) {
+ ys.push(x);
+ if (ys.length > n)
+ yield ys.shift();
+ }
+}));
+exports.dropLast = dropLast;
+
+// Returns a lazy sequence of the elements of `xs` with duplicates
+// removed
+//
+// Implements clojure distinct
+// http://clojuredocs.org/clojure_core/clojure.core/distinct
+const distinct = sequence => seq(function* () {
+ let items = new Set();
+ for (let item of sequence) {
+ if (!items.has(item)) {
+ items.add(item);
+ yield item;
+ }
+ }
+});
+exports.distinct = distinct;
+
+// Returns a lazy sequence of the items in `xs` for which
+// `p(x)` returns false. `p` must be free of side-effects.
+//
+// Implements clojure remove
+// http://clojuredocs.org/clojure_core/clojure.core/remove
+const remove = (p, xs) => filter(complement(p), xs);
+exports.remove = remove;
+
+// Returns the result of applying concat to the result of
+// `map(f, xs)`. Thus function `f` should return a sequence.
+//
+// Implements clojure mapcat
+// http://clojuredocs.org/clojure_core/clojure.core/mapcat
+const mapcat = (f, sequence) => seq(function* () {
+ const sequences = map(f, sequence);
+ for (let sequence of sequences)
+ for (let item of sequence)
+ yield item;
+});
+exports.mapcat = mapcat;
diff --git a/components/jetpack/sdk/util/uuid.js b/components/jetpack/sdk/util/uuid.js
new file mode 100644
index 000000000..6d0f2de53
--- /dev/null
+++ b/components/jetpack/sdk/util/uuid.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Cc, Ci, components: { ID: parseUUID } } = require('chrome');
+const { generateUUID } = Cc['@mozilla.org/uuid-generator;1'].
+ getService(Ci.nsIUUIDGenerator);
+
+// Returns `uuid`. If `id` is passed then it's parsed to `uuid` and returned
+// if not then new one is generated.
+exports.uuid = function uuid(id) {
+ return id ? parseUUID(id) : generateUUID();
+};
diff --git a/components/jetpack/sdk/view/core.js b/components/jetpack/sdk/view/core.js
new file mode 100644
index 000000000..5e82e9b5d
--- /dev/null
+++ b/components/jetpack/sdk/view/core.js
@@ -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/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+var { Ci } = require("chrome");
+var method = require("../../method/core");
+
+// Returns DOM node associated with a view for
+// the given `value`. If `value` has no view associated
+// it returns `null`. You can implement this method for
+// this type to define what the result should be for it.
+var getNodeView = method("getNodeView");
+getNodeView.define(x =>
+ x instanceof Ci.nsIDOMNode ? x :
+ x instanceof Ci.nsIDOMWindow ? x :
+ null);
+exports.getNodeView = getNodeView;
+exports.viewFor = getNodeView;
+
+var getActiveView = method("getActiveView");
+exports.getActiveView = getActiveView;
diff --git a/components/jetpack/sdk/window/browser.js b/components/jetpack/sdk/window/browser.js
new file mode 100644
index 000000000..380b5a486
--- /dev/null
+++ b/components/jetpack/sdk/window/browser.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { Class } = require('../core/heritage');
+const { windowNS } = require('./namespace');
+const { on, off, once } = require('../event/core');
+const { method } = require('../lang/functional');
+const { getWindowTitle } = require('./utils');
+const unload = require('../system/unload');
+const { EventTarget } = require('../event/target');
+const { isPrivate } = require('../private-browsing/utils');
+const { isWindowPrivate, isFocused } = require('../window/utils');
+const { viewFor } = require('../view/core');
+
+const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("sdk/tabs") instead';
+
+const BrowserWindow = Class({
+ initialize: function initialize(options) {
+ EventTarget.prototype.initialize.call(this, options);
+ windowNS(this).window = options.window;
+ },
+ activate: function activate() {
+ // TODO
+ return null;
+ },
+ close: function() {
+ throw new Error(ERR_FENNEC_MSG);
+ return null;
+ },
+ get title() {
+ return getWindowTitle(windowNS(this).window);
+ },
+ // NOTE: Fennec only has one window, which is assumed below
+ // TODO: remove assumption below
+ // NOTE: tabs requires windows
+ get tabs() {
+ return require('../tabs');
+ },
+ get activeTab() {
+ return require('../tabs').activeTab;
+ },
+ on: method(on),
+ removeListener: method(off),
+ once: method(once)
+});
+exports.BrowserWindow = BrowserWindow;
+
+const getWindowView = window => windowNS(window).window;
+
+viewFor.define(BrowserWindow, getWindowView);
+isPrivate.define(BrowserWindow, (window) => isWindowPrivate(viewFor(window).window));
+isFocused.define(BrowserWindow, (window) => isFocused(viewFor(window).window));
diff --git a/components/jetpack/sdk/window/events.js b/components/jetpack/sdk/window/events.js
new file mode 100644
index 000000000..b1d3a1f3e
--- /dev/null
+++ b/components/jetpack/sdk/window/events.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Ci, Cu } = require("chrome");
+const { observe } = require("../event/chrome");
+const { open } = require("../event/dom");
+const { windows } = require("../window/utils");
+const { filter, merge, map, expand } = require("../event/utils");
+
+function documentMatches(weakWindow, event) {
+ let window = weakWindow.get();
+ return window && event.target === window.document;
+}
+
+function makeStrictDocumentFilter(window) {
+ // Note: Do not define a closure within this function. Otherwise
+ // you may leak the window argument.
+ let weak = Cu.getWeakReference(window);
+ return documentMatches.bind(null, weak);
+}
+
+function toEventWithDefaultViewTarget({type, target}) {
+ return { type: type, target: target.defaultView }
+}
+
+// Function registers single shot event listeners for relevant window events
+// that forward events to exported event stream.
+function eventsFor(window) {
+ // NOTE: Do no use pass a closure from this function into a stream
+ // transform function. You will capture the window in the
+ // closure and leak the window until the event stream is
+ // completely closed.
+ let interactive = open(window, "DOMContentLoaded", { capture: true });
+ let complete = open(window, "load", { capture: true });
+ let states = merge([interactive, complete]);
+ let changes = filter(states, makeStrictDocumentFilter(window));
+ return map(changes, toEventWithDefaultViewTarget);
+}
+
+// Create our event channels. We do this in a separate function to
+// minimize the chance of leaking intermediate objects on the global.
+function makeEvents() {
+ // In addition to observing windows that are open we also observe windows
+ // that are already already opened in case they're in process of loading.
+ var opened = windows(null, { includePrivate: true });
+ var currentEvents = merge(opened.map(eventsFor));
+
+ // Register system event listeners for top level window open / close.
+ function rename({type, target, data}) {
+ return { type: rename[type], target: target, data: data }
+ }
+ rename.domwindowopened = "open";
+ rename.domwindowclosed = "close";
+
+ var openEvents = map(observe("domwindowopened"), rename);
+ var closeEvents = map(observe("domwindowclosed"), rename);
+ var futureEvents = expand(openEvents, ({target}) => eventsFor(target));
+
+ return merge([currentEvents, futureEvents, openEvents, closeEvents]);
+}
+
+exports.events = makeEvents();
diff --git a/components/jetpack/sdk/window/helpers.js b/components/jetpack/sdk/window/helpers.js
new file mode 100644
index 000000000..56cfcaba7
--- /dev/null
+++ b/components/jetpack/sdk/window/helpers.js
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { defer, all } = require('../core/promise');
+const events = require('../system/events');
+const { open: openWindow, onFocus, getToplevelWindow,
+ isInteractive, isStartupFinished, getOuterId } = require('./utils');
+const { Ci } = require("chrome");
+
+function open(uri, options) {
+ return promise(openWindow.apply(null, arguments), 'load').then(focus);
+}
+exports.open = open;
+
+function close(window) {
+ let deferred = defer();
+ let toplevelWindow = getToplevelWindow(window);
+ let outerId = getOuterId(toplevelWindow);
+ events.on("outer-window-destroyed", function onclose({subject}) {
+ let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (id == outerId) {
+ events.off("outer-window-destroyed", onclose);
+ deferred.resolve();
+ }
+ }, true);
+ window.close();
+ return deferred.promise;
+}
+exports.close = close;
+
+function focus(window) {
+ let p = onFocus(window);
+ window.focus();
+ return p;
+}
+exports.focus = focus;
+
+function ready(window) {
+ let { promise: result, resolve } = defer();
+
+ if (isInteractive(window))
+ resolve(window);
+ else
+ resolve(promise(window, 'DOMContentLoaded'));
+
+ return result;
+}
+exports.ready = ready;
+
+function startup(window) {
+ let { promise: result, resolve } = defer();
+
+ if (isStartupFinished(window)) {
+ resolve(window);
+ } else {
+ events.on("browser-delayed-startup-finished", function listener({subject}) {
+ if (subject === window) {
+ events.off("browser-delayed-startup-finished", listener);
+ resolve(window);
+ }
+ });
+ }
+
+ return result;
+}
+exports.startup = startup;
+
+function promise(target, evt, capture) {
+ let deferred = defer();
+ capture = !!capture;
+
+ target.addEventListener(evt, function eventHandler() {
+ target.removeEventListener(evt, eventHandler, capture);
+ deferred.resolve(target);
+ }, capture);
+
+ return deferred.promise;
+}
+exports.promise = promise;
diff --git a/components/jetpack/sdk/window/namespace.js b/components/jetpack/sdk/window/namespace.js
new file mode 100644
index 000000000..b486f888d
--- /dev/null
+++ b/components/jetpack/sdk/window/namespace.js
@@ -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/. */
+"use strict";
+
+exports.windowNS = require('../core/namespace').ns();
diff --git a/components/jetpack/sdk/window/utils.js b/components/jetpack/sdk/window/utils.js
new file mode 100644
index 000000000..db91a0fed
--- /dev/null
+++ b/components/jetpack/sdk/window/utils.js
@@ -0,0 +1,460 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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';
+
+module.metadata = {
+ 'stability': 'unstable'
+};
+
+const { Cc, Ci } = require('chrome');
+const array = require('../util/array');
+const { defer } = require('sdk/core/promise');
+const { dispatcher } = require("../util/dispatcher");
+
+const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
+ getService(Ci.nsIWindowWatcher);
+const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
+ getService(Ci.nsIWindowMediator);
+const io = Cc['@mozilla.org/network/io-service;1'].
+ getService(Ci.nsIIOService);
+const FM = Cc["@mozilla.org/focus-manager;1"].
+ getService(Ci.nsIFocusManager);
+
+const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+
+const prefs = require("../preferences/service");
+const BROWSER = 'navigator:browser',
+ URI_BROWSER = prefs.get('browser.chromeURL', null),
+ NAME = '_blank',
+ FEATURES = 'chrome,all,dialog=no,non-private';
+
+function isWindowPrivate(win) {
+ if (!win)
+ return false;
+
+ // if the pbService is undefined, the PrivateBrowsingUtils.jsm is available,
+ // and the app is Firefox, then assume per-window private browsing is
+ // enabled.
+ try {
+ return win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext)
+ .usePrivateBrowsing;
+ }
+ catch(e) {}
+
+ // Sometimes the input is not a nsIDOMWindow.. but it is still a winodw.
+ try {
+ return !!win.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing;
+ }
+ catch (e) {}
+
+ return false;
+}
+exports.isWindowPrivate = isWindowPrivate;
+
+function getMostRecentBrowserWindow() {
+ return getMostRecentWindow(BROWSER);
+}
+exports.getMostRecentBrowserWindow = getMostRecentBrowserWindow;
+
+function getHiddenWindow() {
+ return appShellService.hiddenDOMWindow;
+}
+exports.getHiddenWindow = getHiddenWindow;
+
+function getMostRecentWindow(type) {
+ return WM.getMostRecentWindow(type);
+}
+exports.getMostRecentWindow = getMostRecentWindow;
+
+/**
+ * Returns the ID of the window's current inner window.
+ */
+function getInnerId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+};
+exports.getInnerId = getInnerId;
+
+/**
+ * Returns the ID of the window's outer window.
+ */
+function getOuterId(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+};
+exports.getOuterId = getOuterId;
+
+/**
+ * Returns window by the outer window id.
+ */
+const getByOuterId = WM.getOuterWindowWithId;
+exports.getByOuterId = getByOuterId;
+
+const getByInnerId = WM.getCurrentInnerWindowWithId;
+exports.getByInnerId = getByInnerId;
+
+/**
+ * Returns `nsIXULWindow` for the given `nsIDOMWindow`.
+ */
+function getXULWindow(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShellTreeItem).
+ treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIXULWindow);
+};
+exports.getXULWindow = getXULWindow;
+
+function getDOMWindow(xulWindow) {
+ return xulWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindow);
+}
+exports.getDOMWindow = getDOMWindow;
+
+/**
+ * Returns `nsIBaseWindow` for the given `nsIDOMWindow`.
+ */
+function getBaseWindow(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShell).
+ QueryInterface(Ci.nsIDocShellTreeItem).
+ treeOwner.
+ QueryInterface(Ci.nsIBaseWindow);
+}
+exports.getBaseWindow = getBaseWindow;
+
+/**
+ * Returns the `nsIDOMWindow` toplevel window for any child/inner window
+ */
+function getToplevelWindow(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+}
+exports.getToplevelWindow = getToplevelWindow;
+
+function getWindowDocShell(window) {
+ return window.gBrowser.docShell;
+}
+exports.getWindowDocShell = getWindowDocShell;
+
+function getWindowLoadingContext(window) {
+ return getWindowDocShell(window).
+ QueryInterface(Ci.nsILoadContext);
+}
+exports.getWindowLoadingContext = getWindowLoadingContext;
+
+const isTopLevel = window => window && getToplevelWindow(window) === window;
+exports.isTopLevel = isTopLevel;
+
+/**
+ * Takes hash of options and serializes it to a features string that
+ * can be used passed to `window.open`. For more details on features string see:
+ * https://developer.mozilla.org/en/DOM/window.open#Position_and_size_features
+ */
+function serializeFeatures(options) {
+ return Object.keys(options).reduce(function(result, name) {
+ let value = options[name];
+
+ // the chrome and private features are special
+ if ((name == 'private' || name == 'chrome' || name == 'all'))
+ return result + ((value === true) ? ',' + name : '');
+
+ return result + ',' + name + '=' +
+ (value === true ? 'yes' : value === false ? 'no' : value);
+ }, '').substr(1);
+}
+
+/**
+ * Opens a top level window and returns it's `nsIDOMWindow` representation.
+ * @params {String} uri
+ * URI of the document to be loaded into window.
+ * @params {nsIDOMWindow} options.parent
+ * Used as parent for the created window.
+ * @params {String} options.name
+ * Optional name that is assigned to the window.
+ * @params {Object} options.features
+ * Map of key, values like: `{ width: 10, height: 15, chrome: true, private: true }`.
+ */
+function open(uri, options) {
+ uri = uri || URI_BROWSER;
+ options = options || {};
+
+ if (!uri)
+ throw new Error('browser.chromeURL is undefined, please provide an explicit uri');
+
+ if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0)
+ throw new Error('only chrome, resource and data uris are allowed');
+
+ let newWindow = windowWatcher.
+ openWindow(options.parent || null,
+ uri,
+ options.name || null,
+ options.features ? serializeFeatures(options.features) : null,
+ options.args || null);
+
+ return newWindow;
+}
+exports.open = open;
+
+function onFocus(window) {
+ let { resolve, promise } = defer();
+
+ if (isFocused(window)) {
+ resolve(window);
+ }
+ else {
+ window.addEventListener("focus", function focusListener() {
+ window.removeEventListener("focus", focusListener, true);
+ resolve(window);
+ }, true);
+ }
+
+ return promise;
+}
+exports.onFocus = onFocus;
+
+var isFocused = dispatcher("window-isFocused");
+isFocused.when(x => x instanceof Ci.nsIDOMWindow, (window) => {
+ const FM = Cc["@mozilla.org/focus-manager;1"].
+ getService(Ci.nsIFocusManager);
+
+ let childTargetWindow = {};
+ FM.getFocusedElementForWindow(window, true, childTargetWindow);
+ childTargetWindow = childTargetWindow.value;
+
+ let focusedChildWindow = {};
+ if (FM.activeWindow) {
+ FM.getFocusedElementForWindow(FM.activeWindow, true, focusedChildWindow);
+ focusedChildWindow = focusedChildWindow.value;
+ }
+
+ return (focusedChildWindow === childTargetWindow);
+});
+exports.isFocused = isFocused;
+
+/**
+ * Opens a top level window and returns it's `nsIDOMWindow` representation.
+ * Same as `open` but with more features
+ * @param {Object} options
+ *
+ */
+function openDialog(options) {
+ options = options || {};
+
+ let features = options.features || FEATURES;
+ let featureAry = features.toLowerCase().split(',');
+
+ if (!!options.private) {
+ // add private flag if private window is desired
+ if (!array.has(featureAry, 'private')) {
+ featureAry.push('private');
+ }
+
+ // remove the non-private flag ig a private window is desired
+ let nonPrivateIndex = featureAry.indexOf('non-private');
+ if (nonPrivateIndex >= 0) {
+ featureAry.splice(nonPrivateIndex, 1);
+ }
+
+ features = featureAry.join(',');
+ }
+
+ let browser = getMostRecentBrowserWindow();
+
+ // if there is no browser then do nothing
+ if (!browser)
+ return undefined;
+
+ let newWindow = browser.openDialog.apply(
+ browser,
+ array.flatten([
+ options.url || URI_BROWSER,
+ options.name || NAME,
+ features,
+ options.args || null
+ ])
+ );
+
+ return newWindow;
+}
+exports.openDialog = openDialog;
+
+/**
+ * Returns an array of all currently opened windows.
+ * Note that these windows may still be loading.
+ */
+function windows(type, options) {
+ options = options || {};
+ let list = [];
+ let winEnum = WM.getEnumerator(type);
+ while (winEnum.hasMoreElements()) {
+ let window = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
+ // Only add non-private windows when pb permission isn't set,
+ // unless an option forces the addition of them.
+ if (!window.closed && (options.includePrivate || !isWindowPrivate(window))) {
+ list.push(window);
+ }
+ }
+ return list;
+}
+exports.windows = windows;
+
+/**
+ * Check if the given window is interactive.
+ * i.e. if its "DOMContentLoaded" event has already been fired.
+ * @params {nsIDOMWindow} window
+ */
+const isInteractive = window =>
+ window.document.readyState === "interactive" ||
+ isDocumentLoaded(window) ||
+ // XUL documents stays '"uninitialized"' until it's `readyState` becomes
+ // `"complete"`.
+ isXULDocumentWindow(window) && window.document.readyState === "interactive";
+exports.isInteractive = isInteractive;
+
+/**
+ * Check if the given browser window has finished the startup.
+ * @params {nsIDOMWindow} window
+ */
+const isStartupFinished = (window) =>
+ isBrowser(window) &&
+ window.gBrowserInit &&
+ window.gBrowserInit.delayedStartupFinished;
+
+exports.isStartupFinished = isStartupFinished;
+
+const isXULDocumentWindow = ({document}) =>
+ document.documentElement &&
+ document.documentElement.namespaceURI === XUL_NS;
+
+/**
+ * Check if the given window is completely loaded.
+ * i.e. if its "load" event has already been fired and all possible DOM content
+ * is done loading (the whole DOM document, images content, ...)
+ * @params {nsIDOMWindow} window
+ */
+function isDocumentLoaded(window) {
+ return window.document.readyState == "complete";
+}
+exports.isDocumentLoaded = isDocumentLoaded;
+
+function isBrowser(window) {
+ try {
+ return window.document.documentElement.getAttribute("windowtype") === BROWSER;
+ }
+ catch (e) {}
+ return false;
+};
+exports.isBrowser = isBrowser;
+
+function getWindowTitle(window) {
+ return window && window.document ? window.document.title : null;
+}
+exports.getWindowTitle = getWindowTitle;
+
+function isXULBrowser(window) {
+ return !!(isBrowser(window) && window.XULBrowserWindow);
+}
+exports.isXULBrowser = isXULBrowser;
+
+/**
+ * Returns the most recent focused window
+ */
+function getFocusedWindow() {
+ let window = WM.getMostRecentWindow(BROWSER);
+
+ return window ? window.document.commandDispatcher.focusedWindow : null;
+}
+exports.getFocusedWindow = getFocusedWindow;
+
+/**
+ * Returns the focused browser window if any, or the most recent one.
+ * Opening new window, updates most recent window, but focus window
+ * changes later; so most recent window and focused window are not always
+ * the same.
+ */
+function getFocusedBrowser() {
+ let window = FM.activeWindow;
+ return isBrowser(window) ? window : getMostRecentBrowserWindow()
+}
+exports.getFocusedBrowser = getFocusedBrowser;
+
+/**
+ * Returns the focused element in the most recent focused window
+ */
+function getFocusedElement() {
+ let window = WM.getMostRecentWindow(BROWSER);
+
+ return window ? window.document.commandDispatcher.focusedElement : null;
+}
+exports.getFocusedElement = getFocusedElement;
+
+function getFrames(window) {
+ return Array.slice(window.frames).reduce(function(frames, frame) {
+ return frames.concat(frame, getFrames(frame));
+ }, []);
+}
+exports.getFrames = getFrames;
+
+function getScreenPixelsPerCSSPixel(window) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).screenPixelsPerCSSPixel;
+}
+exports.getScreenPixelsPerCSSPixel = getScreenPixelsPerCSSPixel;
+
+function getOwnerBrowserWindow(node) {
+ /**
+ Takes DOM node and returns browser window that contains it.
+ **/
+ let window = getToplevelWindow(node.ownerDocument.defaultView);
+ // If anchored window is browser then it's target browser window.
+ return isBrowser(window) ? window : null;
+}
+exports.getOwnerBrowserWindow = getOwnerBrowserWindow;
+
+function getParentWindow(window) {
+ try {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).parent
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ }
+ catch (e) {}
+ return null;
+}
+exports.getParentWindow = getParentWindow;
+
+
+function getParentFrame(window) {
+ try {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).parent
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ }
+ catch (e) {}
+ return null;
+}
+exports.getParentWindow = getParentWindow;
+
+// The element in which the window is embedded, or `null`
+// if the window is top-level. Similar to `window.frameElement`
+// but can cross chrome-content boundries.
+const getFrameElement = target =>
+ (target instanceof Ci.nsIDOMDocument ? target.defaultView : target).
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ containerElement;
+exports.getFrameElement = getFrameElement;
diff --git a/components/jetpack/sdk/windows.js b/components/jetpack/sdk/windows.js
new file mode 100644
index 000000000..06dbe70b2
--- /dev/null
+++ b/components/jetpack/sdk/windows.js
@@ -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/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'stable'
+};
+
+const { isBrowser } = require('./window/utils');
+const { modelFor } = require('./model/core');
+const { viewFor } = require('./view/core');
+
+
+if (require('./system/xul-app').is('Fennec')) {
+ module.exports = require('./windows/fennec');
+}
+else {
+ module.exports = require('./windows/firefox');
+}
+
+
+const browsers = module.exports.browserWindows;
+
+//
+modelFor.when(isBrowser, view => {
+ for (let model of browsers) {
+ if (viewFor(model) === view)
+ return model;
+ }
+ return null;
+});
diff --git a/components/jetpack/sdk/windows/fennec.js b/components/jetpack/sdk/windows/fennec.js
new file mode 100644
index 000000000..3c3b6c313
--- /dev/null
+++ b/components/jetpack/sdk/windows/fennec.js
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { Class } = require('../core/heritage');
+const { BrowserWindow } = require('../window/browser');
+const { WindowTracker } = require('../deprecated/window-utils');
+const { isBrowser, getMostRecentBrowserWindow } = require('../window/utils');
+const { windowNS } = require('../window/namespace');
+const { on, off, once, emit } = require('../event/core');
+const { method } = require('../lang/functional');
+const { EventTarget } = require('../event/target');
+const { List, addListItem } = require('../util/list');
+
+const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("sdk/tabs") instead';
+
+// NOTE: On Fennec there is only one window.
+
+var BrowserWindows = Class({
+ implements: [ List ],
+ extends: EventTarget,
+ initialize: function() {
+ List.prototype.initialize.apply(this);
+ },
+ get activeWindow() {
+ let window = getMostRecentBrowserWindow();
+ return window ? getBrowserWindow({window: window}) : null;
+ },
+ open: function open(options) {
+ throw new Error(ERR_FENNEC_MSG);
+ return null;
+ }
+});
+const browserWindows = exports.browserWindows = BrowserWindows();
+
+
+/**
+ * Gets a `BrowserWindow` for the given `chromeWindow` if previously
+ * registered, `null` otherwise.
+ */
+function getRegisteredWindow(chromeWindow) {
+ for (let window of browserWindows) {
+ if (chromeWindow === windowNS(window).window)
+ return window;
+ }
+
+ return null;
+}
+
+/**
+ * Gets a `BrowserWindow` for the provided window options obj
+ * @params {Object} options
+ * Options that are passed to the the `BrowserWindow`
+ * @returns {BrowserWindow}
+ */
+function getBrowserWindow(options) {
+ let window = null;
+
+ // if we have a BrowserWindow already then use it
+ if ('window' in options)
+ window = getRegisteredWindow(options.window);
+ if (window)
+ return window;
+
+ // we don't have a BrowserWindow yet, so create one
+ window = BrowserWindow(options);
+ addListItem(browserWindows, window);
+ return window;
+}
+
+WindowTracker({
+ onTrack: function onTrack(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return;
+ let window = getBrowserWindow({ window: chromeWindow });
+ emit(browserWindows, 'open', window);
+ },
+ onUntrack: function onUntrack(chromeWindow) {
+ if (!isBrowser(chromeWindow)) return;
+ let window = getBrowserWindow({ window: chromeWindow });
+ emit(browserWindows, 'close', window);
+ }
+});
diff --git a/components/jetpack/sdk/windows/firefox.js b/components/jetpack/sdk/windows/firefox.js
new file mode 100644
index 000000000..1eb1d8488
--- /dev/null
+++ b/components/jetpack/sdk/windows/firefox.js
@@ -0,0 +1,224 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { Class } = require('../core/heritage');
+const { observer } = require('./observer');
+const { isBrowser, getMostRecentBrowserWindow, windows, open, getInnerId,
+ getWindowTitle, getToplevelWindow, isFocused, isWindowPrivate } = require('../window/utils');
+const { List, addListItem, removeListItem } = require('../util/list');
+const { viewFor } = require('../view/core');
+const { modelFor } = require('../model/core');
+const { emit, emitOnObject, setListeners } = require('../event/core');
+const { once } = require('../dom/events');
+const { EventTarget } = require('../event/target');
+const { getSelectedTab } = require('../tabs/utils');
+const { Cc, Ci } = require('chrome');
+const { Options } = require('../tabs/common');
+const system = require('../system/events');
+const { ignoreWindow, isPrivate, isWindowPBSupported } = require('../private-browsing/utils');
+const { data, isPrivateBrowsingSupported } = require('../self');
+const { setImmediate } = require('../timers');
+
+const supportPrivateWindows = isPrivateBrowsingSupported && isWindowPBSupported;
+
+const modelsFor = new WeakMap();
+const viewsFor = new WeakMap();
+
+const Window = Class({
+ implements: [EventTarget],
+ initialize: function(domWindow) {
+ modelsFor.set(domWindow, this);
+ viewsFor.set(this, domWindow);
+ },
+
+ get title() {
+ return getWindowTitle(viewsFor.get(this));
+ },
+
+ activate: function() {
+ viewsFor.get(this).focus();
+ },
+
+ close: function(callback) {
+ let domWindow = viewsFor.get(this);
+
+ if (callback) {
+ // We want to catch the close event immediately after the close events are
+ // emitted everywhere but without letting the event loop spin. Registering
+ // for the same events as windowEventListener but afterwards does this
+ let listener = (event, closedWin) => {
+ if (event != "close" || closedWin != domWindow)
+ return;
+
+ observer.off("*", listener);
+ callback();
+ }
+
+ observer.on("*", listener);
+ }
+
+ domWindow.close();
+ }
+});
+
+const windowTabs = new WeakMap();
+
+const BrowserWindow = Class({
+ extends: Window,
+
+ get tabs() {
+ let tabs = windowTabs.get(this);
+ if (tabs)
+ return tabs;
+
+ return new WindowTabs(this);
+ }
+});
+
+const WindowTabs = Class({
+ implements: [EventTarget],
+ extends: List,
+ initialize: function(window) {
+ List.prototype.initialize.call(this);
+ windowTabs.set(window, this);
+ viewsFor.set(this, viewsFor.get(window));
+
+ // Make sure the tabs module has loaded and found all existing tabs
+ const tabs = require('../tabs');
+
+ for (let tab of tabs) {
+ if (tab.window == window)
+ addListItem(this, tab);
+ }
+ },
+
+ get activeTab() {
+ return modelFor(getSelectedTab(viewsFor.get(this)));
+ },
+
+ open: function(options) {
+ options = Options(options);
+
+ let domWindow = viewsFor.get(this);
+ let { Tab } = require('../tabs/tab-firefox');
+
+ // The capturing listener will see the TabOpen event before
+ // sdk/tabs/observer giving us time to set up the tab and listeners before
+ // the real open event is fired
+ let listener = event => {
+ new Tab(event.target, options);
+ };
+
+ once(domWindow, "TabOpen", listener, true);
+ domWindow.gBrowser.addTab(options.url);
+ }
+});
+
+const BrowserWindows = Class({
+ implements: [EventTarget],
+ extends: List,
+ initialize: function() {
+ List.prototype.initialize.call(this);
+ },
+
+ get activeWindow() {
+ let domWindow = getMostRecentBrowserWindow();
+ if (ignoreWindow(domWindow))
+ return null;
+ return modelsFor.get(domWindow);
+ },
+
+ open: function(options) {
+ if (typeof options == "string")
+ options = { url: options };
+
+ let { url, isPrivate } = options;
+ if (url)
+ url = data.url(url);
+
+ let args = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ args.data = url;
+
+ let features = {
+ chrome: true,
+ all: true,
+ dialog: false
+ };
+ features.private = supportPrivateWindows && isPrivate;
+
+ let domWindow = open(null, {
+ parent: null,
+ name: "_blank",
+ features,
+ args
+ })
+
+ let window = makeNewWindow(domWindow, true);
+ setListeners(window, options);
+ return window;
+ }
+});
+
+const browserWindows = new BrowserWindows();
+exports.browserWindows = browserWindows;
+
+function windowEmit(window, event, ...args) {
+ if (window instanceof BrowserWindow && (event == "open" || event == "close"))
+ emitOnObject(window, event, browserWindows, window, ...args);
+ else
+ emit(window, event, window, ...args);
+
+ if (window instanceof BrowserWindow)
+ emit(browserWindows, event, window, ...args);
+}
+
+function makeNewWindow(domWindow, browserHint = false) {
+ if (browserHint || isBrowser(domWindow))
+ return new BrowserWindow(domWindow);
+ else
+ return new Window(domWindow);
+}
+
+for (let domWindow of windows(null, {includePrivate: supportPrivateWindows})) {
+ let window = makeNewWindow(domWindow);
+ if (window instanceof BrowserWindow)
+ addListItem(browserWindows, window);
+}
+
+var windowEventListener = (event, domWindow, ...args) => {
+ let toplevelWindow = getToplevelWindow(domWindow);
+
+ if (ignoreWindow(toplevelWindow))
+ return;
+
+ let window = modelsFor.get(toplevelWindow);
+ if (!window)
+ window = makeNewWindow(toplevelWindow);
+
+ if (isBrowser(toplevelWindow)) {
+ if (event == "open")
+ addListItem(browserWindows, window);
+ else if (event == "close")
+ removeListItem(browserWindows, window);
+ }
+
+ windowEmit(window, event, ...args);
+
+ // The window object shouldn't be reachable after closed
+ if (event == "close") {
+ viewsFor.delete(window);
+ modelsFor.delete(toplevelWindow);
+ }
+};
+observer.on("*", windowEventListener);
+
+viewFor.define(BrowserWindow, window => {
+ return viewsFor.get(window);
+})
+
+const isBrowserWindow = (x) => x instanceof BrowserWindow;
+isPrivate.when(isBrowserWindow, (w) => isWindowPrivate(viewsFor.get(w)));
+isFocused.when(isBrowserWindow, (w) => isFocused(viewsFor.get(w)));
diff --git a/components/jetpack/sdk/windows/observer.js b/components/jetpack/sdk/windows/observer.js
new file mode 100644
index 000000000..5ba2535f1
--- /dev/null
+++ b/components/jetpack/sdk/windows/observer.js
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { EventTarget } = require("../event/target");
+const { emit } = require("../event/core");
+const { WindowTracker, windowIterator } = require("../deprecated/window-utils");
+const { DOMEventAssembler } = require("../deprecated/events/assembler");
+const { Class } = require("../core/heritage");
+const { Cu } = require("chrome");
+
+// Event emitter objects used to register listeners and emit events on them
+// when they occur.
+const Observer = Class({
+ initialize() {
+ // Using `WindowTracker` to track window events.
+ WindowTracker({
+ onTrack: chromeWindow => {
+ emit(this, "open", chromeWindow);
+ this.observe(chromeWindow);
+ },
+ onUntrack: chromeWindow => {
+ emit(this, "close", chromeWindow);
+ this.ignore(chromeWindow);
+ }
+ });
+ },
+ implements: [EventTarget, DOMEventAssembler],
+ /**
+ * Events that are supported and emitted by the module.
+ */
+ supportedEventsTypes: [ "activate", "deactivate" ],
+ /**
+ * Function handles all the supported events on all the windows that are
+ * observed. Method is used to proxy events to the listeners registered on
+ * this event emitter.
+ * @param {Event} event
+ * Keyboard event being emitted.
+ */
+ handleEvent(event) {
+ // Ignore events from windows in the child process as they can't be top-level
+ if (Cu.isCrossProcessWrapper(event.target))
+ return;
+ emit(this, event.type, event.target, event);
+ }
+});
+
+exports.observer = new Observer();
diff --git a/components/jetpack/sdk/windows/tabs-fennec.js b/components/jetpack/sdk/windows/tabs-fennec.js
new file mode 100644
index 000000000..0ef5ec9f5
--- /dev/null
+++ b/components/jetpack/sdk/windows/tabs-fennec.js
@@ -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/. */
+'use strict';
+
+const { Class } = require('../core/heritage');
+const { Tab } = require('../tabs/tab');
+const { browserWindows } = require('./fennec');
+const { windowNS } = require('../window/namespace');
+const { tabsNS, tabNS } = require('../tabs/namespace');
+const { openTab, getTabs, getSelectedTab, getTabForBrowser: getRawTabForBrowser,
+ getTabContentWindow } = require('../tabs/utils');
+const { Options } = require('../tabs/common');
+const { getTabForBrowser, getTabForRawTab } = require('../tabs/helpers');
+const { on, once, off, emit } = require('../event/core');
+const { method } = require('../lang/functional');
+const { EVENTS } = require('../tabs/events');
+const { EventTarget } = require('../event/target');
+const { when: unload } = require('../system/unload');
+const { windowIterator } = require('../deprecated/window-utils');
+const { List, addListItem, removeListItem } = require('../util/list');
+const { isPrivateBrowsingSupported, data } = require('../self');
+const { isTabPBSupported, ignoreWindow } = require('../private-browsing/utils');
+
+const mainWindow = windowNS(browserWindows.activeWindow).window;
+
+const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';
+
+const supportPrivateTabs = isPrivateBrowsingSupported && isTabPBSupported;
+
+const Tabs = Class({
+ implements: [ List ],
+ extends: EventTarget,
+ initialize: function initialize(options) {
+ let tabsInternals = tabsNS(this);
+ let window = tabsNS(this).window = options.window || mainWindow;
+
+ EventTarget.prototype.initialize.call(this, options);
+ List.prototype.initialize.apply(this, getTabs(window).map(Tab));
+
+ // TabOpen event
+ window.BrowserApp.deck.addEventListener(EVENTS.open.dom, onTabOpen, false);
+
+ // TabSelect
+ window.BrowserApp.deck.addEventListener(EVENTS.activate.dom, onTabSelect, false);
+ },
+ get activeTab() {
+ return getTabForRawTab(getSelectedTab(tabsNS(this).window));
+ },
+ open: function(options) {
+ options = Options(options);
+ let activeWin = browserWindows.activeWindow;
+
+ if (options.isPinned) {
+ console.error(ERR_FENNEC_MSG); // TODO
+ }
+
+ let url = options.url ? data.url(options.url) : options.url;
+ let rawTab = openTab(windowNS(activeWin).window, url, {
+ inBackground: options.inBackground,
+ isPrivate: supportPrivateTabs && options.isPrivate
+ });
+
+ // by now the tab has been created
+ let tab = getTabForRawTab(rawTab);
+
+ if (options.onClose)
+ tab.on('close', options.onClose);
+
+ if (options.onOpen) {
+ // NOTE: on Fennec this will be true
+ if (tabNS(tab).opened)
+ options.onOpen(tab);
+
+ tab.on('open', options.onOpen);
+ }
+
+ if (options.onReady)
+ tab.on('ready', options.onReady);
+
+ if (options.onLoad)
+ tab.on('load', options.onLoad);
+
+ if (options.onPageShow)
+ tab.on('pageshow', options.onPageShow);
+
+ if (options.onActivate)
+ tab.on('activate', options.onActivate);
+
+ return tab;
+ }
+});
+var gTabs = exports.tabs = Tabs(mainWindow);
+
+function tabsUnloader(event, window) {
+ window = window || (event && event.target);
+ if (!(window && window.BrowserApp))
+ return;
+ window.BrowserApp.deck.removeEventListener(EVENTS.open.dom, onTabOpen, false);
+ window.BrowserApp.deck.removeEventListener(EVENTS.activate.dom, onTabSelect, false);
+}
+
+// unload handler
+unload(function() {
+ for (let window in windowIterator()) {
+ tabsUnloader(null, window);
+ }
+});
+
+function addTab(tab) {
+ addListItem(gTabs, tab);
+ return tab;
+}
+
+function removeTab(tab) {
+ removeListItem(gTabs, tab);
+ return tab;
+}
+
+// TabOpen
+function onTabOpen(event) {
+ let browser = event.target;
+
+ // Eventually ignore private tabs
+ if (ignoreWindow(browser.contentWindow))
+ return;
+
+ let tab = getTabForBrowser(browser);
+ if (tab === null) {
+ let rawTab = getRawTabForBrowser(browser);
+
+ // create a Tab instance for this new tab
+ tab = addTab(Tab(rawTab));
+ }
+
+ tabNS(tab).opened = true;
+
+ tab.on('ready', () => emit(gTabs, 'ready', tab));
+ tab.once('close', onTabClose);
+
+ tab.on('pageshow', (_tab, persisted) =>
+ emit(gTabs, 'pageshow', tab, persisted));
+
+ emit(tab, 'open', tab);
+ emit(gTabs, 'open', tab);
+}
+
+// TabSelect
+function onTabSelect(event) {
+ let browser = event.target;
+
+ // Eventually ignore private tabs
+ if (ignoreWindow(browser.contentWindow))
+ return;
+
+ // Set value whenever new tab becomes active.
+ let tab = getTabForBrowser(browser);
+ emit(tab, 'activate', tab);
+ emit(gTabs, 'activate', tab);
+
+ for (let t of gTabs) {
+ if (t === tab) continue;
+ emit(t, 'deactivate', t);
+ emit(gTabs, 'deactivate', t);
+ }
+}
+
+// TabClose
+function onTabClose(tab) {
+ removeTab(tab);
+ emit(gTabs, EVENTS.close.name, tab);
+}
diff --git a/components/jetpack/sdk/worker/utils.js b/components/jetpack/sdk/worker/utils.js
new file mode 100644
index 000000000..fca19be63
--- /dev/null
+++ b/components/jetpack/sdk/worker/utils.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+module.metadata = {
+ 'stability': 'deprecated'
+};
+
+const {
+ requiresAddonGlobal, attach, detach, destroy, WorkerHost
+} = require('../content/utils');
+
+exports.WorkerHost = WorkerHost;
+exports.detach = detach;
+exports.attach = attach;
+exports.destroy = destroy;
+exports.requiresAddonGlobal = requiresAddonGlobal;
diff --git a/components/jetpack/sdk/zip/utils.js b/components/jetpack/sdk/zip/utils.js
new file mode 100644
index 000000000..e600380cb
--- /dev/null
+++ b/components/jetpack/sdk/zip/utils.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/. */
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+
+function getZipReader(aFile) {
+ return new Promise(resolve => {
+ let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+ createInstance(Ci.nsIZipReader);
+ zipReader.open(aFile);
+ resolve(zipReader);
+ });
+};
+exports.getZipReader = getZipReader;
diff --git a/components/jetpack/test.js b/components/jetpack/test.js
new file mode 100644
index 000000000..f0219f0a4
--- /dev/null
+++ b/components/jetpack/test.js
@@ -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/. */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+module.exports = require("sdk/test");
diff --git a/components/jetpack/toolkit/loader.js b/components/jetpack/toolkit/loader.js
new file mode 100644
index 000000000..4c22550d8
--- /dev/null
+++ b/components/jetpack/toolkit/loader.js
@@ -0,0 +1,1147 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+;((factory) => { // Module boilerplate :(
+ if (typeof(require) === 'function') { // CommonJS
+ require("chrome").Cu.import(module.uri, exports);
+ }
+ else if (~String(this).indexOf('BackstagePass')) { // JSM
+ let module = { uri: __URI__, id: "toolkit/loader", exports: Object.create(null) }
+ factory(module);
+ Object.assign(this, module.exports);
+ this.EXPORTED_SYMBOLS = Object.getOwnPropertyNames(module.exports);
+ }
+ else {
+ throw Error("Loading environment is not supported");
+ }
+})(module => {
+
+'use strict';
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
+ results: Cr, manager: Cm } = Components;
+const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
+const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
+ getService(Ci.mozIJSSubScriptLoader);
+const { addObserver, notifyObservers } = Cc['@mozilla.org/observer-service;1'].
+ getService(Ci.nsIObserverService);
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "resProto",
+ "@mozilla.org/network/protocol;1?name=resource",
+ "nsIResProtocolHandler");
+XPCOMUtils.defineLazyServiceGetter(this, "zipCache",
+ "@mozilla.org/libjar/zip-reader-cache;1",
+ "nsIZipReaderCache");
+
+XPCOMUtils.defineLazyGetter(this, "XulApp", () => {
+ let xulappURI = module.uri.replace("toolkit/loader.js",
+ "sdk/system/xul-app.jsm");
+ return Cu.import(xulappURI, {});
+});
+
+// Define some shortcuts.
+const bind = Function.call.bind(Function.bind);
+const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+const prototypeOf = Object.getPrototypeOf;
+const getOwnIdentifiers = x => [...Object.getOwnPropertyNames(x),
+ ...Object.getOwnPropertySymbols(x)];
+
+const NODE_MODULES = new Set([
+ "assert",
+ "buffer_ieee754",
+ "buffer",
+ "child_process",
+ "cluster",
+ "console",
+ "constants",
+ "crypto",
+ "_debugger",
+ "dgram",
+ "dns",
+ "domain",
+ "events",
+ "freelist",
+ "fs",
+ "http",
+ "https",
+ "_linklist",
+ "module",
+ "net",
+ "os",
+ "path",
+ "punycode",
+ "querystring",
+ "readline",
+ "repl",
+ "stream",
+ "string_decoder",
+ "sys",
+ "timers",
+ "tls",
+ "tty",
+ "url",
+ "util",
+ "vm",
+ "zlib",
+]);
+
+const COMPONENT_ERROR = '`Components` is not available in this context.\n' +
+ 'Functionality provided by Components may be available in an SDK\n' +
+ 'module: https://developer.mozilla.org/en-US/Add-ons/SDK \n\n' +
+ 'However, if you still need to import Components, you may use the\n' +
+ '`chrome` module\'s properties for shortcuts to Component properties:\n\n' +
+ 'Shortcuts: \n' +
+ ' Cc = Components' + '.classes \n' +
+ ' Ci = Components' + '.interfaces \n' +
+ ' Cu = Components' + '.utils \n' +
+ ' CC = Components' + '.Constructor \n' +
+ 'Example: \n' +
+ ' let { Cc, Ci } = require(\'chrome\');\n';
+
+// Workaround for bug 674195. Freezing objects from other compartments fail,
+// so we use `Object.freeze` from the same component instead.
+function freeze(object) {
+ if (prototypeOf(object) === null) {
+ Object.freeze(object);
+ }
+ else {
+ prototypeOf(prototypeOf(object.isPrototypeOf)).
+ constructor. // `Object` from the owner compartment.
+ freeze(object);
+ }
+ return object;
+}
+
+// Returns map of given `object`-s own property descriptors.
+const descriptor = iced(function descriptor(object) {
+ let value = {};
+ getOwnIdentifiers(object).forEach(function(name) {
+ value[name] = getOwnPropertyDescriptor(object, name)
+ });
+ return value;
+});
+Loader.descriptor = descriptor;
+
+// Freeze important built-ins so they can't be used by untrusted code as a
+// message passing channel.
+freeze(Object);
+freeze(Object.prototype);
+freeze(Function);
+freeze(Function.prototype);
+freeze(Array);
+freeze(Array.prototype);
+freeze(String);
+freeze(String.prototype);
+
+// This function takes `f` function sets it's `prototype` to undefined and
+// freezes it. We need to do this kind of deep freeze with all the exposed
+// functions so that untrusted code won't be able to use them a message
+// passing channel.
+function iced(f) {
+ if (!Object.isFrozen(f)) {
+ f.prototype = undefined;
+ }
+ return freeze(f);
+}
+
+// Defines own properties of given `properties` object on the given
+// target object overriding any existing property with a conflicting name.
+// Returns `target` object. Note we only export this function because it's
+// useful during loader bootstrap when other util modules can't be used &
+// thats only case where this export should be used.
+const override = iced(function override(target, source) {
+ let properties = descriptor(target)
+ let extension = descriptor(source || {})
+ getOwnIdentifiers(extension).forEach(function(name) {
+ properties[name] = extension[name];
+ });
+ return Object.defineProperties({}, properties);
+});
+Loader.override = override;
+
+function sourceURI(uri) { return String(uri).split(" -> ").pop(); }
+Loader.sourceURI = iced(sourceURI);
+
+function isntLoaderFrame(frame) { return frame.fileName !== module.uri }
+
+function parseURI(uri) { return String(uri).split(" -> ").pop(); }
+Loader.parseURI = parseURI;
+
+function parseStack(stack) {
+ let lines = String(stack).split("\n");
+ return lines.reduce(function(frames, line) {
+ if (line) {
+ let atIndex = line.indexOf("@");
+ let columnIndex = line.lastIndexOf(":");
+ let lineIndex = line.lastIndexOf(":", columnIndex - 1);
+ let fileName = parseURI(line.slice(atIndex + 1, lineIndex));
+ let lineNumber = parseInt(line.slice(lineIndex + 1, columnIndex));
+ let columnNumber = parseInt(line.slice(columnIndex + 1));
+ let name = line.slice(0, atIndex).split("(").shift();
+ frames.unshift({
+ fileName: fileName,
+ name: name,
+ lineNumber: lineNumber,
+ columnNumber: columnNumber
+ });
+ }
+ return frames;
+ }, []);
+}
+Loader.parseStack = parseStack;
+
+function serializeStack(frames) {
+ return frames.reduce(function(stack, frame) {
+ return frame.name + "@" +
+ frame.fileName + ":" +
+ frame.lineNumber + ":" +
+ frame.columnNumber + "\n" +
+ stack;
+ }, "");
+}
+Loader.serializeStack = serializeStack;
+
+class DefaultMap extends Map {
+ constructor(createItem, items = undefined) {
+ super(items);
+
+ this.createItem = createItem;
+ }
+
+ get(key) {
+ if (!this.has(key)) {
+ this.set(key, this.createItem(key));
+ }
+
+ return super.get(key);
+ }
+}
+
+const urlCache = {
+ /**
+ * Returns a list of fully-qualified URLs for entries within the zip
+ * file at the given URI which are either directories or files with a
+ * .js or .json extension.
+ *
+ * @param {nsIJARURI} uri
+ * @param {string} baseURL
+ * The original base URL, prior to resolution.
+ *
+ * @returns {Set<string>}
+ */
+ getZipFileContents(uri, baseURL) {
+ // Make sure the path has a trailing slash, and strip off the leading
+ // slash, so that we can easily check whether it is a path prefix.
+ let basePath = addTrailingSlash(uri.JAREntry).slice(1);
+ let file = uri.JARFile.QueryInterface(Ci.nsIFileURL).file;
+
+ let enumerator = zipCache.getZip(file).findEntries("(*.js|*.json|*/)");
+
+ let results = new Set();
+ for (let entry of XPCOMUtils.IterStringEnumerator(enumerator)) {
+ if (entry.startsWith(basePath)) {
+ let path = entry.slice(basePath.length);
+
+ results.add(baseURL + path);
+ }
+ }
+
+ return results;
+ },
+
+ zipContentsCache: new DefaultMap(baseURL => {
+ let uri = NetUtil.newURI(baseURL);
+
+ if (baseURL.startsWith("resource:")) {
+ uri = NetUtil.newURI(resProto.resolveURI(uri));
+ }
+
+ if (uri instanceof Ci.nsIJARURI) {
+ return urlCache.getZipFileContents(uri, baseURL);
+ }
+
+ return null;
+ }),
+
+ filesCache: new DefaultMap(url => {
+ try {
+ let uri = NetUtil.newURI(url).QueryInterface(Ci.nsIFileURL);
+
+ return uri.file.exists();
+ } catch (e) {
+ return false;
+ }
+ }),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),
+
+ observe() {
+ // Clear any module resolution caches when the startup cache is flushed,
+ // since it probably means we're loading new copies of extensions.
+ this.zipContentsCache.clear();
+ this.filesCache.clear();
+ },
+
+ /**
+ * Returns the base URL for the given URL, if one can be determined. For
+ * a resource: URL, this is the root of the resource package. For a jar:
+ * URL, it is the root of the JAR file. Otherwise, null is returned.
+ *
+ * @param {string} url
+ * @returns {string?}
+ */
+ getBaseURL(url) {
+ // By using simple string matching for the common case of resource: URLs
+ // backed by jar: URLs, we can avoid creating any nsIURI objects for the
+ // common case where the JAR contents are already cached.
+ if (url.startsWith("resource://")) {
+ return /^resource:\/\/[^\/]+\//.exec(url)[0];
+ }
+
+ let uri = NetUtil.newURI(url);
+ if (uri instanceof Ci.nsIJARURI) {
+ return `jar:${uri.JARFile.spec}!/`;
+ }
+
+ return null;
+ },
+
+ /**
+ * Returns true if the target of the given URL exists as a local file,
+ * or as an entry in a local zip file.
+ *
+ * @param {string} url
+ * @returns {boolean}
+ */
+ exists(url) {
+ if (!/\.(?:js|json)$/.test(url)) {
+ url = addTrailingSlash(url);
+ }
+
+ let baseURL = this.getBaseURL(url);
+ let scripts = baseURL && this.zipContentsCache.get(baseURL);
+ if (scripts) {
+ return scripts.has(url);
+ }
+
+ return this.filesCache.get(url);
+ },
+}
+addObserver(urlCache, "startupcache-invalidate", true);
+
+function readURI(uri) {
+ let nsURI = NetUtil.newURI(uri);
+ if (nsURI.scheme == "resource") {
+ // Resolve to a real URI, this will catch any obvious bad paths without
+ // logging assertions in debug builds, see bug 1135219
+ uri = resProto.resolveURI(nsURI);
+ }
+
+ let stream = NetUtil.newChannel({
+ uri: NetUtil.newURI(uri, 'UTF-8'),
+ loadUsingSystemPrincipal: true}
+ ).open2();
+ let count = stream.available();
+ let data = NetUtil.readInputStreamToString(stream, count, {
+ charset: 'UTF-8'
+ });
+
+ stream.close();
+
+ return data;
+}
+
+// Combines all arguments into a resolved, normalized path
+function join(base, ...paths) {
+ // If this is an absolute URL, we need to normalize only the path portion,
+ // or we wind up stripping too many slashes and producing invalid URLs.
+ let match = /^((?:resource|file|chrome)\:\/\/[^\/]*|jar:[^!]+!)(.*)/.exec(base);
+ if (match) {
+ return match[1] + normalize(pathJoin(match[2], ...paths));
+ }
+
+ return normalize(pathJoin(base, ...paths));
+}
+Loader.join = join;
+
+// Function takes set of options and returns a JS sandbox. Function may be
+// passed set of options:
+// - `name`: A string value which identifies the sandbox in about:memory. Will
+// throw exception if omitted.
+// - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to
+// system principal.
+// - `prototype`: Ancestor for the sandbox that will be created. Defaults to
+// `{}`.
+// - `wantXrays`: A Boolean value indicating whether code outside the sandbox
+// wants X-ray vision with respect to objects inside the sandbox. Defaults
+// to `true`.
+// - `sandbox`: A sandbox to share JS compartment with. If omitted new
+// compartment will be created.
+// - `metadata`: A metadata object associated with the sandbox. It should
+// be JSON-serializable.
+// For more details see:
+// https://developer.mozilla.org/en/Components.utils.Sandbox
+const Sandbox = iced(function Sandbox(options) {
+ // Normalize options and rename to match `Cu.Sandbox` expectations.
+ options = {
+ // Do not expose `Components` if you really need them (bad idea!) you
+ // still can expose via prototype.
+ wantComponents: false,
+ sandboxName: options.name,
+ principal: 'principal' in options ? options.principal : systemPrincipal,
+ wantXrays: 'wantXrays' in options ? options.wantXrays : true,
+ wantGlobalProperties: 'wantGlobalProperties' in options ?
+ options.wantGlobalProperties : [],
+ sandboxPrototype: 'prototype' in options ? options.prototype : {},
+ invisibleToDebugger: 'invisibleToDebugger' in options ?
+ options.invisibleToDebugger : false,
+ metadata: 'metadata' in options ? options.metadata : {},
+ waiveIntereposition: !!options.waiveIntereposition
+ };
+
+ if (options.metadata && options.metadata.addonID) {
+ options.addonId = options.metadata.addonID;
+ }
+
+ let sandbox = Cu.Sandbox(options.principal, options);
+
+ // Each sandbox at creation gets set of own properties that will be shadowing
+ // ones from it's prototype. We override delete such `sandbox` properties
+ // to avoid shadowing.
+ delete sandbox.Iterator;
+ delete sandbox.Components;
+ delete sandbox.importFunction;
+ delete sandbox.debug;
+
+ return sandbox;
+});
+Loader.Sandbox = Sandbox;
+
+// Evaluates code from the given `uri` into given `sandbox`. If
+// `options.source` is passed, then that code is evaluated instead.
+// Optionally following options may be given:
+// - `options.encoding`: Source encoding, defaults to 'UTF-8'.
+// - `options.line`: Line number to start count from for stack traces.
+// Defaults to 1.
+// - `options.version`: Version of JS used, defaults to '1.8'.
+const evaluate = iced(function evaluate(sandbox, uri, options) {
+ let { source, line, version, encoding } = override({
+ encoding: 'UTF-8',
+ line: 1,
+ version: '1.8',
+ source: null
+ }, options);
+
+ return source ? Cu.evalInSandbox(source, sandbox, version, uri, line)
+ : loadSubScript(uri, sandbox, encoding);
+});
+Loader.evaluate = evaluate;
+
+// Populates `exports` of the given CommonJS `module` object, in the context
+// of the given `loader` by evaluating code associated with it.
+const load = iced(function load(loader, module) {
+ let { sandboxes, globals, loadModuleHook } = loader;
+ let require = Require(loader, module);
+
+ // We expose set of properties defined by `CommonJS` specification via
+ // prototype of the sandbox. Also globals are deeper in the prototype
+ // chain so that each module has access to them as well.
+ let descriptors = descriptor({
+ require: require,
+ module: module,
+ exports: module.exports,
+ get Components() {
+ // Expose `Components` property to throw error on usage with
+ // additional information
+ throw new ReferenceError(COMPONENT_ERROR);
+ }
+ });
+
+ let sandbox;
+ if ((loader.useSharedGlobalSandbox || isSystemURI(module.uri)) &&
+ loader.sharedGlobalBlocklist.indexOf(module.id) == -1) {
+ // Create a new object in this sandbox, that will be used as
+ // the scope object for this particular module
+ sandbox = new loader.sharedGlobalSandbox.Object();
+ // Inject all expected globals in the scope object
+ getOwnIdentifiers(globals).forEach(function(name) {
+ descriptors[name] = getOwnPropertyDescriptor(globals, name)
+ descriptors[name].configurable = true;
+ });
+ Object.defineProperties(sandbox, descriptors);
+ }
+ else {
+ sandbox = Sandbox({
+ name: module.uri,
+ prototype: Object.create(globals, descriptors),
+ wantXrays: false,
+ wantGlobalProperties: module.id == "sdk/indexed-db" ? ["indexedDB"] : [],
+ invisibleToDebugger: loader.invisibleToDebugger,
+ metadata: {
+ addonID: loader.id,
+ URI: module.uri
+ }
+ });
+ }
+ sandboxes[module.uri] = sandbox;
+
+ try {
+ evaluate(sandbox, module.uri);
+ }
+ catch (error) {
+ let { message, fileName, lineNumber } = error;
+ let stack = error.stack || Error().stack;
+ let frames = parseStack(stack).filter(isntLoaderFrame);
+ let toString = String(error);
+ let file = sourceURI(fileName);
+
+ // Note that `String(error)` where error is from subscript loader does
+ // not puts `:` after `"Error"` unlike regular errors thrown by JS code.
+ // If there is a JS stack then this error has already been handled by an
+ // inner module load.
+ if (/^Error opening input stream/.test(String(error))) {
+ let caller = frames.slice(0).pop();
+ fileName = caller.fileName;
+ lineNumber = caller.lineNumber;
+ message = "Module `" + module.id + "` is not found at " + module.uri;
+ toString = message;
+ }
+ // Workaround for a Bug 910653. Errors thrown by subscript loader
+ // do not include `stack` field and above created error won't have
+ // fileName or lineNumber of the module being loaded, so we ensure
+ // it does.
+ else if (frames[frames.length - 1].fileName !== file) {
+ frames.push({ fileName: file, lineNumber: lineNumber, name: "" });
+ }
+
+ let prototype = typeof(error) === "object" ? error.constructor.prototype :
+ Error.prototype;
+
+ throw Object.create(prototype, {
+ message: { value: message, writable: true, configurable: true },
+ fileName: { value: fileName, writable: true, configurable: true },
+ lineNumber: { value: lineNumber, writable: true, configurable: true },
+ stack: { value: serializeStack(frames), writable: true, configurable: true },
+ toString: { value: () => toString, writable: true, configurable: true },
+ });
+ }
+
+ if (loadModuleHook) {
+ module = loadModuleHook(module, require);
+ }
+
+ if (loader.checkCompatibility) {
+ let err = XulApp.incompatibility(module);
+ if (err) {
+ throw err;
+ }
+ }
+
+ if (module.exports && typeof(module.exports) === 'object')
+ freeze(module.exports);
+
+ return module;
+});
+Loader.load = load;
+
+// Utility function to normalize module `uri`s so they have `.js` extension.
+function normalizeExt(uri) {
+ return isJSURI(uri) ? uri :
+ isJSONURI(uri) ? uri :
+ isJSMURI(uri) ? uri :
+ uri + '.js';
+}
+
+// Strips `rootURI` from `string` -- used to remove absolute resourceURI
+// from a relative path
+function stripBase(rootURI, string) {
+ return string.replace(rootURI, './');
+}
+
+// Utility function to join paths. In common case `base` is a
+// `requirer.uri` but in some cases it may be `baseURI`. In order to
+// avoid complexity we require `baseURI` with a trailing `/`.
+const resolve = iced(function resolve(id, base) {
+ if (!isRelative(id))
+ return id;
+
+ let baseDir = dirname(base);
+ if (!baseDir)
+ return normalize(id);
+
+ let resolved = join(baseDir, id);
+
+ // Joining and normalizing removes the './' from relative files.
+ // We need to ensure the resolution still has the root
+ if (isRelative(base))
+ resolved = './' + resolved;
+
+ return resolved;
+});
+Loader.resolve = resolve;
+
+// Attempts to load `path` and then `path.js`
+// Returns `path` with valid file, or `undefined` otherwise
+function resolveAsFile(path) {
+ // Append '.js' to path name unless it's another support filetype
+ path = normalizeExt(path);
+ if (urlCache.exists(path)) {
+ return path;
+ }
+
+ return null;
+}
+
+// Attempts to load `path/package.json`'s `main` entry,
+// followed by `path/index.js`, or `undefined` otherwise
+function resolveAsDirectory(path) {
+ try {
+ // If `path/package.json` exists, parse the `main` entry
+ // and attempt to load that
+ let manifestPath = addTrailingSlash(path) + 'package.json';
+
+ let main = (urlCache.exists(manifestPath) &&
+ getManifestMain(JSON.parse(readURI(manifestPath))));
+ if (main) {
+ let found = resolveAsFile(join(path, main));
+ if (found) {
+ return found
+ }
+ }
+ } catch (e) {}
+
+ return resolveAsFile(addTrailingSlash(path) + 'index.js');
+}
+
+function resolveRelative(rootURI, modulesDir, id) {
+ let fullId = join(rootURI, modulesDir, id);
+
+ let resolvedPath = (resolveAsFile(fullId) ||
+ resolveAsDirectory(fullId));
+ if (resolvedPath) {
+ return stripBase(rootURI, resolvedPath);
+ }
+
+ return null;
+}
+
+// From `resolve` module
+// https://github.com/substack/node-resolve/blob/master/lib/node-modules-paths.js
+function* getNodeModulePaths(rootURI, start) {
+ let moduleDir = 'node_modules';
+
+ let parts = start.split('/');
+ while (parts.length) {
+ let leaf = parts.pop();
+ let path = join(...parts, leaf, moduleDir);
+ if (leaf !== moduleDir && urlCache.exists(join(rootURI, path))) {
+ yield path;
+ }
+ }
+
+ if (urlCache.exists(join(rootURI, moduleDir))) {
+ yield moduleDir;
+ }
+}
+
+// Node-style module lookup
+// Takes an id and path and attempts to load a file using node's resolving
+// algorithm.
+// `id` should already be resolved relatively at this point.
+// http://nodejs.org/api/modules.html#modules_all_together
+const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
+ // Resolve again
+ id = Loader.resolve(id, requirer);
+
+ // If this is already an absolute URI then there is no resolution to do
+ if (isAbsoluteURI(id)) {
+ return null;
+ }
+
+ // we assume that extensions are correct, i.e., a directory doesnt't have '.js'
+ // and a js file isn't named 'file.json.js'
+ let resolvedPath;
+
+ if ((resolvedPath = resolveRelative(rootURI, "", id))) {
+ return resolvedPath;
+ }
+
+ // If the requirer is an absolute URI then the node module resolution below
+ // won't work correctly as we prefix everything with rootURI
+ if (isAbsoluteURI(requirer)) {
+ return null;
+ }
+
+ // If manifest has dependencies, attempt to look up node modules
+ // in the `dependencies` list
+ for (let modulesDir of getNodeModulePaths(rootURI, dirname(requirer))) {
+ if ((resolvedPath = resolveRelative(rootURI, modulesDir, id))) {
+ return resolvedPath;
+ }
+ }
+
+ // We would not find lookup for things like `sdk/tabs`, as that's part of
+ // the alias mapping. If during `generateMap`, the runtime lookup resolves
+ // with `resolveURI` -- if during runtime, then `resolve` will throw.
+ return null;
+});
+
+Loader.nodeResolve = nodeResolve;
+
+function addTrailingSlash(path) {
+ return path.replace(/\/*$/, "/");
+}
+
+const resolveURI = iced(function resolveURI(id, mapping) {
+ // Do not resolve if already a resource URI
+ if (isAbsoluteURI(id))
+ return normalizeExt(id);
+
+ for (let [path, uri] of mapping) {
+ // Strip off any trailing slashes to make comparisons simpler
+ let stripped = path.replace(/\/+$/, "");
+
+ // We only want to match path segments explicitly. Examples:
+ // * "foo/bar" matches for "foo/bar"
+ // * "foo/bar" matches for "foo/bar/baz"
+ // * "foo/bar" does not match for "foo/bar-1"
+ // * "foo/bar/" does not match for "foo/bar"
+ // * "foo/bar/" matches for "foo/bar/baz"
+ //
+ // Check for an empty path, an exact match, or a substring match
+ // with the next character being a forward slash.
+ if(stripped === "" || id === stripped || id.startsWith(stripped + "/")) {
+ return normalizeExt(id.replace(path, uri));
+ }
+ }
+ return null;
+});
+Loader.resolveURI = resolveURI;
+
+// Creates version of `require` that will be exposed to the given `module`
+// in the context of the given `loader`. Each module gets own limited copy
+// of `require` that is allowed to load only a modules that are associated
+// with it during link time.
+const Require = iced(function Require(loader, requirer) {
+ let {
+ modules, mapping, resolve: loaderResolve, load,
+ manifest, rootURI, isNative, requireMap,
+ requireHook
+ } = loader;
+
+ if (isSystemURI(requirer.uri)) {
+ // Built-in modules don't require the expensive module resolution
+ // algorithm used by SDK add-ons, so give them the more efficient standard
+ // resolve instead.
+ isNative = false;
+ loaderResolve = Loader.resolve;
+ }
+
+ function require(id) {
+ if (!id) // Throw if `id` is not passed.
+ throw Error('You must provide a module name when calling require() from '
+ + requirer.id, requirer.uri);
+
+ if (requireHook) {
+ return requireHook(id, _require);
+ }
+
+ return _require(id);
+ }
+
+ function _require(id) {
+ let { uri, requirement } = getRequirements(id);
+ let module = null;
+ // If module is already cached by loader then just use it.
+ if (uri in modules) {
+ module = modules[uri];
+ }
+ else if (isJSMURI(uri)) {
+ module = modules[uri] = Module(requirement, uri);
+ module.exports = Cu.import(uri, {});
+ freeze(module);
+ }
+ else if (isJSONURI(uri)) {
+ let data;
+
+ // First attempt to load and parse json uri
+ // ex: `test.json`
+ // If that doesn't exist, check for `test.json.js`
+ // for node parity
+ try {
+ data = JSON.parse(readURI(uri));
+ module = modules[uri] = Module(requirement, uri);
+ module.exports = data;
+ freeze(module);
+ }
+ catch (err) {
+ // If error thrown from JSON parsing, throw that, do not
+ // attempt to find .json.js file
+ if (err && /JSON\.parse/.test(err.message))
+ throw err;
+ uri = uri + '.js';
+ }
+ }
+
+ // If not yet cached, load and cache it.
+ // We also freeze module to prevent it from further changes
+ // at runtime.
+ if (!(uri in modules)) {
+ // Many of the loader's functionalities are dependent
+ // on modules[uri] being set before loading, so we set it and
+ // remove it if we have any errors.
+ module = modules[uri] = Module(requirement, uri);
+ try {
+ freeze(load(loader, module));
+ }
+ catch (e) {
+ // Clear out modules cache so we can throw on a second invalid require
+ delete modules[uri];
+ // Also clear out the Sandbox that was created
+ delete loader.sandboxes[uri];
+ throw e;
+ }
+ }
+
+ return module.exports;
+ }
+
+ // Resolution function taking a module name/path and
+ // returning a resourceURI and a `requirement` used by the loader.
+ // Used by both `require` and `require.resolve`.
+ function getRequirements(id) {
+ if (!id) // Throw if `id` is not passed.
+ throw Error('you must provide a module name when calling require() from '
+ + requirer.id, requirer.uri);
+
+ let requirement, uri;
+
+ // TODO should get native Firefox modules before doing node-style lookups
+ // to save on loading time
+ if (isNative) {
+ // If a requireMap is available from `generateMap`, use that to
+ // immediately resolve the node-style mapping.
+ // TODO: write more tests for this use case
+ if (requireMap && requireMap[requirer.id])
+ requirement = requireMap[requirer.id][id];
+
+ let { overrides } = manifest.jetpack;
+ for (let key in overrides) {
+ // ignore any overrides using relative keys
+ if (/^[.\/]/.test(key)) {
+ continue;
+ }
+
+ // If the override is for x -> y,
+ // then using require("x/lib/z") to get reqire("y/lib/z")
+ // should also work
+ if (id == key || id.startsWith(key + "/")) {
+ id = overrides[key] + id.substr(key.length);
+ id = id.replace(/^[.\/]+/, "");
+ }
+ }
+
+ // For native modules, we want to check if it's a module specified
+ // in 'modules', like `chrome`, or `@loader` -- if it exists,
+ // just set the uri to skip resolution
+ if (!requirement && modules[id])
+ uri = requirement = id;
+
+ // If no requireMap was provided, or resolution not found in
+ // the requireMap, and not a npm dependency, attempt a runtime lookup
+ if (!requirement && !NODE_MODULES.has(id)) {
+ // If `isNative` defined, this is using the new, native-style
+ // loader, not cuddlefish, so lets resolve using node's algorithm
+ // and get back a path that needs to be resolved via paths mapping
+ // in `resolveURI`
+ requirement = loaderResolve(id, requirer.id, {
+ manifest: manifest,
+ rootURI: rootURI
+ });
+ }
+
+ // If not found in the map, not a node module, and wasn't able to be
+ // looked up, it's something
+ // found in the paths most likely, like `sdk/tabs`, which should
+ // be resolved relatively if needed using traditional resolve
+ if (!requirement) {
+ requirement = isRelative(id) ? Loader.resolve(id, requirer.id) : id;
+ }
+ }
+ else if (modules[id]) {
+ uri = requirement = id;
+ }
+ else if (requirer) {
+ // Resolve `id` to its requirer if it's relative.
+ requirement = loaderResolve(id, requirer.id);
+ }
+ else {
+ requirement = id;
+ }
+
+ // Resolves `uri` of module using loaders resolve function.
+ uri = uri || resolveURI(requirement, mapping);
+
+ // Throw if `uri` can not be resolved.
+ if (!uri) {
+ throw Error('Module: Can not resolve "' + id + '" module required by ' +
+ requirer.id + ' located at ' + requirer.uri, requirer.uri);
+ }
+
+ return { uri: uri, requirement: requirement };
+ }
+
+ // Expose the `resolve` function for this `Require` instance
+ require.resolve = _require.resolve = function resolve(id) {
+ let { uri } = getRequirements(id);
+ return uri;
+ }
+
+ // This is like webpack's require.context. It returns a new require
+ // function that prepends the prefix to any requests.
+ require.context = prefix => {
+ return id => {
+ return require(prefix + id);
+ };
+ };
+
+ // Make `require.main === module` evaluate to true in main module scope.
+ require.main = loader.main === requirer ? requirer : undefined;
+ return iced(require);
+});
+Loader.Require = Require;
+
+const main = iced(function main(loader, id) {
+ // If no main entry provided, and native loader is used,
+ // read the entry in the manifest
+ if (!id && loader.isNative)
+ id = getManifestMain(loader.manifest);
+ let uri = resolveURI(id, loader.mapping);
+ let module = loader.main = loader.modules[uri] = Module(id, uri);
+ return loader.load(loader, module).exports;
+});
+Loader.main = main;
+
+// Makes module object that is made available to CommonJS modules when they
+// are evaluated, along with `exports` and `require`.
+const Module = iced(function Module(id, uri) {
+ return Object.create(null, {
+ id: { enumerable: true, value: id },
+ exports: { enumerable: true, writable: true, value: Object.create(null),
+ configurable: true },
+ uri: { value: uri }
+ });
+});
+Loader.Module = Module;
+
+// Takes `loader`, and unload `reason` string and notifies all observers that
+// they should cleanup after them-self.
+const unload = iced(function unload(loader, reason) {
+ // subject is a unique object created per loader instance.
+ // This allows any code to cleanup on loader unload regardless of how
+ // it was loaded. To handle unload for specific loader subject may be
+ // asserted against loader.destructor or require('@loader/unload')
+ // Note: We don not destroy loader's module cache or sandboxes map as
+ // some modules may do cleanup in subsequent turns of event loop. Destroying
+ // cache may cause module identity problems in such cases.
+ let subject = { wrappedJSObject: loader.destructor };
+ notifyObservers(subject, 'sdk:loader:destroy', reason);
+});
+Loader.unload = unload;
+
+// Function makes new loader that can be used to load CommonJS modules
+// described by a given `options.manifest`. Loader takes following options:
+// - `globals`: Optional map of globals, that all module scopes will inherit
+// from. Map is also exposed under `globals` property of the returned loader
+// so it can be extended further later. Defaults to `{}`.
+// - `modules` Optional map of built-in module exports mapped by module id.
+// These modules will incorporated into module cache. Each module will be
+// frozen.
+// - `resolve` Optional module `id` resolution function. If given it will be
+// used to resolve module URIs, by calling it with require term, requirer
+// module object (that has `uri` property) and `baseURI` of the loader.
+// If `resolve` does not returns `uri` string exception will be thrown by
+// an associated `require` call.
+function Loader(options) {
+ if (options.sharedGlobalBlacklist && !options.sharedGlobalBlocklist) {
+ options.sharedGlobalBlocklist = options.sharedGlobalBlacklist;
+ }
+ let {
+ modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative,
+ metadata, sharedGlobal, sharedGlobalBlocklist, checkCompatibility, waiveIntereposition
+ } = override({
+ paths: {},
+ modules: {},
+ globals: {
+ get console() {
+ // Import Console.jsm from here to prevent loading it until someone uses it
+ let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm");
+ let console = new ConsoleAPI({
+ consoleID: options.id ? "addon/" + options.id : ""
+ });
+ Object.defineProperty(this, "console", { value: console });
+ return this.console;
+ }
+ },
+ checkCompatibility: false,
+ resolve: options.isNative ?
+ // Make the returned resolve function have the same signature
+ (id, requirer) => Loader.nodeResolve(id, requirer, { rootURI: rootURI }) :
+ Loader.resolve,
+ sharedGlobalBlocklist: ["sdk/indexed-db"],
+ waiveIntereposition: false
+ }, options);
+
+ // Create overrides defaults, none at the moment
+ if (typeof manifest != "object" || !manifest) {
+ manifest = {};
+ }
+ if (typeof manifest.jetpack != "object" || !manifest.jetpack) {
+ manifest.jetpack = {
+ overrides: {}
+ };
+ }
+ if (typeof manifest.jetpack.overrides != "object" || !manifest.jetpack.overrides) {
+ manifest.jetpack.overrides = {};
+ }
+
+ // We create an identity object that will be dispatched on an unload
+ // event as subject. This way unload listeners will be able to assert
+ // which loader is unloaded. Please note that we intentionally don't
+ // use `loader` as subject to prevent a loader access leakage through
+ // observer notifications.
+ let destructor = freeze(Object.create(null));
+
+ // Make mapping array that is sorted from longest path to shortest path.
+ let mapping = Object.keys(paths)
+ .sort((a, b) => b.length - a.length)
+ .map(path => [path, paths[path]]);
+
+ // Define pseudo modules.
+ modules = override({
+ '@loader/unload': destructor,
+ '@loader/options': options,
+ 'chrome': { Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
+ CC: bind(CC, Components), components: Components,
+ // `ChromeWorker` has to be inject in loader global scope.
+ // It is done by bootstrap.js:loadSandbox for the SDK.
+ ChromeWorker: ChromeWorker
+ }
+ }, modules);
+
+ const builtinModuleExports = modules;
+ modules = {};
+ for (let id of Object.keys(builtinModuleExports)) {
+ // We resolve `uri` from `id` since modules are cached by `uri`.
+ let uri = resolveURI(id, mapping);
+ // In native loader, the mapping will not contain values for
+ // pseudomodules -- store them as their ID rather than the URI
+ if (isNative && !uri)
+ uri = id;
+ let module = Module(id, uri);
+
+ // Lazily expose built-in modules in order to
+ // allow them to be loaded lazily.
+ Object.defineProperty(module, "exports", {
+ enumerable: true,
+ get: function() {
+ return builtinModuleExports[id];
+ }
+ });
+
+ modules[uri] = freeze(module);
+ }
+
+ // Create the unique sandbox we will be using for all modules,
+ // so that we prevent creating a new comportment per module.
+ // The side effect is that all modules will share the same
+ // global objects.
+ let sharedGlobalSandbox = Sandbox({
+ name: "Addon-SDK",
+ wantXrays: false,
+ wantGlobalProperties: [],
+ invisibleToDebugger: options.invisibleToDebugger || false,
+ metadata: {
+ addonID: options.id,
+ URI: "Addon-SDK"
+ },
+ prototype: options.sandboxPrototype || {}
+ });
+
+ // Loader object is just a representation of a environment
+ // state. We freeze it and mark make it's properties non-enumerable
+ // as they are pure implementation detail that no one should rely upon.
+ let returnObj = {
+ destructor: { enumerable: false, value: destructor },
+ globals: { enumerable: false, value: globals },
+ mapping: { enumerable: false, value: mapping },
+ // Map of module objects indexed by module URIs.
+ modules: { enumerable: false, value: modules },
+ metadata: { enumerable: false, value: metadata },
+ useSharedGlobalSandbox: { enumerable: false, value: !!sharedGlobal },
+ sharedGlobalSandbox: { enumerable: false, value: sharedGlobalSandbox },
+ sharedGlobalBlocklist: { enumerable: false, value: sharedGlobalBlocklist },
+ sharedGlobalBlacklist: { enumerable: false, value: sharedGlobalBlocklist },
+ // Map of module sandboxes indexed by module URIs.
+ sandboxes: { enumerable: false, value: {} },
+ resolve: { enumerable: false, value: resolve },
+ // ID of the addon, if provided.
+ id: { enumerable: false, value: options.id },
+ // Whether the modules loaded should be ignored by the debugger
+ invisibleToDebugger: { enumerable: false,
+ value: options.invisibleToDebugger || false },
+ load: { enumerable: false, value: options.load || load },
+ checkCompatibility: { enumerable: false, value: checkCompatibility },
+ requireHook: { enumerable: false, value: options.requireHook },
+ loadModuleHook: { enumerable: false, value: options.loadModuleHook },
+ // Main (entry point) module, it can be set only once, since loader
+ // instance can have only one main module.
+ main: new function() {
+ let main;
+ return {
+ enumerable: false,
+ get: function() { return main; },
+ // Only set main if it has not being set yet!
+ set: function(module) { main = main || module; }
+ }
+ }
+ };
+
+ if (isNative) {
+ returnObj.isNative = { enumerable: false, value: true };
+ returnObj.manifest = { enumerable: false, value: manifest };
+ returnObj.requireMap = { enumerable: false, value: requireMap };
+ returnObj.rootURI = { enumerable: false, value: addTrailingSlash(rootURI) };
+ }
+
+ return freeze(Object.create(null, returnObj));
+};
+Loader.Loader = Loader;
+
+var isSystemURI = uri => /^resource:\/\/(gre|devtools|testing-common)\//.test(uri);
+
+var isJSONURI = uri => uri.endsWith('.json');
+var isJSMURI = uri => uri.endsWith('.jsm');
+var isJSURI = uri => uri.endsWith('.js');
+var isAbsoluteURI = uri => uri.startsWith("resource://") ||
+ uri.startsWith("chrome://") ||
+ uri.startsWith("file://");
+var isRelative = id => id.startsWith(".");
+
+// Default `main` entry to './index.js' and ensure is relative,
+// since node allows 'lib/index.js' without relative `./`
+function getManifestMain(manifest) {
+ let main = manifest.main || './index.js';
+ return isRelative(main) ? main : './' + main;
+}
+
+module.exports = iced(Loader);
+});
diff --git a/components/jetpack/toolkit/require.js b/components/jetpack/toolkit/require.js
new file mode 100644
index 000000000..dc3d26ab2
--- /dev/null
+++ b/components/jetpack/toolkit/require.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/. */
+
+const make = (exports, rootURI, components) => {
+ const { Loader: { Loader, Require, Module, main } } =
+ components.utils.import(rootURI + "toolkit/loader.js", {});
+
+ const loader = Loader({
+ id: "toolkit/require",
+ rootURI: rootURI,
+ isNative: true,
+ paths: {
+ "": rootURI,
+ "devtools/": "resource://devtools/"
+ }
+ });
+
+ // Implement require.unload(uri) that can be used to unload
+ // already loaded module which is convinient during development phase.
+ const unload = uri => {
+ delete loader.sandboxes[uri];
+ delete loader.modules[uri];
+ };
+
+ const builtins = new Set(Object.keys(loader.modules));
+
+ // Below we define `require` & `require.resolve` that resolve passed
+ // module id relative to the caller URI. This is not perfect but good
+ // enough for common case & there is always an option to pass absolute
+ // id when that
+ // but presumably well enough to cover
+
+ const require = (id, options={}) => {
+ const { reload, all } = options;
+ const requirerURI = components.stack.caller.filename;
+ const requirer = Module(requirerURI, requirerURI);
+ const require = Require(loader, requirer);
+ if (reload) {
+ // To load JS code into modules, loader uses `mozIJSSubScriptLoader`
+ // which uses startup cache to avoid reading source from the same URI
+ // more than once. Unless we invalidate statup cache changes to a module
+ // won't be reflected even after reload. Therefor we must dispatch an
+ // nsIObserverService notification that causes cache invalidation.
+ // Note: This is not ideal since it destroys whole cache, but since there
+ // is no way to invalidate individual entries, we assume performance hit
+ // during development is acceptable.
+ components.classes["@mozilla.org/observer-service;1"].
+ getService(components.interfaces.nsIObserverService).
+ notifyObservers({}, "startupcache-invalidate", null);
+
+ if (all) {
+ for (let uri of Object.keys(loader.sandboxes)) {
+ unload(uri);
+ }
+ }
+ else {
+ unload(require.resolve(id));
+ }
+ }
+ return require(id);
+ };
+
+ require.resolve = id => {
+ const requirerURI = components.stack.caller.filename;
+ const requirer = Module(requirerURI, requirerURI);
+ return Require(loader, requirer).resolve(id);
+ };
+
+ exports.require = require;
+}
+
+// If loaded in the context of commonjs module, reload as JSM into an
+// exports object.
+if (typeof(require) === "function" && typeof(module) === "object") {
+ require("chrome").Cu.import(module.uri, module.exports);
+}
+// If loaded in the context of JSM make a loader & require and define
+// new symbols as exported ones.
+else if (typeof(__URI__) === "string" && this["Components"]) {
+ const builtin = Object.keys(this);
+ const uri = __URI__.replace("toolkit/require.js", "");
+ make(this, uri, this["Components"]);
+
+ this.EXPORTED_SYMBOLS = Object.
+ keys(this).
+ filter($ => builtin.indexOf($) < 0);
+}
+else {
+ throw Error("Loading require.js in this environment isn't supported")
+}
diff --git a/components/jsdebugger/moz.build b/components/jsdebugger/moz.build
new file mode 100644
index 000000000..6c2cdf11f
--- /dev/null
+++ b/components/jsdebugger/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['public/IJSDebugger.idl']
+
+SOURCES += ['src/JSDebugger.cpp']
+
+EXTRA_JS_MODULES += ['src/jsdebugger.jsm']
+
+XPIDL_MODULE = 'jsdebugger'
+FINAL_LIBRARY = 'xul'
diff --git a/components/jsdebugger/public/IJSDebugger.idl b/components/jsdebugger/public/IJSDebugger.idl
new file mode 100644
index 000000000..dc3cd0423
--- /dev/null
+++ b/components/jsdebugger/public/IJSDebugger.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"
+
+/**
+ * Do not use this interface. Instead, write:
+ * Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+ * addDebuggerToGlobal(global);
+ */
+[scriptable, uuid(a36fa816-31da-4b23-bc97-6412771f0867)]
+interface IJSDebugger : nsISupports
+{
+ /**
+ * Define the global Debugger constructor on a given global.
+ */
+ [implicit_jscontext]
+ void addClass(in jsval global);
+};
diff --git a/components/jsdebugger/src/JSDebugger.cpp b/components/jsdebugger/src/JSDebugger.cpp
new file mode 100644
index 000000000..074008cdc
--- /dev/null
+++ b/components/jsdebugger/src/JSDebugger.cpp
@@ -0,0 +1,86 @@
+/* -*- 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 "JSDebugger.h"
+#include "nsIXPConnect.h"
+#include "nsThreadUtils.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "jswrapper.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+
+#define JSDEBUGGER_CONTRACTID \
+ "@mozilla.org/jsdebugger;1"
+
+#define JSDEBUGGER_CID \
+{ 0x0365cbd5, 0xd46e, 0x4e94, { 0xa3, 0x9f, 0x83, 0xb6, 0x3c, 0xd1, 0xa9, 0x63 } }
+
+namespace mozilla {
+namespace jsdebugger {
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(JSDebugger)
+
+NS_IMPL_ISUPPORTS(JSDebugger, IJSDebugger)
+
+JSDebugger::JSDebugger()
+{
+}
+
+JSDebugger::~JSDebugger()
+{
+}
+
+NS_IMETHODIMP
+JSDebugger::AddClass(JS::Handle<JS::Value> global, JSContext* cx)
+{
+ nsresult rv;
+ nsCOMPtr<nsIXPConnect> xpc = do_GetService(nsIXPConnect::GetCID(), &rv);
+
+ if (!global.isObject()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JS::RootedObject obj(cx, &global.toObject());
+ obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
+ if (!obj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSAutoCompartment ac(cx, obj);
+ if (JS_GetGlobalForObject(cx, obj) != obj) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!JS_DefineDebuggerObject(cx, obj)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace jsdebugger
+} // namespace mozilla
+
+NS_DEFINE_NAMED_CID(JSDEBUGGER_CID);
+
+static const mozilla::Module::CIDEntry kJSDebuggerCIDs[] = {
+ { &kJSDEBUGGER_CID, false, nullptr, mozilla::jsdebugger::JSDebuggerConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kJSDebuggerContracts[] = {
+ { JSDEBUGGER_CONTRACTID, &kJSDEBUGGER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kJSDebuggerModule = {
+ mozilla::Module::kVersion,
+ kJSDebuggerCIDs,
+ kJSDebuggerContracts
+};
+
+NSMODULE_DEFN(jsdebugger) = &kJSDebuggerModule;
diff --git a/components/jsdebugger/src/JSDebugger.h b/components/jsdebugger/src/JSDebugger.h
new file mode 100644
index 000000000..ce5ab81ad
--- /dev/null
+++ b/components/jsdebugger/src/JSDebugger.h
@@ -0,0 +1,30 @@
+/* -*- 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 JSDebugger_h
+#define JSDebugger_h
+
+#include "IJSDebugger.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace jsdebugger {
+
+class JSDebugger final : public IJSDebugger
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_IJSDEBUGGER
+
+ JSDebugger();
+
+private:
+ ~JSDebugger();
+};
+
+} // namespace jsdebugger
+} // namespace mozilla
+
+#endif /* JSDebugger_h */
diff --git a/components/jsdebugger/src/jsdebugger.jsm b/components/jsdebugger/src/jsdebugger.jsm
new file mode 100644
index 000000000..8b87c0a05
--- /dev/null
+++ b/components/jsdebugger/src/jsdebugger.jsm
@@ -0,0 +1,85 @@
+/* -*- 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/. */
+
+this.EXPORTED_SYMBOLS = [ "addDebuggerToGlobal" ];
+
+/*
+ * This is the js module for Debugger. Import it like so:
+ * Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+ * addDebuggerToGlobal(this);
+ *
+ * This will create a 'Debugger' object, which provides an interface to debug
+ * JavaScript code running in other compartments in the same process, on the
+ * same thread.
+ *
+ * For documentation on the API, see:
+ * https://developer.mozilla.org/en-US/docs/Tools/Debugger-API
+ */
+
+const init = Components.classes["@mozilla.org/jsdebugger;1"].createInstance(Components.interfaces.IJSDebugger);
+this.addDebuggerToGlobal = function addDebuggerToGlobal(global) {
+ init.addClass(global);
+ initPromiseDebugging(global);
+};
+
+function initPromiseDebugging(global) {
+ if (global.Debugger.Object.prototype.PromiseDebugging) {
+ return;
+ }
+
+ // If the PromiseDebugging object doesn't have all legacy functions, we're
+ // using the new accessors on Debugger.Object already.
+ if (!PromiseDebugging.getDependentPromises) {
+ return;
+ }
+
+ // Otherwise, polyfill them using PromiseDebugging.
+ global.Debugger.Object.prototype.PromiseDebugging = PromiseDebugging;
+ global.eval(polyfillSource);
+}
+
+let polyfillSource = `
+ Object.defineProperty(Debugger.Object.prototype, "promiseState", {
+ get() {
+ const state = this.PromiseDebugging.getState(this.unsafeDereference());
+ return {
+ state: state.state,
+ value: this.makeDebuggeeValue(state.value),
+ reason: this.makeDebuggeeValue(state.reason)
+ };
+ }
+ });
+ Object.defineProperty(Debugger.Object.prototype, "promiseLifetime", {
+ get() {
+ return this.PromiseDebugging.getPromiseLifetime(this.unsafeDereference());
+ }
+ });
+ Object.defineProperty(Debugger.Object.prototype, "promiseTimeToResolution", {
+ get() {
+ return this.PromiseDebugging.getTimeToSettle(this.unsafeDereference());
+ }
+ });
+ Object.defineProperty(Debugger.Object.prototype, "promiseDependentPromises", {
+ get() {
+ let promises = this.PromiseDebugging.getDependentPromises(this.unsafeDereference());
+ return promises.map(p => this.makeDebuggeeValue(p));
+ }
+ });
+ Object.defineProperty(Debugger.Object.prototype, "promiseAllocationSite", {
+ get() {
+ return this.PromiseDebugging.getAllocationStack(this.unsafeDereference());
+ }
+ });
+ Object.defineProperty(Debugger.Object.prototype, "promiseResolutionSite", {
+ get() {
+ let state = this.promiseState.state;
+ if (state === "fulfilled") {
+ return this.PromiseDebugging.getFullfillmentStack(this.unsafeDereference());
+ } else {
+ return this.PromiseDebugging.getRejectionStack(this.unsafeDereference());
+ }
+ }
+ });
+`;
diff --git a/components/jsdownloads/moz.build b/components/jsdownloads/moz.build
new file mode 100644
index 000000000..82a240e0b
--- /dev/null
+++ b/components/jsdownloads/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+DIRS += ['public', 'src']
diff --git a/components/jsdownloads/public/moz.build b/components/jsdownloads/public/moz.build
new file mode 100644
index 000000000..6ea66bf5f
--- /dev/null
+++ b/components/jsdownloads/public/moz.build
@@ -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/.
+
+XPIDL_MODULE = 'jsdownloads'
+
+XPIDL_SOURCES += [
+ 'mozIDownloadPlatform.idl',
+]
diff --git a/components/jsdownloads/public/mozIDownloadPlatform.idl b/components/jsdownloads/public/mozIDownloadPlatform.idl
new file mode 100644
index 000000000..ca7b8a165
--- /dev/null
+++ b/components/jsdownloads/public/mozIDownloadPlatform.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 nsIURI;
+interface nsIFile;
+
+[scriptable, uuid(9f556e4a-d9b3-46c3-9f8f-d0db1ac6c8c1)]
+interface mozIDownloadPlatform : nsISupports
+{
+ /**
+ * Perform platform specific operations when a download is done.
+ *
+ * Windows:
+ * Add the download to the recent documents list
+ * Set the file to be indexed for searching
+ * GTK:
+ * Add the download to the recent documents list
+ * Save the source uri in the downloaded file's metadata
+ *
+ * @param aSource
+ * Source URI of the download
+ * @param aReferrer
+ * Referrer URI of the download
+ * @param aTarget
+ * Downloaded file
+ * @param aContentType
+ * The source's content type
+ * @param aIsPrivate
+ * True for private downloads
+ * @return none
+ */
+ void downloadDone(in nsIURI aSource, in nsIURI aReferrer, in nsIFile aTarget,
+ in ACString aContentType, in boolean aIsPrivate);
+
+ /**
+ * Security Zone constants. Used by mapUrlToZone().
+ */
+ const unsigned long ZONE_MY_COMPUTER = 0;
+ const unsigned long ZONE_INTRANET = 1;
+ const unsigned long ZONE_TRUSTED = 2;
+ const unsigned long ZONE_INTERNET = 3;
+ const unsigned long ZONE_RESTRICTED = 4;
+
+ /**
+ * Proxy for IInternetSecurityManager::MapUrlToZone().
+ *
+ * Windows only.
+ *
+ * @param aURL
+ * URI of the download
+ * @return Security Zone corresponding to aURL.
+ */
+ unsigned long mapUrlToZone(in AString aURL);
+};
diff --git a/components/jsdownloads/src/DownloadCore.jsm b/components/jsdownloads/src/DownloadCore.jsm
new file mode 100644
index 000000000..007d705f7
--- /dev/null
+++ b/components/jsdownloads/src/DownloadCore.jsm
@@ -0,0 +1,2799 @@
+/* -*- 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/. */
+
+/**
+ * This file includes the following constructors and global objects:
+ *
+ * Download
+ * Represents a single download, with associated state and actions. This object
+ * is transient, though it can be included in a DownloadList so that it can be
+ * managed by the user interface and persisted across sessions.
+ *
+ * DownloadSource
+ * Represents the source of a download, for example a document or an URI.
+ *
+ * DownloadTarget
+ * Represents the target of a download, for example a file in the global
+ * downloads directory, or a file in the system temporary directory.
+ *
+ * DownloadError
+ * Provides detailed information about a download failure.
+ *
+ * DownloadSaver
+ * Template for an object that actually transfers the data for the download.
+ *
+ * DownloadCopySaver
+ * Saver object that simply copies the entire source file to the target.
+ *
+ * DownloadLegacySaver
+ * Saver object that integrates with the legacy nsITransfer interface.
+ *
+ * DownloadPDFSaver
+ * This DownloadSaver type creates a PDF file from the current document in a
+ * given window, specified using the windowRef property of the DownloadSource
+ * object associated with the download.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "Download",
+ "DownloadSource",
+ "DownloadTarget",
+ "DownloadError",
+ "DownloadSaver",
+ "DownloadCopySaver",
+ "DownloadLegacySaver",
+ "DownloadPDFSaver",
+];
+
+// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Integration.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.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, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDownloadHistory",
+ "@mozilla.org/browser/download-history;1",
+ Ci.nsIDownloadHistory);
+XPCOMUtils.defineLazyServiceGetter(this, "gExternalAppLauncher",
+ "@mozilla.org/uriloader/external-helper-app-service;1",
+ Ci.nsPIExternalAppLauncher);
+XPCOMUtils.defineLazyServiceGetter(this, "gExternalHelperAppService",
+ "@mozilla.org/uriloader/external-helper-app-service;1",
+ Ci.nsIExternalHelperAppService);
+XPCOMUtils.defineLazyServiceGetter(this, "gPrintSettingsService",
+ "@mozilla.org/gfx/printsettings-service;1",
+ Ci.nsIPrintSettingsService);
+
+Integration.downloads.defineModuleGetter(this, "DownloadIntegration",
+ "resource://gre/modules/DownloadIntegration.jsm");
+
+const BackgroundFileSaverStreamListener = Components.Constructor(
+ "@mozilla.org/network/background-file-saver;1?mode=streamlistener",
+ "nsIBackgroundFileSaver");
+
+/**
+ * Returns true if the given value is a primitive string or a String object.
+ */
+function isString(aValue) {
+ // We cannot use the "instanceof" operator reliably across module boundaries.
+ return (typeof aValue == "string") ||
+ (typeof aValue == "object" && "charAt" in aValue);
+}
+
+/**
+ * Serialize the unknown properties of aObject into aSerializable.
+ */
+function serializeUnknownProperties(aObject, aSerializable)
+{
+ if (aObject._unknownProperties) {
+ for (let property in aObject._unknownProperties) {
+ aSerializable[property] = aObject._unknownProperties[property];
+ }
+ }
+}
+
+/**
+ * Check for any unknown properties in aSerializable and preserve those in the
+ * _unknownProperties field of aObject. aFilterFn is called for each property
+ * name of aObject and should return true only for unknown properties.
+ */
+function deserializeUnknownProperties(aObject, aSerializable, aFilterFn)
+{
+ for (let property in aSerializable) {
+ if (aFilterFn(property)) {
+ if (!aObject._unknownProperties) {
+ aObject._unknownProperties = { };
+ }
+
+ aObject._unknownProperties[property] = aSerializable[property];
+ }
+ }
+}
+
+/**
+ * This determines the minimum time interval between updates to the number of
+ * bytes transferred, and is a limiting factor to the sequence of readings used
+ * in calculating the speed of the download.
+ */
+const kProgressUpdateIntervalMs = 400;
+
+// Download
+
+/**
+ * Represents a single download, with associated state and actions. This object
+ * is transient, though it can be included in a DownloadList so that it can be
+ * managed by the user interface and persisted across sessions.
+ */
+this.Download = function ()
+{
+ this._deferSucceeded = Promise.defer();
+}
+
+this.Download.prototype = {
+ /**
+ * DownloadSource object associated with this download.
+ */
+ source: null,
+
+ /**
+ * DownloadTarget object associated with this download.
+ */
+ target: null,
+
+ /**
+ * DownloadSaver object associated with this download.
+ */
+ saver: null,
+
+ /**
+ * Indicates that the download never started, has been completed successfully,
+ * failed, or has been canceled. This property becomes false when a download
+ * is started for the first time, or when a failed or canceled download is
+ * restarted.
+ */
+ stopped: true,
+
+ /**
+ * Indicates that the download has been completed successfully.
+ */
+ succeeded: false,
+
+ /**
+ * Indicates that the download has been canceled. This property can become
+ * true, then it can be reset to false when a canceled download is restarted.
+ *
+ * This property becomes true as soon as the "cancel" method is called, though
+ * the "stopped" property might remain false until the cancellation request
+ * has been processed. Temporary files or part files may still exist even if
+ * they are expected to be deleted, until the "stopped" property becomes true.
+ */
+ canceled: false,
+
+ /**
+ * When the download fails, this is set to a DownloadError instance indicating
+ * the cause of the failure. If the download has been completed successfully
+ * or has been canceled, this property is null. This property is reset to
+ * null when a failed download is restarted.
+ */
+ error: null,
+
+ /**
+ * Indicates the start time of the download. When the download starts,
+ * this property is set to a valid Date object. The default value is null
+ * before the download starts.
+ */
+ startTime: null,
+
+ /**
+ * Indicates whether this download's "progress" property is able to report
+ * partial progress while the download proceeds, and whether the value in
+ * totalBytes is relevant. This depends on the saver and the download source.
+ */
+ hasProgress: false,
+
+ /**
+ * Progress percent, from 0 to 100. Intermediate values are reported only if
+ * hasProgress is true.
+ *
+ * @note You shouldn't rely on this property being equal to 100 to determine
+ * whether the download is completed. You should use the individual
+ * state properties instead.
+ */
+ progress: 0,
+
+ /**
+ * When hasProgress is true, indicates the total number of bytes to be
+ * transferred before the download finishes, that can be zero for empty files.
+ *
+ * When hasProgress is false, this property is always zero.
+ *
+ * @note This property may be different than the final file size on disk for
+ * downloads that are encoded during the network transfer. You can use
+ * the "size" property of the DownloadTarget object to get the actual
+ * size on disk once the download succeeds.
+ */
+ totalBytes: 0,
+
+ /**
+ * Number of bytes currently transferred. This value starts at zero, and may
+ * be updated regardless of the value of hasProgress.
+ *
+ * @note You shouldn't rely on this property being equal to totalBytes to
+ * determine whether the download is completed. You should use the
+ * individual state properties instead. This property may not be
+ * updated during the last part of the download.
+ */
+ currentBytes: 0,
+
+ /**
+ * Fractional number representing the speed of the download, in bytes per
+ * second. This value is zero when the download is stopped, and may be
+ * updated regardless of the value of hasProgress.
+ */
+ speed: 0,
+
+ /**
+ * Indicates whether, at this time, there is any partially downloaded data
+ * that can be used when restarting a failed or canceled download.
+ *
+ * Even if the download has partial data on disk, hasPartialData will be false
+ * if that data cannot be used to restart the download. In order to determine
+ * if a part file is being used which contains partial data the
+ * Download.target.partFilePath should be checked.
+ *
+ * This property is relevant while the download is in progress, and also if it
+ * failed or has been canceled. If the download has been completed
+ * successfully, this property is always false.
+ *
+ * Whether partial data can actually be retained depends on the saver and the
+ * download source, and may not be known before the download is started.
+ */
+ hasPartialData: false,
+
+ /**
+ * Indicates whether, at this time, there is any data that has been blocked.
+ * Since reputation blocking takes place after the download has fully
+ * completed a value of true also indicates 100% of the data is present.
+ */
+ hasBlockedData: false,
+
+ /**
+ * This can be set to a function that is called after other properties change.
+ */
+ onchange: null,
+
+ /**
+ * This tells if the user has chosen to open/run the downloaded file after
+ * download has completed.
+ */
+ launchWhenSucceeded: false,
+
+ /**
+ * This represents the MIME type of the download.
+ */
+ contentType: null,
+
+ /**
+ * This indicates the path of the application to be used to launch the file,
+ * or null if the file should be launched with the default application.
+ */
+ launcherPath: null,
+
+ /**
+ * Raises the onchange notification.
+ */
+ _notifyChange: function D_notifyChange() {
+ try {
+ if (this.onchange) {
+ this.onchange();
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ },
+
+ /**
+ * The download may be stopped and restarted multiple times before it
+ * completes successfully. This may happen if any of the download attempts is
+ * canceled or fails.
+ *
+ * This property contains a promise that is linked to the current attempt, or
+ * null if the download is either stopped or in the process of being canceled.
+ * If the download restarts, this property is replaced with a new promise.
+ *
+ * The promise is resolved if the attempt it represents finishes successfully,
+ * and rejected if the attempt fails.
+ */
+ _currentAttempt: null,
+
+ /**
+ * Starts the download for the first time, or restarts a download that failed
+ * or has been canceled.
+ *
+ * Calling this method when the download has been completed successfully has
+ * no effect, and the method returns a resolved promise. If the download is
+ * in progress, the method returns the same promise as the previous call.
+ *
+ * If the "cancel" method was called but the cancellation process has not
+ * finished yet, this method waits for the cancellation to finish, then
+ * restarts the download immediately.
+ *
+ * @note If you need to start a new download from the same source, rather than
+ * restarting a failed or canceled one, you should create a separate
+ * Download object with the same source as the current one.
+ *
+ * @return {Promise}
+ * @resolves When the download has finished successfully.
+ * @rejects JavaScript exception if the download failed.
+ */
+ start: function D_start()
+ {
+ // If the download succeeded, it's the final state, we have nothing to do.
+ if (this.succeeded) {
+ return Promise.resolve();
+ }
+
+ // If the download already started and hasn't failed or hasn't been
+ // canceled, return the same promise as the previous call, allowing the
+ // caller to wait for the current attempt to finish.
+ if (this._currentAttempt) {
+ return this._currentAttempt;
+ }
+
+ // While shutting down or disposing of this object, we prevent the download
+ // from returning to be in progress.
+ if (this._finalized) {
+ return Promise.reject(new DownloadError({
+ message: "Cannot start after finalization."}));
+ }
+
+ // Initialize all the status properties for a new or restarted download.
+ this.stopped = false;
+ this.canceled = false;
+ this.error = null;
+ this.hasProgress = false;
+ this.hasBlockedData = false;
+ this.progress = 0;
+ this.totalBytes = 0;
+ this.currentBytes = 0;
+ this.startTime = new Date();
+
+ // Create a new deferred object and an associated promise before starting
+ // the actual download. We store it on the download as the current attempt.
+ let deferAttempt = Promise.defer();
+ let currentAttempt = deferAttempt.promise;
+ this._currentAttempt = currentAttempt;
+
+ // Restart the progress and speed calculations from scratch.
+ this._lastProgressTimeMs = 0;
+
+ // This function propagates progress from the DownloadSaver object, unless
+ // it comes in late from a download attempt that was replaced by a new one.
+ // If the cancellation process for the download has started, then the update
+ // is ignored.
+ function DS_setProgressBytes(aCurrentBytes, aTotalBytes, aHasPartialData)
+ {
+ if (this._currentAttempt == currentAttempt) {
+ this._setBytes(aCurrentBytes, aTotalBytes, aHasPartialData);
+ }
+ }
+
+ // This function propagates download properties from the DownloadSaver
+ // object, unless it comes in late from a download attempt that was
+ // replaced by a new one. If the cancellation process for the download has
+ // started, then the update is ignored.
+ function DS_setProperties(aOptions)
+ {
+ if (this._currentAttempt != currentAttempt) {
+ return;
+ }
+
+ let changeMade = false;
+
+ for (let property of ["contentType", "progress", "hasPartialData",
+ "hasBlockedData"]) {
+ if (property in aOptions && this[property] != aOptions[property]) {
+ this[property] = aOptions[property];
+ changeMade = true;
+ }
+ }
+
+ if (changeMade) {
+ this._notifyChange();
+ }
+ }
+
+ // Now that we stored the promise in the download object, we can start the
+ // task that will actually execute the download.
+ deferAttempt.resolve(Task.spawn(function* task_D_start() {
+ // Wait upon any pending operation before restarting.
+ if (this._promiseCanceled) {
+ yield this._promiseCanceled;
+ }
+ if (this._promiseRemovePartialData) {
+ try {
+ yield this._promiseRemovePartialData;
+ } catch (ex) {
+ // Ignore any errors, which are already reported by the original
+ // caller of the removePartialData method.
+ }
+ }
+
+ // In case the download was restarted while cancellation was in progress,
+ // but the previous attempt actually succeeded before cancellation could
+ // be processed, it is possible that the download has already finished.
+ if (this.succeeded) {
+ return;
+ }
+
+ try {
+ // Disallow download if parental controls service restricts it.
+ if (yield DownloadIntegration.shouldBlockForParentalControls(this)) {
+ throw new DownloadError({ becauseBlockedByParentalControls: true });
+ }
+
+ // Disallow download if needed runtime permissions have not been granted
+ // by user.
+ if (yield DownloadIntegration.shouldBlockForRuntimePermissions()) {
+ throw new DownloadError({ becauseBlockedByRuntimePermissions: true });
+ }
+
+ // We should check if we have been canceled in the meantime, after all
+ // the previous asynchronous operations have been executed and just
+ // before we call the "execute" method of the saver.
+ if (this._promiseCanceled) {
+ // The exception will become a cancellation in the "catch" block.
+ throw undefined;
+ }
+
+ // Execute the actual download through the saver object.
+ this._saverExecuting = true;
+ yield this.saver.execute(DS_setProgressBytes.bind(this),
+ DS_setProperties.bind(this));
+
+ // Now that the actual saving finished, read the actual file size on
+ // disk, that may be different from the amount of data transferred.
+ yield this.target.refresh();
+
+ // Check for the last time if the download has been canceled. This must
+ // be done right before setting the "stopped" property of the download,
+ // without any asynchronous operations in the middle, so that another
+ // cancellation request cannot start in the meantime and stay unhandled.
+ if (this._promiseCanceled) {
+ try {
+ yield OS.File.remove(this.target.path);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ this.target.exists = false;
+ this.target.size = 0;
+
+ // Cancellation exceptions will be changed in the catch block below.
+ throw new DownloadError();
+ }
+
+ // Update the status properties for a successful download.
+ this.progress = 100;
+ this.succeeded = true;
+ this.hasPartialData = false;
+ } catch (originalEx) {
+ // We may choose a different exception to propagate in the code below,
+ // or wrap the original one. We do this mutation in a different variable
+ // because of the "no-ex-assign" ESLint rule.
+ let ex = originalEx;
+
+ // Fail with a generic status code on cancellation, so that the caller
+ // is forced to actually check the status properties to see if the
+ // download was canceled or failed because of other reasons.
+ if (this._promiseCanceled) {
+ throw new DownloadError({ message: "Download canceled." });
+ }
+
+ // An HTTP 450 error code is used by Windows to indicate that a uri is
+ // blocked by parental controls. This will prevent the download from
+ // occuring, so an error needs to be raised. This is not performed
+ // during the parental controls check above as it requires the request
+ // to start.
+ if (this._blockedByParentalControls) {
+ ex = new DownloadError({ becauseBlockedByParentalControls: true });
+ }
+
+ // Update the download error, unless a new attempt already started. The
+ // change in the status property is notified in the finally block.
+ if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
+ if (!(ex instanceof DownloadError)) {
+ let properties = {innerException: ex};
+
+ if (ex.message) {
+ properties.message = ex.message;
+ }
+
+ ex = new DownloadError(properties);
+ }
+
+ this.error = ex;
+ }
+ throw ex;
+ } finally {
+ // Any cancellation request has now been processed.
+ this._saverExecuting = false;
+ this._promiseCanceled = null;
+
+ // Update the status properties, unless a new attempt already started.
+ if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
+ this._currentAttempt = null;
+ this.stopped = true;
+ this.speed = 0;
+ this._notifyChange();
+ if (this.succeeded) {
+ yield this._succeed();
+ }
+ }
+ }
+ }.bind(this)));
+
+ // Notify the new download state before returning.
+ this._notifyChange();
+ return currentAttempt;
+ },
+
+ /**
+ * Perform the actions necessary when a Download succeeds.
+ *
+ * @return {Promise}
+ * @resolves When the steps to take after success have completed.
+ * @rejects JavaScript exception if any of the operations failed.
+ */
+ _succeed: Task.async(function* () {
+ yield DownloadIntegration.downloadDone(this);
+
+ this._deferSucceeded.resolve();
+
+ if (this.launchWhenSucceeded) {
+ this.launch().then(null, Cu.reportError);
+
+ // Always schedule files to be deleted at the end of the private browsing
+ // mode, regardless of the value of the pref.
+ if (this.source.isPrivate) {
+ gExternalAppLauncher.deleteTemporaryPrivateFileWhenPossible(
+ new FileUtils.File(this.target.path));
+ } else if (Services.prefs.getBoolPref(
+ "browser.helperApps.deleteTempFileOnExit")) {
+ gExternalAppLauncher.deleteTemporaryFileOnExit(
+ new FileUtils.File(this.target.path));
+ }
+ }
+ }),
+
+ /**
+ * When a request to unblock the download is received, contains a promise
+ * that will be resolved when the unblock request is completed. This property
+ * will then continue to hold the promise indefinitely.
+ */
+ _promiseUnblock: null,
+
+ /**
+ * When a request to confirm the block of the download is received, contains
+ * a promise that will be resolved when cleaning up the download has
+ * completed. This property will then continue to hold the promise
+ * indefinitely.
+ */
+ _promiseConfirmBlock: null,
+
+ /**
+ * Unblocks a download which had been blocked by reputation.
+ *
+ * The file will be moved out of quarantine and the download will be
+ * marked as succeeded.
+ *
+ * @return {Promise}
+ * @resolves When the Download has been unblocked and succeeded.
+ * @rejects JavaScript exception if any of the operations failed.
+ */
+ unblock: function() {
+ if (this._promiseUnblock) {
+ return this._promiseUnblock;
+ }
+
+ if (this._promiseConfirmBlock) {
+ return Promise.reject(new Error(
+ "Download block has been confirmed, cannot unblock."));
+ }
+
+ if (!this.hasBlockedData) {
+ return Promise.reject(new Error(
+ "unblock may only be called on Downloads with blocked data."));
+ }
+
+ this._promiseUnblock = Task.spawn(function* () {
+ try {
+ yield OS.File.move(this.target.partFilePath, this.target.path);
+ yield this.target.refresh();
+ } catch (ex) {
+ yield this.refresh();
+ this._promiseUnblock = null;
+ throw ex;
+ }
+
+ this.succeeded = true;
+ this.hasBlockedData = false;
+ this._notifyChange();
+ yield this._succeed();
+ }.bind(this));
+
+ return this._promiseUnblock;
+ },
+
+ /**
+ * Confirms that a blocked download should be cleaned up.
+ *
+ * If a download was blocked but retained on disk this method can be used
+ * to remove the file.
+ *
+ * @return {Promise}
+ * @resolves When the Download's data has been removed.
+ * @rejects JavaScript exception if any of the operations failed.
+ */
+ confirmBlock: function() {
+ if (this._promiseConfirmBlock) {
+ return this._promiseConfirmBlock;
+ }
+
+ if (this._promiseUnblock) {
+ return Promise.reject(new Error(
+ "Download is being unblocked, cannot confirmBlock."));
+ }
+
+ if (!this.hasBlockedData) {
+ return Promise.reject(new Error(
+ "confirmBlock may only be called on Downloads with blocked data."));
+ }
+
+ this._promiseConfirmBlock = Task.spawn(function* () {
+ try {
+ yield OS.File.remove(this.target.partFilePath);
+ } catch (ex) {
+ yield this.refresh();
+ this._promiseConfirmBlock = null;
+ throw ex;
+ }
+
+ this.hasBlockedData = false;
+ this._notifyChange();
+ }.bind(this));
+
+ return this._promiseConfirmBlock;
+ },
+
+ /*
+ * Launches the file after download has completed. This can open
+ * the file with the default application for the target MIME type
+ * or file extension, or with a custom application if launcherPath
+ * is set.
+ *
+ * @return {Promise}
+ * @resolves When the instruction to launch the file has been
+ * successfully given to the operating system. Note that
+ * the OS might still take a while until the file is actually
+ * launched.
+ * @rejects JavaScript exception if there was an error trying to launch
+ * the file.
+ */
+ launch: function () {
+ if (!this.succeeded) {
+ return Promise.reject(
+ new Error("launch can only be called if the download succeeded")
+ );
+ }
+
+ return DownloadIntegration.launchDownload(this);
+ },
+
+ /*
+ * Shows the folder containing the target file, or where the target file
+ * will be saved. This may be called at any time, even if the download
+ * failed or is currently in progress.
+ *
+ * @return {Promise}
+ * @resolves When the instruction to open the containing folder has been
+ * successfully given to the operating system. Note that
+ * the OS might still take a while until the folder is actually
+ * opened.
+ * @rejects JavaScript exception if there was an error trying to open
+ * the containing folder.
+ */
+ showContainingDirectory: function D_showContainingDirectory() {
+ return DownloadIntegration.showContainingDirectory(this.target.path);
+ },
+
+ /**
+ * When a request to cancel the download is received, contains a promise that
+ * will be resolved when the cancellation request is processed. When the
+ * request is processed, this property becomes null again.
+ */
+ _promiseCanceled: null,
+
+ /**
+ * True between the call to the "execute" method of the saver and the
+ * completion of the current download attempt.
+ */
+ _saverExecuting: false,
+
+ /**
+ * Cancels the download.
+ *
+ * The cancellation request is asynchronous. Until the cancellation process
+ * finishes, temporary files or part files may still exist even if they are
+ * expected to be deleted.
+ *
+ * In case the download completes successfully before the cancellation request
+ * could be processed, this method has no effect, and it returns a resolved
+ * promise. You should check the properties of the download at the time the
+ * returned promise is resolved to determine if the download was cancelled.
+ *
+ * Calling this method when the download has been completed successfully,
+ * failed, or has been canceled has no effect, and the method returns a
+ * resolved promise. This behavior is designed for the case where the call
+ * to "cancel" happens asynchronously, and is consistent with the case where
+ * the cancellation request could not be processed in time.
+ *
+ * @return {Promise}
+ * @resolves When the cancellation process has finished.
+ * @rejects Never.
+ */
+ cancel: function D_cancel()
+ {
+ // If the download is currently stopped, we have nothing to do.
+ if (this.stopped) {
+ return Promise.resolve();
+ }
+
+ if (!this._promiseCanceled) {
+ // Start a new cancellation request.
+ let deferCanceled = Promise.defer();
+ this._currentAttempt.then(() => deferCanceled.resolve(),
+ () => deferCanceled.resolve());
+ this._promiseCanceled = deferCanceled.promise;
+
+ // The download can already be restarted.
+ this._currentAttempt = null;
+
+ // Notify that the cancellation request was received.
+ this.canceled = true;
+ this._notifyChange();
+
+ // Execute the actual cancellation through the saver object, in case it
+ // has already started. Otherwise, the cancellation will be handled just
+ // before the saver is started.
+ if (this._saverExecuting) {
+ this.saver.cancel();
+ }
+ }
+
+ return this._promiseCanceled;
+ },
+
+ /**
+ * Indicates whether any partially downloaded data should be retained, to use
+ * when restarting a failed or canceled download. The default is false.
+ *
+ * Whether partial data can actually be retained depends on the saver and the
+ * download source, and may not be known before the download is started.
+ *
+ * To have any effect, this property must be set before starting the download.
+ * Resetting this property to false after the download has already started
+ * will not remove any partial data.
+ *
+ * If this property is set to true, care should be taken that partial data is
+ * removed before the reference to the download is discarded. This can be
+ * done using the removePartialData or the "finalize" methods.
+ */
+ tryToKeepPartialData: false,
+
+ /**
+ * When a request to remove partially downloaded data is received, contains a
+ * promise that will be resolved when the removal request is processed. When
+ * the request is processed, this property becomes null again.
+ */
+ _promiseRemovePartialData: null,
+
+ /**
+ * Removes any partial data kept as part of a canceled or failed download.
+ *
+ * If the download is not canceled or failed, this method has no effect, and
+ * it returns a resolved promise. If the "cancel" method was called but the
+ * cancellation process has not finished yet, this method waits for the
+ * cancellation to finish, then removes the partial data.
+ *
+ * After this method has been called, if the tryToKeepPartialData property is
+ * still true when the download is restarted, partial data will be retained
+ * during the new download attempt.
+ *
+ * @return {Promise}
+ * @resolves When the partial data has been successfully removed.
+ * @rejects JavaScript exception if the operation could not be completed.
+ */
+ removePartialData: function ()
+ {
+ if (!this.canceled && !this.error) {
+ return Promise.resolve();
+ }
+
+ let promiseRemovePartialData = this._promiseRemovePartialData;
+
+ if (!promiseRemovePartialData) {
+ let deferRemovePartialData = Promise.defer();
+ promiseRemovePartialData = deferRemovePartialData.promise;
+ this._promiseRemovePartialData = promiseRemovePartialData;
+
+ deferRemovePartialData.resolve(
+ Task.spawn(function* task_D_removePartialData() {
+ try {
+ // Wait upon any pending cancellation request.
+ if (this._promiseCanceled) {
+ yield this._promiseCanceled;
+ }
+ // Ask the saver object to remove any partial data.
+ yield this.saver.removePartialData();
+ // For completeness, clear the number of bytes transferred.
+ if (this.currentBytes != 0 || this.hasPartialData) {
+ this.currentBytes = 0;
+ this.hasPartialData = false;
+ this._notifyChange();
+ }
+ } finally {
+ this._promiseRemovePartialData = null;
+ }
+ }.bind(this)));
+ }
+
+ return promiseRemovePartialData;
+ },
+
+ /**
+ * This deferred object contains a promise that is resolved as soon as this
+ * download finishes successfully, and is never rejected. This property is
+ * initialized when the download is created, and never changes.
+ */
+ _deferSucceeded: null,
+
+ /**
+ * Returns a promise that is resolved as soon as this download finishes
+ * successfully, even if the download was stopped and restarted meanwhile.
+ *
+ * You can use this property for scheduling download completion actions in the
+ * current session, for downloads that are controlled interactively. If the
+ * download is not controlled interactively, you should use the promise
+ * returned by the "start" method instead, to check for success or failure.
+ *
+ * @return {Promise}
+ * @resolves When the download has finished successfully.
+ * @rejects Never.
+ */
+ whenSucceeded: function D_whenSucceeded()
+ {
+ return this._deferSucceeded.promise;
+ },
+
+ /**
+ * Updates the state of a finished, failed, or canceled download based on the
+ * current state in the file system. If the download is in progress or it has
+ * been finalized, this method has no effect, and it returns a resolved
+ * promise.
+ *
+ * This allows the properties of the download to be updated in case the user
+ * moved or deleted the target file or its associated ".part" file.
+ *
+ * @return {Promise}
+ * @resolves When the operation has completed.
+ * @rejects Never.
+ */
+ refresh: function ()
+ {
+ return Task.spawn(function* () {
+ if (!this.stopped || this._finalized) {
+ return;
+ }
+
+ if (this.succeeded) {
+ let oldExists = this.target.exists;
+ let oldSize = this.target.size;
+ yield this.target.refresh();
+ if (oldExists != this.target.exists || oldSize != this.target.size) {
+ this._notifyChange();
+ }
+ return;
+ }
+
+ // Update the current progress from disk if we retained partial data.
+ if ((this.hasPartialData || this.hasBlockedData) &&
+ this.target.partFilePath) {
+
+ try {
+ let stat = yield OS.File.stat(this.target.partFilePath);
+
+ // Ignore the result if the state has changed meanwhile.
+ if (!this.stopped || this._finalized) {
+ return;
+ }
+
+ // Update the bytes transferred and the related progress properties.
+ this.currentBytes = stat.size;
+ if (this.totalBytes > 0) {
+ this.hasProgress = true;
+ this.progress = Math.floor(this.currentBytes /
+ this.totalBytes * 100);
+ }
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+ throw ex;
+ }
+ // Ignore the result if the state has changed meanwhile.
+ if (!this.stopped || this._finalized) {
+ return;
+ }
+
+ this.hasBlockedData = false;
+ this.hasPartialData = false;
+ }
+
+ this._notifyChange();
+ }
+ }.bind(this)).then(null, Cu.reportError);
+ },
+
+ /**
+ * True if the "finalize" method has been called. This prevents the download
+ * from starting again after having been stopped.
+ */
+ _finalized: false,
+
+ /**
+ * Ensures that the download is stopped, and optionally removes any partial
+ * data kept as part of a canceled or failed download. After this method has
+ * been called, the download cannot be started again.
+ *
+ * This method should be used in place of "cancel" and removePartialData while
+ * shutting down or disposing of the download object, to prevent other callers
+ * from interfering with the operation. This is required because cancellation
+ * and other operations are asynchronous.
+ *
+ * @param aRemovePartialData
+ * Whether any partially downloaded data should be removed after the
+ * download has been stopped.
+ *
+ * @return {Promise}
+ * @resolves When the operation has finished successfully.
+ * @rejects JavaScript exception if an error occurred while removing the
+ * partially downloaded data.
+ */
+ finalize: function (aRemovePartialData)
+ {
+ // Prevents the download from starting again after having been stopped.
+ this._finalized = true;
+
+ if (aRemovePartialData) {
+ // Cancel the download, in case it is currently in progress, then remove
+ // any partially downloaded data. The removal operation waits for
+ // cancellation to be completed before resolving the promise it returns.
+ this.cancel();
+ return this.removePartialData();
+ }
+ // Just cancel the download, in case it is currently in progress.
+ return this.cancel();
+ },
+
+ /**
+ * Indicates the time of the last progress notification, expressed as the
+ * number of milliseconds since January 1, 1970, 00:00:00 UTC. This is zero
+ * until some bytes have actually been transferred.
+ */
+ _lastProgressTimeMs: 0,
+
+ /**
+ * Updates progress notifications based on the number of bytes transferred.
+ *
+ * The number of bytes transferred is not updated unless enough time passed
+ * since this function was last called. This limits the computation load, in
+ * particular when the listeners update the user interface in response.
+ *
+ * @param aCurrentBytes
+ * Number of bytes transferred until now.
+ * @param aTotalBytes
+ * Total number of bytes to be transferred, or -1 if unknown.
+ * @param aHasPartialData
+ * Indicates whether the partially downloaded data can be used when
+ * restarting the download if it fails or is canceled.
+ */
+ _setBytes: function D_setBytes(aCurrentBytes, aTotalBytes, aHasPartialData) {
+ let changeMade = (this.hasPartialData != aHasPartialData);
+ this.hasPartialData = aHasPartialData;
+
+ // Unless aTotalBytes is -1, we can report partial download progress. In
+ // this case, notify when the related properties changed since last time.
+ if (aTotalBytes != -1 && (!this.hasProgress ||
+ this.totalBytes != aTotalBytes)) {
+ this.hasProgress = true;
+ this.totalBytes = aTotalBytes;
+ changeMade = true;
+ }
+
+ // Updating the progress and computing the speed require that enough time
+ // passed since the last update, or that we haven't started throttling yet.
+ let currentTimeMs = Date.now();
+ let intervalMs = currentTimeMs - this._lastProgressTimeMs;
+ if (intervalMs >= kProgressUpdateIntervalMs) {
+ // Don't compute the speed unless we started throttling notifications.
+ if (this._lastProgressTimeMs != 0) {
+ // Calculate the speed in bytes per second.
+ let rawSpeed = (aCurrentBytes - this.currentBytes) / intervalMs * 1000;
+ if (this.speed == 0) {
+ // When the previous speed is exactly zero instead of a fractional
+ // number, this can be considered the first element of the series.
+ this.speed = rawSpeed;
+ } else {
+ // Apply exponential smoothing, with a smoothing factor of 0.1.
+ this.speed = rawSpeed * 0.1 + this.speed * 0.9;
+ }
+ }
+
+ // Start throttling notifications only when we have actually received some
+ // bytes for the first time. The timing of the first part of the download
+ // is not reliable, due to possible latency in the initial notifications.
+ // This also allows automated tests to receive and verify the number of
+ // bytes initially transferred.
+ if (aCurrentBytes > 0) {
+ this._lastProgressTimeMs = currentTimeMs;
+
+ // Update the progress now that we don't need its previous value.
+ this.currentBytes = aCurrentBytes;
+ if (this.totalBytes > 0) {
+ this.progress = Math.floor(this.currentBytes / this.totalBytes * 100);
+ }
+ changeMade = true;
+ }
+ }
+
+ if (changeMade) {
+ this._notifyChange();
+ }
+ },
+
+ /**
+ * Returns a static representation of the current object state.
+ *
+ * @return A JavaScript object that can be serialized to JSON.
+ */
+ toSerializable: function ()
+ {
+ let serializable = {
+ source: this.source.toSerializable(),
+ target: this.target.toSerializable(),
+ };
+
+ let saver = this.saver.toSerializable();
+ if (!serializable.source || !saver) {
+ // If we are unable to serialize either the source or the saver,
+ // we won't persist the download.
+ return null;
+ }
+
+ // Simplify the representation for the most common saver type. If the saver
+ // is an object instead of a simple string, we can't simplify it because we
+ // need to persist all its properties, not only "type". This may happen for
+ // savers of type "copy" as well as other types.
+ if (saver !== "copy") {
+ serializable.saver = saver;
+ }
+
+ if (this.error) {
+ serializable.errorObj = this.error.toSerializable();
+ }
+
+ if (this.startTime) {
+ serializable.startTime = this.startTime.toJSON();
+ }
+
+ // These are serialized unless they are false, null, or empty strings.
+ for (let property of kPlainSerializableDownloadProperties) {
+ if (this[property]) {
+ serializable[property] = this[property];
+ }
+ }
+
+ serializeUnknownProperties(this, serializable);
+
+ return serializable;
+ },
+
+ /**
+ * Returns a value that changes only when one of the properties of a Download
+ * object that should be saved into a file also change. This excludes
+ * properties whose value doesn't usually change during the download lifetime.
+ *
+ * This function is used to determine whether the download should be
+ * serialized after a property change notification has been received.
+ *
+ * @return String representing the relevant download state.
+ */
+ getSerializationHash: function ()
+ {
+ // The "succeeded", "canceled", "error", and startTime properties are not
+ // taken into account because they all change before the "stopped" property
+ // changes, and are not altered in other cases.
+ return this.stopped + "," + this.totalBytes + "," + this.hasPartialData +
+ "," + this.contentType;
+ },
+};
+
+/**
+ * Defines which properties of the Download object are serializable.
+ */
+const kPlainSerializableDownloadProperties = [
+ "succeeded",
+ "canceled",
+ "totalBytes",
+ "hasPartialData",
+ "hasBlockedData",
+ "tryToKeepPartialData",
+ "launcherPath",
+ "launchWhenSucceeded",
+ "contentType",
+];
+
+/**
+ * Creates a new Download object from a serializable representation. This
+ * function is used by the createDownload method of Downloads.jsm when a new
+ * Download object is requested, thus some properties may refer to live objects
+ * in place of their serializable representations.
+ *
+ * @param aSerializable
+ * An object with the following fields:
+ * {
+ * source: DownloadSource object, or its serializable representation.
+ * See DownloadSource.fromSerializable for details.
+ * target: DownloadTarget object, or its serializable representation.
+ * See DownloadTarget.fromSerializable for details.
+ * saver: Serializable representation of a DownloadSaver object. See
+ * DownloadSaver.fromSerializable for details. If omitted,
+ * defaults to "copy".
+ * }
+ *
+ * @return The newly created Download object.
+ */
+Download.fromSerializable = function (aSerializable) {
+ let download = new Download();
+ if (aSerializable.source instanceof DownloadSource) {
+ download.source = aSerializable.source;
+ } else {
+ download.source = DownloadSource.fromSerializable(aSerializable.source);
+ }
+ if (aSerializable.target instanceof DownloadTarget) {
+ download.target = aSerializable.target;
+ } else {
+ download.target = DownloadTarget.fromSerializable(aSerializable.target);
+ }
+ if ("saver" in aSerializable) {
+ download.saver = DownloadSaver.fromSerializable(aSerializable.saver);
+ } else {
+ download.saver = DownloadSaver.fromSerializable("copy");
+ }
+ download.saver.download = download;
+
+ if ("startTime" in aSerializable) {
+ let time = aSerializable.startTime.getTime
+ ? aSerializable.startTime.getTime()
+ : aSerializable.startTime;
+ download.startTime = new Date(time);
+ }
+
+ // If 'errorObj' is present it will take precedence over the 'error' property.
+ // 'error' is a legacy property only containing message, which is insufficient
+ // to represent all of the error information.
+ //
+ // Instead of just replacing 'error' we use a new 'errorObj' so that previous
+ // versions will keep it as an unknown property.
+ if ("errorObj" in aSerializable) {
+ download.error = DownloadError.fromSerializable(aSerializable.errorObj);
+ } else if ("error" in aSerializable) {
+ download.error = aSerializable.error;
+ }
+
+ for (let property of kPlainSerializableDownloadProperties) {
+ if (property in aSerializable) {
+ download[property] = aSerializable[property];
+ }
+ }
+
+ deserializeUnknownProperties(download, aSerializable, property =>
+ kPlainSerializableDownloadProperties.indexOf(property) == -1 &&
+ property != "startTime" &&
+ property != "source" &&
+ property != "target" &&
+ property != "error" &&
+ property != "saver");
+
+ return download;
+};
+
+// DownloadSource
+
+/**
+ * Represents the source of a download, for example a document or an URI.
+ */
+this.DownloadSource = function () {}
+
+this.DownloadSource.prototype = {
+ /**
+ * String containing the URI for the download source.
+ */
+ url: null,
+
+ /**
+ * Indicates whether the download originated from a private window. This
+ * determines the context of the network request that is made to retrieve the
+ * resource.
+ */
+ isPrivate: false,
+
+ /**
+ * String containing the referrer URI of the download source, or null if no
+ * referrer should be sent or the download source is not HTTP.
+ */
+ referrer: null,
+
+ /**
+ * For downloads handled by the (default) DownloadCopySaver, this function
+ * can adjust the network channel before it is opened, for example to change
+ * the HTTP headers or to upload a stream as POST data.
+ *
+ * @note If this is defined this object will not be serializable, thus the
+ * Download object will not be persisted across sessions.
+ *
+ * @param aChannel
+ * The nsIChannel to be adjusted.
+ *
+ * @return {Promise}
+ * @resolves When the channel has been adjusted and can be opened.
+ * @rejects JavaScript exception that will cause the download to fail.
+ */
+ adjustChannel: null,
+
+ /**
+ * Returns a static representation of the current object state.
+ *
+ * @return A JavaScript object that can be serialized to JSON.
+ */
+ toSerializable: function ()
+ {
+ if (this.adjustChannel) {
+ // If the callback was used, we can't reproduce this across sessions.
+ return null;
+ }
+
+ // Simplify the representation if we don't have other details.
+ if (!this.isPrivate && !this.referrer && !this._unknownProperties) {
+ return this.url;
+ }
+
+ let serializable = { url: this.url };
+ if (this.isPrivate) {
+ serializable.isPrivate = true;
+ }
+ if (this.referrer) {
+ serializable.referrer = this.referrer;
+ }
+
+ serializeUnknownProperties(this, serializable);
+ return serializable;
+ },
+};
+
+/**
+ * Creates a new DownloadSource object from its serializable representation.
+ *
+ * @param aSerializable
+ * Serializable representation of a DownloadSource object. This may be a
+ * string containing the URI for the download source, an nsIURI, or an
+ * object with the following properties:
+ * {
+ * url: String containing the URI for the download source.
+ * isPrivate: Indicates whether the download originated from a private
+ * window. If omitted, the download is public.
+ * referrer: String containing the referrer URI of the download source.
+ * Can be omitted or null if no referrer should be sent or
+ * the download source is not HTTP.
+ * adjustChannel: For downloads handled by (default) DownloadCopySaver,
+ * this function can adjust the network channel before
+ * it is opened, for example to change the HTTP headers
+ * or to upload a stream as POST data. Optional.
+ * }
+ *
+ * @return The newly created DownloadSource object.
+ */
+this.DownloadSource.fromSerializable = function (aSerializable) {
+ let source = new DownloadSource();
+ if (isString(aSerializable)) {
+ // Convert String objects to primitive strings at this point.
+ source.url = aSerializable.toString();
+ } else if (aSerializable instanceof Ci.nsIURI) {
+ source.url = aSerializable.spec;
+ } else if (aSerializable instanceof Ci.nsIDOMWindow) {
+ source.url = aSerializable.location.href;
+ source.isPrivate = PrivateBrowsingUtils.isContentWindowPrivate(aSerializable);
+ source.windowRef = Cu.getWeakReference(aSerializable);
+ } else {
+ // Convert String objects to primitive strings at this point.
+ source.url = aSerializable.url.toString();
+ if ("isPrivate" in aSerializable) {
+ source.isPrivate = aSerializable.isPrivate;
+ }
+ if ("referrer" in aSerializable) {
+ source.referrer = aSerializable.referrer;
+ }
+ if ("adjustChannel" in aSerializable) {
+ source.adjustChannel = aSerializable.adjustChannel;
+ }
+
+ deserializeUnknownProperties(source, aSerializable, property =>
+ property != "url" && property != "isPrivate" && property != "referrer");
+ }
+
+ return source;
+};
+
+// DownloadTarget
+
+/**
+ * Represents the target of a download, for example a file in the global
+ * downloads directory, or a file in the system temporary directory.
+ */
+this.DownloadTarget = function () {}
+
+this.DownloadTarget.prototype = {
+ /**
+ * String containing the path of the target file.
+ */
+ path: null,
+
+ /**
+ * String containing the path of the ".part" file containing the data
+ * downloaded so far, or null to disable the use of a ".part" file to keep
+ * partially downloaded data.
+ */
+ partFilePath: null,
+
+ /**
+ * Indicates whether the target file exists.
+ *
+ * This is a dynamic property updated when the download finishes or when the
+ * "refresh" method of the Download object is called. It can be used by the
+ * front-end to reduce I/O compared to checking the target file directly.
+ */
+ exists: false,
+
+ /**
+ * Size in bytes of the target file, or zero if the download has not finished.
+ *
+ * Even if the target file does not exist anymore, this property may still
+ * have a value taken from the download metadata. If the metadata has never
+ * been available in this session and the size cannot be obtained from the
+ * file because it has already been deleted, this property will be zero.
+ *
+ * For single-file downloads, this property will always match the actual file
+ * size on disk, while the totalBytes property of the Download object, when
+ * available, may represent the size of the encoded data instead.
+ *
+ * For downloads involving multiple files, like complete web pages saved to
+ * disk, the meaning of this value is undefined. It currently matches the size
+ * of the main file only rather than the sum of all the written data.
+ *
+ * This is a dynamic property updated when the download finishes or when the
+ * "refresh" method of the Download object is called. It can be used by the
+ * front-end to reduce I/O compared to checking the target file directly.
+ */
+ size: 0,
+
+ /**
+ * Sets the "exists" and "size" properties based on the actual file on disk.
+ *
+ * @return {Promise}
+ * @resolves When the operation has finished successfully.
+ * @rejects JavaScript exception.
+ */
+ refresh: Task.async(function* () {
+ try {
+ this.size = (yield OS.File.stat(this.path)).size;
+ this.exists = true;
+ } catch (ex) {
+ // Report any error not caused by the file not being there. In any case,
+ // the size of the download is not updated and the known value is kept.
+ if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
+ Cu.reportError(ex);
+ }
+ this.exists = false;
+ }
+ }),
+
+ /**
+ * Returns a static representation of the current object state.
+ *
+ * @return A JavaScript object that can be serialized to JSON.
+ */
+ toSerializable: function ()
+ {
+ // Simplify the representation if we don't have other details.
+ if (!this.partFilePath && !this._unknownProperties) {
+ return this.path;
+ }
+
+ let serializable = { path: this.path,
+ partFilePath: this.partFilePath };
+ serializeUnknownProperties(this, serializable);
+ return serializable;
+ },
+};
+
+/**
+ * Creates a new DownloadTarget object from its serializable representation.
+ *
+ * @param aSerializable
+ * Serializable representation of a DownloadTarget object. This may be a
+ * string containing the path of the target file, an nsIFile, or an
+ * object with the following properties:
+ * {
+ * path: String containing the path of the target file.
+ * partFilePath: optional string containing the part file path.
+ * }
+ *
+ * @return The newly created DownloadTarget object.
+ */
+this.DownloadTarget.fromSerializable = function (aSerializable) {
+ let target = new DownloadTarget();
+ if (isString(aSerializable)) {
+ // Convert String objects to primitive strings at this point.
+ target.path = aSerializable.toString();
+ } else if (aSerializable instanceof Ci.nsIFile) {
+ // Read the "path" property of nsIFile after checking the object type.
+ target.path = aSerializable.path;
+ } else {
+ // Read the "path" property of the serializable DownloadTarget
+ // representation, converting String objects to primitive strings.
+ target.path = aSerializable.path.toString();
+ if ("partFilePath" in aSerializable) {
+ target.partFilePath = aSerializable.partFilePath;
+ }
+
+ deserializeUnknownProperties(target, aSerializable, property =>
+ property != "path" && property != "partFilePath");
+ }
+ return target;
+};
+
+// DownloadError
+
+/**
+ * Provides detailed information about a download failure.
+ *
+ * @param aProperties
+ * Object which may contain any of the following properties:
+ * {
+ * result: Result error code, defaulting to Cr.NS_ERROR_FAILURE
+ * message: String error message to be displayed, or null to use the
+ * message associated with the result code.
+ * inferCause: If true, attempts to determine if the cause of the
+ * download is a network failure or a local file failure,
+ * based on a set of known values of the result code.
+ * This is useful when the error is received by a
+ * component that handles both aspects of the download.
+ * }
+ * The properties object may also contain any of the DownloadError's
+ * because properties, which will be set accordingly in the error object.
+ */
+this.DownloadError = function (aProperties)
+{
+ const NS_ERROR_MODULE_BASE_OFFSET = 0x45;
+ const NS_ERROR_MODULE_NETWORK = 6;
+ const NS_ERROR_MODULE_FILES = 13;
+
+ // Set the error name used by the Error object prototype first.
+ this.name = "DownloadError";
+ this.result = aProperties.result || Cr.NS_ERROR_FAILURE;
+ if (aProperties.message) {
+ this.message = aProperties.message;
+ } else if (aProperties.becauseBlocked ||
+ aProperties.becauseBlockedByParentalControls ||
+ aProperties.becauseBlockedByRuntimePermissions) {
+ this.message = "Download blocked.";
+ } else {
+ let exception = new Components.Exception("", this.result);
+ this.message = exception.toString();
+ }
+ if (aProperties.inferCause) {
+ let module = ((this.result & 0x7FFF0000) >> 16) -
+ NS_ERROR_MODULE_BASE_OFFSET;
+ this.becauseSourceFailed = (module == NS_ERROR_MODULE_NETWORK);
+ this.becauseTargetFailed = (module == NS_ERROR_MODULE_FILES);
+ }
+ else {
+ if (aProperties.becauseSourceFailed) {
+ this.becauseSourceFailed = true;
+ }
+ if (aProperties.becauseTargetFailed) {
+ this.becauseTargetFailed = true;
+ }
+ }
+
+ if (aProperties.becauseBlockedByParentalControls) {
+ this.becauseBlocked = true;
+ this.becauseBlockedByParentalControls = true;
+ } else if (aProperties.becauseBlockedByRuntimePermissions) {
+ this.becauseBlocked = true;
+ this.becauseBlockedByRuntimePermissions = true;
+ } else if (aProperties.becauseBlocked) {
+ this.becauseBlocked = true;
+ }
+
+ if (aProperties.innerException) {
+ this.innerException = aProperties.innerException;
+ }
+
+ this.stack = new Error().stack;
+}
+
+this.DownloadError.prototype = {
+ __proto__: Error.prototype,
+
+ /**
+ * The result code associated with this error.
+ */
+ result: false,
+
+ /**
+ * Indicates an error occurred while reading from the remote location.
+ */
+ becauseSourceFailed: false,
+
+ /**
+ * Indicates an error occurred while writing to the local target.
+ */
+ becauseTargetFailed: false,
+
+ /**
+ * Indicates the download failed because it was blocked. If the reason for
+ * blocking is known, the corresponding property will be also set.
+ */
+ becauseBlocked: false,
+
+ /**
+ * Indicates the download was blocked because downloads are globally
+ * disallowed by the Parental Controls or Family Safety features on Windows.
+ */
+ becauseBlockedByParentalControls: false,
+
+ /**
+ * Indicates the download was blocked because a runtime permission required to
+ * download files was not granted.
+ *
+ * This does not apply to all systems.
+ */
+ becauseBlockedByRuntimePermissions: false,
+
+ /**
+ * If this DownloadError was caused by an exception this property will
+ * contain the original exception. This will not be serialized when saving
+ * to the store.
+ */
+ innerException: null,
+
+ /**
+ * Returns a static representation of the current object state.
+ *
+ * @return A JavaScript object that can be serialized to JSON.
+ */
+ toSerializable: function ()
+ {
+ let serializable = {
+ result: this.result,
+ message: this.message,
+ becauseSourceFailed: this.becauseSourceFailed,
+ becauseTargetFailed: this.becauseTargetFailed,
+ becauseBlocked: this.becauseBlocked,
+ becauseBlockedByParentalControls: this.becauseBlockedByParentalControls,
+ becauseBlockedByRuntimePermissions: this.becauseBlockedByRuntimePermissions,
+ };
+
+ serializeUnknownProperties(this, serializable);
+ return serializable;
+ },
+};
+
+/**
+ * Creates a new DownloadError object from its serializable representation.
+ *
+ * @param aSerializable
+ * Serializable representation of a DownloadError object.
+ *
+ * @return The newly created DownloadError object.
+ */
+this.DownloadError.fromSerializable = function (aSerializable) {
+ let e = new DownloadError(aSerializable);
+ deserializeUnknownProperties(e, aSerializable, property =>
+ property != "result" &&
+ property != "message" &&
+ property != "becauseSourceFailed" &&
+ property != "becauseTargetFailed" &&
+ property != "becauseBlocked" &&
+ property != "becauseBlockedByParentalControls" &&
+ property != "becauseBlockedByRuntimePermissions");
+
+ return e;
+};
+
+// DownloadSaver
+
+/**
+ * Template for an object that actually transfers the data for the download.
+ */
+this.DownloadSaver = function () {}
+
+this.DownloadSaver.prototype = {
+ /**
+ * Download object for raising notifications and reading properties.
+ *
+ * If the tryToKeepPartialData property of the download object is false, the
+ * saver should never try to keep partially downloaded data if the download
+ * fails.
+ */
+ download: null,
+
+ /**
+ * Executes the download.
+ *
+ * @param aSetProgressBytesFn
+ * This function may be called by the saver to report progress. It
+ * takes three arguments: the first is the number of bytes transferred
+ * until now, the second is the total number of bytes to be
+ * transferred (or -1 if unknown), the third indicates whether the
+ * partially downloaded data can be used when restarting the download
+ * if it fails or is canceled.
+ * @param aSetPropertiesFn
+ * This function may be called by the saver to report information
+ * about new download properties discovered by the saver during the
+ * download process. It takes an object where the keys represents
+ * the names of the properties to set, and the value represents the
+ * value to set.
+ *
+ * @return {Promise}
+ * @resolves When the download has finished successfully.
+ * @rejects JavaScript exception if the download failed.
+ */
+ execute: function DS_execute(aSetProgressBytesFn, aSetPropertiesFn)
+ {
+ throw new Error("Not implemented.");
+ },
+
+ /**
+ * Cancels the download.
+ */
+ cancel: function DS_cancel()
+ {
+ throw new Error("Not implemented.");
+ },
+
+ /**
+ * Removes any partial data kept as part of a canceled or failed download.
+ *
+ * This method is never called until the promise returned by "execute" is
+ * either resolved or rejected, and the "execute" method is not called again
+ * until the promise returned by this method is resolved or rejected.
+ *
+ * @return {Promise}
+ * @resolves When the operation has finished successfully.
+ * @rejects JavaScript exception.
+ */
+ removePartialData: function DS_removePartialData()
+ {
+ return Promise.resolve();
+ },
+
+ /**
+ * This can be called by the saver implementation when the download is already
+ * started, to add it to the browsing history. This method has no effect if
+ * the download is private.
+ */
+ addToHistory: function ()
+ {
+ if (this.download.source.isPrivate) {
+ return;
+ }
+
+ let sourceUri = NetUtil.newURI(this.download.source.url);
+ let referrer = this.download.source.referrer;
+ let referrerUri = referrer ? NetUtil.newURI(referrer) : null;
+ let targetUri = NetUtil.newURI(new FileUtils.File(
+ this.download.target.path));
+
+ // The start time is always available when we reach this point.
+ let startPRTime = this.download.startTime.getTime() * 1000;
+
+ try {
+ gDownloadHistory.addDownload(sourceUri, referrerUri, startPRTime,
+ targetUri);
+ }
+ catch (ex) {
+ if (!(ex instanceof Components.Exception) ||
+ ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+ throw ex;
+ }
+ //
+ // Under normal operation the download history service may not
+ // be available. We don't want all downloads that are public to fail
+ // when this happens so we'll ignore this error and this error only!
+ //
+ }
+ },
+
+ /**
+ * Returns a static representation of the current object state.
+ *
+ * @return A JavaScript object that can be serialized to JSON.
+ */
+ toSerializable: function ()
+ {
+ throw new Error("Not implemented.");
+ },
+
+ /**
+ * Returns the SHA-256 hash of the downloaded file, if it exists.
+ */
+ getSha256Hash: function ()
+ {
+ throw new Error("Not implemented.");
+ },
+
+ getSignatureInfo: function ()
+ {
+ throw new Error("Not implemented.");
+ },
+}; // DownloadSaver
+
+/**
+ * Creates a new DownloadSaver object from its serializable representation.
+ *
+ * @param aSerializable
+ * Serializable representation of a DownloadSaver object. If no initial
+ * state information for the saver object is needed, can be a string
+ * representing the class of the download operation, for example "copy".
+ *
+ * @return The newly created DownloadSaver object.
+ */
+this.DownloadSaver.fromSerializable = function (aSerializable) {
+ let serializable = isString(aSerializable) ? { type: aSerializable }
+ : aSerializable;
+ let saver;
+ switch (serializable.type) {
+ case "copy":
+ saver = DownloadCopySaver.fromSerializable(serializable);
+ break;
+ case "legacy":
+ saver = DownloadLegacySaver.fromSerializable(serializable);
+ break;
+ case "pdf":
+ saver = DownloadPDFSaver.fromSerializable(serializable);
+ break;
+ default:
+ throw new Error("Unrecognized download saver type.");
+ }
+ return saver;
+};
+
+// DownloadCopySaver
+
+/**
+ * Saver object that simply copies the entire source file to the target.
+ */
+this.DownloadCopySaver = function () {}
+
+this.DownloadCopySaver.prototype = {
+ __proto__: DownloadSaver.prototype,
+
+ /**
+ * BackgroundFileSaver object currently handling the download.
+ */
+ _backgroundFileSaver: null,
+
+ /**
+ * Indicates whether the "cancel" method has been called. This is used to
+ * prevent the request from starting in case the operation is canceled before
+ * the BackgroundFileSaver instance has been created.
+ */
+ _canceled: false,
+
+ /**
+ * Save the SHA-256 hash in raw bytes of the downloaded file. This is null
+ * unless BackgroundFileSaver has successfully completed saving the file.
+ */
+ _sha256Hash: null,
+
+ /**
+ * Save the signature info as an nsIArray of nsIX509CertList of nsIX509Cert
+ * if the file is signed. This is empty if the file is unsigned, and null
+ * unless BackgroundFileSaver has successfully completed saving the file.
+ */
+ _signatureInfo: null,
+
+ /**
+ * Save the redirects chain as an nsIArray of nsIPrincipal.
+ */
+ _redirects: null,
+
+ /**
+ * True if the associated download has already been added to browsing history.
+ */
+ alreadyAddedToHistory: false,
+
+ /**
+ * String corresponding to the entityID property of the nsIResumableChannel
+ * used to execute the download, or null if the channel was not resumable or
+ * the saver was instructed not to keep partially downloaded data.
+ */
+ entityID: null,
+
+ /**
+ * Implements "DownloadSaver.execute".
+ */
+ execute: function DCS_execute(aSetProgressBytesFn, aSetPropertiesFn)
+ {
+ let copySaver = this;
+
+ this._canceled = false;
+
+ let download = this.download;
+ let targetPath = download.target.path;
+ let partFilePath = download.target.partFilePath;
+ let keepPartialData = download.tryToKeepPartialData;
+
+ return Task.spawn(function* task_DCS_execute() {
+ // Add the download to history the first time it is started in this
+ // session. If the download is restarted in a different session, a new
+ // history visit will be added. We do this just to avoid the complexity
+ // of serializing this state between sessions, since adding a new visit
+ // does not have any noticeable side effect.
+ if (!this.alreadyAddedToHistory) {
+ this.addToHistory();
+ this.alreadyAddedToHistory = true;
+ }
+
+ // To reduce the chance that other downloads reuse the same final target
+ // file name, we should create a placeholder as soon as possible, before
+ // starting the network request. The placeholder is also required in case
+ // we are using a ".part" file instead of the final target while the
+ // download is in progress.
+ try {
+ // If the file already exists, don't delete its contents yet.
+ let file = yield OS.File.open(targetPath, { write: true });
+ yield file.close();
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error)) {
+ throw ex;
+ }
+ // Throw a DownloadError indicating that the operation failed because of
+ // the target file. We cannot translate this into a specific result
+ // code, but we preserve the original message using the toString method.
+ let error = new DownloadError({ message: ex.toString() });
+ error.becauseTargetFailed = true;
+ throw error;
+ }
+
+ try {
+ let deferSaveComplete = Promise.defer();
+
+ if (this._canceled) {
+ // Don't create the BackgroundFileSaver object if we have been
+ // canceled meanwhile.
+ throw new DownloadError({ message: "Saver canceled." });
+ }
+
+ // Create the object that will save the file in a background thread.
+ let backgroundFileSaver = new BackgroundFileSaverStreamListener();
+ try {
+ // When the operation completes, reflect the status in the promise
+ // returned by this download execution function.
+ backgroundFileSaver.observer = {
+ onTargetChange: function () { },
+ onSaveComplete: (aSaver, aStatus) => {
+ // Send notifications now that we can restart if needed.
+ if (Components.isSuccessCode(aStatus)) {
+ // Save the hash before freeing backgroundFileSaver.
+ this._sha256Hash = aSaver.sha256Hash;
+ this._signatureInfo = aSaver.signatureInfo;
+ this._redirects = aSaver.redirects;
+ deferSaveComplete.resolve();
+ } else {
+ // Infer the origin of the error from the failure code, because
+ // BackgroundFileSaver does not provide more specific data.
+ let properties = { result: aStatus, inferCause: true };
+ deferSaveComplete.reject(new DownloadError(properties));
+ }
+ // Free the reference cycle, to release resources earlier.
+ backgroundFileSaver.observer = null;
+ this._backgroundFileSaver = null;
+ },
+ };
+
+ // Create a channel from the source, and listen to progress
+ // notifications.
+ let channel = NetUtil.newChannel({
+ uri: download.source.url,
+ loadUsingSystemPrincipal: true,
+ });
+ if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
+ channel.setPrivate(download.source.isPrivate);
+ }
+ if (channel instanceof Ci.nsIHttpChannel &&
+ download.source.referrer) {
+ channel.referrer = NetUtil.newURI(download.source.referrer);
+ }
+
+ // If we have data that we can use to resume the download from where
+ // it stopped, try to use it.
+ let resumeAttempted = false;
+ let resumeFromBytes = 0;
+ if (channel instanceof Ci.nsIResumableChannel && this.entityID &&
+ partFilePath && keepPartialData) {
+ try {
+ let stat = yield OS.File.stat(partFilePath);
+ channel.resumeAt(stat.size, this.entityID);
+ resumeAttempted = true;
+ resumeFromBytes = stat.size;
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+ throw ex;
+ }
+ }
+ }
+
+ channel.notificationCallbacks = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor]),
+ getInterface: XPCOMUtils.generateQI([Ci.nsIProgressEventSink]),
+ onProgress: function DCSE_onProgress(aRequest, aContext, aProgress,
+ aProgressMax)
+ {
+ let currentBytes = resumeFromBytes + aProgress;
+ let totalBytes = aProgressMax == -1 ? -1 : (resumeFromBytes +
+ aProgressMax);
+ aSetProgressBytesFn(currentBytes, totalBytes, aProgress > 0 &&
+ partFilePath && keepPartialData);
+ },
+ onStatus: function () { },
+ };
+
+ // If the callback was set, handle it now before opening the channel.
+ if (download.source.adjustChannel) {
+ yield download.source.adjustChannel(channel);
+ }
+
+ // Open the channel, directing output to the background file saver.
+ backgroundFileSaver.QueryInterface(Ci.nsIStreamListener);
+ channel.asyncOpen2({
+ onStartRequest: function (aRequest, aContext) {
+ backgroundFileSaver.onStartRequest(aRequest, aContext);
+
+ // Check if the request's response has been blocked by Windows
+ // Parental Controls with an HTTP 450 error code.
+ if (aRequest instanceof Ci.nsIHttpChannel &&
+ aRequest.responseStatus == 450) {
+ // Set a flag that can be retrieved later when handling the
+ // cancellation so that the proper error can be thrown.
+ this.download._blockedByParentalControls = true;
+ aRequest.cancel(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ aSetPropertiesFn({ contentType: channel.contentType });
+
+ // Ensure we report the value of "Content-Length", if available,
+ // even if the download doesn't generate any progress events
+ // later.
+ if (channel.contentLength >= 0) {
+ aSetProgressBytesFn(0, channel.contentLength);
+ }
+
+ // If the URL we are downloading from includes a file extension
+ // that matches the "Content-Encoding" header, for example ".gz"
+ // with a "gzip" encoding, we should save the file in its encoded
+ // form. In all other cases, we decode the body while saving.
+ if (channel instanceof Ci.nsIEncodedChannel &&
+ channel.contentEncodings) {
+ let uri = channel.URI;
+ if (uri instanceof Ci.nsIURL && uri.fileExtension) {
+ // Only the first, outermost encoding is considered.
+ let encoding = channel.contentEncodings.getNext();
+ if (encoding) {
+ channel.applyConversion =
+ gExternalHelperAppService.applyDecodingForExtension(
+ uri.fileExtension, encoding);
+ }
+ }
+ }
+
+ if (keepPartialData) {
+ // If the source is not resumable, don't keep partial data even
+ // if we were asked to try and do it.
+ if (aRequest instanceof Ci.nsIResumableChannel) {
+ try {
+ // If reading the ID succeeds, the source is resumable.
+ this.entityID = aRequest.entityID;
+ } catch (ex) {
+ if (!(ex instanceof Components.Exception) ||
+ ex.result != Cr.NS_ERROR_NOT_RESUMABLE) {
+ throw ex;
+ }
+ keepPartialData = false;
+ }
+ } else {
+ keepPartialData = false;
+ }
+ }
+
+ // Enable hashing and signature verification before setting the
+ // target.
+ backgroundFileSaver.enableSha256();
+ backgroundFileSaver.enableSignatureInfo();
+ if (partFilePath) {
+ // If we actually resumed a request, append to the partial data.
+ if (resumeAttempted) {
+ // TODO: Handle Cr.NS_ERROR_ENTITY_CHANGED
+ backgroundFileSaver.enableAppend();
+ }
+
+ // Use a part file, determining if we should keep it on failure.
+ backgroundFileSaver.setTarget(new FileUtils.File(partFilePath),
+ keepPartialData);
+ } else {
+ // Set the final target file, and delete it on failure.
+ backgroundFileSaver.setTarget(new FileUtils.File(targetPath),
+ false);
+ }
+ }.bind(copySaver),
+
+ onStopRequest: function (aRequest, aContext, aStatusCode) {
+ try {
+ backgroundFileSaver.onStopRequest(aRequest, aContext,
+ aStatusCode);
+ } finally {
+ // If the data transfer completed successfully, indicate to the
+ // background file saver that the operation can finish. If the
+ // data transfer failed, the saver has been already stopped.
+ if (Components.isSuccessCode(aStatusCode)) {
+ backgroundFileSaver.finish(Cr.NS_OK);
+ }
+ }
+ }.bind(copySaver),
+
+ onDataAvailable: function (aRequest, aContext, aInputStream,
+ aOffset, aCount) {
+ backgroundFileSaver.onDataAvailable(aRequest, aContext,
+ aInputStream, aOffset,
+ aCount);
+ }.bind(copySaver),
+ });
+
+ // We should check if we have been canceled in the meantime, after
+ // all the previous asynchronous operations have been executed and
+ // just before we set the _backgroundFileSaver property.
+ if (this._canceled) {
+ throw new DownloadError({ message: "Saver canceled." });
+ }
+
+ // If the operation succeeded, store the object to allow cancellation.
+ this._backgroundFileSaver = backgroundFileSaver;
+ } catch (ex) {
+ // In case an error occurs while setting up the chain of objects for
+ // the download, ensure that we release the resources of the saver.
+ backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
+ // Since we're not going to handle deferSaveComplete.promise below,
+ // we need to make sure that the rejection is handled.
+ deferSaveComplete.promise.catch(() => {});
+ throw ex;
+ }
+
+ // We will wait on this promise in case no error occurred while setting
+ // up the chain of objects for the download.
+ yield deferSaveComplete.promise;
+
+ yield this._moveFinalDownload(aSetPropertiesFn);
+ } catch (ex) {
+ // Ensure we always remove the placeholder for the final target file on
+ // failure, independently of which code path failed. In some cases, the
+ // background file saver may have already removed the file.
+ try {
+ yield OS.File.remove(targetPath);
+ } catch (e2) {
+ // If we failed during the operation, we report the error but use the
+ // original one as the failure reason of the download. Note that on
+ // Windows we may get an access denied error instead of a no such file
+ // error if the file existed before, and was recently deleted.
+ if (!(e2 instanceof OS.File.Error &&
+ (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
+ Cu.reportError(e2);
+ }
+ }
+ throw ex;
+ }
+ }.bind(this));
+ },
+
+ /**
+ * If the download passes the reputation check and is using a part file we
+ * will move it to the target path.
+ *
+ * @param aSetPropertiesFn
+ * Function provided to the "execute" method.
+ *
+ * @return {Promise}
+ * @resolves When the cleanup is complete.
+ */
+ _moveFinalDownload: Task.async(function* (aSetPropertiesFn) {
+ let download = this.download;
+ let targetPath = this.download.target.path;
+ let partFilePath = this.download.target.partFilePath;
+
+ if (partFilePath) {
+ yield OS.File.move(partFilePath, targetPath);
+ }
+ }),
+
+ /**
+ * Implements "DownloadSaver.cancel".
+ */
+ cancel: function DCS_cancel()
+ {
+ this._canceled = true;
+ if (this._backgroundFileSaver) {
+ this._backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
+ this._backgroundFileSaver = null;
+ }
+ },
+
+ /**
+ * Implements "DownloadSaver.removePartialData".
+ */
+ removePartialData: function ()
+ {
+ return Task.spawn(function* task_DCS_removePartialData() {
+ if (this.download.target.partFilePath) {
+ try {
+ yield OS.File.remove(this.download.target.partFilePath);
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+ throw ex;
+ }
+ }
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Implements "DownloadSaver.toSerializable".
+ */
+ toSerializable: function ()
+ {
+ // Simplify the representation if we don't have other details.
+ if (!this.entityID && !this._unknownProperties) {
+ return "copy";
+ }
+
+ let serializable = { type: "copy",
+ entityID: this.entityID };
+ serializeUnknownProperties(this, serializable);
+ return serializable;
+ },
+
+ /**
+ * Implements "DownloadSaver.getSha256Hash"
+ */
+ getSha256Hash: function ()
+ {
+ return this._sha256Hash;
+ },
+
+ /*
+ * Implements DownloadSaver.getSignatureInfo.
+ */
+ getSignatureInfo: function ()
+ {
+ return this._signatureInfo;
+ },
+
+ /*
+ * Implements DownloadSaver.getRedirects.
+ */
+ getRedirects: function ()
+ {
+ return this._redirects;
+ }
+};
+
+/**
+ * Creates a new DownloadCopySaver object, with its initial state derived from
+ * its serializable representation.
+ *
+ * @param aSerializable
+ * Serializable representation of a DownloadCopySaver object.
+ *
+ * @return The newly created DownloadCopySaver object.
+ */
+this.DownloadCopySaver.fromSerializable = function (aSerializable) {
+ let saver = new DownloadCopySaver();
+ if ("entityID" in aSerializable) {
+ saver.entityID = aSerializable.entityID;
+ }
+
+ deserializeUnknownProperties(saver, aSerializable, property =>
+ property != "entityID" && property != "type");
+
+ return saver;
+};
+
+// DownloadLegacySaver
+
+/**
+ * Saver object that integrates with the legacy nsITransfer interface.
+ *
+ * For more background on the process, see the DownloadLegacyTransfer object.
+ */
+this.DownloadLegacySaver = function ()
+{
+ this.deferExecuted = Promise.defer();
+ this.deferCanceled = Promise.defer();
+}
+
+this.DownloadLegacySaver.prototype = {
+ __proto__: DownloadSaver.prototype,
+
+ /**
+ * Save the SHA-256 hash in raw bytes of the downloaded file. This may be
+ * null when nsExternalHelperAppService (and thus BackgroundFileSaver) is not
+ * invoked.
+ */
+ _sha256Hash: null,
+
+ /**
+ * Save the signature info as an nsIArray of nsIX509CertList of nsIX509Cert
+ * if the file is signed. This is empty if the file is unsigned, and null
+ * unless BackgroundFileSaver has successfully completed saving the file.
+ */
+ _signatureInfo: null,
+
+ /**
+ * Save the redirect chain as an nsIArray of nsIPrincipal.
+ */
+ _redirects: null,
+
+ /**
+ * nsIRequest object associated to the status and progress updates we
+ * received. This object is null before we receive the first status and
+ * progress update, and is also reset to null when the download is stopped.
+ */
+ request: null,
+
+ /**
+ * This deferred object contains a promise that is resolved as soon as this
+ * download finishes successfully, and is rejected in case the download is
+ * canceled or receives a failure notification through nsITransfer.
+ */
+ deferExecuted: null,
+
+ /**
+ * This deferred object contains a promise that is resolved if the download
+ * receives a cancellation request through the "cancel" method, and is never
+ * rejected. The nsITransfer implementation will register a handler that
+ * actually causes the download cancellation.
+ */
+ deferCanceled: null,
+
+ /**
+ * This is populated with the value of the aSetProgressBytesFn argument of the
+ * "execute" method, and is null before the method is called.
+ */
+ setProgressBytesFn: null,
+
+ /**
+ * Called by the nsITransfer implementation while the download progresses.
+ *
+ * @param aCurrentBytes
+ * Number of bytes transferred until now.
+ * @param aTotalBytes
+ * Total number of bytes to be transferred, or -1 if unknown.
+ */
+ onProgressBytes: function DLS_onProgressBytes(aCurrentBytes, aTotalBytes)
+ {
+ this.progressWasNotified = true;
+
+ // Ignore progress notifications until we are ready to process them.
+ if (!this.setProgressBytesFn) {
+ // Keep the data from the last progress notification that was received.
+ this.currentBytes = aCurrentBytes;
+ this.totalBytes = aTotalBytes;
+ return;
+ }
+
+ let hasPartFile = !!this.download.target.partFilePath;
+
+ this.setProgressBytesFn(aCurrentBytes, aTotalBytes,
+ aCurrentBytes > 0 && hasPartFile);
+ },
+
+ /**
+ * Whether the onProgressBytes function has been called at least once.
+ */
+ progressWasNotified: false,
+
+ /**
+ * Called by the nsITransfer implementation when the request has started.
+ *
+ * @param aRequest
+ * nsIRequest associated to the status update.
+ * @param aAlreadyAddedToHistory
+ * Indicates that the nsIExternalHelperAppService component already
+ * added the download to the browsing history, unless it was started
+ * from a private browsing window. When this parameter is false, the
+ * download is added to the browsing history here. Private downloads
+ * are never added to history even if this parameter is false.
+ */
+ onTransferStarted: function (aRequest, aAlreadyAddedToHistory)
+ {
+ // Store the entity ID to use for resuming if required.
+ if (this.download.tryToKeepPartialData &&
+ aRequest instanceof Ci.nsIResumableChannel) {
+ try {
+ // If reading the ID succeeds, the source is resumable.
+ this.entityID = aRequest.entityID;
+ } catch (ex) {
+ if (!(ex instanceof Components.Exception) ||
+ ex.result != Cr.NS_ERROR_NOT_RESUMABLE) {
+ throw ex;
+ }
+ }
+ }
+
+ // For legacy downloads, we must update the referrer at this time.
+ if (aRequest instanceof Ci.nsIHttpChannel && aRequest.referrer) {
+ this.download.source.referrer = aRequest.referrer.spec;
+ }
+
+ if (!aAlreadyAddedToHistory) {
+ this.addToHistory();
+ }
+ },
+
+ /**
+ * Called by the nsITransfer implementation when the request has finished.
+ *
+ * @param aRequest
+ * nsIRequest associated to the status update.
+ * @param aStatus
+ * Status code received by the nsITransfer implementation.
+ */
+ onTransferFinished: function DLS_onTransferFinished(aRequest, aStatus)
+ {
+ // Store a reference to the request, used when handling completion.
+ this.request = aRequest;
+
+ if (Components.isSuccessCode(aStatus)) {
+ this.deferExecuted.resolve();
+ } else {
+ // Infer the origin of the error from the failure code, because more
+ // specific data is not available through the nsITransfer implementation.
+ let properties = { result: aStatus, inferCause: true };
+ this.deferExecuted.reject(new DownloadError(properties));
+ }
+ },
+
+ /**
+ * When the first execution of the download finished, it can be restarted by
+ * using a DownloadCopySaver object instead of the original legacy component
+ * that executed the download.
+ */
+ firstExecutionFinished: false,
+
+ /**
+ * In case the download is restarted after the first execution finished, this
+ * property contains a reference to the DownloadCopySaver that is executing
+ * the new download attempt.
+ */
+ copySaver: null,
+
+ /**
+ * String corresponding to the entityID property of the nsIResumableChannel
+ * used to execute the download, or null if the channel was not resumable or
+ * the saver was instructed not to keep partially downloaded data.
+ */
+ entityID: null,
+
+ /**
+ * Implements "DownloadSaver.execute".
+ */
+ execute: function DLS_execute(aSetProgressBytesFn, aSetPropertiesFn)
+ {
+ // Check if this is not the first execution of the download. The Download
+ // object guarantees that this function is not re-entered during execution.
+ if (this.firstExecutionFinished) {
+ if (!this.copySaver) {
+ this.copySaver = new DownloadCopySaver();
+ this.copySaver.download = this.download;
+ this.copySaver.entityID = this.entityID;
+ this.copySaver.alreadyAddedToHistory = true;
+ }
+ return this.copySaver.execute.apply(this.copySaver, arguments);
+ }
+
+ this.setProgressBytesFn = aSetProgressBytesFn;
+ if (this.progressWasNotified) {
+ this.onProgressBytes(this.currentBytes, this.totalBytes);
+ }
+
+ return Task.spawn(function* task_DLS_execute() {
+ try {
+ // Wait for the component that executes the download to finish.
+ yield this.deferExecuted.promise;
+
+ // At this point, the "request" property has been populated. Ensure we
+ // report the value of "Content-Length", if available, even if the
+ // download didn't generate any progress events.
+ if (!this.progressWasNotified &&
+ this.request instanceof Ci.nsIChannel &&
+ this.request.contentLength >= 0) {
+ aSetProgressBytesFn(0, this.request.contentLength);
+ }
+
+ // If the component executing the download provides the path of a
+ // ".part" file, it means that it expects the listener to move the file
+ // to its final target path when the download succeeds. In this case,
+ // an empty ".part" file is created even if no data was received from
+ // the source.
+ //
+ // When no ".part" file path is provided the download implementation may
+ // not have created the target file (if no data was received from the
+ // source). In this case, ensure that an empty file is created as
+ // expected.
+ if (!this.download.target.partFilePath) {
+ try {
+ // This atomic operation is more efficient than an existence check.
+ let file = yield OS.File.open(this.download.target.path,
+ { create: true });
+ yield file.close();
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error) || !ex.becauseExists) {
+ throw ex;
+ }
+ }
+ }
+
+ yield this._moveFinalDownload(aSetPropertiesFn);
+
+ } catch (ex) {
+ // Ensure we always remove the final target file on failure,
+ // independently of which code path failed. In some cases, the
+ // component executing the download may have already removed the file.
+ try {
+ yield OS.File.remove(this.download.target.path);
+ } catch (e2) {
+ // If we failed during the operation, we report the error but use the
+ // original one as the failure reason of the download. Note that on
+ // Windows we may get an access denied error instead of a no such file
+ // error if the file existed before, and was recently deleted.
+ if (!(e2 instanceof OS.File.Error &&
+ (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
+ Cu.reportError(e2);
+ }
+ }
+ // In case the operation failed, ensure we stop downloading data. Since
+ // we never re-enter this function, deferCanceled is always available.
+ this.deferCanceled.resolve();
+ throw ex;
+ } finally {
+ // We don't need the reference to the request anymore. We must also set
+ // deferCanceled to null in order to free any indirect references it
+ // may hold to the request.
+ this.request = null;
+ this.deferCanceled = null;
+ // Allow the download to restart through a DownloadCopySaver.
+ this.firstExecutionFinished = true;
+ }
+ }.bind(this));
+ },
+
+ _moveFinalDownload: function () {
+ return DownloadCopySaver.prototype._moveFinalDownload
+ .apply(this, arguments);
+ },
+
+ /**
+ * Implements "DownloadSaver.cancel".
+ */
+ cancel: function DLS_cancel()
+ {
+ // We may be using a DownloadCopySaver to handle resuming.
+ if (this.copySaver) {
+ return this.copySaver.cancel.apply(this.copySaver, arguments);
+ }
+
+ // If the download hasn't stopped already, resolve deferCanceled so that the
+ // operation is canceled as soon as a cancellation handler is registered.
+ // Note that the handler might not have been registered yet.
+ if (this.deferCanceled) {
+ this.deferCanceled.resolve();
+ }
+ },
+
+ /**
+ * Implements "DownloadSaver.removePartialData".
+ */
+ removePartialData: function ()
+ {
+ // DownloadCopySaver and DownloadLeagcySaver use the same logic for removing
+ // partially downloaded data, though this implementation isn't shared by
+ // other saver types, thus it isn't found on their shared prototype.
+ return DownloadCopySaver.prototype.removePartialData.call(this);
+ },
+
+ /**
+ * Implements "DownloadSaver.toSerializable".
+ */
+ toSerializable: function ()
+ {
+ // This object depends on legacy components that are created externally,
+ // thus it cannot be rebuilt during deserialization. To support resuming
+ // across different browser sessions, this object is transformed into a
+ // DownloadCopySaver for the purpose of serialization.
+ return DownloadCopySaver.prototype.toSerializable.call(this);
+ },
+
+ /**
+ * Implements "DownloadSaver.getSha256Hash".
+ */
+ getSha256Hash: function ()
+ {
+ if (this.copySaver) {
+ return this.copySaver.getSha256Hash();
+ }
+ return this._sha256Hash;
+ },
+
+ /**
+ * Called by the nsITransfer implementation when the hash is available.
+ */
+ setSha256Hash: function (hash)
+ {
+ this._sha256Hash = hash;
+ },
+
+ /**
+ * Implements "DownloadSaver.getSignatureInfo".
+ */
+ getSignatureInfo: function ()
+ {
+ if (this.copySaver) {
+ return this.copySaver.getSignatureInfo();
+ }
+ return this._signatureInfo;
+ },
+
+ /**
+ * Called by the nsITransfer implementation when the hash is available.
+ */
+ setSignatureInfo: function (signatureInfo)
+ {
+ this._signatureInfo = signatureInfo;
+ },
+
+ /**
+ * Implements "DownloadSaver.getRedirects".
+ */
+ getRedirects: function ()
+ {
+ if (this.copySaver) {
+ return this.copySaver.getRedirects();
+ }
+ return this._redirects;
+ },
+
+ /**
+ * Called by the nsITransfer implementation when the redirect chain is
+ * available.
+ */
+ setRedirects: function (redirects)
+ {
+ this._redirects = redirects;
+ },
+};
+
+/**
+ * Returns a new DownloadLegacySaver object. This saver type has a
+ * deserializable form only when creating a new object in memory, because it
+ * cannot be serialized to disk.
+ */
+this.DownloadLegacySaver.fromSerializable = function () {
+ return new DownloadLegacySaver();
+};
+
+// DownloadPDFSaver
+
+/**
+ * This DownloadSaver type creates a PDF file from the current document in a
+ * given window, specified using the windowRef property of the DownloadSource
+ * object associated with the download.
+ *
+ * In order to prevent the download from saving a different document than the one
+ * originally loaded in the window, any attempt to restart the download will fail.
+ *
+ * Since this DownloadSaver type requires a live document as a source, it cannot
+ * be persisted across sessions, unless the download already succeeded.
+ */
+this.DownloadPDFSaver = function () {
+}
+
+this.DownloadPDFSaver.prototype = {
+ __proto__: DownloadSaver.prototype,
+
+ /**
+ * An nsIWebBrowserPrint instance for printing this page.
+ * This is null when saving has not started or has completed,
+ * or while the operation is being canceled.
+ */
+ _webBrowserPrint: null,
+
+ /**
+ * Implements "DownloadSaver.execute".
+ */
+ execute: function (aSetProgressBytesFn, aSetPropertiesFn)
+ {
+ return Task.spawn(function* task_DCS_execute() {
+ if (!this.download.source.windowRef) {
+ throw new DownloadError({
+ message: "PDF saver must be passed an open window, and cannot be restarted.",
+ becauseSourceFailed: true,
+ });
+ }
+
+ let win = this.download.source.windowRef.get();
+
+ // Set windowRef to null to avoid re-trying.
+ this.download.source.windowRef = null;
+
+ if (!win) {
+ throw new DownloadError({
+ message: "PDF saver can't save a window that has been closed.",
+ becauseSourceFailed: true,
+ });
+ }
+
+ this.addToHistory();
+
+ let targetPath = this.download.target.path;
+
+ // An empty target file must exist for the PDF printer to work correctly.
+ let file = yield OS.File.open(targetPath, { truncate: true });
+ yield file.close();
+
+ let printSettings = gPrintSettingsService.newPrintSettings;
+
+ printSettings.printToFile = true;
+ printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
+ printSettings.toFileName = targetPath;
+
+ printSettings.printSilent = true;
+ printSettings.showPrintProgress = false;
+
+ printSettings.printBGImages = true;
+ printSettings.printBGColors = true;
+ printSettings.printFrameType = Ci.nsIPrintSettings.kFramesAsIs;
+ printSettings.headerStrCenter = "";
+ printSettings.headerStrLeft = "";
+ printSettings.headerStrRight = "";
+ printSettings.footerStrCenter = "";
+ printSettings.footerStrLeft = "";
+ printSettings.footerStrRight = "";
+
+ this._webBrowserPrint = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebBrowserPrint);
+
+ try {
+ yield new Promise((resolve, reject) => {
+ this._webBrowserPrint.print(printSettings, {
+ onStateChange: function (webProgress, request, stateFlags, status) {
+ if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ if (!Components.isSuccessCode(status)) {
+ reject(new DownloadError({ result: status,
+ inferCause: true }));
+ } else {
+ resolve();
+ }
+ }
+ },
+ onProgressChange: function (webProgress, request, curSelfProgress,
+ maxSelfProgress, curTotalProgress,
+ maxTotalProgress) {
+ aSetProgressBytesFn(curTotalProgress, maxTotalProgress, false);
+ },
+ onLocationChange: function () {},
+ onStatusChange: function () {},
+ onSecurityChange: function () {},
+ });
+ });
+ } finally {
+ // Remove the print object to avoid leaks
+ this._webBrowserPrint = null;
+ }
+
+ let fileInfo = yield OS.File.stat(targetPath);
+ aSetProgressBytesFn(fileInfo.size, fileInfo.size, false);
+ }.bind(this));
+ },
+
+ /**
+ * Implements "DownloadSaver.cancel".
+ */
+ cancel: function DCS_cancel()
+ {
+ if (this._webBrowserPrint) {
+ this._webBrowserPrint.cancel();
+ this._webBrowserPrint = null;
+ }
+ },
+
+ /**
+ * Implements "DownloadSaver.toSerializable".
+ */
+ toSerializable: function ()
+ {
+ if (this.download.succeeded) {
+ return DownloadCopySaver.prototype.toSerializable.call(this);
+ }
+
+ // This object needs a window to recreate itself. If it didn't succeded
+ // it will not be possible to restart. Returning null here will
+ // prevent us from serializing it at all.
+ return null;
+ },
+};
+
+/**
+ * Creates a new DownloadPDFSaver object, with its initial state derived from
+ * its serializable representation.
+ *
+ * @param aSerializable
+ * Serializable representation of a DownloadPDFSaver object.
+ *
+ * @return The newly created DownloadPDFSaver object.
+ */
+this.DownloadPDFSaver.fromSerializable = function (aSerializable) {
+ return new DownloadPDFSaver();
+};
diff --git a/components/jsdownloads/src/DownloadImport.jsm b/components/jsdownloads/src/DownloadImport.jsm
new file mode 100644
index 000000000..063a93f4f
--- /dev/null
+++ b/components/jsdownloads/src/DownloadImport.jsm
@@ -0,0 +1,192 @@
+/* -*- 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadImport",
+];
+
+// 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, "OS",
+ "resource://gre/modules/osfile.jsm")
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
+ "resource://gre/modules/Sqlite.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+
+/**
+ * These values come from the previous interface
+ * nsIDownloadManager, which has now been deprecated.
+ * These are the only types of download states that
+ * we will import.
+ */
+const DOWNLOAD_NOTSTARTED = -1;
+const DOWNLOAD_DOWNLOADING = 0;
+const DOWNLOAD_PAUSED = 4;
+const DOWNLOAD_QUEUED = 5;
+
+// DownloadImport
+
+/**
+ * Provides an object that has a method to import downloads
+ * from the previous SQLite storage format.
+ *
+ * @param aList A DownloadList where each successfully
+ * imported download will be added.
+ * @param aPath The path to the database file.
+ */
+this.DownloadImport = function (aList, aPath)
+{
+ this.list = aList;
+ this.path = aPath;
+}
+
+this.DownloadImport.prototype = {
+ /**
+ * Imports unfinished downloads from the previous SQLite storage
+ * format (supporting schemas 7 and up), to the new Download object
+ * format. Each imported download will be added to the DownloadList
+ *
+ * @return {Promise}
+ * @resolves When the operation has completed (i.e., every download
+ * from the previous database has been read and added to
+ * the DownloadList)
+ */
+ import: function () {
+ return Task.spawn(function* task_DI_import() {
+ let connection = yield Sqlite.openConnection({ path: this.path });
+
+ try {
+ let schemaVersion = yield connection.getSchemaVersion();
+ // We don't support schemas older than version 7 (from 2007)
+ // - Version 7 added the columns mimeType, preferredApplication
+ // and preferredAction in 2007
+ // - Version 8 added the column autoResume in 2007
+ // (if we encounter version 7 we will treat autoResume = false)
+ // - Version 9 is the last known version, which added a unique
+ // GUID text column that is not used here
+ if (schemaVersion < 7) {
+ throw new Error("Unable to import in-progress downloads because "
+ + "the existing profile is too old.");
+ }
+
+ let rows = yield connection.execute("SELECT * FROM moz_downloads");
+
+ for (let row of rows) {
+ try {
+ // Get the DB row data
+ let source = row.getResultByName("source");
+ let target = row.getResultByName("target");
+ let tempPath = row.getResultByName("tempPath");
+ let startTime = row.getResultByName("startTime");
+ let state = row.getResultByName("state");
+ let referrer = row.getResultByName("referrer");
+ let maxBytes = row.getResultByName("maxBytes");
+ let mimeType = row.getResultByName("mimeType");
+ let preferredApplication = row.getResultByName("preferredApplication");
+ let preferredAction = row.getResultByName("preferredAction");
+ let entityID = row.getResultByName("entityID");
+
+ let autoResume = false;
+ try {
+ autoResume = (row.getResultByName("autoResume") == 1);
+ } catch (ex) {
+ // autoResume wasn't present in schema version 7
+ }
+
+ if (!source) {
+ throw new Error("Attempted to import a row with an empty " +
+ "source column.");
+ }
+
+ let resumeDownload = false;
+
+ switch (state) {
+ case DOWNLOAD_NOTSTARTED:
+ case DOWNLOAD_QUEUED:
+ case DOWNLOAD_DOWNLOADING:
+ resumeDownload = true;
+ break;
+
+ case DOWNLOAD_PAUSED:
+ resumeDownload = autoResume;
+ break;
+
+ default:
+ // We won't import downloads in other states
+ continue;
+ }
+
+ // Transform the data
+ let targetPath = NetUtil.newURI(target)
+ .QueryInterface(Ci.nsIFileURL).file.path;
+
+ let launchWhenSucceeded = (preferredAction != Ci.nsIMIMEInfo.saveToDisk);
+
+ let downloadOptions = {
+ source: {
+ url: source,
+ referrer: referrer
+ },
+ target: {
+ path: targetPath,
+ partFilePath: tempPath,
+ },
+ saver: {
+ type: "copy",
+ entityID: entityID
+ },
+ startTime: new Date(startTime / 1000),
+ totalBytes: maxBytes,
+ hasPartialData: !!tempPath,
+ tryToKeepPartialData: true,
+ launchWhenSucceeded: launchWhenSucceeded,
+ contentType: mimeType,
+ launcherPath: preferredApplication
+ };
+
+ // Paused downloads that should not be auto-resumed are considered
+ // in a "canceled" state.
+ if (!resumeDownload) {
+ downloadOptions.canceled = true;
+ }
+
+ let download = yield Downloads.createDownload(downloadOptions);
+
+ yield this.list.add(download);
+
+ if (resumeDownload) {
+ download.start().catch(() => {});
+ } else {
+ yield download.refresh();
+ }
+
+ } catch (ex) {
+ Cu.reportError("Error importing download: " + ex);
+ }
+ }
+
+ } catch (ex) {
+ Cu.reportError(ex);
+ } finally {
+ yield connection.close();
+ }
+ }.bind(this));
+ }
+}
+
diff --git a/components/jsdownloads/src/DownloadIntegration.jsm b/components/jsdownloads/src/DownloadIntegration.jsm
new file mode 100644
index 000000000..8b5c64498
--- /dev/null
+++ b/components/jsdownloads/src/DownloadIntegration.jsm
@@ -0,0 +1,1189 @@
+/* -*- 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/. */
+
+/**
+ * Provides functions to integrate with the host application, handling for
+ * example the global prompts on shutdown.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadIntegration",
+];
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Integration.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+ "resource://gre/modules/AsyncShutdown.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+ "resource://gre/modules/DeferredTask.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadStore",
+ "resource://gre/modules/DownloadStore.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadImport",
+ "resource://gre/modules/DownloadImport.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
+ "resource://gre/modules/DownloadUIHelper.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");
+#ifdef MOZ_PLACES
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+#endif
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDownloadPlatform",
+ "@mozilla.org/toolkit/download-platform;1",
+ "mozIDownloadPlatform");
+XPCOMUtils.defineLazyServiceGetter(this, "gEnvironment",
+ "@mozilla.org/process/environment;1",
+ "nsIEnvironment");
+XPCOMUtils.defineLazyServiceGetter(this, "gMIMEService",
+ "@mozilla.org/mime;1",
+ "nsIMIMEService");
+XPCOMUtils.defineLazyServiceGetter(this, "gExternalProtocolService",
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ "nsIExternalProtocolService");
+
+XPCOMUtils.defineLazyGetter(this, "gParentalControlsService", function() {
+ if ("@mozilla.org/parental-controls-service;1" in Cc) {
+ return Cc["@mozilla.org/parental-controls-service;1"]
+ .createInstance(Ci.nsIParentalControlsService);
+ }
+ return null;
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
+ "@mozilla.org/telephony/volume-service;1",
+ "nsIVolumeService");
+
+// We have to use the gCombinedDownloadIntegration identifier because, in this
+// module only, the DownloadIntegration identifier refers to the base version.
+Integration.downloads.defineModuleGetter(this, "gCombinedDownloadIntegration",
+ "resource://gre/modules/DownloadIntegration.jsm",
+ "DownloadIntegration");
+
+const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
+ "initWithCallback");
+
+/**
+ * Indicates the delay between a change to the downloads data and the related
+ * save operation.
+ *
+ * For best efficiency, this value should be high enough that the input/output
+ * for opening or closing the target file does not overlap with the one for
+ * saving the list of downloads.
+ */
+const kSaveDelayMs = 1500;
+
+/**
+ * This pref indicates if we have already imported (or attempted to import)
+ * the downloads database from the previous SQLite storage.
+ */
+const kPrefImportedFromSqlite = "browser.download.importedFromSqlite";
+
+/**
+ * List of observers to listen against
+ */
+const kObserverTopics = [
+ "quit-application-requested",
+ "offline-requested",
+ "last-pb-context-exiting",
+ "last-pb-context-exited",
+ "sleep_notification",
+ "suspend_process_notification",
+ "wake_notification",
+ "resume_process_notification",
+ "network:offline-about-to-go-offline",
+ "network:offline-status-changed",
+ "xpcom-will-shutdown",
+];
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadIntegration
+
+/**
+ * Provides functions to integrate with the host application, handling for
+ * example the global prompts on shutdown.
+ */
+this.DownloadIntegration = {
+ /**
+ * Main DownloadStore object for loading and saving the list of persistent
+ * downloads, or null if the download list was never requested and thus it
+ * doesn't need to be persisted.
+ */
+ _store: null,
+
+ /**
+ * Returns whether data for blocked downloads should be kept on disk.
+ * Implementations which support unblocking downloads may return true to
+ * keep the blocked download on disk until its fate is decided.
+ *
+ * If a download is blocked and the partial data is kept the Download's
+ * 'hasBlockedData' property will be true. In this state Download.unblock()
+ * or Download.confirmBlock() may be used to either unblock the download or
+ * remove the downloaded data respectively.
+ *
+ * Even if shouldKeepBlockedData returns true, if the download did not use a
+ * partFile the blocked data will be removed - preventing the complete
+ * download from existing on disk with its final filename.
+ *
+ * @return boolean True if data should be kept.
+ */
+ shouldKeepBlockedData() {
+ const FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
+ return Services.appinfo.ID == FIREFOX_ID;
+ },
+
+ /**
+ * Performs initialization of the list of persistent downloads, before its
+ * first use by the host application. This function may be called only once
+ * during the entire lifetime of the application.
+ *
+ * @param list
+ * DownloadList object to be initialized.
+ *
+ * @return {Promise}
+ * @resolves When the list has been initialized.
+ * @rejects JavaScript exception.
+ */
+ initializePublicDownloadList: Task.async(function* (list) {
+ try {
+ yield this.loadPublicDownloadListFromStore(list);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ // After the list of persistent downloads has been loaded, we can add the
+ // history observers, even if the load operation failed. This object is kept
+ // alive by the history service.
+ new DownloadHistoryObserver(list);
+ }),
+
+ /**
+ * Called by initializePublicDownloadList to load the list of persistent
+ * downloads, before its first use by the host application. This function may
+ * be called only once during the entire lifetime of the application.
+ *
+ * @param list
+ * DownloadList object to be populated with the download objects
+ * serialized from the previous session. This list will be persisted
+ * to disk during the session lifetime.
+ *
+ * @return {Promise}
+ * @resolves When the list has been populated.
+ * @rejects JavaScript exception.
+ */
+ loadPublicDownloadListFromStore: Task.async(function* (list) {
+ if (this._store) {
+ throw new Error("Initialization may be performed only once.");
+ }
+
+ this._store = new DownloadStore(list, OS.Path.join(
+ OS.Constants.Path.profileDir,
+ "downloads.json"));
+ this._store.onsaveitem = this.shouldPersistDownload.bind(this);
+
+ try {
+ if (this._importedFromSqlite) {
+ yield this._store.load();
+ } else {
+ let sqliteDBpath = OS.Path.join(OS.Constants.Path.profileDir,
+ "downloads.sqlite");
+
+ if (yield OS.File.exists(sqliteDBpath)) {
+ let sqliteImport = new DownloadImport(list, sqliteDBpath);
+ yield sqliteImport.import();
+
+ let importCount = (yield list.getAll()).length;
+ if (importCount > 0) {
+ try {
+ yield this._store.save();
+ } catch (ex) { }
+ }
+
+ // No need to wait for the file removal.
+ OS.File.remove(sqliteDBpath).then(null, Cu.reportError);
+ }
+
+ Services.prefs.setBoolPref(kPrefImportedFromSqlite, true);
+
+ // Don't even report error here because this file is pre Firefox 3
+ // and most likely doesn't exist.
+ OS.File.remove(OS.Path.join(OS.Constants.Path.profileDir,
+ "downloads.rdf")).catch(() => {});
+
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ // Add the view used for detecting changes to downloads to be persisted.
+ // We must do this after the list of persistent downloads has been loaded,
+ // even if the load operation failed. We wait for a complete initialization
+ // so other callers cannot modify the list without being detected. The
+ // DownloadAutoSaveView is kept alive by the underlying DownloadList.
+ yield new DownloadAutoSaveView(list, this._store).initialize();
+ }),
+
+ /**
+ * Determines if a Download object from the list of persistent downloads
+ * should be saved into a file, so that it can be restored across sessions.
+ *
+ * This function allows filtering out downloads that the host application is
+ * not interested in persisting across sessions, for example downloads that
+ * finished successfully.
+ *
+ * @param aDownload
+ * The Download object to be inspected. This is originally taken from
+ * the global DownloadList object for downloads that were not started
+ * from a private browsing window. The item may have been removed
+ * from the list since the save operation started, though in this case
+ * the save operation will be repeated later.
+ *
+ * @return True to save the download, false otherwise.
+ */
+ shouldPersistDownload(aDownload) {
+ // On all platforms, we save all the downloads currently in progress, as
+ // well as stopped downloads for which we retained partially downloaded
+ // data or we have blocked data.
+ if (!aDownload.stopped || aDownload.hasPartialData ||
+ aDownload.hasBlockedData) {
+ return true;
+ }
+
+ // On Desktop, stopped downloads for which we don't need to track the
+ // presence of a ".part" file are only retained in the browser history.
+ return false;
+ },
+
+ /**
+ * Returns the system downloads directory asynchronously.
+ *
+ * @return {Promise}
+ * @resolves The downloads directory string path.
+ */
+ getSystemDownloadsDirectory: Task.async(function* () {
+ if (this._downloadsDirectory) {
+ return this._downloadsDirectory;
+ }
+
+ let directoryPath = null;
+#ifdef XP_WIN
+ // For XP/2K, use My Documents/Downloads. Other version uses
+ // the default Downloads directory.
+ let version = parseFloat(Services.sysinfo.getProperty("version"));
+ if (version < 6) {
+ directoryPath = yield this._createDownloadsDirectory("Pers");
+ } else {
+ directoryPath = this._getDirectory("DfltDwnld");
+ }
+
+#elifdef XP_UNIX
+ // For Linux, use XDG download dir, with a fallback to Home/Downloads
+ // if the XDG user dirs are disabled.
+ try {
+ directoryPath = this._getDirectory("DfltDwnld");
+ } catch(e) {
+ directoryPath = yield this._createDownloadsDirectory("Home");
+ }
+#else
+ directoryPath = yield this._createDownloadsDirectory("Home");
+#endif
+
+ this._downloadsDirectory = directoryPath;
+ return this._downloadsDirectory;
+ }),
+ _downloadsDirectory: null,
+
+ /**
+ * Returns the user downloads directory asynchronously.
+ *
+ * @return {Promise}
+ * @resolves The downloads directory string path.
+ */
+ getPreferredDownloadsDirectory: Task.async(function* () {
+ let directoryPath = null;
+ let prefValue = Services.prefs.getIntPref("browser.download.folderList", 1);
+
+ switch(prefValue) {
+ case 0: // Desktop
+ directoryPath = this._getDirectory("Desk");
+ break;
+ case 1: // Downloads
+ directoryPath = yield this.getSystemDownloadsDirectory();
+ break;
+ case 2: // Custom
+ try {
+ let directory = Services.prefs.getComplexValue("browser.download.dir",
+ Ci.nsIFile);
+ directoryPath = directory.path;
+ yield OS.File.makeDir(directoryPath, { ignoreExisting: true });
+ } catch(ex) {
+ // Either the preference isn't set or the directory cannot be created.
+ directoryPath = yield this.getSystemDownloadsDirectory();
+ }
+ break;
+ default:
+ directoryPath = yield this.getSystemDownloadsDirectory();
+ }
+ return directoryPath;
+ }),
+
+ /**
+ * Returns the temporary downloads directory asynchronously.
+ *
+ * @return {Promise}
+ * @resolves The downloads directory string path.
+ */
+ getTemporaryDownloadsDirectory: Task.async(function* () {
+ let directoryPath = null;
+ directoryPath = this._getDirectory("TmpD");
+ return directoryPath;
+ }),
+
+ /**
+ * Checks to determine whether to block downloads for parental controls.
+ *
+ * aParam aDownload
+ * The download object.
+ *
+ * @return {Promise}
+ * @resolves The boolean indicates to block downloads or not.
+ */
+ shouldBlockForParentalControls(aDownload) {
+ let isEnabled = gParentalControlsService &&
+ gParentalControlsService.parentalControlsEnabled;
+ let shouldBlock = isEnabled &&
+ gParentalControlsService.blockFileDownloadsEnabled;
+
+ // Log the event if required by parental controls settings.
+ if (isEnabled && gParentalControlsService.loggingEnabled) {
+ gParentalControlsService.log(gParentalControlsService.ePCLog_FileDownload,
+ shouldBlock,
+ NetUtil.newURI(aDownload.source.url), null);
+ }
+
+ return Promise.resolve(shouldBlock);
+ },
+
+ /**
+ * Checks to determine whether to block downloads for not granted runtime permissions.
+ *
+ * @return {Promise}
+ * @resolves The boolean indicates to block downloads or not.
+ */
+ shouldBlockForRuntimePermissions() {
+ return Promise.resolve(false);
+ },
+
+#ifdef XP_WIN
+ /**
+ * Checks whether downloaded files should be marked as coming from
+ * Internet Zone.
+ *
+ * @return true if files should be marked
+ */
+ _shouldSaveZoneInformation() {
+ let zonePref = 2;
+ try {
+ zonePref = Services.prefs.getIntPref("browser.download.saveZoneInformation");
+ } catch (ex) {}
+
+ switch (zonePref) {
+ case 0: // Never
+ return false;
+ case 1: // Always
+ return true;
+ case 2: // System-defined
+ let key = Cc["@mozilla.org/windows-registry-key;1"]
+ .createInstance(Ci.nsIWindowsRegKey);
+ try {
+ key.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Attachments",
+ Ci.nsIWindowsRegKey.ACCESS_QUERY_VALUE);
+ try {
+ return key.readIntValue("SaveZoneInformation") != 1;
+ } finally {
+ key.close();
+ }
+ } catch (ex) {
+ // If the key is not present, files should be marked by default.
+ return true;
+ }
+ default: // Invalid pref value defaults marking files.
+ return true;
+ }
+ },
+#endif
+
+ /**
+ * Performs platform-specific operations when a download is done.
+ *
+ * aParam aDownload
+ * The Download object.
+ *
+ * @return {Promise}
+ * @resolves When all the operations completed successfully.
+ * @rejects JavaScript exception if any of the operations failed.
+ */
+ downloadDone: Task.async(function* (aDownload) {
+#ifdef XP_WIN
+ // On Windows, we mark any file saved to the NTFS file system as coming
+ // from the Internet security zone unless Group Policy disables the
+ // feature. We do this by writing to the "Zone.Identifier" Alternate
+ // Data Stream directly, because the Save method of the
+ // IAttachmentExecute interface would trigger operations that may cause
+ // the application to hang, or other performance issues.
+ // The stream created in this way is forward-compatible with all the
+ // current and future versions of Windows.
+ if (this._shouldSaveZoneInformation()) {
+ let zone;
+ try {
+ zone = gDownloadPlatform.mapUrlToZone(aDownload.source.url);
+ } catch (e) {
+ // Default to Internet Zone if mapUrlToZone failed for
+ // whatever reason.
+ zone = Ci.mozIDownloadPlatform.ZONE_INTERNET;
+ }
+ try {
+ // Don't write zone IDs for Local, Intranet, or Trusted sites
+ // to match Windows behavior.
+ if (zone >= Ci.mozIDownloadPlatform.ZONE_INTERNET) {
+ let streamPath = aDownload.target.path + ":Zone.Identifier";
+ let stream = yield OS.File.open(
+ streamPath,
+ { create: true },
+ { winAllowLengthBeyondMaxPathWithCaveats: true }
+ );
+ try {
+ yield stream.write(new TextEncoder().encode("[ZoneTransfer]\r\nZoneId=" + zone + "\r\n"));
+ } finally {
+ yield stream.close();
+ }
+ }
+ } catch (ex) {
+ // If writing to the stream fails, we ignore the error and continue.
+ // The Windows API error 123 (ERROR_INVALID_NAME) is expected to
+ // occur when working on a file system that does not support
+ // Alternate Data Streams, like FAT32, thus we don't report this
+ // specific error.
+ if (!(ex instanceof OS.File.Error) || ex.winLastError != 123) {
+ Cu.reportError(ex);
+ }
+ }
+ }
+#endif
+
+ // The file with the partially downloaded data has restrictive permissions
+ // that don't allow other users on the system to access it. Now that the
+ // download is completed, we need to adjust permissions based on whether
+ // this is a permanently downloaded file or a temporary download to be
+ // opened read-only with an external application.
+ try {
+ // The following logic to determine whether this is a temporary download
+ // is due to the fact that "deleteTempFileOnExit" is false on Mac, where
+ // downloads to be opened with external applications are preserved in
+ // the "Downloads" folder like normal downloads.
+ let isTemporaryDownload =
+ aDownload.launchWhenSucceeded && (aDownload.source.isPrivate ||
+ Services.prefs.getBoolPref("browser.helperApps.deleteTempFileOnExit"));
+ // Permanently downloaded files are made accessible by other users on
+ // this system, while temporary downloads are marked as read-only.
+ let options = {};
+ if (isTemporaryDownload) {
+ options.unixMode = 0o400;
+ options.winAttributes = {readOnly: true};
+ } else {
+ options.unixMode = 0o666;
+ }
+ // On Unix, the umask of the process is respected.
+ yield OS.File.setPermissions(aDownload.target.path, options);
+ } catch (ex) {
+ // We should report errors with making the permissions less restrictive
+ // or marking the file as read-only on Unix and Mac, but this should not
+ // prevent the download from completing.
+ // The setPermissions API error EPERM is expected to occur when working
+ // on a file system that does not support file permissions, like FAT32,
+ // thus we don't report this error.
+ if (!(ex instanceof OS.File.Error) || ex.unixErrno != OS.Constants.libc.EPERM) {
+ Cu.reportError(ex);
+ }
+ }
+
+ let aReferrer = null;
+ if (aDownload.source.referrer) {
+ aReferrer = NetUtil.newURI(aDownload.source.referrer);
+ }
+
+ gDownloadPlatform.downloadDone(NetUtil.newURI(aDownload.source.url),
+ aReferrer,
+ new FileUtils.File(aDownload.target.path),
+ aDownload.contentType,
+ aDownload.source.isPrivate);
+ }),
+
+ /**
+ * Launches a file represented by the target of a download. This can
+ * open the file with the default application for the target MIME type
+ * or file extension, or with a custom application if
+ * aDownload.launcherPath is set.
+ *
+ * @param aDownload
+ * A Download object that contains the necessary information
+ * to launch the file. The relevant properties are: the target
+ * file, the contentType and the custom application chosen
+ * to launch it.
+ *
+ * @return {Promise}
+ * @resolves When the instruction to launch the file has been
+ * successfully given to the operating system. Note that
+ * the OS might still take a while until the file is actually
+ * launched.
+ * @rejects JavaScript exception if there was an error trying to launch
+ * the file.
+ */
+ launchDownload: Task.async(function* (aDownload) {
+ let file = new FileUtils.File(aDownload.target.path);
+
+ // In case of a double extension, like ".tar.gz", we only
+ // consider the last one, because the MIME service cannot
+ // handle multiple extensions.
+ let fileExtension = null, mimeInfo = null;
+ let match = file.leafName.match(/\.([^.]+)$/);
+ if (match) {
+ fileExtension = match[1];
+ }
+
+#ifdef XP_WIN
+ let isWindowsExe = fileExtension.toLowerCase() == "exe";
+#else
+ let isWindowsExe = false;
+#endif
+
+ // Ask for confirmation if the file is executable, except for .exe on
+ // Windows where the operating system will show the prompt based on the
+ // security zone. We do this here, instead of letting the caller handle
+ // the prompt separately in the user interface layer, for two reasons. The
+ // first is because of its security nature, so that add-ons cannot forget
+ // to do this check. The second is that the system-level security prompt
+ // would be displayed at launch time in any case.
+ if (file.isExecutable() && !isWindowsExe &&
+ !(yield this.confirmLaunchExecutable(file.path))) {
+ return;
+ }
+
+ try {
+ // The MIME service might throw if contentType == "" and it can't find
+ // a MIME type for the given extension, so we'll treat this case as
+ // an unknown mimetype.
+ mimeInfo = gMIMEService.getFromTypeAndExtension(aDownload.contentType,
+ fileExtension);
+ } catch (e) { }
+
+ if (aDownload.launcherPath) {
+ if (!mimeInfo) {
+ // This should not happen on normal circumstances because launcherPath
+ // is only set when we had an instance of nsIMIMEInfo to retrieve
+ // the custom application chosen by the user.
+ throw new Error(
+ "Unable to create nsIMIMEInfo to launch a custom application");
+ }
+
+ // Custom application chosen
+ let localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
+ .createInstance(Ci.nsILocalHandlerApp);
+ localHandlerApp.executable = new FileUtils.File(aDownload.launcherPath);
+
+ mimeInfo.preferredApplicationHandler = localHandlerApp;
+ mimeInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;
+
+ this.launchFile(file, mimeInfo);
+ return;
+ }
+
+#ifdef XP_WIN
+ // When a file has no extension, and there's an executable file with the
+ // same name in the same folder, the Windows shell can get confused.
+ // For this reason, we show the file in the containing folder instead of
+ // trying to open it.
+ // We also don't trust mimeinfo; it could be a type we can forward to a
+ // system handler, but it could also be an executable type, and we
+ // don't have an exhaustive list with all of them.
+ if (!fileExtension) {
+ // We can't check for the existence of a same-name file with every
+ // possible executable extension, so this is a catch-all.
+ this.showContainingDirectory(aDownload.target.path);
+ return;
+ }
+#endif
+
+ // No custom application chosen, let's launch the file with the default
+ // handler. First, let's try to launch it through the MIME service.
+ if (mimeInfo) {
+ mimeInfo.preferredAction = Ci.nsIMIMEInfo.useSystemDefault;
+
+ try {
+ this.launchFile(file, mimeInfo);
+ return;
+ } catch (ex) { }
+ }
+
+ // If it didn't work or if there was no MIME info available,
+ // let's try to directly launch the file.
+ try {
+ this.launchFile(file);
+ return;
+ } catch (ex) { }
+
+ // If our previous attempts failed, try sending it through
+ // the system's external "file:" URL handler.
+ gExternalProtocolService.loadUrl(NetUtil.newURI(file));
+ }),
+
+ /**
+ * Asks for confirmation for launching the specified executable file. This
+ * can be overridden by regression tests to avoid the interactive prompt.
+ */
+ confirmLaunchExecutable: Task.async(function* (path) {
+ // We don't anchor the prompt to a specific window intentionally, not
+ // only because this is the same behavior as the system-level prompt,
+ // but also because the most recently active window is the right choice
+ // in basically all cases.
+ return yield DownloadUIHelper.getPrompter().confirmLaunchExecutable(path);
+ }),
+
+ /**
+ * Launches the specified file, unless overridden by regression tests.
+ */
+ launchFile(file, mimeInfo) {
+ if (mimeInfo) {
+ mimeInfo.launchWithFile(file);
+ } else {
+ file.launch();
+ }
+ },
+
+ /**
+ * Shows the containing folder of a file.
+ *
+ * @param aFilePath
+ * The path to the file.
+ *
+ * @return {Promise}
+ * @resolves When the instruction to open the containing folder has been
+ * successfully given to the operating system. Note that
+ * the OS might still take a while until the folder is actually
+ * opened.
+ * @rejects JavaScript exception if there was an error trying to open
+ * the containing folder.
+ */
+ showContainingDirectory: Task.async(function* (aFilePath) {
+ let file = new FileUtils.File(aFilePath);
+
+ try {
+ // Show the directory containing the file and select the file.
+ file.reveal();
+ return;
+ } 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 = file.parent;
+ if (!parent) {
+ throw new Error(
+ "Unexpected reference to a top-level directory instead of a file");
+ }
+
+ try {
+ // Open the parent directory to show where the file should be.
+ parent.launch();
+ return;
+ } catch (ex) { }
+
+ // If launch also fails (probably because it's not implemented), let
+ // the OS handler try to open the parent.
+ gExternalProtocolService.loadUrl(NetUtil.newURI(parent));
+ }),
+
+ /**
+ * Calls the directory service, create a downloads directory and returns an
+ * nsIFile for the downloads directory.
+ *
+ * @return {Promise}
+ * @resolves The directory string path.
+ */
+ _createDownloadsDirectory(aName) {
+ // We read the name of the directory from the list of translated strings
+ // that is kept by the UI helper module, even if this string is not strictly
+ // displayed in the user interface.
+ let directoryPath = OS.Path.join(this._getDirectory(aName),
+ DownloadUIHelper.strings.downloadsFolder);
+
+ // Create the Downloads folder and ignore if it already exists.
+ return OS.File.makeDir(directoryPath, { ignoreExisting: true })
+ .then(() => directoryPath);
+ },
+
+ /**
+ * Returns the string path for the given directory service location name. This
+ * can be overridden by regression tests to return the path of the system
+ * temporary directory in all cases.
+ */
+ _getDirectory(name) {
+ return Services.dirsvc.get(name, Ci.nsIFile).path;
+ },
+
+ /**
+ * Register the downloads interruption observers.
+ *
+ * @param aList
+ * The public or private downloads list.
+ * @param aIsPrivate
+ * True if the list is private, false otherwise.
+ *
+ * @return {Promise}
+ * @resolves When the views and observers are added.
+ */
+ addListObservers(aList, aIsPrivate) {
+ DownloadObserver.registerView(aList, aIsPrivate);
+ if (!DownloadObserver.observersAdded) {
+ DownloadObserver.observersAdded = true;
+ for (let topic of kObserverTopics) {
+ Services.obs.addObserver(DownloadObserver, topic, false);
+ }
+ }
+ return Promise.resolve();
+ },
+
+ /**
+ * Force a save on _store if it exists. Used to ensure downloads do not
+ * persist after being sanitized.
+ *
+ * @return {Promise}
+ * @resolves When _store.save() completes.
+ */
+ forceSave() {
+ if (this._store) {
+ return this._store.save();
+ }
+ return Promise.resolve();
+ },
+
+ /**
+ * Checks if we have already imported (or attempted to import)
+ * the downloads database from the previous SQLite storage.
+ *
+ * @return boolean True if we the previous DB was imported.
+ */
+ get _importedFromSqlite() {
+ try {
+ return Services.prefs.getBoolPref(kPrefImportedFromSqlite);
+ } catch (ex) {
+ return false;
+ }
+ },
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadObserver
+
+this.DownloadObserver = {
+ /**
+ * Flag to determine if the observers have been added previously.
+ */
+ observersAdded: false,
+
+ /**
+ * Timer used to delay restarting canceled downloads upon waking and returning
+ * online.
+ */
+ _wakeTimer: null,
+
+ /**
+ * Set that contains the in progress publics downloads.
+ * It's kept updated when a public download is added, removed or changes its
+ * properties.
+ */
+ _publicInProgressDownloads: new Set(),
+
+ /**
+ * Set that contains the in progress private downloads.
+ * It's kept updated when a private download is added, removed or changes its
+ * properties.
+ */
+ _privateInProgressDownloads: new Set(),
+
+ /**
+ * Set that contains the downloads that have been canceled when going offline
+ * or to sleep. These are started again when returning online or waking. This
+ * list is not persisted so when exiting and restarting, the downloads will not
+ * be started again.
+ */
+ _canceledOfflineDownloads: new Set(),
+
+ /**
+ * Registers a view that updates the corresponding downloads state set, based
+ * on the aIsPrivate argument. The set is updated when a download is added,
+ * removed or changes its properties.
+ *
+ * @param aList
+ * The public or private downloads list.
+ * @param aIsPrivate
+ * True if the list is private, false otherwise.
+ */
+ registerView: function DO_registerView(aList, aIsPrivate) {
+ let downloadsSet = aIsPrivate ? this._privateInProgressDownloads
+ : this._publicInProgressDownloads;
+ let downloadsView = {
+ onDownloadAdded: aDownload => {
+ if (!aDownload.stopped) {
+ downloadsSet.add(aDownload);
+ }
+ },
+ onDownloadChanged: aDownload => {
+ if (aDownload.stopped) {
+ downloadsSet.delete(aDownload);
+ } else {
+ downloadsSet.add(aDownload);
+ }
+ },
+ onDownloadRemoved: aDownload => {
+ downloadsSet.delete(aDownload);
+ // The download must also be removed from the canceled when offline set.
+ this._canceledOfflineDownloads.delete(aDownload);
+ }
+ };
+
+ // We register the view asynchronously.
+ aList.addView(downloadsView).then(null, Cu.reportError);
+ },
+
+ /**
+ * Wrapper that handles the test mode before calling the prompt that display
+ * a warning message box that informs that there are active downloads,
+ * and asks whether the user wants to cancel them or not.
+ *
+ * @param aCancel
+ * The observer notification subject.
+ * @param aDownloadsCount
+ * The current downloads count.
+ * @param aPrompter
+ * The prompter object that shows the confirm dialog.
+ * @param aPromptType
+ * The type of prompt notification depending on the observer.
+ */
+ _confirmCancelDownloads: function DO_confirmCancelDownload(
+ aCancel, aDownloadsCount, aPrompter, aPromptType) {
+ // If user has already dismissed the request, then do nothing.
+ if ((aCancel instanceof Ci.nsISupportsPRBool) && aCancel.data) {
+ return;
+ }
+ // Handle test mode
+ if (gCombinedDownloadIntegration._testPromptDownloads) {
+ gCombinedDownloadIntegration._testPromptDownloads = aDownloadsCount;
+ return;
+ }
+
+ aCancel.data = aPrompter.confirmCancelDownloads(aDownloadsCount, aPromptType);
+ },
+
+ /**
+ * Resume all downloads that were paused when going offline, used when waking
+ * from sleep or returning from being offline.
+ */
+ _resumeOfflineDownloads: function DO_resumeOfflineDownloads() {
+ this._wakeTimer = null;
+
+ for (let download of this._canceledOfflineDownloads) {
+ download.start().catch(() => {});
+ }
+ },
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// nsIObserver
+
+ observe: function DO_observe(aSubject, aTopic, aData) {
+ let downloadsCount;
+ let p = DownloadUIHelper.getPrompter();
+ switch (aTopic) {
+ case "quit-application-requested":
+ downloadsCount = this._publicInProgressDownloads.size +
+ this._privateInProgressDownloads.size;
+ this._confirmCancelDownloads(aSubject, downloadsCount, p, p.ON_QUIT);
+ break;
+ case "offline-requested":
+ downloadsCount = this._publicInProgressDownloads.size +
+ this._privateInProgressDownloads.size;
+ this._confirmCancelDownloads(aSubject, downloadsCount, p, p.ON_OFFLINE);
+ break;
+ case "last-pb-context-exiting":
+ downloadsCount = this._privateInProgressDownloads.size;
+ this._confirmCancelDownloads(aSubject, downloadsCount, p,
+ p.ON_LEAVE_PRIVATE_BROWSING);
+ break;
+ case "last-pb-context-exited":
+ let promise = Task.spawn(function() {
+ let list = yield Downloads.getList(Downloads.PRIVATE);
+ let downloads = yield list.getAll();
+
+ // We can remove the downloads and finalize them in parallel.
+ for (let download of downloads) {
+ list.remove(download).then(null, Cu.reportError);
+ download.finalize(true).then(null, Cu.reportError);
+ }
+ });
+ // Handle test mode
+ if (gCombinedDownloadIntegration._testResolveClearPrivateList) {
+ gCombinedDownloadIntegration._testResolveClearPrivateList(promise);
+ } else {
+ promise.catch(ex => Cu.reportError(ex));
+ }
+ break;
+ case "sleep_notification":
+ case "suspend_process_notification":
+ case "network:offline-about-to-go-offline":
+ for (let download of this._publicInProgressDownloads) {
+ download.cancel();
+ this._canceledOfflineDownloads.add(download);
+ }
+ for (let download of this._privateInProgressDownloads) {
+ download.cancel();
+ this._canceledOfflineDownloads.add(download);
+ }
+ break;
+ case "wake_notification":
+ case "resume_process_notification":
+ let wakeDelay = 10000;
+ try {
+ wakeDelay = Services.prefs.getIntPref("browser.download.manager.resumeOnWakeDelay");
+ } catch(e) {}
+
+ if (wakeDelay >= 0) {
+ this._wakeTimer = new Timer(this._resumeOfflineDownloads.bind(this), wakeDelay,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ break;
+ case "network:offline-status-changed":
+ if (aData == "online") {
+ this._resumeOfflineDownloads();
+ }
+ break;
+ // We need to unregister observers explicitly before we reach the
+ // "xpcom-shutdown" phase, otherwise observers may be notified when some
+ // required services are not available anymore. We can't unregister
+ // observers on "quit-application", because this module is also loaded
+ // during "make package" automation, and the quit notification is not sent
+ // in that execution environment (bug 973637).
+ case "xpcom-will-shutdown":
+ for (let topic of kObserverTopics) {
+ Services.obs.removeObserver(this, topic);
+ }
+ break;
+ }
+ },
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadHistoryObserver
+
+#ifdef MOZ_PLACES
+/**
+ * Registers a Places observer so that operations on download history are
+ * reflected on the provided list of downloads.
+ *
+ * You do not need to keep a reference to this object in order to keep it alive,
+ * because the history service already keeps a strong reference to it.
+ *
+ * @param aList
+ * DownloadList object linked to this observer.
+ */
+this.DownloadHistoryObserver = function (aList)
+{
+ this._list = aList;
+ PlacesUtils.history.addObserver(this, false);
+}
+
+this.DownloadHistoryObserver.prototype = {
+ /**
+ * DownloadList object linked to this observer.
+ */
+ _list: null,
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver]),
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// nsINavHistoryObserver
+
+ onDeleteURI: function DL_onDeleteURI(aURI, aGUID) {
+ this._list.removeFinished(download => aURI.equals(NetUtil.newURI(
+ download.source.url)));
+ },
+
+ onClearHistory: function DL_onClearHistory() {
+ this._list.removeFinished();
+ },
+
+ onTitleChanged: function () {},
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onVisit: function () {},
+ onPageChanged: function () {},
+ onDeleteVisits: function () {},
+};
+#else
+/**
+ * Empty implementation when we have no Places support, for example on B2G.
+ */
+this.DownloadHistoryObserver = function (aList) {}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadAutoSaveView
+
+/**
+ * This view can be added to a DownloadList object to trigger a save operation
+ * in the given DownloadStore object when a relevant change occurs. You should
+ * call the "initialize" method in order to register the view and load the
+ * current state from disk.
+ *
+ * You do not need to keep a reference to this object in order to keep it alive,
+ * because the DownloadList object already keeps a strong reference to it.
+ *
+ * @param aList
+ * The DownloadList object on which the view should be registered.
+ * @param aStore
+ * The DownloadStore object used for saving.
+ */
+this.DownloadAutoSaveView = function (aList, aStore)
+{
+ this._list = aList;
+ this._store = aStore;
+ this._downloadsMap = new Map();
+ this._writer = new DeferredTask(() => this._store.save(), kSaveDelayMs);
+ AsyncShutdown.profileBeforeChange.addBlocker("DownloadAutoSaveView: writing data",
+ () => this._writer.finalize());
+}
+
+this.DownloadAutoSaveView.prototype = {
+ /**
+ * DownloadList object linked to this view.
+ */
+ _list: null,
+
+ /**
+ * The DownloadStore object used for saving.
+ */
+ _store: null,
+
+ /**
+ * True when the initial state of the downloads has been loaded.
+ */
+ _initialized: false,
+
+ /**
+ * Registers the view and loads the current state from disk.
+ *
+ * @return {Promise}
+ * @resolves When the view has been registered.
+ * @rejects JavaScript exception.
+ */
+ initialize: function ()
+ {
+ // We set _initialized to true after adding the view, so that
+ // onDownloadAdded doesn't cause a save to occur.
+ return this._list.addView(this).then(() => this._initialized = true);
+ },
+
+ /**
+ * This map contains only Download objects that should be saved to disk, and
+ * associates them with the result of their getSerializationHash function, for
+ * the purpose of detecting changes to the relevant properties.
+ */
+ _downloadsMap: null,
+
+ /**
+ * DeferredTask for the save operation.
+ */
+ _writer: null,
+
+ /**
+ * Called when the list of downloads changed, this triggers the asynchronous
+ * serialization of the list of downloads.
+ */
+ saveSoon: function ()
+ {
+ this._writer.arm();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// DownloadList view
+
+ onDownloadAdded: function (aDownload)
+ {
+ if (gCombinedDownloadIntegration.shouldPersistDownload(aDownload)) {
+ this._downloadsMap.set(aDownload, aDownload.getSerializationHash());
+ if (this._initialized) {
+ this.saveSoon();
+ }
+ }
+ },
+
+ onDownloadChanged: function (aDownload)
+ {
+ if (!gCombinedDownloadIntegration.shouldPersistDownload(aDownload)) {
+ if (this._downloadsMap.has(aDownload)) {
+ this._downloadsMap.delete(aDownload);
+ this.saveSoon();
+ }
+ return;
+ }
+
+ let hash = aDownload.getSerializationHash();
+ if (this._downloadsMap.get(aDownload) != hash) {
+ this._downloadsMap.set(aDownload, hash);
+ this.saveSoon();
+ }
+ },
+
+ onDownloadRemoved: function (aDownload)
+ {
+ if (this._downloadsMap.has(aDownload)) {
+ this._downloadsMap.delete(aDownload);
+ this.saveSoon();
+ }
+ },
+};
diff --git a/components/jsdownloads/src/DownloadLegacy.js b/components/jsdownloads/src/DownloadLegacy.js
new file mode 100644
index 000000000..3b7e8a1eb
--- /dev/null
+++ b/components/jsdownloads/src/DownloadLegacy.js
@@ -0,0 +1,308 @@
+/* -*- 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/. */
+
+/**
+ * This component implements the XPCOM interfaces required for integration with
+ * the legacy download components.
+ *
+ * New code is expected to use the "Downloads.jsm" module directly, without
+ * going through the interfaces implemented in this XPCOM component. These
+ * interfaces are only maintained for backwards compatibility with components
+ * that still work synchronously on the main thread.
+ */
+
+"use strict";
+
+// 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, "Promise",
+ "resource://gre/modules/Promise.jsm");
+
+// DownloadLegacyTransfer
+
+/**
+ * nsITransfer implementation that provides a bridge to a Download object.
+ *
+ * Legacy downloads work differently than the JavaScript implementation. In the
+ * latter, the caller only provides the properties for the Download object and
+ * the entire process is handled by the "start" method. In the legacy
+ * implementation, the caller must create a separate object to execute the
+ * download, and then make the download visible to the user by hooking it up to
+ * an nsITransfer instance.
+ *
+ * Since nsITransfer instances may be created before the download system is
+ * initialized, and initialization as well as other operations are asynchronous,
+ * this implementation is able to delay all progress and status notifications it
+ * receives until the associated Download object is finally created.
+ *
+ * Conversely, the DownloadLegacySaver object can also receive execution and
+ * cancellation requests asynchronously, before or after it is connected to
+ * this nsITransfer instance. For that reason, those requests are communicated
+ * in a potentially deferred way, using promise objects.
+ *
+ * The component that executes the download implements nsICancelable to receive
+ * cancellation requests, but after cancellation it cannot be reused again.
+ *
+ * Since the components that execute the download may be different and they
+ * don't always give consistent results, this bridge takes care of enforcing the
+ * expectations, for example by ensuring the target file exists when the
+ * download is successful, even if the source has a size of zero bytes.
+ */
+function DownloadLegacyTransfer()
+{
+ this._deferDownload = Promise.defer();
+}
+
+DownloadLegacyTransfer.prototype = {
+ classID: Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"),
+
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsIWebProgressListener2,
+ Ci.nsITransfer]),
+
+ // nsIWebProgressListener
+
+ onStateChange: function DLT_onStateChange(aWebProgress, aRequest, aStateFlags,
+ aStatus)
+ {
+ if (!Components.isSuccessCode(aStatus)) {
+ this._componentFailed = true;
+ }
+
+ if ((aStateFlags & Ci.nsIWebProgressListener.STATE_START) &&
+ (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
+
+ let blockedByParentalControls = false;
+ // If it is a failed download, aRequest.responseStatus doesn't exist.
+ // (missing file on the server, network failure to download)
+ try {
+ // If the request's response has been blocked by Windows Parental Controls
+ // with an HTTP 450 error code, we must cancel the request synchronously.
+ blockedByParentalControls = aRequest instanceof Ci.nsIHttpChannel &&
+ aRequest.responseStatus == 450;
+ } catch (e) {
+ if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ aRequest.cancel(Cr.NS_BINDING_ABORTED);
+ }
+ }
+
+ if (blockedByParentalControls) {
+ aRequest.cancel(Cr.NS_BINDING_ABORTED);
+ }
+
+ // The main request has just started. Wait for the associated Download
+ // object to be available before notifying.
+ this._deferDownload.promise.then(download => {
+ // If the request was blocked, now that we have the download object we
+ // should set a flag that can be retrieved later when handling the
+ // cancellation so that the proper error can be thrown.
+ if (blockedByParentalControls) {
+ download._blockedByParentalControls = true;
+ }
+
+ download.saver.onTransferStarted(
+ aRequest,
+ this._cancelable instanceof Ci.nsIHelperAppLauncher);
+
+ // To handle asynchronous cancellation properly, we should hook up the
+ // handler only after we have been notified that the main request
+ // started. We will wait until the main request stopped before
+ // notifying that the download has been canceled. Since the request has
+ // not completed yet, deferCanceled is guaranteed to be set.
+ return download.saver.deferCanceled.promise.then(() => {
+ // Only cancel if the object executing the download is still running.
+ if (this._cancelable && !this._componentFailed) {
+ this._cancelable.cancel(Cr.NS_ERROR_ABORT);
+ if (this._cancelable instanceof Ci.nsIWebBrowserPersist) {
+ // This component will not send the STATE_STOP notification.
+ download.saver.onTransferFinished(aRequest, Cr.NS_ERROR_ABORT);
+ this._cancelable = null;
+ }
+ }
+ });
+ }).then(null, Cu.reportError);
+ } else if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) &&
+ (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
+ // The last file has been received, or the download failed. Wait for the
+ // associated Download object to be available before notifying.
+ this._deferDownload.promise.then(download => {
+ // At this point, the hash has been set and we need to copy it to the
+ // DownloadSaver.
+ if (Components.isSuccessCode(aStatus)) {
+ download.saver.setSha256Hash(this._sha256Hash);
+ download.saver.setSignatureInfo(this._signatureInfo);
+ download.saver.setRedirects(this._redirects);
+ }
+ download.saver.onTransferFinished(aRequest, aStatus);
+ }).then(null, Cu.reportError);
+
+ // Release the reference to the component executing the download.
+ this._cancelable = null;
+ }
+ },
+
+ onProgressChange: function DLT_onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress)
+ {
+ this.onProgressChange64(aWebProgress, aRequest, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ },
+
+ onLocationChange: function () { },
+
+ onStatusChange: function DLT_onStatusChange(aWebProgress, aRequest, aStatus,
+ aMessage)
+ {
+ // The status change may optionally be received in addition to the state
+ // change, but if no network request actually started, it is possible that
+ // we only receive a status change with an error status code.
+ if (!Components.isSuccessCode(aStatus)) {
+ this._componentFailed = true;
+
+ // Wait for the associated Download object to be available.
+ this._deferDownload.promise.then(function DLT_OSC_onDownload(aDownload) {
+ aDownload.saver.onTransferFinished(aRequest, aStatus);
+ }).then(null, Cu.reportError);
+ }
+ },
+
+ onSecurityChange: function () { },
+
+ // nsIWebProgressListener2
+
+ onProgressChange64: function DLT_onProgressChange64(aWebProgress, aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress)
+ {
+ // Wait for the associated Download object to be available.
+ this._deferDownload.promise.then(function DLT_OPC64_onDownload(aDownload) {
+ aDownload.saver.onProgressBytes(aCurTotalProgress, aMaxTotalProgress);
+ }).then(null, Cu.reportError);
+ },
+
+ onRefreshAttempted: function DLT_onRefreshAttempted(aWebProgress, aRefreshURI,
+ aMillis, aSameURI)
+ {
+ // Indicate that refreshes and redirects are allowed by default. However,
+ // note that download components don't usually call this method at all.
+ return true;
+ },
+
+ // nsITransfer
+
+ init: function DLT_init(aSource, aTarget, aDisplayName, aMIMEInfo, aStartTime,
+ aTempFile, aCancelable, aIsPrivate)
+ {
+ this._cancelable = aCancelable;
+
+ let launchWhenSucceeded = false, contentType = null, launcherPath = null;
+
+ if (aMIMEInfo instanceof Ci.nsIMIMEInfo) {
+ launchWhenSucceeded =
+ aMIMEInfo.preferredAction != Ci.nsIMIMEInfo.saveToDisk;
+ contentType = aMIMEInfo.type;
+
+ let appHandler = aMIMEInfo.preferredApplicationHandler;
+ if (aMIMEInfo.preferredAction == Ci.nsIMIMEInfo.useHelperApp &&
+ appHandler instanceof Ci.nsILocalHandlerApp) {
+ launcherPath = appHandler.executable.path;
+ }
+ }
+
+ // Create a new Download object associated to a DownloadLegacySaver, and
+ // wait for it to be available. This operation may cause the entire
+ // download system to initialize before the object is created.
+ Downloads.createDownload({
+ source: { url: aSource.spec, isPrivate: aIsPrivate },
+ target: { path: aTarget.QueryInterface(Ci.nsIFileURL).file.path,
+ partFilePath: aTempFile && aTempFile.path },
+ saver: "legacy",
+ launchWhenSucceeded: launchWhenSucceeded,
+ contentType: contentType,
+ launcherPath: launcherPath
+ }).then(function DLT_I_onDownload(aDownload) {
+ // Legacy components keep partial data when they use a ".part" file.
+ if (aTempFile) {
+ aDownload.tryToKeepPartialData = true;
+ }
+
+ // Start the download before allowing it to be controlled. Ignore errors.
+ aDownload.start().catch(() => {});
+
+ // Start processing all the other events received through nsITransfer.
+ this._deferDownload.resolve(aDownload);
+
+ // Add the download to the list, allowing it to be seen and canceled.
+ return Downloads.getList(Downloads.ALL).then(list => list.add(aDownload));
+ }.bind(this)).then(null, Cu.reportError);
+ },
+
+ setSha256Hash: function (hash)
+ {
+ this._sha256Hash = hash;
+ },
+
+ setSignatureInfo: function (signatureInfo)
+ {
+ this._signatureInfo = signatureInfo;
+ },
+
+ setRedirects: function (redirects)
+ {
+ this._redirects = redirects;
+ },
+
+ // Private methods and properties
+
+ /**
+ * This deferred object contains a promise that is resolved with the Download
+ * object associated with this nsITransfer instance, when it is available.
+ */
+ _deferDownload: null,
+
+ /**
+ * Reference to the component that is executing the download. This component
+ * allows cancellation through its nsICancelable interface.
+ */
+ _cancelable: null,
+
+ /**
+ * Indicates that the component that executes the download has notified a
+ * failure condition. In this case, we should never use the component methods
+ * that cancel the download.
+ */
+ _componentFailed: false,
+
+ /**
+ * Save the SHA-256 hash in raw bytes of the downloaded file.
+ */
+ _sha256Hash: null,
+
+ /**
+ * Save the signature info in a serialized protobuf of the downloaded file.
+ */
+ _signatureInfo: null,
+};
+
+// Module
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadLegacyTransfer]);
diff --git a/components/jsdownloads/src/DownloadList.jsm b/components/jsdownloads/src/DownloadList.jsm
new file mode 100644
index 000000000..89f0c101f
--- /dev/null
+++ b/components/jsdownloads/src/DownloadList.jsm
@@ -0,0 +1,558 @@
+/* -*- 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/. */
+
+/**
+ * This file includes the following constructors and global objects:
+ *
+ * DownloadList
+ * Represents a collection of Download objects that can be viewed and managed by
+ * the user interface, and persisted across sessions.
+ *
+ * DownloadCombinedList
+ * Provides a unified, unordered list combining public and private downloads.
+ *
+ * DownloadSummary
+ * Provides an aggregated view on the contents of a DownloadList.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadList",
+ "DownloadCombinedList",
+ "DownloadSummary",
+];
+
+// 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, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+// DownloadList
+
+/**
+ * Represents a collection of Download objects that can be viewed and managed by
+ * the user interface, and persisted across sessions.
+ */
+this.DownloadList = function ()
+{
+ this._downloads = [];
+ this._views = new Set();
+}
+
+this.DownloadList.prototype = {
+ /**
+ * Array of Download objects currently in the list.
+ */
+ _downloads: null,
+
+ /**
+ * Retrieves a snapshot of the downloads that are currently in the list. The
+ * returned array does not change when downloads are added or removed, though
+ * the Download objects it contains are still updated in real time.
+ *
+ * @return {Promise}
+ * @resolves An array of Download objects.
+ * @rejects JavaScript exception.
+ */
+ getAll: function DL_getAll() {
+ return Promise.resolve(Array.slice(this._downloads, 0));
+ },
+
+ /**
+ * Adds a new download to the end of the items list.
+ *
+ * @note When a download is added to the list, its "onchange" event is
+ * registered by the list, thus it cannot be used to monitor the
+ * download. To receive change notifications for downloads that are
+ * added to the list, use the addView method to register for
+ * onDownloadChanged notifications.
+ *
+ * @param aDownload
+ * The Download object to add.
+ *
+ * @return {Promise}
+ * @resolves When the download has been added.
+ * @rejects JavaScript exception.
+ */
+ add: function DL_add(aDownload) {
+ this._downloads.push(aDownload);
+ aDownload.onchange = this._change.bind(this, aDownload);
+ this._notifyAllViews("onDownloadAdded", aDownload);
+
+ return Promise.resolve();
+ },
+
+ /**
+ * Removes a download from the list. If the download was already removed,
+ * this method has no effect.
+ *
+ * This method does not change the state of the download, to allow adding it
+ * to another list, or control it directly. If you want to dispose of the
+ * download object, you should cancel it afterwards, and remove any partially
+ * downloaded data if needed.
+ *
+ * @param aDownload
+ * The Download object to remove.
+ *
+ * @return {Promise}
+ * @resolves When the download has been removed.
+ * @rejects JavaScript exception.
+ */
+ remove: function DL_remove(aDownload) {
+ let index = this._downloads.indexOf(aDownload);
+ if (index != -1) {
+ this._downloads.splice(index, 1);
+ aDownload.onchange = null;
+ this._notifyAllViews("onDownloadRemoved", aDownload);
+ }
+
+ return Promise.resolve();
+ },
+
+ /**
+ * This function is called when "onchange" events of downloads occur.
+ *
+ * @param aDownload
+ * The Download object that changed.
+ */
+ _change: function DL_change(aDownload) {
+ this._notifyAllViews("onDownloadChanged", aDownload);
+ },
+
+ /**
+ * Set of currently registered views.
+ */
+ _views: null,
+
+ /**
+ * Adds a view that will be notified of changes to downloads. The newly added
+ * view will receive onDownloadAdded notifications for all the downloads that
+ * are already in the list.
+ *
+ * @param aView
+ * The view object to add. The following methods may be defined:
+ * {
+ * onDownloadAdded: function (aDownload) {
+ * // Called after aDownload is added to the end of the list.
+ * },
+ * onDownloadChanged: function (aDownload) {
+ * // Called after the properties of aDownload change.
+ * },
+ * onDownloadRemoved: function (aDownload) {
+ * // Called after aDownload is removed from the list.
+ * },
+ * }
+ *
+ * @return {Promise}
+ * @resolves When the view has been registered and all the onDownloadAdded
+ * notifications for the existing downloads have been sent.
+ * @rejects JavaScript exception.
+ */
+ addView: function DL_addView(aView)
+ {
+ this._views.add(aView);
+
+ if ("onDownloadAdded" in aView) {
+ for (let download of this._downloads) {
+ try {
+ aView.onDownloadAdded(download);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ }
+
+ return Promise.resolve();
+ },
+
+ /**
+ * Removes a view that was previously added using addView.
+ *
+ * @param aView
+ * The view object to remove.
+ *
+ * @return {Promise}
+ * @resolves When the view has been removed. At this point, the removed view
+ * will not receive any more notifications.
+ * @rejects JavaScript exception.
+ */
+ removeView: function DL_removeView(aView)
+ {
+ this._views.delete(aView);
+
+ return Promise.resolve();
+ },
+
+ /**
+ * Notifies all the views of a download addition, change, or removal.
+ *
+ * @param aMethodName
+ * String containing the name of the method to call on the view.
+ * @param aDownload
+ * The Download object that changed.
+ */
+ _notifyAllViews: function (aMethodName, aDownload) {
+ for (let view of this._views) {
+ try {
+ if (aMethodName in view) {
+ view[aMethodName](aDownload);
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ },
+
+ /**
+ * Removes downloads from the list that have finished, have failed, or have
+ * been canceled without keeping partial data. A filter function may be
+ * specified to remove only a subset of those downloads.
+ *
+ * This method finalizes each removed download, ensuring that any partially
+ * downloaded data associated with it is also removed.
+ *
+ * @param aFilterFn
+ * The filter function is called with each download as its only
+ * argument, and should return true to remove the download and false
+ * to keep it. This parameter may be null or omitted to have no
+ * additional filter.
+ */
+ removeFinished: function DL_removeFinished(aFilterFn) {
+ Task.spawn(function* () {
+ let list = yield this.getAll();
+ for (let download of list) {
+ // Remove downloads that have been canceled, even if the cancellation
+ // operation hasn't completed yet so we don't check "stopped" here.
+ // Failed downloads with partial data are also removed.
+ if (download.stopped && (!download.hasPartialData || download.error) &&
+ (!aFilterFn || aFilterFn(download))) {
+ // Remove the download first, so that the views don't get the change
+ // notifications that may occur during finalization.
+ yield this.remove(download);
+ // Ensure that the download is stopped and no partial data is kept.
+ // This works even if the download state has changed meanwhile. We
+ // don't need to wait for the procedure to be complete before
+ // processing the other downloads in the list.
+ download.finalize(true).then(null, Cu.reportError);
+ }
+ }
+ }.bind(this)).then(null, Cu.reportError);
+ },
+};
+
+// DownloadCombinedList
+
+/**
+ * Provides a unified, unordered list combining public and private downloads.
+ *
+ * Download objects added to this list are also added to one of the two
+ * underlying lists, based on their "source.isPrivate" property. Views on this
+ * list will receive notifications for both public and private downloads.
+ *
+ * @param aPublicList
+ * Underlying DownloadList containing public downloads.
+ * @param aPrivateList
+ * Underlying DownloadList containing private downloads.
+ */
+this.DownloadCombinedList = function (aPublicList, aPrivateList)
+{
+ DownloadList.call(this);
+ this._publicList = aPublicList;
+ this._privateList = aPrivateList;
+ aPublicList.addView(this).then(null, Cu.reportError);
+ aPrivateList.addView(this).then(null, Cu.reportError);
+}
+
+this.DownloadCombinedList.prototype = {
+ __proto__: DownloadList.prototype,
+
+ /**
+ * Underlying DownloadList containing public downloads.
+ */
+ _publicList: null,
+
+ /**
+ * Underlying DownloadList containing private downloads.
+ */
+ _privateList: null,
+
+ /**
+ * Adds a new download to the end of the items list.
+ *
+ * @note When a download is added to the list, its "onchange" event is
+ * registered by the list, thus it cannot be used to monitor the
+ * download. To receive change notifications for downloads that are
+ * added to the list, use the addView method to register for
+ * onDownloadChanged notifications.
+ *
+ * @param aDownload
+ * The Download object to add.
+ *
+ * @return {Promise}
+ * @resolves When the download has been added.
+ * @rejects JavaScript exception.
+ */
+ add: function (aDownload)
+ {
+ if (aDownload.source.isPrivate) {
+ return this._privateList.add(aDownload);
+ }
+ return this._publicList.add(aDownload);
+ },
+
+ /**
+ * Removes a download from the list. If the download was already removed,
+ * this method has no effect.
+ *
+ * This method does not change the state of the download, to allow adding it
+ * to another list, or control it directly. If you want to dispose of the
+ * download object, you should cancel it afterwards, and remove any partially
+ * downloaded data if needed.
+ *
+ * @param aDownload
+ * The Download object to remove.
+ *
+ * @return {Promise}
+ * @resolves When the download has been removed.
+ * @rejects JavaScript exception.
+ */
+ remove: function (aDownload)
+ {
+ if (aDownload.source.isPrivate) {
+ return this._privateList.remove(aDownload);
+ }
+ return this._publicList.remove(aDownload);
+ },
+
+ // DownloadList view
+
+ onDownloadAdded: function (aDownload)
+ {
+ this._downloads.push(aDownload);
+ this._notifyAllViews("onDownloadAdded", aDownload);
+ },
+
+ onDownloadChanged: function (aDownload)
+ {
+ this._notifyAllViews("onDownloadChanged", aDownload);
+ },
+
+ onDownloadRemoved: function (aDownload)
+ {
+ let index = this._downloads.indexOf(aDownload);
+ if (index != -1) {
+ this._downloads.splice(index, 1);
+ }
+ this._notifyAllViews("onDownloadRemoved", aDownload);
+ },
+};
+
+// DownloadSummary
+
+/**
+ * Provides an aggregated view on the contents of a DownloadList.
+ */
+this.DownloadSummary = function ()
+{
+ this._downloads = [];
+ this._views = new Set();
+}
+
+this.DownloadSummary.prototype = {
+ /**
+ * Array of Download objects that are currently part of the summary.
+ */
+ _downloads: null,
+
+ /**
+ * Underlying DownloadList whose contents should be summarized.
+ */
+ _list: null,
+
+ /**
+ * This method may be called once to bind this object to a DownloadList.
+ *
+ * Views on the summarized data can be registered before this object is bound
+ * to an actual list. This allows the summary to be used without requiring
+ * the initialization of the DownloadList first.
+ *
+ * @param aList
+ * Underlying DownloadList whose contents should be summarized.
+ *
+ * @return {Promise}
+ * @resolves When the view on the underlying list has been registered.
+ * @rejects JavaScript exception.
+ */
+ bindToList: function (aList)
+ {
+ if (this._list) {
+ throw new Error("bindToList may be called only once.");
+ }
+
+ return aList.addView(this).then(() => {
+ // Set the list reference only after addView has returned, so that we don't
+ // send a notification to our views for each download that is added.
+ this._list = aList;
+ this._onListChanged();
+ });
+ },
+
+ /**
+ * Set of currently registered views.
+ */
+ _views: null,
+
+ /**
+ * Adds a view that will be notified of changes to the summary. The newly
+ * added view will receive an initial onSummaryChanged notification.
+ *
+ * @param aView
+ * The view object to add. The following methods may be defined:
+ * {
+ * onSummaryChanged: function () {
+ * // Called after any property of the summary has changed.
+ * },
+ * }
+ *
+ * @return {Promise}
+ * @resolves When the view has been registered and the onSummaryChanged
+ * notification has been sent.
+ * @rejects JavaScript exception.
+ */
+ addView: function (aView)
+ {
+ this._views.add(aView);
+
+ if ("onSummaryChanged" in aView) {
+ try {
+ aView.onSummaryChanged();
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+
+ return Promise.resolve();
+ },
+
+ /**
+ * Removes a view that was previously added using addView.
+ *
+ * @param aView
+ * The view object to remove.
+ *
+ * @return {Promise}
+ * @resolves When the view has been removed. At this point, the removed view
+ * will not receive any more notifications.
+ * @rejects JavaScript exception.
+ */
+ removeView: function (aView)
+ {
+ this._views.delete(aView);
+
+ return Promise.resolve();
+ },
+
+ /**
+ * Indicates whether all the downloads are currently stopped.
+ */
+ allHaveStopped: true,
+
+ /**
+ * Indicates the total number of bytes to be transferred before completing all
+ * the downloads that are currently in progress.
+ *
+ * For downloads that do not have a known final size, the number of bytes
+ * currently transferred is reported as part of this property.
+ *
+ * This is zero if no downloads are currently in progress.
+ */
+ progressTotalBytes: 0,
+
+ /**
+ * Number of bytes currently transferred as part of all the downloads that are
+ * currently in progress.
+ *
+ * This is zero if no downloads are currently in progress.
+ */
+ progressCurrentBytes: 0,
+
+ /**
+ * This function is called when any change in the list of downloads occurs,
+ * and will recalculate the summary and notify the views in case the
+ * aggregated properties are different.
+ */
+ _onListChanged: function () {
+ let allHaveStopped = true;
+ let progressTotalBytes = 0;
+ let progressCurrentBytes = 0;
+
+ // Recalculate the aggregated state. See the description of the individual
+ // properties for an explanation of the summarization logic.
+ for (let download of this._downloads) {
+ if (!download.stopped) {
+ allHaveStopped = false;
+ progressTotalBytes += download.hasProgress ? download.totalBytes
+ : download.currentBytes;
+ progressCurrentBytes += download.currentBytes;
+ }
+ }
+
+ // Exit now if the properties did not change.
+ if (this.allHaveStopped == allHaveStopped &&
+ this.progressTotalBytes == progressTotalBytes &&
+ this.progressCurrentBytes == progressCurrentBytes) {
+ return;
+ }
+
+ this.allHaveStopped = allHaveStopped;
+ this.progressTotalBytes = progressTotalBytes;
+ this.progressCurrentBytes = progressCurrentBytes;
+
+ // Notify all the views that our properties changed.
+ for (let view of this._views) {
+ try {
+ if ("onSummaryChanged" in view) {
+ view.onSummaryChanged();
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ },
+
+ // DownloadList view
+
+ onDownloadAdded: function (aDownload)
+ {
+ this._downloads.push(aDownload);
+ if (this._list) {
+ this._onListChanged();
+ }
+ },
+
+ onDownloadChanged: function (aDownload)
+ {
+ this._onListChanged();
+ },
+
+ onDownloadRemoved: function (aDownload)
+ {
+ let index = this._downloads.indexOf(aDownload);
+ if (index != -1) {
+ this._downloads.splice(index, 1);
+ }
+ this._onListChanged();
+ },
+};
diff --git a/components/jsdownloads/src/DownloadPlatform.cpp b/components/jsdownloads/src/DownloadPlatform.cpp
new file mode 100644
index 000000000..14277e5bd
--- /dev/null
+++ b/components/jsdownloads/src/DownloadPlatform.cpp
@@ -0,0 +1,191 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "DownloadPlatform.h"
+#include "nsAutoPtr.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsINestedURI.h"
+#include "nsIProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsDirectoryServiceDefs.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+
+#define PREF_BDM_ADDTORECENTDOCS "browser.download.manager.addToRecentDocs"
+
+#ifdef XP_WIN
+#include <shlobj.h>
+#include <urlmon.h>
+#include "nsILocalFileWin.h"
+#endif
+
+#ifdef MOZ_WIDGET_GTK
+#include <gtk/gtk.h>
+#endif
+
+using namespace mozilla;
+
+DownloadPlatform *DownloadPlatform::gDownloadPlatformService = nullptr;
+
+NS_IMPL_ISUPPORTS(DownloadPlatform, mozIDownloadPlatform);
+
+DownloadPlatform* DownloadPlatform::GetDownloadPlatform()
+{
+ if (!gDownloadPlatformService) {
+ gDownloadPlatformService = new DownloadPlatform();
+ }
+
+ NS_ADDREF(gDownloadPlatformService);
+
+#if defined(MOZ_WIDGET_GTK)
+ g_type_init();
+#endif
+
+ return gDownloadPlatformService;
+}
+
+#ifdef MOZ_ENABLE_GIO
+static void gio_set_metadata_done(GObject *source_obj, GAsyncResult *res, gpointer user_data)
+{
+ GError *err = nullptr;
+ g_file_set_attributes_finish(G_FILE(source_obj), res, nullptr, &err);
+ if (err) {
+#ifdef DEBUG
+ NS_DebugBreak(NS_DEBUG_WARNING, "Set file metadata failed: ", err->message, __FILE__, __LINE__);
+#endif
+ g_error_free(err);
+ }
+}
+#endif
+
+nsresult DownloadPlatform::DownloadDone(nsIURI* aSource, nsIURI* aReferrer, nsIFile* aTarget,
+ const nsACString& aContentType, bool aIsPrivate)
+{
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+
+ nsAutoString path;
+ if (aTarget && NS_SUCCEEDED(aTarget->GetPath(path))) {
+ // On Windows and Gtk, add the download to the system's "recent documents"
+ // list, with a pref to disable.
+ {
+ bool addToRecentDocs = Preferences::GetBool(PREF_BDM_ADDTORECENTDOCS);
+ if (addToRecentDocs && !aIsPrivate) {
+#ifdef XP_WIN
+ ::SHAddToRecentDocs(SHARD_PATHW, path.get());
+#elif defined(MOZ_WIDGET_GTK)
+ GtkRecentManager* manager = gtk_recent_manager_get_default();
+
+ gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),
+ nullptr, nullptr);
+ if (uri) {
+ gtk_recent_manager_add_item(manager, uri);
+ g_free(uri);
+ }
+#endif
+ }
+
+#ifdef MOZ_ENABLE_GIO
+ // Use GIO to store the source URI for later display in the file manager.
+ GFile* gio_file = g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());
+ nsCString source_uri;
+ nsresult rv = aSource->GetSpec(source_uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ GFileInfo *file_info = g_file_info_new();
+ g_file_info_set_attribute_string(file_info, "metadata::download-uri", source_uri.get());
+ g_file_set_attributes_async(gio_file,
+ file_info,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ nullptr, gio_set_metadata_done, nullptr);
+ g_object_unref(file_info);
+ g_object_unref(gio_file);
+#endif // MOZ_ENABLE_GIO
+ }
+ }
+#endif // defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+
+ return NS_OK;
+}
+
+nsresult DownloadPlatform::MapUrlToZone(const nsAString& aURL,
+ uint32_t* aZone)
+{
+#ifdef XP_WIN
+ RefPtr<IInternetSecurityManager> inetSecMgr;
+ if (FAILED(CoCreateInstance(CLSID_InternetSecurityManager, NULL,
+ CLSCTX_ALL, IID_IInternetSecurityManager,
+ getter_AddRefs(inetSecMgr)))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ DWORD zone;
+ if (inetSecMgr->MapUrlToZone(PromiseFlatString(aURL).get(),
+ &zone, 0) != S_OK) {
+ return NS_ERROR_UNEXPECTED;
+ } else {
+ *aZone = zone;
+ }
+
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+// Check if a URI is likely to be web-based, by checking its URI flags.
+// If in doubt (e.g. if anything fails during the check) claims things
+// are from the web.
+bool DownloadPlatform::IsURLPossiblyFromWeb(nsIURI* aURI)
+{
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ nsCOMPtr<nsIURI> uri = aURI;
+ if (!ios) {
+ return true;
+ }
+
+ while (uri) {
+ // We're not using nsIIOService::ProtocolHasFlags because it doesn't
+ // take per-URI flags into account. We're also not using
+ // NS_URIChainHasFlags because we're checking for *any* of 3 flags
+ // to be present on *all* of the nested URIs, which it can't do.
+ nsAutoCString scheme;
+ nsresult rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ nsCOMPtr<nsIProtocolHandler> ph;
+ rv = ios->GetProtocolHandler(scheme.get(), getter_AddRefs(ph));
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ uint32_t flags;
+ rv = ph->DoGetProtocolFlags(uri, &flags);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ // If not dangerous to load, not a UI resource and not a local file,
+ // assume this is from the web:
+ if (!(flags & nsIProtocolHandler::URI_DANGEROUS_TO_LOAD) &&
+ !(flags & nsIProtocolHandler::URI_IS_UI_RESOURCE) &&
+ !(flags & nsIProtocolHandler::URI_IS_LOCAL_FILE)) {
+ return true;
+ }
+ // Otherwise, check if the URI is nested, and if so go through
+ // the loop again:
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
+ uri = nullptr;
+ if (nestedURI) {
+ rv = nestedURI->GetInnerURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
diff --git a/components/jsdownloads/src/DownloadPlatform.h b/components/jsdownloads/src/DownloadPlatform.h
new file mode 100644
index 000000000..ef3c7554f
--- /dev/null
+++ b/components/jsdownloads/src/DownloadPlatform.h
@@ -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/. */
+
+#ifndef __DownloadPlatform_h__
+#define __DownloadPlatform_h__
+
+#include "mozIDownloadPlatform.h"
+
+#include "nsCOMPtr.h"
+class nsIURI;
+
+class DownloadPlatform : public mozIDownloadPlatform
+{
+protected:
+
+ virtual ~DownloadPlatform() { }
+
+public:
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZIDOWNLOADPLATFORM
+
+ DownloadPlatform() { }
+
+ static DownloadPlatform *gDownloadPlatformService;
+
+ static DownloadPlatform* GetDownloadPlatform();
+
+private:
+ static bool IsURLPossiblyFromWeb(nsIURI* aURI);
+};
+
+#endif
diff --git a/components/jsdownloads/src/DownloadStore.jsm b/components/jsdownloads/src/DownloadStore.jsm
new file mode 100644
index 000000000..c96681cc6
--- /dev/null
+++ b/components/jsdownloads/src/DownloadStore.jsm
@@ -0,0 +1,202 @@
+/* -*- 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/. */
+
+/**
+ * Handles serialization of Download objects and persistence into a file, so
+ * that the state of downloads can be restored across sessions.
+ *
+ * The file is stored in JSON format, without indentation. With indentation
+ * applied, the file would look like this:
+ *
+ * {
+ * "list": [
+ * {
+ * "source": "http://www.example.com/download.txt",
+ * "target": "/home/user/Downloads/download.txt"
+ * },
+ * {
+ * "source": {
+ * "url": "http://www.example.com/download.txt",
+ * "referrer": "http://www.example.com/referrer.html"
+ * },
+ * "target": "/home/user/Downloads/download-2.txt"
+ * }
+ * ]
+ * }
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadStore",
+];
+
+// 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, "OS",
+ "resource://gre/modules/osfile.jsm")
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function () {
+ return new TextDecoder();
+});
+
+XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function () {
+ return new TextEncoder();
+});
+
+// DownloadStore
+
+/**
+ * Handles serialization of Download objects and persistence into a file, so
+ * that the state of downloads can be restored across sessions.
+ *
+ * @param aList
+ * DownloadList object to be populated or serialized.
+ * @param aPath
+ * String containing the file path where data should be saved.
+ */
+this.DownloadStore = function (aList, aPath)
+{
+ this.list = aList;
+ this.path = aPath;
+}
+
+this.DownloadStore.prototype = {
+ /**
+ * DownloadList object to be populated or serialized.
+ */
+ list: null,
+
+ /**
+ * String containing the file path where data should be saved.
+ */
+ path: "",
+
+ /**
+ * This function is called with a Download object as its first argument, and
+ * should return true if the item should be saved.
+ */
+ onsaveitem: () => true,
+
+ /**
+ * Loads persistent downloads from the file to the list.
+ *
+ * @return {Promise}
+ * @resolves When the operation finished successfully.
+ * @rejects JavaScript exception.
+ */
+ load: function DS_load()
+ {
+ return Task.spawn(function* task_DS_load() {
+ let bytes;
+ try {
+ bytes = yield OS.File.read(this.path);
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error) || !ex.becauseNoSuchFile) {
+ throw ex;
+ }
+ // If the file does not exist, there are no downloads to load.
+ return;
+ }
+
+ let storeData = JSON.parse(gTextDecoder.decode(bytes));
+
+ // Create live downloads based on the static snapshot.
+ for (let downloadData of storeData.list) {
+ try {
+ let download = yield Downloads.createDownload(downloadData);
+ try {
+ if (!download.succeeded && !download.canceled && !download.error) {
+ // Try to restart the download if it was in progress during the
+ // previous session. Ignore errors.
+ download.start().catch(() => {});
+ } else {
+ // If the download was not in progress, try to update the current
+ // progress from disk. This is relevant in case we retained
+ // partially downloaded data.
+ yield download.refresh();
+ }
+ } finally {
+ // Add the download to the list if we succeeded in creating it,
+ // after we have updated its initial state.
+ yield this.list.add(download);
+ }
+ } catch (ex) {
+ // If an item is unrecognized, don't prevent others from being loaded.
+ Cu.reportError(ex);
+ }
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Saves persistent downloads from the list to the file.
+ *
+ * If an error occurs, the previous file is not deleted.
+ *
+ * @return {Promise}
+ * @resolves When the operation finished successfully.
+ * @rejects JavaScript exception.
+ */
+ save: function DS_save()
+ {
+ return Task.spawn(function* task_DS_save() {
+ let downloads = yield this.list.getAll();
+
+ // Take a static snapshot of the current state of all the downloads.
+ let storeData = { list: [] };
+ let atLeastOneDownload = false;
+ for (let download of downloads) {
+ try {
+ if (!this.onsaveitem(download)) {
+ continue;
+ }
+
+ let serializable = download.toSerializable();
+ if (!serializable) {
+ // This item cannot be persisted across sessions.
+ continue;
+ }
+ storeData.list.push(serializable);
+ atLeastOneDownload = true;
+ } catch (ex) {
+ // If an item cannot be converted to a serializable form, don't
+ // prevent others from being saved.
+ Cu.reportError(ex);
+ }
+ }
+
+ if (atLeastOneDownload) {
+ // Create or overwrite the file if there are downloads to save.
+ let bytes = gTextEncoder.encode(JSON.stringify(storeData));
+ yield OS.File.writeAtomic(this.path, bytes,
+ { tmpPath: this.path + ".tmp" });
+ } else {
+ // Remove the file if there are no downloads to save at all.
+ try {
+ yield OS.File.remove(this.path);
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error) ||
+ !(ex.becauseNoSuchFile || ex.becauseAccessDenied)) {
+ throw ex;
+ }
+ // On Windows, we may get an access denied error instead of a no such
+ // file error if the file existed before, and was recently deleted.
+ }
+ }
+ }.bind(this));
+ },
+};
diff --git a/components/jsdownloads/src/DownloadUIHelper.jsm b/components/jsdownloads/src/DownloadUIHelper.jsm
new file mode 100644
index 000000000..860a325d4
--- /dev/null
+++ b/components/jsdownloads/src/DownloadUIHelper.jsm
@@ -0,0 +1,212 @@
+/* -*- 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/. */
+
+/**
+ * Provides functions to handle status and messages in the user interface.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadUIHelper",
+];
+
+// 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, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+const kStringBundleUrl =
+ "chrome://mozapps/locale/downloads/downloads.properties";
+
+const kStringsRequiringFormatting = {
+ fileExecutableSecurityWarning: true,
+ cancelDownloadsOKTextMultiple: true,
+ quitCancelDownloadsAlertMsgMultiple: true,
+ quitCancelDownloadsAlertMsgMacMultiple: true,
+ offlineCancelDownloadsAlertMsgMultiple: true,
+ leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2: true
+};
+
+// DownloadUIHelper
+
+/**
+ * Provides functions to handle status and messages in the user interface.
+ */
+this.DownloadUIHelper = {
+ /**
+ * Returns an object that can be used to display prompts related to downloads.
+ *
+ * The prompts may be either anchored to a specified window, or anchored to
+ * the most recently active window, for example if the prompt is displayed in
+ * response to global notifications that are not associated with any window.
+ *
+ * @param aParent
+ * If specified, should reference the nsIDOMWindow to which the prompts
+ * should be attached. If omitted, the prompts will be attached to the
+ * most recently active window.
+ *
+ * @return A DownloadPrompter object.
+ */
+ getPrompter: function (aParent)
+ {
+ return new DownloadPrompter(aParent || null);
+ },
+};
+
+/**
+ * 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.
+ */
+XPCOMUtils.defineLazyGetter(DownloadUIHelper, "strings", function () {
+ let strings = {};
+ let sb = Services.strings.createBundle(kStringBundleUrl);
+ let enumerator = sb.getSimpleEnumeration();
+ while (enumerator.hasMoreElements()) {
+ let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+ let stringName = string.key;
+ if (stringName in kStringsRequiringFormatting) {
+ strings[stringName] = function () {
+ // Convert "arguments" to a real array before calling into XPCOM.
+ return sb.formatStringFromName(stringName,
+ Array.slice(arguments, 0),
+ arguments.length);
+ };
+ } else {
+ strings[stringName] = string.value;
+ }
+ }
+ return strings;
+});
+
+// DownloadPrompter
+
+/**
+ * Allows displaying prompts related to downloads.
+ *
+ * @param aParent
+ * The nsIDOMWindow to which prompts should be attached, or null to
+ * attach prompts to the most recently active window.
+ */
+this.DownloadPrompter = function (aParent)
+{
+ this._prompter = Services.ww.getNewPrompter(aParent);
+}
+
+this.DownloadPrompter.prototype = {
+ /**
+ * Constants with the different type of prompts.
+ */
+ ON_QUIT: "prompt-on-quit",
+ ON_OFFLINE: "prompt-on-offline",
+ ON_LEAVE_PRIVATE_BROWSING: "prompt-on-leave-private-browsing",
+
+ /**
+ * nsIPrompt instance for displaying messages.
+ */
+ _prompter: null,
+
+ /**
+ * Displays a warning message box that informs that the specified file is
+ * executable, and asks whether the user wants to launch it.
+ *
+ * @param aPath
+ * String containing the full path to the file to be opened.
+ *
+ * @resolves Boolean indicating whether the launch operation can continue.
+ */
+ async confirmLaunchExecutable: function (aPath)
+ {
+ const kPrefConfirmOpenExe = "browser.download.confirmOpenExecutable";
+
+ // Always launch in case we have no prompter implementation.
+ if (!this._prompter) {
+ return true;
+ }
+
+ try {
+ if (!Services.prefs.getBoolPref(kPrefConfirmOpenExe)) {
+ return true;
+ }
+ } catch (ex) {
+ // If the preference does not exist, continue with the prompt.
+ }
+
+ let leafName = OS.Path.basename(aPath);
+
+ let s = DownloadUIHelper.strings;
+ return this._prompter.confirm(s.fileExecutableSecurityWarningTitle,
+ s.fileExecutableSecurityWarning(leafName, leafName));
+ },
+
+ /**
+ * Displays a warning message box that informs that there are active
+ * downloads, and asks whether the user wants to cancel them or not.
+ *
+ * @param aDownloadsCount
+ * The current downloads count.
+ * @param aPromptType
+ * The type of prompt notification depending on the observer.
+ *
+ * @return False to cancel the downloads and continue, true to abort the
+ * operation.
+ */
+ confirmCancelDownloads: function DP_confirmCancelDownload(aDownloadsCount,
+ aPromptType)
+ {
+ // Always continue in case we have no prompter implementation, or if there
+ // are no active downloads.
+ if (!this._prompter || aDownloadsCount <= 0) {
+ return false;
+ }
+
+ let s = DownloadUIHelper.strings;
+ let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
+ (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
+ let okButton = aDownloadsCount > 1 ? s.cancelDownloadsOKTextMultiple(aDownloadsCount)
+ : s.cancelDownloadsOKText;
+ let title, message, cancelButton;
+
+ switch (aPromptType) {
+ case this.ON_QUIT:
+ title = s.quitCancelDownloadsAlertTitle;
+ message = aDownloadsCount > 1
+ ? s.quitCancelDownloadsAlertMsgMultiple(aDownloadsCount)
+ : s.quitCancelDownloadsAlertMsg;
+ cancelButton = s.dontQuitButtonWin;
+ break;
+ case this.ON_OFFLINE:
+ title = s.offlineCancelDownloadsAlertTitle;
+ message = aDownloadsCount > 1
+ ? s.offlineCancelDownloadsAlertMsgMultiple(aDownloadsCount)
+ : s.offlineCancelDownloadsAlertMsg;
+ cancelButton = s.dontGoOfflineButton;
+ break;
+ case this.ON_LEAVE_PRIVATE_BROWSING:
+ title = s.leavePrivateBrowsingCancelDownloadsAlertTitle;
+ message = aDownloadsCount > 1
+ ? s.leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2(aDownloadsCount)
+ : s.leavePrivateBrowsingWindowsCancelDownloadsAlertMsg2;
+ cancelButton = s.dontLeavePrivateBrowsingButton2;
+ break;
+ }
+
+ let rv = this._prompter.confirmEx(title, message, buttonFlags, okButton,
+ cancelButton, null, null, {});
+ return (rv == 1);
+ }
+};
diff --git a/components/jsdownloads/src/Downloads.jsm b/components/jsdownloads/src/Downloads.jsm
new file mode 100644
index 000000000..3f15ec003
--- /dev/null
+++ b/components/jsdownloads/src/Downloads.jsm
@@ -0,0 +1,298 @@
+/* -*- 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/. */
+
+/**
+ * Main entry point to get references to all the back-end objects.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "Downloads",
+];
+
+// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Integration.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/DownloadCore.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadCombinedList",
+ "resource://gre/modules/DownloadList.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadList",
+ "resource://gre/modules/DownloadList.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadSummary",
+ "resource://gre/modules/DownloadList.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
+ "resource://gre/modules/DownloadUIHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+Integration.downloads.defineModuleGetter(this, "DownloadIntegration",
+ "resource://gre/modules/DownloadIntegration.jsm");
+
+// Downloads
+
+/**
+ * This object is exposed directly to the consumers of this JavaScript module,
+ * and provides the only entry point to get references to back-end objects.
+ */
+this.Downloads = {
+ /**
+ * Work on downloads that were not started from a private browsing window.
+ */
+ get PUBLIC() {
+ return "{Downloads.PUBLIC}";
+ },
+ /**
+ * Work on downloads that were started from a private browsing window.
+ */
+ get PRIVATE() {
+ return "{Downloads.PRIVATE}";
+ },
+ /**
+ * Work on both Downloads.PRIVATE and Downloads.PUBLIC downloads.
+ */
+ get ALL() {
+ return "{Downloads.ALL}";
+ },
+
+ /**
+ * Creates a new Download object.
+ *
+ * @param aProperties
+ * Provides the initial properties for the newly created download.
+ * This matches the serializable representation of a Download object.
+ * Some of the most common properties in this object include:
+ * {
+ * source: String containing the URI for the download source.
+ * Alternatively, may be an nsIURI, a DownloadSource object,
+ * or an object with the following properties:
+ * {
+ * url: String containing the URI for the download source.
+ * isPrivate: Indicates whether the download originated from a
+ * private window. If omitted, the download is public.
+ * referrer: String containing the referrer URI of the download
+ * source. Can be omitted or null if no referrer should
+ * be sent or the download source is not HTTP.
+ * },
+ * target: String containing the path of the target file.
+ * Alternatively, may be an nsIFile, a DownloadTarget object,
+ * or an object with the following properties:
+ * {
+ * path: String containing the path of the target file.
+ * },
+ * saver: String representing the class of the download operation.
+ * If omitted, defaults to "copy". Alternatively, may be the
+ * serializable representation of a DownloadSaver object.
+ * }
+ *
+ * @return {Promise}
+ * @resolves The newly created Download object.
+ * @rejects JavaScript exception.
+ */
+ createDownload: function D_createDownload(aProperties)
+ {
+ try {
+ return Promise.resolve(Download.fromSerializable(aProperties));
+ } catch (ex) {
+ return Promise.reject(ex);
+ }
+ },
+
+ /**
+ * Downloads data from a remote network location to a local file.
+ *
+ * This download method does not provide user interface, or the ability to
+ * cancel or restart the download programmatically. For that, you should
+ * obtain a reference to a Download object using the createDownload function.
+ *
+ * Since the download cannot be restarted, any partially downloaded data will
+ * not be kept in case the download fails.
+ *
+ * @param aSource
+ * String containing the URI for the download source. Alternatively,
+ * may be an nsIURI or a DownloadSource object.
+ * @param aTarget
+ * String containing the path of the target file. Alternatively, may
+ * be an nsIFile or a DownloadTarget object.
+ * @param aOptions
+ * An optional object used to control the behavior of this function.
+ * You may pass an object with a subset of the following fields:
+ * {
+ * isPrivate: Indicates whether the download originated from a
+ * private window.
+ * }
+ *
+ * @return {Promise}
+ * @resolves When the download has finished successfully.
+ * @rejects JavaScript exception if the download failed.
+ */
+ fetch: function (aSource, aTarget, aOptions) {
+ return this.createDownload({
+ source: aSource,
+ target: aTarget,
+ }).then(function D_SD_onSuccess(aDownload) {
+ if (aOptions && ("isPrivate" in aOptions)) {
+ aDownload.source.isPrivate = aOptions.isPrivate;
+ }
+ return aDownload.start();
+ });
+ },
+
+ /**
+ * Retrieves the specified type of DownloadList object. There is one download
+ * list for each type, and this method always retrieves a reference to the
+ * same download list when called with the same argument.
+ *
+ * Calling this function may cause the list of public downloads to be reloaded
+ * from the previous session, if it wasn't loaded already.
+ *
+ * @param aType
+ * This can be Downloads.PUBLIC, Downloads.PRIVATE, or Downloads.ALL.
+ * Downloads added to the Downloads.PUBLIC and Downloads.PRIVATE lists
+ * are reflected in the Downloads.ALL list, and downloads added to the
+ * Downloads.ALL list are also added to either the Downloads.PUBLIC or
+ * the Downloads.PRIVATE list based on their properties.
+ *
+ * @return {Promise}
+ * @resolves The requested DownloadList or DownloadCombinedList object.
+ * @rejects JavaScript exception.
+ */
+ getList: function (aType)
+ {
+ if (!this._promiseListsInitialized) {
+ this._promiseListsInitialized = Task.spawn(function* () {
+ let publicList = new DownloadList();
+ let privateList = new DownloadList();
+ let combinedList = new DownloadCombinedList(publicList, privateList);
+
+ try {
+ yield DownloadIntegration.addListObservers(publicList, false);
+ yield DownloadIntegration.addListObservers(privateList, true);
+ yield DownloadIntegration.initializePublicDownloadList(publicList);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ let publicSummary = yield this.getSummary(Downloads.PUBLIC);
+ let privateSummary = yield this.getSummary(Downloads.PRIVATE);
+ let combinedSummary = yield this.getSummary(Downloads.ALL);
+
+ yield publicSummary.bindToList(publicList);
+ yield privateSummary.bindToList(privateList);
+ yield combinedSummary.bindToList(combinedList);
+
+ this._lists[Downloads.PUBLIC] = publicList;
+ this._lists[Downloads.PRIVATE] = privateList;
+ this._lists[Downloads.ALL] = combinedList;
+ }.bind(this));
+ }
+
+ return this._promiseListsInitialized.then(() => this._lists[aType]);
+ },
+
+ /**
+ * Promise resolved when the initialization of the download lists has
+ * completed, or null if initialization has never been requested.
+ */
+ _promiseListsInitialized: null,
+
+ /**
+ * After initialization, this object is populated with one key for each type
+ * of download list that can be returned (Downloads.PUBLIC, Downloads.PRIVATE,
+ * or Downloads.ALL). The values are the DownloadList objects.
+ */
+ _lists: {},
+
+ /**
+ * Retrieves the specified type of DownloadSummary object. There is one
+ * download summary for each type, and this method always retrieves a
+ * reference to the same download summary when called with the same argument.
+ *
+ * Calling this function does not cause the list of public downloads to be
+ * reloaded from the previous session. The summary will behave as if no
+ * downloads are present until the getList method is called.
+ *
+ * @param aType
+ * This can be Downloads.PUBLIC, Downloads.PRIVATE, or Downloads.ALL.
+ *
+ * @return {Promise}
+ * @resolves The requested DownloadList or DownloadCombinedList object.
+ * @rejects JavaScript exception.
+ */
+ getSummary: function (aType)
+ {
+ if (aType != Downloads.PUBLIC && aType != Downloads.PRIVATE &&
+ aType != Downloads.ALL) {
+ throw new Error("Invalid aType argument.");
+ }
+
+ if (!(aType in this._summaries)) {
+ this._summaries[aType] = new DownloadSummary();
+ }
+
+ return Promise.resolve(this._summaries[aType]);
+ },
+
+ /**
+ * This object is populated by the getSummary method with one key for each
+ * type of object that can be returned (Downloads.PUBLIC, Downloads.PRIVATE,
+ * or Downloads.ALL). The values are the DownloadSummary objects.
+ */
+ _summaries: {},
+
+ /**
+ * Returns the system downloads directory asynchronously.
+ * Windows:
+ * User downloads directory
+ * Linux:
+ * XDG user dir spec, with a fallback to Home/Downloads
+ *
+ * @return {Promise}
+ * @resolves The downloads directory string path.
+ */
+ getSystemDownloadsDirectory: function D_getSystemDownloadsDirectory() {
+ return DownloadIntegration.getSystemDownloadsDirectory();
+ },
+
+ /**
+ * Returns the preferred downloads directory based on the user preferences
+ * in the current profile asynchronously.
+ *
+ * @return {Promise}
+ * @resolves The downloads directory string path.
+ */
+ getPreferredDownloadsDirectory: function D_getPreferredDownloadsDirectory() {
+ return DownloadIntegration.getPreferredDownloadsDirectory();
+ },
+
+ /**
+ * Returns the temporary directory where downloads are placed before the
+ * final location is chosen, or while the document is opened temporarily
+ * with an external application. This may or may not be the system temporary
+ * directory, based on the platform asynchronously.
+ *
+ * @return {Promise}
+ * @resolves The downloads directory string path.
+ */
+ getTemporaryDownloadsDirectory: function D_getTemporaryDownloadsDirectory() {
+ return DownloadIntegration.getTemporaryDownloadsDirectory();
+ },
+
+ /**
+ * Constructor for a DownloadError object. When you catch an exception during
+ * a download, you can use this to verify if "ex instanceof Downloads.Error",
+ * before reading the exception properties with the error details.
+ */
+ Error: DownloadError,
+};
diff --git a/components/jsdownloads/src/Downloads.manifest b/components/jsdownloads/src/Downloads.manifest
new file mode 100644
index 000000000..03d4ed4a6
--- /dev/null
+++ b/components/jsdownloads/src/Downloads.manifest
@@ -0,0 +1,2 @@
+component {1b4c85df-cbdd-4bb6-b04e-613caece083c} DownloadLegacy.js
+contract @mozilla.org/transfer;1 {1b4c85df-cbdd-4bb6-b04e-613caece083c}
diff --git a/components/jsdownloads/src/moz.build b/components/jsdownloads/src/moz.build
new file mode 100644
index 000000000..115435f64
--- /dev/null
+++ b/components/jsdownloads/src/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+SOURCES += [
+ 'DownloadPlatform.cpp',
+]
+
+EXTRA_COMPONENTS += [
+ 'DownloadLegacy.js',
+ 'Downloads.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'DownloadCore.jsm',
+ 'DownloadImport.jsm',
+ 'DownloadList.jsm',
+ 'Downloads.jsm',
+ 'DownloadStore.jsm',
+ 'DownloadUIHelper.jsm',
+]
+
+EXTRA_PP_JS_MODULES += [
+ 'DownloadIntegration.jsm',
+]
+
+FINAL_LIBRARY = 'xul'
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
diff --git a/components/jsinspector/moz.build b/components/jsinspector/moz.build
new file mode 100644
index 000000000..a05cddc34
--- /dev/null
+++ b/components/jsinspector/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['public/nsIJSInspector.idl']
+
+SOURCES += ['src/nsJSInspector.cpp']
+
+XPIDL_MODULE = 'jsinspector'
+FINAL_LIBRARY = 'xul'
diff --git a/components/jsinspector/public/nsIJSInspector.idl b/components/jsinspector/public/nsIJSInspector.idl
new file mode 100644
index 000000000..40ad49523
--- /dev/null
+++ b/components/jsinspector/public/nsIJSInspector.idl
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"
+
+/**
+ * Utilities for running nested event loops, asking them to return, and
+ * keeping track of which ones are still running.
+ */
+[scriptable, uuid(6758d0d7-e96a-4c5c-bca8-3bcbe5a15943)]
+interface nsIJSInspector : nsISupports
+{
+ /**
+ * Process the current thread's event queue, calling event handlers until
+ * a call to exitNestedEventLoop, below, asks us to return.
+ *
+ * The name 'enterNestedEventLoop' may be misleading if read too literally.
+ * This method loops calling event handlers until one asks it to stop, and
+ * then returns. So by that point, the nested event loop has been not only
+ * entered, but also run and exited.
+ *
+ * When enterNestedEventLoop calls an event handler, that handler may itself
+ * call enterNestedEventLoop, and so on, so that there may be arbitrarily
+ * many such calls on the stack at the same time.
+ *
+ * We say an enterNestedEventLoop call is "running" if it has not yet been
+ * asked to return, or "stopped" if it has been asked to return once it has
+ * finished processing the current event.
+ *
+ * @param requestor A token of the caller's choice to identify this event
+ * loop.
+ *
+ * @return depth The number of running enterNestedEventLoop calls
+ * remaining, now that this one has returned.
+ *
+ * (Note that not all calls still on the stack are
+ * necessary running; exitNestedEventLoop can ask any
+ * number of enterNestedEventLoop calls to return.)
+ */
+ unsigned long enterNestedEventLoop(in jsval requestor);
+
+ /**
+ * Stop the youngest running enterNestedEventLoop call, asking it to return
+ * once it has finished processing the current event.
+ *
+ * The name 'exitNestedEventLoop' may be misleading if read too literally.
+ * The affected event loop does not return immediately when this method is
+ * called. Rather, this method simply returns to its caller; the affected
+ * loop's current event handler is allowed to run to completion; and then
+ * that loop returns without processing any more events.
+ *
+ * This method ignores loops that have already been stopped, and operates on
+ * the youngest loop that is still running. Each call to this method stops
+ * another running loop.
+ *
+ * @return depth The number of running enterNestedEventLoop calls
+ * remaining, now that one has been stopped.
+ *
+ * @throws NS_ERROR_FAILURE if there are no running enterNestedEventLoop calls.
+ */
+ unsigned long exitNestedEventLoop();
+
+ /**
+ * The number of running enterNestedEventLoop calls on the stack.
+ * This count does not include stopped enterNestedEventLoop calls.
+ */
+ readonly attribute unsigned long eventLoopNestLevel;
+
+ /**
+ * The |requestor| value that was passed to the youngest running
+ * enterNestedEventLoop call.
+ */
+ readonly attribute jsval lastNestRequestor;
+};
diff --git a/components/jsinspector/src/nsJSInspector.cpp b/components/jsinspector/src/nsJSInspector.cpp
new file mode 100644
index 000000000..457e64c08
--- /dev/null
+++ b/components/jsinspector/src/nsJSInspector.cpp
@@ -0,0 +1,146 @@
+/* -*- 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 "nsJSInspector.h"
+#include "nsIXPConnect.h"
+#include "nsThreadUtils.h"
+#include "jsfriendapi.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsArray.h"
+#include "nsTArray.h"
+
+#define JSINSPECTOR_CONTRACTID \
+ "@mozilla.org/jsinspector;1"
+
+#define JSINSPECTOR_CID \
+{ 0xec5aa99c, 0x7abb, 0x4142, { 0xac, 0x5f, 0xaa, 0xb2, 0x41, 0x9e, 0x38, 0xe2 } }
+
+namespace mozilla {
+namespace jsinspector {
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsJSInspector)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsJSInspector)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsIJSInspector)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsJSInspector)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSInspector)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSInspector)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsJSInspector)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsJSInspector)
+ tmp->mRequestors.Clear();
+ tmp->mLastRequestor = JS::NullValue();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsJSInspector)
+ for (uint32_t i = 0; i < tmp->mRequestors.Length(); ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRequestors[i])
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLastRequestor)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+nsJSInspector::nsJSInspector() : mNestedLoopLevel(0), mRequestors(1), mLastRequestor(JS::NullValue())
+{
+}
+
+nsJSInspector::~nsJSInspector()
+{
+ MOZ_ASSERT(mRequestors.Length() == 0);
+ MOZ_ASSERT(mLastRequestor.isNull());
+ mozilla::DropJSObjects(this);
+}
+
+NS_IMETHODIMP
+nsJSInspector::EnterNestedEventLoop(JS::Handle<JS::Value> requestor, uint32_t *out)
+{
+ nsresult rv = NS_OK;
+
+ mLastRequestor = requestor;
+ mRequestors.AppendElement(requestor);
+ mozilla::HoldJSObjects(this);
+
+ mozilla::dom::AutoNoJSAPI nojsapi;
+
+ uint32_t nestLevel = ++mNestedLoopLevel;
+ while (NS_SUCCEEDED(rv) && mNestedLoopLevel >= nestLevel) {
+ if (!NS_ProcessNextEvent())
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ NS_ASSERTION(mNestedLoopLevel <= nestLevel,
+ "nested event didn't unwind properly");
+
+ if (mNestedLoopLevel == nestLevel) {
+ mLastRequestor = mRequestors.ElementAt(--mNestedLoopLevel);
+ }
+
+ *out = mNestedLoopLevel;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsJSInspector::ExitNestedEventLoop(uint32_t *out)
+{
+ if (mNestedLoopLevel > 0) {
+ mRequestors.RemoveElementAt(--mNestedLoopLevel);
+ if (mNestedLoopLevel > 0)
+ mLastRequestor = mRequestors.ElementAt(mNestedLoopLevel - 1);
+ else
+ mLastRequestor = JS::NullValue();
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ *out = mNestedLoopLevel;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJSInspector::GetEventLoopNestLevel(uint32_t *out)
+{
+ *out = mNestedLoopLevel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsJSInspector::GetLastNestRequestor(JS::MutableHandle<JS::Value> out)
+{
+ out.set(mLastRequestor);
+ return NS_OK;
+}
+
+} // namespace jsinspector
+} // namespace mozilla
+
+NS_DEFINE_NAMED_CID(JSINSPECTOR_CID);
+
+static const mozilla::Module::CIDEntry kJSInspectorCIDs[] = {
+ { &kJSINSPECTOR_CID, false, nullptr, mozilla::jsinspector::nsJSInspectorConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kJSInspectorContracts[] = {
+ { JSINSPECTOR_CONTRACTID, &kJSINSPECTOR_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kJSInspectorModule = {
+ mozilla::Module::kVersion,
+ kJSInspectorCIDs,
+ kJSInspectorContracts
+};
+
+NSMODULE_DEFN(jsinspector) = &kJSInspectorModule;
diff --git a/components/jsinspector/src/nsJSInspector.h b/components/jsinspector/src/nsJSInspector.h
new file mode 100644
index 000000000..4e60b0428
--- /dev/null
+++ b/components/jsinspector/src/nsJSInspector.h
@@ -0,0 +1,39 @@
+/* -*- 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 COMPONENTS_JSINSPECTOR_H
+#define COMPONENTS_JSINSPECTOR_H
+
+#include "nsIJSInspector.h"
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTArray.h"
+#include "js/Value.h"
+#include "js/RootingAPI.h"
+
+namespace mozilla {
+namespace jsinspector {
+
+class nsJSInspector final : public nsIJSInspector
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsJSInspector)
+ NS_DECL_NSIJSINSPECTOR
+
+ nsJSInspector();
+
+private:
+ ~nsJSInspector();
+
+ uint32_t mNestedLoopLevel;
+ nsTArray<JS::Heap<JS::Value> > mRequestors;
+ JS::Heap<JS::Value> mLastRequestor;
+};
+
+} // namespace jsinspector
+} // namespace mozilla
+
+#endif
diff --git a/components/lz4/lz4.cpp b/components/lz4/lz4.cpp
new file mode 100644
index 000000000..34d568025
--- /dev/null
+++ b/components/lz4/lz4.cpp
@@ -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/. */
+
+#include "mozilla/Compression.h"
+
+/**
+ * LZ4 is a very fast byte-wise compression algorithm.
+ *
+ * Compared to Google's Snappy it is faster to compress and decompress and
+ * generally produces output of about the same size.
+ *
+ * Compared to zlib it compresses at about 10x the speed, decompresses at about
+ * 4x the speed and produces output of about 1.5x the size.
+ *
+ */
+
+using namespace mozilla::Compression;
+
+/**
+ * Compresses 'inputSize' bytes from 'source' into 'dest'.
+ * Destination buffer must be already allocated,
+ * and must be sized to handle worst cases situations (input data not compressible)
+ * Worst case size evaluation is provided by function LZ4_compressBound()
+ *
+ * @param inputSize is the input size. Max supported value is ~1.9GB
+ * @param return the number of bytes written in buffer dest
+ */
+extern "C" MOZ_EXPORT size_t
+workerlz4_compress(const char* source, size_t inputSize, char* dest) {
+ return LZ4::compress(source, inputSize, dest);
+}
+
+/**
+ * If the source stream is malformed, the function will stop decoding
+ * and return a negative result, indicating the byte position of the
+ * faulty instruction
+ *
+ * This function never writes outside of provided buffers, and never
+ * modifies input buffer.
+ *
+ * note : destination buffer must be already allocated.
+ * its size must be a minimum of 'outputSize' bytes.
+ * @param outputSize is the output size, therefore the original size
+ * @return true/false
+ */
+extern "C" MOZ_EXPORT int
+workerlz4_decompress(const char* source, size_t inputSize,
+ char* dest, size_t maxOutputSize,
+ size_t *bytesOutput) {
+ return LZ4::decompress(source, inputSize,
+ dest, maxOutputSize,
+ bytesOutput);
+}
+
+
+/*
+ Provides the maximum size that LZ4 may output in a "worst case"
+ scenario (input data not compressible) primarily useful for memory
+ allocation of output buffer.
+ note : this function is limited by "int" range (2^31-1)
+
+ @param inputSize is the input size. Max supported value is ~1.9GB
+ @return maximum output size in a "worst case" scenario
+*/
+extern "C" MOZ_EXPORT size_t
+workerlz4_maxCompressedSize(size_t inputSize)
+{
+ return LZ4::maxCompressedSize(inputSize);
+}
+
+
+
diff --git a/components/lz4/lz4.js b/components/lz4/lz4.js
new file mode 100644
index 000000000..8d4ffcf8e
--- /dev/null
+++ b/components/lz4/lz4.js
@@ -0,0 +1,156 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 SharedAll;
+var Primitives;
+if (typeof Components != "undefined") {
+ let Cu = Components.utils;
+ SharedAll = {};
+ Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
+ Cu.import("resource://gre/modules/lz4_internal.js");
+ Cu.import("resource://gre/modules/ctypes.jsm");
+
+ this.EXPORTED_SYMBOLS = [
+ "Lz4"
+ ];
+ this.exports = {};
+} else if (typeof module != "undefined" && typeof require != "undefined") {
+ SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ Primitives = require("resource://gre/modules/lz4_internal.js");
+} else {
+ throw new Error("Please load this module with Component.utils.import or with require()");
+}
+
+const MAGIC_NUMBER = new Uint8Array([109, 111, 122, 76, 122, 52, 48, 0]); // "mozLz4a\0"
+
+const BYTES_IN_SIZE_HEADER = ctypes.uint32_t.size;
+
+const HEADER_SIZE = MAGIC_NUMBER.byteLength + BYTES_IN_SIZE_HEADER;
+
+const EXPECTED_HEADER_TYPE = new ctypes.ArrayType(ctypes.uint8_t, HEADER_SIZE);
+const EXPECTED_SIZE_BUFFER_TYPE = new ctypes.ArrayType(ctypes.uint8_t, BYTES_IN_SIZE_HEADER);
+
+/**
+ * An error during (de)compression
+ *
+ * @param {string} operation The name of the operation ("compress", "decompress")
+ * @param {string} reason A reason to be used when matching errors. Must start
+ * with "because", e.g. "becauseInvalidContent".
+ * @param {string} message A human-readable message.
+ */
+function LZError(operation, reason, message) {
+ SharedAll.OSError.call(this);
+ this.operation = operation;
+ this[reason] = true;
+ this.message = message;
+}
+LZError.prototype = Object.create(SharedAll.OSError);
+LZError.prototype.toString = function toString() {
+ return this.message;
+};
+exports.Error = LZError;
+
+/**
+ * Compress a block to a form suitable for writing to disk.
+ *
+ * Compatibility note: For the moment, we are basing our code on lz4
+ * 1.3, which does not specify a *file* format. Therefore, we define
+ * our own format. Once lz4 defines a complete file format, we will
+ * migrate both |compressFileContent| and |decompressFileContent| to this file
+ * format. For backwards-compatibility, |decompressFileContent| will however
+ * keep the ability to decompress files provided with older versions of
+ * |compressFileContent|.
+ *
+ * Compressed files have the following layout:
+ *
+ * | MAGIC_NUMBER (8 bytes) | content size (uint32_t, little endian) | content, as obtained from lz4_compress |
+ *
+ * @param {TypedArray|void*} buffer The buffer to write to the disk.
+ * @param {object=} options An object that may contain the following fields:
+ * - {number} bytes The number of bytes to read from |buffer|. If |buffer|
+ * is an |ArrayBuffer|, |bytes| defaults to |buffer.byteLength|. If
+ * |buffer| is a |void*|, |bytes| MUST be provided.
+ * @return {Uint8Array} An array of bytes suitable for being written to the
+ * disk.
+ */
+function compressFileContent(array, options = {}) {
+ // Prepare the output array
+ let inputBytes;
+ if (SharedAll.isTypedArray(array) && !(options && "bytes" in options)) {
+ inputBytes = array.byteLength;
+ } else if (options && options.bytes) {
+ inputBytes = options.bytes;
+ } else {
+ throw new TypeError("compressFileContent requires a size");
+ }
+ let maxCompressedSize = Primitives.maxCompressedSize(inputBytes);
+ let outputArray = new Uint8Array(HEADER_SIZE + maxCompressedSize);
+
+ // Compress to output array
+ let payload = new Uint8Array(outputArray.buffer, outputArray.byteOffset + HEADER_SIZE);
+ let compressedSize = Primitives.compress(array, inputBytes, payload);
+
+ // Add headers
+ outputArray.set(MAGIC_NUMBER);
+ let view = new DataView(outputArray.buffer);
+ view.setUint32(MAGIC_NUMBER.byteLength, inputBytes, true);
+
+ return new Uint8Array(outputArray.buffer, 0, HEADER_SIZE + compressedSize);
+}
+exports.compressFileContent = compressFileContent;
+
+function decompressFileContent(array, options = {}) {
+ let bytes = SharedAll.normalizeBufferArgs(array, options.bytes || null);
+ if (bytes < HEADER_SIZE) {
+ throw new LZError("decompress", "becauseLZNoHeader",
+ `Buffer is too short (no header) - Data: ${ options.path || array }`);
+ }
+
+ // Read headers
+ let expectMagicNumber = new DataView(array.buffer, 0, MAGIC_NUMBER.byteLength);
+ for (let i = 0; i < MAGIC_NUMBER.byteLength; ++i) {
+ if (expectMagicNumber.getUint8(i) != MAGIC_NUMBER[i]) {
+ throw new LZError("decompress", "becauseLZWrongMagicNumber",
+ `Invalid header (no magic number) - Data: ${ options.path || array }`);
+ }
+ }
+
+ let sizeBuf = new DataView(array.buffer, MAGIC_NUMBER.byteLength, BYTES_IN_SIZE_HEADER);
+ let expectDecompressedSize =
+ sizeBuf.getUint8(0) +
+ (sizeBuf.getUint8(1) << 8) +
+ (sizeBuf.getUint8(2) << 16) +
+ (sizeBuf.getUint8(3) << 24);
+ if (expectDecompressedSize == 0) {
+ // The underlying algorithm cannot handle a size of 0
+ return new Uint8Array(0);
+ }
+
+ // Prepare the input buffer
+ let inputData = new DataView(array.buffer, HEADER_SIZE);
+
+ // Prepare the output buffer
+ let outputBuffer = new Uint8Array(expectDecompressedSize);
+ let decompressedBytes = (new SharedAll.Type.size_t.implementation(0));
+
+ // Decompress
+ let success = Primitives.decompress(inputData, bytes - HEADER_SIZE,
+ outputBuffer, outputBuffer.byteLength,
+ decompressedBytes.address());
+ if (!success) {
+ throw new LZError("decompress", "becauseLZInvalidContent",
+ `Invalid content: Decompression stopped at ${decompressedBytes.value} - Data: ${ options.path || array }`);
+ }
+ return new Uint8Array(outputBuffer.buffer, outputBuffer.byteOffset, decompressedBytes.value);
+}
+exports.decompressFileContent = decompressFileContent;
+
+if (typeof Components != "undefined") {
+ this.Lz4 = {
+ compressFileContent: compressFileContent,
+ decompressFileContent: decompressFileContent
+ };
+}
diff --git a/components/lz4/lz4_internal.js b/components/lz4/lz4_internal.js
new file mode 100644
index 000000000..d1227da6c
--- /dev/null
+++ b/components/lz4/lz4_internal.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var Primitives = {};
+
+var SharedAll;
+if (typeof Components != "undefined") {
+ let Cu = Components.utils;
+ SharedAll = {};
+ Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
+
+ this.EXPORTED_SYMBOLS = [
+ "Primitives"
+ ];
+ this.Primitives = Primitives;
+ this.exports = {};
+} else if (typeof module != "undefined" && typeof require != "undefined") {
+ SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+} else {
+ throw new Error("Please load this module with Component.utils.import or with require()");
+}
+
+var libxul = new SharedAll.Library("libxul", SharedAll.Constants.Path.libxul);
+var Type = SharedAll.Type;
+
+libxul.declareLazyFFI(Primitives, "compress",
+ "workerlz4_compress",
+ null,
+ /* return*/ Type.size_t,
+ /* const source*/ Type.void_t.in_ptr,
+ /* inputSize*/ Type.size_t,
+ /* dest*/ Type.void_t.out_ptr
+);
+
+libxul.declareLazyFFI(Primitives, "decompress",
+ "workerlz4_decompress",
+ null,
+ /* return*/ Type.int,
+ /* const source*/ Type.void_t.in_ptr,
+ /* inputSize*/ Type.size_t,
+ /* dest*/ Type.void_t.out_ptr,
+ /* maxOutputSize*/ Type.size_t,
+ /* actualOutputSize*/ Type.size_t.out_ptr
+);
+
+libxul.declareLazyFFI(Primitives, "maxCompressedSize",
+ "workerlz4_maxCompressedSize",
+ null,
+ /* return*/ Type.size_t,
+ /* inputSize*/ Type.size_t
+);
+
+if (typeof module != "undefined") {
+ module.exports = {
+ get compress() {
+ return Primitives.compress;
+ },
+ get decompress() {
+ return Primitives.decompress;
+ },
+ get maxCompressedSize() {
+ return Primitives.maxCompressedSize;
+ }
+ };
+}
diff --git a/components/lz4/moz.build b/components/lz4/moz.build
new file mode 100644
index 000000000..470f964d0
--- /dev/null
+++ b/components/lz4/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_JS_MODULES += [
+ 'lz4.js',
+ 'lz4_internal.js',
+]
+
+SOURCES += ['lz4.cpp']
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/mediasniffer/moz.build b/components/mediasniffer/moz.build
new file mode 100644
index 000000000..fcbb1af56
--- /dev/null
+++ b/components/mediasniffer/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXPORTS += ['nsMediaSniffer.h']
+
+UNIFIED_SOURCES += [
+ 'mp3sniff.c',
+ 'nsMediaSniffer.cpp',
+ 'nsMediaSnifferModule.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/mediasniffer/mp3sniff.c b/components/mediasniffer/mp3sniff.c
new file mode 100644
index 000000000..a515d9c58
--- /dev/null
+++ b/components/mediasniffer/mp3sniff.c
@@ -0,0 +1,156 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* MPEG format parsing */
+
+#include "mp3sniff.h"
+
+/* Maximum packet size is 320 kbits/s * 144 / 32 kHz + 1 padding byte */
+#define MP3_MAX_SIZE 1441
+
+typedef struct {
+ int version;
+ int layer;
+ int errp;
+ int bitrate;
+ int freq;
+ int pad;
+ int priv;
+ int mode;
+ int modex;
+ int copyright;
+ int original;
+ int emphasis;
+} mp3_header;
+
+/* Parse the 4-byte header in p and fill in the header struct. */
+static void mp3_parse(const uint8_t *p, mp3_header *header)
+{
+ const int bitrates[2][16] = {
+ /* MPEG version 1 layer 3 bitrates. */
+ {0, 32000, 40000, 48000, 56000, 64000, 80000, 96000,
+ 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0},
+ /* MPEG Version 2 and 2.5 layer 3 bitrates */
+ {0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000,
+ 80000, 96000, 112000, 128000, 144000, 160000, 0} };
+ const int samplerates[4] = {44100, 48000, 32000, 0};
+
+ header->version = (p[1] & 0x18) >> 3;
+ header->layer = 4 - ((p[1] & 0x06) >> 1);
+ header->errp = (p[1] & 0x01);
+
+ header->bitrate = bitrates[(header->version & 1) ? 0 : 1][(p[2] & 0xf0) >> 4];
+ header->freq = samplerates[(p[2] & 0x0c) >> 2];
+ if (header->version == 2) header->freq >>= 1;
+ else if (header->version == 0) header->freq >>= 2;
+ header->pad = (p[2] & 0x02) >> 1;
+ header->priv = (p[2] & 0x01);
+
+ header->mode = (p[3] & 0xc0) >> 6;
+ header->modex = (p[3] & 0x30) >> 4;
+ header->copyright = (p[3] & 0x08) >> 3;
+ header->original = (p[3] & 0x04) >> 2;
+ header->emphasis = (p[3] & 0x03);
+}
+
+/* calculate the size of an mp3 frame from its header */
+static int mp3_framesize(mp3_header *header)
+{
+ int size;
+ int scale;
+
+ if ((header->version & 1) == 0) scale = 72;
+ else scale = 144;
+ size = header->bitrate * scale / header->freq;
+ if (header->pad) size += 1;
+
+ return size;
+}
+
+static int is_mp3(const uint8_t *p, long length) {
+ /* Do we have enough room to see a 4 byte header? */
+ if (length < 4) return 0;
+ /* Do we have a sync pattern? */
+ if (p[0] == 0xff && (p[1] & 0xe0) == 0xe0) {
+ /* Do we have any illegal field values? */
+ if (((p[1] & 0x06) >> 1) == 0) return 0; /* No layer 4 */
+ if (((p[2] & 0xf0) >> 4) == 15) return 0; /* Bitrate can't be 1111 */
+ if (((p[2] & 0x0c) >> 2) == 3) return 0; /* Samplerate can't be 11 */
+ /* Looks like a header. */
+ if ((4 - ((p[1] & 0x06) >> 1)) != 3) return 0; /* Only want level 3 */
+ return 1;
+ }
+ return 0;
+}
+
+/* Identify an ID3 tag based on its header. */
+/* http://id3.org/id3v2.4.0-structure */
+static int is_id3(const uint8_t *p, long length) {
+ /* Do we have enough room to see the header? */
+ if (length < 10) return 0;
+ /* Do we have a sync pattern? */
+ if (p[0] == 'I' && p[1] == 'D' && p[2] == '3') {
+ if (p[3] == 0xff || p[4] == 0xff) return 0; /* Illegal version. */
+ if (p[6] & 0x80 || p[7] & 0x80 ||
+ p[8] & 0x80) return 0; /* Bad length encoding. */
+ /* Looks like an id3 header. */
+ return 1;
+ }
+ return 0;
+}
+
+/* Calculate the size of an id3 tag structure from its header. */
+static int id3_framesize(const uint8_t *p, long length)
+{
+ int size;
+
+ /* Header is 10 bytes. */
+ if (length < 10) {
+ return 0;
+ }
+ /* Frame is header plus declared size. */
+ size = 10 + (p[9] | (p[8] << 7) | (p[7] << 14) | (p[6] << 21));
+
+ return size;
+}
+
+int mp3_sniff(const uint8_t *buf, long length)
+{
+ mp3_header header;
+ const uint8_t *p;
+ long skip;
+ long avail;
+
+ p = buf;
+ avail = length;
+ while (avail >= 4) {
+ if (is_id3(p, avail)) {
+ /* Skip over any id3 tags */
+ skip = id3_framesize(p, avail);
+ p += skip;
+ avail -= skip;
+ } else if (is_mp3(p, avail)) {
+ mp3_parse(p, &header);
+ skip = mp3_framesize(&header);
+ if (skip < 4 || skip + 4 >= avail) {
+ return 0;
+ }
+ p += skip;
+ avail -= skip;
+ /* Check for a second header at the expected offset. */
+ if (is_mp3(p, avail)) {
+ /* Looks like mp3. */
+ return 1;
+ } else {
+ /* No second header. Not mp3. */
+ return 0;
+ }
+ } else {
+ /* No id3 tag or mp3 header. Not mp3. */
+ return 0;
+ }
+ }
+
+ return 0;
+}
diff --git a/components/mediasniffer/mp3sniff.h b/components/mediasniffer/mp3sniff.h
new file mode 100644
index 000000000..5b041a0a2
--- /dev/null
+++ b/components/mediasniffer/mp3sniff.h
@@ -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/. */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int mp3_sniff(const uint8_t *buf, long length);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/components/mediasniffer/nsMediaSniffer.cpp b/components/mediasniffer/nsMediaSniffer.cpp
new file mode 100644
index 000000000..29ba311e6
--- /dev/null
+++ b/components/mediasniffer/nsMediaSniffer.cpp
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 tw=80 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsMediaSniffer.h"
+#include "nsIHttpChannel.h"
+#include "nsString.h"
+#include "nsMimeTypes.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ModuleUtils.h"
+#include "mp3sniff.h"
+#include "nestegg/nestegg.h"
+#include "FlacDemuxer.h"
+
+#include "nsIClassInfoImpl.h"
+#include <algorithm>
+
+// The minimum number of bytes that are needed to attempt to sniff an mp4 file.
+static const unsigned MP4_MIN_BYTES_COUNT = 12;
+// The maximum number of bytes to consider when attempting to sniff a file.
+static const uint32_t MAX_BYTES_SNIFFED = 512;
+// The maximum number of bytes to consider when attempting to sniff for a mp3
+// bitstream.
+// This is 320kbps * 144 / 32kHz + 1 padding byte + 4 bytes of capture pattern.
+static const uint32_t MAX_BYTES_SNIFFED_MP3 = 320 * 144 / 32 + 1 + 4;
+
+NS_IMPL_ISUPPORTS(nsMediaSniffer, nsIContentSniffer)
+
+nsMediaSnifferEntry nsMediaSniffer::sSnifferEntries[] = {
+ // The string OggS, followed by the null byte.
+ PATTERN_ENTRY("\xFF\xFF\xFF\xFF\xFF", "OggS", APPLICATION_OGG),
+ // The string RIFF, followed by four bytes, followed by the string WAVE
+ PATTERN_ENTRY("\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", "RIFF\x00\x00\x00\x00WAVE", AUDIO_WAV),
+ // mp3 with ID3 tags, the string "ID3".
+ PATTERN_ENTRY("\xFF\xFF\xFF", "ID3", AUDIO_MP3),
+ // FLAC with standard header
+ PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "fLaC", AUDIO_FLAC)
+};
+
+// For a complete list of file types, see http://www.ftyps.com/index.html
+nsMediaSnifferEntry sFtypEntries[] = {
+ PATTERN_ENTRY("\xFF\xFF\xFF", "mp4", VIDEO_MP4), // Could be mp41 or mp42.
+ PATTERN_ENTRY("\xFF\xFF\xFF", "avc", VIDEO_MP4), // Could be avc1, avc2, ...
+ PATTERN_ENTRY("\xFF\xFF\xFF", "3gp", VIDEO_3GPP), // Could be 3gp4, 3gp5, ...
+ PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "M4A ", AUDIO_MP4),
+ PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "M4P ", AUDIO_MP4),
+ PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "qt ", VIDEO_QUICKTIME),
+ PATTERN_ENTRY("\xFF\xFF\xFF", "iso", VIDEO_MP4), // Could be isom or iso2.
+ PATTERN_ENTRY("\xFF\xFF\xFF\xFF", "mmp4", VIDEO_MP4),
+};
+
+static bool MatchesBrands(const uint8_t aData[4], nsACString& aSniffedType)
+{
+ for (size_t i = 0; i < mozilla::ArrayLength(sFtypEntries); ++i) {
+ const auto& currentEntry = sFtypEntries[i];
+ bool matched = true;
+ MOZ_ASSERT(currentEntry.mLength <= 4, "Pattern is too large to match brand strings.");
+ for (uint32_t j = 0; j < currentEntry.mLength; ++j) {
+ if ((currentEntry.mMask[j] & aData[j]) != currentEntry.mPattern[j]) {
+ matched = false;
+ break;
+ }
+ }
+ if (matched) {
+ aSniffedType.AssignASCII(currentEntry.mContentType);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// This function implements sniffing algorithm for MP4 family file types,
+// including MP4 (described at http://mimesniff.spec.whatwg.org/#signature-for-mp4),
+// M4A (Apple iTunes audio), and 3GPP.
+static bool MatchesMP4(const uint8_t* aData, const uint32_t aLength, nsACString& aSniffedType)
+{
+ if (aLength <= MP4_MIN_BYTES_COUNT) {
+ return false;
+ }
+ // Conversion from big endian to host byte order.
+ uint32_t boxSize = (uint32_t)(aData[3] | aData[2] << 8 | aData[1] << 16 | aData[0] << 24);
+
+ // Boxsize should be evenly divisible by 4.
+ if (boxSize % 4 || aLength < boxSize) {
+ return false;
+ }
+ // The string "ftyp".
+ if (aData[4] != 0x66 ||
+ aData[5] != 0x74 ||
+ aData[6] != 0x79 ||
+ aData[7] != 0x70) {
+ return false;
+ }
+ if (MatchesBrands(&aData[8], aSniffedType)) {
+ return true;
+ }
+ // Skip minor_version (bytes 12-15).
+ uint32_t bytesRead = 16;
+ while (bytesRead < boxSize) {
+ if (MatchesBrands(&aData[bytesRead], aSniffedType)) {
+ return true;
+ }
+ bytesRead += 4;
+ }
+
+ return false;
+}
+
+static bool MatchesWebM(const uint8_t* aData, const uint32_t aLength)
+{
+ return nestegg_sniff((uint8_t*)aData, aLength) ? true : false;
+}
+
+// This function implements mp3 sniffing based on parsing
+// packet headers and looking for expected boundaries.
+static bool MatchesMP3(const uint8_t* aData, const uint32_t aLength)
+{
+ return mp3_sniff(aData, (long)aLength);
+}
+
+static bool MatchesFLAC(const uint8_t* aData, const uint32_t aLength)
+{
+ return mozilla::FlacDemuxer::FlacSniffer(aData, aLength);
+}
+
+NS_IMETHODIMP
+nsMediaSniffer::GetMIMETypeFromContent(nsIRequest* aRequest,
+ const uint8_t* aData,
+ const uint32_t aLength,
+ nsACString& aSniffedType)
+{
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel) {
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ if (!(loadFlags & nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE)) {
+ // For media, we want to sniff only if the Content-Type is unknown, or if it
+ // is application/octet-stream.
+ nsAutoCString contentType;
+ nsresult rv = channel->GetContentType(contentType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!contentType.IsEmpty() &&
+ !contentType.EqualsLiteral(APPLICATION_OCTET_STREAM) &&
+ !contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ }
+
+ const uint32_t clampedLength = std::min(aLength, MAX_BYTES_SNIFFED);
+
+ for (size_t i = 0; i < mozilla::ArrayLength(sSnifferEntries); ++i) {
+ const nsMediaSnifferEntry& currentEntry = sSnifferEntries[i];
+ if (clampedLength < currentEntry.mLength || currentEntry.mLength == 0) {
+ continue;
+ }
+ bool matched = true;
+ for (uint32_t j = 0; j < currentEntry.mLength; ++j) {
+ if ((currentEntry.mMask[j] & aData[j]) != currentEntry.mPattern[j]) {
+ matched = false;
+ break;
+ }
+ }
+ if (matched) {
+ aSniffedType.AssignASCII(currentEntry.mContentType);
+ return NS_OK;
+ }
+ }
+
+ if (MatchesMP4(aData, clampedLength, aSniffedType)) {
+ return NS_OK;
+ }
+
+ if (MatchesWebM(aData, clampedLength)) {
+ aSniffedType.AssignLiteral(VIDEO_WEBM);
+ return NS_OK;
+ }
+
+ // Bug 950023: 512 bytes are often not enough to sniff for mp3.
+ if (MatchesMP3(aData, std::min(aLength, MAX_BYTES_SNIFFED_MP3))) {
+ aSniffedType.AssignLiteral(AUDIO_MP3);
+ return NS_OK;
+ }
+
+ // Flac frames are generally big, often in excess of 24kB.
+ // Using a size of MAX_BYTES_SNIFFED effectively means that we will only
+ // recognize flac content if it starts with a frame.
+ if (MatchesFLAC(aData, clampedLength)) {
+ aSniffedType.AssignLiteral(AUDIO_FLAC);
+ return NS_OK;
+ }
+
+ // Could not sniff the media type, we are required to set it to
+ // application/octet-stream.
+ aSniffedType.AssignLiteral(APPLICATION_OCTET_STREAM);
+ return NS_ERROR_NOT_AVAILABLE;
+}
diff --git a/components/mediasniffer/nsMediaSniffer.h b/components/mediasniffer/nsMediaSniffer.h
new file mode 100644
index 000000000..45f6ac854
--- /dev/null
+++ b/components/mediasniffer/nsMediaSniffer.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 tw=80 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsMediaSniffer_h
+#define nsMediaSniffer_h
+
+#include "nsIModule.h"
+#include "nsIFactory.h"
+
+#include "nsIComponentManager.h"
+#include "nsIComponentRegistrar.h"
+#include "nsIContentSniffer.h"
+#include "mozilla/Attributes.h"
+
+// ed905ba3-c656-480e-934e-6bc35bd36aff
+#define NS_MEDIA_SNIFFER_CID \
+{0x3fdd6c28, 0x5b87, 0x4e3e, \
+{0x8b, 0x57, 0x8e, 0x83, 0xc2, 0x3c, 0x1a, 0x6d}}
+
+#define NS_MEDIA_SNIFFER_CONTRACTID "@mozilla.org/media/sniffer;1"
+
+#define PATTERN_ENTRY(mask, pattern, contentType) \
+ {(const uint8_t*)mask, (const uint8_t*)pattern, sizeof(mask) - 1, contentType}
+
+struct nsMediaSnifferEntry {
+ const uint8_t* mMask;
+ const uint8_t* mPattern;
+ const uint32_t mLength;
+ const char* mContentType;
+};
+
+class nsMediaSniffer final : public nsIContentSniffer
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTSNIFFER
+
+ private:
+ ~nsMediaSniffer() {}
+
+ static nsMediaSnifferEntry sSnifferEntries[];
+};
+
+#endif
diff --git a/components/mediasniffer/nsMediaSnifferModule.cpp b/components/mediasniffer/nsMediaSnifferModule.cpp
new file mode 100644
index 000000000..f95e61084
--- /dev/null
+++ b/components/mediasniffer/nsMediaSnifferModule.cpp
@@ -0,0 +1,37 @@
+/* -*- 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 "nsMediaSniffer.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMediaSniffer)
+
+NS_DEFINE_NAMED_CID(NS_MEDIA_SNIFFER_CID);
+
+static const mozilla::Module::CIDEntry kMediaSnifferCIDs[] = {
+ { &kNS_MEDIA_SNIFFER_CID, false, nullptr, nsMediaSnifferConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kMediaSnifferContracts[] = {
+ { NS_MEDIA_SNIFFER_CONTRACTID, &kNS_MEDIA_SNIFFER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kMediaSnifferCategories[] = {
+ { "content-sniffing-services", NS_MEDIA_SNIFFER_CONTRACTID, NS_MEDIA_SNIFFER_CONTRACTID},
+ { "net-content-sniffers", NS_MEDIA_SNIFFER_CONTRACTID, NS_MEDIA_SNIFFER_CONTRACTID},
+ { nullptr }
+};
+
+static const mozilla::Module kMediaSnifferModule = {
+ mozilla::Module::kVersion,
+ kMediaSnifferCIDs,
+ kMediaSnifferContracts,
+ kMediaSnifferCategories
+};
+
+NSMODULE_DEFN(nsMediaSnifferModule) = &kMediaSnifferModule;
diff --git a/components/microformats/microformat-shiv.js b/components/microformats/microformat-shiv.js
new file mode 100644
index 000000000..b81e10796
--- /dev/null
+++ b/components/microformats/microformat-shiv.js
@@ -0,0 +1,4523 @@
+/*
+ Modern
+ microformat-shiv - v1.4.0
+ Built: 2016-03-02 10:03 - http://microformat-shiv.com
+ Copyright (c) 2016 Glenn Jones
+ Licensed MIT
+*/
+
+
+var Microformats; // jshint ignore:line
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ define([], factory);
+ } else if (typeof exports === 'object') {
+ module.exports = factory();
+ } else {
+ root.Microformats = factory();
+ }
+}(this, function () {
+
+ var modules = {};
+
+
+ modules.version = '1.4.0';
+ modules.livingStandard = '2015-09-25T12:26:04Z';
+
+ /**
+ * constructor
+ *
+ */
+ modules.Parser = function () {
+ this.rootPrefix = 'h-';
+ this.propertyPrefixes = ['p-', 'dt-', 'u-', 'e-'];
+ this.excludeTags = ['br', 'hr'];
+ };
+
+
+ // create objects incase the v1 map modules don't load
+ modules.maps = (modules.maps)? modules.maps : {};
+ modules.rels = (modules.rels)? modules.rels : {};
+
+
+ modules.Parser.prototype = {
+
+ init: function() {
+ this.rootNode = null;
+ this.document = null;
+ this.options = {
+ 'baseUrl': '',
+ 'filters': [],
+ 'textFormat': 'whitespacetrimmed',
+ 'dateFormat': 'auto', // html5 for testing
+ 'overlappingVersions': false,
+ 'impliedPropertiesByVersion': true,
+ 'parseLatLonGeo': false
+ };
+ this.rootID = 0;
+ this.errors = [];
+ this.noContentErr = 'No options.node or options.html was provided and no document object could be found.';
+ },
+
+
+ /**
+ * internal parse function
+ *
+ * @param {Object} options
+ * @return {Object}
+ */
+ get: function(options) {
+ var out = this.formatEmpty(),
+ data = [],
+ rels;
+
+ this.init();
+ options = (options)? options : {};
+ this.mergeOptions(options);
+ this.getDOMContext( options );
+
+ // if we do not have any context create error
+ if (!this.rootNode || !this.document) {
+ this.errors.push(this.noContentErr);
+ } else {
+
+ // only parse h-* microformats if we need to
+ // this is added to speed up parsing
+ if (this.hasMicroformats(this.rootNode, options)) {
+ this.prepareDOM( options );
+
+ if (this.options.filters.length > 0) {
+ // parse flat list of items
+ var newRootNode = this.findFilterNodes(this.rootNode, this.options.filters);
+ data = this.walkRoot(newRootNode);
+ } else {
+ // parse whole document from root
+ data = this.walkRoot(this.rootNode);
+ }
+
+ out.items = data;
+ // don't clear-up DOM if it was cloned
+ if (modules.domUtils.canCloneDocument(this.document) === false) {
+ this.clearUpDom(this.rootNode);
+ }
+ }
+
+ // find any rels
+ if (this.findRels) {
+ rels = this.findRels(this.rootNode);
+ out.rels = rels.rels;
+ out['rel-urls'] = rels['rel-urls'];
+ }
+
+ }
+
+ if (this.errors.length > 0) {
+ return this.formatError();
+ }
+ return out;
+ },
+
+
+ /**
+ * parse to get parent microformat of passed node
+ *
+ * @param {DOM Node} node
+ * @param {Object} options
+ * @return {Object}
+ */
+ getParent: function(node, options) {
+ this.init();
+ options = (options)? options : {};
+
+ if (node) {
+ return this.getParentTreeWalk(node, options);
+ }
+ this.errors.push(this.noContentErr);
+ return this.formatError();
+ },
+
+
+ /**
+ * get the count of microformats
+ *
+ * @param {DOM Node} rootNode
+ * @return {Int}
+ */
+ count: function( options ) {
+ var out = {},
+ items,
+ classItems,
+ x,
+ i;
+
+ this.init();
+ options = (options)? options : {};
+ this.getDOMContext( options );
+
+ // if we do not have any context create error
+ if (!this.rootNode || !this.document) {
+ return {'errors': [this.noContentErr]};
+ }
+ items = this.findRootNodes( this.rootNode, true );
+ i = items.length;
+ while (i--) {
+ classItems = modules.domUtils.getAttributeList(items[i], 'class');
+ x = classItems.length;
+ while (x--) {
+ // find v2 names
+ if (modules.utils.startWith( classItems[x], 'h-' )) {
+ this.appendCount(classItems[x], 1, out);
+ }
+ // find v1 names
+ for (var key in modules.maps) {
+ // dont double count if v1 and v2 roots are present
+ if (modules.maps[key].root === classItems[x] && classItems.indexOf(key) === -1) {
+ this.appendCount(key, 1, out);
+ }
+ }
+ }
+ }
+ var relCount = this.countRels( this.rootNode );
+ if (relCount > 0) {
+ out.rels = relCount;
+ }
+
+ return out;
+ },
+
+
+ /**
+ * does a node have a class that marks it as a microformats root
+ *
+ * @param {DOM Node} node
+ * @param {Objecte} options
+ * @return {Boolean}
+ */
+ isMicroformat: function( node, options ) {
+ var classes,
+ i;
+
+ if (!node) {
+ return false;
+ }
+
+ // if documemt gets topmost node
+ node = modules.domUtils.getTopMostNode( node );
+
+ // look for h-* microformats
+ classes = this.getUfClassNames(node);
+ if (options && options.filters && modules.utils.isArray(options.filters)) {
+ i = options.filters.length;
+ while (i--) {
+ if (classes.root.indexOf(options.filters[i]) > -1) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return (classes.root.length > 0);
+ },
+
+
+ /**
+ * does a node or its children have microformats
+ *
+ * @param {DOM Node} node
+ * @param {Objecte} options
+ * @return {Boolean}
+ */
+ hasMicroformats: function( node, options ) {
+ var items,
+ i;
+
+ if (!node) {
+ return false;
+ }
+
+ // if browser based documemt get topmost node
+ node = modules.domUtils.getTopMostNode( node );
+
+ // returns all microformat roots
+ items = this.findRootNodes( node, true );
+ if (options && options.filters && modules.utils.isArray(options.filters)) {
+ i = items.length;
+ while (i--) {
+ if ( this.isMicroformat( items[i], options ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return (items.length > 0);
+ },
+
+
+ /**
+ * add a new v1 mapping object to parser
+ *
+ * @param {Array} maps
+ */
+ add: function( maps ) {
+ maps.forEach(function(map) {
+ if (map && map.root && map.name && map.properties) {
+ modules.maps[map.name] = JSON.parse(JSON.stringify(map));
+ }
+ });
+ },
+
+
+ /**
+ * internal parse to get parent microformats by walking up the tree
+ *
+ * @param {DOM Node} node
+ * @param {Object} options
+ * @param {Int} recursive
+ * @return {Object}
+ */
+ getParentTreeWalk: function (node, options, recursive) {
+ options = (options)? options : {};
+
+ // recursive calls
+ if (recursive === undefined) {
+ if (node.parentNode && node.nodeName !== 'HTML') {
+ return this.getParentTreeWalk(node.parentNode, options, true);
+ }
+ return this.formatEmpty();
+ }
+ if (node !== null && node !== undefined && node.parentNode) {
+ if (this.isMicroformat( node, options )) {
+ // if we have a match return microformat
+ options.node = node;
+ return this.get( options );
+ }
+ return this.getParentTreeWalk(node.parentNode, options, true);
+ }
+ return this.formatEmpty();
+ },
+
+
+
+ /**
+ * configures what are the base DOM objects for parsing
+ *
+ * @param {Object} options
+ */
+ getDOMContext: function( options ) {
+ var nodes = modules.domUtils.getDOMContext( options );
+ this.rootNode = nodes.rootNode;
+ this.document = nodes.document;
+ },
+
+
+ /**
+ * prepares DOM before the parse begins
+ *
+ * @param {Object} options
+ * @return {Boolean}
+ */
+ prepareDOM: function( options ) {
+ var baseTag,
+ href;
+
+ // use current document to define baseUrl, try/catch needed for IE10+ error
+ try {
+ if (!options.baseUrl && this.document && this.document.location) {
+ this.options.baseUrl = this.document.location.href;
+ }
+ } catch (e) {
+ // there is no alt action
+ }
+
+
+ // find base tag to set baseUrl
+ baseTag = modules.domUtils.querySelector(this.document, 'base');
+ if (baseTag) {
+ href = modules.domUtils.getAttribute(baseTag, 'href');
+ if (href) {
+ this.options.baseUrl = href;
+ }
+ }
+
+ // get path to rootNode
+ // then clone document
+ // then reset the rootNode to its cloned version in a new document
+ var path,
+ newDocument,
+ newRootNode;
+
+ path = modules.domUtils.getNodePath(this.rootNode);
+ newDocument = modules.domUtils.cloneDocument(this.document);
+ newRootNode = modules.domUtils.getNodeByPath(newDocument, path);
+
+ // check results as early IE fails
+ if (newDocument && newRootNode) {
+ this.document = newDocument;
+ this.rootNode = newRootNode;
+ }
+
+ // add includes
+ if (this.addIncludes) {
+ this.addIncludes( this.document );
+ }
+
+ return (this.rootNode && this.document);
+ },
+
+
+ /**
+ * returns an empty structure with errors
+ *
+ * @return {Object}
+ */
+ formatError: function() {
+ var out = this.formatEmpty();
+ out.errors = this.errors;
+ return out;
+ },
+
+
+ /**
+ * returns an empty structure
+ *
+ * @return {Object}
+ */
+ formatEmpty: function() {
+ return {
+ 'items': [],
+ 'rels': {},
+ 'rel-urls': {}
+ };
+ },
+
+
+ // find microformats of a given type and return node structures
+ findFilterNodes: function(rootNode, filters) {
+ if (modules.utils.isString(filters)) {
+ filters = [filters];
+ }
+ var newRootNode = modules.domUtils.createNode('div'),
+ items = this.findRootNodes(rootNode, true),
+ i = 0,
+ x = 0,
+ y = 0;
+
+ // add v1 names
+ y = filters.length;
+ while (y--) {
+ if (this.getMapping(filters[y])) {
+ var v1Name = this.getMapping(filters[y]).root;
+ filters.push(v1Name);
+ }
+ }
+
+ if (items) {
+ i = items.length;
+ while (x < i) {
+ // append matching nodes into newRootNode
+ y = filters.length;
+ while (y--) {
+ if (modules.domUtils.hasAttributeValue(items[x], 'class', filters[y])) {
+ var clone = modules.domUtils.clone(items[x]);
+ modules.domUtils.appendChild(newRootNode, clone);
+ break;
+ }
+ }
+ x++;
+ }
+ }
+
+ return newRootNode;
+ },
+
+
+ /**
+ * appends data to output object for count
+ *
+ * @param {string} name
+ * @param {Int} count
+ * @param {Object}
+ */
+ appendCount: function(name, count, out) {
+ if (out[name]) {
+ out[name] = out[name] + count;
+ } else {
+ out[name] = count;
+ }
+ },
+
+
+ /**
+ * is the microformats type in the filter list
+ *
+ * @param {Object} uf
+ * @param {Array} filters
+ * @return {Boolean}
+ */
+ shouldInclude: function(uf, filters) {
+ var i;
+
+ if (modules.utils.isArray(filters) && filters.length > 0) {
+ i = filters.length;
+ while (i--) {
+ if (uf.type[0] === filters[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return true;
+ },
+
+
+ /**
+ * finds all microformat roots in a rootNode
+ *
+ * @param {DOM Node} rootNode
+ * @param {Boolean} includeRoot
+ * @return {Array}
+ */
+ findRootNodes: function(rootNode, includeRoot) {
+ var arr = null,
+ out = [],
+ classList = [],
+ items,
+ x,
+ i,
+ y,
+ key;
+
+
+ // build an array of v1 root names
+ for (key in modules.maps) {
+ if (modules.maps.hasOwnProperty(key)) {
+ classList.push(modules.maps[key].root);
+ }
+ }
+
+ // get all elements that have a class attribute
+ includeRoot = (includeRoot) ? includeRoot : false;
+ if (includeRoot && rootNode.parentNode) {
+ arr = modules.domUtils.getNodesByAttribute(rootNode.parentNode, 'class');
+ } else {
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'class');
+ }
+
+ // loop elements that have a class attribute
+ x = 0;
+ i = arr.length;
+ while (x < i) {
+
+ items = modules.domUtils.getAttributeList(arr[x], 'class');
+
+ // loop classes on an element
+ y = items.length;
+ while (y--) {
+ // match v1 root names
+ if (classList.indexOf(items[y]) > -1) {
+ out.push(arr[x]);
+ break;
+ }
+
+ // match v2 root name prefix
+ if (modules.utils.startWith(items[y], 'h-')) {
+ out.push(arr[x]);
+ break;
+ }
+ }
+
+ x++;
+ }
+ return out;
+ },
+
+
+ /**
+ * starts the tree walk to find microformats
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ walkRoot: function(node) {
+ var context = this,
+ children = [],
+ child,
+ classes,
+ items = [],
+ out = [];
+
+ classes = this.getUfClassNames(node);
+ // if it is a root microformat node
+ if (classes && classes.root.length > 0) {
+ items = this.walkTree(node);
+
+ if (items.length > 0) {
+ out = out.concat(items);
+ }
+ } else {
+ // check if there are children and one of the children has a root microformat
+ children = modules.domUtils.getChildren( node );
+ if (children && children.length > 0 && this.findRootNodes(node, true).length > -1) {
+ for (var i = 0; i < children.length; i++) {
+ child = children[i];
+ items = context.walkRoot(child);
+ if (items.length > 0) {
+ out = out.concat(items);
+ }
+ }
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * starts the tree walking for a single microformat
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ walkTree: function(node) {
+ var classes,
+ out = [],
+ obj,
+ itemRootID;
+
+ // loop roots found on one element
+ classes = this.getUfClassNames(node);
+ if (classes && classes.root.length && classes.root.length > 0) {
+
+ this.rootID++;
+ itemRootID = this.rootID;
+ obj = this.createUfObject(classes.root, classes.typeVersion);
+
+ this.walkChildren(node, obj, classes.root, itemRootID, classes);
+ if (this.impliedRules) {
+ this.impliedRules(node, obj, classes);
+ }
+ out.push( this.cleanUfObject(obj) );
+
+
+ }
+ return out;
+ },
+
+
+ /**
+ * finds child properties of microformat
+ *
+ * @param {DOM Node} node
+ * @param {Object} out
+ * @param {String} ufName
+ * @param {Int} rootID
+ * @param {Object} parentClasses
+ */
+ walkChildren: function(node, out, ufName, rootID, parentClasses) {
+ var context = this,
+ children = [],
+ rootItem,
+ itemRootID,
+ value,
+ propertyName,
+ propertyVersion,
+ i,
+ x,
+ y,
+ z,
+ child;
+
+ children = modules.domUtils.getChildren( node );
+
+ y = 0;
+ z = children.length;
+ while (y < z) {
+ child = children[y];
+
+ // get microformat classes for this single element
+ var classes = context.getUfClassNames(child, ufName);
+
+ // a property which is a microformat
+ if (classes.root.length > 0 && classes.properties.length > 0 && !child.addedAsRoot) {
+ // create object with type, property and value
+ rootItem = context.createUfObject(
+ classes.root,
+ classes.typeVersion,
+ modules.text.parse(this.document, child, context.options.textFormat)
+ );
+
+ // add the microformat as an array of properties
+ propertyName = context.removePropPrefix(classes.properties[0][0]);
+
+ // modifies value with "implied value rule"
+ if (parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
+ if (context.impliedValueRule) {
+ out = context.impliedValueRule(out, parentClasses.properties[0][0], classes.properties[0][0], value);
+ }
+ }
+
+ if (out.properties[propertyName]) {
+ out.properties[propertyName].push(rootItem);
+ } else {
+ out.properties[propertyName] = [rootItem];
+ }
+
+ context.rootID++;
+ // used to stop duplication in heavily nested structures
+ child.addedAsRoot = true;
+
+
+ x = 0;
+ i = rootItem.type.length;
+ itemRootID = context.rootID;
+ while (x < i) {
+ context.walkChildren(child, rootItem, rootItem.type, itemRootID, classes);
+ x++;
+ }
+ if (this.impliedRules) {
+ context.impliedRules(child, rootItem, classes);
+ }
+ this.cleanUfObject(rootItem);
+
+ }
+
+ // a property which is NOT a microformat and has not been used for a given root element
+ if (classes.root.length === 0 && classes.properties.length > 0) {
+
+ x = 0;
+ i = classes.properties.length;
+ while (x < i) {
+
+ value = context.getValue(child, classes.properties[x][0], out);
+ propertyName = context.removePropPrefix(classes.properties[x][0]);
+ propertyVersion = classes.properties[x][1];
+
+ // modifies value with "implied value rule"
+ if (parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
+ if (context.impliedValueRule) {
+ out = context.impliedValueRule(out, parentClasses.properties[0][0], classes.properties[x][0], value);
+ }
+ }
+
+ // if we have not added this value into a property with the same name already
+ if (!context.hasRootID(child, rootID, propertyName)) {
+ // check the root and property is the same version or if overlapping versions are allowed
+ if ( context.isAllowedPropertyVersion( out.typeVersion, propertyVersion ) ) {
+ // add the property as an array of properties
+ if (out.properties[propertyName]) {
+ out.properties[propertyName].push(value);
+ } else {
+ out.properties[propertyName] = [value];
+ }
+ // add rootid to node so we can track its use
+ context.appendRootID(child, rootID, propertyName);
+ }
+ }
+
+ x++;
+ }
+
+ context.walkChildren(child, out, ufName, rootID, classes);
+ }
+
+ // if the node has no microformat classes, see if its children have
+ if (classes.root.length === 0 && classes.properties.length === 0) {
+ context.walkChildren(child, out, ufName, rootID, classes);
+ }
+
+ // if the node is a child root add it to the children tree
+ if (classes.root.length > 0 && classes.properties.length === 0) {
+
+ // create object with type, property and value
+ rootItem = context.createUfObject(
+ classes.root,
+ classes.typeVersion,
+ modules.text.parse(this.document, child, context.options.textFormat)
+ );
+
+ // add the microformat as an array of properties
+ if (!out.children) {
+ out.children = [];
+ }
+
+ if (!context.hasRootID(child, rootID, 'child-root')) {
+ out.children.push( rootItem );
+ context.appendRootID(child, rootID, 'child-root');
+ context.rootID++;
+ }
+
+ x = 0;
+ i = rootItem.type.length;
+ itemRootID = context.rootID;
+ while (x < i) {
+ context.walkChildren(child, rootItem, rootItem.type, itemRootID, classes);
+ x++;
+ }
+ if (this.impliedRules) {
+ context.impliedRules(child, rootItem, classes);
+ }
+ context.cleanUfObject( rootItem );
+
+ }
+
+
+
+ y++;
+ }
+
+ },
+
+
+
+
+ /**
+ * gets the value of a property from a node
+ *
+ * @param {DOM Node} node
+ * @param {String} className
+ * @param {Object} uf
+ * @return {String || Object}
+ */
+ getValue: function(node, className, uf) {
+ var value = '';
+
+ if (modules.utils.startWith(className, 'p-')) {
+ value = this.getPValue(node, true);
+ }
+
+ if (modules.utils.startWith(className, 'e-')) {
+ value = this.getEValue(node);
+ }
+
+ if (modules.utils.startWith(className, 'u-')) {
+ value = this.getUValue(node, true);
+ }
+
+ if (modules.utils.startWith(className, 'dt-')) {
+ value = this.getDTValue(node, className, uf, true);
+ }
+ return value;
+ },
+
+
+ /**
+ * gets the value of a node which contains a 'p-' property
+ *
+ * @param {DOM Node} node
+ * @param {Boolean} valueParse
+ * @return {String}
+ */
+ getPValue: function(node, valueParse) {
+ var out = '';
+ if (valueParse) {
+ out = this.getValueClass(node, 'p');
+ }
+
+ if (!out && valueParse) {
+ out = this.getValueTitle(node);
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['data', 'input'], 'value');
+ }
+
+ if (node.name === 'br' || node.name === 'hr') {
+ out = '';
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['img', 'area'], 'alt');
+ }
+
+ if (!out) {
+ out = modules.text.parse(this.document, node, this.options.textFormat);
+ }
+
+ return (out) ? out : '';
+ },
+
+
+ /**
+ * gets the value of a node which contains the 'e-' property
+ *
+ * @param {DOM Node} node
+ * @return {Object}
+ */
+ getEValue: function(node) {
+
+ var out = {value: '', html: ''};
+
+ this.expandURLs(node, 'src', this.options.baseUrl);
+ this.expandURLs(node, 'href', this.options.baseUrl);
+
+ out.value = modules.text.parse(this.document, node, this.options.textFormat);
+ out.html = modules.html.parse(node);
+
+ return out;
+ },
+
+
+ /**
+ * gets the value of a node which contains the 'u-' property
+ *
+ * @param {DOM Node} node
+ * @param {Boolean} valueParse
+ * @return {String}
+ */
+ getUValue: function(node, valueParse) {
+ var out = '';
+ if (valueParse) {
+ out = this.getValueClass(node, 'u');
+ }
+
+ if (!out && valueParse) {
+ out = this.getValueTitle(node);
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['a', 'area'], 'href');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['img', 'audio', 'video', 'source'], 'src');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['object'], 'data');
+ }
+
+ // if we have no protocol separator, turn relative url to absolute url
+ if (out && out !== '' && out.indexOf('://') === -1) {
+ out = modules.url.resolve(out, this.options.baseUrl);
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['data', 'input'], 'value');
+ }
+
+ if (!out) {
+ out = modules.text.parse(this.document, node, this.options.textFormat);
+ }
+
+ return (out) ? out : '';
+ },
+
+
+ /**
+ * gets the value of a node which contains the 'dt-' property
+ *
+ * @param {DOM Node} node
+ * @param {String} className
+ * @param {Object} uf
+ * @param {Boolean} valueParse
+ * @return {String}
+ */
+ getDTValue: function(node, className, uf, valueParse) {
+ var out = '';
+
+ if (valueParse) {
+ out = this.getValueClass(node, 'dt');
+ }
+
+ if (!out && valueParse) {
+ out = this.getValueTitle(node);
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['time', 'ins', 'del'], 'datetime');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+
+ if (!out) {
+ out = modules.domUtils.getAttrValFromTagList(node, ['data', 'input'], 'value');
+ }
+
+ if (!out) {
+ out = modules.text.parse(this.document, node, this.options.textFormat);
+ }
+
+ if (out) {
+ if (modules.dates.isDuration(out)) {
+ // just duration
+ return out;
+ } else if (modules.dates.isTime(out)) {
+ // just time or time+timezone
+ if (uf) {
+ uf.times.push([className, modules.dates.parseAmPmTime(out, this.options.dateFormat)]);
+ }
+ return modules.dates.parseAmPmTime(out, this.options.dateFormat);
+ }
+ // returns a date - microformat profile
+ if (uf) {
+ uf.dates.push([className, new modules.ISODate(out).toString( this.options.dateFormat )]);
+ }
+ return new modules.ISODate(out).toString( this.options.dateFormat );
+ }
+ return '';
+ },
+
+
+ /**
+ * appends a new rootid to a given node
+ *
+ * @param {DOM Node} node
+ * @param {String} id
+ * @param {String} propertyName
+ */
+ appendRootID: function(node, id, propertyName) {
+ if (this.hasRootID(node, id, propertyName) === false) {
+ var rootids = [];
+ if (modules.domUtils.hasAttribute(node, 'rootids')) {
+ rootids = modules.domUtils.getAttributeList(node, 'rootids');
+ }
+ rootids.push('id' + id + '-' + propertyName);
+ modules.domUtils.setAttribute(node, 'rootids', rootids.join(' '));
+ }
+ },
+
+
+ /**
+ * does a given node already have a rootid
+ *
+ * @param {DOM Node} node
+ * @param {String} id
+ * @param {String} propertyName
+ * @return {Boolean}
+ */
+ hasRootID: function(node, id, propertyName) {
+ var rootids = [];
+ if (!modules.domUtils.hasAttribute(node, 'rootids')) {
+ return false;
+ }
+ rootids = modules.domUtils.getAttributeList(node, 'rootids');
+ return (rootids.indexOf('id' + id + '-' + propertyName) > -1);
+ },
+
+
+
+ /**
+ * gets the text of any child nodes with a class value
+ *
+ * @param {DOM Node} node
+ * @param {String} propertyName
+ * @return {String || null}
+ */
+ getValueClass: function(node, propertyType) {
+ var context = this,
+ children = [],
+ out = [],
+ child,
+ x,
+ i;
+
+ children = modules.domUtils.getChildren( node );
+
+ x = 0;
+ i = children.length;
+ while (x < i) {
+ child = children[x];
+ var value = null;
+ if (modules.domUtils.hasAttributeValue(child, 'class', 'value')) {
+ switch (propertyType) {
+ case 'p':
+ value = context.getPValue(child, false);
+ break;
+ case 'u':
+ value = context.getUValue(child, false);
+ break;
+ case 'dt':
+ value = context.getDTValue(child, '', null, false);
+ break;
+ }
+ if (value) {
+ out.push(modules.utils.trim(value));
+ }
+ }
+ x++;
+ }
+ if (out.length > 0) {
+ if (propertyType === 'p') {
+ return modules.text.parseText( this.document, out.join(' '), this.options.textFormat);
+ }
+ if (propertyType === 'u') {
+ return out.join('');
+ }
+ if (propertyType === 'dt') {
+ return modules.dates.concatFragments(out, this.options.dateFormat).toString(this.options.dateFormat);
+ }
+ return undefined;
+ }
+ return null;
+ },
+
+
+ /**
+ * returns a single string of the 'title' attr from all
+ * the child nodes with the class 'value-title'
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ getValueTitle: function(node) {
+ var out = [],
+ items,
+ i,
+ x;
+
+ items = modules.domUtils.getNodesByAttributeValue(node, 'class', 'value-title');
+ x = 0;
+ i = items.length;
+ while (x < i) {
+ if (modules.domUtils.hasAttribute(items[x], 'title')) {
+ out.push(modules.domUtils.getAttribute(items[x], 'title'));
+ }
+ x++;
+ }
+ return out.join('');
+ },
+
+
+ /**
+ * finds out whether a node has h-* class v1 and v2
+ *
+ * @param {DOM Node} node
+ * @return {Boolean}
+ */
+ hasHClass: function(node) {
+ var classes = this.getUfClassNames(node);
+ if (classes.root && classes.root.length > 0) {
+ return true;
+ }
+ return false;
+ },
+
+
+ /**
+ * get both the root and property class names from a node
+ *
+ * @param {DOM Node} node
+ * @param {Array} ufNameArr
+ * @return {Object}
+ */
+ getUfClassNames: function(node, ufNameArr) {
+ var context = this,
+ out = {
+ 'root': [],
+ 'properties': []
+ },
+ classNames,
+ key,
+ items,
+ item,
+ i,
+ x,
+ z,
+ y,
+ map,
+ prop,
+ propName,
+ v2Name,
+ impiedRel,
+ ufName;
+
+ // don't get classes from excluded list of tags
+ if (modules.domUtils.hasTagName(node, this.excludeTags) === false) {
+
+ // find classes for node
+ classNames = modules.domUtils.getAttribute(node, 'class');
+ if (classNames) {
+ items = classNames.split(' ');
+ x = 0;
+ i = items.length;
+ while (x < i) {
+
+ item = modules.utils.trim(items[x]);
+
+ // test for root prefix - v2
+ if (modules.utils.startWith(item, context.rootPrefix)) {
+ if (out.root.indexOf(item) === -1) {
+ out.root.push(item);
+ }
+ out.typeVersion = 'v2';
+ }
+
+ // test for property prefix - v2
+ z = context.propertyPrefixes.length;
+ while (z--) {
+ if (modules.utils.startWith(item, context.propertyPrefixes[z])) {
+ out.properties.push([item, 'v2']);
+ }
+ }
+
+ // test for mapped root classnames v1
+ for (key in modules.maps) {
+ if (modules.maps.hasOwnProperty(key)) {
+ // only add a root once
+ if (modules.maps[key].root === item && out.root.indexOf(key) === -1) {
+ // if root map has subTree set to true
+ // test to see if we should create a property or root
+ if (modules.maps[key].subTree) {
+ out.properties.push(['p-' + modules.maps[key].root, 'v1']);
+ } else {
+ out.root.push(key);
+ if (!out.typeVersion) {
+ out.typeVersion = 'v1';
+ }
+ }
+ }
+ }
+ }
+
+
+ // test for mapped property classnames v1
+ if (ufNameArr) {
+ for (var a = 0; a < ufNameArr.length; a++) {
+ ufName = ufNameArr[a];
+ // get mapped property v1 microformat
+ map = context.getMapping(ufName);
+ if (map) {
+ for (key in map.properties) {
+ if (map.properties.hasOwnProperty(key)) {
+
+ prop = map.properties[key];
+ propName = (prop.map) ? prop.map : 'p-' + key;
+
+ if (key === item) {
+ if (prop.uf) {
+ // loop all the classList make sure
+ // 1. this property is a root
+ // 2. that there is not already an equivalent v2 property i.e. url and u-url on the same element
+ y = 0;
+ while (y < i) {
+ v2Name = context.getV2RootName(items[y]);
+ // add new root
+ if (prop.uf.indexOf(v2Name) > -1 && out.root.indexOf(v2Name) === -1) {
+ out.root.push(v2Name);
+ out.typeVersion = 'v1';
+ }
+ y++;
+ }
+ // only add property once
+ if (out.properties.indexOf(propName) === -1) {
+ out.properties.push([propName, 'v1']);
+ }
+ } else if (out.properties.indexOf(propName) === -1) {
+ out.properties.push([propName, 'v1']);
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ }
+
+ x++;
+
+ }
+ }
+ }
+
+
+ // finds any alt rel=* mappings for a given node/microformat
+ if (ufNameArr && this.findRelImpied) {
+ for (var b = 0; b < ufNameArr.length; b++) {
+ ufName = ufNameArr[b];
+ impiedRel = this.findRelImpied(node, ufName);
+ if (impiedRel && out.properties.indexOf(impiedRel) === -1) {
+ out.properties.push([impiedRel, 'v1']);
+ }
+ }
+ }
+
+
+ // if(out.root.length === 1 && out.properties.length === 1) {
+ // if(out.root[0].replace('h-','') === this.removePropPrefix(out.properties[0][0])) {
+ // out.typeVersion = 'v2';
+ // }
+ // }
+
+ return out;
+ },
+
+
+ /**
+ * given a v1 or v2 root name, return mapping object
+ *
+ * @param {String} name
+ * @return {Object || null}
+ */
+ getMapping: function(name) {
+ var key;
+ for (key in modules.maps) {
+ if (modules.maps[key].root === name || key === name) {
+ return modules.maps[key];
+ }
+ }
+ return null;
+ },
+
+
+ /**
+ * given a v1 root name returns a v2 root name i.e. vcard >>> h-card
+ *
+ * @param {String} name
+ * @return {String || null}
+ */
+ getV2RootName: function(name) {
+ var key;
+ for (key in modules.maps) {
+ if (modules.maps[key].root === name) {
+ return key;
+ }
+ }
+ return null;
+ },
+
+
+ /**
+ * whether a property is the right microformats version for its root type
+ *
+ * @param {String} typeVersion
+ * @param {String} propertyVersion
+ * @return {Boolean}
+ */
+ isAllowedPropertyVersion: function(typeVersion, propertyVersion) {
+ if (this.options.overlappingVersions === true) {
+ return true;
+ }
+ return (typeVersion === propertyVersion);
+ },
+
+
+ /**
+ * creates a blank microformats object
+ *
+ * @param {String} name
+ * @param {String} value
+ * @return {Object}
+ */
+ createUfObject: function(names, typeVersion, value) {
+ var out = {};
+
+ // is more than just whitespace
+ if (value && modules.utils.isOnlyWhiteSpace(value) === false) {
+ out.value = value;
+ }
+ // add type i.e. ["h-card", "h-org"]
+ if (modules.utils.isArray(names)) {
+ out.type = names;
+ } else {
+ out.type = [names];
+ }
+ out.properties = {};
+ // metadata properties for parsing
+ out.typeVersion = typeVersion;
+ out.times = [];
+ out.dates = [];
+ out.altValue = null;
+
+ return out;
+ },
+
+
+ /**
+ * removes unwanted microformats property before output
+ *
+ * @param {Object} microformat
+ */
+ cleanUfObject: function( microformat ) {
+ delete microformat.times;
+ delete microformat.dates;
+ delete microformat.typeVersion;
+ delete microformat.altValue;
+ return microformat;
+ },
+
+
+
+ /**
+ * removes microformat property prefixes from text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ removePropPrefix: function(text) {
+ var i;
+
+ i = this.propertyPrefixes.length;
+ while (i--) {
+ var prefix = this.propertyPrefixes[i];
+ if (modules.utils.startWith(text, prefix)) {
+ text = text.substr(prefix.length);
+ }
+ }
+ return text;
+ },
+
+
+ /**
+ * expands all relative URLs to absolute ones where it can
+ *
+ * @param {DOM Node} node
+ * @param {String} attrName
+ * @param {String} baseUrl
+ */
+ expandURLs: function(node, attrName, baseUrl) {
+ var i,
+ nodes,
+ attr;
+
+ nodes = modules.domUtils.getNodesByAttribute(node, attrName);
+ i = nodes.length;
+ while (i--) {
+ try {
+ // the url parser can blow up if the format is not right
+ attr = modules.domUtils.getAttribute(nodes[i], attrName);
+ if (attr && attr !== '' && baseUrl !== '' && attr.indexOf('://') === -1) {
+ // attr = urlParser.resolve(baseUrl, attr);
+ attr = modules.url.resolve(attr, baseUrl);
+ modules.domUtils.setAttribute(nodes[i], attrName, attr);
+ }
+ } catch (err) {
+ // do nothing - convert only the urls we can, leave the rest as they are
+ }
+ }
+ },
+
+
+
+ /**
+ * merges passed and default options -single level clone of properties
+ *
+ * @param {Object} options
+ */
+ mergeOptions: function(options) {
+ var key;
+ for (key in options) {
+ if (options.hasOwnProperty(key)) {
+ this.options[key] = options[key];
+ }
+ }
+ },
+
+
+ /**
+ * removes all rootid attributes
+ *
+ * @param {DOM Node} rootNode
+ */
+ removeRootIds: function(rootNode) {
+ var arr,
+ i;
+
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'rootids');
+ i = arr.length;
+ while (i--) {
+ modules.domUtils.removeAttribute(arr[i], 'rootids');
+ }
+ },
+
+
+ /**
+ * removes all changes made to the DOM
+ *
+ * @param {DOM Node} rootNode
+ */
+ clearUpDom: function(rootNode) {
+ if (this.removeIncludes) {
+ this.removeIncludes(rootNode);
+ }
+ this.removeRootIds(rootNode);
+ }
+
+
+ };
+
+
+ modules.Parser.prototype.constructor = modules.Parser;
+
+
+ // check parser module is loaded
+ if (modules.Parser) {
+
+ /**
+ * applies "implied rules" microformat output structure i.e. feed-title, name, photo, url and date
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf (microformat output structure)
+ * @param {Object} parentClasses (classes structure)
+ * @param {Boolean} impliedPropertiesByVersion
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedRules = function(node, uf, parentClasses) {
+ var typeVersion = (uf.typeVersion)? uf.typeVersion: 'v2';
+
+ // TEMP: override to allow v1 implied properties while spec changes
+ if (this.options.impliedPropertiesByVersion === false) {
+ typeVersion = 'v2';
+ }
+
+ if (node && uf && uf.properties) {
+ uf = this.impliedBackwardComp( node, uf, parentClasses );
+ if (typeVersion === 'v2') {
+ uf = this.impliedhFeedTitle( uf );
+ uf = this.impliedName( node, uf );
+ uf = this.impliedPhoto( node, uf );
+ uf = this.impliedUrl( node, uf );
+ }
+ uf = this.impliedValue( node, uf, parentClasses );
+ uf = this.impliedDate( uf );
+
+ // TEMP: flagged while spec changes are put forward
+ if (this.options.parseLatLonGeo === true) {
+ uf = this.impliedGeo( uf );
+ }
+ }
+
+ return uf;
+ };
+
+
+ /**
+ * apply implied name rule
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedName = function(node, uf) {
+ // implied name rule
+ /*
+ img.h-x[alt] <img class="h-card" src="glenn.htm" alt="Glenn Jones"></a>
+ area.h-x[alt] <area class="h-card" href="glenn.htm" alt="Glenn Jones"></area>
+ abbr.h-x[title] <abbr class="h-card" title="Glenn Jones"GJ</abbr>
+
+ .h-x>img:only-child[alt]:not[.h-*] <div class="h-card"><a src="glenn.htm" alt="Glenn Jones"></a></div>
+ .h-x>area:only-child[alt]:not[.h-*] <div class="h-card"><area href="glenn.htm" alt="Glenn Jones"></area></div>
+ .h-x>abbr:only-child[title] <div class="h-card"><abbr title="Glenn Jones">GJ</abbr></div>
+
+ .h-x>:only-child>img:only-child[alt]:not[.h-*] <div class="h-card"><span><img src="jane.html" alt="Jane Doe"/></span></div>
+ .h-x>:only-child>area:only-child[alt]:not[.h-*] <div class="h-card"><span><area href="jane.html" alt="Jane Doe"></area></span></div>
+ .h-x>:only-child>abbr:only-child[title] <div class="h-card"><span><abbr title="Jane Doe">JD</abbr></span></div>
+ */
+ var name,
+ value;
+
+ if (!uf.properties.name) {
+ value = this.getImpliedProperty(node, ['img', 'area', 'abbr'], this.getNameAttr);
+ var textFormat = this.options.textFormat;
+ // if no value for tags/properties use text
+ if (!value) {
+ name = [modules.text.parse(this.document, node, textFormat)];
+ } else {
+ name = [modules.text.parseText(this.document, value, textFormat)];
+ }
+ if (name && name[0] !== '') {
+ uf.properties.name = name;
+ }
+ }
+
+ return uf;
+ };
+
+
+ /**
+ * apply implied photo rule
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedPhoto = function(node, uf) {
+ // implied photo rule
+ /*
+ img.h-x[src] <img class="h-card" alt="Jane Doe" src="jane.jpeg"/>
+ object.h-x[data] <object class="h-card" data="jane.jpeg"/>Jane Doe</object>
+ .h-x>img[src]:only-of-type:not[.h-*] <div class="h-card"><img alt="Jane Doe" src="jane.jpeg"/></div>
+ .h-x>object[data]:only-of-type:not[.h-*] <div class="h-card"><object data="jane.jpeg"/>Jane Doe</object></div>
+ .h-x>:only-child>img[src]:only-of-type:not[.h-*] <div class="h-card"><span><img alt="Jane Doe" src="jane.jpeg"/></span></div>
+ .h-x>:only-child>object[data]:only-of-type:not[.h-*] <div class="h-card"><span><object data="jane.jpeg"/>Jane Doe</object></span></div>
+ */
+ var value;
+ if (!uf.properties.photo) {
+ value = this.getImpliedProperty(node, ['img', 'object'], this.getPhotoAttr);
+ if (value) {
+ // relative to absolute URL
+ if (value && value !== '' && this.options.baseUrl !== '' && value.indexOf('://') === -1) {
+ value = modules.url.resolve(value, this.options.baseUrl);
+ }
+ uf.properties.photo = [modules.utils.trim(value)];
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * apply implied URL rule
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedUrl = function(node, uf) {
+ // implied URL rule
+ /*
+ a.h-x[href] <a class="h-card" href="glenn.html">Glenn</a>
+ area.h-x[href] <area class="h-card" href="glenn.html">Glenn</area>
+ .h-x>a[href]:only-of-type:not[.h-*] <div class="h-card" ><a href="glenn.html">Glenn</a><p>...</p></div>
+ .h-x>area[href]:only-of-type:not[.h-*] <div class="h-card" ><area href="glenn.html">Glenn</area><p>...</p></div>
+ */
+ var value;
+ if (!uf.properties.url) {
+ value = this.getImpliedProperty(node, ['a', 'area'], this.getURLAttr);
+ if (value) {
+ // relative to absolute URL
+ if (value && value !== '' && this.options.baseUrl !== '' && value.indexOf('://') === -1) {
+ value = modules.url.resolve(value, this.options.baseUrl);
+ }
+ uf.properties.url = [modules.utils.trim(value)];
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * apply implied date rule - if there is a time only property try to concat it with any date property
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedDate = function(uf) {
+ // implied date rule
+ // http://microformats.org/wiki/value-class-pattern#microformats2_parsers
+ // http://microformats.org/wiki/microformats2-parsing-issues#implied_date_for_dt_properties_both_mf2_and_backcompat
+ var newDate;
+ if (uf.times.length > 0 && uf.dates.length > 0) {
+ newDate = modules.dates.dateTimeUnion(uf.dates[0][1], uf.times[0][1], this.options.dateFormat);
+ uf.properties[this.removePropPrefix(uf.times[0][0])][0] = newDate.toString(this.options.dateFormat);
+ }
+ // clean-up object
+ delete uf.times;
+ delete uf.dates;
+ return uf;
+ };
+
+
+ /**
+ * get an implied property value from pre-defined tag/attriubte combinations
+ *
+ * @param {DOM Node} node
+ * @param {String} tagList (Array of tags from which an implied value can be pulled)
+ * @param {String} getAttrFunction (Function which can extract implied value)
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getImpliedProperty = function(node, tagList, getAttrFunction) {
+ // i.e. img.h-card
+ var value = getAttrFunction(node),
+ descendant,
+ child;
+
+ if (!value) {
+ // i.e. .h-card>img:only-of-type:not(.h-card)
+ descendant = modules.domUtils.getSingleDescendantOfType( node, tagList);
+ if (descendant && this.hasHClass(descendant) === false) {
+ value = getAttrFunction(descendant);
+ }
+ if (node.children.length > 0 ) {
+ // i.e. .h-card>:only-child>img:only-of-type:not(.h-card)
+ child = modules.domUtils.getSingleDescendant(node);
+ if (child && this.hasHClass(child) === false) {
+ descendant = modules.domUtils.getSingleDescendantOfType(child, tagList);
+ if (descendant && this.hasHClass(descendant) === false) {
+ value = getAttrFunction(descendant);
+ }
+ }
+ }
+ }
+
+ return value;
+ };
+
+
+ /**
+ * get an implied name value from a node
+ *
+ * @param {DOM Node} node
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getNameAttr = function(node) {
+ var value = modules.domUtils.getAttrValFromTagList(node, ['img', 'area'], 'alt');
+ if (!value) {
+ value = modules.domUtils.getAttrValFromTagList(node, ['abbr'], 'title');
+ }
+ return value;
+ };
+
+
+ /**
+ * get an implied photo value from a node
+ *
+ * @param {DOM Node} node
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getPhotoAttr = function(node) {
+ var value = modules.domUtils.getAttrValFromTagList(node, ['img'], 'src');
+ if (!value && modules.domUtils.hasAttributeValue(node, 'class', 'include') === false) {
+ value = modules.domUtils.getAttrValFromTagList(node, ['object'], 'data');
+ }
+ return value;
+ };
+
+
+ /**
+ * get an implied photo value from a node
+ *
+ * @param {DOM Node} node
+ * @return {String || null}
+ */
+ modules.Parser.prototype.getURLAttr = function(node) {
+ var value = null;
+ if (modules.domUtils.hasAttributeValue(node, 'class', 'include') === false) {
+
+ value = modules.domUtils.getAttrValFromTagList(node, ['a'], 'href');
+ if (!value) {
+ value = modules.domUtils.getAttrValFromTagList(node, ['area'], 'href');
+ }
+
+ }
+ return value;
+ };
+
+
+ /**
+ *
+ *
+ * @param {DOM Node} node
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedValue = function(node, uf, parentClasses) {
+
+ // intersection of implied name and implied value rules
+ if (uf.properties.name) {
+ if (uf.value && parentClasses.root.length > 0 && parentClasses.properties.length === 1) {
+ uf = this.getAltValue(uf, parentClasses.properties[0][0], 'p-name', uf.properties.name[0]);
+ }
+ }
+
+ // intersection of implied URL and implied value rules
+ if (uf.properties.url) {
+ if (parentClasses && parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
+ uf = this.getAltValue(uf, parentClasses.properties[0][0], 'u-url', uf.properties.url[0]);
+ }
+ }
+
+ // apply alt value
+ if (uf.altValue !== null) {
+ uf.value = uf.altValue.value;
+ }
+ delete uf.altValue;
+
+
+ return uf;
+ };
+
+
+ /**
+ * get alt value based on rules about parent property prefix
+ *
+ * @param {Object} uf
+ * @param {String} parentPropertyName
+ * @param {String} propertyName
+ * @param {String} value
+ * @return {Object}
+ */
+ modules.Parser.prototype.getAltValue = function(uf, parentPropertyName, propertyName, value) {
+ if (uf.value && !uf.altValue) {
+ // first p-name of the h-* child
+ if (modules.utils.startWith(parentPropertyName, 'p-') && propertyName === 'p-name') {
+ uf.altValue = {name: propertyName, value: value};
+ }
+ // if it's an e-* property element
+ if (modules.utils.startWith(parentPropertyName, 'e-') && modules.utils.startWith(propertyName, 'e-')) {
+ uf.altValue = {name: propertyName, value: value};
+ }
+ // if it's an u-* property element
+ if (modules.utils.startWith(parentPropertyName, 'u-') && propertyName === 'u-url') {
+ uf.altValue = {name: propertyName, value: value};
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * if a h-feed does not have a title use the title tag of a page
+ *
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedhFeedTitle = function( uf ) {
+ if (uf.type && uf.type.indexOf('h-feed') > -1) {
+ // has no name property
+ if (uf.properties.name === undefined || uf.properties.name[0] === '' ) {
+ // use the text from the title tag
+ var title = modules.domUtils.querySelector(this.document, 'title');
+ if (title) {
+ uf.properties.name = [modules.domUtils.textContent(title)];
+ }
+ }
+ }
+ return uf;
+ };
+
+
+
+ /**
+ * implied Geo from pattern <abbr class="p-geo" title="37.386013;-122.082932">
+ *
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedGeo = function( uf ) {
+ var geoPair,
+ parts,
+ longitude,
+ latitude,
+ valid = true;
+
+ if (uf.type && uf.type.indexOf('h-geo') > -1) {
+
+ // has no latitude or longitude property
+ if (uf.properties.latitude === undefined || uf.properties.longitude === undefined ) {
+
+ geoPair = (uf.properties.name)? uf.properties.name[0] : null;
+ geoPair = (!geoPair && uf.properties.value)? uf.properties.value : geoPair;
+
+ if (geoPair) {
+ // allow for the use of a ';' as in microformats and also ',' as in Geo URL
+ geoPair = geoPair.replace(';', ',');
+
+ // has sep char
+ if (geoPair.indexOf(',') > -1 ) {
+ parts = geoPair.split(',');
+
+ // only correct if we have two or more parts
+ if (parts.length > 1) {
+
+ // latitude no value outside the range -90 or 90
+ latitude = parseFloat( parts[0] );
+ if (modules.utils.isNumber(latitude) && latitude > 90 || latitude < -90) {
+ valid = false;
+ }
+
+ // longitude no value outside the range -180 to 180
+ longitude = parseFloat( parts[1] );
+ if (modules.utils.isNumber(longitude) && longitude > 180 || longitude < -180) {
+ valid = false;
+ }
+
+ if (valid) {
+ uf.properties.latitude = [latitude];
+ uf.properties.longitude = [longitude];
+ }
+ }
+
+ }
+ }
+ }
+ }
+ return uf;
+ };
+
+
+ /**
+ * if a backwards compat built structure has no properties add name through this.impliedName
+ *
+ * @param {Object} uf
+ * @return {Object}
+ */
+ modules.Parser.prototype.impliedBackwardComp = function(node, uf, parentClasses) {
+
+ // look for pattern in parent classes like "p-geo h-geo"
+ // these are structures built from backwards compat parsing of geo
+ if (parentClasses.root.length === 1 && parentClasses.properties.length === 1) {
+ if (parentClasses.root[0].replace('h-', '') === this.removePropPrefix(parentClasses.properties[0][0])) {
+
+ // if microformat has no properties apply the impliedName rule to get value from containing node
+ // this will get value from html such as <abbr class="geo" title="30.267991;-97.739568">Brighton</abbr>
+ if ( modules.utils.hasProperties(uf.properties) === false ) {
+ uf = this.impliedName( node, uf );
+ }
+ }
+ }
+
+ return uf;
+ };
+
+
+
+ }
+
+
+ // check parser module is loaded
+ if (modules.Parser) {
+
+
+ /**
+ * appends clones of include Nodes into the DOM structure
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.addIncludes = function(rootNode) {
+ this.addAttributeIncludes(rootNode, 'itemref');
+ this.addAttributeIncludes(rootNode, 'headers');
+ this.addClassIncludes(rootNode);
+ };
+
+
+ /**
+ * appends clones of include Nodes into the DOM structure for attribute based includes
+ *
+ * @param {DOM node} rootNode
+ * @param {String} attributeName
+ */
+ modules.Parser.prototype.addAttributeIncludes = function(rootNode, attributeName) {
+ var arr,
+ idList,
+ i,
+ x,
+ z,
+ y;
+
+ arr = modules.domUtils.getNodesByAttribute(rootNode, attributeName);
+ x = 0;
+ i = arr.length;
+ while (x < i) {
+ idList = modules.domUtils.getAttributeList(arr[x], attributeName);
+ if (idList) {
+ z = 0;
+ y = idList.length;
+ while (z < y) {
+ this.apppendInclude(arr[x], idList[z]);
+ z++;
+ }
+ }
+ x++;
+ }
+ };
+
+
+ /**
+ * appends clones of include Nodes into the DOM structure for class based includes
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.addClassIncludes = function(rootNode) {
+ var id,
+ arr,
+ x = 0,
+ i;
+
+ arr = modules.domUtils.getNodesByAttributeValue(rootNode, 'class', 'include');
+ i = arr.length;
+ while (x < i) {
+ id = modules.domUtils.getAttrValFromTagList(arr[x], ['a'], 'href');
+ if (!id) {
+ id = modules.domUtils.getAttrValFromTagList(arr[x], ['object'], 'data');
+ }
+ this.apppendInclude(arr[x], id);
+ x++;
+ }
+ };
+
+
+ /**
+ * appends a clone of an include into another Node using Id
+ *
+ * @param {DOM node} rootNode
+ * @param {Stringe} id
+ */
+ modules.Parser.prototype.apppendInclude = function(node, id) {
+ var include,
+ clone;
+
+ id = modules.utils.trim(id.replace('#', ''));
+ include = modules.domUtils.getElementById(this.document, id);
+ if (include) {
+ clone = modules.domUtils.clone(include);
+ this.markIncludeChildren(clone);
+ modules.domUtils.appendChild(node, clone);
+ }
+ };
+
+
+ /**
+ * adds an attribute marker to all the child microformat roots
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.markIncludeChildren = function(rootNode) {
+ var arr,
+ x,
+ i;
+
+ // loop the array and add the attribute
+ arr = this.findRootNodes(rootNode);
+ x = 0;
+ i = arr.length;
+ modules.domUtils.setAttribute(rootNode, 'data-include', 'true');
+ modules.domUtils.setAttribute(rootNode, 'style', 'display:none');
+ while (x < i) {
+ modules.domUtils.setAttribute(arr[x], 'data-include', 'true');
+ x++;
+ }
+ };
+
+
+ /**
+ * removes all appended include clones from DOM
+ *
+ * @param {DOM node} rootNode
+ */
+ modules.Parser.prototype.removeIncludes = function(rootNode) {
+ var arr,
+ i;
+
+ // remove all the items that were added as includes
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'data-include');
+ i = arr.length;
+ while (i--) {
+ modules.domUtils.removeChild(rootNode, arr[i]);
+ }
+ };
+
+
+ }
+
+
+ // check parser module is loaded
+ if (modules.Parser) {
+
+ /**
+ * finds rel=* structures
+ *
+ * @param {DOM node} rootNode
+ * @return {Object}
+ */
+ modules.Parser.prototype.findRels = function(rootNode) {
+ var out = {
+ 'items': [],
+ 'rels': {},
+ 'rel-urls': {}
+ },
+ x,
+ i,
+ y,
+ z,
+ relList,
+ items,
+ item,
+ value,
+ arr;
+
+ arr = modules.domUtils.getNodesByAttribute(rootNode, 'rel');
+ x = 0;
+ i = arr.length;
+ while (x < i) {
+ relList = modules.domUtils.getAttribute(arr[x], 'rel');
+
+ if (relList) {
+ items = relList.split(' ');
+
+
+ // add rels
+ z = 0;
+ y = items.length;
+ while (z < y) {
+ item = modules.utils.trim(items[z]);
+
+ // get rel value
+ value = modules.domUtils.getAttrValFromTagList(arr[x], ['a', 'area'], 'href');
+ if (!value) {
+ value = modules.domUtils.getAttrValFromTagList(arr[x], ['link'], 'href');
+ }
+
+ // create the key
+ if (!out.rels[item]) {
+ out.rels[item] = [];
+ }
+
+ if (typeof this.options.baseUrl === 'string' && typeof value === 'string') {
+
+ var resolved = modules.url.resolve(value, this.options.baseUrl);
+ // do not add duplicate rels - based on resolved URLs
+ if (out.rels[item].indexOf(resolved) === -1) {
+ out.rels[item].push( resolved );
+ }
+ }
+ z++;
+ }
+
+
+ var url = null;
+ if (modules.domUtils.hasAttribute(arr[x], 'href')) {
+ url = modules.domUtils.getAttribute(arr[x], 'href');
+ if (url) {
+ url = modules.url.resolve(url, this.options.baseUrl );
+ }
+ }
+
+
+ // add to rel-urls
+ var relUrl = this.getRelProperties(arr[x]);
+ relUrl.rels = items;
+ // do not add duplicate rel-urls - based on resolved URLs
+ if (url && out['rel-urls'][url] === undefined) {
+ out['rel-urls'][url] = relUrl;
+ }
+
+
+ }
+ x++;
+ }
+ return out;
+ };
+
+
+ /**
+ * gets the properties of a rel=*
+ *
+ * @param {DOM node} node
+ * @return {Object}
+ */
+ modules.Parser.prototype.getRelProperties = function(node) {
+ var obj = {};
+
+ if (modules.domUtils.hasAttribute(node, 'media')) {
+ obj.media = modules.domUtils.getAttribute(node, 'media');
+ }
+ if (modules.domUtils.hasAttribute(node, 'type')) {
+ obj.type = modules.domUtils.getAttribute(node, 'type');
+ }
+ if (modules.domUtils.hasAttribute(node, 'hreflang')) {
+ obj.hreflang = modules.domUtils.getAttribute(node, 'hreflang');
+ }
+ if (modules.domUtils.hasAttribute(node, 'title')) {
+ obj.title = modules.domUtils.getAttribute(node, 'title');
+ }
+ if (modules.utils.trim(this.getPValue(node, false)) !== '') {
+ obj.text = this.getPValue(node, false);
+ }
+
+ return obj;
+ };
+
+
+ /**
+ * finds any alt rel=* mappings for a given node/microformat
+ *
+ * @param {DOM node} node
+ * @param {String} ufName
+ * @return {String || undefined}
+ */
+ modules.Parser.prototype.findRelImpied = function(node, ufName) {
+ var out,
+ map,
+ i;
+
+ map = this.getMapping(ufName);
+ if (map) {
+ for (var key in map.properties) {
+ if (map.properties.hasOwnProperty(key)) {
+ var prop = map.properties[key],
+ propName = (prop.map) ? prop.map : 'p-' + key,
+ relCount = 0;
+
+ // is property an alt rel=* mapping
+ if (prop.relAlt && modules.domUtils.hasAttribute(node, 'rel')) {
+ i = prop.relAlt.length;
+ while (i--) {
+ if (modules.domUtils.hasAttributeValue(node, 'rel', prop.relAlt[i])) {
+ relCount++;
+ }
+ }
+ if (relCount === prop.relAlt.length) {
+ out = propName;
+ }
+ }
+ }
+ }
+ }
+ return out;
+ };
+
+
+ /**
+ * returns whether a node or its children has rel=* microformat
+ *
+ * @param {DOM node} node
+ * @return {Boolean}
+ */
+ modules.Parser.prototype.hasRel = function(node) {
+ return (this.countRels(node) > 0);
+ };
+
+
+ /**
+ * returns the number of rel=* microformats
+ *
+ * @param {DOM node} node
+ * @return {Int}
+ */
+ modules.Parser.prototype.countRels = function(node) {
+ if (node) {
+ return modules.domUtils.getNodesByAttribute(node, 'rel').length;
+ }
+ return 0;
+ };
+
+
+
+ }
+
+
+ modules.utils = {
+
+ /**
+ * is the object a string
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isString: function( obj ) {
+ return typeof( obj ) === 'string';
+ },
+
+ /**
+ * is the object a number
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isNumber: function( obj ) {
+ return !isNaN(parseFloat( obj )) && isFinite( obj );
+ },
+
+
+ /**
+ * is the object an array
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isArray: function( obj ) {
+ return obj && !( obj.propertyIsEnumerable( 'length' ) ) && typeof obj === 'object' && typeof obj.length === 'number';
+ },
+
+
+ /**
+ * is the object a function
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ isFunction: function(obj) {
+ return !!(obj && obj.constructor && obj.call && obj.apply);
+ },
+
+
+ /**
+ * does the text start with a test string
+ *
+ * @param {String} text
+ * @param {String} test
+ * @return {Boolean}
+ */
+ startWith: function( text, test ) {
+ return (text.indexOf(test) === 0);
+ },
+
+
+ /**
+ * removes spaces at front and back of text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ trim: function( text ) {
+ if (text && this.isString(text)) {
+ return (text.trim())? text.trim() : text.replace(/^\s+|\s+$/g, '');
+ }
+ return '';
+ },
+
+
+ /**
+ * replaces a character in text
+ *
+ * @param {String} text
+ * @param {Int} index
+ * @param {String} character
+ * @return {String}
+ */
+ replaceCharAt: function( text, index, character ) {
+ if (text && text.length > index) {
+ return text.substr(0, index) + character + text.substr(index+character.length);
+ }
+ return text;
+ },
+
+
+ /**
+ * removes whitespace, tabs and returns from start and end of text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ trimWhitespace: function( text ) {
+ if (text && text.length) {
+ var i = text.length,
+ x = 0;
+
+ // turn all whitespace chars at end into spaces
+ while (i--) {
+ if (this.isOnlyWhiteSpace(text[i])) {
+ text = this.replaceCharAt( text, i, ' ' );
+ } else {
+ break;
+ }
+ }
+
+ // turn all whitespace chars at start into spaces
+ i = text.length;
+ while (x < i) {
+ if (this.isOnlyWhiteSpace(text[x])) {
+ text = this.replaceCharAt( text, i, ' ' );
+ } else {
+ break;
+ }
+ x++;
+ }
+ }
+ return this.trim(text);
+ },
+
+
+ /**
+ * does text only contain whitespace characters
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ isOnlyWhiteSpace: function( text ) {
+ return !(/[^\t\n\r ]/.test( text ));
+ },
+
+
+ /**
+ * removes whitespace from text (leaves a single space)
+ *
+ * @param {String} text
+ * @return {Sring}
+ */
+ collapseWhiteSpace: function( text ) {
+ return text.replace(/[\t\n\r ]+/g, ' ');
+ },
+
+
+ /**
+ * does an object have any of its own properties
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+ hasProperties: function( obj ) {
+ var key;
+ for (key in obj) {
+ if ( obj.hasOwnProperty( key ) ) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * a sort function - to sort objects in an array by a given property
+ *
+ * @param {String} property
+ * @param {Boolean} reverse
+ * @return {Int}
+ */
+ sortObjects: function(property, reverse) {
+ reverse = (reverse) ? -1 : 1;
+ return function (a, b) {
+ a = a[property];
+ b = b[property];
+ if (a < b) {
+ return reverse * -1;
+ }
+ if (a > b) {
+ return reverse * 1;
+ }
+ return 0;
+ };
+ }
+
+ };
+
+
+ modules.domUtils = {
+
+ // blank objects for DOM
+ document: null,
+ rootNode: null,
+
+
+ /**
+ * gets DOMParser object
+ *
+ * @return {Object || undefined}
+ */
+ getDOMParser: function () {
+ if (typeof DOMParser === "undefined") {
+ try {
+ return Components.classes["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Components.interfaces.nsIDOMParser);
+ } catch (e) {
+ return undefined;
+ }
+ } else {
+ return new DOMParser();
+ }
+ },
+
+
+ /**
+ * configures what are the base DOM objects for parsing
+ *
+ * @param {Object} options
+ * @return {DOM Node} node
+ */
+ getDOMContext: function( options ) {
+
+ // if a node is passed
+ if (options.node) {
+ this.rootNode = options.node;
+ }
+
+
+ // if a html string is passed
+ if (options.html) {
+ // var domParser = new DOMParser();
+ var domParser = this.getDOMParser();
+ this.rootNode = domParser.parseFromString( options.html, 'text/html' );
+ }
+
+
+ // find top level document from rootnode
+ if (this.rootNode !== null) {
+ if (this.rootNode.nodeType === 9) {
+ this.document = this.rootNode;
+ this.rootNode = modules.domUtils.querySelector(this.rootNode, 'html');
+ } else {
+ // if it's DOM node get parent DOM Document
+ this.document = modules.domUtils.ownerDocument(this.rootNode);
+ }
+ }
+
+
+ // use global document object
+ if (!this.rootNode && document) {
+ this.rootNode = modules.domUtils.querySelector(document, 'html');
+ this.document = document;
+ }
+
+
+ if (this.rootNode && this.document) {
+ return {document: this.document, rootNode: this.rootNode};
+ }
+
+ return {document: null, rootNode: null};
+ },
+
+
+
+ /**
+ * gets the first DOM node
+ *
+ * @param {Dom Document}
+ * @return {DOM Node} node
+ */
+ getTopMostNode: function( node ) {
+ // var doc = this.ownerDocument(node);
+ // if(doc && doc.nodeType && doc.nodeType === 9 && doc.documentElement){
+ // return doc.documentElement;
+ // }
+ return node;
+ },
+
+
+
+ /**
+ * abstracts DOM ownerDocument
+ *
+ * @param {DOM Node} node
+ * @return {Dom Document}
+ */
+ ownerDocument: function(node) {
+ return node.ownerDocument;
+ },
+
+
+ /**
+ * abstracts DOM textContent
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ textContent: function(node) {
+ if (node.textContent) {
+ return node.textContent;
+ } else if (node.innerText) {
+ return node.innerText;
+ }
+ return '';
+ },
+
+
+ /**
+ * abstracts DOM innerHTML
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ innerHTML: function(node) {
+ return node.innerHTML;
+ },
+
+
+ /**
+ * abstracts DOM hasAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {Boolean}
+ */
+ hasAttribute: function(node, attributeName) {
+ return node.hasAttribute(attributeName);
+ },
+
+
+ /**
+ * does an attribute contain a value
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @param {String} value
+ * @return {Boolean}
+ */
+ hasAttributeValue: function(node, attributeName, value) {
+ return (this.getAttributeList(node, attributeName).indexOf(value) > -1);
+ },
+
+
+ /**
+ * abstracts DOM getAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {String || null}
+ */
+ getAttribute: function(node, attributeName) {
+ return node.getAttribute(attributeName);
+ },
+
+
+ /**
+ * abstracts DOM setAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @param {String} attributeValue
+ */
+ setAttribute: function(node, attributeName, attributeValue) {
+ node.setAttribute(attributeName, attributeValue);
+ },
+
+
+ /**
+ * abstracts DOM removeAttribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ */
+ removeAttribute: function(node, attributeName) {
+ node.removeAttribute(attributeName);
+ },
+
+
+ /**
+ * abstracts DOM getElementById
+ *
+ * @param {DOM Node || DOM Document} node
+ * @param {String} id
+ * @return {DOM Node}
+ */
+ getElementById: function(docNode, id) {
+ return docNode.querySelector( '#' + id );
+ },
+
+
+ /**
+ * abstracts DOM querySelector
+ *
+ * @param {DOM Node || DOM Document} node
+ * @param {String} selector
+ * @return {DOM Node}
+ */
+ querySelector: function(docNode, selector) {
+ return docNode.querySelector( selector );
+ },
+
+
+ /**
+ * get value of a Node attribute as an array
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {Array}
+ */
+ getAttributeList: function(node, attributeName) {
+ var out = [],
+ attList;
+
+ attList = node.getAttribute(attributeName);
+ if (attList && attList !== '') {
+ if (attList.indexOf(' ') > -1) {
+ out = attList.split(' ');
+ } else {
+ out.push(attList);
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * gets all child nodes with a given attribute
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {NodeList}
+ */
+ getNodesByAttribute: function(node, attributeName) {
+ var selector = '[' + attributeName + ']';
+ return node.querySelectorAll(selector);
+ },
+
+
+ /**
+ * gets all child nodes with a given attribute containing a given value
+ *
+ * @param {DOM Node} node
+ * @param {String} attributeName
+ * @return {DOM NodeList}
+ */
+ getNodesByAttributeValue: function(rootNode, name, value) {
+ var arr = [],
+ x = 0,
+ i,
+ out = [];
+
+ arr = this.getNodesByAttribute(rootNode, name);
+ if (arr) {
+ i = arr.length;
+ while (x < i) {
+ if (this.hasAttributeValue(arr[x], name, value)) {
+ out.push(arr[x]);
+ }
+ x++;
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * gets attribute value from controlled list of tags
+ *
+ * @param {Array} tagNames
+ * @param {String} attributeName
+ * @return {String || null}
+ */
+ getAttrValFromTagList: function(node, tagNames, attributeName) {
+ var i = tagNames.length;
+
+ while (i--) {
+ if (node.tagName.toLowerCase() === tagNames[i]) {
+ var attrValue = this.getAttribute(node, attributeName);
+ if (attrValue && attrValue !== '') {
+ return attrValue;
+ }
+ }
+ }
+ return null;
+ },
+
+
+ /**
+ * get node if it has no siblings. CSS equivalent is :only-child
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {DOM Node || null}
+ */
+ getSingleDescendant: function(node) {
+ return this.getDescendant( node, null, false );
+ },
+
+
+ /**
+ * get node if it has no siblings of the same type. CSS equivalent is :only-of-type
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {DOM Node || null}
+ */
+ getSingleDescendantOfType: function(node, tagNames) {
+ return this.getDescendant( node, tagNames, true );
+ },
+
+
+ /**
+ * get child node limited by presence of siblings - either CSS :only-of-type or :only-child
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {DOM Node || null}
+ */
+ getDescendant: function( node, tagNames, onlyOfType ) {
+ var i = node.children.length,
+ countAll = 0,
+ countOfType = 0,
+ child,
+ out = null;
+
+ while (i--) {
+ child = node.children[i];
+ if (child.nodeType === 1) {
+ if (tagNames) {
+ // count just only-of-type
+ if (this.hasTagName(child, tagNames)) {
+ out = child;
+ countOfType++;
+ }
+ } else {
+ // count all elements
+ out = child;
+ countAll++;
+ }
+ }
+ }
+ if (onlyOfType === true) {
+ return (countOfType === 1)? out : null;
+ }
+ return (countAll === 1)? out : null;
+ },
+
+
+ /**
+ * is a node one of a list of tags
+ *
+ * @param {DOM Node} rootNode
+ * @param {Array} tagNames
+ * @return {Boolean}
+ */
+ hasTagName: function(node, tagNames) {
+ var i = tagNames.length;
+ while (i--) {
+ if (node.tagName.toLowerCase() === tagNames[i]) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * abstracts DOM appendChild
+ *
+ * @param {DOM Node} node
+ * @param {DOM Node} childNode
+ * @return {DOM Node}
+ */
+ appendChild: function(node, childNode) {
+ return node.appendChild(childNode);
+ },
+
+
+ /**
+ * abstracts DOM removeChild
+ *
+ * @param {DOM Node} childNode
+ * @return {DOM Node || null}
+ */
+ removeChild: function(childNode) {
+ if (childNode.parentNode) {
+ return childNode.parentNode.removeChild(childNode);
+ }
+ return null;
+ },
+
+
+ /**
+ * abstracts DOM cloneNode
+ *
+ * @param {DOM Node} node
+ * @return {DOM Node}
+ */
+ clone: function(node) {
+ var newNode = node.cloneNode(true);
+ newNode.removeAttribute('id');
+ return newNode;
+ },
+
+
+ /**
+ * gets the text of a node
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ getElementText: function( node ) {
+ if (node && node.data) {
+ return node.data;
+ }
+ return '';
+ },
+
+
+ /**
+ * gets the attributes of a node - ordered by sequence in html
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ getOrderedAttributes: function( node ) {
+ var nodeStr = node.outerHTML,
+ attrs = [];
+
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes[i];
+ attr.indexNum = nodeStr.indexOf(attr.name);
+
+ attrs.push( attr );
+ }
+ return attrs.sort( modules.utils.sortObjects( 'indexNum' ) );
+ },
+
+
+ /**
+ * decodes html entities in given text
+ *
+ * @param {DOM Document} doc
+ * @param String} text
+ * @return {String}
+ */
+ decodeEntities: function( doc, text ) {
+ // return text;
+ return doc.createTextNode( text ).nodeValue;
+ },
+
+
+ /**
+ * clones a DOM document
+ *
+ * @param {DOM Document} document
+ * @return {DOM Document}
+ */
+ cloneDocument: function( document ) {
+ var newNode,
+ newDocument = null;
+
+ if ( this.canCloneDocument( document )) {
+ newDocument = document.implementation.createHTMLDocument('');
+ newNode = newDocument.importNode( document.documentElement, true );
+ newDocument.replaceChild(newNode, newDocument.querySelector('html'));
+ }
+ return (newNode && newNode.nodeType && newNode.nodeType === 1)? newDocument : document;
+ },
+
+
+ /**
+ * can environment clone a DOM document
+ *
+ * @param {DOM Document} document
+ * @return {Boolean}
+ */
+ canCloneDocument: function( document ) {
+ return (document && document.importNode && document.implementation && document.implementation.createHTMLDocument);
+ },
+
+
+ /**
+ * get the child index of a node. Used to create a node path
+ *
+ * @param {DOM Node} node
+ * @return {Int}
+ */
+ getChildIndex: function (node) {
+ var parent = node.parentNode,
+ i = -1,
+ child;
+ while (parent && (child = parent.childNodes[++i])) {
+ if (child === node) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+
+ /**
+ * get a node's path
+ *
+ * @param {DOM Node} node
+ * @return {Array}
+ */
+ getNodePath: function (node) {
+ var parent = node.parentNode,
+ path = [],
+ index = this.getChildIndex(node);
+
+ if (parent && (path = this.getNodePath(parent))) {
+ if (index > -1) {
+ path.push(index);
+ }
+ }
+ return path;
+ },
+
+
+ /**
+ * get a node from a path.
+ *
+ * @param {DOM document} document
+ * @param {Array} path
+ * @return {DOM Node}
+ */
+ getNodeByPath: function (document, path) {
+ var node = document.documentElement,
+ i = 0,
+ index;
+ while ((index = path[++i]) > -1) {
+ node = node.childNodes[index];
+ }
+ return node;
+ },
+
+
+ /**
+ * get an array/nodeList of child nodes
+ *
+ * @param {DOM node} node
+ * @return {Array}
+ */
+ getChildren: function( node ) {
+ return node.children;
+ },
+
+
+ /**
+ * create a node
+ *
+ * @param {String} tagName
+ * @return {DOM node}
+ */
+ createNode: function( tagName ) {
+ return this.document.createElement(tagName);
+ },
+
+
+ /**
+ * create a node with text content
+ *
+ * @param {String} tagName
+ * @param {String} text
+ * @return {DOM node}
+ */
+ createNodeWithText: function( tagName, text ) {
+ var node = this.document.createElement(tagName);
+ node.innerHTML = text;
+ return node;
+ }
+
+
+
+ };
+
+
+ modules.url = {
+
+
+ /**
+ * creates DOM objects needed to resolve URLs
+ */
+ init: function() {
+ // this._domParser = new DOMParser();
+ this._domParser = modules.domUtils.getDOMParser();
+ // do not use a head tag it does not work with IE9
+ this._html = '<base id="base" href=""></base><a id="link" href=""></a>';
+ this._nodes = this._domParser.parseFromString( this._html, 'text/html' );
+ this._baseNode = modules.domUtils.getElementById(this._nodes, 'base');
+ this._linkNode = modules.domUtils.getElementById(this._nodes, 'link');
+ },
+
+
+ /**
+ * resolves url to absolute version using baseUrl
+ *
+ * @param {String} url
+ * @param {String} baseUrl
+ * @return {String}
+ */
+ resolve: function(url, baseUrl) {
+ // use modern URL web API where we can
+ if (modules.utils.isString(url) && modules.utils.isString(baseUrl) && url.indexOf('://') === -1) {
+ // this try catch is required as IE has an URL object but no constuctor support
+ // http://glennjones.net/articles/the-problem-with-window-url
+ try {
+ var resolved = new URL(url, baseUrl).toString();
+ // deal with early Webkit not throwing an error - for Safari
+ if (resolved === '[object URL]') {
+ resolved = URI.resolve(baseUrl, url);
+ }
+ return resolved;
+ } catch (e) {
+ // otherwise fallback to DOM
+ if (this._domParser === undefined) {
+ this.init();
+ }
+
+ // do not use setAttribute it does not work with IE9
+ this._baseNode.href = baseUrl;
+ this._linkNode.href = url;
+
+ // dont use getAttribute as it returns orginal value not resolved
+ return this._linkNode.href;
+ }
+ } else {
+ if (modules.utils.isString(url)) {
+ return url;
+ }
+ return '';
+ }
+ },
+
+ };
+
+
+ /**
+ * constructor
+ * parses text to find just the date element of an ISO date/time string i.e. 2008-05-01
+ *
+ * @param {String} dateString
+ * @param {String} format
+ * @return {String}
+ */
+ modules.ISODate = function ( dateString, format ) {
+ this.clear();
+
+ this.format = (format)? format : 'auto'; // auto or W3C or RFC3339 or HTML5
+ this.setFormatSep();
+
+ // optional should be full iso date/time string
+ if (arguments[0]) {
+ this.parse(dateString, format);
+ }
+ };
+
+
+ modules.ISODate.prototype = {
+
+
+ /**
+ * clear all states
+ *
+ */
+ clear: function() {
+ this.clearDate();
+ this.clearTime();
+ this.clearTimeZone();
+ this.setAutoProfileState();
+ },
+
+
+ /**
+ * clear date states
+ *
+ */
+ clearDate: function() {
+ this.dY = -1;
+ this.dM = -1;
+ this.dD = -1;
+ this.dDDD = -1;
+ },
+
+
+ /**
+ * clear time states
+ *
+ */
+ clearTime: function() {
+ this.tH = -1;
+ this.tM = -1;
+ this.tS = -1;
+ this.tD = -1;
+ },
+
+
+ /**
+ * clear timezone states
+ *
+ */
+ clearTimeZone: function() {
+ this.tzH = -1;
+ this.tzM = -1;
+ this.tzPN = '+';
+ this.z = false;
+ },
+
+
+ /**
+ * resets the auto profile state
+ *
+ */
+ setAutoProfileState: function() {
+ this.autoProfile = {
+ sep: 'T',
+ dsep: '-',
+ tsep: ':',
+ tzsep: ':',
+ tzZulu: 'Z'
+ };
+ },
+
+
+ /**
+ * parses text to find ISO date/time string i.e. 2008-05-01T15:45:19Z
+ *
+ * @param {String} dateString
+ * @param {String} format
+ * @return {String}
+ */
+ parse: function( dateString, format ) {
+ this.clear();
+
+ var parts = [],
+ tzArray = [],
+ position = 0,
+ datePart = '',
+ timePart = '',
+ timeZonePart = '';
+
+ if (format) {
+ this.format = format;
+ }
+
+
+
+ // discover date time separtor for auto profile
+ // Set to 'T' by default
+ if (dateString.indexOf('t') > -1) {
+ this.autoProfile.sep = 't';
+ }
+ if (dateString.indexOf('z') > -1) {
+ this.autoProfile.tzZulu = 'z';
+ }
+ if (dateString.indexOf('Z') > -1) {
+ this.autoProfile.tzZulu = 'Z';
+ }
+ if (dateString.toUpperCase().indexOf('T') === -1) {
+ this.autoProfile.sep = ' ';
+ }
+
+
+ dateString = dateString.toUpperCase().replace(' ', 'T');
+
+ // break on 'T' divider or space
+ if (dateString.indexOf('T') > -1) {
+ parts = dateString.split('T');
+ datePart = parts[0];
+ timePart = parts[1];
+
+ // zulu UTC
+ if (timePart.indexOf( 'Z' ) > -1) {
+ this.z = true;
+ }
+
+ // timezone
+ if (timePart.indexOf( '+' ) > -1 || timePart.indexOf( '-' ) > -1) {
+ tzArray = timePart.split( 'Z' ); // incase of incorrect use of Z
+ timePart = tzArray[0];
+ timeZonePart = tzArray[1];
+
+ // timezone
+ if (timePart.indexOf( '+' ) > -1 || timePart.indexOf( '-' ) > -1) {
+ position = 0;
+
+ if (timePart.indexOf( '+' ) > -1) {
+ position = timePart.indexOf( '+' );
+ } else {
+ position = timePart.indexOf( '-' );
+ }
+
+ timeZonePart = timePart.substring( position, timePart.length );
+ timePart = timePart.substring( 0, position );
+ }
+ }
+
+ } else {
+ datePart = dateString;
+ }
+
+ if (datePart !== '') {
+ this.parseDate( datePart );
+ if (timePart !== '') {
+ this.parseTime( timePart );
+ if (timeZonePart !== '') {
+ this.parseTimeZone( timeZonePart );
+ }
+ }
+ }
+ return this.toString( format );
+ },
+
+
+ /**
+ * parses text to find just the date element of an ISO date/time string i.e. 2008-05-01
+ *
+ * @param {String} dateString
+ * @param {String} format
+ * @return {String}
+ */
+ parseDate: function( dateString, format ) {
+ this.clearDate();
+
+ var parts = [];
+
+ // discover timezone separtor for auto profile // default is ':'
+ if (dateString.indexOf('-') === -1) {
+ this.autoProfile.tsep = '';
+ }
+
+ // YYYY-DDD
+ parts = dateString.match( /(\d\d\d\d)-(\d\d\d)/ );
+ if (parts) {
+ if (parts[1]) {
+ this.dY = parts[1];
+ }
+ if (parts[2]) {
+ this.dDDD = parts[2];
+ }
+ }
+
+ if (this.dDDD === -1) {
+ // YYYY-MM-DD ie 2008-05-01 and YYYYMMDD ie 20080501
+ parts = dateString.match( /(\d\d\d\d)?-?(\d\d)?-?(\d\d)?/ );
+ if (parts[1]) {
+ this.dY = parts[1];
+ }
+ if (parts[2]) {
+ this.dM = parts[2];
+ }
+ if (parts[3]) {
+ this.dD = parts[3];
+ }
+ }
+ return this.toString(format);
+ },
+
+
+ /**
+ * parses text to find just the time element of an ISO date/time string i.e. 13:30:45
+ *
+ * @param {String} timeString
+ * @param {String} format
+ * @return {String}
+ */
+ parseTime: function( timeString, format ) {
+ this.clearTime();
+ var parts = [];
+
+ // discover date separtor for auto profile // default is ':'
+ if (timeString.indexOf(':') === -1) {
+ this.autoProfile.tsep = '';
+ }
+
+ // finds timezone HH:MM:SS and HHMMSS ie 13:30:45, 133045 and 13:30:45.0135
+ parts = timeString.match( /(\d\d)?:?(\d\d)?:?(\d\d)?.?([0-9]+)?/ );
+ if (parts[1]) {
+ this.tH = parts[1];
+ }
+ if (parts[2]) {
+ this.tM = parts[2];
+ }
+ if (parts[3]) {
+ this.tS = parts[3];
+ }
+ if (parts[4]) {
+ this.tD = parts[4];
+ }
+ return this.toTimeString(format);
+ },
+
+
+ /**
+ * parses text to find just the time element of an ISO date/time string i.e. +08:00
+ *
+ * @param {String} timeString
+ * @param {String} format
+ * @return {String}
+ */
+ parseTimeZone: function( timeString, format ) {
+ this.clearTimeZone();
+ var parts = [];
+
+ if (timeString.toLowerCase() === 'z') {
+ this.z = true;
+ // set case for z
+ this.autoProfile.tzZulu = (timeString === 'z')? 'z' : 'Z';
+ } else {
+
+ // discover timezone separtor for auto profile // default is ':'
+ if (timeString.indexOf(':') === -1) {
+ this.autoProfile.tzsep = '';
+ }
+
+ // finds timezone +HH:MM and +HHMM ie +13:30 and +1330
+ parts = timeString.match( /([\-\+]{1})?(\d\d)?:?(\d\d)?/ );
+ if (parts[1]) {
+ this.tzPN = parts[1];
+ }
+ if (parts[2]) {
+ this.tzH = parts[2];
+ }
+ if (parts[3]) {
+ this.tzM = parts[3];
+ }
+
+
+ }
+ this.tzZulu = 'z';
+ return this.toTimeString( format );
+ },
+
+
+ /**
+ * returns ISO date/time string in W3C Note, RFC 3339, HTML5, or auto profile
+ *
+ * @param {String} format
+ * @return {String}
+ */
+ toString: function( format ) {
+ var output = '';
+
+ if (format) {
+ this.format = format;
+ }
+ this.setFormatSep();
+
+ if (this.dY > -1) {
+ output = this.dY;
+ if (this.dM > 0 && this.dM < 13) {
+ output += this.dsep + this.dM;
+ if (this.dD > 0 && this.dD < 32) {
+ output += this.dsep + this.dD;
+ if (this.tH > -1 && this.tH < 25) {
+ output += this.sep + this.toTimeString( format );
+ }
+ }
+ }
+ if (this.dDDD > -1) {
+ output += this.dsep + this.dDDD;
+ }
+ } else if (this.tH > -1) {
+ output += this.toTimeString( format );
+ }
+
+ return output;
+ },
+
+
+ /**
+ * returns just the time string element of an ISO date/time
+ * in W3C Note, RFC 3339, HTML5, or auto profile
+ *
+ * @param {String} format
+ * @return {String}
+ */
+ toTimeString: function( format ) {
+ var out = '';
+
+ if (format) {
+ this.format = format;
+ }
+ this.setFormatSep();
+
+ // time can only be created with a full date
+ if (this.tH) {
+ if (this.tH > -1 && this.tH < 25) {
+ out += this.tH;
+ if (this.tM > -1 && this.tM < 61) {
+ out += this.tsep + this.tM;
+ if (this.tS > -1 && this.tS < 61) {
+ out += this.tsep + this.tS;
+ if (this.tD > -1) {
+ out += '.' + this.tD;
+ }
+ }
+ }
+
+
+
+ // time zone offset
+ if (this.z) {
+ out += this.tzZulu;
+ } else if (this.tzH && this.tzH > -1 && this.tzH < 25) {
+ out += this.tzPN + this.tzH;
+ if (this.tzM > -1 && this.tzM < 61) {
+ out += this.tzsep + this.tzM;
+ }
+ }
+ }
+ }
+ return out;
+ },
+
+
+ /**
+ * set the current profile to W3C Note, RFC 3339, HTML5, or auto profile
+ *
+ */
+ setFormatSep: function() {
+ switch ( this.format.toLowerCase() ) {
+ case 'rfc3339':
+ this.sep = 'T';
+ this.dsep = '';
+ this.tsep = '';
+ this.tzsep = '';
+ this.tzZulu = 'Z';
+ break;
+ case 'w3c':
+ this.sep = 'T';
+ this.dsep = '-';
+ this.tsep = ':';
+ this.tzsep = ':';
+ this.tzZulu = 'Z';
+ break;
+ case 'html5':
+ this.sep = ' ';
+ this.dsep = '-';
+ this.tsep = ':';
+ this.tzsep = ':';
+ this.tzZulu = 'Z';
+ break;
+ default:
+ // auto - defined by format of input string
+ this.sep = this.autoProfile.sep;
+ this.dsep = this.autoProfile.dsep;
+ this.tsep = this.autoProfile.tsep;
+ this.tzsep = this.autoProfile.tzsep;
+ this.tzZulu = this.autoProfile.tzZulu;
+ }
+ },
+
+
+ /**
+ * does current data contain a full date i.e. 2015-03-23
+ *
+ * @return {Boolean}
+ */
+ hasFullDate: function() {
+ return (this.dY !== -1 && this.dM !== -1 && this.dD !== -1);
+ },
+
+
+ /**
+ * does current data contain a minimum date which is just a year number i.e. 2015
+ *
+ * @return {Boolean}
+ */
+ hasDate: function() {
+ return (this.dY !== -1);
+ },
+
+
+ /**
+ * does current data contain a minimum time which is just a hour number i.e. 13
+ *
+ * @return {Boolean}
+ */
+ hasTime: function() {
+ return (this.tH !== -1);
+ },
+
+ /**
+ * does current data contain a minimum timezone i.e. -1 || +1 || z
+ *
+ * @return {Boolean}
+ */
+ hasTimeZone: function() {
+ return (this.tzH !== -1);
+ }
+
+ };
+
+ modules.ISODate.prototype.constructor = modules.ISODate;
+
+
+ modules.dates = {
+
+
+ /**
+ * does text contain am
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ hasAM: function( text ) {
+ text = text.toLowerCase();
+ return (text.indexOf('am') > -1 || text.indexOf('a.m.') > -1);
+ },
+
+
+ /**
+ * does text contain pm
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ hasPM: function( text ) {
+ text = text.toLowerCase();
+ return (text.indexOf('pm') > -1 || text.indexOf('p.m.') > -1);
+ },
+
+
+ /**
+ * remove am and pm from text and return it
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ removeAMPM: function( text ) {
+ return text.replace('pm', '').replace('p.m.', '').replace('am', '').replace('a.m.', '');
+ },
+
+
+ /**
+ * simple test of whether ISO date string is a duration i.e. PY17M or PW12
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ isDuration: function( text ) {
+ if (modules.utils.isString( text )) {
+ text = text.toLowerCase();
+ if (modules.utils.startWith(text, 'p') ) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * is text a time or timezone
+ * i.e. HH-MM-SS or z+-HH-MM-SS 08:43 | 15:23:00:0567 | 10:34pm | 10:34 p.m. | +01:00:00 | -02:00 | z15:00 | 0843
+ *
+ * @param {String} text
+ * @return {Boolean}
+ */
+ isTime: function( text ) {
+ if (modules.utils.isString(text)) {
+ text = text.toLowerCase();
+ text = modules.utils.trim( text );
+ // start with timezone char
+ if ( text.match(':') && ( modules.utils.startWith(text, 'z') || modules.utils.startWith(text, '-') || modules.utils.startWith(text, '+') )) {
+ return true;
+ }
+ // has ante meridiem or post meridiem
+ if ( text.match(/^[0-9]/) &&
+ ( this.hasAM(text) || this.hasPM(text) )) {
+ return true;
+ }
+ // contains time delimiter but not datetime delimiter
+ if ( text.match(':') && !text.match(/t|\s/) ) {
+ return true;
+ }
+
+ // if it's a number of 2, 4 or 6 chars
+ if (modules.utils.isNumber(text)) {
+ if (text.length === 2 || text.length === 4 || text.length === 6) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+
+
+ /**
+ * parses a time from text and returns 24hr time string
+ * i.e. 5:34am = 05:34:00 and 1:52:04p.m. = 13:52:04
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ parseAmPmTime: function( text ) {
+ var out = text,
+ times = [];
+
+ // if the string has a text : or am or pm
+ if (modules.utils.isString(out)) {
+ // text = text.toLowerCase();
+ text = text.replace(/[ ]+/g, '');
+
+ if (text.match(':') || this.hasAM(text) || this.hasPM(text)) {
+
+ if (text.match(':')) {
+ times = text.split(':');
+ } else {
+ // single number text i.e. 5pm
+ times[0] = text;
+ times[0] = this.removeAMPM(times[0]);
+ }
+
+ // change pm hours to 24hr number
+ if (this.hasPM(text)) {
+ if (times[0] < 12) {
+ times[0] = parseInt(times[0], 10) + 12;
+ }
+ }
+
+ // add leading zero's where needed
+ if (times[0] && times[0].length === 1) {
+ times[0] = '0' + times[0];
+ }
+
+ // rejoin text elements together
+ if (times[0]) {
+ text = times.join(':');
+ }
+ }
+ }
+
+ // remove am/pm strings
+ return this.removeAMPM(text);
+ },
+
+
+ /**
+ * overlays a time on a date to return the union of the two
+ *
+ * @param {String} date
+ * @param {String} time
+ * @param {String} format ( Modules.ISODate profile format )
+ * @return {Object} Modules.ISODate
+ */
+ dateTimeUnion: function(date, time, format) {
+ var isodate = new modules.ISODate(date, format),
+ isotime = new modules.ISODate();
+
+ isotime.parseTime(this.parseAmPmTime(time), format);
+ if (isodate.hasFullDate() && isotime.hasTime()) {
+ isodate.tH = isotime.tH;
+ isodate.tM = isotime.tM;
+ isodate.tS = isotime.tS;
+ isodate.tD = isotime.tD;
+ return isodate;
+ }
+ if (isodate.hasFullDate()) {
+ return isodate;
+ }
+ return new modules.ISODate();
+ },
+
+
+ /**
+ * concatenate an array of date and time text fragments to create an ISODate object
+ * used for microformat value and value-title rules
+ *
+ * @param {Array} arr ( Array of Strings )
+ * @param {String} format ( Modules.ISODate profile format )
+ * @return {Object} Modules.ISODate
+ */
+ concatFragments: function (arr, format) {
+ var out = new modules.ISODate(),
+ i = 0,
+ value = '';
+
+ // if the fragment already contains a full date just return it once
+ if (arr[0].toUpperCase().match('T')) {
+ return new modules.ISODate(arr[0], format);
+ }
+ for (i = 0; i < arr.length; i++) {
+ value = arr[i];
+
+ // date pattern
+ if ( value.charAt(4) === '-' && out.hasFullDate() === false ) {
+ out.parseDate(value);
+ }
+
+ // time pattern
+ if ( (value.indexOf(':') > -1 || modules.utils.isNumber( this.parseAmPmTime(value) )) && out.hasTime() === false ) {
+ // split time and timezone
+ var items = this.splitTimeAndZone(value);
+ value = items[0];
+
+ // parse any use of am/pm
+ value = this.parseAmPmTime(value);
+ out.parseTime(value);
+
+ // parse any timezone
+ if (items.length > 1) {
+ out.parseTimeZone(items[1], format);
+ }
+ }
+
+ // timezone pattern
+ if (value.charAt(0) === '-' || value.charAt(0) === '+' || value.toUpperCase() === 'Z') {
+ if ( out.hasTimeZone() === false ) {
+ out.parseTimeZone(value);
+ }
+ }
+
+ }
+ return out;
+ },
+
+
+ /**
+ * parses text by splitting it into an array of time and timezone strings
+ *
+ * @param {String} text
+ * @return {Array} Modules.ISODate
+ */
+ splitTimeAndZone: function ( text ) {
+ var out = [text],
+ chars = ['-', '+', 'z', 'Z'],
+ i = chars.length;
+
+ while (i--) {
+ if (text.indexOf(chars[i]) > -1) {
+ out[0] = text.slice( 0, text.indexOf(chars[i]) );
+ out.push( text.slice( text.indexOf(chars[i]) ) );
+ break;
+ }
+ }
+ return out;
+ }
+
+ };
+
+
+ modules.text = {
+
+ // normalised or whitespace or whitespacetrimmed
+ textFormat: 'whitespacetrimmed',
+
+ // block level tags, used to add line returns
+ blockLevelTags: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'hr', 'pre', 'table',
+ 'address', 'article', 'aside', 'blockquote', 'caption', 'col', 'colgroup', 'dd', 'div',
+ 'dt', 'dir', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'header', 'hgroup', 'hr',
+ 'li', 'map', 'menu', 'nav', 'optgroup', 'option', 'section', 'tbody', 'testarea',
+ 'tfoot', 'th', 'thead', 'tr', 'td', 'ul', 'ol', 'dl', 'details'],
+
+ // tags to exclude
+ excludeTags: ['noframe', 'noscript', 'template', 'script', 'style', 'frames', 'frameset'],
+
+
+ /**
+ * parses the text from the DOM Node
+ *
+ * @param {DOM Node} node
+ * @param {String} textFormat
+ * @return {String}
+ */
+ parse: function(doc, node, textFormat) {
+ var out;
+ this.textFormat = (textFormat)? textFormat : this.textFormat;
+ if (this.textFormat === 'normalised') {
+ out = this.walkTreeForText( node );
+ if (out !== undefined) {
+ return this.normalise( doc, out );
+ }
+ return '';
+ }
+ return this.formatText( doc, modules.domUtils.textContent(node), this.textFormat );
+ },
+
+
+ /**
+ * parses the text from a html string
+ *
+ * @param {DOM Document} doc
+ * @param {String} text
+ * @param {String} textFormat
+ * @return {String}
+ */
+ parseText: function( doc, text, textFormat ) {
+ var node = modules.domUtils.createNodeWithText( 'div', text );
+ return this.parse( doc, node, textFormat );
+ },
+
+
+ /**
+ * parses the text from a html string - only for whitespace or whitespacetrimmed formats
+ *
+ * @param {String} text
+ * @param {String} textFormat
+ * @return {String}
+ */
+ formatText: function( doc, text, textFormat ) {
+ this.textFormat = (textFormat)? textFormat : this.textFormat;
+ if (text) {
+ var out = '',
+ regex = /(<([^>]+)>)/ig;
+
+ out = text.replace(regex, '');
+ if (this.textFormat === 'whitespacetrimmed') {
+ out = modules.utils.trimWhitespace( out );
+ }
+
+ // return entities.decode( out, 2 );
+ return modules.domUtils.decodeEntities( doc, out );
+ }
+ return '';
+ },
+
+
+ /**
+ * normalises whitespace in given text
+ *
+ * @param {String} text
+ * @return {String}
+ */
+ normalise: function( doc, text ) {
+ text = text.replace( /&nbsp;/g, ' ') ; // exchanges html entity for space into space char
+ text = modules.utils.collapseWhiteSpace( text ); // removes linefeeds, tabs and addtional spaces
+ text = modules.domUtils.decodeEntities( doc, text ); // decode HTML entities
+ text = text.replace( '–', '-' ); // correct dash decoding
+ return modules.utils.trim( text );
+ },
+
+
+ /**
+ * walks DOM tree parsing the text from DOM Nodes
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ walkTreeForText: function( node ) {
+ var out = '',
+ j = 0;
+
+ if (node.tagName && this.excludeTags.indexOf( node.tagName.toLowerCase() ) > -1) {
+ return out;
+ }
+
+ // if node is a text node get its text
+ if (node.nodeType && node.nodeType === 3) {
+ out += modules.domUtils.getElementText( node );
+ }
+
+ // get the text of the child nodes
+ if (node.childNodes && node.childNodes.length > 0) {
+ for (j = 0; j < node.childNodes.length; j++) {
+ var text = this.walkTreeForText( node.childNodes[j] );
+ if (text !== undefined) {
+ out += text;
+ }
+ }
+ }
+
+ // if it's a block level tag add an additional space at the end
+ if (node.tagName && this.blockLevelTags.indexOf( node.tagName.toLowerCase() ) !== -1) {
+ out += ' ';
+ }
+
+ return (out === '')? undefined : out ;
+ }
+
+ };
+
+
+ modules.html = {
+
+ // elements which are self-closing
+ selfClosingElt: ['area', 'base', 'br', 'col', 'hr', 'img', 'input', 'link', 'meta', 'param', 'command', 'keygen', 'source'],
+
+
+ /**
+ * parse the html string from DOM Node
+ *
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ parse: function( node ) {
+ var out = '',
+ j = 0;
+
+ // we do not want the outer container
+ if (node.childNodes && node.childNodes.length > 0) {
+ for (j = 0; j < node.childNodes.length; j++) {
+ var text = this.walkTreeForHtml( node.childNodes[j] );
+ if (text !== undefined) {
+ out += text;
+ }
+ }
+ }
+
+ return out;
+ },
+
+
+ /**
+ * walks the DOM tree parsing the html string from the nodes
+ *
+ * @param {DOM Document} doc
+ * @param {DOM Node} node
+ * @return {String}
+ */
+ walkTreeForHtml: function( node ) {
+ var out = '',
+ j = 0;
+
+ // if node is a text node get its text
+ if (node.nodeType && node.nodeType === 3) {
+ out += modules.domUtils.getElementText( node );
+ }
+
+
+ // exclude text which has been added with include pattern -
+ if (node.nodeType && node.nodeType === 1 && modules.domUtils.hasAttribute(node, 'data-include') === false) {
+
+ // begin tag
+ out += '<' + node.tagName.toLowerCase();
+
+ // add attributes
+ var attrs = modules.domUtils.getOrderedAttributes(node);
+ for (j = 0; j < attrs.length; j++) {
+ out += ' ' + attrs[j].name + '=' + '"' + attrs[j].value + '"';
+ }
+
+ if (this.selfClosingElt.indexOf(node.tagName.toLowerCase()) === -1) {
+ out += '>';
+ }
+
+ // get the text of the child nodes
+ if (node.childNodes && node.childNodes.length > 0) {
+
+ for (j = 0; j < node.childNodes.length; j++) {
+ var text = this.walkTreeForHtml( node.childNodes[j] );
+ if (text !== undefined) {
+ out += text;
+ }
+ }
+ }
+
+ // end tag
+ if (this.selfClosingElt.indexOf(node.tagName.toLowerCase()) > -1) {
+ out += ' />';
+ } else {
+ out += '</' + node.tagName.toLowerCase() + '>';
+ }
+ }
+
+ return (out === '')? undefined : out;
+ }
+
+
+ };
+
+
+ modules.maps['h-adr'] = {
+ root: 'adr',
+ name: 'h-adr',
+ properties: {
+ 'post-office-box': {},
+ 'street-address': {},
+ 'extended-address': {},
+ 'locality': {},
+ 'region': {},
+ 'postal-code': {},
+ 'country-name': {}
+ }
+ };
+
+
+ modules.maps['h-card'] = {
+ root: 'vcard',
+ name: 'h-card',
+ properties: {
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'adr': {
+ 'map': 'p-adr',
+ 'uf': ['h-adr']
+ },
+ 'agent': {
+ 'uf': ['h-card']
+ },
+ 'bday': {
+ 'map': 'dt-bday'
+ },
+ 'class': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'email': {
+ 'map': 'u-email'
+ },
+ 'geo': {
+ 'map': 'p-geo',
+ 'uf': ['h-geo']
+ },
+ 'key': {
+ 'map': 'u-key'
+ },
+ 'label': {},
+ 'logo': {
+ 'map': 'u-logo'
+ },
+ 'mailer': {},
+ 'honorific-prefix': {},
+ 'given-name': {},
+ 'additional-name': {},
+ 'family-name': {},
+ 'honorific-suffix': {},
+ 'nickname': {},
+ 'note': {}, // could be html i.e. e-note
+ 'org': {},
+ 'p-organization-name': {},
+ 'p-organization-unit': {},
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ 'rev': {
+ 'map': 'dt-rev'
+ },
+ 'role': {},
+ 'sequence': {},
+ 'sort-string': {},
+ 'sound': {
+ 'map': 'u-sound'
+ },
+ 'title': {
+ 'map': 'p-job-title'
+ },
+ 'tel': {},
+ 'tz': {},
+ 'uid': {
+ 'map': 'u-uid'
+ },
+ 'url': {
+ 'map': 'u-url'
+ }
+ }
+ };
+
+
+ modules.maps['h-entry'] = {
+ root: 'hentry',
+ name: 'h-entry',
+ properties: {
+ 'entry-title': {
+ 'map': 'p-name'
+ },
+ 'entry-summary': {
+ 'map': 'p-summary'
+ },
+ 'entry-content': {
+ 'map': 'e-content'
+ },
+ 'published': {
+ 'map': 'dt-published'
+ },
+ 'updated': {
+ 'map': 'dt-updated'
+ },
+ 'author': {
+ 'uf': ['h-card']
+ },
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'geo': {
+ 'map': 'p-geo',
+ 'uf': ['h-geo']
+ },
+ 'latitude': {},
+ 'longitude': {},
+ 'url': {
+ 'map': 'u-url',
+ 'relAlt': ['bookmark']
+ }
+ }
+ };
+
+
+ modules.maps['h-event'] = {
+ root: 'vevent',
+ name: 'h-event',
+ properties: {
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'dtstart': {
+ 'map': 'dt-start'
+ },
+ 'dtend': {
+ 'map': 'dt-end'
+ },
+ 'description': {},
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'location': {
+ 'uf': ['h-card']
+ },
+ 'geo': {
+ 'uf': ['h-geo']
+ },
+ 'latitude': {},
+ 'longitude': {},
+ 'duration': {
+ 'map': 'dt-duration'
+ },
+ 'contact': {
+ 'uf': ['h-card']
+ },
+ 'organizer': {
+ 'uf': ['h-card']},
+ 'attendee': {
+ 'uf': ['h-card']},
+ 'uid': {
+ 'map': 'u-uid'
+ },
+ 'attach': {
+ 'map': 'u-attach'
+ },
+ 'status': {},
+ 'rdate': {},
+ 'rrule': {}
+ }
+ };
+
+
+ modules.maps['h-feed'] = {
+ root: 'hfeed',
+ name: 'h-feed',
+ properties: {
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'summary': {
+ 'map': 'p-summary'
+ },
+ 'author': {
+ 'uf': ['h-card']
+ },
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ }
+ };
+
+
+ modules.maps['h-geo'] = {
+ root: 'geo',
+ name: 'h-geo',
+ properties: {
+ 'latitude': {},
+ 'longitude': {}
+ }
+ };
+
+
+ modules.maps['h-item'] = {
+ root: 'item',
+ name: 'h-item',
+ subTree: false,
+ properties: {
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ }
+ }
+ };
+
+
+ modules.maps['h-listing'] = {
+ root: 'hlisting',
+ name: 'h-listing',
+ properties: {
+ 'version': {},
+ 'lister': {
+ 'uf': ['h-card']
+ },
+ 'dtlisted': {
+ 'map': 'dt-listed'
+ },
+ 'dtexpired': {
+ 'map': 'dt-expired'
+ },
+ 'location': {},
+ 'price': {},
+ 'item': {
+ 'uf': ['h-card', 'a-adr', 'h-geo']
+ },
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'description': {
+ 'map': 'e-description'
+ },
+ 'listing': {}
+ }
+ };
+
+
+ modules.maps['h-news'] = {
+ root: 'hnews',
+ name: 'h-news',
+ properties: {
+ 'entry': {
+ 'uf': ['h-entry']
+ },
+ 'geo': {
+ 'uf': ['h-geo']
+ },
+ 'latitude': {},
+ 'longitude': {},
+ 'source-org': {
+ 'uf': ['h-card']
+ },
+ 'dateline': {
+ 'uf': ['h-card']
+ },
+ 'item-license': {
+ 'map': 'u-item-license'
+ },
+ 'principles': {
+ 'map': 'u-principles',
+ 'relAlt': ['principles']
+ }
+ }
+ };
+
+
+ modules.maps['h-org'] = {
+ root: 'h-x-org', // drop this from v1 as it causes issue with fn org hcard pattern
+ name: 'h-org',
+ childStructure: true,
+ properties: {
+ 'organization-name': {},
+ 'organization-unit': {}
+ }
+ };
+
+
+ modules.maps['h-product'] = {
+ root: 'hproduct',
+ name: 'h-product',
+ properties: {
+ 'brand': {
+ 'uf': ['h-card']
+ },
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'price': {},
+ 'description': {
+ 'map': 'e-description'
+ },
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ 'url': {
+ 'map': 'u-url'
+ },
+ 'review': {
+ 'uf': ['h-review', 'h-review-aggregate']
+ },
+ 'listing': {
+ 'uf': ['h-listing']
+ },
+ 'identifier': {
+ 'map': 'u-identifier'
+ }
+ }
+ };
+
+
+ modules.maps['h-recipe'] = {
+ root: 'hrecipe',
+ name: 'h-recipe',
+ properties: {
+ 'fn': {
+ 'map': 'p-name'
+ },
+ 'ingredient': {
+ 'map': 'e-ingredient'
+ },
+ 'yield': {},
+ 'instructions': {
+ 'map': 'e-instructions'
+ },
+ 'duration': {
+ 'map': 'dt-duration'
+ },
+ 'photo': {
+ 'map': 'u-photo'
+ },
+ 'summary': {},
+ 'author': {
+ 'uf': ['h-card']
+ },
+ 'published': {
+ 'map': 'dt-published'
+ },
+ 'nutrition': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ }
+ };
+
+
+ modules.maps['h-resume'] = {
+ root: 'hresume',
+ name: 'h-resume',
+ properties: {
+ 'summary': {},
+ 'contact': {
+ 'uf': ['h-card']
+ },
+ 'education': {
+ 'uf': ['h-card', 'h-event']
+ },
+ 'experience': {
+ 'uf': ['h-card', 'h-event']
+ },
+ 'skill': {},
+ 'affiliation': {
+ 'uf': ['h-card']
+ }
+ }
+ };
+
+
+ modules.maps['h-review-aggregate'] = {
+ root: 'hreview-aggregate',
+ name: 'h-review-aggregate',
+ properties: {
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'item': {
+ 'map': 'p-item',
+ 'uf': ['h-item', 'h-geo', 'h-adr', 'h-card', 'h-event', 'h-product']
+ },
+ 'rating': {},
+ 'average': {},
+ 'best': {},
+ 'worst': {},
+ 'count': {},
+ 'votes': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'url': {
+ 'map': 'u-url',
+ 'relAlt': ['self', 'bookmark']
+ }
+ }
+ };
+
+
+ modules.maps['h-review'] = {
+ root: 'hreview',
+ name: 'h-review',
+ properties: {
+ 'summary': {
+ 'map': 'p-name'
+ },
+ 'description': {
+ 'map': 'e-description'
+ },
+ 'item': {
+ 'map': 'p-item',
+ 'uf': ['h-item', 'h-geo', 'h-adr', 'h-card', 'h-event', 'h-product']
+ },
+ 'reviewer': {
+ 'uf': ['h-card']
+ },
+ 'dtreviewer': {
+ 'map': 'dt-reviewer'
+ },
+ 'rating': {},
+ 'best': {},
+ 'worst': {},
+ 'category': {
+ 'map': 'p-category',
+ 'relAlt': ['tag']
+ },
+ 'url': {
+ 'map': 'u-url',
+ 'relAlt': ['self', 'bookmark']
+ }
+ }
+ };
+
+
+ modules.rels = {
+ // xfn
+ 'friend': [ 'yes', 'external'],
+ 'acquaintance': [ 'yes', 'external'],
+ 'contact': [ 'yes', 'external'],
+ 'met': [ 'yes', 'external'],
+ 'co-worker': [ 'yes', 'external'],
+ 'colleague': [ 'yes', 'external'],
+ 'co-resident': [ 'yes', 'external'],
+ 'neighbor': [ 'yes', 'external'],
+ 'child': [ 'yes', 'external'],
+ 'parent': [ 'yes', 'external'],
+ 'sibling': [ 'yes', 'external'],
+ 'spouse': [ 'yes', 'external'],
+ 'kin': [ 'yes', 'external'],
+ 'muse': [ 'yes', 'external'],
+ 'crush': [ 'yes', 'external'],
+ 'date': [ 'yes', 'external'],
+ 'sweetheart': [ 'yes', 'external'],
+ 'me': [ 'yes', 'external'],
+
+ // other rel=*
+ 'license': [ 'yes', 'yes'],
+ 'nofollow': [ 'no', 'external'],
+ 'tag': [ 'no', 'yes'],
+ 'self': [ 'no', 'external'],
+ 'bookmark': [ 'no', 'external'],
+ 'author': [ 'no', 'external'],
+ 'home': [ 'no', 'external'],
+ 'directory': [ 'no', 'external'],
+ 'enclosure': [ 'no', 'external'],
+ 'pronunciation': [ 'no', 'external'],
+ 'payment': [ 'no', 'external'],
+ 'principles': [ 'no', 'external']
+
+ };
+
+
+
+ var External = {
+ version: modules.version,
+ livingStandard: modules.livingStandard
+ };
+
+
+ External.get = function(options) {
+ var parser = new modules.Parser();
+ addV1(parser, options);
+ return parser.get( options );
+ };
+
+
+ External.getParent = function(node, options) {
+ var parser = new modules.Parser();
+ addV1(parser, options);
+ return parser.getParent( node, options );
+ };
+
+
+ External.count = function(options) {
+ var parser = new modules.Parser();
+ addV1(parser, options);
+ return parser.count( options );
+ };
+
+
+ External.isMicroformat = function( node, options ) {
+ var parser = new modules.Parser();
+ addV1(parser, options);
+ return parser.isMicroformat( node, options );
+ };
+
+
+ External.hasMicroformats = function( node, options ) {
+ var parser = new modules.Parser();
+ addV1(parser, options);
+ return parser.hasMicroformats( node, options );
+ };
+
+
+ function addV1(parser, options) {
+ if (options && options.maps) {
+ if (Array.isArray(options.maps)) {
+ parser.add(options.maps);
+ } else {
+ parser.add([options.maps]);
+ }
+ }
+ }
+
+
+ return External;
+
+
+}));
+try {
+ // mozilla jsm support
+ Components.utils.importGlobalProperties(["URL"]);
+} catch (e) {}
+this.EXPORTED_SYMBOLS = ['Microformats'];
diff --git a/components/microformats/moz.build b/components/microformats/moz.build
new file mode 100644
index 000000000..eadb6fe66
--- /dev/null
+++ b/components/microformats/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_JS_MODULES += ['microformat-shiv.js']
diff --git a/components/microformats/update/package.json b/components/microformats/update/package.json
new file mode 100644
index 000000000..371986694
--- /dev/null
+++ b/components/microformats/update/package.json
@@ -0,0 +1,21 @@
+{
+ "author": "Glenn Jones",
+ "name": "microformat-shiv-updater",
+ "description": "A script for updating microformat-shiv in mozilla-central from source repo",
+ "version": "1.0.0",
+ "license": "MIT",
+ "homepage": "http://microformat-shiv.com",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/glennjones/microformat-shiv-updater.git"
+ },
+ "main": "update.js",
+ "scripts": {
+ "start": "update"
+ },
+ "dependencies": {
+ "download-github-repo": "0.1.x",
+ "fs-extra": "0.19.x",
+ "request": ">=2.68.0"
+ }
+}
diff --git a/components/microformats/update/readme.txt b/components/microformats/update/readme.txt
new file mode 100644
index 000000000..0e41447a8
--- /dev/null
+++ b/components/microformats/update/readme.txt
@@ -0,0 +1,33 @@
+/*!
+ update.js
+
+ This node.js script downloads latest version of microformat-shiv and it tests form the authors github repo.
+
+ Make sure your have an uptodate copy of node.js on your machine then using a command line navigate the
+ directory containing the update.js and run the following commands:
+
+ $ npm install
+ $ node unpdate.js
+
+ The script will
+
+ 1. Checks the current build status of the project.
+ 2. Checks the date of the last commit
+ 3. Downloads and updates the following directories and files:
+ * microformat-shiv.js
+ * test/lib
+ * test/interface-tests
+ * test/module-tests
+ * test/standards-tests
+ * test/static
+ 4. Adds the EXPORTED_SYMBOLS to the bottom of microformat-shiv.js
+ 5. Repath the links in test/module-tests/index.html file
+
+
+ This will update the microformats parser and all the related tests.
+
+
+
+ Copyright (C) 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ */ \ No newline at end of file
diff --git a/components/microformats/update/update.js b/components/microformats/update/update.js
new file mode 100644
index 000000000..80795d523
--- /dev/null
+++ b/components/microformats/update/update.js
@@ -0,0 +1,266 @@
+/* !
+ update.js
+
+ run $ npm install
+ run $ node unpdate.js
+
+ Downloads latest version of microformat-shiv and it tests form github repo
+ Files downloaded:
+ * microformat-shiv.js (note: modern version)
+ * lib
+ * test/interface-tests
+ * test/module-tests
+ * test/standards-tests
+ * test/static
+
+ Copyright (C) 2015 Glenn Jones. All Rights Reserved.
+ MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
+ */
+
+// configuration
+var deployDir = '../'
+ exportedSymbol = 'try {\n // mozilla jsm support\n Components.utils.importGlobalProperties(["URL"]);\n} catch(e) {}\nthis.EXPORTED_SYMBOLS = [\'Microformats\'];';
+
+
+
+var path = require('path'),
+ request = require('request'),
+ fs = require('fs-extra'),
+ download = require('download-github-repo');
+
+
+var repo = 'glennjones/microformat-shiv',
+ tempDir = path.resolve(__dirname, 'temp-repo'),
+ deployDirResolved = path.resolve(__dirname, deployDir),
+ pathList = [
+ ['/modern/microformat-shiv-modern.js', '/microformat-shiv.js'],
+ ['/lib', '/test/lib'],
+ ['/test/interface-tests', '/test/interface-tests'],
+ ['/test/module-tests', '/test/module-tests'],
+ ['/test/standards-tests', '/test/standards-tests'],
+ ['/test/static', '/test/static']
+ ];
+
+
+
+getLastBuildState( repo, function( err, buildState) {
+ if (buildState) {
+ console.log('last build state:', buildState);
+
+ if (buildState === 'passed') {
+
+ console.log('downloading git repo', repo);
+ getLastCommitDate( repo, function( err, date) {
+ if (date) {
+ console.log( 'last commit:', new Date(date).toString() );
+ }
+ });
+ updateFromRepo();
+
+ } else {
+ console.log('not updating because of build state is failing please contact Glenn Jones glennjones@gmail.com');
+ }
+
+ } else {
+ console.log('could not get build state from travis-ci:', err);
+ }
+});
+
+
+/**
+ * updates from directories and files from repo
+ *
+ */
+function updateFromRepo() {
+ download(repo, tempDir, function(err, data) {
+
+ // the err and data from download-github-repo give false negatives
+ if ( fs.existsSync( tempDir ) ) {
+
+ var version = getRepoVersion();
+ removeCurrentFiles( pathList, deployDirResolved );
+ addNewFiles( pathList, deployDirResolved );
+ fs.removeSync(tempDir);
+
+ // changes files for firefox
+ replaceInFile('/test/module-tests/index.html', /..\/..\/lib\//g, '../lib/' );
+ addExportedSymbol( '/microformat-shiv.js' );
+
+ console.log('microformat-shiv is now uptodate to v' + version);
+
+ } else {
+ console.log('error getting repo', err);
+ }
+
+ });
+}
+
+
+/**
+ * removes old version of delpoyed directories and files
+ *
+ * @param {Array} pathList
+ * @param {String} deployDirResolved
+ */
+function removeCurrentFiles( pathList, deployDirResolved ) {
+ pathList.forEach( function( path ) {
+ console.log('removed:', deployDirResolved + path[1]);
+ fs.removeSync(deployDirResolved + path[1]);
+ });
+}
+
+
+/**
+ * copies over required directories and files into deployed path
+ *
+ * @param {Array} pathList
+ * @param {String} deployDirResolved
+ */
+function addNewFiles( pathList, deployDirResolved ) {
+ pathList.forEach( function( path ) {
+ console.log('added:', deployDirResolved + path[1]);
+ fs.copySync(tempDir + path[0], deployDirResolved + path[1]);
+ });
+
+}
+
+
+/**
+ * gets the repo version number
+ *
+ * @return {String}
+ */
+function getRepoVersion() {
+ var pack = fs.readFileSync(path.resolve(tempDir, 'package.json'), {encoding: 'utf8'});
+ if (pack) {
+ pack = JSON.parse(pack)
+ if (pack && pack.version) {
+ return pack.version;
+ }
+ }
+ return '';
+}
+
+
+/**
+ * get the last commit date from github repo
+ *
+ * @param {String} repo
+ * @param {Function} callback
+ */
+function getLastCommitDate( repo, callback ) {
+
+ var options = {
+ url: 'https://api.github.com/repos/' + repo + '/commits?per_page=1',
+ headers: {
+ 'User-Agent': 'request'
+ }
+ };
+
+ request(options, function (error, response, body) {
+ if (!error && response.statusCode == 200) {
+ var date = null,
+ json = JSON.parse(body);
+ if (json && json.length && json[0].commit && json[0].commit.author ) {
+ date = json[0].commit.author.date;
+ }
+ callback(null, date);
+ } else {
+ console.log(error, response, body);
+ callback('fail to get last commit date', null);
+ }
+ });
+}
+
+
+/**
+ * get the last build state from travis-ci
+ *
+ * @param {String} repo
+ * @param {Function} callback
+ */
+function getLastBuildState( repo, callback ) {
+
+ var options = {
+ url: 'https://api.travis-ci.org/repos/' + repo,
+ headers: {
+ 'User-Agent': 'request',
+ 'Accept': 'application/vnd.travis-ci.2+json'
+ }
+ };
+
+ request(options, function (error, response, body) {
+ if (!error && response.statusCode == 200) {
+ var buildState = null,
+ json = JSON.parse(body);
+ if (json && json.repo && json.repo.last_build_state ) {
+ buildState = json.repo.last_build_state;
+ }
+ callback(null, buildState);
+ } else {
+ console.log(error, response, body);
+ callback('fail to get last build state', null);
+ }
+ });
+}
+
+
+/**
+ * adds exported symbol to microformat-shiv.js file
+ *
+ * @param {String} path
+ * @param {String} content
+ */
+function addExportedSymbol( path ) {
+ if (path === '/microformat-shiv.js') {
+ fs.appendFileSync(deployDirResolved + '/microformat-shiv.js', '\r\n' + exportedSymbol + '\r\n');
+ console.log('appended exported symbol to microformat-shiv.js');
+ }
+}
+
+
+/**
+ * adds exported symbol to microformat-shiv.js file
+ *
+ * @param {String} path
+ * @param {String} content
+ */
+function replaceInFile( path, findStr, replaceStr ) {
+ readFile(deployDirResolved + path, function(err, fileStr) {
+ if (fileStr) {
+ fileStr = fileStr.replace(findStr, replaceStr)
+ writeFile(deployDirResolved + path, fileStr);
+ console.log('replaced ' + findStr + ' with ' + replaceStr + ' in ' + path);
+ } else {
+ console.log('error replaced strings in ' + path);
+ }
+ })
+}
+
+
+/**
+ * write a file
+ *
+ * @param {String} path
+ * @param {String} content
+ */
+function writeFile(path, content) {
+ fs.writeFile(path, content, 'utf8', function(err) {
+ if (err) {
+ console.log(err);
+ } else {
+ console.log('The file: ' + path + ' was saved');
+ }
+ });
+}
+
+
+/**
+ * read a file
+ *
+ * @param {String} path
+ * @param {Function} callback
+ */
+function readFile(path, callback) {
+ fs.readFile(path, 'utf8', callback);
+}
diff --git a/components/mork/build/moz.build b/components/mork/build/moz.build
new file mode 100644
index 000000000..6026b2f05
--- /dev/null
+++ b/components/mork/build/moz.build
@@ -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/.
+
+EXPORTS += [
+ 'nsIMdbFactoryFactory.h',
+ 'nsMorkCID.h',
+]
+
+SOURCES += [
+ 'nsMorkFactory.cpp',
+]
+
+if CONFIG['MOZ_INCOMPLETE_EXTERNAL_LINKAGE']:
+ XPCOMBinaryComponent('mork')
+ USE_LIBS += [
+ 'nspr',
+ 'xpcomglue_s',
+ 'xul',
+ ]
+else:
+ Library('mork')
+ FINAL_LIBRARY = 'xul'
diff --git a/components/mork/build/nsIMdbFactoryFactory.h b/components/mork/build/nsIMdbFactoryFactory.h
new file mode 100644
index 000000000..8b5294396
--- /dev/null
+++ b/components/mork/build/nsIMdbFactoryFactory.h
@@ -0,0 +1,30 @@
+/* -*- 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 nsIMdbFactoryFactory_h__
+#define nsIMdbFactoryFactory_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+class nsIMdbFactory;
+
+// 2794D0B7-E740-47a4-91C0-3E4FCB95B806
+#define NS_IMDBFACTORYFACTORY_IID \
+{ 0x2794d0b7, 0xe740, 0x47a4, { 0x91, 0xc0, 0x3e, 0x4f, 0xcb, 0x95, 0xb8, 0x6 } }
+
+// because Mork doesn't support XPCOM, we have to wrap the mdb factory interface
+// with an interface that gives you an mdb factory.
+class nsIMdbFactoryService : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBFACTORYFACTORY_IID)
+ NS_IMETHOD GetMdbFactory(nsIMdbFactory **aFactory) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbFactoryService, NS_IMDBFACTORYFACTORY_IID)
+
+#endif
diff --git a/components/mork/build/nsMorkCID.h b/components/mork/build/nsMorkCID.h
new file mode 100644
index 000000000..79d7a6074
--- /dev/null
+++ b/components/mork/build/nsMorkCID.h
@@ -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/. */
+
+#ifndef nsMorkCID_h__
+#define nsMorkCID_h__
+
+#include "nsISupports.h"
+#include "nsIFactory.h"
+#include "nsIComponentManager.h"
+
+#define NS_MORK_CONTRACTID \
+ "@mozilla.org/db/mork;1"
+
+// 36d90300-27f5-11d3-8d74-00805f8a6617
+#define NS_MORK_CID \
+{ 0x36d90300, 0x27f5, 0x11d3, \
+ { 0x8d, 0x74, 0x00, 0x80, 0x5f, 0x8a, 0x66, 0x17 } }
+
+#endif
diff --git a/components/mork/build/nsMorkFactory.cpp b/components/mork/build/nsMorkFactory.cpp
new file mode 100644
index 000000000..6ca358293
--- /dev/null
+++ b/components/mork/build/nsMorkFactory.cpp
@@ -0,0 +1,56 @@
+/* -*- 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/ModuleUtils.h"
+#include "nsCOMPtr.h"
+#include "nsMorkCID.h"
+#include "nsIMdbFactoryFactory.h"
+#include "mdb.h"
+
+class nsMorkFactoryService final : public nsIMdbFactoryService
+{
+public:
+ nsMorkFactoryService() {};
+ // nsISupports methods
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD GetMdbFactory(nsIMdbFactory **aFactory) override;
+
+protected:
+ ~nsMorkFactoryService() {}
+ nsCOMPtr<nsIMdbFactory> mMdbFactory;
+};
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMorkFactoryService)
+
+NS_DEFINE_NAMED_CID(NS_MORK_CID);
+
+const mozilla::Module::CIDEntry kMorkCIDs[] = {
+ { &kNS_MORK_CID, false, NULL, nsMorkFactoryServiceConstructor },
+ { NULL }
+};
+
+const mozilla::Module::ContractIDEntry kMorkContracts[] = {
+ { NS_MORK_CONTRACTID, &kNS_MORK_CID },
+ { NULL }
+};
+
+static const mozilla::Module kMorkModule = {
+ mozilla::Module::kVersion,
+ kMorkCIDs,
+ kMorkContracts
+};
+
+NSMODULE_DEFN(nsMorkModule) = &kMorkModule;
+
+NS_IMPL_ISUPPORTS(nsMorkFactoryService, nsIMdbFactoryService)
+
+NS_IMETHODIMP nsMorkFactoryService::GetMdbFactory(nsIMdbFactory **aFactory)
+{
+ if (!mMdbFactory)
+ mMdbFactory = MakeMdbFactory();
+ NS_IF_ADDREF(*aFactory = mMdbFactory);
+ return *aFactory ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
diff --git a/components/mork/moz.build b/components/mork/moz.build
new file mode 100644
index 000000000..bcfb69967
--- /dev/null
+++ b/components/mork/moz.build
@@ -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/.
+
+DIRS += [
+ 'public',
+ 'src',
+ 'build',
+]
diff --git a/components/mork/public/mdb.h b/components/mork/public/mdb.h
new file mode 100644
index 000000000..cd2b61293
--- /dev/null
+++ b/components/mork/public/mdb.h
@@ -0,0 +1,2512 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Blake Ross (blake@blakeross.com)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _MDB_
+#define _MDB_ 1
+
+#include "nscore.h"
+#include "nsISupports.h"
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// { %%%%% begin scalar typedefs %%%%%
+typedef unsigned char mdb_u1; // make sure this is one byte
+typedef unsigned short mdb_u2; // make sure this is two bytes
+typedef short mdb_i2; // make sure this is two bytes
+typedef uint32_t mdb_u4; // make sure this is four bytes
+typedef int32_t mdb_i4; // make sure this is four bytes
+typedef PRWord mdb_ip; // make sure sizeof(mdb_ip) == sizeof(void*)
+
+typedef mdb_u1 mdb_bool; // unsigned byte with zero=false, nonzero=true
+
+/* canonical boolean constants provided only for code clarity: */
+#define mdbBool_kTrue ((mdb_bool) 1) /* actually any nonzero means true */
+#define mdbBool_kFalse ((mdb_bool) 0) /* only zero means false */
+
+typedef mdb_u4 mdb_id; // unsigned object identity in a scope
+typedef mdb_id mdb_rid; // unsigned row identity inside scope
+typedef mdb_id mdb_tid; // unsigned table identity inside scope
+typedef mdb_u4 mdb_token; // unsigned token for atomized string
+typedef mdb_token mdb_scope; // token used to id scope for rows
+typedef mdb_token mdb_kind; // token used to id kind for tables
+typedef mdb_token mdb_column; // token used to id columns for rows
+typedef mdb_token mdb_cscode; // token used to id charset names
+typedef mdb_u4 mdb_seed; // unsigned collection change counter
+typedef mdb_u4 mdb_count; // unsigned collection member count
+typedef mdb_u4 mdb_size; // unsigned physical media size
+typedef mdb_u4 mdb_fill; // unsigned logical content size
+typedef mdb_u4 mdb_more; // more available bytes for larger buffer
+
+typedef mdb_u2 mork_uses; // 2-byte strong uses count
+typedef mdb_u2 mork_refs; // 2-byte actual reference count
+
+#define mdbId_kNone ((mdb_id) -1) /* never a valid Mork object ID */
+
+typedef mdb_u4 mdb_percent; // 0..100, with values >100 same as 100
+
+typedef mdb_u1 mdb_priority; // 0..9, for a total of ten different values
+
+// sequence position is signed; negative is useful to mean "before first":
+typedef mdb_i4 mdb_pos; // signed zero-based ordinal collection position
+
+#define mdbPos_kBeforeFirst ((mdb_pos) -1) /* any negative is before zero */
+
+// order is also signed, so we can use three states for comparison order:
+typedef mdb_i4 mdb_order; // neg:lessthan, zero:equalto, pos:greaterthan
+
+typedef mdb_order (* mdbAny_Order)(const void* inA, const void* inB,
+ const void* inClosure);
+
+// } %%%%% end scalar typedefs %%%%%
+
+// { %%%%% begin C structs %%%%%
+
+#ifndef mdbScopeStringSet_typedef
+typedef struct mdbScopeStringSet mdbScopeStringSet;
+#define mdbScopeStringSet_typedef 1
+#endif
+
+/*| mdbScopeStringSet: a set of null-terminated C strings that enumerate some
+**| names of row scopes, so that row scopes intended for use by an application
+**| can be declared by an app when trying to open or create a database file.
+**| (We use strings and not tokens because we cannot know the tokens for any
+**| particular db without having first opened the db.) The goal is to inform
+**| a db runtime that scopes not appearing in this list can be given relatively
+**| short shrift in runtime representation, with the expectation that other
+**| scopes will not actually be used. However, a db should still be prepared
+**| to handle accessing row scopes not in this list, rather than raising errors.
+**| But it could be quite expensive to access a row scope not on the list.
+**| Note a zero count for the string set means no such string set is being
+**| specified, and that a db should handle all row scopes efficiently.
+**| (It does NOT mean an app plans to use no content whatsoever.)
+|*/
+#ifndef mdbScopeStringSet_struct
+#define mdbScopeStringSet_struct 1
+struct mdbScopeStringSet { // vector of scopes for use in db opening policy
+ // when mScopeStringSet_Count is zero, this means no scope constraints
+ mdb_count mScopeStringSet_Count; // number of strings in vector below
+ const char** mScopeStringSet_Strings; // null-ended ascii scope strings
+};
+#endif /*mdbScopeStringSet_struct*/
+
+#ifndef mdbOpenPolicy_typedef
+typedef struct mdbOpenPolicy mdbOpenPolicy;
+#define mdbOpenPolicy_typedef 1
+#endif
+
+#ifndef mdbOpenPolicy_struct
+#define mdbOpenPolicy_struct 1
+struct mdbOpenPolicy { // policies affecting db usage for ports and stores
+ mdbScopeStringSet mOpenPolicy_ScopePlan; // predeclare scope usage plan
+ mdb_bool mOpenPolicy_MaxLazy; // nonzero: do least work
+ mdb_bool mOpenPolicy_MinMemory; // nonzero: use least memory
+};
+#endif /*mdbOpenPolicy_struct*/
+
+#ifndef mdbTokenSet_typedef
+typedef struct mdbTokenSet mdbTokenSet;
+#define mdbTokenSet_typedef 1
+#endif
+
+#ifndef mdbTokenSet_struct
+#define mdbTokenSet_struct 1
+struct mdbTokenSet { // array for a set of tokens, and actual slots used
+ mdb_count mTokenSet_Count; // number of token slots in the array
+ mdb_fill mTokenSet_Fill; // the subset of count slots actually used
+ mdb_more mTokenSet_More; // more tokens available for bigger array
+ mdb_token* mTokenSet_Tokens; // array of count mdb_token instances
+};
+#endif /*mdbTokenSet_struct*/
+
+#ifndef mdbUsagePolicy_typedef
+typedef struct mdbUsagePolicy mdbUsagePolicy;
+#define mdbUsagePolicy_typedef 1
+#endif
+
+/*| mdbUsagePolicy: another version of mdbOpenPolicy which uses tokens instead
+**| of scope strings, because usage policies can be constructed for use with a
+**| db that is already open, while an open policy must be constructed before a
+**| db has yet been opened.
+|*/
+#ifndef mdbUsagePolicy_struct
+#define mdbUsagePolicy_struct 1
+struct mdbUsagePolicy { // policies affecting db usage for ports and stores
+ mdbTokenSet mUsagePolicy_ScopePlan; // current scope usage plan
+ mdb_bool mUsagePolicy_MaxLazy; // nonzero: do least work
+ mdb_bool mUsagePolicy_MinMemory; // nonzero: use least memory
+};
+#endif /*mdbUsagePolicy_struct*/
+
+#ifndef mdbOid_typedef
+typedef struct mdbOid mdbOid;
+#define mdbOid_typedef 1
+#endif
+
+#ifndef mdbOid_struct
+#define mdbOid_struct 1
+struct mdbOid { // identity of some row or table inside a database
+ mdb_scope mOid_Scope; // scope token for an id's namespace
+ mdb_id mOid_Id; // identity of object inside scope namespace
+};
+#endif /*mdbOid_struct*/
+
+#ifndef mdbRange_typedef
+typedef struct mdbRange mdbRange;
+#define mdbRange_typedef 1
+#endif
+
+#ifndef mdbRange_struct
+#define mdbRange_struct 1
+struct mdbRange { // range of row positions in a table
+ mdb_pos mRange_FirstPos; // position of first row
+ mdb_pos mRange_LastPos; // position of last row
+};
+#endif /*mdbRange_struct*/
+
+#ifndef mdbColumnSet_typedef
+typedef struct mdbColumnSet mdbColumnSet;
+#define mdbColumnSet_typedef 1
+#endif
+
+#ifndef mdbColumnSet_struct
+#define mdbColumnSet_struct 1
+struct mdbColumnSet { // array of column tokens (just the same as mdbTokenSet)
+ mdb_count mColumnSet_Count; // number of columns
+ mdb_column* mColumnSet_Columns; // count mdb_column instances
+};
+#endif /*mdbColumnSet_struct*/
+
+#ifndef mdbYarn_typedef
+typedef struct mdbYarn mdbYarn;
+#define mdbYarn_typedef 1
+#endif
+
+#ifdef MDB_BEGIN_C_LINKAGE_define
+#define MDB_BEGIN_C_LINKAGE_define 1
+#define MDB_BEGIN_C_LINKAGE extern "C" {
+#define MDB_END_C_LINKAGE }
+#endif /*MDB_BEGIN_C_LINKAGE_define*/
+
+/*| mdbYarn_mGrow: an abstract API for growing the size of a mdbYarn
+**| instance. With respect to a specific API that requires a caller
+**| to supply a string (mdbYarn) that a callee fills with content
+**| that might exceed the specified size, mdbYarn_mGrow is a caller-
+**| supplied means of letting a callee attempt to increase the string
+**| size to become large enough to receive all content available.
+**|
+**|| Grow(): a method for requesting that a yarn instance be made
+**| larger in size. Note that such requests need not be honored, and
+**| need not be honored in full if only partial size growth is desired.
+**| (Note that no nsIMdbEnv instance is passed as argument, although one
+**| might be needed in some circumstances. So if an nsIMdbEnv is needed,
+**| a reference to one might be held inside a mdbYarn member slot.)
+**|
+**|| self: a yarn instance to be grown. Presumably this yarn is
+**| the instance which holds the mYarn_Grow method pointer. Yarn
+**| instancesshould only be passed to grow methods which they were
+**| specifically designed to fit, as indicated by the mYarn_Grow slot.
+**|
+**|| inNewSize: the new desired value for slot mYarn_Size in self.
+**| If mYarn_Size is already this big, then nothing should be done.
+**| If inNewSize is larger than seems feasible or desirable to honor,
+**| then any size restriction policy can be used to grow to some size
+**| greater than mYarn_Size. (Grow() might even grow to a size
+**| greater than inNewSize in order to make the increase in size seem
+**| worthwhile, rather than growing in many smaller steps over time.)
+|*/
+typedef void (* mdbYarn_mGrow)(mdbYarn* self, mdb_size inNewSize);
+// mdbYarn_mGrow methods must be declared with C linkage in C++
+
+/*| mdbYarn: a variable length "string" of arbitrary binary bytes,
+**| whose length is mYarn_Fill, inside a buffer mYarn_Buf that has
+**| at most mYarn_Size byte of physical space.
+**|
+**|| mYarn_Buf: a pointer to space containing content. This slot
+**| might never be nil when mYarn_Size is nonzero, but checks for nil
+**| are recommended anyway.
+**| (Implementations of mdbYarn_mGrow methods should take care to
+**| ensure the existence of a replacement before dropping old Bufs.)
+**| Content in Buf can be anything in any format, but the mYarn_Form
+**| implies the actual format by some caller-to-callee convention.
+**| mYarn_Form==0 implies US-ASCII iso-8859-1 Latin1 string content.
+**|
+**|| mYarn_Size: the physical size of Buf in bytes. Note that if one
+**| intends to terminate a string with a null byte, that it must not
+**| be written at or after mYarn_Buf[mYarn_Size] because this is after
+**| the last byte in the physical buffer space. Size can be zero,
+**| which means the string has no content whatsoever; note that when
+**| Size is zero, this is a suitable reason for Buf==nil as well.
+**|
+**|| mYarn_Fill: the logical content in Buf in bytes, where Fill must
+**| never exceed mYarn_Size. Note that yarn strings might not have a
+**| terminating null byte (since they might not even be C strings), but
+**| when they do, such terminating nulls are considered part of content
+**| and therefore Fill will count such null bytes. So an "empty" C
+**| string will have Fill==1, because content includes one null byte.
+**| Fill does not mean "length" when applied to C strings for this
+**| reason. However, clients using yarns to hold C strings can infer
+**| that length is equal to Fill-1 (but should take care to handle the
+**| case where Fill==0). To be paranoid, one can always copy to a
+**| destination with size exceeding Fill, and place a redundant null
+**| byte in the Fill position when this simplifies matters.
+**|
+**|| mYarn_Form: a designation of content format within mYarn_Buf.
+**| The semantics of this slot are the least well defined, since the
+**| actual meaning is context dependent, to the extent that callers
+**| and callees must agree on format encoding conventions when such
+**| are not standardized in many computing contexts. However, in the
+**| context of a specific mdb database, mYarn_Form is a token for an
+**| atomized string in that database that typically names a preferred
+**| mime type charset designation. If and when mdbYarn is used for
+**| other purposes away from the mdb interface, folks can use another
+**| convention system for encoding content formats. However, in all
+**| contexts is it useful to maintain the convention that Form==0
+**| implies Buf contains US-ASCII iso-8859-1 Latin1 string content.
+**|
+**|| mYarn_Grow: either a mdbYarn_mGrow method, or else nil. When
+**| a mdbYarn_mGrow method is provided, this method can be used to
+**| request a yarn buf size increase. A caller who constructs the
+**| original mdbYarn instance decides whether a grow method is necessary
+**| or desirable, and uses only grow methods suitable for the buffering
+**| nature of a specific mdbYarn instance. (For example, Buf might be a
+**| statically allocated string space which switches to something heap-based
+**| when grown, and subsequent calls to grow the yarn must distinguish the
+**| original static string from heap allocated space, etc.) Note that the
+**| method stored in mYarn_Grow can change, and this might be a common way
+**| to track memory managent changes in policy for mYarn_Buf.
+|*/
+#ifndef mdbYarn_struct
+#define mdbYarn_struct 1
+struct mdbYarn { // buffer with caller space allocation semantics
+ void* mYarn_Buf; // space for holding any binary content
+ mdb_fill mYarn_Fill; // logical content in Buf in bytes
+ mdb_size mYarn_Size; // physical size of Buf in bytes
+ mdb_more mYarn_More; // more available bytes if Buf is bigger
+ mdb_cscode mYarn_Form; // charset format encoding
+ mdbYarn_mGrow mYarn_Grow; // optional method to grow mYarn_Buf
+
+ // Subclasses might add further slots after mYarn_Grow in order to
+ // maintain bookkeeping needs, such as state info about mYarn_Buf.
+};
+#endif /*mdbYarn_struct*/
+
+// } %%%%% end C structs %%%%%
+
+// { %%%%% begin class forward defines %%%%%
+class nsIMdbEnv;
+class nsIMdbObject;
+class nsIMdbErrorHook;
+class nsIMdbThumb;
+class nsIMdbFactory;
+class nsIMdbFile;
+class nsIMdbPort;
+class nsIMdbStore;
+class nsIMdbCursor;
+class nsIMdbPortTableCursor;
+class nsIMdbCollection;
+class nsIMdbTable;
+class nsIMdbTableRowCursor;
+class nsIMdbRow;
+class nsIMdbRowCellCursor;
+class nsIMdbBlob;
+class nsIMdbCell;
+class nsIMdbSorting;
+// } %%%%% end class forward defines %%%%%
+
+
+// { %%%%% begin C++ abstract class interfaces %%%%%
+
+/*| nsIMdbObject: base class for all message db class interfaces
+**|
+**|| factory: all nsIMdbObjects from the same code suite have the same factory
+**|
+**|| refcounting: both strong and weak references, to ensure strong refs are
+**| acyclic, while weak refs can cause cycles. CloseMdbObject() is
+**| called when (strong) use counts hit zero, but clients can call this close
+**| method early for some reason, if absolutely necessary even though it will
+**| thwart the other uses of the same object. Note that implementations must
+**| cope with close methods being called arbitrary numbers of times. The COM
+**| calls to AddRef() and release ref map directly to strong use ref calls,
+**| but the total ref count for COM objects is the sum of weak & strong refs.
+|*/
+
+#define NS_IMDBOBJECT_IID_STR "5533ea4b-14c3-4bef-ac60-22f9e9a49084"
+
+#define NS_IMDBOBJECT_IID \
+{0x5533ea4b, 0x14c3, 0x4bef, \
+{ 0xac, 0x60, 0x22, 0xf9, 0xe9, 0xa4, 0x90, 0x84}}
+
+class nsIMdbObject : public nsISupports { // msg db base class
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBOBJECT_IID)
+// { ===== begin nsIMdbObject methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD IsFrozenMdbObject(nsIMdbEnv* ev, mdb_bool* outIsReadonly) = 0;
+ // same as nsIMdbPort::GetIsPortReadonly() when this object is inside a port.
+ // } ----- end attribute methods -----
+
+ // { ----- begin factory methods -----
+ NS_IMETHOD GetMdbFactory(nsIMdbEnv* ev, nsIMdbFactory** acqFactory) = 0;
+ // } ----- end factory methods -----
+
+ // { ----- begin ref counting for well-behaved cyclic graphs -----
+ NS_IMETHOD GetWeakRefCount(nsIMdbEnv* ev, // weak refs
+ mdb_count* outCount) = 0;
+ NS_IMETHOD GetStrongRefCount(nsIMdbEnv* ev, // strong refs
+ mdb_count* outCount) = 0;
+
+ NS_IMETHOD AddWeakRef(nsIMdbEnv* ev) = 0;
+ NS_IMETHOD_(mork_uses) AddStrongRef(nsIMdbEnv* ev) = 0;
+
+ NS_IMETHOD CutWeakRef(nsIMdbEnv* ev) = 0;
+ NS_IMETHOD CutStrongRef(nsIMdbEnv* ev) = 0;
+
+ NS_IMETHOD CloseMdbObject(nsIMdbEnv* ev) = 0; // called at strong refs zero
+ NS_IMETHOD IsOpenMdbObject(nsIMdbEnv* ev, mdb_bool* outOpen) = 0;
+ // } ----- end ref counting -----
+
+// } ===== end nsIMdbObject methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbObject, NS_IMDBOBJECT_IID)
+
+/*| nsIMdbErrorHook: a base class for clients of this API to subclass, in order
+**| to provide a callback installable in nsIMdbEnv for error notifications. If
+**| apps that subclass nsIMdbErrorHook wish to maintain a reference to the env
+**| that contains the hook, then this should be a weak ref to avoid cycles.
+**|
+**|| OnError: when nsIMdbEnv has an error condition that causes the total count
+**| of errors to increase, then nsIMdbEnv should call OnError() to report the
+**| error in some fashion when an instance of nsIMdbErrorHook is installed. The
+**| variety of string flavors is currently due to the uncertainty here in the
+**| nsIMdbBlob and nsIMdbCell interfaces. (Note that overloading by using the
+**| same method name is not necessary here, and potentially less clear.)
+|*/
+class nsIMdbErrorHook : public nsISupports{ // env callback handler to report errors
+public:
+
+// { ===== begin error methods =====
+ NS_IMETHOD OnErrorString(nsIMdbEnv* ev, const char* inAscii) = 0;
+ NS_IMETHOD OnErrorYarn(nsIMdbEnv* ev, const mdbYarn* inYarn) = 0;
+// } ===== end error methods =====
+
+// { ===== begin warning methods =====
+ NS_IMETHOD OnWarningString(nsIMdbEnv* ev, const char* inAscii) = 0;
+ NS_IMETHOD OnWarningYarn(nsIMdbEnv* ev, const mdbYarn* inYarn) = 0;
+// } ===== end warning methods =====
+
+// { ===== begin abort hint methods =====
+ NS_IMETHOD OnAbortHintString(nsIMdbEnv* ev, const char* inAscii) = 0;
+ NS_IMETHOD OnAbortHintYarn(nsIMdbEnv* ev, const mdbYarn* inYarn) = 0;
+// } ===== end abort hint methods =====
+};
+
+/*| nsIMdbHeap: abstract memory allocation interface.
+**|
+**|| Alloc: return a block at least inSize bytes in size with alignment
+**| suitable for any native type (such as long integers). When no such
+**| block can be allocated, failure is indicated by a null address in
+**| addition to reporting an error in the environment.
+**|
+**|| Free: deallocate a block allocated or resized earlier by the same
+**| heap instance. If the inBlock parameter is nil, the heap should do
+**| nothing (and crashing is strongly discouraged).
+|*/
+class nsIMdbHeap { // caller-supplied memory management interface
+public:
+// { ===== begin nsIMdbHeap methods =====
+ NS_IMETHOD Alloc(nsIMdbEnv* ev, // allocate a piece of memory
+ mdb_size inSize, // requested byte size of new memory block
+ void** outBlock) = 0; // memory block of inSize bytes, or nil
+
+ NS_IMETHOD Free(nsIMdbEnv* ev, // free block from Alloc or Resize()
+ void* ioBlock) = 0; // block to be destroyed/deallocated
+
+ virtual size_t GetUsedSize() = 0;
+
+ virtual ~nsIMdbHeap() {};
+// } ===== end nsIMdbHeap methods =====
+};
+
+/*| nsIMdbCPlusHeap: Alloc() with global ::new(), Free() with global ::delete().
+**| Resize() is done by ::new() followed by ::delete().
+|*/
+class nsIMdbCPlusHeap { // caller-supplied memory management interface
+public:
+// { ===== begin nsIMdbHeap methods =====
+ NS_IMETHOD Alloc(nsIMdbEnv* ev, // allocate a piece of memory
+ mdb_size inSize, // requested size of new memory block
+ void** outBlock); // memory block of inSize bytes, or nil
+
+ NS_IMETHOD Free(nsIMdbEnv* ev, // free block allocated earlier by Alloc()
+ void* inBlock);
+
+ NS_IMETHOD HeapAddStrongRef(nsIMdbEnv* ev);
+ NS_IMETHOD HeapCutStrongRef(nsIMdbEnv* ev);
+// } ===== end nsIMdbHeap methods =====
+};
+
+/*| nsIMdbThumb:
+|*/
+
+
+#define NS_IMDBTHUMB_IID_STR "6d3ad7c1-a809-4e74-8577-49fa9a4562fa"
+
+#define NS_IMDBTHUMB_IID \
+{0x6d3ad7c1, 0xa809, 0x4e74, \
+{ 0x85, 0x77, 0x49, 0xfa, 0x9a, 0x45, 0x62, 0xfa}}
+
+
+class nsIMdbThumb : public nsISupports { // closure for repeating incremental method
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBTHUMB_IID)
+
+// { ===== begin nsIMdbThumb methods =====
+ NS_IMETHOD GetProgress(nsIMdbEnv* ev,
+ mdb_count* outTotal, // total somethings to do in operation
+ mdb_count* outCurrent, // subportion of total completed so far
+ mdb_bool* outDone, // is operation finished?
+ mdb_bool* outBroken // is operation irreparably dead and broken?
+ ) = 0;
+
+ NS_IMETHOD DoMore(nsIMdbEnv* ev,
+ mdb_count* outTotal, // total somethings to do in operation
+ mdb_count* outCurrent, // subportion of total completed so far
+ mdb_bool* outDone, // is operation finished?
+ mdb_bool* outBroken // is operation irreparably dead and broken?
+ ) = 0;
+
+ NS_IMETHOD CancelAndBreakThumb( // cancel pending operation
+ nsIMdbEnv* ev) = 0;
+// } ===== end nsIMdbThumb methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbThumb, NS_IMDBTHUMB_IID)
+
+/*| nsIMdbEnv: a context parameter used when calling most abstract db methods.
+**| The main purpose of such an object is to permit a database implementation
+**| to avoid the use of globals to share information between various parts of
+**| the implementation behind the abstract db interface. An environment acts
+**| like a session object for a given calling thread, and callers should use
+**| at least one different nsIMdbEnv instance for each thread calling the API.
+**| While the database implementation might not be threaded, it is highly
+**| desirable that the db be thread-safe if calling threads use distinct
+**| instances of nsIMdbEnv. Callers can stop at one nsIMdbEnv per thread, or they
+**| might decide to make on nsIMdbEnv instance for every nsIMdbPort opened, so that
+**| error information is segregated by database instance. Callers create
+**| instances of nsIMdbEnv by calling the MakeEnv() method in nsIMdbFactory.
+**|
+**|| tracing: an environment might support some kind of tracing, and this
+**| boolean attribute permits such activity to be enabled or disabled.
+**|
+**|| errors: when a call to the abstract db interface returns, a caller might
+**| check the number of outstanding errors to see whether the operation did
+**| actually succeed. Each nsIMdbEnv should have all its errors cleared by a
+**| call to ClearErrors() before making each call to the abstract db API,
+**| because outstanding errors might disable further database actions. (This
+**| is not done inside the db interface, because the db cannot in general know
+**| when a call originates from inside or outside -- only the app knows this.)
+**|
+**|| error hook: callers can install an instance of nsIMdbErrorHook to receive
+**| error notifications whenever the error count increases. The hook can
+**| be uninstalled by passing a null pointer.
+**|
+|*/
+
+#define NS_IMDBENV_IID_STR "a765e46b-efb6-41e6-b75b-c5d6bd710594"
+
+#define NS_IMDBENV_IID \
+{0xa765e46b, 0xefb6, 0x41e6, \
+{ 0xb7, 0x5b, 0xc5, 0xd6, 0xbd, 0x71, 0x05, 0x94}}
+
+class nsIMdbEnv : public nsISupports { // db specific context parameter
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBENV_IID)
+// { ===== begin nsIMdbEnv methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD GetErrorCount(mdb_count* outCount,
+ mdb_bool* outShouldAbort) = 0;
+ NS_IMETHOD GetWarningCount(mdb_count* outCount,
+ mdb_bool* outShouldAbort) = 0;
+
+ NS_IMETHOD GetEnvBeVerbose(mdb_bool* outBeVerbose) = 0;
+ NS_IMETHOD SetEnvBeVerbose(mdb_bool inBeVerbose) = 0;
+
+ NS_IMETHOD GetDoTrace(mdb_bool* outDoTrace) = 0;
+ NS_IMETHOD SetDoTrace(mdb_bool inDoTrace) = 0;
+
+ NS_IMETHOD GetAutoClear(mdb_bool* outAutoClear) = 0;
+ NS_IMETHOD SetAutoClear(mdb_bool inAutoClear) = 0;
+
+ NS_IMETHOD GetErrorHook(nsIMdbErrorHook** acqErrorHook) = 0;
+ NS_IMETHOD SetErrorHook(
+ nsIMdbErrorHook* ioErrorHook) = 0; // becomes referenced
+
+ NS_IMETHOD GetHeap(nsIMdbHeap** acqHeap) = 0;
+ NS_IMETHOD SetHeap(
+ nsIMdbHeap* ioHeap) = 0; // becomes referenced
+ // } ----- end attribute methods -----
+
+ NS_IMETHOD ClearErrors() = 0; // clear errors beore re-entering db API
+ NS_IMETHOD ClearWarnings() = 0; // clear warnings
+ NS_IMETHOD ClearErrorsAndWarnings() = 0; // clear both errors & warnings
+// } ===== end nsIMdbEnv methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbEnv, NS_IMDBENV_IID)
+
+/*| nsIMdbFactory: the main entry points to the abstract db interface. A DLL
+**| that supports this mdb interface need only have a single exported method
+**| that will return an instance of nsIMdbFactory, so that further methods in
+**| the suite can be accessed from objects returned by nsIMdbFactory methods.
+**|
+**|| mdbYarn: note all nsIMdbFactory subclasses must guarantee null
+**| termination of all strings written into mdbYarn instances, as long as
+**| mYarn_Size and mYarn_Buf are nonzero. Even truncated string values must
+**| be null terminated. This is more strict behavior than mdbYarn requires,
+**| but it is part of the nsIMdbFactory interface.
+**|
+**|| envs: an environment instance is required as per-thread context for
+**| most of the db method calls, so nsIMdbFactory creates such instances.
+**|
+**|| rows: callers must be able to create row instances that are independent
+**| of storage space that is part of the db content graph. Many interfaces
+**| for data exchange have strictly copy semantics, so that a row instance
+**| has no specific identity inside the db content model, and the text in
+**| cells are an independenty copy of unexposed content inside the db model.
+**| Callers are expected to maintain one or more row instances as a buffer
+**| for staging cell content copied into or out of a table inside the db.
+**| Callers are urged to use an instance of nsIMdbRow created by the nsIMdbFactory
+**| code suite, because reading and writing might be much more efficient than
+**| when using a hand-rolled nsIMdbRow subclass with no relation to the suite.
+**|
+**|| ports: a port is a readonly interface to a specific database file. Most
+**| of the methods to access a db file are suitable for a readonly interface,
+**| so a port is the basic minimum for accessing content. This makes it
+**| possible to read other external formats for import purposes, without
+**| needing the code or competence necessary to write every such format. So
+**| we can write generic import code just once, as long as every format can
+**| show a face based on nsIMdbPort. (However, same suite import can be faster.)
+**| Given a file name and the first 512 bytes of a file, a factory can say if
+**| a port can be opened by this factory. Presumably an app maintains chains
+**| of factories for different suites, and asks each in turn about opening a
+**| a prospective file for reading (as a port) or writing (as a store). I'm
+**| not ready to tackle issues of format fidelity and factory chain ordering.
+**|
+**|| stores: a store is a mutable interface to a specific database file, and
+**| includes the port interface plus any methods particular to writing, which
+**| are few in number. Presumably the set of files that can be opened as
+**| stores is a subset of the set of files that can be opened as ports. A
+**| new store can be created with CreateNewFileStore() by supplying a new
+**| file name which does not yet exist (callers are always responsible for
+**| destroying any existing files before calling this method).
+|*/
+
+#define NS_IMDBFACTORY_IID_STR "2b80395c-b91e-4990-b1a7-023e99ab14e9"
+
+#define NS_IMDBFACTORY_IID \
+{0xf04aa4ab, 0x1fe, 0x4115, \
+{ 0xa4, 0xa5, 0x68, 0x19, 0xdf, 0xf1, 0x10, 0x3d}}
+
+
+class nsIMdbFactory : public nsISupports { // suite entry points
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBFACTORY_IID)
+// { ===== begin nsIMdbFactory methods =====
+
+ // { ----- begin file methods -----
+ NS_IMETHOD OpenOldFile(nsIMdbEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath,
+ mdb_bool inFrozen, nsIMdbFile** acqFile) = 0;
+ // Choose some subclass of nsIMdbFile to instantiate, in order to read
+ // (and write if not frozen) the file known by inFilePath. The file
+ // returned should be open and ready for use, and presumably positioned
+ // at the first byte position of the file. The exact manner in which
+ // files must be opened is considered a subclass specific detail, and
+ // other portions or Mork source code don't want to know how it's done.
+
+ NS_IMETHOD CreateNewFile(nsIMdbEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath,
+ nsIMdbFile** acqFile) = 0;
+ // Choose some subclass of nsIMdbFile to instantiate, in order to read
+ // (and write if not frozen) the file known by inFilePath. The file
+ // returned should be created and ready for use, and presumably positioned
+ // at the first byte position of the file. The exact manner in which
+ // files must be opened is considered a subclass specific detail, and
+ // other portions or Mork source code don't want to know how it's done.
+ // } ----- end file methods -----
+
+ // { ----- begin env methods -----
+ NS_IMETHOD MakeEnv(nsIMdbHeap* ioHeap, nsIMdbEnv** acqEnv) = 0; // acquire new env
+ // ioHeap can be nil, causing a MakeHeap() style heap instance to be used
+ // } ----- end env methods -----
+
+ // { ----- begin heap methods -----
+ NS_IMETHOD MakeHeap(nsIMdbEnv* ev, nsIMdbHeap** acqHeap) = 0; // acquire new heap
+ // } ----- end heap methods -----
+
+ // { ----- begin row methods -----
+ NS_IMETHOD MakeRow(nsIMdbEnv* ev, nsIMdbHeap* ioHeap, nsIMdbRow** acqRow) = 0; // new row
+ // ioHeap can be nil, causing the heap associated with ev to be used
+ // } ----- end row methods -----
+
+ // { ----- begin port methods -----
+ NS_IMETHOD CanOpenFilePort(
+ nsIMdbEnv* ev, // context
+ // const char* inFilePath, // the file to investigate
+ // const mdbYarn* inFirst512Bytes,
+ nsIMdbFile* ioFile, // db abstract file interface
+ mdb_bool* outCanOpen, // whether OpenFilePort() might succeed
+ mdbYarn* outFormatVersion) = 0; // informal file format description
+
+ NS_IMETHOD OpenFilePort(
+ nsIMdbEnv* ev, // context
+ nsIMdbHeap* ioHeap, // can be nil to cause ev's heap attribute to be used
+ // const char* inFilePath, // the file to open for readonly import
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy, // runtime policies for using db
+ nsIMdbThumb** acqThumb) = 0; // acquire thumb for incremental port open
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then call nsIMdbFactory::ThumbToOpenPort() to get the port instance.
+
+ NS_IMETHOD ThumbToOpenPort( // redeeming a completed thumb from OpenFilePort()
+ nsIMdbEnv* ev, // context
+ nsIMdbThumb* ioThumb, // thumb from OpenFilePort() with done status
+ nsIMdbPort** acqPort) = 0; // acquire new port object
+ // } ----- end port methods -----
+
+ // { ----- begin store methods -----
+ NS_IMETHOD CanOpenFileStore(
+ nsIMdbEnv* ev, // context
+ // const char* inFilePath, // the file to investigate
+ // const mdbYarn* inFirst512Bytes,
+ nsIMdbFile* ioFile, // db abstract file interface
+ mdb_bool* outCanOpenAsStore, // whether OpenFileStore() might succeed
+ mdb_bool* outCanOpenAsPort, // whether OpenFilePort() might succeed
+ mdbYarn* outFormatVersion) = 0; // informal file format description
+
+ NS_IMETHOD OpenFileStore( // open an existing database
+ nsIMdbEnv* ev, // context
+ nsIMdbHeap* ioHeap, // can be nil to cause ev's heap attribute to be used
+ // const char* inFilePath, // the file to open for general db usage
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy, // runtime policies for using db
+ nsIMdbThumb** acqThumb) = 0; // acquire thumb for incremental store open
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then call nsIMdbFactory::ThumbToOpenStore() to get the store instance.
+
+ NS_IMETHOD
+ ThumbToOpenStore( // redeem completed thumb from OpenFileStore()
+ nsIMdbEnv* ev, // context
+ nsIMdbThumb* ioThumb, // thumb from OpenFileStore() with done status
+ nsIMdbStore** acqStore) = 0; // acquire new db store object
+
+ NS_IMETHOD CreateNewFileStore( // create a new db with minimal content
+ nsIMdbEnv* ev, // context
+ nsIMdbHeap* ioHeap, // can be nil to cause ev's heap attribute to be used
+ // const char* inFilePath, // name of file which should not yet exist
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy, // runtime policies for using db
+ nsIMdbStore** acqStore) = 0; // acquire new db store object
+ // } ----- end store methods -----
+
+// } ===== end nsIMdbFactory methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbFactory, NS_IMDBFACTORY_IID)
+
+extern "C" nsIMdbFactory* MakeMdbFactory();
+
+/*| nsIMdbFile: abstract file interface resembling the original morkFile
+**| abstract interface (which was in turn modeled on the file interface
+**| from public domain IronDoc). The design of this file interface is
+**| complicated by the fact that some DB's will not find this interface
+**| adequate for all runtime requirements (even though this file API is
+**| enough to implement text-based DB's like Mork). For this reason,
+**| more methods have been added to let a DB library force the file to
+**| become closed so the DB can reopen the file in some other manner.
+**| Folks are encouraged to suggest ways to tune this interface to suit
+**| DB's that cannot manage to pull their maneuvers even given this API.
+**|
+**|| Tell: get the current i/o position in file
+**|
+**|| Seek: change the current i/o position in file
+**|
+**|| Eof: return file's total length in bytes
+**|
+**|| Read: input inSize bytes into outBuf, returning actual transfer size
+**|
+**|| Get: read starting at specific file offset (e.g. Seek(); Read();)
+**|
+**|| Write: output inSize bytes from inBuf, returning actual transfer size
+**|
+**|| Put: write starting at specific file offset (e.g. Seek(); Write();)
+**|
+**|| Flush: if written bytes are buffered, push them to final destination
+**|
+**|| Path: get file path in some string representation. This is intended
+**| either to support the display of file name in a user presentation, or
+**| to support the closing and reopening of the file when the DB needs more
+**| exotic file access than is presented by the nsIMdbFile interface.
+**|
+**|| Steal: tell this file to close any associated i/o stream in the file
+**| system, because the file ioThief intends to reopen the file in order
+**| to provide the MDB implementation with more exotic file access than is
+**| offered by the nsIMdbFile alone. Presumably the thief knows enough
+**| from Path() in order to know which file to reopen. If Steal() is
+**| successful, this file should probably delegate all future calls to
+**| the nsIMdbFile interface down to the thief files, so that even after
+**| the file has been stolen, it can still be read, written, or forcibly
+**| closed (by a call to CloseMdbObject()).
+**|
+**|| Thief: acquire and return thief passed to an earlier call to Steal().
+|*/
+
+#define NS_IMDBFILE_IID_STR "f04aa4ab-1fe7-4115-a4a5-6819dff1103d"
+
+#define NS_IMDBFILE_IID \
+{0xf04aa4ab, 0x1fe, 0x4115, \
+{ 0xa4, 0xa5, 0x68, 0x19, 0xdf, 0xf1, 0x10, 0x3d}}
+
+class nsIMdbFile : public nsISupports { // minimal file interface
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBFILE_IID)
+// { ===== begin nsIMdbFile methods =====
+
+ // { ----- begin pos methods -----
+ NS_IMETHOD Tell(nsIMdbEnv* ev, mdb_pos* outPos) const = 0;
+ NS_IMETHOD Seek(nsIMdbEnv* ev, mdb_pos inPos, mdb_pos *outPos) = 0;
+ NS_IMETHOD Eof(nsIMdbEnv* ev, mdb_pos* outPos) = 0;
+ // } ----- end pos methods -----
+
+ // { ----- begin read methods -----
+ NS_IMETHOD Read(nsIMdbEnv* ev, void* outBuf, mdb_size inSize,
+ mdb_size* outActualSize) = 0;
+ NS_IMETHOD Get(nsIMdbEnv* ev, void* outBuf, mdb_size inSize,
+ mdb_pos inPos, mdb_size* outActualSize) = 0;
+ // } ----- end read methods -----
+
+ // { ----- begin write methods -----
+ NS_IMETHOD Write(nsIMdbEnv* ev, const void* inBuf, mdb_size inSize,
+ mdb_size* outActualSize) = 0;
+ NS_IMETHOD Put(nsIMdbEnv* ev, const void* inBuf, mdb_size inSize,
+ mdb_pos inPos, mdb_size* outActualSize) = 0;
+ NS_IMETHOD Flush(nsIMdbEnv* ev) = 0;
+ // } ----- end attribute methods -----
+
+ // { ----- begin path methods -----
+ NS_IMETHOD Path(nsIMdbEnv* ev, mdbYarn* outFilePath) = 0;
+ // } ----- end path methods -----
+
+ // { ----- begin replacement methods -----
+ NS_IMETHOD Steal(nsIMdbEnv* ev, nsIMdbFile* ioThief) = 0;
+ NS_IMETHOD Thief(nsIMdbEnv* ev, nsIMdbFile** acqThief) = 0;
+ // } ----- end replacement methods -----
+
+ // { ----- begin versioning methods -----
+ NS_IMETHOD BecomeTrunk(nsIMdbEnv* ev) = 0;
+ // If this file is a file version branch created by calling AcquireBud(),
+ // BecomeTrunk() causes this file's content to replace the original
+ // file's content, typically by assuming the original file's identity.
+ // This default implementation of BecomeTrunk() does nothing, and this
+ // is appropriate behavior for files which are not branches, and is
+ // also the right behavior for files returned from AcquireBud() which are
+ // in fact the original file that has been truncated down to zero length.
+
+ NS_IMETHOD AcquireBud(nsIMdbEnv* ev, nsIMdbHeap* ioHeap,
+ nsIMdbFile** acqBud) = 0; // acquired file for new version of content
+ // AcquireBud() starts a new "branch" version of the file, empty of content,
+ // so that a new version of the file can be written. This new file
+ // can later be told to BecomeTrunk() the original file, so the branch
+ // created by budding the file will replace the original file. Some
+ // file subclasses might initially take the unsafe but expedient
+ // approach of simply truncating this file down to zero length, and
+ // then returning the same morkFile pointer as this, with an extra
+ // reference count increment. Note that the caller of AcquireBud() is
+ // expected to eventually call CutStrongRef() on the returned file
+ // in order to release the strong reference. High quality versions
+ // of morkFile subclasses will create entirely new files which later
+ // are renamed to become the old file, so that better transactional
+ // behavior is exhibited by the file, so crashes protect old files.
+ // Note that AcquireBud() is an illegal operation on readonly files.
+ // } ----- end versioning methods -----
+
+// } ===== end nsIMdbFile methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbFile, NS_IMDBFILE_IID)
+
+/*| nsIMdbPort: a readonly interface to a specific database file. The mutable
+**| nsIMdbStore interface is a subclass that includes writing behavior, but
+**| most of the needed db methods appear in the readonly nsIMdbPort interface.
+**|
+**|| mdbYarn: note all nsIMdbPort and nsIMdbStore subclasses must guarantee null
+**| termination of all strings written into mdbYarn instances, as long as
+**| mYarn_Size and mYarn_Buf are nonzero. Even truncated string values must
+**| be null terminated. This is more strict behavior than mdbYarn requires,
+**| but it is part of the nsIMdbPort and nsIMdbStore interface.
+**|
+**|| attributes: methods are provided to distinguish a readonly port from a
+**| mutable store, and whether a mutable store actually has any dirty content.
+**|
+**|| filepath: the file path used to open the port from the nsIMdbFactory can be
+**| queried and discovered by GetPortFilePath(), which includes format info.
+**|
+**|| export: a port can write itself in other formats, with perhaps a typical
+**| emphasis on text interchange formats used by other systems. A port can be
+**| queried to determine its preferred export interchange format, and a port
+**| can be queried to see whether a specific export format is supported. And
+**| actually exporting a port requires a new destination file name and format.
+**|
+**|| tokens: a port supports queries about atomized strings to map tokens to
+**| strings or strings to token integers. (All atomized strings must be in
+**| US-ASCII iso-8859-1 Latin1 charset encoding.) When a port is actually a
+**| mutable store and a string has not yet been atomized, then StringToToken()
+**| will actually do so and modify the store. The QueryToken() method will not
+**| atomize a string if it has not already been atomized yet, even in stores.
+**|
+**|| tables: other than string tokens, all port content is presented through
+**| tables, which are ordered collections of rows. Tables are identified by
+**| row scope and table kind, which might or might not be unique in a port,
+**| depending on app convention. When tables are effectively unique, then
+**| queries for specific scope and kind pairs will find those tables. To see
+**| all tables that match specific row scope and table kind patterns, even in
+**| the presence of duplicates, every port supports a GetPortTableCursor()
+**| method that returns an iterator over all matching tables. Table kind is
+**| considered scoped inside row scope, so passing a zero for table kind will
+**| find all table kinds for some nonzero row scope. Passing a zero for row
+**| scope will iterate over all tables in the port, in some undefined order.
+**| (A new table can be added to a port using nsIMdbStore::NewTable(), even when
+**| the requested scope and kind combination is already used by other tables.)
+**|
+**|| memory: callers can request that a database use less memory footprint in
+**| several flavors, from an inconsequential idle flavor to a rather drastic
+**| panic flavor. Callers might perform an idle purge very frequently if desired
+**| with very little cost, since only normally scheduled memory management will
+**| be conducted, such as freeing resources for objects scheduled to be dropped.
+**| Callers should perform session memory purges infrequently because they might
+**| involve costly scanning of data structures to removed cached content, and
+**| session purges are recommended only when a caller experiences memory crunch.
+**| Callers should only rarely perform a panic purge, in response to dire memory
+**| straits, since this is likely to make db operations much more expensive
+**| than they would be otherwise. A panic purge asks a database to free as much
+**| memory as possible while staying effective and operational, because a caller
+**| thinks application failure might otherwise occur. (Apps might better close
+**| an open db, so panic purges only make sense when a db is urgently needed.)
+|*/
+class nsIMdbPort : public nsISupports {
+public:
+
+// { ===== begin nsIMdbPort methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD GetIsPortReadonly(nsIMdbEnv* ev, mdb_bool* outBool) = 0;
+ NS_IMETHOD GetIsStore(nsIMdbEnv* ev, mdb_bool* outBool) = 0;
+ NS_IMETHOD GetIsStoreAndDirty(nsIMdbEnv* ev, mdb_bool* outBool) = 0;
+
+ NS_IMETHOD GetUsagePolicy(nsIMdbEnv* ev,
+ mdbUsagePolicy* ioUsagePolicy) = 0;
+
+ NS_IMETHOD SetUsagePolicy(nsIMdbEnv* ev,
+ const mdbUsagePolicy* inUsagePolicy) = 0;
+ // } ----- end attribute methods -----
+
+ // { ----- begin memory policy methods -----
+ NS_IMETHOD IdleMemoryPurge( // do memory management already scheduled
+ nsIMdbEnv* ev, // context
+ mdb_size* outEstimatedBytesFreed) = 0; // approximate bytes actually freed
+
+ NS_IMETHOD SessionMemoryPurge( // request specific footprint decrease
+ nsIMdbEnv* ev, // context
+ mdb_size inDesiredBytesFreed, // approximate number of bytes wanted
+ mdb_size* outEstimatedBytesFreed) = 0; // approximate bytes actually freed
+
+ NS_IMETHOD PanicMemoryPurge( // desperately free all possible memory
+ nsIMdbEnv* ev, // context
+ mdb_size* outEstimatedBytesFreed) = 0; // approximate bytes actually freed
+ // } ----- end memory policy methods -----
+
+ // { ----- begin filepath methods -----
+ NS_IMETHOD GetPortFilePath(
+ nsIMdbEnv* ev, // context
+ mdbYarn* outFilePath, // name of file holding port content
+ mdbYarn* outFormatVersion) = 0; // file format description
+
+ NS_IMETHOD GetPortFile(
+ nsIMdbEnv* ev, // context
+ nsIMdbFile** acqFile) = 0; // acquire file used by port or store
+ // } ----- end filepath methods -----
+
+ // { ----- begin export methods -----
+ NS_IMETHOD BestExportFormat( // determine preferred export format
+ nsIMdbEnv* ev, // context
+ mdbYarn* outFormatVersion) = 0; // file format description
+
+ // some tentative suggested import/export formats
+ // "ns:msg:db:port:format:ldif:ns4.0:passthrough" // necessary
+ // "ns:msg:db:port:format:ldif:ns4.5:utf8" // necessary
+ // "ns:msg:db:port:format:ldif:ns4.5:tabbed"
+ // "ns:msg:db:port:format:ldif:ns4.5:binary" // necessary
+ // "ns:msg:db:port:format:html:ns3.0:addressbook" // necessary
+ // "ns:msg:db:port:format:html:display:verbose"
+ // "ns:msg:db:port:format:html:display:concise"
+ // "ns:msg:db:port:format:mork:zany:verbose" // necessary
+ // "ns:msg:db:port:format:mork:zany:atomized" // necessary
+ // "ns:msg:db:port:format:rdf:xml"
+ // "ns:msg:db:port:format:xml:mork"
+ // "ns:msg:db:port:format:xml:display:verbose"
+ // "ns:msg:db:port:format:xml:display:concise"
+ // "ns:msg:db:port:format:xml:print:verbose" // recommended
+ // "ns:msg:db:port:format:xml:print:concise"
+
+ NS_IMETHOD
+ CanExportToFormat( // can export content in given specific format?
+ nsIMdbEnv* ev, // context
+ const char* inFormatVersion, // file format description
+ mdb_bool* outCanExport) = 0; // whether ExportSource() might succeed
+
+ NS_IMETHOD ExportToFormat( // export content in given specific format
+ nsIMdbEnv* ev, // context
+ // const char* inFilePath, // the file to receive exported content
+ nsIMdbFile* ioFile, // destination abstract file interface
+ const char* inFormatVersion, // file format description
+ nsIMdbThumb** acqThumb) = 0; // acquire thumb for incremental export
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the export will be finished.
+
+ // } ----- end export methods -----
+
+ // { ----- begin token methods -----
+ NS_IMETHOD TokenToString( // return a string name for an integer token
+ nsIMdbEnv* ev, // context
+ mdb_token inToken, // token for inTokenName inside this port
+ mdbYarn* outTokenName) = 0; // the type of table to access
+
+ NS_IMETHOD StringToToken( // return an integer token for scope name
+ nsIMdbEnv* ev, // context
+ const char* inTokenName, // Latin1 string to tokenize if possible
+ mdb_token* outToken) = 0; // token for inTokenName inside this port
+
+ // String token zero is never used and never supported. If the port
+ // is a mutable store, then StringToToken() to create a new
+ // association of inTokenName with a new integer token if possible.
+ // But a readonly port will return zero for an unknown scope name.
+
+ NS_IMETHOD QueryToken( // like StringToToken(), but without adding
+ nsIMdbEnv* ev, // context
+ const char* inTokenName, // Latin1 string to tokenize if possible
+ mdb_token* outToken) = 0; // token for inTokenName inside this port
+
+ // QueryToken() will return a string token if one already exists,
+ // but unlike StringToToken(), will not assign a new token if not
+ // already in use.
+
+ // } ----- end token methods -----
+
+ // { ----- begin row methods -----
+ NS_IMETHOD HasRow( // contains a row with the specified oid?
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // hypothetical row oid
+ mdb_bool* outHasRow) = 0; // whether GetRow() might succeed
+
+ NS_IMETHOD GetRowRefCount( // get number of tables that contain a row
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // hypothetical row oid
+ mdb_count* outRefCount) = 0; // number of tables containing inRowKey
+
+ NS_IMETHOD GetRow( // access one row with specific oid
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // hypothetical row oid
+ nsIMdbRow** acqRow) = 0; // acquire specific row (or null)
+
+ // NS_IMETHOD
+ // GetPortRowCursor( // get cursor for all rows in specific scope
+ // nsIMdbEnv* ev, // context
+ // mdb_scope inRowScope, // row scope for row ids
+ // nsIMdbPortRowCursor** acqCursor) = 0; // all such rows in the port
+
+ NS_IMETHOD FindRow(nsIMdbEnv* ev, // search for row with matching cell
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_column inColumn, // the column to search (and maintain an index)
+ const mdbYarn* inTargetCellValue, // cell value for which to search
+ mdbOid* outRowOid, // out row oid on match (or {0,-1} for no match)
+ nsIMdbRow** acqRow) = 0; // acquire matching row (or nil for no match)
+ // can be null if you only want the oid
+ // FindRow() searches for one row that has a cell in column inColumn with
+ // a contained value with the same form (i.e. charset) and is byte-wise
+ // identical to the blob described by yarn inTargetCellValue. Both content
+ // and form of the yarn must be an exact match to find a matching row.
+ //
+ // (In other words, both a yarn's blob bytes and form are significant. The
+ // form is not expected to vary in columns used for identity anyway. This
+ // is intended to make the cost of FindRow() cheaper for MDB implementors,
+ // since any cell value atomization performed internally must necessarily
+ // make yarn form significant in order to avoid data loss in atomization.)
+ //
+ // FindRow() can lazily create an index on attribute inColumn for all rows
+ // with that attribute in row space scope inRowScope, so that subsequent
+ // calls to FindRow() will perform faster. Such an index might or might
+ // not be persistent (but this seems desirable if it is cheap to do so).
+ // Note that lazy index creation in readonly DBs is not very feasible.
+ //
+ // This FindRow() interface assumes that attribute inColumn is effectively
+ // an alternative means of unique identification for a row in a rowspace,
+ // so correct behavior is only guaranteed when no duplicates for this col
+ // appear in the given set of rows. (If more than one row has the same cell
+ // value in this column, no more than one will be found; and cutting one of
+ // two duplicate rows can cause the index to assume no other such row lives
+ // in the row space, so future calls return nil for negative search results
+ // even though some duplicate row might still live within the rowspace.)
+ //
+ // In other words, the FindRow() implementation is allowed to assume simple
+ // hash tables mapping unqiue column keys to associated row values will be
+ // sufficient, where any duplication is not recorded because only one copy
+ // of a given key need be remembered. Implementors are not required to sort
+ // all rows by the specified column.
+ // } ----- end row methods -----
+
+ // { ----- begin table methods -----
+ NS_IMETHOD HasTable( // supports a table with the specified oid?
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // hypothetical table oid
+ mdb_bool* outHasTable) = 0; // whether GetTable() might succeed
+
+ NS_IMETHOD GetTable( // access one table with specific oid
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // hypothetical table oid
+ nsIMdbTable** acqTable) = 0; // acquire specific table (or null)
+
+ NS_IMETHOD HasTableKind( // supports a table of the specified type?
+ nsIMdbEnv* ev, // context
+ mdb_scope inRowScope, // rid scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ mdb_count* outTableCount, // current number of such tables
+ mdb_bool* outSupportsTable) = 0; // whether GetTableKind() might succeed
+
+ // row scopes to be supported include the following suggestions:
+ // "ns:msg:db:row:scope:address:cards:all"
+ // "ns:msg:db:row:scope:mail:messages:all"
+ // "ns:msg:db:row:scope:news:articles:all"
+
+ // table kinds to be supported include the following suggestions:
+ // "ns:msg:db:table:kind:address:cards:main"
+ // "ns:msg:db:table:kind:address:lists:all"
+ // "ns:msg:db:table:kind:address:list"
+ // "ns:msg:db:table:kind:news:threads:all"
+ // "ns:msg:db:table:kind:news:thread"
+ // "ns:msg:db:table:kind:mail:threads:all"
+ // "ns:msg:db:table:kind:mail:thread"
+
+ NS_IMETHOD GetTableKind( // access one (random) table of specific type
+ nsIMdbEnv* ev, // context
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ mdb_count* outTableCount, // current number of such tables
+ mdb_bool* outMustBeUnique, // whether port can hold only one of these
+ nsIMdbTable** acqTable) = 0; // acquire scoped collection of rows
+
+ NS_IMETHOD
+ GetPortTableCursor( // get cursor for all tables of specific type
+ nsIMdbEnv* ev, // context
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ nsIMdbPortTableCursor** acqCursor) = 0; // all such tables in the port
+ // } ----- end table methods -----
+
+
+ // { ----- begin commit methods -----
+
+ NS_IMETHOD ShouldCompress( // store wastes at least inPercentWaste?
+ nsIMdbEnv* ev, // context
+ mdb_percent inPercentWaste, // 0..100 percent file size waste threshold
+ mdb_percent* outActualWaste, // 0..100 percent of file actually wasted
+ mdb_bool* outShould) = 0; // true when about inPercentWaste% is wasted
+ // ShouldCompress() returns true if the store can determine that the file
+ // will shrink by an estimated percentage of inPercentWaste% (or more) if
+ // CompressCommit() is called, because that percentage of the file seems
+ // to be recoverable free space. The granularity is only in terms of
+ // percentage points, and any value over 100 is considered equal to 100.
+ //
+ // If a store only has an approximate idea how much space might be saved
+ // during a compress, then a best guess should be made. For example, the
+ // Mork implementation might keep track of how much file space began with
+ // text content before the first updating transaction, and then consider
+ // all content following the start of the first transaction as potentially
+ // wasted space if it is all updates and not just new content. (This is
+ // a safe assumption in the sense that behavior will stabilize on a low
+ // estimate of wastage after a commit removes all transaction updates.)
+ //
+ // Some db formats might attempt to keep a very accurate reckoning of free
+ // space size, so a very accurate determination can be made. But other db
+ // formats might have difficulty determining size of free space, and might
+ // require some lengthy calculation to answer. This is the reason for
+ // passing in the percentage threshold of interest, so that such lengthy
+ // computations can terminate early as soon as at least inPercentWaste is
+ // found, so that the entire file need not be groveled when unnecessary.
+ // However, we hope implementations will always favor fast but imprecise
+ // heuristic answers instead of extremely slow but very precise answers.
+ //
+ // If the outActualWaste parameter is non-nil, it will be used to return
+ // the actual estimated space wasted as a percentage of file size. (This
+ // parameter is provided so callers need not call repeatedly with altered
+ // inPercentWaste values to isolate the actual wastage figure.) Note the
+ // actual wastage figure returned can exactly equal inPercentWaste even
+ // when this grossly underestimates the real figure involved, if the db
+ // finds it very expensive to determine the extent of wastage after it is
+ // known to at least exceed inPercentWaste. Note we expect that whenever
+ // outShould returns true, that outActualWaste returns >= inPercentWaste.
+ //
+ // The effect of different inPercentWaste values is not very uniform over
+ // the permitted range. For example, 50 represents 50% wastage, or a file
+ // that is about double what it should be ideally. But 99 represents 99%
+ // wastage, or a file that is about ninety-nine times as big as it should
+ // be ideally. In the smaller direction, 25 represents 25% wastage, or
+ // a file that is only 33% larger than it should be ideally.
+ //
+ // Callers can determine what policy they want to use for considering when
+ // a file holds too much wasted space, and express this as a percentage
+ // of total file size to pass as in the inPercentWaste parameter. A zero
+ // likely returns always trivially true, and 100 always trivially false.
+ // The great majority of callers are expected to use values from 25 to 75,
+ // since most plausible thresholds for compressing might fall between the
+ // extremes of 133% of ideal size and 400% of ideal size. (Presumably the
+ // larger a file gets, the more important the percentage waste involved, so
+ // a sliding scale for compress thresholds might use smaller numbers for
+ // much bigger file sizes.)
+
+ // } ----- end commit methods -----
+
+// } ===== end nsIMdbPort methods =====
+};
+
+/*| nsIMdbStore: a mutable interface to a specific database file.
+**|
+**|| tables: one can force a new table to exist in a store with NewTable()
+**| and nonzero values for both row scope and table kind. (If one wishes only
+**| one table of a certain kind, then one might look for it first using the
+**| GetTableKind() method). One can pass inMustBeUnique to force future
+**| users of this store to be unable to create other tables with the same pair
+**| of scope and kind attributes. When inMustBeUnique is true, and the table
+**| with the given scope and kind pair already exists, then the existing one
+**| is returned instead of making a new table. Similarly, if one passes false
+**| for inMustBeUnique, but the table kind has already been marked unique by a
+**| previous user of the store, then the existing unique table is returned.
+**|
+**|| import: all or some of another port's content can be imported by calling
+**| AddPortContent() with a row scope identifying the extent of content to
+**| be imported. A zero row scope will import everything. A nonzero row
+**| scope will only import tables with a matching row scope. Note that one
+**| must somehow find a way to negotiate possible conflicts between existing
+**| row content and imported row content, and this involves a specific kind of
+**| definition for row identity involving either row IDs or unique attributes,
+**| or some combination of these two. At the moment I am just going to wave
+**| my hands, and say the default behavior is to assign all new row identities
+**| to all imported content, which will result in no merging of content; this
+**| must change later because it is unacceptable in some contexts.
+**|
+**|| commits: to manage modifications in a mutable store, very few methods are
+**| really needed to indicate global policy choices that are independent of
+**| the actual modifications that happen in objects at the level of tables,
+**| rows, and cells, etc. The most important policy to specify is which sets
+**| of changes are considered associated in a manner such that they should be
+**| applied together atomically to a given store. We call each such group of
+**| changes a transaction. We handle three different grades of transaction,
+**| but they differ only in semantic significance to the application, and are
+**| not intended to nest. (If small transactions were nested inside large
+**| transactions, that would imply that a single large transaction must be
+**| atomic over all the contained small transactions; but actually we intend
+**| smalls transaction never be undone once committed due to, say, aborting a
+**| transaction of greater significance.) The small, large, and session level
+**| commits have equal granularity, and differ only in risk of loss from the
+**| perspective of an application. Small commits characterize changes that
+**| can be lost with relatively small risk, so small transactions can delay
+**| until later if they are expensive or impractical to commit. Large commits
+**| involve changes that would probably inconvenience users if lost, so the
+**| need to pay costs of writing is rather greater than with small commits.
+**| Session commits are last ditch attempts to save outstanding changes before
+**| stopping the use of a particular database, so there will be no later point
+**| in time to save changes that have been delayed due to possible high cost.
+**| If large commits are never delayed, then a session commit has about the
+**| same performance effect as another large commit; but if small and large
+**| commits are always delayed, then a session commit is likely to be rather
+**| expensive as a runtime cost compared to any earlier database usage.
+**|
+**|| aborts: the only way to abort changes to a store is by closing the store.
+**| So there is no specific method for causing any abort. Stores must discard
+**| all changes made that are uncommitted when a store is closed. This design
+**| choice makes the implementations of tables, rows, and cells much less
+**| complex because they need not maintain a record of undobable changes. When
+**| a store is closed, presumably this precipitates the closure of all tables,
+**| rows, and cells in the store as well. So an application can revert the
+**| state of a store in the user interface by quietly closing and reopening a
+**| store, because this will discard uncommitted changes and show old content.
+**| This implies an app that closes a store will need to send a "scramble"
+**| event notification to any views that depend on old discarded content.
+|*/
+
+#define NS_IMDBSTORE_IID_STR "74d6218d-44b0-43b5-9ebe-69a17dfb562c"
+#define NS_IMDBSTORE_IID \
+{0x74d6218d, 0x44b0, 0x43b5, \
+{0x9e, 0xbe, 0x69, 0xa1, 0x7d, 0xfb, 0x56, 0x2c}}
+
+class nsIMdbStore : public nsIMdbPort {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBSTORE_IID)
+
+// { ===== begin nsIMdbStore methods =====
+
+ // { ----- begin table methods -----
+ NS_IMETHOD NewTable( // make one new table of specific type
+ nsIMdbEnv* ev, // context
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ mdb_bool inMustBeUnique, // whether store can hold only one of these
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ nsIMdbTable** acqTable) = 0; // acquire scoped collection of rows
+
+ NS_IMETHOD NewTableWithOid( // make one new table of specific type
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // caller assigned oid
+ mdb_kind inTableKind, // the type of table to access
+ mdb_bool inMustBeUnique, // whether store can hold only one of these
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ nsIMdbTable** acqTable) = 0; // acquire scoped collection of rows
+ // } ----- end table methods -----
+
+ // { ----- begin row scope methods -----
+ NS_IMETHOD RowScopeHasAssignedIds(nsIMdbEnv* ev,
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
+ mdb_bool* outStoreAssigned) = 0; // nonzero if store db assigned specified
+
+ NS_IMETHOD SetCallerAssignedIds(nsIMdbEnv* ev,
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
+ mdb_bool* outStoreAssigned) = 0; // nonzero if store db assigned specified
+
+ NS_IMETHOD SetStoreAssignedIds(nsIMdbEnv* ev,
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
+ mdb_bool* outStoreAssigned) = 0; // nonzero if store db assigned specified
+ // } ----- end row scope methods -----
+
+ // { ----- begin row methods -----
+ NS_IMETHOD NewRowWithOid(nsIMdbEnv* ev, // new row w/ caller assigned oid
+ const mdbOid* inOid, // caller assigned oid
+ nsIMdbRow** acqRow) = 0; // create new row
+
+ NS_IMETHOD NewRow(nsIMdbEnv* ev, // new row with db assigned oid
+ mdb_scope inRowScope, // row scope for row ids
+ nsIMdbRow** acqRow) = 0; // create new row
+ // Note this row must be added to some table or cell child before the
+ // store is closed in order to make this row persist across sessions.
+
+ // } ----- end row methods -----
+
+ // { ----- begin inport/export methods -----
+ NS_IMETHOD ImportContent( // import content from port
+ nsIMdbEnv* ev, // context
+ mdb_scope inRowScope, // scope for rows (or zero for all?)
+ nsIMdbPort* ioPort, // the port with content to add to store
+ nsIMdbThumb** acqThumb) = 0; // acquire thumb for incremental import
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the import will be finished.
+
+ NS_IMETHOD ImportFile( // import content from port
+ nsIMdbEnv* ev, // context
+ nsIMdbFile* ioFile, // the file with content to add to store
+ nsIMdbThumb** acqThumb) = 0; // acquire thumb for incremental import
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the import will be finished.
+ // } ----- end inport/export methods -----
+
+ // { ----- begin hinting methods -----
+ NS_IMETHOD
+ ShareAtomColumnsHint( // advise re shared column content atomizing
+ nsIMdbEnv* ev, // context
+ mdb_scope inScopeHint, // zero, or suggested shared namespace
+ const mdbColumnSet* inColumnSet) = 0; // cols desired tokenized together
+
+ NS_IMETHOD
+ AvoidAtomColumnsHint( // advise column with poor atomizing prospects
+ nsIMdbEnv* ev, // context
+ const mdbColumnSet* inColumnSet) = 0; // cols with poor atomizing prospects
+ // } ----- end hinting methods -----
+
+ // { ----- begin commit methods -----
+ NS_IMETHOD LargeCommit( // save important changes if at all possible
+ nsIMdbEnv* ev, // context
+ nsIMdbThumb** acqThumb) = 0; // acquire thumb for incremental commit
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the commit will be finished. Note the store is effectively write
+ // locked until commit is finished or canceled through the thumb instance.
+ // Until the commit is done, the store will report it has readonly status.
+
+ NS_IMETHOD SessionCommit( // save all changes if large commits delayed
+ nsIMdbEnv* ev, // context
+ nsIMdbThumb** acqThumb) = 0; // acquire thumb for incremental commit
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the commit will be finished. Note the store is effectively write
+ // locked until commit is finished or canceled through the thumb instance.
+ // Until the commit is done, the store will report it has readonly status.
+
+ NS_IMETHOD
+ CompressCommit( // commit and make db physically smaller if possible
+ nsIMdbEnv* ev, // context
+ nsIMdbThumb** acqThumb) = 0; // acquire thumb for incremental commit
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the commit will be finished. Note the store is effectively write
+ // locked until commit is finished or canceled through the thumb instance.
+ // Until the commit is done, the store will report it has readonly status.
+
+ // } ----- end commit methods -----
+
+// } ===== end nsIMdbStore methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbStore, NS_IMDBSTORE_IID)
+
+/*| nsIMdbCursor: base cursor class for iterating row cells and table rows
+**|
+**|| count: the number of elements in the collection (table or row)
+**|
+**|| seed: the change count in the underlying collection, which is synced
+**| with the collection when the iteration position is set, and henceforth
+**| acts to show whether the iter has lost collection synchronization, in
+**| case it matters to clients whether any change happens during iteration.
+**|
+**|| pos: the position of the current element in the collection. Negative
+**| means a position logically before the first element. A positive value
+**| equal to count (or larger) implies a position after the last element.
+**| To iterate over all elements, set the position to negative, so subsequent
+**| calls to any 'next' method will access the first collection element.
+**|
+**|| doFailOnSeedOutOfSync: whether a cursor should return an error if the
+**| cursor's snapshot of a table's seed becomes stale with respect the table's
+**| current seed value (which implies the iteration is less than total) in
+**| between to cursor calls that actually access collection content. By
+**| default, a cursor should assume this attribute is false until specified,
+**| so that iterations quietly try to re-sync when they lose coherence.
+|*/
+
+#define NS_IMDBCURSOR_IID_STR "a0c37337-6ebc-474c-90db-e65ea0b850aa"
+
+#define NS_IMDBCURSOR_IID \
+{0xa0c37337, 0x6ebc, 0x474c, \
+{0x90, 0xdb, 0xe6, 0x5e, 0xa0, 0xb8, 0x50, 0xaa}}
+
+class nsIMdbCursor : public nsISupports { // collection iterator
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBCURSOR_IID)
+// { ===== begin nsIMdbCursor methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD GetCount(nsIMdbEnv* ev, mdb_count* outCount) = 0; // readonly
+ NS_IMETHOD GetSeed(nsIMdbEnv* ev, mdb_seed* outSeed) = 0; // readonly
+
+ NS_IMETHOD SetPos(nsIMdbEnv* ev, mdb_pos inPos) = 0; // mutable
+ NS_IMETHOD GetPos(nsIMdbEnv* ev, mdb_pos* outPos) = 0;
+
+ NS_IMETHOD SetDoFailOnSeedOutOfSync(nsIMdbEnv* ev, mdb_bool inFail) = 0;
+ NS_IMETHOD GetDoFailOnSeedOutOfSync(nsIMdbEnv* ev, mdb_bool* outFail) = 0;
+ // } ----- end attribute methods -----
+
+// } ===== end nsIMdbCursor methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbCursor, NS_IMDBCURSOR_IID)
+
+#define NS_IMDBPORTTABLECURSOR_IID_STR = "f181a41e-933d-49b3-af93-20d3634b8b78"
+
+#define NS_IMDBPORTTABLECURSOR_IID \
+{0xf181a41e, 0x933d, 0x49b3, \
+{0xaf, 0x93, 0x20, 0xd3, 0x63, 0x4b, 0x8b, 0x78}}
+
+/*| nsIMdbPortTableCursor: cursor class for iterating port tables
+**|
+**|| port: the cursor is associated with a specific port, which can be
+**| set to a different port (which resets the position to -1 so the
+**| next table acquired is the first in the port.
+**|
+|*/
+class nsIMdbPortTableCursor : public nsISupports { // table collection iterator
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBPORTTABLECURSOR_IID)
+// { ===== begin nsIMdbPortTableCursor methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD SetPort(nsIMdbEnv* ev, nsIMdbPort* ioPort) = 0; // sets pos to -1
+ NS_IMETHOD GetPort(nsIMdbEnv* ev, nsIMdbPort** acqPort) = 0;
+
+ NS_IMETHOD SetRowScope(nsIMdbEnv* ev, // sets pos to -1
+ mdb_scope inRowScope) = 0;
+ NS_IMETHOD GetRowScope(nsIMdbEnv* ev, mdb_scope* outRowScope) = 0;
+ // setting row scope to zero iterates over all row scopes in port
+
+ NS_IMETHOD SetTableKind(nsIMdbEnv* ev, // sets pos to -1
+ mdb_kind inTableKind) = 0;
+ NS_IMETHOD GetTableKind(nsIMdbEnv* ev, mdb_kind* outTableKind) = 0;
+ // setting table kind to zero iterates over all table kinds in row scope
+ // } ----- end attribute methods -----
+
+ // { ----- begin table iteration methods -----
+ NS_IMETHOD NextTable( // get table at next position in the db
+ nsIMdbEnv* ev, // context
+ nsIMdbTable** acqTable) = 0; // the next table in the iteration
+ // } ----- end table iteration methods -----
+
+// } ===== end nsIMdbPortTableCursor methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbPortTableCursor,
+ NS_IMDBPORTTABLECURSOR_IID)
+
+/*| nsIMdbCollection: an object that collects a set of other objects as members.
+**| The main purpose of this base class is to unify the perceived semantics
+**| of tables and rows where their collection behavior is similar. This helps
+**| isolate the mechanics of collection behavior from the other semantics that
+**| are more characteristic of rows and tables.
+**|
+**|| count: the number of objects in a collection is the member count. (Some
+**| collection interfaces call this attribute the 'size', but that can be a
+**| little ambiguous, and counting actual members is harder to confuse.)
+**|
+**|| seed: the seed of a collection is a counter for changes in membership in
+**| a specific collection. This seed should change when members are added to
+**| or removed from a collection, but not when a member changes internal state.
+**| The seed should also change whenever the internal collection of members has
+**| a complex state change that reorders member positions (say by sorting) that
+**| would affect the nature of an iteration over that collection of members.
+**| The purpose of a seed is to inform any outstanding collection cursors that
+**| they might be stale, without incurring the cost of broadcasting an event
+**| notification to such cursors, which would need more data structure support.
+**| Presumably a cursor in a particular mdb code suite has much more direct
+**| access to a collection seed member slot that this abstract COM interface,
+**| so this information is intended more for clients outside mdb that want to
+**| make inferences similar to those made by the collection cursors. The seed
+**| value as an integer magnitude is not very important, and callers should not
+**| assume meaningful information can be derived from an integer value beyond
+**| whether it is equal or different from a previous inspection. A seed uses
+**| integers of many bits in order to make the odds of wrapping and becoming
+**| equal to an earlier seed value have probability that is vanishingly small.
+**|
+**|| port: every collection is associated with a specific database instance.
+**|
+**|| cursor: a subclass of nsIMdbCursor suitable for this specific collection
+**| subclass. The ability to GetCursor() from the base nsIMdbCollection class
+**| is not really as useful as getting a more specifically typed cursor more
+**| directly from the base class without any casting involved. So including
+**| this method here is more for conceptual illustration.
+**|
+**|| oid: every collection has an identity that persists from session to
+**| session. Implementations are probably able to distinguish row IDs from
+**| table IDs, but we don't specify anything official in this regard. A
+**| collection has the same identity for the lifetime of the collection,
+**| unless identity is swapped with another collection by means of a call to
+**| BecomeContent(), which is considered a way to swap a new representation
+**| for an old well-known object. (Even so, only content appears to change,
+**| while the identity seems to stay the same.)
+**|
+**|| become: developers can effectively cause two objects to swap identities,
+**| in order to effect a complete swap between what persistent content is
+**| represented by two oids. The caller should consider this a content swap,
+**| and not identity wap, because identities will seem to stay the same while
+**| only content changes. However, implementations will likely do this
+**| internally by swapping identities. Callers must swap content only
+**| between objects of similar type, such as a row with another row, and a
+**| table with another table, because implementations need not support
+**| cross-object swapping because it might break object name spaces.
+**|
+**|| dropping: when a caller expects a row or table will no longer be used, the
+**| caller can tell the collection to 'drop activity', which means the runtime
+**| object can have its internal representation purged to save memory or any
+**| other resource that is being consumed by the collection's representation.
+**| This has no effect on the collection's persistent content or semantics,
+**| and is only considered a runtime effect. After a collection drops
+**| activity, the object should still be as usable as before (because it has
+**| NOT been closed), but further usage can be expensive to re-instate because
+**| it might involve reallocating space and/or re-reading disk space. But
+**| since this future usage is not expected, the caller does not expect to
+**| pay the extra expense. An implementation can choose to implement
+**| 'dropping activity' in different ways, or even not at all if this
+**| operation is not really feasible. Callers cannot ask objects whether they
+**| are 'dropped' or not, so this should be transparent. (Note that
+**| implementors might fear callers do not really know whether future
+**| usage will occur, and therefore might delay the act of dropping until
+**| the near future, until seeing whether the object is used again
+**| immediately elsewhere. Such use soon after the drop request might cause
+**| the drop to be cancelled.)
+|*/
+class nsIMdbCollection : public nsISupports { // sequence of objects
+public:
+
+// { ===== begin nsIMdbCollection methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD GetSeed(nsIMdbEnv* ev,
+ mdb_seed* outSeed) = 0; // member change count
+ NS_IMETHOD GetCount(nsIMdbEnv* ev,
+ mdb_count* outCount) = 0; // member count
+
+ NS_IMETHOD GetPort(nsIMdbEnv* ev,
+ nsIMdbPort** acqPort) = 0; // collection container
+ // } ----- end attribute methods -----
+
+ // { ----- begin cursor methods -----
+ NS_IMETHOD GetCursor( // make a cursor starting iter at inMemberPos
+ nsIMdbEnv* ev, // context
+ mdb_pos inMemberPos, // zero-based ordinal pos of member in collection
+ nsIMdbCursor** acqCursor) = 0; // acquire new cursor instance
+ // } ----- end cursor methods -----
+
+ // { ----- begin ID methods -----
+ NS_IMETHOD GetOid(nsIMdbEnv* ev,
+ mdbOid* outOid) = 0; // read object identity
+ NS_IMETHOD BecomeContent(nsIMdbEnv* ev,
+ const mdbOid* inOid) = 0; // exchange content
+ // } ----- end ID methods -----
+
+ // { ----- begin activity dropping methods -----
+ NS_IMETHOD DropActivity( // tell collection usage no longer expected
+ nsIMdbEnv* ev) = 0;
+ // } ----- end activity dropping methods -----
+
+// } ===== end nsIMdbCollection methods =====
+};
+
+/*| nsIMdbTable: an ordered collection of rows
+**|
+**|| row scope: an integer token for an atomized string in this database
+**| that names a space for row IDs. This attribute of a table is intended
+**| as guidance metainformation that helps with searching a database for
+**| tables that operate on collections of rows of the specific type. By
+**| convention, a table with a specific row scope is expected to focus on
+**| containing rows that belong to that scope, however exceptions are easily
+**| allowed because all rows in a table are known by both row ID and scope.
+**| (A table with zero row scope is never allowed because this would make it
+**| ambiguous to use a zero row scope when iterating over tables in a port to
+**| indicate that all row scopes should be seen by a cursor.)
+**|
+**|| table kind: an integer token for an atomized string in this database
+**| that names a kind of table as a subset of the associated row scope. This
+**| attribute is intended as guidance metainformation to clarify the role of
+**| this table with respect to other tables in the same row scope, and this
+**| also helps search for such tables in a database. By convention, a table
+**| with a specific table kind has a consistent role for containing rows with
+**| respect to other collections of such rows in the same row scope. Also by
+**| convention, at least one table in a row scope has a table kind purporting
+**| to contain ALL the rows that belong in that row scope, so that at least
+**| one table exists that allows all rows in a scope to be interated over.
+**| (A table with zero table kind is never allowed because this would make it
+**| ambiguous to use a zero table kind when iterating over tables in a port to
+**| indicate that all table kinds in a row scope should be seen by a cursor.)
+**|
+**|| port: every table is considered part of some port that contains the
+**| table, so that closing the containing port will cause the table to be
+**| indirectly closed as well. We make it easy to get the containing port for
+**| a table, because the port supports important semantic interfaces that will
+**| affect how content in table is presented; the most important port context
+**| that affects a table is specified by the set of token to string mappings
+**| that affect all tokens used throughout the database, and which drive the
+**| meanings of row scope, table kind, cell columns, etc.
+**|
+**|| cursor: a cursor that iterates over the rows in this table, where rows
+**| have zero-based index positions from zero to count-1. Making a cursor
+**| with negative position will next iterate over the first row in the table.
+**|
+**|| position: given any position from zero to count-1, a table will return
+**| the row ID and row scope for the row at that position. (One can use the
+**| GetRowAllCells() method to read that row, or else use a row cursor to both
+**| get the row at some position and read its content at the same time.) The
+**| position depends on whether a table is sorted, and upon the actual sort.
+**| Note that moving a row's position is only possible in unsorted tables.
+**|
+**|| row set: every table contains a collection of rows, where a member row is
+**| referenced by the table using the row ID and row scope for the row. No
+**| single table owns a given row instance, because rows are effectively ref-
+**| counted and destroyed only when the last table removes a reference to that
+**| particular row. (But a row can be emptied of all content no matter how
+**| many refs exist, and this might be the next best thing to destruction.)
+**| Once a row exists in a least one table (after NewRow() is called), then it
+**| can be added to any other table by calling AddRow(), or removed from any
+**| table by calling CutRow(), or queried as a member by calling HasRow(). A
+**| row can only be added to a table once, and further additions do nothing and
+**| complain not at all. Cutting a row from a table only does something when
+**| the row was actually a member, and otherwise does nothing silently.
+**|
+**|| row ref count: one can query the number of tables (and/or cells)
+**| containing a row as a member or a child.
+**|
+**|| row content: one can access or modify the cell content in a table's row
+**| by moving content to or from an instance of nsIMdbRow. Note that nsIMdbRow
+**| never represents the actual row inside a table, and this is the reason
+**| why nsIMdbRow instances do not have row IDs or row scopes. So an instance
+**| of nsIMdbRow always and only contains a snapshot of some or all content in
+**| past, present, or future persistent row inside a table. This means that
+**| reading and writing rows in tables has strictly copy semantics, and we
+**| currently do not plan any exceptions for specific performance reasons.
+**|
+**|| sorting: note all rows are assumed sorted by row ID as a secondary
+**| sort following the primary column sort, when table rows are sorted.
+**|
+**|| indexes:
+|*/
+
+
+#define NS_IMDBTABLE_IID_STR = "fe11bc98-d02b-4128-9fac-87042fdf9639"
+
+#define NS_IMDBTABLE_IID \
+{0xfe11bc98, 0xd02b, 0x4128, \
+{0x9f, 0xac, 0x87, 0x04, 0x2f, 0xdf, 0x96, 0x39}}
+
+class nsIMdbTable : public nsIMdbCollection { // a collection of rows
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBTABLE_IID)
+// { ===== begin nsIMdbTable methods =====
+
+ // { ----- begin meta attribute methods -----
+ NS_IMETHOD SetTablePriority(nsIMdbEnv* ev, mdb_priority inPrio) = 0;
+ NS_IMETHOD GetTablePriority(nsIMdbEnv* ev, mdb_priority* outPrio) = 0;
+
+ NS_IMETHOD GetTableBeVerbose(nsIMdbEnv* ev, mdb_bool* outBeVerbose) = 0;
+ NS_IMETHOD SetTableBeVerbose(nsIMdbEnv* ev, mdb_bool inBeVerbose) = 0;
+
+ NS_IMETHOD GetTableIsUnique(nsIMdbEnv* ev, mdb_bool* outIsUnique) = 0;
+
+ NS_IMETHOD GetTableKind(nsIMdbEnv* ev, mdb_kind* outTableKind) = 0;
+ NS_IMETHOD GetRowScope(nsIMdbEnv* ev, mdb_scope* outRowScope) = 0;
+
+ NS_IMETHOD GetMetaRow(
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ mdbOid* outOid, // output meta row oid, can be nil to suppress output
+ nsIMdbRow** acqRow) = 0; // acquire table's unique singleton meta row
+ // The purpose of a meta row is to support the persistent recording of
+ // meta info about a table as cells put into the distinguished meta row.
+ // Each table has exactly one meta row, which is not considered a member
+ // of the collection of rows inside the table. The only way to tell
+ // whether a row is a meta row is by the fact that it is returned by this
+ // GetMetaRow() method from some table. Otherwise nothing distinguishes
+ // a meta row from any other row. A meta row can be used anyplace that
+ // any other row can be used, and can even be put into other tables (or
+ // the same table) as a table member, if this is useful for some reason.
+ // The first attempt to access a table's meta row using GetMetaRow() will
+ // cause the meta row to be created if it did not already exist. When the
+ // meta row is created, it will have the row oid that was previously
+ // requested for this table's meta row; or if no oid was ever explicitly
+ // specified for this meta row, then a unique oid will be generated in
+ // the row scope named "m" (so obviously MDB clients should not
+ // manually allocate any row IDs from that special meta scope namespace).
+ // The meta row oid can be specified either when the table is created, or
+ // else the first time that GetMetaRow() is called, by passing a non-nil
+ // pointer to an oid for parameter inOptionalMetaRowOid. The meta row's
+ // actual oid is returned in outOid (if this is a non-nil pointer), and
+ // it will be different from inOptionalMetaRowOid when the meta row was
+ // already given a different oid earlier.
+ // } ----- end meta attribute methods -----
+
+
+ // { ----- begin cursor methods -----
+ NS_IMETHOD GetTableRowCursor( // make a cursor, starting iteration at inRowPos
+ nsIMdbEnv* ev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ nsIMdbTableRowCursor** acqCursor) = 0; // acquire new cursor instance
+ // } ----- end row position methods -----
+
+ // { ----- begin row position methods -----
+ NS_IMETHOD PosToOid( // get row member for a table position
+ nsIMdbEnv* ev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ mdbOid* outOid) = 0; // row oid at the specified position
+
+ NS_IMETHOD OidToPos( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // row to find in table
+ mdb_pos* outPos) = 0; // zero-based ordinal position of row in table
+
+ NS_IMETHOD PosToRow( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ nsIMdbRow** acqRow) = 0; // acquire row at table position inRowPos
+
+ NS_IMETHOD RowToPos( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow, // row to find in table
+ mdb_pos* outPos) = 0; // zero-based ordinal position of row in table
+ // } ----- end row position methods -----
+
+ // { ----- begin oid set methods -----
+ NS_IMETHOD AddOid( // make sure the row with inOid is a table member
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid) = 0; // row to ensure membership in table
+
+ NS_IMETHOD HasOid( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // row to find in table
+ mdb_bool* outHasOid) = 0; // whether inOid is a member row
+
+ NS_IMETHOD CutOid( // make sure the row with inOid is not a member
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid) = 0; // row to remove from table
+ // } ----- end oid set methods -----
+
+ // { ----- begin row set methods -----
+ NS_IMETHOD NewRow( // create a new row instance in table
+ nsIMdbEnv* ev, // context
+ mdbOid* ioOid, // please use minus one (unbound) rowId for db-assigned IDs
+ nsIMdbRow** acqRow) = 0; // create new row
+
+ NS_IMETHOD AddRow( // make sure the row with inOid is a table member
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow) = 0; // row to ensure membership in table
+
+ NS_IMETHOD HasRow( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow, // row to find in table
+ mdb_bool* outHasRow) = 0; // whether row is a table member
+
+ NS_IMETHOD CutRow( // make sure the row with inOid is not a member
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow) = 0; // row to remove from table
+
+ NS_IMETHOD CutAllRows( // remove all rows from the table
+ nsIMdbEnv* ev) = 0; // context
+ // } ----- end row set methods -----
+
+ // { ----- begin hinting methods -----
+ NS_IMETHOD SearchColumnsHint( // advise re future expected search cols
+ nsIMdbEnv* ev, // context
+ const mdbColumnSet* inColumnSet) = 0; // columns likely to be searched
+
+ NS_IMETHOD SortColumnsHint( // advise re future expected sort columns
+ nsIMdbEnv* ev, // context
+ const mdbColumnSet* inColumnSet) = 0; // columns for likely sort requests
+
+ NS_IMETHOD StartBatchChangeHint( // advise before many adds and cuts
+ nsIMdbEnv* ev, // context
+ const void* inLabel) = 0; // intend unique address to match end call
+ // If batch starts nest by virtue of nesting calls in the stack, then
+ // the address of a local variable makes a good batch start label that
+ // can be used at batch end time, and such addresses remain unique.
+
+ NS_IMETHOD EndBatchChangeHint( // advise before many adds and cuts
+ nsIMdbEnv* ev, // context
+ const void* inLabel) = 0; // label matching start label
+ // Suppose a table is maintaining one or many sort orders for a table,
+ // so that every row added to the table must be inserted in each sort,
+ // and every row cut must be removed from each sort. If a db client
+ // intends to make many such changes before needing any information
+ // about the order or positions of rows inside a table, then a client
+ // might tell the table to start batch changes in order to disable
+ // sorting of rows for the interim. Presumably a table will then do
+ // a full sort of all rows at need when the batch changes end, or when
+ // a surprise request occurs for row position during batch changes.
+ // } ----- end hinting methods -----
+
+ // { ----- begin searching methods -----
+ NS_IMETHOD FindRowMatches( // search variable number of sorted cols
+ nsIMdbEnv* ev, // context
+ const mdbYarn* inPrefix, // content to find as prefix in row's column cell
+ nsIMdbTableRowCursor** acqCursor) = 0; // set of matching rows
+
+ NS_IMETHOD GetSearchColumns( // query columns used by FindRowMatches()
+ nsIMdbEnv* ev, // context
+ mdb_count* outCount, // context
+ mdbColumnSet* outColSet) = 0; // caller supplied space to put columns
+ // GetSearchColumns() returns the columns actually searched when the
+ // FindRowMatches() method is called. No more than mColumnSet_Count
+ // slots of mColumnSet_Columns will be written, since mColumnSet_Count
+ // indicates how many slots are present in the column array. The
+ // actual number of search column used by the table is returned in
+ // the outCount parameter; if this number exceeds mColumnSet_Count,
+ // then a caller needs a bigger array to read the entire column set.
+ // The minimum of mColumnSet_Count and outCount is the number slots
+ // in mColumnSet_Columns that were actually written by this method.
+ //
+ // Callers are expected to change this set of columns by calls to
+ // nsIMdbTable::SearchColumnsHint() or SetSearchSorting(), or both.
+ // } ----- end searching methods -----
+
+ // { ----- begin sorting methods -----
+ // sorting: note all rows are assumed sorted by row ID as a secondary
+ // sort following the primary column sort, when table rows are sorted.
+
+ NS_IMETHOD
+ CanSortColumn( // query which column is currently used for sorting
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // column to query sorting potential
+ mdb_bool* outCanSort) = 0; // whether the column can be sorted
+
+ NS_IMETHOD GetSorting( // view same table in particular sorting
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // requested new column for sorting table
+ nsIMdbSorting** acqSorting) = 0; // acquire sorting for column
+
+ NS_IMETHOD SetSearchSorting( // use this sorting in FindRowMatches()
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // often same as nsIMdbSorting::GetSortColumn()
+ nsIMdbSorting* ioSorting) = 0; // requested sorting for some column
+ // SetSearchSorting() attempts to inform the table that ioSorting
+ // should be used during calls to FindRowMatches() for searching
+ // the column which is actually sorted by ioSorting. This method
+ // is most useful in conjunction with nsIMdbSorting::SetCompare(),
+ // because otherwise a caller would not be able to override the
+ // comparison ordering method used during searches. Note that some
+ // database implementations might be unable to use an arbitrarily
+ // specified sort order, either due to schema or runtime interface
+ // constraints, in which case ioSorting might not actually be used.
+ // Presumably ioSorting is an instance that was returned from some
+ // earlier call to nsIMdbTable::GetSorting(). A caller can also
+ // use nsIMdbTable::SearchColumnsHint() to specify desired change
+ // in which columns are sorted and searched by FindRowMatches().
+ //
+ // A caller can pass a nil pointer for ioSorting to request that
+ // column inColumn no longer be used at all by FindRowMatches().
+ // But when ioSorting is non-nil, then inColumn should match the
+ // column actually sorted by ioSorting; when these do not agree,
+ // implementations are instructed to give precedence to the column
+ // specified by ioSorting (so this means callers might just pass
+ // zero for inColumn when ioSorting is also provided, since then
+ // inColumn is both redundant and ignored).
+ // } ----- end sorting methods -----
+
+ // { ----- begin moving methods -----
+ // moving a row does nothing unless a table is currently unsorted
+
+ NS_IMETHOD MoveOid( // change position of row in unsorted table
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // row oid to find in table
+ mdb_pos inHintFromPos, // suggested hint regarding start position
+ mdb_pos inToPos, // desired new position for row inRowId
+ mdb_pos* outActualPos) = 0; // actual new position of row in table
+
+ NS_IMETHOD MoveRow( // change position of row in unsorted table
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow, // row oid to find in table
+ mdb_pos inHintFromPos, // suggested hint regarding start position
+ mdb_pos inToPos, // desired new position for row inRowId
+ mdb_pos* outActualPos) = 0; // actual new position of row in table
+ // } ----- end moving methods -----
+
+ // { ----- begin index methods -----
+ NS_IMETHOD AddIndex( // create a sorting index for column if possible
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // the column to sort by index
+ nsIMdbThumb** acqThumb) = 0; // acquire thumb for incremental index building
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the index addition will be finished.
+
+ NS_IMETHOD CutIndex( // stop supporting a specific column index
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // the column with index to be removed
+ nsIMdbThumb** acqThumb) = 0; // acquire thumb for incremental index destroy
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the index removal will be finished.
+
+ NS_IMETHOD HasIndex( // query for current presence of a column index
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // the column to investigate
+ mdb_bool* outHasIndex) = 0; // whether column has index for this column
+
+
+ NS_IMETHOD EnableIndexOnSort( // create an index for col on first sort
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn) = 0; // the column to index if ever sorted
+
+ NS_IMETHOD QueryIndexOnSort( // check whether index on sort is enabled
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // the column to investigate
+ mdb_bool* outIndexOnSort) = 0; // whether column has index-on-sort enabled
+
+ NS_IMETHOD DisableIndexOnSort( // prevent future index creation on sort
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn) = 0; // the column to index if ever sorted
+ // } ----- end index methods -----
+
+// } ===== end nsIMdbTable methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbTable, NS_IMDBTABLE_IID)
+
+/*| nsIMdbSorting: a view of a table in some particular sort order. This
+**| row order closely resembles a readonly array of rows with the same row
+**| membership as the underlying table, but in a different order than the
+**| table's explicit row order. But the sorting's row membership changes
+**| whenever the table's membership changes (without any notification, so
+**| keep this in mind when modifying the table).
+**|
+**|| table: every sorting is associated with a particular table. You
+**| cannot change which table is used by a sorting (just ask some new
+**| table for a suitable sorting instance instead).
+**|
+**|| compare: the ordering method used by a sorting, wrapped up in a
+**| abstract plug-in interface. When this was never installed by an
+**| explicit call to SetNewCompare(), a compare object is still returned,
+**| and it might match the compare instance returned by the factory method
+**| nsIMdbFactory::MakeCompare(), which represents a default sort order
+**| (which we fervently hope is consistently ASCII byte ordering).
+**|
+**|| cursor: in case callers are more comfortable with a cursor style
+**| of accessing row members, each sorting will happily return a cursor
+**| instance with behavior very similar to a cursor returned from a call
+**| to nsIMdbTable::GetTableRowCursor(), but with different row order.
+**| A cursor should show exactly the same information as the pos methods.
+**|
+**|| pos: the PosToOid() and PosToRow() methods are just like the table
+**| methods of the same name, except they show rows in the sort order of
+**| the sorting, rather than that of the table. These methods are like
+**| readonly array position accessor's, or like a C++ operator[].
+|*/
+class nsIMdbSorting : public nsIMdbObject { // sorting of some table
+public:
+// { ===== begin nsIMdbSorting methods =====
+
+ // { ----- begin attribute methods -----
+ // sorting: note all rows are assumed sorted by row ID as a secondary
+ // sort following the primary column sort, when table rows are sorted.
+
+ NS_IMETHOD GetTable(nsIMdbEnv* ev, nsIMdbTable** acqTable) = 0;
+ NS_IMETHOD GetSortColumn( // query which col is currently sorted
+ nsIMdbEnv* ev, // context
+ mdb_column* outColumn) = 0; // col the table uses for sorting (or zero)
+
+ // } ----- end attribute methods -----
+
+ // { ----- begin cursor methods -----
+ NS_IMETHOD GetSortingRowCursor( // make a cursor, starting at inRowPos
+ nsIMdbEnv* ev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ nsIMdbTableRowCursor** acqCursor) = 0; // acquire new cursor instance
+ // A cursor interface turning same info as PosToOid() or PosToRow().
+ // } ----- end row position methods -----
+
+ // { ----- begin row position methods -----
+ NS_IMETHOD PosToOid( // get row member for a table position
+ nsIMdbEnv* ev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ mdbOid* outOid) = 0; // row oid at the specified position
+
+ NS_IMETHOD PosToRow( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ nsIMdbRow** acqRow) = 0; // acquire row at table position inRowPos
+ // } ----- end row position methods -----
+
+// } ===== end nsIMdbSorting methods =====
+};
+
+/*| nsIMdbTableRowCursor: cursor class for iterating table rows
+**|
+**|| table: the cursor is associated with a specific table, which can be
+**| set to a different table (which resets the position to -1 so the
+**| next row acquired is the first in the table.
+**|
+**|| NextRowId: the rows in the table can be iterated by identity alone,
+**| without actually reading the cells of any row with this method.
+**|
+**|| NextRowCells: read the next row in the table, but only read cells
+**| from the table which are already present in the row (so no new cells
+**| are added to the row, even if they are present in the table). All the
+**| cells will have content specified, even it is the empty string. No
+**| columns will be removed, even if missing from the row (because missing
+**| and empty are semantically equivalent).
+**|
+**|| NextRowAllCells: read the next row in the table, and access all the
+**| cells for this row in the table, adding any missing columns to the row
+**| as needed until all cells are represented. All the
+**| cells will have content specified, even it is the empty string. No
+**| columns will be removed, even if missing from the row (because missing
+**| and empty are semantically equivalent).
+**|
+|*/
+
+#define NS_IMDBTABLEROWCURSOR_IID_STR = "4f325dad-0385-4b62-a992-c914ab93587e"
+
+#define NS_IMDBTABLEROWCURSOR_IID \
+{0x4f325dad, 0x0385, 0x4b62, \
+{0xa9, 0x92, 0xc9, 0x14, 0xab, 0x93, 0x58, 0x7e}}
+
+
+
+class nsIMdbTableRowCursor : public nsISupports { // table row iterator
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBTABLEROWCURSOR_IID)
+
+// { ===== begin nsIMdbTableRowCursor methods =====
+
+ // { ----- begin attribute methods -----
+ // NS_IMETHOD SetTable(nsIMdbEnv* ev, nsIMdbTable* ioTable) = 0; // sets pos to -1
+ // Method SetTable() cut and made obsolete in keeping with new sorting methods.
+
+ NS_IMETHOD GetTable(nsIMdbEnv* ev, nsIMdbTable** acqTable) = 0;
+ // } ----- end attribute methods -----
+
+ // { ----- begin duplicate row removal methods -----
+ NS_IMETHOD CanHaveDupRowMembers(nsIMdbEnv* ev, // cursor might hold dups?
+ mdb_bool* outCanHaveDups) = 0;
+
+ NS_IMETHOD MakeUniqueCursor( // clone cursor, removing duplicate rows
+ nsIMdbEnv* ev, // context
+ nsIMdbTableRowCursor** acqCursor) = 0; // acquire clone with no dups
+ // Note that MakeUniqueCursor() is never necessary for a cursor which was
+ // created by table method nsIMdbTable::GetTableRowCursor(), because a table
+ // never contains the same row as a member more than once. However, a cursor
+ // created by table method nsIMdbTable::FindRowMatches() might contain the
+ // same row more than once, because the same row can generate a hit by more
+ // than one column with a matching string prefix. Note this method can
+ // return the very same cursor instance with just an incremented refcount,
+ // when the original cursor could not contain any duplicate rows (calling
+ // CanHaveDupRowMembers() shows this case on a false return). Otherwise
+ // this method returns a different cursor instance. Callers should not use
+ // this MakeUniqueCursor() method lightly, because it tends to defeat the
+ // purpose of lazy programming techniques, since it can force creation of
+ // an explicit row collection in a new cursor's representation, in order to
+ // inspect the row membership and remove any duplicates; this can have big
+ // impact if a collection holds tens of thousands of rows or more, when
+ // the original cursor with dups simply referenced rows indirectly by row
+ // position ranges, without using an explicit row set representation.
+ // Callers are encouraged to use nsIMdbCursor::GetCount() to determine
+ // whether the row collection is very large (tens of thousands), and to
+ // delay calling MakeUniqueCursor() when possible, until a user interface
+ // element actually demands the creation of an explicit set representation.
+ // } ----- end duplicate row removal methods -----
+
+ // { ----- begin oid iteration methods -----
+ NS_IMETHOD NextRowOid( // get row id of next row in the table
+ nsIMdbEnv* ev, // context
+ mdbOid* outOid, // out row oid
+ mdb_pos* outRowPos) = 0; // zero-based position of the row in table
+ // } ----- end oid iteration methods -----
+
+ // { ----- begin row iteration methods -----
+ NS_IMETHOD NextRow( // get row cells from table for cells already in row
+ nsIMdbEnv* ev, // context
+ nsIMdbRow** acqRow, // acquire next row in table
+ mdb_pos* outRowPos) = 0; // zero-based position of the row in table
+
+ NS_IMETHOD PrevRowOid( // get row id of previous row in the table
+ nsIMdbEnv* ev, // context
+ mdbOid* outOid, // out row oid
+ mdb_pos* outRowPos) = 0; // zero-based position of the row in table
+ // } ----- end oid iteration methods -----
+
+ // { ----- begin row iteration methods -----
+ NS_IMETHOD PrevRow( // get row cells from table for cells already in row
+ nsIMdbEnv* ev, // context
+ nsIMdbRow** acqRow, // acquire previous row in table
+ mdb_pos* outRowPos) = 0; // zero-based position of the row in table
+
+ // } ----- end row iteration methods -----
+
+ // { ----- begin copy iteration methods -----
+ // NS_IMETHOD NextRowCopy( // put row cells into sink only when already in sink
+ // nsIMdbEnv* ev, // context
+ // nsIMdbRow* ioSinkRow, // sink for row cells read from next row
+ // mdbOid* outOid, // out row oid
+ // mdb_pos* outRowPos) = 0; // zero-based position of the row in table
+ //
+ // NS_IMETHOD NextRowCopyAll( // put all row cells into sink, adding to sink
+ // nsIMdbEnv* ev, // context
+ // nsIMdbRow* ioSinkRow, // sink for row cells read from next row
+ // mdbOid* outOid, // out row oid
+ // mdb_pos* outRowPos) = 0; // zero-based position of the row in table
+ // } ----- end copy iteration methods -----
+
+// } ===== end nsIMdbTableRowCursor methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbTableRowCursor, NS_IMDBTABLEROWCURSOR_IID)
+
+/*| nsIMdbRow: a collection of cells
+**|
+|*/
+
+#define NS_IMDBROW_IID_STR "271e8d6e-183a-40e3-9f18-36913b4c7853"
+
+
+#define NS_IMDBROW_IID \
+{0x271e8d6e, 0x183a, 0x40e3, \
+{0x9f, 0x18, 0x36, 0x91, 0x3b, 0x4c, 0x78, 0x53}}
+
+
+class nsIMdbRow : public nsIMdbCollection { // cell tuple
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBROW_IID)
+// { ===== begin nsIMdbRow methods =====
+
+ // { ----- begin cursor methods -----
+ NS_IMETHOD GetRowCellCursor( // make a cursor starting iteration at inCellPos
+ nsIMdbEnv* ev, // context
+ mdb_pos inCellPos, // zero-based ordinal position of cell in row
+ nsIMdbRowCellCursor** acqCursor) = 0; // acquire new cursor instance
+ // } ----- end cursor methods -----
+
+ // { ----- begin column methods -----
+ NS_IMETHOD AddColumn( // make sure a particular column is inside row
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // column to add
+ const mdbYarn* inYarn) = 0; // cell value to install
+
+ NS_IMETHOD CutColumn( // make sure a column is absent from the row
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn) = 0; // column to ensure absent from row
+
+ NS_IMETHOD CutAllColumns( // remove all columns from the row
+ nsIMdbEnv* ev) = 0; // context
+ // } ----- end column methods -----
+
+ // { ----- begin cell methods -----
+ NS_IMETHOD NewCell( // get cell for specified column, or add new one
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // column to add
+ nsIMdbCell** acqCell) = 0; // cell column and value
+
+ NS_IMETHOD AddCell( // copy a cell from another row to this row
+ nsIMdbEnv* ev, // context
+ const nsIMdbCell* inCell) = 0; // cell column and value
+
+ NS_IMETHOD GetCell( // find a cell in this row
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // column to find
+ nsIMdbCell** acqCell) = 0; // cell for specified column, or null
+
+ NS_IMETHOD EmptyAllCells( // make all cells in row empty of content
+ nsIMdbEnv* ev) = 0; // context
+ // } ----- end cell methods -----
+
+ // { ----- begin row methods -----
+ NS_IMETHOD AddRow( // add all cells in another row to this one
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioSourceRow) = 0; // row to union with
+
+ NS_IMETHOD SetRow( // make exact duplicate of another row
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioSourceRow) = 0; // row to duplicate
+ // } ----- end row methods -----
+
+ // { ----- begin blob methods -----
+ NS_IMETHOD SetCellYarn(nsIMdbEnv* ev, // synonym for AddColumn()
+ mdb_column inColumn, // column to write
+ const mdbYarn* inYarn) = 0; // reads from yarn slots
+ // make this text object contain content from the yarn's buffer
+
+ NS_IMETHOD GetCellYarn(nsIMdbEnv* ev,
+ mdb_column inColumn, // column to read
+ mdbYarn* outYarn) = 0; // writes some yarn slots
+ // copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+
+ NS_IMETHOD AliasCellYarn(nsIMdbEnv* ev,
+ mdb_column inColumn, // column to alias
+ mdbYarn* outYarn) = 0; // writes ALL yarn slots
+
+ NS_IMETHOD NextCellYarn(nsIMdbEnv* ev, // iterative version of GetCellYarn()
+ mdb_column* ioColumn, // next column to read
+ mdbYarn* outYarn) = 0; // writes some yarn slots
+ // copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+ //
+ // The ioColumn argument is an inout parameter which initially contains the
+ // last column accessed and returns the next column corresponding to the
+ // content read into the yarn. Callers should start with a zero column
+ // value to say 'no previous column', which causes the first column to be
+ // read. Then the value returned in ioColumn is perfect for the next call
+ // to NextCellYarn(), since it will then be the previous column accessed.
+ // Callers need only examine the column token returned to see which cell
+ // in the row is being read into the yarn. When no more columns remain,
+ // and the iteration has ended, ioColumn will return a zero token again.
+ // So iterating over cells starts and ends with a zero column token.
+
+ NS_IMETHOD SeekCellYarn( // resembles nsIMdbRowCellCursor::SeekCell()
+ nsIMdbEnv* ev, // context
+ mdb_pos inPos, // position of cell in row sequence
+ mdb_column* outColumn, // column for this particular cell
+ mdbYarn* outYarn) = 0; // writes some yarn slots
+ // copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+ // Callers can pass nil for outYarn to indicate no interest in content, so
+ // only the outColumn value is returned. NOTE to subclasses: you must be
+ // able to ignore outYarn when the pointer is nil; please do not crash.
+
+ // } ----- end blob methods -----
+
+// } ===== end nsIMdbRow methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbRow, NS_IMDBROW_IID)
+
+/*| nsIMdbRowCellCursor: cursor class for iterating row cells
+**|
+**|| row: the cursor is associated with a specific row, which can be
+**| set to a different row (which resets the position to -1 so the
+**| next cell acquired is the first in the row.
+**|
+**|| NextCell: get the next cell in the row and return its position and
+**| a new instance of a nsIMdbCell to represent this next cell.
+|*/
+
+#define NS_IMDBROWCELLCURSOR_IID_STR "b33371a7-5d63-4d10-85a8-e44dffe75c28"
+
+
+#define NS_IMDBROWCELLCURSOR_IID \
+{0x271e8d6e, 0x5d63, 0x4d10 , \
+{0x85, 0xa8, 0xe4, 0x4d, 0xff, 0xe7, 0x5c, 0x28}}
+
+
+class nsIMdbRowCellCursor : public nsISupports{ // cell collection iterator
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBROWCELLCURSOR_IID)
+// { ===== begin nsIMdbRowCellCursor methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD SetRow(nsIMdbEnv* ev, nsIMdbRow* ioRow) = 0; // sets pos to -1
+ NS_IMETHOD GetRow(nsIMdbEnv* ev, nsIMdbRow** acqRow) = 0;
+ // } ----- end attribute methods -----
+
+ // { ----- begin cell seeking methods -----
+ NS_IMETHOD SeekCell(
+ nsIMdbEnv* ev, // context
+ mdb_pos inPos, // position of cell in row sequence
+ mdb_column* outColumn, // column for this particular cell
+ nsIMdbCell** acqCell) = 0; // the cell at inPos
+ // } ----- end cell seeking methods -----
+
+ // { ----- begin cell iteration methods -----
+ NS_IMETHOD NextCell( // get next cell in the row
+ nsIMdbEnv* ev, // context
+ nsIMdbCell** acqCell, // changes to the next cell in the iteration
+ mdb_column* outColumn, // column for this particular cell
+ mdb_pos* outPos) = 0; // position of cell in row sequence
+
+ NS_IMETHOD PickNextCell( // get next cell in row within filter set
+ nsIMdbEnv* ev, // context
+ nsIMdbCell* ioCell, // changes to the next cell in the iteration
+ const mdbColumnSet* inFilterSet, // col set of actual caller interest
+ mdb_column* outColumn, // column for this particular cell
+ mdb_pos* outPos) = 0; // position of cell in row sequence
+
+ // Note that inFilterSet should not have too many (many more than 10?)
+ // cols, since this might imply a potential excessive consumption of time
+ // over many cursor calls when looking for column and filter intersection.
+ // } ----- end cell iteration methods -----
+
+// } ===== end nsIMdbRowCellCursor methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbRowCellCursor, NS_IMDBROWCELLCURSOR_IID)
+
+/*| nsIMdbBlob: a base class for objects composed mainly of byte sequence state.
+**| (This provides a base class for nsIMdbCell, so that cells themselves can
+**| be used to set state in another cell, without extracting a buffer.)
+|*/
+class nsIMdbBlob : public nsISupports { // a string with associated charset
+public:
+
+// { ===== begin nsIMdbBlob methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD SetBlob(nsIMdbEnv* ev,
+ nsIMdbBlob* ioBlob) = 0; // reads inBlob slots
+ // when inBlob is in the same suite, this might be fastest cell-to-cell
+
+ NS_IMETHOD ClearBlob( // make empty (so content has zero length)
+ nsIMdbEnv* ev) = 0;
+ // clearing a yarn is like SetYarn() with empty yarn instance content
+
+ NS_IMETHOD GetBlobFill(nsIMdbEnv* ev,
+ mdb_fill* outFill) = 0; // size of blob
+ // Same value that would be put into mYarn_Fill, if one called GetYarn()
+ // with a yarn instance that had mYarn_Buf==nil and mYarn_Size==0.
+
+ NS_IMETHOD SetYarn(nsIMdbEnv* ev,
+ const mdbYarn* inYarn) = 0; // reads from yarn slots
+ // make this text object contain content from the yarn's buffer
+
+ NS_IMETHOD GetYarn(nsIMdbEnv* ev,
+ mdbYarn* outYarn) = 0; // writes some yarn slots
+ // copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+
+ NS_IMETHOD AliasYarn(nsIMdbEnv* ev,
+ mdbYarn* outYarn) = 0; // writes ALL yarn slots
+ // AliasYarn() reveals sensitive internal text buffer state to the caller
+ // by setting mYarn_Buf to point into the guts of this text implementation.
+ //
+ // The caller must take great care to avoid writing on this space, and to
+ // avoid calling any method that would cause the state of this text object
+ // to change (say by directly or indirectly setting the text to hold more
+ // content that might grow the size of the buffer and free the old buffer).
+ // In particular, callers should scrupulously avoid making calls into the
+ // mdb interface to write any content while using the buffer pointer found
+ // in the returned yarn instance. Best safe usage involves copying content
+ // into some other kind of external content representation beyond mdb.
+ //
+ // (The original design of this method a week earlier included the concept
+ // of very fast and efficient cooperative locking via a pointer to some lock
+ // member slot. But let's ignore that complexity in the current design.)
+ //
+ // AliasYarn() is specifically intended as the first step in transferring
+ // content from nsIMdbBlob to a nsString representation, without forcing extra
+ // allocations and/or memory copies. (A standard nsIMdbBlob_AsString() utility
+ // will use AliasYarn() as the first step in setting a nsString instance.)
+ //
+ // This is an alternative to the GetYarn() method, which has copy semantics
+ // only; AliasYarn() relaxes a robust safety principle only for performance
+ // reasons, to accommodate the need for callers to transform text content to
+ // some other canonical representation that would necessitate an additional
+ // copy and transformation when such is incompatible with the mdbYarn format.
+ //
+ // The implementation of AliasYarn() should have extremely little overhead
+ // besides the virtual dispatch to the method implementation, and the code
+ // necessary to populate all the mdbYarn member slots with internal buffer
+ // address and metainformation that describes the buffer content. Note that
+ // mYarn_Grow must always be set to nil to indicate no resizing is allowed.
+
+ // } ----- end attribute methods -----
+
+// } ===== end nsIMdbBlob methods =====
+};
+
+/*| nsIMdbCell: the text in a single column of a row. The base nsIMdbBlob
+**| class provides all the interface related to accessing cell text.
+**|
+**|| column: each cell in a row appears in a specific column, where this
+**| column is identified by the an integer mdb_scope value (generated by
+**| the StringToScopeToken() method in the containing nsIMdbPort instance).
+**| Because a row cannot have more than one cell with the same column,
+**| something must give if one calls SetColumn() with an existing column
+**| in the same row. When this happens, the other cell is replaced with
+**| this cell (and the old cell is closed if it has outstanding refs).
+**|
+**|| row: every cell instance is a part of some row, and every cell knows
+**| which row is the parent row. (Note this should be represented by a
+**| weak backpointer, so that outstanding cell references cannot keep a
+**| row open that should be closed. Otherwise we'd have ref graph cycles.)
+**|
+**|| text: a cell can either be text, or it can have a child row or table,
+**| but not both at once. If text is read from a cell with a child, the text
+**| content should be empty (for AliasYarn()) or a description of the type
+**| of child (perhaps "mdb:cell:child:row" or "mdb:cell:child:table").
+**|
+**|| child: a cell might reference another row or a table, rather than text.
+**| The interface for putting and getting children rows and tables was first
+**| defined in the nsIMdbTable interface, but then this was moved to this cell
+**| interface as more natural.
+|*/
+
+
+
+#define NS_IMDBCELL_IID \
+{0xa3b62f71, 0xa181, 0x4a91, \
+{0xb6, 0x6b, 0x27, 0x10, 0x9b, 0x88, 0x98, 0x35}}
+
+#define NS_IMDBCELL_IID_STR = "a3b62f71-a181-4a91-b66b-27109b889835"
+
+class nsIMdbCell : public nsIMdbBlob { // text attribute in row with column scope
+public:
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMDBTABLEROWCURSOR_IID)
+// { ===== begin nsIMdbCell methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD SetColumn(nsIMdbEnv* ev, mdb_column inColumn) = 0;
+ NS_IMETHOD GetColumn(nsIMdbEnv* ev, mdb_column* outColumn) = 0;
+
+ NS_IMETHOD GetCellInfo( // all cell metainfo except actual content
+ nsIMdbEnv* ev,
+ mdb_column* outColumn, // the column in the containing row
+ mdb_fill* outBlobFill, // the size of text content in bytes
+ mdbOid* outChildOid, // oid of possible row or table child
+ mdb_bool* outIsRowChild) = 0; // nonzero if child, and a row child
+
+ // Checking all cell metainfo is a good way to avoid forcing a large cell
+ // in to memory when you don't actually want to use the content.
+
+ NS_IMETHOD GetRow(nsIMdbEnv* ev, // parent row for this cell
+ nsIMdbRow** acqRow) = 0;
+ NS_IMETHOD GetPort(nsIMdbEnv* ev, // port containing cell
+ nsIMdbPort** acqPort) = 0;
+ // } ----- end attribute methods -----
+
+ // { ----- begin children methods -----
+ NS_IMETHOD HasAnyChild( // does cell have a child instead of text?
+ nsIMdbEnv* ev,
+ mdbOid* outOid, // out id of row or table (or unbound if no child)
+ mdb_bool* outIsRow) = 0; // nonzero if child is a row (rather than a table)
+
+ NS_IMETHOD GetAnyChild( // access table of specific attribute
+ nsIMdbEnv* ev, // context
+ nsIMdbRow** acqRow, // child row (or null)
+ nsIMdbTable** acqTable) = 0; // child table (or null)
+
+
+ NS_IMETHOD SetChildRow( // access table of specific attribute
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow) = 0; // inRow must be bound inside this same db port
+
+ NS_IMETHOD GetChildRow( // access row of specific attribute
+ nsIMdbEnv* ev, // context
+ nsIMdbRow** acqRow) = 0; // acquire child row (or nil if no child)
+
+
+ NS_IMETHOD SetChildTable( // access table of specific attribute
+ nsIMdbEnv* ev, // context
+ nsIMdbTable* inTable) = 0; // table must be bound inside this same db port
+
+ NS_IMETHOD GetChildTable( // access table of specific attribute
+ nsIMdbEnv* ev, // context
+ nsIMdbTable** acqTable) = 0; // acquire child table (or nil if no child)
+ // } ----- end children methods -----
+
+// } ===== end nsIMdbCell methods =====
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIMdbCell, NS_IMDBTABLEROWCURSOR_IID)
+
+// } %%%%% end C++ abstract class interfaces %%%%%
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MDB_ */
+
diff --git a/components/mork/public/moz.build b/components/mork/public/moz.build
new file mode 100644
index 000000000..77b58b5ba
--- /dev/null
+++ b/components/mork/public/moz.build
@@ -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/.
+
+EXPORTS += [
+ 'mdb.h',
+]
+
diff --git a/components/mork/src/mork.h b/components/mork/src/mork.h
new file mode 100644
index 000000000..606e8f3d9
--- /dev/null
+++ b/components/mork/src/mork.h
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _MORK_
+#define _MORK_ 1
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#include "nscore.h"
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+// { %%%%% begin disable unused param warnings %%%%%
+#define MORK_USED_1(x) (void)(&x)
+#define MORK_USED_2(x,y) (void)(&x);(void)(&y);
+#define MORK_USED_3(x,y,z) (void)(&x);(void)(&y);(void)(&z);
+#define MORK_USED_4(w,x,y,z) (void)(&w);(void)(&x);(void)(&y);(void)(&z);
+
+// } %%%%% end disable unused param warnings %%%%%
+
+// { %%%%% begin macro for finding class member offset %%%%%
+
+/*| OffsetOf: the unsigned integer offset of a class or struct
+**| field from the beginning of that class or struct. This is
+**| the same as the similarly named public domain IronDoc macro,
+**| and is also the same as another macro appearing in stdlib.h.
+**| We want these offsets so we can correctly convert pointers
+**| to member slots back into pointers to enclosing objects, and
+**| have this exactly match what the compiler thinks is true.
+**|
+**|| Bascially we are asking the compiler to determine the offset at
+**| compile time, and we use the definition of address artithmetic
+**| to do this. By casting integer zero to a pointer of type obj*,
+**| we can reference the address of a slot in such an object that
+**| is hypothetically physically placed at address zero, but without
+**| actually dereferencing a memory location. The absolute address
+**| of slot is the same as offset of that slot, when the object is
+**| placed at address zero.
+|*/
+#define mork_OffsetOf(obj,slot) ((unsigned int)&((obj*) 0)->slot)
+
+// } %%%%% end macro for finding class member offset %%%%%
+
+// { %%%%% begin specific-size integer scalar typedefs %%%%%
+typedef unsigned char mork_u1; // make sure this is one byte
+typedef unsigned short mork_u2; // make sure this is two bytes
+typedef short mork_i2; // make sure this is two bytes
+typedef uint32_t mork_u4; // make sure this is four bytes
+typedef int32_t mork_i4; // make sure this is four bytes
+typedef PRWord mork_ip; // make sure sizeof(mork_ip) == sizeof(void*)
+
+typedef mork_u1 mork_ch; // small byte-sized character (never wide)
+typedef mork_u1 mork_flags; // one byte's worth of predicate bit flags
+
+typedef mork_u2 mork_base; // 2-byte magic class signature slot in object
+typedef mork_u2 mork_derived; // 2-byte magic class signature slot in object
+
+typedef mork_u4 mork_token; // unsigned token for atomized string
+typedef mork_token mork_scope; // token used to id scope for rows
+typedef mork_token mork_kind; // token used to id kind for tables
+typedef mork_token mork_cscode; // token used to id charset names
+typedef mork_token mork_aid; // token used to id atomize cell values
+
+typedef mork_token mork_column; // token used to id columns for rows
+typedef mork_column mork_delta; // mork_column plus mork_change
+
+typedef mork_token mork_color; // bead ID
+#define morkColor_kNone ((mork_color) 0)
+
+typedef mork_u4 mork_magic; // unsigned magic signature
+
+typedef mork_u4 mork_seed; // unsigned collection change counter
+typedef mork_u4 mork_count; // unsigned collection member count
+typedef mork_count mork_num; // synonym for count
+typedef mork_u4 mork_size; // unsigned physical media size
+typedef mork_u4 mork_fill; // unsigned logical content size
+typedef mork_u4 mork_more; // more available bytes for larger buffer
+
+typedef mdb_u4 mork_percent; // 0..100, with values >100 same as 100
+
+typedef mork_i4 mork_pos; // negative means "before first" (at zero pos)
+typedef mork_i4 mork_line; // negative means "before first line in file"
+
+typedef mork_u1 mork_usage; // 1-byte magic usage signature slot in object
+typedef mork_u1 mork_access; // 1-byte magic access signature slot in object
+
+typedef mork_u1 mork_change; // add, cut, put, set, nil
+typedef mork_u1 mork_priority; // 0..9, for a total of ten different values
+
+typedef mork_u1 mork_able; // on, off, asleep (clone IronDoc's fe_able)
+typedef mork_u1 mork_load; // dirty or clean (clone IronDoc's fe_load)
+// } %%%%% end specific-size integer scalar typedefs %%%%%
+
+// 'test' is a public domain Mithril for key equality tests in probe maps
+typedef mork_i2 mork_test; /* neg=>kVoid, zero=>kHit, pos=>kMiss */
+
+#define morkTest_kVoid ((mork_test) -1) /* -1: nil key slot, no key order */
+#define morkTest_kHit ((mork_test) 0) /* 0: keys are equal, a map hit */
+#define morkTest_kMiss ((mork_test) 1) /* 1: keys not equal, a map miss */
+
+// { %%%%% begin constants for Mork scalar types %%%%%
+#define morkPriority_kHi ((mork_priority) 0) /* best priority */
+#define morkPriority_kMin ((mork_priority) 0) /* best priority is smallest */
+
+#define morkPriority_kLo ((mork_priority) 9) /* worst priority */
+#define morkPriority_kMax ((mork_priority) 9) /* worst priority is biggest */
+
+#define morkPriority_kCount 10 /* number of distinct priority values */
+
+#define morkAble_kEnabled ((mork_able) 0x55) /* same as IronDoc constant */
+#define morkAble_kDisabled ((mork_able) 0xAA) /* same as IronDoc constant */
+#define morkAble_kAsleep ((mork_able) 0x5A) /* same as IronDoc constant */
+
+#define morkChange_kAdd 'a' /* add member */
+#define morkChange_kCut 'c' /* cut member */
+#define morkChange_kPut 'p' /* put member */
+#define morkChange_kSet 's' /* set all members */
+#define morkChange_kNil 0 /* no change in this member */
+#define morkChange_kDup 'd' /* duplicate changes have no effect */
+// kDup is intended to replace another change constant in an object as a
+// conclusion about change feasibility while staging intended alterations.
+
+#define morkLoad_kDirty ((mork_load) 0xDD) /* same as IronDoc constant */
+#define morkLoad_kClean ((mork_load) 0x22) /* same as IronDoc constant */
+
+#define morkAccess_kOpen 'o'
+#define morkAccess_kClosing 'c'
+#define morkAccess_kShut 's'
+#define morkAccess_kDead 'd'
+// } %%%%% end constants for Mork scalar types %%%%%
+
+// { %%%%% begin non-specific-size integer scalar typedefs %%%%%
+typedef int mork_char; // nominal type for ints used to hold input byte
+#define morkChar_IsWhite(c) \
+ ((c) == 0xA || (c) == 0x9 || (c) == 0xD || (c) == ' ')
+// } %%%%% end non-specific-size integer scalar typedefs %%%%%
+
+// { %%%%% begin mdb-driven scalar typedefs %%%%%
+// easier to define bool exactly the same as mdb:
+typedef mdb_bool mork_bool; // unsigned byte with zero=false, nonzero=true
+
+/* canonical boolean constants provided only for code clarity: */
+#define morkBool_kTrue ((mork_bool) 1) /* actually any nonzero means true */
+#define morkBool_kFalse ((mork_bool) 0) /* only zero means false */
+
+// mdb clients can assign these, so we cannot pick maximum size:
+typedef mdb_id mork_id; // unsigned object identity in a scope
+typedef mork_id mork_rid; // unsigned row identity inside scope
+typedef mork_id mork_tid; // unsigned table identity inside scope
+typedef mork_id mork_gid; // unsigned group identity without any scope
+
+// we only care about neg, zero, pos -- so we don't care about size:
+typedef mdb_order mork_order; // neg:lessthan, zero:equalto, pos:greaterthan
+// } %%%%% end mdb-driven scalar typedefs %%%%%
+
+#define morkId_kMinusOne ((mdb_id) -1)
+
+// { %%%%% begin class forward defines %%%%%
+// try to put these in alphabetical order for easier examination:
+class morkMid;
+class morkAtom;
+class morkAtomSpace;
+class morkBookAtom;
+class morkBuf;
+class morkBuilder;
+class morkCell;
+class morkCellObject;
+class morkCursor;
+class morkEnv;
+class morkFactory;
+class morkFile;
+class morkHandle;
+class morkHandleFace; // just an opaque cookie type
+class morkHandleFrame;
+class morkHashArrays;
+class morkMap;
+class morkNode;
+class morkObject;
+class morkOidAtom;
+class morkParser;
+class morkPool;
+class morkPlace;
+class morkPort;
+class morkPortTableCursor;
+class morkProbeMap;
+class morkRow;
+class morkRowCellCursor;
+class morkRowObject;
+class morkRowSpace;
+class morkSorting;
+class morkSortingRowCursor;
+class morkSpace;
+class morkSpan;
+class morkStore;
+class morkStream;
+class morkTable;
+class morkTableChange;
+class morkTableRowCursor;
+class morkThumb;
+class morkWriter;
+class morkZone;
+// } %%%%% end class forward defines %%%%%
+
+// include this config file last for platform & environment specific stuff:
+#ifndef _MORKCONFIG_
+#include "morkConfig.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORK_ */
diff --git a/components/mork/src/morkArray.cpp b/components/mork/src/morkArray.cpp
new file mode 100644
index 000000000..5b83b85e7
--- /dev/null
+++ b/components/mork/src/morkArray.cpp
@@ -0,0 +1,297 @@
+/* -*- 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 "nscore.h"
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKARRAY_
+#include "morkArray.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkArray::CloseMorkNode(morkEnv* ev) // CloseTable() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseArray(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkArray::~morkArray() // assert CloseTable() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+ MORK_ASSERT(mArray_Slots==0);
+}
+
+/*public non-poly*/
+morkArray::morkArray(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, mork_size inSize, nsIMdbHeap* ioSlotHeap)
+: morkNode(ev, inUsage, ioHeap)
+, mArray_Slots( 0 )
+, mArray_Heap( 0 )
+, mArray_Fill( 0 )
+, mArray_Size( 0 )
+, mArray_Seed( (mork_u4)NS_PTR_TO_INT32(this) ) // "random" integer assignment
+{
+ if ( ev->Good() )
+ {
+ if ( ioSlotHeap )
+ {
+ nsIMdbHeap_SlotStrongHeap(ioSlotHeap, ev, &mArray_Heap);
+ if ( ev->Good() )
+ {
+ if ( inSize < 3 )
+ inSize = 3;
+ mdb_size byteSize = inSize * sizeof(void*);
+ void** block = 0;
+ ioSlotHeap->Alloc(ev->AsMdbEnv(), byteSize, (void**) &block);
+ if ( block && ev->Good() )
+ {
+ mArray_Slots = block;
+ mArray_Size = inSize;
+ MORK_MEMSET(mArray_Slots, 0, byteSize);
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kArray;
+ }
+ }
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+/*public non-poly*/ void
+morkArray::CloseArray(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ if ( mArray_Heap && mArray_Slots )
+ mArray_Heap->Free(ev->AsMdbEnv(), mArray_Slots);
+
+ mArray_Slots = 0;
+ mArray_Size = 0;
+ mArray_Fill = 0;
+ ++mArray_Seed;
+ nsIMdbHeap_SlotStrongHeap((nsIMdbHeap*) 0, ev, &mArray_Heap);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkArray::NonArrayTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkArray");
+}
+
+/*static*/ void
+morkArray::IndexBeyondEndError(morkEnv* ev)
+{
+ ev->NewError("array index beyond end");
+}
+
+/*static*/ void
+morkArray::NilSlotsAddressError(morkEnv* ev)
+{
+ ev->NewError("nil mArray_Slots");
+}
+
+/*static*/ void
+morkArray::FillBeyondSizeError(morkEnv* ev)
+{
+ ev->NewError("mArray_Fill > mArray_Size");
+}
+
+mork_bool
+morkArray::Grow(morkEnv* ev, mork_size inNewSize)
+// Grow() returns true if capacity becomes >= inNewSize and ev->Good()
+{
+ if ( ev->Good() && inNewSize > mArray_Size ) // make array larger?
+ {
+ if ( mArray_Fill <= mArray_Size ) // fill and size fit the invariant?
+ {
+ if (mArray_Size <= 3)
+ inNewSize = mArray_Size + 3;
+ else
+ inNewSize = mArray_Size * 2;// + 3; // try doubling size here - used to grow by 3
+
+ mdb_size newByteSize = inNewSize * sizeof(void*);
+ void** newBlock = 0;
+ mArray_Heap->Alloc(ev->AsMdbEnv(), newByteSize, (void**) &newBlock);
+ if ( newBlock && ev->Good() ) // okay new block?
+ {
+ void** oldSlots = mArray_Slots;
+ void** oldEnd = oldSlots + mArray_Fill;
+
+ void** newSlots = newBlock;
+ void** newEnd = newBlock + inNewSize;
+
+ while ( oldSlots < oldEnd )
+ *newSlots++ = *oldSlots++;
+
+ while ( newSlots < newEnd )
+ *newSlots++ = (void*) 0;
+
+ oldSlots = mArray_Slots;
+ mArray_Size = inNewSize;
+ mArray_Slots = newBlock;
+ mArray_Heap->Free(ev->AsMdbEnv(), oldSlots);
+ }
+ }
+ else
+ this->FillBeyondSizeError(ev);
+ }
+ ++mArray_Seed; // always modify seed, since caller intends to add slots
+ return ( ev->Good() && mArray_Size >= inNewSize );
+}
+
+void*
+morkArray::SafeAt(morkEnv* ev, mork_pos inPos)
+{
+ if ( mArray_Slots )
+ {
+ if ( inPos >= 0 && inPos < (mork_pos) mArray_Fill )
+ return mArray_Slots[ inPos ];
+ else
+ this->IndexBeyondEndError(ev);
+ }
+ else
+ this->NilSlotsAddressError(ev);
+
+ return (void*) 0;
+}
+
+void
+morkArray::SafeAtPut(morkEnv* ev, mork_pos inPos, void* ioSlot)
+{
+ if ( mArray_Slots )
+ {
+ if ( inPos >= 0 && inPos < (mork_pos) mArray_Fill )
+ {
+ mArray_Slots[ inPos ] = ioSlot;
+ ++mArray_Seed;
+ }
+ else
+ this->IndexBeyondEndError(ev);
+ }
+ else
+ this->NilSlotsAddressError(ev);
+}
+
+mork_pos
+morkArray::AppendSlot(morkEnv* ev, void* ioSlot)
+{
+ mork_pos outPos = -1;
+ if ( mArray_Slots )
+ {
+ mork_fill fill = mArray_Fill;
+ if ( this->Grow(ev, fill+1) )
+ {
+ outPos = (mork_pos) fill;
+ mArray_Slots[ fill ] = ioSlot;
+ mArray_Fill = fill + 1;
+ // note Grow() increments mArray_Seed
+ }
+ }
+ else
+ this->NilSlotsAddressError(ev);
+
+ return outPos;
+}
+
+void
+morkArray::AddSlot(morkEnv* ev, mork_pos inPos, void* ioSlot)
+{
+ if ( mArray_Slots )
+ {
+ mork_fill fill = mArray_Fill;
+ if ( this->Grow(ev, fill+1) )
+ {
+ void** slot = mArray_Slots; // the slot vector
+ void** end = slot + fill; // one past the last used array slot
+ slot += inPos; // the slot to be added
+
+ while ( --end >= slot ) // another slot to move upward?
+ end[ 1 ] = *end;
+
+ *slot = ioSlot;
+ mArray_Fill = fill + 1;
+ // note Grow() increments mArray_Seed
+ }
+ }
+ else
+ this->NilSlotsAddressError(ev);
+}
+
+void
+morkArray::CutSlot(morkEnv* ev, mork_pos inPos)
+{
+ MORK_USED_1(ev);
+ mork_fill fill = mArray_Fill;
+ if ( inPos >= 0 && inPos < (mork_pos) fill ) // cutting slot in used array portion?
+ {
+ void** slot = mArray_Slots; // the slot vector
+ void** end = slot + fill; // one past the last used array slot
+ slot += inPos; // the slot to be cut
+
+ while ( ++slot < end ) // another slot to move downward?
+ slot[ -1 ] = *slot;
+
+ slot[ -1 ] = 0; // clear the last used slot which is now unused
+
+ // note inPos<fill implies fill>0, so fill-1 must be nonnegative:
+ mArray_Fill = fill - 1;
+ ++mArray_Seed;
+ }
+}
+
+void
+morkArray::CutAllSlots(morkEnv* ev)
+{
+ if ( mArray_Slots )
+ {
+ if ( mArray_Fill <= mArray_Size )
+ {
+ mdb_size oldByteSize = mArray_Fill * sizeof(void*);
+ MORK_MEMSET(mArray_Slots, 0, oldByteSize);
+ }
+ else
+ this->FillBeyondSizeError(ev);
+ }
+ else
+ this->NilSlotsAddressError(ev);
+
+ ++mArray_Seed;
+ mArray_Fill = 0;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkArray.h b/components/mork/src/morkArray.h
new file mode 100644
index 000000000..d987b5d25
--- /dev/null
+++ b/components/mork/src/morkArray.h
@@ -0,0 +1,98 @@
+/* -*- 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/. */
+
+#ifndef _MORKARRAY_
+#define _MORKARRAY_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kArray /*i*/ 0x4179 /* ascii 'Ay' */
+
+class morkArray : public morkNode { // row iterator
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+public: // state is public because the entire Mork system is private
+ void** mArray_Slots; // array of pointers
+ nsIMdbHeap* mArray_Heap; // required heap for allocating mArray_Slots
+ mork_fill mArray_Fill; // logical count of used slots in mArray_Slots
+ mork_size mArray_Size; // physical count of mArray_Slots ( >= Fill)
+ mork_seed mArray_Seed; // change counter for syncing with iterators
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseArray()
+ virtual ~morkArray(); // assert that close executed earlier
+
+public: // morkArray construction & destruction
+ morkArray(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, mork_size inSize, nsIMdbHeap* ioSlotHeap);
+ void CloseArray(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkArray(const morkArray& other);
+ morkArray& operator=(const morkArray& other);
+
+public: // dynamic type identification
+ mork_bool IsArray() const
+ { return IsNode() && mNode_Derived == morkDerived_kArray; }
+// } ===== end morkNode methods =====
+
+public: // typing & errors
+ static void NonArrayTypeError(morkEnv* ev);
+ static void IndexBeyondEndError(morkEnv* ev);
+ static void NilSlotsAddressError(morkEnv* ev);
+ static void FillBeyondSizeError(morkEnv* ev);
+
+public: // other table row cursor methods
+
+ mork_fill Length() const { return mArray_Fill; }
+ mork_size Capacity() const { return mArray_Size; }
+
+ mork_bool Grow(morkEnv* ev, mork_size inNewSize);
+ // Grow() returns true if capacity becomes >= inNewSize and ev->Good()
+
+ void* At(mork_pos inPos) const { return mArray_Slots[ inPos ]; }
+ void AtPut(mork_pos inPos, void* ioSlot)
+ { mArray_Slots[ inPos ] = ioSlot; }
+
+ void* SafeAt(morkEnv* ev, mork_pos inPos);
+ void SafeAtPut(morkEnv* ev, mork_pos inPos, void* ioSlot);
+
+ mork_pos AppendSlot(morkEnv* ev, void* ioSlot);
+ void AddSlot(morkEnv* ev, mork_pos inPos, void* ioSlot);
+ void CutSlot(morkEnv* ev, mork_pos inPos);
+ void CutAllSlots(morkEnv* ev);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakArray(morkArray* me,
+ morkEnv* ev, morkArray** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongArray(morkArray* me,
+ morkEnv* ev, morkArray** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKTABLEROWCURSOR_ */
diff --git a/components/mork/src/morkAtom.cpp b/components/mork/src/morkAtom.cpp
new file mode 100644
index 000000000..b0621c3e9
--- /dev/null
+++ b/components/mork/src/morkAtom.cpp
@@ -0,0 +1,523 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKBLOB_
+#include "morkBlob.h"
+#endif
+
+#ifndef _MORKATOM_
+#include "morkAtom.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKATOMSPACE_
+#include "morkAtomSpace.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/* static */
+mork_bool morkAtom::GetYarn(const morkAtom* atom, mdbYarn* outYarn)
+{
+ const void* source = 0;
+ mdb_fill fill = 0;
+ mdb_cscode form = 0;
+ outYarn->mYarn_More = 0;
+
+ if (atom) {
+ if (atom->IsWeeBook()) {
+ morkWeeBookAtom* weeBook = (morkWeeBookAtom*)atom;
+ source = weeBook->mWeeBookAtom_Body;
+ fill = weeBook->mAtom_Size;
+ } else if (atom->IsBigBook()) {
+ morkBigBookAtom* bigBook = (morkBigBookAtom*)atom;
+ source = bigBook->mBigBookAtom_Body;
+ fill = bigBook->mBigBookAtom_Size;
+ form = bigBook->mBigBookAtom_Form;
+ } else if (atom->IsWeeAnon()) {
+ morkWeeAnonAtom* weeAnon = (morkWeeAnonAtom*)atom;
+ source = weeAnon->mWeeAnonAtom_Body;
+ fill = weeAnon->mAtom_Size;
+ } else if (atom->IsBigAnon()) {
+ morkBigAnonAtom* bigAnon = (morkBigAnonAtom*)atom;
+ source = bigAnon->mBigAnonAtom_Body;
+ fill = bigAnon->mBigAnonAtom_Size;
+ form = bigAnon->mBigAnonAtom_Form;
+ }
+ }
+
+ if ( source && fill ) // have an atom with nonempty content?
+ {
+ // if we have too many bytes, and yarn seems growable:
+ if ( fill > outYarn->mYarn_Size && outYarn->mYarn_Grow ) // try grow?
+ (*outYarn->mYarn_Grow)(outYarn, (mdb_size) fill); // request bigger
+
+ mdb_size size = outYarn->mYarn_Size; // max dest size
+ if ( fill > size ) // too much atom content?
+ {
+ outYarn->mYarn_More = fill - size; // extra atom bytes omitted
+ fill = size; // copy no more bytes than size of yarn buffer
+ }
+ void* dest = outYarn->mYarn_Buf; // where bytes are going
+ if ( !dest ) // nil destination address buffer?
+ fill = 0; // we can't write any content at all
+
+ if ( fill ) // anything to copy?
+ MORK_MEMCPY(dest, source, fill); // copy fill bytes to yarn
+
+ outYarn->mYarn_Fill = fill; // tell yarn size of copied content
+ }
+ else // no content to put into the yarn
+ {
+ outYarn->mYarn_Fill = 0; // tell yarn that atom has no bytes
+ }
+ outYarn->mYarn_Form = form; // always update the form slot
+
+ return ( source != 0 );
+}
+
+/* static */
+mork_bool
+morkAtom::AliasYarn(const morkAtom* atom, mdbYarn* outYarn)
+{
+ outYarn->mYarn_More = 0;
+ outYarn->mYarn_Form = 0;
+
+ if ( atom )
+ {
+ if ( atom->IsWeeBook() )
+ {
+ morkWeeBookAtom* weeBook = (morkWeeBookAtom*) atom;
+ outYarn->mYarn_Buf = weeBook->mWeeBookAtom_Body;
+ outYarn->mYarn_Fill = weeBook->mAtom_Size;
+ outYarn->mYarn_Size = weeBook->mAtom_Size;
+ }
+ else if ( atom->IsBigBook() )
+ {
+ morkBigBookAtom* bigBook = (morkBigBookAtom*) atom;
+ outYarn->mYarn_Buf = bigBook->mBigBookAtom_Body;
+ outYarn->mYarn_Fill = bigBook->mBigBookAtom_Size;
+ outYarn->mYarn_Size = bigBook->mBigBookAtom_Size;
+ outYarn->mYarn_Form = bigBook->mBigBookAtom_Form;
+ }
+ else if ( atom->IsWeeAnon() )
+ {
+ morkWeeAnonAtom* weeAnon = (morkWeeAnonAtom*) atom;
+ outYarn->mYarn_Buf = weeAnon->mWeeAnonAtom_Body;
+ outYarn->mYarn_Fill = weeAnon->mAtom_Size;
+ outYarn->mYarn_Size = weeAnon->mAtom_Size;
+ }
+ else if ( atom->IsBigAnon() )
+ {
+ morkBigAnonAtom* bigAnon = (morkBigAnonAtom*) atom;
+ outYarn->mYarn_Buf = bigAnon->mBigAnonAtom_Body;
+ outYarn->mYarn_Fill = bigAnon->mBigAnonAtom_Size;
+ outYarn->mYarn_Size = bigAnon->mBigAnonAtom_Size;
+ outYarn->mYarn_Form = bigAnon->mBigAnonAtom_Form;
+ }
+ else
+ atom = 0; // show desire to put empty content in yarn
+ }
+
+ if ( !atom ) // empty content for yarn?
+ {
+ outYarn->mYarn_Buf = 0;
+ outYarn->mYarn_Fill = 0;
+ outYarn->mYarn_Size = 0;
+ // outYarn->mYarn_Grow = 0; // please don't modify the Grow slot
+ }
+ return ( atom != 0 );
+}
+
+mork_aid
+morkAtom::GetBookAtomAid() const // zero or book atom's ID
+{
+ return ( this->IsBook() )? ((morkBookAtom*) this)->mBookAtom_Id : 0;
+}
+
+mork_scope
+morkAtom::GetBookAtomSpaceScope(morkEnv* ev) const // zero or book's space's scope
+{
+ mork_scope outScope = 0;
+ if ( this->IsBook() )
+ {
+ const morkBookAtom* bookAtom = (const morkBookAtom*) this;
+ morkAtomSpace* space = bookAtom->mBookAtom_Space;
+ if ( space->IsAtomSpace() )
+ outScope = space->SpaceScope();
+ else
+ space->NonAtomSpaceTypeError(ev);
+ }
+
+ return outScope;
+}
+
+void
+morkAtom::MakeCellUseForever(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ mAtom_CellUses = morkAtom_kForeverCellUses;
+}
+
+mork_u1
+morkAtom::AddCellUse(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ if ( mAtom_CellUses < morkAtom_kMaxCellUses ) // not already maxed out?
+ ++mAtom_CellUses;
+
+ return mAtom_CellUses;
+}
+
+mork_u1
+morkAtom::CutCellUse(morkEnv* ev)
+{
+ if ( mAtom_CellUses ) // any outstanding uses to cut?
+ {
+ if ( mAtom_CellUses < morkAtom_kMaxCellUses ) // not frozen at max?
+ --mAtom_CellUses;
+ }
+ else
+ this->CellUsesUnderflowWarning(ev);
+
+ return mAtom_CellUses;
+}
+
+/*static*/ void
+morkAtom::CellUsesUnderflowWarning(morkEnv* ev)
+{
+ ev->NewWarning("mAtom_CellUses underflow");
+}
+
+/*static*/ void
+morkAtom::BadAtomKindError(morkEnv* ev)
+{
+ ev->NewError("bad mAtom_Kind");
+}
+
+/*static*/ void
+morkAtom::ZeroAidError(morkEnv* ev)
+{
+ ev->NewError("zero atom ID");
+}
+
+/*static*/ void
+morkAtom::AtomSizeOverflowError(morkEnv* ev)
+{
+ ev->NewError("atom mAtom_Size overflow");
+}
+
+void
+morkOidAtom::InitRowOidAtom(morkEnv* ev, const mdbOid& inOid)
+{
+ MORK_USED_1(ev);
+ mAtom_CellUses = 0;
+ mAtom_Kind = morkAtom_kKindRowOid;
+ mAtom_Change = morkChange_kNil;
+ mAtom_Size = 0;
+ mOidAtom_Oid = inOid; // bitwise copy
+}
+
+void
+morkOidAtom::InitTableOidAtom(morkEnv* ev, const mdbOid& inOid)
+{
+ MORK_USED_1(ev);
+ mAtom_CellUses = 0;
+ mAtom_Kind = morkAtom_kKindTableOid;
+ mAtom_Change = morkChange_kNil;
+ mAtom_Size = 0;
+ mOidAtom_Oid = inOid; // bitwise copy
+}
+
+void
+morkWeeAnonAtom::InitWeeAnonAtom(morkEnv* ev, const morkBuf& inBuf)
+{
+ mAtom_Kind = 0;
+ mAtom_Change = morkChange_kNil;
+ if ( inBuf.mBuf_Fill <= morkAtom_kMaxByteSize )
+ {
+ mAtom_CellUses = 0;
+ mAtom_Kind = morkAtom_kKindWeeAnon;
+ mork_size size = inBuf.mBuf_Fill;
+ mAtom_Size = (mork_u1) size;
+ if ( size && inBuf.mBuf_Body )
+ MORK_MEMCPY(mWeeAnonAtom_Body, inBuf.mBuf_Body, size);
+
+ mWeeAnonAtom_Body[ size ] = 0;
+ }
+ else
+ this->AtomSizeOverflowError(ev);
+}
+
+void
+morkBigAnonAtom::InitBigAnonAtom(morkEnv* ev, const morkBuf& inBuf,
+ mork_cscode inForm)
+{
+ MORK_USED_1(ev);
+ mAtom_CellUses = 0;
+ mAtom_Kind = morkAtom_kKindBigAnon;
+ mAtom_Change = morkChange_kNil;
+ mAtom_Size = 0;
+ mBigAnonAtom_Form = inForm;
+ mork_size size = inBuf.mBuf_Fill;
+ mBigAnonAtom_Size = size;
+ if ( size && inBuf.mBuf_Body )
+ MORK_MEMCPY(mBigAnonAtom_Body, inBuf.mBuf_Body, size);
+
+ mBigAnonAtom_Body[ size ] = 0;
+}
+
+/*static*/ void
+morkBookAtom::NonBookAtomTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkBookAtom");
+}
+
+mork_u4
+morkBookAtom::HashFormAndBody(morkEnv* ev) const
+{
+ // This hash is obviously a variation of the dragon book string hash.
+ // (I won't bother to explain or rationalize this usage for you.)
+
+ mork_u4 outHash = 0; // hash value returned
+ unsigned char c; // next character
+ const mork_u1* body; // body of bytes to hash
+ mork_size size = 0; // the number of bytes to hash
+
+ if ( this->IsWeeBook() )
+ {
+ size = mAtom_Size;
+ body = ((const morkWeeBookAtom*) this)->mWeeBookAtom_Body;
+ }
+ else if ( this->IsBigBook() )
+ {
+ size = ((const morkBigBookAtom*) this)->mBigBookAtom_Size;
+ body = ((const morkBigBookAtom*) this)->mBigBookAtom_Body;
+ }
+ else if ( this->IsFarBook() )
+ {
+ size = ((const morkFarBookAtom*) this)->mFarBookAtom_Size;
+ body = ((const morkFarBookAtom*) this)->mFarBookAtom_Body;
+ }
+ else
+ {
+ this->NonBookAtomTypeError(ev);
+ return 0;
+ }
+
+ const mork_u1* end = body + size;
+ while ( body < end )
+ {
+ c = *body++;
+ outHash <<= 4;
+ outHash += c;
+ mork_u4 top = outHash & 0xF0000000L; // top four bits
+ if ( top ) // any of high four bits equal to one?
+ {
+ outHash ^= (top >> 24); // fold down high bits
+ outHash ^= top; // zero top four bits
+ }
+ }
+
+ return outHash;
+}
+
+mork_bool
+morkBookAtom::EqualFormAndBody(morkEnv* ev, const morkBookAtom* inAtom) const
+{
+ mork_bool outEqual = morkBool_kFalse;
+
+ const mork_u1* body = 0; // body of inAtom bytes to compare
+ mork_size size; // the number of inAtom bytes to compare
+ mork_cscode form; // nominal charset for ioAtom
+
+ if ( inAtom->IsWeeBook() )
+ {
+ size = inAtom->mAtom_Size;
+ body = ((const morkWeeBookAtom*) inAtom)->mWeeBookAtom_Body;
+ form = 0;
+ }
+ else if ( inAtom->IsBigBook() )
+ {
+ size = ((const morkBigBookAtom*) inAtom)->mBigBookAtom_Size;
+ body = ((const morkBigBookAtom*) inAtom)->mBigBookAtom_Body;
+ form = ((const morkBigBookAtom*) inAtom)->mBigBookAtom_Form;
+ }
+ else if ( inAtom->IsFarBook() )
+ {
+ size = ((const morkFarBookAtom*) inAtom)->mFarBookAtom_Size;
+ body = ((const morkFarBookAtom*) inAtom)->mFarBookAtom_Body;
+ form = ((const morkFarBookAtom*) inAtom)->mFarBookAtom_Form;
+ }
+ else
+ {
+ inAtom->NonBookAtomTypeError(ev);
+ return morkBool_kFalse;
+ }
+
+ const mork_u1* thisBody = 0; // body of bytes in this to compare
+ mork_size thisSize; // the number of bytes in this to compare
+ mork_cscode thisForm; // nominal charset for this atom
+
+ if ( this->IsWeeBook() )
+ {
+ thisSize = mAtom_Size;
+ thisBody = ((const morkWeeBookAtom*) this)->mWeeBookAtom_Body;
+ thisForm = 0;
+ }
+ else if ( this->IsBigBook() )
+ {
+ thisSize = ((const morkBigBookAtom*) this)->mBigBookAtom_Size;
+ thisBody = ((const morkBigBookAtom*) this)->mBigBookAtom_Body;
+ thisForm = ((const morkBigBookAtom*) this)->mBigBookAtom_Form;
+ }
+ else if ( this->IsFarBook() )
+ {
+ thisSize = ((const morkFarBookAtom*) this)->mFarBookAtom_Size;
+ thisBody = ((const morkFarBookAtom*) this)->mFarBookAtom_Body;
+ thisForm = ((const morkFarBookAtom*) this)->mFarBookAtom_Form;
+ }
+ else
+ {
+ this->NonBookAtomTypeError(ev);
+ return morkBool_kFalse;
+ }
+
+ // if atoms are empty, form is irrelevant
+ if ( body && thisBody && size == thisSize && (!size || form == thisForm ))
+ outEqual = (MORK_MEMCMP(body, thisBody, size) == 0);
+
+ return outEqual;
+}
+
+
+void
+morkBookAtom::CutBookAtomFromSpace(morkEnv* ev)
+{
+ morkAtomSpace* space = mBookAtom_Space;
+ if ( space )
+ {
+ mBookAtom_Space = 0;
+ space->mAtomSpace_AtomBodies.CutAtom(ev, this);
+ space->mAtomSpace_AtomAids.CutAtom(ev, this);
+ }
+ else
+ ev->NilPointerError();
+}
+
+morkWeeBookAtom::morkWeeBookAtom(mork_aid inAid)
+{
+ mAtom_Kind = morkAtom_kKindWeeBook;
+ mAtom_CellUses = 0;
+ mAtom_Change = morkChange_kNil;
+ mAtom_Size = 0;
+
+ mBookAtom_Space = 0;
+ mBookAtom_Id = inAid;
+
+ mWeeBookAtom_Body[ 0 ] = 0;
+}
+
+void
+morkWeeBookAtom::InitWeeBookAtom(morkEnv* ev, const morkBuf& inBuf,
+ morkAtomSpace* ioSpace, mork_aid inAid)
+{
+ mAtom_Kind = 0;
+ mAtom_Change = morkChange_kNil;
+ if ( ioSpace )
+ {
+ if ( inAid )
+ {
+ if ( inBuf.mBuf_Fill <= morkAtom_kMaxByteSize )
+ {
+ mAtom_CellUses = 0;
+ mAtom_Kind = morkAtom_kKindWeeBook;
+ mBookAtom_Space = ioSpace;
+ mBookAtom_Id = inAid;
+ mork_size size = inBuf.mBuf_Fill;
+ mAtom_Size = (mork_u1) size;
+ if ( size && inBuf.mBuf_Body )
+ MORK_MEMCPY(mWeeBookAtom_Body, inBuf.mBuf_Body, size);
+
+ mWeeBookAtom_Body[ size ] = 0;
+ }
+ else
+ this->AtomSizeOverflowError(ev);
+ }
+ else
+ this->ZeroAidError(ev);
+ }
+ else
+ ev->NilPointerError();
+}
+
+void
+morkBigBookAtom::InitBigBookAtom(morkEnv* ev, const morkBuf& inBuf,
+ mork_cscode inForm, morkAtomSpace* ioSpace, mork_aid inAid)
+{
+ mAtom_Kind = 0;
+ mAtom_Change = morkChange_kNil;
+ if ( ioSpace )
+ {
+ if ( inAid )
+ {
+ mAtom_CellUses = 0;
+ mAtom_Kind = morkAtom_kKindBigBook;
+ mAtom_Size = 0;
+ mBookAtom_Space = ioSpace;
+ mBookAtom_Id = inAid;
+ mBigBookAtom_Form = inForm;
+ mork_size size = inBuf.mBuf_Fill;
+ mBigBookAtom_Size = size;
+ if ( size && inBuf.mBuf_Body )
+ MORK_MEMCPY(mBigBookAtom_Body, inBuf.mBuf_Body, size);
+
+ mBigBookAtom_Body[ size ] = 0;
+ }
+ else
+ this->ZeroAidError(ev);
+ }
+ else
+ ev->NilPointerError();
+}
+
+void morkFarBookAtom::InitFarBookAtom(morkEnv* ev, const morkBuf& inBuf,
+ mork_cscode inForm, morkAtomSpace* ioSpace, mork_aid inAid)
+{
+ mAtom_Kind = 0;
+ mAtom_Change = morkChange_kNil;
+ if ( ioSpace )
+ {
+ if ( inAid )
+ {
+ mAtom_CellUses = 0;
+ mAtom_Kind = morkAtom_kKindFarBook;
+ mAtom_Size = 0;
+ mBookAtom_Space = ioSpace;
+ mBookAtom_Id = inAid;
+ mFarBookAtom_Form = inForm;
+ mFarBookAtom_Size = inBuf.mBuf_Fill;
+ mFarBookAtom_Body = (mork_u1*) inBuf.mBuf_Body;
+ }
+ else
+ this->ZeroAidError(ev);
+ }
+ else
+ ev->NilPointerError();
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
diff --git a/components/mork/src/morkAtom.h b/components/mork/src/morkAtom.h
new file mode 100644
index 000000000..1afe21cd8
--- /dev/null
+++ b/components/mork/src/morkAtom.h
@@ -0,0 +1,365 @@
+/* -*- 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/. */
+
+#ifndef _MORKATOM_
+#define _MORKATOM_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+#define morkAtom_kMaxByteSize 255 /* max for 8-bit integer */
+#define morkAtom_kForeverCellUses 0x0FF /* max for 8-bit integer */
+#define morkAtom_kMaxCellUses 0x07F /* max for 7-bit integer */
+
+#define morkAtom_kKindWeeAnon 'a' /* means morkWeeAnonAtom subclass */
+#define morkAtom_kKindBigAnon 'A' /* means morkBigAnonAtom subclass */
+#define morkAtom_kKindWeeBook 'b' /* means morkWeeBookAtom subclass */
+#define morkAtom_kKindBigBook 'B' /* means morkBigBookAtom subclass */
+#define morkAtom_kKindFarBook 'f' /* means morkFarBookAtom subclass */
+#define morkAtom_kKindRowOid 'r' /* means morkOidAtom subclass */
+#define morkAtom_kKindTableOid 't' /* means morkOidAtom subclass */
+
+/*| Atom: .
+|*/
+class morkAtom { //
+
+public:
+
+ mork_u1 mAtom_Kind; // identifies a specific atom subclass
+ mork_u1 mAtom_CellUses; // number of persistent uses in a cell
+ mork_change mAtom_Change; // how has this atom been changed?
+ mork_u1 mAtom_Size; // only for atoms smaller than 256 bytes
+
+public:
+ morkAtom(mork_aid inAid, mork_u1 inKind);
+
+ mork_bool IsWeeAnon() const { return mAtom_Kind == morkAtom_kKindWeeAnon; }
+ mork_bool IsBigAnon() const { return mAtom_Kind == morkAtom_kKindBigAnon; }
+ mork_bool IsWeeBook() const { return mAtom_Kind == morkAtom_kKindWeeBook; }
+ mork_bool IsBigBook() const { return mAtom_Kind == morkAtom_kKindBigBook; }
+ mork_bool IsFarBook() const { return mAtom_Kind == morkAtom_kKindFarBook; }
+ mork_bool IsRowOid() const { return mAtom_Kind == morkAtom_kKindRowOid; }
+ mork_bool IsTableOid() const { return mAtom_Kind == morkAtom_kKindTableOid; }
+
+ mork_bool IsBook() const { return this->IsWeeBook() || this->IsBigBook(); }
+
+public: // clean vs dirty
+
+ void SetAtomClean() { mAtom_Change = morkChange_kNil; }
+ void SetAtomDirty() { mAtom_Change = morkChange_kAdd; }
+
+ mork_bool IsAtomClean() const { return mAtom_Change == morkChange_kNil; }
+ mork_bool IsAtomDirty() const { return mAtom_Change == morkChange_kAdd; }
+
+public: // atom space scope if IsBook() is true, or else zero:
+
+ mork_scope GetBookAtomSpaceScope(morkEnv* ev) const;
+ // zero or book's space's scope
+
+ mork_aid GetBookAtomAid() const;
+ // zero or book atom's ID
+
+public: // empty construction does nothing
+ morkAtom() { }
+
+public: // one-byte refcounting, freezing at maximum
+ void MakeCellUseForever(morkEnv* ev);
+ mork_u1 AddCellUse(morkEnv* ev);
+ mork_u1 CutCellUse(morkEnv* ev);
+
+ mork_bool IsCellUseForever() const
+ { return mAtom_CellUses == morkAtom_kForeverCellUses; }
+
+private: // warnings
+
+ static void CellUsesUnderflowWarning(morkEnv* ev);
+
+public: // errors
+
+ static void BadAtomKindError(morkEnv* ev);
+ static void ZeroAidError(morkEnv* ev);
+ static void AtomSizeOverflowError(morkEnv* ev);
+
+public: // yarns
+
+ static mork_bool AliasYarn(const morkAtom* atom, mdbYarn* outYarn);
+ static mork_bool GetYarn(const morkAtom* atom, mdbYarn* outYarn);
+
+private: // copying is not allowed
+ morkAtom(const morkAtom& other);
+ morkAtom& operator=(const morkAtom& other);
+};
+
+/*| OidAtom: an atom that references a row or table by identity.
+|*/
+class morkOidAtom : public morkAtom { //
+
+ // mork_u1 mAtom_Kind; // identifies a specific atom subclass
+ // mork_u1 mAtom_CellUses; // number of persistent uses in a cell
+ // mork_change mAtom_Change; // how has this atom been changed?
+ // mork_u1 mAtom_Size; // NOT USED IN "BIG" format atoms
+
+public:
+ mdbOid mOidAtom_Oid; // identity of referenced object
+
+public: // empty construction does nothing
+ morkOidAtom() { }
+ void InitRowOidAtom(morkEnv* ev, const mdbOid& inOid);
+ void InitTableOidAtom(morkEnv* ev, const mdbOid& inOid);
+
+private: // copying is not allowed
+ morkOidAtom(const morkOidAtom& other);
+ morkOidAtom& operator=(const morkOidAtom& other);
+};
+
+/*| WeeAnonAtom: an atom whose content immediately follows morkAtom slots
+**| in an inline fashion, so that morkWeeAnonAtom contains both leading
+**| atom slots and then the content bytes without further overhead. Note
+**| that charset encoding is not indicated, so zero is implied for Latin1.
+**| (Non-Latin1 content must be stored in a morkBigAnonAtom with a charset.)
+**|
+**|| An anon (anonymous) atom has no identity, with no associated bookkeeping
+**| for lookup needed for sharing like a book atom.
+**|
+**|| A wee anon atom is immediate but not shared with any other users of this
+**| atom, so no bookkeeping for sharing is needed. This means the atom has
+**| no ID, because the atom has no identity other than this immediate content,
+**| and no hash table is needed to look up this particular atom. This also
+**| applies to the larger format morkBigAnonAtom, which has more slots.
+|*/
+class morkWeeAnonAtom : public morkAtom { //
+
+ // mork_u1 mAtom_Kind; // identifies a specific atom subclass
+ // mork_u1 mAtom_CellUses; // number of persistent uses in a cell
+ // mork_change mAtom_Change; // how has this atom been changed?
+ // mork_u1 mAtom_Size; // only for atoms smaller than 256 bytes
+
+public:
+ mork_u1 mWeeAnonAtom_Body[ 1 ]; // 1st byte of immediate content vector
+
+public: // empty construction does nothing
+ morkWeeAnonAtom() { }
+ void InitWeeAnonAtom(morkEnv* ev, const morkBuf& inBuf);
+
+ // allow extra trailing byte for a null byte:
+ static mork_size SizeForFill(mork_fill inFill)
+ { return sizeof(morkWeeAnonAtom) + inFill; }
+
+private: // copying is not allowed
+ morkWeeAnonAtom(const morkWeeAnonAtom& other);
+ morkWeeAnonAtom& operator=(const morkWeeAnonAtom& other);
+};
+
+/*| BigAnonAtom: another immediate atom that cannot be encoded as the smaller
+**| morkWeeAnonAtom format because either the size is too great, and/or the
+**| charset is not the default zero for Latin1 and must be explicitly noted.
+**|
+**|| An anon (anonymous) atom has no identity, with no associated bookkeeping
+**| for lookup needed for sharing like a book atom.
+|*/
+class morkBigAnonAtom : public morkAtom { //
+
+ // mork_u1 mAtom_Kind; // identifies a specific atom subclass
+ // mork_u1 mAtom_CellUses; // number of persistent uses in a cell
+ // mork_change mAtom_Change; // how has this atom been changed?
+ // mork_u1 mAtom_Size; // NOT USED IN "BIG" format atoms
+
+public:
+ mork_cscode mBigAnonAtom_Form; // charset format encoding
+ mork_size mBigAnonAtom_Size; // size of content vector
+ mork_u1 mBigAnonAtom_Body[ 1 ]; // 1st byte of immed content vector
+
+public: // empty construction does nothing
+ morkBigAnonAtom() { }
+ void InitBigAnonAtom(morkEnv* ev, const morkBuf& inBuf, mork_cscode inForm);
+
+ // allow extra trailing byte for a null byte:
+ static mork_size SizeForFill(mork_fill inFill)
+ { return sizeof(morkBigAnonAtom) + inFill; }
+
+private: // copying is not allowed
+ morkBigAnonAtom(const morkBigAnonAtom& other);
+ morkBigAnonAtom& operator=(const morkBigAnonAtom& other);
+};
+
+#define morkBookAtom_kMaxBodySize 1024 /* if larger, cannot be shared */
+
+/*| BookAtom: the common subportion of wee book atoms and big book atoms that
+**| includes the atom ID and the pointer to the space referencing this atom
+**| through a hash table.
+|*/
+class morkBookAtom : public morkAtom { //
+ // mork_u1 mAtom_Kind; // identifies a specific atom subclass
+ // mork_u1 mAtom_CellUses; // number of persistent uses in a cell
+ // mork_change mAtom_Change; // how has this atom been changed?
+ // mork_u1 mAtom_Size; // only for atoms smaller than 256 bytes
+
+public:
+ morkAtomSpace* mBookAtom_Space; // mBookAtom_Space->SpaceScope() is atom scope
+ mork_aid mBookAtom_Id; // identity token for this shared atom
+
+public: // empty construction does nothing
+ morkBookAtom() { }
+
+ static void NonBookAtomTypeError(morkEnv* ev);
+
+public: // Hash() and Equal() for atom ID maps are same for all subclasses:
+
+ mork_u4 HashAid() const { return mBookAtom_Id; }
+ mork_bool EqualAid(const morkBookAtom* inAtom) const
+ { return ( mBookAtom_Id == inAtom->mBookAtom_Id); }
+
+public: // Hash() and Equal() for atom body maps know about subclasses:
+
+ // YOU CANNOT SUBCLASS morkBookAtom WITHOUT FIXING Hash and Equal METHODS:
+
+ mork_u4 HashFormAndBody(morkEnv* ev) const;
+ mork_bool EqualFormAndBody(morkEnv* ev, const morkBookAtom* inAtom) const;
+
+public: // separation from containing space
+
+ void CutBookAtomFromSpace(morkEnv* ev);
+
+private: // copying is not allowed
+ morkBookAtom(const morkBookAtom& other);
+ morkBookAtom& operator=(const morkBookAtom& other);
+};
+
+/*| FarBookAtom: this alternative format for book atoms was introduced
+**| in May 2000 in order to support finding atoms in hash tables without
+**| first copying the strings from original parsing buffers into a new
+**| atom format. This was consuming too much time. However, we can
+**| use morkFarBookAtom to stage a hash table query, as long as we then
+**| fix HashFormAndBody() and EqualFormAndBody() to use morkFarBookAtom
+**| correctly.
+**|
+**|| Note we do NOT intend that instances of morkFarBookAtom will ever
+**| be installed in hash tables, because this is not space efficient.
+**| We only expect to create temp instances for table lookups.
+|*/
+class morkFarBookAtom : public morkBookAtom { //
+
+ // mork_u1 mAtom_Kind; // identifies a specific atom subclass
+ // mork_u1 mAtom_CellUses; // number of persistent uses in a cell
+ // mork_change mAtom_Change; // how has this atom been changed?
+ // mork_u1 mAtom_Size; // NOT USED IN "BIG" format atoms
+
+ // morkAtomSpace* mBookAtom_Space; // mBookAtom_Space->SpaceScope() is scope
+ // mork_aid mBookAtom_Id; // identity token for this shared atom
+
+public:
+ mork_cscode mFarBookAtom_Form; // charset format encoding
+ mork_size mFarBookAtom_Size; // size of content vector
+ mork_u1* mFarBookAtom_Body; // bytes are elsewere, out of line
+
+public: // empty construction does nothing
+ morkFarBookAtom() { }
+ void InitFarBookAtom(morkEnv* ev, const morkBuf& inBuf,
+ mork_cscode inForm, morkAtomSpace* ioSpace, mork_aid inAid);
+
+private: // copying is not allowed
+ morkFarBookAtom(const morkFarBookAtom& other);
+ morkFarBookAtom& operator=(const morkFarBookAtom& other);
+};
+
+/*| WeeBookAtom: .
+|*/
+class morkWeeBookAtom : public morkBookAtom { //
+ // mork_u1 mAtom_Kind; // identifies a specific atom subclass
+ // mork_u1 mAtom_CellUses; // number of persistent uses in a cell
+ // mork_change mAtom_Change; // how has this atom been changed?
+ // mork_u1 mAtom_Size; // only for atoms smaller than 256 bytes
+
+ // morkAtomSpace* mBookAtom_Space; // mBookAtom_Space->SpaceScope() is scope
+ // mork_aid mBookAtom_Id; // identity token for this shared atom
+
+public:
+ mork_u1 mWeeBookAtom_Body[ 1 ]; // 1st byte of immed content vector
+
+public: // empty construction does nothing
+ morkWeeBookAtom() { }
+ explicit morkWeeBookAtom(mork_aid inAid);
+
+ void InitWeeBookAtom(morkEnv* ev, const morkBuf& inBuf,
+ morkAtomSpace* ioSpace, mork_aid inAid);
+
+ // allow extra trailing byte for a null byte:
+ static mork_size SizeForFill(mork_fill inFill)
+ { return sizeof(morkWeeBookAtom) + inFill; }
+
+private: // copying is not allowed
+ morkWeeBookAtom(const morkWeeBookAtom& other);
+ morkWeeBookAtom& operator=(const morkWeeBookAtom& other);
+};
+
+/*| BigBookAtom: .
+|*/
+class morkBigBookAtom : public morkBookAtom { //
+
+ // mork_u1 mAtom_Kind; // identifies a specific atom subclass
+ // mork_u1 mAtom_CellUses; // number of persistent uses in a cell
+ // mork_change mAtom_Change; // how has this atom been changed?
+ // mork_u1 mAtom_Size; // NOT USED IN "BIG" format atoms
+
+ // morkAtomSpace* mBookAtom_Space; // mBookAtom_Space->SpaceScope() is scope
+ // mork_aid mBookAtom_Id; // identity token for this shared atom
+
+public:
+ mork_cscode mBigBookAtom_Form; // charset format encoding
+ mork_size mBigBookAtom_Size; // size of content vector
+ mork_u1 mBigBookAtom_Body[ 1 ]; // 1st byte of immed content vector
+
+public: // empty construction does nothing
+ morkBigBookAtom() { }
+ void InitBigBookAtom(morkEnv* ev, const morkBuf& inBuf,
+ mork_cscode inForm, morkAtomSpace* ioSpace, mork_aid inAid);
+
+ // allow extra trailing byte for a null byte:
+ static mork_size SizeForFill(mork_fill inFill)
+ { return sizeof(morkBigBookAtom) + inFill; }
+
+private: // copying is not allowed
+ morkBigBookAtom(const morkBigBookAtom& other);
+ morkBigBookAtom& operator=(const morkBigBookAtom& other);
+};
+
+/*| MaxBookAtom: .
+|*/
+class morkMaxBookAtom : public morkBigBookAtom { //
+
+ // mork_u1 mAtom_Kind; // identifies a specific atom subclass
+ // mork_u1 mAtom_CellUses; // number of persistent uses in a cell
+ // mork_change mAtom_Change; // how has this atom been changed?
+ // mork_u1 mAtom_Size; // NOT USED IN "BIG" format atoms
+
+ // morkAtomSpace* mBookAtom_Space; // mBookAtom_Space->SpaceScope() is scope
+ // mork_aid mBookAtom_Id; // identity token for this shared atom
+
+ // mork_cscode mBigBookAtom_Form; // charset format encoding
+ // mork_size mBigBookAtom_Size; // size of content vector
+ // mork_u1 mBigBookAtom_Body[ 1 ]; // 1st byte of immed content vector
+
+public:
+ mork_u1 mMaxBookAtom_Body[ morkBookAtom_kMaxBodySize + 3 ]; // max bytes
+
+public: // empty construction does nothing
+ morkMaxBookAtom() { }
+ void InitMaxBookAtom(morkEnv* ev, const morkBuf& inBuf,
+ mork_cscode inForm, morkAtomSpace* ioSpace, mork_aid inAid)
+ { this->InitBigBookAtom(ev, inBuf, inForm, ioSpace, inAid); }
+
+private: // copying is not allowed
+ morkMaxBookAtom(const morkMaxBookAtom& other);
+ morkMaxBookAtom& operator=(const morkMaxBookAtom& other);
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKATOM_ */
+
diff --git a/components/mork/src/morkAtomMap.cpp b/components/mork/src/morkAtomMap.cpp
new file mode 100644
index 000000000..2d8658402
--- /dev/null
+++ b/components/mork/src/morkAtomMap.cpp
@@ -0,0 +1,427 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKATOMMAP_
+#include "morkAtomMap.h"
+#endif
+
+#ifndef _MORKATOM_
+#include "morkAtom.h"
+#endif
+
+#ifndef _MORKINTMAP_
+#include "morkIntMap.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkAtomAidMap::CloseMorkNode(morkEnv* ev) // CloseAtomAidMap() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseAtomAidMap(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkAtomAidMap::~morkAtomAidMap() // assert CloseAtomAidMap() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+
+/*public non-poly*/
+morkAtomAidMap::morkAtomAidMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+#ifdef MORK_ENABLE_PROBE_MAPS
+: morkProbeMap(ev, inUsage, ioHeap,
+ /*inKeySize*/ sizeof(morkBookAtom*), /*inValSize*/ 0,
+ ioSlotHeap, morkAtomAidMap_kStartSlotCount,
+ /*inZeroIsClearKey*/ morkBool_kTrue)
+#else /*MORK_ENABLE_PROBE_MAPS*/
+: morkMap(ev, inUsage, ioHeap,
+ /*inKeySize*/ sizeof(morkBookAtom*), /*inValSize*/ 0,
+ morkAtomAidMap_kStartSlotCount, ioSlotHeap,
+ /*inHoldChanges*/ morkBool_kFalse)
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kAtomAidMap;
+}
+
+/*public non-poly*/ void
+morkAtomAidMap::CloseAtomAidMap(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+#ifdef MORK_ENABLE_PROBE_MAPS
+ this->CloseProbeMap(ev);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ this->CloseMap(ev);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+
+ /*virtual*/ mork_test // hit(a,b) implies hash(a) == hash(b)
+ morkAtomAidMap::MapTest(morkEnv* ev, const void* inMapKey,
+ const void* inAppKey) const
+ {
+ MORK_USED_1(ev);
+ const morkBookAtom* key = *(const morkBookAtom**) inMapKey;
+ if ( key )
+ {
+ mork_bool hit = key->EqualAid(*(const morkBookAtom**) inAppKey);
+ return ( hit ) ? morkTest_kHit : morkTest_kMiss;
+ }
+ else
+ return morkTest_kVoid;
+ }
+
+ /*virtual*/ mork_u4 // hit(a,b) implies hash(a) == hash(b)
+ morkAtomAidMap::MapHash(morkEnv* ev, const void* inAppKey) const
+ {
+ const morkBookAtom* key = *(const morkBookAtom**) inAppKey;
+ if ( key )
+ return key->HashAid();
+ else
+ {
+ ev->NilPointerWarning();
+ return 0;
+ }
+ }
+
+ /*virtual*/ mork_u4
+ morkAtomAidMap::ProbeMapHashMapKey(morkEnv* ev,
+ const void* inMapKey) const
+ {
+ const morkBookAtom* key = *(const morkBookAtom**) inMapKey;
+ if ( key )
+ return key->HashAid();
+ else
+ {
+ ev->NilPointerWarning();
+ return 0;
+ }
+ }
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ // { ===== begin morkMap poly interface =====
+ /*virtual*/ mork_bool //
+ morkAtomAidMap::Equal(morkEnv* ev, const void* inKeyA,
+ const void* inKeyB) const
+ {
+ MORK_USED_1(ev);
+ return (*(const morkBookAtom**) inKeyA)->EqualAid(
+ *(const morkBookAtom**) inKeyB);
+ }
+
+ /*virtual*/ mork_u4 //
+ morkAtomAidMap::Hash(morkEnv* ev, const void* inKey) const
+ {
+ MORK_USED_1(ev);
+ return (*(const morkBookAtom**) inKey)->HashAid();
+ }
+ // } ===== end morkMap poly interface =====
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+
+mork_bool
+morkAtomAidMap::AddAtom(morkEnv* ev, morkBookAtom* ioAtom)
+{
+ if ( ev->Good() )
+ {
+#ifdef MORK_ENABLE_PROBE_MAPS
+ this->MapAtPut(ev, &ioAtom, /*val*/ (void*) 0,
+ /*key*/ (void*) 0, /*val*/ (void*) 0);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ this->Put(ev, &ioAtom, /*val*/ (void*) 0,
+ /*key*/ (void*) 0, /*val*/ (void*) 0, (mork_change**) 0);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ }
+ return ev->Good();
+}
+
+morkBookAtom*
+morkAtomAidMap::CutAtom(morkEnv* ev, const morkBookAtom* inAtom)
+{
+ morkBookAtom* oldKey = 0;
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ MORK_USED_1(inAtom);
+ morkProbeMap::ProbeMapCutError(ev);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ this->Cut(ev, &inAtom, &oldKey, /*val*/ (void*) 0,
+ (mork_change**) 0);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+ return oldKey;
+}
+
+morkBookAtom*
+morkAtomAidMap::GetAtom(morkEnv* ev, const morkBookAtom* inAtom)
+{
+ morkBookAtom* key = 0; // old val in the map
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ this->MapAt(ev, &inAtom, &key, /*val*/ (void*) 0);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ this->Get(ev, &inAtom, &key, /*val*/ (void*) 0, (mork_change**) 0);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+ return key;
+}
+
+morkBookAtom*
+morkAtomAidMap::GetAid(morkEnv* ev, mork_aid inAid)
+{
+ morkWeeBookAtom weeAtom(inAid);
+ morkBookAtom* key = &weeAtom; // we need a pointer
+ morkBookAtom* oldKey = 0; // old key in the map
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ this->MapAt(ev, &key, &oldKey, /*val*/ (void*) 0);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ this->Get(ev, &key, &oldKey, /*val*/ (void*) 0, (mork_change**) 0);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+ return oldKey;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkAtomBodyMap::CloseMorkNode(morkEnv* ev) // CloseAtomBodyMap() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseAtomBodyMap(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkAtomBodyMap::~morkAtomBodyMap() // assert CloseAtomBodyMap() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+
+/*public non-poly*/
+morkAtomBodyMap::morkAtomBodyMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+#ifdef MORK_ENABLE_PROBE_MAPS
+: morkProbeMap(ev, inUsage, ioHeap,
+ /*inKeySize*/ sizeof(morkBookAtom*), /*inValSize*/ 0,
+ ioSlotHeap, morkAtomBodyMap_kStartSlotCount,
+ /*inZeroIsClearKey*/ morkBool_kTrue)
+#else /*MORK_ENABLE_PROBE_MAPS*/
+: morkMap(ev, inUsage, ioHeap,
+ /*inKeySize*/ sizeof(morkBookAtom*), /*inValSize*/ 0,
+ morkAtomBodyMap_kStartSlotCount, ioSlotHeap,
+ /*inHoldChanges*/ morkBool_kFalse)
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kAtomBodyMap;
+}
+
+/*public non-poly*/ void
+morkAtomBodyMap::CloseAtomBodyMap(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+#ifdef MORK_ENABLE_PROBE_MAPS
+ this->CloseProbeMap(ev);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ this->CloseMap(ev);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+#ifdef MORK_ENABLE_PROBE_MAPS
+
+ /*virtual*/ mork_test // hit(a,b) implies hash(a) == hash(b)
+ morkAtomBodyMap::MapTest(morkEnv* ev, const void* inMapKey,
+ const void* inAppKey) const
+ {
+ const morkBookAtom* key = *(const morkBookAtom**) inMapKey;
+ if ( key )
+ {
+ return ( key->EqualFormAndBody(ev, *(const morkBookAtom**) inAppKey) ) ?
+ morkTest_kHit : morkTest_kMiss;
+ }
+ else
+ return morkTest_kVoid;
+ }
+
+ /*virtual*/ mork_u4 // hit(a,b) implies hash(a) == hash(b)
+ morkAtomBodyMap::MapHash(morkEnv* ev, const void* inAppKey) const
+ {
+ const morkBookAtom* key = *(const morkBookAtom**) inAppKey;
+ if ( key )
+ return key->HashFormAndBody(ev);
+ else
+ return 0;
+ }
+
+ /*virtual*/ mork_u4
+ morkAtomBodyMap::ProbeMapHashMapKey(morkEnv* ev, const void* inMapKey) const
+ {
+ const morkBookAtom* key = *(const morkBookAtom**) inMapKey;
+ if ( key )
+ return key->HashFormAndBody(ev);
+ else
+ return 0;
+ }
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ // { ===== begin morkMap poly interface =====
+ /*virtual*/ mork_bool //
+ morkAtomBodyMap::Equal(morkEnv* ev, const void* inKeyA,
+ const void* inKeyB) const
+ {
+ return (*(const morkBookAtom**) inKeyA)->EqualFormAndBody(ev,
+ *(const morkBookAtom**) inKeyB);
+ }
+
+ /*virtual*/ mork_u4 //
+ morkAtomBodyMap::Hash(morkEnv* ev, const void* inKey) const
+ {
+ return (*(const morkBookAtom**) inKey)->HashFormAndBody(ev);
+ }
+ // } ===== end morkMap poly interface =====
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+
+mork_bool
+morkAtomBodyMap::AddAtom(morkEnv* ev, morkBookAtom* ioAtom)
+{
+ if ( ev->Good() )
+ {
+#ifdef MORK_ENABLE_PROBE_MAPS
+ this->MapAtPut(ev, &ioAtom, /*val*/ (void*) 0,
+ /*key*/ (void*) 0, /*val*/ (void*) 0);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ this->Put(ev, &ioAtom, /*val*/ (void*) 0,
+ /*key*/ (void*) 0, /*val*/ (void*) 0, (mork_change**) 0);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ }
+ return ev->Good();
+}
+
+morkBookAtom*
+morkAtomBodyMap::CutAtom(morkEnv* ev, const morkBookAtom* inAtom)
+{
+ morkBookAtom* oldKey = 0;
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ MORK_USED_1(inAtom);
+ morkProbeMap::ProbeMapCutError(ev);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ this->Cut(ev, &inAtom, &oldKey, /*val*/ (void*) 0,
+ (mork_change**) 0);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+ return oldKey;
+}
+
+morkBookAtom*
+morkAtomBodyMap::GetAtom(morkEnv* ev, const morkBookAtom* inAtom)
+{
+ morkBookAtom* key = 0; // old val in the map
+#ifdef MORK_ENABLE_PROBE_MAPS
+ this->MapAt(ev, &inAtom, &key, /*val*/ (void*) 0);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ this->Get(ev, &inAtom, &key, /*val*/ (void*) 0, (mork_change**) 0);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+ return key;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+morkAtomRowMap::~morkAtomRowMap()
+{
+}
+
+// I changed to sizeof(mork_ip) from sizeof(mork_aid) to fix a crash on
+// 64 bit machines. I am not sure it was the right way to fix the problem,
+// but it does stop the crash. Perhaps we should be using the
+// morkPointerMap instead?
+morkAtomRowMap::morkAtomRowMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap, mork_column inIndexColumn)
+ : morkIntMap(ev, inUsage, sizeof(mork_ip), ioHeap, ioSlotHeap,
+ /*inHoldChanges*/ morkBool_kFalse)
+, mAtomRowMap_IndexColumn( inIndexColumn )
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kAtomRowMap;
+}
+
+void morkAtomRowMap::AddRow(morkEnv* ev, morkRow* ioRow)
+// add ioRow only if it contains a cell in mAtomRowMap_IndexColumn.
+{
+ mork_aid aid = ioRow->GetCellAtomAid(ev, mAtomRowMap_IndexColumn);
+ if ( aid )
+ this->AddAid(ev, aid, ioRow);
+}
+
+void morkAtomRowMap::CutRow(morkEnv* ev, morkRow* ioRow)
+// cut ioRow only if it contains a cell in mAtomRowMap_IndexColumn.
+{
+ mork_aid aid = ioRow->GetCellAtomAid(ev, mAtomRowMap_IndexColumn);
+ if ( aid )
+ this->CutAid(ev, aid);
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkAtomMap.h b/components/mork/src/morkAtomMap.h
new file mode 100644
index 000000000..6ddecc5c2
--- /dev/null
+++ b/components/mork/src/morkAtomMap.h
@@ -0,0 +1,365 @@
+/* -*- 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/. */
+
+#ifndef _MORKATOMMAP_
+#define _MORKATOMMAP_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKPROBEMAP_
+#include "morkProbeMap.h"
+#endif
+
+#ifndef _MORKINTMAP_
+#include "morkIntMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kAtomAidMap /*i*/ 0x6141 /* ascii 'aA' */
+
+#define morkAtomAidMap_kStartSlotCount 23
+
+/*| morkAtomAidMap: keys of morkBookAtom organized by atom ID
+|*/
+#ifdef MORK_ENABLE_PROBE_MAPS
+class morkAtomAidMap : public morkProbeMap { // for mapping tokens to maps
+#else /*MORK_ENABLE_PROBE_MAPS*/
+class morkAtomAidMap : public morkMap { // for mapping tokens to maps
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseAtomAidMap() only if open
+ virtual ~morkAtomAidMap(); // assert that CloseAtomAidMap() executed earlier
+
+public: // morkMap construction & destruction
+ morkAtomAidMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap);
+ void CloseAtomAidMap(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsAtomAidMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kAtomAidMap; }
+// } ===== end morkNode methods =====
+
+public:
+#ifdef MORK_ENABLE_PROBE_MAPS
+ // { ===== begin morkProbeMap methods =====
+ virtual mork_test // hit(a,b) implies hash(a) == hash(b)
+ MapTest(morkEnv* ev, const void* inMapKey, const void* inAppKey) const override;
+
+ virtual mork_u4 // hit(a,b) implies hash(a) == hash(b)
+ MapHash(morkEnv* ev, const void* inAppKey) const override;
+
+ virtual mork_u4 ProbeMapHashMapKey(morkEnv* ev, const void* inMapKey) const override;
+
+ // virtual mork_bool ProbeMapIsKeyNil(morkEnv* ev, void* ioMapKey);
+
+ // virtual void ProbeMapClearKey(morkEnv* ev, // put 'nil' alls keys inside map
+ // void* ioMapKey, mork_count inKeyCount); // array of keys inside map
+
+ // virtual void ProbeMapPushIn(morkEnv* ev, // move (key,val) into the map
+ // const void* inAppKey, const void* inAppVal, // (key,val) outside map
+ // void* outMapKey, void* outMapVal); // (key,val) inside map
+
+ // virtual void ProbeMapPullOut(morkEnv* ev, // move (key,val) out from the map
+ // const void* inMapKey, const void* inMapVal, // (key,val) inside map
+ // void* outAppKey, void* outAppVal) const; // (key,val) outside map
+ // } ===== end morkProbeMap methods =====
+#else /*MORK_ENABLE_PROBE_MAPS*/
+// { ===== begin morkMap poly interface =====
+ virtual mork_bool // note: equal(a,b) implies hash(a) == hash(b)
+ Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const override;
+ // implemented using morkBookAtom::HashAid()
+
+ virtual mork_u4 // note: equal(a,b) implies hash(a) == hash(b)
+ Hash(morkEnv* ev, const void* inKey) const override;
+ // implemented using morkBookAtom::EqualAid()
+// } ===== end morkMap poly interface =====
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+public: // other map methods
+
+ mork_bool AddAtom(morkEnv* ev, morkBookAtom* ioAtom);
+ // AddAtom() returns ev->Good()
+
+ morkBookAtom* CutAtom(morkEnv* ev, const morkBookAtom* inAtom);
+ // CutAtom() returns the atom removed equal to inAtom, if there was one
+
+ morkBookAtom* GetAtom(morkEnv* ev, const morkBookAtom* inAtom);
+ // GetAtom() returns the atom equal to inAtom, or else nil
+
+ morkBookAtom* GetAid(morkEnv* ev, mork_aid inAid);
+ // GetAid() returns the atom equal to inAid, or else nil
+
+ // note the atoms are owned elsewhere, usuall by morkAtomSpace
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakAtomAidMap(morkAtomAidMap* me,
+ morkEnv* ev, morkAtomAidMap** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongAtomAidMap(morkAtomAidMap* me,
+ morkEnv* ev, morkAtomAidMap** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+class morkAtomAidMapIter: public morkProbeMapIter { // typesafe wrapper class
+#else /*MORK_ENABLE_PROBE_MAPS*/
+class morkAtomAidMapIter: public morkMapIter { // typesafe wrapper class
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+public:
+#ifdef MORK_ENABLE_PROBE_MAPS
+ morkAtomAidMapIter(morkEnv* ev, morkAtomAidMap* ioMap)
+ : morkProbeMapIter(ev, ioMap) { }
+
+ morkAtomAidMapIter( ) : morkProbeMapIter() { }
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ morkAtomAidMapIter(morkEnv* ev, morkAtomAidMap* ioMap)
+ : morkMapIter(ev, ioMap) { }
+
+ morkAtomAidMapIter( ) : morkMapIter() { }
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+ void InitAtomAidMapIter(morkEnv* ev, morkAtomAidMap* ioMap)
+ { this->InitMapIter(ev, ioMap); }
+
+ mork_change* FirstAtom(morkEnv* ev, morkBookAtom** outAtomPtr)
+ { return this->First(ev, outAtomPtr, /*val*/ (void*) 0); }
+
+ mork_change* NextAtom(morkEnv* ev, morkBookAtom** outAtomPtr)
+ { return this->Next(ev, outAtomPtr, /*val*/ (void*) 0); }
+
+ mork_change* HereAtom(morkEnv* ev, morkBookAtom** outAtomPtr)
+ { return this->Here(ev, outAtomPtr, /*val*/ (void*) 0); }
+
+ mork_change* CutHereAtom(morkEnv* ev, morkBookAtom** outAtomPtr)
+ { return this->CutHere(ev, outAtomPtr, /*val*/ (void*) 0); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kAtomBodyMap /*i*/ 0x6142 /* ascii 'aB' */
+
+#define morkAtomBodyMap_kStartSlotCount 23
+
+/*| morkAtomBodyMap: keys of morkBookAtom organized by body bytes
+|*/
+#ifdef MORK_ENABLE_PROBE_MAPS
+class morkAtomBodyMap : public morkProbeMap { // for mapping tokens to maps
+#else /*MORK_ENABLE_PROBE_MAPS*/
+class morkAtomBodyMap : public morkMap { // for mapping tokens to maps
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseAtomBodyMap() only if open
+ virtual ~morkAtomBodyMap(); // assert CloseAtomBodyMap() executed earlier
+
+public: // morkMap construction & destruction
+ morkAtomBodyMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap);
+ void CloseAtomBodyMap(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsAtomBodyMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kAtomBodyMap; }
+// } ===== end morkNode methods =====
+
+public:
+#ifdef MORK_ENABLE_PROBE_MAPS
+ // { ===== begin morkProbeMap methods =====
+ virtual mork_test // hit(a,b) implies hash(a) == hash(b)
+ MapTest(morkEnv* ev, const void* inMapKey, const void* inAppKey) const override;
+
+ virtual mork_u4 // hit(a,b) implies hash(a) == hash(b)
+ MapHash(morkEnv* ev, const void* inAppKey) const override;
+
+ virtual mork_u4 ProbeMapHashMapKey(morkEnv* ev, const void* inMapKey) const override;
+
+ // virtual mork_bool ProbeMapIsKeyNil(morkEnv* ev, void* ioMapKey);
+
+ // virtual void ProbeMapClearKey(morkEnv* ev, // put 'nil' alls keys inside map
+ // void* ioMapKey, mork_count inKeyCount); // array of keys inside map
+
+ // virtual void ProbeMapPushIn(morkEnv* ev, // move (key,val) into the map
+ // const void* inAppKey, const void* inAppVal, // (key,val) outside map
+ // void* outMapKey, void* outMapVal); // (key,val) inside map
+
+ // virtual void ProbeMapPullOut(morkEnv* ev, // move (key,val) out from the map
+ // const void* inMapKey, const void* inMapVal, // (key,val) inside map
+ // void* outAppKey, void* outAppVal) const; // (key,val) outside map
+ // } ===== end morkProbeMap methods =====
+#else /*MORK_ENABLE_PROBE_MAPS*/
+// { ===== begin morkMap poly interface =====
+ virtual mork_bool // note: equal(a,b) implies hash(a) == hash(b)
+ Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const override;
+ // implemented using morkBookAtom::EqualFormAndBody()
+
+ virtual mork_u4 // note: equal(a,b) implies hash(a) == hash(b)
+ Hash(morkEnv* ev, const void* inKey) const override;
+ // implemented using morkBookAtom::HashFormAndBody()
+// } ===== end morkMap poly interface =====
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+public: // other map methods
+
+ mork_bool AddAtom(morkEnv* ev, morkBookAtom* ioAtom);
+ // AddAtom() returns ev->Good()
+
+ morkBookAtom* CutAtom(morkEnv* ev, const morkBookAtom* inAtom);
+ // CutAtom() returns the atom removed equal to inAtom, if there was one
+
+ morkBookAtom* GetAtom(morkEnv* ev, const morkBookAtom* inAtom);
+ // GetAtom() returns the atom equal to inAtom, or else nil
+
+ // note the atoms are owned elsewhere, usuall by morkAtomSpace
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakAtomBodyMap(morkAtomBodyMap* me,
+ morkEnv* ev, morkAtomBodyMap** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongAtomBodyMap(morkAtomBodyMap* me,
+ morkEnv* ev, morkAtomBodyMap** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+class morkAtomBodyMapIter: public morkProbeMapIter{ // typesafe wrapper class
+#else /*MORK_ENABLE_PROBE_MAPS*/
+class morkAtomBodyMapIter: public morkMapIter{ // typesafe wrapper class
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+public:
+#ifdef MORK_ENABLE_PROBE_MAPS
+ morkAtomBodyMapIter(morkEnv* ev, morkAtomBodyMap* ioMap)
+ : morkProbeMapIter(ev, ioMap) { }
+
+ morkAtomBodyMapIter( ) : morkProbeMapIter() { }
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ morkAtomBodyMapIter(morkEnv* ev, morkAtomBodyMap* ioMap)
+ : morkMapIter(ev, ioMap) { }
+
+ morkAtomBodyMapIter( ) : morkMapIter() { }
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+ void InitAtomBodyMapIter(morkEnv* ev, morkAtomBodyMap* ioMap)
+ { this->InitMapIter(ev, ioMap); }
+
+ mork_change* FirstAtom(morkEnv* ev, morkBookAtom** outAtomPtr)
+ { return this->First(ev, outAtomPtr, /*val*/ (void*) 0); }
+
+ mork_change* NextAtom(morkEnv* ev, morkBookAtom** outAtomPtr)
+ { return this->Next(ev, outAtomPtr, /*val*/ (void*) 0); }
+
+ mork_change* HereAtom(morkEnv* ev, morkBookAtom** outAtomPtr)
+ { return this->Here(ev, outAtomPtr, /*val*/ (void*) 0); }
+
+ mork_change* CutHereAtom(morkEnv* ev, morkBookAtom** outAtomPtr)
+ { return this->CutHere(ev, outAtomPtr, /*val*/ (void*) 0); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kAtomRowMap /*i*/ 0x6152 /* ascii 'aR' */
+
+/*| morkAtomRowMap: maps morkAtom* -> morkRow*
+|*/
+class morkAtomRowMap : public morkIntMap { // for mapping atoms to rows
+
+public:
+ mork_column mAtomRowMap_IndexColumn; // row column being indexed
+
+public:
+
+ virtual ~morkAtomRowMap();
+ morkAtomRowMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap, mork_column inIndexColumn);
+
+public: // adding and cutting from morkRow instance candidate
+
+ void AddRow(morkEnv* ev, morkRow* ioRow);
+ // add ioRow only if it contains a cell in mAtomRowMap_IndexColumn.
+
+ void CutRow(morkEnv* ev, morkRow* ioRow);
+ // cut ioRow only if it contains a cell in mAtomRowMap_IndexColumn.
+
+public: // other map methods
+
+ mork_bool AddAid(morkEnv* ev, mork_aid inAid, morkRow* ioRow)
+ { return this->AddInt(ev, inAid, ioRow); }
+ // the AddAid() boolean return equals ev->Good().
+
+ mork_bool CutAid(morkEnv* ev, mork_aid inAid)
+ { return this->CutInt(ev, inAid); }
+ // The CutAid() boolean return indicates whether removal happened.
+
+ morkRow* GetAid(morkEnv* ev, mork_aid inAid)
+ { return (morkRow*) this->GetInt(ev, inAid); }
+ // Note the returned space does NOT have an increase in refcount for this.
+
+public: // dynamic type identification
+ mork_bool IsAtomRowMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kAtomRowMap; }
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakAtomRowMap(morkAtomRowMap* me,
+ morkEnv* ev, morkAtomRowMap** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongAtomRowMap(morkAtomRowMap* me,
+ morkEnv* ev, morkAtomRowMap** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+};
+
+class morkAtomRowMapIter: public morkMapIter{ // typesafe wrapper class
+
+public:
+ morkAtomRowMapIter(morkEnv* ev, morkAtomRowMap* ioMap)
+ : morkMapIter(ev, ioMap) { }
+
+ morkAtomRowMapIter( ) : morkMapIter() { }
+ void InitAtomRowMapIter(morkEnv* ev, morkAtomRowMap* ioMap)
+ { this->InitMapIter(ev, ioMap); }
+
+ mork_change*
+ FirstAtomAndRow(morkEnv* ev, morkAtom** outAtom, morkRow** outRow)
+ { return this->First(ev, outAtom, outRow); }
+
+ mork_change*
+ NextAtomAndRow(morkEnv* ev, morkAtom** outAtom, morkRow** outRow)
+ { return this->Next(ev, outAtom, outRow); }
+
+ mork_change*
+ HereAtomAndRow(morkEnv* ev, morkAtom** outAtom, morkRow** outRow)
+ { return this->Here(ev, outAtom, outRow); }
+
+ mork_change*
+ CutHereAtomAndRow(morkEnv* ev, morkAtom** outAtom, morkRow** outRow)
+ { return this->CutHere(ev, outAtom, outRow); }
+};
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKATOMMAP_ */
diff --git a/components/mork/src/morkAtomSpace.cpp b/components/mork/src/morkAtomSpace.cpp
new file mode 100644
index 000000000..e9c1c16ed
--- /dev/null
+++ b/components/mork/src/morkAtomSpace.cpp
@@ -0,0 +1,270 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKSPACE_
+#include "morkSpace.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKSPACE_
+#include "morkSpace.h"
+#endif
+
+#ifndef _MORKATOMSPACE_
+#include "morkAtomSpace.h"
+#endif
+
+#ifndef _MORKPOOL_
+#include "morkPool.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKATOM_
+#include "morkAtom.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkAtomSpace::CloseMorkNode(morkEnv* ev) // CloseAtomSpace() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseAtomSpace(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkAtomSpace::~morkAtomSpace() // assert CloseAtomSpace() executed earlier
+{
+ MORK_ASSERT(mAtomSpace_HighUnderId==0);
+ MORK_ASSERT(mAtomSpace_HighOverId==0);
+ MORK_ASSERT(this->IsShutNode());
+ MORK_ASSERT(mAtomSpace_AtomAids.IsShutNode());
+ MORK_ASSERT(mAtomSpace_AtomBodies.IsShutNode());
+}
+
+/*public non-poly*/
+morkAtomSpace::morkAtomSpace(morkEnv* ev, const morkUsage& inUsage,
+ mork_scope inScope, morkStore* ioStore,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+: morkSpace(ev, inUsage, inScope, ioStore, ioHeap, ioSlotHeap)
+, mAtomSpace_HighUnderId( morkAtomSpace_kMinUnderId )
+, mAtomSpace_HighOverId( morkAtomSpace_kMinOverId )
+, mAtomSpace_AtomAids(ev, morkUsage::kMember, (nsIMdbHeap*) 0, ioSlotHeap)
+, mAtomSpace_AtomBodies(ev, morkUsage::kMember, (nsIMdbHeap*) 0, ioSlotHeap)
+{
+ // the morkSpace base constructor handles any dirty propagation
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kAtomSpace;
+}
+
+/*public non-poly*/ void
+morkAtomSpace::CloseAtomSpace(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ mAtomSpace_AtomBodies.CloseMorkNode(ev);
+ morkStore* store = mSpace_Store;
+ if ( store )
+ this->CutAllAtoms(ev, &store->mStore_Pool);
+
+ mAtomSpace_AtomAids.CloseMorkNode(ev);
+ this->CloseSpace(ev);
+ mAtomSpace_HighUnderId = 0;
+ mAtomSpace_HighOverId = 0;
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkAtomSpace::NonAtomSpaceTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkAtomSpace");
+}
+
+mork_num
+morkAtomSpace::CutAllAtoms(morkEnv* ev, morkPool* ioPool)
+{
+#ifdef MORK_ENABLE_ZONE_ARENAS
+ MORK_USED_2(ev, ioPool);
+ return 0;
+#else /*MORK_ENABLE_ZONE_ARENAS*/
+ if ( this->IsAtomSpaceClean() )
+ this->MaybeDirtyStoreAndSpace();
+
+ mork_num outSlots = mAtomSpace_AtomAids.MapFill();
+ morkBookAtom* a = 0; // old key atom in the map
+
+ morkStore* store = mSpace_Store;
+ mork_change* c = 0;
+ morkAtomAidMapIter i(ev, &mAtomSpace_AtomAids);
+ for ( c = i.FirstAtom(ev, &a); c ; c = i.NextAtom(ev, &a) )
+ {
+ if ( a )
+ ioPool->ZapAtom(ev, a, &store->mStore_Zone);
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ // do not cut anything from the map
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ i.CutHereAtom(ev, /*key*/ (morkBookAtom**) 0);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ }
+
+ return outSlots;
+#endif /*MORK_ENABLE_ZONE_ARENAS*/
+}
+
+
+morkBookAtom*
+morkAtomSpace::MakeBookAtomCopyWithAid(morkEnv* ev,
+ const morkFarBookAtom& inAtom, mork_aid inAid)
+// Make copy of inAtom and put it in both maps, using specified ID.
+{
+ morkBookAtom* outAtom = 0;
+ morkStore* store = mSpace_Store;
+ if ( ev->Good() && store )
+ {
+ morkPool* pool = this->GetSpaceStorePool();
+ outAtom = pool->NewFarBookAtomCopy(ev, inAtom, &store->mStore_Zone);
+ if ( outAtom )
+ {
+ if ( store->mStore_CanDirty )
+ {
+ outAtom->SetAtomDirty();
+ if ( this->IsAtomSpaceClean() )
+ this->MaybeDirtyStoreAndSpace();
+ }
+
+ outAtom->mBookAtom_Id = inAid;
+ outAtom->mBookAtom_Space = this;
+ mAtomSpace_AtomAids.AddAtom(ev, outAtom);
+ mAtomSpace_AtomBodies.AddAtom(ev, outAtom);
+ if ( this->SpaceScope() == morkAtomSpace_kColumnScope )
+ outAtom->MakeCellUseForever(ev);
+
+ if ( mAtomSpace_HighUnderId <= inAid )
+ mAtomSpace_HighUnderId = inAid + 1;
+ }
+ }
+ return outAtom;
+}
+
+morkBookAtom*
+morkAtomSpace::MakeBookAtomCopy(morkEnv* ev, const morkFarBookAtom& inAtom)
+// make copy of inAtom and put it in both maps, using a new ID as needed.
+{
+ morkBookAtom* outAtom = 0;
+ morkStore* store = mSpace_Store;
+ if ( ev->Good() && store )
+ {
+ if ( store->mStore_CanAutoAssignAtomIdentity )
+ {
+ morkPool* pool = this->GetSpaceStorePool();
+ morkBookAtom* atom = pool->NewFarBookAtomCopy(ev, inAtom, &mSpace_Store->mStore_Zone);
+ if ( atom )
+ {
+ mork_aid id = this->MakeNewAtomId(ev, atom);
+ if ( id )
+ {
+ if ( store->mStore_CanDirty )
+ {
+ atom->SetAtomDirty();
+ if ( this->IsAtomSpaceClean() )
+ this->MaybeDirtyStoreAndSpace();
+ }
+
+ outAtom = atom;
+ atom->mBookAtom_Space = this;
+ mAtomSpace_AtomAids.AddAtom(ev, atom);
+ mAtomSpace_AtomBodies.AddAtom(ev, atom);
+ if ( this->SpaceScope() == morkAtomSpace_kColumnScope )
+ outAtom->MakeCellUseForever(ev);
+ }
+ else
+ pool->ZapAtom(ev, atom, &mSpace_Store->mStore_Zone);
+ }
+ }
+ else
+ mSpace_Store->CannotAutoAssignAtomIdentityError(ev);
+ }
+ return outAtom;
+}
+
+
+mork_aid
+morkAtomSpace::MakeNewAtomId(morkEnv* ev, morkBookAtom* ioAtom)
+{
+ mork_aid outAid = 0;
+ mork_tid id = mAtomSpace_HighUnderId;
+ mork_num count = 8; // try up to eight times
+
+ while ( !outAid && count ) // still trying to find an unused table ID?
+ {
+ --count;
+ ioAtom->mBookAtom_Id = id;
+ if ( !mAtomSpace_AtomAids.GetAtom(ev, ioAtom) )
+ outAid = id;
+ else
+ {
+ MORK_ASSERT(morkBool_kFalse); // alert developer about ID problems
+ ++id;
+ }
+ }
+
+ mAtomSpace_HighUnderId = id + 1;
+ return outAid;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+morkAtomSpaceMap::~morkAtomSpaceMap()
+{
+}
+
+morkAtomSpaceMap::morkAtomSpaceMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+ : morkNodeMap(ev, inUsage, ioHeap, ioSlotHeap)
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kAtomSpaceMap;
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
diff --git a/components/mork/src/morkAtomSpace.h b/components/mork/src/morkAtomSpace.h
new file mode 100644
index 000000000..6048261c5
--- /dev/null
+++ b/components/mork/src/morkAtomSpace.h
@@ -0,0 +1,219 @@
+/* -*- 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/. */
+
+#ifndef _MORKATOMSPACE_
+#define _MORKATOMSPACE_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKSPACE_
+#include "morkSpace.h"
+#endif
+
+#ifndef _MORKATOMMAP_
+#include "morkAtomMap.h"
+#endif
+
+#ifndef _MORKNODEMAP_
+#include "morkNodeMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*| kMinUnderId: the smallest ID we auto-assign to the 'under' namespace
+**| reserved for tokens expected to occur very frequently, such as the names
+**| of columns. We reserve single byte ids in the ASCII range to correspond
+**| one-to-one to those tokens consisting single ASCII characters (so that
+**| this assignment is always known and constant). So we start at 0x80, and
+**| then reserve the upper half of two hex digit ids and all the three hex
+**| digit IDs for the 'under' namespace for common tokens.
+|*/
+#define morkAtomSpace_kMinUnderId 0x80 /* low 7 bits mean byte tokens */
+
+#define morkAtomSpace_kMaxSevenBitAid 0x7F /* low seven bit integer ID */
+
+/*| kMinOverId: the smallest ID we auto-assign to the 'over' namespace that
+**| might include very large numbers of tokens that are used infrequently,
+**| so that we care less whether the shortest hex representation is used.
+**| So we start all IDs for 'over' category tokens at a value range that
+**| needs at least four hex digits, so we can reserve three hex digits and
+**| shorter for more commonly occurring tokens in the 'under' category.
+|*/
+#define morkAtomSpace_kMinOverId 0x1000 /* using at least four hex bytes */
+
+#define morkDerived_kAtomSpace /*i*/ 0x6153 /* ascii 'aS' */
+
+#define morkAtomSpace_kColumnScope ((mork_scope) 'c') /* column scope is forever */
+
+/*| morkAtomSpace:
+|*/
+class morkAtomSpace : public morkSpace { //
+
+// public: // slots inherited from morkSpace (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // morkStore* mSpace_Store; // weak ref to containing store
+
+ // mork_bool mSpace_DoAutoIDs; // whether db should assign member IDs
+ // mork_bool mSpace_HaveDoneAutoIDs; // whether actually auto assigned IDs
+ // mork_u1 mSpace_Pad[ 2 ]; // pad to u4 alignment
+
+public: // state is public because the entire Mork system is private
+
+ mork_aid mAtomSpace_HighUnderId; // high ID in 'under' range
+ mork_aid mAtomSpace_HighOverId; // high ID in 'over' range
+
+ morkAtomAidMap mAtomSpace_AtomAids; // all atoms in space by ID
+ morkAtomBodyMap mAtomSpace_AtomBodies; // all atoms in space by body
+
+public: // more specific dirty methods for atom space:
+ void SetAtomSpaceDirty() { this->SetNodeDirty(); }
+ void SetAtomSpaceClean() { this->SetNodeClean(); }
+
+ mork_bool IsAtomSpaceClean() const { return this->IsNodeClean(); }
+ mork_bool IsAtomSpaceDirty() const { return this->IsNodeDirty(); }
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseAtomSpace() only if open
+ virtual ~morkAtomSpace(); // assert that CloseAtomSpace() executed earlier
+
+public: // morkMap construction & destruction
+ morkAtomSpace(morkEnv* ev, const morkUsage& inUsage, mork_scope inScope,
+ morkStore* ioStore, nsIMdbHeap* ioNodeHeap, nsIMdbHeap* ioSlotHeap);
+ void CloseAtomSpace(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsAtomSpace() const
+ { return IsNode() && mNode_Derived == morkDerived_kAtomSpace; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ void NonAtomSpaceTypeError(morkEnv* ev);
+
+public: // setup
+
+ mork_bool MarkAllAtomSpaceContentDirty(morkEnv* ev);
+ // MarkAllAtomSpaceContentDirty() visits every space object and marks
+ // them dirty, including every table, row, cell, and atom. The return
+ // equals ev->Good(), to show whether any error happened. This method is
+ // intended for use in the beginning of a "compress commit" which writes
+ // all store content, whether dirty or not. We dirty everything first so
+ // that later iterations over content can mark things clean as they are
+ // written, and organize the process of serialization so that objects are
+ // written only at need (because of being dirty).
+
+public: // other space methods
+
+ // void ReserveColumnAidCount(mork_count inCount)
+ // {
+ // mAtomSpace_HighUnderId = morkAtomSpace_kMinUnderId + inCount;
+ // mAtomSpace_HighOverId = morkAtomSpace_kMinOverId + inCount;
+ // }
+
+ mork_num CutAllAtoms(morkEnv* ev, morkPool* ioPool);
+ // CutAllAtoms() puts all the atoms back in the pool.
+
+ morkBookAtom* MakeBookAtomCopyWithAid(morkEnv* ev,
+ const morkFarBookAtom& inAtom, mork_aid inAid);
+ // Make copy of inAtom and put it in both maps, using specified ID.
+
+ morkBookAtom* MakeBookAtomCopy(morkEnv* ev, const morkFarBookAtom& inAtom);
+ // Make copy of inAtom and put it in both maps, using a new ID as needed.
+
+ mork_aid MakeNewAtomId(morkEnv* ev, morkBookAtom* ioAtom);
+ // generate an unused atom id.
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakAtomSpace(morkAtomSpace* me,
+ morkEnv* ev, morkAtomSpace** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongAtomSpace(morkAtomSpace* me,
+ morkEnv* ev, morkAtomSpace** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kAtomSpaceMap /*i*/ 0x615A /* ascii 'aZ' */
+
+/*| morkAtomSpaceMap: maps mork_scope -> morkAtomSpace
+|*/
+class morkAtomSpaceMap : public morkNodeMap { // for mapping tokens to tables
+
+public:
+
+ virtual ~morkAtomSpaceMap();
+ morkAtomSpaceMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap);
+
+public: // other map methods
+
+ mork_bool AddAtomSpace(morkEnv* ev, morkAtomSpace* ioAtomSpace)
+ { return this->AddNode(ev, ioAtomSpace->SpaceScope(), ioAtomSpace); }
+ // the AddAtomSpace() boolean return equals ev->Good().
+
+ mork_bool CutAtomSpace(morkEnv* ev, mork_scope inScope)
+ { return this->CutNode(ev, inScope); }
+ // The CutAtomSpace() boolean return indicates whether removal happened.
+
+ morkAtomSpace* GetAtomSpace(morkEnv* ev, mork_scope inScope)
+ { return (morkAtomSpace*) this->GetNode(ev, inScope); }
+ // Note the returned space does NOT have an increase in refcount for this.
+
+ mork_num CutAllAtomSpaces(morkEnv* ev)
+ { return this->CutAllNodes(ev); }
+ // CutAllAtomSpaces() releases all the referenced table values.
+};
+
+class morkAtomSpaceMapIter: public morkMapIter{ // typesafe wrapper class
+
+public:
+ morkAtomSpaceMapIter(morkEnv* ev, morkAtomSpaceMap* ioMap)
+ : morkMapIter(ev, ioMap) { }
+
+ morkAtomSpaceMapIter( ) : morkMapIter() { }
+ void InitAtomSpaceMapIter(morkEnv* ev, morkAtomSpaceMap* ioMap)
+ { this->InitMapIter(ev, ioMap); }
+
+ mork_change*
+ FirstAtomSpace(morkEnv* ev, mork_scope* outScope, morkAtomSpace** outAtomSpace)
+ { return this->First(ev, outScope, outAtomSpace); }
+
+ mork_change*
+ NextAtomSpace(morkEnv* ev, mork_scope* outScope, morkAtomSpace** outAtomSpace)
+ { return this->Next(ev, outScope, outAtomSpace); }
+
+ mork_change*
+ HereAtomSpace(morkEnv* ev, mork_scope* outScope, morkAtomSpace** outAtomSpace)
+ { return this->Here(ev, outScope, outAtomSpace); }
+
+ mork_change*
+ CutHereAtomSpace(morkEnv* ev, mork_scope* outScope, morkAtomSpace** outAtomSpace)
+ { return this->CutHere(ev, outScope, outAtomSpace); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKATOMSPACE_ */
+
diff --git a/components/mork/src/morkBead.cpp b/components/mork/src/morkBead.cpp
new file mode 100644
index 000000000..2b552eff1
--- /dev/null
+++ b/components/mork/src/morkBead.cpp
@@ -0,0 +1,425 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKBEAD_
+#include "morkBead.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkBead::CloseMorkNode(morkEnv* ev) // CloseBead() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseBead(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkBead::~morkBead() // assert CloseBead() executed earlier
+{
+ MORK_ASSERT(mBead_Color==0 || mNode_Usage == morkUsage_kStack );
+}
+
+/*public non-poly*/
+morkBead::morkBead(mork_color inBeadColor)
+: morkNode( morkUsage_kStack )
+, mBead_Color( inBeadColor )
+{
+}
+
+/*public non-poly*/
+morkBead::morkBead(const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ mork_color inBeadColor)
+: morkNode( inUsage, ioHeap )
+, mBead_Color( inBeadColor )
+{
+}
+
+/*public non-poly*/
+morkBead::morkBead(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap, mork_color inBeadColor)
+: morkNode(ev, inUsage, ioHeap)
+, mBead_Color( inBeadColor )
+{
+ if ( ev->Good() )
+ {
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kBead;
+ }
+}
+
+/*public non-poly*/ void
+morkBead::CloseBead(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ if ( !this->IsShutNode() )
+ {
+ mBead_Color = 0;
+ this->MarkShut();
+ }
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkBeadMap::CloseMorkNode(morkEnv* ev) // CloseBeadMap() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseBeadMap(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkBeadMap::~morkBeadMap() // assert CloseBeadMap() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkBeadMap::morkBeadMap(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+: morkMap(ev, inUsage, ioHeap, sizeof(morkBead*), /*inValSize*/ 0,
+ /*slotCount*/ 11, ioSlotHeap, /*holdChanges*/ morkBool_kFalse)
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kBeadMap;
+}
+
+/*public non-poly*/ void
+morkBeadMap::CloseBeadMap(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ this->CutAllBeads(ev);
+ this->CloseMap(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+mork_bool
+morkBeadMap::AddBead(morkEnv* ev, morkBead* ioBead)
+ // the AddBead() boolean return equals ev->Good().
+{
+ if ( ioBead && ev->Good() )
+ {
+ morkBead* oldBead = 0; // old key in the map
+
+ mork_bool put = this->Put(ev, &ioBead, /*val*/ (void*) 0,
+ /*key*/ &oldBead, /*val*/ (void*) 0, (mork_change**) 0);
+
+ if ( put ) // replaced an existing key?
+ {
+ if ( oldBead != ioBead ) // new bead was not already in table?
+ ioBead->AddStrongRef(ev); // now there's another ref
+
+ if ( oldBead && oldBead != ioBead ) // need to release old node?
+ oldBead->CutStrongRef(ev);
+ }
+ else
+ ioBead->AddStrongRef(ev); // another ref if not already in table
+ }
+ else if ( !ioBead )
+ ev->NilPointerError();
+
+ return ev->Good();
+}
+
+mork_bool
+morkBeadMap::CutBead(morkEnv* ev, mork_color inColor)
+{
+ morkBead* oldBead = 0; // old key in the map
+ morkBead bead(inColor);
+ morkBead* key = &bead;
+
+ mork_bool outCutNode = this->Cut(ev, &key,
+ /*key*/ &oldBead, /*val*/ (void*) 0, (mork_change**) 0);
+
+ if ( oldBead )
+ oldBead->CutStrongRef(ev);
+
+ bead.CloseBead(ev);
+ return outCutNode;
+}
+
+morkBead*
+morkBeadMap::GetBead(morkEnv* ev, mork_color inColor)
+ // Note the returned bead does NOT have an increase in refcount for this.
+{
+ morkBead* oldBead = 0; // old key in the map
+ morkBead bead(inColor);
+ morkBead* key = &bead;
+
+ this->Get(ev, &key, /*key*/ &oldBead, /*val*/ (void*) 0, (mork_change**) 0);
+
+ bead.CloseBead(ev);
+ return oldBead;
+}
+
+mork_num
+morkBeadMap::CutAllBeads(morkEnv* ev)
+ // CutAllBeads() releases all the referenced beads.
+{
+ mork_num outSlots = mMap_Slots;
+
+ morkBeadMapIter i(ev, this);
+ morkBead* b = i.FirstBead(ev);
+
+ while ( b )
+ {
+ b->CutStrongRef(ev);
+ i.CutHereBead(ev);
+ b = i.NextBead(ev);
+ }
+
+ return outSlots;
+}
+
+
+// { ===== begin morkMap poly interface =====
+/*virtual*/ mork_bool
+morkBeadMap::Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const
+{
+ MORK_USED_1(ev);
+ return (*(const morkBead**) inKeyA)->BeadEqual(
+ *(const morkBead**) inKeyB);
+}
+
+/*virtual*/ mork_u4
+morkBeadMap::Hash(morkEnv* ev, const void* inKey) const
+{
+ MORK_USED_1(ev);
+ return (*(const morkBead**) inKey)->BeadHash();
+}
+// } ===== end morkMap poly interface =====
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+morkBead* morkBeadMapIter::FirstBead(morkEnv* ev)
+{
+ morkBead* bead = 0;
+ this->First(ev, &bead, /*val*/ (void*) 0);
+ return bead;
+}
+
+morkBead* morkBeadMapIter::NextBead(morkEnv* ev)
+{
+ morkBead* bead = 0;
+ this->Next(ev, &bead, /*val*/ (void*) 0);
+ return bead;
+}
+
+morkBead* morkBeadMapIter::HereBead(morkEnv* ev)
+{
+ morkBead* bead = 0;
+ this->Here(ev, &bead, /*val*/ (void*) 0);
+ return bead;
+}
+
+void morkBeadMapIter::CutHereBead(morkEnv* ev)
+{
+ this->CutHere(ev, /*key*/ (void*) 0, /*val*/ (void*) 0);
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkBeadProbeMap::CloseMorkNode(morkEnv* ev) // CloseBeadProbeMap() if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseBeadProbeMap(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkBeadProbeMap::~morkBeadProbeMap() // assert CloseBeadProbeMap() earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+
+/*public non-poly*/
+morkBeadProbeMap::morkBeadProbeMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+: morkProbeMap(ev, inUsage, ioHeap,
+ /*inKeySize*/ sizeof(morkBead*), /*inValSize*/ 0,
+ ioSlotHeap, /*startSlotCount*/ 11,
+ /*inZeroIsClearKey*/ morkBool_kTrue)
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kBeadProbeMap;
+}
+
+/*public non-poly*/ void
+morkBeadProbeMap::CloseBeadProbeMap(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ this->CutAllBeads(ev);
+ this->CloseProbeMap(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*virtual*/ mork_test // hit(a,b) implies hash(a) == hash(b)
+morkBeadProbeMap::MapTest(morkEnv* ev, const void* inMapKey,
+ const void* inAppKey) const
+{
+ MORK_USED_1(ev);
+ const morkBead* key = *(const morkBead**) inMapKey;
+ if ( key )
+ {
+ mork_bool hit = key->BeadEqual(*(const morkBead**) inAppKey);
+ return ( hit ) ? morkTest_kHit : morkTest_kMiss;
+ }
+ else
+ return morkTest_kVoid;
+}
+
+/*virtual*/ mork_u4 // hit(a,b) implies hash(a) == hash(b)
+morkBeadProbeMap::MapHash(morkEnv* ev, const void* inAppKey) const
+{
+ const morkBead* key = *(const morkBead**) inAppKey;
+ if ( key )
+ return key->BeadHash();
+ else
+ {
+ ev->NilPointerWarning();
+ return 0;
+ }
+}
+
+/*virtual*/ mork_u4
+morkBeadProbeMap::ProbeMapHashMapKey(morkEnv* ev,
+ const void* inMapKey) const
+{
+ const morkBead* key = *(const morkBead**) inMapKey;
+ if ( key )
+ return key->BeadHash();
+ else
+ {
+ ev->NilPointerWarning();
+ return 0;
+ }
+}
+
+mork_bool
+morkBeadProbeMap::AddBead(morkEnv* ev, morkBead* ioBead)
+{
+ if ( ioBead && ev->Good() )
+ {
+ morkBead* bead = 0; // old key in the map
+
+ mork_bool put = this->MapAtPut(ev, &ioBead, /*val*/ (void*) 0,
+ /*key*/ &bead, /*val*/ (void*) 0);
+
+ if ( put ) // replaced an existing key?
+ {
+ if ( bead != ioBead ) // new bead was not already in table?
+ ioBead->AddStrongRef(ev); // now there's another ref
+
+ if ( bead && bead != ioBead ) // need to release old node?
+ bead->CutStrongRef(ev);
+ }
+ else
+ ioBead->AddStrongRef(ev); // now there's another ref
+ }
+ else if ( !ioBead )
+ ev->NilPointerError();
+
+ return ev->Good();
+}
+
+morkBead*
+morkBeadProbeMap::GetBead(morkEnv* ev, mork_color inColor)
+{
+ morkBead* oldBead = 0; // old key in the map
+ morkBead bead(inColor);
+ morkBead* key = &bead;
+
+ this->MapAt(ev, &key, &oldBead, /*val*/ (void*) 0);
+
+ bead.CloseBead(ev);
+ return oldBead;
+}
+
+mork_num
+morkBeadProbeMap::CutAllBeads(morkEnv* ev)
+ // CutAllBeads() releases all the referenced bead values.
+{
+ mork_num outSlots = sMap_Slots;
+
+ morkBeadProbeMapIter i(ev, this);
+ morkBead* b = i.FirstBead(ev);
+
+ while ( b )
+ {
+ b->CutStrongRef(ev);
+ b = i.NextBead(ev);
+ }
+ this->MapCutAll(ev);
+
+ return outSlots;
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
diff --git a/components/mork/src/morkBead.h b/components/mork/src/morkBead.h
new file mode 100644
index 000000000..df86c40ad
--- /dev/null
+++ b/components/mork/src/morkBead.h
@@ -0,0 +1,245 @@
+/* -*- 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/. */
+
+#ifndef _MORKBEAD_
+#define _MORKBEAD_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKPROBEMAP_
+#include "morkProbeMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kBead /*i*/ 0x426F /* ascii 'Bo' */
+
+/*| morkBead: subclass of morkNode that adds knowledge of db suite factory
+**| and containing port to those objects that are exposed as instances of
+**| nsIMdbBead in the public interface.
+|*/
+class morkBead : public morkNode {
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+public: // state is public because the entire Mork system is private
+
+ mork_color mBead_Color; // ID for this bead
+
+public: // Hash() and Equal() for bead maps are same for all subclasses:
+
+ mork_u4 BeadHash() const { return (mork_u4) mBead_Color; }
+ mork_bool BeadEqual(const morkBead* inBead) const
+ { return ( mBead_Color == inBead->mBead_Color); }
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseBead() only if open
+ virtual ~morkBead(); // assert that CloseBead() executed earlier
+
+public: // special case for stack construction for map usage:
+ explicit morkBead(mork_color inBeadColor); // stack-based bead instance
+
+protected: // special case for morkObject:
+ morkBead(const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ mork_color inBeadColor);
+
+public: // morkEnv construction & destruction
+ morkBead(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ mork_color inBeadColor);
+ void CloseBead(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkBead(const morkBead& other);
+ morkBead& operator=(const morkBead& other);
+
+public: // dynamic type identification
+ mork_bool IsBead() const
+ { return IsNode() && mNode_Derived == morkDerived_kBead; }
+// } ===== end morkNode methods =====
+
+ // void NewNilHandleError(morkEnv* ev); // mBead_Handle is nil
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakBead(morkBead* me,
+ morkEnv* ev, morkBead** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongBead(morkBead* me,
+ morkEnv* ev, morkBead** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kBeadMap /*i*/ 0x744D /* ascii 'bM' */
+
+/*| morkBeadMap: maps bead -> bead (key only using mBead_Color)
+|*/
+class morkBeadMap : public morkMap {
+
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseBeadMap() only if open
+ virtual ~morkBeadMap(); // assert that CloseBeadMap() executed earlier
+
+public: // morkMap construction & destruction
+ morkBeadMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap);
+ void CloseBeadMap(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsBeadMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kBeadMap; }
+// } ===== end morkNode methods =====
+
+// { ===== begin morkMap poly interface =====
+public:
+ virtual mork_bool // *((mork_u4*) inKeyA) == *((mork_u4*) inKeyB)
+ Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const override;
+
+ virtual mork_u4 // some integer function of *((mork_u4*) inKey)
+ Hash(morkEnv* ev, const void* inKey) const override;
+// } ===== end morkMap poly interface =====
+
+public: // other map methods
+
+ mork_bool AddBead(morkEnv* ev, morkBead* ioBead);
+ // the AddBead() boolean return equals ev->Good().
+
+ mork_bool CutBead(morkEnv* ev, mork_color inColor);
+ // The CutBead() boolean return indicates whether removal happened.
+
+ morkBead* GetBead(morkEnv* ev, mork_color inColor);
+ // Note the returned bead does NOT have an increase in refcount for this.
+
+ mork_num CutAllBeads(morkEnv* ev);
+ // CutAllBeads() releases all the referenced beads.
+};
+
+class morkBeadMapIter: public morkMapIter{ // typesafe wrapper class
+
+public:
+ morkBeadMapIter(morkEnv* ev, morkBeadMap* ioMap)
+ : morkMapIter(ev, ioMap) { }
+
+ morkBeadMapIter( ) : morkMapIter() { }
+ void InitBeadMapIter(morkEnv* ev, morkBeadMap* ioMap)
+ { this->InitMapIter(ev, ioMap); }
+
+ morkBead* FirstBead(morkEnv* ev);
+ morkBead* NextBead(morkEnv* ev);
+ morkBead* HereBead(morkEnv* ev);
+ void CutHereBead(morkEnv* ev);
+
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kBeadProbeMap /*i*/ 0x6D74 /* ascii 'mb' */
+
+/*| morkBeadProbeMap: maps bead -> bead (key only using mBead_Color)
+|*/
+class morkBeadProbeMap : public morkProbeMap {
+
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseBeadProbeMap() only if open
+ virtual ~morkBeadProbeMap(); // assert that CloseBeadProbeMap() executed earlier
+
+public: // morkMap construction & destruction
+ morkBeadProbeMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap);
+ void CloseBeadProbeMap(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsBeadProbeMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kBeadProbeMap; }
+// } ===== end morkNode methods =====
+
+ // { ===== begin morkProbeMap methods =====
+public:
+ virtual mork_test // hit(a,b) implies hash(a) == hash(b)
+ MapTest(morkEnv* ev, const void* inMapKey, const void* inAppKey) const override;
+
+ virtual mork_u4 // hit(a,b) implies hash(a) == hash(b)
+ MapHash(morkEnv* ev, const void* inAppKey) const override;
+
+ virtual mork_u4 ProbeMapHashMapKey(morkEnv* ev, const void* inMapKey) const override;
+
+ // virtual mork_bool ProbeMapIsKeyNil(morkEnv* ev, void* ioMapKey);
+
+ // virtual void ProbeMapClearKey(morkEnv* ev, // put 'nil' alls keys inside map
+ // void* ioMapKey, mork_count inKeyCount); // array of keys inside map
+
+ // virtual void ProbeMapPushIn(morkEnv* ev, // move (key,val) into the map
+ // const void* inAppKey, const void* inAppVal, // (key,val) outside map
+ // void* outMapKey, void* outMapVal); // (key,val) inside map
+
+ // virtual void ProbeMapPullOut(morkEnv* ev, // move (key,val) out from the map
+ // const void* inMapKey, const void* inMapVal, // (key,val) inside map
+ // void* outAppKey, void* outAppVal) const; // (key,val) outside map
+ // } ===== end morkProbeMap methods =====
+
+public: // other map methods
+
+ mork_bool AddBead(morkEnv* ev, morkBead* ioBead);
+ // the AddBead() boolean return equals ev->Good().
+
+ morkBead* GetBead(morkEnv* ev, mork_color inColor);
+ // Note the returned bead does NOT have an increase in refcount for this.
+
+ mork_num CutAllBeads(morkEnv* ev);
+ // CutAllBeads() releases all the referenced bead values.
+};
+
+class morkBeadProbeMapIter: public morkProbeMapIter { // typesafe wrapper class
+
+public:
+ morkBeadProbeMapIter(morkEnv* ev, morkBeadProbeMap* ioMap)
+ : morkProbeMapIter(ev, ioMap) { }
+
+ morkBeadProbeMapIter( ) : morkProbeMapIter() { }
+ void InitBeadProbeMapIter(morkEnv* ev, morkBeadProbeMap* ioMap)
+ { this->InitProbeMapIter(ev, ioMap); }
+
+ morkBead* FirstBead(morkEnv* ev)
+ { return (morkBead*) this->IterFirstKey(ev); }
+
+ morkBead* NextBead(morkEnv* ev)
+ { return (morkBead*) this->IterNextKey(ev); }
+
+ morkBead* HereBead(morkEnv* ev)
+ { return (morkBead*) this->IterHereKey(ev); }
+
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKBEAD_ */
diff --git a/components/mork/src/morkBlob.cpp b/components/mork/src/morkBlob.cpp
new file mode 100644
index 000000000..0d3f3cccd
--- /dev/null
+++ b/components/mork/src/morkBlob.cpp
@@ -0,0 +1,110 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKBLOB_
+#include "morkBlob.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*static*/ void
+morkBuf::NilBufBodyError(morkEnv* ev)
+{
+ ev->NewError("nil mBuf_Body");
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*static*/ void
+morkBlob::BlobFillOverSizeError(morkEnv* ev)
+{
+ ev->NewError("mBuf_Fill > mBlob_Size");
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+mork_bool
+morkBlob::GrowBlob(morkEnv* ev, nsIMdbHeap* ioHeap, mork_size inNewSize)
+{
+ if ( ioHeap )
+ {
+ if ( !mBuf_Body ) // no body? implies zero sized?
+ mBlob_Size = 0;
+
+ if ( mBuf_Fill > mBlob_Size ) // fill more than size?
+ {
+ ev->NewWarning("mBuf_Fill > mBlob_Size");
+ mBuf_Fill = mBlob_Size;
+ }
+
+ if ( inNewSize > mBlob_Size ) // need to allocate larger blob?
+ {
+ mork_u1* body = 0;
+ ioHeap->Alloc(ev->AsMdbEnv(), inNewSize, (void**) &body);
+ if ( body && ev->Good() )
+ {
+ void* oldBody = mBuf_Body;
+ if ( mBlob_Size ) // any old content to transfer?
+ MORK_MEMCPY(body, oldBody, mBlob_Size);
+
+ mBlob_Size = inNewSize; // install new size
+ mBuf_Body = body; // install new body
+
+ if ( oldBody ) // need to free old buffer body?
+ ioHeap->Free(ev->AsMdbEnv(), oldBody);
+ }
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ if ( ev->Good() && mBlob_Size < inNewSize )
+ ev->NewError("mBlob_Size < inNewSize");
+
+ return ev->Good();
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+morkCoil::morkCoil(morkEnv* ev, nsIMdbHeap* ioHeap)
+{
+ mBuf_Body = 0;
+ mBuf_Fill = 0;
+ mBlob_Size = 0;
+ mText_Form = 0;
+ mCoil_Heap = ioHeap;
+ if ( !ioHeap )
+ ev->NilPointerError();
+}
+
+void
+morkCoil::CloseCoil(morkEnv* ev)
+{
+ void* body = mBuf_Body;
+ nsIMdbHeap* heap = mCoil_Heap;
+
+ mBuf_Body = 0;
+ mCoil_Heap = 0;
+
+ if ( body && heap )
+ {
+ heap->Free(ev->AsMdbEnv(), body);
+ }
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkBlob.h b/components/mork/src/morkBlob.h
new file mode 100644
index 000000000..5d07098e1
--- /dev/null
+++ b/components/mork/src/morkBlob.h
@@ -0,0 +1,141 @@
+/* -*- 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/. */
+
+#ifndef _MORKBLOB_
+#define _MORKBLOB_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*| Buf: the minimum needed to describe location and content length.
+**| This is typically only enough to read from this buffer, since
+**| one cannot write effectively without knowing the size of a buf.
+|*/
+class morkBuf { // subset of nsIMdbYarn slots
+public:
+ void* mBuf_Body; // space for holding any binary content
+ mork_fill mBuf_Fill; // logical content in Buf in bytes
+
+public:
+ morkBuf() { }
+ morkBuf(const void* ioBuf, mork_fill inFill)
+ : mBuf_Body((void*) ioBuf), mBuf_Fill(inFill) { }
+
+ void ClearBufFill() { mBuf_Fill = 0; }
+
+ static void NilBufBodyError(morkEnv* ev);
+
+private: // copying is not allowed
+ morkBuf(const morkBuf& other);
+ morkBuf& operator=(const morkBuf& other);
+};
+
+/*| Blob: a buffer with an associated size, to increase known buf info
+**| to include max capacity in addition to buf location and content.
+**| This form factor allows us to allocate a vector of such blobs,
+**| which can share the same managing heap stored elsewhere, and that
+**| is why we don't include a pointer to a heap in this blob class.
+|*/
+class morkBlob : public morkBuf { // greater subset of nsIMdbYarn slots
+
+ // void* mBuf_Body; // space for holding any binary content
+ // mdb_fill mBuf_Fill; // logical content in Buf in bytes
+public:
+ mork_size mBlob_Size; // physical size of Buf in bytes
+
+public:
+ morkBlob() { }
+ morkBlob(const void* ioBuf, mork_fill inFill, mork_size inSize)
+ : morkBuf(ioBuf, inFill), mBlob_Size(inSize) { }
+
+ static void BlobFillOverSizeError(morkEnv* ev);
+
+public:
+ mork_bool GrowBlob(morkEnv* ev, nsIMdbHeap* ioHeap,
+ mork_size inNewSize);
+
+private: // copying is not allowed
+ morkBlob(const morkBlob& other);
+ morkBlob& operator=(const morkBlob& other);
+
+};
+
+/*| Text: a blob with an associated charset annotation, where the
+**| charset actually includes the general notion of typing, and not
+**| just a specification of character set alone; we want to permit
+**| arbitrary charset annotations for ad hoc binary types as well.
+**| (We avoid including a nsIMdbHeap pointer in morkText for the same
+**| reason morkBlob does: we want minimal size vectors of morkText.)
+|*/
+class morkText : public morkBlob { // greater subset of nsIMdbYarn slots
+
+ // void* mBuf_Body; // space for holding any binary content
+ // mdb_fill mBuf_Fill; // logical content in Buf in bytes
+ // mdb_size mBlob_Size; // physical size of Buf in bytes
+
+public:
+ mork_cscode mText_Form; // charset format encoding
+
+ morkText() { }
+
+private: // copying is not allowed
+ morkText(const morkText& other);
+ morkText& operator=(const morkText& other);
+};
+
+/*| Coil: a text with an associated nsIMdbHeap instance that provides
+**| all memory management for the space pointed to by mBuf_Body. (This
+**| was the hardest type to give a name in this small class hierarchy,
+**| because it's hard to characterize self-management of one's space.)
+**| A coil is a self-contained blob that knows how to grow itself as
+**| necessary to hold more content when necessary. Coil descends from
+**| morkText to include the mText_Form slot, even though this won't be
+**| needed always, because we are not as concerned about the overall
+**| size of this particular Coil object (if we were concerned about
+**| the size of an array of Coil instances, we would not bother with
+**| a separate heap pointer for each of them).
+**|
+**|| A coil makes a good medium in which to stream content as a sink,
+**| so we will have a subclass of morkSink called morkCoil that
+**| will stream bytes into this self-contained coil object. The name
+**| of this morkCoil class derives more from this intended usage than
+**| from anything else. The Mork code to parse db content will use
+**| coils with associated sinks to accumulate parsed strings.
+**|
+**|| Heap: this is the heap used for memory allocation. This instance
+**| is NOT refcounted, since this coil always assumes the heap is held
+**| through a reference elsewhere (for example, through the same object
+**| that contains or holds the coil itself. This lack of refcounting
+**| is consistent with the fact that morkCoil itself is not refcounted,
+**| and is not intended for use as a standalone object.
+|*/
+class morkCoil : public morkText { // self-managing text blob object
+
+ // void* mBuf_Body; // space for holding any binary content
+ // mdb_fill mBuf_Fill; // logical content in Buf in bytes
+ // mdb_size mBlob_Size; // physical size of Buf in bytes
+ // mdb_cscode mText_Form; // charset format encoding
+public:
+ nsIMdbHeap* mCoil_Heap; // storage manager for mBuf_Body pointer
+
+public:
+ morkCoil(morkEnv* ev, nsIMdbHeap* ioHeap);
+
+ void CloseCoil(morkEnv* ev);
+
+ mork_bool GrowCoil(morkEnv* ev, mork_size inNewSize)
+ { return this->GrowBlob(ev, mCoil_Heap, inNewSize); }
+
+private: // copying is not allowed
+ morkCoil(const morkCoil& other);
+ morkCoil& operator=(const morkCoil& other);
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKBLOB_ */
diff --git a/components/mork/src/morkBuilder.cpp b/components/mork/src/morkBuilder.cpp
new file mode 100644
index 000000000..27e5bd198
--- /dev/null
+++ b/components/mork/src/morkBuilder.cpp
@@ -0,0 +1,1031 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKPARSER_
+#include "morkParser.h"
+#endif
+
+#ifndef _MORKBUILDER_
+#include "morkBuilder.h"
+#endif
+
+#ifndef _MORKCELL_
+#include "morkCell.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKTABLE_
+#include "morkTable.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+#ifndef _MORKCELL_
+#include "morkCell.h"
+#endif
+
+#ifndef _MORKATOM_
+#include "morkAtom.h"
+#endif
+
+#ifndef _MORKATOMSPACE_
+#include "morkAtomSpace.h"
+#endif
+
+#ifndef _MORKROWSPACE_
+#include "morkRowSpace.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkBuilder::CloseMorkNode(morkEnv* ev) // CloseBuilder() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseBuilder(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkBuilder::~morkBuilder() // assert CloseBuilder() executed earlier
+{
+ MORK_ASSERT(mBuilder_Store==0);
+ MORK_ASSERT(mBuilder_Row==0);
+ MORK_ASSERT(mBuilder_Table==0);
+ MORK_ASSERT(mBuilder_Cell==0);
+ MORK_ASSERT(mBuilder_RowSpace==0);
+ MORK_ASSERT(mBuilder_AtomSpace==0);
+}
+
+/*public non-poly*/
+morkBuilder::morkBuilder(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ morkStream* ioStream, mdb_count inBytesPerParseSegment,
+ nsIMdbHeap* ioSlotHeap, morkStore* ioStore)
+
+: morkParser(ev, inUsage, ioHeap, ioStream,
+ inBytesPerParseSegment, ioSlotHeap)
+
+, mBuilder_Store( 0 )
+
+, mBuilder_Table( 0 )
+, mBuilder_Row( 0 )
+, mBuilder_Cell( 0 )
+
+, mBuilder_RowSpace( 0 )
+, mBuilder_AtomSpace( 0 )
+
+, mBuilder_OidAtomSpace( 0 )
+, mBuilder_ScopeAtomSpace( 0 )
+
+, mBuilder_PortForm( 0 )
+, mBuilder_PortRowScope( (mork_scope) 'r' )
+, mBuilder_PortAtomScope( (mork_scope) 'v' )
+
+, mBuilder_TableForm( 0 )
+, mBuilder_TableRowScope( (mork_scope) 'r' )
+, mBuilder_TableAtomScope( (mork_scope) 'v' )
+, mBuilder_TableKind( 0 )
+
+, mBuilder_TablePriority( morkPriority_kLo )
+, mBuilder_TableIsUnique( morkBool_kFalse )
+, mBuilder_TableIsVerbose( morkBool_kFalse )
+, mBuilder_TablePadByte( 0 )
+
+, mBuilder_RowForm( 0 )
+, mBuilder_RowRowScope( (mork_scope) 'r' )
+, mBuilder_RowAtomScope( (mork_scope) 'v' )
+
+, mBuilder_CellForm( 0 )
+, mBuilder_CellAtomScope( (mork_scope) 'v' )
+
+, mBuilder_DictForm( 0 )
+, mBuilder_DictAtomScope( (mork_scope) 'v' )
+
+, mBuilder_MetaTokenSlot( 0 )
+
+, mBuilder_DoCutRow( morkBool_kFalse )
+, mBuilder_DoCutCell( morkBool_kFalse )
+, mBuilder_CellsVecFill( 0 )
+{
+ if ( ev->Good() )
+ {
+ if ( ioStore )
+ {
+ morkStore::SlotWeakStore(ioStore, ev, &mBuilder_Store);
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kBuilder;
+ }
+ else
+ ev->NilPointerError();
+ }
+
+}
+
+/*public non-poly*/ void
+morkBuilder::CloseBuilder(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ mBuilder_Row = 0;
+ mBuilder_Cell = 0;
+ mBuilder_MetaTokenSlot = 0;
+
+ morkTable::SlotStrongTable((morkTable*) 0, ev, &mBuilder_Table);
+ morkStore::SlotWeakStore((morkStore*) 0, ev, &mBuilder_Store);
+
+ morkRowSpace::SlotStrongRowSpace((morkRowSpace*) 0, ev,
+ &mBuilder_RowSpace);
+
+ morkAtomSpace::SlotStrongAtomSpace((morkAtomSpace*) 0, ev,
+ &mBuilder_AtomSpace);
+
+ morkAtomSpace::SlotStrongAtomSpace((morkAtomSpace*) 0, ev,
+ &mBuilder_OidAtomSpace);
+
+ morkAtomSpace::SlotStrongAtomSpace((morkAtomSpace*) 0, ev,
+ &mBuilder_ScopeAtomSpace);
+ this->CloseParser(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkBuilder::NonBuilderTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkBuilder");
+}
+
+/*static*/ void
+morkBuilder::NilBuilderCellError(morkEnv* ev)
+{
+ ev->NewError("nil mBuilder_Cell");
+}
+
+/*static*/ void
+morkBuilder::NilBuilderRowError(morkEnv* ev)
+{
+ ev->NewError("nil mBuilder_Row");
+}
+
+/*static*/ void
+morkBuilder::NilBuilderTableError(morkEnv* ev)
+{
+ ev->NewError("nil mBuilder_Table");
+}
+
+/*static*/ void
+morkBuilder::NonColumnSpaceScopeError(morkEnv* ev)
+{
+ ev->NewError("column space != 'c'");
+}
+
+void
+morkBuilder::LogGlitch(morkEnv* ev, const morkGlitch& inGlitch,
+ const char* inKind)
+{
+ MORK_USED_2(inGlitch,inKind);
+ ev->NewWarning("parsing glitch");
+}
+
+/*virtual*/ void
+morkBuilder::MidToYarn(morkEnv* ev,
+ const morkMid& inMid, // typically an alias to concat with strings
+ mdbYarn* outYarn)
+// The parser might ask that some aliases be turned into yarns, so they
+// can be concatenated into longer blobs under some circumstances. This
+// is an alternative to using a long and complex callback for many parts
+// for a single cell value.
+{
+ mBuilder_Store->MidToYarn(ev, inMid, outYarn);
+}
+
+/*virtual*/ void
+morkBuilder::OnNewPort(morkEnv* ev, const morkPlace& inPlace)
+// mp:Start ::= OnNewPort mp:PortItem* OnPortEnd
+// mp:PortItem ::= mp:Content | mp:Group | OnPortGlitch
+// mp:Content ::= mp:PortRow | mp:Dict | mp:Table | mp:Row
+{
+ MORK_USED_2(ev,inPlace);
+ // mParser_InPort = morkBool_kTrue;
+ mBuilder_PortForm = 0;
+ mBuilder_PortRowScope = (mork_scope) 'r';
+ mBuilder_PortAtomScope = (mork_scope) 'v';
+}
+
+/*virtual*/ void
+morkBuilder::OnPortGlitch(morkEnv* ev, const morkGlitch& inGlitch)
+{
+ this->LogGlitch(ev, inGlitch, "port");
+}
+
+/*virtual*/ void
+morkBuilder::OnPortEnd(morkEnv* ev, const morkSpan& inSpan)
+// mp:Start ::= OnNewPort mp:PortItem* OnPortEnd
+{
+ MORK_USED_2(ev,inSpan);
+ // ev->StubMethodOnlyError();
+ // nothing to do?
+ // mParser_InPort = morkBool_kFalse;
+}
+
+/*virtual*/ void
+morkBuilder::OnNewGroup(morkEnv* ev, const morkPlace& inPlace, mork_gid inGid)
+{
+ MORK_USED_1(inPlace);
+ mParser_InGroup = morkBool_kTrue;
+ mork_pos startPos = inPlace.mPlace_Pos;
+
+ morkStore* store = mBuilder_Store;
+ if ( store )
+ {
+ if ( inGid >= store->mStore_CommitGroupIdentity )
+ store->mStore_CommitGroupIdentity = inGid + 1;
+
+ if ( !store->mStore_FirstCommitGroupPos )
+ store->mStore_FirstCommitGroupPos = startPos;
+ else if ( !store->mStore_SecondCommitGroupPos )
+ store->mStore_SecondCommitGroupPos = startPos;
+ }
+}
+
+/*virtual*/ void
+morkBuilder::OnGroupGlitch(morkEnv* ev, const morkGlitch& inGlitch)
+{
+ this->LogGlitch(ev, inGlitch, "group");
+}
+
+/*virtual*/ void
+morkBuilder::OnGroupCommitEnd(morkEnv* ev, const morkSpan& inSpan)
+{
+ MORK_USED_2(ev,inSpan);
+ // mParser_InGroup = morkBool_kFalse;
+ // ev->StubMethodOnlyError();
+}
+
+/*virtual*/ void
+morkBuilder::OnGroupAbortEnd(morkEnv* ev, const morkSpan& inSpan)
+{
+ MORK_USED_1(inSpan);
+ // mParser_InGroup = morkBool_kFalse;
+ ev->StubMethodOnlyError();
+}
+
+/*virtual*/ void
+morkBuilder::OnNewPortRow(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid& inMid, mork_change inChange)
+{
+ MORK_USED_3(inMid,inPlace,inChange);
+ // mParser_InPortRow = morkBool_kTrue;
+ ev->StubMethodOnlyError();
+}
+
+/*virtual*/ void
+morkBuilder::OnPortRowGlitch(morkEnv* ev, const morkGlitch& inGlitch)
+{
+ this->LogGlitch(ev, inGlitch, "port row");
+}
+
+/*virtual*/ void
+morkBuilder::OnPortRowEnd(morkEnv* ev, const morkSpan& inSpan)
+{
+ MORK_USED_1(inSpan);
+ // mParser_InPortRow = morkBool_kFalse;
+ ev->StubMethodOnlyError();
+}
+
+/*virtual*/ void
+morkBuilder::OnNewTable(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid& inMid, mork_bool inCutAllRows)
+// mp:Table ::= OnNewTable mp:TableItem* OnTableEnd
+// mp:TableItem ::= mp:Row | mp:MetaTable | OnTableGlitch
+// mp:MetaTable ::= OnNewMeta mp:MetaItem* mp:Row OnMetaEnd
+// mp:Meta ::= OnNewMeta mp:MetaItem* OnMetaEnd
+// mp:MetaItem ::= mp:Cell | OnMetaGlitch
+{
+ MORK_USED_1(inPlace);
+ // mParser_InTable = morkBool_kTrue;
+ mBuilder_TableForm = mBuilder_PortForm;
+ mBuilder_TableRowScope = mBuilder_PortRowScope;
+ mBuilder_TableAtomScope = mBuilder_PortAtomScope;
+ mBuilder_TableKind = morkStore_kNoneToken;
+
+ mBuilder_TablePriority = morkPriority_kLo;
+ mBuilder_TableIsUnique = morkBool_kFalse;
+ mBuilder_TableIsVerbose = morkBool_kFalse;
+
+ morkTable* table = mBuilder_Store->MidToTable(ev, inMid);
+ morkTable::SlotStrongTable(table, ev, &mBuilder_Table);
+ if ( table )
+ {
+ if ( table->mTable_RowSpace )
+ mBuilder_TableRowScope = table->mTable_RowSpace->SpaceScope();
+
+ if ( inCutAllRows )
+ table->CutAllRows(ev);
+ }
+}
+
+/*virtual*/ void
+morkBuilder::OnTableGlitch(morkEnv* ev, const morkGlitch& inGlitch)
+{
+ this->LogGlitch(ev, inGlitch, "table");
+}
+
+/*virtual*/ void
+morkBuilder::OnTableEnd(morkEnv* ev, const morkSpan& inSpan)
+// mp:Table ::= OnNewTable mp:TableItem* OnTableEnd
+{
+ MORK_USED_1(inSpan);
+ // mParser_InTable = morkBool_kFalse;
+ if ( mBuilder_Table )
+ {
+ mBuilder_Table->mTable_Priority = mBuilder_TablePriority;
+
+ if ( mBuilder_TableIsUnique )
+ mBuilder_Table->SetTableUnique();
+
+ if ( mBuilder_TableIsVerbose )
+ mBuilder_Table->SetTableVerbose();
+
+ morkTable::SlotStrongTable((morkTable*) 0, ev, &mBuilder_Table);
+ }
+ else
+ this->NilBuilderTableError(ev);
+
+ mBuilder_Row = 0;
+ mBuilder_Cell = 0;
+
+
+ mBuilder_TablePriority = morkPriority_kLo;
+ mBuilder_TableIsUnique = morkBool_kFalse;
+ mBuilder_TableIsVerbose = morkBool_kFalse;
+
+ if ( mBuilder_TableKind == morkStore_kNoneToken )
+ ev->NewError("missing table kind");
+
+ mBuilder_CellAtomScope = mBuilder_RowAtomScope =
+ mBuilder_TableAtomScope = mBuilder_PortAtomScope;
+
+ mBuilder_DoCutCell = morkBool_kFalse;
+ mBuilder_DoCutRow = morkBool_kFalse;
+}
+
+/*virtual*/ void
+morkBuilder::OnNewMeta(morkEnv* ev, const morkPlace& inPlace)
+// mp:Meta ::= OnNewMeta mp:MetaItem* OnMetaEnd
+// mp:MetaItem ::= mp:Cell | OnMetaGlitch
+// mp:Cell ::= OnMinusCell? OnNewCell mp:CellItem? OnCellEnd
+// mp:CellItem ::= mp:Slot | OnCellForm | OnCellGlitch
+// mp:Slot ::= OnValue | OnValueMid | OnRowMid | OnTableMid
+{
+ MORK_USED_2(ev,inPlace);
+ // mParser_InMeta = morkBool_kTrue;
+
+}
+
+/*virtual*/ void
+morkBuilder::OnMetaGlitch(morkEnv* ev, const morkGlitch& inGlitch)
+{
+ this->LogGlitch(ev, inGlitch, "meta");
+}
+
+/*virtual*/ void
+morkBuilder::OnMetaEnd(morkEnv* ev, const morkSpan& inSpan)
+// mp:Meta ::= OnNewMeta mp:MetaItem* OnMetaEnd
+{
+ MORK_USED_2(ev,inSpan);
+ // mParser_InMeta = morkBool_kFalse;
+}
+
+/*virtual*/ void
+morkBuilder::OnMinusRow(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ mBuilder_DoCutRow = morkBool_kTrue;
+}
+
+/*virtual*/ void
+morkBuilder::OnNewRow(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid& inMid, mork_bool inCutAllCols)
+// mp:Table ::= OnNewTable mp:TableItem* OnTableEnd
+// mp:TableItem ::= mp:Row | mp:MetaTable | OnTableGlitch
+// mp:MetaTable ::= OnNewMeta mp:MetaItem* mp:Row OnMetaEnd
+// mp:Row ::= OnMinusRow? OnNewRow mp:RowItem* OnRowEnd
+// mp:RowItem ::= mp:Cell | mp:Meta | OnRowGlitch
+// mp:Cell ::= OnMinusCell? OnNewCell mp:CellItem? OnCellEnd
+// mp:CellItem ::= mp:Slot | OnCellForm | OnCellGlitch
+// mp:Slot ::= OnValue | OnValueMid | OnRowMid | OnTableMid
+{
+ MORK_USED_1(inPlace);
+ // mParser_InRow = morkBool_kTrue;
+
+ mBuilder_CellForm = mBuilder_RowForm = mBuilder_TableForm;
+ mBuilder_CellAtomScope = mBuilder_RowAtomScope = mBuilder_TableAtomScope;
+ mBuilder_RowRowScope = mBuilder_TableRowScope;
+ morkStore* store = mBuilder_Store;
+
+ if ( !inMid.mMid_Buf && !inMid.mMid_Oid.mOid_Scope )
+ {
+ morkMid mid(inMid);
+ mid.mMid_Oid.mOid_Scope = mBuilder_RowRowScope;
+ mBuilder_Row = store->MidToRow(ev, mid);
+ }
+ else
+ {
+ mBuilder_Row = store->MidToRow(ev, inMid);
+ }
+ morkRow* row = mBuilder_Row;
+ if ( row && inCutAllCols )
+ {
+ row->CutAllColumns(ev);
+ }
+
+ morkTable* table = mBuilder_Table;
+ if ( table )
+ {
+ if ( row )
+ {
+ if ( mParser_InMeta )
+ {
+ morkRow* metaRow = table->mTable_MetaRow;
+ if ( !metaRow )
+ {
+ table->mTable_MetaRow = row;
+ table->mTable_MetaRowOid = row->mRow_Oid;
+ row->AddRowGcUse(ev);
+ }
+ else if ( metaRow != row ) // not identical?
+ ev->NewError("duplicate table meta row");
+ }
+ else
+ {
+ if ( mBuilder_DoCutRow )
+ table->CutRow(ev, row);
+ else
+ table->AddRow(ev, row);
+ }
+ }
+ }
+ // else // it is now okay to have rows outside a table:
+ // this->NilBuilderTableError(ev);
+
+ mBuilder_DoCutRow = morkBool_kFalse;
+}
+
+/*virtual*/ void
+morkBuilder::OnRowPos(morkEnv* ev, mork_pos inRowPos)
+{
+ if ( mBuilder_Row && mBuilder_Table && !mParser_InMeta )
+ {
+ mork_pos hintFromPos = 0; // best hint when we don't know position
+ mBuilder_Table->MoveRow(ev, mBuilder_Row, hintFromPos, inRowPos);
+ }
+}
+
+/*virtual*/ void
+morkBuilder::OnRowGlitch(morkEnv* ev, const morkGlitch& inGlitch)
+{
+ this->LogGlitch(ev, inGlitch, "row");
+}
+
+void
+morkBuilder::FlushBuilderCells(morkEnv* ev)
+{
+ if ( mBuilder_Row )
+ {
+ morkPool* pool = mBuilder_Store->StorePool();
+ morkCell* cells = mBuilder_CellsVec;
+ mork_fill fill = mBuilder_CellsVecFill;
+ mBuilder_Row->TakeCells(ev, cells, fill, mBuilder_Store);
+
+ morkCell* end = cells + fill;
+ --cells; // prepare for preincrement
+ while ( ++cells < end )
+ {
+ if ( cells->mCell_Atom )
+ cells->SetAtom(ev, (morkAtom*) 0, pool);
+ }
+ mBuilder_CellsVecFill = 0;
+ }
+ else
+ this->NilBuilderRowError(ev);
+}
+
+/*virtual*/ void
+morkBuilder::OnRowEnd(morkEnv* ev, const morkSpan& inSpan)
+// mp:Row ::= OnMinusRow? OnNewRow mp:RowItem* OnRowEnd
+{
+ MORK_USED_1(inSpan);
+ // mParser_InRow = morkBool_kFalse;
+ if ( mBuilder_Row )
+ {
+ this->FlushBuilderCells(ev);
+ }
+ else
+ this->NilBuilderRowError(ev);
+
+ mBuilder_Row = 0;
+ mBuilder_Cell = 0;
+
+ mBuilder_DoCutCell = morkBool_kFalse;
+ mBuilder_DoCutRow = morkBool_kFalse;
+}
+
+/*virtual*/ void
+morkBuilder::OnNewDict(morkEnv* ev, const morkPlace& inPlace)
+// mp:Dict ::= OnNewDict mp:DictItem* OnDictEnd
+// mp:DictItem ::= OnAlias | OnAliasGlitch | mp:Meta | OnDictGlitch
+{
+ MORK_USED_2(ev,inPlace);
+ // mParser_InDict = morkBool_kTrue;
+
+ mBuilder_CellForm = mBuilder_DictForm = mBuilder_PortForm;
+ mBuilder_CellAtomScope = mBuilder_DictAtomScope = mBuilder_PortAtomScope;
+}
+
+/*virtual*/ void
+morkBuilder::OnDictGlitch(morkEnv* ev, const morkGlitch& inGlitch)
+{
+ this->LogGlitch(ev, inGlitch, "dict");
+}
+
+/*virtual*/ void
+morkBuilder::OnDictEnd(morkEnv* ev, const morkSpan& inSpan)
+// mp:Dict ::= OnNewDict mp:DictItem* OnDictEnd
+{
+ MORK_USED_2(ev,inSpan);
+ // mParser_InDict = morkBool_kFalse;
+
+ mBuilder_DictForm = 0;
+ mBuilder_DictAtomScope = 0;
+}
+
+/*virtual*/ void
+morkBuilder::OnAlias(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid)
+{
+ MORK_USED_1(inSpan);
+ if ( mParser_InDict )
+ {
+ morkMid mid = inMid; // local copy for modification
+ mid.mMid_Oid.mOid_Scope = mBuilder_DictAtomScope;
+ mBuilder_Store->AddAlias(ev, mid, mBuilder_DictForm);
+ }
+ else
+ ev->NewError("alias not in dict");
+}
+
+/*virtual*/ void
+morkBuilder::OnAliasGlitch(morkEnv* ev, const morkGlitch& inGlitch)
+{
+ this->LogGlitch(ev, inGlitch, "alias");
+}
+
+
+morkCell*
+morkBuilder::AddBuilderCell(morkEnv* ev,
+ const morkMid& inMid, mork_change inChange)
+{
+ morkCell* outCell = 0;
+ mork_column column = inMid.mMid_Oid.mOid_Id;
+
+ if ( ev->Good() )
+ {
+ if ( mBuilder_CellsVecFill >= morkBuilder_kCellsVecSize )
+ this->FlushBuilderCells(ev);
+ if ( ev->Good() )
+ {
+ if ( mBuilder_CellsVecFill < morkBuilder_kCellsVecSize )
+ {
+ mork_fill indx = mBuilder_CellsVecFill++;
+ outCell = mBuilder_CellsVec + indx;
+ outCell->SetColumnAndChange(column, inChange);
+ outCell->mCell_Atom = 0;
+ }
+ else
+ ev->NewError("out of builder cells");
+ }
+ }
+ return outCell;
+}
+
+/*virtual*/ void
+morkBuilder::OnMinusCell(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ mBuilder_DoCutCell = morkBool_kTrue;
+}
+
+/*virtual*/ void
+morkBuilder::OnNewCell(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid* inMid, const morkBuf* inBuf)
+// Exactly one of inMid and inBuf is nil, and the other is non-nil.
+// When hex ID syntax is used for a column, then inMid is not nil, and
+// when a naked string names a column, then inBuf is not nil.
+
+ // mp:Cell ::= OnMinusCell? OnNewCell mp:CellItem? OnCellEnd
+ // mp:CellItem ::= mp:Slot | OnCellForm | OnCellGlitch
+ // mp:Slot ::= OnValue | OnValueMid | OnRowMid | OnTableMid
+{
+ MORK_USED_1(inPlace);
+ // mParser_InCell = morkBool_kTrue;
+
+ mork_change cellChange = ( mBuilder_DoCutCell )?
+ morkChange_kCut : morkChange_kAdd;
+
+ mBuilder_DoCutCell = morkBool_kFalse;
+
+ mBuilder_CellAtomScope = mBuilder_RowAtomScope;
+
+ mBuilder_Cell = 0; // nil until determined for a row
+ morkStore* store = mBuilder_Store;
+ mork_scope scope = morkStore_kColumnSpaceScope;
+ morkMid tempMid; // space for local and modifiable cell mid
+ morkMid* cellMid = &tempMid; // default to local if inMid==0
+
+ if ( inMid ) // mid parameter is actually provided?
+ {
+ *cellMid = *inMid; // bitwise copy for modifiable local mid
+
+ if ( !cellMid->mMid_Oid.mOid_Scope )
+ {
+ if ( cellMid->mMid_Buf )
+ {
+ scope = store->BufToToken(ev, cellMid->mMid_Buf);
+ cellMid->mMid_Buf = 0; // don't do scope lookup again
+ ev->NewWarning("column mids need column scope");
+ }
+ cellMid->mMid_Oid.mOid_Scope = scope;
+ }
+ }
+ else if ( inBuf ) // buf points to naked column string name?
+ {
+ cellMid->ClearMid();
+ cellMid->mMid_Oid.mOid_Id = store->BufToToken(ev, inBuf);
+ cellMid->mMid_Oid.mOid_Scope = scope; // kColumnSpaceScope
+ }
+ else
+ ev->NilPointerError(); // either inMid or inBuf must be non-nil
+
+ mork_column column = cellMid->mMid_Oid.mOid_Id;
+
+ if ( mBuilder_Row && ev->Good() ) // this cell must be inside a row
+ {
+ // mBuilder_Cell = this->AddBuilderCell(ev, *cellMid, cellChange);
+
+ if ( mBuilder_CellsVecFill >= morkBuilder_kCellsVecSize )
+ this->FlushBuilderCells(ev);
+ if ( ev->Good() )
+ {
+ if ( mBuilder_CellsVecFill < morkBuilder_kCellsVecSize )
+ {
+ mork_fill ix = mBuilder_CellsVecFill++;
+ morkCell* cell = mBuilder_CellsVec + ix;
+ cell->SetColumnAndChange(column, cellChange);
+
+ cell->mCell_Atom = 0;
+ mBuilder_Cell = cell;
+ }
+ else
+ ev->NewError("out of builder cells");
+ }
+ }
+
+ else if ( mParser_InMeta && ev->Good() ) // cell is in metainfo structure?
+ {
+ if ( scope == morkStore_kColumnSpaceScope )
+ {
+ if ( mParser_InTable ) // metainfo for table?
+ {
+ if ( column == morkStore_kKindColumn )
+ mBuilder_MetaTokenSlot = &mBuilder_TableKind;
+ else if ( column == morkStore_kStatusColumn )
+ mBuilder_MetaTokenSlot = &mBuilder_TableStatus;
+ else if ( column == morkStore_kRowScopeColumn )
+ mBuilder_MetaTokenSlot = &mBuilder_TableRowScope;
+ else if ( column == morkStore_kAtomScopeColumn )
+ mBuilder_MetaTokenSlot = &mBuilder_TableAtomScope;
+ else if ( column == morkStore_kFormColumn )
+ mBuilder_MetaTokenSlot = &mBuilder_TableForm;
+ }
+ else if ( mParser_InDict ) // metainfo for dict?
+ {
+ if ( column == morkStore_kAtomScopeColumn )
+ mBuilder_MetaTokenSlot = &mBuilder_DictAtomScope;
+ else if ( column == morkStore_kFormColumn )
+ mBuilder_MetaTokenSlot = &mBuilder_DictForm;
+ }
+ else if ( mParser_InRow ) // metainfo for row?
+ {
+ if ( column == morkStore_kAtomScopeColumn )
+ mBuilder_MetaTokenSlot = &mBuilder_RowAtomScope;
+ else if ( column == morkStore_kRowScopeColumn )
+ mBuilder_MetaTokenSlot = &mBuilder_RowRowScope;
+ else if ( column == morkStore_kFormColumn )
+ mBuilder_MetaTokenSlot = &mBuilder_RowForm;
+ }
+ }
+ else
+ ev->NewWarning("expected column scope");
+ }
+}
+
+/*virtual*/ void
+morkBuilder::OnCellGlitch(morkEnv* ev, const morkGlitch& inGlitch)
+{
+ this->LogGlitch(ev, inGlitch, "cell");
+}
+
+/*virtual*/ void
+morkBuilder::OnCellForm(morkEnv* ev, mork_cscode inCharsetFormat)
+{
+ morkCell* cell = mBuilder_Cell;
+ if ( cell )
+ {
+ mBuilder_CellForm = inCharsetFormat;
+ }
+ else
+ this->NilBuilderCellError(ev);
+}
+
+/*virtual*/ void
+morkBuilder::OnCellEnd(morkEnv* ev, const morkSpan& inSpan)
+// mp:Cell ::= OnMinusCell? OnNewCell mp:CellItem? OnCellEnd
+{
+ MORK_USED_2(ev,inSpan);
+ // mParser_InCell = morkBool_kFalse;
+
+ mBuilder_MetaTokenSlot = 0;
+ mBuilder_CellAtomScope = mBuilder_RowAtomScope;
+}
+
+/*virtual*/ void
+morkBuilder::OnValue(morkEnv* ev, const morkSpan& inSpan,
+ const morkBuf& inBuf)
+// mp:CellItem ::= mp:Slot | OnCellForm | OnCellGlitch
+// mp:Slot ::= OnValue | OnValueMid | OnRowMid | OnTableMid
+{
+ MORK_USED_1(inSpan);
+ morkStore* store = mBuilder_Store;
+ morkCell* cell = mBuilder_Cell;
+ if ( cell )
+ {
+ mdbYarn yarn;
+ yarn.mYarn_Buf = inBuf.mBuf_Body;
+ yarn.mYarn_Fill = yarn.mYarn_Size = inBuf.mBuf_Fill;
+ yarn.mYarn_More = 0;
+ yarn.mYarn_Form = mBuilder_CellForm;
+ yarn.mYarn_Grow = 0;
+ morkAtom* atom = store->YarnToAtom(ev, &yarn, true /* create */);
+ cell->SetAtom(ev, atom, store->StorePool());
+ }
+ else if ( mParser_InMeta )
+ {
+ mork_token* metaSlot = mBuilder_MetaTokenSlot;
+ if ( metaSlot )
+ {
+ if ( metaSlot == &mBuilder_TableStatus ) // table status?
+ {
+ if ( mParser_InTable && mBuilder_Table )
+ {
+ const char* body = (const char*) inBuf.mBuf_Body;
+ mork_fill bufFill = inBuf.mBuf_Fill;
+ if ( body && bufFill )
+ {
+ const char* bodyEnd = body + bufFill;
+ while ( body < bodyEnd )
+ {
+ int c = *body++;
+ switch ( c )
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ mBuilder_TablePriority = (mork_priority) ( c - '0' );
+ break;
+
+ case 'u':
+ case 'U':
+ mBuilder_TableIsUnique = morkBool_kTrue;
+ break;
+
+ case 'v':
+ case 'V':
+ mBuilder_TableIsVerbose = morkBool_kTrue;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ mork_token token = store->BufToToken(ev, &inBuf);
+ if ( token )
+ {
+ *metaSlot = token;
+ if ( metaSlot == &mBuilder_TableKind ) // table kind?
+ {
+ if ( mParser_InTable && mBuilder_Table )
+ mBuilder_Table->mTable_Kind = token;
+ }
+ }
+ }
+ }
+ }
+ else
+ this->NilBuilderCellError(ev);
+}
+
+/*virtual*/ void
+morkBuilder::OnValueMid(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid)
+// mp:CellItem ::= mp:Slot | OnCellForm | OnCellGlitch
+// mp:Slot ::= OnValue | OnValueMid | OnRowMid | OnTableMid
+{
+ MORK_USED_1(inSpan);
+ morkStore* store = mBuilder_Store;
+ morkCell* cell = mBuilder_Cell;
+
+ morkMid valMid; // local mid for modifications
+ mdbOid* valOid = &valMid.mMid_Oid; // ref to oid inside mid
+ *valOid = inMid.mMid_Oid; // bitwise copy inMid's oid
+
+ if ( inMid.mMid_Buf )
+ {
+ if ( !valOid->mOid_Scope )
+ store->MidToOid(ev, inMid, valOid);
+ }
+ else if ( !valOid->mOid_Scope )
+ valOid->mOid_Scope = mBuilder_CellAtomScope;
+
+ if ( cell )
+ {
+ morkBookAtom* atom = store->MidToAtom(ev, valMid);
+ if ( atom )
+ cell->SetAtom(ev, atom, store->StorePool());
+ else
+ ev->NewError("undefined cell value alias");
+ }
+ else if ( mParser_InMeta )
+ {
+ mork_token* metaSlot = mBuilder_MetaTokenSlot;
+ if ( metaSlot )
+ {
+ mork_scope valScope = valOid->mOid_Scope;
+ if ( !valScope || valScope == morkStore_kColumnSpaceScope )
+ {
+ if ( ev->Good() && valMid.HasSomeId() )
+ {
+ *metaSlot = valOid->mOid_Id;
+ if ( metaSlot == &mBuilder_TableKind ) // table kind?
+ {
+ if ( mParser_InTable && mBuilder_Table )
+ {
+ mBuilder_Table->mTable_Kind = valOid->mOid_Id;
+ }
+ else
+ ev->NewWarning("mBuilder_TableKind not in table");
+ }
+ else if ( metaSlot == &mBuilder_TableStatus ) // table status?
+ {
+ if ( mParser_InTable && mBuilder_Table )
+ {
+ // $$ what here??
+ }
+ else
+ ev->NewWarning("mBuilder_TableStatus not in table");
+ }
+ }
+ }
+ else
+ this->NonColumnSpaceScopeError(ev);
+ }
+ }
+ else
+ this->NilBuilderCellError(ev);
+}
+
+/*virtual*/ void
+morkBuilder::OnRowMid(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid)
+// mp:CellItem ::= mp:Slot | OnCellForm | OnCellGlitch
+// mp:Slot ::= OnValue | OnValueMid | OnRowMid | OnTableMid
+{
+ MORK_USED_1(inSpan);
+ morkStore* store = mBuilder_Store;
+ morkCell* cell = mBuilder_Cell;
+ if ( cell )
+ {
+ mdbOid rowOid = inMid.mMid_Oid;
+ if ( inMid.mMid_Buf )
+ {
+ if ( !rowOid.mOid_Scope )
+ store->MidToOid(ev, inMid, &rowOid);
+ }
+ else if ( !rowOid.mOid_Scope )
+ rowOid.mOid_Scope = mBuilder_RowRowScope;
+
+ if ( ev->Good() )
+ {
+ morkPool* pool = store->StorePool();
+ morkAtom* atom = pool->NewRowOidAtom(ev, rowOid, &store->mStore_Zone);
+ if ( atom )
+ {
+ cell->SetAtom(ev, atom, pool);
+ morkRow* row = store->OidToRow(ev, &rowOid);
+ if ( row ) // found or created such a row?
+ row->AddRowGcUse(ev);
+ }
+ }
+ }
+ else
+ this->NilBuilderCellError(ev);
+}
+
+/*virtual*/ void
+morkBuilder::OnTableMid(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid)
+// mp:CellItem ::= mp:Slot | OnCellForm | OnCellGlitch
+// mp:Slot ::= OnValue | OnValueMid | OnRowMid | OnTableMid
+{
+ MORK_USED_1(inSpan);
+ morkStore* store = mBuilder_Store;
+ morkCell* cell = mBuilder_Cell;
+ if ( cell )
+ {
+ mdbOid tableOid = inMid.mMid_Oid;
+ if ( inMid.mMid_Buf )
+ {
+ if ( !tableOid.mOid_Scope )
+ store->MidToOid(ev, inMid, &tableOid);
+ }
+ else if ( !tableOid.mOid_Scope )
+ tableOid.mOid_Scope = mBuilder_RowRowScope;
+
+ if ( ev->Good() )
+ {
+ morkPool* pool = store->StorePool();
+ morkAtom* atom = pool->NewTableOidAtom(ev, tableOid, &store->mStore_Zone);
+ if ( atom )
+ {
+ cell->SetAtom(ev, atom, pool);
+ morkTable* table = store->OidToTable(ev, &tableOid,
+ /*optionalMetaRowOid*/ (mdbOid*) 0);
+ if ( table ) // found or created such a table?
+ table->AddTableGcUse(ev);
+ }
+ }
+ }
+ else
+ this->NilBuilderCellError(ev);
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkBuilder.h b/components/mork/src/morkBuilder.h
new file mode 100644
index 000000000..cd8b9527a
--- /dev/null
+++ b/components/mork/src/morkBuilder.h
@@ -0,0 +1,303 @@
+/* -*- 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/. */
+
+#ifndef _MORKBUILDER_
+#define _MORKBUILDER_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKPARSER_
+#include "morkParser.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*| kCellsVecSize: length of cell vector buffer inside morkBuilder
+|*/
+#define morkBuilder_kCellsVecSize 64
+
+#define morkBuilder_kDefaultBytesPerParseSegment 512 /* plausible to big */
+
+#define morkDerived_kBuilder /*i*/ 0x4275 /* ascii 'Bu' */
+
+class morkBuilder /*d*/ : public morkParser {
+
+// public: // slots inherited from morkParser (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+
+ // nsIMdbHeap* mParser_Heap; // refcounted heap used for allocation
+ // morkStream* mParser_Stream; // refcounted input stream
+
+ // mork_u4 mParser_Tag; // must equal morkParser_kTag
+ // mork_count mParser_MoreGranularity; // constructor inBytesPerParseSegment
+
+ // mork_u4 mParser_State; // state where parser should resume
+
+ // after finding ends of group transactions, we can re-seek the start:
+ // mork_pos mParser_GroupContentStartPos; // start of this group
+
+ // mdbOid mParser_TableOid; // table oid if inside a table
+ // mdbOid mParser_RowOid; // row oid if inside a row
+ // mork_gid mParser_GroupId; // group ID if inside a group
+
+ // mork_bool mParser_InPort; // called OnNewPort but not OnPortEnd?
+ // mork_bool mParser_InDict; // called OnNewDict but not OnDictEnd?
+ // mork_bool mParser_InCell; // called OnNewCell but not OnCellEnd?
+ // mork_bool mParser_InMeta; // called OnNewMeta but not OnMetaEnd?
+
+ // morkMid mParser_Mid; // current alias being parsed
+ // note that mParser_Mid.mMid_Buf points at mParser_ScopeCoil below:
+
+ // blob coils allocated in mParser_Heap
+ // morkCoil mParser_ScopeCoil; // place to accumulate ID scope blobs
+ // morkCoil mParser_ValueCoil; // place to accumulate value blobs
+ // morkCoil mParser_ColumnCoil; // place to accumulate column blobs
+ // morkCoil mParser_StringCoil; // place to accumulate string blobs
+
+ // morkSpool mParser_ScopeSpool; // writes to mParser_ScopeCoil
+ // morkSpool mParser_ValueSpool; // writes to mParser_ValueCoil
+ // morkSpool mParser_ColumnSpool; // writes to mParser_ColumnCoil
+ // morkSpool mParser_StringSpool; // writes to mParser_StringCoil
+
+ // yarns allocated in mParser_Heap
+ // morkYarn mParser_MidYarn; // place to receive from MidToYarn()
+
+ // span showing current ongoing file position status:
+ // morkSpan mParser_PortSpan; // span of current db port file
+
+ // various spans denoting nested subspaces inside the file's port span:
+ // morkSpan mParser_GroupSpan; // span of current transaction group
+ // morkSpan mParser_DictSpan;
+ // morkSpan mParser_AliasSpan;
+ // morkSpan mParser_MetaDictSpan;
+ // morkSpan mParser_TableSpan;
+ // morkSpan mParser_MetaTableSpan;
+ // morkSpan mParser_RowSpan;
+ // morkSpan mParser_MetaRowSpan;
+ // morkSpan mParser_CellSpan;
+ // morkSpan mParser_ColumnSpan;
+ // morkSpan mParser_SlotSpan;
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+protected: // protected morkBuilder members
+
+ // weak refs that do not prevent closure of referenced nodes:
+ morkStore* mBuilder_Store; // weak ref to builder's store
+
+ // strong refs that do indeed prevent closure of referenced nodes:
+ morkTable* mBuilder_Table; // current table being built (or nil)
+ morkRow* mBuilder_Row; // current row being built (or nil)
+ morkCell* mBuilder_Cell; // current cell within CellsVec (or nil)
+
+ morkRowSpace* mBuilder_RowSpace; // space for mBuilder_CellRowScope
+ morkAtomSpace* mBuilder_AtomSpace; // space for mBuilder_CellAtomScope
+
+ morkAtomSpace* mBuilder_OidAtomSpace; // ground atom space for oids
+ morkAtomSpace* mBuilder_ScopeAtomSpace; // ground atom space for scopes
+
+ // scoped object ids for current objects under construction:
+ mdbOid mBuilder_TableOid; // full oid for current table
+ mdbOid mBuilder_RowOid; // full oid for current row
+
+ // tokens that become set as the result of meta cells in port rows:
+ mork_cscode mBuilder_PortForm; // default port charset format
+ mork_scope mBuilder_PortRowScope; // port row scope
+ mork_scope mBuilder_PortAtomScope; // port atom scope
+
+ // tokens that become set as the result of meta cells in meta tables:
+ mork_cscode mBuilder_TableForm; // default table charset format
+ mork_scope mBuilder_TableRowScope; // table row scope
+ mork_scope mBuilder_TableAtomScope; // table atom scope
+ mork_kind mBuilder_TableKind; // table kind
+
+ mork_token mBuilder_TableStatus; // dummy: priority/unique/verbose
+
+ mork_priority mBuilder_TablePriority; // table priority
+ mork_bool mBuilder_TableIsUnique; // table uniqueness
+ mork_bool mBuilder_TableIsVerbose; // table verboseness
+ mork_u1 mBuilder_TablePadByte; // for u4 alignment
+
+ // tokens that become set as the result of meta cells in meta rows:
+ mork_cscode mBuilder_RowForm; // default row charset format
+ mork_scope mBuilder_RowRowScope; // row scope per row metainfo
+ mork_scope mBuilder_RowAtomScope; // row atom scope
+
+ // meta tokens currently in force, driven by meta info slots above:
+ mork_cscode mBuilder_CellForm; // cell charset format
+ mork_scope mBuilder_CellAtomScope; // cell atom scope
+
+ mork_cscode mBuilder_DictForm; // dict charset format
+ mork_scope mBuilder_DictAtomScope; // dict atom scope
+
+ mork_token* mBuilder_MetaTokenSlot; // pointer to some slot above
+
+ // If any of these 'cut' bools are true, it means a minus was seen in the
+ // Mork source text to indicate removal of content from some container.
+ // (Note there is no corresponding 'add' bool, since add is the default.)
+ // CutRow implies the current row should be cut from the table.
+ // CutCell implies the current column should be cut from the row.
+ mork_bool mBuilder_DoCutRow; // row with kCut change
+ mork_bool mBuilder_DoCutCell; // cell with kCut change
+ mork_u1 mBuilder_row_pad; // pad to u4 alignment
+ mork_u1 mBuilder_cell_pad; // pad to u4 alignment
+
+ morkCell mBuilder_CellsVec[ morkBuilder_kCellsVecSize + 1 ];
+ mork_fill mBuilder_CellsVecFill; // count used in CellsVec
+ // Note when mBuilder_CellsVecFill equals morkBuilder_kCellsVecSize, and
+ // another cell is added, this means all the cells in the vector above
+ // must be flushed to the current row being built to create more room.
+
+protected: // protected inlines
+
+ mork_bool CellVectorIsFull() const
+ { return ( mBuilder_CellsVecFill == morkBuilder_kCellsVecSize ); }
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseBuilder() only if open
+ virtual ~morkBuilder(); // assert that CloseBuilder() executed earlier
+
+public: // morkYarn construction & destruction
+ morkBuilder(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ morkStream* ioStream, // the readonly stream for input bytes
+ mdb_count inBytesPerParseSegment, // target for ParseMore()
+ nsIMdbHeap* ioSlotHeap, morkStore* ioStore
+ );
+
+ void CloseBuilder(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkBuilder(const morkBuilder& other);
+ morkBuilder& operator=(const morkBuilder& other);
+
+public: // dynamic type identification
+ mork_bool IsBuilder() const
+ { return IsNode() && mNode_Derived == morkDerived_kBuilder; }
+// } ===== end morkNode methods =====
+
+public: // errors
+ static void NonBuilderTypeError(morkEnv* ev);
+ static void NilBuilderCellError(morkEnv* ev);
+ static void NilBuilderRowError(morkEnv* ev);
+ static void NilBuilderTableError(morkEnv* ev);
+ static void NonColumnSpaceScopeError(morkEnv* ev);
+
+ void LogGlitch(morkEnv* ev, const morkGlitch& inGlitch,
+ const char* inKind);
+
+public: // other builder methods
+
+ morkCell* AddBuilderCell(morkEnv* ev,
+ const morkMid& inMid, mork_change inChange);
+
+ void FlushBuilderCells(morkEnv* ev);
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // in virtual morkParser methods, data flow subclass to parser
+
+ virtual void MidToYarn(morkEnv* ev,
+ const morkMid& inMid, // typically an alias to concat with strings
+ mdbYarn* outYarn) override;
+ // The parser might ask that some aliases be turned into yarns, so they
+ // can be concatenated into longer blobs under some circumstances. This
+ // is an alternative to using a long and complex callback for many parts
+ // for a single cell value.
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // out virtual morkParser methods, data flow parser to subclass
+
+ virtual void OnNewPort(morkEnv* ev, const morkPlace& inPlace) override;
+ virtual void OnPortGlitch(morkEnv* ev, const morkGlitch& inGlitch) override;
+ virtual void OnPortEnd(morkEnv* ev, const morkSpan& inSpan) override;
+
+ virtual void OnNewGroup(morkEnv* ev, const morkPlace& inPlace, mork_gid inGid) override;
+ virtual void OnGroupGlitch(morkEnv* ev, const morkGlitch& inGlitch) override;
+ virtual void OnGroupCommitEnd(morkEnv* ev, const morkSpan& inSpan) override;
+ virtual void OnGroupAbortEnd(morkEnv* ev, const morkSpan& inSpan) override;
+
+ virtual void OnNewPortRow(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid& inMid, mork_change inChange) override;
+ virtual void OnPortRowGlitch(morkEnv* ev, const morkGlitch& inGlitch) override;
+ virtual void OnPortRowEnd(morkEnv* ev, const morkSpan& inSpan) override;
+
+ virtual void OnNewTable(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid& inMid, mork_bool inCutAllRows) override;
+ virtual void OnTableGlitch(morkEnv* ev, const morkGlitch& inGlitch) override;
+ virtual void OnTableEnd(morkEnv* ev, const morkSpan& inSpan) override;
+
+ virtual void OnNewMeta(morkEnv* ev, const morkPlace& inPlace) override;
+ virtual void OnMetaGlitch(morkEnv* ev, const morkGlitch& inGlitch) override;
+ virtual void OnMetaEnd(morkEnv* ev, const morkSpan& inSpan) override;
+
+ virtual void OnMinusRow(morkEnv* ev) override;
+ virtual void OnNewRow(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid& inMid, mork_bool inCutAllCols) override;
+ virtual void OnRowPos(morkEnv* ev, mork_pos inRowPos) override;
+ virtual void OnRowGlitch(morkEnv* ev, const morkGlitch& inGlitch) override;
+ virtual void OnRowEnd(morkEnv* ev, const morkSpan& inSpan) override;
+
+ virtual void OnNewDict(morkEnv* ev, const morkPlace& inPlace) override;
+ virtual void OnDictGlitch(morkEnv* ev, const morkGlitch& inGlitch) override;
+ virtual void OnDictEnd(morkEnv* ev, const morkSpan& inSpan) override;
+
+ virtual void OnAlias(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid) override;
+
+ virtual void OnAliasGlitch(morkEnv* ev, const morkGlitch& inGlitch) override;
+
+ virtual void OnMinusCell(morkEnv* ev) override;
+ virtual void OnNewCell(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid* inMid, const morkBuf* inBuf) override;
+ // Exactly one of inMid and inBuf is nil, and the other is non-nil.
+ // When hex ID syntax is used for a column, then inMid is not nil, and
+ // when a naked string names a column, then inBuf is not nil.
+
+ virtual void OnCellGlitch(morkEnv* ev, const morkGlitch& inGlitch) override;
+ virtual void OnCellForm(morkEnv* ev, mork_cscode inCharsetFormat) override;
+ virtual void OnCellEnd(morkEnv* ev, const morkSpan& inSpan) override;
+
+ virtual void OnValue(morkEnv* ev, const morkSpan& inSpan,
+ const morkBuf& inBuf) override;
+
+ virtual void OnValueMid(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid) override;
+
+ virtual void OnRowMid(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid) override;
+
+ virtual void OnTableMid(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid) override;
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // public non-poly morkBuilder methods
+
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakBuilder(morkBuilder* me,
+ morkEnv* ev, morkBuilder** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongBuilder(morkBuilder* me,
+ morkEnv* ev, morkBuilder** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKBUILDER_ */
diff --git a/components/mork/src/morkCell.cpp b/components/mork/src/morkCell.cpp
new file mode 100644
index 000000000..80f9e04d0
--- /dev/null
+++ b/components/mork/src/morkCell.cpp
@@ -0,0 +1,114 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKPOOL_
+#include "morkPool.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKCELL_
+#include "morkCell.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+void
+morkCell::SetYarn(morkEnv* ev, const mdbYarn* inYarn, morkStore* ioStore)
+{
+ morkAtom* atom = ioStore->YarnToAtom(ev, inYarn, true /* create */);
+ if ( atom )
+ this->SetAtom(ev, atom, ioStore->StorePool()); // refcounts atom
+}
+
+void
+morkCell::GetYarn(morkEnv* ev, mdbYarn* outYarn) const
+{
+ MORK_USED_1(ev);
+ morkAtom::GetYarn(mCell_Atom, outYarn);
+}
+
+void
+morkCell::AliasYarn(morkEnv* ev, mdbYarn* outYarn) const
+{
+ MORK_USED_1(ev);
+ morkAtom::AliasYarn(mCell_Atom, outYarn);
+}
+
+void
+morkCell::SetCellClean()
+{
+ mork_column col = this->GetColumn();
+ this->SetColumnAndChange(col, morkChange_kNil);
+}
+
+void
+morkCell::SetCellDirty()
+{
+ mork_column col = this->GetColumn();
+ this->SetColumnAndChange(col, morkChange_kAdd);
+}
+
+void
+morkCell::SetAtom(morkEnv* ev, morkAtom* ioAtom, morkPool* ioPool)
+ // SetAtom() "acquires" the new ioAtom if non-nil, by calling AddCellUse()
+ // to increase the refcount, and puts ioAtom into mCell_Atom. If the old
+ // atom in mCell_Atom is non-nil, then it is "released" first by a call to
+ // CutCellUse(), and if the use count then becomes zero, then the old atom
+ // is deallocated by returning it to the pool ioPool. (And this is
+ // why ioPool is a parameter to this method.) Note that ioAtom can be nil
+ // to cause the cell to refer to nothing, and the old atom in mCell_Atom
+ // can also be nil, and all the atom refcounting is handled correctly.
+ //
+ // Note that if ioAtom was just created, it typically has a zero use count
+ // before calling SetAtom(). But use count is one higher after SetAtom().
+{
+ morkAtom* oldAtom = mCell_Atom;
+ if ( oldAtom != ioAtom ) // ioAtom is not already installed in this cell?
+ {
+ if ( oldAtom )
+ {
+ mCell_Atom = 0;
+ if ( oldAtom->CutCellUse(ev) == 0 )
+ {
+ // this was zapping atoms still in use - comment out until davidmc
+ // can figure out a better fix.
+// if ( ioPool )
+// {
+// if ( oldAtom->IsBook() )
+// ((morkBookAtom*) oldAtom)->CutBookAtomFromSpace(ev);
+
+// ioPool->ZapAtom(ev, oldAtom);
+// }
+// else
+// ev->NilPointerError();
+ }
+ }
+ if ( ioAtom )
+ ioAtom->AddCellUse(ev);
+
+ mCell_Atom = ioAtom;
+ }
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkCell.h b/components/mork/src/morkCell.h
new file mode 100644
index 000000000..50642d916
--- /dev/null
+++ b/components/mork/src/morkCell.h
@@ -0,0 +1,89 @@
+/* -*- 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/. */
+
+#ifndef _MORKCELL_
+#define _MORKCELL_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDelta_kShift 8 /* 8 bit shift */
+#define morkDelta_kChangeMask 0x0FF /* low 8 bit mask */
+#define morkDelta_kColumnMask (~ (mork_column) morkDelta_kChangeMask)
+#define morkDelta_Init(self,cl,ch) ((self) = ((cl)<<morkDelta_kShift) | (ch))
+#define morkDelta_Change(self) ((mork_change) ((self) & morkDelta_kChangeMask))
+#define morkDelta_Column(self) ((self) >> morkDelta_kShift)
+
+class morkCell { // minimal cell format
+
+public:
+ mork_delta mCell_Delta; // encoding of both column and change
+ morkAtom* mCell_Atom; // content in this cell
+
+public:
+ morkCell() : mCell_Delta( 0 ), mCell_Atom( 0 ) { }
+
+ morkCell(const morkCell& c)
+ : mCell_Delta( c.mCell_Delta ), mCell_Atom( c.mCell_Atom ) { }
+
+ // note if ioAtom is non-nil, caller needs to call ioAtom->AddCellUse():
+ morkCell(mork_column inCol, mork_change inChange, morkAtom* ioAtom)
+ {
+ morkDelta_Init(mCell_Delta, inCol,inChange);
+ mCell_Atom = ioAtom;
+ }
+
+ // note if ioAtom is non-nil, caller needs to call ioAtom->AddCellUse():
+ void Init(mork_column inCol, mork_change inChange, morkAtom* ioAtom)
+ {
+ morkDelta_Init(mCell_Delta,inCol,inChange);
+ mCell_Atom = ioAtom;
+ }
+
+ mork_column GetColumn() const { return morkDelta_Column(mCell_Delta); }
+ mork_change GetChange() const { return morkDelta_Change(mCell_Delta); }
+
+ mork_bool IsCellClean() const { return GetChange() == morkChange_kNil; }
+ mork_bool IsCellDirty() const { return GetChange() != morkChange_kNil; }
+
+ void SetCellClean(); // set change to kNil
+ void SetCellDirty(); // set change to kAdd
+
+ void SetCellColumnDirty(mork_column inCol)
+ { this->SetColumnAndChange(inCol, morkChange_kAdd); }
+
+ void SetCellColumnClean(mork_column inCol)
+ { this->SetColumnAndChange(inCol, morkChange_kNil); }
+
+ void SetColumnAndChange(mork_column inCol, mork_change inChange)
+ { morkDelta_Init(mCell_Delta, inCol, inChange); }
+
+ morkAtom* GetAtom() { return mCell_Atom; }
+
+ void SetAtom(morkEnv* ev, morkAtom* ioAtom, morkPool* ioPool);
+ // SetAtom() "acquires" the new ioAtom if non-nil, by calling AddCellUse()
+ // to increase the refcount, and puts ioAtom into mCell_Atom. If the old
+ // atom in mCell_Atom is non-nil, then it is "released" first by a call to
+ // CutCellUse(), and if the use count then becomes zero, then the old atom
+ // is deallocated by returning it to the pool ioPool. (And this is
+ // why ioPool is a parameter to this method.) Note that ioAtom can be nil
+ // to cause the cell to refer to nothing, and the old atom in mCell_Atom
+ // can also be nil, and all the atom refcounting is handled correctly.
+ //
+ // Note that if ioAtom was just created, it typically has a zero use count
+ // before calling SetAtom(). But use count is one higher after SetAtom().
+
+ void SetYarn(morkEnv* ev, const mdbYarn* inYarn, morkStore* ioStore);
+
+ void AliasYarn(morkEnv* ev, mdbYarn* outYarn) const;
+ void GetYarn(morkEnv* ev, mdbYarn* outYarn) const;
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKCELL_ */
diff --git a/components/mork/src/morkCellObject.cpp b/components/mork/src/morkCellObject.cpp
new file mode 100644
index 000000000..210b6d6e2
--- /dev/null
+++ b/components/mork/src/morkCellObject.cpp
@@ -0,0 +1,530 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKCELLOBJECT_
+#include "morkCellObject.h"
+#endif
+
+#ifndef _MORKROWOBJECT_
+#include "morkRowObject.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+#ifndef _MORKCELL_
+#include "morkCell.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkCellObject::CloseMorkNode(morkEnv* ev) // CloseCellObject() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseCellObject(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkCellObject::~morkCellObject() // assert CloseCellObject() executed earlier
+{
+ CloseMorkNode(mMorkEnv);
+ MORK_ASSERT(mCellObject_Row==0);
+}
+
+/*public non-poly*/
+morkCellObject::morkCellObject(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkRow* ioRow, morkCell* ioCell,
+ mork_column inCol, mork_pos inPos)
+: morkObject(ev, inUsage, ioHeap, morkColor_kNone, (morkHandle*) 0)
+, mCellObject_RowObject( 0 )
+, mCellObject_Row( 0 )
+, mCellObject_Cell( 0 )
+, mCellObject_Col( inCol )
+, mCellObject_RowSeed( 0 )
+, mCellObject_Pos( (mork_u2) inPos )
+{
+ if ( ev->Good() )
+ {
+ if ( ioRow && ioCell )
+ {
+ if ( ioRow->IsRow() )
+ {
+ morkStore* store = ioRow->GetRowSpaceStore(ev);
+ if ( store )
+ {
+ morkRowObject* rowObj = ioRow->AcquireRowObject(ev, store);
+ if ( rowObj )
+ {
+ mCellObject_Row = ioRow;
+ mCellObject_Cell = ioCell;
+ mCellObject_RowSeed = ioRow->mRow_Seed;
+
+ // morkRowObject::SlotStrongRowObject(rowObj, ev,
+ // &mCellObject_RowObject);
+
+ mCellObject_RowObject = rowObj; // assume control of strong ref
+ }
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kCellObject;
+ }
+ }
+ else
+ ioRow->NonRowTypeError(ev);
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkCellObject, morkObject, nsIMdbCell)
+
+/*public non-poly*/ void
+morkCellObject::CloseCellObject(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ NS_RELEASE(mCellObject_RowObject);
+ mCellObject_Row = 0;
+ mCellObject_Cell = 0;
+ mCellObject_RowSeed = 0;
+ this->CloseObject(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+mork_bool
+morkCellObject::ResyncWithRow(morkEnv* ev)
+{
+ morkRow* row = mCellObject_Row;
+ mork_pos pos = 0;
+ morkCell* cell = row->GetCell(ev, mCellObject_Col, &pos);
+ if ( cell )
+ {
+ mCellObject_Pos = (mork_u2) pos;
+ mCellObject_Cell = cell;
+ mCellObject_RowSeed = row->mRow_Seed;
+ }
+ else
+ {
+ mCellObject_Cell = 0;
+ this->MissingRowColumnError(ev);
+ }
+ return ev->Good();
+}
+
+morkAtom*
+morkCellObject::GetCellAtom(morkEnv* ev) const
+{
+ morkCell* cell = mCellObject_Cell;
+ if ( cell )
+ return cell->GetAtom();
+ else
+ this->NilCellError(ev);
+
+ return (morkAtom*) 0;
+}
+
+/*static*/ void
+morkCellObject::WrongRowObjectRowError(morkEnv* ev)
+{
+ ev->NewError("mCellObject_Row != mCellObject_RowObject->mRowObject_Row");
+}
+
+/*static*/ void
+morkCellObject::NilRowError(morkEnv* ev)
+{
+ ev->NewError("nil mCellObject_Row");
+}
+
+/*static*/ void
+morkCellObject::NilRowObjectError(morkEnv* ev)
+{
+ ev->NewError("nil mCellObject_RowObject");
+}
+
+/*static*/ void
+morkCellObject::NilCellError(morkEnv* ev)
+{
+ ev->NewError("nil mCellObject_Cell");
+}
+
+/*static*/ void
+morkCellObject::NonCellObjectTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkCellObject");
+}
+
+/*static*/ void
+morkCellObject::MissingRowColumnError(morkEnv* ev)
+{
+ ev->NewError("mCellObject_Col not in mCellObject_Row");
+}
+
+nsIMdbCell*
+morkCellObject::AcquireCellHandle(morkEnv* ev)
+{
+ nsIMdbCell* outCell = this;
+ NS_ADDREF(outCell);
+ return outCell;
+}
+
+
+morkEnv*
+morkCellObject::CanUseCell(nsIMdbEnv* mev, mork_bool inMutable,
+ nsresult* outErr, morkCell** outCell)
+{
+ morkEnv* outEnv = 0;
+ morkCell* cell = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( IsCellObject() )
+ {
+ if ( IsMutable() || !inMutable )
+ {
+ morkRowObject* rowObj = mCellObject_RowObject;
+ if ( rowObj )
+ {
+ morkRow* row = mCellObject_Row;
+ if ( row )
+ {
+ if ( rowObj->mRowObject_Row == row )
+ {
+ mork_u2 oldSeed = mCellObject_RowSeed;
+ if ( row->mRow_Seed == oldSeed || ResyncWithRow(ev) )
+ {
+ cell = mCellObject_Cell;
+ if ( cell )
+ {
+ outEnv = ev;
+ }
+ else
+ NilCellError(ev);
+ }
+ }
+ else
+ WrongRowObjectRowError(ev);
+ }
+ else
+ NilRowError(ev);
+ }
+ else
+ NilRowObjectError(ev);
+ }
+ else
+ NonMutableNodeError(ev);
+ }
+ else
+ NonCellObjectTypeError(ev);
+ }
+ *outErr = ev->AsErr();
+ MORK_ASSERT(outEnv);
+ *outCell = cell;
+
+ return outEnv;
+}
+
+// { ----- begin attribute methods -----
+NS_IMETHODIMP morkCellObject::SetBlob(nsIMdbEnv* /* mev */,
+ nsIMdbBlob* /* ioBlob */)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+} // reads inBlob slots
+
+// when inBlob is in the same suite, this might be fastest cell-to-cell
+
+NS_IMETHODIMP morkCellObject::ClearBlob( // make empty (so content has zero length)
+ nsIMdbEnv* /* mev */)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // remember row->MaybeDirtySpaceStoreAndRow();
+}
+// clearing a yarn is like SetYarn() with empty yarn instance content
+
+NS_IMETHODIMP morkCellObject::GetBlobFill(nsIMdbEnv* mev,
+ mdb_fill* outFill)
+// Same value that would be put into mYarn_Fill, if one called GetYarn()
+// with a yarn instance that had mYarn_Buf==nil and mYarn_Size==0.
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+} // size of blob
+
+NS_IMETHODIMP morkCellObject::SetYarn(nsIMdbEnv* mev,
+ const mdbYarn* inYarn)
+{
+ nsresult outErr = NS_OK;
+ morkCell* cell = 0;
+ morkEnv* ev = this->CanUseCell(mev, /*inMutable*/ morkBool_kTrue,
+ &outErr, &cell);
+ if ( ev )
+ {
+ morkRow* row = mCellObject_Row;
+ if ( row )
+ {
+ morkStore* store = row->GetRowSpaceStore(ev);
+ if ( store )
+ {
+ cell->SetYarn(ev, inYarn, store);
+ if ( row->IsRowClean() && store->mStore_CanDirty )
+ row->MaybeDirtySpaceStoreAndRow();
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+
+ return outErr;
+} // reads from yarn slots
+// make this text object contain content from the yarn's buffer
+
+NS_IMETHODIMP morkCellObject::GetYarn(nsIMdbEnv* mev,
+ mdbYarn* outYarn)
+{
+ nsresult outErr = NS_OK;
+ morkCell* cell = 0;
+ morkEnv* ev = this->CanUseCell(mev, /*inMutable*/ morkBool_kTrue,
+ &outErr, &cell);
+ if ( ev )
+ {
+ morkAtom* atom = cell->GetAtom();
+ morkAtom::GetYarn(atom, outYarn);
+ outErr = ev->AsErr();
+ }
+
+ return outErr;
+} // writes some yarn slots
+// copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+
+NS_IMETHODIMP morkCellObject::AliasYarn(nsIMdbEnv* mev,
+ mdbYarn* outYarn)
+{
+ nsresult outErr = NS_OK;
+ morkCell* cell = 0;
+ morkEnv* ev = this->CanUseCell(mev, /*inMutable*/ morkBool_kTrue,
+ &outErr, &cell);
+ if ( ev )
+ {
+ morkAtom* atom = cell->GetAtom();
+ morkAtom::AliasYarn(atom, outYarn);
+ outErr = ev->AsErr();
+ }
+
+ return outErr;
+} // writes ALL yarn slots
+
+// } ----- end attribute methods -----
+
+// } ===== end nsIMdbBlob methods =====
+
+// { ===== begin nsIMdbCell methods =====
+
+// { ----- begin attribute methods -----
+NS_IMETHODIMP morkCellObject::SetColumn(nsIMdbEnv* mev, mdb_column inColumn)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // remember row->MaybeDirtySpaceStoreAndRow();
+}
+
+NS_IMETHODIMP morkCellObject::GetColumn(nsIMdbEnv* mev, mdb_column* outColumn)
+{
+ nsresult outErr = NS_OK;
+ mdb_column col = 0;
+ morkCell* cell = 0;
+ morkEnv* ev = this->CanUseCell(mev, /*inMutable*/ morkBool_kTrue,
+ &outErr, &cell);
+ if ( ev )
+ {
+ col = mCellObject_Col;
+ outErr = ev->AsErr();
+ }
+ if ( outColumn )
+ *outColumn = col;
+ return outErr;
+}
+
+NS_IMETHODIMP morkCellObject::GetCellInfo( // all cell metainfo except actual content
+ nsIMdbEnv* mev,
+ mdb_column* outColumn, // the column in the containing row
+ mdb_fill* outBlobFill, // the size of text content in bytes
+ mdbOid* outChildOid, // oid of possible row or table child
+ mdb_bool* outIsRowChild) // nonzero if child, and a row child
+// Checking all cell metainfo is a good way to avoid forcing a large cell
+// in to memory when you don't actually want to use the content.
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP morkCellObject::GetRow(nsIMdbEnv* mev, // parent row for this cell
+ nsIMdbRow** acqRow)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ morkCell* cell = 0;
+ morkEnv* ev = this->CanUseCell(mev, /*inMutable*/ morkBool_kTrue,
+ &outErr, &cell);
+ if ( ev )
+ {
+ outRow = mCellObject_RowObject->AcquireRowHandle(ev);
+
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+ return outErr;
+}
+
+NS_IMETHODIMP morkCellObject::GetPort(nsIMdbEnv* mev, // port containing cell
+ nsIMdbPort** acqPort)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbPort* outPort = 0;
+ morkCell* cell = 0;
+ morkEnv* ev = this->CanUseCell(mev, /*inMutable*/ morkBool_kTrue,
+ &outErr, &cell);
+ if ( ev )
+ {
+ if ( mCellObject_Row )
+ {
+ morkStore* store = mCellObject_Row->GetRowSpaceStore(ev);
+ if ( store )
+ outPort = store->AcquireStoreHandle(ev);
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( acqPort )
+ *acqPort = outPort;
+ return outErr;
+}
+// } ----- end attribute methods -----
+
+// { ----- begin children methods -----
+NS_IMETHODIMP morkCellObject::HasAnyChild( // does cell have a child instead of text?
+ nsIMdbEnv* mev,
+ mdbOid* outOid, // out id of row or table (or unbound if no child)
+ mdb_bool* outIsRow) // nonzero if child is a row (rather than a table)
+{
+ nsresult outErr = NS_OK;
+ mdb_bool isRow = morkBool_kFalse;
+ outOid->mOid_Scope = 0;
+ outOid->mOid_Id = morkId_kMinusOne;
+ morkCell* cell = 0;
+ morkEnv* ev = this->CanUseCell(mev, /*inMutable*/ morkBool_kTrue,
+ &outErr, &cell);
+ if ( ev )
+ {
+ morkAtom* atom = GetCellAtom(ev);
+ if ( atom )
+ {
+ isRow = atom->IsRowOid();
+ if ( isRow || atom->IsTableOid() )
+ *outOid = ((morkOidAtom*) atom)->mOidAtom_Oid;
+ }
+
+ outErr = ev->AsErr();
+ }
+ if ( outIsRow )
+ *outIsRow = isRow;
+
+ return outErr;
+}
+
+NS_IMETHODIMP morkCellObject::GetAnyChild( // access table of specific attribute
+ nsIMdbEnv* mev, // context
+ nsIMdbRow** acqRow, // child row (or null)
+ nsIMdbTable** acqTable) // child table (or null)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP morkCellObject::SetChildRow( // access table of specific attribute
+ nsIMdbEnv* mev, // context
+ nsIMdbRow* ioRow)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+} // inRow must be bound inside this same db port
+
+NS_IMETHODIMP morkCellObject::GetChildRow( // access row of specific attribute
+ nsIMdbEnv* mev, // context
+ nsIMdbRow** acqRow) // acquire child row (or nil if no child)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP morkCellObject::SetChildTable( // access table of specific attribute
+ nsIMdbEnv* mev, // context
+ nsIMdbTable* inTable) // table must be bound inside this same db port
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // remember row->MaybeDirtySpaceStoreAndRow();
+}
+
+NS_IMETHODIMP morkCellObject::GetChildTable( // access table of specific attribute
+ nsIMdbEnv* mev, // context
+ nsIMdbTable** acqTable) // acquire child tabdle (or nil if no chil)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+// } ----- end children methods -----
+
+// } ===== end nsIMdbCell methods =====
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkCellObject.h b/components/mork/src/morkCellObject.h
new file mode 100644
index 000000000..fced0accd
--- /dev/null
+++ b/components/mork/src/morkCellObject.h
@@ -0,0 +1,173 @@
+/* -*- 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/. */
+
+#ifndef _MORKCELLOBJECT_
+#define _MORKCELLOBJECT_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kCellObject /*i*/ 0x634F /* ascii 'cO' */
+
+class morkCellObject : public morkObject, public nsIMdbCell { // blob attribute in column scope
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // mork_color mBead_Color; // ID for this bead
+ // morkHandle* mObject_Handle; // weak ref to handle for this object
+
+public: // state is public because the entire Mork system is private
+ NS_DECL_ISUPPORTS_INHERITED
+
+ morkRowObject* mCellObject_RowObject; // strong ref to row's object
+ morkRow* mCellObject_Row; // cell's row if still in row object
+ morkCell* mCellObject_Cell; // cell in row if rowseed matches
+ mork_column mCellObject_Col; // col of cell last living in pos
+ mork_u2 mCellObject_RowSeed; // copy of row's seed
+ mork_u2 mCellObject_Pos; // position of cell in row
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseCellObject() only if open
+
+public: // morkCellObject construction & destruction
+ morkCellObject(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkRow* ioRow, morkCell* ioCell,
+ mork_column inCol, mork_pos inPos);
+ void CloseCellObject(morkEnv* ev); // called by CloseMorkNode();
+
+ NS_IMETHOD SetBlob(nsIMdbEnv* ev,
+ nsIMdbBlob* ioBlob) override; // reads inBlob slots
+ // when inBlob is in the same suite, this might be fastest cell-to-cell
+
+ NS_IMETHOD ClearBlob( // make empty (so content has zero length)
+ nsIMdbEnv* ev) override;
+ // clearing a yarn is like SetYarn() with empty yarn instance content
+
+ NS_IMETHOD GetBlobFill(nsIMdbEnv* ev,
+ mdb_fill* outFill) override; // size of blob
+ // Same value that would be put into mYarn_Fill, if one called GetYarn()
+ // with a yarn instance that had mYarn_Buf==nil and mYarn_Size==0.
+
+ NS_IMETHOD SetYarn(nsIMdbEnv* ev,
+ const mdbYarn* inYarn) override; // reads from yarn slots
+ // make this text object contain content from the yarn's buffer
+
+ NS_IMETHOD GetYarn(nsIMdbEnv* ev,
+ mdbYarn* outYarn) override; // writes some yarn slots
+ // copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+
+ NS_IMETHOD AliasYarn(nsIMdbEnv* ev,
+ mdbYarn* outYarn) override; // writes ALL yarn slots
+ NS_IMETHOD SetColumn(nsIMdbEnv* ev, mdb_column inColumn) override;
+ NS_IMETHOD GetColumn(nsIMdbEnv* ev, mdb_column* outColumn) override;
+
+ NS_IMETHOD GetCellInfo( // all cell metainfo except actual content
+ nsIMdbEnv* ev,
+ mdb_column* outColumn, // the column in the containing row
+ mdb_fill* outBlobFill, // the size of text content in bytes
+ mdbOid* outChildOid, // oid of possible row or table child
+ mdb_bool* outIsRowChild) override; // nonzero if child, and a row child
+
+ // Checking all cell metainfo is a good way to avoid forcing a large cell
+ // in to memory when you don't actually want to use the content.
+
+ NS_IMETHOD GetRow(nsIMdbEnv* ev, // parent row for this cell
+ nsIMdbRow** acqRow) override;
+ NS_IMETHOD GetPort(nsIMdbEnv* ev, // port containing cell
+ nsIMdbPort** acqPort) override;
+ // } ----- end attribute methods -----
+
+ // { ----- begin children methods -----
+ NS_IMETHOD HasAnyChild( // does cell have a child instead of text?
+ nsIMdbEnv* ev,
+ mdbOid* outOid, // out id of row or table (or unbound if no child)
+ mdb_bool* outIsRow) override; // nonzero if child is a row (rather than a table)
+
+ NS_IMETHOD GetAnyChild( // access table of specific attribute
+ nsIMdbEnv* ev, // context
+ nsIMdbRow** acqRow, // child row (or null)
+ nsIMdbTable** acqTable) override; // child table (or null)
+
+
+ NS_IMETHOD SetChildRow( // access table of specific attribute
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow) override; // inRow must be bound inside this same db port
+
+ NS_IMETHOD GetChildRow( // access row of specific attribute
+ nsIMdbEnv* ev, // context
+ nsIMdbRow** acqRow) override; // acquire child row (or nil if no child)
+
+
+ NS_IMETHOD SetChildTable( // access table of specific attribute
+ nsIMdbEnv* ev, // context
+ nsIMdbTable* inTable) override; // table must be bound inside this same db port
+
+ NS_IMETHOD GetChildTable( // access table of specific attribute
+ nsIMdbEnv* ev, // context
+ nsIMdbTable** acqTable) override; // acquire child table (or nil if no child)
+ // } ----- end children methods -----
+
+// } ===== end nsIMdbCell methods =====
+private: // copying is not allowed
+ virtual ~morkCellObject(); // assert that CloseCellObject() executed earlier
+ morkCellObject(const morkCellObject& other);
+ morkCellObject& operator=(const morkCellObject& other);
+
+public: // dynamic type identification
+ mork_bool IsCellObject() const
+ { return IsNode() && mNode_Derived == morkDerived_kCellObject; }
+// } ===== end morkNode methods =====
+
+public: // other cell node methods
+
+ morkEnv* CanUseCell(nsIMdbEnv* mev, mork_bool inMutable,
+ nsresult* outErr, morkCell** outCell);
+
+ mork_bool ResyncWithRow(morkEnv* ev); // return ev->Good()
+ morkAtom* GetCellAtom(morkEnv* ev) const;
+
+ static void MissingRowColumnError(morkEnv* ev);
+ static void NilRowError(morkEnv* ev);
+ static void NilCellError(morkEnv* ev);
+ static void NilRowObjectError(morkEnv* ev);
+ static void WrongRowObjectRowError(morkEnv* ev);
+ static void NonCellObjectTypeError(morkEnv* ev);
+
+ nsIMdbCell* AcquireCellHandle(morkEnv* ev);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakCellObject(morkCellObject* me,
+ morkEnv* ev, morkCellObject** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongCellObject(morkCellObject* me,
+ morkEnv* ev, morkCellObject** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKCELLOBJECT_ */
diff --git a/components/mork/src/morkCh.cpp b/components/mork/src/morkCh.cpp
new file mode 100644
index 000000000..07162a315
--- /dev/null
+++ b/components/mork/src/morkCh.cpp
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKCH_
+#include "morkCh.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/* this byte char predicate source file derives from public domain Mithril */
+/* (that means much of this has a copyright dedicated to the public domain) */
+
+/*============================================================================*/
+/* morkCh_Type */
+
+const mork_flags morkCh_Type[] = /* derives from public domain Mithril table */
+{
+ 0, /* 0x0 */
+ 0, /* 0x1 */
+ 0, /* 0x2 */
+ 0, /* 0x3 */
+ 0, /* 0x4 */
+ 0, /* 0x5 */
+ 0, /* 0x6 */
+ 0, /* 0x7 */
+ morkCh_kW, /* 0x8 backspace */
+ morkCh_kW, /* 0x9 tab */
+ morkCh_kW, /* 0xA linefeed */
+ 0, /* 0xB */
+ morkCh_kW, /* 0xC page */
+ morkCh_kW, /* 0xD return */
+ 0, /* 0xE */
+ 0, /* 0xF */
+ 0, /* 0x10 */
+ 0, /* 0x11 */
+ 0, /* 0x12 */
+ 0, /* 0x13 */
+ 0, /* 0x14 */
+ 0, /* 0x15 */
+ 0, /* 0x16 */
+ 0, /* 0x17 */
+ 0, /* 0x18 */
+ 0, /* 0x19 */
+ 0, /* 0x1A */
+ 0, /* 0x1B */
+ 0, /* 0x1C */
+ 0, /* 0x1D */
+ 0, /* 0x1E */
+ 0, /* 0x1F */
+
+ morkCh_kV|morkCh_kW, /* 0x20 space */
+ morkCh_kV|morkCh_kM, /* 0x21 ! */
+ morkCh_kV, /* 0x22 " */
+ morkCh_kV, /* 0x23 # */
+ 0, /* 0x24 $ cannot be kV because needs escape */
+ morkCh_kV, /* 0x25 % */
+ morkCh_kV, /* 0x26 & */
+ morkCh_kV, /* 0x27 ' */
+ morkCh_kV, /* 0x28 ( */
+ 0, /* 0x29 ) cannot be kV because needs escape */
+ morkCh_kV, /* 0x2A * */
+ morkCh_kV|morkCh_kM, /* 0x2B + */
+ morkCh_kV, /* 0x2C , */
+ morkCh_kV|morkCh_kM, /* 0x2D - */
+ morkCh_kV, /* 0x2E . */
+ morkCh_kV, /* 0x2F / */
+
+ morkCh_kV|morkCh_kD|morkCh_kX, /* 0x30 0 */
+ morkCh_kV|morkCh_kD|morkCh_kX, /* 0x31 1 */
+ morkCh_kV|morkCh_kD|morkCh_kX, /* 0x32 2 */
+ morkCh_kV|morkCh_kD|morkCh_kX, /* 0x33 3 */
+ morkCh_kV|morkCh_kD|morkCh_kX, /* 0x34 4 */
+ morkCh_kV|morkCh_kD|morkCh_kX, /* 0x35 5 */
+ morkCh_kV|morkCh_kD|morkCh_kX, /* 0x36 6 */
+ morkCh_kV|morkCh_kD|morkCh_kX, /* 0x37 7 */
+ morkCh_kV|morkCh_kD|morkCh_kX, /* 0x38 8 */
+ morkCh_kV|morkCh_kD|morkCh_kX, /* 0x39 9 */
+ morkCh_kV|morkCh_kN|morkCh_kM, /* 0x3A : */
+ morkCh_kV, /* 0x3B ; */
+ morkCh_kV, /* 0x3C < */
+ morkCh_kV, /* 0x3D = */
+ morkCh_kV, /* 0x3E > */
+ morkCh_kV|morkCh_kM, /* 0x3F ? */
+
+ morkCh_kV, /* 0x40 @ */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU|morkCh_kX, /* 0x41 A */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU|morkCh_kX, /* 0x42 B */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU|morkCh_kX, /* 0x43 C */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU|morkCh_kX, /* 0x44 D */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU|morkCh_kX, /* 0x45 E */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU|morkCh_kX, /* 0x46 F */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x47 G */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x48 H */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x49 I */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x4A J */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x4B K */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x4C L */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x4D M */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x4E N */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x4F O */
+
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x50 P */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x51 Q */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x52 R */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x53 S */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x54 T */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x55 U */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x56 V */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x57 W */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x58 X */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x59 Y */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kU, /* 0x5A Z */
+ morkCh_kV, /* 0x5B [ */
+ 0, /* 0x5C \ cannot be kV because needs escape */
+ morkCh_kV, /* 0x5D ] */
+ morkCh_kV, /* 0x5E ^ */
+ morkCh_kV|morkCh_kN|morkCh_kM, /* 0x5F _ */
+
+ morkCh_kV, /* 0x60 ` */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL|morkCh_kX, /* 0x61 a */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL|morkCh_kX, /* 0x62 b */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL|morkCh_kX, /* 0x63 c */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL|morkCh_kX, /* 0x64 d */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL|morkCh_kX, /* 0x65 e */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL|morkCh_kX, /* 0x66 f */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x67 g */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x68 h */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x69 i */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x6A j */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x6B k */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x6C l */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x6D m */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x6E n */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x6F o */
+
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x70 p */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x71 q */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x72 r */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x73 s */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x74 t */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x75 u */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x76 v */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x77 w */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x78 x */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x79 y */
+ morkCh_kV|morkCh_kN|morkCh_kM|morkCh_kL, /* 0x7A z */
+ morkCh_kV, /* 0x7B { */
+ morkCh_kV, /* 0x7C | */
+ morkCh_kV, /* 0x7D } */
+ morkCh_kV, /* 0x7E ~ */
+ morkCh_kW, /* 0x7F rubout */
+
+/* $"80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F" */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* $"90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F" */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* $"A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF" */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* $"B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF" */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* $"C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF" */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* $"D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF" */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* $"E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF" */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+
+/* $"F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF" */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkCh.h b/components/mork/src/morkCh.h
new file mode 100644
index 000000000..1d77ec64e
--- /dev/null
+++ b/components/mork/src/morkCh.h
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _MORKCH_
+#define _MORKCH_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+/* this byte char predicate header file derives from public domain Mithril */
+/* (that means much of this has a copyright dedicated to the public domain) */
+
+/* Use all 8 pred bits; lose some pred bits only if we need to reuse them. */
+
+/* ch pred bits: W:white D:digit V:value U:upper L:lower N:name M:more */
+#define morkCh_kW (1 << 0)
+#define morkCh_kD (1 << 1)
+#define morkCh_kV (1 << 2)
+#define morkCh_kU (1 << 3)
+#define morkCh_kL (1 << 4)
+#define morkCh_kX (1 << 5)
+#define morkCh_kN (1 << 6)
+#define morkCh_kM (1 << 7)
+
+extern const mork_flags morkCh_Type[]; /* 256 byte predicate bits ch map */
+
+/* is a numeric decimal digit: (note memory access might be slower) */
+/* define morkCh_IsDigit(c) ( morkCh_Type[ (mork_ch)(c) ] & morkCh_kD ) */
+#define morkCh_IsDigit(c) ( ((mork_ch) c) >= '0' && ((mork_ch) c) <= '9' )
+
+/* is a numeric octal digit: */
+#define morkCh_IsOctal(c) ( ((mork_ch) c) >= '0' && ((mork_ch) c) <= '7' )
+
+/* is a numeric hexadecimal digit: */
+#define morkCh_IsHex(c) ( morkCh_Type[ (mork_ch)(c) ] & morkCh_kX )
+
+/* is value (can be printed in Mork value without needing hex or escape): */
+#define morkCh_IsValue(c) ( morkCh_Type[ (mork_ch)(c) ] & morkCh_kV )
+
+/* is white space : */
+#define morkCh_IsWhite(c) ( morkCh_Type[ (mork_ch)(c) ] & morkCh_kW )
+
+/* is name (can start a Mork name): */
+#define morkCh_IsName(c) ( morkCh_Type[ (mork_ch)(c) ] & morkCh_kN )
+
+/* is name (can continue a Mork name): */
+#define morkCh_IsMore(c) ( morkCh_Type[ (mork_ch)(c) ] & morkCh_kM )
+
+/* is alphabetic upper or lower case */
+#define morkCh_IsAlpha(c) \
+ ( morkCh_Type[ (mork_ch)(c) ] & (morkCh_kL|morkCh_kU) )
+
+/* is alphanumeric, including lower case, upper case, and digits */
+#define morkCh_IsAlphaNum(c) \
+ (morkCh_Type[ (mork_ch)(c) ]&(morkCh_kL|morkCh_kU|morkCh_kD))
+
+/* ````` repeated testing of predicate bits in single flag byte ````` */
+
+#define morkCh_GetFlags(c) ( morkCh_Type[ (mork_ch)(c) ] )
+
+#define morkFlags_IsDigit(f) ( (f) & morkCh_kD )
+#define morkFlags_IsHex(f) ( (f) & morkCh_kX )
+#define morkFlags_IsValue(f) ( (f) & morkCh_kV )
+#define morkFlags_IsWhite(f) ( (f) & morkCh_kW )
+#define morkFlags_IsName(f) ( (f) & morkCh_kN )
+#define morkFlags_IsMore(f) ( (f) & morkCh_kM )
+#define morkFlags_IsAlpha(f) ( (f) & (morkCh_kL|morkCh_kU) )
+#define morkFlags_IsAlphaNum(f) ( (f) & (morkCh_kL|morkCh_kU|morkCh_kD) )
+
+#define morkFlags_IsUpper(f) ( (f) & morkCh_kU )
+#define morkFlags_IsLower(f) ( (f) & morkCh_kL )
+
+/* ````` character case (e.g. for case insensitive operations) ````` */
+
+
+#define morkCh_IsAscii(c) ( ((mork_u1) c) <= 0x7F )
+#define morkCh_IsSevenBitChar(c) ( ((mork_u1) c) <= 0x7F )
+
+/* ````` character case (e.g. for case insensitive operations) ````` */
+
+#define morkCh_ToLower(c) ((c)-'A'+'a')
+#define morkCh_ToUpper(c) ((c)-'a'+'A')
+
+/* extern int morkCh_IsUpper (int c); */
+#define morkCh_IsUpper(c) ( morkCh_Type[ (mork_ch)(c) ] & morkCh_kU )
+
+/* extern int morkCh_IsLower (int c); */
+#define morkCh_IsLower(c) ( morkCh_Type[ (mork_ch)(c) ] & morkCh_kL )
+
+#endif
+/* _MORKCH_ */
diff --git a/components/mork/src/morkConfig.cpp b/components/mork/src/morkConfig.cpp
new file mode 100644
index 000000000..b58cd03c8
--- /dev/null
+++ b/components/mork/src/morkConfig.cpp
@@ -0,0 +1,201 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKCONFIG_
+#include "morkConfig.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+void mork_assertion_signal(const char* inMessage)
+{
+#if defined(MORK_WIN) || defined(MORK_MAC)
+ // asm { int 3 }
+ NS_ERROR(inMessage);
+#endif /*MORK_WIN*/
+}
+
+#ifdef MORK_PROVIDE_STDLIB
+
+MORK_LIB_IMPL(mork_i4)
+mork_memcmp(const void* inOne, const void* inTwo, mork_size inSize)
+{
+ const mork_u1* t = (const mork_u1*) inTwo;
+ const mork_u1* s = (const mork_u1*) inOne;
+ const mork_u1* end = s + inSize;
+ mork_i4 delta;
+
+ while ( s < end )
+ {
+ delta = ((mork_i4) *s) - ((mork_i4) *t);
+ if ( delta )
+ return delta;
+ else
+ {
+ ++t;
+ ++s;
+ }
+ }
+ return 0;
+}
+
+MORK_LIB_IMPL(void)
+mork_memcpy(void* outDst, const void* inSrc, mork_size inSize)
+{
+ mork_u1* d = (mork_u1*) outDst;
+ mork_u1* end = d + inSize;
+ const mork_u1* s = ((const mork_u1*) inSrc);
+
+ while ( inSize >= 8 )
+ {
+ *d++ = *s++;
+ *d++ = *s++;
+ *d++ = *s++;
+ *d++ = *s++;
+
+ *d++ = *s++;
+ *d++ = *s++;
+ *d++ = *s++;
+ *d++ = *s++;
+
+ inSize -= 8;
+ }
+
+ while ( d < end )
+ *d++ = *s++;
+}
+
+MORK_LIB_IMPL(void)
+mork_memmove(void* outDst, const void* inSrc, mork_size inSize)
+{
+ mork_u1* d = (mork_u1*) outDst;
+ const mork_u1* s = (const mork_u1*) inSrc;
+ if ( d != s && inSize ) // copy is necessary?
+ {
+ const mork_u1* srcEnd = s + inSize; // one past last source byte
+
+ if ( d > s && d < srcEnd ) // overlap? need to copy backwards?
+ {
+ s = srcEnd; // start one past last source byte
+ d += inSize; // start one past last dest byte
+ mork_u1* dstBegin = d; // last byte to write is first in dest range
+ while ( d - dstBegin >= 8 )
+ {
+ *--d = *--s;
+ *--d = *--s;
+ *--d = *--s;
+ *--d = *--s;
+
+ *--d = *--s;
+ *--d = *--s;
+ *--d = *--s;
+ *--d = *--s;
+ }
+ while ( d > dstBegin )
+ *--d = *--s;
+ }
+ else // can copy forwards without any overlap
+ {
+ mork_u1* dstEnd = d + inSize;
+ while ( dstEnd - d >= 8 )
+ {
+ *d++ = *s++;
+ *d++ = *s++;
+ *d++ = *s++;
+ *d++ = *s++;
+
+ *d++ = *s++;
+ *d++ = *s++;
+ *d++ = *s++;
+ *d++ = *s++;
+ }
+ while ( d < dstEnd )
+ *d++ = *s++;
+ }
+ }
+}
+
+MORK_LIB_IMPL(void)
+mork_memset(void* outDst, int inByte, mork_size inSize)
+{
+ mork_u1* d = (mork_u1*) outDst;
+ mork_u1* end = d + inSize;
+ while ( d < end )
+ *d++ = (mork_u1) inByte;
+}
+
+MORK_LIB_IMPL(void)
+mork_strcpy(void* outDst, const void* inSrc)
+{
+ // back up one first to support preincrement
+ mork_u1* d = ((mork_u1*) outDst) - 1;
+ const mork_u1* s = ((const mork_u1*) inSrc) - 1;
+ while ( ( *++d = *++s ) != 0 )
+ /* empty */;
+}
+
+MORK_LIB_IMPL(mork_i4)
+mork_strcmp(const void* inOne, const void* inTwo)
+{
+ const mork_u1* t = (const mork_u1*) inTwo;
+ const mork_u1* s = ((const mork_u1*) inOne);
+ mork_i4 a;
+ mork_i4 b;
+ mork_i4 delta;
+
+ do
+ {
+ a = (mork_i4) *s++;
+ b = (mork_i4) *t++;
+ delta = a - b;
+ }
+ while ( !delta && a && b );
+
+ return delta;
+}
+
+MORK_LIB_IMPL(mork_i4)
+mork_strncmp(const void* inOne, const void* inTwo, mork_size inSize)
+{
+ const mork_u1* t = (const mork_u1*) inTwo;
+ const mork_u1* s = (const mork_u1*) inOne;
+ const mork_u1* end = s + inSize;
+ mork_i4 delta;
+ mork_i4 a;
+ mork_i4 b;
+
+ while ( s < end )
+ {
+ a = (mork_i4) *s++;
+ b = (mork_i4) *t++;
+ delta = a - b;
+ if ( delta || !a || !b )
+ return delta;
+ }
+ return 0;
+}
+
+MORK_LIB_IMPL(mork_size)
+mork_strlen(const void* inString)
+{
+ // back up one first to support preincrement
+ const mork_u1* s = ((const mork_u1*) inString) - 1;
+ while ( *++s ) // preincrement is cheapest
+ /* empty */;
+
+ return s - ((const mork_u1*) inString); // distance from original address
+}
+
+#endif /*MORK_PROVIDE_STDLIB*/
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkConfig.h b/components/mork/src/morkConfig.h
new file mode 100644
index 000000000..dbd4ebdf5
--- /dev/null
+++ b/components/mork/src/morkConfig.h
@@ -0,0 +1,148 @@
+/* -*- 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/. */
+
+#ifndef _MORKCONFIG_
+#define _MORKCONFIG_ 1
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// { %%%%% begin debug mode options in Mork %%%%%
+#define MORK_DEBUG 1
+// } %%%%% end debug mode options in Mork %%%%%
+
+#ifdef MORK_DEBUG
+#define MORK_MAX_CODE_COMPILE 1
+#endif
+
+// { %%%%% begin platform defs peculiar to Mork %%%%%
+
+#ifdef XP_WIN
+#define MORK_WIN 1
+#endif
+
+#ifdef XP_UNIX
+#define MORK_UNIX 1
+#endif
+
+// } %%%%% end platform defs peculiar to Mork %%%%%
+
+#if defined(MORK_WIN) || defined(MORK_UNIX)
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* for SEEK_SET, SEEK_END */
+#endif
+
+#include "nsDebug.h"
+
+#define MORK_ISPRINT(c) isprint(c)
+
+#define MORK_FILETELL(file) ftell(file)
+#define MORK_FILESEEK(file, where, how) fseek(file, where, how)
+#define MORK_FILEREAD(outbuf, insize, file) fread(outbuf, 1, insize, file)
+#if defined(MORK_WIN)
+void mork_fileflush(FILE * file);
+#define MORK_FILEFLUSH(file) mork_fileflush(file)
+#else
+#define MORK_FILEFLUSH(file) fflush(file)
+#endif /*MORK_WIN*/
+
+#define MORK_FILEOPEN(file, how) fopen(file, how)
+#define MORK_FILECLOSE(file) fclose(file)
+#endif /*MORK_WIN*/
+
+/* ===== separating switchable features ===== */
+
+#define MORK_ENABLE_ZONE_ARENAS 1 /* using morkZone for pooling */
+
+//#define MORK_ENABLE_PROBE_MAPS 1 /* use smaller hash tables */
+
+#define MORK_BEAD_OVER_NODE_MAPS 1 /* use bead not node maps */
+
+/* ===== pooling ===== */
+
+#if defined(HAVE_64BIT_BUILD)
+#define MORK_CONFIG_ALIGN_8 1 /* must have 8 byte alignment */
+#else
+#define MORK_CONFIG_PTR_SIZE_4 1 /* sizeof(void*) == 4 */
+#endif
+
+// #define MORK_DEBUG_HEAP_STATS 1 /* analyze per-block heap usage */
+
+/* ===== ===== ===== ===== line characters ===== ===== ===== ===== */
+#define mork_kCR 0x0D
+#define mork_kLF 0x0A
+#define mork_kVTAB '\013'
+#define mork_kFF '\014'
+#define mork_kTAB '\011'
+#define mork_kCRLF "\015\012" /* A CR LF equivalent string */
+
+#if defined(MORK_WIN)
+# define mork_kNewline "\015\012"
+# define mork_kNewlineSize 2
+#else
+# if defined(MORK_UNIX)
+# define mork_kNewline "\012"
+# define mork_kNewlineSize 1
+# endif /* MORK_UNIX */
+#endif /* MORK_WIN */
+
+// { %%%%% begin assertion macro %%%%%
+extern void mork_assertion_signal(const char* inMessage);
+#define MORK_ASSERTION_SIGNAL(Y) mork_assertion_signal(Y)
+#define MORK_ASSERT(X) if (!(X)) MORK_ASSERTION_SIGNAL(#X)
+// } %%%%% end assertion macro %%%%%
+
+#define MORK_LIB(return) return /*API return declaration*/
+#define MORK_LIB_IMPL(return) return /*implementation return declaration*/
+
+// { %%%%% begin standard c utility methods %%%%%
+
+#if defined(MORK_WIN) || defined(MORK_UNIX)
+#define MORK_USE_C_STDLIB 1
+#endif /*MORK_WIN*/
+
+#ifdef MORK_USE_C_STDLIB
+#define MORK_MEMCMP(src1,src2,size) memcmp(src1,src2,size)
+#define MORK_MEMCPY(dest,src,size) memcpy(dest,src,size)
+#define MORK_MEMMOVE(dest,src,size) memmove(dest,src,size)
+#define MORK_MEMSET(dest,byte,size) memset(dest,byte,size)
+#define MORK_STRCPY(dest,src) strcpy(dest,src)
+#define MORK_STRCMP(one,two) strcmp(one,two)
+#define MORK_STRNCMP(one,two,length) strncmp(one,two,length)
+#define MORK_STRLEN(string) strlen(string)
+#endif /*MORK_USE_C_STDLIB*/
+
+#ifdef MORK_PROVIDE_STDLIB
+MORK_LIB(mork_i4) mork_memcmp(const void* a, const void* b, mork_size inSize);
+MORK_LIB(void) mork_memcpy(void* dst, const void* src, mork_size inSize);
+MORK_LIB(void) mork_memmove(void* dst, const void* src, mork_size inSize);
+MORK_LIB(void) mork_memset(void* dst, int inByte, mork_size inSize);
+MORK_LIB(void) mork_strcpy(void* dst, const void* src);
+MORK_LIB(mork_i4) mork_strcmp(const void* a, const void* b);
+MORK_LIB(mork_i4) mork_strncmp(const void* a, const void* b, mork_size inSize);
+MORK_LIB(mork_size) mork_strlen(const void* inString);
+
+#define MORK_MEMCMP(src1,src2,size) mork_memcmp(src1,src2,size)
+#define MORK_MEMCPY(dest,src,size) mork_memcpy(dest,src,size)
+#define MORK_MEMMOVE(dest,src,size) mork_memmove(dest,src,size)
+#define MORK_MEMSET(dest,byte,size) mork_memset(dest,byte,size)
+#define MORK_STRCPY(dest,src) mork_strcpy(dest,src)
+#define MORK_STRCMP(one,two) mork_strcmp(one,two)
+#define MORK_STRNCMP(one,two,length) mork_strncmp(one,two,length)
+#define MORK_STRLEN(string) mork_strlen(string)
+#endif /*MORK_PROVIDE_STDLIB*/
+
+// } %%%%% end standard c utility methods %%%%%
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKCONFIG_ */
+
diff --git a/components/mork/src/morkCursor.cpp b/components/mork/src/morkCursor.cpp
new file mode 100644
index 000000000..f6aa59297
--- /dev/null
+++ b/components/mork/src/morkCursor.cpp
@@ -0,0 +1,201 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKCURSOR_
+#include "morkCursor.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkCursor::CloseMorkNode(morkEnv* ev) // CloseCursor() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseCursor(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkCursor::~morkCursor() // assert CloseCursor() executed earlier
+{
+}
+
+/*public non-poly*/
+morkCursor::morkCursor(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap)
+: morkObject(ev, inUsage, ioHeap, morkColor_kNone, (morkHandle*) 0)
+, mCursor_Seed( 0 )
+, mCursor_Pos( -1 )
+, mCursor_DoFailOnSeedOutOfSync( morkBool_kFalse )
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kCursor;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkCursor, morkObject, nsIMdbCursor)
+
+/*public non-poly*/ void
+morkCursor::CloseCursor(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ mCursor_Seed = 0;
+ mCursor_Pos = -1;
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// { ----- begin ref counting for well-behaved cyclic graphs -----
+NS_IMETHODIMP
+morkCursor::GetWeakRefCount(nsIMdbEnv* mev, // weak refs
+ mdb_count* outCount)
+{
+ *outCount = WeakRefsOnly();
+ return NS_OK;
+}
+NS_IMETHODIMP
+morkCursor::GetStrongRefCount(nsIMdbEnv* mev, // strong refs
+ mdb_count* outCount)
+{
+ *outCount = StrongRefsOnly();
+ return NS_OK;
+}
+// ### TODO - clean up this cast, if required
+NS_IMETHODIMP
+morkCursor::AddWeakRef(nsIMdbEnv* mev)
+{
+ // XXX Casting mork_refs to nsresult
+ return static_cast<nsresult>(morkNode::AddWeakRef((morkEnv *) mev));
+}
+
+#ifndef _MSC_VER
+NS_IMETHODIMP_(mork_uses)
+morkCursor::AddStrongRef(morkEnv* mev)
+{
+ return morkNode::AddStrongRef(mev);
+}
+#endif
+
+NS_IMETHODIMP_(mork_uses)
+morkCursor::AddStrongRef(nsIMdbEnv* mev)
+{
+ return morkNode::AddStrongRef((morkEnv *) mev);
+}
+
+NS_IMETHODIMP
+morkCursor::CutWeakRef(nsIMdbEnv* mev)
+{
+ // XXX Casting mork_refs to nsresult
+ return static_cast<nsresult>(morkNode::CutWeakRef((morkEnv *) mev));
+}
+
+#ifndef _MSC_VER
+NS_IMETHODIMP_(mork_uses)
+morkCursor::CutStrongRef(morkEnv* mev)
+{
+ return morkNode::CutStrongRef(mev);
+}
+#endif
+
+NS_IMETHODIMP
+morkCursor::CutStrongRef(nsIMdbEnv* mev)
+{
+ // XXX Casting mork_uses to nsresult
+ return static_cast<nsresult>(morkNode::CutStrongRef((morkEnv *) mev));
+}
+
+NS_IMETHODIMP
+morkCursor::CloseMdbObject(nsIMdbEnv* mev)
+{
+ return morkNode::CloseMdbObject((morkEnv *) mev);
+}
+
+NS_IMETHODIMP
+morkCursor::IsOpenMdbObject(nsIMdbEnv* mev, mdb_bool* outOpen)
+{
+ *outOpen = IsOpenNode();
+ return NS_OK;
+}
+NS_IMETHODIMP
+morkCursor::IsFrozenMdbObject(nsIMdbEnv* mev, mdb_bool* outIsReadonly)
+{
+ *outIsReadonly = IsFrozen();
+ return NS_OK;
+}
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+NS_IMETHODIMP
+morkCursor::GetCount(nsIMdbEnv* mev, mdb_count* outCount)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkCursor::GetSeed(nsIMdbEnv* mev, mdb_seed* outSeed)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkCursor::SetPos(nsIMdbEnv* mev, mdb_pos inPos)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkCursor::GetPos(nsIMdbEnv* mev, mdb_pos* outPos)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkCursor::SetDoFailOnSeedOutOfSync(nsIMdbEnv* mev, mdb_bool inFail)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkCursor::GetDoFailOnSeedOutOfSync(nsIMdbEnv* mev, mdb_bool* outFail)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkCursor.h b/components/mork/src/morkCursor.h
new file mode 100644
index 000000000..5b3518f95
--- /dev/null
+++ b/components/mork/src/morkCursor.h
@@ -0,0 +1,127 @@
+/* -*- 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/. */
+
+#ifndef _MORKCURSOR_
+#define _MORKCURSOR_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kCursor /*i*/ 0x4375 /* ascii 'Cu' */
+
+class morkCursor : public morkObject, public nsIMdbCursor{ // collection iterator
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // mork_color mBead_Color; // ID for this bead
+ // morkHandle* mObject_Handle; // weak ref to handle for this object
+
+public: // state is public because the entire Mork system is private
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD IsFrozenMdbObject(nsIMdbEnv* ev, mdb_bool* outIsReadonly) override;
+ // same as nsIMdbPort::GetIsPortReadonly() when this object is inside a port.
+ // } ----- end attribute methods -----
+
+ // { ----- begin ref counting for well-behaved cyclic graphs -----
+ NS_IMETHOD GetWeakRefCount(nsIMdbEnv* ev, // weak refs
+ mdb_count* outCount) override;
+ NS_IMETHOD GetStrongRefCount(nsIMdbEnv* ev, // strong refs
+ mdb_count* outCount) override;
+
+ NS_IMETHOD AddWeakRef(nsIMdbEnv* ev) override;
+#ifndef _MSC_VER
+ // The first declaration of AddStrongRef is to suppress -Werror,-Woverloaded-virtual.
+ NS_IMETHOD_(mork_uses) AddStrongRef(morkEnv* ev) override;
+#endif
+ NS_IMETHOD_(mork_uses) AddStrongRef(nsIMdbEnv* ev) override;
+
+ NS_IMETHOD CutWeakRef(nsIMdbEnv* ev) override;
+#ifndef _MSC_VER
+ // The first declaration of CutStrongRef is to suppress -Werror,-Woverloaded-virtual.
+ NS_IMETHOD_(mork_uses) CutStrongRef(morkEnv* ev) override;
+#endif
+ NS_IMETHOD CutStrongRef(nsIMdbEnv* ev) override;
+
+ NS_IMETHOD CloseMdbObject(nsIMdbEnv* ev) override; // called at strong refs zero
+ NS_IMETHOD IsOpenMdbObject(nsIMdbEnv* ev, mdb_bool* outOpen) override;
+ // } ----- end ref counting -----
+
+// } ===== end nsIMdbObject methods =====
+
+// { ===== begin nsIMdbCursor methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD GetCount(nsIMdbEnv* ev, mdb_count* outCount) override; // readonly
+ NS_IMETHOD GetSeed(nsIMdbEnv* ev, mdb_seed* outSeed) override; // readonly
+
+ NS_IMETHOD SetPos(nsIMdbEnv* ev, mdb_pos inPos) override; // mutable
+ NS_IMETHOD GetPos(nsIMdbEnv* ev, mdb_pos* outPos) override;
+
+ NS_IMETHOD SetDoFailOnSeedOutOfSync(nsIMdbEnv* ev, mdb_bool inFail) override;
+ NS_IMETHOD GetDoFailOnSeedOutOfSync(nsIMdbEnv* ev, mdb_bool* outFail) override;
+ // } ----- end attribute methods -----
+
+// } ===== end nsIMdbCursor methods =====
+
+ // } ----- end attribute methods -----
+
+ mork_seed mCursor_Seed;
+ mork_pos mCursor_Pos;
+ mork_bool mCursor_DoFailOnSeedOutOfSync;
+ mork_u1 mCursor_Pad[ 3 ]; // explicitly pad to u4 alignment
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseCursor() only if open
+
+public: // morkCursor construction & destruction
+ morkCursor(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap);
+ void CloseCursor(morkEnv* ev); // called by CloseMorkNode();
+
+protected:
+ virtual ~morkCursor(); // assert that CloseCursor() executed earlier
+
+private: // copying is not allowed
+ morkCursor(const morkCursor& other);
+ morkCursor& operator=(const morkCursor& other);
+
+public: // dynamic type identification
+ mork_bool IsCursor() const
+ { return IsNode() && mNode_Derived == morkDerived_kCursor; }
+// } ===== end morkNode methods =====
+
+public: // other cursor methods
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakCursor(morkCursor* me,
+ morkEnv* ev, morkCursor** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongCursor(morkCursor* me,
+ morkEnv* ev, morkCursor** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKCURSOR_ */
diff --git a/components/mork/src/morkDeque.cpp b/components/mork/src/morkDeque.cpp
new file mode 100644
index 000000000..3a380b5d8
--- /dev/null
+++ b/components/mork/src/morkDeque.cpp
@@ -0,0 +1,288 @@
+/*************************************************************************
+This software is part of a public domain IronDoc source code distribution,
+and is provided on an "AS IS" basis, with all risks borne by the consumers
+or users of the IronDoc software. There are no warranties, guarantees, or
+promises about quality of any kind; and no remedies for failure exist.
+
+Permission is hereby granted to use this IronDoc software for any purpose
+at all, without need for written agreements, without royalty or license
+fees, and without fees or obligations of any other kind. Anyone can use,
+copy, change and distribute this software for any purpose, and nothing is
+required, implicitly or otherwise, in exchange for this usage.
+
+You cannot apply your own copyright to this software, but otherwise you
+are encouraged to enjoy the use of this software in any way you see fit.
+However, it would be rude to remove names of developers from the code.
+(IronDoc is also known by the short name "Fe" and a longer name "Ferrum",
+which are used interchangeably with the name IronDoc in the sources.)
+*************************************************************************/
+/*
+ * File: morkDeque.cpp
+ * Contains: Ferrum deque (double ended queue (linked list))
+ *
+ * Copied directly from public domain IronDoc, with minor naming tweaks:
+ * Designed and written by David McCusker, but all this code is public domain.
+ * There are no warranties, no guarantees, no promises, and no remedies.
+ */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKDEQUE_
+#include "morkDeque.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+/*=============================================================================
+ * morkNext: linked list node for very simple, singly-linked list
+ */
+
+morkNext::morkNext() : mNext_Link( 0 )
+{
+}
+
+/*static*/ void*
+morkNext::MakeNewNext(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev)
+{
+ void* next = 0;
+ ioHeap.Alloc(ev->AsMdbEnv(), inSize, (void**) &next);
+ if ( !next )
+ ev->OutOfMemoryError();
+
+ return next;
+}
+
+/*static*/
+void morkNext::ZapOldNext(morkEnv* ev, nsIMdbHeap* ioHeap)
+{
+ if ( ioHeap )
+ {
+ ioHeap->Free(ev->AsMdbEnv(), this);
+ }
+ else
+ ev->NilPointerError();
+}
+
+/*=============================================================================
+ * morkList: simple, singly-linked list
+ */
+
+morkList::morkList() : mList_Head( 0 ), mList_Tail( 0 )
+{
+}
+
+void morkList::CutAndZapAllListMembers(morkEnv* ev, nsIMdbHeap* ioHeap)
+// make empty list, zapping every member by calling ZapOldNext()
+{
+ if ( ioHeap )
+ {
+ morkNext* next = 0;
+ while ( (next = this->PopHead()) != 0 )
+ next->ZapOldNext(ev, ioHeap);
+
+ mList_Head = 0;
+ mList_Tail = 0;
+ }
+ else
+ ev->NilPointerError();
+}
+
+void morkList::CutAllListMembers()
+// just make list empty, dropping members without zapping
+{
+ while ( this->PopHead() )
+ /* empty */;
+
+ mList_Head = 0;
+ mList_Tail = 0;
+}
+
+morkNext* morkList::PopHead() // cut head of list
+{
+ morkNext* outHead = mList_Head;
+ if ( outHead ) // anything to cut from list?
+ {
+ morkNext* next = outHead->mNext_Link;
+ mList_Head = next;
+ if ( !next ) // cut the last member, so tail no longer exists?
+ mList_Tail = 0;
+
+ outHead->mNext_Link = 0; // nil outgoing node link; unnecessary, but tidy
+ }
+ return outHead;
+}
+
+
+void morkList::PushHead(morkNext* ioLink) // add to head of list
+{
+ morkNext* head = mList_Head; // old head of list
+ morkNext* tail = mList_Tail; // old tail of list
+
+ MORK_ASSERT( (head && tail) || (!head && !tail));
+
+ ioLink->mNext_Link = head; // make old head follow the new link
+ if ( !head ) // list was previously empty?
+ mList_Tail = ioLink; // head is also tail for first member added
+
+ mList_Head = ioLink; // head of list is the new link
+}
+
+void morkList::PushTail(morkNext* ioLink) // add to tail of list
+{
+ morkNext* head = mList_Head; // old head of list
+ morkNext* tail = mList_Tail; // old tail of list
+
+ MORK_ASSERT( (head && tail) || (!head && !tail));
+
+ ioLink->mNext_Link = 0;
+ if ( tail )
+ {
+ tail->mNext_Link = ioLink;
+ mList_Tail = ioLink;
+ }
+ else // list was previously empty?
+ mList_Head = mList_Tail = ioLink; // tail is also head for first member added
+}
+
+/*=============================================================================
+ * morkLink: linked list node embedded in objs to allow insertion in morkDeques
+ */
+
+morkLink::morkLink() : mLink_Next( 0 ), mLink_Prev( 0 )
+{
+}
+
+/*static*/ void*
+morkLink::MakeNewLink(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev)
+{
+ void* alink = 0;
+ ioHeap.Alloc(ev->AsMdbEnv(), inSize, (void**) &alink);
+ if ( !alink )
+ ev->OutOfMemoryError();
+
+ return alink;
+}
+
+/*static*/
+void morkLink::ZapOldLink(morkEnv* ev, nsIMdbHeap* ioHeap)
+{
+ if ( ioHeap )
+ {
+ ioHeap->Free(ev->AsMdbEnv(), this);
+ }
+ else
+ ev->NilPointerError();
+}
+
+/*=============================================================================
+ * morkDeque: doubly linked list modeled after VAX queue instructions
+ */
+
+morkDeque::morkDeque()
+{
+ mDeque_Head.SelfRefer();
+}
+
+
+/*| RemoveFirst:
+|*/
+morkLink*
+morkDeque::RemoveFirst() /*i*/
+{
+ morkLink* alink = mDeque_Head.mLink_Next;
+ if ( alink != &mDeque_Head )
+ {
+ (mDeque_Head.mLink_Next = alink->mLink_Next)->mLink_Prev =
+ &mDeque_Head;
+ return alink;
+ }
+ return (morkLink*) 0;
+}
+
+/*| RemoveLast:
+|*/
+morkLink*
+morkDeque::RemoveLast() /*i*/
+{
+ morkLink* alink = mDeque_Head.mLink_Prev;
+ if ( alink != &mDeque_Head )
+ {
+ (mDeque_Head.mLink_Prev = alink->mLink_Prev)->mLink_Next =
+ &mDeque_Head;
+ return alink;
+ }
+ return (morkLink*) 0;
+}
+
+/*| At:
+|*/
+morkLink*
+morkDeque::At(mork_pos index) const /*i*/
+ /* indexes are one based (and not zero based) */
+{
+ mork_num count = 0;
+ morkLink* alink;
+ for ( alink = this->First(); alink; alink = this->After(alink) )
+ {
+ if ( ++count == (mork_num) index )
+ break;
+ }
+ return alink;
+}
+
+/*| IndexOf:
+|*/
+mork_pos
+morkDeque::IndexOf(const morkLink* member) const /*i*/
+ /* indexes are one based (and not zero based) */
+ /* zero means member is not in deque */
+{
+ mork_num count = 0;
+ const morkLink* alink;
+ for ( alink = this->First(); alink; alink = this->After(alink) )
+ {
+ ++count;
+ if ( member == alink )
+ return (mork_pos) count;
+ }
+ return 0;
+}
+
+/*| Length:
+|*/
+mork_num
+morkDeque::Length() const /*i*/
+{
+ mork_num count = 0;
+ morkLink* alink;
+ for ( alink = this->First(); alink; alink = this->After(alink) )
+ ++count;
+ return count;
+}
+
+/*| LengthCompare:
+|*/
+int
+morkDeque::LengthCompare(mork_num c) const /*i*/
+{
+ mork_num count = 0;
+ const morkLink* alink;
+ for ( alink = this->First(); alink; alink = this->After(alink) )
+ {
+ if ( ++count > c )
+ return 1;
+ }
+ return ( count == c )? 0 : -1;
+}
diff --git a/components/mork/src/morkDeque.h b/components/mork/src/morkDeque.h
new file mode 100644
index 000000000..ad38cdbe2
--- /dev/null
+++ b/components/mork/src/morkDeque.h
@@ -0,0 +1,239 @@
+/*************************************************************************
+This software is part of a public domain IronDoc source code distribution,
+and is provided on an "AS IS" basis, with all risks borne by the consumers
+or users of the IronDoc software. There are no warranties, guarantees, or
+promises about quality of any kind; and no remedies for failure exist.
+
+Permission is hereby granted to use this IronDoc software for any purpose
+at all, without need for written agreements, without royalty or license
+fees, and without fees or obligations of any other kind. Anyone can use,
+copy, change and distribute this software for any purpose, and nothing is
+required, implicitly or otherwise, in exchange for this usage.
+
+You cannot apply your own copyright to this software, but otherwise you
+are encouraged to enjoy the use of this software in any way you see fit.
+However, it would be rude to remove names of developers from the code.
+(IronDoc is also known by the short name "Fe" and a longer name "Ferrum",
+which are used interchangeably with the name IronDoc in the sources.)
+*************************************************************************/
+/*
+ * File: morkDeque.h
+ * Contains: Ferrum deque (double ended queue (linked list))
+ *
+ * Copied directly from public domain IronDoc, with minor naming tweaks:
+ * Designed and written by David McCusker, but all this code is public domain.
+ * There are no warranties, no guarantees, no promises, and no remedies.
+ */
+
+#ifndef _MORKDEQUE_
+#define _MORKDEQUE_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+/*=============================================================================
+ * morkNext: linked list node for very simple, singly-linked list
+ */
+
+class morkNext /*d*/ {
+public:
+ morkNext* mNext_Link;
+
+public:
+ explicit morkNext(int inZero) : mNext_Link( 0 ) { }
+
+ explicit morkNext(morkNext* ioLink) : mNext_Link( ioLink ) { }
+
+ morkNext(); // mNext_Link( 0 ), { }
+
+public:
+ morkNext* GetNextLink() const { return mNext_Link; }
+
+public: // link memory management methods
+ static void* MakeNewNext(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev);
+ void ZapOldNext(morkEnv* ev, nsIMdbHeap* ioHeap);
+
+public: // link memory management operators
+ void* operator new(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev) CPP_THROW_NEW
+ { return morkNext::MakeNewNext(inSize, ioHeap, ev); }
+
+ void operator delete(void* ioAddress) // DO NOT CALL THIS, hope to crash:
+ { ((morkNext*) 0)->ZapOldNext((morkEnv*) 0, (nsIMdbHeap*) 0); } // boom
+};
+
+/*=============================================================================
+ * morkList: simple, singly-linked list
+ */
+
+/*| morkList: a list of singly-linked members (instances of morkNext), where
+**| the number of list members might be so numerous that we must about cost
+**| for two pointer link slots per member (as happens with morkLink).
+**|
+**|| morkList is intended to support lists of changes in morkTable, where we
+**| are worried about the space cost of representing such changes. (Later we
+**| can use an array instead, when we get even more worried, to avoid cost
+**| of link slots at all, per member).
+**|
+**|| Do NOT create cycles in links using this list class, since we do not
+**| deal with them very nicely.
+|*/
+class morkList /*d*/ {
+public:
+ morkNext* mList_Head; // first link in the list
+ morkNext* mList_Tail; // last link in the list
+
+public:
+ morkNext* GetListHead() const { return mList_Head; }
+ morkNext* GetListTail() const { return mList_Tail; }
+
+ mork_bool IsListEmpty() const { return ( mList_Head == 0 ); }
+ mork_bool HasListMembers() const { return ( mList_Head != 0 ); }
+
+public:
+ morkList(); // : mList_Head( 0 ), mList_Tail( 0 ) { }
+
+ void CutAndZapAllListMembers(morkEnv* ev, nsIMdbHeap* ioHeap);
+ // make empty list, zapping every member by calling ZapOldNext()
+
+ void CutAllListMembers();
+ // just make list empty, dropping members without zapping
+
+public:
+ morkNext* PopHead(); // cut head of list
+
+ // Note we don't support PopTail(), so use morkDeque if you need that.
+
+ void PushHead(morkNext* ioLink); // add to head of list
+ void PushTail(morkNext* ioLink); // add to tail of list
+};
+
+/*=============================================================================
+ * morkLink: linked list node embedded in objs to allow insertion in morkDeques
+ */
+
+class morkLink /*d*/ {
+public:
+ morkLink* mLink_Next;
+ morkLink* mLink_Prev;
+
+public:
+ explicit morkLink(int inZero) : mLink_Next( 0 ), mLink_Prev( 0 ) { }
+
+ morkLink(); // mLink_Next( 0 ), mLink_Prev( 0 ) { }
+
+public:
+ morkLink* Next() const { return mLink_Next; }
+ morkLink* Prev() const { return mLink_Prev; }
+
+ void SelfRefer() { mLink_Next = mLink_Prev = this; }
+ void Clear() { mLink_Next = mLink_Prev = 0; }
+
+ void AddBefore(morkLink* old)
+ {
+ ((old)->mLink_Prev->mLink_Next = (this))->mLink_Prev = (old)->mLink_Prev;
+ ((this)->mLink_Next = (old))->mLink_Prev = this;
+ }
+
+ void AddAfter(morkLink* old)
+ {
+ ((old)->mLink_Next->mLink_Prev = (this))->mLink_Next = (old)->mLink_Next;
+ ((this)->mLink_Prev = (old))->mLink_Next = this;
+ }
+
+ void Remove()
+ {
+ (mLink_Prev->mLink_Next = mLink_Next)->mLink_Prev = mLink_Prev;
+ }
+
+public: // link memory management methods
+ static void* MakeNewLink(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev);
+ void ZapOldLink(morkEnv* ev, nsIMdbHeap* ioHeap);
+
+public: // link memory management operators
+ void* operator new(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev) CPP_THROW_NEW
+ { return morkLink::MakeNewLink(inSize, ioHeap, ev); }
+
+};
+
+/*=============================================================================
+ * morkDeque: doubly linked list modeled after VAX queue instructions
+ */
+
+class morkDeque /*d*/ {
+public:
+ morkLink mDeque_Head;
+
+public: // construction
+ morkDeque(); // { mDeque_Head.SelfRefer(); }
+
+public:// methods
+ morkLink* RemoveFirst();
+
+ morkLink* RemoveLast();
+
+ morkLink* At(mork_pos index) const ; /* one-based, not zero-based */
+
+ mork_pos IndexOf(const morkLink* inMember) const;
+ /* one-based index ; zero means member is not in deque */
+
+ mork_num Length() const;
+
+ /* the following method is more efficient for long lists: */
+ int LengthCompare(mork_num inCount) const;
+ /* -1: length < count, 0: length == count, 1: length > count */
+
+public: // inlines
+
+ mork_bool IsEmpty()const
+ { return (mDeque_Head.mLink_Next == (morkLink*) &mDeque_Head); }
+
+ morkLink* After(const morkLink* old) const
+ { return (((old)->mLink_Next != &mDeque_Head)?
+ (old)->mLink_Next : (morkLink*) 0); }
+
+ morkLink* Before(const morkLink* old) const
+ { return (((old)->mLink_Prev != &mDeque_Head)?
+ (old)->mLink_Prev : (morkLink*) 0); }
+
+ morkLink* First() const
+ { return ((mDeque_Head.mLink_Next != &mDeque_Head)?
+ mDeque_Head.mLink_Next : (morkLink*) 0); }
+
+ morkLink* Last() const
+ { return ((mDeque_Head.mLink_Prev != &mDeque_Head)?
+ mDeque_Head.mLink_Prev : (morkLink*) 0); }
+
+/*
+From IronDoc documentation for AddFirst:
++--------+ +--------+ +--------+ +--------+ +--------+
+| h.next |-->| b.next | | h.next |-->| a.next |-->| b.next |
++--------+ +--------+ ==> +--------+ +--------+ +--------+
+| h.prev |<--| b.prev | | h.prev |<--| a.prev |<--| b.prev |
++--------+ +--------+ +--------+ +--------+ +--------+
+*/
+
+ void AddFirst(morkLink* in) /*i*/
+ {
+ ( (mDeque_Head.mLink_Next->mLink_Prev =
+ (in))->mLink_Next = mDeque_Head.mLink_Next,
+ ((in)->mLink_Prev = &mDeque_Head)->mLink_Next = (in) );
+ }
+/*
+From IronDoc documentation for AddLast:
++--------+ +--------+ +--------+ +--------+ +--------+
+| y.next |-->| h.next | | y.next |-->| z.next |-->| h.next |
++--------+ +--------+ ==> +--------+ +--------+ +--------+
+| y.prev |<--| h.prev | | y.prev |<--| z.prev |<--| h.prev |
++--------+ +--------+ +--------+ +--------+ +--------+
+*/
+
+ void AddLast(morkLink* in)
+ {
+ ( (mDeque_Head.mLink_Prev->mLink_Next =
+ (in))->mLink_Prev = mDeque_Head.mLink_Prev,
+ ((in)->mLink_Next = &mDeque_Head)->mLink_Prev = (in) );
+ }
+};
+
+#endif /* _MORKDEQUE_ */
diff --git a/components/mork/src/morkEnv.cpp b/components/mork/src/morkEnv.cpp
new file mode 100644
index 000000000..083942f67
--- /dev/null
+++ b/components/mork/src/morkEnv.cpp
@@ -0,0 +1,615 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKCH_
+#include "morkCh.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKFACTORY_
+#include "morkFactory.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkEnv::CloseMorkNode(morkEnv* ev) /*i*/ // CloseEnv() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseEnv(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkEnv::~morkEnv() /*i*/ // assert CloseEnv() executed earlier
+{
+ CloseMorkNode(mMorkEnv);
+ if (mEnv_Heap)
+ {
+ mork_bool ownsHeap = mEnv_OwnsHeap;
+ nsIMdbHeap*saveHeap = mEnv_Heap;
+
+ if (ownsHeap)
+ {
+#ifdef MORK_DEBUG_HEAP_STATS
+ printf("%d blocks remaining \n", ((orkinHeap *) saveHeap)->HeapBlockCount());
+ mork_u4* array = (mork_u4*) this;
+ array -= 3;
+ // null out heap ptr in mem block so we won't crash trying to use it to
+ // delete the env.
+ *array = nullptr;
+#endif // MORK_DEBUG_HEAP_STATS
+ // whoops, this is our heap - hmm. Can't delete it, or not allocate env's from
+ // an orkinHeap.
+ delete saveHeap;
+ }
+
+ }
+// MORK_ASSERT(mEnv_SelfAsMdbEnv==0);
+ MORK_ASSERT(mEnv_ErrorHook==0);
+}
+
+/* choose morkBool_kTrue or morkBool_kFalse for kBeVerbose: */
+#define morkEnv_kBeVerbose morkBool_kFalse
+
+/*public non-poly*/
+morkEnv::morkEnv(const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ morkFactory* ioFactory, nsIMdbHeap* ioSlotHeap)
+: morkObject(inUsage, ioHeap, morkColor_kNone)
+, mEnv_Factory( ioFactory )
+, mEnv_Heap( ioSlotHeap )
+
+, mEnv_SelfAsMdbEnv( 0 )
+, mEnv_ErrorHook( 0 )
+, mEnv_HandlePool( 0 )
+
+, mEnv_ErrorCount( 0 )
+, mEnv_WarningCount( 0 )
+
+, mEnv_ErrorCode(NS_OK)
+
+, mEnv_DoTrace( morkBool_kFalse )
+, mEnv_AutoClear( morkAble_kDisabled )
+, mEnv_ShouldAbort( morkBool_kFalse )
+, mEnv_BeVerbose( morkEnv_kBeVerbose )
+, mEnv_OwnsHeap ( morkBool_kFalse )
+{
+ MORK_ASSERT(ioSlotHeap && ioFactory );
+ if ( ioSlotHeap )
+ {
+ // mEnv_Heap is NOT refcounted:
+ // nsIMdbHeap_SlotStrongHeap(ioSlotHeap, this, &mEnv_Heap);
+
+ mEnv_HandlePool = new morkPool(morkUsage::kGlobal,
+ (nsIMdbHeap*) 0, ioSlotHeap);
+
+ MORK_ASSERT(mEnv_HandlePool);
+ if ( mEnv_HandlePool && this->Good() )
+ {
+ mNode_Derived = morkDerived_kEnv;
+ mNode_Refs += morkEnv_kWeakRefCountEnvBonus;
+ }
+ }
+}
+
+/*public non-poly*/
+morkEnv::morkEnv(morkEnv* ev, /*i*/
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap, nsIMdbEnv* inSelfAsMdbEnv,
+ morkFactory* ioFactory, nsIMdbHeap* ioSlotHeap)
+: morkObject(ev, inUsage, ioHeap, morkColor_kNone, (morkHandle*) 0)
+, mEnv_Factory( ioFactory )
+, mEnv_Heap( ioSlotHeap )
+
+, mEnv_SelfAsMdbEnv( inSelfAsMdbEnv )
+, mEnv_ErrorHook( 0 )
+, mEnv_HandlePool( 0 )
+
+, mEnv_ErrorCount( 0 )
+, mEnv_WarningCount( 0 )
+
+, mEnv_ErrorCode(NS_OK)
+
+, mEnv_DoTrace( morkBool_kFalse )
+, mEnv_AutoClear( morkAble_kDisabled )
+, mEnv_ShouldAbort( morkBool_kFalse )
+, mEnv_BeVerbose( morkEnv_kBeVerbose )
+, mEnv_OwnsHeap ( morkBool_kFalse )
+{
+ // $$$ do we need to refcount the inSelfAsMdbEnv nsIMdbEnv??
+
+ if ( ioFactory && inSelfAsMdbEnv && ioSlotHeap)
+ {
+ // mEnv_Heap is NOT refcounted:
+ // nsIMdbHeap_SlotStrongHeap(ioSlotHeap, ev, &mEnv_Heap);
+
+ mEnv_HandlePool = new(*ioSlotHeap, ev) morkPool(ev,
+ morkUsage::kHeap, ioSlotHeap, ioSlotHeap);
+
+ MORK_ASSERT(mEnv_HandlePool);
+ if ( mEnv_HandlePool && ev->Good() )
+ {
+ mNode_Derived = morkDerived_kEnv;
+ mNode_Refs += morkEnv_kWeakRefCountEnvBonus;
+ }
+ }
+ else
+ ev->NilPointerError();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkEnv, morkObject, nsIMdbEnv)
+/*public non-poly*/ void
+morkEnv::CloseEnv(morkEnv* ev) /*i*/ // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ // $$$ release mEnv_SelfAsMdbEnv??
+ // $$$ release mEnv_ErrorHook??
+
+ mEnv_SelfAsMdbEnv = 0;
+ mEnv_ErrorHook = 0;
+
+ morkPool* savePool = mEnv_HandlePool;
+ morkPool::SlotStrongPool((morkPool*) 0, ev, &mEnv_HandlePool);
+ // free the pool
+ if (mEnv_SelfAsMdbEnv)
+ {
+ if (savePool && mEnv_Heap)
+ mEnv_Heap->Free(this->AsMdbEnv(), savePool);
+ }
+ else
+ {
+ if (savePool)
+ {
+ if (savePool->IsOpenNode())
+ savePool->CloseMorkNode(ev);
+ delete savePool;
+ }
+ // how do we free this? might need to get rid of asserts.
+ }
+ // mEnv_Factory is NOT refcounted
+
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+mork_size
+morkEnv::OidAsHex(void* outBuf, const mdbOid& inOid)
+// sprintf(buf, "%lX:^%lX", (long) inOid.mOid_Id, (long) inOid.mOid_Scope);
+{
+ mork_u1* p = (mork_u1*) outBuf;
+ mork_size outSize = this->TokenAsHex(p, inOid.mOid_Id);
+ p += outSize;
+ *p++ = ':';
+
+ mork_scope scope = inOid.mOid_Scope;
+ if ( scope < 0x80 && morkCh_IsName((mork_ch) scope) )
+ {
+ *p++ = (mork_u1) scope;
+ *p = 0; // null termination
+ outSize += 2;
+ }
+ else
+ {
+ *p++ = '^';
+ mork_size scopeSize = this->TokenAsHex(p, scope);
+ outSize += scopeSize + 2;
+ }
+ return outSize;
+}
+
+
+mork_u1
+morkEnv::HexToByte(mork_ch inFirstHex, mork_ch inSecondHex)
+{
+ mork_u1 hi = 0; // high four hex bits
+ mork_flags f = morkCh_GetFlags(inFirstHex);
+ if ( morkFlags_IsDigit(f) )
+ hi = (mork_u1) (inFirstHex - (mork_ch) '0');
+ else if ( morkFlags_IsUpper(f) )
+ hi = (mork_u1) ((inFirstHex - (mork_ch) 'A') + 10);
+ else if ( morkFlags_IsLower(f) )
+ hi = (mork_u1) ((inFirstHex - (mork_ch) 'a') + 10);
+
+ mork_u1 lo = 0; // low four hex bits
+ f = morkCh_GetFlags(inSecondHex);
+ if ( morkFlags_IsDigit(f) )
+ lo = (mork_u1) (inSecondHex - (mork_ch) '0');
+ else if ( morkFlags_IsUpper(f) )
+ lo = (mork_u1) ((inSecondHex - (mork_ch) 'A') + 10);
+ else if ( morkFlags_IsLower(f) )
+ lo = (mork_u1) ((inSecondHex - (mork_ch) 'a') + 10);
+
+ return (mork_u1) ((hi << 4) | lo);
+}
+
+mork_size
+morkEnv::TokenAsHex(void* outBuf, mork_token inToken)
+ // TokenAsHex() is the same as sprintf(outBuf, "%lX", (long) inToken);
+{
+ static const char morkEnv_kHexDigits[] = "0123456789ABCDEF";
+ char* p = (char*) outBuf;
+ char* end = p + 32; // write no more than 32 digits for safety
+ if ( inToken )
+ {
+ // first write all the hex digits in backwards order:
+ while ( p < end && inToken ) // more digits to write?
+ {
+ *p++ = morkEnv_kHexDigits[ inToken & 0x0F ]; // low four bits
+ inToken >>= 4; // we fervently hope this does not sign extend
+ }
+ *p = 0; // end the string with a null byte
+ char* s = (char*) outBuf; // first byte in string
+ mork_size size = (mork_size) (p - s); // distance from start
+
+ // now reverse the string in place:
+ // note that p starts on the null byte, so we need predecrement:
+ while ( --p > s ) // need to swap another byte in the string?
+ {
+ char c = *p; // temp for swap
+ *p = *s;
+ *s++ = c; // move s forward here, and p backward in the test
+ }
+ return size;
+ }
+ else // special case for zero integer
+ {
+ *p++ = '0'; // write a zero digit
+ *p = 0; // end with a null byte
+ return 1; // one digit in hex representation
+ }
+}
+
+void
+morkEnv::StringToYarn(const char* inString, mdbYarn* outYarn)
+{
+ if ( outYarn )
+ {
+ mdb_fill fill = ( inString )? (mdb_fill) MORK_STRLEN(inString) : 0;
+
+ if ( fill ) // have nonempty content?
+ {
+ mdb_size size = outYarn->mYarn_Size; // max dest size
+ if ( fill > size ) // too much string content?
+ {
+ outYarn->mYarn_More = fill - size; // extra string bytes omitted
+ fill = size; // copy no more bytes than size of yarn buffer
+ }
+ void* dest = outYarn->mYarn_Buf; // where bytes are going
+ if ( !dest ) // nil destination address buffer?
+ fill = 0; // we can't write any content at all
+
+ if ( fill ) // anything to copy?
+ MORK_MEMCPY(dest, inString, fill); // copy fill bytes to yarn
+
+ outYarn->mYarn_Fill = fill; // tell yarn size of copied content
+ }
+ else // no content to put into the yarn
+ {
+ outYarn->mYarn_Fill = 0; // tell yarn that string has no bytes
+ }
+ outYarn->mYarn_Form = 0; // always update the form slot
+ }
+ else
+ this->NilPointerError();
+}
+
+char*
+morkEnv::CopyString(nsIMdbHeap* ioHeap, const char* inString)
+{
+ char* outString = 0;
+ if ( ioHeap && inString )
+ {
+ mork_size size = MORK_STRLEN(inString) + 1;
+ ioHeap->Alloc(this->AsMdbEnv(), size, (void**) &outString);
+ if ( outString )
+ MORK_STRCPY(outString, inString);
+ }
+ else
+ this->NilPointerError();
+ return outString;
+}
+
+void
+morkEnv::FreeString(nsIMdbHeap* ioHeap, char* ioString)
+{
+ if ( ioHeap )
+ {
+ if ( ioString )
+ ioHeap->Free(this->AsMdbEnv(), ioString);
+ }
+ else
+ this->NilPointerError();
+}
+
+void
+morkEnv::NewError(const char* inString)
+{
+ MORK_ASSERT(morkBool_kFalse); // get developer's attention
+
+ ++mEnv_ErrorCount;
+ mEnv_ErrorCode = NS_ERROR_FAILURE;
+
+ if ( mEnv_ErrorHook )
+ mEnv_ErrorHook->OnErrorString(this->AsMdbEnv(), inString);
+}
+
+void
+morkEnv::NewWarning(const char* inString)
+{
+ MORK_ASSERT(morkBool_kFalse); // get developer's attention
+
+ ++mEnv_WarningCount;
+ if ( mEnv_ErrorHook )
+ mEnv_ErrorHook->OnWarningString(this->AsMdbEnv(), inString);
+}
+
+void
+morkEnv::StubMethodOnlyError()
+{
+ this->NewError("method is stub only");
+}
+
+void
+morkEnv::OutOfMemoryError()
+{
+ this->NewError("out of memory");
+}
+
+void
+morkEnv::CantMakeWhenBadError()
+{
+ this->NewError("can't make an object when ev->Bad()");
+}
+
+static const char morkEnv_kNilPointer[] = "nil pointer";
+
+void
+morkEnv::NilPointerError()
+{
+ this->NewError(morkEnv_kNilPointer);
+}
+
+void
+morkEnv::NilPointerWarning()
+{
+ this->NewWarning(morkEnv_kNilPointer);
+}
+
+void
+morkEnv::NewNonEnvError()
+{
+ this->NewError("non-env instance");
+}
+
+void
+morkEnv::NilEnvSlotError()
+{
+ if ( !mEnv_HandlePool || !mEnv_Factory )
+ {
+ if ( !mEnv_HandlePool )
+ this->NewError("nil mEnv_HandlePool");
+ if ( !mEnv_Factory )
+ this->NewError("nil mEnv_Factory");
+ }
+ else
+ this->NewError("unknown nil env slot");
+}
+
+
+void morkEnv::NonEnvTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkEnv");
+}
+
+void
+morkEnv::ClearMorkErrorsAndWarnings()
+{
+ mEnv_ErrorCount = 0;
+ mEnv_WarningCount = 0;
+ mEnv_ErrorCode = NS_OK;
+ mEnv_ShouldAbort = morkBool_kFalse;
+}
+
+void
+morkEnv::AutoClearMorkErrorsAndWarnings()
+{
+ if ( this->DoAutoClear() )
+ {
+ mEnv_ErrorCount = 0;
+ mEnv_WarningCount = 0;
+ mEnv_ErrorCode = NS_OK;
+ mEnv_ShouldAbort = morkBool_kFalse;
+ }
+}
+
+/*static*/ morkEnv*
+morkEnv::FromMdbEnv(nsIMdbEnv* ioEnv) // dynamic type checking
+{
+ morkEnv* outEnv = 0;
+ if ( ioEnv )
+ {
+ // Note this cast is expected to perform some address adjustment of the
+ // pointer, so oenv likely does not equal ioEnv. Do not cast to void*
+ // first to force an exactly equal pointer (we tried it and it's wrong).
+ morkEnv* ev = (morkEnv*) ioEnv;
+ if ( ev && ev->IsEnv() )
+ {
+ if ( ev->DoAutoClear() )
+ {
+ ev->mEnv_ErrorCount = 0;
+ ev->mEnv_WarningCount = 0;
+ ev->mEnv_ErrorCode = NS_OK;
+ }
+ outEnv = ev;
+ }
+ else
+ MORK_ASSERT(outEnv);
+ }
+ else
+ MORK_ASSERT(outEnv);
+ return outEnv;
+}
+
+
+NS_IMETHODIMP
+morkEnv::GetErrorCount(mdb_count* outCount,
+ mdb_bool* outShouldAbort)
+{
+ if ( outCount )
+ *outCount = mEnv_ErrorCount;
+ if ( outShouldAbort )
+ *outShouldAbort = mEnv_ShouldAbort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::GetWarningCount(mdb_count* outCount,
+ mdb_bool* outShouldAbort)
+{
+ if ( outCount )
+ *outCount = mEnv_WarningCount;
+ if ( outShouldAbort )
+ *outShouldAbort = mEnv_ShouldAbort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::GetEnvBeVerbose(mdb_bool* outBeVerbose)
+{
+ NS_ENSURE_ARG_POINTER(outBeVerbose);
+ *outBeVerbose = mEnv_BeVerbose;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::SetEnvBeVerbose(mdb_bool inBeVerbose)
+{
+ mEnv_BeVerbose = inBeVerbose;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::GetDoTrace(mdb_bool* outDoTrace)
+{
+ NS_ENSURE_ARG_POINTER(outDoTrace);
+ *outDoTrace = mEnv_DoTrace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::SetDoTrace(mdb_bool inDoTrace)
+{
+ mEnv_DoTrace = inDoTrace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::GetAutoClear(mdb_bool* outAutoClear)
+{
+ NS_ENSURE_ARG_POINTER(outAutoClear);
+ *outAutoClear = DoAutoClear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::SetAutoClear(mdb_bool inAutoClear)
+{
+ if ( inAutoClear )
+ EnableAutoClear();
+ else
+ DisableAutoClear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::GetErrorHook(nsIMdbErrorHook** acqErrorHook)
+{
+ NS_ENSURE_ARG_POINTER(acqErrorHook);
+ *acqErrorHook = mEnv_ErrorHook;
+ NS_IF_ADDREF(mEnv_ErrorHook);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::SetErrorHook(
+ nsIMdbErrorHook* ioErrorHook) // becomes referenced
+{
+ mEnv_ErrorHook = ioErrorHook;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::GetHeap(nsIMdbHeap** acqHeap)
+{
+ NS_ENSURE_ARG_POINTER(acqHeap);
+ nsIMdbHeap* outHeap = mEnv_Heap;
+
+ if ( acqHeap )
+ *acqHeap = outHeap;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::SetHeap(
+ nsIMdbHeap* ioHeap) // becomes referenced
+{
+ nsIMdbHeap_SlotStrongHeap(ioHeap, this, &mEnv_Heap);
+ return NS_OK;
+}
+// } ----- end attribute methods -----
+
+NS_IMETHODIMP
+morkEnv::ClearErrors() // clear errors beore re-entering db API
+{
+ mEnv_ErrorCount = 0;
+ mEnv_ErrorCode = NS_OK;
+ mEnv_ShouldAbort = morkBool_kFalse;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::ClearWarnings() // clear warning
+{
+ mEnv_WarningCount = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkEnv::ClearErrorsAndWarnings() // clear both errors & warnings
+{
+ ClearMorkErrorsAndWarnings();
+ return NS_OK;
+}
+// } ===== end nsIMdbEnv methods =====
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkEnv.h b/components/mork/src/morkEnv.h
new file mode 100644
index 000000000..827a56d72
--- /dev/null
+++ b/components/mork/src/morkEnv.h
@@ -0,0 +1,218 @@
+/* -*- 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/. */
+
+#ifndef _MORKENV_
+#define _MORKENV_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+#ifndef _MORKPOOL_
+#include "morkPool.h"
+#endif
+
+// sean was here
+#include "nsError.h"
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kEnv /*i*/ 0x4576 /* ascii 'Ev' */
+
+// use NS error codes to make Mork easier to use with the rest of mozilla
+#define morkEnv_kNoError NS_SUCCEEDED /* no error has happened */
+#define morkEnv_kNonEnvTypeError NS_ERROR_FAILURE /* morkEnv::IsEnv() is false */
+
+#define morkEnv_kStubMethodOnlyError NS_ERROR_NO_INTERFACE
+#define morkEnv_kOutOfMemoryError NS_ERROR_OUT_OF_MEMORY
+#define morkEnv_kNilPointerError NS_ERROR_NULL_POINTER
+#define morkEnv_kNewNonEnvError NS_ERROR_FAILURE
+#define morkEnv_kNilEnvSlotError NS_ERROR_FAILURE
+
+#define morkEnv_kBadFactoryError NS_ERROR_FACTORY_NOT_LOADED
+#define morkEnv_kBadFactoryEnvError NS_ERROR_FACTORY_NOT_LOADED
+#define morkEnv_kBadEnvError NS_ERROR_FAILURE
+
+#define morkEnv_kNonHandleTypeError NS_ERROR_FAILURE
+#define morkEnv_kNonOpenNodeError NS_ERROR_FAILURE
+
+
+#define morkEnv_kWeakRefCountEnvBonus 0 /* try NOT to leak all env instances */
+
+/*| morkEnv:
+|*/
+class morkEnv : public morkObject, public nsIMdbEnv {
+ NS_DECL_ISUPPORTS_INHERITED
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // mork_color mBead_Color; // ID for this bead
+ // morkHandle* mObject_Handle; // weak ref to handle for this object
+
+public: // state is public because the entire Mork system is private
+
+ morkFactory* mEnv_Factory; // NON-refcounted factory
+ nsIMdbHeap* mEnv_Heap; // NON-refcounted heap
+
+ nsIMdbEnv* mEnv_SelfAsMdbEnv;
+ nsIMdbErrorHook* mEnv_ErrorHook;
+
+ morkPool* mEnv_HandlePool; // pool for re-using handles
+
+ mork_u2 mEnv_ErrorCount;
+ mork_u2 mEnv_WarningCount;
+
+ nsresult mEnv_ErrorCode;
+
+ mork_bool mEnv_DoTrace;
+ mork_able mEnv_AutoClear;
+ mork_bool mEnv_ShouldAbort;
+ mork_bool mEnv_BeVerbose;
+ mork_bool mEnv_OwnsHeap;
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseEnv() only if open
+ virtual ~morkEnv(); // assert that CloseEnv() executed earlier
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD GetErrorCount(mdb_count* outCount,
+ mdb_bool* outShouldAbort) override;
+ NS_IMETHOD GetWarningCount(mdb_count* outCount,
+ mdb_bool* outShouldAbort) override;
+
+ NS_IMETHOD GetEnvBeVerbose(mdb_bool* outBeVerbose) override;
+ NS_IMETHOD SetEnvBeVerbose(mdb_bool inBeVerbose) override;
+
+ NS_IMETHOD GetDoTrace(mdb_bool* outDoTrace) override;
+ NS_IMETHOD SetDoTrace(mdb_bool inDoTrace) override;
+
+ NS_IMETHOD GetAutoClear(mdb_bool* outAutoClear) override;
+ NS_IMETHOD SetAutoClear(mdb_bool inAutoClear) override;
+
+ NS_IMETHOD GetErrorHook(nsIMdbErrorHook** acqErrorHook) override;
+ NS_IMETHOD SetErrorHook(
+ nsIMdbErrorHook* ioErrorHook) override; // becomes referenced
+
+ NS_IMETHOD GetHeap(nsIMdbHeap** acqHeap) override;
+ NS_IMETHOD SetHeap(nsIMdbHeap* ioHeap) override; // becomes referenced
+ // } ----- end attribute methods -----
+
+ NS_IMETHOD ClearErrors() override; // clear errors beore re-entering db API
+ NS_IMETHOD ClearWarnings() override; // clear warnings
+ NS_IMETHOD ClearErrorsAndWarnings() override; // clear both errors & warnings
+// } ===== end nsIMdbEnv methods =====
+public: // morkEnv construction & destruction
+ morkEnv(const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ morkFactory* ioFactory, nsIMdbHeap* ioSlotHeap);
+ morkEnv(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ nsIMdbEnv* inSelfAsMdbEnv, morkFactory* ioFactory,
+ nsIMdbHeap* ioSlotHeap);
+ void CloseEnv(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkEnv(const morkEnv& other);
+ morkEnv& operator=(const morkEnv& other);
+
+public: // dynamic type identification
+ mork_bool IsEnv() const
+ { return IsNode() && mNode_Derived == morkDerived_kEnv; }
+// } ===== end morkNode methods =====
+
+public: // utility env methods
+
+ mork_u1 HexToByte(mork_ch inFirstHex, mork_ch inSecondHex);
+
+ mork_size TokenAsHex(void* outBuf, mork_token inToken);
+ // TokenAsHex() is the same as sprintf(outBuf, "%lX", (long) inToken);
+
+ mork_size OidAsHex(void* outBuf, const mdbOid& inOid);
+ // sprintf(buf, "%lX:^%lX", (long) inOid.mOid_Id, (long) inOid.mOid_Scope);
+
+ char* CopyString(nsIMdbHeap* ioHeap, const char* inString);
+ void FreeString(nsIMdbHeap* ioHeap, char* ioString);
+ void StringToYarn(const char* inString, mdbYarn* outYarn);
+
+public: // other env methods
+
+ morkHandleFace* NewHandle(mork_size inSize)
+ { return mEnv_HandlePool->NewHandle(this, inSize, (morkZone*) 0); }
+
+ void ZapHandle(morkHandleFace* ioHandle)
+ { mEnv_HandlePool->ZapHandle(this, ioHandle); }
+
+ void EnableAutoClear() { mEnv_AutoClear = morkAble_kEnabled; }
+ void DisableAutoClear() { mEnv_AutoClear = morkAble_kDisabled; }
+
+ mork_bool DoAutoClear() const
+ { return mEnv_AutoClear == morkAble_kEnabled; }
+
+ void NewError(const char* inString);
+ void NewWarning(const char* inString);
+
+ void ClearMorkErrorsAndWarnings(); // clear both errors & warnings
+ void AutoClearMorkErrorsAndWarnings(); // clear if auto is enabled
+
+ void StubMethodOnlyError();
+ void OutOfMemoryError();
+ void NilPointerError();
+ void NilPointerWarning();
+ void CantMakeWhenBadError();
+ void NewNonEnvError();
+ void NilEnvSlotError();
+
+ void NonEnvTypeError(morkEnv* ev);
+
+ // canonical env convenience methods to check for presence of errors:
+ mork_bool Good() const { return ( mEnv_ErrorCount == 0 ); }
+ mork_bool Bad() const { return ( mEnv_ErrorCount != 0 ); }
+
+ nsIMdbEnv* AsMdbEnv() { return (nsIMdbEnv *) this; }
+ static morkEnv* FromMdbEnv(nsIMdbEnv* ioEnv); // dynamic type checking
+
+ nsresult AsErr() const { return mEnv_ErrorCode; }
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakEnv(morkEnv* me,
+ morkEnv* ev, morkEnv** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongEnv(morkEnv* me,
+ morkEnv* ev, morkEnv** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+#undef MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING
+#ifdef MOZ_IS_DESTRUCTIBLE
+#define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X) \
+ static_assert(!MOZ_IS_DESTRUCTIBLE(X) || \
+ mozilla::IsSame<X, morkEnv>::value, \
+ "Reference-counted class " #X " should not have a public destructor. " \
+ "Try to make this class's destructor non-public. If that is really " \
+ "not possible, you can whitelist this class by providing a " \
+ "HasDangerousPublicDestructor specialization for it.");
+#else
+#define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X)
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKENV_ */
diff --git a/components/mork/src/morkFactory.cpp b/components/mork/src/morkFactory.cpp
new file mode 100644
index 000000000..a3aeadfd4
--- /dev/null
+++ b/components/mork/src/morkFactory.cpp
@@ -0,0 +1,610 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKFACTORY_
+#include "morkFactory.h"
+#endif
+
+#ifndef _ORKINHEAP_
+#include "orkinHeap.h"
+#endif
+
+#ifndef _MORKFILE_
+#include "morkFile.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKTHUMB_
+#include "morkThumb.h"
+#endif
+
+#ifndef _MORKWRITER_
+#include "morkWriter.h"
+#endif
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkFactory::CloseMorkNode(morkEnv* ev) /*i*/ // CloseFactory() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseFactory(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkFactory::~morkFactory() /*i*/ // assert CloseFactory() executed earlier
+{
+ CloseFactory(&mFactory_Env);
+ MORK_ASSERT(mFactory_Env.IsShutNode());
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkFactory::morkFactory() // uses orkinHeap
+: morkObject(morkUsage::kGlobal, (nsIMdbHeap*) 0, morkColor_kNone)
+, mFactory_Env(morkUsage::kMember, (nsIMdbHeap*) 0, this,
+ new orkinHeap())
+, mFactory_Heap()
+{
+ if ( mFactory_Env.Good() )
+ {
+ mNode_Derived = morkDerived_kFactory;
+ mNode_Refs += morkFactory_kWeakRefCountBonus;
+ }
+}
+
+/*public non-poly*/
+morkFactory::morkFactory(nsIMdbHeap* ioHeap)
+: morkObject(morkUsage::kHeap, ioHeap, morkColor_kNone)
+, mFactory_Env(morkUsage::kMember, (nsIMdbHeap*) 0, this, ioHeap)
+, mFactory_Heap()
+{
+ if ( mFactory_Env.Good() )
+ {
+ mNode_Derived = morkDerived_kFactory;
+ mNode_Refs += morkFactory_kWeakRefCountBonus;
+ }
+}
+
+/*public non-poly*/
+morkFactory::morkFactory(morkEnv* ev, /*i*/
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap)
+: morkObject(ev, inUsage, ioHeap, morkColor_kNone, (morkHandle*) 0)
+, mFactory_Env(morkUsage::kMember, (nsIMdbHeap*) 0, this, ioHeap)
+, mFactory_Heap()
+{
+ if ( ev->Good() )
+ {
+ mNode_Derived = morkDerived_kFactory;
+ mNode_Refs += morkFactory_kWeakRefCountBonus;
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkFactory, morkObject, nsIMdbFactory)
+
+extern "C" nsIMdbFactory* MakeMdbFactory()
+{
+ return new morkFactory(new orkinHeap());
+}
+
+
+/*public non-poly*/ void
+morkFactory::CloseFactory(morkEnv* ev) /*i*/ // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ mFactory_Env.CloseMorkNode(ev);
+ this->CloseObject(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+morkEnv* morkFactory::GetInternalFactoryEnv(nsresult* outErr)
+{
+ morkEnv* outEnv = 0;
+ if (IsNode() && IsOpenNode() && IsFactory() )
+ {
+ morkEnv* fenv = &mFactory_Env;
+ if ( fenv && fenv->IsNode() && fenv->IsOpenNode() && fenv->IsEnv() )
+ {
+ fenv->ClearMorkErrorsAndWarnings(); // drop any earlier errors
+ outEnv = fenv;
+ }
+ else
+ *outErr = morkEnv_kBadFactoryEnvError;
+ }
+ else
+ *outErr = morkEnv_kBadFactoryError;
+
+ return outEnv;
+}
+
+
+void
+morkFactory::NonFactoryTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkFactory");
+}
+
+
+NS_IMETHODIMP
+morkFactory::OpenOldFile(nsIMdbEnv* mev, nsIMdbHeap* ioHeap,
+ const char* inFilePath,
+ mork_bool inFrozen, nsIMdbFile** acqFile)
+ // Choose some subclass of nsIMdbFile to instantiate, in order to read
+ // (and write if not frozen) the file known by inFilePath. The file
+ // returned should be open and ready for use, and presumably positioned
+ // at the first byte position of the file. The exact manner in which
+ // files must be opened is considered a subclass specific detail, and
+ // other portions or Mork source code don't want to know how it's done.
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ morkFile* file = nullptr;
+ if ( ev )
+ {
+ if ( !ioHeap )
+ ioHeap = &mFactory_Heap;
+
+ file = morkFile::OpenOldFile(ev, ioHeap, inFilePath, inFrozen);
+ NS_IF_ADDREF( file );
+
+ outErr = ev->AsErr();
+ }
+ if ( acqFile )
+ *acqFile = file;
+
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkFactory::CreateNewFile(nsIMdbEnv* mev, nsIMdbHeap* ioHeap,
+ const char* inFilePath, nsIMdbFile** acqFile)
+ // Choose some subclass of nsIMdbFile to instantiate, in order to read
+ // (and write if not frozen) the file known by inFilePath. The file
+ // returned should be created and ready for use, and presumably positioned
+ // at the first byte position of the file. The exact manner in which
+ // files must be opened is considered a subclass specific detail, and
+ // other portions or Mork source code don't want to know how it's done.
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ morkFile* file = nullptr;
+ if ( ev )
+ {
+ if ( !ioHeap )
+ ioHeap = &mFactory_Heap;
+
+ file = morkFile::CreateNewFile(ev, ioHeap, inFilePath);
+ if ( file )
+ NS_ADDREF(file);
+
+ outErr = ev->AsErr();
+ }
+ if ( acqFile )
+ *acqFile = file;
+
+ return outErr;
+}
+// } ----- end file methods -----
+
+// { ----- begin env methods -----
+NS_IMETHODIMP
+morkFactory::MakeEnv(nsIMdbHeap* ioHeap, nsIMdbEnv** acqEnv)
+// ioHeap can be nil, causing a MakeHeap() style heap instance to be used
+{
+ nsresult outErr = NS_OK;
+ nsIMdbEnv* outEnv = 0;
+ mork_bool ownsHeap = (ioHeap == 0);
+ if ( !ioHeap )
+ ioHeap = new orkinHeap();
+
+ if ( acqEnv && ioHeap )
+ {
+ morkEnv* fenv = this->GetInternalFactoryEnv(&outErr);
+ if ( fenv )
+ {
+ morkEnv* newEnv = new(*ioHeap, fenv)
+ morkEnv(morkUsage::kHeap, ioHeap, this, ioHeap);
+
+ if ( newEnv )
+ {
+ newEnv->mEnv_OwnsHeap = ownsHeap;
+ newEnv->mNode_Refs += morkEnv_kWeakRefCountEnvBonus;
+ NS_ADDREF(newEnv);
+ newEnv->mEnv_SelfAsMdbEnv = newEnv;
+ outEnv = newEnv;
+ }
+ else
+ outErr = morkEnv_kOutOfMemoryError;
+ }
+
+ *acqEnv = outEnv;
+ }
+ else
+ outErr = morkEnv_kNilPointerError;
+
+ return outErr;
+}
+// } ----- end env methods -----
+
+// { ----- begin heap methods -----
+NS_IMETHODIMP
+morkFactory::MakeHeap(nsIMdbEnv* mev, nsIMdbHeap** acqHeap)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbHeap* outHeap = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ outHeap = new orkinHeap();
+ if ( !outHeap )
+ ev->OutOfMemoryError();
+ }
+ MORK_ASSERT(acqHeap);
+ if ( acqHeap )
+ *acqHeap = outHeap;
+ return outErr;
+}
+// } ----- end heap methods -----
+
+// { ----- begin row methods -----
+NS_IMETHODIMP
+morkFactory::MakeRow(nsIMdbEnv* mev, nsIMdbHeap* ioHeap,
+ nsIMdbRow** acqRow)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+// ioHeap can be nil, causing the heap associated with ev to be used
+// } ----- end row methods -----
+
+// { ----- begin port methods -----
+NS_IMETHODIMP
+morkFactory::CanOpenFilePort(
+ nsIMdbEnv* mev, // context
+ // const char* inFilePath, // the file to investigate
+ // const mdbYarn* inFirst512Bytes,
+ nsIMdbFile* ioFile, // db abstract file interface
+ mdb_bool* outCanOpen, // whether OpenFilePort() might succeed
+ mdbYarn* outFormatVersion)
+{
+ nsresult outErr = NS_OK;
+ if ( outFormatVersion )
+ {
+ outFormatVersion->mYarn_Fill = 0;
+ }
+ mdb_bool canOpenAsPort = morkBool_kFalse;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( ioFile && outCanOpen )
+ {
+ canOpenAsPort = this->CanOpenMorkTextFile(ev, ioFile);
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+
+ if ( outCanOpen )
+ *outCanOpen = canOpenAsPort;
+
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkFactory::OpenFilePort(
+ nsIMdbEnv* mev, // context
+ nsIMdbHeap* ioHeap, // can be nil to cause ev's heap attribute to be used
+ // const char* inFilePath, // the file to open for readonly import
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy, // runtime policies for using db
+ nsIMdbThumb** acqThumb)
+{
+ NS_ASSERTION(false, "this doesn't look implemented");
+ MORK_USED_1(ioHeap);
+ nsresult outErr = NS_OK;
+ nsIMdbThumb* outThumb = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( ioFile && inOpenPolicy && acqThumb )
+ {
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( acqThumb )
+ *acqThumb = outThumb;
+ return outErr;
+}
+// Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+// then call nsIMdbFactory::ThumbToOpenPort() to get the port instance.
+
+NS_IMETHODIMP
+morkFactory::ThumbToOpenPort( // redeeming a completed thumb from OpenFilePort()
+ nsIMdbEnv* mev, // context
+ nsIMdbThumb* ioThumb, // thumb from OpenFilePort() with done status
+ nsIMdbPort** acqPort)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbPort* outPort = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( ioThumb && acqPort )
+ {
+ morkThumb* thumb = (morkThumb*) ioThumb;
+ morkStore* store = thumb->ThumbToOpenStore(ev);
+ if ( store )
+ {
+ store->mStore_CanAutoAssignAtomIdentity = morkBool_kTrue;
+ store->mStore_CanDirty = morkBool_kTrue;
+ store->SetStoreAndAllSpacesCanDirty(ev, morkBool_kTrue);
+
+ NS_ADDREF(store);
+ outPort = store;
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( acqPort )
+ *acqPort = outPort;
+ return outErr;
+}
+// } ----- end port methods -----
+
+mork_bool
+morkFactory::CanOpenMorkTextFile(morkEnv* ev,
+ // const mdbYarn* inFirst512Bytes,
+ nsIMdbFile* ioFile)
+{
+ MORK_USED_1(ev);
+ mork_bool outBool = morkBool_kFalse;
+ mork_size headSize = MORK_STRLEN(morkWriter_kFileHeader);
+
+ char localBuf[ 256 + 4 ]; // for extra for sloppy safety
+ mdbYarn localYarn;
+ mdbYarn* y = &localYarn;
+ y->mYarn_Buf = localBuf; // space to hold content
+ y->mYarn_Fill = 0; // no logical content yet
+ y->mYarn_Size = 256; // physical capacity is 256 bytes
+ y->mYarn_More = 0;
+ y->mYarn_Form = 0;
+ y->mYarn_Grow = 0;
+
+ if ( ioFile )
+ {
+ nsIMdbEnv* menv = ev->AsMdbEnv();
+ mdb_size actualSize = 0;
+ ioFile->Get(menv, y->mYarn_Buf, y->mYarn_Size, /*pos*/ 0, &actualSize);
+ y->mYarn_Fill = actualSize;
+
+ if ( y->mYarn_Buf && actualSize >= headSize && ev->Good() )
+ {
+ mork_u1* buf = (mork_u1*) y->mYarn_Buf;
+ outBool = ( MORK_MEMCMP(morkWriter_kFileHeader, buf, headSize) == 0 );
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ return outBool;
+}
+
+// { ----- begin store methods -----
+NS_IMETHODIMP
+morkFactory::CanOpenFileStore(
+ nsIMdbEnv* mev, // context
+ // const char* inFilePath, // the file to investigate
+ // const mdbYarn* inFirst512Bytes,
+ nsIMdbFile* ioFile, // db abstract file interface
+ mdb_bool* outCanOpenAsStore, // whether OpenFileStore() might succeed
+ mdb_bool* outCanOpenAsPort, // whether OpenFilePort() might succeed
+ mdbYarn* outFormatVersion)
+{
+ mdb_bool canOpenAsStore = morkBool_kFalse;
+ mdb_bool canOpenAsPort = morkBool_kFalse;
+ if ( outFormatVersion )
+ {
+ outFormatVersion->mYarn_Fill = 0;
+ }
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( ioFile && outCanOpenAsStore )
+ {
+ // right now always say true; later we should look for magic patterns
+ canOpenAsStore = this->CanOpenMorkTextFile(ev, ioFile);
+ canOpenAsPort = canOpenAsStore;
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( outCanOpenAsStore )
+ *outCanOpenAsStore = canOpenAsStore;
+
+ if ( outCanOpenAsPort )
+ *outCanOpenAsPort = canOpenAsPort;
+
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkFactory::OpenFileStore( // open an existing database
+ nsIMdbEnv* mev, // context
+ nsIMdbHeap* ioHeap, // can be nil to cause ev's heap attribute to be used
+ // const char* inFilePath, // the file to open for general db usage
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy, // runtime policies for using db
+ nsIMdbThumb** acqThumb)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbThumb* outThumb = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( !ioHeap ) // need to use heap from env?
+ ioHeap = ev->mEnv_Heap;
+
+ if ( ioFile && inOpenPolicy && acqThumb )
+ {
+ morkStore* store = new(*ioHeap, ev)
+ morkStore(ev, morkUsage::kHeap, ioHeap, this, ioHeap);
+
+ if ( store )
+ {
+ mork_bool frozen = morkBool_kFalse; // open store mutable access
+ if ( store->OpenStoreFile(ev, frozen, ioFile, inOpenPolicy) )
+ {
+ morkThumb* thumb = morkThumb::Make_OpenFileStore(ev, ioHeap, store);
+ if ( thumb )
+ {
+ outThumb = thumb;
+ thumb->AddRef();
+ }
+ }
+// store->CutStrongRef(mev); // always cut ref (handle has its own ref)
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( acqThumb )
+ *acqThumb = outThumb;
+ return outErr;
+}
+// Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+// then call nsIMdbFactory::ThumbToOpenStore() to get the store instance.
+
+NS_IMETHODIMP
+morkFactory::ThumbToOpenStore( // redeem completed thumb from OpenFileStore()
+ nsIMdbEnv* mev, // context
+ nsIMdbThumb* ioThumb, // thumb from OpenFileStore() with done status
+ nsIMdbStore** acqStore)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbStore* outStore = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( ioThumb && acqStore )
+ {
+ morkThumb* thumb = (morkThumb*) ioThumb;
+ morkStore* store = thumb->ThumbToOpenStore(ev);
+ if ( store )
+ {
+ store->mStore_CanAutoAssignAtomIdentity = morkBool_kTrue;
+ store->mStore_CanDirty = morkBool_kTrue;
+ store->SetStoreAndAllSpacesCanDirty(ev, morkBool_kTrue);
+
+ outStore = store;
+ NS_ADDREF(store);
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( acqStore )
+ *acqStore = outStore;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkFactory::CreateNewFileStore( // create a new db with minimal content
+ nsIMdbEnv* mev, // context
+ nsIMdbHeap* ioHeap, // can be nil to cause ev's heap attribute to be used
+ // const char* inFilePath, // name of file which should not yet exist
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy, // runtime policies for using db
+ nsIMdbStore** acqStore)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbStore* outStore = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( !ioHeap ) // need to use heap from env?
+ ioHeap = ev->mEnv_Heap;
+
+ if ( ioFile && inOpenPolicy && acqStore && ioHeap )
+ {
+ morkStore* store = new(*ioHeap, ev)
+ morkStore(ev, morkUsage::kHeap, ioHeap, this, ioHeap);
+
+ if ( store )
+ {
+ store->mStore_CanAutoAssignAtomIdentity = morkBool_kTrue;
+ store->mStore_CanDirty = morkBool_kTrue;
+ store->SetStoreAndAllSpacesCanDirty(ev, morkBool_kTrue);
+
+ if ( store->CreateStoreFile(ev, ioFile, inOpenPolicy) )
+ outStore = store;
+ NS_ADDREF(store);
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( acqStore )
+ *acqStore = outStore;
+ return outErr;
+}
+// } ----- end store methods -----
+
+// } ===== end nsIMdbFactory methods =====
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkFactory.h b/components/mork/src/morkFactory.h
new file mode 100644
index 000000000..f6f045ae0
--- /dev/null
+++ b/components/mork/src/morkFactory.h
@@ -0,0 +1,203 @@
+/* -*- 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/. */
+
+#ifndef _MORKFACTORY_
+#define _MORKFACTORY_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+#ifndef _ORKINHEAP_
+#include "orkinHeap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class nsIMdbFactory;
+
+#define morkDerived_kFactory /*i*/ 0x4663 /* ascii 'Fc' */
+#define morkFactory_kWeakRefCountBonus 0 /* try NOT to leak all factories */
+
+/*| morkFactory:
+|*/
+class morkFactory : public morkObject, public nsIMdbFactory { // nsIMdbObject
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // mork_color mBead_Color; // ID for this bead
+ // morkHandle* mObject_Handle; // weak ref to handle for this object
+
+public: // state is public because the entire Mork system is private
+
+ morkEnv mFactory_Env; // private env instance used internally
+ orkinHeap mFactory_Heap;
+
+ NS_DECL_ISUPPORTS_INHERITED
+// { ===== begin morkNode interface =====
+public: // morkFactory virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseFactory() only if open
+
+
+// { ===== begin nsIMdbFactory methods =====
+
+ // { ----- begin file methods -----
+ NS_IMETHOD OpenOldFile(nsIMdbEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath,
+ mdb_bool inFrozen, nsIMdbFile** acqFile) override;
+ // Choose some subclass of nsIMdbFile to instantiate, in order to read
+ // (and write if not frozen) the file known by inFilePath. The file
+ // returned should be open and ready for use, and presumably positioned
+ // at the first byte position of the file. The exact manner in which
+ // files must be opened is considered a subclass specific detail, and
+ // other portions or Mork source code don't want to know how it's done.
+
+ NS_IMETHOD CreateNewFile(nsIMdbEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath,
+ nsIMdbFile** acqFile) override;
+ // Choose some subclass of nsIMdbFile to instantiate, in order to read
+ // (and write if not frozen) the file known by inFilePath. The file
+ // returned should be created and ready for use, and presumably positioned
+ // at the first byte position of the file. The exact manner in which
+ // files must be opened is considered a subclass specific detail, and
+ // other portions or Mork source code don't want to know how it's done.
+ // } ----- end file methods -----
+
+ // { ----- begin env methods -----
+ NS_IMETHOD MakeEnv(nsIMdbHeap* ioHeap, nsIMdbEnv** acqEnv) override; // new env
+ // ioHeap can be nil, causing a MakeHeap() style heap instance to be used
+ // } ----- end env methods -----
+
+ // { ----- begin heap methods -----
+ NS_IMETHOD MakeHeap(nsIMdbEnv* ev, nsIMdbHeap** acqHeap) override; // new heap
+ // } ----- end heap methods -----
+
+ // { ----- begin row methods -----
+ NS_IMETHOD MakeRow(nsIMdbEnv* ev, nsIMdbHeap* ioHeap, nsIMdbRow** acqRow) override; // new row
+ // ioHeap can be nil, causing the heap associated with ev to be used
+ // } ----- end row methods -----
+
+ // { ----- begin port methods -----
+ NS_IMETHOD CanOpenFilePort(
+ nsIMdbEnv* ev, // context
+ // const char* inFilePath, // the file to investigate
+ // const mdbYarn* inFirst512Bytes,
+ nsIMdbFile* ioFile, // db abstract file interface
+ mdb_bool* outCanOpen, // whether OpenFilePort() might succeed
+ mdbYarn* outFormatVersion) override; // informal file format description
+
+ NS_IMETHOD OpenFilePort(
+ nsIMdbEnv* ev, // context
+ nsIMdbHeap* ioHeap, // can be nil to cause ev's heap attribute to be used
+ // const char* inFilePath, // the file to open for readonly import
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy, // runtime policies for using db
+ nsIMdbThumb** acqThumb) override; // acquire thumb for incremental port open
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then call nsIMdbFactory::ThumbToOpenPort() to get the port instance.
+
+ NS_IMETHOD ThumbToOpenPort( // redeeming a completed thumb from OpenFilePort()
+ nsIMdbEnv* ev, // context
+ nsIMdbThumb* ioThumb, // thumb from OpenFilePort() with done status
+ nsIMdbPort** acqPort) override; // acquire new port object
+ // } ----- end port methods -----
+
+ // { ----- begin store methods -----
+ NS_IMETHOD CanOpenFileStore(
+ nsIMdbEnv* ev, // context
+ // const char* inFilePath, // the file to investigate
+ // const mdbYarn* inFirst512Bytes,
+ nsIMdbFile* ioFile, // db abstract file interface
+ mdb_bool* outCanOpenAsStore, // whether OpenFileStore() might succeed
+ mdb_bool* outCanOpenAsPort, // whether OpenFilePort() might succeed
+ mdbYarn* outFormatVersion) override; // informal file format description
+
+ NS_IMETHOD OpenFileStore( // open an existing database
+ nsIMdbEnv* ev, // context
+ nsIMdbHeap* ioHeap, // can be nil to cause ev's heap attribute to be used
+ // const char* inFilePath, // the file to open for general db usage
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy, // runtime policies for using db
+ nsIMdbThumb** acqThumb) override; // acquire thumb for incremental store open
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then call nsIMdbFactory::ThumbToOpenStore() to get the store instance.
+
+ NS_IMETHOD
+ ThumbToOpenStore( // redeem completed thumb from OpenFileStore()
+ nsIMdbEnv* ev, // context
+ nsIMdbThumb* ioThumb, // thumb from OpenFileStore() with done status
+ nsIMdbStore** acqStore) override; // acquire new db store object
+
+ NS_IMETHOD CreateNewFileStore( // create a new db with minimal content
+ nsIMdbEnv* ev, // context
+ nsIMdbHeap* ioHeap, // can be nil to cause ev's heap attribute to be used
+ // const char* inFilePath, // name of file which should not yet exist
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy, // runtime policies for using db
+ nsIMdbStore** acqStore) override; // acquire new db store object
+ // } ----- end store methods -----
+
+// } ===== end nsIMdbFactory methods =====
+
+public: // morkYarn construction & destruction
+ morkFactory(); // uses orkinHeap
+ explicit morkFactory(nsIMdbHeap* ioHeap); // caller supplied heap
+ morkFactory(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap);
+ void CloseFactory(morkEnv* ev); // called by CloseMorkNode();
+
+
+public: // morkNode memory management operators
+ void* operator new(size_t inSize) CPP_THROW_NEW
+ { return ::operator new(inSize); }
+
+ void* operator new(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev) CPP_THROW_NEW
+ { return morkNode::MakeNew(inSize, ioHeap, ev); }
+
+private: // copying is not allowed
+ morkFactory(const morkFactory& other);
+ morkFactory& operator=(const morkFactory& other);
+ virtual ~morkFactory(); // assert that CloseFactory() executed earlier
+
+public: // dynamic type identification
+ mork_bool IsFactory() const
+ { return IsNode() && mNode_Derived == morkDerived_kFactory; }
+// } ===== end morkNode methods =====
+
+public: // other factory methods
+
+ void NonFactoryTypeError(morkEnv* ev);
+ morkEnv* GetInternalFactoryEnv(nsresult* outErr);
+ mork_bool CanOpenMorkTextFile(morkEnv* ev, nsIMdbFile* ioFile);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakFactory(morkFactory* me,
+ morkEnv* ev, morkFactory** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongFactory(morkFactory* me,
+ morkEnv* ev, morkFactory** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKFACTORY_ */
diff --git a/components/mork/src/morkFile.cpp b/components/mork/src/morkFile.cpp
new file mode 100644
index 000000000..040d1a8dc
--- /dev/null
+++ b/components/mork/src/morkFile.cpp
@@ -0,0 +1,874 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKFILE_
+#include "morkFile.h"
+#endif
+
+#ifdef MORK_WIN
+#include "io.h"
+#include <windows.h>
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkFile::CloseMorkNode(morkEnv* ev) // CloseFile() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseFile(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkFile::~morkFile() // assert CloseFile() executed earlier
+{
+ MORK_ASSERT(mFile_Frozen==0);
+ MORK_ASSERT(mFile_DoTrace==0);
+ MORK_ASSERT(mFile_IoOpen==0);
+ MORK_ASSERT(mFile_Active==0);
+}
+
+/*public non-poly*/
+morkFile::morkFile(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+: morkObject(ev, inUsage, ioHeap, morkColor_kNone, (morkHandle*) 0)
+, mFile_Frozen( 0 )
+, mFile_DoTrace( 0 )
+, mFile_IoOpen( 0 )
+, mFile_Active( 0 )
+
+, mFile_SlotHeap( 0 )
+, mFile_Name( 0 )
+, mFile_Thief( 0 )
+{
+ if ( ev->Good() )
+ {
+ if ( ioSlotHeap )
+ {
+ nsIMdbHeap_SlotStrongHeap(ioSlotHeap, ev, &mFile_SlotHeap);
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kFile;
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkFile, morkObject, nsIMdbFile)
+/*public non-poly*/ void
+morkFile::CloseFile(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ mFile_Frozen = 0;
+ mFile_DoTrace = 0;
+ mFile_IoOpen = 0;
+ mFile_Active = 0;
+
+ if ( mFile_Name )
+ this->SetFileName(ev, (const char*) 0);
+
+ nsIMdbHeap_SlotStrongHeap((nsIMdbHeap*) 0, ev, &mFile_SlotHeap);
+ nsIMdbFile_SlotStrongFile((nsIMdbFile*) 0, ev, &mFile_Thief);
+
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ morkFile*
+morkFile::OpenOldFile(morkEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath, mork_bool inFrozen)
+ // Choose some subclass of morkFile to instantiate, in order to read
+ // (and write if not frozen) the file known by inFilePath. The file
+ // returned should be open and ready for use, and presumably positioned
+ // at the first byte position of the file. The exact manner in which
+ // files must be opened is considered a subclass specific detail, and
+ // other portions or Mork source code don't want to know how it's done.
+{
+ return morkStdioFile::OpenOldStdioFile(ev, ioHeap, inFilePath, inFrozen);
+}
+
+/*static*/ morkFile*
+morkFile::CreateNewFile(morkEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath)
+ // Choose some subclass of morkFile to instantiate, in order to read
+ // (and write if not frozen) the file known by inFilePath. The file
+ // returned should be created and ready for use, and presumably positioned
+ // at the first byte position of the file. The exact manner in which
+ // files must be opened is considered a subclass specific detail, and
+ // other portions or Mork source code don't want to know how it's done.
+{
+ return morkStdioFile::CreateNewStdioFile(ev, ioHeap, inFilePath);
+}
+
+void
+morkFile::NewMissingIoError(morkEnv* ev) const
+{
+ ev->NewError("file missing io");
+}
+
+/*static*/ void
+morkFile::NonFileTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkFile");
+}
+
+/*static*/ void
+morkFile::NilSlotHeapError(morkEnv* ev)
+{
+ ev->NewError("nil mFile_SlotHeap");
+}
+
+/*static*/ void
+morkFile::NilFileNameError(morkEnv* ev)
+{
+ ev->NewError("nil mFile_Name");
+}
+
+void
+morkFile::SetThief(morkEnv* ev, nsIMdbFile* ioThief)
+{
+ nsIMdbFile_SlotStrongFile(ioThief, ev, &mFile_Thief);
+}
+
+void
+morkFile::SetFileName(morkEnv* ev, const char* inName) // inName can be nil
+{
+ nsIMdbHeap* heap = mFile_SlotHeap;
+ if ( heap )
+ {
+ char* name = mFile_Name;
+ if ( name )
+ {
+ mFile_Name = 0;
+ ev->FreeString(heap, name);
+ }
+ if ( ev->Good() && inName )
+ mFile_Name = ev->CopyString(heap, inName);
+ }
+ else
+ this->NilSlotHeapError(ev);
+}
+
+void
+morkFile::NewFileDownError(morkEnv* ev) const
+// call NewFileDownError() when either IsOpenAndActiveFile()
+// is false, or when IsOpenActiveAndMutableFile() is false.
+{
+ if ( this->IsOpenNode() )
+ {
+ if ( this->FileActive() )
+ {
+ if ( this->FileFrozen() )
+ {
+ ev->NewError("file frozen");
+ }
+ else
+ ev->NewError("unknown file problem");
+ }
+ else
+ ev->NewError("file not active");
+ }
+ else
+ ev->NewError("file not open");
+}
+
+void
+morkFile::NewFileErrnoError(morkEnv* ev) const
+// call NewFileErrnoError() to convert std C errno into AB fault
+{
+ const char* errnoString = strerror(errno);
+ ev->NewError(errnoString); // maybe pass value of strerror() instead
+}
+
+// ````` ````` ````` ````` newlines ````` ````` ````` `````
+
+#if defined(MORK_MAC)
+ static const char morkFile_kNewlines[] =
+ "\015\015\015\015\015\015\015\015\015\015\015\015\015\015\015\015";
+# define morkFile_kNewlinesCount 16
+#else
+# if defined(MORK_WIN)
+ static const char morkFile_kNewlines[] =
+ "\015\012\015\012\015\012\015\012\015\012\015\012\015\012\015\012";
+# define morkFile_kNewlinesCount 8
+# else
+# ifdef MORK_UNIX
+ static const char morkFile_kNewlines[] =
+ "\012\012\012\012\012\012\012\012\012\012\012\012\012\012\012\012";
+# define morkFile_kNewlinesCount 16
+# endif /* MORK_UNIX */
+# endif /* MORK_WIN */
+#endif /* MORK_MAC */
+
+mork_size
+morkFile::WriteNewlines(morkEnv* ev, mork_count inNewlines)
+ // WriteNewlines() returns the number of bytes written.
+{
+ mork_size outSize = 0;
+ while ( inNewlines && ev->Good() ) // more newlines to write?
+ {
+ mork_u4 quantum = inNewlines;
+ if ( quantum > morkFile_kNewlinesCount )
+ quantum = morkFile_kNewlinesCount;
+
+ mork_size quantumSize = quantum * mork_kNewlineSize;
+ mdb_size bytesWritten;
+ this->Write(ev->AsMdbEnv(), morkFile_kNewlines, quantumSize, &bytesWritten);
+ outSize += quantumSize;
+ inNewlines -= quantum;
+ }
+ return outSize;
+}
+
+NS_IMETHODIMP
+morkFile::Eof(nsIMdbEnv* mev, mdb_pos* outPos)
+{
+ nsresult outErr = NS_OK;
+ mdb_pos pos = -1;
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ pos = Length(ev);
+ outErr = ev->AsErr();
+ if ( outPos )
+ *outPos = pos;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkFile::Get(nsIMdbEnv* mev, void* outBuf, mdb_size inSize,
+ mdb_pos inPos, mdb_size* outActualSize)
+{
+ nsresult rv = NS_OK;
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ mdb_pos outPos;
+ Seek(mev, inPos, &outPos);
+ if ( ev->Good() )
+ rv = Read(mev, outBuf, inSize, outActualSize);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+morkFile::Put(nsIMdbEnv* mev, const void* inBuf, mdb_size inSize,
+ mdb_pos inPos, mdb_size* outActualSize)
+{
+ nsresult outErr = NS_OK;
+ *outActualSize = 0;
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ mdb_pos outPos;
+
+ Seek(mev, inPos, &outPos);
+ if ( ev->Good() )
+ Write(mev, inBuf, inSize, outActualSize);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+// { ----- begin path methods -----
+NS_IMETHODIMP
+morkFile::Path(nsIMdbEnv* mev, mdbYarn* outFilePath)
+{
+ nsresult outErr = NS_OK;
+ if ( outFilePath )
+ outFilePath->mYarn_Fill = 0;
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ ev->StringToYarn(GetFileNameString(), outFilePath);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+// } ----- end path methods -----
+
+// { ----- begin replacement methods -----
+
+
+NS_IMETHODIMP
+morkFile::Thief(nsIMdbEnv* mev, nsIMdbFile** acqThief)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbFile* outThief = 0;
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ outThief = GetThief();
+ NS_IF_ADDREF(outThief);
+ outErr = ev->AsErr();
+ }
+ if ( acqThief )
+ *acqThief = outThief;
+ return outErr;
+}
+
+// } ----- end replacement methods -----
+
+// { ----- begin versioning methods -----
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkStdioFile::CloseMorkNode(morkEnv* ev) // CloseStdioFile() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseStdioFile(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkStdioFile::~morkStdioFile() // assert CloseStdioFile() executed earlier
+{
+ if (mStdioFile_File)
+ CloseStdioFile(mMorkEnv);
+ MORK_ASSERT(mStdioFile_File==0);
+}
+
+/*public non-poly*/ void
+morkStdioFile::CloseStdioFile(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ if ( mStdioFile_File && this->FileActive() && this->FileIoOpen() )
+ {
+ this->CloseStdio(ev);
+ }
+
+ mStdioFile_File = 0;
+
+ this->CloseFile(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+// compatible with the morkFile::MakeFile() entry point
+
+/*static*/ morkStdioFile*
+morkStdioFile::OpenOldStdioFile(morkEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath, mork_bool inFrozen)
+{
+ morkStdioFile* outFile = 0;
+ if ( ioHeap && inFilePath )
+ {
+ const char* mode = (inFrozen)? "rb" : "rb+";
+ outFile = new(*ioHeap, ev)
+ morkStdioFile(ev, morkUsage::kHeap, ioHeap, ioHeap, inFilePath, mode);
+
+ if ( outFile )
+ {
+ outFile->SetFileFrozen(inFrozen);
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ return outFile;
+}
+
+/*static*/ morkStdioFile*
+morkStdioFile::CreateNewStdioFile(morkEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath)
+{
+ morkStdioFile* outFile = 0;
+ if ( ioHeap && inFilePath )
+ {
+ const char* mode = "wb+";
+ outFile = new(*ioHeap, ev)
+ morkStdioFile(ev, morkUsage::kHeap, ioHeap, ioHeap, inFilePath, mode);
+ }
+ else
+ ev->NilPointerError();
+
+ return outFile;
+}
+
+
+
+NS_IMETHODIMP
+morkStdioFile::BecomeTrunk(nsIMdbEnv* ev)
+ // If this file is a file version branch created by calling AcquireBud(),
+ // BecomeTrunk() causes this file's content to replace the original
+ // file's content, typically by assuming the original file's identity.
+{
+ return Flush(ev);
+}
+
+NS_IMETHODIMP
+morkStdioFile::AcquireBud(nsIMdbEnv * mdbev, nsIMdbHeap* ioHeap, nsIMdbFile **acquiredFile)
+ // AcquireBud() starts a new "branch" version of the file, empty of content,
+ // so that a new version of the file can be written. This new file
+ // can later be told to BecomeTrunk() the original file, so the branch
+ // created by budding the file will replace the original file. Some
+ // file subclasses might initially take the unsafe but expedient
+ // approach of simply truncating this file down to zero length, and
+ // then returning the same morkFile pointer as this, with an extra
+ // reference count increment. Note that the caller of AcquireBud() is
+ // expected to eventually call CutStrongRef() on the returned file
+ // in order to release the strong reference. High quality versions
+ // of morkFile subclasses will create entirely new files which later
+ // are renamed to become the old file, so that better transactional
+ // behavior is exhibited by the file, so crashes protect old files.
+ // Note that AcquireBud() is an illegal operation on readonly files.
+{
+ NS_ENSURE_ARG(acquiredFile);
+ MORK_USED_1(ioHeap);
+ nsresult rv = NS_OK;
+ morkFile* outFile = 0;
+ morkEnv *ev = morkEnv::FromMdbEnv(mdbev);
+
+ if ( this->IsOpenAndActiveFile() )
+ {
+ FILE* file = (FILE*) mStdioFile_File;
+ if ( file )
+ {
+//#ifdef MORK_WIN
+// truncate(file, /*eof*/ 0);
+//#else /*MORK_WIN*/
+ char* name = mFile_Name;
+ if ( name )
+ {
+ if ( MORK_FILECLOSE(file) >= 0 )
+ {
+ this->SetFileActive(morkBool_kFalse);
+ this->SetFileIoOpen(morkBool_kFalse);
+ mStdioFile_File = 0;
+
+ file = MORK_FILEOPEN(name, "wb+"); // open for write, discarding old content
+ if ( file )
+ {
+ mStdioFile_File = file;
+ this->SetFileActive(morkBool_kTrue);
+ this->SetFileIoOpen(morkBool_kTrue);
+ this->SetFileFrozen(morkBool_kFalse);
+ }
+ else
+ this->new_stdio_file_fault(ev);
+ }
+ else
+ this->new_stdio_file_fault(ev);
+ }
+ else
+ this->NilFileNameError(ev);
+
+//#endif /*MORK_WIN*/
+
+ if ( ev->Good() && this->AddStrongRef(ev->AsMdbEnv()) )
+ {
+ outFile = this;
+ AddRef();
+ }
+ }
+ else if ( mFile_Thief )
+ {
+ rv = mFile_Thief->AcquireBud(ev->AsMdbEnv(), ioHeap, acquiredFile);
+ }
+ else
+ this->NewMissingIoError(ev);
+ }
+ else this->NewFileDownError(ev);
+
+ *acquiredFile = outFile;
+ return rv;
+}
+
+mork_pos
+morkStdioFile::Length(morkEnv * ev) const
+{
+ mork_pos outPos = 0;
+
+ if ( this->IsOpenAndActiveFile() )
+ {
+ FILE* file = (FILE*) mStdioFile_File;
+ if ( file )
+ {
+ long start = MORK_FILETELL(file);
+ if ( start >= 0 )
+ {
+ long fore = MORK_FILESEEK(file, 0, SEEK_END);
+ if ( fore >= 0 )
+ {
+ long eof = MORK_FILETELL(file);
+ if ( eof >= 0 )
+ {
+ long back = MORK_FILESEEK(file, start, SEEK_SET);
+ if ( back >= 0 )
+ outPos = eof;
+ else
+ this->new_stdio_file_fault(ev);
+ }
+ else this->new_stdio_file_fault(ev);
+ }
+ else this->new_stdio_file_fault(ev);
+ }
+ else this->new_stdio_file_fault(ev);
+ }
+ else if ( mFile_Thief )
+ mFile_Thief->Eof(ev->AsMdbEnv(), &outPos);
+ else
+ this->NewMissingIoError(ev);
+ }
+ else this->NewFileDownError(ev);
+
+ return outPos;
+}
+
+NS_IMETHODIMP
+morkStdioFile::Tell(nsIMdbEnv* ev, mork_pos *outPos) const
+{
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG(outPos);
+ morkEnv* mev = morkEnv::FromMdbEnv(ev);
+ if ( this->IsOpenAndActiveFile() )
+ {
+ FILE* file = (FILE*) mStdioFile_File;
+ if ( file )
+ {
+ long where = MORK_FILETELL(file);
+ if ( where >= 0 )
+ *outPos = where;
+ else
+ this->new_stdio_file_fault(mev);
+ }
+ else if ( mFile_Thief )
+ mFile_Thief->Tell(ev, outPos);
+ else
+ this->NewMissingIoError(mev);
+ }
+ else this->NewFileDownError(mev);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+morkStdioFile::Read(nsIMdbEnv* ev, void* outBuf, mork_size inSize, mork_num *outCount)
+{
+ nsresult rv = NS_OK;
+ morkEnv* mev = morkEnv::FromMdbEnv(ev);
+ if ( this->IsOpenAndActiveFile() )
+ {
+ FILE* file = (FILE*) mStdioFile_File;
+ if ( file )
+ {
+ long count = (long) MORK_FILEREAD(outBuf, inSize, file);
+ if ( count >= 0 )
+ {
+ *outCount = (mork_num) count;
+ }
+ else this->new_stdio_file_fault(mev);
+ }
+ else if ( mFile_Thief )
+ mFile_Thief->Read(ev, outBuf, inSize, outCount);
+ else
+ this->NewMissingIoError(mev);
+ }
+ else this->NewFileDownError(mev);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+morkStdioFile::Seek(nsIMdbEnv* mdbev, mork_pos inPos, mork_pos *aOutPos)
+{
+ mork_pos outPos = 0;
+ nsresult rv = NS_OK;
+ morkEnv *ev = morkEnv::FromMdbEnv(mdbev);
+
+ if ( this->IsOpenOrClosingNode() && this->FileActive() )
+ {
+ FILE* file = (FILE*) mStdioFile_File;
+ if ( file )
+ {
+ long where = MORK_FILESEEK(file, inPos, SEEK_SET);
+ if ( where >= 0 )
+ outPos = inPos;
+ else
+ this->new_stdio_file_fault(ev);
+ }
+ else if ( mFile_Thief )
+ mFile_Thief->Seek(mdbev, inPos, aOutPos);
+ else
+ this->NewMissingIoError(ev);
+ }
+ else this->NewFileDownError(ev);
+
+ *aOutPos = outPos;
+ return rv;
+}
+
+NS_IMETHODIMP
+morkStdioFile::Write(nsIMdbEnv* mdbev, const void* inBuf, mork_size inSize, mork_size *aOutSize)
+{
+ mork_num outCount = 0;
+ nsresult rv = NS_OK;
+ morkEnv *ev = morkEnv::FromMdbEnv(mdbev);
+ if ( this->IsOpenActiveAndMutableFile() )
+ {
+ FILE* file = (FILE*) mStdioFile_File;
+ if ( file )
+ {
+ fwrite(inBuf, 1, inSize, file);
+ if ( !ferror(file) )
+ outCount = inSize;
+ else
+ this->new_stdio_file_fault(ev);
+ }
+ else if ( mFile_Thief )
+ mFile_Thief->Write(mdbev, inBuf, inSize, &outCount);
+ else
+ this->NewMissingIoError(ev);
+ }
+ else this->NewFileDownError(ev);
+
+ *aOutSize = outCount;
+ return rv;
+}
+
+NS_IMETHODIMP
+morkStdioFile::Flush(nsIMdbEnv* mdbev)
+{
+ morkEnv *ev = morkEnv::FromMdbEnv(mdbev);
+ if ( this->IsOpenOrClosingNode() && this->FileActive() )
+ {
+ FILE* file = (FILE*) mStdioFile_File;
+ if ( file )
+ {
+ MORK_FILEFLUSH(file);
+
+ }
+ else if ( mFile_Thief )
+ mFile_Thief->Flush(mdbev);
+ else
+ this->NewMissingIoError(ev);
+ }
+ else this->NewFileDownError(ev);
+ return NS_OK;
+}
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+//protected: // protected non-poly morkStdioFile methods
+
+void
+morkStdioFile::new_stdio_file_fault(morkEnv* ev) const
+{
+ FILE* file = (FILE*) mStdioFile_File;
+
+ int copyErrno = errno; // facilitate seeing error in debugger
+
+ // bunch of stuff not ported here
+ if ( !copyErrno && file )
+ {
+ copyErrno = ferror(file);
+ errno = copyErrno;
+ }
+
+ this->NewFileErrnoError(ev);
+}
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+//public: // public non-poly morkStdioFile methods
+
+
+/*public non-poly*/
+morkStdioFile::morkStdioFile(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+: morkFile(ev, inUsage, ioHeap, ioSlotHeap)
+, mStdioFile_File( 0 )
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kStdioFile;
+}
+
+morkStdioFile::morkStdioFile(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap,
+ const char* inName, const char* inMode)
+ // calls OpenStdio() after construction
+: morkFile(ev, inUsage, ioHeap, ioSlotHeap)
+, mStdioFile_File( 0 )
+{
+ if ( ev->Good() )
+ this->OpenStdio(ev, inName, inMode);
+}
+
+morkStdioFile::morkStdioFile(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap,
+ void* ioFile, const char* inName, mork_bool inFrozen)
+ // calls UseStdio() after construction
+: morkFile(ev, inUsage, ioHeap, ioSlotHeap)
+, mStdioFile_File( 0 )
+{
+ if ( ev->Good() )
+ this->UseStdio(ev, ioFile, inName, inFrozen);
+}
+
+void
+morkStdioFile::OpenStdio(morkEnv* ev, const char* inName, const char* inMode)
+ // Open a new FILE with name inName, using mode flags from inMode.
+{
+ if ( ev->Good() )
+ {
+ if ( !inMode )
+ inMode = "";
+
+ mork_bool frozen = (*inMode == 'r'); // cursory attempt to note readonly
+
+ if ( this->IsOpenNode() )
+ {
+ if ( !this->FileActive() )
+ {
+ this->SetFileIoOpen(morkBool_kFalse);
+ if ( inName && *inName )
+ {
+ this->SetFileName(ev, inName);
+ if ( ev->Good() )
+ {
+ FILE* file = MORK_FILEOPEN(inName, inMode);
+ if ( file )
+ {
+ mStdioFile_File = file;
+ this->SetFileActive(morkBool_kTrue);
+ this->SetFileIoOpen(morkBool_kTrue);
+ this->SetFileFrozen(frozen);
+ }
+ else
+ this->new_stdio_file_fault(ev);
+ }
+ }
+ else ev->NewError("no file name");
+ }
+ else ev->NewError("file already active");
+ }
+ else this->NewFileDownError(ev);
+ }
+}
+
+void
+morkStdioFile::UseStdio(morkEnv* ev, void* ioFile, const char* inName,
+ mork_bool inFrozen)
+ // Use an existing file, like stdin/stdout/stderr, which should not
+ // have the io stream closed when the file is closed. The ioFile
+ // parameter must actually be of type FILE (but we don't want to make
+ // this header file include the stdio.h header file).
+{
+ if ( ev->Good() )
+ {
+ if ( this->IsOpenNode() )
+ {
+ if ( !this->FileActive() )
+ {
+ if ( ioFile )
+ {
+ this->SetFileIoOpen(morkBool_kFalse);
+ this->SetFileName(ev, inName);
+ if ( ev->Good() )
+ {
+ mStdioFile_File = ioFile;
+ this->SetFileActive(morkBool_kTrue);
+ this->SetFileFrozen(inFrozen);
+ }
+ }
+ else
+ ev->NilPointerError();
+ }
+ else ev->NewError("file already active");
+ }
+ else this->NewFileDownError(ev);
+ }
+}
+
+void
+morkStdioFile::CloseStdio(morkEnv* ev)
+ // Close the stream io if both and FileActive() and FileIoOpen(), but
+ // this does not close this instances (like CloseStdioFile() does).
+ // If stream io was made active by means of calling UseStdio(),
+ // then this method does little beyond marking the stream inactive
+ // because FileIoOpen() is false.
+{
+ if ( mStdioFile_File && this->FileActive() && this->FileIoOpen() )
+ {
+ FILE* file = (FILE*) mStdioFile_File;
+ if ( MORK_FILECLOSE(file) < 0 )
+ this->new_stdio_file_fault(ev);
+
+ mStdioFile_File = 0;
+ this->SetFileActive(morkBool_kFalse);
+ this->SetFileIoOpen(morkBool_kFalse);
+ }
+}
+
+
+NS_IMETHODIMP
+morkStdioFile::Steal(nsIMdbEnv* ev, nsIMdbFile* ioThief)
+ // If this file is a file version branch created by calling AcquireBud(),
+ // BecomeTrunk() causes this file's content to replace the original
+ // file's content, typically by assuming the original file's identity.
+{
+ morkEnv *mev = morkEnv::FromMdbEnv(ev);
+ if ( mStdioFile_File && FileActive() && FileIoOpen() )
+ {
+ FILE* file = (FILE*) mStdioFile_File;
+ if ( MORK_FILECLOSE(file) < 0 )
+ new_stdio_file_fault(mev);
+
+ mStdioFile_File = 0;
+ }
+ SetThief(mev, ioThief);
+ return NS_OK;
+}
+
+
+#if defined(MORK_WIN)
+
+void mork_fileflush(FILE * file)
+{
+ fflush(file);
+}
+
+#endif /*MORK_WIN*/
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkFile.h b/components/mork/src/morkFile.h
new file mode 100644
index 000000000..39242ce73
--- /dev/null
+++ b/components/mork/src/morkFile.h
@@ -0,0 +1,355 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _MORKFILE_
+#define _MORKFILE_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*=============================================================================
+ * morkFile: abstract file interface
+ */
+
+#define morkDerived_kFile /*i*/ 0x4669 /* ascii 'Fi' */
+
+class morkFile /*d*/ : public morkObject, public nsIMdbFile { /* ````` simple file API ````` */
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+// public: // slots inherited from morkObject (meant to inform only)
+
+ // mork_color mBead_Color; // ID for this bead
+ // morkHandle* mObject_Handle; // weak ref to handle for this object
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+protected: // protected morkFile members (similar to public domain IronDoc)
+ virtual ~morkFile(); // assert that CloseFile() executed earlier
+
+ mork_u1 mFile_Frozen; // 'F' => file allows only read access
+ mork_u1 mFile_DoTrace; // 'T' trace if ev->DoTrace()
+ mork_u1 mFile_IoOpen; // 'O' => io stream is open (& needs a close)
+ mork_u1 mFile_Active; // 'A' => file is active and usable
+
+ nsIMdbHeap* mFile_SlotHeap; // heap for Name and other allocated slots
+ char* mFile_Name; // can be nil if SetFileName() is never called
+ // mFile_Name convention: managed with morkEnv::CopyString()/FreeString()
+
+ nsIMdbFile* mFile_Thief; // from a call to orkinFile::Steal()
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ NS_DECL_ISUPPORTS_INHERITED
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseFile() only if open
+
+public: // morkFile construction & destruction
+ morkFile(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ nsIMdbHeap* ioSlotHeap);
+ void CloseFile(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkFile(const morkFile& other);
+ morkFile& operator=(const morkFile& other);
+
+public: // dynamic type identification
+ mork_bool IsFile() const
+ { return IsNode() && mNode_Derived == morkDerived_kFile; }
+// } ===== end morkNode methods =====
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // public static standard file creation entry point
+
+ static morkFile* OpenOldFile(morkEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath, mork_bool inFrozen);
+ // Choose some subclass of morkFile to instantiate, in order to read
+ // (and write if not frozen) the file known by inFilePath. The file
+ // returned should be open and ready for use, and presumably positioned
+ // at the first byte position of the file. The exact manner in which
+ // files must be opened is considered a subclass specific detail, and
+ // other portions or Mork source code don't want to know how it's done.
+
+ static morkFile* CreateNewFile(morkEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath);
+ // Choose some subclass of morkFile to instantiate, in order to read
+ // (and write if not frozen) the file known by inFilePath. The file
+ // returned should be created and ready for use, and presumably positioned
+ // at the first byte position of the file. The exact manner in which
+ // files must be opened is considered a subclass specific detail, and
+ // other portions or Mork source code don't want to know how it's done.
+
+public: // non-poly morkFile methods
+
+ mork_bool FileFrozen() const { return mFile_Frozen == 'F'; }
+ mork_bool FileDoTrace() const { return mFile_DoTrace == 'T'; }
+ mork_bool FileIoOpen() const { return mFile_IoOpen == 'O'; }
+ mork_bool FileActive() const { return mFile_Active == 'A'; }
+
+ void SetFileFrozen(mork_bool b) { mFile_Frozen = (mork_u1) ((b)? 'F' : 0); }
+ void SetFileDoTrace(mork_bool b) { mFile_DoTrace = (mork_u1) ((b)? 'T' : 0); }
+ void SetFileIoOpen(mork_bool b) { mFile_IoOpen = (mork_u1) ((b)? 'O' : 0); }
+ void SetFileActive(mork_bool b) { mFile_Active = (mork_u1) ((b)? 'A' : 0); }
+
+
+ mork_bool IsOpenActiveAndMutableFile() const
+ { return ( IsOpenNode() && FileActive() && !FileFrozen() ); }
+ // call IsOpenActiveAndMutableFile() before writing a file
+
+ mork_bool IsOpenAndActiveFile() const
+ { return ( this->IsOpenNode() && this->FileActive() ); }
+ // call IsOpenAndActiveFile() before using a file
+
+
+ nsIMdbFile* GetThief() const { return mFile_Thief; }
+ void SetThief(morkEnv* ev, nsIMdbFile* ioThief); // ioThief can be nil
+
+ const char* GetFileNameString() const { return mFile_Name; }
+ void SetFileName(morkEnv* ev, const char* inName); // inName can be nil
+ static void NilSlotHeapError(morkEnv* ev);
+ static void NilFileNameError(morkEnv* ev);
+ static void NonFileTypeError(morkEnv* ev);
+
+ void NewMissingIoError(morkEnv* ev) const;
+
+ void NewFileDownError(morkEnv* ev) const;
+ // call NewFileDownError() when either IsOpenAndActiveFile()
+ // is false, or when IsOpenActiveAndMutableFile() is false.
+
+ void NewFileErrnoError(morkEnv* ev) const;
+ // call NewFileErrnoError() to convert std C errno into AB fault
+
+ mork_size WriteNewlines(morkEnv* ev, mork_count inNewlines);
+ // WriteNewlines() returns the number of bytes written.
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakFile(morkFile* me,
+ morkEnv* ev, morkFile** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongFile(morkFile* me,
+ morkEnv* ev, morkFile** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+public:
+ virtual mork_pos Length(morkEnv* ev) const = 0; // eof
+ // nsIMdbFile methods
+ NS_IMETHOD Tell(nsIMdbEnv* ev, mdb_pos* outPos) const override = 0;
+ NS_IMETHOD Seek(nsIMdbEnv* ev, mdb_pos inPos, mdb_pos *outPos) override = 0;
+ NS_IMETHOD Eof(nsIMdbEnv* ev, mdb_pos* outPos) override;
+ // } ----- end pos methods -----
+
+ // { ----- begin read methods -----
+ NS_IMETHOD Read(nsIMdbEnv* ev, void* outBuf, mdb_size inSize,
+ mdb_size* outActualSize) override = 0;
+ NS_IMETHOD Get(nsIMdbEnv* ev, void* outBuf, mdb_size inSize,
+ mdb_pos inPos, mdb_size* outActualSize) override;
+ // } ----- end read methods -----
+
+ // { ----- begin write methods -----
+ NS_IMETHOD Write(nsIMdbEnv* ev, const void* inBuf, mdb_size inSize,
+ mdb_size* outActualSize) override = 0;
+ NS_IMETHOD Put(nsIMdbEnv* ev, const void* inBuf, mdb_size inSize,
+ mdb_pos inPos, mdb_size* outActualSize) override;
+ NS_IMETHOD Flush(nsIMdbEnv* ev) override = 0;
+ // } ----- end attribute methods -----
+
+ // { ----- begin path methods -----
+ NS_IMETHOD Path(nsIMdbEnv* ev, mdbYarn* outFilePath) override ;
+ // } ----- end path methods -----
+
+ // { ----- begin replacement methods -----
+ NS_IMETHOD Steal(nsIMdbEnv* ev, nsIMdbFile* ioThief) override = 0;
+ NS_IMETHOD Thief(nsIMdbEnv* ev, nsIMdbFile** acqThief) override;
+ // } ----- end replacement methods -----
+
+ // { ----- begin versioning methods -----
+ NS_IMETHOD BecomeTrunk(nsIMdbEnv* ev) override = 0;
+
+ NS_IMETHOD AcquireBud(nsIMdbEnv* ev, nsIMdbHeap* ioHeap,
+ nsIMdbFile** acqBud) override = 0;
+ // } ----- end versioning methods -----
+
+// } ===== end nsIMdbFile methods =====
+
+};
+
+/*=============================================================================
+ * morkStdioFile: concrete file using standard C file io
+ */
+
+#define morkDerived_kStdioFile /*i*/ 0x7346 /* ascii 'sF' */
+
+class morkStdioFile /*d*/ : public morkFile { /* `` copied from IronDoc `` */
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+protected: // protected morkStdioFile members
+
+ void* mStdioFile_File;
+ // actually type FILE*, but using opaque void* type
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseStdioFile() only if open
+ virtual ~morkStdioFile(); // assert that CloseStdioFile() executed earlier
+
+public: // morkStdioFile construction & destruction
+ morkStdioFile(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap);
+ void CloseStdioFile(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkStdioFile(const morkStdioFile& other);
+ morkStdioFile& operator=(const morkStdioFile& other);
+
+public: // dynamic type identification
+ mork_bool IsStdioFile() const
+ { return IsNode() && mNode_Derived == morkDerived_kStdioFile; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ static void NonStdioFileTypeError(morkEnv* ev);
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // compatible with the morkFile::OpenOldFile() entry point
+
+ static morkStdioFile* OpenOldStdioFile(morkEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath, mork_bool inFrozen);
+
+ static morkStdioFile* CreateNewStdioFile(morkEnv* ev, nsIMdbHeap* ioHeap,
+ const char* inFilePath);
+
+ virtual mork_pos Length(morkEnv* ev) const override; // eof
+
+ NS_IMETHOD Tell(nsIMdbEnv* ev, mdb_pos* outPos) const override;
+ NS_IMETHOD Seek(nsIMdbEnv* ev, mdb_pos inPos, mdb_pos *outPos) override;
+// NS_IMETHOD Eof(nsIMdbEnv* ev, mdb_pos* outPos);
+ // } ----- end pos methods -----
+
+ // { ----- begin read methods -----
+ NS_IMETHOD Read(nsIMdbEnv* ev, void* outBuf, mdb_size inSize,
+ mdb_size* outActualSize) override;
+
+ // { ----- begin write methods -----
+ NS_IMETHOD Write(nsIMdbEnv* ev, const void* inBuf, mdb_size inSize,
+ mdb_size* outActualSize) override;
+// NS_IMETHOD Put(nsIMdbEnv* ev, const void* inBuf, mdb_size inSize,
+// mdb_pos inPos, mdb_size* outActualSize);
+ NS_IMETHOD Flush(nsIMdbEnv* ev) override;
+ // } ----- end attribute methods -----
+
+ NS_IMETHOD Steal(nsIMdbEnv* ev, nsIMdbFile* ioThief) override;
+
+ // { ----- begin versioning methods -----
+ NS_IMETHOD BecomeTrunk(nsIMdbEnv* ev) override;
+
+ NS_IMETHOD AcquireBud(nsIMdbEnv* ev, nsIMdbHeap* ioHeap,
+ nsIMdbFile** acqBud) override;
+ // } ----- end versioning methods -----
+
+// } ===== end nsIMdbFile methods =====
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+protected: // protected non-poly morkStdioFile methods
+
+ void new_stdio_file_fault(morkEnv* ev) const;
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // public non-poly morkStdioFile methods
+
+ morkStdioFile(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap,
+ const char* inName, const char* inMode);
+ // calls OpenStdio() after construction
+
+ morkStdioFile(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap,
+ void* ioFile, const char* inName, mork_bool inFrozen);
+ // calls UseStdio() after construction
+
+ void OpenStdio(morkEnv* ev, const char* inName, const char* inMode);
+ // Open a new FILE with name inName, using mode flags from inMode.
+
+ void UseStdio(morkEnv* ev, void* ioFile, const char* inName,
+ mork_bool inFrozen);
+ // Use an existing file, like stdin/stdout/stderr, which should not
+ // have the io stream closed when the file is closed. The ioFile
+ // parameter must actually be of type FILE (but we don't want to make
+ // this header file include the stdio.h header file).
+
+ void CloseStdio(morkEnv* ev);
+ // Close the stream io if both and FileActive() and FileIoOpen(), but
+ // this does not close this instances (like CloseStdioFile() does).
+ // If stream io was made active by means of calling UseStdio(),
+ // then this method does little beyond marking the stream inactive
+ // because FileIoOpen() is false.
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakStdioFile(morkStdioFile* me,
+ morkEnv* ev, morkStdioFile** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongStdioFile(morkStdioFile* me,
+ morkEnv* ev, morkStdioFile** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKFILE_ */
diff --git a/components/mork/src/morkHandle.cpp b/components/mork/src/morkHandle.cpp
new file mode 100644
index 000000000..f6d426887
--- /dev/null
+++ b/components/mork/src/morkHandle.cpp
@@ -0,0 +1,423 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKFACTORY_
+#include "morkFactory.h"
+#endif
+
+#ifndef _MORKPOOL_
+#include "morkPool.h"
+#endif
+
+#ifndef _MORKHANDLE_
+#include "morkHandle.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkHandle::CloseMorkNode(morkEnv* ev) // CloseHandle() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseHandle(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkHandle::~morkHandle() // assert CloseHandle() executed earlier
+{
+ MORK_ASSERT(mHandle_Env==0);
+ MORK_ASSERT(mHandle_Face==0);
+ MORK_ASSERT(mHandle_Object==0);
+ MORK_ASSERT(mHandle_Magic==0);
+ MORK_ASSERT(mHandle_Tag==morkHandle_kTag); // should still have correct tag
+}
+
+/*public non-poly*/
+morkHandle::morkHandle(morkEnv* ev, // note morkUsage is always morkUsage_kPool
+ morkHandleFace* ioFace, // must not be nil, cookie for this handle
+ morkObject* ioObject, // must not be nil, the object for this handle
+ mork_magic inMagic) // magic sig to denote specific subclass
+: morkNode(ev, morkUsage::kPool, (nsIMdbHeap*) 0L)
+, mHandle_Tag( 0 )
+, mHandle_Env( ev )
+, mHandle_Face( ioFace )
+, mHandle_Object( 0 )
+, mHandle_Magic( 0 )
+{
+ if ( ioFace && ioObject )
+ {
+ if ( ev->Good() )
+ {
+ mHandle_Tag = morkHandle_kTag;
+ morkObject::SlotStrongObject(ioObject, ev, &mHandle_Object);
+ morkHandle::SlotWeakHandle(this, ev, &ioObject->mObject_Handle);
+ if ( ev->Good() )
+ {
+ mHandle_Magic = inMagic;
+ mNode_Derived = morkDerived_kHandle;
+ }
+ }
+ else
+ ev->CantMakeWhenBadError();
+ }
+ else
+ ev->NilPointerError();
+}
+
+/*public non-poly*/ void
+morkHandle::CloseHandle(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ morkObject* obj = mHandle_Object;
+ mork_bool objDidRefSelf = ( obj && obj->mObject_Handle == this );
+ if ( objDidRefSelf )
+ obj->mObject_Handle = 0; // drop the reference
+
+ morkObject::SlotStrongObject((morkObject*) 0, ev, &mHandle_Object);
+ mHandle_Magic = 0;
+ // note mHandle_Tag MUST stay morkHandle_kTag for morkNode::ZapOld()
+ this->MarkShut();
+
+ if ( objDidRefSelf )
+ this->CutWeakRef(ev); // do last, because it might self destroy
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+void morkHandle::NilFactoryError(morkEnv* ev) const
+{
+ ev->NewError("nil mHandle_Factory");
+}
+
+void morkHandle::NilHandleObjectError(morkEnv* ev) const
+{
+ ev->NewError("nil mHandle_Object");
+}
+
+void morkHandle::NonNodeObjectError(morkEnv* ev) const
+{
+ ev->NewError("non-node mHandle_Object");
+}
+
+void morkHandle::NonOpenObjectError(morkEnv* ev) const
+{
+ ev->NewError("non-open mHandle_Object");
+}
+
+void morkHandle::NewBadMagicHandleError(morkEnv* ev, mork_magic inMagic) const
+{
+ MORK_USED_1(inMagic);
+ ev->NewError("wrong mHandle_Magic");
+}
+
+void morkHandle::NewDownHandleError(morkEnv* ev) const
+{
+ if ( this->IsHandle() )
+ {
+ if ( this->GoodHandleTag() )
+ {
+ if ( this->IsOpenNode() )
+ ev->NewError("unknown down morkHandle error");
+ else
+ this->NonOpenNodeError(ev);
+ }
+ else
+ ev->NewError("wrong morkHandle tag");
+ }
+ else
+ ev->NewError("non morkHandle");
+}
+
+morkObject* morkHandle::GetGoodHandleObject(morkEnv* ev,
+ mork_bool inMutable, mork_magic inMagicType, mork_bool inClosedOkay) const
+{
+ morkObject* outObject = 0;
+ if ( this->IsHandle() && this->GoodHandleTag() &&
+ ( inClosedOkay || this->IsOpenNode() ) )
+ {
+ if ( !inMagicType || mHandle_Magic == inMagicType )
+ {
+ morkObject* obj = this->mHandle_Object;
+ if ( obj )
+ {
+ if ( obj->IsNode() )
+ {
+ if ( inClosedOkay || obj->IsOpenNode() )
+ {
+ if ( this->IsMutable() || !inMutable )
+ outObject = obj;
+ else
+ this->NonMutableNodeError(ev);
+ }
+ else
+ this->NonOpenObjectError(ev);
+ }
+ else
+ this->NonNodeObjectError(ev);
+ }
+ else if ( !inClosedOkay )
+ this->NilHandleObjectError(ev);
+ }
+ else
+ this->NewBadMagicHandleError(ev, inMagicType);
+ }
+ else
+ this->NewDownHandleError(ev);
+
+ MORK_ASSERT(outObject || inClosedOkay);
+ return outObject;
+}
+
+
+morkEnv*
+morkHandle::CanUseHandle(nsIMdbEnv* mev, mork_bool inMutable,
+ mork_bool inClosedOkay, nsresult* outErr) const
+{
+ morkEnv* outEnv = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkObject* obj = this->GetGoodHandleObject(ev, inMutable,
+ /*magic*/ 0, inClosedOkay);
+ if ( obj )
+ {
+ outEnv = ev;
+ }
+ *outErr = ev->AsErr();
+ }
+ MORK_ASSERT(outEnv || inClosedOkay);
+ return outEnv;
+}
+
+// { ===== begin nsIMdbObject methods =====
+
+// { ----- begin attribute methods -----
+/*virtual*/ nsresult
+morkHandle::Handle_IsFrozenMdbObject(nsIMdbEnv* mev, mdb_bool* outIsReadonly)
+{
+ nsresult outErr = NS_OK;
+ mdb_bool readOnly = mdbBool_kTrue;
+
+ morkEnv* ev = CanUseHandle(mev, /*inMutable*/ morkBool_kFalse,
+ /*inClosedOkay*/ morkBool_kTrue, &outErr);
+ if ( ev )
+ {
+ readOnly = mHandle_Object->IsFrozen();
+
+ outErr = ev->AsErr();
+ }
+ MORK_ASSERT(outIsReadonly);
+ if ( outIsReadonly )
+ *outIsReadonly = readOnly;
+
+ return outErr;
+}
+// same as nsIMdbPort::GetIsPortReadonly() when this object is inside a port.
+// } ----- end attribute methods -----
+
+// { ----- begin factory methods -----
+/*virtual*/ nsresult
+morkHandle::Handle_GetMdbFactory(nsIMdbEnv* mev, nsIMdbFactory** acqFactory)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbFactory* handle = 0;
+
+ morkEnv* ev = CanUseHandle(mev, /*inMutable*/ morkBool_kFalse,
+ /*inClosedOkay*/ morkBool_kTrue, &outErr);
+ if ( ev )
+ {
+ morkFactory* factory = ev->mEnv_Factory;
+ if ( factory )
+ {
+ handle = factory;
+ NS_ADDREF(handle);
+ }
+ else
+ this->NilFactoryError(ev);
+
+ outErr = ev->AsErr();
+ }
+
+ MORK_ASSERT(acqFactory);
+ if ( acqFactory )
+ *acqFactory = handle;
+
+ return outErr;
+}
+// } ----- end factory methods -----
+
+// { ----- begin ref counting for well-behaved cyclic graphs -----
+/*virtual*/ nsresult
+morkHandle::Handle_GetWeakRefCount(nsIMdbEnv* mev, // weak refs
+ mdb_count* outCount)
+{
+ nsresult outErr = NS_OK;
+ mdb_count count = 0;
+
+ morkEnv* ev = CanUseHandle(mev, /*inMutable*/ morkBool_kFalse,
+ /*inClosedOkay*/ morkBool_kTrue, &outErr);
+ if ( ev )
+ {
+ count = this->WeakRefsOnly();
+
+ outErr = ev->AsErr();
+ }
+ MORK_ASSERT(outCount);
+ if ( outCount )
+ *outCount = count;
+
+ return outErr;
+}
+/*virtual*/ nsresult
+morkHandle::Handle_GetStrongRefCount(nsIMdbEnv* mev, // strong refs
+ mdb_count* outCount)
+{
+ nsresult outErr = NS_OK;
+ mdb_count count = 0;
+
+ morkEnv* ev = CanUseHandle(mev, /*inMutable*/ morkBool_kFalse,
+ /*inClosedOkay*/ morkBool_kTrue, &outErr);
+ if ( ev )
+ {
+ count = this->StrongRefsOnly();
+
+ outErr = ev->AsErr();
+ }
+ MORK_ASSERT(outCount);
+ if ( outCount )
+ *outCount = count;
+
+ return outErr;
+}
+
+/*virtual*/ nsresult
+morkHandle::Handle_AddWeakRef(nsIMdbEnv* mev)
+{
+ nsresult outErr = NS_OK;
+
+ morkEnv* ev = CanUseHandle(mev, /*inMutable*/ morkBool_kFalse,
+ /*inClosedOkay*/ morkBool_kTrue, &outErr);
+ if ( ev )
+ {
+ this->AddWeakRef(ev);
+ outErr = ev->AsErr();
+ }
+
+ return outErr;
+}
+/*virtual*/ nsresult
+morkHandle::Handle_AddStrongRef(nsIMdbEnv* mev)
+{
+ nsresult outErr = NS_OK;
+
+ morkEnv* ev = CanUseHandle(mev, /*inMutable*/ morkBool_kFalse,
+ /*inClosedOkay*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ this->AddStrongRef(ev);
+ outErr = ev->AsErr();
+ }
+
+ return outErr;
+}
+
+/*virtual*/ nsresult
+morkHandle::Handle_CutWeakRef(nsIMdbEnv* mev)
+{
+ nsresult outErr = NS_OK;
+
+ morkEnv* ev = CanUseHandle(mev, /*inMutable*/ morkBool_kFalse,
+ /*inClosedOkay*/ morkBool_kTrue, &outErr);
+ if ( ev )
+ {
+ this->CutWeakRef(ev);
+ outErr = ev->AsErr();
+ }
+
+ return outErr;
+}
+/*virtual*/ nsresult
+morkHandle::Handle_CutStrongRef(nsIMdbEnv* mev)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = CanUseHandle(mev, /*inMutable*/ morkBool_kFalse,
+ /*inClosedOkay*/ morkBool_kTrue, &outErr);
+ if ( ev )
+ {
+ this->CutStrongRef(ev);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+/*virtual*/ nsresult
+morkHandle::Handle_CloseMdbObject(nsIMdbEnv* mev)
+// called at strong refs zero
+{
+ // if only one ref, Handle_CutStrongRef will clean up better.
+ if (mNode_Uses == 1)
+ return Handle_CutStrongRef(mev);
+
+ nsresult outErr = NS_OK;
+
+ if ( this->IsNode() && this->IsOpenNode() )
+ {
+ morkEnv* ev = CanUseHandle(mev, /*inMutable*/ morkBool_kFalse,
+ /*inClosedOkay*/ morkBool_kTrue, &outErr);
+ if ( ev )
+ {
+ morkObject* object = mHandle_Object;
+ if ( object && object->IsNode() && object->IsOpenNode() )
+ object->CloseMorkNode(ev);
+
+ this->CloseMorkNode(ev);
+ outErr = ev->AsErr();
+ }
+ }
+ return outErr;
+}
+
+/*virtual*/ nsresult
+morkHandle::Handle_IsOpenMdbObject(nsIMdbEnv* mev, mdb_bool* outOpen)
+{
+ MORK_USED_1(mev);
+ nsresult outErr = NS_OK;
+
+ MORK_ASSERT(outOpen);
+ if ( outOpen )
+ *outOpen = this->IsOpenNode();
+
+ return outErr;
+}
+// } ----- end ref counting -----
+
+// } ===== end nsIMdbObject methods =====
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkHandle.h b/components/mork/src/morkHandle.h
new file mode 100644
index 000000000..8ef7e9057
--- /dev/null
+++ b/components/mork/src/morkHandle.h
@@ -0,0 +1,176 @@
+/* -*- 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/. */
+
+#ifndef _MORKHANDLE_
+#define _MORKHANDLE_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKDEQUE_
+#include "morkDeque.h"
+#endif
+
+#ifndef _MORKPOOL_
+#include "morkPool.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class morkPool;
+class morkObject;
+class morkFactory;
+
+#define morkDerived_kHandle /*i*/ 0x486E /* ascii 'Hn' */
+#define morkHandle_kTag 0x68416E44 /* ascii 'hAnD' */
+
+/*| morkHandle:
+|*/
+class morkHandle : public morkNode {
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+public: // state is public because the entire Mork system is private
+
+ mork_u4 mHandle_Tag; // must equal morkHandle_kTag
+ morkEnv* mHandle_Env; // pool that allocated this handle
+ morkHandleFace* mHandle_Face; // cookie from pool containing this
+ morkObject* mHandle_Object; // object this handle wraps for MDB API
+ mork_magic mHandle_Magic; // magic sig different in each subclass
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseHandle() only if open
+ virtual ~morkHandle(); // assert that CloseHandle() executed earlier
+
+public: // morkHandle construction & destruction
+ morkHandle(morkEnv* ev, // note morkUsage is always morkUsage_kPool
+ morkHandleFace* ioFace, // must not be nil, cookie for this handle
+ morkObject* ioObject, // must not be nil, the object for this handle
+ mork_magic inMagic); // magic sig to denote specific subclass
+ void CloseHandle(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkHandle(const morkHandle& other);
+ morkHandle& operator=(const morkHandle& other);
+
+protected: // special case empty construction for morkHandleFrame
+ friend class morkHandleFrame;
+ morkHandle() { }
+
+public: // dynamic type identification
+ mork_bool IsHandle() const
+ { return IsNode() && mNode_Derived == morkDerived_kHandle; }
+// } ===== end morkNode methods =====
+
+public: // morkHandle memory management operators
+ void* operator new(size_t inSize, morkPool& ioPool, morkZone& ioZone, morkEnv* ev) CPP_THROW_NEW
+ { return ioPool.NewHandle(ev, inSize, &ioZone); }
+
+ void* operator new(size_t inSize, morkPool& ioPool, morkEnv* ev) CPP_THROW_NEW
+ { return ioPool.NewHandle(ev, inSize, (morkZone*) 0); }
+
+ void* operator new(size_t inSize, morkHandleFace* ioFace) CPP_THROW_NEW
+ { MORK_USED_1(inSize); return ioFace; }
+
+
+public: // other handle methods
+ mork_bool GoodHandleTag() const
+ { return mHandle_Tag == morkHandle_kTag; }
+
+ void NewBadMagicHandleError(morkEnv* ev, mork_magic inMagic) const;
+ void NewDownHandleError(morkEnv* ev) const;
+ void NilFactoryError(morkEnv* ev) const;
+ void NilHandleObjectError(morkEnv* ev) const;
+ void NonNodeObjectError(morkEnv* ev) const;
+ void NonOpenObjectError(morkEnv* ev) const;
+
+ morkObject* GetGoodHandleObject(morkEnv* ev,
+ mork_bool inMutable, mork_magic inMagicType, mork_bool inClosedOkay) const;
+
+public: // interface supporting mdbObject methods
+
+ morkEnv* CanUseHandle(nsIMdbEnv* mev, mork_bool inMutable,
+ mork_bool inClosedOkay, nsresult* outErr) const;
+
+ // { ----- begin mdbObject style methods -----
+ nsresult Handle_IsFrozenMdbObject(nsIMdbEnv* ev, mdb_bool* outIsReadonly);
+
+ nsresult Handle_GetMdbFactory(nsIMdbEnv* ev, nsIMdbFactory** acqFactory);
+ nsresult Handle_GetWeakRefCount(nsIMdbEnv* ev, mdb_count* outCount);
+ nsresult Handle_GetStrongRefCount(nsIMdbEnv* ev, mdb_count* outCount);
+
+ nsresult Handle_AddWeakRef(nsIMdbEnv* ev);
+ nsresult Handle_AddStrongRef(nsIMdbEnv* ev);
+
+ nsresult Handle_CutWeakRef(nsIMdbEnv* ev);
+ nsresult Handle_CutStrongRef(nsIMdbEnv* ev);
+
+ nsresult Handle_CloseMdbObject(nsIMdbEnv* ev);
+ nsresult Handle_IsOpenMdbObject(nsIMdbEnv* ev, mdb_bool* outOpen);
+ // } ----- end mdbObject style methods -----
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakHandle(morkHandle* me,
+ morkEnv* ev, morkHandle** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongHandle(morkHandle* me,
+ morkEnv* ev, morkHandle** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+#define morkHandleFrame_kPadSlotCount 4
+
+/*| morkHandleFrame: an object format used for allocating and maintaining
+**| instances of morkHandle, with leading slots used to maintain this in a
+**| linked list, and following slots to provide extra footprint that might
+**| be needed by any morkHandle subclasses that include very little extra
+**| space (by virtue of the fact that each morkHandle subclass is expected
+**| to multiply inherit from another base class that has only abstract methods
+**| for space overhead related only to some vtable representation).
+|*/
+class morkHandleFrame {
+public:
+ morkLink mHandleFrame_Link; // list storage without trampling Handle
+ morkHandle mHandleFrame_Handle;
+ mork_ip mHandleFrame_Padding[ morkHandleFrame_kPadSlotCount ];
+
+public:
+ morkHandle* AsHandle() { return &mHandleFrame_Handle; }
+
+ morkHandleFrame() {} // actually, morkHandleFrame never gets constructed
+
+private: // copying is not allowed
+ morkHandleFrame(const morkHandleFrame& other);
+ morkHandleFrame& operator=(const morkHandleFrame& other);
+};
+
+#define morkHandleFrame_kHandleOffset \
+ mork_OffsetOf(morkHandleFrame,mHandleFrame_Handle)
+
+#define morkHandle_AsHandleFrame(h) ((h)->mHandle_Block , \
+ ((morkHandleFrame*) (((mork_u1*)(h)) - morkHandleFrame_kHandleOffset)))
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKHANDLE_ */
diff --git a/components/mork/src/morkIntMap.cpp b/components/mork/src/morkIntMap.cpp
new file mode 100644
index 000000000..9164b021d
--- /dev/null
+++ b/components/mork/src/morkIntMap.cpp
@@ -0,0 +1,238 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKINTMAP_
+#include "morkIntMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkIntMap::CloseMorkNode(morkEnv* ev) // CloseIntMap() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseIntMap(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkIntMap::~morkIntMap() // assert CloseIntMap() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkIntMap::morkIntMap(morkEnv* ev,
+ const morkUsage& inUsage, mork_size inValSize,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap, mork_bool inHoldChanges)
+: morkMap(ev, inUsage, ioHeap, sizeof(mork_u4), inValSize,
+ morkIntMap_kStartSlotCount, ioSlotHeap, inHoldChanges)
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kIntMap;
+}
+
+/*public non-poly*/ void
+morkIntMap::CloseIntMap(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ this->CloseMap(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+// { ===== begin morkMap poly interface =====
+/*virtual*/ mork_bool // *((mork_u4*) inKeyA) == *((mork_u4*) inKeyB)
+morkIntMap::Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const
+{
+ MORK_USED_1(ev);
+ return *((const mork_u4*) inKeyA) == *((const mork_u4*) inKeyB);
+}
+
+/*virtual*/ mork_u4 // some integer function of *((mork_u4*) inKey)
+morkIntMap::Hash(morkEnv* ev, const void* inKey) const
+{
+ MORK_USED_1(ev);
+ return *((const mork_u4*) inKey);
+}
+// } ===== end morkMap poly interface =====
+
+mork_bool
+morkIntMap::AddInt(morkEnv* ev, mork_u4 inKey, void* ioAddress)
+ // the AddInt() method return value equals ev->Good().
+{
+ if ( ev->Good() )
+ {
+ this->Put(ev, &inKey, &ioAddress,
+ /*key*/ (void*) 0, /*val*/ (void*) 0, (mork_change**) 0);
+ }
+
+ return ev->Good();
+}
+
+mork_bool
+morkIntMap::CutInt(morkEnv* ev, mork_u4 inKey)
+{
+ return this->Cut(ev, &inKey, /*key*/ (void*) 0, /*val*/ (void*) 0,
+ (mork_change**) 0);
+}
+
+void*
+morkIntMap::GetInt(morkEnv* ev, mork_u4 inKey)
+ // Note the returned val does NOT have an increase in refcount for this.
+{
+ void* val = 0; // old val in the map
+ this->Get(ev, &inKey, /*key*/ (void*) 0, &val, (mork_change**) 0);
+
+ return val;
+}
+
+mork_bool
+morkIntMap::HasInt(morkEnv* ev, mork_u4 inKey)
+ // Note the returned val does NOT have an increase in refcount for this.
+{
+ return this->Get(ev, &inKey, /*key*/ (void*) 0, /*val*/ (void*) 0,
+ (mork_change**) 0);
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#ifdef MORK_POINTER_MAP_IMPL
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkPointerMap::CloseMorkNode(morkEnv* ev) // ClosePointerMap() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->ClosePointerMap(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkPointerMap::~morkPointerMap() // assert ClosePointerMap() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkPointerMap::morkPointerMap(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+: morkMap(ev, inUsage, ioHeap, sizeof(void*), sizeof(void*),
+ morkPointerMap_kStartSlotCount, ioSlotHeap,
+ /*inHoldChanges*/ morkBool_kFalse)
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kPointerMap;
+}
+
+/*public non-poly*/ void
+morkPointerMap::ClosePointerMap(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ this->CloseMap(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+// { ===== begin morkMap poly interface =====
+/*virtual*/ mork_bool // *((void**) inKeyA) == *((void**) inKeyB)
+morkPointerMap::Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const
+{
+ MORK_USED_1(ev);
+ return *((const void**) inKeyA) == *((const void**) inKeyB);
+}
+
+/*virtual*/ mork_u4 // some integer function of *((mork_u4*) inKey)
+morkPointerMap::Hash(morkEnv* ev, const void* inKey) const
+{
+ MORK_USED_1(ev);
+ return *((const mork_u4*) inKey);
+}
+// } ===== end morkMap poly interface =====
+
+mork_bool
+morkPointerMap::AddPointer(morkEnv* ev, void* inKey, void* ioAddress)
+ // the AddPointer() method return value equals ev->Good().
+{
+ if ( ev->Good() )
+ {
+ this->Put(ev, &inKey, &ioAddress,
+ /*key*/ (void*) 0, /*val*/ (void*) 0, (mork_change**) 0);
+ }
+
+ return ev->Good();
+}
+
+mork_bool
+morkPointerMap::CutPointer(morkEnv* ev, void* inKey)
+{
+ return this->Cut(ev, &inKey, /*key*/ (void*) 0, /*val*/ (void*) 0,
+ (mork_change**) 0);
+}
+
+void*
+morkPointerMap::GetPointer(morkEnv* ev, void* inKey)
+ // Note the returned val does NOT have an increase in refcount for this.
+{
+ void* val = 0; // old val in the map
+ this->Get(ev, &inKey, /*key*/ (void*) 0, &val, (mork_change**) 0);
+
+ return val;
+}
+
+mork_bool
+morkPointerMap::HasPointer(morkEnv* ev, void* inKey)
+ // Note the returned val does NOT have an increase in refcount for this.
+{
+ return this->Get(ev, &inKey, /*key*/ (void*) 0, /*val*/ (void*) 0,
+ (mork_change**) 0);
+}
+#endif /*MORK_POINTER_MAP_IMPL*/
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkIntMap.h b/components/mork/src/morkIntMap.h
new file mode 100644
index 000000000..543f7e59a
--- /dev/null
+++ b/components/mork/src/morkIntMap.h
@@ -0,0 +1,145 @@
+/* -*- 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/. */
+
+#ifndef _MORKINTMAP_
+#define _MORKINTMAP_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kIntMap /*i*/ 0x694D /* ascii 'iM' */
+
+#define morkIntMap_kStartSlotCount 256
+
+/*| morkIntMap: maps mork_token -> morkNode
+|*/
+class morkIntMap : public morkMap { // for mapping tokens to maps
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseIntMap() only if open
+ virtual ~morkIntMap(); // assert that CloseIntMap() executed earlier
+
+public: // morkMap construction & destruction
+
+ // keySize for morkIntMap equals sizeof(mork_u4)
+ morkIntMap(morkEnv* ev, const morkUsage& inUsage, mork_size inValSize,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap, mork_bool inHoldChanges);
+ void CloseIntMap(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsIntMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kIntMap; }
+// } ===== end morkNode methods =====
+
+// { ===== begin morkMap poly interface =====
+ virtual mork_bool // *((mork_u4*) inKeyA) == *((mork_u4*) inKeyB)
+ Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const override;
+
+ virtual mork_u4 // some integer function of *((mork_u4*) inKey)
+ Hash(morkEnv* ev, const void* inKey) const override;
+// } ===== end morkMap poly interface =====
+
+public: // other map methods
+
+ mork_bool AddInt(morkEnv* ev, mork_u4 inKey, void* ioAddress);
+ // the AddInt() boolean return equals ev->Good().
+
+ mork_bool CutInt(morkEnv* ev, mork_u4 inKey);
+ // The CutInt() boolean return indicates whether removal happened.
+
+ void* GetInt(morkEnv* ev, mork_u4 inKey);
+ // Note the returned node does NOT have an increase in refcount for this.
+
+ mork_bool HasInt(morkEnv* ev, mork_u4 inKey);
+ // Note the returned node does NOT have an increase in refcount for this.
+
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#ifdef MORK_POINTER_MAP_IMPL
+
+#define morkDerived_kPointerMap /*i*/ 0x704D /* ascii 'pM' */
+
+#define morkPointerMap_kStartSlotCount 256
+
+/*| morkPointerMap: maps void* -> void*
+**|
+**| This pointer map class is equivalent to morkIntMap when sizeof(mork_u4)
+**| equals sizeof(void*). However, when these two sizes are different,
+**| then we cannot use the same hash table structure very easily without
+**| being very careful about the size and usage assumptions of those
+**| clients using the smaller data type. So we just go ahead and use
+**| morkPointerMap for hash tables using pointer key types.
+|*/
+class morkPointerMap : public morkMap { // for mapping tokens to maps
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // ClosePointerMap() only if open
+ virtual ~morkPointerMap(); // assert that ClosePointerMap() executed earlier
+
+public: // morkMap construction & destruction
+
+ // keySize for morkPointerMap equals sizeof(mork_u4)
+ morkPointerMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap);
+ void ClosePointerMap(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsPointerMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kPointerMap; }
+// } ===== end morkNode methods =====
+
+// { ===== begin morkMap poly interface =====
+ virtual mork_bool // *((void**) inKeyA) == *((void**) inKeyB)
+ Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const;
+
+ virtual mork_u4 // some integer function of *((mork_u4*) inKey)
+ Hash(morkEnv* ev, const void* inKey) const;
+// } ===== end morkMap poly interface =====
+
+public: // other map methods
+
+ mork_bool AddPointer(morkEnv* ev, void* inKey, void* ioAddress);
+ // the AddPointer() boolean return equals ev->Good().
+
+ mork_bool CutPointer(morkEnv* ev, void* inKey);
+ // The CutPointer() boolean return indicates whether removal happened.
+
+ void* GetPointer(morkEnv* ev, void* inKey);
+ // Note the returned node does NOT have an increase in refcount for this.
+
+ mork_bool HasPointer(morkEnv* ev, void* inKey);
+ // Note the returned node does NOT have an increase in refcount for this.
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakIntMap(morkIntMap* me,
+ morkEnv* ev, morkIntMap** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongIntMap(morkIntMap* me,
+ morkEnv* ev, morkIntMap** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+};
+#endif /*MORK_POINTER_MAP_IMPL*/
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKINTMAP_ */
diff --git a/components/mork/src/morkMap.cpp b/components/mork/src/morkMap.cpp
new file mode 100644
index 000000000..ca6b27827
--- /dev/null
+++ b/components/mork/src/morkMap.cpp
@@ -0,0 +1,953 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// This code is mostly a port to C++ from public domain IronDoc C sources.
+// Note many code comments here come verbatim from cut-and-pasted IronDoc.
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+
+class morkHashArrays {
+public:
+ nsIMdbHeap* mHashArrays_Heap; // copy of mMap_Heap
+ mork_count mHashArrays_Slots; // copy of mMap_Slots
+
+ mork_u1* mHashArrays_Keys; // copy of mMap_Keys
+ mork_u1* mHashArrays_Vals; // copy of mMap_Vals
+ morkAssoc* mHashArrays_Assocs; // copy of mMap_Assocs
+ mork_change* mHashArrays_Changes; // copy of mMap_Changes
+ morkAssoc** mHashArrays_Buckets; // copy of mMap_Buckets
+ morkAssoc* mHashArrays_FreeList; // copy of mMap_FreeList
+
+public:
+ void finalize(morkEnv* ev);
+};
+
+void morkHashArrays::finalize(morkEnv* ev)
+{
+ nsIMdbEnv* menv = ev->AsMdbEnv();
+ nsIMdbHeap* heap = mHashArrays_Heap;
+
+ if ( heap )
+ {
+ if ( mHashArrays_Keys )
+ heap->Free(menv, mHashArrays_Keys);
+ if ( mHashArrays_Vals )
+ heap->Free(menv, mHashArrays_Vals);
+ if ( mHashArrays_Assocs )
+ heap->Free(menv, mHashArrays_Assocs);
+ if ( mHashArrays_Changes )
+ heap->Free(menv, mHashArrays_Changes);
+ if ( mHashArrays_Buckets )
+ heap->Free(menv, mHashArrays_Buckets);
+ }
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkMap::CloseMorkNode(morkEnv* ev) // CloseMap() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseMap(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkMap::~morkMap() // assert CloseMap() executed earlier
+{
+ MORK_ASSERT(mMap_FreeList==0);
+ MORK_ASSERT(mMap_Buckets==0);
+ MORK_ASSERT(mMap_Keys==0);
+ MORK_ASSERT(mMap_Vals==0);
+ MORK_ASSERT(mMap_Changes==0);
+ MORK_ASSERT(mMap_Assocs==0);
+}
+
+/*public non-poly*/ void
+morkMap::CloseMap(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ nsIMdbHeap* heap = mMap_Heap;
+ if ( heap ) /* need to free the arrays? */
+ {
+ nsIMdbEnv* menv = ev->AsMdbEnv();
+
+ if ( mMap_Keys )
+ heap->Free(menv, mMap_Keys);
+
+ if ( mMap_Vals )
+ heap->Free(menv, mMap_Vals);
+
+ if ( mMap_Assocs )
+ heap->Free(menv, mMap_Assocs);
+
+ if ( mMap_Buckets )
+ heap->Free(menv, mMap_Buckets);
+
+ if ( mMap_Changes )
+ heap->Free(menv, mMap_Changes);
+ }
+ mMap_Keys = 0;
+ mMap_Vals = 0;
+ mMap_Buckets = 0;
+ mMap_Assocs = 0;
+ mMap_Changes = 0;
+ mMap_FreeList = 0;
+ MORK_MEMSET(&mMap_Form, 0, sizeof(morkMapForm));
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+void
+morkMap::clear_map(morkEnv* ev, nsIMdbHeap* ioSlotHeap)
+{
+ mMap_Tag = 0;
+ mMap_Seed = 0;
+ mMap_Slots = 0;
+ mMap_Fill = 0;
+ mMap_Keys = 0;
+ mMap_Vals = 0;
+ mMap_Assocs = 0;
+ mMap_Changes = 0;
+ mMap_Buckets = 0;
+ mMap_FreeList = 0;
+ MORK_MEMSET(&mMap_Form, 0, sizeof(morkMapForm));
+
+ mMap_Heap = 0;
+ if ( ioSlotHeap )
+ {
+ nsIMdbHeap_SlotStrongHeap(ioSlotHeap, ev, &mMap_Heap);
+ }
+ else
+ ev->NilPointerError();
+}
+
+morkMap::morkMap(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ mork_size inKeySize, mork_size inValSize,
+ mork_size inSlots, nsIMdbHeap* ioSlotHeap, mork_bool inHoldChanges)
+: morkNode(ev, inUsage, ioHeap)
+, mMap_Heap( 0 )
+{
+ if ( ev->Good() )
+ {
+ this->clear_map(ev, ioSlotHeap);
+ if ( ev->Good() )
+ {
+ mMap_Form.mMapForm_HoldChanges = inHoldChanges;
+
+ mMap_Form.mMapForm_KeySize = inKeySize;
+ mMap_Form.mMapForm_ValSize = inValSize;
+ mMap_Form.mMapForm_KeyIsIP = ( inKeySize == sizeof(mork_ip) );
+ mMap_Form.mMapForm_ValIsIP = ( inValSize == sizeof(mork_ip) );
+
+ this->InitMap(ev, inSlots);
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kMap;
+ }
+ }
+}
+
+void
+morkMap::NewIterOutOfSyncError(morkEnv* ev)
+{
+ ev->NewError("map iter out of sync");
+}
+
+void morkMap::NewBadMapError(morkEnv* ev)
+{
+ ev->NewError("bad morkMap tag");
+}
+
+void morkMap::NewSlotsUnderflowWarning(morkEnv* ev)
+{
+ ev->NewWarning("member count underflow");
+}
+
+void morkMap::InitMap(morkEnv* ev, mork_size inSlots)
+{
+ if ( ev->Good() )
+ {
+ morkHashArrays old;
+ // MORK_MEMCPY(&mMap_Form, &inForm, sizeof(morkMapForm));
+ if ( inSlots < 3 ) /* requested capacity absurdly small? */
+ inSlots = 3; /* bump it up to a minimum practical level */
+ else if ( inSlots > (128 * 1024) ) /* requested slots absurdly big? */
+ inSlots = (128 * 1024); /* decrease it to a maximum practical level */
+
+ if ( this->new_arrays(ev, &old, inSlots) )
+ mMap_Tag = morkMap_kTag;
+
+ MORK_MEMSET(&old, 0, sizeof(morkHashArrays)); // do NOT finalize
+ }
+}
+
+morkAssoc**
+morkMap::find(morkEnv* ev, const void* inKey, mork_u4 inHash) const
+{
+ mork_u1* keys = mMap_Keys;
+ mork_num keySize = this->FormKeySize();
+ // morkMap_mEqual equal = this->FormEqual();
+
+ morkAssoc** ref = mMap_Buckets + (inHash % mMap_Slots);
+ morkAssoc* assoc = *ref;
+ while ( assoc ) /* look at another assoc in the bucket? */
+ {
+ mork_pos i = assoc - mMap_Assocs; /* index of this assoc */
+ if ( this->Equal(ev, keys + (i * keySize), inKey) ) /* found? */
+ return ref;
+
+ ref = &assoc->mAssoc_Next; /* consider next assoc slot in bucket */
+ assoc = *ref; /* if this is null, then we are done */
+ }
+ return 0;
+}
+
+/*| get_assoc: read the key and/or value at index inPos
+|*/
+void
+morkMap::get_assoc(void* outKey, void* outVal, mork_pos inPos) const
+{
+ mork_num valSize = this->FormValSize();
+ if ( valSize && outVal ) /* map holds values? caller wants the value? */
+ {
+ const mork_u1* value = mMap_Vals + (valSize * inPos);
+ if ( valSize == sizeof(mork_ip) && this->FormValIsIP() ) /* ip case? */
+ *((mork_ip*) outVal) = *((const mork_ip*) value);
+ else
+ MORK_MEMCPY(outVal, value, valSize);
+ }
+ if ( outKey ) /* caller wants the key? */
+ {
+ mork_num keySize = this->FormKeySize();
+ const mork_u1* key = mMap_Keys + (keySize * inPos);
+ if ( keySize == sizeof(mork_ip) && this->FormKeyIsIP() ) /* ip case? */
+ *((mork_ip*) outKey) = *((const mork_ip*) key);
+ else
+ MORK_MEMCPY(outKey, key, keySize);
+ }
+}
+
+/*| put_assoc: write the key and/or value at index inPos
+|*/
+void
+morkMap::put_assoc(const void* inKey, const void* inVal, mork_pos inPos) const
+{
+ mork_num valSize = this->FormValSize();
+ if ( valSize && inVal ) /* map holds values? caller sends a value? */
+ {
+ mork_u1* value = mMap_Vals + (valSize * inPos);
+ if ( valSize == sizeof(mork_ip) && this->FormValIsIP() ) /* ip case? */
+ *((mork_ip*) value) = *((const mork_ip*) inVal);
+ else
+ MORK_MEMCPY(value, inVal, valSize);
+ }
+ if ( inKey ) /* caller sends a key? */
+ {
+ mork_num keySize = this->FormKeySize();
+ mork_u1* key = mMap_Keys + (keySize * inPos);
+ if ( keySize == sizeof(mork_ip) && this->FormKeyIsIP() ) /* ip case? */
+ *((mork_ip*) key) = *((const mork_ip*) inKey);
+ else
+ MORK_MEMCPY(key, inKey, keySize);
+ }
+}
+
+void*
+morkMap::clear_alloc(morkEnv* ev, mork_size inSize)
+{
+ void* p = 0;
+ nsIMdbHeap* heap = mMap_Heap;
+ if ( heap )
+ {
+ if (NS_SUCCEEDED(heap->Alloc(ev->AsMdbEnv(), inSize, (void**) &p)) && p )
+ {
+ MORK_MEMSET(p, 0, inSize);
+ return p;
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ return (void*) 0;
+}
+
+void*
+morkMap::alloc(morkEnv* ev, mork_size inSize)
+{
+ void* p = 0;
+ nsIMdbHeap* heap = mMap_Heap;
+ if ( heap )
+ {
+ if (NS_SUCCEEDED(heap->Alloc(ev->AsMdbEnv(), inSize, (void**) &p)) && p )
+ return p;
+ }
+ else
+ ev->NilPointerError();
+
+ return (void*) 0;
+}
+
+/*| new_keys: allocate an array of inSlots new keys filled with zero.
+|*/
+mork_u1*
+morkMap::new_keys(morkEnv* ev, mork_num inSlots)
+{
+ mork_num size = inSlots * this->FormKeySize();
+ return (mork_u1*) this->clear_alloc(ev, size);
+}
+
+/*| new_values: allocate an array of inSlots new values filled with zero.
+**| When values are zero sized, we just return a null pointer.
+|*/
+mork_u1*
+morkMap::new_values(morkEnv* ev, mork_num inSlots)
+{
+ mork_u1* values = 0;
+ mork_num size = inSlots * this->FormValSize();
+ if ( size )
+ values = (mork_u1*) this->clear_alloc(ev, size);
+ return values;
+}
+
+mork_change*
+morkMap::new_changes(morkEnv* ev, mork_num inSlots)
+{
+ mork_change* changes = 0;
+ mork_num size = inSlots * sizeof(mork_change);
+ if ( size && mMap_Form.mMapForm_HoldChanges )
+ changes = (mork_change*) this->clear_alloc(ev, size);
+ return changes;
+}
+
+/*| new_buckets: allocate an array of inSlots new buckets filled with zero.
+|*/
+morkAssoc**
+morkMap::new_buckets(morkEnv* ev, mork_num inSlots)
+{
+ mork_num size = inSlots * sizeof(morkAssoc*);
+ return (morkAssoc**) this->clear_alloc(ev, size);
+}
+
+/*| new_assocs: allocate an array of inSlots new assocs, with each assoc
+**| linked together in a list with the first array element at the list head
+**| and the last element at the list tail. (morkMap::grow() needs this.)
+|*/
+morkAssoc*
+morkMap::new_assocs(morkEnv* ev, mork_num inSlots)
+{
+ mork_num size = inSlots * sizeof(morkAssoc);
+ morkAssoc* assocs = (morkAssoc*) this->alloc(ev, size);
+ if ( assocs ) /* able to allocate the array? */
+ {
+ morkAssoc* a = assocs + (inSlots - 1); /* the last array element */
+ a->mAssoc_Next = 0; /* terminate tail element of the list with null */
+ while ( --a >= assocs ) /* another assoc to link into the list? */
+ a->mAssoc_Next = a + 1; /* each points to the following assoc */
+ }
+ return assocs;
+}
+
+mork_bool
+morkMap::new_arrays(morkEnv* ev, morkHashArrays* old, mork_num inSlots)
+{
+ mork_bool outNew = morkBool_kFalse;
+
+ /* see if we can allocate all the new arrays before we go any further: */
+ morkAssoc** newBuckets = this->new_buckets(ev, inSlots);
+ morkAssoc* newAssocs = this->new_assocs(ev, inSlots);
+ mork_u1* newKeys = this->new_keys(ev, inSlots);
+ mork_u1* newValues = this->new_values(ev, inSlots);
+ mork_change* newChanges = this->new_changes(ev, inSlots);
+
+ /* it is okay for newChanges to be null when changes are not held: */
+ mork_bool okayChanges = ( newChanges || !this->FormHoldChanges() );
+
+ /* it is okay for newValues to be null when values are zero sized: */
+ mork_bool okayValues = ( newValues || !this->FormValSize() );
+
+ if ( newBuckets && newAssocs && newKeys && okayChanges && okayValues )
+ {
+ outNew = morkBool_kTrue; /* yes, we created all the arrays we need */
+
+ /* init the old hashArrays with slots from this hash table: */
+ old->mHashArrays_Heap = mMap_Heap;
+
+ old->mHashArrays_Slots = mMap_Slots;
+ old->mHashArrays_Keys = mMap_Keys;
+ old->mHashArrays_Vals = mMap_Vals;
+ old->mHashArrays_Assocs = mMap_Assocs;
+ old->mHashArrays_Buckets = mMap_Buckets;
+ old->mHashArrays_Changes = mMap_Changes;
+
+ /* now replace all our array slots with the new structures: */
+ ++mMap_Seed; /* note the map is now changed */
+ mMap_Keys = newKeys;
+ mMap_Vals = newValues;
+ mMap_Buckets = newBuckets;
+ mMap_Assocs = newAssocs;
+ mMap_FreeList = newAssocs; /* all are free to start with */
+ mMap_Changes = newChanges;
+ mMap_Slots = inSlots;
+ }
+ else /* free the partial set of arrays that were actually allocated */
+ {
+ nsIMdbEnv* menv = ev->AsMdbEnv();
+ nsIMdbHeap* heap = mMap_Heap;
+ if ( newBuckets )
+ heap->Free(menv, newBuckets);
+ if ( newAssocs )
+ heap->Free(menv, newAssocs);
+ if ( newKeys )
+ heap->Free(menv, newKeys);
+ if ( newValues )
+ heap->Free(menv, newValues);
+ if ( newChanges )
+ heap->Free(menv, newChanges);
+
+ MORK_MEMSET(old, 0, sizeof(morkHashArrays));
+ }
+
+ return outNew;
+}
+
+/*| grow: make the map arrays bigger by 33%. The old map is completely
+**| full, or else we would not have called grow() to get more space. This
+**| means the free list is empty, and also means every old key and value is in
+**| use in the old arrays. So every key and value must be copied to the new
+**| arrays, and since they are contiguous in the old arrays, we can efficiently
+**| bitwise copy them in bulk from the old arrays to the new arrays, without
+**| paying any attention to the structure of the old arrays.
+**|
+**|| The content of the old bucket and assoc arrays need not be copied because
+**| this information is going to be completely rebuilt by rehashing all the
+**| keys into their new buckets, given the new larger map capacity. The new
+**| bucket array from new_arrays() is assumed to contain all zeroes, which is
+**| necessary to ensure the lists in each bucket stay null terminated as we
+**| build the new linked lists. (Note no old bucket ordering is preserved.)
+**|
+**|| If the old capacity is N, then in the new arrays the assocs with indexes
+**| from zero to N-1 are still allocated and must be rehashed into the map.
+**| The new free list contains all the following assocs, starting with the new
+**| assoc link at index N. (We call the links in the link array "assocs"
+**| because allocating a link is the same as allocating the key/value pair
+**| with the same index as the link.)
+**|
+**|| The new free list is initialized simply by pointing at the first new link
+**| in the assoc array after the size of the old assoc array. This assumes
+**| that FeHashTable_new_arrays() has already linked all the new assocs into a
+**| list with the first at the head of the list and the last at the tail of the
+**| list. So by making the free list point to the first of the new uncopied
+**| assocs, the list is already well-formed.
+|*/
+mork_bool morkMap::grow(morkEnv* ev)
+{
+ if ( mMap_Heap ) /* can we grow the map? */
+ {
+ mork_num newSlots = (mMap_Slots * 2); /* +100% */
+ morkHashArrays old; /* a place to temporarily hold all the old arrays */
+ if ( this->new_arrays(ev, &old, newSlots) ) /* have more? */
+ {
+ // morkMap_mHash hash = this->FormHash(); /* for terse loop use */
+
+ /* figure out the bulk volume sizes of old keys and values to move: */
+ mork_num oldSlots = old.mHashArrays_Slots; /* number of old assocs */
+ mork_num keyBulk = oldSlots * this->FormKeySize(); /* key volume */
+ mork_num valBulk = oldSlots * this->FormValSize(); /* values */
+
+ /* convenient variables for new arrays that need to be rehashed: */
+ morkAssoc** newBuckets = mMap_Buckets; /* new all zeroes */
+ morkAssoc* newAssocs = mMap_Assocs; /* hash into buckets */
+ morkAssoc* newFreeList = newAssocs + oldSlots; /* new room is free */
+ mork_u1* key = mMap_Keys; /* the first key to rehash */
+ --newAssocs; /* back up one before preincrement used in while loop */
+
+ /* move all old keys and values to the new arrays: */
+ MORK_MEMCPY(mMap_Keys, old.mHashArrays_Keys, keyBulk);
+ if ( valBulk ) /* are values nonzero sized? */
+ MORK_MEMCPY(mMap_Vals, old.mHashArrays_Vals, valBulk);
+
+ mMap_FreeList = newFreeList; /* remaining assocs are free */
+
+ while ( ++newAssocs < newFreeList ) /* rehash another old assoc? */
+ {
+ morkAssoc** top = newBuckets + (this->Hash(ev, key) % newSlots);
+ key += this->FormKeySize(); /* the next key to rehash */
+ newAssocs->mAssoc_Next = *top; /* link to prev bucket top */
+ *top = newAssocs; /* push assoc on top of bucket */
+ }
+ ++mMap_Seed; /* note the map has changed */
+ old.finalize(ev); /* remember to free the old arrays */
+ }
+ }
+ else ev->OutOfMemoryError();
+
+ return ev->Good();
+}
+
+
+mork_bool
+morkMap::Put(morkEnv* ev, const void* inKey, const void* inVal,
+ void* outKey, void* outVal, mork_change** outChange)
+{
+ mork_bool outPut = morkBool_kFalse;
+
+ if ( this->GoodMap() ) /* looks good? */
+ {
+ mork_u4 hash = this->Hash(ev, inKey);
+ morkAssoc** ref = this->find(ev, inKey, hash);
+ if ( ref ) /* assoc was found? reuse an existing assoc slot? */
+ {
+ outPut = morkBool_kTrue; /* inKey was indeed already inside the map */
+ }
+ else /* assoc not found -- need to allocate a new assoc slot */
+ {
+ morkAssoc* assoc = this->pop_free_assoc();
+ if ( !assoc ) /* no slots remaining in free list? must grow map? */
+ {
+ if ( this->grow(ev) ) /* successfully made map larger? */
+ assoc = this->pop_free_assoc();
+ }
+ if ( assoc ) /* allocated new assoc without error? */
+ {
+ ref = mMap_Buckets + (hash % mMap_Slots);
+ assoc->mAssoc_Next = *ref; /* link to prev bucket top */
+ *ref = assoc; /* push assoc on top of bucket */
+
+ ++mMap_Fill; /* one more member in the collection */
+ ++mMap_Seed; /* note the map has changed */
+ }
+ }
+ if ( ref ) /* did not have an error during possible growth? */
+ {
+ mork_pos i = (*ref) - mMap_Assocs; /* index of assoc */
+ if ( outPut && (outKey || outVal) ) /* copy old before cobbering? */
+ this->get_assoc(outKey, outVal, i);
+
+ this->put_assoc(inKey, inVal, i);
+ ++mMap_Seed; /* note the map has changed */
+
+ if ( outChange )
+ {
+ if ( mMap_Changes )
+ *outChange = mMap_Changes + i;
+ else
+ *outChange = this->FormDummyChange();
+ }
+ }
+ }
+ else this->NewBadMapError(ev);
+
+ return outPut;
+}
+
+mork_num
+morkMap::CutAll(morkEnv* ev)
+{
+ mork_num outCutAll = 0;
+
+ if ( this->GoodMap() ) /* map looks good? */
+ {
+ mork_num slots = mMap_Slots;
+ morkAssoc* before = mMap_Assocs - 1; /* before first member */
+ morkAssoc* assoc = before + slots; /* the very last member */
+
+ ++mMap_Seed; /* note the map is changed */
+
+ /* make the assoc array a linked list headed by first & tailed by last: */
+ assoc->mAssoc_Next = 0; /* null terminate the new free list */
+ while ( --assoc > before ) /* another assoc to link into the list? */
+ assoc->mAssoc_Next = assoc + 1;
+ mMap_FreeList = mMap_Assocs; /* all are free */
+
+ outCutAll = mMap_Fill; /* we'll cut all of them of course */
+
+ mMap_Fill = 0; /* the map is completely empty */
+ }
+ else this->NewBadMapError(ev);
+
+ return outCutAll;
+}
+
+mork_bool
+morkMap::Cut(morkEnv* ev, const void* inKey,
+ void* outKey, void* outVal, mork_change** outChange)
+{
+ mork_bool outCut = morkBool_kFalse;
+
+ if ( this->GoodMap() ) /* looks good? */
+ {
+ mork_u4 hash = this->Hash(ev, inKey);
+ morkAssoc** ref = this->find(ev, inKey, hash);
+ if ( ref ) /* found an assoc for key? */
+ {
+ outCut = morkBool_kTrue;
+ morkAssoc* assoc = *ref;
+ mork_pos i = assoc - mMap_Assocs; /* index of assoc */
+ if ( outKey || outVal )
+ this->get_assoc(outKey, outVal, i);
+
+ *ref = assoc->mAssoc_Next; /* unlink the found assoc */
+ this->push_free_assoc(assoc); /* and put it in free list */
+
+ if ( outChange )
+ {
+ if ( mMap_Changes )
+ *outChange = mMap_Changes + i;
+ else
+ *outChange = this->FormDummyChange();
+ }
+
+ ++mMap_Seed; /* note the map has changed */
+ if ( mMap_Fill ) /* the count shows nonzero members? */
+ --mMap_Fill; /* one less member in the collection */
+ else
+ this->NewSlotsUnderflowWarning(ev);
+ }
+ }
+ else this->NewBadMapError(ev);
+
+ return outCut;
+}
+
+mork_bool
+morkMap::Get(morkEnv* ev, const void* inKey,
+ void* outKey, void* outVal, mork_change** outChange)
+{
+ mork_bool outGet = morkBool_kFalse;
+
+ if ( this->GoodMap() ) /* looks good? */
+ {
+ mork_u4 hash = this->Hash(ev, inKey);
+ morkAssoc** ref = this->find(ev, inKey, hash);
+ if ( ref ) /* found an assoc for inKey? */
+ {
+ mork_pos i = (*ref) - mMap_Assocs; /* index of assoc */
+ outGet = morkBool_kTrue;
+ this->get_assoc(outKey, outVal, i);
+ if ( outChange )
+ {
+ if ( mMap_Changes )
+ *outChange = mMap_Changes + i;
+ else
+ *outChange = this->FormDummyChange();
+ }
+ }
+ }
+ else this->NewBadMapError(ev);
+
+ return outGet;
+}
+
+
+morkMapIter::morkMapIter( )
+: mMapIter_Map( 0 )
+, mMapIter_Seed( 0 )
+
+, mMapIter_Bucket( 0 )
+, mMapIter_AssocRef( 0 )
+, mMapIter_Assoc( 0 )
+, mMapIter_Next( 0 )
+{
+}
+
+void
+morkMapIter::InitMapIter(morkEnv* ev, morkMap* ioMap)
+{
+ mMapIter_Map = 0;
+ mMapIter_Seed = 0;
+
+ mMapIter_Bucket = 0;
+ mMapIter_AssocRef = 0;
+ mMapIter_Assoc = 0;
+ mMapIter_Next = 0;
+
+ if ( ioMap )
+ {
+ if ( ioMap->GoodMap() )
+ {
+ mMapIter_Map = ioMap;
+ mMapIter_Seed = ioMap->mMap_Seed;
+ }
+ else ioMap->NewBadMapError(ev);
+ }
+ else ev->NilPointerError();
+}
+
+morkMapIter::morkMapIter(morkEnv* ev, morkMap* ioMap)
+: mMapIter_Map( 0 )
+, mMapIter_Seed( 0 )
+
+, mMapIter_Bucket( 0 )
+, mMapIter_AssocRef( 0 )
+, mMapIter_Assoc( 0 )
+, mMapIter_Next( 0 )
+{
+ if ( ioMap )
+ {
+ if ( ioMap->GoodMap() )
+ {
+ mMapIter_Map = ioMap;
+ mMapIter_Seed = ioMap->mMap_Seed;
+ }
+ else ioMap->NewBadMapError(ev);
+ }
+ else ev->NilPointerError();
+}
+
+void
+morkMapIter::CloseMapIter(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ mMapIter_Map = 0;
+ mMapIter_Seed = 0;
+
+ mMapIter_Bucket = 0;
+ mMapIter_AssocRef = 0;
+ mMapIter_Assoc = 0;
+ mMapIter_Next = 0;
+}
+
+mork_change*
+morkMapIter::First(morkEnv* ev, void* outKey, void* outVal)
+{
+ mork_change* outFirst = 0;
+
+ morkMap* map = mMapIter_Map;
+
+ if ( map && map->GoodMap() ) /* map looks good? */
+ {
+ morkAssoc** bucket = map->mMap_Buckets;
+ morkAssoc** end = bucket + map->mMap_Slots; /* one past last */
+
+ mMapIter_Seed = map->mMap_Seed; /* sync the seeds */
+
+ while ( bucket < end ) /* another bucket in which to look for assocs? */
+ {
+ morkAssoc* assoc = *bucket++;
+ if ( assoc ) /* found the first map assoc in use? */
+ {
+ mork_pos i = assoc - map->mMap_Assocs;
+ mork_change* c = map->mMap_Changes;
+ outFirst = ( c )? (c + i) : map->FormDummyChange();
+
+ mMapIter_Assoc = assoc; /* current assoc in iteration */
+ mMapIter_Next = assoc->mAssoc_Next; /* more in bucket */
+ mMapIter_Bucket = --bucket; /* bucket for this assoc */
+ mMapIter_AssocRef = bucket; /* slot referencing assoc */
+
+ map->get_assoc(outKey, outVal, i);
+
+ break; /* end while loop */
+ }
+ }
+ }
+ else map->NewBadMapError(ev);
+
+ return outFirst;
+}
+
+mork_change*
+morkMapIter::Next(morkEnv* ev, void* outKey, void* outVal)
+{
+ mork_change* outNext = 0;
+
+ morkMap* map = mMapIter_Map;
+
+ if ( map && map->GoodMap() ) /* map looks good? */
+ {
+ if ( mMapIter_Seed == map->mMap_Seed ) /* in sync? */
+ {
+ morkAssoc* here = mMapIter_Assoc; /* current assoc */
+ if ( here ) /* iteration is not yet concluded? */
+ {
+ morkAssoc* next = mMapIter_Next;
+ morkAssoc* assoc = next; /* default new mMapIter_Assoc */
+ if ( next ) /* there are more assocs in the same bucket after Here? */
+ {
+ morkAssoc** ref = mMapIter_AssocRef;
+
+ /* (*HereRef) equals Here, except when Here has been cut, after
+ ** which (*HereRef) always equals Next. So if (*HereRef) is not
+ ** equal to Next, then HereRef still needs to be updated to point
+ ** somewhere else other than Here. Otherwise it is fine.
+ */
+ if ( *ref != next ) /* Here was not cut? must update HereRef? */
+ mMapIter_AssocRef = &here->mAssoc_Next;
+
+ mMapIter_Next = next->mAssoc_Next;
+ }
+ else /* look for the next assoc in the next nonempty bucket */
+ {
+ morkAssoc** bucket = map->mMap_Buckets;
+ morkAssoc** end = bucket + map->mMap_Slots; /* beyond */
+ mMapIter_Assoc = 0; /* default to no more assocs */
+ bucket = mMapIter_Bucket; /* last exhausted bucket */
+ mMapIter_Assoc = 0; /* default to iteration ended */
+
+ while ( ++bucket < end ) /* another bucket to search for assocs? */
+ {
+ assoc = *bucket;
+ if ( assoc ) /* found another map assoc in use? */
+ {
+ mMapIter_Bucket = bucket;
+ mMapIter_AssocRef = bucket; /* ref to assoc */
+ mMapIter_Next = assoc->mAssoc_Next; /* more */
+
+ break; /* end while loop */
+ }
+ }
+ }
+ if ( assoc ) /* did we find another assoc in the iteration? */
+ {
+ mMapIter_Assoc = assoc; /* current assoc */
+ mork_pos i = assoc - map->mMap_Assocs;
+ mork_change* c = map->mMap_Changes;
+ outNext = ( c )? (c + i) : map->FormDummyChange();
+
+ map->get_assoc( outKey, outVal, i);
+ }
+ }
+ }
+ else map->NewIterOutOfSyncError(ev);
+ }
+ else map->NewBadMapError(ev);
+
+ return outNext;
+}
+
+mork_change*
+morkMapIter::Here(morkEnv* ev, void* outKey, void* outVal)
+{
+ mork_change* outHere = 0;
+
+ morkMap* map = mMapIter_Map;
+
+ if ( map && map->GoodMap() ) /* map looks good? */
+ {
+ if ( mMapIter_Seed == map->mMap_Seed ) /* in sync? */
+ {
+ morkAssoc* here = mMapIter_Assoc; /* current assoc */
+ if ( here ) /* iteration is not yet concluded? */
+ {
+ mork_pos i = here - map->mMap_Assocs;
+ mork_change* c = map->mMap_Changes;
+ outHere = ( c )? (c + i) : map->FormDummyChange();
+
+ map->get_assoc(outKey, outVal, i);
+ }
+ }
+ else map->NewIterOutOfSyncError(ev);
+ }
+ else map->NewBadMapError(ev);
+
+ return outHere;
+}
+
+mork_change*
+morkMapIter::CutHere(morkEnv* ev, void* outKey, void* outVal)
+{
+ mork_change* outCutHere = 0;
+ morkMap* map = mMapIter_Map;
+
+ if ( map && map->GoodMap() ) /* map looks good? */
+ {
+ if ( mMapIter_Seed == map->mMap_Seed ) /* in sync? */
+ {
+ morkAssoc* here = mMapIter_Assoc; /* current assoc */
+ if ( here ) /* iteration is not yet concluded? */
+ {
+ morkAssoc** ref = mMapIter_AssocRef;
+ if ( *ref != mMapIter_Next ) /* not already cut? */
+ {
+ mork_pos i = here - map->mMap_Assocs;
+ mork_change* c = map->mMap_Changes;
+ outCutHere = ( c )? (c + i) : map->FormDummyChange();
+ if ( outKey || outVal )
+ map->get_assoc(outKey, outVal, i);
+
+ map->push_free_assoc(here); /* add to free list */
+ *ref = mMapIter_Next; /* unlink here from bucket list */
+
+ /* note the map has changed, but we are still in sync: */
+ mMapIter_Seed = ++map->mMap_Seed; /* sync */
+
+ if ( map->mMap_Fill ) /* still has nonzero value? */
+ --map->mMap_Fill; /* one less member in the collection */
+ else
+ map->NewSlotsUnderflowWarning(ev);
+ }
+ }
+ }
+ else map->NewIterOutOfSyncError(ev);
+ }
+ else map->NewBadMapError(ev);
+
+ return outCutHere;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkMap.h b/components/mork/src/morkMap.h
new file mode 100644
index 000000000..57104df0e
--- /dev/null
+++ b/components/mork/src/morkMap.h
@@ -0,0 +1,394 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _MORKMAP_
+#define _MORKMAP_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/* (These hash methods closely resemble those in public domain IronDoc.) */
+
+/*| Equal: equal for hash table. Note equal(a,b) implies hash(a)==hash(b).
+|*/
+typedef mork_bool (* morkMap_mEqual)
+(const morkMap* self, morkEnv* ev, const void* inKeyA, const void* inKeyB);
+
+/*| Hash: hash for hash table. Note equal(a,b) implies hash(a)==hash(b).
+|*/
+typedef mork_u4 (* morkMap_mHash)
+(const morkMap* self, morkEnv* ev, const void* inKey);
+
+/*| IsNil: whether a key slot contains a "null" value denoting "no such key".
+|*/
+typedef mork_bool (* morkMap_mIsNil)
+(const morkMap* self, morkEnv* ev, const void* inKey);
+
+/*| Note: notify regarding a refcounting change for a key or a value.
+|*/
+//typedef void (* morkMap_mNote)
+//(morkMap* self, morkEnv* ev, void* inKeyOrVal);
+
+/*| morkMapForm: slots need to initialize a new dict. (This is very similar
+**| to the config object for public domain IronDoc hash tables.)
+|*/
+class morkMapForm { // a struct of callback method pointers for morkMap
+public:
+
+ // const void* mMapForm_NilKey; // externally defined 'nil' bit pattern
+
+ // void* mMapForm_NilBuf[ 8 ]; // potential place to put NilKey
+ // If keys are no larger than 8*sizeof(void*), NilKey can be put in NilBuf.
+ // Note this should be true for all Mork subclasses, and we plan usage so.
+
+ // These three methods must always be provided, so zero will cause errors:
+
+ // morkMap_mEqual mMapForm_Equal; // for comparing two keys for identity
+ // morkMap_mHash mMapForm_Hash; // deterministic key to hash method
+ // morkMap_mIsNil mMapForm_IsNil; // to query whether a key equals 'nil'
+
+ // If any of these method slots are nonzero, then morkMap will call the
+ // appropriate one to notify dict users when a key or value is added or cut.
+ // Presumably a caller wants to know this in order to perform refcounting or
+ // some other kind of memory management. These methods are definitely only
+ // called when references to keys or values are inserted or removed, and are
+ // never called when the actual number of references does not change (such
+ // as when added keys are already present or cut keys are alreading missing).
+ //
+ // morkMap_mNote mMapForm_AddKey; // if nonzero, notify about add key
+ // morkMap_mNote mMapForm_CutKey; // if nonzero, notify about cut key
+ // morkMap_mNote mMapForm_AddVal; // if nonzero, notify about add val
+ // morkMap_mNote mMapForm_CutVal; // if nonzero, notify about cut val
+ //
+ // These note methods have been removed because it seems difficult to
+ // guarantee suitable alignment of objects passed to notification methods.
+
+ // Note dict clients should pick key and val sizes that provide whatever
+ // alignment will be required for an array of such keys and values.
+ mork_size mMapForm_KeySize; // size of every key (cannot be zero)
+ mork_size mMapForm_ValSize; // size of every val (can indeed be zero)
+
+ mork_bool mMapForm_HoldChanges; // support changes array in the map
+ mork_change mMapForm_DummyChange; // change used for false HoldChanges
+ mork_bool mMapForm_KeyIsIP; // key is mork_ip sized
+ mork_bool mMapForm_ValIsIP; // key is mork_ip sized
+};
+
+/*| morkAssoc: a canonical association slot in a morkMap. A single assoc
+**| instance does nothing except point to the next assoc in the same bucket
+**| of a hash table. Each assoc has only two interesting attributes: 1) the
+**| address of the assoc, and 2) the next assoc in a bucket's list. The assoc
+**| address is interesting because in the context of an array of such assocs,
+**| one can determine the index of a particular assoc in the array by address
+**| arithmetic, subtracting the array address from the assoc address. And the
+**| index of each assoc is the same index as the associated key and val slots
+**| in the associated arrays
+**|
+**|| Think of an assoc instance as really also containing a key slot and a val
+**| slot, where each key is mMap_Form.mMapForm_KeySize bytes in size, and
+**| each val is mMap_Form.mMapForm_ValSize in size. But the key and val
+**| slots are stored in separate arrays with indexes that are parallel to the
+**| indexes in the array of morkAssoc instances. We have taken the variable
+**| sized slots out of the morkAssoc structure, and put them into parallel
+**| arrays associated with each morkAssoc by array index. And this leaves us
+**| with only the link field to the next assoc in each assoc instance.
+|*/
+class morkAssoc {
+public:
+ morkAssoc* mAssoc_Next;
+};
+
+
+#define morkDerived_kMap /*i*/ 0x4D70 /* ascii 'Mp' */
+
+#define morkMap_kTag /*i*/ 0x6D4D6150 /* ascii 'mMaP' */
+
+/*| morkMap: a hash table based on the public domain IronDoc hash table
+**| (which is in turn rather like a similar OpenDoc hash table).
+|*/
+class morkMap : public morkNode {
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+public: // state is public because the entire Mork system is private
+
+ nsIMdbHeap* mMap_Heap; // strong ref to heap allocating all space
+ mork_u4 mMap_Tag; // must equal morkMap_kTag
+
+ // When a morkMap instance is constructed, the dict form slots must be
+ // provided in order to properly configure a dict with all runtime needs:
+
+ morkMapForm mMap_Form; // construction time parameterization
+
+ // Whenever the dict changes structure in a way that would affect any
+ // iteration of the dict associations, the seed increments to show this:
+
+ mork_seed mMap_Seed; // counter for member and structural changes
+
+ // The current total assoc capacity of the dict is mMap_Slots, where
+ // mMap_Fill of these slots are actually holding content, so mMap_Fill
+ // is the actual membership count, and mMap_Slots is how larger membership
+ // can become before the hash table must grow the buffers being used.
+
+ mork_count mMap_Slots; // count of slots in the hash table
+ mork_fill mMap_Fill; // number of used slots in the hash table
+
+ // Key and value slots are bound to corresponding mMap_Assocs array slots.
+ // Instead of having a single array like this: {key,val,next}[ mMap_Slots ]
+ // we have instead three parallel arrays with essentially the same meaning:
+ // {key}[ mMap_Slots ], {val}[ mMap_Slots ], {assocs}[ mMap_Slots ]
+
+ mork_u1* mMap_Keys; // mMap_Slots * mMapForm_KeySize buffer
+ mork_u1* mMap_Vals; // mMap_Slots * mMapForm_ValSize buffer
+
+ // An assoc is "used" when it appears in a bucket's linked list of assocs.
+ // Until an assoc is used, it appears in the FreeList linked list. Every
+ // assoc that becomes used goes into the bucket determined by hashing the
+ // key associated with a new assoc. The key associated with a new assoc
+ // goes in to the slot in mMap_Keys which occupies exactly the same array
+ // index as the array index of the used assoc in the mMap_Assocs array.
+
+ morkAssoc* mMap_Assocs; // mMap_Slots * sizeof(morkAssoc) buffer
+
+ // The changes array is only needed when the
+
+ mork_change* mMap_Changes; // mMap_Slots * sizeof(mork_change) buffer
+
+ // The Buckets array need not be the same length as the Assocs array, but we
+ // usually do it that way so the average bucket depth is no more than one.
+ // (We could pick a different policy, or make it parameterizable, but that's
+ // tuning we can do some other time.)
+
+ morkAssoc** mMap_Buckets; // mMap_Slots * sizeof(morkAssoc*) buffer
+
+ // The length of the mMap_FreeList should equal (mMap_Slots - mMap_Fill).
+ // We need a free list instead of a simpler representation because assocs
+ // can be cut and returned to availability in any kind of unknown pattern.
+ // (However, when assocs are first allocated, or when the dict is grown, we
+ // know all new assocs are contiguous and can chain together adjacently.)
+
+ morkAssoc* mMap_FreeList; // list of unused mMap_Assocs array slots
+
+public: // getters (morkProbeMap compatibility)
+ mork_fill MapFill() const { return mMap_Fill; }
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseMap() only if open
+ virtual ~morkMap(); // assert that CloseMap() executed earlier
+
+public: // morkMap construction & destruction
+ morkMap(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioNodeHeap,
+ mork_size inKeySize, mork_size inValSize,
+ mork_size inSlots, nsIMdbHeap* ioSlotHeap, mork_bool inHoldChanges);
+
+ void CloseMap(morkEnv* ev); // called by
+
+public: // dynamic type identification
+ mork_bool IsMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kMap; }
+// } ===== end morkNode methods =====
+
+public: // poly map hash table methods
+
+// { ===== begin morkMap poly interface =====
+ virtual mork_bool // note: equal(a,b) implies hash(a) == hash(b)
+ Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const = 0;
+
+ virtual mork_u4 // note: equal(a,b) implies hash(a) == hash(b)
+ Hash(morkEnv* ev, const void* inKey) const = 0;
+// } ===== end morkMap poly interface =====
+
+public: // open utitity methods
+
+ mork_bool GoodMapTag() const { return mMap_Tag == morkMap_kTag; }
+ mork_bool GoodMap() const
+ { return ( IsNode() && GoodMapTag() ); }
+
+ void NewIterOutOfSyncError(morkEnv* ev);
+ void NewBadMapError(morkEnv* ev);
+ void NewSlotsUnderflowWarning(morkEnv* ev);
+ void InitMap(morkEnv* ev, mork_size inSlots);
+
+protected: // internal utitity methods
+
+ friend class morkMapIter;
+ void clear_map(morkEnv* ev, nsIMdbHeap* ioHeap);
+
+ void* alloc(morkEnv* ev, mork_size inSize);
+ void* clear_alloc(morkEnv* ev, mork_size inSize);
+
+ void push_free_assoc(morkAssoc* ioAssoc)
+ {
+ ioAssoc->mAssoc_Next = mMap_FreeList;
+ mMap_FreeList = ioAssoc;
+ }
+
+ morkAssoc* pop_free_assoc()
+ {
+ morkAssoc* assoc = mMap_FreeList;
+ if ( assoc )
+ mMap_FreeList = assoc->mAssoc_Next;
+ return assoc;
+ }
+
+ morkAssoc** find(morkEnv* ev, const void* inKey, mork_u4 inHash) const;
+
+ mork_u1* new_keys(morkEnv* ev, mork_num inSlots);
+ mork_u1* new_values(morkEnv* ev, mork_num inSlots);
+ mork_change* new_changes(morkEnv* ev, mork_num inSlots);
+ morkAssoc** new_buckets(morkEnv* ev, mork_num inSlots);
+ morkAssoc* new_assocs(morkEnv* ev, mork_num inSlots);
+ mork_bool new_arrays(morkEnv* ev, morkHashArrays* old, mork_num inSlots);
+
+ mork_bool grow(morkEnv* ev);
+
+ void get_assoc(void* outKey, void* outVal, mork_pos inPos) const;
+ void put_assoc(const void* inKey, const void* inVal, mork_pos inPos) const;
+
+public: // inlines to form slots
+ // const void* FormNilKey() const { return mMap_Form.mMapForm_NilKey; }
+
+ // morkMap_mEqual FormEqual() const { return mMap_Form.mMapForm_Equal; }
+ // morkMap_mHash FormHash() const { return mMap_Form.mMapForm_Hash; }
+ // orkMap_mIsNil FormIsNil() const { return mMap_Form.mMapForm_IsNil; }
+
+ // morkMap_mNote FormAddKey() const { return mMap_Form.mMapForm_AddKey; }
+ // morkMap_mNote FormCutKey() const { return mMap_Form.mMapForm_CutKey; }
+ // morkMap_mNote FormAddVal() const { return mMap_Form.mMapForm_AddVal; }
+ // morkMap_mNote FormCutVal() const { return mMap_Form.mMapForm_CutVal; }
+
+ mork_size FormKeySize() const { return mMap_Form.mMapForm_KeySize; }
+ mork_size FormValSize() const { return mMap_Form.mMapForm_ValSize; }
+
+ mork_bool FormKeyIsIP() const { return mMap_Form.mMapForm_KeyIsIP; }
+ mork_bool FormValIsIP() const { return mMap_Form.mMapForm_ValIsIP; }
+
+ mork_bool FormHoldChanges() const
+ { return mMap_Form.mMapForm_HoldChanges; }
+
+ mork_change* FormDummyChange()
+ { return &mMap_Form.mMapForm_DummyChange; }
+
+public: // other map methods
+
+ mork_bool Put(morkEnv* ev, const void* inKey, const void* inVal,
+ void* outKey, void* outVal, mork_change** outChange);
+
+ mork_bool Cut(morkEnv* ev, const void* inKey,
+ void* outKey, void* outVal, mork_change** outChange);
+
+ mork_bool Get(morkEnv* ev, const void* inKey,
+ void* outKey, void* outVal, mork_change** outChange);
+
+ mork_num CutAll(morkEnv* ev);
+
+private: // copying is not allowed
+ morkMap(const morkMap& other);
+ morkMap& operator=(const morkMap& other);
+
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakMap(morkMap* me,
+ morkEnv* ev, morkMap** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongMap(morkMap* me,
+ morkEnv* ev, morkMap** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+/*| morkMapIter: an iterator for morkMap and subclasses. This is not a node,
+**| and expected usage is as a member of some other node subclass, such as in
+**| a cursor subclass or a thumb subclass. Also, iters might be as temp stack
+**| objects when scanning the content of a map.
+|*/
+class morkMapIter{ // iterator for hash table map
+
+protected:
+ morkMap* mMapIter_Map; // map to iterate, NOT refcounted
+ mork_seed mMapIter_Seed; // cached copy of map's seed
+
+ morkAssoc** mMapIter_Bucket; // one bucket in mMap_Buckets array
+ morkAssoc** mMapIter_AssocRef; // usually *AtRef equals Here
+ morkAssoc* mMapIter_Assoc; // the current assoc in an iteration
+ morkAssoc* mMapIter_Next; // mMapIter_Assoc->mAssoc_Next */
+
+public:
+ morkMapIter(morkEnv* ev, morkMap* ioMap);
+ void CloseMapIter(morkEnv* ev);
+
+ morkMapIter( ); // everything set to zero -- need to call InitMapIter()
+
+protected: // we want all subclasses to provide typesafe wrappers:
+
+ void InitMapIter(morkEnv* ev, morkMap* ioMap);
+
+ // The morkAssoc returned below is always either mork_change* or
+ // else nil (when there is no such assoc). We return a pointer to
+ // the change rather than a simple bool, because callers might
+ // want to access change info associated with an assoc.
+
+ mork_change* First(morkEnv* ev, void* outKey, void* outVal);
+ mork_change* Next(morkEnv* ev, void* outKey, void* outVal);
+ mork_change* Here(morkEnv* ev, void* outKey, void* outVal);
+
+ mork_change* CutHere(morkEnv* ev, void* outKey, void* outVal);
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKMAP_ */
diff --git a/components/mork/src/morkNode.cpp b/components/mork/src/morkNode.cpp
new file mode 100644
index 000000000..4e95eed37
--- /dev/null
+++ b/components/mork/src/morkNode.cpp
@@ -0,0 +1,592 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKPOOL_
+#include "morkPool.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKHANDLE_
+#include "morkHandle.h"
+#endif
+
+/*3456789_123456789_123456789_123456789_123456789_123456789_123456789_12345678*/
+
+/* ===== ===== ===== ===== morkUsage ===== ===== ===== ===== */
+
+static morkUsage morkUsage_gHeap; // ensure EnsureReadyStaticUsage()
+const morkUsage& morkUsage::kHeap = morkUsage_gHeap;
+
+static morkUsage morkUsage_gStack; // ensure EnsureReadyStaticUsage()
+const morkUsage& morkUsage::kStack = morkUsage_gStack;
+
+static morkUsage morkUsage_gMember; // ensure EnsureReadyStaticUsage()
+const morkUsage& morkUsage::kMember = morkUsage_gMember;
+
+static morkUsage morkUsage_gGlobal; // ensure EnsureReadyStaticUsage()
+const morkUsage& morkUsage::kGlobal = morkUsage_gGlobal;
+
+static morkUsage morkUsage_gPool; // ensure EnsureReadyStaticUsage()
+const morkUsage& morkUsage::kPool = morkUsage_gPool;
+
+static morkUsage morkUsage_gNone; // ensure EnsureReadyStaticUsage()
+const morkUsage& morkUsage::kNone = morkUsage_gNone;
+
+// This must be structured to allow for non-zero values in global variables
+// just before static init time. We can only safely check for whether a
+// global has the address of some other global. Please, do not initialize
+// either of the variables below to zero, because this could break when a zero
+// is assigned at static init time, but after EnsureReadyStaticUsage() runs.
+
+static mork_u4 morkUsage_g_static_init_target; // only address of this matters
+static mork_u4* morkUsage_g_static_init_done; // is address of target above?
+
+#define morkUsage_do_static_init() \
+ ( morkUsage_g_static_init_done = &morkUsage_g_static_init_target )
+
+#define morkUsage_need_static_init() \
+ ( morkUsage_g_static_init_done != &morkUsage_g_static_init_target )
+
+/*static*/
+void morkUsage::EnsureReadyStaticUsage()
+{
+ if ( morkUsage_need_static_init() )
+ {
+ morkUsage_do_static_init();
+
+ morkUsage_gHeap.InitUsage(morkUsage_kHeap);
+ morkUsage_gStack.InitUsage(morkUsage_kStack);
+ morkUsage_gMember.InitUsage(morkUsage_kMember);
+ morkUsage_gGlobal.InitUsage(morkUsage_kGlobal);
+ morkUsage_gPool.InitUsage(morkUsage_kPool);
+ morkUsage_gNone.InitUsage(morkUsage_kNone);
+ }
+}
+
+/*static*/
+const morkUsage& morkUsage::GetHeap() // kHeap safe at static init time
+{
+ EnsureReadyStaticUsage();
+ return morkUsage_gHeap;
+}
+
+/*static*/
+const morkUsage& morkUsage::GetStack() // kStack safe at static init time
+{
+ EnsureReadyStaticUsage();
+ return morkUsage_gStack;
+}
+
+/*static*/
+const morkUsage& morkUsage::GetMember() // kMember safe at static init time
+{
+ EnsureReadyStaticUsage();
+ return morkUsage_gMember;
+}
+
+/*static*/
+const morkUsage& morkUsage::GetGlobal() // kGlobal safe at static init time
+{
+ EnsureReadyStaticUsage();
+ return morkUsage_gGlobal;
+}
+
+/*static*/
+const morkUsage& morkUsage::GetPool() // kPool safe at static init time
+{
+ EnsureReadyStaticUsage();
+ return morkUsage_gPool;
+}
+
+/*static*/
+const morkUsage& morkUsage::GetNone() // kNone safe at static init time
+{
+ EnsureReadyStaticUsage();
+ return morkUsage_gNone;
+}
+
+morkUsage::morkUsage()
+{
+ if ( morkUsage_need_static_init() )
+ {
+ morkUsage::EnsureReadyStaticUsage();
+ }
+}
+
+morkUsage::morkUsage(mork_usage code)
+ : mUsage_Code(code)
+{
+ if ( morkUsage_need_static_init() )
+ {
+ morkUsage::EnsureReadyStaticUsage();
+ }
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*static*/ void*
+morkNode::MakeNew(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev)
+{
+ void* node = 0;
+ ioHeap.Alloc(ev->AsMdbEnv(), inSize, (void **) &node);
+ if ( !node )
+ ev->OutOfMemoryError();
+
+ return node;
+}
+
+/*public non-poly*/ void
+morkNode::ZapOld(morkEnv* ev, nsIMdbHeap* ioHeap)
+{
+ if ( this->IsNode() )
+ {
+ mork_usage usage = mNode_Usage; // mNode_Usage before ~morkNode
+ this->morkNode::~morkNode(); // first call polymorphic destructor
+ if ( ioHeap ) // was this node heap allocated?
+ ioHeap->Free(ev->AsMdbEnv(), this);
+ else if ( usage == morkUsage_kPool ) // mNode_Usage before ~morkNode
+ {
+ morkHandle* h = (morkHandle*) this;
+ if ( h->IsHandle() && h->GoodHandleTag() )
+ {
+ if ( h->mHandle_Face )
+ {
+ if (ev->mEnv_HandlePool)
+ ev->mEnv_HandlePool->ZapHandle(ev, h->mHandle_Face);
+ else if (h->mHandle_Env && h->mHandle_Env->mEnv_HandlePool)
+ h->mHandle_Env->mEnv_HandlePool->ZapHandle(ev, h->mHandle_Face);
+ }
+ else
+ ev->NilPointerError();
+ }
+ }
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+/*public virtual*/ void
+morkNode::CloseMorkNode(morkEnv* ev) // CloseNode() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseNode(ev);
+ this->MarkShut();
+ }
+}
+NS_IMETHODIMP
+morkNode::CloseMdbObject(nsIMdbEnv* mev)
+{
+ return morkNode::CloseMdbObject((morkEnv *) mev);
+}
+
+nsresult morkNode::CloseMdbObject(morkEnv *ev)
+{
+ // if only one ref, Handle_CutStrongRef will clean up better.
+ if (mNode_Uses == 1)
+ // XXX Casting mork_uses to nsresult
+ return static_cast<nsresult>(CutStrongRef(ev));
+
+ nsresult outErr = NS_OK;
+
+ if ( IsNode() && IsOpenNode() )
+ {
+ if ( ev )
+ {
+ CloseMorkNode(ev);
+ outErr = ev->AsErr();
+ }
+ }
+ return outErr;
+}
+
+/*public virtual*/
+morkNode::~morkNode() // assert that CloseNode() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode() || IsDeadNode()); // sometimes we call destructor explicitly w/o freeing object.
+ mNode_Access = morkAccess_kDead;
+ mNode_Usage = morkUsage_kNone;
+}
+
+/*public virtual*/
+// void CloseMorkNode(morkEnv* ev) = 0; // CloseNode() only if open
+ // CloseMorkNode() is the polymorphic close method called when uses==0,
+ // which must do NOTHING at all when IsOpenNode() is not true. Otherwise,
+ // CloseMorkNode() should call a static close method specific to an object.
+ // Each such static close method should either call inherited static close
+ // methods, or else perform the consolidated effect of calling them, where
+ // subclasses should closely track any changes in base classes with care.
+
+
+/*public non-poly*/
+morkNode::morkNode( mork_usage inCode )
+: mNode_Heap( 0 )
+, mNode_Base( morkBase_kNode )
+, mNode_Derived ( 0 ) // until subclass sets appropriately
+, mNode_Access( morkAccess_kOpen )
+, mNode_Usage( inCode )
+, mNode_Mutable( morkAble_kEnabled )
+, mNode_Load( morkLoad_kClean )
+, mNode_Uses( 1 )
+, mNode_Refs( 1 )
+{
+}
+
+/*public non-poly*/
+morkNode::morkNode(const morkUsage& inUsage, nsIMdbHeap* ioHeap)
+: mNode_Heap( ioHeap )
+, mNode_Base( morkBase_kNode )
+, mNode_Derived ( 0 ) // until subclass sets appropriately
+, mNode_Access( morkAccess_kOpen )
+, mNode_Usage( inUsage.Code() )
+, mNode_Mutable( morkAble_kEnabled )
+, mNode_Load( morkLoad_kClean )
+, mNode_Uses( 1 )
+, mNode_Refs( 1 )
+{
+ if ( !ioHeap && mNode_Usage == morkUsage_kHeap )
+ MORK_ASSERT(ioHeap);
+}
+
+/*public non-poly*/
+morkNode::morkNode(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap)
+: mNode_Heap( ioHeap )
+, mNode_Base( morkBase_kNode )
+, mNode_Derived ( 0 ) // until subclass sets appropriately
+, mNode_Access( morkAccess_kOpen )
+, mNode_Usage( inUsage.Code() )
+, mNode_Mutable( morkAble_kEnabled )
+, mNode_Load( morkLoad_kClean )
+, mNode_Uses( 1 )
+, mNode_Refs( 1 )
+{
+ if ( !ioHeap && mNode_Usage == morkUsage_kHeap )
+ {
+ this->NilHeapError(ev);
+ }
+}
+
+/*protected non-poly*/ void
+morkNode::RefsUnderUsesWarning(morkEnv* ev) const
+{
+ ev->NewError("mNode_Refs < mNode_Uses");
+}
+
+/*protected non-poly*/ void
+morkNode::NonNodeError(morkEnv* ev) const // called when IsNode() is false
+{
+ ev->NewError("non-morkNode");
+}
+
+/*protected non-poly*/ void
+morkNode::NonOpenNodeError(morkEnv* ev) const // when IsOpenNode() is false
+{
+ ev->NewError("non-open-morkNode");
+}
+
+/*protected non-poly*/ void
+morkNode::NonMutableNodeError(morkEnv* ev) const // when IsMutable() is false
+{
+ ev->NewError("non-mutable-morkNode");
+}
+
+/*protected non-poly*/ void
+morkNode::NilHeapError(morkEnv* ev) const // zero mNode_Heap w/ kHeap usage
+{
+ ev->NewError("nil mNode_Heap");
+}
+
+/*protected non-poly*/ void
+morkNode::RefsOverflowWarning(morkEnv* ev) const // mNode_Refs overflow
+{
+ ev->NewWarning("mNode_Refs overflow");
+}
+
+/*protected non-poly*/ void
+morkNode::UsesOverflowWarning(morkEnv* ev) const // mNode_Uses overflow
+{
+ ev->NewWarning("mNode_Uses overflow");
+}
+
+/*protected non-poly*/ void
+morkNode::RefsUnderflowWarning(morkEnv* ev) const // mNode_Refs underflow
+{
+ ev->NewWarning("mNode_Refs underflow");
+}
+
+/*protected non-poly*/ void
+morkNode::UsesUnderflowWarning(morkEnv* ev) const // mNode_Uses underflow
+{
+ ev->NewWarning("mNode_Uses underflow");
+}
+
+/*public non-poly*/ void
+morkNode::CloseNode(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ this->MarkShut();
+ else
+ this->NonNodeError(ev);
+}
+
+
+extern void // utility method very similar to morkNode::SlotStrongNode():
+nsIMdbFile_SlotStrongFile(nsIMdbFile* self, morkEnv* ev, nsIMdbFile** ioSlot)
+ // If *ioSlot is non-nil, that file is released by CutStrongRef() and
+ // then zeroed out. Then if self is non-nil, this is acquired by
+ // calling AddStrongRef(), and if the return value shows success,
+ // then self is put into slot *ioSlot. Note self can be nil, so we take
+ // expression 'nsIMdbFile_SlotStrongFile(0, ev, &slot)'.
+{
+ nsIMdbFile* file = *ioSlot;
+ if ( self != file )
+ {
+ if ( file )
+ {
+ *ioSlot = 0;
+ NS_RELEASE(file);
+ }
+ if (self && ev->Good())
+ NS_ADDREF(*ioSlot = self);
+ }
+}
+
+void // utility method very similar to morkNode::SlotStrongNode():
+nsIMdbHeap_SlotStrongHeap(nsIMdbHeap* self, morkEnv* ev, nsIMdbHeap** ioSlot)
+ // If *ioSlot is non-nil, that heap is released by CutStrongRef() and
+ // then zeroed out. Then if self is non-nil, self is acquired by
+ // calling AddStrongRef(), and if the return value shows success,
+ // then self is put into slot *ioSlot. Note self can be nil, so we
+ // permit expression 'nsIMdbHeap_SlotStrongHeap(0, ev, &slot)'.
+{
+ nsIMdbHeap* heap = *ioSlot;
+ if ( self != heap )
+ {
+ if ( heap )
+ *ioSlot = 0;
+
+ if (self && ev->Good())
+ *ioSlot = self;
+ }
+}
+
+/*public static*/ void
+morkNode::SlotStrongNode(morkNode* me, morkEnv* ev, morkNode** ioSlot)
+ // If *ioSlot is non-nil, that node is released by CutStrongRef() and
+ // then zeroed out. Then if me is non-nil, this is acquired by
+ // calling AddStrongRef(), and if positive is returned to show success,
+ // then me is put into slot *ioSlot. Note me can be nil, so we take
+ // expression 'morkNode::SlotStrongNode((morkNode*) 0, ev, &slot)'.
+{
+ morkNode* node = *ioSlot;
+ if ( me != node )
+ {
+ if ( node )
+ {
+ // what if this nulls out the ev and causes asserts?
+ // can we move this after the CutStrongRef()?
+ *ioSlot = 0;
+ node->CutStrongRef(ev);
+ }
+ if ( me && me->AddStrongRef(ev) )
+ *ioSlot = me;
+ }
+}
+
+/*public static*/ void
+morkNode::SlotWeakNode(morkNode* me, morkEnv* ev, morkNode** ioSlot)
+ // If *ioSlot is non-nil, that node is released by CutWeakRef() and
+ // then zeroed out. Then if me is non-nil, this is acquired by
+ // calling AddWeakRef(), and if positive is returned to show success,
+ // then me is put into slot *ioSlot. Note me can be nil, so we
+ // expression 'morkNode::SlotWeakNode((morkNode*) 0, ev, &slot)'.
+{
+ morkNode* node = *ioSlot;
+ if ( me != node )
+ {
+ if ( node )
+ {
+ *ioSlot = 0;
+ node->CutWeakRef(ev);
+ }
+ if ( me && me->AddWeakRef(ev) )
+ *ioSlot = me;
+ }
+}
+
+/*public non-poly*/ mork_uses
+morkNode::AddStrongRef(morkEnv* ev)
+{
+ mork_uses outUses = 0;
+ if ( this->IsNode() )
+ {
+ mork_uses uses = mNode_Uses;
+ mork_refs refs = mNode_Refs;
+ if ( refs < uses ) // need to fix broken refs/uses relation?
+ {
+ this->RefsUnderUsesWarning(ev);
+ mNode_Refs = mNode_Uses = refs = uses;
+ }
+ if ( refs < morkNode_kMaxRefCount ) // not too great?
+ {
+ mNode_Refs = ++refs;
+ mNode_Uses = ++uses;
+ }
+ else
+ this->RefsOverflowWarning(ev);
+
+ outUses = uses;
+ }
+ else
+ this->NonNodeError(ev);
+ return outUses;
+}
+
+/*private non-poly*/ mork_bool
+morkNode::cut_use_count(morkEnv* ev) // just one part of CutStrongRef()
+{
+ mork_bool didCut = morkBool_kFalse;
+ if ( this->IsNode() )
+ {
+ mork_uses uses = mNode_Uses;
+ if ( uses ) // not yet zero?
+ mNode_Uses = --uses;
+ else
+ this->UsesUnderflowWarning(ev);
+
+ didCut = morkBool_kTrue;
+ if ( !mNode_Uses ) // last use gone? time to close node?
+ {
+ if ( this->IsOpenNode() )
+ {
+ if ( !mNode_Refs ) // no outstanding reference?
+ {
+ this->RefsUnderflowWarning(ev);
+ ++mNode_Refs; // prevent potential crash during close
+ }
+ this->CloseMorkNode(ev); // polymorphic self close
+ // (Note CutNode() is not polymorphic -- so don't call that.)
+ }
+ }
+ }
+ else
+ this->NonNodeError(ev);
+ return didCut;
+}
+
+/*public non-poly*/ mork_uses
+morkNode::CutStrongRef(morkEnv* ev)
+{
+ mork_refs outRefs = 0;
+ if ( this->IsNode() )
+ {
+ if ( this->cut_use_count(ev) )
+ outRefs = this->CutWeakRef(ev);
+ }
+ else
+ this->NonNodeError(ev);
+
+ return outRefs;
+}
+
+/*public non-poly*/ mork_refs
+morkNode::AddWeakRef(morkEnv* ev)
+{
+ mork_refs outRefs = 0;
+ if ( this->IsNode() )
+ {
+ mork_refs refs = mNode_Refs;
+ if ( refs < morkNode_kMaxRefCount ) // not too great?
+ mNode_Refs = ++refs;
+ else
+ this->RefsOverflowWarning(ev);
+
+ outRefs = refs;
+ }
+ else
+ this->NonNodeError(ev);
+
+ return outRefs;
+}
+
+/*public non-poly*/ mork_refs
+morkNode::CutWeakRef(morkEnv* ev)
+{
+ mork_refs outRefs = 0;
+ if ( this->IsNode() )
+ {
+ mork_uses uses = mNode_Uses;
+ mork_refs refs = mNode_Refs;
+ if ( refs ) // not yet zero?
+ mNode_Refs = --refs;
+ else
+ this->RefsUnderflowWarning(ev);
+
+ if ( refs < uses ) // need to fix broken refs/uses relation?
+ {
+ this->RefsUnderUsesWarning(ev);
+ mNode_Refs = mNode_Uses = refs = uses;
+ }
+
+ outRefs = refs;
+ if ( !refs ) // last reference gone? time to destroy node?
+ this->ZapOld(ev, mNode_Heap); // self destroy, use this no longer
+ }
+ else
+ this->NonNodeError(ev);
+
+ return outRefs;
+}
+
+static const char morkNode_kBroken[] = "broken";
+
+/*public non-poly*/ const char*
+morkNode::GetNodeAccessAsString() const // e.g. "open", "shut", etc.
+{
+ const char* outString = morkNode_kBroken;
+ switch( mNode_Access )
+ {
+ case morkAccess_kOpen: outString = "open"; break;
+ case morkAccess_kClosing: outString = "closing"; break;
+ case morkAccess_kShut: outString = "shut"; break;
+ case morkAccess_kDead: outString = "dead"; break;
+ }
+ return outString;
+}
+
+/*public non-poly*/ const char*
+morkNode::GetNodeUsageAsString() const // e.g. "heap", "stack", etc.
+{
+ const char* outString = morkNode_kBroken;
+ switch( mNode_Usage )
+ {
+ case morkUsage_kHeap: outString = "heap"; break;
+ case morkUsage_kStack: outString = "stack"; break;
+ case morkUsage_kMember: outString = "member"; break;
+ case morkUsage_kGlobal: outString = "global"; break;
+ case morkUsage_kPool: outString = "pool"; break;
+ case morkUsage_kNone: outString = "none"; break;
+ }
+ return outString;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkNode.h b/components/mork/src/morkNode.h
new file mode 100644
index 000000000..02dbbb229
--- /dev/null
+++ b/components/mork/src/morkNode.h
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _MORKNODE_
+#define _MORKNODE_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkUsage_kHeap 'h'
+#define morkUsage_kStack 's'
+#define morkUsage_kMember 'm'
+#define morkUsage_kGlobal 'g'
+#define morkUsage_kPool 'p'
+#define morkUsage_kNone 'n'
+
+class morkUsage {
+public:
+ mork_usage mUsage_Code; // kHeap, kStack, kMember, or kGhost
+
+public:
+ explicit morkUsage(mork_usage inCode);
+
+ morkUsage(); // does nothing except maybe call EnsureReadyStaticUsage()
+ void InitUsage( mork_usage inCode)
+ { mUsage_Code = inCode; }
+
+ ~morkUsage() { }
+ mork_usage Code() const { return mUsage_Code; }
+
+ static void EnsureReadyStaticUsage();
+
+public:
+ static const morkUsage& kHeap; // morkUsage_kHeap
+ static const morkUsage& kStack; // morkUsage_kStack
+ static const morkUsage& kMember; // morkUsage_kMember
+ static const morkUsage& kGlobal; // morkUsage_kGlobal
+ static const morkUsage& kPool; // morkUsage_kPool
+ static const morkUsage& kNone; // morkUsage_kNone
+
+ static const morkUsage& GetHeap(); // kHeap, safe at static init time
+ static const morkUsage& GetStack(); // kStack, safe at static init time
+ static const morkUsage& GetMember(); // kMember, safe at static init time
+ static const morkUsage& GetGlobal(); // kGlobal, safe at static init time
+ static const morkUsage& GetPool(); // kPool, safe at static init time
+ static const morkUsage& GetNone(); // kNone, safe at static init time
+
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkNode_kMaxRefCount 0x0FFFF /* count sticks if it hits this */
+
+#define morkBase_kNode /*i*/ 0x4E64 /* ascii 'Nd' */
+
+/*| morkNode: several groups of two-byte integers that track the basic
+**| status of an object that can be used to compose in-memory graphs.
+**| This is the base class for nsIMdbObject (which adds members that fit
+**| the needs of an nsIMdbObject subclass). The morkNode class is also used
+**| as the base class for other Mork db classes with no strong relation to
+**| the MDB class hierarchy.
+**|
+**|| Heap: the heap in which this node was allocated, when the usage equals
+**| morkUsage_kHeap to show dynamic allocation. Note this heap is NOT ref-
+**| counted, because that would be too great and complex a burden for all
+**| the nodes allocated in that heap. So heap users should take care to
+**| understand that nodes allocated in that heap are considered protected
+**| by some inclusive context in which all those nodes are allocated, and
+**| that context must maintain at least one strong refcount for the heap.
+**| Occasionally a node subclass will indeed wish to hold a refcounted
+**| reference to a heap, and possibly the same heap that is in mNode_Heap,
+**| but this is always done in a separate slot that explicitly refcounts,
+**| so we avoid confusing what is meant by the mNode_Heap slot.
+|*/
+class morkNode /*: public nsISupports */ { // base class for constructing Mork object graphs
+
+public: // state is public because the entire Mork system is private
+
+// NS_DECL_ISUPPORTS;
+ nsIMdbHeap* mNode_Heap; // NON-refcounted heap pointer
+
+ mork_base mNode_Base; // must equal morkBase_kNode
+ mork_derived mNode_Derived; // depends on specific node subclass
+
+ mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ mork_able mNode_Mutable; // can this node be modified?
+ mork_load mNode_Load; // is this node clean or dirty?
+
+ mork_uses mNode_Uses; // refcount for strong refs
+ mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+protected: // special case empty construction for morkHandleFrame
+ friend class morkHandleFrame;
+ morkNode() { }
+
+public: // inlines for weird mNode_Mutable and mNode_Load constants
+
+ void SetFrozen() { mNode_Mutable = morkAble_kDisabled; }
+ void SetMutable() { mNode_Mutable = morkAble_kEnabled; }
+ void SetAsleep() { mNode_Mutable = morkAble_kAsleep; }
+
+ mork_bool IsFrozen() const { return mNode_Mutable == morkAble_kDisabled; }
+ mork_bool IsMutable() const { return mNode_Mutable == morkAble_kEnabled; }
+ mork_bool IsAsleep() const { return mNode_Mutable == morkAble_kAsleep; }
+
+ void SetNodeClean() { mNode_Load = morkLoad_kClean; }
+ void SetNodeDirty() { mNode_Load = morkLoad_kDirty; }
+
+ mork_bool IsNodeClean() const { return mNode_Load == morkLoad_kClean; }
+ mork_bool IsNodeDirty() const { return mNode_Load == morkLoad_kDirty; }
+
+public: // morkNode memory management methods
+ static void* MakeNew(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev);
+
+ void ZapOld(morkEnv* ev, nsIMdbHeap* ioHeap); // replaces operator delete()
+ // this->morkNode::~morkNode(); // first call polymorphic destructor
+ // if ( ioHeap ) // was this node heap allocated?
+ // ioHeap->Free(ev->AsMdbEnv(), this);
+
+public: // morkNode memory management operators
+ void* operator new(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev) CPP_THROW_NEW
+ { return morkNode::MakeNew(inSize, ioHeap, ev); }
+
+
+protected: // construction without an anv needed for first env constructed:
+ morkNode(const morkUsage& inUsage, nsIMdbHeap* ioHeap);
+
+ explicit morkNode(mork_usage inCode); // usage == inCode, heap == nil
+
+// { ===== begin basic node interface =====
+public: // morkNode virtual methods
+ // virtual FlushMorkNode(morkEnv* ev, morkStream* ioStream);
+ // virtual WriteMorkNode(morkEnv* ev, morkStream* ioStream);
+
+ virtual ~morkNode(); // assert that CloseNode() executed earlier
+ virtual void CloseMorkNode(morkEnv* ev); // CloseNode() only if open
+
+ // CloseMorkNode() is the polymorphic close method called when uses==0,
+ // which must do NOTHING at all when IsOpenNode() is not true. Otherwise,
+ // CloseMorkNode() should call a static close method specific to an object.
+ // Each such static close method should either call inherited static close
+ // methods, or else perform the consolidated effect of calling them, where
+ // subclasses should closely track any changes in base classes with care.
+
+public: // morkNode construction
+ morkNode(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap);
+ void CloseNode(morkEnv* ev); // called by CloseMorkNode();
+ nsresult CloseMdbObject(morkEnv *ev);
+ NS_IMETHOD CloseMdbObject(nsIMdbEnv *ev);
+private: // copying is not allowed
+ morkNode(const morkNode& other);
+ morkNode& operator=(const morkNode& other);
+
+public: // dynamic type identification
+ mork_bool IsNode() const
+ { return mNode_Base == morkBase_kNode; }
+// } ===== end basic node methods =====
+
+public: // public error & warning methods
+
+ void RefsUnderUsesWarning(morkEnv* ev) const; // call if mNode_Refs < mNode_Uses
+ void NonNodeError(morkEnv* ev) const; // call when IsNode() is false
+ void NilHeapError(morkEnv* ev) const; // zero mNode_Heap when usage is kHeap
+ void NonOpenNodeError(morkEnv* ev) const; // call when IsOpenNode() is false
+
+ void NonMutableNodeError(morkEnv* ev) const; // when IsMutable() is false
+
+ void RefsOverflowWarning(morkEnv* ev) const; // call on mNode_Refs overflow
+ void UsesOverflowWarning(morkEnv* ev) const; // call on mNode_Uses overflow
+ void RefsUnderflowWarning(morkEnv* ev) const; // call on mNode_Refs underflow
+ void UsesUnderflowWarning(morkEnv* ev) const; // call on mNode_Uses underflow
+
+private: // private refcounting methods
+ mork_bool cut_use_count(morkEnv* ev); // just one part of CutStrongRef()
+
+public: // other morkNode methods
+
+ mork_bool GoodRefs() const { return mNode_Refs >= mNode_Uses; }
+ mork_bool BadRefs() const { return mNode_Refs < mNode_Uses; }
+
+ mork_uses StrongRefsOnly() const { return mNode_Uses; }
+ mork_refs WeakRefsOnly() const { return (mork_refs) ( mNode_Refs - mNode_Uses ); }
+
+ // (this refcounting derives from public domain IronDoc node refcounts)
+ virtual mork_uses AddStrongRef(morkEnv* ev);
+ virtual mork_uses CutStrongRef(morkEnv* ev);
+ mork_refs AddWeakRef(morkEnv* ev);
+ mork_refs CutWeakRef(morkEnv* ev);
+
+ const char* GetNodeAccessAsString() const; // e.g. "open", "shut", etc.
+ const char* GetNodeUsageAsString() const; // e.g. "heap", "stack", etc.
+
+ mork_usage NodeUsage() const { return mNode_Usage; }
+
+ mork_bool IsHeapNode() const
+ { return mNode_Usage == morkUsage_kHeap; }
+
+ mork_bool IsOpenNode() const
+ { return mNode_Access == morkAccess_kOpen; }
+
+ mork_bool IsShutNode() const
+ { return mNode_Access == morkAccess_kShut; }
+
+ mork_bool IsDeadNode() const
+ { return mNode_Access == morkAccess_kDead; }
+
+ mork_bool IsClosingNode() const
+ { return mNode_Access == morkAccess_kClosing; }
+
+ mork_bool IsOpenOrClosingNode() const
+ { return IsOpenNode() || IsClosingNode(); }
+
+ mork_bool HasNodeAccess() const
+ { return ( IsOpenNode() || IsShutNode() || IsClosingNode() ); }
+
+ void MarkShut() { mNode_Access = morkAccess_kShut; }
+ void MarkClosing() { mNode_Access = morkAccess_kClosing; }
+ void MarkDead() { mNode_Access = morkAccess_kDead; }
+
+public: // refcounting for typesafe subclass inline methods
+ static void SlotWeakNode(morkNode* me, morkEnv* ev, morkNode** ioSlot);
+ // If *ioSlot is non-nil, that node is released by CutWeakRef() and
+ // then zeroed out. Then if me is non-nil, this is acquired by
+ // calling AddWeakRef(), and if positive is returned to show success,
+ // then this is put into slot *ioSlot. Note me can be nil, so we
+ // permit expression '((morkNode*) 0L)->SlotWeakNode(ev, &slot)'.
+
+ static void SlotStrongNode(morkNode* me, morkEnv* ev, morkNode** ioSlot);
+ // If *ioSlot is non-nil, that node is released by CutStrongRef() and
+ // then zeroed out. Then if me is non-nil, this is acquired by
+ // calling AddStrongRef(), and if positive is returned to show success,
+ // then me is put into slot *ioSlot. Note me can be nil, so we take
+ // expression 'morkNode::SlotStrongNode((morkNode*) 0, ev, &slot)'.
+};
+
+extern void // utility method very similar to morkNode::SlotStrongNode():
+nsIMdbHeap_SlotStrongHeap(nsIMdbHeap* self, morkEnv* ev, nsIMdbHeap** ioSlot);
+ // If *ioSlot is non-nil, that heap is released by CutStrongRef() and
+ // then zeroed out. Then if self is non-nil, this is acquired by
+ // calling AddStrongRef(), and if the return value shows success,
+ // then self is put into slot *ioSlot. Note self can be nil, so we take
+ // expression 'nsIMdbHeap_SlotStrongHeap(0, ev, &slot)'.
+
+extern void // utility method very similar to morkNode::SlotStrongNode():
+nsIMdbFile_SlotStrongFile(nsIMdbFile* self, morkEnv* ev, nsIMdbFile** ioSlot);
+ // If *ioSlot is non-nil, that file is released by CutStrongRef() and
+ // then zeroed out. Then if self is non-nil, this is acquired by
+ // calling AddStrongRef(), and if the return value shows success,
+ // then self is put into slot *ioSlot. Note self can be nil, so we take
+ // expression 'nsIMdbFile_SlotStrongFile(0, ev, &slot)'.
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKNODE_ */
diff --git a/components/mork/src/morkNodeMap.cpp b/components/mork/src/morkNodeMap.cpp
new file mode 100644
index 000000000..7178fbafc
--- /dev/null
+++ b/components/mork/src/morkNodeMap.cpp
@@ -0,0 +1,155 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKINTMAP_
+#include "morkIntMap.h"
+#endif
+
+#ifndef _MORKNODEMAP_
+#include "morkNodeMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkNodeMap::CloseMorkNode(morkEnv* ev) // CloseNodeMap() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseNodeMap(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkNodeMap::~morkNodeMap() // assert CloseNodeMap() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkNodeMap::morkNodeMap(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+: morkIntMap(ev, inUsage, /*valsize*/ sizeof(morkNode*), ioHeap, ioSlotHeap,
+ /*inHoldChanges*/ morkBool_kTrue)
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kNodeMap;
+}
+
+/*public non-poly*/ void
+morkNodeMap::CloseNodeMap(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ this->CutAllNodes(ev);
+ this->CloseMap(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+mork_bool
+morkNodeMap::AddNode(morkEnv* ev, mork_token inToken, morkNode* ioNode)
+ // the AddNode() method return value equals ev->Good().
+{
+ if ( ioNode && ev->Good() )
+ {
+ morkNode* node = 0; // old val in the map
+
+ mork_bool put = this->Put(ev, &inToken, &ioNode,
+ /*key*/ (void*) 0, &node, (mork_change**) 0);
+
+ if ( put ) // replaced an existing value for key inToken?
+ {
+ if ( node && node != ioNode ) // need to release old node?
+ node->CutStrongRef(ev);
+ }
+
+ if ( ev->Bad() || !ioNode->AddStrongRef(ev) )
+ {
+ // problems adding node or increasing refcount?
+ this->Cut(ev, &inToken, // make sure not in map
+ /*key*/ (void*) 0, /*val*/ (void*) 0, (mork_change**) 0);
+ }
+ }
+ else if ( !ioNode )
+ ev->NilPointerError();
+
+ return ev->Good();
+}
+
+mork_bool
+morkNodeMap::CutNode(morkEnv* ev, mork_token inToken)
+{
+ morkNode* node = 0; // old val in the map
+ mork_bool outCutNode = this->Cut(ev, &inToken,
+ /*key*/ (void*) 0, &node, (mork_change**) 0);
+ if ( node )
+ node->CutStrongRef(ev);
+
+ return outCutNode;
+}
+
+morkNode*
+morkNodeMap::GetNode(morkEnv* ev, mork_token inToken)
+ // Note the returned node does NOT have an increase in refcount for this.
+{
+ morkNode* node = 0; // old val in the map
+ this->Get(ev, &inToken, /*key*/ (void*) 0, &node, (mork_change**) 0);
+
+ return node;
+}
+
+mork_num
+morkNodeMap::CutAllNodes(morkEnv* ev)
+ // CutAllNodes() releases all the reference node values.
+{
+ mork_num outSlots = mMap_Slots;
+ mork_token key = 0; // old key token in the map
+ morkNode* val = 0; // old val node in the map
+
+ mork_change* c = 0;
+ morkNodeMapIter i(ev, this);
+ for ( c = i.FirstNode(ev, &key, &val); c ; c = i.NextNode(ev, &key, &val) )
+ {
+ if ( val )
+ val->CutStrongRef(ev);
+ i.CutHereNode(ev, /*key*/ (mork_token*) 0, /*val*/ (morkNode**) 0);
+ }
+
+ return outSlots;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
diff --git a/components/mork/src/morkNodeMap.h b/components/mork/src/morkNodeMap.h
new file mode 100644
index 000000000..9f4f8e519
--- /dev/null
+++ b/components/mork/src/morkNodeMap.h
@@ -0,0 +1,98 @@
+/* -*- 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/. */
+
+#ifndef _MORKNODEMAP_
+#define _MORKNODEMAP_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKINTMAP_
+#include "morkIntMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kNodeMap /*i*/ 0x6E4D /* ascii 'nM' */
+
+#define morkNodeMap_kStartSlotCount 512
+
+/*| morkNodeMap: maps mork_token -> morkNode
+|*/
+class morkNodeMap : public morkIntMap { // for mapping tokens to nodes
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseNodeMap() only if open
+ virtual ~morkNodeMap(); // assert that CloseNodeMap() executed earlier
+
+public: // morkMap construction & destruction
+ morkNodeMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap);
+ void CloseNodeMap(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsNodeMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kNodeMap; }
+// } ===== end morkNode methods =====
+
+// { ===== begin morkMap poly interface =====
+ // use the Equal() and Hash() for mork_u4 inherited from morkIntMap
+// } ===== end morkMap poly interface =====
+
+protected: // we want all subclasses to provide typesafe wrappers:
+
+ mork_bool AddNode(morkEnv* ev, mork_token inToken, morkNode* ioNode);
+ // the AddNode() boolean return equals ev->Good().
+
+ mork_bool CutNode(morkEnv* ev, mork_token inToken);
+ // The CutNode() boolean return indicates whether removal happened.
+
+ morkNode* GetNode(morkEnv* ev, mork_token inToken);
+ // Note the returned node does NOT have an increase in refcount for this.
+
+ mork_num CutAllNodes(morkEnv* ev);
+ // CutAllNodes() releases all the reference node values.
+};
+
+class morkNodeMapIter: public morkMapIter{ // typesafe wrapper class
+
+public:
+ morkNodeMapIter(morkEnv* ev, morkNodeMap* ioMap)
+ : morkMapIter(ev, ioMap) { }
+
+ morkNodeMapIter( ) : morkMapIter() { }
+ void InitNodeMapIter(morkEnv* ev, morkNodeMap* ioMap)
+ { this->InitMapIter(ev, ioMap); }
+
+ mork_change*
+ FirstNode(morkEnv* ev, mork_token* outToken, morkNode** outNode)
+ { return this->First(ev, outToken, outNode); }
+
+ mork_change*
+ NextNode(morkEnv* ev, mork_token* outToken, morkNode** outNode)
+ { return this->Next(ev, outToken, outNode); }
+
+ mork_change*
+ HereNode(morkEnv* ev, mork_token* outToken, morkNode** outNode)
+ { return this->Here(ev, outToken, outNode); }
+
+ mork_change*
+ CutHereNode(morkEnv* ev, mork_token* outToken, morkNode** outNode)
+ { return this->CutHere(ev, outToken, outNode); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKNODEMAP_ */
diff --git a/components/mork/src/morkObject.cpp b/components/mork/src/morkObject.cpp
new file mode 100644
index 000000000..f04aff4d3
--- /dev/null
+++ b/components/mork/src/morkObject.cpp
@@ -0,0 +1,206 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+#ifndef _MORKHANDLE_
+#include "morkHandle.h"
+#endif
+
+#include "nsCOMPtr.h"
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+NS_IMPL_ISUPPORTS(morkObject, nsIMdbObject)
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkObject::CloseMorkNode(morkEnv* ev) // CloseObject() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseObject(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkObject::~morkObject() // assert CloseObject() executed earlier
+{
+ if (!IsShutNode())
+ CloseMorkNode(this->mMorkEnv);
+ MORK_ASSERT(mObject_Handle==0);
+}
+
+/*public non-poly*/
+morkObject::morkObject(const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ mork_color inBeadColor)
+: morkBead(inUsage, ioHeap, inBeadColor)
+, mObject_Handle( 0 )
+{
+ mMorkEnv = nullptr;
+}
+
+/*public non-poly*/
+morkObject::morkObject(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ mork_color inBeadColor, morkHandle* ioHandle)
+: morkBead(ev, inUsage, ioHeap, inBeadColor)
+, mObject_Handle( 0 )
+{
+ mMorkEnv = ev;
+ if ( ev->Good() )
+ {
+ if ( ioHandle )
+ morkHandle::SlotWeakHandle(ioHandle, ev, &mObject_Handle);
+
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kObject;
+ }
+}
+
+/*public non-poly*/ void
+morkObject::CloseObject(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ if ( !this->IsShutNode() )
+ {
+ if ( mObject_Handle )
+ morkHandle::SlotWeakHandle((morkHandle*) 0L, ev, &mObject_Handle);
+
+ mBead_Color = 0; // this->CloseBead(ev);
+ this->MarkShut();
+ }
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+// { ----- begin factory methods -----
+NS_IMETHODIMP
+morkObject::GetMdbFactory(nsIMdbEnv* mev, nsIMdbFactory** acqFactory)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMdbObject> obj = do_QueryInterface(mev);
+ if (obj)
+ rv = obj->GetMdbFactory(mev, acqFactory);
+ else
+ return NS_ERROR_NO_INTERFACE;
+
+ return rv;
+}
+// } ----- end factory methods -----
+
+// { ----- begin ref counting for well-behaved cyclic graphs -----
+NS_IMETHODIMP
+morkObject::GetWeakRefCount(nsIMdbEnv* mev, // weak refs
+ mdb_count* outCount)
+{
+ *outCount = WeakRefsOnly();
+ return NS_OK;
+}
+NS_IMETHODIMP
+morkObject::GetStrongRefCount(nsIMdbEnv* mev, // strong refs
+ mdb_count* outCount)
+{
+ *outCount = StrongRefsOnly();
+ return NS_OK;
+}
+// ### TODO - clean up this cast, if required
+NS_IMETHODIMP
+morkObject::AddWeakRef(nsIMdbEnv* mev)
+{
+ // XXX Casting mork_refs to nsresult
+ return static_cast<nsresult>(morkNode::AddWeakRef((morkEnv *) mev));
+}
+
+#ifndef _MSC_VER
+NS_IMETHODIMP_(mork_uses)
+morkObject::AddStrongRef(morkEnv* mev)
+{
+ return morkNode::AddStrongRef(mev);
+}
+#endif
+
+NS_IMETHODIMP_(mork_uses)
+morkObject::AddStrongRef(nsIMdbEnv* mev)
+{
+ return morkNode::AddStrongRef((morkEnv *) mev);
+}
+
+NS_IMETHODIMP
+morkObject::CutWeakRef(nsIMdbEnv* mev)
+{
+ // XXX Casting mork_refs to nsresult
+ return static_cast<nsresult>(morkNode::CutWeakRef((morkEnv *) mev));
+}
+
+#ifndef _MSC_VER
+NS_IMETHODIMP_(mork_uses)
+morkObject::CutStrongRef(morkEnv* mev)
+{
+ return morkNode::CutStrongRef(mev);
+}
+#endif
+
+NS_IMETHODIMP
+morkObject::CutStrongRef(nsIMdbEnv* mev)
+{
+ // XXX Casting mork_refs to nsresult
+ return static_cast<nsresult>(morkNode::CutStrongRef((morkEnv *) mev));
+}
+
+NS_IMETHODIMP
+morkObject::CloseMdbObject(nsIMdbEnv* mev)
+{
+ return morkNode::CloseMdbObject((morkEnv *) mev);
+}
+
+NS_IMETHODIMP
+morkObject::IsOpenMdbObject(nsIMdbEnv* mev, mdb_bool* outOpen)
+{
+ *outOpen = IsOpenNode();
+ return NS_OK;
+}
+NS_IMETHODIMP
+morkObject::IsFrozenMdbObject(nsIMdbEnv* mev, mdb_bool* outIsReadonly)
+{
+ *outIsReadonly = IsFrozen();
+ return NS_OK;
+}
+
+//void morkObject::NewNilHandleError(morkEnv* ev) // mObject_Handle is nil
+//{
+// ev->NewError("nil mObject_Handle");
+//}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkObject.h b/components/mork/src/morkObject.h
new file mode 100644
index 000000000..eac478d35
--- /dev/null
+++ b/components/mork/src/morkObject.h
@@ -0,0 +1,143 @@
+/* -*- 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/. */
+
+#ifndef _MORKOBJECT_
+#define _MORKOBJECT_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKBEAD_
+#include "morkBead.h"
+#endif
+
+#ifndef _MORKCONFIG_
+#include "morkConfig.h"
+#endif
+
+#ifndef _ORKINHEAP_
+#include "orkinHeap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kObject /*i*/ 0x6F42 /* ascii 'oB' */
+
+/*| morkObject: subclass of morkNode that adds knowledge of db suite factory
+**| and containing port to those objects that are exposed as instances of
+**| nsIMdbObject in the public interface.
+|*/
+class morkObject : public morkBead, public nsIMdbObject {
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // mork_color mBead_Color; // ID for this bead
+
+public: // state is public because the entire Mork system is private
+
+ morkHandle* mObject_Handle; // weak ref to handle for this object
+
+ morkEnv * mMorkEnv; // weak ref to environment this object created in.
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseObject() only if open
+#ifdef MORK_DEBUG_HEAP_STATS
+ void operator delete(void* ioAddress, size_t size)
+ {
+ mork_u4* array = (mork_u4*) ioAddress;
+ array -= 3;
+ orkinHeap *heap = (orkinHeap *) *array;
+ if (heap)
+ heap->Free(nullptr, ioAddress);
+ }
+#endif
+
+ NS_DECL_ISUPPORTS
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD IsFrozenMdbObject(nsIMdbEnv* ev, mdb_bool* outIsReadonly) override;
+ // same as nsIMdbPort::GetIsPortReadonly() when this object is inside a port.
+ // } ----- end attribute methods -----
+
+ // { ----- begin factory methods -----
+ NS_IMETHOD GetMdbFactory(nsIMdbEnv* ev, nsIMdbFactory** acqFactory) override;
+ // } ----- end factory methods -----
+
+ // { ----- begin ref counting for well-behaved cyclic graphs -----
+ NS_IMETHOD GetWeakRefCount(nsIMdbEnv* ev, // weak refs
+ mdb_count* outCount) override;
+ NS_IMETHOD GetStrongRefCount(nsIMdbEnv* ev, // strong refs
+ mdb_count* outCount) override;
+
+ NS_IMETHOD AddWeakRef(nsIMdbEnv* ev) override;
+#ifndef _MSC_VER
+ // The first declaration of AddStrongRef is to suppress -Werror,-Woverloaded-virtual.
+ NS_IMETHOD_(mork_uses) AddStrongRef(morkEnv* ev) override;
+#endif
+ NS_IMETHOD_(mork_uses) AddStrongRef(nsIMdbEnv* ev) override;
+
+ NS_IMETHOD CutWeakRef(nsIMdbEnv* ev) override;
+#ifndef _MSC_VER
+ // The first declaration of CutStrongRef is to suppress -Werror,-Woverloaded-virtual.
+ NS_IMETHOD_(mork_uses) CutStrongRef(morkEnv* ev) override;
+#endif
+ NS_IMETHOD CutStrongRef(nsIMdbEnv* ev) override;
+
+ NS_IMETHOD CloseMdbObject(nsIMdbEnv* ev) override; // called at strong refs zero
+ NS_IMETHOD IsOpenMdbObject(nsIMdbEnv* ev, mdb_bool* outOpen) override;
+ // } ----- end ref counting -----
+
+
+protected: // special case construction of first env without preceding env
+ morkObject(const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ mork_color inBeadColor);
+ virtual ~morkObject(); // assert that CloseObject() executed earlier
+
+public: // morkEnv construction & destruction
+ morkObject(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ mork_color inBeadColor, morkHandle* ioHandle); // ioHandle can be nil
+ void CloseObject(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkObject(const morkObject& other);
+ morkObject& operator=(const morkObject& other);
+
+public: // dynamic type identification
+ mork_bool IsObject() const
+ { return IsNode() && mNode_Derived == morkDerived_kObject; }
+// } ===== end morkNode methods =====
+
+ // void NewNilHandleError(morkEnv* ev); // mObject_Handle is nil
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakObject(morkObject* me,
+ morkEnv* ev, morkObject** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongObject(morkObject* me,
+ morkEnv* ev, morkObject** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKOBJECT_ */
diff --git a/components/mork/src/morkParser.cpp b/components/mork/src/morkParser.cpp
new file mode 100644
index 000000000..667a597fd
--- /dev/null
+++ b/components/mork/src/morkParser.cpp
@@ -0,0 +1,1568 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKPARSER_
+#include "morkParser.h"
+#endif
+
+#ifndef _MORKSTREAM_
+#include "morkStream.h"
+#endif
+
+#ifndef _MORKBLOB_
+#include "morkBlob.h"
+#endif
+
+#ifndef _MORKSINK_
+#include "morkSink.h"
+#endif
+
+#ifndef _MORKCH_
+#include "morkCh.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkParser::CloseMorkNode(morkEnv* ev) // CloseParser() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseParser(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkParser::~morkParser() // assert CloseParser() executed earlier
+{
+ MORK_ASSERT(mParser_Heap==0);
+ MORK_ASSERT(mParser_Stream==0);
+}
+
+/*public non-poly*/
+morkParser::morkParser(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ morkStream* ioStream, mdb_count inBytesPerParseSegment,
+ nsIMdbHeap* ioSlotHeap)
+: morkNode(ev, inUsage, ioHeap)
+, mParser_Heap( 0 )
+, mParser_Stream( 0 )
+, mParser_MoreGranularity( inBytesPerParseSegment )
+, mParser_State( morkParser_kStartState )
+
+, mParser_GroupContentStartPos( 0 )
+
+, mParser_TableMid( )
+, mParser_RowMid( )
+, mParser_CellMid( )
+
+, mParser_InPort( morkBool_kFalse )
+, mParser_InDict( morkBool_kFalse )
+, mParser_InCell( morkBool_kFalse )
+, mParser_InMeta( morkBool_kFalse )
+
+, mParser_InPortRow( morkBool_kFalse )
+, mParser_InRow( morkBool_kFalse )
+, mParser_InTable( morkBool_kFalse )
+, mParser_InGroup( morkBool_kFalse )
+
+, mParser_AtomChange( morkChange_kNil )
+, mParser_CellChange( morkChange_kNil )
+, mParser_RowChange( morkChange_kNil )
+, mParser_TableChange( morkChange_kNil )
+
+, mParser_Change( morkChange_kNil )
+, mParser_IsBroken( morkBool_kFalse )
+, mParser_IsDone( morkBool_kFalse )
+, mParser_DoMore( morkBool_kTrue )
+
+, mParser_Mid()
+
+, mParser_ScopeCoil(ev, ioSlotHeap)
+, mParser_ValueCoil(ev, ioSlotHeap)
+, mParser_ColumnCoil(ev, ioSlotHeap)
+, mParser_StringCoil(ev, ioSlotHeap)
+
+, mParser_ScopeSpool(ev, &mParser_ScopeCoil)
+, mParser_ValueSpool(ev, &mParser_ValueCoil)
+, mParser_ColumnSpool(ev, &mParser_ColumnCoil)
+, mParser_StringSpool(ev, &mParser_StringCoil)
+
+, mParser_MidYarn(ev, morkUsage(morkUsage_kMember), ioSlotHeap)
+{
+ if ( inBytesPerParseSegment < morkParser_kMinGranularity )
+ inBytesPerParseSegment = morkParser_kMinGranularity;
+ else if ( inBytesPerParseSegment > morkParser_kMaxGranularity )
+ inBytesPerParseSegment = morkParser_kMaxGranularity;
+
+ mParser_MoreGranularity = inBytesPerParseSegment;
+
+ if ( ioSlotHeap && ioStream )
+ {
+ nsIMdbHeap_SlotStrongHeap(ioSlotHeap, ev, &mParser_Heap);
+ morkStream::SlotStrongStream(ioStream, ev, &mParser_Stream);
+
+ if ( ev->Good() )
+ {
+ mParser_Tag = morkParser_kTag;
+ mNode_Derived = morkDerived_kParser;
+ }
+ }
+ else
+ ev->NilPointerError();
+}
+
+/*public non-poly*/ void
+morkParser::CloseParser(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ if ( !this->IsShutNode() )
+ {
+ mParser_ScopeCoil.CloseCoil(ev);
+ mParser_ValueCoil.CloseCoil(ev);
+ mParser_ColumnCoil.CloseCoil(ev);
+ mParser_StringCoil.CloseCoil(ev);
+ nsIMdbHeap_SlotStrongHeap((nsIMdbHeap*) 0, ev, &mParser_Heap);
+ morkStream::SlotStrongStream((morkStream*) 0, ev, &mParser_Stream);
+ this->MarkShut();
+ }
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*protected non-poly*/ void
+morkParser::NonGoodParserError(morkEnv* ev) // when GoodParserTag() is false
+{
+ ev->NewError("non-morkNode");
+}
+
+/*protected non-poly*/ void
+morkParser::NonUsableParserError(morkEnv* ev) //
+{
+ if ( this->IsNode() )
+ {
+ if ( this->IsOpenNode() )
+ {
+ if ( this->GoodParserTag() )
+ {
+ // okay
+ }
+ else
+ this->NonGoodParserError(ev);
+ }
+ else
+ this->NonOpenNodeError(ev);
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+
+/*protected non-poly*/ void
+morkParser::StartParse(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ mParser_InCell = morkBool_kFalse;
+ mParser_InMeta = morkBool_kFalse;
+ mParser_InDict = morkBool_kFalse;
+ mParser_InPortRow = morkBool_kFalse;
+
+ mParser_RowMid.ClearMid();
+ mParser_TableMid.ClearMid();
+ mParser_CellMid.ClearMid();
+
+ mParser_GroupId = 0;
+ mParser_InPort = morkBool_kTrue;
+
+ mParser_GroupSpan.ClearSpan();
+ mParser_DictSpan.ClearSpan();
+ mParser_AliasSpan.ClearSpan();
+ mParser_MetaSpan.ClearSpan();
+ mParser_TableSpan.ClearSpan();
+ mParser_RowSpan.ClearSpan();
+ mParser_CellSpan.ClearSpan();
+ mParser_ColumnSpan.ClearSpan();
+ mParser_SlotSpan.ClearSpan();
+
+ mParser_PortSpan.ClearSpan();
+}
+
+/*protected non-poly*/ void
+morkParser::StopParse(morkEnv* ev)
+{
+ if ( mParser_InCell )
+ {
+ mParser_InCell = morkBool_kFalse;
+ mParser_CellSpan.SetEndWithEnd(mParser_PortSpan);
+ this->OnCellEnd(ev, mParser_CellSpan);
+ }
+ if ( mParser_InMeta )
+ {
+ mParser_InMeta = morkBool_kFalse;
+ mParser_MetaSpan.SetEndWithEnd(mParser_PortSpan);
+ this->OnMetaEnd(ev, mParser_MetaSpan);
+ }
+ if ( mParser_InDict )
+ {
+ mParser_InDict = morkBool_kFalse;
+ mParser_DictSpan.SetEndWithEnd(mParser_PortSpan);
+ this->OnDictEnd(ev, mParser_DictSpan);
+ }
+ if ( mParser_InPortRow )
+ {
+ mParser_InPortRow = morkBool_kFalse;
+ mParser_RowSpan.SetEndWithEnd(mParser_PortSpan);
+ this->OnPortRowEnd(ev, mParser_RowSpan);
+ }
+ if ( mParser_InRow )
+ {
+ mParser_InRow = morkBool_kFalse;
+ mParser_RowMid.ClearMid();
+ mParser_RowSpan.SetEndWithEnd(mParser_PortSpan);
+ this->OnRowEnd(ev, mParser_RowSpan);
+ }
+ if ( mParser_InTable )
+ {
+ mParser_InTable = morkBool_kFalse;
+ mParser_TableMid.ClearMid();
+ mParser_TableSpan.SetEndWithEnd(mParser_PortSpan);
+ this->OnTableEnd(ev, mParser_TableSpan);
+ }
+ if ( mParser_GroupId )
+ {
+ mParser_GroupId = 0;
+ mParser_GroupSpan.SetEndWithEnd(mParser_PortSpan);
+ this->OnGroupAbortEnd(ev, mParser_GroupSpan);
+ }
+ if ( mParser_InPort )
+ {
+ mParser_InPort = morkBool_kFalse;
+ this->OnPortEnd(ev, mParser_PortSpan);
+ }
+}
+
+int morkParser::eat_comment(morkEnv* ev) // last char was '/'
+{
+ morkStream* s = mParser_Stream;
+ // Note morkStream::Getc() returns EOF when an error occurs, so
+ // we don't need to check for both c != EOF and ev->Good() below.
+
+ int c = s->Getc(ev);
+ if ( c == '/' ) // C++ style comment?
+ {
+ while ( (c = s->Getc(ev)) != EOF && c != 0xA && c != 0xD )
+ /* empty */;
+
+ if ( c == 0xA || c == 0xD )
+ c = this->eat_line_break(ev, c);
+ }
+ else if ( c == '*' ) /* C style comment? */
+ {
+ int depth = 1; // count depth of comments until depth reaches zero
+
+ while ( depth > 0 && c != EOF ) // still looking for comment end(s)?
+ {
+ while ( (c = s->Getc(ev)) != EOF && c != '/' && c != '*' )
+ {
+ if ( c == 0xA || c == 0xD ) // need to count a line break?
+ {
+ c = this->eat_line_break(ev, c);
+ if ( c == '/' || c == '*' )
+ break; // end while loop
+ }
+ }
+
+ if ( c == '*' ) // maybe end of a comment, if next char is '/'?
+ {
+ if ( (c = s->Getc(ev)) == '/' ) // end of comment?
+ {
+ --depth; // depth of comments has decreased by one
+ if ( !depth ) // comments all done?
+ c = s->Getc(ev); // return the byte after end of comment
+ }
+ else if ( c != EOF ) // need to put the char back?
+ s->Ungetc(c); // especially need to put back '*', 0xA, or 0xD
+ }
+ else if ( c == '/' ) // maybe nested comemnt, if next char is '*'?
+ {
+ if ( (c = s->Getc(ev)) == '*' ) // nested comment?
+ ++depth; // depth of comments has increased by one
+ else if ( c != EOF ) // need to put the char back?
+ s->Ungetc(c); // especially need to put back '/', 0xA, or 0xD
+ }
+
+ if ( ev->Bad() )
+ c = EOF;
+ }
+ if ( c == EOF && depth > 0 )
+ ev->NewWarning("EOF before end of comment");
+ }
+ else
+ ev->NewWarning("expected / or *");
+
+ return c;
+}
+
+int morkParser::eat_line_break(morkEnv* ev, int inLast)
+{
+ morkStream* s = mParser_Stream;
+ int c = s->Getc(ev); // get next char after 0xA or 0xD
+ this->CountLineBreak();
+ if ( c == 0xA || c == 0xD ) // another line break character?
+ {
+ if ( c != inLast ) // not the same as the last one?
+ c = s->Getc(ev); // get next char after two-byte linebreak
+ }
+ return c;
+}
+
+int morkParser::eat_line_continue(morkEnv* ev) // last char was '\'
+{
+ morkStream* s = mParser_Stream;
+ int c = s->Getc(ev);
+ if ( c == 0xA || c == 0xD ) // linebreak follows \ as expected?
+ {
+ c = this->eat_line_break(ev, c);
+ }
+ else
+ ev->NewWarning("expected linebreak");
+
+ return c;
+}
+
+int morkParser::NextChar(morkEnv* ev) // next non-white content
+{
+ morkStream* s = mParser_Stream;
+ int c = s->Getc(ev);
+ while ( c > 0 && ev->Good() )
+ {
+ if ( c == '/' )
+ c = this->eat_comment(ev);
+ else if ( c == 0xA || c == 0xD )
+ c = this->eat_line_break(ev, c);
+ else if ( c == '\\' )
+ c = this->eat_line_continue(ev);
+ else if ( morkCh_IsWhite(c) )
+ c = s->Getc(ev);
+ else
+ break; // end while loop when return c is acceptable
+ }
+ if ( ev->Bad() )
+ {
+ mParser_State = morkParser_kBrokenState;
+ mParser_DoMore = morkBool_kFalse;
+ mParser_IsDone = morkBool_kTrue;
+ mParser_IsBroken = morkBool_kTrue;
+ c = EOF;
+ }
+ else if ( c == EOF )
+ {
+ mParser_DoMore = morkBool_kFalse;
+ mParser_IsDone = morkBool_kTrue;
+ }
+ return c;
+}
+
+void
+morkParser::OnCellState(morkEnv* ev)
+{
+ ev->StubMethodOnlyError();
+}
+
+void
+morkParser::OnMetaState(morkEnv* ev)
+{
+ ev->StubMethodOnlyError();
+}
+
+void
+morkParser::OnRowState(morkEnv* ev)
+{
+ ev->StubMethodOnlyError();
+}
+
+void
+morkParser::OnTableState(morkEnv* ev)
+{
+ ev->StubMethodOnlyError();
+}
+
+void
+morkParser::OnDictState(morkEnv* ev)
+{
+ ev->StubMethodOnlyError();
+}
+
+morkBuf* morkParser::ReadName(morkEnv* ev, int c)
+{
+ morkBuf* outBuf = 0;
+
+ if ( !morkCh_IsName(c) )
+ ev->NewError("not a name char");
+
+ morkCoil* coil = &mParser_ColumnCoil;
+ coil->ClearBufFill();
+
+ morkSpool* spool = &mParser_ColumnSpool;
+ spool->Seek(ev, /*pos*/ 0);
+
+ if ( ev->Good() )
+ {
+ spool->Putc(ev, c);
+
+ morkStream* s = mParser_Stream;
+ while ( (c = s->Getc(ev)) != EOF && morkCh_IsMore(c) && ev->Good() )
+ spool->Putc(ev, c);
+
+ if ( ev->Good() )
+ {
+ if ( c != EOF )
+ {
+ s->Ungetc(c);
+ spool->FlushSink(ev); // update coil->mBuf_Fill
+ }
+ else
+ this->UnexpectedEofError(ev);
+
+ if ( ev->Good() )
+ outBuf = coil;
+ }
+ }
+ return outBuf;
+}
+
+mork_bool
+morkParser::ReadMid(morkEnv* ev, morkMid* outMid)
+{
+ outMid->ClearMid();
+
+ morkStream* s = mParser_Stream;
+ int next;
+ outMid->mMid_Oid.mOid_Id = this->ReadHex(ev, &next);
+ int c = next;
+ if ( c == ':' )
+ {
+ if ( (c = s->Getc(ev)) != EOF && ev->Good() )
+ {
+ if ( c == '^' )
+ {
+ outMid->mMid_Oid.mOid_Scope = this->ReadHex(ev, &next);
+ if ( ev->Good() )
+ s->Ungetc(next);
+ }
+ else if ( morkCh_IsName(c) )
+ {
+ outMid->mMid_Buf = this->ReadName(ev, c);
+ }
+ else
+ ev->NewError("expected name or hex after ':' following ID");
+ }
+
+ if ( c == EOF && ev->Good() )
+ this->UnexpectedEofError(ev);
+ }
+ else
+ s->Ungetc(c);
+
+ return ev->Good();
+}
+
+void
+morkParser::ReadCell(morkEnv* ev)
+{
+ mParser_CellMid.ClearMid();
+ // this->StartSpanOnLastByte(ev, &mParser_CellSpan);
+ morkMid* cellMid = 0; // if mid syntax is used for column
+ morkBuf* cellBuf = 0; // if naked string is used for column
+
+ morkStream* s = mParser_Stream;
+ int c;
+ if ( (c = s->Getc(ev)) != EOF && ev->Good() )
+ {
+ // this->StartSpanOnLastByte(ev, &mParser_ColumnSpan);
+ if ( c == '^' )
+ {
+ cellMid = &mParser_CellMid;
+ this->ReadMid(ev, cellMid);
+ // if ( !mParser_CellMid.mMid_Oid.mOid_Scope )
+ // mParser_CellMid.mMid_Oid.mOid_Scope = (mork_scope) 'c';
+ }
+ else
+ {
+ if (mParser_InMeta && c == morkStore_kFormColumn)
+ {
+ ReadCellForm(ev, c);
+ return;
+ }
+ else
+ cellBuf = this->ReadName(ev, c);
+ }
+ if ( ev->Good() )
+ {
+ // this->EndSpanOnThisByte(ev, &mParser_ColumnSpan);
+
+ mParser_InCell = morkBool_kTrue;
+ this->OnNewCell(ev, *mParser_CellSpan.AsPlace(),
+ cellMid, cellBuf); // , mParser_CellChange
+
+ mParser_CellChange = morkChange_kNil;
+ if ( (c = this->NextChar(ev)) != EOF && ev->Good() )
+ {
+ // this->StartSpanOnLastByte(ev, &mParser_SlotSpan);
+ if ( c == '=' )
+ {
+ morkBuf* buf = this->ReadValue(ev);
+ if ( buf )
+ {
+ // this->EndSpanOnThisByte(ev, &mParser_SlotSpan);
+ this->OnValue(ev, mParser_SlotSpan, *buf);
+ }
+ }
+ else if ( c == '^' )
+ {
+ if ( this->ReadMid(ev, &mParser_Mid) )
+ {
+ // this->EndSpanOnThisByte(ev, &mParser_SlotSpan);
+ if ( (c = this->NextChar(ev)) != EOF && ev->Good() )
+ {
+ if ( c != ')' )
+ ev->NewError("expected ')' after cell ^ID value");
+ }
+ else if ( c == EOF )
+ this->UnexpectedEofError(ev);
+
+ if ( ev->Good() )
+ this->OnValueMid(ev, mParser_SlotSpan, mParser_Mid);
+ }
+ }
+ else if ( c == 'r' || c == 't' || c == '"' || c == '\'' )
+ {
+ ev->NewError("cell syntax not yet supported");
+ }
+ else
+ {
+ ev->NewError("unknown cell syntax");
+ }
+ }
+
+ // this->EndSpanOnThisByte(ev, &mParser_CellSpan);
+ mParser_InCell = morkBool_kFalse;
+ this->OnCellEnd(ev, mParser_CellSpan);
+ }
+ }
+ mParser_CellChange = morkChange_kNil;
+
+ if ( c == EOF && ev->Good() )
+ this->UnexpectedEofError(ev);
+}
+
+void morkParser::ReadRowPos(morkEnv* ev)
+{
+ int c; // next character
+ mork_pos rowPos = this->ReadHex(ev, &c);
+
+ if ( ev->Good() && c != EOF ) // should put back byte after hex?
+ mParser_Stream->Ungetc(c);
+
+ this->OnRowPos(ev, rowPos);
+}
+
+void morkParser::ReadRow(morkEnv* ev, int c)
+// zm:Row ::= zm:S? '[' zm:S? zm:Id zm:RowItem* zm:S? ']'
+// zm:RowItem ::= zm:MetaRow | zm:Cell
+// zm:MetaRow ::= zm:S? '[' zm:S? zm:Cell* zm:S? ']' /* meta attributes */
+// zm:Cell ::= zm:S? '(' zm:Column zm:S? zm:Slot? ')'
+{
+ if ( ev->Good() )
+ {
+ // this->StartSpanOnLastByte(ev, &mParser_RowSpan);
+ if ( mParser_Change )
+ mParser_RowChange = mParser_Change;
+
+ mork_bool cutAllRowCols = morkBool_kFalse;
+
+ if ( c == '[' )
+ {
+ if ( ( c = this->NextChar(ev) ) == '-' )
+ cutAllRowCols = morkBool_kTrue;
+ else if ( ev->Good() && c != EOF )
+ mParser_Stream->Ungetc(c);
+
+ if ( this->ReadMid(ev, &mParser_RowMid) )
+ {
+ mParser_InRow = morkBool_kTrue;
+ this->OnNewRow(ev, *mParser_RowSpan.AsPlace(),
+ mParser_RowMid, cutAllRowCols);
+
+ mParser_Change = mParser_RowChange = morkChange_kNil;
+
+ while ( (c = this->NextChar(ev)) != EOF && ev->Good() && c != ']' )
+ {
+ switch ( c )
+ {
+ case '(': // cell
+ this->ReadCell(ev);
+ break;
+
+ case '[': // meta
+ this->ReadMeta(ev, ']');
+ break;
+
+ // case '+': // plus
+ // mParser_CellChange = morkChange_kAdd;
+ // break;
+
+ case '-': // minus
+ // mParser_CellChange = morkChange_kCut;
+ this->OnMinusCell(ev);
+ break;
+
+ // case '!': // bang
+ // mParser_CellChange = morkChange_kSet;
+ // break;
+
+ default:
+ ev->NewWarning("unexpected byte in row");
+ break;
+ } // switch
+ } // while
+
+ if ( ev->Good() )
+ {
+ if ( (c = this->NextChar(ev)) == '!' )
+ this->ReadRowPos(ev);
+ else if ( c != EOF && ev->Good() )
+ mParser_Stream->Ungetc(c);
+ }
+
+ // this->EndSpanOnThisByte(ev, &mParser_RowSpan);
+ mParser_InRow = morkBool_kFalse;
+ this->OnRowEnd(ev, mParser_RowSpan);
+
+ } // if ReadMid
+ } // if '['
+
+ else // c != '['
+ {
+ morkStream* s = mParser_Stream;
+ s->Ungetc(c);
+ if ( this->ReadMid(ev, &mParser_RowMid) )
+ {
+ mParser_InRow = morkBool_kTrue;
+ this->OnNewRow(ev, *mParser_RowSpan.AsPlace(),
+ mParser_RowMid, cutAllRowCols);
+
+ mParser_Change = mParser_RowChange = morkChange_kNil;
+
+ if ( ev->Good() )
+ {
+ if ( (c = this->NextChar(ev)) == '!' )
+ this->ReadRowPos(ev);
+ else if ( c != EOF && ev->Good() )
+ s->Ungetc(c);
+ }
+
+ // this->EndSpanOnThisByte(ev, &mParser_RowSpan);
+ mParser_InRow = morkBool_kFalse;
+ this->OnRowEnd(ev, mParser_RowSpan);
+ }
+ }
+ }
+
+ if ( ev->Bad() )
+ mParser_State = morkParser_kBrokenState;
+ else if ( c == EOF )
+ mParser_State = morkParser_kDoneState;
+}
+
+void morkParser::ReadTable(morkEnv* ev)
+// zm:Table ::= zm:S? '{' zm:S? zm:Id zm:TableItem* zm:S? '}'
+// zm:TableItem ::= zm:MetaTable | zm:RowRef | zm:Row
+// zm:MetaTable ::= zm:S? '{' zm:S? zm:Cell* zm:S? '}' /* meta attributes */
+{
+ // this->StartSpanOnLastByte(ev, &mParser_TableSpan);
+
+ if ( mParser_Change )
+ mParser_TableChange = mParser_Change;
+
+ mork_bool cutAllTableRows = morkBool_kFalse;
+
+ int c = this->NextChar(ev);
+ if ( c == '-' )
+ cutAllTableRows = morkBool_kTrue;
+ else if ( ev->Good() && c != EOF )
+ mParser_Stream->Ungetc(c);
+
+ if ( ev->Good() && this->ReadMid(ev, &mParser_TableMid) )
+ {
+ mParser_InTable = morkBool_kTrue;
+ this->OnNewTable(ev, *mParser_TableSpan.AsPlace(),
+ mParser_TableMid, cutAllTableRows);
+
+ mParser_Change = mParser_TableChange = morkChange_kNil;
+
+ while ( (c = this->NextChar(ev)) != EOF && ev->Good() && c != '}' )
+ {
+ if ( morkCh_IsHex(c) )
+ {
+ this->ReadRow(ev, c);
+ }
+ else
+ {
+ switch ( c )
+ {
+ case '[': // row
+ this->ReadRow(ev, '[');
+ break;
+
+ case '{': // meta
+ this->ReadMeta(ev, '}');
+ break;
+
+ // case '+': // plus
+ // mParser_RowChange = morkChange_kAdd;
+ // break;
+
+ case '-': // minus
+ // mParser_RowChange = morkChange_kCut;
+ this->OnMinusRow(ev);
+ break;
+
+ // case '!': // bang
+ // mParser_RowChange = morkChange_kSet;
+ // break;
+
+ default:
+ ev->NewWarning("unexpected byte in table");
+ break;
+ }
+ }
+ }
+
+ // this->EndSpanOnThisByte(ev, &mParser_TableSpan);
+ mParser_InTable = morkBool_kFalse;
+ this->OnTableEnd(ev, mParser_TableSpan);
+
+ if ( ev->Bad() )
+ mParser_State = morkParser_kBrokenState;
+ else if ( c == EOF )
+ mParser_State = morkParser_kDoneState;
+ }
+}
+
+mork_id morkParser::ReadHex(morkEnv* ev, int* outNextChar)
+// zm:Hex ::= [0-9a-fA-F] /* a single hex digit */
+// zm:Hex+ ::= zm:Hex | zm:Hex zm:Hex+
+{
+ mork_id hex = 0;
+
+ morkStream* s = mParser_Stream;
+ int c = this->NextChar(ev);
+
+ if ( ev->Good() )
+ {
+ if ( c != EOF )
+ {
+ if ( morkCh_IsHex(c) )
+ {
+ do
+ {
+ if ( morkCh_IsDigit(c) ) // '0' through '9'?
+ c -= '0';
+ else if ( morkCh_IsUpper(c) ) // 'A' through 'F'?
+ c -= ('A' - 10) ; // c = (c - 'A') + 10;
+ else // 'a' through 'f'?
+ c -= ('a' - 10) ; // c = (c - 'a') + 10;
+
+ hex = (hex << 4) + c;
+ }
+ while ( (c = s->Getc(ev)) != EOF && ev->Good() && morkCh_IsHex(c) );
+ }
+ else
+ this->ExpectedHexDigitError(ev, c);
+ }
+ }
+ if ( c == EOF )
+ this->EofInsteadOfHexError(ev);
+
+ *outNextChar = c;
+ return hex;
+}
+
+/*static*/ void
+morkParser::EofInsteadOfHexError(morkEnv* ev)
+{
+ ev->NewWarning("eof instead of hex");
+}
+
+/*static*/ void
+morkParser::ExpectedHexDigitError(morkEnv* ev, int c)
+{
+ MORK_USED_1(c);
+ ev->NewWarning("expected hex digit");
+}
+
+/*static*/ void
+morkParser::ExpectedEqualError(morkEnv* ev)
+{
+ ev->NewWarning("expected '='");
+}
+
+/*static*/ void
+morkParser::UnexpectedEofError(morkEnv* ev)
+{
+ ev->NewWarning("unexpected eof");
+}
+
+
+morkBuf* morkParser::ReadValue(morkEnv* ev)
+{
+ morkBuf* outBuf = 0;
+
+ morkCoil* coil = &mParser_ValueCoil;
+ coil->ClearBufFill();
+
+ morkSpool* spool = &mParser_ValueSpool;
+ spool->Seek(ev, /*pos*/ 0);
+
+ if ( ev->Good() )
+ {
+ morkStream* s = mParser_Stream;
+ int c;
+ while ( (c = s->Getc(ev)) != EOF && c != ')' && ev->Good() )
+ {
+ if ( c == '\\' ) // next char is escaped by '\'?
+ {
+ if ( (c = s->Getc(ev)) == 0xA || c == 0xD ) // linebreak after \?
+ {
+ c = this->eat_line_break(ev, c);
+ if ( c == ')' || c == '\\' || c == '$' )
+ {
+ s->Ungetc(c); // just let while loop test read this again
+ continue; // goto next iteration of while loop
+ }
+ }
+ if ( c == EOF || ev->Bad() )
+ break; // end while loop
+ }
+ else if ( c == '$' ) // "$" escapes next two hex digits?
+ {
+ if ( (c = s->Getc(ev)) != EOF && ev->Good() )
+ {
+ mork_ch first = (mork_ch) c; // first hex digit
+ if ( (c = s->Getc(ev)) != EOF && ev->Good() )
+ {
+ mork_ch second = (mork_ch) c; // second hex digit
+ c = ev->HexToByte(first, second);
+ }
+ else
+ break; // end while loop
+ }
+ else
+ break; // end while loop
+ }
+ spool->Putc(ev, c);
+ }
+
+ if ( ev->Good() )
+ {
+ if ( c != EOF )
+ spool->FlushSink(ev); // update coil->mBuf_Fill
+ else
+ this->UnexpectedEofError(ev);
+
+ if ( ev->Good() )
+ outBuf = coil;
+ }
+ }
+ return outBuf;
+}
+
+void morkParser::ReadDictForm(morkEnv *ev)
+{
+ int nextChar;
+ nextChar = this->NextChar(ev);
+ if (nextChar == '(')
+ {
+ nextChar = this->NextChar(ev);
+ if (nextChar == morkStore_kFormColumn)
+ {
+ int dictForm;
+
+ nextChar = this->NextChar(ev);
+ if (nextChar == '=')
+ {
+ dictForm = this->NextChar(ev);
+ nextChar = this->NextChar(ev);
+ }
+ else if (nextChar == '^')
+ {
+ dictForm = this->ReadHex(ev, &nextChar);
+ }
+ else
+ {
+ ev->NewWarning("unexpected byte in dict form");
+ return;
+ }
+ mParser_ValueCoil.mText_Form = dictForm;
+ if (nextChar == ')')
+ {
+ nextChar = this->NextChar(ev);
+ if (nextChar == '>')
+ return;
+ }
+ }
+ }
+ ev->NewWarning("unexpected byte in dict form");
+}
+
+void morkParser::ReadCellForm(morkEnv *ev, int c)
+{
+ MORK_ASSERT (c == morkStore_kFormColumn);
+ int nextChar;
+ nextChar = this->NextChar(ev);
+ int cellForm;
+
+ if (nextChar == '=')
+ {
+ cellForm = this->NextChar(ev);
+ nextChar = this->NextChar(ev);
+ }
+ else if (nextChar == '^')
+ {
+ cellForm = this->ReadHex(ev, &nextChar);
+ }
+ else
+ {
+ ev->NewWarning("unexpected byte in cell form");
+ return;
+ }
+ // ### not sure about this. Which form should we set?
+ // mBuilder_CellForm = mBuilder_RowForm = cellForm;
+ if (nextChar == ')')
+ {
+ OnCellForm(ev, cellForm);
+ return;
+ }
+ ev->NewWarning("unexpected byte in cell form");
+}
+
+void morkParser::ReadAlias(morkEnv* ev)
+// zm:Alias ::= zm:S? '(' ('#')? zm:Hex+ zm:S? zm:Value ')'
+// zm:Value ::= '=' ([^)$\] | '\' zm:NonCRLF | zm:Continue | zm:Dollar)*
+{
+ // this->StartSpanOnLastByte(ev, &mParser_AliasSpan);
+
+ int nextChar;
+ mork_id hex = this->ReadHex(ev, &nextChar);
+ int c = nextChar;
+
+ mParser_Mid.ClearMid();
+ mParser_Mid.mMid_Oid.mOid_Id = hex;
+
+ if ( morkCh_IsWhite(c) && ev->Good() )
+ c = this->NextChar(ev);
+
+ if ( ev->Good() )
+ {
+ if ( c == '<')
+ {
+ ReadDictForm(ev);
+ if (ev->Good())
+ c = this->NextChar(ev);
+ }
+ if ( c == '=' )
+ {
+ mParser_Mid.mMid_Buf = this->ReadValue(ev);
+ if ( mParser_Mid.mMid_Buf )
+ {
+ // this->EndSpanOnThisByte(ev, &mParser_AliasSpan);
+ this->OnAlias(ev, mParser_AliasSpan, mParser_Mid);
+ // need to reset this somewhere.
+ mParser_ValueCoil.mText_Form = 0;
+
+ }
+ }
+ else
+ this->ExpectedEqualError(ev);
+ }
+}
+
+void morkParser::ReadMeta(morkEnv* ev, int inEndMeta)
+// zm:MetaDict ::= zm:S? '<' zm:S? zm:Cell* zm:S? '>' /* meta attributes */
+// zm:MetaTable ::= zm:S? '{' zm:S? zm:Cell* zm:S? '}' /* meta attributes */
+// zm:MetaRow ::= zm:S? '[' zm:S? zm:Cell* zm:S? ']' /* meta attributes */
+{
+ // this->StartSpanOnLastByte(ev, &mParser_MetaSpan);
+ mParser_InMeta = morkBool_kTrue;
+ this->OnNewMeta(ev, *mParser_MetaSpan.AsPlace());
+
+ mork_bool more = morkBool_kTrue; // until end meta
+ int c;
+ while ( more && (c = this->NextChar(ev)) != EOF && ev->Good() )
+ {
+ switch ( c )
+ {
+ case '(': // cell
+ this->ReadCell(ev);
+ break;
+
+ case '>': // maybe end meta?
+ if ( inEndMeta == '>' )
+ more = morkBool_kFalse; // stop reading meta
+ else
+ this->UnexpectedByteInMetaWarning(ev);
+ break;
+
+ case '}': // maybe end meta?
+ if ( inEndMeta == '}' )
+ more = morkBool_kFalse; // stop reading meta
+ else
+ this->UnexpectedByteInMetaWarning(ev);
+ break;
+
+ case ']': // maybe end meta?
+ if ( inEndMeta == ']' )
+ more = morkBool_kFalse; // stop reading meta
+ else
+ this->UnexpectedByteInMetaWarning(ev);
+ break;
+
+ case '[': // maybe table meta row?
+ if ( mParser_InTable )
+ this->ReadRow(ev, '[');
+ else
+ this->UnexpectedByteInMetaWarning(ev);
+ break;
+
+ default:
+ if ( mParser_InTable && morkCh_IsHex(c) )
+ this->ReadRow(ev, c);
+ else
+ this->UnexpectedByteInMetaWarning(ev);
+ break;
+ }
+ }
+
+ // this->EndSpanOnThisByte(ev, &mParser_MetaSpan);
+ mParser_InMeta = morkBool_kFalse;
+ this->OnMetaEnd(ev, mParser_MetaSpan);
+}
+
+/*static*/ void
+morkParser::UnexpectedByteInMetaWarning(morkEnv* ev)
+{
+ ev->NewWarning("unexpected byte in meta");
+}
+
+/*static*/ void
+morkParser::NonParserTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkParser");
+}
+
+mork_bool morkParser::MatchPattern(morkEnv* ev, const char* inPattern)
+{
+ // if an error occurs, we want original inPattern in the debugger:
+ const char* pattern = inPattern; // mutable copy of pointer
+ morkStream* s = mParser_Stream;
+ int c;
+ while ( *pattern && ev->Good() )
+ {
+ char byte = *pattern++;
+ if ( (c = s->Getc(ev)) != byte )
+ {
+ ev->NewError("byte not in expected pattern");
+ }
+ }
+ return ev->Good();
+}
+
+mork_bool morkParser::FindGroupEnd(morkEnv* ev)
+{
+ mork_bool foundEnd = morkBool_kFalse;
+
+ // char gidBuf[ 64 ]; // to hold hex pattern we want
+ // (void) ev->TokenAsHex(gidBuf, mParser_GroupId);
+
+ morkStream* s = mParser_Stream;
+ int c;
+
+ while ( (c = s->Getc(ev)) != EOF && ev->Good() && !foundEnd )
+ {
+ if ( c == '@' ) // maybe start of group ending?
+ {
+ // this->EndSpanOnThisByte(ev, &mParser_GroupSpan);
+ if ( (c = s->Getc(ev)) == '$' ) // '$' follows '@' ?
+ {
+ if ( (c = s->Getc(ev)) == '$' ) // '$' follows "@$" ?
+ {
+ if ( (c = s->Getc(ev)) == '}' )
+ {
+ foundEnd = this->ReadEndGroupId(ev);
+ // this->EndSpanOnThisByte(ev, &mParser_GroupSpan);
+
+ }
+ else
+ ev->NewError("expected '}' after @$$");
+ }
+ }
+ if ( !foundEnd && c == '@' )
+ s->Ungetc(c);
+ }
+ }
+
+ return foundEnd && ev->Good();
+}
+
+void morkParser::ReadGroup(morkEnv* mev)
+{
+ nsIMdbEnv *ev = mev->AsMdbEnv();
+ int next = 0;
+ mParser_GroupId = this->ReadHex(mev, &next);
+ if ( next == '{' )
+ {
+ morkStream* s = mParser_Stream;
+ int c;
+ if ( (c = s->Getc(mev)) == '@' )
+ {
+ // we really need the following span inside morkBuilder::OnNewGroup():
+ this->StartSpanOnThisByte(mev, &mParser_GroupSpan);
+ mork_pos startPos = mParser_GroupSpan.mSpan_Start.mPlace_Pos;
+
+ // if ( !store->mStore_FirstCommitGroupPos )
+ // store->mStore_FirstCommitGroupPos = startPos;
+ // else if ( !store->mStore_SecondCommitGroupPos )
+ // store->mStore_SecondCommitGroupPos = startPos;
+
+ if ( this->FindGroupEnd(mev) )
+ {
+ mork_pos outPos;
+ s->Seek(ev, startPos, &outPos);
+ if ( mev->Good() )
+ {
+ this->OnNewGroup(mev, mParser_GroupSpan.mSpan_Start,
+ mParser_GroupId);
+
+ this->ReadContent(mev, /*inInsideGroup*/ morkBool_kTrue);
+
+ this->OnGroupCommitEnd(mev, mParser_GroupSpan);
+ }
+ }
+ }
+ else
+ mev->NewError("expected '@' after @$${id{");
+ }
+ else
+ mev->NewError("expected '{' after @$$id");
+
+}
+
+mork_bool morkParser::ReadAt(morkEnv* ev, mork_bool inInsideGroup)
+/* groups must be ignored until properly terminated */
+// zm:Group ::= zm:GroupStart zm:Content zm:GroupEnd /* transaction */
+// zm:GroupStart ::= zm:S? '@$${' zm:Hex+ '{@' /* xaction id has own space */
+// zm:GroupEnd ::= zm:GroupCommit | zm:GroupAbort
+// zm:GroupCommit ::= zm:S? '@$$}' zm:Hex+ '}@' /* id matches start id */
+// zm:GroupAbort ::= zm:S? '@$$}~~}@' /* id matches start id */
+/* We must allow started transactions to be aborted in summary files. */
+/* Note '$$' will never occur unescaped in values we will see in Mork. */
+{
+ if ( this->MatchPattern(ev, "$$") )
+ {
+ morkStream* s = mParser_Stream;
+ int c;
+ if ( ((c = s->Getc(ev)) == '{' || c == '}') && ev->Good() )
+ {
+ if ( c == '{' ) // start of new group?
+ {
+ if ( !inInsideGroup )
+ this->ReadGroup(ev);
+ else
+ ev->NewError("nested @$${ inside another group");
+ }
+ else // c == '}' // end of old group?
+ {
+ if ( inInsideGroup )
+ {
+ this->ReadEndGroupId(ev);
+ mParser_GroupId = 0;
+ }
+ else
+ ev->NewError("unmatched @$$} outside any group");
+ }
+ }
+ else
+ ev->NewError("expected '{' or '}' after @$$");
+ }
+ return ev->Good();
+}
+
+mork_bool morkParser::ReadEndGroupId(morkEnv* ev)
+{
+ mork_bool outSawGroupId = morkBool_kFalse;
+ morkStream* s = mParser_Stream;
+ int c;
+ if ( (c = s->Getc(ev)) != EOF && ev->Good() )
+ {
+ if ( c == '~' ) // transaction is aborted?
+ {
+ this->MatchPattern(ev, "~}@"); // finish rest of pattern
+ }
+ else // push back byte and read expected trailing hex id
+ {
+ s->Ungetc(c);
+ int next = 0;
+ mork_gid endGroupId = this->ReadHex(ev, &next);
+ if ( ev->Good() )
+ {
+ if ( endGroupId == mParser_GroupId ) // matches start?
+ {
+ if ( next == '}' ) // '}' after @$$}id ?
+ {
+ if ( (c = s->Getc(ev)) == '@' ) // '@' after @$$}id} ?
+ {
+ // looks good, so return with no error
+ outSawGroupId = morkBool_kTrue;
+ mParser_InGroup = false;
+ }
+ else
+ ev->NewError("expected '@' after @$$}id}");
+ }
+ else
+ ev->NewError("expected '}' after @$$}id");
+ }
+ else
+ ev->NewError("end group id mismatch");
+ }
+ }
+ }
+ return ( outSawGroupId && ev->Good() );
+}
+
+
+void morkParser::ReadDict(morkEnv* ev)
+// zm:Dict ::= zm:S? '<' zm:DictItem* zm:S? '>'
+// zm:DictItem ::= zm:MetaDict | zm:Alias
+// zm:MetaDict ::= zm:S? '<' zm:S? zm:Cell* zm:S? '>' /* meta attributes */
+// zm:Alias ::= zm:S? '(' ('#')? zm:Hex+ zm:S? zm:Value ')'
+{
+ mParser_Change = morkChange_kNil;
+ mParser_AtomChange = morkChange_kNil;
+
+ // this->StartSpanOnLastByte(ev, &mParser_DictSpan);
+ mParser_InDict = morkBool_kTrue;
+ this->OnNewDict(ev, *mParser_DictSpan.AsPlace());
+
+ int c;
+ while ( (c = this->NextChar(ev)) != EOF && ev->Good() && c != '>' )
+ {
+ switch ( c )
+ {
+ case '(': // alias
+ this->ReadAlias(ev);
+ break;
+
+ case '<': // meta
+ this->ReadMeta(ev, '>');
+ break;
+
+ default:
+ ev->NewWarning("unexpected byte in dict");
+ break;
+ }
+ }
+
+ // this->EndSpanOnThisByte(ev, &mParser_DictSpan);
+ mParser_InDict = morkBool_kFalse;
+ this->OnDictEnd(ev, mParser_DictSpan);
+
+ if ( ev->Bad() )
+ mParser_State = morkParser_kBrokenState;
+ else if ( c == EOF )
+ mParser_State = morkParser_kDoneState;
+}
+
+void morkParser::EndSpanOnThisByte(morkEnv* mev, morkSpan* ioSpan)
+{
+ mork_pos here;
+ nsIMdbEnv *ev = mev->AsMdbEnv();
+ nsresult rv = mParser_Stream->Tell(ev, &here);
+ if (NS_SUCCEEDED(rv) && mev->Good() )
+ {
+ this->SetHerePos(here);
+ ioSpan->SetEndWithEnd(mParser_PortSpan);
+ }
+}
+
+void morkParser::EndSpanOnLastByte(morkEnv* mev, morkSpan* ioSpan)
+{
+ mork_pos here;
+ nsIMdbEnv *ev = mev->AsMdbEnv();
+ nsresult rv= mParser_Stream->Tell(ev, &here);
+ if ( NS_SUCCEEDED(rv) && mev->Good() )
+ {
+ if ( here > 0 )
+ --here;
+ else
+ here = 0;
+
+ this->SetHerePos(here);
+ ioSpan->SetEndWithEnd(mParser_PortSpan);
+ }
+}
+
+void morkParser::StartSpanOnLastByte(morkEnv* mev, morkSpan* ioSpan)
+{
+ mork_pos here;
+ nsIMdbEnv *ev = mev->AsMdbEnv();
+ nsresult rv = mParser_Stream->Tell(ev, &here);
+ if ( NS_SUCCEEDED(rv) && mev->Good() )
+ {
+ if ( here > 0 )
+ --here;
+ else
+ here = 0;
+
+ this->SetHerePos(here);
+ ioSpan->SetStartWithEnd(mParser_PortSpan);
+ ioSpan->SetEndWithEnd(mParser_PortSpan);
+ }
+}
+
+void morkParser::StartSpanOnThisByte(morkEnv* mev, morkSpan* ioSpan)
+{
+ mork_pos here;
+ nsIMdbEnv *ev = mev->AsMdbEnv();
+ nsresult rv = mParser_Stream->Tell(ev, &here);
+ if ( NS_SUCCEEDED(rv) && mev->Good() )
+ {
+ this->SetHerePos(here);
+ ioSpan->SetStartWithEnd(mParser_PortSpan);
+ ioSpan->SetEndWithEnd(mParser_PortSpan);
+ }
+}
+
+mork_bool
+morkParser::ReadContent(morkEnv* ev, mork_bool inInsideGroup)
+{
+ int c;
+ mork_bool keep_going = true;
+ while ( keep_going && (c = this->NextChar(ev)) != EOF && ev->Good())
+ {
+ switch ( c )
+ {
+ case '[': // row
+ this->ReadRow(ev, '[');
+ keep_going = false;
+ break;
+
+ case '{': // table
+ this->ReadTable(ev);
+ keep_going = false;
+ break;
+
+ case '<': // dict
+ this->ReadDict(ev);
+ keep_going = false;
+ break;
+
+ case '@': // group
+ return this->ReadAt(ev, inInsideGroup);
+ // break;
+
+ // case '+': // plus
+ // mParser_Change = morkChange_kAdd;
+ // break;
+
+ // case '-': // minus
+ // mParser_Change = morkChange_kCut;
+ // break;
+
+ // case '!': // bang
+ // mParser_Change = morkChange_kSet;
+ // break;
+
+ default:
+ ev->NewWarning("unexpected byte in ReadContent()");
+ break;
+ }
+ }
+ if ( ev->Bad() )
+ mParser_State = morkParser_kBrokenState;
+ else if ( c == EOF )
+ mParser_State = morkParser_kDoneState;
+
+ return ( ev->Good() && c != EOF );
+}
+
+void
+morkParser::OnPortState(morkEnv* ev)
+{
+ mork_bool firstTime = !mParser_InPort;
+ mParser_InPort = morkBool_kTrue;
+ if (firstTime)
+ this->OnNewPort(ev, *mParser_PortSpan.AsPlace());
+
+ mork_bool done = !this->ReadContent(ev, mParser_InGroup/*inInsideGroup*/);
+
+ if (done)
+ {
+ mParser_InPort = morkBool_kFalse;
+ this->OnPortEnd(ev, mParser_PortSpan);
+ }
+
+ if ( ev->Bad() )
+ mParser_State = morkParser_kBrokenState;
+}
+
+void
+morkParser::OnStartState(morkEnv* mev)
+{
+ morkStream* s = mParser_Stream;
+ nsIMdbEnv *ev = mev->AsMdbEnv();
+ if ( s && s->IsNode() && s->IsOpenNode() )
+ {
+ mork_pos outPos;
+ nsresult rv = s->Seek(ev, 0, &outPos);
+ if (NS_SUCCEEDED(rv) && mev->Good() )
+ {
+ this->StartParse(mev);
+ mParser_State = morkParser_kPortState;
+ }
+ }
+ else
+ mev->NilPointerError();
+
+ if ( mev->Bad() )
+ mParser_State = morkParser_kBrokenState;
+}
+
+/*protected non-poly*/ void
+morkParser::ParseChunk(morkEnv* ev)
+{
+ mParser_Change = morkChange_kNil;
+ mParser_DoMore = morkBool_kTrue;
+
+ switch ( mParser_State )
+ {
+ case morkParser_kCellState: // 0
+ this->OnCellState(ev); break;
+
+ case morkParser_kMetaState: // 1
+ this->OnMetaState(ev); break;
+
+ case morkParser_kRowState: // 2
+ this->OnRowState(ev); break;
+
+ case morkParser_kTableState: // 3
+ this->OnTableState(ev); break;
+
+ case morkParser_kDictState: // 4
+ this->OnDictState(ev); break;
+
+ case morkParser_kPortState: // 5
+ this->OnPortState(ev); break;
+
+ case morkParser_kStartState: // 6
+ this->OnStartState(ev); break;
+
+ case morkParser_kDoneState: // 7
+ mParser_DoMore = morkBool_kFalse;
+ mParser_IsDone = morkBool_kTrue;
+ this->StopParse(ev);
+ break;
+ case morkParser_kBrokenState: // 8
+ mParser_DoMore = morkBool_kFalse;
+ mParser_IsBroken = morkBool_kTrue;
+ this->StopParse(ev);
+ break;
+ default: // ?
+ MORK_ASSERT(morkBool_kFalse);
+ mParser_State = morkParser_kBrokenState;
+ break;
+ }
+}
+
+/*public non-poly*/ mdb_count
+morkParser::ParseMore( // return count of bytes consumed now
+ morkEnv* ev, // context
+ mork_pos* outPos, // current byte pos in the stream afterwards
+ mork_bool* outDone, // is parsing finished?
+ mork_bool* outBroken // is parsing irreparably dead and broken?
+ )
+{
+ mdb_count outCount = 0;
+ if ( this->IsNode() && this->GoodParserTag() && this->IsOpenNode() )
+ {
+ mork_pos startPos = this->HerePos();
+
+ if ( !mParser_IsDone && !mParser_IsBroken )
+ this->ParseChunk(ev);
+
+ // HerePos is only updated for groups. I'd like it to be more accurate.
+
+ mork_pos here;
+ mParser_Stream->Tell(ev, &here);
+
+ if ( outDone )
+ *outDone = mParser_IsDone;
+ if ( outBroken )
+ *outBroken = mParser_IsBroken;
+ if ( outPos )
+ *outPos = here;
+
+ if ( here > startPos )
+ outCount = (mdb_count) (here - startPos);
+ }
+ else
+ {
+ this->NonUsableParserError(ev);
+ if ( outDone )
+ *outDone = morkBool_kTrue;
+ if ( outBroken )
+ *outBroken = morkBool_kTrue;
+ if ( outPos )
+ *outPos = 0;
+ }
+ return outCount;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
diff --git a/components/mork/src/morkParser.h b/components/mork/src/morkParser.h
new file mode 100644
index 000000000..ce1fd8943
--- /dev/null
+++ b/components/mork/src/morkParser.h
@@ -0,0 +1,533 @@
+/* -*- 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/. */
+
+#ifndef _MORKPARSER_
+#define _MORKPARSER_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKBLOB_
+#include "morkBlob.h"
+#endif
+
+#ifndef _MORKSINK_
+#include "morkSink.h"
+#endif
+
+#ifndef _MORKYARN_
+#include "morkYarn.h"
+#endif
+
+#ifndef _MORKCELL_
+#include "morkCell.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*=============================================================================
+ * morkPlace: stream byte position and stream line count
+ */
+
+class morkPlace {
+public:
+ mork_pos mPlace_Pos; // byte offset in an input stream
+ mork_line mPlace_Line; // line count in an input stream
+
+ void ClearPlace()
+ {
+ mPlace_Pos = 0; mPlace_Line = 0;
+ }
+
+ void SetPlace(mork_pos inPos, mork_line inLine)
+ {
+ mPlace_Pos = inPos; mPlace_Line = inLine;
+ }
+
+ morkPlace() { mPlace_Pos = 0; mPlace_Line = 0; }
+
+ morkPlace(mork_pos inPos, mork_line inLine)
+ { mPlace_Pos = inPos; mPlace_Line = inLine; }
+
+ morkPlace(const morkPlace& inPlace)
+ : mPlace_Pos(inPlace.mPlace_Pos), mPlace_Line(inPlace.mPlace_Line) { }
+};
+
+/*=============================================================================
+ * morkGlitch: stream place and error comment describing a parsing error
+ */
+
+class morkGlitch {
+public:
+ morkPlace mGlitch_Place; // place in stream where problem happened
+ const char* mGlitch_Comment; // null-terminated ASCII C string
+
+ morkGlitch() { mGlitch_Comment = 0; }
+
+ morkGlitch(const morkPlace& inPlace, const char* inComment)
+ : mGlitch_Place(inPlace), mGlitch_Comment(inComment) { }
+};
+
+/*=============================================================================
+ * morkMid: all possible ways needed to express an alias ID in Mork syntax
+ */
+
+/*| morkMid: an abstraction of all the variations we might need to support
+**| in order to present an ID through the parser interface most cheaply and
+**| with minimum transformation away from the original text format.
+**|
+**|| An ID can have one of four forms:
+**| 1) idHex (mMid_Oid.mOid_Id <- idHex)
+**| 2) idHex:^scopeHex (mMid_Oid.mOid_Id <- idHex, mOid_Scope <- scopeHex)
+**| 3) idHex:scopeName (mMid_Oid.mOid_Id <- idHex, mMid_Buf <- scopeName)
+**| 4) columnName (mMid_Buf <- columnName, for columns in cells only)
+**|
+**|| Typically, mMid_Oid.mOid_Id will hold a nonzero integer value for
+**| an ID, but we might have an optional scope specified by either an integer
+**| in hex format, or a string name. (Note that while the first ID can be
+**| scoped variably, any integer ID for a scope is assumed always located in
+**| the same scope, so the second ID need not be disambiguated.)
+**|
+**|| The only time mMid_Oid.mOid_Id is ever zero is when mMid_Buf alone
+**| is nonzero, to indicate an explicit string instead of an alias appeared.
+**| This case happens to make the representation of columns in cells somewhat
+**| easier to represent, since columns can just appear as a string name; and
+**| this unifies those interfaces with row and table APIs expecting IDs.
+**|
+**|| So when the parser passes an instance of morkMid to a subclass, the
+**| mMid_Oid.mOid_Id slot should usually be nonzero. And the other two
+**| slots, mMid_Oid.mOid_Scope and mMid_Buf, might both be zero, or at
+**| most one of them will be nonzero to indicate an explicit scope; the
+**| parser is responsible for ensuring at most one of these is nonzero.
+|*/
+class morkMid {
+public:
+ mdbOid mMid_Oid; // mOid_Scope is zero when not specified
+ const morkBuf* mMid_Buf; // points to some specific buf subclass
+
+ morkMid()
+ { mMid_Oid.mOid_Scope = 0; mMid_Oid.mOid_Id = morkId_kMinusOne;
+ mMid_Buf = 0; }
+
+ void InitMidWithCoil(morkCoil* ioCoil)
+ { mMid_Oid.mOid_Scope = 0; mMid_Oid.mOid_Id = morkId_kMinusOne;
+ mMid_Buf = ioCoil; }
+
+ void ClearMid()
+ { mMid_Oid.mOid_Scope = 0; mMid_Oid.mOid_Id = morkId_kMinusOne;
+ mMid_Buf = 0; }
+
+ morkMid(const morkMid& other)
+ : mMid_Oid(other.mMid_Oid), mMid_Buf(other.mMid_Buf) { }
+
+ mork_bool HasNoId() const // ID is unspecified?
+ { return ( mMid_Oid.mOid_Id == morkId_kMinusOne ); }
+
+ mork_bool HasSomeId() const // ID is specified?
+ { return ( mMid_Oid.mOid_Id != morkId_kMinusOne ); }
+};
+
+/*=============================================================================
+ * morkSpan: start and end stream byte position and stream line count
+ */
+
+class morkSpan {
+public:
+ morkPlace mSpan_Start;
+ morkPlace mSpan_End;
+
+public: // methods
+
+public: // inlines
+ morkSpan() { } // use inline empty constructor for each place
+
+ morkPlace* AsPlace() { return &mSpan_Start; }
+ const morkPlace* AsConstPlace() const { return &mSpan_Start; }
+
+ void SetSpan(mork_pos inFromPos, mork_line inFromLine,
+ mork_pos inToPos, mork_line inToLine)
+ {
+ mSpan_Start.SetPlace(inFromPos, inFromLine);
+ mSpan_End.SetPlace(inToPos,inToLine);
+ }
+
+ // setting end, useful to terminate a span using current port span end:
+ void SetEndWithEnd(const morkSpan& inSpan) // end <- span.end
+ { mSpan_End = inSpan.mSpan_End; }
+
+ // setting start, useful to initiate a span using current port span end:
+ void SetStartWithEnd(const morkSpan& inSpan) // start <- span.end
+ { mSpan_Start = inSpan.mSpan_End; }
+
+ void ClearSpan()
+ {
+ mSpan_Start.mPlace_Pos = 0; mSpan_Start.mPlace_Line = 0;
+ mSpan_End.mPlace_Pos = 0; mSpan_End.mPlace_Line = 0;
+ }
+
+ morkSpan(mork_pos inFromPos, mork_line inFromLine,
+ mork_pos inToPos, mork_line inToLine)
+ : mSpan_Start(inFromPos, inFromLine), mSpan_End(inToPos, inToLine)
+ { /* empty implementation */ }
+};
+
+/*=============================================================================
+ * morkParser: for parsing Mork text syntax
+ */
+
+#define morkParser_kMinGranularity 512 /* parse at least half 0.5K at once */
+#define morkParser_kMaxGranularity (64 * 1024) /* parse at most 64 K at once */
+
+#define morkDerived_kParser /*i*/ 0x5073 /* ascii 'Ps' */
+#define morkParser_kTag /*i*/ 0x70417253 /* ascii 'pArS' */
+
+// These are states for the simple parsing virtual machine. Needless to say,
+// these must be distinct, and preferably in a contiguous integer range.
+// Don't change these constants without looking at switch statements in code.
+#define morkParser_kCellState 0 /* cell is tightest scope */
+#define morkParser_kMetaState 1 /* meta is tightest scope */
+#define morkParser_kRowState 2 /* row is tightest scope */
+#define morkParser_kTableState 3 /* table is tightest scope */
+#define morkParser_kDictState 4 /* dict is tightest scope */
+#define morkParser_kPortState 5 /* port is tightest scope */
+
+#define morkParser_kStartState 6 /* parsing has not yet begun */
+#define morkParser_kDoneState 7 /* parsing is complete */
+#define morkParser_kBrokenState 8 /* parsing is to broken to work */
+
+class morkParser /*d*/ : public morkNode {
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+protected: // protected morkParser members
+
+ nsIMdbHeap* mParser_Heap; // refcounted heap used for allocation
+ morkStream* mParser_Stream; // refcounted input stream
+
+ mork_u4 mParser_Tag; // must equal morkParser_kTag
+ mork_count mParser_MoreGranularity; // constructor inBytesPerParseSegment
+
+ mork_u4 mParser_State; // state where parser should resume
+
+ // after finding ends of group transactions, we can re-seek the start:
+ mork_pos mParser_GroupContentStartPos; // start of this group
+
+ morkMid mParser_TableMid; // table mid if inside a table
+ morkMid mParser_RowMid; // row mid if inside a row
+ morkMid mParser_CellMid; // cell mid if inside a row
+ mork_gid mParser_GroupId; // group ID if inside a group
+
+ mork_bool mParser_InPort; // called OnNewPort but not OnPortEnd?
+ mork_bool mParser_InDict; // called OnNewDict but not OnDictEnd?
+ mork_bool mParser_InCell; // called OnNewCell but not OnCellEnd?
+ mork_bool mParser_InMeta; // called OnNewMeta but not OnMetaEnd?
+
+ mork_bool mParser_InPortRow; // called OnNewPortRow but not OnPortRowEnd?
+ mork_bool mParser_InRow; // called OnNewRow but not OnNewRowEnd?
+ mork_bool mParser_InTable; // called OnNewMeta but not OnMetaEnd?
+ mork_bool mParser_InGroup; // called OnNewGroup but not OnGroupEnd?
+
+ mork_change mParser_AtomChange; // driven by mParser_Change
+ mork_change mParser_CellChange; // driven by mParser_Change
+ mork_change mParser_RowChange; // driven by mParser_Change
+ mork_change mParser_TableChange; // driven by mParser_Change
+
+ mork_change mParser_Change; // driven by modifier in text
+ mork_bool mParser_IsBroken; // has the parse become broken?
+ mork_bool mParser_IsDone; // has the parse finished?
+ mork_bool mParser_DoMore; // mParser_MoreGranularity not exhausted?
+
+ morkMid mParser_Mid; // current alias being parsed
+ // note that mParser_Mid.mMid_Buf points at mParser_ScopeCoil below:
+
+ // blob coils allocated in mParser_Heap
+ morkCoil mParser_ScopeCoil; // place to accumulate ID scope blobs
+ morkCoil mParser_ValueCoil; // place to accumulate value blobs
+ morkCoil mParser_ColumnCoil; // place to accumulate column blobs
+ morkCoil mParser_StringCoil; // place to accumulate string blobs
+
+ morkSpool mParser_ScopeSpool; // writes to mParser_ScopeCoil
+ morkSpool mParser_ValueSpool; // writes to mParser_ValueCoil
+ morkSpool mParser_ColumnSpool; // writes to mParser_ColumnCoil
+ morkSpool mParser_StringSpool; // writes to mParser_StringCoil
+
+ // yarns allocated in mParser_Heap
+ morkYarn mParser_MidYarn; // place to receive from MidToYarn()
+
+ // span showing current ongoing file position status:
+ morkSpan mParser_PortSpan; // span of current db port file
+
+ // various spans denoting nested subspaces inside the file's port span:
+ morkSpan mParser_GroupSpan; // span of current transaction group
+ morkSpan mParser_DictSpan;
+ morkSpan mParser_AliasSpan;
+ morkSpan mParser_MetaSpan;
+ morkSpan mParser_TableSpan;
+ morkSpan mParser_RowSpan;
+ morkSpan mParser_CellSpan;
+ morkSpan mParser_ColumnSpan;
+ morkSpan mParser_SlotSpan;
+
+private: // convenience inlines
+
+ mork_pos HerePos() const
+ { return mParser_PortSpan.mSpan_End.mPlace_Pos; }
+
+ void SetHerePos(mork_pos inPos)
+ { mParser_PortSpan.mSpan_End.mPlace_Pos = inPos; }
+
+ void CountLineBreak()
+ { ++mParser_PortSpan.mSpan_End.mPlace_Line; }
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseParser() only if open
+ virtual ~morkParser(); // assert that CloseParser() executed earlier
+
+public: // morkYarn construction & destruction
+ morkParser(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ morkStream* ioStream, // the readonly stream for input bytes
+ mdb_count inBytesPerParseSegment, // target for ParseMore()
+ nsIMdbHeap* ioSlotHeap);
+
+ void CloseParser(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkParser(const morkParser& other);
+ morkParser& operator=(const morkParser& other);
+
+public: // dynamic type identification
+ mork_bool IsParser() const
+ { return IsNode() && mNode_Derived == morkDerived_kParser; }
+
+// } ===== end morkNode methods =====
+
+public: // errors and warnings
+ static void UnexpectedEofError(morkEnv* ev);
+ static void EofInsteadOfHexError(morkEnv* ev);
+ static void ExpectedEqualError(morkEnv* ev);
+ static void ExpectedHexDigitError(morkEnv* ev, int c);
+ static void NonParserTypeError(morkEnv* ev);
+ static void UnexpectedByteInMetaWarning(morkEnv* ev);
+
+public: // other type methods
+ mork_bool GoodParserTag() const { return mParser_Tag == morkParser_kTag; }
+ void NonGoodParserError(morkEnv* ev);
+ void NonUsableParserError(morkEnv* ev);
+ // call when IsNode() or GoodParserTag() is false
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // in virtual morkParser methods, data flow subclass to parser
+
+ virtual void MidToYarn(morkEnv* ev,
+ const morkMid& inMid, // typically an alias to concat with strings
+ mdbYarn* outYarn) = 0;
+ // The parser might ask that some aliases be turned into yarns, so they
+ // can be concatenated into longer blobs under some circumstances. This
+ // is an alternative to using a long and complex callback for many parts
+ // for a single cell value.
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // out virtual morkParser methods, data flow parser to subclass
+
+// The virtual methods below will be called in a pattern corresponding
+// to the following grammar isomorphic to the Mork grammar. There should
+// be no exceptions, so subclasses can rely on seeing an appropriate "end"
+// method whenever some "new" method has been seen earlier. In the event
+// that some error occurs that causes content to be flushed, or sudden early
+// termination of a larger containing entity, we will always call a more
+// enclosed "end" method before we call an "end" method with greater scope.
+
+// Note the "mp" prefix stands for "Mork Parser":
+
+// mp:Start ::= OnNewPort mp:PortItem* OnPortEnd
+// mp:PortItem ::= mp:Content | mp:Group | OnPortGlitch
+// mp:Group ::= OnNewGroup mp:GroupItem* mp:GroupEnd
+// mp:GroupItem ::= mp:Content | OnGroupGlitch
+// mp:GroupEnd ::= OnGroupCommitEnd | OnGroupAbortEnd
+// mp:Content ::= mp:PortRow | mp:Dict | mp:Table | mp:Row
+// mp:PortRow ::= OnNewPortRow mp:RowItem* OnPortRowEnd
+// mp:Dict ::= OnNewDict mp:DictItem* OnDictEnd
+// mp:DictItem ::= OnAlias | OnAliasGlitch | mp:Meta | OnDictGlitch
+// mp:Table ::= OnNewTable mp:TableItem* OnTableEnd
+// mp:TableItem ::= mp:Row | mp:MetaTable | OnTableGlitch
+// mp:MetaTable ::= OnNewMeta mp:MetaItem* mp:Row OnMetaEnd
+// mp:Meta ::= OnNewMeta mp:MetaItem* OnMetaEnd
+// mp:MetaItem ::= mp:Cell | OnMetaGlitch
+// mp:Row ::= OnMinusRow? OnNewRow mp:RowItem* OnRowEnd
+// mp:RowItem ::= mp:Cell | mp:Meta | OnRowGlitch
+// mp:Cell ::= OnMinusCell? OnNewCell mp:CellItem? OnCellEnd
+// mp:CellItem ::= mp:Slot | OnCellForm | OnCellGlitch
+// mp:Slot ::= OnValue | OnValueMid | OnRowMid | OnTableMid
+
+
+ // Note that in interfaces below, mork_change parameters kAdd and kNil
+ // both mean about the same thing by default. Only kCut is interesting,
+ // because this usually means to remove members instead of adding them.
+
+ virtual void OnNewPort(morkEnv* ev, const morkPlace& inPlace) = 0;
+ virtual void OnPortGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
+ virtual void OnPortEnd(morkEnv* ev, const morkSpan& inSpan) = 0;
+
+ virtual void OnNewGroup(morkEnv* ev, const morkPlace& inPlace, mork_gid inGid) = 0;
+ virtual void OnGroupGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
+ virtual void OnGroupCommitEnd(morkEnv* ev, const morkSpan& inSpan) = 0;
+ virtual void OnGroupAbortEnd(morkEnv* ev, const morkSpan& inSpan) = 0;
+
+ virtual void OnNewPortRow(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid& inMid, mork_change inChange) = 0;
+ virtual void OnPortRowGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
+ virtual void OnPortRowEnd(morkEnv* ev, const morkSpan& inSpan) = 0;
+
+ virtual void OnNewTable(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid& inMid, mork_bool inCutAllRows) = 0;
+ virtual void OnTableGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
+ virtual void OnTableEnd(morkEnv* ev, const morkSpan& inSpan) = 0;
+
+ virtual void OnNewMeta(morkEnv* ev, const morkPlace& inPlace) = 0;
+ virtual void OnMetaGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
+ virtual void OnMetaEnd(morkEnv* ev, const morkSpan& inSpan) = 0;
+
+ virtual void OnMinusRow(morkEnv* ev) = 0;
+ virtual void OnNewRow(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid& inMid, mork_bool inCutAllCols) = 0;
+ virtual void OnRowPos(morkEnv* ev, mork_pos inRowPos) = 0;
+ virtual void OnRowGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
+ virtual void OnRowEnd(morkEnv* ev, const morkSpan& inSpan) = 0;
+
+ virtual void OnNewDict(morkEnv* ev, const morkPlace& inPlace) = 0;
+ virtual void OnDictGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
+ virtual void OnDictEnd(morkEnv* ev, const morkSpan& inSpan) = 0;
+
+ virtual void OnAlias(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid) = 0;
+
+ virtual void OnAliasGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
+
+ virtual void OnMinusCell(morkEnv* ev) = 0;
+ virtual void OnNewCell(morkEnv* ev, const morkPlace& inPlace,
+ const morkMid* inMid, const morkBuf* inBuf) = 0;
+ // Exactly one of inMid and inBuf is nil, and the other is non-nil.
+ // When hex ID syntax is used for a column, then inMid is not nil, and
+ // when a naked string names a column, then inBuf is not nil.
+
+ virtual void OnCellGlitch(morkEnv* ev, const morkGlitch& inGlitch) = 0;
+ virtual void OnCellForm(morkEnv* ev, mork_cscode inCharsetFormat) = 0;
+ virtual void OnCellEnd(morkEnv* ev, const morkSpan& inSpan) = 0;
+
+ virtual void OnValue(morkEnv* ev, const morkSpan& inSpan,
+ const morkBuf& inBuf) = 0;
+
+ virtual void OnValueMid(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid) = 0;
+
+ virtual void OnRowMid(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid) = 0;
+
+ virtual void OnTableMid(morkEnv* ev, const morkSpan& inSpan,
+ const morkMid& inMid) = 0;
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+protected: // protected parser helper methods
+
+ void ParseChunk(morkEnv* ev); // find parse continuation and resume
+
+ void StartParse(morkEnv* ev); // prepare for parsing
+ void StopParse(morkEnv* ev); // terminate parsing & call needed methods
+
+ int NextChar(morkEnv* ev); // next non-white content
+
+ void OnCellState(morkEnv* ev);
+ void OnMetaState(morkEnv* ev);
+ void OnRowState(morkEnv* ev);
+ void OnTableState(morkEnv* ev);
+ void OnDictState(morkEnv* ev);
+ void OnPortState(morkEnv* ev);
+ void OnStartState(morkEnv* ev);
+
+ void ReadCell(morkEnv* ev);
+ void ReadRow(morkEnv* ev, int c);
+ void ReadRowPos(morkEnv* ev);
+ void ReadTable(morkEnv* ev);
+ void ReadTableMeta(morkEnv* ev);
+ void ReadDict(morkEnv* ev);
+ mork_bool ReadContent(morkEnv* ev, mork_bool inInsideGroup);
+ void ReadGroup(morkEnv* ev);
+ mork_bool ReadEndGroupId(morkEnv* ev);
+ mork_bool ReadAt(morkEnv* ev, mork_bool inInsideGroup);
+ mork_bool FindGroupEnd(morkEnv* ev);
+ void ReadMeta(morkEnv* ev, int inEndMeta);
+ void ReadAlias(morkEnv* ev);
+ mork_id ReadHex(morkEnv* ev, int* outNextChar);
+ morkBuf* ReadValue(morkEnv* ev);
+ morkBuf* ReadName(morkEnv* ev, int c);
+ mork_bool ReadMid(morkEnv* ev, morkMid* outMid);
+ void ReadDictForm(morkEnv *ev);
+ void ReadCellForm(morkEnv *ev, int c);
+
+ mork_bool MatchPattern(morkEnv* ev, const char* inPattern);
+
+ void EndSpanOnThisByte(morkEnv* ev, morkSpan* ioSpan);
+ void EndSpanOnLastByte(morkEnv* ev, morkSpan* ioSpan);
+ void StartSpanOnLastByte(morkEnv* ev, morkSpan* ioSpan);
+
+ void StartSpanOnThisByte(morkEnv* ev, morkSpan* ioSpan);
+
+
+ // void EndSpanOnThisByte(morkEnv* ev, morkSpan* ioSpan)
+ // { MORK_USED_2(ev,ioSpan); }
+
+ // void EndSpanOnLastByte(morkEnv* ev, morkSpan* ioSpan)
+ // { MORK_USED_2(ev,ioSpan); }
+
+ // void StartSpanOnLastByte(morkEnv* ev, morkSpan* ioSpan)
+ // { MORK_USED_2(ev,ioSpan); }
+
+ // void StartSpanOnThisByte(morkEnv* ev, morkSpan* ioSpan)
+ // { MORK_USED_2(ev,ioSpan); }
+
+ int eat_line_break(morkEnv* ev, int inLast);
+ int eat_line_continue(morkEnv* ev); // last char was '\\'
+ int eat_comment(morkEnv* ev); // last char was '/'
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // public non-poly morkParser methods
+
+ mdb_count ParseMore( // return count of bytes consumed now
+ morkEnv* ev, // context
+ mork_pos* outPos, // current byte pos in the stream afterwards
+ mork_bool* outDone, // is parsing finished?
+ mork_bool* outBroken // is parsing irreparably dead and broken?
+ );
+
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakParser(morkParser* me,
+ morkEnv* ev, morkParser** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongParser(morkParser* me,
+ morkEnv* ev, morkParser** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKPARSER_ */
+
diff --git a/components/mork/src/morkPool.cpp b/components/mork/src/morkPool.cpp
new file mode 100644
index 000000000..6819b8bd0
--- /dev/null
+++ b/components/mork/src/morkPool.cpp
@@ -0,0 +1,552 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKPOOL_
+#include "morkPool.h"
+#endif
+
+#ifndef _MORKATOM_
+#include "morkAtom.h"
+#endif
+
+#ifndef _MORKHANDLE_
+#include "morkHandle.h"
+#endif
+
+#ifndef _MORKCELL_
+#include "morkCell.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+#ifndef _MORKBLOB_
+#include "morkBlob.h"
+#endif
+
+#ifndef _MORKDEQUE_
+#include "morkDeque.h"
+#endif
+
+#ifndef _MORKZONE_
+#include "morkZone.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkPool::CloseMorkNode(morkEnv* ev) // ClosePool() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->ClosePool(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkPool::~morkPool() // assert ClosePool() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkPool::morkPool(const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ nsIMdbHeap* ioSlotHeap)
+: morkNode(inUsage, ioHeap)
+, mPool_Heap( ioSlotHeap )
+, mPool_UsedFramesCount( 0 )
+, mPool_FreeFramesCount( 0 )
+{
+ // mPool_Heap is NOT refcounted
+ MORK_ASSERT(ioSlotHeap);
+ if ( ioSlotHeap )
+ mNode_Derived = morkDerived_kPool;
+}
+
+/*public non-poly*/
+morkPool::morkPool(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+: morkNode(ev, inUsage, ioHeap)
+, mPool_Heap( ioSlotHeap )
+, mPool_UsedFramesCount( 0 )
+, mPool_FreeFramesCount( 0 )
+{
+ if ( ioSlotHeap )
+ {
+ // mPool_Heap is NOT refcounted:
+ // nsIMdbHeap_SlotStrongHeap(ioSlotHeap, ev, &mPool_Heap);
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kPool;
+ }
+ else
+ ev->NilPointerError();
+}
+
+/*public non-poly*/ void
+morkPool::ClosePool(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+#ifdef morkZone_CONFIG_ARENA
+#else /*morkZone_CONFIG_ARENA*/
+ //MORK_USED_1(ioZone);
+#endif /*morkZone_CONFIG_ARENA*/
+
+ nsIMdbHeap* heap = mPool_Heap;
+ nsIMdbEnv* mev = ev->AsMdbEnv();
+ morkLink* aLink;
+ morkDeque* d = &mPool_FreeHandleFrames;
+ while ( (aLink = d->RemoveFirst()) != 0 )
+ heap->Free(mev, aLink);
+
+ // if the pool's closed, get rid of the frames in use too.
+ d = &mPool_UsedHandleFrames;
+ while ( (aLink = d->RemoveFirst()) != 0 )
+ heap->Free(mev, aLink);
+
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+
+// alloc and free individual instances of handles (inside hand frames):
+morkHandleFace*
+morkPool::NewHandle(morkEnv* ev, mork_size inSize, morkZone* ioZone)
+{
+ void* newBlock = 0;
+ if ( inSize <= sizeof(morkHandleFrame) )
+ {
+ morkLink* firstLink = mPool_FreeHandleFrames.RemoveFirst();
+ if ( firstLink )
+ {
+ newBlock = firstLink;
+ if ( mPool_FreeFramesCount )
+ --mPool_FreeFramesCount;
+ else
+ ev->NewWarning("mPool_FreeFramesCount underflow");
+ }
+ else
+ mPool_Heap->Alloc(ev->AsMdbEnv(), sizeof(morkHandleFrame),
+ (void**) &newBlock);
+ }
+ else
+ {
+ ev->NewWarning("inSize > sizeof(morkHandleFrame)");
+ mPool_Heap->Alloc(ev->AsMdbEnv(), inSize, (void**) &newBlock);
+ }
+#ifdef morkZone_CONFIG_ARENA
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+#endif /*morkZone_CONFIG_ARENA*/
+
+ return (morkHandleFace*) newBlock;
+}
+
+void
+morkPool::ZapHandle(morkEnv* ev, morkHandleFace* ioHandle)
+{
+ if ( ioHandle )
+ {
+ morkLink* handleLink = (morkLink*) ioHandle;
+ mPool_FreeHandleFrames.AddLast(handleLink);
+ ++mPool_FreeFramesCount;
+ // lets free all handles to track down leaks
+ // - uncomment out next 3 lines, comment out above 2
+// nsIMdbHeap* heap = mPool_Heap;
+// nsIMdbEnv* mev = ev->AsMdbEnv();
+// heap->Free(mev, handleLink);
+
+ }
+}
+
+
+// alloc and free individual instances of rows:
+morkRow*
+morkPool::NewRow(morkEnv* ev, morkZone* ioZone) // allocate a new row instance
+{
+ morkRow* newRow = 0;
+
+#ifdef morkZone_CONFIG_ARENA
+ // a zone 'chip' remembers no size, and so cannot be deallocated:
+ newRow = (morkRow*) ioZone->ZoneNewChip(ev, sizeof(morkRow));
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ mPool_Heap->Alloc(ev->AsMdbEnv(), sizeof(morkRow), (void**) &newRow);
+#endif /*morkZone_CONFIG_ARENA*/
+
+ if ( newRow )
+ MORK_MEMSET(newRow, 0, sizeof(morkRow));
+
+ return newRow;
+}
+
+void
+morkPool::ZapRow(morkEnv* ev, morkRow* ioRow,
+ morkZone* ioZone) // free old row instance
+{
+#ifdef morkZone_CONFIG_ARENA
+ if ( !ioRow )
+ ev->NilPointerWarning(); // a zone 'chip' cannot be freed
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ if ( ioRow )
+ mPool_Heap->Free(ev->AsMdbEnv(), ioRow);
+#endif /*morkZone_CONFIG_ARENA*/
+}
+
+// alloc and free entire vectors of cells (not just one cell at a time)
+morkCell*
+morkPool::NewCells(morkEnv* ev, mork_size inSize,
+ morkZone* ioZone)
+{
+ morkCell* newCells = 0;
+
+ mork_size size = inSize * sizeof(morkCell);
+ if ( size )
+ {
+#ifdef morkZone_CONFIG_ARENA
+ // a zone 'run' knows its size, and can indeed be deallocated:
+ newCells = (morkCell*) ioZone->ZoneNewRun(ev, size);
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ mPool_Heap->Alloc(ev->AsMdbEnv(), size, (void**) &newCells);
+#endif /*morkZone_CONFIG_ARENA*/
+ }
+
+ // note morkAtom depends on having nil stored in all new mCell_Atom slots:
+ if ( newCells )
+ MORK_MEMSET(newCells, 0, size);
+ return newCells;
+}
+
+void
+morkPool::ZapCells(morkEnv* ev, morkCell* ioVector, mork_size inSize,
+ morkZone* ioZone)
+{
+ MORK_USED_1(inSize);
+
+ if ( ioVector )
+ {
+#ifdef morkZone_CONFIG_ARENA
+ // a zone 'run' knows its size, and can indeed be deallocated:
+ ioZone->ZoneZapRun(ev, ioVector);
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ mPool_Heap->Free(ev->AsMdbEnv(), ioVector);
+#endif /*morkZone_CONFIG_ARENA*/
+ }
+}
+
+// resize (grow or trim) cell vectors inside a containing row instance
+mork_bool
+morkPool::AddRowCells(morkEnv* ev, morkRow* ioRow, mork_size inNewSize,
+ morkZone* ioZone)
+{
+ // note strong implementation similarity to morkArray::Grow()
+
+ MORK_USED_1(ioZone);
+#ifdef morkZone_CONFIG_ARENA
+#else /*morkZone_CONFIG_ARENA*/
+#endif /*morkZone_CONFIG_ARENA*/
+
+ mork_fill fill = ioRow->mRow_Length;
+ if ( ev->Good() && fill < inNewSize ) // need more cells?
+ {
+ morkCell* newCells = this->NewCells(ev, inNewSize, ioZone);
+ if ( newCells )
+ {
+ morkCell* c = newCells; // for iterating during copy
+ morkCell* oldCells = ioRow->mRow_Cells;
+ morkCell* end = oldCells + fill; // copy all the old cells
+ while ( oldCells < end )
+ {
+ *c++ = *oldCells++; // bitwise copy each old cell struct
+ }
+ oldCells = ioRow->mRow_Cells;
+ ioRow->mRow_Cells = newCells;
+ ioRow->mRow_Length = (mork_u2) inNewSize;
+ ++ioRow->mRow_Seed;
+
+ if ( oldCells )
+ this->ZapCells(ev, oldCells, fill, ioZone);
+ }
+ }
+ return ( ev->Good() && ioRow->mRow_Length >= inNewSize );
+}
+
+mork_bool
+morkPool::CutRowCells(morkEnv* ev, morkRow* ioRow,
+ mork_size inNewSize,
+ morkZone* ioZone)
+{
+ MORK_USED_1(ioZone);
+#ifdef morkZone_CONFIG_ARENA
+#else /*morkZone_CONFIG_ARENA*/
+#endif /*morkZone_CONFIG_ARENA*/
+
+ mork_fill fill = ioRow->mRow_Length;
+ if ( ev->Good() && fill > inNewSize ) // need fewer cells?
+ {
+ if ( inNewSize ) // want any row cells at all?
+ {
+ morkCell* newCells = this->NewCells(ev, inNewSize, ioZone);
+ if ( newCells )
+ {
+ morkCell* saveNewCells = newCells; // Keep newcell pos
+ morkCell* oldCells = ioRow->mRow_Cells;
+ morkCell* oldEnd = oldCells + fill; // one past all old cells
+ morkCell* newEnd = oldCells + inNewSize; // copy only kept old cells
+ while ( oldCells < newEnd )
+ {
+ *newCells++ = *oldCells++; // bitwise copy each old cell struct
+ }
+ while ( oldCells < oldEnd )
+ {
+ if ( oldCells->mCell_Atom ) // need to unref old cell atom?
+ oldCells->SetAtom(ev, (morkAtom*) 0, this); // unref cell atom
+ ++oldCells;
+ }
+ oldCells = ioRow->mRow_Cells;
+ ioRow->mRow_Cells = saveNewCells;
+ ioRow->mRow_Length = (mork_u2) inNewSize;
+ ++ioRow->mRow_Seed;
+
+ if ( oldCells )
+ this->ZapCells(ev, oldCells, fill, ioZone);
+ }
+ }
+ else // get rid of all row cells
+ {
+ morkCell* oldCells = ioRow->mRow_Cells;
+ ioRow->mRow_Cells = 0;
+ ioRow->mRow_Length = 0;
+ ++ioRow->mRow_Seed;
+
+ if ( oldCells )
+ this->ZapCells(ev, oldCells, fill, ioZone);
+ }
+ }
+ return ( ev->Good() && ioRow->mRow_Length <= inNewSize );
+}
+
+// alloc & free individual instances of atoms (lots of atom subclasses):
+void
+morkPool::ZapAtom(morkEnv* ev, morkAtom* ioAtom,
+ morkZone* ioZone) // any subclass (by kind)
+{
+#ifdef morkZone_CONFIG_ARENA
+ if ( !ioAtom )
+ ev->NilPointerWarning(); // a zone 'chip' cannot be freed
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ if ( ioAtom )
+ mPool_Heap->Free(ev->AsMdbEnv(), ioAtom);
+#endif /*morkZone_CONFIG_ARENA*/
+}
+
+morkOidAtom*
+morkPool::NewRowOidAtom(morkEnv* ev, const mdbOid& inOid,
+ morkZone* ioZone)
+{
+ morkOidAtom* newAtom = 0;
+
+#ifdef morkZone_CONFIG_ARENA
+ // a zone 'chip' remembers no size, and so cannot be deallocated:
+ newAtom = (morkOidAtom*) ioZone->ZoneNewChip(ev, sizeof(morkOidAtom));
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ mPool_Heap->Alloc(ev->AsMdbEnv(), sizeof(morkOidAtom),(void**) &newAtom);
+#endif /*morkZone_CONFIG_ARENA*/
+
+ if ( newAtom )
+ newAtom->InitRowOidAtom(ev, inOid);
+ return newAtom;
+}
+
+morkOidAtom*
+morkPool::NewTableOidAtom(morkEnv* ev, const mdbOid& inOid,
+ morkZone* ioZone)
+{
+ morkOidAtom* newAtom = 0;
+
+#ifdef morkZone_CONFIG_ARENA
+ // a zone 'chip' remembers no size, and so cannot be deallocated:
+ newAtom = (morkOidAtom*) ioZone->ZoneNewChip(ev, sizeof(morkOidAtom));
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ mPool_Heap->Alloc(ev->AsMdbEnv(), sizeof(morkOidAtom), (void**) &newAtom);
+#endif /*morkZone_CONFIG_ARENA*/
+ if ( newAtom )
+ newAtom->InitTableOidAtom(ev, inOid);
+ return newAtom;
+}
+
+morkAtom*
+morkPool::NewAnonAtom(morkEnv* ev, const morkBuf& inBuf,
+ mork_cscode inForm,
+ morkZone* ioZone)
+// if inForm is zero, and inBuf.mBuf_Fill is less than 256, then a 'wee'
+// anon atom will be created, and otherwise a 'big' anon atom.
+{
+ morkAtom* newAtom = 0;
+
+ mork_bool needBig = ( inForm || inBuf.mBuf_Fill > 255 );
+ mork_size size = ( needBig )?
+ morkBigAnonAtom::SizeForFill(inBuf.mBuf_Fill) :
+ morkWeeAnonAtom::SizeForFill(inBuf.mBuf_Fill);
+
+#ifdef morkZone_CONFIG_ARENA
+ // a zone 'chip' remembers no size, and so cannot be deallocated:
+ newAtom = (morkAtom*) ioZone->ZoneNewChip(ev, size);
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ mPool_Heap->Alloc(ev->AsMdbEnv(), size, (void**) &newAtom);
+#endif /*morkZone_CONFIG_ARENA*/
+ if ( newAtom )
+ {
+ if ( needBig )
+ ((morkBigAnonAtom*) newAtom)->InitBigAnonAtom(ev, inBuf, inForm);
+ else
+ ((morkWeeAnonAtom*) newAtom)->InitWeeAnonAtom(ev, inBuf);
+ }
+ return newAtom;
+}
+
+morkBookAtom*
+morkPool::NewBookAtom(morkEnv* ev, const morkBuf& inBuf,
+ mork_cscode inForm, morkAtomSpace* ioSpace, mork_aid inAid,
+ morkZone* ioZone)
+// if inForm is zero, and inBuf.mBuf_Fill is less than 256, then a 'wee'
+// book atom will be created, and otherwise a 'big' book atom.
+{
+ morkBookAtom* newAtom = 0;
+
+ mork_bool needBig = ( inForm || inBuf.mBuf_Fill > 255 );
+ mork_size size = ( needBig )?
+ morkBigBookAtom::SizeForFill(inBuf.mBuf_Fill) :
+ morkWeeBookAtom::SizeForFill(inBuf.mBuf_Fill);
+
+#ifdef morkZone_CONFIG_ARENA
+ // a zone 'chip' remembers no size, and so cannot be deallocated:
+ newAtom = (morkBookAtom*) ioZone->ZoneNewChip(ev, size);
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ mPool_Heap->Alloc(ev->AsMdbEnv(), size, (void**) &newAtom);
+#endif /*morkZone_CONFIG_ARENA*/
+ if ( newAtom )
+ {
+ if ( needBig )
+ ((morkBigBookAtom*) newAtom)->InitBigBookAtom(ev,
+ inBuf, inForm, ioSpace, inAid);
+ else
+ ((morkWeeBookAtom*) newAtom)->InitWeeBookAtom(ev,
+ inBuf, ioSpace, inAid);
+ }
+ return newAtom;
+}
+
+morkBookAtom*
+morkPool::NewBookAtomCopy(morkEnv* ev, const morkBigBookAtom& inAtom,
+ morkZone* ioZone)
+ // make the smallest kind of book atom that can hold content in inAtom.
+ // The inAtom parameter is often expected to be a staged book atom in
+ // the store, which was used to search an atom space for existing atoms.
+{
+ morkBookAtom* newAtom = 0;
+
+ mork_cscode form = inAtom.mBigBookAtom_Form;
+ mork_fill fill = inAtom.mBigBookAtom_Size;
+ mork_bool needBig = ( form || fill > 255 );
+ mork_size size = ( needBig )?
+ morkBigBookAtom::SizeForFill(fill) :
+ morkWeeBookAtom::SizeForFill(fill);
+
+#ifdef morkZone_CONFIG_ARENA
+ // a zone 'chip' remembers no size, and so cannot be deallocated:
+ newAtom = (morkBookAtom*) ioZone->ZoneNewChip(ev, size);
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ mPool_Heap->Alloc(ev->AsMdbEnv(), size, (void**) &newAtom);
+#endif /*morkZone_CONFIG_ARENA*/
+ if ( newAtom )
+ {
+ morkBuf buf(inAtom.mBigBookAtom_Body, fill);
+ if ( needBig )
+ ((morkBigBookAtom*) newAtom)->InitBigBookAtom(ev,
+ buf, form, inAtom.mBookAtom_Space, inAtom.mBookAtom_Id);
+ else
+ ((morkWeeBookAtom*) newAtom)->InitWeeBookAtom(ev,
+ buf, inAtom.mBookAtom_Space, inAtom.mBookAtom_Id);
+ }
+ return newAtom;
+}
+
+morkBookAtom*
+morkPool::NewFarBookAtomCopy(morkEnv* ev, const morkFarBookAtom& inAtom,
+ morkZone* ioZone)
+ // make the smallest kind of book atom that can hold content in inAtom.
+ // The inAtom parameter is often expected to be a staged book atom in
+ // the store, which was used to search an atom space for existing atoms.
+{
+ morkBookAtom* newAtom = 0;
+
+ mork_cscode form = inAtom.mFarBookAtom_Form;
+ mork_fill fill = inAtom.mFarBookAtom_Size;
+ mork_bool needBig = ( form || fill > 255 );
+ mork_size size = ( needBig )?
+ morkBigBookAtom::SizeForFill(fill) :
+ morkWeeBookAtom::SizeForFill(fill);
+
+#ifdef morkZone_CONFIG_ARENA
+ // a zone 'chip' remembers no size, and so cannot be deallocated:
+ newAtom = (morkBookAtom*) ioZone->ZoneNewChip(ev, size);
+#else /*morkZone_CONFIG_ARENA*/
+ MORK_USED_1(ioZone);
+ mPool_Heap->Alloc(ev->AsMdbEnv(), size, (void**) &newAtom);
+#endif /*morkZone_CONFIG_ARENA*/
+ if ( newAtom )
+ {
+ morkBuf buf(inAtom.mFarBookAtom_Body, fill);
+ if ( needBig )
+ ((morkBigBookAtom*) newAtom)->InitBigBookAtom(ev,
+ buf, form, inAtom.mBookAtom_Space, inAtom.mBookAtom_Id);
+ else
+ ((morkWeeBookAtom*) newAtom)->InitWeeBookAtom(ev,
+ buf, inAtom.mBookAtom_Space, inAtom.mBookAtom_Id);
+ }
+ return newAtom;
+}
+
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
diff --git a/components/mork/src/morkPool.h b/components/mork/src/morkPool.h
new file mode 100644
index 000000000..b2b349347
--- /dev/null
+++ b/components/mork/src/morkPool.h
@@ -0,0 +1,152 @@
+/* -*- 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/. */
+
+#ifndef _MORKPOOL_
+#define _MORKPOOL_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKDEQUE_
+#include "morkDeque.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class morkHandle;
+class morkHandleFrame;
+class morkHandleFace; // just an opaque cookie type
+class morkBigBookAtom;
+class morkFarBookAtom;
+
+#define morkDerived_kPool /*i*/ 0x706C /* ascii 'pl' */
+
+/*| morkPool: a place to manage pools of non-node objects that are memory
+**| managed out of large chunks of space, so that per-object management
+**| space overhead has no significant cost.
+|*/
+class morkPool : public morkNode {
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+public: // state is public because the entire Mork system is private
+ nsIMdbHeap* mPool_Heap; // NON-refcounted heap instance
+
+ morkDeque mPool_Blocks; // linked list of large blocks from heap
+
+ // These two lists contain instances of morkHandleFrame:
+ morkDeque mPool_UsedHandleFrames; // handle frames currently being used
+ morkDeque mPool_FreeHandleFrames; // handle frames currently in free list
+
+ mork_count mPool_UsedFramesCount; // length of mPool_UsedHandleFrames
+ mork_count mPool_FreeFramesCount; // length of mPool_UsedHandleFrames
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev); // ClosePool() only if open
+ virtual ~morkPool(); // assert that ClosePool() executed earlier
+
+public: // morkPool construction & destruction
+ morkPool(const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ nsIMdbHeap* ioSlotHeap);
+ morkPool(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ nsIMdbHeap* ioSlotHeap);
+ void ClosePool(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkPool(const morkPool& other);
+ morkPool& operator=(const morkPool& other);
+
+public: // dynamic type identification
+ mork_bool IsPool() const
+ { return IsNode() && mNode_Derived == morkDerived_kPool; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ void NonPoolTypeError(morkEnv* ev);
+
+public: // morkNode memory management operators
+ void* operator new(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev) CPP_THROW_NEW
+ { return morkNode::MakeNew(inSize, ioHeap, ev); }
+
+ void* operator new(size_t inSize) CPP_THROW_NEW
+ { return ::operator new(inSize); }
+
+
+public: // other pool methods
+
+ // alloc and free individual instances of handles (inside hand frames):
+ morkHandleFace* NewHandle(morkEnv* ev, mork_size inSize, morkZone* ioZone);
+ void ZapHandle(morkEnv* ev, morkHandleFace* ioHandle);
+
+ // alloc and free individual instances of rows:
+ morkRow* NewRow(morkEnv* ev, morkZone* ioZone); // alloc new row instance
+ void ZapRow(morkEnv* ev, morkRow* ioRow, morkZone* ioZone); // free old row instance
+
+ // alloc and free entire vectors of cells (not just one cell at a time)
+ morkCell* NewCells(morkEnv* ev, mork_size inSize, morkZone* ioZone);
+ void ZapCells(morkEnv* ev, morkCell* ioVector, mork_size inSize, morkZone* ioZone);
+
+ // resize (grow or trim) cell vectors inside a containing row instance
+ mork_bool AddRowCells(morkEnv* ev, morkRow* ioRow, mork_size inNewSize, morkZone* ioZone);
+ mork_bool CutRowCells(morkEnv* ev, morkRow* ioRow, mork_size inNewSize, morkZone* ioZone);
+
+ // alloc & free individual instances of atoms (lots of atom subclasses):
+ void ZapAtom(morkEnv* ev, morkAtom* ioAtom, morkZone* ioZone); // any subclass (by kind)
+
+ morkOidAtom* NewRowOidAtom(morkEnv* ev, const mdbOid& inOid, morkZone* ioZone);
+ morkOidAtom* NewTableOidAtom(morkEnv* ev, const mdbOid& inOid, morkZone* ioZone);
+
+ morkAtom* NewAnonAtom(morkEnv* ev, const morkBuf& inBuf,
+ mork_cscode inForm, morkZone* ioZone);
+ // if inForm is zero, and inBuf.mBuf_Fill is less than 256, then a 'wee'
+ // anon atom will be created, and otherwise a 'big' anon atom.
+
+ morkBookAtom* NewBookAtom(morkEnv* ev, const morkBuf& inBuf,
+ mork_cscode inForm, morkAtomSpace* ioSpace, mork_aid inAid, morkZone* ioZone);
+ // if inForm is zero, and inBuf.mBuf_Fill is less than 256, then a 'wee'
+ // book atom will be created, and otherwise a 'big' book atom.
+
+ morkBookAtom* NewBookAtomCopy(morkEnv* ev, const morkBigBookAtom& inAtom, morkZone* ioZone);
+ // make the smallest kind of book atom that can hold content in inAtom.
+ // The inAtom parameter is often expected to be a staged book atom in
+ // the store, which was used to search an atom space for existing atoms.
+
+ morkBookAtom* NewFarBookAtomCopy(morkEnv* ev, const morkFarBookAtom& inAtom, morkZone* ioZone);
+ // make the smallest kind of book atom that can hold content in inAtom.
+ // The inAtom parameter is often expected to be a staged book atom in
+ // the store, which was used to search an atom space for existing atoms.
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakPool(morkPool* me,
+ morkEnv* ev, morkPool** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongPool(morkPool* me,
+ morkEnv* ev, morkPool** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKPOOL_ */
+
diff --git a/components/mork/src/morkPortTableCursor.cpp b/components/mork/src/morkPortTableCursor.cpp
new file mode 100644
index 000000000..2542fcc49
--- /dev/null
+++ b/components/mork/src/morkPortTableCursor.cpp
@@ -0,0 +1,430 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKCURSOR_
+#include "morkCursor.h"
+#endif
+
+#ifndef _MORKPORTTABLECURSOR_
+#include "morkPortTableCursor.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkPortTableCursor::CloseMorkNode(morkEnv* ev) // ClosePortTableCursor() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->ClosePortTableCursor(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkPortTableCursor::~morkPortTableCursor() // ClosePortTableCursor() executed earlier
+{
+ CloseMorkNode(mMorkEnv);
+}
+
+/*public non-poly*/
+morkPortTableCursor::morkPortTableCursor(morkEnv* ev,
+ const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkStore* ioStore, mdb_scope inRowScope,
+ mdb_kind inTableKind, nsIMdbHeap* ioSlotHeap)
+: morkCursor(ev, inUsage, ioHeap)
+, mPortTableCursor_Store( 0 )
+, mPortTableCursor_RowScope( (mdb_scope) -1 ) // we want != inRowScope
+, mPortTableCursor_TableKind( (mdb_kind) -1 ) // we want != inTableKind
+, mPortTableCursor_LastTable ( 0 ) // not refcounted
+, mPortTableCursor_RowSpace( 0 ) // strong ref to row space
+, mPortTableCursor_TablesDidEnd( morkBool_kFalse )
+, mPortTableCursor_SpacesDidEnd( morkBool_kFalse )
+{
+ if ( ev->Good() )
+ {
+ if ( ioStore && ioSlotHeap )
+ {
+ mCursor_Pos = -1;
+ mCursor_Seed = 0; // let the iterator do its own seed handling
+ morkStore::SlotWeakStore(ioStore, ev, &mPortTableCursor_Store);
+
+ if ( this->SetRowScope(ev, inRowScope) )
+ this->SetTableKind(ev, inTableKind);
+
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kPortTableCursor;
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkPortTableCursor, morkCursor, nsIMdbPortTableCursor)
+
+morkEnv*
+morkPortTableCursor::CanUsePortTableCursor(nsIMdbEnv* mev, mork_bool inMutable,
+ nsresult* outErr) const
+{
+ morkEnv* outEnv = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( IsPortTableCursor() )
+ outEnv = ev;
+ else
+ NonPortTableCursorTypeError(ev);
+ *outErr = ev->AsErr();
+ }
+ MORK_ASSERT(outEnv);
+ return outEnv;
+}
+
+
+/*public non-poly*/ void
+morkPortTableCursor::ClosePortTableCursor(morkEnv* ev)
+{
+ if ( this->IsNode() )
+ {
+ mCursor_Pos = -1;
+ mCursor_Seed = 0;
+ mPortTableCursor_LastTable = 0;
+ morkStore::SlotWeakStore((morkStore*) 0, ev, &mPortTableCursor_Store);
+ morkRowSpace::SlotStrongRowSpace((morkRowSpace*) 0, ev,
+ &mPortTableCursor_RowSpace);
+ this->CloseCursor(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkPortTableCursor::NilCursorStoreError(morkEnv* ev)
+{
+ ev->NewError("nil mPortTableCursor_Store");
+}
+
+/*static*/ void
+morkPortTableCursor::NonPortTableCursorTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkPortTableCursor");
+}
+
+mork_bool
+morkPortTableCursor::SetRowScope(morkEnv* ev, mork_scope inRowScope)
+{
+ mPortTableCursor_RowScope = inRowScope;
+ mPortTableCursor_LastTable = 0; // restart iteration of space
+
+ mPortTableCursor_TableIter.CloseMapIter(ev);
+ mPortTableCursor_TablesDidEnd = morkBool_kTrue;
+ mPortTableCursor_SpacesDidEnd = morkBool_kTrue;
+
+ morkStore* store = mPortTableCursor_Store;
+ if ( store )
+ {
+ morkRowSpace* space = mPortTableCursor_RowSpace;
+
+ if ( inRowScope ) // intend to cover a specific scope only?
+ {
+ space = store->LazyGetRowSpace(ev, inRowScope);
+ morkRowSpace::SlotStrongRowSpace(space, ev,
+ &mPortTableCursor_RowSpace);
+
+ // We want mPortTableCursor_SpacesDidEnd == morkBool_kTrue
+ // to show this is the only space to be covered.
+ }
+ else // prepare space map iter to cover all space scopes
+ {
+ morkRowSpaceMapIter* rsi = &mPortTableCursor_SpaceIter;
+ rsi->InitRowSpaceMapIter(ev, &store->mStore_RowSpaces);
+
+ space = 0;
+ (void) rsi->FirstRowSpace(ev, (mork_scope*) 0, &space);
+ morkRowSpace::SlotStrongRowSpace(space, ev,
+ &mPortTableCursor_RowSpace);
+
+ if ( space ) // found first space in store
+ mPortTableCursor_SpacesDidEnd = morkBool_kFalse;
+ }
+
+ this->init_space_tables_map(ev);
+ }
+ else
+ this->NilCursorStoreError(ev);
+
+ return ev->Good();
+}
+
+void
+morkPortTableCursor::init_space_tables_map(morkEnv* ev)
+{
+ morkRowSpace* space = mPortTableCursor_RowSpace;
+ if ( space && ev->Good() )
+ {
+ morkTableMapIter* ti = &mPortTableCursor_TableIter;
+ ti->InitTableMapIter(ev, &space->mRowSpace_Tables);
+ if ( ev->Good() )
+ mPortTableCursor_TablesDidEnd = morkBool_kFalse;
+ }
+}
+
+
+mork_bool
+morkPortTableCursor::SetTableKind(morkEnv* ev, mork_kind inTableKind)
+{
+ mPortTableCursor_TableKind = inTableKind;
+ mPortTableCursor_LastTable = 0; // restart iteration of space
+
+ mPortTableCursor_TablesDidEnd = morkBool_kTrue;
+
+ morkRowSpace* space = mPortTableCursor_RowSpace;
+ if ( !space && mPortTableCursor_RowScope == 0 )
+ {
+ this->SetRowScope(ev, 0);
+ }
+ this->init_space_tables_map(ev);
+
+ return ev->Good();
+}
+
+morkRowSpace*
+morkPortTableCursor::NextSpace(morkEnv* ev)
+{
+ morkRowSpace* outSpace = 0;
+ mPortTableCursor_LastTable = 0;
+ mPortTableCursor_SpacesDidEnd = morkBool_kTrue;
+ mPortTableCursor_TablesDidEnd = morkBool_kTrue;
+
+ if ( !mPortTableCursor_RowScope ) // not just one scope?
+ {
+ morkStore* store = mPortTableCursor_Store;
+ if ( store )
+ {
+ morkRowSpaceMapIter* rsi = &mPortTableCursor_SpaceIter;
+
+ (void) rsi->NextRowSpace(ev, (mork_scope*) 0, &outSpace);
+ morkRowSpace::SlotStrongRowSpace(outSpace, ev,
+ &mPortTableCursor_RowSpace);
+
+ if ( outSpace ) // found next space in store
+ {
+ mPortTableCursor_SpacesDidEnd = morkBool_kFalse;
+
+ this->init_space_tables_map(ev);
+
+ if ( ev->Bad() )
+ outSpace = 0;
+ }
+ }
+ else
+ this->NilCursorStoreError(ev);
+ }
+
+ return outSpace;
+}
+
+morkTable *
+morkPortTableCursor::NextTable(morkEnv* ev)
+{
+ mork_kind kind = mPortTableCursor_TableKind;
+
+ do // until spaces end, or until we find a table in a space
+ {
+ morkRowSpace* space = mPortTableCursor_RowSpace;
+ if ( mPortTableCursor_TablesDidEnd ) // current space exhausted?
+ space = this->NextSpace(ev); // go on to the next space
+
+ if ( space ) // have a space remaining that might hold tables?
+ {
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+ morkTableMapIter* ti = &mPortTableCursor_TableIter;
+ morkTable* table = ( mPortTableCursor_LastTable )?
+ ti->NextTable(ev) : ti->FirstTable(ev);
+
+ for ( ; table && ev->Good(); table = ti->NextTable(ev) )
+
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+ mork_tid* key = 0; // ignore keys in table map
+ morkTable* table = 0; // old value table in the map
+ morkTableMapIter* ti = &mPortTableCursor_TableIter;
+ mork_change* c = ( mPortTableCursor_LastTable )?
+ ti->NextTable(ev, key, &table) : ti->FirstTable(ev, key, &table);
+
+ for ( ; c && ev->Good(); c = ti->NextTable(ev, key, &table) )
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+ {
+ if ( table && table->IsTable() )
+ {
+ if ( !kind || kind == table->mTable_Kind )
+ {
+ mPortTableCursor_LastTable = table; // ti->NextTable() hence
+ return table;
+ }
+ }
+ else
+ table->NonTableTypeWarning(ev);
+ }
+ mPortTableCursor_TablesDidEnd = morkBool_kTrue; // space is done
+ mPortTableCursor_LastTable = 0; // make sure next space starts fresh
+ }
+
+ } while ( ev->Good() && !mPortTableCursor_SpacesDidEnd );
+
+ return (morkTable*) 0;
+}
+
+
+// { ----- begin table iteration methods -----
+
+// { ===== begin nsIMdbPortTableCursor methods =====
+
+// { ----- begin attribute methods -----
+NS_IMETHODIMP
+morkPortTableCursor::SetPort(nsIMdbEnv* mev, nsIMdbPort* ioPort)
+{
+ NS_ASSERTION(false,"not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkPortTableCursor::GetPort(nsIMdbEnv* mev, nsIMdbPort** acqPort)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbPort* outPort = 0;
+ morkEnv* ev =
+ this->CanUsePortTableCursor(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ if ( mPortTableCursor_Store )
+ outPort = mPortTableCursor_Store->AcquireStoreHandle(ev);
+ outErr = ev->AsErr();
+ }
+ if ( acqPort )
+ *acqPort = outPort;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkPortTableCursor::SetRowScope(nsIMdbEnv* mev, // sets pos to -1
+ mdb_scope inRowScope)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev =
+ this->CanUsePortTableCursor(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ mCursor_Pos = -1;
+
+ SetRowScope(ev, inRowScope);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkPortTableCursor::GetRowScope(nsIMdbEnv* mev, mdb_scope* outRowScope)
+{
+ nsresult outErr = NS_OK;
+ mdb_scope rowScope = 0;
+ morkEnv* ev =
+ this->CanUsePortTableCursor(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ rowScope = mPortTableCursor_RowScope;
+ outErr = ev->AsErr();
+ }
+ *outRowScope = rowScope;
+ return outErr;
+}
+// setting row scope to zero iterates over all row scopes in port
+
+NS_IMETHODIMP
+morkPortTableCursor::SetTableKind(nsIMdbEnv* mev, // sets pos to -1
+ mdb_kind inTableKind)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev =
+ this->CanUsePortTableCursor(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ mCursor_Pos = -1;
+
+ SetTableKind(ev, inTableKind);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkPortTableCursor::GetTableKind(nsIMdbEnv* mev, mdb_kind* outTableKind)
+// setting table kind to zero iterates over all table kinds in row scope
+{
+ nsresult outErr = NS_OK;
+ mdb_kind tableKind = 0;
+ morkEnv* ev =
+ this->CanUsePortTableCursor(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ tableKind = mPortTableCursor_TableKind;
+ outErr = ev->AsErr();
+ }
+ *outTableKind = tableKind;
+ return outErr;
+}
+// } ----- end attribute methods -----
+
+// { ----- begin table iteration methods -----
+NS_IMETHODIMP
+morkPortTableCursor::NextTable( // get table at next position in the db
+ nsIMdbEnv* mev, // context
+ nsIMdbTable** acqTable)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbTable* outTable = 0;
+ morkEnv* ev =
+ CanUsePortTableCursor(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkTable* table = NextTable(ev);
+ if ( table && ev->Good() )
+ outTable = table->AcquireTableHandle(ev);
+
+ outErr = ev->AsErr();
+ }
+ if ( acqTable )
+ *acqTable = outTable;
+ return outErr;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkPortTableCursor.h b/components/mork/src/morkPortTableCursor.h
new file mode 100644
index 000000000..cc9edaa67
--- /dev/null
+++ b/components/mork/src/morkPortTableCursor.h
@@ -0,0 +1,141 @@
+/* -*- 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/. */
+
+#ifndef _MORKPORTTABLECURSOR_
+#define _MORKPORTTABLECURSOR_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKCURSOR_
+#include "morkCursor.h"
+#endif
+
+#ifndef _MORKROWSPACE_
+#include "morkRowSpace.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class orkinPortTableCursor;
+#define morkDerived_kPortTableCursor /*i*/ 0x7443 /* ascii 'tC' */
+
+class morkPortTableCursor : public morkCursor, public nsIMdbPortTableCursor { // row iterator
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // morkFactory* mObject_Factory; // weak ref to suite factory
+
+ // mork_seed mCursor_Seed;
+ // mork_pos mCursor_Pos;
+ // mork_bool mCursor_DoFailOnSeedOutOfSync;
+ // mork_u1 mCursor_Pad[ 3 ]; // explicitly pad to u4 alignment
+
+public: // state is public because the entire Mork system is private
+ // { ----- begin attribute methods -----
+ NS_IMETHOD SetPort(nsIMdbEnv* ev, nsIMdbPort* ioPort) override; // sets pos to -1
+ NS_IMETHOD GetPort(nsIMdbEnv* ev, nsIMdbPort** acqPort) override;
+
+ NS_IMETHOD SetRowScope(nsIMdbEnv* ev, // sets pos to -1
+ mdb_scope inRowScope) override;
+ NS_IMETHOD GetRowScope(nsIMdbEnv* ev, mdb_scope* outRowScope) override;
+ // setting row scope to zero iterates over all row scopes in port
+
+ NS_IMETHOD SetTableKind(nsIMdbEnv* ev, // sets pos to -1
+ mdb_kind inTableKind) override;
+ NS_IMETHOD GetTableKind(nsIMdbEnv* ev, mdb_kind* outTableKind) override;
+ // setting table kind to zero iterates over all table kinds in row scope
+ // } ----- end attribute methods -----
+
+ // { ----- begin table iteration methods -----
+ NS_IMETHOD NextTable( // get table at next position in the db
+ nsIMdbEnv* ev, // context
+ nsIMdbTable** acqTable) override; // the next table in the iteration
+ // } ----- end table iteration methods -----
+ morkStore* mPortTableCursor_Store; // weak ref to store
+
+ mdb_scope mPortTableCursor_RowScope;
+ mdb_kind mPortTableCursor_TableKind;
+
+ // We only care if LastTable is non-nil, so it is not refcounted;
+ // so you must never access table state or methods using LastTable:
+
+ morkTable* mPortTableCursor_LastTable; // nil or last table (no refcount)
+ morkRowSpace* mPortTableCursor_RowSpace; // current space (strong ref)
+
+ morkRowSpaceMapIter mPortTableCursor_SpaceIter; // iter over spaces
+ morkTableMapIter mPortTableCursor_TableIter; // iter over tables
+
+ // these booleans indicate when the table or space iterator is exhausted:
+
+ mork_bool mPortTableCursor_TablesDidEnd; // no more tables?
+ mork_bool mPortTableCursor_SpacesDidEnd; // no more spaces?
+ mork_u1 mPortTableCursor_Pad[ 2 ]; // for u4 alignment
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // ClosePortTableCursor()
+
+public: // morkPortTableCursor construction & destruction
+ morkPortTableCursor(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkStore* ioStore, mdb_scope inRowScope,
+ mdb_kind inTableKind, nsIMdbHeap* ioSlotHeap);
+ void ClosePortTableCursor(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkPortTableCursor(const morkPortTableCursor& other);
+ morkPortTableCursor& operator=(const morkPortTableCursor& other);
+
+public: // dynamic type identification
+ mork_bool IsPortTableCursor() const
+ { return IsNode() && mNode_Derived == morkDerived_kPortTableCursor; }
+// } ===== end morkNode methods =====
+
+protected: // utilities
+ virtual ~morkPortTableCursor(); // assert that close executed earlier
+
+ void init_space_tables_map(morkEnv* ev);
+
+public: // other cursor methods
+
+ static void NilCursorStoreError(morkEnv* ev);
+ static void NonPortTableCursorTypeError(morkEnv* ev);
+
+ morkEnv* CanUsePortTableCursor(nsIMdbEnv* mev, mork_bool inMutable,
+ nsresult* outErr) const;
+
+
+ morkRowSpace* NextSpace(morkEnv* ev);
+ morkTable* NextTable(morkEnv* ev);
+
+ mork_bool SetRowScope(morkEnv* ev, mork_scope inRowScope);
+ mork_bool SetTableKind(morkEnv* ev, mork_kind inTableKind);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakPortTableCursor(morkPortTableCursor* me,
+ morkEnv* ev, morkPortTableCursor** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongPortTableCursor(morkPortTableCursor* me,
+ morkEnv* ev, morkPortTableCursor** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKPORTTABLECURSOR_ */
diff --git a/components/mork/src/morkProbeMap.cpp b/components/mork/src/morkProbeMap.cpp
new file mode 100644
index 000000000..c2a9d4645
--- /dev/null
+++ b/components/mork/src/morkProbeMap.cpp
@@ -0,0 +1,1234 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// This code is a port to NS Mork from public domain Mithril C++ sources.
+// Note many code comments here come verbatim from cut-and-pasted Mithril.
+// In many places, code is identical; Mithril versions stay public domain.
+// Changes in porting are mainly class type and scalar type name changes.
+
+#include "nscore.h"
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKPROBEMAP_
+#include "morkProbeMap.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+/*============================================================================*/
+/* morkMapScratch */
+
+void morkMapScratch::halt_map_scratch(morkEnv* ev)
+{
+ nsIMdbHeap* heap = sMapScratch_Heap;
+
+ if ( heap )
+ {
+ if ( sMapScratch_Keys )
+ heap->Free(ev->AsMdbEnv(), sMapScratch_Keys);
+ if ( sMapScratch_Vals )
+ heap->Free(ev->AsMdbEnv(), sMapScratch_Vals);
+ }
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+/*============================================================================*/
+/* morkProbeMap */
+
+void morkProbeMap::ProbeMapBadTagError(morkEnv* ev) const
+{
+ ev->NewError("bad sProbeMap_Tag");
+}
+
+void morkProbeMap::WrapWithNoVoidSlotError(morkEnv* ev) const
+{
+ ev->NewError("wrap without void morkProbeMap slot");
+}
+
+void morkProbeMap::GrowFailsMaxFillError(morkEnv* ev) const
+{
+ ev->NewError("grow fails morkEnv > sMap_Fill");
+}
+
+void morkProbeMap::MapKeyIsNotIPError(morkEnv* ev) const
+{
+ ev->NewError("not sMap_KeyIsIP");
+}
+
+void morkProbeMap::MapValIsNotIPError(morkEnv* ev) const
+{
+ ev->NewError("not sMap_ValIsIP");
+}
+
+void morkProbeMap::rehash_old_map(morkEnv* ev, morkMapScratch* ioScratch)
+{
+ mork_size keySize = sMap_KeySize; // size of every key bucket
+ mork_size valSize = sMap_ValSize; // size of every associated value
+
+ mork_count slots = sMap_Slots; // number of new buckets
+ mork_u1* keys = sMap_Keys; // destination for rehashed keys
+ mork_u1* vals = sMap_Vals; // destination for any copied values
+
+ mork_bool keyIsIP = ( keys && keySize == sizeof(mork_ip) && sMap_KeyIsIP );
+ mork_bool valIsIP = ( vals && valSize == sizeof(mork_ip) && sMap_ValIsIP );
+
+ mork_count oldSlots = ioScratch->sMapScratch_Slots; // sMap_Slots
+ mork_u1* oldKeys = ioScratch->sMapScratch_Keys; // sMap_Keys
+ mork_u1* oldVals = ioScratch->sMapScratch_Vals; // sMap_Vals
+ mork_u1* end = oldKeys + (keySize * oldSlots); // one byte past last key
+
+ mork_fill fill = 0; // let's count the actual fill for a double check
+
+ while ( oldKeys < end ) // another old key bucket to rehash if non-nil?
+ {
+ if ( !this->ProbeMapIsKeyNil(ev, oldKeys) ) // need to rehash?
+ {
+ ++fill; // this had better match sMap_Fill when we are all done
+ mork_u4 hash = this->ProbeMapHashMapKey(ev, oldKeys);
+
+ mork_pos i = hash % slots; // target hash bucket
+ mork_pos startPos = i; // remember start to detect
+
+ mork_u1* k = keys + (i * keySize);
+ while ( !this->ProbeMapIsKeyNil(ev, k) )
+ {
+ if ( ++i >= (mork_pos)slots ) // advanced past end? need to wrap around now?
+ i = 0; // wrap around to first slot in map's hash table
+
+ if ( i == startPos ) // no void slots were found anywhere in map?
+ {
+ this->WrapWithNoVoidSlotError(ev); // should never happen
+ return; // this is bad, and we can't go on with the rehash
+ }
+ k = keys + (i * keySize);
+ }
+ if ( keyIsIP ) // int special case?
+ *((mork_ip*) k) = *((const mork_ip*) oldKeys); // fast bitwise copy
+ else
+ MORK_MEMCPY(k, oldKeys, keySize); // slow bitwise copy
+
+ if ( oldVals ) // need to copy values as well?
+ {
+ mork_size valOffset = (i * valSize);
+ mork_u1* v = vals + valOffset;
+ mork_u1* ov = oldVals + valOffset;
+ if ( valIsIP ) // int special case?
+ *((mork_ip*) v) = *((const mork_ip*) ov); // fast bitwise copy
+ else
+ MORK_MEMCPY(v, ov, valSize); // slow bitwise copy
+ }
+ }
+ oldKeys += keySize; // advance to next key bucket in old map
+ }
+ if ( fill != sMap_Fill ) // is the recorded value of sMap_Fill wrong?
+ {
+ ev->NewWarning("fill != sMap_Fill");
+ sMap_Fill = fill;
+ }
+}
+
+mork_bool morkProbeMap::grow_probe_map(morkEnv* ev)
+{
+ if ( sMap_Heap ) // can we grow the map?
+ {
+ mork_num newSlots = ((sMap_Slots * 4) / 3) + 1; // +25%
+ morkMapScratch old; // a place to temporarily hold all the old arrays
+ if ( this->new_slots(ev, &old, newSlots) ) // have more?
+ {
+ ++sMap_Seed; // note the map has changed
+ this->rehash_old_map(ev, &old);
+
+ if ( ev->Good() )
+ {
+ mork_count slots = sMap_Slots;
+ mork_num emptyReserve = (slots / 7) + 1; // keep this many empty
+ mork_fill maxFill = slots - emptyReserve; // new max occupancy
+ if ( maxFill > sMap_Fill ) // new max is bigger than old occupancy?
+ sProbeMap_MaxFill = maxFill; // we can install new max for fill
+ else
+ this->GrowFailsMaxFillError(ev); // we have invariant failure
+ }
+
+ if ( ev->Bad() ) // rehash failed? need to revert map to last state?
+ this->revert_map(ev, &old); // swap the vectors back again
+
+ old.halt_map_scratch(ev); // remember to free the old arrays
+ }
+ }
+ else ev->OutOfMemoryError();
+
+ return ev->Good();
+}
+
+void morkProbeMap::revert_map(morkEnv* ev, morkMapScratch* ioScratch)
+{
+ mork_count tempSlots = ioScratch->sMapScratch_Slots; // sMap_Slots
+ mork_u1* tempKeys = ioScratch->sMapScratch_Keys; // sMap_Keys
+ mork_u1* tempVals = ioScratch->sMapScratch_Vals; // sMap_Vals
+
+ ioScratch->sMapScratch_Slots = sMap_Slots;
+ ioScratch->sMapScratch_Keys = sMap_Keys;
+ ioScratch->sMapScratch_Vals = sMap_Vals;
+
+ sMap_Slots = tempSlots;
+ sMap_Keys = tempKeys;
+ sMap_Vals = tempVals;
+}
+
+void morkProbeMap::put_probe_kv(morkEnv* ev,
+ const void* inAppKey, const void* inAppVal, mork_pos inPos)
+{
+ mork_u1* mapVal = 0;
+ mork_u1* mapKey = 0;
+
+ mork_num valSize = sMap_ValSize;
+ if ( valSize && inAppVal ) // map holds values? caller sends value?
+ {
+ mork_u1* val = sMap_Vals + (valSize * inPos);
+ if ( valSize == sizeof(mork_ip) && sMap_ValIsIP ) // int special case?
+ *((mork_ip*) val) = *((const mork_ip*) inAppVal);
+ else
+ mapVal = val; // show possible need to call ProbeMapPushIn()
+ }
+ if ( inAppKey ) // caller sends the key?
+ {
+ mork_num keySize = sMap_KeySize;
+ mork_u1* key = sMap_Keys + (keySize * inPos);
+ if ( keySize == sizeof(mork_ip) && sMap_KeyIsIP ) // int special case?
+ *((mork_ip*) key) = *((const mork_ip*) inAppKey);
+ else
+ mapKey = key; // show possible need to call ProbeMapPushIn()
+ }
+ else
+ ev->NilPointerError();
+
+ if ( ( inAppVal && mapVal ) || ( inAppKey && mapKey ) )
+ this->ProbeMapPushIn(ev, inAppKey, inAppVal, mapKey, mapVal);
+
+ if ( sMap_Fill > sProbeMap_MaxFill )
+ this->grow_probe_map(ev);
+}
+
+void morkProbeMap::get_probe_kv(morkEnv* ev,
+ void* outAppKey, void* outAppVal, mork_pos inPos) const
+{
+ const mork_u1* mapVal = 0;
+ const mork_u1* mapKey = 0;
+
+ mork_num valSize = sMap_ValSize;
+ if ( valSize && outAppVal ) // map holds values? caller wants value?
+ {
+ const mork_u1* val = sMap_Vals + (valSize * inPos);
+ if ( valSize == sizeof(mork_ip) && sMap_ValIsIP ) // int special case?
+ *((mork_ip*) outAppVal) = *((const mork_ip*) val);
+ else
+ mapVal = val; // show possible need to call ProbeMapPullOut()
+ }
+ if ( outAppKey ) // caller wants the key?
+ {
+ mork_num keySize = sMap_KeySize;
+ const mork_u1* key = sMap_Keys + (keySize * inPos);
+ if ( keySize == sizeof(mork_ip) && sMap_KeyIsIP ) // int special case?
+ *((mork_ip*) outAppKey) = *((const mork_ip*) key);
+ else
+ mapKey = key; // show possible need to call ProbeMapPullOut()
+ }
+ if ( ( outAppVal && mapVal ) || ( outAppKey && mapKey ) )
+ this->ProbeMapPullOut(ev, mapKey, mapVal, outAppKey, outAppVal);
+}
+
+mork_test
+morkProbeMap::find_key_pos(morkEnv* ev, const void* inAppKey,
+ mork_u4 inHash, mork_pos* outPos) const
+{
+ mork_u1* k = sMap_Keys; // array of keys, each of size sMap_KeySize
+ mork_num size = sMap_KeySize; // number of bytes in each key
+ mork_count slots = sMap_Slots; // total number of key buckets
+ mork_pos i = inHash % slots; // target hash bucket
+ mork_pos startPos = i; // remember start to detect
+
+ mork_test outTest = this->MapTest(ev, k + (i * size), inAppKey);
+ while ( outTest == morkTest_kMiss )
+ {
+ if ( ++i >= (mork_pos)slots ) // advancing goes beyond end? need to wrap around now?
+ i = 0; // wrap around to first slot in map's hash table
+
+ if ( i == startPos ) // no void slots were found anywhere in map?
+ {
+ this->WrapWithNoVoidSlotError(ev); // should never happen
+ break; // end loop on kMiss; note caller expects either kVoid or kHit
+ }
+ outTest = this->MapTest(ev, k + (i * size), inAppKey);
+ }
+ *outPos = i;
+
+ return outTest;
+}
+
+void morkProbeMap::probe_map_lazy_init(morkEnv* ev)
+{
+ if ( this->need_lazy_init() && sMap_Fill == 0 ) // pending lazy action?
+ {
+ // The constructor cannot successfully call virtual ProbeMapClearKey(),
+ // so we lazily do so now, when we add the first member to the map.
+
+ mork_u1* keys = sMap_Keys;
+ if ( keys ) // okay to call lazy virtual clear method on new map keys?
+ {
+ if ( sProbeMap_ZeroIsClearKey ) // zero is good enough to clear keys?
+ {
+ mork_num keyVolume = sMap_Slots * sMap_KeySize;
+ if ( keyVolume )
+ MORK_MEMSET(keys, 0, keyVolume);
+ }
+ else
+ this->ProbeMapClearKey(ev, keys, sMap_Slots);
+ }
+ else
+ this->MapNilKeysError(ev);
+ }
+ sProbeMap_LazyClearOnAdd = 0; // don't do this ever again
+}
+
+mork_bool
+morkProbeMap::MapAtPut(morkEnv* ev,
+ const void* inAppKey, const void* inAppVal,
+ void* outAppKey, void* outAppVal)
+{
+ mork_bool outPut = morkBool_kFalse;
+
+ if ( this->GoodProbeMap() ) /* looks good? */
+ {
+ if ( this->need_lazy_init() && sMap_Fill == 0 ) // pending lazy action?
+ this->probe_map_lazy_init(ev);
+
+ if ( ev->Good() )
+ {
+ mork_pos slotPos = 0;
+ mork_u4 hash = this->MapHash(ev, inAppKey);
+ mork_test test = this->find_key_pos(ev, inAppKey, hash, &slotPos);
+ outPut = ( test == morkTest_kHit );
+
+ if ( outPut ) // replacing an old assoc? no change in member count?
+ {
+ if ( outAppKey || outAppVal ) /* copy old before cobber? */
+ this->get_probe_kv(ev, outAppKey, outAppVal, slotPos);
+ }
+ else // adding a new assoc increases membership by one
+ {
+ ++sMap_Fill; /* one more member in the collection */
+ }
+
+ if ( test != morkTest_kMiss ) /* found slot to hold new assoc? */
+ {
+ ++sMap_Seed; /* note the map has changed */
+ this->put_probe_kv(ev, inAppKey, inAppVal, slotPos);
+ }
+ }
+ }
+ else this->ProbeMapBadTagError(ev);
+
+ return outPut;
+}
+
+mork_bool
+morkProbeMap::MapAt(morkEnv* ev, const void* inAppKey,
+ void* outAppKey, void* outAppVal)
+{
+ if ( this->GoodProbeMap() ) /* looks good? */
+ {
+ if ( this->need_lazy_init() && sMap_Fill == 0 ) // pending lazy action?
+ this->probe_map_lazy_init(ev);
+
+ mork_pos slotPos = 0;
+ mork_u4 hash = this->MapHash(ev, inAppKey);
+ mork_test test = this->find_key_pos(ev, inAppKey, hash, &slotPos);
+ if ( test == morkTest_kHit ) /* found an assoc pair for inAppKey? */
+ {
+ this->get_probe_kv(ev, outAppKey, outAppVal, slotPos);
+ return morkBool_kTrue;
+ }
+ }
+ else this->ProbeMapBadTagError(ev);
+
+ return morkBool_kFalse;
+}
+
+mork_num
+morkProbeMap::MapCutAll(morkEnv* ev)
+{
+ mork_num outCutAll = 0;
+
+ if ( this->GoodProbeMap() ) /* looks good? */
+ {
+ outCutAll = sMap_Fill; /* number of members cut, which is all of them */
+
+ if ( sMap_Keys && !sProbeMap_ZeroIsClearKey )
+ this->ProbeMapClearKey(ev, sMap_Keys, sMap_Slots);
+
+ sMap_Fill = 0; /* map now has no members */
+ }
+ else this->ProbeMapBadTagError(ev);
+
+ return outCutAll;
+}
+
+// { ===== node interface =====
+
+/*virtual*/
+morkProbeMap::~morkProbeMap() // assert NodeStop() finished earlier
+{
+ MORK_ASSERT(sMap_Keys==0);
+ MORK_ASSERT(sProbeMap_Tag==0);
+}
+
+/*public virtual*/ void
+morkProbeMap::CloseMorkNode(morkEnv* ev) // CloseMap() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseProbeMap(ev);
+ this->MarkShut();
+ }
+}
+
+void morkProbeMap::CloseProbeMap(morkEnv* ev)
+{
+ if ( this->IsNode() )
+ {
+ nsIMdbHeap* heap = sMap_Heap;
+ if ( heap ) // able to free map arrays?
+ {
+ void* block = sMap_Keys;
+ if ( block )
+ {
+ heap->Free(ev->AsMdbEnv(), block);
+ sMap_Keys = 0;
+ }
+
+ block = sMap_Vals;
+ if ( block )
+ {
+ heap->Free(ev->AsMdbEnv(), block);
+ sMap_Vals = 0;
+ }
+ }
+ sMap_Keys = 0;
+ sMap_Vals = 0;
+
+ this->CloseNode(ev);
+ sProbeMap_Tag = 0;
+ sProbeMap_MaxFill = 0;
+
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+void*
+morkProbeMap::clear_alloc(morkEnv* ev, mork_size inSize)
+{
+ void* p = 0;
+ nsIMdbHeap* heap = sMap_Heap;
+ if ( heap )
+ {
+ if (NS_SUCCEEDED(heap->Alloc(ev->AsMdbEnv(), inSize, (void**) &p)) && p )
+ {
+ MORK_MEMSET(p, 0, inSize);
+ return p;
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ return (void*) 0;
+}
+
+/*| map_new_keys: allocate an array of inSlots new keys filled with zero.
+**| (cf IronDoc's FeHashTable_new_keys())
+|*/
+mork_u1*
+morkProbeMap::map_new_keys(morkEnv* ev, mork_num inSlots)
+{
+ mork_num size = inSlots * sMap_KeySize;
+ return (mork_u1*) this->clear_alloc(ev, size);
+}
+
+/*| map_new_vals: allocate an array of inSlots new values filled with zero.
+**| When values are zero sized, we just return a null pointer.
+**|
+**| (cf IronDoc's FeHashTable_new_values())
+|*/
+mork_u1*
+morkProbeMap::map_new_vals(morkEnv* ev, mork_num inSlots)
+{
+ mork_u1* values = 0;
+ mork_num size = inSlots * sMap_ValSize;
+ if ( size )
+ values = (mork_u1*) this->clear_alloc(ev, size);
+ return values;
+}
+
+
+void morkProbeMap::MapSeedOutOfSyncError(morkEnv* ev)
+{
+ ev->NewError("sMap_Seed out of sync");
+}
+
+void morkProbeMap::MapFillUnderflowWarning(morkEnv* ev)
+{
+ ev->NewWarning("sMap_Fill underflow");
+}
+
+void morkProbeMap::MapNilKeysError(morkEnv* ev)
+{
+ ev->NewError("nil sMap_Keys");
+}
+
+void morkProbeMap::MapZeroKeySizeError(morkEnv* ev)
+{
+ ev->NewError("zero sMap_KeySize");
+}
+
+/*static*/
+void morkProbeMap::ProbeMapCutError(morkEnv* ev)
+{
+ ev->NewError("morkProbeMap cannot cut");
+}
+
+
+void morkProbeMap::init_probe_map(morkEnv* ev, mork_size inSlots)
+{
+ // Note we cannot successfully call virtual ProbeMapClearKey() when we
+ // call init_probe_map() inside the constructor; so we leave this problem
+ // to the caller. (The constructor will call ProbeMapClearKey() later
+ // after setting a suitable lazy flag to show this action is pending.)
+
+ if ( ev->Good() )
+ {
+ morkMapScratch old;
+
+ if ( inSlots < 7 ) // capacity too small?
+ inSlots = 7; // increase to reasonable minimum
+ else if ( inSlots > (128 * 1024) ) // requested capacity too big?
+ inSlots = (128 * 1024); // decrease to reasonable maximum
+
+ if ( this->new_slots(ev, &old, inSlots) )
+ sProbeMap_Tag = morkProbeMap_kTag;
+
+ mork_count slots = sMap_Slots;
+ mork_num emptyReserve = (slots / 7) + 1; // keep this many empty
+ sProbeMap_MaxFill = slots - emptyReserve;
+
+ MORK_MEMSET(&old, 0, sizeof(morkMapScratch)); // don't bother halting
+ }
+}
+
+mork_bool
+morkProbeMap::new_slots(morkEnv* ev, morkMapScratch* old, mork_num inSlots)
+{
+ mork_bool outNew = morkBool_kFalse;
+
+ // Note we cannot successfully call virtual ProbeMapClearKey() when we
+ // call new_slots() inside the constructor; so we leave this problem
+ // to the caller. (The constructor will call ProbeMapClearKey() later
+ // after setting a suitable lazy flag to show this action is pending.)
+
+ // allocate every new array before we continue:
+ mork_u1* newKeys = this->map_new_keys(ev, inSlots);
+ mork_u1* newVals = this->map_new_vals(ev, inSlots);
+
+ // okay for newVals to be null when values are zero sized?
+ mork_bool okayValues = ( newVals || !sMap_ValSize );
+
+ if ( newKeys && okayValues )
+ {
+ outNew = morkBool_kTrue; // we created every array needed
+
+ // init mapScratch using slots from current map:
+ old->sMapScratch_Heap = sMap_Heap;
+
+ old->sMapScratch_Slots = sMap_Slots;
+ old->sMapScratch_Keys = sMap_Keys;
+ old->sMapScratch_Vals = sMap_Vals;
+
+ // replace all map array slots using the newly allocated members:
+ ++sMap_Seed; // the map has changed
+ sMap_Keys = newKeys;
+ sMap_Vals = newVals;
+ sMap_Slots = inSlots;
+ }
+ else // free any allocations if only partially successful
+ {
+ nsIMdbHeap* heap = sMap_Heap;
+ if ( newKeys )
+ heap->Free(ev->AsMdbEnv(), newKeys);
+ if ( newVals )
+ heap->Free(ev->AsMdbEnv(), newVals);
+
+ MORK_MEMSET(old, 0, sizeof(morkMapScratch)); // zap scratch space
+ }
+
+ return outNew;
+}
+
+void
+morkProbeMap::clear_probe_map(morkEnv* ev, nsIMdbHeap* ioMapHeap)
+{
+ sProbeMap_Tag = 0;
+ sMap_Seed = 0;
+ sMap_Slots = 0;
+ sMap_Fill = 0;
+ sMap_Keys = 0;
+ sMap_Vals = 0;
+ sProbeMap_MaxFill = 0;
+
+ sMap_Heap = ioMapHeap;
+ if ( !ioMapHeap )
+ ev->NilPointerError();
+}
+
+morkProbeMap::morkProbeMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioNodeHeap,
+ mork_size inKeySize, mork_size inValSize,
+ nsIMdbHeap* ioMapHeap, mork_size inSlots,
+ mork_bool inZeroIsClearKey)
+
+: morkNode(ev, inUsage, ioNodeHeap)
+, sMap_Heap( ioMapHeap )
+
+, sMap_Keys( 0 )
+, sMap_Vals( 0 )
+
+, sMap_Seed( 0 ) // change count of members or structure
+
+, sMap_Slots( 0 ) // count of slots in the hash table
+, sMap_Fill( 0 ) // number of used slots in the hash table
+
+, sMap_KeySize( 0 ) // size of each key (cannot be zero)
+, sMap_ValSize( 0 ) // size of each val (zero allowed)
+
+, sMap_KeyIsIP( morkBool_kFalse ) // sMap_KeySize == sizeof(mork_ip)
+, sMap_ValIsIP( morkBool_kFalse ) // sMap_ValSize == sizeof(mork_ip)
+
+, sProbeMap_MaxFill( 0 )
+, sProbeMap_LazyClearOnAdd( 0 )
+, sProbeMap_ZeroIsClearKey( inZeroIsClearKey )
+, sProbeMap_Tag( 0 )
+{
+ // Note we cannot successfully call virtual ProbeMapClearKey() when we
+ // call init_probe_map() inside the constructor; so we leave this problem
+ // to the caller. (The constructor will call ProbeMapClearKey() later
+ // after setting a suitable lazy flag to show this action is pending.)
+
+ if ( ev->Good() )
+ {
+ this->clear_probe_map(ev, ioMapHeap);
+ if ( ev->Good() )
+ {
+ sMap_KeySize = inKeySize;
+ sMap_ValSize = inValSize;
+ sMap_KeyIsIP = ( inKeySize == sizeof(mork_ip) );
+ sMap_ValIsIP = ( inValSize == sizeof(mork_ip) );
+
+ this->init_probe_map(ev, inSlots);
+ if ( ev->Good() )
+ {
+ if ( !inZeroIsClearKey ) // must lazy clear later with virtual method?
+ sProbeMap_LazyClearOnAdd = morkProbeMap_kLazyClearOnAdd;
+
+ mNode_Derived = morkDerived_kProbeMap;
+ }
+ }
+ }
+}
+
+/*============================================================================*/
+
+/*virtual*/ mork_test // hit(a,b) implies hash(a) == hash(b)
+morkProbeMap::MapTest(morkEnv* ev,
+ const void* inMapKey, const void* inAppKey) const
+ // Note inMapKey is always a key already stored in the map, while inAppKey
+ // is always a method argument parameter from a client method call.
+ // This matters the most in morkProbeMap subclasses, which have the
+ // responsibility of putting 'app' keys into slots for 'map' keys, and
+ // the bit pattern representation might be different in such cases.
+ // morkTest_kHit means that inMapKey equals inAppKey (and this had better
+ // also imply that hash(inMapKey) == hash(inAppKey)).
+ // morkTest_kMiss means that inMapKey does NOT equal inAppKey (but this
+ // implies nothing at all about hash(inMapKey) and hash(inAppKey)).
+ // morkTest_kVoid means that inMapKey is not a valid key bit pattern,
+ // which means that key slot in the map is not being used. Note that
+ // kVoid is only expected as a return value in morkProbeMap subclasses,
+ // because morkProbeMap must ask whether a key slot is used or not.
+ // morkChainMap however, always knows when a key slot is used, so only
+ // key slots expected to have valid bit patterns will be presented to
+ // the MapTest() methods for morkChainMap subclasses.
+ //
+ // NOTE: it is very important that subclasses correctly return the value
+ // morkTest_kVoid whenever the slot for inMapKey contains a bit pattern
+ // that means the slot is not being used, because this is the only way a
+ // probe map can terminate an unsuccessful search for a key in the map.
+{
+ mork_size keySize = sMap_KeySize;
+ if ( keySize == sizeof(mork_ip) && sMap_KeyIsIP )
+ {
+ mork_ip mapKey = *((const mork_ip*) inMapKey);
+ if ( mapKey == *((const mork_ip*) inAppKey) )
+ return morkTest_kHit;
+ else
+ {
+ return ( mapKey )? morkTest_kMiss : morkTest_kVoid;
+ }
+ }
+ else
+ {
+ mork_bool allSame = morkBool_kTrue;
+ mork_bool allZero = morkBool_kTrue;
+ const mork_u1* ak = (const mork_u1*) inAppKey;
+ const mork_u1* mk = (const mork_u1*) inMapKey;
+ const mork_u1* end = mk + keySize;
+ --mk; // prepare for preincrement:
+ while ( ++mk < end )
+ {
+ mork_u1 byte = *mk;
+ if ( byte ) // any nonzero byte in map key means slot is not nil?
+ allZero = morkBool_kFalse;
+ if ( byte != *ak++ ) // bytes differ in map and app keys?
+ allSame = morkBool_kFalse;
+ }
+ if ( allSame )
+ return morkTest_kHit;
+ else
+ return ( allZero )? morkTest_kVoid : morkTest_kMiss;
+ }
+}
+
+/*virtual*/ mork_u4 // hit(a,b) implies hash(a) == hash(b)
+morkProbeMap::MapHash(morkEnv* ev, const void* inAppKey) const
+{
+ mork_size keySize = sMap_KeySize;
+ if ( keySize == sizeof(mork_ip) && sMap_KeyIsIP )
+ {
+ return *((const mork_ip*) inAppKey);
+ }
+ else
+ {
+ const mork_u1* key = (const mork_u1*) inAppKey;
+ const mork_u1* end = key + keySize;
+ --key; // prepare for preincrement:
+ while ( ++key < end )
+ {
+ if ( *key ) // any nonzero byte in map key means slot is not nil?
+ return morkBool_kFalse;
+ }
+ return morkBool_kTrue;
+ }
+ return (mork_u4) NS_PTR_TO_INT32(inAppKey);
+}
+
+
+/*============================================================================*/
+
+/*virtual*/ mork_u4
+morkProbeMap::ProbeMapHashMapKey(morkEnv* ev, const void* inMapKey) const
+ // ProbeMapHashMapKey() does logically the same thing as MapHash(), and
+ // the default implementation actually calls virtual MapHash(). However,
+ // Subclasses must override this method whenever the formats of keys in
+ // the map differ from app keys outside the map, because MapHash() only
+ // works on keys in 'app' format, while ProbeMapHashMapKey() only works
+ // on keys in 'map' format. This method is called in order to rehash all
+ // map keys when a map is grown, and this causes all old map members to
+ // move into new slot locations.
+ //
+ // Note it is absolutely imperative that a hash for a key in 'map' format
+ // be exactly the same the hash of the same key in 'app' format, or else
+ // maps will seem corrupt later when keys in 'app' format cannot be found.
+{
+ return this->MapHash(ev, inMapKey);
+}
+
+/*virtual*/ mork_bool
+morkProbeMap::ProbeMapIsKeyNil(morkEnv* ev, void* ioMapKey)
+ // ProbeMapIsKeyNil() must say whether the representation of logical 'nil'
+ // is currently found inside the key at ioMapKey, for a key found within
+ // the map. The the map iterator uses this method to find map keys that
+ // are actually being used for valid map associations; otherwise the
+ // iterator cannot determine which map slots actually denote used keys.
+ // The default method version returns true if all the bits equal zero.
+{
+ if ( sMap_KeySize == sizeof(mork_ip) && sMap_KeyIsIP )
+ {
+ return !*((const mork_ip*) ioMapKey);
+ }
+ else
+ {
+ const mork_u1* key = (const mork_u1*) ioMapKey;
+ const mork_u1* end = key + sMap_KeySize;
+ --key; // prepare for preincrement:
+ while ( ++key < end )
+ {
+ if ( *key ) // any nonzero byte in map key means slot is not nil?
+ return morkBool_kFalse;
+ }
+ return morkBool_kTrue;
+ }
+}
+
+/*virtual*/ void
+morkProbeMap::ProbeMapClearKey(morkEnv* ev, // put 'nil' alls keys inside map
+ void* ioMapKey, mork_count inKeyCount) // array of keys inside map
+ // ProbeMapClearKey() must put some representation of logical 'nil' into
+ // every key slot in the map, such that MapTest() will later recognize
+ // that this bit pattern shows each key slot is not actually being used.
+ //
+ // This method is typically called whenever the map is either created or
+ // grown into a larger size, where ioMapKey is a pointer to an array of
+ // inKeyCount keys, where each key is this->MapKeySize() bytes in size.
+ // Note that keys are assumed immediately adjacent with no padding, so
+ // if any alignment requirements must be met, then subclasses should have
+ // already accounted for this when specifying a key size in the map.
+ //
+ // Since this method will be called when a map is being grown in size,
+ // nothing should be assumed about the state slots of the map, since the
+ // ioMapKey array might not yet live in sMap_Keys, and the array length
+ // inKeyCount might not yet live in sMap_Slots. However, the value kept
+ // in sMap_KeySize never changes, so this->MapKeySize() is always correct.
+{
+ if ( ioMapKey && inKeyCount )
+ {
+ MORK_MEMSET(ioMapKey, 0, (inKeyCount * sMap_KeySize));
+ }
+ else
+ ev->NilPointerWarning();
+}
+
+/*virtual*/ void
+morkProbeMap::ProbeMapPushIn(morkEnv* ev, // move (key,val) into the map
+ const void* inAppKey, const void* inAppVal, // (key,val) outside map
+ void* outMapKey, void* outMapVal) // (key,val) inside map
+ // This method actually puts keys and vals in the map in suitable format.
+ //
+ // ProbeMapPushIn() must copy a caller key and value in 'app' format
+ // into the map slots provided, which are in 'map' format. When the
+ // 'app' and 'map' formats are identical, then this is just a bitwise
+ // copy of this->MapKeySize() key bytes and this->MapValSize() val bytes,
+ // and this is exactly what the default implementation performs. However,
+ // if 'app' and 'map' formats are different, and MapTest() depends on this
+ // difference in format, then subclasses must override this method to do
+ // whatever is necessary to store the input app key in output map format.
+ //
+ // Do NOT write more than this->MapKeySize() bytes of a map key, or more
+ // than this->MapValSize() bytes of a map val, or corruption might ensue.
+ //
+ // The inAppKey and inAppVal parameters are the same ones passed into a
+ // call to MapAtPut(), and the outMapKey and outMapVal parameters are ones
+ // determined by how the map currently positions key inAppKey in the map.
+ //
+ // Note any key or val parameter can be a null pointer, in which case
+ // this method must do nothing with those parameters. In particular, do
+ // no key move at all when either inAppKey or outMapKey is nil, and do
+ // no val move at all when either inAppVal or outMapVal is nil. Note that
+ // outMapVal should always be nil when this->MapValSize() is nil.
+{
+}
+
+/*virtual*/ void
+morkProbeMap::ProbeMapPullOut(morkEnv* ev, // move (key,val) out from the map
+ const void* inMapKey, const void* inMapVal, // (key,val) inside map
+ void* outAppKey, void* outAppVal) const // (key,val) outside map
+ // This method actually gets keys and vals from the map in suitable format.
+ //
+ // ProbeMapPullOut() must copy a key and val in 'map' format into the
+ // caller key and val slots provided, which are in 'app' format. When the
+ // 'app' and 'map' formats are identical, then this is just a bitwise
+ // copy of this->MapKeySize() key bytes and this->MapValSize() val bytes,
+ // and this is exactly what the default implementation performs. However,
+ // if 'app' and 'map' formats are different, and MapTest() depends on this
+ // difference in format, then subclasses must override this method to do
+ // whatever is necessary to store the input map key in output app format.
+ //
+ // The outAppKey and outAppVal parameters are the same ones passed into a
+ // call to either MapAtPut() or MapAt(), while inMapKey and inMapVal are
+ // determined by how the map currently positions the target key in the map.
+ //
+ // Note any key or val parameter can be a null pointer, in which case
+ // this method must do nothing with those parameters. In particular, do
+ // no key move at all when either inMapKey or outAppKey is nil, and do
+ // no val move at all when either inMapVal or outAppVal is nil. Note that
+ // inMapVal should always be nil when this->MapValSize() is nil.
+{
+}
+
+
+/*============================================================================*/
+/* morkProbeMapIter */
+
+morkProbeMapIter::morkProbeMapIter(morkEnv* ev, morkProbeMap* ioMap)
+: sProbeMapIter_Map( 0 )
+, sProbeMapIter_Seed( 0 )
+, sProbeMapIter_HereIx( morkProbeMapIter_kBeforeIx )
+{
+ if ( ioMap )
+ {
+ if ( ioMap->GoodProbeMap() )
+ {
+ if ( ioMap->need_lazy_init() ) // pending lazy action?
+ ioMap->probe_map_lazy_init(ev);
+
+ sProbeMapIter_Map = ioMap;
+ sProbeMapIter_Seed = ioMap->sMap_Seed;
+ }
+ else ioMap->ProbeMapBadTagError(ev);
+ }
+ else ev->NilPointerError();
+}
+
+void morkProbeMapIter::CloseMapIter(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ sProbeMapIter_Map = 0;
+ sProbeMapIter_Seed = 0;
+
+ sProbeMapIter_HereIx = morkProbeMapIter_kAfterIx;
+}
+
+morkProbeMapIter::morkProbeMapIter( )
+// zero most slots; caller must call InitProbeMapIter()
+{
+ sProbeMapIter_Map = 0;
+ sProbeMapIter_Seed = 0;
+
+ sProbeMapIter_HereIx = morkProbeMapIter_kBeforeIx;
+}
+
+void morkProbeMapIter::InitProbeMapIter(morkEnv* ev, morkProbeMap* ioMap)
+{
+ sProbeMapIter_Map = 0;
+ sProbeMapIter_Seed = 0;
+
+ sProbeMapIter_HereIx = morkProbeMapIter_kBeforeIx;
+
+ if ( ioMap )
+ {
+ if ( ioMap->GoodProbeMap() )
+ {
+ if ( ioMap->need_lazy_init() ) // pending lazy action?
+ ioMap->probe_map_lazy_init(ev);
+
+ sProbeMapIter_Map = ioMap;
+ sProbeMapIter_Seed = ioMap->sMap_Seed;
+ }
+ else ioMap->ProbeMapBadTagError(ev);
+ }
+ else ev->NilPointerError();
+}
+
+mork_bool morkProbeMapIter::IterFirst(morkEnv* ev,
+ void* outAppKey, void* outAppVal)
+{
+ sProbeMapIter_HereIx = morkProbeMapIter_kAfterIx; // default to done
+ morkProbeMap* map = sProbeMapIter_Map;
+
+ if ( map && map->GoodProbeMap() ) /* looks good? */
+ {
+ sProbeMapIter_Seed = map->sMap_Seed; /* sync the seeds */
+
+ mork_u1* k = map->sMap_Keys; // array of keys, each of size sMap_KeySize
+ mork_num size = map->sMap_KeySize; // number of bytes in each key
+ mork_count slots = map->sMap_Slots; // total number of key buckets
+ mork_pos here = 0; // first hash bucket
+
+ while ( here < (mork_pos)slots )
+ {
+ if ( !map->ProbeMapIsKeyNil(ev, k + (here * size)) )
+ {
+ map->get_probe_kv(ev, outAppKey, outAppVal, here);
+
+ sProbeMapIter_HereIx = (mork_i4) here;
+ return morkBool_kTrue;
+ }
+ ++here; // next bucket
+ }
+ }
+ else map->ProbeMapBadTagError(ev);
+
+ return morkBool_kFalse;
+}
+
+mork_bool morkProbeMapIter::IterNext(morkEnv* ev,
+ void* outAppKey, void* outAppVal)
+{
+ morkProbeMap* map = sProbeMapIter_Map;
+
+ if ( map && map->GoodProbeMap() ) /* looks good? */
+ {
+ if ( sProbeMapIter_Seed == map->sMap_Seed ) /* in sync? */
+ {
+ if ( sProbeMapIter_HereIx != morkProbeMapIter_kAfterIx )
+ {
+ mork_pos here = (mork_pos) sProbeMapIter_HereIx;
+ if ( sProbeMapIter_HereIx < 0 )
+ here = 0;
+ else
+ ++here;
+
+ sProbeMapIter_HereIx = morkProbeMapIter_kAfterIx; // default to done
+
+ mork_u1* k = map->sMap_Keys; // key array, each of size sMap_KeySize
+ mork_num size = map->sMap_KeySize; // number of bytes in each key
+ mork_count slots = map->sMap_Slots; // total number of key buckets
+
+ while ( here < (mork_pos)slots )
+ {
+ if ( !map->ProbeMapIsKeyNil(ev, k + (here * size)) )
+ {
+ map->get_probe_kv(ev, outAppKey, outAppVal, here);
+
+ sProbeMapIter_HereIx = (mork_i4) here;
+ return morkBool_kTrue;
+ }
+ ++here; // next bucket
+ }
+ }
+ }
+ else map->MapSeedOutOfSyncError(ev);
+ }
+ else map->ProbeMapBadTagError(ev);
+
+ return morkBool_kFalse;
+}
+
+mork_bool morkProbeMapIter::IterHere(morkEnv* ev,
+ void* outAppKey, void* outAppVal)
+{
+ morkProbeMap* map = sProbeMapIter_Map;
+
+ if ( map && map->GoodProbeMap() ) /* looks good? */
+ {
+ if ( sProbeMapIter_Seed == map->sMap_Seed ) /* in sync? */
+ {
+ mork_pos here = (mork_pos) sProbeMapIter_HereIx;
+ mork_count slots = map->sMap_Slots; // total number of key buckets
+ if ( sProbeMapIter_HereIx >= 0 && (here < (mork_pos)slots))
+ {
+ mork_u1* k = map->sMap_Keys; // key array, each of size sMap_KeySize
+ mork_num size = map->sMap_KeySize; // number of bytes in each key
+
+ if ( !map->ProbeMapIsKeyNil(ev, k + (here * size)) )
+ {
+ map->get_probe_kv(ev, outAppKey, outAppVal, here);
+ return morkBool_kTrue;
+ }
+ }
+ }
+ else map->MapSeedOutOfSyncError(ev);
+ }
+ else map->ProbeMapBadTagError(ev);
+
+ return morkBool_kFalse;
+}
+
+mork_change*
+morkProbeMapIter::First(morkEnv* ev, void* outKey, void* outVal)
+{
+ if ( this->IterFirst(ev, outKey, outVal) )
+ return &sProbeMapIter_Change;
+
+ return (mork_change*) 0;
+}
+
+mork_change*
+morkProbeMapIter::Next(morkEnv* ev, void* outKey, void* outVal)
+{
+ if ( this->IterNext(ev, outKey, outVal) )
+ return &sProbeMapIter_Change;
+
+ return (mork_change*) 0;
+}
+
+mork_change*
+morkProbeMapIter::Here(morkEnv* ev, void* outKey, void* outVal)
+{
+ if ( this->IterHere(ev, outKey, outVal) )
+ return &sProbeMapIter_Change;
+
+ return (mork_change*) 0;
+}
+
+mork_change*
+morkProbeMapIter::CutHere(morkEnv* ev, void* outKey, void* outVal)
+{
+ morkProbeMap::ProbeMapCutError(ev);
+
+ return (mork_change*) 0;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// NOTE: the following methods ONLY work for sMap_ValIsIP pointer values.
+// (Note the implied assumption that zero is never a good value pattern.)
+
+void* morkProbeMapIter::IterFirstVal(morkEnv* ev, void* outKey)
+// equivalent to { void* v=0; this->IterFirst(ev, outKey, &v); return v; }
+{
+ morkProbeMap* map = sProbeMapIter_Map;
+ if ( map )
+ {
+ if ( map->sMap_ValIsIP )
+ {
+ void* v = 0;
+ this->IterFirst(ev, outKey, &v);
+ return v;
+ }
+ else
+ map->MapValIsNotIPError(ev);
+ }
+ return (void*) 0;
+}
+
+void* morkProbeMapIter::IterNextVal(morkEnv* ev, void* outKey)
+// equivalent to { void* v=0; this->IterNext(ev, outKey, &v); return v; }
+{
+ morkProbeMap* map = sProbeMapIter_Map;
+ if ( map )
+ {
+ if ( map->sMap_ValIsIP )
+ {
+ void* v = 0;
+ this->IterNext(ev, outKey, &v);
+ return v;
+ }
+ else
+ map->MapValIsNotIPError(ev);
+ }
+ return (void*) 0;
+}
+
+void* morkProbeMapIter::IterHereVal(morkEnv* ev, void* outKey)
+// equivalent to { void* v=0; this->IterHere(ev, outKey, &v); return v; }
+{
+ morkProbeMap* map = sProbeMapIter_Map;
+ if ( map )
+ {
+ if ( map->sMap_ValIsIP )
+ {
+ void* v = 0;
+ this->IterHere(ev, outKey, &v);
+ return v;
+ }
+ else
+ map->MapValIsNotIPError(ev);
+ }
+ return (void*) 0;
+}
+
+// NOTE: the following methods ONLY work for sMap_KeyIsIP pointer values.
+// (Note the implied assumption that zero is never a good key pattern.)
+
+void* morkProbeMapIter::IterFirstKey(morkEnv* ev)
+// equivalent to { void* k=0; this->IterFirst(ev, &k, 0); return k; }
+{
+ morkProbeMap* map = sProbeMapIter_Map;
+ if ( map )
+ {
+ if ( map->sMap_KeyIsIP )
+ {
+ void* k = 0;
+ this->IterFirst(ev, &k, (void*) 0);
+ return k;
+ }
+ else
+ map->MapKeyIsNotIPError(ev);
+ }
+ return (void*) 0;
+}
+
+void* morkProbeMapIter::IterNextKey(morkEnv* ev)
+// equivalent to { void* k=0; this->IterNext(ev, &k, 0); return k; }
+{
+ morkProbeMap* map = sProbeMapIter_Map;
+ if ( map )
+ {
+ if ( map->sMap_KeyIsIP )
+ {
+ void* k = 0;
+ this->IterNext(ev, &k, (void*) 0);
+ return k;
+ }
+ else
+ map->MapKeyIsNotIPError(ev);
+ }
+ return (void*) 0;
+}
+
+void* morkProbeMapIter::IterHereKey(morkEnv* ev)
+// equivalent to { void* k=0; this->IterHere(ev, &k, 0); return k; }
+{
+ morkProbeMap* map = sProbeMapIter_Map;
+ if ( map )
+ {
+ if ( map->sMap_KeyIsIP )
+ {
+ void* k = 0;
+ this->IterHere(ev, &k, (void*) 0);
+ return k;
+ }
+ else
+ map->MapKeyIsNotIPError(ev);
+ }
+ return (void*) 0;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkProbeMap.h b/components/mork/src/morkProbeMap.h
new file mode 100644
index 000000000..8b9b23d21
--- /dev/null
+++ b/components/mork/src/morkProbeMap.h
@@ -0,0 +1,431 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// This code is a port to NS Mork from public domain Mithril C++ sources.
+// Note many code comments here come verbatim from cut-and-pasted Mithril.
+// In many places, code is identical; Mithril versions stay public domain.
+// Changes in porting are mainly class type and scalar type name changes.
+
+#ifndef _MORKPROBEMAP_
+#define _MORKPROBEMAP_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class morkMapScratch { // utility class used by map subclasses
+public:
+ nsIMdbHeap* sMapScratch_Heap; // cached sMap_Heap
+ mork_count sMapScratch_Slots; // cached sMap_Slots
+
+ mork_u1* sMapScratch_Keys; // cached sMap_Keys
+ mork_u1* sMapScratch_Vals; // cached sMap_Vals
+
+public:
+ void halt_map_scratch(morkEnv* ev);
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kProbeMap 0x7072 /* ascii 'pr' */
+#define morkProbeMap_kTag 0x70724D50 /* ascii 'prMP' */
+
+#define morkProbeMap_kLazyClearOnAdd ((mork_u1) 'c')
+
+class morkProbeMap: public morkNode {
+
+protected:
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+protected:
+ // { begin morkMap slots
+ nsIMdbHeap* sMap_Heap; // strong ref to heap allocating all space
+
+ mork_u1* sMap_Keys;
+ mork_u1* sMap_Vals;
+
+ mork_count sMap_Seed; // change count of members or structure
+
+ mork_count sMap_Slots; // count of slots in the hash table
+ mork_fill sMap_Fill; // number of used slots in the hash table
+
+ mork_size sMap_KeySize; // size of each key (cannot be zero)
+ mork_size sMap_ValSize; // size of each val (zero allowed)
+
+ mork_bool sMap_KeyIsIP; // sMap_KeySize == sizeof(mork_ip)
+ mork_bool sMap_ValIsIP; // sMap_ValSize == sizeof(mork_ip)
+ mork_u1 sMap_Pad[ 2 ]; // for u4 alignment
+ // } end morkMap slots
+
+ friend class morkProbeMapIter; // for access to protected slots
+
+public: // getters
+ mork_count MapSeed() const { return sMap_Seed; }
+
+ mork_count MapSlots() const { return sMap_Slots; }
+ mork_fill MapFill() const { return sMap_Fill; }
+
+ mork_size MapKeySize() const { return sMap_KeySize; }
+ mork_size MapValSize() const { return sMap_ValSize; }
+
+ mork_bool MapKeyIsIP() const { return sMap_KeyIsIP; }
+ mork_bool MapValIsIP() const { return sMap_ValIsIP; }
+
+protected: // slots
+ // { begin morkProbeMap slots
+
+ mork_fill sProbeMap_MaxFill; // max sMap_Fill before map must grow
+
+ mork_u1 sProbeMap_LazyClearOnAdd; // true if kLazyClearOnAdd
+ mork_bool sProbeMap_ZeroIsClearKey; // zero is adequate to clear keys
+ mork_u1 sProbeMap_Pad[ 2 ]; // for u4 alignment
+
+ mork_u4 sProbeMap_Tag;
+
+ // } end morkProbeMap slots
+
+public: // lazy clear on add
+
+ mork_bool need_lazy_init() const
+ { return sProbeMap_LazyClearOnAdd == morkProbeMap_kLazyClearOnAdd; }
+
+public: // typing
+ mork_bool GoodProbeMap() const
+ { return sProbeMap_Tag == morkProbeMap_kTag; }
+
+protected: // utilities
+
+ void* clear_alloc(morkEnv* ev, mork_size inSize);
+
+ mork_u1* map_new_vals(morkEnv* ev, mork_num inSlots);
+ mork_u1* map_new_keys(morkEnv* ev, mork_num inSlots);
+
+ void clear_probe_map(morkEnv* ev, nsIMdbHeap* ioMapHeap);
+ void init_probe_map(morkEnv* ev, mork_size inSlots);
+ void probe_map_lazy_init(morkEnv* ev);
+
+ mork_bool new_slots(morkEnv* ev, morkMapScratch* old, mork_num inSlots);
+
+ mork_test find_key_pos(morkEnv* ev, const void* inAppKey,
+ mork_u4 inHash, mork_pos* outPos) const;
+
+ void put_probe_kv(morkEnv* ev,
+ const void* inAppKey, const void* inAppVal, mork_pos inPos);
+ void get_probe_kv(morkEnv* ev,
+ void* outAppKey, void* outAppVal, mork_pos inPos) const;
+
+ mork_bool grow_probe_map(morkEnv* ev);
+ void rehash_old_map(morkEnv* ev, morkMapScratch* ioScratch);
+ void revert_map(morkEnv* ev, morkMapScratch* ioScratch);
+
+public: // errors
+ void ProbeMapBadTagError(morkEnv* ev) const;
+ void WrapWithNoVoidSlotError(morkEnv* ev) const;
+ void GrowFailsMaxFillError(morkEnv* ev) const;
+ void MapKeyIsNotIPError(morkEnv* ev) const;
+ void MapValIsNotIPError(morkEnv* ev) const;
+
+ void MapNilKeysError(morkEnv* ev);
+ void MapZeroKeySizeError(morkEnv* ev);
+
+ void MapSeedOutOfSyncError(morkEnv* ev);
+ void MapFillUnderflowWarning(morkEnv* ev);
+
+ static void ProbeMapCutError(morkEnv* ev);
+
+ // { ===== begin morkMap methods =====
+public:
+
+ virtual mork_test // hit(a,b) implies hash(a) == hash(b)
+ MapTest(morkEnv* ev, const void* inMapKey, const void* inAppKey) const;
+ // Note inMapKey is always a key already stored in the map, while inAppKey
+ // is always a method argument parameter from a client method call.
+ // This matters the most in morkProbeMap subclasses, which have the
+ // responsibility of putting 'app' keys into slots for 'map' keys, and
+ // the bit pattern representation might be different in such cases.
+ // morkTest_kHit means that inMapKey equals inAppKey (and this had better
+ // also imply that hash(inMapKey) == hash(inAppKey)).
+ // morkTest_kMiss means that inMapKey does NOT equal inAppKey (but this
+ // implies nothing at all about hash(inMapKey) and hash(inAppKey)).
+ // morkTest_kVoid means that inMapKey is not a valid key bit pattern,
+ // which means that key slot in the map is not being used. Note that
+ // kVoid is only expected as a return value in morkProbeMap subclasses,
+ // because morkProbeMap must ask whether a key slot is used or not.
+ // morkChainMap however, always knows when a key slot is used, so only
+ // key slots expected to have valid bit patterns will be presented to
+ // the MapTest() methods for morkChainMap subclasses.
+ //
+ // NOTE: it is very important that subclasses correctly return the value
+ // morkTest_kVoid whenever the slot for inMapKey contains a bit pattern
+ // that means the slot is not being used, because this is the only way a
+ // probe map can terminate an unsuccessful search for a key in the map.
+
+ virtual mork_u4 // hit(a,b) implies hash(a) == hash(b)
+ MapHash(morkEnv* ev, const void* inAppKey) const;
+
+ virtual mork_bool
+ MapAtPut(morkEnv* ev, const void* inAppKey, const void* inAppVal,
+ void* outAppKey, void* outAppVal);
+
+ virtual mork_bool
+ MapAt(morkEnv* ev, const void* inAppKey, void* outAppKey, void* outAppVal);
+
+ virtual mork_num
+ MapCutAll(morkEnv* ev);
+ // } ===== end morkMap methods =====
+
+
+ // { ===== begin morkProbeMap methods =====
+public:
+
+ virtual mork_u4
+ ProbeMapHashMapKey(morkEnv* ev, const void* inMapKey) const;
+ // ProbeMapHashMapKey() does logically the same thing as MapHash(), and
+ // the default implementation actually calls virtual MapHash(). However,
+ // Subclasses must override this method whenever the formats of keys in
+ // the map differ from app keys outside the map, because MapHash() only
+ // works on keys in 'app' format, while ProbeMapHashMapKey() only works
+ // on keys in 'map' format. This method is called in order to rehash all
+ // map keys when a map is grown, and this causes all old map members to
+ // move into new slot locations.
+ //
+ // Note it is absolutely imperative that a hash for a key in 'map' format
+ // be exactly the same the hash of the same key in 'app' format, or else
+ // maps will seem corrupt later when keys in 'app' format cannot be found.
+
+ virtual mork_bool
+ ProbeMapIsKeyNil(morkEnv* ev, void* ioMapKey);
+ // ProbeMapIsKeyNil() must say whether the representation of logical 'nil'
+ // is currently found inside the key at ioMapKey, for a key found within
+ // the map. The the map iterator uses this method to find map keys that
+ // are actually being used for valid map associations; otherwise the
+ // iterator cannot determine which map slots actually denote used keys.
+ // The default method version returns true if all the bits equal zero.
+
+ virtual void
+ ProbeMapClearKey(morkEnv* ev, // put 'nil' alls keys inside map
+ void* ioMapKey, mork_count inKeyCount); // array of keys inside map
+ // ProbeMapClearKey() must put some representation of logical 'nil' into
+ // every key slot in the map, such that MapTest() will later recognize
+ // that this bit pattern shows each key slot is not actually being used.
+ //
+ // This method is typically called whenever the map is either created or
+ // grown into a larger size, where ioMapKey is a pointer to an array of
+ // inKeyCount keys, where each key is this->MapKeySize() bytes in size.
+ // Note that keys are assumed immediately adjacent with no padding, so
+ // if any alignment requirements must be met, then subclasses should have
+ // already accounted for this when specifying a key size in the map.
+ //
+ // Since this method will be called when a map is being grown in size,
+ // nothing should be assumed about the state slots of the map, since the
+ // ioMapKey array might not yet live in sMap_Keys, and the array length
+ // inKeyCount might not yet live in sMap_Slots. However, the value kept
+ // in sMap_KeySize never changes, so this->MapKeySize() is always correct.
+
+ virtual void
+ ProbeMapPushIn(morkEnv* ev, // move (key,val) into the map
+ const void* inAppKey, const void* inAppVal, // (key,val) outside map
+ void* outMapKey, void* outMapVal); // (key,val) inside map
+ // This method actually puts keys and vals in the map in suitable format.
+ //
+ // ProbeMapPushIn() must copy a caller key and value in 'app' format
+ // into the map slots provided, which are in 'map' format. When the
+ // 'app' and 'map' formats are identical, then this is just a bitwise
+ // copy of this->MapKeySize() key bytes and this->MapValSize() val bytes,
+ // and this is exactly what the default implementation performs. However,
+ // if 'app' and 'map' formats are different, and MapTest() depends on this
+ // difference in format, then subclasses must override this method to do
+ // whatever is necessary to store the input app key in output map format.
+ //
+ // Do NOT write more than this->MapKeySize() bytes of a map key, or more
+ // than this->MapValSize() bytes of a map val, or corruption might ensue.
+ //
+ // The inAppKey and inAppVal parameters are the same ones passed into a
+ // call to MapAtPut(), and the outMapKey and outMapVal parameters are ones
+ // determined by how the map currently positions key inAppKey in the map.
+ //
+ // Note any key or val parameter can be a null pointer, in which case
+ // this method must do nothing with those parameters. In particular, do
+ // no key move at all when either inAppKey or outMapKey is nil, and do
+ // no val move at all when either inAppVal or outMapVal is nil. Note that
+ // outMapVal should always be nil when this->MapValSize() is nil.
+
+ virtual void
+ ProbeMapPullOut(morkEnv* ev, // move (key,val) out from the map
+ const void* inMapKey, const void* inMapVal, // (key,val) inside map
+ void* outAppKey, void* outAppVal) const; // (key,val) outside map
+ // This method actually gets keys and vals from the map in suitable format.
+ //
+ // ProbeMapPullOut() must copy a key and val in 'map' format into the
+ // caller key and val slots provided, which are in 'app' format. When the
+ // 'app' and 'map' formats are identical, then this is just a bitwise
+ // copy of this->MapKeySize() key bytes and this->MapValSize() val bytes,
+ // and this is exactly what the default implementation performs. However,
+ // if 'app' and 'map' formats are different, and MapTest() depends on this
+ // difference in format, then subclasses must override this method to do
+ // whatever is necessary to store the input map key in output app format.
+ //
+ // The outAppKey and outAppVal parameters are the same ones passed into a
+ // call to either MapAtPut() or MapAt(), while inMapKey and inMapVal are
+ // determined by how the map currently positions the target key in the map.
+ //
+ // Note any key or val parameter can be a null pointer, in which case
+ // this method must do nothing with those parameters. In particular, do
+ // no key move at all when either inMapKey or outAppKey is nil, and do
+ // no val move at all when either inMapVal or outAppVal is nil. Note that
+ // inMapVal should always be nil when this->MapValSize() is nil.
+
+ // } ===== end morkProbeMap methods =====
+
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseProbeMap() only if open
+ virtual ~morkProbeMap(); // assert that CloseProbeMap() executed earlier
+
+public: // morkProbeMap construction & destruction
+ morkProbeMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioNodeHeap,
+ mork_size inKeySize, mork_size inValSize,
+ nsIMdbHeap* ioMapHeap, mork_size inSlots,
+ mork_bool inZeroIsClearKey);
+
+ void CloseProbeMap(morkEnv* ev); // called by
+
+public: // dynamic type identification
+ mork_bool IsProbeMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kProbeMap; }
+// } ===== end morkNode methods =====
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakMap(morkMap* me,
+ morkEnv* ev, morkMap** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongMap(morkMap* me,
+ morkEnv* ev, morkMap** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+/*============================================================================*/
+/* morkProbeMapIter */
+
+#define morkProbeMapIter_kBeforeIx ((mork_i4) -1) /* before first member */
+#define morkProbeMapIter_kAfterIx ((mork_i4) -2) /* after last member */
+
+class morkProbeMapIter {
+
+protected:
+ morkProbeMap* sProbeMapIter_Map; // nonref
+ mork_num sProbeMapIter_Seed; // iter's cached copy of map's seed
+
+ mork_i4 sProbeMapIter_HereIx;
+
+ mork_change sProbeMapIter_Change; // morkMapIter API simulation dummy
+ mork_u1 sProbeMapIter_Pad[ 3 ]; // for u4 alignment
+
+public:
+ morkProbeMapIter(morkEnv* ev, morkProbeMap* ioMap);
+ void CloseMapIter(morkEnv* ev);
+
+ morkProbeMapIter( ); // zero most slots; caller must call InitProbeMapIter()
+
+protected: // protected so subclasses must provide suitable typesafe inlines:
+
+ void InitProbeMapIter(morkEnv* ev, morkProbeMap* ioMap);
+
+ void InitMapIter(morkEnv* ev, morkProbeMap* ioMap) // morkMapIter compatibility
+ { this->InitProbeMapIter(ev, ioMap); }
+
+ mork_bool IterFirst(morkEnv* ev, void* outKey, void* outVal);
+ mork_bool IterNext(morkEnv* ev, void* outKey, void* outVal);
+ mork_bool IterHere(morkEnv* ev, void* outKey, void* outVal);
+
+ // NOTE: the following methods ONLY work for sMap_ValIsIP pointer values.
+ // (Note the implied assumption that zero is never a good value pattern.)
+
+ void* IterFirstVal(morkEnv* ev, void* outKey);
+ // equivalent to { void* v=0; this->IterFirst(ev, outKey, &v); return v; }
+
+ void* IterNextVal(morkEnv* ev, void* outKey);
+ // equivalent to { void* v=0; this->IterNext(ev, outKey, &v); return v; }
+
+ void* IterHereVal(morkEnv* ev, void* outKey);
+ // equivalent to { void* v=0; this->IterHere(ev, outKey, &v); return v; }
+
+ // NOTE: the following methods ONLY work for sMap_KeyIsIP pointer values.
+ // (Note the implied assumption that zero is never a good key pattern.)
+
+ void* IterFirstKey(morkEnv* ev);
+ // equivalent to { void* k=0; this->IterFirst(ev, &k, 0); return k; }
+
+ void* IterNextKey(morkEnv* ev);
+ // equivalent to { void* k=0; this->IterNext(ev, &k, 0); return k; }
+
+ void* IterHereKey(morkEnv* ev);
+ // equivalent to { void* k=0; this->IterHere(ev, &k, 0); return k; }
+
+public: // simulation of the morkMapIter API for morkMap compatibility:
+ mork_change* First(morkEnv* ev, void* outKey, void* outVal);
+ mork_change* Next(morkEnv* ev, void* outKey, void* outVal);
+ mork_change* Here(morkEnv* ev, void* outKey, void* outVal);
+
+ mork_change* CutHere(morkEnv* ev, void* outKey, void* outVal);
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKPROBEMAP_ */
diff --git a/components/mork/src/morkQuickSort.cpp b/components/mork/src/morkQuickSort.cpp
new file mode 100644
index 000000000..c0a4c11d0
--- /dev/null
+++ b/components/mork/src/morkQuickSort.cpp
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* We need this because Solaris' version of qsort is broken and
+ * causes array bounds reads.
+ */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKQUICKSORT_
+#include "morkQuickSort.h"
+#endif
+
+#if !defined(DEBUG) && (defined(__cplusplus) || defined(__gcc))
+# ifndef INLINE
+# define INLINE inline
+# endif
+#else
+# define INLINE
+#endif
+
+static INLINE mork_u1*
+morkQS_med3(mork_u1 *, mork_u1 *, mork_u1 *, mdbAny_Order, void *);
+
+static INLINE void
+morkQS_swapfunc(mork_u1 *, mork_u1 *, int, int);
+
+/*
+ * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function".
+ */
+#define morkQS_swapcode(TYPE, parmi, parmj, n) { \
+ long i = (n) / sizeof (TYPE); \
+ TYPE *pi = (TYPE *) (parmi); \
+ TYPE *pj = (TYPE *) (parmj); \
+ do { \
+ TYPE t = *pi; \
+ *pi++ = *pj; \
+ *pj++ = t; \
+ } while (--i > 0); \
+}
+
+#define morkQS_SwapInit(a, es) swaptype = (a - (mork_u1 *)0) % sizeof(long) || \
+ es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
+
+static INLINE void
+morkQS_swapfunc(mork_u1* a, mork_u1* b, int n, int swaptype)
+{
+ if(swaptype <= 1)
+ morkQS_swapcode(long, a, b, n)
+ else
+ morkQS_swapcode(mork_u1, a, b, n)
+}
+
+#define morkQS_swap(a, b) \
+ if (swaptype == 0) { \
+ long t = *(long *)(a); \
+ *(long *)(a) = *(long *)(b); \
+ *(long *)(b) = t; \
+ } else \
+ morkQS_swapfunc(a, b, (int)inSize, swaptype)
+
+#define morkQS_vecswap(a, b, n) if ((n) > 0) morkQS_swapfunc(a, b, (int)n, swaptype)
+
+static INLINE mork_u1 *
+morkQS_med3(mork_u1* a, mork_u1* b, mork_u1* c, mdbAny_Order cmp, void* closure)
+{
+ return (*cmp)(a, b, closure) < 0 ?
+ ((*cmp)(b, c, closure) < 0 ? b : ((*cmp)(a, c, closure) < 0 ? c : a ))
+ :((*cmp)(b, c, closure) > 0 ? b : ((*cmp)(a, c, closure) < 0 ? a : c ));
+}
+
+#define morkQS_MIN(x,y) ((x)<(y)?(x):(y))
+
+void
+morkQuickSort(mork_u1* ioVec, mork_u4 inCount, mork_u4 inSize,
+ mdbAny_Order inOrder, void* ioClosure)
+{
+ mork_u1* pa, *pb, *pc, *pd, *pl, *pm, *pn;
+ int d, r, swaptype, swap_cnt;
+
+tailCall: morkQS_SwapInit(ioVec, inSize);
+ swap_cnt = 0;
+ if (inCount < 7) {
+ for (pm = ioVec + inSize; pm < ioVec + inCount * inSize; pm += inSize)
+ for (pl = pm; pl > ioVec && (*inOrder)(pl - inSize, pl, ioClosure) > 0;
+ pl -= inSize)
+ morkQS_swap(pl, pl - inSize);
+ return;
+ }
+ pm = ioVec + (inCount / 2) * inSize;
+ if (inCount > 7) {
+ pl = ioVec;
+ pn = ioVec + (inCount - 1) * inSize;
+ if (inCount > 40) {
+ d = (inCount / 8) * inSize;
+ pl = morkQS_med3(pl, pl + d, pl + 2 * d, inOrder, ioClosure);
+ pm = morkQS_med3(pm - d, pm, pm + d, inOrder, ioClosure);
+ pn = morkQS_med3(pn - 2 * d, pn - d, pn, inOrder, ioClosure);
+ }
+ pm = morkQS_med3(pl, pm, pn, inOrder, ioClosure);
+ }
+ morkQS_swap(ioVec, pm);
+ pa = pb = ioVec + inSize;
+
+ pc = pd = ioVec + (inCount - 1) * inSize;
+ for (;;) {
+ while (pb <= pc && (r = (*inOrder)(pb, ioVec, ioClosure)) <= 0) {
+ if (r == 0) {
+ swap_cnt = 1;
+ morkQS_swap(pa, pb);
+ pa += inSize;
+ }
+ pb += inSize;
+ }
+ while (pb <= pc && (r = (*inOrder)(pc, ioVec, ioClosure)) >= 0) {
+ if (r == 0) {
+ swap_cnt = 1;
+ morkQS_swap(pc, pd);
+ pd -= inSize;
+ }
+ pc -= inSize;
+ }
+ if (pb > pc)
+ break;
+ morkQS_swap(pb, pc);
+ swap_cnt = 1;
+ pb += inSize;
+ pc -= inSize;
+ }
+ if (swap_cnt == 0) { /* Switch to insertion sort */
+ for (pm = ioVec + inSize; pm < ioVec + inCount * inSize; pm += inSize)
+ for (pl = pm; pl > ioVec && (*inOrder)(pl - inSize, pl, ioClosure) > 0;
+ pl -= inSize)
+ morkQS_swap(pl, pl - inSize);
+ return;
+ }
+
+ pn = ioVec + inCount * inSize;
+ r = morkQS_MIN(pa - ioVec, pb - pa);
+ morkQS_vecswap(ioVec, pb - r, r);
+ r = morkQS_MIN(pd - pc, (int)(pn - pd - inSize));
+ morkQS_vecswap(pb, pn - r, r);
+ if ((r = pb - pa) > (int)inSize)
+ morkQuickSort(ioVec, r / inSize, inSize, inOrder, ioClosure);
+ if ((r = pd - pc) > (int)inSize) {
+ /* Iterate rather than recurse to save stack space */
+ ioVec = pn - r;
+ inCount = r / inSize;
+ goto tailCall;
+ }
+/* morkQuickSort(pn - r, r / inSize, inSize, inOrder, ioClosure);*/
+}
+
diff --git a/components/mork/src/morkQuickSort.h b/components/mork/src/morkQuickSort.h
new file mode 100644
index 000000000..a9c2e5546
--- /dev/null
+++ b/components/mork/src/morkQuickSort.h
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+
+#ifndef _MORKQUICKSORT_
+#define _MORKQUICKSORT_ 1
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+extern void
+morkQuickSort(mork_u1* ioVec, mork_u4 inCount, mork_u4 inSize,
+ mdbAny_Order inOrder, void* ioClosure);
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKQUICKSORT_ */
diff --git a/components/mork/src/morkRow.cpp b/components/mork/src/morkRow.cpp
new file mode 100644
index 000000000..e68148d06
--- /dev/null
+++ b/components/mork/src/morkRow.cpp
@@ -0,0 +1,939 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKROWSPACE_
+#include "morkRowSpace.h"
+#endif
+
+#ifndef _MORKPOOL_
+#include "morkPool.h"
+#endif
+
+#ifndef _MORKROWOBJECT_
+#include "morkRowObject.h"
+#endif
+
+#ifndef _MORKCELLOBJECT_
+#include "morkCellObject.h"
+#endif
+
+#ifndef _MORKCELL_
+#include "morkCell.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKROWCELLCURSOR_
+#include "morkRowCellCursor.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+// notifications regarding row changes:
+
+void morkRow::NoteRowAddCol(morkEnv* ev, mork_column inColumn)
+{
+ if ( !this->IsRowRewrite() )
+ {
+ mork_delta newDelta;
+ morkDelta_Init(newDelta, inColumn, morkChange_kAdd);
+
+ if ( newDelta != mRow_Delta ) // not repeating existing data?
+ {
+ if ( this->HasRowDelta() ) // already have one change recorded?
+ this->SetRowRewrite(); // just plan to write all row cells
+ else
+ this->SetRowDelta(inColumn, morkChange_kAdd);
+ }
+ }
+ else
+ this->ClearRowDelta();
+}
+
+void morkRow::NoteRowCutCol(morkEnv* ev, mork_column inColumn)
+{
+ if ( !this->IsRowRewrite() )
+ {
+ mork_delta newDelta;
+ morkDelta_Init(newDelta, inColumn, morkChange_kCut);
+
+ if ( newDelta != mRow_Delta ) // not repeating existing data?
+ {
+ if ( this->HasRowDelta() ) // already have one change recorded?
+ this->SetRowRewrite(); // just plan to write all row cells
+ else
+ this->SetRowDelta(inColumn, morkChange_kCut);
+ }
+ }
+ else
+ this->ClearRowDelta();
+}
+
+void morkRow::NoteRowSetCol(morkEnv* ev, mork_column inColumn)
+{
+ if ( !this->IsRowRewrite() )
+ {
+ if ( this->HasRowDelta() ) // already have one change recorded?
+ this->SetRowRewrite(); // just plan to write all row cells
+ else
+ this->SetRowDelta(inColumn, morkChange_kSet);
+ }
+ else
+ this->ClearRowDelta();
+}
+
+void morkRow::NoteRowSetAll(morkEnv* ev)
+{
+ this->SetRowRewrite(); // just plan to write all row cells
+ this->ClearRowDelta();
+}
+
+mork_u2
+morkRow::AddRowGcUse(morkEnv* ev)
+{
+ if ( this->IsRow() )
+ {
+ if ( mRow_GcUses < morkRow_kMaxGcUses ) // not already maxed out?
+ ++mRow_GcUses;
+ }
+ else
+ this->NonRowTypeError(ev);
+
+ return mRow_GcUses;
+}
+
+mork_u2
+morkRow::CutRowGcUse(morkEnv* ev)
+{
+ if ( this->IsRow() )
+ {
+ if ( mRow_GcUses ) // any outstanding uses to cut?
+ {
+ if ( mRow_GcUses < morkRow_kMaxGcUses ) // not frozen at max?
+ --mRow_GcUses;
+ }
+ else
+ this->GcUsesUnderflowWarning(ev);
+ }
+ else
+ this->NonRowTypeError(ev);
+
+ return mRow_GcUses;
+}
+
+/*static*/ void
+morkRow::GcUsesUnderflowWarning(morkEnv* ev)
+{
+ ev->NewWarning("mRow_GcUses underflow");
+}
+
+
+/*static*/ void
+morkRow::NonRowTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkRow");
+}
+
+/*static*/ void
+morkRow::NonRowTypeWarning(morkEnv* ev)
+{
+ ev->NewWarning("non morkRow");
+}
+
+/*static*/ void
+morkRow::LengthBeyondMaxError(morkEnv* ev)
+{
+ ev->NewError("mRow_Length over max");
+}
+
+/*static*/ void
+morkRow::ZeroColumnError(morkEnv* ev)
+{
+ ev->NewError(" zero mork_column");
+}
+
+/*static*/ void
+morkRow::NilCellsError(morkEnv* ev)
+{
+ ev->NewError("nil mRow_Cells");
+}
+
+void
+morkRow::InitRow(morkEnv* ev, const mdbOid* inOid, morkRowSpace* ioSpace,
+ mork_size inLength, morkPool* ioPool)
+ // if inLength is nonzero, cells will be allocated from ioPool
+{
+ if ( ioSpace && ioPool && inOid )
+ {
+ if ( inLength <= morkRow_kMaxLength )
+ {
+ if ( inOid->mOid_Id != morkRow_kMinusOneRid )
+ {
+ mRow_Space = ioSpace;
+ mRow_Object = 0;
+ mRow_Cells = 0;
+ mRow_Oid = *inOid;
+
+ mRow_Length = (mork_u2) inLength;
+ mRow_Seed = (mork_u2) (mork_ip) this; // "random" assignment
+
+ mRow_GcUses = 0;
+ mRow_Pad = 0;
+ mRow_Flags = 0;
+ mRow_Tag = morkRow_kTag;
+
+ morkZone* zone = &ioSpace->mSpace_Store->mStore_Zone;
+
+ if ( inLength )
+ mRow_Cells = ioPool->NewCells(ev, inLength, zone);
+
+ if ( this->MaybeDirtySpaceStoreAndRow() ) // new row might dirty store
+ {
+ this->SetRowRewrite();
+ this->NoteRowSetAll(ev);
+ }
+ }
+ else
+ ioSpace->MinusOneRidError(ev);
+ }
+ else
+ this->LengthBeyondMaxError(ev);
+ }
+ else
+ ev->NilPointerError();
+}
+
+morkRowObject*
+morkRow::AcquireRowObject(morkEnv* ev, morkStore* ioStore)
+{
+ morkRowObject* ro = mRow_Object;
+ if ( ro ) // need new row object?
+ ro->AddRef();
+ else
+ {
+ nsIMdbHeap* heap = ioStore->mPort_Heap;
+ ro = new(*heap, ev)
+ morkRowObject(ev, morkUsage::kHeap, heap, this, ioStore);
+ if ( !ro )
+ return (morkRowObject*) 0;
+
+ morkRowObject::SlotWeakRowObject(ro, ev, &mRow_Object);
+ ro->AddRef();
+ }
+ return ro;
+}
+
+nsIMdbRow*
+morkRow::AcquireRowHandle(morkEnv* ev, morkStore* ioStore)
+{
+ return AcquireRowObject(ev, ioStore);
+}
+
+nsIMdbCell*
+morkRow::AcquireCellHandle(morkEnv* ev, morkCell* ioCell,
+ mdb_column inCol, mork_pos inPos)
+{
+ nsIMdbHeap* heap = ev->mEnv_Heap;
+ morkCellObject* cellObj = new(*heap, ev)
+ morkCellObject(ev, morkUsage::kHeap, heap, this, ioCell, inCol, inPos);
+ if ( cellObj )
+ {
+ nsIMdbCell* cellHandle = cellObj->AcquireCellHandle(ev);
+// cellObj->CutStrongRef(ev->AsMdbEnv());
+ return cellHandle;
+ }
+ return (nsIMdbCell*) 0;
+}
+
+mork_count
+morkRow::CountOverlap(morkEnv* ev, morkCell* ioVector, mork_fill inFill)
+ // Count cells in ioVector that change existing cells in this row when
+ // ioVector is added to the row (as in TakeCells()). This is the set
+ // of cells with the same columns in ioVector and mRow_Cells, which do
+ // not have exactly the same value in mCell_Atom, and which do not both
+ // have change status equal to morkChange_kCut (because cutting a cut
+ // cell still yields a cell that has been cut). CountOverlap() also
+ // modifies the change attribute of any cell in ioVector to kDup when
+ // the change was previously kCut and the same column cell was found
+ // in this row with change also equal to kCut; this tells callers later
+ // they need not look for that cell in the row again on a second pass.
+{
+ mork_count outCount = 0;
+ mork_pos pos = 0; // needed by GetCell()
+ morkCell* cells = ioVector;
+ morkCell* end = cells + inFill;
+ --cells; // prepare for preincrement
+ while ( ++cells < end && ev->Good() )
+ {
+ mork_column col = cells->GetColumn();
+
+ morkCell* old = this->GetCell(ev, col, &pos);
+ if ( old ) // same column?
+ {
+ mork_change newChg = cells->GetChange();
+ mork_change oldChg = old->GetChange();
+ if ( newChg != morkChange_kCut || oldChg != newChg ) // not cut+cut?
+ {
+ if ( cells->mCell_Atom != old->mCell_Atom ) // not same atom?
+ ++outCount; // cells will replace old significantly when added
+ }
+ else
+ cells->SetColumnAndChange(col, morkChange_kDup); // note dup status
+ }
+ }
+ return outCount;
+}
+
+void
+morkRow::MergeCells(morkEnv* ev, morkCell* ioVector,
+ mork_fill inVecLength, mork_fill inOldRowFill, mork_fill inOverlap)
+ // MergeCells() is the part of TakeCells() that does the insertion.
+ // inOldRowFill is the old value of mRow_Length, and inOverlap is the
+ // number of cells in the intersection that must be updated.
+{
+ morkCell* newCells = mRow_Cells + inOldRowFill; // 1st new cell in row
+ morkCell* newEnd = newCells + mRow_Length; // one past last cell
+
+ morkCell* srcCells = ioVector;
+ morkCell* srcEnd = srcCells + inVecLength;
+
+ --srcCells; // prepare for preincrement
+ while ( ++srcCells < srcEnd && ev->Good() )
+ {
+ mork_change srcChg = srcCells->GetChange();
+ if ( srcChg != morkChange_kDup ) // anything to be done?
+ {
+ morkCell* dstCell = 0;
+ if ( inOverlap )
+ {
+ mork_pos pos = 0; // needed by GetCell()
+ dstCell = this->GetCell(ev, srcCells->GetColumn(), &pos);
+ }
+ if ( dstCell )
+ {
+ --inOverlap; // one fewer intersections to resolve
+ // swap the atoms in the cells to avoid ref counting here:
+ morkAtom* dstAtom = dstCell->mCell_Atom;
+ *dstCell = *srcCells; // bitwise copy, taking src atom
+ srcCells->mCell_Atom = dstAtom; // forget cell ref, if any
+ }
+ else if ( newCells < newEnd ) // another new cell exists?
+ {
+ dstCell = newCells++; // alloc another new cell
+ // take atom from source cell, transferring ref to this row:
+ *dstCell = *srcCells; // bitwise copy, taking src atom
+ srcCells->mCell_Atom = 0; // forget cell ref, if any
+ }
+ else // oops, we ran out...
+ ev->NewError("out of new cells");
+ }
+ }
+}
+
+void
+morkRow::TakeCells(morkEnv* ev, morkCell* ioVector, mork_fill inVecLength,
+ morkStore* ioStore)
+{
+ if ( ioVector && inVecLength && ev->Good() )
+ {
+ ++mRow_Seed; // intend to change structure of mRow_Cells
+ mork_size length = (mork_size) mRow_Length;
+
+ mork_count overlap = this->CountOverlap(ev, ioVector, inVecLength);
+
+ mork_size growth = inVecLength - overlap; // cells to add
+ mork_size newLength = length + growth;
+
+ if ( growth && ev->Good() ) // need to add any cells?
+ {
+ morkZone* zone = &ioStore->mStore_Zone;
+ morkPool* pool = ioStore->StorePool();
+ if ( !pool->AddRowCells(ev, this, length + growth, zone) )
+ ev->NewError("cannot take cells");
+ }
+ if ( ev->Good() )
+ {
+ if ( mRow_Length >= newLength )
+ this->MergeCells(ev, ioVector, inVecLength, length, overlap);
+ else
+ ev->NewError("not enough new cells");
+ }
+ }
+}
+
+mork_bool morkRow::MaybeDirtySpaceStoreAndRow()
+{
+ morkRowSpace* rowSpace = mRow_Space;
+ if ( rowSpace )
+ {
+ morkStore* store = rowSpace->mSpace_Store;
+ if ( store && store->mStore_CanDirty )
+ {
+ store->SetStoreDirty();
+ rowSpace->mSpace_CanDirty = morkBool_kTrue;
+ }
+
+ if ( rowSpace->mSpace_CanDirty )
+ {
+ this->SetRowDirty();
+ rowSpace->SetRowSpaceDirty();
+ return morkBool_kTrue;
+ }
+ }
+ return morkBool_kFalse;
+}
+
+morkCell*
+morkRow::NewCell(morkEnv* ev, mdb_column inColumn,
+ mork_pos* outPos, morkStore* ioStore)
+{
+ ++mRow_Seed; // intend to change structure of mRow_Cells
+ mork_size length = (mork_size) mRow_Length;
+ *outPos = (mork_pos) length;
+ morkPool* pool = ioStore->StorePool();
+ morkZone* zone = &ioStore->mStore_Zone;
+
+ mork_bool canDirty = this->MaybeDirtySpaceStoreAndRow();
+
+ if ( pool->AddRowCells(ev, this, length + 1, zone) )
+ {
+ morkCell* cell = mRow_Cells + length;
+ // next line equivalent to inline morkCell::SetCellDirty():
+ if ( canDirty )
+ cell->SetCellColumnDirty(inColumn);
+ else
+ cell->SetCellColumnClean(inColumn);
+
+ if ( canDirty && !this->IsRowRewrite() )
+ this->NoteRowAddCol(ev, inColumn);
+
+ return cell;
+ }
+
+ return (morkCell*) 0;
+}
+
+
+
+void morkRow::SeekColumn(morkEnv* ev, mdb_pos inPos,
+ mdb_column* outColumn, mdbYarn* outYarn)
+{
+ morkCell* cells = mRow_Cells;
+ if ( cells && inPos < mRow_Length && inPos >= 0 )
+ {
+ morkCell* c = cells + inPos;
+ if ( outColumn ) {
+ *outColumn = c->GetColumn();
+ }
+ if ( outYarn ) {
+ morkAtom::GetYarn(c->mCell_Atom, outYarn);
+ }
+ }
+ else
+ {
+ if ( outColumn ) {
+ *outColumn = 0;
+ }
+ if ( outYarn ) {
+ morkAtom::GetYarn((morkAtom*)0, outYarn);
+ }
+ }
+}
+
+void
+morkRow::NextColumn(morkEnv* ev, mdb_column* ioColumn, mdbYarn* outYarn)
+{
+ morkCell* cells = mRow_Cells;
+ if ( cells )
+ {
+ mork_column last = 0;
+ mork_column inCol = *ioColumn;
+ morkCell* end = cells + mRow_Length;
+ while ( cells < end )
+ {
+ if ( inCol == last ) // found column?
+ {
+ if ( outYarn ) {
+ if (outYarn) {
+ morkAtom::GetYarn(cells->mCell_Atom, outYarn);
+ }
+ *ioColumn = cells->GetColumn();
+ return; // stop, we are done
+ }
+ } else {
+ last = cells->GetColumn();
+ ++cells;
+ }
+ }
+ }
+ *ioColumn = 0;
+ if ( outYarn ) {
+ morkAtom::GetYarn((morkAtom*)0, outYarn);
+ }
+}
+
+morkCell*
+morkRow::CellAt(morkEnv* ev, mork_pos inPos) const
+{
+ MORK_USED_1(ev);
+ morkCell* cells = mRow_Cells;
+ if ( cells && inPos < mRow_Length && inPos >= 0 )
+ {
+ return cells + inPos;
+ }
+ return (morkCell*) 0;
+}
+
+morkCell*
+morkRow::GetCell(morkEnv* ev, mdb_column inColumn, mork_pos* outPos) const
+{
+ MORK_USED_1(ev);
+ morkCell* cells = mRow_Cells;
+ if ( cells )
+ {
+ morkCell* end = cells + mRow_Length;
+ while ( cells < end )
+ {
+ mork_column col = cells->GetColumn();
+ if ( col == inColumn ) // found the desired column?
+ {
+ *outPos = cells - mRow_Cells;
+ return cells;
+ }
+ else
+ ++cells;
+ }
+ }
+ *outPos = -1;
+ return (morkCell*) 0;
+}
+
+mork_aid
+morkRow::GetCellAtomAid(morkEnv* ev, mdb_column inColumn) const
+ // GetCellAtomAid() finds the cell with column inColumn, and sees if the
+ // atom has a token ID, and returns the atom's ID if there is one. Or
+ // else zero is returned if there is no such column, or no atom, or if
+ // the atom has no ID to return. This method is intended to support
+ // efficient updating of column indexes for rows in a row space.
+{
+ if (this->IsRow())
+ {
+ morkCell* cells = mRow_Cells;
+ if ( cells )
+ {
+ morkCell* end = cells + mRow_Length;
+ while ( cells < end )
+ {
+ mork_column col = cells->GetColumn();
+ if ( col == inColumn ) // found desired column?
+ {
+ morkAtom* atom = cells->mCell_Atom;
+ if ( atom && atom->IsBook() )
+ return ((morkBookAtom*) atom)->mBookAtom_Id;
+ else
+ return 0;
+ }
+ else
+ ++cells;
+ }
+ }
+ }
+ else
+ this->NonRowTypeError(ev);
+
+ return 0;
+}
+
+void
+morkRow::EmptyAllCells(morkEnv* ev)
+{
+ morkCell* cells = mRow_Cells;
+ if ( cells )
+ {
+ morkStore* store = this->GetRowSpaceStore(ev);
+ if ( store )
+ {
+ if ( this->MaybeDirtySpaceStoreAndRow() )
+ {
+ this->SetRowRewrite();
+ this->NoteRowSetAll(ev);
+ }
+ morkPool* pool = store->StorePool();
+ morkCell* end = cells + mRow_Length;
+ --cells; // prepare for preincrement:
+ while ( ++cells < end )
+ {
+ if ( cells->mCell_Atom )
+ cells->SetAtom(ev, (morkAtom*) 0, pool);
+ }
+ }
+ }
+}
+
+void
+morkRow::cut_all_index_entries(morkEnv* ev)
+{
+ morkRowSpace* rowSpace = mRow_Space;
+ if ( rowSpace->mRowSpace_IndexCount ) // any indexes?
+ {
+ morkCell* cells = mRow_Cells;
+ if ( cells )
+ {
+ morkCell* end = cells + mRow_Length;
+ --cells; // prepare for preincrement:
+ while ( ++cells < end )
+ {
+ morkAtom* atom = cells->mCell_Atom;
+ if ( atom )
+ {
+ mork_aid atomAid = atom->GetBookAtomAid();
+ if ( atomAid )
+ {
+ mork_column col = cells->GetColumn();
+ morkAtomRowMap* map = rowSpace->FindMap(ev, col);
+ if ( map ) // cut row from index for this column?
+ map->CutAid(ev, atomAid);
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+morkRow::CutAllColumns(morkEnv* ev)
+{
+ morkStore* store = this->GetRowSpaceStore(ev);
+ if ( store )
+ {
+ if ( this->MaybeDirtySpaceStoreAndRow() )
+ {
+ this->SetRowRewrite();
+ this->NoteRowSetAll(ev);
+ }
+ morkRowSpace* rowSpace = mRow_Space;
+ if ( rowSpace->mRowSpace_IndexCount ) // any indexes?
+ this->cut_all_index_entries(ev);
+
+ morkPool* pool = store->StorePool();
+ pool->CutRowCells(ev, this, /*newSize*/ 0, &store->mStore_Zone);
+ }
+}
+
+void
+morkRow::SetRow(morkEnv* ev, const morkRow* inSourceRow)
+{
+ // note inSourceRow might be in another DB, with a different store...
+ morkStore* store = this->GetRowSpaceStore(ev);
+ morkStore* srcStore = inSourceRow->GetRowSpaceStore(ev);
+ if ( store && srcStore )
+ {
+ if ( this->MaybeDirtySpaceStoreAndRow() )
+ {
+ this->SetRowRewrite();
+ this->NoteRowSetAll(ev);
+ }
+ morkRowSpace* rowSpace = mRow_Space;
+ mork_count indexes = rowSpace->mRowSpace_IndexCount; // any indexes?
+
+ mork_bool sameStore = ( store == srcStore ); // identical stores?
+ morkPool* pool = store->StorePool();
+ if ( pool->CutRowCells(ev, this, /*newSize*/ 0, &store->mStore_Zone) )
+ {
+ mork_fill fill = inSourceRow->mRow_Length;
+ if ( pool->AddRowCells(ev, this, fill, &store->mStore_Zone) )
+ {
+ morkCell* dst = mRow_Cells;
+ morkCell* dstEnd = dst + mRow_Length;
+
+ const morkCell* src = inSourceRow->mRow_Cells;
+ const morkCell* srcEnd = src + fill;
+ --dst; --src; // prepare both for preincrement:
+
+ while ( ++dst < dstEnd && ++src < srcEnd && ev->Good() )
+ {
+ morkAtom* atom = src->mCell_Atom;
+ mork_column dstCol = src->GetColumn();
+ // Note we modify the mCell_Atom slot directly instead of using
+ // morkCell::SetAtom(), because we know it starts equal to nil.
+
+ if ( sameStore ) // source and dest in same store?
+ {
+ // next line equivalent to inline morkCell::SetCellDirty():
+ dst->SetCellColumnDirty(dstCol);
+ dst->mCell_Atom = atom;
+ if ( atom ) // another ref to non-nil atom?
+ atom->AddCellUse(ev);
+ }
+ else // need to dup items from src store in a dest store
+ {
+ dstCol = store->CopyToken(ev, dstCol, srcStore);
+ if ( dstCol )
+ {
+ // next line equivalent to inline morkCell::SetCellDirty():
+ dst->SetCellColumnDirty(dstCol);
+ atom = store->CopyAtom(ev, atom);
+ dst->mCell_Atom = atom;
+ if ( atom ) // another ref?
+ atom->AddCellUse(ev);
+ }
+ }
+ if ( indexes && atom )
+ {
+ mork_aid atomAid = atom->GetBookAtomAid();
+ if ( atomAid )
+ {
+ morkAtomRowMap* map = rowSpace->FindMap(ev, dstCol);
+ if ( map )
+ map->AddAid(ev, atomAid, this);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+morkRow::AddRow(morkEnv* ev, const morkRow* inSourceRow)
+{
+ if ( mRow_Length ) // any existing cells we might need to keep?
+ {
+ ev->StubMethodOnlyError();
+ }
+ else
+ this->SetRow(ev, inSourceRow); // just exactly duplicate inSourceRow
+}
+
+void
+morkRow::OnZeroRowGcUse(morkEnv* ev)
+// OnZeroRowGcUse() is called when CutRowGcUse() returns zero.
+{
+ MORK_USED_1(ev);
+ // ev->NewWarning("need to implement OnZeroRowGcUse");
+}
+
+void
+morkRow::DirtyAllRowContent(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+
+ if ( this->MaybeDirtySpaceStoreAndRow() )
+ {
+ this->SetRowRewrite();
+ this->NoteRowSetAll(ev);
+ }
+ morkCell* cells = mRow_Cells;
+ if ( cells )
+ {
+ morkCell* end = cells + mRow_Length;
+ --cells; // prepare for preincrement:
+ while ( ++cells < end )
+ {
+ cells->SetCellDirty();
+ }
+ }
+}
+
+morkStore*
+morkRow::GetRowSpaceStore(morkEnv* ev) const
+{
+ morkRowSpace* rowSpace = mRow_Space;
+ if ( rowSpace )
+ {
+ morkStore* store = rowSpace->mSpace_Store;
+ if ( store )
+ {
+ if ( store->IsStore() )
+ {
+ return store;
+ }
+ else
+ store->NonStoreTypeError(ev);
+ }
+ else
+ ev->NilPointerError();
+ }
+ else
+ ev->NilPointerError();
+
+ return (morkStore*) 0;
+}
+
+void morkRow::CutColumn(morkEnv* ev, mdb_column inColumn)
+{
+ mork_pos pos = -1;
+ morkCell* cell = this->GetCell(ev, inColumn, &pos);
+ if ( cell )
+ {
+ morkStore* store = this->GetRowSpaceStore(ev);
+ if ( store )
+ {
+ if ( this->MaybeDirtySpaceStoreAndRow() && !this->IsRowRewrite() )
+ this->NoteRowCutCol(ev, inColumn);
+
+ morkRowSpace* rowSpace = mRow_Space;
+ morkAtomRowMap* map = ( rowSpace->mRowSpace_IndexCount )?
+ rowSpace->FindMap(ev, inColumn) : (morkAtomRowMap*) 0;
+ if ( map ) // this row attribute is indexed by row space?
+ {
+ morkAtom* oldAtom = cell->mCell_Atom;
+ if ( oldAtom ) // need to cut an entry from the index?
+ {
+ mork_aid oldAid = oldAtom->GetBookAtomAid();
+ if ( oldAid ) // cut old row attribute from row index in space?
+ map->CutAid(ev, oldAid);
+ }
+ }
+
+ morkPool* pool = store->StorePool();
+ cell->SetAtom(ev, (morkAtom*) 0, pool);
+
+ mork_fill fill = mRow_Length; // should not be zero
+ MORK_ASSERT(fill);
+ if ( fill ) // index < fill for last cell exists?
+ {
+ mork_fill last = fill - 1; // index of last cell in row
+
+ if ( pos < (mork_pos)last ) // need to move cells following cut cell?
+ {
+ morkCell* lastCell = mRow_Cells + last;
+ mork_count after = last - pos; // cell count after cut cell
+ morkCell* next = cell + 1; // next cell after cut cell
+ MORK_MEMMOVE(cell, next, after * sizeof(morkCell));
+ lastCell->SetColumnAndChange(0, 0);
+ lastCell->mCell_Atom = 0;
+ }
+
+ if ( ev->Good() )
+ pool->CutRowCells(ev, this, fill - 1, &store->mStore_Zone);
+ }
+ }
+ }
+}
+
+morkAtom* morkRow::GetColumnAtom(morkEnv* ev, mdb_column inColumn)
+{
+ if ( ev->Good() )
+ {
+ mork_pos pos = -1;
+ morkCell* cell = this->GetCell(ev, inColumn, &pos);
+ if ( cell )
+ return cell->mCell_Atom;
+ }
+ return (morkAtom*) 0;
+}
+
+void morkRow::AddColumn(morkEnv* ev, mdb_column inColumn,
+ const mdbYarn* inYarn, morkStore* ioStore)
+{
+ if ( ev->Good() )
+ {
+ mork_pos pos = -1;
+ morkCell* cell = this->GetCell(ev, inColumn, &pos);
+ morkCell* oldCell = cell; // need to know later whether new
+ if ( !cell ) // column does not yet exist?
+ cell = this->NewCell(ev, inColumn, &pos, ioStore);
+
+ if ( cell )
+ {
+ morkAtom* oldAtom = cell->mCell_Atom;
+
+ morkAtom* atom = ioStore->YarnToAtom(ev, inYarn, true /* create */);
+ if ( atom && atom != oldAtom )
+ {
+ morkRowSpace* rowSpace = mRow_Space;
+ morkAtomRowMap* map = ( rowSpace->mRowSpace_IndexCount )?
+ rowSpace->FindMap(ev, inColumn) : (morkAtomRowMap*) 0;
+
+ if ( map ) // inColumn is indexed by row space?
+ {
+ if ( oldAtom && oldAtom != atom ) // cut old cell from index?
+ {
+ mork_aid oldAid = oldAtom->GetBookAtomAid();
+ if ( oldAid ) // cut old row attribute from row index in space?
+ map->CutAid(ev, oldAid);
+ }
+ }
+
+ cell->SetAtom(ev, atom, ioStore->StorePool()); // refcounts atom
+
+ if ( oldCell ) // we changed a pre-existing cell in the row?
+ {
+ ++mRow_Seed;
+ if ( this->MaybeDirtySpaceStoreAndRow() && !this->IsRowRewrite() )
+ this->NoteRowAddCol(ev, inColumn);
+ }
+
+ if ( map ) // inColumn is indexed by row space?
+ {
+ mork_aid newAid = atom->GetBookAtomAid();
+ if ( newAid ) // add new row attribute to row index in space?
+ map->AddAid(ev, newAid, this);
+ }
+ }
+ }
+ }
+}
+
+morkRowCellCursor*
+morkRow::NewRowCellCursor(morkEnv* ev, mdb_pos inPos)
+{
+ morkRowCellCursor* outCursor = 0;
+ if ( ev->Good() )
+ {
+ morkStore* store = this->GetRowSpaceStore(ev);
+ if ( store )
+ {
+ morkRowObject* rowObj = this->AcquireRowObject(ev, store);
+ if ( rowObj )
+ {
+ nsIMdbHeap* heap = store->mPort_Heap;
+ morkRowCellCursor* cursor = new(*heap, ev)
+ morkRowCellCursor(ev, morkUsage::kHeap, heap, rowObj);
+
+ if ( cursor )
+ {
+ if ( ev->Good() )
+ {
+ cursor->mRowCellCursor_Col = inPos;
+ outCursor = cursor;
+ }
+ else
+ cursor->CutStrongRef(ev->mEnv_SelfAsMdbEnv);
+ }
+ rowObj->Release(); // always cut ref (cursor has its own)
+ }
+ }
+ }
+ return outCursor;
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
diff --git a/components/mork/src/morkRow.h b/components/mork/src/morkRow.h
new file mode 100644
index 000000000..d33c707e5
--- /dev/null
+++ b/components/mork/src/morkRow.h
@@ -0,0 +1,226 @@
+/* -*- 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/. */
+
+#ifndef _MORKROW_
+#define _MORKROW_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKCELL_
+#include "morkCell.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class nsIMdbRow;
+class nsIMdbCell;
+#define morkDerived_kRow /*i*/ 0x5277 /* ascii 'Rw' */
+
+#define morkRow_kMaxGcUses 0x0FF /* max for 8-bit unsigned int */
+#define morkRow_kMaxLength 0x0FFFF /* max for 16-bit unsigned int */
+#define morkRow_kMinusOneRid ((mork_rid) -1)
+
+#define morkRow_kTag 'r' /* magic signature for mRow_Tag */
+
+#define morkRow_kNotedBit ((mork_u1) (1 << 0)) /* space has change notes */
+#define morkRow_kRewriteBit ((mork_u1) (1 << 1)) /* must rewrite all cells */
+#define morkRow_kDirtyBit ((mork_u1) (1 << 2)) /* row has been changed */
+
+class morkRow{ // row of cells
+
+public: // state is public because the entire Mork system is private
+
+ morkRowSpace* mRow_Space; // mRow_Space->SpaceScope() is the row scope
+ morkRowObject* mRow_Object; // refcount & other state for object sharing
+ morkCell* mRow_Cells;
+ mdbOid mRow_Oid;
+
+ mork_delta mRow_Delta; // space to note a single column change
+
+ mork_u2 mRow_Length; // physical count of cells in mRow_Cells
+ mork_u2 mRow_Seed; // count changes in mRow_Cells structure
+
+ mork_u1 mRow_GcUses; // persistent references from tables
+ mork_u1 mRow_Pad; // for u1 alignment
+ mork_u1 mRow_Flags; // one-byte flags slot
+ mork_u1 mRow_Tag; // one-byte tag (need u4 alignment pad)
+
+public: // interpreting mRow_Delta
+
+ mork_bool HasRowDelta() const { return ( mRow_Delta != 0 ); }
+
+ void ClearRowDelta() { mRow_Delta = 0; }
+
+ void SetRowDelta(mork_column inCol, mork_change inChange)
+ { morkDelta_Init(mRow_Delta, inCol, inChange); }
+
+ mork_column GetDeltaColumn() const { return morkDelta_Column(mRow_Delta); }
+ mork_change GetDeltaChange() const { return morkDelta_Change(mRow_Delta); }
+
+public: // noting row changes
+
+ void NoteRowSetAll(morkEnv* ev);
+ void NoteRowSetCol(morkEnv* ev, mork_column inCol);
+ void NoteRowAddCol(morkEnv* ev, mork_column inCol);
+ void NoteRowCutCol(morkEnv* ev, mork_column inCol);
+
+public: // flags bit twiddling
+
+ void SetRowNoted() { mRow_Flags |= morkRow_kNotedBit; }
+ void SetRowRewrite() { mRow_Flags |= morkRow_kRewriteBit; }
+ void SetRowDirty() { mRow_Flags |= morkRow_kDirtyBit; }
+
+ void ClearRowNoted() { mRow_Flags &= (mork_u1) ~morkRow_kNotedBit; }
+ void ClearRowRewrite() { mRow_Flags &= (mork_u1) ~morkRow_kRewriteBit; }
+ void SetRowClean() { mRow_Flags = 0; mRow_Delta = 0; }
+
+ mork_bool IsRowNoted() const
+ { return ( mRow_Flags & morkRow_kNotedBit ) != 0; }
+
+ mork_bool IsRowRewrite() const
+ { return ( mRow_Flags & morkRow_kRewriteBit ) != 0; }
+
+ mork_bool IsRowClean() const
+ { return ( mRow_Flags & morkRow_kDirtyBit ) == 0; }
+
+ mork_bool IsRowDirty() const
+ { return ( mRow_Flags & morkRow_kDirtyBit ) != 0; }
+
+ mork_bool IsRowUsed() const
+ { return mRow_GcUses != 0; }
+
+public: // other row methods
+ morkRow( ) { }
+ explicit morkRow(const mdbOid* inOid) :mRow_Oid(*inOid) { }
+ void InitRow(morkEnv* ev, const mdbOid* inOid, morkRowSpace* ioSpace,
+ mork_size inLength, morkPool* ioPool);
+ // if inLength is nonzero, cells will be allocated from ioPool
+
+ morkRowObject* AcquireRowObject(morkEnv* ev, morkStore* ioStore);
+ nsIMdbRow* AcquireRowHandle(morkEnv* ev, morkStore* ioStore);
+ nsIMdbCell* AcquireCellHandle(morkEnv* ev, morkCell* ioCell,
+ mdb_column inColumn, mork_pos inPos);
+
+ mork_u2 AddRowGcUse(morkEnv* ev);
+ mork_u2 CutRowGcUse(morkEnv* ev);
+
+
+ mork_bool MaybeDirtySpaceStoreAndRow();
+
+public: // internal row methods
+
+ void cut_all_index_entries(morkEnv* ev);
+
+ // void cut_cell_from_space_index(morkEnv* ev, morkCell* ioCell);
+
+ mork_count CountOverlap(morkEnv* ev, morkCell* ioVector, mork_fill inFill);
+ // Count cells in ioVector that change existing cells in this row when
+ // ioVector is added to the row (as in TakeCells()). This is the set
+ // of cells with the same columns in ioVector and mRow_Cells, which do
+ // not have exactly the same value in mCell_Atom, and which do not both
+ // have change status equal to morkChange_kCut (because cutting a cut
+ // cell still yields a cell that has been cut). CountOverlap() also
+ // modifies the change attribute of any cell in ioVector to kDup when
+ // the change was previously kCut and the same column cell was found
+ // in this row with change also equal to kCut; this tells callers later
+ // they need not look for that cell in the row again on a second pass.
+
+ void MergeCells(morkEnv* ev, morkCell* ioVector,
+ mork_fill inVecLength, mork_fill inOldRowFill, mork_fill inOverlap);
+ // MergeCells() is the part of TakeCells() that does the insertion.
+ // inOldRowFill is the old value of mRow_Length, and inOverlap is the
+ // number of cells in the intersection that must be updated.
+
+ void TakeCells(morkEnv* ev, morkCell* ioVector, mork_fill inVecLength,
+ morkStore* ioStore);
+
+ morkCell* NewCell(morkEnv* ev, mdb_column inColumn, mork_pos* outPos,
+ morkStore* ioStore);
+ morkCell* GetCell(morkEnv* ev, mdb_column inColumn, mork_pos* outPos) const;
+ morkCell* CellAt(morkEnv* ev, mork_pos inPos) const;
+
+ mork_aid GetCellAtomAid(morkEnv* ev, mdb_column inColumn) const;
+ // GetCellAtomAid() finds the cell with column inColumn, and sees if the
+ // atom has a token ID, and returns the atom's ID if there is one. Or
+ // else zero is returned if there is no such column, or no atom, or if
+ // the atom has no ID to return. This method is intended to support
+ // efficient updating of column indexes for rows in a row space.
+
+public: // external row methods
+
+ void DirtyAllRowContent(morkEnv* ev);
+
+ morkStore* GetRowSpaceStore(morkEnv* ev) const;
+
+ void AddColumn(morkEnv* ev, mdb_column inColumn,
+ const mdbYarn* inYarn, morkStore* ioStore);
+
+ morkAtom* GetColumnAtom(morkEnv* ev, mdb_column inColumn);
+
+ void NextColumn(morkEnv* ev, mdb_column* ioColumn, mdbYarn* outYarn);
+
+ void SeekColumn(morkEnv* ev, mdb_pos inPos,
+ mdb_column* outColumn, mdbYarn* outYarn);
+
+ void CutColumn(morkEnv* ev, mdb_column inColumn);
+
+ morkRowCellCursor* NewRowCellCursor(morkEnv* ev, mdb_pos inPos);
+
+ void EmptyAllCells(morkEnv* ev);
+ void AddRow(morkEnv* ev, const morkRow* inSourceRow);
+ void SetRow(morkEnv* ev, const morkRow* inSourceRow);
+ void CutAllColumns(morkEnv* ev);
+
+ void OnZeroRowGcUse(morkEnv* ev);
+ // OnZeroRowGcUse() is called when CutRowGcUse() returns zero.
+
+public: // dynamic typing
+
+ mork_bool IsRow() const { return mRow_Tag == morkRow_kTag; }
+
+public: // hash and equal
+
+ mork_u4 HashRow() const
+ {
+ return (mRow_Oid.mOid_Scope << 16) ^ mRow_Oid.mOid_Id;
+ }
+
+ mork_bool EqualRow(const morkRow* ioRow) const
+ {
+ return
+ (
+ ( mRow_Oid.mOid_Scope == ioRow->mRow_Oid.mOid_Scope )
+ && ( mRow_Oid.mOid_Id == ioRow->mRow_Oid.mOid_Id )
+ );
+ }
+
+ mork_bool EqualOid(const mdbOid* ioOid) const
+ {
+ return
+ (
+ ( mRow_Oid.mOid_Scope == ioOid->mOid_Scope )
+ && ( mRow_Oid.mOid_Id == ioOid->mOid_Id )
+ );
+ }
+
+public: // errors
+ static void ZeroColumnError(morkEnv* ev);
+ static void LengthBeyondMaxError(morkEnv* ev);
+ static void NilCellsError(morkEnv* ev);
+ static void NonRowTypeError(morkEnv* ev);
+ static void NonRowTypeWarning(morkEnv* ev);
+ static void GcUsesUnderflowWarning(morkEnv* ev);
+
+private: // copying is not allowed
+ morkRow(const morkRow& other);
+ morkRow& operator=(const morkRow& other);
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKROW_ */
+
diff --git a/components/mork/src/morkRowCellCursor.cpp b/components/mork/src/morkRowCellCursor.cpp
new file mode 100644
index 000000000..684fd115a
--- /dev/null
+++ b/components/mork/src/morkRowCellCursor.cpp
@@ -0,0 +1,253 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKCURSOR_
+#include "morkCursor.h"
+#endif
+
+#ifndef _MORKROWCELLCURSOR_
+#include "morkRowCellCursor.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKROWOBJECT_
+#include "morkRowObject.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkRowCellCursor::CloseMorkNode(morkEnv* ev) // CloseRowCellCursor() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseRowCellCursor(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkRowCellCursor::~morkRowCellCursor() // CloseRowCellCursor() executed earlier
+{
+ CloseMorkNode(mMorkEnv);
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkRowCellCursor::morkRowCellCursor(morkEnv* ev,
+ const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkRowObject* ioRowObject)
+: morkCursor(ev, inUsage, ioHeap)
+, mRowCellCursor_RowObject( 0 )
+, mRowCellCursor_Col( 0 )
+{
+ if ( ev->Good() )
+ {
+ if ( ioRowObject )
+ {
+ morkRow* row = ioRowObject->mRowObject_Row;
+ if ( row )
+ {
+ if ( row->IsRow() )
+ {
+ mCursor_Pos = -1;
+ mCursor_Seed = row->mRow_Seed;
+
+ morkRowObject::SlotStrongRowObject(ioRowObject, ev,
+ &mRowCellCursor_RowObject);
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kRowCellCursor;
+ }
+ else
+ row->NonRowTypeError(ev);
+ }
+ else
+ ioRowObject->NilRowError(ev);
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkRowCellCursor, morkCursor, nsIMdbRowCellCursor)
+
+/*public non-poly*/ void
+morkRowCellCursor::CloseRowCellCursor(morkEnv* ev)
+{
+ if ( this->IsNode() )
+ {
+ mCursor_Pos = -1;
+ mCursor_Seed = 0;
+ morkRowObject::SlotStrongRowObject((morkRowObject*) 0, ev,
+ &mRowCellCursor_RowObject);
+ this->CloseCursor(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkRowCellCursor::NilRowObjectError(morkEnv* ev)
+{
+ ev->NewError("nil mRowCellCursor_RowObject");
+}
+
+/*static*/ void
+morkRowCellCursor::NonRowCellCursorTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkRowCellCursor");
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+// { ----- begin attribute methods -----
+NS_IMETHODIMP
+morkRowCellCursor::SetRow(nsIMdbEnv* mev, nsIMdbRow* ioRow)
+{
+ nsresult outErr = NS_OK;
+ morkRow* row = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ row = (morkRow *) ioRow;
+ morkStore* store = row->GetRowSpaceStore(ev);
+ if ( store )
+ {
+ morkRowObject* rowObj = row->AcquireRowObject(ev, store);
+ if ( rowObj )
+ {
+ morkRowObject::SlotStrongRowObject((morkRowObject*) 0, ev,
+ &mRowCellCursor_RowObject);
+
+ mRowCellCursor_RowObject = rowObj; // take this strong ref
+ mCursor_Seed = row->mRow_Seed;
+
+ row->GetCell(ev, mRowCellCursor_Col, &mCursor_Pos);
+ }
+ }
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowCellCursor::GetRow(nsIMdbEnv* mev, nsIMdbRow** acqRow)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRowObject* rowObj = mRowCellCursor_RowObject;
+ if ( rowObj )
+ outRow = rowObj->AcquireRowHandle(ev);
+
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+ return outErr;
+}
+// } ----- end attribute methods -----
+
+// { ----- begin cell seeking methods -----
+NS_IMETHODIMP
+morkRowCellCursor::SeekCell(
+ nsIMdbEnv* mev, // context
+ mdb_pos inPos, // position of cell in row sequence
+ mdb_column* outColumn, // column for this particular cell
+ nsIMdbCell** acqCell)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+// } ----- end cell seeking methods -----
+
+// { ----- begin cell iteration methods -----
+NS_IMETHODIMP
+morkRowCellCursor::NextCell( // get next cell in the row
+ nsIMdbEnv* mev, // context
+ nsIMdbCell** acqCell, // changes to the next cell in the iteration
+ mdb_column* outColumn, // column for this particular cell
+ mdb_pos* outPos)
+{
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ mdb_column col = 0;
+ mdb_pos pos = mRowCellCursor_Col;
+ if ( pos < 0 )
+ pos = 0;
+ else
+ ++pos;
+
+ morkCell* cell = mRowCellCursor_RowObject->mRowObject_Row->CellAt(ev, pos);
+ if ( cell )
+ {
+ col = cell->GetColumn();
+ *acqCell = mRowCellCursor_RowObject->mRowObject_Row->AcquireCellHandle(ev, cell, col, pos);
+ }
+ else
+ {
+ *acqCell = nullptr;
+ pos = -1;
+ }
+ if ( outPos )
+ *outPos = pos;
+ if ( outColumn )
+ *outColumn = col;
+
+ mRowCellCursor_Col = pos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkRowCellCursor::PickNextCell( // get next cell in row within filter set
+ nsIMdbEnv* mev, // context
+ nsIMdbCell* ioCell, // changes to the next cell in the iteration
+ const mdbColumnSet* inFilterSet, // col set of actual caller interest
+ mdb_column* outColumn, // column for this particular cell
+ mdb_pos* outPos)
+// Note that inFilterSet should not have too many (many more than 10?)
+// cols, since this might imply a potential excessive consumption of time
+// over many cursor calls when looking for column and filter intersection.
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// } ----- end cell iteration methods -----
+
+// } ===== end nsIMdbRowCellCursor methods =====
+
diff --git a/components/mork/src/morkRowCellCursor.h b/components/mork/src/morkRowCellCursor.h
new file mode 100644
index 000000000..ff5c8c67d
--- /dev/null
+++ b/components/mork/src/morkRowCellCursor.h
@@ -0,0 +1,116 @@
+/* -*- 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/. */
+
+#ifndef _MORKROWCELLCURSOR_
+#define _MORKROWCELLCURSOR_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKCURSOR_
+#include "morkCursor.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class orkinRowCellCursor;
+#define morkDerived_kRowCellCursor /*i*/ 0x6343 /* ascii 'cC' */
+
+class morkRowCellCursor : public morkCursor, public nsIMdbRowCellCursor { // row iterator
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // morkFactory* mObject_Factory; // weak ref to suite factory
+
+ // mork_seed mCursor_Seed;
+ // mork_pos mCursor_Pos;
+ // mork_bool mCursor_DoFailOnSeedOutOfSync;
+ // mork_u1 mCursor_Pad[ 3 ]; // explicitly pad to u4 alignment
+
+public: // state is public because the entire Mork system is private
+
+ NS_DECL_ISUPPORTS_INHERITED
+ morkRowObject* mRowCellCursor_RowObject; // strong ref to row
+ mork_column mRowCellCursor_Col; // col of cell last at mCursor_Pos
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseRowCellCursor()
+
+public: // morkRowCellCursor construction & destruction
+ morkRowCellCursor(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkRowObject* ioRowObject);
+ void CloseRowCellCursor(morkEnv* ev); // called by CloseMorkNode();
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD SetRow(nsIMdbEnv* ev, nsIMdbRow* ioRow) override; // sets pos to -1
+ NS_IMETHOD GetRow(nsIMdbEnv* ev, nsIMdbRow** acqRow) override;
+ // } ----- end attribute methods -----
+
+ // { ----- begin cell seeking methods -----
+ NS_IMETHOD SeekCell(
+ nsIMdbEnv* ev, // context
+ mdb_pos inPos, // position of cell in row sequence
+ mdb_column* outColumn, // column for this particular cell
+ nsIMdbCell** acqCell) override; // the cell at inPos
+ // } ----- end cell seeking methods -----
+
+ // { ----- begin cell iteration methods -----
+ NS_IMETHOD NextCell( // get next cell in the row
+ nsIMdbEnv* ev, // context
+ nsIMdbCell** acqCell, // changes to the next cell in the iteration
+ mdb_column* outColumn, // column for this particular cell
+ mdb_pos* outPos) override; // position of cell in row sequence
+
+ NS_IMETHOD PickNextCell( // get next cell in row within filter set
+ nsIMdbEnv* ev, // context
+ nsIMdbCell* ioCell, // changes to the next cell in the iteration
+ const mdbColumnSet* inFilterSet, // col set of actual caller interest
+ mdb_column* outColumn, // column for this particular cell
+ mdb_pos* outPos) override; // position of cell in row sequence
+
+ // Note that inFilterSet should not have too many (many more than 10?)
+ // cols, since this might imply a potential excessive consumption of time
+ // over many cursor calls when looking for column and filter intersection.
+ // } ----- end cell iteration methods -----
+
+
+private: // copying is not allowed
+ morkRowCellCursor(const morkRowCellCursor& other);
+ morkRowCellCursor& operator=(const morkRowCellCursor& other);
+ virtual ~morkRowCellCursor(); // assert that close executed earlier
+
+public: // dynamic type identification
+ mork_bool IsRowCellCursor() const
+ { return IsNode() && mNode_Derived == morkDerived_kRowCellCursor; }
+// } ===== end morkNode methods =====
+
+public: // errors
+ static void NilRowObjectError(morkEnv* ev);
+ static void NonRowCellCursorTypeError(morkEnv* ev);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakRowCellCursor(morkRowCellCursor* me,
+ morkEnv* ev, morkRowCellCursor** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongRowCellCursor(morkRowCellCursor* me,
+ morkEnv* ev, morkRowCellCursor** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKROWCELLCURSOR_ */
diff --git a/components/mork/src/morkRowMap.cpp b/components/mork/src/morkRowMap.cpp
new file mode 100644
index 000000000..35c8b9202
--- /dev/null
+++ b/components/mork/src/morkRowMap.cpp
@@ -0,0 +1,297 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKROWMAP_
+#include "morkRowMap.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkRowMap::CloseMorkNode(morkEnv* ev) // CloseRowMap() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseRowMap(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkRowMap::~morkRowMap() // assert CloseRowMap() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkRowMap::morkRowMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap, mork_size inSlots)
+: morkMap(ev, inUsage, ioHeap,
+ /*inKeySize*/ sizeof(morkRow*), /*inValSize*/ 0,
+ inSlots, ioSlotHeap, /*inHoldChanges*/ morkBool_kFalse)
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kRowMap;
+}
+
+/*public non-poly*/ void
+morkRowMap::CloseRowMap(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ this->CloseMap(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+
+// { ===== begin morkMap poly interface =====
+/*virtual*/ mork_bool //
+morkRowMap::Equal(morkEnv* ev, const void* inKeyA,
+ const void* inKeyB) const
+{
+ MORK_USED_1(ev);
+ return (*(const morkRow**) inKeyA)->EqualRow(*(const morkRow**) inKeyB);
+}
+
+/*virtual*/ mork_u4 //
+morkRowMap::Hash(morkEnv* ev, const void* inKey) const
+{
+ MORK_USED_1(ev);
+ return (*(const morkRow**) inKey)->HashRow();
+}
+// } ===== end morkMap poly interface =====
+
+
+mork_bool
+morkRowMap::AddRow(morkEnv* ev, morkRow* ioRow)
+{
+ if ( ev->Good() )
+ {
+ this->Put(ev, &ioRow, /*val*/ (void*) 0,
+ /*key*/ (void*) 0, /*val*/ (void*) 0, (mork_change**) 0);
+ }
+ return ev->Good();
+}
+
+morkRow*
+morkRowMap::CutOid(morkEnv* ev, const mdbOid* inOid)
+{
+ morkRow row(inOid);
+ morkRow* key = &row;
+ morkRow* oldKey = 0;
+ this->Cut(ev, &key, &oldKey, /*val*/ (void*) 0,
+ (mork_change**) 0);
+
+ return oldKey;
+}
+
+morkRow*
+morkRowMap::CutRow(morkEnv* ev, const morkRow* ioRow)
+{
+ morkRow* oldKey = 0;
+ this->Cut(ev, &ioRow, &oldKey, /*val*/ (void*) 0,
+ (mork_change**) 0);
+
+ return oldKey;
+}
+
+morkRow*
+morkRowMap::GetOid(morkEnv* ev, const mdbOid* inOid)
+{
+ morkRow row(inOid);
+ morkRow* key = &row;
+ morkRow* oldKey = 0;
+ this->Get(ev, &key, &oldKey, /*val*/ (void*) 0, (mork_change**) 0);
+
+ return oldKey;
+}
+
+morkRow*
+morkRowMap::GetRow(morkEnv* ev, const morkRow* ioRow)
+{
+ morkRow* oldKey = 0;
+ this->Get(ev, &ioRow, &oldKey, /*val*/ (void*) 0, (mork_change**) 0);
+
+ return oldKey;
+}
+
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkRowProbeMap::CloseMorkNode(morkEnv* ev) // CloseRowProbeMap() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseRowProbeMap(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkRowProbeMap::~morkRowProbeMap() // assert CloseRowProbeMap() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkRowProbeMap::morkRowProbeMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap, mork_size inSlots)
+: morkProbeMap(ev, inUsage, ioHeap,
+ /*inKeySize*/ sizeof(morkRow*), /*inValSize*/ 0,
+ ioSlotHeap, inSlots,
+ /*inHoldChanges*/ morkBool_kTrue)
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kRowProbeMap;
+}
+
+/*public non-poly*/ void
+morkRowProbeMap::CloseRowProbeMap(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ this->CloseProbeMap(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*virtual*/ mork_test // hit(a,b) implies hash(a) == hash(b)
+morkRowProbeMap::MapTest(morkEnv* ev, const void* inMapKey,
+ const void* inAppKey) const
+{
+ MORK_USED_1(ev);
+ const morkRow* key = *(const morkRow**) inMapKey;
+ if ( key )
+ {
+ mork_bool hit = key->EqualRow(*(const morkRow**) inAppKey);
+ return ( hit ) ? morkTest_kHit : morkTest_kMiss;
+ }
+ else
+ return morkTest_kVoid;
+}
+
+/*virtual*/ mork_u4 // hit(a,b) implies hash(a) == hash(b)
+morkRowProbeMap::MapHash(morkEnv* ev, const void* inAppKey) const
+{
+ const morkRow* key = *(const morkRow**) inAppKey;
+ if ( key )
+ return key->HashRow();
+ else
+ {
+ ev->NilPointerWarning();
+ return 0;
+ }
+}
+
+/*virtual*/ mork_u4
+morkRowProbeMap::ProbeMapHashMapKey(morkEnv* ev,
+ const void* inMapKey) const
+{
+ const morkRow* key = *(const morkRow**) inMapKey;
+ if ( key )
+ return key->HashRow();
+ else
+ {
+ ev->NilPointerWarning();
+ return 0;
+ }
+}
+
+mork_bool
+morkRowProbeMap::AddRow(morkEnv* ev, morkRow* ioRow)
+{
+ if ( ev->Good() )
+ {
+ this->MapAtPut(ev, &ioRow, /*val*/ (void*) 0,
+ /*key*/ (void*) 0, /*val*/ (void*) 0);
+ }
+ return ev->Good();
+}
+
+morkRow*
+morkRowProbeMap::CutOid(morkEnv* ev, const mdbOid* inOid)
+{
+ MORK_USED_1(inOid);
+ morkProbeMap::ProbeMapCutError(ev);
+
+ return 0;
+}
+
+morkRow*
+morkRowProbeMap::CutRow(morkEnv* ev, const morkRow* ioRow)
+{
+ MORK_USED_1(ioRow);
+ morkProbeMap::ProbeMapCutError(ev);
+
+ return 0;
+}
+
+morkRow*
+morkRowProbeMap::GetOid(morkEnv* ev, const mdbOid* inOid)
+{
+ morkRow row(inOid);
+ morkRow* key = &row;
+ morkRow* oldKey = 0;
+ this->MapAt(ev, &key, &oldKey, /*val*/ (void*) 0);
+
+ return oldKey;
+}
+
+morkRow*
+morkRowProbeMap::GetRow(morkEnv* ev, const morkRow* ioRow)
+{
+ morkRow* oldKey = 0;
+ this->MapAt(ev, &ioRow, &oldKey, /*val*/ (void*) 0);
+
+ return oldKey;
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkRowMap.h b/components/mork/src/morkRowMap.h
new file mode 100644
index 000000000..5bf220829
--- /dev/null
+++ b/components/mork/src/morkRowMap.h
@@ -0,0 +1,211 @@
+/* -*- 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/. */
+
+#ifndef _MORKROWMAP_
+#define _MORKROWMAP_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKPROBEMAP_
+#include "morkProbeMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kRowMap /*i*/ 0x724D /* ascii 'rM' */
+
+/*| morkRowMap: maps a set of morkRow by contained Oid
+|*/
+class morkRowMap : public morkMap { // for mapping row IDs to rows
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseRowMap() only if open
+ virtual ~morkRowMap(); // assert that CloseRowMap() executed earlier
+
+public: // morkMap construction & destruction
+ morkRowMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap, mork_size inSlots);
+ void CloseRowMap(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsRowMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kRowMap; }
+// } ===== end morkNode methods =====
+
+// { ===== begin morkMap poly interface =====
+ virtual mork_bool // note: equal(a,b) implies hash(a) == hash(b)
+ Equal(morkEnv* ev, const void* inKeyA, const void* inKeyB) const override;
+ // implemented using morkRow::EqualRow()
+
+ virtual mork_u4 // note: equal(a,b) implies hash(a) == hash(b)
+ Hash(morkEnv* ev, const void* inKey) const override;
+ // implemented using morkRow::HashRow()
+// } ===== end morkMap poly interface =====
+
+public: // other map methods
+
+ mork_bool AddRow(morkEnv* ev, morkRow* ioRow);
+ // AddRow() returns ev->Good()
+
+ morkRow* CutOid(morkEnv* ev, const mdbOid* inOid);
+ // CutRid() returns the row removed equal to inRid, if there was one
+
+ morkRow* CutRow(morkEnv* ev, const morkRow* ioRow);
+ // CutRow() returns the row removed equal to ioRow, if there was one
+
+ morkRow* GetOid(morkEnv* ev, const mdbOid* inOid);
+ // GetOid() returns the row equal to inRid, or else nil
+
+ morkRow* GetRow(morkEnv* ev, const morkRow* ioRow);
+ // GetRow() returns the row equal to ioRow, or else nil
+
+ // note the rows are owned elsewhere, usuall by morkRowSpace
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakRowMap(morkRowMap* me,
+ morkEnv* ev, morkRowMap** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongRowMap(morkRowMap* me,
+ morkEnv* ev, morkRowMap** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+class morkRowMapIter: public morkMapIter{ // typesafe wrapper class
+
+public:
+ morkRowMapIter(morkEnv* ev, morkRowMap* ioMap)
+ : morkMapIter(ev, ioMap) { }
+
+ morkRowMapIter( ) : morkMapIter() { }
+ void InitRowMapIter(morkEnv* ev, morkRowMap* ioMap)
+ { this->InitMapIter(ev, ioMap); }
+
+ mork_change* FirstRow(morkEnv* ev, morkRow** outRowPtr)
+ { return this->First(ev, outRowPtr, /*val*/ (void*) 0); }
+
+ mork_change* NextRow(morkEnv* ev, morkRow** outRowPtr)
+ { return this->Next(ev, outRowPtr, /*val*/ (void*) 0); }
+
+ mork_change* HereRow(morkEnv* ev, morkRow** outRowPtr)
+ { return this->Here(ev, outRowPtr, /*val*/ (void*) 0); }
+
+ mork_change* CutHereRow(morkEnv* ev, morkRow** outRowPtr)
+ { return this->CutHere(ev, outRowPtr, /*val*/ (void*) 0); }
+};
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kRowProbeMap /*i*/ 0x726D /* ascii 'rm' */
+
+/*| morkRowProbeMap: maps a set of morkRow by contained Oid
+|*/
+class morkRowProbeMap : public morkProbeMap { // for mapping row IDs to rows
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseRowProbeMap() only if open
+ virtual ~morkRowProbeMap(); // assert CloseRowProbeMap() executed earlier
+
+public: // morkMap construction & destruction
+ morkRowProbeMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap, mork_size inSlots);
+ void CloseRowProbeMap(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsRowMap() const
+ { return IsNode() && mNode_Derived == morkDerived_kRowMap; }
+// } ===== end morkNode methods =====
+
+ // { ===== begin morkProbeMap methods =====
+ virtual mork_test // hit(a,b) implies hash(a) == hash(b)
+ MapTest(morkEnv* ev, const void* inMapKey, const void* inAppKey) const override;
+
+ virtual mork_u4 // hit(a,b) implies hash(a) == hash(b)
+ MapHash(morkEnv* ev, const void* inAppKey) const override;
+
+ virtual mork_u4 ProbeMapHashMapKey(morkEnv* ev, const void* inMapKey) const override;
+
+ // virtual mork_bool ProbeMapIsKeyNil(morkEnv* ev, void* ioMapKey);
+
+ // virtual void ProbeMapClearKey(morkEnv* ev, // put 'nil' alls keys inside map
+ // void* ioMapKey, mork_count inKeyCount); // array of keys inside map
+
+ // virtual void ProbeMapPushIn(morkEnv* ev, // move (key,val) into the map
+ // const void* inAppKey, const void* inAppVal, // (key,val) outside map
+ // void* outMapKey, void* outMapVal); // (key,val) inside map
+
+ // virtual void ProbeMapPullOut(morkEnv* ev, // move (key,val) out from the map
+ // const void* inMapKey, const void* inMapVal, // (key,val) inside map
+ // void* outAppKey, void* outAppVal) const; // (key,val) outside map
+ // } ===== end morkProbeMap methods =====
+
+public: // other map methods
+
+ mork_bool AddRow(morkEnv* ev, morkRow* ioRow);
+ // AddRow() returns ev->Good()
+
+ morkRow* CutOid(morkEnv* ev, const mdbOid* inOid);
+ // CutRid() returns the row removed equal to inRid, if there was one
+
+ morkRow* CutRow(morkEnv* ev, const morkRow* ioRow);
+ // CutRow() returns the row removed equal to ioRow, if there was one
+
+ morkRow* GetOid(morkEnv* ev, const mdbOid* inOid);
+ // GetOid() returns the row equal to inRid, or else nil
+
+ morkRow* GetRow(morkEnv* ev, const morkRow* ioRow);
+ // GetRow() returns the row equal to ioRow, or else nil
+
+ // note the rows are owned elsewhere, usuall by morkRowSpace
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakRowProbeMap(morkRowProbeMap* me,
+ morkEnv* ev, morkRowProbeMap** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongRowProbeMap(morkRowProbeMap* me,
+ morkEnv* ev, morkRowProbeMap** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+class morkRowProbeMapIter: public morkProbeMapIter{ // typesafe wrapper class
+
+public:
+ morkRowProbeMapIter(morkEnv* ev, morkRowProbeMap* ioMap)
+ : morkProbeMapIter(ev, ioMap) { }
+
+ morkRowProbeMapIter( ) : morkProbeMapIter() { }
+ void InitRowMapIter(morkEnv* ev, morkRowProbeMap* ioMap)
+ { this->InitMapIter(ev, ioMap); }
+
+ mork_change* FirstRow(morkEnv* ev, morkRow** outRowPtr)
+ { return this->First(ev, outRowPtr, /*val*/ (void*) 0); }
+
+ mork_change* NextRow(morkEnv* ev, morkRow** outRowPtr)
+ { return this->Next(ev, outRowPtr, /*val*/ (void*) 0); }
+
+ mork_change* HereRow(morkEnv* ev, morkRow** outRowPtr)
+ { return this->Here(ev, outRowPtr, /*val*/ (void*) 0); }
+
+ mork_change* CutHereRow(morkEnv* ev, morkRow** outRowPtr)
+ { return this->CutHere(ev, outRowPtr, /*val*/ (void*) 0); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKROWMAP_ */
diff --git a/components/mork/src/morkRowObject.cpp b/components/mork/src/morkRowObject.cpp
new file mode 100644
index 000000000..33909c36d
--- /dev/null
+++ b/components/mork/src/morkRowObject.cpp
@@ -0,0 +1,622 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKROWOBJECT_
+#include "morkRowObject.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKROWCELLCURSOR_
+#include "morkRowCellCursor.h"
+#endif
+
+#ifndef _MORKCELLOBJECT_
+#include "morkCellObject.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkRowObject::CloseMorkNode(morkEnv* ev) // CloseRowObject() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseRowObject(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkRowObject::~morkRowObject() // assert CloseRowObject() executed earlier
+{
+ CloseMorkNode(mMorkEnv);
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkRowObject::morkRowObject(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ morkRow* ioRow, morkStore* ioStore)
+: morkObject(ev, inUsage, ioHeap, morkColor_kNone, (morkHandle*) 0)
+, mRowObject_Row( 0 )
+, mRowObject_Store( 0 )
+{
+ if ( ev->Good() )
+ {
+ if ( ioRow && ioStore )
+ {
+ mRowObject_Row = ioRow;
+ mRowObject_Store = ioStore; // morkRowObjects don't ref-cnt the owning store.
+
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kRowObject;
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkRowObject, morkObject, nsIMdbRow)
+// { ===== begin nsIMdbCollection methods =====
+
+// { ----- begin attribute methods -----
+NS_IMETHODIMP
+morkRowObject::GetSeed(nsIMdbEnv* mev,
+ mdb_seed* outSeed)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ *outSeed = (mdb_seed) mRowObject_Row->mRow_Seed;
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+NS_IMETHODIMP
+morkRowObject::GetCount(nsIMdbEnv* mev,
+ mdb_count* outCount)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ *outCount = (mdb_count) mRowObject_Row->mRow_Length;
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowObject::GetPort(nsIMdbEnv* mev,
+ nsIMdbPort** acqPort)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbPort* outPort = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRowSpace* rowSpace = mRowObject_Row->mRow_Space;
+ if ( rowSpace && rowSpace->mSpace_Store )
+ {
+ morkStore* store = mRowObject_Row->GetRowSpaceStore(ev);
+ if ( store )
+ outPort = store->AcquireStoreHandle(ev);
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( acqPort )
+ *acqPort = outPort;
+
+ return outErr;
+}
+// } ----- end attribute methods -----
+
+// { ----- begin cursor methods -----
+NS_IMETHODIMP
+morkRowObject::GetCursor( // make a cursor starting iter at inMemberPos
+ nsIMdbEnv* mev, // context
+ mdb_pos inMemberPos, // zero-based ordinal pos of member in collection
+ nsIMdbCursor** acqCursor)
+{
+ return this->GetRowCellCursor(mev, inMemberPos,
+ (nsIMdbRowCellCursor**) acqCursor);
+}
+// } ----- end cursor methods -----
+
+// { ----- begin ID methods -----
+NS_IMETHODIMP
+morkRowObject::GetOid(nsIMdbEnv* mev,
+ mdbOid* outOid)
+{
+ *outOid = mRowObject_Row->mRow_Oid;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ return (ev) ? ev->AsErr() : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+morkRowObject::BecomeContent(nsIMdbEnv* mev,
+ const mdbOid* inOid)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // remember row->MaybeDirtySpaceStoreAndRow();
+}
+// } ----- end ID methods -----
+
+// { ----- begin activity dropping methods -----
+NS_IMETHODIMP
+morkRowObject::DropActivity( // tell collection usage no longer expected
+ nsIMdbEnv* mev)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+// } ----- end activity dropping methods -----
+
+// } ===== end nsIMdbCollection methods =====
+
+// { ===== begin nsIMdbRow methods =====
+
+// { ----- begin cursor methods -----
+NS_IMETHODIMP
+morkRowObject::GetRowCellCursor( // make a cursor starting iteration at inCellPos
+ nsIMdbEnv* mev, // context
+ mdb_pos inPos, // zero-based ordinal position of cell in row
+ nsIMdbRowCellCursor** acqCursor)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ nsIMdbRowCellCursor* outCursor = 0;
+ if ( ev )
+ {
+ morkRowCellCursor* cursor = mRowObject_Row->NewRowCellCursor(ev, inPos);
+ if ( cursor )
+ {
+ if ( ev->Good() )
+ {
+ cursor->mCursor_Seed = (mork_seed) inPos;
+ outCursor = cursor;
+ NS_ADDREF(cursor);
+ }
+ }
+ outErr = ev->AsErr();
+ }
+ if ( acqCursor )
+ *acqCursor = outCursor;
+ return outErr;
+}
+// } ----- end cursor methods -----
+
+// { ----- begin column methods -----
+NS_IMETHODIMP
+morkRowObject::AddColumn( // make sure a particular column is inside row
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // column to add
+ const mdbYarn* inYarn)
+{
+ nsresult outErr = NS_ERROR_FAILURE;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( mRowObject_Store && mRowObject_Row)
+ mRowObject_Row->AddColumn(ev, inColumn, inYarn, mRowObject_Store);
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowObject::CutColumn( // make sure a column is absent from the row
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn)
+{
+ nsresult outErr = NS_ERROR_FAILURE;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ mRowObject_Row->CutColumn(ev, inColumn);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowObject::CutAllColumns( // remove all columns from the row
+ nsIMdbEnv* mev)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ mRowObject_Row->CutAllColumns(ev);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+// } ----- end column methods -----
+
+// { ----- begin cell methods -----
+NS_IMETHODIMP
+morkRowObject::NewCell( // get cell for specified column, or add new one
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // column to add
+ nsIMdbCell** acqCell)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ GetCell(mev, inColumn, acqCell);
+ if ( !*acqCell )
+ {
+ if ( mRowObject_Store )
+ {
+ mdbYarn yarn; // to pass empty yarn into morkRowObject::AddColumn()
+ yarn.mYarn_Buf = 0;
+ yarn.mYarn_Fill = 0;
+ yarn.mYarn_Size = 0;
+ yarn.mYarn_More = 0;
+ yarn.mYarn_Form = 0;
+ yarn.mYarn_Grow = 0;
+ AddColumn(ev, inColumn, &yarn);
+ GetCell(mev, inColumn, acqCell);
+ }
+ }
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowObject::AddCell( // copy a cell from another row to this row
+ nsIMdbEnv* mev, // context
+ const nsIMdbCell* inCell)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkCell* cell = 0;
+ morkCellObject* cellObj = (morkCellObject*) inCell;
+ if ( cellObj->CanUseCell(mev, morkBool_kFalse, &outErr, &cell) )
+ {
+
+ morkRow* cellRow = cellObj->mCellObject_Row;
+ if ( cellRow )
+ {
+ if ( mRowObject_Row != cellRow )
+ {
+ morkStore* store = mRowObject_Row->GetRowSpaceStore(ev);
+ morkStore* cellStore = cellRow->GetRowSpaceStore(ev);
+ if ( store && cellStore )
+ {
+ mork_column col = cell->GetColumn();
+ morkAtom* atom = cell->mCell_Atom;
+ mdbYarn yarn;
+ morkAtom::AliasYarn(atom, &yarn); // works even when atom is nil
+
+ if ( store != cellStore )
+ col = store->CopyToken(ev, col, cellStore);
+ if ( ev->Good() )
+ AddColumn(ev, col, &yarn);
+ }
+ else
+ ev->NilPointerError();
+ }
+ }
+ else
+ ev->NilPointerError();
+ }
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowObject::GetCell( // find a cell in this row
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // column to find
+ nsIMdbCell** acqCell)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbCell* outCell = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+
+ if ( ev )
+ {
+ if ( inColumn )
+ {
+ mork_pos pos = 0;
+ morkCell* cell = mRowObject_Row->GetCell(ev, inColumn, &pos);
+ if ( cell )
+ {
+ outCell = mRowObject_Row->AcquireCellHandle(ev, cell, inColumn, pos);
+ }
+ }
+ else
+ mRowObject_Row->ZeroColumnError(ev);
+
+ outErr = ev->AsErr();
+ }
+ if ( acqCell )
+ *acqCell = outCell;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowObject::EmptyAllCells( // make all cells in row empty of content
+ nsIMdbEnv* mev)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ EmptyAllCells(ev);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+// } ----- end cell methods -----
+
+// { ----- begin row methods -----
+NS_IMETHODIMP
+morkRowObject::AddRow( // add all cells in another row to this one
+ nsIMdbEnv* mev, // context
+ nsIMdbRow* ioSourceRow)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRow* unsafeSource = (morkRow*) ioSourceRow; // unsafe cast
+// if ( unsafeSource->CanUseRow(mev, morkBool_kFalse, &outErr, &source) )
+ {
+ mRowObject_Row->AddRow(ev, unsafeSource);
+ }
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowObject::SetRow( // make exact duplicate of another row
+ nsIMdbEnv* mev, // context
+ nsIMdbRow* ioSourceRow)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRowObject *sourceObject = (morkRowObject *) ioSourceRow; // unsafe cast
+ morkRow* unsafeSource = sourceObject->mRowObject_Row;
+// if ( unsafeSource->CanUseRow(mev, morkBool_kFalse, &outErr, &source) )
+ {
+ mRowObject_Row->SetRow(ev, unsafeSource);
+ }
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+// } ----- end row methods -----
+
+// { ----- begin blob methods -----
+NS_IMETHODIMP
+morkRowObject::SetCellYarn( // synonym for AddColumn()
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // column to add
+ const mdbYarn* inYarn)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( mRowObject_Store )
+ AddColumn(ev, inColumn, inYarn);
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+NS_IMETHODIMP
+morkRowObject::GetCellYarn(
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // column to read
+ mdbYarn* outYarn) // writes some yarn slots
+// copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( mRowObject_Store && mRowObject_Row)
+ {
+ morkAtom* atom = mRowObject_Row->GetColumnAtom(ev, inColumn);
+ morkAtom::GetYarn(atom, outYarn);
+ }
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowObject::AliasCellYarn(
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // column to alias
+ mdbYarn* outYarn) // writes ALL yarn slots
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( mRowObject_Store && mRowObject_Row)
+ {
+ morkAtom* atom = mRowObject_Row->GetColumnAtom(ev, inColumn);
+ morkAtom::AliasYarn(atom, outYarn);
+ // note nil atom works and sets yarn correctly
+ }
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowObject::NextCellYarn(nsIMdbEnv* mev, // iterative version of GetCellYarn()
+ mdb_column* ioColumn, // next column to read
+ mdbYarn* outYarn) // writes some yarn slots
+// copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+//
+// The ioColumn argument is an inout parameter which initially contains the
+// last column accessed and returns the next column corresponding to the
+// content read into the yarn. Callers should start with a zero column
+// value to say 'no previous column', which causes the first column to be
+// read. Then the value returned in ioColumn is perfect for the next call
+// to NextCellYarn(), since it will then be the previous column accessed.
+// Callers need only examine the column token returned to see which cell
+// in the row is being read into the yarn. When no more columns remain,
+// and the iteration has ended, ioColumn will return a zero token again.
+// So iterating over cells starts and ends with a zero column token.
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( mRowObject_Store && mRowObject_Row)
+ mRowObject_Row->NextColumn(ev, ioColumn, outYarn);
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkRowObject::SeekCellYarn( // resembles nsIMdbRowCellCursor::SeekCell()
+ nsIMdbEnv* mev, // context
+ mdb_pos inPos, // position of cell in row sequence
+ mdb_column* outColumn, // column for this particular cell
+ mdbYarn* outYarn) // writes some yarn slots
+// copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+// Callers can pass nil for outYarn to indicate no interest in content, so
+// only the outColumn value is returned. NOTE to subclasses: you must be
+// able to ignore outYarn when the pointer is nil; please do not crash.
+
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( mRowObject_Store && mRowObject_Row)
+ mRowObject_Row->SeekColumn(ev, inPos, outColumn, outYarn);
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+// } ----- end blob methods -----
+
+
+// } ===== end nsIMdbRow methods =====
+
+
+
+/*public non-poly*/ void
+morkRowObject::CloseRowObject(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ morkRow* row = mRowObject_Row;
+ mRowObject_Row = 0;
+ this->CloseObject(ev);
+ this->MarkShut();
+
+ if ( row )
+ {
+ MORK_ASSERT(row->mRow_Object == this);
+ if ( row->mRow_Object == this )
+ {
+ row->mRow_Object = 0; // just nil this slot -- cut ref down below
+
+ mRowObject_Store = 0; // morkRowObjects don't ref-cnt the owning store.
+
+ this->CutWeakRef(ev->AsMdbEnv()); // do last, because it might self destroy
+ }
+ }
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkRowObject::NonRowObjectTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkRowObject");
+}
+
+/*static*/ void
+morkRowObject::NilRowError(morkEnv* ev)
+{
+ ev->NewError("nil mRowObject_Row");
+}
+
+/*static*/ void
+morkRowObject::NilStoreError(morkEnv* ev)
+{
+ ev->NewError("nil mRowObject_Store");
+}
+
+/*static*/ void
+morkRowObject::RowObjectRowNotSelfError(morkEnv* ev)
+{
+ ev->NewError("mRowObject_Row->mRow_Object != self");
+}
+
+
+nsIMdbRow*
+morkRowObject::AcquireRowHandle(morkEnv* ev) // mObject_Handle
+{
+ AddRef();
+ return this;
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkRowObject.h b/components/mork/src/morkRowObject.h
new file mode 100644
index 000000000..c30494a45
--- /dev/null
+++ b/components/mork/src/morkRowObject.h
@@ -0,0 +1,200 @@
+/* -*- 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/. */
+
+#ifndef _MORKROWOBJECT_
+#define _MORKROWOBJECT_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class nsIMdbRow;
+#define morkDerived_kRowObject /*i*/ 0x724F /* ascii 'rO' */
+
+class morkRowObject : public morkObject, public nsIMdbRow { //
+
+public: // state is public because the entire Mork system is private
+ NS_DECL_ISUPPORTS_INHERITED
+
+ morkRow* mRowObject_Row; // non-refcounted alias to morkRow
+ morkStore* mRowObject_Store; // non-refcounted ptr to store containing row
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseRowObject() only if open
+
+public: // morkRowObject construction & destruction
+ morkRowObject(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkRow* ioRow, morkStore* ioStore);
+ void CloseRowObject(morkEnv* ev); // called by CloseMorkNode();
+
+// { ===== begin nsIMdbCollection methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD GetSeed(nsIMdbEnv* ev,
+ mdb_seed* outSeed) override; // member change count
+ NS_IMETHOD GetCount(nsIMdbEnv* ev,
+ mdb_count* outCount) override; // member count
+
+ NS_IMETHOD GetPort(nsIMdbEnv* ev,
+ nsIMdbPort** acqPort) override; // collection container
+ // } ----- end attribute methods -----
+
+ // { ----- begin cursor methods -----
+ NS_IMETHOD GetCursor( // make a cursor starting iter at inMemberPos
+ nsIMdbEnv* ev, // context
+ mdb_pos inMemberPos, // zero-based ordinal pos of member in collection
+ nsIMdbCursor** acqCursor) override; // acquire new cursor instance
+ // } ----- end cursor methods -----
+
+ // { ----- begin ID methods -----
+ NS_IMETHOD GetOid(nsIMdbEnv* ev,
+ mdbOid* outOid) override; // read object identity
+ NS_IMETHOD BecomeContent(nsIMdbEnv* ev,
+ const mdbOid* inOid) override; // exchange content
+ // } ----- end ID methods -----
+
+ // { ----- begin activity dropping methods -----
+ NS_IMETHOD DropActivity( // tell collection usage no longer expected
+ nsIMdbEnv* ev) override;
+ // } ----- end activity dropping methods -----
+
+// } ===== end nsIMdbCollection methods =====
+// { ===== begin nsIMdbRow methods =====
+
+ // { ----- begin cursor methods -----
+ NS_IMETHOD GetRowCellCursor( // make a cursor starting iteration at inRowPos
+ nsIMdbEnv* ev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ nsIMdbRowCellCursor** acqCursor) override; // acquire new cursor instance
+ // } ----- end cursor methods -----
+
+ // { ----- begin column methods -----
+ NS_IMETHOD AddColumn( // make sure a particular column is inside row
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // column to add
+ const mdbYarn* inYarn) override; // cell value to install
+
+ NS_IMETHOD CutColumn( // make sure a column is absent from the row
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn) override; // column to ensure absent from row
+
+ NS_IMETHOD CutAllColumns( // remove all columns from the row
+ nsIMdbEnv* ev) override; // context
+ // } ----- end column methods -----
+
+ // { ----- begin cell methods -----
+ NS_IMETHOD NewCell( // get cell for specified column, or add new one
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // column to add
+ nsIMdbCell** acqCell) override; // cell column and value
+
+ NS_IMETHOD AddCell( // copy a cell from another row to this row
+ nsIMdbEnv* ev, // context
+ const nsIMdbCell* inCell) override; // cell column and value
+
+ NS_IMETHOD GetCell( // find a cell in this row
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // column to find
+ nsIMdbCell** acqCell) override; // cell for specified column, or null
+
+ NS_IMETHOD EmptyAllCells( // make all cells in row empty of content
+ nsIMdbEnv* ev) override; // context
+ // } ----- end cell methods -----
+
+ // { ----- begin row methods -----
+ NS_IMETHOD AddRow( // add all cells in another row to this one
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioSourceRow) override; // row to union with
+
+ NS_IMETHOD SetRow( // make exact duplicate of another row
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioSourceRow) override; // row to duplicate
+ // } ----- end row methods -----
+
+ // { ----- begin blob methods -----
+ NS_IMETHOD SetCellYarn(nsIMdbEnv* ev, // synonym for AddColumn()
+ mdb_column inColumn, // column to write
+ const mdbYarn* inYarn) override; // reads from yarn slots
+ // make this text object contain content from the yarn's buffer
+
+ NS_IMETHOD GetCellYarn(nsIMdbEnv* ev,
+ mdb_column inColumn, // column to read
+ mdbYarn* outYarn) override; // writes some yarn slots
+ // copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+
+ NS_IMETHOD AliasCellYarn(nsIMdbEnv* ev,
+ mdb_column inColumn, // column to alias
+ mdbYarn* outYarn) override; // writes ALL yarn slots
+
+ NS_IMETHOD NextCellYarn(nsIMdbEnv* ev, // iterative version of GetCellYarn()
+ mdb_column* ioColumn, // next column to read
+ mdbYarn* outYarn) override; // writes some yarn slots
+ // copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+ //
+ // The ioColumn argument is an inout parameter which initially contains the
+ // last column accessed and returns the next column corresponding to the
+ // content read into the yarn. Callers should start with a zero column
+ // value to say 'no previous column', which causes the first column to be
+ // read. Then the value returned in ioColumn is perfect for the next call
+ // to NextCellYarn(), since it will then be the previous column accessed.
+ // Callers need only examine the column token returned to see which cell
+ // in the row is being read into the yarn. When no more columns remain,
+ // and the iteration has ended, ioColumn will return a zero token again.
+ // So iterating over cells starts and ends with a zero column token.
+
+ NS_IMETHOD SeekCellYarn( // resembles nsIMdbRowCellCursor::SeekCell()
+ nsIMdbEnv* ev, // context
+ mdb_pos inPos, // position of cell in row sequence
+ mdb_column* outColumn, // column for this particular cell
+ mdbYarn* outYarn) override; // writes some yarn slots
+ // copy content into the yarn buffer, and update mYarn_Fill and mYarn_Form
+ // Callers can pass nil for outYarn to indicate no interest in content, so
+ // only the outColumn value is returned. NOTE to subclasses: you must be
+ // able to ignore outYarn when the pointer is nil; please do not crash.
+
+ // } ----- end blob methods -----
+
+// } ===== end nsIMdbRow methods =====
+
+private: // copying is not allowed
+ morkRowObject(const morkRowObject& other);
+ morkRowObject& operator=(const morkRowObject& other);
+ virtual ~morkRowObject(); // assert that CloseRowObject() executed earlier
+
+public: // dynamic type identification
+ mork_bool IsRowObject() const
+ { return IsNode() && mNode_Derived == morkDerived_kRowObject; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ static void NonRowObjectTypeError(morkEnv* ev);
+ static void NilRowError(morkEnv* ev);
+ static void NilStoreError(morkEnv* ev);
+ static void RowObjectRowNotSelfError(morkEnv* ev);
+
+public: // other row node methods
+
+ nsIMdbRow* AcquireRowHandle(morkEnv* ev); // mObject_Handle
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakRowObject(morkRowObject* me,
+ morkEnv* ev, morkRowObject** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongRowObject(morkRowObject* me,
+ morkEnv* ev, morkRowObject** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKROWOBJECT_ */
diff --git a/components/mork/src/morkRowSpace.cpp b/components/mork/src/morkRowSpace.cpp
new file mode 100644
index 000000000..10f2416f5
--- /dev/null
+++ b/components/mork/src/morkRowSpace.cpp
@@ -0,0 +1,632 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKSPACE_
+#include "morkSpace.h"
+#endif
+
+#ifndef _MORKNODEMAP_
+#include "morkNodeMap.h"
+#endif
+
+#ifndef _MORKROWMAP_
+#include "morkRowMap.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKROWSPACE_
+#include "morkRowSpace.h"
+#endif
+
+#ifndef _MORKPOOL_
+#include "morkPool.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKTABLE_
+#include "morkTable.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+#ifndef _MORKATOMMAP_
+#include "morkAtomMap.h"
+#endif
+
+#ifndef _MORKROWOBJECT_
+#include "morkRowObject.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkRowSpace::CloseMorkNode(morkEnv* ev) // CloseRowSpace() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseRowSpace(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkRowSpace::~morkRowSpace() // assert CloseRowSpace() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkRowSpace::morkRowSpace(morkEnv* ev,
+ const morkUsage& inUsage, mork_scope inScope, morkStore* ioStore,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+: morkSpace(ev, inUsage, inScope, ioStore, ioHeap, ioSlotHeap)
+, mRowSpace_SlotHeap( ioSlotHeap )
+, mRowSpace_Rows(ev, morkUsage::kMember, (nsIMdbHeap*) 0, ioSlotHeap,
+ morkRowSpace_kStartRowMapSlotCount)
+, mRowSpace_Tables(ev, morkUsage::kMember, (nsIMdbHeap*) 0, ioSlotHeap)
+, mRowSpace_NextTableId( 1 )
+, mRowSpace_NextRowId( 1 )
+
+, mRowSpace_IndexCount( 0 )
+{
+ morkAtomRowMap** cache = mRowSpace_IndexCache;
+ morkAtomRowMap** cacheEnd = cache + morkRowSpace_kPrimeCacheSize;
+ while ( cache < cacheEnd )
+ *cache++ = 0; // put nil into every slot of cache table
+
+ if ( ev->Good() )
+ {
+ if ( ioSlotHeap )
+ {
+ mNode_Derived = morkDerived_kRowSpace;
+
+ // the morkSpace base constructor handles any dirty propagation
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+/*public non-poly*/ void
+morkRowSpace::CloseRowSpace(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ morkAtomRowMap** cache = mRowSpace_IndexCache;
+ morkAtomRowMap** cacheEnd = cache + morkRowSpace_kPrimeCacheSize;
+ --cache; // prepare for preincrement:
+ while ( ++cache < cacheEnd )
+ {
+ if ( *cache )
+ morkAtomRowMap::SlotStrongAtomRowMap(0, ev, cache);
+ }
+
+ mRowSpace_Tables.CloseMorkNode(ev);
+
+ morkStore* store = mSpace_Store;
+ if ( store )
+ this->CutAllRows(ev, &store->mStore_Pool);
+
+ mRowSpace_Rows.CloseMorkNode(ev);
+ this->CloseSpace(ev);
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkRowSpace::NonRowSpaceTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkRowSpace");
+}
+
+/*static*/ void
+morkRowSpace::ZeroKindError(morkEnv* ev)
+{
+ ev->NewError("zero table kind");
+}
+
+/*static*/ void
+morkRowSpace::ZeroScopeError(morkEnv* ev)
+{
+ ev->NewError("zero row scope");
+}
+
+/*static*/ void
+morkRowSpace::ZeroTidError(morkEnv* ev)
+{
+ ev->NewError("zero table ID");
+}
+
+/*static*/ void
+morkRowSpace::MinusOneRidError(morkEnv* ev)
+{
+ ev->NewError("row ID is -1");
+}
+
+///*static*/ void
+//morkRowSpace::ExpectAutoIdOnlyError(morkEnv* ev)
+//{
+// ev->NewError("zero row ID");
+//}
+
+///*static*/ void
+//morkRowSpace::ExpectAutoIdNeverError(morkEnv* ev)
+//{
+//}
+
+mork_num
+morkRowSpace::CutAllRows(morkEnv* ev, morkPool* ioPool)
+{
+ if ( this->IsRowSpaceClean() )
+ this->MaybeDirtyStoreAndSpace();
+
+ mork_num outSlots = mRowSpace_Rows.MapFill();
+
+#ifdef MORK_ENABLE_ZONE_ARENAS
+ MORK_USED_2(ev, ioPool);
+ return 0;
+#else /*MORK_ENABLE_ZONE_ARENAS*/
+ morkZone* zone = &mSpace_Store->mStore_Zone;
+ morkRow* r = 0; // old key row in the map
+ mork_change* c = 0;
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ morkRowProbeMapIter i(ev, &mRowSpace_Rows);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ morkRowMapIter i(ev, &mRowSpace_Rows);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+ for ( c = i.FirstRow(ev, &r); c && ev->Good();
+ c = i.NextRow(ev, &r) )
+ {
+ if ( r )
+ {
+ if ( r->IsRow() )
+ {
+ if ( r->mRow_Object )
+ {
+ morkRowObject::SlotWeakRowObject((morkRowObject*) 0, ev,
+ &r->mRow_Object);
+ }
+ ioPool->ZapRow(ev, r, zone);
+ }
+ else
+ r->NonRowTypeWarning(ev);
+ }
+ else
+ ev->NilPointerError();
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ // cut nothing from the map
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ i.CutHereRow(ev, /*key*/ (morkRow**) 0);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ }
+#endif /*MORK_ENABLE_ZONE_ARENAS*/
+
+
+ return outSlots;
+}
+
+morkTable*
+morkRowSpace::FindTableByKind(morkEnv* ev, mork_kind inTableKind)
+{
+ if ( inTableKind )
+ {
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+
+ morkTableMapIter i(ev, &mRowSpace_Tables);
+ morkTable* table = i.FirstTable(ev);
+ for ( ; table && ev->Good(); table = i.NextTable(ev) )
+
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+ mork_tid* key = 0; // nil pointer to suppress key access
+ morkTable* table = 0; // old table in the map
+
+ mork_change* c = 0;
+ morkTableMapIter i(ev, &mRowSpace_Tables);
+ for ( c = i.FirstTable(ev, key, &table); c && ev->Good();
+ c = i.NextTable(ev, key, &table) )
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+ {
+ if ( table->mTable_Kind == inTableKind )
+ return table;
+ }
+ }
+ else
+ this->ZeroKindError(ev);
+
+ return (morkTable*) 0;
+}
+
+morkTable*
+morkRowSpace::NewTableWithTid(morkEnv* ev, mork_tid inTid,
+ mork_kind inTableKind,
+ const mdbOid* inOptionalMetaRowOid) // can be nil to avoid specifying
+{
+ morkTable* outTable = 0;
+ morkStore* store = mSpace_Store;
+
+ if ( inTableKind && store )
+ {
+ mdb_bool mustBeUnique = morkBool_kFalse;
+ nsIMdbHeap* heap = store->mPort_Heap;
+ morkTable* table = new(*heap, ev)
+ morkTable(ev, morkUsage::kHeap, heap, store, heap, this,
+ inOptionalMetaRowOid, inTid, inTableKind, mustBeUnique);
+ if ( table )
+ {
+ if ( mRowSpace_Tables.AddTable(ev, table) )
+ {
+ outTable = table;
+ if ( mRowSpace_NextTableId <= inTid )
+ mRowSpace_NextTableId = inTid + 1;
+ }
+
+ if ( this->IsRowSpaceClean() && store->mStore_CanDirty )
+ this->MaybeDirtyStoreAndSpace(); // morkTable does already
+
+ }
+ }
+ else if ( store )
+ this->ZeroKindError(ev);
+ else
+ this->NilSpaceStoreError(ev);
+
+ return outTable;
+}
+
+morkTable*
+morkRowSpace::NewTable(morkEnv* ev, mork_kind inTableKind,
+ mdb_bool inMustBeUnique,
+ const mdbOid* inOptionalMetaRowOid) // can be nil to avoid specifying
+{
+ morkTable* outTable = 0;
+ morkStore* store = mSpace_Store;
+
+ if ( inTableKind && store )
+ {
+ if ( inMustBeUnique ) // need to look for existing table first?
+ outTable = this->FindTableByKind(ev, inTableKind);
+
+ if ( !outTable && ev->Good() )
+ {
+ mork_tid id = this->MakeNewTableId(ev);
+ if ( id )
+ {
+ nsIMdbHeap* heap = mSpace_Store->mPort_Heap;
+ morkTable* table = new(*heap, ev)
+ morkTable(ev, morkUsage::kHeap, heap, mSpace_Store, heap, this,
+ inOptionalMetaRowOid, id, inTableKind, inMustBeUnique);
+ if ( table )
+ {
+ if ( mRowSpace_Tables.AddTable(ev, table) )
+ outTable = table;
+ else
+ table->Release();
+
+ if ( this->IsRowSpaceClean() && store->mStore_CanDirty )
+ this->MaybeDirtyStoreAndSpace(); // morkTable does already
+ }
+ }
+ }
+ }
+ else if ( store )
+ this->ZeroKindError(ev);
+ else
+ this->NilSpaceStoreError(ev);
+
+ return outTable;
+}
+
+mork_tid
+morkRowSpace::MakeNewTableId(morkEnv* ev)
+{
+ mork_tid outTid = 0;
+ mork_tid id = mRowSpace_NextTableId;
+ mork_num count = 9; // try up to eight times
+
+ while ( !outTid && --count ) // still trying to find an unused table ID?
+ {
+ if ( !mRowSpace_Tables.GetTable(ev, id) )
+ outTid = id;
+ else
+ {
+ MORK_ASSERT(morkBool_kFalse); // alert developer about ID problems
+ ++id;
+ }
+ }
+
+ mRowSpace_NextTableId = id + 1;
+ return outTid;
+}
+
+#define MAX_AUTO_ID (morkRow_kMinusOneRid - 2)
+mork_rid
+morkRowSpace::MakeNewRowId(morkEnv* ev)
+{
+ mork_rid outRid = 0;
+ mork_rid id = mRowSpace_NextRowId;
+ mork_num count = 9; // try up to eight times
+ mdbOid oid;
+ oid.mOid_Scope = this->SpaceScope();
+
+ // still trying to find an unused row ID?
+ while (!outRid && --count && id <= MAX_AUTO_ID)
+ {
+ oid.mOid_Id = id;
+ if ( !mRowSpace_Rows.GetOid(ev, &oid) )
+ outRid = id;
+ else
+ {
+ MORK_ASSERT(morkBool_kFalse); // alert developer about ID problems
+ ++id;
+ }
+ }
+
+ if (id < MAX_AUTO_ID)
+ mRowSpace_NextRowId = id + 1;
+ return outRid;
+}
+
+morkAtomRowMap*
+morkRowSpace::make_index(morkEnv* ev, mork_column inCol)
+{
+ morkAtomRowMap* outMap = 0;
+ nsIMdbHeap* heap = mRowSpace_SlotHeap;
+ if ( heap ) // have expected heap for allocations?
+ {
+ morkAtomRowMap* map = new(*heap, ev)
+ morkAtomRowMap(ev, morkUsage::kHeap, heap, heap, inCol);
+
+ if ( map ) // able to create new map index?
+ {
+ if ( ev->Good() ) // no errors during construction?
+ {
+#ifdef MORK_ENABLE_PROBE_MAPS
+ morkRowProbeMapIter i(ev, &mRowSpace_Rows);
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ morkRowMapIter i(ev, &mRowSpace_Rows);
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ mork_change* c = 0;
+ morkRow* row = 0;
+ mork_aid aidKey = 0;
+
+ for ( c = i.FirstRow(ev, &row); c && ev->Good();
+ c = i.NextRow(ev, &row) ) // another row in space?
+ {
+ aidKey = row->GetCellAtomAid(ev, inCol);
+ if ( aidKey ) // row has indexed attribute?
+ map->AddAid(ev, aidKey, row); // include in map
+ }
+ }
+ if ( ev->Good() ) // no errors constructing index?
+ outMap = map; // return from function
+ else
+ map->CutStrongRef(ev); // discard map on error
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ return outMap;
+}
+
+morkAtomRowMap*
+morkRowSpace::ForceMap(morkEnv* ev, mork_column inCol)
+{
+ morkAtomRowMap* outMap = this->FindMap(ev, inCol);
+
+ if ( !outMap && ev->Good() ) // no such existing index?
+ {
+ if ( mRowSpace_IndexCount < morkRowSpace_kMaxIndexCount )
+ {
+ morkAtomRowMap* map = this->make_index(ev, inCol);
+ if ( map ) // created a new index for col?
+ {
+ mork_count wrap = 0; // count times wrap-around occurs
+ morkAtomRowMap** slot = mRowSpace_IndexCache; // table
+ morkAtomRowMap** end = slot + morkRowSpace_kPrimeCacheSize;
+ slot += ( inCol % morkRowSpace_kPrimeCacheSize ); // hash
+ while ( *slot ) // empty slot not yet found?
+ {
+ if ( ++slot >= end ) // wrap around?
+ {
+ slot = mRowSpace_IndexCache; // back to table start
+ if ( ++wrap > 1 ) // wrapped more than once?
+ {
+ ev->NewError("no free cache slots"); // disaster
+ break; // end while loop
+ }
+ }
+ }
+ if ( ev->Good() ) // everything went just fine?
+ {
+ ++mRowSpace_IndexCount; // note another new map
+ *slot = map; // install map in the hash table
+ outMap = map; // return the new map from function
+ }
+ else
+ map->CutStrongRef(ev); // discard map on error
+ }
+ }
+ else
+ ev->NewError("too many indexes"); // why so many indexes?
+ }
+ return outMap;
+}
+
+morkAtomRowMap*
+morkRowSpace::FindMap(morkEnv* ev, mork_column inCol)
+{
+ if ( mRowSpace_IndexCount && ev->Good() )
+ {
+ mork_count wrap = 0; // count times wrap-around occurs
+ morkAtomRowMap** slot = mRowSpace_IndexCache; // table
+ morkAtomRowMap** end = slot + morkRowSpace_kPrimeCacheSize;
+ slot += ( inCol % morkRowSpace_kPrimeCacheSize ); // hash
+ morkAtomRowMap* map = *slot;
+ while ( map ) // another used slot to examine?
+ {
+ if ( inCol == map->mAtomRowMap_IndexColumn ) // found col?
+ return map;
+ if ( ++slot >= end ) // wrap around?
+ {
+ slot = mRowSpace_IndexCache;
+ if ( ++wrap > 1 ) // wrapped more than once?
+ return (morkAtomRowMap*) 0; // stop searching
+ }
+ map = *slot;
+ }
+ }
+ return (morkAtomRowMap*) 0;
+}
+
+morkRow*
+morkRowSpace::FindRow(morkEnv* ev, mork_column inCol, const mdbYarn* inYarn)
+{
+ morkRow* outRow = 0;
+
+ // if yarn hasn't been atomized, there can't be a corresponding row,
+ // so pass in false to not create the row - should help history bloat
+ morkAtom* atom = mSpace_Store->YarnToAtom(ev, inYarn, false);
+ if ( atom ) // have or created an atom corresponding to input yarn?
+ {
+ mork_aid atomAid = atom->GetBookAtomAid();
+ if ( atomAid ) // atom has an identity for use in hash table?
+ {
+ morkAtomRowMap* map = this->ForceMap(ev, inCol);
+ if ( map ) // able to find or create index for col?
+ {
+ outRow = map->GetAid(ev, atomAid); // search for row
+ }
+ }
+ }
+
+ return outRow;
+}
+
+morkRow*
+morkRowSpace::NewRowWithOid(morkEnv* ev, const mdbOid* inOid)
+{
+ morkRow* outRow = mRowSpace_Rows.GetOid(ev, inOid);
+ MORK_ASSERT(outRow==0);
+ if ( !outRow && ev->Good() )
+ {
+ morkStore* store = mSpace_Store;
+ if ( store )
+ {
+ morkPool* pool = this->GetSpaceStorePool();
+ morkRow* row = pool->NewRow(ev, &store->mStore_Zone);
+ if ( row )
+ {
+ row->InitRow(ev, inOid, this, /*length*/ 0, pool);
+
+ if ( ev->Good() && mRowSpace_Rows.AddRow(ev, row) )
+ {
+ outRow = row;
+ mork_rid rid = inOid->mOid_Id;
+ if ( mRowSpace_NextRowId <= rid )
+ mRowSpace_NextRowId = rid + 1;
+ }
+ else
+ pool->ZapRow(ev, row, &store->mStore_Zone);
+
+ if ( this->IsRowSpaceClean() && store->mStore_CanDirty )
+ this->MaybeDirtyStoreAndSpace(); // InitRow() does already
+ }
+ }
+ else
+ this->NilSpaceStoreError(ev);
+ }
+ return outRow;
+}
+
+morkRow*
+morkRowSpace::NewRow(morkEnv* ev)
+{
+ morkRow* outRow = 0;
+ if ( ev->Good() )
+ {
+ mork_rid id = this->MakeNewRowId(ev);
+ if ( id )
+ {
+ morkStore* store = mSpace_Store;
+ if ( store )
+ {
+ mdbOid oid;
+ oid.mOid_Scope = this->SpaceScope();
+ oid.mOid_Id = id;
+ morkPool* pool = this->GetSpaceStorePool();
+ morkRow* row = pool->NewRow(ev, &store->mStore_Zone);
+ if ( row )
+ {
+ row->InitRow(ev, &oid, this, /*length*/ 0, pool);
+
+ if ( ev->Good() && mRowSpace_Rows.AddRow(ev, row) )
+ outRow = row;
+ else
+ pool->ZapRow(ev, row, &store->mStore_Zone);
+
+ if ( this->IsRowSpaceClean() && store->mStore_CanDirty )
+ this->MaybeDirtyStoreAndSpace(); // InitRow() does already
+ }
+ }
+ else
+ this->NilSpaceStoreError(ev);
+ }
+ }
+ return outRow;
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+morkRowSpaceMap::~morkRowSpaceMap()
+{
+}
+
+morkRowSpaceMap::morkRowSpaceMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+ : morkNodeMap(ev, inUsage, ioHeap, ioSlotHeap)
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kRowSpaceMap;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkRowSpace.h b/components/mork/src/morkRowSpace.h
new file mode 100644
index 000000000..0ef331bb8
--- /dev/null
+++ b/components/mork/src/morkRowSpace.h
@@ -0,0 +1,233 @@
+/* -*- 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/. */
+
+#ifndef _MORKROWSPACE_
+#define _MORKROWSPACE_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKSPACE_
+#include "morkSpace.h"
+#endif
+
+#ifndef _MORKNODEMAP_
+#include "morkNodeMap.h"
+#endif
+
+#ifndef _MORKROWMAP_
+#include "morkRowMap.h"
+#endif
+
+#ifndef _MORKTABLE_
+#include "morkTable.h"
+#endif
+
+#ifndef _MORKARRAY_
+#include "morkArray.h"
+#endif
+
+#ifndef _MORKDEQUE_
+#include "morkDeque.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kRowSpace /*i*/ 0x7253 /* ascii 'rS' */
+
+#define morkRowSpace_kStartRowMapSlotCount 11
+
+#define morkRowSpace_kMaxIndexCount 8 /* no more indexes than this */
+#define morkRowSpace_kPrimeCacheSize 17 /* should be prime number */
+
+class morkAtomRowMap;
+
+/*| morkRowSpace:
+|*/
+class morkRowSpace : public morkSpace { //
+
+// public: // slots inherited from morkSpace (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // morkStore* mSpace_Store; // weak ref to containing store
+
+ // mork_bool mSpace_DoAutoIDs; // whether db should assign member IDs
+ // mork_bool mSpace_HaveDoneAutoIDs; // whether actually auto assigned IDs
+ // mork_u1 mSpace_Pad[ 2 ]; // pad to u4 alignment
+
+public: // state is public because the entire Mork system is private
+
+ nsIMdbHeap* mRowSpace_SlotHeap;
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ morkRowProbeMap mRowSpace_Rows; // hash table of morkRow instances
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ morkRowMap mRowSpace_Rows; // hash table of morkRow instances
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ morkTableMap mRowSpace_Tables; // all the tables in this row scope
+
+ mork_tid mRowSpace_NextTableId; // for auto-assigning table IDs
+ mork_rid mRowSpace_NextRowId; // for auto-assigning row IDs
+
+ mork_count mRowSpace_IndexCount; // if nonzero, row indexes exist
+
+ // every nonzero slot in IndexCache is a strong ref to a morkAtomRowMap:
+ morkAtomRowMap* mRowSpace_IndexCache[ morkRowSpace_kPrimeCacheSize ];
+
+ morkDeque mRowSpace_TablesByPriority[ morkPriority_kCount ];
+
+public: // more specific dirty methods for row space:
+ void SetRowSpaceDirty() { this->SetNodeDirty(); }
+ void SetRowSpaceClean() { this->SetNodeClean(); }
+
+ mork_bool IsRowSpaceClean() const { return this->IsNodeClean(); }
+ mork_bool IsRowSpaceDirty() const { return this->IsNodeDirty(); }
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseRowSpace() only if open
+ virtual ~morkRowSpace(); // assert that CloseRowSpace() executed earlier
+
+public: // morkMap construction & destruction
+ morkRowSpace(morkEnv* ev, const morkUsage& inUsage, mork_scope inScope,
+ morkStore* ioStore, nsIMdbHeap* ioNodeHeap, nsIMdbHeap* ioSlotHeap);
+ void CloseRowSpace(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsRowSpace() const
+ { return IsNode() && mNode_Derived == morkDerived_kRowSpace; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ static void NonRowSpaceTypeError(morkEnv* ev);
+ static void ZeroScopeError(morkEnv* ev);
+ static void ZeroKindError(morkEnv* ev);
+ static void ZeroTidError(morkEnv* ev);
+ static void MinusOneRidError(morkEnv* ev);
+
+ //static void ExpectAutoIdOnlyError(morkEnv* ev);
+ //static void ExpectAutoIdNeverError(morkEnv* ev);
+
+public: // other space methods
+
+ mork_num CutAllRows(morkEnv* ev, morkPool* ioPool);
+ // CutAllRows() puts all rows and cells back into the pool.
+
+ morkTable* NewTable(morkEnv* ev, mork_kind inTableKind,
+ mdb_bool inMustBeUnique, const mdbOid* inOptionalMetaRowOid);
+
+ morkTable* NewTableWithTid(morkEnv* ev, mork_tid inTid,
+ mork_kind inTableKind, const mdbOid* inOptionalMetaRowOid);
+
+ morkTable* FindTableByKind(morkEnv* ev, mork_kind inTableKind);
+ morkTable* FindTableByTid(morkEnv* ev, mork_tid inTid)
+ { return mRowSpace_Tables.GetTable(ev, inTid); }
+
+ mork_tid MakeNewTableId(morkEnv* ev);
+ mork_rid MakeNewRowId(morkEnv* ev);
+
+ // morkRow* FindRowByRid(morkEnv* ev, mork_rid inRid)
+ // { return (morkRow*) mRowSpace_Rows.GetRow(ev, inRid); }
+
+ morkRow* NewRowWithOid(morkEnv* ev, const mdbOid* inOid);
+ morkRow* NewRow(morkEnv* ev);
+
+ morkRow* FindRow(morkEnv* ev, mork_column inColumn, const mdbYarn* inYarn);
+
+ morkAtomRowMap* ForceMap(morkEnv* ev, mork_column inColumn);
+ morkAtomRowMap* FindMap(morkEnv* ev, mork_column inColumn);
+
+protected: // internal utilities
+ morkAtomRowMap* make_index(morkEnv* ev, mork_column inColumn);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakRowSpace(morkRowSpace* me,
+ morkEnv* ev, morkRowSpace** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongRowSpace(morkRowSpace* me,
+ morkEnv* ev, morkRowSpace** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kRowSpaceMap /*i*/ 0x725A /* ascii 'rZ' */
+
+/*| morkRowSpaceMap: maps mork_scope -> morkRowSpace
+|*/
+class morkRowSpaceMap : public morkNodeMap { // for mapping tokens to tables
+
+public:
+
+ virtual ~morkRowSpaceMap();
+ morkRowSpaceMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap);
+
+public: // other map methods
+
+ mork_bool AddRowSpace(morkEnv* ev, morkRowSpace* ioRowSpace)
+ { return this->AddNode(ev, ioRowSpace->SpaceScope(), ioRowSpace); }
+ // the AddRowSpace() boolean return equals ev->Good().
+
+ mork_bool CutRowSpace(morkEnv* ev, mork_scope inScope)
+ { return this->CutNode(ev, inScope); }
+ // The CutRowSpace() boolean return indicates whether removal happened.
+
+ morkRowSpace* GetRowSpace(morkEnv* ev, mork_scope inScope)
+ { return (morkRowSpace*) this->GetNode(ev, inScope); }
+ // Note the returned space does NOT have an increase in refcount for this.
+
+ mork_num CutAllRowSpaces(morkEnv* ev)
+ { return this->CutAllNodes(ev); }
+ // CutAllRowSpaces() releases all the referenced table values.
+};
+
+class morkRowSpaceMapIter: public morkMapIter{ // typesafe wrapper class
+
+public:
+ morkRowSpaceMapIter(morkEnv* ev, morkRowSpaceMap* ioMap)
+ : morkMapIter(ev, ioMap) { }
+
+ morkRowSpaceMapIter( ) : morkMapIter() { }
+ void InitRowSpaceMapIter(morkEnv* ev, morkRowSpaceMap* ioMap)
+ { this->InitMapIter(ev, ioMap); }
+
+ mork_change*
+ FirstRowSpace(morkEnv* ev, mork_scope* outScope, morkRowSpace** outRowSpace)
+ { return this->First(ev, outScope, outRowSpace); }
+
+ mork_change*
+ NextRowSpace(morkEnv* ev, mork_scope* outScope, morkRowSpace** outRowSpace)
+ { return this->Next(ev, outScope, outRowSpace); }
+
+ mork_change*
+ HereRowSpace(morkEnv* ev, mork_scope* outScope, morkRowSpace** outRowSpace)
+ { return this->Here(ev, outScope, outRowSpace); }
+
+ mork_change*
+ CutHereRowSpace(morkEnv* ev, mork_scope* outScope, morkRowSpace** outRowSpace)
+ { return this->CutHere(ev, outScope, outRowSpace); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKROWSPACE_ */
diff --git a/components/mork/src/morkSearchRowCursor.cpp b/components/mork/src/morkSearchRowCursor.cpp
new file mode 100644
index 000000000..5bc0b4372
--- /dev/null
+++ b/components/mork/src/morkSearchRowCursor.cpp
@@ -0,0 +1,169 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKCURSOR_
+#include "morkCursor.h"
+#endif
+
+#ifndef _MORKSEARCHROWCURSOR_
+#include "morkSearchRowCursor.h"
+#endif
+
+#ifndef _MORKUNIQROWCURSOR_
+#include "morkUniqRowCursor.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKTABLE_
+#include "morkTable.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkSearchRowCursor::CloseMorkNode(morkEnv* ev) // CloseSearchRowCursor() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseSearchRowCursor(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkSearchRowCursor::~morkSearchRowCursor() // CloseSearchRowCursor() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkSearchRowCursor::morkSearchRowCursor(morkEnv* ev,
+ const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkTable* ioTable, mork_pos inRowPos)
+: morkTableRowCursor(ev, inUsage, ioHeap, ioTable, inRowPos)
+// , mSortingRowCursor_Sorting( 0 )
+{
+ if ( ev->Good() )
+ {
+ if ( ioTable )
+ {
+ // morkSorting::SlotWeakSorting(ioSorting, ev, &mSortingRowCursor_Sorting);
+ if ( ev->Good() )
+ {
+ // mNode_Derived = morkDerived_kTableRowCursor;
+ // mNode_Derived must stay equal to kTableRowCursor
+ }
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+/*public non-poly*/ void
+morkSearchRowCursor::CloseSearchRowCursor(morkEnv* ev)
+{
+ if ( this->IsNode() )
+ {
+ // morkSorting::SlotWeakSorting((morkSorting*) 0, ev, &mSortingRowCursor_Sorting);
+ this->CloseTableRowCursor(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkSearchRowCursor::NonSearchRowCursorTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkSearchRowCursor");
+}
+
+morkUniqRowCursor*
+morkSearchRowCursor::MakeUniqCursor(morkEnv* ev)
+{
+ morkUniqRowCursor* outCursor = 0;
+
+ return outCursor;
+}
+
+#if 0
+orkinTableRowCursor*
+morkSearchRowCursor::AcquireUniqueRowCursorHandle(morkEnv* ev)
+{
+ orkinTableRowCursor* outCursor = 0;
+
+ morkUniqRowCursor* uniqCursor = this->MakeUniqCursor(ev);
+ if ( uniqCursor )
+ {
+ outCursor = uniqCursor->AcquireTableRowCursorHandle(ev);
+ uniqCursor->CutStrongRef(ev);
+ }
+ return outCursor;
+}
+#endif
+mork_bool
+morkSearchRowCursor::CanHaveDupRowMembers(morkEnv* ev)
+{
+ return morkBool_kTrue; // true is correct
+}
+
+mork_count
+morkSearchRowCursor::GetMemberCount(morkEnv* ev)
+{
+ morkTable* table = mTableRowCursor_Table;
+ if ( table )
+ return table->mTable_RowArray.mArray_Fill;
+ else
+ return 0;
+}
+
+morkRow*
+morkSearchRowCursor::NextRow(morkEnv* ev, mdbOid* outOid, mdb_pos* outPos)
+{
+ morkRow* outRow = 0;
+ mork_pos pos = -1;
+
+ morkTable* table = mTableRowCursor_Table;
+ if ( table )
+ {
+ }
+ else
+ ev->NilPointerError();
+
+ *outPos = pos;
+ return outRow;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkSearchRowCursor.h b/components/mork/src/morkSearchRowCursor.h
new file mode 100644
index 000000000..019610643
--- /dev/null
+++ b/components/mork/src/morkSearchRowCursor.h
@@ -0,0 +1,105 @@
+/* -*- 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/. */
+
+#ifndef _MORKSEARCHROWCURSOR_
+#define _MORKSEARCHROWCURSOR_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKCURSOR_
+#include "morkCursor.h"
+#endif
+
+#ifndef _MORKTABLEROWCURSOR_
+#include "morkTableRowCursor.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class morkUniqRowCursor;
+class orkinTableRowCursor;
+// #define morkDerived_kSearchRowCursor /*i*/ 0x7352 /* ascii 'sR' */
+
+class morkSearchRowCursor : public morkTableRowCursor { // row iterator
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // morkFactory* mObject_Factory; // weak ref to suite factory
+
+ // mork_seed mCursor_Seed;
+ // mork_pos mCursor_Pos;
+ // mork_bool mCursor_DoFailOnSeedOutOfSync;
+ // mork_u1 mCursor_Pad[ 3 ]; // explicitly pad to u4 alignment
+
+ // morkTable* mTableRowCursor_Table; // weak ref to table
+
+public: // state is public because the entire Mork system is private
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev); // CloseSearchRowCursor()
+ virtual ~morkSearchRowCursor(); // assert that close executed earlier
+
+public: // morkSearchRowCursor construction & destruction
+ morkSearchRowCursor(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkTable* ioTable, mork_pos inRowPos);
+ void CloseSearchRowCursor(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkSearchRowCursor(const morkSearchRowCursor& other);
+ morkSearchRowCursor& operator=(const morkSearchRowCursor& other);
+
+public: // dynamic type identification
+ // mork_bool IsSearchRowCursor() const
+ // { return IsNode() && mNode_Derived == morkDerived_kSearchRowCursor; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ static void NonSearchRowCursorTypeError(morkEnv* ev);
+
+public: // uniquify
+
+ morkUniqRowCursor* MakeUniqCursor(morkEnv* ev);
+
+public: // other search row cursor methods
+
+ virtual mork_bool CanHaveDupRowMembers(morkEnv* ev);
+ virtual mork_count GetMemberCount(morkEnv* ev);
+
+#if 0
+ virtual orkinTableRowCursor* AcquireUniqueRowCursorHandle(morkEnv* ev);
+#endif
+
+ // virtual mdb_pos NextRowOid(morkEnv* ev, mdbOid* outOid);
+ virtual morkRow* NextRow(morkEnv* ev, mdbOid* outOid, mdb_pos* outPos);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakSearchRowCursor(morkSearchRowCursor* me,
+ morkEnv* ev, morkSearchRowCursor** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongSearchRowCursor(morkSearchRowCursor* me,
+ morkEnv* ev, morkSearchRowCursor** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKSEARCHROWCURSOR_ */
diff --git a/components/mork/src/morkSink.cpp b/components/mork/src/morkSink.cpp
new file mode 100644
index 000000000..1d4089c0a
--- /dev/null
+++ b/components/mork/src/morkSink.cpp
@@ -0,0 +1,292 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKSINK_
+#include "morkSink.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKBLOB_
+#include "morkBlob.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*virtual*/ morkSink::~morkSink()
+{
+ mSink_At = 0;
+ mSink_End = 0;
+}
+
+/*virtual*/ void
+morkSpool::FlushSink(morkEnv* ev) // sync mSpool_Coil->mBuf_Fill
+{
+ morkCoil* coil = mSpool_Coil;
+ if ( coil )
+ {
+ mork_u1* body = (mork_u1*) coil->mBuf_Body;
+ if ( body )
+ {
+ mork_u1* at = mSink_At;
+ mork_u1* end = mSink_End;
+ if ( at >= body && at <= end ) // expected cursor order?
+ {
+ mork_fill fill = (mork_fill) (at - body); // current content size
+ if ( fill <= coil->mBlob_Size )
+ coil->mBuf_Fill = fill;
+ else
+ {
+ coil->BlobFillOverSizeError(ev);
+ coil->mBuf_Fill = coil->mBlob_Size; // make it safe
+ }
+ }
+ else
+ this->BadSpoolCursorOrderError(ev);
+ }
+ else
+ coil->NilBufBodyError(ev);
+ }
+ else
+ this->NilSpoolCoilError(ev);
+}
+
+/*virtual*/ void
+morkSpool::SpillPutc(morkEnv* ev, int c) // grow coil and write byte
+{
+ morkCoil* coil = mSpool_Coil;
+ if ( coil )
+ {
+ mork_u1* body = (mork_u1*) coil->mBuf_Body;
+ if ( body )
+ {
+ mork_u1* at = mSink_At;
+ mork_u1* end = mSink_End;
+ if ( at >= body && at <= end ) // expected cursor order?
+ {
+ mork_size size = coil->mBlob_Size;
+ mork_fill fill = (mork_fill) (at - body); // current content size
+ if ( fill <= size ) // less content than medium size?
+ {
+ coil->mBuf_Fill = fill;
+ if ( at >= end ) // need to grow the coil?
+ {
+ if ( size > 2048 ) // grow slower over 2K?
+ size += 512;
+ else
+ {
+ mork_size growth = ( size * 4 ) / 3; // grow by 33%
+ if ( growth < 64 ) // grow faster under (64 * 3)?
+ growth = 64;
+ size += growth;
+ }
+ if ( coil->GrowCoil(ev, size) ) // made coil bigger?
+ {
+ body = (mork_u1*) coil->mBuf_Body;
+ if ( body ) // have a coil body?
+ {
+ mSink_At = at = body + fill;
+ mSink_End = end = body + coil->mBlob_Size;
+ }
+ else
+ coil->NilBufBodyError(ev);
+ }
+ }
+ if ( ev->Good() ) // seem ready to write byte c?
+ {
+ if ( at < end ) // morkSink::Putc() would succeed?
+ {
+ *at++ = (mork_u1) c;
+ mSink_At = at;
+ coil->mBuf_Fill = fill + 1;
+ }
+ else
+ this->BadSpoolCursorOrderError(ev);
+ }
+ }
+ else // fill exceeds size
+ {
+ coil->BlobFillOverSizeError(ev);
+ coil->mBuf_Fill = coil->mBlob_Size; // make it safe
+ }
+ }
+ else
+ this->BadSpoolCursorOrderError(ev);
+ }
+ else
+ coil->NilBufBodyError(ev);
+ }
+ else
+ this->NilSpoolCoilError(ev);
+}
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+// public: // public non-poly morkSink methods
+
+/*virtual*/
+morkSpool::~morkSpool()
+// Zero all slots to show this sink is disabled, but destroy no memory.
+// Note it is typically unnecessary to flush this coil sink, since all
+// content is written directly to the coil without any buffering.
+{
+ mSink_At = 0;
+ mSink_End = 0;
+ mSpool_Coil = 0;
+}
+
+morkSpool::morkSpool(morkEnv* ev, morkCoil* ioCoil)
+// After installing the coil, calls Seek(ev, 0) to prepare for writing.
+: morkSink()
+, mSpool_Coil( 0 )
+{
+ mSink_At = 0; // set correctly later in Seek()
+ mSink_End = 0; // set correctly later in Seek()
+
+ if ( ev->Good() )
+ {
+ if ( ioCoil )
+ {
+ mSpool_Coil = ioCoil;
+ this->Seek(ev, /*pos*/ 0);
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+// ----- All boolean return values below are equal to ev->Good(): -----
+
+/*static*/ void
+morkSpool::BadSpoolCursorOrderError(morkEnv* ev)
+{
+ ev->NewError("bad morkSpool cursor order");
+}
+
+/*static*/ void
+morkSpool::NilSpoolCoilError(morkEnv* ev)
+{
+ ev->NewError("nil mSpool_Coil");
+}
+
+mork_bool
+morkSpool::Seek(morkEnv* ev, mork_pos inPos)
+// Changed the current write position in coil's buffer to inPos.
+// For example, to start writing the coil from scratch, use inPos==0.
+{
+ morkCoil* coil = mSpool_Coil;
+ if ( coil )
+ {
+ mork_size minSize = (mork_size) (inPos + 64);
+
+ if ( coil->mBlob_Size < minSize )
+ coil->GrowCoil(ev, minSize);
+
+ if ( ev->Good() )
+ {
+ coil->mBuf_Fill = (mork_fill) inPos;
+ mork_u1* body = (mork_u1*) coil->mBuf_Body;
+ if ( body )
+ {
+ mSink_At = body + inPos;
+ mSink_End = body + coil->mBlob_Size;
+ }
+ else
+ coil->NilBufBodyError(ev);
+ }
+ }
+ else
+ this->NilSpoolCoilError(ev);
+
+ return ev->Good();
+}
+
+mork_bool
+morkSpool::Write(morkEnv* ev, const void* inBuf, mork_size inSize)
+// write inSize bytes of inBuf to current position inside coil's buffer
+{
+ // This method is conceptually very similar to morkStream::Write(),
+ // and this code was written while looking at that method for clues.
+
+ morkCoil* coil = mSpool_Coil;
+ if ( coil )
+ {
+ mork_u1* body = (mork_u1*) coil->mBuf_Body;
+ if ( body )
+ {
+ if ( inBuf && inSize ) // anything to write?
+ {
+ mork_u1* at = mSink_At;
+ mork_u1* end = mSink_End;
+ if ( at >= body && at <= end ) // expected cursor order?
+ {
+ // note coil->mBuf_Fill can be stale after morkSink::Putc():
+ mork_pos fill = at - body; // current content size
+ mork_num space = (mork_num) (end - at); // space left in body
+ if ( space < inSize ) // not enough to hold write?
+ {
+ mork_size minGrowth = space + 16;
+ mork_size minSize = coil->mBlob_Size + minGrowth;
+ if ( coil->GrowCoil(ev, minSize) )
+ {
+ body = (mork_u1*) coil->mBuf_Body;
+ if ( body )
+ {
+ mSink_At = at = body + fill;
+ mSink_End = end = body + coil->mBlob_Size;
+ space = (mork_num) (end - at); // space left in body
+ }
+ else
+ coil->NilBufBodyError(ev);
+ }
+ }
+ if ( ev->Good() )
+ {
+ if ( space >= inSize ) // enough room to hold write?
+ {
+ MORK_MEMCPY(at, inBuf, inSize); // into body
+ mSink_At = at + inSize; // advance past written bytes
+ coil->mBuf_Fill = fill + inSize; // "flush" to fix fill
+ }
+ else
+ ev->NewError("insufficient morkSpool space");
+ }
+ }
+ else
+ this->BadSpoolCursorOrderError(ev);
+ }
+ }
+ else
+ coil->NilBufBodyError(ev);
+ }
+ else
+ this->NilSpoolCoilError(ev);
+
+ return ev->Good();
+}
+
+mork_bool
+morkSpool::PutString(morkEnv* ev, const char* inString)
+// call Write() with inBuf=inString and inSize=strlen(inString),
+// unless inString is null, in which case we then do nothing at all.
+{
+ if ( inString )
+ {
+ mork_size size = MORK_STRLEN(inString);
+ this->Write(ev, inString, size);
+ }
+ return ev->Good();
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkSink.h b/components/mork/src/morkSink.h
new file mode 100644
index 000000000..4d501e76c
--- /dev/null
+++ b/components/mork/src/morkSink.h
@@ -0,0 +1,161 @@
+/* -*- 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/. */
+
+#ifndef _MORKSINK_
+#define _MORKSINK_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKBLOB_
+#include "morkBlob.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*| morkSink is intended to be a very cheap buffered i/o sink which
+**| writes to bufs and other strings a single byte at a time. The
+**| basic idea is that writing a single byte has a very cheap average
+**| cost, because a polymophic function call need only occur when the
+**| space between At and End is exhausted. The rest of the time a
+**| very cheap inline method will write a byte, and then bump a pointer.
+**|
+**|| At: the current position in some sequence of bytes at which to
+**| write the next byte put into the sink. Presumably At points into
+**| the private storage of some space which is not yet filled (except
+**| when At reaches End, and the overflow must then spill). Note both
+**| At and End are zeroed in the destructor to help show that a sink
+**| is no longer usable; this is safe because At==End causes the case
+**| where SpillPutc() is called to handled an exhausted buffer space.
+**|
+**|| End: an address one byte past the last byte which can be written
+**| without needing to make a buffer larger than previously. When At
+**| and End are equal, this means there is no space to write a byte,
+**| and that some underlying buffer space must be grown before another
+**| byte can be written. Note At must always be less than or equal to
+**| End, and otherwise an important invariant has failed severely.
+**|
+**|| Buf: this original class slot has been commented out in the new
+**| and more abstract version of this sink class, but the general idea
+**| behind this slot should be explained to help design subclasses.
+**| Each subclass should provide space into which At and End can point,
+**| where End is beyond the last writable byte, and At is less than or
+**| equal to this point inside the same buffer. With some kinds of
+**| medium, such as writing to an instance of morkBlob, it is feasible
+**| to point directly into the final resting place for all the content
+**| written to the medium. Other mediums such as files, which write
+**| only through function calls, will typically need a local buffer
+**| to efficiently accumulate many bytes between such function calls.
+**|
+**|| FlushSink: this flush method should move any buffered content to
+**| its final destination. For example, for buffered writes to a
+**| string medium, where string methods are function calls and not just
+**| inline macros, it is faster to accumulate many bytes in a small
+**| local buffer and then move these en masse later in a single call.
+**|
+**|| SpillPutc: when At is greater than or equal to End, this means an
+**| underlying buffer has become full, so the buffer must be flushed
+**| before a new byte can be written. The intention is that SpillPutc()
+**| will be equivalent to calling FlushSink() followed by another call
+**| to Putc(), where the flush is expected to make At less then End once
+**| again. Except that FlushSink() need not make the underlying buffer
+**| any larger, and SpillPutc() typically must make room for more bytes.
+**| Note subclasses might want to guard against the case that both At
+**| and End are null, which happens when a sink is destroyed, which sets
+**| both these pointers to null as an indication the sink is disabled.
+|*/
+class morkSink {
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // public sink virtual methods
+
+ virtual void FlushSink(morkEnv* ev) = 0;
+ virtual void SpillPutc(morkEnv* ev, int c) = 0;
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // member variables
+
+ mork_u1* mSink_At; // pointer into mSink_Buf
+ mork_u1* mSink_End; // one byte past last content byte
+
+// define morkSink_kBufSize 256 /* small enough to go on stack */
+
+ // mork_u1 mSink_Buf[ morkSink_kBufSize + 4 ];
+ // want plus one for any needed end null byte; use plus 4 for alignment
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // public non-poly morkSink methods
+
+ virtual ~morkSink(); // zero both At and End; virtual for subclasses
+ morkSink() { } // does nothing; subclasses must set At and End suitably
+
+ void Putc(morkEnv* ev, int c)
+ {
+ if ( mSink_At < mSink_End )
+ *mSink_At++ = (mork_u1) c;
+ else
+ this->SpillPutc(ev, c);
+ }
+};
+
+/*| morkSpool: an output sink that efficiently writes individual bytes
+**| or entire byte sequences to a coil instance, which grows as needed by
+**| using the heap instance in the coil to grow the internal buffer.
+**|
+**|| Note we do not "own" the coil referenced by mSpool_Coil, and
+**| the lifetime of the coil is expected to equal or exceed that of this
+**| sink by some external means. Typical usage might involve keeping an
+**| instance of morkCoil and an instance of morkSpool in the same
+**| owning parent object, which uses the spool with the associated coil.
+|*/
+class morkSpool : public morkSink { // for buffered i/o to a morkCoil
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // public sink virtual methods
+
+ // when morkSink::Putc() moves mSink_At, mSpool_Coil->mBuf_Fill is wrong:
+
+ virtual void FlushSink(morkEnv* ev); // sync mSpool_Coil->mBuf_Fill
+ virtual void SpillPutc(morkEnv* ev, int c); // grow coil and write byte
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // member variables
+ morkCoil* mSpool_Coil; // destination medium for written bytes
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // public non-poly morkSink methods
+
+ static void BadSpoolCursorOrderError(morkEnv* ev);
+ static void NilSpoolCoilError(morkEnv* ev);
+
+ virtual ~morkSpool();
+ // Zero all slots to show this sink is disabled, but destroy no memory.
+ // Note it is typically unnecessary to flush this coil sink, since all
+ // content is written directly to the coil without any buffering.
+
+ morkSpool(morkEnv* ev, morkCoil* ioCoil);
+ // After installing the coil, calls Seek(ev, 0) to prepare for writing.
+
+ // ----- All boolean return values below are equal to ev->Good(): -----
+
+ mork_bool Seek(morkEnv* ev, mork_pos inPos);
+ // Changed the current write position in coil's buffer to inPos.
+ // For example, to start writing the coil from scratch, use inPos==0.
+
+ mork_bool Write(morkEnv* ev, const void* inBuf, mork_size inSize);
+ // write inSize bytes of inBuf to current position inside coil's buffer
+
+ mork_bool PutBuf(morkEnv* ev, const morkBuf& inBuffer)
+ { return this->Write(ev, inBuffer.mBuf_Body, inBuffer.mBuf_Fill); }
+
+ mork_bool PutString(morkEnv* ev, const char* inString);
+ // call Write() with inBuf=inString and inSize=strlen(inString),
+ // unless inString is null, in which case we then do nothing at all.
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKSINK_ */
diff --git a/components/mork/src/morkSpace.cpp b/components/mork/src/morkSpace.cpp
new file mode 100644
index 000000000..b19a6e737
--- /dev/null
+++ b/components/mork/src/morkSpace.cpp
@@ -0,0 +1,152 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKSPACE_
+#include "morkSpace.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkSpace::CloseMorkNode(morkEnv* ev) // CloseSpace() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseSpace(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkSpace::~morkSpace() // assert CloseSpace() executed earlier
+{
+ MORK_ASSERT(SpaceScope()==0);
+ MORK_ASSERT(mSpace_Store==0);
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+//morkSpace::morkSpace(morkEnv* ev, const morkUsage& inUsage,
+// nsIMdbHeap* ioNodeHeap, const morkMapForm& inForm,
+// nsIMdbHeap* ioSlotHeap)
+//: morkNode(ev, inUsage, ioNodeHeap)
+//, mSpace_Map(ev, morkUsage::kMember, (nsIMdbHeap*) 0, ioSlotHeap)
+//{
+// ev->StubMethodOnlyError();
+//}
+
+/*public non-poly*/
+morkSpace::morkSpace(morkEnv* ev,
+ const morkUsage& inUsage, mork_scope inScope, morkStore* ioStore,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+: morkBead(ev, inUsage, ioHeap, inScope)
+, mSpace_Store( 0 )
+, mSpace_DoAutoIDs( morkBool_kFalse )
+, mSpace_HaveDoneAutoIDs( morkBool_kFalse )
+, mSpace_CanDirty( morkBool_kFalse ) // only when store can be dirtied
+{
+ if ( ev->Good() )
+ {
+ if ( ioStore && ioSlotHeap )
+ {
+ morkStore::SlotWeakStore(ioStore, ev, &mSpace_Store);
+
+ mSpace_CanDirty = ioStore->mStore_CanDirty;
+ if ( mSpace_CanDirty ) // this new space dirties the store?
+ this->MaybeDirtyStoreAndSpace();
+
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kSpace;
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+/*public non-poly*/ void
+morkSpace::CloseSpace(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ morkStore::SlotWeakStore((morkStore*) 0, ev, &mSpace_Store);
+ mBead_Color = 0; // this->CloseBead();
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkSpace::NonAsciiSpaceScopeName(morkEnv* ev)
+{
+ ev->NewError("SpaceScope() > 0x7F");
+}
+
+/*static*/ void
+morkSpace::NilSpaceStoreError(morkEnv* ev)
+{
+ ev->NewError("nil mSpace_Store");
+}
+
+morkPool* morkSpace::GetSpaceStorePool() const
+{
+ return &mSpace_Store->mStore_Pool;
+}
+
+mork_bool morkSpace::MaybeDirtyStoreAndSpace()
+{
+ morkStore* store = mSpace_Store;
+ if ( store && store->mStore_CanDirty )
+ {
+ store->SetStoreDirty();
+ mSpace_CanDirty = morkBool_kTrue;
+ }
+
+ if ( mSpace_CanDirty )
+ {
+ this->SetSpaceDirty();
+ return morkBool_kTrue;
+ }
+
+ return morkBool_kFalse;
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkSpace.h b/components/mork/src/morkSpace.h
new file mode 100644
index 000000000..a0a12d116
--- /dev/null
+++ b/components/mork/src/morkSpace.h
@@ -0,0 +1,110 @@
+/* -*- 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/. */
+
+#ifndef _MORKSPACE_
+#define _MORKSPACE_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKBEAD_
+#include "morkBead.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkSpace_kInitialSpaceSlots /*i*/ 1024 /* default */
+#define morkDerived_kSpace /*i*/ 0x5370 /* ascii 'Sp' */
+
+/*| morkSpace:
+|*/
+class morkSpace : public morkBead { //
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // mork_color mBead_Color; // ID for this bead
+
+public: // bead color setter & getter replace obsolete member mTable_Id:
+
+ mork_tid SpaceScope() const { return mBead_Color; }
+ void SetSpaceScope(mork_scope inScope) { mBead_Color = inScope; }
+
+public: // state is public because the entire Mork system is private
+
+ morkStore* mSpace_Store; // weak ref to containing store
+
+ mork_bool mSpace_DoAutoIDs; // whether db should assign member IDs
+ mork_bool mSpace_HaveDoneAutoIDs; // whether actually auto assigned IDs
+ mork_bool mSpace_CanDirty; // changes imply the store becomes dirty?
+ mork_u1 mSpace_Pad; // pad to u4 alignment
+
+public: // more specific dirty methods for space:
+ void SetSpaceDirty() { this->SetNodeDirty(); }
+ void SetSpaceClean() { this->SetNodeClean(); }
+
+ mork_bool IsSpaceClean() const { return this->IsNodeClean(); }
+ mork_bool IsSpaceDirty() const { return this->IsNodeDirty(); }
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev); // CloseSpace() only if open
+ virtual ~morkSpace(); // assert that CloseSpace() executed earlier
+
+public: // morkMap construction & destruction
+ //morkSpace(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioNodeHeap,
+ // const morkMapForm& inForm, nsIMdbHeap* ioSlotHeap);
+
+ morkSpace(morkEnv* ev, const morkUsage& inUsage,mork_scope inScope,
+ morkStore* ioStore, nsIMdbHeap* ioNodeHeap, nsIMdbHeap* ioSlotHeap);
+ void CloseSpace(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsSpace() const
+ { return IsNode() && mNode_Derived == morkDerived_kSpace; }
+// } ===== end morkNode methods =====
+
+public: // other space methods
+
+ mork_bool MaybeDirtyStoreAndSpace();
+
+ static void NonAsciiSpaceScopeName(morkEnv* ev);
+ static void NilSpaceStoreError(morkEnv* ev);
+
+ morkPool* GetSpaceStorePool() const;
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakSpace(morkSpace* me,
+ morkEnv* ev, morkSpace** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongSpace(morkSpace* me,
+ morkEnv* ev, morkSpace** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKSPACE_ */
diff --git a/components/mork/src/morkStore.cpp b/components/mork/src/morkStore.cpp
new file mode 100644
index 000000000..d6e70becd
--- /dev/null
+++ b/components/mork/src/morkStore.cpp
@@ -0,0 +1,2290 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKBLOB_
+#include "morkBlob.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKFACTORY_
+#include "morkFactory.h"
+#endif
+
+#ifndef _MORKNODEMAP_
+#include "morkNodeMap.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+#ifndef _MORKTHUMB_
+#include "morkThumb.h"
+#endif
+// #ifndef _MORKFILE_
+// #include "morkFile.h"
+// #endif
+
+#ifndef _MORKBUILDER_
+#include "morkBuilder.h"
+#endif
+
+#ifndef _MORKATOMSPACE_
+#include "morkAtomSpace.h"
+#endif
+
+#ifndef _MORKSTREAM_
+#include "morkStream.h"
+#endif
+
+#ifndef _MORKATOMSPACE_
+#include "morkAtomSpace.h"
+#endif
+
+#ifndef _MORKROWSPACE_
+#include "morkRowSpace.h"
+#endif
+
+#ifndef _MORKPORTTABLECURSOR_
+#include "morkPortTableCursor.h"
+#endif
+
+#ifndef _MORKTABLE_
+#include "morkTable.h"
+#endif
+
+#ifndef _MORKROWMAP_
+#include "morkRowMap.h"
+#endif
+
+#ifndef _MORKPARSER_
+#include "morkParser.h"
+#endif
+
+#include "nsCOMPtr.h"
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkStore::CloseMorkNode(morkEnv* ev) // ClosePort() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseStore(ev);
+ this->MarkShut();
+ }
+}
+
+/*public non-poly*/ void
+morkStore::ClosePort(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ morkFactory::SlotWeakFactory((morkFactory*) 0, ev, &mPort_Factory);
+ nsIMdbHeap_SlotStrongHeap((nsIMdbHeap*) 0, ev, &mPort_Heap);
+ this->CloseObject(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+/*public virtual*/
+morkStore::~morkStore() // assert CloseStore() executed earlier
+{
+ MOZ_COUNT_DTOR(morkStore);
+ if (IsOpenNode())
+ CloseMorkNode(mMorkEnv);
+ MORK_ASSERT(this->IsShutNode());
+ MORK_ASSERT(mStore_File==0);
+ MORK_ASSERT(mStore_InStream==0);
+ MORK_ASSERT(mStore_OutStream==0);
+ MORK_ASSERT(mStore_Builder==0);
+ MORK_ASSERT(mStore_OidAtomSpace==0);
+ MORK_ASSERT(mStore_GroundAtomSpace==0);
+ MORK_ASSERT(mStore_GroundColumnSpace==0);
+ MORK_ASSERT(mStore_RowSpaces.IsShutNode());
+ MORK_ASSERT(mStore_AtomSpaces.IsShutNode());
+ MORK_ASSERT(mStore_Pool.IsShutNode());
+}
+
+/*public non-poly*/
+morkStore::morkStore(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioNodeHeap, // the heap (if any) for this node instance
+ morkFactory* inFactory, // the factory for this
+ nsIMdbHeap* ioPortHeap // the heap to hold all content in the port
+ )
+: morkObject(ev, inUsage, ioNodeHeap, morkColor_kNone, (morkHandle*) 0)
+, mPort_Env( ev )
+, mPort_Factory( 0 )
+, mPort_Heap( 0 )
+, mStore_OidAtomSpace( 0 )
+, mStore_GroundAtomSpace( 0 )
+, mStore_GroundColumnSpace( 0 )
+
+, mStore_File( 0 )
+, mStore_InStream( 0 )
+, mStore_Builder( 0 )
+, mStore_OutStream( 0 )
+
+, mStore_RowSpaces(ev, morkUsage::kMember, (nsIMdbHeap*) 0, ioPortHeap)
+, mStore_AtomSpaces(ev, morkUsage::kMember, (nsIMdbHeap*) 0, ioPortHeap)
+, mStore_Zone(ev, morkUsage::kMember, (nsIMdbHeap*) 0, ioPortHeap)
+, mStore_Pool(ev, morkUsage::kMember, (nsIMdbHeap*) 0, ioPortHeap)
+
+, mStore_CommitGroupIdentity( 0 )
+
+, mStore_FirstCommitGroupPos( 0 )
+, mStore_SecondCommitGroupPos( 0 )
+
+// disable auto-assignment of atom IDs until someone knows it is okay:
+, mStore_CanAutoAssignAtomIdentity( morkBool_kFalse )
+, mStore_CanDirty( morkBool_kFalse ) // not until the store is open
+, mStore_CanWriteIncremental( morkBool_kTrue ) // always with few exceptions
+{
+ MOZ_COUNT_CTOR(morkStore);
+ if ( ev->Good() )
+ {
+ if ( inFactory && ioPortHeap )
+ {
+ morkFactory::SlotWeakFactory(inFactory, ev, &mPort_Factory);
+ nsIMdbHeap_SlotStrongHeap(ioPortHeap, ev, &mPort_Heap);
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kPort;
+ }
+ else
+ ev->NilPointerError();
+ }
+ if ( ev->Good() )
+ {
+ mNode_Derived = morkDerived_kStore;
+
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkStore, morkObject, nsIMdbStore)
+
+/*public non-poly*/ void
+morkStore::CloseStore(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+
+ nsIMdbFile* file = mStore_File;
+ file->AddRef();
+
+ morkFactory::SlotWeakFactory((morkFactory*) 0, ev, &mPort_Factory);
+ nsIMdbHeap_SlotStrongHeap((nsIMdbHeap*) 0, ev, &mPort_Heap);
+ morkAtomSpace::SlotStrongAtomSpace((morkAtomSpace*) 0, ev,
+ &mStore_OidAtomSpace);
+ morkAtomSpace::SlotStrongAtomSpace((morkAtomSpace*) 0, ev,
+ &mStore_GroundAtomSpace);
+ morkAtomSpace::SlotStrongAtomSpace((morkAtomSpace*) 0, ev,
+ &mStore_GroundColumnSpace);
+ mStore_RowSpaces.CloseMorkNode(ev);
+ mStore_AtomSpaces.CloseMorkNode(ev);
+ morkBuilder::SlotStrongBuilder((morkBuilder*) 0, ev, &mStore_Builder);
+
+ nsIMdbFile_SlotStrongFile((nsIMdbFile*) 0, ev,
+ &mStore_File);
+
+ file->Release();
+
+ morkStream::SlotStrongStream((morkStream*) 0, ev, &mStore_InStream);
+ morkStream::SlotStrongStream((morkStream*) 0, ev, &mStore_OutStream);
+
+ mStore_Pool.CloseMorkNode(ev);
+ mStore_Zone.CloseMorkNode(ev);
+ this->ClosePort(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+
+mork_bool morkStore::DoPreferLargeOverCompressCommit(morkEnv* ev)
+ // true when mStore_CanWriteIncremental && store has file large enough
+{
+ nsIMdbFile* file = mStore_File;
+ if ( file && mStore_CanWriteIncremental )
+ {
+ mdb_pos fileEof = 0;
+ file->Eof(ev->AsMdbEnv(), &fileEof);
+ if ( ev->Good() && fileEof > 128 )
+ return morkBool_kTrue;
+ }
+ return morkBool_kFalse;
+}
+
+mork_percent morkStore::PercentOfStoreWasted(morkEnv* ev)
+{
+ mork_percent outPercent = 0;
+ nsIMdbFile* file = mStore_File;
+
+ if ( file )
+ {
+ mork_pos firstPos = mStore_FirstCommitGroupPos;
+ mork_pos secondPos = mStore_SecondCommitGroupPos;
+ if ( firstPos || secondPos )
+ {
+ if ( firstPos < 512 && secondPos > firstPos )
+ firstPos = secondPos; // better approximation of first commit
+
+ mork_pos fileLength = 0;
+ file->Eof(ev->AsMdbEnv(), &fileLength); // end of file
+ if ( ev->Good() && fileLength > firstPos )
+ {
+ mork_size groupContent = fileLength - firstPos;
+ outPercent = ( groupContent * 100 ) / fileLength;
+ }
+ }
+ }
+ else
+ this->NilStoreFileError(ev);
+
+ return outPercent;
+}
+
+void
+morkStore::SetStoreAndAllSpacesCanDirty(morkEnv* ev, mork_bool inCanDirty)
+{
+ mStore_CanDirty = inCanDirty;
+
+ mork_change* c = 0;
+ mork_scope* key = 0; // ignore keys in maps
+
+ if ( ev->Good() )
+ {
+ morkAtomSpaceMapIter asi(ev, &mStore_AtomSpaces);
+
+ morkAtomSpace* atomSpace = 0; // old val node in the map
+
+ for ( c = asi.FirstAtomSpace(ev, key, &atomSpace); c && ev->Good();
+ c = asi.NextAtomSpace(ev, key, &atomSpace) )
+ {
+ if ( atomSpace )
+ {
+ if ( atomSpace->IsAtomSpace() )
+ atomSpace->mSpace_CanDirty = inCanDirty;
+ else
+ atomSpace->NonAtomSpaceTypeError(ev);
+ }
+ else
+ ev->NilPointerError();
+ }
+ }
+
+ if ( ev->Good() )
+ {
+ morkRowSpaceMapIter rsi(ev, &mStore_RowSpaces);
+ morkRowSpace* rowSpace = 0; // old val node in the map
+
+ for ( c = rsi.FirstRowSpace(ev, key, &rowSpace); c && ev->Good();
+ c = rsi.NextRowSpace(ev, key, &rowSpace) )
+ {
+ if ( rowSpace )
+ {
+ if ( rowSpace->IsRowSpace() )
+ rowSpace->mSpace_CanDirty = inCanDirty;
+ else
+ rowSpace->NonRowSpaceTypeError(ev);
+ }
+ }
+ }
+}
+
+void
+morkStore::RenumberAllCollectableContent(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ // do nothing currently
+}
+
+nsIMdbStore*
+morkStore::AcquireStoreHandle(morkEnv* ev)
+{
+ return this;
+}
+
+
+morkFarBookAtom*
+morkStore::StageAliasAsFarBookAtom(morkEnv* ev, const morkMid* inMid,
+ morkAtomSpace* ioSpace, mork_cscode inForm)
+{
+ if ( inMid && inMid->mMid_Buf )
+ {
+ const morkBuf* buf = inMid->mMid_Buf;
+ mork_size length = buf->mBuf_Fill;
+ if ( length <= morkBookAtom_kMaxBodySize )
+ {
+ mork_aid dummyAid = 1;
+ //mStore_BookAtom.InitMaxBookAtom(ev, *buf,
+ // inForm, ioSpace, dummyAid);
+
+ mStore_FarBookAtom.InitFarBookAtom(ev, *buf,
+ inForm, ioSpace, dummyAid);
+ return &mStore_FarBookAtom;
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ return (morkFarBookAtom*) 0;
+}
+
+morkFarBookAtom*
+morkStore::StageYarnAsFarBookAtom(morkEnv* ev, const mdbYarn* inYarn,
+ morkAtomSpace* ioSpace)
+{
+ if ( inYarn && inYarn->mYarn_Buf )
+ {
+ mork_size length = inYarn->mYarn_Fill;
+ if ( length <= morkBookAtom_kMaxBodySize )
+ {
+ morkBuf buf(inYarn->mYarn_Buf, length);
+ mork_aid dummyAid = 1;
+ //mStore_BookAtom.InitMaxBookAtom(ev, buf,
+ // inYarn->mYarn_Form, ioSpace, dummyAid);
+ mStore_FarBookAtom.InitFarBookAtom(ev, buf,
+ inYarn->mYarn_Form, ioSpace, dummyAid);
+ return &mStore_FarBookAtom;
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ return (morkFarBookAtom*) 0;
+}
+
+morkFarBookAtom*
+morkStore::StageStringAsFarBookAtom(morkEnv* ev, const char* inString,
+ mork_cscode inForm, morkAtomSpace* ioSpace)
+{
+ if ( inString )
+ {
+ mork_size length = MORK_STRLEN(inString);
+ if ( length <= morkBookAtom_kMaxBodySize )
+ {
+ morkBuf buf(inString, length);
+ mork_aid dummyAid = 1;
+ //mStore_BookAtom.InitMaxBookAtom(ev, buf, inForm, ioSpace, dummyAid);
+ mStore_FarBookAtom.InitFarBookAtom(ev, buf, inForm, ioSpace, dummyAid);
+ return &mStore_FarBookAtom;
+ }
+ }
+ else
+ ev->NilPointerError();
+
+ return (morkFarBookAtom*) 0;
+}
+
+morkAtomSpace* morkStore::LazyGetOidAtomSpace(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ if ( !mStore_OidAtomSpace )
+ {
+ }
+ return mStore_OidAtomSpace;
+}
+
+morkAtomSpace* morkStore::LazyGetGroundAtomSpace(morkEnv* ev)
+{
+ if ( !mStore_GroundAtomSpace )
+ {
+ mork_scope atomScope = morkStore_kValueSpaceScope;
+ nsIMdbHeap* heap = mPort_Heap;
+ morkAtomSpace* space = new(*heap, ev)
+ morkAtomSpace(ev, morkUsage::kHeap, atomScope, this, heap, heap);
+
+ if ( space ) // successful space creation?
+ {
+ this->MaybeDirtyStore();
+
+ mStore_GroundAtomSpace = space; // transfer strong ref to this slot
+ mStore_AtomSpaces.AddAtomSpace(ev, space);
+ }
+ }
+ return mStore_GroundAtomSpace;
+}
+
+morkAtomSpace* morkStore::LazyGetGroundColumnSpace(morkEnv* ev)
+{
+ if ( !mStore_GroundColumnSpace )
+ {
+ mork_scope atomScope = morkStore_kGroundColumnSpace;
+ nsIMdbHeap* heap = mPort_Heap;
+ morkAtomSpace* space = new(*heap, ev)
+ morkAtomSpace(ev, morkUsage::kHeap, atomScope, this, heap, heap);
+
+ if ( space ) // successful space creation?
+ {
+ this->MaybeDirtyStore();
+
+ mStore_GroundColumnSpace = space; // transfer strong ref to this slot
+ mStore_AtomSpaces.AddAtomSpace(ev, space);
+ }
+ }
+ return mStore_GroundColumnSpace;
+}
+
+morkStream* morkStore::LazyGetInStream(morkEnv* ev)
+{
+ if ( !mStore_InStream )
+ {
+ nsIMdbFile* file = mStore_File;
+ if ( file )
+ {
+ morkStream* stream = new(*mPort_Heap, ev)
+ morkStream(ev, morkUsage::kHeap, mPort_Heap, file,
+ morkStore_kStreamBufSize, /*frozen*/ morkBool_kTrue);
+ if ( stream )
+ {
+ this->MaybeDirtyStore();
+ mStore_InStream = stream; // transfer strong ref to this slot
+ }
+ }
+ else
+ this->NilStoreFileError(ev);
+ }
+ return mStore_InStream;
+}
+
+morkStream* morkStore::LazyGetOutStream(morkEnv* ev)
+{
+ if ( !mStore_OutStream )
+ {
+ nsIMdbFile* file = mStore_File;
+ if ( file )
+ {
+ morkStream* stream = new(*mPort_Heap, ev)
+ morkStream(ev, morkUsage::kHeap, mPort_Heap, file,
+ morkStore_kStreamBufSize, /*frozen*/ morkBool_kFalse);
+ if ( stream )
+ {
+ this->MaybeDirtyStore();
+ mStore_InStream = stream; // transfer strong ref to this slot
+ }
+ }
+ else
+ this->NilStoreFileError(ev);
+ }
+ return mStore_OutStream;
+}
+
+void
+morkStore::ForgetBuilder(morkEnv* ev)
+{
+ if ( mStore_Builder )
+ morkBuilder::SlotStrongBuilder((morkBuilder*) 0, ev, &mStore_Builder);
+ if ( mStore_InStream )
+ morkStream::SlotStrongStream((morkStream*) 0, ev, &mStore_InStream);
+}
+
+morkBuilder* morkStore::LazyGetBuilder(morkEnv* ev)
+{
+ if ( !mStore_Builder )
+ {
+ morkStream* stream = this->LazyGetInStream(ev);
+ if ( stream )
+ {
+ nsIMdbHeap* heap = mPort_Heap;
+ morkBuilder* builder = new(*heap, ev)
+ morkBuilder(ev, morkUsage::kHeap, heap, stream,
+ morkBuilder_kDefaultBytesPerParseSegment, heap, this);
+ if ( builder )
+ {
+ mStore_Builder = builder; // transfer strong ref to this slot
+ }
+ }
+ }
+ return mStore_Builder;
+}
+
+morkRowSpace*
+morkStore::LazyGetRowSpace(morkEnv* ev, mdb_scope inRowScope)
+{
+ morkRowSpace* outSpace = mStore_RowSpaces.GetRowSpace(ev, inRowScope);
+ if ( !outSpace && ev->Good() ) // try to make new space?
+ {
+ nsIMdbHeap* heap = mPort_Heap;
+ outSpace = new(*heap, ev)
+ morkRowSpace(ev, morkUsage::kHeap, inRowScope, this, heap, heap);
+
+ if ( outSpace ) // successful space creation?
+ {
+ this->MaybeDirtyStore();
+
+ // note adding to node map creates its own strong ref...
+ if ( mStore_RowSpaces.AddRowSpace(ev, outSpace) )
+ outSpace->CutStrongRef(ev); // ...so we can drop our strong ref
+ }
+ }
+ return outSpace;
+}
+
+morkAtomSpace*
+morkStore::LazyGetAtomSpace(morkEnv* ev, mdb_scope inAtomScope)
+{
+ morkAtomSpace* outSpace = mStore_AtomSpaces.GetAtomSpace(ev, inAtomScope);
+ if ( !outSpace && ev->Good() ) // try to make new space?
+ {
+ if ( inAtomScope == morkStore_kValueSpaceScope )
+ outSpace = this->LazyGetGroundAtomSpace(ev);
+
+ else if ( inAtomScope == morkStore_kGroundColumnSpace )
+ outSpace = this->LazyGetGroundColumnSpace(ev);
+ else
+ {
+ nsIMdbHeap* heap = mPort_Heap;
+ outSpace = new(*heap, ev)
+ morkAtomSpace(ev, morkUsage::kHeap, inAtomScope, this, heap, heap);
+
+ if ( outSpace ) // successful space creation?
+ {
+ this->MaybeDirtyStore();
+
+ // note adding to node map creates its own strong ref...
+ if ( mStore_AtomSpaces.AddAtomSpace(ev, outSpace) )
+ outSpace->CutStrongRef(ev); // ...so we can drop our strong ref
+ }
+ }
+ }
+ return outSpace;
+}
+
+/*static*/ void
+morkStore::NonStoreTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkStore");
+}
+
+/*static*/ void
+morkStore::NilStoreFileError(morkEnv* ev)
+{
+ ev->NewError("nil mStore_File");
+}
+
+/*static*/ void
+morkStore::CannotAutoAssignAtomIdentityError(morkEnv* ev)
+{
+ ev->NewError("false mStore_CanAutoAssignAtomIdentity");
+}
+
+
+mork_bool
+morkStore::OpenStoreFile(morkEnv* ev, mork_bool inFrozen,
+ // const char* inFilePath,
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy)
+{
+ MORK_USED_2(inOpenPolicy,inFrozen);
+ nsIMdbFile_SlotStrongFile(ioFile, ev, &mStore_File);
+
+ // if ( ev->Good() )
+ // {
+ // morkFile* file = morkFile::OpenOldFile(ev, mPort_Heap,
+ // inFilePath, inFrozen);
+ // if ( ioFile )
+ // {
+ // if ( ev->Good() )
+ // morkFile::SlotStrongFile(file, ev, &mStore_File);
+ // else
+ // file->CutStrongRef(ev);
+ //
+ // }
+ // }
+ return ev->Good();
+}
+
+mork_bool
+morkStore::CreateStoreFile(morkEnv* ev,
+ // const char* inFilePath,
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy)
+{
+ MORK_USED_1(inOpenPolicy);
+ nsIMdbFile_SlotStrongFile(ioFile, ev, &mStore_File);
+
+ return ev->Good();
+}
+
+morkAtom*
+morkStore::CopyAtom(morkEnv* ev, const morkAtom* inAtom)
+// copy inAtom (from some other store) over to this store
+{
+ morkAtom* outAtom = 0;
+ if ( inAtom )
+ {
+ mdbYarn yarn;
+ if ( morkAtom::AliasYarn(inAtom, &yarn) )
+ outAtom = this->YarnToAtom(ev, &yarn, true /* create */);
+ }
+ return outAtom;
+}
+
+morkAtom*
+morkStore::YarnToAtom(morkEnv* ev, const mdbYarn* inYarn, bool createIfMissing /* = true */)
+{
+ morkAtom* outAtom = 0;
+ if ( ev->Good() )
+ {
+ morkAtomSpace* groundSpace = this->LazyGetGroundAtomSpace(ev);
+ if ( groundSpace )
+ {
+ morkFarBookAtom* keyAtom =
+ this->StageYarnAsFarBookAtom(ev, inYarn, groundSpace);
+
+ if ( keyAtom )
+ {
+ morkAtomBodyMap* map = &groundSpace->mAtomSpace_AtomBodies;
+ outAtom = map->GetAtom(ev, keyAtom);
+ if ( !outAtom && createIfMissing)
+ {
+ this->MaybeDirtyStore();
+ outAtom = groundSpace->MakeBookAtomCopy(ev, *keyAtom);
+ }
+ }
+ else if ( ev->Good() )
+ {
+ morkBuf b(inYarn->mYarn_Buf, inYarn->mYarn_Fill);
+ morkZone* z = &mStore_Zone;
+ outAtom = mStore_Pool.NewAnonAtom(ev, b, inYarn->mYarn_Form, z);
+ }
+ }
+ }
+ return outAtom;
+}
+
+mork_bool
+morkStore::MidToOid(morkEnv* ev, const morkMid& inMid, mdbOid* outOid)
+{
+ *outOid = inMid.mMid_Oid;
+ const morkBuf* buf = inMid.mMid_Buf;
+ if ( buf && !outOid->mOid_Scope )
+ {
+ if ( buf->mBuf_Fill <= morkBookAtom_kMaxBodySize )
+ {
+ if ( buf->mBuf_Fill == 1 )
+ {
+ mork_u1* name = (mork_u1*) buf->mBuf_Body;
+ if ( name )
+ {
+ outOid->mOid_Scope = (mork_scope) *name;
+ return ev->Good();
+ }
+ }
+ morkAtomSpace* groundSpace = this->LazyGetGroundColumnSpace(ev);
+ if ( groundSpace )
+ {
+ mork_cscode form = 0; // default
+ mork_aid aid = 1; // dummy
+ mStore_FarBookAtom.InitFarBookAtom(ev, *buf, form, groundSpace, aid);
+ morkFarBookAtom* keyAtom = &mStore_FarBookAtom;
+ morkAtomBodyMap* map = &groundSpace->mAtomSpace_AtomBodies;
+ morkBookAtom* bookAtom = map->GetAtom(ev, keyAtom);
+ if ( bookAtom )
+ outOid->mOid_Scope = bookAtom->mBookAtom_Id;
+ else
+ {
+ this->MaybeDirtyStore();
+ bookAtom = groundSpace->MakeBookAtomCopy(ev, *keyAtom);
+ if ( bookAtom )
+ {
+ outOid->mOid_Scope = bookAtom->mBookAtom_Id;
+ bookAtom->MakeCellUseForever(ev);
+ }
+ }
+ }
+ }
+ }
+ return ev->Good();
+}
+
+morkRow*
+morkStore::MidToRow(morkEnv* ev, const morkMid& inMid)
+{
+ mdbOid tempOid;
+ this->MidToOid(ev, inMid, &tempOid);
+ return this->OidToRow(ev, &tempOid);
+}
+
+morkTable*
+morkStore::MidToTable(morkEnv* ev, const morkMid& inMid)
+{
+ mdbOid tempOid;
+ this->MidToOid(ev, inMid, &tempOid);
+ return this->OidToTable(ev, &tempOid, /*metarow*/ (mdbOid*) 0);
+}
+
+mork_bool
+morkStore::MidToYarn(morkEnv* ev, const morkMid& inMid, mdbYarn* outYarn)
+{
+ mdbOid tempOid;
+ this->MidToOid(ev, inMid, &tempOid);
+ return this->OidToYarn(ev, tempOid, outYarn);
+}
+
+mork_bool
+morkStore::OidToYarn(morkEnv* ev, const mdbOid& inOid, mdbYarn* outYarn)
+{
+ morkBookAtom* atom = 0;
+
+ morkAtomSpace* atomSpace = mStore_AtomSpaces.GetAtomSpace(ev, inOid.mOid_Scope);
+ if ( atomSpace )
+ {
+ morkAtomAidMap* map = &atomSpace->mAtomSpace_AtomAids;
+ atom = map->GetAid(ev, (mork_aid) inOid.mOid_Id);
+ }
+ morkAtom::GetYarn(atom, outYarn);
+
+ return ev->Good();
+}
+
+morkBookAtom*
+morkStore::MidToAtom(morkEnv* ev, const morkMid& inMid)
+{
+ morkBookAtom* outAtom = 0;
+ mdbOid oid;
+ if ( this->MidToOid(ev, inMid, &oid) )
+ {
+ morkAtomSpace* atomSpace = mStore_AtomSpaces.GetAtomSpace(ev, oid.mOid_Scope);
+ if ( atomSpace )
+ {
+ morkAtomAidMap* map = &atomSpace->mAtomSpace_AtomAids;
+ outAtom = map->GetAid(ev, (mork_aid) oid.mOid_Id);
+ }
+ }
+ return outAtom;
+}
+
+/*static*/ void
+morkStore::SmallTokenToOneByteYarn(morkEnv* ev, mdb_token inToken,
+ mdbYarn* outYarn)
+{
+ MORK_USED_1(ev);
+ if ( outYarn->mYarn_Buf && outYarn->mYarn_Size ) // any space in yarn at all?
+ {
+ mork_u1* buf = (mork_u1*) outYarn->mYarn_Buf; // for byte arithmetic
+ buf[ 0 ] = (mork_u1) inToken; // write the single byte
+ outYarn->mYarn_Fill = 1;
+ outYarn->mYarn_More = 0;
+ }
+ else // just record we could not write the single byte
+ {
+ outYarn->mYarn_More = 1;
+ outYarn->mYarn_Fill = 0;
+ }
+}
+
+void
+morkStore::TokenToString(morkEnv* ev, mdb_token inToken, mdbYarn* outTokenName)
+{
+ if ( inToken > morkAtomSpace_kMaxSevenBitAid )
+ {
+ morkBookAtom* atom = 0;
+ morkAtomSpace* space = mStore_GroundColumnSpace;
+ if ( space ) {
+ atom = space->mAtomSpace_AtomAids.GetAid(ev, (mork_aid) inToken);
+ }
+ morkAtom::GetYarn(atom, outTokenName);
+ }
+ else // token is an "immediate" single byte string representation?
+ this->SmallTokenToOneByteYarn(ev, inToken, outTokenName);
+}
+
+// void
+// morkStore::SyncTokenIdChange(morkEnv* ev, const morkBookAtom* inAtom,
+// const mdbOid* inOid)
+// {
+// mork_token mStore_MorkNoneToken; // token for "mork:none" // fill=9
+// mork_column mStore_CharsetToken; // token for "charset" // fill=7
+// mork_column mStore_AtomScopeToken; // token for "atomScope" // fill=9
+// mork_column mStore_RowScopeToken; // token for "rowScope" // fill=8
+// mork_column mStore_TableScopeToken; // token for "tableScope" // fill=10
+// mork_column mStore_ColumnScopeToken; // token for "columnScope" // fill=11
+// mork_kind mStore_TableKindToken; // token for "tableKind" // fill=9
+// ---------------------ruler-for-token-length-above---123456789012
+//
+// if ( inOid->mOid_Scope == morkStore_kColumnSpaceScope && inAtom->IsWeeBook() )
+// {
+// const mork_u1* body = ((const morkWeeBookAtom*) inAtom)->mWeeBookAtom_Body;
+// mork_size size = inAtom->mAtom_Size;
+//
+// if ( size >= 7 && size <= 11 )
+// {
+// if ( size == 9 )
+// {
+// if ( *body == 'm' )
+// {
+// if ( MORK_MEMCMP(body, "mork:none", 9) == 0 )
+// mStore_MorkNoneToken = inAtom->mBookAtom_Id;
+// }
+// else if ( *body == 'a' )
+// {
+// if ( MORK_MEMCMP(body, "atomScope", 9) == 0 )
+// mStore_AtomScopeToken = inAtom->mBookAtom_Id;
+// }
+// else if ( *body == 't' )
+// {
+// if ( MORK_MEMCMP(body, "tableKind", 9) == 0 )
+// mStore_TableKindToken = inAtom->mBookAtom_Id;
+// }
+// }
+// else if ( size == 7 && *body == 'c' )
+// {
+// if ( MORK_MEMCMP(body, "charset", 7) == 0 )
+// mStore_CharsetToken = inAtom->mBookAtom_Id;
+// }
+// else if ( size == 8 && *body == 'r' )
+// {
+// if ( MORK_MEMCMP(body, "rowScope", 8) == 0 )
+// mStore_RowScopeToken = inAtom->mBookAtom_Id;
+// }
+// else if ( size == 10 && *body == 't' )
+// {
+// if ( MORK_MEMCMP(body, "tableScope", 10) == 0 )
+// mStore_TableScopeToken = inAtom->mBookAtom_Id;
+// }
+// else if ( size == 11 && *body == 'c' )
+// {
+// if ( MORK_MEMCMP(body, "columnScope", 11) == 0 )
+// mStore_ColumnScopeToken = inAtom->mBookAtom_Id;
+// }
+// }
+// }
+// }
+
+morkAtom*
+morkStore::AddAlias(morkEnv* ev, const morkMid& inMid, mork_cscode inForm)
+{
+ morkBookAtom* outAtom = 0;
+ if ( ev->Good() )
+ {
+ const mdbOid* oid = &inMid.mMid_Oid;
+ morkAtomSpace* atomSpace = this->LazyGetAtomSpace(ev, oid->mOid_Scope);
+ if ( atomSpace )
+ {
+ morkFarBookAtom* keyAtom =
+ this->StageAliasAsFarBookAtom(ev, &inMid, atomSpace, inForm);
+ if ( keyAtom )
+ {
+ morkAtomAidMap* map = &atomSpace->mAtomSpace_AtomAids;
+ outAtom = map->GetAid(ev, (mork_aid) oid->mOid_Id);
+ if ( outAtom )
+ {
+ if ( !outAtom->EqualFormAndBody(ev, keyAtom) )
+ ev->NewError("duplicate alias ID");
+ }
+ else
+ {
+ this->MaybeDirtyStore();
+ keyAtom->mBookAtom_Id = oid->mOid_Id;
+ outAtom = atomSpace->MakeBookAtomCopyWithAid(ev,
+ *keyAtom, (mork_aid) oid->mOid_Id);
+
+ // if ( outAtom && outAtom->IsWeeBook() )
+ // {
+ // if ( oid->mOid_Scope == morkStore_kColumnSpaceScope )
+ // {
+ // mork_size size = outAtom->mAtom_Size;
+ // if ( size >= 7 && size <= 11 )
+ // this->SyncTokenIdChange(ev, outAtom, oid);
+ // }
+ // }
+ }
+ }
+ }
+ }
+ return outAtom;
+}
+
+#define morkStore_kMaxCopyTokenSize 512 /* if larger, cannot be copied */
+
+mork_token
+morkStore::CopyToken(morkEnv* ev, mdb_token inToken, morkStore* inStore)
+// copy inToken from inStore over to this store
+{
+ mork_token outToken = 0;
+ if ( inStore == this ) // same store?
+ outToken = inToken; // just return token unchanged
+ else
+ {
+ char yarnBuf[ morkStore_kMaxCopyTokenSize ];
+ mdbYarn yarn;
+ yarn.mYarn_Buf = yarnBuf;
+ yarn.mYarn_Fill = 0;
+ yarn.mYarn_Size = morkStore_kMaxCopyTokenSize;
+ yarn.mYarn_More = 0;
+ yarn.mYarn_Form = 0;
+ yarn.mYarn_Grow = 0;
+
+ inStore->TokenToString(ev, inToken, &yarn);
+ if ( ev->Good() )
+ {
+ morkBuf buf(yarn.mYarn_Buf, yarn.mYarn_Fill);
+ outToken = this->BufToToken(ev, &buf);
+ }
+ }
+ return outToken;
+}
+
+mork_token
+morkStore::BufToToken(morkEnv* ev, const morkBuf* inBuf)
+{
+ mork_token outToken = 0;
+ if ( ev->Good() )
+ {
+ const mork_u1* s = (const mork_u1*) inBuf->mBuf_Body;
+ mork_bool nonAscii = ( *s > 0x7F );
+ mork_size length = inBuf->mBuf_Fill;
+ if ( nonAscii || length > 1 ) // more than one byte?
+ {
+ mork_cscode form = 0; // default charset
+ morkAtomSpace* space = this->LazyGetGroundColumnSpace(ev);
+ if ( space )
+ {
+ morkFarBookAtom* keyAtom = 0;
+ if ( length <= morkBookAtom_kMaxBodySize )
+ {
+ mork_aid aid = 1; // dummy
+ //mStore_BookAtom.InitMaxBookAtom(ev, *inBuf, form, space, aid);
+ mStore_FarBookAtom.InitFarBookAtom(ev, *inBuf, form, space, aid);
+ keyAtom = &mStore_FarBookAtom;
+ }
+ if ( keyAtom )
+ {
+ morkAtomBodyMap* map = &space->mAtomSpace_AtomBodies;
+ morkBookAtom* bookAtom = map->GetAtom(ev, keyAtom);
+ if ( bookAtom )
+ outToken = bookAtom->mBookAtom_Id;
+ else
+ {
+ this->MaybeDirtyStore();
+ bookAtom = space->MakeBookAtomCopy(ev, *keyAtom);
+ if ( bookAtom )
+ {
+ outToken = bookAtom->mBookAtom_Id;
+ bookAtom->MakeCellUseForever(ev);
+ }
+ }
+ }
+ }
+ }
+ else // only a single byte in inTokenName string:
+ outToken = *s;
+ }
+
+ return outToken;
+}
+
+mork_token
+morkStore::StringToToken(morkEnv* ev, const char* inTokenName)
+{
+ mork_token outToken = 0;
+ if ( ev->Good() )
+ {
+ const mork_u1* s = (const mork_u1*) inTokenName;
+ mork_bool nonAscii = ( *s > 0x7F );
+ if ( nonAscii || ( *s && s[ 1 ] ) ) // more than one byte?
+ {
+ mork_cscode form = 0; // default charset
+ morkAtomSpace* groundSpace = this->LazyGetGroundColumnSpace(ev);
+ if ( groundSpace )
+ {
+ morkFarBookAtom* keyAtom =
+ this->StageStringAsFarBookAtom(ev, inTokenName, form, groundSpace);
+ if ( keyAtom )
+ {
+ morkAtomBodyMap* map = &groundSpace->mAtomSpace_AtomBodies;
+ morkBookAtom* bookAtom = map->GetAtom(ev, keyAtom);
+ if ( bookAtom )
+ outToken = bookAtom->mBookAtom_Id;
+ else
+ {
+ this->MaybeDirtyStore();
+ bookAtom = groundSpace->MakeBookAtomCopy(ev, *keyAtom);
+ if ( bookAtom )
+ {
+ outToken = bookAtom->mBookAtom_Id;
+ bookAtom->MakeCellUseForever(ev);
+ }
+ }
+ }
+ }
+ }
+ else // only a single byte in inTokenName string:
+ outToken = *s;
+ }
+
+ return outToken;
+}
+
+mork_token
+morkStore::QueryToken(morkEnv* ev, const char* inTokenName)
+{
+ mork_token outToken = 0;
+ if ( ev->Good() )
+ {
+ const mork_u1* s = (const mork_u1*) inTokenName;
+ mork_bool nonAscii = ( *s > 0x7F );
+ if ( nonAscii || ( *s && s[ 1 ] ) ) // more than one byte?
+ {
+ mork_cscode form = 0; // default charset
+ morkAtomSpace* groundSpace = this->LazyGetGroundColumnSpace(ev);
+ if ( groundSpace )
+ {
+ morkFarBookAtom* keyAtom =
+ this->StageStringAsFarBookAtom(ev, inTokenName, form, groundSpace);
+ if ( keyAtom )
+ {
+ morkAtomBodyMap* map = &groundSpace->mAtomSpace_AtomBodies;
+ morkBookAtom* bookAtom = map->GetAtom(ev, keyAtom);
+ if ( bookAtom )
+ {
+ outToken = bookAtom->mBookAtom_Id;
+ bookAtom->MakeCellUseForever(ev);
+ }
+ }
+ }
+ }
+ else // only a single byte in inTokenName string:
+ outToken = *s;
+ }
+
+ return outToken;
+}
+
+mork_bool
+morkStore::HasTableKind(morkEnv* ev, mdb_scope inRowScope,
+ mdb_kind inTableKind, mdb_count* outTableCount)
+{
+ MORK_USED_2(inRowScope,inTableKind);
+ mork_bool outBool = morkBool_kFalse;
+ mdb_count tableCount = 0;
+
+ ev->StubMethodOnlyError();
+
+ if ( outTableCount )
+ *outTableCount = tableCount;
+ return outBool;
+}
+
+morkTable*
+morkStore::GetTableKind(morkEnv* ev, mdb_scope inRowScope,
+ mdb_kind inTableKind, mdb_count* outTableCount,
+ mdb_bool* outMustBeUnique)
+{
+ morkTable* outTable = 0;
+ if ( ev->Good() )
+ {
+ morkRowSpace* rowSpace = this->LazyGetRowSpace(ev, inRowScope);
+ if ( rowSpace )
+ {
+ outTable = rowSpace->FindTableByKind(ev, inTableKind);
+ if ( outTable )
+ {
+ if ( outTableCount )
+ *outTableCount = outTable->GetRowCount();
+ if ( outMustBeUnique )
+ *outMustBeUnique = outTable->IsTableUnique();
+ }
+ }
+ }
+ return outTable;
+}
+
+morkRow*
+morkStore::FindRow(morkEnv* ev, mdb_scope inScope, mdb_column inColumn,
+ const mdbYarn* inYarn)
+{
+ morkRow* outRow = 0;
+ if ( ev->Good() )
+ {
+ morkRowSpace* rowSpace = this->LazyGetRowSpace(ev, inScope);
+ if ( rowSpace )
+ {
+ outRow = rowSpace->FindRow(ev, inColumn, inYarn);
+ }
+ }
+ return outRow;
+}
+
+morkRow*
+morkStore::GetRow(morkEnv* ev, const mdbOid* inOid)
+{
+ morkRow* outRow = 0;
+ if ( ev->Good() )
+ {
+ morkRowSpace* rowSpace = this->LazyGetRowSpace(ev, inOid->mOid_Scope);
+ if ( rowSpace )
+ {
+ outRow = rowSpace->mRowSpace_Rows.GetOid(ev, inOid);
+ }
+ }
+ return outRow;
+}
+
+morkTable*
+morkStore::GetTable(morkEnv* ev, const mdbOid* inOid)
+{
+ morkTable* outTable = 0;
+ if ( ev->Good() )
+ {
+ morkRowSpace* rowSpace = this->LazyGetRowSpace(ev, inOid->mOid_Scope);
+ if ( rowSpace )
+ {
+ outTable = rowSpace->FindTableByTid(ev, inOid->mOid_Id);
+ }
+ }
+ return outTable;
+}
+
+morkTable*
+morkStore::NewTable(morkEnv* ev, mdb_scope inRowScope,
+ mdb_kind inTableKind, mdb_bool inMustBeUnique,
+ const mdbOid* inOptionalMetaRowOid) // can be nil to avoid specifying
+{
+ morkTable* outTable = 0;
+ if ( ev->Good() )
+ {
+ morkRowSpace* rowSpace = this->LazyGetRowSpace(ev, inRowScope);
+ if ( rowSpace )
+ outTable = rowSpace->NewTable(ev, inTableKind, inMustBeUnique,
+ inOptionalMetaRowOid);
+ }
+ return outTable;
+}
+
+morkPortTableCursor*
+morkStore::GetPortTableCursor(morkEnv* ev, mdb_scope inRowScope,
+ mdb_kind inTableKind)
+{
+ morkPortTableCursor* outCursor = 0;
+ if ( ev->Good() )
+ {
+ nsIMdbHeap* heap = mPort_Heap;
+ outCursor = new(*heap, ev)
+ morkPortTableCursor(ev, morkUsage::kHeap, heap, this,
+ inRowScope, inTableKind, heap);
+ }
+ NS_IF_ADDREF(outCursor);
+ return outCursor;
+}
+
+morkRow*
+morkStore::NewRow(morkEnv* ev, mdb_scope inRowScope)
+{
+ morkRow* outRow = 0;
+ if ( ev->Good() )
+ {
+ morkRowSpace* rowSpace = this->LazyGetRowSpace(ev, inRowScope);
+ if ( rowSpace )
+ outRow = rowSpace->NewRow(ev);
+ }
+ return outRow;
+}
+
+morkRow*
+morkStore::NewRowWithOid(morkEnv* ev, const mdbOid* inOid)
+{
+ morkRow* outRow = 0;
+ if ( ev->Good() )
+ {
+ morkRowSpace* rowSpace = this->LazyGetRowSpace(ev, inOid->mOid_Scope);
+ if ( rowSpace )
+ outRow = rowSpace->NewRowWithOid(ev, inOid);
+ }
+ return outRow;
+}
+
+morkRow*
+morkStore::OidToRow(morkEnv* ev, const mdbOid* inOid)
+ // OidToRow() finds old row with oid, or makes new one if not found.
+{
+ morkRow* outRow = 0;
+ if ( ev->Good() )
+ {
+ morkRowSpace* rowSpace = this->LazyGetRowSpace(ev, inOid->mOid_Scope);
+ if ( rowSpace )
+ {
+ outRow = rowSpace->mRowSpace_Rows.GetOid(ev, inOid);
+ if ( !outRow && ev->Good() )
+ outRow = rowSpace->NewRowWithOid(ev, inOid);
+ }
+ }
+ return outRow;
+}
+
+morkTable*
+morkStore::OidToTable(morkEnv* ev, const mdbOid* inOid,
+ const mdbOid* inOptionalMetaRowOid) // can be nil to avoid specifying
+ // OidToTable() finds old table with oid, or makes new one if not found.
+{
+ morkTable* outTable = 0;
+ if ( ev->Good() )
+ {
+ morkRowSpace* rowSpace = this->LazyGetRowSpace(ev, inOid->mOid_Scope);
+ if ( rowSpace )
+ {
+ outTable = rowSpace->mRowSpace_Tables.GetTable(ev, inOid->mOid_Id);
+ if ( !outTable && ev->Good() )
+ {
+ mork_kind tableKind = morkStore_kNoneToken;
+ outTable = rowSpace->NewTableWithTid(ev, inOid->mOid_Id, tableKind,
+ inOptionalMetaRowOid);
+ }
+ }
+ }
+ return outTable;
+}
+
+// { ===== begin nsIMdbObject methods =====
+
+// { ----- begin ref counting for well-behaved cyclic graphs -----
+NS_IMETHODIMP
+morkStore::GetWeakRefCount(nsIMdbEnv* mev, // weak refs
+ mdb_count* outCount)
+{
+ *outCount = WeakRefsOnly();
+ return NS_OK;
+}
+NS_IMETHODIMP
+morkStore::GetStrongRefCount(nsIMdbEnv* mev, // strong refs
+ mdb_count* outCount)
+{
+ *outCount = StrongRefsOnly();
+ return NS_OK;
+}
+// ### TODO - clean up this cast, if required
+NS_IMETHODIMP
+morkStore::AddWeakRef(nsIMdbEnv* mev)
+{
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ // XXX Casting mork_refs to nsresult
+ return static_cast<nsresult>(morkNode::AddWeakRef(ev));
+}
+#ifndef _MSC_VER
+NS_IMETHODIMP_(mork_uses)
+morkStore::AddStrongRef(morkEnv* mev)
+{
+ return AddRef();
+}
+#endif
+NS_IMETHODIMP_(mork_uses)
+morkStore::AddStrongRef(nsIMdbEnv* mev)
+{
+ return AddRef();
+}
+NS_IMETHODIMP
+morkStore::CutWeakRef(nsIMdbEnv* mev)
+{
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ // XXX Casting mork_refs to nsresult
+ return static_cast<nsresult>(morkNode::CutWeakRef(ev));
+}
+#ifndef _MSC_VER
+NS_IMETHODIMP_(mork_uses)
+morkStore::CutStrongRef(morkEnv* mev)
+{
+ return Release();
+}
+#endif
+NS_IMETHODIMP
+morkStore::CutStrongRef(nsIMdbEnv* mev)
+{
+ // XXX Casting nsrefcnt to nsresult
+ return static_cast<nsresult>(Release());
+}
+
+NS_IMETHODIMP
+morkStore::CloseMdbObject(nsIMdbEnv* mev)
+{
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ CloseMorkNode(ev);
+ Release();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkStore::IsOpenMdbObject(nsIMdbEnv* mev, mdb_bool* outOpen)
+{
+ *outOpen = IsOpenNode();
+ return NS_OK;
+}
+// } ----- end ref counting -----
+
+// } ===== end nsIMdbObject methods =====
+
+// { ===== begin nsIMdbPort methods =====
+
+// { ----- begin attribute methods -----
+NS_IMETHODIMP
+morkStore::GetIsPortReadonly(nsIMdbEnv* mev, mdb_bool* outBool)
+{
+ nsresult outErr = NS_OK;
+ mdb_bool isReadOnly = morkBool_kFalse;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ ev->StubMethodOnlyError();
+ outErr = ev->AsErr();
+ }
+ if ( outBool )
+ *outBool = isReadOnly;
+ return outErr;
+}
+
+morkEnv*
+morkStore::CanUseStore(nsIMdbEnv* mev, mork_bool inMutable,
+ nsresult* outErr) const
+{
+ morkEnv* outEnv = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if (IsStore())
+ outEnv = ev;
+ else
+ NonStoreTypeError(ev);
+ *outErr = ev->AsErr();
+ }
+ MORK_ASSERT(outEnv);
+ return outEnv;
+}
+
+NS_IMETHODIMP
+morkStore::GetIsStore(nsIMdbEnv* mev, mdb_bool* outBool)
+{
+ MORK_USED_1(mev);
+ if ( outBool )
+ *outBool = morkBool_kTrue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkStore::GetIsStoreAndDirty(nsIMdbEnv* mev, mdb_bool* outBool)
+{
+ nsresult outErr = NS_OK;
+ mdb_bool isStoreAndDirty = morkBool_kFalse;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ ev->StubMethodOnlyError();
+ outErr = ev->AsErr();
+ }
+ if ( outBool )
+ *outBool = isStoreAndDirty;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::GetUsagePolicy(nsIMdbEnv* mev,
+ mdbUsagePolicy* ioUsagePolicy)
+{
+ MORK_USED_1(ioUsagePolicy);
+ nsresult outErr = NS_OK;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ ev->StubMethodOnlyError();
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::SetUsagePolicy(nsIMdbEnv* mev,
+ const mdbUsagePolicy* inUsagePolicy)
+{
+ MORK_USED_1(inUsagePolicy);
+ nsresult outErr = NS_OK;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ // ev->StubMethodOnlyError(); // okay to do nothing?
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+// } ----- end attribute methods -----
+
+// { ----- begin memory policy methods -----
+NS_IMETHODIMP
+morkStore::IdleMemoryPurge( // do memory management already scheduled
+ nsIMdbEnv* mev, // context
+ mdb_size* outEstimatedBytesFreed) // approximate bytes actually freed
+{
+ nsresult outErr = NS_OK;
+ mdb_size estimatedBytesFreed = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ // ev->StubMethodOnlyError(); // okay to do nothing?
+ outErr = ev->AsErr();
+ }
+ if ( outEstimatedBytesFreed )
+ *outEstimatedBytesFreed = estimatedBytesFreed;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::SessionMemoryPurge( // request specific footprint decrease
+ nsIMdbEnv* mev, // context
+ mdb_size inDesiredBytesFreed, // approximate number of bytes wanted
+ mdb_size* outEstimatedBytesFreed) // approximate bytes actually freed
+{
+ MORK_USED_1(inDesiredBytesFreed);
+ nsresult outErr = NS_OK;
+ mdb_size estimate = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ // ev->StubMethodOnlyError(); // okay to do nothing?
+ outErr = ev->AsErr();
+ }
+ if ( outEstimatedBytesFreed )
+ *outEstimatedBytesFreed = estimate;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::PanicMemoryPurge( // desperately free all possible memory
+ nsIMdbEnv* mev, // context
+ mdb_size* outEstimatedBytesFreed) // approximate bytes actually freed
+{
+ nsresult outErr = NS_OK;
+ mdb_size estimate = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ // ev->StubMethodOnlyError(); // okay to do nothing?
+ outErr = ev->AsErr();
+ }
+ if ( outEstimatedBytesFreed )
+ *outEstimatedBytesFreed = estimate;
+ return outErr;
+}
+// } ----- end memory policy methods -----
+
+// { ----- begin filepath methods -----
+NS_IMETHODIMP
+morkStore::GetPortFilePath(
+ nsIMdbEnv* mev, // context
+ mdbYarn* outFilePath, // name of file holding port content
+ mdbYarn* outFormatVersion) // file format description
+{
+ nsresult outErr = NS_OK;
+ if ( outFormatVersion )
+ outFormatVersion->mYarn_Fill = 0;
+ if ( outFilePath )
+ outFilePath->mYarn_Fill = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ if ( mStore_File )
+ mStore_File->Path(mev, outFilePath);
+ else
+ NilStoreFileError(ev);
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::GetPortFile(
+ nsIMdbEnv* mev, // context
+ nsIMdbFile** acqFile) // acquire file used by port or store
+{
+ nsresult outErr = NS_OK;
+ if ( acqFile )
+ *acqFile = 0;
+
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+
+ if ( mStore_File )
+ {
+ if ( acqFile )
+ {
+ mStore_File->AddRef();
+ if ( ev->Good() )
+ *acqFile = mStore_File;
+ }
+ }
+ else
+ NilStoreFileError(ev);
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+// } ----- end filepath methods -----
+
+// { ----- begin export methods -----
+NS_IMETHODIMP
+morkStore::BestExportFormat( // determine preferred export format
+ nsIMdbEnv* mev, // context
+ mdbYarn* outFormatVersion) // file format description
+{
+ nsresult outErr = NS_OK;
+ if ( outFormatVersion )
+ outFormatVersion->mYarn_Fill = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ ev->StubMethodOnlyError();
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::CanExportToFormat( // can export content in given specific format?
+ nsIMdbEnv* mev, // context
+ const char* inFormatVersion, // file format description
+ mdb_bool* outCanExport) // whether ExportSource() might succeed
+{
+ MORK_USED_1(inFormatVersion);
+ mdb_bool canExport = morkBool_kFalse;
+ nsresult outErr = NS_OK;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ ev->StubMethodOnlyError();
+ outErr = ev->AsErr();
+ }
+ if ( outCanExport )
+ *outCanExport = canExport;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::ExportToFormat( // export content in given specific format
+ nsIMdbEnv* mev, // context
+ // const char* inFilePath, // the file to receive exported content
+ nsIMdbFile* ioFile, // destination abstract file interface
+ const char* inFormatVersion, // file format description
+ nsIMdbThumb** acqThumb) // acquire thumb for incremental export
+// Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+// then the export will be finished.
+{
+ nsresult outErr = NS_OK;
+ nsIMdbThumb* outThumb = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ if ( ioFile && inFormatVersion && acqThumb )
+ {
+ ev->StubMethodOnlyError();
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( acqThumb )
+ *acqThumb = outThumb;
+ return outErr;
+}
+
+// } ----- end export methods -----
+
+// { ----- begin token methods -----
+NS_IMETHODIMP
+morkStore::TokenToString( // return a string name for an integer token
+ nsIMdbEnv* mev, // context
+ mdb_token inToken, // token for inTokenName inside this port
+ mdbYarn* outTokenName) // the type of table to access
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ TokenToString(ev, inToken, outTokenName);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::StringToToken( // return an integer token for scope name
+ nsIMdbEnv* mev, // context
+ const char* inTokenName, // Latin1 string to tokenize if possible
+ mdb_token* outToken) // token for inTokenName inside this port
+ // String token zero is never used and never supported. If the port
+ // is a mutable store, then StringToToken() to create a new
+ // association of inTokenName with a new integer token if possible.
+ // But a readonly port will return zero for an unknown scope name.
+{
+ nsresult outErr = NS_OK;
+ mdb_token token = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ token = StringToToken(ev, inTokenName);
+ outErr = ev->AsErr();
+ }
+ if ( outToken )
+ *outToken = token;
+ return outErr;
+}
+
+
+NS_IMETHODIMP
+morkStore::QueryToken( // like StringToToken(), but without adding
+ nsIMdbEnv* mev, // context
+ const char* inTokenName, // Latin1 string to tokenize if possible
+ mdb_token* outToken) // token for inTokenName inside this port
+ // QueryToken() will return a string token if one already exists,
+ // but unlike StringToToken(), will not assign a new token if not
+ // already in use.
+{
+ nsresult outErr = NS_OK;
+ mdb_token token = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ token = QueryToken(ev, inTokenName);
+ outErr = ev->AsErr();
+ }
+ if ( outToken )
+ *outToken = token;
+ return outErr;
+}
+
+
+// } ----- end token methods -----
+
+// { ----- begin row methods -----
+NS_IMETHODIMP
+morkStore::HasRow( // contains a row with the specified oid?
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid, // hypothetical row oid
+ mdb_bool* outHasRow) // whether GetRow() might succeed
+{
+ nsresult outErr = NS_OK;
+ mdb_bool hasRow = morkBool_kFalse;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkRow* row = GetRow(ev, inOid);
+ if ( row )
+ hasRow = morkBool_kTrue;
+
+ outErr = ev->AsErr();
+ }
+ if ( outHasRow )
+ *outHasRow = hasRow;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::GetRow( // access one row with specific oid
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid, // hypothetical row oid
+ nsIMdbRow** acqRow) // acquire specific row (or null)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkRow* row = GetRow(ev, inOid);
+ if ( row && ev->Good() )
+ outRow = row->AcquireRowHandle(ev, this);
+
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::GetRowRefCount( // get number of tables that contain a row
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid, // hypothetical row oid
+ mdb_count* outRefCount) // number of tables containing inRowKey
+{
+ nsresult outErr = NS_OK;
+ mdb_count count = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkRow* row = GetRow(ev, inOid);
+ if ( row && ev->Good() )
+ count = row->mRow_GcUses;
+
+ outErr = ev->AsErr();
+ }
+ if ( outRefCount )
+ *outRefCount = count;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::FindRow(nsIMdbEnv* mev, // search for row with matching cell
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_column inColumn, // the column to search (and maintain an index)
+ const mdbYarn* inTargetCellValue, // cell value for which to search
+ mdbOid* outRowOid, // out row oid on match (or {0,-1} for no match)
+ nsIMdbRow** acqRow) // acquire matching row (or nil for no match)
+ // FindRow() searches for one row that has a cell in column inColumn with
+ // a contained value with the same form (i.e. charset) and is byte-wise
+ // identical to the blob described by yarn inTargetCellValue. Both content
+ // and form of the yarn must be an exact match to find a matching row.
+ //
+ // (In other words, both a yarn's blob bytes and form are significant. The
+ // form is not expected to vary in columns used for identity anyway. This
+ // is intended to make the cost of FindRow() cheaper for MDB implementors,
+ // since any cell value atomization performed internally must necessarily
+ // make yarn form significant in order to avoid data loss in atomization.)
+ //
+ // FindRow() can lazily create an index on attribute inColumn for all rows
+ // with that attribute in row space scope inRowScope, so that subsequent
+ // calls to FindRow() will perform faster. Such an index might or might
+ // not be persistent (but this seems desirable if it is cheap to do so).
+ // Note that lazy index creation in readonly DBs is not very feasible.
+ //
+ // This FindRow() interface assumes that attribute inColumn is effectively
+ // an alternative means of unique identification for a row in a rowspace,
+ // so correct behavior is only guaranteed when no duplicates for this col
+ // appear in the given set of rows. (If more than one row has the same cell
+ // value in this column, no more than one will be found; and cutting one of
+ // two duplicate rows can cause the index to assume no other such row lives
+ // in the row space, so future calls return nil for negative search results
+ // even though some duplicate row might still live within the rowspace.)
+ //
+ // In other words, the FindRow() implementation is allowed to assume simple
+ // hash tables mapping unqiue column keys to associated row values will be
+ // sufficient, where any duplication is not recorded because only one copy
+ // of a given key need be remembered. Implementors are not required to sort
+ // all rows by the specified column.
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ mdbOid rowOid;
+ rowOid.mOid_Scope = 0;
+ rowOid.mOid_Id = (mdb_id) -1;
+
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkRow* row = FindRow(ev, inRowScope, inColumn, inTargetCellValue);
+ if ( row && ev->Good() )
+ {
+ rowOid = row->mRow_Oid;
+ if ( acqRow )
+ outRow = row->AcquireRowHandle(ev, this);
+ }
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+ if ( outRowOid )
+ *outRowOid = rowOid;
+
+ return outErr;
+}
+
+// } ----- end row methods -----
+
+// { ----- begin table methods -----
+NS_IMETHODIMP
+morkStore::HasTable( // supports a table with the specified oid?
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid, // hypothetical table oid
+ mdb_bool* outHasTable) // whether GetTable() might succeed
+{
+ nsresult outErr = NS_OK;
+ mork_bool hasTable = morkBool_kFalse;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkTable* table = GetTable(ev, inOid);
+ if ( table )
+ hasTable = morkBool_kTrue;
+
+ outErr = ev->AsErr();
+ }
+ if ( outHasTable )
+ *outHasTable = hasTable;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::GetTable( // access one table with specific oid
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid, // hypothetical table oid
+ nsIMdbTable** acqTable) // acquire specific table (or null)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbTable* outTable = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkTable* table = GetTable(ev, inOid);
+ if ( table && ev->Good() )
+ outTable = table->AcquireTableHandle(ev);
+ outErr = ev->AsErr();
+ }
+ if ( acqTable )
+ *acqTable = outTable;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::HasTableKind( // supports a table of the specified type?
+ nsIMdbEnv* mev, // context
+ mdb_scope inRowScope, // rid scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ mdb_count* outTableCount, // current number of such tables
+ mdb_bool* outSupportsTable) // whether GetTableKind() might succeed
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ *outSupportsTable = HasTableKind(ev, inRowScope,
+ inTableKind, outTableCount);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::GetTableKind( // access one (random) table of specific type
+ nsIMdbEnv* mev, // context
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ mdb_count* outTableCount, // current number of such tables
+ mdb_bool* outMustBeUnique, // whether port can hold only one of these
+ nsIMdbTable** acqTable) // acquire scoped collection of rows
+{
+ nsresult outErr = NS_OK;
+ nsIMdbTable* outTable = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkTable* table = GetTableKind(ev, inRowScope,
+ inTableKind, outTableCount, outMustBeUnique);
+ if ( table && ev->Good() )
+ outTable = table->AcquireTableHandle(ev);
+ outErr = ev->AsErr();
+ }
+ if ( acqTable )
+ *acqTable = outTable;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::GetPortTableCursor( // get cursor for all tables of specific type
+ nsIMdbEnv* mev, // context
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ nsIMdbPortTableCursor** acqCursor) // all such tables in the port
+{
+ nsresult outErr = NS_OK;
+ nsIMdbPortTableCursor* outCursor = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkPortTableCursor* cursor =
+ GetPortTableCursor(ev, inRowScope,
+ inTableKind);
+ if ( cursor && ev->Good() )
+ outCursor = cursor;
+
+ outErr = ev->AsErr();
+ }
+ if ( acqCursor )
+ *acqCursor = outCursor;
+ return outErr;
+}
+// } ----- end table methods -----
+
+// { ----- begin commit methods -----
+
+NS_IMETHODIMP
+morkStore::ShouldCompress( // store wastes at least inPercentWaste?
+ nsIMdbEnv* mev, // context
+ mdb_percent inPercentWaste, // 0..100 percent file size waste threshold
+ mdb_percent* outActualWaste, // 0..100 percent of file actually wasted
+ mdb_bool* outShould) // true when about inPercentWaste% is wasted
+{
+ mdb_percent actualWaste = 0;
+ mdb_bool shouldCompress = morkBool_kFalse;
+ nsresult outErr = NS_OK;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ actualWaste = PercentOfStoreWasted(ev);
+ if ( inPercentWaste > 100 )
+ inPercentWaste = 100;
+ shouldCompress = ( actualWaste >= inPercentWaste );
+ outErr = ev->AsErr();
+ }
+ if ( outActualWaste )
+ *outActualWaste = actualWaste;
+ if ( outShould )
+ *outShould = shouldCompress;
+ return outErr;
+}
+
+
+// } ===== end nsIMdbPort methods =====
+
+NS_IMETHODIMP
+morkStore::NewTable( // make one new table of specific type
+ nsIMdbEnv* mev, // context
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ mdb_bool inMustBeUnique, // whether store can hold only one of these
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ nsIMdbTable** acqTable) // acquire scoped collection of rows
+{
+ nsresult outErr = NS_OK;
+ nsIMdbTable* outTable = 0;
+ morkEnv* ev = this->CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkTable* table = NewTable(ev, inRowScope,
+ inTableKind, inMustBeUnique, inOptionalMetaRowOid);
+ if ( table && ev->Good() )
+ outTable = table->AcquireTableHandle(ev);
+ outErr = ev->AsErr();
+ }
+ if ( acqTable )
+ *acqTable = outTable;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::NewTableWithOid( // make one new table of specific type
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid, // caller assigned oid
+ mdb_kind inTableKind, // the type of table to access
+ mdb_bool inMustBeUnique, // whether store can hold only one of these
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ nsIMdbTable** acqTable) // acquire scoped collection of rows
+{
+ nsresult outErr = NS_OK;
+ nsIMdbTable* outTable = 0;
+ morkEnv* ev = CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkTable* table = OidToTable(ev, inOid,
+ inOptionalMetaRowOid);
+ if ( table && ev->Good() )
+ {
+ table->mTable_Kind = inTableKind;
+ if ( inMustBeUnique )
+ table->SetTableUnique();
+ outTable = table->AcquireTableHandle(ev);
+ }
+ outErr = ev->AsErr();
+ }
+ if ( acqTable )
+ *acqTable = outTable;
+ return outErr;
+}
+// } ----- end table methods -----
+
+// { ----- begin row scope methods -----
+NS_IMETHODIMP
+morkStore::RowScopeHasAssignedIds(nsIMdbEnv* mev,
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
+ mdb_bool* outStoreAssigned) // nonzero if store db assigned specified
+{
+ NS_ASSERTION(false, " not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkStore::SetCallerAssignedIds(nsIMdbEnv* mev,
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
+ mdb_bool* outStoreAssigned) // nonzero if store db assigned specified
+{
+ NS_ASSERTION(false, " not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkStore::SetStoreAssignedIds(nsIMdbEnv* mev,
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
+ mdb_bool* outStoreAssigned) // nonzero if store db assigned specified
+{
+ NS_ASSERTION(false, " not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+// } ----- end row scope methods -----
+
+// { ----- begin row methods -----
+NS_IMETHODIMP
+morkStore::NewRowWithOid(nsIMdbEnv* mev, // new row w/ caller assigned oid
+ const mdbOid* inOid, // caller assigned oid
+ nsIMdbRow** acqRow) // create new row
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ morkEnv* ev = CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkRow* row = NewRowWithOid(ev, inOid);
+ if ( row && ev->Good() )
+ outRow = row->AcquireRowHandle(ev, this);
+
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::NewRow(nsIMdbEnv* mev, // new row with db assigned oid
+ mdb_scope inRowScope, // row scope for row ids
+ nsIMdbRow** acqRow) // create new row
+// Note this row must be added to some table or cell child before the
+// store is closed in order to make this row persist across sessions.
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ morkEnv* ev = CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ morkRow* row = NewRow(ev, inRowScope);
+ if ( row && ev->Good() )
+ outRow = row->AcquireRowHandle(ev, this);
+
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+ return outErr;
+}
+// } ----- end row methods -----
+
+// { ----- begin inport/export methods -----
+NS_IMETHODIMP
+morkStore::ImportContent( // import content from port
+ nsIMdbEnv* mev, // context
+ mdb_scope inRowScope, // scope for rows (or zero for all?)
+ nsIMdbPort* ioPort, // the port with content to add to store
+ nsIMdbThumb** acqThumb) // acquire thumb for incremental import
+// Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+// then the import will be finished.
+{
+ NS_ASSERTION(false, " not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkStore::ImportFile( // import content from port
+ nsIMdbEnv* mev, // context
+ nsIMdbFile* ioFile, // the file with content to add to store
+ nsIMdbThumb** acqThumb) // acquire thumb for incremental import
+// Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+// then the import will be finished.
+{
+ NS_ASSERTION(false, " not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+// } ----- end inport/export methods -----
+
+// { ----- begin hinting methods -----
+NS_IMETHODIMP
+morkStore::ShareAtomColumnsHint( // advise re shared col content atomizing
+ nsIMdbEnv* mev, // context
+ mdb_scope inScopeHint, // zero, or suggested shared namespace
+ const mdbColumnSet* inColumnSet) // cols desired tokenized together
+{
+ MORK_USED_2(inColumnSet,inScopeHint);
+ nsresult outErr = NS_OK;
+ morkEnv* ev = CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ // ev->StubMethodOnlyError(); // okay to do nothing for a hint method
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::AvoidAtomColumnsHint( // advise col w/ poor atomizing prospects
+ nsIMdbEnv* mev, // context
+ const mdbColumnSet* inColumnSet) // cols with poor atomizing prospects
+{
+ MORK_USED_1(inColumnSet);
+ nsresult outErr = NS_OK;
+ morkEnv* ev = CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ // ev->StubMethodOnlyError(); // okay to do nothing for a hint method
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+// } ----- end hinting methods -----
+
+// { ----- begin commit methods -----
+NS_IMETHODIMP
+morkStore::LargeCommit( // save important changes if at all possible
+ nsIMdbEnv* mev, // context
+ nsIMdbThumb** acqThumb) // acquire thumb for incremental commit
+// Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+// then the commit will be finished. Note the store is effectively write
+// locked until commit is finished or canceled through the thumb instance.
+// Until the commit is done, the store will report it has readonly status.
+{
+ nsresult outErr = NS_OK;
+ nsIMdbThumb* outThumb = 0;
+ morkEnv* ev = CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+
+ morkThumb* thumb = 0;
+ // morkFile* file = store->mStore_File;
+ if ( DoPreferLargeOverCompressCommit(ev) )
+ {
+ thumb = morkThumb::Make_LargeCommit(ev, mPort_Heap, this);
+ }
+ else
+ {
+ mork_bool doCollect = morkBool_kFalse;
+ thumb = morkThumb::Make_CompressCommit(ev, mPort_Heap, this, doCollect);
+ }
+
+ if ( thumb )
+ {
+ outThumb = thumb;
+ thumb->AddRef();
+ }
+
+ outErr = ev->AsErr();
+ }
+ if ( acqThumb )
+ *acqThumb = outThumb;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::SessionCommit( // save all changes if large commits delayed
+ nsIMdbEnv* mev, // context
+ nsIMdbThumb** acqThumb) // acquire thumb for incremental commit
+// Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+// then the commit will be finished. Note the store is effectively write
+// locked until commit is finished or canceled through the thumb instance.
+// Until the commit is done, the store will report it has readonly status.
+{
+ nsresult outErr = NS_OK;
+ nsIMdbThumb* outThumb = 0;
+ morkEnv* ev = CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+
+ morkThumb* thumb = 0;
+ if ( DoPreferLargeOverCompressCommit(ev) )
+ {
+ thumb = morkThumb::Make_LargeCommit(ev, mPort_Heap, this);
+ }
+ else
+ {
+ mork_bool doCollect = morkBool_kFalse;
+ thumb = morkThumb::Make_CompressCommit(ev, mPort_Heap, this, doCollect);
+ }
+
+ if ( thumb )
+ {
+ outThumb = thumb;
+ thumb->AddRef();
+ }
+ outErr = ev->AsErr();
+ }
+ if ( acqThumb )
+ *acqThumb = outThumb;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkStore::CompressCommit( // commit and make db smaller if possible
+ nsIMdbEnv* mev, // context
+ nsIMdbThumb** acqThumb) // acquire thumb for incremental commit
+// Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+// then the commit will be finished. Note the store is effectively write
+// locked until commit is finished or canceled through the thumb instance.
+// Until the commit is done, the store will report it has readonly status.
+{
+ nsresult outErr = NS_OK;
+ nsIMdbThumb* outThumb = 0;
+ morkEnv* ev = CanUseStore(mev, /*inMutable*/ morkBool_kFalse, &outErr);
+ if ( ev )
+ {
+ mork_bool doCollect = morkBool_kFalse;
+ morkThumb* thumb = morkThumb::Make_CompressCommit(ev, mPort_Heap, this, doCollect);
+ if ( thumb )
+ {
+ outThumb = thumb;
+ thumb->AddRef();
+ mStore_CanWriteIncremental = morkBool_kTrue;
+ }
+
+ outErr = ev->AsErr();
+ }
+ if ( acqThumb )
+ *acqThumb = outThumb;
+ return outErr;
+}
+
+// } ----- end commit methods -----
+
+// } ===== end nsIMdbStore methods =====
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
diff --git a/components/mork/src/morkStore.h b/components/mork/src/morkStore.h
new file mode 100644
index 000000000..fe548245e
--- /dev/null
+++ b/components/mork/src/morkStore.h
@@ -0,0 +1,768 @@
+/* -*- 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/. */
+
+#ifndef _MORKSTORE_
+#define _MORKSTORE_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+#ifndef _MORKNODEMAP_
+#include "morkNodeMap.h"
+#endif
+
+#ifndef _MORKPOOL_
+#include "morkPool.h"
+#endif
+
+#ifndef _MORKZONE_
+#include "morkZone.h"
+#endif
+
+#ifndef _MORKATOM_
+#include "morkAtom.h"
+#endif
+
+#ifndef _MORKROWSPACE_
+#include "morkRowSpace.h"
+#endif
+
+#ifndef _MORKATOMSPACE_
+#include "morkAtomSpace.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kPort /*i*/ 0x7054 /* ascii 'pT' */
+
+#define morkDerived_kStore /*i*/ 0x7354 /* ascii 'sT' */
+
+/*| kGroundColumnSpace: we use the 'column space' as the default scope
+**| for grounding column name IDs, and this is also the default scope for
+**| all other explicitly tokenized strings.
+|*/
+#define morkStore_kGroundColumnSpace 'c' /* for mStore_GroundColumnSpace*/
+#define morkStore_kColumnSpaceScope ((mork_scope) 'c') /*kGroundColumnSpace*/
+#define morkStore_kValueSpaceScope ((mork_scope) 'v')
+#define morkStore_kStreamBufSize (8 * 1024) /* okay buffer size */
+
+#define morkStore_kReservedColumnCount 0x20 /* for well-known columns */
+
+#define morkStore_kNoneToken ((mork_token) 'n')
+#define morkStore_kFormColumn ((mork_column) 'f')
+#define morkStore_kAtomScopeColumn ((mork_column) 'a')
+#define morkStore_kRowScopeColumn ((mork_column) 'r')
+#define morkStore_kMetaScope ((mork_scope) 'm')
+#define morkStore_kKindColumn ((mork_column) 'k')
+#define morkStore_kStatusColumn ((mork_column) 's')
+
+/*| morkStore:
+|*/
+class morkStore : public morkObject, public nsIMdbStore {
+
+public: // state is public because the entire Mork system is private
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ morkEnv* mPort_Env; // non-refcounted env which created port
+ morkFactory* mPort_Factory; // weak ref to suite factory
+ nsIMdbHeap* mPort_Heap; // heap in which this port allocs objects
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+
+ void ClosePort(morkEnv* ev); // called by CloseMorkNode();
+
+public: // dynamic type identification
+ mork_bool IsPort() const
+ { return IsNode() && mNode_Derived == morkDerived_kPort; }
+// } ===== end morkNode methods =====
+
+public: // other port methods
+
+ // { ----- begin attribute methods -----
+// NS_IMETHOD IsFrozenMdbObject(nsIMdbEnv* ev, mdb_bool* outIsReadonly);
+ // same as nsIMdbPort::GetIsPortReadonly() when this object is inside a port.
+ // } ----- end attribute methods -----
+
+ // { ----- begin factory methods -----
+// NS_IMETHOD GetMdbFactory(nsIMdbEnv* ev, nsIMdbFactory** acqFactory);
+ // } ----- end factory methods -----
+
+ // { ----- begin ref counting for well-behaved cyclic graphs -----
+ NS_IMETHOD GetWeakRefCount(nsIMdbEnv* ev, // weak refs
+ mdb_count* outCount) override;
+ NS_IMETHOD GetStrongRefCount(nsIMdbEnv* ev, // strong refs
+ mdb_count* outCount) override;
+
+ NS_IMETHOD AddWeakRef(nsIMdbEnv* ev) override;
+#ifndef _MSC_VER
+ // The first declaration of AddStrongRef is to suppress -Werror,-Woverloaded-virtual.
+ NS_IMETHOD_(mork_uses) AddStrongRef(morkEnv* ev) override;
+#endif
+ NS_IMETHOD_(mork_uses) AddStrongRef(nsIMdbEnv* ev) override;
+
+ NS_IMETHOD CutWeakRef(nsIMdbEnv* ev) override;
+#ifndef _MSC_VER
+ // The first declaration of CutStrongRef is to suppress -Werror,-Woverloaded-virtual.
+ NS_IMETHOD_(mork_uses) CutStrongRef(morkEnv* ev) override;
+#endif
+ NS_IMETHOD CutStrongRef(nsIMdbEnv* ev) override;
+
+ NS_IMETHOD CloseMdbObject(nsIMdbEnv* ev) override; // called at strong refs zero
+ NS_IMETHOD IsOpenMdbObject(nsIMdbEnv* ev, mdb_bool* outOpen) override;
+ // } ----- end ref counting -----
+
+// } ===== end nsIMdbObject methods =====
+
+// { ===== begin nsIMdbPort methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD GetIsPortReadonly(nsIMdbEnv* ev, mdb_bool* outBool) override;
+ NS_IMETHOD GetIsStore(nsIMdbEnv* ev, mdb_bool* outBool) override;
+ NS_IMETHOD GetIsStoreAndDirty(nsIMdbEnv* ev, mdb_bool* outBool) override;
+
+ NS_IMETHOD GetUsagePolicy(nsIMdbEnv* ev,
+ mdbUsagePolicy* ioUsagePolicy) override;
+
+ NS_IMETHOD SetUsagePolicy(nsIMdbEnv* ev,
+ const mdbUsagePolicy* inUsagePolicy) override;
+ // } ----- end attribute methods -----
+
+ // { ----- begin memory policy methods -----
+ NS_IMETHOD IdleMemoryPurge( // do memory management already scheduled
+ nsIMdbEnv* ev, // context
+ mdb_size* outEstimatedBytesFreed) override; // approximate bytes actually freed
+
+ NS_IMETHOD SessionMemoryPurge( // request specific footprint decrease
+ nsIMdbEnv* ev, // context
+ mdb_size inDesiredBytesFreed, // approximate number of bytes wanted
+ mdb_size* outEstimatedBytesFreed) override; // approximate bytes actually freed
+
+ NS_IMETHOD PanicMemoryPurge( // desperately free all possible memory
+ nsIMdbEnv* ev, // context
+ mdb_size* outEstimatedBytesFreed) override; // approximate bytes actually freed
+ // } ----- end memory policy methods -----
+
+ // { ----- begin filepath methods -----
+ NS_IMETHOD GetPortFilePath(
+ nsIMdbEnv* ev, // context
+ mdbYarn* outFilePath, // name of file holding port content
+ mdbYarn* outFormatVersion) override; // file format description
+
+ NS_IMETHOD GetPortFile(
+ nsIMdbEnv* ev, // context
+ nsIMdbFile** acqFile) override; // acquire file used by port or store
+ // } ----- end filepath methods -----
+
+ // { ----- begin export methods -----
+ NS_IMETHOD BestExportFormat( // determine preferred export format
+ nsIMdbEnv* ev, // context
+ mdbYarn* outFormatVersion) override; // file format description
+
+ NS_IMETHOD
+ CanExportToFormat( // can export content in given specific format?
+ nsIMdbEnv* ev, // context
+ const char* inFormatVersion, // file format description
+ mdb_bool* outCanExport) override; // whether ExportSource() might succeed
+
+ NS_IMETHOD ExportToFormat( // export content in given specific format
+ nsIMdbEnv* ev, // context
+ // const char* inFilePath, // the file to receive exported content
+ nsIMdbFile* ioFile, // destination abstract file interface
+ const char* inFormatVersion, // file format description
+ nsIMdbThumb** acqThumb) override; // acquire thumb for incremental export
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the export will be finished.
+
+ // } ----- end export methods -----
+
+ // { ----- begin token methods -----
+ NS_IMETHOD TokenToString( // return a string name for an integer token
+ nsIMdbEnv* ev, // context
+ mdb_token inToken, // token for inTokenName inside this port
+ mdbYarn* outTokenName) override; // the type of table to access
+
+ NS_IMETHOD StringToToken( // return an integer token for scope name
+ nsIMdbEnv* ev, // context
+ const char* inTokenName, // Latin1 string to tokenize if possible
+ mdb_token* outToken) override; // token for inTokenName inside this port
+
+ // String token zero is never used and never supported. If the port
+ // is a mutable store, then StringToToken() to create a new
+ // association of inTokenName with a new integer token if possible.
+ // But a readonly port will return zero for an unknown scope name.
+
+ NS_IMETHOD QueryToken( // like StringToToken(), but without adding
+ nsIMdbEnv* ev, // context
+ const char* inTokenName, // Latin1 string to tokenize if possible
+ mdb_token* outToken) override; // token for inTokenName inside this port
+
+ // QueryToken() will return a string token if one already exists,
+ // but unlike StringToToken(), will not assign a new token if not
+ // already in use.
+
+ // } ----- end token methods -----
+
+ // { ----- begin row methods -----
+ NS_IMETHOD HasRow( // contains a row with the specified oid?
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // hypothetical row oid
+ mdb_bool* outHasRow) override; // whether GetRow() might succeed
+
+ NS_IMETHOD GetRowRefCount( // get number of tables that contain a row
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // hypothetical row oid
+ mdb_count* outRefCount) override; // number of tables containing inRowKey
+
+ NS_IMETHOD GetRow( // access one row with specific oid
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // hypothetical row oid
+ nsIMdbRow** acqRow) override; // acquire specific row (or null)
+
+ NS_IMETHOD FindRow(nsIMdbEnv* ev, // search for row with matching cell
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_column inColumn, // the column to search (and maintain an index)
+ const mdbYarn* inTargetCellValue, // cell value for which to search
+ mdbOid* outRowOid, // out row oid on match (or {0,-1} for no match)
+ nsIMdbRow** acqRow) override; // acquire matching row (or nil for no match)
+ // can be null if you only want the oid
+ // FindRow() searches for one row that has a cell in column inColumn with
+ // a contained value with the same form (i.e. charset) and is byte-wise
+ // identical to the blob described by yarn inTargetCellValue. Both content
+ // and form of the yarn must be an exact match to find a matching row.
+ //
+ // (In other words, both a yarn's blob bytes and form are significant. The
+ // form is not expected to vary in columns used for identity anyway. This
+ // is intended to make the cost of FindRow() cheaper for MDB implementors,
+ // since any cell value atomization performed internally must necessarily
+ // make yarn form significant in order to avoid data loss in atomization.)
+ //
+ // FindRow() can lazily create an index on attribute inColumn for all rows
+ // with that attribute in row space scope inRowScope, so that subsequent
+ // calls to FindRow() will perform faster. Such an index might or might
+ // not be persistent (but this seems desirable if it is cheap to do so).
+ // Note that lazy index creation in readonly DBs is not very feasible.
+ //
+ // This FindRow() interface assumes that attribute inColumn is effectively
+ // an alternative means of unique identification for a row in a rowspace,
+ // so correct behavior is only guaranteed when no duplicates for this col
+ // appear in the given set of rows. (If more than one row has the same cell
+ // value in this column, no more than one will be found; and cutting one of
+ // two duplicate rows can cause the index to assume no other such row lives
+ // in the row space, so future calls return nil for negative search results
+ // even though some duplicate row might still live within the rowspace.)
+ //
+ // In other words, the FindRow() implementation is allowed to assume simple
+ // hash tables mapping unqiue column keys to associated row values will be
+ // sufficient, where any duplication is not recorded because only one copy
+ // of a given key need be remembered. Implementors are not required to sort
+ // all rows by the specified column.
+ // } ----- end row methods -----
+
+ // { ----- begin table methods -----
+ NS_IMETHOD HasTable( // supports a table with the specified oid?
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // hypothetical table oid
+ mdb_bool* outHasTable) override; // whether GetTable() might succeed
+
+ NS_IMETHOD GetTable( // access one table with specific oid
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // hypothetical table oid
+ nsIMdbTable** acqTable) override; // acquire specific table (or null)
+
+ NS_IMETHOD HasTableKind( // supports a table of the specified type?
+ nsIMdbEnv* ev, // context
+ mdb_scope inRowScope, // rid scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ mdb_count* outTableCount, // current number of such tables
+ mdb_bool* outSupportsTable) override; // whether GetTableKind() might succeed
+
+ NS_IMETHOD GetTableKind( // access one (random) table of specific type
+ nsIMdbEnv* ev, // context
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ mdb_count* outTableCount, // current number of such tables
+ mdb_bool* outMustBeUnique, // whether port can hold only one of these
+ nsIMdbTable** acqTable) override; // acquire scoped collection of rows
+
+ NS_IMETHOD
+ GetPortTableCursor( // get cursor for all tables of specific type
+ nsIMdbEnv* ev, // context
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ nsIMdbPortTableCursor** acqCursor) override; // all such tables in the port
+ // } ----- end table methods -----
+
+
+ // { ----- begin commit methods -----
+
+ NS_IMETHOD ShouldCompress( // store wastes at least inPercentWaste?
+ nsIMdbEnv* ev, // context
+ mdb_percent inPercentWaste, // 0..100 percent file size waste threshold
+ mdb_percent* outActualWaste, // 0..100 percent of file actually wasted
+ mdb_bool* outShould) override; // true when about inPercentWaste% is wasted
+ // ShouldCompress() returns true if the store can determine that the file
+ // will shrink by an estimated percentage of inPercentWaste% (or more) if
+ // CompressCommit() is called, because that percentage of the file seems
+ // to be recoverable free space. The granularity is only in terms of
+ // percentage points, and any value over 100 is considered equal to 100.
+ //
+ // If a store only has an approximate idea how much space might be saved
+ // during a compress, then a best guess should be made. For example, the
+ // Mork implementation might keep track of how much file space began with
+ // text content before the first updating transaction, and then consider
+ // all content following the start of the first transaction as potentially
+ // wasted space if it is all updates and not just new content. (This is
+ // a safe assumption in the sense that behavior will stabilize on a low
+ // estimate of wastage after a commit removes all transaction updates.)
+ //
+ // Some db formats might attempt to keep a very accurate reckoning of free
+ // space size, so a very accurate determination can be made. But other db
+ // formats might have difficulty determining size of free space, and might
+ // require some lengthy calculation to answer. This is the reason for
+ // passing in the percentage threshold of interest, so that such lengthy
+ // computations can terminate early as soon as at least inPercentWaste is
+ // found, so that the entire file need not be groveled when unnecessary.
+ // However, we hope implementations will always favor fast but imprecise
+ // heuristic answers instead of extremely slow but very precise answers.
+ //
+ // If the outActualWaste parameter is non-nil, it will be used to return
+ // the actual estimated space wasted as a percentage of file size. (This
+ // parameter is provided so callers need not call repeatedly with altered
+ // inPercentWaste values to isolate the actual wastage figure.) Note the
+ // actual wastage figure returned can exactly equal inPercentWaste even
+ // when this grossly underestimates the real figure involved, if the db
+ // finds it very expensive to determine the extent of wastage after it is
+ // known to at least exceed inPercentWaste. Note we expect that whenever
+ // outShould returns true, that outActualWaste returns >= inPercentWaste.
+ //
+ // The effect of different inPercentWaste values is not very uniform over
+ // the permitted range. For example, 50 represents 50% wastage, or a file
+ // that is about double what it should be ideally. But 99 represents 99%
+ // wastage, or a file that is about ninety-nine times as big as it should
+ // be ideally. In the smaller direction, 25 represents 25% wastage, or
+ // a file that is only 33% larger than it should be ideally.
+ //
+ // Callers can determine what policy they want to use for considering when
+ // a file holds too much wasted space, and express this as a percentage
+ // of total file size to pass as in the inPercentWaste parameter. A zero
+ // likely returns always trivially true, and 100 always trivially false.
+ // The great majority of callers are expected to use values from 25 to 75,
+ // since most plausible thresholds for compressing might fall between the
+ // extremes of 133% of ideal size and 400% of ideal size. (Presumably the
+ // larger a file gets, the more important the percentage waste involved, so
+ // a sliding scale for compress thresholds might use smaller numbers for
+ // much bigger file sizes.)
+
+ // } ----- end commit methods -----
+
+// } ===== end nsIMdbPort methods =====
+
+// { ===== begin nsIMdbStore methods =====
+
+ // { ----- begin table methods -----
+ NS_IMETHOD NewTable( // make one new table of specific type
+ nsIMdbEnv* ev, // context
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_kind inTableKind, // the type of table to access
+ mdb_bool inMustBeUnique, // whether store can hold only one of these
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ nsIMdbTable** acqTable) override; // acquire scoped collection of rows
+
+ NS_IMETHOD NewTableWithOid( // make one new table of specific type
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // caller assigned oid
+ mdb_kind inTableKind, // the type of table to access
+ mdb_bool inMustBeUnique, // whether store can hold only one of these
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ nsIMdbTable** acqTable) override; // acquire scoped collection of rows
+ // } ----- end table methods -----
+
+ // { ----- begin row scope methods -----
+ NS_IMETHOD RowScopeHasAssignedIds(nsIMdbEnv* ev,
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
+ mdb_bool* outStoreAssigned) override; // nonzero if store db assigned specified
+
+ NS_IMETHOD SetCallerAssignedIds(nsIMdbEnv* ev,
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
+ mdb_bool* outStoreAssigned) override; // nonzero if store db assigned specified
+
+ NS_IMETHOD SetStoreAssignedIds(nsIMdbEnv* ev,
+ mdb_scope inRowScope, // row scope for row ids
+ mdb_bool* outCallerAssigned, // nonzero if caller assigned specified
+ mdb_bool* outStoreAssigned) override; // nonzero if store db assigned specified
+ // } ----- end row scope methods -----
+
+ // { ----- begin row methods -----
+ NS_IMETHOD NewRowWithOid(nsIMdbEnv* ev, // new row w/ caller assigned oid
+ const mdbOid* inOid, // caller assigned oid
+ nsIMdbRow** acqRow) override; // create new row
+
+ NS_IMETHOD NewRow(nsIMdbEnv* ev, // new row with db assigned oid
+ mdb_scope inRowScope, // row scope for row ids
+ nsIMdbRow** acqRow) override; // create new row
+ // Note this row must be added to some table or cell child before the
+ // store is closed in order to make this row persist across sessions.
+
+ // } ----- end row methods -----
+
+ // { ----- begin inport/export methods -----
+ NS_IMETHOD ImportContent( // import content from port
+ nsIMdbEnv* ev, // context
+ mdb_scope inRowScope, // scope for rows (or zero for all?)
+ nsIMdbPort* ioPort, // the port with content to add to store
+ nsIMdbThumb** acqThumb) override; // acquire thumb for incremental import
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the import will be finished.
+
+ NS_IMETHOD ImportFile( // import content from port
+ nsIMdbEnv* ev, // context
+ nsIMdbFile* ioFile, // the file with content to add to store
+ nsIMdbThumb** acqThumb) override; // acquire thumb for incremental import
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the import will be finished.
+ // } ----- end inport/export methods -----
+
+ // { ----- begin hinting methods -----
+ NS_IMETHOD
+ ShareAtomColumnsHint( // advise re shared column content atomizing
+ nsIMdbEnv* ev, // context
+ mdb_scope inScopeHint, // zero, or suggested shared namespace
+ const mdbColumnSet* inColumnSet) override; // cols desired tokenized together
+
+ NS_IMETHOD
+ AvoidAtomColumnsHint( // advise column with poor atomizing prospects
+ nsIMdbEnv* ev, // context
+ const mdbColumnSet* inColumnSet) override; // cols with poor atomizing prospects
+ // } ----- end hinting methods -----
+
+ // { ----- begin commit methods -----
+ NS_IMETHOD LargeCommit( // save important changes if at all possible
+ nsIMdbEnv* ev, // context
+ nsIMdbThumb** acqThumb) override; // acquire thumb for incremental commit
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the commit will be finished. Note the store is effectively write
+ // locked until commit is finished or canceled through the thumb instance.
+ // Until the commit is done, the store will report it has readonly status.
+
+ NS_IMETHOD SessionCommit( // save all changes if large commits delayed
+ nsIMdbEnv* ev, // context
+ nsIMdbThumb** acqThumb) override; // acquire thumb for incremental commit
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the commit will be finished. Note the store is effectively write
+ // locked until commit is finished or canceled through the thumb instance.
+ // Until the commit is done, the store will report it has readonly status.
+
+ NS_IMETHOD
+ CompressCommit( // commit and make db physically smaller if possible
+ nsIMdbEnv* ev, // context
+ nsIMdbThumb** acqThumb) override; // acquire thumb for incremental commit
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the commit will be finished. Note the store is effectively write
+ // locked until commit is finished or canceled through the thumb instance.
+ // Until the commit is done, the store will report it has readonly status.
+
+ // } ----- end commit methods -----
+
+// } ===== end nsIMdbStore methods =====
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakPort(morkPort* me,
+ morkEnv* ev, morkPort** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongPort(morkPort* me,
+ morkEnv* ev, morkPort** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+// public: // slots inherited from morkPort (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // morkEnv* mPort_Env; // non-refcounted env which created port
+ // morkFactory* mPort_Factory; // weak ref to suite factory
+ // nsIMdbHeap* mPort_Heap; // heap in which this port allocs objects
+
+public: // state is public because the entire Mork system is private
+
+// mStore_OidAtomSpace might be unnecessary; I don't remember why I wanted it.
+ morkAtomSpace* mStore_OidAtomSpace; // ground atom space for oids
+ morkAtomSpace* mStore_GroundAtomSpace; // ground atom space for scopes
+ morkAtomSpace* mStore_GroundColumnSpace; // ground column space for scopes
+
+ nsIMdbFile* mStore_File; // the file containing Mork text
+ morkStream* mStore_InStream; // stream using file used by the builder
+ morkBuilder* mStore_Builder; // to parse Mork text and build structures
+
+ morkStream* mStore_OutStream; // stream using file used by the writer
+
+ morkRowSpaceMap mStore_RowSpaces; // maps mork_scope -> morkSpace
+ morkAtomSpaceMap mStore_AtomSpaces; // maps mork_scope -> morkSpace
+
+ morkZone mStore_Zone;
+
+ morkPool mStore_Pool;
+
+ // we alloc a max size book atom to reuse space for atom map key searches:
+ // morkMaxBookAtom mStore_BookAtom; // staging area for atom map searches
+
+ morkFarBookAtom mStore_FarBookAtom; // staging area for atom map searches
+
+ // GroupIdentity should be one more than largest seen in a parsed db file:
+ mork_gid mStore_CommitGroupIdentity; // transaction ID number
+
+ // group positions are used to help compute PercentOfStoreWasted():
+ mork_pos mStore_FirstCommitGroupPos; // start of first group
+ mork_pos mStore_SecondCommitGroupPos; // start of second group
+ // If the first commit group is very near the start of the file (say less
+ // than 512 bytes), then we might assume the file started nearly empty and
+ // that most of the first group is not wasted. In that case, the pos of
+ // the second commit group might make a better estimate of the start of
+ // transaction space that might represent wasted file space. That's why
+ // we support fields for both first and second commit group positions.
+ //
+ // We assume that a zero in either group pos means that the slot has not
+ // yet been given a valid value, since the file will always start with a
+ // tag, and a commit group cannot actually start at position zero.
+ //
+ // Either or both the first or second commit group positions might be
+ // supplied by either morkWriter (while committing) or morkBuilder (while
+ // parsing), since either reading or writing the file might encounter the
+ // first transaction groups which came into existence either in the past
+ // or in the very recent present.
+
+ mork_bool mStore_CanAutoAssignAtomIdentity;
+ mork_bool mStore_CanDirty; // changes imply the store becomes dirty?
+ mork_u1 mStore_CanWriteIncremental; // compress not required?
+ mork_u1 mStore_Pad; // for u4 alignment
+
+ // mStore_CanDirty should be FALSE when parsing a file while building the
+ // content going into the store, because such data structure modifications
+ // are actuallly in sync with the file. So content read from a file must
+ // be clean with respect to the file. After a file is finished parsing,
+ // the mStore_CanDirty slot should become TRUE, so that any additional
+ // changes at runtime cause structures to be marked dirty with respect to
+ // the file which must later be updated with changes during a commit.
+ //
+ // It might also make sense to set mStore_CanDirty to FALSE while a commit
+ // is in progress, lest some internal transformations make more content
+ // appear dirty when it should not. So anyone modifying content during a
+ // commit should think about the intended significance regarding dirty.
+
+public: // more specific dirty methods for store:
+ void SetStoreDirty() { this->SetNodeDirty(); }
+ void SetStoreClean() { this->SetNodeClean(); }
+
+ mork_bool IsStoreClean() const { return this->IsNodeClean(); }
+ mork_bool IsStoreDirty() const { return this->IsNodeDirty(); }
+
+public: // setting dirty based on CanDirty:
+
+ void MaybeDirtyStore()
+ { if ( mStore_CanDirty ) this->SetStoreDirty(); }
+
+public: // space waste analysis
+
+ mork_percent PercentOfStoreWasted(morkEnv* ev);
+
+public: // setting store and all subspaces canDirty:
+
+ void SetStoreAndAllSpacesCanDirty(morkEnv* ev, mork_bool inCanDirty);
+
+public: // building an atom inside mStore_FarBookAtom from a char* string
+
+ morkFarBookAtom* StageAliasAsFarBookAtom(morkEnv* ev,
+ const morkMid* inMid, morkAtomSpace* ioSpace, mork_cscode inForm);
+
+ morkFarBookAtom* StageYarnAsFarBookAtom(morkEnv* ev,
+ const mdbYarn* inYarn, morkAtomSpace* ioSpace);
+
+ morkFarBookAtom* StageStringAsFarBookAtom(morkEnv* ev,
+ const char* inString, mork_cscode inForm, morkAtomSpace* ioSpace);
+
+public: // determining whether incremental writing is a good use of time:
+
+ mork_bool DoPreferLargeOverCompressCommit(morkEnv* ev);
+ // true when mStore_CanWriteIncremental && store has file large enough
+
+public: // lazy creation of members and nested row or atom spaces
+
+ morkAtomSpace* LazyGetOidAtomSpace(morkEnv* ev);
+ morkAtomSpace* LazyGetGroundAtomSpace(morkEnv* ev);
+ morkAtomSpace* LazyGetGroundColumnSpace(morkEnv* ev);
+
+ morkStream* LazyGetInStream(morkEnv* ev);
+ morkBuilder* LazyGetBuilder(morkEnv* ev);
+ void ForgetBuilder(morkEnv* ev);
+
+ morkStream* LazyGetOutStream(morkEnv* ev);
+
+ morkRowSpace* LazyGetRowSpace(morkEnv* ev, mdb_scope inRowScope);
+ morkAtomSpace* LazyGetAtomSpace(morkEnv* ev, mdb_scope inAtomScope);
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseStore() only if open
+
+public: // morkStore construction & destruction
+ morkStore(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioNodeHeap, // the heap (if any) for this node instance
+ morkFactory* inFactory, // the factory for this
+ nsIMdbHeap* ioPortHeap // the heap to hold all content in the port
+ );
+ void CloseStore(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkStore(const morkStore& other);
+ morkStore& operator=(const morkStore& other);
+ virtual ~morkStore(); // assert that CloseStore() executed earlier
+
+public: // dynamic type identification
+ morkEnv* CanUseStore(nsIMdbEnv* mev, mork_bool inMutable,
+ nsresult* outErr) const;
+ mork_bool IsStore() const
+ { return IsNode() && mNode_Derived == morkDerived_kStore; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ static void NonStoreTypeError(morkEnv* ev);
+ static void NilStoreFileError(morkEnv* ev);
+ static void CannotAutoAssignAtomIdentityError(morkEnv* ev);
+
+public: // store utilities
+
+ morkAtom* YarnToAtom(morkEnv* ev, const mdbYarn* inYarn, bool createIfMissing = true);
+ morkAtom* AddAlias(morkEnv* ev, const morkMid& inMid,
+ mork_cscode inForm);
+
+public: // other store methods
+
+ void RenumberAllCollectableContent(morkEnv* ev);
+
+ nsIMdbStore* AcquireStoreHandle(morkEnv* ev); // mObject_Handle
+
+ morkPool* StorePool() { return &mStore_Pool; }
+
+ mork_bool OpenStoreFile(morkEnv* ev, // return value equals ev->Good()
+ mork_bool inFrozen,
+ // const char* inFilePath,
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy);
+
+ mork_bool CreateStoreFile(morkEnv* ev, // return value equals ev->Good()
+ // const char* inFilePath,
+ nsIMdbFile* ioFile, // db abstract file interface
+ const mdbOpenPolicy* inOpenPolicy);
+
+ morkAtom* CopyAtom(morkEnv* ev, const morkAtom* inAtom);
+ // copy inAtom (from some other store) over to this store
+
+ mork_token CopyToken(morkEnv* ev, mdb_token inToken, morkStore* inStore);
+ // copy inToken from inStore over to this store
+
+ mork_token BufToToken(morkEnv* ev, const morkBuf* inBuf);
+ mork_token StringToToken(morkEnv* ev, const char* inTokenName);
+ mork_token QueryToken(morkEnv* ev, const char* inTokenName);
+ void TokenToString(morkEnv* ev, mdb_token inToken, mdbYarn* outTokenName);
+
+ mork_bool MidToOid(morkEnv* ev, const morkMid& inMid,
+ mdbOid* outOid);
+ mork_bool OidToYarn(morkEnv* ev, const mdbOid& inOid, mdbYarn* outYarn);
+ mork_bool MidToYarn(morkEnv* ev, const morkMid& inMid,
+ mdbYarn* outYarn);
+
+ morkBookAtom* MidToAtom(morkEnv* ev, const morkMid& inMid);
+ morkRow* MidToRow(morkEnv* ev, const morkMid& inMid);
+ morkTable* MidToTable(morkEnv* ev, const morkMid& inMid);
+
+ morkRow* OidToRow(morkEnv* ev, const mdbOid* inOid);
+ // OidToRow() finds old row with oid, or makes new one if not found.
+
+ morkTable* OidToTable(morkEnv* ev, const mdbOid* inOid,
+ const mdbOid* inOptionalMetaRowOid);
+ // OidToTable() finds old table with oid, or makes new one if not found.
+
+ static void SmallTokenToOneByteYarn(morkEnv* ev, mdb_token inToken,
+ mdbYarn* outYarn);
+
+ mork_bool HasTableKind(morkEnv* ev, mdb_scope inRowScope,
+ mdb_kind inTableKind, mdb_count* outTableCount);
+
+ morkTable* GetTableKind(morkEnv* ev, mdb_scope inRowScope,
+ mdb_kind inTableKind, mdb_count* outTableCount,
+ mdb_bool* outMustBeUnique);
+
+ morkRow* FindRow(morkEnv* ev, mdb_scope inScope, mdb_column inColumn,
+ const mdbYarn* inTargetCellValue);
+
+ morkRow* GetRow(morkEnv* ev, const mdbOid* inOid);
+ morkTable* GetTable(morkEnv* ev, const mdbOid* inOid);
+
+ morkTable* NewTable(morkEnv* ev, mdb_scope inRowScope,
+ mdb_kind inTableKind, mdb_bool inMustBeUnique,
+ const mdbOid* inOptionalMetaRowOid);
+
+ morkPortTableCursor* GetPortTableCursor(morkEnv* ev, mdb_scope inRowScope,
+ mdb_kind inTableKind) ;
+
+ morkRow* NewRowWithOid(morkEnv* ev, const mdbOid* inOid);
+ morkRow* NewRow(morkEnv* ev, mdb_scope inRowScope);
+
+ morkThumb* MakeCompressCommitThumb(morkEnv* ev, mork_bool inDoCollect);
+
+public: // commit related methods
+
+ mork_bool MarkAllStoreContentDirty(morkEnv* ev);
+ // MarkAllStoreContentDirty() visits every object in the store and marks
+ // them dirty, including every table, row, cell, and atom. The return
+ // equals ev->Good(), to show whether any error happened. This method is
+ // intended for use in the beginning of a "compress commit" which writes
+ // all store content, whether dirty or not. We dirty everything first so
+ // that later iterations over content can mark things clean as they are
+ // written, and organize the process of serialization so that objects are
+ // written only at need (because of being dirty).
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakStore(morkStore* me,
+ morkEnv* ev, morkStore** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongStore(morkStore* me,
+ morkEnv* ev, morkStore** ioSlot)
+ {
+ morkStore* store = *ioSlot;
+ if ( me != store )
+ {
+ if ( store )
+ {
+ // what if this nulls out the ev and causes asserts?
+ // can we move this after the CutStrongRef()?
+ *ioSlot = 0;
+ store->Release();
+ }
+ if ( me && me->AddRef() )
+ *ioSlot = me;
+ }
+ }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKSTORE_ */
+
diff --git a/components/mork/src/morkStream.cpp b/components/mork/src/morkStream.cpp
new file mode 100644
index 000000000..64cd1011e
--- /dev/null
+++ b/components/mork/src/morkStream.cpp
@@ -0,0 +1,859 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKFILE_
+#include "morkFile.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKSTREAM_
+#include "morkStream.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkStream::CloseMorkNode(morkEnv* ev) // CloseStream() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseStream(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkStream::~morkStream() // assert CloseStream() executed earlier
+{
+ MORK_ASSERT(mStream_ContentFile==0);
+ MORK_ASSERT(mStream_Buf==0);
+}
+
+/*public non-poly*/
+morkStream::morkStream(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap,
+ nsIMdbFile* ioContentFile, mork_size inBufSize, mork_bool inFrozen)
+: morkFile(ev, inUsage, ioHeap, ioHeap)
+, mStream_At( 0 )
+, mStream_ReadEnd( 0 )
+, mStream_WriteEnd( 0 )
+
+, mStream_ContentFile( 0 )
+
+, mStream_Buf( 0 )
+, mStream_BufSize( inBufSize )
+, mStream_BufPos( 0 )
+, mStream_Dirty( morkBool_kFalse )
+, mStream_HitEof( morkBool_kFalse )
+{
+ if ( ev->Good() )
+ {
+ if ( inBufSize < morkStream_kMinBufSize )
+ mStream_BufSize = inBufSize = morkStream_kMinBufSize;
+ else if ( inBufSize > morkStream_kMaxBufSize )
+ mStream_BufSize = inBufSize = morkStream_kMaxBufSize;
+
+ if ( ioContentFile && ioHeap )
+ {
+ // if ( ioContentFile->FileFrozen() ) // forced to be readonly?
+ // inFrozen = morkBool_kTrue; // override the input value
+
+ nsIMdbFile_SlotStrongFile(ioContentFile, ev, &mStream_ContentFile);
+ if ( ev->Good() )
+ {
+ mork_u1* buf = 0;
+ ioHeap->Alloc(ev->AsMdbEnv(), inBufSize, (void**) &buf);
+ if ( buf )
+ {
+ mStream_At = mStream_Buf = buf;
+
+ if ( !inFrozen )
+ {
+ // physical buffer end never moves:
+ mStream_WriteEnd = buf + inBufSize;
+ }
+ else
+ mStream_WriteEnd = 0; // no writing is allowed
+
+ if ( inFrozen )
+ {
+ // logical buffer end starts at Buf with no content:
+ mStream_ReadEnd = buf;
+ this->SetFileFrozen(inFrozen);
+ }
+ else
+ mStream_ReadEnd = 0; // no reading is allowed
+
+ this->SetFileActive(morkBool_kTrue);
+ this->SetFileIoOpen(morkBool_kTrue);
+ }
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kStream;
+ }
+ }
+ else ev->NilPointerError();
+ }
+}
+
+/*public non-poly*/ void
+morkStream::CloseStream(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ nsIMdbFile_SlotStrongFile((nsIMdbFile*) 0, ev, &mStream_ContentFile);
+ nsIMdbHeap* heap = mFile_SlotHeap;
+ mork_u1* buf = mStream_Buf;
+ mStream_Buf = 0;
+
+ if ( heap && buf )
+ heap->Free(ev->AsMdbEnv(), buf);
+
+ this->CloseFile(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+#define morkStream_kSpacesPerIndent 1 /* one space per indent */
+#define morkStream_kMaxIndentDepth 70 /* max indent of 70 space bytes */
+static const char morkStream_kSpaces[] // next line to ease length perception
+= " ";
+// 123456789_123456789_123456789_123456789_123456789_123456789_123456789_
+// morkStream_kSpaces above must contain (at least) 70 spaces (ASCII 0x20)
+
+mork_size
+morkStream::PutIndent(morkEnv* ev, mork_count inDepth)
+ // PutIndent() puts a linebreak, and then
+ // "indents" by inDepth, and returns the line length after indentation.
+{
+ mork_size outLength = 0;
+ nsIMdbEnv *mev = ev->AsMdbEnv();
+ if ( ev->Good() )
+ {
+ this->PutLineBreak(ev);
+ if ( ev->Good() )
+ {
+ outLength = inDepth;
+ mdb_size bytesWritten;
+ if ( inDepth )
+ this->Write(mev, morkStream_kSpaces, inDepth, &bytesWritten);
+ }
+ }
+ return outLength;
+}
+
+mork_size
+morkStream::PutByteThenIndent(morkEnv* ev, int inByte, mork_count inDepth)
+ // PutByteThenIndent() puts the byte, then a linebreak, and then
+ // "indents" by inDepth, and returns the line length after indentation.
+{
+ mork_size outLength = 0;
+ nsIMdbEnv *mev = ev->AsMdbEnv();
+
+ if ( inDepth > morkStream_kMaxIndentDepth )
+ inDepth = morkStream_kMaxIndentDepth;
+
+ this->Putc(ev, inByte);
+ if ( ev->Good() )
+ {
+ this->PutLineBreak(ev);
+ if ( ev->Good() )
+ {
+ outLength = inDepth;
+ mdb_size bytesWritten;
+ if ( inDepth )
+ this->Write(mev, morkStream_kSpaces, inDepth, &bytesWritten);
+ }
+ }
+ return outLength;
+}
+
+mork_size
+morkStream::PutStringThenIndent(morkEnv* ev,
+ const char* inString, mork_count inDepth)
+// PutStringThenIndent() puts the string, then a linebreak, and then
+// "indents" by inDepth, and returns the line length after indentation.
+{
+ mork_size outLength = 0;
+ mdb_size bytesWritten;
+ nsIMdbEnv *mev = ev->AsMdbEnv();
+
+ if ( inDepth > morkStream_kMaxIndentDepth )
+ inDepth = morkStream_kMaxIndentDepth;
+
+ if ( inString )
+ {
+ mork_size length = MORK_STRLEN(inString);
+ if ( length && ev->Good() ) // any bytes to write?
+ this->Write(mev, inString, length, &bytesWritten);
+ }
+
+ if ( ev->Good() )
+ {
+ this->PutLineBreak(ev);
+ if ( ev->Good() )
+ {
+ outLength = inDepth;
+ if ( inDepth )
+ this->Write(mev, morkStream_kSpaces, inDepth, &bytesWritten);
+ }
+ }
+ return outLength;
+}
+
+mork_size
+morkStream::PutString(morkEnv* ev, const char* inString)
+{
+ nsIMdbEnv *mev = ev->AsMdbEnv();
+ mork_size outSize = 0;
+ mdb_size bytesWritten;
+ if ( inString )
+ {
+ outSize = MORK_STRLEN(inString);
+ if ( outSize && ev->Good() ) // any bytes to write?
+ {
+ this->Write(mev, inString, outSize, &bytesWritten);
+ }
+ }
+ return outSize;
+}
+
+mork_size
+morkStream::PutStringThenNewline(morkEnv* ev, const char* inString)
+ // PutStringThenNewline() returns total number of bytes written.
+{
+ nsIMdbEnv *mev = ev->AsMdbEnv();
+ mork_size outSize = 0;
+ mdb_size bytesWritten;
+ if ( inString )
+ {
+ outSize = MORK_STRLEN(inString);
+ if ( outSize && ev->Good() ) // any bytes to write?
+ {
+ this->Write(mev, inString, outSize, &bytesWritten);
+ if ( ev->Good() )
+ outSize += this->PutLineBreak(ev);
+ }
+ }
+ return outSize;
+}
+
+mork_size
+morkStream::PutByteThenNewline(morkEnv* ev, int inByte)
+ // PutByteThenNewline() returns total number of bytes written.
+{
+ mork_size outSize = 1; // one for the following byte
+ this->Putc(ev, inByte);
+ if ( ev->Good() )
+ outSize += this->PutLineBreak(ev);
+ return outSize;
+}
+
+mork_size
+morkStream::PutLineBreak(morkEnv* ev)
+{
+#if defined(MORK_MAC)
+
+ this->Putc(ev, mork_kCR);
+ return 1;
+
+#else
+# if defined(MORK_WIN)
+
+ this->Putc(ev, mork_kCR);
+ this->Putc(ev, mork_kLF);
+ return 2;
+
+# else
+# ifdef MORK_UNIX
+
+ this->Putc(ev, mork_kLF);
+ return 1;
+
+# endif /* MORK_UNIX */
+# endif /* MORK_WIN */
+#endif /* MORK_MAC */
+}
+// ````` ````` ````` ````` ````` ````` ````` `````
+// public: // virtual morkFile methods
+
+
+NS_IMETHODIMP
+morkStream::Steal(nsIMdbEnv* mev, nsIMdbFile* ioThief)
+ // Steal: tell this file to close any associated i/o stream in the file
+ // system, because the file ioThief intends to reopen the file in order
+ // to provide the MDB implementation with more exotic file access than is
+ // offered by the nsIMdbFile alone. Presumably the thief knows enough
+ // from Path() in order to know which file to reopen. If Steal() is
+ // successful, this file should probably delegate all future calls to
+ // the nsIMdbFile interface down to the thief files, so that even after
+ // the file has been stolen, it can still be read, written, or forcibly
+ // closed (by a call to CloseMdbObject()).
+{
+ MORK_USED_1(ioThief);
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ ev->StubMethodOnlyError();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkStream::BecomeTrunk(nsIMdbEnv* mev)
+ // If this file is a file version branch created by calling AcquireBud(),
+ // BecomeTrunk() causes this file's content to replace the original
+ // file's content, typically by assuming the original file's identity.
+{
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ ev->StubMethodOnlyError();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkStream::AcquireBud(nsIMdbEnv* mev, nsIMdbHeap* ioHeap, nsIMdbFile **acqBud)
+ // AcquireBud() starts a new "branch" version of the file, empty of content,
+ // so that a new version of the file can be written. This new file
+ // can later be told to BecomeTrunk() the original file, so the branch
+ // created by budding the file will replace the original file. Some
+ // file subclasses might initially take the unsafe but expedient
+ // approach of simply truncating this file down to zero length, and
+ // then returning the same morkFile pointer as this, with an extra
+ // reference count increment. Note that the caller of AcquireBud() is
+ // expected to eventually call CutStrongRef() on the returned file
+ // in order to release the strong reference. High quality versions
+ // of morkFile subclasses will create entirely new files which later
+ // are renamed to become the old file, so that better transactional
+ // behavior is exhibited by the file, so crashes protect old files.
+ // Note that AcquireBud() is an illegal operation on readonly files.
+{
+ MORK_USED_1(ioHeap);
+ morkFile* outFile = 0;
+ nsIMdbFile* file = mStream_ContentFile;
+ morkEnv *ev = morkEnv::FromMdbEnv(mev);
+ if ( this->IsOpenAndActiveFile() && file )
+ {
+ // figure out how this interacts with buffering and mStream_WriteEnd:
+ ev->StubMethodOnlyError();
+ }
+ else this->NewFileDownError(ev);
+
+ *acqBud = outFile;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+mork_pos
+morkStream::Length(morkEnv* ev) const // eof
+{
+ mork_pos outPos = 0;
+
+ nsIMdbFile* file = mStream_ContentFile;
+ if ( this->IsOpenAndActiveFile() && file )
+ {
+ mork_pos contentEof = 0;
+ file->Eof(ev->AsMdbEnv(), &contentEof);
+ if ( ev->Good() )
+ {
+ if ( mStream_WriteEnd ) // this stream supports writing?
+ {
+ // the local buffer might have buffered content past content eof
+ if ( ev->Good() ) // no error happened during Length() above?
+ {
+ mork_u1* at = mStream_At;
+ mork_u1* buf = mStream_Buf;
+ if ( at >= buf ) // expected cursor order?
+ {
+ mork_pos localContent = mStream_BufPos + (at - buf);
+ if ( localContent > contentEof ) // buffered past eof?
+ contentEof = localContent; // return new logical eof
+
+ outPos = contentEof;
+ }
+ else this->NewBadCursorOrderError(ev);
+ }
+ }
+ else
+ outPos = contentEof; // frozen files get length from content file
+ }
+ }
+ else this->NewFileDownError(ev);
+
+ return outPos;
+}
+
+void morkStream::NewBadCursorSlotsError(morkEnv* ev) const
+{ ev->NewError("bad stream cursor slots"); }
+
+void morkStream::NewNullStreamBufferError(morkEnv* ev) const
+{ ev->NewError("null stream buffer"); }
+
+void morkStream::NewCantReadSinkError(morkEnv* ev) const
+{ ev->NewError("can not read stream sink"); }
+
+void morkStream::NewCantWriteSourceError(morkEnv* ev) const
+{ ev->NewError("can not write stream source"); }
+
+void morkStream::NewPosBeyondEofError(morkEnv* ev) const
+{ ev->NewError("stream pos beyond eof"); }
+
+void morkStream::NewBadCursorOrderError(morkEnv* ev) const
+{ ev->NewError("bad stream cursor order"); }
+
+NS_IMETHODIMP
+morkStream::Tell(nsIMdbEnv* mdbev, mork_pos *aOutPos) const
+{
+ nsresult rv = NS_OK;
+ morkEnv *ev = morkEnv::FromMdbEnv(mdbev);
+
+ NS_ENSURE_ARG_POINTER(aOutPos);
+
+ nsIMdbFile* file = mStream_ContentFile;
+ if ( this->IsOpenAndActiveFile() && file )
+ {
+ mork_u1* buf = mStream_Buf;
+ mork_u1* at = mStream_At;
+
+ mork_u1* readEnd = mStream_ReadEnd; // nonzero only if readonly
+ mork_u1* writeEnd = mStream_WriteEnd; // nonzero only if writeonly
+
+ if ( writeEnd )
+ {
+ if ( buf && at >= buf && at <= writeEnd )
+ {
+ *aOutPos = mStream_BufPos + (at - buf);
+ }
+ else this->NewBadCursorOrderError(ev);
+ }
+ else if ( readEnd )
+ {
+ if ( buf && at >= buf && at <= readEnd )
+ {
+ *aOutPos = mStream_BufPos + (at - buf);
+ }
+ else this->NewBadCursorOrderError(ev);
+ }
+ }
+ else this->NewFileDownError(ev);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+morkStream::Read(nsIMdbEnv* mdbev, void* outBuf, mork_size inSize, mork_size *aOutSize)
+{
+ NS_ENSURE_ARG_POINTER(aOutSize);
+ // First we satisfy the request from buffered bytes, if any. Then
+ // if additional bytes are needed, we satisfy these by direct reads
+ // from the content file without any local buffering (but we still need
+ // to adjust the buffer position to reflect the current i/o point).
+
+ morkEnv *ev = morkEnv::FromMdbEnv(mdbev);
+ nsresult rv = NS_OK;
+
+ nsIMdbFile* file = mStream_ContentFile;
+ if ( this->IsOpenAndActiveFile() && file )
+ {
+ mork_u1* end = mStream_ReadEnd; // byte after last buffered byte
+ if ( end ) // file is open for read access?
+ {
+ if ( inSize ) // caller wants any output?
+ {
+ mork_u1* sink = (mork_u1*) outBuf; // where we plan to write bytes
+ if ( sink ) // caller passed good buffer address?
+ {
+ mork_u1* at = mStream_At;
+ mork_u1* buf = mStream_Buf;
+ if ( at >= buf && at <= end ) // expected cursor order?
+ {
+ mork_num remaining = (mork_num) (end - at); // bytes left in buffer
+
+ mork_num quantum = inSize; // number of bytes to copy
+ if ( quantum > remaining ) // more than buffer content?
+ quantum = remaining; // restrict to buffered bytes
+
+ if ( quantum ) // any bytes left in the buffer?
+ {
+ MORK_MEMCPY(sink, at, quantum); // from buffer bytes
+
+ at += quantum; // advance past read bytes
+ mStream_At = at;
+ *aOutSize += quantum; // this much copied so far
+
+ sink += quantum; // in case we need to copy more
+ inSize -= quantum; // filled this much of request
+ mStream_HitEof = morkBool_kFalse;
+ }
+
+ if ( inSize ) // we still need to read more content?
+ {
+ // We need to read more bytes directly from the
+ // content file, without local buffering. We have
+ // exhausted the local buffer, so we need to show
+ // it is now empty, and adjust the current buf pos.
+
+ mork_num posDelta = (mork_num) (at - buf); // old buf content
+ mStream_BufPos += posDelta; // past now empty buf
+
+ mStream_At = mStream_ReadEnd = buf; // empty buffer
+
+ // file->Seek(ev, mStream_BufPos); // set file pos
+ // if ( ev->Good() ) // no seek error?
+ // {
+ // }
+
+ mork_num actual = 0;
+ nsIMdbEnv* menv = ev->AsMdbEnv();
+ file->Get(menv, sink, inSize, mStream_BufPos, &actual);
+ if ( ev->Good() ) // no read error?
+ {
+ if ( actual )
+ {
+ *aOutSize += actual;
+ mStream_BufPos += actual;
+ mStream_HitEof = morkBool_kFalse;
+ }
+ else if ( !*aOutSize )
+ mStream_HitEof = morkBool_kTrue;
+ }
+ }
+ }
+ else this->NewBadCursorOrderError(ev);
+ }
+ else this->NewNullStreamBufferError(ev);
+ }
+ }
+ else this->NewCantReadSinkError(ev);
+ }
+ else this->NewFileDownError(ev);
+
+ if ( ev->Bad() )
+ *aOutSize = 0;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+morkStream::Seek(nsIMdbEnv * mdbev, mork_pos inPos, mork_pos *aOutPos)
+{
+ NS_ENSURE_ARG_POINTER(aOutPos);
+ morkEnv *ev = morkEnv::FromMdbEnv(mdbev);
+ *aOutPos = 0;
+ nsresult rv = NS_OK;
+ nsIMdbFile* file = mStream_ContentFile;
+ if ( this->IsOpenOrClosingNode() && this->FileActive() && file )
+ {
+ mork_u1* at = mStream_At; // current position in buffer
+ mork_u1* buf = mStream_Buf; // beginning of buffer
+ mork_u1* readEnd = mStream_ReadEnd; // nonzero only if readonly
+ mork_u1* writeEnd = mStream_WriteEnd; // nonzero only if writeonly
+
+ if ( writeEnd ) // file is mutable/writeonly?
+ {
+ if ( mStream_Dirty ) // need to commit buffer changes?
+ this->Flush(mdbev);
+
+ if ( ev->Good() ) // no errors during flush or earlier?
+ {
+ if ( at == buf ) // expected post flush cursor value?
+ {
+ if ( mStream_BufPos != inPos ) // need to change pos?
+ {
+ mork_pos eof = 0;
+ nsIMdbEnv* menv = ev->AsMdbEnv();
+ file->Eof(menv, &eof);
+ if ( ev->Good() ) // no errors getting length?
+ {
+ if ( inPos <= eof ) // acceptable new position?
+ {
+ mStream_BufPos = inPos; // new stream position
+ *aOutPos = inPos;
+ }
+ else this->NewPosBeyondEofError(ev);
+ }
+ }
+ }
+ else this->NewBadCursorOrderError(ev);
+ }
+ }
+ else if ( readEnd ) // file is frozen/readonly?
+ {
+ if ( at >= buf && at <= readEnd ) // expected cursor order?
+ {
+ mork_pos eof = 0;
+ nsIMdbEnv* menv = ev->AsMdbEnv();
+ file->Eof(menv, &eof);
+ if ( ev->Good() ) // no errors getting length?
+ {
+ if ( inPos <= eof ) // acceptable new position?
+ {
+ *aOutPos = inPos;
+ mStream_BufPos = inPos; // new stream position
+ mStream_At = mStream_ReadEnd = buf; // empty buffer
+ if ( inPos == eof ) // notice eof reached?
+ mStream_HitEof = morkBool_kTrue;
+ }
+ else this->NewPosBeyondEofError(ev);
+ }
+ }
+ else this->NewBadCursorOrderError(ev);
+ }
+
+ }
+ else this->NewFileDownError(ev);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+morkStream::Write(nsIMdbEnv* menv, const void* inBuf, mork_size inSize, mork_size *aOutSize)
+{
+ mork_num outActual = 0;
+ morkEnv *ev = morkEnv::FromMdbEnv(menv);
+
+ nsIMdbFile* file = mStream_ContentFile;
+ if ( this->IsOpenActiveAndMutableFile() && file )
+ {
+ mork_u1* end = mStream_WriteEnd; // byte after last buffered byte
+ if ( end ) // file is open for write access?
+ {
+ if ( inSize ) // caller provided any input?
+ {
+ const mork_u1* source = (const mork_u1*) inBuf; // from where
+ if ( source ) // caller passed good buffer address?
+ {
+ mork_u1* at = mStream_At;
+ mork_u1* buf = mStream_Buf;
+ if ( at >= buf && at <= end ) // expected cursor order?
+ {
+ mork_num space = (mork_num) (end - at); // space left in buffer
+
+ mork_num quantum = inSize; // number of bytes to write
+ if ( quantum > space ) // more than buffer size?
+ quantum = space; // restrict to avail space
+
+ if ( quantum ) // any space left in the buffer?
+ {
+ mStream_Dirty = morkBool_kTrue; // to ensure later flush
+ MORK_MEMCPY(at, source, quantum); // into buffer
+
+ mStream_At += quantum; // advance past written bytes
+ outActual += quantum; // this much written so far
+
+ source += quantum; // in case we need to write more
+ inSize -= quantum; // filled this much of request
+ }
+
+ if ( inSize ) // we still need to write more content?
+ {
+ // We need to write more bytes directly to the
+ // content file, without local buffering. We have
+ // exhausted the local buffer, so we need to flush
+ // it and empty it, and adjust the current buf pos.
+ // After flushing, if the rest of the write fits
+ // inside the buffer, we will put bytes into the
+ // buffer rather than write them to content file.
+
+ if ( mStream_Dirty )
+ this->Flush(menv); // will update mStream_BufPos
+
+ at = mStream_At;
+ if ( at < buf || at > end ) // bad cursor?
+ this->NewBadCursorOrderError(ev);
+
+ if ( ev->Good() ) // no errors?
+ {
+ space = (mork_num) (end - at); // space left in buffer
+ if ( space > inSize ) // write to buffer?
+ {
+ mStream_Dirty = morkBool_kTrue; // ensure flush
+ MORK_MEMCPY(at, source, inSize); // copy
+
+ mStream_At += inSize; // past written bytes
+ outActual += inSize; // this much written
+ }
+ else // directly to content file instead
+ {
+ // file->Seek(ev, mStream_BufPos); // set pos
+ // if ( ev->Good() ) // no seek error?
+ // {
+ // }
+
+ mork_num actual = 0;
+ file->Put(menv, source, inSize, mStream_BufPos, &actual);
+ if ( ev->Good() ) // no write error?
+ {
+ outActual += actual;
+ mStream_BufPos += actual;
+ }
+ }
+ }
+ }
+ }
+ else this->NewBadCursorOrderError(ev);
+ }
+ else this->NewNullStreamBufferError(ev);
+ }
+ }
+ else this->NewCantWriteSourceError(ev);
+ }
+ else this->NewFileDownError(ev);
+
+ if ( ev->Bad() )
+ outActual = 0;
+
+ *aOutSize = outActual;
+ return ev->AsErr();
+}
+
+NS_IMETHODIMP
+morkStream::Flush(nsIMdbEnv* ev)
+{
+ morkEnv *mev = morkEnv::FromMdbEnv(ev);
+ nsresult rv = NS_ERROR_FAILURE;
+ nsIMdbFile* file = mStream_ContentFile;
+ if ( this->IsOpenOrClosingNode() && this->FileActive() && file )
+ {
+ if ( mStream_Dirty )
+ this->spill_buf(mev);
+
+ rv = file->Flush(ev);
+ }
+ else this->NewFileDownError(mev);
+ return rv;
+}
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+// protected: // protected non-poly morkStream methods (for char io)
+
+int
+morkStream::fill_getc(morkEnv* ev)
+{
+ int c = EOF;
+
+ nsIMdbFile* file = mStream_ContentFile;
+ if ( this->IsOpenAndActiveFile() && file )
+ {
+ mork_u1* buf = mStream_Buf;
+ mork_u1* end = mStream_ReadEnd; // beyond buf after earlier read
+ if ( end > buf ) // any earlier read bytes buffered?
+ {
+ mStream_BufPos += ( end - buf ); // advance past old read
+ }
+
+ if ( ev->Good() ) // no errors yet?
+ {
+ // file->Seek(ev, mStream_BufPos); // set file pos
+ // if ( ev->Good() ) // no seek error?
+ // {
+ // }
+
+ nsIMdbEnv* menv = ev->AsMdbEnv();
+ mork_num actual = 0;
+ file->Get(menv, buf, mStream_BufSize, mStream_BufPos, &actual);
+ if ( ev->Good() ) // no read errors?
+ {
+ if ( actual > mStream_BufSize ) // more than asked for??
+ actual = mStream_BufSize;
+
+ mStream_At = buf;
+ mStream_ReadEnd = buf + actual;
+ if ( actual ) // any bytes actually read?
+ {
+ c = *mStream_At++; // return first byte from buffer
+ mStream_HitEof = morkBool_kFalse;
+ }
+ else
+ mStream_HitEof = morkBool_kTrue;
+ }
+ }
+ }
+ else this->NewFileDownError(ev);
+
+ return c;
+}
+
+void
+morkStream::spill_putc(morkEnv* ev, int c)
+{
+ this->spill_buf(ev);
+ if ( ev->Good() && mStream_At < mStream_WriteEnd )
+ this->Putc(ev, c);
+}
+
+void
+morkStream::spill_buf(morkEnv* ev) // spill/flush from buffer to file
+{
+ nsIMdbFile* file = mStream_ContentFile;
+ if ( this->IsOpenOrClosingNode() && this->FileActive() && file )
+ {
+ mork_u1* buf = mStream_Buf;
+ if ( mStream_Dirty )
+ {
+ mork_u1* at = mStream_At;
+ if ( at >= buf && at <= mStream_WriteEnd ) // order?
+ {
+ mork_num count = (mork_num) (at - buf); // bytes buffered
+ if ( count ) // anything to write to the string?
+ {
+ if ( count > mStream_BufSize ) // no more than max?
+ {
+ count = mStream_BufSize;
+ mStream_WriteEnd = buf + mStream_BufSize;
+ this->NewBadCursorSlotsError(ev);
+ }
+ if ( ev->Good() )
+ {
+ // file->Seek(ev, mStream_BufPos);
+ // if ( ev->Good() )
+ // {
+ // }
+ nsIMdbEnv* menv = ev->AsMdbEnv();
+ mork_num actual = 0;
+
+ file->Put(menv, buf, count, mStream_BufPos, &actual);
+ if ( ev->Good() )
+ {
+ mStream_BufPos += actual; // past bytes written
+ mStream_At = buf; // reset buffer cursor
+ mStream_Dirty = morkBool_kFalse;
+ }
+ }
+ }
+ }
+ else this->NewBadCursorOrderError(ev);
+ }
+ else
+ {
+#ifdef MORK_DEBUG
+ ev->NewWarning("stream:spill:not:dirty");
+#endif /*MORK_DEBUG*/
+ }
+ }
+ else this->NewFileDownError(ev);
+
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkStream.h b/components/mork/src/morkStream.h
new file mode 100644
index 000000000..418b68070
--- /dev/null
+++ b/components/mork/src/morkStream.h
@@ -0,0 +1,251 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef _MORKSTREAM_
+#define _MORKSTREAM_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKFILE_
+#include "morkFile.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*=============================================================================
+ * morkStream: buffered file i/o
+ */
+
+/*| morkStream exists to define an morkFile subclass that provides buffered
+**| i/o for an underlying content file. Naturally this arrangement only makes
+**| sense when the underlying content file is itself not efficiently buffered
+**| (especially for character by character i/o).
+**|
+**|| morkStream is intended for either reading use or writing use, but not
+**| both simultaneously or interleaved. Pick one when the stream is created
+**| and don't change your mind. This restriction is intended to avoid obscure
+**| and complex bugs that might arise from interleaved reads and writes -- so
+**| just don't do it. A stream is either a sink or a source, but not both.
+**|
+**|| (When the underlying content file is intended to support both reading and
+**| writing, a developer might use two instances of morkStream where one is for
+**| reading and the other is for writing. In this case, a developer must take
+**| care to keep the two streams in sync because each will maintain a separate
+**| buffer representing a cache consistency problem for the other. A simple
+**| approach is to invalidate the buffer of one when one uses the other, with
+**| the assumption that closely mixed reading and writing is not expected, so
+**| that little cost is associated with changing read/write streaming modes.)
+**|
+**|| Exactly one of mStream_ReadEnd or mStream_WriteEnd must be a null pointer,
+**| and this will cause the right thing to occur when inlines use them, because
+**| mStream_At < mStream_WriteEnd (for example) will always be false and the
+**| else branch of the statement calls a function that raises an appropriate
+**| error to complain about either reading a sink or writing a source.
+**|
+**|| morkStream is a direct clone of ab_Stream from Communicator 4.5's
+**| address book code, which in turn was based on the stream class in the
+**| public domain Mithril programming language.
+|*/
+
+#define morkStream_kPrintBufSize /*i*/ 512 /* buffer size used by printf() */
+
+#define morkStream_kMinBufSize /*i*/ 512 /* buffer no fewer bytes */
+#define morkStream_kMaxBufSize /*i*/ (32 * 1024) /* buffer no more bytes */
+
+#define morkDerived_kStream /*i*/ 0x7A74 /* ascii 'zt' */
+
+class morkStream /*d*/ : public morkFile { /* from Mithril's AgStream class */
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+protected: // protected morkStream members
+ mork_u1* mStream_At; // pointer into mStream_Buf
+ mork_u1* mStream_ReadEnd; // null or one byte past last readable byte
+ mork_u1* mStream_WriteEnd; // null or mStream_Buf + mStream_BufSize
+
+ nsIMdbFile* mStream_ContentFile; // where content is read and written
+
+ mork_u1* mStream_Buf; // dynamically allocated memory to buffer io
+ mork_size mStream_BufSize; // requested buf size (fixed by min and max)
+ mork_pos mStream_BufPos; // logical position of byte at mStream_Buf
+ mork_bool mStream_Dirty; // does the buffer need to be flushed?
+ mork_bool mStream_HitEof; // has eof been reached? (only frozen streams)
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseStream() only if open
+ virtual ~morkStream(); // assert that CloseStream() executed earlier
+
+public: // morkStream construction & destruction
+ morkStream(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ nsIMdbFile* ioContentFile, mork_size inBufSize, mork_bool inFrozen);
+ void CloseStream(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkStream(const morkStream& other);
+ morkStream& operator=(const morkStream& other);
+
+public: // dynamic type identification
+ mork_bool IsStream() const
+ { return IsNode() && mNode_Derived == morkDerived_kStream; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ void NonStreamTypeError(morkEnv* ev);
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // virtual morkFile methods
+
+ NS_IMETHOD Steal(nsIMdbEnv* ev, nsIMdbFile* ioThief) override;
+ // Steal: tell this file to close any associated i/o stream in the file
+ // system, because the file ioThief intends to reopen the file in order
+ // to provide the MDB implementation with more exotic file access than is
+ // offered by the nsIMdbFile alone. Presumably the thief knows enough
+ // from Path() in order to know which file to reopen. If Steal() is
+ // successful, this file should probably delegate all future calls to
+ // the nsIMdbFile interface down to the thief files, so that even after
+ // the file has been stolen, it can still be read, written, or forcibly
+ // closed (by a call to CloseMdbObject()).
+
+ NS_IMETHOD BecomeTrunk(nsIMdbEnv* ev) override;
+ // If this file is a file version branch created by calling AcquireBud(),
+ // BecomeTrunk() causes this file's content to replace the original
+ // file's content, typically by assuming the original file's identity.
+
+ NS_IMETHOD AcquireBud(nsIMdbEnv* ev, nsIMdbHeap* ioHeap, nsIMdbFile** acqBud) override;
+ // AcquireBud() starts a new "branch" version of the file, empty of content,
+ // so that a new version of the file can be written. This new file
+ // can later be told to BecomeTrunk() the original file, so the branch
+ // created by budding the file will replace the original file. Some
+ // file subclasses might initially take the unsafe but expedient
+ // approach of simply truncating this file down to zero length, and
+ // then returning the same morkFile pointer as this, with an extra
+ // reference count increment. Note that the caller of AcquireBud() is
+ // expected to eventually call CutStrongRef() on the returned file
+ // in order to release the strong reference. High quality versions
+ // of morkFile subclasses will create entirely new files which later
+ // are renamed to become the old file, so that better transactional
+ // behavior is exhibited by the file, so crashes protect old files.
+ // Note that AcquireBud() is an illegal operation on readonly files.
+
+ virtual mork_pos Length(morkEnv* ev) const override; // eof
+ NS_IMETHOD Tell(nsIMdbEnv* ev, mork_pos *aOutPos ) const override;
+ NS_IMETHOD Read(nsIMdbEnv* ev, void* outBuf, mork_size inSize, mork_size *aOutCount) override;
+ NS_IMETHOD Seek(nsIMdbEnv* ev, mork_pos inPos, mork_pos *aOutPos) override;
+ NS_IMETHOD Write(nsIMdbEnv* ev, const void* inBuf, mork_size inSize, mork_size *aOutCount) override;
+ NS_IMETHOD Flush(nsIMdbEnv* ev) override;
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+protected: // protected non-poly morkStream methods (for char io)
+
+ int fill_getc(morkEnv* ev);
+ void spill_putc(morkEnv* ev, int c);
+ void spill_buf(morkEnv* ev); // spill/flush from buffer to file
+
+// ````` ````` ````` ````` ````` ````` ````` `````
+public: // public non-poly morkStream methods
+
+ void NewBadCursorSlotsError(morkEnv* ev) const;
+ void NewBadCursorOrderError(morkEnv* ev) const;
+ void NewNullStreamBufferError(morkEnv* ev) const;
+ void NewCantReadSinkError(morkEnv* ev) const;
+ void NewCantWriteSourceError(morkEnv* ev) const;
+ void NewPosBeyondEofError(morkEnv* ev) const;
+
+ nsIMdbFile* GetStreamContentFile() const { return mStream_ContentFile; }
+ mork_size GetStreamBufferSize() const { return mStream_BufSize; }
+
+ mork_size PutIndent(morkEnv* ev, mork_count inDepth);
+ // PutIndent() puts a linebreak, and then
+ // "indents" by inDepth, and returns the line length after indentation.
+
+ mork_size PutByteThenIndent(morkEnv* ev, int inByte, mork_count inDepth);
+ // PutByteThenIndent() puts the byte, then a linebreak, and then
+ // "indents" by inDepth, and returns the line length after indentation.
+
+ mork_size PutStringThenIndent(morkEnv* ev,
+ const char* inString, mork_count inDepth);
+ // PutStringThenIndent() puts the string, then a linebreak, and then
+ // "indents" by inDepth, and returns the line length after indentation.
+
+ mork_size PutString(morkEnv* ev, const char* inString);
+ // PutString() returns the length of the string written.
+
+ mork_size PutStringThenNewline(morkEnv* ev, const char* inString);
+ // PutStringThenNewline() returns total number of bytes written.
+
+ mork_size PutByteThenNewline(morkEnv* ev, int inByte);
+ // PutByteThenNewline() returns total number of bytes written.
+
+ // ````` ````` stdio type methods ````` `````
+ void Ungetc(int c) /*i*/
+ { if ( mStream_At > mStream_Buf && c > 0 ) *--mStream_At = (mork_u1) c; }
+
+ // Note Getc() returns EOF consistently after any fill_getc() error occurs.
+ int Getc(morkEnv* ev) /*i*/
+ { return ( mStream_At < mStream_ReadEnd )? *mStream_At++ : fill_getc(ev); }
+
+ void Putc(morkEnv* ev, int c) /*i*/
+ {
+ mStream_Dirty = morkBool_kTrue;
+ if ( mStream_At < mStream_WriteEnd )
+ *mStream_At++ = (mork_u1) c;
+ else
+ spill_putc(ev, c);
+ }
+
+ mork_size PutLineBreak(morkEnv* ev);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakStream(morkStream* me,
+ morkEnv* ev, morkStream** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongStream(morkStream* me,
+ morkEnv* ev, morkStream** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKSTREAM_ */
diff --git a/components/mork/src/morkTable.cpp b/components/mork/src/morkTable.cpp
new file mode 100644
index 000000000..ba92f222c
--- /dev/null
+++ b/components/mork/src/morkTable.cpp
@@ -0,0 +1,1610 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKTABLE_
+#include "morkTable.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKROWSPACE_
+#include "morkRowSpace.h"
+#endif
+
+#ifndef _MORKARRAY_
+#include "morkArray.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+#ifndef _MORKTABLEROWCURSOR_
+#include "morkTableRowCursor.h"
+#endif
+
+#ifndef _MORKROWOBJECT_
+#include "morkRowObject.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkTable::CloseMorkNode(morkEnv* ev) /*i*/ // CloseTable() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ morkObject::CloseMorkNode(ev); // give base class a chance.
+ this->MarkClosing();
+ this->CloseTable(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkTable::~morkTable() /*i*/ // assert CloseTable() executed earlier
+{
+ CloseMorkNode(mMorkEnv);
+ MORK_ASSERT(this->IsShutNode());
+ MORK_ASSERT(mTable_Store==0);
+ MORK_ASSERT(mTable_RowSpace==0);
+}
+
+/*public non-poly*/
+morkTable::morkTable(morkEnv* ev, /*i*/
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ morkStore* ioStore, nsIMdbHeap* ioSlotHeap, morkRowSpace* ioRowSpace,
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ mork_tid inTid, mork_kind inKind, mork_bool inMustBeUnique)
+: morkObject(ev, inUsage, ioHeap, (mork_color) inTid, (morkHandle*) 0)
+, mTable_Store( 0 )
+, mTable_RowSpace( 0 )
+, mTable_MetaRow( 0 )
+
+, mTable_RowMap( 0 )
+// , mTable_RowMap(ev, morkUsage::kMember, (nsIMdbHeap*) 0, ioSlotHeap,
+// morkTable_kStartRowMapSlotCount)
+, mTable_RowArray(ev, morkUsage::kMember, (nsIMdbHeap*) 0,
+ morkTable_kStartRowArraySize, ioSlotHeap)
+
+, mTable_ChangeList()
+, mTable_ChangesCount( 0 )
+, mTable_ChangesMax( 3 ) // any very small number greater than zero
+
+, mTable_Kind( inKind )
+
+, mTable_Flags( 0 )
+, mTable_Priority( morkPriority_kLo ) // NOT high priority
+, mTable_GcUses( 0 )
+, mTable_Pad( 0 )
+{
+ this->mLink_Next = 0;
+ this->mLink_Prev = 0;
+
+ if ( ev->Good() )
+ {
+ if ( ioStore && ioSlotHeap && ioRowSpace )
+ {
+ if ( inKind )
+ {
+ if ( inMustBeUnique )
+ this->SetTableUnique();
+ mTable_Store = ioStore;
+ mTable_RowSpace = ioRowSpace;
+ if ( inOptionalMetaRowOid )
+ mTable_MetaRowOid = *inOptionalMetaRowOid;
+ else
+ {
+ mTable_MetaRowOid.mOid_Scope = 0;
+ mTable_MetaRowOid.mOid_Id = morkRow_kMinusOneRid;
+ }
+ if ( ev->Good() )
+ {
+ if ( this->MaybeDirtySpaceStoreAndTable() )
+ this->SetTableRewrite(); // everything is dirty
+
+ mNode_Derived = morkDerived_kTable;
+ }
+ this->MaybeDirtySpaceStoreAndTable(); // new table might dirty store
+ }
+ else
+ ioRowSpace->ZeroKindError(ev);
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkTable, morkObject, nsIMdbTable)
+
+/*public non-poly*/ void
+morkTable::CloseTable(morkEnv* ev) /*i*/ // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ morkRowMap::SlotStrongRowMap((morkRowMap*) 0, ev, &mTable_RowMap);
+ // mTable_RowMap.CloseMorkNode(ev);
+ mTable_RowArray.CloseMorkNode(ev);
+ mTable_Store = 0;
+ mTable_RowSpace = 0;
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+// { ===== begin nsIMdbCollection methods =====
+
+// { ----- begin attribute methods -----
+NS_IMETHODIMP
+morkTable::GetSeed(nsIMdbEnv* mev,
+ mdb_seed* outSeed) // member change count
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ *outSeed = mTable_RowArray.mArray_Seed;
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::GetCount(nsIMdbEnv* mev,
+ mdb_count* outCount) // member count
+{
+ NS_ENSURE_ARG_POINTER(outCount);
+ *outCount = mTable_RowArray.mArray_Fill;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkTable::GetPort(nsIMdbEnv* mev,
+ nsIMdbPort** acqPort) // collection container
+{
+ (void) morkEnv::FromMdbEnv(mev);
+ NS_ENSURE_ARG_POINTER(acqPort);
+ *acqPort = mTable_Store;
+ return NS_OK;
+}
+// } ----- end attribute methods -----
+
+// { ----- begin cursor methods -----
+NS_IMETHODIMP
+morkTable::GetCursor( // make a cursor starting iter at inMemberPos
+ nsIMdbEnv* mev, // context
+ mdb_pos inMemberPos, // zero-based ordinal pos of member in collection
+ nsIMdbCursor** acqCursor) // acquire new cursor instance
+{
+ return this->GetTableRowCursor(mev, inMemberPos,
+ (nsIMdbTableRowCursor**) acqCursor);
+}
+// } ----- end cursor methods -----
+
+// { ----- begin ID methods -----
+NS_IMETHODIMP
+morkTable::GetOid(nsIMdbEnv* mev,
+ mdbOid* outOid) // read object identity
+{
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ GetTableOid(ev, outOid);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkTable::BecomeContent(nsIMdbEnv* mev,
+ const mdbOid* inOid) // exchange content
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // remember table->MaybeDirtySpaceStoreAndTable();
+}
+
+// } ----- end ID methods -----
+
+// { ----- begin activity dropping methods -----
+NS_IMETHODIMP
+morkTable::DropActivity( // tell collection usage no longer expected
+ nsIMdbEnv* mev)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// } ----- end activity dropping methods -----
+
+// } ===== end nsIMdbCollection methods =====
+
+// { ===== begin nsIMdbTable methods =====
+
+// { ----- begin attribute methods -----
+
+NS_IMETHODIMP
+morkTable::SetTablePriority(nsIMdbEnv* mev, mdb_priority inPrio)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( inPrio > morkPriority_kMax )
+ inPrio = morkPriority_kMax;
+
+ mTable_Priority = inPrio;
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::GetTablePriority(nsIMdbEnv* mev, mdb_priority* outPrio)
+{
+ nsresult outErr = NS_OK;
+ mork_priority prio = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ prio = mTable_Priority;
+ if ( prio > morkPriority_kMax )
+ {
+ prio = morkPriority_kMax;
+ mTable_Priority = prio;
+ }
+ outErr = ev->AsErr();
+ }
+ if ( outPrio )
+ *outPrio = prio;
+ return outErr;
+}
+
+
+NS_IMETHODIMP
+morkTable:: GetTableBeVerbose(nsIMdbEnv* mev, mdb_bool* outBeVerbose)
+{
+ NS_ENSURE_ARG_POINTER(outBeVerbose);
+ *outBeVerbose = IsTableVerbose();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkTable::SetTableBeVerbose(nsIMdbEnv* mev, mdb_bool inBeVerbose)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( inBeVerbose )
+ SetTableVerbose();
+ else
+ ClearTableVerbose();
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::GetTableIsUnique(nsIMdbEnv* mev, mdb_bool* outIsUnique)
+{
+ NS_ENSURE_ARG_POINTER(outIsUnique);
+ *outIsUnique = IsTableUnique();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkTable::GetTableKind(nsIMdbEnv* mev, mdb_kind* outTableKind)
+{
+ NS_ENSURE_ARG_POINTER(outTableKind);
+ *outTableKind = mTable_Kind;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkTable::GetRowScope(nsIMdbEnv* mev, mdb_scope* outRowScope)
+{
+ nsresult outErr = NS_OK;
+ mdb_scope rowScope = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( mTable_RowSpace )
+ rowScope = mTable_RowSpace->SpaceScope();
+ else
+ NilRowSpaceError(ev);
+
+ outErr = ev->AsErr();
+ }
+ if ( outRowScope )
+ *outRowScope = rowScope;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::GetMetaRow( nsIMdbEnv* mev,
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ mdbOid* outOid, // output meta row oid, can be nil to suppress output
+ nsIMdbRow** acqRow) // acquire table's unique singleton meta row
+ // The purpose of a meta row is to support the persistent recording of
+ // meta info about a table as cells put into the distinguished meta row.
+ // Each table has exactly one meta row, which is not considered a member
+ // of the collection of rows inside the table. The only way to tell
+ // whether a row is a meta row is by the fact that it is returned by this
+ // GetMetaRow() method from some table. Otherwise nothing distinguishes
+ // a meta row from any other row. A meta row can be used anyplace that
+ // any other row can be used, and can even be put into other tables (or
+ // the same table) as a table member, if this is useful for some reason.
+ // The first attempt to access a table's meta row using GetMetaRow() will
+ // cause the meta row to be created if it did not already exist. When the
+ // meta row is created, it will have the row oid that was previously
+ // requested for this table's meta row; or if no oid was ever explicitly
+ // specified for this meta row, then a unique oid will be generated in
+ // the row scope named "metaScope" (so obviously MDB clients should not
+ // manually allocate any row IDs from that special meta scope namespace).
+ // The meta row oid can be specified either when the table is created, or
+ // else the first time that GetMetaRow() is called, by passing a non-nil
+ // pointer to an oid for parameter inOptionalMetaRowOid. The meta row's
+ // actual oid is returned in outOid (if this is a non-nil pointer), and
+ // it will be different from inOptionalMetaRowOid when the meta row was
+ // already given a different oid earlier.
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRow* row = GetMetaRow(ev, inOptionalMetaRowOid);
+ if ( row && ev->Good() )
+ {
+ if ( outOid )
+ *outOid = row->mRow_Oid;
+
+ outRow = row->AcquireRowHandle(ev, mTable_Store);
+ }
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+
+ if ( ev->Bad() && outOid )
+ {
+ outOid->mOid_Scope = 0;
+ outOid->mOid_Id = morkRow_kMinusOneRid;
+ }
+ return outErr;
+}
+
+// } ----- end attribute methods -----
+
+// { ----- begin cursor methods -----
+NS_IMETHODIMP
+morkTable::GetTableRowCursor( // make a cursor, starting iteration at inRowPos
+ nsIMdbEnv* mev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ nsIMdbTableRowCursor** acqCursor) // acquire new cursor instance
+{
+ nsresult outErr = NS_OK;
+ nsIMdbTableRowCursor* outCursor = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkTableRowCursor* cursor = NewTableRowCursor(ev, inRowPos);
+ if ( cursor )
+ {
+ if ( ev->Good() )
+ {
+ // cursor->mCursor_Seed = (mork_seed) inRowPos;
+ outCursor = cursor;
+ outCursor->AddRef();
+ }
+ }
+
+ outErr = ev->AsErr();
+ }
+ if ( acqCursor )
+ *acqCursor = outCursor;
+ return outErr;
+}
+// } ----- end row position methods -----
+
+// { ----- begin row position methods -----
+NS_IMETHODIMP
+morkTable::PosToOid( // get row member for a table position
+ nsIMdbEnv* mev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ mdbOid* outOid) // row oid at the specified position
+{
+ nsresult outErr = NS_OK;
+ mdbOid roid;
+ roid.mOid_Scope = 0;
+ roid.mOid_Id = (mork_id) -1;
+
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRow* row = SafeRowAt(ev, inRowPos);
+ if ( row )
+ roid = row->mRow_Oid;
+
+ outErr = ev->AsErr();
+ }
+ if ( outOid )
+ *outOid = roid;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::OidToPos( // test for the table position of a row member
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid, // row to find in table
+ mdb_pos* outPos) // zero-based ordinal position of row in table
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ mork_pos pos = ArrayHasOid(ev, inOid);
+ if ( outPos )
+ *outPos = pos;
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::PosToRow( // get row member for a table position
+ nsIMdbEnv* mev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ nsIMdbRow** acqRow) // acquire row at table position inRowPos
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRow* row = SafeRowAt(ev, inRowPos);
+ if ( row && mTable_Store )
+ outRow = row->AcquireRowHandle(ev, mTable_Store);
+
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::RowToPos( // test for the table position of a row member
+ nsIMdbEnv* mev, // context
+ nsIMdbRow* ioRow, // row to find in table
+ mdb_pos* outPos) // zero-based ordinal position of row in table
+{
+ nsresult outErr = NS_OK;
+ mork_pos pos = -1;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRowObject* row = (morkRowObject*) ioRow;
+ pos = ArrayHasOid(ev, &row->mRowObject_Row->mRow_Oid);
+ outErr = ev->AsErr();
+ }
+ if ( outPos )
+ *outPos = pos;
+ return outErr;
+}
+
+// Note that HasRow() performs the inverse oid->pos mapping
+// } ----- end row position methods -----
+
+// { ----- begin oid set methods -----
+NS_IMETHODIMP
+morkTable::AddOid( // make sure the row with inOid is a table member
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid) // row to ensure membership in table
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::HasOid( // test for the table position of a row member
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid, // row to find in table
+ mdb_bool* outHasOid) // whether inOid is a member row
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( outHasOid )
+ *outHasOid = MapHasOid(ev, inOid);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::CutOid( // make sure the row with inOid is not a member
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid) // row to remove from table
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( inOid && mTable_Store )
+ {
+ morkRow* row = mTable_Store->GetRow(ev, inOid);
+ if ( row )
+ CutRow(ev, row);
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+// } ----- end oid set methods -----
+
+// { ----- begin row set methods -----
+NS_IMETHODIMP
+morkTable::NewRow( // create a new row instance in table
+ nsIMdbEnv* mev, // context
+ mdbOid* ioOid, // please use zero (unbound) rowId for db-assigned IDs
+ nsIMdbRow** acqRow) // create new row
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( ioOid && mTable_Store )
+ {
+ morkRow* row = 0;
+ if ( ioOid->mOid_Id == morkRow_kMinusOneRid )
+ row = mTable_Store->NewRow(ev, ioOid->mOid_Scope);
+ else
+ row = mTable_Store->NewRowWithOid(ev, ioOid);
+
+ if ( row && AddRow(ev, row) )
+ outRow = row->AcquireRowHandle(ev, mTable_Store);
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::AddRow( // make sure the row with inOid is a table member
+ nsIMdbEnv* mev, // context
+ nsIMdbRow* ioRow) // row to ensure membership in table
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRowObject *rowObj = (morkRowObject *) ioRow;
+ morkRow* row = rowObj->mRowObject_Row;
+ AddRow(ev, row);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::HasRow( // test for the table position of a row member
+ nsIMdbEnv* mev, // context
+ nsIMdbRow* ioRow, // row to find in table
+ mdb_bool* outBool) // zero-based ordinal position of row in table
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRowObject *rowObj = (morkRowObject *) ioRow;
+ morkRow* row = rowObj->mRowObject_Row;
+ if ( outBool )
+ *outBool = MapHasOid(ev, &row->mRow_Oid);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+
+NS_IMETHODIMP
+morkTable::CutRow( // make sure the row with inOid is not a member
+ nsIMdbEnv* mev, // context
+ nsIMdbRow* ioRow) // row to remove from table
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRowObject *rowObj = (morkRowObject *) ioRow;
+ morkRow* row = rowObj->mRowObject_Row;
+ CutRow(ev, row);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::CutAllRows( // remove all rows from the table
+ nsIMdbEnv* mev) // context
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ CutAllRows(ev);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+// } ----- end row set methods -----
+
+// { ----- begin searching methods -----
+NS_IMETHODIMP
+morkTable::FindRowMatches( // search variable number of sorted cols
+ nsIMdbEnv* mev, // context
+ const mdbYarn* inPrefix, // content to find as prefix in row's column cell
+ nsIMdbTableRowCursor** acqCursor) // set of matching rows
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::GetSearchColumns( // query columns used by FindRowMatches()
+ nsIMdbEnv* mev, // context
+ mdb_count* outCount, // context
+ mdbColumnSet* outColSet) // caller supplied space to put columns
+ // GetSearchColumns() returns the columns actually searched when the
+ // FindRowMatches() method is called. No more than mColumnSet_Count
+ // slots of mColumnSet_Columns will be written, since mColumnSet_Count
+ // indicates how many slots are present in the column array. The
+ // actual number of search column used by the table is returned in
+ // the outCount parameter; if this number exceeds mColumnSet_Count,
+ // then a caller needs a bigger array to read the entire column set.
+ // The minimum of mColumnSet_Count and outCount is the number slots
+ // in mColumnSet_Columns that were actually written by this method.
+ //
+ // Callers are expected to change this set of columns by calls to
+ // nsIMdbTable::SearchColumnsHint() or SetSearchSorting(), or both.
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+// } ----- end searching methods -----
+
+// { ----- begin hinting methods -----
+NS_IMETHODIMP
+morkTable::SearchColumnsHint( // advise re future expected search cols
+ nsIMdbEnv* mev, // context
+ const mdbColumnSet* inColumnSet) // columns likely to be searched
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::SortColumnsHint( // advise re future expected sort columns
+ nsIMdbEnv* mev, // context
+ const mdbColumnSet* inColumnSet) // columns for likely sort requests
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::StartBatchChangeHint( // advise before many adds and cuts
+ nsIMdbEnv* mev, // context
+ const void* inLabel) // intend unique address to match end call
+ // If batch starts nest by virtue of nesting calls in the stack, then
+ // the address of a local variable makes a good batch start label that
+ // can be used at batch end time, and such addresses remain unique.
+{
+ // we don't do anything here.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+morkTable::EndBatchChangeHint( // advise before many adds and cuts
+ nsIMdbEnv* mev, // context
+ const void* inLabel) // label matching start label
+ // Suppose a table is maintaining one or many sort orders for a table,
+ // so that every row added to the table must be inserted in each sort,
+ // and every row cut must be removed from each sort. If a db client
+ // intends to make many such changes before needing any information
+ // about the order or positions of rows inside a table, then a client
+ // might tell the table to start batch changes in order to disable
+ // sorting of rows for the interim. Presumably a table will then do
+ // a full sort of all rows at need when the batch changes end, or when
+ // a surprise request occurs for row position during batch changes.
+{
+ // we don't do anything here.
+ return NS_OK;
+}
+// } ----- end hinting methods -----
+
+// { ----- begin sorting methods -----
+// sorting: note all rows are assumed sorted by row ID as a secondary
+// sort following the primary column sort, when table rows are sorted.
+
+NS_IMETHODIMP
+morkTable::CanSortColumn( // query which column is currently used for sorting
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // column to query sorting potential
+ mdb_bool* outCanSort) // whether the column can be sorted
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::GetSorting( // view same table in particular sorting
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // requested new column for sorting table
+ nsIMdbSorting** acqSorting) // acquire sorting for column
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::SetSearchSorting( // use this sorting in FindRowMatches()
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // often same as nsIMdbSorting::GetSortColumn()
+ nsIMdbSorting* ioSorting) // requested sorting for some column
+ // SetSearchSorting() attempts to inform the table that ioSorting
+ // should be used during calls to FindRowMatches() for searching
+ // the column which is actually sorted by ioSorting. This method
+ // is most useful in conjunction with nsIMdbSorting::SetCompare(),
+ // because otherwise a caller would not be able to override the
+ // comparison ordering method used during searches. Note that some
+ // database implementations might be unable to use an arbitrarily
+ // specified sort order, either due to schema or runtime interface
+ // constraints, in which case ioSorting might not actually be used.
+ // Presumably ioSorting is an instance that was returned from some
+ // earlier call to nsIMdbTable::GetSorting(). A caller can also
+ // use nsIMdbTable::SearchColumnsHint() to specify desired change
+ // in which columns are sorted and searched by FindRowMatches().
+ //
+ // A caller can pass a nil pointer for ioSorting to request that
+ // column inColumn no longer be used at all by FindRowMatches().
+ // But when ioSorting is non-nil, then inColumn should match the
+ // column actually sorted by ioSorting; when these do not agree,
+ // implementations are instructed to give precedence to the column
+ // specified by ioSorting (so this means callers might just pass
+ // zero for inColumn when ioSorting is also provided, since then
+ // inColumn is both redundant and ignored).
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// } ----- end sorting methods -----
+
+// { ----- begin moving methods -----
+// moving a row does nothing unless a table is currently unsorted
+
+NS_IMETHODIMP
+morkTable::MoveOid( // change position of row in unsorted table
+ nsIMdbEnv* mev, // context
+ const mdbOid* inOid, // row oid to find in table
+ mdb_pos inHintFromPos, // suggested hint regarding start position
+ mdb_pos inToPos, // desired new position for row inOid
+ mdb_pos* outActualPos) // actual new position of row in table
+{
+ nsresult outErr = NS_OK;
+ mdb_pos actualPos = -1; // meaning it was never found in table
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( inOid && mTable_Store )
+ {
+ morkRow* row = mTable_Store->GetRow(ev, inOid);
+ if ( row )
+ actualPos = MoveRow(ev, row, inHintFromPos, inToPos);
+ }
+ else
+ ev->NilPointerError();
+
+ outErr = ev->AsErr();
+ }
+ if ( outActualPos )
+ *outActualPos = actualPos;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTable::MoveRow( // change position of row in unsorted table
+ nsIMdbEnv* mev, // context
+ nsIMdbRow* ioRow, // row oid to find in table
+ mdb_pos inHintFromPos, // suggested hint regarding start position
+ mdb_pos inToPos, // desired new position for row ioRow
+ mdb_pos* outActualPos) // actual new position of row in table
+{
+ mdb_pos actualPos = -1; // meaning it was never found in table
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ morkRowObject *rowObj = (morkRowObject *) ioRow;
+ morkRow* row = rowObj->mRowObject_Row;
+ actualPos = MoveRow(ev, row, inHintFromPos, inToPos);
+ outErr = ev->AsErr();
+ }
+ if ( outActualPos )
+ *outActualPos = actualPos;
+ return outErr;
+}
+// } ----- end moving methods -----
+
+// { ----- begin index methods -----
+NS_IMETHODIMP
+morkTable::AddIndex( // create a sorting index for column if possible
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // the column to sort by index
+ nsIMdbThumb** acqThumb) // acquire thumb for incremental index building
+// Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+// then the index addition will be finished.
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::CutIndex( // stop supporting a specific column index
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // the column with index to be removed
+ nsIMdbThumb** acqThumb) // acquire thumb for incremental index destroy
+// Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+// then the index removal will be finished.
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::HasIndex( // query for current presence of a column index
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // the column to investigate
+ mdb_bool* outHasIndex) // whether column has index for this column
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::EnableIndexOnSort( // create an index for col on first sort
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn) // the column to index if ever sorted
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::QueryIndexOnSort( // check whether index on sort is enabled
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn, // the column to investigate
+ mdb_bool* outIndexOnSort) // whether column has index-on-sort enabled
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+morkTable::DisableIndexOnSort( // prevent future index creation on sort
+ nsIMdbEnv* mev, // context
+ mdb_column inColumn) // the column to index if ever sorted
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+// } ----- end index methods -----
+
+// } ===== end nsIMdbTable methods =====
+
+// we override these so that we'll use the xpcom add and release ref.
+#ifndef _MSC_VER
+mork_refs
+morkTable::AddStrongRef(nsIMdbEnv *ev)
+{
+ return (mork_refs) AddRef();
+}
+#endif
+
+mork_refs
+morkTable::AddStrongRef(morkEnv *ev)
+{
+ return (mork_refs) AddRef();
+}
+
+#ifndef _MSC_VER
+nsresult
+morkTable::CutStrongRef(nsIMdbEnv *ev)
+{
+ return (nsresult) Release();
+}
+#endif
+
+mork_refs
+morkTable::CutStrongRef(morkEnv *ev)
+{
+ return (mork_refs) Release();
+}
+
+mork_u2
+morkTable::AddTableGcUse(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ if ( mTable_GcUses < morkTable_kMaxTableGcUses ) // not already maxed out?
+ ++mTable_GcUses;
+
+ return mTable_GcUses;
+}
+
+mork_u2
+morkTable::CutTableGcUse(morkEnv* ev)
+{
+ if ( mTable_GcUses ) // any outstanding uses to cut?
+ {
+ if ( mTable_GcUses < morkTable_kMaxTableGcUses ) // not frozen at max?
+ --mTable_GcUses;
+ }
+ else
+ this->TableGcUsesUnderflowWarning(ev);
+
+ return mTable_GcUses;
+}
+
+// table dirty handling more complex thatn morkNode::SetNodeDirty() etc.
+
+void morkTable::SetTableClean(morkEnv* ev)
+{
+ if ( mTable_ChangeList.HasListMembers() )
+ {
+ nsIMdbHeap* heap = mTable_Store->mPort_Heap;
+ mTable_ChangeList.CutAndZapAllListMembers(ev, heap); // forget changes
+ }
+ mTable_ChangesCount = 0;
+
+ mTable_Flags = 0;
+ this->SetNodeClean();
+}
+
+// notifications regarding table changes:
+
+void morkTable::NoteTableMoveRow(morkEnv* ev, morkRow* ioRow, mork_pos inPos)
+{
+ nsIMdbHeap* heap = mTable_Store->mPort_Heap;
+ if ( this->IsTableRewrite() || this->HasChangeOverflow() )
+ this->NoteTableSetAll(ev);
+ else
+ {
+ morkTableChange* tableChange = new(*heap, ev)
+ morkTableChange(ev, ioRow, inPos);
+ if ( tableChange )
+ {
+ if ( ev->Good() )
+ {
+ mTable_ChangeList.PushTail(tableChange);
+ ++mTable_ChangesCount;
+ }
+ else
+ {
+ tableChange->ZapOldNext(ev, heap);
+ this->SetTableRewrite(); // just plan to write all table rows
+ }
+ }
+ }
+}
+
+void morkTable::note_row_move(morkEnv* ev, morkRow* ioRow, mork_pos inNewPos)
+{
+ if ( this->IsTableRewrite() || this->HasChangeOverflow() )
+ this->NoteTableSetAll(ev);
+ else
+ {
+ nsIMdbHeap* heap = mTable_Store->mPort_Heap;
+ morkTableChange* tableChange = new(*heap, ev)
+ morkTableChange(ev, ioRow, inNewPos);
+ if ( tableChange )
+ {
+ if ( ev->Good() )
+ {
+ mTable_ChangeList.PushTail(tableChange);
+ ++mTable_ChangesCount;
+ }
+ else
+ {
+ tableChange->ZapOldNext(ev, heap);
+ this->NoteTableSetAll(ev);
+ }
+ }
+ }
+}
+
+void morkTable::note_row_change(morkEnv* ev, mork_change inChange,
+ morkRow* ioRow)
+{
+ if ( this->IsTableRewrite() || this->HasChangeOverflow() )
+ this->NoteTableSetAll(ev);
+ else
+ {
+ nsIMdbHeap* heap = mTable_Store->mPort_Heap;
+ morkTableChange* tableChange = new(*heap, ev)
+ morkTableChange(ev, inChange, ioRow);
+ if ( tableChange )
+ {
+ if ( ev->Good() )
+ {
+ mTable_ChangeList.PushTail(tableChange);
+ ++mTable_ChangesCount;
+ }
+ else
+ {
+ tableChange->ZapOldNext(ev, heap);
+ this->NoteTableSetAll(ev);
+ }
+ }
+ }
+}
+
+void morkTable::NoteTableSetAll(morkEnv* ev)
+{
+ if ( mTable_ChangeList.HasListMembers() )
+ {
+ nsIMdbHeap* heap = mTable_Store->mPort_Heap;
+ mTable_ChangeList.CutAndZapAllListMembers(ev, heap); // forget changes
+ }
+ mTable_ChangesCount = 0;
+ this->SetTableRewrite();
+}
+
+/*static*/ void
+morkTable::TableGcUsesUnderflowWarning(morkEnv* ev)
+{
+ ev->NewWarning("mTable_GcUses underflow");
+}
+
+/*static*/ void
+morkTable::NonTableTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkTable");
+}
+
+/*static*/ void
+morkTable::NonTableTypeWarning(morkEnv* ev)
+{
+ ev->NewWarning("non morkTable");
+}
+
+/*static*/ void
+morkTable::NilRowSpaceError(morkEnv* ev)
+{
+ ev->NewError("nil mTable_RowSpace");
+}
+
+mork_bool morkTable::MaybeDirtySpaceStoreAndTable()
+{
+ morkRowSpace* rowSpace = mTable_RowSpace;
+ if ( rowSpace )
+ {
+ morkStore* store = rowSpace->mSpace_Store;
+ if ( store && store->mStore_CanDirty )
+ {
+ store->SetStoreDirty();
+ rowSpace->mSpace_CanDirty = morkBool_kTrue;
+ }
+
+ if ( rowSpace->mSpace_CanDirty ) // first time being dirtied?
+ {
+ if ( this->IsTableClean() )
+ {
+ mork_count rowCount = this->GetRowCount();
+ mork_count oneThird = rowCount / 4; // one third of rows
+ if ( oneThird > 0x07FFF ) // more than half max u2?
+ oneThird = 0x07FFF;
+
+ mTable_ChangesMax = (mork_u2) oneThird;
+ }
+ this->SetTableDirty();
+ rowSpace->SetRowSpaceDirty();
+
+ return morkBool_kTrue;
+ }
+ }
+ return morkBool_kFalse;
+}
+
+morkRow*
+morkTable::GetMetaRow(morkEnv* ev, const mdbOid* inOptionalMetaRowOid)
+{
+ morkRow* outRow = mTable_MetaRow;
+ if ( !outRow )
+ {
+ morkStore* store = mTable_Store;
+ mdbOid* oid = &mTable_MetaRowOid;
+ if ( inOptionalMetaRowOid && !oid->mOid_Scope )
+ *oid = *inOptionalMetaRowOid;
+
+ if ( oid->mOid_Scope ) // oid already recorded in table?
+ outRow = store->OidToRow(ev, oid);
+ else
+ {
+ outRow = store->NewRow(ev, morkStore_kMetaScope);
+ if ( outRow ) // need to record new oid in table?
+ *oid = outRow->mRow_Oid;
+ }
+ mTable_MetaRow = outRow;
+ if ( outRow ) // need to note another use of this row?
+ {
+ outRow->AddRowGcUse(ev);
+
+ this->SetTableNewMeta();
+ if ( this->IsTableClean() ) // catch dirty status of meta row?
+ this->MaybeDirtySpaceStoreAndTable();
+ }
+ }
+
+ return outRow;
+}
+
+void
+morkTable::GetTableOid(morkEnv* ev, mdbOid* outOid)
+{
+ morkRowSpace* space = mTable_RowSpace;
+ if ( space )
+ {
+ outOid->mOid_Scope = space->SpaceScope();
+ outOid->mOid_Id = this->TableId();
+ }
+ else
+ this->NilRowSpaceError(ev);
+}
+
+nsIMdbTable*
+morkTable::AcquireTableHandle(morkEnv* ev)
+{
+ AddRef();
+ return this;
+}
+
+mork_pos
+morkTable::ArrayHasOid(morkEnv* ev, const mdbOid* inOid)
+{
+ MORK_USED_1(ev);
+ mork_count count = mTable_RowArray.mArray_Fill;
+ mork_pos pos = -1;
+ while ( ++pos < (mork_pos)count )
+ {
+ morkRow* row = (morkRow*) mTable_RowArray.At(pos);
+ MORK_ASSERT(row);
+ if ( row && row->EqualOid(inOid) )
+ {
+ return pos;
+ }
+ }
+ return -1;
+}
+
+mork_bool
+morkTable::MapHasOid(morkEnv* ev, const mdbOid* inOid)
+{
+ if ( mTable_RowMap )
+ return ( mTable_RowMap->GetOid(ev, inOid) != 0 );
+ else
+ return ( ArrayHasOid(ev, inOid) >= 0 );
+}
+
+void morkTable::build_row_map(morkEnv* ev)
+{
+ morkRowMap* map = mTable_RowMap;
+ if ( !map )
+ {
+ mork_count count = mTable_RowArray.mArray_Fill + 3;
+ nsIMdbHeap* heap = mTable_Store->mPort_Heap;
+ map = new(*heap, ev) morkRowMap(ev, morkUsage::kHeap, heap, heap, count);
+ if ( map )
+ {
+ if ( ev->Good() )
+ {
+ mTable_RowMap = map; // put strong ref here
+ count = mTable_RowArray.mArray_Fill;
+ mork_pos pos = -1;
+ while ( ++pos < (mork_pos)count )
+ {
+ morkRow* row = (morkRow*) mTable_RowArray.At(pos);
+ if ( row && row->IsRow() )
+ map->AddRow(ev, row);
+ else
+ row->NonRowTypeError(ev);
+ }
+ }
+ else
+ map->CutStrongRef(ev);
+ }
+ }
+}
+
+morkRow* morkTable::find_member_row(morkEnv* ev, morkRow* ioRow)
+{
+ if ( mTable_RowMap )
+ return mTable_RowMap->GetRow(ev, ioRow);
+ else
+ {
+ mork_count count = mTable_RowArray.mArray_Fill;
+ mork_pos pos = -1;
+ while ( ++pos < (mork_pos)count )
+ {
+ morkRow* row = (morkRow*) mTable_RowArray.At(pos);
+ if ( row == ioRow )
+ return row;
+ }
+ }
+ return (morkRow*) 0;
+}
+
+mork_pos
+morkTable::MoveRow(morkEnv* ev, morkRow* ioRow, // change row position
+ mork_pos inHintFromPos, // suggested hint regarding start position
+ mork_pos inToPos) // desired new position for row ioRow
+ // MoveRow() returns the actual position of ioRow afterwards; this
+ // position is -1 if and only if ioRow was not found as a member.
+{
+ mork_pos outPos = -1; // means ioRow was not a table member
+ mork_bool canDirty = ( this->IsTableClean() )?
+ this->MaybeDirtySpaceStoreAndTable() : morkBool_kTrue;
+
+ morkRow** rows = (morkRow**) mTable_RowArray.mArray_Slots;
+ mork_count count = mTable_RowArray.mArray_Fill;
+ if ( count && rows && ev->Good() ) // any members at all? no errors?
+ {
+ mork_pos lastPos = count - 1; // index of last row slot
+
+ if ( inToPos > lastPos ) // beyond last used array slot?
+ inToPos = lastPos; // put row into last available slot
+ else if ( inToPos < 0 ) // before first usable slot?
+ inToPos = 0; // put row in very first slow
+
+ if ( inHintFromPos > lastPos ) // beyond last used array slot?
+ inHintFromPos = lastPos; // seek row in last available slot
+ else if ( inHintFromPos < 0 ) // before first usable slot?
+ inHintFromPos = 0; // seek row in very first slow
+
+ morkRow** fromSlot = 0; // becomes nonzero of ioRow is ever found
+ morkRow** rowsEnd = rows + count; // one past last used array slot
+
+ if ( inHintFromPos <= 0 ) // start of table? just scan for row?
+ {
+ morkRow** cursor = rows - 1; // before first array slot
+ while ( ++cursor < rowsEnd )
+ {
+ if ( *cursor == ioRow )
+ {
+ fromSlot = cursor;
+ break; // end while loop
+ }
+ }
+ }
+ else // search near the start position and work outwards
+ {
+ morkRow** lo = rows + inHintFromPos; // lowest search point
+ morkRow** hi = lo; // highest search point starts at lowest point
+
+ // Seek ioRow in spiral widening search below and above inHintFromPos.
+ // This is faster when inHintFromPos is at all accurate, but is slower
+ // than a straightforward scan when inHintFromPos is nearly random.
+
+ while ( lo >= rows || hi < rowsEnd ) // keep searching?
+ {
+ if ( lo >= rows ) // low direction search still feasible?
+ {
+ if ( *lo == ioRow ) // actually found the row?
+ {
+ fromSlot = lo;
+ break; // end while loop
+ }
+ --lo; // advance further lower
+ }
+ if ( hi < rowsEnd ) // high direction search still feasible?
+ {
+ if ( *hi == ioRow ) // actually found the row?
+ {
+ fromSlot = hi;
+ break; // end while loop
+ }
+ ++hi; // advance further higher
+ }
+ }
+ }
+
+ if ( fromSlot ) // ioRow was found as a table member?
+ {
+ outPos = fromSlot - rows; // actual position where row was found
+ if ( outPos != inToPos ) // actually need to move this row?
+ {
+ morkRow** toSlot = rows + inToPos; // slot where row must go
+
+ ++mTable_RowArray.mArray_Seed; // we modify the array now:
+
+ if ( fromSlot < toSlot ) // row is moving upwards?
+ {
+ morkRow** up = fromSlot; // leading pointer going upward
+ while ( ++up <= toSlot ) // have not gone above destination?
+ {
+ *fromSlot = *up; // shift down one
+ fromSlot = up; // shift trailing pointer up
+ }
+ }
+ else // ( fromSlot > toSlot ) // row is moving downwards
+ {
+ morkRow** down = fromSlot; // leading pointer going downward
+ while ( --down >= toSlot ) // have not gone below destination?
+ {
+ *fromSlot = *down; // shift up one
+ fromSlot = down; // shift trailing pointer
+ }
+ }
+ *toSlot = ioRow;
+ outPos = inToPos; // okay, we actually moved the row here
+
+ if ( canDirty )
+ this->note_row_move(ev, ioRow, inToPos);
+ }
+ }
+ }
+ return outPos;
+}
+
+mork_bool
+morkTable::AddRow(morkEnv* ev, morkRow* ioRow)
+{
+ morkRow* row = this->find_member_row(ev, ioRow);
+ if ( !row && ev->Good() )
+ {
+ mork_bool canDirty = ( this->IsTableClean() )?
+ this->MaybeDirtySpaceStoreAndTable() : morkBool_kTrue;
+
+ mork_pos pos = mTable_RowArray.AppendSlot(ev, ioRow);
+ if ( ev->Good() && pos >= 0 )
+ {
+ ioRow->AddRowGcUse(ev);
+ if ( mTable_RowMap )
+ {
+ if ( mTable_RowMap->AddRow(ev, ioRow) )
+ {
+ // okay, anything else?
+ }
+ else
+ mTable_RowArray.CutSlot(ev, pos);
+ }
+ else if ( mTable_RowArray.mArray_Fill >= morkTable_kMakeRowMapThreshold )
+ this->build_row_map(ev);
+
+ if ( canDirty && ev->Good() )
+ this->NoteTableAddRow(ev, ioRow);
+ }
+ }
+ return ev->Good();
+}
+
+mork_bool
+morkTable::CutRow(morkEnv* ev, morkRow* ioRow)
+{
+ morkRow* row = this->find_member_row(ev, ioRow);
+ if ( row )
+ {
+ mork_bool canDirty = ( this->IsTableClean() )?
+ this->MaybeDirtySpaceStoreAndTable() : morkBool_kTrue;
+
+ mork_count count = mTable_RowArray.mArray_Fill;
+ morkRow** rowSlots = (morkRow**) mTable_RowArray.mArray_Slots;
+ if ( rowSlots ) // array has vector as expected?
+ {
+ mork_pos pos = -1;
+ morkRow** end = rowSlots + count;
+ morkRow** slot = rowSlots - 1; // prepare for preincrement:
+ while ( ++slot < end ) // another slot to check?
+ {
+ if ( *slot == row ) // found the slot containing row?
+ {
+ pos = slot - rowSlots; // record absolute position
+ break; // end while loop
+ }
+ }
+ if ( pos >= 0 ) // need to cut if from the array?
+ mTable_RowArray.CutSlot(ev, pos);
+ else
+ ev->NewWarning("row not found in array");
+ }
+ else
+ mTable_RowArray.NilSlotsAddressError(ev);
+
+ if ( mTable_RowMap )
+ mTable_RowMap->CutRow(ev, ioRow);
+
+ if ( canDirty )
+ this->NoteTableCutRow(ev, ioRow);
+
+ if ( ioRow->CutRowGcUse(ev) == 0 )
+ ioRow->OnZeroRowGcUse(ev);
+ }
+ return ev->Good();
+}
+
+
+mork_bool
+morkTable::CutAllRows(morkEnv* ev)
+{
+ if ( this->MaybeDirtySpaceStoreAndTable() )
+ {
+ this->SetTableRewrite(); // everything is dirty
+ this->NoteTableSetAll(ev);
+ }
+
+ if ( ev->Good() )
+ {
+ mTable_RowArray.CutAllSlots(ev);
+ if ( mTable_RowMap )
+ {
+ morkRowMapIter i(ev, mTable_RowMap);
+ mork_change* c = 0;
+ morkRow* r = 0;
+
+ for ( c = i.FirstRow(ev, &r); c; c = i.NextRow(ev, &r) )
+ {
+ if ( r )
+ {
+ if ( r->CutRowGcUse(ev) == 0 )
+ r->OnZeroRowGcUse(ev);
+
+ i.CutHereRow(ev, (morkRow**) 0);
+ }
+ else
+ ev->NewWarning("nil row in table map");
+ }
+ }
+ }
+ return ev->Good();
+}
+
+morkTableRowCursor*
+morkTable::NewTableRowCursor(morkEnv* ev, mork_pos inRowPos)
+{
+ morkTableRowCursor* outCursor = 0;
+ if ( ev->Good() )
+ {
+ nsIMdbHeap* heap = mTable_Store->mPort_Heap;
+ morkTableRowCursor* cursor = new(*heap, ev)
+ morkTableRowCursor(ev, morkUsage::kHeap, heap, this, inRowPos);
+ if ( cursor )
+ {
+ if ( ev->Good() )
+ outCursor = cursor;
+ else
+ cursor->CutStrongRef((nsIMdbEnv *) ev);
+ }
+ }
+ return outCursor;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+morkTableChange::morkTableChange(morkEnv* ev, mork_change inChange,
+ morkRow* ioRow)
+// use this constructor for inChange == morkChange_kAdd or morkChange_kCut
+: morkNext()
+, mTableChange_Row( ioRow )
+, mTableChange_Pos( morkTableChange_kNone )
+{
+ if ( ioRow )
+ {
+ if ( ioRow->IsRow() )
+ {
+ if ( inChange == morkChange_kAdd )
+ mTableChange_Pos = morkTableChange_kAdd;
+ else if ( inChange == morkChange_kCut )
+ mTableChange_Pos = morkTableChange_kCut;
+ else
+ this->UnknownChangeError(ev);
+ }
+ else
+ ioRow->NonRowTypeError(ev);
+ }
+ else
+ ev->NilPointerError();
+}
+
+morkTableChange::morkTableChange(morkEnv* ev, morkRow* ioRow, mork_pos inPos)
+// use this constructor when the row is moved
+: morkNext()
+, mTableChange_Row( ioRow )
+, mTableChange_Pos( inPos )
+{
+ if ( ioRow )
+ {
+ if ( ioRow->IsRow() )
+ {
+ if ( inPos < 0 )
+ this->NegativeMovePosError(ev);
+ }
+ else
+ ioRow->NonRowTypeError(ev);
+ }
+ else
+ ev->NilPointerError();
+}
+
+void morkTableChange::UnknownChangeError(morkEnv* ev) const
+// morkChange_kAdd or morkChange_kCut
+{
+ ev->NewError("mTableChange_Pos neither kAdd nor kCut");
+}
+
+void morkTableChange::NegativeMovePosError(morkEnv* ev) const
+// move must be non-neg position
+{
+ ev->NewError("negative mTableChange_Pos for row move");
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+morkTableMap::~morkTableMap()
+{
+}
+
+morkTableMap::morkTableMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap)
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+ : morkBeadMap(ev, inUsage, ioHeap, ioSlotHeap)
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+ : morkNodeMap(ev, inUsage, ioHeap, ioSlotHeap)
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kTableMap;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
diff --git a/components/mork/src/morkTable.h b/components/mork/src/morkTable.h
new file mode 100644
index 000000000..196eeb8a6
--- /dev/null
+++ b/components/mork/src/morkTable.h
@@ -0,0 +1,729 @@
+/* -*- 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/. */
+
+#ifndef _MORKTABLE_
+#define _MORKTABLE_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKDEQUE_
+#include "morkDeque.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+#ifndef _MORKARRAY_
+#include "morkArray.h"
+#endif
+
+#ifndef _MORKROWMAP_
+#include "morkRowMap.h"
+#endif
+
+#ifndef _MORKNODEMAP_
+#include "morkNodeMap.h"
+#endif
+
+#ifndef _MORKPROBEMAP_
+#include "morkProbeMap.h"
+#endif
+
+#ifndef _MORKBEAD_
+#include "morkBead.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class nsIMdbTable;
+#define morkDerived_kTable /*i*/ 0x5462 /* ascii 'Tb' */
+
+/*| kStartRowArraySize: starting physical size of array for mTable_RowArray.
+**| We want this number very small, so that a table containing exactly one
+**| row member will not pay too significantly in space overhead. But we want
+**| a number bigger than one, so there is some space for growth.
+|*/
+#define morkTable_kStartRowArraySize 3 /* modest starting size for array */
+
+/*| kMakeRowMapThreshold: this is the number of rows in a table which causes
+**| a hash table (mTable_RowMap) to be lazily created for faster member row
+**| identification, during such operations as cuts and adds. This number must
+**| be small enough that linear searches are not bad for member counts less
+**| than this; but this number must also be large enough that creating a hash
+**| table does not increase the per-row space overhead by a big percentage.
+**| For speed, numbers on the order of ten to twenty are all fine; for space,
+**| I believe a number as small as ten will have too much space overhead.
+|*/
+#define morkTable_kMakeRowMapThreshold 17 /* when to build mTable_RowMap */
+
+#define morkTable_kStartRowMapSlotCount 13
+#define morkTable_kMaxTableGcUses 0x0FF /* max for 8-bit unsigned int */
+
+#define morkTable_kUniqueBit ((mork_u1) (1 << 0))
+#define morkTable_kVerboseBit ((mork_u1) (1 << 1))
+#define morkTable_kNotedBit ((mork_u1) (1 << 2)) /* space has change notes */
+#define morkTable_kRewriteBit ((mork_u1) (1 << 3)) /* must rewrite all rows */
+#define morkTable_kNewMetaBit ((mork_u1) (1 << 4)) /* new table meta row */
+
+class morkTable : public morkObject, public morkLink, public nsIMdbTable {
+
+ // NOTE the morkLink base is for morkRowSpace::mRowSpace_TablesByPriority
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // mork_color mBead_Color; // ID for this bead
+ // morkHandle* mObject_Handle; // weak ref to handle for this object
+
+public: // bead color setter & getter replace obsolete member mTable_Id:
+
+ NS_DECL_ISUPPORTS_INHERITED
+ mork_tid TableId() const { return mBead_Color; }
+ void SetTableId(mork_tid inTid) { mBead_Color = inTid; }
+
+ // we override these so we use xpcom ref-counting semantics.
+#ifndef _MSC_VER
+ // The first declaration of AddStrongRef is to suppress -Werror,-Woverloaded-virtual.
+ virtual mork_refs AddStrongRef(nsIMdbEnv* ev) override;
+#endif
+ virtual mork_refs AddStrongRef(morkEnv* ev) override;
+#ifndef _MSC_VER
+ // The first declaration of CutStrongRef is to suppress -Werror,-Woverloaded-virtual.
+ virtual nsresult CutStrongRef(nsIMdbEnv* ev) override;
+#endif
+ virtual mork_refs CutStrongRef(morkEnv* ev) override;
+public: // state is public because the entire Mork system is private
+
+// { ===== begin nsIMdbCollection methods =====
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD GetSeed(nsIMdbEnv* ev,
+ mdb_seed* outSeed) override; // member change count
+ NS_IMETHOD GetCount(nsIMdbEnv* ev,
+ mdb_count* outCount) override; // member count
+
+ NS_IMETHOD GetPort(nsIMdbEnv* ev,
+ nsIMdbPort** acqPort) override; // collection container
+ // } ----- end attribute methods -----
+
+ // { ----- begin cursor methods -----
+ NS_IMETHOD GetCursor( // make a cursor starting iter at inMemberPos
+ nsIMdbEnv* ev, // context
+ mdb_pos inMemberPos, // zero-based ordinal pos of member in collection
+ nsIMdbCursor** acqCursor) override; // acquire new cursor instance
+ // } ----- end cursor methods -----
+
+ // { ----- begin ID methods -----
+ NS_IMETHOD GetOid(nsIMdbEnv* ev,
+ mdbOid* outOid) override; // read object identity
+ NS_IMETHOD BecomeContent(nsIMdbEnv* ev,
+ const mdbOid* inOid) override; // exchange content
+ // } ----- end ID methods -----
+
+ // { ----- begin activity dropping methods -----
+ NS_IMETHOD DropActivity( // tell collection usage no longer expected
+ nsIMdbEnv* ev) override;
+ // } ----- end activity dropping methods -----
+
+// } ===== end nsIMdbCollection methods =====
+ NS_IMETHOD SetTablePriority(nsIMdbEnv* ev, mdb_priority inPrio) override;
+ NS_IMETHOD GetTablePriority(nsIMdbEnv* ev, mdb_priority* outPrio) override;
+
+ NS_IMETHOD GetTableBeVerbose(nsIMdbEnv* ev, mdb_bool* outBeVerbose) override;
+ NS_IMETHOD SetTableBeVerbose(nsIMdbEnv* ev, mdb_bool inBeVerbose) override;
+
+ NS_IMETHOD GetTableIsUnique(nsIMdbEnv* ev, mdb_bool* outIsUnique) override;
+
+ NS_IMETHOD GetTableKind(nsIMdbEnv* ev, mdb_kind* outTableKind) override;
+ NS_IMETHOD GetRowScope(nsIMdbEnv* ev, mdb_scope* outRowScope) override;
+
+ NS_IMETHOD GetMetaRow(
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ mdbOid* outOid, // output meta row oid, can be nil to suppress output
+ nsIMdbRow** acqRow) override; // acquire table's unique singleton meta row
+ // The purpose of a meta row is to support the persistent recording of
+ // meta info about a table as cells put into the distinguished meta row.
+ // Each table has exactly one meta row, which is not considered a member
+ // of the collection of rows inside the table. The only way to tell
+ // whether a row is a meta row is by the fact that it is returned by this
+ // GetMetaRow() method from some table. Otherwise nothing distinguishes
+ // a meta row from any other row. A meta row can be used anyplace that
+ // any other row can be used, and can even be put into other tables (or
+ // the same table) as a table member, if this is useful for some reason.
+ // The first attempt to access a table's meta row using GetMetaRow() will
+ // cause the meta row to be created if it did not already exist. When the
+ // meta row is created, it will have the row oid that was previously
+ // requested for this table's meta row; or if no oid was ever explicitly
+ // specified for this meta row, then a unique oid will be generated in
+ // the row scope named "m" (so obviously MDB clients should not
+ // manually allocate any row IDs from that special meta scope namespace).
+ // The meta row oid can be specified either when the table is created, or
+ // else the first time that GetMetaRow() is called, by passing a non-nil
+ // pointer to an oid for parameter inOptionalMetaRowOid. The meta row's
+ // actual oid is returned in outOid (if this is a non-nil pointer), and
+ // it will be different from inOptionalMetaRowOid when the meta row was
+ // already given a different oid earlier.
+ // } ----- end meta attribute methods -----
+
+
+ // { ----- begin cursor methods -----
+ NS_IMETHOD GetTableRowCursor( // make a cursor, starting iteration at inRowPos
+ nsIMdbEnv* ev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ nsIMdbTableRowCursor** acqCursor) override; // acquire new cursor instance
+ // } ----- end row position methods -----
+
+ // { ----- begin row position methods -----
+ NS_IMETHOD PosToOid( // get row member for a table position
+ nsIMdbEnv* ev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ mdbOid* outOid) override; // row oid at the specified position
+
+ NS_IMETHOD OidToPos( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // row to find in table
+ mdb_pos* outPos) override; // zero-based ordinal position of row in table
+
+ NS_IMETHOD PosToRow( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ mdb_pos inRowPos, // zero-based ordinal position of row in table
+ nsIMdbRow** acqRow) override; // acquire row at table position inRowPos
+
+ NS_IMETHOD RowToPos( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow, // row to find in table
+ mdb_pos* outPos) override; // zero-based ordinal position of row in table
+ // } ----- end row position methods -----
+
+ // { ----- begin oid set methods -----
+ NS_IMETHOD AddOid( // make sure the row with inOid is a table member
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid) override; // row to ensure membership in table
+
+ NS_IMETHOD HasOid( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // row to find in table
+ mdb_bool* outHasOid) override; // whether inOid is a member row
+
+ NS_IMETHOD CutOid( // make sure the row with inOid is not a member
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid) override; // row to remove from table
+ // } ----- end oid set methods -----
+
+ // { ----- begin row set methods -----
+ NS_IMETHOD NewRow( // create a new row instance in table
+ nsIMdbEnv* ev, // context
+ mdbOid* ioOid, // please use minus one (unbound) rowId for db-assigned IDs
+ nsIMdbRow** acqRow) override; // create new row
+
+ NS_IMETHOD AddRow( // make sure the row with inOid is a table member
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow) override; // row to ensure membership in table
+
+ NS_IMETHOD HasRow( // test for the table position of a row member
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow, // row to find in table
+ mdb_bool* outHasRow) override; // whether row is a table member
+
+ NS_IMETHOD CutRow( // make sure the row with inOid is not a member
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow) override; // row to remove from table
+
+ NS_IMETHOD CutAllRows( // remove all rows from the table
+ nsIMdbEnv* ev) override; // context
+ // } ----- end row set methods -----
+
+ // { ----- begin hinting methods -----
+ NS_IMETHOD SearchColumnsHint( // advise re future expected search cols
+ nsIMdbEnv* ev, // context
+ const mdbColumnSet* inColumnSet) override; // columns likely to be searched
+
+ NS_IMETHOD SortColumnsHint( // advise re future expected sort columns
+ nsIMdbEnv* ev, // context
+ const mdbColumnSet* inColumnSet) override; // columns for likely sort requests
+
+ NS_IMETHOD StartBatchChangeHint( // advise before many adds and cuts
+ nsIMdbEnv* ev, // context
+ const void* inLabel) override; // intend unique address to match end call
+ // If batch starts nest by virtue of nesting calls in the stack, then
+ // the address of a local variable makes a good batch start label that
+ // can be used at batch end time, and such addresses remain unique.
+
+ NS_IMETHOD EndBatchChangeHint( // advise before many adds and cuts
+ nsIMdbEnv* ev, // context
+ const void* inLabel) override; // label matching start label
+ // Suppose a table is maintaining one or many sort orders for a table,
+ // so that every row added to the table must be inserted in each sort,
+ // and every row cut must be removed from each sort. If a db client
+ // intends to make many such changes before needing any information
+ // about the order or positions of rows inside a table, then a client
+ // might tell the table to start batch changes in order to disable
+ // sorting of rows for the interim. Presumably a table will then do
+ // a full sort of all rows at need when the batch changes end, or when
+ // a surprise request occurs for row position during batch changes.
+ // } ----- end hinting methods -----
+
+ // { ----- begin searching methods -----
+ NS_IMETHOD FindRowMatches( // search variable number of sorted cols
+ nsIMdbEnv* ev, // context
+ const mdbYarn* inPrefix, // content to find as prefix in row's column cell
+ nsIMdbTableRowCursor** acqCursor) override; // set of matching rows
+
+ NS_IMETHOD GetSearchColumns( // query columns used by FindRowMatches()
+ nsIMdbEnv* ev, // context
+ mdb_count* outCount, // context
+ mdbColumnSet* outColSet) override; // caller supplied space to put columns
+ // GetSearchColumns() returns the columns actually searched when the
+ // FindRowMatches() method is called. No more than mColumnSet_Count
+ // slots of mColumnSet_Columns will be written, since mColumnSet_Count
+ // indicates how many slots are present in the column array. The
+ // actual number of search column used by the table is returned in
+ // the outCount parameter; if this number exceeds mColumnSet_Count,
+ // then a caller needs a bigger array to read the entire column set.
+ // The minimum of mColumnSet_Count and outCount is the number slots
+ // in mColumnSet_Columns that were actually written by this method.
+ //
+ // Callers are expected to change this set of columns by calls to
+ // nsIMdbTable::SearchColumnsHint() or SetSearchSorting(), or both.
+ // } ----- end searching methods -----
+
+ // { ----- begin sorting methods -----
+ // sorting: note all rows are assumed sorted by row ID as a secondary
+ // sort following the primary column sort, when table rows are sorted.
+
+ NS_IMETHOD
+ CanSortColumn( // query which column is currently used for sorting
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // column to query sorting potential
+ mdb_bool* outCanSort) override; // whether the column can be sorted
+
+ NS_IMETHOD GetSorting( // view same table in particular sorting
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // requested new column for sorting table
+ nsIMdbSorting** acqSorting) override; // acquire sorting for column
+
+ NS_IMETHOD SetSearchSorting( // use this sorting in FindRowMatches()
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // often same as nsIMdbSorting::GetSortColumn()
+ nsIMdbSorting* ioSorting) override; // requested sorting for some column
+ // SetSearchSorting() attempts to inform the table that ioSorting
+ // should be used during calls to FindRowMatches() for searching
+ // the column which is actually sorted by ioSorting. This method
+ // is most useful in conjunction with nsIMdbSorting::SetCompare(),
+ // because otherwise a caller would not be able to override the
+ // comparison ordering method used during searches. Note that some
+ // database implementations might be unable to use an arbitrarily
+ // specified sort order, either due to schema or runtime interface
+ // constraints, in which case ioSorting might not actually be used.
+ // Presumably ioSorting is an instance that was returned from some
+ // earlier call to nsIMdbTable::GetSorting(). A caller can also
+ // use nsIMdbTable::SearchColumnsHint() to specify desired change
+ // in which columns are sorted and searched by FindRowMatches().
+ //
+ // A caller can pass a nil pointer for ioSorting to request that
+ // column inColumn no longer be used at all by FindRowMatches().
+ // But when ioSorting is non-nil, then inColumn should match the
+ // column actually sorted by ioSorting; when these do not agree,
+ // implementations are instructed to give precedence to the column
+ // specified by ioSorting (so this means callers might just pass
+ // zero for inColumn when ioSorting is also provided, since then
+ // inColumn is both redundant and ignored).
+ // } ----- end sorting methods -----
+
+ // { ----- begin moving methods -----
+ // moving a row does nothing unless a table is currently unsorted
+
+ NS_IMETHOD MoveOid( // change position of row in unsorted table
+ nsIMdbEnv* ev, // context
+ const mdbOid* inOid, // row oid to find in table
+ mdb_pos inHintFromPos, // suggested hint regarding start position
+ mdb_pos inToPos, // desired new position for row inRowId
+ mdb_pos* outActualPos) override; // actual new position of row in table
+
+ NS_IMETHOD MoveRow( // change position of row in unsorted table
+ nsIMdbEnv* ev, // context
+ nsIMdbRow* ioRow, // row oid to find in table
+ mdb_pos inHintFromPos, // suggested hint regarding start position
+ mdb_pos inToPos, // desired new position for row inRowId
+ mdb_pos* outActualPos) override; // actual new position of row in table
+ // } ----- end moving methods -----
+
+ // { ----- begin index methods -----
+ NS_IMETHOD AddIndex( // create a sorting index for column if possible
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // the column to sort by index
+ nsIMdbThumb** acqThumb) override; // acquire thumb for incremental index building
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the index addition will be finished.
+
+ NS_IMETHOD CutIndex( // stop supporting a specific column index
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // the column with index to be removed
+ nsIMdbThumb** acqThumb) override; // acquire thumb for incremental index destroy
+ // Call nsIMdbThumb::DoMore() until done, or until the thumb is broken, and
+ // then the index removal will be finished.
+
+ NS_IMETHOD HasIndex( // query for current presence of a column index
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // the column to investigate
+ mdb_bool* outHasIndex) override; // whether column has index for this column
+
+
+ NS_IMETHOD EnableIndexOnSort( // create an index for col on first sort
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn) override; // the column to index if ever sorted
+
+ NS_IMETHOD QueryIndexOnSort( // check whether index on sort is enabled
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn, // the column to investigate
+ mdb_bool* outIndexOnSort) override; // whether column has index-on-sort enabled
+
+ NS_IMETHOD DisableIndexOnSort( // prevent future index creation on sort
+ nsIMdbEnv* ev, // context
+ mdb_column inColumn) override; // the column to index if ever sorted
+ // } ----- end index methods -----
+
+ morkStore* mTable_Store; // non-refcnted ptr to port
+
+ // mTable_RowSpace->SpaceScope() is row scope
+ morkRowSpace* mTable_RowSpace; // non-refcnted ptr to containing space
+
+ morkRow* mTable_MetaRow; // table's actual meta row
+ mdbOid mTable_MetaRowOid; // oid for meta row
+
+ morkRowMap* mTable_RowMap; // (strong ref) hash table of all members
+ morkArray mTable_RowArray; // array of morkRow pointers
+
+ morkList mTable_ChangeList; // list of table changes
+ mork_u2 mTable_ChangesCount; // length of changes list
+ mork_u2 mTable_ChangesMax; // max list length before rewrite
+
+ // mork_tid mTable_Id;
+ mork_kind mTable_Kind;
+
+ mork_u1 mTable_Flags; // bit flags
+ mork_priority mTable_Priority; // 0..9, any other value equals 9
+ mork_u1 mTable_GcUses; // persistent references from cells
+ mork_u1 mTable_Pad; // for u4 alignment
+
+public: // flags bit twiddling
+
+ void SetTableUnique() { mTable_Flags |= morkTable_kUniqueBit; }
+ void SetTableVerbose() { mTable_Flags |= morkTable_kVerboseBit; }
+ void SetTableNoted() { mTable_Flags |= morkTable_kNotedBit; }
+ void SetTableRewrite() { mTable_Flags |= morkTable_kRewriteBit; }
+ void SetTableNewMeta() { mTable_Flags |= morkTable_kNewMetaBit; }
+
+ void ClearTableUnique() { mTable_Flags &= (mork_u1) ~morkTable_kUniqueBit; }
+ void ClearTableVerbose() { mTable_Flags &= (mork_u1) ~morkTable_kVerboseBit; }
+ void ClearTableNoted() { mTable_Flags &= (mork_u1) ~morkTable_kNotedBit; }
+ void ClearTableRewrite() { mTable_Flags &= (mork_u1) ~morkTable_kRewriteBit; }
+ void ClearTableNewMeta() { mTable_Flags &= (mork_u1) ~morkTable_kNewMetaBit; }
+
+ mork_bool IsTableUnique() const
+ { return ( mTable_Flags & morkTable_kUniqueBit ) != 0; }
+
+ mork_bool IsTableVerbose() const
+ { return ( mTable_Flags & morkTable_kVerboseBit ) != 0; }
+
+ mork_bool IsTableNoted() const
+ { return ( mTable_Flags & morkTable_kNotedBit ) != 0; }
+
+ mork_bool IsTableRewrite() const
+ { return ( mTable_Flags & morkTable_kRewriteBit ) != 0; }
+
+ mork_bool IsTableNewMeta() const
+ { return ( mTable_Flags & morkTable_kNewMetaBit ) != 0; }
+
+public: // table dirty handling more complex than morkNode::SetNodeDirty() etc.
+
+ void SetTableDirty() { this->SetNodeDirty(); }
+ void SetTableClean(morkEnv* ev);
+
+ mork_bool IsTableClean() const { return this->IsNodeClean(); }
+ mork_bool IsTableDirty() const { return this->IsNodeDirty(); }
+
+public: // morkNode memory management operators
+ void* operator new(size_t inSize, nsIMdbHeap& ioHeap, morkEnv* ev) CPP_THROW_NEW
+ { return morkNode::MakeNew(inSize, ioHeap, ev); }
+
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseTable() if open
+
+public: // morkTable construction & destruction
+ morkTable(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioNodeHeap, morkStore* ioStore,
+ nsIMdbHeap* ioSlotHeap, morkRowSpace* ioRowSpace,
+ const mdbOid* inOptionalMetaRowOid, // can be nil to avoid specifying
+ mork_tid inTableId,
+ mork_kind inKind, mork_bool inMustBeUnique);
+ void CloseTable(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkTable(const morkTable& other);
+ morkTable& operator=(const morkTable& other);
+ virtual ~morkTable(); // assert that close executed earlier
+
+public: // dynamic type identification
+ mork_bool IsTable() const
+ { return IsNode() && mNode_Derived == morkDerived_kTable; }
+// } ===== end morkNode methods =====
+
+public: // errors
+ static void NonTableTypeError(morkEnv* ev);
+ static void NonTableTypeWarning(morkEnv* ev);
+ static void NilRowSpaceError(morkEnv* ev);
+
+public: // warnings
+ static void TableGcUsesUnderflowWarning(morkEnv* ev);
+
+public: // noting table changes
+
+ mork_bool HasChangeOverflow() const
+ { return mTable_ChangesCount >= mTable_ChangesMax; }
+
+ void NoteTableSetAll(morkEnv* ev);
+ void NoteTableMoveRow(morkEnv* ev, morkRow* ioRow, mork_pos inPos);
+
+ void note_row_change(morkEnv* ev, mork_change inChange, morkRow* ioRow);
+ void note_row_move(morkEnv* ev, morkRow* ioRow, mork_pos inNewPos);
+
+ void NoteTableAddRow(morkEnv* ev, morkRow* ioRow)
+ { this->note_row_change(ev, morkChange_kAdd, ioRow); }
+
+ void NoteTableCutRow(morkEnv* ev, morkRow* ioRow)
+ { this->note_row_change(ev, morkChange_kCut, ioRow); }
+
+protected: // internal row map methods
+
+ morkRow* find_member_row(morkEnv* ev, morkRow* ioRow);
+ void build_row_map(morkEnv* ev);
+
+public: // other table methods
+
+ mork_bool MaybeDirtySpaceStoreAndTable();
+
+ morkRow* GetMetaRow(morkEnv* ev, const mdbOid* inOptionalMetaRowOid);
+
+ mork_u2 AddTableGcUse(morkEnv* ev);
+ mork_u2 CutTableGcUse(morkEnv* ev);
+
+ // void DirtyAllTableContent(morkEnv* ev);
+
+ mork_seed TableSeed() const { return mTable_RowArray.mArray_Seed; }
+
+ morkRow* SafeRowAt(morkEnv* ev, mork_pos inPos)
+ { return (morkRow*) mTable_RowArray.SafeAt(ev, inPos); }
+
+ nsIMdbTable* AcquireTableHandle(morkEnv* ev); // mObject_Handle
+
+ mork_count GetRowCount() const { return mTable_RowArray.mArray_Fill; }
+
+ mork_bool IsTableUsed() const
+ { return (mTable_GcUses != 0 || this->GetRowCount() != 0); }
+
+ void GetTableOid(morkEnv* ev, mdbOid* outOid);
+ mork_pos ArrayHasOid(morkEnv* ev, const mdbOid* inOid);
+ mork_bool MapHasOid(morkEnv* ev, const mdbOid* inOid);
+ mork_bool AddRow(morkEnv* ev, morkRow* ioRow); // returns ev->Good()
+ mork_bool CutRow(morkEnv* ev, morkRow* ioRow); // returns ev->Good()
+ mork_bool CutAllRows(morkEnv* ev); // returns ev->Good()
+
+ mork_pos MoveRow(morkEnv* ev, morkRow* ioRow, // change row position
+ mork_pos inHintFromPos, // suggested hint regarding start position
+ mork_pos inToPos); // desired new position for row ioRow
+ // MoveRow() returns the actual position of ioRow afterwards; this
+ // position is -1 if and only if ioRow was not found as a member.
+
+
+ morkTableRowCursor* NewTableRowCursor(morkEnv* ev, mork_pos inRowPos);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakTable(morkTable* me,
+ morkEnv* ev, morkTable** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongTable(morkTable* me,
+ morkEnv* ev, morkTable** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// use negative values for kCut and kAdd, to keep non-neg move pos distinct:
+#define morkTableChange_kCut ((mork_pos) -1) /* shows row was cut */
+#define morkTableChange_kAdd ((mork_pos) -2) /* shows row was added */
+#define morkTableChange_kNone ((mork_pos) -3) /* unknown change */
+
+class morkTableChange : public morkNext {
+public: // state is public because the entire Mork system is private
+
+ morkRow* mTableChange_Row; // the row in the change
+
+ mork_pos mTableChange_Pos; // kAdd, kCut, or non-neg for row move
+
+public:
+ morkTableChange(morkEnv* ev, mork_change inChange, morkRow* ioRow);
+ // use this constructor for inChange == morkChange_kAdd or morkChange_kCut
+
+ morkTableChange(morkEnv* ev, morkRow* ioRow, mork_pos inPos);
+ // use this constructor when the row is moved
+
+public:
+ void UnknownChangeError(morkEnv* ev) const; // morkChange_kAdd or morkChange_kCut
+ void NegativeMovePosError(morkEnv* ev) const; // move must be non-neg position
+
+public:
+
+ mork_bool IsAddRowTableChange() const
+ { return ( mTableChange_Pos == morkTableChange_kAdd ); }
+
+ mork_bool IsCutRowTableChange() const
+ { return ( mTableChange_Pos == morkTableChange_kCut ); }
+
+ mork_bool IsMoveRowTableChange() const
+ { return ( mTableChange_Pos >= 0 ); }
+
+public:
+
+ mork_pos GetMovePos() const { return mTableChange_Pos; }
+ // GetMovePos() assumes that IsMoveRowTableChange() is true.
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkDerived_kTableMap /*i*/ 0x744D /* ascii 'tM' */
+
+/*| morkTableMap: maps mork_token -> morkTable
+|*/
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+class morkTableMap : public morkBeadMap {
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+class morkTableMap : public morkNodeMap { // for mapping tokens to tables
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+
+public:
+
+ virtual ~morkTableMap();
+ morkTableMap(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap);
+
+public: // other map methods
+
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+ mork_bool AddTable(morkEnv* ev, morkTable* ioTable)
+ { return this->AddBead(ev, ioTable); }
+ // the AddTable() boolean return equals ev->Good().
+
+ mork_bool CutTable(morkEnv* ev, mork_tid inTid)
+ { return this->CutBead(ev, inTid); }
+ // The CutTable() boolean return indicates whether removal happened.
+
+ morkTable* GetTable(morkEnv* ev, mork_tid inTid)
+ { return (morkTable*) this->GetBead(ev, inTid); }
+ // Note the returned table does NOT have an increase in refcount for this.
+
+ mork_num CutAllTables(morkEnv* ev)
+ { return this->CutAllBeads(ev); }
+ // CutAllTables() releases all the referenced table values.
+
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+ mork_bool AddTable(morkEnv* ev, morkTable* ioTable)
+ { return this->AddNode(ev, ioTable->TableId(), ioTable); }
+ // the AddTable() boolean return equals ev->Good().
+
+ mork_bool CutTable(morkEnv* ev, mork_tid inTid)
+ { return this->CutNode(ev, inTid); }
+ // The CutTable() boolean return indicates whether removal happened.
+
+ morkTable* GetTable(morkEnv* ev, mork_tid inTid)
+ { return (morkTable*) this->GetNode(ev, inTid); }
+ // Note the returned table does NOT have an increase in refcount for this.
+
+ mork_num CutAllTables(morkEnv* ev)
+ { return this->CutAllNodes(ev); }
+ // CutAllTables() releases all the referenced table values.
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+
+};
+
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+class morkTableMapIter: public morkBeadMapIter {
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+class morkTableMapIter: public morkMapIter{ // typesafe wrapper class
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+
+public:
+
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+ morkTableMapIter(morkEnv* ev, morkTableMap* ioMap)
+ : morkBeadMapIter(ev, ioMap) { }
+
+ morkTableMapIter( ) : morkBeadMapIter() { }
+ void InitTableMapIter(morkEnv* ev, morkTableMap* ioMap)
+ { this->InitBeadMapIter(ev, ioMap); }
+
+ morkTable* FirstTable(morkEnv* ev)
+ { return (morkTable*) this->FirstBead(ev); }
+
+ morkTable* NextTable(morkEnv* ev)
+ { return (morkTable*) this->NextBead(ev); }
+
+ morkTable* HereTable(morkEnv* ev)
+ { return (morkTable*) this->HereBead(ev); }
+
+
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+ morkTableMapIter(morkEnv* ev, morkTableMap* ioMap)
+ : morkMapIter(ev, ioMap) { }
+
+ morkTableMapIter( ) : morkMapIter() { }
+ void InitTableMapIter(morkEnv* ev, morkTableMap* ioMap)
+ { this->InitMapIter(ev, ioMap); }
+
+ mork_change*
+ FirstTable(morkEnv* ev, mork_tid* outTid, morkTable** outTable)
+ { return this->First(ev, outTid, outTable); }
+
+ mork_change*
+ NextTable(morkEnv* ev, mork_tid* outTid, morkTable** outTable)
+ { return this->Next(ev, outTid, outTable); }
+
+ mork_change*
+ HereTable(morkEnv* ev, mork_tid* outTid, morkTable** outTable)
+ { return this->Here(ev, outTid, outTable); }
+
+ // cutting while iterating hash map might dirty the parent table:
+ mork_change*
+ CutHereTable(morkEnv* ev, mork_tid* outTid, morkTable** outTable)
+ { return this->CutHere(ev, outTid, outTable); }
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKTABLE_ */
+
diff --git a/components/mork/src/morkTableRowCursor.cpp b/components/mork/src/morkTableRowCursor.cpp
new file mode 100644
index 000000000..6f3693269
--- /dev/null
+++ b/components/mork/src/morkTableRowCursor.cpp
@@ -0,0 +1,493 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKCURSOR_
+#include "morkCursor.h"
+#endif
+
+#ifndef _MORKTABLEROWCURSOR_
+#include "morkTableRowCursor.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKTABLE_
+#include "morkTable.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkTableRowCursor::CloseMorkNode(morkEnv* ev) // CloseTableRowCursor() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseTableRowCursor(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkTableRowCursor::~morkTableRowCursor() // CloseTableRowCursor() executed earlier
+{
+ CloseMorkNode(mMorkEnv);
+ MORK_ASSERT(this->IsShutNode());
+}
+
+/*public non-poly*/
+morkTableRowCursor::morkTableRowCursor(morkEnv* ev,
+ const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkTable* ioTable, mork_pos inRowPos)
+: morkCursor(ev, inUsage, ioHeap)
+, mTableRowCursor_Table( 0 )
+{
+ if ( ev->Good() )
+ {
+ if ( ioTable )
+ {
+ mCursor_Pos = inRowPos;
+ mCursor_Seed = ioTable->TableSeed();
+ morkTable::SlotWeakTable(ioTable, ev, &mTableRowCursor_Table);
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kTableRowCursor;
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkTableRowCursor, morkCursor, nsIMdbTableRowCursor)
+/*public non-poly*/ void
+morkTableRowCursor::CloseTableRowCursor(morkEnv* ev)
+{
+ if ( this->IsNode() )
+ {
+ mCursor_Pos = -1;
+ mCursor_Seed = 0;
+ morkTable::SlotWeakTable((morkTable*) 0, ev, &mTableRowCursor_Table);
+ this->CloseCursor(ev);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+// { ----- begin attribute methods -----
+/*virtual*/ nsresult
+morkTableRowCursor::GetCount(nsIMdbEnv* mev, mdb_count* outCount)
+{
+ nsresult outErr = NS_OK;
+ mdb_count count = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ count = GetMemberCount(ev);
+ outErr = ev->AsErr();
+ }
+ if ( outCount )
+ *outCount = count;
+ return outErr;
+}
+
+/*virtual*/ nsresult
+morkTableRowCursor::GetSeed(nsIMdbEnv* mev, mdb_seed* outSeed)
+{
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/*virtual*/ nsresult
+morkTableRowCursor::SetPos(nsIMdbEnv* mev, mdb_pos inPos)
+{
+ mCursor_Pos = inPos;
+ return NS_OK;
+}
+
+/*virtual*/ nsresult
+morkTableRowCursor::GetPos(nsIMdbEnv* mev, mdb_pos* outPos)
+{
+ *outPos = mCursor_Pos;
+ return NS_OK;
+}
+
+/*virtual*/ nsresult
+morkTableRowCursor::SetDoFailOnSeedOutOfSync(nsIMdbEnv* mev, mdb_bool inFail)
+{
+ mCursor_DoFailOnSeedOutOfSync = inFail;
+ return NS_OK;
+}
+
+/*virtual*/ nsresult
+morkTableRowCursor::GetDoFailOnSeedOutOfSync(nsIMdbEnv* mev, mdb_bool* outFail)
+{
+ NS_ENSURE_ARG_POINTER(outFail);
+ *outFail = mCursor_DoFailOnSeedOutOfSync;
+ return NS_OK;
+}
+// } ----- end attribute methods -----
+
+
+// { ===== begin nsIMdbTableRowCursor methods =====
+
+// { ----- begin attribute methods -----
+
+NS_IMETHODIMP
+morkTableRowCursor::GetTable(nsIMdbEnv* mev, nsIMdbTable** acqTable)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbTable* outTable = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( mTableRowCursor_Table )
+ outTable = mTableRowCursor_Table->AcquireTableHandle(ev);
+
+ outErr = ev->AsErr();
+ }
+ if ( acqTable )
+ *acqTable = outTable;
+ return outErr;
+}
+// } ----- end attribute methods -----
+
+// { ----- begin oid iteration methods -----
+NS_IMETHODIMP
+morkTableRowCursor::NextRowOid( // get row id of next row in the table
+ nsIMdbEnv* mev, // context
+ mdbOid* outOid, // out row oid
+ mdb_pos* outRowPos)
+{
+ nsresult outErr = NS_OK;
+ mork_pos pos = -1;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( outOid )
+ {
+ pos = NextRowOid(ev, outOid);
+ }
+ else
+ ev->NilPointerError();
+ outErr = ev->AsErr();
+ }
+ if ( outRowPos )
+ *outRowPos = pos;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTableRowCursor::PrevRowOid( // get row id of previous row in the table
+ nsIMdbEnv* mev, // context
+ mdbOid* outOid, // out row oid
+ mdb_pos* outRowPos)
+{
+ nsresult outErr = NS_OK;
+ mork_pos pos = -1;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ if ( outOid )
+ {
+ pos = PrevRowOid(ev, outOid);
+ }
+ else
+ ev->NilPointerError();
+ outErr = ev->AsErr();
+ }
+ if ( outRowPos )
+ *outRowPos = pos;
+ return outErr;
+}
+// } ----- end oid iteration methods -----
+
+// { ----- begin row iteration methods -----
+NS_IMETHODIMP
+morkTableRowCursor::NextRow( // get row cells from table for cells already in row
+ nsIMdbEnv* mev, // context
+ nsIMdbRow** acqRow, // acquire next row in table
+ mdb_pos* outRowPos)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+
+ mdbOid oid; // place to put oid we intend to ignore
+ morkRow* row = NextRow(ev, &oid, outRowPos);
+ if ( row )
+ {
+ morkStore* store = row->GetRowSpaceStore(ev);
+ if ( store )
+ outRow = row->AcquireRowHandle(ev, store);
+ }
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTableRowCursor::PrevRow( // get row cells from table for cells already in row
+ nsIMdbEnv* mev, // context
+ nsIMdbRow** acqRow, // acquire previous row in table
+ mdb_pos* outRowPos)
+{
+ nsresult outErr = NS_OK;
+ nsIMdbRow* outRow = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+
+ mdbOid oid; // place to put oid we intend to ignore
+ morkRow* row = PrevRow(ev, &oid, outRowPos);
+ if ( row )
+ {
+ morkStore* store = row->GetRowSpaceStore(ev);
+ if ( store )
+ outRow = row->AcquireRowHandle(ev, store);
+ }
+ outErr = ev->AsErr();
+ }
+ if ( acqRow )
+ *acqRow = outRow;
+ return outErr;
+}
+
+// } ----- end row iteration methods -----
+
+
+// { ----- begin duplicate row removal methods -----
+NS_IMETHODIMP
+morkTableRowCursor::CanHaveDupRowMembers(nsIMdbEnv* mev, // cursor might hold dups?
+ mdb_bool* outCanHaveDups)
+{
+ nsresult outErr = NS_OK;
+ mdb_bool canHaveDups = mdbBool_kFalse;
+
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ canHaveDups = CanHaveDupRowMembers(ev);
+ outErr = ev->AsErr();
+ }
+ if ( outCanHaveDups )
+ *outCanHaveDups = canHaveDups;
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkTableRowCursor::MakeUniqueCursor( // clone cursor, removing duplicate rows
+ nsIMdbEnv* mev, // context
+ nsIMdbTableRowCursor** acqCursor) // acquire clone with no dups
+ // Note that MakeUniqueCursor() is never necessary for a cursor which was
+ // created by table method nsIMdbTable::GetTableRowCursor(), because a table
+ // never contains the same row as a member more than once. However, a cursor
+ // created by table method nsIMdbTable::FindRowMatches() might contain the
+ // same row more than once, because the same row can generate a hit by more
+ // than one column with a matching string prefix. Note this method can
+ // return the very same cursor instance with just an incremented refcount,
+ // when the original cursor could not contain any duplicate rows (calling
+ // CanHaveDupRowMembers() shows this case on a false return). Otherwise
+ // this method returns a different cursor instance. Callers should not use
+ // this MakeUniqueCursor() method lightly, because it tends to defeat the
+ // purpose of lazy programming techniques, since it can force creation of
+ // an explicit row collection in a new cursor's representation, in order to
+ // inspect the row membership and remove any duplicates; this can have big
+ // impact if a collection holds tens of thousands of rows or more, when
+ // the original cursor with dups simply referenced rows indirectly by row
+ // position ranges, without using an explicit row set representation.
+ // Callers are encouraged to use nsIMdbCursor::GetCount() to determine
+ // whether the row collection is very large (tens of thousands), and to
+ // delay calling MakeUniqueCursor() when possible, until a user interface
+ // element actually demands the creation of an explicit set representation.
+{
+ nsresult outErr = NS_OK;
+ nsIMdbTableRowCursor* outCursor = 0;
+
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ AddRef();
+ outCursor = this;
+
+ outErr = ev->AsErr();
+ }
+ if ( acqCursor )
+ *acqCursor = outCursor;
+ return outErr;
+}
+// } ----- end duplicate row removal methods -----
+
+// } ===== end nsIMdbTableRowCursor methods =====
+
+
+/*static*/ void
+morkTableRowCursor::NonTableRowCursorTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkTableRowCursor");
+}
+
+
+mdb_pos
+morkTableRowCursor::NextRowOid(morkEnv* ev, mdbOid* outOid)
+{
+ mdb_pos outPos = -1;
+ (void) this->NextRow(ev, outOid, &outPos);
+ return outPos;
+}
+
+mdb_pos
+morkTableRowCursor::PrevRowOid(morkEnv* ev, mdbOid* outOid)
+{
+ mdb_pos outPos = -1;
+ (void) this->PrevRow(ev, outOid, &outPos);
+ return outPos;
+}
+
+mork_bool
+morkTableRowCursor::CanHaveDupRowMembers(morkEnv* ev)
+{
+ return morkBool_kFalse; // false default is correct
+}
+
+mork_count
+morkTableRowCursor::GetMemberCount(morkEnv* ev)
+{
+ morkTable* table = mTableRowCursor_Table;
+ if ( table )
+ return table->mTable_RowArray.mArray_Fill;
+ else
+ return 0;
+}
+
+morkRow*
+morkTableRowCursor::PrevRow(morkEnv* ev, mdbOid* outOid, mdb_pos* outPos)
+{
+ morkRow* outRow = 0;
+ mork_pos pos = -1;
+
+ morkTable* table = mTableRowCursor_Table;
+ if ( table )
+ {
+ if ( table->IsOpenNode() )
+ {
+ morkArray* array = &table->mTable_RowArray;
+ pos = mCursor_Pos - 1;
+
+ if ( pos >= 0 && pos < (mork_pos)(array->mArray_Fill) )
+ {
+ mCursor_Pos = pos; // update for next time
+ morkRow* row = (morkRow*) array->At(pos);
+ if ( row )
+ {
+ if ( row->IsRow() )
+ {
+ outRow = row;
+ *outOid = row->mRow_Oid;
+ }
+ else
+ row->NonRowTypeError(ev);
+ }
+ else
+ ev->NilPointerError();
+ }
+ else
+ {
+ outOid->mOid_Scope = 0;
+ outOid->mOid_Id = morkId_kMinusOne;
+ }
+ }
+ else
+ table->NonOpenNodeError(ev);
+ }
+ else
+ ev->NilPointerError();
+
+ *outPos = pos;
+ return outRow;
+}
+
+morkRow*
+morkTableRowCursor::NextRow(morkEnv* ev, mdbOid* outOid, mdb_pos* outPos)
+{
+ morkRow* outRow = 0;
+ mork_pos pos = -1;
+
+ morkTable* table = mTableRowCursor_Table;
+ if ( table )
+ {
+ if ( table->IsOpenNode() )
+ {
+ morkArray* array = &table->mTable_RowArray;
+ pos = mCursor_Pos;
+ if ( pos < 0 )
+ pos = 0;
+ else
+ ++pos;
+
+ if ( pos < (mork_pos)(array->mArray_Fill) )
+ {
+ mCursor_Pos = pos; // update for next time
+ morkRow* row = (morkRow*) array->At(pos);
+ if ( row )
+ {
+ if ( row->IsRow() )
+ {
+ outRow = row;
+ *outOid = row->mRow_Oid;
+ }
+ else
+ row->NonRowTypeError(ev);
+ }
+ else
+ ev->NilPointerError();
+ }
+ else
+ {
+ outOid->mOid_Scope = 0;
+ outOid->mOid_Id = morkId_kMinusOne;
+ }
+ }
+ else
+ table->NonOpenNodeError(ev);
+ }
+ else
+ ev->NilPointerError();
+
+ *outPos = pos;
+ return outRow;
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkTableRowCursor.h b/components/mork/src/morkTableRowCursor.h
new file mode 100644
index 000000000..edd98947b
--- /dev/null
+++ b/components/mork/src/morkTableRowCursor.h
@@ -0,0 +1,146 @@
+/* -*- 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/. */
+
+#ifndef _MORKTABLEROWCURSOR_
+#define _MORKTABLEROWCURSOR_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKCURSOR_
+#include "morkCursor.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class orkinTableRowCursor;
+#define morkDerived_kTableRowCursor /*i*/ 0x7243 /* ascii 'rC' */
+
+class morkTableRowCursor : public morkCursor, public nsIMdbTableRowCursor { // row iterator
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // morkFactory* mObject_Factory; // weak ref to suite factory
+
+ // mork_seed mCursor_Seed;
+ // mork_pos mCursor_Pos;
+ // mork_bool mCursor_DoFailOnSeedOutOfSync;
+ // mork_u1 mCursor_Pad[ 3 ]; // explicitly pad to u4 alignment
+
+public: // state is public because the entire Mork system is private
+ morkTable* mTableRowCursor_Table; // weak ref to table
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseTableRowCursor()
+
+protected:
+ virtual ~morkTableRowCursor(); // assert that close executed earlier
+
+public: // morkTableRowCursor construction & destruction
+ morkTableRowCursor(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkTable* ioTable, mork_pos inRowPos);
+ void CloseTableRowCursor(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkTableRowCursor(const morkTableRowCursor& other);
+ morkTableRowCursor& operator=(const morkTableRowCursor& other);
+
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // { ----- begin attribute methods -----
+ NS_IMETHOD GetCount(nsIMdbEnv* ev, mdb_count* outCount) override; // readonly
+ NS_IMETHOD GetSeed(nsIMdbEnv* ev, mdb_seed* outSeed) override; // readonly
+
+ NS_IMETHOD SetPos(nsIMdbEnv* ev, mdb_pos inPos) override; // mutable
+ NS_IMETHOD GetPos(nsIMdbEnv* ev, mdb_pos* outPos) override;
+
+ NS_IMETHOD SetDoFailOnSeedOutOfSync(nsIMdbEnv* ev, mdb_bool inFail) override;
+ NS_IMETHOD GetDoFailOnSeedOutOfSync(nsIMdbEnv* ev, mdb_bool* outFail) override;
+
+ // } ----- end attribute methods -----
+ NS_IMETHOD GetTable(nsIMdbEnv* ev, nsIMdbTable** acqTable) override;
+ // } ----- end attribute methods -----
+
+ // { ----- begin duplicate row removal methods -----
+ NS_IMETHOD CanHaveDupRowMembers(nsIMdbEnv* ev, // cursor might hold dups?
+ mdb_bool* outCanHaveDups) override;
+
+ NS_IMETHOD MakeUniqueCursor( // clone cursor, removing duplicate rows
+ nsIMdbEnv* ev, // context
+ nsIMdbTableRowCursor** acqCursor) override; // acquire clone with no dups
+ // } ----- end duplicate row removal methods -----
+
+ // { ----- begin oid iteration methods -----
+ NS_IMETHOD NextRowOid( // get row id of next row in the table
+ nsIMdbEnv* ev, // context
+ mdbOid* outOid, // out row oid
+ mdb_pos* outRowPos) override; // zero-based position of the row in table
+ NS_IMETHOD PrevRowOid( // get row id of previous row in the table
+ nsIMdbEnv* ev, // context
+ mdbOid* outOid, // out row oid
+ mdb_pos* outRowPos) override; // zero-based position of the row in table
+ // } ----- end oid iteration methods -----
+
+ // { ----- begin row iteration methods -----
+ NS_IMETHOD NextRow( // get row cells from table for cells already in row
+ nsIMdbEnv* ev, // context
+ nsIMdbRow** acqRow, // acquire next row in table
+ mdb_pos* outRowPos) override; // zero-based position of the row in table
+ NS_IMETHOD PrevRow( // get row cells from table for cells already in row
+ nsIMdbEnv* ev, // context
+ nsIMdbRow** acqRow, // acquire previous row in table
+ mdb_pos* outRowPos) override; // zero-based position of the row in table
+ // } ----- end row iteration methods -----
+
+
+public: // dynamic type identification
+ mork_bool IsTableRowCursor() const
+ { return IsNode() && mNode_Derived == morkDerived_kTableRowCursor; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ static void NonTableRowCursorTypeError(morkEnv* ev);
+
+public: // oid only iteration
+ mdb_pos NextRowOid(morkEnv* ev, mdbOid* outOid);
+ mdb_pos PrevRowOid(morkEnv* ev, mdbOid* outOid);
+
+public: // other table row cursor methods
+
+ virtual mork_bool CanHaveDupRowMembers(morkEnv* ev);
+ virtual mork_count GetMemberCount(morkEnv* ev);
+
+ virtual morkRow* NextRow(morkEnv* ev, mdbOid* outOid, mdb_pos* outPos);
+ virtual morkRow* PrevRow(morkEnv* ev, mdbOid* outOid, mdb_pos* outPos);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakTableRowCursor(morkTableRowCursor* me,
+ morkEnv* ev, morkTableRowCursor** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongTableRowCursor(morkTableRowCursor* me,
+ morkEnv* ev, morkTableRowCursor** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKTABLEROWCURSOR_ */
diff --git a/components/mork/src/morkThumb.cpp b/components/mork/src/morkThumb.cpp
new file mode 100644
index 000000000..ed88ea971
--- /dev/null
+++ b/components/mork/src/morkThumb.cpp
@@ -0,0 +1,523 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKTHUMB_
+#include "morkThumb.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+// #ifndef _MORKFILE_
+// #include "morkFile.h"
+// #endif
+
+#ifndef _MORKWRITER_
+#include "morkWriter.h"
+#endif
+
+#ifndef _MORKPARSER_
+#include "morkParser.h"
+#endif
+
+#ifndef _MORKBUILDER_
+#include "morkBuilder.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkThumb::CloseMorkNode(morkEnv* ev) // CloseThumb() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseThumb(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkThumb::~morkThumb() // assert CloseThumb() executed earlier
+{
+ CloseMorkNode(mMorkEnv);
+ MORK_ASSERT(mThumb_Magic==0);
+ MORK_ASSERT(mThumb_Store==0);
+ MORK_ASSERT(mThumb_File==0);
+}
+
+/*public non-poly*/
+morkThumb::morkThumb(morkEnv* ev,
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap,
+ nsIMdbHeap* ioSlotHeap, mork_magic inMagic)
+: morkObject(ev, inUsage, ioHeap, morkColor_kNone, (morkHandle*) 0)
+, mThumb_Magic( 0 )
+, mThumb_Total( 0 )
+, mThumb_Current( 0 )
+
+, mThumb_Done( morkBool_kFalse )
+, mThumb_Broken( morkBool_kFalse )
+, mThumb_Seed( 0 )
+
+, mThumb_Store( 0 )
+, mThumb_File( 0 )
+, mThumb_Writer( 0 )
+, mThumb_Builder( 0 )
+, mThumb_SourcePort( 0 )
+
+, mThumb_DoCollect( morkBool_kFalse )
+{
+ if ( ev->Good() )
+ {
+ if ( ioSlotHeap )
+ {
+ mThumb_Magic = inMagic;
+ mNode_Derived = morkDerived_kThumb;
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(morkThumb, morkObject, nsIMdbThumb)
+
+/*public non-poly*/ void
+morkThumb::CloseThumb(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ mThumb_Magic = 0;
+ if ( mThumb_Builder && mThumb_Store )
+ mThumb_Store->ForgetBuilder(ev);
+ morkBuilder::SlotStrongBuilder((morkBuilder*) 0, ev, &mThumb_Builder);
+
+ morkWriter::SlotStrongWriter((morkWriter*) 0, ev, &mThumb_Writer);
+ nsIMdbFile_SlotStrongFile((nsIMdbFile*) 0, ev, &mThumb_File);
+ morkStore::SlotStrongStore((morkStore*) 0, ev, &mThumb_Store);
+ morkStore::SlotStrongPort((morkPort*) 0, ev, &mThumb_SourcePort);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+// { ===== begin nsIMdbThumb methods =====
+NS_IMETHODIMP
+morkThumb::GetProgress(nsIMdbEnv* mev, mdb_count* outTotal,
+ mdb_count* outCurrent, mdb_bool* outDone, mdb_bool* outBroken)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ GetProgress(ev, outTotal, outCurrent, outDone, outBroken);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkThumb::DoMore(nsIMdbEnv* mev, mdb_count* outTotal,
+ mdb_count* outCurrent, mdb_bool* outDone, mdb_bool* outBroken)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ DoMore(ev, outTotal, outCurrent, outDone, outBroken);
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+
+NS_IMETHODIMP
+morkThumb::CancelAndBreakThumb(nsIMdbEnv* mev)
+{
+ nsresult outErr = NS_OK;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ mThumb_Done = morkBool_kTrue;
+ mThumb_Broken = morkBool_kTrue;
+ CloseMorkNode(ev); // should I close this here?
+ outErr = ev->AsErr();
+ }
+ return outErr;
+}
+// } ===== end nsIMdbThumb methods =====
+
+/*static*/ void morkThumb::NonThumbTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkThumb");
+}
+
+/*static*/ void morkThumb::UnsupportedThumbMagicError(morkEnv* ev)
+{
+ ev->NewError("unsupported mThumb_Magic");
+}
+
+/*static*/ void morkThumb::NilThumbStoreError(morkEnv* ev)
+{
+ ev->NewError("nil mThumb_Store");
+}
+
+/*static*/ void morkThumb::NilThumbFileError(morkEnv* ev)
+{
+ ev->NewError("nil mThumb_File");
+}
+
+/*static*/ void morkThumb::NilThumbWriterError(morkEnv* ev)
+{
+ ev->NewError("nil mThumb_Writer");
+}
+
+/*static*/ void morkThumb::NilThumbBuilderError(morkEnv* ev)
+{
+ ev->NewError("nil mThumb_Builder");
+}
+
+/*static*/ void morkThumb::NilThumbSourcePortError(morkEnv* ev)
+{
+ ev->NewError("nil mThumb_SourcePort");
+}
+
+/*static*/ morkThumb*
+morkThumb::Make_OpenFileStore(morkEnv* ev, nsIMdbHeap* ioHeap,
+ morkStore* ioStore)
+{
+ morkThumb* outThumb = 0;
+ if ( ioHeap && ioStore )
+ {
+ nsIMdbFile* file = ioStore->mStore_File;
+ if ( file )
+ {
+ mork_pos fileEof = 0;
+ file->Eof(ev->AsMdbEnv(), &fileEof);
+ if ( ev->Good() )
+ {
+ outThumb = new(*ioHeap, ev)
+ morkThumb(ev, morkUsage::kHeap, ioHeap, ioHeap,
+ morkThumb_kMagic_OpenFileStore);
+
+ if ( outThumb )
+ {
+ morkBuilder* builder = ioStore->LazyGetBuilder(ev);
+ if ( builder )
+ {
+ outThumb->mThumb_Total = (mork_count) fileEof;
+ morkStore::SlotStrongStore(ioStore, ev, &outThumb->mThumb_Store);
+ morkBuilder::SlotStrongBuilder(builder, ev,
+ &outThumb->mThumb_Builder);
+ }
+ }
+ }
+ }
+ else
+ ioStore->NilStoreFileError(ev);
+ }
+ else
+ ev->NilPointerError();
+
+ return outThumb;
+}
+
+
+/*static*/ morkThumb*
+morkThumb::Make_LargeCommit(morkEnv* ev,
+ nsIMdbHeap* ioHeap, morkStore* ioStore)
+{
+ morkThumb* outThumb = 0;
+ if ( ioHeap && ioStore )
+ {
+ nsIMdbFile* file = ioStore->mStore_File;
+ if ( file )
+ {
+ outThumb = new(*ioHeap, ev)
+ morkThumb(ev, morkUsage::kHeap, ioHeap, ioHeap,
+ morkThumb_kMagic_LargeCommit);
+
+ if ( outThumb )
+ {
+ morkWriter* writer = new(*ioHeap, ev)
+ morkWriter(ev, morkUsage::kHeap, ioHeap, ioStore, file, ioHeap);
+ if ( writer )
+ {
+ writer->mWriter_CommitGroupIdentity =
+ ++ioStore->mStore_CommitGroupIdentity;
+ writer->mWriter_NeedDirtyAll = morkBool_kFalse;
+ outThumb->mThumb_DoCollect = morkBool_kFalse;
+ morkStore::SlotStrongStore(ioStore, ev, &outThumb->mThumb_Store);
+
+ nsIMdbFile_SlotStrongFile(file, ev, &outThumb->mThumb_File);
+
+ outThumb->mThumb_Writer = writer; // pass writer ownership to thumb
+ }
+ }
+ }
+ else
+ ioStore->NilStoreFileError(ev);
+ }
+ else
+ ev->NilPointerError();
+
+ return outThumb;
+}
+
+/*static*/ morkThumb*
+morkThumb::Make_CompressCommit(morkEnv* ev,
+ nsIMdbHeap* ioHeap, morkStore* ioStore, mork_bool inDoCollect)
+{
+ morkThumb* outThumb = 0;
+ if ( ioHeap && ioStore )
+ {
+ nsIMdbFile* file = ioStore->mStore_File;
+ if ( file )
+ {
+ outThumb = new(*ioHeap, ev)
+ morkThumb(ev, morkUsage::kHeap, ioHeap, ioHeap,
+ morkThumb_kMagic_CompressCommit);
+
+ if ( outThumb )
+ {
+ morkWriter* writer = new(*ioHeap, ev)
+ morkWriter(ev, morkUsage::kHeap, ioHeap, ioStore, file, ioHeap);
+ if ( writer )
+ {
+ writer->mWriter_NeedDirtyAll = morkBool_kTrue;
+ outThumb->mThumb_DoCollect = inDoCollect;
+ morkStore::SlotStrongStore(ioStore, ev, &outThumb->mThumb_Store);
+ nsIMdbFile_SlotStrongFile(file, ev, &outThumb->mThumb_File);
+ outThumb->mThumb_Writer = writer; // pass writer ownership to thumb
+
+ // cope with fact that parsed transaction groups are going away:
+ ioStore->mStore_FirstCommitGroupPos = 0;
+ ioStore->mStore_SecondCommitGroupPos = 0;
+ }
+ }
+ }
+ else
+ ioStore->NilStoreFileError(ev);
+ }
+ else
+ ev->NilPointerError();
+
+ return outThumb;
+}
+
+// { ===== begin non-poly methods imitating nsIMdbThumb =====
+void morkThumb::GetProgress(morkEnv* ev, mdb_count* outTotal,
+ mdb_count* outCurrent, mdb_bool* outDone, mdb_bool* outBroken)
+{
+ MORK_USED_1(ev);
+ if ( outTotal )
+ *outTotal = mThumb_Total;
+ if ( outCurrent )
+ *outCurrent = mThumb_Current;
+ if ( outDone )
+ *outDone = mThumb_Done;
+ if ( outBroken )
+ *outBroken = mThumb_Broken;
+}
+
+void morkThumb::DoMore(morkEnv* ev, mdb_count* outTotal,
+ mdb_count* outCurrent, mdb_bool* outDone, mdb_bool* outBroken)
+{
+ if ( !mThumb_Done && !mThumb_Broken )
+ {
+ switch ( mThumb_Magic )
+ {
+ case morkThumb_kMagic_OpenFilePort: // 1 /* factory method */
+ this->DoMore_OpenFilePort(ev); break;
+
+ case morkThumb_kMagic_OpenFileStore: // 2 /* factory method */
+ this->DoMore_OpenFileStore(ev); break;
+
+ case morkThumb_kMagic_ExportToFormat: // 3 /* port method */
+ this->DoMore_ExportToFormat(ev); break;
+
+ case morkThumb_kMagic_ImportContent: // 4 /* store method */
+ this->DoMore_ImportContent(ev); break;
+
+ case morkThumb_kMagic_LargeCommit: // 5 /* store method */
+ this->DoMore_LargeCommit(ev); break;
+
+ case morkThumb_kMagic_SessionCommit: // 6 /* store method */
+ this->DoMore_SessionCommit(ev); break;
+
+ case morkThumb_kMagic_CompressCommit: // 7 /* store method */
+ this->DoMore_CompressCommit(ev); break;
+
+ case morkThumb_kMagic_SearchManyColumns: // 8 /* table method */
+ this->DoMore_SearchManyColumns(ev); break;
+
+ case morkThumb_kMagic_NewSortColumn: // 9 /* table metho) */
+ this->DoMore_NewSortColumn(ev); break;
+
+ case morkThumb_kMagic_NewSortColumnWithCompare: // 10 /* table method */
+ this->DoMore_NewSortColumnWithCompare(ev); break;
+
+ case morkThumb_kMagic_CloneSortColumn: // 11 /* table method */
+ this->DoMore_CloneSortColumn(ev); break;
+
+ case morkThumb_kMagic_AddIndex: // 12 /* table method */
+ this->DoMore_AddIndex(ev); break;
+
+ case morkThumb_kMagic_CutIndex: // 13 /* table method */
+ this->DoMore_CutIndex(ev); break;
+
+ default:
+ this->UnsupportedThumbMagicError(ev);
+ break;
+ }
+ }
+ if ( outTotal )
+ *outTotal = mThumb_Total;
+ if ( outCurrent )
+ *outCurrent = mThumb_Current;
+ if ( outDone )
+ *outDone = mThumb_Done;
+ if ( outBroken )
+ *outBroken = mThumb_Broken;
+}
+
+void morkThumb::CancelAndBreakThumb(morkEnv* ev)
+{
+ MORK_USED_1(ev);
+ mThumb_Broken = morkBool_kTrue;
+}
+
+// } ===== end non-poly methods imitating nsIMdbThumb =====
+
+morkStore*
+morkThumb::ThumbToOpenStore(morkEnv* ev)
+// for orkinFactory::ThumbToOpenStore() after OpenFileStore()
+{
+ MORK_USED_1(ev);
+ return mThumb_Store;
+}
+
+void morkThumb::DoMore_OpenFilePort(morkEnv* ev)
+{
+ this->UnsupportedThumbMagicError(ev);
+}
+
+void morkThumb::DoMore_OpenFileStore(morkEnv* ev)
+{
+ morkBuilder* builder = mThumb_Builder;
+ if ( builder )
+ {
+ mork_pos pos = 0;
+ builder->ParseMore(ev, &pos, &mThumb_Done, &mThumb_Broken);
+ // mThumb_Total = builder->mBuilder_TotalCount;
+ // mThumb_Current = builder->mBuilder_DoneCount;
+ mThumb_Current = (mork_count) pos;
+ }
+ else
+ {
+ this->NilThumbBuilderError(ev);
+ mThumb_Broken = morkBool_kTrue;
+ mThumb_Done = morkBool_kTrue;
+ }
+}
+
+void morkThumb::DoMore_ExportToFormat(morkEnv* ev)
+{
+ this->UnsupportedThumbMagicError(ev);
+}
+
+void morkThumb::DoMore_ImportContent(morkEnv* ev)
+{
+ this->UnsupportedThumbMagicError(ev);
+}
+
+void morkThumb::DoMore_LargeCommit(morkEnv* ev)
+{
+ this->DoMore_Commit(ev);
+}
+
+void morkThumb::DoMore_SessionCommit(morkEnv* ev)
+{
+ this->DoMore_Commit(ev);
+}
+
+void morkThumb::DoMore_Commit(morkEnv* ev)
+{
+ morkWriter* writer = mThumb_Writer;
+ if ( writer )
+ {
+ writer->WriteMore(ev);
+ mThumb_Total = writer->mWriter_TotalCount;
+ mThumb_Current = writer->mWriter_DoneCount;
+ mThumb_Done = ( ev->Bad() || writer->IsWritingDone() );
+ mThumb_Broken = ev->Bad();
+ }
+ else
+ {
+ this->NilThumbWriterError(ev);
+ mThumb_Broken = morkBool_kTrue;
+ mThumb_Done = morkBool_kTrue;
+ }
+}
+
+void morkThumb::DoMore_CompressCommit(morkEnv* ev)
+{
+ this->DoMore_Commit(ev);
+}
+
+void morkThumb::DoMore_SearchManyColumns(morkEnv* ev)
+{
+ this->UnsupportedThumbMagicError(ev);
+}
+
+void morkThumb::DoMore_NewSortColumn(morkEnv* ev)
+{
+ this->UnsupportedThumbMagicError(ev);
+}
+
+void morkThumb::DoMore_NewSortColumnWithCompare(morkEnv* ev)
+{
+ this->UnsupportedThumbMagicError(ev);
+}
+
+void morkThumb::DoMore_CloneSortColumn(morkEnv* ev)
+{
+ this->UnsupportedThumbMagicError(ev);
+}
+
+void morkThumb::DoMore_AddIndex(morkEnv* ev)
+{
+ this->UnsupportedThumbMagicError(ev);
+}
+
+void morkThumb::DoMore_CutIndex(morkEnv* ev)
+{
+ this->UnsupportedThumbMagicError(ev);
+}
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkThumb.h b/components/mork/src/morkThumb.h
new file mode 100644
index 000000000..f17aee9a5
--- /dev/null
+++ b/components/mork/src/morkThumb.h
@@ -0,0 +1,180 @@
+/* -*- 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/. */
+
+#ifndef _MORKTHUMB_
+#define _MORKTHUMB_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKOBJECT_
+#include "morkObject.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+#define morkThumb_kMagic_OpenFilePort 1 /* factory method */
+#define morkThumb_kMagic_OpenFileStore 2 /* factory method */
+#define morkThumb_kMagic_ExportToFormat 3 /* port method */
+#define morkThumb_kMagic_ImportContent 4 /* store method */
+#define morkThumb_kMagic_LargeCommit 5 /* store method */
+#define morkThumb_kMagic_SessionCommit 6 /* store method */
+#define morkThumb_kMagic_CompressCommit 7 /* store method */
+#define morkThumb_kMagic_SearchManyColumns 8 /* table method */
+#define morkThumb_kMagic_NewSortColumn 9 /* table metho) */
+#define morkThumb_kMagic_NewSortColumnWithCompare 10 /* table method */
+#define morkThumb_kMagic_CloneSortColumn 11 /* table method */
+#define morkThumb_kMagic_AddIndex 12 /* table method */
+#define morkThumb_kMagic_CutIndex 13 /* table method */
+
+#define morkDerived_kThumb /*i*/ 0x5468 /* ascii 'Th' */
+
+/*| morkThumb:
+|*/
+class morkThumb : public morkObject, public nsIMdbThumb {
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // mork_color mBead_Color; // ID for this bead
+ // morkHandle* mObject_Handle; // weak ref to handle for this object
+
+public: // state is public because the entire Mork system is private
+ NS_DECL_ISUPPORTS_INHERITED
+
+// { ===== begin nsIMdbThumb methods =====
+ NS_IMETHOD GetProgress(nsIMdbEnv* ev, mdb_count* outTotal,
+ mdb_count* outCurrent, mdb_bool* outDone, mdb_bool* outBroken) override;
+
+ NS_IMETHOD DoMore(nsIMdbEnv* ev, mdb_count* outTotal,
+ mdb_count* outCurrent, mdb_bool* outDone, mdb_bool* outBroken) override;
+
+ NS_IMETHOD CancelAndBreakThumb(nsIMdbEnv* ev) override;
+// } ===== end nsIMdbThumb methods =====
+
+ // might as well include all the return values here:
+
+ mork_magic mThumb_Magic; // magic sig different in each thumb type
+ mork_count mThumb_Total;
+ mork_count mThumb_Current;
+
+ mork_bool mThumb_Done;
+ mork_bool mThumb_Broken;
+ mork_u2 mThumb_Seed; // optional seed for u4 alignment padding
+
+ morkStore* mThumb_Store; // weak ref to created store
+ nsIMdbFile* mThumb_File; // strong ref to file (store, import, export)
+ morkWriter* mThumb_Writer; // strong ref to writer (for commit)
+ morkBuilder* mThumb_Builder; // strong ref to builder (for store open)
+ morkPort* mThumb_SourcePort; // strong ref to port for import
+
+ mork_bool mThumb_DoCollect; // influence whether a collect happens
+ mork_bool mThumb_Pad[ 3 ]; // padding for u4 alignment
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseThumb() only if open
+
+public: // morkThumb construction & destruction
+ morkThumb(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, nsIMdbHeap* ioSlotHeap, mork_magic inMagic);
+ void CloseThumb(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkThumb(const morkThumb& other);
+ morkThumb& operator=(const morkThumb& other);
+ virtual ~morkThumb(); // assert that CloseThumb() executed earlier
+
+public: // dynamic type identification
+ mork_bool IsThumb() const
+ { return IsNode() && mNode_Derived == morkDerived_kThumb; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ static void NonThumbTypeError(morkEnv* ev);
+ static void UnsupportedThumbMagicError(morkEnv* ev);
+
+ static void NilThumbStoreError(morkEnv* ev);
+ static void NilThumbFileError(morkEnv* ev);
+ static void NilThumbWriterError(morkEnv* ev);
+ static void NilThumbBuilderError(morkEnv* ev);
+ static void NilThumbSourcePortError(morkEnv* ev);
+
+public: // 'do more' methods
+
+ void DoMore_OpenFilePort(morkEnv* ev);
+ void DoMore_OpenFileStore(morkEnv* ev);
+ void DoMore_ExportToFormat(morkEnv* ev);
+ void DoMore_ImportContent(morkEnv* ev);
+ void DoMore_LargeCommit(morkEnv* ev);
+ void DoMore_SessionCommit(morkEnv* ev);
+ void DoMore_CompressCommit(morkEnv* ev);
+ void DoMore_Commit(morkEnv* ev);
+ void DoMore_SearchManyColumns(morkEnv* ev);
+ void DoMore_NewSortColumn(morkEnv* ev);
+ void DoMore_NewSortColumnWithCompare(morkEnv* ev);
+ void DoMore_CloneSortColumn(morkEnv* ev);
+ void DoMore_AddIndex(morkEnv* ev);
+ void DoMore_CutIndex(morkEnv* ev);
+
+public: // other thumb methods
+
+ morkStore* ThumbToOpenStore(morkEnv* ev);
+ // for orkinFactory::ThumbToOpenStore() after OpenFileStore()
+
+public: // assorted thumb constructors
+
+ static morkThumb* Make_OpenFileStore(morkEnv* ev,
+ nsIMdbHeap* ioHeap, morkStore* ioStore);
+
+ static morkThumb* Make_CompressCommit(morkEnv* ev,
+ nsIMdbHeap* ioHeap, morkStore* ioStore, mork_bool inDoCollect);
+
+ static morkThumb* Make_LargeCommit(morkEnv* ev,
+ nsIMdbHeap* ioHeap, morkStore* ioStore);
+
+// { ===== begin non-poly methods imitating nsIMdbThumb =====
+ void GetProgress(morkEnv* ev, mdb_count* outTotal,
+ mdb_count* outCurrent, mdb_bool* outDone, mdb_bool* outBroken);
+
+ void DoMore(morkEnv* ev, mdb_count* outTotal,
+ mdb_count* outCurrent, mdb_bool* outDone, mdb_bool* outBroken);
+
+ void CancelAndBreakThumb(morkEnv* ev);
+// } ===== end non-poly methods imitating nsIMdbThumb =====
+
+
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakThumb(morkThumb* me,
+ morkEnv* ev, morkThumb** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongThumb(morkThumb* me,
+ morkEnv* ev, morkThumb** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKTHUMB_ */
diff --git a/components/mork/src/morkUniqRowCursor.h b/components/mork/src/morkUniqRowCursor.h
new file mode 100644
index 000000000..44eac9471
--- /dev/null
+++ b/components/mork/src/morkUniqRowCursor.h
@@ -0,0 +1,94 @@
+/* -*- 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/. */
+
+#ifndef _MORKUNIQROWCURSOR_
+#define _MORKUNIQROWCURSOR_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKCURSOR_
+#include "morkCursor.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+class orkinTableRowCursor;
+// #define morkDerived_kUniqRowCursor /*i*/ 0x7352 /* ascii 'sR' */
+
+class morkUniqRowCursor : public morkTableRowCursor { // row iterator
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+ // morkFactory* mObject_Factory; // weak ref to suite factory
+
+ // mork_seed mCursor_Seed;
+ // mork_pos mCursor_Pos;
+ // mork_bool mCursor_DoFailOnSeedOutOfSync;
+ // mork_u1 mCursor_Pad[ 3 ]; // explicitly pad to u4 alignment
+
+ // morkTable* mTableRowCursor_Table; // weak ref to table
+
+public: // state is public because the entire Mork system is private
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseUniqRowCursor()
+ virtual ~morkUniqRowCursor(); // assert that close executed earlier
+
+public: // morkUniqRowCursor construction & destruction
+ morkUniqRowCursor(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkTable* ioTable, mork_pos inRowPos);
+ void CloseUniqRowCursor(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkUniqRowCursor(const morkUniqRowCursor& other);
+ morkUniqRowCursor& operator=(const morkUniqRowCursor& other);
+
+public: // dynamic type identification
+ // mork_bool IsUniqRowCursor() const
+ // { return IsNode() && mNode_Derived == morkDerived_kUniqRowCursor; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ static void NonUniqRowCursorTypeError(morkEnv* ev);
+
+public: // other search row cursor methods
+
+ virtual mork_bool CanHaveDupRowMembers(morkEnv* ev);
+ virtual mork_count GetMemberCount(morkEnv* ev);
+
+ virtual orkinTableRowCursor* AcquireUniqueRowCursorHandle(morkEnv* ev);
+
+ // virtual mdb_pos NextRowOid(morkEnv* ev, mdbOid* outOid);
+ virtual morkRow* NextRow(morkEnv* ev, mdbOid* outOid, mdb_pos* outPos);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakUniqRowCursor(morkUniqRowCursor* me,
+ morkEnv* ev, morkUniqRowCursor** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongUniqRowCursor(morkUniqRowCursor* me,
+ morkEnv* ev, morkUniqRowCursor** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKUNIQROWCURSOR_ */
diff --git a/components/mork/src/morkWriter.cpp b/components/mork/src/morkWriter.cpp
new file mode 100644
index 000000000..98ca5457f
--- /dev/null
+++ b/components/mork/src/morkWriter.cpp
@@ -0,0 +1,2206 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKBLOB_
+#include "morkBlob.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKARRAY_
+#include "morkWriter.h"
+#endif
+
+// #ifndef _MORKFILE_
+// #include "morkFile.h"
+// #endif
+
+#ifndef _MORKSTREAM_
+#include "morkStream.h"
+#endif
+
+#ifndef _MORKSTORE_
+#include "morkStore.h"
+#endif
+
+#ifndef _MORKATOMSPACE_
+#include "morkAtomSpace.h"
+#endif
+
+#ifndef _MORKROWSPACE_
+#include "morkRowSpace.h"
+#endif
+
+#ifndef _MORKROWMAP_
+#include "morkRowMap.h"
+#endif
+
+#ifndef _MORKATOMMAP_
+#include "morkAtomMap.h"
+#endif
+
+#ifndef _MORKROW_
+#include "morkRow.h"
+#endif
+
+#ifndef _MORKTABLE_
+#include "morkTable.h"
+#endif
+
+#ifndef _MORKCELL_
+#include "morkCell.h"
+#endif
+
+#ifndef _MORKATOM_
+#include "morkAtom.h"
+#endif
+
+#ifndef _MORKCH_
+#include "morkCh.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkWriter::CloseMorkNode(morkEnv* ev) // CloseTable() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseWriter(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkWriter::~morkWriter() // assert CloseTable() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+ MORK_ASSERT(mWriter_Store==0);
+}
+
+/*public non-poly*/
+morkWriter::morkWriter(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkStore* ioStore, nsIMdbFile* ioFile,
+ nsIMdbHeap* ioSlotHeap)
+: morkNode(ev, inUsage, ioHeap)
+, mWriter_Store( 0 )
+, mWriter_File( 0 )
+, mWriter_Bud( 0 )
+, mWriter_Stream( 0 )
+, mWriter_SlotHeap( 0 )
+
+, mWriter_CommitGroupIdentity( 0 ) // see mStore_CommitGroupIdentity
+, mWriter_GroupBufFill( 0 )
+
+, mWriter_TotalCount( morkWriter_kCountNumberOfPhases )
+, mWriter_DoneCount( 0 )
+
+, mWriter_LineSize( 0 )
+, mWriter_MaxIndent( morkWriter_kMaxIndent )
+, mWriter_MaxLine( morkWriter_kMaxLine )
+
+, mWriter_TableForm( 0 )
+, mWriter_TableAtomScope( 'v' )
+, mWriter_TableRowScope( 0 )
+, mWriter_TableKind( 0 )
+
+, mWriter_RowForm( 0 )
+, mWriter_RowAtomScope( 0 )
+, mWriter_RowScope( 0 )
+
+, mWriter_DictForm( 0 )
+, mWriter_DictAtomScope( 'v' )
+
+, mWriter_NeedDirtyAll( morkBool_kFalse )
+, mWriter_Incremental( morkBool_kTrue ) // opposite of mWriter_NeedDirtyAll
+, mWriter_DidStartDict( morkBool_kFalse )
+, mWriter_DidEndDict( morkBool_kTrue )
+
+, mWriter_SuppressDirtyRowNewline( morkBool_kFalse )
+, mWriter_DidStartGroup( morkBool_kFalse )
+, mWriter_DidEndGroup( morkBool_kTrue )
+, mWriter_Phase( morkWriter_kPhaseNothingDone )
+
+, mWriter_BeVerbose( ev->mEnv_BeVerbose )
+
+, mWriter_TableRowArrayPos( 0 )
+
+// empty constructors for map iterators:
+, mWriter_StoreAtomSpacesIter( )
+, mWriter_AtomSpaceAtomAidsIter( )
+
+, mWriter_StoreRowSpacesIter( )
+, mWriter_RowSpaceTablesIter( )
+, mWriter_RowSpaceRowsIter( )
+{
+ mWriter_GroupBuf[ 0 ] = 0;
+
+ mWriter_SafeNameBuf[ 0 ] = 0;
+ mWriter_SafeNameBuf[ morkWriter_kMaxColumnNameSize * 2 ] = 0;
+ mWriter_ColNameBuf[ 0 ] = 0;
+ mWriter_ColNameBuf[ morkWriter_kMaxColumnNameSize ] = 0;
+
+ mdbYarn* y = &mWriter_ColYarn;
+ y->mYarn_Buf = mWriter_ColNameBuf; // where to put col bytes
+ y->mYarn_Fill = 0; // set later by writer
+ y->mYarn_Size = morkWriter_kMaxColumnNameSize; // our buf size
+ y->mYarn_More = 0; // set later by writer
+ y->mYarn_Form = 0; // set later by writer
+ y->mYarn_Grow = 0; // do not allow buffer growth
+
+ y = &mWriter_SafeYarn;
+ y->mYarn_Buf = mWriter_SafeNameBuf; // where to put col bytes
+ y->mYarn_Fill = 0; // set later by writer
+ y->mYarn_Size = morkWriter_kMaxColumnNameSize * 2; // our buf size
+ y->mYarn_More = 0; // set later by writer
+ y->mYarn_Form = 0; // set later by writer
+ y->mYarn_Grow = 0; // do not allow buffer growth
+
+ if ( ev->Good() )
+ {
+ if ( ioSlotHeap && ioFile && ioStore )
+ {
+ morkStore::SlotWeakStore(ioStore, ev, &mWriter_Store);
+ nsIMdbFile_SlotStrongFile(ioFile, ev, &mWriter_File);
+ nsIMdbHeap_SlotStrongHeap(ioSlotHeap, ev, &mWriter_SlotHeap);
+ if ( ev->Good() )
+ {
+ mNode_Derived = morkDerived_kWriter;
+ }
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+
+void
+morkWriter::MakeWriterStream(morkEnv* ev) // give writer a suitable stream
+{
+ mWriter_Incremental = !mWriter_NeedDirtyAll; // opposites
+
+ if ( !mWriter_Stream && ev->Good() )
+ {
+ if ( mWriter_File )
+ {
+ morkStream* stream = 0;
+ mork_bool frozen = morkBool_kFalse; // need to modify
+ nsIMdbHeap* heap = mWriter_SlotHeap;
+
+ if ( mWriter_Incremental )
+ {
+ stream = new(*heap, ev)
+ morkStream(ev, morkUsage::kHeap, heap, mWriter_File,
+ morkWriter_kStreamBufSize, frozen);
+ }
+ else // compress commit
+ {
+ nsIMdbFile* bud = 0;
+ mWriter_File->AcquireBud(ev->AsMdbEnv(), heap, &bud);
+ if ( bud )
+ {
+ if ( ev->Good() )
+ {
+ mWriter_Bud = bud;
+ stream = new(*heap, ev)
+ morkStream(ev, morkUsage::kHeap, heap, bud,
+ morkWriter_kStreamBufSize, frozen);
+ }
+ else
+ bud->Release();
+ }
+ }
+
+ if ( stream )
+ {
+ if ( ev->Good() )
+ mWriter_Stream = stream;
+ else
+ stream->CutStrongRef(ev->AsMdbEnv());
+ }
+ }
+ else
+ this->NilWriterFileError(ev);
+ }
+}
+
+/*public non-poly*/ void
+morkWriter::CloseWriter(morkEnv* ev) // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ {
+ morkStore::SlotWeakStore((morkStore*) 0, ev, &mWriter_Store);
+ nsIMdbFile_SlotStrongFile((nsIMdbFile*) 0, ev, &mWriter_File);
+ nsIMdbFile_SlotStrongFile((nsIMdbFile*) 0, ev, &mWriter_Bud);
+ morkStream::SlotStrongStream((morkStream*) 0, ev, &mWriter_Stream);
+ nsIMdbHeap_SlotStrongHeap((nsIMdbHeap*) 0, ev, &mWriter_SlotHeap);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkWriter::NonWriterTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkWriter");
+}
+
+/*static*/ void
+morkWriter::NilWriterStoreError(morkEnv* ev)
+{
+ ev->NewError("nil mWriter_Store");
+}
+
+/*static*/ void
+morkWriter::NilWriterBudError(morkEnv* ev)
+{
+ ev->NewError("nil mWriter_Bud");
+}
+
+/*static*/ void
+morkWriter::NilWriterFileError(morkEnv* ev)
+{
+ ev->NewError("nil mWriter_File");
+}
+
+/*static*/ void
+morkWriter::NilWriterStreamError(morkEnv* ev)
+{
+ ev->NewError("nil mWriter_Stream");
+}
+
+/*static*/ void
+morkWriter::UnsupportedPhaseError(morkEnv* ev)
+{
+ ev->NewError("unsupported mWriter_Phase");
+}
+
+mork_bool
+morkWriter::WriteMore(morkEnv* ev) // call until IsWritingDone() is true
+{
+ if ( this->IsOpenNode() )
+ {
+ if ( this->IsWriter() )
+ {
+ if ( !mWriter_Stream )
+ this->MakeWriterStream(ev);
+
+ if ( mWriter_Stream )
+ {
+ if ( ev->Bad() )
+ {
+ ev->NewWarning("writing stops on error");
+ mWriter_Phase = morkWriter_kPhaseWritingDone;
+ }
+ switch( mWriter_Phase )
+ {
+ case morkWriter_kPhaseNothingDone:
+ OnNothingDone(ev); break;
+
+ case morkWriter_kPhaseDirtyAllDone:
+ OnDirtyAllDone(ev); break;
+
+ case morkWriter_kPhasePutHeaderDone:
+ OnPutHeaderDone(ev); break;
+
+ case morkWriter_kPhaseRenumberAllDone:
+ OnRenumberAllDone(ev); break;
+
+ case morkWriter_kPhaseStoreAtomSpaces:
+ OnStoreAtomSpaces(ev); break;
+
+ case morkWriter_kPhaseAtomSpaceAtomAids:
+ OnAtomSpaceAtomAids(ev); break;
+
+ case morkWriter_kPhaseStoreRowSpacesTables:
+ OnStoreRowSpacesTables(ev); break;
+
+ case morkWriter_kPhaseRowSpaceTables:
+ OnRowSpaceTables(ev); break;
+
+ case morkWriter_kPhaseTableRowArray:
+ OnTableRowArray(ev); break;
+
+ case morkWriter_kPhaseStoreRowSpacesRows:
+ OnStoreRowSpacesRows(ev); break;
+
+ case morkWriter_kPhaseRowSpaceRows:
+ OnRowSpaceRows(ev); break;
+
+ case morkWriter_kPhaseContentDone:
+ OnContentDone(ev); break;
+
+ case morkWriter_kPhaseWritingDone:
+ OnWritingDone(ev); break;
+
+ default:
+ this->UnsupportedPhaseError(ev);
+ }
+ }
+ else
+ this->NilWriterStreamError(ev);
+ }
+ else
+ this->NonWriterTypeError(ev);
+ }
+ else
+ this->NonOpenNodeError(ev);
+
+ return ev->Good();
+}
+
+static const char morkWriter_kHexDigits[] = "0123456789ABCDEF";
+
+mork_size
+morkWriter::WriteYarn(morkEnv* ev, const mdbYarn* inYarn)
+ // return number of atom bytes written on the current line (which
+ // implies that escaped line breaks will make the size value smaller
+ // than the entire yarn's size, since only part goes on a last line).
+{
+
+
+ mork_size outSize = 0;
+ mork_size lineSize = mWriter_LineSize;
+ morkStream* stream = mWriter_Stream;
+
+ const mork_u1* b = (const mork_u1*) inYarn->mYarn_Buf;
+ if ( b )
+ {
+ int c;
+ mork_fill fill = inYarn->mYarn_Fill;
+
+ const mork_u1* end = b + fill;
+ while ( b < end && ev->Good() )
+ {
+ if ( lineSize + outSize >= mWriter_MaxLine ) // continue line?
+ {
+ stream->PutByteThenNewline(ev, '\\');
+ mWriter_LineSize = lineSize = outSize = 0;
+ }
+
+ c = *b++; // next byte to print
+ if ( morkCh_IsValue(c) )
+ {
+ stream->Putc(ev, c);
+ ++outSize; // c
+ }
+ else if ( c == ')' || c == '$' || c == '\\' )
+ {
+ stream->Putc(ev, '\\');
+ stream->Putc(ev, c);
+ outSize += 2; // '\' c
+ }
+ else
+ {
+ outSize += 3; // '$' hex hex
+ stream->Putc(ev, '$');
+ stream->Putc(ev, morkWriter_kHexDigits[ (c >> 4) & 0x0F ]);
+ stream->Putc(ev, morkWriter_kHexDigits[ c & 0x0F ]);
+ }
+ }
+ }
+ mWriter_LineSize += outSize;
+
+ return outSize;
+}
+
+mork_size
+morkWriter::WriteAtom(morkEnv* ev, const morkAtom* inAtom)
+ // return number of atom bytes written on the current line (which
+ // implies that escaped line breaks will make the size value smaller
+ // than the entire atom's size, since only part goes on a last line).
+{
+ mork_size outSize = 0;
+ mdbYarn yarn; // to ref content inside atom
+
+ if ( morkAtom::AliasYarn(inAtom, &yarn) )
+ {
+ if ( mWriter_DidStartDict && yarn.mYarn_Form != mWriter_DictForm )
+ this->ChangeDictForm(ev, yarn.mYarn_Form);
+
+ outSize = this->WriteYarn(ev, &yarn);
+ // mWriter_LineSize += stream->Write(ev, inYarn->mYarn_Buf, outSize);
+ }
+ else
+ inAtom->BadAtomKindError(ev);
+
+ return outSize;
+}
+
+void
+morkWriter::WriteAtomSpaceAsDict(morkEnv* ev, morkAtomSpace* ioSpace)
+{
+ morkStream* stream = mWriter_Stream;
+ nsIMdbEnv *mdbev = ev->AsMdbEnv();
+ mork_scope scope = ioSpace->SpaceScope();
+ if ( scope < 0x80 )
+ {
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+ stream->PutString(ev, "< <(a=");
+ stream->Putc(ev, (int) scope);
+ ++mWriter_LineSize;
+ stream->PutString(ev, ")> // (f=iso-8859-1)");
+ mWriter_LineSize = stream->PutIndent(ev, morkWriter_kDictAliasDepth);
+ }
+ else
+ ioSpace->NonAsciiSpaceScopeName(ev);
+
+ if ( ev->Good() )
+ {
+ mdbYarn yarn; // to ref content inside atom
+ char buf[ 64 ]; // buffer for staging the dict alias hex ID
+ char* idBuf = buf + 1; // where the id always starts
+ buf[ 0 ] = '('; // we always start with open paren
+ morkBookAtom* atom = 0;
+ morkAtomAidMapIter* ai = &mWriter_AtomSpaceAtomAidsIter;
+ ai->InitAtomAidMapIter(ev, &ioSpace->mAtomSpace_AtomAids);
+ mork_change* c = 0;
+
+ for ( c = ai->FirstAtom(ev, &atom); c && ev->Good();
+ c = ai->NextAtom(ev, &atom) )
+ {
+ if ( atom )
+ {
+ if ( atom->IsAtomDirty() )
+ {
+ atom->SetAtomClean(); // neutralize change
+
+ morkAtom::AliasYarn(atom, &yarn);
+ mork_size size = ev->TokenAsHex(idBuf, atom->mBookAtom_Id);
+
+ if ( yarn.mYarn_Form != mWriter_DictForm )
+ this->ChangeDictForm(ev, yarn.mYarn_Form);
+
+ mork_size pending = yarn.mYarn_Fill + size +
+ morkWriter_kYarnEscapeSlop + 4;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasDepth);
+ mork_size bytesWritten;
+ stream->Write(mdbev, buf, size+1, &bytesWritten); // + '('
+ mWriter_LineSize += bytesWritten;
+
+ pending -= ( size + 1 );
+ this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasValueDepth);
+ stream->Putc(ev, '='); // start alias
+ ++mWriter_LineSize;
+
+ this->WriteYarn(ev, &yarn);
+ stream->Putc(ev, ')'); // end alias
+ ++mWriter_LineSize;
+
+ ++mWriter_DoneCount;
+ }
+ }
+ else
+ ev->NilPointerError();
+ }
+ ai->CloseMapIter(ev);
+ }
+
+ if ( ev->Good() )
+ {
+ ioSpace->SetAtomSpaceClean();
+ // this->IndentAsNeeded(ev, 0);
+ // stream->PutByteThenNewline(ev, '>'); // end dict
+
+ stream->Putc(ev, '>'); // end dict
+ ++mWriter_LineSize;
+ }
+}
+
+/*
+(I'm putting the text of this message in file morkWriter.cpp.)
+
+I'm making a change which should cause rows and tables to go away
+when a Mork db is compress committed, when the rows and tables
+are no longer needed. Because this is subtle, I'm describing it
+here in case misbehavior is ever observed. Otherwise you'll have
+almost no hope of fixing a related bug.
+
+This is done entirely in morkWriter.cpp: morkWriter::DirtyAll(),
+which currently marks all rows and tables dirty so they will be
+written in a later phase of the commit. My change is to merely
+selectively not mark certain rows and tables dirty, when they seem
+to be superfluous.
+
+A row is no longer needed when the mRow_GcUses slot hits zero, and
+this is used by the following inline morkRow method:
+
+ mork_bool IsRowUsed() const { return mRow_GcUses != 0; }
+
+Naturally disaster ensues if mRow_GcUses is ever smaller than right.
+
+Similarly, we should drop tables when mTable_GcUses hits zero, but
+only when a table contains no row members. We consider tables to
+self reference (and prevent collection) when they contain content.
+Again, disaster ensues if mTable_GcUses is ever smaller than right.
+
+ mork_count GetRowCount() const
+ { return mTable_RowArray.mArray_Fill; }
+
+ mork_bool IsTableUsed() const
+ { return (mTable_GcUses != 0 || this->GetRowCount() != 0); }
+
+Now let's question why the design involves filtering what gets set
+to dirty. Why not apply a filter in the later phase when we write
+content? Because I'm afraid of missing some subtle interaction in
+updating table and row relationships. It seems safer to write a row
+or table when it starts out dirty, before morkWriter::DirtyAll() is
+called. So this design calls for writing out rows and tables when
+they are still clearly used, and additionally, <i>when we have just
+been actively writing to them right before this commit</i>.
+
+Presumably if they are truly useless, they will no longer be dirtied
+in later sessions and will get collected during the next compress
+commit. So we wait to collect them until they become all dead, and
+not just mostly dead. (At which time you can feel free to go through
+their pockets looking for loose change.)
+*/
+
+mork_bool
+morkWriter::DirtyAll(morkEnv* ev)
+ // DirtyAll() visits every store sub-object and marks
+ // them dirty, including every table, row, cell, and atom. The return
+ // equals ev->Good(), to show whether any error happened. This method is
+ // intended for use in the beginning of a "compress commit" which writes
+ // all store content, whether dirty or not. We dirty everything first so
+ // that later iterations over content can mark things clean as they are
+ // written, and organize the process of serialization so that objects are
+ // written only at need (because of being dirty). Note the method can
+ // stop early when any error happens, since this will abort any commit.
+{
+ morkStore* store = mWriter_Store;
+ if ( store )
+ {
+ store->SetStoreDirty();
+ mork_change* c = 0;
+
+ if ( ev->Good() )
+ {
+ morkAtomSpaceMapIter* asi = &mWriter_StoreAtomSpacesIter;
+ asi->InitAtomSpaceMapIter(ev, &store->mStore_AtomSpaces);
+
+ mork_scope* key = 0; // ignore keys in map
+ morkAtomSpace* space = 0; // old val node in the map
+
+ for ( c = asi->FirstAtomSpace(ev, key, &space); c && ev->Good();
+ c = asi->NextAtomSpace(ev, key, &space) )
+ {
+ if ( space )
+ {
+ if ( space->IsAtomSpace() )
+ {
+ space->SetAtomSpaceDirty();
+ morkBookAtom* atom = 0;
+ morkAtomAidMapIter* ai = &mWriter_AtomSpaceAtomAidsIter;
+ ai->InitAtomAidMapIter(ev, &space->mAtomSpace_AtomAids);
+
+ for ( c = ai->FirstAtom(ev, &atom); c && ev->Good();
+ c = ai->NextAtom(ev, &atom) )
+ {
+ if ( atom )
+ {
+ atom->SetAtomDirty();
+ ++mWriter_TotalCount;
+ }
+ else
+ ev->NilPointerError();
+ }
+
+ ai->CloseMapIter(ev);
+ }
+ else
+ space->NonAtomSpaceTypeError(ev);
+ }
+ else
+ ev->NilPointerError();
+ }
+ }
+
+ if ( ev->Good() )
+ {
+ morkRowSpaceMapIter* rsi = &mWriter_StoreRowSpacesIter;
+ rsi->InitRowSpaceMapIter(ev, &store->mStore_RowSpaces);
+
+ mork_scope* key = 0; // ignore keys in map
+ morkRowSpace* space = 0; // old val node in the map
+
+ for ( c = rsi->FirstRowSpace(ev, key, &space); c && ev->Good();
+ c = rsi->NextRowSpace(ev, key, &space) )
+ {
+ if ( space )
+ {
+ if ( space->IsRowSpace() )
+ {
+ space->SetRowSpaceDirty();
+ if ( ev->Good() )
+ {
+#ifdef MORK_ENABLE_PROBE_MAPS
+ morkRowProbeMapIter* ri = &mWriter_RowSpaceRowsIter;
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ morkRowMapIter* ri = &mWriter_RowSpaceRowsIter;
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ ri->InitRowMapIter(ev, &space->mRowSpace_Rows);
+
+ morkRow* row = 0; // old key row in the map
+
+ for ( c = ri->FirstRow(ev, &row); c && ev->Good();
+ c = ri->NextRow(ev, &row) )
+ {
+ if ( row && row->IsRow() ) // need to dirty row?
+ {
+ if ( row->IsRowUsed() || row->IsRowDirty() )
+ {
+ row->DirtyAllRowContent(ev);
+ ++mWriter_TotalCount;
+ }
+ }
+ else
+ row->NonRowTypeWarning(ev);
+ }
+ ri->CloseMapIter(ev);
+ }
+
+ if ( ev->Good() )
+ {
+ morkTableMapIter* ti = &mWriter_RowSpaceTablesIter;
+ ti->InitTableMapIter(ev, &space->mRowSpace_Tables);
+
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+ morkTable* table = ti->FirstTable(ev);
+
+ for ( ; table && ev->Good(); table = ti->NextTable(ev) )
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+ mork_tid* tableKey = 0; // ignore keys in table map
+ morkTable* table = 0; // old key row in the map
+
+ for ( c = ti->FirstTable(ev, tableKey, &table); c && ev->Good();
+ c = ti->NextTable(ev, tableKey, &table) )
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+ {
+ if ( table && table->IsTable() ) // need to dirty table?
+ {
+ if ( table->IsTableUsed() || table->IsTableDirty() )
+ {
+ // table->DirtyAllTableContent(ev);
+ // only necessary to mark table itself dirty:
+ table->SetTableDirty();
+ table->SetTableRewrite();
+ ++mWriter_TotalCount;
+ }
+ }
+ else
+ table->NonTableTypeWarning(ev);
+ }
+ ti->CloseMapIter(ev);
+ }
+ }
+ else
+ space->NonRowSpaceTypeError(ev);
+ }
+ else
+ ev->NilPointerError();
+ }
+ }
+ }
+ else
+ this->NilWriterStoreError(ev);
+
+ return ev->Good();
+}
+
+
+mork_bool
+morkWriter::OnNothingDone(morkEnv* ev)
+{
+ mWriter_Incremental = !mWriter_NeedDirtyAll; // opposites
+
+ if (!mWriter_Store->IsStoreDirty() && !mWriter_NeedDirtyAll)
+ {
+ mWriter_Phase = morkWriter_kPhaseWritingDone;
+ return morkBool_kTrue;
+ }
+
+ // morkStream* stream = mWriter_Stream;
+ if ( mWriter_NeedDirtyAll )
+ this->DirtyAll(ev);
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhaseDirtyAllDone;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::StartGroup(morkEnv* ev)
+{
+ nsIMdbEnv *mdbev = ev->AsMdbEnv();
+ morkStream* stream = mWriter_Stream;
+ mWriter_DidStartGroup = morkBool_kTrue;
+ mWriter_DidEndGroup = morkBool_kFalse;
+
+ char buf[ 64 ];
+ char* p = buf;
+ *p++ = '@';
+ *p++ = '$';
+ *p++ = '$';
+ *p++ = '{';
+
+ mork_token groupID = mWriter_CommitGroupIdentity;
+ mork_fill idFill = ev->TokenAsHex(p, groupID);
+ mWriter_GroupBufFill = 0;
+ // ev->TokenAsHex(mWriter_GroupBuf, groupID);
+ if ( idFill < morkWriter_kGroupBufSize )
+ {
+ MORK_MEMCPY(mWriter_GroupBuf, p, idFill + 1);
+ mWriter_GroupBufFill = idFill;
+ }
+ else
+ *mWriter_GroupBuf = 0;
+
+ p += idFill;
+ *p++ = '{';
+ *p++ = '@';
+ *p = 0;
+
+ stream->PutLineBreak(ev);
+
+ morkStore* store = mWriter_Store;
+ if ( store ) // might need to capture commit group position?
+ {
+ mork_pos groupPos;
+ stream->Tell(mdbev, &groupPos);
+ if ( !store->mStore_FirstCommitGroupPos )
+ store->mStore_FirstCommitGroupPos = groupPos;
+ else if ( !store->mStore_SecondCommitGroupPos )
+ store->mStore_SecondCommitGroupPos = groupPos;
+ }
+
+ mork_size bytesWritten;
+ stream->Write(mdbev, buf, idFill + 6, &bytesWritten); // '@$${' + idFill + '{@'
+ stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::CommitGroup(morkEnv* ev)
+{
+ if ( mWriter_DidStartGroup )
+ {
+ nsIMdbEnv *mdbev = ev->AsMdbEnv();
+ mork_size bytesWritten;
+ morkStream* stream = mWriter_Stream;
+
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ stream->Putc(ev, '@');
+ stream->Putc(ev, '$');
+ stream->Putc(ev, '$');
+ stream->Putc(ev, '}');
+
+ mork_fill bufFill = mWriter_GroupBufFill;
+ if ( bufFill )
+ stream->Write(mdbev, mWriter_GroupBuf, bufFill, &bytesWritten);
+
+ stream->Putc(ev, '}');
+ stream->Putc(ev, '@');
+ stream->PutLineBreak(ev);
+
+ mWriter_LineSize = 0;
+ }
+
+ mWriter_DidStartGroup = morkBool_kFalse;
+ mWriter_DidEndGroup = morkBool_kTrue;
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::AbortGroup(morkEnv* ev)
+{
+ if ( mWriter_DidStartGroup )
+ {
+ morkStream* stream = mWriter_Stream;
+ stream->PutLineBreak(ev);
+ stream->PutStringThenNewline(ev, "@$$}~~}@");
+ mWriter_LineSize = 0;
+ }
+
+ mWriter_DidStartGroup = morkBool_kFalse;
+ mWriter_DidEndGroup = morkBool_kTrue;
+
+ return ev->Good();
+}
+
+
+mork_bool
+morkWriter::OnDirtyAllDone(morkEnv* ev)
+{
+ if ( ev->Good() )
+ {
+ nsIMdbEnv *mdbev = ev->AsMdbEnv();
+ morkStream* stream = mWriter_Stream;
+ mork_pos resultPos;
+ if ( mWriter_NeedDirtyAll ) // compress commit
+ {
+
+ stream->Seek(mdbev, 0, &resultPos); // beginning of stream
+ stream->PutStringThenNewline(ev, morkWriter_kFileHeader);
+ mWriter_LineSize = 0;
+ }
+ else // else mWriter_Incremental
+ {
+ mork_pos eos = stream->Length(ev); // length is end of stream
+ if ( ev->Good() )
+ {
+ stream->Seek(mdbev, eos, &resultPos); // goto end of stream
+ if ( eos < 128 ) // maybe need file header?
+ {
+ stream->PutStringThenNewline(ev, morkWriter_kFileHeader);
+ mWriter_LineSize = 0;
+ }
+ this->StartGroup(ev); // begin incremental transaction
+ }
+ }
+ }
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhasePutHeaderDone;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::OnPutHeaderDone(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnPutHeaderDone()");
+ mWriter_LineSize = 0;
+
+ if ( mWriter_NeedDirtyAll ) // compress commit
+ {
+ morkStore* store = mWriter_Store;
+ if ( store )
+ store->RenumberAllCollectableContent(ev);
+ else
+ this->NilWriterStoreError(ev);
+ }
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhaseRenumberAllDone;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::OnRenumberAllDone(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnRenumberAllDone()");
+ mWriter_LineSize = 0;
+
+ if ( mWriter_NeedDirtyAll ) // compress commit
+ {
+ }
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhaseStoreAtomSpaces;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::OnStoreAtomSpaces(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnStoreAtomSpaces()");
+ mWriter_LineSize = 0;
+
+ if ( mWriter_NeedDirtyAll ) // compress commit
+ {
+ }
+
+ if ( ev->Good() )
+ {
+ morkStore* store = mWriter_Store;
+ if ( store )
+ {
+ morkAtomSpace* space = store->LazyGetGroundColumnSpace(ev);
+ if ( space && space->IsAtomSpaceDirty() )
+ {
+ // stream->PutStringThenNewline(ev, "// ground column space dict:");
+
+ if ( mWriter_LineSize )
+ {
+ stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+ }
+ this->WriteAtomSpaceAsDict(ev, space);
+ space->SetAtomSpaceClean();
+ }
+ }
+ else
+ this->NilWriterStoreError(ev);
+ }
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhaseStoreRowSpacesTables;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::OnAtomSpaceAtomAids(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnAtomSpaceAtomAids()");
+ mWriter_LineSize = 0;
+
+ if ( mWriter_NeedDirtyAll ) // compress commit
+ {
+ }
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhaseStoreRowSpacesTables;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+void
+morkWriter::WriteAllStoreTables(morkEnv* ev)
+{
+ morkStore* store = mWriter_Store;
+ if ( store && ev->Good() )
+ {
+ morkRowSpaceMapIter* rsi = &mWriter_StoreRowSpacesIter;
+ rsi->InitRowSpaceMapIter(ev, &store->mStore_RowSpaces);
+
+ mork_scope* key = 0; // ignore keys in map
+ morkRowSpace* space = 0; // old val node in the map
+ mork_change* c = 0;
+
+ for ( c = rsi->FirstRowSpace(ev, key, &space); c && ev->Good();
+ c = rsi->NextRowSpace(ev, key, &space) )
+ {
+ if ( space )
+ {
+ if ( space->IsRowSpace() )
+ {
+ space->SetRowSpaceClean();
+ if ( ev->Good() )
+ {
+ morkTableMapIter* ti = &mWriter_RowSpaceTablesIter;
+ ti->InitTableMapIter(ev, &space->mRowSpace_Tables);
+
+#ifdef MORK_BEAD_OVER_NODE_MAPS
+ morkTable* table = ti->FirstTable(ev);
+
+ for ( ; table && ev->Good(); table = ti->NextTable(ev) )
+#else /*MORK_BEAD_OVER_NODE_MAPS*/
+ mork_tid* key2 = 0; // ignore keys in table map
+ morkTable* table = 0; // old key row in the map
+
+ for ( c = ti->FirstTable(ev, key2, &table); c && ev->Good();
+ c = ti->NextTable(ev, key2, &table) )
+#endif /*MORK_BEAD_OVER_NODE_MAPS*/
+ {
+ if ( table && table->IsTable() )
+ {
+ if ( table->IsTableDirty() )
+ {
+ mWriter_BeVerbose =
+ ( ev->mEnv_BeVerbose || table->IsTableVerbose() );
+
+ if ( this->PutTableDict(ev, table) )
+ this->PutTable(ev, table);
+
+ table->SetTableClean(ev);
+ mWriter_BeVerbose = ev->mEnv_BeVerbose;
+ }
+ }
+ else
+ table->NonTableTypeWarning(ev);
+ }
+ ti->CloseMapIter(ev);
+ }
+ if ( ev->Good() )
+ {
+ mWriter_TableRowScope = 0; // ensure no table context now
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ morkRowProbeMapIter* ri = &mWriter_RowSpaceRowsIter;
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ morkRowMapIter* ri = &mWriter_RowSpaceRowsIter;
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+ ri->InitRowMapIter(ev, &space->mRowSpace_Rows);
+
+ morkRow* row = 0; // old row in the map
+
+ for ( c = ri->FirstRow(ev, &row); c && ev->Good();
+ c = ri->NextRow(ev, &row) )
+ {
+ if ( row && row->IsRow() )
+ {
+ // later we should also check that table use count is nonzero:
+ if ( row->IsRowDirty() ) // && row->IsRowUsed() ??
+ {
+ mWriter_BeVerbose = ev->mEnv_BeVerbose;
+ if ( this->PutRowDict(ev, row) )
+ {
+ if ( ev->Good() && mWriter_DidStartDict )
+ {
+ this->EndDict(ev);
+ if ( mWriter_LineSize < 32 && ev->Good() )
+ mWriter_SuppressDirtyRowNewline = morkBool_kTrue;
+ }
+
+ if ( ev->Good() )
+ this->PutRow(ev, row);
+ }
+ mWriter_BeVerbose = ev->mEnv_BeVerbose;
+ }
+ }
+ else
+ row->NonRowTypeWarning(ev);
+ }
+ ri->CloseMapIter(ev);
+ }
+ }
+ else
+ space->NonRowSpaceTypeError(ev);
+ }
+ else
+ ev->NilPointerError();
+ }
+ }
+}
+
+mork_bool
+morkWriter::OnStoreRowSpacesTables(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnStoreRowSpacesTables()");
+ mWriter_LineSize = 0;
+
+ if ( mWriter_NeedDirtyAll ) // compress commit
+ {
+ }
+
+ // later we'll break this up, but today we'll write all in one shot:
+ this->WriteAllStoreTables(ev);
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhaseStoreRowSpacesRows;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::OnRowSpaceTables(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnRowSpaceTables()");
+ mWriter_LineSize = 0;
+
+ if ( mWriter_NeedDirtyAll ) // compress commit
+ {
+ }
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhaseStoreRowSpacesRows;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::OnTableRowArray(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnTableRowArray()");
+ mWriter_LineSize = 0;
+
+ if ( mWriter_NeedDirtyAll ) // compress commit
+ {
+ }
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhaseStoreRowSpacesRows;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::OnStoreRowSpacesRows(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnStoreRowSpacesRows()");
+ mWriter_LineSize = 0;
+
+ if ( mWriter_NeedDirtyAll ) // compress commit
+ {
+ }
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhaseContentDone;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::OnRowSpaceRows(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnRowSpaceRows()");
+ mWriter_LineSize = 0;
+
+ if ( mWriter_NeedDirtyAll ) // compress commit
+ {
+ }
+
+ if ( ev->Good() )
+ mWriter_Phase = morkWriter_kPhaseContentDone;
+ else
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop on error
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::OnContentDone(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+
+ // if ( mWriter_NeedDirtyAll )
+ // stream->PutStringThenNewline(ev, "// OnContentDone()");
+ mWriter_LineSize = 0;
+
+ if ( mWriter_Incremental )
+ {
+ if ( ev->Good() )
+ this->CommitGroup(ev);
+ else
+ this->AbortGroup(ev);
+ }
+ else if ( mWriter_Store && ev->Good() )
+ {
+ // after rewriting everything, there are no transaction groups:
+ mWriter_Store->mStore_FirstCommitGroupPos = 0;
+ mWriter_Store->mStore_SecondCommitGroupPos = 0;
+ }
+
+ stream->Flush(ev->AsMdbEnv());
+ nsIMdbFile* bud = mWriter_Bud;
+ if ( bud )
+ {
+ bud->Flush(ev->AsMdbEnv());
+ bud->BecomeTrunk(ev->AsMdbEnv());
+ nsIMdbFile_SlotStrongFile((nsIMdbFile*) 0, ev, &mWriter_Bud);
+ }
+ else if ( !mWriter_Incremental ) // should have a bud?
+ this->NilWriterBudError(ev);
+
+ mWriter_Phase = morkWriter_kPhaseWritingDone; // stop always
+ mWriter_DoneCount = mWriter_TotalCount;
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::OnWritingDone(morkEnv* ev)
+{
+ mWriter_DoneCount = mWriter_TotalCount;
+ ev->NewWarning("writing is done");
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::PutTableChange(morkEnv* ev, const morkTableChange* inChange)
+{
+ nsIMdbEnv *mdbev = ev->AsMdbEnv();
+ if ( inChange->IsAddRowTableChange() )
+ {
+ this->PutRow(ev, inChange->mTableChange_Row ); // row alone means add
+ }
+ else if ( inChange->IsCutRowTableChange() )
+ {
+ mWriter_Stream->Putc(ev, '-'); // prefix '-' indicates cut row
+ ++mWriter_LineSize;
+ this->PutRow(ev, inChange->mTableChange_Row );
+ }
+ else if ( inChange->IsMoveRowTableChange() )
+ {
+ this->PutRow(ev, inChange->mTableChange_Row );
+ char buf[ 64 ];
+ char* p = buf;
+ *p++ = '!'; // for moves, position is indicated by prefix '!'
+ mork_size posSize = ev->TokenAsHex(p, inChange->mTableChange_Pos);
+ p += posSize;
+ *p++ = ' ';
+ mork_size bytesWritten;
+ mWriter_Stream->Write(mdbev, buf, posSize + 2, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+ }
+ else
+ inChange->UnknownChangeError(ev);
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::PutTable(morkEnv* ev, morkTable* ioTable)
+{
+ if ( ev->Good() )
+ this->StartTable(ev, ioTable);
+
+ if ( ev->Good() )
+ {
+ if ( ioTable->IsTableRewrite() || mWriter_NeedDirtyAll )
+ {
+ morkArray* array = &ioTable->mTable_RowArray; // vector of rows
+ mork_fill fill = array->mArray_Fill; // count of rows
+ morkRow** rows = (morkRow**) array->mArray_Slots;
+ if ( rows && fill )
+ {
+ morkRow** end = rows + fill;
+ while ( rows < end && ev->Good() )
+ {
+ morkRow* r = *rows++; // next row to consider
+ this->PutRow(ev, r);
+ }
+ }
+ }
+ else // incremental write only table changes
+ {
+ morkList* list = &ioTable->mTable_ChangeList;
+ morkNext* next = list->GetListHead();
+ while ( next && ev->Good() )
+ {
+ this->PutTableChange(ev, (morkTableChange*) next);
+ next = next->GetNextLink();
+ }
+ }
+ }
+
+ if ( ev->Good() )
+ this->EndTable(ev);
+
+ ioTable->SetTableClean(ev); // note this also cleans change list
+ mWriter_TableRowScope = 0;
+
+ ++mWriter_DoneCount;
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::PutTableDict(morkEnv* ev, morkTable* ioTable)
+{
+ morkRowSpace* space = ioTable->mTable_RowSpace;
+ mWriter_TableRowScope = space->SpaceScope();
+ mWriter_TableForm = 0; // (f=iso-8859-1)
+ mWriter_TableAtomScope = 'v'; // (a=v)
+ mWriter_TableKind = ioTable->mTable_Kind;
+
+ mWriter_RowForm = mWriter_TableForm;
+ mWriter_RowAtomScope = mWriter_TableAtomScope;
+ mWriter_RowScope = mWriter_TableRowScope;
+
+ mWriter_DictForm = mWriter_TableForm;
+ mWriter_DictAtomScope = mWriter_TableAtomScope;
+
+ // if ( ev->Good() )
+ // this->StartDict(ev); // delay as long as possible
+
+ if ( ev->Good() )
+ {
+ morkRow* r = ioTable->mTable_MetaRow;
+ if ( r )
+ {
+ if ( r->IsRow() )
+ this->PutRowDict(ev, r);
+ else
+ r->NonRowTypeError(ev);
+ }
+ morkArray* array = &ioTable->mTable_RowArray; // vector of rows
+ mork_fill fill = array->mArray_Fill; // count of rows
+ morkRow** rows = (morkRow**) array->mArray_Slots;
+ if ( rows && fill )
+ {
+ morkRow** end = rows + fill;
+ while ( rows < end && ev->Good() )
+ {
+ r = *rows++; // next row to consider
+ if ( r && r->IsRow() )
+ this->PutRowDict(ev, r);
+ else
+ r->NonRowTypeError(ev);
+ }
+ }
+ // we may have a change for a row which is no longer in the
+ // table, but contains a cell with something not in the dictionary.
+ // So, loop through the rows in the change log, writing out any
+ // dirty dictionary elements.
+ morkList* list = &ioTable->mTable_ChangeList;
+ morkNext* next = list->GetListHead();
+ while ( next && ev->Good() )
+ {
+ r = ((morkTableChange*) next)->mTableChange_Row;
+ if ( r && r->IsRow() )
+ this->PutRowDict(ev, r);
+ next = next->GetNextLink();
+ }
+ }
+ if ( ev->Good() )
+ this->EndDict(ev);
+
+ return ev->Good();
+}
+
+void
+morkWriter::WriteTokenToTokenMetaCell(morkEnv* ev,
+ mork_token inCol, mork_token inValue)
+{
+ morkStream* stream = mWriter_Stream;
+ mork_bool isKindCol = ( morkStore_kKindColumn == inCol );
+ mork_u1 valSep = (mork_u1) (( isKindCol )? '^' : '=');
+
+ char buf[ 128 ]; // buffer for staging the two hex IDs
+ char* p = buf;
+
+ mork_size bytesWritten;
+ if ( inCol < 0x80 )
+ {
+ stream->Putc(ev, '(');
+ stream->Putc(ev, (char) inCol);
+ stream->Putc(ev, valSep);
+ }
+ else
+ {
+ *p++ = '('; // we always start with open paren
+
+ *p++ = '^'; // indicates col is hex ID
+ mork_size colSize = ev->TokenAsHex(p, inCol);
+ p += colSize;
+ *p++ = (char) valSep;
+ stream->Write(ev->AsMdbEnv(), buf, colSize + 3, &bytesWritten);
+
+ mWriter_LineSize += bytesWritten;
+ }
+
+ if ( isKindCol )
+ {
+ p = buf;
+ mork_size valSize = ev->TokenAsHex(p, inValue);
+ p += valSize;
+ *p++ = ':';
+ *p++ = 'c';
+ *p++ = ')';
+ stream->Write(ev->AsMdbEnv(), buf, valSize + 3, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+ }
+ else
+ {
+ this->IndentAsNeeded(ev, morkWriter_kTableMetaCellValueDepth);
+ mdbYarn* yarn = &mWriter_ColYarn;
+ // mork_u1* yarnBuf = (mork_u1*) yarn->mYarn_Buf;
+ mWriter_Store->TokenToString(ev, inValue, yarn);
+ this->WriteYarn(ev, yarn);
+ stream->Putc(ev, ')');
+ ++mWriter_LineSize;
+ }
+
+ // mork_fill fill = yarn->mYarn_Fill;
+ // yarnBuf[ fill ] = ')'; // append terminator
+ // mWriter_LineSize += stream->Write(ev, yarnBuf, fill + 1); // +1 for ')'
+}
+
+void
+morkWriter::WriteStringToTokenDictCell(morkEnv* ev,
+ const char* inCol, mork_token inValue)
+ // Note inCol should begin with '(' and end with '=', with col in between.
+{
+ morkStream* stream = mWriter_Stream;
+ mWriter_LineSize += stream->PutString(ev, inCol);
+
+ this->IndentAsNeeded(ev, morkWriter_kDictMetaCellValueDepth);
+ mdbYarn* yarn = &mWriter_ColYarn;
+ // mork_u1* yarnBuf = (mork_u1*) yarn->mYarn_Buf;
+ mWriter_Store->TokenToString(ev, inValue, yarn);
+ this->WriteYarn(ev, yarn);
+ stream->Putc(ev, ')');
+ ++mWriter_LineSize;
+
+ // mork_fill fill = yarn->mYarn_Fill;
+ // yarnBuf[ fill ] = ')'; // append terminator
+ // mWriter_LineSize += stream->Write(ev, yarnBuf, fill + 1); // +1 for ')'
+}
+
+void
+morkWriter::ChangeDictAtomScope(morkEnv* ev, mork_scope inScope)
+{
+ if ( inScope != mWriter_DictAtomScope )
+ {
+ ev->NewWarning("unexpected atom scope change");
+
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+
+ char buf[ 128 ]; // buffer for staging the two hex IDs
+ char* p = buf;
+ *p++ = '<'; // we always start with open paren
+ *p++ = '('; // we always start with open paren
+ *p++ = (char) morkStore_kAtomScopeColumn;
+
+ mork_size scopeSize = 1; // default to one byte
+ if ( inScope >= 0x80 )
+ {
+ *p++ = '^'; // indicates col is hex ID
+ scopeSize = ev->TokenAsHex(p, inScope);
+ p += scopeSize;
+ }
+ else
+ {
+ *p++ = '='; // indicates col is imm byte
+ *p++ = (char) (mork_u1) inScope;
+ }
+
+ *p++ = ')';
+ *p++ = '>';
+ *p = 0;
+
+ mork_size pending = scopeSize + 6;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasDepth);
+ mork_size bytesWritten;
+
+ stream->Write(ev->AsMdbEnv(), buf, pending, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ mWriter_DictAtomScope = inScope;
+ }
+}
+
+void
+morkWriter::ChangeRowForm(morkEnv* ev, mork_cscode inNewForm)
+{
+ if ( inNewForm != mWriter_RowForm )
+ {
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+
+ char buf[ 128 ]; // buffer for staging the two hex IDs
+ char* p = buf;
+ *p++ = '['; // we always start with open bracket
+ *p++ = '('; // we always start with open paren
+ *p++ = (char) morkStore_kFormColumn;
+
+ mork_size formSize = 1; // default to one byte
+ if (! morkCh_IsValue(inNewForm))
+ {
+ *p++ = '^'; // indicates col is hex ID
+ formSize = ev->TokenAsHex(p, inNewForm);
+ p += formSize;
+ }
+ else
+ {
+ *p++ = '='; // indicates col is imm byte
+ *p++ = (char) (mork_u1) inNewForm;
+ }
+
+ *p++ = ')';
+ *p++ = ']';
+ *p = 0;
+
+ mork_size pending = formSize + 6;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
+ mork_size bytesWritten;
+ stream->Write(ev->AsMdbEnv(), buf, pending, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ mWriter_RowForm = inNewForm;
+ }
+}
+
+void
+morkWriter::ChangeDictForm(morkEnv* ev, mork_cscode inNewForm)
+{
+ if ( inNewForm != mWriter_DictForm )
+ {
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+
+ char buf[ 128 ]; // buffer for staging the two hex IDs
+ char* p = buf;
+ *p++ = '<'; // we always start with open angle
+ *p++ = '('; // we always start with open paren
+ *p++ = (char) morkStore_kFormColumn;
+
+ mork_size formSize = 1; // default to one byte
+ if (! morkCh_IsValue(inNewForm))
+ {
+ *p++ = '^'; // indicates col is hex ID
+ formSize = ev->TokenAsHex(p, inNewForm);
+ p += formSize;
+ }
+ else
+ {
+ *p++ = '='; // indicates col is imm byte
+ *p++ = (char) (mork_u1) inNewForm;
+ }
+
+ *p++ = ')';
+ *p++ = '>';
+ *p = 0;
+
+ mork_size pending = formSize + 6;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasDepth);
+
+ mork_size bytesWritten;
+ stream->Write(ev->AsMdbEnv(), buf, pending, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ mWriter_DictForm = inNewForm;
+ }
+}
+
+void
+morkWriter::StartDict(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_DidStartDict )
+ {
+ stream->Putc(ev, '>'); // end dict
+ ++mWriter_LineSize;
+ }
+ mWriter_DidStartDict = morkBool_kTrue;
+ mWriter_DidEndDict = morkBool_kFalse;
+
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+
+ if ( mWriter_TableRowScope ) // blank line before table's dict?
+ stream->PutLineBreak(ev);
+
+ if ( mWriter_DictForm || mWriter_DictAtomScope != 'v' )
+ {
+ stream->Putc(ev, '<');
+ stream->Putc(ev, ' ');
+ stream->Putc(ev, '<');
+ mWriter_LineSize = 3;
+ if ( mWriter_DictForm )
+ this->WriteStringToTokenDictCell(ev, "(f=", mWriter_DictForm);
+ if ( mWriter_DictAtomScope != 'v' )
+ this->WriteStringToTokenDictCell(ev, "(a=", mWriter_DictAtomScope);
+
+ stream->Putc(ev, '>');
+ ++mWriter_LineSize;
+
+ mWriter_LineSize = stream->PutIndent(ev, morkWriter_kDictAliasDepth);
+ }
+ else
+ {
+ stream->Putc(ev, '<');
+ // stream->Putc(ev, ' ');
+ ++mWriter_LineSize;
+ }
+}
+
+void
+morkWriter::EndDict(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_DidStartDict )
+ {
+ stream->Putc(ev, '>'); // end dict
+ ++mWriter_LineSize;
+ }
+ mWriter_DidStartDict = morkBool_kFalse;
+ mWriter_DidEndDict = morkBool_kTrue;
+}
+
+void
+morkWriter::StartTable(morkEnv* ev, morkTable* ioTable)
+{
+ mdbOid toid; // to receive table oid
+ ioTable->GetTableOid(ev, &toid);
+
+ if ( ev->Good() )
+ {
+ morkStream* stream = mWriter_Stream;
+ if ( mWriter_LineSize )
+ stream->PutLineBreak(ev);
+ mWriter_LineSize = 0;
+ // stream->PutLineBreak(ev);
+
+ char buf[ 64 + 16 ]; // buffer for staging hex
+ char* p = buf;
+ *p++ = '{'; // punct 1
+ mork_size punctSize = (mWriter_BeVerbose) ? 10 : 3; // counting "{ {/*r=*/ "
+
+ if ( ioTable->IsTableRewrite() && mWriter_Incremental )
+ {
+ *p++ = '-';
+ ++punctSize; // counting '-' // punct ++
+ ++mWriter_LineSize;
+ }
+ mork_size oidSize = ev->OidAsHex(p, toid);
+ p += oidSize;
+ *p++ = ' '; // punct 2
+ *p++ = '{'; // punct 3
+ if (mWriter_BeVerbose)
+ {
+
+ *p++ = '/'; // punct=4
+ *p++ = '*'; // punct=5
+ *p++ = 'r'; // punct=6
+ *p++ = '='; // punct=7
+
+ mork_token tableUses = (mork_token) ioTable->mTable_GcUses;
+ mork_size usesSize = ev->TokenAsHex(p, tableUses);
+ punctSize += usesSize;
+ p += usesSize;
+
+ *p++ = '*'; // punct=8
+ *p++ = '/'; // punct=9
+ *p++ = ' '; // punct=10
+ }
+ mork_size bytesWritten;
+
+ stream->Write(ev->AsMdbEnv(), buf, oidSize + punctSize, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ mork_kind tk = mWriter_TableKind;
+ if ( tk )
+ {
+ this->IndentAsNeeded(ev, morkWriter_kTableMetaCellDepth);
+ this->WriteTokenToTokenMetaCell(ev, morkStore_kKindColumn, tk);
+ }
+
+ stream->Putc(ev, '('); // start 's' col cell
+ stream->Putc(ev, 's'); // column
+ stream->Putc(ev, '='); // column
+ mWriter_LineSize += 3;
+
+ int prio = (int) ioTable->mTable_Priority;
+ if ( prio > 9 ) // need to force down to max decimal digit?
+ prio = 9;
+ prio += '0'; // add base digit zero
+ stream->Putc(ev, prio); // priority: (s=0
+ ++mWriter_LineSize;
+
+ if ( ioTable->IsTableUnique() )
+ {
+ stream->Putc(ev, 'u'); // (s=0u
+ ++mWriter_LineSize;
+ }
+ if ( ioTable->IsTableVerbose() )
+ {
+ stream->Putc(ev, 'v'); // (s=0uv
+ ++mWriter_LineSize;
+ }
+
+ // stream->Putc(ev, ':'); // (s=0uv:
+ // stream->Putc(ev, 'c'); // (s=0uv:c
+ stream->Putc(ev, ')'); // end 's' col cell (s=0uv:c)
+ mWriter_LineSize += 1; // maybe 3 if we add ':' and 'c'
+
+ morkRow* r = ioTable->mTable_MetaRow;
+ if ( r )
+ {
+ if ( r->IsRow() )
+ {
+ mWriter_SuppressDirtyRowNewline = morkBool_kTrue;
+ this->PutRow(ev, r);
+ }
+ else
+ r->NonRowTypeError(ev);
+ }
+
+ stream->Putc(ev, '}'); // end meta
+ ++mWriter_LineSize;
+
+ if ( mWriter_LineSize < mWriter_MaxIndent )
+ {
+ stream->Putc(ev, ' '); // nice white space
+ ++mWriter_LineSize;
+ }
+ }
+}
+
+void
+morkWriter::EndTable(morkEnv* ev)
+{
+ morkStream* stream = mWriter_Stream;
+ stream->Putc(ev, '}'); // end table
+ ++mWriter_LineSize;
+
+ mWriter_TableAtomScope = 'v'; // (a=v)
+}
+
+mork_bool
+morkWriter::PutRowDict(morkEnv* ev, morkRow* ioRow)
+{
+ mWriter_RowForm = mWriter_TableForm;
+
+ morkCell* cells = ioRow->mRow_Cells;
+ if ( cells )
+ {
+ morkStream* stream = mWriter_Stream;
+ mdbYarn yarn; // to ref content inside atom
+ char buf[ 64 ]; // buffer for staging the dict alias hex ID
+ char* idBuf = buf + 1; // where the id always starts
+ buf[ 0 ] = '('; // we always start with open paren
+
+ morkCell* end = cells + ioRow->mRow_Length;
+ --cells; // prepare for preincrement:
+ while ( ++cells < end && ev->Good() )
+ {
+ morkAtom* atom = cells->GetAtom();
+ if ( atom && atom->IsAtomDirty() )
+ {
+ if ( atom->IsBook() ) // is it possible to write atom ID?
+ {
+ if ( !this->DidStartDict() )
+ {
+ this->StartDict(ev);
+ if ( ev->Bad() )
+ break;
+ }
+ atom->SetAtomClean(); // neutralize change
+
+ this->IndentAsNeeded(ev, morkWriter_kDictAliasDepth);
+ morkBookAtom* ba = (morkBookAtom*) atom;
+ mork_size size = ev->TokenAsHex(idBuf, ba->mBookAtom_Id);
+ mork_size bytesWritten;
+ stream->Write(ev->AsMdbEnv(), buf, size+1, &bytesWritten); // '('
+ mWriter_LineSize += bytesWritten;
+
+ if ( morkAtom::AliasYarn(atom, &yarn) )
+ {
+ mork_scope atomScope = atom->GetBookAtomSpaceScope(ev);
+ if ( atomScope && atomScope != mWriter_DictAtomScope )
+ this->ChangeDictAtomScope(ev, atomScope);
+
+ if ( mWriter_DidStartDict && yarn.mYarn_Form != mWriter_DictForm )
+ this->ChangeDictForm(ev, yarn.mYarn_Form);
+
+ mork_size pending = yarn.mYarn_Fill + morkWriter_kYarnEscapeSlop + 1;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kDictAliasValueDepth);
+
+ stream->Putc(ev, '='); // start value
+ ++mWriter_LineSize;
+
+ this->WriteYarn(ev, &yarn);
+
+ stream->Putc(ev, ')'); // end value
+ ++mWriter_LineSize;
+ }
+ else
+ atom->BadAtomKindError(ev);
+
+ ++mWriter_DoneCount;
+ }
+ }
+ }
+ }
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::IsYarnAllValue(const mdbYarn* inYarn)
+{
+ mork_fill fill = inYarn->mYarn_Fill;
+ const mork_u1* buf = (const mork_u1*) inYarn->mYarn_Buf;
+ const mork_u1* end = buf + fill;
+ --buf; // prepare for preincrement
+ while ( ++buf < end )
+ {
+ mork_ch c = *buf;
+ if ( !morkCh_IsValue(c) )
+ return morkBool_kFalse;
+ }
+ return morkBool_kTrue;
+}
+
+mork_bool
+morkWriter::PutVerboseCell(morkEnv* ev, morkCell* ioCell, mork_bool inWithVal)
+{
+ morkStream* stream = mWriter_Stream;
+ morkStore* store = mWriter_Store;
+
+ mdbYarn* colYarn = &mWriter_ColYarn;
+
+ morkAtom* atom = (inWithVal)? ioCell->GetAtom() : (morkAtom*) 0;
+
+ mork_column col = ioCell->GetColumn();
+ store->TokenToString(ev, col, colYarn);
+
+ mdbYarn yarn; // to ref content inside atom
+ morkAtom::AliasYarn(atom, &yarn); // works even when atom==nil
+
+ if ( yarn.mYarn_Form != mWriter_RowForm )
+ this->ChangeRowForm(ev, yarn.mYarn_Form);
+
+ mork_size pending = yarn.mYarn_Fill + colYarn->mYarn_Fill +
+ morkWriter_kYarnEscapeSlop + 3;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
+
+ stream->Putc(ev, '('); // start cell
+ ++mWriter_LineSize;
+
+ this->WriteYarn(ev, colYarn); // column
+
+ pending = yarn.mYarn_Fill + morkWriter_kYarnEscapeSlop;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellValueDepth);
+ stream->Putc(ev, '=');
+ ++mWriter_LineSize;
+
+ this->WriteYarn(ev, &yarn); // value
+
+ stream->Putc(ev, ')'); // end cell
+ ++mWriter_LineSize;
+
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::PutVerboseRowCells(morkEnv* ev, morkRow* ioRow)
+{
+ morkCell* cells = ioRow->mRow_Cells;
+ if ( cells )
+ {
+
+ morkCell* end = cells + ioRow->mRow_Length;
+ --cells; // prepare for preincrement:
+ while ( ++cells < end && ev->Good() )
+ {
+ // note we prefer to avoid writing cells here with no value:
+ if ( cells->GetAtom() ) // does cell have any value?
+ this->PutVerboseCell(ev, cells, /*inWithVal*/ morkBool_kTrue);
+ }
+ }
+ return ev->Good();
+}
+
+
+mork_bool
+morkWriter::PutCell(morkEnv* ev, morkCell* ioCell, mork_bool inWithVal)
+{
+ morkStream* stream = mWriter_Stream;
+ char buf[ 128 ]; // buffer for staging hex ids
+ char* idBuf = buf + 2; // where the id always starts
+ buf[ 0 ] = '('; // we always start with open paren
+ buf[ 1 ] = '^'; // column is always a hex ID
+
+ mork_size colSize = 0; // the size of col hex ID
+ mork_size bytesWritten;
+
+ morkAtom* atom = (inWithVal)? ioCell->GetAtom() : (morkAtom*) 0;
+
+ mork_column col = ioCell->GetColumn();
+ char* p = idBuf;
+ colSize = ev->TokenAsHex(p, col);
+ p += colSize;
+
+ mdbYarn yarn; // to ref content inside atom
+ morkAtom::AliasYarn(atom, &yarn); // works even when atom==nil
+
+ if ( yarn.mYarn_Form != mWriter_RowForm )
+ this->ChangeRowForm(ev, yarn.mYarn_Form);
+
+ if ( atom && atom->IsBook() ) // is it possible to write atom ID?
+ {
+ this->IndentAsNeeded(ev, morkWriter_kRowCellDepth);
+ *p++ = '^';
+ morkBookAtom* ba = (morkBookAtom*) atom;
+
+ mork_size valSize = ev->TokenAsHex(p, ba->mBookAtom_Id);
+ mork_fill yarnFill = yarn.mYarn_Fill;
+ mork_bool putImmYarn = ( yarnFill <= valSize );
+ if ( putImmYarn )
+ putImmYarn = this->IsYarnAllValue(&yarn);
+
+ if ( putImmYarn ) // value no bigger than id?
+ {
+ p[ -1 ] = '='; // go back and clobber '^' with '=' instead
+ if ( yarnFill )
+ {
+ MORK_MEMCPY(p, yarn.mYarn_Buf, yarnFill);
+ p += yarnFill;
+ }
+ *p++ = ')';
+ mork_size distance = (mork_size) (p - buf);
+ stream->Write(ev->AsMdbEnv(), buf, distance, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+ }
+ else
+ {
+ p += valSize;
+ *p = ')';
+ stream->Write(ev->AsMdbEnv(), buf, colSize + valSize + 4, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+ }
+
+ if ( atom->IsAtomDirty() )
+ {
+ atom->SetAtomClean();
+ ++mWriter_DoneCount;
+ }
+ }
+ else // must write an anonymous atom
+ {
+ mork_size pending = yarn.mYarn_Fill + colSize +
+ morkWriter_kYarnEscapeSlop + 2;
+ this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
+
+ mork_size bytesWritten;
+ stream->Write(ev->AsMdbEnv(), buf, colSize + 2, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ pending -= ( colSize + 2 );
+ this->IndentOverMaxLine(ev, pending, morkWriter_kRowCellDepth);
+ stream->Putc(ev, '=');
+ ++mWriter_LineSize;
+
+ this->WriteYarn(ev, &yarn);
+ stream->Putc(ev, ')'); // end cell
+ ++mWriter_LineSize;
+ }
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::PutRowCells(morkEnv* ev, morkRow* ioRow)
+{
+ morkCell* cells = ioRow->mRow_Cells;
+ if ( cells )
+ {
+ morkCell* end = cells + ioRow->mRow_Length;
+ --cells; // prepare for preincrement:
+ while ( ++cells < end && ev->Good() )
+ {
+ // note we prefer to avoid writing cells here with no value:
+ if ( cells->GetAtom() ) // does cell have any value?
+ this->PutCell(ev, cells, /*inWithVal*/ morkBool_kTrue);
+ }
+ }
+ return ev->Good();
+}
+
+mork_bool
+morkWriter::PutRow(morkEnv* ev, morkRow* ioRow)
+{
+ if ( ioRow && ioRow->IsRow() )
+ {
+ mWriter_RowForm = mWriter_TableForm;
+
+ mork_size bytesWritten;
+ morkStream* stream = mWriter_Stream;
+ char buf[ 128 + 16 ]; // buffer for staging hex
+ char* p = buf;
+ mdbOid* roid = &ioRow->mRow_Oid;
+ mork_size ridSize = 0;
+
+ mork_scope tableScope = mWriter_TableRowScope;
+
+ if ( ioRow->IsRowDirty() )
+ {
+ if ( mWriter_SuppressDirtyRowNewline || !mWriter_LineSize )
+ mWriter_SuppressDirtyRowNewline = morkBool_kFalse;
+ else
+ {
+ if ( tableScope ) // in a table?
+ mWriter_LineSize = stream->PutIndent(ev, morkWriter_kRowDepth);
+ else
+ mWriter_LineSize = stream->PutIndent(ev, 0); // no indent
+ }
+
+// mork_rid rid = roid->mOid_Id;
+ *p++ = '['; // start row punct=1
+ mork_size punctSize = (mWriter_BeVerbose) ? 9 : 1; // counting "[ /*r=*/ "
+
+ mork_bool rowRewrite = ioRow->IsRowRewrite();
+
+ if ( rowRewrite && mWriter_Incremental )
+ {
+ *p++ = '-';
+ ++punctSize; // counting '-'
+ ++mWriter_LineSize;
+ }
+
+ if ( tableScope && roid->mOid_Scope == tableScope )
+ ridSize = ev->TokenAsHex(p, roid->mOid_Id);
+ else
+ ridSize = ev->OidAsHex(p, *roid);
+
+ p += ridSize;
+
+ if (mWriter_BeVerbose)
+ {
+ *p++ = ' '; // punct=2
+ *p++ = '/'; // punct=3
+ *p++ = '*'; // punct=4
+ *p++ = 'r'; // punct=5
+ *p++ = '='; // punct=6
+
+ mork_size usesSize = ev->TokenAsHex(p, (mork_token) ioRow->mRow_GcUses);
+ punctSize += usesSize;
+ p += usesSize;
+
+ *p++ = '*'; // punct=7
+ *p++ = '/'; // punct=8
+ *p++ = ' '; // punct=9
+ }
+ stream->Write(ev->AsMdbEnv(), buf, ridSize + punctSize, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+
+ // special case situation where row puts exactly one column:
+ if ( !rowRewrite && mWriter_Incremental && ioRow->HasRowDelta() )
+ {
+ mork_column col = ioRow->GetDeltaColumn();
+ morkCell dummy(col, morkChange_kNil, (morkAtom*) 0);
+ morkCell* cell = 0;
+
+ mork_bool withVal = ( ioRow->GetDeltaChange() != morkChange_kCut );
+
+ if ( withVal )
+ {
+ mork_pos cellPos = 0; // dummy pos
+ cell = ioRow->GetCell(ev, col, &cellPos);
+ }
+ if ( !cell )
+ cell = &dummy;
+
+ if ( mWriter_BeVerbose )
+ this->PutVerboseCell(ev, cell, withVal);
+ else
+ this->PutCell(ev, cell, withVal);
+ }
+ else // put entire row?
+ {
+ if ( mWriter_BeVerbose )
+ this->PutVerboseRowCells(ev, ioRow); // write all, verbosely
+ else
+ this->PutRowCells(ev, ioRow); // write all, hex notation
+ }
+
+ stream->Putc(ev, ']'); // end row
+ ++mWriter_LineSize;
+ }
+ else
+ {
+ this->IndentAsNeeded(ev, morkWriter_kRowDepth);
+
+ if ( tableScope && roid->mOid_Scope == tableScope )
+ ridSize = ev->TokenAsHex(p, roid->mOid_Id);
+ else
+ ridSize = ev->OidAsHex(p, *roid);
+
+ stream->Write(ev->AsMdbEnv(), buf, ridSize, &bytesWritten);
+ mWriter_LineSize += bytesWritten;
+ stream->Putc(ev, ' ');
+ ++mWriter_LineSize;
+ }
+
+ ++mWriter_DoneCount;
+
+ ioRow->SetRowClean(); // try to do this at the very last
+ }
+ else
+ ioRow->NonRowTypeWarning(ev);
+
+ return ev->Good();
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
diff --git a/components/mork/src/morkWriter.h b/components/mork/src/morkWriter.h
new file mode 100644
index 000000000..1610d47c7
--- /dev/null
+++ b/components/mork/src/morkWriter.h
@@ -0,0 +1,343 @@
+/* -*- 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/. */
+
+#ifndef _MORKWRITER_
+#define _MORKWRITER_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKMAP_
+#include "morkMap.h"
+#endif
+
+#ifndef _MORKROWMAP_
+#include "morkRowMap.h"
+#endif
+
+#ifndef _MORKTABLE_
+#include "morkTable.h"
+#endif
+
+#ifndef _MORKATOMMAP_
+#include "morkAtomMap.h"
+#endif
+
+#ifndef _MORKATOMSPACE_
+#include "morkAtomSpace.h"
+#endif
+
+#ifndef _MORKROWSPACE_
+#include "morkRowSpace.h"
+#endif
+
+#ifndef _MORKSTREAM_
+#include "morkStream.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+#define morkWriter_kStreamBufSize /*i*/ (16 * 1024) /* buffer size for stream */
+
+#define morkDerived_kWriter /*i*/ 0x5772 /* ascii 'Wr' */
+
+#define morkWriter_kPhaseNothingDone 0 /* nothing has yet been done */
+#define morkWriter_kPhaseDirtyAllDone 1 /* DirtyAll() is done */
+#define morkWriter_kPhasePutHeaderDone 2 /* PutHeader() is done */
+
+#define morkWriter_kPhaseRenumberAllDone 3 /* RenumberAll() is done */
+
+#define morkWriter_kPhaseStoreAtomSpaces 4 /*mWriter_StoreAtomSpacesIter*/
+#define morkWriter_kPhaseAtomSpaceAtomAids 5 /*mWriter_AtomSpaceAtomAidsIter*/
+
+#define morkWriter_kPhaseStoreRowSpacesTables 6 /*mWriter_StoreRowSpacesIter*/
+#define morkWriter_kPhaseRowSpaceTables 7 /*mWriter_RowSpaceTablesIter*/
+#define morkWriter_kPhaseTableRowArray 8 /*mWriter_TableRowArrayPos */
+
+#define morkWriter_kPhaseStoreRowSpacesRows 9 /*mWriter_StoreRowSpacesIter*/
+#define morkWriter_kPhaseRowSpaceRows 10 /*mWriter_RowSpaceRowsIter*/
+
+#define morkWriter_kPhaseContentDone 11 /* all content written */
+#define morkWriter_kPhaseWritingDone 12 /* everything has been done */
+
+#define morkWriter_kCountNumberOfPhases 13 /* part of mWrite_TotalCount */
+
+#define morkWriter_kMaxColumnNameSize 128 /* longest writable col name */
+
+#define morkWriter_kMaxIndent 66 /* default value for mWriter_MaxIndent */
+#define morkWriter_kMaxLine 78 /* default value for mWriter_MaxLine */
+
+#define morkWriter_kYarnEscapeSlop 4 /* guess average yarn escape overhead */
+
+#define morkWriter_kTableMetaCellDepth 4 /* */
+#define morkWriter_kTableMetaCellValueDepth 6 /* */
+
+#define morkWriter_kDictMetaCellDepth 4 /* */
+#define morkWriter_kDictMetaCellValueDepth 6 /* */
+
+#define morkWriter_kDictAliasDepth 2 /* */
+#define morkWriter_kDictAliasValueDepth 4 /* */
+
+#define morkWriter_kRowDepth 2 /* */
+#define morkWriter_kRowCellDepth 4 /* */
+#define morkWriter_kRowCellValueDepth 6 /* */
+
+#define morkWriter_kGroupBufSize 64 /* */
+
+// v=1.1 retired on 23-Mar-99 (for metainfo one char column names)
+// v=1.2 retired on 20-Apr-99 (for ":c" suffix on table kind hex refs)
+// v=1.3 retired on 20-Apr-99 (for 1CE:m instead of ill-formed 1CE:^6D)
+#define morkWriter_kFileHeader "// <!-- <mdb:mork:z v=\"1.4\"/> -->"
+
+class morkWriter : public morkNode { // row iterator
+
+// public: // slots inherited from morkObject (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+public: // state is public because the entire Mork system is private
+
+ morkStore* mWriter_Store; // weak ref to committing store
+ nsIMdbFile* mWriter_File; // strong ref to store's file
+ nsIMdbFile* mWriter_Bud; // strong ref to bud of mWriter_File
+ morkStream* mWriter_Stream; // strong ref to stream on bud file
+ nsIMdbHeap* mWriter_SlotHeap; // strong ref to slot heap
+
+ // GroupIdentity should be based on mStore_CommitGroupIdentity:
+ mork_gid mWriter_CommitGroupIdentity; // transaction ID number
+
+ // GroupBuf holds a hex version of mWriter_CommitGroupIdentity:
+ char mWriter_GroupBuf[ morkWriter_kGroupBufSize ];
+ mork_fill mWriter_GroupBufFill; // actual bytes in GroupBuf
+
+ mork_count mWriter_TotalCount; // count of all things to be written
+ mork_count mWriter_DoneCount; // count of things already written
+
+ mork_size mWriter_LineSize; // length of current line being written
+ mork_size mWriter_MaxIndent; // line size forcing a line break
+ mork_size mWriter_MaxLine; // line size forcing a value continuation
+
+ mork_cscode mWriter_TableForm; // current charset metainfo
+ mork_scope mWriter_TableAtomScope; // current atom scope
+ mork_scope mWriter_TableRowScope; // current row scope
+ mork_kind mWriter_TableKind; // current table kind
+
+ mork_cscode mWriter_RowForm; // current charset metainfo
+ mork_scope mWriter_RowAtomScope; // current atom scope
+ mork_scope mWriter_RowScope; // current row scope
+
+ mork_cscode mWriter_DictForm; // current charset metainfo
+ mork_scope mWriter_DictAtomScope; // current atom scope
+
+ mork_bool mWriter_NeedDirtyAll; // need to call DirtyAll()
+ mork_bool mWriter_Incremental; // opposite of mWriter_NeedDirtyAll
+ mork_bool mWriter_DidStartDict; // true when a dict has been started
+ mork_bool mWriter_DidEndDict; // true when a dict has been ended
+
+ mork_bool mWriter_SuppressDirtyRowNewline; // for table meta rows
+ mork_bool mWriter_DidStartGroup; // true when a group has been started
+ mork_bool mWriter_DidEndGroup; // true when a group has been ended
+ mork_u1 mWriter_Phase; // status of writing process
+
+ mork_bool mWriter_BeVerbose; // driven by env and table verbose settings:
+ // mWriter_BeVerbose equals ( ev->mEnv_BeVerbose || table->IsTableVerbose() )
+
+ mork_u1 mWriter_Pad[ 3 ]; // for u4 alignment
+
+ mork_pos mWriter_TableRowArrayPos; // index into mTable_RowArray
+
+ char mWriter_SafeNameBuf[ (morkWriter_kMaxColumnNameSize * 2) + 4 ];
+ // Note: extra four bytes in ColNameBuf means we can always append to yarn
+
+ char mWriter_ColNameBuf[ morkWriter_kMaxColumnNameSize + 4 ];
+ // Note: extra four bytes in ColNameBuf means we can always append to yarn
+
+ mdbYarn mWriter_ColYarn; // a yarn to describe space in ColNameBuf:
+ // mYarn_Buf == mWriter_ColNameBuf, mYarn_Size == morkWriter_kMaxColumnNameSize
+
+ mdbYarn mWriter_SafeYarn; // a yarn to describe space in ColNameBuf:
+ // mYarn_Buf == mWriter_SafeNameBuf, mYarn_Size == (kMaxColumnNameSize * 2)
+
+ morkAtomSpaceMapIter mWriter_StoreAtomSpacesIter; // for mStore_AtomSpaces
+ morkAtomAidMapIter mWriter_AtomSpaceAtomAidsIter; // for AtomSpace_AtomAids
+
+ morkRowSpaceMapIter mWriter_StoreRowSpacesIter; // for mStore_RowSpaces
+ morkTableMapIter mWriter_RowSpaceTablesIter; // for mRowSpace_Tables
+
+#ifdef MORK_ENABLE_PROBE_MAPS
+ morkRowProbeMapIter mWriter_RowSpaceRowsIter; // for mRowSpace_Rows
+#else /*MORK_ENABLE_PROBE_MAPS*/
+ morkRowMapIter mWriter_RowSpaceRowsIter; // for mRowSpace_Rows
+#endif /*MORK_ENABLE_PROBE_MAPS*/
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseWriter()
+ virtual ~morkWriter(); // assert that close executed earlier
+
+public: // morkWriter construction & destruction
+ morkWriter(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioHeap, morkStore* ioStore, nsIMdbFile* ioFile,
+ nsIMdbHeap* ioSlotHeap);
+ void CloseWriter(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkWriter(const morkWriter& other);
+ morkWriter& operator=(const morkWriter& other);
+
+public: // dynamic type identification
+ mork_bool IsWriter() const
+ { return IsNode() && mNode_Derived == morkDerived_kWriter; }
+// } ===== end morkNode methods =====
+
+public: // typing & errors
+ static void NonWriterTypeError(morkEnv* ev);
+ static void NilWriterStoreError(morkEnv* ev);
+ static void NilWriterBudError(morkEnv* ev);
+ static void NilWriterStreamError(morkEnv* ev);
+ static void NilWriterFileError(morkEnv* ev);
+ static void UnsupportedPhaseError(morkEnv* ev);
+
+public: // utitlities
+ void ChangeRowForm(morkEnv* ev, mork_cscode inNewForm);
+ void ChangeDictForm(morkEnv* ev, mork_cscode inNewForm);
+ void ChangeDictAtomScope(morkEnv* ev, mork_scope inScope);
+
+public: // inlines
+ mork_bool DidStartDict() const { return mWriter_DidStartDict; }
+ mork_bool DidEndDict() const { return mWriter_DidEndDict; }
+
+ void IndentAsNeeded(morkEnv* ev, mork_size inDepth)
+ {
+ if ( mWriter_LineSize > mWriter_MaxIndent )
+ mWriter_LineSize = mWriter_Stream->PutIndent(ev, inDepth);
+ }
+
+ void IndentOverMaxLine(morkEnv* ev,
+ mork_size inPendingSize, mork_size inDepth)
+ {
+ if ( mWriter_LineSize + inPendingSize > mWriter_MaxLine )
+ mWriter_LineSize = mWriter_Stream->PutIndent(ev, inDepth);
+ }
+
+public: // delayed construction
+
+ void MakeWriterStream(morkEnv* ev); // give writer a suitable stream
+
+public: // iterative/asynchronous writing
+
+ mork_bool WriteMore(morkEnv* ev); // call until IsWritingDone() is true
+
+ mork_bool IsWritingDone() const // don't call WriteMore() any longer?
+ { return mWriter_Phase == morkWriter_kPhaseWritingDone; }
+
+public: // marking all content dirty
+ mork_bool DirtyAll(morkEnv* ev);
+ // DirtyAll() visits every store sub-object and marks
+ // them dirty, including every table, row, cell, and atom. The return
+ // equals ev->Good(), to show whether any error happened. This method is
+ // intended for use in the beginning of a "compress commit" which writes
+ // all store content, whether dirty or not. We dirty everything first so
+ // that later iterations over content can mark things clean as they are
+ // written, and organize the process of serialization so that objects are
+ // written only at need (because of being dirty). Note the method can
+ // stop early when any error happens, since this will abort any commit.
+
+public: // group commit transactions
+
+ mork_bool StartGroup(morkEnv* ev);
+ mork_bool CommitGroup(morkEnv* ev);
+ mork_bool AbortGroup(morkEnv* ev);
+
+public: // phase methods
+ mork_bool OnNothingDone(morkEnv* ev);
+ mork_bool OnDirtyAllDone(morkEnv* ev);
+ mork_bool OnPutHeaderDone(morkEnv* ev);
+
+ mork_bool OnRenumberAllDone(morkEnv* ev);
+
+ mork_bool OnStoreAtomSpaces(morkEnv* ev);
+ mork_bool OnAtomSpaceAtomAids(morkEnv* ev);
+
+ mork_bool OnStoreRowSpacesTables(morkEnv* ev);
+ mork_bool OnRowSpaceTables(morkEnv* ev);
+ mork_bool OnTableRowArray(morkEnv* ev);
+
+ mork_bool OnStoreRowSpacesRows(morkEnv* ev);
+ mork_bool OnRowSpaceRows(morkEnv* ev);
+
+ mork_bool OnContentDone(morkEnv* ev);
+ mork_bool OnWritingDone(morkEnv* ev);
+
+public: // writing dict items first pass
+ mork_bool PutTableDict(morkEnv* ev, morkTable* ioTable);
+ mork_bool PutRowDict(morkEnv* ev, morkRow* ioRow);
+
+public: // writing node content second pass
+ mork_bool PutTable(morkEnv* ev, morkTable* ioTable);
+ mork_bool PutRow(morkEnv* ev, morkRow* ioRow);
+ mork_bool PutRowCells(morkEnv* ev, morkRow* ioRow);
+ mork_bool PutVerboseRowCells(morkEnv* ev, morkRow* ioRow);
+
+ mork_bool PutCell(morkEnv* ev, morkCell* ioCell, mork_bool inWithVal);
+ mork_bool PutVerboseCell(morkEnv* ev, morkCell* ioCell, mork_bool inWithVal);
+
+ mork_bool PutTableChange(morkEnv* ev, const morkTableChange* inChange);
+
+public: // other writer methods
+
+ mork_bool IsYarnAllValue(const mdbYarn* inYarn);
+
+ mork_size WriteYarn(morkEnv* ev, const mdbYarn* inYarn);
+ // return number of atom bytes written on the current line (which
+ // implies that escaped line breaks will make the size value smaller
+ // than the entire yarn's size, since only part goes on a last line).
+
+ mork_size WriteAtom(morkEnv* ev, const morkAtom* inAtom);
+ // return number of atom bytes written on the current line (which
+ // implies that escaped line breaks will make the size value smaller
+ // than the entire atom's size, since only part goes on a last line).
+
+ void WriteAllStoreTables(morkEnv* ev);
+ void WriteAtomSpaceAsDict(morkEnv* ev, morkAtomSpace* ioSpace);
+
+ void WriteTokenToTokenMetaCell(morkEnv* ev, mork_token inCol,
+ mork_token inValue);
+ void WriteStringToTokenDictCell(morkEnv* ev, const char* inCol,
+ mork_token inValue);
+ // Note inCol should begin with '(' and end with '=', with col in between.
+
+ void StartDict(morkEnv* ev);
+ void EndDict(morkEnv* ev);
+
+ void StartTable(morkEnv* ev, morkTable* ioTable);
+ void EndTable(morkEnv* ev);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakWriter(morkWriter* me,
+ morkEnv* ev, morkWriter** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongWriter(morkWriter* me,
+ morkEnv* ev, morkWriter** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKTABLEROWCURSOR_ */
diff --git a/components/mork/src/morkYarn.cpp b/components/mork/src/morkYarn.cpp
new file mode 100644
index 000000000..ce52a741e
--- /dev/null
+++ b/components/mork/src/morkYarn.cpp
@@ -0,0 +1,75 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#ifndef _MORKYARN_
+#include "morkYarn.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// ````` ````` ````` ````` `````
+// { ===== begin morkNode interface =====
+
+/*public virtual*/ void
+morkYarn::CloseMorkNode(morkEnv* ev) /*i*/ // CloseYarn() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseYarn(ev);
+ this->MarkShut();
+ }
+}
+
+/*public virtual*/
+morkYarn::~morkYarn() /*i*/ // assert CloseYarn() executed earlier
+{
+ MORK_ASSERT(mYarn_Body.mYarn_Buf==0);
+}
+
+/*public non-poly*/
+morkYarn::morkYarn(morkEnv* ev, /*i*/
+ const morkUsage& inUsage, nsIMdbHeap* ioHeap)
+ : morkNode(ev, inUsage, ioHeap)
+{
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kYarn;
+}
+
+/*public non-poly*/ void
+morkYarn::CloseYarn(morkEnv* ev) /*i*/ // called by CloseMorkNode();
+{
+ if ( this->IsNode() )
+ this->MarkShut();
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+// ````` ````` ````` ````` `````
+
+/*static*/ void
+morkYarn::NonYarnTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkYarn");
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/morkYarn.h b/components/mork/src/morkYarn.h
new file mode 100644
index 000000000..a5b38bbf3
--- /dev/null
+++ b/components/mork/src/morkYarn.h
@@ -0,0 +1,75 @@
+/* -*- 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/. */
+
+#ifndef _MORKYARN_
+#define _MORKYARN_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+#define morkDerived_kYarn /*i*/ 0x7952 /* ascii 'yR' */
+
+/*| morkYarn: a reference counted nsIMdbYarn C struct. This is for use in those
+**| few cases where single instances of reference counted buffers are needed
+**| in Mork, and we expect few enough instances that overhead is not a factor
+**| in decided whether to use such a thing.
+|*/
+class morkYarn : public morkNode { // refcounted yarn
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+public: // state is public because the entire Mork system is private
+ mdbYarn mYarn_Body;
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseYarn() only if open
+ virtual ~morkYarn(); // assert that CloseYarn() executed earlier
+
+public: // morkYarn construction & destruction
+ morkYarn(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioHeap);
+ void CloseYarn(morkEnv* ev); // called by CloseMorkNode();
+
+private: // copying is not allowed
+ morkYarn(const morkYarn& other);
+ morkYarn& operator=(const morkYarn& other);
+
+public: // dynamic type identification
+ mork_bool IsYarn() const
+ { return IsNode() && mNode_Derived == morkDerived_kYarn; }
+// } ===== end morkNode methods =====
+
+public: // typing
+ static void NonYarnTypeError(morkEnv* ev);
+
+public: // typesafe refcounting inlines calling inherited morkNode methods
+ static void SlotWeakYarn(morkYarn* me,
+ morkEnv* ev, morkYarn** ioSlot)
+ { morkNode::SlotWeakNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+
+ static void SlotStrongYarn(morkYarn* me,
+ morkEnv* ev, morkYarn** ioSlot)
+ { morkNode::SlotStrongNode((morkNode*) me, ev, (morkNode**) ioSlot); }
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKYARN_ */
diff --git a/components/mork/src/morkZone.cpp b/components/mork/src/morkZone.cpp
new file mode 100644
index 000000000..239e86bba
--- /dev/null
+++ b/components/mork/src/morkZone.cpp
@@ -0,0 +1,527 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKZONE_
+#include "morkZone.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// { ===== begin morkNode interface =====
+// public: // morkNode virtual methods
+void morkZone::CloseMorkNode(morkEnv* ev) // CloseZone() only if open
+{
+ if ( this->IsOpenNode() )
+ {
+ this->MarkClosing();
+ this->CloseZone(ev);
+ this->MarkShut();
+ }
+}
+
+morkZone::~morkZone() // assert that CloseZone() executed earlier
+{
+ MORK_ASSERT(this->IsShutNode());
+}
+
+// public: // morkMap construction & destruction
+morkZone::morkZone(morkEnv* ev, const morkUsage& inUsage,
+ nsIMdbHeap* ioNodeHeap, nsIMdbHeap* ioZoneHeap)
+: morkNode(ev, inUsage, ioNodeHeap)
+, mZone_Heap( 0 )
+, mZone_HeapVolume( 0 )
+, mZone_BlockVolume( 0 )
+, mZone_RunVolume( 0 )
+, mZone_ChipVolume( 0 )
+
+, mZone_FreeOldRunVolume( 0 )
+
+, mZone_HunkCount( 0 )
+, mZone_FreeOldRunCount( 0 )
+
+, mZone_HunkList( 0 )
+, mZone_FreeOldRunList( 0 )
+
+, mZone_At( 0 )
+, mZone_AtSize( 0 )
+
+ // morkRun* mZone_FreeRuns[ morkZone_kBuckets + 1 ];
+{
+
+ morkRun** runs = mZone_FreeRuns;
+ morkRun** end = runs + (morkZone_kBuckets + 1); // one past last slot
+ --runs; // prepare for preincrement
+ while ( ++runs < end ) // another slot in array?
+ *runs = 0; // clear all the slots
+
+ if ( ev->Good() )
+ {
+ if ( ioZoneHeap )
+ {
+ nsIMdbHeap_SlotStrongHeap(ioZoneHeap, ev, &mZone_Heap);
+ if ( ev->Good() )
+ mNode_Derived = morkDerived_kZone;
+ }
+ else
+ ev->NilPointerError();
+ }
+}
+
+void morkZone::CloseZone(morkEnv* ev) // called by CloseMorkNode()
+{
+ if ( this->IsNode() )
+ {
+ nsIMdbHeap* heap = mZone_Heap;
+ if ( heap )
+ {
+ morkHunk* hunk = 0;
+ nsIMdbEnv* mev = ev->AsMdbEnv();
+
+ morkHunk* next = mZone_HunkList;
+ while ( ( hunk = next ) != 0 )
+ {
+#ifdef morkHunk_USE_TAG_SLOT
+ if ( !hunk->HunkGoodTag() )
+ hunk->BadHunkTagWarning(ev);
+#endif /* morkHunk_USE_TAG_SLOT */
+
+ next = hunk->HunkNext();
+ heap->Free(mev, hunk);
+ }
+ }
+ nsIMdbHeap_SlotStrongHeap((nsIMdbHeap*) 0, ev, &mZone_Heap);
+ this->MarkShut();
+ }
+ else
+ this->NonNodeError(ev);
+}
+
+// } ===== end morkNode methods =====
+
+/*static*/ void
+morkZone::NonZoneTypeError(morkEnv* ev)
+{
+ ev->NewError("non morkZone");
+}
+
+/*static*/ void
+morkZone::NilZoneHeapError(morkEnv* ev)
+{
+ ev->NewError("nil mZone_Heap");
+}
+
+/*static*/ void
+morkHunk::BadHunkTagWarning(morkEnv* ev)
+{
+ ev->NewWarning("bad mHunk_Tag");
+}
+
+/*static*/ void
+morkRun::BadRunTagError(morkEnv* ev)
+{
+ ev->NewError("bad mRun_Tag");
+}
+
+/*static*/ void
+morkRun::RunSizeAlignError(morkEnv* ev)
+{
+ ev->NewError("bad RunSize() alignment");
+}
+
+// { ===== begin morkZone methods =====
+
+
+mork_size morkZone::zone_grow_at(morkEnv* ev, mork_size inNeededSize)
+{
+ mZone_At = 0; // remove any ref to current hunk
+ mZone_AtSize = 0; // zero available bytes in current hunk
+
+ mork_size runSize = 0; // actual size of a particular run
+
+ // try to find a run in old run list with at least inNeededSize bytes:
+ morkRun* run = mZone_FreeOldRunList; // cursor in list scan
+ morkRun* prev = 0; // the node before run in the list scan
+
+ while ( run ) // another run in list to check?
+ {
+ morkOldRun* oldRun = (morkOldRun*) run;
+ mork_size oldSize = oldRun->OldSize();
+ if ( oldSize >= inNeededSize ) // found one big enough?
+ {
+ runSize = oldSize;
+ break; // end while loop early
+ }
+ prev = run; // remember last position in singly linked list
+ run = run->RunNext(); // advance cursor to next node in list
+ }
+ if ( runSize && run ) // found a usable old run?
+ {
+ morkRun* next = run->RunNext();
+ if ( prev ) // another node in free list precedes run?
+ prev->RunSetNext(next); // unlink run
+ else
+ mZone_FreeOldRunList = next; // unlink run from head of list
+
+ morkOldRun *oldRun = (morkOldRun *) run;
+ oldRun->OldSetSize(runSize);
+ mZone_At = (mork_u1*) run;
+ mZone_AtSize = runSize;
+
+#ifdef morkZone_CONFIG_DEBUG
+#ifdef morkZone_CONFIG_ALIGN_8
+ mork_ip lowThree = ((mork_ip) mZone_At) & 7;
+ if ( lowThree ) // not 8 byte aligned?
+#else /*morkZone_CONFIG_ALIGN_8*/
+ mork_ip lowTwo = ((mork_ip) mZone_At) & 3;
+ if ( lowTwo ) // not 4 byte aligned?
+#endif /*morkZone_CONFIG_ALIGN_8*/
+ ev->NewWarning("mZone_At not aligned");
+#endif /*morkZone_CONFIG_DEBUG*/
+ }
+ else // need to allocate a brand new run
+ {
+ inNeededSize += 7; // allow for possible alignment padding
+ mork_size newSize = ( inNeededSize > morkZone_kNewHunkSize )?
+ inNeededSize : morkZone_kNewHunkSize;
+
+ morkHunk* hunk = this->zone_new_hunk(ev, newSize);
+ if ( hunk )
+ {
+ morkRun* hunkRun = hunk->HunkRun();
+ mork_u1* at = (mork_u1*) hunkRun->RunAsBlock();
+ mork_ip lowBits = ((mork_ip) at) & 7;
+ if ( lowBits ) // not 8 byte aligned?
+ {
+ mork_ip skip = (8 - lowBits); // skip the complement to align
+ at += skip;
+ newSize -= skip;
+ }
+ mZone_At = at;
+ mZone_AtSize = newSize;
+ }
+ }
+
+ return mZone_AtSize;
+}
+
+morkHunk* morkZone::zone_new_hunk(morkEnv* ev, mdb_size inSize) // alloc
+{
+ mdb_size hunkSize = inSize + sizeof(morkHunk);
+ void* outBlock = 0; // we are going straight to the heap:
+ mZone_Heap->Alloc(ev->AsMdbEnv(), hunkSize, &outBlock);
+ if ( outBlock )
+ {
+#ifdef morkZone_CONFIG_VOL_STATS
+ mZone_HeapVolume += hunkSize; // track all heap allocations
+#endif /* morkZone_CONFIG_VOL_STATS */
+
+ morkHunk* hunk = (morkHunk*) outBlock;
+#ifdef morkHunk_USE_TAG_SLOT
+ hunk->HunkInitTag();
+#endif /* morkHunk_USE_TAG_SLOT */
+
+ hunk->HunkSetNext(mZone_HunkList);
+ mZone_HunkList = hunk;
+ ++mZone_HunkCount;
+
+ morkRun* run = hunk->HunkRun();
+ run->RunSetSize(inSize);
+#ifdef morkRun_USE_TAG_SLOT
+ run->RunInitTag();
+#endif /* morkRun_USE_TAG_SLOT */
+
+ return hunk;
+ }
+ if ( ev->Good() ) // got this far without any error reported yet?
+ ev->OutOfMemoryError();
+ return (morkHunk*) 0;
+}
+
+void* morkZone::zone_new_chip(morkEnv* ev, mdb_size inSize) // alloc
+{
+#ifdef morkZone_CONFIG_VOL_STATS
+ mZone_BlockVolume += inSize; // sum sizes of both chips and runs
+#endif /* morkZone_CONFIG_VOL_STATS */
+
+ mork_u1* at = mZone_At;
+ mork_size atSize = mZone_AtSize; // available bytes in current hunk
+ if ( atSize >= inSize ) // current hunk can satisfy request?
+ {
+ mZone_At = at + inSize;
+ mZone_AtSize = atSize - inSize;
+ return at;
+ }
+ else if ( atSize > morkZone_kMaxHunkWaste ) // over max waste allowed?
+ {
+ morkHunk* hunk = this->zone_new_hunk(ev, inSize);
+ if ( hunk )
+ return hunk->HunkRun();
+
+ return (void*) 0; // show allocation has failed
+ }
+ else // get ourselves a new hunk for suballocation:
+ {
+ atSize = this->zone_grow_at(ev, inSize); // get a new hunk
+ }
+
+ if ( atSize >= inSize ) // current hunk can satisfy request?
+ {
+ at = mZone_At;
+ mZone_At = at + inSize;
+ mZone_AtSize = atSize - inSize;
+ return at;
+ }
+
+ if ( ev->Good() ) // got this far without any error reported yet?
+ ev->OutOfMemoryError();
+
+ return (void*) 0; // show allocation has failed
+}
+
+void* morkZone::ZoneNewChip(morkEnv* ev, mdb_size inSize) // alloc
+{
+#ifdef morkZone_CONFIG_ARENA
+
+#ifdef morkZone_CONFIG_DEBUG
+ if ( !this->IsZone() )
+ this->NonZoneTypeError(ev);
+ else if ( !mZone_Heap )
+ this->NilZoneHeapError(ev);
+#endif /*morkZone_CONFIG_DEBUG*/
+
+#ifdef morkZone_CONFIG_ALIGN_8
+ inSize += 7;
+ inSize &= ~((mork_ip) 7); // force to multiple of 8 bytes
+#else /*morkZone_CONFIG_ALIGN_8*/
+ inSize += 3;
+ inSize &= ~((mork_ip) 3); // force to multiple of 4 bytes
+#endif /*morkZone_CONFIG_ALIGN_8*/
+
+#ifdef morkZone_CONFIG_VOL_STATS
+ mZone_ChipVolume += inSize; // sum sizes of chips only
+#endif /* morkZone_CONFIG_VOL_STATS */
+
+ return this->zone_new_chip(ev, inSize);
+
+#else /*morkZone_CONFIG_ARENA*/
+ void* outBlock = 0;
+ mZone_Heap->Alloc(ev->AsMdbEnv(), inSize, &outBlock);
+ return outBlock;
+#endif /*morkZone_CONFIG_ARENA*/
+
+}
+
+// public: // ...but runs do indeed know how big they are
+void* morkZone::ZoneNewRun(morkEnv* ev, mdb_size inSize) // alloc
+{
+#ifdef morkZone_CONFIG_ARENA
+
+#ifdef morkZone_CONFIG_DEBUG
+ if ( !this->IsZone() )
+ this->NonZoneTypeError(ev);
+ else if ( !mZone_Heap )
+ this->NilZoneHeapError(ev);
+#endif /*morkZone_CONFIG_DEBUG*/
+
+ inSize += morkZone_kRoundAdd;
+ inSize &= morkZone_kRoundMask;
+ if ( inSize <= morkZone_kMaxCachedRun )
+ {
+ morkRun** bucket = mZone_FreeRuns + (inSize >> morkZone_kRoundBits);
+ morkRun* hit = *bucket;
+ if ( hit ) // cache hit?
+ {
+ *bucket = hit->RunNext();
+ hit->RunSetSize(inSize);
+ return hit->RunAsBlock();
+ }
+ }
+ mdb_size blockSize = inSize + sizeof(morkRun); // plus run overhead
+#ifdef morkZone_CONFIG_VOL_STATS
+ mZone_RunVolume += blockSize; // sum sizes of runs only
+#endif /* morkZone_CONFIG_VOL_STATS */
+ morkRun* run = (morkRun*) this->zone_new_chip(ev, blockSize);
+ if ( run )
+ {
+ run->RunSetSize(inSize);
+#ifdef morkRun_USE_TAG_SLOT
+ run->RunInitTag();
+#endif /* morkRun_USE_TAG_SLOT */
+ return run->RunAsBlock();
+ }
+
+ if ( ev->Good() ) // got this far without any error reported yet?
+ ev->OutOfMemoryError();
+
+ return (void*) 0; // indicate failed allocation
+
+#else /*morkZone_CONFIG_ARENA*/
+ void* outBlock = 0;
+ mZone_Heap->Alloc(ev->AsMdbEnv(), inSize, &outBlock);
+ return outBlock;
+#endif /*morkZone_CONFIG_ARENA*/
+}
+
+void morkZone::ZoneZapRun(morkEnv* ev, void* ioRunBlock) // free
+{
+#ifdef morkZone_CONFIG_ARENA
+
+ morkRun* run = morkRun::BlockAsRun(ioRunBlock);
+ mdb_size runSize = run->RunSize();
+#ifdef morkZone_CONFIG_VOL_STATS
+ mZone_BlockVolume -= runSize; // tracking sizes of both chips and runs
+#endif /* morkZone_CONFIG_VOL_STATS */
+
+#ifdef morkZone_CONFIG_DEBUG
+ if ( !this->IsZone() )
+ this->NonZoneTypeError(ev);
+ else if ( !mZone_Heap )
+ this->NilZoneHeapError(ev);
+ else if ( !ioRunBlock )
+ ev->NilPointerError();
+ else if ( runSize & morkZone_kRoundAdd )
+ run->RunSizeAlignError(ev);
+#ifdef morkRun_USE_TAG_SLOT
+ else if ( !run->RunGoodTag() )
+ run->BadRunTagError(ev);
+#endif /* morkRun_USE_TAG_SLOT */
+#endif /*morkZone_CONFIG_DEBUG*/
+
+ if ( runSize <= morkZone_kMaxCachedRun ) // goes into free run list?
+ {
+ morkRun** bucket = mZone_FreeRuns + (runSize >> morkZone_kRoundBits);
+ run->RunSetNext(*bucket); // push onto free run list
+ *bucket = run;
+ }
+ else // free old run list
+ {
+ run->RunSetNext(mZone_FreeOldRunList); // push onto free old run list
+ mZone_FreeOldRunList = run;
+ ++mZone_FreeOldRunCount;
+#ifdef morkZone_CONFIG_VOL_STATS
+ mZone_FreeOldRunVolume += runSize;
+#endif /* morkZone_CONFIG_VOL_STATS */
+
+ morkOldRun* oldRun = (morkOldRun*) run; // to access extra size slot
+ oldRun->OldSetSize(runSize); // so we know how big this is later
+ }
+
+#else /*morkZone_CONFIG_ARENA*/
+ mZone_Heap->Free(ev->AsMdbEnv(), ioRunBlock);
+#endif /*morkZone_CONFIG_ARENA*/
+}
+
+void* morkZone::ZoneGrowRun(morkEnv* ev, void* ioRunBlock, mdb_size inSize)
+{
+#ifdef morkZone_CONFIG_ARENA
+
+ morkRun* run = morkRun::BlockAsRun(ioRunBlock);
+ mdb_size runSize = run->RunSize();
+
+#ifdef morkZone_CONFIG_DEBUG
+ if ( !this->IsZone() )
+ this->NonZoneTypeError(ev);
+ else if ( !mZone_Heap )
+ this->NilZoneHeapError(ev);
+#endif /*morkZone_CONFIG_DEBUG*/
+
+#ifdef morkZone_CONFIG_ALIGN_8
+ inSize += 7;
+ inSize &= ~((mork_ip) 7); // force to multiple of 8 bytes
+#else /*morkZone_CONFIG_ALIGN_8*/
+ inSize += 3;
+ inSize &= ~((mork_ip) 3); // force to multiple of 4 bytes
+#endif /*morkZone_CONFIG_ALIGN_8*/
+
+ if ( inSize > runSize )
+ {
+ void* newBuf = this->ZoneNewRun(ev, inSize);
+ if ( newBuf )
+ {
+ MORK_MEMCPY(newBuf, ioRunBlock, runSize);
+ this->ZoneZapRun(ev, ioRunBlock);
+
+ return newBuf;
+ }
+ }
+ else
+ return ioRunBlock; // old size is big enough
+
+ if ( ev->Good() ) // got this far without any error reported yet?
+ ev->OutOfMemoryError();
+
+ return (void*) 0; // indicate failed allocation
+
+#else /*morkZone_CONFIG_ARENA*/
+ void* outBlock = 0;
+ mZone_Heap->Free(ev->AsMdbEnv(), ioRunBlock);
+ return outBlock;
+#endif /*morkZone_CONFIG_ARENA*/
+}
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+// { ===== begin nsIMdbHeap methods =====
+/*virtual*/ nsresult
+morkZone::Alloc(nsIMdbEnv* mev, // allocate a piece of memory
+ mdb_size inSize, // requested size of new memory block
+ void** outBlock) // memory block of inSize bytes, or nil
+{
+ nsresult outErr = NS_OK;
+ void* block = 0;
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ block = this->ZoneNewRun(ev, inSize);
+ outErr = ev->AsErr();
+ }
+ else
+ outErr = morkEnv_kOutOfMemoryError;
+
+ if ( outBlock )
+ *outBlock = block;
+
+ return outErr;
+}
+
+/*virtual*/ nsresult
+morkZone::Free(nsIMdbEnv* mev, // free block allocated earlier by Alloc()
+ void* inBlock)
+{
+ nsresult outErr = NS_OK;
+ if ( inBlock )
+ {
+ morkEnv* ev = morkEnv::FromMdbEnv(mev);
+ if ( ev )
+ {
+ this->ZoneZapRun(ev, inBlock);
+ outErr = ev->AsErr();
+ }
+ else
+ // XXX 1 is not a valid nsresult
+ outErr = static_cast<nsresult>(1);
+ }
+
+ return outErr;
+}
+
+// } ===== end nsIMdbHeap methods =====
+
diff --git a/components/mork/src/morkZone.h b/components/mork/src/morkZone.h
new file mode 100644
index 000000000..807424949
--- /dev/null
+++ b/components/mork/src/morkZone.h
@@ -0,0 +1,321 @@
+/* -*- 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/. */
+
+#ifndef _MORKZONE_
+#define _MORKZONE_ 1
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _MORKNODE_
+#include "morkNode.h"
+#endif
+
+#ifndef _MORKDEQUE_
+#include "morkDeque.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*| CONFIG_DEBUG: do paranoid debug checks if defined.
+|*/
+#ifdef MORK_DEBUG
+#define morkZone_CONFIG_DEBUG 1 /* debug paranoid if defined */
+#endif /*MORK_DEBUG*/
+
+/*| CONFIG_STATS: keep volume and usage statistics.
+|*/
+#define morkZone_CONFIG_VOL_STATS 1 /* count space used by zone instance */
+
+/*| CONFIG_ARENA: if this is defined, then the morkZone class will alloc big
+**| blocks from the zone's heap, and suballocate from these. If undefined,
+**| then morkZone will just pass all calls through to the zone's heap.
+|*/
+#ifdef MORK_ENABLE_ZONE_ARENAS
+#define morkZone_CONFIG_ARENA 1 /* be arena, if defined; otherwise no-op */
+#endif /*MORK_ENABLE_ZONE_ARENAS*/
+
+/*| CONFIG_ALIGN_8: if this is defined, then the morkZone class will give
+**| blocks 8 byte alignment instead of only 4 byte alignment.
+|*/
+#ifdef MORK_CONFIG_ALIGN_8
+#define morkZone_CONFIG_ALIGN_8 1 /* ifdef: align to 8 bytes, otherwise 4 */
+#endif /*MORK_CONFIG_ALIGN_8*/
+
+/*| CONFIG_PTR_SIZE_4: if this is defined, then the morkZone class will
+**| assume sizeof(void*) == 4, so a tag slot for padding is needed.
+|*/
+#ifdef MORK_CONFIG_PTR_SIZE_4
+#define morkZone_CONFIG_PTR_SIZE_4 1 /* ifdef: sizeof(void*) == 4 */
+#endif /*MORK_CONFIG_PTR_SIZE_4*/
+
+/*| morkZone_USE_TAG_SLOT: if this is defined, then define slot mRun_Tag
+**| in order to achieve eight byte alignment after the mRun_Next slot.
+|*/
+#if defined(morkZone_CONFIG_ALIGN_8) && defined(morkZone_CONFIG_PTR_SIZE_4)
+#define morkRun_USE_TAG_SLOT 1 /* need mRun_Tag slot inside morkRun */
+#define morkHunk_USE_TAG_SLOT 1 /* need mHunk_Tag slot inside morkHunk */
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkRun_kTag ((mork_u4) 0x6D52754E ) /* ascii 'mRuN' */
+
+/*| morkRun: structure used by morkZone for sized blocks
+|*/
+class morkRun {
+
+protected: // member variable slots
+#ifdef morkRun_USE_TAG_SLOT
+ mork_u4 mRun_Tag; // force 8 byte alignment after mRun_Next
+#endif /* morkRun_USE_TAG_SLOT */
+
+ morkRun* mRun_Next;
+
+public: // pointer interpretation of mRun_Next (when inside a list):
+ morkRun* RunNext() const { return mRun_Next; }
+ void RunSetNext(morkRun* ioNext) { mRun_Next = ioNext; }
+
+public: // size interpretation of mRun_Next (when not inside a list):
+ mork_size RunSize() const { return (mork_size) ((mork_ip) mRun_Next); }
+ void RunSetSize(mork_size inSize)
+ { mRun_Next = (morkRun*) ((mork_ip) inSize); }
+
+public: // maintenance and testing of optional tag magic signature slot:
+#ifdef morkRun_USE_TAG_SLOT
+ void RunInitTag() { mRun_Tag = morkRun_kTag; }
+ mork_bool RunGoodTag() { return ( mRun_Tag == morkRun_kTag ); }
+#endif /* morkRun_USE_TAG_SLOT */
+
+public: // conversion back and forth to inline block following run instance:
+ void* RunAsBlock() { return (((mork_u1*) this) + sizeof(morkRun)); }
+
+ static morkRun* BlockAsRun(void* ioBlock)
+ { return (morkRun*) (((mork_u1*) ioBlock) - sizeof(morkRun)); }
+
+public: // typing & errors
+ static void BadRunTagError(morkEnv* ev);
+ static void RunSizeAlignError(morkEnv* ev);
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+/*| morkOldRun: more space to record size when run is put into old free list
+|*/
+class morkOldRun : public morkRun {
+
+protected: // need another size field when mRun_Next is used for linkage:
+ mdb_size mOldRun_Size;
+
+public: // size getter/setter
+ mork_size OldSize() const { return mOldRun_Size; }
+ void OldSetSize(mork_size inSize) { mOldRun_Size = inSize; }
+
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define morkHunk_kTag ((mork_u4) 0x68556E4B ) /* ascii 'hUnK' */
+
+/*| morkHunk: structure used by morkZone for heap allocations.
+|*/
+class morkHunk {
+
+protected: // member variable slots
+
+#ifdef morkHunk_USE_TAG_SLOT
+ mork_u4 mHunk_Tag; // force 8 byte alignment after mHunk_Next
+#endif /* morkHunk_USE_TAG_SLOT */
+
+ morkHunk* mHunk_Next;
+
+ morkRun mHunk_Run;
+
+public: // setters
+ void HunkSetNext(morkHunk* ioNext) { mHunk_Next = ioNext; }
+
+public: // getters
+ morkHunk* HunkNext() const { return mHunk_Next; }
+
+ morkRun* HunkRun() { return &mHunk_Run; }
+
+public: // maintenance and testing of optional tag magic signature slot:
+#ifdef morkHunk_USE_TAG_SLOT
+ void HunkInitTag() { mHunk_Tag = morkHunk_kTag; }
+ mork_bool HunkGoodTag() { return ( mHunk_Tag == morkHunk_kTag ); }
+#endif /* morkHunk_USE_TAG_SLOT */
+
+public: // typing & errors
+ static void BadHunkTagWarning(morkEnv* ev);
+
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+/*| kNewHunkSize: the default size for a hunk, assuming we must allocate
+**| a new one whenever the free hunk list does not already have. Note this
+**| number should not be changed without also considering suitable changes
+**| in the related kMaxHunkWaste and kMinHunkSize constants.
+|*/
+#define morkZone_kNewHunkSize ((mork_size) (64 * 1024)) /* 64K per hunk */
+
+/*| kMaxFreeVolume: some number of bytes of free space in the free hunk list
+**| over which we no longer want to add more free hunks to the list, for fear
+**| of accumulating too much unused, fragmented free space. This should be a
+**| small multiple of kNewHunkSize, say about two to four times as great, to
+**| allow for no more free hunk space than fits in a handful of new hunks.
+**| This strategy will let us usefully accumulate "some" free space in the
+**| free hunk list, but without accumulating "too much" free space that way.
+|*/
+#define morkZone_kMaxFreeVolume (morkZone_kNewHunkSize * 3)
+
+/*| kMaxHunkWaste: if a current request is larger than this, and we cannot
+**| satisfy the request with the current hunk, then we just allocate the
+**| block from the heap without changing the current hunk. Basically this
+**| number represents the largest amount of memory we are willing to waste,
+**| since a block request barely less than this can cause the current hunk
+**| to be retired (with any unused space wasted) as well get a new hunk.
+|*/
+#define morkZone_kMaxHunkWaste ((mork_size) 4096) /* 1/16 kNewHunkSize */
+
+/*| kRound*: the algorithm for rounding up allocation sizes for caching
+**| in free lists works like the following. We add kRoundAdd to any size
+**| requested, and then bitwise AND with kRoundMask, and this will give us
+**| the smallest multiple of kRoundSize that is at least as large as the
+**| requested size. Then if we rightshift this number by kRoundBits, we
+**| will have the index into the mZone_FreeRuns array which will hold any
+**| cache runs of that size. So 4 bits of shift gives us a granularity
+**| of 16 bytes, so that free lists will hold successive runs that are
+**| 16 bytes greater than the next smaller run size. If we have 256 free
+**| lists of nonzero sized runs altogether, then the largest run that can
+**| be cached is 4096, or 4K (since 4096 == 16 * 256). A larger run that
+**| gets freed will go in to the free hunk list (or back to the heap).
+|*/
+#define morkZone_kRoundBits 4 /* bits to round-up size for free lists */
+#define morkZone_kRoundSize (1 << morkZone_kRoundBits)
+#define morkZone_kRoundAdd ((1 << morkZone_kRoundBits) - 1)
+#define morkZone_kRoundMask (~ ((mork_ip) morkZone_kRoundAdd))
+
+#define morkZone_kBuckets 256 /* number of distinct free lists */
+
+/*| kMaxCachedRun: the largest run that will be stored inside a free
+**| list of old zapped runs. A run larger than this cannot be put in
+**| a free list, and must be allocated from the heap at need, and put
+**| into the free hunk list when discarded.
+|*/
+#define morkZone_kMaxCachedRun (morkZone_kBuckets * morkZone_kRoundSize)
+
+#define morkDerived_kZone /*i*/ 0x5A6E /* ascii 'Zn' */
+
+/*| morkZone: a pooled memory allocator like an NSPR arena. The term 'zone'
+**| is roughly synonymous with 'heap'. I avoid calling this class a "heap"
+**| to avoid any confusion with nsIMdbHeap, and I avoid calling this class
+**| an arean to avoid confusion with NSPR usage.
+|*/
+class morkZone : public morkNode, public nsIMdbHeap {
+
+// public: // slots inherited from morkNode (meant to inform only)
+ // nsIMdbHeap* mNode_Heap;
+
+ // mork_base mNode_Base; // must equal morkBase_kNode
+ // mork_derived mNode_Derived; // depends on specific node subclass
+
+ // mork_access mNode_Access; // kOpen, kClosing, kShut, or kDead
+ // mork_usage mNode_Usage; // kHeap, kStack, kMember, kGlobal, kNone
+ // mork_able mNode_Mutable; // can this node be modified?
+ // mork_load mNode_Load; // is this node clean or dirty?
+
+ // mork_uses mNode_Uses; // refcount for strong refs
+ // mork_refs mNode_Refs; // refcount for strong refs + weak refs
+
+public: // state is public because the entire Mork system is private
+
+ nsIMdbHeap* mZone_Heap; // strong ref to heap allocating all space
+
+ mork_size mZone_HeapVolume; // total bytes allocated from heap
+ mork_size mZone_BlockVolume; // total bytes in all zone blocks
+ mork_size mZone_RunVolume; // total bytes in all zone runs
+ mork_size mZone_ChipVolume; // total bytes in all zone chips
+
+ mork_size mZone_FreeOldRunVolume; // total bytes in all used hunks
+
+ mork_count mZone_HunkCount; // total number of used hunks
+ mork_count mZone_FreeOldRunCount; // total free old runs
+
+ morkHunk* mZone_HunkList; // linked list of all used hunks
+ morkRun* mZone_FreeOldRunList; // linked list of free old runs
+
+ // note mZone_At is a byte pointer for single byte address arithmetic:
+ mork_u1* mZone_At; // current position in most recent hunk
+ mork_size mZone_AtSize; // number of bytes remaining in this hunk
+
+ // kBuckets+1 so indexes zero through kBuckets are all okay to use:
+
+ morkRun* mZone_FreeRuns[ morkZone_kBuckets + 1 ];
+ // Each piece of memory stored in list mZone_FreeRuns[ i ] has an
+ // allocation size equal to sizeof(morkRun) + (i * kRoundSize), so
+ // that callers can be given a piece of memory with (i * kRoundSize)
+ // bytes of writeable space while reserving the first sizeof(morkRun)
+ // bytes to keep track of size information for later re-use. Note
+ // that mZone_FreeRuns[ 0 ] is unused because no run will be zero
+ // bytes in size (and morkZone plans to complain about zero sizes).
+
+protected: // zone utilities
+
+ mork_size zone_grow_at(morkEnv* ev, mork_size inNeededSize);
+
+ void* zone_new_chip(morkEnv* ev, mdb_size inSize); // alloc
+ morkHunk* zone_new_hunk(morkEnv* ev, mdb_size inRunSize); // alloc
+
+// { ===== begin nsIMdbHeap methods =====
+public:
+ NS_IMETHOD Alloc(nsIMdbEnv* ev, // allocate a piece of memory
+ mdb_size inSize, // requested size of new memory block
+ void** outBlock) override; // memory block of inSize bytes, or nil
+
+ NS_IMETHOD Free(nsIMdbEnv* ev, // free block allocated earlier by Alloc()
+ void* inBlock) override;
+
+ virtual size_t GetUsedSize() override { return mZone_Heap->GetUsedSize(); }
+// } ===== end nsIMdbHeap methods =====
+
+// { ===== begin morkNode interface =====
+public: // morkNode virtual methods
+ virtual void CloseMorkNode(morkEnv* ev) override; // CloseZone() only if open
+ virtual ~morkZone(); // assert that CloseMap() executed earlier
+
+public: // morkMap construction & destruction
+ morkZone(morkEnv* ev, const morkUsage& inUsage, nsIMdbHeap* ioNodeHeap,
+ nsIMdbHeap* ioZoneHeap);
+
+ void CloseZone(morkEnv* ev); // called by CloseMorkNode()
+
+public: // dynamic type identification
+ mork_bool IsZone() const
+ { return IsNode() && mNode_Derived == morkDerived_kZone; }
+// } ===== end morkNode methods =====
+
+// { ===== begin morkZone methods =====
+public: // chips do not know how big they are...
+ void* ZoneNewChip(morkEnv* ev, mdb_size inSize); // alloc
+
+public: // ...but runs do indeed know how big they are
+ void* ZoneNewRun(morkEnv* ev, mdb_size inSize); // alloc
+ void ZoneZapRun(morkEnv* ev, void* ioRunBody); // free
+ void* ZoneGrowRun(morkEnv* ev, void* ioRunBody, mdb_size inSize); // realloc
+
+// } ===== end morkZone methods =====
+
+public: // typing & errors
+ static void NonZoneTypeError(morkEnv* ev);
+ static void NilZoneHeapError(morkEnv* ev);
+ static void BadZoneTagError(morkEnv* ev);
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _MORKZONE_ */
diff --git a/components/mork/src/moz.build b/components/mork/src/moz.build
new file mode 100644
index 000000000..efd639b9f
--- /dev/null
+++ b/components/mork/src/moz.build
@@ -0,0 +1,54 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ 'morkArray.cpp',
+ 'morkAtom.cpp',
+ 'morkAtomMap.cpp',
+ 'morkAtomSpace.cpp',
+ 'morkBead.cpp',
+ 'morkBlob.cpp',
+ 'morkBuilder.cpp',
+ 'morkCell.cpp',
+ 'morkCellObject.cpp',
+ 'morkCh.cpp',
+ 'morkConfig.cpp',
+ 'morkCursor.cpp',
+ 'morkDeque.cpp',
+ 'morkEnv.cpp',
+ 'morkFactory.cpp',
+ 'morkFile.cpp',
+ 'morkHandle.cpp',
+ 'morkIntMap.cpp',
+ 'morkMap.cpp',
+ 'morkNode.cpp',
+ 'morkNodeMap.cpp',
+ 'morkObject.cpp',
+ 'morkParser.cpp',
+ 'morkPool.cpp',
+ 'morkPortTableCursor.cpp',
+ 'morkProbeMap.cpp',
+ 'morkRow.cpp',
+ 'morkRowCellCursor.cpp',
+ 'morkRowMap.cpp',
+ 'morkRowObject.cpp',
+ 'morkRowSpace.cpp',
+ 'morkSink.cpp',
+ 'morkSpace.cpp',
+ 'morkStore.cpp',
+ 'morkStream.cpp',
+ 'morkTable.cpp',
+ 'morkTableRowCursor.cpp',
+ 'morkThumb.cpp',
+ 'morkWriter.cpp',
+ 'morkYarn.cpp',
+ 'morkZone.cpp',
+ 'orkinHeap.cpp',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += ['morkSearchRowCursor.cpp']
+
+FINAL_LIBRARY = 'mork'
+
diff --git a/components/mork/src/orkinHeap.cpp b/components/mork/src/orkinHeap.cpp
new file mode 100644
index 000000000..4a309a154
--- /dev/null
+++ b/components/mork/src/orkinHeap.cpp
@@ -0,0 +1,84 @@
+/* -*- 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/. */
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+#ifndef _ORKINHEAP_
+#include "orkinHeap.h"
+#endif
+
+#ifndef _MORKENV_
+#include "morkEnv.h"
+#endif
+
+#include "nsIMemoryReporter.h"
+
+#include <stdlib.h>
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+
+orkinHeap::orkinHeap() // does nothing
+ : mUsedSize(0)
+{
+}
+
+/*virtual*/
+orkinHeap::~orkinHeap() // does nothing
+{
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(MorkSizeOfOnAlloc)
+MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(MorkSizeOfOnFree)
+
+// { ===== begin nsIMdbHeap methods =====
+/*virtual*/ nsresult
+orkinHeap::Alloc(nsIMdbEnv* mev, // allocate a piece of memory
+ mdb_size inSize, // requested size of new memory block
+ void** outBlock) // memory block of inSize bytes, or nil
+{
+
+ MORK_USED_1(mev);
+ nsresult outErr = NS_OK;
+ void* block = malloc(inSize);
+ if ( !block )
+ outErr = morkEnv_kOutOfMemoryError;
+ else
+ mUsedSize += MorkSizeOfOnAlloc(block);
+
+ MORK_ASSERT(outBlock);
+ if ( outBlock )
+ *outBlock = block;
+ return outErr;
+}
+
+/*virtual*/ nsresult
+orkinHeap::Free(nsIMdbEnv* mev, // free block allocated earlier by Alloc()
+ void* inBlock)
+{
+ MORK_USED_1(mev);
+ MORK_ASSERT(inBlock);
+ if ( inBlock )
+ {
+ mUsedSize -= MorkSizeOfOnFree(inBlock);
+ free(inBlock);
+ }
+ return NS_OK;
+}
+
+size_t
+orkinHeap::GetUsedSize()
+{
+ return mUsedSize;
+}
+// } ===== end nsIMdbHeap methods =====
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
diff --git a/components/mork/src/orkinHeap.h b/components/mork/src/orkinHeap.h
new file mode 100644
index 000000000..a3c14d295
--- /dev/null
+++ b/components/mork/src/orkinHeap.h
@@ -0,0 +1,52 @@
+/* -*- 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/. */
+
+#ifndef _ORKINHEAP_
+#define _ORKINHEAP_ 1
+
+#ifndef _MDB_
+#include "mdb.h"
+#endif
+
+#ifndef _MORK_
+#include "mork.h"
+#endif
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#define orkinHeap_kTag 0x68456150 /* ascii 'hEaP' */
+
+/*| orkinHeap:
+|*/
+class orkinHeap : public nsIMdbHeap { //
+protected:
+ size_t mUsedSize;
+
+public:
+ orkinHeap(); // does nothing
+ virtual ~orkinHeap(); // does nothing
+
+private: // copying is not allowed
+ orkinHeap(const orkinHeap& other);
+ orkinHeap& operator=(const orkinHeap& other);
+
+public:
+
+// { ===== begin nsIMdbHeap methods =====
+ NS_IMETHOD Alloc(nsIMdbEnv* ev, // allocate a piece of memory
+ mdb_size inSize, // requested size of new memory block
+ void** outBlock); // memory block of inSize bytes, or nil
+
+ NS_IMETHOD Free(nsIMdbEnv* ev, // free block allocated earlier by Alloc()
+ void* inBlock);
+
+ virtual size_t GetUsedSize();
+// } ===== end nsIMdbHeap methods =====
+
+};
+
+//3456789_123456789_123456789_123456789_123456789_123456789_123456789_123456789
+
+#endif /* _ORKINHEAP_ */
diff --git a/components/moz.build b/components/moz.build
new file mode 100644
index 000000000..f91cb7ffc
--- /dev/null
+++ b/components/moz.build
@@ -0,0 +1,123 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+DIRS += [
+ 'aboutcache',
+ 'aboutcheckerboard',
+ 'aboutmemory',
+ 'aboutperformance',
+ 'addoncompat',
+ 'addons',
+ 'alerts',
+ 'apppicker',
+ 'appshell',
+ 'asyncshutdown',
+ 'autocomplete',
+ 'blocklist',
+ 'bindings',
+ 'captivedetect',
+ 'commandlines',
+ 'console',
+ 'contentprefs',
+ 'permissions',
+ 'crashmonitor',
+ 'downloads',
+ 'directory',
+ 'exthelper',
+ 'filewatcher',
+ 'finalizationwitness',
+ 'formautofill',
+ 'find',
+ 'htmlfive',
+ 'htmlparser',
+ 'gfx',
+ 'global',
+ 'handling',
+ 'jar',
+ 'jsdebugger',
+ 'jsdownloads',
+ 'jsinspector',
+ 'lz4',
+ 'mediasniffer',
+ 'microformats',
+ 'mozintl',
+ 'mozprotocol',
+ 'narrate',
+ 'osfile',
+ 'parentalcontrols',
+ 'passwordmgr',
+ 'perf',
+ 'perfmonitoring',
+ 'places',
+ 'pluginproblem',
+ 'preferences',
+ 'printing',
+ 'privatebrowsing',
+ 'processsingleton',
+ 'profile',
+ 'promiseworker',
+ 'prompts',
+ 'proxy',
+ 'rdf',
+ 'reader',
+ 'remotebrowserutils',
+ 'reflect',
+ 'satchel',
+ 'scache',
+ 'startup',
+ 'statusfilter',
+ 'storage',
+ 'terminator',
+ 'thumbnails',
+ 'timermanager',
+ 'tooltiptext',
+ 'typeaheadfind',
+ 'uriloader',
+ 'utils',
+ 'urlformatter',
+ 'viewconfig',
+ 'viewsource',
+ 'webbrowser',
+ 'windowcreator',
+ 'windowds',
+ 'windowwatcher',
+ 'workerloader',
+ 'xmlparser',
+ 'xulstore'
+]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ DIRS += ['filepicker', 'gservice']
+
+if CONFIG['MOZ_PREF_EXTENSIONS']:
+ DIRS += ['autoconfig']
+
+if CONFIG['BUILD_CTYPES']:
+ DIRS += ['ctypes']
+
+if CONFIG['MOZ_FEEDS']:
+ DIRS += ['feeds']
+
+if CONFIG['MOZ_JETPACK']:
+ DIRS += ['jetpack']
+
+if CONFIG['MOZ_MAILNEWS']:
+ DIRS += ['mork']
+
+if CONFIG['MOZ_ENABLE_XREMOTE']:
+ DIRS += ['remote']
+
+if CONFIG['MOZ_TOOLKIT_SEARCH']:
+ DIRS += ['search']
+
+if CONFIG['MOZ_SERVICES_SYNC']:
+ DIRS += ['weave']
+
+DIRS += ['build']
+
+EXTRA_COMPONENTS += [
+ 'nsDefaultCLH.js',
+ 'nsDefaultCLH.manifest',
+]
diff --git a/components/mozintl/MozIntl.cpp b/components/mozintl/MozIntl.cpp
new file mode 100644
index 000000000..9c61c73a6
--- /dev/null
+++ b/components/mozintl/MozIntl.cpp
@@ -0,0 +1,100 @@
+/* -*- 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 "MozIntl.h"
+#include "jswrapper.h"
+#include "mozilla/ModuleUtils.h"
+
+#define MOZ_MOZINTL_CID \
+ { 0x83f8f991, 0x6b81, 0x4dd8, { 0xa0, 0x93, 0x72, 0x0b, 0xfb, 0x67, 0x4d, 0x38 } }
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(MozIntl, mozIMozIntl)
+
+MozIntl::MozIntl()
+{
+}
+
+MozIntl::~MozIntl()
+{
+}
+
+NS_IMETHODIMP
+MozIntl::AddGetCalendarInfo(JS::Handle<JS::Value> val, JSContext* cx)
+{
+ if (!val.isObject()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JS::Rooted<JSObject*> realIntlObj(cx, js::CheckedUnwrap(&val.toObject()));
+ if (!realIntlObj) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JSAutoCompartment ac(cx, realIntlObj);
+
+ static const JSFunctionSpec funcs[] = {
+ JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
+ JS_FS_END
+ };
+
+ if (!JS_DefineFunctions(cx, realIntlObj, funcs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MozIntl::AddGetDisplayNames(JS::Handle<JS::Value> val, JSContext* cx)
+{
+ if (!val.isObject()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JS::Rooted<JSObject*> realIntlObj(cx, js::CheckedUnwrap(&val.toObject()));
+ if (!realIntlObj) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ JSAutoCompartment ac(cx, realIntlObj);
+
+ static const JSFunctionSpec funcs[] = {
+ JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
+ JS_FS_END
+ };
+
+ if (!JS_DefineFunctions(cx, realIntlObj, funcs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(MozIntl)
+NS_DEFINE_NAMED_CID(MOZ_MOZINTL_CID);
+
+static const Module::CIDEntry kMozIntlCIDs[] = {
+ { &kMOZ_MOZINTL_CID, false, nullptr, MozIntlConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kMozIntlContracts[] = {
+ { "@mozilla.org/mozintl;1", &kMOZ_MOZINTL_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kMozIntlModule = {
+ mozilla::Module::kVersion,
+ kMozIntlCIDs,
+ kMozIntlContracts,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr
+};
+
+NSMODULE_DEFN(mozMozIntlModule) = &kMozIntlModule;
diff --git a/components/mozintl/MozIntl.h b/components/mozintl/MozIntl.h
new file mode 100644
index 000000000..00c10ed19
--- /dev/null
+++ b/components/mozintl/MozIntl.h
@@ -0,0 +1,22 @@
+/* -*- 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 "mozIMozIntl.h"
+
+namespace mozilla {
+
+class MozIntl final : public mozIMozIntl
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZIMOZINTL
+
+ MozIntl();
+
+private:
+ ~MozIntl();
+};
+
+} // namespace mozilla
diff --git a/components/mozintl/moz.build b/components/mozintl/moz.build
new file mode 100644
index 000000000..53d3ad968
--- /dev/null
+++ b/components/mozintl/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['mozIMozIntl.idl']
+
+XPIDL_MODULE = 'mozintl'
+
+SOURCES += ['MozIntl.cpp']
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/mozintl/mozIMozIntl.idl b/components/mozintl/mozIMozIntl.idl
new file mode 100644
index 000000000..f28824d47
--- /dev/null
+++ b/components/mozintl/mozIMozIntl.idl
@@ -0,0 +1,13 @@
+/* -*- Mode: IDL; 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"
+
+[scriptable, uuid(9f9bc42e-54f4-11e6-9aed-4b1429ac0ba0)]
+interface mozIMozIntl : nsISupports
+{
+ [implicit_jscontext] void addGetCalendarInfo(in jsval intlObject);
+ [implicit_jscontext] void addGetDisplayNames(in jsval intlObject);
+};
diff --git a/components/mozprotocol/moz.build b/components/mozprotocol/moz.build
new file mode 100644
index 000000000..b96a64ec2
--- /dev/null
+++ b/components/mozprotocol/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'mozProtocolHandler.js',
+ 'mozProtocolHandler.manifest',
+]
diff --git a/components/mozprotocol/mozProtocolHandler.js b/components/mozprotocol/mozProtocolHandler.js
new file mode 100644
index 000000000..97bfb737e
--- /dev/null
+++ b/components/mozprotocol/mozProtocolHandler.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function mozProtocolHandler() {
+ XPCOMUtils.defineLazyPreferenceGetter(this, "urlToLoad", "toolkit.mozprotocol.url",
+ "http://thereisonlyxul.org/");
+}
+
+mozProtocolHandler.prototype = {
+ scheme: "moz",
+ defaultPort: -1,
+ protocolFlags: Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,
+
+ newURI(spec, charset, base) {
+ let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+ if (base) {
+ uri.spec = base.resolve(spec);
+ } else {
+ uri.spec = spec;
+ }
+ return uri;
+ },
+
+ newChannel2(uri, loadInfo) {
+ let realURL = NetUtil.newURI(this.urlToLoad);
+ let channel = Services.io.newChannelFromURIWithLoadInfo(realURL, loadInfo)
+ channel.loadFlags |= Ci.nsIChannel.LOAD_REPLACE;
+ return channel;
+ },
+
+ newChannel(uri) {
+ return this.newChannel(uri, null);
+ },
+
+ classID: Components.ID("{47a45e5f-691e-4799-8686-14f8d3fc0f8c}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([mozProtocolHandler]);
diff --git a/components/mozprotocol/mozProtocolHandler.manifest b/components/mozprotocol/mozProtocolHandler.manifest
new file mode 100644
index 000000000..bbfdf780a
--- /dev/null
+++ b/components/mozprotocol/mozProtocolHandler.manifest
@@ -0,0 +1,2 @@
+component {47a45e5f-691e-4799-8686-14f8d3fc0f8c} mozProtocolHandler.js
+contract @mozilla.org/network/protocol;1?name=moz {47a45e5f-691e-4799-8686-14f8d3fc0f8c}
diff --git a/components/narrate/.eslintrc.js b/components/narrate/.eslintrc.js
new file mode 100644
index 000000000..b2d443575
--- /dev/null
+++ b/components/narrate/.eslintrc.js
@@ -0,0 +1,94 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": [
+ "../../.eslintrc.js"
+ ],
+
+ "globals": {
+ "Components": true,
+ "dump": true,
+ "Iterator": true
+ },
+
+ "env": { "browser": true },
+
+ "rules": {
+ // Mozilla stuff
+ "mozilla/no-aArgs": "warn",
+ "mozilla/reject-importGlobalProperties": "warn",
+ "mozilla/var-only-at-top-level": "warn",
+
+ "block-scoped-var": "error",
+ "brace-style": ["warn", "1tbs", {"allowSingleLine": false}],
+ "camelcase": "warn",
+ "comma-dangle": "off",
+ "comma-spacing": ["warn", {"before": false, "after": true}],
+ "comma-style": ["warn", "last"],
+ "complexity": "warn",
+ "consistent-return": "error",
+ "curly": "error",
+ "dot-location": ["warn", "property"],
+ "dot-notation": "error",
+ "eol-last": "error",
+ "generator-star-spacing": ["warn", "after"],
+ "indent": ["warn", 2, {"SwitchCase": 1}],
+ "key-spacing": ["warn", {"beforeColon": false, "afterColon": true}],
+ "keyword-spacing": "warn",
+ "max-len": ["warn", 80, 2, {"ignoreUrls": true}],
+ "max-nested-callbacks": ["error", 3],
+ "new-cap": ["error", {"capIsNew": false}],
+ "new-parens": "error",
+ "no-array-constructor": "error",
+ "no-cond-assign": "error",
+ "no-control-regex": "error",
+ "no-debugger": "error",
+ "no-delete-var": "error",
+ "no-dupe-args": "error",
+ "no-dupe-keys": "error",
+ "no-duplicate-case": "error",
+ "no-else-return": "error",
+ "no-eval": "error",
+ "no-extend-native": "error",
+ "no-extra-bind": "error",
+ "no-extra-boolean-cast": "error",
+ "no-extra-semi": "warn",
+ "no-fallthrough": "error",
+ "no-inline-comments": "warn",
+ "no-lonely-if": "error",
+ "no-mixed-spaces-and-tabs": "error",
+ "no-multi-spaces": "warn",
+ "no-multi-str": "warn",
+ "no-multiple-empty-lines": ["warn", {"max": 1}],
+ "no-native-reassign": "error",
+ "no-nested-ternary": "error",
+ "no-redeclare": "error",
+ "no-return-assign": "error",
+ "no-self-compare": "error",
+ "no-sequences": "error",
+ "no-shadow": "warn",
+ "no-shadow-restricted-names": "error",
+ "no-spaced-func": "warn",
+ "no-throw-literal": "error",
+ "no-trailing-spaces": "error",
+ "no-undef": "error",
+ "no-unneeded-ternary": "error",
+ "no-unreachable": "error",
+ "no-unused-vars": "error",
+ "no-with": "error",
+ "padded-blocks": ["warn", "never"],
+ "quotes": ["warn", "double", "avoid-escape"],
+ "semi": ["warn", "always"],
+ "semi-spacing": ["warn", {"before": false, "after": true}],
+ "space-before-blocks": ["warn", "always"],
+ "space-before-function-paren": ["warn", "never"],
+ "space-in-parens": ["warn", "never"],
+ "space-infix-ops": ["warn", {"int32Hint": true}],
+ "space-unary-ops": ["warn", { "words": true, "nonwords": false }],
+ "spaced-comment": ["warn", "always"],
+ "strict": ["error", "global"],
+ "use-isnan": "error",
+ "valid-typeof": "error",
+ "yoda": "error"
+ }
+};
diff --git a/components/narrate/NarrateControls.jsm b/components/narrate/NarrateControls.jsm
new file mode 100644
index 000000000..56b3deaf8
--- /dev/null
+++ b/components/narrate/NarrateControls.jsm
@@ -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/. */
+
+"use strict";
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/narrate/VoiceSelect.jsm");
+Cu.import("resource://gre/modules/narrate/Narrator.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AsyncPrefs.jsm");
+
+this.EXPORTED_SYMBOLS = ["NarrateControls"];
+
+var gStrings = Services.strings.createBundle("chrome://global/locale/narrate.properties");
+
+function NarrateControls(win, languagePromise) {
+ this._winRef = Cu.getWeakReference(win);
+ this._languagePromise = languagePromise;
+
+ win.addEventListener("unload", this);
+
+ // Append content style sheet in document head
+ let style = win.document.createElement("link");
+ style.rel = "stylesheet";
+ style.href = "chrome://global/skin/narrate.css";
+ win.document.head.appendChild(style);
+
+ function localize(pieces, ...substitutions) {
+ let result = pieces[0];
+ for (let i = 0; i < substitutions.length; ++i) {
+ result += gStrings.GetStringFromName(substitutions[i]) + pieces[i + 1];
+ }
+ return result;
+ }
+
+ let dropdown = win.document.createElement("ul");
+ dropdown.className = "dropdown narrate-dropdown";
+ // We need inline svg here for the animation to work (bug 908634 & 1190881).
+ // eslint-disable-next-line no-unsanitized/property
+ dropdown.innerHTML =
+ localize`<li>
+ <button class="dropdown-toggle button narrate-toggle"
+ title="${"narrate"}" hidden>
+ <svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="24" height="24" viewBox="0 0 24 24">
+ <style>
+ @keyframes grow {
+ 0% { transform: scaleY(1); }
+ 15% { transform: scaleY(1.5); }
+ 15% { transform: scaleY(1.5); }
+ 30% { transform: scaleY(1); }
+ 100% { transform: scaleY(1); }
+ }
+
+ .waveform > rect {
+ fill: #808080;
+ }
+
+ .speaking .waveform > rect {
+ fill: #58bf43;
+ transform-box: fill-box;
+ transform-origin: 50% 50%;
+ animation-name: grow;
+ animation-duration: 1750ms;
+ animation-iteration-count: infinite;
+ animation-timing-function: linear;
+ }
+
+ .waveform > rect:nth-child(2) { animation-delay: 250ms; }
+ .waveform > rect:nth-child(3) { animation-delay: 500ms; }
+ .waveform > rect:nth-child(4) { animation-delay: 750ms; }
+ .waveform > rect:nth-child(5) { animation-delay: 1000ms; }
+ .waveform > rect:nth-child(6) { animation-delay: 1250ms; }
+ .waveform > rect:nth-child(7) { animation-delay: 1500ms; }
+
+ </style>
+ <g class="waveform">
+ <rect x="1" y="8" width="2" height="8" rx=".5" ry=".5" />
+ <rect x="4" y="5" width="2" height="14" rx=".5" ry=".5" />
+ <rect x="7" y="8" width="2" height="8" rx=".5" ry=".5" />
+ <rect x="10" y="4" width="2" height="16" rx=".5" ry=".5" />
+ <rect x="13" y="2" width="2" height="20" rx=".5" ry=".5" />
+ <rect x="16" y="4" width="2" height="16" rx=".5" ry=".5" />
+ <rect x="19" y="7" width="2" height="10" rx=".5" ry=".5" />
+ </g>
+ </svg>
+ </button>
+ </li>
+ <li class="dropdown-popup">
+ <div class="narrate-row narrate-control">
+ <button disabled class="narrate-skip-previous"
+ title="${"back"}"></button>
+ <button class="narrate-start-stop" title="${"start"}"></button>
+ <button disabled class="narrate-skip-next"
+ title="${"forward"}"></button>
+ </div>
+ <div class="narrate-row narrate-rate">
+ <input class="narrate-rate-input" value="0" title="${"speed"}"
+ step="5" max="100" min="-100" type="range">
+ </div>
+ <div class="narrate-row narrate-voices"></div>
+ <div class="dropdown-arrow"></div>
+ </li>`;
+
+ this.narrator = new Narrator(win, languagePromise);
+
+ let branch = Services.prefs.getBranch("narrate.");
+ let selectLabel = gStrings.GetStringFromName("selectvoicelabel");
+ this.voiceSelect = new VoiceSelect(win, selectLabel);
+ this.voiceSelect.element.addEventListener("change", this);
+ this.voiceSelect.element.classList.add("voice-select");
+ win.speechSynthesis.addEventListener("voiceschanged", this);
+ dropdown.querySelector(".narrate-voices").appendChild(
+ this.voiceSelect.element);
+
+ dropdown.addEventListener("click", this, true);
+
+ let rateRange = dropdown.querySelector(".narrate-rate > input");
+ rateRange.addEventListener("change", this);
+
+ // The rate is stored as an integer.
+ rateRange.value = branch.getIntPref("rate");
+
+ this._setupVoices();
+
+ let tb = win.document.querySelector(".reader-toolbar");
+ tb.appendChild(dropdown);
+}
+
+NarrateControls.prototype = {
+ handleEvent(evt) {
+ switch (evt.type) {
+ case "change":
+ if (evt.target.classList.contains("narrate-rate-input")) {
+ this._onRateInput(evt);
+ } else {
+ this._onVoiceChange();
+ }
+ break;
+ case "click":
+ this._onButtonClick(evt);
+ break;
+ case "voiceschanged":
+ this._setupVoices();
+ break;
+ }
+ },
+
+ /**
+ * Returns true if synth voices are available.
+ */
+ _setupVoices() {
+ return this._languagePromise.then(language => {
+ this.voiceSelect.clear();
+ let win = this._win;
+ let voicePrefs = this._getVoicePref();
+ let selectedVoice = voicePrefs[language || "default"];
+ let comparer = win.Intl ?
+ (new Intl.Collator()).compare : (a, b) => a.localeCompare(b);
+ let filter = !Services.prefs.getBoolPref("narrate.filter-voices");
+ let options = win.speechSynthesis.getVoices().filter(v => {
+ return filter || !language || v.lang.split("-")[0] == language;
+ }).map(v => {
+ return {
+ label: this._createVoiceLabel(v),
+ value: v.voiceURI,
+ selected: selectedVoice == v.voiceURI
+ };
+ }).sort((a, b) => comparer(a.label, b.label));
+
+ if (options.length) {
+ options.unshift({
+ label: gStrings.GetStringFromName("defaultvoice"),
+ value: "automatic",
+ selected: selectedVoice == "automatic"
+ });
+ this.voiceSelect.addOptions(options);
+ }
+
+ let narrateToggle = win.document.querySelector(".narrate-toggle");
+ let initial = !this._voicesInitialized;
+ this._voicesInitialized = true;
+
+ // We disable this entire feature if there are no available voices.
+ narrateToggle.hidden = !options.length;
+ });
+ },
+
+ _getVoicePref() {
+ let voicePref = Services.prefs.getCharPref("narrate.voice");
+ try {
+ return JSON.parse(voicePref);
+ } catch (e) {
+ return { default: voicePref };
+ }
+ },
+
+ _onRateInput(evt) {
+ AsyncPrefs.set("narrate.rate", parseInt(evt.target.value, 10));
+ this.narrator.setRate(this._convertRate(evt.target.value));
+ },
+
+ _onVoiceChange() {
+ let voice = this.voice;
+ this.narrator.setVoice(voice);
+ this._languagePromise.then(language => {
+ if (language) {
+ let voicePref = this._getVoicePref();
+ voicePref[language || "default"] = voice;
+ AsyncPrefs.set("narrate.voice", JSON.stringify(voicePref));
+ }
+ });
+ },
+
+ _onButtonClick(evt) {
+ let classList = evt.target.classList;
+ if (classList.contains("narrate-skip-previous")) {
+ this.narrator.skipPrevious();
+ } else if (classList.contains("narrate-skip-next")) {
+ this.narrator.skipNext();
+ } else if (classList.contains("narrate-start-stop")) {
+ if (this.narrator.speaking) {
+ this.narrator.stop();
+ } else {
+ this._updateSpeechControls(true);
+ let options = { rate: this.rate, voice: this.voice };
+ this.narrator.start(options).then(() => {
+ this._updateSpeechControls(false);
+ }, err => {
+ Cu.reportError(`Narrate failed: ${err}.`);
+ this._updateSpeechControls(false);
+ });
+ }
+ }
+ },
+
+ _updateSpeechControls(speaking) {
+ let dropdown = this._doc.querySelector(".narrate-dropdown");
+ dropdown.classList.toggle("keep-open", speaking);
+ dropdown.classList.toggle("speaking", speaking);
+
+ let startStopButton = this._doc.querySelector(".narrate-start-stop");
+ startStopButton.title =
+ gStrings.GetStringFromName(speaking ? "stop" : "start");
+
+ this._doc.querySelector(".narrate-skip-previous").disabled = !speaking;
+ this._doc.querySelector(".narrate-skip-next").disabled = !speaking;
+ },
+
+ _createVoiceLabel(voice) {
+ // This is a highly imperfect method of making human-readable labels
+ // for system voices. Because each platform has a different naming scheme
+ // for voices, we use a different method for each platform.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ // On windows the language is included in the name, so just use the name
+ return voice.name;
+ case "Linux":
+ // On Linux, the name is usually the unlocalized language name.
+ // Use a localized language name, and have the language tag in
+ // parenthisis. This is to avoid six languages called "English".
+ return gStrings.formatStringFromName("voiceLabel",
+ [this._getLanguageName(voice.lang) || voice.name, voice.lang], 2);
+ default:
+ // On Mac the language is not included in the name, find a localized
+ // language name or show the tag if none exists.
+ // This is the ideal naming scheme so it is also the "default".
+ return gStrings.formatStringFromName("voiceLabel",
+ [voice.name, this._getLanguageName(voice.lang) || voice.lang], 2);
+ }
+ },
+
+ _getLanguageName(lang) {
+ if (!this._langStrings) {
+ this._langStrings = Services.strings.createBundle(
+ "chrome://global/locale/languageNames.properties ");
+ }
+
+ try {
+ // language tags will be lower case ascii between 2 and 3 characters long.
+ return this._langStrings.GetStringFromName(lang.match(/^[a-z]{2,3}/)[0]);
+ } catch (e) {
+ return "";
+ }
+ },
+
+ _convertRate(rate) {
+ // We need to convert a relative percentage value to a fraction rate value.
+ // eg. -100 is half the speed, 100 is twice the speed in percentage,
+ // 0.5 is half the speed and 2 is twice the speed in fractions.
+ return Math.pow(Math.abs(rate / 100) + 1, rate < 0 ? -1 : 1);
+ },
+
+ get _win() {
+ return this._winRef.get();
+ },
+
+ get _doc() {
+ return this._win.document;
+ },
+
+ get rate() {
+ return this._convertRate(
+ this._doc.querySelector(".narrate-rate-input").value);
+ },
+
+ get voice() {
+ return this.voiceSelect.value;
+ }
+};
diff --git a/components/narrate/Narrator.jsm b/components/narrate/Narrator.jsm
new file mode 100644
index 000000000..ac0b2e040
--- /dev/null
+++ b/components/narrate/Narrator.jsm
@@ -0,0 +1,440 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+this.EXPORTED_SYMBOLS = [ "Narrator" ];
+
+// Maximum time into paragraph when pressing "skip previous" will go
+// to previous paragraph and not the start of current one.
+const PREV_THRESHOLD = 2000;
+// All text-related style rules that we should copy over to the highlight node.
+const kTextStylesRules = ["font-family", "font-kerning", "font-size",
+ "font-size-adjust", "font-stretch", "font-variant", "font-weight",
+ "line-height", "letter-spacing", "text-orientation",
+ "text-transform", "word-spacing"];
+
+function Narrator(win, languagePromise) {
+ this._winRef = Cu.getWeakReference(win);
+ this._languagePromise = languagePromise;
+ this._inTest = Services.prefs.getBoolPref("narrate.test");
+ this._speechOptions = {};
+ this._startTime = 0;
+ this._stopped = false;
+}
+
+Narrator.prototype = {
+ get _doc() {
+ return this._winRef.get().document;
+ },
+
+ get _win() {
+ return this._winRef.get();
+ },
+
+ get _treeWalker() {
+ if (!this._treeWalkerRef) {
+ let wu = this._win.QueryInterface(
+ Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ let nf = this._win.NodeFilter;
+
+ let filter = {
+ _matches: new Set(),
+
+ // We want high-level elements that have non-empty text nodes.
+ // For example, paragraphs. But nested anchors and other elements
+ // are not interesting since their text already appears in their
+ // parent's textContent.
+ acceptNode(node) {
+ if (this._matches.has(node.parentNode)) {
+ // Reject sub-trees of accepted nodes.
+ return nf.FILTER_REJECT;
+ }
+
+ if (!/\S/.test(node.textContent)) {
+ // Reject nodes with no text.
+ return nf.FILTER_REJECT;
+ }
+
+ let bb = wu.getBoundsWithoutFlushing(node);
+ if (!bb.width || !bb.height) {
+ // Skip non-rendered nodes. We don't reject because a zero-sized
+ // container can still have visible, "overflowed", content.
+ return nf.FILTER_SKIP;
+ }
+
+ for (let c = node.firstChild; c; c = c.nextSibling) {
+ if (c.nodeType == c.TEXT_NODE && /\S/.test(c.textContent)) {
+ // If node has a non-empty text child accept it.
+ this._matches.add(node);
+ return nf.FILTER_ACCEPT;
+ }
+ }
+
+ return nf.FILTER_SKIP;
+ }
+ };
+
+ this._treeWalkerRef = new WeakMap();
+
+ // We can't hold a weak reference on the treewalker, because there
+ // are no other strong references, and it will be GC'ed. Instead,
+ // we rely on the window's lifetime and use it as a weak reference.
+ this._treeWalkerRef.set(this._win,
+ this._doc.createTreeWalker(this._doc.querySelector(".container"),
+ nf.SHOW_ELEMENT, filter, false));
+ }
+
+ return this._treeWalkerRef.get(this._win);
+ },
+
+ get _timeIntoParagraph() {
+ let rv = Date.now() - this._startTime;
+ return rv;
+ },
+
+ get speaking() {
+ return this._win.speechSynthesis.speaking ||
+ this._win.speechSynthesis.pending;
+ },
+
+ _getVoice(voiceURI) {
+ if (!this._voiceMap || !this._voiceMap.has(voiceURI)) {
+ this._voiceMap = new Map(
+ this._win.speechSynthesis.getVoices().map(v => [v.voiceURI, v]));
+ }
+
+ return this._voiceMap.get(voiceURI);
+ },
+
+ _isParagraphInView(paragraph) {
+ if (!paragraph) {
+ return false;
+ }
+
+ let bb = paragraph.getBoundingClientRect();
+ return bb.top >= 0 && bb.top < this._win.innerHeight;
+ },
+
+ _sendTestEvent(eventType, detail) {
+ let win = this._win;
+ win.dispatchEvent(new win.CustomEvent(eventType,
+ { detail: Cu.cloneInto(detail, win.document) }));
+ },
+
+ _speakInner() {
+ this._win.speechSynthesis.cancel();
+ let tw = this._treeWalker;
+ let paragraph = tw.currentNode;
+ if (paragraph == tw.root) {
+ this._sendTestEvent("paragraphsdone", {});
+ return Promise.resolve();
+ }
+
+ let utterance = new this._win.SpeechSynthesisUtterance(
+ paragraph.textContent);
+ utterance.rate = this._speechOptions.rate;
+ if (this._speechOptions.voice) {
+ utterance.voice = this._speechOptions.voice;
+ } else {
+ utterance.lang = this._speechOptions.lang;
+ }
+
+ this._startTime = Date.now();
+
+ let highlighter = new Highlighter(paragraph);
+
+ if (this._inTest) {
+ let onTestSynthEvent = e => {
+ if (e.detail.type == "boundary") {
+ let args = Object.assign({ utterance }, e.detail.args);
+ let evt = new this._win.SpeechSynthesisEvent(e.detail.type, args);
+ utterance.dispatchEvent(evt);
+ }
+ };
+
+ let removeListeners = () => {
+ this._win.removeEventListener("testsynthevent", onTestSynthEvent);
+ };
+
+ this._win.addEventListener("testsynthevent", onTestSynthEvent);
+ utterance.addEventListener("end", removeListeners);
+ utterance.addEventListener("error", removeListeners);
+ }
+
+ return new Promise((resolve, reject) => {
+ utterance.addEventListener("start", () => {
+ paragraph.classList.add("narrating");
+ let bb = paragraph.getBoundingClientRect();
+ if (bb.top < 0 || bb.bottom > this._win.innerHeight) {
+ paragraph.scrollIntoView({ behavior: "smooth", block: "start"});
+ }
+
+ if (this._inTest) {
+ this._sendTestEvent("paragraphstart", {
+ voice: utterance.chosenVoiceURI,
+ rate: utterance.rate,
+ paragraph: paragraph.textContent,
+ tag: paragraph.localName
+ });
+ }
+ });
+
+ utterance.addEventListener("end", () => {
+ if (!this._win) {
+ // page got unloaded, don't do anything.
+ return;
+ }
+
+ highlighter.remove();
+ paragraph.classList.remove("narrating");
+ this._startTime = 0;
+ if (this._inTest) {
+ this._sendTestEvent("paragraphend", {});
+ }
+
+ if (this._stopped) {
+ // User pressed stopped.
+ resolve();
+ } else {
+ tw.currentNode = tw.nextNode() || tw.root;
+ this._speakInner().then(resolve, reject);
+ }
+ });
+
+ utterance.addEventListener("error", () => {
+ reject("speech synthesis failed");
+ });
+
+ utterance.addEventListener("boundary", e => {
+ if (e.name != "word") {
+ // We are only interested in word boundaries for now.
+ return;
+ }
+
+ if (e.charLength) {
+ highlighter.highlight(e.charIndex, e.charLength);
+ if (this._inTest) {
+ this._sendTestEvent("wordhighlight", {
+ start: e.charIndex,
+ end: e.charIndex + e.charLength
+ });
+ }
+ }
+ });
+
+ this._win.speechSynthesis.speak(utterance);
+ });
+ },
+
+ start(speechOptions) {
+ this._speechOptions = {
+ rate: speechOptions.rate,
+ voice: this._getVoice(speechOptions.voice)
+ };
+
+ this._stopped = false;
+ return this._languagePromise.then(language => {
+ if (!this._speechOptions.voice) {
+ this._speechOptions.lang = language;
+ }
+
+ let tw = this._treeWalker;
+ if (!this._isParagraphInView(tw.currentNode)) {
+ tw.currentNode = tw.root;
+ while (tw.nextNode()) {
+ if (this._isParagraphInView(tw.currentNode)) {
+ break;
+ }
+ }
+ }
+ if (tw.currentNode == tw.root) {
+ tw.nextNode();
+ }
+
+ return this._speakInner();
+ });
+ },
+
+ stop() {
+ this._stopped = true;
+ this._win.speechSynthesis.cancel();
+ },
+
+ skipNext() {
+ this._win.speechSynthesis.cancel();
+ },
+
+ skipPrevious() {
+ this._goBackParagraphs(this._timeIntoParagraph < PREV_THRESHOLD ? 2 : 1);
+ },
+
+ setRate(rate) {
+ this._speechOptions.rate = rate;
+ /* repeat current paragraph */
+ this._goBackParagraphs(1);
+ },
+
+ setVoice(voice) {
+ this._speechOptions.voice = this._getVoice(voice);
+ /* repeat current paragraph */
+ this._goBackParagraphs(1);
+ },
+
+ _goBackParagraphs(count) {
+ let tw = this._treeWalker;
+ for (let i = 0; i < count; i++) {
+ if (!tw.previousNode()) {
+ tw.currentNode = tw.root;
+ }
+ }
+ this._win.speechSynthesis.cancel();
+ }
+};
+
+/**
+ * The Highlighter class is used to highlight a range of text in a container.
+ *
+ * @param {nsIDOMElement} container a text container
+ */
+function Highlighter(container) {
+ this.container = container;
+}
+
+Highlighter.prototype = {
+ /**
+ * Highlight the range within offsets relative to the container.
+ *
+ * @param {Number} startOffset the start offset
+ * @param {Number} length the length in characters of the range
+ */
+ highlight(startOffset, length) {
+ let containerRect = this.container.getBoundingClientRect();
+ let range = this._getRange(startOffset, startOffset + length);
+ let rangeRects = range.getClientRects();
+ let win = this.container.ownerGlobal;
+ let computedStyle = win.getComputedStyle(range.endContainer.parentNode);
+ let nodes = this._getFreshHighlightNodes(rangeRects.length);
+
+ let textStyle = {};
+ for (let textStyleRule of kTextStylesRules) {
+ textStyle[textStyleRule] = computedStyle[textStyleRule];
+ }
+
+ for (let i = 0; i < rangeRects.length; i++) {
+ let r = rangeRects[i];
+ let node = nodes[i];
+
+ let style = Object.assign({
+ "top": `${r.top - containerRect.top + r.height / 2}px`,
+ "left": `${r.left - containerRect.left + r.width / 2}px`,
+ "width": `${r.width}px`,
+ "height": `${r.height}px`
+ }, textStyle);
+
+ // Enables us to vary the CSS transition on a line change.
+ node.classList.toggle("newline", style.top != node.dataset.top);
+ node.dataset.top = style.top;
+
+ // Enables CSS animations.
+ node.classList.remove("animate");
+ win.requestAnimationFrame(() => {
+ node.classList.add("animate");
+ });
+
+ // Enables alternative word display with a CSS pseudo-element.
+ node.dataset.word = range.toString();
+
+ // Apply style
+ node.style = Object.entries(style).map(
+ s => `${s[0]}: ${s[1]};`).join(" ");
+ }
+ },
+
+ /**
+ * Releases reference to container and removes all highlight nodes.
+ */
+ remove() {
+ for (let node of this._nodes) {
+ node.remove();
+ }
+
+ this.container = null;
+ },
+
+ /**
+ * Returns specified amount of highlight nodes. Creates new ones if necessary
+ * and purges any additional nodes that are not needed.
+ *
+ * @param {Number} count number of nodes needed
+ */
+ _getFreshHighlightNodes(count) {
+ let doc = this.container.ownerDocument;
+ let nodes = Array.from(this._nodes);
+
+ // Remove nodes we don't need anymore (nodes.length - count > 0).
+ for (let toRemove = 0; toRemove < nodes.length - count; toRemove++) {
+ nodes.shift().remove();
+ }
+
+ // Add additional nodes if we need them (count - nodes.length > 0).
+ for (let toAdd = 0; toAdd < count - nodes.length; toAdd++) {
+ let node = doc.createElement("div");
+ node.className = "narrate-word-highlight";
+ this.container.appendChild(node);
+ nodes.push(node);
+ }
+
+ return nodes;
+ },
+
+ /**
+ * Create and return a range object with the start and end offsets relative
+ * to the container node.
+ *
+ * @param {Number} startOffset the start offset
+ * @param {Number} endOffset the end offset
+ */
+ _getRange(startOffset, endOffset) {
+ let doc = this.container.ownerDocument;
+ let i = 0;
+ let treeWalker = doc.createTreeWalker(
+ this.container, doc.defaultView.NodeFilter.SHOW_TEXT);
+ let node = treeWalker.nextNode();
+
+ function _findNodeAndOffset(offset) {
+ do {
+ let length = node.data.length;
+ if (offset >= i && offset <= i + length) {
+ return [node, offset - i];
+ }
+ i += length;
+ } while ((node = treeWalker.nextNode()));
+
+ // Offset is out of bounds, return last offset of last node.
+ node = treeWalker.lastChild();
+ return [node, node.data.length];
+ }
+
+ let range = doc.createRange();
+ range.setStart(..._findNodeAndOffset(startOffset));
+ range.setEnd(..._findNodeAndOffset(endOffset));
+
+ return range;
+ },
+
+ /*
+ * Get all existing highlight nodes for container.
+ */
+ get _nodes() {
+ return this.container.querySelectorAll(".narrate-word-highlight");
+ }
+};
diff --git a/components/narrate/VoiceSelect.jsm b/components/narrate/VoiceSelect.jsm
new file mode 100644
index 000000000..861a21c97
--- /dev/null
+++ b/components/narrate/VoiceSelect.jsm
@@ -0,0 +1,300 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = ["VoiceSelect"];
+
+function VoiceSelect(win, label) {
+ this._winRef = Cu.getWeakReference(win);
+
+ let element = win.document.createElement("div");
+ element.classList.add("voiceselect");
+ // eslint-disable-next-line no-unsanitized/property
+ element.innerHTML =
+ `<button class="select-toggle" aria-controls="voice-options">
+ <span class="label">${label}</span> <span class="current-voice"></span>
+ </button>
+ <div class="options" id="voice-options" role="listbox"></div>`;
+
+ this._elementRef = Cu.getWeakReference(element);
+
+ let button = this.selectToggle;
+ button.addEventListener("click", this);
+ button.addEventListener("keypress", this);
+
+ let listbox = this.listbox;
+ listbox.addEventListener("click", this);
+ listbox.addEventListener("mousemove", this);
+ listbox.addEventListener("keypress", this);
+ listbox.addEventListener("wheel", this, true);
+
+ win.addEventListener("resize", () => {
+ this._updateDropdownHeight();
+ });
+}
+
+VoiceSelect.prototype = {
+ add(label, value) {
+ let option = this._doc.createElement("button");
+ option.dataset.value = value;
+ option.classList.add("option");
+ option.tabIndex = "-1";
+ option.setAttribute("role", "option");
+ option.textContent = label;
+ this.listbox.appendChild(option);
+ return option;
+ },
+
+ addOptions(options) {
+ let selected = null;
+ for (let option of options) {
+ if (option.selected) {
+ selected = this.add(option.label, option.value);
+ } else {
+ this.add(option.label, option.value);
+ }
+ }
+
+ this._select(selected || this.options[0], true);
+ },
+
+ clear() {
+ this.listbox.innerHTML = "";
+ },
+
+ toggleList(force, focus = true) {
+ if (this.element.classList.toggle("open", force)) {
+ if (focus) {
+ (this.selected || this.options[0]).focus();
+ }
+
+ this._updateDropdownHeight(true);
+ this.listbox.setAttribute("aria-expanded", true);
+ this._win.addEventListener("focus", this, true);
+ } else {
+ if (focus) {
+ this.element.querySelector(".select-toggle").focus();
+ }
+
+ this.listbox.setAttribute("aria-expanded", false);
+ this._win.removeEventListener("focus", this, true);
+ }
+ },
+
+ handleEvent(evt) {
+ let target = evt.target;
+
+ switch (evt.type) {
+ case "click":
+ if (target.classList.contains("option")) {
+ if (!target.classList.contains("selected")) {
+ this.selected = target;
+ }
+
+ this.toggleList(false);
+ } else if (target.classList.contains("select-toggle")) {
+ this.toggleList();
+ }
+ break;
+
+ case "mousemove":
+ this.listbox.classList.add("hovering");
+ break;
+
+ case "keypress":
+ if (target.classList.contains("select-toggle")) {
+ if (evt.altKey) {
+ this.toggleList(true);
+ } else {
+ this._keyPressedButton(evt);
+ }
+ } else {
+ this.listbox.classList.remove("hovering");
+ this._keyPressedInBox(evt);
+ }
+ break;
+
+ case "wheel":
+ // Don't let wheel events bubble to document. It will scroll the page
+ // and close the entire narrate dialog.
+ evt.stopPropagation();
+ break;
+
+ case "focus":
+ if (!target.closest(".voiceselect")) {
+ this.toggleList(false, false);
+ }
+ break;
+ }
+ },
+
+ _getPagedOption(option, up) {
+ let height = elem => elem.getBoundingClientRect().height;
+ let listboxHeight = height(this.listbox);
+
+ let next = option;
+ for (let delta = 0; delta < listboxHeight; delta += height(next)) {
+ let sibling = up ? next.previousElementSibling : next.nextElementSibling;
+ if (!sibling) {
+ break;
+ }
+
+ next = sibling;
+ }
+
+ return next;
+ },
+
+ _keyPressedButton(evt) {
+ if (evt.altKey && (evt.key === "ArrowUp" || evt.key === "ArrowUp")) {
+ this.toggleList(true);
+ return;
+ }
+
+ let toSelect;
+ switch (evt.key) {
+ case "PageUp":
+ case "ArrowUp":
+ toSelect = this.selected.previousElementSibling;
+ break;
+ case "PageDown":
+ case "ArrowDown":
+ toSelect = this.selected.nextElementSibling;
+ break;
+ case "Home":
+ toSelect = this.selected.parentNode.firstElementChild;
+ break;
+ case "End":
+ toSelect = this.selected.parentNode.lastElementChild;
+ break;
+ }
+
+ if (toSelect && toSelect.classList.contains("option")) {
+ evt.preventDefault();
+ this.selected = toSelect;
+ }
+ },
+
+ _keyPressedInBox(evt) {
+ let toFocus;
+ let cur = this._doc.activeElement;
+
+ switch (evt.key) {
+ case "ArrowUp":
+ toFocus = cur.previousElementSibling || this.listbox.lastElementChild;
+ break;
+ case "ArrowDown":
+ toFocus = cur.nextElementSibling || this.listbox.firstElementChild;
+ break;
+ case "PageUp":
+ toFocus = this._getPagedOption(cur, true);
+ break;
+ case "PageDown":
+ toFocus = this._getPagedOption(cur, false);
+ break;
+ case "Home":
+ toFocus = cur.parentNode.firstElementChild;
+ break;
+ case "End":
+ toFocus = cur.parentNode.lastElementChild;
+ break;
+ case "Escape":
+ this.toggleList(false);
+ break;
+ }
+
+ if (toFocus && toFocus.classList.contains("option")) {
+ evt.preventDefault();
+ toFocus.focus();
+ }
+ },
+
+ _select(option, suppressEvent = false) {
+ let oldSelected = this.selected;
+ if (oldSelected) {
+ oldSelected.removeAttribute("aria-selected");
+ oldSelected.classList.remove("selected");
+ }
+
+ if (option) {
+ option.setAttribute("aria-selected", true);
+ option.classList.add("selected");
+ this.element.querySelector(".current-voice").textContent =
+ option.textContent;
+ }
+
+ if (!suppressEvent) {
+ let evt = this.element.ownerDocument.createEvent("Event");
+ evt.initEvent("change", true, true);
+ this.element.dispatchEvent(evt);
+ }
+ },
+
+ _updateDropdownHeight(now) {
+ let updateInner = () => {
+ let winHeight = this._win.innerHeight;
+ let listbox = this.listbox;
+ let listboxTop = listbox.getBoundingClientRect().top;
+ listbox.style.maxHeight = (winHeight - listboxTop - 10) + "px";
+ };
+
+ if (now) {
+ updateInner();
+ } else if (!this._pendingDropdownUpdate) {
+ this._pendingDropdownUpdate = true;
+ this._win.requestAnimationFrame(() => {
+ updateInner();
+ delete this._pendingDropdownUpdate;
+ });
+ }
+ },
+
+ _getOptionFromValue(value) {
+ return Array.from(this.options).find(o => o.dataset.value === value);
+ },
+
+ get element() {
+ return this._elementRef.get();
+ },
+
+ get listbox() {
+ return this._elementRef.get().querySelector(".options");
+ },
+
+ get selectToggle() {
+ return this._elementRef.get().querySelector(".select-toggle");
+ },
+
+ get _win() {
+ return this._winRef.get();
+ },
+
+ get _doc() {
+ return this._win.document;
+ },
+
+ set selected(option) {
+ this._select(option);
+ },
+
+ get selected() {
+ return this.element.querySelector(".options > .option.selected");
+ },
+
+ get options() {
+ return this.element.querySelectorAll(".options > .option");
+ },
+
+ set value(value) {
+ this._select(this._getOptionFromValue(value));
+ },
+
+ get value() {
+ let selected = this.selected;
+ return selected ? selected.dataset.value : "";
+ }
+};
diff --git a/components/narrate/moz.build b/components/narrate/moz.build
new file mode 100644
index 000000000..fe1dfb6ca
--- /dev/null
+++ b/components/narrate/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_JS_MODULES.narrate = [
+ 'NarrateControls.jsm',
+ 'Narrator.jsm',
+ 'VoiceSelect.jsm'
+]
diff --git a/components/nsDefaultCLH.js b/components/nsDefaultCLH.js
new file mode 100644
index 000000000..affbf75ee
--- /dev/null
+++ b/components/nsDefaultCLH.js
@@ -0,0 +1,119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsISupports = Components.interfaces.nsISupports;
+
+const nsICommandLine = Components.interfaces.nsICommandLine;
+const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler;
+const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
+const nsISupportsString = Components.interfaces.nsISupportsString;
+const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
+const nsIProperties = Components.interfaces.nsIProperties;
+const nsIFile = Components.interfaces.nsIFile;
+const nsISimpleEnumerator = Components.interfaces.nsISimpleEnumerator;
+
+/**
+ * This file provides a generic default command-line handler.
+ *
+ * It opens the chrome window specified by the pref "toolkit.defaultChromeURI"
+ * with the flags specified by the pref "toolkit.defaultChromeFeatures"
+ * or "chrome,dialog=no,all" is it is not available.
+ * The arguments passed to the window are the nsICommandLine instance.
+ *
+ * It doesn't do anything if the pref "toolkit.defaultChromeURI" is unset.
+ */
+
+function getDirectoryService()
+{
+ return Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(nsIProperties);
+}
+
+function nsDefaultCLH() { }
+nsDefaultCLH.prototype = {
+ classID: Components.ID("{6ebc941a-f2ff-4d56-b3b6-f7d0b9d73344}"),
+
+ /* nsISupports */
+
+ QueryInterface : XPCOMUtils.generateQI([nsICommandLineHandler]),
+
+ /* nsICommandLineHandler */
+
+ handle : function clh_handle(cmdLine) {
+ var printDir;
+ while ((printDir = cmdLine.handleFlagWithParam("print-xpcom-dir", false))) {
+ var out = "print-xpcom-dir(\"" + printDir + "\"): ";
+ try {
+ out += getDirectoryService().get(printDir, nsIFile).path;
+ }
+ catch (e) {
+ out += "<Not Provided>";
+ }
+
+ dump(out + "\n");
+ Components.utils.reportError(out);
+ }
+
+ var printDirList;
+ while ((printDirList = cmdLine.handleFlagWithParam("print-xpcom-dirlist",
+ false))) {
+ out = "print-xpcom-dirlist(\"" + printDirList + "\"): ";
+ try {
+ var list = getDirectoryService().get(printDirList,
+ nsISimpleEnumerator);
+ while (list.hasMoreElements())
+ out += list.getNext().QueryInterface(nsIFile).path + ";";
+ }
+ catch (e) {
+ out += "<Not Provided>";
+ }
+
+ dump(out + "\n");
+ Components.utils.reportError(out);
+ }
+
+ if (cmdLine.handleFlag("silent", false)) {
+ cmdLine.preventDefault = true;
+ }
+
+ if (cmdLine.preventDefault)
+ return;
+
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(nsIPrefBranch);
+
+ try {
+ var singletonWindowType =
+ prefs.getCharPref("toolkit.singletonWindowType");
+ var windowMediator =
+ Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+
+ var win = windowMediator.getMostRecentWindow(singletonWindowType);
+ if (win) {
+ win.focus();
+ cmdLine.preventDefault = true;
+ return;
+ }
+ }
+ catch (e) { }
+
+ // if the pref is missing, ignore the exception
+ try {
+ var chromeURI = prefs.getCharPref("toolkit.defaultChromeURI");
+ var flags = prefs.getCharPref("toolkit.defaultChromeFeatures", "chrome,dialog=no,all");
+ var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(nsIWindowWatcher);
+ wwatch.openWindow(null, chromeURI, "_blank",
+ flags, cmdLine);
+ }
+ catch (e) { }
+ },
+
+ helpInfo : "",
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsDefaultCLH]);
diff --git a/components/nsDefaultCLH.manifest b/components/nsDefaultCLH.manifest
new file mode 100644
index 000000000..075c8c416
--- /dev/null
+++ b/components/nsDefaultCLH.manifest
@@ -0,0 +1,3 @@
+component {6ebc941a-f2ff-4d56-b3b6-f7d0b9d73344} nsDefaultCLH.js
+contract @mozilla.org/toolkit/default-clh;1 {6ebc941a-f2ff-4d56-b3b6-f7d0b9d73344}
+category command-line-handler y-default @mozilla.org/toolkit/default-clh;1
diff --git a/components/osfile/NativeOSFileInternals.cpp b/components/osfile/NativeOSFileInternals.cpp
new file mode 100644
index 000000000..801c8c37d
--- /dev/null
+++ b/components/osfile/NativeOSFileInternals.cpp
@@ -0,0 +1,915 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Native implementation of some OS.File operations.
+ */
+
+#include "nsString.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCID.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsServiceManagerUtils.h"
+#include "nsProxyRelease.h"
+
+#include "nsINativeOSFileInternals.h"
+#include "NativeOSFileInternals.h"
+#include "mozilla/dom/NativeOSFileInternalsBinding.h"
+
+#include "nsIUnicodeDecoder.h"
+#include "nsIEventTarget.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/TimeStamp.h"
+
+#include "prio.h"
+#include "prerror.h"
+#include "private/pprio.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Utility.h"
+#include "xpcpublic.h"
+
+#include <algorithm>
+#if defined(XP_UNIX)
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#endif // defined (XP_UNIX)
+
+#if defined(XP_WIN)
+#include <windows.h>
+#endif // defined (XP_WIN)
+
+namespace mozilla {
+
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close)
+
+namespace {
+
+// Utilities for safely manipulating ArrayBuffer contents even in the
+// absence of a JSContext.
+
+/**
+ * The C buffer underlying to an ArrayBuffer. Throughout the code, we manipulate
+ * this instead of a void* buffer, as this lets us transfer data across threads
+ * and into JavaScript without copy.
+ */
+struct ArrayBufferContents {
+ /**
+ * The data of the ArrayBuffer. This is the pointer manipulated to
+ * read/write the contents of the buffer.
+ */
+ uint8_t* data;
+ /**
+ * The number of bytes in the ArrayBuffer.
+ */
+ size_t nbytes;
+};
+
+/**
+ * RAII for ArrayBufferContents.
+ */
+struct ScopedArrayBufferContentsTraits {
+ typedef ArrayBufferContents type;
+ const static type empty() {
+ type result = {0, 0};
+ return result;
+ }
+ static void release(type ptr) {
+ js_free(ptr.data);
+ ptr.data = nullptr;
+ ptr.nbytes = 0;
+ }
+};
+
+struct MOZ_NON_TEMPORARY_CLASS ScopedArrayBufferContents: public Scoped<ScopedArrayBufferContentsTraits> {
+ explicit ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM):
+ Scoped<ScopedArrayBufferContentsTraits>(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT)
+ { }
+ explicit ScopedArrayBufferContents(const ArrayBufferContents& v
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM):
+ Scoped<ScopedArrayBufferContentsTraits>(v MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
+ { }
+ ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) {
+ Scoped<ScopedArrayBufferContentsTraits>::operator=(ptr);
+ return *this;
+ }
+
+ /**
+ * Request memory for this ArrayBufferContent. This memory may later
+ * be used to create an ArrayBuffer object (possibly on another
+ * thread) without copy.
+ *
+ * @return true In case of success, false otherwise.
+ */
+ bool Allocate(uint32_t length) {
+ dispose();
+ ArrayBufferContents& value = rwget();
+ void *ptr = js_calloc(1, length);
+ if (ptr) {
+ value.data = (uint8_t *) ptr;
+ value.nbytes = length;
+ return true;
+ }
+ return false;
+ }
+private:
+ explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) = delete;
+ ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) = delete;
+};
+
+///////// Cross-platform issues
+
+// Platform specific constants. As OS.File always uses OS-level
+// errors, we need to map a few high-level errors to OS-level
+// constants.
+#if defined(XP_UNIX)
+#define OS_ERROR_NOMEM ENOMEM
+#define OS_ERROR_INVAL EINVAL
+#define OS_ERROR_TOO_LARGE EFBIG
+#define OS_ERROR_RACE EIO
+#elif defined(XP_WIN)
+#define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY
+#define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS
+#define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE
+#define OS_ERROR_RACE ERROR_SHARING_VIOLATION
+#else
+#error "We do not have platform-specific constants for this platform"
+#endif
+
+///////// Results of OS.File operations
+
+/**
+ * Base class for results passed to the callbacks.
+ *
+ * This base class implements caching of JS values returned to the client.
+ * We make use of this caching in derived classes e.g. to avoid accidents
+ * when we transfer data allocated on another thread into JS. Note that
+ * this caching can lead to cycles (e.g. if a client adds a back-reference
+ * in the JS value), so we implement all Cycle Collector primitives in
+ * AbstractResult.
+ */
+class AbstractResult: public nsINativeOSFileResult {
+public:
+ NS_DECL_NSINATIVEOSFILERESULT
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult)
+
+ /**
+ * Construct the result object. Must be called on the main thread
+ * as the AbstractResult is cycle-collected.
+ *
+ * @param aStartDate The instant at which the operation was
+ * requested.
+ */
+ explicit AbstractResult(TimeStamp aStartDate)
+ : mStartDate(aStartDate)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::HoldJSObjects(this);
+ }
+
+ /**
+ * Setup the AbstractResult once data is available.
+ *
+ * @param aDispatchDate The instant at which the IO thread received
+ * the operation request.
+ * @param aExecutionDuration The duration of the operation on the
+ * IO thread.
+ */
+ void Init(TimeStamp aDispatchDate,
+ TimeDuration aExecutionDuration) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mDispatchDuration = (aDispatchDate - mStartDate);
+ mExecutionDuration = aExecutionDuration;
+ }
+
+ /**
+ * Drop any data that could lead to a cycle.
+ */
+ void DropJSData() {
+ mCachedResult = JS::UndefinedValue();
+ }
+
+protected:
+ virtual ~AbstractResult() {
+ MOZ_ASSERT(NS_IsMainThread());
+ DropJSData();
+ mozilla::DropJSObjects(this);
+ }
+
+ virtual nsresult GetCacheableResult(JSContext *cx, JS::MutableHandleValue aResult) = 0;
+
+private:
+ TimeStamp mStartDate;
+ TimeDuration mDispatchDuration;
+ TimeDuration mExecutionDuration;
+ JS::Heap<JS::Value> mCachedResult;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult)
+ NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCachedResult)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult)
+ tmp->DropJSData();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMETHODIMP
+AbstractResult::GetDispatchDurationMS(double *aDispatchDuration)
+{
+ *aDispatchDuration = mDispatchDuration.ToMilliseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AbstractResult::GetExecutionDurationMS(double *aExecutionDuration)
+{
+ *aExecutionDuration = mExecutionDuration.ToMilliseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AbstractResult::GetResult(JSContext *cx, JS::MutableHandleValue aResult)
+{
+ if (mCachedResult.isUndefined()) {
+ nsresult rv = GetCacheableResult(cx, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mCachedResult = aResult;
+ return NS_OK;
+ }
+ aResult.set(mCachedResult);
+ return NS_OK;
+}
+
+/**
+ * Return a result as a string.
+ *
+ * In this implementation, attribute |result| is a string. Strings are
+ * passed to JS without copy.
+ */
+class StringResult final : public AbstractResult
+{
+public:
+ explicit StringResult(TimeStamp aStartDate)
+ : AbstractResult(aStartDate)
+ {
+ }
+
+ /**
+ * Initialize the object once the contents of the result as available.
+ *
+ * @param aContents The string to pass to JavaScript. Ownership of the
+ * string and its contents is passed to StringResult. The string must
+ * be valid UTF-16.
+ */
+ void Init(TimeStamp aDispatchDate,
+ TimeDuration aExecutionDuration,
+ nsString& aContents) {
+ AbstractResult::Init(aDispatchDate, aExecutionDuration);
+ mContents = aContents;
+ }
+
+protected:
+ nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) override;
+
+private:
+ nsString mContents;
+};
+
+nsresult
+StringResult::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mContents.get());
+
+ // Convert mContents to a js string without copy. Note that this
+ // may have the side-effect of stealing the contents of the string
+ // from XPCOM and into JS.
+ if (!xpc::StringToJsval(cx, mContents, aResult)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+
+/**
+ * Return a result as a Uint8Array.
+ *
+ * In this implementation, attribute |result| is a Uint8Array. The array
+ * is passed to JS without memory copy.
+ */
+class TypedArrayResult final : public AbstractResult
+{
+public:
+ explicit TypedArrayResult(TimeStamp aStartDate)
+ : AbstractResult(aStartDate)
+ {
+ }
+
+ /**
+ * @param aContents The contents to pass to JS. Calling this method.
+ * transmits ownership of the ArrayBufferContents to the TypedArrayResult.
+ * Do not reuse this value anywhere else.
+ */
+ void Init(TimeStamp aDispatchDate,
+ TimeDuration aExecutionDuration,
+ ArrayBufferContents aContents) {
+ AbstractResult::Init(aDispatchDate, aExecutionDuration);
+ mContents = aContents;
+ }
+
+protected:
+ nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) override;
+private:
+ ScopedArrayBufferContents mContents;
+};
+
+nsresult
+TypedArrayResult::GetCacheableResult(JSContext* cx, JS::MutableHandle<JS::Value> aResult)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // We cannot simply construct a typed array using contents.data as
+ // this would allow us to have several otherwise unrelated
+ // ArrayBuffers with the same underlying C buffer. As this would be
+ // very unsafe, we need to cache the result once we have it.
+
+ const ArrayBufferContents& contents = mContents.get();
+ MOZ_ASSERT(contents.data);
+
+ JS::Rooted<JSObject*>
+ arrayBuffer(cx, JS_NewArrayBufferWithContents(cx, contents.nbytes, contents.data));
+ if (!arrayBuffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ JS::Rooted<JSObject*>
+ result(cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer,
+ 0, contents.nbytes));
+ if (!result) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ // The memory of contents has been allocated on a thread that
+ // doesn't have a JSRuntime, hence without a context. Now that we
+ // have a context, attach the memory to where it belongs.
+ JS_updateMallocCounter(cx, contents.nbytes);
+ mContents.forget();
+
+ aResult.setObject(*result);
+ return NS_OK;
+}
+
+//////// Callback events
+
+/**
+ * An event used to notify asynchronously of an error.
+ */
+class ErrorEvent final : public Runnable {
+public:
+ /**
+ * @param aOnSuccess The success callback.
+ * @param aOnError The error callback.
+ * @param aDiscardedResult The discarded result.
+ * @param aOperation The name of the operation, used for error reporting.
+ * @param aOSError The OS error of the operation, as returned by errno/
+ * GetLastError().
+ *
+ * Note that we pass both the success callback and the error
+ * callback, as well as the discarded result to ensure that they are
+ * all released on the main thread, rather than on the IO thread
+ * (which would hopefully segfault). Also, we pass the callbacks as
+ * alread_AddRefed to ensure that we do not manipulate main-thread
+ * only refcounters off the main thread.
+ */
+ ErrorEvent(nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError,
+ already_AddRefed<AbstractResult>& aDiscardedResult,
+ const nsACString& aOperation,
+ int32_t aOSError)
+ : mOnSuccess(aOnSuccess)
+ , mOnError(aOnError)
+ , mDiscardedResult(aDiscardedResult)
+ , mOSError(aOSError)
+ , mOperation(aOperation)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ (void)mOnError->Complete(mOperation, mOSError);
+
+ // Ensure that the callbacks are released on the main thread.
+ mOnSuccess = nullptr;
+ mOnError = nullptr;
+ mDiscardedResult = nullptr;
+
+ return NS_OK;
+ }
+ private:
+ // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally
+ // xpconnect values, which cannot be manipulated with nsCOMPtr off
+ // the main thread. We store both the success callback and the
+ // error callback to ensure that they are safely released on the
+ // main thread.
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
+ RefPtr<AbstractResult> mDiscardedResult;
+ int32_t mOSError;
+ nsCString mOperation;
+};
+
+/**
+ * An event used to notify of a success.
+ */
+class SuccessEvent final : public Runnable {
+public:
+ /**
+ * @param aOnSuccess The success callback.
+ * @param aOnError The error callback.
+ *
+ * Note that we pass both the success callback and the error
+ * callback to ensure that they are both released on the main
+ * thread, rather than on the IO thread (which would hopefully
+ * segfault). Also, we pass them as alread_AddRefed to ensure that
+ * we do not manipulate xpconnect refcounters off the main thread
+ * (which is illegal).
+ */
+ SuccessEvent(nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError,
+ already_AddRefed<nsINativeOSFileResult>& aResult)
+ : mOnSuccess(aOnSuccess)
+ , mOnError(aOnError)
+ , mResult(aResult)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ (void)mOnSuccess->Complete(mResult);
+
+ // Ensure that the callbacks are released on the main thread.
+ mOnSuccess = nullptr;
+ mOnError = nullptr;
+ mResult = nullptr;
+
+ return NS_OK;
+ }
+ private:
+ // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally
+ // xpconnect values, which cannot be manipulated with nsCOMPtr off
+ // the main thread. We store both the success callback and the
+ // error callback to ensure that they are safely released on the
+ // main thread.
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
+ RefPtr<nsINativeOSFileResult> mResult;
+};
+
+
+//////// Action events
+
+/**
+ * Base class shared by actions.
+ */
+class AbstractDoEvent: public Runnable {
+public:
+ AbstractDoEvent(nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
+ : mOnSuccess(aOnSuccess)
+ , mOnError(aOnError)
+#if defined(DEBUG)
+ , mResolved(false)
+#endif // defined(DEBUG)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ /**
+ * Fail, asynchronously.
+ */
+ void Fail(const nsACString& aOperation,
+ already_AddRefed<AbstractResult>&& aDiscardedResult,
+ int32_t aOSError = 0) {
+ Resolve();
+ RefPtr<ErrorEvent> event = new ErrorEvent(mOnSuccess,
+ mOnError,
+ aDiscardedResult,
+ aOperation,
+ aOSError);
+ nsresult rv = NS_DispatchToMainThread(event);
+ if (NS_FAILED(rv)) {
+ // Last ditch attempt to release on the main thread - some of
+ // the members of event are not thread-safe, so letting the
+ // pointer go out of scope would cause a crash.
+ NS_ReleaseOnMainThread(event.forget());
+ }
+ }
+
+ /**
+ * Succeed, asynchronously.
+ */
+ void Succeed(already_AddRefed<nsINativeOSFileResult>&& aResult) {
+ Resolve();
+ RefPtr<SuccessEvent> event = new SuccessEvent(mOnSuccess,
+ mOnError,
+ aResult);
+ nsresult rv = NS_DispatchToMainThread(event);
+ if (NS_FAILED(rv)) {
+ // Last ditch attempt to release on the main thread - some of
+ // the members of event are not thread-safe, so letting the
+ // pointer go out of scope would cause a crash.
+ NS_ReleaseOnMainThread(event.forget());
+ }
+
+ }
+
+private:
+
+ /**
+ * Mark the event as complete, for debugging purposes.
+ */
+ void Resolve() {
+#if defined(DEBUG)
+ MOZ_ASSERT(!mResolved);
+ mResolved = true;
+#endif // defined(DEBUG)
+ }
+
+private:
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> mOnSuccess;
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> mOnError;
+#if defined(DEBUG)
+ // |true| once the action is complete
+ bool mResolved;
+#endif // defined(DEBUG)
+};
+
+/**
+ * An abstract event implementing reading from a file.
+ *
+ * Concrete subclasses are responsible for handling the
+ * data obtained from the file and possibly post-processing it.
+ */
+class AbstractReadEvent: public AbstractDoEvent {
+public:
+ /**
+ * @param aPath The path of the file.
+ */
+ AbstractReadEvent(const nsAString& aPath,
+ const uint64_t aBytes,
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
+ : AbstractDoEvent(aOnSuccess, aOnError)
+ , mPath(aPath)
+ , mBytes(aBytes)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ TimeStamp dispatchDate = TimeStamp::Now();
+
+ nsresult rv = BeforeRead();
+ if (NS_FAILED(rv)) {
+ // Error reporting is handled by BeforeRead();
+ return NS_OK;
+ }
+
+ ScopedArrayBufferContents buffer;
+ rv = Read(buffer);
+ if (NS_FAILED(rv)) {
+ // Error reporting is handled by Read();
+ return NS_OK;
+ }
+
+ AfterRead(dispatchDate, buffer);
+ return NS_OK;
+ }
+
+ private:
+ /**
+ * Read synchronously.
+ *
+ * Must be called off the main thread.
+ *
+ * @param aBuffer The destination buffer.
+ */
+ nsresult Read(ScopedArrayBufferContents& aBuffer)
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ ScopedPRFileDesc file;
+#if defined(XP_WIN)
+ // On Windows, we can't use PR_OpenFile because it doesn't
+ // handle UTF-16 encoding, which is pretty bad. In addition,
+ // PR_OpenFile opens files without sharing, which is not the
+ // general semantics of OS.File.
+ HANDLE handle =
+ ::CreateFileW(mPath.get(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ /*Security attributes*/nullptr,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
+ /*Template file*/ nullptr);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ Fail(NS_LITERAL_CSTRING("open"), nullptr, ::GetLastError());
+ return NS_ERROR_FAILURE;
+ }
+
+ file = PR_ImportFile((PROsfd)handle);
+ if (!file) {
+ // |file| is closed by PR_ImportFile
+ Fail(NS_LITERAL_CSTRING("ImportFile"), nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+
+#else
+ // On other platforms, PR_OpenFile will do.
+ NS_ConvertUTF16toUTF8 path(mPath);
+ file = PR_OpenFile(path.get(), PR_RDONLY, 0);
+ if (!file) {
+ Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+
+#endif // defined(XP_XIN)
+
+ PRFileInfo64 stat;
+ if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) {
+ Fail(NS_LITERAL_CSTRING("stat"), nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t bytes = std::min((uint64_t)stat.size, mBytes);
+ if (bytes > UINT32_MAX) {
+ Fail(NS_LITERAL_CSTRING("Arithmetics"), nullptr, OS_ERROR_INVAL);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aBuffer.Allocate(bytes)) {
+ Fail(NS_LITERAL_CSTRING("allocate"), nullptr, OS_ERROR_NOMEM);
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t total_read = 0;
+ int32_t just_read = 0;
+ char* dest_chars = reinterpret_cast<char*>(aBuffer.rwget().data);
+ do {
+ just_read = PR_Read(file, dest_chars + total_read,
+ std::min(uint64_t(PR_INT32_MAX), bytes - total_read));
+ if (just_read == -1) {
+ Fail(NS_LITERAL_CSTRING("read"), nullptr, PR_GetOSError());
+ return NS_ERROR_FAILURE;
+ }
+ total_read += just_read;
+ } while (just_read != 0 && total_read < bytes);
+ if (total_read != bytes) {
+ // We seem to have a race condition here.
+ Fail(NS_LITERAL_CSTRING("read"), nullptr, OS_ERROR_RACE);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+protected:
+ /**
+ * Any steps that need to be taken before reading.
+ *
+ * In case of error, this method should call Fail() and return
+ * a failure code.
+ */
+ virtual
+ nsresult BeforeRead() {
+ return NS_OK;
+ }
+
+ /**
+ * Proceed after reading.
+ */
+ virtual
+ void AfterRead(TimeStamp aDispatchDate, ScopedArrayBufferContents& aBuffer) = 0;
+
+ protected:
+ const nsString mPath;
+ const uint64_t mBytes;
+};
+
+/**
+ * An implementation of a Read event that provides the data
+ * as a TypedArray.
+ */
+class DoReadToTypedArrayEvent final : public AbstractReadEvent {
+public:
+ DoReadToTypedArrayEvent(const nsAString& aPath,
+ const uint32_t aBytes,
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
+ : AbstractReadEvent(aPath, aBytes,
+ aOnSuccess, aOnError)
+ , mResult(new TypedArrayResult(TimeStamp::Now()))
+ { }
+
+ ~DoReadToTypedArrayEvent() {
+ // If AbstractReadEvent::Run() has bailed out, we may need to cleanup
+ // mResult, which is main-thread only data
+ if (!mResult) {
+ return;
+ }
+ NS_ReleaseOnMainThread(mResult.forget());
+ }
+
+protected:
+ void AfterRead(TimeStamp aDispatchDate,
+ ScopedArrayBufferContents& aBuffer) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+ mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBuffer.forget());
+ Succeed(mResult.forget());
+ }
+
+ private:
+ RefPtr<TypedArrayResult> mResult;
+};
+
+/**
+ * An implementation of a Read event that provides the data
+ * as a JavaScript string.
+ */
+class DoReadToStringEvent final : public AbstractReadEvent {
+public:
+ DoReadToStringEvent(const nsAString& aPath,
+ const nsACString& aEncoding,
+ const uint32_t aBytes,
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback>& aOnSuccess,
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback>& aOnError)
+ : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError)
+ , mEncoding(aEncoding)
+ , mResult(new StringResult(TimeStamp::Now()))
+ { }
+
+ ~DoReadToStringEvent() {
+ // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup
+ // mResult, which is main-thread only data
+ if (!mResult) {
+ return;
+ }
+ NS_ReleaseOnMainThread(mResult.forget());
+ }
+
+protected:
+ nsresult BeforeRead() override {
+ // Obtain the decoder. We do this before reading to avoid doing
+ // any unnecessary I/O in case the name of the encoding is incorrect.
+ MOZ_ASSERT(!NS_IsMainThread());
+ nsAutoCString encodingName;
+ if (!dom::EncodingUtils::FindEncodingForLabel(mEncoding, encodingName)) {
+ Fail(NS_LITERAL_CSTRING("Decode"), mResult.forget(), OS_ERROR_INVAL);
+ return NS_ERROR_FAILURE;
+ }
+ mDecoder = dom::EncodingUtils::DecoderForEncoding(encodingName);
+ if (!mDecoder) {
+ Fail(NS_LITERAL_CSTRING("DecoderForEncoding"), mResult.forget(), OS_ERROR_INVAL);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ void AfterRead(TimeStamp aDispatchDate,
+ ScopedArrayBufferContents& aBuffer) override {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ int32_t maxChars;
+ const char* sourceChars = reinterpret_cast<const char*>(aBuffer.get().data);
+ int32_t sourceBytes = aBuffer.get().nbytes;
+ if (sourceBytes < 0) {
+ Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE);
+ return;
+ }
+
+ nsresult rv = mDecoder->GetMaxLength(sourceChars, sourceBytes, &maxChars);
+ if (NS_FAILED(rv)) {
+ Fail(NS_LITERAL_CSTRING("GetMaxLength"), mResult.forget(), OS_ERROR_INVAL);
+ return;
+ }
+
+ if (maxChars < 0) {
+ Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE);
+ return;
+ }
+
+ nsString resultString;
+ resultString.SetLength(maxChars);
+ if (resultString.Length() != (nsString::size_type)maxChars) {
+ Fail(NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE);
+ return;
+ }
+
+
+ rv = mDecoder->Convert(sourceChars, &sourceBytes,
+ resultString.BeginWriting(), &maxChars);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ resultString.SetLength(maxChars);
+
+ mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, resultString);
+ Succeed(mResult.forget());
+ }
+
+ private:
+ nsCString mEncoding;
+ nsCOMPtr<nsIUnicodeDecoder> mDecoder;
+ RefPtr<StringResult> mResult;
+};
+
+} // namespace
+
+// The OS.File service
+
+NS_IMPL_ISUPPORTS(NativeOSFileInternalsService, nsINativeOSFileInternalsService);
+
+NS_IMETHODIMP
+NativeOSFileInternalsService::Read(const nsAString& aPath,
+ JS::HandleValue aOptions,
+ nsINativeOSFileSuccessCallback *aOnSuccess,
+ nsINativeOSFileErrorCallback *aOnError,
+ JSContext* cx)
+{
+ // Extract options
+ nsCString encoding;
+ uint64_t bytes = UINT64_MAX;
+
+ if (aOptions.isObject()) {
+ dom::NativeOSFileReadOptions dict;
+ if (!dict.Init(cx, aOptions)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (dict.mEncoding.WasPassed()) {
+ CopyUTF16toUTF8(dict.mEncoding.Value(), encoding);
+ }
+
+ if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) {
+ bytes = dict.mBytes.Value().Value();
+ }
+ }
+
+ // Prepare the off main thread event and dispatch it
+ nsCOMPtr<nsINativeOSFileSuccessCallback> onSuccess(aOnSuccess);
+ nsMainThreadPtrHandle<nsINativeOSFileSuccessCallback> onSuccessHandle(
+ new nsMainThreadPtrHolder<nsINativeOSFileSuccessCallback>(onSuccess));
+ nsCOMPtr<nsINativeOSFileErrorCallback> onError(aOnError);
+ nsMainThreadPtrHandle<nsINativeOSFileErrorCallback> onErrorHandle(
+ new nsMainThreadPtrHolder<nsINativeOSFileErrorCallback>(onError));
+
+ RefPtr<AbstractDoEvent> event;
+ if (encoding.IsEmpty()) {
+ event = new DoReadToTypedArrayEvent(aPath, bytes,
+ onSuccessHandle,
+ onErrorHandle);
+ } else {
+ event = new DoReadToStringEvent(aPath, encoding, bytes,
+ onSuccessHandle,
+ onErrorHandle);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+} // namespace mozilla
+
diff --git a/components/osfile/NativeOSFileInternals.h b/components/osfile/NativeOSFileInternals.h
new file mode 100644
index 000000000..2da929357
--- /dev/null
+++ b/components/osfile/NativeOSFileInternals.h
@@ -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/. */
+
+#ifndef mozilla_nativeosfileinternalservice_h__
+#define mozilla_nativeosfileinternalservice_h__
+
+#include "nsINativeOSFileInternals.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+
+class NativeOSFileInternalsService final : public nsINativeOSFileInternalsService {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINATIVEOSFILEINTERNALSSERVICE
+private:
+ ~NativeOSFileInternalsService() {}
+ // Avoid accidental use of built-in operator=
+ void operator=(const NativeOSFileInternalsService& other) = delete;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_finalizationwitnessservice_h__
diff --git a/components/osfile/modules/moz.build b/components/osfile/modules/moz.build
new file mode 100644
index 000000000..27f3c97cb
--- /dev/null
+++ b/components/osfile/modules/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_JS_MODULES.osfile += [
+ 'osfile_async_front.jsm',
+ 'osfile_async_worker.js',
+ 'osfile_native.jsm',
+ 'osfile_shared_allthreads.jsm',
+ 'osfile_shared_front.jsm',
+ 'osfile_unix_allthreads.jsm',
+ 'osfile_unix_back.jsm',
+ 'osfile_unix_front.jsm',
+ 'osfile_win_allthreads.jsm',
+ 'osfile_win_back.jsm',
+ 'osfile_win_front.jsm',
+ 'ospath.jsm',
+ 'ospath_unix.jsm',
+ 'ospath_win.jsm',
+]
diff --git a/components/osfile/modules/osfile_async_front.jsm b/components/osfile/modules/osfile_async_front.jsm
new file mode 100644
index 000000000..826266ce5
--- /dev/null
+++ b/components/osfile/modules/osfile_async_front.jsm
@@ -0,0 +1,1476 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Asynchronous front-end for OS.File.
+ *
+ * This front-end is meant to be imported from the main thread. In turn,
+ * it spawns one worker (perhaps more in the future) and delegates all
+ * disk I/O to this worker.
+ *
+ * Documentation note: most of the functions and methods in this module
+ * return promises. For clarity, we denote as follows a promise that may resolve
+ * with type |A| and some value |value| or reject with type |B| and some
+ * reason |reason|
+ * @resolves {A} value
+ * @rejects {B} reason
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["OS"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+var SharedAll = {};
+Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Timer.jsm", this);
+
+
+// Boilerplate, to simplify the transition to require()
+var LOG = SharedAll.LOG.bind(SharedAll, "Controller");
+var isTypedArray = SharedAll.isTypedArray;
+
+// The constructor for file errors.
+var SysAll = {};
+if (SharedAll.Constants.Win) {
+ Cu.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", SysAll);
+} else if (SharedAll.Constants.libc) {
+ Cu.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", SysAll);
+} else {
+ throw new Error("I am neither under Windows nor under a Posix system");
+}
+var OSError = SysAll.Error;
+var Type = SysAll.Type;
+
+var Path = {};
+Cu.import("resource://gre/modules/osfile/ospath.jsm", Path);
+
+// The library of promises.
+Cu.import("resource://gre/modules/Promise.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+
+// The implementation of communications
+Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
+var Native = Cu.import("resource://gre/modules/osfile/osfile_native.jsm", {});
+
+
+// It's possible for osfile.jsm to get imported before the profile is
+// set up. In this case, some path constants aren't yet available.
+// Here, we make them lazy loaders.
+
+function lazyPathGetter(constProp, dirKey) {
+ return function () {
+ let path;
+ try {
+ path = Services.dirsvc.get(dirKey, Ci.nsIFile).path;
+ delete SharedAll.Constants.Path[constProp];
+ SharedAll.Constants.Path[constProp] = path;
+ } catch (ex) {
+ // Ignore errors if the value still isn't available. Hopefully
+ // the next access will return it.
+ }
+
+ return path;
+ };
+}
+
+for (let [constProp, dirKey] of [
+ ["localProfileDir", "ProfLD"],
+ ["profileDir", "ProfD"],
+ ["userApplicationDataDir", "UAppData"],
+ ["winAppDataDir", "AppData"],
+ ["winStartMenuProgsDir", "Progs"],
+ ]) {
+
+ if (constProp in SharedAll.Constants.Path) {
+ continue;
+ }
+
+ LOG("Installing lazy getter for OS.Constants.Path." + constProp +
+ " because it isn't defined and profile may not be loaded.");
+ Object.defineProperty(SharedAll.Constants.Path, constProp, {
+ get: lazyPathGetter(constProp, dirKey),
+ });
+}
+
+/**
+ * Return a shallow clone of the enumerable properties of an object.
+ */
+var clone = SharedAll.clone;
+
+/**
+ * Extract a shortened version of an object, fit for logging.
+ *
+ * This function returns a copy of the original object in which all
+ * long strings, Arrays, TypedArrays, ArrayBuffers are removed and
+ * replaced with placeholders. Use this function to sanitize objects
+ * if you wish to log them or to keep them in memory.
+ *
+ * @param {*} obj The obj to shorten.
+ * @return {*} array A shorter object, fit for logging.
+ */
+function summarizeObject(obj) {
+ if (!obj) {
+ return null;
+ }
+ if (typeof obj == "string") {
+ if (obj.length > 1024) {
+ return {"Long string": obj.length};
+ }
+ return obj;
+ }
+ if (typeof obj == "object") {
+ if (Array.isArray(obj)) {
+ if (obj.length > 32) {
+ return {"Long array": obj.length};
+ }
+ return obj.map(summarizeObject);
+ }
+ if ("byteLength" in obj) {
+ // Assume TypedArray or ArrayBuffer
+ return {"Binary Data": obj.byteLength};
+ }
+ let result = {};
+ for (let k of Object.keys(obj)) {
+ result[k] = summarizeObject(obj[k]);
+ }
+ return result;
+ }
+ return obj;
+}
+
+// In order to expose Scheduler to the unfiltered Cu.import return value variant
+// on B2G we need to save it to `this`. This does not make it public;
+// EXPORTED_SYMBOLS still controls that in all cases.
+var Scheduler = this.Scheduler = {
+
+ /**
+ * |true| once we have sent at least one message to the worker.
+ * This field is unaffected by resetting the worker.
+ */
+ launched: false,
+
+ /**
+ * |true| once shutdown has begun i.e. we should reject any
+ * message, including resets.
+ */
+ shutdown: false,
+
+ /**
+ * A promise resolved once all currently pending operations are complete.
+ *
+ * This promise is never rejected and the result is always undefined.
+ */
+ queue: Promise.resolve(),
+
+ /**
+ * A promise resolved once all currently pending `kill` operations
+ * are complete.
+ *
+ * This promise is never rejected and the result is always undefined.
+ */
+ _killQueue: Promise.resolve(),
+
+ /**
+ * Miscellaneous debugging information
+ */
+ Debugging: {
+ /**
+ * The latest message sent and still waiting for a reply.
+ */
+ latestSent: undefined,
+
+ /**
+ * The latest reply received, or null if we are waiting for a reply.
+ */
+ latestReceived: undefined,
+
+ /**
+ * Number of messages sent to the worker. This includes the
+ * initial SET_DEBUG, if applicable.
+ */
+ messagesSent: 0,
+
+ /**
+ * Total number of messages ever queued, including the messages
+ * sent.
+ */
+ messagesQueued: 0,
+
+ /**
+ * Number of messages received from the worker.
+ */
+ messagesReceived: 0,
+ },
+
+ /**
+ * A timer used to automatically shut down the worker after some time.
+ */
+ resetTimer: null,
+
+ /**
+ * The worker to which to send requests.
+ *
+ * If the worker has never been created or has been reset, this is a
+ * fresh worker, initialized with osfile_async_worker.js.
+ *
+ * @type {PromiseWorker}
+ */
+ get worker() {
+ if (!this._worker) {
+ // Either the worker has never been created or it has been
+ // reset. In either case, it is time to instantiate the worker.
+ this._worker = new BasePromiseWorker("resource://gre/modules/osfile/osfile_async_worker.js");
+ this._worker.log = LOG;
+ this._worker.ExceptionHandlers["OS.File.Error"] = OSError.fromMsg;
+ }
+ return this._worker;
+ },
+
+ _worker: null,
+
+ /**
+ * Prepare to kill the OS.File worker after a few seconds.
+ */
+ restartTimer: function(arg) {
+ let delay = Services.prefs.getIntPref("osfile.reset_worker_delay", 0);
+
+ if (!delay) {
+ // Don't auto-shutdown if we don't have a delay preference set.
+ return;
+ }
+
+ if (this.resetTimer) {
+ clearTimeout(this.resetTimer);
+ }
+ this.resetTimer = setTimeout(
+ () => Scheduler.kill({reset: true, shutdown: false}),
+ delay
+ );
+ },
+
+ /**
+ * Shutdown OS.File.
+ *
+ * @param {*} options
+ * - {boolean} shutdown If |true|, reject any further request. Otherwise,
+ * further requests will resurrect the worker.
+ * - {boolean} reset If |true|, instruct the worker to shutdown if this
+ * would not cause leaks. Otherwise, assume that the worker will be shutdown
+ * through some other mean.
+ */
+ kill: function({shutdown, reset}) {
+ // Grab the kill queue to make sure that we
+ // cannot be interrupted by another call to `kill`.
+ let killQueue = this._killQueue;
+
+ // Deactivate the queue, to ensure that no message is sent
+ // to an obsolete worker (we reactivate it in the `finally`).
+ // This needs to be done right now so that we maintain relative
+ // ordering with calls to post(), etc.
+ let deferred = Promise.defer();
+ let savedQueue = this.queue;
+ this.queue = deferred.promise;
+
+ return this._killQueue = Task.spawn(function*() {
+
+ yield killQueue;
+ // From this point, and until the end of the Task, we are the
+ // only call to `kill`, regardless of any `yield`.
+
+ yield savedQueue;
+
+ try {
+ // Enter critical section: no yield in this block
+ // (we want to make sure that we remain the only
+ // request in the queue).
+
+ if (!this.launched || this.shutdown || !this._worker) {
+ // Nothing to kill
+ this.shutdown = this.shutdown || shutdown;
+ this._worker = null;
+ return null;
+ }
+
+ // Exit critical section
+
+ let message = ["Meta_shutdown", [reset]];
+
+ Scheduler.latestReceived = [];
+ Scheduler.latestSent = [Date.now(),
+ Task.Debugging.generateReadableStack(new Error().stack),
+ ...message];
+
+ // Wait for result
+ let resources;
+ try {
+ resources = yield this._worker.post(...message);
+
+ Scheduler.latestReceived = [Date.now(), message];
+ } catch (ex) {
+ LOG("Could not dispatch Meta_reset", ex);
+ // It's most likely a programmer error, but we'll assume that
+ // the worker has been shutdown, as it's less risky than the
+ // opposite stance.
+ resources = {openedFiles: [], openedDirectoryIterators: [], killed: true};
+
+ Scheduler.latestReceived = [Date.now(), message, ex];
+ }
+
+ let {openedFiles, openedDirectoryIterators, killed} = resources;
+ if (!reset
+ && (openedFiles && openedFiles.length
+ || ( openedDirectoryIterators && openedDirectoryIterators.length))) {
+ // The worker still holds resources. Report them.
+
+ let msg = "";
+ if (openedFiles.length > 0) {
+ msg += "The following files are still open:\n" +
+ openedFiles.join("\n");
+ }
+ if (openedDirectoryIterators.length > 0) {
+ msg += "The following directory iterators are still open:\n" +
+ openedDirectoryIterators.join("\n");
+ }
+
+ LOG("WARNING: File descriptors leaks detected.\n" + msg);
+ }
+
+ // Make sure that we do not leave an invalid |worker| around.
+ if (killed || shutdown) {
+ this._worker = null;
+ }
+
+ this.shutdown = shutdown;
+
+ return resources;
+
+ } finally {
+ // Resume accepting messages. If we have set |shutdown| to |true|,
+ // any pending/future request will be rejected. Otherwise, any
+ // pending/future request will spawn a new worker if necessary.
+ deferred.resolve();
+ }
+
+ }.bind(this));
+ },
+
+ /**
+ * Push a task at the end of the queue.
+ *
+ * @param {function} code A function returning a Promise.
+ * This function will be executed once all the previously
+ * pushed tasks have completed.
+ * @return {Promise} A promise with the same behavior as
+ * the promise returned by |code|.
+ */
+ push: function(code) {
+ let promise = this.queue.then(code);
+ // By definition, |this.queue| can never reject.
+ this.queue = promise.then(null, () => undefined);
+ // Fork |promise| to ensure that uncaught errors are reported
+ return promise.then(null, null);
+ },
+
+ /**
+ * Post a message to the worker thread.
+ *
+ * @param {string} method The name of the method to call.
+ * @param {...} args The arguments to pass to the method. These arguments
+ * must be clonable.
+ * @return {Promise} A promise conveying the result/error caused by
+ * calling |method| with arguments |args|.
+ */
+ post: function post(method, args = undefined, closure = undefined) {
+ if (this.shutdown) {
+ LOG("OS.File is not available anymore. The following request has been rejected.",
+ method, args);
+ return Promise.reject(new Error("OS.File has been shut down. Rejecting post to " + method));
+ }
+ let firstLaunch = !this.launched;
+ this.launched = true;
+
+ if (firstLaunch && SharedAll.Config.DEBUG) {
+ // If we have delayed sending SET_DEBUG, do it now.
+ this.worker.post("SET_DEBUG", [true]);
+ Scheduler.Debugging.messagesSent++;
+ }
+
+ Scheduler.Debugging.messagesQueued++;
+ return this.push(Task.async(function*() {
+ if (this.shutdown) {
+ LOG("OS.File is not available anymore. The following request has been rejected.",
+ method, args);
+ throw new Error("OS.File has been shut down. Rejecting request to " + method);
+ }
+
+ // Update debugging information. As |args| may be quite
+ // expensive, we only keep a shortened version of it.
+ Scheduler.Debugging.latestReceived = null;
+ Scheduler.Debugging.latestSent = [Date.now(), method, summarizeObject(args)];
+
+ // Don't kill the worker just yet
+ Scheduler.restartTimer();
+
+
+ let reply;
+ try {
+ try {
+ Scheduler.Debugging.messagesSent++;
+ Scheduler.Debugging.latestSent = Scheduler.Debugging.latestSent.slice(0, 2);
+ reply = yield this.worker.post(method, args, closure);
+ Scheduler.Debugging.latestReceived = [Date.now(), summarizeObject(reply)];
+ return reply;
+ } finally {
+ Scheduler.Debugging.messagesReceived++;
+ }
+ } catch (error) {
+ Scheduler.Debugging.latestReceived = [Date.now(), error.message, error.fileName, error.lineNumber];
+ throw error;
+ } finally {
+ Scheduler.restartTimer();
+ }
+ }.bind(this)));
+ }
+
+};
+
+const PREF_OSFILE_LOG = "toolkit.osfile.log";
+const PREF_OSFILE_LOG_REDIRECT = "toolkit.osfile.log.redirect";
+
+/**
+ * Listen to PREF_OSFILE_LOG changes and update gShouldLog flag
+ * appropriately.
+ */
+Services.prefs.addObserver(PREF_OSFILE_LOG,
+ function prefObserver(aSubject, aTopic, aData) {
+ SharedAll.Config.DEBUG = Services.prefs.getBoolPref(PREF_OSFILE_LOG, SharedAll.Config.DEBUG);
+ if (Scheduler.launched) {
+ // Don't start the worker just to set this preference.
+ Scheduler.post("SET_DEBUG", [SharedAll.Config.DEBUG]);
+ }
+ }, false);
+SharedAll.Config.DEBUG = Services.prefs.getBoolPref(PREF_OSFILE_LOG, false);
+
+Services.prefs.addObserver(PREF_OSFILE_LOG_REDIRECT,
+ function prefObserver(aSubject, aTopic, aData) {
+ SharedAll.Config.TEST = Services.prefs.getBoolPref(PREF_OSFILE_LOG_REDIRECT, OS.Shared.TEST);
+ }, false);
+SharedAll.Config.TEST = Services.prefs.getBoolPref(PREF_OSFILE_LOG_REDIRECT, false);
+
+
+/**
+ * If |true|, use the native implementaiton of OS.File methods
+ * whenever possible. Otherwise, force the use of the JS version.
+ */
+var nativeWheneverAvailable = true;
+const PREF_OSFILE_NATIVE = "toolkit.osfile.native";
+Services.prefs.addObserver(PREF_OSFILE_NATIVE,
+ function prefObserver(aSubject, aTopic, aData) {
+ nativeWheneverAvailable = Services.prefs.getBoolPref(PREF_OSFILE_NATIVE, nativeWheneverAvailable);
+ }, false);
+
+
+// Update worker's DEBUG flag if it's true.
+// Don't start the worker just for this, though.
+if (SharedAll.Config.DEBUG && Scheduler.launched) {
+ Scheduler.post("SET_DEBUG", [true]);
+}
+
+// Observer topics used for monitoring shutdown
+const WEB_WORKERS_SHUTDOWN_TOPIC = "web-workers-shutdown";
+
+// Preference used to configure test shutdown observer.
+const PREF_OSFILE_TEST_SHUTDOWN_OBSERVER =
+ "toolkit.osfile.test.shutdown.observer";
+
+AsyncShutdown.webWorkersShutdown.addBlocker(
+ "OS.File: flush pending requests, warn about unclosed files, shut down service.",
+ Task.async(function*() {
+ // Give clients a last chance to enqueue requests.
+ yield Barriers.shutdown.wait({crashAfterMS: null});
+
+ // Wait until all requests are complete and kill the worker.
+ yield Scheduler.kill({reset: false, shutdown: true});
+ }),
+ () => {
+ let details = Barriers.getDetails();
+ details.clients = Barriers.shutdown.state;
+ return details;
+ }
+);
+
+
+// Attaching an observer for PREF_OSFILE_TEST_SHUTDOWN_OBSERVER to enable or
+// disable the test shutdown event observer.
+// Note: By default the PREF_OSFILE_TEST_SHUTDOWN_OBSERVER is unset.
+// Note: This is meant to be used for testing purposes only.
+Services.prefs.addObserver(PREF_OSFILE_TEST_SHUTDOWN_OBSERVER,
+ function prefObserver() {
+ // The temporary phase topic used to trigger the unclosed
+ // phase warning.
+ let TOPIC = Services.prefs.getCharPref(PREF_OSFILE_TEST_SHUTDOWN_OBSERVER,
+ "");
+ if (TOPIC) {
+ // Generate a phase, add a blocker.
+ // Note that this can work only if AsyncShutdown itself has been
+ // configured for testing by the testsuite.
+ let phase = AsyncShutdown._getPhase(TOPIC);
+ phase.addBlocker(
+ "(for testing purposes) OS.File: warn about unclosed files",
+ () => Scheduler.kill({shutdown: false, reset: false})
+ );
+ }
+ }, false);
+
+/**
+ * Representation of a file, with asynchronous methods.
+ *
+ * @param {*} fdmsg The _message_ representing the platform-specific file
+ * handle.
+ *
+ * @constructor
+ */
+var File = function File(fdmsg) {
+ // FIXME: At the moment, |File| does not close on finalize
+ // (see bug 777715)
+ this._fdmsg = fdmsg;
+ this._closeResult = null;
+ this._closed = null;
+};
+
+
+File.prototype = {
+ /**
+ * Close a file asynchronously.
+ *
+ * This method is idempotent.
+ *
+ * @return {promise}
+ * @resolves {null}
+ * @rejects {OS.File.Error}
+ */
+ close: function close() {
+ if (this._fdmsg != null) {
+ let msg = this._fdmsg;
+ this._fdmsg = null;
+ return this._closeResult =
+ Scheduler.post("File_prototype_close", [msg], this);
+ }
+ return this._closeResult;
+ },
+
+ /**
+ * Fetch information about the file.
+ *
+ * @return {promise}
+ * @resolves {OS.File.Info} The latest information about the file.
+ * @rejects {OS.File.Error}
+ */
+ stat: function stat() {
+ return Scheduler.post("File_prototype_stat", [this._fdmsg], this).then(
+ File.Info.fromMsg
+ );
+ },
+
+ /**
+ * Write bytes from a buffer to this file.
+ *
+ * Note that, by default, this function may perform several I/O
+ * operations to ensure that the buffer is fully written.
+ *
+ * @param {Typed array | C pointer} buffer The buffer in which the
+ * the bytes are stored. The buffer must be large enough to
+ * accomodate |bytes| bytes. Using the buffer before the operation
+ * is complete is a BAD IDEA.
+ * @param {*=} options Optionally, an object that may contain the
+ * following fields:
+ * - {number} bytes The number of |bytes| to write from the buffer. If
+ * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
+ * if |buffer| is a C pointer.
+ *
+ * @return {number} The number of bytes actually written.
+ */
+ write: function write(buffer, options = {}) {
+ // If |buffer| is a typed array and there is no |bytes| options,
+ // we need to extract the |byteLength| now, as it will be lost
+ // by communication.
+ // Options might be a nullish value, so better check for that before using
+ // the |in| operator.
+ if (isTypedArray(buffer) && !(options && "bytes" in options)) {
+ // Preserve reference to option |outExecutionDuration|, if it is passed.
+ options = clone(options, ["outExecutionDuration"]);
+ options.bytes = buffer.byteLength;
+ }
+ return Scheduler.post("File_prototype_write",
+ [this._fdmsg,
+ Type.void_t.in_ptr.toMsg(buffer),
+ options],
+ buffer/*Ensure that |buffer| is not gc-ed*/);
+ },
+
+ /**
+ * Read bytes from this file to a new buffer.
+ *
+ * @param {number=} bytes If unspecified, read all the remaining bytes from
+ * this file. If specified, read |bytes| bytes, or less if the file does not
+ * contain that many bytes.
+ * @param {JSON} options
+ * @return {promise}
+ * @resolves {Uint8Array} An array containing the bytes read.
+ */
+ read: function read(nbytes, options = {}) {
+ let promise = Scheduler.post("File_prototype_read",
+ [this._fdmsg,
+ nbytes, options]);
+ return promise.then(
+ function onSuccess(data) {
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+ });
+ },
+
+ /**
+ * Return the current position in the file, as bytes.
+ *
+ * @return {promise}
+ * @resolves {number} The current position in the file,
+ * as a number of bytes since the start of the file.
+ */
+ getPosition: function getPosition() {
+ return Scheduler.post("File_prototype_getPosition",
+ [this._fdmsg]);
+ },
+
+ /**
+ * Set the current position in the file, as bytes.
+ *
+ * @param {number} pos A number of bytes.
+ * @param {number} whence The reference position in the file,
+ * which may be either POS_START (from the start of the file),
+ * POS_END (from the end of the file) or POS_CUR (from the
+ * current position in the file).
+ *
+ * @return {promise}
+ */
+ setPosition: function setPosition(pos, whence) {
+ return Scheduler.post("File_prototype_setPosition",
+ [this._fdmsg, pos, whence]);
+ },
+
+ /**
+ * Flushes the file's buffers and causes all buffered data
+ * to be written.
+ * Disk flushes are very expensive and therefore should be used carefully,
+ * sparingly and only in scenarios where it is vital that data survives
+ * system crashes. Even though the function will be executed off the
+ * main-thread, it might still affect the overall performance of any running
+ * application.
+ *
+ * @return {promise}
+ */
+ flush: function flush() {
+ return Scheduler.post("File_prototype_flush",
+ [this._fdmsg]);
+ },
+
+ /**
+ * Set the file's access permissions. This does nothing on Windows.
+ *
+ * This operation is likely to fail if applied to a file that was
+ * not created by the currently running program (more precisely,
+ * if it was created by a program running under a different OS-level
+ * user account). It may also fail, or silently do nothing, if the
+ * filesystem containing the file does not support access permissions.
+ *
+ * @param {*=} options Object specifying the requested permissions:
+ *
+ * - {number} unixMode The POSIX file mode to set on the file. If omitted,
+ * the POSIX file mode is reset to the default used by |OS.file.open|. If
+ * specified, the permissions will respect the process umask as if they
+ * had been specified as arguments of |OS.File.open|, unless the
+ * |unixHonorUmask| parameter tells otherwise.
+ * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
+ * modified by the process umask, as |OS.File.open| would have done. If
+ * false, the exact value of |unixMode| will be applied.
+ */
+ setPermissions: function setPermissions(options = {}) {
+ return Scheduler.post("File_prototype_setPermissions",
+ [this._fdmsg, options]);
+ }
+};
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * @return {promise}
+ * @rejects {TypeError}
+ * @rejects {OS.File.Error}
+ */
+File.prototype.setDates = function(accessDate, modificationDate) {
+ return Scheduler.post("File_prototype_setDates",
+ [this._fdmsg, accessDate, modificationDate], this);
+};
+
+/**
+ * Open a file asynchronously.
+ *
+ * @return {promise}
+ * @resolves {OS.File}
+ * @rejects {OS.Error}
+ */
+File.open = function open(path, mode, options) {
+ return Scheduler.post(
+ "open", [Type.path.toMsg(path), mode, options],
+ path
+ ).then(
+ function onSuccess(msg) {
+ return new File(msg);
+ }
+ );
+};
+
+/**
+ * Creates and opens a file with a unique name. By default, generate a random HEX number and use it to create a unique new file name.
+ *
+ * @param {string} path The path to the file.
+ * @param {*=} options Additional options for file opening. This
+ * implementation interprets the following fields:
+ *
+ * - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext.
+ * If |false| use HEX numbers ie: filename-A65BC0.ext
+ * - {number} maxReadableNumber Used to limit the amount of tries after a failed
+ * file creation. Default is 20.
+ *
+ * @return {Object} contains A file object{file} and the path{path}.
+ * @throws {OS.File.Error} If the file could not be opened.
+ */
+File.openUnique = function openUnique(path, options) {
+ return Scheduler.post(
+ "openUnique", [Type.path.toMsg(path), options],
+ path
+ ).then(
+ function onSuccess(msg) {
+ return {
+ path: msg.path,
+ file: new File(msg.file)
+ };
+ }
+ );
+};
+
+/**
+ * Get the information on the file.
+ *
+ * @return {promise}
+ * @resolves {OS.File.Info}
+ * @rejects {OS.Error}
+ */
+File.stat = function stat(path, options) {
+ return Scheduler.post(
+ "stat", [Type.path.toMsg(path), options],
+ path).then(File.Info.fromMsg);
+};
+
+
+/**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * @return {promise}
+ * @rejects {TypeError}
+ * @rejects {OS.File.Error}
+ */
+File.setDates = function setDates(path, accessDate, modificationDate) {
+ return Scheduler.post("setDates",
+ [Type.path.toMsg(path), accessDate, modificationDate],
+ this);
+};
+
+/**
+ * Set the file's access permissions. This does nothing on Windows.
+ *
+ * This operation is likely to fail if applied to a file that was
+ * not created by the currently running program (more precisely,
+ * if it was created by a program running under a different OS-level
+ * user account). It may also fail, or silently do nothing, if the
+ * filesystem containing the file does not support access permissions.
+ *
+ * @param {string} path The path to the file.
+ * @param {*=} options Object specifying the requested permissions:
+ *
+ * - {number} unixMode The POSIX file mode to set on the file. If omitted,
+ * the POSIX file mode is reset to the default used by |OS.file.open|. If
+ * specified, the permissions will respect the process umask as if they
+ * had been specified as arguments of |OS.File.open|, unless the
+ * |unixHonorUmask| parameter tells otherwise.
+ * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
+ * modified by the process umask, as |OS.File.open| would have done. If
+ * false, the exact value of |unixMode| will be applied.
+ */
+File.setPermissions = function setPermissions(path, options = {}) {
+ return Scheduler.post("setPermissions",
+ [Type.path.toMsg(path), options]);
+};
+
+/**
+ * Fetch the current directory
+ *
+ * @return {promise}
+ * @resolves {string} The current directory, as a path usable with OS.Path
+ * @rejects {OS.Error}
+ */
+File.getCurrentDirectory = function getCurrentDirectory() {
+ return Scheduler.post(
+ "getCurrentDirectory"
+ ).then(Type.path.fromMsg);
+};
+
+/**
+ * Change the current directory
+ *
+ * @param {string} path The OS-specific path to the current directory.
+ * You should use the methods of OS.Path and the constants of OS.Constants.Path
+ * to build OS-specific paths in a portable manner.
+ *
+ * @return {promise}
+ * @resolves {null}
+ * @rejects {OS.Error}
+ */
+File.setCurrentDirectory = function setCurrentDirectory(path) {
+ return Scheduler.post(
+ "setCurrentDirectory", [Type.path.toMsg(path)], path
+ );
+};
+
+/**
+ * Copy a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be copied.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If true, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ *
+ * @rejects {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be copied with the file. The
+ * behavior may not be the same across all platforms.
+*/
+File.copy = function copy(sourcePath, destPath, options) {
+ return Scheduler.post("copy", [Type.path.toMsg(sourcePath),
+ Type.path.toMsg(destPath), options], [sourcePath, destPath]);
+};
+
+/**
+ * Move a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be moved.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ *
+ * @returns {Promise}
+ * @rejects {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be moved with the file. The
+ * behavior may not be the same across all platforms.
+ */
+File.move = function move(sourcePath, destPath, options) {
+ return Scheduler.post("move", [Type.path.toMsg(sourcePath),
+ Type.path.toMsg(destPath), options], [sourcePath, destPath]);
+};
+
+/**
+ * Create a symbolic link to a source.
+ *
+ * @param {string} sourcePath The platform-specific path to which
+ * the symbolic link should point.
+ * @param {string} destPath The platform-specific path at which the
+ * symbolic link should be created.
+ *
+ * @returns {Promise}
+ * @rejects {OS.File.Error} In case of any error.
+ */
+if (!SharedAll.Constants.Win) {
+ File.unixSymLink = function unixSymLink(sourcePath, destPath) {
+ return Scheduler.post("unixSymLink", [Type.path.toMsg(sourcePath),
+ Type.path.toMsg(destPath)], [sourcePath, destPath]);
+ };
+}
+
+/**
+ * Gets the number of bytes available on disk to the current user.
+ *
+ * @param {string} Platform-specific path to a directory on the disk to
+ * query for free available bytes.
+ *
+ * @return {number} The number of bytes available for the current user.
+ * @throws {OS.File.Error} In case of any error.
+ */
+File.getAvailableFreeSpace = function getAvailableFreeSpace(sourcePath) {
+ return Scheduler.post("getAvailableFreeSpace",
+ [Type.path.toMsg(sourcePath)], sourcePath
+ ).then(Type.uint64_t.fromMsg);
+};
+
+/**
+ * Remove an empty directory.
+ *
+ * @param {string} path The name of the directory to remove.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |true|, do not fail if the
+ * directory does not exist yet.
+ */
+File.removeEmptyDir = function removeEmptyDir(path, options) {
+ return Scheduler.post("removeEmptyDir",
+ [Type.path.toMsg(path), options], path);
+};
+
+/**
+ * Remove an existing file.
+ *
+ * @param {string} path The name of the file.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the file does
+ * not exist. |true| by default.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+File.remove = function remove(path, options) {
+ return Scheduler.post("remove",
+ [Type.path.toMsg(path), options], path);
+};
+
+
+
+/**
+ * Create a directory and, optionally, its parent directories.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options.
+ *
+ * - {string} from If specified, the call to |makeDir| creates all the
+ * ancestors of |path| that are descendants of |from|. Note that |path|
+ * must be a descendant of |from|, and that |from| and its existing
+ * subdirectories present in |path| must be user-writeable.
+ * Example:
+ * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+ * creates directories profileDir/foo, profileDir/foo/bar
+ * - {bool} ignoreExisting If |false|, throw an error if the directory
+ * already exists. |true| by default. Ignored if |from| is specified.
+ * - {number} unixMode Under Unix, if specified, a file creation mode,
+ * as per libc function |mkdir|. If unspecified, dirs are
+ * created with a default mode of 0700 (dir is private to
+ * the user, the user can read, write and execute). Ignored under Windows
+ * or if the file system does not support file creation modes.
+ * - {C pointer} winSecurity Under Windows, if specified, security
+ * attributes as per winapi function |CreateDirectory|. If
+ * unspecified, use the default security descriptor, inherited from
+ * the parent directory. Ignored under Unix or if the file system
+ * does not support security descriptors.
+ */
+File.makeDir = function makeDir(path, options) {
+ return Scheduler.post("makeDir",
+ [Type.path.toMsg(path), options], path);
+};
+
+/**
+ * Return the contents of a file
+ *
+ * @param {string} path The path to the file.
+ * @param {number=} bytes Optionally, an upper bound to the number of bytes
+ * to read. DEPRECATED - please use options.bytes instead.
+ * @param {JSON} options Additional options.
+ * - {boolean} sequential A flag that triggers a population of the page cache
+ * with data from a file so that subsequent reads from that file would not
+ * block on disk I/O. If |true| or unspecified, inform the system that the
+ * contents of the file will be read in order. Otherwise, make no such
+ * assumption. |true| by default.
+ * - {number} bytes An upper bound to the number of bytes to read.
+ * - {string} compression If "lz4" and if the file is compressed using the lz4
+ * compression algorithm, decompress the file contents on the fly.
+ *
+ * @resolves {Uint8Array} A buffer holding the bytes
+ * read from the file.
+ */
+File.read = function read(path, bytes, options = {}) {
+ if (typeof bytes == "object") {
+ // Passing |bytes| as an argument is deprecated.
+ // We should now be passing it as a field of |options|.
+ options = bytes || {};
+ } else {
+ options = clone(options, ["outExecutionDuration"]);
+ if (typeof bytes != "undefined") {
+ options.bytes = bytes;
+ }
+ }
+
+ if (options.compression || !nativeWheneverAvailable) {
+ // We need to use the JS implementation.
+ let promise = Scheduler.post("read",
+ [Type.path.toMsg(path), bytes, options], path);
+ return promise.then(
+ function onSuccess(data) {
+ if (typeof data == "string") {
+ return data;
+ }
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+ });
+ }
+
+ // Otherwise, use the native implementation.
+ return Scheduler.push(() => Native.read(path, options));
+};
+
+/**
+ * Find outs if a file exists.
+ *
+ * @param {string} path The path to the file.
+ *
+ * @return {bool} true if the file exists, false otherwise.
+ */
+File.exists = function exists(path) {
+ return Scheduler.post("exists",
+ [Type.path.toMsg(path)], path);
+};
+
+/**
+ * Write a file, atomically.
+ *
+ * By opposition to a regular |write|, this operation ensures that,
+ * until the contents are fully written, the destination file is
+ * not modified.
+ *
+ * Limitation: In a few extreme cases (hardware failure during the
+ * write, user unplugging disk during the write, etc.), data may be
+ * corrupted. If your data is user-critical (e.g. preferences,
+ * application data, etc.), you may wish to consider adding options
+ * |tmpPath| and/or |flush| to reduce the likelihood of corruption, as
+ * detailed below. Note that no combination of options can be
+ * guaranteed to totally eliminate the risk of corruption.
+ *
+ * @param {string} path The path of the file to modify.
+ * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write.
+ * @param {*=} options Optionally, an object determining the behavior
+ * of this function. This object may contain the following fields:
+ * - {number} bytes The number of bytes to write. If unspecified,
+ * |buffer.byteLength|. Required if |buffer| is a C pointer.
+ * - {string} tmpPath If |null| or unspecified, write all data directly
+ * to |path|. If specified, write all data to a temporary file called
+ * |tmpPath| and, once this write is complete, rename the file to
+ * replace |path|. Performing this additional operation is a little
+ * slower but also a little safer.
+ * - {bool} noOverwrite - If set, this function will fail if a file already
+ * exists at |path|.
+ * - {bool} flush - If |false| or unspecified, return immediately once the
+ * write is complete. If |true|, before writing, force the operating system
+ * to write its internal disk buffers to the disk. This is considerably slower
+ * (not just for the application but for the whole system) but also safer:
+ * if the system shuts down improperly (typically due to a kernel freeze
+ * or a power failure) or if the device is disconnected before the buffer
+ * is flushed, the file has more chances of not being corrupted.
+ * - {string} backupTo - If specified, backup the destination file as |backupTo|.
+ * Note that this function renames the destination file before overwriting it.
+ * If the process or the operating system freezes or crashes
+ * during the short window between these operations,
+ * the destination file will have been moved to its backup.
+ *
+ * @return {promise}
+ * @resolves {number} The number of bytes actually written.
+ */
+File.writeAtomic = function writeAtomic(path, buffer, options = {}) {
+ // Copy |options| to avoid modifying the original object but preserve the
+ // reference to |outExecutionDuration| option if it is passed.
+ options = clone(options, ["outExecutionDuration"]);
+ // As options.tmpPath is a path, we need to encode it as |Type.path| message
+ if ("tmpPath" in options) {
+ options.tmpPath = Type.path.toMsg(options.tmpPath);
+ };
+ if (isTypedArray(buffer) && (!("bytes" in options))) {
+ options.bytes = buffer.byteLength;
+ };
+ let promise = Scheduler.post("writeAtomic",
+ [Type.path.toMsg(path),
+ Type.void_t.in_ptr.toMsg(buffer),
+ options], [options, buffer, path]);
+ return promise;
+};
+
+File.removeDir = function(path, options = {}) {
+ return Scheduler.post("removeDir",
+ [Type.path.toMsg(path), options], path);
+};
+
+/**
+ * Information on a file, as returned by OS.File.stat or
+ * OS.File.prototype.stat
+ *
+ * @constructor
+ */
+File.Info = function Info(value) {
+ // Note that we can't just do this[k] = value[k] because our
+ // prototype defines getters for all of these fields.
+ for (let k in value) {
+ if (k != "creationDate") {
+ Object.defineProperty(this, k, {value: value[k]});
+ }
+ }
+ Object.defineProperty(this, "_deprecatedCreationDate", {value: value["creationDate"]});
+};
+File.Info.prototype = SysAll.AbstractInfo.prototype;
+
+// Deprecated
+Object.defineProperty(File.Info.prototype, "creationDate", {
+ get: function creationDate() {
+ let {Deprecated} = Cu.import("resource://gre/modules/Deprecated.jsm", {});
+ Deprecated.warning("Field 'creationDate' is deprecated.", "https://developer.mozilla.org/en-US/docs/JavaScript_OS.File/OS.File.Info#Cross-platform_Attributes");
+ return this._deprecatedCreationDate;
+ }
+});
+
+File.Info.fromMsg = function fromMsg(value) {
+ return new File.Info(value);
+};
+
+/**
+ * Get worker's current DEBUG flag.
+ * Note: This is used for testing purposes.
+ */
+File.GET_DEBUG = function GET_DEBUG() {
+ return Scheduler.post("GET_DEBUG");
+};
+
+/**
+ * Iterate asynchronously through a directory
+ *
+ * @constructor
+ */
+var DirectoryIterator = function DirectoryIterator(path, options) {
+ /**
+ * Open the iterator on the worker thread
+ *
+ * @type {Promise}
+ * @resolves {*} A message accepted by the methods of DirectoryIterator
+ * in the worker thread
+ * @rejects {StopIteration} If all entries have already been visited
+ * or the iterator has been closed.
+ */
+ this.__itmsg = Scheduler.post(
+ "new_DirectoryIterator", [Type.path.toMsg(path), options],
+ path
+ );
+ this._isClosed = false;
+};
+DirectoryIterator.prototype = {
+ iterator: function () {
+ return this;
+ },
+ __iterator__: function () {
+ return this;
+ },
+
+ // Once close() is called, _itmsg should reject with a
+ // StopIteration. However, we don't want to create the promise until
+ // it's needed because it might never be used. In that case, we
+ // would get a warning on the console.
+ get _itmsg() {
+ if (!this.__itmsg) {
+ this.__itmsg = Promise.reject(StopIteration);
+ }
+ return this.__itmsg;
+ },
+
+ /**
+ * Determine whether the directory exists.
+ *
+ * @resolves {boolean}
+ */
+ exists: function exists() {
+ return this._itmsg.then(
+ function onSuccess(iterator) {
+ return Scheduler.post("DirectoryIterator_prototype_exists", [iterator]);
+ }
+ );
+ },
+ /**
+ * Get the next entry in the directory.
+ *
+ * @return {Promise}
+ * @resolves {OS.File.Entry}
+ * @rejects {StopIteration} If all entries have already been visited.
+ */
+ next: function next() {
+ let self = this;
+ let promise = this._itmsg;
+
+ // Get the iterator, call _next
+ promise = promise.then(
+ function withIterator(iterator) {
+ return self._next(iterator);
+ });
+
+ return promise;
+ },
+ /**
+ * Get several entries at once.
+ *
+ * @param {number=} length If specified, the number of entries
+ * to return. If unspecified, return all remaining entries.
+ * @return {Promise}
+ * @resolves {Array} An array containing the |length| next entries.
+ */
+ nextBatch: function nextBatch(size) {
+ if (this._isClosed) {
+ return Promise.resolve([]);
+ }
+ let promise = this._itmsg;
+ promise = promise.then(
+ function withIterator(iterator) {
+ return Scheduler.post("DirectoryIterator_prototype_nextBatch", [iterator, size]);
+ });
+ promise = promise.then(
+ function withEntries(array) {
+ return array.map(DirectoryIterator.Entry.fromMsg);
+ });
+ return promise;
+ },
+ /**
+ * Apply a function to all elements of the directory sequentially.
+ *
+ * @param {Function} cb This function will be applied to all entries
+ * of the directory. It receives as arguments
+ * - the OS.File.Entry corresponding to the entry;
+ * - the index of the entry in the enumeration;
+ * - the iterator itself - return |iterator.close()| to stop the loop.
+ *
+ * If the callback returns a promise, iteration waits until the
+ * promise is resolved before proceeding.
+ *
+ * @return {Promise} A promise resolved once the loop has reached
+ * its end.
+ */
+ forEach: function forEach(cb, options) {
+ if (this._isClosed) {
+ return Promise.resolve();
+ }
+
+ let self = this;
+ let position = 0;
+ let iterator;
+
+ // Grab iterator
+ let promise = this._itmsg.then(
+ function(aIterator) {
+ iterator = aIterator;
+ }
+ );
+
+ // Then iterate
+ let loop = function loop() {
+ if (self._isClosed) {
+ return Promise.resolve();
+ }
+ return self._next(iterator).then(
+ function onSuccess(value) {
+ return Promise.resolve(cb(value, position++, self)).then(loop);
+ },
+ function onFailure(reason) {
+ if (reason == StopIteration) {
+ return;
+ }
+ throw reason;
+ }
+ );
+ };
+
+ return promise.then(loop);
+ },
+ /**
+ * Auxiliary method: fetch the next item
+ *
+ * @rejects {StopIteration} If all entries have already been visited
+ * or the iterator has been closed.
+ */
+ _next: function _next(iterator) {
+ if (this._isClosed) {
+ return this._itmsg;
+ }
+ let self = this;
+ let promise = Scheduler.post("DirectoryIterator_prototype_next", [iterator]);
+ promise = promise.then(
+ DirectoryIterator.Entry.fromMsg,
+ function onReject(reason) {
+ if (reason == StopIteration) {
+ self.close();
+ throw StopIteration;
+ }
+ throw reason;
+ });
+ return promise;
+ },
+ /**
+ * Close the iterator
+ */
+ close: function close() {
+ if (this._isClosed) {
+ return Promise.resolve();
+ }
+ this._isClosed = true;
+ let self = this;
+ return this._itmsg.then(
+ function withIterator(iterator) {
+ // Set __itmsg to null so that the _itmsg getter returns a
+ // rejected StopIteration promise if it's ever used.
+ self.__itmsg = null;
+ return Scheduler.post("DirectoryIterator_prototype_close", [iterator]);
+ }
+ );
+ }
+};
+
+DirectoryIterator.Entry = function Entry(value) {
+ return value;
+};
+DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
+
+DirectoryIterator.Entry.fromMsg = function fromMsg(value) {
+ return new DirectoryIterator.Entry(value);
+};
+
+File.resetWorker = function() {
+ return Task.spawn(function*() {
+ let resources = yield Scheduler.kill({shutdown: false, reset: true});
+ if (resources && !resources.killed) {
+ throw new Error("Could not reset worker, this would leak file descriptors: " + JSON.stringify(resources));
+ }
+ });
+};
+
+// Constants
+File.POS_START = SysAll.POS_START;
+File.POS_CURRENT = SysAll.POS_CURRENT;
+File.POS_END = SysAll.POS_END;
+
+// Exports
+File.Error = OSError;
+File.DirectoryIterator = DirectoryIterator;
+
+this.OS = {};
+this.OS.File = File;
+this.OS.Constants = SharedAll.Constants;
+this.OS.Shared = {
+ LOG: SharedAll.LOG,
+ Type: SysAll.Type,
+ get DEBUG() {
+ return SharedAll.Config.DEBUG;
+ },
+ set DEBUG(x) {
+ return SharedAll.Config.DEBUG = x;
+ }
+};
+Object.freeze(this.OS.Shared);
+this.OS.Path = Path;
+
+// Returns a resolved promise when all the queued operation have been completed.
+Object.defineProperty(OS.File, "queue", {
+ get: function() {
+ return Scheduler.queue;
+ }
+});
+
+// `true` if this is a content process, `false` otherwise.
+// It would be nicer to go through `Services.appInfo`, but some tests need to be
+// able to replace that field with a custom implementation before it is first
+// called.
+const isContent = Components.classes["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+
+/**
+ * Shutdown barriers, to let clients register to be informed during shutdown.
+ */
+var Barriers = {
+ shutdown: new AsyncShutdown.Barrier("OS.File: Waiting for clients before full shutdown"),
+ /**
+ * Return the shutdown state of OS.File
+ */
+ getDetails: function() {
+ let result = {
+ launched: Scheduler.launched,
+ shutdown: Scheduler.shutdown,
+ worker: !!Scheduler._worker,
+ pendingReset: !!Scheduler.resetTimer,
+ latestSent: Scheduler.Debugging.latestSent,
+ latestReceived: Scheduler.Debugging.latestReceived,
+ messagesSent: Scheduler.Debugging.messagesSent,
+ messagesReceived: Scheduler.Debugging.messagesReceived,
+ messagesQueued: Scheduler.Debugging.messagesQueued,
+ DEBUG: SharedAll.Config.DEBUG,
+ };
+ // Convert dates to strings for better readability
+ for (let key of ["latestSent", "latestReceived"]) {
+ if (result[key] && typeof result[key][0] == "number") {
+ result[key][0] = Date(result[key][0]);
+ }
+ }
+ return result;
+ }
+};
+
+function setupShutdown(phaseName) {
+ Barriers[phaseName] = new AsyncShutdown.Barrier(`OS.File: Waiting for clients before ${phaseName}`),
+ File[phaseName] = Barriers[phaseName].client;
+
+ // Auto-flush OS.File during `phaseName`. This ensures that any I/O
+ // that has been queued *before* `phaseName` is properly completed.
+ // To ensure that I/O queued *during* `phaseName` change is completed,
+ // clients should register using AsyncShutdown.addBlocker.
+ AsyncShutdown[phaseName].addBlocker(
+ `OS.File: flush I/O queued before ${phaseName}`,
+ Task.async(function*() {
+ // Give clients a last chance to enqueue requests.
+ yield Barriers[phaseName].wait({crashAfterMS: null});
+
+ // Wait until all currently enqueued requests are completed.
+ yield Scheduler.queue;
+ }),
+ () => {
+ let details = Barriers.getDetails();
+ details.clients = Barriers[phaseName].state;
+ return details;
+ }
+ );
+}
+
+// profile-before-change only exists in the parent, and OS.File should
+// not be used in the child process anyways.
+if (!isContent) {
+ setupShutdown("profileBeforeChange")
+}
+File.shutdown = Barriers.shutdown.client;
diff --git a/components/osfile/modules/osfile_async_worker.js b/components/osfile/modules/osfile_async_worker.js
new file mode 100644
index 000000000..3ae99e705
--- /dev/null
+++ b/components/osfile/modules/osfile_async_worker.js
@@ -0,0 +1,406 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 (this.Components) {
+ throw new Error("This worker can only be loaded from a worker thread");
+}
+
+// Worker thread for osfile asynchronous front-end
+
+(function(exports) {
+ "use strict";
+
+ // The object is set to |null| once it has been sent
+ // to the main thread.
+ let timeStamps = {
+ entered: Date.now(),
+ loaded: null
+ };
+
+ importScripts("resource://gre/modules/osfile.jsm");
+
+ let PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");
+ let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let LOG = SharedAll.LOG.bind(SharedAll, "Agent");
+
+ let worker = new PromiseWorker.AbstractWorker();
+ worker.dispatch = function(method, args = []) {
+ return Agent[method](...args);
+ },
+ worker.log = LOG;
+ worker.postMessage = function(message, ...transfers) {
+ if (timeStamps) {
+ message.timeStamps = timeStamps;
+ timeStamps = null;
+ }
+ self.postMessage(message, ...transfers);
+ };
+ worker.close = function() {
+ self.close();
+ };
+ let Meta = PromiseWorker.Meta;
+
+ self.addEventListener("message", msg => worker.handleMessage(msg));
+
+ /**
+ * A data structure used to track opened resources
+ */
+ let ResourceTracker = function ResourceTracker() {
+ // A number used to generate ids
+ this._idgen = 0;
+ // A map from id to resource
+ this._map = new Map();
+ };
+ ResourceTracker.prototype = {
+ /**
+ * Get a resource from its unique identifier.
+ */
+ get: function(id) {
+ let result = this._map.get(id);
+ if (result == null) {
+ return result;
+ }
+ return result.resource;
+ },
+ /**
+ * Remove a resource from its unique identifier.
+ */
+ remove: function(id) {
+ if (!this._map.has(id)) {
+ throw new Error("Cannot find resource id " + id);
+ }
+ this._map.delete(id);
+ },
+ /**
+ * Add a resource, return a new unique identifier
+ *
+ * @param {*} resource A resource.
+ * @param {*=} info Optional information. For debugging purposes.
+ *
+ * @return {*} A unique identifier. For the moment, this is a number,
+ * but this might not remain the case forever.
+ */
+ add: function(resource, info) {
+ let id = this._idgen++;
+ this._map.set(id, {resource:resource, info:info});
+ return id;
+ },
+ /**
+ * Return a list of all open resources i.e. the ones still present in
+ * ResourceTracker's _map.
+ */
+ listOpenedResources: function listOpenedResources() {
+ return Array.from(this._map, ([id, resource]) => resource.info.path);
+ }
+ };
+
+ /**
+ * A map of unique identifiers to opened files.
+ */
+ let OpenedFiles = new ResourceTracker();
+
+ /**
+ * Execute a function in the context of a given file.
+ *
+ * @param {*} id A unique identifier, as used by |OpenFiles|.
+ * @param {Function} f A function to call.
+ * @param {boolean} ignoreAbsent If |true|, the error is ignored. Otherwise, the error causes an exception.
+ * @return The return value of |f()|
+ *
+ * This function attempts to get the file matching |id|. If
+ * the file exists, it executes |f| within the |this| set
+ * to the corresponding file. Otherwise, it throws an error.
+ */
+ let withFile = function withFile(id, f, ignoreAbsent) {
+ let file = OpenedFiles.get(id);
+ if (file == null) {
+ if (!ignoreAbsent) {
+ throw OS.File.Error.closed("accessing file");
+ }
+ return undefined;
+ }
+ return f.call(file);
+ };
+
+ let OpenedDirectoryIterators = new ResourceTracker();
+ let withDir = function withDir(fd, f, ignoreAbsent) {
+ let file = OpenedDirectoryIterators.get(fd);
+ if (file == null) {
+ if (!ignoreAbsent) {
+ throw OS.File.Error.closed("accessing directory");
+ }
+ return undefined;
+ }
+ if (!(file instanceof File.DirectoryIterator)) {
+ throw new Error("file is not a directory iterator " + file.__proto__.toSource());
+ }
+ return f.call(file);
+ };
+
+ let Type = exports.OS.Shared.Type;
+
+ let File = exports.OS.File;
+
+ /**
+ * The agent.
+ *
+ * It is in charge of performing method-specific deserialization
+ * of messages, calling the function/method of OS.File and serializing
+ * back the results.
+ */
+ let Agent = {
+ // Update worker's OS.Shared.DEBUG flag message from controller.
+ SET_DEBUG: function(aDEBUG) {
+ SharedAll.Config.DEBUG = aDEBUG;
+ },
+ // Return worker's current OS.Shared.DEBUG value to controller.
+ // Note: This is used for testing purposes.
+ GET_DEBUG: function() {
+ return SharedAll.Config.DEBUG;
+ },
+ /**
+ * Execute shutdown sequence, returning data on leaked file descriptors.
+ *
+ * @param {bool} If |true|, kill the worker if this would not cause
+ * leaks.
+ */
+ Meta_shutdown: function(kill) {
+ let result = {
+ openedFiles: OpenedFiles.listOpenedResources(),
+ openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources(),
+ killed: false // Placeholder
+ };
+
+ // Is it safe to kill the worker?
+ let safe = result.openedFiles.length == 0
+ && result.openedDirectoryIterators.length == 0;
+ result.killed = safe && kill;
+
+ return new Meta(result, {shutdown: result.killed});
+ },
+ // Functions of OS.File
+ stat: function stat(path, options) {
+ return exports.OS.File.Info.toMsg(
+ exports.OS.File.stat(Type.path.fromMsg(path), options));
+ },
+ setPermissions: function setPermissions(path, options = {}) {
+ return exports.OS.File.setPermissions(Type.path.fromMsg(path), options);
+ },
+ setDates: function setDates(path, accessDate, modificationDate) {
+ return exports.OS.File.setDates(Type.path.fromMsg(path), accessDate,
+ modificationDate);
+ },
+ getCurrentDirectory: function getCurrentDirectory() {
+ return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory());
+ },
+ setCurrentDirectory: function setCurrentDirectory(path) {
+ File.setCurrentDirectory(exports.OS.Shared.Type.path.fromMsg(path));
+ },
+ copy: function copy(sourcePath, destPath, options) {
+ return File.copy(Type.path.fromMsg(sourcePath),
+ Type.path.fromMsg(destPath), options);
+ },
+ move: function move(sourcePath, destPath, options) {
+ return File.move(Type.path.fromMsg(sourcePath),
+ Type.path.fromMsg(destPath), options);
+ },
+ getAvailableFreeSpace: function getAvailableFreeSpace(sourcePath) {
+ return Type.uint64_t.toMsg(
+ File.getAvailableFreeSpace(Type.path.fromMsg(sourcePath)));
+ },
+ makeDir: function makeDir(path, options) {
+ return File.makeDir(Type.path.fromMsg(path), options);
+ },
+ removeEmptyDir: function removeEmptyDir(path, options) {
+ return File.removeEmptyDir(Type.path.fromMsg(path), options);
+ },
+ remove: function remove(path, options) {
+ return File.remove(Type.path.fromMsg(path), options);
+ },
+ open: function open(path, mode, options) {
+ let filePath = Type.path.fromMsg(path);
+ let file = File.open(filePath, mode, options);
+ return OpenedFiles.add(file, {
+ // Adding path information to keep track of opened files
+ // to report leaks when debugging.
+ path: filePath
+ });
+ },
+ openUnique: function openUnique(path, options) {
+ let filePath = Type.path.fromMsg(path);
+ let openedFile = OS.Shared.AbstractFile.openUnique(filePath, options);
+ let resourceId = OpenedFiles.add(openedFile.file, {
+ // Adding path information to keep track of opened files
+ // to report leaks when debugging.
+ path: openedFile.path
+ });
+
+ return {
+ path: openedFile.path,
+ file: resourceId
+ };
+ },
+ read: function read(path, bytes, options) {
+ let data = File.read(Type.path.fromMsg(path), bytes, options);
+ if (typeof data == "string") {
+ return data;
+ }
+ return new Meta({
+ buffer: data.buffer,
+ byteOffset: data.byteOffset,
+ byteLength: data.byteLength
+ }, {
+ transfers: [data.buffer]
+ });
+ },
+ exists: function exists(path) {
+ return File.exists(Type.path.fromMsg(path));
+ },
+ writeAtomic: function writeAtomic(path, buffer, options) {
+ if (options.tmpPath) {
+ options.tmpPath = Type.path.fromMsg(options.tmpPath);
+ }
+ return File.writeAtomic(Type.path.fromMsg(path),
+ Type.voidptr_t.fromMsg(buffer),
+ options
+ );
+ },
+ removeDir: function(path, options) {
+ return File.removeDir(Type.path.fromMsg(path), options);
+ },
+ new_DirectoryIterator: function new_DirectoryIterator(path, options) {
+ let directoryPath = Type.path.fromMsg(path);
+ let iterator = new File.DirectoryIterator(directoryPath, options);
+ return OpenedDirectoryIterators.add(iterator, {
+ // Adding path information to keep track of opened directory
+ // iterators to report leaks when debugging.
+ path: directoryPath
+ });
+ },
+ // Methods of OS.File
+ File_prototype_close: function close(fd) {
+ return withFile(fd,
+ function do_close() {
+ try {
+ return this.close();
+ } finally {
+ OpenedFiles.remove(fd);
+ }
+ });
+ },
+ File_prototype_stat: function stat(fd) {
+ return withFile(fd,
+ function do_stat() {
+ return exports.OS.File.Info.toMsg(this.stat());
+ });
+ },
+ File_prototype_setPermissions: function setPermissions(fd, options = {}) {
+ return withFile(fd,
+ function do_setPermissions() {
+ return this.setPermissions(options);
+ });
+ },
+ File_prototype_setDates: function setDates(fd, accessTime, modificationTime) {
+ return withFile(fd,
+ function do_setDates() {
+ return this.setDates(accessTime, modificationTime);
+ });
+ },
+ File_prototype_read: function read(fd, nbytes, options) {
+ return withFile(fd,
+ function do_read() {
+ let data = this.read(nbytes, options);
+ return new Meta({
+ buffer: data.buffer,
+ byteOffset: data.byteOffset,
+ byteLength: data.byteLength
+ }, {
+ transfers: [data.buffer]
+ });
+ }
+ );
+ },
+ File_prototype_readTo: function readTo(fd, buffer, options) {
+ return withFile(fd,
+ function do_readTo() {
+ return this.readTo(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer),
+ options);
+ });
+ },
+ File_prototype_write: function write(fd, buffer, options) {
+ return withFile(fd,
+ function do_write() {
+ return this.write(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer),
+ options);
+ });
+ },
+ File_prototype_setPosition: function setPosition(fd, pos, whence) {
+ return withFile(fd,
+ function do_setPosition() {
+ return this.setPosition(pos, whence);
+ });
+ },
+ File_prototype_getPosition: function getPosition(fd) {
+ return withFile(fd,
+ function do_getPosition() {
+ return this.getPosition();
+ });
+ },
+ File_prototype_flush: function flush(fd) {
+ return withFile(fd,
+ function do_flush() {
+ return this.flush();
+ });
+ },
+ // Methods of OS.File.DirectoryIterator
+ DirectoryIterator_prototype_next: function next(dir) {
+ return withDir(dir,
+ function do_next() {
+ try {
+ return File.DirectoryIterator.Entry.toMsg(this.next());
+ } catch (x) {
+ if (x == StopIteration) {
+ OpenedDirectoryIterators.remove(dir);
+ }
+ throw x;
+ }
+ }, false);
+ },
+ DirectoryIterator_prototype_nextBatch: function nextBatch(dir, size) {
+ return withDir(dir,
+ function do_nextBatch() {
+ let result;
+ try {
+ result = this.nextBatch(size);
+ } catch (x) {
+ OpenedDirectoryIterators.remove(dir);
+ throw x;
+ }
+ return result.map(File.DirectoryIterator.Entry.toMsg);
+ }, false);
+ },
+ DirectoryIterator_prototype_close: function close(dir) {
+ return withDir(dir,
+ function do_close() {
+ this.close();
+ OpenedDirectoryIterators.remove(dir);
+ }, true);// ignore error to support double-closing |DirectoryIterator|
+ },
+ DirectoryIterator_prototype_exists: function exists(dir) {
+ return withDir(dir,
+ function do_exists() {
+ return this.exists();
+ });
+ }
+ };
+ if (!SharedAll.Constants.Win) {
+ Agent.unixSymLink = function unixSymLink(sourcePath, destPath) {
+ return File.unixSymLink(Type.path.fromMsg(sourcePath),
+ Type.path.fromMsg(destPath));
+ };
+ }
+
+ timeStamps.loaded = Date.now();
+})(this);
diff --git a/components/osfile/modules/osfile_native.jsm b/components/osfile/modules/osfile_native.jsm
new file mode 100644
index 000000000..16cd3c92a
--- /dev/null
+++ b/components/osfile/modules/osfile_native.jsm
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Native (xpcom) implementation of key OS.File functions
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["read"];
+
+var {results: Cr, utils: Cu, interfaces: Ci} = Components;
+
+var SharedAll = Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", {});
+
+var SysAll = {};
+if (SharedAll.Constants.Win) {
+ Cu.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", SysAll);
+} else if (SharedAll.Constants.libc) {
+ Cu.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", SysAll);
+} else {
+ throw new Error("I am neither under Windows nor under a Posix system");
+}
+var {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+/**
+ * The native service holding the implementation of the functions.
+ */
+XPCOMUtils.defineLazyServiceGetter(this,
+ "Internals",
+ "@mozilla.org/toolkit/osfile/native-internals;1",
+ "nsINativeOSFileInternalsService");
+
+/**
+ * Native implementation of OS.File.read
+ *
+ * This implementation does not handle option |compression|.
+ */
+this.read = function(path, options = {}) {
+ // Sanity check on types of options
+ if ("encoding" in options && typeof options.encoding != "string") {
+ return Promise.reject(new TypeError("Invalid type for option encoding"));
+ }
+ if ("compression" in options && typeof options.compression != "string") {
+ return Promise.reject(new TypeError("Invalid type for option compression"));
+ }
+ if ("bytes" in options && typeof options.bytes != "number") {
+ return Promise.reject(new TypeError("Invalid type for option bytes"));
+ }
+
+ let deferred = Promise.defer();
+ Internals.read(path,
+ options,
+ function onSuccess(success) {
+ success.QueryInterface(Ci.nsINativeOSFileResult);
+ if ("outExecutionDuration" in options) {
+ options.outExecutionDuration =
+ success.executionDurationMS +
+ (options.outExecutionDuration || 0);
+ }
+ deferred.resolve(success.result);
+ },
+ function onError(operation, oserror) {
+ deferred.reject(new SysAll.Error(operation, oserror, path));
+ }
+ );
+ return deferred.promise;
+};
diff --git a/components/osfile/modules/osfile_shared_allthreads.jsm b/components/osfile/modules/osfile_shared_allthreads.jsm
new file mode 100644
index 000000000..c5c505102
--- /dev/null
+++ b/components/osfile/modules/osfile_shared_allthreads.jsm
@@ -0,0 +1,1315 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+/**
+ * OS.File utilities used by all threads.
+ *
+ * This module defines:
+ * - logging;
+ * - the base constants;
+ * - base types and primitives for declaring new types;
+ * - primitives for importing C functions;
+ * - primitives for dealing with integers, pointers, typed arrays;
+ * - the base class OSError;
+ * - a few additional utilities.
+ */
+
+// Boilerplate used to be able to import this module both from the main
+// thread and from worker threads.
+
+// Since const is lexically scoped, hoist the
+// conditionally-useful definition ourselves.
+const Cu = typeof Components != "undefined" ? Components.utils : undefined;
+const Ci = typeof Components != "undefined" ? Components.interfaces : undefined;
+const Cc = typeof Components != "undefined" ? Components.classes : undefined;
+
+/**
+ * A constructor for messages that require transfers instead of copies.
+ *
+ * See BasePromiseWorker.Meta.
+ *
+ * @constructor
+ */
+var Meta;
+if (typeof Components != "undefined") {
+ // Global definition of |exports|, to keep everybody happy.
+ // In non-main thread, |exports| is provided by the module
+ // loader.
+ this.exports = {};
+
+ Cu.import("resource://gre/modules/Services.jsm", this);
+ Meta = Cu.import("resource://gre/modules/PromiseWorker.jsm", {}).BasePromiseWorker.Meta;
+} else {
+ importScripts("resource://gre/modules/workers/require.js");
+ Meta = require("resource://gre/modules/workers/PromiseWorker.js").Meta;
+}
+
+var EXPORTED_SYMBOLS = [
+ "LOG",
+ "clone",
+ "Config",
+ "Constants",
+ "Type",
+ "HollowStructure",
+ "OSError",
+ "Library",
+ "declareFFI",
+ "declareLazy",
+ "declareLazyFFI",
+ "normalizeBufferArgs",
+ "projectValue",
+ "isArrayBuffer",
+ "isTypedArray",
+ "defineLazyGetter",
+ "OS" // Warning: this exported symbol will disappear
+];
+
+////////////////////// Configuration of OS.File
+
+var Config = {
+ /**
+ * If |true|, calls to |LOG| are shown. Otherwise, they are hidden.
+ *
+ * This configuration option is controlled by preference "toolkit.osfile.log".
+ */
+ DEBUG: false,
+
+ /**
+ * TEST
+ */
+ TEST: false
+};
+exports.Config = Config;
+
+////////////////////// OS Constants
+
+if (typeof Components != "undefined") {
+ // On the main thread, OS.Constants is defined by a xpcom
+ // component. On other threads, it is available automatically
+ Cu.import("resource://gre/modules/ctypes.jsm");
+ Cc["@mozilla.org/net/osfileconstantsservice;1"].
+ getService(Ci.nsIOSFileConstantsService).init();
+}
+
+exports.Constants = OS.Constants;
+
+///////////////////// Utilities
+
+// Define a lazy getter for a property
+var defineLazyGetter = function defineLazyGetter(object, name, getter) {
+ Object.defineProperty(object, name, {
+ configurable: true,
+ get: function lazy() {
+ delete this[name];
+ let value = getter.call(this);
+ Object.defineProperty(object, name, {
+ value: value
+ });
+ return value;
+ }
+ });
+};
+exports.defineLazyGetter = defineLazyGetter;
+
+
+///////////////////// Logging
+
+/**
+ * The default implementation of the logger.
+ *
+ * The choice of logger can be overridden with Config.TEST.
+ */
+var gLogger;
+if (typeof window != "undefined" && window.console && console.log) {
+ gLogger = console.log.bind(console, "OS");
+} else {
+ gLogger = function(...args) {
+ dump("OS " + args.join(" ") + "\n");
+ };
+}
+
+/**
+ * Attempt to stringify an argument into something useful for
+ * debugging purposes, by using |.toString()| or |JSON.stringify|
+ * if available.
+ *
+ * @param {*} arg An argument to be stringified if possible.
+ * @return {string} A stringified version of |arg|.
+ */
+var stringifyArg = function stringifyArg(arg) {
+ if (typeof arg === "string") {
+ return arg;
+ }
+ if (arg && typeof arg === "object") {
+ let argToString = "" + arg;
+
+ /**
+ * The only way to detect whether this object has a non-default
+ * implementation of |toString| is to check whether it returns
+ * '[object Object]'. Unfortunately, we cannot simply compare |arg.toString|
+ * and |Object.prototype.toString| as |arg| typically comes from another
+ * compartment.
+ */
+ if (argToString === "[object Object]") {
+ return JSON.stringify(arg, function(key, value) {
+ if (isTypedArray(value)) {
+ return "["+ value.constructor.name + " " + value.byteOffset + " " + value.byteLength + "]";
+ }
+ if (isArrayBuffer(arg)) {
+ return "[" + value.constructor.name + " " + value.byteLength + "]";
+ }
+ return value;
+ });
+ } else {
+ return argToString;
+ }
+ }
+ return arg;
+};
+
+var LOG = function (...args) {
+ if (!Config.DEBUG) {
+ // If logging is deactivated, don't log
+ return;
+ }
+
+ let logFunc = gLogger;
+ if (Config.TEST && typeof Components != "undefined") {
+ // If _TESTING_LOGGING is set, and if we are on the main thread,
+ // redirect logs to Services.console, for testing purposes
+ logFunc = function logFunc(...args) {
+ let message = ["TEST", "OS"].concat(args).join(" ");
+ Services.console.logStringMessage(message + "\n");
+ };
+ }
+ logFunc.apply(null, args.map(stringifyArg));
+};
+
+exports.LOG = LOG;
+
+/**
+ * Return a shallow clone of the enumerable properties of an object.
+ *
+ * Utility used whenever normalizing options requires making (shallow)
+ * changes to an option object. The copy ensures that we do not modify
+ * a client-provided object by accident.
+ *
+ * Note: to reference and not copy specific fields, provide an optional
+ * |refs| argument containing their names.
+ *
+ * @param {JSON} object Options to be cloned.
+ * @param {Array} refs An optional array of field names to be passed by
+ * reference instead of copying.
+ */
+var clone = function (object, refs = []) {
+ let result = {};
+ // Make a reference between result[key] and object[key].
+ let refer = function refer(result, key, object) {
+ Object.defineProperty(result, key, {
+ enumerable: true,
+ get: function() {
+ return object[key];
+ },
+ set: function(value) {
+ object[key] = value;
+ }
+ });
+ };
+ for (let k in object) {
+ if (refs.indexOf(k) < 0) {
+ result[k] = object[k];
+ } else {
+ refer(result, k, object);
+ }
+ }
+ return result;
+};
+
+exports.clone = clone;
+
+///////////////////// Abstractions above js-ctypes
+
+/**
+ * Abstraction above js-ctypes types.
+ *
+ * Use values of this type to register FFI functions. In addition to the
+ * usual features of js-ctypes, values of this type perform the necessary
+ * transformations to ensure that C errors are handled nicely, to connect
+ * resources with their finalizer, etc.
+ *
+ * @param {string} name The name of the type. Must be unique.
+ * @param {CType} implementation The js-ctypes implementation of the type.
+ *
+ * @constructor
+ */
+function Type(name, implementation) {
+ if (!(typeof name == "string")) {
+ throw new TypeError("Type expects as first argument a name, got: "
+ + name);
+ }
+ if (!(implementation instanceof ctypes.CType)) {
+ throw new TypeError("Type expects as second argument a ctypes.CType"+
+ ", got: " + implementation);
+ }
+ Object.defineProperty(this, "name", { value: name });
+ Object.defineProperty(this, "implementation", { value: implementation });
+}
+Type.prototype = {
+ /**
+ * Serialize a value of |this| |Type| into a format that can
+ * be transmitted as a message (not necessarily a string).
+ *
+ * In the default implementation, the method returns the
+ * value unchanged.
+ */
+ toMsg: function default_toMsg(value) {
+ return value;
+ },
+ /**
+ * Deserialize a message to a value of |this| |Type|.
+ *
+ * In the default implementation, the method returns the
+ * message unchanged.
+ */
+ fromMsg: function default_fromMsg(msg) {
+ return msg;
+ },
+ /**
+ * Import a value from C.
+ *
+ * In this default implementation, return the value
+ * unchanged.
+ */
+ importFromC: function default_importFromC(value) {
+ return value;
+ },
+
+ /**
+ * A pointer/array used to pass data to the foreign function.
+ */
+ get in_ptr() {
+ delete this.in_ptr;
+ let ptr_t = new PtrType(
+ "[in] " + this.name + "*",
+ this.implementation.ptr,
+ this);
+ Object.defineProperty(this, "in_ptr",
+ {
+ get: function() {
+ return ptr_t;
+ }
+ });
+ return ptr_t;
+ },
+
+ /**
+ * A pointer/array used to receive data from the foreign function.
+ */
+ get out_ptr() {
+ delete this.out_ptr;
+ let ptr_t = new PtrType(
+ "[out] " + this.name + "*",
+ this.implementation.ptr,
+ this);
+ Object.defineProperty(this, "out_ptr",
+ {
+ get: function() {
+ return ptr_t;
+ }
+ });
+ return ptr_t;
+ },
+
+ /**
+ * A pointer/array used to both pass data to the foreign function
+ * and receive data from the foreign function.
+ *
+ * Whenever possible, prefer using |in_ptr| or |out_ptr|, which
+ * are generally faster.
+ */
+ get inout_ptr() {
+ delete this.inout_ptr;
+ let ptr_t = new PtrType(
+ "[inout] " + this.name + "*",
+ this.implementation.ptr,
+ this);
+ Object.defineProperty(this, "inout_ptr",
+ {
+ get: function() {
+ return ptr_t;
+ }
+ });
+ return ptr_t;
+ },
+
+ /**
+ * Attach a finalizer to a type.
+ */
+ releaseWith: function releaseWith(finalizer) {
+ let parent = this;
+ let type = this.withName("[auto " + this.name + ", " + finalizer + "] ");
+ type.importFromC = function importFromC(value, operation) {
+ return ctypes.CDataFinalizer(
+ parent.importFromC(value, operation),
+ finalizer);
+ };
+ return type;
+ },
+
+ /**
+ * Lazy variant of releaseWith.
+ * Attach a finalizer lazily to a type.
+ *
+ * @param {function} getFinalizer The function that
+ * returns finalizer lazily.
+ */
+ releaseWithLazy: function releaseWithLazy(getFinalizer) {
+ let parent = this;
+ let type = this.withName("[auto " + this.name + ", (lazy)] ");
+ type.importFromC = function importFromC(value, operation) {
+ return ctypes.CDataFinalizer(
+ parent.importFromC(value, operation),
+ getFinalizer());
+ };
+ return type;
+ },
+
+ /**
+ * Return an alias to a type with a different name.
+ */
+ withName: function withName(name) {
+ return Object.create(this, {name: {value: name}});
+ },
+
+ /**
+ * Cast a C value to |this| type.
+ *
+ * Throw an error if the value cannot be casted.
+ */
+ cast: function cast(value) {
+ return ctypes.cast(value, this.implementation);
+ },
+
+ /**
+ * Return the number of bytes in a value of |this| type.
+ *
+ * This may not be defined, e.g. for |void_t|, array types
+ * without length, etc.
+ */
+ get size() {
+ return this.implementation.size;
+ }
+};
+
+/**
+ * Utility function used to determine whether an object is a typed array
+ */
+var isTypedArray = function isTypedArray(obj) {
+ return obj != null && typeof obj == "object"
+ && "byteOffset" in obj;
+};
+exports.isTypedArray = isTypedArray;
+
+/**
+ * Utility function used to determine whether an object is an ArrayBuffer.
+ */
+var isArrayBuffer = function(obj) {
+ return obj != null && typeof obj == "object" &&
+ obj.constructor.name == "ArrayBuffer";
+};
+exports.isArrayBuffer = isArrayBuffer;
+
+/**
+ * A |Type| of pointers.
+ *
+ * @param {string} name The name of this type.
+ * @param {CType} implementation The type of this pointer.
+ * @param {Type} targetType The target type.
+ */
+function PtrType(name, implementation, targetType) {
+ Type.call(this, name, implementation);
+ if (targetType == null || !targetType instanceof Type) {
+ throw new TypeError("targetType must be an instance of Type");
+ }
+ /**
+ * The type of values targeted by this pointer type.
+ */
+ Object.defineProperty(this, "targetType", {
+ value: targetType
+ });
+}
+PtrType.prototype = Object.create(Type.prototype);
+
+/**
+ * Convert a value to a pointer.
+ *
+ * Protocol:
+ * - |null| returns |null|
+ * - a string returns |{string: value}|
+ * - a typed array returns |{ptr: address_of_buffer}|
+ * - a C array returns |{ptr: address_of_buffer}|
+ * everything else raises an error
+ */
+PtrType.prototype.toMsg = function ptr_toMsg(value) {
+ if (value == null) {
+ return null;
+ }
+ if (typeof value == "string") {
+ return { string: value };
+ }
+ if (isTypedArray(value)) {
+ // Automatically transfer typed arrays
+ return new Meta({data: value}, {transfers: [value.buffer]});
+ }
+ if (isArrayBuffer(value)) {
+ // Automatically transfer array buffers
+ return new Meta({data: value}, {transfers: [value]});
+ }
+ let normalized;
+ if ("addressOfElement" in value) { // C array
+ normalized = value.addressOfElement(0);
+ } else if ("isNull" in value) { // C pointer
+ normalized = value;
+ } else {
+ throw new TypeError("Value " + value +
+ " cannot be converted to a pointer");
+ }
+ let cast = Type.uintptr_t.cast(normalized);
+ return {ptr: cast.value.toString()};
+};
+
+/**
+ * Convert a message back to a pointer.
+ */
+PtrType.prototype.fromMsg = function ptr_fromMsg(msg) {
+ if (msg == null) {
+ return null;
+ }
+ if ("string" in msg) {
+ return msg.string;
+ }
+ if ("data" in msg) {
+ return msg.data;
+ }
+ if ("ptr" in msg) {
+ let address = ctypes.uintptr_t(msg.ptr);
+ return this.cast(address);
+ }
+ throw new TypeError("Message " + msg.toSource() +
+ " does not represent a pointer");
+};
+
+exports.Type = Type;
+
+
+/*
+ * Some values are large integers on 64 bit platforms. Unfortunately,
+ * in practice, 64 bit integers cannot be manipulated in JS. We
+ * therefore project them to regular numbers whenever possible.
+ */
+
+var projectLargeInt = function projectLargeInt(x) {
+ let str = x.toString();
+ let rv = parseInt(str, 10);
+ if (rv.toString() !== str) {
+ throw new TypeError("Number " + str + " cannot be projected to a double");
+ }
+ return rv;
+};
+var projectLargeUInt = function projectLargeUInt(x) {
+ return projectLargeInt(x);
+};
+var projectValue = function projectValue(x) {
+ if (!(x instanceof ctypes.CData)) {
+ return x;
+ }
+ if (!("value" in x)) { // Sanity check
+ throw new TypeError("Number " + x.toSource() + " has no field |value|");
+ }
+ return x.value;
+};
+
+function projector(type, signed) {
+ LOG("Determining best projection for", type,
+ "(size: ", type.size, ")", signed?"signed":"unsigned");
+ if (type instanceof Type) {
+ type = type.implementation;
+ }
+ if (!type.size) {
+ throw new TypeError("Argument is not a proper C type");
+ }
+ // Determine if type is projected to Int64/Uint64
+ if (type.size == 8 // Usual case
+ // The following cases have special treatment in js-ctypes
+ // Regardless of their size, the value getter returns
+ // a Int64/Uint64
+ || type == ctypes.size_t // Special cases
+ || type == ctypes.ssize_t
+ || type == ctypes.intptr_t
+ || type == ctypes.uintptr_t
+ || type == ctypes.off_t) {
+ if (signed) {
+ LOG("Projected as a large signed integer");
+ return projectLargeInt;
+ } else {
+ LOG("Projected as a large unsigned integer");
+ return projectLargeUInt;
+ }
+ }
+ LOG("Projected as a regular number");
+ return projectValue;
+};
+exports.projectValue = projectValue;
+
+/**
+ * Get the appropriate type for an unsigned int of the given size.
+ *
+ * This function is useful to define types such as |mode_t| whose
+ * actual width depends on the OS/platform.
+ *
+ * @param {number} size The number of bytes requested.
+ */
+Type.uintn_t = function uintn_t(size) {
+ switch (size) {
+ case 1: return Type.uint8_t;
+ case 2: return Type.uint16_t;
+ case 4: return Type.uint32_t;
+ case 8: return Type.uint64_t;
+ default:
+ throw new Error("Cannot represent unsigned integers of " + size + " bytes");
+ }
+};
+
+/**
+ * Get the appropriate type for an signed int of the given size.
+ *
+ * This function is useful to define types such as |mode_t| whose
+ * actual width depends on the OS/platform.
+ *
+ * @param {number} size The number of bytes requested.
+ */
+Type.intn_t = function intn_t(size) {
+ switch (size) {
+ case 1: return Type.int8_t;
+ case 2: return Type.int16_t;
+ case 4: return Type.int32_t;
+ case 8: return Type.int64_t;
+ default:
+ throw new Error("Cannot represent integers of " + size + " bytes");
+ }
+};
+
+/**
+ * Actual implementation of common C types.
+ */
+
+/**
+ * The void value.
+ */
+Type.void_t =
+ new Type("void",
+ ctypes.void_t);
+
+/**
+ * Shortcut for |void*|.
+ */
+Type.voidptr_t =
+ new PtrType("void*",
+ ctypes.voidptr_t,
+ Type.void_t);
+
+// void* is a special case as we can cast any pointer to/from it
+// so we have to shortcut |in_ptr|/|out_ptr|/|inout_ptr| and
+// ensure that js-ctypes' casting mechanism is invoked directly
+["in_ptr", "out_ptr", "inout_ptr"].forEach(function(key) {
+ Object.defineProperty(Type.void_t, key,
+ {
+ value: Type.voidptr_t
+ });
+});
+
+/**
+ * A Type of integers.
+ *
+ * @param {string} name The name of this type.
+ * @param {CType} implementation The underlying js-ctypes implementation.
+ * @param {bool} signed |true| if this is a type of signed integers,
+ * |false| otherwise.
+ *
+ * @constructor
+ */
+function IntType(name, implementation, signed) {
+ Type.call(this, name, implementation);
+ this.importFromC = projector(implementation, signed);
+ this.project = this.importFromC;
+};
+IntType.prototype = Object.create(Type.prototype);
+IntType.prototype.toMsg = function toMsg(value) {
+ if (typeof value == "number") {
+ return value;
+ }
+ return this.project(value);
+};
+
+/**
+ * A C char (one byte)
+ */
+Type.char =
+ new Type("char",
+ ctypes.char);
+
+/**
+ * A C wide char (two bytes)
+ */
+Type.char16_t =
+ new Type("char16_t",
+ ctypes.char16_t);
+
+ /**
+ * Base string types.
+ */
+Type.cstring = Type.char.in_ptr.withName("[in] C string");
+Type.wstring = Type.char16_t.in_ptr.withName("[in] wide string");
+Type.out_cstring = Type.char.out_ptr.withName("[out] C string");
+Type.out_wstring = Type.char16_t.out_ptr.withName("[out] wide string");
+
+/**
+ * A C integer (8-bits).
+ */
+Type.int8_t =
+ new IntType("int8_t", ctypes.int8_t, true);
+
+Type.uint8_t =
+ new IntType("uint8_t", ctypes.uint8_t, false);
+
+/**
+ * A C integer (16-bits).
+ *
+ * Also known as WORD under Windows.
+ */
+Type.int16_t =
+ new IntType("int16_t", ctypes.int16_t, true);
+
+Type.uint16_t =
+ new IntType("uint16_t", ctypes.uint16_t, false);
+
+/**
+ * A C integer (32-bits).
+ *
+ * Also known as DWORD under Windows.
+ */
+Type.int32_t =
+ new IntType("int32_t", ctypes.int32_t, true);
+
+Type.uint32_t =
+ new IntType("uint32_t", ctypes.uint32_t, false);
+
+/**
+ * A C integer (64-bits).
+ */
+Type.int64_t =
+ new IntType("int64_t", ctypes.int64_t, true);
+
+Type.uint64_t =
+ new IntType("uint64_t", ctypes.uint64_t, false);
+
+ /**
+ * A C integer
+ *
+ * Size depends on the platform.
+ */
+Type.int = Type.intn_t(ctypes.int.size).
+ withName("int");
+
+Type.unsigned_int = Type.intn_t(ctypes.unsigned_int.size).
+ withName("unsigned int");
+
+/**
+ * A C long integer.
+ *
+ * Size depends on the platform.
+ */
+Type.long =
+ Type.intn_t(ctypes.long.size).withName("long");
+
+Type.unsigned_long =
+ Type.intn_t(ctypes.unsigned_long.size).withName("unsigned long");
+
+/**
+ * An unsigned integer with the same size as a pointer.
+ *
+ * Used to cast a pointer to an integer, whenever necessary.
+ */
+Type.uintptr_t =
+ Type.uintn_t(ctypes.uintptr_t.size).withName("uintptr_t");
+
+/**
+ * A boolean.
+ * Implemented as a C integer.
+ */
+Type.bool = Type.int.withName("bool");
+Type.bool.importFromC = function projectBool(x) {
+ return !!(x.value);
+};
+
+/**
+ * A user identifier.
+ *
+ * Implemented as a C integer.
+ */
+Type.uid_t =
+ Type.int.withName("uid_t");
+
+/**
+ * A group identifier.
+ *
+ * Implemented as a C integer.
+ */
+Type.gid_t =
+ Type.int.withName("gid_t");
+
+/**
+ * An offset (positive or negative).
+ *
+ * Implemented as a C integer.
+ */
+Type.off_t =
+ new IntType("off_t", ctypes.off_t, true);
+
+/**
+ * A size (positive).
+ *
+ * Implemented as a C size_t.
+ */
+Type.size_t =
+ new IntType("size_t", ctypes.size_t, false);
+
+/**
+ * An offset (positive or negative).
+ * Implemented as a C integer.
+ */
+Type.ssize_t =
+ new IntType("ssize_t", ctypes.ssize_t, true);
+
+/**
+ * Encoding/decoding strings
+ */
+Type.uencoder =
+ new Type("uencoder", ctypes.StructType("uencoder"));
+Type.udecoder =
+ new Type("udecoder", ctypes.StructType("udecoder"));
+
+/**
+ * Utility class, used to build a |struct| type
+ * from a set of field names, types and offsets.
+ *
+ * @param {string} name The name of the |struct| type.
+ * @param {number} size The total size of the |struct| type in bytes.
+ */
+function HollowStructure(name, size) {
+ if (!name) {
+ throw new TypeError("HollowStructure expects a name");
+ }
+ if (!size || size < 0) {
+ throw new TypeError("HollowStructure expects a (positive) size");
+ }
+
+ // A mapping from offsets in the struct to name/type pairs
+ // (or nothing if no field starts at that offset).
+ this.offset_to_field_info = [];
+
+ // The name of the struct
+ this.name = name;
+
+ // The size of the struct, in bytes
+ this.size = size;
+
+ // The number of paddings inserted so far.
+ // Used to give distinct names to padding fields.
+ this._paddings = 0;
+}
+HollowStructure.prototype = {
+ /**
+ * Add a field at a given offset.
+ *
+ * @param {number} offset The offset at which to insert the field.
+ * @param {string} name The name of the field.
+ * @param {CType|Type} type The type of the field.
+ */
+ add_field_at: function add_field_at(offset, name, type) {
+ if (offset == null) {
+ throw new TypeError("add_field_at requires a non-null offset");
+ }
+ if (!name) {
+ throw new TypeError("add_field_at requires a non-null name");
+ }
+ if (!type) {
+ throw new TypeError("add_field_at requires a non-null type");
+ }
+ if (type instanceof Type) {
+ type = type.implementation;
+ }
+ if (this.offset_to_field_info[offset]) {
+ throw new Error("HollowStructure " + this.name +
+ " already has a field at offset " + offset);
+ }
+ if (offset + type.size > this.size) {
+ throw new Error("HollowStructure " + this.name +
+ " cannot place a value of type " + type +
+ " at offset " + offset +
+ " without exceeding its size of " + this.size);
+ }
+ let field = {name: name, type:type};
+ this.offset_to_field_info[offset] = field;
+ },
+
+ /**
+ * Create a pseudo-field that will only serve as padding.
+ *
+ * @param {number} size The number of bytes in the field.
+ * @return {Object} An association field-name => field-type,
+ * as expected by |ctypes.StructType|.
+ */
+ _makePaddingField: function makePaddingField(size) {
+ let field = ({});
+ field["padding_" + this._paddings] =
+ ctypes.ArrayType(ctypes.uint8_t, size);
+ this._paddings++;
+ return field;
+ },
+
+ /**
+ * Convert this |HollowStructure| into a |Type|.
+ */
+ getType: function getType() {
+ // Contents of the structure, in the format expected
+ // by ctypes.StructType.
+ let struct = [];
+
+ let i = 0;
+ while (i < this.size) {
+ let currentField = this.offset_to_field_info[i];
+ if (!currentField) {
+ // No field was specified at this offset, we need to
+ // introduce some padding.
+
+ // Firstly, determine how many bytes of padding
+ let padding_length = 1;
+ while (i + padding_length < this.size
+ && !this.offset_to_field_info[i + padding_length]) {
+ ++padding_length;
+ }
+
+ // Then add the padding
+ struct.push(this._makePaddingField(padding_length));
+
+ // And proceed
+ i += padding_length;
+ } else {
+ // We have a field at this offset.
+
+ // Firstly, ensure that we do not have two overlapping fields
+ for (let j = 1; j < currentField.type.size; ++j) {
+ let candidateField = this.offset_to_field_info[i + j];
+ if (candidateField) {
+ throw new Error("Fields " + currentField.name +
+ " and " + candidateField.name +
+ " overlap at position " + (i + j));
+ }
+ }
+
+ // Then add the field
+ let field = ({});
+ field[currentField.name] = currentField.type;
+ struct.push(field);
+
+ // And proceed
+ i += currentField.type.size;
+ }
+ }
+ let result = new Type(this.name, ctypes.StructType(this.name, struct));
+ if (result.implementation.size != this.size) {
+ throw new Error("Wrong size for type " + this.name +
+ ": expected " + this.size +
+ ", found " + result.implementation.size +
+ " (" + result.implementation.toSource() + ")");
+ }
+ return result;
+ }
+};
+exports.HollowStructure = HollowStructure;
+
+/**
+ * Representation of a native library.
+ *
+ * The native library is opened lazily, during the first call to its
+ * field |library| or whenever accessing one of the methods imported
+ * with declareLazyFFI.
+ *
+ * @param {string} name A human-readable name for the library. Used
+ * for debugging and error reporting.
+ * @param {string...} candidates A list of system libraries that may
+ * represent this library. Used e.g. to try different library names
+ * on distinct operating systems ("libxul", "XUL", etc.).
+ *
+ * @constructor
+ */
+function Library(name, ...candidates) {
+ this.name = name;
+ this._candidates = candidates;
+};
+Library.prototype = Object.freeze({
+ /**
+ * The native library as a js-ctypes object.
+ *
+ * @throws {Error} If none of the candidate libraries could be opened.
+ */
+ get library() {
+ let library;
+ delete this.library;
+ for (let candidate of this._candidates) {
+ try {
+ library = ctypes.open(candidate);
+ break;
+ } catch (ex) {
+ LOG("Could not open library", candidate, ex);
+ }
+ }
+ this._candidates = null;
+ if (library) {
+ Object.defineProperty(this, "library", {
+ value: library
+ });
+ Object.freeze(this);
+ return library;
+ }
+ let error = new Error("Could not open library " + this.name);
+ Object.defineProperty(this, "library", {
+ get: function() {
+ throw error;
+ }
+ });
+ Object.freeze(this);
+ throw error;
+ },
+
+ /**
+ * Declare a function, lazily.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {Type} returnType The type of values returned by the function.
+ * @param {...Type} argTypes The type of arguments to the function.
+ */
+ declareLazyFFI: function(object, field, ...args) {
+ let lib = this;
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ let ffi = declareFFI(lib.library, ...args);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ },
+
+ /**
+ * Define a js-ctypes function lazily using ctypes method declare.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {ctypes.CType} returnType The type of values returned by the function.
+ * @param {...ctypes.CType} argTypes The type of arguments to the function.
+ */
+ declareLazy: function(object, field, ...args) {
+ let lib = this;
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ let ffi = lib.library.declare(...args);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ },
+
+ /**
+ * Define a js-ctypes function lazily using ctypes method declare,
+ * with a fallback library to use if this library can't be opened
+ * or the function cannot be declared.
+ *
+ * @param {fallbacklibrary} The fallback Library object.
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {ctypes.CType} returnType The type of values returned by the function.
+ * @param {...ctypes.CType} argTypes The type of arguments to the function.
+ */
+ declareLazyWithFallback: function(fallbacklibrary, object, field, ...args) {
+ let lib = this;
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ try {
+ let ffi = lib.library.declare(...args);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ } catch (ex) {
+ // Use the fallback library and get the symbol from there.
+ fallbacklibrary.declareLazy(object, field, ...args);
+ return object[field];
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ },
+
+ toString: function() {
+ return "[Library " + this.name + "]";
+ }
+});
+exports.Library = Library;
+
+/**
+ * Declare a function through js-ctypes
+ *
+ * @param {ctypes.library} lib The ctypes library holding the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {Type} returnType The type of values returned by the function.
+ * @param {...Type} argTypes The type of arguments to the function.
+ *
+ * @return null if the function could not be defined (generally because
+ * it does not exist), or a JavaScript wrapper performing the call to C
+ * and any type conversion required.
+ */
+var declareFFI = function declareFFI(lib, symbol, abi,
+ returnType /*, argTypes ...*/) {
+ LOG("Attempting to declare FFI ", symbol);
+ // We guard agressively, to avoid any late surprise
+ if (typeof symbol != "string") {
+ throw new TypeError("declareFFI expects as first argument a string");
+ }
+ abi = abi || ctypes.default_abi;
+ if (Object.prototype.toString.call(abi) != "[object CABI]") {
+ // Note: This is the only known manner of checking whether an object
+ // is an abi.
+ throw new TypeError("declareFFI expects as second argument an abi or null");
+ }
+ if (!returnType.importFromC) {
+ throw new TypeError("declareFFI expects as third argument an instance of Type");
+ }
+ let signature = [symbol, abi];
+ let argtypes = [];
+ for (let i = 3; i < arguments.length; ++i) {
+ let current = arguments[i];
+ if (!current) {
+ throw new TypeError("Missing type for argument " + ( i - 3 ) +
+ " of symbol " + symbol);
+ }
+ if (!current.implementation) {
+ throw new TypeError("Missing implementation for argument " + (i - 3)
+ + " of symbol " + symbol
+ + " ( " + current.name + " )" );
+ }
+ signature.push(current.implementation);
+ }
+ try {
+ let fun = lib.declare.apply(lib, signature);
+ let result = function ffi(...args) {
+ for (let i = 0; i < args.length; i++) {
+ if (typeof args[i] == "undefined") {
+ throw new TypeError("Argument " + i + " of " + symbol + " is undefined");
+ }
+ }
+ let result = fun.apply(fun, args);
+ return returnType.importFromC(result, symbol);
+ };
+ LOG("Function", symbol, "declared");
+ return result;
+ } catch (x) {
+ // Note: Not being able to declare a function is normal.
+ // Some functions are OS (or OS version)-specific.
+ LOG("Could not declare function ", symbol, x);
+ return null;
+ }
+};
+exports.declareFFI = declareFFI;
+
+/**
+ * Define a lazy getter to a js-ctypes function using declareFFI.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {ctypes.library} lib The ctypes library holding the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {Type} returnType The type of values returned by the function.
+ * @param {...Type} argTypes The type of arguments to the function.
+ */
+function declareLazyFFI(object, field, ...declareFFIArgs) {
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ let ffi = declareFFI(...declareFFIArgs);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+}
+exports.declareLazyFFI = declareLazyFFI;
+
+/**
+ * Define a lazy getter to a js-ctypes function using ctypes method declare.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {ctypes.library} lib The ctypes library holding the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {ctypes.CType} returnType The type of values returned by the function.
+ * @param {...ctypes.CType} argTypes The type of arguments to the function.
+ */
+function declareLazy(object, field, lib, ...declareArgs) {
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ try {
+ let ffi = lib.declare(...declareArgs);
+ return this[field] = ffi;
+ } catch (ex) {
+ // The symbol doesn't exist
+ return undefined;
+ }
+ },
+ configurable: true
+ });
+}
+exports.declareLazy = declareLazy;
+
+/**
+ * Utility function used to sanity check buffer and length arguments. The
+ * buffer must be a Typed Array.
+ *
+ * @param {Typed array} candidate The buffer.
+ * @param {number} bytes The number of bytes that |candidate| should contain.
+ *
+ * @return number The bytes argument clamped to the length of the buffer.
+ */
+function normalizeBufferArgs(candidate, bytes) {
+ if (!candidate) {
+ throw new TypeError("Expecting a Typed Array");
+ }
+ if (!isTypedArray(candidate)) {
+ throw new TypeError("Expecting a Typed Array");
+ }
+ if (bytes == null) {
+ bytes = candidate.byteLength;
+ } else if (candidate.byteLength < bytes) {
+ throw new TypeError("Buffer is too short. I need at least " +
+ bytes +
+ " bytes but I have only " +
+ candidate.byteLength +
+ "bytes");
+ }
+ return bytes;
+};
+exports.normalizeBufferArgs = normalizeBufferArgs;
+
+///////////////////// OS interactions
+
+/**
+ * An OS error.
+ *
+ * This class is provided mostly for type-matching. If you need more
+ * details about an error, you should use the platform-specific error
+ * codes provided by subclasses of |OS.Shared.Error|.
+ *
+ * @param {string} operation The operation that failed.
+ * @param {string=} path The path of the file on which the operation failed,
+ * or nothing if there was no file involved in the failure.
+ *
+ * @constructor
+ */
+function OSError(operation, path = "") {
+ Error.call(this);
+ this.operation = operation;
+ this.path = path;
+}
+OSError.prototype = Object.create(Error.prototype);
+exports.OSError = OSError;
+
+
+///////////////////// Temporary boilerplate
+// Boilerplate, to simplify the transition to require()
+// Do not rely upon this symbol, it will disappear with
+// bug 883050.
+exports.OS = {
+ Constants: exports.Constants,
+ Shared: {
+ LOG: LOG,
+ clone: clone,
+ Type: Type,
+ HollowStructure: HollowStructure,
+ Error: OSError,
+ declareFFI: declareFFI,
+ projectValue: projectValue,
+ isTypedArray: isTypedArray,
+ defineLazyGetter: defineLazyGetter
+ }
+};
+
+Object.defineProperty(exports.OS.Shared, "DEBUG", {
+ get: function() {
+ return Config.DEBUG;
+ },
+ set: function(x) {
+ return Config.DEBUG = x;
+ }
+});
+Object.defineProperty(exports.OS.Shared, "TEST", {
+ get: function() {
+ return Config.TEST;
+ },
+ set: function(x) {
+ return Config.TEST = x;
+ }
+});
+
+
+///////////////////// Permanent boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}
diff --git a/components/osfile/modules/osfile_shared_front.jsm b/components/osfile/modules/osfile_shared_front.jsm
new file mode 100644
index 000000000..a2971991d
--- /dev/null
+++ b/components/osfile/modules/osfile_shared_front.jsm
@@ -0,0 +1,567 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Code shared by OS.File front-ends.
+ *
+ * This code is meant to be included by another library. It is also meant to
+ * be executed only on a worker thread.
+ */
+
+if (typeof Components != "undefined") {
+ throw new Error("osfile_shared_front.jsm cannot be used from the main thread");
+}
+(function(exports) {
+
+var SharedAll =
+ require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+var Path = require("resource://gre/modules/osfile/ospath.jsm");
+var Lz4 =
+ require("resource://gre/modules/lz4.js");
+var LOG = SharedAll.LOG.bind(SharedAll, "Shared front-end");
+var clone = SharedAll.clone;
+
+/**
+ * Code shared by implementations of File.
+ *
+ * @param {*} fd An OS-specific file handle.
+ * @param {string} path File path of the file handle, used for error-reporting.
+ * @constructor
+ */
+var AbstractFile = function AbstractFile(fd, path) {
+ this._fd = fd;
+ if (!path) {
+ throw new TypeError("path is expected");
+ }
+ this._path = path;
+};
+
+AbstractFile.prototype = {
+ /**
+ * Return the file handle.
+ *
+ * @throw OS.File.Error if the file has been closed.
+ */
+ get fd() {
+ if (this._fd) {
+ return this._fd;
+ }
+ throw OS.File.Error.closed("accessing file", this._path);
+ },
+ /**
+ * Read bytes from this file to a new buffer.
+ *
+ * @param {number=} maybeBytes (deprecated, please use options.bytes)
+ * @param {JSON} options
+ * @return {Uint8Array} An array containing the bytes read.
+ */
+ read: function read(maybeBytes, options = {}) {
+ if (typeof maybeBytes === "object") {
+ // Caller has skipped `maybeBytes` and provided an options object.
+ options = clone(maybeBytes);
+ maybeBytes = null;
+ } else {
+ options = options || {};
+ }
+ let bytes = options.bytes || undefined;
+ if (bytes === undefined) {
+ bytes = maybeBytes == null ? this.stat().size : maybeBytes;
+ }
+ let buffer = new Uint8Array(bytes);
+ let pos = 0;
+ while (pos < bytes) {
+ let length = bytes - pos;
+ let view = new DataView(buffer.buffer, pos, length);
+ let chunkSize = this._read(view, length, options);
+ if (chunkSize == 0) {
+ break;
+ }
+ pos += chunkSize;
+ }
+ if (pos == bytes) {
+ return buffer;
+ } else {
+ return buffer.subarray(0, pos);
+ }
+ },
+
+ /**
+ * Write bytes from a buffer to this file.
+ *
+ * Note that, by default, this function may perform several I/O
+ * operations to ensure that the buffer is fully written.
+ *
+ * @param {Typed array} buffer The buffer in which the the bytes are
+ * stored. The buffer must be large enough to accomodate |bytes| bytes.
+ * @param {*=} options Optionally, an object that may contain the
+ * following fields:
+ * - {number} bytes The number of |bytes| to write from the buffer. If
+ * unspecified, this is |buffer.byteLength|.
+ *
+ * @return {number} The number of bytes actually written.
+ */
+ write: function write(buffer, options = {}) {
+ let bytes =
+ SharedAll.normalizeBufferArgs(buffer, ("bytes" in options) ? options.bytes : undefined);
+ let pos = 0;
+ while (pos < bytes) {
+ let length = bytes - pos;
+ let view = new DataView(buffer.buffer, buffer.byteOffset + pos, length);
+ let chunkSize = this._write(view, length, options);
+ pos += chunkSize;
+ }
+ return pos;
+ }
+};
+
+/**
+ * Creates and opens a file with a unique name. By default, generate a random HEX number and use it to create a unique new file name.
+ *
+ * @param {string} path The path to the file.
+ * @param {*=} options Additional options for file opening. This
+ * implementation interprets the following fields:
+ *
+ * - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext.
+ * If |false| use HEX numbers ie: filename-A65BC0.ext
+ * - {number} maxReadableNumber Used to limit the amount of tries after a failed
+ * file creation. Default is 20.
+ *
+ * @return {Object} contains A file object{file} and the path{path}.
+ * @throws {OS.File.Error} If the file could not be opened.
+ */
+AbstractFile.openUnique = function openUnique(path, options = {}) {
+ let mode = {
+ create : true
+ };
+
+ let dirName = Path.dirname(path);
+ let leafName = Path.basename(path);
+ let lastDotCharacter = leafName.lastIndexOf('.');
+ let fileName = leafName.substring(0, lastDotCharacter != -1 ? lastDotCharacter : leafName.length);
+ let suffix = (lastDotCharacter != -1 ? leafName.substring(lastDotCharacter) : "");
+ let uniquePath = "";
+ let maxAttempts = options.maxAttempts || 99;
+ let humanReadable = !!options.humanReadable;
+ const HEX_RADIX = 16;
+ // We produce HEX numbers between 0 and 2^24 - 1.
+ const MAX_HEX_NUMBER = 16777215;
+
+ try {
+ return {
+ path: path,
+ file: OS.File.open(path, mode)
+ };
+ } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) {
+ for (let i = 0; i < maxAttempts; ++i) {
+ try {
+ if (humanReadable) {
+ uniquePath = Path.join(dirName, fileName + "-" + (i + 1) + suffix);
+ } else {
+ let hexNumber = Math.floor(Math.random() * MAX_HEX_NUMBER).toString(HEX_RADIX);
+ uniquePath = Path.join(dirName, fileName + "-" + hexNumber + suffix);
+ }
+ return {
+ path: uniquePath,
+ file: OS.File.open(uniquePath, mode)
+ };
+ } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) {
+ // keep trying ...
+ }
+ }
+ throw OS.File.Error.exists("could not find an unused file name.", path);
+ }
+};
+
+/**
+ * Code shared by iterators.
+ */
+AbstractFile.AbstractIterator = function AbstractIterator() {
+};
+AbstractFile.AbstractIterator.prototype = {
+ /**
+ * Allow iterating with |for|
+ */
+ __iterator__: function __iterator__() {
+ return this;
+ },
+ /**
+ * Apply a function to all elements of the directory sequentially.
+ *
+ * @param {Function} cb This function will be applied to all entries
+ * of the directory. It receives as arguments
+ * - the OS.File.Entry corresponding to the entry;
+ * - the index of the entry in the enumeration;
+ * - the iterator itself - calling |close| on the iterator stops
+ * the loop.
+ */
+ forEach: function forEach(cb) {
+ let index = 0;
+ for (let entry in this) {
+ cb(entry, index++, this);
+ }
+ },
+ /**
+ * Return several entries at once.
+ *
+ * Entries are returned in the same order as a walk with |forEach| or
+ * |for(...)|.
+ *
+ * @param {number=} length If specified, the number of entries
+ * to return. If unspecified, return all remaining entries.
+ * @return {Array} An array containing the next |length| entries, or
+ * less if the iteration contains less than |length| entries left.
+ */
+ nextBatch: function nextBatch(length) {
+ let array = [];
+ let i = 0;
+ for (let entry in this) {
+ array.push(entry);
+ if (++i >= length) {
+ return array;
+ }
+ }
+ return array;
+ }
+};
+
+/**
+ * Utility function shared by implementations of |OS.File.open|:
+ * extract read/write/trunc/create/existing flags from a |mode|
+ * object.
+ *
+ * @param {*=} mode An object that may contain fields |read|,
+ * |write|, |truncate|, |create|, |existing|. These fields
+ * are interpreted only if true-ish.
+ * @return {{read:bool, write:bool, trunc:bool, create:bool,
+ * existing:bool}} an object recapitulating the options set
+ * by |mode|.
+ * @throws {TypeError} If |mode| contains other fields, or
+ * if it contains both |create| and |truncate|, or |create|
+ * and |existing|.
+ */
+AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) {
+ let result = {
+ read: false,
+ write: false,
+ trunc: false,
+ create: false,
+ existing: false,
+ append: true
+ };
+ for (let key in mode) {
+ let val = !!mode[key]; // bool cast.
+ switch (key) {
+ case "read":
+ result.read = val;
+ break;
+ case "write":
+ result.write = val;
+ break;
+ case "truncate": // fallthrough
+ case "trunc":
+ result.trunc = val;
+ result.write |= val;
+ break;
+ case "create":
+ result.create = val;
+ result.write |= val;
+ break;
+ case "existing": // fallthrough
+ case "exist":
+ result.existing = val;
+ break;
+ case "append":
+ result.append = val;
+ break;
+ default:
+ throw new TypeError("Mode " + key + " not understood");
+ }
+ }
+ // Reject opposite modes
+ if (result.existing && result.create) {
+ throw new TypeError("Cannot specify both existing:true and create:true");
+ }
+ if (result.trunc && result.create) {
+ throw new TypeError("Cannot specify both trunc:true and create:true");
+ }
+ // Handle read/write
+ if (!result.write) {
+ result.read = true;
+ }
+ return result;
+};
+
+/**
+ * Return the contents of a file.
+ *
+ * @param {string} path The path to the file.
+ * @param {number=} bytes Optionally, an upper bound to the number of bytes
+ * to read. DEPRECATED - please use options.bytes instead.
+ * @param {object=} options Optionally, an object with some of the following
+ * fields:
+ * - {number} bytes An upper bound to the number of bytes to read.
+ * - {string} compression If "lz4" and if the file is compressed using the lz4
+ * compression algorithm, decompress the file contents on the fly.
+ *
+ * @return {Uint8Array} A buffer holding the bytes
+ * and the number of bytes read from the file.
+ */
+AbstractFile.read = function read(path, bytes, options = {}) {
+ if (bytes && typeof bytes == "object") {
+ options = bytes;
+ bytes = options.bytes || null;
+ }
+ if ("encoding" in options && typeof options.encoding != "string") {
+ throw new TypeError("Invalid type for option encoding");
+ }
+ if ("compression" in options && typeof options.compression != "string") {
+ throw new TypeError("Invalid type for option compression: " + options.compression);
+ }
+ if ("bytes" in options && typeof options.bytes != "number") {
+ throw new TypeError("Invalid type for option bytes");
+ }
+ let file = exports.OS.File.open(path);
+ try {
+ let buffer = file.read(bytes, options);
+ if ("compression" in options) {
+ if (options.compression == "lz4") {
+ options = Object.create(options);
+ options.path = path;
+ buffer = Lz4.decompressFileContent(buffer, options);
+ } else {
+ throw OS.File.Error.invalidArgument("Compression");
+ }
+ }
+ if (!("encoding" in options)) {
+ return buffer;
+ }
+ let decoder;
+ try {
+ decoder = new TextDecoder(options.encoding);
+ } catch (ex if ex instanceof RangeError) {
+ throw OS.File.Error.invalidArgument("Decode");
+ }
+ return decoder.decode(buffer);
+ } finally {
+ file.close();
+ }
+};
+
+/**
+ * Write a file, atomically.
+ *
+ * By opposition to a regular |write|, this operation ensures that,
+ * until the contents are fully written, the destination file is
+ * not modified.
+ *
+ * Limitation: In a few extreme cases (hardware failure during the
+ * write, user unplugging disk during the write, etc.), data may be
+ * corrupted. If your data is user-critical (e.g. preferences,
+ * application data, etc.), you may wish to consider adding options
+ * |tmpPath| and/or |flush| to reduce the likelihood of corruption, as
+ * detailed below. Note that no combination of options can be
+ * guaranteed to totally eliminate the risk of corruption.
+ *
+ * @param {string} path The path of the file to modify.
+ * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write.
+ * @param {*=} options Optionally, an object determining the behavior
+ * of this function. This object may contain the following fields:
+ * - {number} bytes The number of bytes to write. If unspecified,
+ * |buffer.byteLength|. Required if |buffer| is a C pointer.
+ * - {string} tmpPath If |null| or unspecified, write all data directly
+ * to |path|. If specified, write all data to a temporary file called
+ * |tmpPath| and, once this write is complete, rename the file to
+ * replace |path|. Performing this additional operation is a little
+ * slower but also a little safer.
+ * - {bool} noOverwrite - If set, this function will fail if a file already
+ * exists at |path|.
+ * - {bool} flush - If |false| or unspecified, return immediately once the
+ * write is complete. If |true|, before writing, force the operating system
+ * to write its internal disk buffers to the disk. This is considerably slower
+ * (not just for the application but for the whole system) but also safer:
+ * if the system shuts down improperly (typically due to a kernel freeze
+ * or a power failure) or if the device is disconnected before the buffer
+ * is flushed, the file has more chances of not being corrupted.
+ * - {string} compression - If empty or unspecified, do not compress the file.
+ * If "lz4", compress the contents of the file atomically using lz4. For the
+ * time being, the container format is specific to Mozilla and cannot be read
+ * by means other than OS.File.read(..., { compression: "lz4"})
+ * - {string} backupTo - If specified, backup the destination file as |backupTo|.
+ * Note that this function renames the destination file before overwriting it.
+ * If the process or the operating system freezes or crashes
+ * during the short window between these operations,
+ * the destination file will have been moved to its backup.
+ *
+ * @return {number} The number of bytes actually written.
+ */
+AbstractFile.writeAtomic =
+ function writeAtomic(path, buffer, options = {}) {
+
+ // Verify that path is defined and of the correct type
+ if (typeof path != "string" || path == "") {
+ throw new TypeError("File path should be a (non-empty) string");
+ }
+ let noOverwrite = options.noOverwrite;
+ if (noOverwrite && OS.File.exists(path)) {
+ throw OS.File.Error.exists("writeAtomic", path);
+ }
+
+ if (typeof buffer == "string") {
+ // Normalize buffer to a C buffer by encoding it
+ let encoding = options.encoding || "utf-8";
+ buffer = new TextEncoder(encoding).encode(buffer);
+ }
+
+ if ("compression" in options && options.compression == "lz4") {
+ buffer = Lz4.compressFileContent(buffer, options);
+ options = Object.create(options);
+ options.bytes = buffer.byteLength;
+ }
+
+ let bytesWritten = 0;
+
+ if (!options.tmpPath) {
+ if (options.backupTo) {
+ try {
+ OS.File.move(path, options.backupTo, {noCopy: true});
+ } catch (ex if ex.becauseNoSuchFile) {
+ // The file doesn't exist, nothing to backup.
+ }
+ }
+ // Just write, without any renaming trick
+ let dest = OS.File.open(path, {write: true, truncate: true});
+ try {
+ bytesWritten = dest.write(buffer, options);
+ if (options.flush) {
+ dest.flush();
+ }
+ } finally {
+ dest.close();
+ }
+ return bytesWritten;
+ }
+
+ let tmpFile = OS.File.open(options.tmpPath, {write: true, truncate: true});
+ try {
+ bytesWritten = tmpFile.write(buffer, options);
+ if (options.flush) {
+ tmpFile.flush();
+ }
+ } catch (x) {
+ OS.File.remove(options.tmpPath);
+ throw x;
+ } finally {
+ tmpFile.close();
+ }
+
+ if (options.backupTo) {
+ try {
+ OS.File.move(path, options.backupTo, {noCopy: true});
+ } catch (ex if ex.becauseNoSuchFile) {
+ // The file doesn't exist, nothing to backup.
+ }
+ }
+
+ OS.File.move(options.tmpPath, path, {noCopy: true});
+ return bytesWritten;
+};
+
+/**
+ * This function is used by removeDir to avoid calling lstat for each
+ * files under the specified directory. External callers should not call
+ * this function directly.
+ */
+AbstractFile.removeRecursive = function(path, options = {}) {
+ let iterator = new OS.File.DirectoryIterator(path);
+ if (!iterator.exists()) {
+ if (!("ignoreAbsent" in options) || options.ignoreAbsent) {
+ return;
+ }
+ }
+
+ try {
+ for (let entry in iterator) {
+ if (entry.isDir) {
+ if (entry.isLink) {
+ // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
+ // directories are directories themselves. OS.File.remove()
+ // will not work for them.
+ OS.File.removeEmptyDir(entry.path, options);
+ } else {
+ // Normal directories.
+ AbstractFile.removeRecursive(entry.path, options);
+ }
+ } else {
+ // NTFS symlinks to files, Unix symlinks, or regular files.
+ OS.File.remove(entry.path, options);
+ }
+ }
+ } finally {
+ iterator.close();
+ }
+
+ OS.File.removeEmptyDir(path);
+};
+
+/**
+ * Create a directory and, optionally, its parent directories.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options.
+ *
+ * - {string} from If specified, the call to |makeDir| creates all the
+ * ancestors of |path| that are descendants of |from|. Note that |path|
+ * must be a descendant of |from|, and that |from| and its existing
+ * subdirectories present in |path| must be user-writeable.
+ * Example:
+ * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+ * creates directories profileDir/foo, profileDir/foo/bar
+ * - {bool} ignoreExisting If |false|, throw an error if the directory
+ * already exists. |true| by default. Ignored if |from| is specified.
+ * - {number} unixMode Under Unix, if specified, a file creation mode,
+ * as per libc function |mkdir|. If unspecified, dirs are
+ * created with a default mode of 0700 (dir is private to
+ * the user, the user can read, write and execute). Ignored under Windows
+ * or if the file system does not support file creation modes.
+ * - {C pointer} winSecurity Under Windows, if specified, security
+ * attributes as per winapi function |CreateDirectory|. If
+ * unspecified, use the default security descriptor, inherited from
+ * the parent directory. Ignored under Unix or if the file system
+ * does not support security descriptors.
+ */
+AbstractFile.makeDir = function(path, options = {}) {
+ let from = options.from;
+ if (!from) {
+ OS.File._makeDir(path, options);
+ return;
+ }
+ if (!path.startsWith(from)) {
+ // Apparently, `from` is not a parent of `path`. However, we may
+ // have false negatives due to non-normalized paths, e.g.
+ // "foo//bar" is a parent of "foo/bar/sna".
+ path = Path.normalize(path);
+ from = Path.normalize(from);
+ if (!path.startsWith(from)) {
+ throw new Error("Incorrect use of option |from|: " + path + " is not a descendant of " + from);
+ }
+ }
+ let innerOptions = Object.create(options, {
+ ignoreExisting: {
+ value: true
+ }
+ });
+ // Compute the elements that appear in |path| but not in |from|.
+ let items = Path.split(path).components.slice(Path.split(from).components.length);
+ let current = from;
+ for (let item of items) {
+ current = Path.join(current, item);
+ OS.File._makeDir(current, innerOptions);
+ }
+};
+
+if (!exports.OS.Shared) {
+ exports.OS.Shared = {};
+}
+exports.OS.Shared.AbstractFile = AbstractFile;
+})(this);
diff --git a/components/osfile/modules/osfile_unix_allthreads.jsm b/components/osfile/modules/osfile_unix_allthreads.jsm
new file mode 100644
index 000000000..632f9c40b
--- /dev/null
+++ b/components/osfile/modules/osfile_unix_allthreads.jsm
@@ -0,0 +1,375 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 defines the thread-agnostic components of the Unix version
+ * of OS.File. It depends on the thread-agnostic cross-platform components
+ * of OS.File.
+ *
+ * It serves the following purposes:
+ * - open libc;
+ * - define OS.Unix.Error;
+ * - define a few constants and types that need to be defined on all platforms.
+ *
+ * This module can be:
+ * - opened from the main thread as a jsm module;
+ * - opened from a chrome worker through require().
+ */
+
+"use strict";
+
+var SharedAll;
+if (typeof Components != "undefined") {
+ let Cu = Components.utils;
+ // Module is opened as a jsm module
+ Cu.import("resource://gre/modules/ctypes.jsm", this);
+
+ SharedAll = {};
+ Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
+ this.exports = {};
+} else if (typeof module != "undefined" && typeof require != "undefined") {
+ // Module is loaded with require()
+ SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+} else {
+ throw new Error("Please open this module with Component.utils.import or with require()");
+}
+
+var LOG = SharedAll.LOG.bind(SharedAll, "Unix", "allthreads");
+var Const = SharedAll.Constants.libc;
+
+// Open libc
+var libc = new SharedAll.Library("libc",
+ "libc.so", "libSystem.B.dylib", "a.out");
+exports.libc = libc;
+
+// Define declareFFI
+var declareFFI = SharedAll.declareFFI.bind(null, libc);
+exports.declareFFI = declareFFI;
+
+// Define lazy binding
+var LazyBindings = {};
+libc.declareLazy(LazyBindings, "strerror",
+ "strerror", ctypes.default_abi,
+ /*return*/ ctypes.char.ptr,
+ /*errnum*/ ctypes.int);
+
+/**
+ * A File-related error.
+ *
+ * To obtain a human-readable error message, use method |toString|.
+ * To determine the cause of the error, use the various |becauseX|
+ * getters. To determine the operation that failed, use field
+ * |operation|.
+ *
+ * Additionally, this implementation offers a field
+ * |unixErrno|, which holds the OS-specific error
+ * constant. If you need this level of detail, you may match the value
+ * of this field against the error constants of |OS.Constants.libc|.
+ *
+ * @param {string=} operation The operation that failed. If unspecified,
+ * the name of the calling function is taken to be the operation that
+ * failed.
+ * @param {number=} lastError The OS-specific constant detailing the
+ * reason of the error. If unspecified, this is fetched from the system
+ * status.
+ * @param {string=} path The file path that manipulated. If unspecified,
+ * assign the empty string.
+ *
+ * @constructor
+ * @extends {OS.Shared.Error}
+ */
+var OSError = function OSError(operation = "unknown operation",
+ errno = ctypes.errno, path = "") {
+ SharedAll.OSError.call(this, operation, path);
+ this.unixErrno = errno;
+};
+OSError.prototype = Object.create(SharedAll.OSError.prototype);
+OSError.prototype.toString = function toString() {
+ return "Unix error " + this.unixErrno +
+ " during operation " + this.operation +
+ (this.path? " on file " + this.path : "") +
+ " (" + LazyBindings.strerror(this.unixErrno).readString() + ")";
+};
+OSError.prototype.toMsg = function toMsg() {
+ return OSError.toMsg(this);
+};
+
+/**
+ * |true| if the error was raised because a file or directory
+ * already exists, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseExists", {
+ get: function becauseExists() {
+ return this.unixErrno == Const.EEXIST;
+ }
+});
+/**
+ * |true| if the error was raised because a file or directory
+ * does not exist, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseNoSuchFile", {
+ get: function becauseNoSuchFile() {
+ return this.unixErrno == Const.ENOENT;
+ }
+});
+
+/**
+ * |true| if the error was raised because a directory is not empty
+ * does not exist, |false| otherwise.
+ */
+ Object.defineProperty(OSError.prototype, "becauseNotEmpty", {
+ get: function becauseNotEmpty() {
+ return this.unixErrno == Const.ENOTEMPTY;
+ }
+ });
+/**
+ * |true| if the error was raised because a file or directory
+ * is closed, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseClosed", {
+ get: function becauseClosed() {
+ return this.unixErrno == Const.EBADF;
+ }
+});
+/**
+ * |true| if the error was raised because permission is denied to
+ * access a file or directory, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseAccessDenied", {
+ get: function becauseAccessDenied() {
+ return this.unixErrno == Const.EACCES;
+ }
+});
+/**
+ * |true| if the error was raised because some invalid argument was passed,
+ * |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseInvalidArgument", {
+ get: function becauseInvalidArgument() {
+ return this.unixErrno == Const.EINVAL;
+ }
+});
+
+/**
+ * Serialize an instance of OSError to something that can be
+ * transmitted across threads (not necessarily a string).
+ */
+OSError.toMsg = function toMsg(error) {
+ return {
+ exn: "OS.File.Error",
+ fileName: error.moduleName,
+ lineNumber: error.lineNumber,
+ stack: error.moduleStack,
+ operation: error.operation,
+ unixErrno: error.unixErrno,
+ path: error.path
+ };
+};
+
+/**
+ * Deserialize a message back to an instance of OSError
+ */
+OSError.fromMsg = function fromMsg(msg) {
+ let error = new OSError(msg.operation, msg.unixErrno, msg.path);
+ error.stack = msg.stack;
+ error.fileName = msg.fileName;
+ error.lineNumber = msg.lineNumber;
+ return error;
+};
+exports.Error = OSError;
+
+/**
+ * Code shared by implementations of File.Info on Unix
+ *
+ * @constructor
+*/
+var AbstractInfo = function AbstractInfo(path, isDir, isSymLink, size, lastAccessDate,
+ lastModificationDate, unixLastStatusChangeDate,
+ unixOwner, unixGroup, unixMode) {
+ this._path = path;
+ this._isDir = isDir;
+ this._isSymlLink = isSymLink;
+ this._size = size;
+ this._lastAccessDate = lastAccessDate;
+ this._lastModificationDate = lastModificationDate;
+ this._unixLastStatusChangeDate = unixLastStatusChangeDate;
+ this._unixOwner = unixOwner;
+ this._unixGroup = unixGroup;
+ this._unixMode = unixMode;
+};
+
+AbstractInfo.prototype = {
+ /**
+ * The path of the file, used for error-reporting.
+ *
+ * @type {string}
+ */
+ get path() {
+ return this._path;
+ },
+ /**
+ * |true| if this file is a directory, |false| otherwise
+ */
+ get isDir() {
+ return this._isDir;
+ },
+ /**
+ * |true| if this file is a symbolink link, |false| otherwise
+ */
+ get isSymLink() {
+ return this._isSymlLink;
+ },
+ /**
+ * The size of the file, in bytes.
+ *
+ * Note that the result may be |NaN| if the size of the file cannot be
+ * represented in JavaScript.
+ *
+ * @type {number}
+ */
+ get size() {
+ return this._size;
+ },
+ /**
+ * The date of last access to this file.
+ *
+ * Note that the definition of last access may depend on the
+ * underlying operating system and file system.
+ *
+ * @type {Date}
+ */
+ get lastAccessDate() {
+ return this._lastAccessDate;
+ },
+ /**
+ * Return the date of last modification of this file.
+ */
+ get lastModificationDate() {
+ return this._lastModificationDate;
+ },
+ /**
+ * Return the date at which the status of this file was last modified
+ * (this is the date of the latest write/renaming/mode change/...
+ * of the file)
+ */
+ get unixLastStatusChangeDate() {
+ return this._unixLastStatusChangeDate;
+ },
+ /*
+ * Return the Unix owner of this file
+ */
+ get unixOwner() {
+ return this._unixOwner;
+ },
+ /*
+ * Return the Unix group of this file
+ */
+ get unixGroup() {
+ return this._unixGroup;
+ },
+ /*
+ * Return the Unix group of this file
+ */
+ get unixMode() {
+ return this._unixMode;
+ }
+};
+exports.AbstractInfo = AbstractInfo;
+
+/**
+ * Code shared by implementations of File.DirectoryIterator.Entry on Unix
+ *
+ * @constructor
+*/
+var AbstractEntry = function AbstractEntry(isDir, isSymLink, name, path) {
+ this._isDir = isDir;
+ this._isSymlLink = isSymLink;
+ this._name = name;
+ this._path = path;
+};
+
+AbstractEntry.prototype = {
+ /**
+ * |true| if the entry is a directory, |false| otherwise
+ */
+ get isDir() {
+ return this._isDir;
+ },
+ /**
+ * |true| if the entry is a directory, |false| otherwise
+ */
+ get isSymLink() {
+ return this._isSymlLink;
+ },
+ /**
+ * The name of the entry
+ * @type {string}
+ */
+ get name() {
+ return this._name;
+ },
+ /**
+ * The full path to the entry
+ */
+ get path() {
+ return this._path;
+ }
+};
+exports.AbstractEntry = AbstractEntry;
+
+// Special constants that need to be defined on all platforms
+
+exports.POS_START = Const.SEEK_SET;
+exports.POS_CURRENT = Const.SEEK_CUR;
+exports.POS_END = Const.SEEK_END;
+
+// Special types that need to be defined for communication
+// between threads
+var Type = Object.create(SharedAll.Type);
+exports.Type = Type;
+
+/**
+ * Native paths
+ *
+ * Under Unix, expressed as C strings
+ */
+Type.path = Type.cstring.withName("[in] path");
+Type.out_path = Type.out_cstring.withName("[out] path");
+
+// Special constructors that need to be defined on all threads
+OSError.closed = function closed(operation, path) {
+ return new OSError(operation, Const.EBADF, path);
+};
+
+OSError.exists = function exists(operation, path) {
+ return new OSError(operation, Const.EEXIST, path);
+};
+
+OSError.noSuchFile = function noSuchFile(operation, path) {
+ return new OSError(operation, Const.ENOENT, path);
+};
+
+OSError.invalidArgument = function invalidArgument(operation) {
+ return new OSError(operation, Const.EINVAL);
+};
+
+var EXPORTED_SYMBOLS = [
+ "declareFFI",
+ "libc",
+ "Error",
+ "AbstractInfo",
+ "AbstractEntry",
+ "Type",
+ "POS_START",
+ "POS_CURRENT",
+ "POS_END"
+];
+
+//////////// Boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}
diff --git a/components/osfile/modules/osfile_unix_back.jsm b/components/osfile/modules/osfile_unix_back.jsm
new file mode 100644
index 000000000..4cc444567
--- /dev/null
+++ b/components/osfile/modules/osfile_unix_back.jsm
@@ -0,0 +1,736 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 (typeof Components != "undefined") {
+ // We do not wish osfile_unix_back.jsm to be used directly as a main thread
+ // module yet. When time comes, it will be loaded by a combination of
+ // a main thread front-end/worker thread implementation that makes sure
+ // that we are not executing synchronous IO code in the main thread.
+
+ throw new Error("osfile_unix_back.jsm cannot be used from the main thread yet");
+ }
+ (function(exports) {
+ "use strict";
+ if (exports.OS && exports.OS.Unix && exports.OS.Unix.File) {
+ return; // Avoid double initialization
+ }
+
+ let SharedAll =
+ require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let SysAll =
+ require("resource://gre/modules/osfile/osfile_unix_allthreads.jsm");
+ let LOG = SharedAll.LOG.bind(SharedAll, "Unix", "back");
+ let libc = SysAll.libc;
+ let Const = SharedAll.Constants.libc;
+
+ /**
+ * Initialize the Unix module.
+ *
+ * @param {function=} declareFFI
+ */
+ // FIXME: Both |init| and |aDeclareFFI| are deprecated, we should remove them
+ let init = function init(aDeclareFFI) {
+ let declareFFI;
+ if (aDeclareFFI) {
+ declareFFI = aDeclareFFI.bind(null, libc);
+ } else {
+ declareFFI = SysAll.declareFFI;
+ }
+ let declareLazyFFI = SharedAll.declareLazyFFI;
+
+ // Initialize types that require additional OS-specific
+ // support - either finalization or matching against
+ // OS-specific constants.
+ let Type = Object.create(SysAll.Type);
+ let SysFile = exports.OS.Unix.File = { Type: Type };
+
+ /**
+ * A file descriptor.
+ */
+ Type.fd = Type.int.withName("fd");
+ Type.fd.importFromC = function importFromC(fd_int) {
+ return ctypes.CDataFinalizer(fd_int, SysFile._close);
+ };
+
+
+ /**
+ * A C integer holding -1 in case of error or a file descriptor
+ * in case of success.
+ */
+ Type.negativeone_or_fd = Type.fd.withName("negativeone_or_fd");
+ Type.negativeone_or_fd.importFromC =
+ function importFromC(fd_int) {
+ if (fd_int == -1) {
+ return -1;
+ }
+ return ctypes.CDataFinalizer(fd_int, SysFile._close);
+ };
+
+ /**
+ * A C integer holding -1 in case of error or a meaningless value
+ * in case of success.
+ */
+ Type.negativeone_or_nothing =
+ Type.int.withName("negativeone_or_nothing");
+
+ /**
+ * A C integer holding -1 in case of error or a positive integer
+ * in case of success.
+ */
+ Type.negativeone_or_ssize_t =
+ Type.ssize_t.withName("negativeone_or_ssize_t");
+
+ /**
+ * Various libc integer types
+ */
+ Type.mode_t =
+ Type.intn_t(Const.OSFILE_SIZEOF_MODE_T).withName("mode_t");
+ Type.uid_t =
+ Type.intn_t(Const.OSFILE_SIZEOF_UID_T).withName("uid_t");
+ Type.gid_t =
+ Type.intn_t(Const.OSFILE_SIZEOF_GID_T).withName("gid_t");
+
+ /**
+ * Type |time_t|
+ */
+ Type.time_t =
+ Type.intn_t(Const.OSFILE_SIZEOF_TIME_T).withName("time_t");
+
+ // Structure |dirent|
+ // Building this type is rather complicated, as its layout varies between
+ // variants of Unix. For this reason, we rely on a number of constants
+ // (computed in C from the C data structures) that give us the layout.
+ // The structure we compute looks like
+ // { int8_t[...] before_d_type; // ignored content
+ // int8_t d_type ;
+ // int8_t[...] before_d_name; // ignored content
+ // char[...] d_name;
+ // };
+ {
+ let d_name_extra_size = 0;
+ if (Const.OSFILE_SIZEOF_DIRENT_D_NAME < 8) {
+ // d_name is defined like "char d_name[1];" on some platforms
+ // (e.g. Solaris), we need to give it more size for our structure.
+ d_name_extra_size = 256;
+ }
+
+ let dirent = new SharedAll.HollowStructure("dirent",
+ Const.OSFILE_SIZEOF_DIRENT + d_name_extra_size);
+ if (Const.OSFILE_OFFSETOF_DIRENT_D_TYPE != undefined) {
+ // |dirent| doesn't have d_type on some platforms (e.g. Solaris).
+ dirent.add_field_at(Const.OSFILE_OFFSETOF_DIRENT_D_TYPE,
+ "d_type", ctypes.uint8_t);
+ }
+ dirent.add_field_at(Const.OSFILE_OFFSETOF_DIRENT_D_NAME,
+ "d_name", ctypes.ArrayType(ctypes.char,
+ Const.OSFILE_SIZEOF_DIRENT_D_NAME + d_name_extra_size));
+
+ // We now have built |dirent|.
+ Type.dirent = dirent.getType();
+ }
+ Type.null_or_dirent_ptr =
+ new SharedAll.Type("null_of_dirent",
+ Type.dirent.out_ptr.implementation);
+
+ // Structure |stat|
+ // Same technique
+ {
+ let stat = new SharedAll.HollowStructure("stat",
+ Const.OSFILE_SIZEOF_STAT);
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_MODE,
+ "st_mode", Type.mode_t.implementation);
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_UID,
+ "st_uid", Type.uid_t.implementation);
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_GID,
+ "st_gid", Type.gid_t.implementation);
+
+ // Here, things get complicated with different data structures.
+ // Some platforms have |time_t st_atime| and some platforms have
+ // |timespec st_atimespec|. However, since |timespec| starts with
+ // a |time_t|, followed by nanoseconds, we just cheat and pretend
+ // that everybody has |time_t st_atime|, possibly followed by padding
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_ATIME,
+ "st_atime", Type.time_t.implementation);
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_MTIME,
+ "st_mtime", Type.time_t.implementation);
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_CTIME,
+ "st_ctime", Type.time_t.implementation);
+
+ // To complicate further, MacOS and some BSDs have a field |birthtime|
+ if ("OSFILE_OFFSETOF_STAT_ST_BIRTHTIME" in Const) {
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_BIRTHTIME,
+ "st_birthtime", Type.time_t.implementation);
+ }
+
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_SIZE,
+ "st_size", Type.off_t.implementation);
+ Type.stat = stat.getType();
+ }
+
+ // Structure |DIR|
+ if ("OSFILE_SIZEOF_DIR" in Const) {
+ // On platforms for which we need to access the fields of DIR
+ // directly (e.g. because certain functions are implemented
+ // as macros), we need to define DIR as a hollow structure.
+ let DIR = new SharedAll.HollowStructure(
+ "DIR",
+ Const.OSFILE_SIZEOF_DIR);
+
+ DIR.add_field_at(
+ Const.OSFILE_OFFSETOF_DIR_DD_FD,
+ "dd_fd",
+ Type.fd.implementation);
+
+ Type.DIR = DIR.getType();
+ } else {
+ // On other platforms, we keep DIR as a blackbox
+ Type.DIR =
+ new SharedAll.Type("DIR",
+ ctypes.StructType("DIR"));
+ }
+
+ Type.null_or_DIR_ptr =
+ Type.DIR.out_ptr.withName("null_or_DIR*");
+ Type.null_or_DIR_ptr.importFromC = function importFromC(dir) {
+ if (dir == null || dir.isNull()) {
+ return null;
+ }
+ return ctypes.CDataFinalizer(dir, SysFile._close_dir);
+ };
+
+ // Structure |timeval|
+ {
+ let timeval = new SharedAll.HollowStructure(
+ "timeval",
+ Const.OSFILE_SIZEOF_TIMEVAL);
+ timeval.add_field_at(
+ Const.OSFILE_OFFSETOF_TIMEVAL_TV_SEC,
+ "tv_sec",
+ Type.long.implementation);
+ timeval.add_field_at(
+ Const.OSFILE_OFFSETOF_TIMEVAL_TV_USEC,
+ "tv_usec",
+ Type.long.implementation);
+ Type.timeval = timeval.getType();
+ Type.timevals = new SharedAll.Type("two timevals",
+ ctypes.ArrayType(Type.timeval.implementation, 2));
+ }
+
+ // Types fsblkcnt_t and fsfilcnt_t, used by structure |statvfs|
+ Type.fsblkcnt_t =
+ Type.uintn_t(Const.OSFILE_SIZEOF_FSBLKCNT_T).withName("fsblkcnt_t");
+
+ // Structure |statvfs|
+ // Use an hollow structure
+ {
+ let statvfs = new SharedAll.HollowStructure("statvfs",
+ Const.OSFILE_SIZEOF_STATVFS);
+
+ statvfs.add_field_at(Const.OSFILE_OFFSETOF_STATVFS_F_BSIZE,
+ "f_bsize", Type.unsigned_long.implementation);
+ statvfs.add_field_at(Const.OSFILE_OFFSETOF_STATVFS_F_BAVAIL,
+ "f_bavail", Type.fsblkcnt_t.implementation);
+
+ Type.statvfs = statvfs.getType();
+ }
+
+ // Declare libc functions as functions of |OS.Unix.File|
+
+ // Finalizer-related functions
+ libc.declareLazy(SysFile, "_close",
+ "close", ctypes.default_abi,
+ /*return */ctypes.int,
+ /*fd*/ ctypes.int);
+
+ SysFile.close = function close(fd) {
+ // Detach the finalizer and call |_close|.
+ return fd.dispose();
+ };
+
+ libc.declareLazy(SysFile, "_close_dir",
+ "closedir", ctypes.default_abi,
+ /*return */ctypes.int,
+ /*dirp*/ Type.DIR.in_ptr.implementation);
+
+ SysFile.closedir = function closedir(fd) {
+ // Detach the finalizer and call |_close_dir|.
+ return fd.dispose();
+ };
+
+ {
+ // Symbol free() is special.
+ // We override the definition of free() on several platforms.
+ let default_lib = new SharedAll.Library("default_lib",
+ "a.out");
+
+ // On platforms for which we override free(), nspr defines
+ // a special library name "a.out" that will resolve to the
+ // correct implementation free().
+ // If it turns out we don't have an a.out library or a.out
+ // doesn't contain free, use the ordinary libc free.
+
+ default_lib.declareLazyWithFallback(libc, SysFile, "free",
+ "free", ctypes.default_abi,
+ /*return*/ ctypes.void_t,
+ /*ptr*/ ctypes.voidptr_t);
+ }
+
+
+ // Other functions
+ libc.declareLazyFFI(SysFile, "access",
+ "access", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*mode*/ Type.int);
+
+ libc.declareLazyFFI(SysFile, "chdir",
+ "chdir", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "chmod",
+ "chmod", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*mode*/ Type.mode_t);
+
+ libc.declareLazyFFI(SysFile, "chown",
+ "chown", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*uid*/ Type.uid_t,
+ /*gid*/ Type.gid_t);
+
+ libc.declareLazyFFI(SysFile, "copyfile",
+ "copyfile", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*source*/ Type.path,
+ /*dest*/ Type.path,
+ /*state*/ Type.void_t.in_ptr, // Ignored atm
+ /*flags*/ Type.uint32_t);
+
+ libc.declareLazyFFI(SysFile, "dup",
+ "dup", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_fd,
+ /*fd*/ Type.fd);
+
+ if ("OSFILE_SIZEOF_DIR" in Const) {
+ // On platforms for which |dirfd| is a macro
+ SysFile.dirfd =
+ function dirfd(DIRp) {
+ return Type.DIR.in_ptr.implementation(DIRp).contents.dd_fd;
+ };
+ } else {
+ // On platforms for which |dirfd| is a function
+ libc.declareLazyFFI(SysFile, "dirfd",
+ "dirfd", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_fd,
+ /*dir*/ Type.DIR.in_ptr);
+ }
+
+ libc.declareLazyFFI(SysFile, "chdir",
+ "chdir", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "fchdir",
+ "fchdir", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd);
+
+ libc.declareLazyFFI(SysFile, "fchmod",
+ "fchmod", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*mode*/ Type.mode_t);
+
+ libc.declareLazyFFI(SysFile, "fchown",
+ "fchown", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*uid_t*/ Type.uid_t,
+ /*gid_t*/ Type.gid_t);
+
+ libc.declareLazyFFI(SysFile, "fsync",
+ "fsync", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd);
+
+ libc.declareLazyFFI(SysFile, "getcwd",
+ "getcwd", ctypes.default_abi,
+ /*return*/ Type.out_path,
+ /*buf*/ Type.out_path,
+ /*size*/ Type.size_t);
+
+ libc.declareLazyFFI(SysFile, "getwd",
+ "getwd", ctypes.default_abi,
+ /*return*/ Type.out_path,
+ /*buf*/ Type.out_path);
+
+ // Two variants of |getwd| which allocate the memory
+ // dynamically.
+
+ // Linux version
+ libc.declareLazyFFI(SysFile, "get_current_dir_name",
+ "get_current_dir_name", ctypes.default_abi,
+ /*return*/ Type.out_path.releaseWithLazy(() =>
+ SysFile.free
+ ));
+
+ // MacOS/BSD version (will return NULL on Linux)
+ libc.declareLazyFFI(SysFile, "getwd_auto",
+ "getwd", ctypes.default_abi,
+ /*return*/ Type.out_path.releaseWithLazy(() =>
+ SysFile.free
+ ),
+ /*buf*/ Type.void_t.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "fdatasync",
+ "fdatasync", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd); // Note: MacOS/BSD-specific
+
+ libc.declareLazyFFI(SysFile, "ftruncate",
+ "ftruncate", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*length*/ Type.off_t);
+
+
+ libc.declareLazyFFI(SysFile, "lchown",
+ "lchown", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*uid_t*/ Type.uid_t,
+ /*gid_t*/ Type.gid_t);
+
+ libc.declareLazyFFI(SysFile, "link",
+ "link", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*source*/ Type.path,
+ /*dest*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "lseek",
+ "lseek", ctypes.default_abi,
+ /*return*/ Type.off_t,
+ /*fd*/ Type.fd,
+ /*offset*/ Type.off_t,
+ /*whence*/ Type.int);
+
+ libc.declareLazyFFI(SysFile, "mkdir",
+ "mkdir", ctypes.default_abi,
+ /*return*/ Type.int,
+ /*path*/ Type.path,
+ /*mode*/ Type.int);
+
+ libc.declareLazyFFI(SysFile, "mkstemp",
+ "mkstemp", ctypes.default_abi,
+ /*return*/ Type.fd,
+ /*template*/ Type.out_path);
+
+ libc.declareLazyFFI(SysFile, "open",
+ "open", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_fd,
+ /*path*/ Type.path,
+ /*oflags*/ Type.int,
+ /*mode*/ Type.int);
+
+ if (OS.Constants.Sys.Name == "NetBSD") {
+ libc.declareLazyFFI(SysFile, "opendir",
+ "__opendir30", ctypes.default_abi,
+ /*return*/ Type.null_or_DIR_ptr,
+ /*path*/ Type.path);
+ } else {
+ libc.declareLazyFFI(SysFile, "opendir",
+ "opendir", ctypes.default_abi,
+ /*return*/ Type.null_or_DIR_ptr,
+ /*path*/ Type.path);
+ }
+
+ libc.declareLazyFFI(SysFile, "pread",
+ "pread", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_ssize_t,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.void_t.out_ptr,
+ /*nbytes*/ Type.size_t,
+ /*offset*/ Type.off_t);
+
+ libc.declareLazyFFI(SysFile, "pwrite",
+ "pwrite", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_ssize_t,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.void_t.in_ptr,
+ /*nbytes*/ Type.size_t,
+ /*offset*/ Type.off_t);
+
+ libc.declareLazyFFI(SysFile, "read",
+ "read", ctypes.default_abi,
+ /*return*/Type.negativeone_or_ssize_t,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.void_t.out_ptr,
+ /*nbytes*/Type.size_t);
+
+ libc.declareLazyFFI(SysFile, "posix_fadvise",
+ "posix_fadvise", ctypes.default_abi,
+ /*return*/ Type.int,
+ /*fd*/ Type.fd,
+ /*offset*/ Type.off_t,
+ /*len*/ Type.off_t,
+ /*advise*/ Type.int);
+
+ if (Const._DARWIN_FEATURE_64_BIT_INODE) {
+ // Special case for MacOS X 10.5+
+ // Symbol name "readdir" still exists but is used for a
+ // deprecated function that does not match the
+ // constants of |Const|.
+ libc.declareLazyFFI(SysFile, "readdir",
+ "readdir$INODE64", ctypes.default_abi,
+ /*return*/ Type.null_or_dirent_ptr,
+ /*dir*/ Type.DIR.in_ptr); // For MacOS X
+ } else if (OS.Constants.Sys.Name == "NetBSD") {
+ libc.declareLazyFFI(SysFile, "readdir",
+ "__readdir30", ctypes.default_abi,
+ /*return*/Type.null_or_dirent_ptr,
+ /*dir*/ Type.DIR.in_ptr); // Other Unices
+ } else {
+ libc.declareLazyFFI(SysFile, "readdir",
+ "readdir", ctypes.default_abi,
+ /*return*/Type.null_or_dirent_ptr,
+ /*dir*/ Type.DIR.in_ptr); // Other Unices
+ }
+
+ libc.declareLazyFFI(SysFile, "rename",
+ "rename", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*old*/ Type.path,
+ /*new*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "rmdir",
+ "rmdir", ctypes.default_abi,
+ /*return*/ Type.int,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "splice",
+ "splice", ctypes.default_abi,
+ /*return*/ Type.long,
+ /*fd_in*/ Type.fd,
+ /*off_in*/ Type.off_t.in_ptr,
+ /*fd_out*/ Type.fd,
+ /*off_out*/Type.off_t.in_ptr,
+ /*len*/ Type.size_t,
+ /*flags*/ Type.unsigned_int); // Linux-specific
+
+ libc.declareLazyFFI(SysFile, "statfs",
+ "statfs", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.statvfs.out_ptr); // Other platforms
+
+ libc.declareLazyFFI(SysFile, "statvfs",
+ "statvfs", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.statvfs.out_ptr); // Other platforms
+
+ libc.declareLazyFFI(SysFile, "symlink",
+ "symlink", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*source*/ Type.path,
+ /*dest*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "truncate",
+ "truncate", ctypes.default_abi,
+ /*return*/Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*length*/ Type.off_t);
+
+ libc.declareLazyFFI(SysFile, "unlink",
+ "unlink", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "write",
+ "write", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_ssize_t,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.void_t.in_ptr,
+ /*nbytes*/ Type.size_t);
+
+ // Weird cases that require special treatment
+
+ // OSes use a variety of hacks to differentiate between
+ // 32-bits and 64-bits versions of |stat|, |lstat|, |fstat|.
+ if (Const._DARWIN_FEATURE_64_BIT_INODE) {
+ // MacOS X 64-bits
+ libc.declareLazyFFI(SysFile, "stat",
+ "stat$INODE64", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "lstat",
+ "lstat$INODE64", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "fstat",
+ "fstat$INODE64", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.fd,
+ /*buf*/ Type.stat.out_ptr
+ );
+ } else if (Const._STAT_VER != undefined) {
+ const ver = Const._STAT_VER;
+ let xstat_name, lxstat_name, fxstat_name;
+ if (OS.Constants.Sys.Name == "SunOS") {
+ // Solaris
+ xstat_name = "_xstat";
+ lxstat_name = "_lxstat";
+ fxstat_name = "_fxstat";
+ } else {
+
+ // Linux, all widths
+ xstat_name = "__xstat";
+ lxstat_name = "__lxstat";
+ fxstat_name = "__fxstat";
+ }
+
+ let Stat = {};
+ libc.declareLazyFFI(Stat, "xstat",
+ xstat_name, ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*_stat_ver*/ Type.int,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr);
+ libc.declareLazyFFI(Stat, "lxstat",
+ lxstat_name, ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*_stat_ver*/ Type.int,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr);
+ libc.declareLazyFFI(Stat, "fxstat",
+ fxstat_name, ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*_stat_ver*/ Type.int,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.stat.out_ptr);
+
+
+ SysFile.stat = function stat(path, buf) {
+ return Stat.xstat(ver, path, buf);
+ };
+
+ SysFile.lstat = function lstat(path, buf) {
+ return Stat.lxstat(ver, path, buf);
+ };
+
+ SysFile.fstat = function fstat(fd, buf) {
+ return Stat.fxstat(ver, fd, buf);
+ };
+ } else if (OS.Constants.Sys.Name == "NetBSD") {
+ // NetBSD 5.0 and newer
+ libc.declareLazyFFI(SysFile, "stat",
+ "__stat50", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "lstat",
+ "__lstat50", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "fstat",
+ "__fstat50", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.stat.out_ptr
+ );
+ } else {
+ // Mac OS X 32-bits, other Unix
+ libc.declareLazyFFI(SysFile, "stat",
+ "stat", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "lstat",
+ "lstat", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "fstat",
+ "fstat", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.stat.out_ptr
+ );
+ }
+
+ // We cannot make a C array of CDataFinalizer, so
+ // pipe cannot be directly defined as a C function.
+
+ let Pipe = {};
+ libc.declareLazyFFI(Pipe, "_pipe",
+ "pipe", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fds*/ new SharedAll.Type("two file descriptors",
+ ctypes.ArrayType(ctypes.int, 2)));
+
+ // A shared per-thread buffer used to communicate with |pipe|
+ let _pipebuf = new (ctypes.ArrayType(ctypes.int, 2))();
+
+ SysFile.pipe = function pipe(array) {
+ let result = Pipe._pipe(_pipebuf);
+ if (result == -1) {
+ return result;
+ }
+ array[0] = ctypes.CDataFinalizer(_pipebuf[0], SysFile._close);
+ array[1] = ctypes.CDataFinalizer(_pipebuf[1], SysFile._close);
+ return result;
+ };
+
+ if (OS.Constants.Sys.Name == "NetBSD") {
+ libc.declareLazyFFI(SysFile, "utimes",
+ "__utimes50", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*timeval[2]*/ Type.timevals.out_ptr
+ );
+ } else {
+ libc.declareLazyFFI(SysFile, "utimes",
+ "utimes", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*timeval[2]*/ Type.timevals.out_ptr
+ );
+ }
+ if (OS.Constants.Sys.Name == "NetBSD") {
+ libc.declareLazyFFI(SysFile, "futimes",
+ "__futimes50", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*timeval[2]*/ Type.timevals.out_ptr
+ );
+ } else {
+ libc.declareLazyFFI(SysFile, "futimes",
+ "futimes", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*timeval[2]*/ Type.timevals.out_ptr
+ );
+ }
+ };
+
+ exports.OS.Unix = {
+ File: {
+ _init: init
+ }
+ };
+ })(this);
+}
diff --git a/components/osfile/modules/osfile_unix_front.jsm b/components/osfile/modules/osfile_unix_front.jsm
new file mode 100644
index 000000000..4e41036df
--- /dev/null
+++ b/components/osfile/modules/osfile_unix_front.jsm
@@ -0,0 +1,1188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Synchronous front-end for the JavaScript OS.File library.
+ * Unix implementation.
+ *
+ * This front-end is meant to be imported by a worker thread.
+ */
+
+{
+ if (typeof Components != "undefined") {
+ // We do not wish osfile_unix_front.jsm to be used directly as a main thread
+ // module yet.
+
+ throw new Error("osfile_unix_front.jsm cannot be used from the main thread yet");
+ }
+ (function(exports) {
+ "use strict";
+
+ // exports.OS.Unix is created by osfile_unix_back.jsm
+ if (exports.OS && exports.OS.File) {
+ return; // Avoid double-initialization
+ }
+
+ let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let Path = require("resource://gre/modules/osfile/ospath.jsm");
+ let SysAll = require("resource://gre/modules/osfile/osfile_unix_allthreads.jsm");
+ exports.OS.Unix.File._init();
+ let LOG = SharedAll.LOG.bind(SharedAll, "Unix front-end");
+ let Const = SharedAll.Constants.libc;
+ let UnixFile = exports.OS.Unix.File;
+ let Type = UnixFile.Type;
+
+ /**
+ * Representation of a file.
+ *
+ * You generally do not need to call this constructor yourself. Rather,
+ * to open a file, use function |OS.File.open|.
+ *
+ * @param fd A OS-specific file descriptor.
+ * @param {string} path File path of the file handle, used for error-reporting.
+ * @constructor
+ */
+ let File = function File(fd, path) {
+ exports.OS.Shared.AbstractFile.call(this, fd, path);
+ this._closeResult = null;
+ };
+ File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
+
+ /**
+ * Close the file.
+ *
+ * This method has no effect if the file is already closed. However,
+ * if the first call to |close| has thrown an error, further calls
+ * will throw the same error.
+ *
+ * @throws File.Error If closing the file revealed an error that could
+ * not be reported earlier.
+ */
+ File.prototype.close = function close() {
+ if (this._fd) {
+ let fd = this._fd;
+ this._fd = null;
+ // Call |close(fd)|, detach finalizer if any
+ // (|fd| may not be a CDataFinalizer if it has been
+ // instantiated from a controller thread).
+ let result = UnixFile._close(fd);
+ if (typeof fd == "object" && "forget" in fd) {
+ fd.forget();
+ }
+ if (result == -1) {
+ this._closeResult = new File.Error("close", ctypes.errno, this._path);
+ }
+ }
+ if (this._closeResult) {
+ throw this._closeResult;
+ }
+ return;
+ };
+
+ /**
+ * Read some bytes from a file.
+ *
+ * @param {C pointer} buffer A buffer for holding the data
+ * once it is read.
+ * @param {number} nbytes The number of bytes to read. It must not
+ * exceed the size of |buffer| in bytes but it may exceed the number
+ * of bytes unread in the file.
+ * @param {*=} options Additional options for reading. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively read. If zero,
+ * the end of the file has been reached.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._read = function _read(buffer, nbytes, options = {}) {
+ // Populate the page cache with data from a file so the subsequent reads
+ // from that file will not block on disk I/O.
+ if (typeof(UnixFile.posix_fadvise) === 'function' &&
+ (options.sequential || !("sequential" in options))) {
+ UnixFile.posix_fadvise(this.fd, 0, nbytes,
+ OS.Constants.libc.POSIX_FADV_SEQUENTIAL);
+ }
+ return throw_on_negative("read",
+ UnixFile.read(this.fd, buffer, nbytes),
+ this._path
+ );
+ };
+
+ /**
+ * Write some bytes to a file.
+ *
+ * @param {Typed array} buffer A buffer holding the data that must be
+ * written.
+ * @param {number} nbytes The number of bytes to write. It must not
+ * exceed the size of |buffer| in bytes.
+ * @param {*=} options Additional options for writing. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively written.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._write = function _write(buffer, nbytes, options = {}) {
+ return throw_on_negative("write",
+ UnixFile.write(this.fd, buffer, nbytes),
+ this._path
+ );
+ };
+
+ /**
+ * Return the current position in the file.
+ */
+ File.prototype.getPosition = function getPosition(pos) {
+ return this.setPosition(0, File.POS_CURRENT);
+ };
+
+ /**
+ * Change the current position in the file.
+ *
+ * @param {number} pos The new position. Whether this position
+ * is considered from the current position, from the start of
+ * the file or from the end of the file is determined by
+ * argument |whence|. Note that |pos| may exceed the length of
+ * the file.
+ * @param {number=} whence The reference position. If omitted
+ * or |OS.File.POS_START|, |pos| is relative to the start of the
+ * file. If |OS.File.POS_CURRENT|, |pos| is relative to the
+ * current position in the file. If |OS.File.POS_END|, |pos| is
+ * relative to the end of the file.
+ *
+ * @return The new position in the file.
+ */
+ File.prototype.setPosition = function setPosition(pos, whence) {
+ if (whence === undefined) {
+ whence = Const.SEEK_SET;
+ }
+ return throw_on_negative("setPosition",
+ UnixFile.lseek(this.fd, pos, whence),
+ this._path
+ );
+ };
+
+ /**
+ * Fetch the information on the file.
+ *
+ * @return File.Info The information on |this| file.
+ */
+ File.prototype.stat = function stat() {
+ throw_on_negative("stat", UnixFile.fstat(this.fd, gStatDataPtr),
+ this._path);
+ return new File.Info(gStatData, this._path);
+ };
+
+ /**
+ * Set the file's access permissions.
+ *
+ * This operation is likely to fail if applied to a file that was
+ * not created by the currently running program (more precisely,
+ * if it was created by a program running under a different OS-level
+ * user account). It may also fail, or silently do nothing, if the
+ * filesystem containing the file does not support access permissions.
+ *
+ * @param {*=} options Object specifying the requested permissions:
+ *
+ * - {number} unixMode The POSIX file mode to set on the file. If omitted,
+ * the POSIX file mode is reset to the default used by |OS.file.open|. If
+ * specified, the permissions will respect the process umask as if they
+ * had been specified as arguments of |OS.File.open|, unless the
+ * |unixHonorUmask| parameter tells otherwise.
+ * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
+ * modified by the process umask, as |OS.File.open| would have done. If
+ * false, the exact value of |unixMode| will be applied.
+ */
+ File.prototype.setPermissions = function setPermissions(options = {}) {
+ throw_on_negative("setPermissions",
+ UnixFile.fchmod(this.fd, unixMode(options)),
+ this._path);
+ };
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid parameters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype.setDates = function(accessDate, modificationDate) {
+ let {value, ptr} = datesToTimevals(accessDate, modificationDate);
+ throw_on_negative("setDates",
+ UnixFile.futimes(this.fd, ptr),
+ this._path);
+ };
+
+ /**
+ * Flushes the file's buffers and causes all buffered data
+ * to be written.
+ * Disk flushes are very expensive and therefore should be used carefully,
+ * sparingly and only in scenarios where it is vital that data survives
+ * system crashes. Even though the function will be executed off the
+ * main-thread, it might still affect the overall performance of any
+ * running application.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype.flush = function flush() {
+ throw_on_negative("flush", UnixFile.fsync(this.fd), this._path);
+ };
+
+ // The default unix mode for opening (0600)
+ const DEFAULT_UNIX_MODE = 384;
+
+ /**
+ * Open a file
+ *
+ * @param {string} path The path to the file.
+ * @param {*=} mode The opening mode for the file, as
+ * an object that may contain the following fields:
+ *
+ * - {bool} truncate If |true|, the file will be opened
+ * for writing. If the file does not exist, it will be
+ * created. If the file exists, its contents will be
+ * erased. Cannot be specified with |create|.
+ * - {bool} create If |true|, the file will be opened
+ * for writing. If the file exists, this function fails.
+ * If the file does not exist, it will be created. Cannot
+ * be specified with |truncate| or |existing|.
+ * - {bool} existing. If the file does not exist, this function
+ * fails. Cannot be specified with |create|.
+ * - {bool} read If |true|, the file will be opened for
+ * reading. The file may also be opened for writing, depending
+ * on the other fields of |mode|.
+ * - {bool} write If |true|, the file will be opened for
+ * writing. The file may also be opened for reading, depending
+ * on the other fields of |mode|.
+ * - {bool} append If |true|, the file will be opened for appending,
+ * meaning the equivalent of |.setPosition(0, POS_END)| is executed
+ * before each write. The default is |true|, i.e. opening a file for
+ * appending. Specify |append: false| to open the file in regular mode.
+ *
+ * If neither |truncate|, |create| or |write| is specified, the file
+ * is opened for reading.
+ *
+ * Note that |false|, |null| or |undefined| flags are simply ignored.
+ *
+ * @param {*=} options Additional options for file opening. This
+ * implementation interprets the following fields:
+ *
+ * - {number} unixFlags If specified, file opening flags, as
+ * per libc function |open|. Replaces |mode|.
+ * - {number} unixMode If specified, a file creation mode,
+ * as per libc function |open|. If unspecified, files are
+ * created with a default mode of 0600 (file is private to the
+ * user, the user can read and write).
+ *
+ * @return {File} A file object.
+ * @throws {OS.File.Error} If the file could not be opened.
+ */
+ File.open = function Unix_open(path, mode, options = {}) {
+ // We don't need to filter for the umask because "open" does this for us.
+ let omode = options.unixMode !== undefined ?
+ options.unixMode : DEFAULT_UNIX_MODE;
+ let flags;
+ if (options.unixFlags !== undefined) {
+ flags = options.unixFlags;
+ } else {
+ mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
+ // Handle read/write
+ if (!mode.write) {
+ flags = Const.O_RDONLY;
+ } else if (mode.read) {
+ flags = Const.O_RDWR;
+ } else {
+ flags = Const.O_WRONLY;
+ }
+ // Finally, handle create/existing/trunc
+ if (mode.trunc) {
+ if (mode.existing) {
+ flags |= Const.O_TRUNC;
+ } else {
+ flags |= Const.O_CREAT | Const.O_TRUNC;
+ }
+ } else if (mode.create) {
+ flags |= Const.O_CREAT | Const.O_EXCL;
+ } else if (mode.read && !mode.write) {
+ // flags are sufficient
+ } else if (!mode.existing) {
+ flags |= Const.O_CREAT;
+ }
+ if (mode.append) {
+ flags |= Const.O_APPEND;
+ }
+ }
+ return error_or_file(UnixFile.open(path, flags, omode), path);
+ };
+
+ /**
+ * Checks if a file exists
+ *
+ * @param {string} path The path to the file.
+ *
+ * @return {bool} true if the file exists, false otherwise.
+ */
+ File.exists = function Unix_exists(path) {
+ if (UnixFile.access(path, Const.F_OK) == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ };
+
+ /**
+ * Remove an existing file.
+ *
+ * @param {string} path The name of the file.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the file does
+ * not exist. |true| by default.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.remove = function remove(path, options = {}) {
+ let result = UnixFile.unlink(path);
+ if (result == -1) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.errno == Const.ENOENT) {
+ return;
+ }
+ throw new File.Error("remove", ctypes.errno, path);
+ }
+ };
+
+ /**
+ * Remove an empty directory.
+ *
+ * @param {string} path The name of the directory to remove.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory
+ * does not exist. |true| by default
+ */
+ File.removeEmptyDir = function removeEmptyDir(path, options = {}) {
+ let result = UnixFile.rmdir(path);
+ if (result == -1) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.errno == Const.ENOENT) {
+ return;
+ }
+ throw new File.Error("removeEmptyDir", ctypes.errno, path);
+ }
+ };
+
+ /**
+ * Gets the number of bytes available on disk to the current user.
+ *
+ * @param {string} sourcePath Platform-specific path to a directory on
+ * the disk to query for free available bytes.
+ *
+ * @return {number} The number of bytes available for the current user.
+ * @throws {OS.File.Error} In case of any error.
+ */
+ File.getAvailableFreeSpace = function Unix_getAvailableFreeSpace(sourcePath) {
+ let fileSystemInfo = new Type.statvfs.implementation();
+ let fileSystemInfoPtr = fileSystemInfo.address();
+
+ throw_on_negative("statvfs", (UnixFile.statvfs || UnixFile.statfs)(sourcePath, fileSystemInfoPtr));
+
+ let bytes = new Type.uint64_t.implementation(
+ fileSystemInfo.f_bsize * fileSystemInfo.f_bavail);
+
+ return bytes.value;
+ };
+
+ /**
+ * Default mode for opening directories.
+ */
+ const DEFAULT_UNIX_MODE_DIR = Const.S_IRWXU;
+
+ /**
+ * Create a directory.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options. This
+ * implementation interprets the following fields:
+ *
+ * - {number} unixMode If specified, a file creation mode,
+ * as per libc function |mkdir|. If unspecified, dirs are
+ * created with a default mode of 0700 (dir is private to
+ * the user, the user can read, write and execute).
+ * - {bool} ignoreExisting If |false|, throw error if the directory
+ * already exists. |true| by default
+ * - {string} from If specified, the call to |makeDir| creates all the
+ * ancestors of |path| that are descendants of |from|. Note that |from|
+ * and its existing descendants must be user-writeable and that |path|
+ * must be a descendant of |from|.
+ * Example:
+ * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+ * creates directories profileDir/foo, profileDir/foo/bar
+ */
+ File._makeDir = function makeDir(path, options = {}) {
+ let omode = options.unixMode !== undefined ? options.unixMode : DEFAULT_UNIX_MODE_DIR;
+ let result = UnixFile.mkdir(path, omode);
+ if (result == -1) {
+ if ((!("ignoreExisting" in options) || options.ignoreExisting) &&
+ (ctypes.errno == Const.EEXIST || ctypes.errno == Const.EISDIR)) {
+ return;
+ }
+ throw new File.Error("makeDir", ctypes.errno, path);
+ }
+ };
+
+ /**
+ * Copy a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be copied.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be copied with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.copy = null;
+
+ /**
+ * Move a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be moved.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ * @option {bool} noCopy - If set, this function will fail if the
+ * operation is more sophisticated than a simple renaming, i.e. if
+ * |sourcePath| and |destPath| are not situated on the same device.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be moved with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.move = null;
+
+ if (UnixFile.copyfile) {
+ // This implementation uses |copyfile(3)|, from the BSD library.
+ // Adding copying of hierarchies and/or attributes is just a flag
+ // away.
+ File.copy = function copyfile(sourcePath, destPath, options = {}) {
+ let flags = Const.COPYFILE_DATA;
+ if (options.noOverwrite) {
+ flags |= Const.COPYFILE_EXCL;
+ }
+ throw_on_negative("copy",
+ UnixFile.copyfile(sourcePath, destPath, null, flags),
+ sourcePath
+ );
+ };
+ } else {
+ // If the OS does not implement file copying for us, we need to
+ // implement it ourselves. For this purpose, we need to define
+ // a pumping function.
+
+ /**
+ * Copy bytes from one file to another one.
+ *
+ * @param {File} source The file containing the data to be copied. It
+ * should be opened for reading.
+ * @param {File} dest The file to which the data should be written. It
+ * should be opened for writing.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {number} nbytes The maximal number of bytes to
+ * copy. If unspecified, copy everything from the current
+ * position.
+ * @option {number} bufSize A hint regarding the size of the
+ * buffer to use for copying. The implementation may decide to
+ * ignore this hint.
+ * @option {bool} unixUserland Will force the copy operation to be
+ * caried out in user land, instead of using optimized syscalls such
+ * as splice(2).
+ *
+ * @throws {OS.File.Error} In case of error.
+ */
+ let pump;
+
+ // A buffer used by |pump_userland|
+ let pump_buffer = null;
+
+ // An implementation of |pump| using |read|/|write|
+ let pump_userland = function pump_userland(source, dest, options = {}) {
+ let bufSize = options.bufSize > 0 ? options.bufSize : 4096;
+ let nbytes = options.nbytes > 0 ? options.nbytes : Infinity;
+ if (!pump_buffer || pump_buffer.length < bufSize) {
+ pump_buffer = new (ctypes.ArrayType(ctypes.char))(bufSize);
+ }
+ let read = source._read.bind(source);
+ let write = dest._write.bind(dest);
+ // Perform actual copy
+ let total_read = 0;
+ while (true) {
+ let chunk_size = Math.min(nbytes, bufSize);
+ let bytes_just_read = read(pump_buffer, bufSize);
+ if (bytes_just_read == 0) {
+ return total_read;
+ }
+ total_read += bytes_just_read;
+ let bytes_written = 0;
+ do {
+ bytes_written += write(
+ pump_buffer.addressOfElement(bytes_written),
+ bytes_just_read - bytes_written
+ );
+ } while (bytes_written < bytes_just_read);
+ nbytes -= bytes_written;
+ if (nbytes <= 0) {
+ return total_read;
+ }
+ }
+ };
+
+ // Fortunately, under Linux, that pumping function can be optimized.
+ if (UnixFile.splice) {
+ const BUFSIZE = 1 << 17;
+
+ // An implementation of |pump| using |splice| (for Linux)
+ pump = function pump_splice(source, dest, options = {}) {
+ let nbytes = options.nbytes > 0 ? options.nbytes : Infinity;
+ let pipe = [];
+ throw_on_negative("pump", UnixFile.pipe(pipe));
+ let pipe_read = pipe[0];
+ let pipe_write = pipe[1];
+ let source_fd = source.fd;
+ let dest_fd = dest.fd;
+ let total_read = 0;
+ let total_written = 0;
+ try {
+ while (true) {
+ let chunk_size = Math.min(nbytes, BUFSIZE);
+ let bytes_read = throw_on_negative("pump",
+ UnixFile.splice(source_fd, null,
+ pipe_write, null, chunk_size, 0)
+ );
+ if (!bytes_read) {
+ break;
+ }
+ total_read += bytes_read;
+ let bytes_written = throw_on_negative(
+ "pump",
+ UnixFile.splice(pipe_read, null,
+ dest_fd, null, bytes_read,
+ (bytes_read == chunk_size)?Const.SPLICE_F_MORE:0
+ ));
+ if (!bytes_written) {
+ // This should never happen
+ throw new Error("Internal error: pipe disconnected");
+ }
+ total_written += bytes_written;
+ nbytes -= bytes_read;
+ if (!nbytes) {
+ break;
+ }
+ }
+ return total_written;
+ } catch (x) {
+ if (x.unixErrno == Const.EINVAL) {
+ // We *might* be on a file system that does not support splice.
+ // Try again with a fallback pump.
+ if (total_read) {
+ source.setPosition(-total_read, File.POS_CURRENT);
+ }
+ if (total_written) {
+ dest.setPosition(-total_written, File.POS_CURRENT);
+ }
+ return pump_userland(source, dest, options);
+ }
+ throw x;
+ } finally {
+ pipe_read.dispose();
+ pipe_write.dispose();
+ }
+ };
+ } else {
+ // Fallback implementation of pump for other Unix platforms.
+ pump = pump_userland;
+ }
+
+ // Implement |copy| using |pump|.
+ // This implementation would require some work before being able to
+ // copy directories
+ File.copy = function copy(sourcePath, destPath, options = {}) {
+ let source, dest;
+ let result;
+ try {
+ source = File.open(sourcePath);
+ // Need to open the output file with |append:false|, or else |splice|
+ // won't work.
+ if (options.noOverwrite) {
+ dest = File.open(destPath, {create:true, append:false});
+ } else {
+ dest = File.open(destPath, {trunc:true, append:false});
+ }
+ if (options.unixUserland) {
+ result = pump_userland(source, dest, options);
+ } else {
+ result = pump(source, dest, options);
+ }
+ } catch (x) {
+ if (dest) {
+ dest.close();
+ }
+ if (source) {
+ source.close();
+ }
+ throw x;
+ }
+ };
+ } // End of definition of copy
+
+ // Implement |move| using |rename| (wherever possible) or |copy|
+ // (if files are on distinct devices).
+ File.move = function move(sourcePath, destPath, options = {}) {
+ // An implementation using |rename| whenever possible or
+ // |File.pump| when required, for other Unices.
+ // It can move directories on one file system, not
+ // across file systems
+
+ // If necessary, fail if the destination file exists
+ if (options.noOverwrite) {
+ let fd = UnixFile.open(destPath, Const.O_RDONLY, 0);
+ if (fd != -1) {
+ fd.dispose();
+ // The file exists and we have access
+ throw new File.Error("move", Const.EEXIST, sourcePath);
+ } else if (ctypes.errno == Const.EACCESS) {
+ // The file exists and we don't have access
+ throw new File.Error("move", Const.EEXIST, sourcePath);
+ }
+ }
+
+ // If we can, rename the file
+ let result = UnixFile.rename(sourcePath, destPath);
+ if (result != -1)
+ return;
+
+ // If the error is not EXDEV ("not on the same device"),
+ // or if the error is EXDEV and we have passed an option
+ // that prevents us from crossing devices, throw the
+ // error.
+ if (ctypes.errno != Const.EXDEV || options.noCopy) {
+ throw new File.Error("move", ctypes.errno, sourcePath);
+ }
+
+ // Otherwise, copy and remove.
+ File.copy(sourcePath, destPath, options);
+ // FIXME: Clean-up in case of copy error?
+ File.remove(sourcePath);
+ };
+
+ File.unixSymLink = function unixSymLink(sourcePath, destPath) {
+ throw_on_negative("symlink", UnixFile.symlink(sourcePath, destPath),
+ sourcePath);
+ };
+
+ /**
+ * Iterate on one directory.
+ *
+ * This iterator will not enter subdirectories.
+ *
+ * @param {string} path The directory upon which to iterate.
+ * @param {*=} options Ignored in this implementation.
+ *
+ * @throws {File.Error} If |path| does not represent a directory or
+ * if the directory cannot be iterated.
+ * @constructor
+ */
+ File.DirectoryIterator = function DirectoryIterator(path, options) {
+ exports.OS.Shared.AbstractFile.AbstractIterator.call(this);
+ this._path = path;
+ this._dir = UnixFile.opendir(this._path);
+ if (this._dir == null) {
+ let error = ctypes.errno;
+ if (error != Const.ENOENT) {
+ throw new File.Error("DirectoryIterator", error, path);
+ }
+ this._exists = false;
+ this._closed = true;
+ } else {
+ this._exists = true;
+ this._closed = false;
+ }
+ };
+ File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype);
+
+ /**
+ * Return the next entry in the directory, if any such entry is
+ * available.
+ *
+ * Skip special directories "." and "..".
+ *
+ * @return {File.Entry} The next entry in the directory.
+ * @throws {StopIteration} Once all files in the directory have been
+ * encountered.
+ */
+ File.DirectoryIterator.prototype.next = function next() {
+ if (!this._exists) {
+ throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path);
+ }
+ if (this._closed) {
+ throw StopIteration;
+ }
+ for (let entry = UnixFile.readdir(this._dir);
+ entry != null && !entry.isNull();
+ entry = UnixFile.readdir(this._dir)) {
+ let contents = entry.contents;
+ let name = contents.d_name.readString();
+ if (name == "." || name == "..") {
+ continue;
+ }
+
+ let isDir, isSymLink;
+ if (!("d_type" in contents)) {
+ // |dirent| doesn't have d_type on some platforms (e.g. Solaris).
+ let path = Path.join(this._path, name);
+ throw_on_negative("lstat", UnixFile.lstat(path, gStatDataPtr), this._path);
+ isDir = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFDIR;
+ isSymLink = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFLNK;
+ } else {
+ isDir = contents.d_type == Const.DT_DIR;
+ isSymLink = contents.d_type == Const.DT_LNK;
+ }
+
+ return new File.DirectoryIterator.Entry(isDir, isSymLink, name, this._path);
+ }
+ this.close();
+ throw StopIteration;
+ };
+
+ /**
+ * Close the iterator and recover all resources.
+ * You should call this once you have finished iterating on a directory.
+ */
+ File.DirectoryIterator.prototype.close = function close() {
+ if (this._closed) return;
+ this._closed = true;
+ UnixFile.closedir(this._dir);
+ this._dir = null;
+ };
+
+ /**
+ * Determine whether the directory exists.
+ *
+ * @return {boolean}
+ */
+ File.DirectoryIterator.prototype.exists = function exists() {
+ return this._exists;
+ };
+
+ /**
+ * Return directory as |File|
+ */
+ File.DirectoryIterator.prototype.unixAsFile = function unixAsFile() {
+ if (!this._dir) throw File.Error.closed("unixAsFile", this._path);
+ return error_or_file(UnixFile.dirfd(this._dir), this._path);
+ };
+
+ /**
+ * An entry in a directory.
+ */
+ File.DirectoryIterator.Entry = function Entry(isDir, isSymLink, name, parent) {
+ // Copy the relevant part of |unix_entry| to ensure that
+ // our data is not overwritten prematurely.
+ this._parent = parent;
+ let path = Path.join(this._parent, name);
+
+ SysAll.AbstractEntry.call(this, isDir, isSymLink, name, path);
+ };
+ File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
+
+ /**
+ * Return a version of an instance of
+ * File.DirectoryIterator.Entry that can be sent from a worker
+ * thread to the main thread. Note that deserialization is
+ * asymmetric and returns an object with a different
+ * implementation.
+ */
+ File.DirectoryIterator.Entry.toMsg = function toMsg(value) {
+ if (!(value instanceof File.DirectoryIterator.Entry)) {
+ throw new TypeError("parameter of " +
+ "File.DirectoryIterator.Entry.toMsg must be a " +
+ "File.DirectoryIterator.Entry");
+ }
+ let serialized = {};
+ for (let key in File.DirectoryIterator.Entry.prototype) {
+ serialized[key] = value[key];
+ }
+ return serialized;
+ };
+
+ let gStatData = new Type.stat.implementation();
+ let gStatDataPtr = gStatData.address();
+
+ let MODE_MASK = 4095 /*= 07777*/;
+ File.Info = function Info(stat, path) {
+ let isDir = (stat.st_mode & Const.S_IFMT) == Const.S_IFDIR;
+ let isSymLink = (stat.st_mode & Const.S_IFMT) == Const.S_IFLNK;
+ let size = Type.off_t.importFromC(stat.st_size);
+
+ let lastAccessDate = new Date(stat.st_atime * 1000);
+ let lastModificationDate = new Date(stat.st_mtime * 1000);
+ let unixLastStatusChangeDate = new Date(stat.st_ctime * 1000);
+
+ let unixOwner = Type.uid_t.importFromC(stat.st_uid);
+ let unixGroup = Type.gid_t.importFromC(stat.st_gid);
+ let unixMode = Type.mode_t.importFromC(stat.st_mode & MODE_MASK);
+
+ SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size,
+ lastAccessDate, lastModificationDate, unixLastStatusChangeDate,
+ unixOwner, unixGroup, unixMode);
+
+ // Some platforms (e.g. MacOS X, some BSDs) store a file creation date
+ if ("OSFILE_OFFSETOF_STAT_ST_BIRTHTIME" in Const) {
+ let date = new Date(stat.st_birthtime * 1000);
+
+ /**
+ * The date of creation of this file.
+ *
+ * Note that the date returned by this method is not always
+ * reliable. Not all file systems are able to provide this
+ * information.
+ *
+ * @type {Date}
+ */
+ this.macBirthDate = date;
+ }
+ };
+ File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype);
+
+ // Deprecated, use macBirthDate/winBirthDate instead
+ Object.defineProperty(File.Info.prototype, "creationDate", {
+ get: function creationDate() {
+ // On the Macintosh, returns the birth date if available.
+ // On other Unix, as the birth date is not available,
+ // returns the epoch.
+ return this.macBirthDate || new Date(0);
+ }
+ });
+
+ /**
+ * Return a version of an instance of File.Info that can be sent
+ * from a worker thread to the main thread. Note that deserialization
+ * is asymmetric and returns an object with a different implementation.
+ */
+ File.Info.toMsg = function toMsg(stat) {
+ if (!(stat instanceof File.Info)) {
+ throw new TypeError("parameter of File.Info.toMsg must be a File.Info");
+ }
+ let serialized = {};
+ for (let key in File.Info.prototype) {
+ serialized[key] = stat[key];
+ }
+ return serialized;
+ };
+
+ /**
+ * Fetch the information on a file.
+ *
+ * @param {string} path The full name of the file to open.
+ * @param {*=} options Additional options. In this implementation:
+ *
+ * - {bool} unixNoFollowingLinks If set and |true|, if |path|
+ * represents a symbolic link, the call will return the information
+ * of the link itself, rather than that of the target file.
+ *
+ * @return {File.Information}
+ */
+ File.stat = function stat(path, options = {}) {
+ if (options.unixNoFollowingLinks) {
+ throw_on_negative("stat", UnixFile.lstat(path, gStatDataPtr), path);
+ } else {
+ throw_on_negative("stat", UnixFile.stat(path, gStatDataPtr), path);
+ }
+ return new File.Info(gStatData, path);
+ };
+
+ /**
+ * Set the file's access permissions.
+ *
+ * This operation is likely to fail if applied to a file that was
+ * not created by the currently running program (more precisely,
+ * if it was created by a program running under a different OS-level
+ * user account). It may also fail, or silently do nothing, if the
+ * filesystem containing the file does not support access permissions.
+ *
+ * @param {string} path The name of the file to reset the permissions of.
+ * @param {*=} options Object specifying the requested permissions:
+ *
+ * - {number} unixMode The POSIX file mode to set on the file. If omitted,
+ * the POSIX file mode is reset to the default used by |OS.file.open|. If
+ * specified, the permissions will respect the process umask as if they
+ * had been specified as arguments of |OS.File.open|, unless the
+ * |unixHonorUmask| parameter tells otherwise.
+ * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
+ * modified by the process umask, as |OS.File.open| would have done. If
+ * false, the exact value of |unixMode| will be applied.
+ */
+ File.setPermissions = function setPermissions(path, options = {}) {
+ throw_on_negative("setPermissions",
+ UnixFile.chmod(path, unixMode(options)),
+ path);
+ };
+
+ /**
+ * Convert an access date and a modification date to an array
+ * of two |timeval|.
+ */
+ function datesToTimevals(accessDate, modificationDate) {
+ accessDate = normalizeDate("File.setDates", accessDate);
+ modificationDate = normalizeDate("File.setDates", modificationDate);
+
+ let timevals = new Type.timevals.implementation();
+ let timevalsPtr = timevals.address();
+
+ timevals[0].tv_sec = (accessDate / 1000) | 0;
+ timevals[0].tv_usec = 0;
+ timevals[1].tv_sec = (modificationDate / 1000) | 0;
+ timevals[1].tv_usec = 0;
+
+ return { value: timevals, ptr: timevalsPtr };
+ }
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * @param {string} path The full name of the file to set the dates for.
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid paramters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.setDates = function setDates(path, accessDate, modificationDate) {
+ let {value, ptr} = datesToTimevals(accessDate, modificationDate);
+ throw_on_negative("setDates",
+ UnixFile.utimes(path, ptr),
+ path);
+ };
+
+ File.read = exports.OS.Shared.AbstractFile.read;
+ File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
+ File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
+ File.makeDir = exports.OS.Shared.AbstractFile.makeDir;
+
+ /**
+ * Remove an existing directory and its contents.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
+ * exist. |true| by default.
+ * - {boolean} ignorePermissions If |true|, remove the file even when lacking write
+ * permission.
+ *
+ * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
+ * not a directory.
+ *
+ * Note: This function will remove a symlink even if it points a directory.
+ */
+ File.removeDir = function(path, options = {}) {
+ let isSymLink;
+ try {
+ let info = File.stat(path, {unixNoFollowingLinks: true});
+ isSymLink = info.isSymLink;
+ } catch (e) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.errno == Const.ENOENT) {
+ return;
+ }
+ throw e;
+ }
+ if (isSymLink) {
+ // A Unix symlink itself is not a directory even if it points
+ // a directory.
+ File.remove(path, options);
+ return;
+ }
+ exports.OS.Shared.AbstractFile.removeRecursive(path, options);
+ };
+
+ /**
+ * Get the current directory by getCurrentDirectory.
+ */
+ File.getCurrentDirectory = function getCurrentDirectory() {
+ let path, buf;
+ if (UnixFile.get_current_dir_name) {
+ path = UnixFile.get_current_dir_name();
+ } else if (UnixFile.getwd_auto) {
+ path = UnixFile.getwd_auto(null);
+ } else {
+ for (let length = Const.PATH_MAX; !path; length *= 2) {
+ buf = new (ctypes.char.array(length));
+ path = UnixFile.getcwd(buf, length);
+ };
+ }
+ throw_on_null("getCurrentDirectory", path);
+ return path.readString();
+ };
+
+ /**
+ * Set the current directory by setCurrentDirectory.
+ */
+ File.setCurrentDirectory = function setCurrentDirectory(path) {
+ throw_on_negative("setCurrentDirectory",
+ UnixFile.chdir(path),
+ path
+ );
+ };
+
+ /**
+ * Get/set the current directory.
+ */
+ Object.defineProperty(File, "curDir", {
+ set: function(path) {
+ this.setCurrentDirectory(path);
+ },
+ get: function() {
+ return this.getCurrentDirectory();
+ }
+ }
+ );
+
+ // Utility functions
+
+ /**
+ * Turn the result of |open| into an Error or a File
+ * @param {number} maybe The result of the |open| operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.errno, otherwise it returns the opened file.
+ * @param {string=} path The path of the file.
+ */
+ function error_or_file(maybe, path) {
+ if (maybe == -1) {
+ throw new File.Error("open", ctypes.errno, path);
+ }
+ return new File(maybe, path);
+ }
+
+ /**
+ * Utility function to sort errors represented as "-1" from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {number} result The result of the operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.errno, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_negative(operation, result, path) {
+ if (result < 0) {
+ throw new File.Error(operation, ctypes.errno, path);
+ }
+ return result;
+ }
+
+ /**
+ * Utility function to sort errors represented as |null| from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {pointer} result The result of the operation that may
+ * represent either an error or a success. If |null|, this function raises
+ * an error holding ctypes.errno, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_null(operation, result, path) {
+ if (result == null || (result.isNull && result.isNull())) {
+ throw new File.Error(operation, ctypes.errno, path);
+ }
+ return result;
+ }
+
+ /**
+ * Normalize and verify a Date or numeric date value.
+ *
+ * @param {string} fn Function name of the calling function.
+ * @param {Date,number} date The date to normalize. If omitted or null,
+ * then the current date will be used.
+ *
+ * @throws {TypeError} Invalid date provided.
+ *
+ * @return {number} Sanitized, numeric date in milliseconds since epoch.
+ */
+ function normalizeDate(fn, date) {
+ if (typeof date !== "number" && !date) {
+ // |date| was Omitted or null.
+ date = Date.now();
+ } else if (typeof date.getTime === "function") {
+ // Input might be a date or date-like object.
+ date = date.getTime();
+ }
+
+ if (typeof date !== "number" || Number.isNaN(date)) {
+ throw new TypeError("|date| parameter of " + fn + " must be a " +
+ "|Date| instance or number");
+ }
+ return date;
+ };
+
+ /**
+ * Helper used by both versions of setPermissions.
+ */
+ function unixMode(options) {
+ let mode = options.unixMode !== undefined ?
+ options.unixMode : DEFAULT_UNIX_MODE;
+ let unixHonorUmask = true;
+ if ("unixHonorUmask" in options) {
+ unixHonorUmask = options.unixHonorUmask;
+ }
+ if (unixHonorUmask) {
+ mode &= ~SharedAll.Constants.Sys.umask;
+ }
+ return mode;
+ }
+
+ File.Unix = exports.OS.Unix.File;
+ File.Error = SysAll.Error;
+ exports.OS.File = File;
+ exports.OS.Shared.Type = Type;
+
+ Object.defineProperty(File, "POS_START", { value: SysAll.POS_START });
+ Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT });
+ Object.defineProperty(File, "POS_END", { value: SysAll.POS_END });
+ })(this);
+}
diff --git a/components/osfile/modules/osfile_win_allthreads.jsm b/components/osfile/modules/osfile_win_allthreads.jsm
new file mode 100644
index 000000000..b059d4e12
--- /dev/null
+++ b/components/osfile/modules/osfile_win_allthreads.jsm
@@ -0,0 +1,425 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 defines the thread-agnostic components of the Win version
+ * of OS.File. It depends on the thread-agnostic cross-platform components
+ * of OS.File.
+ *
+ * It serves the following purposes:
+ * - open kernel32;
+ * - define OS.Shared.Win.Error;
+ * - define a few constants and types that need to be defined on all platforms.
+ *
+ * This module can be:
+ * - opened from the main thread as a jsm module;
+ * - opened from a chrome worker through require().
+ */
+
+"use strict";
+
+var SharedAll;
+if (typeof Components != "undefined") {
+ let Cu = Components.utils;
+ // Module is opened as a jsm module
+ Cu.import("resource://gre/modules/ctypes.jsm", this);
+
+ SharedAll = {};
+ Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
+ this.exports = {};
+} else if (typeof module != "undefined" && typeof require != "undefined") {
+ // Module is loaded with require()
+ SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+} else {
+ throw new Error("Please open this module with Component.utils.import or with require()");
+}
+
+var LOG = SharedAll.LOG.bind(SharedAll, "Win", "allthreads");
+var Const = SharedAll.Constants.Win;
+
+// Open libc
+var libc = new SharedAll.Library("libc", "kernel32.dll");
+exports.libc = libc;
+
+// Define declareFFI
+var declareFFI = SharedAll.declareFFI.bind(null, libc);
+exports.declareFFI = declareFFI;
+
+var Scope = {};
+
+// Define Error
+libc.declareLazy(Scope, "FormatMessage",
+ "FormatMessageW", ctypes.winapi_abi,
+ /*return*/ ctypes.uint32_t,
+ /*flags*/ ctypes.uint32_t,
+ /*source*/ ctypes.voidptr_t,
+ /*msgid*/ ctypes.uint32_t,
+ /*langid*/ ctypes.uint32_t,
+ /*buf*/ ctypes.char16_t.ptr,
+ /*size*/ ctypes.uint32_t,
+ /*Arguments*/ctypes.voidptr_t);
+
+/**
+ * A File-related error.
+ *
+ * To obtain a human-readable error message, use method |toString|.
+ * To determine the cause of the error, use the various |becauseX|
+ * getters. To determine the operation that failed, use field
+ * |operation|.
+ *
+ * Additionally, this implementation offers a field
+ * |winLastError|, which holds the OS-specific error
+ * constant. If you need this level of detail, you may match the value
+ * of this field against the error constants of |OS.Constants.Win|.
+ *
+ * @param {string=} operation The operation that failed. If unspecified,
+ * the name of the calling function is taken to be the operation that
+ * failed.
+ * @param {number=} lastError The OS-specific constant detailing the
+ * reason of the error. If unspecified, this is fetched from the system
+ * status.
+ * @param {string=} path The file path that manipulated. If unspecified,
+ * assign the empty string.
+ *
+ * @constructor
+ * @extends {OS.Shared.Error}
+ */
+var OSError = function OSError(operation = "unknown operation",
+ lastError = ctypes.winLastError, path = "") {
+ operation = operation;
+ SharedAll.OSError.call(this, operation, path);
+ this.winLastError = lastError;
+};
+OSError.prototype = Object.create(SharedAll.OSError.prototype);
+OSError.prototype.toString = function toString() {
+ let buf = new (ctypes.ArrayType(ctypes.char16_t, 1024))();
+ let result = Scope.FormatMessage(
+ Const.FORMAT_MESSAGE_FROM_SYSTEM |
+ Const.FORMAT_MESSAGE_IGNORE_INSERTS,
+ null,
+ /* The error number */ this.winLastError,
+ /* Default language */ 0,
+ /* Output buffer*/ buf,
+ /* Minimum size of buffer */ 1024,
+ /* Format args*/ null
+ );
+ if (!result) {
+ buf = "additional error " +
+ ctypes.winLastError +
+ " while fetching system error message";
+ }
+ return "Win error " + this.winLastError + " during operation "
+ + this.operation + (this.path? " on file " + this.path : "") +
+ " (" + buf.readString() + ")";
+};
+OSError.prototype.toMsg = function toMsg() {
+ return OSError.toMsg(this);
+};
+
+/**
+ * |true| if the error was raised because a file or directory
+ * already exists, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseExists", {
+ get: function becauseExists() {
+ return this.winLastError == Const.ERROR_FILE_EXISTS ||
+ this.winLastError == Const.ERROR_ALREADY_EXISTS;
+ }
+});
+/**
+ * |true| if the error was raised because a file or directory
+ * does not exist, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseNoSuchFile", {
+ get: function becauseNoSuchFile() {
+ return this.winLastError == Const.ERROR_FILE_NOT_FOUND ||
+ this.winLastError == Const.ERROR_PATH_NOT_FOUND;
+ }
+});
+/**
+ * |true| if the error was raised because a directory is not empty
+ * does not exist, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseNotEmpty", {
+ get: function becauseNotEmpty() {
+ return this.winLastError == Const.ERROR_DIR_NOT_EMPTY;
+ }
+});
+/**
+ * |true| if the error was raised because a file or directory
+ * is closed, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseClosed", {
+ get: function becauseClosed() {
+ return this.winLastError == Const.ERROR_INVALID_HANDLE;
+ }
+});
+/**
+ * |true| if the error was raised because permission is denied to
+ * access a file or directory, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseAccessDenied", {
+ get: function becauseAccessDenied() {
+ return this.winLastError == Const.ERROR_ACCESS_DENIED;
+ }
+});
+/**
+ * |true| if the error was raised because some invalid argument was passed,
+ * |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseInvalidArgument", {
+ get: function becauseInvalidArgument() {
+ return this.winLastError == Const.ERROR_NOT_SUPPORTED ||
+ this.winLastError == Const.ERROR_BAD_ARGUMENTS;
+ }
+});
+
+/**
+ * Serialize an instance of OSError to something that can be
+ * transmitted across threads (not necessarily a string).
+ */
+OSError.toMsg = function toMsg(error) {
+ return {
+ exn: "OS.File.Error",
+ fileName: error.moduleName,
+ lineNumber: error.lineNumber,
+ stack: error.moduleStack,
+ operation: error.operation,
+ winLastError: error.winLastError,
+ path: error.path
+ };
+};
+
+/**
+ * Deserialize a message back to an instance of OSError
+ */
+OSError.fromMsg = function fromMsg(msg) {
+ let error = new OSError(msg.operation, msg.winLastError, msg.path);
+ error.stack = msg.stack;
+ error.fileName = msg.fileName;
+ error.lineNumber = msg.lineNumber;
+ return error;
+};
+exports.Error = OSError;
+
+/**
+ * Code shared by implementation of File.Info on Windows
+ *
+ * @constructor
+ */
+var AbstractInfo = function AbstractInfo(path, isDir, isSymLink, size,
+ winBirthDate,
+ lastAccessDate, lastWriteDate,
+ winAttributes) {
+ this._path = path;
+ this._isDir = isDir;
+ this._isSymLink = isSymLink;
+ this._size = size;
+ this._winBirthDate = winBirthDate;
+ this._lastAccessDate = lastAccessDate;
+ this._lastModificationDate = lastWriteDate;
+ this._winAttributes = winAttributes;
+};
+
+AbstractInfo.prototype = {
+ /**
+ * The path of the file, used for error-reporting.
+ *
+ * @type {string}
+ */
+ get path() {
+ return this._path;
+ },
+ /**
+ * |true| if this file is a directory, |false| otherwise
+ */
+ get isDir() {
+ return this._isDir;
+ },
+ /**
+ * |true| if this file is a symbolic link, |false| otherwise
+ */
+ get isSymLink() {
+ return this._isSymLink;
+ },
+ /**
+ * The size of the file, in bytes.
+ *
+ * Note that the result may be |NaN| if the size of the file cannot be
+ * represented in JavaScript.
+ *
+ * @type {number}
+ */
+ get size() {
+ return this._size;
+ },
+ // Deprecated
+ get creationDate() {
+ return this._winBirthDate;
+ },
+ /**
+ * The date of creation of this file.
+ *
+ * @type {Date}
+ */
+ get winBirthDate() {
+ return this._winBirthDate;
+ },
+ /**
+ * The date of last access to this file.
+ *
+ * Note that the definition of last access may depend on the underlying
+ * operating system and file system.
+ *
+ * @type {Date}
+ */
+ get lastAccessDate() {
+ return this._lastAccessDate;
+ },
+ /**
+ * The date of last modification of this file.
+ *
+ * Note that the definition of last access may depend on the underlying
+ * operating system and file system.
+ *
+ * @type {Date}
+ */
+ get lastModificationDate() {
+ return this._lastModificationDate;
+ },
+ /**
+ * The Object with following boolean properties of this file.
+ * {readOnly, system, hidden}
+ *
+ * @type {object}
+ */
+ get winAttributes() {
+ return this._winAttributes;
+ }
+};
+exports.AbstractInfo = AbstractInfo;
+
+/**
+ * Code shared by implementation of File.DirectoryIterator.Entry on Windows
+ *
+ * @constructor
+ */
+var AbstractEntry = function AbstractEntry(isDir, isSymLink, name,
+ winCreationDate, winLastWriteDate,
+ winLastAccessDate, path) {
+ this._isDir = isDir;
+ this._isSymLink = isSymLink;
+ this._name = name;
+ this._winCreationDate = winCreationDate;
+ this._winLastWriteDate = winLastWriteDate;
+ this._winLastAccessDate = winLastAccessDate;
+ this._path = path;
+};
+
+AbstractEntry.prototype = {
+ /**
+ * |true| if the entry is a directory, |false| otherwise
+ */
+ get isDir() {
+ return this._isDir;
+ },
+ /**
+ * |true| if the entry is a symbolic link, |false| otherwise
+ */
+ get isSymLink() {
+ return this._isSymLink;
+ },
+ /**
+ * The name of the entry.
+ * @type {string}
+ */
+ get name() {
+ return this._name;
+ },
+ /**
+ * The creation time of this file.
+ * @type {Date}
+ */
+ get winCreationDate() {
+ return this._winCreationDate;
+ },
+ /**
+ * The last modification time of this file.
+ * @type {Date}
+ */
+ get winLastWriteDate() {
+ return this._winLastWriteDate;
+ },
+ /**
+ * The last access time of this file.
+ * @type {Date}
+ */
+ get winLastAccessDate() {
+ return this._winLastAccessDate;
+ },
+ /**
+ * The full path of the entry
+ * @type {string}
+ */
+ get path() {
+ return this._path;
+ }
+};
+exports.AbstractEntry = AbstractEntry;
+
+// Special constants that need to be defined on all platforms
+
+exports.POS_START = Const.FILE_BEGIN;
+exports.POS_CURRENT = Const.FILE_CURRENT;
+exports.POS_END = Const.FILE_END;
+
+// Special types that need to be defined for communication
+// between threads
+var Type = Object.create(SharedAll.Type);
+exports.Type = Type;
+
+/**
+ * Native paths
+ *
+ * Under Windows, expressed as wide strings
+ */
+Type.path = Type.wstring.withName("[in] path");
+Type.out_path = Type.out_wstring.withName("[out] path");
+
+// Special constructors that need to be defined on all threads
+OSError.closed = function closed(operation, path) {
+ return new OSError(operation, Const.ERROR_INVALID_HANDLE, path);
+};
+
+OSError.exists = function exists(operation, path) {
+ return new OSError(operation, Const.ERROR_FILE_EXISTS, path);
+};
+
+OSError.noSuchFile = function noSuchFile(operation, path) {
+ return new OSError(operation, Const.ERROR_FILE_NOT_FOUND, path);
+};
+
+OSError.invalidArgument = function invalidArgument(operation) {
+ return new OSError(operation, Const.ERROR_NOT_SUPPORTED);
+};
+
+var EXPORTED_SYMBOLS = [
+ "declareFFI",
+ "libc",
+ "Error",
+ "AbstractInfo",
+ "AbstractEntry",
+ "Type",
+ "POS_START",
+ "POS_CURRENT",
+ "POS_END"
+];
+
+//////////// Boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}
diff --git a/components/osfile/modules/osfile_win_back.jsm b/components/osfile/modules/osfile_win_back.jsm
new file mode 100644
index 000000000..21172b6bf
--- /dev/null
+++ b/components/osfile/modules/osfile_win_back.jsm
@@ -0,0 +1,437 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 can be used in the following contexts:
+ *
+ * 1. included from a non-osfile worker thread using importScript
+ * (it serves to define a synchronous API for that worker thread)
+ * (bug 707681)
+ *
+ * 2. included from the main thread using Components.utils.import
+ * (it serves to define the asynchronous API, whose implementation
+ * resides in the worker thread)
+ * (bug 729057)
+ *
+ * 3. included from the osfile worker thread using importScript
+ * (it serves to define the implementation of the asynchronous API)
+ * (bug 729057)
+ */
+
+{
+ if (typeof Components != "undefined") {
+ // We do not wish osfile_win.jsm to be used directly as a main thread
+ // module yet. When time comes, it will be loaded by a combination of
+ // a main thread front-end/worker thread implementation that makes sure
+ // that we are not executing synchronous IO code in the main thread.
+
+ throw new Error("osfile_win.jsm cannot be used from the main thread yet");
+ }
+
+ (function(exports) {
+ "use strict";
+ if (exports.OS && exports.OS.Win && exports.OS.Win.File) {
+ return; // Avoid double initialization
+ }
+
+ let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let SysAll = require("resource://gre/modules/osfile/osfile_win_allthreads.jsm");
+ let LOG = SharedAll.LOG.bind(SharedAll, "Unix", "back");
+ let libc = SysAll.libc;
+ let advapi32 = new SharedAll.Library("advapi32", "advapi32.dll");
+ let Const = SharedAll.Constants.Win;
+
+ /**
+ * Initialize the Windows module.
+ *
+ * @param {function=} declareFFI
+ */
+ // FIXME: Both |init| and |aDeclareFFI| are deprecated, we should remove them
+ let init = function init(aDeclareFFI) {
+ let declareFFI;
+ if (aDeclareFFI) {
+ declareFFI = aDeclareFFI.bind(null, libc);
+ } else {
+ declareFFI = SysAll.declareFFI;
+ }
+ let declareLazyFFI = SharedAll.declareLazyFFI;
+
+ // Initialize types that require additional OS-specific
+ // support - either finalization or matching against
+ // OS-specific constants.
+ let Type = Object.create(SysAll.Type);
+ let SysFile = exports.OS.Win.File = { Type: Type };
+
+ // Initialize types
+
+ /**
+ * A C integer holding INVALID_HANDLE_VALUE in case of error or
+ * a file descriptor in case of success.
+ */
+ Type.HANDLE =
+ Type.voidptr_t.withName("HANDLE");
+ Type.HANDLE.importFromC = function importFromC(maybe) {
+ if (Type.int.cast(maybe).value == INVALID_HANDLE) {
+ // Ensure that API clients can effectively compare against
+ // Const.INVALID_HANDLE_VALUE. Without this cast,
+ // == would always return |false|.
+ return INVALID_HANDLE;
+ }
+ return ctypes.CDataFinalizer(maybe, this.finalizeHANDLE);
+ };
+ Type.HANDLE.finalizeHANDLE = function placeholder() {
+ throw new Error("finalizeHANDLE should be implemented");
+ };
+ let INVALID_HANDLE = Const.INVALID_HANDLE_VALUE;
+
+ Type.file_HANDLE = Type.HANDLE.withName("file HANDLE");
+ SharedAll.defineLazyGetter(Type.file_HANDLE,
+ "finalizeHANDLE",
+ function() {
+ return SysFile._CloseHandle;
+ });
+
+ Type.find_HANDLE = Type.HANDLE.withName("find HANDLE");
+ SharedAll.defineLazyGetter(Type.find_HANDLE,
+ "finalizeHANDLE",
+ function() {
+ return SysFile._FindClose;
+ });
+
+ Type.DWORD = Type.uint32_t.withName("DWORD");
+
+ /* A special type used to represent flags passed as DWORDs to a function.
+ * In JavaScript, bitwise manipulation of numbers, such as or-ing flags,
+ * can produce negative numbers. Since DWORD is unsigned, these negative
+ * numbers simply cannot be converted to DWORD. For this reason, whenever
+ * bit manipulation is called for, you should rather use DWORD_FLAGS,
+ * which is represented as a signed integer, hence has the correct
+ * semantics.
+ */
+ Type.DWORD_FLAGS = Type.int32_t.withName("DWORD_FLAGS");
+
+ /**
+ * A C integer holding 0 in case of error or a positive integer
+ * in case of success.
+ */
+ Type.zero_or_DWORD =
+ Type.DWORD.withName("zero_or_DWORD");
+
+ /**
+ * A C integer holding 0 in case of error, any other value in
+ * case of success.
+ */
+ Type.zero_or_nothing =
+ Type.int.withName("zero_or_nothing");
+
+ /**
+ * A C integer holding flags related to NTFS security.
+ */
+ Type.SECURITY_ATTRIBUTES =
+ Type.void_t.withName("SECURITY_ATTRIBUTES");
+
+ /**
+ * A C integer holding pointers related to NTFS security.
+ */
+ Type.PSID =
+ Type.voidptr_t.withName("PSID");
+
+ Type.PACL =
+ Type.voidptr_t.withName("PACL");
+
+ Type.PSECURITY_DESCRIPTOR =
+ Type.voidptr_t.withName("PSECURITY_DESCRIPTOR");
+
+ /**
+ * A C integer holding Win32 local memory handle.
+ */
+ Type.HLOCAL =
+ Type.voidptr_t.withName("HLOCAL");
+
+ Type.FILETIME =
+ new SharedAll.Type("FILETIME",
+ ctypes.StructType("FILETIME", [
+ { lo: Type.DWORD.implementation },
+ { hi: Type.DWORD.implementation }]));
+
+ Type.FindData =
+ new SharedAll.Type("FIND_DATA",
+ ctypes.StructType("FIND_DATA", [
+ { dwFileAttributes: ctypes.uint32_t },
+ { ftCreationTime: Type.FILETIME.implementation },
+ { ftLastAccessTime: Type.FILETIME.implementation },
+ { ftLastWriteTime: Type.FILETIME.implementation },
+ { nFileSizeHigh: Type.DWORD.implementation },
+ { nFileSizeLow: Type.DWORD.implementation },
+ { dwReserved0: Type.DWORD.implementation },
+ { dwReserved1: Type.DWORD.implementation },
+ { cFileName: ctypes.ArrayType(ctypes.char16_t, Const.MAX_PATH) },
+ { cAlternateFileName: ctypes.ArrayType(ctypes.char16_t, 14) }
+ ]));
+
+ Type.FILE_INFORMATION =
+ new SharedAll.Type("FILE_INFORMATION",
+ ctypes.StructType("FILE_INFORMATION", [
+ { dwFileAttributes: ctypes.uint32_t },
+ { ftCreationTime: Type.FILETIME.implementation },
+ { ftLastAccessTime: Type.FILETIME.implementation },
+ { ftLastWriteTime: Type.FILETIME.implementation },
+ { dwVolumeSerialNumber: ctypes.uint32_t },
+ { nFileSizeHigh: Type.DWORD.implementation },
+ { nFileSizeLow: Type.DWORD.implementation },
+ { nNumberOfLinks: ctypes.uint32_t },
+ { nFileIndex: ctypes.uint64_t }
+ ]));
+
+ Type.SystemTime =
+ new SharedAll.Type("SystemTime",
+ ctypes.StructType("SystemTime", [
+ { wYear: ctypes.int16_t },
+ { wMonth: ctypes.int16_t },
+ { wDayOfWeek: ctypes.int16_t },
+ { wDay: ctypes.int16_t },
+ { wHour: ctypes.int16_t },
+ { wMinute: ctypes.int16_t },
+ { wSecond: ctypes.int16_t },
+ { wMilliSeconds: ctypes.int16_t }
+ ]));
+
+ // Special case: these functions are used by the
+ // finalizer
+ libc.declareLazy(SysFile, "_CloseHandle",
+ "CloseHandle", ctypes.winapi_abi,
+ /*return */ctypes.bool,
+ /*handle*/ ctypes.voidptr_t);
+
+ SysFile.CloseHandle = function(fd) {
+ if (fd == INVALID_HANDLE) {
+ return true;
+ } else {
+ return fd.dispose(); // Returns the value of |CloseHandle|.
+ }
+ };
+
+ libc.declareLazy(SysFile, "_FindClose",
+ "FindClose", ctypes.winapi_abi,
+ /*return */ctypes.bool,
+ /*handle*/ ctypes.voidptr_t);
+
+ SysFile.FindClose = function(handle) {
+ if (handle == INVALID_HANDLE) {
+ return true;
+ } else {
+ return handle.dispose(); // Returns the value of |FindClose|.
+ }
+ };
+
+ // Declare libc functions as functions of |OS.Win.File|
+
+ libc.declareLazyFFI(SysFile, "CopyFile",
+ "CopyFileW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*sourcePath*/ Type.path,
+ /*destPath*/ Type.path,
+ /*bailIfExist*/Type.bool);
+
+ libc.declareLazyFFI(SysFile, "CreateDirectory",
+ "CreateDirectoryW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*name*/ Type.char16_t.in_ptr,
+ /*security*/Type.SECURITY_ATTRIBUTES.in_ptr);
+
+ libc.declareLazyFFI(SysFile, "CreateFile",
+ "CreateFileW", ctypes.winapi_abi,
+ /*return*/ Type.file_HANDLE,
+ /*name*/ Type.path,
+ /*access*/ Type.DWORD_FLAGS,
+ /*share*/ Type.DWORD_FLAGS,
+ /*security*/Type.SECURITY_ATTRIBUTES.in_ptr,
+ /*creation*/Type.DWORD_FLAGS,
+ /*flags*/ Type.DWORD_FLAGS,
+ /*template*/Type.HANDLE);
+
+ libc.declareLazyFFI(SysFile, "DeleteFile",
+ "DeleteFileW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "FileTimeToSystemTime",
+ "FileTimeToSystemTime", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*filetime*/Type.FILETIME.in_ptr,
+ /*systime*/ Type.SystemTime.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "SystemTimeToFileTime",
+ "SystemTimeToFileTime", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*systime*/ Type.SystemTime.in_ptr,
+ /*filetime*/ Type.FILETIME.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "FindFirstFile",
+ "FindFirstFileW", ctypes.winapi_abi,
+ /*return*/ Type.find_HANDLE,
+ /*pattern*/Type.path,
+ /*data*/ Type.FindData.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "FindNextFile",
+ "FindNextFileW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*prev*/ Type.find_HANDLE,
+ /*data*/ Type.FindData.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "FormatMessage",
+ "FormatMessageW", ctypes.winapi_abi,
+ /*return*/ Type.DWORD,
+ /*flags*/ Type.DWORD_FLAGS,
+ /*source*/ Type.void_t.in_ptr,
+ /*msgid*/ Type.DWORD_FLAGS,
+ /*langid*/ Type.DWORD_FLAGS,
+ /*buf*/ Type.out_wstring,
+ /*size*/ Type.DWORD,
+ /*Arguments*/Type.void_t.in_ptr
+ );
+
+ libc.declareLazyFFI(SysFile, "GetCurrentDirectory",
+ "GetCurrentDirectoryW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_DWORD,
+ /*length*/ Type.DWORD,
+ /*buf*/ Type.out_path
+ );
+
+ libc.declareLazyFFI(SysFile, "GetFullPathName",
+ "GetFullPathNameW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_DWORD,
+ /*fileName*/ Type.path,
+ /*length*/ Type.DWORD,
+ /*buf*/ Type.out_path,
+ /*filePart*/ Type.DWORD
+ );
+
+ libc.declareLazyFFI(SysFile, "GetDiskFreeSpaceEx",
+ "GetDiskFreeSpaceExW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*directoryName*/ Type.path,
+ /*freeBytesForUser*/ Type.uint64_t.out_ptr,
+ /*totalBytesForUser*/ Type.uint64_t.out_ptr,
+ /*freeTotalBytesOnDrive*/ Type.uint64_t.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "GetFileInformationByHandle",
+ "GetFileInformationByHandle", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*handle*/ Type.HANDLE,
+ /*info*/ Type.FILE_INFORMATION.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "MoveFileEx",
+ "MoveFileExW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*sourcePath*/ Type.path,
+ /*destPath*/ Type.path,
+ /*flags*/ Type.DWORD
+ );
+
+ libc.declareLazyFFI(SysFile, "ReadFile",
+ "ReadFile", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*file*/ Type.HANDLE,
+ /*buffer*/ Type.voidptr_t,
+ /*nbytes*/ Type.DWORD,
+ /*nbytes_read*/Type.DWORD.out_ptr,
+ /*overlapped*/Type.void_t.inout_ptr // FIXME: Implement?
+ );
+
+ libc.declareLazyFFI(SysFile, "RemoveDirectory",
+ "RemoveDirectoryW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "SetCurrentDirectory",
+ "SetCurrentDirectoryW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*path*/ Type.path
+ );
+
+ libc.declareLazyFFI(SysFile, "SetEndOfFile",
+ "SetEndOfFile", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*file*/ Type.HANDLE);
+
+ libc.declareLazyFFI(SysFile, "SetFilePointer",
+ "SetFilePointer", ctypes.winapi_abi,
+ /*return*/ Type.DWORD,
+ /*file*/ Type.HANDLE,
+ /*distlow*/Type.long,
+ /*disthi*/ Type.long.in_ptr,
+ /*method*/ Type.DWORD);
+
+ libc.declareLazyFFI(SysFile, "SetFileTime",
+ "SetFileTime", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*file*/ Type.HANDLE,
+ /*creation*/ Type.FILETIME.in_ptr,
+ /*access*/ Type.FILETIME.in_ptr,
+ /*write*/ Type.FILETIME.in_ptr);
+
+
+ libc.declareLazyFFI(SysFile, "WriteFile",
+ "WriteFile", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*file*/ Type.HANDLE,
+ /*buffer*/ Type.voidptr_t,
+ /*nbytes*/ Type.DWORD,
+ /*nbytes_wr*/Type.DWORD.out_ptr,
+ /*overlapped*/Type.void_t.inout_ptr // FIXME: Implement?
+ );
+
+ libc.declareLazyFFI(SysFile, "FlushFileBuffers",
+ "FlushFileBuffers", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*file*/ Type.HANDLE);
+
+ libc.declareLazyFFI(SysFile, "GetFileAttributes",
+ "GetFileAttributesW", ctypes.winapi_abi,
+ /*return*/ Type.DWORD_FLAGS,
+ /*fileName*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "SetFileAttributes",
+ "SetFileAttributesW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*fileName*/ Type.path,
+ /*fileAttributes*/ Type.DWORD_FLAGS);
+
+ advapi32.declareLazyFFI(SysFile, "GetNamedSecurityInfo",
+ "GetNamedSecurityInfoW", ctypes.winapi_abi,
+ /*return*/ Type.DWORD,
+ /*objectName*/ Type.path,
+ /*objectType*/ Type.DWORD,
+ /*securityInfo*/ Type.DWORD,
+ /*sidOwner*/ Type.PSID.out_ptr,
+ /*sidGroup*/ Type.PSID.out_ptr,
+ /*dacl*/ Type.PACL.out_ptr,
+ /*sacl*/ Type.PACL.out_ptr,
+ /*securityDesc*/ Type.PSECURITY_DESCRIPTOR.out_ptr);
+
+ advapi32.declareLazyFFI(SysFile, "SetNamedSecurityInfo",
+ "SetNamedSecurityInfoW", ctypes.winapi_abi,
+ /*return*/ Type.DWORD,
+ /*objectName*/ Type.path,
+ /*objectType*/ Type.DWORD,
+ /*securityInfo*/ Type.DWORD,
+ /*sidOwner*/ Type.PSID,
+ /*sidGroup*/ Type.PSID,
+ /*dacl*/ Type.PACL,
+ /*sacl*/ Type.PACL);
+
+ libc.declareLazyFFI(SysFile, "LocalFree",
+ "LocalFree", ctypes.winapi_abi,
+ /*return*/ Type.HLOCAL,
+ /*mem*/ Type.HLOCAL);
+ };
+
+ exports.OS.Win = {
+ File: {
+ _init: init
+ }
+ };
+ })(this);
+}
diff --git a/components/osfile/modules/osfile_win_front.jsm b/components/osfile/modules/osfile_win_front.jsm
new file mode 100644
index 000000000..1123b251c
--- /dev/null
+++ b/components/osfile/modules/osfile_win_front.jsm
@@ -0,0 +1,1266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Synchronous front-end for the JavaScript OS.File library.
+ * Windows implementation.
+ *
+ * This front-end is meant to be imported by a worker thread.
+ */
+
+{
+ if (typeof Components != "undefined") {
+ // We do not wish osfile_win_front.jsm to be used directly as a main thread
+ // module yet.
+ throw new Error("osfile_win_front.jsm cannot be used from the main thread yet");
+ }
+
+ (function(exports) {
+ "use strict";
+
+
+ // exports.OS.Win is created by osfile_win_back.jsm
+ if (exports.OS && exports.OS.File) {
+ return; // Avoid double-initialization
+ }
+
+ let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let Path = require("resource://gre/modules/osfile/ospath.jsm");
+ let SysAll = require("resource://gre/modules/osfile/osfile_win_allthreads.jsm");
+ exports.OS.Win.File._init();
+ let Const = exports.OS.Constants.Win;
+ let WinFile = exports.OS.Win.File;
+ let Type = WinFile.Type;
+
+ // Mutable thread-global data
+ // In the Windows implementation, methods |read| and |write|
+ // require passing a pointer to an uint32 to determine how many
+ // bytes have been read/written. In C, this is a benigne operation,
+ // but in js-ctypes, this has a cost. Rather than re-allocating a
+ // C uint32 and a C uint32* for each |read|/|write|, we take advantage
+ // of the fact that the state is thread-private -- hence that two
+ // |read|/|write| operations cannot take place at the same time --
+ // and we use the following global mutable values:
+ let gBytesRead = new ctypes.uint32_t(0);
+ let gBytesReadPtr = gBytesRead.address();
+ let gBytesWritten = new ctypes.uint32_t(0);
+ let gBytesWrittenPtr = gBytesWritten.address();
+
+ // Same story for GetFileInformationByHandle
+ let gFileInfo = new Type.FILE_INFORMATION.implementation();
+ let gFileInfoPtr = gFileInfo.address();
+
+ /**
+ * Representation of a file.
+ *
+ * You generally do not need to call this constructor yourself. Rather,
+ * to open a file, use function |OS.File.open|.
+ *
+ * @param fd A OS-specific file descriptor.
+ * @param {string} path File path of the file handle, used for error-reporting.
+ * @constructor
+ */
+ let File = function File(fd, path) {
+ exports.OS.Shared.AbstractFile.call(this, fd, path);
+ this._closeResult = null;
+ };
+ File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
+
+ /**
+ * Close the file.
+ *
+ * This method has no effect if the file is already closed. However,
+ * if the first call to |close| has thrown an error, further calls
+ * will throw the same error.
+ *
+ * @throws File.Error If closing the file revealed an error that could
+ * not be reported earlier.
+ */
+ File.prototype.close = function close() {
+ if (this._fd) {
+ let fd = this._fd;
+ this._fd = null;
+ // Call |close(fd)|, detach finalizer if any
+ // (|fd| may not be a CDataFinalizer if it has been
+ // instantiated from a controller thread).
+ let result = WinFile._CloseHandle(fd);
+ if (typeof fd == "object" && "forget" in fd) {
+ fd.forget();
+ }
+ if (result == -1) {
+ this._closeResult = new File.Error("close", ctypes.winLastError, this._path);
+ }
+ }
+ if (this._closeResult) {
+ throw this._closeResult;
+ }
+ return;
+ };
+
+ /**
+ * Read some bytes from a file.
+ *
+ * @param {C pointer} buffer A buffer for holding the data
+ * once it is read.
+ * @param {number} nbytes The number of bytes to read. It must not
+ * exceed the size of |buffer| in bytes but it may exceed the number
+ * of bytes unread in the file.
+ * @param {*=} options Additional options for reading. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively read. If zero,
+ * the end of the file has been reached.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._read = function _read(buffer, nbytes, options) {
+ // |gBytesReadPtr| is a pointer to |gBytesRead|.
+ throw_on_zero("read",
+ WinFile.ReadFile(this.fd, buffer, nbytes, gBytesReadPtr, null),
+ this._path
+ );
+ return gBytesRead.value;
+ };
+
+ /**
+ * Write some bytes to a file.
+ *
+ * @param {Typed array} buffer A buffer holding the data that must be
+ * written.
+ * @param {number} nbytes The number of bytes to write. It must not
+ * exceed the size of |buffer| in bytes.
+ * @param {*=} options Additional options for writing. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively written.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._write = function _write(buffer, nbytes, options) {
+ if (this._appendMode) {
+ // Need to manually seek on Windows, as O_APPEND is not supported.
+ // This is, of course, a race, but there is no real way around this.
+ this.setPosition(0, File.POS_END);
+ }
+ // |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
+ throw_on_zero("write",
+ WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null),
+ this._path
+ );
+ return gBytesWritten.value;
+ };
+
+ /**
+ * Return the current position in the file.
+ */
+ File.prototype.getPosition = function getPosition(pos) {
+ return this.setPosition(0, File.POS_CURRENT);
+ };
+
+ /**
+ * Change the current position in the file.
+ *
+ * @param {number} pos The new position. Whether this position
+ * is considered from the current position, from the start of
+ * the file or from the end of the file is determined by
+ * argument |whence|. Note that |pos| may exceed the length of
+ * the file.
+ * @param {number=} whence The reference position. If omitted
+ * or |OS.File.POS_START|, |pos| is relative to the start of the
+ * file. If |OS.File.POS_CURRENT|, |pos| is relative to the
+ * current position in the file. If |OS.File.POS_END|, |pos| is
+ * relative to the end of the file.
+ *
+ * @return The new position in the file.
+ */
+ File.prototype.setPosition = function setPosition(pos, whence) {
+ if (whence === undefined) {
+ whence = Const.FILE_BEGIN;
+ }
+ let pos64 = ctypes.Int64(pos);
+ // Per MSDN, while |lDistanceToMove| (low) is declared as int32_t, when
+ // providing |lDistanceToMoveHigh| as well, it should countain the
+ // bottom 32 bits of the 64-bit integer. Hence the following |posLo|
+ // cast is OK.
+ let posLo = new ctypes.uint32_t(ctypes.Int64.lo(pos64));
+ posLo = ctypes.cast(posLo, ctypes.int32_t);
+ let posHi = new ctypes.int32_t(ctypes.Int64.hi(pos64));
+ let result = WinFile.SetFilePointer(
+ this.fd, posLo.value, posHi.address(), whence);
+ // INVALID_SET_FILE_POINTER might be still a valid result, as it
+ // represents the lower 32 bit of the int64 result. MSDN says to check
+ // both, INVALID_SET_FILE_POINTER and a non-zero winLastError.
+ if (result == Const.INVALID_SET_FILE_POINTER && ctypes.winLastError) {
+ throw new File.Error("setPosition", ctypes.winLastError, this._path);
+ }
+ pos64 = ctypes.Int64.join(posHi.value, result);
+ return Type.int64_t.project(pos64);
+ };
+
+ /**
+ * Fetch the information on the file.
+ *
+ * @return File.Info The information on |this| file.
+ */
+ File.prototype.stat = function stat() {
+ throw_on_zero("stat",
+ WinFile.GetFileInformationByHandle(this.fd, gFileInfoPtr),
+ this._path);
+ return new File.Info(gFileInfo, this._path);
+ };
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid parameters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype.setDates = function setDates(accessDate, modificationDate) {
+ accessDate = Date_to_FILETIME("File.prototype.setDates", accessDate, this._path);
+ modificationDate = Date_to_FILETIME("File.prototype.setDates",
+ modificationDate,
+ this._path);
+ throw_on_zero("setDates",
+ WinFile.SetFileTime(this.fd, null, accessDate.address(),
+ modificationDate.address()),
+ this._path);
+ };
+
+ /**
+ * Set the file's access permission bits.
+ */
+ File.prototype.setPermissions = function setPermissions(options = {}) {
+ if (!("winAttributes" in options)) {
+ return;
+ }
+ let oldAttributes = WinFile.GetFileAttributes(this._path);
+ if (oldAttributes == Const.INVALID_FILE_ATTRIBUTES) {
+ throw new File.Error("setPermissions", ctypes.winLastError, this._path);
+ }
+ let newAttributes = toFileAttributes(options.winAttributes, oldAttributes);
+ throw_on_zero("setPermissions",
+ WinFile.SetFileAttributes(this._path, newAttributes),
+ this._path);
+ };
+
+ /**
+ * Flushes the file's buffers and causes all buffered data
+ * to be written.
+ * Disk flushes are very expensive and therefore should be used carefully,
+ * sparingly and only in scenarios where it is vital that data survives
+ * system crashes. Even though the function will be executed off the
+ * main-thread, it might still affect the overall performance of any
+ * running application.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype.flush = function flush() {
+ throw_on_zero("flush", WinFile.FlushFileBuffers(this.fd), this._path);
+ };
+
+ // The default sharing mode for opening files: files are not
+ // locked against being reopened for reading/writing or against
+ // being deleted by the same process or another process.
+ // This is consistent with the default Unix policy.
+ const DEFAULT_SHARE = Const.FILE_SHARE_READ |
+ Const.FILE_SHARE_WRITE | Const.FILE_SHARE_DELETE;
+
+ // The default flags for opening files.
+ const DEFAULT_FLAGS = Const.FILE_ATTRIBUTE_NORMAL;
+
+ /**
+ * Open a file
+ *
+ * @param {string} path The path to the file.
+ * @param {*=} mode The opening mode for the file, as
+ * an object that may contain the following fields:
+ *
+ * - {bool} truncate If |true|, the file will be opened
+ * for writing. If the file does not exist, it will be
+ * created. If the file exists, its contents will be
+ * erased. Cannot be specified with |create|.
+ * - {bool} create If |true|, the file will be opened
+ * for writing. If the file exists, this function fails.
+ * If the file does not exist, it will be created. Cannot
+ * be specified with |truncate| or |existing|.
+ * - {bool} existing. If the file does not exist, this function
+ * fails. Cannot be specified with |create|.
+ * - {bool} read If |true|, the file will be opened for
+ * reading. The file may also be opened for writing, depending
+ * on the other fields of |mode|.
+ * - {bool} write If |true|, the file will be opened for
+ * writing. The file may also be opened for reading, depending
+ * on the other fields of |mode|.
+ * - {bool} append If |true|, the file will be opened for appending,
+ * meaning the equivalent of |.setPosition(0, POS_END)| is executed
+ * before each write. The default is |true|, i.e. opening a file for
+ * appending. Specify |append: false| to open the file in regular mode.
+ *
+ * If neither |truncate|, |create| or |write| is specified, the file
+ * is opened for reading.
+ *
+ * Note that |false|, |null| or |undefined| flags are simply ignored.
+ *
+ * @param {*=} options Additional options for file opening. This
+ * implementation interprets the following fields:
+ *
+ * - {number} winShare If specified, a share mode, as per
+ * Windows function |CreateFile|. You can build it from
+ * constants |OS.Constants.Win.FILE_SHARE_*|. If unspecified,
+ * the file uses the default sharing policy: it can be opened
+ * for reading and/or writing and it can be removed by other
+ * processes and by the same process.
+ * - {number} winSecurity If specified, Windows security
+ * attributes, as per Windows function |CreateFile|. If unspecified,
+ * no security attributes.
+ * - {number} winAccess If specified, Windows access mode, as
+ * per Windows function |CreateFile|. This also requires option
+ * |winDisposition| and this replaces argument |mode|. If unspecified,
+ * uses the string |mode|.
+ * - {number} winDisposition If specified, Windows disposition mode,
+ * as per Windows function |CreateFile|. This also requires option
+ * |winAccess| and this replaces argument |mode|. If unspecified,
+ * uses the string |mode|.
+ *
+ * @return {File} A file object.
+ * @throws {OS.File.Error} If the file could not be opened.
+ */
+ File.open = function Win_open(path, mode = {}, options = {}) {
+ let share = options.winShare !== undefined ? options.winShare : DEFAULT_SHARE;
+ let security = options.winSecurity || null;
+ let flags = options.winFlags !== undefined ? options.winFlags : DEFAULT_FLAGS;
+ let template = options.winTemplate ? options.winTemplate._fd : null;
+ let access;
+ let disposition;
+
+ mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
+
+ // The following option isn't a generic implementation of access to paths
+ // of arbitrary lengths. It allows for the specific case of writing to an
+ // Alternate Data Stream on a file whose path length is already close to
+ // MAX_PATH. This implementation is safe with a full path as input, if
+ // the first part of the path comes from local configuration and the
+ // file without the ADS was successfully opened before, so we know the
+ // path is valid.
+ if (options.winAllowLengthBeyondMaxPathWithCaveats) {
+ // Use the \\?\ syntax to allow lengths beyond MAX_PATH. This limited
+ // implementation only supports a DOS local path or UNC path as input.
+ let isUNC = path.length >= 2 && (path[0] == "\\" || path[0] == "/") &&
+ (path[1] == "\\" || path[1] == "/");
+ let pathToUse = "\\\\?\\" + (isUNC ? "UNC\\" + path.slice(2) : path);
+ // Use GetFullPathName to normalize slashes into backslashes. This is
+ // required because CreateFile won't do this for the \\?\ syntax.
+ let buffer_size = 512;
+ let array = new (ctypes.ArrayType(ctypes.char16_t, buffer_size))();
+ let expected_size = throw_on_zero("open",
+ WinFile.GetFullPathName(pathToUse, buffer_size, array, 0)
+ );
+ if (expected_size > buffer_size) {
+ // We don't need to allow an arbitrary path length for now.
+ throw new File.Error("open", ctypes.winLastError, path);
+ }
+ path = array.readString();
+ }
+
+ if ("winAccess" in options && "winDisposition" in options) {
+ access = options.winAccess;
+ disposition = options.winDisposition;
+ } else if (("winAccess" in options && !("winDisposition" in options))
+ ||(!("winAccess" in options) && "winDisposition" in options)) {
+ throw new TypeError("OS.File.open requires either both options " +
+ "winAccess and winDisposition or neither");
+ } else {
+ if (mode.read) {
+ access |= Const.GENERIC_READ;
+ }
+ if (mode.write) {
+ access |= Const.GENERIC_WRITE;
+ }
+ // Finally, handle create/existing/trunc
+ if (mode.trunc) {
+ if (mode.existing) {
+ // It seems that Const.TRUNCATE_EXISTING is broken
+ // in presence of links (source, anyone?). We need
+ // to open normally, then perform truncation manually.
+ disposition = Const.OPEN_EXISTING;
+ } else {
+ disposition = Const.CREATE_ALWAYS;
+ }
+ } else if (mode.create) {
+ disposition = Const.CREATE_NEW;
+ } else if (mode.read && !mode.write) {
+ disposition = Const.OPEN_EXISTING;
+ } else if (mode.existing) {
+ disposition = Const.OPEN_EXISTING;
+ } else {
+ disposition = Const.OPEN_ALWAYS;
+ }
+ }
+
+ let file = error_or_file(WinFile.CreateFile(path,
+ access, share, security, disposition, flags, template), path);
+
+ file._appendMode = !!mode.append;
+
+ if (!(mode.trunc && mode.existing)) {
+ return file;
+ }
+ // Now, perform manual truncation
+ file.setPosition(0, File.POS_START);
+ throw_on_zero("open",
+ WinFile.SetEndOfFile(file.fd),
+ path);
+ return file;
+ };
+
+ /**
+ * Checks if a file or directory exists
+ *
+ * @param {string} path The path to the file.
+ *
+ * @return {bool} true if the file exists, false otherwise.
+ */
+ File.exists = function Win_exists(path) {
+ try {
+ let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
+ file.close();
+ return true;
+ } catch (x) {
+ return false;
+ }
+ };
+
+ /**
+ * Remove an existing file.
+ *
+ * @param {string} path The name of the file.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the file does
+ * not exist. |true| by default.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.remove = function remove(path, options = {}) {
+ if (WinFile.DeleteFile(path)) {
+ return;
+ }
+
+ if (ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND ||
+ ctypes.winLastError == Const.ERROR_PATH_NOT_FOUND) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent)) {
+ return;
+ }
+ } else if (ctypes.winLastError == Const.ERROR_ACCESS_DENIED) {
+ // Save winLastError before another ctypes call.
+ let lastError = ctypes.winLastError;
+ let attributes = WinFile.GetFileAttributes(path);
+ if (attributes != Const.INVALID_FILE_ATTRIBUTES) {
+ if (!(attributes & Const.FILE_ATTRIBUTE_READONLY)) {
+ throw new File.Error("remove", lastError, path);
+ }
+ let newAttributes = attributes & ~Const.FILE_ATTRIBUTE_READONLY;
+ if (WinFile.SetFileAttributes(path, newAttributes) &&
+ WinFile.DeleteFile(path)) {
+ return;
+ }
+ }
+ }
+
+ throw new File.Error("remove", ctypes.winLastError, path);
+ };
+
+ /**
+ * Remove an empty directory.
+ *
+ * @param {string} path The name of the directory to remove.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory
+ * does not exist. |true| by default
+ */
+ File.removeEmptyDir = function removeEmptyDir(path, options = {}) {
+ let result = WinFile.RemoveDirectory(path);
+ if (!result) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
+ return;
+ }
+ throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
+ }
+ };
+
+ /**
+ * Create a directory and, optionally, its parent directories.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options. This
+ * implementation interprets the following fields:
+ *
+ * - {C pointer} winSecurity If specified, security attributes
+ * as per winapi function |CreateDirectory|. If unspecified,
+ * use the default security descriptor, inherited from the
+ * parent directory.
+ * - {bool} ignoreExisting If |false|, throw an error if the directory
+ * already exists. |true| by default
+ * - {string} from If specified, the call to |makeDir| creates all the
+ * ancestors of |path| that are descendants of |from|. Note that |from|
+ * and its existing descendants must be user-writeable and that |path|
+ * must be a descendant of |from|.
+ * Example:
+ * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+ * creates directories profileDir/foo, profileDir/foo/bar
+ */
+ File._makeDir = function makeDir(path, options = {}) {
+ let security = options.winSecurity || null;
+ let result = WinFile.CreateDirectory(path, security);
+
+ if (result) {
+ return;
+ }
+
+ if (("ignoreExisting" in options) && !options.ignoreExisting) {
+ throw new File.Error("makeDir", ctypes.winLastError, path);
+ }
+
+ if (ctypes.winLastError == Const.ERROR_ALREADY_EXISTS) {
+ return;
+ }
+
+ // If the user has no access, but it's a root directory, no error should be thrown
+ let splitPath = OS.Path.split(path);
+ // Removing last component if it's empty
+ // An empty last component is caused by trailing slashes in path
+ // This is always the case with root directories
+ if( splitPath.components[splitPath.components.length - 1].length === 0 ) {
+ splitPath.components.pop();
+ }
+ // One component consisting of a drive letter implies a directory root.
+ if (ctypes.winLastError == Const.ERROR_ACCESS_DENIED &&
+ splitPath.winDrive &&
+ splitPath.components.length === 1 ) {
+ return;
+ }
+
+ throw new File.Error("makeDir", ctypes.winLastError, path);
+ };
+
+ /**
+ * Copy a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be copied.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If true, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be copied with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.copy = function copy(sourcePath, destPath, options = {}) {
+ throw_on_zero("copy",
+ WinFile.CopyFile(sourcePath, destPath, options.noOverwrite || false),
+ sourcePath
+ );
+ };
+
+ /**
+ * Move a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be moved.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ * @option {bool} noCopy - If set, this function will fail if the
+ * operation is more sophisticated than a simple renaming, i.e. if
+ * |sourcePath| and |destPath| are not situated on the same drive.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be moved with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.move = function move(sourcePath, destPath, options = {}) {
+ let flags = 0;
+ if (!options.noCopy) {
+ flags = Const.MOVEFILE_COPY_ALLOWED;
+ }
+ if (!options.noOverwrite) {
+ flags = flags | Const.MOVEFILE_REPLACE_EXISTING;
+ }
+ throw_on_zero("move",
+ WinFile.MoveFileEx(sourcePath, destPath, flags),
+ sourcePath
+ );
+
+ // Inherit NTFS permissions from the destination directory
+ // if possible.
+ if (Path.dirname(sourcePath) === Path.dirname(destPath)) {
+ // Skip if the move operation was the simple rename,
+ return;
+ }
+ // The function may fail for various reasons (e.g. not all
+ // filesystems support NTFS permissions or the user may not
+ // have the enough rights to read/write permissions).
+ // However we can safely ignore errors. The file was already
+ // moved. Setting permissions is not mandatory.
+ let dacl = new ctypes.voidptr_t();
+ let sd = new ctypes.voidptr_t();
+ WinFile.GetNamedSecurityInfo(destPath, Const.SE_FILE_OBJECT,
+ Const.DACL_SECURITY_INFORMATION,
+ null /*sidOwner*/, null /*sidGroup*/,
+ dacl.address(), null /*sacl*/,
+ sd.address());
+ // dacl will be set only if the function succeeds.
+ if (!dacl.isNull()) {
+ WinFile.SetNamedSecurityInfo(destPath, Const.SE_FILE_OBJECT,
+ Const.DACL_SECURITY_INFORMATION |
+ Const.UNPROTECTED_DACL_SECURITY_INFORMATION,
+ null /*sidOwner*/, null /*sidGroup*/,
+ dacl, null /*sacl*/);
+ }
+ // sd will be set only if the function succeeds.
+ if (!sd.isNull()) {
+ WinFile.LocalFree(Type.HLOCAL.cast(sd));
+ }
+ };
+
+ /**
+ * Gets the number of bytes available on disk to the current user.
+ *
+ * @param {string} sourcePath Platform-specific path to a directory on
+ * the disk to query for free available bytes.
+ *
+ * @return {number} The number of bytes available for the current user.
+ * @throws {OS.File.Error} In case of any error.
+ */
+ File.getAvailableFreeSpace = function Win_getAvailableFreeSpace(sourcePath) {
+ let freeBytesAvailableToUser = new Type.uint64_t.implementation(0);
+ let freeBytesAvailableToUserPtr = freeBytesAvailableToUser.address();
+
+ throw_on_zero("getAvailableFreeSpace",
+ WinFile.GetDiskFreeSpaceEx(sourcePath, freeBytesAvailableToUserPtr, null, null)
+ );
+
+ return freeBytesAvailableToUser.value;
+ };
+
+ /**
+ * A global value used to receive data during time conversions.
+ */
+ let gSystemTime = new Type.SystemTime.implementation();
+ let gSystemTimePtr = gSystemTime.address();
+
+ /**
+ * Utility function: convert a FILETIME to a JavaScript Date.
+ */
+ let FILETIME_to_Date = function FILETIME_to_Date(fileTime, path) {
+ if (fileTime == null) {
+ throw new TypeError("Expecting a non-null filetime");
+ }
+ throw_on_zero("FILETIME_to_Date",
+ WinFile.FileTimeToSystemTime(fileTime.address(),
+ gSystemTimePtr),
+ path);
+ // Windows counts hours, minutes, seconds from UTC,
+ // JS counts from local time, so we need to go through UTC.
+ let utc = Date.UTC(gSystemTime.wYear,
+ gSystemTime.wMonth - 1
+ /*Windows counts months from 1, JS from 0*/,
+ gSystemTime.wDay, gSystemTime.wHour,
+ gSystemTime.wMinute, gSystemTime.wSecond,
+ gSystemTime.wMilliSeconds);
+ return new Date(utc);
+ };
+
+ /**
+ * Utility function: convert Javascript Date to FileTime.
+ *
+ * @param {string} fn Name of the calling function.
+ * @param {Date,number} date The date to be converted. If omitted or null,
+ * then the current date will be used. If numeric, assumed to be the date
+ * in milliseconds since epoch.
+ */
+ let Date_to_FILETIME = function Date_to_FILETIME(fn, date, path) {
+ if (typeof date === "number") {
+ date = new Date(date);
+ } else if (!date) {
+ date = new Date();
+ } else if (typeof date.getUTCFullYear !== "function") {
+ throw new TypeError("|date| parameter of " + fn + " must be a " +
+ "|Date| instance or number");
+ }
+ gSystemTime.wYear = date.getUTCFullYear();
+ // Windows counts months from 1, JS from 0.
+ gSystemTime.wMonth = date.getUTCMonth() + 1;
+ gSystemTime.wDay = date.getUTCDate();
+ gSystemTime.wHour = date.getUTCHours();
+ gSystemTime.wMinute = date.getUTCMinutes();
+ gSystemTime.wSecond = date.getUTCSeconds();
+ gSystemTime.wMilliseconds = date.getUTCMilliseconds();
+ let result = new OS.Shared.Type.FILETIME.implementation();
+ throw_on_zero("Date_to_FILETIME",
+ WinFile.SystemTimeToFileTime(gSystemTimePtr,
+ result.address()),
+ path);
+ return result;
+ };
+
+ /**
+ * Iterate on one directory.
+ *
+ * This iterator will not enter subdirectories.
+ *
+ * @param {string} path The directory upon which to iterate.
+ * @param {*=} options An object that may contain the following field:
+ * @option {string} winPattern Windows file name pattern; if set,
+ * only files matching this pattern are returned.
+ *
+ * @throws {File.Error} If |path| does not represent a directory or
+ * if the directory cannot be iterated.
+ * @constructor
+ */
+ File.DirectoryIterator = function DirectoryIterator(path, options) {
+ exports.OS.Shared.AbstractFile.AbstractIterator.call(this);
+ if (options && options.winPattern) {
+ this._pattern = path + "\\" + options.winPattern;
+ } else {
+ this._pattern = path + "\\*";
+ }
+ this._path = path;
+
+ // Pre-open the first item.
+ this._first = true;
+ this._findData = new Type.FindData.implementation();
+ this._findDataPtr = this._findData.address();
+ this._handle = WinFile.FindFirstFile(this._pattern, this._findDataPtr);
+ if (this._handle == Const.INVALID_HANDLE_VALUE) {
+ let error = ctypes.winLastError;
+ this._findData = null;
+ this._findDataPtr = null;
+ if (error == Const.ERROR_FILE_NOT_FOUND) {
+ // Directory is empty, let's behave as if it were closed
+ SharedAll.LOG("Directory is empty");
+ this._closed = true;
+ this._exists = true;
+ } else if (error == Const.ERROR_PATH_NOT_FOUND) {
+ // Directory does not exist, let's throw if we attempt to walk it
+ SharedAll.LOG("Directory does not exist");
+ this._closed = true;
+ this._exists = false;
+ } else {
+ throw new File.Error("DirectoryIterator", error, this._path);
+ }
+ } else {
+ this._closed = false;
+ this._exists = true;
+ }
+ };
+
+ File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype);
+
+
+ /**
+ * Fetch the next entry in the directory.
+ *
+ * @return null If we have reached the end of the directory.
+ */
+ File.DirectoryIterator.prototype._next = function _next() {
+ // Bailout if the directory does not exist
+ if (!this._exists) {
+ throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path);
+ }
+ // Bailout if the iterator is closed.
+ if (this._closed) {
+ return null;
+ }
+ // If this is the first entry, we have obtained it already
+ // during construction.
+ if (this._first) {
+ this._first = false;
+ return this._findData;
+ }
+
+ if (WinFile.FindNextFile(this._handle, this._findDataPtr)) {
+ return this._findData;
+ } else {
+ let error = ctypes.winLastError;
+ this.close();
+ if (error == Const.ERROR_NO_MORE_FILES) {
+ return null;
+ } else {
+ throw new File.Error("iter (FindNextFile)", error, this._path);
+ }
+ }
+ },
+
+ /**
+ * Return the next entry in the directory, if any such entry is
+ * available.
+ *
+ * Skip special directories "." and "..".
+ *
+ * @return {File.Entry} The next entry in the directory.
+ * @throws {StopIteration} Once all files in the directory have been
+ * encountered.
+ */
+ File.DirectoryIterator.prototype.next = function next() {
+ // FIXME: If we start supporting "\\?\"-prefixed paths, do not forget
+ // that "." and ".." are absolutely normal file names if _path starts
+ // with such prefix
+ for (let entry = this._next(); entry != null; entry = this._next()) {
+ let name = entry.cFileName.readString();
+ if (name == "." || name == "..") {
+ continue;
+ }
+ return new File.DirectoryIterator.Entry(entry, this._path);
+ }
+ throw StopIteration;
+ };
+
+ File.DirectoryIterator.prototype.close = function close() {
+ if (this._closed) {
+ return;
+ }
+ this._closed = true;
+ if (this._handle) {
+ // We might not have a handle if the iterator is closed
+ // before being used.
+ throw_on_zero("FindClose",
+ WinFile.FindClose(this._handle),
+ this._path);
+ this._handle = null;
+ }
+ };
+
+ /**
+ * Determine whether the directory exists.
+ *
+ * @return {boolean}
+ */
+ File.DirectoryIterator.prototype.exists = function exists() {
+ return this._exists;
+ };
+
+ File.DirectoryIterator.Entry = function Entry(win_entry, parent) {
+ if (!win_entry.dwFileAttributes || !win_entry.ftCreationTime ||
+ !win_entry.ftLastAccessTime || !win_entry.ftLastWriteTime)
+ throw new TypeError();
+
+ // Copy the relevant part of |win_entry| to ensure that
+ // our data is not overwritten prematurely.
+ let isDir = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
+ let isSymLink = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);
+
+ let winCreationDate = FILETIME_to_Date(win_entry.ftCreationTime, this._path);
+ let winLastWriteDate = FILETIME_to_Date(win_entry.ftLastWriteTime, this._path);
+ let winLastAccessDate = FILETIME_to_Date(win_entry.ftLastAccessTime, this._path);
+
+ let name = win_entry.cFileName.readString();
+ if (!name) {
+ throw new TypeError("Empty name");
+ }
+
+ if (!parent) {
+ throw new TypeError("Empty parent");
+ }
+ this._parent = parent;
+
+ let path = Path.join(this._parent, name);
+
+ SysAll.AbstractEntry.call(this, isDir, isSymLink, name,
+ winCreationDate, winLastWriteDate,
+ winLastAccessDate, path);
+ };
+ File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
+
+ /**
+ * Return a version of an instance of
+ * File.DirectoryIterator.Entry that can be sent from a worker
+ * thread to the main thread. Note that deserialization is
+ * asymmetric and returns an object with a different
+ * implementation.
+ */
+ File.DirectoryIterator.Entry.toMsg = function toMsg(value) {
+ if (!(value instanceof File.DirectoryIterator.Entry)) {
+ throw new TypeError("parameter of " +
+ "File.DirectoryIterator.Entry.toMsg must be a " +
+ "File.DirectoryIterator.Entry");
+ }
+ let serialized = {};
+ for (let key in File.DirectoryIterator.Entry.prototype) {
+ serialized[key] = value[key];
+ }
+ return serialized;
+ };
+
+
+ /**
+ * Information on a file.
+ *
+ * To obtain the latest information on a file, use |File.stat|
+ * (for an unopened file) or |File.prototype.stat| (for an
+ * already opened file).
+ *
+ * @constructor
+ */
+ File.Info = function Info(stat, path) {
+ let isDir = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
+ let isSymLink = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);
+
+ let winBirthDate = FILETIME_to_Date(stat.ftCreationTime, this._path);
+ let lastAccessDate = FILETIME_to_Date(stat.ftLastAccessTime, this._path);
+ let lastWriteDate = FILETIME_to_Date(stat.ftLastWriteTime, this._path);
+
+ let value = ctypes.UInt64.join(stat.nFileSizeHigh, stat.nFileSizeLow);
+ let size = Type.uint64_t.importFromC(value);
+ let winAttributes = {
+ readOnly: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_READONLY),
+ system: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_SYSTEM),
+ hidden: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_HIDDEN),
+ };
+
+ SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size,
+ winBirthDate, lastAccessDate, lastWriteDate, winAttributes);
+ };
+ File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype);
+
+ /**
+ * Return a version of an instance of File.Info that can be sent
+ * from a worker thread to the main thread. Note that deserialization
+ * is asymmetric and returns an object with a different implementation.
+ */
+ File.Info.toMsg = function toMsg(stat) {
+ if (!(stat instanceof File.Info)) {
+ throw new TypeError("parameter of File.Info.toMsg must be a File.Info");
+ }
+ let serialized = {};
+ for (let key in File.Info.prototype) {
+ serialized[key] = stat[key];
+ }
+ return serialized;
+ };
+
+
+ /**
+ * Fetch the information on a file.
+ *
+ * Performance note: if you have opened the file already,
+ * method |File.prototype.stat| is generally much faster
+ * than method |File.stat|.
+ *
+ * Platform-specific note: under Windows, if the file is
+ * already opened without sharing of the read capability,
+ * this function will fail.
+ *
+ * @return {File.Information}
+ */
+ File.stat = function stat(path) {
+ let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
+ try {
+ return file.stat();
+ } finally {
+ file.close();
+ }
+ };
+ // All of the following is required to ensure that File.stat
+ // also works on directories.
+ const FILE_STAT_MODE = {
+ read: true
+ };
+ const FILE_STAT_OPTIONS = {
+ // Directories can be opened neither for reading(!) nor for writing
+ winAccess: 0,
+ // Directories can only be opened with backup semantics(!)
+ winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
+ winDisposition: Const.OPEN_EXISTING
+ };
+
+ /**
+ * Set the file's access permission bits.
+ */
+ File.setPermissions = function setPermissions(path, options = {}) {
+ if (!("winAttributes" in options)) {
+ return;
+ }
+ let oldAttributes = WinFile.GetFileAttributes(path);
+ if (oldAttributes == Const.INVALID_FILE_ATTRIBUTES) {
+ throw new File.Error("setPermissions", ctypes.winLastError, path);
+ }
+ let newAttributes = toFileAttributes(options.winAttributes, oldAttributes);
+ throw_on_zero("setPermissions",
+ WinFile.SetFileAttributes(path, newAttributes),
+ path);
+ };
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * Performance note: if you have opened the file already in write mode,
+ * method |File.prototype.stat| is generally much faster
+ * than method |File.stat|.
+ *
+ * Platform-specific note: under Windows, if the file is
+ * already opened without sharing of the write capability,
+ * this function will fail.
+ *
+ * @param {string} path The full name of the file to set the dates for.
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid paramters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.setDates = function setDates(path, accessDate, modificationDate) {
+ let file = File.open(path, FILE_SETDATES_MODE, FILE_SETDATES_OPTIONS);
+ try {
+ return file.setDates(accessDate, modificationDate);
+ } finally {
+ file.close();
+ }
+ };
+ // All of the following is required to ensure that File.setDates
+ // also works on directories.
+ const FILE_SETDATES_MODE = {
+ write: true
+ };
+ const FILE_SETDATES_OPTIONS = {
+ winAccess: Const.GENERIC_WRITE,
+ // Directories can only be opened with backup semantics(!)
+ winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
+ winDisposition: Const.OPEN_EXISTING
+ };
+
+ File.read = exports.OS.Shared.AbstractFile.read;
+ File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
+ File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
+ File.makeDir = exports.OS.Shared.AbstractFile.makeDir;
+
+ /**
+ * Remove an existing directory and its contents.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
+ * exist. |true| by default.
+ * - {boolean} ignorePermissions If |true|, remove the file even when lacking write
+ * permission.
+ *
+ * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
+ * not a directory.
+ */
+ File.removeDir = function(path, options = {}) {
+ // We can't use File.stat here because it will follow the symlink.
+ let attributes = WinFile.GetFileAttributes(path);
+ if (attributes == Const.INVALID_FILE_ATTRIBUTES) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
+ return;
+ }
+ throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
+ }
+ if (attributes & Const.FILE_ATTRIBUTE_REPARSE_POINT) {
+ // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
+ // directories are directories themselves. OS.File.remove()
+ // will not work for them.
+ OS.File.removeEmptyDir(path, options);
+ return;
+ }
+ exports.OS.Shared.AbstractFile.removeRecursive(path, options);
+ };
+
+ /**
+ * Get the current directory by getCurrentDirectory.
+ */
+ File.getCurrentDirectory = function getCurrentDirectory() {
+ // This function is more complicated than one could hope.
+ //
+ // This is due to two facts:
+ // - the maximal length of a path under Windows is not completely
+ // specified (there is a constant MAX_PATH, but it is quite possible
+ // to create paths that are much larger, see bug 744413);
+ // - if we attempt to call |GetCurrentDirectory| with a buffer that
+ // is too short, it returns the length of the current directory, but
+ // this length might be insufficient by the time we can call again
+ // the function with a larger buffer, in the (unlikely but possible)
+ // case in which the process changes directory to a directory with
+ // a longer name between both calls.
+ //
+ let buffer_size = 4096;
+ while (true) {
+ let array = new (ctypes.ArrayType(ctypes.char16_t, buffer_size))();
+ let expected_size = throw_on_zero("getCurrentDirectory",
+ WinFile.GetCurrentDirectory(buffer_size, array)
+ );
+ if (expected_size <= buffer_size) {
+ return array.readString();
+ }
+ // At this point, we are in a case in which our buffer was not
+ // large enough to hold the name of the current directory.
+ // Consequently, we need to increase the size of the buffer.
+ // Note that, even in crazy scenarios, the loop will eventually
+ // converge, as the length of the paths cannot increase infinitely.
+ buffer_size = expected_size + 1 /* to store \0 */;
+ }
+ };
+
+ /**
+ * Set the current directory by setCurrentDirectory.
+ */
+ File.setCurrentDirectory = function setCurrentDirectory(path) {
+ throw_on_zero("setCurrentDirectory",
+ WinFile.SetCurrentDirectory(path),
+ path);
+ };
+
+ /**
+ * Get/set the current directory by |curDir|.
+ */
+ Object.defineProperty(File, "curDir", {
+ set: function(path) {
+ this.setCurrentDirectory(path);
+ },
+ get: function() {
+ return this.getCurrentDirectory();
+ }
+ }
+ );
+
+ // Utility functions, used for error-handling
+
+ /**
+ * Turn the result of |open| into an Error or a File
+ * @param {number} maybe The result of the |open| operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns the opened file.
+ * @param {string=} path The path of the file.
+ */
+ function error_or_file(maybe, path) {
+ if (maybe == Const.INVALID_HANDLE_VALUE) {
+ throw new File.Error("open", ctypes.winLastError, path);
+ }
+ return new File(maybe, path);
+ }
+
+ /**
+ * Utility function to sort errors represented as "0" from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {number} result The result of the operation that may
+ * represent either an error or a success. If 0, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_zero(operation, result, path) {
+ if (result == 0) {
+ throw new File.Error(operation, ctypes.winLastError, path);
+ }
+ return result;
+ }
+
+ /**
+ * Utility function to sort errors represented as "-1" from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {number} result The result of the operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_negative(operation, result, path) {
+ if (result < 0) {
+ throw new File.Error(operation, ctypes.winLastError, path);
+ }
+ return result;
+ }
+
+ /**
+ * Utility function to sort errors represented as |null| from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {pointer} result The result of the operation that may
+ * represent either an error or a success. If |null|, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_null(operation, result, path) {
+ if (result == null || (result.isNull && result.isNull())) {
+ throw new File.Error(operation, ctypes.winLastError, path);
+ }
+ return result;
+ }
+
+ /**
+ * Helper used by both versions of setPermissions
+ */
+ function toFileAttributes(winAttributes, oldDwAttrs) {
+ if ("readOnly" in winAttributes) {
+ if (winAttributes.readOnly) {
+ oldDwAttrs |= Const.FILE_ATTRIBUTE_READONLY;
+ } else {
+ oldDwAttrs &= ~Const.FILE_ATTRIBUTE_READONLY;
+ }
+ }
+ if ("system" in winAttributes) {
+ if (winAttributes.system) {
+ oldDwAttrs |= Const.FILE_ATTRIBUTE_SYSTEM;
+ } else {
+ oldDwAttrs &= ~Const.FILE_ATTRIBUTE_SYSTEM;
+ }
+ }
+ if ("hidden" in winAttributes) {
+ if (winAttributes.hidden) {
+ oldDwAttrs |= Const.FILE_ATTRIBUTE_HIDDEN;
+ } else {
+ oldDwAttrs &= ~Const.FILE_ATTRIBUTE_HIDDEN;
+ }
+ }
+ return oldDwAttrs;
+ }
+
+ File.Win = exports.OS.Win.File;
+ File.Error = SysAll.Error;
+ exports.OS.File = File;
+ exports.OS.Shared.Type = Type;
+
+ Object.defineProperty(File, "POS_START", { value: SysAll.POS_START });
+ Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT });
+ Object.defineProperty(File, "POS_END", { value: SysAll.POS_END });
+ })(this);
+}
diff --git a/components/osfile/modules/ospath.jsm b/components/osfile/modules/ospath.jsm
new file mode 100644
index 000000000..68bbe4345
--- /dev/null
+++ b/components/osfile/modules/ospath.jsm
@@ -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/. */
+
+/**
+ * Handling native paths.
+ *
+ * This module contains a number of functions destined to simplify
+ * working with native paths through a cross-platform API. Functions
+ * of this module will only work with the following assumptions:
+ *
+ * - paths are valid;
+ * - paths are defined with one of the grammars that this module can
+ * parse (see later);
+ * - all path concatenations go through function |join|.
+ */
+
+"use strict";
+
+if (typeof Components == "undefined") {
+ let Path;
+ if (OS.Constants.Win) {
+ Path = require("resource://gre/modules/osfile/ospath_win.jsm");
+ } else {
+ Path = require("resource://gre/modules/osfile/ospath_unix.jsm");
+ }
+ module.exports = Path;
+} else {
+ let Cu = Components.utils;
+ let Scope = {};
+ Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", Scope);
+
+ let Path = {};
+ if (Scope.OS.Constants.Win) {
+ Cu.import("resource://gre/modules/osfile/ospath_win.jsm", Path);
+ } else {
+ Cu.import("resource://gre/modules/osfile/ospath_unix.jsm", Path);
+ }
+
+ this.EXPORTED_SYMBOLS = [];
+ for (let k in Path) {
+ this.EXPORTED_SYMBOLS.push(k);
+ this[k] = Path[k];
+ }
+}
diff --git a/components/osfile/modules/ospath_unix.jsm b/components/osfile/modules/ospath_unix.jsm
new file mode 100644
index 000000000..1d574baed
--- /dev/null
+++ b/components/osfile/modules/ospath_unix.jsm
@@ -0,0 +1,202 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Handling native paths.
+ *
+ * This module contains a number of functions destined to simplify
+ * working with native paths through a cross-platform API. Functions
+ * of this module will only work with the following assumptions:
+ *
+ * - paths are valid;
+ * - paths are defined with one of the grammars that this module can
+ * parse (see later);
+ * - all path concatenations go through function |join|.
+ */
+
+"use strict";
+
+// Boilerplate used to be able to import this module both from the main
+// thread and from worker threads.
+if (typeof Components != "undefined") {
+ Components.utils.importGlobalProperties(["URL"]);
+ // Global definition of |exports|, to keep everybody happy.
+ // In non-main thread, |exports| is provided by the module
+ // loader.
+ this.exports = {};
+} else if (typeof module == "undefined" || typeof exports == "undefined") {
+ throw new Error("Please load this module using require()");
+}
+
+var EXPORTED_SYMBOLS = [
+ "basename",
+ "dirname",
+ "join",
+ "normalize",
+ "split",
+ "toFileURI",
+ "fromFileURI",
+];
+
+/**
+ * Return the final part of the path.
+ * The final part of the path is everything after the last "/".
+ */
+var basename = function(path) {
+ return path.slice(path.lastIndexOf("/") + 1);
+};
+exports.basename = basename;
+
+/**
+ * Return the directory part of the path.
+ * The directory part of the path is everything before the last
+ * "/". If the last few characters of this part are also "/",
+ * they are ignored.
+ *
+ * If the path contains no directory, return ".".
+ */
+var dirname = function(path) {
+ let index = path.lastIndexOf("/");
+ if (index == -1) {
+ return ".";
+ }
+ while (index >= 0 && path[index] == "/") {
+ --index;
+ }
+ return path.slice(0, index + 1);
+};
+exports.dirname = dirname;
+
+/**
+ * Join path components.
+ * This is the recommended manner of getting the path of a file/subdirectory
+ * in a directory.
+ *
+ * Example: Obtaining $TMP/foo/bar in an OS-independent manner
+ * var tmpDir = OS.Constants.Path.tmpDir;
+ * var path = OS.Path.join(tmpDir, "foo", "bar");
+ *
+ * Under Unix, this will return "/tmp/foo/bar".
+ *
+ * Empty components are ignored, i.e. `OS.Path.join("foo", "", "bar)` is the
+ * same as `OS.Path.join("foo", "bar")`.
+ */
+var join = function(...path) {
+ // If there is a path that starts with a "/", eliminate everything before
+ let paths = [];
+ for (let subpath of path) {
+ if (subpath == null) {
+ throw new TypeError("invalid path component");
+ }
+ if (subpath.length == 0) {
+ continue;
+ } else if (subpath[0] == "/") {
+ paths = [subpath];
+ } else {
+ paths.push(subpath);
+ }
+ }
+ return paths.join("/");
+};
+exports.join = join;
+
+/**
+ * Normalize a path by removing any unneeded ".", "..", "//".
+ */
+var normalize = function(path) {
+ let stack = [];
+ let absolute;
+ if (path.length >= 0 && path[0] == "/") {
+ absolute = true;
+ } else {
+ absolute = false;
+ }
+ path.split("/").forEach(function(v) {
+ switch (v) {
+ case "": case ".":// fallthrough
+ break;
+ case "..":
+ if (stack.length == 0) {
+ if (absolute) {
+ throw new Error("Path is ill-formed: attempting to go past root");
+ } else {
+ stack.push("..");
+ }
+ } else {
+ if (stack[stack.length - 1] == "..") {
+ stack.push("..");
+ } else {
+ stack.pop();
+ }
+ }
+ break;
+ default:
+ stack.push(v);
+ }
+ });
+ let string = stack.join("/");
+ return absolute ? "/" + string : string;
+};
+exports.normalize = normalize;
+
+/**
+ * Return the components of a path.
+ * You should generally apply this function to a normalized path.
+ *
+ * @return {{
+ * {bool} absolute |true| if the path is absolute, |false| otherwise
+ * {array} components the string components of the path
+ * }}
+ *
+ * Other implementations may add additional OS-specific informations.
+ */
+var split = function(path) {
+ return {
+ absolute: path.length && path[0] == "/",
+ components: path.split("/")
+ };
+};
+exports.split = split;
+
+/**
+ * Returns the file:// URI file path of the given local file path.
+ */
+// The case of %3b is designed to match Services.io, but fundamentally doesn't matter.
+var toFileURIExtraEncodings = {';': '%3b', '?': '%3F', '#': '%23'};
+var toFileURI = function toFileURI(path) {
+ // Per https://url.spec.whatwg.org we should not encode [] in the path
+ let dontNeedEscaping = {'%5B': '[', '%5D': ']'};
+ let uri = encodeURI(this.normalize(path)).replace(/%(5B|5D)/gi,
+ match => dontNeedEscaping[match]);
+
+ // add a prefix, and encodeURI doesn't escape a few characters that we do
+ // want to escape, so fix that up
+ let prefix = "file://";
+ uri = prefix + uri.replace(/[;?#]/g, match => toFileURIExtraEncodings[match]);
+
+ return uri;
+};
+exports.toFileURI = toFileURI;
+
+/**
+ * Returns the local file path from a given file URI.
+ */
+var fromFileURI = function fromFileURI(uri) {
+ let url = new URL(uri);
+ if (url.protocol != 'file:') {
+ throw new Error("fromFileURI expects a file URI");
+ }
+ let path = this.normalize(decodeURIComponent(url.pathname));
+ return path;
+};
+exports.fromFileURI = fromFileURI;
+
+
+//////////// Boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}
diff --git a/components/osfile/modules/ospath_win.jsm b/components/osfile/modules/ospath_win.jsm
new file mode 100644
index 000000000..31a87b115
--- /dev/null
+++ b/components/osfile/modules/ospath_win.jsm
@@ -0,0 +1,373 @@
+/**
+ * Handling native paths.
+ *
+ * This module contains a number of functions destined to simplify
+ * working with native paths through a cross-platform API. Functions
+ * of this module will only work with the following assumptions:
+ *
+ * - paths are valid;
+ * - paths are defined with one of the grammars that this module can
+ * parse (see later);
+ * - all path concatenations go through function |join|.
+ *
+ * Limitations of this implementation.
+ *
+ * Windows supports 6 distinct grammars for paths. For the moment, this
+ * implementation supports the following subset:
+ *
+ * - drivename:backslash-separated components
+ * - backslash-separated components
+ * - \\drivename\ followed by backslash-separated components
+ *
+ * Additionally, |normalize| can convert a path containing slash-
+ * separated components to a path containing backslash-separated
+ * components.
+ */
+
+"use strict";
+
+// Boilerplate used to be able to import this module both from the main
+// thread and from worker threads.
+if (typeof Components != "undefined") {
+ Components.utils.importGlobalProperties(["URL"]);
+ // Global definition of |exports|, to keep everybody happy.
+ // In non-main thread, |exports| is provided by the module
+ // loader.
+ this.exports = {};
+} else if (typeof module == "undefined" || typeof exports == "undefined") {
+ throw new Error("Please load this module using require()");
+}
+
+var EXPORTED_SYMBOLS = [
+ "basename",
+ "dirname",
+ "join",
+ "normalize",
+ "split",
+ "winGetDrive",
+ "winIsAbsolute",
+ "toFileURI",
+ "fromFileURI",
+];
+
+/**
+ * Return the final part of the path.
+ * The final part of the path is everything after the last "\\".
+ */
+var basename = function(path) {
+ if (path.startsWith("\\\\")) {
+ // UNC-style path
+ let index = path.lastIndexOf("\\");
+ if (index != 1) {
+ return path.slice(index + 1);
+ }
+ return ""; // Degenerate case
+ }
+ return path.slice(Math.max(path.lastIndexOf("\\"),
+ path.lastIndexOf(":")) + 1);
+};
+exports.basename = basename;
+
+/**
+ * Return the directory part of the path.
+ *
+ * If the path contains no directory, return the drive letter,
+ * or "." if the path contains no drive letter or if option
+ * |winNoDrive| is set.
+ *
+ * Otherwise, return everything before the last backslash,
+ * including the drive/server name.
+ *
+ *
+ * @param {string} path The path.
+ * @param {*=} options Platform-specific options controlling the behavior
+ * of this function. This implementation supports the following options:
+ * - |winNoDrive| If |true|, also remove the letter from the path name.
+ */
+var dirname = function(path, options) {
+ let noDrive = (options && options.winNoDrive);
+
+ // Find the last occurrence of "\\"
+ let index = path.lastIndexOf("\\");
+ if (index == -1) {
+ // If there is no directory component...
+ if (!noDrive) {
+ // Return the drive path if possible, falling back to "."
+ return this.winGetDrive(path) || ".";
+ } else {
+ // Or just "."
+ return ".";
+ }
+ }
+
+ if (index == 1 && path.charAt(0) == "\\") {
+ // The path is reduced to a UNC drive
+ if (noDrive) {
+ return ".";
+ } else {
+ return path;
+ }
+ }
+
+ // Ignore any occurrence of "\\: immediately before that one
+ while (index >= 0 && path[index] == "\\") {
+ --index;
+ }
+
+ // Compute what is left, removing the drive name if necessary
+ let start;
+ if (noDrive) {
+ start = (this.winGetDrive(path) || "").length;
+ } else {
+ start = 0;
+ }
+ return path.slice(start, index + 1);
+};
+exports.dirname = dirname;
+
+/**
+ * Join path components.
+ * This is the recommended manner of getting the path of a file/subdirectory
+ * in a directory.
+ *
+ * Example: Obtaining $TMP/foo/bar in an OS-independent manner
+ * var tmpDir = OS.Constants.Path.tmpDir;
+ * var path = OS.Path.join(tmpDir, "foo", "bar");
+ *
+ * Under Windows, this will return "$TMP\foo\bar".
+ *
+ * Empty components are ignored, i.e. `OS.Path.join("foo", "", "bar)` is the
+ * same as `OS.Path.join("foo", "bar")`.
+ */
+var join = function(...path) {
+ let paths = [];
+ let root;
+ let absolute = false;
+ for (let subpath of path) {
+ if (subpath == null) {
+ throw new TypeError("invalid path component");
+ }
+ if (subpath == "") {
+ continue;
+ }
+ let drive = this.winGetDrive(subpath);
+ if (drive) {
+ root = drive;
+ let component = trimBackslashes(subpath.slice(drive.length));
+ if (component) {
+ paths = [component];
+ } else {
+ paths = [];
+ }
+ absolute = true;
+ } else if (this.winIsAbsolute(subpath)) {
+ paths = [trimBackslashes(subpath)];
+ absolute = true;
+ } else {
+ paths.push(trimBackslashes(subpath));
+ }
+ }
+ let result = "";
+ if (root) {
+ result += root;
+ }
+ if (absolute) {
+ result += "\\";
+ }
+ result += paths.join("\\");
+ return result;
+};
+exports.join = join;
+
+/**
+ * Return the drive name of a path, or |null| if the path does
+ * not contain a drive name.
+ *
+ * Drive name appear either as "DriveName:..." (the return drive
+ * name includes the ":") or "\\\\DriveName..." (the returned drive name
+ * includes "\\\\").
+ */
+var winGetDrive = function(path) {
+ if (path == null) {
+ throw new TypeError("path is invalid");
+ }
+
+ if (path.startsWith("\\\\")) {
+ // UNC path
+ if (path.length == 2) {
+ return null;
+ }
+ let index = path.indexOf("\\", 2);
+ if (index == -1) {
+ return path;
+ }
+ return path.slice(0, index);
+ }
+ // Non-UNC path
+ let index = path.indexOf(":");
+ if (index <= 0) return null;
+ return path.slice(0, index + 1);
+};
+exports.winGetDrive = winGetDrive;
+
+/**
+ * Return |true| if the path is absolute, |false| otherwise.
+ *
+ * We consider that a path is absolute if it starts with "\\"
+ * or "driveletter:\\".
+ */
+var winIsAbsolute = function(path) {
+ let index = path.indexOf(":");
+ return path.length > index + 1 && path[index + 1] == "\\";
+};
+exports.winIsAbsolute = winIsAbsolute;
+
+/**
+ * Normalize a path by removing any unneeded ".", "..", "\\".
+ * Also convert any "/" to a "\\".
+ */
+var normalize = function(path) {
+ let stack = [];
+
+ if (!path.startsWith("\\\\")) {
+ // Normalize "/" to "\\"
+ path = path.replace(/\//g, "\\");
+ }
+
+ // Remove the drive (we will put it back at the end)
+ let root = this.winGetDrive(path);
+ if (root) {
+ path = path.slice(root.length);
+ }
+
+ // Remember whether we need to restore a leading "\\" or drive name.
+ let absolute = this.winIsAbsolute(path);
+
+ // And now, fill |stack| from the components,
+ // popping whenever there is a ".."
+ path.split("\\").forEach(function loop(v) {
+ switch (v) {
+ case "": case ".": // Ignore
+ break;
+ case "..":
+ if (stack.length == 0) {
+ if (absolute) {
+ throw new Error("Path is ill-formed: attempting to go past root");
+ } else {
+ stack.push("..");
+ }
+ } else {
+ if (stack[stack.length - 1] == "..") {
+ stack.push("..");
+ } else {
+ stack.pop();
+ }
+ }
+ break;
+ default:
+ stack.push(v);
+ }
+ });
+
+ // Put everything back together
+ let result = stack.join("\\");
+ if (absolute || root) {
+ result = "\\" + result;
+ }
+ if (root) {
+ result = root + result;
+ }
+ return result;
+};
+exports.normalize = normalize;
+
+/**
+ * Return the components of a path.
+ * You should generally apply this function to a normalized path.
+ *
+ * @return {{
+ * {bool} absolute |true| if the path is absolute, |false| otherwise
+ * {array} components the string components of the path
+ * {string?} winDrive the drive or server for this path
+ * }}
+ *
+ * Other implementations may add additional OS-specific informations.
+ */
+var split = function(path) {
+ return {
+ absolute: this.winIsAbsolute(path),
+ winDrive: this.winGetDrive(path),
+ components: path.split("\\")
+ };
+};
+exports.split = split;
+
+/**
+ * Return the file:// URI file path of the given local file path.
+ */
+// The case of %3b is designed to match Services.io, but fundamentally doesn't matter.
+var toFileURIExtraEncodings = {';': '%3b', '?': '%3F', '#': '%23'};
+var toFileURI = function toFileURI(path) {
+ // URI-escape forward slashes and convert backward slashes to forward
+ path = this.normalize(path).replace(/[\\\/]/g, m => (m=='\\')? '/' : '%2F');
+ // Per https://url.spec.whatwg.org we should not encode [] in the path
+ let dontNeedEscaping = {'%5B': '[', '%5D': ']'};
+ let uri = encodeURI(path).replace(/%(5B|5D)/gi,
+ match => dontNeedEscaping[match]);
+
+ // add a prefix, and encodeURI doesn't escape a few characters that we do
+ // want to escape, so fix that up
+ let prefix = "file:///";
+ uri = prefix + uri.replace(/[;?#]/g, match => toFileURIExtraEncodings[match]);
+
+ // turn e.g., file:///C: into file:///C:/
+ if (uri.charAt(uri.length - 1) === ':') {
+ uri += "/"
+ }
+
+ return uri;
+};
+exports.toFileURI = toFileURI;
+
+/**
+ * Returns the local file path from a given file URI.
+ */
+var fromFileURI = function fromFileURI(uri) {
+ let url = new URL(uri);
+ if (url.protocol != 'file:') {
+ throw new Error("fromFileURI expects a file URI");
+ }
+
+ // strip leading slash, since Windows paths don't start with one
+ uri = url.pathname.substr(1);
+
+ let path = decodeURI(uri);
+ // decode a few characters where URL's parsing is overzealous
+ path = path.replace(/%(3b|3f|23)/gi,
+ match => decodeURIComponent(match));
+ path = this.normalize(path);
+
+ // this.normalize() does not remove the trailing slash if the path
+ // component is a drive letter. eg. 'C:\'' will not get normalized.
+ if (path.endsWith(":\\")) {
+ path = path.substr(0, path.length - 1);
+ }
+ return this.normalize(path);
+};
+exports.fromFileURI = fromFileURI;
+
+/**
+* Utility function: Remove any leading/trailing backslashes
+* from a string.
+*/
+var trimBackslashes = function trimBackslashes(string) {
+ return string.replace(/^\\+|\\+$/g,'');
+};
+
+//////////// Boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}
diff --git a/components/osfile/moz.build b/components/osfile/moz.build
new file mode 100644
index 000000000..e7c8cead1
--- /dev/null
+++ b/components/osfile/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+DIRS += ['modules']
+
+XPIDL_MODULE = 'toolkit_osfile'
+
+XPIDL_SOURCES += ['nsINativeOSFileInternals.idl']
+
+EXPORTS.mozilla += ['NativeOSFileInternals.h']
+
+SOURCES += ['NativeOSFileInternals.cpp']
+
+EXTRA_PP_JS_MODULES += ['osfile.jsm']
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/osfile/nsINativeOSFileInternals.idl b/components/osfile/nsINativeOSFileInternals.idl
new file mode 100644
index 000000000..6e534bd43
--- /dev/null
+++ b/components/osfile/nsINativeOSFileInternals.idl
@@ -0,0 +1,92 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * The result of a successful asynchronous operation.
+ */
+[scriptable, builtinclass, uuid(08B4CF29-3D65-4E79-B522-A694C322ED07)]
+interface nsINativeOSFileResult: nsISupports
+{
+ /**
+ * The actual value produced by the operation.
+ *
+ * Actual type of this value depends on the options passed to the
+ * operation.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval result;
+
+ /**
+ * Delay between when the operation was requested on the main thread and
+ * when the operation was started off main thread.
+ */
+ readonly attribute double dispatchDurationMS;
+
+ /**
+ * Duration of the off main thread execution.
+ */
+ readonly attribute double executionDurationMS;
+};
+
+/**
+ * A callback invoked in case of success.
+ */
+[scriptable, function, uuid(2C1922CA-CA1B-4099-8B61-EC23CFF49412)]
+interface nsINativeOSFileSuccessCallback: nsISupports
+{
+ void complete(in nsINativeOSFileResult result);
+};
+
+/**
+ * A callback invoked in case of error.
+ */
+[scriptable, function, uuid(F612E0FC-6736-4D24-AA50-FD661B3B40B6)]
+interface nsINativeOSFileErrorCallback: nsISupports
+{
+ /**
+ * @param operation The name of the failed operation. Provided to aid
+ * debugging only, may change without notice.
+ * @param OSstatus The OS status of the operation (errno under Unix,
+ * GetLastError under Windows).
+ */
+ void complete(in ACString operation, in long OSstatus);
+};
+
+/**
+ * A service providing native implementations of some of the features
+ * of OS.File.
+ */
+[scriptable, builtinclass, uuid(913362AD-1526-4623-9E6B-A2EB08AFBBB9)]
+interface nsINativeOSFileInternalsService: nsISupports
+{
+ /**
+ * Implementation of OS.File.read
+ *
+ * @param path The absolute path to the file to read.
+ * @param options An object that may contain some of the following fields
+ * - {number} bytes The maximal number of bytes to read.
+ * - {string} encoding If provided, return the result as a string, decoded
+ * using this encoding. Otherwise, pass the result as an ArrayBuffer.
+ * Invalid encodings cause onError to be called with the platform-specific
+ * "invalid argument" constant.
+ * - {string} compression Unimplemented at the moment.
+ * @param onSuccess The success callback.
+ * @param onError The error callback.
+ */
+ [implicit_jscontext]
+ void read(in AString path, in jsval options,
+ in nsINativeOSFileSuccessCallback onSuccess,
+ in nsINativeOSFileErrorCallback onError);
+};
+
+
+%{ C++
+
+#define NATIVE_OSFILE_INTERNALS_SERVICE_CID {0x63A69303,0x8A64,0x45A9,{0x84, 0x8C, 0xD4, 0xE2, 0x79, 0x27, 0x94, 0xE6}}
+#define NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID "@mozilla.org/toolkit/osfile/native-internals;1"
+
+%}
diff --git a/components/osfile/osfile.jsm b/components/osfile/osfile.jsm
new file mode 100644
index 000000000..20f21591a
--- /dev/null
+++ b/components/osfile/osfile.jsm
@@ -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/. */
+
+/**
+ * Common front for various implementations of OS.File
+ */
+
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = ["OS"];
+ Components.utils.import("resource://gre/modules/osfile/osfile_async_front.jsm", this);
+} else {
+ // At this stage, we need to import all sources at once to avoid
+ // a unique failure on tbpl + talos that seems caused by a
+ // what looks like a nested event loop bug (see bug 794091).
+#ifdef XP_WIN
+ importScripts(
+ "resource://gre/modules/workers/require.js",
+ "resource://gre/modules/osfile/osfile_win_back.jsm",
+ "resource://gre/modules/osfile/osfile_shared_front.jsm",
+ "resource://gre/modules/osfile/osfile_win_front.jsm"
+ );
+#else
+ importScripts(
+ "resource://gre/modules/workers/require.js",
+ "resource://gre/modules/osfile/osfile_unix_back.jsm",
+ "resource://gre/modules/osfile/osfile_shared_front.jsm",
+ "resource://gre/modules/osfile/osfile_unix_front.jsm"
+ );
+#endif
+ OS.Path = require("resource://gre/modules/osfile/ospath.jsm");
+}
diff --git a/components/parentalcontrols/moz.build b/components/parentalcontrols/moz.build
new file mode 100644
index 000000000..6c8bd9a8c
--- /dev/null
+++ b/components/parentalcontrols/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['nsIParentalControlsService.idl']
+
+XPIDL_MODULE = 'parentalcontrols'
+
+if not CONFIG['MOZ_DISABLE_PARENTAL_CONTROLS']:
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ SOURCES += ['nsParentalControlsServiceWin.cpp']
+ else:
+ SOURCES += ['nsParentalControlsServiceDefault.cpp']
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/parentalcontrols/nsIParentalControlsService.idl b/components/parentalcontrols/nsIParentalControlsService.idl
new file mode 100644
index 000000000..b9d49131f
--- /dev/null
+++ b/components/parentalcontrols/nsIParentalControlsService.idl
@@ -0,0 +1,103 @@
+/* -*- 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 nsIURI;
+interface nsIFile;
+interface nsIInterfaceRequestor;
+interface nsIArray;
+
+[scriptable, uuid(2e97e5dd-467b-4aea-a1bb-6773c0f2beb0)]
+interface nsIParentalControlsService : nsISupports
+{
+ /**
+ * Action types that can be blocked for users.
+ */
+ const short DOWNLOAD = 1; // Downloading files
+ const short INSTALL_EXTENSION = 2; // Installing extensions
+ const short INSTALL_APP = 3; // Installing webapps
+ const short BROWSE = 4; // Opening specific urls
+ const short SHARE = 5; // Sharing
+ const short BOOKMARK = 6; // Creating bookmarks
+ const short ADD_CONTACT = 7; // Add contacts to the system database
+ const short SET_IMAGE = 8; // Setting images as wall paper
+ const short MODIFY_ACCOUNTS = 9; // Modifying system accounts
+ const short REMOTE_DEBUGGING = 10; // Remote debugging
+ const short IMPORT_SETTINGS = 11; // Importing settings from other apps
+ const short PRIVATE_BROWSING = 12; // Disallow usage of private browsing
+ const short DATA_CHOICES = 13; // Choose whether or not to send usage information
+ const short CLEAR_HISTORY = 14; // Clear browsing history
+ const short MASTER_PASSWORD = 15; // Setting master password for logins
+ const short GUEST_BROWSING = 16; // Disallow usage of guest browsing
+ const short ADVANCED_SETTINGS = 17; // Advanced settings
+ const short CAMERA_MICROPHONE = 18; // Camera and microphone (WebRTC)
+ const short BLOCK_LIST = 19; // Block websites that include sensitive content
+ // 20 and 21 are unused. Was: Telemetry, FHR
+ const short DEFAULT_THEME = 22; // Use default theme or a special parental controls theme
+
+ /**
+ * @returns true if the current user account has parental controls
+ * restrictions enabled.
+ */
+ readonly attribute boolean parentalControlsEnabled;
+
+ /**
+ * @returns true if the current user account parental controls
+ * restrictions include the blocking of all file downloads.
+ */
+ readonly attribute boolean blockFileDownloadsEnabled;
+
+ /**
+ * Check if the user can do the prescibed action for this uri.
+ *
+ * @param aAction Action being performed
+ * @param aUri The uri requesting this action
+ * @param aWindow The window generating this event.
+ */
+ boolean isAllowed(in short aAction, [optional] in nsIURI aUri);
+
+ /**
+ * Request that blocked URI(s) be allowed through parental
+ * control filters. Returns true if the URI was successfully
+ * overriden. Note, may block while native UI is shown.
+ *
+ * @param aTarget(s) URI to be overridden. In the case of
+ * multiple URI, the first URI in the array
+ * should be the root URI of the site.
+ * @param window Window that generates the event.
+ */
+ boolean requestURIOverride(in nsIURI aTarget, [optional] in nsIInterfaceRequestor aWindowContext);
+ boolean requestURIOverrides(in nsIArray aTargets, [optional] in nsIInterfaceRequestor aWindowContext);
+
+ /**
+ * @returns true if the current user account has parental controls
+ * logging enabled. If true, applications should log relevent events
+ * using 'log'.
+ */
+ readonly attribute boolean loggingEnabled;
+
+ /**
+ * Log entry types. Additional types can be defined and implemented
+ * as needed. Other possible event types might include email events,
+ * media related events, and IM events.
+ */
+ const short ePCLog_URIVisit = 1; /* Web content */
+ const short ePCLog_FileDownload = 2; /* File downloads */
+
+ /**
+ * Log an application specific parental controls
+ * event.
+ *
+ * @param aEntryType Constant defining the type of event.
+ * @param aFlag A flag indicating if the subject content
+ * was blocked.
+ * @param aSource The URI source of the subject content.
+ * @param aTarget The location the content was saved to if
+ * no blocking occurred.
+ */
+ void log(in short aEntryType, in boolean aFlag, in nsIURI aSource, [optional] in nsIFile aTarget);
+};
diff --git a/components/parentalcontrols/nsParentalControlsService.h b/components/parentalcontrols/nsParentalControlsService.h
new file mode 100644
index 000000000..a0dc9c2db
--- /dev/null
+++ b/components/parentalcontrols/nsParentalControlsService.h
@@ -0,0 +1,44 @@
+/* -*- 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 nsParentalControlsService_h__
+#define nsParentalControlsService_h__
+
+#include "nsIParentalControlsService.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsIURI.h"
+
+#if defined(XP_WIN)
+// wpcevents.h requires this be elevated
+#if (WINVER < 0x0600)
+# undef WINVER
+# define WINVER 0x0600
+#endif
+#include <wpcapi.h>
+#include <wpcevent.h>
+#endif
+
+class nsParentalControlsService : public nsIParentalControlsService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPARENTALCONTROLSSERVICE
+
+ nsParentalControlsService();
+
+protected:
+ virtual ~nsParentalControlsService();
+
+private:
+ bool mEnabled;
+#if defined(XP_WIN)
+ REGHANDLE mProvider;
+ IWindowsParentalControls * mPC;
+ void LogFileDownload(bool blocked, nsIURI *aSource, nsIFile *aTarget);
+#endif
+};
+
+#endif /* nsParentalControlsService_h__ */
diff --git a/components/parentalcontrols/nsParentalControlsServiceDefault.cpp b/components/parentalcontrols/nsParentalControlsServiceDefault.cpp
new file mode 100644
index 000000000..5d97b6f1b
--- /dev/null
+++ b/components/parentalcontrols/nsParentalControlsServiceDefault.cpp
@@ -0,0 +1,73 @@
+/* -*- 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 "nsParentalControlsService.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "mozilla/Unused.h"
+
+NS_IMPL_ISUPPORTS(nsParentalControlsService, nsIParentalControlsService)
+
+nsParentalControlsService::nsParentalControlsService() :
+ mEnabled(false)
+{
+ mozilla::Unused << mEnabled;
+}
+
+nsParentalControlsService::~nsParentalControlsService()
+{
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::GetParentalControlsEnabled(bool *aResult)
+{
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::GetBlockFileDownloadsEnabled(bool *aResult)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::GetLoggingEnabled(bool *aResult)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::Log(int16_t aEntryType,
+ bool blocked,
+ nsIURI *aSource,
+ nsIFile *aTarget)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::RequestURIOverride(nsIURI *aTarget,
+ nsIInterfaceRequestor *aWindowContext,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::RequestURIOverrides(nsIArray *aTargets,
+ nsIInterfaceRequestor *aWindowContext,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::IsAllowed(int16_t aAction,
+ nsIURI *aUri,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
diff --git a/components/parentalcontrols/nsParentalControlsServiceWin.cpp b/components/parentalcontrols/nsParentalControlsServiceWin.cpp
new file mode 100644
index 000000000..e73bb097a
--- /dev/null
+++ b/components/parentalcontrols/nsParentalControlsServiceWin.cpp
@@ -0,0 +1,347 @@
+/* -*- 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 "nsParentalControlsService.h"
+#include "nsString.h"
+#include "nsIArray.h"
+#include "nsIWidget.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIFile.h"
+#include "nsILocalFileWin.h"
+#include "nsArrayUtils.h"
+#include "nsIXULAppInfo.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+
+using namespace mozilla;
+
+static const CLSID CLSID_WinParentalControls = {0xE77CC89B,0x7401,0x4C04,{0x8C,0xED,0x14,0x9D,0xB3,0x5A,0xDD,0x04}};
+static const IID IID_IWinParentalControls = {0x28B4D88B,0xE072,0x49E6,{0x80,0x4D,0x26,0xED,0xBE,0x21,0xA7,0xB9}};
+
+NS_IMPL_ISUPPORTS(nsParentalControlsService, nsIParentalControlsService)
+
+static HINSTANCE gAdvAPIDLLInst = nullptr;
+
+decltype(EventWrite)* gEventWrite = nullptr;
+decltype(EventRegister)* gEventRegister = nullptr;
+decltype(EventUnregister)* gEventUnregister = nullptr;
+
+nsParentalControlsService::nsParentalControlsService() :
+ mEnabled(false)
+, mProvider(0)
+, mPC(nullptr)
+{
+ HRESULT hr;
+ CoInitialize(nullptr);
+ hr = CoCreateInstance(CLSID_WinParentalControls, nullptr, CLSCTX_INPROC,
+ IID_IWinParentalControls, (void**)&mPC);
+ if (FAILED(hr))
+ return;
+
+ RefPtr<IWPCSettings> wpcs;
+ if (FAILED(mPC->GetUserSettings(nullptr, getter_AddRefs(wpcs)))) {
+ // Not available on this os or not enabled for this user account or we're running as admin
+ mPC->Release();
+ mPC = nullptr;
+ return;
+ }
+
+ DWORD settings = 0;
+ wpcs->GetRestrictions(&settings);
+
+ // If we can't determine specifically whether Web Filtering is on/off (i.e.
+ // we're on Windows < 8), then assume it's on unless no restrictions are set.
+ bool enable = IsWin8OrLater() ? settings & WPCFLAG_WEB_FILTERED
+ : settings != WPCFLAG_NO_RESTRICTION;
+
+ if (enable) {
+ gAdvAPIDLLInst = ::LoadLibrary("Advapi32.dll");
+ if(gAdvAPIDLLInst)
+ {
+ gEventWrite = (decltype(EventWrite)*) GetProcAddress(gAdvAPIDLLInst, "EventWrite");
+ gEventRegister = (decltype(EventRegister)*) GetProcAddress(gAdvAPIDLLInst, "EventRegister");
+ gEventUnregister = (decltype(EventUnregister)*) GetProcAddress(gAdvAPIDLLInst, "EventUnregister");
+ }
+ mEnabled = true;
+ }
+}
+
+nsParentalControlsService::~nsParentalControlsService()
+{
+ if (mPC)
+ mPC->Release();
+
+ if (gEventUnregister && mProvider)
+ gEventUnregister(mProvider);
+
+ if (gAdvAPIDLLInst)
+ ::FreeLibrary(gAdvAPIDLLInst);
+}
+
+//------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsParentalControlsService::GetParentalControlsEnabled(bool *aResult)
+{
+ *aResult = false;
+
+ if (mEnabled)
+ *aResult = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::GetBlockFileDownloadsEnabled(bool *aResult)
+{
+ *aResult = false;
+
+ if (!mEnabled)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<IWPCWebSettings> wpcws;
+ if (SUCCEEDED(mPC->GetWebSettings(nullptr, getter_AddRefs(wpcws)))) {
+ DWORD settings = 0;
+ wpcws->GetSettings(&settings);
+ if (settings == WPCFLAG_WEB_SETTING_DOWNLOADSBLOCKED)
+ *aResult = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::GetLoggingEnabled(bool *aResult)
+{
+ *aResult = false;
+
+ if (!mEnabled)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Check the general purpose logging flag
+ RefPtr<IWPCSettings> wpcs;
+ if (SUCCEEDED(mPC->GetUserSettings(nullptr, getter_AddRefs(wpcs)))) {
+ BOOL enabled = FALSE;
+ wpcs->IsLoggingRequired(&enabled);
+ if (enabled)
+ *aResult = true;
+ }
+
+ return NS_OK;
+}
+
+// Post a log event to the system
+NS_IMETHODIMP
+nsParentalControlsService::Log(int16_t aEntryType, bool blocked, nsIURI *aSource, nsIFile *aTarget)
+{
+ if (!mEnabled)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ENSURE_ARG_POINTER(aSource);
+
+ // Confirm we should be logging
+ bool enabled;
+ GetLoggingEnabled(&enabled);
+ if (!enabled)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Register a Vista log event provider associated with the parental controls channel.
+ if (!mProvider) {
+ if (!gEventRegister)
+ return NS_ERROR_NOT_AVAILABLE;
+ if (gEventRegister(&WPCPROV, nullptr, nullptr, &mProvider) != ERROR_SUCCESS)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ switch(aEntryType) {
+ case ePCLog_URIVisit:
+ // Not needed, Vista's web content filter handles this for us
+ break;
+ case ePCLog_FileDownload:
+ LogFileDownload(blocked, aSource, aTarget);
+ break;
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+// Override a single URI
+NS_IMETHODIMP
+nsParentalControlsService::RequestURIOverride(nsIURI *aTarget, nsIInterfaceRequestor *aWindowContext, bool *_retval)
+{
+ *_retval = false;
+
+ if (!mEnabled)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ENSURE_ARG_POINTER(aTarget);
+
+ nsAutoCString spec;
+ aTarget->GetSpec(spec);
+ if (spec.IsEmpty())
+ return NS_ERROR_INVALID_ARG;
+
+ HWND hWnd = nullptr;
+ // If we have a native window, use its handle instead
+ nsCOMPtr<nsIWidget> widget(do_GetInterface(aWindowContext));
+ if (widget)
+ hWnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (hWnd == nullptr)
+ hWnd = GetDesktopWindow();
+
+ BOOL ret;
+ RefPtr<IWPCWebSettings> wpcws;
+ if (SUCCEEDED(mPC->GetWebSettings(nullptr, getter_AddRefs(wpcws)))) {
+ wpcws->RequestURLOverride(hWnd, NS_ConvertUTF8toUTF16(spec).get(),
+ 0, nullptr, &ret);
+ *_retval = ret;
+ }
+
+
+ return NS_OK;
+}
+
+// Override a web page
+NS_IMETHODIMP
+nsParentalControlsService::RequestURIOverrides(nsIArray *aTargets, nsIInterfaceRequestor *aWindowContext, bool *_retval)
+{
+ *_retval = false;
+
+ if (!mEnabled)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ENSURE_ARG_POINTER(aTargets);
+
+ uint32_t arrayLength = 0;
+ aTargets->GetLength(&arrayLength);
+ if (!arrayLength)
+ return NS_ERROR_INVALID_ARG;
+
+ if (arrayLength == 1) {
+ nsCOMPtr<nsIURI> uri = do_QueryElementAt(aTargets, 0);
+ if (!uri)
+ return NS_ERROR_INVALID_ARG;
+ return RequestURIOverride(uri, aWindowContext, _retval);
+ }
+
+ HWND hWnd = nullptr;
+ // If we have a native window, use its handle instead
+ nsCOMPtr<nsIWidget> widget(do_GetInterface(aWindowContext));
+ if (widget)
+ hWnd = (HWND)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (hWnd == nullptr)
+ hWnd = GetDesktopWindow();
+
+ // The first entry should be the root uri
+ nsAutoCString rootSpec;
+ nsCOMPtr<nsIURI> rootURI = do_QueryElementAt(aTargets, 0);
+ if (!rootURI)
+ return NS_ERROR_INVALID_ARG;
+
+ rootURI->GetSpec(rootSpec);
+ if (rootSpec.IsEmpty())
+ return NS_ERROR_INVALID_ARG;
+
+ // Allocate an array of sub uri
+ int32_t count = arrayLength - 1;
+ auto arrUrls = MakeUnique<LPCWSTR[]>(count);
+
+ uint32_t uriIdx = 0, idx;
+ for (idx = 1; idx < arrayLength; idx++)
+ {
+ nsCOMPtr<nsIURI> uri = do_QueryElementAt(aTargets, idx);
+ if (!uri)
+ continue;
+
+ nsAutoCString subURI;
+ if (NS_FAILED(uri->GetSpec(subURI)))
+ continue;
+
+ arrUrls[uriIdx] = (LPCWSTR)UTF8ToNewUnicode(subURI); // allocation
+ if (!arrUrls[uriIdx])
+ continue;
+
+ uriIdx++;
+ }
+
+ if (!uriIdx)
+ return NS_ERROR_INVALID_ARG;
+
+ BOOL ret;
+ RefPtr<IWPCWebSettings> wpcws;
+ if (SUCCEEDED(mPC->GetWebSettings(nullptr, getter_AddRefs(wpcws)))) {
+ wpcws->RequestURLOverride(hWnd, NS_ConvertUTF8toUTF16(rootSpec).get(),
+ uriIdx, (LPCWSTR*)arrUrls.get(), &ret);
+ *_retval = ret;
+ }
+
+ // Free up the allocated strings in our array
+ for (idx = 0; idx < uriIdx; idx++)
+ free((void*)arrUrls[idx]);
+
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------
+
+// Sends a file download event to the Vista Event Log
+void
+nsParentalControlsService::LogFileDownload(bool blocked, nsIURI *aSource, nsIFile *aTarget)
+{
+ nsAutoCString curi;
+
+ if (!gEventWrite)
+ return;
+
+ // Note, EventDataDescCreate is a macro defined in the headers, not a function
+
+ aSource->GetSpec(curi);
+ nsAutoString uri = NS_ConvertUTF8toUTF16(curi);
+
+ // Get the name of the currently running process
+ nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
+ nsAutoCString asciiAppName;
+ if (appInfo)
+ appInfo->GetName(asciiAppName);
+ nsAutoString appName = NS_ConvertUTF8toUTF16(asciiAppName);
+
+ static const WCHAR fill[] = L"";
+
+ // See wpcevent.h and msdn for event formats
+ EVENT_DATA_DESCRIPTOR eventData[WPC_ARGS_FILEDOWNLOADEVENT_CARGS];
+ DWORD dwBlocked = blocked;
+
+ EventDataDescCreate(&eventData[WPC_ARGS_FILEDOWNLOADEVENT_URL], (const void*)uri.get(),
+ ((ULONG)uri.Length()+1)*sizeof(WCHAR));
+ EventDataDescCreate(&eventData[WPC_ARGS_FILEDOWNLOADEVENT_APPNAME], (const void*)appName.get(),
+ ((ULONG)appName.Length()+1)*sizeof(WCHAR));
+ EventDataDescCreate(&eventData[WPC_ARGS_FILEDOWNLOADEVENT_VERSION], (const void*)fill, sizeof(fill));
+ EventDataDescCreate(&eventData[WPC_ARGS_FILEDOWNLOADEVENT_BLOCKED], (const void*)&dwBlocked,
+ sizeof(dwBlocked));
+
+ nsCOMPtr<nsILocalFileWin> local(do_QueryInterface(aTarget)); // May be null
+ if (local) {
+ nsAutoString path;
+ local->GetCanonicalPath(path);
+ EventDataDescCreate(&eventData[WPC_ARGS_FILEDOWNLOADEVENT_PATH], (const void*)path.get(),
+ ((ULONG)path.Length()+1)*sizeof(WCHAR));
+ }
+ else {
+ EventDataDescCreate(&eventData[WPC_ARGS_FILEDOWNLOADEVENT_PATH], (const void*)fill, sizeof(fill));
+ }
+
+ gEventWrite(mProvider, &WPCEVENT_WEB_FILEDOWNLOAD, ARRAYSIZE(eventData), eventData);
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::IsAllowed(int16_t aAction,
+ nsIURI *aUri,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
diff --git a/components/passwordmgr/.eslintrc.js b/components/passwordmgr/.eslintrc.js
new file mode 100644
index 000000000..188f7eeff
--- /dev/null
+++ b/components/passwordmgr/.eslintrc.js
@@ -0,0 +1,36 @@
+"use strict";
+
+module.exports = { // eslint-disable-line no-undef
+ "extends": "../../.eslintrc.js",
+ "rules": {
+ // Require spacing around =>
+ "arrow-spacing": "error",
+
+ // No newline before open brace for a block
+ "brace-style": ["error", "1tbs", {"allowSingleLine": true}],
+
+ // No space before always a space after a comma
+ "comma-spacing": ["error", {"before": false, "after": true}],
+
+ // Commas at the end of the line not the start
+ "comma-style": "error",
+
+ // Use [] instead of Array()
+ "no-array-constructor": "error",
+
+ // Use {} instead of new Object()
+ "no-new-object": "error",
+
+ // No using undeclared variables
+ "no-undef": "error",
+
+ // Don't allow unused local variables unless they match the pattern
+ "no-unused-vars": ["error", {"args": "none", "vars": "local", "varsIgnorePattern": "^(ids|ignored|unused)$"}],
+
+ // Always require semicolon at end of statement
+ "semi": ["error", "always"],
+
+ // Require spaces around operators
+ "space-infix-ops": "error",
+ }
+};
diff --git a/components/passwordmgr/content/passwordManager.js b/components/passwordmgr/content/passwordManager.js
new file mode 100644
index 000000000..233ed83c0
--- /dev/null
+++ b/components/passwordmgr/content/passwordManager.js
@@ -0,0 +1,725 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/** * =================== SAVED SIGNONS CODE =================== ***/
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+ "resource://gre/modules/DeferredTask.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+let kSignonBundle;
+
+// Default value for signon table sorting
+let lastSignonSortColumn = "hostname";
+let lastSignonSortAscending = true;
+
+let showingPasswords = false;
+
+// password-manager lists
+let signons = [];
+let deletedSignons = [];
+
+// Elements that would be used frequently
+let filterField;
+let togglePasswordsButton;
+let signonsIntro;
+let removeButton;
+let removeAllButton;
+let signonsTree;
+
+let signonReloadDisplay = {
+ observe: function(subject, topic, data) {
+ if (topic == "passwordmgr-storage-changed") {
+ switch (data) {
+ case "addLogin":
+ case "modifyLogin":
+ case "removeLogin":
+ case "removeAllLogins":
+ if (!signonsTree) {
+ return;
+ }
+ signons.length = 0;
+ LoadSignons();
+ // apply the filter if needed
+ if (filterField && filterField.value != "") {
+ FilterPasswords();
+ }
+ break;
+ }
+ Services.obs.notifyObservers(null, "passwordmgr-dialog-updated", null);
+ }
+ }
+};
+
+// Formatter for localization.
+let dateFormatter = new Intl.DateTimeFormat(undefined,
+ { day: "numeric", month: "short", year: "numeric" });
+let dateAndTimeFormatter = new Intl.DateTimeFormat(undefined,
+ { day: "numeric", month: "short", year: "numeric",
+ hour: "numeric", minute: "numeric" });
+
+function Startup() {
+ // be prepared to reload the display if anything changes
+ Services.obs.addObserver(signonReloadDisplay, "passwordmgr-storage-changed", false);
+
+ signonsTree = document.getElementById("signonsTree");
+ kSignonBundle = document.getElementById("signonBundle");
+ filterField = document.getElementById("filter");
+ togglePasswordsButton = document.getElementById("togglePasswords");
+ signonsIntro = document.getElementById("signonsIntro");
+ removeButton = document.getElementById("removeSignon");
+ removeAllButton = document.getElementById("removeAllSignons");
+
+ togglePasswordsButton.label = kSignonBundle.getString("showPasswords");
+ togglePasswordsButton.accessKey = kSignonBundle.getString("showPasswordsAccessKey");
+ signonsIntro.textContent = kSignonBundle.getString("loginsDescriptionAll");
+ removeAllButton.setAttribute("label", kSignonBundle.getString("removeAll.label"));
+ removeAllButton.setAttribute("accesskey", kSignonBundle.getString("removeAll.accesskey"));
+ document.getElementsByTagName("treecols")[0].addEventListener("click", (event) => {
+ let { target, button } = event;
+ let sortField = target.getAttribute("data-field-name");
+
+ if (target.nodeName != "treecol" || button != 0 || !sortField) {
+ return;
+ }
+
+ SignonColumnSort(sortField);
+ });
+
+ LoadSignons();
+
+ // filter the table if requested by caller
+ if (window.arguments &&
+ window.arguments[0] &&
+ window.arguments[0].filterString) {
+ setFilter(window.arguments[0].filterString);
+ }
+
+ FocusFilterBox();
+}
+
+function Shutdown() {
+ Services.obs.removeObserver(signonReloadDisplay, "passwordmgr-storage-changed");
+}
+
+function setFilter(aFilterString) {
+ filterField.value = aFilterString;
+ FilterPasswords();
+}
+
+let signonsTreeView = {
+ // Keep track of which favicons we've fetched or started fetching.
+ // Maps a login origin to a favicon URL.
+ _faviconMap: new Map(),
+ _filterSet: [],
+ // Coalesce invalidations to avoid repeated flickering.
+ _invalidateTask: new DeferredTask(() => {
+ signonsTree.treeBoxObject.invalidateColumn(signonsTree.columns.siteCol);
+ }, 10),
+ _lastSelectedRanges: [],
+ selection: null,
+
+ rowCount: 0,
+ setTree(tree) {},
+ getImageSrc(row, column) {
+ if (column.element.getAttribute("id") !== "siteCol") {
+ return "";
+ }
+
+ const signon = this._filterSet.length ? this._filterSet[row] : signons[row];
+
+ // We already have the favicon URL or we started to fetch (value is null).
+ if (this._faviconMap.has(signon.hostname)) {
+ return this._faviconMap.get(signon.hostname);
+ }
+
+ // Record the fact that we already starting fetching a favicon for this
+ // origin in order to avoid multiple requests for the same origin.
+ this._faviconMap.set(signon.hostname, null);
+
+ PlacesUtils.promiseFaviconLinkUrl(signon.hostname)
+ .then(faviconURI => {
+ this._faviconMap.set(signon.hostname, faviconURI.spec);
+ this._invalidateTask.arm();
+ }).catch(Cu.reportError);
+
+ return "";
+ },
+ getProgressMode(row, column) {},
+ getCellValue(row, column) {},
+ getCellText(row, column) {
+ let time;
+ let signon = this._filterSet.length ? this._filterSet[row] : signons[row];
+ switch (column.id) {
+ case "siteCol":
+ return signon.httpRealm ?
+ (signon.hostname + " (" + signon.httpRealm + ")") :
+ signon.hostname;
+ case "userCol":
+ return signon.username || "";
+ case "passwordCol":
+ return signon.password || "";
+ case "timeCreatedCol":
+ time = new Date(signon.timeCreated);
+ return dateFormatter.format(time);
+ case "timeLastUsedCol":
+ time = new Date(signon.timeLastUsed);
+ return dateAndTimeFormatter.format(time);
+ case "timePasswordChangedCol":
+ time = new Date(signon.timePasswordChanged);
+ return dateFormatter.format(time);
+ case "timesUsedCol":
+ return signon.timesUsed;
+ default:
+ return "";
+ }
+ },
+ isEditable(row, col) {
+ if (col.id == "userCol" || col.id == "passwordCol") {
+ return true;
+ }
+ return false;
+ },
+ isSeparator(index) { return false; },
+ isSorted() { return false; },
+ isContainer(index) { return false; },
+ cycleHeader(column) {},
+ getRowProperties(row) { return ""; },
+ getColumnProperties(column) { return ""; },
+ getCellProperties(row, column) {
+ if (column.element.getAttribute("id") == "siteCol")
+ return "ltr";
+
+ return "";
+ },
+ setCellText(row, col, value) {
+ // If there is a filter, _filterSet needs to be used, otherwise signons is used.
+ let table = signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons;
+ function _editLogin(field) {
+ if (value == table[row][field]) {
+ return;
+ }
+ let existingLogin = table[row].clone();
+ table[row][field] = value;
+ table[row].timePasswordChanged = Date.now();
+ Services.logins.modifyLogin(existingLogin, table[row]);
+ signonsTree.treeBoxObject.invalidateRow(row);
+ }
+
+ if (col.id == "userCol") {
+ _editLogin("username");
+
+ } else if (col.id == "passwordCol") {
+ if (!value) {
+ return;
+ }
+ _editLogin("password");
+ }
+ },
+};
+
+function SortTree(column, ascending) {
+ let table = signonsTreeView._filterSet.length ? signonsTreeView._filterSet : signons;
+ // remember which item was selected so we can restore it after the sort
+ let selections = GetTreeSelections();
+ let selectedNumber = selections.length ? table[selections[0]].number : -1;
+
+ function compareFunc(a, b) {
+ let valA, valB;
+ switch (column) {
+ case "hostname":
+ let realmA = a.httpRealm;
+ let realmB = b.httpRealm;
+ realmA = realmA == null ? "" : realmA.toLowerCase();
+ realmB = realmB == null ? "" : realmB.toLowerCase();
+
+ valA = a[column].toLowerCase() + realmA;
+ valB = b[column].toLowerCase() + realmB;
+ break;
+ case "username":
+ case "password":
+ valA = a[column].toLowerCase();
+ valB = b[column].toLowerCase();
+ break;
+
+ default:
+ valA = a[column];
+ valB = b[column];
+ }
+
+ if (valA < valB)
+ return -1;
+ if (valA > valB)
+ return 1;
+ return 0;
+ }
+
+ // do the sort
+ table.sort(compareFunc);
+ if (!ascending) {
+ table.reverse();
+ }
+
+ // restore the selection
+ let selectedRow = -1;
+ if (selectedNumber >= 0 && false) {
+ for (let s = 0; s < table.length; s++) {
+ if (table[s].number == selectedNumber) {
+ // update selection
+ // note: we need to deselect before reselecting in order to trigger ...Selected()
+ signonsTree.view.selection.select(-1);
+ signonsTree.view.selection.select(s);
+ selectedRow = s;
+ break;
+ }
+ }
+ }
+
+ // display the results
+ signonsTree.treeBoxObject.invalidate();
+ if (selectedRow >= 0) {
+ signonsTree.treeBoxObject.ensureRowIsVisible(selectedRow);
+ }
+}
+
+function LoadSignons() {
+ // loads signons into table
+ try {
+ signons = Services.logins.getAllLogins();
+ } catch (e) {
+ signons = [];
+ }
+ signons.forEach(login => login.QueryInterface(Ci.nsILoginMetaInfo));
+ signonsTreeView.rowCount = signons.length;
+
+ // sort and display the table
+ signonsTree.view = signonsTreeView;
+ // The sort column didn't change. SortTree (called by
+ // SignonColumnSort) assumes we want to toggle the sort
+ // direction but here we don't so we have to trick it
+ lastSignonSortAscending = !lastSignonSortAscending;
+ SignonColumnSort(lastSignonSortColumn);
+
+ // disable "remove all signons" button if there are no signons
+ if (signons.length == 0) {
+ removeAllButton.setAttribute("disabled", "true");
+ togglePasswordsButton.setAttribute("disabled", "true");
+ } else {
+ removeAllButton.removeAttribute("disabled");
+ togglePasswordsButton.removeAttribute("disabled");
+ }
+
+ return true;
+}
+
+function GetTreeSelections() {
+ let selections = [];
+ let select = signonsTree.view.selection;
+ if (select && signonsTree.view.rowCount > 0) {
+ let count = select.getRangeCount();
+ let min = {};
+ let max = {};
+ for (let i = 0; i < count; i++) {
+ select.getRangeAt(i, min, max);
+ for (let k = min.value; k <= max.value; k++) {
+ if (k != -1) {
+ selections[selections.length] = k;
+ }
+ }
+ }
+ }
+ return selections;
+}
+
+function SignonSelected() {
+ let selections = GetTreeSelections();
+ if (selections.length) {
+ removeButton.removeAttribute("disabled");
+ } else {
+ removeButton.setAttribute("disabled", true);
+ }
+}
+
+function DeleteSignon() {
+ let filterSet = signonsTreeView._filterSet;
+ let syncNeeded = (filterSet.length != 0);
+ let tree = signonsTree;
+ let view = signonsTreeView;
+ let table = filterSet.length ? filterSet : signons;
+
+ // Turn off tree selection notifications during the deletion
+ tree.view.selection.selectEventsSuppressed = true;
+
+ // remove selected items from list (by setting them to null) and place in deleted list
+ let selections = GetTreeSelections();
+ for (let s = selections.length - 1; s >= 0; s--) {
+ let i = selections[s];
+ deletedSignons.push(table[i]);
+ table[i] = null;
+ }
+
+ // collapse list by removing all the null entries
+ for (let j = 0; j < table.length; j++) {
+ if (table[j] == null) {
+ let k = j;
+ while ((k < table.length) && (table[k] == null)) {
+ k++;
+ }
+ table.splice(j, k - j);
+ view.rowCount -= k - j;
+ tree.treeBoxObject.rowCountChanged(j, j - k);
+ }
+ }
+
+ // update selection and/or buttons
+ if (table.length) {
+ // update selection
+ let nextSelection = (selections[0] < table.length) ? selections[0] : table.length - 1;
+ tree.view.selection.select(nextSelection);
+ tree.treeBoxObject.ensureRowIsVisible(nextSelection);
+ } else {
+ // disable buttons
+ removeButton.setAttribute("disabled", "true");
+ removeAllButton.setAttribute("disabled", "true");
+ }
+ tree.view.selection.selectEventsSuppressed = false;
+ FinalizeSignonDeletions(syncNeeded);
+}
+
+function DeleteAllSignons() {
+ let prompter = Cc["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Ci.nsIPromptService);
+
+ // Confirm the user wants to remove all passwords
+ let dummy = { value: false };
+ if (prompter.confirmEx(window,
+ kSignonBundle.getString("removeAllPasswordsTitle"),
+ kSignonBundle.getString("removeAllPasswordsPrompt"),
+ prompter.STD_YES_NO_BUTTONS + prompter.BUTTON_POS_1_DEFAULT,
+ null, null, null, null, dummy) == 1) // 1 == "No" button
+ return;
+
+ let filterSet = signonsTreeView._filterSet;
+ let syncNeeded = (filterSet.length != 0);
+ let view = signonsTreeView;
+ let table = filterSet.length ? filterSet : signons;
+
+ // remove all items from table and place in deleted table
+ for (let i = 0; i < table.length; i++) {
+ deletedSignons.push(table[i]);
+ }
+ table.length = 0;
+
+ // clear out selections
+ view.selection.select(-1);
+
+ // update the tree view and notify the tree
+ view.rowCount = 0;
+
+ let box = signonsTree.treeBoxObject;
+ box.rowCountChanged(0, -deletedSignons.length);
+ box.invalidate();
+
+ // disable buttons
+ removeButton.setAttribute("disabled", "true");
+ removeAllButton.setAttribute("disabled", "true");
+ FinalizeSignonDeletions(syncNeeded);
+}
+
+function TogglePasswordVisible() {
+ if (showingPasswords || masterPasswordLogin(AskUserShowPasswords)) {
+ showingPasswords = !showingPasswords;
+ togglePasswordsButton.label = kSignonBundle.getString(showingPasswords ? "hidePasswords" : "showPasswords");
+ togglePasswordsButton.accessKey = kSignonBundle.getString(showingPasswords ? "hidePasswordsAccessKey" : "showPasswordsAccessKey");
+ document.getElementById("passwordCol").hidden = !showingPasswords;
+ FilterPasswords();
+ }
+
+ // Notify observers that the password visibility toggling is
+ // completed. (Mostly useful for tests)
+ Services.obs.notifyObservers(null, "passwordmgr-password-toggle-complete", null);
+}
+
+function AskUserShowPasswords() {
+ let prompter = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
+ let dummy = { value: false };
+
+ // Confirm the user wants to display passwords
+ return prompter.confirmEx(window,
+ null,
+ kSignonBundle.getString("noMasterPasswordPrompt"), prompter.STD_YES_NO_BUTTONS,
+ null, null, null, null, dummy) == 0; // 0=="Yes" button
+}
+
+function FinalizeSignonDeletions(syncNeeded) {
+ for (let s = 0; s < deletedSignons.length; s++) {
+ Services.logins.removeLogin(deletedSignons[s]);
+ }
+ // If the deletion has been performed in a filtered view, reflect the deletion in the unfiltered table.
+ // See bug 405389.
+ if (syncNeeded) {
+ try {
+ signons = Services.logins.getAllLogins();
+ } catch (e) {
+ signons = [];
+ }
+ }
+ deletedSignons.length = 0;
+}
+
+function HandleSignonKeyPress(e) {
+ // If editing is currently performed, don't do anything.
+ if (signonsTree.getAttribute("editing")) {
+ return;
+ }
+
+ if (e.keyCode == KeyboardEvent.DOM_VK_DELETE) {
+ DeleteSignon();
+ }
+}
+
+function getColumnByName(column) {
+ switch (column) {
+ case "hostname":
+ return document.getElementById("siteCol");
+ case "username":
+ return document.getElementById("userCol");
+ case "password":
+ return document.getElementById("passwordCol");
+ case "timeCreated":
+ return document.getElementById("timeCreatedCol");
+ case "timeLastUsed":
+ return document.getElementById("timeLastUsedCol");
+ case "timePasswordChanged":
+ return document.getElementById("timePasswordChangedCol");
+ case "timesUsed":
+ return document.getElementById("timesUsedCol");
+ }
+ return undefined;
+}
+
+function SignonColumnSort(column) {
+ let sortedCol = getColumnByName(column);
+ let lastSortedCol = getColumnByName(lastSignonSortColumn);
+
+ // clear out the sortDirection attribute on the old column
+ lastSortedCol.removeAttribute("sortDirection");
+
+ // determine if sort is to be ascending or descending
+ lastSignonSortAscending = (column == lastSignonSortColumn) ? !lastSignonSortAscending : true;
+
+ // sort
+ lastSignonSortColumn = column;
+ SortTree(lastSignonSortColumn, lastSignonSortAscending);
+
+ // set the sortDirection attribute to get the styling going
+ // first we need to get the right element
+ sortedCol.setAttribute("sortDirection", lastSignonSortAscending ?
+ "ascending" : "descending");
+}
+
+function SignonClearFilter() {
+ let singleSelection = (signonsTreeView.selection.count == 1);
+
+ // Clear the Tree Display
+ signonsTreeView.rowCount = 0;
+ signonsTree.treeBoxObject.rowCountChanged(0, -signonsTreeView._filterSet.length);
+ signonsTreeView._filterSet = [];
+
+ // Just reload the list to make sure deletions are respected
+ LoadSignons();
+
+ // Restore selection
+ if (singleSelection) {
+ signonsTreeView.selection.clearSelection();
+ for (let i = 0; i < signonsTreeView._lastSelectedRanges.length; ++i) {
+ let range = signonsTreeView._lastSelectedRanges[i];
+ signonsTreeView.selection.rangedSelect(range.min, range.max, true);
+ }
+ } else {
+ signonsTreeView.selection.select(0);
+ }
+ signonsTreeView._lastSelectedRanges = [];
+
+ signonsIntro.textContent = kSignonBundle.getString("loginsDescriptionAll");
+ removeAllButton.setAttribute("label", kSignonBundle.getString("removeAll.label"));
+ removeAllButton.setAttribute("accesskey", kSignonBundle.getString("removeAll.accesskey"));
+}
+
+function FocusFilterBox() {
+ if (filterField.getAttribute("focused") != "true") {
+ filterField.focus();
+ }
+}
+
+function SignonMatchesFilter(aSignon, aFilterValue) {
+ if (aSignon.hostname.toLowerCase().indexOf(aFilterValue) != -1)
+ return true;
+ if (aSignon.username &&
+ aSignon.username.toLowerCase().indexOf(aFilterValue) != -1)
+ return true;
+ if (aSignon.httpRealm &&
+ aSignon.httpRealm.toLowerCase().indexOf(aFilterValue) != -1)
+ return true;
+ if (showingPasswords && aSignon.password &&
+ aSignon.password.toLowerCase().indexOf(aFilterValue) != -1)
+ return true;
+
+ return false;
+}
+
+function _filterPasswords(aFilterValue, view) {
+ aFilterValue = aFilterValue.toLowerCase();
+ return signons.filter(s => SignonMatchesFilter(s, aFilterValue));
+}
+
+function SignonSaveState() {
+ // Save selection
+ let seln = signonsTreeView.selection;
+ signonsTreeView._lastSelectedRanges = [];
+ let rangeCount = seln.getRangeCount();
+ for (let i = 0; i < rangeCount; ++i) {
+ let min = {}; let max = {};
+ seln.getRangeAt(i, min, max);
+ signonsTreeView._lastSelectedRanges.push({ min: min.value, max: max.value });
+ }
+}
+
+function FilterPasswords() {
+ if (filterField.value == "") {
+ SignonClearFilter();
+ return;
+ }
+
+ let newFilterSet = _filterPasswords(filterField.value, signonsTreeView);
+ if (!signonsTreeView._filterSet.length) {
+ // Save Display Info for the Non-Filtered mode when we first
+ // enter Filtered mode.
+ SignonSaveState();
+ }
+ signonsTreeView._filterSet = newFilterSet;
+
+ // Clear the display
+ let oldRowCount = signonsTreeView.rowCount;
+ signonsTreeView.rowCount = 0;
+ signonsTree.treeBoxObject.rowCountChanged(0, -oldRowCount);
+ // Set up the filtered display
+ signonsTreeView.rowCount = signonsTreeView._filterSet.length;
+ signonsTree.treeBoxObject.rowCountChanged(0, signonsTreeView.rowCount);
+
+ // if the view is not empty then select the first item
+ if (signonsTreeView.rowCount > 0)
+ signonsTreeView.selection.select(0);
+
+ signonsIntro.textContent = kSignonBundle.getString("loginsDescriptionFiltered");
+ removeAllButton.setAttribute("label", kSignonBundle.getString("removeAllShown.label"));
+ removeAllButton.setAttribute("accesskey", kSignonBundle.getString("removeAllShown.accesskey"));
+}
+
+function CopyPassword() {
+ // Don't copy passwords if we aren't already showing the passwords & a master
+ // password hasn't been entered.
+ if (!showingPasswords && !masterPasswordLogin())
+ return;
+ // Copy selected signon's password to clipboard
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ let row = signonsTree.currentIndex;
+ let password = signonsTreeView.getCellText(row, {id : "passwordCol" });
+ clipboard.copyString(password);
+}
+
+function CopyUsername() {
+ // Copy selected signon's username to clipboard
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ let row = signonsTree.currentIndex;
+ let username = signonsTreeView.getCellText(row, {id : "userCol" });
+ clipboard.copyString(username);
+}
+
+function EditCellInSelectedRow(columnName) {
+ let row = signonsTree.currentIndex;
+ let columnElement = getColumnByName(columnName);
+ signonsTree.startEditing(row, signonsTree.columns.getColumnFor(columnElement));
+}
+
+function UpdateContextMenu() {
+ let singleSelection = (signonsTreeView.selection.count == 1);
+ let menuItems = new Map();
+ let menupopup = document.getElementById("signonsTreeContextMenu");
+ for (let menuItem of menupopup.querySelectorAll("menuitem")) {
+ menuItems.set(menuItem.id, menuItem);
+ }
+
+ if (!singleSelection) {
+ for (let menuItem of menuItems.values()) {
+ menuItem.setAttribute("disabled", "true");
+ }
+ return;
+ }
+
+ let selectedRow = signonsTree.currentIndex;
+
+ // Disable "Copy Username" if the username is empty.
+ if (signonsTreeView.getCellText(selectedRow, { id: "userCol" }) != "") {
+ menuItems.get("context-copyusername").removeAttribute("disabled");
+ } else {
+ menuItems.get("context-copyusername").setAttribute("disabled", "true");
+ }
+
+ menuItems.get("context-editusername").removeAttribute("disabled");
+ menuItems.get("context-copypassword").removeAttribute("disabled");
+
+ // Disable "Edit Password" if the password column isn't showing.
+ if (!document.getElementById("passwordCol").hidden) {
+ menuItems.get("context-editpassword").removeAttribute("disabled");
+ } else {
+ menuItems.get("context-editpassword").setAttribute("disabled", "true");
+ }
+}
+
+function masterPasswordLogin(noPasswordCallback) {
+ // This doesn't harm if passwords are not encrypted
+ let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"]
+ .createInstance(Ci.nsIPK11TokenDB);
+ let token = tokendb.getInternalKeyToken();
+
+ // If there is no master password, still give the user a chance to opt-out of displaying passwords
+ if (token.checkPassword(""))
+ return noPasswordCallback ? noPasswordCallback() : true;
+
+ // So there's a master password. But since checkPassword didn't succeed, we're logged out (per nsIPK11Token.idl).
+ try {
+ // Relogin and ask for the master password.
+ token.login(true); // 'true' means always prompt for token password. User will be prompted until
+ // clicking 'Cancel' or entering the correct password.
+ } catch (e) {
+ // An exception will be thrown if the user cancels the login prompt dialog.
+ // User is also logged out of Software Security Device.
+ }
+
+ return token.isLoggedIn();
+}
+
+function escapeKeyHandler() {
+ // If editing is currently performed, don't do anything.
+ if (signonsTree.getAttribute("editing")) {
+ return;
+ }
+ window.close();
+}
+
+#if defined(XP_WIN) && defined(MOZ_AUSTRALIS)
+function OpenMigrator() {
+ const { MigrationUtils } = Cu.import("resource:///modules/MigrationUtils.jsm", {});
+ // We pass in the type of source we're using:
+ MigrationUtils.showMigrationWizard(window, [MigrationUtils.MIGRATION_ENTRYPOINT_PASSWORDS]);
+}
+#endif
diff --git a/components/passwordmgr/content/passwordManager.xul b/components/passwordmgr/content/passwordManager.xul
new file mode 100644
index 000000000..0c22d5e45
--- /dev/null
+++ b/components/passwordmgr/content/passwordManager.xul
@@ -0,0 +1,131 @@
+<?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://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://global/skin/passwordmgr.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://passwordmgr/locale/passwordManager.dtd" >
+
+<window id="SignonViewerDialog"
+ windowtype="Toolkit:PasswordManager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="Startup();"
+ onunload="Shutdown();"
+ title="&savedLogins.title;"
+ style="width: 45em;"
+ persist="width height screenX screenY">
+
+ <script type="application/javascript" src="chrome://passwordmgr/content/passwordManager.js"/>
+
+ <stringbundle id="signonBundle"
+ src="chrome://passwordmgr/locale/passwordmgr.properties"/>
+
+ <keyset>
+ <key keycode="VK_ESCAPE" oncommand="escapeKeyHandler();"/>
+ <key key="&windowClose.key;" modifiers="accel" oncommand="escapeKeyHandler();"/>
+ <key key="&focusSearch1.key;" modifiers="accel" oncommand="FocusFilterBox();"/>
+ <key key="&focusSearch2.key;" modifiers="accel" oncommand="FocusFilterBox();"/>
+ </keyset>
+
+ <popupset id="signonsTreeContextSet">
+ <menupopup id="signonsTreeContextMenu"
+ onpopupshowing="UpdateContextMenu()">
+ <menuitem id="context-copyusername"
+ label="&copyUsernameCmd.label;"
+ accesskey="&copyUsernameCmd.accesskey;"
+ oncommand="CopyUsername()"/>
+ <menuitem id="context-editusername"
+ label="&editUsernameCmd.label;"
+ accesskey="&editUsernameCmd.accesskey;"
+ oncommand="EditCellInSelectedRow('username')"/>
+ <menuseparator/>
+ <menuitem id="context-copypassword"
+ label="&copyPasswordCmd.label;"
+ accesskey="&copyPasswordCmd.accesskey;"
+ oncommand="CopyPassword()"/>
+ <menuitem id="context-editpassword"
+ label="&editPasswordCmd.label;"
+ accesskey="&editPasswordCmd.accesskey;"
+ oncommand="EditCellInSelectedRow('password')"/>
+ </menupopup>
+ </popupset>
+
+ <!-- saved signons -->
+ <vbox id="savedsignons" class="contentPane" flex="1">
+ <!-- filter -->
+ <hbox align="center">
+ <label accesskey="&filter.accesskey;" control="filter">&filter.label;</label>
+ <textbox id="filter" flex="1" type="search"
+ aria-controls="signonsTree"
+ oncommand="FilterPasswords();"/>
+ </hbox>
+
+ <label control="signonsTree" id="signonsIntro"/>
+ <separator class="thin"/>
+ <tree id="signonsTree" flex="1"
+ width="750"
+ style="height: 20em;"
+ onkeypress="HandleSignonKeyPress(event)"
+ onselect="SignonSelected();"
+ editable="true"
+ context="signonsTreeContextMenu">
+ <treecols>
+ <treecol id="siteCol" label="&treehead.site.label;" flex="40"
+ data-field-name="hostname" persist="width"
+ ignoreincolumnpicker="true"
+ sortDirection="ascending"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="userCol" label="&treehead.username.label;" flex="25"
+ ignoreincolumnpicker="true"
+ data-field-name="username" persist="width"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="passwordCol" label="&treehead.password.label;" flex="15"
+ ignoreincolumnpicker="true"
+ data-field-name="password" persist="width"
+ hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="timeCreatedCol" label="&treehead.timeCreated.label;" flex="10"
+ data-field-name="timeCreated" persist="width hidden"
+ hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="timeLastUsedCol" label="&treehead.timeLastUsed.label;" flex="20"
+ data-field-name="timeLastUsed" persist="width hidden"
+ hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="timePasswordChangedCol" label="&treehead.timePasswordChanged.label;" flex="10"
+ data-field-name="timePasswordChanged" persist="width hidden"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="timesUsedCol" label="&treehead.timesUsed.label;" flex="1"
+ data-field-name="timesUsed" persist="width hidden"
+ hidden="true"/>
+ <splitter class="tree-splitter"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ <separator class="thin"/>
+ <hbox id="SignonViewerButtons">
+ <button id="removeSignon" disabled="true" icon="remove"
+ label="&remove.label;" accesskey="&remove.accesskey;"
+ oncommand="DeleteSignon();"/>
+ <button id="removeAllSignons" icon="clear"
+ oncommand="DeleteAllSignons();"/>
+ <spacer flex="1"/>
+#if defined(XP_WIN) && defined(MOZ_AUSTRALIS)
+ <button accesskey="&import.accesskey;"
+ label="&import.label;"
+ oncommand="OpenMigrator();"/>
+#endif
+ <button id="togglePasswords"
+ oncommand="TogglePasswordVisible();"/>
+ </hbox>
+ </vbox>
+ <hbox align="end">
+ <hbox class="actionButtons" flex="1">
+ <spacer flex="1"/>
+ <button oncommand="close();" icon="close"
+ label="&closebutton.label;" accesskey="&closebutton.accesskey;"/>
+ </hbox>
+ </hbox>
+</window>
diff --git a/components/passwordmgr/content/recipes.json b/components/passwordmgr/content/recipes.json
new file mode 100644
index 000000000..fc747219b
--- /dev/null
+++ b/components/passwordmgr/content/recipes.json
@@ -0,0 +1,31 @@
+{
+ "siteRecipes": [
+ {
+ "description": "okta uses a hidden password field to disable filling",
+ "hosts": ["mozilla.okta.com"],
+ "passwordSelector": "#pass-signin"
+ },
+ {
+ "description": "anthem uses a hidden password and username field to disable filling",
+ "hosts": ["www.anthem.com"],
+ "passwordSelector": "#LoginContent_txtLoginPass"
+ },
+ {
+ "description": "An ephemeral password-shim field is incorrectly selected as the username field.",
+ "hosts": ["www.discover.com"],
+ "usernameSelector": "#login-account"
+ },
+ {
+ "description": "Tibia uses type=password for its username field and puts the email address before the password field during registration",
+ "hosts": ["secure.tibia.com"],
+ "usernameSelector": "#accountname, input[name='loginname']",
+ "passwordSelector": "#password1, input[name='loginpassword']",
+ "pathRegex": "^\/account\/"
+ },
+ {
+ "description": "Username field will be incorrectly captured in the change password form (bug 1243722)",
+ "hosts": ["www.facebook.com"],
+ "notUsernameSelector": "#password_strength"
+ }
+ ]
+}
diff --git a/components/passwordmgr/jar.mn b/components/passwordmgr/jar.mn
new file mode 100644
index 000000000..db6d7ffef
--- /dev/null
+++ b/components/passwordmgr/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/.
+
+toolkit.jar:
+% content passwordmgr %content/passwordmgr/
+* content/passwordmgr/passwordManager.xul (content/passwordManager.xul)
+* content/passwordmgr/passwordManager.js (content/passwordManager.js)
+ content/passwordmgr/recipes.json (content/recipes.json)
diff --git a/components/passwordmgr/locale/passwordManager.dtd b/components/passwordmgr/locale/passwordManager.dtd
new file mode 100644
index 000000000..84e4ff69c
--- /dev/null
+++ b/components/passwordmgr/locale/passwordManager.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 savedLogins.title "Saved Logins">
+
+<!ENTITY closebutton.label "Close">
+<!ENTITY closebutton.accesskey "C">
+
+<!ENTITY treehead.site.label "Site">
+<!ENTITY treehead.username.label "Username">
+<!ENTITY treehead.password.label "Password">
+<!ENTITY treehead.timeCreated.label "First Used">
+<!ENTITY treehead.timeLastUsed.label "Last Used">
+<!ENTITY treehead.timePasswordChanged.label "Last Changed">
+<!ENTITY treehead.timesUsed.label "Times Used">
+
+<!ENTITY remove.label "Remove">
+<!ENTITY remove.accesskey "R">
+
+<!ENTITY addLogin.label "Add Login">
+<!ENTITY addLogin.accesskey "L">
+
+<!ENTITY import.label "Import…">
+<!ENTITY import.accesskey "I">
+
+<!ENTITY filter.label "Search:">
+<!ENTITY filter.accesskey "S">
+
+<!ENTITY windowClose.key "w">
+<!ENTITY focusSearch1.key "f">
+<!ENTITY focusSearch2.key "k">
+
+<!ENTITY copyPasswordCmd.label "Copy Password">
+<!ENTITY copyPasswordCmd.accesskey "C">
+
+<!ENTITY copyUsernameCmd.label "Copy Username">
+<!ENTITY copyUsernameCmd.accesskey "U">
+
+<!ENTITY editPasswordCmd.label "Edit Password">
+<!ENTITY editPasswordCmd.accesskey "E">
+
+<!ENTITY editUsernameCmd.label "Edit Username">
+<!ENTITY editUsernameCmd.accesskey "d">
diff --git a/components/passwordmgr/locale/passwordmgr.properties b/components/passwordmgr/locale/passwordmgr.properties
new file mode 100644
index 000000000..6a399bbfc
--- /dev/null
+++ b/components/passwordmgr/locale/passwordmgr.properties
@@ -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/.
+
+rememberValue = Use Password Manager to remember this value.
+rememberPassword = Use Password Manager to remember this password.
+savePasswordTitle = Confirm
+# LOCALIZATION NOTE (rememberLoginMsg, rememberLoginMsgNoUser): %S is brandShortName
+rememberLoginMsg = Would you like %S to remember this login?
+rememberLoginMsgNoUser = Would you like %S to remember this password?
+rememberLoginButtonText = Remember
+rememberLoginButtonAccessKey = R
+updateLoginMsg = Would you like to update this login?
+updateLoginMsgNoUser = Would you like to update this password?
+updateLoginButtonText = Update
+updateLoginButtonAccessKey = U
+# LOCALIZATION NOTE (rememberPasswordMsg):
+# 1st string is the username for the login, 2nd is the login's hostname.
+# Note that long usernames may be truncated.
+rememberPasswordMsg = Would you like to remember the password for “%1$S†on %2$S?
+# LOCALIZATION NOTE (rememberPasswordMsgNoUsername):
+# String is the login's hostname.
+rememberPasswordMsgNoUsername = Would you like to remember the password on %S?
+# LOCALIZATION NOTE (noUsernamePlaceholder):
+# This is displayed in place of the username when it is missing.
+noUsernamePlaceholder=No username
+togglePasswordLabel=Show password
+togglePasswordAccessKey=S
+notNowButtonText = &Not Now
+notifyBarNotNowButtonText = Not Now
+notifyBarNotNowButtonAccessKey = N
+neverForSiteButtonText = Ne&ver for This Site
+notifyBarNeverRememberButtonText = Never Remember Password for This Site
+notifyBarNeverRememberButtonAccessKey = e
+rememberButtonText = &Remember
+notifyBarRememberPasswordButtonText = Remember Password
+notifyBarRememberPasswordButtonAccessKey = R
+passwordChangeTitle = Confirm Password Change
+# LOCALIZATION NOTE (updatePasswordMsg):
+# String is the username for the login.
+updatePasswordMsg = Would you like to update the saved password for “%S�
+updatePasswordMsgNoUser = Would you like to update the saved password?
+notifyBarUpdateButtonText = Update Password
+notifyBarUpdateButtonAccessKey = U
+notifyBarDontChangeButtonText = Don’t Change
+notifyBarDontChangeButtonAccessKey = D
+userSelectText = Please confirm which user you are changing the password for
+hidePasswords=Hide Passwords
+hidePasswordsAccessKey=P
+showPasswords=Show Passwords
+showPasswordsAccessKey=P
+noMasterPasswordPrompt=Are you sure you wish to show your passwords?
+removeAllPasswordsPrompt=Are you sure you wish to remove all passwords?
+removeAllPasswordsTitle=Remove all passwords
+removeLoginPrompt=Are you sure you wish to remove this login?
+removeLoginTitle=Remove login
+loginsDescriptionAll=Logins for the following sites are stored on your computer:
+loginsDescriptionFiltered=The following logins match your search:
+# LOCALIZATION NOTE (loginHostAge):
+# This is used to show the context menu login items with their age.
+# 1st string is the username for the login, 2nd is the login's age.
+loginHostAge=%1$S (%2$S)
+# LOCALIZATION NOTE (noUsername):
+# String is used on the context menu when a login doesn't have a username.
+noUsername=No username
+duplicateLoginTitle=Login already exists
+duplicateLogin=A duplicate login already exists.
+
+insecureFieldWarningDescription = This connection is not secure. Logins entered here could be compromised.
+insecureFieldWarningLearnMore = Learn More
+
+# LOCALIZATION NOTE (removeAll, removeAllShown):
+# removeAll and removeAllShown are both used on the same one button,
+# never displayed together and can share the same accesskey.
+# When only partial sites are shown as a result of keyword search,
+# removeAllShown is displayed as button label.
+# removeAll is displayed when no keyword search and all sites are shown.
+removeAll.label=Remove All
+removeAll.accesskey=A
+removeAllShown.label=Remove All Shown
+removeAllShown.accesskey=A
diff --git a/components/passwordmgr/moz.build b/components/passwordmgr/moz.build
new file mode 100644
index 000000000..d45753362
--- /dev/null
+++ b/components/passwordmgr/moz.build
@@ -0,0 +1,46 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+if CONFIG['MOZ_PHOENIX']:
+ DEFINES['MOZ_BUILD_APP_IS_BROWSER'] = True
+
+XPIDL_SOURCES += [
+ 'public/nsILoginInfo.idl',
+ 'public/nsILoginManager.idl',
+ 'public/nsILoginManagerCrypto.idl',
+ 'public/nsILoginManagerPrompter.idl',
+ 'public/nsILoginManagerStorage.idl',
+ 'public/nsILoginMetaInfo.idl',
+]
+
+EXTRA_COMPONENTS += [
+ 'passwordmgr.manifest',
+ 'src/crypto-SDR.js',
+ 'src/nsLoginInfo.js',
+ 'src/nsLoginManager.js',
+ 'src/nsLoginManagerPrompter.js',
+ 'src/storage-json.js',
+]
+
+EXTRA_JS_MODULES += [
+ 'src/InsecurePasswordUtils.jsm',
+ 'src/LoginHelper.jsm',
+ 'src/LoginImport.jsm',
+ 'src/LoginManagerContent.jsm',
+ 'src/LoginManagerParent.jsm',
+ 'src/LoginRecipes.jsm',
+ 'src/LoginStore.jsm',
+]
+
+if CONFIG['OS_TARGET'] == 'WINNT':
+ EXTRA_JS_MODULES += ['src/OSCrypto_win.js']
+
+if CONFIG['MOZ_PHOENIX']:
+ EXTRA_JS_MODULES += ['src/LoginManagerContextMenu.jsm']
+
+EXTRA_PP_JS_MODULES += ['src/OSCrypto.jsm']
+
+XPIDL_MODULE = 'loginmgr'
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/passwordmgr/passwordmgr.manifest b/components/passwordmgr/passwordmgr.manifest
new file mode 100644
index 000000000..c2c37ebff
--- /dev/null
+++ b/components/passwordmgr/passwordmgr.manifest
@@ -0,0 +1,12 @@
+component {cb9e0de8-3598-4ed7-857b-827f011ad5d8} nsLoginManager.js
+contract @mozilla.org/login-manager;1 {cb9e0de8-3598-4ed7-857b-827f011ad5d8}
+component {749e62f4-60ae-4569-a8a2-de78b649660e} nsLoginManagerPrompter.js
+contract @mozilla.org/passwordmanager/authpromptfactory;1 {749e62f4-60ae-4569-a8a2-de78b649660e}
+component {8aa66d77-1bbb-45a6-991e-b8f47751c291} nsLoginManagerPrompter.js
+contract @mozilla.org/login-manager/prompter;1 {8aa66d77-1bbb-45a6-991e-b8f47751c291}
+component {0f2f347c-1e4f-40cc-8efd-792dea70a85e} nsLoginInfo.js
+contract @mozilla.org/login-manager/loginInfo;1 {0f2f347c-1e4f-40cc-8efd-792dea70a85e}
+component {c00c432d-a0c9-46d7-bef6-9c45b4d07341} storage-json.js
+contract @mozilla.org/login-manager/storage/json;1 {c00c432d-a0c9-46d7-bef6-9c45b4d07341}
+component {dc6c2976-0f73-4f1f-b9ff-3d72b4e28309} crypto-SDR.js
+contract @mozilla.org/login-manager/crypto/SDR;1 {dc6c2976-0f73-4f1f-b9ff-3d72b4e28309} \ No newline at end of file
diff --git a/components/passwordmgr/public/nsILoginInfo.idl b/components/passwordmgr/public/nsILoginInfo.idl
new file mode 100644
index 000000000..7dce9033d
--- /dev/null
+++ b/components/passwordmgr/public/nsILoginInfo.idl
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"
+
+[scriptable, uuid(c41b7dff-6b9b-42fe-b78d-113051facb05)]
+
+/**
+ * An object containing information for a login stored by the
+ * password manager.
+ */
+interface nsILoginInfo : nsISupports {
+ /**
+ * The hostname the login applies to.
+ *
+ * The hostname should be formatted as an URL. For example,
+ * "https://site.com", "http://site.com:1234", "ftp://ftp.site.com".
+ */
+ attribute AString hostname;
+
+ /**
+ * The URL a form-based login was submitted to.
+ *
+ * For logins obtained from HTML forms, this field is the |action|
+ * attribute from the |form| element, with the path removed. For
+ * example "http://www.site.com". [Forms with no |action| attribute
+ * default to submitting to their origin URL, so we store that.]
+ *
+ * For logins obtained from a HTTP or FTP protocol authentication,
+ * this field is NULL.
+ */
+ attribute AString formSubmitURL;
+
+ /**
+ * The HTTP Realm a login was requested for.
+ *
+ * When an HTTP server sends a 401 result, the WWW-Authenticate
+ * header includes a realm to identify the "protection space." See
+ * RFC2617. If the response sent has a missing or blank realm, the
+ * hostname is used instead.
+ *
+ * For logins obtained from HTML forms, this field is NULL.
+ */
+ attribute AString httpRealm;
+
+ /**
+ * The username for the login.
+ */
+ attribute AString username;
+
+ /**
+ * The |name| attribute for the username input field.
+ *
+ * For logins obtained from a HTTP or FTP protocol authentication,
+ * this field is an empty string.
+ */
+ attribute AString usernameField;
+
+ /**
+ * The password for the login.
+ */
+ attribute AString password;
+
+ /**
+ * The |name| attribute for the password input field.
+ *
+ * For logins obtained from a HTTP or FTP protocol authentication,
+ * this field is an empty string.
+ */
+ attribute AString passwordField;
+
+ /**
+ * Initialize a newly created nsLoginInfo object.
+ *
+ * The arguments are the fields for the new object.
+ */
+ void init(in AString aHostname,
+ in AString aFormSubmitURL, in AString aHttpRealm,
+ in AString aUsername, in AString aPassword,
+ in AString aUsernameField, in AString aPasswordField);
+
+ /**
+ * Test for strict equality with another nsILoginInfo object.
+ *
+ * @param aLoginInfo
+ * The other object to test.
+ */
+ boolean equals(in nsILoginInfo aLoginInfo);
+
+ /**
+ * Test for loose equivalency with another nsILoginInfo object. The
+ * passwordField and usernameField values are ignored, and the password
+ * values may be optionally ignored. If one login's formSubmitURL is an
+ * empty string (but not null), it will be treated as a wildcard. [The
+ * blank value indicates the login was stored before bug 360493 was fixed.]
+ *
+ * @param aLoginInfo
+ * The other object to test.
+ * @param ignorePassword
+ * If true, ignore the password when checking for match.
+ */
+ boolean matches(in nsILoginInfo aLoginInfo, in boolean ignorePassword);
+
+ /**
+ * Create an identical copy of the login, duplicating all of the login's
+ * nsILoginInfo and nsILoginMetaInfo properties.
+ *
+ * This allows code to be forwards-compatible, when additional properties
+ * are added to nsILoginMetaInfo (or nsILoginInfo) in the future.
+ */
+ nsILoginInfo clone();
+};
+
+%{C++
+
+#define NS_LOGININFO_CONTRACTID "@mozilla.org/login-manager/loginInfo;1"
+
+%}
diff --git a/components/passwordmgr/public/nsILoginManager.idl b/components/passwordmgr/public/nsILoginManager.idl
new file mode 100644
index 000000000..30b5a0449
--- /dev/null
+++ b/components/passwordmgr/public/nsILoginManager.idl
@@ -0,0 +1,262 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsILoginInfo;
+interface nsIAutoCompleteResult;
+interface nsIFormAutoCompleteObserver;
+interface nsIDOMHTMLInputElement;
+interface nsIDOMHTMLFormElement;
+interface nsIPropertyBag;
+
+[scriptable, uuid(38c7f6af-7df9-49c7-b558-2776b24e6cc1)]
+interface nsILoginManager : nsISupports {
+ /**
+ * This promise is resolved when initialization is complete, and is rejected
+ * in case initialization failed. This includes the initial loading of the
+ * login data as well as any migration from previous versions.
+ *
+ * Calling any method of nsILoginManager before this promise is resolved
+ * might trigger the synchronous initialization fallback.
+ */
+ readonly attribute jsval initializationPromise;
+
+
+ /**
+ * Store a new login in the login manager.
+ *
+ * @param aLogin
+ * The login to be added.
+ * @return a clone of the login info with the guid set (even if it was not provided)
+ *
+ * Default values for the login's nsILoginMetaInfo properties will be
+ * created. However, if the caller specifies non-default values, they will
+ * be used instead.
+ */
+ nsILoginInfo addLogin(in nsILoginInfo aLogin);
+
+
+ /**
+ * Remove a login from the login manager.
+ *
+ * @param aLogin
+ * The login to be removed.
+ *
+ * The specified login must exactly match a stored login. However, the
+ * values of any nsILoginMetaInfo properties are ignored.
+ */
+ void removeLogin(in nsILoginInfo aLogin);
+
+
+ /**
+ * Modify an existing login in the login manager.
+ *
+ * @param oldLogin
+ * The login to be modified.
+ * @param newLoginData
+ * The new login values (either a nsILoginInfo or nsIProperyBag)
+ *
+ * If newLoginData is a nsILoginInfo, all of the old login's nsILoginInfo
+ * properties are changed to the values from newLoginData (but the old
+ * login's nsILoginMetaInfo properties are unmodified).
+ *
+ * If newLoginData is a nsIPropertyBag, only the specified properties
+ * will be changed. The nsILoginMetaInfo properties of oldLogin can be
+ * changed in this manner.
+ *
+ * If the propertybag contains an item named "timesUsedIncrement", the
+ * login's timesUsed property will be incremented by the item's value.
+ */
+ void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData);
+
+
+ /**
+ * Remove all logins known to login manager.
+ *
+ * The browser sanitization feature allows the user to clear any stored
+ * passwords. This interface allows that to be done without getting each
+ * login first (which might require knowing the master password).
+ *
+ */
+ void removeAllLogins();
+
+
+ /**
+ * Fetch all logins in the login manager. An array is always returned;
+ * if there are no logins the array is empty.
+ *
+ * @param count
+ * The number of elements in the array. JS callers can simply use
+ * the array's .length property and omit this param.
+ * @param logins
+ * An array of nsILoginInfo objects.
+ *
+ * NOTE: This can be called from JS as:
+ * var logins = pwmgr.getAllLogins();
+ * (|logins| is an array).
+ */
+ void getAllLogins([optional] out unsigned long count,
+ [retval, array, size_is(count)] out nsILoginInfo logins);
+
+
+ /**
+ * Obtain a list of all hosts for which password saving is disabled.
+ *
+ * @param count
+ * The number of elements in the array. JS callers can simply use
+ * the array's .length property and omit this param.
+ * @param hostnames
+ * An array of hostname strings, in origin URL format without a
+ * pathname. For example: "https://www.site.com".
+ *
+ * NOTE: This can be called from JS as:
+ * var logins = pwmgr.getDisabledAllLogins();
+ */
+ void getAllDisabledHosts([optional] out unsigned long count,
+ [retval, array, size_is(count)] out wstring hostnames);
+
+
+ /**
+ * Check to see if saving logins has been disabled for a host.
+ *
+ * @param aHost
+ * The hostname to check. This argument should be in the origin
+ * URL format, without a pathname. For example: "http://foo.com".
+ */
+ boolean getLoginSavingEnabled(in AString aHost);
+
+
+ /**
+ * Disable (or enable) storing logins for the specified host. When
+ * disabled, the login manager will not prompt to store logins for
+ * that host. Existing logins are not affected.
+ *
+ * @param aHost
+ * The hostname to set. This argument should be in the origin
+ * URL format, without a pathname. For example: "http://foo.com".
+ * @param isEnabled
+ * Specify if saving logins should be enabled (true) or
+ * disabled (false)
+ */
+ void setLoginSavingEnabled(in AString aHost, in boolean isEnabled);
+
+
+ /**
+ * Search for logins matching the specified criteria. Called when looking
+ * for logins that might be applicable to a form or authentication request.
+ *
+ * @param count
+ * The number of elements in the array. JS callers can simply use
+ * the array's .length property, and supply an dummy object for
+ * this out param. For example: |findLogins({}, hostname, ...)|
+ * @param aHostname
+ * The hostname to restrict searches to, in URL format. For
+ * example: "http://www.site.com".
+ * To find logins for a given nsIURI, you would typically pass in
+ * its prePath.
+ * @param aActionURL
+ * For form logins, this argument should be the URL to which the
+ * form will be submitted. For protocol logins, specify null.
+ * An empty string ("") will match any value (except null).
+ * @param aHttpRealm
+ * For protocol logins, this argument should be the HTTP Realm
+ * for which the login applies. This is obtained from the
+ * WWW-Authenticate header. See RFC2617. For form logins,
+ * specify null.
+ * An empty string ("") will match any value (except null).
+ * @param logins
+ * An array of nsILoginInfo objects.
+ *
+ * NOTE: This can be called from JS as:
+ * var logins = pwmgr.findLogins({}, hostname, ...);
+ *
+ */
+ void findLogins(out unsigned long count, in AString aHostname,
+ in AString aActionURL, in AString aHttpRealm,
+ [retval, array, size_is(count)] out nsILoginInfo logins);
+
+
+ /**
+ * Search for logins matching the specified criteria, as with
+ * findLogins(). This interface only returns the number of matching
+ * logins (and not the logins themselves), which allows a caller to
+ * check for logins without causing the user to be prompted for a master
+ * password to decrypt the logins.
+ *
+ * @param aHostname
+ * The hostname to restrict searches to. Specify an empty string
+ * to match all hosts. A null value will not match any logins, and
+ * will thus always return a count of 0.
+ * @param aActionURL
+ * The URL to which a form login will be submitted. To match any
+ * form login, specify an empty string. To not match any form
+ * login, specify null.
+ * @param aHttpRealm
+ * The HTTP Realm for which the login applies. To match logins for
+ * any realm, specify an empty string. To not match logins for any
+ * realm, specify null.
+ */
+ unsigned long countLogins(in AString aHostname, in AString aActionURL,
+ in AString aHttpRealm);
+
+
+ /**
+ * Generate results for a userfield autocomplete menu.
+ *
+ * NOTE: This interface is provided for use only by the FormFillController,
+ * which calls it directly. This isn't really ideal, it should
+ * probably be callback registered through the FFC.
+ */
+ void autoCompleteSearchAsync(in AString aSearchString,
+ in nsIAutoCompleteResult aPreviousResult,
+ in nsIDOMHTMLInputElement aElement,
+ in nsIFormAutoCompleteObserver aListener);
+
+ /**
+ * Stop a previously-started async search.
+ */
+ void stopSearch();
+
+ /**
+ * Search for logins in the login manager. An array is always returned;
+ * if there are no logins the array is empty.
+ *
+ * @param count
+ * The number of elements in the array. JS callers can simply use
+ * the array's .length property, and supply an dummy object for
+ * this out param. For example: |searchLogins({}, matchData)|
+ * @param matchData
+ * The data used to search. This does not follow the same
+ * requirements as findLogins for those fields. Wildcard matches are
+ * simply not specified.
+ * @param logins
+ * An array of nsILoginInfo objects.
+ *
+ * NOTE: This can be called from JS as:
+ * var logins = pwmgr.searchLogins({}, matchData);
+ * (|logins| is an array).
+ */
+ void searchLogins(out unsigned long count, in nsIPropertyBag matchData,
+ [retval, array, size_is(count)] out nsILoginInfo logins);
+
+ /**
+ * True when a master password prompt is being displayed.
+ */
+ readonly attribute boolean uiBusy;
+
+ /**
+ * True when the master password has already been entered, and so a caller
+ * can ask for decrypted logins without triggering a prompt.
+ */
+ readonly attribute boolean isLoggedIn;
+};
+
+%{C++
+
+#define NS_LOGINMANAGER_CONTRACTID "@mozilla.org/login-manager;1"
+
+%}
diff --git a/components/passwordmgr/public/nsILoginManagerCrypto.idl b/components/passwordmgr/public/nsILoginManagerCrypto.idl
new file mode 100644
index 000000000..8af36a258
--- /dev/null
+++ b/components/passwordmgr/public/nsILoginManagerCrypto.idl
@@ -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/. */
+
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(2030770e-542e-40cd-8061-cd9d4ad4227f)]
+
+interface nsILoginManagerCrypto : nsISupports {
+
+ const unsigned long ENCTYPE_BASE64 = 0; // obsolete
+ const unsigned long ENCTYPE_SDR = 1;
+
+ /**
+ * encrypt
+ *
+ * @param plainText
+ * The string to be encrypted.
+ *
+ * Encrypts the specified string, returning the ciphertext value.
+ *
+ * NOTE: The current implemention of this inferface simply uses NSS/PSM's
+ * "Secret Decoder Ring" service. It is not recommended for general
+ * purpose encryption/decryption.
+ *
+ * Can throw if the user cancels entry of their master password.
+ */
+ AString encrypt(in AString plainText);
+
+ /**
+ * decrypt
+ *
+ * @param cipherText
+ * The string to be decrypted.
+ *
+ * Decrypts the specified string, returning the plaintext value.
+ *
+ * Can throw if the user cancels entry of their master password, or if the
+ * cipherText value can not be successfully decrypted (eg, if it was
+ * encrypted with some other key).
+ */
+ AString decrypt(in AString cipherText);
+
+ /**
+ * uiBusy
+ *
+ * True when a master password prompt is being displayed.
+ */
+ readonly attribute boolean uiBusy;
+
+ /**
+ * isLoggedIn
+ *
+ * Current login state of the token used for encryption. If the user is
+ * not logged in, performing a crypto operation will result in a master
+ * password prompt.
+ */
+ readonly attribute boolean isLoggedIn;
+
+ /**
+ * defaultEncType
+ *
+ * Default encryption type used by an implementation of this interface.
+ */
+ readonly attribute unsigned long defaultEncType;
+};
diff --git a/components/passwordmgr/public/nsILoginManagerPrompter.idl b/components/passwordmgr/public/nsILoginManagerPrompter.idl
new file mode 100644
index 000000000..c673154d1
--- /dev/null
+++ b/components/passwordmgr/public/nsILoginManagerPrompter.idl
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsILoginInfo;
+interface nsIDOMElement;
+interface nsIDOMWindow;
+
+[scriptable, uuid(425f73b9-b2db-4e8a-88c5-9ac2512934ce)]
+interface nsILoginManagerPrompter : nsISupports {
+ /**
+ * Initialize the prompter. Must be called before using other interfaces.
+ *
+ * @param aWindow
+ * The window in which the user is doing some login-related action that's
+ * resulting in a need to prompt them for something. The prompt
+ * will be associated with this window (or, if a notification bar
+ * is being used, topmost opener in some cases).
+ *
+ * aWindow can be null if there is no associated window, e.g. in a JSM
+ * or Sandbox. In this case there will be no checkbox to save the login
+ * since the window is needed to know if this is a private context.
+ *
+ * If this window is a content window, the corresponding window and browser
+ * elements will be calculated. If this window is a chrome window, the
+ * corresponding browser element needs to be set using setBrowser.
+ */
+ void init(in nsIDOMWindow aWindow);
+
+ /**
+ * The browser this prompter is being created for.
+ * This is required if the init function received a chrome window as argument.
+ */
+ attribute nsIDOMElement browser;
+
+ /**
+ * The opener that was used to open the window passed to init.
+ * The opener can be used to determine in which window the prompt
+ * should be shown. Must be a content window that is not a frame window,
+ * make sure to pass the top window using e.g. window.top.
+ */
+ attribute nsIDOMWindow opener;
+
+ /**
+ * Ask the user if they want to save a login (Yes, Never, Not Now)
+ *
+ * @param aLogin
+ * The login to be saved.
+ */
+ void promptToSavePassword(in nsILoginInfo aLogin);
+
+ /**
+ * Ask the user if they want to change a login's password or username.
+ * If the user consents, modifyLogin() will be called.
+ *
+ * @param aOldLogin
+ * The existing login (with the old password).
+ * @param aNewLogin
+ * The new login.
+ */
+ void promptToChangePassword(in nsILoginInfo aOldLogin,
+ in nsILoginInfo aNewLogin);
+
+ /**
+ * Ask the user if they want to change the password for one of
+ * multiple logins, when the caller can't determine exactly which
+ * login should be changed. If the user consents, modifyLogin() will
+ * be called.
+ *
+ * @param logins
+ * An array of existing logins.
+ * @param count
+ * (length of the array)
+ * @param aNewLogin
+ * The new login.
+ *
+ * Note: Because the caller does not know the username of the login
+ * to be changed, aNewLogin.username and aNewLogin.usernameField
+ * will be set (using the user's selection) before modifyLogin()
+ * is called.
+ */
+ void promptToChangePasswordWithUsernames(
+ [array, size_is(count)] in nsILoginInfo logins,
+ in uint32_t count,
+ in nsILoginInfo aNewLogin);
+};
+%{C++
+
+#define NS_LOGINMANAGERPROMPTER_CONTRACTID "@mozilla.org/login-manager/prompter/;1"
+
+%}
diff --git a/components/passwordmgr/public/nsILoginManagerStorage.idl b/components/passwordmgr/public/nsILoginManagerStorage.idl
new file mode 100644
index 000000000..4ad3dbfe9
--- /dev/null
+++ b/components/passwordmgr/public/nsILoginManagerStorage.idl
@@ -0,0 +1,211 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIFile;
+interface nsILoginInfo;
+interface nsIPropertyBag;
+
+[scriptable, uuid(5df81a93-25e6-4b45-a696-089479e15c7d)]
+
+/*
+ * NOTE: This interface is intended to be implemented by modules
+ * providing storage mechanisms for the login manager.
+ * Other code should use the login manager's interfaces
+ * (nsILoginManager), and should not call storage modules
+ * directly.
+ */
+interface nsILoginManagerStorage : nsISupports {
+ /**
+ * Initialize the component.
+ *
+ * At present, other methods of this interface may be called before the
+ * returned promise is resolved or rejected.
+ *
+ * @return {Promise}
+ * @resolves When initialization is complete.
+ * @rejects JavaScript exception.
+ */
+ jsval initialize();
+
+
+ /**
+ * Ensures that all data has been written to disk and all files are closed.
+ *
+ * At present, this method is called by regression tests only. Finalization
+ * on shutdown is done by observers within the component.
+ *
+ * @return {Promise}
+ * @resolves When finalization is complete.
+ * @rejects JavaScript exception.
+ */
+ jsval terminate();
+
+
+ /**
+ * Store a new login in the storage module.
+ *
+ * @param aLogin
+ * The login to be added.
+ * @return a clone of the login info with the guid set (even if it was not provided).
+ *
+ * Default values for the login's nsILoginMetaInfo properties will be
+ * created. However, if the caller specifies non-default values, they will
+ * be used instead.
+ */
+ nsILoginInfo addLogin(in nsILoginInfo aLogin);
+
+
+ /**
+ * Remove a login from the storage module.
+ *
+ * @param aLogin
+ * The login to be removed.
+ *
+ * The specified login must exactly match a stored login. However, the
+ * values of any nsILoginMetaInfo properties are ignored.
+ */
+ void removeLogin(in nsILoginInfo aLogin);
+
+
+ /**
+ * Modify an existing login in the storage module.
+ *
+ * @param oldLogin
+ * The login to be modified.
+ * @param newLoginData
+ * The new login values (either a nsILoginInfo or nsIProperyBag)
+ *
+ * If newLoginData is a nsILoginInfo, all of the old login's nsILoginInfo
+ * properties are changed to the values from newLoginData (but the old
+ * login's nsILoginMetaInfo properties are unmodified).
+ *
+ * If newLoginData is a nsIPropertyBag, only the specified properties
+ * will be changed. The nsILoginMetaInfo properties of oldLogin can be
+ * changed in this manner.
+ *
+ * If the propertybag contains an item named "timesUsedIncrement", the
+ * login's timesUsed property will be incremented by the item's value.
+ */
+ void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData);
+
+
+ /**
+ * Remove all stored logins.
+ *
+ * The browser sanitization feature allows the user to clear any stored
+ * passwords. This interface allows that to be done without getting each
+ * login first (which might require knowing the master password).
+ *
+ */
+ void removeAllLogins();
+
+
+ /**
+ * Fetch all logins in the login manager. An array is always returned;
+ * if there are no logins the array is empty.
+ *
+ * @param count
+ * The number of elements in the array. JS callers can simply use
+ * the array's .length property and omit this param.
+ * @param logins
+ * An array of nsILoginInfo objects.
+ *
+ * NOTE: This can be called from JS as:
+ * var logins = pwmgr.getAllLogins();
+ * (|logins| is an array).
+ */
+ void getAllLogins([optional] out unsigned long count,
+ [retval, array, size_is(count)] out nsILoginInfo logins);
+
+
+ /**
+ * Search for logins in the login manager. An array is always returned;
+ * if there are no logins the array is empty.
+ *
+ * @param count
+ * The number of elements in the array. JS callers can simply use
+ * the array's .length property, and supply an dummy object for
+ * this out param. For example: |searchLogins({}, matchData)|
+ * @param matchData
+ * The data used to search. This does not follow the same
+ * requirements as findLogins for those fields. Wildcard matches are
+ * simply not specified.
+ * @param logins
+ * An array of nsILoginInfo objects.
+ *
+ * NOTE: This can be called from JS as:
+ * var logins = pwmgr.searchLogins({}, matchData);
+ * (|logins| is an array).
+ */
+ void searchLogins(out unsigned long count, in nsIPropertyBag matchData,
+ [retval, array, size_is(count)] out nsILoginInfo logins);
+
+
+ /**
+ * Search for logins matching the specified criteria. Called when looking
+ * for logins that might be applicable to a form or authentication request.
+ *
+ * @param count
+ * The number of elements in the array. JS callers can simply use
+ * the array's .length property, and supply an dummy object for
+ * this out param. For example: |findLogins({}, hostname, ...)|
+ * @param aHostname
+ * The hostname to restrict searches to, in URL format. For
+ * example: "http://www.site.com".
+ * @param aActionURL
+ * For form logins, this argument should be the URL to which the
+ * form will be submitted. For protocol logins, specify null.
+ * @param aHttpRealm
+ * For protocol logins, this argument should be the HTTP Realm
+ * for which the login applies. This is obtained from the
+ * WWW-Authenticate header. See RFC2617. For form logins,
+ * specify null.
+ * @param logins
+ * An array of nsILoginInfo objects.
+ *
+ * NOTE: This can be called from JS as:
+ * var logins = pwmgr.findLogins({}, hostname, ...);
+ *
+ */
+ void findLogins(out unsigned long count, in AString aHostname,
+ in AString aActionURL, in AString aHttpRealm,
+ [retval, array, size_is(count)] out nsILoginInfo logins);
+
+
+ /**
+ * Search for logins matching the specified criteria, as with
+ * findLogins(). This interface only returns the number of matching
+ * logins (and not the logins themselves), which allows a caller to
+ * check for logins without causing the user to be prompted for a master
+ * password to decrypt the logins.
+ *
+ * @param aHostname
+ * The hostname to restrict searches to. Specify an empty string
+ * to match all hosts. A null value will not match any logins, and
+ * will thus always return a count of 0.
+ * @param aActionURL
+ * The URL to which a form login will be submitted. To match any
+ * form login, specify an empty string. To not match any form
+ * login, specify null.
+ * @param aHttpRealm
+ * The HTTP Realm for which the login applies. To match logins for
+ * any realm, specify an empty string. To not match logins for any
+ * realm, specify null.
+ */
+ unsigned long countLogins(in AString aHostname, in AString aActionURL,
+ in AString aHttpRealm);
+ /**
+ * True when a master password prompt is being shown.
+ */
+ readonly attribute boolean uiBusy;
+
+ /**
+ * True when the master password has already been entered, and so a caller
+ * can ask for decrypted logins without triggering a prompt.
+ */
+ readonly attribute boolean isLoggedIn;
+};
diff --git a/components/passwordmgr/public/nsILoginMetaInfo.idl b/components/passwordmgr/public/nsILoginMetaInfo.idl
new file mode 100644
index 000000000..92d8f2bc8
--- /dev/null
+++ b/components/passwordmgr/public/nsILoginMetaInfo.idl
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(20d8eb40-c494-497f-b2a6-aaa32f807ebd)]
+
+/**
+ * An object containing metainfo for a login stored by the login manager.
+ *
+ * Code using login manager can generally ignore this interface. When adding
+ * logins, default value will be created. When modifying logins, these
+ * properties will be unchanged unless a change is explicitly requested [by
+ * using modifyLogin() with a nsIPropertyBag]. When deleting a login or
+ * comparing logins, these properties are ignored.
+ */
+interface nsILoginMetaInfo : nsISupports {
+ /**
+ * The GUID to uniquely identify the login. This can be any arbitrary
+ * string, but a format as created by nsIUUIDGenerator is recommended.
+ * For example, "{d4e1a1f6-5ea0-40ee-bff5-da57982f21cf}"
+ *
+ * addLogin will generate a random value unless a value is provided.
+ *
+ * addLogin and modifyLogin will throw if the GUID already exists.
+ */
+ attribute AString guid;
+
+ /**
+ * The time, in Unix Epoch milliseconds, when the login was first created.
+ */
+ attribute unsigned long long timeCreated;
+
+ /**
+ * The time, in Unix Epoch milliseconds, when the login was last submitted
+ * in a form or used to begin an HTTP auth session.
+ */
+ attribute unsigned long long timeLastUsed;
+
+ /**
+ * The time, in Unix Epoch milliseconds, when the login was last modified.
+ *
+ * Contrary to what the name may suggest, this attribute takes into account
+ * not only the password but also the username attribute.
+ */
+ attribute unsigned long long timePasswordChanged;
+
+ /**
+ * The number of times the login was submitted in a form or used to begin
+ * an HTTP auth session.
+ */
+ attribute unsigned long timesUsed;
+};
diff --git a/components/passwordmgr/src/InsecurePasswordUtils.jsm b/components/passwordmgr/src/InsecurePasswordUtils.jsm
new file mode 100644
index 000000000..abd7c7fb4
--- /dev/null
+++ b/components/passwordmgr/src/InsecurePasswordUtils.jsm
@@ -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/. */
+
+this.EXPORTED_SYMBOLS = [ "InsecurePasswordUtils" ];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+const STRINGS_URI = "chrome://global/locale/security/security.properties";
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "devtools",
+ "resource://devtools/shared/Loader.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
+ "@mozilla.org/contentsecuritymanager;1",
+ "nsIContentSecurityManager");
+XPCOMUtils.defineLazyServiceGetter(this, "gScriptSecurityManager",
+ "@mozilla.org/scriptsecuritymanager;1",
+ "nsIScriptSecurityManager");
+XPCOMUtils.defineLazyGetter(this, "WebConsoleUtils", () => {
+ return this.devtools.require("devtools/server/actors/utils/webconsole-utils").Utils;
+});
+
+/*
+ * A module that provides utility functions for form security.
+ *
+ * Note:
+ * This module uses isSecureContextIfOpenerIgnored instead of isSecureContext.
+ *
+ * We don't want to expose JavaScript APIs in a non-Secure Context even if
+ * the context is only insecure because the windows has an insecure opener.
+ * Doing so prevents sites from implementing postMessage workarounds to enable
+ * an insecure opener to gain access to Secure Context-only APIs. However,
+ * in the case of form fields such as password fields we don't need to worry
+ * about whether the opener is secure or not. In fact to flag a password
+ * field as insecure in such circumstances would unnecessarily confuse our
+ * users.
+ */
+this.InsecurePasswordUtils = {
+ _formRootsWarned: new WeakMap(),
+ _sendWebConsoleMessage(messageTag, domDoc) {
+ let windowId = WebConsoleUtils.getInnerWindowId(domDoc.defaultView);
+ let category = "Insecure Password Field";
+ // All web console messages are warnings for now.
+ let flag = Ci.nsIScriptError.warningFlag;
+ let bundle = Services.strings.createBundle(STRINGS_URI);
+ let message = bundle.GetStringFromName(messageTag);
+ let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
+ consoleMsg.initWithWindowID(message, domDoc.location.href, 0, 0, 0, flag, category, windowId);
+
+ Services.console.logMessage(consoleMsg);
+ },
+
+ /**
+ * Gets the security state of the passed form.
+ *
+ * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
+ *
+ * @returns {Object} An object with the following boolean values:
+ * isFormSubmitHTTP: if the submit action is an http:// URL
+ * isFormSubmitSecure: if the submit action URL is secure,
+ * either because it is HTTPS or because its origin is considered trustworthy
+ */
+ _checkFormSecurity(aForm) {
+ let isFormSubmitHTTP = false, isFormSubmitSecure = false;
+ if (aForm.rootElement instanceof Ci.nsIDOMHTMLFormElement) {
+ let uri = Services.io.newURI(aForm.rootElement.action || aForm.rootElement.baseURI,
+ null, null);
+ let principal = gScriptSecurityManager.getCodebasePrincipal(uri);
+
+ if (uri.schemeIs("http")) {
+ isFormSubmitHTTP = true;
+ if (gContentSecurityManager.isOriginPotentiallyTrustworthy(principal)) {
+ isFormSubmitSecure = true;
+ }
+ } else {
+ isFormSubmitSecure = true;
+ }
+ }
+
+ return { isFormSubmitHTTP, isFormSubmitSecure };
+ },
+
+ /**
+ * Checks if there are insecure password fields present on the form's document
+ * i.e. passwords inside forms with http action, inside iframes with http src,
+ * or on insecure web pages.
+ *
+ * @param {FormLike} aForm A form-like object. @See {LoginFormFactory}
+ * @return {boolean} whether the form is secure
+ */
+ isFormSecure(aForm) {
+ // Ignores window.opener, see top level documentation.
+ let isSafePage = aForm.ownerDocument.defaultView.isSecureContextIfOpenerIgnored;
+ let { isFormSubmitSecure, isFormSubmitHTTP } = this._checkFormSecurity(aForm);
+
+ return isSafePage && (isFormSubmitSecure || !isFormSubmitHTTP);
+ },
+
+ /**
+ * Report insecure password fields in a form to the web console to warn developers.
+ *
+ * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
+ */
+ reportInsecurePasswords(aForm) {
+ if (this._formRootsWarned.has(aForm.rootElement) ||
+ this._formRootsWarned.get(aForm.rootElement)) {
+ return;
+ }
+
+ let domDoc = aForm.ownerDocument;
+ // Ignores window.opener, see top level documentation.
+ let isSafePage = domDoc.defaultView.isSecureContextIfOpenerIgnored;
+
+ let { isFormSubmitHTTP, isFormSubmitSecure } = this._checkFormSecurity(aForm);
+
+ if (!isSafePage) {
+ if (domDoc.defaultView == domDoc.defaultView.parent) {
+ this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
+ } else {
+ this._sendWebConsoleMessage("InsecurePasswordsPresentOnIframe", domDoc);
+ }
+ this._formRootsWarned.set(aForm.rootElement, true);
+ } else if (isFormSubmitHTTP && !isFormSubmitSecure) {
+ this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
+ this._formRootsWarned.set(aForm.rootElement, true);
+ }
+
+ // The safety of a password field determined by the form action and the page protocol
+ let passwordSafety;
+ if (isSafePage) {
+ if (isFormSubmitSecure) {
+ passwordSafety = 0;
+ } else if (isFormSubmitHTTP) {
+ passwordSafety = 1;
+ } else {
+ passwordSafety = 2;
+ }
+ } else if (isFormSubmitSecure) {
+ passwordSafety = 3;
+ } else if (isFormSubmitHTTP) {
+ passwordSafety = 4;
+ } else {
+ passwordSafety = 5;
+ }
+
+ },
+};
diff --git a/components/passwordmgr/src/LoginHelper.jsm b/components/passwordmgr/src/LoginHelper.jsm
new file mode 100644
index 000000000..c6cd40915
--- /dev/null
+++ b/components/passwordmgr/src/LoginHelper.jsm
@@ -0,0 +1,725 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Contains functions shared by different Login Manager components.
+ *
+ * This JavaScript module exists in order to share code between the different
+ * XPCOM components that constitute the Login Manager, including implementations
+ * of nsILoginManager and nsILoginManagerStorage.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "LoginHelper",
+];
+
+// Globals
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// LoginHelper
+
+/**
+ * Contains functions shared by different Login Manager components.
+ */
+this.LoginHelper = {
+ /**
+ * Warning: these only update if a logger was created.
+ */
+ debug: Services.prefs.getBoolPref("signon.debug"),
+ formlessCaptureEnabled: Services.prefs.getBoolPref("signon.formlessCapture.enabled"),
+ schemeUpgrades: Services.prefs.getBoolPref("signon.schemeUpgrades"),
+ insecureAutofill: Services.prefs.getBoolPref("signon.autofillForms.http"),
+ showInsecureFieldWarning: Services.prefs.getBoolPref("security.insecure_field_warning.contextual.enabled"),
+
+ createLogger(aLogPrefix) {
+ let getMaxLogLevel = () => {
+ return this.debug ? "debug" : "warn";
+ };
+
+ // Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
+ let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
+ let consoleOptions = {
+ maxLogLevel: getMaxLogLevel(),
+ prefix: aLogPrefix,
+ };
+ let logger = new ConsoleAPI(consoleOptions);
+
+ // Watch for pref changes and update this.debug and the maxLogLevel for created loggers
+ Services.prefs.addObserver("signon.", () => {
+ this.debug = Services.prefs.getBoolPref("signon.debug");
+ this.formlessCaptureEnabled = Services.prefs.getBoolPref("signon.formlessCapture.enabled");
+ this.schemeUpgrades = Services.prefs.getBoolPref("signon.schemeUpgrades");
+ this.insecureAutofill = Services.prefs.getBoolPref("signon.autofillForms.http");
+ logger.maxLogLevel = getMaxLogLevel();
+ }, false);
+
+ Services.prefs.addObserver("security.insecure_field_warning.", () => {
+ this.showInsecureFieldWarning = Services.prefs.getBoolPref("security.insecure_field_warning.contextual.enabled");
+ }, false);
+
+ return logger;
+ },
+
+ /**
+ * Due to the way the signons2.txt file is formatted, we need to make
+ * sure certain field values or characters do not cause the file to
+ * be parsed incorrectly. Reject hostnames that we can't store correctly.
+ *
+ * @throws String with English message in case validation failed.
+ */
+ checkHostnameValue(aHostname) {
+ // Nulls are invalid, as they don't round-trip well. Newlines are also
+ // invalid for any field stored as plaintext, and a hostname made of a
+ // single dot cannot be stored in the legacy format.
+ if (aHostname == "." ||
+ aHostname.indexOf("\r") != -1 ||
+ aHostname.indexOf("\n") != -1 ||
+ aHostname.indexOf("\0") != -1) {
+ throw new Error("Invalid hostname");
+ }
+ },
+
+ /**
+ * Due to the way the signons2.txt file is formatted, we need to make
+ * sure certain field values or characters do not cause the file to
+ * be parsed incorrectly. Reject logins that we can't store correctly.
+ *
+ * @throws String with English message in case validation failed.
+ */
+ checkLoginValues(aLogin) {
+ function badCharacterPresent(l, c) {
+ return ((l.formSubmitURL && l.formSubmitURL.indexOf(c) != -1) ||
+ (l.httpRealm && l.httpRealm.indexOf(c) != -1) ||
+ l.hostname.indexOf(c) != -1 ||
+ l.usernameField.indexOf(c) != -1 ||
+ l.passwordField.indexOf(c) != -1);
+ }
+
+ // Nulls are invalid, as they don't round-trip well.
+ // Mostly not a formatting problem, although ".\0" can be quirky.
+ if (badCharacterPresent(aLogin, "\0")) {
+ throw new Error("login values can't contain nulls");
+ }
+
+ // In theory these nulls should just be rolled up into the encrypted
+ // values, but nsISecretDecoderRing doesn't use nsStrings, so the
+ // nulls cause truncation. Check for them here just to avoid
+ // unexpected round-trip surprises.
+ if (aLogin.username.indexOf("\0") != -1 ||
+ aLogin.password.indexOf("\0") != -1) {
+ throw new Error("login values can't contain nulls");
+ }
+
+ // Newlines are invalid for any field stored as plaintext.
+ if (badCharacterPresent(aLogin, "\r") ||
+ badCharacterPresent(aLogin, "\n")) {
+ throw new Error("login values can't contain newlines");
+ }
+
+ // A line with just a "." can have special meaning.
+ if (aLogin.usernameField == "." ||
+ aLogin.formSubmitURL == ".") {
+ throw new Error("login values can't be periods");
+ }
+
+ // A hostname with "\ \(" won't roundtrip.
+ // eg host="foo (", realm="bar" --> "foo ( (bar)"
+ // vs host="foo", realm=" (bar" --> "foo ( (bar)"
+ if (aLogin.hostname.indexOf(" (") != -1) {
+ throw new Error("bad parens in hostname");
+ }
+ },
+
+ /**
+ * Returns a new XPCOM property bag with the provided properties.
+ *
+ * @param {Object} aProperties
+ * Each property of this object is copied to the property bag. This
+ * parameter can be omitted to return an empty property bag.
+ *
+ * @return A new property bag, that is an instance of nsIWritablePropertyBag,
+ * nsIWritablePropertyBag2, nsIPropertyBag, and nsIPropertyBag2.
+ */
+ newPropertyBag(aProperties) {
+ let propertyBag = Cc["@mozilla.org/hash-property-bag;1"]
+ .createInstance(Ci.nsIWritablePropertyBag);
+ if (aProperties) {
+ for (let [name, value] of Object.entries(aProperties)) {
+ propertyBag.setProperty(name, value);
+ }
+ }
+ return propertyBag.QueryInterface(Ci.nsIPropertyBag)
+ .QueryInterface(Ci.nsIPropertyBag2)
+ .QueryInterface(Ci.nsIWritablePropertyBag2);
+ },
+
+ /**
+ * Helper to avoid the `count` argument and property bags when calling
+ * Services.logins.searchLogins from JS.
+ *
+ * @param {Object} aSearchOptions - A regular JS object to copy to a property bag before searching
+ * @return {nsILoginInfo[]} - The result of calling searchLogins.
+ */
+ searchLoginsWithObject(aSearchOptions) {
+ return Services.logins.searchLogins({}, this.newPropertyBag(aSearchOptions));
+ },
+
+ /**
+ * @param {String} aLoginOrigin - An origin value from a stored login's
+ * hostname or formSubmitURL properties.
+ * @param {String} aSearchOrigin - The origin that was are looking to match
+ * with aLoginOrigin. This would normally come
+ * from a form or page that we are considering.
+ * @param {nsILoginFindOptions} aOptions - Options to affect whether the origin
+ * from the login (aLoginOrigin) is a
+ * match for the origin we're looking
+ * for (aSearchOrigin).
+ */
+ isOriginMatching(aLoginOrigin, aSearchOrigin, aOptions = {
+ schemeUpgrades: false,
+ }) {
+ if (aLoginOrigin == aSearchOrigin) {
+ return true;
+ }
+
+ if (!aOptions) {
+ return false;
+ }
+
+ if (aOptions.schemeUpgrades) {
+ try {
+ let loginURI = Services.io.newURI(aLoginOrigin, null, null);
+ let searchURI = Services.io.newURI(aSearchOrigin, null, null);
+ if (loginURI.scheme == "http" && searchURI.scheme == "https" &&
+ loginURI.hostPort == searchURI.hostPort) {
+ return true;
+ }
+ } catch (ex) {
+ // newURI will throw for some values
+ return false;
+ }
+ }
+
+ return false;
+ },
+
+ doLoginsMatch(aLogin1, aLogin2, {
+ ignorePassword = false,
+ ignoreSchemes = false,
+ }) {
+ if (aLogin1.httpRealm != aLogin2.httpRealm ||
+ aLogin1.username != aLogin2.username)
+ return false;
+
+ if (!ignorePassword && aLogin1.password != aLogin2.password)
+ return false;
+
+ if (ignoreSchemes) {
+ let hostname1URI = Services.io.newURI(aLogin1.hostname, null, null);
+ let hostname2URI = Services.io.newURI(aLogin2.hostname, null, null);
+ if (hostname1URI.hostPort != hostname2URI.hostPort)
+ return false;
+
+ if (aLogin1.formSubmitURL != "" && aLogin2.formSubmitURL != "" &&
+ Services.io.newURI(aLogin1.formSubmitURL, null, null).hostPort !=
+ Services.io.newURI(aLogin2.formSubmitURL, null, null).hostPort)
+ return false;
+ } else {
+ if (aLogin1.hostname != aLogin2.hostname)
+ return false;
+
+ // If either formSubmitURL is blank (but not null), then match.
+ if (aLogin1.formSubmitURL != "" && aLogin2.formSubmitURL != "" &&
+ aLogin1.formSubmitURL != aLogin2.formSubmitURL)
+ return false;
+ }
+
+ // The .usernameField and .passwordField values are ignored.
+
+ return true;
+ },
+
+ /**
+ * Creates a new login object that results by modifying the given object with
+ * the provided data.
+ *
+ * @param aOldStoredLogin
+ * Existing nsILoginInfo object to modify.
+ * @param aNewLoginData
+ * The new login values, either as nsILoginInfo or nsIProperyBag.
+ *
+ * @return The newly created nsILoginInfo object.
+ *
+ * @throws String with English message in case validation failed.
+ */
+ buildModifiedLogin(aOldStoredLogin, aNewLoginData) {
+ function bagHasProperty(aPropName) {
+ try {
+ aNewLoginData.getProperty(aPropName);
+ return true;
+ } catch (ex) { }
+ return false;
+ }
+
+ aOldStoredLogin.QueryInterface(Ci.nsILoginMetaInfo);
+
+ let newLogin;
+ if (aNewLoginData instanceof Ci.nsILoginInfo) {
+ // Clone the existing login to get its nsILoginMetaInfo, then init it
+ // with the replacement nsILoginInfo data from the new login.
+ newLogin = aOldStoredLogin.clone();
+ newLogin.init(aNewLoginData.hostname,
+ aNewLoginData.formSubmitURL, aNewLoginData.httpRealm,
+ aNewLoginData.username, aNewLoginData.password,
+ aNewLoginData.usernameField, aNewLoginData.passwordField);
+ newLogin.QueryInterface(Ci.nsILoginMetaInfo);
+
+ // Automatically update metainfo when password is changed.
+ if (newLogin.password != aOldStoredLogin.password) {
+ newLogin.timePasswordChanged = Date.now();
+ }
+ } else if (aNewLoginData instanceof Ci.nsIPropertyBag) {
+ // Clone the existing login, along with all its properties.
+ newLogin = aOldStoredLogin.clone();
+ newLogin.QueryInterface(Ci.nsILoginMetaInfo);
+
+ // Automatically update metainfo when password is changed.
+ // (Done before the main property updates, lest the caller be
+ // explicitly updating both .password and .timePasswordChanged)
+ if (bagHasProperty("password")) {
+ let newPassword = aNewLoginData.getProperty("password");
+ if (newPassword != aOldStoredLogin.password) {
+ newLogin.timePasswordChanged = Date.now();
+ }
+ }
+
+ let propEnum = aNewLoginData.enumerator;
+ while (propEnum.hasMoreElements()) {
+ let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
+ switch (prop.name) {
+ // nsILoginInfo
+ case "hostname":
+ case "httpRealm":
+ case "formSubmitURL":
+ case "username":
+ case "password":
+ case "usernameField":
+ case "passwordField":
+ // nsILoginMetaInfo
+ case "guid":
+ case "timeCreated":
+ case "timeLastUsed":
+ case "timePasswordChanged":
+ case "timesUsed":
+ newLogin[prop.name] = prop.value;
+ break;
+
+ // Fake property, allows easy incrementing.
+ case "timesUsedIncrement":
+ newLogin.timesUsed += prop.value;
+ break;
+
+ // Fail if caller requests setting an unknown property.
+ default:
+ throw new Error("Unexpected propertybag item: " + prop.name);
+ }
+ }
+ } else {
+ throw new Error("newLoginData needs an expected interface!");
+ }
+
+ // Sanity check the login
+ if (newLogin.hostname == null || newLogin.hostname.length == 0) {
+ throw new Error("Can't add a login with a null or empty hostname.");
+ }
+
+ // For logins w/o a username, set to "", not null.
+ if (newLogin.username == null) {
+ throw new Error("Can't add a login with a null username.");
+ }
+
+ if (newLogin.password == null || newLogin.password.length == 0) {
+ throw new Error("Can't add a login with a null or empty password.");
+ }
+
+ if (newLogin.formSubmitURL || newLogin.formSubmitURL == "") {
+ // We have a form submit URL. Can't have a HTTP realm.
+ if (newLogin.httpRealm != null) {
+ throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
+ }
+ } else if (newLogin.httpRealm) {
+ // We have a HTTP realm. Can't have a form submit URL.
+ if (newLogin.formSubmitURL != null) {
+ throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
+ }
+ } else {
+ // Need one or the other!
+ throw new Error("Can't add a login without a httpRealm or formSubmitURL.");
+ }
+
+ // Throws if there are bogus values.
+ this.checkLoginValues(newLogin);
+
+ return newLogin;
+ },
+
+ /**
+ * Removes duplicates from a list of logins while preserving the sort order.
+ *
+ * @param {nsILoginInfo[]} logins
+ * A list of logins we want to deduplicate.
+ * @param {string[]} [uniqueKeys = ["username", "password"]]
+ * A list of login attributes to use as unique keys for the deduplication.
+ * @param {string[]} [resolveBy = ["timeLastUsed"]]
+ * Ordered array of keyword strings used to decide which of the
+ * duplicates should be used. "scheme" would prefer the login that has
+ * a scheme matching `preferredOrigin`'s if there are two logins with
+ * the same `uniqueKeys`. The default preference to distinguish two
+ * logins is `timeLastUsed`. If there is no preference between two
+ * logins, the first one found wins.
+ * @param {string} [preferredOrigin = undefined]
+ * String representing the origin to use for preferring one login over
+ * another when they are dupes. This is used with "scheme" for
+ * `resolveBy` so the scheme from this origin will be preferred.
+ *
+ * @returns {nsILoginInfo[]} list of unique logins.
+ */
+ dedupeLogins(logins, uniqueKeys = ["username", "password"],
+ resolveBy = ["timeLastUsed"],
+ preferredOrigin = undefined) {
+ const KEY_DELIMITER = ":";
+
+ if (!preferredOrigin && resolveBy.includes("scheme")) {
+ throw new Error("dedupeLogins: `preferredOrigin` is required in order to " +
+ "prefer schemes which match it.");
+ }
+
+ let preferredOriginScheme;
+ if (preferredOrigin) {
+ try {
+ preferredOriginScheme = Services.io.newURI(preferredOrigin, null, null).scheme;
+ } catch (ex) {
+ // Handle strings that aren't valid URIs
+ }
+ }
+
+ if (!preferredOriginScheme && resolveBy.includes("scheme")) {
+ log.warn("dedupeLogins: Deduping with a scheme preference but couldn't " +
+ "get the preferred origin scheme.");
+ }
+
+ // We use a Map to easily lookup logins by their unique keys.
+ let loginsByKeys = new Map();
+
+ // Generate a unique key string from a login.
+ function getKey(login, uniqueKeys) {
+ return uniqueKeys.reduce((prev, key) => prev + KEY_DELIMITER + login[key], "");
+ }
+
+ /**
+ * @return {bool} whether `login` is preferred over its duplicate (considering `uniqueKeys`)
+ * `existingLogin`.
+ *
+ * `resolveBy` is a sorted array so we can return true the first time `login` is preferred
+ * over the existingLogin.
+ */
+ function isLoginPreferred(existingLogin, login) {
+ if (!resolveBy || resolveBy.length == 0) {
+ // If there is no preference, prefer the existing login.
+ return false;
+ }
+
+ for (let preference of resolveBy) {
+ switch (preference) {
+ case "scheme": {
+ if (!preferredOriginScheme) {
+ break;
+ }
+
+ try {
+ // Only `hostname` is currently considered
+ let existingLoginURI = Services.io.newURI(existingLogin.hostname, null, null);
+ let loginURI = Services.io.newURI(login.hostname, null, null);
+ // If the schemes of the two logins are the same or neither match the
+ // preferredOriginScheme then we have no preference and look at the next resolveBy.
+ if (loginURI.scheme == existingLoginURI.scheme ||
+ (loginURI.scheme != preferredOriginScheme &&
+ existingLoginURI.scheme != preferredOriginScheme)) {
+ break;
+ }
+
+ return loginURI.scheme == preferredOriginScheme;
+ } catch (ex) {
+ // Some URLs aren't valid nsIURI
+ log.debug("dedupeLogins/shouldReplaceExisting: Error comparing schemes:",
+ existingLogin.hostname, login.hostname,
+ "preferredOrigin:", preferredOrigin, ex);
+ }
+ break;
+ }
+ case "timeLastUsed":
+ case "timePasswordChanged": {
+ // If we find a more recent login for the same key, replace the existing one.
+ let loginDate = login.QueryInterface(Ci.nsILoginMetaInfo)[preference];
+ let storedLoginDate = existingLogin.QueryInterface(Ci.nsILoginMetaInfo)[preference];
+ if (loginDate == storedLoginDate) {
+ break;
+ }
+
+ return loginDate > storedLoginDate;
+ }
+ default: {
+ throw new Error("dedupeLogins: Invalid resolveBy preference: " + preference);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ for (let login of logins) {
+ let key = getKey(login, uniqueKeys);
+
+ if (loginsByKeys.has(key)) {
+ if (!isLoginPreferred(loginsByKeys.get(key), login)) {
+ // If there is no preference for the new login, use the existing one.
+ continue;
+ }
+ }
+ loginsByKeys.set(key, login);
+ }
+
+ // Return the map values in the form of an array.
+ return [...loginsByKeys.values()];
+ },
+
+ /**
+ * Open the password manager window.
+ *
+ * @param {Window} window
+ * the window from where we want to open the dialog
+ *
+ * @param {string} [filterString=""]
+ * the filterString parameter to pass to the login manager dialog
+ */
+ openPasswordManager(window, filterString = "") {
+ let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
+ if (win) {
+ win.setFilter(filterString);
+ win.focus();
+ } else {
+ window.openDialog("chrome://passwordmgr/content/passwordManager.xul",
+ "Toolkit:PasswordManager", "",
+ {filterString : filterString});
+ }
+ },
+
+ /**
+ * Checks if a field type is username compatible.
+ *
+ * @param {Element} element
+ * the field we want to check.
+ *
+ * @returns {Boolean} true if the field type is one
+ * of the username types.
+ */
+ isUsernameFieldType(element) {
+ if (!(element instanceof Ci.nsIDOMHTMLInputElement))
+ return false;
+
+ let fieldType = (element.hasAttribute("type") ?
+ element.getAttribute("type").toLowerCase() :
+ element.type);
+ if (fieldType == "text" ||
+ fieldType == "email" ||
+ fieldType == "url" ||
+ fieldType == "tel" ||
+ fieldType == "number") {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Add the login to the password manager if a similar one doesn't already exist. Merge it
+ * otherwise with the similar existing ones.
+ * @param {Object} loginData - the data about the login that needs to be added.
+ * @returns {nsILoginInfo} the newly added login, or null if no login was added.
+ * Note that we will also return null if an existing login
+ * was modified.
+ */
+ maybeImportLogin(loginData) {
+ // create a new login
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
+ login.init(loginData.hostname,
+ loginData.formSubmitURL || (typeof(loginData.httpRealm) == "string" ? null : ""),
+ typeof(loginData.httpRealm) == "string" ? loginData.httpRealm : null,
+ loginData.username,
+ loginData.password,
+ loginData.usernameElement || "",
+ loginData.passwordElement || "");
+
+ login.QueryInterface(Ci.nsILoginMetaInfo);
+ login.timeCreated = loginData.timeCreated;
+ login.timeLastUsed = loginData.timeLastUsed || loginData.timeCreated;
+ login.timePasswordChanged = loginData.timePasswordChanged || loginData.timeCreated;
+ login.timesUsed = loginData.timesUsed || 1;
+ // While here we're passing formSubmitURL and httpRealm, they could be empty/null and get
+ // ignored in that case, leading to multiple logins for the same username.
+ let existingLogins = Services.logins.findLogins({}, login.hostname,
+ login.formSubmitURL,
+ login.httpRealm);
+ // Check for an existing login that matches *including* the password.
+ // If such a login exists, we do not need to add a new login.
+ if (existingLogins.some(l => login.matches(l, false /* ignorePassword */))) {
+ return null;
+ }
+ // Now check for a login with the same username, where it may be that we have an
+ // updated password.
+ let foundMatchingLogin = false;
+ for (let existingLogin of existingLogins) {
+ if (login.username == existingLogin.username) {
+ foundMatchingLogin = true;
+ existingLogin.QueryInterface(Ci.nsILoginMetaInfo);
+ if (login.password != existingLogin.password &
+ login.timePasswordChanged > existingLogin.timePasswordChanged) {
+ // if a login with the same username and different password already exists and it's older
+ // than the current one, update its password and timestamp.
+ let propBag = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ propBag.setProperty("password", login.password);
+ propBag.setProperty("timePasswordChanged", login.timePasswordChanged);
+ Services.logins.modifyLogin(existingLogin, propBag);
+ }
+ }
+ }
+ // if the new login is an update or is older than an exiting login, don't add it.
+ if (foundMatchingLogin) {
+ return null;
+ }
+ return Services.logins.addLogin(login);
+ },
+
+ /**
+ * Convert an array of nsILoginInfo to vanilla JS objects suitable for
+ * sending over IPC.
+ *
+ * NB: All members of nsILoginInfo and nsILoginMetaInfo are strings.
+ */
+ loginsToVanillaObjects(logins) {
+ return logins.map(this.loginToVanillaObject);
+ },
+
+ /**
+ * Same as above, but for a single login.
+ */
+ loginToVanillaObject(login) {
+ let obj = {};
+ for (let i in login.QueryInterface(Ci.nsILoginMetaInfo)) {
+ if (typeof login[i] !== 'function') {
+ obj[i] = login[i];
+ }
+ }
+
+ return obj;
+ },
+
+ /**
+ * Convert an object received from IPC into an nsILoginInfo (with guid).
+ */
+ vanillaObjectToLogin(login) {
+ let formLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
+ createInstance(Ci.nsILoginInfo);
+ formLogin.init(login.hostname, login.formSubmitURL,
+ login.httpRealm, login.username,
+ login.password, login.usernameField,
+ login.passwordField);
+
+ formLogin.QueryInterface(Ci.nsILoginMetaInfo);
+ for (let prop of ["guid", "timeCreated", "timeLastUsed", "timePasswordChanged", "timesUsed"]) {
+ formLogin[prop] = login[prop];
+ }
+ return formLogin;
+ },
+
+ /**
+ * As above, but for an array of objects.
+ */
+ vanillaObjectsToLogins(logins) {
+ return logins.map(this.vanillaObjectToLogin);
+ },
+
+ removeLegacySignonFiles() {
+ const {Constants, Path, File} = Cu.import("resource://gre/modules/osfile.jsm").OS;
+
+ const profileDir = Constants.Path.profileDir;
+ const defaultSignonFilePrefs = new Map([
+ ["signon.SignonFileName", "signons.txt"],
+ ["signon.SignonFileName2", "signons2.txt"],
+ ["signon.SignonFileName3", "signons3.txt"]
+ ]);
+ const toDeletes = new Set();
+
+ for (let [pref, val] of defaultSignonFilePrefs.entries()) {
+ toDeletes.add(Path.join(profileDir, val));
+
+ try {
+ let signonFile = Services.prefs.getCharPref(pref);
+
+ toDeletes.add(Path.join(profileDir, signonFile));
+ Services.prefs.clearUserPref(pref);
+ } catch (e) {}
+ }
+
+ for (let file of toDeletes) {
+ File.remove(file);
+ }
+ },
+
+ /**
+ * Returns true if the user has a master password set and false otherwise.
+ */
+ isMasterPasswordSet() {
+ let secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
+ getService(Ci.nsIPKCS11ModuleDB);
+ let slot = secmodDB.findSlotByName("");
+ if (!slot) {
+ return false;
+ }
+ let hasMP = slot.status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED &&
+ slot.status != Ci.nsIPKCS11Slot.SLOT_READY;
+ return hasMP;
+ },
+
+ /**
+ * Send a notification when stored data is changed.
+ */
+ notifyStorageChanged(changeType, data) {
+ let dataObject = data;
+ // Can't pass a raw JS string or array though notifyObservers(). :-(
+ if (Array.isArray(data)) {
+ dataObject = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ for (let i = 0; i < data.length; i++) {
+ dataObject.appendElement(data[i], false);
+ }
+ } else if (typeof(data) == "string") {
+ dataObject = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ dataObject.data = data;
+ }
+ Services.obs.notifyObservers(dataObject, "passwordmgr-storage-changed", changeType);
+ }
+};
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let logger = LoginHelper.createLogger("LoginHelper");
+ return logger;
+});
diff --git a/components/passwordmgr/src/LoginImport.jsm b/components/passwordmgr/src/LoginImport.jsm
new file mode 100644
index 000000000..05f0220ca
--- /dev/null
+++ b/components/passwordmgr/src/LoginImport.jsm
@@ -0,0 +1,172 @@
+/* -*- 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/. */
+
+/**
+ * Provides an object that has a method to import login-related data from the
+ * previous SQLite storage format.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "LoginImport",
+];
+
+// 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/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
+ "resource://gre/modules/Sqlite.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+
+// LoginImport
+
+/**
+ * Provides an object that has a method to import login-related data from the
+ * previous SQLite storage format.
+ *
+ * @param aStore
+ * LoginStore object where imported data will be added.
+ * @param aPath
+ * String containing the file path of the SQLite login database.
+ */
+this.LoginImport = function (aStore, aPath) {
+ this.store = aStore;
+ this.path = aPath;
+};
+
+this.LoginImport.prototype = {
+ /**
+ * LoginStore object where imported data will be added.
+ */
+ store: null,
+
+ /**
+ * String containing the file path of the SQLite login database.
+ */
+ path: null,
+
+ /**
+ * Imports login-related data from the previous SQLite storage format.
+ */
+ import: Task.async(function* () {
+ // We currently migrate data directly from the database to the JSON store at
+ // first run, then we set a preference to prevent repeating the import.
+ // Thus, merging with existing data is not a use case we support. This
+ // restriction might be removed to support re-importing passwords set by an
+ // old version by flipping the import preference and restarting.
+ if (this.store.data.logins.length > 0 ||
+ this.store.data.disabledHosts.length > 0) {
+ throw new Error("Unable to import saved passwords because some data " +
+ "has already been imported or saved.");
+ }
+
+ // When a timestamp is not specified, we will use the same reference time.
+ let referenceTimeMs = Date.now();
+
+ let connection = yield Sqlite.openConnection({ path: this.path });
+ try {
+ let schemaVersion = yield connection.getSchemaVersion();
+
+ // We support importing database schema versions from 3 onwards.
+ // Version 3 was implemented in bug 316084 (Firefox 3.6, March 2009).
+ // Version 4 was implemented in bug 465636 (Firefox 4, March 2010).
+ // Version 5 was implemented in bug 718817 (Firefox 13, February 2012).
+ if (schemaVersion < 3) {
+ throw new Error("Unable to import saved passwords because " +
+ "the existing profile is too old.");
+ }
+
+ let rows = yield connection.execute("SELECT * FROM moz_logins");
+ for (let row of rows) {
+ try {
+ let hostname = row.getResultByName("hostname");
+ let httpRealm = row.getResultByName("httpRealm");
+ let formSubmitURL = row.getResultByName("formSubmitURL");
+ let usernameField = row.getResultByName("usernameField");
+ let passwordField = row.getResultByName("passwordField");
+ let encryptedUsername = row.getResultByName("encryptedUsername");
+ let encryptedPassword = row.getResultByName("encryptedPassword");
+
+ // The "guid" field was introduced in schema version 2, and the
+ // "enctype" field was introduced in schema version 3. We don't
+ // support upgrading from older versions of the database.
+ let guid = row.getResultByName("guid");
+ let encType = row.getResultByName("encType");
+
+ // The time and count fields were introduced in schema version 4.
+ let timeCreated = null;
+ let timeLastUsed = null;
+ let timePasswordChanged = null;
+ let timesUsed = null;
+ try {
+ timeCreated = row.getResultByName("timeCreated");
+ timeLastUsed = row.getResultByName("timeLastUsed");
+ timePasswordChanged = row.getResultByName("timePasswordChanged");
+ timesUsed = row.getResultByName("timesUsed");
+ } catch (ex) { }
+
+ // These columns may be null either because they were not present in
+ // the database or because the record was created on a new schema
+ // version by an old application version.
+ if (!timeCreated) {
+ timeCreated = referenceTimeMs;
+ }
+ if (!timeLastUsed) {
+ timeLastUsed = referenceTimeMs;
+ }
+ if (!timePasswordChanged) {
+ timePasswordChanged = referenceTimeMs;
+ }
+ if (!timesUsed) {
+ timesUsed = 1;
+ }
+
+ this.store.data.logins.push({
+ id: this.store.data.nextId++,
+ hostname: hostname,
+ httpRealm: httpRealm,
+ formSubmitURL: formSubmitURL,
+ usernameField: usernameField,
+ passwordField: passwordField,
+ encryptedUsername: encryptedUsername,
+ encryptedPassword: encryptedPassword,
+ guid: guid,
+ encType: encType,
+ timeCreated: timeCreated,
+ timeLastUsed: timeLastUsed,
+ timePasswordChanged: timePasswordChanged,
+ timesUsed: timesUsed,
+ });
+ } catch (ex) {
+ Cu.reportError("Error importing login: " + ex);
+ }
+ }
+
+ rows = yield connection.execute("SELECT * FROM moz_disabledHosts");
+ for (let row of rows) {
+ try {
+ let hostname = row.getResultByName("hostname");
+
+ this.store.data.disabledHosts.push(hostname);
+ } catch (ex) {
+ Cu.reportError("Error importing disabled host: " + ex);
+ }
+ }
+ } finally {
+ yield connection.close();
+ }
+ }),
+};
diff --git a/components/passwordmgr/src/LoginManagerContent.jsm b/components/passwordmgr/src/LoginManagerContent.jsm
new file mode 100644
index 000000000..e8dcf8ad3
--- /dev/null
+++ b/components/passwordmgr/src/LoginManagerContent.jsm
@@ -0,0 +1,1614 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [ "LoginManagerContent",
+ "LoginFormFactory",
+ "UserAutoCompleteResult" ];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
+const AUTOCOMPLETE_AFTER_CONTEXTMENU_THRESHOLD_MS = 250;
+
+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/InsecurePasswordUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
+ "resource://gre/modules/FormLikeFactory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
+ "resource://gre/modules/LoginRecipes.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
+ "resource://gre/modules/InsecurePasswordUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetUtil",
+ "@mozilla.org/network/util;1",
+ "nsINetUtil");
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let logger = LoginHelper.createLogger("LoginManagerContent");
+ return logger.log.bind(logger);
+});
+
+// These mirror signon.* prefs.
+var gEnabled, gAutofillForms, gStoreWhenAutocompleteOff;
+var gLastContextMenuEventTimeStamp = Number.NEGATIVE_INFINITY;
+
+var observer = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsIFormSubmitObserver,
+ Ci.nsIWebProgressListener,
+ Ci.nsIDOMEventListener,
+ Ci.nsISupportsWeakReference]),
+
+ // nsIFormSubmitObserver
+ notify(formElement, aWindow, actionURI) {
+ log("observer notified for form submission.");
+
+ // We're invoked before the content's |onsubmit| handlers, so we
+ // can grab form data before it might be modified (see bug 257781).
+
+ try {
+ let formLike = LoginFormFactory.createFromForm(formElement);
+ LoginManagerContent._onFormSubmit(formLike);
+ } catch (e) {
+ log("Caught error in onFormSubmit(", e.lineNumber, "):", e.message);
+ Cu.reportError(e);
+ }
+
+ return true; // Always return true, or form submit will be canceled.
+ },
+
+ onPrefChange() {
+ gEnabled = Services.prefs.getBoolPref("signon.rememberSignons");
+ gAutofillForms = Services.prefs.getBoolPref("signon.autofillForms");
+ gStoreWhenAutocompleteOff = Services.prefs.getBoolPref("signon.storeWhenAutocompleteOff");
+ },
+
+ // nsIWebProgressListener
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ // Only handle pushState/replaceState here.
+ if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) ||
+ !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)) {
+ return;
+ }
+
+ log("onLocationChange handled:", aLocation.spec, aWebProgress.DOMWindow.document);
+
+ LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
+ },
+
+ onStateChange(aWebProgress, aRequest, aState, aStatus) {
+ if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
+ return;
+ }
+
+ // We only care about when a page triggered a load, not the user. For example:
+ // clicking refresh/back/forward, typing a URL and hitting enter, and loading a bookmark aren't
+ // likely to be when a user wants to save a login.
+ let channel = aRequest.QueryInterface(Ci.nsIChannel);
+ let triggeringPrincipal = channel.loadInfo.triggeringPrincipal;
+ if (triggeringPrincipal.isNullPrincipal ||
+ triggeringPrincipal.equals(Services.scriptSecurityManager.getSystemPrincipal())) {
+ return;
+ }
+
+ // Don't handle history navigation, reload, or pushState not triggered via chrome UI.
+ // e.g. history.go(-1), location.reload(), history.replaceState()
+ if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_NORMAL)) {
+ log("onStateChange: loadType isn't LOAD_CMD_NORMAL:", aWebProgress.loadType);
+ return;
+ }
+
+ log("onStateChange handled:", channel);
+ LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
+ },
+
+ handleEvent(aEvent) {
+ if (!aEvent.isTrusted) {
+ return;
+ }
+
+ if (!gEnabled) {
+ return;
+ }
+
+ switch (aEvent.type) {
+ // Only used for username fields.
+ case "focus": {
+ LoginManagerContent._onUsernameFocus(aEvent);
+ break;
+ }
+
+ case "contextmenu": {
+ gLastContextMenuEventTimeStamp = Date.now();
+ break;
+ }
+
+ default: {
+ throw new Error("Unexpected event");
+ }
+ }
+ },
+};
+
+Services.obs.addObserver(observer, "earlyformsubmit", false);
+var prefBranch = Services.prefs.getBranch("signon.");
+prefBranch.addObserver("", observer.onPrefChange, false);
+
+observer.onPrefChange(); // read initial values
+
+
+function messageManagerFromWindow(win) {
+ return win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+}
+
+// This object maps to the "child" process (even in the single-process case).
+var LoginManagerContent = {
+
+ __formFillService : null, // FormFillController, for username autocompleting
+ get _formFillService() {
+ if (!this.__formFillService)
+ this.__formFillService =
+ Cc["@mozilla.org/satchel/form-fill-controller;1"].
+ getService(Ci.nsIFormFillController);
+ return this.__formFillService;
+ },
+
+ _getRandomId() {
+ return Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+ },
+
+ _messages: [ "RemoteLogins:loginsFound",
+ "RemoteLogins:loginsAutoCompleted" ],
+
+ /**
+ * WeakMap of the root element of a FormLike to the FormLike representing its fields.
+ *
+ * This is used to be able to lookup an existing FormLike for a given root element since multiple
+ * calls to LoginFormFactory won't give the exact same object. When batching fills we don't always
+ * want to use the most recent list of elements for a FormLike since we may end up doing multiple
+ * fills for the same set of elements when a field gets added between arming and running the
+ * DeferredTask.
+ *
+ * @type {WeakMap}
+ */
+ _formLikeByRootElement: new WeakMap(),
+
+ /**
+ * WeakMap of the root element of a WeakMap to the DeferredTask to fill its fields.
+ *
+ * This is used to be able to throttle fills for a FormLike since onDOMInputPasswordAdded gets
+ * dispatched for each password field added to a document but we only want to fill once per
+ * FormLike when multiple fields are added at once.
+ *
+ * @type {WeakMap}
+ */
+ _deferredPasswordAddedTasksByRootElement: new WeakMap(),
+
+ // Map from form login requests to information about that request.
+ _requests: new Map(),
+
+ // Number of outstanding requests to each manager.
+ _managers: new Map(),
+
+ _takeRequest(msg) {
+ let data = msg.data;
+ let request = this._requests.get(data.requestId);
+
+ this._requests.delete(data.requestId);
+
+ let count = this._managers.get(msg.target);
+ if (--count === 0) {
+ this._managers.delete(msg.target);
+
+ for (let message of this._messages)
+ msg.target.removeMessageListener(message, this);
+ } else {
+ this._managers.set(msg.target, count);
+ }
+
+ return request;
+ },
+
+ _sendRequest(messageManager, requestData,
+ name, messageData) {
+ let count;
+ if (!(count = this._managers.get(messageManager))) {
+ this._managers.set(messageManager, 1);
+
+ for (let message of this._messages)
+ messageManager.addMessageListener(message, this);
+ } else {
+ this._managers.set(messageManager, ++count);
+ }
+
+ let requestId = this._getRandomId();
+ messageData.requestId = requestId;
+
+ messageManager.sendAsyncMessage(name, messageData);
+
+ let deferred = Promise.defer();
+ requestData.promise = deferred;
+ this._requests.set(requestId, requestData);
+ return deferred.promise;
+ },
+
+ receiveMessage(msg, window) {
+ if (msg.name == "RemoteLogins:fillForm") {
+ this.fillForm({
+ topDocument: window.document,
+ loginFormOrigin: msg.data.loginFormOrigin,
+ loginsFound: LoginHelper.vanillaObjectsToLogins(msg.data.logins),
+ recipes: msg.data.recipes,
+ inputElement: msg.objects.inputElement,
+ });
+ return;
+ }
+
+ let request = this._takeRequest(msg);
+ switch (msg.name) {
+ case "RemoteLogins:loginsFound": {
+ let loginsFound = LoginHelper.vanillaObjectsToLogins(msg.data.logins);
+ request.promise.resolve({
+ form: request.form,
+ loginsFound: loginsFound,
+ recipes: msg.data.recipes,
+ });
+ break;
+ }
+
+ case "RemoteLogins:loginsAutoCompleted": {
+ let loginsFound =
+ LoginHelper.vanillaObjectsToLogins(msg.data.logins);
+ // If we're in the parent process, don't pass a message manager so our
+ // autocomplete result objects know they can remove the login from the
+ // login manager directly.
+ let messageManager =
+ (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) ?
+ msg.target : undefined;
+ request.promise.resolve({ logins: loginsFound, messageManager });
+ break;
+ }
+ }
+ },
+
+ /**
+ * Get relevant logins and recipes from the parent
+ *
+ * @param {HTMLFormElement} form - form to get login data for
+ * @param {Object} options
+ * @param {boolean} options.showMasterPassword - whether to show a master password prompt
+ */
+ _getLoginDataFromParent(form, options) {
+ let doc = form.ownerDocument;
+ let win = doc.defaultView;
+
+ let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
+ if (!formOrigin) {
+ return Promise.reject("_getLoginDataFromParent: A form origin is required");
+ }
+ let actionOrigin = LoginUtils._getActionOrigin(form);
+
+ let messageManager = messageManagerFromWindow(win);
+
+ // XXX Weak??
+ let requestData = { form: form };
+ let messageData = { formOrigin: formOrigin,
+ actionOrigin: actionOrigin,
+ options: options };
+
+ return this._sendRequest(messageManager, requestData,
+ "RemoteLogins:findLogins",
+ messageData);
+ },
+
+ _autoCompleteSearchAsync(aSearchString, aPreviousResult,
+ aElement, aRect) {
+ let doc = aElement.ownerDocument;
+ let form = LoginFormFactory.createFromField(aElement);
+ let win = doc.defaultView;
+
+ let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
+ let actionOrigin = LoginUtils._getActionOrigin(form);
+
+ let messageManager = messageManagerFromWindow(win);
+
+ let remote = (Services.appinfo.processType ===
+ Services.appinfo.PROCESS_TYPE_CONTENT);
+
+ let previousResult = aPreviousResult ?
+ { searchString: aPreviousResult.searchString,
+ logins: LoginHelper.loginsToVanillaObjects(aPreviousResult.logins) } :
+ null;
+
+ let requestData = {};
+ let messageData = { formOrigin: formOrigin,
+ actionOrigin: actionOrigin,
+ searchString: aSearchString,
+ previousResult: previousResult,
+ rect: aRect,
+ isSecure: InsecurePasswordUtils.isFormSecure(form),
+ isPasswordField: aElement.type == "password",
+ remote: remote };
+
+ return this._sendRequest(messageManager, requestData,
+ "RemoteLogins:autoCompleteLogins",
+ messageData);
+ },
+
+ setupProgressListener(window) {
+ if (!LoginHelper.formlessCaptureEnabled) {
+ return;
+ }
+
+ try {
+ let webProgress = window.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShell).
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress);
+ webProgress.addProgressListener(observer,
+ Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT |
+ Ci.nsIWebProgress.NOTIFY_LOCATION);
+ } catch (ex) {
+ // Ignore NS_ERROR_FAILURE if the progress listener was already added
+ }
+ },
+
+ onDOMFormHasPassword(event, window) {
+ if (!event.isTrusted) {
+ return;
+ }
+
+ let form = event.target;
+ let formLike = LoginFormFactory.createFromForm(form);
+ log("onDOMFormHasPassword:", form, formLike);
+ this._fetchLoginsFromParentAndFillForm(formLike, window);
+ },
+
+ onDOMInputPasswordAdded(event, window) {
+ if (!event.isTrusted) {
+ return;
+ }
+
+ let pwField = event.target;
+ if (pwField.form) {
+ // Fill is handled by onDOMFormHasPassword which is already throttled.
+ return;
+ }
+
+ // Only setup the listener for formless inputs.
+ // Capture within a <form> but without a submit event is bug 1287202.
+ this.setupProgressListener(window);
+
+ let formLike = LoginFormFactory.createFromField(pwField);
+ log("onDOMInputPasswordAdded:", pwField, formLike);
+
+ let deferredTask = this._deferredPasswordAddedTasksByRootElement.get(formLike.rootElement);
+ if (!deferredTask) {
+ log("Creating a DeferredTask to call _fetchLoginsFromParentAndFillForm soon");
+ this._formLikeByRootElement.set(formLike.rootElement, formLike);
+
+ deferredTask = new DeferredTask(function* deferredInputProcessing() {
+ // Get the updated formLike instead of the one at the time of creating the DeferredTask via
+ // a closure since it could be stale since FormLike.elements isn't live.
+ let formLike2 = this._formLikeByRootElement.get(formLike.rootElement);
+ log("Running deferred processing of onDOMInputPasswordAdded", formLike2);
+ this._deferredPasswordAddedTasksByRootElement.delete(formLike2.rootElement);
+ this._fetchLoginsFromParentAndFillForm(formLike2, window);
+ }.bind(this), PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS);
+
+ this._deferredPasswordAddedTasksByRootElement.set(formLike.rootElement, deferredTask);
+ }
+
+ if (deferredTask.isArmed) {
+ log("DeferredTask is already armed so just updating the FormLike");
+ // We update the FormLike so it (most important .elements) is fresh when the task eventually
+ // runs since changes to the elements could affect our field heuristics.
+ this._formLikeByRootElement.set(formLike.rootElement, formLike);
+ } else if (window.document.readyState == "complete") {
+ log("Arming the DeferredTask we just created since document.readyState == 'complete'");
+ deferredTask.arm();
+ } else {
+ window.addEventListener("DOMContentLoaded", function armPasswordAddedTask() {
+ window.removeEventListener("DOMContentLoaded", armPasswordAddedTask);
+ log("Arming the onDOMInputPasswordAdded DeferredTask due to DOMContentLoaded");
+ deferredTask.arm();
+ });
+ }
+ },
+
+ /**
+ * Fetch logins from the parent for a given form and then attempt to fill it.
+ *
+ * @param {FormLike} form to fetch the logins for then try autofill.
+ * @param {Window} window
+ */
+ _fetchLoginsFromParentAndFillForm(form, window) {
+ this._detectInsecureFormLikes(window);
+
+ let messageManager = messageManagerFromWindow(window);
+ messageManager.sendAsyncMessage("LoginStats:LoginEncountered");
+
+ if (!gEnabled) {
+ return;
+ }
+
+ this._getLoginDataFromParent(form, { showMasterPassword: true })
+ .then(this.loginsFound.bind(this))
+ .then(null, Cu.reportError);
+ },
+
+ onPageShow(event, window) {
+ this._detectInsecureFormLikes(window);
+ },
+
+ /**
+ * Maps all DOM content documents in this content process, including those in
+ * frames, to the current state used by the Login Manager.
+ */
+ loginFormStateByDocument: new WeakMap(),
+
+ /**
+ * Retrieves a reference to the state object associated with the given
+ * document. This is initialized to an object with default values.
+ */
+ stateForDocument(document) {
+ let loginFormState = this.loginFormStateByDocument.get(document);
+ if (!loginFormState) {
+ loginFormState = {
+ /**
+ * Keeps track of filled fields and values.
+ */
+ fillsByRootElement: new WeakMap(),
+ loginFormRootElements: new Set(),
+ };
+ this.loginFormStateByDocument.set(document, loginFormState);
+ }
+ return loginFormState;
+ },
+
+ /**
+ * Compute whether there is an insecure login form on any frame of the current page, and
+ * notify the parent process. This is used to control whether insecure password UI appears.
+ */
+ _detectInsecureFormLikes(topWindow) {
+ log("_detectInsecureFormLikes", topWindow.location.href);
+
+ // Returns true if this window or any subframes have insecure login forms.
+ let hasInsecureLoginForms = (thisWindow) => {
+ let doc = thisWindow.document;
+ let hasLoginForm = this.stateForDocument(doc).loginFormRootElements.size > 0;
+ // Ignores window.opener, because it's not relevant for indicating
+ // form security. See InsecurePasswordUtils docs for details.
+ return (hasLoginForm && !thisWindow.isSecureContextIfOpenerIgnored) ||
+ Array.some(thisWindow.frames,
+ frame => hasInsecureLoginForms(frame));
+ };
+
+ let messageManager = messageManagerFromWindow(topWindow);
+ messageManager.sendAsyncMessage("RemoteLogins:insecureLoginFormPresent", {
+ hasInsecureLoginForms: hasInsecureLoginForms(topWindow),
+ });
+ },
+
+ /**
+ * Perform a password fill upon user request coming from the parent process.
+ * The fill will be in the form previously identified during page navigation.
+ *
+ * @param An object with the following properties:
+ * {
+ * topDocument:
+ * DOM document currently associated to the the top-level window
+ * for which the fill is requested. This may be different from the
+ * document that originally caused the login UI to be displayed.
+ * loginFormOrigin:
+ * String with the origin for which the login UI was displayed.
+ * This must match the origin of the form used for the fill.
+ * loginsFound:
+ * Array containing the login to fill. While other messages may
+ * have more logins, for this use case this is expected to have
+ * exactly one element. The origin of the login may be different
+ * from the origin of the form used for the fill.
+ * recipes:
+ * Fill recipes transmitted together with the original message.
+ * inputElement:
+ * Username or password input element from the form we want to fill.
+ * }
+ */
+ fillForm({ topDocument, loginFormOrigin, loginsFound, recipes, inputElement }) {
+ if (!inputElement) {
+ log("fillForm: No input element specified");
+ return;
+ }
+ if (LoginUtils._getPasswordOrigin(topDocument.documentURI) != loginFormOrigin) {
+ if (!inputElement ||
+ LoginUtils._getPasswordOrigin(inputElement.ownerDocument.documentURI) != loginFormOrigin) {
+ log("fillForm: The requested origin doesn't match the one form the",
+ "document. This may mean we navigated to a document from a different",
+ "site before we had a chance to indicate this change in the user",
+ "interface.");
+ return;
+ }
+ }
+
+ let clobberUsername = true;
+ let options = {
+ inputElement,
+ };
+
+ let form = LoginFormFactory.createFromField(inputElement);
+ if (inputElement.type == "password") {
+ clobberUsername = false;
+ }
+ this._fillForm(form, true, clobberUsername, true, true, loginsFound, recipes, options);
+ },
+
+ loginsFound({ form, loginsFound, recipes }) {
+ let doc = form.ownerDocument;
+ let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);
+
+ this._fillForm(form, autofillForm, false, false, false, loginsFound, recipes);
+ },
+
+ /**
+ * Focus event handler for username fields to decide whether to show autocomplete.
+ * @param {FocusEvent} event
+ */
+ _onUsernameFocus(event) {
+ let focusedField = event.target;
+ if (!focusedField.mozIsTextField(true) || focusedField.readOnly) {
+ return;
+ }
+
+ if (this._isLoginAlreadyFilled(focusedField)) {
+ log("_onUsernameFocus: Already filled");
+ return;
+ }
+
+ /*
+ * A `focus` event is fired before a `contextmenu` event if a user right-clicks into an
+ * unfocused field. In that case we don't want to show both autocomplete and a context menu
+ * overlapping so we spin the event loop to see if a `contextmenu` event is coming next. If no
+ * `contextmenu` event was seen and the focused field is still focused by the form fill
+ * controller then show the autocomplete popup.
+ */
+ let timestamp = Date.now();
+ setTimeout(function maybeOpenAutocompleteAfterFocus() {
+ // Even though the `focus` event happens first, its .timeStamp is greater in
+ // testing and I don't want to rely on that so the absolute value is used.
+ let timeDiff = Math.abs(gLastContextMenuEventTimeStamp - timestamp);
+ if (timeDiff < AUTOCOMPLETE_AFTER_CONTEXTMENU_THRESHOLD_MS) {
+ log("Not opening autocomplete after focus since a context menu was opened within",
+ timeDiff, "ms");
+ return;
+ }
+
+ if (this._formFillService.focusedInput == focusedField) {
+ log("maybeOpenAutocompleteAfterFocus: Opening the autocomplete popup. Time diff:", timeDiff);
+ this._formFillService.showPopup();
+ } else {
+ log("maybeOpenAutocompleteAfterFocus: FormFillController has a different focused input");
+ }
+ }.bind(this), 0);
+ },
+
+ /**
+ * Listens for DOMAutoComplete and blur events on an input field.
+ */
+ onUsernameInput(event) {
+ if (!event.isTrusted)
+ return;
+
+ if (!gEnabled)
+ return;
+
+ var acInputField = event.target;
+
+ // This is probably a bit over-conservatative.
+ if (!(acInputField.ownerDocument instanceof Ci.nsIDOMHTMLDocument))
+ return;
+
+ if (!LoginHelper.isUsernameFieldType(acInputField))
+ return;
+
+ var acForm = LoginFormFactory.createFromField(acInputField);
+ if (!acForm)
+ return;
+
+ // If the username is blank, bail out now -- we don't want
+ // fillForm() to try filling in a login without a username
+ // to filter on (bug 471906).
+ if (!acInputField.value)
+ return;
+
+ log("onUsernameInput from", event.type);
+
+ let doc = acForm.ownerDocument;
+ let messageManager = messageManagerFromWindow(doc.defaultView);
+ let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
+ formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
+ })[0];
+
+ // Make sure the username field fillForm will use is the
+ // same field as the autocomplete was activated on.
+ var [usernameField, passwordField, ignored] =
+ this._getFormFields(acForm, false, recipes);
+ if (usernameField == acInputField && passwordField) {
+ this._getLoginDataFromParent(acForm, { showMasterPassword: false })
+ .then(({ form, loginsFound, recipes }) => {
+ this._fillForm(form, true, false, true, true, loginsFound, recipes);
+ })
+ .then(null, Cu.reportError);
+ } else {
+ // Ignore the event, it's for some input we don't care about.
+ }
+ },
+
+ /**
+ * @param {FormLike} form - the FormLike to look for password fields in.
+ * @param {bool} [skipEmptyFields=false] - Whether to ignore password fields with no value.
+ * Used at capture time since saving empty values isn't
+ * useful.
+ * @return {Array|null} Array of password field elements for the specified form.
+ * If no pw fields are found, or if more than 3 are found, then null
+ * is returned.
+ */
+ _getPasswordFields(form, skipEmptyFields = false) {
+ // Locate the password fields in the form.
+ let pwFields = [];
+ for (let i = 0; i < form.elements.length; i++) {
+ let element = form.elements[i];
+ if (!(element instanceof Ci.nsIDOMHTMLInputElement) ||
+ element.type != "password") {
+ continue;
+ }
+
+ if (skipEmptyFields && !element.value.trim()) {
+ continue;
+ }
+
+ pwFields[pwFields.length] = {
+ index : i,
+ element : element
+ };
+ }
+
+ // If too few or too many fields, bail out.
+ if (pwFields.length == 0) {
+ log("(form ignored -- no password fields.)");
+ return null;
+ } else if (pwFields.length > 3) {
+ log("(form ignored -- too many password fields. [ got ", pwFields.length, "])");
+ return null;
+ }
+
+ return pwFields;
+ },
+
+ /**
+ * Returns the username and password fields found in the form.
+ * Can handle complex forms by trying to figure out what the
+ * relevant fields are.
+ *
+ * @param {FormLike} form
+ * @param {bool} isSubmission
+ * @param {Set} recipes
+ * @return {Array} [usernameField, newPasswordField, oldPasswordField]
+ *
+ * usernameField may be null.
+ * newPasswordField will always be non-null.
+ * oldPasswordField may be null. If null, newPasswordField is just
+ * "theLoginField". If not null, the form is apparently a
+ * change-password field, with oldPasswordField containing the password
+ * that is being changed.
+ *
+ * Note that even though we can create a FormLike from a text field,
+ * this method will only return a non-null usernameField if the
+ * FormLike has a password field.
+ */
+ _getFormFields(form, isSubmission, recipes) {
+ var usernameField = null;
+ var pwFields = null;
+ var fieldOverrideRecipe = LoginRecipesContent.getFieldOverrides(recipes, form);
+ if (fieldOverrideRecipe) {
+ var pwOverrideField = LoginRecipesContent.queryLoginField(
+ form,
+ fieldOverrideRecipe.passwordSelector
+ );
+ if (pwOverrideField) {
+ // The field from the password override may be in a different FormLike.
+ let formLike = LoginFormFactory.createFromField(pwOverrideField);
+ pwFields = [{
+ index : [...formLike.elements].indexOf(pwOverrideField),
+ element : pwOverrideField,
+ }];
+ }
+
+ var usernameOverrideField = LoginRecipesContent.queryLoginField(
+ form,
+ fieldOverrideRecipe.usernameSelector
+ );
+ if (usernameOverrideField) {
+ usernameField = usernameOverrideField;
+ }
+ }
+
+ if (!pwFields) {
+ // Locate the password field(s) in the form. Up to 3 supported.
+ // If there's no password field, there's nothing for us to do.
+ pwFields = this._getPasswordFields(form, isSubmission);
+ }
+
+ if (!pwFields) {
+ return [null, null, null];
+ }
+
+ if (!usernameField) {
+ // Locate the username field in the form by searching backwards
+ // from the first password field, assume the first text field is the
+ // username. We might not find a username field if the user is
+ // already logged in to the site.
+ for (var i = pwFields[0].index - 1; i >= 0; i--) {
+ var element = form.elements[i];
+ if (!LoginHelper.isUsernameFieldType(element)) {
+ continue;
+ }
+
+ if (fieldOverrideRecipe && fieldOverrideRecipe.notUsernameSelector &&
+ element.matches(fieldOverrideRecipe.notUsernameSelector)) {
+ continue;
+ }
+
+ usernameField = element;
+ break;
+ }
+ }
+
+ if (!usernameField)
+ log("(form -- no username field found)");
+ else
+ log("Username field ", usernameField, "has name/value:",
+ usernameField.name, "/", usernameField.value);
+
+ // If we're not submitting a form (it's a page load), there are no
+ // password field values for us to use for identifying fields. So,
+ // just assume the first password field is the one to be filled in.
+ if (!isSubmission || pwFields.length == 1) {
+ var passwordField = pwFields[0].element;
+ log("Password field", passwordField, "has name: ", passwordField.name);
+ return [usernameField, passwordField, null];
+ }
+
+
+ // Try to figure out WTF is in the form based on the password values.
+ var oldPasswordField, newPasswordField;
+ var pw1 = pwFields[0].element.value;
+ var pw2 = pwFields[1].element.value;
+ var pw3 = (pwFields[2] ? pwFields[2].element.value : null);
+
+ if (pwFields.length == 3) {
+ // Look for two identical passwords, that's the new password
+
+ if (pw1 == pw2 && pw2 == pw3) {
+ // All 3 passwords the same? Weird! Treat as if 1 pw field.
+ newPasswordField = pwFields[0].element;
+ oldPasswordField = null;
+ } else if (pw1 == pw2) {
+ newPasswordField = pwFields[0].element;
+ oldPasswordField = pwFields[2].element;
+ } else if (pw2 == pw3) {
+ oldPasswordField = pwFields[0].element;
+ newPasswordField = pwFields[2].element;
+ } else if (pw1 == pw3) {
+ // A bit odd, but could make sense with the right page layout.
+ newPasswordField = pwFields[0].element;
+ oldPasswordField = pwFields[1].element;
+ } else {
+ // We can't tell which of the 3 passwords should be saved.
+ log("(form ignored -- all 3 pw fields differ)");
+ return [null, null, null];
+ }
+ } else if (pw1 == pw2) {
+ // pwFields.length == 2
+ // Treat as if 1 pw field
+ newPasswordField = pwFields[0].element;
+ oldPasswordField = null;
+ } else {
+ // Just assume that the 2nd password is the new password
+ oldPasswordField = pwFields[0].element;
+ newPasswordField = pwFields[1].element;
+ }
+
+ log("Password field (new) id/name is: ", newPasswordField.id, " / ", newPasswordField.name);
+ if (oldPasswordField) {
+ log("Password field (old) id/name is: ", oldPasswordField.id, " / ", oldPasswordField.name);
+ } else {
+ log("Password field (old):", oldPasswordField);
+ }
+ return [usernameField, newPasswordField, oldPasswordField];
+ },
+
+
+ /**
+ * @return true if the page requests autocomplete be disabled for the
+ * specified element.
+ */
+ _isAutocompleteDisabled(element) {
+ return element && element.autocomplete == "off";
+ },
+
+ /**
+ * Trigger capture on any relevant FormLikes due to a navigation alone (not
+ * necessarily due to an actual form submission). This method is used to
+ * capture logins for cases where form submit events are not used.
+ *
+ * To avoid multiple notifications for the same FormLike, this currently
+ * avoids capturing when dealing with a real <form> which are ideally already
+ * using a submit event.
+ *
+ * @param {Document} document being navigated
+ */
+ _onNavigation(aDocument) {
+ let state = this.stateForDocument(aDocument);
+ let loginFormRootElements = state.loginFormRootElements;
+ log("_onNavigation: state:", state, "loginFormRootElements size:", loginFormRootElements.size,
+ "document:", aDocument);
+
+ for (let formRoot of state.loginFormRootElements) {
+ if (formRoot instanceof Ci.nsIDOMHTMLFormElement) {
+ // For now only perform capture upon navigation for FormLike's without
+ // a <form> to avoid capture from both an earlyformsubmit and
+ // navigation for the same "form".
+ log("Ignoring navigation for the form root to avoid multiple prompts " +
+ "since it was for a real <form>");
+ continue;
+ }
+ let formLike = this._formLikeByRootElement.get(formRoot);
+ this._onFormSubmit(formLike);
+ }
+ },
+
+ /**
+ * Called by our observer when notified of a form submission.
+ * [Note that this happens before any DOM onsubmit handlers are invoked.]
+ * Looks for a password change in the submitted form, so we can update
+ * our stored password.
+ *
+ * @param {FormLike} form
+ */
+ _onFormSubmit(form) {
+ log("_onFormSubmit", form);
+ var doc = form.ownerDocument;
+ var win = doc.defaultView;
+
+ if (PrivateBrowsingUtils.isContentWindowPrivate(win)) {
+ // We won't do anything in private browsing mode anyway,
+ // so there's no need to perform further checks.
+ log("(form submission ignored in private browsing mode)");
+ return;
+ }
+
+ // If password saving is disabled (globally or for host), bail out now.
+ if (!gEnabled)
+ return;
+
+ var hostname = LoginUtils._getPasswordOrigin(doc.documentURI);
+ if (!hostname) {
+ log("(form submission ignored -- invalid hostname)");
+ return;
+ }
+
+ let formSubmitURL = LoginUtils._getActionOrigin(form);
+ let messageManager = messageManagerFromWindow(win);
+
+ let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
+ formOrigin: hostname,
+ })[0];
+
+ // Get the appropriate fields from the form.
+ var [usernameField, newPasswordField, oldPasswordField] =
+ this._getFormFields(form, true, recipes);
+
+ // Need at least 1 valid password field to do anything.
+ if (newPasswordField == null)
+ return;
+
+ // Check for autocomplete=off attribute. We don't use it to prevent
+ // autofilling (for existing logins), but won't save logins when it's
+ // present and the storeWhenAutocompleteOff pref is false.
+ // XXX spin out a bug that we don't update timeLastUsed in this case?
+ if ((this._isAutocompleteDisabled(form) ||
+ this._isAutocompleteDisabled(usernameField) ||
+ this._isAutocompleteDisabled(newPasswordField) ||
+ this._isAutocompleteDisabled(oldPasswordField)) &&
+ !gStoreWhenAutocompleteOff) {
+ log("(form submission ignored -- autocomplete=off found)");
+ return;
+ }
+
+ // Don't try to send DOM nodes over IPC.
+ let mockUsername = usernameField ?
+ { name: usernameField.name,
+ value: usernameField.value } :
+ null;
+ let mockPassword = { name: newPasswordField.name,
+ value: newPasswordField.value };
+ let mockOldPassword = oldPasswordField ?
+ { name: oldPasswordField.name,
+ value: oldPasswordField.value } :
+ null;
+
+ // Make sure to pass the opener's top in case it was in a frame.
+ let openerTopWindow = win.opener ? win.opener.top : null;
+
+ messageManager.sendAsyncMessage("RemoteLogins:onFormSubmit",
+ { hostname: hostname,
+ formSubmitURL: formSubmitURL,
+ usernameField: mockUsername,
+ newPasswordField: mockPassword,
+ oldPasswordField: mockOldPassword },
+ { openerTopWindow });
+ },
+
+ /**
+ * Attempt to find the username and password fields in a form, and fill them
+ * in using the provided logins and recipes.
+ *
+ * @param {LoginForm} form
+ * @param {bool} autofillForm denotes if we should fill the form in automatically
+ * @param {bool} clobberUsername controls if an existing username can be overwritten.
+ * If this is false and an inputElement of type password
+ * is also passed, the username field will be ignored.
+ * If this is false and no inputElement is passed, if the username
+ * field value is not found in foundLogins, it will not fill the password.
+ * @param {bool} clobberPassword controls if an existing password value can be
+ * overwritten
+ * @param {bool} userTriggered is an indication of whether this filling was triggered by
+ * the user
+ * @param {nsILoginInfo[]} foundLogins is an array of nsILoginInfo that could be used for the form
+ * @param {Set} recipes that could be used to affect how the form is filled
+ * @param {Object} [options = {}] is a list of options for this method.
+ - [inputElement] is an optional target input element we want to fill
+ */
+ _fillForm(form, autofillForm, clobberUsername, clobberPassword,
+ userTriggered, foundLogins, recipes, {inputElement} = {}) {
+ if (form instanceof Ci.nsIDOMHTMLFormElement) {
+ throw new Error("_fillForm should only be called with FormLike objects");
+ }
+
+ log("_fillForm", form.elements);
+ let ignoreAutocomplete = true;
+ // Will be set to one of AUTOFILL_RESULT in the `try` block.
+ let autofillResult = -1;
+ const AUTOFILL_RESULT = {
+ FILLED: 0,
+ NO_PASSWORD_FIELD: 1,
+ PASSWORD_DISABLED_READONLY: 2,
+ NO_LOGINS_FIT: 3,
+ NO_SAVED_LOGINS: 4,
+ EXISTING_PASSWORD: 5,
+ EXISTING_USERNAME: 6,
+ MULTIPLE_LOGINS: 7,
+ NO_AUTOFILL_FORMS: 8,
+ AUTOCOMPLETE_OFF: 9,
+ INSECURE: 10,
+ };
+
+ try {
+ // Nothing to do if we have no matching logins available,
+ // and there isn't a need to show the insecure form warning.
+ if (foundLogins.length == 0 &&
+ (InsecurePasswordUtils.isFormSecure(form) ||
+ !LoginHelper.showInsecureFieldWarning)) {
+ // We don't log() here since this is a very common case.
+ autofillResult = AUTOFILL_RESULT.NO_SAVED_LOGINS;
+ return;
+ }
+
+ // Heuristically determine what the user/pass fields are
+ // We do this before checking to see if logins are stored,
+ // so that the user isn't prompted for a master password
+ // without need.
+ var [usernameField, passwordField, ignored] =
+ this._getFormFields(form, false, recipes);
+
+ // If we have a password inputElement parameter and it's not
+ // the same as the one heuristically found, use the parameter
+ // one instead.
+ if (inputElement) {
+ if (inputElement.type == "password") {
+ passwordField = inputElement;
+ if (!clobberUsername) {
+ usernameField = null;
+ }
+ } else if (LoginHelper.isUsernameFieldType(inputElement)) {
+ usernameField = inputElement;
+ } else {
+ throw new Error("Unexpected input element type.");
+ }
+ }
+
+ // Need a valid password field to do anything.
+ if (passwordField == null) {
+ log("not filling form, no password field found");
+ autofillResult = AUTOFILL_RESULT.NO_PASSWORD_FIELD;
+ return;
+ }
+
+ // If the password field is disabled or read-only, there's nothing to do.
+ if (passwordField.disabled || passwordField.readOnly) {
+ log("not filling form, password field disabled or read-only");
+ autofillResult = AUTOFILL_RESULT.PASSWORD_DISABLED_READONLY;
+ return;
+ }
+
+ // Attach autocomplete stuff to the username field, if we have
+ // one. This is normally used to select from multiple accounts,
+ // but even with one account we should refill if the user edits.
+ // We would also need this attached to show the insecure login
+ // warning, regardless of saved login.
+ if (usernameField) {
+ this._formFillService.markAsLoginManagerField(usernameField);
+ }
+
+ // Nothing to do if we have no matching logins available.
+ if (foundLogins.length == 0) {
+ // We don't log() here since this is a very common case.
+ autofillResult = AUTOFILL_RESULT.NO_SAVED_LOGINS;
+ return;
+ }
+
+ // Prevent autofilling insecure forms.
+ if (!userTriggered && !LoginHelper.insecureAutofill &&
+ !InsecurePasswordUtils.isFormSecure(form)) {
+ log("not filling form since it's insecure");
+ autofillResult = AUTOFILL_RESULT.INSECURE;
+ return;
+ }
+
+ var isAutocompleteOff = false;
+ if (this._isAutocompleteDisabled(form) ||
+ this._isAutocompleteDisabled(usernameField) ||
+ this._isAutocompleteDisabled(passwordField)) {
+ isAutocompleteOff = true;
+ }
+
+ // Discard logins which have username/password values that don't
+ // fit into the fields (as specified by the maxlength attribute).
+ // The user couldn't enter these values anyway, and it helps
+ // with sites that have an extra PIN to be entered (bug 391514)
+ var maxUsernameLen = Number.MAX_VALUE;
+ var maxPasswordLen = Number.MAX_VALUE;
+
+ // If attribute wasn't set, default is -1.
+ if (usernameField && usernameField.maxLength >= 0)
+ maxUsernameLen = usernameField.maxLength;
+ if (passwordField.maxLength >= 0)
+ maxPasswordLen = passwordField.maxLength;
+
+ var logins = foundLogins.filter(function (l) {
+ var fit = (l.username.length <= maxUsernameLen &&
+ l.password.length <= maxPasswordLen);
+ if (!fit)
+ log("Ignored", l.username, "login: won't fit");
+
+ return fit;
+ }, this);
+
+ if (logins.length == 0) {
+ log("form not filled, none of the logins fit in the field");
+ autofillResult = AUTOFILL_RESULT.NO_LOGINS_FIT;
+ return;
+ }
+
+ // Don't clobber an existing password.
+ if (passwordField.value && !clobberPassword) {
+ log("form not filled, the password field was already filled");
+ autofillResult = AUTOFILL_RESULT.EXISTING_PASSWORD;
+ return;
+ }
+
+ // Select a login to use for filling in the form.
+ var selectedLogin;
+ if (!clobberUsername && usernameField && (usernameField.value ||
+ usernameField.disabled ||
+ usernameField.readOnly)) {
+ // If username was specified in the field, it's disabled or it's readOnly, only fill in the
+ // password if we find a matching login.
+ var username = usernameField.value.toLowerCase();
+
+ let matchingLogins = logins.filter(l =>
+ l.username.toLowerCase() == username);
+ if (matchingLogins.length == 0) {
+ log("Password not filled. None of the stored logins match the username already present.");
+ autofillResult = AUTOFILL_RESULT.EXISTING_USERNAME;
+ return;
+ }
+
+ // If there are multiple, and one matches case, use it
+ for (let l of matchingLogins) {
+ if (l.username == usernameField.value) {
+ selectedLogin = l;
+ }
+ }
+ // Otherwise just use the first
+ if (!selectedLogin) {
+ selectedLogin = matchingLogins[0];
+ }
+ } else if (logins.length == 1) {
+ selectedLogin = logins[0];
+ } else {
+ // We have multiple logins. Handle a special case here, for sites
+ // which have a normal user+pass login *and* a password-only login
+ // (eg, a PIN). Prefer the login that matches the type of the form
+ // (user+pass or pass-only) when there's exactly one that matches.
+ let matchingLogins;
+ if (usernameField)
+ matchingLogins = logins.filter(l => l.username);
+ else
+ matchingLogins = logins.filter(l => !l.username);
+
+ if (matchingLogins.length != 1) {
+ log("Multiple logins for form, so not filling any.");
+ autofillResult = AUTOFILL_RESULT.MULTIPLE_LOGINS;
+ return;
+ }
+
+ selectedLogin = matchingLogins[0];
+ }
+
+ // We will always have a selectedLogin at this point.
+
+ if (!autofillForm) {
+ log("autofillForms=false but form can be filled");
+ autofillResult = AUTOFILL_RESULT.NO_AUTOFILL_FORMS;
+ return;
+ }
+
+ if (isAutocompleteOff && !ignoreAutocomplete) {
+ log("Not filling the login because we're respecting autocomplete=off");
+ autofillResult = AUTOFILL_RESULT.AUTOCOMPLETE_OFF;
+ return;
+ }
+
+ // Fill the form
+
+ if (usernameField) {
+ // Don't modify the username field if it's disabled or readOnly so we preserve its case.
+ let disabledOrReadOnly = usernameField.disabled || usernameField.readOnly;
+
+ let userNameDiffers = selectedLogin.username != usernameField.value;
+ // Don't replace the username if it differs only in case, and the user triggered
+ // this autocomplete. We assume that if it was user-triggered the entered text
+ // is desired.
+ let userEnteredDifferentCase = userTriggered && userNameDiffers &&
+ usernameField.value.toLowerCase() == selectedLogin.username.toLowerCase();
+
+ if (!disabledOrReadOnly && !userEnteredDifferentCase && userNameDiffers) {
+ usernameField.setUserInput(selectedLogin.username);
+ }
+ }
+
+ let doc = form.ownerDocument;
+ if (passwordField.value != selectedLogin.password) {
+ passwordField.setUserInput(selectedLogin.password);
+ let autoFilledLogin = {
+ guid: selectedLogin.QueryInterface(Ci.nsILoginMetaInfo).guid,
+ username: selectedLogin.username,
+ usernameField: usernameField ? Cu.getWeakReference(usernameField) : null,
+ password: selectedLogin.password,
+ passwordField: Cu.getWeakReference(passwordField),
+ };
+ log("Saving autoFilledLogin", autoFilledLogin.guid, "for", form.rootElement);
+ this.stateForDocument(doc).fillsByRootElement.set(form.rootElement, autoFilledLogin);
+ }
+
+ log("_fillForm succeeded");
+ autofillResult = AUTOFILL_RESULT.FILLED;
+
+ let win = doc.defaultView;
+ let messageManager = messageManagerFromWindow(win);
+ messageManager.sendAsyncMessage("LoginStats:LoginFillSuccessful");
+ } finally {
+ if (autofillResult == -1) {
+ // eslint-disable-next-line no-unsafe-finally
+ throw new Error("_fillForm: autofillResult must be specified");
+ }
+
+ if (!userTriggered) {
+ if (usernameField) {
+ let focusedElement = this._formFillService.focusedInput;
+ if (usernameField == focusedElement &&
+ autofillResult !== AUTOFILL_RESULT.FILLED) {
+ log("_fillForm: Opening username autocomplete popup since the form wasn't autofilled");
+ this._formFillService.showPopup();
+ }
+ }
+ }
+
+ if (usernameField) {
+ log("_fillForm: Attaching event listeners to usernameField");
+ usernameField.addEventListener("focus", observer);
+ usernameField.addEventListener("contextmenu", observer);
+ }
+
+ Services.obs.notifyObservers(form.rootElement, "passwordmgr-processed-form", null);
+ }
+ },
+
+ /**
+ * Given a field, determine whether that field was last filled as a username
+ * field AND whether the username is still filled in with the username AND
+ * whether the associated password field has the matching password.
+ *
+ * @note This could possibly be unified with getFieldContext but they have
+ * slightly different use cases. getFieldContext looks up recipes whereas this
+ * method doesn't need to since it's only returning a boolean based upon the
+ * recipes used for the last fill (in _fillForm).
+ *
+ * @param {HTMLInputElement} aUsernameField element contained in a FormLike
+ * cached in _formLikeByRootElement.
+ * @returns {Boolean} whether the username and password fields still have the
+ * last-filled values, if previously filled.
+ */
+ _isLoginAlreadyFilled(aUsernameField) {
+ let formLikeRoot = FormLikeFactory.findRootForField(aUsernameField);
+ // Look for the existing FormLike.
+ let existingFormLike = this._formLikeByRootElement.get(formLikeRoot);
+ if (!existingFormLike) {
+ throw new Error("_isLoginAlreadyFilled called with a username field with " +
+ "no rootElement FormLike");
+ }
+
+ log("_isLoginAlreadyFilled: existingFormLike", existingFormLike);
+ let filledLogin = this.stateForDocument(aUsernameField.ownerDocument).fillsByRootElement.get(formLikeRoot);
+ if (!filledLogin) {
+ return false;
+ }
+
+ // Unpack the weak references.
+ let autoFilledUsernameField = filledLogin.usernameField ? filledLogin.usernameField.get() : null;
+ let autoFilledPasswordField = filledLogin.passwordField.get();
+
+ // Check username and password values match what was filled.
+ if (!autoFilledUsernameField ||
+ autoFilledUsernameField != aUsernameField ||
+ autoFilledUsernameField.value != filledLogin.username ||
+ !autoFilledPasswordField ||
+ autoFilledPasswordField.value != filledLogin.password) {
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Verify if a field is a valid login form field and
+ * returns some information about it's FormLike.
+ *
+ * @param {Element} aField
+ * A form field we want to verify.
+ *
+ * @returns {Object} an object with information about the
+ * FormLike username and password field
+ * or null if the passed field is invalid.
+ */
+ getFieldContext(aField) {
+ // If the element is not a proper form field, return null.
+ if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
+ (aField.type != "password" && !LoginHelper.isUsernameFieldType(aField)) ||
+ !aField.ownerDocument) {
+ return null;
+ }
+ let form = LoginFormFactory.createFromField(aField);
+
+ let doc = aField.ownerDocument;
+ let messageManager = messageManagerFromWindow(doc.defaultView);
+ let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
+ formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
+ })[0];
+
+ let [usernameField, newPasswordField] =
+ this._getFormFields(form, false, recipes);
+
+ // If we are not verifying a password field, we want
+ // to use aField as the username field.
+ if (aField.type != "password") {
+ usernameField = aField;
+ }
+
+ return {
+ usernameField: {
+ found: !!usernameField,
+ disabled: usernameField && (usernameField.disabled || usernameField.readOnly),
+ },
+ passwordField: {
+ found: !!newPasswordField,
+ disabled: newPasswordField && (newPasswordField.disabled || newPasswordField.readOnly),
+ },
+ };
+ },
+};
+
+var LoginUtils = {
+ /**
+ * Get the parts of the URL we want for identification.
+ * Strip out things like the userPass portion
+ */
+ _getPasswordOrigin(uriString, allowJS) {
+ var realm = "";
+ try {
+ var uri = Services.io.newURI(uriString, null, null);
+
+ if (allowJS && uri.scheme == "javascript")
+ return "javascript:";
+
+ // Build this manually instead of using prePath to avoid including the userPass portion.
+ realm = uri.scheme + "://" + uri.hostPort;
+ } catch (e) {
+ // bug 159484 - disallow url types that don't support a hostPort.
+ // (although we handle "javascript:..." as a special case above.)
+ log("Couldn't parse origin for", uriString, e);
+ realm = null;
+ }
+
+ return realm;
+ },
+
+ _getActionOrigin(form) {
+ var uriString = form.action;
+
+ // A blank or missing action submits to where it came from.
+ if (uriString == "")
+ uriString = form.baseURI; // ala bug 297761
+
+ return this._getPasswordOrigin(uriString, true);
+ },
+};
+
+// nsIAutoCompleteResult implementation
+function UserAutoCompleteResult(aSearchString, matchingLogins, {isSecure, messageManager, isPasswordField}) {
+ function loginSort(a, b) {
+ var userA = a.username.toLowerCase();
+ var userB = b.username.toLowerCase();
+
+ if (userA < userB)
+ return -1;
+
+ if (userA > userB)
+ return 1;
+
+ return 0;
+ }
+
+ function findDuplicates(loginList) {
+ let seen = new Set();
+ let duplicates = new Set();
+ for (let login of loginList) {
+ if (seen.has(login.username)) {
+ duplicates.add(login.username);
+ }
+ seen.add(login.username);
+ }
+ return duplicates;
+ }
+
+ this._showInsecureFieldWarning = (!isSecure && LoginHelper.showInsecureFieldWarning) ? 1 : 0;
+ this.searchString = aSearchString;
+ this.logins = matchingLogins.sort(loginSort);
+ this.matchCount = matchingLogins.length + this._showInsecureFieldWarning;
+ this._messageManager = messageManager;
+ this._stringBundle = Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
+ this._dateAndTimeFormatter = new Intl.DateTimeFormat(undefined,
+ { day: "numeric", month: "short", year: "numeric" });
+
+ this._isPasswordField = isPasswordField;
+
+ this._duplicateUsernames = findDuplicates(matchingLogins);
+
+ if (this.matchCount > 0) {
+ this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
+ this.defaultIndex = 0;
+ }
+}
+
+UserAutoCompleteResult.prototype = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult,
+ Ci.nsISupportsWeakReference]),
+
+ // private
+ logins : null,
+
+ // Allow autoCompleteSearch to get at the JS object so it can
+ // modify some readonly properties for internal use.
+ get wrappedJSObject() {
+ return this;
+ },
+
+ // Interfaces from idl...
+ searchString : null,
+ searchResult : Ci.nsIAutoCompleteResult.RESULT_NOMATCH,
+ defaultIndex : -1,
+ errorDescription : "",
+ matchCount : 0,
+
+ getValueAt(index) {
+ if (index < 0 || index >= this.matchCount) {
+ throw new Error("Index out of range.");
+ }
+
+ if (this._showInsecureFieldWarning && index === 0) {
+ return "";
+ }
+
+ let selectedLogin = this.logins[index - this._showInsecureFieldWarning];
+
+ return this._isPasswordField ? selectedLogin.password : selectedLogin.username;
+ },
+
+ getLabelAt(index) {
+ if (index < 0 || index >= this.matchCount) {
+ throw new Error("Index out of range.");
+ }
+
+ if (this._showInsecureFieldWarning && index === 0) {
+ return this._stringBundle.GetStringFromName("insecureFieldWarningDescription") + " " +
+ this._stringBundle.GetStringFromName("insecureFieldWarningLearnMore");
+ }
+
+ let that = this;
+
+ function getLocalizedString(key, formatArgs) {
+ if (formatArgs) {
+ return that._stringBundle.formatStringFromName(key, formatArgs, formatArgs.length);
+ }
+ return that._stringBundle.GetStringFromName(key);
+ }
+
+ let login = this.logins[index - this._showInsecureFieldWarning];
+ let username = login.username;
+ // If login is empty or duplicated we want to append a modification date to it.
+ if (!username || this._duplicateUsernames.has(username)) {
+ if (!username) {
+ username = getLocalizedString("noUsername");
+ }
+ let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
+ let time = this._dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
+ username = getLocalizedString("loginHostAge", [username, time]);
+ }
+
+ return username;
+ },
+
+ getCommentAt(index) {
+ return "";
+ },
+
+ getStyleAt(index) {
+ if (index == 0 && this._showInsecureFieldWarning) {
+ return "insecureWarning";
+ }
+
+ return "login";
+ },
+
+ getImageAt(index) {
+ return "";
+ },
+
+ getFinalCompleteValueAt(index) {
+ return this.getValueAt(index);
+ },
+
+ removeValueAt(index, removeFromDB) {
+ if (index < 0 || index >= this.matchCount) {
+ throw new Error("Index out of range.");
+ }
+
+ if (this._showInsecureFieldWarning && index === 0) {
+ // Ignore the warning message item.
+ return;
+ }
+ if (this._showInsecureFieldWarning) {
+ index--;
+ }
+
+ var [removedLogin] = this.logins.splice(index, 1);
+
+ this.matchCount--;
+ if (this.defaultIndex > this.logins.length)
+ this.defaultIndex--;
+
+ if (removeFromDB) {
+ if (this._messageManager) {
+ let vanilla = LoginHelper.loginToVanillaObject(removedLogin);
+ this._messageManager.sendAsyncMessage("RemoteLogins:removeLogin",
+ { login: vanilla });
+ } else {
+ Services.logins.removeLogin(removedLogin);
+ }
+ }
+ }
+};
+
+/**
+ * A factory to generate FormLike objects that represent a set of login fields
+ * which aren't necessarily marked up with a <form> element.
+ */
+var LoginFormFactory = {
+ /**
+ * Create a LoginForm object from a <form>.
+ *
+ * @param {HTMLFormElement} aForm
+ * @return {LoginForm}
+ * @throws Error if aForm isn't an HTMLFormElement
+ */
+ createFromForm(aForm) {
+ let formLike = FormLikeFactory.createFromForm(aForm);
+ formLike.action = LoginUtils._getActionOrigin(aForm);
+
+ let state = LoginManagerContent.stateForDocument(formLike.ownerDocument);
+ state.loginFormRootElements.add(formLike.rootElement);
+ log("adding", formLike.rootElement, "to loginFormRootElements for", formLike.ownerDocument);
+
+ LoginManagerContent._formLikeByRootElement.set(formLike.rootElement, formLike);
+ return formLike;
+ },
+
+ /**
+ * Create a LoginForm object from a password or username field.
+ *
+ * If the field is in a <form>, construct the LoginForm from the form.
+ * Otherwise, create a LoginForm with a rootElement (wrapper) according to
+ * heuristics. Currently all <input> not in a <form> are one LoginForm but this
+ * shouldn't be relied upon as the heuristics may change to detect multiple
+ * "forms" (e.g. registration and login) on one page with a <form>.
+ *
+ * Note that two LoginForms created from the same field won't return the same LoginForm object.
+ * Use the `rootElement` property on the LoginForm as a key instead.
+ *
+ * @param {HTMLInputElement} aField - a password or username field in a document
+ * @return {LoginForm}
+ * @throws Error if aField isn't a password or username field in a document
+ */
+ createFromField(aField) {
+ if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
+ (aField.type != "password" && !LoginHelper.isUsernameFieldType(aField)) ||
+ !aField.ownerDocument) {
+ throw new Error("createFromField requires a password or username field in a document");
+ }
+
+ if (aField.form) {
+ return this.createFromForm(aField.form);
+ }
+
+ let formLike = FormLikeFactory.createFromField(aField);
+ formLike.action = LoginUtils._getPasswordOrigin(aField.ownerDocument.baseURI);
+ log("Created non-form FormLike for rootElement:", aField.ownerDocument.documentElement);
+
+ let state = LoginManagerContent.stateForDocument(formLike.ownerDocument);
+ state.loginFormRootElements.add(formLike.rootElement);
+ log("adding", formLike.rootElement, "to loginFormRootElements for", formLike.ownerDocument);
+
+
+ LoginManagerContent._formLikeByRootElement.set(formLike.rootElement, formLike);
+
+ return formLike;
+ },
+};
diff --git a/components/passwordmgr/src/LoginManagerContextMenu.jsm b/components/passwordmgr/src/LoginManagerContextMenu.jsm
new file mode 100644
index 000000000..5c88687bf
--- /dev/null
+++ b/components/passwordmgr/src/LoginManagerContextMenu.jsm
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["LoginManagerContextMenu"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
+ "resource://gre/modules/LoginManagerParent.jsm");
+
+/*
+ * Password manager object for the browser contextual menu.
+ */
+var LoginManagerContextMenu = {
+ /**
+ * Look for login items and add them to the contextual menu.
+ *
+ * @param {HTMLInputElement} inputElement
+ * The target input element of the context menu click.
+ * @param {xul:browser} browser
+ * The browser for the document the context menu was open on.
+ * @param {nsIURI} documentURI
+ * The URI of the document that the context menu was activated from.
+ * This isn't the same as the browser's top-level document URI
+ * when subframes are involved.
+ * @returns {DocumentFragment} a document fragment with all the login items.
+ */
+ addLoginsToMenu(inputElement, browser, documentURI) {
+ let foundLogins = this._findLogins(documentURI);
+
+ if (!foundLogins.length) {
+ return null;
+ }
+
+ let fragment = browser.ownerDocument.createDocumentFragment();
+ let duplicateUsernames = this._findDuplicates(foundLogins);
+ for (let login of foundLogins) {
+ let item = fragment.ownerDocument.createElement("menuitem");
+
+ let username = login.username;
+ // If login is empty or duplicated we want to append a modification date to it.
+ if (!username || duplicateUsernames.has(username)) {
+ if (!username) {
+ username = this._getLocalizedString("noUsername");
+ }
+ let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
+ let time = this.dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
+ username = this._getLocalizedString("loginHostAge", [username, time]);
+ }
+ item.setAttribute("label", username);
+ item.setAttribute("class", "context-login-item");
+
+ // login is bound so we can keep the reference to each object.
+ item.addEventListener("command", function(login, event) {
+ this._fillTargetField(login, inputElement, browser, documentURI);
+ }.bind(this, login));
+
+ fragment.appendChild(item);
+ }
+
+ return fragment;
+ },
+
+ /**
+ * Undoes the work of addLoginsToMenu for the same menu.
+ *
+ * @param {Document}
+ * The context menu owner document.
+ */
+ clearLoginsFromMenu(document) {
+ let loginItems = document.getElementsByClassName("context-login-item");
+ while (loginItems.item(0)) {
+ loginItems.item(0).remove();
+ }
+ },
+
+ /**
+ * Find logins for the current URI.
+ *
+ * @param {nsIURI} documentURI
+ * URI object with the hostname of the logins we want to find.
+ * This isn't the same as the browser's top-level document URI
+ * when subframes are involved.
+ *
+ * @returns {nsILoginInfo[]} a login list
+ */
+ _findLogins(documentURI) {
+ let searchParams = {
+ hostname: documentURI.prePath,
+ schemeUpgrades: LoginHelper.schemeUpgrades,
+ };
+ let logins = LoginHelper.searchLoginsWithObject(searchParams);
+ let resolveBy = [
+ "scheme",
+ "timePasswordChanged",
+ ];
+ logins = LoginHelper.dedupeLogins(logins, ["username", "password"], resolveBy, documentURI.prePath);
+
+ // Sort logins in alphabetical order and by date.
+ logins.sort((loginA, loginB) => {
+ // Sort alphabetically
+ let result = loginA.username.localeCompare(loginB.username);
+ if (result) {
+ // Forces empty logins to be at the end
+ if (!loginA.username) {
+ return 1;
+ }
+ if (!loginB.username) {
+ return -1;
+ }
+ return result;
+ }
+
+ // Same username logins are sorted by last change date
+ let metaA = loginA.QueryInterface(Ci.nsILoginMetaInfo);
+ let metaB = loginB.QueryInterface(Ci.nsILoginMetaInfo);
+ return metaB.timePasswordChanged - metaA.timePasswordChanged;
+ });
+
+ return logins;
+ },
+
+ /**
+ * Find duplicate usernames in a login list.
+ *
+ * @param {nsILoginInfo[]} loginList
+ * A list of logins we want to look for duplicate usernames.
+ *
+ * @returns {Set} a set with the duplicate usernames.
+ */
+ _findDuplicates(loginList) {
+ let seen = new Set();
+ let duplicates = new Set();
+ for (let login of loginList) {
+ if (seen.has(login.username)) {
+ duplicates.add(login.username);
+ }
+ seen.add(login.username);
+ }
+ return duplicates;
+ },
+
+ /**
+ * @param {nsILoginInfo} login
+ * The login we want to fill the form with.
+ * @param {Element} inputElement
+ * The target input element we want to fill.
+ * @param {xul:browser} browser
+ * The target tab browser.
+ * @param {nsIURI} documentURI
+ * URI of the document owning the form we want to fill.
+ * This isn't the same as the browser's top-level
+ * document URI when subframes are involved.
+ */
+ _fillTargetField(login, inputElement, browser, documentURI) {
+ LoginManagerParent.fillForm({
+ browser: browser,
+ loginFormOrigin: documentURI.prePath,
+ login: login,
+ inputElement: inputElement,
+ }).catch(Cu.reportError);
+ },
+
+ /**
+ * @param {string} key
+ * The localized string key
+ * @param {string[]} formatArgs
+ * An array of formatting argument string
+ *
+ * @returns {string} the localized string for the specified key,
+ * formatted with arguments if required.
+ */
+ _getLocalizedString(key, formatArgs) {
+ if (formatArgs) {
+ return this._stringBundle.formatStringFromName(key, formatArgs, formatArgs.length);
+ }
+ return this._stringBundle.GetStringFromName(key);
+ },
+};
+
+XPCOMUtils.defineLazyGetter(LoginManagerContextMenu, "_stringBundle", function() {
+ return Services.strings.
+ createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
+});
+
+XPCOMUtils.defineLazyGetter(LoginManagerContextMenu, "dateAndTimeFormatter", function() {
+ return new Intl.DateTimeFormat(undefined, {
+ day: "numeric",
+ month: "short",
+ year: "numeric",
+ });
+});
diff --git a/components/passwordmgr/src/LoginManagerParent.jsm b/components/passwordmgr/src/LoginManagerParent.jsm
new file mode 100644
index 000000000..2d75dac7a
--- /dev/null
+++ b/components/passwordmgr/src/LoginManagerParent.jsm
@@ -0,0 +1,516 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, results: Cr, utils: Cu } = Components;
+
+Cu.importGlobalProperties(["URL"]);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "UserAutoCompleteResult",
+ "resource://gre/modules/LoginManagerContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AutoCompletePopup",
+ "resource://gre/modules/AutoCompletePopup.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+ "resource://gre/modules/DeferredTask.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let logger = LoginHelper.createLogger("LoginManagerParent");
+ return logger.log.bind(logger);
+});
+
+this.EXPORTED_SYMBOLS = [ "LoginManagerParent" ];
+
+var LoginManagerParent = {
+ /**
+ * Reference to the default LoginRecipesParent (instead of the initialization promise) for
+ * synchronous access. This is a temporary hack and new consumers should yield on
+ * recipeParentPromise instead.
+ *
+ * @type LoginRecipesParent
+ * @deprecated
+ */
+ _recipeManager: null,
+
+ // Tracks the last time the user cancelled the master password prompt,
+ // to avoid spamming master password prompts on autocomplete searches.
+ _lastMPLoginCancelled: Math.NEGATIVE_INFINITY,
+
+ _searchAndDedupeLogins: function (formOrigin, actionOrigin) {
+ let logins;
+ try {
+ logins = LoginHelper.searchLoginsWithObject({
+ hostname: formOrigin,
+ formSubmitURL: actionOrigin,
+ schemeUpgrades: LoginHelper.schemeUpgrades,
+ });
+ } catch (e) {
+ // Record the last time the user cancelled the MP prompt
+ // to avoid spamming them with MP prompts for autocomplete.
+ if (e.result == Cr.NS_ERROR_ABORT) {
+ log("User cancelled master password prompt.");
+ this._lastMPLoginCancelled = Date.now();
+ return [];
+ }
+ throw e;
+ }
+
+ // Dedupe so the length checks below still make sense with scheme upgrades.
+ let resolveBy = [
+ "scheme",
+ "timePasswordChanged",
+ ];
+ return LoginHelper.dedupeLogins(logins, ["username"], resolveBy, formOrigin);
+ },
+
+ init: function() {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("RemoteLogins:findLogins", this);
+ mm.addMessageListener("RemoteLogins:findRecipes", this);
+ mm.addMessageListener("RemoteLogins:onFormSubmit", this);
+ mm.addMessageListener("RemoteLogins:autoCompleteLogins", this);
+ mm.addMessageListener("RemoteLogins:removeLogin", this);
+ mm.addMessageListener("RemoteLogins:insecureLoginFormPresent", this);
+
+ XPCOMUtils.defineLazyGetter(this, "recipeParentPromise", () => {
+ const { LoginRecipesParent } = Cu.import("resource://gre/modules/LoginRecipes.jsm", {});
+ this._recipeManager = new LoginRecipesParent({
+ defaults: Services.prefs.getComplexValue("signon.recipes.path", Ci.nsISupportsString).data,
+ });
+ return this._recipeManager.initializationPromise;
+ });
+ },
+
+ receiveMessage: function (msg) {
+ let data = msg.data;
+ switch (msg.name) {
+ case "RemoteLogins:findLogins": {
+ // TODO Verify msg.target's principals against the formOrigin?
+ this.sendLoginDataToChild(data.options.showMasterPassword,
+ data.formOrigin,
+ data.actionOrigin,
+ data.requestId,
+ msg.target.messageManager);
+ break;
+ }
+
+ case "RemoteLogins:findRecipes": {
+ try {
+ let formHost = (new URL(data.formOrigin)).host;
+ return this._recipeManager.getRecipesForHost(formHost);
+ } catch(e) {
+ // Just return an empty set in case of error.
+ return new Set();
+ }
+ }
+
+ case "RemoteLogins:onFormSubmit": {
+ // TODO Verify msg.target's principals against the formOrigin?
+ this.onFormSubmit(data.hostname,
+ data.formSubmitURL,
+ data.usernameField,
+ data.newPasswordField,
+ data.oldPasswordField,
+ msg.objects.openerTopWindow,
+ msg.target);
+ break;
+ }
+
+ case "RemoteLogins:insecureLoginFormPresent": {
+ this.setHasInsecureLoginForms(msg.target, data.hasInsecureLoginForms);
+ break;
+ }
+
+ case "RemoteLogins:autoCompleteLogins": {
+ this.doAutocompleteSearch(data, msg.target);
+ break;
+ }
+
+ case "RemoteLogins:removeLogin": {
+ let login = LoginHelper.vanillaObjectToLogin(data.login);
+ AutoCompletePopup.removeLogin(login);
+ break;
+ }
+ }
+
+ return undefined;
+ },
+
+ /**
+ * Trigger a login form fill and send relevant data (e.g. logins and recipes)
+ * to the child process (LoginManagerContent).
+ */
+ fillForm: Task.async(function* ({ browser, loginFormOrigin, login, inputElement }) {
+ let recipes = [];
+ if (loginFormOrigin) {
+ let formHost;
+ try {
+ formHost = (new URL(loginFormOrigin)).host;
+ let recipeManager = yield this.recipeParentPromise;
+ recipes = recipeManager.getRecipesForHost(formHost);
+ } catch (ex) {
+ // Some schemes e.g. chrome aren't supported by URL
+ }
+ }
+
+ // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
+ // doesn't support structured cloning.
+ let jsLogins = [LoginHelper.loginToVanillaObject(login)];
+
+ let objects = inputElement ? {inputElement} : null;
+ browser.messageManager.sendAsyncMessage("RemoteLogins:fillForm", {
+ loginFormOrigin,
+ logins: jsLogins,
+ recipes,
+ }, objects);
+ }),
+
+ /**
+ * Send relevant data (e.g. logins and recipes) to the child process (LoginManagerContent).
+ */
+ sendLoginDataToChild: Task.async(function*(showMasterPassword, formOrigin, actionOrigin,
+ requestId, target) {
+ let recipes = [];
+ if (formOrigin) {
+ let formHost;
+ try {
+ formHost = (new URL(formOrigin)).host;
+ let recipeManager = yield this.recipeParentPromise;
+ recipes = recipeManager.getRecipesForHost(formHost);
+ } catch (ex) {
+ // Some schemes e.g. chrome aren't supported by URL
+ }
+ }
+
+ if (!showMasterPassword && !Services.logins.isLoggedIn) {
+ try {
+ target.sendAsyncMessage("RemoteLogins:loginsFound", {
+ requestId: requestId,
+ logins: [],
+ recipes,
+ });
+ } catch (e) {
+ log("error sending message to target", e);
+ }
+ return;
+ }
+
+ // If we're currently displaying a master password prompt, defer
+ // processing this form until the user handles the prompt.
+ if (Services.logins.uiBusy) {
+ log("deferring sendLoginDataToChild for", formOrigin);
+ let self = this;
+ let observer = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ observe: function (subject, topic, data) {
+ log("Got deferred sendLoginDataToChild notification:", topic);
+ // Only run observer once.
+ Services.obs.removeObserver(this, "passwordmgr-crypto-login");
+ Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
+ if (topic == "passwordmgr-crypto-loginCanceled") {
+ target.sendAsyncMessage("RemoteLogins:loginsFound", {
+ requestId: requestId,
+ logins: [],
+ recipes,
+ });
+ return;
+ }
+
+ self.sendLoginDataToChild(showMasterPassword, formOrigin, actionOrigin,
+ requestId, target);
+ },
+ };
+
+ // Possible leak: it's possible that neither of these notifications
+ // will fire, and if that happens, we'll leak the observer (and
+ // never return). We should guarantee that at least one of these
+ // will fire.
+ // See bug XXX.
+ Services.obs.addObserver(observer, "passwordmgr-crypto-login", false);
+ Services.obs.addObserver(observer, "passwordmgr-crypto-loginCanceled", false);
+ return;
+ }
+
+ let logins = this._searchAndDedupeLogins(formOrigin, actionOrigin);
+
+ log("sendLoginDataToChild:", logins.length, "deduped logins");
+ // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
+ // doesn't support structured cloning.
+ var jsLogins = LoginHelper.loginsToVanillaObjects(logins);
+ target.sendAsyncMessage("RemoteLogins:loginsFound", {
+ requestId: requestId,
+ logins: jsLogins,
+ recipes,
+ });
+ }),
+
+ doAutocompleteSearch: function({ formOrigin, actionOrigin,
+ searchString, previousResult,
+ rect, requestId, isSecure, isPasswordField,
+ remote }, target) {
+ // Note: previousResult is a regular object, not an
+ // nsIAutoCompleteResult.
+
+ // Cancel if we unsuccessfully prompted for the master password too recently.
+ if (!Services.logins.isLoggedIn) {
+ let timeDiff = Date.now() - this._lastMPLoginCancelled;
+ if (timeDiff < this._repromptTimeout) {
+ log("Not searching logins for autocomplete since the master password " +
+ `prompt was last cancelled ${Math.round(timeDiff / 1000)} seconds ago.`);
+ // Send an empty array to make LoginManagerContent clear the
+ // outstanding request it has temporarily saved.
+ target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
+ requestId,
+ logins: [],
+ });
+ return;
+ }
+ }
+
+ let searchStringLower = searchString.toLowerCase();
+ let logins;
+ if (previousResult &&
+ searchStringLower.startsWith(previousResult.searchString.toLowerCase())) {
+ log("Using previous autocomplete result");
+
+ // We have a list of results for a shorter search string, so just
+ // filter them further based on the new search string.
+ logins = LoginHelper.vanillaObjectsToLogins(previousResult.logins);
+ } else {
+ log("Creating new autocomplete search result.");
+
+ logins = this._searchAndDedupeLogins(formOrigin, actionOrigin);
+ }
+
+ let matchingLogins = logins.filter(function(fullMatch) {
+ let match = fullMatch.username;
+
+ // Remove results that are too short, or have different prefix.
+ // Also don't offer empty usernames as possible results except
+ // for password field.
+ if (isPasswordField) {
+ return true;
+ }
+ return match && match.toLowerCase().startsWith(searchStringLower);
+ });
+
+ // XXX In the E10S case, we're responsible for showing our own
+ // autocomplete popup here because the autocomplete protocol hasn't
+ // been e10s-ized yet. In the non-e10s case, our caller is responsible
+ // for showing the autocomplete popup (via the regular
+ // nsAutoCompleteController).
+ if (remote) {
+ let results = new UserAutoCompleteResult(searchString, matchingLogins, {isSecure});
+ AutoCompletePopup.showPopupWithResults({ browser: target.ownerDocument.defaultView, rect, results });
+ }
+
+ // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
+ // doesn't support structured cloning.
+ var jsLogins = LoginHelper.loginsToVanillaObjects(matchingLogins);
+ target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
+ requestId: requestId,
+ logins: jsLogins,
+ });
+ },
+
+ onFormSubmit: function(hostname, formSubmitURL,
+ usernameField, newPasswordField,
+ oldPasswordField, openerTopWindow,
+ target) {
+ function getPrompter() {
+ var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
+ createInstance(Ci.nsILoginManagerPrompter);
+ prompterSvc.init(target.ownerDocument.defaultView);
+ prompterSvc.browser = target;
+ prompterSvc.opener = openerTopWindow;
+ return prompterSvc;
+ }
+
+ function recordLoginUse(login) {
+ // Update the lastUsed timestamp and increment the use count.
+ let propBag = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ propBag.setProperty("timeLastUsed", Date.now());
+ propBag.setProperty("timesUsedIncrement", 1);
+ Services.logins.modifyLogin(login, propBag);
+ }
+
+ if (!Services.logins.getLoginSavingEnabled(hostname)) {
+ log("(form submission ignored -- saving is disabled for:", hostname, ")");
+ return;
+ }
+
+ var formLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
+ createInstance(Ci.nsILoginInfo);
+ formLogin.init(hostname, formSubmitURL, null,
+ (usernameField ? usernameField.value : ""),
+ newPasswordField.value,
+ (usernameField ? usernameField.name : ""),
+ newPasswordField.name);
+
+ // Below here we have one login per hostPort + action + username with the
+ // matching scheme being preferred.
+ let logins = this._searchAndDedupeLogins(hostname, formSubmitURL);
+
+ // If we didn't find a username field, but seem to be changing a
+ // password, allow the user to select from a list of applicable
+ // logins to update the password for.
+ if (!usernameField && oldPasswordField && logins.length > 0) {
+ var prompter = getPrompter();
+
+ if (logins.length == 1) {
+ var oldLogin = logins[0];
+
+ if (oldLogin.password == formLogin.password) {
+ recordLoginUse(oldLogin);
+ log("(Not prompting to save/change since we have no username and the " +
+ "only saved password matches the new password)");
+ return;
+ }
+
+ formLogin.username = oldLogin.username;
+ formLogin.usernameField = oldLogin.usernameField;
+
+ prompter.promptToChangePassword(oldLogin, formLogin);
+ } else {
+ // Note: It's possible that that we already have the correct u+p saved
+ // but since we don't have the username, we don't know if the user is
+ // changing a second account to the new password so we ask anyways.
+
+ prompter.promptToChangePasswordWithUsernames(
+ logins, logins.length, formLogin);
+ }
+
+ return;
+ }
+
+
+ var existingLogin = null;
+ // Look for an existing login that matches the form login.
+ for (let login of logins) {
+ let same;
+
+ // If one login has a username but the other doesn't, ignore
+ // the username when comparing and only match if they have the
+ // same password. Otherwise, compare the logins and match even
+ // if the passwords differ.
+ if (!login.username && formLogin.username) {
+ var restoreMe = formLogin.username;
+ formLogin.username = "";
+ same = LoginHelper.doLoginsMatch(formLogin, login, {
+ ignorePassword: false,
+ ignoreSchemes: LoginHelper.schemeUpgrades,
+ });
+ formLogin.username = restoreMe;
+ } else if (!formLogin.username && login.username) {
+ formLogin.username = login.username;
+ same = LoginHelper.doLoginsMatch(formLogin, login, {
+ ignorePassword: false,
+ ignoreSchemes: LoginHelper.schemeUpgrades,
+ });
+ formLogin.username = ""; // we know it's always blank.
+ } else {
+ same = LoginHelper.doLoginsMatch(formLogin, login, {
+ ignorePassword: true,
+ ignoreSchemes: LoginHelper.schemeUpgrades,
+ });
+ }
+
+ if (same) {
+ existingLogin = login;
+ break;
+ }
+ }
+
+ if (existingLogin) {
+ log("Found an existing login matching this form submission");
+
+ // Change password if needed.
+ if (existingLogin.password != formLogin.password) {
+ log("...passwords differ, prompting to change.");
+ prompter = getPrompter();
+ prompter.promptToChangePassword(existingLogin, formLogin);
+ } else if (!existingLogin.username && formLogin.username) {
+ log("...empty username update, prompting to change.");
+ prompter = getPrompter();
+ prompter.promptToChangePassword(existingLogin, formLogin);
+ } else {
+ recordLoginUse(existingLogin);
+ }
+
+ return;
+ }
+
+
+ // Prompt user to save login (via dialog or notification bar)
+ prompter = getPrompter();
+ prompter.promptToSavePassword(formLogin);
+ },
+
+ /**
+ * Maps all the <browser> elements for tabs in the parent process to the
+ * current state used to display tab-specific UI.
+ *
+ * This mapping is not updated in case a web page is moved to a different
+ * chrome window by the swapDocShells method. In this case, it is possible
+ * that a UI update just requested for the login fill doorhanger and then
+ * delayed by a few hundred milliseconds will be lost. Later requests would
+ * use the new browser reference instead.
+ *
+ * Given that the case above is rare, and it would not cause any origin
+ * mismatch at the time of filling because the origin is checked later in the
+ * content process, this case is left unhandled.
+ */
+ loginFormStateByBrowser: new WeakMap(),
+
+ /**
+ * Retrieves a reference to the state object associated with the given
+ * browser. This is initialized to an empty object.
+ */
+ stateForBrowser(browser) {
+ let loginFormState = this.loginFormStateByBrowser.get(browser);
+ if (!loginFormState) {
+ loginFormState = {};
+ this.loginFormStateByBrowser.set(browser, loginFormState);
+ }
+ return loginFormState;
+ },
+
+ /**
+ * Returns true if the page currently loaded in the given browser element has
+ * insecure login forms. This state may be updated asynchronously, in which
+ * case a custom event named InsecureLoginFormsStateChange will be dispatched
+ * on the browser element.
+ */
+ hasInsecureLoginForms(browser) {
+ return !!this.stateForBrowser(browser).hasInsecureLoginForms;
+ },
+
+ /**
+ * Called to indicate whether an insecure password field is present so
+ * insecure password UI can know when to show.
+ */
+ setHasInsecureLoginForms(browser, hasInsecureLoginForms) {
+ let state = this.stateForBrowser(browser);
+
+ // Update the data to use to the latest known values. Since messages are
+ // processed in order, this will always be the latest version to use.
+ state.hasInsecureLoginForms = hasInsecureLoginForms;
+
+ // Report the insecure login form state immediately.
+ browser.dispatchEvent(new browser.ownerDocument.defaultView
+ .CustomEvent("InsecureLoginFormsStateChange"));
+ },
+};
+
+XPCOMUtils.defineLazyPreferenceGetter(LoginManagerParent, "_repromptTimeout",
+ "signon.masterPasswordReprompt.timeout_ms", 900000); // 15 Minutes
diff --git a/components/passwordmgr/src/LoginRecipes.jsm b/components/passwordmgr/src/LoginRecipes.jsm
new file mode 100644
index 000000000..4a8124bbc
--- /dev/null
+++ b/components/passwordmgr/src/LoginRecipes.jsm
@@ -0,0 +1,260 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["LoginRecipesContent", "LoginRecipesParent"];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+const REQUIRED_KEYS = ["hosts"];
+const OPTIONAL_KEYS = ["description", "notUsernameSelector", "passwordSelector", "pathRegex", "usernameSelector"];
+const SUPPORTED_KEYS = REQUIRED_KEYS.concat(OPTIONAL_KEYS);
+
+Cu.importGlobalProperties(["URL"]);
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "log", () => LoginHelper.createLogger("LoginRecipes"));
+
+/**
+ * Create an instance of the object to manage recipes in the parent process.
+ * Consumers should wait until {@link initializationPromise} resolves before
+ * calling methods on the object.
+ *
+ * @constructor
+ * @param {String} [aOptions.defaults=null] the URI to load the recipes from.
+ * If it's null, nothing is loaded.
+ *
+*/
+function LoginRecipesParent(aOptions = { defaults: null }) {
+ if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ throw new Error("LoginRecipesParent should only be used from the main process");
+ }
+ this._defaults = aOptions.defaults;
+ this.reset();
+}
+
+LoginRecipesParent.prototype = {
+ /**
+ * Promise resolved with an instance of itself when the module is ready.
+ *
+ * @type {Promise}
+ */
+ initializationPromise: null,
+
+ /**
+ * @type {bool} Whether default recipes were loaded at construction time.
+ */
+ _defaults: null,
+
+ /**
+ * @type {Map} Map of hosts (including non-default port numbers) to Sets of recipes.
+ * e.g. "example.com:8080" => Set({...})
+ */
+ _recipesByHost: null,
+
+ /**
+ * @param {Object} aRecipes an object containing recipes to load for use. The object
+ * should be compatible with JSON (e.g. no RegExp).
+ * @return {Promise} resolving when the recipes are loaded
+ */
+ load(aRecipes) {
+ let recipeErrors = 0;
+ for (let rawRecipe of aRecipes.siteRecipes) {
+ try {
+ rawRecipe.pathRegex = rawRecipe.pathRegex ? new RegExp(rawRecipe.pathRegex) : undefined;
+ this.add(rawRecipe);
+ } catch (ex) {
+ recipeErrors++;
+ log.error("Error loading recipe", rawRecipe, ex);
+ }
+ }
+
+ if (recipeErrors) {
+ return Promise.reject(`There were ${recipeErrors} recipe error(s)`);
+ }
+
+ return Promise.resolve();
+ },
+
+ /**
+ * Reset the set of recipes to the ones from the time of construction.
+ */
+ reset() {
+ log.debug("Resetting recipes with defaults:", this._defaults);
+ this._recipesByHost = new Map();
+
+ if (this._defaults) {
+ let channel = NetUtil.newChannel({uri: NetUtil.newURI(this._defaults, "UTF-8"),
+ loadUsingSystemPrincipal: true});
+ channel.contentType = "application/json";
+
+ try {
+ this.initializationPromise = new Promise(function(resolve) {
+ NetUtil.asyncFetch(channel, function (stream, result) {
+ if (!Components.isSuccessCode(result)) {
+ throw new Error("Error fetching recipe file:" + result);
+ }
+ let count = stream.available();
+ let data = NetUtil.readInputStreamToString(stream, count, { charset: "UTF-8" });
+ resolve(JSON.parse(data));
+ });
+ }).then(recipes => {
+ return this.load(recipes);
+ }).then(resolve => {
+ return this;
+ });
+ } catch (e) {
+ throw new Error("Error reading recipe file:" + e);
+ }
+ } else {
+ this.initializationPromise = Promise.resolve(this);
+ }
+ },
+
+ /**
+ * Validate the recipe is sane and then add it to the set of recipes.
+ *
+ * @param {Object} recipe
+ */
+ add(recipe) {
+ log.debug("Adding recipe:", recipe);
+ let recipeKeys = Object.keys(recipe);
+ let unknownKeys = recipeKeys.filter(key => SUPPORTED_KEYS.indexOf(key) == -1);
+ if (unknownKeys.length > 0) {
+ throw new Error("The following recipe keys aren't supported: " + unknownKeys.join(", "));
+ }
+
+ let missingRequiredKeys = REQUIRED_KEYS.filter(key => recipeKeys.indexOf(key) == -1);
+ if (missingRequiredKeys.length > 0) {
+ throw new Error("The following required recipe keys are missing: " + missingRequiredKeys.join(", "));
+ }
+
+ if (!Array.isArray(recipe.hosts)) {
+ throw new Error("'hosts' must be a array");
+ }
+
+ if (!recipe.hosts.length) {
+ throw new Error("'hosts' must be a non-empty array");
+ }
+
+ if (recipe.pathRegex && recipe.pathRegex.constructor.name != "RegExp") {
+ throw new Error("'pathRegex' must be a regular expression");
+ }
+
+ const OPTIONAL_STRING_PROPS = ["description", "passwordSelector", "usernameSelector"];
+ for (let prop of OPTIONAL_STRING_PROPS) {
+ if (recipe[prop] && typeof(recipe[prop]) != "string") {
+ throw new Error(`'${prop}' must be a string`);
+ }
+ }
+
+ // Add the recipe to the map for each host
+ for (let host of recipe.hosts) {
+ if (!this._recipesByHost.has(host)) {
+ this._recipesByHost.set(host, new Set());
+ }
+ this._recipesByHost.get(host).add(recipe);
+ }
+ },
+
+ /**
+ * Currently only exact host matches are returned but this will eventually handle parent domains.
+ *
+ * @param {String} aHost (e.g. example.com:8080 [non-default port] or sub.example.com)
+ * @return {Set} of recipes that apply to the host ordered by host priority
+ */
+ getRecipesForHost(aHost) {
+ let hostRecipes = this._recipesByHost.get(aHost);
+ if (!hostRecipes) {
+ return new Set();
+ }
+
+ return hostRecipes;
+ },
+};
+
+
+var LoginRecipesContent = {
+ /**
+ * @param {Set} aRecipes - Possible recipes that could apply to the form
+ * @param {FormLike} aForm - We use a form instead of just a URL so we can later apply
+ * tests to the page contents.
+ * @return {Set} a subset of recipes that apply to the form with the order preserved
+ */
+ _filterRecipesForForm(aRecipes, aForm) {
+ let formDocURL = aForm.ownerDocument.location;
+ let hostRecipes = aRecipes;
+ let recipes = new Set();
+ log.debug("_filterRecipesForForm", aRecipes);
+ if (!hostRecipes) {
+ return recipes;
+ }
+
+ for (let hostRecipe of hostRecipes) {
+ if (hostRecipe.pathRegex && !hostRecipe.pathRegex.test(formDocURL.pathname)) {
+ continue;
+ }
+ recipes.add(hostRecipe);
+ }
+
+ return recipes;
+ },
+
+ /**
+ * Given a set of recipes that apply to the host, choose the one most applicable for
+ * overriding login fields in the form.
+ *
+ * @param {Set} aRecipes The set of recipes to consider for the form
+ * @param {FormLike} aForm The form where login fields exist.
+ * @return {Object} The recipe that is most applicable for the form.
+ */
+ getFieldOverrides(aRecipes, aForm) {
+ let recipes = this._filterRecipesForForm(aRecipes, aForm);
+ log.debug("getFieldOverrides: filtered recipes:", recipes);
+ if (!recipes.size) {
+ return null;
+ }
+
+ let chosenRecipe = null;
+ // Find the first (most-specific recipe that involves field overrides).
+ for (let recipe of recipes) {
+ if (!recipe.usernameSelector && !recipe.passwordSelector &&
+ !recipe.notUsernameSelector) {
+ continue;
+ }
+
+ chosenRecipe = recipe;
+ break;
+ }
+
+ return chosenRecipe;
+ },
+
+ /**
+ * @param {HTMLElement} aParent the element to query for the selector from.
+ * @param {CSSSelector} aSelector the CSS selector to query for the login field.
+ * @return {HTMLElement|null}
+ */
+ queryLoginField(aParent, aSelector) {
+ if (!aSelector) {
+ return null;
+ }
+ let field = aParent.ownerDocument.querySelector(aSelector);
+ if (!field) {
+ log.debug("Login field selector wasn't matched:", aSelector);
+ return null;
+ }
+ if (!(field instanceof aParent.ownerDocument.defaultView.HTMLInputElement)) {
+ log.warn("Login field isn't an <input> so ignoring it:", aSelector);
+ return null;
+ }
+ return field;
+ },
+};
diff --git a/components/passwordmgr/src/LoginStore.jsm b/components/passwordmgr/src/LoginStore.jsm
new file mode 100644
index 000000000..9fa6e7dff
--- /dev/null
+++ b/components/passwordmgr/src/LoginStore.jsm
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Handles serialization of the data and persistence into a file.
+ *
+ * The file is stored in JSON format, without indentation, using UTF-8 encoding.
+ * With indentation applied, the file would look like this:
+ *
+ * {
+ * "logins": [
+ * {
+ * "id": 2,
+ * "hostname": "http://www.example.com",
+ * "httpRealm": null,
+ * "formSubmitURL": "http://www.example.com/submit-url",
+ * "usernameField": "username_field",
+ * "passwordField": "password_field",
+ * "encryptedUsername": "...",
+ * "encryptedPassword": "...",
+ * "guid": "...",
+ * "encType": 1,
+ * "timeCreated": 1262304000000,
+ * "timeLastUsed": 1262304000000,
+ * "timePasswordChanged": 1262476800000,
+ * "timesUsed": 1
+ * },
+ * {
+ * "id": 4,
+ * (...)
+ * }
+ * ],
+ * "disabledHosts": [
+ * "http://www.example.org",
+ * "http://www.example.net"
+ * ],
+ * "nextId": 10,
+ * "version": 1
+ * }
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "LoginStore",
+];
+
+// Globals
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
+ "resource://gre/modules/JSONFile.jsm");
+
+/**
+ * Current data version assigned by the code that last touched the data.
+ *
+ * This number should be updated only when it is important to understand whether
+ * an old version of the code has touched the data, for example to execute an
+ * update logic. In most cases, this number should not be changed, in
+ * particular when no special one-time update logic is needed.
+ *
+ * For example, this number should NOT be changed when a new optional field is
+ * added to a login entry.
+ */
+const kDataVersion = 2;
+
+// The permission type we store in the permission manager.
+const PERMISSION_SAVE_LOGINS = "login-saving";
+
+// LoginStore
+
+/**
+ * Inherits from JSONFile and handles serialization of login-related data and
+ * persistence into a file.
+ *
+ * @param aPath
+ * String containing the file path where data should be saved.
+ */
+function LoginStore(aPath) {
+ JSONFile.call(this, {
+ path: aPath,
+ dataPostProcessor: this._dataPostProcessor.bind(this)
+ });
+}
+
+LoginStore.prototype = Object.create(JSONFile.prototype);
+LoginStore.prototype.constructor = LoginStore;
+
+/**
+ * Synchronously work on the data just loaded into memory.
+ */
+LoginStore.prototype._dataPostProcessor = function(data) {
+ if (data.nextId === undefined) {
+ data.nextId = 1;
+ }
+
+ // Create any arrays that are not present in the saved file.
+ if (!data.logins) {
+ data.logins = [];
+ }
+
+ // Stub needed for login imports before data has been migrated.
+ if (!data.disabledHosts) {
+ data.disabledHosts = [];
+ }
+
+ if (data.version === 1) {
+ this._migrateDisabledHosts(data);
+ }
+
+ // Indicate that the current version of the code has touched the file.
+ data.version = kDataVersion;
+
+ return data;
+};
+
+/**
+ * Migrates disabled hosts to the permission manager.
+ */
+LoginStore.prototype._migrateDisabledHosts = function (data) {
+ for (let host of data.disabledHosts) {
+ try {
+ let uri = Services.io.newURI(host, null, null);
+ Services.perms.add(uri, PERMISSION_SAVE_LOGINS, Services.perms.DENY_ACTION);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+
+ delete data.disabledHosts;
+};
diff --git a/components/passwordmgr/src/OSCrypto.jsm b/components/passwordmgr/src/OSCrypto.jsm
new file mode 100644
index 000000000..3d2b27c4e
--- /dev/null
+++ b/components/passwordmgr/src/OSCrypto.jsm
@@ -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/. */
+
+/**
+ * Common front for various implementations of OSCrypto
+ */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+this.EXPORTED_SYMBOLS = ["OSCrypto"];
+
+var OSCrypto = {};
+
+#ifdef XP_WIN
+Services.scriptloader.loadSubScript("resource://gre/modules/OSCrypto_win.js", this);
+#else
+throw new Error("OSCrypto.jsm isn't supported on this platform");
+#endif
diff --git a/components/passwordmgr/src/OSCrypto_win.js b/components/passwordmgr/src/OSCrypto_win.js
new file mode 100644
index 000000000..0f52f4269
--- /dev/null
+++ b/components/passwordmgr/src/OSCrypto_win.js
@@ -0,0 +1,245 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes", "resource://gre/modules/ctypes.jsm");
+
+const FLAGS_NOT_SET = 0;
+
+const wintypes = {
+ BOOL: ctypes.bool,
+ BYTE: ctypes.uint8_t,
+ DWORD: ctypes.uint32_t,
+ PBYTE: ctypes.unsigned_char.ptr,
+ PCHAR: ctypes.char.ptr,
+ PDWORD: ctypes.uint32_t.ptr,
+ PVOID: ctypes.voidptr_t,
+ WORD: ctypes.uint16_t,
+};
+
+function OSCrypto() {
+ this._structs = {};
+ this._functions = new Map();
+ this._libs = new Map();
+ this._structs.DATA_BLOB = new ctypes.StructType("DATA_BLOB",
+ [
+ {cbData: wintypes.DWORD},
+ {pbData: wintypes.PVOID}
+ ]);
+
+ try {
+
+ this._libs.set("crypt32", ctypes.open("Crypt32"));
+ this._libs.set("kernel32", ctypes.open("Kernel32"));
+
+ this._functions.set("CryptProtectData",
+ this._libs.get("crypt32").declare("CryptProtectData",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ this._structs.DATA_BLOB.ptr,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.DWORD,
+ this._structs.DATA_BLOB.ptr));
+ this._functions.set("CryptUnprotectData",
+ this._libs.get("crypt32").declare("CryptUnprotectData",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ this._structs.DATA_BLOB.ptr,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.DWORD,
+ this._structs.DATA_BLOB.ptr));
+ this._functions.set("LocalFree",
+ this._libs.get("kernel32").declare("LocalFree",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ wintypes.PVOID));
+ } catch (ex) {
+ Cu.reportError(ex);
+ this.finalize();
+ throw ex;
+ }
+}
+OSCrypto.prototype = {
+ /**
+ * Convert an array containing only two bytes unsigned numbers to a string.
+ * @param {number[]} arr - the array that needs to be converted.
+ * @returns {string} the string representation of the array.
+ */
+ arrayToString(arr) {
+ let str = "";
+ for (let i = 0; i < arr.length; i++) {
+ str += String.fromCharCode(arr[i]);
+ }
+ return str;
+ },
+
+ /**
+ * Convert a string to an array.
+ * @param {string} str - the string that needs to be converted.
+ * @returns {number[]} the array representation of the string.
+ */
+ stringToArray(str) {
+ let arr = [];
+ for (let i = 0; i < str.length; i++) {
+ arr.push(str.charCodeAt(i));
+ }
+ return arr;
+ },
+
+ /**
+ * Calculate the hash value used by IE as the name of the registry value where login details are
+ * stored.
+ * @param {string} data - the string value that needs to be hashed.
+ * @returns {string} the hash value of the string.
+ */
+ getIELoginHash(data) {
+ // return the two-digit hexadecimal code for a byte
+ function toHexString(charCode) {
+ return ("00" + charCode.toString(16)).slice(-2);
+ }
+
+ // the data needs to be encoded in null terminated UTF-16
+ data += "\0";
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-16";
+ // result is an out parameter,
+ // result.value will contain the array length
+ let result = {};
+ // dataArray is an array of bytes
+ let dataArray = converter.convertToByteArray(data, result);
+ // calculation of SHA1 hash value
+ let cryptoHash = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ cryptoHash.init(cryptoHash.SHA1);
+ cryptoHash.update(dataArray, dataArray.length);
+ let hash = cryptoHash.finish(false);
+
+ let tail = 0; // variable to calculate value for the last 2 bytes
+ // convert to a character string in hexadecimal notation
+ for (let c of hash) {
+ tail += c.charCodeAt(0);
+ }
+ hash += String.fromCharCode(tail % 256);
+
+ // convert the binary hash data to a hex string.
+ let hashStr = Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
+ return hashStr.toUpperCase();
+ },
+
+ /**
+ * Decrypt a string using the windows CryptUnprotectData API.
+ * @param {string} data - the encrypted string that needs to be decrypted.
+ * @param {?string} entropy - the entropy value of the decryption (could be null). Its value must
+ * be the same as the one used when the data was encrypted.
+ * @returns {string} the decryption of the string.
+ */
+ decryptData(data, entropy = null) {
+ let array = this.stringToArray(data);
+ let decryptedData = "";
+ let encryptedData = wintypes.BYTE.array(array.length)(array);
+ let inData = new this._structs.DATA_BLOB(encryptedData.length, encryptedData);
+ let outData = new this._structs.DATA_BLOB();
+ let entropyParam;
+ if (entropy) {
+ let entropyArray = this.stringToArray(entropy);
+ entropyArray.push(0);
+ let entropyData = wintypes.WORD.array(entropyArray.length)(entropyArray);
+ let optionalEntropy = new this._structs.DATA_BLOB(entropyData.length * 2,
+ entropyData);
+ entropyParam = optionalEntropy.address();
+ } else {
+ entropyParam = null;
+ }
+
+ let status = this._functions.get("CryptUnprotectData")(inData.address(), null,
+ entropyParam,
+ null, null, FLAGS_NOT_SET,
+ outData.address());
+ if (status === 0) {
+ throw new Error("decryptData failed: " + status);
+ }
+
+ // convert byte array to JS string.
+ let len = outData.cbData;
+ let decrypted = ctypes.cast(outData.pbData,
+ wintypes.BYTE.array(len).ptr).contents;
+ for (let i = 0; i < decrypted.length; i++) {
+ decryptedData += String.fromCharCode(decrypted[i]);
+ }
+
+ this._functions.get("LocalFree")(outData.pbData);
+ return decryptedData;
+ },
+
+ /**
+ * Encrypt a string using the windows CryptProtectData API.
+ * @param {string} data - the string that is going to be encrypted.
+ * @param {?string} entropy - the entropy value of the encryption (could be null). Its value must
+ * be the same as the one that is going to be used for the decryption.
+ * @returns {string} the encrypted string.
+ */
+ encryptData(data, entropy = null) {
+ let encryptedData = "";
+ let decryptedData = wintypes.BYTE.array(data.length)(this.stringToArray(data));
+
+ let inData = new this._structs.DATA_BLOB(data.length, decryptedData);
+ let outData = new this._structs.DATA_BLOB();
+ let entropyParam;
+ if (!entropy) {
+ entropyParam = null;
+ } else {
+ let entropyArray = this.stringToArray(entropy);
+ entropyArray.push(0);
+ let entropyData = wintypes.WORD.array(entropyArray.length)(entropyArray);
+ let optionalEntropy = new this._structs.DATA_BLOB(entropyData.length * 2,
+ entropyData);
+ entropyParam = optionalEntropy.address();
+ }
+
+ let status = this._functions.get("CryptProtectData")(inData.address(), null,
+ entropyParam,
+ null, null, FLAGS_NOT_SET,
+ outData.address());
+ if (status === 0) {
+ throw new Error("encryptData failed: " + status);
+ }
+
+ // convert byte array to JS string.
+ let len = outData.cbData;
+ let encrypted = ctypes.cast(outData.pbData,
+ wintypes.BYTE.array(len).ptr).contents;
+ encryptedData = this.arrayToString(encrypted);
+ this._functions.get("LocalFree")(outData.pbData);
+ return encryptedData;
+ },
+
+ /**
+ * Must be invoked once after last use of any of the provided helpers.
+ */
+ finalize() {
+ this._structs = {};
+ this._functions.clear();
+ for (let lib of this._libs.values()) {
+ try {
+ lib.close();
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ this._libs.clear();
+ },
+};
diff --git a/components/passwordmgr/src/crypto-SDR.js b/components/passwordmgr/src/crypto-SDR.js
new file mode 100644
index 000000000..b0916eb29
--- /dev/null
+++ b/components/passwordmgr/src/crypto-SDR.js
@@ -0,0 +1,207 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
+function LoginManagerCrypto_SDR() {
+ this.init();
+}
+
+LoginManagerCrypto_SDR.prototype = {
+
+ classID : Components.ID("{dc6c2976-0f73-4f1f-b9ff-3d72b4e28309}"),
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerCrypto]),
+
+ __sdrSlot : null, // PKCS#11 slot being used by the SDR.
+ get _sdrSlot() {
+ if (!this.__sdrSlot) {
+ let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"].
+ getService(Ci.nsIPKCS11ModuleDB);
+ this.__sdrSlot = modules.findSlotByName("");
+ }
+ return this.__sdrSlot;
+ },
+
+ __decoderRing : null, // nsSecretDecoderRing service
+ get _decoderRing() {
+ if (!this.__decoderRing)
+ this.__decoderRing = Cc["@mozilla.org/security/sdr;1"].
+ getService(Ci.nsISecretDecoderRing);
+ return this.__decoderRing;
+ },
+
+ __utfConverter : null, // UCS2 <--> UTF8 string conversion
+ get _utfConverter() {
+ if (!this.__utfConverter) {
+ this.__utfConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ this.__utfConverter.charset = "UTF-8";
+ }
+ return this.__utfConverter;
+ },
+
+ _utfConverterReset : function() {
+ this.__utfConverter = null;
+ },
+
+ _uiBusy : false,
+
+
+ init : function () {
+ // Check to see if the internal PKCS#11 token has been initialized.
+ // If not, set a blank password.
+ let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].
+ getService(Ci.nsIPK11TokenDB);
+
+ let token = tokenDB.getInternalKeyToken();
+ if (token.needsUserInit) {
+ this.log("Initializing key3.db with default blank password.");
+ token.initPassword("");
+ }
+ },
+
+
+ /*
+ * encrypt
+ *
+ * Encrypts the specified string, using the SecretDecoderRing.
+ *
+ * Returns the encrypted string, or throws an exception if there was a
+ * problem.
+ */
+ encrypt : function (plainText) {
+ let cipherText = null;
+
+ let wasLoggedIn = this.isLoggedIn;
+ let canceledMP = false;
+
+ this._uiBusy = true;
+ try {
+ let plainOctet = this._utfConverter.ConvertFromUnicode(plainText);
+ plainOctet += this._utfConverter.Finish();
+ cipherText = this._decoderRing.encryptString(plainOctet);
+ } catch (e) {
+ this.log("Failed to encrypt string. (" + e.name + ")");
+ // If the user clicks Cancel, we get NS_ERROR_FAILURE.
+ // (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
+ if (e.result == Cr.NS_ERROR_FAILURE) {
+ canceledMP = true;
+ throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
+ } else {
+ throw Components.Exception("Couldn't encrypt string", Cr.NS_ERROR_FAILURE);
+ }
+ } finally {
+ this._uiBusy = false;
+ // If we triggered a master password prompt, notify observers.
+ if (!wasLoggedIn && this.isLoggedIn)
+ this._notifyObservers("passwordmgr-crypto-login");
+ else if (canceledMP)
+ this._notifyObservers("passwordmgr-crypto-loginCanceled");
+ }
+ return cipherText;
+ },
+
+
+ /*
+ * decrypt
+ *
+ * Decrypts the specified string, using the SecretDecoderRing.
+ *
+ * Returns the decrypted string, or throws an exception if there was a
+ * problem.
+ */
+ decrypt : function (cipherText) {
+ let plainText = null;
+
+ let wasLoggedIn = this.isLoggedIn;
+ let canceledMP = false;
+
+ this._uiBusy = true;
+ try {
+ let plainOctet;
+ plainOctet = this._decoderRing.decryptString(cipherText);
+ plainText = this._utfConverter.ConvertToUnicode(plainOctet);
+ } catch (e) {
+ this.log("Failed to decrypt string: " + cipherText +
+ " (" + e.name + ")");
+
+ // In the unlikely event the converter threw, reset it.
+ this._utfConverterReset();
+
+ // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
+ // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
+ // Wrong passwords are handled by the decoderRing reprompting;
+ // we get no notification.
+ if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ canceledMP = true;
+ throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
+ } else {
+ throw Components.Exception("Couldn't decrypt string", Cr.NS_ERROR_FAILURE);
+ }
+ } finally {
+ this._uiBusy = false;
+ // If we triggered a master password prompt, notify observers.
+ if (!wasLoggedIn && this.isLoggedIn)
+ this._notifyObservers("passwordmgr-crypto-login");
+ else if (canceledMP)
+ this._notifyObservers("passwordmgr-crypto-loginCanceled");
+ }
+
+ return plainText;
+ },
+
+
+ /*
+ * uiBusy
+ */
+ get uiBusy() {
+ return this._uiBusy;
+ },
+
+
+ /*
+ * isLoggedIn
+ */
+ get isLoggedIn() {
+ let status = this._sdrSlot.status;
+ this.log("SDR slot status is " + status);
+ if (status == Ci.nsIPKCS11Slot.SLOT_READY ||
+ status == Ci.nsIPKCS11Slot.SLOT_LOGGED_IN)
+ return true;
+ if (status == Ci.nsIPKCS11Slot.SLOT_NOT_LOGGED_IN)
+ return false;
+ throw Components.Exception("unexpected slot status: " + status, Cr.NS_ERROR_FAILURE);
+ },
+
+
+ /*
+ * defaultEncType
+ */
+ get defaultEncType() {
+ return Ci.nsILoginManagerCrypto.ENCTYPE_SDR;
+ },
+
+
+ /*
+ * _notifyObservers
+ */
+ _notifyObservers : function(topic) {
+ this.log("Prompted for a master password, notifying for " + topic);
+ Services.obs.notifyObservers(null, topic, null);
+ },
+}; // end of nsLoginManagerCrypto_SDR implementation
+
+XPCOMUtils.defineLazyGetter(this.LoginManagerCrypto_SDR.prototype, "log", () => {
+ let logger = LoginHelper.createLogger("Login crypto");
+ return logger.log.bind(logger);
+});
+
+var component = [LoginManagerCrypto_SDR];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
diff --git a/components/passwordmgr/src/nsLoginInfo.js b/components/passwordmgr/src/nsLoginInfo.js
new file mode 100644
index 000000000..d6ea86446
--- /dev/null
+++ b/components/passwordmgr/src/nsLoginInfo.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
+
+function nsLoginInfo() {}
+
+nsLoginInfo.prototype = {
+
+ classID : Components.ID("{0f2f347c-1e4f-40cc-8efd-792dea70a85e}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginInfo, Ci.nsILoginMetaInfo]),
+
+ //
+ // nsILoginInfo interfaces...
+ //
+
+ hostname : null,
+ formSubmitURL : null,
+ httpRealm : null,
+ username : null,
+ password : null,
+ usernameField : null,
+ passwordField : null,
+
+ init : function (aHostname, aFormSubmitURL, aHttpRealm,
+ aUsername, aPassword,
+ aUsernameField, aPasswordField) {
+ this.hostname = aHostname;
+ this.formSubmitURL = aFormSubmitURL;
+ this.httpRealm = aHttpRealm;
+ this.username = aUsername;
+ this.password = aPassword;
+ this.usernameField = aUsernameField;
+ this.passwordField = aPasswordField;
+ },
+
+ matches(aLogin, ignorePassword) {
+ return LoginHelper.doLoginsMatch(this, aLogin, {
+ ignorePassword,
+ });
+ },
+
+ equals : function (aLogin) {
+ if (this.hostname != aLogin.hostname ||
+ this.formSubmitURL != aLogin.formSubmitURL ||
+ this.httpRealm != aLogin.httpRealm ||
+ this.username != aLogin.username ||
+ this.password != aLogin.password ||
+ this.usernameField != aLogin.usernameField ||
+ this.passwordField != aLogin.passwordField)
+ return false;
+
+ return true;
+ },
+
+ clone : function() {
+ let clone = Cc["@mozilla.org/login-manager/loginInfo;1"].
+ createInstance(Ci.nsILoginInfo);
+ clone.init(this.hostname, this.formSubmitURL, this.httpRealm,
+ this.username, this.password,
+ this.usernameField, this.passwordField);
+
+ // Copy nsILoginMetaInfo props
+ clone.QueryInterface(Ci.nsILoginMetaInfo);
+ clone.guid = this.guid;
+ clone.timeCreated = this.timeCreated;
+ clone.timeLastUsed = this.timeLastUsed;
+ clone.timePasswordChanged = this.timePasswordChanged;
+ clone.timesUsed = this.timesUsed;
+
+ return clone;
+ },
+
+ //
+ // nsILoginMetaInfo interfaces...
+ //
+
+ guid : null,
+ timeCreated : null,
+ timeLastUsed : null,
+ timePasswordChanged : null,
+ timesUsed : null
+
+}; // end of nsLoginInfo implementation
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsLoginInfo]);
diff --git a/components/passwordmgr/src/nsLoginManager.js b/components/passwordmgr/src/nsLoginManager.js
new file mode 100644
index 000000000..7d249ab9e
--- /dev/null
+++ b/components/passwordmgr/src/nsLoginManager.js
@@ -0,0 +1,461 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, results: Cr, utils: Cu } = Components;
+
+const PERMISSION_SAVE_LOGINS = "login-saving";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+Cu.import("resource://gre/modules/LoginManagerContent.jsm"); /* global UserAutoCompleteResult */
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginFormFactory",
+ "resource://gre/modules/LoginManagerContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
+ "resource://gre/modules/InsecurePasswordUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let logger = LoginHelper.createLogger("nsLoginManager");
+ return logger;
+});
+
+const MS_PER_DAY = 24 * 60 * 60 * 1000;
+
+function LoginManager() {
+ this.init();
+}
+
+LoginManager.prototype = {
+
+ classID: Components.ID("{cb9e0de8-3598-4ed7-857b-827f011ad5d8}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginManager,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIInterfaceRequestor]),
+ getInterface(aIID) {
+ if (aIID.equals(Ci.mozIStorageConnection) && this._storage) {
+ let ir = this._storage.QueryInterface(Ci.nsIInterfaceRequestor);
+ return ir.getInterface(aIID);
+ }
+
+ if (aIID.equals(Ci.nsIVariant)) {
+ // Allows unwrapping the JavaScript object for regression tests.
+ return this;
+ }
+
+ throw new Components.Exception("Interface not available", Cr.NS_ERROR_NO_INTERFACE);
+ },
+
+
+ /* ---------- private members ---------- */
+
+
+ __formFillService: null, // FormFillController, for username autocompleting
+ get _formFillService() {
+ if (!this.__formFillService) {
+ this.__formFillService = Cc["@mozilla.org/satchel/form-fill-controller;1"].
+ getService(Ci.nsIFormFillController);
+ }
+ return this.__formFillService;
+ },
+
+
+ _storage: null, // Storage component which contains the saved logins
+ _prefBranch: null, // Preferences service
+ _remember: true, // mirrors signon.rememberSignons preference
+
+
+ /**
+ * Initialize the Login Manager. Automatically called when service
+ * is created.
+ *
+ * Note: Service created in /browser/base/content/browser.js,
+ * delayedStartup()
+ */
+ init() {
+
+ // Cache references to current |this| in utility objects
+ this._observer._pwmgr = this;
+
+ // Preferences. Add observer so we get notified of changes.
+ this._prefBranch = Services.prefs.getBranch("signon.");
+ this._prefBranch.addObserver("rememberSignons", this._observer, false);
+
+ this._remember = this._prefBranch.getBoolPref("rememberSignons");
+ this._autoCompleteLookupPromise = null;
+
+ // Form submit observer checks forms for new logins and pw changes.
+ Services.obs.addObserver(this._observer, "xpcom-shutdown", false);
+
+ if (Services.appinfo.processType ===
+ Services.appinfo.PROCESS_TYPE_DEFAULT) {
+ Services.obs.addObserver(this._observer, "passwordmgr-storage-replace",
+ false);
+
+ // Initialize storage so that asynchronous data loading can start.
+ this._initStorage();
+ }
+
+ },
+
+
+ _initStorage() {
+ let contractID = "@mozilla.org/login-manager/storage/json;1";
+
+ try {
+ let catMan = Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager);
+ contractID = catMan.getCategoryEntry("login-manager-storage",
+ "nsILoginManagerStorage");
+ log.debug("Found alternate nsILoginManagerStorage with contract ID:", contractID);
+ } catch (e) {
+ log.debug("No alternate nsILoginManagerStorage registered");
+ }
+
+ this._storage = Cc[contractID].
+ createInstance(Ci.nsILoginManagerStorage);
+ this.initializationPromise = this._storage.initialize();
+ },
+
+
+ /* ---------- Utility objects ---------- */
+
+
+ /**
+ * Internal utility object, implements the nsIObserver interface.
+ * Used to receive notification for: form submission, preference changes.
+ */
+ _observer: {
+ _pwmgr: null,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ // nsIObserver
+ observe(subject, topic, data) {
+ if (topic == "nsPref:changed") {
+ var prefName = data;
+ log.debug("got change to", prefName, "preference");
+
+ if (prefName == "rememberSignons") {
+ this._pwmgr._remember =
+ this._pwmgr._prefBranch.getBoolPref("rememberSignons");
+ } else {
+ log.debug("Oops! Pref not handled, change ignored.");
+ }
+ } else if (topic == "xpcom-shutdown") {
+ delete this._pwmgr.__formFillService;
+ delete this._pwmgr._storage;
+ delete this._pwmgr._prefBranch;
+ this._pwmgr = null;
+ } else if (topic == "passwordmgr-storage-replace") {
+ Task.spawn(function* () {
+ yield this._pwmgr._storage.terminate();
+ this._pwmgr._initStorage();
+ yield this._pwmgr.initializationPromise;
+ Services.obs.notifyObservers(null,
+ "passwordmgr-storage-replace-complete", null);
+ }.bind(this));
+ } else {
+ log.debug("Oops! Unexpected notification:", topic);
+ }
+ }
+ },
+
+
+
+ /* ---------- Primary Public interfaces ---------- */
+
+
+ /**
+ * @type Promise
+ * This promise is resolved when initialization is complete, and is rejected
+ * in case the asynchronous part of initialization failed.
+ */
+ initializationPromise: null,
+
+
+ /**
+ * Add a new login to login storage.
+ */
+ addLogin(login) {
+ // Sanity check the login
+ if (login.hostname == null || login.hostname.length == 0) {
+ throw new Error("Can't add a login with a null or empty hostname.");
+ }
+
+ // For logins w/o a username, set to "", not null.
+ if (login.username == null) {
+ throw new Error("Can't add a login with a null username.");
+ }
+
+ if (login.password == null || login.password.length == 0) {
+ throw new Error("Can't add a login with a null or empty password.");
+ }
+
+ if (login.formSubmitURL || login.formSubmitURL == "") {
+ // We have a form submit URL. Can't have a HTTP realm.
+ if (login.httpRealm != null) {
+ throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
+ }
+ } else if (login.httpRealm) {
+ // We have a HTTP realm. Can't have a form submit URL.
+ if (login.formSubmitURL != null) {
+ throw new Error("Can't add a login with both a httpRealm and formSubmitURL.");
+ }
+ } else {
+ // Need one or the other!
+ throw new Error("Can't add a login without a httpRealm or formSubmitURL.");
+ }
+
+
+ // Look for an existing entry.
+ var logins = this.findLogins({}, login.hostname, login.formSubmitURL,
+ login.httpRealm);
+
+ if (logins.some(l => login.matches(l, true))) {
+ throw new Error("This login already exists.");
+ }
+
+ log.debug("Adding login");
+ return this._storage.addLogin(login);
+ },
+
+ /**
+ * Remove the specified login from the stored logins.
+ */
+ removeLogin(login) {
+ log.debug("Removing login");
+ return this._storage.removeLogin(login);
+ },
+
+
+ /**
+ * Change the specified login to match the new login.
+ */
+ modifyLogin(oldLogin, newLogin) {
+ log.debug("Modifying login");
+ return this._storage.modifyLogin(oldLogin, newLogin);
+ },
+
+
+ /**
+ * Get a dump of all stored logins. Used by the login manager UI.
+ *
+ * @param count - only needed for XPCOM.
+ * @return {nsILoginInfo[]} - If there are no logins, the array is empty.
+ */
+ getAllLogins(count) {
+ log.debug("Getting a list of all logins");
+ return this._storage.getAllLogins(count);
+ },
+
+
+ /**
+ * Remove all stored logins.
+ */
+ removeAllLogins() {
+ log.debug("Removing all logins");
+ this._storage.removeAllLogins();
+ },
+
+ /**
+ * Get a list of all origins for which logins are disabled.
+ *
+ * @param {Number} count - only needed for XPCOM.
+ *
+ * @return {String[]} of disabled origins. If there are no disabled origins,
+ * the array is empty.
+ */
+ getAllDisabledHosts(count) {
+ log.debug("Getting a list of all disabled origins");
+
+ let disabledHosts = [];
+ let enumerator = Services.perms.enumerator;
+
+ while (enumerator.hasMoreElements()) {
+ let perm = enumerator.getNext();
+ if (perm.type == PERMISSION_SAVE_LOGINS && perm.capability == Services.perms.DENY_ACTION) {
+ disabledHosts.push(perm.principal.URI.prePath);
+ }
+ }
+
+ if (count)
+ count.value = disabledHosts.length; // needed for XPCOM
+
+ log.debug("getAllDisabledHosts: returning", disabledHosts.length, "disabled hosts.");
+ return disabledHosts;
+ },
+
+
+ /**
+ * Search for the known logins for entries matching the specified criteria.
+ */
+ findLogins(count, origin, formActionOrigin, httpRealm) {
+ log.debug("Searching for logins matching origin:", origin,
+ "formActionOrigin:", formActionOrigin, "httpRealm:", httpRealm);
+
+ return this._storage.findLogins(count, origin, formActionOrigin,
+ httpRealm);
+ },
+
+
+ /**
+ * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
+ * JavaScript object and decrypt the results.
+ *
+ * @return {nsILoginInfo[]} which are decrypted.
+ */
+ searchLogins(count, matchData) {
+ log.debug("Searching for logins");
+
+ matchData.QueryInterface(Ci.nsIPropertyBag2);
+ if (!matchData.hasKey("guid")) {
+ if (!matchData.hasKey("hostname")) {
+ log.warn("searchLogins: A `hostname` is recommended");
+ }
+
+ if (!matchData.hasKey("formSubmitURL") && !matchData.hasKey("httpRealm")) {
+ log.warn("searchLogins: `formSubmitURL` or `httpRealm` is recommended");
+ }
+ }
+
+ return this._storage.searchLogins(count, matchData);
+ },
+
+
+ /**
+ * Search for the known logins for entries matching the specified criteria,
+ * returns only the count.
+ */
+ countLogins(origin, formActionOrigin, httpRealm) {
+ log.debug("Counting logins matching origin:", origin,
+ "formActionOrigin:", formActionOrigin, "httpRealm:", httpRealm);
+
+ return this._storage.countLogins(origin, formActionOrigin, httpRealm);
+ },
+
+
+ get uiBusy() {
+ return this._storage.uiBusy;
+ },
+
+
+ get isLoggedIn() {
+ return this._storage.isLoggedIn;
+ },
+
+
+ /**
+ * Check to see if user has disabled saving logins for the origin.
+ */
+ getLoginSavingEnabled(origin) {
+ log.debug("Checking if logins to", origin, "can be saved.");
+ if (!this._remember) {
+ return false;
+ }
+
+ let uri = Services.io.newURI(origin, null, null);
+ return Services.perms.testPermission(uri, PERMISSION_SAVE_LOGINS) != Services.perms.DENY_ACTION;
+ },
+
+
+ /**
+ * Enable or disable storing logins for the specified origin.
+ */
+ setLoginSavingEnabled(origin, enabled) {
+ // Throws if there are bogus values.
+ LoginHelper.checkHostnameValue(origin);
+
+ let uri = Services.io.newURI(origin, null, null);
+ if (enabled) {
+ Services.perms.remove(uri, PERMISSION_SAVE_LOGINS);
+ } else {
+ Services.perms.add(uri, PERMISSION_SAVE_LOGINS, Services.perms.DENY_ACTION);
+ }
+
+ log.debug("Login saving for", origin, "now enabled?", enabled);
+ LoginHelper.notifyStorageChanged(enabled ? "hostSavingEnabled" : "hostSavingDisabled", origin);
+ },
+
+ /**
+ * Yuck. This is called directly by satchel:
+ * nsFormFillController::StartSearch()
+ * [toolkit/components/satchel/nsFormFillController.cpp]
+ *
+ * We really ought to have a simple way for code to register an
+ * auto-complete provider, and not have satchel calling pwmgr directly.
+ */
+ autoCompleteSearchAsync(aSearchString, aPreviousResult,
+ aElement, aCallback) {
+ // aPreviousResult is an nsIAutoCompleteResult, aElement is
+ // nsIDOMHTMLInputElement
+
+ let form = LoginFormFactory.createFromField(aElement);
+ let isSecure = InsecurePasswordUtils.isFormSecure(form);
+ let isPasswordField = aElement.type == "password";
+
+ let completeSearch = (autoCompleteLookupPromise, { logins, messageManager }) => {
+ // If the search was canceled before we got our
+ // results, don't bother reporting them.
+ if (this._autoCompleteLookupPromise !== autoCompleteLookupPromise) {
+ return;
+ }
+
+ this._autoCompleteLookupPromise = null;
+ let results = new UserAutoCompleteResult(aSearchString, logins, {
+ messageManager,
+ isSecure,
+ isPasswordField,
+ });
+ aCallback.onSearchCompletion(results);
+ };
+
+ if (isPasswordField && aSearchString) {
+ // Return empty result on password fields with password already filled.
+ let acLookupPromise = this._autoCompleteLookupPromise = Promise.resolve({ logins: [] });
+ acLookupPromise.then(completeSearch.bind(this, acLookupPromise));
+ return;
+ }
+
+ if (!this._remember) {
+ let acLookupPromise = this._autoCompleteLookupPromise = Promise.resolve({ logins: [] });
+ acLookupPromise.then(completeSearch.bind(this, acLookupPromise));
+ return;
+ }
+
+ log.debug("AutoCompleteSearch invoked. Search is:", aSearchString);
+
+ let previousResult;
+ if (aPreviousResult) {
+ previousResult = { searchString: aPreviousResult.searchString,
+ logins: aPreviousResult.wrappedJSObject.logins };
+ } else {
+ previousResult = null;
+ }
+
+ let rect = BrowserUtils.getElementBoundingScreenRect(aElement);
+ let acLookupPromise = this._autoCompleteLookupPromise =
+ LoginManagerContent._autoCompleteSearchAsync(aSearchString, previousResult,
+ aElement, rect);
+ acLookupPromise.then(completeSearch.bind(this, acLookupPromise))
+ .then(null, Cu.reportError);
+ },
+
+ stopSearch() {
+ this._autoCompleteLookupPromise = null;
+ },
+}; // end of LoginManager implementation
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LoginManager]);
diff --git a/components/passwordmgr/src/nsLoginManagerPrompter.js b/components/passwordmgr/src/nsLoginManagerPrompter.js
new file mode 100644
index 000000000..7adabf214
--- /dev/null
+++ b/components/passwordmgr/src/nsLoginManagerPrompter.js
@@ -0,0 +1,1731 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+const { PromptUtils } = Cu.import("resource://gre/modules/SharedPromptUtils.jsm", {});
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
+const LoginInfo =
+ Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
+ "nsILoginInfo", "init");
+
+const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";
+
+/**
+ * Implements nsIPromptFactory
+ *
+ * Invoked by [toolkit/components/prompts/src/nsPrompter.js]
+ */
+function LoginManagerPromptFactory() {
+ Services.obs.addObserver(this, "quit-application-granted", true);
+ Services.obs.addObserver(this, "passwordmgr-crypto-login", true);
+ Services.obs.addObserver(this, "passwordmgr-crypto-loginCanceled", true);
+}
+
+LoginManagerPromptFactory.prototype = {
+
+ classID : Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"),
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIObserver, Ci.nsISupportsWeakReference]),
+
+ _asyncPrompts : {},
+ _asyncPromptInProgress : false,
+
+ observe : function (subject, topic, data) {
+ this.log("Observed: " + topic);
+ if (topic == "quit-application-granted") {
+ this._cancelPendingPrompts();
+ } else if (topic == "passwordmgr-crypto-login") {
+ // Start processing the deferred prompters.
+ this._doAsyncPrompt();
+ } else if (topic == "passwordmgr-crypto-loginCanceled") {
+ // User canceled a Master Password prompt, so go ahead and cancel
+ // all pending auth prompts to avoid nagging over and over.
+ this._cancelPendingPrompts();
+ }
+ },
+
+ getPrompt : function (aWindow, aIID) {
+ var prompt = new LoginManagerPrompter().QueryInterface(aIID);
+ prompt.init(aWindow, this);
+ return prompt;
+ },
+
+ _doAsyncPrompt : function() {
+ if (this._asyncPromptInProgress) {
+ this.log("_doAsyncPrompt bypassed, already in progress");
+ return;
+ }
+
+ // Find the first prompt key we have in the queue
+ var hashKey = null;
+ for (hashKey in this._asyncPrompts)
+ break;
+
+ if (!hashKey) {
+ this.log("_doAsyncPrompt:run bypassed, no prompts in the queue");
+ return;
+ }
+
+ // If login manger has logins for this host, defer prompting if we're
+ // already waiting on a master password entry.
+ var prompt = this._asyncPrompts[hashKey];
+ var prompter = prompt.prompter;
+ var [hostname, httpRealm] = prompter._getAuthTarget(prompt.channel, prompt.authInfo);
+ var hasLogins = (prompter._pwmgr.countLogins(hostname, null, httpRealm) > 0);
+ if (!hasLogins && LoginHelper.schemeUpgrades && hostname.startsWith("https://")) {
+ let httpHostname = hostname.replace(/^https:\/\//, "http://");
+ hasLogins = (prompter._pwmgr.countLogins(httpHostname, null, httpRealm) > 0);
+ }
+ if (hasLogins && prompter._pwmgr.uiBusy) {
+ this.log("_doAsyncPrompt:run bypassed, master password UI busy");
+ return;
+ }
+
+ // Set up a counter for ensuring that the basic auth prompt can not
+ // be abused for DOS-style attacks. With this counter, each eTLD+1
+ // per browser will get a limited number of times a user can
+ // cancel the prompt until we stop showing it.
+ let browser = prompter._browser;
+ let baseDomain = null;
+ if (browser && browser.isAuthDOSProtected) {
+ try {
+ baseDomain = Services.eTLD.getBaseDomainFromHost(hostname);
+ } catch (e) {
+ baseDomain = hostname;
+ }
+
+ if (!browser.authPromptCounter) {
+ browser.authPromptCounter = {};
+ }
+
+ if (!browser.authPromptCounter[baseDomain]) {
+ browser.authPromptCounter[baseDomain] = 0;
+ }
+ }
+
+ var self = this;
+
+ var runnable = {
+ cancel: false,
+ run : function() {
+ var ok = false;
+ if (!this.cancel) {
+ try {
+ self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
+ ok = prompter.promptAuth(prompt.channel,
+ prompt.level,
+ prompt.authInfo);
+ } catch (e) {
+ if (e instanceof Components.Exception &&
+ e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
+ self.log("_doAsyncPrompt:run bypassed, UI is not available in this context");
+ } else {
+ Components.utils.reportError("LoginManagerPrompter: " +
+ "_doAsyncPrompt:run: " + e + "\n");
+ }
+ }
+
+ delete self._asyncPrompts[hashKey];
+ prompt.inProgress = false;
+ self._asyncPromptInProgress = false;
+
+ if (browser && browser.isAuthDOSProtected) {
+ // Reset the counter state if the user replied to a prompt and actually
+ // tried to login (vs. simply clicking any button to get out).
+ if (ok && (prompt.authInfo.username || prompt.authInfo.password)) {
+ browser.authPromptCounter[baseDomain] = 0;
+ } else {
+ browser.authPromptCounter[baseDomain] += 1;
+ }
+ }
+ }
+
+ for (var consumer of prompt.consumers) {
+ if (!consumer.callback)
+ // Not having a callback means that consumer didn't provide it
+ // or canceled the notification
+ continue;
+
+ self.log("Calling back to " + consumer.callback + " ok=" + ok);
+ try {
+ if (ok) {
+ consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
+ } else {
+ consumer.callback.onAuthCancelled(consumer.context, !this.cancel);
+ }
+ } catch (e) { /* Throw away exceptions caused by callback */ }
+ }
+ self._doAsyncPrompt();
+ }
+ };
+
+ var cancelDialogLimit = Services.prefs.getIntPref("prompts.authentication_dialog_abuse_limit");
+
+ // Block the auth prompt if:
+ // - There is an attached browser element
+ // - The browser element has opted-in to DOS protection
+ // - The dialog cancellation limit is not 0 (= feature disabled)
+ // - The amount of cancellations >= the set abuse limit
+ if (browser && browser.isAuthDOSProtected) {
+ let cancelationCounter = browser.authPromptCounter[baseDomain];
+ this.log("cancelationCounter =", cancelationCounter);
+
+ if (cancelDialogLimit && cancelationCounter >= cancelDialogLimit) {
+ this.log("Blocking auth dialog, due to exceeding dialog bloat limit");
+ delete this._asyncPrompts[hashKey];
+
+ // just make the runnable cancel all consumers
+ runnable.cancel = true;
+ } else {
+ this._asyncPromptInProgress = true;
+ prompt.inProgress = true;
+ }
+ } else {
+ // No DOS protection: prompt
+ this._asyncPromptInProgress = true;
+ prompt.inProgress = true;
+ }
+
+ Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
+ this.log("_doAsyncPrompt:run dispatched");
+ },
+
+
+ _cancelPendingPrompts : function() {
+ this.log("Canceling all pending prompts...");
+ var asyncPrompts = this._asyncPrompts;
+ this.__proto__._asyncPrompts = {};
+
+ for (var hashKey in asyncPrompts) {
+ let prompt = asyncPrompts[hashKey];
+ // Watch out! If this prompt is currently prompting, let it handle
+ // notifying the callbacks of success/failure, since it's already
+ // asking the user for input. Reusing a callback can be crashy.
+ if (prompt.inProgress) {
+ this.log("skipping a prompt in progress");
+ continue;
+ }
+
+ for (var consumer of prompt.consumers) {
+ if (!consumer.callback)
+ continue;
+
+ this.log("Canceling async auth prompt callback " + consumer.callback);
+ try {
+ consumer.callback.onAuthCancelled(consumer.context, true);
+ } catch (e) { /* Just ignore exceptions from the callback */ }
+ }
+ }
+ },
+}; // end of LoginManagerPromptFactory implementation
+
+XPCOMUtils.defineLazyGetter(this.LoginManagerPromptFactory.prototype, "log", () => {
+ let logger = LoginHelper.createLogger("Login PromptFactory");
+ return logger.log.bind(logger);
+});
+
+
+
+
+/* ==================== LoginManagerPrompter ==================== */
+
+
+
+
+/**
+ * Implements interfaces for prompting the user to enter/save/change auth info.
+ *
+ * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox.
+ *
+ * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication
+ * (eg HTTP Authenticate, FTP login).
+ *
+ * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins
+ * found in HTML forms.
+ */
+function LoginManagerPrompter() {}
+
+LoginManagerPrompter.prototype = {
+
+ classID : Components.ID("{8aa66d77-1bbb-45a6-991e-b8f47751c291}"),
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPrompt,
+ Ci.nsIAuthPrompt2,
+ Ci.nsILoginManagerPrompter]),
+
+ _factory : null,
+ _chromeWindow : null,
+ _browser : null,
+ _opener : null,
+
+ __pwmgr : null, // Password Manager service
+ get _pwmgr() {
+ if (!this.__pwmgr)
+ this.__pwmgr = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+ return this.__pwmgr;
+ },
+
+ __promptService : null, // Prompt service for user interaction
+ get _promptService() {
+ if (!this.__promptService)
+ this.__promptService =
+ Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService2);
+ return this.__promptService;
+ },
+
+
+ __strBundle : null, // String bundle for L10N
+ get _strBundle() {
+ if (!this.__strBundle) {
+ var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService);
+ this.__strBundle = bunService.createBundle(
+ "chrome://passwordmgr/locale/passwordmgr.properties");
+ if (!this.__strBundle)
+ throw new Error("String bundle for Login Manager not present!");
+ }
+
+ return this.__strBundle;
+ },
+
+
+ __ellipsis : null,
+ get _ellipsis() {
+ if (!this.__ellipsis) {
+ this.__ellipsis = "\u2026";
+ try {
+ this.__ellipsis = Services.prefs.getComplexValue(
+ "intl.ellipsis", Ci.nsIPrefLocalizedString).data;
+ } catch (e) { }
+ }
+ return this.__ellipsis;
+ },
+
+
+ // Whether we are in private browsing mode
+ get _inPrivateBrowsing() {
+ if (this._chromeWindow) {
+ return PrivateBrowsingUtils.isWindowPrivate(this._chromeWindow);
+ }
+ // If we don't that we're in private browsing mode if the caller did
+ // not provide a window. The callers which really care about this
+ // will indeed pass down a window to us, and for those who don't,
+ // we can just assume that we don't want to save the entered login
+ // information.
+ this.log("We have no chromeWindow so assume we're in a private context");
+ return true;
+ },
+
+
+
+
+ /* ---------- nsIAuthPrompt prompts ---------- */
+
+
+ /**
+ * Wrapper around the prompt service prompt. Saving random fields here
+ * doesn't really make sense and therefore isn't implemented.
+ */
+ prompt : function (aDialogTitle, aText, aPasswordRealm,
+ aSavePassword, aDefaultText, aResult) {
+ if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER)
+ throw new Components.Exception("prompt only supports SAVE_PASSWORD_NEVER",
+ Cr.NS_ERROR_NOT_IMPLEMENTED);
+
+ this.log("===== prompt() called =====");
+
+ if (aDefaultText) {
+ aResult.value = aDefaultText;
+ }
+
+ return this._promptService.prompt(this._chromeWindow,
+ aDialogTitle, aText, aResult, null, {});
+ },
+
+
+ /**
+ * Looks up a username and password in the database. Will prompt the user
+ * with a dialog, even if a username and password are found.
+ */
+ promptUsernameAndPassword : function (aDialogTitle, aText, aPasswordRealm,
+ aSavePassword, aUsername, aPassword) {
+ this.log("===== promptUsernameAndPassword() called =====");
+
+ if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
+ throw new Components.Exception("promptUsernameAndPassword doesn't support SAVE_PASSWORD_FOR_SESSION",
+ Cr.NS_ERROR_NOT_IMPLEMENTED);
+
+ var selectedLogin = null;
+ var checkBox = { value : false };
+ var checkBoxLabel = null;
+ var [hostname, realm, unused] = this._getRealmInfo(aPasswordRealm);
+
+ // If hostname is null, we can't save this login.
+ if (hostname) {
+ var canRememberLogin;
+ if (this._inPrivateBrowsing)
+ canRememberLogin = false;
+ else
+ canRememberLogin = (aSavePassword ==
+ Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
+ this._pwmgr.getLoginSavingEnabled(hostname);
+
+ // if checkBoxLabel is null, the checkbox won't be shown at all.
+ if (canRememberLogin)
+ checkBoxLabel = this._getLocalizedString("rememberPassword");
+
+ // Look for existing logins.
+ var foundLogins = this._pwmgr.findLogins({}, hostname, null,
+ realm);
+
+ // XXX Like the original code, we can't deal with multiple
+ // account selection. (bug 227632)
+ if (foundLogins.length > 0) {
+ selectedLogin = foundLogins[0];
+
+ // If the caller provided a username, try to use it. If they
+ // provided only a password, this will try to find a password-only
+ // login (or return null if none exists).
+ if (aUsername.value)
+ selectedLogin = this._repickSelectedLogin(foundLogins,
+ aUsername.value);
+
+ if (selectedLogin) {
+ checkBox.value = true;
+ aUsername.value = selectedLogin.username;
+ // If the caller provided a password, prefer it.
+ if (!aPassword.value)
+ aPassword.value = selectedLogin.password;
+ }
+ }
+ }
+
+ var ok = this._promptService.promptUsernameAndPassword(this._chromeWindow,
+ aDialogTitle, aText, aUsername, aPassword,
+ checkBoxLabel, checkBox);
+
+ if (!ok || !checkBox.value || !hostname)
+ return ok;
+
+ if (!aPassword.value) {
+ this.log("No password entered, so won't offer to save.");
+ return ok;
+ }
+
+ // XXX We can't prompt with multiple logins yet (bug 227632), so
+ // the entered login might correspond to an existing login
+ // other than the one we originally selected.
+ selectedLogin = this._repickSelectedLogin(foundLogins, aUsername.value);
+
+ // If we didn't find an existing login, or if the username
+ // changed, save as a new login.
+ let newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
+ createInstance(Ci.nsILoginInfo);
+ newLogin.init(hostname, null, realm,
+ aUsername.value, aPassword.value, "", "");
+ if (!selectedLogin) {
+ // add as new
+ this.log("New login seen for " + realm);
+ this._pwmgr.addLogin(newLogin);
+ } else if (aPassword.value != selectedLogin.password) {
+ // update password
+ this.log("Updating password for " + realm);
+ this._updateLogin(selectedLogin, newLogin);
+ } else {
+ this.log("Login unchanged, no further action needed.");
+ this._updateLogin(selectedLogin);
+ }
+
+ return ok;
+ },
+
+
+ /**
+ * If a password is found in the database for the password realm, it is
+ * returned straight away without displaying a dialog.
+ *
+ * If a password is not found in the database, the user will be prompted
+ * with a dialog with a text field and ok/cancel buttons. If the user
+ * allows it, then the password will be saved in the database.
+ */
+ promptPassword : function (aDialogTitle, aText, aPasswordRealm,
+ aSavePassword, aPassword) {
+ this.log("===== promptPassword called() =====");
+
+ if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
+ throw new Components.Exception("promptPassword doesn't support SAVE_PASSWORD_FOR_SESSION",
+ Cr.NS_ERROR_NOT_IMPLEMENTED);
+
+ var checkBox = { value : false };
+ var checkBoxLabel = null;
+ var [hostname, realm, username] = this._getRealmInfo(aPasswordRealm);
+
+ username = decodeURIComponent(username);
+
+ // If hostname is null, we can't save this login.
+ if (hostname && !this._inPrivateBrowsing) {
+ var canRememberLogin = (aSavePassword ==
+ Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
+ this._pwmgr.getLoginSavingEnabled(hostname);
+
+ // if checkBoxLabel is null, the checkbox won't be shown at all.
+ if (canRememberLogin)
+ checkBoxLabel = this._getLocalizedString("rememberPassword");
+
+ if (!aPassword.value) {
+ // Look for existing logins.
+ var foundLogins = this._pwmgr.findLogins({}, hostname, null,
+ realm);
+
+ // XXX Like the original code, we can't deal with multiple
+ // account selection (bug 227632). We can deal with finding the
+ // account based on the supplied username - but in this case we'll
+ // just return the first match.
+ for (var i = 0; i < foundLogins.length; ++i) {
+ if (foundLogins[i].username == username) {
+ aPassword.value = foundLogins[i].password;
+ // wallet returned straight away, so this mimics that code
+ return true;
+ }
+ }
+ }
+ }
+
+ var ok = this._promptService.promptPassword(this._chromeWindow, aDialogTitle,
+ aText, aPassword,
+ checkBoxLabel, checkBox);
+
+ if (ok && checkBox.value && hostname && aPassword.value) {
+ var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
+ createInstance(Ci.nsILoginInfo);
+ newLogin.init(hostname, null, realm, username,
+ aPassword.value, "", "");
+
+ this.log("New login seen for " + realm);
+
+ this._pwmgr.addLogin(newLogin);
+ }
+
+ return ok;
+ },
+
+ /* ---------- nsIAuthPrompt helpers ---------- */
+
+
+ /**
+ * Given aRealmString, such as "http://user@example.com/foo", returns an
+ * array of:
+ * - the formatted hostname
+ * - the realm (hostname + path)
+ * - the username, if present
+ *
+ * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S]
+ * channels, e.g. "example.com:80 (httprealm)", null is returned for all
+ * arguments to let callers know the login can't be saved because we don't
+ * know whether it's http or https.
+ */
+ _getRealmInfo : function (aRealmString) {
+ var httpRealm = /^.+ \(.+\)$/;
+ if (httpRealm.test(aRealmString))
+ return [null, null, null];
+
+ var uri = Services.io.newURI(aRealmString, null, null);
+ var pathname = "";
+
+ if (uri.path != "/")
+ pathname = uri.path;
+
+ var formattedHostname = this._getFormattedHostname(uri);
+
+ return [formattedHostname, formattedHostname + pathname, uri.username];
+ },
+
+ /* ---------- nsIAuthPrompt2 prompts ---------- */
+
+
+
+
+ /**
+ * Implementation of nsIAuthPrompt2.
+ *
+ * @param {nsIChannel} aChannel
+ * @param {int} aLevel
+ * @param {nsIAuthInformation} aAuthInfo
+ */
+ promptAuth : function (aChannel, aLevel, aAuthInfo) {
+ var selectedLogin = null;
+ var checkbox = { value : false };
+ var checkboxLabel = null;
+ var epicfail = false;
+ var canAutologin = false;
+ var notifyObj;
+ var foundLogins;
+
+ try {
+ this.log("===== promptAuth called =====");
+
+ // If the user submits a login but it fails, we need to remove the
+ // notification bar that was displayed. Conveniently, the user will
+ // be prompted for authentication again, which brings us here.
+ this._removeLoginNotifications();
+
+ var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);
+
+ // Looks for existing logins to prefill the prompt with.
+ foundLogins = LoginHelper.searchLoginsWithObject({
+ hostname,
+ httpRealm,
+ schemeUpgrades: LoginHelper.schemeUpgrades,
+ });
+ this.log("found", foundLogins.length, "matching logins.");
+ let resolveBy = [
+ "scheme",
+ "timePasswordChanged",
+ ];
+ foundLogins = LoginHelper.dedupeLogins(foundLogins, ["username"], resolveBy, hostname);
+ this.log(foundLogins.length, "matching logins remain after deduping");
+
+ // XXX Can't select from multiple accounts yet. (bug 227632)
+ if (foundLogins.length > 0) {
+ selectedLogin = foundLogins[0];
+ this._SetAuthInfo(aAuthInfo, selectedLogin.username,
+ selectedLogin.password);
+
+ // Allow automatic proxy login
+ if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY &&
+ !(aAuthInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) &&
+ Services.prefs.getBoolPref("signon.autologin.proxy") &&
+ !this._inPrivateBrowsing) {
+
+ this.log("Autologin enabled, skipping auth prompt.");
+ canAutologin = true;
+ }
+
+ checkbox.value = true;
+ }
+
+ var canRememberLogin = this._pwmgr.getLoginSavingEnabled(hostname);
+ if (this._inPrivateBrowsing)
+ canRememberLogin = false;
+
+ // if checkboxLabel is null, the checkbox won't be shown at all.
+ notifyObj = this._getPopupNote() || this._getNotifyBox();
+ if (canRememberLogin && !notifyObj)
+ checkboxLabel = this._getLocalizedString("rememberPassword");
+ } catch (e) {
+ // Ignore any errors and display the prompt anyway.
+ epicfail = true;
+ Components.utils.reportError("LoginManagerPrompter: " +
+ "Epic fail in promptAuth: " + e + "\n");
+ }
+
+ var ok = canAutologin;
+ if (!ok) {
+ if (this._chromeWindow)
+ PromptUtils.fireDialogEvent(this._chromeWindow, "DOMWillOpenModalDialog", this._browser);
+ ok = this._promptService.promptAuth(this._chromeWindow,
+ aChannel, aLevel, aAuthInfo,
+ checkboxLabel, checkbox);
+ }
+
+ // If there's a notification box, use it to allow the user to
+ // determine if the login should be saved. If there isn't a
+ // notification box, only save the login if the user set the
+ // checkbox to do so.
+ var rememberLogin = notifyObj ? canRememberLogin : checkbox.value;
+ if (!ok || !rememberLogin || epicfail)
+ return ok;
+
+ try {
+ var [username, password] = this._GetAuthInfo(aAuthInfo);
+
+ if (!password) {
+ this.log("No password entered, so won't offer to save.");
+ return ok;
+ }
+
+ // XXX We can't prompt with multiple logins yet (bug 227632), so
+ // the entered login might correspond to an existing login
+ // other than the one we originally selected.
+ selectedLogin = this._repickSelectedLogin(foundLogins, username);
+
+ // If we didn't find an existing login, or if the username
+ // changed, save as a new login.
+ let newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
+ createInstance(Ci.nsILoginInfo);
+ newLogin.init(hostname, null, httpRealm,
+ username, password, "", "");
+ if (!selectedLogin) {
+ this.log("New login seen for " + username +
+ " @ " + hostname + " (" + httpRealm + ")");
+
+ if (notifyObj)
+ this._showSaveLoginNotification(notifyObj, newLogin);
+ else
+ this._pwmgr.addLogin(newLogin);
+ } else if (password != selectedLogin.password) {
+ this.log("Updating password for " + username +
+ " @ " + hostname + " (" + httpRealm + ")");
+ if (notifyObj)
+ this._showChangeLoginNotification(notifyObj,
+ selectedLogin, newLogin);
+ else
+ this._updateLogin(selectedLogin, newLogin);
+ } else {
+ this.log("Login unchanged, no further action needed.");
+ this._updateLogin(selectedLogin);
+ }
+ } catch (e) {
+ Components.utils.reportError("LoginManagerPrompter: " +
+ "Fail2 in promptAuth: " + e + "\n");
+ }
+
+ return ok;
+ },
+
+ asyncPromptAuth : function (aChannel, aCallback, aContext, aLevel, aAuthInfo) {
+ var cancelable = null;
+
+ try {
+ this.log("===== asyncPromptAuth called =====");
+
+ // If the user submits a login but it fails, we need to remove the
+ // notification bar that was displayed. Conveniently, the user will
+ // be prompted for authentication again, which brings us here.
+ this._removeLoginNotifications();
+
+ cancelable = this._newAsyncPromptConsumer(aCallback, aContext);
+
+ var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);
+
+ var hashKey = aLevel + "|" + hostname + "|" + httpRealm;
+ this.log("Async prompt key = " + hashKey);
+ var asyncPrompt = this._factory._asyncPrompts[hashKey];
+ if (asyncPrompt) {
+ this.log("Prompt bound to an existing one in the queue, callback = " + aCallback);
+ asyncPrompt.consumers.push(cancelable);
+ return cancelable;
+ }
+
+ this.log("Adding new prompt to the queue, callback = " + aCallback);
+ asyncPrompt = {
+ consumers: [cancelable],
+ channel: aChannel,
+ authInfo: aAuthInfo,
+ level: aLevel,
+ inProgress : false,
+ prompter: this
+ };
+
+ this._factory._asyncPrompts[hashKey] = asyncPrompt;
+ this._factory._doAsyncPrompt();
+ } catch (e) {
+ Components.utils.reportError("LoginManagerPrompter: " +
+ "asyncPromptAuth: " + e + "\nFalling back to promptAuth\n");
+ // Fail the prompt operation to let the consumer fall back
+ // to synchronous promptAuth method
+ throw e;
+ }
+
+ return cancelable;
+ },
+
+
+
+
+ /* ---------- nsILoginManagerPrompter prompts ---------- */
+
+
+ init : function (aWindow = null, aFactory = null) {
+ if (!aWindow) {
+ // There may be no applicable window e.g. in a Sandbox or JSM.
+ this._chromeWindow = null;
+ this._browser = null;
+ } else if (aWindow instanceof Ci.nsIDOMChromeWindow) {
+ this._chromeWindow = aWindow;
+ // needs to be set explicitly using setBrowser
+ this._browser = null;
+ } else {
+ let {win, browser} = this._getChromeWindow(aWindow);
+ this._chromeWindow = win;
+ this._browser = browser;
+ }
+ this._opener = null;
+ this._factory = aFactory || null;
+
+ this.log("===== initialized =====");
+ },
+
+ set browser(aBrowser) {
+ this._browser = aBrowser;
+ },
+
+ set opener(aOpener) {
+ this._opener = aOpener;
+ },
+
+ promptToSavePassword : function (aLogin) {
+ this.log("promptToSavePassword");
+ var notifyObj = this._getPopupNote() || this._getNotifyBox();
+ if (notifyObj)
+ this._showSaveLoginNotification(notifyObj, aLogin);
+ else
+ this._showSaveLoginDialog(aLogin);
+ },
+
+ /**
+ * Displays a notification bar.
+ */
+ _showLoginNotification : function (aNotifyBox, aName, aText, aButtons) {
+ var oldBar = aNotifyBox.getNotificationWithValue(aName);
+ const priority = aNotifyBox.PRIORITY_INFO_MEDIUM;
+
+ this.log("Adding new " + aName + " notification bar");
+ var newBar = aNotifyBox.appendNotification(
+ aText, aName, "",
+ priority, aButtons);
+
+ // The page we're going to hasn't loaded yet, so we want to persist
+ // across the first location change.
+ newBar.persistence++;
+
+ // Sites like Gmail perform a funky redirect dance before you end up
+ // at the post-authentication page. I don't see a good way to
+ // heuristically determine when to ignore such location changes, so
+ // we'll try ignoring location changes based on a time interval.
+ newBar.timeout = Date.now() + 20000; // 20 seconds
+
+ if (oldBar) {
+ this.log("(...and removing old " + aName + " notification bar)");
+ aNotifyBox.removeNotification(oldBar);
+ }
+ },
+
+ /**
+ * Displays the PopupNotifications.jsm doorhanger for password save or change.
+ *
+ * @param {nsILoginInfo} login
+ * Login to save or change. For changes, this login should contain the
+ * new password.
+ * @param {string} type
+ * This is "password-save" or "password-change" depending on the
+ * original notification type. This is used for tests.
+ */
+ _showLoginCaptureDoorhanger(login, type) {
+ let { browser } = this._getNotifyWindow();
+ if (!browser) {
+ return;
+ }
+
+ let saveMsgNames = {
+ prompt: login.username === "" ? "rememberLoginMsgNoUser"
+ : "rememberLoginMsg",
+ buttonLabel: "rememberLoginButtonText",
+ buttonAccessKey: "rememberLoginButtonAccessKey",
+ };
+
+ let changeMsgNames = {
+ prompt: login.username === "" ? "updateLoginMsgNoUser"
+ : "updateLoginMsg",
+ buttonLabel: "updateLoginButtonText",
+ buttonAccessKey: "updateLoginButtonAccessKey",
+ };
+
+ let initialMsgNames = type == "password-save" ? saveMsgNames
+ : changeMsgNames;
+
+ let brandBundle = Services.strings.createBundle(BRAND_BUNDLE);
+ let brandShortName = brandBundle.GetStringFromName("brandShortName");
+ let promptMsg = type == "password-save" ? this._getLocalizedString(saveMsgNames.prompt, [brandShortName])
+ : this._getLocalizedString(changeMsgNames.prompt);
+
+ let chromeDoc = browser.ownerDocument;
+
+ let currentNotification;
+
+ let updateButtonStatus = (element) => {
+ let mainActionButton = chromeDoc.getAnonymousElementByAttribute(element.button, "anonid", "button");
+ // Disable the main button inside the menu-button if the password field is empty.
+ if (login.password.length == 0) {
+ mainActionButton.setAttribute("disabled", true);
+ chromeDoc.getElementById("password-notification-password")
+ .classList.add("popup-notification-invalid-input");
+ } else {
+ mainActionButton.removeAttribute("disabled");
+ chromeDoc.getElementById("password-notification-password")
+ .classList.remove("popup-notification-invalid-input");
+ }
+ };
+
+ let updateButtonLabel = () => {
+ let foundLogins = LoginHelper.searchLoginsWithObject({
+ formSubmitURL: login.formSubmitURL,
+ hostname: login.hostname,
+ httpRealm: login.httpRealm,
+ schemeUpgrades: LoginHelper.schemeUpgrades,
+ });
+
+ let logins = this._filterUpdatableLogins(login, foundLogins);
+ let msgNames = (logins.length == 0) ? saveMsgNames : changeMsgNames;
+
+ // Update the label based on whether this will be a new login or not.
+ let label = this._getLocalizedString(msgNames.buttonLabel);
+ let accessKey = this._getLocalizedString(msgNames.buttonAccessKey);
+
+ // Update the labels for the next time the panel is opened.
+ currentNotification.mainAction.label = label;
+ currentNotification.mainAction.accessKey = accessKey;
+
+ // Update the labels in real time if the notification is displayed.
+ let element = [...currentNotification.owner.panel.childNodes]
+ .find(n => n.notification == currentNotification);
+ if (element) {
+ element.setAttribute("buttonlabel", label);
+ element.setAttribute("buttonaccesskey", accessKey);
+ updateButtonStatus(element);
+ }
+ };
+
+ let writeDataToUI = () => {
+ // setAttribute is used since the <textbox> binding may not be attached yet.
+ chromeDoc.getElementById("password-notification-username")
+ .setAttribute("placeholder", usernamePlaceholder);
+ chromeDoc.getElementById("password-notification-username")
+ .setAttribute("value", login.username);
+
+ let toggleCheckbox = chromeDoc.getElementById("password-notification-visibilityToggle");
+ toggleCheckbox.removeAttribute("checked");
+ let passwordField = chromeDoc.getElementById("password-notification-password");
+ // Ensure the type is reset so the field is masked.
+ passwordField.setAttribute("type", "password");
+ passwordField.setAttribute("value", login.password);
+ updateButtonLabel();
+ };
+
+ let readDataFromUI = () => {
+ login.username =
+ chromeDoc.getElementById("password-notification-username").value;
+ login.password =
+ chromeDoc.getElementById("password-notification-password").value;
+ };
+
+ let onInput = () => {
+ readDataFromUI();
+ updateButtonLabel();
+ };
+
+ let onVisibilityToggle = (commandEvent) => {
+ let passwordField = chromeDoc.getElementById("password-notification-password");
+ // Gets the caret position before changing the type of the textbox
+ let selectionStart = passwordField.selectionStart;
+ let selectionEnd = passwordField.selectionEnd;
+ passwordField.setAttribute("type", commandEvent.target.checked ? "" : "password");
+ if (!passwordField.hasAttribute("focused")) {
+ return;
+ }
+ passwordField.selectionStart = selectionStart;
+ passwordField.selectionEnd = selectionEnd;
+ };
+
+ let persistData = () => {
+ let foundLogins = LoginHelper.searchLoginsWithObject({
+ formSubmitURL: login.formSubmitURL,
+ hostname: login.hostname,
+ httpRealm: login.httpRealm,
+ schemeUpgrades: LoginHelper.schemeUpgrades,
+ });
+
+ let logins = this._filterUpdatableLogins(login, foundLogins);
+
+ if (logins.length == 0) {
+ // The original login we have been provided with might have its own
+ // metadata, but we don't want it propagated to the newly created one.
+ Services.logins.addLogin(new LoginInfo(login.hostname,
+ login.formSubmitURL,
+ login.httpRealm,
+ login.username,
+ login.password,
+ login.usernameField,
+ login.passwordField));
+ } else if (logins.length == 1) {
+ if (logins[0].password == login.password &&
+ logins[0].username == login.username) {
+ // We only want to touch the login's use count and last used time.
+ this._updateLogin(logins[0]);
+ } else {
+ this._updateLogin(logins[0], login);
+ }
+ } else {
+ Cu.reportError("Unexpected match of multiple logins.");
+ }
+ };
+
+ // The main action is the "Remember" or "Update" button.
+ let mainAction = {
+ label: this._getLocalizedString(initialMsgNames.buttonLabel),
+ accessKey: this._getLocalizedString(initialMsgNames.buttonAccessKey),
+ callback: () => {
+ readDataFromUI();
+ persistData();
+ browser.focus();
+ }
+ };
+
+ // Include a "Never for this site" button when saving a new password.
+ let secondaryActions = type == "password-save" ? [{
+ label: this._getLocalizedString("notifyBarNeverRememberButtonText"),
+ accessKey: this._getLocalizedString("notifyBarNeverRememberButtonAccessKey"),
+ callback: () => {
+ Services.logins.setLoginSavingEnabled(login.hostname, false);
+ browser.focus();
+ }
+ }] : null;
+
+ let usernamePlaceholder = this._getLocalizedString("noUsernamePlaceholder");
+ let togglePasswordLabel = this._getLocalizedString("togglePasswordLabel");
+ let togglePasswordAccessKey = this._getLocalizedString("togglePasswordAccessKey");
+
+ this._getPopupNote().show(
+ browser,
+ "password",
+ promptMsg,
+ "password-notification-icon",
+ mainAction,
+ secondaryActions,
+ {
+ timeout: Date.now() + 10000,
+ displayURI: Services.io.newURI(login.hostname, null, null),
+ persistWhileVisible: true,
+ passwordNotificationType: type,
+ eventCallback: function (topic) {
+ switch (topic) {
+ case "showing":
+ currentNotification = this;
+ chromeDoc.getElementById("password-notification-password")
+ .removeAttribute("focused");
+ chromeDoc.getElementById("password-notification-username")
+ .removeAttribute("focused");
+ chromeDoc.getElementById("password-notification-username")
+ .addEventListener("input", onInput);
+ chromeDoc.getElementById("password-notification-password")
+ .addEventListener("input", onInput);
+ let toggleBtn = chromeDoc.getElementById("password-notification-visibilityToggle");
+
+ if (Services.prefs.getBoolPref("signon.rememberSignons.visibilityToggle")) {
+ toggleBtn.addEventListener("command", onVisibilityToggle);
+ toggleBtn.setAttribute("label", togglePasswordLabel);
+ toggleBtn.setAttribute("accesskey", togglePasswordAccessKey);
+ toggleBtn.setAttribute("hidden", LoginHelper.isMasterPasswordSet());
+ }
+ if (this.wasDismissed) {
+ chromeDoc.getElementById("password-notification-visibilityToggle")
+ .setAttribute("hidden", true);
+ }
+ break;
+ case "shown":
+ writeDataToUI();
+ break;
+ case "dismissed":
+ this.wasDismissed = true;
+ readDataFromUI();
+ // Fall through.
+ case "removed":
+ currentNotification = null;
+ chromeDoc.getElementById("password-notification-username")
+ .removeEventListener("input", onInput);
+ chromeDoc.getElementById("password-notification-password")
+ .removeEventListener("input", onInput);
+ chromeDoc.getElementById("password-notification-visibilityToggle")
+ .removeEventListener("command", onVisibilityToggle);
+ break;
+ }
+ return false;
+ },
+ }
+ );
+ },
+
+ /**
+ * Displays a notification bar or a popup notification, to allow the user
+ * to save the specified login. This allows the user to see the results of
+ * their login, and only save a login which they know worked.
+ *
+ * @param aNotifyObj
+ * A notification box or a popup notification.
+ * @param aLogin
+ * The login captured from the form.
+ */
+ _showSaveLoginNotification : function (aNotifyObj, aLogin) {
+ // Ugh. We can't use the strings from the popup window, because they
+ // have the access key marked in the string (eg "Mo&zilla"), along
+ // with some weird rules for handling access keys that do not occur
+ // in the string, for L10N. See commonDialog.js's setLabelForNode().
+ var neverButtonText =
+ this._getLocalizedString("notifyBarNeverRememberButtonText");
+ var neverButtonAccessKey =
+ this._getLocalizedString("notifyBarNeverRememberButtonAccessKey");
+ var rememberButtonText =
+ this._getLocalizedString("notifyBarRememberPasswordButtonText");
+ var rememberButtonAccessKey =
+ this._getLocalizedString("notifyBarRememberPasswordButtonAccessKey");
+
+ var displayHost = this._getShortDisplayHost(aLogin.hostname);
+ var notificationText = this._getLocalizedString(
+ "rememberPasswordMsgNoUsername",
+ [displayHost]);
+
+ // The callbacks in |buttons| have a closure to access the variables
+ // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
+ // without a getService() call.
+ var pwmgr = this._pwmgr;
+
+ // Notification is a PopupNotification
+ if (aNotifyObj == this._getPopupNote()) {
+ this._showLoginCaptureDoorhanger(aLogin, "password-save");
+ } else {
+ var notNowButtonText =
+ this._getLocalizedString("notifyBarNotNowButtonText");
+ var notNowButtonAccessKey =
+ this._getLocalizedString("notifyBarNotNowButtonAccessKey");
+ var buttons = [
+ // "Remember" button
+ {
+ label: rememberButtonText,
+ accessKey: rememberButtonAccessKey,
+ popup: null,
+ callback: function(aNotifyObj, aButton) {
+ pwmgr.addLogin(aLogin);
+ }
+ },
+
+ // "Never for this site" button
+ {
+ label: neverButtonText,
+ accessKey: neverButtonAccessKey,
+ popup: null,
+ callback: function(aNotifyObj, aButton) {
+ pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
+ }
+ },
+
+ // "Not now" button
+ {
+ label: notNowButtonText,
+ accessKey: notNowButtonAccessKey,
+ popup: null,
+ callback: function() { /* NOP */ }
+ }
+ ];
+
+ this._showLoginNotification(aNotifyObj, "password-save",
+ notificationText, buttons);
+ }
+
+ Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save", null);
+ },
+
+ _removeLoginNotifications : function () {
+ var popupNote = this._getPopupNote();
+ if (popupNote)
+ popupNote = popupNote.getNotification("password");
+ if (popupNote)
+ popupNote.remove();
+
+ var notifyBox = this._getNotifyBox();
+ if (notifyBox) {
+ var oldBar = notifyBox.getNotificationWithValue("password-save");
+ if (oldBar) {
+ this.log("Removing save-password notification bar.");
+ notifyBox.removeNotification(oldBar);
+ }
+
+ oldBar = notifyBox.getNotificationWithValue("password-change");
+ if (oldBar) {
+ this.log("Removing change-password notification bar.");
+ notifyBox.removeNotification(oldBar);
+ }
+ }
+ },
+
+
+ /**
+ * Called when we detect a new login in a form submission,
+ * asks the user what to do.
+ */
+ _showSaveLoginDialog : function (aLogin) {
+ const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT +
+ (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
+ (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) +
+ (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2);
+
+ var displayHost = this._getShortDisplayHost(aLogin.hostname);
+
+ var dialogText;
+ if (aLogin.username) {
+ var displayUser = this._sanitizeUsername(aLogin.username);
+ dialogText = this._getLocalizedString(
+ "rememberPasswordMsg",
+ [displayUser, displayHost]);
+ } else {
+ dialogText = this._getLocalizedString(
+ "rememberPasswordMsgNoUsername",
+ [displayHost]);
+
+ }
+ var dialogTitle = this._getLocalizedString(
+ "savePasswordTitle");
+ var neverButtonText = this._getLocalizedString(
+ "neverForSiteButtonText");
+ var rememberButtonText = this._getLocalizedString(
+ "rememberButtonText");
+ var notNowButtonText = this._getLocalizedString(
+ "notNowButtonText");
+
+ this.log("Prompting user to save/ignore login");
+ var userChoice = this._promptService.confirmEx(this._chromeWindow,
+ dialogTitle, dialogText,
+ buttonFlags, rememberButtonText,
+ notNowButtonText, neverButtonText,
+ null, {});
+ // Returns:
+ // 0 - Save the login
+ // 1 - Ignore the login this time
+ // 2 - Never save logins for this site
+ if (userChoice == 2) {
+ this.log("Disabling " + aLogin.hostname + " logins by request.");
+ this._pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
+ } else if (userChoice == 0) {
+ this.log("Saving login for " + aLogin.hostname);
+ this._pwmgr.addLogin(aLogin);
+ } else {
+ // userChoice == 1 --> just ignore the login.
+ this.log("Ignoring login.");
+ }
+
+ Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save", null);
+ },
+
+
+ /**
+ * Called when we think we detect a password or username change for
+ * an existing login, when the form being submitted contains multiple
+ * password fields.
+ *
+ * @param {nsILoginInfo} aOldLogin
+ * The old login we may want to update.
+ * @param {nsILoginInfo} aNewLogin
+ * The new login from the page form.
+ */
+ promptToChangePassword(aOldLogin, aNewLogin) {
+ this.log("promptToChangePassword");
+ let notifyObj = this._getPopupNote() || this._getNotifyBox();
+
+ if (notifyObj) {
+ this._showChangeLoginNotification(notifyObj, aOldLogin,
+ aNewLogin);
+ } else {
+ this._showChangeLoginDialog(aOldLogin, aNewLogin);
+ }
+ },
+
+ /**
+ * Shows the Change Password notification bar or popup notification.
+ *
+ * @param aNotifyObj
+ * A notification box or a popup notification.
+ *
+ * @param aOldLogin
+ * The stored login we want to update.
+ *
+ * @param aNewLogin
+ * The login object with the changes we want to make.
+ */
+ _showChangeLoginNotification(aNotifyObj, aOldLogin, aNewLogin) {
+ var changeButtonText =
+ this._getLocalizedString("notifyBarUpdateButtonText");
+ var changeButtonAccessKey =
+ this._getLocalizedString("notifyBarUpdateButtonAccessKey");
+
+ // We reuse the existing message, even if it expects a username, until we
+ // switch to the final terminology in bug 1144856.
+ var displayHost = this._getShortDisplayHost(aOldLogin.hostname);
+ var notificationText = this._getLocalizedString("updatePasswordMsg",
+ [displayHost]);
+
+ // The callbacks in |buttons| have a closure to access the variables
+ // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
+ // without a getService() call.
+ var self = this;
+
+ // Notification is a PopupNotification
+ if (aNotifyObj == this._getPopupNote()) {
+ aOldLogin.hostname = aNewLogin.hostname;
+ aOldLogin.formSubmitURL = aNewLogin.formSubmitURL;
+ aOldLogin.password = aNewLogin.password;
+ aOldLogin.username = aNewLogin.username;
+ this._showLoginCaptureDoorhanger(aOldLogin, "password-change");
+ } else {
+ var dontChangeButtonText =
+ this._getLocalizedString("notifyBarDontChangeButtonText");
+ var dontChangeButtonAccessKey =
+ this._getLocalizedString("notifyBarDontChangeButtonAccessKey");
+ var buttons = [
+ // "Yes" button
+ {
+ label: changeButtonText,
+ accessKey: changeButtonAccessKey,
+ popup: null,
+ callback: function(aNotifyObj, aButton) {
+ self._updateLogin(aOldLogin, aNewLogin);
+ }
+ },
+
+ // "No" button
+ {
+ label: dontChangeButtonText,
+ accessKey: dontChangeButtonAccessKey,
+ popup: null,
+ callback: function(aNotifyObj, aButton) {
+ // do nothing
+ }
+ }
+ ];
+
+ this._showLoginNotification(aNotifyObj, "password-change",
+ notificationText, buttons);
+ }
+
+ let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid;
+ Services.obs.notifyObservers(aNewLogin, "passwordmgr-prompt-change", oldGUID);
+ },
+
+
+ /**
+ * Shows the Change Password dialog.
+ */
+ _showChangeLoginDialog(aOldLogin, aNewLogin) {
+ const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
+
+ var dialogText;
+ if (aOldLogin.username)
+ dialogText = this._getLocalizedString(
+ "updatePasswordMsg",
+ [aOldLogin.username]);
+ else
+ dialogText = this._getLocalizedString(
+ "updatePasswordMsgNoUser");
+
+ var dialogTitle = this._getLocalizedString(
+ "passwordChangeTitle");
+
+ // returns 0 for yes, 1 for no.
+ var ok = !this._promptService.confirmEx(this._chromeWindow,
+ dialogTitle, dialogText, buttonFlags,
+ null, null, null,
+ null, {});
+ if (ok) {
+ this.log("Updating password for user " + aOldLogin.username);
+ this._updateLogin(aOldLogin, aNewLogin);
+ }
+
+ let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid;
+ Services.obs.notifyObservers(aNewLogin, "passwordmgr-prompt-change", oldGUID);
+ },
+
+
+ /**
+ * Called when we detect a password change in a form submission, but we
+ * don't know which existing login (username) it's for. Asks the user
+ * to select a username and confirm the password change.
+ *
+ * Note: The caller doesn't know the username for aNewLogin, so this
+ * function fills in .username and .usernameField with the values
+ * from the login selected by the user.
+ *
+ * Note; XPCOM stupidity: |count| is just |logins.length|.
+ */
+ promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) {
+ this.log("promptToChangePasswordWithUsernames with count:", count);
+
+ var usernames = logins.map(l => l.username);
+ var dialogText = this._getLocalizedString("userSelectText");
+ var dialogTitle = this._getLocalizedString("passwordChangeTitle");
+ var selectedIndex = { value: null };
+
+ // If user selects ok, outparam.value is set to the index
+ // of the selected username.
+ var ok = this._promptService.select(this._chromeWindow,
+ dialogTitle, dialogText,
+ usernames.length, usernames,
+ selectedIndex);
+ if (ok) {
+ // Now that we know which login to use, modify its password.
+ var selectedLogin = logins[selectedIndex.value];
+ this.log("Updating password for user " + selectedLogin.username);
+ var newLoginWithUsername = Cc["@mozilla.org/login-manager/loginInfo;1"].
+ createInstance(Ci.nsILoginInfo);
+ newLoginWithUsername.init(aNewLogin.hostname,
+ aNewLogin.formSubmitURL, aNewLogin.httpRealm,
+ selectedLogin.username, aNewLogin.password,
+ selectedLogin.userNameField, aNewLogin.passwordField);
+ this._updateLogin(selectedLogin, newLoginWithUsername);
+ }
+ },
+
+
+
+
+ /* ---------- Internal Methods ---------- */
+
+
+
+
+ _updateLogin(login, aNewLogin = null) {
+ var now = Date.now();
+ var propBag = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ if (aNewLogin) {
+ propBag.setProperty("formSubmitURL", aNewLogin.formSubmitURL);
+ propBag.setProperty("hostname", aNewLogin.hostname);
+ propBag.setProperty("password", aNewLogin.password);
+ propBag.setProperty("username", aNewLogin.username);
+ // Explicitly set the password change time here (even though it would
+ // be changed automatically), to ensure that it's exactly the same
+ // value as timeLastUsed.
+ propBag.setProperty("timePasswordChanged", now);
+ }
+ propBag.setProperty("timeLastUsed", now);
+ propBag.setProperty("timesUsedIncrement", 1);
+ this._pwmgr.modifyLogin(login, propBag);
+ },
+
+ /**
+ * Given a content DOM window, returns the chrome window and browser it's in.
+ */
+ _getChromeWindow: function (aWindow) {
+ // Handle non-e10s toolkit consumers.
+ if (!Cu.isCrossProcessWrapper(aWindow)) {
+ let chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler.ownerGlobal;
+ if (!chromeWin) {
+ return null;
+ }
+
+ // gBrowser only exists on some apps, like Firefox.
+ let tabbrowser = chromeWin.gBrowser ||
+ (typeof chromeWin.getBrowser == "function" ? chromeWin.getBrowser() : null);
+ // At least serve the chrome window if getBrowser()
+ // or getBrowserForContentWindow() are not supported.
+ if (!tabbrowser || typeof tabbrowser.getBrowserForContentWindow != "function") {
+ return { win: chromeWin };
+ }
+
+ let browser = tabbrowser.getBrowserForContentWindow(aWindow);
+ return { win: chromeWin, browser };
+ }
+
+ let windows = Services.wm.getEnumerator(null);
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ let tabbrowser = win.gBrowser || win.getBrowser();
+ let browser = tabbrowser.getBrowserForContentWindow(aWindow);
+ if (browser) {
+ return { win, browser };
+ }
+ }
+ return null;
+ },
+
+ _getNotifyWindow: function () {
+ // Some sites pop up a temporary login window, which disappears
+ // upon submission of credentials. We want to put the notification
+ // bar in the opener window if this seems to be happening.
+ if (this._opener) {
+ let chromeDoc = this._chromeWindow.document.documentElement;
+
+ // Check to see if the current window was opened with chrome
+ // disabled, and if so use the opener window. But if the window
+ // has been used to visit other pages (ie, has a history),
+ // assume it'll stick around and *don't* use the opener.
+ if (chromeDoc.getAttribute("chromehidden") && !this._browser.canGoBack) {
+ this.log("Using opener window for notification bar.");
+ return this._getChromeWindow(this._opener);
+ }
+ }
+
+ return { win: this._chromeWindow, browser: this._browser };
+ },
+
+ /**
+ * Returns the popup notification to this prompter,
+ * or null if there isn't one available.
+ */
+ _getPopupNote : function () {
+ let popupNote = null;
+
+ try {
+ let { win: notifyWin } = this._getNotifyWindow();
+
+ // .wrappedJSObject needed here -- see bug 422974 comment 5.
+ popupNote = notifyWin.wrappedJSObject.PopupNotifications;
+ } catch (e) {
+ this.log("Popup notifications not available on window");
+ }
+
+ return popupNote;
+ },
+
+
+ /**
+ * Returns the notification box to this prompter, or null if there isn't
+ * a notification box available.
+ */
+ _getNotifyBox : function () {
+ let notifyBox = null;
+
+ try {
+ let { win: notifyWin } = this._getNotifyWindow();
+
+ // .wrappedJSObject needed here -- see bug 422974 comment 5.
+ notifyBox = notifyWin.wrappedJSObject.getNotificationBox(notifyWin);
+ } catch (e) {
+ this.log("Notification bars not available on window");
+ }
+
+ return notifyBox;
+ },
+
+
+ /**
+ * The user might enter a login that isn't the one we prefilled, but
+ * is the same as some other existing login. So, pick a login with a
+ * matching username, or return null.
+ */
+ _repickSelectedLogin : function (foundLogins, username) {
+ for (var i = 0; i < foundLogins.length; i++)
+ if (foundLogins[i].username == username)
+ return foundLogins[i];
+ return null;
+ },
+
+
+ /**
+ * Can be called as:
+ * _getLocalizedString("key1");
+ * _getLocalizedString("key2", ["arg1"]);
+ * _getLocalizedString("key3", ["arg1", "arg2"]);
+ * (etc)
+ *
+ * Returns the localized string for the specified key,
+ * formatted if required.
+ *
+ */
+ _getLocalizedString : function (key, formatArgs) {
+ if (formatArgs)
+ return this._strBundle.formatStringFromName(
+ key, formatArgs, formatArgs.length);
+ return this._strBundle.GetStringFromName(key);
+ },
+
+
+ /**
+ * Sanitizes the specified username, by stripping quotes and truncating if
+ * it's too long. This helps prevent an evil site from messing with the
+ * "save password?" prompt too much.
+ */
+ _sanitizeUsername : function (username) {
+ if (username.length > 30) {
+ username = username.substring(0, 30);
+ username += this._ellipsis;
+ }
+ return username.replace(/['"]/g, "");
+ },
+
+
+ /**
+ * The aURI parameter may either be a string uri, or an nsIURI instance.
+ *
+ * Returns the hostname to use in a nsILoginInfo object (for example,
+ * "http://example.com").
+ */
+ _getFormattedHostname : function (aURI) {
+ let uri;
+ if (aURI instanceof Ci.nsIURI) {
+ uri = aURI;
+ } else {
+ uri = Services.io.newURI(aURI, null, null);
+ }
+
+ return uri.scheme + "://" + uri.hostPort;
+ },
+
+
+ /**
+ * Converts a login's hostname field (a URL) to a short string for
+ * prompting purposes. Eg, "http://foo.com" --> "foo.com", or
+ * "ftp://www.site.co.uk" --> "site.co.uk".
+ */
+ _getShortDisplayHost: function (aURIString) {
+ var displayHost;
+
+ var eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].
+ getService(Ci.nsIEffectiveTLDService);
+ var idnService = Cc["@mozilla.org/network/idn-service;1"].
+ getService(Ci.nsIIDNService);
+ try {
+ var uri = Services.io.newURI(aURIString, null, null);
+ var baseDomain = eTLDService.getBaseDomain(uri);
+ displayHost = idnService.convertToDisplayIDN(baseDomain, {});
+ } catch (e) {
+ this.log("_getShortDisplayHost couldn't process " + aURIString);
+ }
+
+ if (!displayHost)
+ displayHost = aURIString;
+
+ return displayHost;
+ },
+
+
+ /**
+ * Returns the hostname and realm for which authentication is being
+ * requested, in the format expected to be used with nsILoginInfo.
+ */
+ _getAuthTarget : function (aChannel, aAuthInfo) {
+ var hostname, realm;
+
+ // If our proxy is demanding authentication, don't use the
+ // channel's actual destination.
+ if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
+ this.log("getAuthTarget is for proxy auth");
+ if (!(aChannel instanceof Ci.nsIProxiedChannel))
+ throw new Error("proxy auth needs nsIProxiedChannel");
+
+ var info = aChannel.proxyInfo;
+ if (!info)
+ throw new Error("proxy auth needs nsIProxyInfo");
+
+ // Proxies don't have a scheme, but we'll use "moz-proxy://"
+ // so that it's more obvious what the login is for.
+ var idnService = Cc["@mozilla.org/network/idn-service;1"].
+ getService(Ci.nsIIDNService);
+ hostname = "moz-proxy://" +
+ idnService.convertUTF8toACE(info.host) +
+ ":" + info.port;
+ realm = aAuthInfo.realm;
+ if (!realm)
+ realm = hostname;
+
+ return [hostname, realm];
+ }
+
+ hostname = this._getFormattedHostname(aChannel.URI);
+
+ // If a HTTP WWW-Authenticate header specified a realm, that value
+ // will be available here. If it wasn't set or wasn't HTTP, we'll use
+ // the formatted hostname instead.
+ realm = aAuthInfo.realm;
+ if (!realm)
+ realm = hostname;
+
+ return [hostname, realm];
+ },
+
+
+ /**
+ * Returns [username, password] as extracted from aAuthInfo (which
+ * holds this info after having prompted the user).
+ *
+ * If the authentication was for a Windows domain, we'll prepend the
+ * return username with the domain. (eg, "domain\user")
+ */
+ _GetAuthInfo : function (aAuthInfo) {
+ var username, password;
+
+ var flags = aAuthInfo.flags;
+ if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain)
+ username = aAuthInfo.domain + "\\" + aAuthInfo.username;
+ else
+ username = aAuthInfo.username;
+
+ password = aAuthInfo.password;
+
+ return [username, password];
+ },
+
+
+ /**
+ * Given a username (possibly in DOMAIN\user form) and password, parses the
+ * domain out of the username if necessary and sets domain, username and
+ * password on the auth information object.
+ */
+ _SetAuthInfo : function (aAuthInfo, username, password) {
+ var flags = aAuthInfo.flags;
+ if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
+ // Domain is separated from username by a backslash
+ var idx = username.indexOf("\\");
+ if (idx == -1) {
+ aAuthInfo.username = username;
+ } else {
+ aAuthInfo.domain = username.substring(0, idx);
+ aAuthInfo.username = username.substring(idx + 1);
+ }
+ } else {
+ aAuthInfo.username = username;
+ }
+ aAuthInfo.password = password;
+ },
+
+ _newAsyncPromptConsumer : function(aCallback, aContext) {
+ return {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+ callback: aCallback,
+ context: aContext,
+ cancel: function() {
+ this.callback.onAuthCancelled(this.context, false);
+ this.callback = null;
+ this.context = null;
+ }
+ };
+ },
+
+ /**
+ * This function looks for existing logins that can be updated
+ * to match a submitted login, instead of creating a new one.
+ *
+ * Given a login and a loginList, it filters the login list
+ * to find every login with either the same username as aLogin
+ * or with the same password as aLogin and an empty username
+ * so the user can add a username.
+ *
+ * @param {nsILoginInfo} aLogin
+ * login to use as filter.
+ * @param {nsILoginInfo[]} aLoginList
+ * Array of logins to filter.
+ * @returns {nsILoginInfo[]} the filtered array of logins.
+ */
+ _filterUpdatableLogins(aLogin, aLoginList) {
+ return aLoginList.filter(l => l.username == aLogin.username ||
+ (l.password == aLogin.password &&
+ !l.username));
+ },
+
+}; // end of LoginManagerPrompter implementation
+
+XPCOMUtils.defineLazyGetter(this.LoginManagerPrompter.prototype, "log", () => {
+ let logger = LoginHelper.createLogger("LoginManagerPrompter");
+ return logger.log.bind(logger);
+});
+
+var component = [LoginManagerPromptFactory, LoginManagerPrompter];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
diff --git a/components/passwordmgr/src/storage-json.js b/components/passwordmgr/src/storage-json.js
new file mode 100644
index 000000000..20834d45b
--- /dev/null
+++ b/components/passwordmgr/src/storage-json.js
@@ -0,0 +1,514 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsILoginManagerStorage implementation for the JSON back-end.
+ */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginImport",
+ "resource://gre/modules/LoginImport.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginStore",
+ "resource://gre/modules/LoginStore.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+this.LoginManagerStorage_json = function () {};
+
+this.LoginManagerStorage_json.prototype = {
+ classID: Components.ID("{c00c432d-a0c9-46d7-bef6-9c45b4d07341}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginManagerStorage]),
+
+ __crypto: null, // nsILoginManagerCrypto service
+ get _crypto() {
+ if (!this.__crypto)
+ this.__crypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"].
+ getService(Ci.nsILoginManagerCrypto);
+ return this.__crypto;
+ },
+
+ initialize() {
+ try {
+ // Force initialization of the crypto module.
+ // See bug 717490 comment 17.
+ this._crypto;
+
+ // Set the reference to LoginStore synchronously.
+ let jsonPath = OS.Path.join(OS.Constants.Path.profileDir,
+ "logins.json");
+ this._store = new LoginStore(jsonPath);
+
+ return Task.spawn(function* () {
+ // Load the data asynchronously.
+ this.log("Opening database at", this._store.path);
+ yield this._store.load();
+
+ // The import from previous versions operates the first time
+ // that this built-in storage back-end is used. This may be
+ // later than expected, in case add-ons have registered an
+ // alternate storage that disabled the default one.
+ try {
+ if (Services.prefs.getBoolPref("signon.importedFromSqlite")) {
+ return;
+ }
+ } catch (ex) {
+ // If the preference does not exist, we need to import.
+ }
+
+ // Import only happens asynchronously.
+ let sqlitePath = OS.Path.join(OS.Constants.Path.profileDir,
+ "signons.sqlite");
+ if (yield OS.File.exists(sqlitePath)) {
+ let loginImport = new LoginImport(this._store, sqlitePath);
+ // Failures during import, for example due to a corrupt
+ // file or a schema version that is too old, will not
+ // prevent us from marking the operation as completed.
+ // At the next startup, we will not try the import again.
+ yield loginImport.import().catch(Cu.reportError);
+ this._store.saveSoon();
+ }
+
+ // We won't attempt import again on next startup.
+ Services.prefs.setBoolPref("signon.importedFromSqlite", true);
+ }.bind(this)).catch(Cu.reportError);
+ } catch (e) {
+ this.log("Initialization failed:", e);
+ throw new Error("Initialization failed");
+ }
+ },
+
+ /**
+ * Internal method used by regression tests only. It is called before
+ * replacing this storage module with a new instance.
+ */
+ terminate() {
+ this._store._saver.disarm();
+ return this._store._save();
+ },
+
+ addLogin(login) {
+ this._store.ensureDataReady();
+
+ // Throws if there are bogus values.
+ LoginHelper.checkLoginValues(login);
+
+ let [encUsername, encPassword, encType] = this._encryptLogin(login);
+
+ // Clone the login, so we don't modify the caller's object.
+ let loginClone = login.clone();
+
+ // Initialize the nsILoginMetaInfo fields, unless the caller gave us values
+ loginClone.QueryInterface(Ci.nsILoginMetaInfo);
+ if (loginClone.guid) {
+ if (!this._isGuidUnique(loginClone.guid))
+ throw new Error("specified GUID already exists");
+ } else {
+ loginClone.guid = gUUIDGenerator.generateUUID().toString();
+ }
+
+ // Set timestamps
+ let currentTime = Date.now();
+ if (!loginClone.timeCreated)
+ loginClone.timeCreated = currentTime;
+ if (!loginClone.timeLastUsed)
+ loginClone.timeLastUsed = currentTime;
+ if (!loginClone.timePasswordChanged)
+ loginClone.timePasswordChanged = currentTime;
+ if (!loginClone.timesUsed)
+ loginClone.timesUsed = 1;
+
+ this._store.data.logins.push({
+ id: this._store.data.nextId++,
+ hostname: loginClone.hostname,
+ httpRealm: loginClone.httpRealm,
+ formSubmitURL: loginClone.formSubmitURL,
+ usernameField: loginClone.usernameField,
+ passwordField: loginClone.passwordField,
+ encryptedUsername: encUsername,
+ encryptedPassword: encPassword,
+ guid: loginClone.guid,
+ encType: encType,
+ timeCreated: loginClone.timeCreated,
+ timeLastUsed: loginClone.timeLastUsed,
+ timePasswordChanged: loginClone.timePasswordChanged,
+ timesUsed: loginClone.timesUsed
+ });
+ this._store.saveSoon();
+
+ // Send a notification that a login was added.
+ LoginHelper.notifyStorageChanged("addLogin", loginClone);
+ return loginClone;
+ },
+
+ removeLogin(login) {
+ this._store.ensureDataReady();
+
+ let [idToDelete, storedLogin] = this._getIdForLogin(login);
+ if (!idToDelete)
+ throw new Error("No matching logins");
+
+ let foundIndex = this._store.data.logins.findIndex(l => l.id == idToDelete);
+ if (foundIndex != -1) {
+ this._store.data.logins.splice(foundIndex, 1);
+ this._store.saveSoon();
+ }
+
+ LoginHelper.notifyStorageChanged("removeLogin", storedLogin);
+ },
+
+ modifyLogin(oldLogin, newLoginData) {
+ this._store.ensureDataReady();
+
+ let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
+ if (!idToModify)
+ throw new Error("No matching logins");
+
+ let newLogin = LoginHelper.buildModifiedLogin(oldStoredLogin, newLoginData);
+
+ // Check if the new GUID is duplicate.
+ if (newLogin.guid != oldStoredLogin.guid &&
+ !this._isGuidUnique(newLogin.guid)) {
+ throw new Error("specified GUID already exists");
+ }
+
+ // Look for an existing entry in case key properties changed.
+ if (!newLogin.matches(oldLogin, true)) {
+ let logins = this.findLogins({}, newLogin.hostname,
+ newLogin.formSubmitURL,
+ newLogin.httpRealm);
+
+ if (logins.some(login => newLogin.matches(login, true)))
+ throw new Error("This login already exists.");
+ }
+
+ // Get the encrypted value of the username and password.
+ let [encUsername, encPassword, encType] = this._encryptLogin(newLogin);
+
+ for (let loginItem of this._store.data.logins) {
+ if (loginItem.id == idToModify) {
+ loginItem.hostname = newLogin.hostname;
+ loginItem.httpRealm = newLogin.httpRealm;
+ loginItem.formSubmitURL = newLogin.formSubmitURL;
+ loginItem.usernameField = newLogin.usernameField;
+ loginItem.passwordField = newLogin.passwordField;
+ loginItem.encryptedUsername = encUsername;
+ loginItem.encryptedPassword = encPassword;
+ loginItem.guid = newLogin.guid;
+ loginItem.encType = encType;
+ loginItem.timeCreated = newLogin.timeCreated;
+ loginItem.timeLastUsed = newLogin.timeLastUsed;
+ loginItem.timePasswordChanged = newLogin.timePasswordChanged;
+ loginItem.timesUsed = newLogin.timesUsed;
+ this._store.saveSoon();
+ break;
+ }
+ }
+
+ LoginHelper.notifyStorageChanged("modifyLogin", [oldStoredLogin, newLogin]);
+ },
+
+ /**
+ * @return {nsILoginInfo[]}
+ */
+ getAllLogins(count) {
+ let [logins, ids] = this._searchLogins({});
+
+ // decrypt entries for caller.
+ logins = this._decryptLogins(logins);
+
+ this.log("_getAllLogins: returning", logins.length, "logins.");
+ if (count)
+ count.value = logins.length; // needed for XPCOM
+ return logins;
+ },
+
+ /**
+ * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
+ * JavaScript object and decrypt the results.
+ *
+ * @return {nsILoginInfo[]} which are decrypted.
+ */
+ searchLogins(count, matchData) {
+ let realMatchData = {};
+ let options = {};
+ // Convert nsIPropertyBag to normal JS object
+ let propEnum = matchData.enumerator;
+ while (propEnum.hasMoreElements()) {
+ let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
+ switch (prop.name) {
+ // Some property names aren't field names but are special options to affect the search.
+ case "schemeUpgrades": {
+ options[prop.name] = prop.value;
+ break;
+ }
+ default: {
+ realMatchData[prop.name] = prop.value;
+ break;
+ }
+ }
+ }
+
+ let [logins, ids] = this._searchLogins(realMatchData, options);
+
+ // Decrypt entries found for the caller.
+ logins = this._decryptLogins(logins);
+
+ count.value = logins.length; // needed for XPCOM
+ return logins;
+ },
+
+ /**
+ * Private method to perform arbitrary searches on any field. Decryption is
+ * left to the caller.
+ *
+ * Returns [logins, ids] for logins that match the arguments, where logins
+ * is an array of encrypted nsLoginInfo and ids is an array of associated
+ * ids in the database.
+ */
+ _searchLogins(matchData, aOptions = {
+ schemeUpgrades: false,
+ }) {
+ this._store.ensureDataReady();
+
+ function match(aLogin) {
+ for (let field in matchData) {
+ let wantedValue = matchData[field];
+ switch (field) {
+ case "formSubmitURL":
+ if (wantedValue != null) {
+ // Historical compatibility requires this special case
+ if (aLogin.formSubmitURL == "") {
+ break;
+ }
+ if (!LoginHelper.isOriginMatching(aLogin[field], wantedValue, aOptions)) {
+ return false;
+ }
+ break;
+ }
+ // fall through
+ case "hostname":
+ if (wantedValue != null) { // needed for formSubmitURL fall through
+ if (!LoginHelper.isOriginMatching(aLogin[field], wantedValue, aOptions)) {
+ return false;
+ }
+ break;
+ }
+ // fall through
+ // Normal cases.
+ case "httpRealm":
+ case "id":
+ case "usernameField":
+ case "passwordField":
+ case "encryptedUsername":
+ case "encryptedPassword":
+ case "guid":
+ case "encType":
+ case "timeCreated":
+ case "timeLastUsed":
+ case "timePasswordChanged":
+ case "timesUsed":
+ if (wantedValue == null && aLogin[field]) {
+ return false;
+ } else if (aLogin[field] != wantedValue) {
+ return false;
+ }
+ break;
+ // Fail if caller requests an unknown property.
+ default:
+ throw new Error("Unexpected field: " + field);
+ }
+ }
+ return true;
+ }
+
+ let foundLogins = [], foundIds = [];
+ for (let loginItem of this._store.data.logins) {
+ if (match(loginItem)) {
+ // Create the new nsLoginInfo object, push to array
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"].
+ createInstance(Ci.nsILoginInfo);
+ login.init(loginItem.hostname, loginItem.formSubmitURL,
+ loginItem.httpRealm, loginItem.encryptedUsername,
+ loginItem.encryptedPassword, loginItem.usernameField,
+ loginItem.passwordField);
+ // set nsILoginMetaInfo values
+ login.QueryInterface(Ci.nsILoginMetaInfo);
+ login.guid = loginItem.guid;
+ login.timeCreated = loginItem.timeCreated;
+ login.timeLastUsed = loginItem.timeLastUsed;
+ login.timePasswordChanged = loginItem.timePasswordChanged;
+ login.timesUsed = loginItem.timesUsed;
+ foundLogins.push(login);
+ foundIds.push(loginItem.id);
+ }
+ }
+
+ this.log("_searchLogins: returning", foundLogins.length, "logins for", matchData,
+ "with options", aOptions);
+ return [foundLogins, foundIds];
+ },
+
+ /**
+ * Removes all logins from storage.
+ */
+ removeAllLogins() {
+ this._store.ensureDataReady();
+
+ this.log("Removing all logins");
+ this._store.data.logins = [];
+ this._store.saveSoon();
+
+ LoginHelper.notifyStorageChanged("removeAllLogins", null);
+ },
+
+ findLogins(count, hostname, formSubmitURL, httpRealm) {
+ let loginData = {
+ hostname: hostname,
+ formSubmitURL: formSubmitURL,
+ httpRealm: httpRealm
+ };
+ let matchData = { };
+ for (let field of ["hostname", "formSubmitURL", "httpRealm"])
+ if (loginData[field] != '')
+ matchData[field] = loginData[field];
+ let [logins, ids] = this._searchLogins(matchData);
+
+ // Decrypt entries found for the caller.
+ logins = this._decryptLogins(logins);
+
+ this.log("_findLogins: returning", logins.length, "logins");
+ count.value = logins.length; // needed for XPCOM
+ return logins;
+ },
+
+ countLogins(hostname, formSubmitURL, httpRealm) {
+ let loginData = {
+ hostname: hostname,
+ formSubmitURL: formSubmitURL,
+ httpRealm: httpRealm
+ };
+ let matchData = { };
+ for (let field of ["hostname", "formSubmitURL", "httpRealm"])
+ if (loginData[field] != '')
+ matchData[field] = loginData[field];
+ let [logins, ids] = this._searchLogins(matchData);
+
+ this.log("_countLogins: counted logins:", logins.length);
+ return logins.length;
+ },
+
+ get uiBusy() {
+ return this._crypto.uiBusy;
+ },
+
+ get isLoggedIn() {
+ return this._crypto.isLoggedIn;
+ },
+
+ /**
+ * Returns an array with two items: [id, login]. If the login was not
+ * found, both items will be null. The returned login contains the actual
+ * stored login (useful for looking at the actual nsILoginMetaInfo values).
+ */
+ _getIdForLogin(login) {
+ let matchData = { };
+ for (let field of ["hostname", "formSubmitURL", "httpRealm"])
+ if (login[field] != '')
+ matchData[field] = login[field];
+ let [logins, ids] = this._searchLogins(matchData);
+
+ let id = null;
+ let foundLogin = null;
+
+ // The specified login isn't encrypted, so we need to ensure
+ // the logins we're comparing with are decrypted. We decrypt one entry
+ // at a time, lest _decryptLogins return fewer entries and screw up
+ // indices between the two.
+ for (let i = 0; i < logins.length; i++) {
+ let [decryptedLogin] = this._decryptLogins([logins[i]]);
+
+ if (!decryptedLogin || !decryptedLogin.equals(login))
+ continue;
+
+ // We've found a match, set id and break
+ foundLogin = decryptedLogin;
+ id = ids[i];
+ break;
+ }
+
+ return [id, foundLogin];
+ },
+
+ /**
+ * Checks to see if the specified GUID already exists.
+ */
+ _isGuidUnique(guid) {
+ this._store.ensureDataReady();
+
+ return this._store.data.logins.every(l => l.guid != guid);
+ },
+
+ /**
+ * Returns the encrypted username, password, and encrypton type for the specified
+ * login. Can throw if the user cancels a master password entry.
+ */
+ _encryptLogin(login) {
+ let encUsername = this._crypto.encrypt(login.username);
+ let encPassword = this._crypto.encrypt(login.password);
+ let encType = this._crypto.defaultEncType;
+
+ return [encUsername, encPassword, encType];
+ },
+
+ /**
+ * Decrypts username and password fields in the provided array of
+ * logins.
+ *
+ * The entries specified by the array will be decrypted, if possible.
+ * An array of successfully decrypted logins will be returned. The return
+ * value should be given to external callers (since still-encrypted
+ * entries are useless), whereas internal callers generally don't want
+ * to lose unencrypted entries (eg, because the user clicked Cancel
+ * instead of entering their master password)
+ */
+ _decryptLogins(logins) {
+ let result = [];
+
+ for (let login of logins) {
+ try {
+ login.username = this._crypto.decrypt(login.username);
+ login.password = this._crypto.decrypt(login.password);
+ } catch (e) {
+ // If decryption failed (corrupt entry?), just skip it.
+ // Rethrow other errors (like canceling entry of a master pw)
+ if (e.result == Cr.NS_ERROR_FAILURE)
+ continue;
+ throw e;
+ }
+ result.push(login);
+ }
+
+ return result;
+ },
+};
+
+XPCOMUtils.defineLazyGetter(this.LoginManagerStorage_json.prototype, "log", () => {
+ let logger = LoginHelper.createLogger("Login storage");
+ return logger.log.bind(logger);
+});
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LoginManagerStorage_json]);
diff --git a/components/perf/.eslintrc.js b/components/perf/.eslintrc.js
new file mode 100644
index 000000000..4e6d4bcf0
--- /dev/null
+++ b/components/perf/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../testing/mochitest/chrome.eslintrc.js"
+ ]
+};
diff --git a/components/perf/PerfMeasurement.cpp b/components/perf/PerfMeasurement.cpp
new file mode 100644
index 000000000..1b211b79c
--- /dev/null
+++ b/components/perf/PerfMeasurement.cpp
@@ -0,0 +1,120 @@
+/* -*- 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 "PerfMeasurement.h"
+#include "jsperf.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsMemory.h"
+#include "mozilla/Preferences.h"
+#include "mozJSComponentLoader.h"
+#include "nsZipArchive.h"
+#include "xpc_make_class.h"
+
+#define JSPERF_CONTRACTID \
+ "@mozilla.org/jsperf;1"
+
+#define JSPERF_CID \
+{ 0x421c38e6, 0xaee0, 0x4509, \
+ { 0xa0, 0x25, 0x13, 0x0f, 0x43, 0x78, 0x03, 0x5a } }
+
+namespace mozilla {
+namespace jsperf {
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(Module)
+
+NS_IMPL_ISUPPORTS(Module, nsIXPCScriptable)
+
+Module::Module()
+{
+}
+
+Module::~Module()
+{
+}
+
+#define XPC_MAP_CLASSNAME Module
+#define XPC_MAP_QUOTED_CLASSNAME "Module"
+#define XPC_MAP_WANT_CALL
+#define XPC_MAP_FLAGS nsIXPCScriptable::WANT_CALL
+#include "xpc_map_end.h"
+
+static bool
+SealObjectAndPrototype(JSContext* cx, JS::Handle<JSObject *> parent, const char* name)
+{
+ JS::Rooted<JS::Value> prop(cx);
+ if (!JS_GetProperty(cx, parent, name, &prop))
+ return false;
+
+ if (prop.isUndefined()) {
+ // Pretend we sealed the object.
+ return true;
+ }
+
+ JS::Rooted<JSObject*> obj(cx, prop.toObjectOrNull());
+ if (!JS_GetProperty(cx, obj, "prototype", &prop))
+ return false;
+
+ JS::Rooted<JSObject*> prototype(cx, prop.toObjectOrNull());
+ return JS_FreezeObject(cx, obj) && JS_FreezeObject(cx, prototype);
+}
+
+static bool
+InitAndSealPerfMeasurementClass(JSContext* cx, JS::Handle<JSObject*> global)
+{
+ // Init the PerfMeasurement class
+ if (!JS::RegisterPerfMeasurement(cx, global))
+ return false;
+
+ // Seal up Object, Function, and Array and their prototypes. (This single
+ // object instance is shared amongst everyone who imports the jsperf module.)
+ if (!SealObjectAndPrototype(cx, global, "Object") ||
+ !SealObjectAndPrototype(cx, global, "Function") ||
+ !SealObjectAndPrototype(cx, global, "Array"))
+ return false;
+
+ // Finally, seal the global object, for good measure. (But not recursively;
+ // this breaks things.)
+ return JS_FreezeObject(cx, global);
+}
+
+NS_IMETHODIMP
+Module::Call(nsIXPConnectWrappedNative* wrapper,
+ JSContext* cx,
+ JSObject* obj,
+ const JS::CallArgs& args,
+ bool* _retval)
+{
+
+ mozJSComponentLoader* loader = mozJSComponentLoader::Get();
+ JS::Rooted<JSObject*> targetObj(cx);
+ nsresult rv = loader->FindTargetObject(cx, &targetObj);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = InitAndSealPerfMeasurementClass(cx, targetObj);
+ return NS_OK;
+}
+
+} // namespace jsperf
+} // namespace mozilla
+
+NS_DEFINE_NAMED_CID(JSPERF_CID);
+
+static const mozilla::Module::CIDEntry kPerfCIDs[] = {
+ { &kJSPERF_CID, false, nullptr, mozilla::jsperf::ModuleConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kPerfContracts[] = {
+ { JSPERF_CONTRACTID, &kJSPERF_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kPerfModule = {
+ mozilla::Module::kVersion,
+ kPerfCIDs,
+ kPerfContracts
+};
+
+NSMODULE_DEFN(jsperf) = &kPerfModule;
diff --git a/components/perf/PerfMeasurement.h b/components/perf/PerfMeasurement.h
new file mode 100644
index 000000000..b158d1685
--- /dev/null
+++ b/components/perf/PerfMeasurement.h
@@ -0,0 +1,30 @@
+/* -*- 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 COMPONENTS_PERFMEASUREMENT_H
+#define COMPONENTS_PERFMEASUREMENT_H
+
+#include "nsIXPCScriptable.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace jsperf {
+
+class Module final : public nsIXPCScriptable
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIXPCSCRIPTABLE
+
+ Module();
+
+private:
+ ~Module();
+};
+
+} // namespace jsperf
+} // namespace mozilla
+
+#endif
diff --git a/components/perf/PerfMeasurement.jsm b/components/perf/PerfMeasurement.jsm
new file mode 100644
index 000000000..29a221c6f
--- /dev/null
+++ b/components/perf/PerfMeasurement.jsm
@@ -0,0 +1,19 @@
+/* -*- 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/. */
+
+this.EXPORTED_SYMBOLS = [ "PerfMeasurement" ];
+
+/*
+ * This is the js module for jsperf. Import it like so:
+ * Components.utils.import("resource://gre/modules/PerfMeasurement.jsm");
+ *
+ * This will create a 'PerfMeasurement' class. Instances of this class can
+ * be used to benchmark browser operations.
+ *
+ * For documentation on the API, see js/src/perf/jsperf.h.
+ *
+ */
+
+Components.classes["@mozilla.org/jsperf;1"].createInstance()();
diff --git a/components/perf/moz.build b/components/perf/moz.build
new file mode 100644
index 000000000..099a1c4eb
--- /dev/null
+++ b/components/perf/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+SOURCES += [
+ 'PerfMeasurement.cpp',
+]
+
+EXTRA_JS_MODULES += [
+ 'PerfMeasurement.jsm',
+]
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/js/xpconnect/loader',
+]
diff --git a/components/perfmonitoring/AddonWatcher.jsm b/components/perfmonitoring/AddonWatcher.jsm
new file mode 100644
index 000000000..e5b95d1d8
--- /dev/null
+++ b/components/perfmonitoring/AddonWatcher.jsm
@@ -0,0 +1,227 @@
+// -*- 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["AddonWatcher"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+ "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PerformanceWatcher",
+ "resource://gre/modules/PerformanceWatcher.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "IdleService",
+ "@mozilla.org/widget/idleservice;1",
+ Ci.nsIIdleService);
+
+/**
+ * Don't notify observers of slow add-ons if at least `SUSPICIOUSLY_MANY_ADDONS`
+ * show up at the same time. We assume that this indicates that the system itself
+ * is busy, and that add-ons are not responsible.
+ */
+let SUSPICIOUSLY_MANY_ADDONS = 5;
+
+this.AddonWatcher = {
+ /**
+ * Watch this topic to be informed when a slow add-on is detected and should
+ * be reported to the user.
+ *
+ * If you need finer-grained control, use PerformanceWatcher.jsm.
+ */
+ TOPIC_SLOW_ADDON_DETECTED: "addon-watcher-detected-slow-addon",
+
+ init: function() {
+ this._initializedTimeStamp = Cu.now();
+
+ try {
+ this._ignoreList = new Set(JSON.parse(Preferences.get("browser.addon-watch.ignore", null)));
+ } catch (ex) {
+ // probably some malformed JSON, ignore and carry on
+ this._ignoreList = new Set();
+ }
+
+ this._warmupPeriod = Preferences.get("browser.addon-watch.warmup-ms", 60 * 1000 /* 1 minute */);
+ this._idleThreshold = Preferences.get("browser.addon-watch.deactivate-after-idle-ms", 3000);
+ this.paused = false;
+ },
+ uninit: function() {
+ this.paused = true;
+ },
+ _initializedTimeStamp: 0,
+
+ set paused(paused) {
+ if (paused) {
+ if (this._listener) {
+ PerformanceWatcher.removePerformanceListener({addonId: "*"}, this._listener);
+ }
+ this._listener = null;
+ } else {
+ this._listener = this._onSlowAddons.bind(this);
+ PerformanceWatcher.addPerformanceListener({addonId: "*"}, this._listener);
+ }
+ },
+ get paused() {
+ return !this._listener;
+ },
+ _listener: null,
+
+ /**
+ * Provide the following object for each addon:
+ * {number} occurrences The total number of performance alerts recorded for this addon.
+ * {number} occurrencesSinceLastNotification The number of performances alerts recorded
+ * since we last notified the user.
+ * {number} latestNotificationTimeStamp The timestamp of the latest user notification
+ * that this add-on is slow.
+ */
+ _getAlerts: function(addonId) {
+ let alerts = this._alerts.get(addonId);
+ if (!alerts) {
+ alerts = {
+ occurrences: 0,
+ occurrencesSinceLastNotification: 0,
+ latestNotificationTimeStamp: 0,
+ };
+ this._alerts.set(addonId, alerts);
+ }
+ return alerts;
+ },
+ _alerts: new Map(),
+ _onSlowAddons: function(addons) {
+ try {
+ if (IdleService.idleTime >= this._idleThreshold) {
+ // The application is idle. Maybe the computer is sleeping, or maybe
+ // the user isn't in front of it. Regardless, the user doesn't care
+ // about things that slow down her browser while she's not using it.
+ return;
+ }
+
+ if (addons.length > SUSPICIOUSLY_MANY_ADDONS) {
+ // Heuristic: if we are notified of many slow addons at once, the issue
+ // is probably not with the add-ons themselves with the system. We may
+ // for instance be waking up from hibernation, or the system may be
+ // busy swapping.
+ return;
+ }
+
+ let now = Cu.now();
+ if (now - this._initializedTimeStamp < this._warmupPeriod) {
+ // Heuristic: do not report slowdowns during or just after startup.
+ return;
+ }
+
+ // We expect that users don't care about real-time alerts unless their
+ // browser is going very, very slowly. Therefore, we use the following
+ // heuristic:
+ // - if jank is above freezeThreshold (e.g. 5 seconds), report immediately; otherwise
+ // - if jank is below jankThreshold (e.g. 128ms), disregard; otherwise
+ // - if the latest jank was more than prescriptionDelay (e.g. 5 minutes) ago, reset number of occurrences;
+ // - if we have had fewer than occurrencesBetweenAlerts janks (e.g. 3) since last alert, disregard; otherwise
+ // - if we have displayed an alert for this add-on less than delayBetweenAlerts ago (e.g. 6h), disregard; otherwise
+ // - also, don't report more than highestNumberOfAddonsToReport (e.g. 1) at once.
+ let freezeThreshold = Preferences.get("browser.addon-watch.freeze-threshold-micros", /* 5 seconds */ 5000000);
+ let jankThreshold = Preferences.get("browser.addon-watch.jank-threshold-micros", /* 256 ms == 8 frames*/ 256000);
+ let occurrencesBetweenAlerts = Preferences.get("browser.addon-watch.occurrences-between-alerts", 3);
+ let delayBetweenAlerts = Preferences.get("browser.addon-watch.delay-between-alerts-ms", 6 * 3600 * 1000 /* 6h */);
+ let delayBetweenFreezeAlerts = Preferences.get("browser.addon-watch.delay-between-freeze-alerts-ms", 2 * 60 * 1000 /* 2 min */);
+ let prescriptionDelay = Preferences.get("browser.addon-watch.prescription-delay", 5 * 60 * 1000 /* 5 minutes */);
+ let highestNumberOfAddonsToReport = Preferences.get("browser.addon-watch.max-simultaneous-reports", 1);
+
+ addons = addons.filter(x => x.details.highestJank >= jankThreshold).
+ sort((a, b) => a.details.highestJank - b.details.highestJank);
+
+ for (let {source: {addonId}, details} of addons) {
+ if (highestNumberOfAddonsToReport <= 0) {
+ return;
+ }
+ if (this._ignoreList.has(addonId)) {
+ // Add-on is ignored.
+ continue;
+ }
+
+ let alerts = this._getAlerts(addonId);
+ if (now - alerts.latestOccurrence >= prescriptionDelay) {
+ // While this add-on has already caused slownesss, this
+ // was a long time ago, let's forgive.
+ alerts.occurrencesSinceLastNotification = 0;
+ }
+
+ alerts.occurrencesSinceLastNotification++;
+ alerts.occurrences++;
+
+ if (details.highestJank < freezeThreshold) {
+ if (alerts.occurrencesSinceLastNotification <= occurrencesBetweenAlerts) {
+ // While the add-on has caused jank at least once, we are only
+ // interested in repeat offenders. Store the data for future use.
+ continue;
+ }
+ if (now - alerts.latestNotificationTimeStamp <= delayBetweenAlerts) {
+ // We have already displayed an alert for this add-on recently.
+ // Wait a little before displaying another one.
+ continue;
+ }
+ } else if (now - alerts.latestNotificationTimeStamp <= delayBetweenFreezeAlerts) {
+ // Even in case of freeze, we want to avoid needlessly spamming the user.
+ // We have already displayed an alert for this add-on recently.
+ // Wait a little before displaying another one.
+ continue;
+ }
+
+ // Ok, time to inform the user.
+ alerts.latestNotificationTimeStamp = now;
+ alerts.occurrencesSinceLastNotification = 0;
+ Services.obs.notifyObservers(null, this.TOPIC_SLOW_ADDON_DETECTED, addonId);
+
+ highestNumberOfAddonsToReport--;
+ }
+ } catch (ex) {
+ Cu.reportError("Error in AddonWatcher._onSlowAddons " + ex);
+ Cu.reportError(Task.Debugging.generateReadableStack(ex.stack));
+ }
+ },
+
+ ignoreAddonForSession: function(addonid) {
+ this._ignoreList.add(addonid);
+ },
+ ignoreAddonPermanently: function(addonid) {
+ this._ignoreList.add(addonid);
+ try {
+ let ignoreList = JSON.parse(Preferences.get("browser.addon-watch.ignore", "[]"))
+ if (!ignoreList.includes(addonid)) {
+ ignoreList.push(addonid);
+ Preferences.set("browser.addon-watch.ignore", JSON.stringify(ignoreList));
+ }
+ } catch (ex) {
+ Preferences.set("browser.addon-watch.ignore", JSON.stringify([addonid]));
+ }
+ },
+
+ /**
+ * The list of alerts for this session.
+ *
+ * @type {Map<String, Object>} A map associating addonId to
+ * objects with fields
+ * {number} occurrences The total number of performance alerts recorded for this addon.
+ * {number} occurrencesSinceLastNotification The number of performances alerts recorded
+ * since we last notified the user.
+ * {number} latestNotificationTimeStamp The timestamp of the latest user notification
+ * that this add-on is slow.
+ */
+ get alerts() {
+ let result = new Map();
+ for (let [k, v] of this._alerts) {
+ result.set(k, Cu.cloneInto(v, this));
+ }
+ return result;
+ },
+};
diff --git a/components/perfmonitoring/PerformanceStats-content.js b/components/perfmonitoring/PerformanceStats-content.js
new file mode 100644
index 000000000..9a6a2d81d
--- /dev/null
+++ b/components/perfmonitoring/PerformanceStats-content.js
@@ -0,0 +1,144 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A proxy implementing communication between the PerformanceStats.jsm modules
+ * of the parent and children processes.
+ *
+ * This script is loaded in all processes but is essentially a NOOP in the
+ * parent process.
+ */
+
+"use strict";
+
+var { utils: Cu, classes: Cc, interfaces: Ci } = Components;
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+XPCOMUtils.defineLazyModuleGetter(this, "PerformanceStats",
+ "resource://gre/modules/PerformanceStats.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+/**
+ * A global performance monitor used by this process.
+ *
+ * For the sake of simplicity, rather than attempting to map each PerformanceMonitor
+ * of the parent to a PerformanceMonitor in each child process, we maintain a single
+ * PerformanceMonitor in each child process. Probes activation/deactivation for this
+ * monitor is controlled by the activation/deactivation of probes in the parent.
+ *
+ * In the parent, this is always an empty monitor.
+ */
+var gMonitor = PerformanceStats.getMonitor([]);
+
+/**
+ * `true` if this is a content process, `false` otherwise.
+ */
+var isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+
+/**
+ * Handle message `performance-stats-service-acquire`: ensure that the global
+ * monitor has a given probe. This message must be sent by the parent process
+ * whenever a probe is activated application-wide.
+ *
+ * Note that we may miss acquire messages if they are sent before this process is
+ * launched. For this reason, `performance-stats-service-collect` automatically
+ * re-acquires probes if it realizes that they are missing.
+ *
+ * This operation is a NOOP on the parent process.
+ *
+ * @param {{payload: Array<string>}} msg.data The message received. `payload`
+ * must be an array of probe names.
+ */
+Services.cpmm.addMessageListener("performance-stats-service-acquire", function(msg) {
+ if (!isContent) {
+ return;
+ }
+ let name = msg.data.payload;
+ ensureAcquired(name);
+});
+
+/**
+ * Handle message `performance-stats-service-release`: release a given probe
+ * from the global monitor. This message must be sent by the parent process
+ * whenever a probe is deactivated application-wide.
+ *
+ * Note that we may miss release messages if they are sent before this process is
+ * launched. This is ok, as probes are inactive by default: if we miss the release
+ * message, we have already missed the acquire message, and the effect of both
+ * messages together is to reset to the default state.
+ *
+ * This operation is a NOOP on the parent process.
+ *
+ * @param {{payload: Array<string>}} msg.data The message received. `payload`
+ * must be an array of probe names.
+ */
+Services.cpmm.addMessageListener("performance-stats-service-release", function(msg) {
+ if (!isContent) {
+ return;
+ }
+
+ // Keep only the probes that do not appear in the payload
+ let probes = gMonitor.probeNames
+ .filter(x => msg.data.payload.indexOf(x) == -1);
+ gMonitor = PerformanceStats.getMonitor(probes);
+});
+
+/**
+ * Ensure that this process has all the probes it needs.
+ *
+ * @param {Array<string>} probeNames The name of all probes needed by the
+ * process.
+ */
+function ensureAcquired(probeNames) {
+ let alreadyAcquired = gMonitor.probeNames;
+
+ // Algorithm is O(n^2) because we expect that n ≤ 3.
+ let shouldAcquire = [];
+ for (let probeName of probeNames) {
+ if (alreadyAcquired.indexOf(probeName) == -1) {
+ shouldAcquire.push(probeName)
+ }
+ }
+
+ if (shouldAcquire.length == 0) {
+ return;
+ }
+ gMonitor = PerformanceStats.getMonitor([...alreadyAcquired, ...shouldAcquire]);
+}
+
+/**
+ * Handle message `performance-stats-service-collected`: collect the data
+ * obtained by the monitor. This message must be sent by the parent process
+ * whenever we grab a performance snapshot of the application.
+ *
+ * This operation provides `null` on the parent process.
+ *
+ * @param {{data: {payload: Array<string>}}} msg The message received. `payload`
+ * must be an array of probe names.
+ */
+Services.cpmm.addMessageListener("performance-stats-service-collect", Task.async(function*(msg) {
+ let {id, payload: {probeNames}} = msg.data;
+ if (!isContent) {
+ // This message was sent by the parent process to itself.
+ // As per protocol, respond `null`.
+ Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
+ id,
+ data: null
+ });
+ return;
+ }
+
+ // We may have missed acquire messages if the process was loaded too late.
+ // Catch up now.
+ ensureAcquired(probeNames);
+
+ // Collect and return data.
+ let data = yield gMonitor.promiseSnapshot({probeNames});
+ Services.cpmm.sendAsyncMessage("performance-stats-service-collect", {
+ id,
+ data
+ });
+}));
diff --git a/components/perfmonitoring/PerformanceStats.jsm b/components/perfmonitoring/PerformanceStats.jsm
new file mode 100644
index 000000000..20f27a51b
--- /dev/null
+++ b/components/perfmonitoring/PerformanceStats.jsm
@@ -0,0 +1,1000 @@
+// -*- 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["PerformanceStats"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+/**
+ * API for querying and examining performance data.
+ *
+ * This API exposes data from several probes implemented by the JavaScript VM.
+ * See `PerformanceStats.getMonitor()` for information on how to monitor data
+ * from one or more probes and `PerformanceData` for the information obtained
+ * from the probes.
+ *
+ * Data is collected by "Performance Group". Typically, a Performance Group
+ * is an add-on, or a frame, or the internals of the application.
+ *
+ * Generally, if you have the choice between PerformanceStats and PerformanceWatcher,
+ * you should favor PerformanceWatcher.
+ */
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+Cu.import("resource://gre/modules/ObjectUtils.jsm", this);
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+ "resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
+ "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
+ "resource://gre/modules/Timer.jsm");
+
+// The nsIPerformanceStatsService provides lower-level
+// access to SpiderMonkey and the probes.
+XPCOMUtils.defineLazyServiceGetter(this, "performanceStatsService",
+ "@mozilla.org/toolkit/performance-stats-service;1",
+ Ci.nsIPerformanceStatsService);
+
+// The finalizer lets us automatically release (and when possible deactivate)
+// probes when a monitor is garbage-collected.
+XPCOMUtils.defineLazyServiceGetter(this, "finalizer",
+ "@mozilla.org/toolkit/finalizationwitness;1",
+ Ci.nsIFinalizationWitnessService
+);
+
+// The topic used to notify that a PerformanceMonitor has been garbage-collected
+// and that we can release/close the probes it holds.
+const FINALIZATION_TOPIC = "performancemonitor-finalize";
+
+const PROPERTIES_META_IMMUTABLE = ["addonId", "isSystem", "isChildProcess", "groupId", "processId"];
+const PROPERTIES_META = [...PROPERTIES_META_IMMUTABLE, "windowId", "title", "name"];
+
+// How long we wait for children processes to respond.
+const MAX_WAIT_FOR_CHILD_PROCESS_MS = 5000;
+
+var isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+/**
+ * Access to a low-level performance probe.
+ *
+ * Each probe is dedicated to some form of performance monitoring.
+ * As each probe may have a performance impact, a probe is activated
+ * only when a client has requested a PerformanceMonitor for this probe,
+ * and deactivated once all clients are disposed of.
+ */
+function Probe(name, impl) {
+ this._name = name;
+ this._counter = 0;
+ this._impl = impl;
+}
+Probe.prototype = {
+ /**
+ * Acquire the probe on behalf of a client.
+ *
+ * If the probe was inactive, activate it. Note that activating a probe
+ * can incur a memory or performance cost.
+ */
+ acquire: function() {
+ if (this._counter == 0) {
+ this._impl.isActive = true;
+ Process.broadcast("acquire", [this._name]);
+ }
+ this._counter++;
+ },
+
+ /**
+ * Release the probe on behalf of a client.
+ *
+ * If this was the last client for this probe, deactivate it.
+ */
+ release: function() {
+ this._counter--;
+ if (this._counter == 0) {
+ try {
+ this._impl.isActive = false;
+ } catch (ex) {
+ if (ex && typeof ex == "object" && ex.result == Components.results.NS_ERROR_NOT_AVAILABLE) {
+ // The service has already been shutdown. Ignore further shutdown requests.
+ return;
+ }
+ throw ex;
+ }
+ Process.broadcast("release", [this._name]);
+ }
+ },
+
+ /**
+ * Obtain data from this probe, once it is available.
+ *
+ * @param {nsIPerformanceStats} xpcom A xpcom object obtained from
+ * SpiderMonkey. Only the fields updated by the low-level probe
+ * are in a specified state.
+ * @return {object} An object containing the data extracted from this
+ * probe. Actual format depends on the probe.
+ */
+ extract: function(xpcom) {
+ if (!this._impl.isActive) {
+ throw new Error(`Probe is inactive: ${this._name}`);
+ }
+ return this._impl.extract(xpcom);
+ },
+
+ /**
+ * @param {object} a An object returned by `this.extract()`.
+ * @param {object} b An object returned by `this.extract()`.
+ *
+ * @return {true} If `a` and `b` hold identical values.
+ */
+ isEqual: function(a, b) {
+ if (a == null && b == null) {
+ return true;
+ }
+ if (a != null && b != null) {
+ return this._impl.isEqual(a, b);
+ }
+ return false;
+ },
+
+ /**
+ * @param {object} a An object returned by `this.extract()`. May
+ * NOT be `null`.
+ * @param {object} b An object returned by `this.extract()`. May
+ * be `null`.
+ *
+ * @return {object} An object representing `a - b`. If `b` is
+ * `null`, this is `a`.
+ */
+ subtract: function(a, b) {
+ if (a == null) {
+ throw new TypeError();
+ }
+ if (b == null) {
+ return a;
+ }
+ return this._impl.subtract(a, b);
+ },
+
+ importChildCompartments: function(parent, children) {
+ if (!Array.isArray(children)) {
+ throw new TypeError();
+ }
+ if (!parent || !(parent instanceof PerformanceDataLeaf)) {
+ throw new TypeError();
+ }
+ return this._impl.importChildCompartments(parent, children);
+ },
+
+ /**
+ * The name of the probe.
+ */
+ get name() {
+ return this._name;
+ },
+
+ compose: function(stats) {
+ if (!Array.isArray(stats)) {
+ throw new TypeError();
+ }
+ return this._impl.compose(stats);
+ }
+};
+
+// Utility function. Return the position of the last non-0 item in an
+// array, or -1 if there isn't any such item.
+function lastNonZero(array) {
+ for (let i = array.length - 1; i >= 0; --i) {
+ if (array[i] != 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/**
+ * The actual Probes implemented by SpiderMonkey.
+ */
+var Probes = {
+ /**
+ * A probe measuring jank.
+ *
+ * Data provided by this probe uses the following format:
+ *
+ * @field {number} totalCPUTime The total amount of time spent using the
+ * CPU for this performance group, in µs.
+ * @field {number} totalSystemTime The total amount of time spent in the
+ * kernel for this performance group, in µs.
+ * @field {Array<number>} durations An array containing at each position `i`
+ * the number of times execution of this component has lasted at least `2^i`
+ * milliseconds.
+ * @field {number} longestDuration The index of the highest non-0 value in
+ * `durations`.
+ */
+ jank: new Probe("jank", {
+ set isActive(x) {
+ performanceStatsService.isMonitoringJank = x;
+ },
+ get isActive() {
+ return performanceStatsService.isMonitoringJank;
+ },
+ extract: function(xpcom) {
+ let durations = xpcom.getDurations();
+ return {
+ totalUserTime: xpcom.totalUserTime,
+ totalSystemTime: xpcom.totalSystemTime,
+ totalCPUTime: xpcom.totalUserTime + xpcom.totalSystemTime,
+ durations: durations,
+ longestDuration: lastNonZero(durations)
+ }
+ },
+ isEqual: function(a, b) {
+ // invariant: `a` and `b` are both non-null
+ if (a.totalUserTime != b.totalUserTime) {
+ return false;
+ }
+ if (a.totalSystemTime != b.totalSystemTime) {
+ return false;
+ }
+ for (let i = 0; i < a.durations.length; ++i) {
+ if (a.durations[i] != b.durations[i]) {
+ return false;
+ }
+ }
+ return true;
+ },
+ subtract: function(a, b) {
+ // invariant: `a` and `b` are both non-null
+ let result = {
+ totalUserTime: a.totalUserTime - b.totalUserTime,
+ totalSystemTime: a.totalSystemTime - b.totalSystemTime,
+ totalCPUTime: a.totalCPUTime - b.totalCPUTime,
+ durations: [],
+ longestDuration: -1,
+ };
+ for (let i = 0; i < a.durations.length; ++i) {
+ result.durations[i] = a.durations[i] - b.durations[i];
+ }
+ result.longestDuration = lastNonZero(result.durations);
+ return result;
+ },
+ importChildCompartments: function() { /* nothing to do */ },
+ compose: function(stats) {
+ let result = {
+ totalUserTime: 0,
+ totalSystemTime: 0,
+ totalCPUTime: 0,
+ durations: [],
+ longestDuration: -1
+ };
+ for (let stat of stats) {
+ result.totalUserTime += stat.totalUserTime;
+ result.totalSystemTime += stat.totalSystemTime;
+ result.totalCPUTime += stat.totalCPUTime;
+ for (let i = 0; i < stat.durations.length; ++i) {
+ result.durations[i] += stat.durations[i];
+ }
+ result.longestDuration = Math.max(result.longestDuration, stat.longestDuration);
+ }
+ return result;
+ }
+ }),
+
+ /**
+ * A probe measuring CPOW activity.
+ *
+ * Data provided by this probe uses the following format:
+ *
+ * @field {number} totalCPOWTime The amount of wallclock time
+ * spent executing blocking cross-process calls, in µs.
+ */
+ cpow: new Probe("cpow", {
+ set isActive(x) {
+ performanceStatsService.isMonitoringCPOW = x;
+ },
+ get isActive() {
+ return performanceStatsService.isMonitoringCPOW;
+ },
+ extract: function(xpcom) {
+ return {
+ totalCPOWTime: xpcom.totalCPOWTime
+ };
+ },
+ isEqual: function(a, b) {
+ return a.totalCPOWTime == b.totalCPOWTime;
+ },
+ subtract: function(a, b) {
+ return {
+ totalCPOWTime: a.totalCPOWTime - b.totalCPOWTime
+ };
+ },
+ importChildCompartments: function() { /* nothing to do */ },
+ compose: function(stats) {
+ let totalCPOWTime = 0;
+ for (let stat of stats) {
+ totalCPOWTime += stat.totalCPOWTime;
+ }
+ return { totalCPOWTime };
+ },
+ }),
+
+ /**
+ * A probe measuring activations, i.e. the number
+ * of times code execution has entered a given
+ * PerformanceGroup.
+ *
+ * Note that this probe is always active.
+ *
+ * Data provided by this probe uses the following format:
+ * @type {number} ticks The number of times execution has entered
+ * this performance group.
+ */
+ ticks: new Probe("ticks", {
+ set isActive(x) { /* this probe cannot be deactivated */ },
+ get isActive() { return true; },
+ extract: function(xpcom) {
+ return {
+ ticks: xpcom.ticks
+ };
+ },
+ isEqual: function(a, b) {
+ return a.ticks == b.ticks;
+ },
+ subtract: function(a, b) {
+ return {
+ ticks: a.ticks - b.ticks
+ };
+ },
+ importChildCompartments: function() { /* nothing to do */ },
+ compose: function(stats) {
+ let ticks = 0;
+ for (let stat of stats) {
+ ticks += stat.ticks;
+ }
+ return { ticks };
+ },
+ }),
+
+ compartments: new Probe("compartments", {
+ set isActive(x) {
+ performanceStatsService.isMonitoringPerCompartment = x;
+ },
+ get isActive() {
+ return performanceStatsService.isMonitoringPerCompartment;
+ },
+ extract: function(xpcom) {
+ return null;
+ },
+ isEqual: function(a, b) {
+ return true;
+ },
+ subtract: function(a, b) {
+ return true;
+ },
+ importChildCompartments: function(parent, children) {
+ parent.children = children;
+ },
+ compose: function(stats) {
+ return null;
+ },
+ }),
+};
+
+/**
+ * A monitor for a set of probes.
+ *
+ * Keeping probes active when they are unused is often a bad
+ * idea for performance reasons. Upon destruction, or whenever
+ * a client calls `dispose`, this monitor releases the probes,
+ * which may let the system deactivate them.
+ */
+function PerformanceMonitor(probes) {
+ this._probes = probes;
+
+ // Activate low-level features as needed
+ for (let probe of probes) {
+ probe.acquire();
+ }
+
+ // A finalization witness. At some point after the garbage-collection of
+ // `this` object, a notification of `FINALIZATION_TOPIC` will be triggered
+ // with `id` as message.
+ this._id = PerformanceMonitor.makeId();
+ this._finalizer = finalizer.make(FINALIZATION_TOPIC, this._id)
+ PerformanceMonitor._monitors.set(this._id, probes);
+}
+PerformanceMonitor.prototype = {
+ /**
+ * The names of probes activated in this monitor.
+ */
+ get probeNames() {
+ return this._probes.map(probe => probe.name);
+ },
+
+ /**
+ * Return asynchronously a snapshot with the data
+ * for each probe monitored by this PerformanceMonitor.
+ *
+ * All numeric values are non-negative and can only increase. Depending on
+ * the probe and the underlying operating system, probes may not be available
+ * immediately and may miss some activity.
+ *
+ * Clients should NOT expect that the first call to `promiseSnapshot()`
+ * will return a `Snapshot` in which all values are 0. For most uses,
+ * the appropriate scenario is to perform a first call to `promiseSnapshot()`
+ * to obtain a baseline, and then watch evolution of the values by calling
+ * `promiseSnapshot()` and `subtract()`.
+ *
+ * On the other hand, numeric values are also monotonic across several instances
+ * of a PerformanceMonitor with the same probes.
+ * let a = PerformanceStats.getMonitor(someProbes);
+ * let snapshot1 = yield a.promiseSnapshot();
+ *
+ * // ...
+ * let b = PerformanceStats.getMonitor(someProbes); // Same list of probes
+ * let snapshot2 = yield b.promiseSnapshot();
+ *
+ * // all values of `snapshot2` are greater or equal to values of `snapshot1`.
+ *
+ * @param {object} options If provided, an object that may contain the following
+ * fields:
+ * {Array<string>} probeNames The subset of probes to use for this snapshot.
+ * These probes must be a subset of the probes active in the monitor.
+ *
+ * @return {Promise}
+ * @resolve {Snapshot}
+ */
+ _checkBeforeSnapshot: function(options) {
+ if (!this._finalizer) {
+ throw new Error("dispose() has already been called, this PerformanceMonitor is not usable anymore");
+ }
+ let probes;
+ if (options && options.probeNames || undefined) {
+ if (!Array.isArray(options.probeNames)) {
+ throw new TypeError();
+ }
+ // Make sure that we only request probes that we have
+ for (let probeName of options.probeNames) {
+ let probe = this._probes.find(probe => probe.name == probeName);
+ if (!probe) {
+ throw new TypeError(`I need probe ${probeName} but I only have ${this.probeNames}`);
+ }
+ if (!probes) {
+ probes = [];
+ }
+ probes.push(probe);
+ }
+ } else {
+ probes = this._probes;
+ }
+ return probes;
+ },
+ promiseContentSnapshot: function(options = null) {
+ this._checkBeforeSnapshot(options);
+ return (new ProcessSnapshot(performanceStatsService.getSnapshot()));
+ },
+ promiseSnapshot: function(options = null) {
+ let probes = this._checkBeforeSnapshot(options);
+ return Task.spawn(function*() {
+ let childProcesses = yield Process.broadcastAndCollect("collect", {probeNames: probes.map(p => p.name)});
+ let xpcom = performanceStatsService.getSnapshot();
+ return new ApplicationSnapshot({
+ xpcom,
+ childProcesses,
+ probes,
+ date: Cu.now()
+ });
+ });
+ },
+
+ /**
+ * Release the probes used by this monitor.
+ *
+ * Releasing probes as soon as they are unused is a good idea, as some probes
+ * cost CPU and/or memory.
+ */
+ dispose: function() {
+ if (!this._finalizer) {
+ return;
+ }
+ this._finalizer.forget();
+ PerformanceMonitor.dispose(this._id);
+
+ // As a safeguard against double-release, reset everything to `null`
+ this._probes = null;
+ this._id = null;
+ this._finalizer = null;
+ }
+};
+/**
+ * @type {Map<string, Array<string>>} A map from id (as produced by `makeId`)
+ * to list of probes. Used to deallocate a list of probes during finalization.
+ */
+PerformanceMonitor._monitors = new Map();
+
+/**
+ * Create a `PerformanceMonitor` for a list of probes, register it for
+ * finalization.
+ */
+PerformanceMonitor.make = function(probeNames) {
+ // Sanity checks
+ if (!Array.isArray(probeNames)) {
+ throw new TypeError("Expected an array, got " + probes);
+ }
+ let probes = [];
+ for (let probeName of probeNames) {
+ if (!(probeName in Probes)) {
+ throw new TypeError("Probe not implemented: " + probeName);
+ }
+ probes.push(Probes[probeName]);
+ }
+
+ return (new PerformanceMonitor(probes));
+};
+
+/**
+ * Implementation of `dispose`.
+ *
+ * The actual implementation of `dispose` is as a method of `PerformanceMonitor`,
+ * rather than `PerformanceMonitor.prototype`, to avoid needing a strong reference
+ * to instances of `PerformanceMonitor`, which would defeat the purpose of
+ * finalization.
+ */
+PerformanceMonitor.dispose = function(id) {
+ let probes = PerformanceMonitor._monitors.get(id);
+ if (!probes) {
+ throw new TypeError("`dispose()` has already been called on this monitor");
+ }
+
+ PerformanceMonitor._monitors.delete(id);
+ for (let probe of probes) {
+ probe.release();
+ }
+}
+
+// Generate a unique id for each PerformanceMonitor. Used during
+// finalization.
+PerformanceMonitor._counter = 0;
+PerformanceMonitor.makeId = function() {
+ return "PerformanceMonitor-" + (this._counter++);
+}
+
+// Once a `PerformanceMonitor` has been garbage-collected,
+// release the probes unless `dispose()` has already been called.
+Services.obs.addObserver(function(subject, topic, value) {
+ PerformanceMonitor.dispose(value);
+}, FINALIZATION_TOPIC, false);
+
+// Public API
+this.PerformanceStats = {
+ /**
+ * Create a monitor for observing a set of performance probes.
+ */
+ getMonitor: function(probes) {
+ return PerformanceMonitor.make(probes);
+ }
+};
+
+
+/**
+ * Information on a single performance group.
+ *
+ * This offers the following fields:
+ *
+ * @field {string} name The name of the performance group:
+ * - for the process itself, "<process>";
+ * - for platform code, "<platform>";
+ * - for an add-on, the identifier of the addon (e.g. "myaddon@foo.bar");
+ * - for a webpage, the url of the page.
+ *
+ * @field {string} addonId The identifier of the addon (e.g. "myaddon@foo.bar").
+ *
+ * @field {string|null} title The title of the webpage to which this code
+ * belongs. Note that this is the title of the entire webpage (i.e. the tab),
+ * even if the code is executed in an iframe. Also note that this title may
+ * change over time.
+ *
+ * @field {number} windowId The outer window ID of the top-level nsIDOMWindow
+ * to which this code belongs. May be 0 if the code doesn't belong to any
+ * nsIDOMWindow.
+ *
+ * @field {boolean} isSystem `true` if the component is a system component (i.e.
+ * an add-on or platform-code), `false` otherwise (i.e. a webpage).
+ *
+ * @field {object|undefined} activations See the documentation of probe "ticks".
+ * `undefined` if this probe is not active.
+ *
+ * @field {object|undefined} jank See the documentation of probe "jank".
+ * `undefined` if this probe is not active.
+ *
+ * @field {object|undefined} cpow See the documentation of probe "cpow".
+ * `undefined` if this probe is not active.
+ */
+function PerformanceDataLeaf({xpcom, json, probes}) {
+ if (xpcom && json) {
+ throw new TypeError("Cannot import both xpcom and json data");
+ }
+ let source = xpcom || json;
+ for (let k of PROPERTIES_META) {
+ this[k] = source[k];
+ }
+ if (xpcom) {
+ for (let probe of probes) {
+ this[probe.name] = probe.extract(xpcom);
+ }
+ this.isChildProcess = false;
+ } else {
+ for (let probe of probes) {
+ this[probe.name] = json[probe.name];
+ }
+ this.isChildProcess = true;
+ }
+ this.owner = null;
+}
+PerformanceDataLeaf.prototype = {
+ /**
+ * Compare two instances of `PerformanceData`
+ *
+ * @return `true` if `this` and `to` have equal values in all fields.
+ */
+ equals: function(to) {
+ if (!(to instanceof PerformanceDataLeaf)) {
+ throw new TypeError();
+ }
+ for (let probeName of Object.keys(Probes)) {
+ let probe = Probes[probeName];
+ if (!probe.isEqual(this[probeName], to[probeName])) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Compute the delta between two instances of `PerformanceData`.
+ *
+ * @param {PerformanceData|null} to. If `null`, assumed an instance of
+ * `PerformanceData` in which all numeric values are 0.
+ *
+ * @return {PerformanceDiff} The performance usage between `to` and `this`.
+ */
+ subtract: function(to = null) {
+ return (new PerformanceDiffLeaf(this, to));
+ }
+};
+
+function PerformanceData(timestamp) {
+ this._parent = null;
+ this._content = new Map();
+ this._all = [];
+ this._timestamp = timestamp;
+}
+PerformanceData.prototype = {
+ addChild: function(stat) {
+ if (!(stat instanceof PerformanceDataLeaf)) {
+ throw new TypeError(); // FIXME
+ }
+ if (!stat.isChildProcess) {
+ throw new TypeError(); // FIXME
+ }
+ this._content.set(stat.groupId, stat);
+ this._all.push(stat);
+ stat.owner = this;
+ },
+ setParent: function(stat) {
+ if (!(stat instanceof PerformanceDataLeaf)) {
+ throw new TypeError(); // FIXME
+ }
+ if (stat.isChildProcess) {
+ throw new TypeError(); // FIXME
+ }
+ this._parent = stat;
+ this._all.push(stat);
+ stat.owner = this;
+ },
+ equals: function(to) {
+ if (this._parent && !to._parent) {
+ return false;
+ }
+ if (!this._parent && to._parent) {
+ return false;
+ }
+ if (this._content.size != to._content.size) {
+ return false;
+ }
+ if (this._parent && !this._parent.equals(to._parent)) {
+ return false;
+ }
+ for (let [k, v] of this._content) {
+ let v2 = to._content.get(k);
+ if (!v2) {
+ return false;
+ }
+ if (!v.equals(v2)) {
+ return false;
+ }
+ }
+ return true;
+ },
+ subtract: function(to = null) {
+ return (new PerformanceDiff(this, to));
+ },
+ get addonId() {
+ return this._all[0].addonId;
+ },
+ get title() {
+ return this._all[0].title;
+ }
+};
+
+function PerformanceDiff(current, old = null) {
+ this.addonId = current.addonId;
+ this.title = current.title;
+ this.windowId = current.windowId;
+ this.deltaT = old ? current._timestamp - old._timestamp : Infinity;
+ this._all = [];
+
+ // Handle the parent, if any.
+ if (current._parent) {
+ this._parent = old?current._parent.subtract(old._parent):current._parent;
+ this._all.push(this._parent);
+ this._parent.owner = this;
+ } else {
+ this._parent = null;
+ }
+
+ // Handle the children, if any.
+ this._content = new Map();
+ for (let [k, stat] of current._content) {
+ let diff = stat.subtract(old ? old._content.get(k) : null);
+ this._content.set(k, diff);
+ this._all.push(diff);
+ diff.owner = this;
+ }
+
+ // Now consolidate data
+ for (let k of Object.keys(Probes)) {
+ if (!(k in this._all[0])) {
+ // The stats don't contain data from this probe.
+ continue;
+ }
+ let data = this._all.map(item => item[k]);
+ let probe = Probes[k];
+ this[k] = probe.compose(data);
+ }
+}
+PerformanceDiff.prototype = {
+ toString: function() {
+ return `[PerformanceDiff] ${this.key}`;
+ },
+ get windowIds() {
+ return this._all.map(item => item.windowId).filter(x => !!x);
+ },
+ get groupIds() {
+ return this._all.map(item => item.groupId);
+ },
+ get key() {
+ if (this.addonId) {
+ return this.addonId;
+ }
+ if (this._parent) {
+ return this._parent.windowId;
+ }
+ return this._all[0].groupId;
+ },
+ get names() {
+ return this._all.map(item => item.name);
+ },
+ get processes() {
+ return this._all.map(item => ({ isChildProcess: item.isChildProcess, processId: item.processId}));
+ }
+};
+
+/**
+ * The delta between two instances of `PerformanceDataLeaf`.
+ *
+ * Used to monitor resource usage between two timestamps.
+ */
+function PerformanceDiffLeaf(current, old = null) {
+ for (let k of PROPERTIES_META) {
+ this[k] = current[k];
+ }
+
+ for (let probeName of Object.keys(Probes)) {
+ let other = null;
+ if (old && probeName in old) {
+ other = old[probeName];
+ }
+
+ if (probeName in current) {
+ this[probeName] = Probes[probeName].subtract(current[probeName], other);
+ }
+ }
+}
+
+/**
+ * A snapshot of a single process.
+ */
+function ProcessSnapshot({xpcom, probes}) {
+ this.componentsData = [];
+
+ let subgroups = new Map();
+ let enumeration = xpcom.getComponentsData().enumerate();
+ while (enumeration.hasMoreElements()) {
+ let xpcom = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats);
+ let stat = (new PerformanceDataLeaf({xpcom, probes}));
+
+ if (!xpcom.parentId) {
+ this.componentsData.push(stat);
+ } else {
+ let siblings = subgroups.get(xpcom.parentId);
+ if (!siblings) {
+ subgroups.set(xpcom.parentId, (siblings = []));
+ }
+ siblings.push(stat);
+ }
+ }
+
+ for (let group of this.componentsData) {
+ for (let probe of probes) {
+ probe.importChildCompartments(group, subgroups.get(group.groupId) || []);
+ }
+ }
+
+ this.processData = (new PerformanceDataLeaf({xpcom: xpcom.getProcessData(), probes}));
+}
+
+/**
+ * A snapshot of the performance usage of the application.
+ *
+ * @param {nsIPerformanceSnapshot} xpcom The data acquired from this process.
+ * @param {Array<Object>} childProcesses The data acquired from children processes.
+ * @param {Array<Probe>} probes The active probes.
+ */
+function ApplicationSnapshot({xpcom, childProcesses, probes, date}) {
+ ProcessSnapshot.call(this, {xpcom, probes});
+
+ this.addons = new Map();
+ this.webpages = new Map();
+ this.date = date;
+
+ // Child processes
+ for (let {componentsData} of (childProcesses || [])) {
+ // We are only interested in `componentsData` for the time being.
+ for (let json of componentsData) {
+ let leaf = (new PerformanceDataLeaf({json, probes}));
+ this.componentsData.push(leaf);
+ }
+ }
+
+ for (let leaf of this.componentsData) {
+ let key, map;
+ if (leaf.addonId) {
+ key = leaf.addonId;
+ map = this.addons;
+ } else if (leaf.windowId) {
+ key = leaf.windowId;
+ map = this.webpages;
+ } else {
+ continue;
+ }
+
+ let combined = map.get(key);
+ if (!combined) {
+ combined = new PerformanceData(date);
+ map.set(key, combined);
+ }
+ if (leaf.isChildProcess) {
+ combined.addChild(leaf);
+ } else {
+ combined.setParent(leaf);
+ }
+ }
+}
+
+/**
+ * Communication with other processes
+ */
+var Process = {
+ // a counter used to match responses to requests
+ _idcounter: 0,
+ _loader: null,
+ /**
+ * If we are in a child process, return `null`.
+ * Otherwise, return the global parent process message manager
+ * and load the script to connect to children processes.
+ */
+ get loader() {
+ if (isContent) {
+ return null;
+ }
+ if (this._loader) {
+ return this._loader;
+ }
+ Services.ppmm.loadProcessScript("resource://gre/modules/PerformanceStats-content.js",
+ true/* including future processes*/);
+ return this._loader = Services.ppmm;
+ },
+
+ /**
+ * Broadcast a message to all children processes.
+ *
+ * NOOP if we are in a child process.
+ */
+ broadcast: function(topic, payload) {
+ if (!this.loader) {
+ return;
+ }
+ this.loader.broadcastAsyncMessage("performance-stats-service-" + topic, {payload});
+ },
+
+ /**
+ * Brodcast a message to all children processes and wait for answer.
+ *
+ * NOOP if we are in a child process, or if we have no children processes,
+ * in which case we return `undefined`.
+ *
+ * @return {undefined} If we have no children processes, in particular
+ * if we are in a child process.
+ * @return {Promise<Array<Object>>} If we have children processes, an
+ * array of objects with a structure similar to PerformanceData. Note
+ * that the array may be empty if no child process responded.
+ */
+ broadcastAndCollect: Task.async(function*(topic, payload) {
+ if (!this.loader || this.loader.childCount == 1) {
+ return undefined;
+ }
+ const TOPIC = "performance-stats-service-" + topic;
+ let id = this._idcounter++;
+
+ // The number of responses we are expecting. Note that we may
+ // not receive all responses if a process is too long to respond.
+ let expecting = this.loader.childCount;
+
+ // The responses we have collected, in arbitrary order.
+ let collected = [];
+ let deferred = PromiseUtils.defer();
+
+ let observer = function({data, target}) {
+ if (data.id != id) {
+ // Collision between two collections,
+ // ignore the other one.
+ return;
+ }
+ if (data.data) {
+ collected.push(data.data)
+ }
+ if (--expecting > 0) {
+ // We are still waiting for at least one response.
+ return;
+ }
+ deferred.resolve();
+ };
+ this.loader.addMessageListener(TOPIC, observer);
+ this.loader.broadcastAsyncMessage(
+ TOPIC,
+ {id, payload}
+ );
+
+ // Processes can die/freeze/be busy loading a page..., so don't expect
+ // that they will always respond.
+ let timeout = setTimeout(() => {
+ if (expecting == 0) {
+ return;
+ }
+ deferred.resolve();
+ }, MAX_WAIT_FOR_CHILD_PROCESS_MS);
+
+ deferred.promise.then(() => {
+ clearTimeout(timeout);
+ });
+
+ yield deferred.promise;
+ this.loader.removeMessageListener(TOPIC, observer);
+
+ return collected;
+ })
+};
diff --git a/components/perfmonitoring/PerformanceWatcher-content.js b/components/perfmonitoring/PerformanceWatcher-content.js
new file mode 100644
index 000000000..2956cf5d0
--- /dev/null
+++ b/components/perfmonitoring/PerformanceWatcher-content.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * An API for being informed of slow add-ons and tabs
+ * (content process scripts).
+ */
+
+const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+/**
+ * `true` if this is a content process, `false` otherwise.
+ */
+let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+
+if (isContent) {
+
+const { PerformanceWatcher } = Cu.import("resource://gre/modules/PerformanceWatcher.jsm", {});
+
+let toMsg = function(alerts) {
+ let result = [];
+ for (let {source, details} of alerts) {
+ // Convert xpcom values to serializable data.
+ let serializableSource = {};
+ for (let k of ["groupId", "name", "addonId", "windowId", "isSystem", "processId", "isContentProcess"]) {
+ serializableSource[k] = source[k];
+ }
+
+ let serializableDetails = {};
+ for (let k of ["reason", "highestJank", "highestCPOW"]) {
+ serializableDetails[k] = details[k];
+ }
+ result.push({source:serializableSource, details:serializableDetails});
+ }
+ return result;
+}
+
+PerformanceWatcher.addPerformanceListener({addonId: "*"}, alerts => {
+ Services.cpmm.sendAsyncMessage("performancewatcher-propagate-notifications",
+ {addons: toMsg(alerts)}
+ );
+});
+
+PerformanceWatcher.addPerformanceListener({windowId: 0}, alerts => {
+ Services.cpmm.sendAsyncMessage("performancewatcher-propagate-notifications",
+ {windows: toMsg(alerts)}
+ );
+});
+
+}
diff --git a/components/perfmonitoring/PerformanceWatcher.jsm b/components/perfmonitoring/PerformanceWatcher.jsm
new file mode 100644
index 000000000..d0d034974
--- /dev/null
+++ b/components/perfmonitoring/PerformanceWatcher.jsm
@@ -0,0 +1,367 @@
+// -*- 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/. */
+
+"use strict";
+
+/**
+ * An API for being informed of slow add-ons and tabs.
+ *
+ * Generally, this API is both more CPU-efficient and more battery-efficient
+ * than PerformanceStats. As PerformanceStats, this API does not provide any
+ * information during the startup or shutdown of Firefox.
+ *
+ * = Examples =
+ *
+ * Example use: reporting whenever a specific add-on slows down Firefox.
+ * let listener = function(source, details) {
+ * // This listener is triggered whenever the addon causes Firefox to miss
+ * // frames. Argument `source` contains information about the source of the
+ * // slowdown (including the process in which it happens), while `details`
+ * // contains performance statistics.
+ * console.log(`Oops, add-on ${source.addonId} seems to be slowing down Firefox.`, details);
+ * };
+ * PerformanceWatcher.addPerformanceListener({addonId: "myaddon@myself.name"}, listener);
+ *
+ * Example use: reporting whenever any webpage slows down Firefox.
+ * let listener = function(alerts) {
+ * // This listener is triggered whenever any window causes Firefox to miss
+ * // frames. FieldArgument `source` contains information about the source of the
+ * // slowdown (including the process in which it happens), while `details`
+ * // contains performance statistics.
+ * for (let {source, details} of alerts) {
+ * console.log(`Oops, window ${source.windowId} seems to be slowing down Firefox.`, details);
+ * };
+ * // Special windowId 0 lets us to listen to all webpages.
+ * PerformanceWatcher.addPerformanceListener({windowId: 0}, listener);
+ *
+ *
+ * = How this works =
+ *
+ * This high-level API is based on the lower-level nsIPerformanceStatsService.
+ * At the end of each event (including micro-tasks), the nsIPerformanceStatsService
+ * updates its internal performance statistics and determines whether any
+ * add-on/window in the current process has exceeded the jank threshold.
+ *
+ * The PerformanceWatcher maintains low-level performance observers in each
+ * process and forwards alerts to the main process. Internal observers collate
+ * low-level main process alerts and children process alerts and notify clients
+ * of this API.
+ */
+
+this.EXPORTED_SYMBOLS = ["PerformanceWatcher"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let { PerformanceStats, performanceStatsService } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {});
+let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+// `true` if the code is executed in content, `false` otherwise
+let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
+
+if (!isContent) {
+ // Initialize communication with children.
+ //
+ // To keep the protocol simple, the children inform the parent whenever a slow
+ // add-on/tab is detected. We do not attempt to implement thresholds.
+ Services.ppmm.loadProcessScript("resource://gre/modules/PerformanceWatcher-content.js",
+ true/* including future processes*/);
+
+ Services.ppmm.addMessageListener("performancewatcher-propagate-notifications",
+ (...args) => ChildManager.notifyObservers(...args)
+ );
+}
+
+// Configure the performance stats service to inform us in case of jank.
+performanceStatsService.jankAlertThreshold = 64000 /* us */;
+
+
+/**
+ * Handle communications with child processes. Handle listening to
+ * either a single add-on id (including the special add-on id "*",
+ * which is notified for all add-ons) or a single window id (including
+ * the special window id 0, which is notified for all windows).
+ *
+ * Acquire through `ChildManager.getAddon` and `ChildManager.getWindow`.
+ */
+function ChildManager(map, key) {
+ this.key = key;
+ this._map = map;
+ this._listeners = new Set();
+}
+ChildManager.prototype = {
+ /**
+ * Add a listener, which will be notified whenever a child process
+ * reports a slow performance alert for this addon/window.
+ */
+ addListener: function(listener) {
+ this._listeners.add(listener);
+ },
+ /**
+ * Remove a listener.
+ */
+ removeListener: function(listener) {
+ let deleted = this._listeners.delete(listener);
+ if (!deleted) {
+ throw new Error("Unknown listener");
+ }
+ },
+
+ listeners: function() {
+ return this._listeners.values();
+ }
+};
+
+/**
+ * Dispatch child alerts to observers.
+ *
+ * Triggered by messages from content processes.
+ */
+ChildManager.notifyObservers = function({data: {addons, windows}}) {
+ if (addons && addons.length > 0) {
+ // Dispatch the entire list to universal listeners
+ this._notify(ChildManager.getAddon("*").listeners(), addons);
+
+ // Dispatch individual alerts to individual listeners
+ for (let {source, details} of addons) {
+ this._notify(ChildManager.getAddon(source.addonId).listeners(), source, details);
+ }
+ }
+ if (windows && windows.length > 0) {
+ // Dispatch the entire list to universal listeners
+ this._notify(ChildManager.getWindow(0).listeners(), windows);
+
+ // Dispatch individual alerts to individual listeners
+ for (let {source, details} of windows) {
+ this._notify(ChildManager.getWindow(source.windowId).listeners(), source, details);
+ }
+ }
+};
+
+ChildManager._notify = function(targets, ...args) {
+ for (let target of targets) {
+ target(...args);
+ }
+};
+
+ChildManager.getAddon = function(key) {
+ return this._get(this._addons, key);
+};
+ChildManager._addons = new Map();
+
+ChildManager.getWindow = function(key) {
+ return this._get(this._windows, key);
+};
+ChildManager._windows = new Map();
+
+ChildManager._get = function(map, key) {
+ let result = map.get(key);
+ if (!result) {
+ result = new ChildManager(map, key);
+ map.set(key, result);
+ }
+ return result;
+};
+let gListeners = new WeakMap();
+
+/**
+ * An object in charge of managing all the observables for a single
+ * target (window/addon/all windows/all addons).
+ *
+ * In a content process, a target is represented by a single observable.
+ * The situation is more sophisticated in a parent process, as a target
+ * has both an in-process observable and several observables across children
+ * processes.
+ *
+ * This class abstracts away the difference to simplify the work of
+ * (un)registering observers for targets.
+ *
+ * @param {object} target The target being observed, as an object
+ * with one of the following fields:
+ * - {string} addonId Either "*" for the universal add-on observer
+ * or the add-on id of an addon. Note that this class does not
+ * check whether the add-on effectively exists, and that observers
+ * may be registered for an add-on before the add-on is installed
+ * or started.
+ * - {xul:tab} tab A single tab. It must already be initialized.
+ * - {number} windowId Either 0 for the universal window observer
+ * or the outer window id of the window.
+ */
+function Observable(target) {
+ // A mapping from `listener` (function) to `Observer`.
+ this._observers = new Map();
+ if ("addonId" in target) {
+ this._key = `addonId: ${target.addonId}`;
+ this._process = performanceStatsService.getObservableAddon(target.addonId);
+ this._children = isContent ? null : ChildManager.getAddon(target.addonId);
+ this._isBuffered = target.addonId == "*";
+ } else if ("tab" in target || "windowId" in target) {
+ let windowId;
+ if ("tab" in target) {
+ windowId = target.tab.linkedBrowser.outerWindowID;
+ // By convention, outerWindowID may not be 0.
+ } else if ("windowId" in target) {
+ windowId = target.windowId;
+ }
+ if (windowId == undefined || windowId == null) {
+ throw new TypeError(`No outerWindowID. Perhaps the target is a tab that is not initialized yet.`);
+ }
+ this._key = `tab-windowId: ${windowId}`;
+ this._process = performanceStatsService.getObservableWindow(windowId);
+ this._children = isContent ? null : ChildManager.getWindow(windowId);
+ this._isBuffered = windowId == 0;
+ } else {
+ throw new TypeError("Unexpected target");
+ }
+}
+Observable.prototype = {
+ addJankObserver: function(listener) {
+ if (this._observers.has(listener)) {
+ throw new TypeError(`Listener already registered for target ${this._key}`);
+ }
+ if (this._children) {
+ this._children.addListener(listener);
+ }
+ let observer = this._isBuffered ? new BufferedObserver(listener)
+ : new Observer(listener);
+ // Store the observer to be able to call `this._process.removeJankObserver`.
+ this._observers.set(listener, observer);
+
+ this._process.addJankObserver(observer);
+ },
+ removeJankObserver: function(listener) {
+ let observer = this._observers.get(listener);
+ if (!observer) {
+ throw new TypeError(`No listener for target ${this._key}`);
+ }
+ this._observers.delete(listener);
+
+ if (this._children) {
+ this._children.removeListener(listener);
+ }
+
+ this._process.removeJankObserver(observer);
+ observer.dispose();
+ },
+};
+
+/**
+ * Get a cached observable for a given target.
+ */
+Observable.get = function(target) {
+ let key;
+ if ("addonId" in target) {
+ key = target.addonId;
+ } else if ("tab" in target) {
+ // We do not want to use a tab as a key, as this would prevent it from
+ // being garbage-collected.
+ key = target.tab.linkedBrowser.outerWindowID;
+ } else if ("windowId" in target) {
+ key = target.windowId;
+ }
+ if (key == null) {
+ throw new TypeError(`Could not extract a key from ${JSON.stringify(target)}. Could the target be an unitialized tab?`);
+ }
+ let observable = this._cache.get(key);
+ if (!observable) {
+ observable = new Observable(target);
+ this._cache.set(key, observable);
+ }
+ return observable;
+};
+Observable._cache = new Map();
+
+/**
+ * Wrap a listener callback as an unbuffered nsIPerformanceObserver.
+ *
+ * Each observation is propagated immediately to the listener.
+ */
+function Observer(listener) {
+ // Make sure that monitoring stays alive (in all processes) at least as
+ // long as the observer.
+ this._monitor = PerformanceStats.getMonitor(["jank", "cpow"]);
+ this._listener = listener;
+}
+Observer.prototype = {
+ observe: function(...args) {
+ this._listener(...args);
+ },
+ dispose: function() {
+ this._monitor.dispose();
+ this.observe = function poison() {
+ throw new Error("Internal error: I should have stopped receiving notifications");
+ }
+ },
+};
+
+/**
+ * Wrap a listener callback as an buffered nsIPerformanceObserver.
+ *
+ * Observations are buffered and dispatch in the next tick to the listener.
+ */
+function BufferedObserver(listener) {
+ Observer.call(this, listener);
+ this._buffer = [];
+ this._isDispatching = false;
+ this._pending = null;
+}
+BufferedObserver.prototype = Object.create(Observer.prototype);
+BufferedObserver.prototype.observe = function(source, details) {
+ this._buffer.push({source, details});
+ if (!this._isDispatching) {
+ this._isDispatching = true;
+ Services.tm.mainThread.dispatch(() => {
+ // Grab buffer, in case something in the listener could modify it.
+ let buffer = this._buffer;
+ this._buffer = [];
+
+ // As of this point, any further observations need to use the new buffer
+ // and a new dispatcher.
+ this._isDispatching = false;
+
+ this._listener(buffer);
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+};
+
+this.PerformanceWatcher = {
+ /**
+ * Add a listener informed whenever we receive a slow performance alert
+ * in the application.
+ *
+ * @param {object} target An object with one of the following fields:
+ * - {string} addonId Either "*" to observe all add-ons or a full add-on ID.
+ * to observe a single add-on.
+ * - {number} windowId Either 0 to observe all windows or an outer window ID
+ * to observe a single tab.
+ * - {xul:browser} tab To observe a single tab.
+ * @param {function} listener A function that will be triggered whenever
+ * the target causes a slow performance notification. The notification may
+ * have originated in any process of the application.
+ *
+ * If the listener listens to a single add-on/webpage, it is triggered with
+ * the following arguments:
+ * source: {groupId, name, addonId, windowId, isSystem, processId}
+ * Information on the source of the notification.
+ * details: {reason, highestJank, highestCPOW} Information on the
+ * notification.
+ *
+ * If the listener listens to all add-ons/all webpages, it is triggered with
+ * an array of {source, details}, as described above.
+ */
+ addPerformanceListener: function(target, listener) {
+ if (typeof listener != "function") {
+ throw new TypeError();
+ }
+ let observable = Observable.get(target);
+ observable.addJankObserver(listener);
+ },
+ removePerformanceListener: function(target, listener) {
+ if (typeof listener != "function") {
+ throw new TypeError();
+ }
+ let observable = Observable.get(target);
+ observable.removeJankObserver(listener);
+ },
+};
diff --git a/components/perfmonitoring/README.md b/components/perfmonitoring/README.md
new file mode 100644
index 000000000..abd109e45
--- /dev/null
+++ b/components/perfmonitoring/README.md
@@ -0,0 +1,120 @@
+This directory is part of the implementation of the Performance Monitoring API
+
+# What is the Performance Monitoring API?
+
+The Performance Monitoring API is a set of interfaces designed to let front-end code find out if the application or a specific process is currently janky, quantify this jank and its evolution, and investigate what is causing jank (system code? a webpage? an add-on? CPOW?). In other words, this is a form of minimal profiler, designed to be lightweight enough to be enabled at all times in production code.
+
+In Firefox Nightly, the Performance Monitoring API is used to:
+- inform users if their machine janks because of an add-on;
+- upload add-on performance to Telemetry for the benefit of AMO maintainers and add-on developers;
+- let users inspect the performance of their browser through about:performance.
+
+# How can I use the API?
+
+The API is designed mainly to be used from JavaScript client code, using PerformanceStats.jsm. If you really need to use it from C++ code, you should use the performance stats service defined in nsIPerformanceStats.idl. Note that PerformanceStats.jsm contains support for entire e10s-enabled applications, while nsIPerformanceStats.idl only supports one process at a time.
+
+
+# How does the Performance Monitoring API work?
+
+At the time of this writing, the implementation of this API monitors only performance information related to the execution of JavaScript code, and only in the main thread. This is performed by an instrumentation of js/, orchestrated by toolkit/.
+
+At low-level, the unit of code used for monitoring is the JS Compartment: one .jsm module, one XPCOM component, one sandbox, one script in an iframe, ... When executing code in a compartment, it is possible to inspect either the compartment or the script itself to find out who this compartment belongs to: a `<xul:browser>`, an add-on, etc.
+
+At higher-level, the unit of code used for monitoring is the Performance Group. One Performance Group represents one or more JS Compartments, grouped together because we are interested in their performance. The current implementation uses Performance Groups to represent individual JS Compartments, entire add-ons, entire webpages including iframes and entire threads. Other applications have been discussed to represent entire eTLD+1 domains (e.g. to monitor the cost of ads), etc.
+
+A choice was made to represent the CPU cost in *clock cycles* at low-level, as extracting a number of clock cycles has a very low latency (typically a few dozen cycles on recent architectures) and is much more precise than `getrusage`-style CPU clocks (which are often limited to a precision of 16ms). The drawback of this choice is that distinct CPUs/cores may, depending on the architecture, have entirely unrelated clock cycles count. We assume that the risk of false positives is reasonably low, and bound the uncertainty by renormalizing the result with the actual CPU clocks once per event.
+
+## SpiderMonkey-level
+
+The instrumentation of SpiderMonkey lives in `js/src/vm/Stopwatch.*`. As SpiderMonkey does not know about the Gecko event loop, or DOM events, or windows, so any such information must be provided by the embedding. To communicate with higher levels, SpiderMonkey exposes a virtual class `js::PerformanceGroup` designed to be subclassed and instantiated by the embedding based on its interests.
+
+An instance of `js::PerformanceGroup` may be acquired (to mark that it is currently being monitored) and released (once monitoring is complete or cancelled) by SpiderMonkey. Furthermore, a `js::PerformanceGroup` can be marked as active (to mark that the embedding is currently interested in its performance) or inactive (otherwise) by the embedding.
+
+Each `js::Performance` holds a total CPU cost measured in *clock cycles* and a total CPOW cost measured in *microseconds*. Both costs can only increase while measuring data, and can be reset to 0 by the embedding, once we have finished execution of the event loop.
+
+### Upon starting to execute code in a JS Compartment `cx`
+1. If global monitoring is deactivated, bailout;
+2. If XPConnect has informed us that we are entering a nested event loop, cancel any ongoing measure on the outer event loop and proceed with the current measure;
+3. If we do not know to which performance groups `cx` is associated, request the information from the embedding;
+4. For each performance group `group` to which `cx` belongs *and* that is not acquired *and* for which monitoring is active, acquire the group;
+5. If no group was acquired, bailout;
+6. Capture a timestamp for the CPU cost of `cx`, in *clock cycles*. This value is provided directly by the CPU;
+7. Capture a timestamp for the CPOW cost of `cx`, in *CPOW microseconds*. This value is provided by the CPOW-level embedding.
+
+### Upon stopping execution of the code in the JS compartment `cx`
+1. If global monitoring is deactivated, bailout;
+2. If the measure has been canceled, bailout;
+3. If no group was acquired, bailout;
+4. Capture a timestamp for the CPU cost of `cx`, use it to update the total CPU cost of each of the groups acquired;
+5. Capture a timestamp for the CPOW cost of `cx`, use it to update the total CPOW cost of each of the groups acquired;
+6. Mark acquired groups as executed recently;
+7. Release groups.
+
+### When informed by the embedding that the iteration of the event loop is complete
+1. Commit all the groups executed recently to the embedding;
+2. Release all groups;
+3. Reset all CPU/CPOW costs to 0.
+
+## Cross-Process Object Wrapper-level
+
+The instrumentation of CPOW lives in `js/ipc/src`. It maintains a CPOW clock that increases whenever the process is blocked by a CPOW call.
+
+## XPConnect-level
+
+The instrumentation of XPConnect lives in `js/xpconnect/src/XPCJSContext.cpp`.
+
+### When we enter a nested event loop
+
+1. Inform the SpiderMonkey-level instrumentation, to let it cancel any ongoing measure.
+
+### When we finish executing an iteration of the event loop, including microtasks:
+
+1. Inform the SpiderMonkey-level instrumentation, to let it commit its recent data.
+
+## nsIPerformanceStatsService-level
+
+This code lives in `toolkit/components/perfmonitoring/ns*`. Its role is to orchestrate the information provided by SpiderMonkey at the scale of a single thread of a single process. At the time of this writing, this instrumentation is only activated on the main thread, for all Gecko processes.
+
+The service defines a class `nsPerformanceGroup`, designed to be the sole concrete implementation of `js::PerformanceGroup`. `nsPerformanceGroup` extends `js::PerformanceGroup` with the global performance information gathered for the group since the start of the service. The information is:
+- total CPU time measured;
+- total CPOW time measured;
+- number of times CPU time exceeded 1ms;
+- number of times CPU time exceeded 2ms;
+- number of times CPU time exceeded 4ms;
+- ...
+- number of times CPU time exceeded 2^9ms.
+
+Also, `nsPerformanceGroup` extends `js::PerformanceGroup` with high-level identification:
+- id of the window that executed the code, if any;
+- id of the add-on that provided the code, if any.
+
+### When the SpiderMonkey-level instrumentation requests a list of PerformanceGroup for a compartment
+
+Return a list with the following groups:
+* all compartments are associated with the "top group", which represents the entire thread;
+* find out if the compartment is code from a window, if so add a group shared by all compartments for this specific window;
+* find out if the compartment is code from an add-on, if so add a group shared by all compartments for this add-on;
+* add a group representing this specific compartment.
+
+For performance reasons, groups representing a single compartment are inactive by default, while all other groups are active by default.
+
+Performance groups are refcounted and destroyed with the implementation of `delete` used by toolkit/.
+
+### When the SpiderMonkey-level instrumentation commits a list of PerformanceGroups
+
+For each group in the list:
+1. transfer recent CPU time and recent CPOW time to total CPU time, total CPOW time, number of times CPU time exceeded *n* ms;
+2. reset group.
+
+Future versions are expected to trigger low-performance alerts at this stage.
+
+### Snapshotting
+
+(to be documented)
+
+## PerformanceStats.jsm-level
+
+PerformanceStats provides a JS-friendly API on top of nsIPerformanceStatsService. The main differences are:
+- utilities for subtracting snapshots;
+- tracking clients that need specific measures;
+- synchronization between e10s processes.
diff --git a/components/perfmonitoring/moz.build b/components/perfmonitoring/moz.build
new file mode 100644
index 000000000..51fb42690
--- /dev/null
+++ b/components/perfmonitoring/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_MODULE = 'toolkit_perfmonitoring'
+
+XPIDL_SOURCES += ['nsIPerformanceStats.idl']
+
+EXPORTS += ['nsPerformanceStats.h']
+
+LOCAL_INCLUDES += ['/dom/base']
+
+UNIFIED_SOURCES += ['nsPerformanceStats.cpp']
+
+EXTRA_JS_MODULES += [
+ 'AddonWatcher.jsm',
+ 'PerformanceStats-content.js',
+ 'PerformanceStats.jsm',
+ 'PerformanceWatcher-content.js',
+ 'PerformanceWatcher.jsm',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/perfmonitoring/nsIPerformanceStats.idl b/components/perfmonitoring/nsIPerformanceStats.idl
new file mode 100644
index 000000000..edc2d2edc
--- /dev/null
+++ b/components/perfmonitoring/nsIPerformanceStats.idl
@@ -0,0 +1,332 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "nsIArray.idl"
+#include "nsIDOMWindow.idl"
+
+/**
+ * Mechanisms for querying the current process about performance
+ * information.
+ *
+ * JavaScript clients should rather use PerformanceStats.jsm.
+ */
+
+/**
+ * Identification details for a performance group.
+ *
+ * A performance group is a set of JavaScript compartments whose
+ * performance is observed as a single entity. Typical examples of
+ * performance groups: an add-on, a webpage without its frames, a
+ * webpage with all its frames, the entire JS runtime, ...
+ */
+[scriptable, builtinclass, uuid(994c56be-939a-4f20-8364-124f6422d86a)]
+interface nsIPerformanceGroupDetails: nsISupports {
+ /**
+ * An identifier unique to the component.
+ *
+ * This identifier is somewhat human-readable to aid with debugging,
+ * but clients should not rely upon the format.
+ */
+ readonly attribute AString groupId;
+
+ /**
+ * A somewhat human-readable name for the component.
+ */
+ readonly attribute AString name;
+
+ /**
+ * If the component is an add-on, the ID of the addon,
+ * otherwise an empty string.
+ */
+ readonly attribute AString addonId;
+
+ /**
+ * If the component is code executed in a window, the ID of the topmost
+ * outer window (i.e. the tab), otherwise 0.
+ */
+ readonly attribute uint64_t windowId;
+
+ /**
+ * `true` if this component is executed with system privileges
+ * (e.g. the platform itself or an add-on), `false` otherwise
+ * (e.g. webpages).
+ */
+ readonly attribute bool isSystem;
+
+ /**
+ * The process running this group.
+ */
+ readonly attribute unsigned long long processId;
+
+ /**
+ * `true` if the code is executed in a content process, `false` otherwise.
+ */
+ readonly attribute bool isContentProcess;
+};
+
+/**
+ * Snapshot of the performance of a component, e.g. an add-on, a web
+ * page, system built-ins, a module or the entire process itself.
+ *
+ * All values are monotonic and are updated only when
+ * `nsIPerformanceStatsService.isStopwatchActive` is `true`.
+ */
+[scriptable, builtinclass, uuid(8a635d4b-aa56-466b-9a7d-9f91ca9405ef)]
+interface nsIPerformanceStats: nsIPerformanceGroupDetails {
+ /**
+ * Total amount of time spent executing code in this group, in
+ * microseconds.
+ */
+ readonly attribute unsigned long long totalUserTime;
+ readonly attribute unsigned long long totalSystemTime;
+ readonly attribute unsigned long long totalCPOWTime;
+
+ /**
+ * Total number of times code execution entered this group,
+ * since process launch. This may be greater than the number
+ * of times we have entered the event loop.
+ */
+ readonly attribute unsigned long long ticks;
+
+ /**
+ * Jank indicator.
+ *
+ * durations[i] == number of times execution of this group
+ * lasted at lest 2^i ms.
+ */
+ void getDurations([optional] out unsigned long aCount,
+ [retval, array, size_is(aCount)]out unsigned long long aNumberOfOccurrences);
+};
+
+/**
+ * A snapshot of the performance data of the process.
+ */
+[scriptable, builtinclass, uuid(13cc235b-739e-4690-b0e3-d89cbe036a93)]
+interface nsIPerformanceSnapshot: nsISupports {
+ /**
+ * Data on all individual components.
+ */
+ nsIArray getComponentsData();
+
+ /**
+ * Information on the process itself.
+ *
+ * This contains the total amount of time spent executing JS code,
+ * the total amount of time spent waiting for system calls while
+ * executing JS code, the total amount of time performing blocking
+ * inter-process calls, etc.
+ */
+ nsIPerformanceStats getProcessData();
+};
+
+/**
+ * A performance alert.
+ */
+[scriptable, builtinclass, uuid(a85706ab-d703-4687-8865-78cd771eab93)]
+interface nsIPerformanceAlert: nsISupports {
+ /**
+ * A slowdown was detected.
+ *
+ * See REASON_JANK_* for details on whether this slowdown was user-noticeable.
+ */
+ const unsigned long REASON_SLOWDOWN = 1;
+
+ /**
+ * This alert was triggered during a jank in animation.
+ *
+ * In the current implementation, we consider that there is a jank
+ * in animation if delivery of the vsync message to the main thread
+ * has been delayed too much (see
+ * nsIPerformanceStatsService.animationJankLevelThreshold).
+ *
+ * Note that this is a heuristic which may provide false positives,
+ * so clients of this API are expected to perform post-processing to
+ * filter out such false positives.
+ */
+ const unsigned long REASON_JANK_IN_ANIMATION = 2;
+
+ /**
+ * This alert was triggered during a jank in user input.
+ *
+ * In the current implementation, we consider that there is a jank
+ * in animation if a user input was received either immediately
+ * before executing the offending code (see
+ * nsIPerformanceStatsService.userInputDelayThreshold) or while
+ * executing the offending code.
+ *
+ * Note that this is a heuristic which may provide false positives,
+ * so clients of this API are expected to perform post-processing to
+ * filter out such false positives.
+ */
+ const unsigned long REASON_JANK_IN_INPUT = 4;
+
+ /**
+ * The reason for the alert, as a bitwise or of the various REASON_*
+ * constants.
+ */
+ readonly attribute unsigned long reason;
+
+ /**
+ * Longest interval spent executing code in this group
+ * since the latest alert, in microseconds.
+ *
+ * Note that the underlying algorithm is probabilistic and may
+ * provide false positives, so clients of this API are expected to
+ * perform post-processing to filter out such false positives. In
+ * particular, a high system load will increase the noise level on
+ * this measure.
+ */
+ readonly attribute unsigned long long highestJank;
+
+ /**
+ * Longest interval spent executing CPOW in this group
+ * since the latest alert, in microseconds.
+ *
+ * This measure is reliable and involves no heuristics. However,
+ * note that the duration of CPOWs is increased by high system
+ * loads.
+ */
+ readonly attribute unsigned long long highestCPOW;
+};
+
+
+/**
+ * An observer for slow performance alerts.
+ */
+[scriptable, function, uuid(b746a929-3fec-420b-8ed8-c35d71995e05)]
+interface nsIPerformanceObserver: nsISupports {
+ /**
+ * @param target The performance group that caused the jank.
+ * @param alert The performance cost that triggered the alert.
+ */
+ void observe(in nsIPerformanceGroupDetails target, in nsIPerformanceAlert alert);
+};
+
+
+/**
+ * A part of the system that may be observed for slow performance.
+ */
+[scriptable, builtinclass, uuid(b85720d0-e328-4342-9e46-8ca1acf8c70e)]
+interface nsIPerformanceObservable: nsISupports {
+ /**
+ * If a single group is being observed, information on this group.
+ */
+ readonly attribute nsIPerformanceGroupDetails target;
+
+ /**
+ * Add an observer that will be informed in case of jank.
+ *
+ * Set `jankAlertThreshold` to determine how much jank is needed
+ * to trigger alerts.
+ *
+ * If the same observer is added more than once, it will be
+ * triggered as many times as it has been added.
+ */
+ void addJankObserver(in nsIPerformanceObserver observer);
+
+ /**
+ * Remove an observer previously added with `addJankObserver`.
+ *
+ * Noop if the observer hasn't been added.
+ */
+ void removeJankObserver(in nsIPerformanceObserver observer);
+};
+
+
+[scriptable, uuid(505bc42e-be38-4a53-baba-92cb33690cde)]
+interface nsIPerformanceStatsService : nsISupports {
+ /**
+ * `true` if we should monitor CPOW, `false` otherwise.
+ */
+ [implicit_jscontext] attribute bool isMonitoringCPOW;
+
+ /**
+ * `true` if we should monitor jank, `false` otherwise.
+ */
+ [implicit_jscontext] attribute bool isMonitoringJank;
+
+ /**
+ * `true` if all compartments need to be monitored individually,
+ * `false` if only performance groups (i.e. entire add-ons, entire
+ * webpages, etc.) need to be monitored.
+ */
+ [implicit_jscontext] attribute bool isMonitoringPerCompartment;
+
+ /**
+ * Capture a snapshot of the performance data.
+ */
+ [implicit_jscontext] nsIPerformanceSnapshot getSnapshot();
+
+ /**
+ * The threshold, in microseconds, above which a performance group is
+ * considered "slow" and should raise performance alerts.
+ */
+ attribute unsigned long long jankAlertThreshold;
+
+ /**
+ * If a user is seeing an animation and we spend too long executing
+ * JS code while blocking refresh, this will be visible to the user.
+ *
+ * We assume that any jank during an animation and lasting more than
+ * 2^animationJankLevelThreshold ms will be visible.
+ */
+ attribute short animationJankLevelThreshold;
+
+ /**
+ * If a user performs an input (e.g. clicking, pressing a key, but
+ * *NOT* moving the mouse), and we spend too long executing JS code
+ * before displaying feedback, this will be visible to the user even
+ * if there is no ongoing animation.
+ *
+ * We assume that any jank during `userInputDelayThreshold` us after
+ * the user input will be visible.
+ */
+ attribute unsigned long long userInputDelayThreshold;
+
+ /**
+ * A buffering delay, in milliseconds, used by the service to
+ * regroup performance alerts, before observers are actually
+ * noticed. Higher delays let the system avoid redundant
+ * notifications for the same group, and are generally better for
+ * performance.
+ */
+ attribute unsigned long jankAlertBufferingDelay;
+
+ /**
+ * Get a nsIPerformanceObservable representing an add-on. This
+ * observable may then be used to (un)register for watching
+ * performance alerts for this add-on.
+ *
+ * Note that this method has no way of finding out whether an add-on with this
+ * id is installed on the system. Also note that this covers only the current
+ * process.
+ *
+ * Use special add-on name "*" to get an observable that may be used
+ * to (un)register for watching performance alerts of all add-ons at
+ * once.
+ */
+ nsIPerformanceObservable getObservableAddon(in AString addonId);
+
+ /**
+ * Get a nsIPerformanceObservable representing a DOM window. This
+ * observable may then be used to (un)register for watching
+ * performance alerts for this window.
+ *
+ * Note that this covers only the current process.
+ *
+ * Use special window id 0 to get an observable that may be used to
+ * (un)register for watching performance alerts of all windows at
+ * once.
+ */
+ nsIPerformanceObservable getObservableWindow(in unsigned long long windowId);
+};
+
+
+%{C++
+#define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID {0xfd7435d4, 0x9ec4, 0x4699, \
+ {0xad, 0xd4, 0x1b, 0xe8, 0x3d, 0xd6, 0x8e, 0xf3} }
+#define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CONTRACTID "@mozilla.org/toolkit/performance-stats-service;1"
+%}
diff --git a/components/perfmonitoring/nsPerformanceStats.cpp b/components/perfmonitoring/nsPerformanceStats.cpp
new file mode 100644
index 000000000..15a021bba
--- /dev/null
+++ b/components/perfmonitoring/nsPerformanceStats.cpp
@@ -0,0 +1,1565 @@
+/* -*- 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 "nsPerformanceStats.h"
+
+#include "nsMemory.h"
+#include "nsLiteralString.h"
+#include "nsCRTGlue.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsCOMArray.h"
+#include "nsContentUtils.h"
+#include "nsIMutableArray.h"
+#include "nsReadableUtils.h"
+
+#include "jsapi.h"
+#include "nsJSUtils.h"
+#include "xpcpublic.h"
+#include "jspubtd.h"
+
+#include "nsIDOMWindow.h"
+#include "nsGlobalWindow.h"
+#include "nsRefreshDriver.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/Services.h"
+
+#if defined(XP_WIN)
+#include <processthreadsapi.h>
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif // defined(XP_WIN)
+
+#if defined(XP_UNIX)
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif // defined(XP_UNIX)
+/* ------------------------------------------------------
+ *
+ * Utility functions.
+ *
+ */
+
+namespace {
+
+/**
+ * Get the private window for the current compartment.
+ *
+ * @return null if the code is not executed in a window or in
+ * case of error, a nsPIDOMWindow otherwise.
+ */
+already_AddRefed<nsPIDOMWindowOuter>
+GetPrivateWindow(JSContext* cx) {
+ nsGlobalWindow* win = xpc::CurrentWindowOrNull(cx);
+ if (!win) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowOuter* outer = win->AsInner()->GetOuterWindow();
+ if (!outer) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> top = outer->GetTop();
+ if (!top) {
+ return nullptr;
+ }
+
+ return top.forget();
+}
+
+bool
+URLForGlobal(JSContext* cx, JS::Handle<JSObject*> global, nsAString& url) {
+ nsCOMPtr<nsIPrincipal> principal = nsContentUtils::ObjectPrincipal(global);
+ if (!principal) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = principal->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv) || !uri) {
+ return false;
+ }
+
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ url.Assign(NS_ConvertUTF8toUTF16(spec));
+ return true;
+}
+
+/**
+ * Extract a somewhat human-readable name from the current context.
+ */
+void
+CompartmentName(JSContext* cx, JS::Handle<JSObject*> global, nsAString& name) {
+ // Attempt to use the URL as name.
+ if (URLForGlobal(cx, global, name)) {
+ return;
+ }
+
+ // Otherwise, fallback to XPConnect's less readable but more
+ // complete naming scheme.
+ nsAutoCString cname;
+ xpc::GetCurrentCompartmentName(cx, cname);
+ name.Assign(NS_ConvertUTF8toUTF16(cname));
+}
+
+/**
+ * Generate a unique-to-the-application identifier for a group.
+ */
+void
+GenerateUniqueGroupId(const JSContext* cx, uint64_t uid, uint64_t processId, nsAString& groupId) {
+ uint64_t contextId = reinterpret_cast<uintptr_t>(cx);
+
+ groupId.AssignLiteral("process: ");
+ groupId.AppendInt(processId);
+ groupId.AppendLiteral(", thread: ");
+ groupId.AppendInt(contextId);
+ groupId.AppendLiteral(", group: ");
+ groupId.AppendInt(uid);
+}
+
+static const char* TOPICS[] = {
+ "profile-before-change",
+ "quit-application",
+ "quit-application-granted",
+ "xpcom-will-shutdown"
+};
+
+} // namespace
+
+/* ------------------------------------------------------
+ *
+ * class nsPerformanceObservationTarget
+ *
+ */
+
+
+NS_IMPL_ISUPPORTS(nsPerformanceObservationTarget, nsIPerformanceObservable)
+
+
+
+NS_IMETHODIMP
+nsPerformanceObservationTarget::GetTarget(nsIPerformanceGroupDetails** _result) {
+ if (mDetails) {
+ NS_IF_ADDREF(*_result = mDetails);
+ }
+ return NS_OK;
+};
+
+void
+nsPerformanceObservationTarget::SetTarget(nsPerformanceGroupDetails* details) {
+ MOZ_ASSERT(!mDetails);
+ mDetails = details;
+};
+
+NS_IMETHODIMP
+nsPerformanceObservationTarget::AddJankObserver(nsIPerformanceObserver* observer) {
+ if (!mObservers.append(observer)) {
+ MOZ_CRASH();
+ }
+ return NS_OK;
+};
+
+NS_IMETHODIMP
+nsPerformanceObservationTarget::RemoveJankObserver(nsIPerformanceObserver* observer) {
+ for (auto iter = mObservers.begin(), end = mObservers.end(); iter < end; ++iter) {
+ if (*iter == observer) {
+ mObservers.erase(iter);
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+};
+
+bool
+nsPerformanceObservationTarget::HasObservers() const {
+ return !mObservers.empty();
+}
+
+void
+nsPerformanceObservationTarget::NotifyJankObservers(nsIPerformanceGroupDetails* source, nsIPerformanceAlert* gravity) {
+ // Copy the vector to make sure that it won't change under our feet.
+ mozilla::Vector<nsCOMPtr<nsIPerformanceObserver>> observers;
+ if (!observers.appendAll(mObservers)) {
+ MOZ_CRASH();
+ }
+
+ // Now actually notify.
+ for (auto iter = observers.begin(), end = observers.end(); iter < end; ++iter) {
+ nsCOMPtr<nsIPerformanceObserver> observer = *iter;
+ mozilla::Unused << observer->Observe(source, gravity);
+ }
+}
+
+/* ------------------------------------------------------
+ *
+ * class nsGroupHolder
+ *
+ */
+
+nsPerformanceObservationTarget*
+nsGroupHolder::ObservationTarget() {
+ if (!mPendingObservationTarget) {
+ mPendingObservationTarget = new nsPerformanceObservationTarget();
+ }
+ return mPendingObservationTarget;
+}
+
+nsPerformanceGroup*
+nsGroupHolder::GetGroup() {
+ return mGroup;
+}
+
+void
+nsGroupHolder::SetGroup(nsPerformanceGroup* group) {
+ MOZ_ASSERT(!mGroup);
+ mGroup = group;
+ group->SetObservationTarget(ObservationTarget());
+ mPendingObservationTarget->SetTarget(group->Details());
+}
+
+/* ------------------------------------------------------
+ *
+ * struct PerformanceData
+ *
+ */
+
+PerformanceData::PerformanceData()
+ : mTotalUserTime(0)
+ , mTotalSystemTime(0)
+ , mTotalCPOWTime(0)
+ , mTicks(0)
+{
+ mozilla::PodArrayZero(mDurations);
+}
+
+/* ------------------------------------------------------
+ *
+ * class nsPerformanceGroupDetails
+ *
+ */
+
+NS_IMPL_ISUPPORTS(nsPerformanceGroupDetails, nsIPerformanceGroupDetails)
+
+const nsAString&
+nsPerformanceGroupDetails::Name() const {
+ return mName;
+}
+
+const nsAString&
+nsPerformanceGroupDetails::GroupId() const {
+ return mGroupId;
+}
+
+const nsAString&
+nsPerformanceGroupDetails::AddonId() const {
+ return mAddonId;
+}
+
+uint64_t
+nsPerformanceGroupDetails::WindowId() const {
+ return mWindowId;
+}
+
+uint64_t
+nsPerformanceGroupDetails::ProcessId() const {
+ return mProcessId;
+}
+
+bool
+nsPerformanceGroupDetails::IsSystem() const {
+ return mIsSystem;
+}
+
+bool
+nsPerformanceGroupDetails::IsAddon() const {
+ return mAddonId.Length() != 0;
+}
+
+bool
+nsPerformanceGroupDetails::IsWindow() const {
+ return mWindowId != 0;
+}
+
+bool
+nsPerformanceGroupDetails::IsContentProcess() const {
+ return XRE_GetProcessType() == GeckoProcessType_Content;
+}
+
+/* readonly attribute AString name; */
+NS_IMETHODIMP
+nsPerformanceGroupDetails::GetName(nsAString& aName) {
+ aName.Assign(Name());
+ return NS_OK;
+};
+
+/* readonly attribute AString groupId; */
+NS_IMETHODIMP
+nsPerformanceGroupDetails::GetGroupId(nsAString& aGroupId) {
+ aGroupId.Assign(GroupId());
+ return NS_OK;
+};
+
+/* readonly attribute AString addonId; */
+NS_IMETHODIMP
+nsPerformanceGroupDetails::GetAddonId(nsAString& aAddonId) {
+ aAddonId.Assign(AddonId());
+ return NS_OK;
+};
+
+/* readonly attribute uint64_t windowId; */
+NS_IMETHODIMP
+nsPerformanceGroupDetails::GetWindowId(uint64_t *aWindowId) {
+ *aWindowId = WindowId();
+ return NS_OK;
+}
+
+/* readonly attribute bool isSystem; */
+NS_IMETHODIMP
+nsPerformanceGroupDetails::GetIsSystem(bool *_retval) {
+ *_retval = IsSystem();
+ return NS_OK;
+}
+
+/*
+ readonly attribute unsigned long long processId;
+*/
+NS_IMETHODIMP
+nsPerformanceGroupDetails::GetProcessId(uint64_t* processId) {
+ *processId = ProcessId();
+ return NS_OK;
+}
+
+/* readonly attribute bool IsContentProcess; */
+NS_IMETHODIMP
+nsPerformanceGroupDetails::GetIsContentProcess(bool *_retval) {
+ *_retval = IsContentProcess();
+ return NS_OK;
+}
+
+
+/* ------------------------------------------------------
+ *
+ * class nsPerformanceStats
+ *
+ */
+
+class nsPerformanceStats final: public nsIPerformanceStats
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPERFORMANCESTATS
+ NS_FORWARD_NSIPERFORMANCEGROUPDETAILS(mDetails->)
+
+ nsPerformanceStats(nsPerformanceGroupDetails* item,
+ const PerformanceData& aPerformanceData)
+ : mDetails(item)
+ , mPerformanceData(aPerformanceData)
+ {
+ }
+
+
+private:
+ RefPtr<nsPerformanceGroupDetails> mDetails;
+ PerformanceData mPerformanceData;
+
+ ~nsPerformanceStats() {}
+};
+
+NS_IMPL_ISUPPORTS(nsPerformanceStats, nsIPerformanceStats, nsIPerformanceGroupDetails)
+
+/* readonly attribute unsigned long long totalUserTime; */
+NS_IMETHODIMP
+nsPerformanceStats::GetTotalUserTime(uint64_t *aTotalUserTime) {
+ *aTotalUserTime = mPerformanceData.mTotalUserTime;
+ return NS_OK;
+};
+
+/* readonly attribute unsigned long long totalSystemTime; */
+NS_IMETHODIMP
+nsPerformanceStats::GetTotalSystemTime(uint64_t *aTotalSystemTime) {
+ *aTotalSystemTime = mPerformanceData.mTotalSystemTime;
+ return NS_OK;
+};
+
+/* readonly attribute unsigned long long totalCPOWTime; */
+NS_IMETHODIMP
+nsPerformanceStats::GetTotalCPOWTime(uint64_t *aCpowTime) {
+ *aCpowTime = mPerformanceData.mTotalCPOWTime;
+ return NS_OK;
+};
+
+/* readonly attribute unsigned long long ticks; */
+NS_IMETHODIMP
+nsPerformanceStats::GetTicks(uint64_t *aTicks) {
+ *aTicks = mPerformanceData.mTicks;
+ return NS_OK;
+};
+
+/* void getDurations (out unsigned long aCount, [array, size_is (aCount), retval] out unsigned long long aNumberOfOccurrences); */
+NS_IMETHODIMP
+nsPerformanceStats::GetDurations(uint32_t *aCount, uint64_t **aNumberOfOccurrences) {
+ const size_t length = mozilla::ArrayLength(mPerformanceData.mDurations);
+ if (aCount) {
+ *aCount = length;
+ }
+ *aNumberOfOccurrences = new uint64_t[length];
+ for (size_t i = 0; i < length; ++i) {
+ (*aNumberOfOccurrences)[i] = mPerformanceData.mDurations[i];
+ }
+ return NS_OK;
+};
+
+
+/* ------------------------------------------------------
+ *
+ * struct nsPerformanceSnapshot
+ *
+ */
+
+class nsPerformanceSnapshot final : public nsIPerformanceSnapshot
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPERFORMANCESNAPSHOT
+
+ nsPerformanceSnapshot() {}
+
+ /**
+ * Append statistics to the list of components data.
+ */
+ void AppendComponentsStats(nsIPerformanceStats* stats);
+
+ /**
+ * Set the statistics attached to process data.
+ */
+ void SetProcessStats(nsIPerformanceStats* group);
+
+private:
+ ~nsPerformanceSnapshot() {}
+
+private:
+ /**
+ * The data for all components.
+ */
+ nsCOMArray<nsIPerformanceStats> mComponentsData;
+
+ /**
+ * The data for the process.
+ */
+ nsCOMPtr<nsIPerformanceStats> mProcessData;
+};
+
+NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot)
+
+
+/* nsIArray getComponentsData (); */
+NS_IMETHODIMP
+nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents)
+{
+ const size_t length = mComponentsData.Length();
+ nsCOMPtr<nsIMutableArray> components = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ for (size_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsIPerformanceStats> stats = mComponentsData[i];
+ mozilla::DebugOnly<nsresult> rv = components->AppendElement(stats, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ components.forget(aComponents);
+ return NS_OK;
+}
+
+/* nsIPerformanceStats getProcessData (); */
+NS_IMETHODIMP
+nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess)
+{
+ NS_IF_ADDREF(*aProcess = mProcessData);
+ return NS_OK;
+}
+
+void
+nsPerformanceSnapshot::AppendComponentsStats(nsIPerformanceStats* stats)
+{
+ mComponentsData.AppendElement(stats);
+}
+
+void
+nsPerformanceSnapshot::SetProcessStats(nsIPerformanceStats* stats)
+{
+ mProcessData = stats;
+}
+
+
+
+/* ------------------------------------------------------
+ *
+ * class PerformanceAlert
+ *
+ */
+class PerformanceAlert final: public nsIPerformanceAlert {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPERFORMANCEALERT
+
+ PerformanceAlert(const uint32_t reason, nsPerformanceGroup* source);
+private:
+ ~PerformanceAlert() {}
+
+ const uint32_t mReason;
+
+ // The highest values reached by this group since the latest alert,
+ // in microseconds.
+ const uint64_t mHighestJank;
+ const uint64_t mHighestCPOW;
+};
+
+NS_IMPL_ISUPPORTS(PerformanceAlert, nsIPerformanceAlert);
+
+PerformanceAlert::PerformanceAlert(const uint32_t reason, nsPerformanceGroup* source)
+ : mReason(reason)
+ , mHighestJank(source->HighestRecentJank())
+ , mHighestCPOW(source->HighestRecentCPOW())
+{ }
+
+NS_IMETHODIMP
+PerformanceAlert::GetHighestJank(uint64_t* result) {
+ *result = mHighestJank;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PerformanceAlert::GetHighestCPOW(uint64_t* result) {
+ *result = mHighestCPOW;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PerformanceAlert::GetReason(uint32_t* result) {
+ *result = mReason;
+ return NS_OK;
+}
+/* ------------------------------------------------------
+ *
+ * class PendingAlertsCollector
+ *
+ */
+
+/**
+ * A timer callback in charge of collecting the groups in
+ * `mPendingAlerts` and triggering dispatch of performance alerts.
+ */
+class PendingAlertsCollector final: public nsITimerCallback {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ explicit PendingAlertsCollector(nsPerformanceStatsService* service)
+ : mService(service)
+ , mPending(false)
+ { }
+
+ nsresult Start(uint32_t timerDelayMS);
+ nsresult Dispose();
+
+private:
+ ~PendingAlertsCollector() {}
+
+ RefPtr<nsPerformanceStatsService> mService;
+ bool mPending;
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ mozilla::Vector<uint64_t> mJankLevels;
+};
+
+NS_IMPL_ISUPPORTS(PendingAlertsCollector, nsITimerCallback);
+
+NS_IMETHODIMP
+PendingAlertsCollector::Notify(nsITimer*) {
+ mPending = false;
+ mService->NotifyJankObservers(mJankLevels);
+ return NS_OK;
+}
+
+nsresult
+PendingAlertsCollector::Start(uint32_t timerDelayMS) {
+ if (mPending) {
+ // Collector is already started.
+ return NS_OK;
+ }
+
+ if (!mTimer) {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+
+ nsresult rv = mTimer->InitWithCallback(this, timerDelayMS, nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mPending = true;
+ {
+ mozilla::DebugOnly<bool> result = nsRefreshDriver::GetJankLevels(mJankLevels);
+ MOZ_ASSERT(result);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+PendingAlertsCollector::Dispose() {
+ if (mTimer) {
+ mozilla::Unused << mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ mService = nullptr;
+ return NS_OK;
+}
+
+
+
+/* ------------------------------------------------------
+ *
+ * class nsPerformanceStatsService
+ *
+ */
+
+NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService, nsIObserver)
+
+nsPerformanceStatsService::nsPerformanceStatsService()
+ : mIsAvailable(false)
+ , mDisposed(false)
+#if defined(XP_WIN)
+ , mProcessId(GetCurrentProcessId())
+#else
+ , mProcessId(getpid())
+#endif
+ , mContext(mozilla::dom::danger::GetJSContext())
+ , mUIdCounter(0)
+ , mTopGroup(nsPerformanceGroup::Make(mContext,
+ this,
+ NS_LITERAL_STRING("<process>"), // name
+ NS_LITERAL_STRING(""), // addonid
+ 0, // windowId
+ mProcessId,
+ true, // isSystem
+ nsPerformanceGroup::GroupScope::RUNTIME // scope
+ ))
+ , mIsHandlingUserInput(false)
+ , mIsMonitoringPerCompartment(false)
+ , mJankAlertThreshold(mozilla::MaxValue<uint64_t>::value) // By default, no alerts
+ , mJankAlertBufferingDelay(1000 /* ms */)
+ , mJankLevelVisibilityThreshold(/* 2 ^ */ 8 /* ms */)
+ , mMaxExpectedDurationOfInteractionUS(150 * 1000)
+{
+ mPendingAlertsCollector = new PendingAlertsCollector(this);
+
+ // Attach some artificial group information to the universal listeners, to aid with debugging.
+ nsString groupIdForAddons;
+ GenerateUniqueGroupId(mContext, GetNextId(), mProcessId, groupIdForAddons);
+ mUniversalTargets.mAddons->
+ SetTarget(new nsPerformanceGroupDetails(NS_LITERAL_STRING("<universal add-on listener>"),
+ groupIdForAddons,
+ NS_LITERAL_STRING("<universal add-on listener>"),
+ 0, // window id
+ mProcessId,
+ false));
+
+
+ nsString groupIdForWindows;
+ GenerateUniqueGroupId(mContext, GetNextId(), mProcessId, groupIdForWindows);
+ mUniversalTargets.mWindows->
+ SetTarget(new nsPerformanceGroupDetails(NS_LITERAL_STRING("<universal window listener>"),
+ groupIdForWindows,
+ NS_LITERAL_STRING("<universal window listener>"),
+ 0, // window id
+ mProcessId,
+ false));
+}
+
+nsPerformanceStatsService::~nsPerformanceStatsService()
+{ }
+
+/**
+ * Clean up the service.
+ *
+ * Called during shutdown. Idempotent.
+ */
+void
+nsPerformanceStatsService::Dispose()
+{
+ // Make sure that we do not accidentally destroy `this` while we are
+ // cleaning up back references.
+ RefPtr<nsPerformanceStatsService> kungFuDeathGrip(this);
+ mIsAvailable = false;
+
+ if (mDisposed) {
+ // Make sure that we don't double-dispose.
+ return;
+ }
+ mDisposed = true;
+
+ // Disconnect from nsIObserverService.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ for (size_t i = 0; i < mozilla::ArrayLength(TOPICS); ++i) {
+ mozilla::Unused << obs->RemoveObserver(this, TOPICS[i]);
+ }
+ }
+
+ // Clear up and disconnect from JSAPI.
+ JSContext* cx = mContext;
+ js::DisposePerformanceMonitoring(cx);
+
+ mozilla::Unused << js::SetStopwatchIsMonitoringCPOW(cx, false);
+ mozilla::Unused << js::SetStopwatchIsMonitoringJank(cx, false);
+
+ mozilla::Unused << js::SetStopwatchStartCallback(cx, nullptr, nullptr);
+ mozilla::Unused << js::SetStopwatchCommitCallback(cx, nullptr, nullptr);
+ mozilla::Unused << js::SetGetPerformanceGroupsCallback(cx, nullptr, nullptr);
+
+ // Clear up and disconnect the alerts collector.
+ if (mPendingAlertsCollector) {
+ mPendingAlertsCollector->Dispose();
+ mPendingAlertsCollector = nullptr;
+ }
+ mPendingAlerts.clear();
+
+ // Disconnect universal observers. Per-group observers will be
+ // disconnected below as part of `group->Dispose()`.
+ mUniversalTargets.mAddons = nullptr;
+ mUniversalTargets.mWindows = nullptr;
+
+ // At this stage, the JS VM may still be holding references to
+ // instances of PerformanceGroup on the stack. To let the service be
+ // collected, we need to break the references from these groups to
+ // `this`.
+ mTopGroup->Dispose();
+ mTopGroup = nullptr;
+
+ // Copy references to the groups to a vector to ensure that we do
+ // not modify the hashtable while iterating it.
+ GroupVector groups;
+ for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
+ if (!groups.append(iter.Get()->GetKey())) {
+ MOZ_CRASH();
+ }
+ }
+ for (auto iter = groups.begin(), end = groups.end(); iter < end; ++iter) {
+ RefPtr<nsPerformanceGroup> group = *iter;
+ group->Dispose();
+ }
+
+ // Any remaining references to PerformanceGroup will be released as
+ // the VM unrolls the stack. If there are any nested event loops,
+ // this may take time.
+}
+
+nsresult
+nsPerformanceStatsService::Init()
+{
+ nsresult rv = InitInternal();
+ if (NS_FAILED(rv)) {
+ // Attempt to clean up.
+ Dispose();
+ }
+ return rv;
+}
+
+nsresult
+nsPerformanceStatsService::InitInternal()
+{
+ // Make sure that we release everything during shutdown.
+ // We are a bit defensive here, as we know that some strange behavior can break the
+ // regular shutdown order.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ for (size_t i = 0; i < mozilla::ArrayLength(TOPICS); ++i) {
+ mozilla::Unused << obs->AddObserver(this, TOPICS[i], false);
+ }
+ }
+
+ // Connect to JSAPI.
+ JSContext* cx = mContext;
+ if (!js::SetStopwatchStartCallback(cx, StopwatchStartCallback, this)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!js::SetStopwatchCommitCallback(cx, StopwatchCommitCallback, this)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!js::SetGetPerformanceGroupsCallback(cx, GetPerformanceGroupsCallback, this)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mTopGroup->setIsActive(true);
+ mIsAvailable = true;
+
+ return NS_OK;
+}
+
+// Observe shutdown events.
+NS_IMETHODIMP
+nsPerformanceStatsService::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ MOZ_ASSERT(strcmp(aTopic, "profile-before-change") == 0
+ || strcmp(aTopic, "quit-application") == 0
+ || strcmp(aTopic, "quit-application-granted") == 0
+ || strcmp(aTopic, "xpcom-will-shutdown") == 0);
+
+ Dispose();
+ return NS_OK;
+}
+
+/*static*/ bool
+nsPerformanceStatsService::IsHandlingUserInput() {
+ if (mozilla::EventStateManager::LatestUserInputStart().IsNull()) {
+ return false;
+ }
+ bool result = mozilla::TimeStamp::Now() - mozilla::EventStateManager::LatestUserInputStart() <= mozilla::TimeDuration::FromMicroseconds(mMaxExpectedDurationOfInteractionUS);
+ return result;
+}
+
+/* [implicit_jscontext] attribute bool isMonitoringCPOW; */
+NS_IMETHODIMP
+nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive)
+{
+ if (!mIsAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aIsStopwatchActive = js::GetStopwatchIsMonitoringCPOW(cx);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool aIsStopwatchActive)
+{
+ if (!mIsAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!js::SetStopwatchIsMonitoringCPOW(cx, aIsStopwatchActive)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+/* [implicit_jscontext] attribute bool isMonitoringJank; */
+NS_IMETHODIMP
+nsPerformanceStatsService::GetIsMonitoringJank(JSContext* cx, bool *aIsStopwatchActive)
+{
+ if (!mIsAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aIsStopwatchActive = js::GetStopwatchIsMonitoringJank(cx);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive)
+{
+ if (!mIsAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!js::SetStopwatchIsMonitoringJank(cx, aIsStopwatchActive)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+/* [implicit_jscontext] attribute bool isMonitoringPerCompartment; */
+NS_IMETHODIMP
+nsPerformanceStatsService::GetIsMonitoringPerCompartment(JSContext*, bool *aIsMonitoringPerCompartment)
+{
+ if (!mIsAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aIsMonitoringPerCompartment = mIsMonitoringPerCompartment;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext*, bool aIsMonitoringPerCompartment)
+{
+ if (!mIsAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aIsMonitoringPerCompartment == mIsMonitoringPerCompartment) {
+ return NS_OK;
+ }
+
+ // Relatively slow update: walk the entire lost of performance groups,
+ // update the active flag of those that have changed.
+ //
+ // Alternative strategies could be envisioned to make the update
+ // much faster, at the expense of the speed of calling `isActive()`,
+ // (e.g. deferring `isActive()` to the nsPerformanceStatsService),
+ // but we expect that `isActive()` can be called thousands of times
+ // per second, while `SetIsMonitoringPerCompartment` is not called
+ // at all during most Firefox runs.
+
+ for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<nsPerformanceGroup> group = iter.Get()->GetKey();
+ if (group->Scope() == nsPerformanceGroup::GroupScope::COMPARTMENT) {
+ group->setIsActive(aIsMonitoringPerCompartment);
+ }
+ }
+ mIsMonitoringPerCompartment = aIsMonitoringPerCompartment;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPerformanceStatsService::GetJankAlertThreshold(uint64_t* result) {
+ *result = mJankAlertThreshold;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPerformanceStatsService::SetJankAlertThreshold(uint64_t value) {
+ mJankAlertThreshold = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPerformanceStatsService::GetJankAlertBufferingDelay(uint32_t* result) {
+ *result = mJankAlertBufferingDelay;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPerformanceStatsService::SetJankAlertBufferingDelay(uint32_t value) {
+ mJankAlertBufferingDelay = value;
+ return NS_OK;
+}
+
+
+/* static */ nsIPerformanceStats*
+nsPerformanceStatsService::GetStatsForGroup(const js::PerformanceGroup* group)
+{
+ return GetStatsForGroup(nsPerformanceGroup::Get(group));
+}
+
+/* static */ nsIPerformanceStats*
+nsPerformanceStatsService::GetStatsForGroup(const nsPerformanceGroup* group)
+{
+ return new nsPerformanceStats(group->Details(), group->data);
+}
+
+/* [implicit_jscontext] nsIPerformanceSnapshot getSnapshot (); */
+NS_IMETHODIMP
+nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot)
+{
+ if (!mIsAvailable) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot();
+ snapshot->SetProcessStats(GetStatsForGroup(mTopGroup));
+
+ for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
+ auto* entry = iter.Get();
+ nsPerformanceGroup* group = entry->GetKey();
+ if (group->isActive()) {
+ snapshot->AppendComponentsStats(GetStatsForGroup(group));
+ }
+ }
+
+ snapshot.forget(aSnapshot);
+
+ return NS_OK;
+}
+
+uint64_t
+nsPerformanceStatsService::GetNextId() {
+ return ++mUIdCounter;
+}
+
+/* static*/ bool
+nsPerformanceStatsService::GetPerformanceGroupsCallback(JSContext* cx,
+ js::PerformanceGroupVector& out,
+ void* closure)
+{
+ RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
+ return self->GetPerformanceGroups(cx, out);
+}
+
+bool
+nsPerformanceStatsService::GetPerformanceGroups(JSContext* cx,
+ js::PerformanceGroupVector& out)
+{
+ JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ if (!global) {
+ // While it is possible for a compartment to have no global
+ // (e.g. atoms), this compartment is not very interesting for us.
+ return true;
+ }
+
+ // All compartments belong to the top group.
+ if (!out.append(mTopGroup)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ nsAutoString name;
+ CompartmentName(cx, global, name);
+ bool isSystem = nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global));
+
+ // Find out if the compartment is executed by an add-on. If so, its
+ // duration should count towards the total duration of the add-on.
+ JSAddonId* jsaddonId = AddonIdOfObject(global);
+ nsString addonId;
+ if (jsaddonId) {
+ AssignJSFlatString(addonId, (JSFlatString*)jsaddonId);
+ auto entry = mAddonIdToGroup.PutEntry(addonId);
+ if (!entry->GetGroup()) {
+ nsString addonName = name;
+ addonName.AppendLiteral(" (as addon ");
+ addonName.Append(addonId);
+ addonName.AppendLiteral(")");
+ entry->
+ SetGroup(nsPerformanceGroup::Make(mContext, this,
+ addonName, addonId, 0,
+ mProcessId, isSystem,
+ nsPerformanceGroup::GroupScope::ADDON)
+ );
+ }
+ if (!out.append(entry->GetGroup())) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ // Find out if the compartment is executed by a window. If so, its
+ // duration should count towards the total duration of the window.
+ uint64_t windowId = 0;
+ if (nsCOMPtr<nsPIDOMWindowOuter> ptop = GetPrivateWindow(cx)) {
+ windowId = ptop->WindowID();
+ auto entry = mWindowIdToGroup.PutEntry(windowId);
+ if (!entry->GetGroup()) {
+ nsString windowName = name;
+ windowName.AppendLiteral(" (as window ");
+ windowName.AppendInt(windowId);
+ windowName.AppendLiteral(")");
+ entry->
+ SetGroup(nsPerformanceGroup::Make(mContext, this,
+ windowName, EmptyString(), windowId,
+ mProcessId, isSystem,
+ nsPerformanceGroup::GroupScope::WINDOW)
+ );
+ }
+ if (!out.append(entry->GetGroup())) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ }
+
+ // All compartments have their own group.
+ auto group =
+ nsPerformanceGroup::Make(mContext, this,
+ name, addonId, windowId,
+ mProcessId, isSystem,
+ nsPerformanceGroup::GroupScope::COMPARTMENT);
+ if (!out.append(group)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ // Returning a vector that is too large would cause allocations all over the
+ // place in the JS engine. We want to be sure that all data is stored inline.
+ MOZ_ASSERT(out.length() <= out.sMaxInlineStorage);
+ return true;
+}
+
+/*static*/ bool
+nsPerformanceStatsService::StopwatchStartCallback(uint64_t iteration, void* closure) {
+ RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
+ return self->StopwatchStart(iteration);
+}
+
+bool
+nsPerformanceStatsService::StopwatchStart(uint64_t iteration) {
+ mIteration = iteration;
+
+ mIsHandlingUserInput = IsHandlingUserInput();
+ mUserInputCount = mozilla::EventStateManager::UserInputCount();
+
+ nsresult rv = GetResources(&mUserTimeStart, &mSystemTimeStart);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return true;
+}
+
+/*static*/ bool
+nsPerformanceStatsService::StopwatchCommitCallback(uint64_t iteration,
+ js::PerformanceGroupVector& recentGroups,
+ void* closure)
+{
+ RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
+ return self->StopwatchCommit(iteration, recentGroups);
+}
+
+bool
+nsPerformanceStatsService::StopwatchCommit(uint64_t iteration,
+ js::PerformanceGroupVector& recentGroups)
+{
+ MOZ_ASSERT(iteration == mIteration);
+ MOZ_ASSERT(!recentGroups.empty());
+
+ uint64_t userTimeStop, systemTimeStop;
+ nsresult rv = GetResources(&userTimeStop, &systemTimeStop);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // `GetResources` is not guaranteed to be monotonic, so round up
+ // any negative result to 0 milliseconds.
+ uint64_t userTimeDelta = 0;
+ if (userTimeStop > mUserTimeStart)
+ userTimeDelta = userTimeStop - mUserTimeStart;
+
+ uint64_t systemTimeDelta = 0;
+ if (systemTimeStop > mSystemTimeStart)
+ systemTimeDelta = systemTimeStop - mSystemTimeStart;
+
+ MOZ_ASSERT(mTopGroup->isUsedInThisIteration());
+ const uint64_t totalRecentCycles = mTopGroup->recentCycles(iteration);
+
+ const bool isHandlingUserInput = mIsHandlingUserInput || mozilla::EventStateManager::UserInputCount() > mUserInputCount;
+
+ // We should only reach this stage if `group` has had some activity.
+ MOZ_ASSERT(mTopGroup->recentTicks(iteration) > 0);
+ for (auto iter = recentGroups.begin(), end = recentGroups.end(); iter != end; ++iter) {
+ RefPtr<nsPerformanceGroup> group = nsPerformanceGroup::Get(*iter);
+ CommitGroup(iteration, userTimeDelta, systemTimeDelta, totalRecentCycles, isHandlingUserInput, group);
+ }
+
+ // Make sure that `group` was treated along with the other items of `recentGroups`.
+ MOZ_ASSERT(!mTopGroup->isUsedInThisIteration());
+ MOZ_ASSERT(mTopGroup->recentTicks(iteration) == 0);
+
+ if (!mPendingAlerts.empty()) {
+ mPendingAlertsCollector->Start(mJankAlertBufferingDelay);
+ }
+
+ return true;
+}
+
+void
+nsPerformanceStatsService::CommitGroup(uint64_t iteration,
+ uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta,
+ uint64_t totalCyclesDelta,
+ bool isHandlingUserInput,
+ nsPerformanceGroup* group) {
+
+ MOZ_ASSERT(group->isUsedInThisIteration());
+
+ const uint64_t ticksDelta = group->recentTicks(iteration);
+ const uint64_t cpowTimeDelta = group->recentCPOW(iteration);
+ const uint64_t cyclesDelta = group->recentCycles(iteration);
+ group->resetRecentData();
+
+ // We have now performed all cleanup and may `return` at any time without fear of leaks.
+
+ if (group->iteration() != iteration) {
+ // Stale data, don't commit.
+ return;
+ }
+
+ // When we add a group as changed, we immediately set its
+ // `recentTicks` from 0 to 1. If we have `ticksDelta == 0` at
+ // this stage, we have already called `resetRecentData` but we
+ // haven't removed it from the list.
+ MOZ_ASSERT(ticksDelta != 0);
+ MOZ_ASSERT(cyclesDelta <= totalCyclesDelta);
+ if (cyclesDelta == 0 || totalCyclesDelta == 0) {
+ // Nothing useful, don't commit.
+ return;
+ }
+
+ double proportion = (double)cyclesDelta / (double)totalCyclesDelta;
+ MOZ_ASSERT(proportion <= 1);
+
+ const uint64_t userTimeDelta = proportion * totalUserTimeDelta;
+ const uint64_t systemTimeDelta = proportion * totalSystemTimeDelta;
+
+ group->data.mTotalUserTime += userTimeDelta;
+ group->data.mTotalSystemTime += systemTimeDelta;
+ group->data.mTotalCPOWTime += cpowTimeDelta;
+ group->data.mTicks += ticksDelta;
+
+ const uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta + cpowTimeDelta;
+ uint64_t duration = 1000; // 1ms in µs
+ for (size_t i = 0;
+ i < mozilla::ArrayLength(group->data.mDurations) && duration < totalTimeDelta;
+ ++i, duration *= 2) {
+ group->data.mDurations[i]++;
+ }
+
+ group->RecordJank(totalTimeDelta);
+ group->RecordCPOW(cpowTimeDelta);
+ if (isHandlingUserInput) {
+ group->RecordUserInput();
+ }
+
+ if (totalTimeDelta >= mJankAlertThreshold) {
+ if (!group->HasPendingAlert()) {
+ if (mPendingAlerts.append(group)) {
+ group->SetHasPendingAlert(true);
+ }
+ return;
+ }
+ }
+
+ return;
+}
+
+nsresult
+nsPerformanceStatsService::GetResources(uint64_t* userTime,
+ uint64_t* systemTime) const {
+ MOZ_ASSERT(userTime);
+ MOZ_ASSERT(systemTime);
+
+#if defined(XP_UNIX)
+ struct rusage rusage;
+#if defined(RUSAGE_THREAD)
+ // Under Linux, we can obtain per-thread statistics
+ int err = getrusage(RUSAGE_THREAD, &rusage);
+#else
+ // Under other Unices, we need to do with more noisy
+ // per-process statistics.
+ int err = getrusage(RUSAGE_SELF, &rusage);
+#endif // defined(RUSAGE_THREAD)
+
+ if (err)
+ return NS_ERROR_FAILURE;
+
+ *userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000;
+ *systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000;
+
+#elif defined(XP_WIN)
+ // Under Windows, we can obtain per-thread statistics. Experience
+ // seems to suggest that they are not very accurate under Windows
+ // XP, though.
+ FILETIME creationFileTime; // Ignored
+ FILETIME exitFileTime; // Ignored
+ FILETIME kernelFileTime;
+ FILETIME userFileTime;
+ BOOL success = GetThreadTimes(GetCurrentThread(),
+ &creationFileTime, &exitFileTime,
+ &kernelFileTime, &userFileTime);
+
+ if (!success)
+ return NS_ERROR_FAILURE;
+
+ ULARGE_INTEGER kernelTimeInt;
+ kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
+ kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
+ // Convert 100 ns to 1 us.
+ *systemTime = kernelTimeInt.QuadPart / 10;
+
+ ULARGE_INTEGER userTimeInt;
+ userTimeInt.LowPart = userFileTime.dwLowDateTime;
+ userTimeInt.HighPart = userFileTime.dwHighDateTime;
+ // Convert 100 ns to 1 us.
+ *userTime = userTimeInt.QuadPart / 10;
+
+#endif // defined(XP_UNIX) || defined(XP_WIN)
+
+ return NS_OK;
+}
+
+void
+nsPerformanceStatsService::NotifyJankObservers(const mozilla::Vector<uint64_t>& aPreviousJankLevels) {
+
+ // The move operation is generally constant time, unless
+ // `mPendingAlerts.length()` is very small, in which case it's fast anyway.
+ GroupVector alerts(Move(mPendingAlerts));
+ mPendingAlerts = GroupVector(); // Reconstruct after `Move`.
+
+ if (!mPendingAlertsCollector) {
+ // We are shutting down.
+ return;
+ }
+
+ // Find out if we have noticed any user-noticeable delay in an
+ // animation recently (i.e. since the start of the execution of JS
+ // code that caused this collector to start). If so, we'll mark any
+ // alert as part of a user-noticeable jank. Note that this doesn't
+ // mean with any certainty that the alert is the only cause of jank,
+ // or even the main cause of jank.
+ mozilla::Vector<uint64_t> latestJankLevels;
+ {
+ mozilla::DebugOnly<bool> result = nsRefreshDriver::GetJankLevels(latestJankLevels);
+ MOZ_ASSERT(result);
+ }
+ MOZ_ASSERT(latestJankLevels.length() == aPreviousJankLevels.length());
+
+ bool isJankInAnimation = false;
+ for (size_t i = mJankLevelVisibilityThreshold; i < latestJankLevels.length(); ++i) {
+ if (latestJankLevels[i] > aPreviousJankLevels[i]) {
+ isJankInAnimation = true;
+ break;
+ }
+ }
+
+ MOZ_ASSERT(!alerts.empty());
+ const bool hasUniversalAddonObservers = mUniversalTargets.mAddons->HasObservers();
+ const bool hasUniversalWindowObservers = mUniversalTargets.mWindows->HasObservers();
+ for (auto iter = alerts.begin(); iter < alerts.end(); ++iter) {
+ MOZ_ASSERT(iter);
+ RefPtr<nsPerformanceGroup> group = *iter;
+ group->SetHasPendingAlert(false);
+
+ RefPtr<nsPerformanceGroupDetails> details = group->Details();
+ nsPerformanceObservationTarget* targets[3] = {
+ hasUniversalAddonObservers && details->IsAddon() ? mUniversalTargets.mAddons.get() : nullptr,
+ hasUniversalWindowObservers && details->IsWindow() ? mUniversalTargets.mWindows.get() : nullptr,
+ group->ObservationTarget()
+ };
+
+ bool isJankInInput = group->HasRecentUserInput();
+
+ RefPtr<PerformanceAlert> alert;
+ for (nsPerformanceObservationTarget* target : targets) {
+ if (!target) {
+ continue;
+ }
+ if (!alert) {
+ const uint32_t reason = nsIPerformanceAlert::REASON_SLOWDOWN
+ | (isJankInAnimation ? nsIPerformanceAlert::REASON_JANK_IN_ANIMATION : 0)
+ | (isJankInInput ? nsIPerformanceAlert::REASON_JANK_IN_INPUT : 0);
+ // Wait until we are sure we need to allocate before we allocate.
+ alert = new PerformanceAlert(reason, group);
+ }
+ target->NotifyJankObservers(details, alert);
+ }
+
+ group->ResetRecent();
+ }
+
+}
+
+NS_IMETHODIMP
+nsPerformanceStatsService::GetObservableAddon(const nsAString& addonId,
+ nsIPerformanceObservable** result) {
+ if (addonId.Equals(NS_LITERAL_STRING("*"))) {
+ NS_IF_ADDREF(*result = mUniversalTargets.mAddons);
+ } else {
+ auto entry = mAddonIdToGroup.PutEntry(addonId);
+ NS_IF_ADDREF(*result = entry->ObservationTarget());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPerformanceStatsService::GetObservableWindow(uint64_t windowId,
+ nsIPerformanceObservable** result) {
+ if (windowId == 0) {
+ NS_IF_ADDREF(*result = mUniversalTargets.mWindows);
+ } else {
+ auto entry = mWindowIdToGroup.PutEntry(windowId);
+ NS_IF_ADDREF(*result = entry->ObservationTarget());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPerformanceStatsService::GetAnimationJankLevelThreshold(short* result) {
+ *result = mJankLevelVisibilityThreshold;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPerformanceStatsService::SetAnimationJankLevelThreshold(short value) {
+ mJankLevelVisibilityThreshold = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPerformanceStatsService::GetUserInputDelayThreshold(uint64_t* result) {
+ *result = mMaxExpectedDurationOfInteractionUS;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPerformanceStatsService::SetUserInputDelayThreshold(uint64_t value) {
+ mMaxExpectedDurationOfInteractionUS = value;
+ return NS_OK;
+}
+
+
+
+nsPerformanceStatsService::UniversalTargets::UniversalTargets()
+ : mAddons(new nsPerformanceObservationTarget())
+ , mWindows(new nsPerformanceObservationTarget())
+{ }
+
+/* ------------------------------------------------------
+ *
+ * Class nsPerformanceGroup
+ *
+ */
+
+/*static*/ nsPerformanceGroup*
+nsPerformanceGroup::Make(JSContext* cx,
+ nsPerformanceStatsService* service,
+ const nsAString& name,
+ const nsAString& addonId,
+ uint64_t windowId,
+ uint64_t processId,
+ bool isSystem,
+ GroupScope scope)
+{
+ nsString groupId;
+ ::GenerateUniqueGroupId(cx, service->GetNextId(), processId, groupId);
+ return new nsPerformanceGroup(service, name, groupId, addonId, windowId, processId, isSystem, scope);
+}
+
+nsPerformanceGroup::nsPerformanceGroup(nsPerformanceStatsService* service,
+ const nsAString& name,
+ const nsAString& groupId,
+ const nsAString& addonId,
+ uint64_t windowId,
+ uint64_t processId,
+ bool isSystem,
+ GroupScope scope)
+ : mDetails(new nsPerformanceGroupDetails(name, groupId, addonId, windowId, processId, isSystem))
+ , mService(service)
+ , mScope(scope)
+ , mHighestJank(0)
+ , mHighestCPOW(0)
+ , mHasRecentUserInput(false)
+ , mHasPendingAlert(false)
+{
+ mozilla::Unused << mService->mGroups.PutEntry(this);
+
+#if defined(DEBUG)
+ if (scope == GroupScope::ADDON) {
+ MOZ_ASSERT(mDetails->IsAddon());
+ MOZ_ASSERT(!mDetails->IsWindow());
+ } else if (scope == GroupScope::WINDOW) {
+ MOZ_ASSERT(mDetails->IsWindow());
+ MOZ_ASSERT(!mDetails->IsAddon());
+ } else if (scope == GroupScope::RUNTIME) {
+ MOZ_ASSERT(!mDetails->IsWindow());
+ MOZ_ASSERT(!mDetails->IsAddon());
+ }
+#endif // defined(DEBUG)
+ setIsActive(mScope != GroupScope::COMPARTMENT || mService->mIsMonitoringPerCompartment);
+}
+
+void
+nsPerformanceGroup::Dispose() {
+ if (!mService) {
+ // We have already called `Dispose()`.
+ return;
+ }
+ if (mObservationTarget) {
+ mObservationTarget = nullptr;
+ }
+
+ // Remove any reference to the service.
+ RefPtr<nsPerformanceStatsService> service;
+ service.swap(mService);
+
+ // Remove any dangling pointer to `this`.
+ service->mGroups.RemoveEntry(this);
+
+ if (mScope == GroupScope::ADDON) {
+ MOZ_ASSERT(mDetails->IsAddon());
+ service->mAddonIdToGroup.RemoveEntry(mDetails->AddonId());
+ } else if (mScope == GroupScope::WINDOW) {
+ MOZ_ASSERT(mDetails->IsWindow());
+ service->mWindowIdToGroup.RemoveEntry(mDetails->WindowId());
+ }
+}
+
+nsPerformanceGroup::~nsPerformanceGroup() {
+ Dispose();
+}
+
+nsPerformanceGroup::GroupScope
+nsPerformanceGroup::Scope() const {
+ return mScope;
+}
+
+nsPerformanceGroupDetails*
+nsPerformanceGroup::Details() const {
+ return mDetails;
+}
+
+void
+nsPerformanceGroup::SetObservationTarget(nsPerformanceObservationTarget* target) {
+ MOZ_ASSERT(!mObservationTarget);
+ mObservationTarget = target;
+}
+
+nsPerformanceObservationTarget*
+nsPerformanceGroup::ObservationTarget() const {
+ return mObservationTarget;
+}
+
+bool
+nsPerformanceGroup::HasPendingAlert() const {
+ return mHasPendingAlert;
+}
+
+void
+nsPerformanceGroup::SetHasPendingAlert(bool value) {
+ mHasPendingAlert = value;
+}
+
+
+void
+nsPerformanceGroup::RecordJank(uint64_t jank) {
+ if (jank > mHighestJank) {
+ mHighestJank = jank;
+ }
+}
+
+void
+nsPerformanceGroup::RecordCPOW(uint64_t cpow) {
+ if (cpow > mHighestCPOW) {
+ mHighestCPOW = cpow;
+ }
+}
+
+uint64_t
+nsPerformanceGroup::HighestRecentJank() {
+ return mHighestJank;
+}
+
+uint64_t
+nsPerformanceGroup::HighestRecentCPOW() {
+ return mHighestCPOW;
+}
+
+bool
+nsPerformanceGroup::HasRecentUserInput() {
+ return mHasRecentUserInput;
+}
+
+void
+nsPerformanceGroup::RecordUserInput() {
+ mHasRecentUserInput = true;
+}
+
+void
+nsPerformanceGroup::ResetRecent() {
+ mHighestJank = 0;
+ mHighestCPOW = 0;
+ mHasRecentUserInput = false;
+}
diff --git a/components/perfmonitoring/nsPerformanceStats.h b/components/perfmonitoring/nsPerformanceStats.h
new file mode 100644
index 000000000..661a78a1a
--- /dev/null
+++ b/components/perfmonitoring/nsPerformanceStats.h
@@ -0,0 +1,810 @@
+/* -*- 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/. */
+
+#ifndef nsPerformanceStats_h
+#define nsPerformanceStats_h
+
+#include "jsapi.h"
+
+#include "nsHashKeys.h"
+#include "nsTHashtable.h"
+
+#include "nsIObserver.h"
+#include "nsPIDOMWindow.h"
+
+#include "nsIPerformanceStats.h"
+
+class nsPerformanceGroup;
+class nsPerformanceGroupDetails;
+
+typedef mozilla::Vector<RefPtr<nsPerformanceGroup>, 8> GroupVector;
+
+/**
+ * A data structure for registering observers interested in
+ * performance alerts.
+ *
+ * Each performance group owns a single instance of this class.
+ * Additionally, the service owns instances designed to observe the
+ * performance alerts in all add-ons (respectively webpages).
+ */
+class nsPerformanceObservationTarget final: public nsIPerformanceObservable {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPERFORMANCEOBSERVABLE
+
+ /**
+ * `true` if this target has at least once performance observer
+ * registered, `false` otherwise.
+ */
+ bool HasObservers() const;
+
+ /**
+ * Notify all the observers that jank has happened.
+ */
+ void NotifyJankObservers(nsIPerformanceGroupDetails* source, nsIPerformanceAlert* gravity);
+
+ /**
+ * Set the details on the group being observed.
+ */
+ void SetTarget(nsPerformanceGroupDetails* details);
+
+private:
+ ~nsPerformanceObservationTarget() {}
+
+ // The observers for this target. We hold them as a vector, despite
+ // the linear removal cost, as we expect that the typical number of
+ // observers will be lower than 3, and that (un)registrations will
+ // be fairly infrequent.
+ mozilla::Vector<nsCOMPtr<nsIPerformanceObserver>> mObservers;
+
+ // Details on the group being observed. May be `nullptr`.
+ RefPtr<nsPerformanceGroupDetails> mDetails;
+};
+
+/**
+ * The base class for entries of maps from addon id/window id to
+ * performance group.
+ *
+ * Performance observers may be registered before their group is
+ * created (e.g., one may register an observer for an add-on before
+ * all its modules are loaded, or even before the add-on is loaded at
+ * all or for an observer for a webpage before all its iframes are
+ * loaded). This class serves to hold the observation target until the
+ * performance group may be created, and then to associate the
+ * observation target and the performance group.
+ */
+class nsGroupHolder {
+public:
+ nsGroupHolder()
+ : mGroup(nullptr)
+ , mPendingObservationTarget(nullptr)
+ { }
+
+ /**
+ * Get the observation target, creating it if necessary.
+ */
+ nsPerformanceObservationTarget* ObservationTarget();
+
+ /**
+ * Get the group, if it has been created.
+ *
+ * May return `null` if the group hasn't been created yet.
+ */
+ class nsPerformanceGroup* GetGroup();
+
+ /**
+ * Set the group.
+ *
+ * Once this method has been called, calling
+ * `this->ObservationTarget()` and `group->ObservationTarget()` is equivalent.
+ *
+ * Must only be called once.
+ */
+ void SetGroup(class nsPerformanceGroup*);
+private:
+ // The group. Initially `nullptr`, until we have called `SetGroup`.
+ class nsPerformanceGroup* mGroup;
+
+ // The observation target. Instantiated by the first call to
+ // `ObservationTarget()`.
+ RefPtr<nsPerformanceObservationTarget> mPendingObservationTarget;
+};
+
+/**
+ * An implementation of the nsIPerformanceStatsService.
+ *
+ * Note that this implementation is not thread-safe.
+ */
+class nsPerformanceStatsService final : public nsIPerformanceStatsService,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPERFORMANCESTATSSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsPerformanceStatsService();
+ nsresult Init();
+
+private:
+ nsresult InitInternal();
+ void Dispose();
+ ~nsPerformanceStatsService();
+
+protected:
+ friend nsPerformanceGroup;
+
+ /**
+ * `false` until `Init()` and after `Dispose()`, `true` inbetween.
+ */
+ bool mIsAvailable;
+
+ /**
+ * `true` once we have called `Dispose()`.
+ */
+ bool mDisposed;
+
+ /**
+ * A unique identifier for the process.
+ *
+ * Process HANDLE under Windows, pid under Unix.
+ */
+ const uint64_t mProcessId;
+
+ /**
+ * The JS Context for the main thread.
+ */
+ JSContext* const mContext;
+
+ /**
+ * Generate unique identifiers.
+ */
+ uint64_t GetNextId();
+ uint64_t mUIdCounter;
+
+
+
+ /**
+ * Extract a snapshot of performance statistics from a performance group.
+ */
+ static nsIPerformanceStats* GetStatsForGroup(const js::PerformanceGroup* group);
+ static nsIPerformanceStats* GetStatsForGroup(const nsPerformanceGroup* group);
+
+
+
+ /**
+ * Get the performance groups associated to a given JS compartment.
+ *
+ * A compartment is typically associated to the following groups:
+ * - the top group, shared by the entire process;
+ * - the window group, if the code is executed in a window, shared
+ * by all compartments for that window (typically, all frames);
+ * - the add-on group, if the code is executed as an add-on, shared
+ * by all compartments for that add-on (typically, all modules);
+ * - the compartment's own group.
+ *
+ * Pre-condition: the VM must have entered the JS compartment.
+ *
+ * The caller is expected to cache the results of this method, as
+ * calling it more than once may not return the same instances of
+ * performance groups.
+ */
+ bool GetPerformanceGroups(JSContext* cx, js::PerformanceGroupVector&);
+ static bool GetPerformanceGroupsCallback(JSContext* cx, js::PerformanceGroupVector&, void* closure);
+
+
+
+ /**********************************************************
+ *
+ * Sets of all performance groups, indexed by several keys.
+ *
+ * These sets do not keep the performance groups alive. Rather, a
+ * performance group is inserted in the relevant sets upon
+ * construction and removed from the sets upon destruction or when
+ * we Dispose() of the service.
+ *
+ * A `nsPerformanceGroup` is typically kept alive (as a
+ * `js::PerformanceGroup`) by the JSCompartment to which it is
+ * associated. It may also temporarily be kept alive by the JS
+ * stack, in particular in case of nested event loops.
+ */
+
+ /**
+ * Set of performance groups associated to add-ons, indexed
+ * by add-on id. Each item is shared by all the compartments
+ * that belong to the add-on.
+ */
+ struct AddonIdToGroup: public nsStringHashKey,
+ public nsGroupHolder {
+ explicit AddonIdToGroup(const nsAString* key)
+ : nsStringHashKey(key)
+ { }
+ };
+ nsTHashtable<AddonIdToGroup> mAddonIdToGroup;
+
+ /**
+ * Set of performance groups associated to windows, indexed by outer
+ * window id. Each item is shared by all the compartments that
+ * belong to the window.
+ */
+ struct WindowIdToGroup: public nsUint64HashKey,
+ public nsGroupHolder {
+ explicit WindowIdToGroup(const uint64_t* key)
+ : nsUint64HashKey(key)
+ {}
+ };
+ nsTHashtable<WindowIdToGroup> mWindowIdToGroup;
+
+ /**
+ * Set of all performance groups.
+ */
+ struct Groups: public nsPtrHashKey<nsPerformanceGroup> {
+ explicit Groups(const nsPerformanceGroup* key)
+ : nsPtrHashKey<nsPerformanceGroup>(key)
+ {}
+ };
+ nsTHashtable<Groups> mGroups;
+
+ /**
+ * The performance group representing the runtime itself. All
+ * compartments are associated to this group.
+ */
+ RefPtr<nsPerformanceGroup> mTopGroup;
+
+ /**********************************************************
+ *
+ * Measuring and recording the CPU use of the system.
+ *
+ */
+
+ /**
+ * Get the OS-reported time spent in userland/systemland, in
+ * microseconds. On most platforms, this data is per-thread,
+ * but on some platforms we need to fall back to per-process.
+ *
+ * Data is not guaranteed to be monotonic.
+ */
+ nsresult GetResources(uint64_t* userTime, uint64_t* systemTime) const;
+
+ /**
+ * Amount of user/system CPU time used by the thread (or process,
+ * for platforms that don't support per-thread measure) since start.
+ * Updated by `StopwatchStart` at most once per event.
+ *
+ * Unit: microseconds.
+ */
+ uint64_t mUserTimeStart;
+ uint64_t mSystemTimeStart;
+
+ bool mIsHandlingUserInput;
+
+ /**
+ * The number of user inputs since the start of the process. Used to
+ * determine whether the current iteration has triggered a
+ * (JS-implemented) user input.
+ */
+ uint64_t mUserInputCount;
+
+ /**********************************************************
+ *
+ * Callbacks triggered by the JS VM when execution of JavaScript
+ * code starts/completes.
+ *
+ * As measures of user CPU time/system CPU time have low resolution
+ * (and are somewhat slow), we measure both only during the calls to
+ * `StopwatchStart`/`StopwatchCommit` and we make the assumption
+ * that each group's user/system CPU time is proportional to the
+ * number of clock cycles spent executing code in the group between
+ * `StopwatchStart`/`StopwatchCommit`.
+ *
+ * The results may be skewed by the thread being rescheduled to a
+ * different CPU during the measure, but we expect that on average,
+ * the skew will have limited effects, and will generally tend to
+ * make already-slow executions appear slower.
+ */
+
+ /**
+ * Execution of JavaScript code has started. This may happen several
+ * times in succession if the JavaScript code contains nested event
+ * loops, in which case only the innermost call will receive
+ * `StopwatchCommitCallback`.
+ *
+ * @param iteration The number of times we have started executing
+ * JavaScript code.
+ */
+ static bool StopwatchStartCallback(uint64_t iteration, void* closure);
+ bool StopwatchStart(uint64_t iteration);
+
+ /**
+ * Execution of JavaScript code has reached completion (including
+ * enqueued microtasks). In cse of tested event loops, any ongoing
+ * measurement on outer loops is silently cancelled without any call
+ * to this method.
+ *
+ * @param iteration The number of times we have started executing
+ * JavaScript code.
+ * @param recentGroups The groups that have seen activity during this
+ * event.
+ */
+ static bool StopwatchCommitCallback(uint64_t iteration,
+ js::PerformanceGroupVector& recentGroups,
+ void* closure);
+ bool StopwatchCommit(uint64_t iteration, js::PerformanceGroupVector& recentGroups);
+
+ /**
+ * The number of times we have started executing JavaScript code.
+ */
+ uint64_t mIteration;
+
+ /**
+ * Commit performance measures of a single group.
+ *
+ * Data is transfered from `group->recent*` to `group->data`.
+ *
+ *
+ * @param iteration The current iteration.
+ * @param userTime The total user CPU time for this thread (or
+ * process, if per-thread data is not available) between the
+ * calls to `StopwatchStart` and `StopwatchCommit`.
+ * @param systemTime The total system CPU time for this thread (or
+ * process, if per-thread data is not available) between the
+ * calls to `StopwatchStart` and `StopwatchCommit`.
+ * @param cycles The total number of cycles for this thread
+ * between the calls to `StopwatchStart` and `StopwatchCommit`.
+ * @param isJankVisible If `true`, expect that the user will notice
+ * any slowdown.
+ * @param group The group containing the data to commit.
+ */
+ void CommitGroup(uint64_t iteration,
+ uint64_t userTime, uint64_t systemTime, uint64_t cycles,
+ bool isJankVisible,
+ nsPerformanceGroup* group);
+
+
+ /**********************************************************
+ *
+ * Options controlling measurements.
+ */
+
+ /**
+ * Determine if we are measuring the performance of every individual
+ * compartment (in particular, every individual module, frame,
+ * sandbox). Note that this makes measurements noticeably slower.
+ */
+ bool mIsMonitoringPerCompartment;
+
+
+ /**********************************************************
+ *
+ * Determining whether jank is user-visible.
+ */
+
+ /**
+ * `true` if we believe that any slowdown can cause a noticeable
+ * delay in handling user-input.
+ *
+ * In the current implementation, we return `true` if the latest
+ * user input was less than MAX_DURATION_OF_INTERACTION_MS ago. This
+ * includes all inputs (mouse, keyboard, other devices), with the
+ * exception of mousemove.
+ */
+ bool IsHandlingUserInput();
+
+
+public:
+ /**********************************************************
+ *
+ * Letting observers register themselves to watch for performance
+ * alerts.
+ *
+ * To avoid saturating clients with alerts (or even creating loops
+ * of alerts), each alert is buffered. At the end of each iteration
+ * of the event loop, groups that have caused performance alerts
+ * are registered in a set of pending alerts, and the collection
+ * timer hasn't been started yet, it is started. Once the timer
+ * firers, we gather all the pending alerts, empty the set and
+ * dispatch to observers.
+ */
+
+ /**
+ * Clear the set of pending alerts and dispatch the pending alerts
+ * to observers.
+ */
+ void NotifyJankObservers(const mozilla::Vector<uint64_t>& previousJankLevels);
+
+private:
+ /**
+ * The set of groups for which we know that an alert should be
+ * raised. This set is cleared once `mPendingAlertsCollector`
+ * fires.
+ *
+ * Invariant: no group may appear twice in this vector.
+ */
+ GroupVector mPendingAlerts;
+
+ /**
+ * A timer callback in charge of collecting the groups in
+ * `mPendingAlerts` and triggering `NotifyJankObservers` to dispatch
+ * performance alerts.
+ */
+ RefPtr<class PendingAlertsCollector> mPendingAlertsCollector;
+
+
+ /**
+ * Observation targets that are not attached to a specific group.
+ */
+ struct UniversalTargets {
+ UniversalTargets();
+ /**
+ * A target for observers interested in watching all addons.
+ */
+ RefPtr<nsPerformanceObservationTarget> mAddons;
+
+ /**
+ * A target for observers interested in watching all windows.
+ */
+ RefPtr<nsPerformanceObservationTarget> mWindows;
+ };
+ UniversalTargets mUniversalTargets;
+
+ /**
+ * The threshold, in microseconds, above which a performance group is
+ * considered "slow" and should raise performance alerts.
+ */
+ uint64_t mJankAlertThreshold;
+
+ /**
+ * A buffering delay, in milliseconds, used by the service to
+ * regroup performance alerts, before observers are actually
+ * noticed. Higher delays let the system avoid redundant
+ * notifications for the same group, and are generally better for
+ * performance.
+ */
+ uint32_t mJankAlertBufferingDelay;
+
+ /**
+ * The threshold above which jank, as reported by the refresh drivers,
+ * is considered user-visible.
+ *
+ * A value of n means that any jank above 2^n ms will be considered
+ * user visible.
+ */
+ short mJankLevelVisibilityThreshold;
+
+ /**
+ * The number of microseconds during which we assume that a
+ * user-interaction can keep the code jank-critical. Any user
+ * interaction that lasts longer than this duration is expected to
+ * either have already caused jank or have caused a nested event
+ * loop.
+ *
+ * In either case, we consider that monitoring
+ * jank-during-interaction after this duration is useless.
+ */
+ uint64_t mMaxExpectedDurationOfInteractionUS;
+};
+
+
+
+/**
+ * Container for performance data.
+ *
+ * All values are monotonic.
+ *
+ * All values are updated after running to completion.
+ */
+struct PerformanceData {
+ /**
+ * Number of times we have spent at least 2^n consecutive
+ * milliseconds executing code in this group.
+ * durations[0] is increased whenever we spend at least 1 ms
+ * executing code in this group
+ * durations[1] whenever we spend 2ms+
+ * ...
+ * durations[i] whenever we spend 2^ims+
+ */
+ uint64_t mDurations[10];
+
+ /**
+ * Total amount of time spent executing code in this group, in
+ * microseconds.
+ */
+ uint64_t mTotalUserTime;
+ uint64_t mTotalSystemTime;
+ uint64_t mTotalCPOWTime;
+
+ /**
+ * Total number of times code execution entered this group, since
+ * process launch. This may be greater than the number of times we
+ * have entered the event loop.
+ */
+ uint64_t mTicks;
+
+ PerformanceData();
+ PerformanceData(const PerformanceData& from) = default;
+ PerformanceData& operator=(const PerformanceData& from) = default;
+};
+
+
+
+/**
+ * Identification information for an item that can hold performance
+ * data.
+ */
+class nsPerformanceGroupDetails final: public nsIPerformanceGroupDetails {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPERFORMANCEGROUPDETAILS
+
+ nsPerformanceGroupDetails(const nsAString& aName,
+ const nsAString& aGroupId,
+ const nsAString& aAddonId,
+ const uint64_t aWindowId,
+ const uint64_t aProcessId,
+ const bool aIsSystem)
+ : mName(aName)
+ , mGroupId(aGroupId)
+ , mAddonId(aAddonId)
+ , mWindowId(aWindowId)
+ , mProcessId(aProcessId)
+ , mIsSystem(aIsSystem)
+ { }
+public:
+ const nsAString& Name() const;
+ const nsAString& GroupId() const;
+ const nsAString& AddonId() const;
+ uint64_t WindowId() const;
+ uint64_t ProcessId() const;
+ bool IsAddon() const;
+ bool IsWindow() const;
+ bool IsSystem() const;
+ bool IsContentProcess() const;
+private:
+ ~nsPerformanceGroupDetails() {}
+
+ const nsString mName;
+ const nsString mGroupId;
+ const nsString mAddonId;
+ const uint64_t mWindowId;
+ const uint64_t mProcessId;
+ const bool mIsSystem;
+};
+
+/**
+ * The kind of compartments represented by this group.
+ */
+enum class PerformanceGroupScope {
+ /**
+ * This group represents the entire runtime (i.e. the thread).
+ */
+ RUNTIME,
+
+ /**
+ * This group represents all the compartments executed in a window.
+ */
+ WINDOW,
+
+ /**
+ * This group represents all the compartments provided by an addon.
+ */
+ ADDON,
+
+ /**
+ * This group represents a single compartment.
+ */
+ COMPARTMENT,
+};
+
+/**
+ * A concrete implementation of `js::PerformanceGroup`, also holding
+ * performance data. Instances may represent individual compartments,
+ * windows, addons or the entire runtime.
+ *
+ * This class is intended to be the sole implementation of
+ * `js::PerformanceGroup`.
+ */
+class nsPerformanceGroup final: public js::PerformanceGroup {
+public:
+
+ // Ideally, we would define the enum class in nsPerformanceGroup,
+ // but this seems to choke some versions of gcc.
+ typedef PerformanceGroupScope GroupScope;
+
+ /**
+ * Construct a performance group.
+ *
+ * @param cx The container context. Used to generate a unique identifier.
+ * @param service The performance service. Used during destruction to
+ * cleanup the hash tables.
+ * @param name A name for the group, designed mostly for debugging purposes,
+ * so it should be at least somewhat human-readable.
+ * @param addonId The identifier of the add-on. Should be "" when the
+ * group is not part of an add-on,
+ * @param windowId The identifier of the window. Should be 0 when the
+ * group is not part of a window.
+ * @param processId A unique identifier for the process.
+ * @param isSystem `true` if the code of the group is executed with
+ * system credentials, `false` otherwise.
+ * @param scope the scope of this group.
+ */
+ static nsPerformanceGroup*
+ Make(JSContext* cx,
+ nsPerformanceStatsService* service,
+ const nsAString& name,
+ const nsAString& addonId,
+ uint64_t windowId,
+ uint64_t processId,
+ bool isSystem,
+ GroupScope scope);
+
+ /**
+ * Utility: type-safer conversion from js::PerformanceGroup to nsPerformanceGroup.
+ */
+ static inline nsPerformanceGroup* Get(js::PerformanceGroup* self) {
+ return static_cast<nsPerformanceGroup*>(self);
+ }
+ static inline const nsPerformanceGroup* Get(const js::PerformanceGroup* self) {
+ return static_cast<const nsPerformanceGroup*>(self);
+ }
+
+ /**
+ * The performance data committed to this group.
+ */
+ PerformanceData data;
+
+ /**
+ * The scope of this group. Used to determine whether the group
+ * should be (de)activated.
+ */
+ GroupScope Scope() const;
+
+ /**
+ * Identification details for this group.
+ */
+ nsPerformanceGroupDetails* Details() const;
+
+ /**
+ * Cleanup any references.
+ */
+ void Dispose();
+
+ /**
+ * Set the observation target for this group.
+ *
+ * This method must be called exactly once, when the performance
+ * group is attached to its `nsGroupHolder`.
+ */
+ void SetObservationTarget(nsPerformanceObservationTarget*);
+
+
+ /**
+ * `true` if we have already noticed that a performance alert should
+ * be raised for this group but we have not dispatched it yet,
+ * `false` otherwise.
+ */
+ bool HasPendingAlert() const;
+ void SetHasPendingAlert(bool value);
+
+protected:
+ nsPerformanceGroup(nsPerformanceStatsService* service,
+ const nsAString& name,
+ const nsAString& groupId,
+ const nsAString& addonId,
+ uint64_t windowId,
+ uint64_t processId,
+ bool isSystem,
+ GroupScope scope);
+
+
+ /**
+ * Virtual implementation of `delete`, to make sure that objects are
+ * destoyed with an implementation of `delete` compatible with the
+ * implementation of `new` used to allocate them.
+ *
+ * Called by SpiderMonkey.
+ */
+ virtual void Delete() override {
+ delete this;
+ }
+ ~nsPerformanceGroup();
+
+private:
+ /**
+ * Identification details for this group.
+ */
+ RefPtr<nsPerformanceGroupDetails> mDetails;
+
+ /**
+ * The stats service. Used to perform cleanup during destruction.
+ */
+ RefPtr<nsPerformanceStatsService> mService;
+
+ /**
+ * The scope of this group. Used to determine whether the group
+ * should be (de)activated.
+ */
+ const GroupScope mScope;
+
+// Observing performance alerts.
+
+public:
+ /**
+ * The observation target, used to register observers.
+ */
+ nsPerformanceObservationTarget* ObservationTarget() const;
+
+ /**
+ * Record a jank duration.
+ *
+ * Update the highest recent jank if necessary.
+ */
+ void RecordJank(uint64_t jank);
+ uint64_t HighestRecentJank();
+
+ /**
+ * Record a CPOW duration.
+ *
+ * Update the highest recent CPOW if necessary.
+ */
+ void RecordCPOW(uint64_t cpow);
+ uint64_t HighestRecentCPOW();
+
+ /**
+ * Record that this group has recently been involved in handling
+ * user input. Note that heuristics are involved here, so the
+ * result is not 100% accurate.
+ */
+ void RecordUserInput();
+ bool HasRecentUserInput();
+
+ /**
+ * Reset recent values (recent highest CPOW and jank, involvement in
+ * user input).
+ */
+ void ResetRecent();
+private:
+ /**
+ * The target used by observers to register for watching slow
+ * performance alerts caused by this group.
+ *
+ * May be nullptr for groups that cannot be watched (the top group).
+ */
+ RefPtr<class nsPerformanceObservationTarget> mObservationTarget;
+
+ /**
+ * The highest jank encountered since jank observers for this group
+ * were last called, in microseconds.
+ */
+ uint64_t mHighestJank;
+
+ /**
+ * The highest CPOW encountered since jank observers for this group
+ * were last called, in microseconds.
+ */
+ uint64_t mHighestCPOW;
+
+ /**
+ * `true` if this group has been involved in handling user input,
+ * `false` otherwise.
+ *
+ * Note that we use heuristics to determine whether a group is
+ * involved in handling user input, so this value is not 100%
+ * accurate.
+ */
+ bool mHasRecentUserInput;
+
+ /**
+ * `true` if this group has caused a performance alert and this alert
+ * hasn't been dispatched yet.
+ *
+ * We use this as part of the buffering of performance alerts. If
+ * the group generates several alerts several times during the
+ * buffering delay, we only wish to add the group once to the list
+ * of alerts.
+ */
+ bool mHasPendingAlert;
+};
+
+#endif
diff --git a/components/permissions/content/cookieAcceptDialog.js b/components/permissions/content/cookieAcceptDialog.js
new file mode 100644
index 000000000..5f5760305
--- /dev/null
+++ b/components/permissions/content/cookieAcceptDialog.js
@@ -0,0 +1,203 @@
+// -*- 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 nsICookieAcceptDialog = Components.interfaces.nsICookieAcceptDialog;
+const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
+const nsICookie = Components.interfaces.nsICookie;
+const nsICookiePromptService = Components.interfaces.nsICookiePromptService;
+
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+var params;
+var cookieBundle;
+var gDateService = null;
+
+var showDetails = "";
+var hideDetails = "";
+var detailsAccessKey = "";
+
+function onload()
+{
+ doSetOKCancel(cookieAcceptNormal, cookieDeny, cookieAcceptSession);
+
+ var dialog = document.documentElement;
+
+ document.getElementById("Button2").collapsed = false;
+
+ document.getElementById("ok").label = dialog.getAttribute("acceptLabel");
+ document.getElementById("ok").accessKey = dialog.getAttribute("acceptKey");
+ document.getElementById("Button2").label = dialog.getAttribute("extra1Label");
+ document.getElementById("Button2").accessKey = dialog.getAttribute("extra1Key");
+ document.getElementById("cancel").label = dialog.getAttribute("cancelLabel");
+ document.getElementById("cancel").accessKey = dialog.getAttribute("cancelKey");
+
+ // hook up button icons where implemented
+ document.getElementById("ok").setAttribute("icon", "accept");
+ document.getElementById("cancel").setAttribute("icon", "cancel");
+ document.getElementById("disclosureButton").setAttribute("icon", "properties");
+
+ // Initialize the date formatter
+ if (!gDateService) {
+ const nsScriptableDateFormat_CONTRACTID = "@mozilla.org/intl/scriptabledateformat;1";
+ const nsIScriptableDateFormat = Components.interfaces.nsIScriptableDateFormat;
+ gDateService = Components.classes[nsScriptableDateFormat_CONTRACTID]
+ .getService(nsIScriptableDateFormat);
+ }
+
+ cookieBundle = document.getElementById("cookieBundle");
+
+ // cache strings
+ if (!showDetails) {
+ showDetails = cookieBundle.getString('showDetails');
+ }
+ if (!hideDetails) {
+ hideDetails = cookieBundle.getString('hideDetails');
+ }
+ detailsAccessKey = cookieBundle.getString('detailsAccessKey');
+
+ if (document.getElementById('infobox').hidden) {
+ document.getElementById('disclosureButton').setAttribute("label", showDetails);
+ } else {
+ document.getElementById('disclosureButton').setAttribute("label", hideDetails);
+ }
+ document.getElementById('disclosureButton').setAttribute("accesskey", detailsAccessKey);
+
+ if ("arguments" in window && window.arguments.length >= 1 && window.arguments[0]) {
+ try {
+ params = window.arguments[0].QueryInterface(nsIDialogParamBlock);
+ var cookie = params.objects.queryElementAt(0, nsICookie);
+ var cookiesFromHost = params.GetInt(nsICookieAcceptDialog.COOKIESFROMHOST);
+
+ var messageFormat;
+ if (params.GetInt(nsICookieAcceptDialog.CHANGINGCOOKIE))
+ messageFormat = 'permissionToModifyCookie';
+ else if (cookiesFromHost > 1)
+ messageFormat = 'permissionToSetAnotherCookie';
+ else if (cookiesFromHost == 1)
+ messageFormat = 'permissionToSetSecondCookie';
+ else
+ messageFormat = 'permissionToSetACookie';
+
+ var hostname = params.GetString(nsICookieAcceptDialog.HOSTNAME);
+
+ var messageText;
+ if (cookie)
+ messageText = cookieBundle.getFormattedString(messageFormat, [hostname, cookiesFromHost]);
+ else
+ // No cookies means something went wrong. Bring up the dialog anyway
+ // to not make the mess worse.
+ messageText = cookieBundle.getFormattedString(messageFormat, ["", cookiesFromHost]);
+
+ var messageParent = document.getElementById("dialogtextbox");
+ var messageParagraphs = messageText.split("\n");
+
+ // use value for the header, so it doesn't wrap.
+ var headerNode = document.getElementById("dialog-header");
+ headerNode.setAttribute("value", messageParagraphs[0]);
+
+ // use childnodes here, the text can wrap
+ for (var i = 1; i < messageParagraphs.length; i++) {
+ var descriptionNode = document.createElement("description");
+ text = document.createTextNode(messageParagraphs[i]);
+ descriptionNode.appendChild(text);
+ messageParent.appendChild(descriptionNode);
+ }
+
+ if (cookie) {
+ document.getElementById('ifl_name').setAttribute("value", cookie.name);
+ document.getElementById('ifl_value').setAttribute("value", cookie.value);
+ document.getElementById('ifl_host').setAttribute("value", cookie.host);
+ document.getElementById('ifl_path').setAttribute("value", cookie.path);
+ document.getElementById('ifl_isSecure').setAttribute("value",
+ cookie.isSecure ?
+ cookieBundle.getString("forSecureOnly") : cookieBundle.getString("forAnyConnection")
+ );
+ document.getElementById('ifl_expires').setAttribute("value", GetExpiresString(cookie.expires));
+ document.getElementById('ifl_isDomain').setAttribute("value",
+ cookie.isDomain ?
+ cookieBundle.getString("domainColon") : cookieBundle.getString("hostColon")
+ );
+ }
+ // set default result to not accept the cookie
+ params.SetInt(nsICookieAcceptDialog.ACCEPT_COOKIE, 0);
+ // and to not persist
+ params.SetInt(nsICookieAcceptDialog.REMEMBER_DECISION, 0);
+ } catch (e) {
+ }
+ }
+
+ // The Private Browsing service might not be available
+ try {
+ if (window.opener && PrivateBrowsingUtils.isWindowPrivate(window.opener)) {
+ var persistCheckbox = document.getElementById("persistDomainAcceptance");
+ persistCheckbox.removeAttribute("checked");
+ persistCheckbox.setAttribute("disabled", "true");
+ }
+ } catch (ex) {}
+}
+
+function showhideinfo()
+{
+ var infobox=document.getElementById('infobox');
+
+ if (infobox.hidden) {
+ infobox.setAttribute("hidden", "false");
+ document.getElementById('disclosureButton').setAttribute("label", hideDetails);
+ } else {
+ infobox.setAttribute("hidden", "true");
+ document.getElementById('disclosureButton').setAttribute("label", showDetails);
+ }
+ sizeToContent();
+}
+
+function cookieAcceptNormal()
+{
+ // accept the cookie normally
+ params.SetInt(nsICookieAcceptDialog.ACCEPT_COOKIE, nsICookiePromptService.ACCEPT_COOKIE);
+ // And remember that when needed
+ params.SetInt(nsICookieAcceptDialog.REMEMBER_DECISION, document.getElementById('persistDomainAcceptance').checked);
+ window.close();
+}
+
+function cookieAcceptSession()
+{
+ // accept for the session only
+ params.SetInt(nsICookieAcceptDialog.ACCEPT_COOKIE, nsICookiePromptService.ACCEPT_SESSION_COOKIE);
+ // And remember that when needed
+ params.SetInt(nsICookieAcceptDialog.REMEMBER_DECISION, document.getElementById('persistDomainAcceptance').checked);
+ window.close();
+}
+
+function cookieDeny()
+{
+ // say that the cookie was rejected
+ params.SetInt(nsICookieAcceptDialog.ACCEPT_COOKIE, nsICookiePromptService.DENY_COOKIE);
+ // And remember that when needed
+ params.SetInt(nsICookieAcceptDialog.REMEMBER_DECISION, document.getElementById('persistDomainAcceptance').checked);
+ window.close();
+}
+
+function GetExpiresString(secondsUntilExpires) {
+ if (secondsUntilExpires) {
+ var date = new Date(1000*secondsUntilExpires);
+
+ // if a server manages to set a really long-lived cookie, the dateservice
+ // can't cope with it properly, so we'll return a descriptive string instead.
+ var expiry = "";
+ try {
+ expiry = gDateService.FormatDateTime("", gDateService.dateFormatLong,
+ gDateService.timeFormatSeconds,
+ date.getFullYear(), date.getMonth()+1,
+ date.getDate(), date.getHours(),
+ date.getMinutes(), date.getSeconds());
+ } catch (ex) {
+ // Expiry duration was out of range for the date formatter, meaning a silly long time.
+ expiry = cookieBundle.getString("expireInAVeryLongTime");
+ }
+ return expiry;
+ }
+ return cookieBundle.getString("expireAtEndOfSession");
+}
diff --git a/components/permissions/content/cookieAcceptDialog.xul b/components/permissions/content/cookieAcceptDialog.xul
new file mode 100644
index 000000000..99fd92c0c
--- /dev/null
+++ b/components/permissions/content/cookieAcceptDialog.xul
@@ -0,0 +1,118 @@
+<?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://cookie/locale/cookieAcceptDialog.dtd">
+
+<!-- use a overlay te be able to put the accept/deny buttons not on the bottom -->
+<?xul-overlay href="chrome://global/content/dialogOverlay.xul"?>
+
+<!-- use buttons="disclosure" to hide ok/cancel buttons. Those are added manually later -->
+<dialog id="cookieAcceptDialog"
+ acceptLabel="&button.allow.label;"
+ acceptKey="&button.allow.accesskey;"
+ extra1Label="&button.session.label;"
+ extra1Key="&button.session.accesskey;"
+ cancelLabel="&button.deny.label;"
+ cancelKey="&button.deny.accesskey;"
+ onload="onload();"
+ ondialogaccept="return doOKButton();"
+ title="&dialog.title;"
+ buttons="disclosure"
+ aria-describedby="dialog-header"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="cookieAcceptDialog.js" type="application/javascript"/>
+ <stringbundle id="cookieBundle"
+ src="chrome://cookie/locale/cookieAcceptDialog.properties"/>
+
+ <vbox>
+ <hbox>
+ <hbox align="start">
+ <image id="infoicon" class="spaced alert-icon"/>
+ </hbox>
+
+ <vbox flex="1">
+ <!-- text -->
+ <vbox id="dialogtextbox">
+ <description id="dialog-header" class="header"/>
+ </vbox>
+
+ <hbox id="checkboxContainer">
+ <checkbox id="persistDomainAcceptance"
+ label="&dialog.remember.label;"
+ accesskey="&dialog.remember.accesskey;"
+ persist="checked"/>
+ </hbox>
+ </vbox>
+
+ </hbox>
+
+ <hbox>
+ <button id="disclosureButton" dlgtype="disclosure" class="exit-dialog"
+ oncommand="showhideinfo();"/>
+ <spacer flex="1"/>
+ <hbox id="okCancelButtonsRight"/>
+ </hbox>
+
+ <vbox id="infobox" hidden="true" persist="hidden">
+ <separator class="groove"/>
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label value="&props.name.label;" control="ifl_name"/>
+ </hbox>
+ <textbox id="ifl_name" readonly="true" class="plain"/>
+ </row>
+
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label value="&props.value.label;" control="ifl_value"/>
+ </hbox>
+ <textbox id="ifl_value" readonly="true" class="plain"/>
+ </row>
+
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label id="ifl_isDomain" value="&props.domain.label;" control="ifl_host"/>
+ </hbox>
+ <textbox id="ifl_host" readonly="true" class="plain"/>
+ </row>
+
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label value="&props.path.label;" control="ifl_path"/>
+ </hbox>
+ <textbox id="ifl_path" readonly="true" class="plain"/>
+ </row>
+
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label value="&props.secure.label;" control="ifl_isSecure"/>
+ </hbox>
+ <textbox id="ifl_isSecure" readonly="true" class="plain"/>
+ </row>
+
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label value="&props.expires.label;" control="ifl_expires"/>
+ </hbox>
+ <textbox id="ifl_expires" readonly="true" class="plain"/>
+ </row>
+
+ </rows>
+ </grid>
+ </vbox>
+ </vbox>
+</dialog>
+
diff --git a/components/permissions/jar.mn b/components/permissions/jar.mn
new file mode 100644
index 000000000..2826bbcd6
--- /dev/null
+++ b/components/permissions/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/.
+
+toolkit.jar:
+% content cookie %content/cookie/
+ content/cookie/cookieAcceptDialog.xul (content/cookieAcceptDialog.xul)
+ content/cookie/cookieAcceptDialog.js (content/cookieAcceptDialog.js)
diff --git a/components/permissions/locale/cookieAcceptDialog.dtd b/components/permissions/locale/cookieAcceptDialog.dtd
new file mode 100644
index 000000000..52664f1de
--- /dev/null
+++ b/components/permissions/locale/cookieAcceptDialog.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 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 button.allow.label "Allow">
+<!ENTITY button.allow.accesskey "A">
+<!ENTITY button.session.label "Allow for Session">
+<!ENTITY button.session.accesskey "S">
+<!ENTITY button.deny.label "Deny">
+<!ENTITY button.deny.accesskey "D">
+
+<!ENTITY dialog.title "Confirm setting cookie">
+<!ENTITY dialog.remember.label "Use my choice for all cookies from this site">
+<!ENTITY dialog.remember.accesskey "U">
diff --git a/components/permissions/locale/cookieAcceptDialog.properties b/components/permissions/locale/cookieAcceptDialog.properties
new file mode 100644
index 000000000..698c6f010
--- /dev/null
+++ b/components/permissions/locale/cookieAcceptDialog.properties
@@ -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/.
+
+hostColon=Host:
+domainColon=Domain:
+forSecureOnly=Encrypted connections only
+forAnyConnection=Any type of connection
+expireAtEndOfSession=At end of session
+# LOCALIZATION NOTE (expireInAVeryLongTime) This string is used if a cookie won't expire for a literal life age and then some.
+expireInAVeryLongTime=Not in your lifetime
+
+showDetails=Show Details
+hideDetails=Hide Details
+detailsAccessKey=T
+
+permissionToSetACookie = The site %S wants to set a cookie.
+permissionToSetSecondCookie = The site %S wants to set a second cookie.
+# LOCALIZATION NOTE (PermissionToSetAnotherCookie): First %S: sitename, second %S: number of cookies already present for that site
+permissionToSetAnotherCookie = The site %S wants to set another cookie.\nYou already have %S cookies from this site.
+permissionToModifyCookie = The site %S wants to modify an existing cookie.
diff --git a/components/permissions/moz.build b/components/permissions/moz.build
new file mode 100644
index 000000000..e25484d9f
--- /dev/null
+++ b/components/permissions/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+XPIDL_SOURCES += [
+ 'public/nsICookieAcceptDialog.idl',
+ 'public/nsICookiePermission.idl',
+ 'public/nsICookiePromptService.idl',
+ 'public/nsIPermission.idl',
+ 'public/nsIPermissionManager.idl',
+]
+
+UNIFIED_SOURCES += [
+ 'src/nsContentBlocker.cpp',
+ 'src/nsCookiePermission.cpp',
+ 'src/nsCookiePromptService.cpp',
+ 'src/nsPermission.cpp',
+ 'src/nsPermissionFactory.cpp',
+ 'src/nsPermissionManager.cpp',
+ 'src/nsPopupWindowManager.cpp',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+XPIDL_MODULE = 'permissions'
+FINAL_LIBRARY = 'xul'
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/permissions/public/nsICookieAcceptDialog.idl b/components/permissions/public/nsICookieAcceptDialog.idl
new file mode 100644
index 000000000..b02339780
--- /dev/null
+++ b/components/permissions/public/nsICookieAcceptDialog.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+/*
+
+ This file contains some constants for the cookie accept dialog
+
+ */
+
+[scriptable, uuid(3F2F0D2C-BDEA-4B5A-AFC6-FCF18F66B97E)]
+interface nsICookieAcceptDialog: nsISupports {
+
+ const short ACCEPT_COOKIE=0;
+ const short REMEMBER_DECISION=1;
+ const short HOSTNAME=2;
+ const short COOKIESFROMHOST=3;
+ const short CHANGINGCOOKIE=4;
+};
diff --git a/components/permissions/public/nsICookiePermission.idl b/components/permissions/public/nsICookiePermission.idl
new file mode 100644
index 000000000..fd4a879f9
--- /dev/null
+++ b/components/permissions/public/nsICookiePermission.idl
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsICookie2;
+interface nsIURI;
+interface nsIChannel;
+
+typedef long nsCookieAccess;
+
+/**
+ * An interface to test for cookie permissions
+ */
+[scriptable, uuid(11ddd4ed-8f5b-40b3-b2a0-27c20ea1c88d)]
+interface nsICookiePermission : nsISupports
+{
+ /**
+ * nsCookieAccess values
+ */
+ const nsCookieAccess ACCESS_DEFAULT = 0;
+ const nsCookieAccess ACCESS_ALLOW = 1;
+ const nsCookieAccess ACCESS_DENY = 2;
+
+ /**
+ * additional values for nsCookieAccess which may not match
+ * nsIPermissionManager. Keep 3-7 available to allow nsIPermissionManager to
+ * add values without colliding. ACCESS_SESSION is not directly returned by
+ * any methods on this interface.
+ */
+ const nsCookieAccess ACCESS_SESSION = 8;
+ const nsCookieAccess ACCESS_ALLOW_FIRST_PARTY_ONLY = 9;
+ const nsCookieAccess ACCESS_LIMIT_THIRD_PARTY = 10;
+
+ /**
+ * setAccess
+ *
+ * this method is called to block cookie access for the given URI. this
+ * may result in other URIs being blocked as well (e.g., URIs which share
+ * the same host name).
+ *
+ * @param aURI
+ * the URI to block
+ * @param aAccess
+ * the new cookie access for the URI.
+ */
+ void setAccess(in nsIURI aURI,
+ in nsCookieAccess aAccess);
+
+ /**
+ * canAccess
+ *
+ * this method is called to test whether or not the given URI/channel may
+ * access the cookie database, either to set or get cookies.
+ *
+ * @param aURI
+ * the URI trying to access cookies
+ * @param aChannel
+ * the channel corresponding to aURI
+ *
+ * @return one of the following nsCookieAccess values:
+ * ACCESS_DEFAULT, ACCESS_ALLOW, ACCESS_DENY, or
+ * ACCESS_ALLOW_FIRST_PARTY_ONLY
+ */
+ nsCookieAccess canAccess(in nsIURI aURI,
+ in nsIChannel aChannel);
+
+ /**
+ * canSetCookie
+ *
+ * this method is called to test whether or not the given URI/channel may
+ * set a specific cookie. this method is always preceded by a call to
+ * canAccess. it may modify the isSession and expiry attributes of the
+ * cookie via the aIsSession and aExpiry parameters, in order to limit
+ * or extend the lifetime of the cookie. this is useful, for instance, to
+ * downgrade a cookie to session-only if it fails to meet certain criteria.
+ *
+ * @param aURI
+ * the URI trying to set the cookie
+ * @param aChannel
+ * the channel corresponding to aURI
+ * @param aCookie
+ * the cookie being added to the cookie database
+ * @param aIsSession
+ * when canSetCookie is invoked, this is the current isSession attribute
+ * of the cookie. canSetCookie may leave this value unchanged to
+ * preserve this attribute of the cookie.
+ * @param aExpiry
+ * when canSetCookie is invoked, this is the current expiry time of
+ * the cookie. canSetCookie may leave this value unchanged to
+ * preserve this attribute of the cookie.
+ *
+ * @return true if the cookie can be set.
+ */
+ boolean canSetCookie(in nsIURI aURI,
+ in nsIChannel aChannel,
+ in nsICookie2 aCookie,
+ inout boolean aIsSession,
+ inout int64_t aExpiry);
+};
+
+%{ C++
+/**
+ * The nsICookiePermission implementation is an XPCOM service registered
+ * under the ContractID:
+ */
+#define NS_COOKIEPERMISSION_CONTRACTID "@mozilla.org/cookie/permission;1"
+%}
diff --git a/components/permissions/public/nsICookiePromptService.idl b/components/permissions/public/nsICookiePromptService.idl
new file mode 100644
index 000000000..163da6a26
--- /dev/null
+++ b/components/permissions/public/nsICookiePromptService.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * An interface to open a dialog to ask to permission to accept the cookie.
+ */
+
+interface mozIDOMWindowProxy;
+interface nsICookie;
+
+[scriptable, uuid(65ca07c3-6241-4de1-bf41-3336470499db)]
+interface nsICookiePromptService : nsISupports
+{
+ const uint32_t DENY_COOKIE = 0;
+ const uint32_t ACCEPT_COOKIE = 1;
+ const uint32_t ACCEPT_SESSION_COOKIE = 2;
+
+ /* Open a dialog that asks for permission to accept a cookie
+ *
+ * @param parent
+ * @param cookie
+ * @param hostname the host that wants to set the cookie,
+ * not the domain: part of the cookie
+ * @param cookiesFromHost the number of cookies there are already for this host
+ * @param changingCookie are we changing this cookie?
+ * @param rememberDecision should we set the matching permission for this host?
+ * @returns 0 == deny cookie
+ * 1 == accept cookie
+ * 2 == accept cookie for current session
+ */
+
+ long cookieDialog(in mozIDOMWindowProxy parent,
+ in nsICookie cookie,
+ in ACString hostname,
+ in long cookiesFromHost,
+ in boolean changingCookie,
+ out boolean rememberDecision);
+};
+
+%{C++
+#define NS_COOKIEPROMPTSERVICE_CONTRACTID "@mozilla.org/embedcomp/cookieprompt-service;1"
+%}
diff --git a/components/permissions/public/nsIPermission.idl b/components/permissions/public/nsIPermission.idl
new file mode 100644
index 000000000..c5ddd90fe
--- /dev/null
+++ b/components/permissions/public/nsIPermission.idl
@@ -0,0 +1,77 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+
+[scriptable, uuid(bb409a51-2371-4fea-9dc9-b7286a458b8c)]
+/**
+ * This interface defines a "permission" object,
+ * used to specify allowed/blocked objects from
+ * user-specified sites (cookies, images etc).
+ */
+
+interface nsIPermission : nsISupports
+{
+ /**
+ * The principal for which this permission applies.
+ */
+ readonly attribute nsIPrincipal principal;
+
+ /**
+ * a case-sensitive ASCII string, indicating the type of permission
+ * (e.g., "cookie", "image", etc).
+ * This string is specified by the consumer when adding a permission
+ * via nsIPermissionManager.
+ * @see nsIPermissionManager
+ */
+ readonly attribute ACString type;
+
+ /**
+ * The permission (see nsIPermissionManager.idl for allowed values)
+ */
+ readonly attribute uint32_t capability;
+
+ /**
+ * The expiration type of the permission (session, time-based or none).
+ * Constants are EXPIRE_*, defined in nsIPermissionManager.
+ * @see nsIPermissionManager
+ */
+ readonly attribute uint32_t expireType;
+
+ /**
+ * The expiration time of the permission (milliseconds since Jan 1 1970
+ * 0:00:00).
+ */
+ readonly attribute int64_t expireTime;
+
+ /**
+ * Test whether a principal would be affected by this permission.
+ *
+ * @param principal the principal to test
+ * @param exactHost If true, only the specific host will be matched,
+ * @see nsIPermissionManager::testExactPermission.
+ * If false, subdomains will also be searched,
+ * @see nsIPermissionManager::testPermission.
+ */
+ boolean matches(in nsIPrincipal principal,
+ in boolean exactHost);
+
+ /**
+ * Test whether a URI would be affected by this permission.
+ * NOTE: This performs matches with default origin attribute values.
+ *
+ * @param uri the uri to test
+ * @param exactHost If true, only the specific host will be matched,
+ * @see nsIPermissionManager::testExactPermission.
+ * If false, subdomains will also be searched,
+ * @see nsIPermissionManager::testPermission.
+ */
+ boolean matchesURI(in nsIURI uri,
+ in boolean exactHost);
+};
diff --git a/components/permissions/public/nsIPermissionManager.idl b/components/permissions/public/nsIPermissionManager.idl
new file mode 100644
index 000000000..b61817d4c
--- /dev/null
+++ b/components/permissions/public/nsIPermissionManager.idl
@@ -0,0 +1,271 @@
+/* -*- 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/. */
+
+/**
+ * This file contains an interface to the Permission Manager,
+ * used to persistenly store permissions for different object types (cookies,
+ * images etc) on a site-by-site basis.
+ *
+ * This service broadcasts the following notification when the permission list
+ * is changed:
+ *
+ * topic : "perm-changed" (PERM_CHANGE_NOTIFICATION)
+ * broadcast whenever the permission list changes in some way. there
+ * are four possible data strings for this notification; one
+ * notification will be broadcast for each change, and will involve
+ * a single permission.
+ * subject: an nsIPermission interface pointer representing the permission object
+ * that changed.
+ * data : "deleted"
+ * a permission was deleted. the subject is the deleted permission.
+ * "added"
+ * a permission was added. the subject is the added permission.
+ * "changed"
+ * a permission was changed. the subject is the new permission.
+ * "cleared"
+ * the entire permission list was cleared. the subject is null.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIObserver;
+interface nsIPrincipal;
+interface mozIDOMWindow;
+interface nsIPermission;
+interface nsISimpleEnumerator;
+
+[scriptable, uuid(4dcb3851-eba2-4e42-b236-82d2596fca22)]
+interface nsIPermissionManager : nsISupports
+{
+ /**
+ * Predefined return values for the testPermission method and for
+ * the permission param of the add method
+ * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
+ * default permission when no entry is found for a host, and
+ * should not be used by consumers to indicate otherwise.
+ */
+ const uint32_t UNKNOWN_ACTION = 0;
+ const uint32_t ALLOW_ACTION = 1;
+ const uint32_t DENY_ACTION = 2;
+ const uint32_t PROMPT_ACTION = 3;
+
+ /**
+ * Predefined expiration types for permissions. Permissions can be permanent
+ * (never expire), expire at the end of the session, or expire at a specified
+ * time. Permissions that expire at the end of a session may also have a
+ * specified expiration time.
+ */
+ const uint32_t EXPIRE_NEVER = 0;
+ const uint32_t EXPIRE_SESSION = 1;
+ const uint32_t EXPIRE_TIME = 2;
+
+ /**
+ * Add permission information for a given URI and permission type. This
+ * operation will cause the type string to be registered if it does not
+ * currently exist. If a permission already exists for a given type, it
+ * will be modified.
+ *
+ * @param uri the uri to add the permission for
+ * @param type a case-sensitive ASCII string, identifying the consumer.
+ * Consumers should choose this string to be unique, with
+ * respect to other consumers.
+ * @param permission an integer representing the desired action (e.g. allow
+ * or deny). The interpretation of this number is up to the
+ * consumer, and may represent different actions for different
+ * types. Consumers may use one of the enumerated permission
+ * actions defined above, for convenience.
+ * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
+ * default permission when no entry is found for a host, and
+ * should not be used by consumers to indicate otherwise.
+ * @param expiretype a constant defining whether this permission should
+ * never expire (EXPIRE_NEVER), expire at the end of the
+ * session (EXPIRE_SESSION), or expire at a specified time
+ * (EXPIRE_TIME).
+ * @param expiretime an integer representation of when this permission
+ * should be forgotten (milliseconds since Jan 1 1970 0:00:00).
+ */
+ void add(in nsIURI uri,
+ in string type,
+ in uint32_t permission,
+ [optional] in uint32_t expireType,
+ [optional] in int64_t expireTime);
+
+ /**
+ * Get all custom permissions for a given URI. This will return
+ * an enumerator of all permissions which are not set to default
+ * and which belong to the matching prinicpal of the given URI.
+ *
+ * @param uri the URI to get all permissions for
+ */
+ nsISimpleEnumerator getAllForURI(in nsIURI uri);
+
+ /**
+ * Add permission information for a given principal.
+ * It is internally calling the other add() method using the nsIURI from the
+ * principal.
+ * Passing a system principal will be a no-op because they will always be
+ * granted permissions.
+ */
+ void addFromPrincipal(in nsIPrincipal principal, in string typed,
+ in uint32_t permission,
+ [optional] in uint32_t expireType,
+ [optional] in int64_t expireTime);
+
+ /**
+ * Remove permission information for a given URI and permission type. This will
+ * remove the permission for the entire host described by the uri, acting as the
+ * opposite operation to the add() method.
+ *
+ * @param uri the uri to remove the permission for
+ * @param type a case-sensitive ASCII string, identifying the consumer.
+ * The type must have been previously registered using the
+ * add() method.
+ */
+ void remove(in nsIURI uri,
+ in string type);
+
+ /**
+ * Remove permission information for a given principal.
+ * This is internally calling remove() with the host from the principal's URI.
+ * Passing system principal will be a no-op because we never add them to the
+ * database.
+ */
+ void removeFromPrincipal(in nsIPrincipal principal, in string type);
+
+ /**
+ * Remove the given permission from the permission manager.
+ *
+ * @param perm a permission obtained from the permission manager.
+ */
+ void removePermission(in nsIPermission perm);
+
+ /**
+ * Clear permission information for all websites.
+ */
+ void removeAll();
+
+ /**
+ * Clear all permission information added since the specified time.
+ */
+ void removeAllSince(in int64_t since);
+
+ /**
+ * Test whether a website has permission to perform the given action.
+ * @param uri the uri to be tested
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testPermission(in nsIURI uri,
+ in string type);
+
+ /**
+ * Test whether the principal has the permission to perform a given action.
+ * System principals will always have permissions granted.
+ */
+ uint32_t testPermissionFromPrincipal(in nsIPrincipal principal,
+ in string type);
+
+ /**
+ * Test whether the principal associated with the window's document has the
+ * permission to perform a given action. System principals will always
+ * have permissions granted.
+ */
+ uint32_t testPermissionFromWindow(in mozIDOMWindow window,
+ in string type);
+
+ /**
+ * Test whether a website has permission to perform the given action.
+ * This requires an exact hostname match, subdomains are not a match.
+ * @param uri the uri to be tested
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testExactPermission(in nsIURI uri,
+ in string type);
+
+ /**
+ * See testExactPermission() above.
+ * System principals will always have permissions granted.
+ */
+ uint32_t testExactPermissionFromPrincipal(in nsIPrincipal principal,
+ in string type);
+
+ /**
+ * Test whether a website has permission to perform the given action
+ * ignoring active sessions.
+ * System principals will always have permissions granted.
+ *
+ * @param principal the principal
+ * @param type a case-sensitive ASCII string, identifying the consumer
+ * @param return see add(), param permission. returns UNKNOWN_ACTION when
+ * there is no stored permission for this uri and / or type.
+ */
+ uint32_t testExactPermanentPermission(in nsIPrincipal principal,
+ in string type);
+
+ /**
+ * Get the permission object associated with the given principal and action.
+ * @param principal The principal
+ * @param type A case-sensitive ASCII string identifying the consumer
+ * @param exactHost If true, only the specific host will be matched,
+ * @see testExactPermission. If false, subdomains will
+ * also be searched, @see testPermission.
+ * @returns The matching permission object, or null if no matching object
+ * was found. No matching object is equivalent to UNKNOWN_ACTION.
+ * @note Clients in general should prefer the test* methods unless they
+ * need to know the specific stored details.
+ * @note This method will always return null for the system principal.
+ */
+ nsIPermission getPermissionObject(in nsIPrincipal principal,
+ in string type,
+ in boolean exactHost);
+
+ /**
+ * Allows enumeration of all stored permissions
+ * @return an nsISimpleEnumerator interface that allows access to
+ * nsIPermission objects
+ */
+ readonly attribute nsISimpleEnumerator enumerator;
+
+ /**
+ * Remove all permissions that will match the origin pattern.
+ */
+ void removePermissionsWithAttributes(in DOMString patternAsJSON);
+
+ /**
+ * If the current permission is set to expire, reset the expiration time. If
+ * there is no permission or the current permission does not expire, this
+ * method will silently return.
+ *
+ * @param sessionExpiretime an integer representation of when this permission
+ * should be forgotten (milliseconds since
+ * Jan 1 1970 0:00:00), if it is currently
+ * EXPIRE_SESSION.
+ * @param sessionExpiretime an integer representation of when this permission
+ * should be forgotten (milliseconds since
+ * Jan 1 1970 0:00:00), if it is currently
+ * EXPIRE_TIME.
+ */
+ void updateExpireTime(in nsIPrincipal principal,
+ in string type,
+ in boolean exactHost,
+ in uint64_t sessionExpireTime,
+ in uint64_t persistentExpireTime);
+
+ /**
+ * Remove all current permission settings and get permission settings from
+ * chrome process.
+ */
+ void refreshPermission();
+};
+
+%{ C++
+#define NS_PERMISSIONMANAGER_CONTRACTID "@mozilla.org/permissionmanager;1"
+
+#define PERM_CHANGE_NOTIFICATION "perm-changed"
+%}
diff --git a/components/permissions/src/nsContentBlocker.cpp b/components/permissions/src/nsContentBlocker.cpp
new file mode 100644
index 000000000..391785dc3
--- /dev/null
+++ b/components/permissions/src/nsContentBlocker.cpp
@@ -0,0 +1,385 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsContentBlocker.h"
+#include "nsIContent.h"
+#include "nsIURI.h"
+#include "nsIServiceManager.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIDocShell.h"
+#include "nsString.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIObjectLoadingContent.h"
+#include "mozilla/ArrayUtils.h"
+#include "nsContentUtils.h"
+
+// Possible behavior pref values
+// Those map to the nsIPermissionManager values where possible
+#define BEHAVIOR_ACCEPT nsIPermissionManager::ALLOW_ACTION
+#define BEHAVIOR_REJECT nsIPermissionManager::DENY_ACTION
+#define BEHAVIOR_NOFOREIGN 3
+
+// From nsIContentPolicy
+// and nsIContentPolicyBase.idl: Their order must be retained!
+static const char *kTypeString[] = {
+ "other",
+ "script",
+ "image",
+ "stylesheet",
+ "object",
+ "document",
+ "subdocument",
+ "refresh",
+ "xbl",
+ "ping",
+ "xmlhttprequest",
+ "objectsubrequest",
+ "dtd",
+ "font",
+ "media",
+ "websocket",
+ "csp_report",
+ "xslt",
+ "beacon",
+ "fetch",
+ "image",
+ "manifest",
+ "", // TYPE_INTERNAL_SCRIPT
+ "", // TYPE_INTERNAL_WORKER
+ "", // TYPE_INTERNAL_SHARED_WORKER
+ "", // TYPE_INTERNAL_EMBED
+ "", // TYPE_INTERNAL_OBJECT
+ "", // TYPE_INTERNAL_FRAME
+ "", // TYPE_INTERNAL_IFRAME
+ "", // TYPE_INTERNAL_AUDIO
+ "", // TYPE_INTERNAL_VIDEO
+ "", // TYPE_INTERNAL_TRACK
+ "", // TYPE_INTERNAL_XMLHTTPREQUEST
+ "", // TYPE_INTERNAL_EVENTSOURCE
+ "", // TYPE_INTERNAL_SERVICE_WORKER
+ "", // TYPE_INTERNAL_SCRIPT_PRELOAD
+ "", // TYPE_INTERNAL_IMAGE
+ "", // TYPE_INTERNAL_IMAGE_PRELOAD
+ "", // TYPE_INTERNAL_STYLESHEET
+ "", // TYPE_INTERNAL_STYLESHEET_PRELOAD
+ "", // TYPE_INTERNAL_IMAGE_FAVICON
+ "saveas_download",
+};
+
+#define NUMBER_OF_TYPES MOZ_ARRAY_LENGTH(kTypeString)
+uint8_t nsContentBlocker::mBehaviorPref[NUMBER_OF_TYPES];
+
+NS_IMPL_ISUPPORTS(nsContentBlocker,
+ nsIContentPolicy,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+nsContentBlocker::nsContentBlocker()
+{
+ memset(mBehaviorPref, BEHAVIOR_ACCEPT, NUMBER_OF_TYPES);
+}
+
+nsresult
+nsContentBlocker::Init()
+{
+ nsresult rv;
+ mPermissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefService->GetBranch("permissions.default.", getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Migrate old image blocker pref
+ nsCOMPtr<nsIPrefBranch> oldPrefBranch;
+ oldPrefBranch = do_QueryInterface(prefService);
+ int32_t oldPref;
+ rv = oldPrefBranch->GetIntPref("network.image.imageBehavior", &oldPref);
+ if (NS_SUCCEEDED(rv) && oldPref) {
+ int32_t newPref;
+ switch (oldPref) {
+ default:
+ newPref = BEHAVIOR_ACCEPT;
+ break;
+ case 1:
+ newPref = BEHAVIOR_NOFOREIGN;
+ break;
+ case 2:
+ newPref = BEHAVIOR_REJECT;
+ break;
+ }
+ prefBranch->SetIntPref("image", newPref);
+ oldPrefBranch->ClearUserPref("network.image.imageBehavior");
+ }
+
+
+ // The branch is not a copy of the prefservice, but a new object, because
+ // it is a non-default branch. Adding obeservers to it will only work if
+ // we make sure that the object doesn't die. So, keep a reference to it.
+ mPrefBranchInternal = do_QueryInterface(prefBranch, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mPrefBranchInternal->AddObserver("", this, true);
+ PrefChanged(prefBranch, nullptr);
+
+ return rv;
+}
+
+#undef LIMIT
+#define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default))
+
+void
+nsContentBlocker::PrefChanged(nsIPrefBranch *aPrefBranch,
+ const char *aPref)
+{
+ int32_t val;
+
+#define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P))
+
+ for(uint32_t i = 0; i < NUMBER_OF_TYPES; ++i) {
+ if (*kTypeString[i] &&
+ PREF_CHANGED(kTypeString[i]) &&
+ NS_SUCCEEDED(aPrefBranch->GetIntPref(kTypeString[i], &val)))
+ mBehaviorPref[i] = LIMIT(val, 1, 3, 1);
+ }
+
+}
+
+// nsIContentPolicy Implementation
+NS_IMETHODIMP
+nsContentBlocker::ShouldLoad(uint32_t aContentType,
+ nsIURI *aContentLocation,
+ nsIURI *aRequestingLocation,
+ nsISupports *aRequestingContext,
+ const nsACString &aMimeGuess,
+ nsISupports *aExtra,
+ nsIPrincipal *aRequestPrincipal,
+ int16_t *aDecision)
+{
+ MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
+ "We should only see external content policy types here.");
+
+ *aDecision = nsIContentPolicy::ACCEPT;
+ nsresult rv;
+
+ // Ony support NUMBER_OF_TYPES content types. that all there is at the
+ // moment, but you never know...
+ if (aContentType > NUMBER_OF_TYPES)
+ return NS_OK;
+
+ // we can't do anything without this
+ if (!aContentLocation)
+ return NS_OK;
+
+ // The final type of an object tag may mutate before it reaches
+ // shouldProcess, so we cannot make any sane blocking decisions here
+ if (aContentType == nsIContentPolicy::TYPE_OBJECT)
+ return NS_OK;
+
+ // we only want to check http, https, ftp
+ // for chrome:// and resources and others, no need to check.
+ nsAutoCString scheme;
+ aContentLocation->GetScheme(scheme);
+ if (!scheme.LowerCaseEqualsLiteral("ftp") &&
+ !scheme.LowerCaseEqualsLiteral("http") &&
+ !scheme.LowerCaseEqualsLiteral("https"))
+ return NS_OK;
+
+ bool shouldLoad, fromPrefs;
+ rv = TestPermission(aContentLocation, aRequestingLocation, aContentType,
+ &shouldLoad, &fromPrefs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!shouldLoad) {
+ if (fromPrefs) {
+ *aDecision = nsIContentPolicy::REJECT_TYPE;
+ } else {
+ *aDecision = nsIContentPolicy::REJECT_SERVER;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentBlocker::ShouldProcess(uint32_t aContentType,
+ nsIURI *aContentLocation,
+ nsIURI *aRequestingLocation,
+ nsISupports *aRequestingContext,
+ const nsACString &aMimeGuess,
+ nsISupports *aExtra,
+ nsIPrincipal *aRequestPrincipal,
+ int16_t *aDecision)
+{
+ MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
+ "We should only see external content policy types here.");
+
+ // For loads where aRequestingContext is chrome, we should just
+ // accept. Those are most likely toplevel loads in windows, and
+ // chrome generally knows what it's doing anyway.
+ nsCOMPtr<nsIDocShellTreeItem> item =
+ do_QueryInterface(NS_CP_GetDocShellFromContext(aRequestingContext));
+
+ if (item && item->ItemType() == nsIDocShellTreeItem::typeChrome) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+
+ // For objects, we only check policy in shouldProcess, as the final type isn't
+ // determined until the channel is open -- We don't want to block images in
+ // object tags because plugins are disallowed.
+ // NOTE that this bypasses the aContentLocation checks in ShouldLoad - this is
+ // intentional, as aContentLocation may be null for plugins that load by type
+ // (e.g. java)
+ if (aContentType == nsIContentPolicy::TYPE_OBJECT) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+
+ bool shouldLoad, fromPrefs;
+ nsresult rv = TestPermission(aContentLocation, aRequestingLocation,
+ aContentType, &shouldLoad, &fromPrefs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!shouldLoad) {
+ if (fromPrefs) {
+ *aDecision = nsIContentPolicy::REJECT_TYPE;
+ } else {
+ *aDecision = nsIContentPolicy::REJECT_SERVER;
+ }
+ }
+ return NS_OK;
+ }
+
+ // This isn't a load from chrome or an object tag - Just do a ShouldLoad()
+ // check -- we want the same answer here
+ return ShouldLoad(aContentType, aContentLocation, aRequestingLocation,
+ aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal,
+ aDecision);
+}
+
+nsresult
+nsContentBlocker::TestPermission(nsIURI *aCurrentURI,
+ nsIURI *aFirstURI,
+ int32_t aContentType,
+ bool *aPermission,
+ bool *aFromPrefs)
+{
+ *aFromPrefs = false;
+
+ if (!*kTypeString[aContentType - 1]) {
+ // Disallow internal content policy types, they should not be used here.
+ *aPermission = false;
+ return NS_OK;
+ }
+
+ // This default will also get used if there is an unknown value in the
+ // permission list, or if the permission manager returns unknown values.
+ *aPermission = true;
+
+ // check the permission list first; if we find an entry, it overrides
+ // default prefs.
+ // Don't forget the aContentType ranges from 1..8, while the
+ // array is indexed 0..7
+ uint32_t permission;
+ nsresult rv = mPermissionManager->TestPermission(aCurrentURI,
+ kTypeString[aContentType - 1],
+ &permission);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If there is nothing on the list, use the default.
+ if (!permission) {
+ permission = mBehaviorPref[aContentType - 1];
+ *aFromPrefs = true;
+ }
+
+ // Use the fact that the nsIPermissionManager values map to
+ // the BEHAVIOR_* values above.
+ switch (permission) {
+ case BEHAVIOR_ACCEPT:
+ *aPermission = true;
+ break;
+ case BEHAVIOR_REJECT:
+ *aPermission = false;
+ break;
+
+ case BEHAVIOR_NOFOREIGN:
+ // Third party checking
+
+ // Need a requesting uri for third party checks to work.
+ if (!aFirstURI)
+ return NS_OK;
+
+ bool trustedSource = false;
+ rv = aFirstURI->SchemeIs("chrome", &trustedSource);
+ NS_ENSURE_SUCCESS(rv,rv);
+ if (!trustedSource) {
+ rv = aFirstURI->SchemeIs("resource", &trustedSource);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ if (trustedSource)
+ return NS_OK;
+
+ // compare tails of names checking to see if they have a common domain
+ // we do this by comparing the tails of both names where each tail
+ // includes at least one dot
+
+ // A more generic method somewhere would be nice
+
+ nsAutoCString currentHost;
+ rv = aCurrentURI->GetAsciiHost(currentHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Search for two dots, starting at the end.
+ // If there are no two dots found, ++dot will turn to zero,
+ // that will return the entire string.
+ int32_t dot = currentHost.RFindChar('.');
+ dot = currentHost.RFindChar('.', dot-1);
+ ++dot;
+
+ // Get the domain, ie the last part of the host (www.domain.com -> domain.com)
+ // This will break on co.uk
+ const nsCSubstring &tail =
+ Substring(currentHost, dot, currentHost.Length() - dot);
+
+ nsAutoCString firstHost;
+ rv = aFirstURI->GetAsciiHost(firstHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the tail is longer then the whole firstHost, it will never match
+ if (firstHost.Length() < tail.Length()) {
+ *aPermission = false;
+ return NS_OK;
+ }
+
+ // Get the last part of the firstUri with the same length as |tail|
+ const nsCSubstring &firstTail =
+ Substring(firstHost, firstHost.Length() - tail.Length(), tail.Length());
+
+ // Check that both tails are the same, and that just before the tail in
+ // |firstUri| there is a dot. That means both url are in the same domain
+ if ((firstHost.Length() > tail.Length() &&
+ firstHost.CharAt(firstHost.Length() - tail.Length() - 1) != '.') ||
+ !tail.Equals(firstTail)) {
+ *aPermission = false;
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentBlocker::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ NS_ASSERTION(!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic),
+ "unexpected topic - we only deal with pref changes!");
+
+ if (mPrefBranchInternal)
+ PrefChanged(mPrefBranchInternal, NS_LossyConvertUTF16toASCII(aData).get());
+ return NS_OK;
+}
diff --git a/components/permissions/src/nsContentBlocker.h b/components/permissions/src/nsContentBlocker.h
new file mode 100644
index 000000000..880300952
--- /dev/null
+++ b/components/permissions/src/nsContentBlocker.h
@@ -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/. */
+#ifndef nsContentBlocker_h__
+#define nsContentBlocker_h__
+
+#include "nsIContentPolicy.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrefBranch.h"
+#include "mozilla/Attributes.h"
+
+class nsIPrefBranch;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsContentBlocker final : public nsIContentPolicy,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+ NS_DECL_NSIOBSERVER
+
+ nsContentBlocker();
+ nsresult Init();
+
+private:
+ ~nsContentBlocker() {}
+
+ void PrefChanged(nsIPrefBranch *, const char *);
+ nsresult TestPermission(nsIURI *aCurrentURI,
+ nsIURI *aFirstURI,
+ int32_t aContentType,
+ bool *aPermission,
+ bool *aFromPrefs);
+
+ nsCOMPtr<nsIPermissionManager> mPermissionManager;
+ nsCOMPtr<nsIPrefBranch> mPrefBranchInternal;
+ static uint8_t mBehaviorPref[];
+};
+
+#define NS_CONTENTBLOCKER_CID \
+{ 0x4ca6b67b, 0x5cc7, 0x4e71, \
+ { 0xa9, 0x8a, 0x97, 0xaf, 0x1c, 0x13, 0x48, 0x62 } }
+
+#define NS_CONTENTBLOCKER_CONTRACTID "@mozilla.org/permissions/contentblocker;1"
+
+#endif /* nsContentBlocker_h__ */
diff --git a/components/permissions/src/nsCookiePermission.cpp b/components/permissions/src/nsCookiePermission.cpp
new file mode 100644
index 000000000..21e555376
--- /dev/null
+++ b/components/permissions/src/nsCookiePermission.cpp
@@ -0,0 +1,272 @@
+/* -*- 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 "nsCookiePermission.h"
+
+#include "mozIThirdPartyUtil.h"
+#include "nsICookie2.h"
+#include "nsIServiceManager.h"
+#include "nsICookiePromptService.h"
+#include "nsICookieManager2.h"
+#include "nsNetUtil.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIDOMWindow.h"
+#include "nsIPrincipal.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsILoadContext.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsNetCID.h"
+#include "prtime.h"
+
+/****************************************************************
+ ************************ nsCookiePermission ********************
+ ****************************************************************/
+
+// values for mCookiesLifetimePolicy
+// 0 == accept normally
+// 1 == ask before accepting, no more supported, treated like ACCEPT_NORMALLY (Bug 606655).
+// 2 == downgrade to session
+// 3 == limit lifetime to N days
+static const uint32_t ACCEPT_NORMALLY = 0;
+static const uint32_t ASK_BEFORE_ACCEPT = 1;
+static const uint32_t ACCEPT_SESSION = 2;
+static const uint32_t ACCEPT_FOR_N_DAYS = 3;
+
+static const bool kDefaultPolicy = true;
+static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
+static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days";
+
+static const char kCookiesPrefsMigrated[] = "network.cookie.prefsMigrated";
+// obsolete pref names for migration
+static const char kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled";
+static const char kCookiesLifetimeBehavior[] = "network.cookie.lifetime.behavior";
+
+static const char kPermissionType[] = "cookie";
+
+NS_IMPL_ISUPPORTS(nsCookiePermission,
+ nsICookiePermission,
+ nsIObserver)
+
+bool
+nsCookiePermission::Init()
+{
+ // Initialize nsIPermissionManager and fetch relevant prefs. This is only
+ // required for some methods on nsICookiePermission, so it should be done
+ // lazily.
+ nsresult rv;
+ mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return false;
+ mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return false;
+
+ // failure to access the pref service is non-fatal...
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ prefBranch->AddObserver(kCookiesLifetimePolicy, this, false);
+ prefBranch->AddObserver(kCookiesLifetimeDays, this, false);
+ PrefChanged(prefBranch, nullptr);
+
+ // migration code for original cookie prefs
+ bool migrated;
+ rv = prefBranch->GetBoolPref(kCookiesPrefsMigrated, &migrated);
+ if (NS_FAILED(rv) || !migrated) {
+ bool lifetimeEnabled = false;
+ prefBranch->GetBoolPref(kCookiesLifetimeEnabled, &lifetimeEnabled);
+
+ // if they're limiting lifetime, use the appropriate limited lifetime pref
+ if (lifetimeEnabled) {
+ int32_t lifetimeBehavior;
+ prefBranch->GetIntPref(kCookiesLifetimeBehavior, &lifetimeBehavior);
+ if (lifetimeBehavior)
+ prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_FOR_N_DAYS);
+ else
+ prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_SESSION);
+ }
+ prefBranch->SetBoolPref(kCookiesPrefsMigrated, true);
+ }
+ }
+
+ return true;
+}
+
+void
+nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch,
+ const char *aPref)
+{
+ int32_t val;
+
+#define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P))
+
+ if (PREF_CHANGED(kCookiesLifetimePolicy) &&
+ NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimePolicy, &val))) {
+ if (val != static_cast<int32_t>(ACCEPT_SESSION) && val != static_cast<int32_t>(ACCEPT_FOR_N_DAYS)) {
+ val = ACCEPT_NORMALLY;
+ }
+ mCookiesLifetimePolicy = val;
+ }
+
+ if (PREF_CHANGED(kCookiesLifetimeDays) &&
+ NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimeDays, &val)))
+ // save cookie lifetime in seconds instead of days
+ mCookiesLifetimeSec = (int64_t)val * 24 * 60 * 60;
+}
+
+NS_IMETHODIMP
+nsCookiePermission::SetAccess(nsIURI *aURI,
+ nsCookieAccess aAccess)
+{
+ // Lazily initialize ourselves
+ if (!EnsureInitialized())
+ return NS_ERROR_UNEXPECTED;
+
+ //
+ // NOTE: nsCookieAccess values conveniently match up with
+ // the permission codes used by nsIPermissionManager.
+ // this is nice because it avoids conversion code.
+ //
+ return mPermMgr->Add(aURI, kPermissionType, aAccess,
+ nsIPermissionManager::EXPIRE_NEVER, 0);
+}
+
+NS_IMETHODIMP
+nsCookiePermission::CanAccess(nsIURI *aURI,
+ nsIChannel *aChannel,
+ nsCookieAccess *aResult)
+{
+ // Check this protocol doesn't allow cookies
+ bool hasFlags;
+ nsresult rv =
+ NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS,
+ &hasFlags);
+ if (NS_FAILED(rv) || hasFlags) {
+ *aResult = ACCESS_DENY;
+ return NS_OK;
+ }
+
+ // Lazily initialize ourselves
+ if (!EnsureInitialized())
+ return NS_ERROR_UNEXPECTED;
+
+ // finally, check with permission manager...
+ rv = mPermMgr->TestPermission(aURI, kPermissionType, (uint32_t *) aResult);
+ if (NS_SUCCEEDED(rv)) {
+ if (*aResult == nsICookiePermission::ACCESS_SESSION) {
+ *aResult = nsICookiePermission::ACCESS_ALLOW;
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCookiePermission::CanSetCookie(nsIURI *aURI,
+ nsIChannel *aChannel,
+ nsICookie2 *aCookie,
+ bool *aIsSession,
+ int64_t *aExpiry,
+ bool *aResult)
+{
+ NS_ASSERTION(aURI, "null uri");
+
+ *aResult = kDefaultPolicy;
+
+ // Lazily initialize ourselves
+ if (!EnsureInitialized())
+ return NS_ERROR_UNEXPECTED;
+
+ uint32_t perm;
+ mPermMgr->TestPermission(aURI, kPermissionType, &perm);
+ bool isThirdParty = false;
+ switch (perm) {
+ case nsICookiePermission::ACCESS_SESSION:
+ *aIsSession = true;
+ MOZ_FALLTHROUGH;
+
+ case nsICookiePermission::ACCESS_ALLOW:
+ *aResult = true;
+ break;
+
+ case nsICookiePermission::ACCESS_DENY:
+ *aResult = false;
+ break;
+
+ case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aURI, &isThirdParty);
+ // If it's third party, we can't set the cookie
+ if (isThirdParty)
+ *aResult = false;
+ break;
+
+ case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aURI, &isThirdParty);
+ // If it's third party, check whether cookies are already set
+ if (isThirdParty) {
+ nsresult rv;
+ nsCOMPtr<nsICookieManager2> cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ *aResult = false;
+ break;
+ }
+ uint32_t priorCookieCount = 0;
+ nsAutoCString hostFromURI;
+ aURI->GetHost(hostFromURI);
+ cookieManager->CountCookiesFromHost(hostFromURI, &priorCookieCount);
+ *aResult = priorCookieCount != 0;
+ }
+ break;
+
+ default:
+ // the permission manager has nothing to say about this cookie -
+ // so, we apply the default prefs to it.
+ NS_ASSERTION(perm == nsIPermissionManager::UNKNOWN_ACTION, "unknown permission");
+
+ // now we need to figure out what type of accept policy we're dealing with
+ // if we accept cookies normally, just bail and return
+ if (mCookiesLifetimePolicy == ACCEPT_NORMALLY) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ // declare this here since it'll be used in all of the remaining cases
+ int64_t currentTime = PR_Now() / PR_USEC_PER_SEC;
+ int64_t delta = *aExpiry - currentTime;
+
+ // We are accepting the cookie, but,
+ // if it's not a session cookie, we may have to limit its lifetime.
+ if (!*aIsSession && delta > 0) {
+ if (mCookiesLifetimePolicy == ACCEPT_SESSION) {
+ // limit lifetime to session
+ *aIsSession = true;
+ } else if (delta > mCookiesLifetimeSec) {
+ // limit lifetime to specified time
+ *aExpiry = currentTime + mCookiesLifetimeSec;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCookiePermission::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic),
+ "unexpected topic - we only deal with pref changes!");
+
+ if (prefBranch)
+ PrefChanged(prefBranch, NS_LossyConvertUTF16toASCII(aData).get());
+ return NS_OK;
+}
diff --git a/components/permissions/src/nsCookiePermission.h b/components/permissions/src/nsCookiePermission.h
new file mode 100644
index 000000000..d683f206a
--- /dev/null
+++ b/components/permissions/src/nsCookiePermission.h
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCookiePermission_h__
+#define nsCookiePermission_h__
+
+#include "nsICookiePermission.h"
+#include "nsIPermissionManager.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "mozIThirdPartyUtil.h"
+
+class nsIPrefBranch;
+
+class nsCookiePermission : public nsICookiePermission
+ , public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOOKIEPERMISSION
+ NS_DECL_NSIOBSERVER
+
+ nsCookiePermission()
+ : mCookiesLifetimeSec(INT64_MAX)
+ , mCookiesLifetimePolicy(0) // ACCEPT_NORMALLY
+ {}
+
+ bool Init();
+ void PrefChanged(nsIPrefBranch *, const char *);
+
+private:
+ virtual ~nsCookiePermission() {}
+
+ bool EnsureInitialized() { return (mPermMgr != nullptr && mThirdPartyUtil != nullptr) || Init(); };
+
+ nsCOMPtr<nsIPermissionManager> mPermMgr;
+ nsCOMPtr<mozIThirdPartyUtil> mThirdPartyUtil;
+
+ int64_t mCookiesLifetimeSec; // lifetime limit specified in seconds
+ uint8_t mCookiesLifetimePolicy; // pref for how long cookies are stored
+};
+
+// {EF565D0A-AB9A-4A13-9160-0644CDFD859A}
+#define NS_COOKIEPERMISSION_CID \
+ {0xEF565D0A, 0xAB9A, 0x4A13, {0x91, 0x60, 0x06, 0x44, 0xcd, 0xfd, 0x85, 0x9a }}
+
+#endif
diff --git a/components/permissions/src/nsCookiePromptService.cpp b/components/permissions/src/nsCookiePromptService.cpp
new file mode 100644
index 000000000..258193ab8
--- /dev/null
+++ b/components/permissions/src/nsCookiePromptService.cpp
@@ -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/. */
+
+
+#include "nsCookiePromptService.h"
+#include "nsICookie.h"
+#include "nsICookieAcceptDialog.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWindowWatcher.h"
+#include "nsIServiceManager.h"
+#include "nsString.h"
+#include "nsIDialogParamBlock.h"
+#include "nsIMutableArray.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+/****************************************************************
+ ************************ nsCookiePromptService *****************
+ ****************************************************************/
+
+NS_IMPL_ISUPPORTS(nsCookiePromptService, nsICookiePromptService)
+
+nsCookiePromptService::nsCookiePromptService() {
+}
+
+nsCookiePromptService::~nsCookiePromptService() {
+}
+
+NS_IMETHODIMP
+nsCookiePromptService::CookieDialog(mozIDOMWindowProxy *aParent,
+ nsICookie *aCookie,
+ const nsACString &aHostname,
+ int32_t aCookiesFromHost,
+ bool aChangingCookie,
+ bool *aRememberDecision,
+ int32_t *aAccept)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIDialogParamBlock> block = do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID,&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ block->SetInt(nsICookieAcceptDialog::ACCEPT_COOKIE, 1);
+ block->SetString(nsICookieAcceptDialog::HOSTNAME, NS_ConvertUTF8toUTF16(aHostname).get());
+ block->SetInt(nsICookieAcceptDialog::COOKIESFROMHOST, aCookiesFromHost);
+ block->SetInt(nsICookieAcceptDialog::CHANGINGCOOKIE, aChangingCookie ? 1 : 0);
+
+ nsCOMPtr<nsIMutableArray> objects =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = objects->AppendElement(aCookie, false);
+ if (NS_FAILED(rv)) return rv;
+
+ block->SetObjects(objects);
+
+ nsCOMPtr<nsIWindowWatcher> wwatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISupports> arguments = do_QueryInterface(block);
+
+ nsCOMPtr<mozIDOMWindowProxy> parent(aParent);
+ if (!parent) // if no parent provided, consult the window watcher:
+ wwatcher->GetActiveWindow(getter_AddRefs(parent));
+
+ if (parent) {
+ auto* privateParent = nsPIDOMWindowOuter::From(parent);
+ if (privateParent)
+ privateParent = privateParent->GetPrivateRoot();
+ parent = privateParent;
+ }
+
+ // We're opening a chrome window and passing in a nsIDialogParamBlock. Setting
+ // the nsIDialogParamBlock as the .arguments property on the chrome window
+ // requires system principals on the stack, so we use an AutoNoJSAPI for that.
+ mozilla::dom::AutoNoJSAPI nojsapi;
+
+ // The cookie dialog will be modal for the root chrome window rather than the
+ // tab containing the permission-requesting page. This removes confusion
+ // about which monitor is displaying the dialog (see bug 470356), but also
+ // avoids unwanted tab switches (see bug 405239).
+ nsCOMPtr<mozIDOMWindowProxy> dialog;
+ rv = wwatcher->OpenWindow(parent, "chrome://cookie/content/cookieAcceptDialog.xul", "_blank",
+ "centerscreen,chrome,modal,titlebar", arguments,
+ getter_AddRefs(dialog));
+
+ if (NS_FAILED(rv)) return rv;
+
+ // get back output parameters
+ int32_t tempValue;
+ block->GetInt(nsICookieAcceptDialog::ACCEPT_COOKIE, &tempValue);
+ *aAccept = tempValue;
+
+ // GetInt returns a int32_t; we need to sanitize it into bool
+ block->GetInt(nsICookieAcceptDialog::REMEMBER_DECISION, &tempValue);
+ *aRememberDecision = (tempValue == 1);
+
+ return rv;
+}
+
diff --git a/components/permissions/src/nsCookiePromptService.h b/components/permissions/src/nsCookiePromptService.h
new file mode 100644
index 000000000..a6e10fce4
--- /dev/null
+++ b/components/permissions/src/nsCookiePromptService.h
@@ -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/. */
+
+#ifndef nsCookiePromptService_h__
+#define nsCookiePromptService_h__
+
+#include "nsICookiePromptService.h"
+
+class nsCookiePromptService : public nsICookiePromptService {
+
+ virtual ~nsCookiePromptService();
+
+public:
+
+ nsCookiePromptService();
+
+ NS_DECL_NSICOOKIEPROMPTSERVICE
+ NS_DECL_ISUPPORTS
+
+private:
+
+};
+
+// {CE002B28-92B7-4701-8621-CC925866FB87}
+#define NS_COOKIEPROMPTSERVICE_CID \
+ {0xCE002B28, 0x92B7, 0x4701, {0x86, 0x21, 0xCC, 0x92, 0x58, 0x66, 0xFB, 0x87}}
+
+#endif
diff --git a/components/permissions/src/nsPermission.cpp b/components/permissions/src/nsPermission.cpp
new file mode 100644
index 000000000..3d1eb140a
--- /dev/null
+++ b/components/permissions/src/nsPermission.cpp
@@ -0,0 +1,201 @@
+/* -*- 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 "nsPermission.h"
+#include "nsContentUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIEffectiveTLDService.h"
+#include "mozilla/BasePrincipal.h"
+
+// nsPermission Implementation
+
+NS_IMPL_CLASSINFO(nsPermission, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(nsPermission, nsIPermission)
+
+nsPermission::nsPermission(nsIPrincipal* aPrincipal,
+ const nsACString &aType,
+ uint32_t aCapability,
+ uint32_t aExpireType,
+ int64_t aExpireTime)
+ : mPrincipal(aPrincipal)
+ , mType(aType)
+ , mCapability(aCapability)
+ , mExpireType(aExpireType)
+ , mExpireTime(aExpireTime)
+{
+}
+
+already_AddRefed<nsPermission>
+nsPermission::Create(nsIPrincipal* aPrincipal,
+ const nsACString &aType,
+ uint32_t aCapability,
+ uint32_t aExpireType,
+ int64_t aExpireTime)
+{
+ NS_ENSURE_TRUE(aPrincipal, nullptr);
+ nsCOMPtr<nsIPrincipal> principal =
+ mozilla::BasePrincipal::Cast(aPrincipal)->CloneStrippingUserContextIdAndFirstPartyDomain();
+
+ NS_ENSURE_TRUE(principal, nullptr);
+
+ RefPtr<nsPermission> permission =
+ new nsPermission(principal, aType, aCapability, aExpireType, aExpireTime);
+ return permission.forget();
+}
+
+NS_IMETHODIMP
+nsPermission::GetPrincipal(nsIPrincipal** aPrincipal)
+{
+ nsCOMPtr<nsIPrincipal> copy = mPrincipal;
+ copy.forget(aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::GetType(nsACString &aType)
+{
+ aType = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::GetCapability(uint32_t *aCapability)
+{
+ *aCapability = mCapability;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::GetExpireType(uint32_t *aExpireType)
+{
+ *aExpireType = mExpireType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::GetExpireTime(int64_t *aExpireTime)
+{
+ *aExpireTime = mExpireTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::Matches(nsIPrincipal* aPrincipal, bool aExactHost, bool* aMatches)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aMatches);
+
+ *aMatches = false;
+
+ nsCOMPtr<nsIPrincipal> principal =
+ mozilla::BasePrincipal::Cast(aPrincipal)->CloneStrippingUserContextIdAndFirstPartyDomain();
+
+ if (!principal) {
+ *aMatches = false;
+ return NS_OK;
+ }
+
+ // If the principals are equal, then they match.
+ if (mPrincipal->Equals(principal)) {
+ *aMatches = true;
+ return NS_OK;
+ }
+
+ // If we are matching with an exact host, we're done now - the permissions don't match
+ // otherwise, we need to start comparing subdomains!
+ if (aExactHost) {
+ return NS_OK;
+ }
+
+ // Compare their OriginAttributes
+ const mozilla::PrincipalOriginAttributes& theirAttrs = mozilla::BasePrincipal::Cast(principal)->OriginAttributesRef();
+ const mozilla::PrincipalOriginAttributes& ourAttrs = mozilla::BasePrincipal::Cast(mPrincipal)->OriginAttributesRef();
+
+ if (theirAttrs != ourAttrs) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> theirURI;
+ nsresult rv = principal->GetURI(getter_AddRefs(theirURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> ourURI;
+ rv = mPrincipal->GetURI(getter_AddRefs(ourURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Compare schemes
+ nsAutoCString theirScheme;
+ rv = theirURI->GetScheme(theirScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ourScheme;
+ rv = ourURI->GetScheme(ourScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (theirScheme != ourScheme) {
+ return NS_OK;
+ }
+
+ // Compare ports
+ int32_t theirPort;
+ rv = theirURI->GetPort(&theirPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t ourPort;
+ rv = ourURI->GetPort(&ourPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (theirPort != ourPort) {
+ return NS_OK;
+ }
+
+ // Check if the host or any subdomain of their host matches.
+ nsAutoCString theirHost;
+ rv = theirURI->GetHost(theirHost);
+ if (NS_FAILED(rv) || theirHost.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsAutoCString ourHost;
+ rv = ourURI->GetHost(ourHost);
+ if (NS_FAILED(rv) || ourHost.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ NS_ERROR("Should have a tld service!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // This loop will not loop forever, as GetNextSubDomain will eventually fail
+ // with NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS.
+ while (theirHost != ourHost) {
+ rv = tldService->GetNextSubDomain(theirHost, theirHost);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ return NS_OK;
+ } else {
+ return rv;
+ }
+ }
+ }
+
+ *aMatches = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::MatchesURI(nsIURI* aURI, bool aExactHost, bool* aMatches)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ mozilla::PrincipalOriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+ NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
+
+ return Matches(principal, aExactHost, aMatches);
+}
diff --git a/components/permissions/src/nsPermission.h b/components/permissions/src/nsPermission.h
new file mode 100644
index 000000000..6beb8aef4
--- /dev/null
+++ b/components/permissions/src/nsPermission.h
@@ -0,0 +1,43 @@
+/* -*- 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/. */
+
+#ifndef nsPermission_h__
+#define nsPermission_h__
+
+#include "nsIPermission.h"
+#include "nsString.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsPermission : public nsIPermission
+{
+public:
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPERMISSION
+
+ static already_AddRefed<nsPermission> Create(nsIPrincipal* aPrincipal,
+ const nsACString &aType,
+ uint32_t aCapability,
+ uint32_t aExpireType,
+ int64_t aExpireTime);
+
+protected:
+ nsPermission(nsIPrincipal* aPrincipal,
+ const nsACString &aType,
+ uint32_t aCapability,
+ uint32_t aExpireType,
+ int64_t aExpireTime);
+
+ virtual ~nsPermission() {};
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCString mType;
+ uint32_t mCapability;
+ uint32_t mExpireType;
+ int64_t mExpireTime;
+};
+
+#endif // nsPermission_h__
diff --git a/components/permissions/src/nsPermissionFactory.cpp b/components/permissions/src/nsPermissionFactory.cpp
new file mode 100644
index 000000000..e170c9460
--- /dev/null
+++ b/components/permissions/src/nsPermissionFactory.cpp
@@ -0,0 +1,60 @@
+/* -*- 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 "nsIServiceManager.h"
+#include "nsPermissionManager.h"
+#include "nsPopupWindowManager.h"
+#include "nsICategoryManager.h"
+#include "nsContentBlocker.h"
+#include "nsCookiePromptService.h"
+#include "nsCookiePermission.h"
+#include "nsXPIDLString.h"
+
+// Define the constructor function for the objects
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPermissionManager,
+ nsPermissionManager::GetXPCOMSingleton)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPopupWindowManager, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsContentBlocker, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCookiePermission)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCookiePromptService)
+
+NS_DEFINE_NAMED_CID(NS_PERMISSIONMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_POPUPWINDOWMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_CONTENTBLOCKER_CID);
+NS_DEFINE_NAMED_CID(NS_COOKIEPROMPTSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_COOKIEPERMISSION_CID);
+
+static const mozilla::Module::CIDEntry kPermissionsCIDs[] = {
+ { &kNS_PERMISSIONMANAGER_CID, false, nullptr, nsIPermissionManagerConstructor },
+ { &kNS_POPUPWINDOWMANAGER_CID, false, nullptr, nsPopupWindowManagerConstructor },
+ { &kNS_CONTENTBLOCKER_CID, false, nullptr, nsContentBlockerConstructor },
+ { &kNS_COOKIEPROMPTSERVICE_CID, false, nullptr, nsCookiePromptServiceConstructor },
+ { &kNS_COOKIEPERMISSION_CID, false, nullptr, nsCookiePermissionConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kPermissionsContracts[] = {
+ { NS_PERMISSIONMANAGER_CONTRACTID, &kNS_PERMISSIONMANAGER_CID },
+ { NS_POPUPWINDOWMANAGER_CONTRACTID, &kNS_POPUPWINDOWMANAGER_CID },
+ { NS_CONTENTBLOCKER_CONTRACTID, &kNS_CONTENTBLOCKER_CID },
+ { NS_COOKIEPROMPTSERVICE_CONTRACTID, &kNS_COOKIEPROMPTSERVICE_CID },
+ { NS_COOKIEPERMISSION_CONTRACTID, &kNS_COOKIEPERMISSION_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kPermissionsCategories[] = {
+ { "content-policy", NS_CONTENTBLOCKER_CONTRACTID, NS_CONTENTBLOCKER_CONTRACTID },
+ { nullptr }
+};
+
+static const mozilla::Module kPermissionsModule = {
+ mozilla::Module::kVersion,
+ kPermissionsCIDs,
+ kPermissionsContracts,
+ kPermissionsCategories
+};
+
+NSMODULE_DEFN(nsPermissionsModule) = &kPermissionsModule;
diff --git a/components/permissions/src/nsPermissionManager.cpp b/components/permissions/src/nsPermissionManager.cpp
new file mode 100644
index 000000000..afdaea63f
--- /dev/null
+++ b/components/permissions/src/nsPermissionManager.cpp
@@ -0,0 +1,2862 @@
+/* -*- 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/Attributes.h"
+#include "mozilla/DebugOnly.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "nsPermissionManager.h"
+#include "nsPermission.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsTArray.h"
+#include "nsReadableUtils.h"
+#include "nsILineInputStream.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "prprf.h"
+#include "mozilla/storage.h"
+#include "mozilla/Attributes.h"
+#include "nsXULAppAPI.h"
+#include "nsIPrincipal.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocument.h"
+#include "mozilla/net/NeckoMessageUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsReadLine.h"
+#include "nsIConsoleService.h"
+#include "nsINavHistoryService.h"
+#include "nsToolkitCompsCID.h"
+#include "nsIObserverService.h"
+
+static nsPermissionManager *gPermissionManager = nullptr;
+
+using mozilla::dom::ContentParent;
+using mozilla::dom::ContentChild;
+using mozilla::Unused; // ha!
+
+static bool
+IsChildProcess()
+{
+ return XRE_IsContentProcess();
+}
+
+/**
+ * @returns The child process object, or if we are not in the child
+ * process, nullptr.
+ */
+static ContentChild*
+ChildProcess()
+{
+ if (IsChildProcess()) {
+ ContentChild* cpc = ContentChild::GetSingleton();
+ if (!cpc)
+ NS_RUNTIMEABORT("Content Process is nullptr!");
+ return cpc;
+ }
+
+ return nullptr;
+}
+
+static void
+LogToConsole(const nsAString& aMsg)
+{
+ nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
+ if (!console) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+
+ nsAutoString msg(aMsg);
+ console->LogStringMessage(msg.get());
+}
+
+#define ENSURE_NOT_CHILD_PROCESS_(onError) \
+ PR_BEGIN_MACRO \
+ if (IsChildProcess()) { \
+ NS_ERROR("Cannot perform action in content process!"); \
+ onError \
+ } \
+ PR_END_MACRO
+
+#define ENSURE_NOT_CHILD_PROCESS \
+ ENSURE_NOT_CHILD_PROCESS_({ return NS_ERROR_NOT_AVAILABLE; })
+
+#define ENSURE_NOT_CHILD_PROCESS_NORET \
+ ENSURE_NOT_CHILD_PROCESS_(;)
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+nsresult
+GetOriginFromPrincipal(nsIPrincipal* aPrincipal, nsACString& aOrigin)
+{
+ nsresult rv = aPrincipal->GetOriginNoSuffix(aOrigin);
+ // The principal may belong to the about:blank content viewer, so this can be
+ // expected to fail.
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString suffix;
+ rv = aPrincipal->GetOriginSuffix(suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromSuffix(suffix)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // mPrivateBrowsingId must be set to false because PermissionManager is not supposed to have
+ // any knowledge of private browsing. Allowing it to be true changes the suffix being hashed.
+ attrs.mPrivateBrowsingId = 0;
+
+ // Disable userContext and firstParty isolation for permissions.
+ attrs.StripUserContextIdAndFirstPartyDomain();
+
+ attrs.CreateSuffix(suffix);
+ aOrigin.Append(suffix);
+ return NS_OK;
+}
+
+nsresult
+GetPrincipalFromOrigin(const nsACString& aOrigin, nsIPrincipal** aPrincipal)
+{
+ nsAutoCString originNoSuffix;
+ mozilla::PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Disable userContext and firstParty isolation for permissions.
+ attrs.StripUserContextIdAndFirstPartyDomain();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs);
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+nsresult
+GetPrincipal(nsIURI* aURI, uint32_t aAppId, bool aIsInIsolatedMozBrowserElement, nsIPrincipal** aPrincipal)
+{
+ mozilla::PrincipalOriginAttributes attrs(aAppId, aIsInIsolatedMozBrowserElement);
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+ NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
+
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+nsresult
+GetPrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal)
+{
+ mozilla::PrincipalOriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+ NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
+
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+nsCString
+GetNextSubDomainForHost(const nsACString& aHost)
+{
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ NS_ERROR("Should have a tld service!");
+ return EmptyCString();
+ }
+
+ nsCString subDomain;
+ nsresult rv = tldService->GetNextSubDomain(aHost, subDomain);
+ // We can fail if there is no more subdomain or if the host can't have a
+ // subdomain.
+ if (NS_FAILED(rv)) {
+ return EmptyCString();
+ }
+
+ return subDomain;
+}
+
+class ClearOriginDataObserver final : public nsIObserver {
+ ~ClearOriginDataObserver() {}
+
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIObserver implementation.
+ NS_IMETHOD
+ Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) override
+ {
+ MOZ_ASSERT(!nsCRT::strcmp(aTopic, "clear-origin-attributes-data"));
+
+ nsCOMPtr<nsIPermissionManager> permManager = do_GetService("@mozilla.org/permissionmanager;1");
+ return permManager->RemovePermissionsWithAttributes(nsDependentString(aData));
+ }
+};
+
+NS_IMPL_ISUPPORTS(ClearOriginDataObserver, nsIObserver)
+
+class MOZ_STACK_CLASS UpgradeHostToOriginHelper {
+public:
+ virtual nsresult Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) = 0;
+};
+
+class MOZ_STACK_CLASS UpgradeHostToOriginDBMigration final : public UpgradeHostToOriginHelper {
+public:
+ UpgradeHostToOriginDBMigration(mozIStorageConnection* aDBConn, int64_t* aID) : mDBConn(aDBConn)
+ , mID(aID)
+ {
+ mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_hosts_new "
+ "(id, origin, type, permission, expireType, expireTime, modificationTime) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmt));
+ }
+
+ nsresult
+ Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) final
+ {
+ nsresult rv = mStmt->BindInt64ByIndex(0, *mID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(1, aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(2, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(3, aPermission);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(4, aExpireType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(5, aExpireTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(6, aModificationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Increment the working identifier, as we are about to use this one
+ (*mID)++;
+
+ rv = mStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<mozIStorageStatement> mStmt;
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ int64_t* mID;
+};
+
+class MOZ_STACK_CLASS UpgradeHostToOriginHostfileImport final : public UpgradeHostToOriginHelper {
+public:
+ UpgradeHostToOriginHostfileImport(nsPermissionManager* aPm,
+ nsPermissionManager::DBOperationType aOperation,
+ int64_t aID) : mPm(aPm)
+ , mOperation(aOperation)
+ , mID(aID)
+ {}
+
+ nsresult
+ Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) final
+ {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(aOrigin, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mPm->AddInternal(principal, aType, aPermission, mID,
+ aExpireType, aExpireTime, aModificationTime,
+ nsPermissionManager::eDontNotify, mOperation);
+ }
+
+private:
+ RefPtr<nsPermissionManager> mPm;
+ nsPermissionManager::DBOperationType mOperation;
+ int64_t mID;
+};
+
+class MOZ_STACK_CLASS UpgradeIPHostToOriginDB final : public UpgradeHostToOriginHelper {
+public:
+ UpgradeIPHostToOriginDB(mozIStorageConnection* aDBConn, int64_t* aID) : mDBConn(aDBConn)
+ , mID(aID)
+ {
+ mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_perms"
+ "(id, origin, type, permission, expireType, expireTime, modificationTime) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmt));
+
+ mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id FROM moz_perms WHERE origin = ?1 AND type = ?2"),
+ getter_AddRefs(mLookupStmt));
+ }
+
+ nsresult
+ Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) final
+ {
+ // Every time the migration code wants to insert an origin into
+ // the database we need to check to see if someone has already
+ // created a permissions entry for that permission. If they have,
+ // we don't want to insert a duplicate row.
+ //
+ // We can afford to do this lookup unconditionally and not perform
+ // caching, as a origin type pair should only be attempted to be
+ // inserted once.
+
+ nsresult rv = mLookupStmt->Reset();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mLookupStmt->BindUTF8StringByIndex(0, aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mLookupStmt->BindUTF8StringByIndex(1, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if we already have the row in the database, if we do, then
+ // we don't want to be inserting it again.
+ bool moreStmts = false;
+ if (NS_FAILED(mLookupStmt->ExecuteStep(&moreStmts)) || moreStmts) {
+ mLookupStmt->Reset();
+ NS_WARNING("A permissions entry was going to be re-migrated, "
+ "but was already found in the permissions database.");
+ return NS_OK;
+ }
+
+ // Actually insert the statement into the database.
+ rv = mStmt->BindInt64ByIndex(0, *mID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(1, aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(2, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(3, aPermission);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(4, aExpireType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(5, aExpireTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(6, aModificationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Increment the working identifier, as we are about to use this one
+ (*mID)++;
+
+ rv = mStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<mozIStorageStatement> mStmt;
+ nsCOMPtr<mozIStorageStatement> mLookupStmt;
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ int64_t* mID;
+};
+
+
+nsresult
+UpgradeHostToOriginAndInsert(const nsACString& aHost, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime, uint32_t aAppId, bool aIsInIsolatedMozBrowserElement,
+ UpgradeHostToOriginHelper* aHelper)
+{
+ if (aHost.EqualsLiteral("<file>")) {
+ // We no longer support the magic host <file>
+ NS_WARNING("The magic host <file> is no longer supported. "
+ "It is being removed from the permissions database.");
+ return NS_OK;
+ }
+
+ // First, we check to see if the host is a valid URI. If it is, it can be imported directly
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aHost);
+ if (NS_SUCCEEDED(rv)) {
+ // It was previously possible to insert useless entries to your permissions database
+ // for URIs which have a null principal. This acts as a cleanup, getting rid of
+ // these useless database entries
+ bool nullpScheme = false;
+ if (NS_SUCCEEDED(uri->SchemeIs("moz-nullprincipal", &nullpScheme)) && nullpScheme) {
+ NS_WARNING("A moz-nullprincipal: permission is being discarded.");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString origin;
+ rv = GetOriginFromPrincipal(principal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ return NS_OK;
+ }
+
+ // The user may use this host at non-standard ports or protocols, we can use their history
+ // to guess what ports and protocols we want to add permissions for.
+ // We find every URI which they have visited with this host (or a subdomain of this host),
+ // and try to add it as a principal.
+ bool foundHistory = false;
+
+ nsCOMPtr<nsINavHistoryService> histSrv = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
+
+ if (histSrv) {
+ nsCOMPtr<nsINavHistoryQuery> histQuery;
+ rv = histSrv->GetNewQuery(getter_AddRefs(histQuery));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the eTLD+1 of the domain
+ nsAutoCString eTLD1;
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ MOZ_ASSERT(tldService); // We should always have a tldService
+ if (tldService) {
+ rv = tldService->GetBaseDomainFromHost(aHost, 0, eTLD1);
+ }
+
+ if (!tldService || NS_FAILED(rv)) {
+ // If the lookup on the tldService for the base domain for the host failed,
+ // that means that we just want to directly use the host as the host name
+ // for the lookup.
+ eTLD1 = aHost;
+ }
+
+ // We want to only find history items for this particular eTLD+1, and subdomains
+ rv = histQuery->SetDomain(eTLD1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = histQuery->SetDomainIsHost(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryQueryOptions> histQueryOpts;
+ rv = histSrv->GetNewQueryOptions(getter_AddRefs(histQueryOpts));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We want to get the URIs for every item in the user's history with the given host
+ rv = histQueryOpts->SetResultType(nsINavHistoryQueryOptions::RESULTS_AS_URI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We only search history, because searching both bookmarks and history
+ // is not supported, and history tends to be more comprehensive.
+ rv = histQueryOpts->SetQueryType(nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We include hidden URIs (such as those visited via iFrames) as they may have permissions too
+ rv = histQueryOpts->SetIncludeHidden(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryResult> histResult;
+ rv = histSrv->ExecuteQuery(histQuery, histQueryOpts, getter_AddRefs(histResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryContainerResultNode> histResultContainer;
+ rv = histResult->GetRoot(getter_AddRefs(histResultContainer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = histResultContainer->SetContainerOpen(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t childCount = 0;
+ rv = histResultContainer->GetChildCount(&childCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTHashtable<nsCStringHashKey> insertedOrigins;
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsCOMPtr<nsINavHistoryResultNode> child;
+ histResultContainer->GetChild(i, getter_AddRefs(child));
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ uint32_t type;
+ rv = child->GetType(&type);
+ if (NS_WARN_IF(NS_FAILED(rv)) || type != nsINavHistoryResultNode::RESULT_TYPE_URI) {
+ NS_WARNING("Unexpected non-RESULT_TYPE_URI node in "
+ "UpgradeHostToOriginAndInsert()");
+ continue;
+ }
+
+ nsAutoCString uriSpec;
+ rv = child->GetUri(uriSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ // Use the provided host - this URI may be for a subdomain, rather than the host we care about.
+ rv = uri->SetHost(aHost);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ // We now have a URI which we can make a nsIPrincipal out of
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ nsAutoCString origin;
+ rv = GetOriginFromPrincipal(principal, origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ // Ensure that we don't insert the same origin repeatedly
+ if (insertedOrigins.Contains(origin)) {
+ continue;
+ }
+
+ foundHistory = true;
+ rv = aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Insert failed");
+ insertedOrigins.PutEntry(origin);
+ }
+
+ rv = histResultContainer->SetContainerOpen(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If we didn't find any origins for this host in the poermissions database,
+ // we can insert the default http:// and https:// permissions into the database.
+ // This has a relatively high liklihood of applying the permission to the correct
+ // origin.
+ if (!foundHistory) {
+ nsAutoCString hostSegment;
+ nsCOMPtr<nsIPrincipal> principal;
+ nsAutoCString origin;
+
+ // If this is an ipv6 URI, we need to surround it in '[', ']' before trying to
+ // parse it as a URI.
+ if (aHost.FindChar(':') != -1) {
+ hostSegment.Assign("[");
+ hostSegment.Append(aHost);
+ hostSegment.Append("]");
+ } else {
+ hostSegment.Assign(aHost);
+ }
+
+ // http:// URI default
+ rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + hostSegment);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetOriginFromPrincipal(principal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+
+ // https:// URI default
+ rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://") + hostSegment);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetOriginFromPrincipal(principal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ }
+
+ return NS_OK;
+}
+
+static bool
+IsExpandedPrincipal(nsIPrincipal* aPrincipal)
+{
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
+ return !!ep;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsPermissionManager::PermissionKey::PermissionKey(nsIPrincipal* aPrincipal)
+{
+ MOZ_ALWAYS_SUCCEEDS(GetOriginFromPrincipal(aPrincipal, mOrigin));
+}
+
+/**
+ * Simple callback used by |AsyncClose| to trigger a treatment once
+ * the database is closed.
+ *
+ * Note: Beware that, if you hold onto a |CloseDatabaseListener| from a
+ * |nsPermissionManager|, this will create a cycle.
+ *
+ * Note: Once the callback has been called this DeleteFromMozHostListener cannot
+ * be reused.
+ */
+class CloseDatabaseListener final : public mozIStorageCompletionCallback
+{
+ ~CloseDatabaseListener() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
+ /**
+ * @param aManager The owning manager.
+ * @param aRebuildOnSuccess If |true|, reinitialize the database once
+ * it has been closed. Otherwise, do nothing such.
+ */
+ CloseDatabaseListener(nsPermissionManager* aManager,
+ bool aRebuildOnSuccess);
+
+protected:
+ RefPtr<nsPermissionManager> mManager;
+ bool mRebuildOnSuccess;
+};
+
+NS_IMPL_ISUPPORTS(CloseDatabaseListener, mozIStorageCompletionCallback)
+
+CloseDatabaseListener::CloseDatabaseListener(nsPermissionManager* aManager,
+ bool aRebuildOnSuccess)
+ : mManager(aManager)
+ , mRebuildOnSuccess(aRebuildOnSuccess)
+{
+}
+
+NS_IMETHODIMP
+CloseDatabaseListener::Complete(nsresult, nsISupports*)
+{
+ // Help breaking cycles
+ RefPtr<nsPermissionManager> manager = mManager.forget();
+ if (mRebuildOnSuccess && !manager->mIsShuttingDown) {
+ return manager->InitDB(true);
+ }
+ return NS_OK;
+}
+
+
+/**
+ * Simple callback used by |RemoveAllInternal| to trigger closing
+ * the database and reinitializing it.
+ *
+ * Note: Beware that, if you hold onto a |DeleteFromMozHostListener| from a
+ * |nsPermissionManager|, this will create a cycle.
+ *
+ * Note: Once the callback has been called this DeleteFromMozHostListener cannot
+ * be reused.
+ */
+class DeleteFromMozHostListener final : public mozIStorageStatementCallback
+{
+ ~DeleteFromMozHostListener() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGESTATEMENTCALLBACK
+
+ /**
+ * @param aManager The owning manager.
+ */
+ explicit DeleteFromMozHostListener(nsPermissionManager* aManager);
+
+protected:
+ RefPtr<nsPermissionManager> mManager;
+};
+
+NS_IMPL_ISUPPORTS(DeleteFromMozHostListener, mozIStorageStatementCallback)
+
+DeleteFromMozHostListener::
+DeleteFromMozHostListener(nsPermissionManager* aManager)
+ : mManager(aManager)
+{
+}
+
+NS_IMETHODIMP DeleteFromMozHostListener::HandleResult(mozIStorageResultSet *)
+{
+ MOZ_CRASH("Should not get any results");
+}
+
+NS_IMETHODIMP DeleteFromMozHostListener::HandleError(mozIStorageError *)
+{
+ // Errors are handled in |HandleCompletion|
+ return NS_OK;
+}
+
+NS_IMETHODIMP DeleteFromMozHostListener::HandleCompletion(uint16_t aReason)
+{
+ // Help breaking cycles
+ RefPtr<nsPermissionManager> manager = mManager.forget();
+
+ if (aReason == REASON_ERROR) {
+ manager->CloseDB(true);
+ }
+
+ return NS_OK;
+}
+
+/* static */ void
+nsPermissionManager::ClearOriginDataObserverInit()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->AddObserver(new ClearOriginDataObserver(), "clear-origin-attributes-data", /* ownsWeak= */ false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsPermissionManager Implementation
+
+#define PERMISSIONS_FILE_NAME "permissions.sqlite"
+#define HOSTS_SCHEMA_VERSION 9
+
+#define HOSTPERM_FILE_NAME "hostperm.1"
+
+// Default permissions are read from a URL - this is the preference we read
+// to find that URL. If not set, don't use any default permissions.
+static const char kDefaultsUrlPrefName[] = "permissions.manager.defaultsUrl";
+
+static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
+
+NS_IMPL_ISUPPORTS(nsPermissionManager, nsIPermissionManager, nsIObserver, nsISupportsWeakReference)
+
+nsPermissionManager::nsPermissionManager()
+ : mMemoryOnlyDB(false)
+ , mLargestID(0)
+ , mIsShuttingDown(false)
+{
+}
+
+nsPermissionManager::~nsPermissionManager()
+{
+ RemoveAllFromMemory();
+ gPermissionManager = nullptr;
+}
+
+// static
+nsIPermissionManager*
+nsPermissionManager::GetXPCOMSingleton()
+{
+ if (gPermissionManager) {
+ NS_ADDREF(gPermissionManager);
+ return gPermissionManager;
+ }
+
+ // Create a new singleton nsPermissionManager.
+ // We AddRef only once since XPCOM has rules about the ordering of module
+ // teardowns - by the time our module destructor is called, it's too late to
+ // Release our members, since GC cycles have already been completed and
+ // would result in serious leaks.
+ // See bug 209571.
+ gPermissionManager = new nsPermissionManager();
+ if (gPermissionManager) {
+ NS_ADDREF(gPermissionManager);
+ if (NS_FAILED(gPermissionManager->Init())) {
+ NS_RELEASE(gPermissionManager);
+ }
+ }
+
+ return gPermissionManager;
+}
+
+nsresult
+nsPermissionManager::Init()
+{
+ // If the 'permissions.memory_only' pref is set to true, then don't write any
+ // permission settings to disk, but keep them in a memory-only database.
+ mMemoryOnlyDB = mozilla::Preferences::GetBool("permissions.memory_only", false);
+
+ if (IsChildProcess()) {
+ // Stop here; we don't need the DB in the child process
+ return FetchPermissions();
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "profile-before-change", true);
+ observerService->AddObserver(this, "profile-do-change", true);
+ }
+
+ // ignore failure here, since it's non-fatal (we can run fine without
+ // persistent storage - e.g. if there's no profile).
+ // XXX should we tell the user about this?
+ InitDB(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RefreshPermission() {
+ NS_ENSURE_TRUE(IsChildProcess(), NS_ERROR_FAILURE);
+
+ nsresult rv = RemoveAllFromMemory();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = FetchPermissions();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::OpenDatabase(nsIFile* aPermissionsFile)
+{
+ nsresult rv;
+ nsCOMPtr<mozIStorageService> storage = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ if (!storage) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // cache a connection to the hosts database
+ if (mMemoryOnlyDB) {
+ rv = storage->OpenSpecialDatabase("memory", getter_AddRefs(mDBConn));
+ } else {
+ rv = storage->OpenDatabase(aPermissionsFile, getter_AddRefs(mDBConn));
+ }
+ return rv;
+}
+
+nsresult
+nsPermissionManager::InitDB(bool aRemoveFile)
+{
+ nsCOMPtr<nsIFile> permissionsFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_PERMISSION_PARENT_DIR, getter_AddRefs(permissionsFile));
+ if (NS_FAILED(rv)) {
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+ rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(PERMISSIONS_FILE_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aRemoveFile) {
+ bool exists = false;
+ rv = permissionsFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ rv = permissionsFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ rv = OpenDatabase(permissionsFile);
+ if (rv == NS_ERROR_FILE_CORRUPTED) {
+ LogToConsole(NS_LITERAL_STRING("permissions.sqlite is corrupted! Try again!"));
+
+ // delete corrupted permissions.sqlite and try again
+ rv = permissionsFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("Corrupted permissions.sqlite has been removed."));
+
+ rv = OpenDatabase(permissionsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("OpenDatabase to permissions.sqlite is successful!"));
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool ready;
+ mDBConn->GetConnectionReady(&ready);
+ if (!ready) {
+ LogToConsole(NS_LITERAL_STRING("Fail to get connection to permissions.sqlite! Try again!"));
+
+ // delete and try again
+ rv = permissionsFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("Defective permissions.sqlite has been removed."));
+
+ rv = OpenDatabase(permissionsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("OpenDatabase to permissions.sqlite is successful!"));
+
+ mDBConn->GetConnectionReady(&ready);
+ if (!ready)
+ return NS_ERROR_UNEXPECTED;
+ }
+
+
+ bool tableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &tableExists);
+ if (!tableExists) {
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
+ }
+ if (!tableExists) {
+ rv = CreateTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // table already exists; check the schema version before reading
+ int32_t dbSchemaVersion;
+ rv = mDBConn->GetSchemaVersion(&dbSchemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (dbSchemaVersion) {
+ // upgrading.
+ // every time you increment the database schema, you need to implement
+ // the upgrading code from the previous version to the new one.
+ // fall through to current version
+
+ case 1:
+ {
+ // previous non-expiry version of database. Upgrade it by adding the
+ // expiration columns
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD expireType INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD expireTime INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // TODO: we want to make default version as version 2 in order to fix bug 784875.
+ case 0:
+ case 2:
+ {
+ // Add appId/isInBrowserElement fields.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD appId INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD isInBrowserElement INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->SetSchemaVersion(3);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // Version 3->4 is the creation of the modificationTime field.
+ case 3:
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD modificationTime INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We leave the modificationTime at zero for all existing records; using
+ // now() would mean, eg, that doing "remove all from the last hour"
+ // within the first hour after migration would remove all permissions.
+
+ rv = mDBConn->SetSchemaVersion(4);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // In version 5, host appId, and isInBrowserElement were merged into a
+ // single origin entry
+ //
+ // In version 6, the tables were renamed for backwards compatability reasons
+ // with version 4 and earlier.
+ //
+ // In version 7, a bug in the migration used for version 4->5 was discovered
+ // which could have triggered data-loss. Because of that, all users with a
+ // version 4, 5, or 6 database will be re-migrated from the backup database.
+ // (bug 1186034). This migration bug is not present after bug 1185340, and the
+ // re-migration ensures that all users have the fix.
+ case 5:
+ // This branch could also be reached via dbSchemaVersion == 3, in which case
+ // we want to fall through to the dbSchemaVersion == 4 case.
+ // The easiest way to do that is to perform this extra check here to make
+ // sure that we didn't get here via a fallthrough from v3
+ if (dbSchemaVersion == 5) {
+ // In version 5, the backup database is named moz_hosts_v4. We perform
+ // the version 5->6 migration to get the tables to have consistent
+ // naming conventions.
+
+ // Version 5->6 is the renaming of moz_hosts to moz_perms, and
+ // moz_hosts_v4 to moz_hosts (bug 1185343)
+ //
+ // In version 5, we performed the modifications to the permissions
+ // database in place, this meant that if you upgraded to a version which
+ // used V5, and then downgraded to a version which used v4 or earlier,
+ // the fallback path would drop the table, and your permissions data
+ // would be lost. This migration undoes that mistake, by restoring the
+ // old moz_hosts table (if it was present), and instead using the new
+ // table moz_perms for the new permissions schema.
+ //
+ // NOTE: If you downgrade, store new permissions, and then upgrade
+ // again, these new permissions won't be migrated or reflected in the
+ // updated database. This migration only occurs once, as if moz_perms
+ // exists, it will skip creating it. In addition, permissions added
+ // after the migration will not be visible in previous versions of
+ // firefox.
+
+ bool permsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
+ if (!permsTableExists) {
+ // Move the upgraded database to moz_perms
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts RENAME TO moz_perms"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_WARNING("moz_hosts was not renamed to moz_perms, "
+ "as a moz_perms table already exists");
+
+ // In the situation where a moz_perms table already exists, but the
+ // schema is lower than 6, a migration has already previously occured
+ // to V6, but a downgrade has caused the moz_hosts table to be
+ // dropped. This should only occur in the case of a downgrade to a V5
+ // database, which was only present in a few day's nightlies. As that
+ // version was likely used only on a temporary basis, we assume that
+ // the database from the previous V6 has the permissions which the
+ // user actually wants to use. We have to get rid of moz_hosts such
+ // that moz_hosts_v4 can be moved into its place if it exists.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+#ifdef DEBUG
+ // The moz_hosts table shouldn't exist anymore
+ bool hostsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &hostsTableExists);
+ MOZ_ASSERT(!hostsTableExists);
+#endif
+
+ // Rename moz_hosts_v4 back to it's original location, if it exists
+ bool v4TableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_v4"), &v4TableExists);
+ if (v4TableExists) {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts_v4 RENAME TO moz_hosts"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->SetSchemaVersion(6);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // At this point, the version 5 table has been migrated to a version 6 table
+ // We are guaranteed to have at least one of moz_hosts and moz_perms. If
+ // we have moz_hosts, we will migrate moz_hosts into moz_perms (even if
+ // we already have a moz_perms, as we need a re-migration due to bug 1186034).
+ //
+ // After this migration, we are guaranteed to have both a moz_hosts (for backwards
+ // compatability), and a moz_perms table. The moz_hosts table will have a v4 schema,
+ // and the moz_perms table will have a v6 schema.
+ case 4:
+ case 6:
+ {
+ bool hostsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &hostsTableExists);
+ if (hostsTableExists) {
+ // Both versions 4 and 6 have a version 4 formatted hosts table named
+ // moz_hosts. We can migrate this table to our version 7 table moz_perms.
+ // If moz_perms is present, then we can use it as a basis for comparison.
+
+ rv = mDBConn->BeginTransaction();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool tableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_new"), &tableExists);
+ if (tableExists) {
+ NS_WARNING("The temporary database moz_hosts_new already exists, dropping it.");
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts_new"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts_new ("
+ " id INTEGER PRIMARY KEY"
+ ",origin TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT host, type, permission, expireType, expireTime, "
+ "modificationTime, appId, isInBrowserElement FROM moz_hosts"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t id = 0;
+ nsAutoCString host, type;
+ uint32_t permission;
+ uint32_t expireType;
+ int64_t expireTime;
+ int64_t modificationTime;
+ uint32_t appId;
+ bool isInBrowserElement;
+ bool hasResult;
+
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ // Read in the old row
+ rv = stmt->GetUTF8String(0, host);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ rv = stmt->GetUTF8String(1, type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ permission = stmt->AsInt32(2);
+ expireType = stmt->AsInt32(3);
+ expireTime = stmt->AsInt64(4);
+ modificationTime = stmt->AsInt64(5);
+ if (NS_WARN_IF(stmt->AsInt64(6) < 0)) {
+ continue;
+ }
+ appId = static_cast<uint32_t>(stmt->AsInt64(6));
+ isInBrowserElement = static_cast<bool>(stmt->AsInt32(7));
+
+ // Perform the meat of the migration by deferring to the
+ // UpgradeHostToOriginAndInsert function.
+ UpgradeHostToOriginDBMigration upHelper(mDBConn, &id);
+ rv = UpgradeHostToOriginAndInsert(host, type, permission,
+ expireType, expireTime,
+ modificationTime, appId,
+ isInBrowserElement,
+ &upHelper);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unexpected failure when upgrading migrating permission "
+ "from host to origin");
+ }
+ }
+
+ // We don't drop the moz_hosts table such that it is avaliable for
+ // backwards-compatability and for future migrations in case of
+ // migration errors in the current code.
+ // Create a marker empty table which will indicate that the moz_hosts
+ // table is intended to act as a backup. If this table is not present,
+ // then the moz_hosts table was created as a random empty table.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts_is_backup (dummy INTEGER PRIMARY KEY)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool permsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
+ if (permsTableExists) {
+ // The user already had a moz_perms table, and we are performing a
+ // re-migration.
+ // Back up the old moz_perms database as moz_perms_v6 before we
+ // move the new table into its position
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_perms RENAME TO moz_perms_v6"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts_new RENAME TO moz_perms"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CommitTransaction();
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // We don't have a moz_hosts table, so we create one for downgrading purposes.
+ // This table is empty.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts ("
+ " id INTEGER PRIMARY KEY"
+ ",host TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ",appId INTEGER"
+ ",isInBrowserElement INTEGER"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We are guaranteed to have a moz_perms table at this point.
+ }
+
+#ifdef DEBUG
+ {
+ // At this point, both the moz_hosts and moz_perms tables should exist
+ bool hostsTableExists = false;
+ bool permsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &hostsTableExists);
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
+ MOZ_ASSERT(hostsTableExists && permsTableExists);
+ }
+#endif
+
+ rv = mDBConn->SetSchemaVersion(7);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // The version 7-8 migration is the re-migration of localhost and ip-address
+ // entries due to errors in the previous version 7 migration which caused
+ // localhost and ip-address entries to be incorrectly discarded.
+ // The version 7 migration logic has been corrected, and thus this logic only
+ // needs to execute if the user is currently on version 7.
+ case 7:
+ {
+ // This migration will be relatively expensive as we need to perform
+ // database lookups for each origin which we want to insert. Fortunately,
+ // it shouldn't be too expensive as we only want to insert a small number
+ // of entries created for localhost or IP addresses.
+
+ // We only want to perform the re-migration if moz_hosts is a backup
+ bool hostsIsBackupExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_is_backup"),
+ &hostsIsBackupExists);
+
+ // Only perform this migration if the original schema version was 7, and
+ // the moz_hosts table is a backup.
+ if (dbSchemaVersion == 7 && hostsIsBackupExists) {
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ MOZ_ASSERT(tldService); // We should always have a tldService
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT host, type, permission, expireType, expireTime, "
+ "modificationTime, appId, isInBrowserElement FROM moz_hosts"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> idStmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT MAX(id) FROM moz_hosts"), getter_AddRefs(idStmt));
+ int64_t id = 0;
+ bool hasResult = false;
+ if (NS_SUCCEEDED(rv) &&
+ NS_SUCCEEDED(idStmt->ExecuteStep(&hasResult)) &&
+ hasResult) {
+ id = idStmt->AsInt32(0) + 1;
+ }
+
+ nsAutoCString host, type;
+ uint32_t permission;
+ uint32_t expireType;
+ int64_t expireTime;
+ int64_t modificationTime;
+ uint32_t appId;
+ bool isInBrowserElement;
+
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ // Read in the old row
+ rv = stmt->GetUTF8String(0, host);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsAutoCString eTLD1;
+ rv = tldService->GetBaseDomainFromHost(host, 0, eTLD1);
+ if (NS_SUCCEEDED(rv)) {
+ // We only care about entries which the tldService can't handle
+ continue;
+ }
+
+ rv = stmt->GetUTF8String(1, type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ permission = stmt->AsInt32(2);
+ expireType = stmt->AsInt32(3);
+ expireTime = stmt->AsInt64(4);
+ modificationTime = stmt->AsInt64(5);
+ if (NS_WARN_IF(stmt->AsInt64(6) < 0)) {
+ continue;
+ }
+ appId = static_cast<uint32_t>(stmt->AsInt64(6));
+ isInBrowserElement = static_cast<bool>(stmt->AsInt32(7));
+
+ // Perform the meat of the migration by deferring to the
+ // UpgradeHostToOriginAndInsert function.
+ UpgradeIPHostToOriginDB upHelper(mDBConn, &id);
+ rv = UpgradeHostToOriginAndInsert(host, type, permission,
+ expireType, expireTime,
+ modificationTime, appId,
+ isInBrowserElement,
+ &upHelper);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unexpected failure when upgrading migrating permission "
+ "from host to origin");
+ }
+ }
+ }
+
+ // Even if we didn't perform the migration, we want to bump the schema
+ // version to 8.
+ rv = mDBConn->SetSchemaVersion(8);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // The version 8-9 migration removes the unnecessary backup moz-hosts database contents.
+ // as the data no longer needs to be migrated
+ case 8:
+ {
+ // We only want to clear out the old table if it is a backup. If it isn't a backup,
+ // we don't need to touch it.
+ bool hostsIsBackupExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_is_backup"),
+ &hostsIsBackupExists);
+ if (hostsIsBackupExists) {
+ // Delete everything from the backup, we want to keep around the table so that
+ // you can still downgrade and not break things, but we don't need to keep the
+ // rows around.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The table is no longer a backup, so get rid of it.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts_is_backup"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->SetSchemaVersion(9);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // current version.
+ case HOSTS_SCHEMA_VERSION:
+ break;
+
+ // downgrading.
+ // if columns have been added to the table, we can still use the ones we
+ // understand safely. if columns have been deleted or altered, just
+ // blow away the table and start from scratch! if you change the way
+ // a column is interpreted, make sure you also change its name so this
+ // check will catch it.
+ default:
+ {
+ // check if all the expected columns exist
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT origin, type, permission, expireType, expireTime, "
+ "modificationTime FROM moz_perms"),
+ getter_AddRefs(stmt));
+ if (NS_SUCCEEDED(rv))
+ break;
+
+ // our columns aren't there - drop the table!
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_perms"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CreateTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+ }
+
+ // cache frequently used statements (for insertion, deletion, and updating)
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_perms "
+ "(id, origin, type, permission, expireType, expireTime, modificationTime) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmtInsert));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_perms "
+ "WHERE id = ?1"), getter_AddRefs(mStmtDelete));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_perms "
+ "SET permission = ?2, expireType= ?3, expireTime = ?4, modificationTime = ?5 WHERE id = ?1"),
+ getter_AddRefs(mStmtUpdate));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Always import default permissions.
+ ImportDefaults();
+ // check whether to import or just read in the db
+ if (tableExists)
+ return Read();
+
+ return Import();
+}
+
+// sets the schema version and creates the moz_perms table.
+nsresult
+nsPermissionManager::CreateTable()
+{
+ // set the schema version, before creating the table
+ nsresult rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
+ if (NS_FAILED(rv)) return rv;
+
+ // create the table
+ // SQL also lives in automation.py.in. If you change this SQL change that
+ // one too
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_perms ("
+ " id INTEGER PRIMARY KEY"
+ ",origin TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ")"));
+ if (NS_FAILED(rv)) return rv;
+
+ // We also create a legacy V4 table, for backwards compatability,
+ // and to ensure that downgrades don't trigger a schema version change.
+ return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts ("
+ " id INTEGER PRIMARY KEY"
+ ",host TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ",appId INTEGER"
+ ",isInBrowserElement INTEGER"
+ ")"));
+}
+
+NS_IMETHODIMP
+nsPermissionManager::Add(nsIURI *aURI,
+ const char *aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return AddFromPrincipal(principal, aType, aPermission, aExpireType, aExpireTime);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::AddFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType, uint32_t aPermission,
+ uint32_t aExpireType, int64_t aExpireTime)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+ NS_ENSURE_TRUE(aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
+ aExpireType == nsIPermissionManager::EXPIRE_TIME ||
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION,
+ NS_ERROR_INVALID_ARG);
+
+ // Skip addition if the permission is already expired. Note that EXPIRE_SESSION only
+ // honors expireTime if it is nonzero.
+ if ((aExpireType == nsIPermissionManager::EXPIRE_TIME ||
+ (aExpireType == nsIPermissionManager::EXPIRE_SESSION && aExpireTime != 0)) &&
+ aExpireTime <= (PR_Now() / 1000)) {
+ return NS_OK;
+ }
+
+ // We don't add the system principal because it actually has no URI and we
+ // always allow action for them.
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Null principals can't meaningfully have persisted permissions attached to
+ // them, so we don't allow adding permissions for them.
+ if (aPrincipal->GetIsNullPrincipal()) {
+ return NS_OK;
+ }
+
+ // Permissions may not be added to expanded principals.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // A modificationTime of zero will cause AddInternal to use now().
+ int64_t modificationTime = 0;
+
+ return AddInternal(aPrincipal, nsDependentCString(aType), aPermission, 0,
+ aExpireType, aExpireTime, modificationTime, eNotify, eWriteToDB);
+}
+
+nsresult
+nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal,
+ const nsAFlatCString &aType,
+ uint32_t aPermission,
+ int64_t aID,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ int64_t aModificationTime,
+ NotifyOperationType aNotifyOperation,
+ DBOperationType aDBOperation,
+ const bool aIgnoreSessionPermissions)
+{
+ nsAutoCString origin;
+ nsresult rv = GetOriginFromPrincipal(aPrincipal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!IsChildProcess()) {
+ IPC::Permission permission(origin, aType, aPermission,
+ aExpireType, aExpireTime);
+
+ nsTArray<ContentParent*> cplist;
+ ContentParent::GetAll(cplist);
+ for (uint32_t i = 0; i < cplist.Length(); ++i) {
+ ContentParent* cp = cplist[i];
+ // On platforms where we use a preallocated template process we don't
+ // want to notify this process about session specific permissions so
+ // new tabs or apps created on it won't inherit the session permissions.
+ if (cp->IsPreallocated() &&
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION)
+ continue;
+ if (cp->NeedsPermissionsUpdate())
+ Unused << cp->SendAddPermission(permission);
+ }
+ }
+
+ // look up the type index
+ int32_t typeIndex = GetTypeIndex(aType.get(), true);
+ NS_ENSURE_TRUE(typeIndex != -1, NS_ERROR_OUT_OF_MEMORY);
+
+ // When an entry already exists, PutEntry will return that, instead
+ // of adding a new one
+ RefPtr<PermissionKey> key = new PermissionKey(aPrincipal);
+ PermissionHashKey* entry = mPermissionTable.PutEntry(key);
+ if (!entry) return NS_ERROR_FAILURE;
+ if (!entry->GetKey()) {
+ mPermissionTable.RemoveEntry(entry);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // figure out the transaction type, and get any existing permission value
+ OperationType op;
+ int32_t index = entry->GetPermissionIndex(typeIndex);
+ if (index == -1) {
+ if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
+ op = eOperationNone;
+ else
+ op = eOperationAdding;
+
+ } else {
+ PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
+
+ // remove the permission if the permission is UNKNOWN, update the
+ // permission if its value or expire type have changed OR if the time has
+ // changed and the expire type is time, otherwise, don't modify. There's
+ // no need to modify a permission that doesn't expire with time when the
+ // only thing changed is the expire time.
+ if (aPermission == oldPermissionEntry.mPermission &&
+ aExpireType == oldPermissionEntry.mExpireType &&
+ (aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
+ aExpireTime == oldPermissionEntry.mExpireTime))
+ op = eOperationNone;
+ else if (oldPermissionEntry.mID == cIDPermissionIsDefault)
+ // The existing permission is one added as a default and the new permission
+ // doesn't exactly match so we are replacing the default. This is true
+ // even if the new permission is UNKNOWN_ACTION (which means a "logical
+ // remove" of the default)
+ op = eOperationReplacingDefault;
+ else if (aID == cIDPermissionIsDefault)
+ // We are adding a default permission but a "real" permission already
+ // exists. This almost-certainly means we just did a removeAllSince and
+ // are re-importing defaults - so we can ignore this.
+ op = eOperationNone;
+ else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
+ op = eOperationRemoving;
+ else
+ op = eOperationChanging;
+ }
+
+ // child processes should *always* be passed a modificationTime of zero.
+ MOZ_ASSERT(!IsChildProcess() || aModificationTime == 0);
+
+ // do the work for adding, deleting, or changing a permission:
+ // update the in-memory list, write to the db, and notify consumers.
+ int64_t id;
+ if (aModificationTime == 0) {
+ aModificationTime = PR_Now() / 1000;
+ }
+
+ switch (op) {
+ case eOperationNone:
+ {
+ // nothing to do
+ return NS_OK;
+ }
+
+ case eOperationAdding:
+ {
+ if (aDBOperation == eWriteToDB) {
+ // we'll be writing to the database - generate a known unique id
+ id = ++mLargestID;
+ } else {
+ // we're reading from the database - use the id already assigned
+ id = aID;
+ }
+
+ entry->GetPermissions().AppendElement(PermissionEntry(id, typeIndex, aPermission,
+ aExpireType, aExpireTime,
+ aModificationTime));
+
+ if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION) {
+ UpdateDB(op, mStmtInsert, id, origin, aType, aPermission, aExpireType, aExpireTime, aModificationTime);
+ }
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ aPermission,
+ aExpireType,
+ aExpireTime,
+ u"added");
+ }
+
+ break;
+ }
+
+ case eOperationRemoving:
+ {
+ PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
+ id = oldPermissionEntry.mID;
+ entry->GetPermissions().RemoveElementAt(index);
+
+ if (aDBOperation == eWriteToDB)
+ // We care only about the id here so we pass dummy values for all other
+ // parameters.
+ UpdateDB(op, mStmtDelete, id, EmptyCString(), EmptyCString(), 0,
+ nsIPermissionManager::EXPIRE_NEVER, 0, 0);
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ oldPermissionEntry.mPermission,
+ oldPermissionEntry.mExpireType,
+ oldPermissionEntry.mExpireTime,
+ u"deleted");
+ }
+
+ // If there are no more permissions stored for that entry, clear it.
+ if (entry->GetPermissions().IsEmpty()) {
+ mPermissionTable.RemoveEntry(entry);
+ }
+
+ break;
+ }
+
+ case eOperationChanging:
+ {
+ id = entry->GetPermissions()[index].mID;
+
+ // If the new expireType is EXPIRE_SESSION, then we have to keep a
+ // copy of the previous permission/expireType values. This cached value will be
+ // used when restoring the permissions of an app.
+ if (entry->GetPermissions()[index].mExpireType != nsIPermissionManager::EXPIRE_SESSION &&
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION) {
+ entry->GetPermissions()[index].mNonSessionPermission = entry->GetPermissions()[index].mPermission;
+ entry->GetPermissions()[index].mNonSessionExpireType = entry->GetPermissions()[index].mExpireType;
+ entry->GetPermissions()[index].mNonSessionExpireTime = entry->GetPermissions()[index].mExpireTime;
+ } else if (aExpireType != nsIPermissionManager::EXPIRE_SESSION) {
+ entry->GetPermissions()[index].mNonSessionPermission = aPermission;
+ entry->GetPermissions()[index].mNonSessionExpireType = aExpireType;
+ entry->GetPermissions()[index].mNonSessionExpireTime = aExpireTime;
+ }
+
+ entry->GetPermissions()[index].mPermission = aPermission;
+ entry->GetPermissions()[index].mExpireType = aExpireType;
+ entry->GetPermissions()[index].mExpireTime = aExpireTime;
+ entry->GetPermissions()[index].mModificationTime = aModificationTime;
+
+ if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION)
+ // We care only about the id, the permission and expireType/expireTime/modificationTime here.
+ // We pass dummy values for all other parameters.
+ UpdateDB(op, mStmtUpdate, id, EmptyCString(), EmptyCString(),
+ aPermission, aExpireType, aExpireTime, aModificationTime);
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ aPermission,
+ aExpireType,
+ aExpireTime,
+ u"changed");
+ }
+
+ break;
+ }
+ case eOperationReplacingDefault:
+ {
+ // this is handling the case when we have an existing permission
+ // entry that was created as a "default" (and thus isn't in the DB) with
+ // an explicit permission (that may include UNKNOWN_ACTION.)
+ // Note we will *not* get here if we are replacing an already replaced
+ // default value - that is handled as eOperationChanging.
+
+ // So this is a hybrid of eOperationAdding (as we are writing a new entry
+ // to the DB) and eOperationChanging (as we are replacing the in-memory
+ // repr and sending a "changed" notification).
+
+ // We want a new ID even if not writing to the DB, so the modified entry
+ // in memory doesn't have the magic cIDPermissionIsDefault value.
+ id = ++mLargestID;
+
+ // The default permission being replaced can't have session expiry.
+ NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType != nsIPermissionManager::EXPIRE_SESSION,
+ NS_ERROR_UNEXPECTED);
+ // We don't support the new entry having any expiry - supporting that would
+ // make things far more complex and none of the permissions we set as a
+ // default support that.
+ NS_ENSURE_TRUE(aExpireType == EXPIRE_NEVER, NS_ERROR_UNEXPECTED);
+
+ // update the existing entry in memory.
+ entry->GetPermissions()[index].mID = id;
+ entry->GetPermissions()[index].mPermission = aPermission;
+ entry->GetPermissions()[index].mExpireType = aExpireType;
+ entry->GetPermissions()[index].mExpireTime = aExpireTime;
+ entry->GetPermissions()[index].mModificationTime = aModificationTime;
+
+ // If requested, create the entry in the DB.
+ if (aDBOperation == eWriteToDB) {
+ UpdateDB(eOperationAdding, mStmtInsert, id, origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ }
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ aPermission,
+ aExpireType,
+ aExpireTime,
+ u"changed");
+ }
+
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::Remove(nsIURI* aURI,
+ const char* aType)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RemoveFromPrincipal(principal, aType);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemoveFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ // System principals are never added to the database, no need to remove them.
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Permissions may not be added to expanded principals.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // AddInternal() handles removal, just let it do the work
+ return AddInternal(aPrincipal,
+ nsDependentCString(aType),
+ nsIPermissionManager::UNKNOWN_ACTION,
+ 0,
+ nsIPermissionManager::EXPIRE_NEVER,
+ 0,
+ 0,
+ eNotify,
+ eWriteToDB);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemovePermission(nsIPermission* aPerm)
+{
+ if (!aPerm) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = aPerm->GetPrincipal(getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString type;
+ rv = aPerm->GetType(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Permissions are uniquely identified by their principal and type.
+ // We remove the permission using these two pieces of data.
+ return RemoveFromPrincipal(principal, type.get());
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemoveAll()
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ return RemoveAllInternal(true);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemoveAllSince(int64_t aSince)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ return RemoveAllModifiedSince(aSince);
+}
+
+void
+nsPermissionManager::CloseDB(bool aRebuildOnSuccess)
+{
+ // Null the statements, this will finalize them.
+ mStmtInsert = nullptr;
+ mStmtDelete = nullptr;
+ mStmtUpdate = nullptr;
+ if (mDBConn) {
+ mozIStorageCompletionCallback* cb = new CloseDatabaseListener(this,
+ aRebuildOnSuccess);
+ mozilla::DebugOnly<nsresult> rv = mDBConn->AsyncClose(cb);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mDBConn = nullptr; // Avoid race conditions
+ }
+}
+
+nsresult
+nsPermissionManager::RemoveAllInternal(bool aNotifyObservers)
+{
+ // Remove from memory and notify immediately. Since the in-memory
+ // database is authoritative, we do not need confirmation from the
+ // on-disk database to notify observers.
+ RemoveAllFromMemory();
+
+ // Re-import the defaults
+ ImportDefaults();
+
+ if (aNotifyObservers) {
+ NotifyObservers(nullptr, u"cleared");
+ }
+
+ // clear the db
+ if (mDBConn) {
+ nsCOMPtr<mozIStorageAsyncStatement> removeStmt;
+ nsresult rv = mDBConn->
+ CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_perms"
+ ), getter_AddRefs(removeStmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!removeStmt) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<mozIStoragePendingStatement> pending;
+ mozIStorageStatementCallback* cb = new DeleteFromMozHostListener(this);
+ rv = removeStmt->ExecuteAsync(cb, getter_AddRefs(pending));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestExactPermission(nsIURI *aURI,
+ const char *aType,
+ uint32_t *aPermission)
+{
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return TestExactPermissionFromPrincipal(principal, aType, aPermission);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestExactPermissionFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ return CommonTestPermission(aPrincipal, aType, aPermission, true, true);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestExactPermanentPermission(nsIPrincipal* aPrincipal,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ return CommonTestPermission(aPrincipal, aType, aPermission, true, false);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestPermission(nsIURI *aURI,
+ const char *aType,
+ uint32_t *aPermission)
+{
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return TestPermissionFromPrincipal(principal, aType, aPermission);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestPermissionFromWindow(mozIDOMWindow* aWindow,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ NS_ENSURE_ARG(aWindow);
+ nsCOMPtr<nsPIDOMWindowInner> window = nsPIDOMWindowInner::From(aWindow);
+
+ // Get the document for security check
+ nsCOMPtr<nsIDocument> document = window->GetExtantDoc();
+ NS_ENSURE_TRUE(document, NS_NOINTERFACE);
+
+ nsCOMPtr<nsIPrincipal> principal = document->NodePrincipal();
+ return TestPermissionFromPrincipal(principal, aType, aPermission);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestPermissionFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ return CommonTestPermission(aPrincipal, aType, aPermission, false, true);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::GetPermissionObject(nsIPrincipal* aPrincipal,
+ const char* aType,
+ bool aExactHostMatch,
+ nsIPermission** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aResult = nullptr;
+
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Querying the permission object of an nsEP is non-sensical.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int32_t typeIndex = GetTypeIndex(aType, false);
+ // If type == -1, the type isn't known,
+ // so just return NS_OK
+ if (typeIndex == -1) return NS_OK;
+
+ PermissionHashKey* entry = GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
+ if (!entry) {
+ return NS_OK;
+ }
+
+ // We don't call GetPermission(typeIndex) because that returns a fake
+ // UNKNOWN_ACTION entry if there is no match.
+ int32_t idx = entry->GetPermissionIndex(typeIndex);
+ if (-1 == idx) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PermissionEntry& perm = entry->GetPermissions()[idx];
+ nsCOMPtr<nsIPermission> r = nsPermission::Create(principal,
+ mTypeArray.ElementAt(perm.mType),
+ perm.mPermission,
+ perm.mExpireType,
+ perm.mExpireTime);
+ if (NS_WARN_IF(!r)) {
+ return NS_ERROR_FAILURE;
+ }
+ r.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::CommonTestPermission(nsIPrincipal* aPrincipal,
+ const char *aType,
+ uint32_t *aPermission,
+ bool aExactHostMatch,
+ bool aIncludingSession)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ *aPermission = nsIPermissionManager::ALLOW_ACTION;
+ return NS_OK;
+ }
+
+ // Set the default.
+ *aPermission = nsIPermissionManager::UNKNOWN_ACTION;
+
+ // For expanded principals, we want to iterate over the whitelist and see
+ // if the permission is granted for any of them.
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
+ if (ep) {
+ nsTArray<nsCOMPtr<nsIPrincipal>>* whitelist;
+ nsresult rv = ep->GetWhiteList(&whitelist);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (size_t i = 0; i < whitelist->Length(); ++i) {
+ uint32_t perm;
+ rv = CommonTestPermission(whitelist->ElementAt(i), aType, &perm, aExactHostMatch,
+ aIncludingSession);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (perm == nsIPermissionManager::ALLOW_ACTION) {
+ *aPermission = perm;
+ return NS_OK;
+ } else if (perm == nsIPermissionManager::PROMPT_ACTION) {
+ // Store it, but keep going to see if we can do better.
+ *aPermission = perm;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ int32_t typeIndex = GetTypeIndex(aType, false);
+ // If type == -1, the type isn't known,
+ // so just return NS_OK
+ if (typeIndex == -1) return NS_OK;
+
+ PermissionHashKey* entry = GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
+ if (!entry ||
+ (!aIncludingSession &&
+ entry->GetPermission(typeIndex).mNonSessionExpireType ==
+ nsIPermissionManager::EXPIRE_SESSION)) {
+ return NS_OK;
+ }
+
+ *aPermission = aIncludingSession
+ ? entry->GetPermission(typeIndex).mPermission
+ : entry->GetPermission(typeIndex).mNonSessionPermission;
+
+ return NS_OK;
+}
+
+// Returns PermissionHashKey for a given { host, appId, isInBrowserElement } tuple.
+// This is not simply using PermissionKey because we will walk-up domains in
+// case of |host| contains sub-domains.
+// Returns null if nothing found.
+// Also accepts host on the format "<foo>". This will perform an exact match
+// lookup as the string doesn't contain any dots.
+nsPermissionManager::PermissionHashKey*
+nsPermissionManager::GetPermissionHashKey(nsIPrincipal* aPrincipal,
+ uint32_t aType,
+ bool aExactHostMatch)
+{
+ PermissionHashKey* entry = nullptr;
+
+ RefPtr<PermissionKey> key = new PermissionKey(aPrincipal);
+ entry = mPermissionTable.GetEntry(key);
+
+ if (entry) {
+ PermissionEntry permEntry = entry->GetPermission(aType);
+
+ // if the entry is expired, remove and keep looking for others.
+ // Note that EXPIRE_SESSION only honors expireTime if it is nonzero.
+ if ((permEntry.mExpireType == nsIPermissionManager::EXPIRE_TIME ||
+ (permEntry.mExpireType == nsIPermissionManager::EXPIRE_SESSION &&
+ permEntry.mExpireTime != 0)) &&
+ permEntry.mExpireTime <= (PR_Now() / 1000)) {
+ entry = nullptr;
+ RemoveFromPrincipal(aPrincipal, mTypeArray[aType].get());
+ } else if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+ entry = nullptr;
+ }
+ }
+
+ if (entry) {
+ return entry;
+ }
+
+ // If aExactHostMatch wasn't true, we can check if the base domain has a permission entry.
+ if (!aExactHostMatch) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsCString domain = GetNextSubDomainForHost(host);
+ if (domain.IsEmpty()) {
+ return nullptr;
+ }
+
+ // Create a new principal which is identical to the current one, but with the new host
+ nsCOMPtr<nsIURI> newURI;
+ rv = uri->Clone(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ rv = newURI->SetHost(domain);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ // Copy the attributes over
+ mozilla::PrincipalOriginAttributes attrs =
+ mozilla::BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
+
+ // Disable userContext and firstParty isolation for permissions.
+ attrs.StripUserContextIdAndFirstPartyDomain();
+
+ nsCOMPtr<nsIPrincipal> principal =
+ mozilla::BasePrincipal::CreateCodebasePrincipal(newURI, attrs);
+
+ return GetPermissionHashKey(principal, aType, aExactHostMatch);
+ }
+
+ // No entry, really...
+ return nullptr;
+}
+
+NS_IMETHODIMP nsPermissionManager::GetEnumerator(nsISimpleEnumerator **aEnum)
+{
+ // roll an nsCOMArray of all our permissions, then hand out an enumerator
+ nsCOMArray<nsIPermission> array;
+
+ for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
+ PermissionHashKey* entry = iter.Get();
+ for (const auto& permEntry : entry->GetPermissions()) {
+ // Given how "default" permissions work and the possibility of them being
+ // overridden with UNKNOWN_ACTION, we might see this value here - but we
+ // do *not* want to return them via the enumerator.
+ if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
+ getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ array.AppendObject(permission);
+ }
+ }
+
+ return NS_NewArrayEnumerator(aEnum, array);
+}
+
+NS_IMETHODIMP nsPermissionManager::GetAllForURI(nsIURI* aURI, nsISimpleEnumerator **aEnum)
+{
+ nsCOMArray<nsIPermission> array;
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<PermissionKey> key = new PermissionKey(principal);
+ PermissionHashKey* entry = mPermissionTable.GetEntry(key);
+
+ if (entry) {
+ for (const auto& permEntry : entry->GetPermissions()) {
+ // Only return custom permissions
+ if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ array.AppendObject(permission);
+ }
+ }
+
+ return NS_NewArrayEnumerator(aEnum, array);
+}
+
+NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
+ // The profile is about to change,
+ // or is going away because the application is shutting down.
+ mIsShuttingDown = true;
+ RemoveAllFromMemory();
+ CloseDB(false);
+ } else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
+ // the profile has already changed; init the db from the new location
+ InitDB(false);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::RemoveAllModifiedSince(int64_t aModificationTime)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ nsCOMArray<nsIPermission> array;
+ for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
+ PermissionHashKey* entry = iter.Get();
+ for (const auto& permEntry : entry->GetPermissions()) {
+ if (aModificationTime > permEntry.mModificationTime) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
+ getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ array.AppendObject(permission);
+ }
+ }
+
+ for (int32_t i = 0; i<array.Count(); ++i) {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsAutoCString type;
+
+ nsresult rv = array[i]->GetPrincipal(getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("GetPrincipal() failed!");
+ continue;
+ }
+
+ rv = array[i]->GetType(type);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("GetType() failed!");
+ continue;
+ }
+
+ // AddInternal handles removal, so let it do the work...
+ AddInternal(
+ principal,
+ type,
+ nsIPermissionManager::UNKNOWN_ACTION,
+ 0,
+ nsIPermissionManager::EXPIRE_NEVER, 0, 0,
+ nsPermissionManager::eNotify,
+ nsPermissionManager::eWriteToDB);
+ }
+ // now re-import any defaults as they may now be required if we just deleted
+ // an override.
+ ImportDefaults();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemovePermissionsWithAttributes(const nsAString& aPattern)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ mozilla::OriginAttributesPattern pattern;
+ if (!pattern.Init(aPattern)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return RemovePermissionsWithAttributes(pattern);
+}
+
+nsresult
+nsPermissionManager::RemovePermissionsWithAttributes(mozilla::OriginAttributesPattern& aPattern)
+{
+ nsCOMArray<nsIPermission> permissions;
+ for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
+ PermissionHashKey* entry = iter.Get();
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
+ getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (!aPattern.Matches(mozilla::BasePrincipal::Cast(principal)->OriginAttributesRef())) {
+ continue;
+ }
+
+ for (const auto& permEntry : entry->GetPermissions()) {
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ permissions.AppendObject(permission);
+ }
+ }
+
+ for (int32_t i = 0; i < permissions.Count(); ++i) {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsAutoCString type;
+
+ permissions[i]->GetPrincipal(getter_AddRefs(principal));
+ permissions[i]->GetType(type);
+
+ AddInternal(principal,
+ type,
+ nsIPermissionManager::UNKNOWN_ACTION,
+ 0,
+ nsIPermissionManager::EXPIRE_NEVER,
+ 0,
+ 0,
+ nsPermissionManager::eNotify,
+ nsPermissionManager::eWriteToDB);
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+//*** nsPermissionManager private methods
+//*****************************************************************************
+
+nsresult
+nsPermissionManager::RemoveAllFromMemory()
+{
+ mLargestID = 0;
+ mTypeArray.Clear();
+ mPermissionTable.Clear();
+
+ return NS_OK;
+}
+
+// Returns -1 on failure
+int32_t
+nsPermissionManager::GetTypeIndex(const char *aType,
+ bool aAdd)
+{
+ for (uint32_t i = 0; i < mTypeArray.Length(); ++i)
+ if (mTypeArray[i].Equals(aType))
+ return i;
+
+ if (!aAdd) {
+ // Not found, but that is ok - we were just looking.
+ return -1;
+ }
+
+ // This type was not registered before.
+ // append it to the array, without copy-constructing the string
+ nsCString *elem = mTypeArray.AppendElement();
+ if (!elem)
+ return -1;
+
+ elem->Assign(aType);
+ return mTypeArray.Length() - 1;
+}
+
+// wrapper function for mangling (host,type,perm,expireType,expireTime)
+// set into an nsIPermission.
+void
+nsPermissionManager::NotifyObserversWithPermission(nsIPrincipal* aPrincipal,
+ const nsCString &aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(aPrincipal, aType, aPermission,
+ aExpireType, aExpireTime);
+ if (permission)
+ NotifyObservers(permission, aData);
+}
+
+// notify observers that the permission list changed. there are four possible
+// values for aData:
+// "deleted" means a permission was deleted. aPermission is the deleted permission.
+// "added" means a permission was added. aPermission is the added permission.
+// "changed" means a permission was altered. aPermission is the new permission.
+// "cleared" means the entire permission list was cleared. aPermission is null.
+void
+nsPermissionManager::NotifyObservers(nsIPermission *aPermission,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(aPermission,
+ kPermissionChangeNotification,
+ aData);
+}
+
+nsresult
+nsPermissionManager::Read()
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ nsresult rv;
+
+ // delete expired permissions before we read in the db
+ {
+ // this deletion has its own scope so the write lock is released when done.
+ nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_perms WHERE expireType = ?1 AND expireTime <= ?2"),
+ getter_AddRefs(stmtDeleteExpired));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmtDeleteExpired->BindInt32ByIndex(0, nsIPermissionManager::EXPIRE_TIME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmtDeleteExpired->BindInt64ByIndex(1, PR_Now() / 1000);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult;
+ rv = stmtDeleteExpired->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id, origin, type, permission, expireType, expireTime, modificationTime "
+ "FROM moz_perms"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t id;
+ nsAutoCString origin, type;
+ uint32_t permission;
+ uint32_t expireType;
+ int64_t expireTime;
+ int64_t modificationTime;
+ bool hasResult;
+ bool readError = false;
+
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ // explicitly set our entry id counter for use in AddInternal(),
+ // and keep track of the largest id so we know where to pick up.
+ id = stmt->AsInt64(0);
+ if (id > mLargestID)
+ mLargestID = id;
+
+ rv = stmt->GetUTF8String(1, origin);
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+
+ rv = stmt->GetUTF8String(2, type);
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+
+ permission = stmt->AsInt32(3);
+ expireType = stmt->AsInt32(4);
+
+ // convert into int64_t values (milliseconds)
+ expireTime = stmt->AsInt64(5);
+ modificationTime = stmt->AsInt64(6);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(origin, getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+
+ rv = AddInternal(principal, type, permission, id, expireType, expireTime,
+ modificationTime, eDontNotify, eNoDBOperation);
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+ }
+
+ if (readError) {
+ NS_ERROR("Error occured while reading the permissions database!");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static const char kMatchTypeHost[] = "host";
+static const char kMatchTypeOrigin[] = "origin";
+
+// Import() will read a file from the profile directory and add them to the
+// database before deleting the file - ie, this is a one-shot operation that
+// will not succeed on subsequent runs as the file imported from is removed.
+nsresult
+nsPermissionManager::Import()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> permissionsFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(HOSTPERM_FILE_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> fileInputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
+ permissionsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = _DoImport(fileInputStream, mDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we successfully imported and wrote to the DB - delete the old file.
+ permissionsFile->Remove(false);
+ return NS_OK;
+}
+
+// ImportDefaults will read a URL with default permissions and add them to the
+// in-memory copy of permissions. The database is *not* written to.
+nsresult
+nsPermissionManager::ImportDefaults()
+{
+ nsCString defaultsURL = mozilla::Preferences::GetCString(kDefaultsUrlPrefName);
+ if (defaultsURL.IsEmpty()) { // == Don't use built-in permissions.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> defaultsURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(defaultsURI), defaultsURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ defaultsURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = channel->Open2(getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = _DoImport(inputStream, nullptr);
+ inputStream->Close();
+ return rv;
+}
+
+// _DoImport reads the specified stream and adds the parsed elements. If
+// |conn| is passed, the imported data will be written to the database, but if
+// |conn| is null the data will be added only to the in-memory copy of the
+// database.
+nsresult
+nsPermissionManager::_DoImport(nsIInputStream *inputStream, mozIStorageConnection *conn)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ nsresult rv;
+ // start a transaction on the storage db, to optimize insertions.
+ // transaction will automically commit on completion
+ // (note the transaction is a no-op if a null connection is passed)
+ mozStorageTransaction transaction(conn, true);
+
+ // The DB operation - we only try and write if a connection was passed.
+ DBOperationType operation = conn ? eWriteToDB : eNoDBOperation;
+ // and if no DB connection was passed we assume this is a "default" permission,
+ // so use the special ID which indicates this.
+ int64_t id = conn ? 0 : cIDPermissionIsDefault;
+
+ /* format is:
+ * matchtype \t type \t permission \t host
+ * Only "host" is supported for matchtype
+ * type is a string that identifies the type of permission (e.g. "cookie")
+ * permission is an integer between 1 and 15
+ */
+
+ // Ideally we'd do this with nsILineInputString, but this is called with an
+ // nsIInputStream that comes from a resource:// URI, which doesn't support
+ // that interface. So NS_ReadLine to the rescue...
+ nsLineBuffer<char> lineBuffer;
+ nsCString line;
+ bool isMore = true;
+ do {
+ rv = NS_ReadLine(inputStream, &lineBuffer, line, &isMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (line.IsEmpty() || line.First() == '#') {
+ continue;
+ }
+
+ nsTArray<nsCString> lineArray;
+
+ // Split the line at tabs
+ ParseString(line, '\t', lineArray);
+
+ if (lineArray[0].EqualsLiteral(kMatchTypeHost) &&
+ lineArray.Length() == 4) {
+ nsresult error = NS_OK;
+ uint32_t permission = lineArray[2].ToInteger(&error);
+ if (NS_FAILED(error))
+ continue;
+
+ // the import file format doesn't handle modification times, so we use
+ // 0, which AddInternal will convert to now()
+ int64_t modificationTime = 0;
+
+ UpgradeHostToOriginHostfileImport upHelper(this, operation, id);
+ error = UpgradeHostToOriginAndInsert(lineArray[3], lineArray[1], permission,
+ nsIPermissionManager::EXPIRE_NEVER, 0,
+ modificationTime, nsIScriptSecurityManager::NO_APP_ID,
+ false, &upHelper);
+ if (NS_FAILED(error)) {
+ NS_WARNING("There was a problem importing a host permission");
+ }
+ } else if (lineArray[0].EqualsLiteral(kMatchTypeOrigin) &&
+ lineArray.Length() == 4) {
+ nsresult error = NS_OK;
+ uint32_t permission = lineArray[2].ToInteger(&error);
+ if (NS_FAILED(error))
+ continue;
+
+ nsCOMPtr<nsIPrincipal> principal;
+ error = GetPrincipalFromOrigin(lineArray[3], getter_AddRefs(principal));
+ if (NS_FAILED(error)) {
+ NS_WARNING("Couldn't import an origin permission - malformed origin");
+ continue;
+ }
+
+ // the import file format doesn't handle modification times, so we use
+ // 0, which AddInternal will convert to now()
+ int64_t modificationTime = 0;
+
+ error = AddInternal(principal, lineArray[1], permission, id,
+ nsIPermissionManager::EXPIRE_NEVER, 0,
+ modificationTime,
+ eDontNotify, operation);
+ if (NS_FAILED(error)) {
+ NS_WARNING("There was a problem importing an origin permission");
+ }
+ }
+
+ } while (isMore);
+
+ return NS_OK;
+}
+
+void
+nsPermissionManager::UpdateDB(OperationType aOp,
+ mozIStorageAsyncStatement* aStmt,
+ int64_t aID,
+ const nsACString &aOrigin,
+ const nsACString &aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ int64_t aModificationTime)
+{
+ ENSURE_NOT_CHILD_PROCESS_NORET;
+
+ nsresult rv;
+
+ // no statement is ok - just means we don't have a profile
+ if (!aStmt)
+ return;
+
+ switch (aOp) {
+ case eOperationAdding:
+ {
+ rv = aStmt->BindInt64ByIndex(0, aID);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindUTF8StringByIndex(1, aOrigin);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindUTF8StringByIndex(2, aType);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(3, aPermission);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(4, aExpireType);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(5, aExpireTime);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(6, aModificationTime);
+ break;
+ }
+
+ case eOperationRemoving:
+ {
+ rv = aStmt->BindInt64ByIndex(0, aID);
+ break;
+ }
+
+ case eOperationChanging:
+ {
+ rv = aStmt->BindInt64ByIndex(0, aID);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(1, aPermission);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(2, aExpireType);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(3, aExpireTime);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(4, aModificationTime);
+ break;
+ }
+
+ default:
+ {
+ NS_NOTREACHED("need a valid operation in UpdateDB()!");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("db change failed!");
+ return;
+ }
+
+ nsCOMPtr<mozIStoragePendingStatement> pending;
+ rv = aStmt->ExecuteAsync(nullptr, getter_AddRefs(pending));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+NS_IMETHODIMP
+nsPermissionManager::UpdateExpireTime(nsIPrincipal* aPrincipal,
+ const char* aType,
+ bool aExactHostMatch,
+ uint64_t aSessionExpireTime,
+ uint64_t aPersistentExpireTime)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ uint64_t nowms = PR_Now() / 1000;
+ if (aSessionExpireTime < nowms || aPersistentExpireTime < nowms) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Setting the expire time of an nsEP is non-sensical.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int32_t typeIndex = GetTypeIndex(aType, false);
+ // If type == -1, the type isn't known,
+ // so just return NS_OK
+ if (typeIndex == -1) return NS_OK;
+
+ PermissionHashKey* entry = GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
+ if (!entry) {
+ return NS_OK;
+ }
+
+ int32_t idx = entry->GetPermissionIndex(typeIndex);
+ if (-1 == idx) {
+ return NS_OK;
+ }
+
+ PermissionEntry& perm = entry->GetPermissions()[idx];
+ if (perm.mExpireType == EXPIRE_TIME) {
+ perm.mExpireTime = aPersistentExpireTime;
+ } else if (perm.mExpireType == EXPIRE_SESSION && perm.mExpireTime != 0) {
+ perm.mExpireTime = aSessionExpireTime;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::FetchPermissions() {
+ MOZ_ASSERT(IsChildProcess(), "FetchPermissions can only be invoked in child process");
+ // Get the permissions from the parent process
+ InfallibleTArray<IPC::Permission> perms;
+ ChildProcess()->SendReadPermissions(&perms);
+
+ for (uint32_t i = 0; i < perms.Length(); i++) {
+ const IPC::Permission &perm = perms[i];
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(perm.origin, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The child process doesn't care about modification times - it neither
+ // reads nor writes, nor removes them based on the date - so 0 (which
+ // will end up as now()) is fine.
+ uint64_t modificationTime = 0;
+ AddInternal(principal, perm.type, perm.capability, 0, perm.expireType,
+ perm.expireTime, modificationTime, eNotify, eNoDBOperation,
+ true /* ignoreSessionPermissions */);
+ }
+ return NS_OK;
+}
diff --git a/components/permissions/src/nsPermissionManager.h b/components/permissions/src/nsPermissionManager.h
new file mode 100644
index 000000000..4b21500b1
--- /dev/null
+++ b/components/permissions/src/nsPermissionManager.h
@@ -0,0 +1,295 @@
+/* -*- 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/. */
+
+#ifndef nsPermissionManager_h__
+#define nsPermissionManager_h__
+
+#include "nsIPermissionManager.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsTHashtable.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsPermission.h"
+#include "nsHashKeys.h"
+#include "nsCOMArray.h"
+#include "nsDataHashtable.h"
+
+namespace mozilla {
+class OriginAttributesPattern;
+}
+
+class nsIPermission;
+class mozIStorageConnection;
+class mozIStorageAsyncStatement;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsPermissionManager final : public nsIPermissionManager,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ class PermissionEntry
+ {
+ public:
+ PermissionEntry(int64_t aID, uint32_t aType, uint32_t aPermission,
+ uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime)
+ : mID(aID)
+ , mType(aType)
+ , mPermission(aPermission)
+ , mExpireType(aExpireType)
+ , mExpireTime(aExpireTime)
+ , mModificationTime(aModificationTime)
+ , mNonSessionPermission(aPermission)
+ , mNonSessionExpireType(aExpireType)
+ , mNonSessionExpireTime(aExpireTime)
+ {}
+
+ int64_t mID;
+ uint32_t mType;
+ uint32_t mPermission;
+ uint32_t mExpireType;
+ int64_t mExpireTime;
+ int64_t mModificationTime;
+ uint32_t mNonSessionPermission;
+ uint32_t mNonSessionExpireType;
+ uint32_t mNonSessionExpireTime;
+ };
+
+ /**
+ * PermissionKey is the key used by PermissionHashKey hash table.
+ *
+ * NOTE: It could be implementing nsIHashable but there is no reason to worry
+ * with XPCOM interfaces while we don't need to.
+ */
+ class PermissionKey
+ {
+ public:
+ explicit PermissionKey(nsIPrincipal* aPrincipal);
+ explicit PermissionKey(const nsACString& aOrigin)
+ : mOrigin(aOrigin)
+ {
+ }
+
+ bool operator==(const PermissionKey& aKey) const {
+ return mOrigin.Equals(aKey.mOrigin);
+ }
+
+ PLDHashNumber GetHashCode() const {
+ return mozilla::HashString(mOrigin);
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PermissionKey)
+
+ nsCString mOrigin;
+
+ private:
+ // Default ctor shouldn't be used.
+ PermissionKey() = delete;
+
+ // Dtor shouldn't be used outside of the class.
+ ~PermissionKey() {};
+ };
+
+ class PermissionHashKey : public nsRefPtrHashKey<PermissionKey>
+ {
+ public:
+ explicit PermissionHashKey(const PermissionKey* aPermissionKey)
+ : nsRefPtrHashKey<PermissionKey>(aPermissionKey)
+ {}
+
+ PermissionHashKey(const PermissionHashKey& toCopy)
+ : nsRefPtrHashKey<PermissionKey>(toCopy)
+ , mPermissions(toCopy.mPermissions)
+ {}
+
+ bool KeyEquals(const PermissionKey* aKey) const
+ {
+ return *aKey == *GetKey();
+ }
+
+ static PLDHashNumber HashKey(const PermissionKey* aKey)
+ {
+ return aKey->GetHashCode();
+ }
+
+ // Force the hashtable to use the copy constructor when shuffling entries
+ // around, otherwise the Auto part of our AutoTArray won't be happy!
+ enum { ALLOW_MEMMOVE = false };
+
+ inline nsTArray<PermissionEntry> & GetPermissions()
+ {
+ return mPermissions;
+ }
+
+ inline int32_t GetPermissionIndex(uint32_t aType) const
+ {
+ for (uint32_t i = 0; i < mPermissions.Length(); ++i)
+ if (mPermissions[i].mType == aType)
+ return i;
+
+ return -1;
+ }
+
+ inline PermissionEntry GetPermission(uint32_t aType) const
+ {
+ for (uint32_t i = 0; i < mPermissions.Length(); ++i)
+ if (mPermissions[i].mType == aType)
+ return mPermissions[i];
+
+ // unknown permission... return relevant data
+ return PermissionEntry(-1, aType, nsIPermissionManager::UNKNOWN_ACTION,
+ nsIPermissionManager::EXPIRE_NEVER, 0, 0);
+ }
+
+ private:
+ AutoTArray<PermissionEntry, 1> mPermissions;
+ };
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPERMISSIONMANAGER
+ NS_DECL_NSIOBSERVER
+
+ nsPermissionManager();
+ static nsIPermissionManager* GetXPCOMSingleton();
+ nsresult Init();
+
+ // enums for AddInternal()
+ enum OperationType {
+ eOperationNone,
+ eOperationAdding,
+ eOperationRemoving,
+ eOperationChanging,
+ eOperationReplacingDefault
+ };
+
+ enum DBOperationType {
+ eNoDBOperation,
+ eWriteToDB
+ };
+
+ enum NotifyOperationType {
+ eDontNotify,
+ eNotify
+ };
+
+ // A special value for a permission ID that indicates the ID was loaded as
+ // a default value. These will never be written to the database, but may
+ // be overridden with an explicit permission (including UNKNOWN_ACTION)
+ static const int64_t cIDPermissionIsDefault = -1;
+
+ nsresult AddInternal(nsIPrincipal* aPrincipal,
+ const nsAFlatCString &aType,
+ uint32_t aPermission,
+ int64_t aID,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ int64_t aModificationTime,
+ NotifyOperationType aNotifyOperation,
+ DBOperationType aDBOperation,
+ const bool aIgnoreSessionPermissions = false);
+
+ /**
+ * Initialize the "clear-origin-attributes-data" observing.
+ * Will create a nsPermissionManager instance if needed.
+ * That way, we can prevent have nsPermissionManager created at startup just
+ * to be able to clear data when an application is uninstalled.
+ */
+ static void ClearOriginDataObserverInit();
+
+ nsresult
+ RemovePermissionsWithAttributes(mozilla::OriginAttributesPattern& aAttrs);
+
+private:
+ virtual ~nsPermissionManager();
+
+ int32_t GetTypeIndex(const char *aTypeString,
+ bool aAdd);
+
+ PermissionHashKey* GetPermissionHashKey(nsIPrincipal* aPrincipal,
+ uint32_t aType,
+ bool aExactHostMatch);
+
+ nsresult CommonTestPermission(nsIPrincipal* aPrincipal,
+ const char *aType,
+ uint32_t *aPermission,
+ bool aExactHostMatch,
+ bool aIncludingSession);
+
+ nsresult OpenDatabase(nsIFile* permissionsFile);
+ nsresult InitDB(bool aRemoveFile);
+ nsresult CreateTable();
+ nsresult Import();
+ nsresult ImportDefaults();
+ nsresult _DoImport(nsIInputStream *inputStream, mozIStorageConnection *aConn);
+ nsresult Read();
+ void NotifyObserversWithPermission(nsIPrincipal* aPrincipal,
+ const nsCString &aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ const char16_t *aData);
+ void NotifyObservers(nsIPermission *aPermission, const char16_t *aData);
+
+ // Finalize all statements, close the DB and null it.
+ // if aRebuildOnSuccess, reinitialize database
+ void CloseDB(bool aRebuildOnSuccess = false);
+
+ nsresult RemoveAllInternal(bool aNotifyObservers);
+ nsresult RemoveAllFromMemory();
+ static void UpdateDB(OperationType aOp,
+ mozIStorageAsyncStatement* aStmt,
+ int64_t aID,
+ const nsACString& aOrigin,
+ const nsACString& aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ int64_t aModificationTime);
+
+ /**
+ * This method removes all permissions modified after the specified time.
+ */
+ nsresult
+ RemoveAllModifiedSince(int64_t aModificationTime);
+
+ /**
+ * Retrieve permissions from chrome process.
+ */
+ nsresult
+ FetchPermissions();
+
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ nsCOMPtr<mozIStorageAsyncStatement> mStmtInsert;
+ nsCOMPtr<mozIStorageAsyncStatement> mStmtDelete;
+ nsCOMPtr<mozIStorageAsyncStatement> mStmtUpdate;
+
+ bool mMemoryOnlyDB;
+
+ nsTHashtable<PermissionHashKey> mPermissionTable;
+ // a unique, monotonically increasing id used to identify each database entry
+ int64_t mLargestID;
+
+ // An array to store the strings identifying the different types.
+ nsTArray<nsCString> mTypeArray;
+
+ // Initially, |false|. Set to |true| once shutdown has started, to avoid
+ // reopening the database.
+ bool mIsShuttingDown;
+
+ friend class DeleteFromMozHostListener;
+ friend class CloseDatabaseListener;
+};
+
+// {4F6B5E00-0C36-11d5-A535-0010A401EB10}
+#define NS_PERMISSIONMANAGER_CID \
+{ 0x4f6b5e00, 0xc36, 0x11d5, { 0xa5, 0x35, 0x0, 0x10, 0xa4, 0x1, 0xeb, 0x10 } }
+
+#endif /* nsPermissionManager_h__ */
diff --git a/components/permissions/src/nsPopupWindowManager.cpp b/components/permissions/src/nsPopupWindowManager.cpp
new file mode 100644
index 000000000..d95fb7440
--- /dev/null
+++ b/components/permissions/src/nsPopupWindowManager.cpp
@@ -0,0 +1,111 @@
+/* -*- 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 "nsPopupWindowManager.h"
+
+#include "nsCRT.h"
+#include "nsIServiceManager.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrincipal.h"
+#include "nsIURI.h"
+#include "mozilla/Services.h"
+
+/**
+ * The Popup Window Manager maintains popup window permissions by website.
+ */
+
+static const char kPopupDisablePref[] = "dom.disable_open_during_load";
+
+//*****************************************************************************
+//*** nsPopupWindowManager object management and nsISupports
+//*****************************************************************************
+
+nsPopupWindowManager::nsPopupWindowManager() :
+ mPolicy(ALLOW_POPUP)
+{
+}
+
+nsPopupWindowManager::~nsPopupWindowManager()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsPopupWindowManager,
+ nsIPopupWindowManager,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+nsresult
+nsPopupWindowManager::Init()
+{
+ nsresult rv;
+ mPermissionManager = mozilla::services::GetPermissionManager();
+
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ bool permission;
+ rv = prefBranch->GetBoolPref(kPopupDisablePref, &permission);
+ if (NS_FAILED(rv)) {
+ permission = true;
+ }
+ mPolicy = permission ? (uint32_t) DENY_POPUP : (uint32_t) ALLOW_POPUP;
+
+ prefBranch->AddObserver(kPopupDisablePref, this, true);
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+//*** nsPopupWindowManager::nsIPopupWindowManager
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsPopupWindowManager::TestPermission(nsIPrincipal* aPrincipal,
+ uint32_t *aPermission)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aPermission);
+
+ uint32_t permit;
+ *aPermission = mPolicy;
+
+ if (mPermissionManager) {
+ if (NS_SUCCEEDED(mPermissionManager->TestPermissionFromPrincipal(aPrincipal, "popup", &permit))) {
+ // Share some constants between interfaces?
+ if (permit == nsIPermissionManager::ALLOW_ACTION) {
+ *aPermission = ALLOW_POPUP;
+ } else if (permit == nsIPermissionManager::DENY_ACTION) {
+ *aPermission = DENY_POPUP;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+//*** nsPopupWindowManager::nsIObserver
+//*****************************************************************************
+NS_IMETHODIMP
+nsPopupWindowManager::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic),
+ "unexpected topic - we only deal with pref changes!");
+
+ if (prefBranch) {
+ // refresh our local copy of the "disable popups" pref
+ bool permission = true;
+ prefBranch->GetBoolPref(kPopupDisablePref, &permission);
+
+ mPolicy = permission ? (uint32_t) DENY_POPUP : (uint32_t) ALLOW_POPUP;
+ }
+
+ return NS_OK;
+}
diff --git a/components/permissions/src/nsPopupWindowManager.h b/components/permissions/src/nsPopupWindowManager.h
new file mode 100644
index 000000000..dd83b5abb
--- /dev/null
+++ b/components/permissions/src/nsPopupWindowManager.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsPopupWindowManager_h__
+#define nsPopupWindowManager_h__
+
+#include "nsCOMPtr.h"
+
+#include "nsIObserver.h"
+#include "nsIPermissionManager.h"
+#include "nsIPopupWindowManager.h"
+#include "nsWeakReference.h"
+
+class nsPopupWindowManager : public nsIPopupWindowManager,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPOPUPWINDOWMANAGER
+ NS_DECL_NSIOBSERVER
+
+ nsPopupWindowManager();
+ nsresult Init();
+
+private:
+ virtual ~nsPopupWindowManager();
+
+ uint32_t mPolicy;
+ nsCOMPtr<nsIPermissionManager> mPermissionManager;
+};
+
+// {822bcd11-6432-48be-9e9d-36f7804b7747}
+#define NS_POPUPWINDOWMANAGER_CID \
+ {0x822bcd11, 0x6432, 0x48be, {0x9e, 0x9d, 0x36, 0xf7, 0x80, 0x4b, 0x77, 0x47}}
+
+#endif /* nsPopupWindowManager_h__ */
diff --git a/components/places/locale/places.properties b/components/places/locale/places.properties
new file mode 100644
index 000000000..5c0134180
--- /dev/null
+++ b/components/places/locale/places.properties
@@ -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/.
+
+BookmarksMenuFolderTitle=Bookmarks Menu
+BookmarksToolbarFolderTitle=Bookmarks Toolbar
+UnsortedBookmarksFolderTitle=Unsorted Bookmarks
+OtherBookmarksFolderTitle=Other Bookmarks
+TagsFolderTitle=Tags
+MobileBookmarksFolderTitle=Mobile Bookmarks
+
+# LOCALIZATION NOTE (dateName):
+# These are used to generate history containers when history is grouped by date
+finduri-AgeInDays-is-0=Today
+finduri-AgeInDays-is-1=Yesterday
+finduri-AgeInDays-is=%S days ago
+finduri-AgeInDays-last-is=Last %S days
+finduri-AgeInDays-isgreater=Older than %S days
+finduri-AgeInMonths-is-0=This month
+finduri-AgeInMonths-isgreater=Older than %S months
+# LOCALIZATION NOTE (finduri-MonthYear):
+# %1$S is the month name, %2$S is the year (4 digits format).
+finduri-MonthYear=%1$S %2$S
+
+# LOCALIZATION NOTE (localFiles):
+# This is used to generate local files container when history is grouped by site
+localhost=(local files)
+
+# LOCALIZATION NOTE
+# The string is used for showing file size of each backup in the "fileRestorePopup" popup
+# %1$S is the file size
+# %2$S is the file size unit
+backupFileSizeText=%1$S %2$S
diff --git a/components/places/moz.build b/components/places/moz.build
new file mode 100644
index 000000000..2be099873
--- /dev/null
+++ b/components/places/moz.build
@@ -0,0 +1,89 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['public/nsINavHistoryService.idl']
+
+if CONFIG['MOZ_PLACES']:
+ XPIDL_SOURCES += [
+ 'public/mozIAsyncFavicons.idl',
+ 'public/mozIAsyncHistory.idl',
+ 'public/mozIAsyncLivemarks.idl',
+ 'public/mozIColorAnalyzer.idl',
+ 'public/mozIPlacesAutoComplete.idl',
+ 'public/mozIPlacesPendingOperation.idl',
+ 'public/nsIAnnotationService.idl',
+ 'public/nsIBrowserHistory.idl',
+ 'public/nsIDownloadHistory.idl',
+ 'public/nsIFaviconService.idl',
+ 'public/nsINavBookmarksService.idl',
+ 'public/nsITaggingService.idl',
+ 'public/nsPIPlacesDatabase.idl',
+ ]
+
+ EXPORTS.mozilla.places = [
+ 'src/Database.h',
+ 'src/History.h',
+ ]
+
+ UNIFIED_SOURCES += [
+ 'src/Database.cpp',
+ 'src/FaviconHelpers.cpp',
+ 'src/Helpers.cpp',
+ 'src/History.cpp',
+ 'src/nsAnnoProtocolHandler.cpp',
+ 'src/nsAnnotationService.cpp',
+ 'src/nsFaviconService.cpp',
+ 'src/nsNavBookmarks.cpp',
+ 'src/nsNavHistory.cpp',
+ 'src/nsNavHistoryQuery.cpp',
+ 'src/nsNavHistoryResult.cpp',
+ 'src/nsPlacesModule.cpp',
+ 'src/PlaceInfo.cpp',
+ 'src/Shutdown.cpp',
+ 'src/SQLFunctions.cpp',
+ 'src/VisitInfo.cpp',
+ ]
+
+ LOCAL_INCLUDES += ['../build']
+
+ EXTRA_COMPONENTS += [
+ 'nsPlacesAutoComplete.manifest',
+ 'src/ColorAnalyzer.js',
+ 'src/nsLivemarkService.js',
+ 'src/nsPlacesAutoComplete.js',
+ 'src/nsPlacesExpiration.js',
+ 'src/nsTaggingService.js',
+ 'src/PageIconProtocolHandler.js',
+ 'src/PlacesCategoriesStarter.js',
+ 'src/UnifiedComplete.js',
+ 'toolkitplaces.manifest',
+ ]
+
+ EXTRA_JS_MODULES += [
+ 'src/BookmarkHTMLUtils.jsm',
+ 'src/BookmarkJSONUtils.jsm',
+ 'src/Bookmarks.jsm',
+ 'src/ClusterLib.js',
+ 'src/ColorAnalyzer_worker.js',
+ 'src/ColorConversion.js',
+ 'src/ExtensionSearchHandler.jsm',
+ 'src/History.jsm',
+ 'src/PlacesBackups.jsm',
+ 'src/PlacesDBUtils.jsm',
+ 'src/PlacesRemoteTabsAutocompleteProvider.jsm',
+ 'src/PlacesSearchAutocompleteProvider.jsm',
+ 'src/PlacesSyncUtils.jsm',
+ 'src/PlacesTransactions.jsm',
+ 'src/PlacesUtils.jsm',
+ ]
+
+ FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+XPIDL_MODULE = 'places' \ No newline at end of file
diff --git a/components/places/nsPlacesAutoComplete.manifest b/components/places/nsPlacesAutoComplete.manifest
new file mode 100644
index 000000000..eb704f449
--- /dev/null
+++ b/components/places/nsPlacesAutoComplete.manifest
@@ -0,0 +1,6 @@
+component {d0272978-beab-4adc-a3d4-04b76acfa4e7} nsPlacesAutoComplete.js
+contract @mozilla.org/autocomplete/search;1?name=history {d0272978-beab-4adc-a3d4-04b76acfa4e7}
+
+component {c88fae2d-25cf-4338-a1f4-64a320ea7440} nsPlacesAutoComplete.js
+contract @mozilla.org/autocomplete/search;1?name=urlinline {c88fae2d-25cf-4338-a1f4-64a320ea7440}
+
diff --git a/components/places/public/mozIAsyncFavicons.idl b/components/places/public/mozIAsyncFavicons.idl
new file mode 100644
index 000000000..f1be18278
--- /dev/null
+++ b/components/places/public/mozIAsyncFavicons.idl
@@ -0,0 +1,174 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsIURI;
+interface nsIFaviconDataCallback;
+interface nsIPrincipal;
+interface mozIPlacesPendingOperation;
+
+[scriptable, uuid(a9c81797-9133-4823-b55f-3646e67cfd41)]
+interface mozIAsyncFavicons : nsISupports
+{
+ /**
+ * Declares that a given page uses a favicon with the given URI and
+ * attempts to fetch and save the icon data by loading the favicon URI
+ * through an async network request.
+ *
+ * If the icon data already exists, we won't try to reload the icon unless
+ * aForceReload is true. Similarly, if the icon is in the failed favicon
+ * cache we won't do anything unless aForceReload is true, in which case
+ * we'll try to reload the favicon.
+ *
+ * This function will only save favicons for pages that are already stored in
+ * the database, like visited pages or bookmarks. For any other URIs, it
+ * will succeed but do nothing. This function will also ignore the error
+ * page favicon URI (see FAVICON_ERRORPAGE_URL below).
+ *
+ * Icons that fail to load will automatically be added to the failed favicon
+ * cache, and this function will not save favicons for non-bookmarked URIs
+ * when history is disabled.
+ *
+ * @note This function is identical to
+ * nsIFaviconService::setAndLoadFaviconForPage.
+ *
+ * @param aPageURI
+ * URI of the page whose favicon is being set.
+ * @param aFaviconURI
+ * URI of the favicon to associate with the page.
+ * @param aForceReload
+ * If aForceReload is false, we try to reload the favicon only if we
+ * don't have it or it has expired from the cache. Setting
+ * aForceReload to true causes us to reload the favicon even if we
+ * have a usable copy.
+ * @param aFaviconLoadType
+ * Set to FAVICON_LOAD_PRIVATE if the favicon is loaded from a private
+ * browsing window. Set to FAVICON_LOAD_NON_PRIVATE otherwise.
+ * @param aCallback
+ * Once we're done setting and/or fetching the favicon, we invoke this
+ * callback.
+ * @param aLoadingPrincipal
+ * Principal of the page whose favicon is being set. If this argument
+ * is omitted, the loadingPrincipal defaults to the nullPrincipal.
+ *
+ * @see nsIFaviconDataCallback in nsIFaviconService.idl.
+ */
+ mozIPlacesPendingOperation setAndFetchFaviconForPage(
+ in nsIURI aPageURI,
+ in nsIURI aFaviconURI,
+ in boolean aForceReload,
+ in unsigned long aFaviconLoadType,
+ [optional] in nsIFaviconDataCallback aCallback,
+ [optional] in nsIPrincipal aLoadingPrincipal);
+ /**
+ * Sets the data for a given favicon URI either by replacing existing data in
+ * the database or taking the place of otherwise fetched icon data when
+ * calling setAndFetchFaviconForPage later.
+ *
+ * Favicon data for favicon URIs that are not associated with a page URI via
+ * setAndFetchFaviconForPage will be stored in memory, but may be expired at
+ * any time, so you should make an effort to associate favicon URIs with page
+ * URIs as soon as possible.
+ *
+ * It's better to not use this function for chrome: icon URIs since you can
+ * reference the chrome image yourself. getFaviconLinkForIcon/Page will ignore
+ * any associated data if the favicon URI is "chrome:" and just return the
+ * same chrome URI.
+ *
+ * This function does NOT send out notifications that the data has changed.
+ * Pages using this favicons that are visible in history or bookmarks views
+ * will keep the old icon until they have been refreshed by other means.
+ *
+ * This function tries to optimize the favicon size, if it is bigger
+ * than a defined limit we will try to convert it to a 16x16 png image.
+ * If the conversion fails and favicon is still bigger than our max accepted
+ * size it won't be saved.
+ *
+ * @param aFaviconURI
+ * URI of the favicon whose data is being set.
+ * @param aData
+ * Binary contents of the favicon to save
+ * @param aDataLength
+ * Length of binary data
+ * @param aMimeType
+ * MIME type of the data to store. This is important so that we know
+ * what to report when the favicon is used. You should always set this
+ * param unless you are clearing an icon.
+ * @param aExpiration
+ * Time in microseconds since the epoch when this favicon expires.
+ * Until this time, we won't try to load it again.
+ * @throws NS_ERROR_FAILURE
+ * Thrown if the favicon is overbloated and won't be saved to the db.
+ */
+ void replaceFaviconData(in nsIURI aFaviconURI,
+ [const,array,size_is(aDataLen)] in octet aData,
+ in unsigned long aDataLen,
+ in AUTF8String aMimeType,
+ [optional] in PRTime aExpiration);
+
+ /**
+ * Same as replaceFaviconData but the data is provided by a string
+ * containing a data URL.
+ *
+ * @see replaceFaviconData
+ *
+ * @param aFaviconURI
+ * URI of the favicon whose data is being set.
+ * @param aDataURL
+ * string containing a data URL that represents the contents of
+ * the favicon to save
+ * @param aExpiration
+ * Time in microseconds since the epoch when this favicon expires.
+ * Until this time, we won't try to load it again.
+ * @param aLoadingPrincipal
+ * Principal of the page whose favicon is being set. If this argument
+ * is omitted, the loadingPrincipal defaults to the nullPrincipal.
+ * @throws NS_ERROR_FAILURE
+ * Thrown if the favicon is overbloated and won't be saved to the db.
+ */
+ void replaceFaviconDataFromDataURL(in nsIURI aFaviconURI,
+ in AString aDataURL,
+ [optional] in PRTime aExpiration,
+ [optional] in nsIPrincipal aLoadingPrincipal);
+
+ /**
+ * Retrieves the favicon URI associated to the given page, if any.
+ *
+ * @param aPageURI
+ * URI of the page whose favicon URI we're looking up.
+ * @param aCallback
+ * This callback is always invoked to notify the result of the lookup.
+ * The aURI parameter will be the favicon URI, or null when no favicon
+ * is associated with the page or an error occurred while fetching it.
+ *
+ * @note When the callback is invoked, aDataLen will be always 0, aData will
+ * be an empty array, and aMimeType will be an empty string, regardless
+ * of whether a favicon is associated with the page.
+ *
+ * @see nsIFaviconDataCallback in nsIFaviconService.idl.
+ */
+ void getFaviconURLForPage(in nsIURI aPageURI,
+ in nsIFaviconDataCallback aCallback);
+
+ /**
+ * Retrieves the favicon URI and data associated to the given page, if any.
+ *
+ * @param aPageURI
+ * URI of the page whose favicon URI and data we're looking up.
+ * @param aCallback
+ * This callback is always invoked to notify the result of the lookup. The aURI
+ * parameter will be the favicon URI, or null when no favicon is
+ * associated with the page or an error occurred while fetching it. If
+ * aURI is not null, the other parameters may contain the favicon data.
+ * However, if no favicon data is currently associated with the favicon
+ * URI, aDataLen will be 0, aData will be an empty array, and aMimeType
+ * will be an empty string.
+ *
+ * @see nsIFaviconDataCallback in nsIFaviconService.idl.
+ */
+ void getFaviconDataForPage(in nsIURI aPageURI,
+ in nsIFaviconDataCallback aCallback);
+};
diff --git a/components/places/public/mozIAsyncHistory.idl b/components/places/public/mozIAsyncHistory.idl
new file mode 100644
index 000000000..35c8cc3a6
--- /dev/null
+++ b/components/places/public/mozIAsyncHistory.idl
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIVariant;
+
+[scriptable, uuid(41e4ccc9-f0c8-4cd7-9753-7a38514b8488)]
+interface mozIVisitInfo : nsISupports
+{
+ /**
+ * The machine-local (internal) id of the visit.
+ */
+ readonly attribute long long visitId;
+
+ /**
+ * The time the visit occurred.
+ */
+ readonly attribute PRTime visitDate;
+
+ /**
+ * The transition type used to get to this visit. One of the TRANSITION_TYPE
+ * constants on nsINavHistory.
+ *
+ * @see nsINavHistory.idl
+ */
+ readonly attribute unsigned long transitionType;
+
+ /**
+ * The referring URI of this visit. This may be null.
+ */
+ readonly attribute nsIURI referrerURI;
+};
+
+[scriptable, uuid(ad83e137-c92a-4b7b-b67e-0a318811f91e)]
+interface mozIPlaceInfo : nsISupports
+{
+ /**
+ * The machine-local (internal) id of the place.
+ */
+ readonly attribute long long placeId;
+
+ /**
+ * The globally unique id of the place.
+ */
+ readonly attribute ACString guid;
+
+ /**
+ * The URI of the place.
+ */
+ readonly attribute nsIURI uri;
+
+ /**
+ * The title associated with the place.
+ */
+ readonly attribute AString title;
+
+ /**
+ * The frecency of the place.
+ */
+ readonly attribute long long frecency;
+
+ /**
+ * An array of mozIVisitInfo objects for the place.
+ */
+ [implicit_jscontext]
+ readonly attribute jsval visits;
+};
+
+/**
+ * Shared Callback interface for mozIAsyncHistory methods. The semantics
+ * for each method are detailed in mozIAsyncHistory.
+ */
+[scriptable, uuid(1f266877-2859-418b-a11b-ec3ae4f4f93d)]
+interface mozIVisitInfoCallback : nsISupports
+{
+ /**
+ * Called when the given place could not be processed.
+ *
+ * @param aResultCode
+ * nsresult indicating the failure reason.
+ * @param aPlaceInfo
+ * The information that was given to the caller for the place.
+ */
+ void handleError(in nsresult aResultCode,
+ in mozIPlaceInfo aPlaceInfo);
+
+ /**
+ * Called for each place processed successfully.
+ *
+ * @param aPlaceInfo
+ * The current info stored for the place.
+ */
+ void handleResult(in mozIPlaceInfo aPlaceInfo);
+
+ /**
+ * Called when all records were processed.
+ */
+ void handleCompletion();
+
+};
+
+[scriptable, function, uuid(994092bf-936f-449b-8dd6-0941e024360d)]
+interface mozIVisitedStatusCallback : nsISupports
+{
+ /**
+ * Notifies whether a certain URI has been visited.
+ *
+ * @param aURI
+ * URI being notified about.
+ * @param aVisitedStatus
+ * The visited status of aURI.
+ */
+ void isVisited(in nsIURI aURI,
+ in boolean aVisitedStatus);
+};
+
+[scriptable, uuid(1643EFD2-A329-4733-A39D-17069C8D3B2D)]
+interface mozIAsyncHistory : nsISupports
+{
+ /**
+ * Gets the available information for the given array of places, each
+ * identified by either nsIURI or places GUID (string).
+ *
+ * The retrieved places info objects DO NOT include the visits data (the
+ * |visits| attribute is set to null).
+ *
+ * If a given place does not exist in the database, aCallback.handleError is
+ * called for it with NS_ERROR_NOT_AVAILABLE result code.
+ *
+ * @param aPlaceIdentifiers
+ * The place[s] for which to retrieve information, identified by either
+ * a single place GUID, a single URI, or a JS array of URIs and/or GUIDs.
+ * @param aCallback
+ * A mozIVisitInfoCallback object which consists of callbacks to be
+ * notified for successful or failed retrievals.
+ * If there's no information available for a given place, aCallback
+ * is called with a stub place info object, containing just the provided
+ * data (GUID or URI).
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * - Passing in NULL for aPlaceIdentifiers or aCallback.
+ * - Not providing at least one valid GUID or URI.
+ */
+ [implicit_jscontext]
+ void getPlacesInfo(in jsval aPlaceIdentifiers,
+ in mozIVisitInfoCallback aCallback);
+
+ /**
+ * Adds a set of visits for one or more mozIPlaceInfo objects, and updates
+ * each mozIPlaceInfo's title or guid.
+ *
+ * aCallback.handleResult is called for each visit added.
+ *
+ * @param aPlaceInfo
+ * The mozIPlaceInfo object[s] containing the information to store or
+ * update. This can be a single object, or an array of objects.
+ * @param [optional] aCallback
+ * A mozIVisitInfoCallback object which consists of callbacks to be
+ * notified for successful and/or failed changes.
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * - Passing in NULL for aPlaceInfo.
+ * - Not providing at least one valid guid, or uri for all
+ * mozIPlaceInfo object[s].
+ * - Not providing an array or nothing for the visits property of
+ * mozIPlaceInfo.
+ * - Not providing a visitDate and transitionType for each
+ * mozIVisitInfo.
+ * - Providing an invalid transitionType for a mozIVisitInfo.
+ */
+ [implicit_jscontext]
+ void updatePlaces(in jsval aPlaceInfo,
+ [optional] in mozIVisitInfoCallback aCallback);
+
+ /**
+ * Checks if a given URI has been visited.
+ *
+ * @param aURI
+ * The URI to check for.
+ * @param aCallback
+ * A mozIVisitStatusCallback object which receives the visited status.
+ */
+ void isURIVisited(in nsIURI aURI,
+ in mozIVisitedStatusCallback aCallback);
+};
diff --git a/components/places/public/mozIAsyncLivemarks.idl b/components/places/public/mozIAsyncLivemarks.idl
new file mode 100644
index 000000000..e84ecca8e
--- /dev/null
+++ b/components/places/public/mozIAsyncLivemarks.idl
@@ -0,0 +1,190 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 mozILivemarkInfo;
+interface mozILivemark;
+
+interface nsINavHistoryResultObserver;
+
+[scriptable, uuid(672387b7-a75d-4e8f-9b49-5c1dcbfff46b)]
+interface mozIAsyncLivemarks : nsISupports
+{
+ /**
+ * Creates a new livemark
+ *
+ * @param aLivemarkInfo
+ * mozILivemarkInfo object containing at least title, parentId,
+ * index and feedURI of the livemark to create.
+ *
+ * @return {Promise}
+ * @throws NS_ERROR_INVALID_ARG if the supplied information is insufficient
+ * for the creation.
+ */
+ jsval addLivemark(in jsval aLivemarkInfo);
+
+ /**
+ * Removes an existing livemark.
+ *
+ * @param aLivemarkInfo
+ * mozILivemarkInfo object containing either an id or a guid of the
+ * livemark to remove.
+ *
+ * @return {Promise}
+ * @throws NS_ERROR_INVALID_ARG if the id/guid is invalid.
+ */
+ jsval removeLivemark(in jsval aLivemarkInfo);
+
+ /**
+ * Gets an existing livemark.
+ *
+ * @param aLivemarkInfo
+ * mozILivemarkInfo object containing either an id or a guid of the
+ * livemark to retrieve.
+ *
+ * @return {Promise}
+ * @throws NS_ERROR_INVALID_ARG if the id/guid is invalid or an invalid
+ * callback is provided.
+ */
+ jsval getLivemark(in jsval aLivemarkInfo);
+
+ /**
+ * Reloads all livemarks if they are expired or if forced to do so.
+ *
+ * @param [optional]aForceUpdate
+ * If set to true forces a reload even if contents are still valid.
+ *
+ * @note The update process is asynchronous, observers registered through
+ * registerForUpdates will be notified of updated contents.
+ */
+ void reloadLivemarks([optional]in boolean aForceUpdate);
+};
+
+[scriptable, uuid(3a3c5e8f-ec4a-4086-ae0a-d16420d30c9f)]
+interface mozILivemarkInfo : nsISupports
+{
+ /**
+ * Id of the bookmarks folder representing this livemark.
+ *
+ * @deprecated Use guid instead.
+ */
+ readonly attribute long long id;
+
+ /**
+ * The globally unique identifier of this livemark.
+ */
+ readonly attribute ACString guid;
+
+ /**
+ * Title of this livemark.
+ */
+ readonly attribute AString title;
+
+ /**
+ * Id of the bookmarks parent folder containing this livemark.
+ *
+ * @deprecated Use parentGuid instead.
+ */
+ readonly attribute long long parentId;
+
+ /**
+ * Guid of the bookmarks parent folder containing this livemark.
+ */
+ readonly attribute long long parentGuid;
+
+ /**
+ * The position of this livemark in the bookmarks parent folder.
+ */
+ readonly attribute long index;
+
+ /**
+ * Time this livemark was created.
+ */
+ readonly attribute PRTime dateAdded;
+
+ /**
+ * Time this livemark's details were last modified. Doesn't track changes to
+ * the livemark contents.
+ */
+ readonly attribute PRTime lastModified;
+
+ /**
+ * The URI of the syndication feed associated with this livemark.
+ */
+ readonly attribute nsIURI feedURI;
+
+ /**
+ * The URI of the website associated with this livemark.
+ */
+ readonly attribute nsIURI siteURI;
+};
+
+[scriptable, uuid(9f6fdfae-db9a-4bd8-bde1-148758cf1b18)]
+interface mozILivemark : mozILivemarkInfo
+{
+ // Indicates the livemark is inactive.
+ const unsigned short STATUS_READY = 0;
+ // Indicates the livemark is fetching new contents.
+ const unsigned short STATUS_LOADING = 1;
+ // Indicates the livemark failed to fetch new contents.
+ const unsigned short STATUS_FAILED = 2;
+
+ /**
+ * Status of this livemark. One of the STATUS_* constants above.
+ */
+ readonly attribute unsigned short status;
+
+ /**
+ * Reload livemark contents if they are expired or if forced to do so.
+ *
+ * @param [optional]aForceUpdate
+ * If set to true forces a reload even if contents are still valid.
+ *
+ * @note The update process is asynchronous, it's possible to register a
+ * result observer to be notified of updated contents through
+ * registerForUpdates.
+ */
+ void reload([optional]in boolean aForceUpdate);
+
+ /**
+ * Returns an array of nsINavHistoryResultNode objects, representing children
+ * of this livemark. The nodes will have aContainerNode as parent.
+ *
+ * @param aContainerNode
+ * Object implementing nsINavHistoryContainerResultNode, to be used as
+ * parent of the livemark nodes.
+ */
+ jsval getNodesForContainer(in jsval aContainerNode);
+
+ /**
+ * Registers a container node for updates on this livemark.
+ * When the livemark contents change, an invalidateContainer(aContainerNode)
+ * request is sent to aResultObserver.
+ *
+ * @param aContainerNode
+ * Object implementing nsINavHistoryContainerResultNode, representing
+ * this livemark.
+ * @param aResultObserver
+ * The nsINavHistoryResultObserver that should be notified of changes
+ * to the livemark contents.
+ */
+ void registerForUpdates(in jsval aContainerNode,
+ in nsINavHistoryResultObserver aResultObserver);
+
+ /**
+ * Unregisters a previously registered container node.
+ *
+ * @param aContainerNode
+ * Object implementing nsINavHistoryContainerResultNode, representing
+ * this livemark.
+ *
+ * @note it's suggested to always unregister containers that are no more used,
+ * to free up the associated resources. A good time to do so is when
+ * the container gets closed.
+ */
+ void unregisterForUpdates(in jsval aContainerNode);
+};
diff --git a/components/places/public/mozIColorAnalyzer.idl b/components/places/public/mozIColorAnalyzer.idl
new file mode 100644
index 000000000..368958cbb
--- /dev/null
+++ b/components/places/public/mozIColorAnalyzer.idl
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+
+[function, scriptable, uuid(e4089e21-71b6-40af-b546-33c21b90e874)]
+interface mozIRepresentativeColorCallback : nsISupports
+{
+ /**
+ * Will be called when color analysis finishes.
+ *
+ * @param success
+ * True if analysis was successful, false otherwise.
+ * Analysis can fail if the image is transparent, imageURI doesn't
+ * resolve to a valid image, or the image is too big.
+ *
+ * @param color
+ * The representative color as an integer in RGB form.
+ * e.g. 0xFF0102 == rgb(255,1,2)
+ * If success is false, color is not provided.
+ */
+ void onComplete(in boolean success, [optional] in unsigned long color);
+};
+
+[scriptable, uuid(d056186c-28a0-494e-aacc-9e433772b143)]
+interface mozIColorAnalyzer : nsISupports
+{
+ /**
+ * Given an image URI, find the most representative color for that image
+ * based on the frequency of each color. Preference is given to colors that
+ * are more interesting. Avoids the background color if it can be
+ * discerned. Ignores sufficiently transparent colors.
+ *
+ * This is intended to be used on favicon images. Larger images take longer
+ * to process, especially those with a larger number of unique colors. If
+ * imageURI points to an image that has more than 128^2 pixels, this method
+ * will fail before analyzing it for performance reasons.
+ *
+ * @param imageURI
+ * A URI pointing to the image - ideally a data: URI, but any scheme
+ * that will load when setting the src attribute of a DOM img element
+ * should work.
+ * @param callback
+ * Function to call when the representative color is found or an
+ * error occurs.
+ */
+ void findRepresentativeColor(in nsIURI imageURI,
+ in mozIRepresentativeColorCallback callback);
+};
diff --git a/components/places/public/mozIPlacesAutoComplete.idl b/components/places/public/mozIPlacesAutoComplete.idl
new file mode 100644
index 000000000..6ff82e667
--- /dev/null
+++ b/components/places/public/mozIPlacesAutoComplete.idl
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=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;
+
+/**
+ * This interface provides some constants used by the Places AutoComplete
+ * search provider as well as methods to track opened pages for AutoComplete
+ * purposes.
+ */
+[scriptable, uuid(61b6348a-09e1-4810-8057-f8cb3cec6ef8)]
+interface mozIPlacesAutoComplete : nsISupports
+{
+ //////////////////////////////////////////////////////////////////////////////
+ //// Matching Constants
+
+ /**
+ * Match anywhere in each searchable term.
+ */
+ const long MATCH_ANYWHERE = 0;
+
+ /**
+ * Match first on word boundaries, and if we do not get enough results, then
+ * match anywhere in each searchable term.
+ */
+ const long MATCH_BOUNDARY_ANYWHERE = 1;
+
+ /**
+ * Match on word boundaries in each searchable term.
+ */
+ const long MATCH_BOUNDARY = 2;
+
+ /**
+ * Match only the beginning of each search term.
+ */
+ const long MATCH_BEGINNING = 3;
+
+ /**
+ * Match anywhere in each searchable term without doing any transformation
+ * or stripping on the underlying data.
+ */
+ const long MATCH_ANYWHERE_UNMODIFIED = 4;
+
+ /**
+ * Match only the beginning of each search term using a case sensitive
+ * comparator.
+ */
+ const long MATCH_BEGINNING_CASE_SENSITIVE = 5;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Search Behavior Constants
+
+ /**
+ * Search through history.
+ */
+ const long BEHAVIOR_HISTORY = 1 << 0;
+
+ /**
+ * Search though bookmarks.
+ */
+ const long BEHAVIOR_BOOKMARK = 1 << 1;
+
+ /**
+ * Search through tags.
+ */
+ const long BEHAVIOR_TAG = 1 << 2;
+
+ /**
+ * Search the title of pages.
+ */
+ const long BEHAVIOR_TITLE = 1 << 3;
+
+ /**
+ * Search the URL of pages.
+ */
+ const long BEHAVIOR_URL = 1 << 4;
+
+ /**
+ * Search for typed pages.
+ */
+ const long BEHAVIOR_TYPED = 1 << 5;
+
+ /**
+ * Search javascript: URLs.
+ */
+ const long BEHAVIOR_JAVASCRIPT = 1 << 6;
+
+ /**
+ * Search for pages that have been marked as being opened, such as a tab
+ * in a tabbrowser.
+ */
+ const long BEHAVIOR_OPENPAGE = 1 << 7;
+
+ /**
+ * Use intersection between history, typed, bookmark, tag and openpage
+ * instead of union, when the restrict bit is set.
+ */
+ const long BEHAVIOR_RESTRICT = 1 << 8;
+
+ /**
+ * Include search suggestions from the currently selected search provider.
+ */
+ const long BEHAVIOR_SEARCHES = 1 << 9;
+
+ /**
+ * Mark a page as being currently open.
+ *
+ * @note Pages will not be automatically unregistered when Private Browsing
+ * mode is entered or exited. Therefore, consumers MUST unregister or
+ * register themselves.
+ *
+ * @param aURI
+ * The URI to register as an open page.
+ */
+ void registerOpenPage(in nsIURI aURI);
+
+ /**
+ * Mark a page as no longer being open (either by closing the window or tab,
+ * or by navigating away from that page).
+ *
+ * @note Pages will not be automatically unregistered when Private Browsing
+ * mode is entered or exited. Therefore, consumers MUST unregister or
+ * register themselves.
+ *
+ * @param aURI
+ * The URI to unregister as an open page.
+ */
+ void unregisterOpenPage(in nsIURI aURI);
+};
diff --git a/components/places/public/mozIPlacesPendingOperation.idl b/components/places/public/mozIPlacesPendingOperation.idl
new file mode 100644
index 000000000..678a90870
--- /dev/null
+++ b/components/places/public/mozIPlacesPendingOperation.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(ebd31374-3808-40e4-9e73-303bf70467c3)]
+interface mozIPlacesPendingOperation : nsISupports {
+ /**
+ * Cancels a pending operation, if possible. This will only fail if you try
+ * to cancel more than once.
+ */
+ void cancel();
+};
diff --git a/components/places/public/nsIAnnotationService.idl b/components/places/public/nsIAnnotationService.idl
new file mode 100644
index 000000000..bdd417ece
--- /dev/null
+++ b/components/places/public/nsIAnnotationService.idl
@@ -0,0 +1,422 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsIURI;
+interface nsIVariant;
+interface mozIAnnotatedResult;
+
+[scriptable, uuid(63fe98e0-6889-4c2c-ac9f-703e4bc25027)]
+interface nsIAnnotationObserver : nsISupports
+{
+ /**
+ * Called when an annotation value is set. It could be a new annotation,
+ * or it could be a new value for an existing annotation.
+ */
+ void onPageAnnotationSet(in nsIURI aPage,
+ in AUTF8String aName);
+ void onItemAnnotationSet(in long long aItemId,
+ in AUTF8String aName,
+ in unsigned short aSource);
+
+ /**
+ * Called when an annotation is deleted. If aName is empty, then ALL
+ * annotations for the given URI have been deleted. This is not called when
+ * annotations are expired (normally happens when the app exits).
+ */
+ void onPageAnnotationRemoved(in nsIURI aURI,
+ in AUTF8String aName);
+ void onItemAnnotationRemoved(in long long aItemId,
+ in AUTF8String aName,
+ in unsigned short aSource);
+};
+
+[scriptable, uuid(D4CDAAB1-8EEC-47A8-B420-AD7CB333056A)]
+interface nsIAnnotationService : nsISupports
+{
+ /**
+ * Valid values for aExpiration, which sets the expiration policy for your
+ * annotation. The times for the days, weeks and months policies are
+ * measured since the last visit date of the page in question. These
+ * will not expire so long as the user keeps visiting the page from time
+ * to time.
+ */
+
+ // For temporary data that can be discarded when the user exits.
+ // Removed at application exit.
+ const unsigned short EXPIRE_SESSION = 0;
+
+ // NOTE: 1 is skipped due to its temporary use as EXPIRE_NEVER in bug #319455.
+
+ // For general page settings, things the user is interested in seeing
+ // if they come back to this page some time in the near future.
+ // Removed at 30 days.
+ const unsigned short EXPIRE_WEEKS = 2;
+
+ // Something that the user will be interested in seeing in their
+ // history like favicons. If they haven't visited a page in a couple
+ // of months, they probably aren't interested in many other annotations,
+ // the positions of things, or other stuff you create, so put that in
+ // the weeks policy.
+ // Removed at 180 days.
+ const unsigned short EXPIRE_MONTHS = 3;
+
+ // For annotations that only live as long as the URI is in the database.
+ // A page annotation will expire if the page has no visits
+ // and is not bookmarked.
+ // An item annotation will expire when the item is deleted.
+ const unsigned short EXPIRE_NEVER = 4;
+
+ // For annotations that only live as long as the URI has visits.
+ // Valid only for page annotations.
+ const unsigned short EXPIRE_WITH_HISTORY = 5;
+
+ // For short-lived temporary data that you still want to outlast a session.
+ // Removed at 7 days.
+ const unsigned short EXPIRE_DAYS = 6;
+
+ // type constants
+ const unsigned short TYPE_INT32 = 1;
+ const unsigned short TYPE_DOUBLE = 2;
+ const unsigned short TYPE_STRING = 3;
+ const unsigned short TYPE_INT64 = 5;
+
+ /**
+ * Sets an annotation, overwriting any previous annotation with the same
+ * URL/name. IT IS YOUR JOB TO NAMESPACE YOUR ANNOTATION NAMES.
+ * Use the form "namespace/value", so your name would be like
+ * "bills_extension/page_state" or "history/thumbnail".
+ *
+ * Do not use characters that are not valid in URLs such as spaces, ":",
+ * commas, or most other symbols. You should stick to ASCII letters and
+ * numbers plus "_", "-", and "/".
+ *
+ * aExpiration is one of EXPIRE_* above. aFlags should be 0 for now, some
+ * flags will be defined in the future.
+ *
+ * NOTE: ALL PAGE ANNOTATIONS WILL GET DELETED WHEN THE PAGE IS REMOVED FROM
+ * HISTORY IF THE PAGE IS NOT BOOKMARKED. This means that if you create an
+ * annotation on an unvisited URI, it will get deleted when the browser
+ * shuts down. Otherwise, URIs can exist in history as annotations but the
+ * user has no way of knowing it, potentially violating their privacy
+ * expectations about actions such as "Clear history".
+ * If there is an important annotation that the user or extension wants to
+ * keep, you should add a bookmark for the page and use an EXPIRE_NEVER
+ * annotation. This will ensure the annotation exists until the item is
+ * removed by the user.
+ * See EXPIRE_* constants above for further information.
+ *
+ * For item annotations, aSource should be a change source constant from
+ * nsINavBookmarksService::SOURCE_*, and defaults to SOURCE_DEFAULT if
+ * omitted.
+ *
+ * The annotation "favicon" is special. Favicons are stored in the favicon
+ * service, but are special cased in the protocol handler so they look like
+ * annotations. Do not set favicons using this service, it will not work.
+ *
+ * Only C++ consumers may use the type-specific methods.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE if the page or the bookmark doesn't exist.
+ */
+ void setPageAnnotation(in nsIURI aURI,
+ in AUTF8String aName,
+ in nsIVariant aValue,
+ in long aFlags,
+ in unsigned short aExpiration);
+ void setItemAnnotation(in long long aItemId,
+ in AUTF8String aName,
+ in nsIVariant aValue,
+ in long aFlags,
+ in unsigned short aExpiration,
+ [optional] in unsigned short aSource);
+
+ /**
+ * @throws NS_ERROR_ILLEGAL_VALUE if the page or the bookmark doesn't exist.
+ */
+ [noscript] void setPageAnnotationString(in nsIURI aURI,
+ in AUTF8String aName,
+ in AString aValue,
+ in long aFlags,
+ in unsigned short aExpiration);
+ [noscript] void setItemAnnotationString(in long long aItemId,
+ in AUTF8String aName,
+ in AString aValue,
+ in long aFlags,
+ in unsigned short aExpiration,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Sets an annotation just like setAnnotationString, but takes an Int32 as
+ * input.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE if the page or the bookmark doesn't exist.
+ */
+ [noscript] void setPageAnnotationInt32(in nsIURI aURI,
+ in AUTF8String aName,
+ in long aValue,
+ in long aFlags,
+ in unsigned short aExpiration);
+ [noscript] void setItemAnnotationInt32(in long long aItemId,
+ in AUTF8String aName,
+ in long aValue,
+ in long aFlags,
+ in unsigned short aExpiration,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Sets an annotation just like setAnnotationString, but takes an Int64 as
+ * input.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE if the page or the bookmark doesn't exist.
+ */
+ [noscript] void setPageAnnotationInt64(in nsIURI aURI,
+ in AUTF8String aName,
+ in long long aValue,
+ in long aFlags,
+ in unsigned short aExpiration);
+ [noscript] void setItemAnnotationInt64(in long long aItemId,
+ in AUTF8String aName,
+ in long long aValue,
+ in long aFlags,
+ in unsigned short aExpiration,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Sets an annotation just like setAnnotationString, but takes a double as
+ * input.
+ *
+ * @throws NS_ERROR_ILLEGAL_VALUE if the page or the bookmark doesn't exist.
+ */
+ [noscript] void setPageAnnotationDouble(in nsIURI aURI,
+ in AUTF8String aName,
+ in double aValue,
+ in long aFlags,
+ in unsigned short aExpiration);
+ [noscript] void setItemAnnotationDouble(in long long aItemId,
+ in AUTF8String aName,
+ in double aValue,
+ in long aFlags,
+ in unsigned short aExpiration,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Retrieves the value of a given annotation. Throws an error if the
+ * annotation does not exist. C++ consumers may use the type-specific
+ * methods.
+ *
+ * The type-specific methods throw if the given annotation is set in
+ * a different type.
+ */
+ nsIVariant getPageAnnotation(in nsIURI aURI,
+ in AUTF8String aName);
+ nsIVariant getItemAnnotation(in long long aItemId,
+ in AUTF8String aName);
+
+ /**
+ * @see getPageAnnotation
+ */
+ [noscript] AString getPageAnnotationString(in nsIURI aURI,
+ in AUTF8String aName);
+ [noscript] AString getItemAnnotationString(in long long aItemId,
+ in AUTF8String aName);
+
+ /**
+ * @see getPageAnnotation
+ */
+ [noscript] long getPageAnnotationInt32(in nsIURI aURI,
+ in AUTF8String aName);
+ [noscript] long getItemAnnotationInt32(in long long aItemId,
+ in AUTF8String aName);
+
+ /**
+ * @see getPageAnnotation
+ */
+ [noscript] long long getPageAnnotationInt64(in nsIURI aURI,
+ in AUTF8String aName);
+ [noscript] long long getItemAnnotationInt64(in long long aItemId,
+ in AUTF8String aName);
+
+ /**
+ * @see getPageAnnotation
+ */
+ [noscript] double getPageAnnotationDouble(in nsIURI aURI,
+ in AUTF8String aName);
+ [noscript] double getItemAnnotationDouble(in long long aItemId,
+ in AUTF8String aName);
+
+ /**
+ * Retrieves info about an existing annotation.
+ *
+ * aType will be one of TYPE_* constansts above
+ *
+ * example JS:
+ * var flags = {}, exp = {}, type = {};
+ * annotator.getAnnotationInfo(myURI, "foo", flags, exp, type);
+ * // now you can use 'exp.value' and 'flags.value'
+ */
+ void getPageAnnotationInfo(in nsIURI aURI,
+ in AUTF8String aName,
+ out int32_t aFlags,
+ out unsigned short aExpiration,
+ out unsigned short aType);
+ void getItemAnnotationInfo(in long long aItemId,
+ in AUTF8String aName,
+ out long aFlags,
+ out unsigned short aExpiration,
+ out unsigned short aType);
+
+ /**
+ * Retrieves the type of an existing annotation
+ * Use getAnnotationInfo if you need this along with the mime-type etc.
+ *
+ * @param aURI
+ * the uri on which the annotation is set
+ * @param aName
+ * the annotation name
+ * @return one of the TYPE_* constants above
+ * @throws if the annotation is not set
+ */
+ uint16_t getPageAnnotationType(in nsIURI aURI,
+ in AUTF8String aName);
+ uint16_t getItemAnnotationType(in long long aItemId,
+ in AUTF8String aName);
+
+ /**
+ * Returns a list of all URIs having a given annotation.
+ */
+ void getPagesWithAnnotation(
+ in AUTF8String name,
+ [optional] out unsigned long resultCount,
+ [retval, array, size_is(resultCount)] out nsIURI results);
+ void getItemsWithAnnotation(
+ in AUTF8String name,
+ [optional] out unsigned long resultCount,
+ [retval, array, size_is(resultCount)] out long long results);
+
+ /**
+ * Returns a list of mozIAnnotation(s), having a given annotation name.
+ *
+ * @param name
+ * The annotation to search for.
+ * @return list of mozIAnnotation objects.
+ */
+ void getAnnotationsWithName(
+ in AUTF8String name,
+ [optional] out unsigned long count,
+ [retval, array, size_is(count)] out mozIAnnotatedResult results);
+
+ /**
+ * Get the names of all annotations for this URI.
+ *
+ * example JS:
+ * var annotations = annotator.getPageAnnotations(myURI, {});
+ */
+ void getPageAnnotationNames(
+ in nsIURI aURI,
+ [optional] out unsigned long count,
+ [retval, array, size_is(count)] out nsIVariant result);
+ void getItemAnnotationNames(
+ in long long aItemId,
+ [optional] out unsigned long count,
+ [retval, array, size_is(count)] out nsIVariant result);
+
+ /**
+ * Test for annotation existence.
+ */
+ boolean pageHasAnnotation(in nsIURI aURI,
+ in AUTF8String aName);
+ boolean itemHasAnnotation(in long long aItemId,
+ in AUTF8String aName);
+
+ /**
+ * Removes a specific annotation. Succeeds even if the annotation is
+ * not found.
+ */
+ void removePageAnnotation(in nsIURI aURI,
+ in AUTF8String aName);
+ void removeItemAnnotation(in long long aItemId,
+ in AUTF8String aName,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Removes all annotations for the given page/item.
+ * We may want some other similar functions to get annotations with given
+ * flags (once we have flags defined).
+ */
+ void removePageAnnotations(in nsIURI aURI);
+ void removeItemAnnotations(in long long aItemId,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Copies all annotations from the source to the destination URI/item. If
+ * the destination already has an annotation with the same name as one on
+ * the source, it will be overwritten if aOverwriteDest is set. Otherwise,
+ * the original annotation will be preferred.
+ *
+ * All the source annotations will stay as-is. If you don't want them
+ * any more, use removePageAnnotations on that URI.
+ */
+ void copyPageAnnotations(in nsIURI aSourceURI,
+ in nsIURI aDestURI,
+ in boolean aOverwriteDest);
+ void copyItemAnnotations(in long long aSourceItemId,
+ in long long aDestItemId,
+ in boolean aOverwriteDest,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Adds an annotation observer. The annotation service will keep an owning
+ * reference to the observer object.
+ */
+ void addObserver(in nsIAnnotationObserver aObserver);
+
+
+ /**
+ * Removes an annotaton observer previously registered by addObserver.
+ */
+ void removeObserver(in nsIAnnotationObserver aObserver);
+};
+
+/**
+ * Represents a place annotated with a given annotation. If a place has
+ * multiple annotations, it can be represented by multiple
+ * mozIAnnotatedResult(s).
+ */
+[scriptable, uuid(81fd0188-db6a-492e-80b6-f6414913b396)]
+interface mozIAnnotatedResult : nsISupports
+{
+ /**
+ * The globally unique identifier of the place with this annotation.
+ *
+ * @note if itemId is valid this is the guid of the bookmark, otherwise
+ * of the page.
+ */
+ readonly attribute AUTF8String guid;
+
+ /**
+ * The URI of the place with this annotation, if available, null otherwise.
+ */
+ readonly attribute nsIURI uri;
+
+ /**
+ * The bookmark id of the place with this annotation, if available,
+ * -1 otherwise.
+ *
+ * @note if itemId is -1, it doesn't mean the page is not bookmarked, just
+ * that this annotation is relative to the page, not to the bookmark.
+ */
+ readonly attribute long long itemId;
+
+ /**
+ * Name of the annotation.
+ */
+ readonly attribute AUTF8String annotationName;
+
+ /**
+ * Value of the annotation.
+ */
+ readonly attribute nsIVariant annotationValue;
+};
diff --git a/components/places/public/nsIBrowserHistory.idl b/components/places/public/nsIBrowserHistory.idl
new file mode 100644
index 000000000..35a186293
--- /dev/null
+++ b/components/places/public/nsIBrowserHistory.idl
@@ -0,0 +1,70 @@
+/* -*- 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/. */
+
+/*
+ * browser-specific interface to global history
+ */
+
+#include "nsISupports.idl"
+interface nsIURI;
+
+[scriptable, uuid(20d31479-38de-49f4-9300-566d6e834c66)]
+interface nsIBrowserHistory : nsISupports
+{
+ /**
+ * Removes a page from global history.
+ *
+ * @note It is preferrable to use this one rather then RemovePages when
+ * removing less than 10 pages, since it won't start a full batch
+ * operation.
+ */
+ void removePage(in nsIURI aURI);
+
+ /**
+ * Removes a list of pages from global history.
+ *
+ * @param aURIs
+ * Array of URIs to be removed.
+ * @param aLength
+ * Length of the array.
+ *
+ * @note the removal happens in a batch.
+ */
+ void removePages([array, size_is(aLength)] in nsIURI aURIs,
+ in unsigned long aLength);
+
+ /**
+ * Removes all global history information about pages for a given host.
+ *
+ * @param aHost
+ * Hostname to be removed.
+ * An empty host name means local files and anything else with no
+ * hostname. You can also pass in the localized "(local files)"
+ * title given to you from a history query to remove all
+ * history information from local files.
+ * @param aEntireDomain
+ * If true, will also delete pages from sub hosts (so if
+ * passed in "microsoft.com" will delete "www.microsoft.com",
+ * "msdn.microsoft.com", etc.).
+ *
+ * @note The removal happens in a batch.
+ */
+ void removePagesFromHost(in AUTF8String aHost,
+ in boolean aEntireDomain);
+
+ /**
+ * Removes all pages for a given timeframe.
+ * Limits are included: aBeginTime <= timeframe <= aEndTime
+ *
+ * @param aBeginTime
+ * Microseconds from epoch, representing the initial time.
+ * @param aEndTime
+ * Microseconds from epoch, representing the final time.
+ *
+ * @note The removal happens in a batch.
+ */
+ void removePagesByTimeframe(in PRTime aBeginTime,
+ in PRTime aEndTime);
+};
diff --git a/components/places/public/nsIDownloadHistory.idl b/components/places/public/nsIDownloadHistory.idl
new file mode 100644
index 000000000..ca0a06549
--- /dev/null
+++ b/components/places/public/nsIDownloadHistory.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=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;
+
+[scriptable, uuid(4dcd6a12-a091-4f38-8360-022929635746)]
+interface nsIDownloadHistory : nsISupports {
+ /**
+ * Adds a download to history. This will also notify observers that the
+ * URI aSource is visited with the topic NS_LINK_VISITED_EVENT_TOPIC if
+ * aSource has not yet been visited.
+ *
+ * @param aSource
+ * The source of the download we are adding to history. This cannot be
+ * null.
+ * @param aReferrer
+ * [optional] The referrer of source URI.
+ * @param aStartTime
+ * [optional] The time the download was started. If the start time
+ * is not given, the current time is used.
+ * @param aDestination
+ * [optional] The target where the download is to be saved on the local
+ * filesystem.
+ * @throws NS_ERROR_NOT_AVAILABLE
+ * In a situation where a history implementation is not available,
+ * where 'history implementation' refers to something like
+ * nsIGlobalHistory and friends.
+ * @note This addition is not guaranteed to be synchronous, since it delegates
+ * the actual addition to the underlying history implementation. If you
+ * need to observe the completion of the addition, use the underlying
+ * history implementation's notifications system (e.g. nsINavHistoryObserver
+ * for toolkit's implementation of this interface).
+ */
+ void addDownload(in nsIURI aSource, [optional] in nsIURI aReferrer,
+ [optional] in PRTime aStartTime,
+ [optional] in nsIURI aDestination);
+
+ /**
+ * Remove all downloads from history.
+ *
+ * @note This removal is not guaranteed to be synchronous, since it delegates
+ * the actual removal to the underlying history implementation. If you
+ * need to observe the completion of the removal, use the underlying
+ * history implementation's notifications system (e.g. nsINavHistoryObserver
+ * for toolkit's implementation of this interface).
+ */
+ void removeAllDownloads();
+};
diff --git a/components/places/public/nsIFaviconService.idl b/components/places/public/nsIFaviconService.idl
new file mode 100644
index 000000000..25339d64b
--- /dev/null
+++ b/components/places/public/nsIFaviconService.idl
@@ -0,0 +1,145 @@
+/* -*- 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 "nsISupports.idl"
+
+interface nsIURI;
+
+[scriptable, uuid(e81e0b0c-b9f1-4c2e-8f3c-b809933cf73c)]
+interface nsIFaviconService : nsISupports
+{
+ // The favicon is being loaded from a private browsing window
+ const unsigned long FAVICON_LOAD_PRIVATE = 1;
+ // The favicon is being loaded from a non-private browsing window
+ const unsigned long FAVICON_LOAD_NON_PRIVATE = 2;
+
+ /**
+ * The limit in bytes of the size of favicons in memory and passed via the
+ * favicon protocol.
+ */
+ const unsigned long MAX_FAVICON_BUFFER_SIZE = 10240;
+
+ /**
+ * For a given icon URI, this will return a URI that will result in the image.
+ * In most cases, this is an annotation URI. For chrome URIs, this will do
+ * nothing but returning the input URI.
+ *
+ * No validity checking is done. If you pass an icon URI that we've never
+ * seen, you'll get back a URI that references an invalid icon. The moz-anno
+ * protocol handler's special case for "favicon" annotations will resolve
+ * invalid icons to the default icon, although without caching.
+ * For invalid chrome URIs, you'll get a broken image.
+ *
+ * @param aFaviconURI
+ * The URI of an icon in the favicon service.
+ * @return A URI that will give you the icon image. This is NOT the URI of
+ * the icon as set on the page, but a URI that will give you the
+ * data out of the favicon service. For a normal page with a
+ * favicon we've stored, this will be an annotation URI which will
+ * then cause the corresponding favicon data to be loaded async from
+ * this service. For pages where we don't have a favicon, this will
+ * be a chrome URI of the default icon. For chrome URIs, the
+ * output will be the same as the input.
+ */
+ nsIURI getFaviconLinkForIcon(in nsIURI aFaviconURI);
+
+ /**
+ * Expire all known favicons from the database.
+ *
+ * @note This is an async method.
+ * On successful completion a "places-favicons-expired" notification is
+ * dispatched through observer's service.
+ */
+ void expireAllFavicons();
+
+ /**
+ * Adds a given favicon's URI to the failed favicon cache.
+ *
+ * The lifespan of the favicon cache is up to the caching system. This cache
+ * will also be written when setAndLoadFaviconForPage hits an error while
+ * fetching an icon.
+ *
+ * @param aFaviconURI
+ * The URI of an icon in the favicon service.
+ */
+ void addFailedFavicon(in nsIURI aFaviconURI);
+
+ /**
+ * Removes the given favicon from the failed favicon cache. If the icon is
+ * not in the cache, it will silently succeed.
+ *
+ * @param aFaviconURI
+ * The URI of an icon in the favicon service.
+ */
+ void removeFailedFavicon(in nsIURI aFaviconURI);
+
+ /**
+ * Checks to see if a favicon is in the failed favicon cache.
+ * A positive return value means the icon is in the failed cache and you
+ * probably shouldn't try to load it. A false return value means that it's
+ * worth trying to load it.
+ * This allows you to avoid trying to load "foo.com/favicon.ico" for every
+ * page on a site that doesn't have a favicon.
+ *
+ * @param aFaviconURI
+ * The URI of an icon in the favicon service.
+ */
+ boolean isFailedFavicon(in nsIURI aFaviconURI);
+
+ /**
+ * The default favicon URI
+ */
+ readonly attribute nsIURI defaultFavicon;
+};
+
+[scriptable, function, uuid(c85e5c82-b70f-4621-9528-beb2aa47fb44)]
+interface nsIFaviconDataCallback : nsISupports
+{
+ /**
+ * Called when the required favicon's information is available.
+ *
+ * It's up to the invoking method to state if the callback is always invoked,
+ * or called on success only. Check the method documentation to ensure that.
+ *
+ * The caller will receive the most information we can gather on the icon,
+ * but it's not guaranteed that all of them will be set. For some method
+ * we could not know the favicon's data (it could just be too expensive to
+ * get it, or the method does not require we actually have any data).
+ * It's up to the caller to check aDataLen > 0 before using any data-related
+ * information like mime-type or data itself.
+ *
+ * @param aFaviconURI
+ * Receives the "favicon URI" (not the "favicon link URI") associated
+ * to the requested page. This can be null if there is no associated
+ * favicon URI, or the callback is notifying a failure.
+ * @param aDataLen
+ * Size of the icon data in bytes. Notice that a value of 0 does not
+ * necessarily mean that we don't have an icon.
+ * @param aData
+ * Icon data, or an empty array if aDataLen is 0.
+ * @param aMimeType
+ * Mime type of the icon, or an empty string if aDataLen is 0.
+ *
+ * @note If you want to open a network channel to access the favicon, it's
+ * recommended that you call the getFaviconLinkForIcon method to convert
+ * the "favicon URI" into a "favicon link URI".
+ */
+ void onComplete(in nsIURI aFaviconURI,
+ in unsigned long aDataLen,
+ [const,array,size_is(aDataLen)] in octet aData,
+ in AUTF8String aMimeType);
+};
+
+%{C++
+
+/**
+ * Notification sent when all favicons are expired.
+ */
+#define NS_PLACES_FAVICONS_EXPIRED_TOPIC_ID "places-favicons-expired"
+
+#define FAVICON_DEFAULT_URL "chrome://mozapps/skin/places/defaultFavicon.png"
+#define FAVICON_ERRORPAGE_URL "chrome://global/skin/icons/warning-16.png"
+
+%}
diff --git a/components/places/public/nsINavBookmarksService.idl b/components/places/public/nsINavBookmarksService.idl
new file mode 100644
index 000000000..e9e49a4f4
--- /dev/null
+++ b/components/places/public/nsINavBookmarksService.idl
@@ -0,0 +1,697 @@
+/* -*- Mode: IDL; 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 nsIFile;
+interface nsIURI;
+interface nsITransaction;
+interface nsINavHistoryBatchCallback;
+
+/**
+ * Observer for bookmarks changes.
+ */
+[scriptable, uuid(c06b4e7d-15b1-4d4f-bdf7-147d2be9084a)]
+interface nsINavBookmarkObserver : nsISupports
+{
+ /*
+ * This observer should not be called for items that are tags.
+ */
+ readonly attribute boolean skipTags;
+
+ /*
+ * This observer should not be called for descendants when the parent is removed.
+ * For example when revmoing a folder containing bookmarks.
+ */
+ readonly attribute boolean skipDescendantsOnItemRemoval;
+
+ /**
+ * Notifies that a batch transaction has started.
+ * Other notifications will be sent during the batch, but the observer is
+ * guaranteed that onEndUpdateBatch() will be called at its completion.
+ * During a batch the observer should do its best to reduce the work done to
+ * handle notifications, since multiple changes are going to happen in a short
+ * timeframe.
+ */
+ void onBeginUpdateBatch();
+
+ /**
+ * Notifies that a batch transaction has ended.
+ */
+ void onEndUpdateBatch();
+
+ /**
+ * Notifies that an item (any type) was added. Called after the actual
+ * addition took place.
+ * When a new item is created, all the items following it in the same folder
+ * will have their index shifted down, but no additional notifications will
+ * be sent.
+ *
+ * @param aItemId
+ * The id of the item that was added.
+ * @param aParentId
+ * The id of the folder to which the item was added.
+ * @param aIndex
+ * The item's index in the folder.
+ * @param aItemType
+ * The type of the added item (see TYPE_* constants below).
+ * @param aURI
+ * The URI of the added item if it was TYPE_BOOKMARK, null otherwise.
+ * @param aTitle
+ * The title of the added item.
+ * @param aDateAdded
+ * The stored date added value, in microseconds from the epoch.
+ * @param aGuid
+ * The unique ID associated with the item.
+ * @param aParentGuid
+ * The unique ID associated with the item's parent.
+ * @param aSource
+ * A change source constant from nsINavBookmarksService::SOURCE_*,
+ * passed to the method that notifies the observer.
+ */
+ void onItemAdded(in long long aItemId,
+ in long long aParentId,
+ in long aIndex,
+ in unsigned short aItemType,
+ in nsIURI aURI,
+ in AUTF8String aTitle,
+ in PRTime aDateAdded,
+ in ACString aGuid,
+ in ACString aParentGuid,
+ in unsigned short aSource);
+
+ /**
+ * Notifies that an item was removed. Called after the actual remove took
+ * place.
+ * When an item is removed, all the items following it in the same folder
+ * will have their index shifted down, but no additional notifications will
+ * be sent.
+ *
+ * @param aItemId
+ * The id of the item that was removed.
+ * @param aParentId
+ * The id of the folder from which the item was removed.
+ * @param aIndex
+ * The bookmark's index in the folder.
+ * @param aItemType
+ * The type of the item to be removed (see TYPE_* constants below).
+ * @param aURI
+ * The URI of the added item if it was TYPE_BOOKMARK, null otherwise.
+ * @param aGuid
+ * The unique ID associated with the item.
+ * @param aParentGuid
+ * The unique ID associated with the item's parent.
+ * @param aSource
+ * A change source constant from nsINavBookmarksService::SOURCE_*,
+ * passed to the method that notifies the observer.
+ */
+ void onItemRemoved(in long long aItemId,
+ in long long aParentId,
+ in long aIndex,
+ in unsigned short aItemType,
+ in nsIURI aURI,
+ in ACString aGuid,
+ in ACString aParentGuid,
+ in unsigned short aSource);
+
+ /**
+ * Notifies that an item's information has changed. This will be called
+ * whenever any attributes like "title" are changed.
+ *
+ * @param aItemId
+ * The id of the item that was changed.
+ * @param aProperty
+ * The property which changed. Can be null for the removal of all of
+ * the annotations, in this case aIsAnnotationProperty is true.
+ * @param aIsAnnotationProperty
+ * Whether or not aProperty is the name of an annotation. If true
+ * aNewValue is always an empty string.
+ * @param aNewValue
+ * For certain properties, this is set to the new value of the
+ * property (see the list below).
+ * @param aLastModified
+ * The updated last-modified value.
+ * @param aItemType
+ * The type of the item to be removed (see TYPE_* constants below).
+ * @param aParentId
+ * The id of the folder containing the item.
+ * @param aGuid
+ * The unique ID associated with the item.
+ * @param aParentGuid
+ * The unique ID associated with the item's parent.
+ * @param aOldValue
+ * For certain properties, this is set to the new value of the
+ * property (see the list below).
+ * @param aSource
+ * A change source constant from nsINavBookmarksService::SOURCE_*,
+ * passed to the method that notifies the observer.
+ *
+ * @note List of values that may be associated with properties:
+ * aProperty | aNewValue
+ * =====================================================================
+ * cleartime | Empty string (all visits to this item were removed).
+ * title | The new title.
+ * favicon | The "moz-anno" URL of the new favicon.
+ * uri | new URL.
+ * tags | Empty string (tags for this item changed)
+ * dateAdded | PRTime (as string) when the item was first added.
+ * lastModified | PRTime (as string) when the item was last modified.
+ *
+ * aProperty | aOldValue
+ * =====================================================================
+ * cleartime | Empty string (currently unused).
+ * title | Empty string (currently unused).
+ * favicon | Empty string (currently unused).
+ * uri | old URL.
+ * tags | Empty string (currently unused).
+ * dateAdded | Empty string (currently unused).
+ * lastModified | Empty string (currently unused).
+ */
+ void onItemChanged(in long long aItemId,
+ in ACString aProperty,
+ in boolean aIsAnnotationProperty,
+ in AUTF8String aNewValue,
+ in PRTime aLastModified,
+ in unsigned short aItemType,
+ in long long aParentId,
+ in ACString aGuid,
+ in ACString aParentGuid,
+ in AUTF8String aOldValue,
+ in unsigned short aSource);
+
+ /**
+ * Notifies that the item was visited. Can be invoked only for TYPE_BOOKMARK
+ * items.
+ *
+ * @param aItemId
+ * The id of the bookmark that was visited.
+ * @param aVisitId
+ * The id of the visit.
+ * @param aTime
+ * The time of the visit.
+ * @param aTransitionType
+ * The transition for the visit. See nsINavHistoryService::TRANSITION_*
+ * constants for a list of possible values.
+ * @param aURI
+ * The nsIURI for this bookmark.
+ * @param aParentId
+ * The id of the folder containing the item.
+ * @param aGuid
+ * The unique ID associated with the item.
+ * @param aParentGuid
+ * The unique ID associated with the item's parent.
+ *
+ * @see onItemChanged with property = "cleartime" for when all visits to an
+ * item are removed.
+ *
+ * @note The reported time is the time of the visit that was added, which may
+ * be well in the past since the visit time can be specified. This
+ * means that the visit the observer is told about may not be the most
+ * recent visit for that page.
+ */
+ void onItemVisited(in long long aItemId,
+ in long long aVisitId,
+ in PRTime aTime,
+ in unsigned long aTransitionType,
+ in nsIURI aURI,
+ in long long aParentId,
+ in ACString aGuid,
+ in ACString aParentGuid);
+
+ /**
+ * Notifies that an item has been moved.
+ *
+ * @param aItemId
+ * The id of the item that was moved.
+ * @param aOldParentId
+ * The id of the old parent.
+ * @param aOldIndex
+ * The old index inside the old parent.
+ * @param aNewParentId
+ * The id of the new parent.
+ * @param aNewIndex
+ * The index inside the new parent.
+ * @param aItemType
+ * The type of the item to be removed (see TYPE_* constants below).
+ * @param aGuid
+ * The unique ID associated with the item.
+ * @param aOldParentGuid
+ * The unique ID associated with the old item's parent.
+ * @param aNewParentGuid
+ * The unique ID associated with the new item's parent.
+ * @param aSource
+ * A change source constant from nsINavBookmarksService::SOURCE_*,
+ * passed to the method that notifies the observer.
+ */
+ void onItemMoved(in long long aItemId,
+ in long long aOldParentId,
+ in long aOldIndex,
+ in long long aNewParentId,
+ in long aNewIndex,
+ in unsigned short aItemType,
+ in ACString aGuid,
+ in ACString aOldParentGuid,
+ in ACString aNewParentGuid,
+ in unsigned short aSource);
+};
+
+/**
+ * The BookmarksService interface provides methods for managing bookmarked
+ * history items. Bookmarks consist of a set of user-customizable
+ * folders. A URI in history can be contained in one or more such folders.
+ */
+
+[scriptable, uuid(24533891-afa6-4663-b72d-3143d03f1b04)]
+interface nsINavBookmarksService : nsISupports
+{
+ /**
+ * The item ID of the Places root.
+ */
+ readonly attribute long long placesRoot;
+
+ /**
+ * The item ID of the bookmarks menu folder.
+ */
+ readonly attribute long long bookmarksMenuFolder;
+
+ /**
+ * The item ID of the top-level folder that contain the tag "folders".
+ */
+ readonly attribute long long tagsFolder;
+
+ /**
+ * The item ID of the unfiled-bookmarks folder.
+ */
+ readonly attribute long long unfiledBookmarksFolder;
+
+ /**
+ * The item ID of the personal toolbar folder.
+ */
+ readonly attribute long long toolbarFolder;
+
+ /**
+ * The item ID of the mobile bookmarks folder.
+ */
+ readonly attribute long long mobileFolder;
+
+ /**
+ * This value should be used for APIs that allow passing in an index
+ * where an index is not known, or not required to be specified.
+ * e.g.: When appending an item to a folder.
+ */
+ const short DEFAULT_INDEX = -1;
+
+ const unsigned short TYPE_BOOKMARK = 1;
+ const unsigned short TYPE_FOLDER = 2;
+ const unsigned short TYPE_SEPARATOR = 3;
+ // Dynamic containers are deprecated and unsupported.
+ // This const exists just to avoid reusing the value.
+ const unsigned short TYPE_DYNAMIC_CONTAINER = 4;
+
+ // Change source constants. These are used to distinguish changes made by
+ // Sync and bookmarks import from other Places consumers, though they can
+ // be extended to support other callers. Sources are passed as optional
+ // parameters to methods used by Sync, and forwarded to observers.
+ const unsigned short SOURCE_DEFAULT = 0;
+ const unsigned short SOURCE_SYNC = 1;
+ const unsigned short SOURCE_IMPORT = 2;
+ const unsigned short SOURCE_IMPORT_REPLACE = 3;
+
+ /**
+ * Inserts a child bookmark into the given folder.
+ *
+ * @param aParentId
+ * The id of the parent folder
+ * @param aURI
+ * The URI to insert
+ * @param aIndex
+ * The index to insert at, or DEFAULT_INDEX to append
+ * @param aTitle
+ * The title for the new bookmark
+ * @param [optional] aGuid
+ * The GUID to be set for the new item. If not set, a new GUID is
+ * generated. Unless you've a very sound reason, such as an undo
+ * manager implementation, do not pass this argument.
+ * @param [optional] aSource
+ * The change source. This is forwarded to all bookmark observers,
+ * allowing them to distinguish between insertions from different
+ * callers. Defaults to SOURCE_DEFAULT if omitted.
+ * @return The ID of the newly-created bookmark.
+ *
+ * @note aTitle will be truncated to TITLE_LENGTH_MAX and
+ * aURI will be truncated to URI_LENGTH_MAX.
+ * @throws if aGuid is malformed.
+ */
+ long long insertBookmark(in long long aParentId, in nsIURI aURI,
+ in long aIndex, in AUTF8String aTitle,
+ [optional] in ACString aGuid,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Removes a child item. Used to delete a bookmark or separator.
+ * @param aItemId
+ * The child item to remove
+ * @param [optional] aSource
+ * The change source, forwarded to all bookmark observers. Defaults
+ * to SOURCE_DEFAULT.
+ */
+ void removeItem(in long long aItemId, [optional] in unsigned short aSource);
+
+ /**
+ * Creates a new child folder and inserts it under the given parent.
+ * @param aParentFolder
+ * The id of the parent folder
+ * @param aName
+ * The name of the new folder
+ * @param aIndex
+ * The index to insert at, or DEFAULT_INDEX to append
+ * @param [optional] aGuid
+ * The GUID to be set for the new item. If not set, a new GUID is
+ * generated. Unless you've a very sound reason, such as an undo
+ * manager implementation, do not pass this argument.
+ * @param [optional] aSource
+ * The change source, forwarded to all bookmark observers. Defaults
+ * to SOURCE_DEFAULT.
+ * @return The ID of the newly-inserted folder.
+ * @throws if aGuid is malformed.
+ */
+ long long createFolder(in long long aParentFolder, in AUTF8String name,
+ in long index,
+ [optional] in ACString aGuid,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Gets an undo-able transaction for removing a folder from the bookmarks
+ * tree.
+ * @param aItemId
+ * The id of the folder to remove.
+ * @param [optional] aSource
+ * The change source, forwarded to all bookmark observers. Defaults
+ * to SOURCE_DEFAULT.
+ * @return An object implementing nsITransaction that can be used to undo
+ * or redo the action.
+ *
+ * This method exists because complex delete->undo operations rely on
+ * recreated folders to have the same ID they had before they were deleted,
+ * so that any other items deleted in different transactions can be
+ * re-inserted correctly. This provides a safe encapsulation of this
+ * functionality without exposing the ability to recreate folders with
+ * specific IDs (potentially dangerous if abused by other code!) in the
+ * public API.
+ */
+ nsITransaction getRemoveFolderTransaction(in long long aItemId,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Convenience function for container services. Removes
+ * all children of the given folder.
+ * @param aItemId
+ * The id of the folder to remove children from.
+ * @param [optional] aSource
+ * The change source, forwarded to all bookmark observers. Defaults
+ * to SOURCE_DEFAULT.
+ */
+ void removeFolderChildren(in long long aItemId,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Moves an item to a different container, preserving its contents.
+ * @param aItemId
+ * The id of the item to move
+ * @param aNewParentId
+ * The id of the new parent
+ * @param aIndex
+ * The index under aNewParent, or DEFAULT_INDEX to append
+ * @param [optional] aSource
+ * The change source, forwarded to all bookmark observers. Defaults
+ * to SOURCE_DEFAULT.
+ *
+ * NOTE: When moving down in the same container we take into account the
+ * removal of the original item. If you want to move from index X to
+ * index Y > X you must use moveItem(id, folder, Y + 1)
+ */
+ void moveItem(in long long aItemId,
+ in long long aNewParentId,
+ in long aIndex,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Inserts a bookmark separator into the given folder at the given index.
+ * The separator can be removed using removeChildAt().
+ * @param aParentId
+ * The id of the parent folder
+ * @param aIndex
+ * The separator's index under folder, or DEFAULT_INDEX to append
+ * @param [optional] aGuid
+ * The GUID to be set for the new item. If not set, a new GUID is
+ * generated. Unless you've a very sound reason, such as an undo
+ * manager implementation, do not pass this argument.
+ * @param [optional] aSource
+ * The change source, forwarded to all bookmark observers. Defaults
+ * to SOURCE_DEFAULT.
+ * @return The ID of the new separator.
+ * @throws if aGuid is malformed.
+ */
+ long long insertSeparator(in long long aParentId, in long aIndex,
+ [optional] in ACString aGuid,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Get the itemId given the containing folder and the index.
+ * @param aParentId
+ * The id of the diret parent folder of the item
+ * @param aIndex
+ * The index of the item within the parent folder.
+ * Pass DEFAULT_INDEX for the last item.
+ * @return The ID of the found item, -1 if the item does not exists.
+ */
+ long long getIdForItemAt(in long long aParentId, in long aIndex);
+
+ /**
+ * Set the title for an item.
+ * @param aItemId
+ * The id of the item whose title should be updated.
+ * @param aTitle
+ * The new title for the bookmark.
+ * @param [optional] aSource
+ * The change source, forwarded to all bookmark observers. Defaults
+ * to SOURCE_DEFAULT.
+ *
+ * @note aTitle will be truncated to TITLE_LENGTH_MAX.
+ */
+ void setItemTitle(in long long aItemId, in AUTF8String aTitle,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Get the title for an item.
+ *
+ * If no item title is available it will return a void string (null in JS).
+ *
+ * @param aItemId
+ * The id of the item whose title should be retrieved
+ * @return The title of the item.
+ */
+ AUTF8String getItemTitle(in long long aItemId);
+
+ /**
+ * Set the date added time for an item.
+ *
+ * @param aItemId
+ * the id of the item whose date added time should be updated.
+ * @param aDateAdded
+ * the new date added value in microseconds. Note that it is rounded
+ * down to milliseconds precision.
+ * @param [optional] aSource
+ * The change source, forwarded to all bookmark observers. Defaults
+ * to SOURCE_DEFAULT.
+ */
+ void setItemDateAdded(in long long aItemId,
+ in PRTime aDateAdded,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Get the date added time for an item.
+ *
+ * @param aItemId
+ * the id of the item whose date added time should be retrieved.
+ *
+ * @return the date added value in microseconds.
+ */
+ PRTime getItemDateAdded(in long long aItemId);
+
+ /**
+ * Set the last modified time for an item.
+ *
+ * @param aItemId
+ * the id of the item whose last modified time should be updated.
+ * @param aLastModified
+ * the new last modified value in microseconds. Note that it is
+ * rounded down to milliseconds precision.
+ * @param [optional] aSource
+ * The change source, forwarded to all bookmark observers. Defaults
+ * to SOURCE_DEFAULT.
+ *
+ * @note This is the only method that will send an itemChanged notification
+ * for the property. lastModified will still be updated in
+ * any other method that changes an item property, but we will send
+ * the corresponding itemChanged notification instead.
+ */
+ void setItemLastModified(in long long aItemId,
+ in PRTime aLastModified,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Get the last modified time for an item.
+ *
+ * @param aItemId
+ * the id of the item whose last modified time should be retrieved.
+ *
+ * @return the date added value in microseconds.
+ *
+ * @note When an item is added lastModified is set to the same value as
+ * dateAdded.
+ */
+ PRTime getItemLastModified(in long long aItemId);
+
+ /**
+ * Get the URI for a bookmark item.
+ */
+ nsIURI getBookmarkURI(in long long aItemId);
+
+ /**
+ * Get the index for an item.
+ */
+ long getItemIndex(in long long aItemId);
+
+ /**
+ * Changes the index for a item. This method does not change the indices of
+ * any other items in the same folder, so ensure that the new index does not
+ * already exist, or change the index of other items accordingly, otherwise
+ * the indices will become corrupted.
+ *
+ * WARNING: This is API is intended for scenarios such as folder sorting,
+ * where the caller manages the indices of *all* items in the folder.
+ * You must always ensure each index is unique after a reordering.
+ *
+ * @param aItemId The id of the item to modify
+ * @param aNewIndex The new index
+ * @param aSource The optional change source, forwarded to all bookmark
+ * observers. Defaults to SOURCE_DEFAULT.
+ *
+ * @throws If aNewIndex is out of bounds.
+ */
+ void setItemIndex(in long long aItemId,
+ in long aNewIndex,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Get an item's type (bookmark, separator, folder).
+ * The type is one of the TYPE_* constants defined above.
+ */
+ unsigned short getItemType(in long long aItemId);
+
+ /**
+ * Returns true if the given URI is in any bookmark folder. If you want the
+ * results to be redirect-aware, use getBookmarkedURIFor()
+ */
+ boolean isBookmarked(in nsIURI aURI);
+
+ /**
+ * Used to see if the given URI is bookmarked, or any page that redirected to
+ * it is bookmarked. For example, if I bookmark "mozilla.org" by manually
+ * typing it in, and follow the bookmark, I will get redirected to
+ * "www.mozilla.org". Logically, this new page is also bookmarked. This
+ * function, if given "www.mozilla.org", will return the URI of the bookmark,
+ * in this case "mozilla.org".
+ *
+ * If there is no bookmarked page found, it will return NULL.
+ *
+ * @note The function will only return bookmarks in the first 2 levels of
+ * redirection (1 -> 2 -> aURI).
+ */
+ nsIURI getBookmarkedURIFor(in nsIURI aURI);
+
+ /**
+ * Change the bookmarked URI for a bookmark.
+ * This changes which "place" the bookmark points at,
+ * which means all annotations, etc are carried along.
+ */
+ void changeBookmarkURI(in long long aItemId,
+ in nsIURI aNewURI,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Get the parent folder's id for an item.
+ */
+ long long getFolderIdForItem(in long long aItemId);
+
+ /**
+ * Returns the list of bookmark ids that contain the given URI.
+ */
+ void getBookmarkIdsForURI(in nsIURI aURI, [optional] out unsigned long count,
+ [array, retval, size_is(count)] out long long bookmarks);
+
+ /**
+ * Associates the given keyword with the given bookmark.
+ *
+ * Use an empty keyword to clear the keyword associated with the URI.
+ * In both of these cases, succeeds but does nothing if the URL/keyword is not found.
+ *
+ * @deprecated Use PlacesUtils.keywords.insert() API instead.
+ */
+ void setKeywordForBookmark(in long long aItemId,
+ in AString aKeyword,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Retrieves the keyword for the given bookmark. Will be void string
+ * (null in JS) if no such keyword is found.
+ *
+ * @deprecated Use PlacesUtils.keywords.fetch() API instead.
+ */
+ AString getKeywordForBookmark(in long long aItemId);
+
+ /**
+ * Returns the URI associated with the given keyword. Empty if no such
+ * keyword is found.
+ *
+ * @deprecated Use PlacesUtils.keywords.fetch() API instead.
+ */
+ nsIURI getURIForKeyword(in AString keyword);
+
+ /**
+ * Adds a bookmark observer. If ownsWeak is false, the bookmark service will
+ * keep an owning reference to the observer. If ownsWeak is true, then
+ * aObserver must implement nsISupportsWeakReference, and the bookmark
+ * service will keep a weak reference to the observer.
+ */
+ void addObserver(in nsINavBookmarkObserver observer, in boolean ownsWeak);
+
+ /**
+ * Removes a bookmark observer.
+ */
+ void removeObserver(in nsINavBookmarkObserver observer);
+
+ /**
+ * Gets an array of registered nsINavBookmarkObserver objects.
+ */
+ void getObservers([optional] out unsigned long count,
+ [retval, array, size_is(count)] out nsINavBookmarkObserver observers);
+
+ /**
+ * Runs the passed callback inside of a database transaction.
+ * Use this when a lot of things are about to change, for example
+ * adding or deleting a large number of bookmark items. Calls can
+ * be nested. Observers are notified when batches begin and end, via
+ * nsINavBookmarkObserver.onBeginUpdateBatch/onEndUpdateBatch.
+ *
+ * @param aCallback
+ * nsINavHistoryBatchCallback interface to call.
+ * @param aUserData
+ * Opaque parameter passed to nsINavBookmarksBatchCallback
+ */
+ void runInBatchMode(in nsINavHistoryBatchCallback aCallback,
+ in nsISupports aUserData);
+};
diff --git a/components/places/public/nsINavHistoryService.idl b/components/places/public/nsINavHistoryService.idl
new file mode 100644
index 000000000..3fd851870
--- /dev/null
+++ b/components/places/public/nsINavHistoryService.idl
@@ -0,0 +1,1451 @@
+/* -*- Mode: IDL; 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/. */
+
+/**
+ * Using Places services after quit-application is not reliable, so make
+ * sure to do any shutdown work on quit-application, or history
+ * synchronization could fail, losing latest changes.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+interface nsIURI;
+interface nsIVariant;
+interface nsIFile;
+
+interface nsINavHistoryContainerResultNode;
+interface nsINavHistoryQueryResultNode;
+interface nsINavHistoryQuery;
+interface nsINavHistoryQueryOptions;
+interface nsINavHistoryResult;
+interface nsINavHistoryBatchCallback;
+
+[scriptable, uuid(91d104bb-17ef-404b-9f9a-d9ed8de6824c)]
+interface nsINavHistoryResultNode : nsISupports
+{
+ /**
+ * Indentifies the parent result node in the result set. This is null for
+ * top level nodes.
+ */
+ readonly attribute nsINavHistoryContainerResultNode parent;
+
+ /**
+ * The history-result to which this node belongs.
+ */
+ readonly attribute nsINavHistoryResult parentResult;
+
+ /**
+ * URI of the resource in question. For visits and URLs, this is the URL of
+ * the page. For folders and queries, this is the place: URI of the
+ * corresponding folder or query. This may be empty for other types of
+ * objects like host containers.
+ */
+ readonly attribute AUTF8String uri;
+
+ /**
+ * Identifies the type of this node. This node can then be QI-ed to the
+ * corresponding specialized result node interface.
+ */
+ const unsigned long RESULT_TYPE_URI = 0; // nsINavHistoryResultNode
+
+ // Visit nodes are deprecated and unsupported.
+ // This line exists just to avoid reusing the value:
+ // const unsigned long RESULT_TYPE_VISIT = 1;
+
+ // Full visit nodes are deprecated and unsupported.
+ // This line exists just to avoid reusing the value:
+ // const unsigned long RESULT_TYPE_FULL_VISIT = 2;
+
+ // Dynamic containers are deprecated and unsupported.
+ // This const exists just to avoid reusing the value:
+ // const unsigned long RESULT_TYPE_DYNAMIC_CONTAINER = 4; // nsINavHistoryContainerResultNode
+
+ const unsigned long RESULT_TYPE_QUERY = 5; // nsINavHistoryQueryResultNode
+ const unsigned long RESULT_TYPE_FOLDER = 6; // nsINavHistoryQueryResultNode
+ const unsigned long RESULT_TYPE_SEPARATOR = 7; // nsINavHistoryResultNode
+ const unsigned long RESULT_TYPE_FOLDER_SHORTCUT = 9; // nsINavHistoryQueryResultNode
+ readonly attribute unsigned long type;
+
+ /**
+ * Title of the web page, or of the node's query (day, host, folder, etc)
+ */
+ readonly attribute AUTF8String title;
+
+ /**
+ * Total number of times the URI has ever been accessed. For hosts, this
+ * is the total of the children under it, NOT the total times the host has
+ * been accessed (this would require an additional query, so is not given
+ * by default when most of the time it is never needed).
+ */
+ readonly attribute unsigned long accessCount;
+
+ /**
+ * This is the time the user accessed the page.
+ *
+ * If this is a visit, it is the exact time that the page visit occurred.
+ *
+ * If this is a URI, it is the most recent time that the URI was visited.
+ * Even if you ask for all URIs for a given date range long ago, this might
+ * contain today's date if the URI was visited today.
+ *
+ * For hosts, or other node types with children, this is the most recent
+ * access time for any of the children.
+ *
+ * For days queries this is the respective endTime - a maximum possible
+ * visit time to fit in the day range.
+ */
+ readonly attribute PRTime time;
+
+ /**
+ * This URI can be used as an image source URI and will give you the favicon
+ * for the page. It is *not* the URI of the favicon, but rather something
+ * that will resolve to the actual image.
+ *
+ * In most cases, this is an annotation URI that will query the favicon
+ * service. If the entry has no favicon, this is the chrome URI of the
+ * default favicon. If the favicon originally lived in chrome, this will
+ * be the original chrome URI of the icon.
+ */
+ readonly attribute AUTF8String icon;
+
+ /**
+ * This is the number of levels between this node and the top of the
+ * hierarchy. The members of result.children have indentLevel = 0, their
+ * children have indentLevel = 1, etc. The indent level of the root node is
+ * set to -1.
+ */
+ readonly attribute long indentLevel;
+
+ /**
+ * When this item is in a bookmark folder (parent is of type folder), this is
+ * the index into that folder of this node. These indices start at 0 and
+ * increase in the order that they appear in the bookmark folder. For items
+ * that are not in a bookmark folder, this value is -1.
+ */
+ readonly attribute long bookmarkIndex;
+
+ /**
+ * If the node is an item (bookmark, folder or a separator) this value is the
+ * row ID of that bookmark in the database. For other nodes, this value is
+ * set to -1.
+ */
+ readonly attribute long long itemId;
+
+ /**
+ * If the node is an item (bookmark, folder or a separator) this value is the
+ * time that the item was created. For other nodes, this value is 0.
+ */
+ readonly attribute PRTime dateAdded;
+
+ /**
+ * If the node is an item (bookmark, folder or a separator) this value is the
+ * time that the item was last modified. For other nodes, this value is 0.
+ *
+ * @note When an item is added lastModified is set to the same value as
+ * dateAdded.
+ */
+ readonly attribute PRTime lastModified;
+
+ /**
+ * For uri nodes, this is a sorted list of the tags, delimited with commans,
+ * for the uri represented by this node. Otherwise this is an empty string.
+ */
+ readonly attribute AString tags;
+
+ /**
+ * The unique ID associated with the page. It my return an empty string
+ * if the result node is a non-URI node.
+ */
+ readonly attribute ACString pageGuid;
+
+ /**
+ * The unique ID associated with the bookmark. It returns an empty string
+ * if the result node is not associated with a bookmark, a folder or a
+ * separator.
+ */
+ readonly attribute ACString bookmarkGuid;
+
+ /**
+ * The unique ID associated with the history visit. For node types other than
+ * history visit nodes, this value is -1.
+ */
+ readonly attribute long long visitId;
+
+ /**
+ * The unique ID associated with visit node which was the referrer of this
+ * history visit. For node types other than history visit nodes, or visits
+ * without any known referrer, this value is -1.
+ */
+ readonly attribute long long fromVisitId;
+
+ /**
+ * The transition type associated with this visit. For node types other than
+ * history visit nodes, this value is 0.
+ */
+ readonly attribute unsigned long visitType;
+};
+
+
+/**
+ * Base class for container results. This includes all types of groupings.
+ * Bookmark folders and places queries will be QueryResultNodes which extends
+ * these items.
+ */
+[scriptable, uuid(3E9CC95F-0D93-45F1-894F-908EEB9866D7)]
+interface nsINavHistoryContainerResultNode : nsINavHistoryResultNode
+{
+
+ /**
+ * Set this to allow descent into the container. When closed, attempting
+ * to call getChildren or childCount will result in an error. You should
+ * set this to false when you are done reading.
+ *
+ * For HOST and DAY groupings, doing this is free since the children have
+ * been precomputed. For queries and bookmark folders, being open means they
+ * will keep themselves up-to-date by listening for updates and re-querying
+ * as needed.
+ */
+ attribute boolean containerOpen;
+
+ /**
+ * Indicates whether the container is closed, loading, or opened. Loading
+ * implies that the container has been opened asynchronously and has not yet
+ * fully opened.
+ */
+ readonly attribute unsigned short state;
+ const unsigned short STATE_CLOSED = 0;
+ const unsigned short STATE_LOADING = 1;
+ const unsigned short STATE_OPENED = 2;
+
+ /**
+ * This indicates whether this node "may" have children, and can be used
+ * when the container is open or closed. When the container is closed, it
+ * will give you an exact answer if the node can easily be populated (for
+ * example, a bookmark folder). If not (for example, a complex history query),
+ * it will return true. When the container is open, it will always be
+ * accurate. It is intended to be used to see if we should draw the "+" next
+ * to a tree item.
+ */
+ readonly attribute boolean hasChildren;
+
+ /**
+ * This gives you the children of the nodes. It is preferrable to use this
+ * interface over the array one, since it avoids creating an nsIArray object
+ * and the interface is already the correct type.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if containerOpen is false.
+ */
+ readonly attribute unsigned long childCount;
+ nsINavHistoryResultNode getChild(in unsigned long aIndex);
+
+ /**
+ * Get the index of a direct child in this container.
+ *
+ * @param aNode
+ * a result node.
+ *
+ * @return aNode's index in this container.
+ * @throws NS_ERROR_NOT_AVAILABLE if containerOpen is false.
+ * @throws NS_ERROR_INVALID_ARG if aNode isn't a direct child of this
+ * container.
+ */
+ unsigned long getChildIndex(in nsINavHistoryResultNode aNode);
+
+ /**
+ * Look for a node in the container by some of its details. Does not search
+ * closed containers.
+ *
+ * @param aURI
+ * the node's uri attribute value
+ * @param aTime
+ * the node's time attribute value.
+ * @param aItemId
+ * the node's itemId attribute value.
+ * @param aRecursive
+ * whether or not to search recursively.
+ *
+ * @throws NS_ERROR_NOT_AVAILABLE if this container is closed.
+ * @return a result node that matches the given details if any, null
+ * otherwise.
+ */
+ nsINavHistoryResultNode findNodeByDetails(in AUTF8String aURIString,
+ in PRTime aTime,
+ in long long aItemId,
+ in boolean aRecursive);
+};
+
+
+/**
+ * Used for places queries and as a base for bookmark folders.
+ *
+ * Note that if you request places to *not* be expanded in the options that
+ * generated this node, this item will report it has no children and never try
+ * to populate itself.
+ */
+[scriptable, uuid(62817759-4FEE-44A3-B58C-3E2F5AFC9D0A)]
+interface nsINavHistoryQueryResultNode : nsINavHistoryContainerResultNode
+{
+ /**
+ * Get the queries which build this node's children.
+ * Only valid for RESULT_TYPE_QUERY nodes.
+ */
+ void getQueries([optional] out unsigned long queryCount,
+ [retval,array,size_is(queryCount)] out nsINavHistoryQuery queries);
+
+ /**
+ * Get the options which group this node's children.
+ * Only valid for RESULT_TYPE_QUERY nodes.
+ */
+ readonly attribute nsINavHistoryQueryOptions queryOptions;
+
+ /**
+ * For both simple folder queries and folder shortcut queries, this is set to
+ * the concrete itemId of the folder (i.e. for folder shortcuts it's the
+ * target folder id). Otherwise, this is set to -1.
+ */
+ readonly attribute long long folderItemId;
+
+ /**
+ * For both simple folder queries and folder shortcut queries, this is set to
+ * the concrete guid of the folder (i.e. for folder shortcuts it's the target
+ * folder guid). Otherwise, this is set to an empty string.
+ */
+ readonly attribute ACString targetFolderGuid;
+};
+
+
+/**
+ * Allows clients to observe what is happening to a result as it updates itself
+ * according to history and bookmark system events. Register this observer on a
+ * result using nsINavHistoryResult::addObserver.
+ */
+[scriptable, uuid(f62d8b6b-3c4e-4a9f-a897-db605d0b7a0f)]
+interface nsINavHistoryResultObserver : nsISupports
+{
+ /**
+ * Called when 'aItem' is inserted into 'aParent' at index 'aNewIndex'.
+ * The item previously at index (if any) and everything below it will have
+ * been shifted down by one. The item may be a container or a leaf.
+ */
+ void nodeInserted(in nsINavHistoryContainerResultNode aParent,
+ in nsINavHistoryResultNode aNode,
+ in unsigned long aNewIndex);
+
+ /**
+ * Called whan 'aItem' is removed from 'aParent' at 'aOldIndex'. The item
+ * may be a container or a leaf. This function will be called after the item
+ * has been removed from its parent list, but before anything else (including
+ * NULLing out the item's parent) has happened.
+ */
+ void nodeRemoved(in nsINavHistoryContainerResultNode aParent,
+ in nsINavHistoryResultNode aItem,
+ in unsigned long aOldIndex);
+
+ /**
+ * Called whan 'aItem' is moved from 'aOldParent' at 'aOldIndex' to
+ * aNewParent at aNewIndex. The item may be a container or a leaf.
+ *
+ * XXX: at the moment, this method is called only when an item is moved
+ * within the same container. When an item is moved between containers,
+ * a new node is created for the item, and the itemRemoved/itemAdded methods
+ * are used.
+ */
+ void nodeMoved(in nsINavHistoryResultNode aNode,
+ in nsINavHistoryContainerResultNode aOldParent,
+ in unsigned long aOldIndex,
+ in nsINavHistoryContainerResultNode aNewParent,
+ in unsigned long aNewIndex);
+
+ /**
+ * Called right after aNode's title has changed.
+ *
+ * @param aNode
+ * a result node
+ * @param aNewTitle
+ * the new title
+ */
+ void nodeTitleChanged(in nsINavHistoryResultNode aNode,
+ in AUTF8String aNewTitle);
+
+ /**
+ * Called right after aNode's uri property has changed.
+ *
+ * @param aNode
+ * a result node
+ * @param aNewURI
+ * the new uri
+ */
+ void nodeURIChanged(in nsINavHistoryResultNode aNode,
+ in AUTF8String aNewURI);
+
+ /**
+ * Called right after aNode's icon property has changed.
+ *
+ * @param aNode
+ * a result node
+ *
+ * @note: The new icon is accessible through aNode.icon.
+ */
+ void nodeIconChanged(in nsINavHistoryResultNode aNode);
+
+ /**
+ * Called right after aNode's time property or accessCount property, or both,
+ * have changed.
+ *
+ * @param aNode
+ * a uri result node
+ * @param aNewVisitDate
+ * the new visit date
+ * @param aNewAccessCount
+ * the new access-count
+ */
+ void nodeHistoryDetailsChanged(in nsINavHistoryResultNode aNode,
+ in PRTime aNewVisitDate,
+ in unsigned long aNewAccessCount);
+
+ /**
+ * Called when the tags set on the uri represented by aNode have changed.
+ *
+ * @param aNode
+ * a uri result node
+ *
+ * @note: The new tags list is accessible through aNode.tags.
+ */
+ void nodeTagsChanged(in nsINavHistoryResultNode aNode);
+
+ /**
+ * Called right after the aNode's keyword property has changed.
+ *
+ * @param aNode
+ * a uri result node
+ * @param aNewKeyword
+ * the new keyword
+ */
+ void nodeKeywordChanged(in nsINavHistoryResultNode aNode,
+ in AUTF8String aNewKeyword);
+
+ /**
+ * Called right after an annotation of aNode's has changed (set, altered, or
+ * unset).
+ *
+ * @param aNode
+ * a result node
+ * @param aAnnoName
+ * the name of the annotation that changed
+ */
+ void nodeAnnotationChanged(in nsINavHistoryResultNode aNode,
+ in AUTF8String aAnnoName);
+
+ /**
+ * Called right after aNode's dateAdded property has changed.
+ *
+ * @param aNode
+ * a result node
+ * @param aNewValue
+ * the new value of the dateAdded property
+ */
+ void nodeDateAddedChanged(in nsINavHistoryResultNode aNode,
+ in PRTime aNewValue);
+
+ /**
+ * Called right after aNode's dateModified property has changed.
+ *
+ * @param aNode
+ * a result node
+ * @param aNewValue
+ * the new value of the dateModified property
+ */
+ void nodeLastModifiedChanged(in nsINavHistoryResultNode aNode,
+ in PRTime aNewValue);
+
+ /**
+ * Called after a container changes state.
+ *
+ * @param aContainerNode
+ * The container that has changed state.
+ * @param aOldState
+ * The state that aContainerNode has transitioned out of.
+ * @param aNewState
+ * The state that aContainerNode has transitioned into.
+ */
+ void containerStateChanged(in nsINavHistoryContainerResultNode aContainerNode,
+ in unsigned long aOldState,
+ in unsigned long aNewState);
+
+ /**
+ * Called when something significant has happened within the container. The
+ * contents of the container should be re-built.
+ *
+ * @param aContainerNode
+ * the container node to invalidate
+ */
+ void invalidateContainer(in nsINavHistoryContainerResultNode aContainerNode);
+
+ /**
+ * This is called to indicate to the UI that the sort has changed to the
+ * given mode. For trees, for example, this would update the column headers
+ * to reflect the sorting. For many other types of views, this won't be
+ * applicable.
+ *
+ * @param sortingMode One of nsINavHistoryQueryOptions.SORT_BY_* that
+ * indicates the new sorting mode.
+ *
+ * This only is expected to update the sorting UI. invalidateAll() will also
+ * get called if the sorting changes to update everything.
+ */
+ void sortingChanged(in unsigned short sortingMode);
+
+ /**
+ * This is called to indicate that a batch operation is about to start or end.
+ * The observer could want to disable some events or updates during batches,
+ * since multiple operations are packed in a short time.
+ * For example treeviews could temporarily suppress select notifications.
+ *
+ * @param aToggleMode
+ * true if a batch is starting, false if it's ending.
+ */
+ void batching(in boolean aToggleMode);
+
+ /**
+ * Called by the result when this observer is added.
+ */
+ attribute nsINavHistoryResult result;
+};
+
+
+/**
+ * TODO: Bug 517719.
+ *
+ * A predefined view adaptor for interfacing results with an nsITree. This
+ * object will remove itself from its associated result when the tree has been
+ * detached. This prevents circular references. Users should be aware of this,
+ * if you want to re-use the same viewer, you will need to keep your own
+ * reference to it and re-initialize it when the tree changes. If you use this
+ * object, attach it to a result, never attach it to a tree, and forget about
+ * it, it will leak!
+ */
+[scriptable, uuid(f8b518c0-1faf-11df-8a39-0800200c9a66)]
+interface nsINavHistoryResultTreeViewer : nsINavHistoryResultObserver
+{
+ /**
+ * This allows you to get at the real node for a given row index. This is
+ * only valid when a tree is attached.
+ */
+ nsINavHistoryResultNode nodeForTreeIndex(in unsigned long aIndex);
+
+ /**
+ * Reverse of nodeForFlatIndex, returns the row index for a given result node.
+ * Returns INDEX_INVISIBLE if the item is not visible (for example, its
+ * parent is collapsed). This is only valid when a tree is attached. The
+ * the result will always be INDEX_INVISIBLE if not.
+ *
+ * Note: This sounds sort of obvious, but it got me: aNode must be a node
+ * retrieved from the same result that this viewer is for. If you
+ * execute another query and get a node from a _different_ result, this
+ * function will always return the index of that node in the tree that
+ * is attached to that result.
+ */
+ const unsigned long INDEX_INVISIBLE = 0xffffffff;
+ unsigned long treeIndexForNode(in nsINavHistoryResultNode aNode);
+};
+
+
+/**
+ * The result of a history/bookmark query.
+ */
+[scriptable, uuid(c2229ce3-2159-4001-859c-7013c52f7619)]
+interface nsINavHistoryResult : nsISupports
+{
+ /**
+ * Sorts all nodes recursively by the given parameter, one of
+ * nsINavHistoryQueryOptions.SORT_BY_* This will update the corresponding
+ * options for this result, so that re-using the current options/queries will
+ * always give you the current view.
+ */
+ attribute unsigned short sortingMode;
+
+ /**
+ * The annotation to use in SORT_BY_ANNOTATION_* sorting modes, set this
+ * before setting the sortingMode attribute.
+ */
+ attribute AUTF8String sortingAnnotation;
+
+ /**
+ * Whether or not notifications on result changes are suppressed.
+ * Initially set to false.
+ *
+ * Use this to avoid flickering and to improve performance when you
+ * do temporary changes to the result structure (e.g. when searching for a
+ * node recursively).
+ */
+ attribute boolean suppressNotifications;
+
+ /**
+ * Adds an observer for changes done in the result.
+ *
+ * @param aObserver
+ * a result observer.
+ * @param aOwnsWeak
+ * If false, the result will keep an owning reference to the observer,
+ * which must be removed using removeObserver.
+ * If true, the result will keep a weak reference to the observer, which
+ * must implement nsISupportsWeakReference.
+ *
+ * @see nsINavHistoryResultObserver
+ */
+ void addObserver(in nsINavHistoryResultObserver aObserver, in boolean aOwnsWeak);
+
+ /**
+ * Removes an observer that was added by addObserver.
+ *
+ * @param aObserver
+ * a result observer that was added by addObserver.
+ */
+ void removeObserver(in nsINavHistoryResultObserver aObserver);
+
+ /**
+ * This is the root of the results. Remember that you need to open all
+ * containers for their contents to be valid.
+ *
+ * When a result goes out of scope it will continue to observe changes till
+ * it is cycle collected. While the result waits to be collected it will stay
+ * in memory, and continue to update itself, potentially causing unwanted
+ * additional work. When you close the root node the result will stop
+ * observing changes, so it is good practice to close the root node when you
+ * are done with a result, since that will avoid unwanted performance hits.
+ */
+ readonly attribute nsINavHistoryContainerResultNode root;
+};
+
+
+/**
+ * Similar to nsIRDFObserver for history. Note that we don't pass the data
+ * source since that is always the global history.
+ *
+ * DANGER! If you are in the middle of a batch transaction, there may be a
+ * database transaction active. You can still access the DB, but be careful.
+ */
+[scriptable, uuid(0f0f45b0-13a1-44ae-a0ab-c6046ec6d4da)]
+interface nsINavHistoryObserver : nsISupports
+{
+ /**
+ * Notifies you that a bunch of things are about to change, don't do any
+ * heavy-duty processing until onEndUpdateBatch is called.
+ */
+ void onBeginUpdateBatch();
+
+ /**
+ * Notifies you that we are done doing a bunch of things and you should go
+ * ahead and update UI, etc.
+ */
+ void onEndUpdateBatch();
+
+ /**
+ * Called everytime a URI is visited.
+ *
+ * @note TRANSITION_EMBED visits (corresponding to images in a page, for
+ * example) are not displayed in history results. Most observers can
+ * ignore TRANSITION_EMBED visit notifications (which will comprise the
+ * majority of visit notifications) to save work.
+ *
+ * @param aVisitId
+ * Id of the visit that was just created.
+ * @param aTime
+ * Time of the visit.
+ * @param aSessionId
+ * No longer supported and always set to 0.
+ * @param aReferrerVisitId
+ * The id of the visit the user came from, defaults to 0 for no referrer.
+ * @param aTransitionType
+ * One of nsINavHistory.TRANSITION_*
+ * @param aGuid
+ * The unique id associated with the page.
+ * @param aHidden
+ * Whether the visited page is marked as hidden.
+ * @param aVisitCount
+ * Number of visits (included this one) for this URI.
+ * @param aTyped
+ * Whether the URI has been typed or not.
+ * TODO (Bug 1271801): This will become a count, rather than a boolean.
+ * For future compatibility, always compare it with "> 0".
+ */
+ void onVisit(in nsIURI aURI,
+ in long long aVisitId,
+ in PRTime aTime,
+ in long long aSessionId,
+ in long long aReferrerVisitId,
+ in unsigned long aTransitionType,
+ in ACString aGuid,
+ in boolean aHidden,
+ in unsigned long aVisitCount,
+ in unsigned long aTyped);
+
+ /**
+ * Called whenever either the "real" title or the custom title of the page
+ * changed. BOTH TITLES ARE ALWAYS INCLUDED in this notification, even though
+ * only one will change at a time. Often, consumers will want to display the
+ * user title if it is available, and fall back to the page title (the one
+ * specified in the <title> tag of the page).
+ *
+ * Note that there is a difference between an empty title and a NULL title.
+ * An empty string means that somebody specifically set the title to be
+ * nothing. NULL means nobody set it. From C++: use IsVoid() and SetIsVoid()
+ * to see whether an empty string is "null" or not (it will always be an
+ * empty string in either case).
+ *
+ * @param aURI
+ * The URI of the page.
+ * @param aPageTitle
+ * The new title of the page.
+ * @param aGUID
+ * The unique ID associated with the page.
+ */
+ void onTitleChanged(in nsIURI aURI,
+ in AString aPageTitle,
+ in ACString aGUID);
+
+ /**
+ * Called when an individual page's frecency has changed.
+ *
+ * This is not called for pages whose frecencies change as the result of some
+ * large operation where some large or unknown number of frecencies change at
+ * once. Use onManyFrecenciesChanged to detect such changes.
+ *
+ * @param aURI
+ * The page's URI.
+ * @param aNewFrecency
+ * The page's new frecency.
+ * @param aGUID
+ * The page's GUID.
+ * @param aHidden
+ * True if the page is marked as hidden.
+ * @param aVisitDate
+ * The page's last visit date.
+ */
+ void onFrecencyChanged(in nsIURI aURI,
+ in long aNewFrecency,
+ in ACString aGUID,
+ in boolean aHidden,
+ in PRTime aVisitDate);
+
+ /**
+ * Called when the frecencies of many pages have changed at once.
+ *
+ * onFrecencyChanged is not called for each of those pages.
+ */
+ void onManyFrecenciesChanged();
+
+ /**
+ * Removed by the user.
+ */
+ const unsigned short REASON_DELETED = 0;
+ /**
+ * Removed by automatic expiration.
+ */
+ const unsigned short REASON_EXPIRED = 1;
+
+ /**
+ * This page and all of its visits are being deleted. Note: the page may not
+ * necessarily have actually existed for this function to be called.
+ *
+ * Delete notifications are only 99.99% accurate. Batch delete operations
+ * must be done in two steps, so first come notifications, then a bulk
+ * delete. If there is some error in the middle (for example, out of memory)
+ * then you'll get a notification and it won't get deleted. There's no easy
+ * way around this.
+ *
+ * @param aURI
+ * The URI that was deleted.
+ * @param aGUID
+ * The unique ID associated with the page.
+ * @param aReason
+ * Indicates the reason for the removal. see REASON_* constants.
+ */
+ void onDeleteURI(in nsIURI aURI,
+ in ACString aGUID,
+ in unsigned short aReason);
+
+ /**
+ * Notification that all of history is being deleted.
+ */
+ void onClearHistory();
+
+ /**
+ * onPageChanged attribute indicating that favicon has been updated.
+ * aNewValue parameter will be set to the new favicon URI string.
+ */
+ const unsigned long ATTRIBUTE_FAVICON = 3;
+
+ /**
+ * An attribute of this page changed.
+ *
+ * @param aURI
+ * The URI of the page on which an attribute changed.
+ * @param aChangedAttribute
+ * The attribute whose value changed. See ATTRIBUTE_* constants.
+ * @param aNewValue
+ * The attribute's new value.
+ * @param aGUID
+ * The unique ID associated with the page.
+ */
+ void onPageChanged(in nsIURI aURI,
+ in unsigned long aChangedAttribute,
+ in AString aNewValue,
+ in ACString aGUID);
+
+ /**
+ * Called when some visits of an history entry are expired.
+ *
+ * @param aURI
+ * The page whose visits have been expired.
+ * @param aVisitTime
+ * The largest visit time in microseconds that has been expired. We
+ * guarantee that we don't have any visit older than this date.
+ * @param aGUID
+ * The unique ID associated with the page.
+ *
+ * @note: when all visits for a page are expired and also the full page entry
+ * is expired, you will only get an onDeleteURI notification. If a
+ * page entry is removed, then you can be sure that we don't have
+ * anymore visits for it.
+ * @param aReason
+ * Indicates the reason for the removal. see REASON_* constants.
+ * @param aTransitionType
+ * If it's a valid TRANSITION_* value, all visits of the specified type
+ * have been removed.
+ */
+ void onDeleteVisits(in nsIURI aURI,
+ in PRTime aVisitTime,
+ in ACString aGUID,
+ in unsigned short aReason,
+ in unsigned long aTransitionType);
+};
+
+
+/**
+ * This object encapsulates all the query parameters you're likely to need
+ * when building up history UI. All parameters are ANDed together.
+ *
+ * This is not intended to be a super-general query mechanism. This was designed
+ * so that most queries can be done in only one SQL query. This is important
+ * because, if the user has their profile on a networked drive, query latency
+ * can be non-negligible.
+ */
+
+[scriptable, uuid(dc87ae79-22f1-4dcf-975b-852b01d210cb)]
+interface nsINavHistoryQuery : nsISupports
+{
+ /**
+ * Time range for results (INCLUSIVE). The *TimeReference is one of the
+ * constants TIME_RELATIVE_* which indicates how to interpret the
+ * corresponding time value.
+ * TIME_RELATIVE_EPOCH (default):
+ * The time is relative to Jan 1 1970 GMT, (this is a normal PRTime)
+ * TIME_RELATIVE_TODAY:
+ * The time is relative to this morning at midnight. Normally used for
+ * queries relative to today. For example, a "past week" query would be
+ * today-6 days -> today+1 day
+ * TIME_RELATIVE_NOW:
+ * The time is relative to right now.
+ *
+ * Note: PRTime is in MICROseconds since 1 Jan 1970. Javascript date objects
+ * are expressed in MILLIseconds since 1 Jan 1970.
+ *
+ * As a special case, a 0 time relative to TIME_RELATIVE_EPOCH indicates that
+ * the time is not part of the query. This is the default, so an empty query
+ * will match any time. The has* functions return whether the corresponding
+ * time is considered.
+ *
+ * You can read absolute*Time to get the time value that the currently loaded
+ * reference points + offset resolve to.
+ */
+ const unsigned long TIME_RELATIVE_EPOCH = 0;
+ const unsigned long TIME_RELATIVE_TODAY = 1;
+ const unsigned long TIME_RELATIVE_NOW = 2;
+
+ attribute PRTime beginTime;
+ attribute unsigned long beginTimeReference;
+ readonly attribute boolean hasBeginTime;
+ readonly attribute PRTime absoluteBeginTime;
+
+ attribute PRTime endTime;
+ attribute unsigned long endTimeReference;
+ readonly attribute boolean hasEndTime;
+ readonly attribute PRTime absoluteEndTime;
+
+ /**
+ * Text search terms.
+ */
+ attribute AString searchTerms;
+ readonly attribute boolean hasSearchTerms;
+
+ /**
+ * Set lower or upper limits for how many times an item has been
+ * visited. The default is -1, and in that case all items are
+ * matched regardless of their visit count.
+ */
+ attribute long minVisits;
+ attribute long maxVisits;
+
+ /**
+ * When the set of transitions is nonempty, results are limited to pages which
+ * have at least one visit for each of the transition types.
+ * @note: For searching on more than one transition this can be very slow.
+ *
+ * Limit results to the specified list of transition types.
+ */
+ void setTransitions([const,array, size_is(count)] in unsigned long transitions,
+ in unsigned long count);
+
+ /**
+ * Get the transitions set for this query.
+ */
+ void getTransitions([optional] out unsigned long count,
+ [retval,array,size_is(count)] out unsigned long transitions);
+
+ /**
+ * Get the count of the set query transitions.
+ */
+ readonly attribute unsigned long transitionCount;
+
+ /**
+ * When set, returns only bookmarked items, when unset, returns anything. Setting this
+ * is equivalent to listing all bookmark folders in the 'folders' parameter.
+ */
+ attribute boolean onlyBookmarked;
+
+ /**
+ * This controls the meaning of 'domain', and whether it is an exact match
+ * 'domainIsHost' = true, or hierarchical (= false).
+ */
+ attribute boolean domainIsHost;
+
+ /**
+ * This is the host or domain name (controlled by domainIsHost). When
+ * domainIsHost, domain only does exact matching on host names. Otherwise,
+ * it will return anything whose host name ends in 'domain'.
+ *
+ * This one is a little different than most. Setting it to an empty string
+ * is a real query and will match any URI that has no host name (local files
+ * and such). Set this to NULL (in C++ use SetIsVoid) if you don't want
+ * domain matching.
+ */
+ attribute AUTF8String domain;
+ readonly attribute boolean hasDomain;
+
+ /**
+ * This is a URI to match, to, for example, find out every time you visited
+ * a given URI. This is an exact match.
+ */
+ attribute nsIURI uri;
+ readonly attribute boolean hasUri;
+
+ /**
+ * Test for existence or non-existence of a given annotation. We don't
+ * currently support >1 annotation name per query. If 'annotationIsNot' is
+ * true, we test for the non-existence of the specified annotation.
+ *
+ * Testing for not annotation will do the same thing as a normal query and
+ * remove everything that doesn't have that annotation. Asking for things
+ * that DO have a given annotation is a little different. It also includes
+ * things that have never been visited. This allows place queries to be
+ * returned as well as anything else that may have been tagged with an
+ * annotation. This will only work for RESULTS_AS_URI since there will be
+ * no visits for these items.
+ */
+ attribute boolean annotationIsNot;
+ attribute AUTF8String annotation;
+ readonly attribute boolean hasAnnotation;
+
+ /**
+ * Limit results to items that are tagged with all of the given tags. This
+ * attribute must be set to an array of strings. When called as a getter it
+ * will return an array of strings sorted ascending in lexicographical order.
+ * The array may be empty in either case. Duplicate tags may be specified
+ * when setting the attribute, but the getter returns only unique tags.
+ *
+ * To search for items that are tagged with any given tags rather than all,
+ * multiple queries may be passed to nsINavHistoryService.executeQueries().
+ */
+ attribute nsIVariant tags;
+
+ /**
+ * If 'tagsAreNot' is true, the results are instead limited to items that
+ * are not tagged with any of the given tags. This attribute is used in
+ * conjunction with the 'tags' attribute.
+ */
+ attribute boolean tagsAreNot;
+
+ /**
+ * Limit results to items that are in all of the given folders.
+ */
+ void getFolders([optional] out unsigned long count,
+ [retval,array,size_is(count)] out long long folders);
+ readonly attribute unsigned long folderCount;
+
+ /**
+ * For the special result type RESULTS_AS_TAG_CONTENTS we can define only
+ * one folder that must be a tag folder. This is not recursive so results
+ * will be returned from the first level of that folder.
+ */
+ void setFolders([const,array, size_is(folderCount)] in long long folders,
+ in unsigned long folderCount);
+
+ /**
+ * Creates a new query item with the same parameters of this one.
+ */
+ nsINavHistoryQuery clone();
+};
+
+/**
+ * This object represents the global options for executing a query.
+ */
+[scriptable, uuid(8198dfa7-8061-4766-95cb-fa86b3c00a47)]
+interface nsINavHistoryQueryOptions : nsISupports
+{
+ /**
+ * You can ask for the results to be pre-sorted. Since the DB has indices
+ * of many items, it can produce sorted results almost for free. These should
+ * be self-explanatory.
+ *
+ * Note: re-sorting is slower, as is sorting by title or when you have a
+ * host name.
+ *
+ * For bookmark items, SORT_BY_NONE means sort by the natural bookmark order.
+ */
+ const unsigned short SORT_BY_NONE = 0;
+ const unsigned short SORT_BY_TITLE_ASCENDING = 1;
+ const unsigned short SORT_BY_TITLE_DESCENDING = 2;
+ const unsigned short SORT_BY_DATE_ASCENDING = 3;
+ const unsigned short SORT_BY_DATE_DESCENDING = 4;
+ const unsigned short SORT_BY_URI_ASCENDING = 5;
+ const unsigned short SORT_BY_URI_DESCENDING = 6;
+ const unsigned short SORT_BY_VISITCOUNT_ASCENDING = 7;
+ const unsigned short SORT_BY_VISITCOUNT_DESCENDING = 8;
+ const unsigned short SORT_BY_KEYWORD_ASCENDING = 9;
+ const unsigned short SORT_BY_KEYWORD_DESCENDING = 10;
+ const unsigned short SORT_BY_DATEADDED_ASCENDING = 11;
+ const unsigned short SORT_BY_DATEADDED_DESCENDING = 12;
+ const unsigned short SORT_BY_LASTMODIFIED_ASCENDING = 13;
+ const unsigned short SORT_BY_LASTMODIFIED_DESCENDING = 14;
+ const unsigned short SORT_BY_TAGS_ASCENDING = 17;
+ const unsigned short SORT_BY_TAGS_DESCENDING = 18;
+ const unsigned short SORT_BY_ANNOTATION_ASCENDING = 19;
+ const unsigned short SORT_BY_ANNOTATION_DESCENDING = 20;
+ const unsigned short SORT_BY_FRECENCY_ASCENDING = 21;
+ const unsigned short SORT_BY_FRECENCY_DESCENDING = 22;
+
+ /**
+ * "URI" results, one for each URI visited in the range. Individual result
+ * nodes will be of type "URI".
+ */
+ const unsigned short RESULTS_AS_URI = 0;
+
+ /**
+ * "Visit" results, with one for each time a page was visited (this will
+ * often give you multiple results for one URI). Individual result nodes will
+ * have type "Visit"
+ *
+ * @note This result type is only supported by QUERY_TYPE_HISTORY.
+ */
+ const unsigned short RESULTS_AS_VISIT = 1;
+
+ /**
+ * This is identical to RESULT_TYPE_VISIT except that individual result nodes
+ * will have type "FullVisit". This is used for the attributes that are not
+ * commonly accessed to save space in the common case (the lists can be very
+ * long).
+ *
+ * @note Not yet implemented. See bug 409662.
+ * @note This result type is only supported by QUERY_TYPE_HISTORY.
+ */
+ const unsigned short RESULTS_AS_FULL_VISIT = 2;
+
+ /**
+ * This returns query nodes for each predefined date range where we
+ * had visits. The node contains information how to load its content:
+ * - visits for the given date range will be loaded.
+ *
+ * @note This result type is only supported by QUERY_TYPE_HISTORY.
+ */
+ const unsigned short RESULTS_AS_DATE_QUERY = 3;
+
+ /**
+ * This returns nsINavHistoryQueryResultNode nodes for each site where we
+ * have visits. The node contains information how to load its content:
+ * - last visit for each url in the given host will be loaded.
+ *
+ * @note This result type is only supported by QUERY_TYPE_HISTORY.
+ */
+ const unsigned short RESULTS_AS_SITE_QUERY = 4;
+
+ /**
+ * This returns nsINavHistoryQueryResultNode nodes for each day where we
+ * have visits. The node contains information how to load its content:
+ * - list of hosts visited in the given period will be loaded.
+ *
+ * @note This result type is only supported by QUERY_TYPE_HISTORY.
+ */
+ const unsigned short RESULTS_AS_DATE_SITE_QUERY = 5;
+
+ /**
+ * This returns nsINavHistoryQueryResultNode nodes for each tag.
+ * The node contains information how to load its content:
+ * - list of bookmarks with the given tag will be loaded.
+ *
+ * @note Setting this resultType will force queryType to QUERY_TYPE_BOOKMARKS.
+ */
+ const unsigned short RESULTS_AS_TAG_QUERY = 6;
+
+ /**
+ * This is a container with an URI result type that contains the last
+ * modified bookmarks for the given tag.
+ * Tag folder id must be defined in the query.
+ *
+ * @note Setting this resultType will force queryType to QUERY_TYPE_BOOKMARKS.
+ */
+ const unsigned short RESULTS_AS_TAG_CONTENTS = 7;
+
+ /**
+ * The sorting mode to be used for this query.
+ * mode is one of SORT_BY_*
+ */
+ attribute unsigned short sortingMode;
+
+ /**
+ * The annotation to use in SORT_BY_ANNOTATION_* sorting modes.
+ */
+ attribute AUTF8String sortingAnnotation;
+
+ /**
+ * Sets the result type. One of RESULT_TYPE_* which includes how URIs are
+ * represented.
+ */
+ attribute unsigned short resultType;
+
+ /**
+ * This option excludes all URIs and separators from a bookmarks query.
+ * This would be used if you just wanted a list of bookmark folders and
+ * queries (such as the left pane of the places page).
+ * Defaults to false.
+ */
+ attribute boolean excludeItems;
+
+ /**
+ * Set to true to exclude queries ("place:" URIs) from the query results.
+ * Simple folder queries (bookmark folder symlinks) will still be included.
+ * Defaults to false.
+ */
+ attribute boolean excludeQueries;
+
+ /**
+ * DO NOT USE THIS API. IT'LL BE REMOVED IN BUG 1072833.
+ *
+ * Set to true to exclude live bookmarks from the query results.
+ */
+ attribute boolean excludeReadOnlyFolders;
+
+ /**
+ * When set, allows items with "place:" URIs to appear as containers,
+ * with the container's contents filled in from the stored query.
+ * If not set, these will appear as normal items. Doesn't do anything if
+ * excludeQueries is set. Defaults to false.
+ *
+ * Note that this has no effect on folder links, which are place: URIs
+ * returned by nsINavBookmarkService.GetFolderURI. These are always expanded
+ * and will appear as bookmark folders.
+ */
+ attribute boolean expandQueries;
+
+ /**
+ * Some pages in history are marked "hidden" and thus don't appear by default
+ * in queries. These include automatic framed visits and redirects. Setting
+ * this attribute will return all pages, even hidden ones. Does nothing for
+ * bookmark queries. Defaults to false.
+ */
+ attribute boolean includeHidden;
+
+ /**
+ * This is the maximum number of results that you want. The query is exeucted,
+ * the results are sorted, and then the top 'maxResults' results are taken
+ * and returned. Set to 0 (the default) to get all results.
+ *
+ * THIS DOES NOT WORK IN CONJUNCTION WITH SORTING BY TITLE. This is because
+ * sorting by title requires us to sort after using locale-sensetive sorting
+ * (as opposed to letting the database do it for us).
+ *
+ * Instead, we get the result ordered by date, pick the maxResult most recent
+ * ones, and THEN sort by title.
+ */
+ attribute unsigned long maxResults;
+
+ const unsigned short QUERY_TYPE_HISTORY = 0;
+ const unsigned short QUERY_TYPE_BOOKMARKS = 1;
+ /* Unified queries are not yet implemented. See bug 378798 */
+ const unsigned short QUERY_TYPE_UNIFIED = 2;
+
+ /**
+ * The type of search to use when querying the DB; This attribute is only
+ * honored by query nodes. It is silently ignored for simple folder queries.
+ */
+ attribute unsigned short queryType;
+
+ /**
+ * When this is true, the root container node generated by these options and
+ * its descendant containers will be opened asynchronously if they support it.
+ * This is false by default.
+ *
+ * @note Currently only bookmark folder containers support being opened
+ * asynchronously.
+ */
+ attribute boolean asyncEnabled;
+
+ /**
+ * Creates a new options item with the same parameters of this one.
+ */
+ nsINavHistoryQueryOptions clone();
+};
+
+[scriptable, uuid(8a1f527e-c9d7-4a51-bf0c-d86f0379b701)]
+interface nsINavHistoryService : nsISupports
+{
+ /**
+ * System Notifications:
+ *
+ * places-init-complete - Sent once the History service is completely
+ * initialized successfully.
+ * places-database-locked - Sent if initialization of the History service
+ * failed due to the inability to open the places.sqlite
+ * for access reasons.
+ */
+
+ /**
+ * This transition type means the user followed a link and got a new toplevel
+ * window.
+ */
+ const unsigned long TRANSITION_LINK = 1;
+
+ /**
+ * This transition type means that the user typed the page's URL in the
+ * URL bar or selected it from URL bar autocomplete results, clicked on
+ * it from a history query (from the History sidebar, History menu,
+ * or history query in the personal toolbar or Places organizer.
+ */
+ const unsigned long TRANSITION_TYPED = 2;
+
+ /**
+ * This transition is set when the user followed a bookmark to get to the
+ * page.
+ */
+ const unsigned long TRANSITION_BOOKMARK = 3;
+
+ /**
+ * This transition type is set when some inner content is loaded. This is
+ * true of all images on a page, and the contents of the iframe. It is also
+ * true of any content in a frame if the user did not explicitly follow
+ * a link to get there.
+ */
+ const unsigned long TRANSITION_EMBED = 4;
+
+ /**
+ * Set when the transition was a permanent redirect.
+ */
+ const unsigned long TRANSITION_REDIRECT_PERMANENT = 5;
+
+ /**
+ * Set when the transition was a temporary redirect.
+ */
+ const unsigned long TRANSITION_REDIRECT_TEMPORARY = 6;
+
+ /**
+ * Set when the transition is a download.
+ */
+ const unsigned long TRANSITION_DOWNLOAD = 7;
+
+ /**
+ * This transition type means the user followed a link and got a visit in
+ * a frame.
+ */
+ const unsigned long TRANSITION_FRAMED_LINK = 8;
+
+ /**
+ * This transition type means the page has been reloaded.
+ */
+ const unsigned long TRANSITION_RELOAD = 9;
+
+ /**
+ * Set when database is coherent
+ */
+ const unsigned short DATABASE_STATUS_OK = 0;
+
+ /**
+ * Set when database did not exist and we created a new one
+ */
+ const unsigned short DATABASE_STATUS_CREATE = 1;
+
+ /**
+ * Set when database was corrupt and we replaced it
+ */
+ const unsigned short DATABASE_STATUS_CORRUPT = 2;
+
+ /**
+ * Set when database schema has been upgraded
+ */
+ const unsigned short DATABASE_STATUS_UPGRADED = 3;
+
+ /**
+ * Returns the current database status
+ */
+ readonly attribute unsigned short databaseStatus;
+
+ /**
+ * True if there is any history. This can be used in UI to determine whether
+ * the "clear history" button should be enabled or not. This is much better
+ * than using BrowserHistory.count since that can be very slow if there is
+ * a lot of history (it must enumerate each item). This is pretty fast.
+ */
+ readonly attribute boolean hasHistoryEntries;
+
+ /**
+ * Gets the original title of the page.
+ * @deprecated use mozIAsyncHistory.getPlacesInfo instead.
+ */
+ AString getPageTitle(in nsIURI aURI);
+
+ /**
+ * This is just like markPageAsTyped (in nsIBrowserHistory, also implemented
+ * by the history service), but for bookmarks. It declares that the given URI
+ * is being opened as a result of following a bookmark. If this URI is loaded
+ * soon after this message has been received, that transition will be marked
+ * as following a bookmark.
+ */
+ void markPageAsFollowedBookmark(in nsIURI aURI);
+
+ /**
+ * Designates the url as having been explicitly typed in by the user.
+ *
+ * @param aURI
+ * URI of the page to be marked.
+ */
+ void markPageAsTyped(in nsIURI aURI);
+
+ /**
+ * Designates the url as coming from a link explicitly followed by
+ * the user (for example by clicking on it).
+ *
+ * @param aURI
+ * URI of the page to be marked.
+ */
+ void markPageAsFollowedLink(in nsIURI aURI);
+
+ /**
+ * Returns true if this URI would be added to the history. You don't have to
+ * worry about calling this, adding a visit will always check before
+ * actually adding the page. This function is public because some components
+ * may want to check if this page would go in the history (i.e. for
+ * annotations).
+ */
+ boolean canAddURI(in nsIURI aURI);
+
+ /**
+ * This returns a new query object that you can pass to executeQuer[y/ies].
+ * It will be initialized to all empty (so using it will give you all history).
+ */
+ nsINavHistoryQuery getNewQuery();
+
+ /**
+ * This returns a new options object that you can pass to executeQuer[y/ies]
+ * after setting the desired options.
+ */
+ nsINavHistoryQueryOptions getNewQueryOptions();
+
+ /**
+ * Executes a single query.
+ */
+ nsINavHistoryResult executeQuery(in nsINavHistoryQuery aQuery,
+ in nsINavHistoryQueryOptions options);
+
+ /**
+ * Executes an array of queries. All of the query objects are ORed
+ * together. Within a query, all the terms are ANDed together as in
+ * executeQuery. See executeQuery()
+ */
+ nsINavHistoryResult executeQueries(
+ [array,size_is(aQueryCount)] in nsINavHistoryQuery aQueries,
+ in unsigned long aQueryCount,
+ in nsINavHistoryQueryOptions options);
+
+ /**
+ * Converts a query URI-like string to an array of actual query objects for
+ * use to executeQueries(). The output query array may be empty if there is
+ * no information. However, there will always be an options structure returned
+ * (if nothing is defined, it will just have the default values).
+ */
+ void queryStringToQueries(in AUTF8String aQueryString,
+ [array, size_is(aResultCount)] out nsINavHistoryQuery aQueries,
+ out unsigned long aResultCount,
+ out nsINavHistoryQueryOptions options);
+
+ /**
+ * Converts a query into an equivalent string that can be persisted. Inverse
+ * of queryStringToQueries()
+ */
+ AUTF8String queriesToQueryString(
+ [array, size_is(aQueryCount)] in nsINavHistoryQuery aQueries,
+ in unsigned long aQueryCount,
+ in nsINavHistoryQueryOptions options);
+
+ /**
+ * Adds a history observer. If ownsWeak is false, the history service will
+ * keep an owning reference to the observer. If ownsWeak is true, then
+ * aObserver must implement nsISupportsWeakReference, and the history service
+ * will keep a weak reference to the observer.
+ */
+ void addObserver(in nsINavHistoryObserver observer, in boolean ownsWeak);
+
+ /**
+ * Removes a history observer.
+ */
+ void removeObserver(in nsINavHistoryObserver observer);
+
+ /**
+ * Gets an array of registered nsINavHistoryObserver objects.
+ */
+ void getObservers([optional] out unsigned long count,
+ [retval, array, size_is(count)] out nsINavHistoryObserver observers);
+
+ /**
+ * Runs the passed callback in batch mode. Use this when a lot of things
+ * are about to change. Calls can be nested, observers will only be
+ * notified when all batches begin/end.
+ *
+ * @param aCallback
+ * nsINavHistoryBatchCallback interface to call.
+ * @param aUserData
+ * Opaque parameter passed to nsINavBookmarksBatchCallback
+ */
+ void runInBatchMode(in nsINavHistoryBatchCallback aCallback,
+ in nsISupports aClosure);
+
+ /**
+ * True if history is disabled. currently,
+ * history is disabled if the places.history.enabled pref is false.
+ */
+ readonly attribute boolean historyDisabled;
+
+ /**
+ * Clear all TRANSITION_EMBED visits.
+ */
+ void clearEmbedVisits();
+};
+
+/**
+ * @see runInBatchMode of nsINavHistoryService/nsINavBookmarksService
+ */
+[scriptable, function, uuid(5a5a9154-95ac-4e3d-90df-558816297407)]
+interface nsINavHistoryBatchCallback : nsISupports {
+ void runBatched(in nsISupports aUserData);
+};
diff --git a/components/places/public/nsITaggingService.idl b/components/places/public/nsITaggingService.idl
new file mode 100644
index 000000000..f3731feb6
--- /dev/null
+++ b/components/places/public/nsITaggingService.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 nsIURI;
+interface nsIVariant;
+
+[scriptable, uuid(9759bd0e-78e2-4421-9ed1-c676e1af3513)]
+interface nsITaggingService : nsISupports
+{
+
+ /**
+ * Defines the maximal length of a tag. Related to the bug 407821
+ * (https://bugzilla.mozilla.org/show_bug.cgi?id=407821)
+ */
+ const unsigned long MAX_TAG_LENGTH = 100;
+
+ /**
+ * Tags a URL with the given set of tags. Current tags set for the URL
+ * persist. Tags in aTags which are already set for the given URL are
+ * ignored.
+ *
+ * @param aURI
+ * the URL to tag.
+ * @param aTags
+ * Array of tags to set for the given URL. Each element within the
+ * array can be either a tag name (non-empty string) or a concrete
+ * itemId of a tag container.
+ * @param [optional] aSource
+ * A change source constant from nsINavBookmarksService::SOURCE_*.
+ * Defaults to SOURCE_DEFAULT if omitted.
+ */
+ void tagURI(in nsIURI aURI,
+ in nsIVariant aTags,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Removes tags from a URL. Tags from aTags which are not set for the
+ * given URL are ignored.
+ *
+ * @param aURI
+ * the URL to un-tag.
+ * @param aTags
+ * Array of tags to unset. Pass null to remove all tags from the given
+ * url. Each element within the array can be either a tag name
+ * (non-empty string) or a concrete itemId of a tag container.
+ * @param [optional] aSource
+ * A change source constant from nsINavBookmarksService::SOURCE_*.
+ * Defaults to SOURCE_DEFAULT if omitted.
+ */
+ void untagURI(in nsIURI aURI,
+ in nsIVariant aTags,
+ [optional] in unsigned short aSource);
+
+ /**
+ * Retrieves all URLs tagged with the given tag.
+ *
+ * @param aTag
+ * tag name
+ * @returns Array of uris tagged with aTag.
+ */
+ nsIVariant getURIsForTag(in AString aTag);
+
+ /**
+ * Retrieves all tags set for the given URL.
+ *
+ * @param aURI
+ * a URL.
+ * @returns array of tags (sorted by name).
+ */
+ void getTagsForURI(in nsIURI aURI,
+ [optional] out unsigned long length,
+ [retval, array, size_is(length)] out wstring aTags);
+
+ /**
+ * Retrieves all tags used to tag URIs in the data-base (sorted by name).
+ */
+ readonly attribute nsIVariant allTags;
+
+ /**
+ * Whether any tags exist.
+ *
+ * @note This is faster than allTags.length, since doesn't need to sort tags.
+ */
+ readonly attribute boolean hasTags;
+};
+
+%{C++
+
+#define TAGGING_SERVICE_CID "@mozilla.org/browser/tagging-service;1"
+
+%}
diff --git a/components/places/public/nsPIPlacesDatabase.idl b/components/places/public/nsPIPlacesDatabase.idl
new file mode 100644
index 000000000..5511b1be6
--- /dev/null
+++ b/components/places/public/nsPIPlacesDatabase.idl
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=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 mozIStorageConnection;
+interface nsINavHistoryQuery;
+interface nsINavHistoryQueryOptions;
+interface mozIStorageStatementCallback;
+interface mozIStoragePendingStatement;
+interface nsIAsyncShutdownClient;
+
+/**
+ * This is a private interface used by Places components to get access to the
+ * database. If outside consumers wish to use this, they should only read from
+ * the database so they do not break any internal invariants.
+ */
+[scriptable, uuid(366ee63e-a413-477d-9ad6-8d6863e89401)]
+interface nsPIPlacesDatabase : nsISupports
+{
+ /**
+ * The database connection used by Places.
+ */
+ readonly attribute mozIStorageConnection DBConnection;
+
+ /**
+ * Asynchronously executes the statement created from queries.
+ *
+ * @see nsINavHistoryService::executeQueries
+ * @note THIS IS A TEMPORARY API. Don't rely on it, since it will be replaced
+ * in future versions by a real async querying API.
+ * @note Results obtained from this method differ from results obtained from
+ * executeQueries, because there is additional filtering and sorting
+ * done by the latter. Thus you should use executeQueries, unless you
+ * are absolutely sure that the returned results are fine for
+ * your use-case.
+ */
+ mozIStoragePendingStatement asyncExecuteLegacyQueries(
+ [array, size_is(aQueryCount)] in nsINavHistoryQuery aQueries,
+ in unsigned long aQueryCount,
+ in nsINavHistoryQueryOptions aOptions,
+ in mozIStorageStatementCallback aCallback);
+
+ /**
+ * Hook for clients who need to perform actions during/by the end of
+ * the shutdown of the database.
+ */
+ readonly attribute nsIAsyncShutdownClient shutdownClient;
+};
diff --git a/components/places/src/BookmarkHTMLUtils.jsm b/components/places/src/BookmarkHTMLUtils.jsm
new file mode 100644
index 000000000..8a54135a7
--- /dev/null
+++ b/components/places/src/BookmarkHTMLUtils.jsm
@@ -0,0 +1,1187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 works on the old-style "bookmarks.html" file. It includes
+ * functions to import and export existing bookmarks to this file format.
+ *
+ * Format
+ * ------
+ *
+ * Primary heading := h1
+ * Old version used this to set attributes on the bookmarks RDF root, such
+ * as the last modified date. We only use H1 to check for the attribute
+ * PLACES_ROOT, which tells us that this hierarchy root is the places root.
+ * For backwards compatibility, if we don't find this, we assume that the
+ * hierarchy is rooted at the bookmarks menu.
+ * Heading := any heading other than h1
+ * Old version used this to set attributes on the current container. We only
+ * care about the content of the heading container, which contains the title
+ * of the bookmark container.
+ * Bookmark := a
+ * HREF is the destination of the bookmark
+ * FEEDURL is the URI of the RSS feed if this is a livemark.
+ * LAST_CHARSET is stored as an annotation so that the next time we go to
+ * that page we remember the user's preference.
+ * WEB_PANEL is set to "true" if the bookmark should be loaded in the sidebar.
+ * ICON will be stored in the favicon service
+ * ICON_URI is new for places bookmarks.html, it refers to the original
+ * URI of the favicon so we don't have to make up favicon URLs.
+ * Text of the <a> container is the name of the bookmark
+ * Ignored: LAST_VISIT, ID (writing out non-RDF IDs can confuse Firefox 2)
+ * Bookmark comment := dd
+ * This affects the previosly added bookmark
+ * Separator := hr
+ * Insert a separator into the current container
+ * The folder hierarchy is defined by <dl>/<ul>/<menu> (the old importing code
+ * handles all these cases, when we write, use <dl>).
+ *
+ * Overall design
+ * --------------
+ *
+ * We need to emulate a recursive parser. A "Bookmark import frame" is created
+ * corresponding to each folder we encounter. These are arranged in a stack,
+ * and contain all the state we need to keep track of.
+ *
+ * A frame is created when we find a heading, which defines a new container.
+ * The frame also keeps track of the nesting of <DL>s, (in well-formed
+ * bookmarks files, these will have a 1-1 correspondence with frames, but we
+ * try to be a little more flexible here). When the nesting count decreases
+ * to 0, then we know a frame is complete and to pop back to the previous
+ * frame.
+ *
+ * Note that a lot of things happen when tags are CLOSED because we need to
+ * get the text from the content of the tag. For example, link and heading tags
+ * both require the content (= title) before actually creating it.
+ */
+
+this.EXPORTED_SYMBOLS = [ "BookmarkHTMLUtils" ];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
+ "resource://gre/modules/PlacesBackups.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+
+const Container_Normal = 0;
+const Container_Toolbar = 1;
+const Container_Menu = 2;
+const Container_Unfiled = 3;
+const Container_Places = 4;
+
+const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
+const DESCRIPTION_ANNO = "bookmarkProperties/description";
+
+const MICROSEC_PER_SEC = 1000000;
+
+const EXPORT_INDENT = " "; // four spaces
+
+// Counter used to build fake favicon urls.
+var serialNumber = 0;
+
+function base64EncodeString(aString) {
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.setData(aString, aString.length);
+ let encoder = Cc["@mozilla.org/scriptablebase64encoder;1"]
+ .createInstance(Ci.nsIScriptableBase64Encoder);
+ return encoder.encodeToString(stream, aString.length);
+}
+
+/**
+ * Provides HTML escaping for use in HTML attributes and body of the bookmarks
+ * file, compatible with the old bookmarks system.
+ */
+function escapeHtmlEntities(aText) {
+ return (aText || "").replace(/&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;")
+ .replace(/"/g, "&quot;")
+ .replace(/'/g, "&#39;");
+}
+
+/**
+ * Provides URL escaping for use in HTML attributes of the bookmarks file,
+ * compatible with the old bookmarks system.
+ */
+function escapeUrl(aText) {
+ return (aText || "").replace(/"/g, "%22");
+}
+
+function notifyObservers(aTopic, aInitialImport) {
+ Services.obs.notifyObservers(null, aTopic, aInitialImport ? "html-initial"
+ : "html");
+}
+
+this.BookmarkHTMLUtils = Object.freeze({
+ /**
+ * Loads the current bookmarks hierarchy from a "bookmarks.html" file.
+ *
+ * @param aSpec
+ * String containing the "file:" URI for the existing "bookmarks.html"
+ * file to be loaded.
+ * @param aInitialImport
+ * Whether this is the initial import executed on a new profile.
+ *
+ * @return {Promise}
+ * @resolves When the new bookmarks have been created.
+ * @rejects JavaScript exception.
+ */
+ importFromURL: function BHU_importFromURL(aSpec, aInitialImport) {
+ return Task.spawn(function* () {
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aInitialImport);
+ try {
+ let importer = new BookmarkImporter(aInitialImport);
+ yield importer.importFromURL(aSpec);
+
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS, aInitialImport);
+ } catch (ex) {
+ Cu.reportError("Failed to import bookmarks from " + aSpec + ": " + ex);
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED, aInitialImport);
+ throw ex;
+ }
+ });
+ },
+
+ /**
+ * Loads the current bookmarks hierarchy from a "bookmarks.html" file.
+ *
+ * @param aFilePath
+ * OS.File path string of the "bookmarks.html" file to be loaded.
+ * @param aInitialImport
+ * Whether this is the initial import executed on a new profile.
+ *
+ * @return {Promise}
+ * @resolves When the new bookmarks have been created.
+ * @rejects JavaScript exception.
+ * @deprecated passing an nsIFile is deprecated
+ */
+ importFromFile: function BHU_importFromFile(aFilePath, aInitialImport) {
+ if (aFilePath instanceof Ci.nsIFile) {
+ Deprecated.warning("Passing an nsIFile to BookmarksJSONUtils.importFromFile " +
+ "is deprecated. Please use an OS.File path string instead.",
+ "https://developer.mozilla.org/docs/JavaScript_OS.File");
+ aFilePath = aFilePath.path;
+ }
+
+ return Task.spawn(function* () {
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aInitialImport);
+ try {
+ if (!(yield OS.File.exists(aFilePath))) {
+ throw new Error("Cannot import from nonexisting html file: " + aFilePath);
+ }
+ let importer = new BookmarkImporter(aInitialImport);
+ yield importer.importFromURL(OS.Path.toFileURI(aFilePath));
+
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS, aInitialImport);
+ } catch (ex) {
+ Cu.reportError("Failed to import bookmarks from " + aFilePath + ": " + ex);
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED, aInitialImport);
+ throw ex;
+ }
+ });
+ },
+
+ /**
+ * Saves the current bookmarks hierarchy to a "bookmarks.html" file.
+ *
+ * @param aFilePath
+ * OS.File path string for the "bookmarks.html" file to be created.
+ *
+ * @return {Promise}
+ * @resolves To the exported bookmarks count when the file has been created.
+ * @rejects JavaScript exception.
+ * @deprecated passing an nsIFile is deprecated
+ */
+ exportToFile: function BHU_exportToFile(aFilePath) {
+ if (aFilePath instanceof Ci.nsIFile) {
+ Deprecated.warning("Passing an nsIFile to BookmarksHTMLUtils.exportToFile " +
+ "is deprecated. Please use an OS.File path string instead.",
+ "https://developer.mozilla.org/docs/JavaScript_OS.File");
+ aFilePath = aFilePath.path;
+ }
+ return Task.spawn(function* () {
+ let [bookmarks, count] = yield PlacesBackups.getBookmarksTree();
+ let startTime = Date.now();
+
+ // Report the time taken to convert the tree to HTML.
+ let exporter = new BookmarkExporter(bookmarks);
+ yield exporter.exportToFile(aFilePath);
+
+ return count;
+ });
+ },
+
+ get defaultPath() {
+ try {
+ return Services.prefs.getCharPref("browser.bookmarks.file");
+ } catch (ex) {}
+ return OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.html")
+ }
+});
+
+function Frame(aFrameId) {
+ this.containerId = aFrameId;
+
+ /**
+ * How many <dl>s have been nested. Each frame/container should start
+ * with a heading, and is then followed by a <dl>, <ul>, or <menu>. When
+ * that list is complete, then it is the end of this container and we need
+ * to pop back up one level for new items. If we never get an open tag for
+ * one of these things, we should assume that the container is empty and
+ * that things we find should be siblings of it. Normally, these <dl>s won't
+ * be nested so this will be 0 or 1.
+ */
+ this.containerNesting = 0;
+
+ /**
+ * when we find a heading tag, it actually affects the title of the NEXT
+ * container in the list. This stores that heading tag and whether it was
+ * special. 'consumeHeading' resets this._
+ */
+ this.lastContainerType = Container_Normal;
+
+ /**
+ * this contains the text from the last begin tag until now. It is reset
+ * at every begin tag. We can check it when we see a </a>, or </h3>
+ * to see what the text content of that node should be.
+ */
+ this.previousText = "";
+
+ /**
+ * true when we hit a <dd>, which contains the description for the preceding
+ * <a> tag. We can't just check for </dd> like we can for </a> or </h3>
+ * because if there is a sub-folder, it is actually a child of the <dd>
+ * because the tag is never explicitly closed. If this is true and we see a
+ * new open tag, that means to commit the description to the previous
+ * bookmark.
+ *
+ * Additional weirdness happens when the previous <dt> tag contains a <h3>:
+ * this means there is a new folder with the given description, and whose
+ * children are contained in the following <dl> list.
+ *
+ * This is handled in openContainer(), which commits previous text if
+ * necessary.
+ */
+ this.inDescription = false;
+
+ /**
+ * contains the URL of the previous bookmark created. This is used so that
+ * when we encounter a <dd>, we know what bookmark to associate the text with.
+ * This is cleared whenever we hit a <h3>, so that we know NOT to save this
+ * with a bookmark, but to keep it until
+ */
+ this.previousLink = null; // nsIURI
+
+ /**
+ * contains the URL of the previous livemark, so that when the link ends,
+ * and the livemark title is known, we can create it.
+ */
+ this.previousFeed = null; // nsIURI
+
+ /**
+ * Contains the id of an imported, or newly created bookmark.
+ */
+ this.previousId = 0;
+
+ /**
+ * Contains the date-added and last-modified-date of an imported item.
+ * Used to override the values set by insertBookmark, createFolder, etc.
+ */
+ this.previousDateAdded = 0;
+ this.previousLastModifiedDate = 0;
+}
+
+function BookmarkImporter(aInitialImport) {
+ this._isImportDefaults = aInitialImport;
+ // The bookmark change source, used to determine the sync status and change
+ // counter.
+ this._source = aInitialImport ? PlacesUtils.bookmarks.SOURCE_IMPORT_REPLACE :
+ PlacesUtils.bookmarks.SOURCE_IMPORT;
+ this._frames = new Array();
+ this._frames.push(new Frame(PlacesUtils.bookmarksMenuFolderId));
+}
+
+BookmarkImporter.prototype = {
+
+ _safeTrim: function safeTrim(aStr) {
+ return aStr ? aStr.trim() : aStr;
+ },
+
+ get _curFrame() {
+ return this._frames[this._frames.length - 1];
+ },
+
+ get _previousFrame() {
+ return this._frames[this._frames.length - 2];
+ },
+
+ /**
+ * This is called when there is a new folder found. The folder takes the
+ * name from the previous frame's heading.
+ */
+ _newFrame: function newFrame() {
+ let containerId = -1;
+ let frame = this._curFrame;
+ let containerTitle = frame.previousText;
+ frame.previousText = "";
+ let containerType = frame.lastContainerType;
+
+ switch (containerType) {
+ case Container_Normal:
+ // append a new folder
+ containerId =
+ PlacesUtils.bookmarks.createFolder(frame.containerId,
+ containerTitle,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ /* aGuid */ null, this._source);
+ break;
+ case Container_Places:
+ containerId = PlacesUtils.placesRootId;
+ break;
+ case Container_Menu:
+ containerId = PlacesUtils.bookmarksMenuFolderId;
+ break;
+ case Container_Unfiled:
+ containerId = PlacesUtils.unfiledBookmarksFolderId;
+ break;
+ case Container_Toolbar:
+ containerId = PlacesUtils.toolbarFolderId;
+ break;
+ default:
+ // NOT REACHED
+ throw new Error("Unreached");
+ }
+
+ if (frame.previousDateAdded > 0) {
+ try {
+ PlacesUtils.bookmarks.setItemDateAdded(containerId, frame.previousDateAdded, this._source);
+ } catch (e) {
+ }
+ frame.previousDateAdded = 0;
+ }
+ if (frame.previousLastModifiedDate > 0) {
+ try {
+ PlacesUtils.bookmarks.setItemLastModified(containerId, frame.previousLastModifiedDate, this._source);
+ } catch (e) {
+ }
+ // don't clear last-modified, in case there's a description
+ }
+
+ frame.previousId = containerId;
+
+ this._frames.push(new Frame(containerId));
+ },
+
+ /**
+ * Handles <hr> as a separator.
+ *
+ * @note Separators may have a title in old html files, though Places dropped
+ * support for them.
+ * We also don't import ADD_DATE or LAST_MODIFIED for separators because
+ * pre-Places bookmarks did not support them.
+ */
+ _handleSeparator: function handleSeparator(aElt) {
+ let frame = this._curFrame;
+ try {
+ frame.previousId =
+ PlacesUtils.bookmarks.insertSeparator(frame.containerId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ /* aGuid */ null,
+ this._source);
+ } catch (e) {}
+ },
+
+ /**
+ * Handles <H1>. We check for the attribute PLACES_ROOT and reset the
+ * container id if it's found. Otherwise, the default bookmark menu
+ * root is assumed and imported things will go into the bookmarks menu.
+ */
+ _handleHead1Begin: function handleHead1Begin(aElt) {
+ if (this._frames.length > 1) {
+ return;
+ }
+ if (aElt.hasAttribute("places_root")) {
+ this._curFrame.containerId = PlacesUtils.placesRootId;
+ }
+ },
+
+ /**
+ * Called for h2,h3,h4,h5,h6. This just stores the correct information in
+ * the current frame; the actual new frame corresponding to the container
+ * associated with the heading will be created when the tag has been closed
+ * and we know the title (we don't know to create a new folder or to merge
+ * with an existing one until we have the title).
+ */
+ _handleHeadBegin: function handleHeadBegin(aElt) {
+ let frame = this._curFrame;
+
+ // after a heading, a previous bookmark is not applicable (for example, for
+ // the descriptions contained in a <dd>). Neither is any previous head type
+ frame.previousLink = null;
+ frame.lastContainerType = Container_Normal;
+
+ // It is syntactically possible for a heading to appear after another heading
+ // but before the <dl> that encloses that folder's contents. This should not
+ // happen in practice, as the file will contain "<dl></dl>" sequence for
+ // empty containers.
+ //
+ // Just to be on the safe side, if we encounter
+ // <h3>FOO</h3>
+ // <h3>BAR</h3>
+ // <dl>...content 1...</dl>
+ // <dl>...content 2...</dl>
+ // we'll pop the stack when we find the h3 for BAR, treating that as an
+ // implicit ending of the FOO container. The output will be FOO and BAR as
+ // siblings. If there's another <dl> following (as in "content 2"), those
+ // items will be treated as further siblings of FOO and BAR
+ // This special frame popping business, of course, only happens when our
+ // frame array has more than one element so we can avoid situations where
+ // we don't have a frame to parse into anymore.
+ if (frame.containerNesting == 0 && this._frames.length > 1) {
+ this._frames.pop();
+ }
+
+ // We have to check for some attributes to see if this is a "special"
+ // folder, which will have different creation rules when the end tag is
+ // processed.
+ if (aElt.hasAttribute("personal_toolbar_folder")) {
+ if (this._isImportDefaults) {
+ frame.lastContainerType = Container_Toolbar;
+ }
+ } else if (aElt.hasAttribute("bookmarks_menu")) {
+ if (this._isImportDefaults) {
+ frame.lastContainerType = Container_Menu;
+ }
+ } else if (aElt.hasAttribute("unfiled_bookmarks_folder")) {
+ if (this._isImportDefaults) {
+ frame.lastContainerType = Container_Unfiled;
+ }
+ } else if (aElt.hasAttribute("places_root")) {
+ if (this._isImportDefaults) {
+ frame.lastContainerType = Container_Places;
+ }
+ } else {
+ let addDate = aElt.getAttribute("add_date");
+ if (addDate) {
+ frame.previousDateAdded =
+ this._convertImportedDateToInternalDate(addDate);
+ }
+ let modDate = aElt.getAttribute("last_modified");
+ if (modDate) {
+ frame.previousLastModifiedDate =
+ this._convertImportedDateToInternalDate(modDate);
+ }
+ }
+ this._curFrame.previousText = "";
+ },
+
+ /*
+ * Handles "<a" tags by creating a new bookmark. The title of the bookmark
+ * will be the text content, which will be stuffed in previousText for us
+ * and which will be saved by handleLinkEnd
+ */
+ _handleLinkBegin: function handleLinkBegin(aElt) {
+ let frame = this._curFrame;
+
+ // Make sure that the feed URIs from previous frames are emptied.
+ frame.previousFeed = null;
+ // Make sure that the bookmark id from previous frames are emptied.
+ frame.previousId = 0;
+ // mPreviousText will hold link text, clear it.
+ frame.previousText = "";
+
+ // Get the attributes we care about.
+ let href = this._safeTrim(aElt.getAttribute("href"));
+ let feedUrl = this._safeTrim(aElt.getAttribute("feedurl"));
+ let icon = this._safeTrim(aElt.getAttribute("icon"));
+ let iconUri = this._safeTrim(aElt.getAttribute("icon_uri"));
+ let lastCharset = this._safeTrim(aElt.getAttribute("last_charset"));
+ let keyword = this._safeTrim(aElt.getAttribute("shortcuturl"));
+ let postData = this._safeTrim(aElt.getAttribute("post_data"));
+ let webPanel = this._safeTrim(aElt.getAttribute("web_panel"));
+ let dateAdded = this._safeTrim(aElt.getAttribute("add_date"));
+ let lastModified = this._safeTrim(aElt.getAttribute("last_modified"));
+ let tags = this._safeTrim(aElt.getAttribute("tags"));
+
+ // For feeds, get the feed URL. If it is invalid, mPreviousFeed will be
+ // NULL and we'll create it as a normal bookmark.
+ if (feedUrl) {
+ frame.previousFeed = NetUtil.newURI(feedUrl);
+ }
+
+ // Ignore <a> tags that have no href.
+ if (href) {
+ // Save the address if it's valid. Note that we ignore errors if this is a
+ // feed since href is optional for them.
+ try {
+ frame.previousLink = NetUtil.newURI(href);
+ } catch (e) {
+ if (!frame.previousFeed) {
+ frame.previousLink = null;
+ return;
+ }
+ }
+ } else {
+ frame.previousLink = null;
+ // The exception is for feeds, where the href is an optional component
+ // indicating the source web site.
+ if (!frame.previousFeed) {
+ return;
+ }
+ }
+
+ // Save bookmark's last modified date.
+ if (lastModified) {
+ frame.previousLastModifiedDate =
+ this._convertImportedDateToInternalDate(lastModified);
+ }
+
+ // If this is a live bookmark, we will handle it in HandleLinkEnd(), so we
+ // can skip bookmark creation.
+ if (frame.previousFeed) {
+ return;
+ }
+
+ // Create the bookmark. The title is unknown for now, we will set it later.
+ try {
+ frame.previousId =
+ PlacesUtils.bookmarks.insertBookmark(frame.containerId,
+ frame.previousLink,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ /* aTitle */ "",
+ /* aGuid */ null,
+ this._source);
+ } catch (e) {
+ return;
+ }
+
+ // Set the date added value, if we have it.
+ if (dateAdded) {
+ try {
+ PlacesUtils.bookmarks.setItemDateAdded(frame.previousId,
+ this._convertImportedDateToInternalDate(dateAdded), this._source);
+ } catch (e) {
+ }
+ }
+
+ // Adds tags to the URI, if there are any.
+ if (tags) {
+ try {
+ let tagsArray = tags.split(",");
+ PlacesUtils.tagging.tagURI(frame.previousLink, tagsArray, this._source);
+ } catch (e) {
+ }
+ }
+
+ // Save the favicon.
+ if (icon || iconUri) {
+ let iconUriObject;
+ try {
+ iconUriObject = NetUtil.newURI(iconUri);
+ } catch (e) {
+ }
+ if (icon || iconUriObject) {
+ try {
+ this._setFaviconForURI(frame.previousLink, iconUriObject, icon);
+ } catch (e) {
+ }
+ }
+ }
+
+ // Save the keyword.
+ if (keyword) {
+ let kwPromise = PlacesUtils.keywords.insert({ keyword,
+ url: frame.previousLink.spec,
+ postData,
+ source: this._source });
+ this._importPromises.push(kwPromise);
+ }
+
+ // Set load-in-sidebar annotation for the bookmark.
+ if (webPanel && webPanel.toLowerCase() == "true") {
+ try {
+ PlacesUtils.annotations.setItemAnnotation(frame.previousId,
+ LOAD_IN_SIDEBAR_ANNO,
+ 1,
+ 0,
+ PlacesUtils.annotations.EXPIRE_NEVER,
+ this._source);
+ } catch (e) {
+ }
+ }
+
+ // Import last charset.
+ if (lastCharset) {
+ let chPromise = PlacesUtils.setCharsetForURI(frame.previousLink, lastCharset, this._source);
+ this._importPromises.push(chPromise);
+ }
+ },
+
+ _handleContainerBegin: function handleContainerBegin() {
+ this._curFrame.containerNesting++;
+ },
+
+ /**
+ * Our "indent" count has decreased, and when we hit 0 that means that this
+ * container is complete and we need to pop back to the outer frame. Never
+ * pop the toplevel frame
+ */
+ _handleContainerEnd: function handleContainerEnd() {
+ let frame = this._curFrame;
+ if (frame.containerNesting > 0)
+ frame.containerNesting --;
+ if (this._frames.length > 1 && frame.containerNesting == 0) {
+ // we also need to re-set the imported last-modified date here. Otherwise
+ // the addition of items will override the imported field.
+ let prevFrame = this._previousFrame;
+ if (prevFrame.previousLastModifiedDate > 0) {
+ PlacesUtils.bookmarks.setItemLastModified(frame.containerId,
+ prevFrame.previousLastModifiedDate,
+ this._source);
+ }
+ this._frames.pop();
+ }
+ },
+
+ /**
+ * Creates the new frame for this heading now that we know the name of the
+ * container (tokens since the heading open tag will have been placed in
+ * previousText).
+ */
+ _handleHeadEnd: function handleHeadEnd() {
+ this._newFrame();
+ },
+
+ /**
+ * Saves the title for the given bookmark.
+ */
+ _handleLinkEnd: function handleLinkEnd() {
+ let frame = this._curFrame;
+ frame.previousText = frame.previousText.trim();
+
+ try {
+ if (frame.previousFeed) {
+ // The is a live bookmark. We create it here since in HandleLinkBegin we
+ // don't know the title.
+ let lmPromise = PlacesUtils.livemarks.addLivemark({
+ "title": frame.previousText,
+ "parentId": frame.containerId,
+ "index": PlacesUtils.bookmarks.DEFAULT_INDEX,
+ "feedURI": frame.previousFeed,
+ "siteURI": frame.previousLink,
+ "source": this._source,
+ });
+ this._importPromises.push(lmPromise);
+ } else if (frame.previousLink) {
+ // This is a common bookmark.
+ PlacesUtils.bookmarks.setItemTitle(frame.previousId,
+ frame.previousText,
+ this._source);
+ }
+ } catch (e) {
+ }
+
+
+ // Set last modified date as the last change.
+ if (frame.previousId > 0 && frame.previousLastModifiedDate > 0) {
+ try {
+ PlacesUtils.bookmarks.setItemLastModified(frame.previousId,
+ frame.previousLastModifiedDate,
+ this._source);
+ } catch (e) {
+ }
+ // Note: don't clear previousLastModifiedDate, because if this item has a
+ // description, we'll need to set it again.
+ }
+
+ frame.previousText = "";
+
+ },
+
+ _openContainer: function openContainer(aElt) {
+ if (aElt.namespaceURI != "http://www.w3.org/1999/xhtml") {
+ return;
+ }
+ switch (aElt.localName) {
+ case "h1":
+ this._handleHead1Begin(aElt);
+ break;
+ case "h2":
+ case "h3":
+ case "h4":
+ case "h5":
+ case "h6":
+ this._handleHeadBegin(aElt);
+ break;
+ case "a":
+ this._handleLinkBegin(aElt);
+ break;
+ case "dl":
+ case "ul":
+ case "menu":
+ this._handleContainerBegin();
+ break;
+ case "dd":
+ this._curFrame.inDescription = true;
+ break;
+ case "hr":
+ this._closeContainer(aElt);
+ this._handleSeparator(aElt);
+ break;
+ }
+ },
+
+ _closeContainer: function closeContainer(aElt) {
+ let frame = this._curFrame;
+
+ // see the comment for the definition of inDescription. Basically, we commit
+ // any text in previousText to the description of the node/folder if there
+ // is any.
+ if (frame.inDescription) {
+ // NOTE ES5 trim trims more than the previous C++ trim.
+ frame.previousText = frame.previousText.trim(); // important
+ if (frame.previousText) {
+
+ let itemId = !frame.previousLink ? frame.containerId
+ : frame.previousId;
+
+ try {
+ if (!PlacesUtils.annotations.itemHasAnnotation(itemId, DESCRIPTION_ANNO)) {
+ PlacesUtils.annotations.setItemAnnotation(itemId,
+ DESCRIPTION_ANNO,
+ frame.previousText,
+ 0,
+ PlacesUtils.annotations.EXPIRE_NEVER,
+ this._source);
+ }
+ } catch (e) {
+ }
+ frame.previousText = "";
+
+ // Set last-modified a 2nd time for all items with descriptions
+ // we need to set last-modified as the *last* step in processing
+ // any item type in the bookmarks.html file, so that we do
+ // not overwrite the imported value. for items without descriptions,
+ // setting this value after setting the item title is that
+ // last point at which we can save this value before it gets reset.
+ // for items with descriptions, it must set after that point.
+ // however, at the point at which we set the title, there's no way
+ // to determine if there will be a description following,
+ // so we need to set the last-modified-date at both places.
+
+ let lastModified;
+ if (!frame.previousLink) {
+ lastModified = this._previousFrame.previousLastModifiedDate;
+ } else {
+ lastModified = frame.previousLastModifiedDate;
+ }
+
+ if (itemId > 0 && lastModified > 0) {
+ PlacesUtils.bookmarks.setItemLastModified(itemId, lastModified,
+ this._source);
+ }
+ }
+ frame.inDescription = false;
+ }
+
+ if (aElt.namespaceURI != "http://www.w3.org/1999/xhtml") {
+ return;
+ }
+ switch (aElt.localName) {
+ case "dl":
+ case "ul":
+ case "menu":
+ this._handleContainerEnd();
+ break;
+ case "dt":
+ break;
+ case "h1":
+ // ignore
+ break;
+ case "h2":
+ case "h3":
+ case "h4":
+ case "h5":
+ case "h6":
+ this._handleHeadEnd();
+ break;
+ case "a":
+ this._handleLinkEnd();
+ break;
+ default:
+ break;
+ }
+ },
+
+ _appendText: function appendText(str) {
+ this._curFrame.previousText += str;
+ },
+
+ /**
+ * data is a string that is a data URI for the favicon. Our job is to
+ * decode it and store it in the favicon service.
+ *
+ * When aIconURI is non-null, we will use that as the URI of the favicon
+ * when storing in the favicon service.
+ *
+ * When aIconURI is null, we have to make up a URI for this favicon so that
+ * it can be stored in the service. The real one will be set the next time
+ * the user visits the page. Our made up one should get expired when the
+ * page no longer references it.
+ */
+ _setFaviconForURI: function setFaviconForURI(aPageURI, aIconURI, aData) {
+ // if the input favicon URI is a chrome: URI, then we just save it and don't
+ // worry about data
+ if (aIconURI) {
+ if (aIconURI.schemeIs("chrome")) {
+ PlacesUtils.favicons.setAndFetchFaviconForPage(aPageURI, aIconURI, false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+ Services.scriptSecurityManager.getSystemPrincipal());
+
+ return;
+ }
+ }
+
+ // some bookmarks have placeholder URIs that contain just "data:"
+ // ignore these
+ if (aData.length <= 5) {
+ return;
+ }
+
+ let faviconURI;
+ if (aIconURI) {
+ faviconURI = aIconURI;
+ } else {
+ // Make up a favicon URI for this page. Later, we'll make sure that this
+ // favicon URI is always associated with local favicon data, so that we
+ // don't load this URI from the network.
+ let faviconSpec = "http://www.mozilla.org/2005/made-up-favicon/"
+ + serialNumber
+ + "-"
+ + new Date().getTime();
+ faviconURI = NetUtil.newURI(faviconSpec);
+ serialNumber++;
+ }
+
+ // This could fail if the favicon is bigger than defined limit, in such a
+ // case neither the favicon URI nor the favicon data will be saved. If the
+ // bookmark is visited again later, the URI and data will be fetched.
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(faviconURI, aData, 0,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ PlacesUtils.favicons.setAndFetchFaviconForPage(aPageURI, faviconURI, false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ },
+
+ /**
+ * Converts a string date in seconds to an int date in microseconds
+ */
+ _convertImportedDateToInternalDate: function convertImportedDateToInternalDate(aDate) {
+ if (aDate && !isNaN(aDate)) {
+ return parseInt(aDate) * 1000000; // in bookmarks.html this value is in seconds, not microseconds
+ }
+ return Date.now();
+ },
+
+ runBatched: function runBatched(aDoc) {
+ if (!aDoc) {
+ return;
+ }
+
+ if (this._isImportDefaults) {
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.bookmarksMenuFolderId, this._source);
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.toolbarFolderId, this._source);
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId, this._source);
+ }
+
+ let current = aDoc;
+ let next;
+ for (;;) {
+ switch (current.nodeType) {
+ case Ci.nsIDOMNode.ELEMENT_NODE:
+ this._openContainer(current);
+ break;
+ case Ci.nsIDOMNode.TEXT_NODE:
+ this._appendText(current.data);
+ break;
+ }
+ if ((next = current.firstChild)) {
+ current = next;
+ continue;
+ }
+ for (;;) {
+ if (current.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
+ this._closeContainer(current);
+ }
+ if (current == aDoc) {
+ return;
+ }
+ if ((next = current.nextSibling)) {
+ current = next;
+ break;
+ }
+ current = current.parentNode;
+ }
+ }
+ },
+
+ _walkTreeForImport: function walkTreeForImport(aDoc) {
+ PlacesUtils.bookmarks.runInBatchMode(this, aDoc);
+ },
+
+ importFromURL: Task.async(function* (href) {
+ this._importPromises = [];
+ yield new Promise((resolve, reject) => {
+ let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ xhr.onload = () => {
+ try {
+ this._walkTreeForImport(xhr.responseXML);
+ resolve();
+ } catch (e) {
+ reject(e);
+ }
+ };
+ xhr.onabort = xhr.onerror = xhr.ontimeout = () => {
+ reject(new Error("xmlhttprequest failed"));
+ };
+ xhr.open("GET", href);
+ xhr.responseType = "document";
+ xhr.overrideMimeType("text/html");
+ xhr.send();
+ });
+ // TODO (bug 1095427) once converted to the new bookmarks API, methods will
+ // yield, so this hack should not be needed anymore.
+ try {
+ yield Promise.all(this._importPromises);
+ } finally {
+ delete this._importPromises;
+ }
+ }),
+};
+
+function BookmarkExporter(aBookmarksTree) {
+ // Create a map of the roots.
+ let rootsMap = new Map();
+ for (let child of aBookmarksTree.children) {
+ if (child.root)
+ rootsMap.set(child.root, child);
+ }
+
+ // For backwards compatibility reasons the bookmarks menu is the root, while
+ // the bookmarks toolbar and unfiled bookmarks will be child items.
+ this._root = rootsMap.get("bookmarksMenuFolder");
+
+ for (let key of [ "toolbarFolder", "unfiledBookmarksFolder" ]) {
+ let root = rootsMap.get(key);
+ if (root.children && root.children.length > 0) {
+ if (!this._root.children)
+ this._root.children = [];
+ this._root.children.push(root);
+ }
+ }
+}
+
+BookmarkExporter.prototype = {
+ exportToFile: function exportToFile(aFilePath) {
+ return Task.spawn(function* () {
+ // Create a file that can be accessed by the current user only.
+ let out = FileUtils.openAtomicFileOutputStream(new FileUtils.File(aFilePath));
+ try {
+ // We need a buffered output stream for performance. See bug 202477.
+ let bufferedOut = Cc["@mozilla.org/network/buffered-output-stream;1"]
+ .createInstance(Ci.nsIBufferedOutputStream);
+ bufferedOut.init(out, 4096);
+ try {
+ // Write bookmarks in UTF-8.
+ this._converterOut = Cc["@mozilla.org/intl/converter-output-stream;1"]
+ .createInstance(Ci.nsIConverterOutputStream);
+ this._converterOut.init(bufferedOut, "utf-8", 0, 0);
+ try {
+ this._writeHeader();
+ yield this._writeContainer(this._root);
+ // Retain the target file on success only.
+ bufferedOut.QueryInterface(Ci.nsISafeOutputStream).finish();
+ } finally {
+ this._converterOut.close();
+ this._converterOut = null;
+ }
+ } finally {
+ bufferedOut.close();
+ }
+ } finally {
+ out.close();
+ }
+ }.bind(this));
+ },
+
+ _converterOut: null,
+
+ _write: function (aText) {
+ this._converterOut.writeString(aText || "");
+ },
+
+ _writeAttribute: function (aName, aValue) {
+ this._write(' ' + aName + '="' + aValue + '"');
+ },
+
+ _writeLine: function (aText) {
+ if (Services.sysinfo.getProperty("name") == "Windows_NT") {
+ // Write CRLF line endings on Windows
+ this._write(aText + "\r\n");
+ } else {
+ this._write(aText + "\n");
+ }
+ },
+
+ _writeHeader: function () {
+ this._writeLine("<!DOCTYPE NETSCAPE-Bookmark-file-1>");
+ this._writeLine("<!-- This is an automatically generated file.");
+ this._writeLine(" It will be read and overwritten.");
+ this._writeLine(" DO NOT EDIT! -->");
+ this._writeLine('<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">');
+ this._writeLine(`<META HTTP-EQUIV="Content-Security-Policy"
+ CONTENT="default-src 'self'; script-src 'none'; img-src data: *; object-src 'none'"></META>`);
+ this._writeLine("<TITLE>Bookmarks</TITLE>");
+ },
+
+ *_writeContainer(aItem, aIndent = "") {
+ if (aItem == this._root) {
+ this._writeLine("<H1>" + escapeHtmlEntities(this._root.title) + "</H1>");
+ this._writeLine("");
+ }
+ else {
+ this._write(aIndent + "<DT><H3");
+ this._writeDateAttributes(aItem);
+
+ if (aItem.root === "toolbarFolder")
+ this._writeAttribute("PERSONAL_TOOLBAR_FOLDER", "true");
+ else if (aItem.root === "unfiledBookmarksFolder")
+ this._writeAttribute("UNFILED_BOOKMARKS_FOLDER", "true");
+ this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</H3>");
+ }
+
+ this._writeDescription(aItem, aIndent);
+
+ this._writeLine(aIndent + "<DL><p>");
+ if (aItem.children)
+ yield this._writeContainerContents(aItem, aIndent);
+ if (aItem == this._root)
+ this._writeLine(aIndent + "</DL>");
+ else
+ this._writeLine(aIndent + "</DL><p>");
+ },
+
+ *_writeContainerContents(aItem, aIndent) {
+ let localIndent = aIndent + EXPORT_INDENT;
+
+ for (let child of aItem.children) {
+ if (child.annos && child.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI)) {
+ this._writeLivemark(child, localIndent);
+ } else if (child.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
+ yield this._writeContainer(child, localIndent);
+ } else if (child.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
+ this._writeSeparator(child, localIndent);
+ } else {
+ yield this._writeItem(child, localIndent);
+ }
+ }
+ },
+
+ _writeSeparator: function (aItem, aIndent) {
+ this._write(aIndent + "<HR");
+ // We keep exporting separator titles, but don't support them anymore.
+ if (aItem.title)
+ this._writeAttribute("NAME", escapeHtmlEntities(aItem.title));
+ this._write(">");
+ },
+
+ _writeLivemark: function (aItem, aIndent) {
+ this._write(aIndent + "<DT><A");
+ let feedSpec = aItem.annos.find(anno => anno.name == PlacesUtils.LMANNO_FEEDURI).value;
+ this._writeAttribute("FEEDURL", escapeUrl(feedSpec));
+ let siteSpecAnno = aItem.annos.find(anno => anno.name == PlacesUtils.LMANNO_SITEURI);
+ if (siteSpecAnno)
+ this._writeAttribute("HREF", escapeUrl(siteSpecAnno.value));
+ this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</A>");
+ this._writeDescription(aItem, aIndent);
+ },
+
+ *_writeItem(aItem, aIndent) {
+ try {
+ NetUtil.newURI(aItem.uri);
+ } catch (ex) {
+ // If the item URI is invalid, skip the item instead of failing later.
+ return;
+ }
+
+ this._write(aIndent + "<DT><A");
+ this._writeAttribute("HREF", escapeUrl(aItem.uri));
+ this._writeDateAttributes(aItem);
+ yield this._writeFaviconAttribute(aItem);
+
+ if (aItem.keyword) {
+ this._writeAttribute("SHORTCUTURL", escapeHtmlEntities(aItem.keyword));
+ if (aItem.postData)
+ this._writeAttribute("POST_DATA", escapeHtmlEntities(aItem.postData));
+ }
+
+ if (aItem.annos && aItem.annos.some(anno => anno.name == LOAD_IN_SIDEBAR_ANNO))
+ this._writeAttribute("WEB_PANEL", "true");
+ if (aItem.charset)
+ this._writeAttribute("LAST_CHARSET", escapeHtmlEntities(aItem.charset));
+ if (aItem.tags)
+ this._writeAttribute("TAGS", escapeHtmlEntities(aItem.tags));
+ this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</A>");
+ this._writeDescription(aItem, aIndent);
+ },
+
+ _writeDateAttributes: function (aItem) {
+ if (aItem.dateAdded)
+ this._writeAttribute("ADD_DATE",
+ Math.floor(aItem.dateAdded / MICROSEC_PER_SEC));
+ if (aItem.lastModified)
+ this._writeAttribute("LAST_MODIFIED",
+ Math.floor(aItem.lastModified / MICROSEC_PER_SEC));
+ },
+
+ *_writeFaviconAttribute(aItem) {
+ if (!aItem.iconuri)
+ return;
+ let favicon;
+ try {
+ favicon = yield PlacesUtils.promiseFaviconData(aItem.uri);
+ } catch (ex) {
+ Components.utils.reportError("Unexpected Error trying to fetch icon data");
+ return;
+ }
+
+ this._writeAttribute("ICON_URI", escapeUrl(favicon.uri.spec));
+
+ if (!favicon.uri.schemeIs("chrome") && favicon.dataLen > 0) {
+ let faviconContents = "data:image/png;base64," +
+ base64EncodeString(String.fromCharCode.apply(String, favicon.data));
+ this._writeAttribute("ICON", faviconContents);
+ }
+ },
+
+ _writeDescription: function (aItem, aIndent) {
+ let descriptionAnno = aItem.annos &&
+ aItem.annos.find(anno => anno.name == DESCRIPTION_ANNO);
+ if (descriptionAnno)
+ this._writeLine(aIndent + "<DD>" + escapeHtmlEntities(descriptionAnno.value));
+ }
+};
diff --git a/components/places/src/BookmarkJSONUtils.jsm b/components/places/src/BookmarkJSONUtils.jsm
new file mode 100644
index 000000000..f212ba07d
--- /dev/null
+++ b/components/places/src/BookmarkJSONUtils.jsm
@@ -0,0 +1,581 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [ "BookmarkJSONUtils" ];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/PromiseUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
+ "resource://gre/modules/PlacesBackups.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => new TextDecoder());
+XPCOMUtils.defineLazyGetter(this, "gTextEncoder", () => new TextEncoder());
+
+/**
+ * Generates an hash for the given string.
+ *
+ * @note The generated hash is returned in base64 form. Mind the fact base64
+ * is case-sensitive if you are going to reuse this code.
+ */
+function generateHash(aString) {
+ let cryptoHash = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ cryptoHash.init(Ci.nsICryptoHash.MD5);
+ let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stringStream.data = aString;
+ cryptoHash.updateFromStream(stringStream, -1);
+ // base64 allows the '/' char, but we can't use it for filenames.
+ return cryptoHash.finish(true).replace(/\//g, "-");
+}
+
+this.BookmarkJSONUtils = Object.freeze({
+ /**
+ * Import bookmarks from a url.
+ *
+ * @param aSpec
+ * url of the bookmark data.
+ * @param aReplace
+ * Boolean if true, replace existing bookmarks, else merge.
+ *
+ * @return {Promise}
+ * @resolves When the new bookmarks have been created.
+ * @rejects JavaScript exception.
+ */
+ importFromURL: function BJU_importFromURL(aSpec, aReplace) {
+ return Task.spawn(function* () {
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN);
+ try {
+ let importer = new BookmarkImporter(aReplace);
+ yield importer.importFromURL(aSpec);
+
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS);
+ } catch (ex) {
+ Cu.reportError("Failed to restore bookmarks from " + aSpec + ": " + ex);
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED);
+ }
+ });
+ },
+
+ /**
+ * Restores bookmarks and tags from a JSON file.
+ * @note any item annotated with "places/excludeFromBackup" won't be removed
+ * before executing the restore.
+ *
+ * @param aFilePath
+ * OS.File path string of bookmarks in JSON or JSONlz4 format to be restored.
+ * @param aReplace
+ * Boolean if true, replace existing bookmarks, else merge.
+ *
+ * @return {Promise}
+ * @resolves When the new bookmarks have been created.
+ * @rejects JavaScript exception.
+ * @deprecated passing an nsIFile is deprecated
+ */
+ importFromFile: function BJU_importFromFile(aFilePath, aReplace) {
+ if (aFilePath instanceof Ci.nsIFile) {
+ Deprecated.warning("Passing an nsIFile to BookmarksJSONUtils.importFromFile " +
+ "is deprecated. Please use an OS.File path string instead.",
+ "https://developer.mozilla.org/docs/JavaScript_OS.File");
+ aFilePath = aFilePath.path;
+ }
+
+ return Task.spawn(function* () {
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN);
+ try {
+ if (!(yield OS.File.exists(aFilePath)))
+ throw new Error("Cannot restore from nonexisting json file");
+
+ let importer = new BookmarkImporter(aReplace);
+ if (aFilePath.endsWith("jsonlz4")) {
+ yield importer.importFromCompressedFile(aFilePath);
+ } else {
+ yield importer.importFromURL(OS.Path.toFileURI(aFilePath));
+ }
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS);
+ } catch (ex) {
+ Cu.reportError("Failed to restore bookmarks from " + aFilePath + ": " + ex);
+ notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED);
+ throw ex;
+ }
+ });
+ },
+
+ /**
+ * Serializes bookmarks using JSON, and writes to the supplied file path.
+ *
+ * @param aFilePath
+ * OS.File path string for the bookmarks file to be created.
+ * @param [optional] aOptions
+ * Object containing options for the export:
+ * - failIfHashIs: if the generated file would have the same hash
+ * defined here, will reject with ex.becauseSameHash
+ * - compress: if true, writes file using lz4 compression
+ * @return {Promise}
+ * @resolves once the file has been created, to an object with the
+ * following properties:
+ * - count: number of exported bookmarks
+ * - hash: file hash for contents comparison
+ * @rejects JavaScript exception.
+ * @deprecated passing an nsIFile is deprecated
+ */
+ exportToFile: function BJU_exportToFile(aFilePath, aOptions={}) {
+ if (aFilePath instanceof Ci.nsIFile) {
+ Deprecated.warning("Passing an nsIFile to BookmarksJSONUtils.exportToFile " +
+ "is deprecated. Please use an OS.File path string instead.",
+ "https://developer.mozilla.org/docs/JavaScript_OS.File");
+ aFilePath = aFilePath.path;
+ }
+ return Task.spawn(function* () {
+ let [bookmarks, count] = yield PlacesBackups.getBookmarksTree();
+ let startTime = Date.now();
+ let jsonString = JSON.stringify(bookmarks);
+
+ let hash = generateHash(jsonString);
+
+ if (hash === aOptions.failIfHashIs) {
+ let e = new Error("Hash conflict");
+ e.becauseSameHash = true;
+ throw e;
+ }
+
+ // Do not write to the tmp folder, otherwise if it has a different
+ // filesystem writeAtomic will fail. Eventual dangling .tmp files should
+ // be cleaned up by the caller.
+ let writeOptions = { tmpPath: OS.Path.join(aFilePath + ".tmp") };
+ if (aOptions.compress)
+ writeOptions.compression = "lz4";
+
+ yield OS.File.writeAtomic(aFilePath, jsonString, writeOptions);
+ return { count: count, hash: hash };
+ });
+ }
+});
+
+function BookmarkImporter(aReplace) {
+ this._replace = aReplace;
+ // The bookmark change source, used to determine the sync status and change
+ // counter.
+ this._source = aReplace ? PlacesUtils.bookmarks.SOURCE_IMPORT_REPLACE :
+ PlacesUtils.bookmarks.SOURCE_IMPORT;
+}
+BookmarkImporter.prototype = {
+ /**
+ * Import bookmarks from a url.
+ *
+ * @param aSpec
+ * url of the bookmark data.
+ *
+ * @return {Promise}
+ * @resolves When the new bookmarks have been created.
+ * @rejects JavaScript exception.
+ */
+ importFromURL(spec) {
+ return new Promise((resolve, reject) => {
+ let streamObserver = {
+ onStreamComplete: (aLoader, aContext, aStatus, aLength, aResult) => {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ try {
+ let jsonString = converter.convertFromByteArray(aResult,
+ aResult.length);
+ resolve(this.importFromJSON(jsonString));
+ } catch (ex) {
+ Cu.reportError("Failed to import from URL: " + ex);
+ reject(ex);
+ }
+ }
+ };
+
+ let uri = NetUtil.newURI(spec);
+ let channel = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true
+ });
+ let streamLoader = Cc["@mozilla.org/network/stream-loader;1"]
+ .createInstance(Ci.nsIStreamLoader);
+ streamLoader.init(streamObserver);
+ channel.asyncOpen2(streamLoader);
+ });
+ },
+
+ /**
+ * Import bookmarks from a compressed file.
+ *
+ * @param aFilePath
+ * OS.File path string of the bookmark data.
+ *
+ * @return {Promise}
+ * @resolves When the new bookmarks have been created.
+ * @rejects JavaScript exception.
+ */
+ importFromCompressedFile: function* BI_importFromCompressedFile(aFilePath) {
+ let aResult = yield OS.File.read(aFilePath, { compression: "lz4" });
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ let jsonString = converter.convertFromByteArray(aResult, aResult.length);
+ yield this.importFromJSON(jsonString);
+ },
+
+ /**
+ * Import bookmarks from a JSON string.
+ *
+ * @param aString
+ * JSON string of serialized bookmark data.
+ */
+ importFromJSON: Task.async(function* (aString) {
+ this._importPromises = [];
+ let deferred = PromiseUtils.defer();
+ let nodes =
+ PlacesUtils.unwrapNodes(aString, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
+
+ if (nodes.length == 0 || !nodes[0].children ||
+ nodes[0].children.length == 0) {
+ deferred.resolve(); // Nothing to restore
+ } else {
+ // Ensure tag folder gets processed last
+ nodes[0].children.sort(function sortRoots(aNode, bNode) {
+ if (aNode.root && aNode.root == "tagsFolder")
+ return 1;
+ if (bNode.root && bNode.root == "tagsFolder")
+ return -1;
+ return 0;
+ });
+
+ let batch = {
+ nodes: nodes[0].children,
+ runBatched: function runBatched() {
+ if (this._replace) {
+ // Get roots excluded from the backup, we will not remove them
+ // before restoring.
+ let excludeItems = PlacesUtils.annotations.getItemsWithAnnotation(
+ PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO);
+ // Delete existing children of the root node, excepting:
+ // 1. special folders: delete the child nodes
+ // 2. tags folder: untag via the tagging api
+ let root = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
+ false, false).root;
+ let childIds = [];
+ for (let i = 0; i < root.childCount; i++) {
+ let childId = root.getChild(i).itemId;
+ if (!excludeItems.includes(childId) &&
+ childId != PlacesUtils.tagsFolderId) {
+ childIds.push(childId);
+ }
+ }
+ root.containerOpen = false;
+
+ for (let i = 0; i < childIds.length; i++) {
+ let rootItemId = childIds[i];
+ if (PlacesUtils.isRootItem(rootItemId)) {
+ PlacesUtils.bookmarks.removeFolderChildren(rootItemId,
+ this._source);
+ } else {
+ PlacesUtils.bookmarks.removeItem(rootItemId, this._source);
+ }
+ }
+ }
+
+ let searchIds = [];
+ let folderIdMap = [];
+
+ for (let node of batch.nodes) {
+ if (!node.children || node.children.length == 0)
+ continue; // Nothing to restore for this root
+
+ if (node.root) {
+ let container = PlacesUtils.placesRootId; // Default to places root
+ switch (node.root) {
+ case "bookmarksMenuFolder":
+ container = PlacesUtils.bookmarksMenuFolderId;
+ break;
+ case "tagsFolder":
+ container = PlacesUtils.tagsFolderId;
+ break;
+ case "unfiledBookmarksFolder":
+ container = PlacesUtils.unfiledBookmarksFolderId;
+ break;
+ case "toolbarFolder":
+ container = PlacesUtils.toolbarFolderId;
+ break;
+ case "mobileFolder":
+ container = PlacesUtils.mobileFolderId;
+ break;
+ }
+
+ // Insert the data into the db
+ for (let child of node.children) {
+ let index = child.index;
+ let [folders, searches] =
+ this.importJSONNode(child, container, index, 0);
+ for (let i = 0; i < folders.length; i++) {
+ if (folders[i])
+ folderIdMap[i] = folders[i];
+ }
+ searchIds = searchIds.concat(searches);
+ }
+ } else {
+ let [folders, searches] = this.importJSONNode(
+ node, PlacesUtils.placesRootId, node.index, 0);
+ for (let i = 0; i < folders.length; i++) {
+ if (folders[i])
+ folderIdMap[i] = folders[i];
+ }
+ searchIds = searchIds.concat(searches);
+ }
+ }
+
+ // Fixup imported place: uris that contain folders
+ for (let id of searchIds) {
+ let oldURI = PlacesUtils.bookmarks.getBookmarkURI(id);
+ let uri = fixupQuery(oldURI, folderIdMap);
+ if (!uri.equals(oldURI)) {
+ PlacesUtils.bookmarks.changeBookmarkURI(id, uri, this._source);
+ }
+ }
+
+ deferred.resolve();
+ }.bind(this)
+ };
+
+ PlacesUtils.bookmarks.runInBatchMode(batch, null);
+ }
+ yield deferred.promise;
+ // TODO (bug 1095426) once converted to the new bookmarks API, methods will
+ // yield, so this hack should not be needed anymore.
+ try {
+ yield Promise.all(this._importPromises);
+ } finally {
+ delete this._importPromises;
+ }
+ }),
+
+ /**
+ * Takes a JSON-serialized node and inserts it into the db.
+ *
+ * @param aData
+ * The unwrapped data blob of dropped or pasted data.
+ * @param aContainer
+ * The container the data was dropped or pasted into
+ * @param aIndex
+ * The index within the container the item was dropped or pasted at
+ * @return an array containing of maps of old folder ids to new folder ids,
+ * and an array of saved search ids that need to be fixed up.
+ * eg: [[[oldFolder1, newFolder1]], [search1]]
+ */
+ importJSONNode: function BI_importJSONNode(aData, aContainer, aIndex,
+ aGrandParentId) {
+ let folderIdMap = [];
+ let searchIds = [];
+ let id = -1;
+ switch (aData.type) {
+ case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
+ if (aContainer == PlacesUtils.tagsFolderId) {
+ // Node is a tag
+ if (aData.children) {
+ for (let child of aData.children) {
+ try {
+ PlacesUtils.tagging.tagURI(
+ NetUtil.newURI(child.uri), [aData.title], this._source);
+ } catch (ex) {
+ // Invalid tag child, skip it
+ }
+ }
+ return [folderIdMap, searchIds];
+ }
+ } else if (aData.annos &&
+ aData.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI)) {
+ // Node is a livemark
+ let feedURI = null;
+ let siteURI = null;
+ aData.annos = aData.annos.filter(function(aAnno) {
+ switch (aAnno.name) {
+ case PlacesUtils.LMANNO_FEEDURI:
+ feedURI = NetUtil.newURI(aAnno.value);
+ return false;
+ case PlacesUtils.LMANNO_SITEURI:
+ siteURI = NetUtil.newURI(aAnno.value);
+ return false;
+ default:
+ return true;
+ }
+ });
+
+ if (feedURI) {
+ let lmPromise = PlacesUtils.livemarks.addLivemark({
+ title: aData.title,
+ feedURI: feedURI,
+ parentId: aContainer,
+ index: aIndex,
+ lastModified: aData.lastModified,
+ siteURI: siteURI,
+ guid: aData.guid,
+ source: this._source
+ }).then(aLivemark => {
+ let id = aLivemark.id;
+ if (aData.dateAdded)
+ PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded,
+ this._source);
+ if (aData.annos && aData.annos.length)
+ PlacesUtils.setAnnotationsForItem(id, aData.annos,
+ this._source);
+ });
+ this._importPromises.push(lmPromise);
+ }
+ } else {
+ let isMobileFolder = aData.annos &&
+ aData.annos.some(anno => anno.name == PlacesUtils.MOBILE_ROOT_ANNO);
+ if (isMobileFolder) {
+ // Mobile bookmark folders are special: we move their children to
+ // the mobile root instead of importing them. We also rewrite
+ // queries to use the special folder ID, and ignore generic
+ // properties like timestamps and annotations set on the folder.
+ id = PlacesUtils.mobileFolderId;
+ } else {
+ // For other folders, set `id` so that we can import timestamps
+ // and annotations at the end of this function.
+ id = PlacesUtils.bookmarks.createFolder(
+ aContainer, aData.title, aIndex, aData.guid, this._source);
+ }
+ folderIdMap[aData.id] = id;
+ // Process children
+ if (aData.children) {
+ for (let i = 0; i < aData.children.length; i++) {
+ let child = aData.children[i];
+ let [folders, searches] =
+ this.importJSONNode(child, id, i, aContainer);
+ for (let j = 0; j < folders.length; j++) {
+ if (folders[j])
+ folderIdMap[j] = folders[j];
+ }
+ searchIds = searchIds.concat(searches);
+ }
+ }
+ }
+ break;
+ case PlacesUtils.TYPE_X_MOZ_PLACE:
+ id = PlacesUtils.bookmarks.insertBookmark(
+ aContainer, NetUtil.newURI(aData.uri), aIndex, aData.title, aData.guid, this._source);
+ if (aData.keyword) {
+ // POST data could be set in 2 ways:
+ // 1. new backups have a postData property
+ // 2. old backups have an item annotation
+ let postDataAnno = aData.annos &&
+ aData.annos.find(anno => anno.name == PlacesUtils.POST_DATA_ANNO);
+ let postData = aData.postData || (postDataAnno && postDataAnno.value);
+ let kwPromise = PlacesUtils.keywords.insert({ keyword: aData.keyword,
+ url: aData.uri,
+ postData,
+ source: this._source });
+ this._importPromises.push(kwPromise);
+ }
+ if (aData.tags) {
+ let tags = aData.tags.split(",").filter(aTag =>
+ aTag.length <= Ci.nsITaggingService.MAX_TAG_LENGTH);
+ if (tags.length) {
+ try {
+ PlacesUtils.tagging.tagURI(NetUtil.newURI(aData.uri), tags, this._source);
+ } catch (ex) {
+ // Invalid tag child, skip it.
+ Cu.reportError(`Unable to set tags "${tags.join(", ")}" for ${aData.uri}: ${ex}`);
+ }
+ }
+ }
+ if (aData.charset) {
+ PlacesUtils.annotations.setPageAnnotation(
+ NetUtil.newURI(aData.uri), PlacesUtils.CHARSET_ANNO, aData.charset,
+ 0, Ci.nsIAnnotationService.EXPIRE_NEVER);
+ }
+ if (aData.uri.substr(0, 6) == "place:")
+ searchIds.push(id);
+ if (aData.icon) {
+ try {
+ // Create a fake faviconURI to use (FIXME: bug 523932)
+ let faviconURI = NetUtil.newURI("fake-favicon-uri:" + aData.uri);
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ faviconURI, aData.icon, 0,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ NetUtil.newURI(aData.uri), faviconURI, false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ } catch (ex) {
+ Components.utils.reportError("Failed to import favicon data:" + ex);
+ }
+ }
+ if (aData.iconUri) {
+ try {
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ NetUtil.newURI(aData.uri), NetUtil.newURI(aData.iconUri), false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+ Services.scriptSecurityManager.getSystemPrincipal());
+ } catch (ex) {
+ Components.utils.reportError("Failed to import favicon URI:" + ex);
+ }
+ }
+ break;
+ case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
+ id = PlacesUtils.bookmarks.insertSeparator(aContainer, aIndex, aData.guid, this._source);
+ break;
+ default:
+ // Unknown node type
+ }
+
+ // Set generic properties, valid for all nodes except tags and the mobile
+ // root.
+ if (id != -1 && id != PlacesUtils.mobileFolderId &&
+ aContainer != PlacesUtils.tagsFolderId &&
+ aGrandParentId != PlacesUtils.tagsFolderId) {
+ if (aData.dateAdded)
+ PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded,
+ this._source);
+ if (aData.lastModified)
+ PlacesUtils.bookmarks.setItemLastModified(id, aData.lastModified,
+ this._source);
+ if (aData.annos && aData.annos.length)
+ PlacesUtils.setAnnotationsForItem(id, aData.annos, this._source);
+ }
+
+ return [folderIdMap, searchIds];
+ }
+}
+
+function notifyObservers(topic) {
+ Services.obs.notifyObservers(null, topic, "json");
+}
+
+/**
+ * Replaces imported folder ids with their local counterparts in a place: URI.
+ *
+ * @param aURI
+ * A place: URI with folder ids.
+ * @param aFolderIdMap
+ * An array mapping old folder id to new folder ids.
+ * @returns the fixed up URI if all matched. If some matched, it returns
+ * the URI with only the matching folders included. If none matched
+ * it returns the input URI unchanged.
+ */
+function fixupQuery(aQueryURI, aFolderIdMap) {
+ let convert = function(str, p1, offset, s) {
+ return "folder=" + aFolderIdMap[p1];
+ }
+ let stringURI = aQueryURI.spec.replace(/folder=([0-9]+)/g, convert);
+
+ return NetUtil.newURI(stringURI);
+}
diff --git a/components/places/src/Bookmarks.jsm b/components/places/src/Bookmarks.jsm
new file mode 100644
index 000000000..835b4fc62
--- /dev/null
+++ b/components/places/src/Bookmarks.jsm
@@ -0,0 +1,1536 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 module provides an asynchronous API for managing bookmarks.
+ *
+ * Bookmarks are organized in a tree structure, and include URLs, folders and
+ * separators. Multiple bookmarks for the same URL are allowed.
+ *
+ * Note that if you are handling bookmarks operations in the UI, you should
+ * not use this API directly, but rather use PlacesTransactions.jsm, so that
+ * any operation is undo/redo-able.
+ *
+ * Each bookmark-item is represented by an object having the following
+ * properties:
+ *
+ * - guid (string)
+ * The globally unique identifier of the item.
+ * - parentGuid (string)
+ * The globally unique identifier of the folder containing the item.
+ * This will be an empty string for the Places root folder.
+ * - index (number)
+ * The 0-based position of the item in the parent folder.
+ * - dateAdded (Date)
+ * The time at which the item was added.
+ * - lastModified (Date)
+ * The time at which the item was last modified.
+ * - type (number)
+ * The item's type, either TYPE_BOOKMARK, TYPE_FOLDER or TYPE_SEPARATOR.
+ *
+ * The following properties are only valid for URLs or folders.
+ *
+ * - title (string)
+ * The item's title, if any. Empty titles and null titles are considered
+ * the same, and the property is unset on retrieval in such a case.
+ * Titles longer than DB_TITLE_LENGTH_MAX will be truncated.
+ *
+ * The following properties are only valid for URLs:
+ *
+ * - url (URL, href or nsIURI)
+ * The item's URL. Note that while input objects can contains either
+ * an URL object, an href string, or an nsIURI, output objects will always
+ * contain an URL object.
+ * An URL cannot be longer than DB_URL_LENGTH_MAX, methods will throw if a
+ * longer value is provided.
+ *
+ * Each successful operation notifies through the nsINavBookmarksObserver
+ * interface. To listen to such notifications you must register using
+ * nsINavBookmarksService addObserver and removeObserver methods.
+ * Note that bookmark addition or order changes won't notify onItemMoved for
+ * items that have their indexes changed.
+ * Similarly, lastModified changes not done explicitly (like changing another
+ * property) won't fire an onItemChanged notification for the lastModified
+ * property.
+ * @see nsINavBookmarkObserver
+ */
+
+this.EXPORTED_SYMBOLS = [ "Bookmarks" ];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.importGlobalProperties(["URL"]);
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
+ "resource://gre/modules/Sqlite.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
+ "resource://gre/modules/PlacesSyncUtils.jsm");
+
+const MATCH_ANYWHERE_UNMODIFIED = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE_UNMODIFIED;
+const BEHAVIOR_BOOKMARK = Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;
+
+var Bookmarks = Object.freeze({
+ /**
+ * Item's type constants.
+ * These should stay consistent with nsINavBookmarksService.idl
+ */
+ TYPE_BOOKMARK: 1,
+ TYPE_FOLDER: 2,
+ TYPE_SEPARATOR: 3,
+
+ /**
+ * Default index used to append a bookmark-item at the end of a folder.
+ * This should stay consistent with nsINavBookmarksService.idl
+ */
+ DEFAULT_INDEX: -1,
+
+ /**
+ * Bookmark change source constants, passed as optional properties and
+ * forwarded to observers. See nsINavBookmarksService.idl for an explanation.
+ */
+ SOURCES: {
+ DEFAULT: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
+ SYNC: Ci.nsINavBookmarksService.SOURCE_SYNC,
+ IMPORT: Ci.nsINavBookmarksService.SOURCE_IMPORT,
+ IMPORT_REPLACE: Ci.nsINavBookmarksService.SOURCE_IMPORT_REPLACE,
+ },
+
+ /**
+ * Special GUIDs associated with bookmark roots.
+ * It's guaranteed that the roots will always have these guids.
+ */
+
+ rootGuid: "root________",
+ menuGuid: "menu________",
+ toolbarGuid: "toolbar_____",
+ unfiledGuid: "unfiled_____",
+ mobileGuid: "mobile______",
+
+ // With bug 424160, tags will stop being bookmarks, thus this root will
+ // be removed. Do not rely on this, rather use the tagging service API.
+ tagsGuid: "tags________",
+
+ /**
+ * Inserts a bookmark-item into the bookmarks tree.
+ *
+ * For creating a bookmark, the following set of properties is required:
+ * - type
+ * - parentGuid
+ * - url, only for bookmarked URLs
+ *
+ * If an index is not specified, it defaults to appending.
+ * It's also possible to pass a non-existent GUID to force creation of an
+ * item with the given GUID, but unless you have a very sound reason, such as
+ * an undo manager implementation or synchronization, don't do that.
+ *
+ * Note that any known properties that don't apply to the specific item type
+ * cause an exception.
+ *
+ * @param info
+ * object representing a bookmark-item.
+ *
+ * @return {Promise} resolved when the creation is complete.
+ * @resolves to an object representing the created bookmark.
+ * @rejects if it's not possible to create the requested bookmark.
+ * @throws if the arguments are invalid.
+ */
+ insert(info) {
+ // Ensure to use the same date for dateAdded and lastModified, even if
+ // dateAdded may be imposed by the caller.
+ let time = (info && info.dateAdded) || new Date();
+ let insertInfo = validateBookmarkObject(info,
+ { type: { defaultValue: this.TYPE_BOOKMARK }
+ , index: { defaultValue: this.DEFAULT_INDEX }
+ , url: { requiredIf: b => b.type == this.TYPE_BOOKMARK
+ , validIf: b => b.type == this.TYPE_BOOKMARK }
+ , parentGuid: { required: true }
+ , title: { validIf: b => [ this.TYPE_BOOKMARK
+ , this.TYPE_FOLDER ].includes(b.type) }
+ , dateAdded: { defaultValue: time
+ , validIf: b => !b.lastModified ||
+ b.dateAdded <= b.lastModified }
+ , lastModified: { defaultValue: time,
+ validIf: b => (!b.dateAdded && b.lastModified >= time) ||
+ (b.dateAdded && b.lastModified >= b.dateAdded) }
+ , source: { defaultValue: this.SOURCES.DEFAULT }
+ });
+
+ return Task.spawn(function* () {
+ // Ensure the parent exists.
+ let parent = yield fetchBookmark({ guid: insertInfo.parentGuid });
+ if (!parent)
+ throw new Error("parentGuid must be valid");
+
+ // Set index in the appending case.
+ if (insertInfo.index == this.DEFAULT_INDEX ||
+ insertInfo.index > parent._childCount) {
+ insertInfo.index = parent._childCount;
+ }
+
+ let item = yield insertBookmark(insertInfo, parent);
+
+ // Notify onItemAdded to listeners.
+ let observers = PlacesUtils.bookmarks.getObservers();
+ // We need the itemId to notify, though once the switch to guids is
+ // complete we may stop using it.
+ let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
+ let itemId = yield PlacesUtils.promiseItemId(item.guid);
+
+ // Pass tagging information for the observers to skip over these notifications when needed.
+ let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
+ let isTagsFolder = parent._id == PlacesUtils.tagsFolderId;
+ notify(observers, "onItemAdded", [ itemId, parent._id, item.index,
+ item.type, uri, item.title || null,
+ PlacesUtils.toPRTime(item.dateAdded), item.guid,
+ item.parentGuid, item.source ],
+ { isTagging: isTagging || isTagsFolder });
+
+ // If it's a tag, notify OnItemChanged to all bookmarks for this URL.
+ if (isTagging) {
+ for (let entry of (yield fetchBookmarksByURL(item))) {
+ notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
+ PlacesUtils.toPRTime(entry.lastModified),
+ entry.type, entry._parentId,
+ entry.guid, entry.parentGuid,
+ "", item.source ]);
+ }
+ }
+
+ // Remove non-enumerable properties.
+ delete item.source;
+ return Object.assign({}, item);
+ }.bind(this));
+ },
+
+ /**
+ * Updates a bookmark-item.
+ *
+ * Only set the properties which should be changed (undefined properties
+ * won't be taken into account).
+ * Moreover, the item's type or dateAdded cannot be changed, since they are
+ * immutable after creation. Trying to change them will reject.
+ *
+ * Note that any known properties that don't apply to the specific item type
+ * cause an exception.
+ *
+ * @param info
+ * object representing a bookmark-item, as defined above.
+ *
+ * @return {Promise} resolved when the update is complete.
+ * @resolves to an object representing the updated bookmark.
+ * @rejects if it's not possible to update the given bookmark.
+ * @throws if the arguments are invalid.
+ */
+ update(info) {
+ // The info object is first validated here to ensure it's consistent, then
+ // it's compared to the existing item to remove any properties that don't
+ // need to be updated.
+ let updateInfo = validateBookmarkObject(info,
+ { guid: { required: true }
+ , index: { requiredIf: b => b.hasOwnProperty("parentGuid")
+ , validIf: b => b.index >= 0 || b.index == this.DEFAULT_INDEX }
+ , source: { defaultValue: this.SOURCES.DEFAULT }
+ });
+
+ // There should be at last one more property in addition to guid and source.
+ if (Object.keys(updateInfo).length < 3)
+ throw new Error("Not enough properties to update");
+
+ return Task.spawn(function* () {
+ // Ensure the item exists.
+ let item = yield fetchBookmark(updateInfo);
+ if (!item)
+ throw new Error("No bookmarks found for the provided GUID");
+ if (updateInfo.hasOwnProperty("type") && updateInfo.type != item.type)
+ throw new Error("The bookmark type cannot be changed");
+ if (updateInfo.hasOwnProperty("dateAdded") &&
+ updateInfo.dateAdded.getTime() != item.dateAdded.getTime())
+ throw new Error("The bookmark dateAdded cannot be changed");
+
+ // Remove any property that will stay the same.
+ removeSameValueProperties(updateInfo, item);
+ // Check if anything should still be updated.
+ if (Object.keys(updateInfo).length < 3) {
+ // Remove non-enumerable properties.
+ return Object.assign({}, item);
+ }
+
+ updateInfo = validateBookmarkObject(updateInfo,
+ { url: { validIf: () => item.type == this.TYPE_BOOKMARK }
+ , title: { validIf: () => [ this.TYPE_BOOKMARK
+ , this.TYPE_FOLDER ].includes(item.type) }
+ , lastModified: { defaultValue: new Date()
+ , validIf: b => b.lastModified >= item.dateAdded }
+ });
+
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: update",
+ Task.async(function*(db) {
+ let parent;
+ if (updateInfo.hasOwnProperty("parentGuid")) {
+ if (item.type == this.TYPE_FOLDER) {
+ // Make sure we are not moving a folder into itself or one of its
+ // descendants.
+ let rows = yield db.executeCached(
+ `WITH RECURSIVE
+ descendants(did) AS (
+ VALUES(:id)
+ UNION ALL
+ SELECT id FROM moz_bookmarks
+ JOIN descendants ON parent = did
+ WHERE type = :type
+ )
+ SELECT guid FROM moz_bookmarks
+ WHERE id IN descendants
+ `, { id: item._id, type: this.TYPE_FOLDER });
+ if (rows.map(r => r.getResultByName("guid")).includes(updateInfo.parentGuid))
+ throw new Error("Cannot insert a folder into itself or one of its descendants");
+ }
+
+ parent = yield fetchBookmark({ guid: updateInfo.parentGuid });
+ if (!parent)
+ throw new Error("No bookmarks found for the provided parentGuid");
+ }
+
+ if (updateInfo.hasOwnProperty("index")) {
+ // If at this point we don't have a parent yet, we are moving into
+ // the same container. Thus we know it exists.
+ if (!parent)
+ parent = yield fetchBookmark({ guid: item.parentGuid });
+
+ if (updateInfo.index >= parent._childCount ||
+ updateInfo.index == this.DEFAULT_INDEX) {
+ updateInfo.index = parent._childCount;
+
+ // Fix the index when moving within the same container.
+ if (parent.guid == item.parentGuid)
+ updateInfo.index--;
+ }
+ }
+
+ let updatedItem = yield updateBookmark(updateInfo, item, parent);
+
+ if (item.type == this.TYPE_BOOKMARK &&
+ item.url.href != updatedItem.url.href) {
+ // ...though we don't wait for the calculation.
+ updateFrecency(db, [item.url]).then(null, Cu.reportError);
+ updateFrecency(db, [updatedItem.url]).then(null, Cu.reportError);
+ }
+
+ // Notify onItemChanged to listeners.
+ let observers = PlacesUtils.bookmarks.getObservers();
+ // For lastModified, we only care about the original input, since we
+ // should not notify implciit lastModified changes.
+ if (info.hasOwnProperty("lastModified") &&
+ updateInfo.hasOwnProperty("lastModified") &&
+ item.lastModified != updatedItem.lastModified) {
+ notify(observers, "onItemChanged", [ updatedItem._id, "lastModified",
+ false,
+ `${PlacesUtils.toPRTime(updatedItem.lastModified)}`,
+ PlacesUtils.toPRTime(updatedItem.lastModified),
+ updatedItem.type,
+ updatedItem._parentId,
+ updatedItem.guid,
+ updatedItem.parentGuid, "",
+ updatedItem.source ]);
+ }
+ if (updateInfo.hasOwnProperty("title")) {
+ notify(observers, "onItemChanged", [ updatedItem._id, "title",
+ false, updatedItem.title,
+ PlacesUtils.toPRTime(updatedItem.lastModified),
+ updatedItem.type,
+ updatedItem._parentId,
+ updatedItem.guid,
+ updatedItem.parentGuid, "",
+ updatedItem.source ]);
+ }
+ if (updateInfo.hasOwnProperty("url")) {
+ notify(observers, "onItemChanged", [ updatedItem._id, "uri",
+ false, updatedItem.url.href,
+ PlacesUtils.toPRTime(updatedItem.lastModified),
+ updatedItem.type,
+ updatedItem._parentId,
+ updatedItem.guid,
+ updatedItem.parentGuid,
+ item.url.href,
+ updatedItem.source ]);
+ }
+ // If the item was moved, notify onItemMoved.
+ if (item.parentGuid != updatedItem.parentGuid ||
+ item.index != updatedItem.index) {
+ notify(observers, "onItemMoved", [ updatedItem._id, item._parentId,
+ item.index, updatedItem._parentId,
+ updatedItem.index, updatedItem.type,
+ updatedItem.guid, item.parentGuid,
+ updatedItem.parentGuid,
+ updatedItem.source ]);
+ }
+
+ // Remove non-enumerable properties.
+ delete updatedItem.source;
+ return Object.assign({}, updatedItem);
+ }.bind(this)));
+ }.bind(this));
+ },
+
+ /**
+ * Removes a bookmark-item.
+ *
+ * @param guidOrInfo
+ * The globally unique identifier of the item to remove, or an
+ * object representing it, as defined above.
+ * @param {Object} [options={}]
+ * Additional options that can be passed to the function.
+ * Currently supports the following properties:
+ * - preventRemovalOfNonEmptyFolders: Causes an exception to be
+ * thrown when attempting to remove a folder that is not empty.
+ * - source: The change source, forwarded to all bookmark observers.
+ * Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
+ *
+ * @return {Promise} resolved when the removal is complete.
+ * @resolves to an object representing the removed bookmark.
+ * @rejects if the provided guid doesn't match any existing bookmark.
+ * @throws if the arguments are invalid.
+ */
+ remove(guidOrInfo, options={}) {
+ let info = guidOrInfo;
+ if (!info)
+ throw new Error("Input should be a valid object");
+ if (typeof(guidOrInfo) != "object")
+ info = { guid: guidOrInfo };
+
+ // Disallow removing the root folders.
+ if ([this.rootGuid, this.menuGuid, this.toolbarGuid, this.unfiledGuid,
+ this.tagsGuid, this.mobileGuid].includes(info.guid)) {
+ throw new Error("It's not possible to remove Places root folders.");
+ }
+
+ // Even if we ignore any other unneeded property, we still validate any
+ // known property to reduce likelihood of hidden bugs.
+ let removeInfo = validateBookmarkObject(info);
+
+ return Task.spawn(function* () {
+ let item = yield fetchBookmark(removeInfo);
+ if (!item)
+ throw new Error("No bookmarks found for the provided GUID.");
+
+ item = yield removeBookmark(item, options);
+
+ // Notify onItemRemoved to listeners.
+ let { source = Bookmarks.SOURCES.DEFAULT } = options;
+ let observers = PlacesUtils.bookmarks.getObservers();
+ let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
+ let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
+ notify(observers, "onItemRemoved", [ item._id, item._parentId, item.index,
+ item.type, uri, item.guid,
+ item.parentGuid,
+ source ],
+ { isTagging: isUntagging });
+
+ if (isUntagging) {
+ for (let entry of (yield fetchBookmarksByURL(item))) {
+ notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
+ PlacesUtils.toPRTime(entry.lastModified),
+ entry.type, entry._parentId,
+ entry.guid, entry.parentGuid,
+ "", source ]);
+ }
+ }
+
+ // Remove non-enumerable properties.
+ return Object.assign({}, item);
+ });
+ },
+
+ /**
+ * Removes ALL bookmarks, resetting the bookmarks storage to an empty tree.
+ *
+ * Note that roots are preserved, only their children will be removed.
+ *
+ * @param {Object} [options={}]
+ * Additional options. Currently supports the following properties:
+ * - source: The change source, forwarded to all bookmark observers.
+ * Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
+ *
+ * @return {Promise} resolved when the removal is complete.
+ * @resolves once the removal is complete.
+ */
+ eraseEverything: function(options={}) {
+ const folderGuids = [this.toolbarGuid, this.menuGuid, this.unfiledGuid,
+ this.mobileGuid];
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: eraseEverything",
+ db => db.executeTransaction(function* () {
+ yield removeFoldersContents(db, folderGuids, options);
+ const time = PlacesUtils.toPRTime(new Date());
+ for (let folderGuid of folderGuids) {
+ yield db.executeCached(
+ `UPDATE moz_bookmarks SET lastModified = :time
+ WHERE id IN (SELECT id FROM moz_bookmarks WHERE guid = :folderGuid )
+ `, { folderGuid, time });
+ }
+ })
+ );
+ },
+
+ /**
+ * Returns a list of recently bookmarked items.
+ *
+ * @param {integer} numberOfItems
+ * The maximum number of bookmark items to return.
+ *
+ * @return {Promise} resolved when the listing is complete.
+ * @resolves to an array of recent bookmark-items.
+ * @rejects if an error happens while querying.
+ */
+ getRecent(numberOfItems) {
+ if (numberOfItems === undefined) {
+ throw new Error("numberOfItems argument is required");
+ }
+ if (!typeof numberOfItems === 'number' || (numberOfItems % 1) !== 0) {
+ throw new Error("numberOfItems argument must be an integer");
+ }
+ if (numberOfItems <= 0) {
+ throw new Error("numberOfItems argument must be greater than zero");
+ }
+
+ return Task.spawn(function* () {
+ return yield fetchRecentBookmarks(numberOfItems);
+ });
+ },
+
+ /**
+ * Fetches information about a bookmark-item.
+ *
+ * REMARK: any successful call to this method resolves to a single
+ * bookmark-item (or null), even when multiple bookmarks may exist
+ * (e.g. fetching by url). If you wish to retrieve all of the
+ * bookmarks for a given match, use the callback instead.
+ *
+ * Input can be either a guid or an object with one, and only one, of these
+ * filtering properties set:
+ * - guid
+ * retrieves the item with the specified guid.
+ * - parentGuid and index
+ * retrieves the item by its position.
+ * - url
+ * retrieves the most recent bookmark having the given URL.
+ * To retrieve ALL of the bookmarks for that URL, you must pass in an
+ * onResult callback, that will be invoked once for each found bookmark.
+ *
+ * @param guidOrInfo
+ * The globally unique identifier of the item to fetch, or an
+ * object representing it, as defined above.
+ * @param onResult [optional]
+ * Callback invoked for each found bookmark.
+ *
+ * @return {Promise} resolved when the fetch is complete.
+ * @resolves to an object representing the found item, as described above, or
+ * an array of such objects. if no item is found, the returned
+ * promise is resolved to null.
+ * @rejects if an error happens while fetching.
+ * @throws if the arguments are invalid.
+ *
+ * @note Any unknown property in the info object is ignored. Known properties
+ * may be overwritten.
+ */
+ fetch(guidOrInfo, onResult=null) {
+ if (onResult && typeof onResult != "function")
+ throw new Error("onResult callback must be a valid function");
+ let info = guidOrInfo;
+ if (!info)
+ throw new Error("Input should be a valid object");
+ if (typeof(info) != "object")
+ info = { guid: guidOrInfo };
+
+ // Only one condition at a time can be provided.
+ let conditionsCount = [
+ v => v.hasOwnProperty("guid"),
+ v => v.hasOwnProperty("parentGuid") && v.hasOwnProperty("index"),
+ v => v.hasOwnProperty("url")
+ ].reduce((old, fn) => old + fn(info)|0, 0);
+ if (conditionsCount != 1)
+ throw new Error(`Unexpected number of conditions provided: ${conditionsCount}`);
+
+ // Even if we ignore any other unneeded property, we still validate any
+ // known property to reduce likelihood of hidden bugs.
+ let fetchInfo = validateBookmarkObject(info,
+ { parentGuid: { requiredIf: b => b.hasOwnProperty("index") }
+ , index: { requiredIf: b => b.hasOwnProperty("parentGuid")
+ , validIf: b => typeof(b.index) == "number" &&
+ b.index >= 0 || b.index == this.DEFAULT_INDEX }
+ });
+
+ return Task.spawn(function* () {
+ let results;
+ if (fetchInfo.hasOwnProperty("guid"))
+ results = yield fetchBookmark(fetchInfo);
+ else if (fetchInfo.hasOwnProperty("parentGuid") && fetchInfo.hasOwnProperty("index"))
+ results = yield fetchBookmarkByPosition(fetchInfo);
+ else if (fetchInfo.hasOwnProperty("url"))
+ results = yield fetchBookmarksByURL(fetchInfo);
+
+ if (!results)
+ return null;
+
+ if (!Array.isArray(results))
+ results = [results];
+ // Remove non-enumerable properties.
+ results = results.map(r => Object.assign({}, r));
+
+ // Ideally this should handle an incremental behavior and thus be invoked
+ // while we fetch. Though, the likelihood of 2 or more bookmarks for the
+ // same match is very low, so it's not worth the added code complication.
+ if (onResult) {
+ for (let result of results) {
+ try {
+ onResult(result);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ }
+
+ return results[0];
+ });
+ },
+
+ /**
+ * Retrieves an object representation of a bookmark-item, along with all of
+ * its descendants, if any.
+ *
+ * Each node in the tree is an object that extends the item representation
+ * described above with some additional properties:
+ *
+ * - [deprecated] id (number)
+ * the item's id. Defined only if aOptions.includeItemIds is set.
+ * - annos (array)
+ * the item's annotations. This is not set if there are no annotations
+ * set for the item.
+ *
+ * The root object of the tree also has the following properties set:
+ * - itemsCount (number, not enumerable)
+ * the number of items, including the root item itself, which are
+ * represented in the resolved object.
+ *
+ * Bookmarked URLs may also have the following properties:
+ * - tags (string)
+ * csv string of the bookmark's tags, if any.
+ * - charset (string)
+ * the last known charset of the bookmark, if any.
+ * - iconurl (URL)
+ * the bookmark's favicon URL, if any.
+ *
+ * Folders may also have the following properties:
+ * - children (array)
+ * the folder's children information, each of them having the same set of
+ * properties as above.
+ *
+ * @param [optional] guid
+ * the topmost item to be queried. If it's not passed, the Places
+ * root folder is queried: that is, you get a representation of the
+ * entire bookmarks hierarchy.
+ * @param [optional] options
+ * Options for customizing the query behavior, in the form of an
+ * object with any of the following properties:
+ * - excludeItemsCallback: a function for excluding items, along with
+ * their descendants. Given an item object (that has everything set
+ * apart its potential children data), it should return true if the
+ * item should be excluded. Once an item is excluded, the function
+ * isn't called for any of its descendants. This isn't called for
+ * the root item.
+ * WARNING: since the function may be called for each item, using
+ * this option can slow down the process significantly if the
+ * callback does anything that's not relatively trivial. It is
+ * highly recommended to avoid any synchronous I/O or DB queries.
+ * - includeItemIds: opt-in to include the deprecated id property.
+ * Use it if you must. It'll be removed once the switch to guids is
+ * complete.
+ *
+ * @return {Promise} resolved when the fetch is complete.
+ * @resolves to an object that represents either a single item or a
+ * bookmarks tree. if guid points to a non-existent item, the
+ * returned promise is resolved to null.
+ * @rejects if an error happens while fetching.
+ * @throws if the arguments are invalid.
+ */
+ // TODO must implement these methods yet:
+ // PlacesUtils.promiseBookmarksTree()
+ fetchTree(guid = "", options = {}) {
+ throw new Error("Not yet implemented");
+ },
+
+ /**
+ * Reorders contents of a folder based on a provided array of GUIDs.
+ *
+ * @param parentGuid
+ * The globally unique identifier of the folder whose contents should
+ * be reordered.
+ * @param orderedChildrenGuids
+ * Ordered array of the children's GUIDs. If this list contains
+ * non-existing entries they will be ignored. If the list is
+ * incomplete, missing entries will be appended.
+ * @param {Object} [options={}]
+ * Additional options. Currently supports the following properties:
+ * - source: The change source, forwarded to all bookmark observers.
+ * Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
+ *
+ * @return {Promise} resolved when reordering is complete.
+ * @rejects if an error happens while reordering.
+ * @throws if the arguments are invalid.
+ */
+ reorder(parentGuid, orderedChildrenGuids, options={}) {
+ let info = { guid: parentGuid, source: this.SOURCES.DEFAULT };
+ info = validateBookmarkObject(info, { guid: { required: true } });
+
+ if (!Array.isArray(orderedChildrenGuids) || !orderedChildrenGuids.length)
+ throw new Error("Must provide a sorted array of children GUIDs.");
+ try {
+ orderedChildrenGuids.forEach(PlacesUtils.BOOKMARK_VALIDATORS.guid);
+ } catch (ex) {
+ throw new Error("Invalid GUID found in the sorted children array.");
+ }
+
+ return Task.spawn(function* () {
+ let parent = yield fetchBookmark(info);
+ if (!parent || parent.type != this.TYPE_FOLDER)
+ throw new Error("No folder found for the provided GUID.");
+
+ let sortedChildren = yield reorderChildren(parent, orderedChildrenGuids);
+
+ let { source = Ci.nsINavBookmarksService.SOURCE_DEFAULT } = options;
+ let observers = PlacesUtils.bookmarks.getObservers();
+ // Note that child.index is the old index.
+ for (let i = 0; i < sortedChildren.length; ++i) {
+ let child = sortedChildren[i];
+ notify(observers, "onItemMoved", [ child._id, child._parentId,
+ child.index, child._parentId,
+ i, child.type,
+ child.guid, child.parentGuid,
+ child.parentGuid,
+ source ]);
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Searches a list of bookmark-items by a search term, url or title.
+ *
+ * IMPORTANT:
+ * This is intended as an interim API for the web-extensions implementation.
+ * It will be removed as soon as we have a new querying API.
+ *
+ * If you just want to search bookmarks by URL, use .fetch() instead.
+ *
+ * @param query
+ * Either a string to use as search term, or an object
+ * containing any of these keys: query, title or url with the
+ * corresponding string to match as value.
+ * The url property can be either a string or an nsIURI.
+ *
+ * @return {Promise} resolved when the search is complete.
+ * @resolves to an array of found bookmark-items.
+ * @rejects if an error happens while searching.
+ * @throws if the arguments are invalid.
+ *
+ * @note Any unknown property in the query object is ignored.
+ * Known properties may be overwritten.
+ */
+ search(query) {
+ if (!query) {
+ throw new Error("Query object is required");
+ }
+ if (typeof query === "string") {
+ query = { query: query };
+ }
+ if (typeof query !== "object") {
+ throw new Error("Query must be an object or a string");
+ }
+ if (query.query && typeof query.query !== "string") {
+ throw new Error("Query option must be a string");
+ }
+ if (query.title && typeof query.title !== "string") {
+ throw new Error("Title option must be a string");
+ }
+
+ if (query.url) {
+ if (typeof query.url === "string" || (query.url instanceof URL)) {
+ query.url = new URL(query.url).href;
+ } else if (query.url instanceof Ci.nsIURI) {
+ query.url = query.url.spec;
+ } else {
+ throw new Error("Url option must be a string or a URL object");
+ }
+ }
+
+ return Task.spawn(function* () {
+ let results = yield queryBookmarks(query);
+
+ return results;
+ });
+ },
+});
+
+// Globals.
+
+/**
+ * Sends a bookmarks notification through the given observers.
+ *
+ * @param observers
+ * array of nsINavBookmarkObserver objects.
+ * @param notification
+ * the notification name.
+ * @param args
+ * array of arguments to pass to the notification.
+ * @param information
+ * Information about the notification, so we can filter based
+ * based on the observer's preferences.
+ */
+function notify(observers, notification, args, information = {}) {
+ for (let observer of observers) {
+ if (information.isTagging && observer.skipTags) {
+ continue;
+ }
+
+ if (information.isDescendantRemoval && observer.skipDescendantsOnItemRemoval) {
+ continue;
+ }
+
+ try {
+ observer[notification](...args);
+ } catch (ex) {}
+ }
+}
+
+// Update implementation.
+
+function updateBookmark(info, item, newParent) {
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: updateBookmark",
+ Task.async(function*(db) {
+
+ let tuples = new Map();
+ if (info.hasOwnProperty("lastModified"))
+ tuples.set("lastModified", { value: PlacesUtils.toPRTime(info.lastModified) });
+ if (info.hasOwnProperty("title"))
+ tuples.set("title", { value: info.title });
+
+ yield db.executeTransaction(function* () {
+ if (info.hasOwnProperty("url")) {
+ // Ensure a page exists in moz_places for this URL.
+ yield maybeInsertPlace(db, info.url);
+ // Update tuples for the update query.
+ tuples.set("url", { value: info.url.href
+ , fragment: "fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url)" });
+ }
+
+ if (newParent) {
+ // For simplicity, update the index regardless.
+ let newIndex = info.hasOwnProperty("index") ? info.index : item.index;
+ tuples.set("position", { value: newIndex });
+
+ if (newParent.guid == item.parentGuid) {
+ // Moving inside the original container.
+ // When moving "up", add 1 to each index in the interval.
+ // Otherwise when moving down, we subtract 1.
+ let sign = newIndex < item.index ? +1 : -1;
+ yield db.executeCached(
+ `UPDATE moz_bookmarks SET position = position + :sign
+ WHERE parent = :newParentId
+ AND position BETWEEN :lowIndex AND :highIndex
+ `, { sign: sign, newParentId: newParent._id,
+ lowIndex: Math.min(item.index, newIndex),
+ highIndex: Math.max(item.index, newIndex) });
+ } else {
+ // Moving across different containers.
+ tuples.set("parent", { value: newParent._id} );
+ yield db.executeCached(
+ `UPDATE moz_bookmarks SET position = position + :sign
+ WHERE parent = :oldParentId
+ AND position >= :oldIndex
+ `, { sign: -1, oldParentId: item._parentId, oldIndex: item.index });
+ yield db.executeCached(
+ `UPDATE moz_bookmarks SET position = position + :sign
+ WHERE parent = :newParentId
+ AND position >= :newIndex
+ `, { sign: +1, newParentId: newParent._id, newIndex: newIndex });
+
+ yield setAncestorsLastModified(db, item.parentGuid, info.lastModified);
+ }
+ yield setAncestorsLastModified(db, newParent.guid, info.lastModified);
+ }
+
+ yield db.executeCached(
+ `UPDATE moz_bookmarks
+ SET ${Array.from(tuples.keys()).map(v => tuples.get(v).fragment || `${v} = :${v}`).join(", ")}
+ WHERE guid = :guid
+ `, Object.assign({ guid: info.guid },
+ [...tuples.entries()].reduce((p, c) => { p[c[0]] = c[1].value; return p; }, {})));
+ });
+
+ // If the parent changed, update related non-enumerable properties.
+ let additionalParentInfo = {};
+ if (newParent) {
+ Object.defineProperty(additionalParentInfo, "_parentId",
+ { value: newParent._id, enumerable: false });
+ Object.defineProperty(additionalParentInfo, "_grandParentId",
+ { value: newParent._parentId, enumerable: false });
+ }
+
+ let updatedItem = mergeIntoNewObject(item, info, additionalParentInfo);
+
+ // Don't return an empty title to the caller.
+ if (updatedItem.hasOwnProperty("title") && updatedItem.title === null)
+ delete updatedItem.title;
+
+ return updatedItem;
+ }));
+}
+
+// Insert implementation.
+
+function insertBookmark(item, parent) {
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: insertBookmark",
+ Task.async(function*(db) {
+
+ // If a guid was not provided, generate one, so we won't need to fetch the
+ // bookmark just after having created it.
+ if (!item.hasOwnProperty("guid"))
+ item.guid = (yield db.executeCached("SELECT GENERATE_GUID() AS guid"))[0].getResultByName("guid");
+
+ yield db.executeTransaction(function* transaction() {
+ if (item.type == Bookmarks.TYPE_BOOKMARK) {
+ // Ensure a page exists in moz_places for this URL.
+ // The IGNORE conflict can trigger on `guid`.
+ yield maybeInsertPlace(db, item.url);
+ }
+
+ // Adjust indices.
+ yield db.executeCached(
+ `UPDATE moz_bookmarks SET position = position + 1
+ WHERE parent = :parent
+ AND position >= :index
+ `, { parent: parent._id, index: item.index });
+
+ // Insert the bookmark into the database.
+ yield db.executeCached(
+ `INSERT INTO moz_bookmarks (fk, type, parent, position, title,
+ dateAdded, lastModified, guid)
+ VALUES ((SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url), :type, :parent,
+ :index, :title, :date_added, :last_modified, :guid)
+ `, { url: item.hasOwnProperty("url") ? item.url.href : "nonexistent",
+ type: item.type, parent: parent._id, index: item.index,
+ title: item.title, date_added: PlacesUtils.toPRTime(item.dateAdded),
+ last_modified: PlacesUtils.toPRTime(item.lastModified), guid: item.guid });
+
+ yield setAncestorsLastModified(db, item.parentGuid, item.dateAdded);
+ });
+
+ // If not a tag recalculate frecency...
+ let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
+ if (item.type == Bookmarks.TYPE_BOOKMARK && !isTagging) {
+ // ...though we don't wait for the calculation.
+ updateFrecency(db, [item.url]).then(null, Cu.reportError);
+ }
+
+ // Don't return an empty title to the caller.
+ if (item.hasOwnProperty("title") && item.title === null)
+ delete item.title;
+
+ return item;
+ }));
+}
+
+// Query implementation.
+
+function queryBookmarks(info) {
+ let queryParams = {tags_folder: PlacesUtils.tagsFolderId};
+ // we're searching for bookmarks, so exclude tags
+ let queryString = "WHERE p.parent <> :tags_folder";
+
+ if (info.title) {
+ queryString += " AND b.title = :title";
+ queryParams.title = info.title;
+ }
+
+ if (info.url) {
+ queryString += " AND h.url_hash = hash(:url) AND h.url = :url";
+ queryParams.url = info.url;
+ }
+
+ if (info.query) {
+ queryString += " AND AUTOCOMPLETE_MATCH(:query, h.url, b.title, NULL, NULL, 1, 1, NULL, :matchBehavior, :searchBehavior) ";
+ queryParams.query = info.query;
+ queryParams.matchBehavior = MATCH_ANYWHERE_UNMODIFIED;
+ queryParams.searchBehavior = BEHAVIOR_BOOKMARK;
+ }
+
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: queryBookmarks",
+ Task.async(function*(db) {
+
+ // _id, _childCount, _grandParentId and _parentId fields
+ // are required to be in the result by the converting function
+ // hence setting them to NULL
+ let rows = yield db.executeCached(
+ `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+ b.dateAdded, b.lastModified, b.type, b.title,
+ h.url AS url, b.parent, p.parent,
+ NULL AS _id,
+ NULL AS _childCount,
+ NULL AS _grandParentId,
+ NULL AS _parentId
+ FROM moz_bookmarks b
+ LEFT JOIN moz_bookmarks p ON p.id = b.parent
+ LEFT JOIN moz_places h ON h.id = b.fk
+ ${queryString}
+ `, queryParams);
+
+ return rowsToItemsArray(rows);
+ }));
+}
+
+
+// Fetch implementation.
+
+function fetchBookmark(info) {
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmark",
+ Task.async(function*(db) {
+
+ let rows = yield db.executeCached(
+ `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+ b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
+ b.id AS _id, b.parent AS _parentId,
+ (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
+ p.parent AS _grandParentId
+ FROM moz_bookmarks b
+ LEFT JOIN moz_bookmarks p ON p.id = b.parent
+ LEFT JOIN moz_places h ON h.id = b.fk
+ WHERE b.guid = :guid
+ `, { guid: info.guid });
+
+ return rows.length ? rowsToItemsArray(rows)[0] : null;
+ }));
+}
+
+function fetchBookmarkByPosition(info) {
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarkByPosition",
+ Task.async(function*(db) {
+ let index = info.index == Bookmarks.DEFAULT_INDEX ? null : info.index;
+
+ let rows = yield db.executeCached(
+ `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+ b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
+ b.id AS _id, b.parent AS _parentId,
+ (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
+ p.parent AS _grandParentId
+ FROM moz_bookmarks b
+ LEFT JOIN moz_bookmarks p ON p.id = b.parent
+ LEFT JOIN moz_places h ON h.id = b.fk
+ WHERE p.guid = :parentGuid
+ AND b.position = IFNULL(:index, (SELECT count(*) - 1
+ FROM moz_bookmarks
+ WHERE parent = p.id))
+ `, { parentGuid: info.parentGuid, index });
+
+ return rows.length ? rowsToItemsArray(rows)[0] : null;
+ }));
+}
+
+function fetchBookmarksByURL(info) {
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarksByURL",
+ Task.async(function*(db) {
+
+ let rows = yield db.executeCached(
+ `/* do not warn (bug no): not worth to add an index */
+ SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+ b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
+ b.id AS _id, b.parent AS _parentId,
+ (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
+ p.parent AS _grandParentId
+ FROM moz_bookmarks b
+ LEFT JOIN moz_bookmarks p ON p.id = b.parent
+ LEFT JOIN moz_places h ON h.id = b.fk
+ WHERE h.url_hash = hash(:url) AND h.url = :url
+ AND _grandParentId <> :tags_folder
+ ORDER BY b.lastModified DESC
+ `, { url: info.url.href,
+ tags_folder: PlacesUtils.tagsFolderId });
+
+ return rows.length ? rowsToItemsArray(rows) : null;
+ }));
+}
+
+function fetchRecentBookmarks(numberOfItems) {
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchRecentBookmarks",
+ Task.async(function*(db) {
+
+ let rows = yield db.executeCached(
+ `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+ b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
+ NULL AS _id, NULL AS _parentId, NULL AS _childCount, NULL AS _grandParentId
+ FROM moz_bookmarks b
+ LEFT JOIN moz_bookmarks p ON p.id = b.parent
+ LEFT JOIN moz_places h ON h.id = b.fk
+ WHERE p.parent <> :tags_folder
+ ORDER BY b.dateAdded DESC, b.ROWID DESC
+ LIMIT :numberOfItems
+ `, { tags_folder: PlacesUtils.tagsFolderId, numberOfItems });
+
+ return rows.length ? rowsToItemsArray(rows) : [];
+ }));
+}
+
+function fetchBookmarksByParent(info) {
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarksByParent",
+ Task.async(function*(db) {
+
+ let rows = yield db.executeCached(
+ `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+ b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
+ b.id AS _id, b.parent AS _parentId,
+ (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
+ p.parent AS _grandParentId
+ FROM moz_bookmarks b
+ LEFT JOIN moz_bookmarks p ON p.id = b.parent
+ LEFT JOIN moz_places h ON h.id = b.fk
+ WHERE p.guid = :parentGuid
+ ORDER BY b.position ASC
+ `, { parentGuid: info.parentGuid });
+
+ return rowsToItemsArray(rows);
+ }));
+}
+
+// Remove implementation.
+
+function removeBookmark(item, options) {
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: removeBookmark",
+ Task.async(function*(db) {
+
+ let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
+
+ yield db.executeTransaction(function* transaction() {
+ // If it's a folder, remove its contents first.
+ if (item.type == Bookmarks.TYPE_FOLDER) {
+ if (options.preventRemovalOfNonEmptyFolders && item._childCount > 0) {
+ throw new Error("Cannot remove a non-empty folder.");
+ }
+ yield removeFoldersContents(db, [item.guid], options);
+ }
+
+ // Remove annotations first. If it's a tag, we can avoid paying that cost.
+ if (!isUntagging) {
+ // We don't go through the annotations service for this cause otherwise
+ // we'd get a pointless onItemChanged notification and it would also
+ // set lastModified to an unexpected value.
+ yield removeAnnotationsForItem(db, item._id);
+ }
+
+ // Remove the bookmark from the database.
+ yield db.executeCached(
+ `DELETE FROM moz_bookmarks WHERE guid = :guid`, { guid: item.guid });
+
+ // Fix indices in the parent.
+ yield db.executeCached(
+ `UPDATE moz_bookmarks SET position = position - 1 WHERE
+ parent = :parentId AND position > :index
+ `, { parentId: item._parentId, index: item.index });
+
+ yield setAncestorsLastModified(db, item.parentGuid, new Date());
+ });
+
+ // If not a tag recalculate frecency...
+ if (item.type == Bookmarks.TYPE_BOOKMARK && !isUntagging) {
+ // ...though we don't wait for the calculation.
+ updateFrecency(db, [item.url]).then(null, Cu.reportError);
+ }
+
+ return item;
+ }));
+}
+
+// Reorder implementation.
+
+function reorderChildren(parent, orderedChildrenGuids) {
+ return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: updateBookmark",
+ db => db.executeTransaction(function* () {
+ // Select all of the direct children for the given parent.
+ let children = yield fetchBookmarksByParent({ parentGuid: parent.guid });
+ if (!children.length)
+ return undefined;
+
+ // Build a map of GUIDs to indices for fast lookups in the comparator
+ // function.
+ let guidIndices = new Map();
+ for (let i = 0; i < orderedChildrenGuids.length; ++i) {
+ let guid = orderedChildrenGuids[i];
+ guidIndices.set(guid, i);
+ }
+
+ // Reorder the children array according to the specified order, provided
+ // GUIDs come first, others are appended in somehow random order.
+ children.sort((a, b) => {
+ // This works provided fetchBookmarksByParent returns sorted children.
+ if (!guidIndices.has(a.guid) && !guidIndices.has(b.guid)) {
+ return 0;
+ }
+ if (!guidIndices.has(a.guid)) {
+ return 1;
+ }
+ if (!guidIndices.has(b.guid)) {
+ return -1;
+ }
+ return guidIndices.get(a.guid) < guidIndices.get(b.guid) ? -1 : 1;
+ });
+
+ // Update the bookmarks position now. If any unknown guid have been
+ // inserted meanwhile, its position will be set to -position, and we'll
+ // handle it later.
+ // To do the update in a single step, we build a VALUES (guid, position)
+ // table. We then use count() in the sorting table to avoid skipping values
+ // when no more existing GUIDs have been provided.
+ let valuesTable = children.map((child, i) => `("${child.guid}", ${i})`)
+ .join();
+ yield db.execute(
+ `WITH sorting(g, p) AS (
+ VALUES ${valuesTable}
+ )
+ UPDATE moz_bookmarks SET position = (
+ SELECT CASE count(*) WHEN 0 THEN -position
+ ELSE count(*) - 1
+ END
+ FROM sorting a
+ JOIN sorting b ON b.p <= a.p
+ WHERE a.g = guid
+ )
+ WHERE parent = :parentId
+ `, { parentId: parent._id});
+
+ // Update position of items that could have been inserted in the meanwhile.
+ // Since this can happen rarely and it's only done for schema coherence
+ // resonds, we won't notify about these changes.
+ yield db.executeCached(
+ `CREATE TEMP TRIGGER moz_bookmarks_reorder_trigger
+ AFTER UPDATE OF position ON moz_bookmarks
+ WHEN NEW.position = -1
+ BEGIN
+ UPDATE moz_bookmarks
+ SET position = (SELECT MAX(position) FROM moz_bookmarks
+ WHERE parent = NEW.parent) +
+ (SELECT count(*) FROM moz_bookmarks
+ WHERE parent = NEW.parent
+ AND position BETWEEN OLD.position AND -1)
+ WHERE guid = NEW.guid;
+ END
+ `);
+
+ yield db.executeCached(
+ `UPDATE moz_bookmarks SET position = -1 WHERE position < 0`);
+
+ yield db.executeCached(`DROP TRIGGER moz_bookmarks_reorder_trigger`);
+
+ return children;
+ }.bind(this))
+ );
+}
+
+// Helpers.
+
+/**
+ * Merges objects into a new object, included non-enumerable properties.
+ *
+ * @param sources
+ * source objects to merge.
+ * @return a new object including all properties from the source objects.
+ */
+function mergeIntoNewObject(...sources) {
+ let dest = {};
+ for (let src of sources) {
+ for (let prop of Object.getOwnPropertyNames(src)) {
+ Object.defineProperty(dest, prop, Object.getOwnPropertyDescriptor(src, prop));
+ }
+ }
+ return dest;
+}
+
+/**
+ * Remove properties that have the same value across two bookmark objects.
+ *
+ * @param dest
+ * destination bookmark object.
+ * @param src
+ * source bookmark object.
+ * @return a cleaned up bookmark object.
+ * @note "guid" is never removed.
+ */
+function removeSameValueProperties(dest, src) {
+ for (let prop in dest) {
+ let remove = false;
+ switch (prop) {
+ case "lastModified":
+ case "dateAdded":
+ remove = src.hasOwnProperty(prop) && dest[prop].getTime() == src[prop].getTime();
+ break;
+ case "url":
+ remove = src.hasOwnProperty(prop) && dest[prop].href == src[prop].href;
+ break;
+ default:
+ remove = dest[prop] == src[prop];
+ }
+ if (remove && prop != "guid")
+ delete dest[prop];
+ }
+}
+
+/**
+ * Convert an array of mozIStorageRow objects to an array of bookmark objects.
+ *
+ * @param rows
+ * the array of mozIStorageRow objects.
+ * @return an array of bookmark objects.
+ */
+function rowsToItemsArray(rows) {
+ return rows.map(row => {
+ let item = {};
+ for (let prop of ["guid", "index", "type"]) {
+ item[prop] = row.getResultByName(prop);
+ }
+ for (let prop of ["dateAdded", "lastModified"]) {
+ item[prop] = PlacesUtils.toDate(row.getResultByName(prop));
+ }
+ for (let prop of ["title", "parentGuid", "url" ]) {
+ let val = row.getResultByName(prop);
+ if (val)
+ item[prop] = prop === "url" ? new URL(val) : val;
+ }
+ for (let prop of ["_id", "_parentId", "_childCount", "_grandParentId"]) {
+ let val = row.getResultByName(prop);
+ if (val !== null) {
+ // These properties should not be returned to the API consumer, thus
+ // they are non-enumerable and removed through Object.assign just before
+ // the object is returned.
+ // Configurable is set to support mergeIntoNewObject overwrites.
+ Object.defineProperty(item, prop, { value: val, enumerable: false,
+ configurable: true });
+ }
+ }
+
+ return item;
+ });
+}
+
+function validateBookmarkObject(input, behavior) {
+ return PlacesUtils.validateItemProperties(
+ PlacesUtils.BOOKMARK_VALIDATORS, input, behavior);
+}
+
+/**
+ * Updates frecency for a list of URLs.
+ *
+ * @param db
+ * the Sqlite.jsm connection handle.
+ * @param urls
+ * the array of URLs to update.
+ */
+var updateFrecency = Task.async(function* (db, urls) {
+ // We just use the hashes, since updating a few additional urls won't hurt.
+ yield db.execute(
+ `UPDATE moz_places
+ SET frecency = NOTIFY_FRECENCY(
+ CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date
+ ) WHERE url_hash IN ( ${urls.map(url => `hash("${url.href}")`).join(", ")} )
+ `);
+
+ yield db.execute(
+ `UPDATE moz_places
+ SET hidden = 0
+ WHERE url_hash IN ( ${urls.map(url => `hash(${JSON.stringify(url.href)})`).join(", ")} )
+ AND frecency <> 0
+ `);
+});
+
+/**
+ * Removes any orphan annotation entries.
+ *
+ * @param db
+ * the Sqlite.jsm connection handle.
+ */
+var removeOrphanAnnotations = Task.async(function* (db) {
+ yield db.executeCached(
+ `DELETE FROM moz_items_annos
+ WHERE id IN (SELECT a.id from moz_items_annos a
+ LEFT JOIN moz_bookmarks b ON a.item_id = b.id
+ WHERE b.id ISNULL)
+ `);
+ yield db.executeCached(
+ `DELETE FROM moz_anno_attributes
+ WHERE id IN (SELECT n.id from moz_anno_attributes n
+ LEFT JOIN moz_annos a1 ON a1.anno_attribute_id = n.id
+ LEFT JOIN moz_items_annos a2 ON a2.anno_attribute_id = n.id
+ WHERE a1.id ISNULL AND a2.id ISNULL)
+ `);
+});
+
+/**
+ * Removes annotations for a given item.
+ *
+ * @param db
+ * the Sqlite.jsm connection handle.
+ * @param itemId
+ * internal id of the item for which to remove annotations.
+ */
+var removeAnnotationsForItem = Task.async(function* (db, itemId) {
+ yield db.executeCached(
+ `DELETE FROM moz_items_annos
+ WHERE item_id = :id
+ `, { id: itemId });
+ yield db.executeCached(
+ `DELETE FROM moz_anno_attributes
+ WHERE id IN (SELECT n.id from moz_anno_attributes n
+ LEFT JOIN moz_annos a1 ON a1.anno_attribute_id = n.id
+ LEFT JOIN moz_items_annos a2 ON a2.anno_attribute_id = n.id
+ WHERE a1.id ISNULL AND a2.id ISNULL)
+ `);
+});
+
+/**
+ * Updates lastModified for all the ancestors of a given folder GUID.
+ *
+ * @param db
+ * the Sqlite.jsm connection handle.
+ * @param folderGuid
+ * the GUID of the folder whose ancestors should be updated.
+ * @param time
+ * a Date object to use for the update.
+ *
+ * @note the folder itself is also updated.
+ */
+var setAncestorsLastModified = Task.async(function* (db, folderGuid, time) {
+ yield db.executeCached(
+ `WITH RECURSIVE
+ ancestors(aid) AS (
+ SELECT id FROM moz_bookmarks WHERE guid = :guid
+ UNION ALL
+ SELECT parent FROM moz_bookmarks
+ JOIN ancestors ON id = aid
+ WHERE type = :type
+ )
+ UPDATE moz_bookmarks SET lastModified = :time
+ WHERE id IN ancestors
+ `, { guid: folderGuid, type: Bookmarks.TYPE_FOLDER,
+ time: PlacesUtils.toPRTime(time) });
+});
+
+/**
+ * Remove all descendants of one or more bookmark folders.
+ *
+ * @param db
+ * the Sqlite.jsm connection handle.
+ * @param folderGuids
+ * array of folder guids.
+ */
+var removeFoldersContents =
+Task.async(function* (db, folderGuids, options) {
+ let itemsRemoved = [];
+ for (let folderGuid of folderGuids) {
+ let rows = yield db.executeCached(
+ `WITH RECURSIVE
+ descendants(did) AS (
+ SELECT b.id FROM moz_bookmarks b
+ JOIN moz_bookmarks p ON b.parent = p.id
+ WHERE p.guid = :folderGuid
+ UNION ALL
+ SELECT id FROM moz_bookmarks
+ JOIN descendants ON parent = did
+ )
+ SELECT b.id AS _id, b.parent AS _parentId, b.position AS 'index',
+ b.type, url, b.guid, p.guid AS parentGuid, b.dateAdded,
+ b.lastModified, b.title, p.parent AS _grandParentId,
+ NULL AS _childCount
+ FROM descendants
+ JOIN moz_bookmarks b ON did = b.id
+ JOIN moz_bookmarks p ON p.id = b.parent
+ LEFT JOIN moz_places h ON b.fk = h.id`, { folderGuid });
+
+ itemsRemoved = itemsRemoved.concat(rowsToItemsArray(rows));
+
+ yield db.executeCached(
+ `WITH RECURSIVE
+ descendants(did) AS (
+ SELECT b.id FROM moz_bookmarks b
+ JOIN moz_bookmarks p ON b.parent = p.id
+ WHERE p.guid = :folderGuid
+ UNION ALL
+ SELECT id FROM moz_bookmarks
+ JOIN descendants ON parent = did
+ )
+ DELETE FROM moz_bookmarks WHERE id IN descendants`, { folderGuid });
+ }
+
+ // Cleanup orphans.
+ yield removeOrphanAnnotations(db);
+
+ // TODO (Bug 1087576): this may leave orphan tags behind.
+
+ let urls = itemsRemoved.filter(item => "url" in item).map(item => item.url);
+ updateFrecency(db, urls).then(null, Cu.reportError);
+
+ // Send onItemRemoved notifications to listeners.
+ // TODO (Bug 1087580): for the case of eraseEverything, this should send a
+ // single clear bookmarks notification rather than notifying for each
+ // bookmark.
+
+ // Notify listeners in reverse order to serve children before parents.
+ let { source = Ci.nsINavBookmarksService.SOURCE_DEFAULT } = options;
+ let observers = PlacesUtils.bookmarks.getObservers();
+ for (let item of itemsRemoved.reverse()) {
+ let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
+ notify(observers, "onItemRemoved", [ item._id, item._parentId,
+ item.index, item.type, uri,
+ item.guid, item.parentGuid,
+ source ],
+ // Notify observers that this item is being
+ // removed as a descendent.
+ { isDescendantRemoval: true });
+
+ let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
+ if (isUntagging) {
+ for (let entry of (yield fetchBookmarksByURL(item))) {
+ notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
+ PlacesUtils.toPRTime(entry.lastModified),
+ entry.type, entry._parentId,
+ entry.guid, entry.parentGuid,
+ "", source ]);
+ }
+ }
+ }
+});
+
+/**
+ * Tries to insert a new place if it doesn't exist yet.
+ * @param url
+ * A valid URL object.
+ * @return {Promise} resolved when the operation is complete.
+ */
+function maybeInsertPlace(db, url) {
+ // The IGNORE conflict can trigger on `guid`.
+ return db.executeCached(
+ `INSERT OR IGNORE INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid)
+ VALUES (:url, hash(:url), :rev_host, 0, :frecency,
+ IFNULL((SELECT guid FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
+ GENERATE_GUID()))
+ `, { url: url.href,
+ rev_host: PlacesUtils.getReversedHost(url),
+ frecency: url.protocol == "place:" ? 0 : -1 });
+}
diff --git a/components/places/src/ClusterLib.js b/components/places/src/ClusterLib.js
new file mode 100644
index 000000000..ae5debff9
--- /dev/null
+++ b/components/places/src/ClusterLib.js
@@ -0,0 +1,248 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Class that can run the hierarchical clustering algorithm with the given
+ * parameters.
+ *
+ * @param distance
+ * Function that should return the distance between two items.
+ * Defaults to clusterlib.euclidean_distance.
+ * @param merge
+ * Function that should take in two items and return a merged one.
+ * Defaults to clusterlib.average_linkage.
+ * @param threshold
+ * The maximum distance between two items for which their clusters
+ * can be merged.
+ */
+function HierarchicalClustering(distance, merge, threshold) {
+ this.distance = distance || clusterlib.euclidean_distance;
+ this.merge = merge || clusterlib.average_linkage;
+ this.threshold = threshold == undefined ? Infinity : threshold;
+}
+
+HierarchicalClustering.prototype = {
+ /**
+ * Run the hierarchical clustering algorithm on the given items to produce
+ * a final set of clusters. Uses the parameters set in the constructor.
+ *
+ * @param items
+ * An array of "things" to cluster - this is the domain-specific
+ * collection you're trying to cluster (colors, points, etc.)
+ * @param snapshotGap
+ * How many iterations of the clustering algorithm to wait between
+ * calling the snapshotCallback
+ * @param snapshotCallback
+ * If provided, will be called as clusters are merged to let you view
+ * the progress of the algorithm. Passed the current array of
+ * clusters, cached distances, and cached closest clusters.
+ *
+ * @return An array of merged clusters. The represented item can be
+ * found in the "item" property of the cluster.
+ */
+ cluster: function HC_cluster(items, snapshotGap, snapshotCallback) {
+ // array of all remaining clusters
+ let clusters = [];
+ // 2D matrix of distances between each pair of clusters, indexed by key
+ let distances = [];
+ // closest cluster key for each cluster, indexed by key
+ let neighbors = [];
+ // an array of all clusters, but indexed by key
+ let clustersByKey = [];
+
+ // set up clusters from the initial items array
+ for (let index = 0; index < items.length; index++) {
+ let cluster = {
+ // the item this cluster represents
+ item: items[index],
+ // a unique key for this cluster, stays constant unless merged itself
+ key: index,
+ // index of cluster in clusters array, can change during any merge
+ index: index,
+ // how many clusters have been merged into this one
+ size: 1
+ };
+ clusters[index] = cluster;
+ clustersByKey[index] = cluster;
+ distances[index] = [];
+ neighbors[index] = 0;
+ }
+
+ // initialize distance matrix and cached neighbors
+ for (let i = 0; i < clusters.length; i++) {
+ for (let j = 0; j <= i; j++) {
+ var dist = (i == j) ? Infinity :
+ this.distance(clusters[i].item, clusters[j].item);
+ distances[i][j] = dist;
+ distances[j][i] = dist;
+
+ if (dist < distances[i][neighbors[i]]) {
+ neighbors[i] = j;
+ }
+ }
+ }
+
+ // merge the next two closest clusters until none of them are close enough
+ let next = null, i = 0;
+ for (; next = this.closestClusters(clusters, distances, neighbors); i++) {
+ if (snapshotCallback && (i % snapshotGap) == 0) {
+ snapshotCallback(clusters);
+ }
+ this.mergeClusters(clusters, distances, neighbors, clustersByKey,
+ clustersByKey[next[0]], clustersByKey[next[1]]);
+ }
+ return clusters;
+ },
+
+ /**
+ * Once we decide to merge two clusters in the cluster method, actually
+ * merge them. Alters the given state of the algorithm.
+ *
+ * @param clusters
+ * The array of all remaining clusters
+ * @param distances
+ * Cached distances between pairs of clusters
+ * @param neighbors
+ * Cached closest clusters
+ * @param clustersByKey
+ * Array of all clusters, indexed by key
+ * @param cluster1
+ * First cluster to merge
+ * @param cluster2
+ * Second cluster to merge
+ */
+ mergeClusters: function HC_mergeClus(clusters, distances, neighbors,
+ clustersByKey, cluster1, cluster2) {
+ let merged = { item: this.merge(cluster1.item, cluster2.item),
+ left: cluster1,
+ right: cluster2,
+ key: cluster1.key,
+ size: cluster1.size + cluster2.size };
+
+ clusters[cluster1.index] = merged;
+ clusters.splice(cluster2.index, 1);
+ clustersByKey[cluster1.key] = merged;
+
+ // update distances with new merged cluster
+ for (let i = 0; i < clusters.length; i++) {
+ var ci = clusters[i];
+ var dist;
+ if (cluster1.key == ci.key) {
+ dist = Infinity;
+ } else if (this.merge == clusterlib.single_linkage) {
+ dist = distances[cluster1.key][ci.key];
+ if (distances[cluster1.key][ci.key] >
+ distances[cluster2.key][ci.key]) {
+ dist = distances[cluster2.key][ci.key];
+ }
+ } else if (this.merge == clusterlib.complete_linkage) {
+ dist = distances[cluster1.key][ci.key];
+ if (distances[cluster1.key][ci.key] <
+ distances[cluster2.key][ci.key]) {
+ dist = distances[cluster2.key][ci.key];
+ }
+ } else if (this.merge == clusterlib.average_linkage) {
+ dist = (distances[cluster1.key][ci.key] * cluster1.size
+ + distances[cluster2.key][ci.key] * cluster2.size)
+ / (cluster1.size + cluster2.size);
+ } else {
+ dist = this.distance(ci.item, cluster1.item);
+ }
+
+ distances[cluster1.key][ci.key] = distances[ci.key][cluster1.key]
+ = dist;
+ }
+
+ // update cached neighbors
+ for (let i = 0; i < clusters.length; i++) {
+ var key1 = clusters[i].key;
+ if (neighbors[key1] == cluster1.key ||
+ neighbors[key1] == cluster2.key) {
+ let minKey = key1;
+ for (let j = 0; j < clusters.length; j++) {
+ var key2 = clusters[j].key;
+ if (distances[key1][key2] < distances[key1][minKey]) {
+ minKey = key2;
+ }
+ }
+ neighbors[key1] = minKey;
+ }
+ clusters[i].index = i;
+ }
+ },
+
+ /**
+ * Given the current state of the algorithm, return the keys of the two
+ * clusters that are closest to each other so we know which ones to merge
+ * next.
+ *
+ * @param clusters
+ * The array of all remaining clusters
+ * @param distances
+ * Cached distances between pairs of clusters
+ * @param neighbors
+ * Cached closest clusters
+ *
+ * @return An array of two keys of clusters to merge, or null if there are
+ * no more clusters close enough to merge
+ */
+ closestClusters: function HC_closestClus(clusters, distances, neighbors) {
+ let minKey = 0, minDist = Infinity;
+ for (let i = 0; i < clusters.length; i++) {
+ var key = clusters[i].key;
+ if (distances[key][neighbors[key]] < minDist) {
+ minKey = key;
+ minDist = distances[key][neighbors[key]];
+ }
+ }
+ if (minDist < this.threshold) {
+ return [minKey, neighbors[minKey]];
+ }
+ return null;
+ }
+};
+
+var clusterlib = {
+ hcluster: function hcluster(items, distance, merge, threshold, snapshotGap,
+ snapshotCallback) {
+ return (new HierarchicalClustering(distance, merge, threshold))
+ .cluster(items, snapshotGap, snapshotCallback);
+ },
+
+ single_linkage: function single_linkage(cluster1, cluster2) {
+ return cluster1;
+ },
+
+ complete_linkage: function complete_linkage(cluster1, cluster2) {
+ return cluster1;
+ },
+
+ average_linkage: function average_linkage(cluster1, cluster2) {
+ return cluster1;
+ },
+
+ euclidean_distance: function euclidean_distance(v1, v2) {
+ let total = 0;
+ for (let i = 0; i < v1.length; i++) {
+ total += Math.pow(v2[i] - v1[i], 2);
+ }
+ return Math.sqrt(total);
+ },
+
+ manhattan_distance: function manhattan_distance(v1, v2) {
+ let total = 0;
+ for (let i = 0; i < v1.length; i++) {
+ total += Math.abs(v2[i] - v1[i]);
+ }
+ return total;
+ },
+
+ max_distance: function max_distance(v1, v2) {
+ let max = 0;
+ for (let i = 0; i < v1.length; i++) {
+ max = Math.max(max, Math.abs(v2[i] - v1[i]));
+ }
+ return max;
+ }
+};
diff --git a/components/places/src/ColorAnalyzer.js b/components/places/src/ColorAnalyzer.js
new file mode 100644
index 000000000..861ce7107
--- /dev/null
+++ b/components/places/src/ColorAnalyzer.js
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+const MAXIMUM_PIXELS = Math.pow(144, 2);
+
+function ColorAnalyzer() {
+ // a queue of callbacks for each job we give to the worker
+ this.callbacks = [];
+
+ this.hiddenWindowDoc = Cc["@mozilla.org/appshell/appShellService;1"].
+ getService(Ci.nsIAppShellService).
+ hiddenDOMWindow.document;
+
+ this.worker = new ChromeWorker("resource://gre/modules/ColorAnalyzer_worker.js");
+ this.worker.onmessage = this.onWorkerMessage.bind(this);
+ this.worker.onerror = this.onWorkerError.bind(this);
+}
+
+ColorAnalyzer.prototype = {
+ findRepresentativeColor: function ColorAnalyzer_frc(imageURI, callback) {
+ function cleanup() {
+ image.removeEventListener("load", loadListener);
+ image.removeEventListener("error", errorListener);
+ }
+ let image = this.hiddenWindowDoc.createElementNS(XHTML_NS, "img");
+ let loadListener = this.onImageLoad.bind(this, image, callback, cleanup);
+ let errorListener = this.onImageError.bind(this, image, callback, cleanup);
+ image.addEventListener("load", loadListener);
+ image.addEventListener("error", errorListener);
+ image.src = imageURI.spec;
+ },
+
+ onImageLoad: function ColorAnalyzer_onImageLoad(image, callback, cleanup) {
+ if (image.naturalWidth * image.naturalHeight > MAXIMUM_PIXELS) {
+ // this will probably take too long to process - fail
+ callback.onComplete(false);
+ } else {
+ let canvas = this.hiddenWindowDoc.createElementNS(XHTML_NS, "canvas");
+ canvas.width = image.naturalWidth;
+ canvas.height = image.naturalHeight;
+ let ctx = canvas.getContext("2d");
+ ctx.drawImage(image, 0, 0);
+ this.startJob(ctx.getImageData(0, 0, canvas.width, canvas.height),
+ callback);
+ }
+ cleanup();
+ },
+
+ onImageError: function ColorAnalyzer_onImageError(image, callback, cleanup) {
+ Cu.reportError("ColorAnalyzer: image at " + image.src + " didn't load");
+ callback.onComplete(false);
+ cleanup();
+ },
+
+ startJob: function ColorAnalyzer_startJob(imageData, callback) {
+ this.callbacks.push(callback);
+ this.worker.postMessage({ imageData: imageData, maxColors: 1 });
+ },
+
+ onWorkerMessage: function ColorAnalyzer_onWorkerMessage(event) {
+ // colors can be empty on failure
+ if (event.data.colors.length < 1) {
+ this.callbacks.shift().onComplete(false);
+ } else {
+ this.callbacks.shift().onComplete(true, event.data.colors[0]);
+ }
+ },
+
+ onWorkerError: function ColorAnalyzer_onWorkerError(error) {
+ // this shouldn't happen, but just in case
+ error.preventDefault();
+ Cu.reportError("ColorAnalyzer worker: " + error.message);
+ this.callbacks.shift().onComplete(false);
+ },
+
+ classID: Components.ID("{d056186c-28a0-494e-aacc-9e433772b143}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.mozIColorAnalyzer])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ColorAnalyzer]);
diff --git a/components/places/src/ColorAnalyzer_worker.js b/components/places/src/ColorAnalyzer_worker.js
new file mode 100644
index 000000000..01fce0637
--- /dev/null
+++ b/components/places/src/ColorAnalyzer_worker.js
@@ -0,0 +1,392 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+importScripts("ClusterLib.js", "ColorConversion.js");
+
+// Offsets in the ImageData pixel array to reach pixel colors
+const PIXEL_RED = 0;
+const PIXEL_GREEN = 1;
+const PIXEL_BLUE = 2;
+const PIXEL_ALPHA = 3;
+
+// Number of components in one ImageData pixel (RGBA)
+const NUM_COMPONENTS = 4;
+
+// Shift a color represented as a 24 bit integer by N bits to get a component
+const RED_SHIFT = 16;
+const GREEN_SHIFT = 8;
+
+// Only run the N most frequent unique colors through the clustering algorithm
+// Images with more than this many unique colors will have reduced accuracy.
+const MAX_COLORS_TO_MERGE = 500;
+
+// Each cluster of colors has a mean color in the Lab color space.
+// If the euclidean distance between the means of two clusters is greater
+// than or equal to this threshold, they won't be merged.
+const MERGE_THRESHOLD = 12;
+
+// The highest the distance handicap can be for large clusters
+const MAX_SIZE_HANDICAP = 5;
+// If the handicap is below this number, it is cut off to zero
+const SIZE_HANDICAP_CUTOFF = 2;
+
+// If potential background colors deviate from the mean background color by
+// this threshold or greater, finding a background color will fail
+const BACKGROUND_THRESHOLD = 10;
+
+// Alpha component of colors must be larger than this in order to make it into
+// the clustering algorithm or be considered a background color (0 - 255).
+const MIN_ALPHA = 25;
+
+// The euclidean distance in the Lab color space under which merged colors
+// are weighted lower for being similar to the background color
+const BACKGROUND_WEIGHT_THRESHOLD = 15;
+
+// The range in which color chroma differences will affect desirability.
+// Colors with chroma outside of the range take on the desirability of
+// their nearest extremes. Should be roughly 0 - 150.
+const CHROMA_WEIGHT_UPPER = 90;
+const CHROMA_WEIGHT_LOWER = 1;
+const CHROMA_WEIGHT_MIDDLE = (CHROMA_WEIGHT_UPPER + CHROMA_WEIGHT_LOWER) / 2;
+
+/**
+ * When we receive a message from the outside world, find the representative
+ * colors of the given image. The colors will be posted back to the caller
+ * through the "colors" property on the event data object as an array of
+ * integers. Colors of lower indices are more representative.
+ * This array can be empty if this worker can't find a color.
+ *
+ * @param event
+ * A MessageEvent whose data should have the following properties:
+ * imageData - A DOM ImageData instance to analyze
+ * maxColors - The maximum number of representative colors to find,
+ * defaults to 1 if not provided
+ */
+onmessage = function(event) {
+ let imageData = event.data.imageData;
+ let pixels = imageData.data;
+ let width = imageData.width;
+ let height = imageData.height;
+ let maxColors = event.data.maxColors;
+ if (typeof(maxColors) != "number") {
+ maxColors = 1;
+ }
+
+ let allColors = getColors(pixels, width, height);
+
+ // Only merge top colors by frequency for speed.
+ let mergedColors = mergeColors(allColors.slice(0, MAX_COLORS_TO_MERGE),
+ width * height, MERGE_THRESHOLD);
+
+ let backgroundColor = getBackgroundColor(pixels, width, height);
+
+ mergedColors = mergedColors.map(function(cluster) {
+ // metadata holds a bunch of information about the color represented by
+ // this cluster
+ let metadata = cluster.item;
+
+ // the basis of color desirability is how much of the image the color is
+ // responsible for, but we'll need to weigh this number differently
+ // depending on other factors
+ metadata.desirability = metadata.ratio;
+ let weight = 1;
+
+ // if the color is close to the background color, we don't want it
+ if (backgroundColor != null) {
+ let backgroundDistance = labEuclidean(metadata.mean, backgroundColor);
+ if (backgroundDistance < BACKGROUND_WEIGHT_THRESHOLD) {
+ weight = backgroundDistance / BACKGROUND_WEIGHT_THRESHOLD;
+ }
+ }
+
+ // prefer more interesting colors, but don't knock low chroma colors
+ // completely out of the running (lower bound), and we don't really care
+ // if a color is slightly more intense than another on the higher end
+ let chroma = labChroma(metadata.mean);
+ if (chroma < CHROMA_WEIGHT_LOWER) {
+ chroma = CHROMA_WEIGHT_LOWER;
+ } else if (chroma > CHROMA_WEIGHT_UPPER) {
+ chroma = CHROMA_WEIGHT_UPPER;
+ }
+ weight *= chroma / CHROMA_WEIGHT_MIDDLE;
+
+ metadata.desirability *= weight;
+ return metadata;
+ });
+
+ // only send back the most desirable colors
+ mergedColors.sort(function(a, b) {
+ return b.desirability != a.desirability ? b.desirability - a.desirability : b.color - a.color;
+ });
+ mergedColors = mergedColors.map(function(metadata) {
+ return metadata.color;
+ }).slice(0, maxColors);
+ postMessage({ colors: mergedColors });
+};
+
+/**
+ * Given the pixel data and dimensions of an image, return an array of objects
+ * associating each unique color and its frequency in the image, sorted
+ * descending by frequency. Sufficiently transparent colors are ignored.
+ *
+ * @param pixels
+ * Pixel data array for the image to get colors from (ImageData.data).
+ * @param width
+ * Width of the image, in # of pixels.
+ * @param height
+ * Height of the image, in # of pixels.
+ *
+ * @return An array of objects with color and freq properties, sorted
+ * descending by freq
+ */
+function getColors(pixels, width, height) {
+ let colorFrequency = {};
+ for (let x = 0; x < width; x++) {
+ for (let y = 0; y < height; y++) {
+ let offset = (x * NUM_COMPONENTS) + (y * NUM_COMPONENTS * width);
+
+ if (pixels[offset + PIXEL_ALPHA] < MIN_ALPHA) {
+ continue;
+ }
+
+ let color = pixels[offset + PIXEL_RED] << RED_SHIFT
+ | pixels[offset + PIXEL_GREEN] << GREEN_SHIFT
+ | pixels[offset + PIXEL_BLUE];
+
+ if (color in colorFrequency) {
+ colorFrequency[color]++;
+ } else {
+ colorFrequency[color] = 1;
+ }
+ }
+ }
+
+ let colors = [];
+ for (var color in colorFrequency) {
+ colors.push({ color: +color, freq: colorFrequency[+color] });
+ }
+ colors.sort(descendingFreqSort);
+ return colors;
+}
+
+/**
+ * Given an array of objects from getColors, the number of pixels in the
+ * image, and a merge threshold, run the clustering algorithm on the colors
+ * and return the set of merged clusters.
+ *
+ * @param colorFrequencies
+ * An array of objects from getColors to cluster
+ * @param numPixels
+ * The number of pixels in the image
+ * @param threshold
+ * The maximum distance between two clusters for which those clusters
+ * can be merged.
+ *
+ * @return An array of merged clusters
+ *
+ * @see clusterlib.hcluster
+ * @see getColors
+ */
+function mergeColors(colorFrequencies, numPixels, threshold) {
+ let items = colorFrequencies.map(function(colorFrequency) {
+ let color = colorFrequency.color;
+ let freq = colorFrequency.freq;
+ return {
+ mean: rgb2lab(color >> RED_SHIFT, color >> GREEN_SHIFT & 0xff,
+ color & 0xff),
+ // the canonical color of the cluster
+ // (one w/ highest freq or closest to mean)
+ color: color,
+ colors: [color],
+ highFreq: freq,
+ highRatio: freq / numPixels,
+ // the individual color w/ the highest frequency in this cluster
+ highColor: color,
+ // ratio of image taken up by colors in this cluster
+ ratio: freq / numPixels,
+ freq: freq,
+ };
+ });
+
+ let merged = clusterlib.hcluster(items, distance, merge, threshold);
+ return merged;
+}
+
+function descendingFreqSort(a, b) {
+ return b.freq != a.freq ? b.freq - a.freq : b.color - a.color;
+}
+
+/**
+ * Given two items for a pair of clusters (as created in mergeColors above),
+ * determine the distance between them so we know if we should merge or not.
+ * Uses the euclidean distance between their mean colors in the lab color
+ * space, weighted so larger items are harder to merge.
+ *
+ * @param item1
+ * The first item to compare
+ * @param item2
+ * The second item to compare
+ *
+ * @return The distance between the two items
+ */
+function distance(item1, item2) {
+ // don't cluster large blocks of color unless they're really similar
+ let minRatio = Math.min(item1.ratio, item2.ratio);
+ let dist = labEuclidean(item1.mean, item2.mean);
+ let handicap = Math.min(MAX_SIZE_HANDICAP, dist * minRatio);
+ if (handicap <= SIZE_HANDICAP_CUTOFF) {
+ handicap = 0;
+ }
+ return dist + handicap;
+}
+
+/**
+ * Find the euclidean distance between two colors in the Lab color space.
+ *
+ * @param color1
+ * The first color to compare
+ * @param color2
+ * The second color to compare
+ *
+ * @return The euclidean distance between the two colors
+ */
+function labEuclidean(color1, color2) {
+ return Math.sqrt(
+ Math.pow(color2.lightness - color1.lightness, 2)
+ + Math.pow(color2.a - color1.a, 2)
+ + Math.pow(color2.b - color1.b, 2));
+}
+
+/**
+ * Given items from two clusters we know are appropriate for merging,
+ * merge them together into a third item such that its metadata describes both
+ * input items. The "color" property is set to the color in the new item that
+ * is closest to its mean color.
+ *
+ * @param item1
+ * The first item to merge
+ * @param item2
+ * The second item to merge
+ *
+ * @return An item that represents the merging of the given items
+ */
+function merge(item1, item2) {
+ let lab1 = item1.mean;
+ let lab2 = item2.mean;
+
+ /* algorithm tweak point - weighting the mean of the cluster */
+ let num1 = item1.freq;
+ let num2 = item2.freq;
+
+ let total = num1 + num2;
+
+ let mean = {
+ lightness: (lab1.lightness * num1 + lab2.lightness * num2) / total,
+ a: (lab1.a * num1 + lab2.a * num2) / total,
+ b: (lab1.b * num1 + lab2.b * num2) / total
+ };
+
+ let colors = item1.colors.concat(item2.colors);
+
+ // get the canonical color of the new cluster
+ let color;
+ let avgFreq = colors.length / (item1.freq + item2.freq);
+ if ((item1.highFreq > item2.highFreq) && (item1.highFreq > avgFreq * 2)) {
+ color = item1.highColor;
+ } else if (item2.highFreq > avgFreq * 2) {
+ color = item2.highColor;
+ } else {
+ // if there's no stand-out color
+ let minDist = Infinity, closest = 0;
+ for (let i = 0; i < colors.length; i++) {
+ let color = colors[i];
+ let lab = rgb2lab(color >> RED_SHIFT, color >> GREEN_SHIFT & 0xff,
+ color & 0xff);
+ let dist = labEuclidean(lab, mean);
+ if (dist < minDist) {
+ minDist = dist;
+ closest = i;
+ }
+ }
+ color = colors[closest];
+ }
+
+ const higherItem = item1.highFreq > item2.highFreq ? item1 : item2;
+
+ return {
+ mean: mean,
+ color: color,
+ highFreq: higherItem.highFreq,
+ highColor: higherItem.highColor,
+ highRatio: higherItem.highRatio,
+ ratio: item1.ratio + item2.ratio,
+ freq: item1.freq + item2.freq,
+ colors: colors,
+ };
+}
+
+/**
+ * Find the background color of the given image.
+ *
+ * @param pixels
+ * The pixel data for the image (an array of component integers)
+ * @param width
+ * The width of the image
+ * @param height
+ * The height of the image
+ *
+ * @return The background color of the image as a Lab object, or null if we
+ * can't determine the background color
+ */
+function getBackgroundColor(pixels, width, height) {
+ // we'll assume that if the four corners are roughly the same color,
+ // then that's the background color
+ let coordinates = [[0, 0], [width - 1, 0], [width - 1, height - 1],
+ [0, height - 1]];
+
+ // find the corner colors in LAB
+ let cornerColors = [];
+ for (let i = 0; i < coordinates.length; i++) {
+ let offset = (coordinates[i][0] * NUM_COMPONENTS)
+ + (coordinates[i][1] * NUM_COMPONENTS * width);
+ if (pixels[offset + PIXEL_ALPHA] < MIN_ALPHA) {
+ // we can't make very accurate judgements below this opacity
+ continue;
+ }
+ cornerColors.push(rgb2lab(pixels[offset + PIXEL_RED],
+ pixels[offset + PIXEL_GREEN],
+ pixels[offset + PIXEL_BLUE]));
+ }
+
+ // we want at least two points at acceptable alpha levels
+ if (cornerColors.length <= 1) {
+ return null;
+ }
+
+ // find the average color among the corners
+ let averageColor = { lightness: 0, a: 0, b: 0 };
+ cornerColors.forEach(function(color) {
+ for (let i in color) {
+ averageColor[i] += color[i];
+ }
+ });
+ for (let i in averageColor) {
+ averageColor[i] /= cornerColors.length;
+ }
+
+ // if we have fewer points due to low alpha, they need to be closer together
+ let threshold = BACKGROUND_THRESHOLD
+ * (cornerColors.length / coordinates.length);
+
+ // if any of the corner colors deviate enough from the average, they aren't
+ // similar enough to be considered the background color
+ for (let cornerColor of cornerColors) {
+ if (labEuclidean(cornerColor, averageColor) > threshold) {
+ return null;
+ }
+ }
+ return averageColor;
+}
diff --git a/components/places/src/ColorConversion.js b/components/places/src/ColorConversion.js
new file mode 100644
index 000000000..b8a2c860d
--- /dev/null
+++ b/components/places/src/ColorConversion.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Given a color in the Lab space, return its chroma (colorfulness,
+ * saturation).
+ *
+ * @param lab
+ * The lab color to get the chroma from
+ *
+ * @return A number greater than zero that measures chroma in the image
+ */
+function labChroma(lab) {
+ return Math.sqrt(Math.pow(lab.a, 2) + Math.pow(lab.b, 2));
+}
+
+/**
+ * Given the RGB components of a color as integers from 0-255, return the
+ * color in the XYZ color space.
+ *
+ * @return An object with x, y, z properties holding those components of the
+ * color in the XYZ color space.
+ */
+function rgb2xyz(r, g, b) {
+ r /= 255;
+ g /= 255;
+ b /= 255;
+
+ // assume sRGB
+ r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
+ g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
+ b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
+
+ return {
+ x: ((r * 0.4124) + (g * 0.3576) + (b * 0.1805)) * 100,
+ y: ((r * 0.2126) + (g * 0.7152) + (b * 0.0722)) * 100,
+ z: ((r * 0.0193) + (g * 0.1192) + (b * 0.9505)) * 100
+ };
+}
+
+/**
+ * Given the RGB components of a color as integers from 0-255, return the
+ * color in the Lab color space.
+ *
+ * @return An object with lightness, a, b properties holding those components
+ * of the color in the Lab color space.
+ */
+function rgb2lab(r, g, b) {
+ let xyz = rgb2xyz(r, g, b),
+ x = xyz.x / 95.047,
+ y = xyz.y / 100,
+ z = xyz.z / 108.883;
+
+ x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
+ y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
+ z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
+
+ return {
+ lightness: (116 * y) - 16,
+ a: 500 * (x - y),
+ b: 200 * (y - z)
+ };
+}
diff --git a/components/places/src/Database.cpp b/components/places/src/Database.cpp
new file mode 100644
index 000000000..08c382377
--- /dev/null
+++ b/components/places/src/Database.cpp
@@ -0,0 +1,2330 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ScopeExit.h"
+
+#include "Database.h"
+
+#include "nsIAnnotationService.h"
+#include "nsINavBookmarksService.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIFile.h"
+#include "nsIWritablePropertyBag2.h"
+
+#include "nsNavHistory.h"
+#include "nsPlacesTables.h"
+#include "nsPlacesIndexes.h"
+#include "nsPlacesTriggers.h"
+#include "nsPlacesMacros.h"
+#include "nsVariant.h"
+#include "SQLFunctions.h"
+#include "Helpers.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "prenv.h"
+#include "prsystem.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "prtime.h"
+
+#include "nsXULAppAPI.h"
+
+// Time between corrupt database backups.
+#define RECENT_BACKUP_TIME_MICROSEC (int64_t)86400 * PR_USEC_PER_SEC // 24H
+
+// Filename of the database.
+#define DATABASE_FILENAME NS_LITERAL_STRING("places.sqlite")
+// Filename used to backup corrupt databases.
+#define DATABASE_CORRUPT_FILENAME NS_LITERAL_STRING("places.sqlite.corrupt")
+
+// Set when the database file was found corrupt by a previous maintenance.
+#define PREF_FORCE_DATABASE_REPLACEMENT "places.database.replaceOnStartup"
+
+// Set to specify the size of the places database growth increments in kibibytes
+#define PREF_GROWTH_INCREMENT_KIB "places.database.growthIncrementKiB"
+
+// Set to disable the default robust storage and use volatile, in-memory
+// storage without robust transaction flushing guarantees. This makes
+// SQLite use much less I/O at the cost of losing data when things crash.
+// The pref is only honored if an environment variable is set. The env
+// variable is intentionally named something scary to help prevent someone
+// from thinking it is a useful performance optimization they should enable.
+#define PREF_DISABLE_DURABILITY "places.database.disableDurability"
+#define ENV_ALLOW_CORRUPTION "ALLOW_PLACES_DATABASE_TO_LOSE_DATA_AND_BECOME_CORRUPT"
+
+// The maximum url length we can store in history.
+// We do not add to history URLs longer than this value.
+#define PREF_HISTORY_MAXURLLEN "places.history.maxUrlLength"
+// This number is mostly a guess based on various facts:
+// * IE didn't support urls longer than 2083 chars
+// * Sitemaps protocol used to support a maximum of 2048 chars
+// * Various SEO guides suggest to not go over 2000 chars
+// * Various apps/services are known to have issues over 2000 chars
+// * RFC 2616 - HTTP/1.1 suggests being cautious about depending
+// on URI lengths above 255 bytes
+#define PREF_HISTORY_MAXURLLEN_DEFAULT 2000
+
+// Maximum size for the WAL file. It should be small enough since in case of
+// crashes we could lose all the transactions in the file. But a too small
+// file could hurt performance.
+#define DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES 512
+
+#define BYTES_PER_KIBIBYTE 1024
+
+// How much time Sqlite can wait before returning a SQLITE_BUSY error.
+#define DATABASE_BUSY_TIMEOUT_MS 100
+
+// Old Sync GUID annotation.
+#define SYNCGUID_ANNO NS_LITERAL_CSTRING("sync/guid")
+
+// Places string bundle, contains internationalized bookmark root names.
+#define PLACES_BUNDLE "chrome://places/locale/places.properties"
+
+// Livemarks annotations.
+#define LMANNO_FEEDURI "livemark/feedURI"
+#define LMANNO_SITEURI "livemark/siteURI"
+
+#define MOBILE_ROOT_GUID "mobile______"
+#define MOBILE_ROOT_ANNO "mobile/bookmarksRoot"
+
+// We use a fixed title for the mobile root to avoid marking the database as
+// corrupt if we can't look up the localized title in the string bundle. Sync
+// sets the title to the localized version when it creates the left pane query.
+#define MOBILE_ROOT_TITLE "mobile"
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace places {
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers
+
+/**
+ * Checks whether exists a database backup created not longer than
+ * RECENT_BACKUP_TIME_MICROSEC ago.
+ */
+bool
+hasRecentCorruptDB()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIFile> profDir;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profDir));
+ NS_ENSURE_TRUE(profDir, false);
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ profDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_TRUE(entries, false);
+ bool hasMore;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> next;
+ entries->GetNext(getter_AddRefs(next));
+ NS_ENSURE_TRUE(next, false);
+ nsCOMPtr<nsIFile> currFile = do_QueryInterface(next);
+ NS_ENSURE_TRUE(currFile, false);
+
+ nsAutoString leafName;
+ if (NS_SUCCEEDED(currFile->GetLeafName(leafName)) &&
+ leafName.Length() >= DATABASE_CORRUPT_FILENAME.Length() &&
+ leafName.Find(".corrupt", DATABASE_FILENAME.Length()) != -1) {
+ PRTime lastMod = 0;
+ currFile->GetLastModifiedTime(&lastMod);
+ NS_ENSURE_TRUE(lastMod > 0, false);
+ return (PR_Now() - lastMod) > RECENT_BACKUP_TIME_MICROSEC;
+ }
+ }
+ return false;
+}
+
+/**
+ * Updates sqlite_stat1 table through ANALYZE.
+ * Since also nsPlacesExpiration.js executes ANALYZE, the analyzed tables
+ * must be the same in both components. So ensure they are in sync.
+ *
+ * @param aDBConn
+ * The database connection.
+ */
+nsresult
+updateSQLiteStatistics(mozIStorageConnection* aDBConn)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<mozIStorageAsyncStatement> analyzePlacesStmt;
+ aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "ANALYZE moz_places"
+ ), getter_AddRefs(analyzePlacesStmt));
+ NS_ENSURE_STATE(analyzePlacesStmt);
+ nsCOMPtr<mozIStorageAsyncStatement> analyzeBookmarksStmt;
+ aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "ANALYZE moz_bookmarks"
+ ), getter_AddRefs(analyzeBookmarksStmt));
+ NS_ENSURE_STATE(analyzeBookmarksStmt);
+ nsCOMPtr<mozIStorageAsyncStatement> analyzeVisitsStmt;
+ aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "ANALYZE moz_historyvisits"
+ ), getter_AddRefs(analyzeVisitsStmt));
+ NS_ENSURE_STATE(analyzeVisitsStmt);
+ nsCOMPtr<mozIStorageAsyncStatement> analyzeInputStmt;
+ aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "ANALYZE moz_inputhistory"
+ ), getter_AddRefs(analyzeInputStmt));
+ NS_ENSURE_STATE(analyzeInputStmt);
+
+ mozIStorageBaseStatement *stmts[] = {
+ analyzePlacesStmt,
+ analyzeBookmarksStmt,
+ analyzeVisitsStmt,
+ analyzeInputStmt
+ };
+
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ (void)aDBConn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
+ getter_AddRefs(ps));
+ return NS_OK;
+}
+
+/**
+ * Sets the connection journal mode to one of the JOURNAL_* types.
+ *
+ * @param aDBConn
+ * The database connection.
+ * @param aJournalMode
+ * One of the JOURNAL_* types.
+ * @returns the current journal mode.
+ * @note this may return a different journal mode than the required one, since
+ * setting it may fail.
+ */
+enum JournalMode
+SetJournalMode(nsCOMPtr<mozIStorageConnection>& aDBConn,
+ enum JournalMode aJournalMode)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsAutoCString journalMode;
+ switch (aJournalMode) {
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Trying to set an unknown journal mode.");
+ // Fall through to the default DELETE journal.
+ case JOURNAL_DELETE:
+ journalMode.AssignLiteral("delete");
+ break;
+ case JOURNAL_TRUNCATE:
+ journalMode.AssignLiteral("truncate");
+ break;
+ case JOURNAL_MEMORY:
+ journalMode.AssignLiteral("memory");
+ break;
+ case JOURNAL_WAL:
+ journalMode.AssignLiteral("wal");
+ break;
+ }
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR
+ "PRAGMA journal_mode = ");
+ query.Append(journalMode);
+ aDBConn->CreateStatement(query, getter_AddRefs(statement));
+ NS_ENSURE_TRUE(statement, JOURNAL_DELETE);
+
+ bool hasResult = false;
+ if (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) && hasResult &&
+ NS_SUCCEEDED(statement->GetUTF8String(0, journalMode))) {
+ if (journalMode.EqualsLiteral("delete")) {
+ return JOURNAL_DELETE;
+ }
+ if (journalMode.EqualsLiteral("truncate")) {
+ return JOURNAL_TRUNCATE;
+ }
+ if (journalMode.EqualsLiteral("memory")) {
+ return JOURNAL_MEMORY;
+ }
+ if (journalMode.EqualsLiteral("wal")) {
+ return JOURNAL_WAL;
+ }
+ // This is an unknown journal.
+ MOZ_ASSERT(true);
+ }
+
+ return JOURNAL_DELETE;
+}
+
+nsresult
+CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
+ const nsCString& aRootName, const nsCString& aGuid,
+ const nsXPIDLString& titleString)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // The position of the new item in its folder.
+ static int32_t itemPosition = 0;
+
+ // A single creation timestamp for all roots so that the root folder's
+ // last modification time isn't earlier than its childrens' creation time.
+ static PRTime timestamp = 0;
+ if (!timestamp)
+ timestamp = RoundedPRNow();
+
+ // Create a new bookmark folder for the root.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_bookmarks "
+ "(type, position, title, dateAdded, lastModified, guid, parent) "
+ "VALUES (:item_type, :item_position, :item_title,"
+ ":date_added, :last_modified, :guid,"
+ "IFNULL((SELECT id FROM moz_bookmarks WHERE parent = 0), 0))"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"),
+ nsINavBookmarksService::TYPE_FOLDER);
+ if (NS_FAILED(rv)) return rv;
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"), itemPosition);
+ if (NS_FAILED(rv)) return rv;
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
+ NS_ConvertUTF16toUTF8(titleString));
+ if (NS_FAILED(rv)) return rv;
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), timestamp);
+ if (NS_FAILED(rv)) return rv;
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), timestamp);
+ if (NS_FAILED(rv)) return rv;
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGuid);
+ if (NS_FAILED(rv)) return rv;
+ rv = stmt->Execute();
+ if (NS_FAILED(rv)) return rv;
+
+ // The 'places' root is a folder containing the other roots.
+ // The first bookmark in a folder has position 0.
+ if (!aRootName.EqualsLiteral("places"))
+ ++itemPosition;
+
+ return NS_OK;
+}
+
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// Database
+
+PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database, gDatabase)
+
+NS_IMPL_ISUPPORTS(Database
+, nsIObserver
+, nsISupportsWeakReference
+)
+
+Database::Database()
+ : mMainThreadStatements(mMainConn)
+ , mMainThreadAsyncStatements(mMainConn)
+ , mAsyncThreadStatements(mMainConn)
+ , mDBPageSize(0)
+ , mDatabaseStatus(nsINavHistoryService::DATABASE_STATUS_OK)
+ , mClosed(false)
+ , mClientsShutdown(new ClientsShutdownBlocker())
+ , mConnectionShutdown(new ConnectionShutdownBlocker(this))
+ , mMaxUrlLength(0)
+{
+ MOZ_ASSERT(!XRE_IsContentProcess(),
+ "Cannot instantiate Places in the content process");
+ // Attempting to create two instances of the service?
+ MOZ_ASSERT(!gDatabase);
+ gDatabase = this;
+}
+
+already_AddRefed<nsIAsyncShutdownClient>
+Database::GetProfileChangeTeardownPhase()
+{
+ nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc = services::GetAsyncShutdown();
+ MOZ_ASSERT(asyncShutdownSvc);
+ if (NS_WARN_IF(!asyncShutdownSvc)) {
+ return nullptr;
+ }
+
+ // Consumers of Places should shutdown before us, at profile-change-teardown.
+ nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
+ DebugOnly<nsresult> rv = asyncShutdownSvc->
+ GetProfileChangeTeardown(getter_AddRefs(shutdownPhase));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return shutdownPhase.forget();
+}
+
+already_AddRefed<nsIAsyncShutdownClient>
+Database::GetProfileBeforeChangePhase()
+{
+ nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc = services::GetAsyncShutdown();
+ MOZ_ASSERT(asyncShutdownSvc);
+ if (NS_WARN_IF(!asyncShutdownSvc)) {
+ return nullptr;
+ }
+
+ // Consumers of Places should shutdown before us, at profile-change-teardown.
+ nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
+ DebugOnly<nsresult> rv = asyncShutdownSvc->
+ GetProfileBeforeChange(getter_AddRefs(shutdownPhase));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return shutdownPhase.forget();
+}
+
+Database::~Database()
+{
+}
+
+bool
+Database::IsShutdownStarted() const
+{
+ if (!mConnectionShutdown) {
+ // We have already broken the cycle between `this` and `mConnectionShutdown`.
+ return true;
+ }
+ return mConnectionShutdown->IsStarted();
+}
+
+already_AddRefed<mozIStorageAsyncStatement>
+Database::GetAsyncStatement(const nsACString& aQuery) const
+{
+ if (IsShutdownStarted()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(NS_IsMainThread());
+ return mMainThreadAsyncStatements.GetCachedStatement(aQuery);
+}
+
+already_AddRefed<mozIStorageStatement>
+Database::GetStatement(const nsACString& aQuery) const
+{
+ if (IsShutdownStarted()) {
+ return nullptr;
+ }
+ if (NS_IsMainThread()) {
+ return mMainThreadStatements.GetCachedStatement(aQuery);
+ }
+ return mAsyncThreadStatements.GetCachedStatement(aQuery);
+}
+
+already_AddRefed<nsIAsyncShutdownClient>
+Database::GetClientsShutdown()
+{
+ MOZ_ASSERT(mClientsShutdown);
+ return mClientsShutdown->GetClient();
+}
+
+// static
+already_AddRefed<Database>
+Database::GetDatabase()
+{
+ if (PlacesShutdownBlocker::IsStarted()) {
+ return nullptr;
+ }
+ return GetSingleton();
+}
+
+nsresult
+Database::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<mozIStorageService> storage =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ NS_ENSURE_STATE(storage);
+
+ // Init the database file and connect to it.
+ bool databaseCreated = false;
+ nsresult rv = InitDatabaseFile(storage, &databaseCreated);
+ if (NS_SUCCEEDED(rv) && databaseCreated) {
+ mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CREATE;
+ }
+ else if (rv == NS_ERROR_FILE_CORRUPTED) {
+ // The database is corrupt, backup and replace it with a new one.
+ mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
+ rv = BackupAndReplaceDatabaseFile(storage);
+ // Fallback to catch-all handler, that notifies a database locked failure.
+ }
+
+ // If the database connection still cannot be opened, it may just be locked
+ // by third parties. Send out a notification and interrupt initialization.
+ if (NS_FAILED(rv)) {
+ RefPtr<PlacesEvent> lockedEvent = new PlacesEvent(TOPIC_DATABASE_LOCKED);
+ (void)NS_DispatchToMainThread(lockedEvent);
+ return rv;
+ }
+
+ // Initialize the database schema. In case of failure the existing schema is
+ // is corrupt or incoherent, thus the database should be replaced.
+ bool databaseMigrated = false;
+ rv = InitSchema(&databaseMigrated);
+ if (NS_FAILED(rv)) {
+ mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
+ rv = BackupAndReplaceDatabaseFile(storage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Try to initialize the schema again on the new database.
+ rv = InitSchema(&databaseMigrated);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (databaseMigrated) {
+ mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
+ }
+
+ if (mDatabaseStatus != nsINavHistoryService::DATABASE_STATUS_OK) {
+ rv = updateSQLiteStatistics(MainConn());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Initialize here all the items that are not part of the on-disk database,
+ // like views, temp triggers or temp tables. The database should not be
+ // considered corrupt if any of the following fails.
+
+ rv = InitTempEntities();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Notify we have finished database initialization.
+ // Enqueue the notification, so if we init another service that requires
+ // nsNavHistoryService we don't recursive try to get it.
+ RefPtr<PlacesEvent> completeEvent =
+ new PlacesEvent(TOPIC_PLACES_INIT_COMPLETE);
+ rv = NS_DispatchToMainThread(completeEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // At this point we know the Database object points to a valid connection
+ // and we need to setup async shutdown.
+ {
+ // First of all Places clients should block profile-change-teardown.
+ nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileChangeTeardownPhase();
+ MOZ_ASSERT(shutdownPhase);
+ if (shutdownPhase) {
+ DebugOnly<nsresult> rv = shutdownPhase->AddBlocker(
+ static_cast<nsIAsyncShutdownBlocker*>(mClientsShutdown.get()),
+ NS_LITERAL_STRING(__FILE__),
+ __LINE__,
+ NS_LITERAL_STRING(""));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ {
+ // Then connection closing should block profile-before-change.
+ nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileBeforeChangePhase();
+ MOZ_ASSERT(shutdownPhase);
+ if (shutdownPhase) {
+ DebugOnly<nsresult> rv = shutdownPhase->AddBlocker(
+ static_cast<nsIAsyncShutdownBlocker*>(mConnectionShutdown.get()),
+ NS_LITERAL_STRING(__FILE__),
+ __LINE__,
+ NS_LITERAL_STRING(""));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // Finally observe profile shutdown notifications.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ (void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Database::InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
+ bool* aNewDatabaseCreated)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ *aNewDatabaseCreated = false;
+
+ nsCOMPtr<nsIFile> databaseFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(databaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = databaseFile->Append(DATABASE_FILENAME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool databaseFileExists = false;
+ rv = databaseFile->Exists(&databaseFileExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (databaseFileExists &&
+ Preferences::GetBool(PREF_FORCE_DATABASE_REPLACEMENT, false)) {
+ // If this pref is set, Maintenance required a database replacement, due to
+ // integrity corruption.
+ // Be sure to clear the pref to avoid handling it more than once.
+ (void)Preferences::ClearUser(PREF_FORCE_DATABASE_REPLACEMENT);
+
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Open the database file. If it does not exist a new one will be created.
+ // Use an unshared connection, it will consume more memory but avoid shared
+ // cache contentions across threads.
+ rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aNewDatabaseCreated = !databaseFileExists;
+ return NS_OK;
+}
+
+nsresult
+Database::BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIFile> profDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> databaseFile;
+ rv = profDir->Clone(getter_AddRefs(databaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = databaseFile->Append(DATABASE_FILENAME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we have
+ // already failed in the last 24 hours avoid to create another corrupt file,
+ // since doing so, in some situation, could cause us to create a new corrupt
+ // file at every try to access any Places service. That is bad because it
+ // would quickly fill the user's disk space without any notice.
+ if (!hasRecentCorruptDB()) {
+ nsCOMPtr<nsIFile> backup;
+ (void)aStorage->BackupDatabaseFile(databaseFile, DATABASE_CORRUPT_FILENAME,
+ profDir, getter_AddRefs(backup));
+ }
+
+ // If anything fails from this point on, we have a stale connection or
+ // database file, and there's not much more we can do.
+ // The only thing we can try to do is to replace the database on the next
+ // startup.
+ {
+ enum eCorruptDBReplaceStage : int8_t {
+ stage_closing = 0,
+ stage_removing,
+ stage_reopening,
+ stage_replaced
+ };
+ eCorruptDBReplaceStage stage = stage_closing;
+ auto guard = MakeScopeExit([&]() {
+ if (stage != stage_replaced) {
+ // Reaching this point means the database is corrupt and we failed to
+ // replace it. For this session part of the application related to
+ // bookmarks and history will misbehave. The frontend may show a
+ // "locked" notification to the user though.
+ // Set up a pref to try replacing the database at the next startup.
+ Preferences::SetBool(PREF_FORCE_DATABASE_REPLACEMENT, true);
+ }
+ });
+
+ // Close database connection if open.
+ if (mMainConn) {
+ rv = mMainConn->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Remove the broken database.
+ stage = stage_removing;
+ rv = databaseFile->Remove(false);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ return rv;
+ }
+
+ // Create a new database file.
+ // Use an unshared connection, it will consume more memory but avoid shared
+ // cache contentions across threads.
+ stage = stage_reopening;
+ rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ stage = stage_replaced;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Database::InitSchema(bool* aDatabaseMigrated)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ *aDatabaseMigrated = false;
+
+ // WARNING: any statement executed before setting the journal mode must be
+ // finalized, since SQLite doesn't allow changing the journal mode if there
+ // is any outstanding statement.
+
+ {
+ // Get the page size. This may be different than the default if the
+ // database file already existed with a different page size.
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
+ ), getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool hasResult = false;
+ rv = statement->ExecuteStep(&hasResult);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
+ rv = statement->GetInt32(0, &mDBPageSize);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && mDBPageSize > 0, NS_ERROR_UNEXPECTED);
+ }
+
+ // Ensure that temp tables are held in memory, not on disk.
+ nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (PR_GetEnv(ENV_ALLOW_CORRUPTION) && Preferences::GetBool(PREF_DISABLE_DURABILITY, false)) {
+ // Volatile storage was requested. Use the in-memory journal (no
+ // filesystem I/O) and don't sync the filesystem after writing.
+ SetJournalMode(mMainConn, JOURNAL_MEMORY);
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA synchronous = OFF"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // Be sure to set journal mode after page_size. WAL would prevent the change
+ // otherwise.
+ if (JOURNAL_WAL == SetJournalMode(mMainConn, JOURNAL_WAL)) {
+ // Set the WAL journal size limit. We want it to be small, since in
+ // synchronous = NORMAL mode a crash could cause loss of all the
+ // transactions in the journal. For added safety we will also force
+ // checkpointing at strategic moments.
+ int32_t checkpointPages =
+ static_cast<int32_t>(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 1024 / mDBPageSize);
+ nsAutoCString checkpointPragma("PRAGMA wal_autocheckpoint = ");
+ checkpointPragma.AppendInt(checkpointPages);
+ rv = mMainConn->ExecuteSimpleSQL(checkpointPragma);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // Ignore errors, if we fail here the database could be considered corrupt
+ // and we won't be able to go on, even if it's just matter of a bogus file
+ // system. The default mode (DELETE) will be fine in such a case.
+ (void)SetJournalMode(mMainConn, JOURNAL_TRUNCATE);
+
+ // Set synchronous to FULL to ensure maximum data integrity, even in
+ // case of crashes or unclean shutdowns.
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA synchronous = FULL"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // The journal is usually free to grow for performance reasons, but it never
+ // shrinks back. Since the space taken may be problematic, especially on
+ // mobile devices, limit its size.
+ // Since exceeding the limit will cause a truncate, allow a slightly
+ // larger limit than DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES to reduce the number
+ // of times it is needed.
+ nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
+ journalSizePragma.AppendInt(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 3);
+ (void)mMainConn->ExecuteSimpleSQL(journalSizePragma);
+
+ // Grow places in |growthIncrementKiB| increments to limit fragmentation on disk.
+ // By default, it's 10 MB.
+ int32_t growthIncrementKiB =
+ Preferences::GetInt(PREF_GROWTH_INCREMENT_KIB, 10 * BYTES_PER_KIBIBYTE);
+ if (growthIncrementKiB > 0) {
+ (void)mMainConn->SetGrowthIncrement(growthIncrementKiB * BYTES_PER_KIBIBYTE, EmptyCString());
+ }
+
+ nsAutoCString busyTimeoutPragma("PRAGMA busy_timeout = ");
+ busyTimeoutPragma.AppendInt(DATABASE_BUSY_TIMEOUT_MS);
+ (void)mMainConn->ExecuteSimpleSQL(busyTimeoutPragma);
+
+ // We use our functions during migration, so initialize them now.
+ rv = InitFunctions();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the database schema version.
+ int32_t currentSchemaVersion;
+ rv = mMainConn->GetSchemaVersion(&currentSchemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool databaseInitialized = currentSchemaVersion > 0;
+
+ if (databaseInitialized && currentSchemaVersion == DATABASE_SCHEMA_VERSION) {
+ // The database is up to date and ready to go.
+ return NS_OK;
+ }
+
+ // We are going to update the database, so everything from now on should be in
+ // a transaction for performances.
+ mozStorageTransaction transaction(mMainConn, false);
+
+ if (databaseInitialized) {
+ // Migration How-to:
+ //
+ // 1. increment PLACES_SCHEMA_VERSION.
+ // 2. implement a method that performs upgrade to your version from the
+ // previous one.
+ //
+ // NOTE: The downgrade process is pretty much complicated by the fact old
+ // versions cannot know what a new version is going to implement.
+ // The only thing we will do for downgrades is setting back the schema
+ // version, so that next upgrades will run again the migration step.
+
+ if (currentSchemaVersion > 36) {
+ // These versions are not downgradable.
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ if (currentSchemaVersion < DATABASE_SCHEMA_VERSION) {
+ *aDatabaseMigrated = true;
+
+ if (currentSchemaVersion < 11) {
+ // These are versions older than Firefox 4 that are not supported
+ // anymore. In this case it's safer to just replace the database.
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ // Firefox 4 uses schema version 11.
+
+ // Firefox 8 uses schema version 12.
+
+ if (currentSchemaVersion < 13) {
+ rv = MigrateV13Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (currentSchemaVersion < 15) {
+ rv = MigrateV15Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (currentSchemaVersion < 17) {
+ rv = MigrateV17Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 12 uses schema version 17.
+
+ if (currentSchemaVersion < 18) {
+ rv = MigrateV18Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (currentSchemaVersion < 19) {
+ rv = MigrateV19Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 13 uses schema version 19.
+
+ if (currentSchemaVersion < 20) {
+ rv = MigrateV20Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (currentSchemaVersion < 21) {
+ rv = MigrateV21Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 14 uses schema version 21.
+
+ if (currentSchemaVersion < 22) {
+ rv = MigrateV22Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 22 uses schema version 22.
+
+ if (currentSchemaVersion < 23) {
+ rv = MigrateV23Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 24 uses schema version 23.
+
+ if (currentSchemaVersion < 24) {
+ rv = MigrateV24Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 34 uses schema version 24.
+
+ if (currentSchemaVersion < 25) {
+ rv = MigrateV25Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 36 uses schema version 25.
+
+ if (currentSchemaVersion < 26) {
+ rv = MigrateV26Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 37 uses schema version 26.
+
+ if (currentSchemaVersion < 27) {
+ rv = MigrateV27Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (currentSchemaVersion < 28) {
+ rv = MigrateV28Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 39 uses schema version 28.
+
+ if (currentSchemaVersion < 30) {
+ rv = MigrateV30Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 41 uses schema version 30.
+
+ if (currentSchemaVersion < 31) {
+ rv = MigrateV31Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 48 uses schema version 31.
+
+ if (currentSchemaVersion < 32) {
+ rv = MigrateV32Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 49 uses schema version 32.
+
+ if (currentSchemaVersion < 33) {
+ rv = MigrateV33Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 50 uses schema version 33.
+
+ if (currentSchemaVersion < 34) {
+ rv = MigrateV34Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 51 uses schema version 34.
+
+ if (currentSchemaVersion < 35) {
+ rv = MigrateV35Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 52 uses schema version 35.
+
+ // Schema Upgrades must add migration code here.
+
+ rv = UpdateBookmarkRootTitles();
+ // We don't want a broken localization to cause us to think
+ // the database is corrupt and needs to be replaced.
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ else {
+ // This is a new database, so we have to create all the tables and indices.
+
+ // moz_places.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FAVICON);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_REVHOST);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_VISITCOUNT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // moz_historyvisits.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // moz_inputhistory.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // moz_hosts.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // moz_bookmarks.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // moz_keywords.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // moz_favicons.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_FAVICONS);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // moz_anno_attributes.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNO_ATTRIBUTES);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // moz_annos.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNOS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // moz_items_annos.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ITEMS_ANNOS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Initialize the bookmark roots in the new DB.
+ rv = CreateBookmarkRoots();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set the schema version to the current one.
+ rv = mMainConn->SetSchemaVersion(DATABASE_SCHEMA_VERSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ForceWALCheckpoint();
+
+ // ANY FAILURE IN THIS METHOD WILL CAUSE US TO MARK THE DATABASE AS CORRUPT
+ // AND TRY TO REPLACE IT.
+ // DO NOT PUT HERE ANYTHING THAT IS NOT RELATED TO INITIALIZATION OR MODIFYING
+ // THE DISK DATABASE.
+
+ return NS_OK;
+}
+
+nsresult
+Database::CreateBookmarkRoots()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ services::GetStringBundleService();
+ NS_ENSURE_STATE(bundleService);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle(PLACES_BUNDLE, getter_AddRefs(bundle));
+ if (NS_FAILED(rv)) return rv;
+
+ nsXPIDLString rootTitle;
+ // The first root's title is an empty string.
+ rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("places"),
+ NS_LITERAL_CSTRING("root________"), rootTitle);
+ if (NS_FAILED(rv)) return rv;
+
+ // Fetch the internationalized folder name from the string bundle.
+ rv = bundle->GetStringFromName(u"BookmarksMenuFolderTitle",
+ getter_Copies(rootTitle));
+ if (NS_FAILED(rv)) return rv;
+ rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("menu"),
+ NS_LITERAL_CSTRING("menu________"), rootTitle);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = bundle->GetStringFromName(u"BookmarksToolbarFolderTitle",
+ getter_Copies(rootTitle));
+ if (NS_FAILED(rv)) return rv;
+ rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("toolbar"),
+ NS_LITERAL_CSTRING("toolbar_____"), rootTitle);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = bundle->GetStringFromName(u"TagsFolderTitle",
+ getter_Copies(rootTitle));
+ if (NS_FAILED(rv)) return rv;
+ rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("tags"),
+ NS_LITERAL_CSTRING("tags________"), rootTitle);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = bundle->GetStringFromName(u"OtherBookmarksFolderTitle",
+ getter_Copies(rootTitle));
+ if (NS_FAILED(rv)) return rv;
+ rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"),
+ NS_LITERAL_CSTRING("unfiled_____"), rootTitle);
+ if (NS_FAILED(rv)) return rv;
+
+ int64_t mobileRootId = CreateMobileRoot();
+ if (mobileRootId <= 0) return NS_ERROR_FAILURE;
+
+#if DEBUG
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT count(*), sum(position) FROM moz_bookmarks"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) return rv;
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ if (NS_FAILED(rv)) return rv;
+ MOZ_ASSERT(hasResult);
+ int32_t bookmarkCount = stmt->AsInt32(0);
+ int32_t positionSum = stmt->AsInt32(1);
+ MOZ_ASSERT(bookmarkCount == 6 && positionSum == 10);
+#endif
+
+ return NS_OK;
+}
+
+nsresult
+Database::InitFunctions()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = GetUnreversedHostFunction::create(mMainConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = MatchAutoCompleteFunction::create(mMainConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = CalculateFrecencyFunction::create(mMainConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GenerateGUIDFunction::create(mMainConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = FixupURLFunction::create(mMainConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = FrecencyNotificationFunction::create(mMainConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StoreLastInsertedIdFunction::create(mMainConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = HashFunction::create(mMainConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::InitTempEntities()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add the triggers that update the moz_hosts table as necessary.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERINSERT_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEHOSTS_TEMP);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEHOSTS_AFTERDELETE_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERDELETE_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_TYPED_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::UpdateBookmarkRootTitles()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ services::GetStringBundleService();
+ NS_ENSURE_STATE(bundleService);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = bundleService->CreateBundle(PLACES_BUNDLE, getter_AddRefs(bundle));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_bookmarks SET title = :new_title WHERE guid = :guid"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ rv = stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ if (NS_FAILED(rv)) return rv;
+
+ const char *rootGuids[] = { "menu________"
+ , "toolbar_____"
+ , "tags________"
+ , "unfiled_____"
+ , "mobile______"
+ };
+ const char *titleStringIDs[] = { "BookmarksMenuFolderTitle"
+ , "BookmarksToolbarFolderTitle"
+ , "TagsFolderTitle"
+ , "OtherBookmarksFolderTitle"
+ , "MobileBookmarksFolderTitle"
+ };
+
+ for (uint32_t i = 0; i < ArrayLength(rootGuids); ++i) {
+ nsXPIDLString title;
+ rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(titleStringIDs[i]).get(),
+ getter_Copies(title));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<mozIStorageBindingParams> params;
+ rv = paramsArray->NewBindingParams(getter_AddRefs(params));
+ if (NS_FAILED(rv)) return rv;
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
+ nsDependentCString(rootGuids[i]));
+ if (NS_FAILED(rv)) return rv;
+ rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("new_title"),
+ NS_ConvertUTF16toUTF8(title));
+ if (NS_FAILED(rv)) return rv;
+ rv = paramsArray->AddParams(params);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = stmt->BindParameters(paramsArray);
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
+ rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt));
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV13Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Dynamic containers are no longer supported.
+ nsCOMPtr<mozIStorageAsyncStatement> deleteDynContainersStmt;
+ nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_bookmarks WHERE type = :item_type"),
+ getter_AddRefs(deleteDynContainersStmt));
+ rv = deleteDynContainersStmt->BindInt32ByName(
+ NS_LITERAL_CSTRING("item_type"),
+ nsINavBookmarksService::TYPE_DYNAMIC_CONTAINER
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = deleteDynContainersStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV15Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Drop moz_bookmarks_beforedelete_v1_trigger, since it's more expensive than
+ // useful.
+ nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TRIGGER IF EXISTS moz_bookmarks_beforedelete_v1_trigger"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove any orphan keywords.
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_keywords "
+ "WHERE NOT EXISTS ( "
+ "SELECT id "
+ "FROM moz_bookmarks "
+ "WHERE keyword_id = moz_keywords.id "
+ ")"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV17Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool tableExists = false;
+
+ nsresult rv = mMainConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!tableExists) {
+ // For anyone who used in-development versions of this autocomplete,
+ // drop the old tables and its indexes.
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX IF EXISTS moz_hostnames_frecencyindex"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE IF EXISTS moz_hostnames"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add the moz_hosts table so we can get hostnames for URL autocomplete.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Fill the moz_hosts table with all the domains in moz_places.
+ nsCOMPtr<mozIStorageAsyncStatement> fillHostsStmt;
+ rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "INSERT OR IGNORE INTO moz_hosts (host, frecency) "
+ "SELECT fixup_url(get_unreversed_host(h.rev_host)) AS host, "
+ "(SELECT MAX(frecency) FROM moz_places "
+ "WHERE rev_host = h.rev_host "
+ "OR rev_host = h.rev_host || 'www.' "
+ ") AS frecency "
+ "FROM moz_places h "
+ "WHERE LENGTH(h.rev_host) > 1 "
+ "GROUP BY h.rev_host"
+ ), getter_AddRefs(fillHostsStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = fillHostsStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV18Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // moz_hosts should distinguish on typed entries.
+
+ // Check if the profile already has a typed column.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT typed FROM moz_hosts"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) {
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD COLUMN typed NOT NULL DEFAULT 0"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // With the addition of the typed column the covering index loses its
+ // advantages. On the other side querying on host and (optionally) typed
+ // largely restricts the number of results, making scans decently fast.
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX IF EXISTS moz_hosts_frecencyhostindex"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Update typed data.
+ nsCOMPtr<mozIStorageAsyncStatement> updateTypedStmt;
+ rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_hosts SET typed = 1 WHERE host IN ( "
+ "SELECT fixup_url(get_unreversed_host(rev_host)) "
+ "FROM moz_places WHERE typed = 1 "
+ ") "
+ ), getter_AddRefs(updateTypedStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = updateTypedStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV19Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Livemarks children are no longer bookmarks.
+
+ // Remove all children of folders annotated as livemarks.
+ nsCOMPtr<mozIStorageStatement> deleteLivemarksChildrenStmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_bookmarks WHERE parent IN("
+ "SELECT b.id FROM moz_bookmarks b "
+ "JOIN moz_items_annos a ON a.item_id = b.id "
+ "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
+ "WHERE b.type = :item_type AND n.name = :anno_name "
+ ")"
+ ), getter_AddRefs(deleteLivemarksChildrenStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksChildrenStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_name"), NS_LITERAL_CSTRING(LMANNO_FEEDURI)
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksChildrenStmt->BindInt32ByName(
+ NS_LITERAL_CSTRING("item_type"), nsINavBookmarksService::TYPE_FOLDER
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksChildrenStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Clear obsolete livemark prefs.
+ (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_seconds");
+ (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_limit_count");
+ (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_delay_time");
+
+ // Remove the old status annotations.
+ nsCOMPtr<mozIStorageStatement> deleteLivemarksAnnosStmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_items_annos WHERE anno_attribute_id IN("
+ "SELECT id FROM moz_anno_attributes "
+ "WHERE name IN (:anno_loading, :anno_loadfailed, :anno_expiration) "
+ ")"
+ ), getter_AddRefs(deleteLivemarksAnnosStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_loading"), NS_LITERAL_CSTRING("livemark/loading")
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_loadfailed"), NS_LITERAL_CSTRING("livemark/loadfailed")
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_expiration"), NS_LITERAL_CSTRING("livemark/expiration")
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksAnnosStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove orphan annotation names.
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_anno_attributes "
+ "WHERE name IN (:anno_loading, :anno_loadfailed, :anno_expiration) "
+ ), getter_AddRefs(deleteLivemarksAnnosStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_loading"), NS_LITERAL_CSTRING("livemark/loading")
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_loadfailed"), NS_LITERAL_CSTRING("livemark/loadfailed")
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksAnnosStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_expiration"), NS_LITERAL_CSTRING("livemark/expiration")
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteLivemarksAnnosStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV20Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Remove obsolete bookmark GUID annotations.
+ nsCOMPtr<mozIStorageStatement> deleteOldBookmarkGUIDAnnosStmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_items_annos WHERE anno_attribute_id = ("
+ "SELECT id FROM moz_anno_attributes "
+ "WHERE name = :anno_guid"
+ ")"
+ ), getter_AddRefs(deleteOldBookmarkGUIDAnnosStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteOldBookmarkGUIDAnnosStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_guid"), NS_LITERAL_CSTRING("placesInternal/GUID")
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteOldBookmarkGUIDAnnosStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove the orphan annotation name.
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_anno_attributes "
+ "WHERE name = :anno_guid"
+ ), getter_AddRefs(deleteOldBookmarkGUIDAnnosStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteOldBookmarkGUIDAnnosStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_guid"), NS_LITERAL_CSTRING("placesInternal/GUID")
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteOldBookmarkGUIDAnnosStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV21Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Add a prefix column to moz_hosts.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT prefix FROM moz_hosts"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) {
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD COLUMN prefix"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV22Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Reset all session IDs to 0 since we don't support them anymore.
+ // We don't set them to NULL to avoid breaking downgrades.
+ nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE moz_historyvisits SET session = 0"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+nsresult
+Database::MigrateV23Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Recalculate hosts prefixes.
+ nsCOMPtr<mozIStorageAsyncStatement> updatePrefixesStmt;
+ nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_hosts SET prefix = ( " HOSTS_PREFIX_PRIORITY_FRAGMENT ") "
+ ), getter_AddRefs(updatePrefixesStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = updatePrefixesStmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV24Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Add a foreign_count column to moz_places
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT foreign_count FROM moz_places"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) {
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_places ADD COLUMN foreign_count INTEGER DEFAULT 0 NOT NULL"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Adjust counts for all the rows
+ nsCOMPtr<mozIStorageStatement> updateStmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_places SET foreign_count = "
+ "(SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id) "
+ ), getter_AddRefs(updateStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozStorageStatementScoper updateScoper(updateStmt);
+ rv = updateStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV25Up()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Change bookmark roots GUIDs to constant values.
+
+ // If moz_bookmarks_roots doesn't exist anymore, it's because we finally have
+ // been able to remove it. In such a case, we already assigned constant GUIDs
+ // to the roots and we can skip this migration.
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT root_name FROM moz_bookmarks_roots"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_bookmarks SET guid = :guid "
+ "WHERE id = (SELECT folder_id FROM moz_bookmarks_roots WHERE root_name = :name) "
+ ), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char *rootNames[] = { "places", "menu", "toolbar", "tags", "unfiled" };
+ const char *rootGuids[] = { "root________"
+ , "menu________"
+ , "toolbar_____"
+ , "tags________"
+ , "unfiled_____"
+ };
+
+ for (uint32_t i = 0; i < ArrayLength(rootNames); ++i) {
+ // Since this is using the synchronous API, we cannot use
+ // a BindingParamsArray.
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
+ nsDependentCString(rootNames[i]));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
+ nsDependentCString(rootGuids[i]));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV26Up() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Round down dateAdded and lastModified values to milliseconds precision.
+ nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE moz_bookmarks SET dateAdded = dateAdded - dateAdded % 1000, "
+ " lastModified = lastModified - lastModified % 1000"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV27Up() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Change keywords store, moving their relation from bookmarks to urls.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT place_id FROM moz_keywords"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) {
+ // Even if these 2 columns have a unique constraint, we allow NULL values
+ // for backwards compatibility. NULL never breaks a unique constraint.
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_keywords ADD COLUMN place_id INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_keywords ADD COLUMN post_data TEXT"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Associate keywords with uris. A keyword could be associated to multiple
+ // bookmarks uris, or multiple keywords could be associated to the same uri.
+ // The new system only allows multiple uris per keyword, provided they have
+ // a different post_data value.
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "INSERT OR REPLACE INTO moz_keywords (id, keyword, place_id, post_data) "
+ "SELECT k.id, k.keyword, h.id, MAX(a.content) "
+ "FROM moz_places h "
+ "JOIN moz_bookmarks b ON b.fk = h.id "
+ "JOIN moz_keywords k ON k.id = b.keyword_id "
+ "LEFT JOIN moz_items_annos a ON a.item_id = b.id "
+ "AND a.anno_attribute_id = (SELECT id FROM moz_anno_attributes "
+ "WHERE name = 'bookmarkProperties/POSTData') "
+ "WHERE k.place_id ISNULL "
+ "GROUP BY keyword"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove any keyword that points to a non-existing place id.
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_keywords "
+ "WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = moz_keywords.place_id)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE moz_bookmarks SET keyword_id = NULL "
+ "WHERE NOT EXISTS (SELECT 1 FROM moz_keywords WHERE id = moz_bookmarks.keyword_id)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Adjust foreign_count for all the rows.
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE moz_places SET foreign_count = "
+ "(SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id) + "
+ "(SELECT count(*) FROM moz_keywords WHERE place_id = moz_places.id) "
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV28Up() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // v27 migration was bogus and set some unrelated annotations as post_data for
+ // keywords having an annotated bookmark.
+ // The current v27 migration function is fixed, but we still need to handle
+ // users that hit the bogus version. Since we can't distinguish, we'll just
+ // set again all of the post data.
+ DebugOnly<nsresult> rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE moz_keywords "
+ "SET post_data = ( "
+ "SELECT content FROM moz_items_annos a "
+ "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
+ "JOIN moz_bookmarks b on b.id = a.item_id "
+ "WHERE n.name = 'bookmarkProperties/POSTData' "
+ "AND b.keyword_id = moz_keywords.id "
+ "ORDER BY b.lastModified DESC "
+ "LIMIT 1 "
+ ") "
+ "WHERE EXISTS(SELECT 1 FROM moz_bookmarks WHERE keyword_id = moz_keywords.id) "
+ ));
+ // In case the update fails a constraint, we don't want to throw away the
+ // whole database for just a few keywords. In rare cases the user might have
+ // to recreate them. Though, at this point, there shouldn't be 2 keywords
+ // pointing to the same url and post data, cause the previous migration step
+ // removed them.
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV30Up() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX IF EXISTS moz_favicons_guid_uniqueindex"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV31Up() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP TABLE IF EXISTS moz_bookmarks_roots"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV32Up() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Remove some old and no more used Places preferences that may be confusing
+ // for the user.
+ mozilla::Unused << Preferences::ClearUser("places.history.expiration.transient_optimal_database_size");
+ mozilla::Unused << Preferences::ClearUser("places.last_vacuum");
+ mozilla::Unused << Preferences::ClearUser("browser.history_expire_sites");
+ mozilla::Unused << Preferences::ClearUser("browser.history_expire_days.mirror");
+ mozilla::Unused << Preferences::ClearUser("browser.history_expire_days_min");
+
+ // For performance reasons we want to remove too long urls from history.
+ // We cannot use the moz_places triggers here, cause they are defined only
+ // after the schema migration. Thus we need to collect the hosts that need to
+ // be updated first.
+ nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TEMP TABLE moz_migrate_v32_temp ("
+ "host TEXT PRIMARY KEY "
+ ") WITHOUT ROWID "
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT OR IGNORE INTO moz_migrate_v32_temp (host) "
+ "SELECT fixup_url(get_unreversed_host(rev_host)) "
+ "FROM moz_places WHERE LENGTH(url) > :maxlen AND foreign_count = 0"
+ ), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozStorageStatementScoper scoper(stmt);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("maxlen"), MaxUrlLength());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Now remove the pages with a long url.
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_places WHERE LENGTH(url) > :maxlen AND foreign_count = 0"
+ ), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozStorageStatementScoper scoper(stmt);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("maxlen"), MaxUrlLength());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Expire orphan visits and update moz_hosts.
+ // These may be a bit more expensive and are not critical for the DB
+ // functionality, so we execute them asynchronously.
+ nsCOMPtr<mozIStorageAsyncStatement> expireOrphansStmt;
+ rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_historyvisits "
+ "WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = place_id)"
+ ), getter_AddRefs(expireOrphansStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIStorageAsyncStatement> deleteHostsStmt;
+ rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_hosts "
+ "WHERE host IN (SELECT host FROM moz_migrate_v32_temp) "
+ "AND NOT EXISTS("
+ "SELECT 1 FROM moz_places "
+ "WHERE rev_host = get_unreversed_host(host || '.') || '.' "
+ "OR rev_host = get_unreversed_host(host || '.') || '.www.' "
+ "); "
+ ), getter_AddRefs(deleteHostsStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIStorageAsyncStatement> updateHostsStmt;
+ rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_hosts "
+ "SET prefix = (" HOSTS_PREFIX_PRIORITY_FRAGMENT ") "
+ "WHERE host IN (SELECT host FROM moz_migrate_v32_temp) "
+ ), getter_AddRefs(updateHostsStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIStorageAsyncStatement> dropTableStmt;
+ rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DROP TABLE IF EXISTS moz_migrate_v32_temp"
+ ), getter_AddRefs(dropTableStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozIStorageBaseStatement *stmts[] = {
+ expireOrphansStmt,
+ deleteHostsStmt,
+ updateHostsStmt,
+ dropTableStmt
+ };
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = mMainConn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
+ getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV33Up() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX IF EXISTS moz_places_url_uniqueindex"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add an url_hash column to moz_places.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT url_hash FROM moz_places"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) {
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_places ADD COLUMN url_hash INTEGER DEFAULT 0 NOT NULL"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE moz_places SET url_hash = hash(url) WHERE url_hash = 0"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create an index on url_hash.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV34Up() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_keywords WHERE id IN ( "
+ "SELECT id FROM moz_keywords k "
+ "WHERE NOT EXISTS (SELECT 1 FROM moz_places h WHERE k.place_id = h.id) "
+ ")"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Database::MigrateV35Up() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int64_t mobileRootId = CreateMobileRoot();
+ if (mobileRootId <= 0) {
+ // Either the schema is broken or there isn't any root. The latter can
+ // happen if a consumer, for example Thunderbird, never used bookmarks.
+ // If there are no roots, this migration should not run.
+ nsCOMPtr<mozIStorageStatement> checkRootsStmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id FROM moz_bookmarks WHERE parent = 0"
+ ), getter_AddRefs(checkRootsStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozStorageStatementScoper scoper(checkRootsStmt);
+ bool hasResult = false;
+ rv = checkRootsStmt->ExecuteStep(&hasResult);
+ if (NS_SUCCEEDED(rv) && !hasResult) {
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ // At this point, we should have no more than two folders with the mobile
+ // bookmarks anno: the new root, and the old folder if one exists. If, for
+ // some reason, we have multiple folders with the anno, we append their
+ // children to the new root.
+ nsTArray<int64_t> folderIds;
+ nsresult rv = GetItemsWithAnno(NS_LITERAL_CSTRING(MOBILE_ROOT_ANNO),
+ nsINavBookmarksService::TYPE_FOLDER,
+ folderIds);
+ if (NS_FAILED(rv)) return rv;
+
+ for (uint32_t i = 0; i < folderIds.Length(); ++i) {
+ if (folderIds[i] == mobileRootId) {
+ // Ignore the new mobile root. We'll remove this anno from the root in
+ // bug 1306445.
+ continue;
+ }
+
+ // Append the folder's children to the new root.
+ nsCOMPtr<mozIStorageStatement> moveStmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_bookmarks "
+ "SET parent = :root_id, "
+ "position = position + IFNULL("
+ "(SELECT MAX(position) + 1 FROM moz_bookmarks "
+ "WHERE parent = :root_id), 0)"
+ "WHERE parent = :folder_id"
+ ), getter_AddRefs(moveStmt));
+ if (NS_FAILED(rv)) return rv;
+ mozStorageStatementScoper moveScoper(moveStmt);
+
+ rv = moveStmt->BindInt64ByName(NS_LITERAL_CSTRING("root_id"),
+ mobileRootId);
+ if (NS_FAILED(rv)) return rv;
+ rv = moveStmt->BindInt64ByName(NS_LITERAL_CSTRING("folder_id"),
+ folderIds[i]);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = moveStmt->Execute();
+ if (NS_FAILED(rv)) return rv;
+
+ // Delete the old folder.
+ rv = DeleteBookmarkItem(folderIds[i]);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Database::GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
+ nsTArray<int64_t>& aItemIds)
+{
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT b.id FROM moz_items_annos a "
+ "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
+ "JOIN moz_bookmarks b ON b.id = a.item_id "
+ "WHERE n.name = :anno_name AND "
+ "b.type = :item_type"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) return rv;
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aAnnoName);
+ if (NS_FAILED(rv)) return rv;
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_type"), aItemType);
+ if (NS_FAILED(rv)) return rv;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
+ int64_t itemId;
+ rv = stmt->GetInt64(0, &itemId);
+ if (NS_FAILED(rv)) return rv;
+ aItemIds.AppendElement(itemId);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Database::DeleteBookmarkItem(int32_t aItemId)
+{
+ // Delete the old bookmark.
+ nsCOMPtr<mozIStorageStatement> deleteStmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_bookmarks WHERE id = :item_id"
+ ), getter_AddRefs(deleteStmt));
+ if (NS_FAILED(rv)) return rv;
+ mozStorageStatementScoper deleteScoper(deleteStmt);
+
+ rv = deleteStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
+ aItemId);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = deleteStmt->Execute();
+ if (NS_FAILED(rv)) return rv;
+
+ // Clean up orphan annotations.
+ nsCOMPtr<mozIStorageStatement> removeAnnosStmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_items_annos WHERE item_id = :item_id"
+ ), getter_AddRefs(removeAnnosStmt));
+ if (NS_FAILED(rv)) return rv;
+ mozStorageStatementScoper removeAnnosScoper(removeAnnosStmt);
+
+ rv = removeAnnosStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
+ aItemId);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = removeAnnosStmt->Execute();
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+int64_t
+Database::CreateMobileRoot()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Create the mobile root, ignoring conflicts if one already exists (for
+ // example, if the user downgraded to an earlier release channel).
+ nsCOMPtr<mozIStorageStatement> createStmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT OR IGNORE INTO moz_bookmarks "
+ "(type, title, dateAdded, lastModified, guid, position, parent) "
+ "SELECT :item_type, :item_title, :timestamp, :timestamp, :guid, "
+ "(SELECT COUNT(*) FROM moz_bookmarks p WHERE p.parent = b.id), b.id "
+ "FROM moz_bookmarks b WHERE b.parent = 0"
+ ), getter_AddRefs(createStmt));
+ if (NS_FAILED(rv)) return -1;
+ mozStorageStatementScoper createScoper(createStmt);
+
+ rv = createStmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"),
+ nsINavBookmarksService::TYPE_FOLDER);
+ if (NS_FAILED(rv)) return -1;
+ rv = createStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
+ NS_LITERAL_CSTRING(MOBILE_ROOT_TITLE));
+ if (NS_FAILED(rv)) return -1;
+ rv = createStmt->BindInt64ByName(NS_LITERAL_CSTRING("timestamp"),
+ RoundedPRNow());
+ if (NS_FAILED(rv)) return -1;
+ rv = createStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
+ NS_LITERAL_CSTRING(MOBILE_ROOT_GUID));
+ if (NS_FAILED(rv)) return -1;
+
+ rv = createStmt->Execute();
+ if (NS_FAILED(rv)) return -1;
+
+ // Find the mobile root ID. We can't use the last inserted ID because the
+ // root might already exist, and we ignore on conflict.
+ nsCOMPtr<mozIStorageStatement> findIdStmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id FROM moz_bookmarks WHERE guid = :guid"
+ ), getter_AddRefs(findIdStmt));
+ if (NS_FAILED(rv)) return -1;
+ mozStorageStatementScoper findIdScoper(findIdStmt);
+
+ rv = findIdStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
+ NS_LITERAL_CSTRING(MOBILE_ROOT_GUID));
+ if (NS_FAILED(rv)) return -1;
+
+ bool hasResult = false;
+ rv = findIdStmt->ExecuteStep(&hasResult);
+ if (NS_FAILED(rv) || !hasResult) return -1;
+
+ int64_t rootId;
+ rv = findIdStmt->GetInt64(0, &rootId);
+ if (NS_FAILED(rv)) return -1;
+
+ // Set the mobile bookmarks anno on the new root, so that Sync code on an
+ // older channel can still find it in case of a downgrade. This can be
+ // removed in bug 1306445.
+ nsCOMPtr<mozIStorageStatement> addAnnoNameStmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT OR IGNORE INTO moz_anno_attributes (name) VALUES (:anno_name)"
+ ), getter_AddRefs(addAnnoNameStmt));
+ if (NS_FAILED(rv)) return -1;
+ mozStorageStatementScoper addAnnoNameScoper(addAnnoNameStmt);
+
+ rv = addAnnoNameStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_name"), NS_LITERAL_CSTRING(MOBILE_ROOT_ANNO));
+ if (NS_FAILED(rv)) return -1;
+ rv = addAnnoNameStmt->Execute();
+ if (NS_FAILED(rv)) return -1;
+
+ nsCOMPtr<mozIStorageStatement> addAnnoStmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT OR IGNORE INTO moz_items_annos "
+ "(id, item_id, anno_attribute_id, content, flags, "
+ "expiration, type, dateAdded, lastModified) "
+ "SELECT "
+ "(SELECT a.id FROM moz_items_annos a "
+ "WHERE a.anno_attribute_id = n.id AND "
+ "a.item_id = :root_id), "
+ ":root_id, n.id, 1, 0, :expiration, :type, :timestamp, :timestamp "
+ "FROM moz_anno_attributes n WHERE name = :anno_name"
+ ), getter_AddRefs(addAnnoStmt));
+ if (NS_FAILED(rv)) return -1;
+ mozStorageStatementScoper addAnnoScoper(addAnnoStmt);
+
+ rv = addAnnoStmt->BindInt64ByName(NS_LITERAL_CSTRING("root_id"), rootId);
+ if (NS_FAILED(rv)) return -1;
+ rv = addAnnoStmt->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno_name"), NS_LITERAL_CSTRING(MOBILE_ROOT_ANNO));
+ if (NS_FAILED(rv)) return -1;
+ rv = addAnnoStmt->BindInt32ByName(NS_LITERAL_CSTRING("expiration"),
+ nsIAnnotationService::EXPIRE_NEVER);
+ if (NS_FAILED(rv)) return -1;
+ rv = addAnnoStmt->BindInt32ByName(NS_LITERAL_CSTRING("type"),
+ nsIAnnotationService::TYPE_INT32);
+ if (NS_FAILED(rv)) return -1;
+ rv = addAnnoStmt->BindInt32ByName(NS_LITERAL_CSTRING("timestamp"),
+ RoundedPRNow());
+ if (NS_FAILED(rv)) return -1;
+
+ rv = addAnnoStmt->Execute();
+ if (NS_FAILED(rv)) return -1;
+
+ return rootId;
+}
+
+void
+Database::Shutdown()
+{
+ // As the last step in the shutdown path, finalize the database handle.
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mClosed);
+
+ // Break cycles with the shutdown blockers.
+ mClientsShutdown = nullptr;
+ nsCOMPtr<mozIStorageCompletionCallback> connectionShutdown = mConnectionShutdown.forget();
+
+ if (!mMainConn) {
+ // The connection has never been initialized. Just mark it as closed.
+ mClosed = true;
+ (void)connectionShutdown->Complete(NS_OK, nullptr);
+ return;
+ }
+
+#ifdef DEBUG
+ { // Sanity check for missing guids.
+ bool haveNullGuids = false;
+ nsCOMPtr<mozIStorageStatement> stmt;
+
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT 1 "
+ "FROM moz_places "
+ "WHERE guid IS NULL "
+ ), getter_AddRefs(stmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = stmt->ExecuteStep(&haveNullGuids);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(!haveNullGuids, "Found a page without a GUID!");
+
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT 1 "
+ "FROM moz_bookmarks "
+ "WHERE guid IS NULL "
+ ), getter_AddRefs(stmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = stmt->ExecuteStep(&haveNullGuids);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(!haveNullGuids, "Found a bookmark without a GUID!");
+ }
+
+ { // Sanity check for unrounded dateAdded and lastModified values (bug
+ // 1107308).
+ bool hasUnroundedDates = false;
+ nsCOMPtr<mozIStorageStatement> stmt;
+
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT 1 "
+ "FROM moz_bookmarks "
+ "WHERE dateAdded % 1000 > 0 OR lastModified % 1000 > 0 LIMIT 1"
+ ), getter_AddRefs(stmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = stmt->ExecuteStep(&hasUnroundedDates);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(!hasUnroundedDates, "Found unrounded dates!");
+ }
+
+ { // Sanity check url_hash
+ bool hasNullHash = false;
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT 1 FROM moz_places WHERE url_hash = 0"
+ ), getter_AddRefs(stmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = stmt->ExecuteStep(&hasNullHash);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(!hasNullHash, "Found a place without a hash!");
+ }
+
+ { // Sanity check unique urls
+ bool hasDupeUrls = false;
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT 1 FROM moz_places GROUP BY url HAVING count(*) > 1 "
+ ), getter_AddRefs(stmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = stmt->ExecuteStep(&hasDupeUrls);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(!hasDupeUrls, "Found a duplicate url!");
+ }
+#endif
+
+ mMainThreadStatements.FinalizeStatements();
+ mMainThreadAsyncStatements.FinalizeStatements();
+
+ RefPtr< FinalizeStatementCacheProxy<mozIStorageStatement> > event =
+ new FinalizeStatementCacheProxy<mozIStorageStatement>(
+ mAsyncThreadStatements,
+ NS_ISUPPORTS_CAST(nsIObserver*, this)
+ );
+ DispatchToAsyncThread(event);
+
+ mClosed = true;
+
+ (void)mMainConn->AsyncClose(connectionShutdown);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+Database::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (strcmp(aTopic, TOPIC_PROFILE_CHANGE_TEARDOWN) == 0) {
+ // Tests simulating shutdown may cause multiple notifications.
+ if (IsShutdownStarted()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ // If shutdown happens in the same mainthread loop as init, observers could
+ // handle the places-init-complete notification after xpcom-shutdown, when
+ // the connection does not exist anymore. Removing those observers would
+ // be less expensive but may cause their RemoveObserver calls to throw.
+ // Thus notify the topic now, so they stop listening for it.
+ nsCOMPtr<nsISimpleEnumerator> e;
+ if (NS_SUCCEEDED(os->EnumerateObservers(TOPIC_PLACES_INIT_COMPLETE,
+ getter_AddRefs(e))) && e) {
+ bool hasMore = false;
+ while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> supports;
+ if (NS_SUCCEEDED(e->GetNext(getter_AddRefs(supports)))) {
+ nsCOMPtr<nsIObserver> observer = do_QueryInterface(supports);
+ (void)observer->Observe(observer, TOPIC_PLACES_INIT_COMPLETE, nullptr);
+ }
+ }
+ }
+
+ // Notify all Places users that we are about to shutdown.
+ (void)os->NotifyObservers(nullptr, TOPIC_PLACES_SHUTDOWN, nullptr);
+ } else if (strcmp(aTopic, TOPIC_SIMULATE_PLACES_SHUTDOWN) == 0) {
+ // This notification is (and must be) only used by tests that are trying
+ // to simulate Places shutdown out of the normal shutdown path.
+
+ // Tests simulating shutdown may cause re-entrance.
+ if (IsShutdownStarted()) {
+ return NS_OK;
+ }
+
+ // We are simulating a shutdown, so invoke the shutdown blockers,
+ // wait for them, then proceed with connection shutdown.
+ // Since we are already going through shutdown, but it's not the real one,
+ // we won't need to block the real one anymore, so we can unblock it.
+ {
+ nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileChangeTeardownPhase();
+ if (shutdownPhase) {
+ shutdownPhase->RemoveBlocker(mClientsShutdown.get());
+ }
+ (void)mClientsShutdown->BlockShutdown(nullptr);
+ }
+
+ // Spin the events loop until the clients are done.
+ // Note, this is just for tests, specifically test_clearHistory_shutdown.js
+ while (mClientsShutdown->State() != PlacesShutdownBlocker::States::RECEIVED_DONE) {
+ (void)NS_ProcessNextEvent();
+ }
+
+ {
+ nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase = GetProfileBeforeChangePhase();
+ if (shutdownPhase) {
+ shutdownPhase->RemoveBlocker(mConnectionShutdown.get());
+ }
+ (void)mConnectionShutdown->BlockShutdown(nullptr);
+ }
+ }
+ return NS_OK;
+}
+
+uint32_t
+Database::MaxUrlLength() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mMaxUrlLength) {
+ mMaxUrlLength = Preferences::GetInt(PREF_HISTORY_MAXURLLEN,
+ PREF_HISTORY_MAXURLLEN_DEFAULT);
+ if (mMaxUrlLength < 255 || mMaxUrlLength > INT32_MAX) {
+ mMaxUrlLength = PREF_HISTORY_MAXURLLEN_DEFAULT;
+ }
+ }
+ return mMaxUrlLength;
+}
+
+
+
+} // namespace places
+} // namespace mozilla
diff --git a/components/places/src/Database.h b/components/places/src/Database.h
new file mode 100644
index 000000000..22488fddb
--- /dev/null
+++ b/components/places/src/Database.h
@@ -0,0 +1,331 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 mozilla_places_Database_h_
+#define mozilla_places_Database_h_
+
+#include "MainThreadUtils.h"
+#include "nsWeakReference.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIObserver.h"
+#include "nsIAsyncShutdown.h"
+#include "mozilla/storage.h"
+#include "mozilla/storage/StatementCache.h"
+#include "mozilla/Attributes.h"
+#include "nsIEventTarget.h"
+#include "Shutdown.h"
+
+// This is the schema version. Update it at any schema change and add a
+// corresponding migrateVxx method below.
+#define DATABASE_SCHEMA_VERSION 35
+
+// Fired after Places inited.
+#define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
+// Fired when initialization fails due to a locked database.
+#define TOPIC_DATABASE_LOCKED "places-database-locked"
+// This topic is received when the profile is about to be lost. Places does
+// initial shutdown work and notifies TOPIC_PLACES_SHUTDOWN to all listeners.
+// Any shutdown work that requires the Places APIs should happen here.
+#define TOPIC_PROFILE_CHANGE_TEARDOWN "profile-change-teardown"
+// Fired when Places is shutting down. Any code should stop accessing Places
+// APIs after this notification. If you need to listen for Places shutdown
+// you should only use this notification, next ones are intended only for
+// internal Places use.
+#define TOPIC_PLACES_SHUTDOWN "places-shutdown"
+// For Internal use only. Fired when connection is about to be closed, only
+// cleanup tasks should run at this stage, nothing should be added to the
+// database, nor APIs should be called.
+#define TOPIC_PLACES_WILL_CLOSE_CONNECTION "places-will-close-connection"
+// Fired when the connection has gone, nothing will work from now on.
+#define TOPIC_PLACES_CONNECTION_CLOSED "places-connection-closed"
+
+// Simulate profile-before-change. This topic may only be used by
+// calling `observe` directly on the database. Used for testing only.
+#define TOPIC_SIMULATE_PLACES_SHUTDOWN "test-simulate-places-shutdown"
+
+class nsIRunnable;
+
+namespace mozilla {
+namespace places {
+
+enum JournalMode {
+ // Default SQLite journal mode.
+ JOURNAL_DELETE = 0
+ // Can reduce fsyncs on Linux when journal is deleted (See bug 460315).
+ // We fallback to this mode when WAL is unavailable.
+, JOURNAL_TRUNCATE
+ // Unsafe in case of crashes on database swap or low memory.
+, JOURNAL_MEMORY
+ // Can reduce number of fsyncs. We try to use this mode by default.
+, JOURNAL_WAL
+};
+
+class ClientsShutdownBlocker;
+class ConnectionShutdownBlocker;
+
+class Database final : public nsIObserver
+ , public nsSupportsWeakReference
+{
+ typedef mozilla::storage::StatementCache<mozIStorageStatement> StatementCache;
+ typedef mozilla::storage::StatementCache<mozIStorageAsyncStatement> AsyncStatementCache;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ Database();
+
+ /**
+ * Initializes the database connection and the schema.
+ * In case of corruption the database is copied to a backup file and replaced.
+ */
+ nsresult Init();
+
+ /**
+ * The AsyncShutdown client used by clients of this API to be informed of shutdown.
+ */
+ already_AddRefed<nsIAsyncShutdownClient> GetClientsShutdown();
+
+ /**
+ * Getter to use when instantiating the class.
+ *
+ * @return Singleton instance of this class.
+ */
+ static already_AddRefed<Database> GetDatabase();
+
+ /**
+ * Returns last known database status.
+ *
+ * @return one of the nsINavHistoryService::DATABASE_STATUS_* constants.
+ */
+ uint16_t GetDatabaseStatus() const
+ {
+ return mDatabaseStatus;
+ }
+
+ /**
+ * Returns a pointer to the storage connection.
+ *
+ * @return The connection handle.
+ */
+ mozIStorageConnection* MainConn() const
+ {
+ return mMainConn;
+ }
+
+ /**
+ * Dispatches a runnable to the connection async thread, to be serialized
+ * with async statements.
+ *
+ * @param aEvent
+ * The runnable to be dispatched.
+ */
+ void DispatchToAsyncThread(nsIRunnable* aEvent) const
+ {
+ if (mClosed) {
+ return;
+ }
+ nsCOMPtr<nsIEventTarget> target = do_GetInterface(mMainConn);
+ if (target) {
+ (void)target->Dispatch(aEvent, NS_DISPATCH_NORMAL);
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Statements Getters.
+
+ /**
+ * Gets a cached synchronous statement.
+ *
+ * @param aQuery
+ * SQL query literal.
+ * @return The cached statement.
+ * @note Always null check the result.
+ * @note Always use a scoper to reset the statement.
+ */
+ template<int N>
+ already_AddRefed<mozIStorageStatement>
+ GetStatement(const char (&aQuery)[N]) const
+ {
+ nsDependentCString query(aQuery, N - 1);
+ return GetStatement(query);
+ }
+
+ /**
+ * Gets a cached synchronous statement.
+ *
+ * @param aQuery
+ * nsCString of SQL query.
+ * @return The cached statement.
+ * @note Always null check the result.
+ * @note Always use a scoper to reset the statement.
+ */
+ already_AddRefed<mozIStorageStatement> GetStatement(const nsACString& aQuery) const;
+
+ /**
+ * Gets a cached asynchronous statement.
+ *
+ * @param aQuery
+ * SQL query literal.
+ * @return The cached statement.
+ * @note Always null check the result.
+ * @note AsyncStatements are automatically reset on execution.
+ */
+ template<int N>
+ already_AddRefed<mozIStorageAsyncStatement>
+ GetAsyncStatement(const char (&aQuery)[N]) const
+ {
+ nsDependentCString query(aQuery, N - 1);
+ return GetAsyncStatement(query);
+ }
+
+ /**
+ * Gets a cached asynchronous statement.
+ *
+ * @param aQuery
+ * nsCString of SQL query.
+ * @return The cached statement.
+ * @note Always null check the result.
+ * @note AsyncStatements are automatically reset on execution.
+ */
+ already_AddRefed<mozIStorageAsyncStatement> GetAsyncStatement(const nsACString& aQuery) const;
+
+ uint32_t MaxUrlLength();
+
+protected:
+ /**
+ * Finalizes the cached statements and closes the database connection.
+ * A TOPIC_PLACES_CONNECTION_CLOSED notification is fired when done.
+ */
+ void Shutdown();
+
+ bool IsShutdownStarted() const;
+
+ /**
+ * Initializes the database file. If the database does not exist or is
+ * corrupt, a new one is created. In case of corruption it also creates a
+ * backup copy of the database.
+ *
+ * @param aStorage
+ * mozStorage service instance.
+ * @param aNewDatabaseCreated
+ * whether a new database file has been created.
+ */
+ nsresult InitDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage,
+ bool* aNewDatabaseCreated);
+
+ /**
+ * Creates a database backup and replaces the original file with a new
+ * one.
+ *
+ * @param aStorage
+ * mozStorage service instance.
+ */
+ nsresult BackupAndReplaceDatabaseFile(nsCOMPtr<mozIStorageService>& aStorage);
+
+ /**
+ * Initializes the database. This performs any necessary migrations for the
+ * database. All migration is done inside a transaction that is rolled back
+ * if any error occurs.
+ * @param aDatabaseMigrated
+ * Whether a schema upgrade happened.
+ */
+ nsresult InitSchema(bool* aDatabaseMigrated);
+
+ /**
+ * Creates bookmark roots in a new DB.
+ */
+ nsresult CreateBookmarkRoots();
+
+ /**
+ * Initializes additionale SQLite functions, defined in SQLFunctions.h
+ */
+ nsresult InitFunctions();
+
+ /**
+ * Initializes temp entities, like triggers, tables, views...
+ */
+ nsresult InitTempEntities();
+
+ /**
+ * Helpers used by schema upgrades.
+ */
+ nsresult MigrateV13Up();
+ nsresult MigrateV15Up();
+ nsresult MigrateV17Up();
+ nsresult MigrateV18Up();
+ nsresult MigrateV19Up();
+ nsresult MigrateV20Up();
+ nsresult MigrateV21Up();
+ nsresult MigrateV22Up();
+ nsresult MigrateV23Up();
+ nsresult MigrateV24Up();
+ nsresult MigrateV25Up();
+ nsresult MigrateV26Up();
+ nsresult MigrateV27Up();
+ nsresult MigrateV28Up();
+ nsresult MigrateV30Up();
+ nsresult MigrateV31Up();
+ nsresult MigrateV32Up();
+ nsresult MigrateV33Up();
+ nsresult MigrateV34Up();
+ nsresult MigrateV35Up();
+
+ nsresult UpdateBookmarkRootTitles();
+
+ friend class ConnectionShutdownBlocker;
+
+ int64_t CreateMobileRoot();
+ nsresult GetItemsWithAnno(const nsACString& aAnnoName, int32_t aItemType,
+ nsTArray<int64_t>& aItemIds);
+ nsresult DeleteBookmarkItem(int32_t aItemId);
+
+private:
+ ~Database();
+
+ /**
+ * Singleton getter, invoked by class instantiation.
+ */
+ static already_AddRefed<Database> GetSingleton();
+
+ static Database* gDatabase;
+
+ nsCOMPtr<mozIStorageConnection> mMainConn;
+
+ mutable StatementCache mMainThreadStatements;
+ mutable AsyncStatementCache mMainThreadAsyncStatements;
+ mutable StatementCache mAsyncThreadStatements;
+
+ int32_t mDBPageSize;
+ uint16_t mDatabaseStatus;
+ bool mClosed;
+
+ /**
+ * Phases for shutting down the Database.
+ * See Shutdown.h for further details about the shutdown procedure.
+ */
+ already_AddRefed<nsIAsyncShutdownClient> GetProfileChangeTeardownPhase();
+ already_AddRefed<nsIAsyncShutdownClient> GetProfileBeforeChangePhase();
+
+ /**
+ * Blockers in charge of waiting for the Places clients and then shutting
+ * down the mozStorage connection.
+ * See Shutdown.h for further details about the shutdown procedure.
+ *
+ * Cycles with these are broken in `Shutdown()`.
+ */
+ RefPtr<ClientsShutdownBlocker> mClientsShutdown;
+ RefPtr<ConnectionShutdownBlocker> mConnectionShutdown;
+
+ // Maximum length of a stored url.
+ // For performance reasons we don't store very long urls in history, since
+ // they are slower to search through and cause abnormal database growth,
+ // affecting the awesomebar fetch time.
+ uint32_t mMaxUrlLength;
+};
+
+} // namespace places
+} // namespace mozilla
+
+#endif // mozilla_places_Database_h_
diff --git a/components/places/src/ExtensionSearchHandler.jsm b/components/places/src/ExtensionSearchHandler.jsm
new file mode 100644
index 000000000..3eb699ca1
--- /dev/null
+++ b/components/places/src/ExtensionSearchHandler.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 = [ "ExtensionSearchHandler" ];
+
+// Used to keep track of all of the registered keywords, where each keyword is
+// mapped to a KeywordInfo instance.
+let gKeywordMap = new Map();
+
+// Used to keep track of the active input session.
+let gActiveInputSession = null;
+
+// Used to keep track of who has control over the active suggestion callback
+// so older callbacks can be ignored. The callback ID should increment whenever
+// the input changes or the input session ends.
+let gCurrentCallbackID = 0;
+
+// Handles keeping track of information associated to the registered keyword.
+class KeywordInfo {
+ constructor(extension, description) {
+ this._extension = extension;
+ this._description = description;
+ }
+
+ get description() {
+ return this._description;
+ }
+
+ set description(desc) {
+ this._description = desc;
+ }
+
+ get extension() {
+ return this._extension;
+ }
+}
+
+// Responsible for handling communication between the extension and the urlbar.
+class InputSession {
+ constructor(keyword, extension) {
+ this._keyword = keyword;
+ this._extension = extension;
+ this._suggestionsCallback = null;
+ this._searchFinishedCallback = null;
+ }
+
+ get keyword() {
+ return this._keyword;
+ }
+
+ addSuggestions(suggestions) {
+ this._suggestionsCallback(suggestions);
+ }
+
+ start(eventName) {
+ this._extension.emit(eventName);
+ }
+
+ update(eventName, text, suggestionsCallback, searchFinishedCallback) {
+ if (this._searchFinishedCallback) {
+ this._searchFinishedCallback();
+ }
+ this._searchFinishedCallback = searchFinishedCallback;
+ this._suggestionsCallback = suggestionsCallback;
+ this._extension.emit(eventName, text, ++gCurrentCallbackID);
+ }
+
+ cancel(eventName) {
+ this._searchFinishedCallback();
+ this._extension.emit(eventName);
+ }
+
+ end(eventName, text, disposition) {
+ this._searchFinishedCallback();
+ this._extension.emit(eventName, text, disposition);
+ }
+}
+
+var ExtensionSearchHandler = Object.freeze({
+ MSG_INPUT_STARTED: "webext-omnibox-input-started",
+ MSG_INPUT_CHANGED: "webext-omnibox-input-changed",
+ MSG_INPUT_ENTERED: "webext-omnibox-input-entered",
+ MSG_INPUT_CANCELLED: "webext-omnibox-input-cancelled",
+
+ /**
+ * Registers a keyword.
+ *
+ * @param {string} keyword The keyword to register.
+ * @param {Extension} extension The extension registering the keyword.
+ */
+ registerKeyword(keyword, extension) {
+ if (gKeywordMap.has(keyword)) {
+ throw new Error(`The keyword provided is already registered: "${keyword}"`);
+ }
+ gKeywordMap.set(keyword, new KeywordInfo(extension, extension.name));
+ },
+
+ /**
+ * Unregisters a keyword.
+ *
+ * @param {string} keyword The keyword to unregister.
+ */
+ unregisterKeyword(keyword) {
+ if (!gKeywordMap.has(keyword)) {
+ throw new Error(`The keyword provided is not registered: "${keyword}"`);
+ }
+ gActiveInputSession = null;
+ gKeywordMap.delete(keyword);
+ },
+
+ /**
+ * Checks if a keyword is registered.
+ *
+ * @param {string} keyword The word to check.
+ * @return {boolean} true if the word is a registered keyword.
+ */
+ isKeywordRegistered(keyword) {
+ return gKeywordMap.has(keyword);
+ },
+
+ /**
+ * @return {boolean} true if there is an active input session.
+ */
+ hasActiveInputSession() {
+ return gActiveInputSession != null;
+ },
+
+ /**
+ * @param {string} keyword The keyword to look up.
+ * @return {string} the description to use for the heuristic result.
+ */
+ getDescription(keyword) {
+ if (!gKeywordMap.has(keyword)) {
+ throw new Error(`The keyword provided is not registered: "${keyword}"`);
+ }
+ return gKeywordMap.get(keyword).description;
+ },
+
+ /**
+ * Sets the default suggestion for the registered keyword. The suggestion's
+ * description will be used for the comment in the heuristic result.
+ *
+ * @param {string} keyword The keyword.
+ * @param {string} description The description to use for the heuristic result.
+ */
+ setDefaultSuggestion(keyword, {description}) {
+ if (!gKeywordMap.has(keyword)) {
+ throw new Error(`The keyword provided is not registered: "${keyword}"`);
+ }
+ gKeywordMap.get(keyword).description = description;
+ },
+
+ /**
+ * Adds suggestions for the registered keyword. This function will throw if
+ * the keyword provided is not registered or active, or if the callback ID
+ * provided is no longer equal to the active callback ID.
+ *
+ * @param {string} keyword The keyword.
+ * @param {integer} id The ID of the suggestion callback.
+ * @param {Array<Object>} suggestions An array of suggestions to provide to the urlbar.
+ */
+ addSuggestions(keyword, id, suggestions) {
+ if (!gKeywordMap.has(keyword)) {
+ throw new Error(`The keyword provided is not registered: "${keyword}"`);
+ }
+
+ if (!gActiveInputSession || gActiveInputSession.keyword != keyword) {
+ throw new Error(`The keyword provided is not apart of an active input session: "${keyword}"`);
+ }
+
+ if (id != gCurrentCallbackID) {
+ throw new Error(`The callback is no longer active for the keyword provided: "${keyword}"`);
+ }
+
+ gActiveInputSession.addSuggestions(suggestions);
+ },
+
+ /**
+ * Called when the input in the urlbar begins with `<keyword><space>`.
+ *
+ * If the keyword is inactive, MSG_INPUT_STARTED is emitted and the
+ * keyword is marked as active. If the keyword is followed by any text,
+ * MSG_INPUT_CHANGED is fired with the current callback ID that can be
+ * used to provide suggestions to the urlbar while the callback ID is active.
+ * The callback is invalidated when either the input changes or the urlbar blurs.
+ *
+ * @param {string} keyword The keyword to handle.
+ * @param {string} text The search text in the urlbar.
+ * @param {Function} callback The callback used to provide search suggestions.
+ * @return {Promise} promise that resolves when the current search is complete.
+ */
+ handleSearch(keyword, text, callback) {
+ if (!gKeywordMap.has(keyword)) {
+ throw new Error(`The keyword provided is not registered: "${keyword}"`);
+ }
+
+ if (gActiveInputSession && gActiveInputSession.keyword != keyword) {
+ throw new Error("A different input session is already ongoing");
+ }
+
+ if (!text || !text.startsWith(`${keyword} `)) {
+ throw new Error(`The text provided must start with: "${keyword} "`);
+ }
+
+ if (!callback) {
+ throw new Error("A callback must be provided");
+ }
+
+ // The search text in the urlbar currently starts with <keyword><space>, and
+ // we only want the text that follows.
+ text = text.substring(keyword.length + 1);
+
+ // We fire MSG_INPUT_STARTED once we have <keyword><space>, and only fire
+ // MSG_INPUT_CHANGED when we have text to process. This is different from Chrome's
+ // behavior, which always fires MSG_INPUT_STARTED right before MSG_INPUT_CHANGED
+ // first fires, but this is a bug in Chrome according to https://crbug.com/258911.
+ if (!gActiveInputSession) {
+ gActiveInputSession = new InputSession(keyword, gKeywordMap.get(keyword).extension);
+ gActiveInputSession.start(this.MSG_INPUT_STARTED);
+
+ // Resolve early if there is no text to process. There can be text to process when
+ // the input starts if the user copy/pastes the text into the urlbar.
+ if (!text.length) {
+ return Promise.resolve();
+ }
+ }
+
+ return new Promise(resolve => {
+ gActiveInputSession.update(this.MSG_INPUT_CHANGED, text, callback, resolve);
+ });
+ },
+
+ /**
+ * Called when the user clicks on a suggestion that was added by
+ * an extension. MSG_INPUT_ENTERED is emitted to the extension with
+ * the keyword, the current search string, and info about how the
+ * the search should be handled. This ends the active input session.
+ *
+ * @param {string} keyword The keyword associated to the suggestion.
+ * @param {string} text The search text in the urlbar.
+ * @param {string} where How the page should be opened. Accepted values are:
+ * "current": open the page in the same tab.
+ * "tab": open the page in a new foreground tab.
+ * "tabshifted": open the page in a new background tab.
+ */
+ handleInputEntered(keyword, text, where) {
+ if (!gKeywordMap.has(keyword)) {
+ throw new Error(`The keyword provided is not registered: "${keyword}"`);
+ }
+
+ if (gActiveInputSession && gActiveInputSession.keyword != keyword) {
+ throw new Error("A different input session is already ongoing");
+ }
+
+ if (!text || !text.startsWith(`${keyword} `)) {
+ throw new Error(`The text provided must start with: "${keyword} "`);
+ }
+
+ let dispositionMap = {
+ current: "currentTab",
+ tab: "newForegroundTab",
+ tabshifted: "newBackgroundTab",
+ }
+ let disposition = dispositionMap[where];
+
+ if (!disposition) {
+ throw new Error(`Invalid "where" argument: ${where}`);
+ }
+
+ // The search text in the urlbar currently starts with <keyword><space>, and
+ // we only want to send the text that follows.
+ text = text.substring(keyword.length + 1);
+
+ gActiveInputSession.end(this.MSG_INPUT_ENTERED, text, disposition)
+ gActiveInputSession = null;
+ },
+
+ /**
+ * If the user has ended the keyword input session without accepting the input,
+ * MSG_INPUT_CANCELLED is emitted and the input session is ended.
+ */
+ handleInputCancelled() {
+ if (!gActiveInputSession) {
+ throw new Error("There is no active input session");
+ }
+ gActiveInputSession.cancel(this.MSG_INPUT_CANCELLED);
+ gActiveInputSession = null;
+ }
+});
diff --git a/components/places/src/FaviconHelpers.cpp b/components/places/src/FaviconHelpers.cpp
new file mode 100644
index 000000000..ca08cdff4
--- /dev/null
+++ b/components/places/src/FaviconHelpers.cpp
@@ -0,0 +1,906 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FaviconHelpers.h"
+
+#include "nsICacheEntry.h"
+#include "nsICachingChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIPrincipal.h"
+
+#include "nsNavHistory.h"
+#include "nsFaviconService.h"
+#include "mozilla/storage.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsStreamUtils.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsISupportsPriority.h"
+#include "nsContentUtils.h"
+#include <algorithm>
+
+using namespace mozilla::places;
+using namespace mozilla::storage;
+
+namespace mozilla {
+namespace places {
+
+namespace {
+
+/**
+ * Fetches information on a page from the Places database.
+ *
+ * @param aDBConn
+ * Database connection to history tables.
+ * @param _page
+ * Page that should be fetched.
+ */
+nsresult
+FetchPageInfo(const RefPtr<Database>& aDB,
+ PageData& _page)
+{
+ MOZ_ASSERT(_page.spec.Length(), "Must have a non-empty spec!");
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // This query finds the bookmarked uri we want to set the icon for,
+ // walking up to two redirect levels.
+ nsCString query = nsPrintfCString(
+ "SELECT h.id, h.favicon_id, h.guid, ( "
+ "SELECT h.url FROM moz_bookmarks b WHERE b.fk = h.id "
+ "UNION ALL " // Union not directly bookmarked pages.
+ "SELECT url FROM moz_places WHERE id = ( "
+ "SELECT COALESCE(grandparent.place_id, parent.place_id) as r_place_id "
+ "FROM moz_historyvisits dest "
+ "LEFT JOIN moz_historyvisits parent ON parent.id = dest.from_visit "
+ "AND dest.visit_type IN (%d, %d) "
+ "LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
+ "AND parent.visit_type IN (%d, %d) "
+ "WHERE dest.place_id = h.id "
+ "AND EXISTS(SELECT 1 FROM moz_bookmarks b WHERE b.fk = r_place_id) "
+ "LIMIT 1 "
+ ") "
+ ") FROM moz_places h WHERE h.url_hash = hash(:page_url) AND h.url = :page_url",
+ nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
+ nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY,
+ nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
+ nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
+ );
+
+ nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(query);
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
+ _page.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hasResult) {
+ // The page does not exist.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ rv = stmt->GetInt64(0, &_page.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isNull;
+ rv = stmt->GetIsNull(1, &isNull);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // favicon_id can be nullptr.
+ if (!isNull) {
+ rv = stmt->GetInt64(1, &_page.iconId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = stmt->GetUTF8String(2, _page.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetIsNull(3, &isNull);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // The page could not be bookmarked.
+ if (!isNull) {
+ rv = stmt->GetUTF8String(3, _page.bookmarkedSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!_page.canAddToHistory) {
+ // Either history is disabled or the scheme is not supported. In such a
+ // case we want to update the icon only if the page is bookmarked.
+
+ if (_page.bookmarkedSpec.IsEmpty()) {
+ // The page is not bookmarked. Since updating the icon with a disabled
+ // history would be a privacy leak, bail out as if the page did not exist.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ else {
+ // The page, or a redirect to it, is bookmarked. If the bookmarked spec
+ // is different from the requested one, use it.
+ if (!_page.bookmarkedSpec.Equals(_page.spec)) {
+ _page.spec = _page.bookmarkedSpec;
+ rv = FetchPageInfo(aDB, _page);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Stores information on a icon in the database.
+ *
+ * @param aDBConn
+ * Database connection to history tables.
+ * @param aIcon
+ * Icon that should be stored.
+ */
+nsresult
+SetIconInfo(const RefPtr<Database>& aDB,
+ const IconData& aIcon)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
+ "INSERT OR REPLACE INTO moz_favicons "
+ "(id, url, data, mime_type, expiration) "
+ "VALUES ((SELECT id FROM moz_favicons WHERE url = :icon_url), "
+ ":icon_url, :data, :mime_type, :expiration) "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), aIcon.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindBlobByName(NS_LITERAL_CSTRING("data"),
+ TO_INTBUFFER(aIcon.data), aIcon.data.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("mime_type"), aIcon.mimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("expiration"), aIcon.expiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+/**
+ * Fetches information on a icon from the Places database.
+ *
+ * @param aDBConn
+ * Database connection to history tables.
+ * @param _icon
+ * Icon that should be fetched.
+ */
+nsresult
+FetchIconInfo(const RefPtr<Database>& aDB,
+ IconData& _icon)
+{
+ MOZ_ASSERT(_icon.spec.Length(), "Must have a non-empty spec!");
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (_icon.status & ICON_STATUS_CACHED) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
+ "SELECT id, expiration, data, mime_type "
+ "FROM moz_favicons WHERE url = :icon_url"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ DebugOnly<nsresult> rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"),
+ _icon.spec);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!hasResult) {
+ // The icon does not exist yet, bail out.
+ return NS_OK;
+ }
+
+ rv = stmt->GetInt64(0, &_icon.id);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Expiration can be nullptr.
+ bool isNull;
+ rv = stmt->GetIsNull(1, &isNull);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!isNull) {
+ rv = stmt->GetInt64(1, reinterpret_cast<int64_t*>(&_icon.expiration));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // Data can be nullptr.
+ rv = stmt->GetIsNull(2, &isNull);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!isNull) {
+ uint8_t* data;
+ uint32_t dataLen = 0;
+ rv = stmt->GetBlob(2, &dataLen, &data);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ _icon.data.Adopt(TO_CHARBUFFER(data), dataLen);
+ // Read mime only if we have data.
+ rv = stmt->GetUTF8String(3, _icon.mimeType);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FetchIconURL(const RefPtr<Database>& aDB,
+ const nsACString& aPageSpec,
+ nsACString& aIconSpec)
+{
+ MOZ_ASSERT(!aPageSpec.IsEmpty(), "Page spec must not be empty.");
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ aIconSpec.Truncate();
+
+ nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
+ "SELECT f.url "
+ "FROM moz_places h "
+ "JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
+ aPageSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult;
+ if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ rv = stmt->GetUTF8String(0, aIconSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Tries to compute the expiration time for a icon from the channel.
+ *
+ * @param aChannel
+ * The network channel used to fetch the icon.
+ * @return a valid expiration value for the fetched icon.
+ */
+PRTime
+GetExpirationTimeFromChannel(nsIChannel* aChannel)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Attempt to get an expiration time from the cache. If this fails, we'll
+ // make one up.
+ PRTime expiration = -1;
+ nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aChannel);
+ if (cachingChannel) {
+ nsCOMPtr<nsISupports> cacheToken;
+ nsresult rv = cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
+ uint32_t seconds;
+ rv = cacheEntry->GetExpirationTime(&seconds);
+ if (NS_SUCCEEDED(rv)) {
+ // Set the expiration, but make sure we honor our cap.
+ expiration = PR_Now() + std::min((PRTime)seconds * PR_USEC_PER_SEC,
+ MAX_FAVICON_EXPIRATION);
+ }
+ }
+ }
+ // If we did not obtain a time from the cache, use the cap value.
+ return expiration < 0 ? PR_Now() + MAX_FAVICON_EXPIRATION
+ : expiration;
+}
+
+/**
+ * Checks the icon and evaluates if it needs to be optimized. In such a case it
+ * will try to reduce its size through OptimizeFaviconImage method of the
+ * favicons service.
+ *
+ * @param aIcon
+ * The icon to be evaluated.
+ * @param aFaviconSvc
+ * Pointer to the favicons service.
+ */
+nsresult
+OptimizeIconSize(IconData& aIcon,
+ nsFaviconService* aFaviconSvc)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Even if the page provides a large image for the favicon (eg, a highres
+ // image or a multiresolution .ico file), don't try to store more data than
+ // needed.
+ nsAutoCString newData, newMimeType;
+ if (aIcon.data.Length() > MAX_FAVICON_FILESIZE) {
+ nsresult rv = aFaviconSvc->OptimizeFaviconImage(TO_INTBUFFER(aIcon.data),
+ aIcon.data.Length(),
+ aIcon.mimeType,
+ newData,
+ newMimeType);
+ if (NS_SUCCEEDED(rv) && newData.Length() < aIcon.data.Length()) {
+ aIcon.data = newData;
+ aIcon.mimeType = newMimeType;
+ }
+ }
+ return NS_OK;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncFetchAndSetIconForPage
+
+NS_IMPL_ISUPPORTS_INHERITED(
+ AsyncFetchAndSetIconForPage
+, Runnable
+, nsIStreamListener
+, nsIInterfaceRequestor
+, nsIChannelEventSink
+, mozIPlacesPendingOperation
+)
+
+AsyncFetchAndSetIconForPage::AsyncFetchAndSetIconForPage(
+ IconData& aIcon
+, PageData& aPage
+, bool aFaviconLoadPrivate
+, nsIFaviconDataCallback* aCallback
+, nsIPrincipal* aLoadingPrincipal
+) : mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(aCallback))
+ , mIcon(aIcon)
+ , mPage(aPage)
+ , mFaviconLoadPrivate(aFaviconLoadPrivate)
+ , mLoadingPrincipal(new nsMainThreadPtrHolder<nsIPrincipal>(aLoadingPrincipal))
+ , mCanceled(false)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+NS_IMETHODIMP
+AsyncFetchAndSetIconForPage::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Try to fetch the icon from the database.
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ nsresult rv = FetchIconInfo(DB, mIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isInvalidIcon = mIcon.data.IsEmpty() ||
+ (mIcon.expiration && PR_Now() > mIcon.expiration);
+ bool fetchIconFromNetwork = mIcon.fetchMode == FETCH_ALWAYS ||
+ (mIcon.fetchMode == FETCH_IF_MISSING && isInvalidIcon);
+
+ if (!fetchIconFromNetwork) {
+ // There is already a valid icon or we don't want to fetch a new one,
+ // directly proceed with association.
+ RefPtr<AsyncAssociateIconToPage> event =
+ new AsyncAssociateIconToPage(mIcon, mPage, mCallback);
+ DB->DispatchToAsyncThread(event);
+
+ return NS_OK;
+ }
+
+ // Fetch the icon from the network, the request starts from the main-thread.
+ // When done this will associate the icon to the page and notify.
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &AsyncFetchAndSetIconForPage::FetchFromNetwork);
+ return NS_DispatchToMainThread(event);
+}
+
+nsresult
+AsyncFetchAndSetIconForPage::FetchFromNetwork() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ // Ensure data is cleared, since it's going to be overwritten.
+ if (mIcon.data.Length() > 0) {
+ mIcon.data.Truncate(0);
+ mIcon.mimeType.Truncate(0);
+ }
+
+ nsCOMPtr<nsIURI> iconURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ iconURI,
+ mLoadingPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_ALLOW_CHROME |
+ nsILoadInfo::SEC_DISALLOW_SCRIPT,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIInterfaceRequestor> listenerRequestor =
+ do_QueryInterface(reinterpret_cast<nsISupports*>(this));
+ NS_ENSURE_STATE(listenerRequestor);
+ rv = channel->SetNotificationCallbacks(listenerRequestor);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(channel);
+ if (pbChannel) {
+ rv = pbChannel->SetPrivate(mFaviconLoadPrivate);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(channel);
+ if (priorityChannel) {
+ priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_LOWEST);
+ }
+
+ rv = channel->AsyncOpen2(this);
+ if (NS_SUCCEEDED(rv)) {
+ mRequest = channel;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+AsyncFetchAndSetIconForPage::Cancel()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mCanceled) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mCanceled = true;
+ if (mRequest) {
+ mRequest->Cancel(NS_BINDING_ABORTED);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncFetchAndSetIconForPage::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ // mRequest should already be set from ::FetchFromNetwork, but in the case of
+ // a redirect we might get a new request, and we should make sure we keep a
+ // reference to the most current request.
+ mRequest = aRequest;
+ if (mCanceled) {
+ mRequest->Cancel(NS_BINDING_ABORTED);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncFetchAndSetIconForPage::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ const size_t kMaxFaviconDownloadSize = 1 * 1024 * 1024;
+ if (mIcon.data.Length() + aCount > kMaxFaviconDownloadSize) {
+ mIcon.data.Truncate();
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ nsAutoCString buffer;
+ nsresult rv = NS_ConsumeStream(aInputStream, aCount, buffer);
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!mIcon.data.Append(buffer, fallible)) {
+ mIcon.data.Truncate();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+AsyncFetchAndSetIconForPage::GetInterface(const nsIID& uuid,
+ void** aResult)
+{
+ return QueryInterface(uuid, aResult);
+}
+
+
+NS_IMETHODIMP
+AsyncFetchAndSetIconForPage::AsyncOnChannelRedirect(
+ nsIChannel* oldChannel
+, nsIChannel* newChannel
+, uint32_t flags
+, nsIAsyncVerifyRedirectCallback *cb
+)
+{
+ // If we've been canceled, stop the redirect with NS_BINDING_ABORTED, and
+ // handle the cancel on the original channel.
+ (void)cb->OnRedirectVerifyCallback(mCanceled ? NS_BINDING_ABORTED : NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncFetchAndSetIconForPage::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatusCode)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Don't need to track this anymore.
+ mRequest = nullptr;
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ nsFaviconService* favicons = nsFaviconService::GetFaviconService();
+ NS_ENSURE_STATE(favicons);
+
+ nsresult rv;
+
+ // If fetching the icon failed, add it to the failed cache.
+ if (NS_FAILED(aStatusCode) || mIcon.data.Length() == 0) {
+ nsCOMPtr<nsIURI> iconURI;
+ rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = favicons->AddFailedFavicon(iconURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ // aRequest should always QI to nsIChannel.
+ MOZ_ASSERT(channel);
+
+ nsAutoCString contentType;
+ channel->GetContentType(contentType);
+ // Bug 366324 - can't sniff SVG yet, so rely on server-specified type
+ if (contentType.EqualsLiteral("image/svg+xml")) {
+ mIcon.mimeType.AssignLiteral("image/svg+xml");
+ } else {
+ NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, aRequest,
+ TO_INTBUFFER(mIcon.data), mIcon.data.Length(),
+ mIcon.mimeType);
+ }
+
+ // If the icon does not have a valid MIME type, add it to the failed cache.
+ if (mIcon.mimeType.IsEmpty()) {
+ nsCOMPtr<nsIURI> iconURI;
+ rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = favicons->AddFailedFavicon(iconURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ mIcon.expiration = GetExpirationTimeFromChannel(channel);
+
+ rv = OptimizeIconSize(mIcon, favicons);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If over the maximum size allowed, don't save data to the database to
+ // avoid bloating it.
+ if (mIcon.data.Length() > nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
+ return NS_OK;
+ }
+
+ mIcon.status = ICON_STATUS_CHANGED;
+
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ RefPtr<AsyncAssociateIconToPage> event =
+ new AsyncAssociateIconToPage(mIcon, mPage, mCallback);
+ DB->DispatchToAsyncThread(event);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncAssociateIconToPage
+
+AsyncAssociateIconToPage::AsyncAssociateIconToPage(
+ const IconData& aIcon
+, const PageData& aPage
+, const nsMainThreadPtrHandle<nsIFaviconDataCallback>& aCallback
+) : mCallback(aCallback)
+ , mIcon(aIcon)
+ , mPage(aPage)
+{
+}
+
+NS_IMETHODIMP
+AsyncAssociateIconToPage::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ nsresult rv = FetchPageInfo(DB, mPage);
+ if (rv == NS_ERROR_NOT_AVAILABLE){
+ // We have never seen this page. If we can add the page to history,
+ // we will try to do it later, otherwise just bail out.
+ if (!mPage.canAddToHistory) {
+ return NS_OK;
+ }
+ }
+ else {
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mozStorageTransaction transaction(DB->MainConn(), false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ // If there is no entry for this icon, or the entry is obsolete, replace it.
+ if (mIcon.id == 0 || (mIcon.status & ICON_STATUS_CHANGED)) {
+ rv = SetIconInfo(DB, mIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the new icon id. Do this regardless mIcon.id, since other code
+ // could have added a entry before us. Indeed we interrupted the thread
+ // after the previous call to FetchIconInfo.
+ mIcon.status = (mIcon.status & ~(ICON_STATUS_CACHED)) | ICON_STATUS_SAVED;
+ rv = FetchIconInfo(DB, mIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If the page does not have an id, don't try to insert a new one, cause we
+ // don't know where the page comes from. Not doing so we may end adding
+ // a page that otherwise we'd explicitly ignore, like a POST or an error page.
+ if (mPage.id == 0) {
+ return NS_OK;
+ }
+
+ // Otherwise just associate the icon to the page, if needed.
+ if (mPage.iconId != mIcon.id) {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ if (mPage.id) {
+ stmt = DB->GetStatement(
+ "UPDATE moz_places SET favicon_id = :icon_id WHERE id = :page_id"
+ );
+ NS_ENSURE_STATE(stmt);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPage.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ stmt = DB->GetStatement(
+ "UPDATE moz_places SET favicon_id = :icon_id "
+ "WHERE url_hash = hash(:page_url) AND url = :page_url"
+ );
+ NS_ENSURE_STATE(stmt);
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPage.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), mIcon.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozStorageStatementScoper scoper(stmt);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIcon.status |= ICON_STATUS_ASSOCIATED;
+ }
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, dispatch an event to the main thread to notify observers.
+ nsCOMPtr<nsIRunnable> event = new NotifyIconObservers(mIcon, mPage, mCallback);
+ rv = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncGetFaviconURLForPage
+
+AsyncGetFaviconURLForPage::AsyncGetFaviconURLForPage(
+ const nsACString& aPageSpec
+, nsIFaviconDataCallback* aCallback
+) : mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(aCallback))
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mPageSpec.Assign(aPageSpec);
+}
+
+NS_IMETHODIMP
+AsyncGetFaviconURLForPage::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ nsAutoCString iconSpec;
+ nsresult rv = FetchIconURL(DB, mPageSpec, iconSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now notify our callback of the icon spec we retrieved, even if empty.
+ IconData iconData;
+ iconData.spec.Assign(iconSpec);
+
+ PageData pageData;
+ pageData.spec.Assign(mPageSpec);
+
+ nsCOMPtr<nsIRunnable> event =
+ new NotifyIconObservers(iconData, pageData, mCallback);
+ rv = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncGetFaviconDataForPage
+
+AsyncGetFaviconDataForPage::AsyncGetFaviconDataForPage(
+ const nsACString& aPageSpec
+, nsIFaviconDataCallback* aCallback
+) : mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(aCallback))
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mPageSpec.Assign(aPageSpec);
+}
+
+NS_IMETHODIMP
+AsyncGetFaviconDataForPage::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ nsAutoCString iconSpec;
+ nsresult rv = FetchIconURL(DB, mPageSpec, iconSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ IconData iconData;
+ iconData.spec.Assign(iconSpec);
+
+ PageData pageData;
+ pageData.spec.Assign(mPageSpec);
+
+ if (!iconSpec.IsEmpty()) {
+ rv = FetchIconInfo(DB, iconData);
+ if (NS_FAILED(rv)) {
+ iconData.spec.Truncate();
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> event =
+ new NotifyIconObservers(iconData, pageData, mCallback);
+ rv = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncReplaceFaviconData
+
+AsyncReplaceFaviconData::AsyncReplaceFaviconData(const IconData &aIcon)
+ : mIcon(aIcon)
+{
+}
+
+NS_IMETHODIMP
+AsyncReplaceFaviconData::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ IconData dbIcon;
+ dbIcon.spec.Assign(mIcon.spec);
+ nsresult rv = FetchIconInfo(DB, dbIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!dbIcon.id) {
+ return NS_OK;
+ }
+
+ rv = SetIconInfo(DB, mIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We can invalidate the cache version since we now persist the icon.
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &AsyncReplaceFaviconData::RemoveIconDataCacheEntry);
+ rv = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+AsyncReplaceFaviconData::RemoveIconDataCacheEntry()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIURI> iconURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(iconURI), mIcon.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsFaviconService* favicons = nsFaviconService::GetFaviconService();
+ NS_ENSURE_STATE(favicons);
+ favicons->mUnassociatedIcons.RemoveEntry(iconURI);
+
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// NotifyIconObservers
+
+NotifyIconObservers::NotifyIconObservers(
+ const IconData& aIcon
+, const PageData& aPage
+, const nsMainThreadPtrHandle<nsIFaviconDataCallback>& aCallback
+)
+: mCallback(aCallback)
+, mIcon(aIcon)
+, mPage(aPage)
+{
+}
+
+NS_IMETHODIMP
+NotifyIconObservers::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIURI> iconURI;
+ if (!mIcon.spec.IsEmpty()) {
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(iconURI), mIcon.spec));
+ if (iconURI)
+ {
+ // Notify observers only if something changed.
+ if (mIcon.status & ICON_STATUS_SAVED ||
+ mIcon.status & ICON_STATUS_ASSOCIATED) {
+ SendGlobalNotifications(iconURI);
+ }
+ }
+ }
+
+ if (mCallback) {
+ (void)mCallback->OnComplete(iconURI, mIcon.data.Length(),
+ TO_INTBUFFER(mIcon.data), mIcon.mimeType);
+ }
+
+ return NS_OK;
+}
+
+void
+NotifyIconObservers::SendGlobalNotifications(nsIURI* aIconURI)
+{
+ nsCOMPtr<nsIURI> pageURI;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(pageURI), mPage.spec));
+ if (pageURI) {
+ nsFaviconService* favicons = nsFaviconService::GetFaviconService();
+ MOZ_ASSERT(favicons);
+ if (favicons) {
+ (void)favicons->SendFaviconNotifications(pageURI, aIconURI, mPage.guid);
+ }
+ }
+
+ // If the page is bookmarked and the bookmarked url is different from the
+ // updated one, start a new task to update its icon as well.
+ if (!mPage.bookmarkedSpec.IsEmpty() &&
+ !mPage.bookmarkedSpec.Equals(mPage.spec)) {
+ // Create a new page struct to avoid polluting it with old data.
+ PageData bookmarkedPage;
+ bookmarkedPage.spec = mPage.bookmarkedSpec;
+
+ RefPtr<Database> DB = Database::GetDatabase();
+ if (!DB)
+ return;
+ // This will be silent, so be sure to not pass in the current callback.
+ nsMainThreadPtrHandle<nsIFaviconDataCallback> nullCallback;
+ RefPtr<AsyncAssociateIconToPage> event =
+ new AsyncAssociateIconToPage(mIcon, bookmarkedPage, nullCallback);
+ DB->DispatchToAsyncThread(event);
+ }
+}
+
+} // namespace places
+} // namespace mozilla
diff --git a/components/places/src/FaviconHelpers.h b/components/places/src/FaviconHelpers.h
new file mode 100644
index 000000000..1c6d5b2bf
--- /dev/null
+++ b/components/places/src/FaviconHelpers.h
@@ -0,0 +1,273 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+#include "nsIFaviconService.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "mozIPlacesPendingOperation.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+
+class nsIPrincipal;
+
+#include "Database.h"
+#include "mozilla/storage.h"
+
+#define ICON_STATUS_UNKNOWN 0
+#define ICON_STATUS_CHANGED 1 << 0
+#define ICON_STATUS_SAVED 1 << 1
+#define ICON_STATUS_ASSOCIATED 1 << 2
+#define ICON_STATUS_CACHED 1 << 3
+
+#define TO_CHARBUFFER(_buffer) \
+ reinterpret_cast<char*>(const_cast<uint8_t*>(_buffer))
+#define TO_INTBUFFER(_string) \
+ reinterpret_cast<uint8_t*>(const_cast<char*>(_string.get()))
+
+/**
+ * The maximum time we will keep a favicon around. We always ask the cache, if
+ * we can, but default to this value if we do not get a time back, or the time
+ * is more in the future than this.
+ * Currently set to one week from now.
+ */
+#define MAX_FAVICON_EXPIRATION ((PRTime)7 * 24 * 60 * 60 * PR_USEC_PER_SEC)
+
+namespace mozilla {
+namespace places {
+
+/**
+ * Indicates when a icon should be fetched from network.
+ */
+enum AsyncFaviconFetchMode {
+ FETCH_NEVER = 0
+, FETCH_IF_MISSING
+, FETCH_ALWAYS
+};
+
+/**
+ * Data cache for a icon entry.
+ */
+struct IconData
+{
+ IconData()
+ : id(0)
+ , expiration(0)
+ , fetchMode(FETCH_NEVER)
+ , status(ICON_STATUS_UNKNOWN)
+ {
+ }
+
+ int64_t id;
+ nsCString spec;
+ nsCString data;
+ nsCString mimeType;
+ PRTime expiration;
+ enum AsyncFaviconFetchMode fetchMode;
+ uint16_t status; // This is a bitset, see ICON_STATUS_* defines above.
+};
+
+/**
+ * Data cache for a page entry.
+ */
+struct PageData
+{
+ PageData()
+ : id(0)
+ , canAddToHistory(true)
+ , iconId(0)
+ {
+ guid.SetIsVoid(true);
+ }
+
+ int64_t id;
+ nsCString spec;
+ nsCString bookmarkedSpec;
+ nsString revHost;
+ bool canAddToHistory; // False for disabled history and unsupported schemas.
+ int64_t iconId;
+ nsCString guid;
+};
+
+/**
+ * Async fetches icon from database or network, associates it with the required
+ * page and finally notifies the change.
+ */
+class AsyncFetchAndSetIconForPage final : public Runnable
+ , public nsIStreamListener
+ , public nsIInterfaceRequestor
+ , public nsIChannelEventSink
+ , public mozIPlacesPendingOperation
+ {
+ public:
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_MOZIPLACESPENDINGOPERATION
+ NS_DECL_ISUPPORTS_INHERITED
+
+ /**
+ * Constructor.
+ *
+ * @param aIcon
+ * Icon to be fetched and associated.
+ * @param aPage
+ * Page to which associate the icon.
+ * @param aFaviconLoadPrivate
+ * Whether this favicon load is in private browsing.
+ * @param aCallback
+ * Function to be called when the fetch-and-associate process finishes.
+ * @param aLoadingPrincipal
+ * LoadingPrincipal of the icon to be fetched.
+ */
+ AsyncFetchAndSetIconForPage(IconData& aIcon,
+ PageData& aPage,
+ bool aFaviconLoadPrivate,
+ nsIFaviconDataCallback* aCallback,
+ nsIPrincipal* aLoadingPrincipal);
+
+private:
+ nsresult FetchFromNetwork();
+ virtual ~AsyncFetchAndSetIconForPage() {}
+
+ nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
+ IconData mIcon;
+ PageData mPage;
+ const bool mFaviconLoadPrivate;
+ nsMainThreadPtrHandle<nsIPrincipal> mLoadingPrincipal;
+ bool mCanceled;
+ nsCOMPtr<nsIRequest> mRequest;
+};
+
+/**
+ * Associates the icon to the required page, finally dispatches an event to the
+ * main thread to notify the change to observers.
+ */
+class AsyncAssociateIconToPage final : public Runnable
+{
+public:
+ NS_DECL_NSIRUNNABLE
+
+ /**
+ * Constructor.
+ *
+ * @param aIcon
+ * Icon to be associated.
+ * @param aPage
+ * Page to which associate the icon.
+ * @param aCallback
+ * Function to be called when the associate process finishes.
+ */
+ AsyncAssociateIconToPage(const IconData& aIcon,
+ const PageData& aPage,
+ const nsMainThreadPtrHandle<nsIFaviconDataCallback>& aCallback);
+
+private:
+ nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
+ IconData mIcon;
+ PageData mPage;
+};
+
+/**
+ * Asynchronously tries to get the URL of a page's favicon, then notifies the
+ * given observer.
+ */
+class AsyncGetFaviconURLForPage final : public Runnable
+{
+public:
+ NS_DECL_NSIRUNNABLE
+
+ /**
+ * Constructor.
+ *
+ * @param aPageSpec
+ * URL of the page whose favicon's URL we're fetching
+ * @param aCallback
+ * function to be called once finished
+ */
+ AsyncGetFaviconURLForPage(const nsACString& aPageSpec,
+ nsIFaviconDataCallback* aCallback);
+
+private:
+ nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
+ nsCString mPageSpec;
+};
+
+
+/**
+ * Asynchronously tries to get the URL and data of a page's favicon, then
+ * notifies the given observer.
+ */
+class AsyncGetFaviconDataForPage final : public Runnable
+{
+public:
+ NS_DECL_NSIRUNNABLE
+
+ /**
+ * Constructor.
+ *
+ * @param aPageSpec
+ * URL of the page whose favicon URL and data we're fetching
+ * @param aCallback
+ * function to be called once finished
+ */
+ AsyncGetFaviconDataForPage(const nsACString& aPageSpec,
+ nsIFaviconDataCallback* aCallback);
+
+private:
+ nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
+ nsCString mPageSpec;
+};
+
+class AsyncReplaceFaviconData final : public Runnable
+{
+public:
+ NS_DECL_NSIRUNNABLE
+
+ explicit AsyncReplaceFaviconData(const IconData& aIcon);
+
+private:
+ nsresult RemoveIconDataCacheEntry();
+
+ IconData mIcon;
+};
+
+/**
+ * Notifies the icon change to favicon observers.
+ */
+class NotifyIconObservers final : public Runnable
+{
+public:
+ NS_DECL_NSIRUNNABLE
+
+ /**
+ * Constructor.
+ *
+ * @param aIcon
+ * Icon information. Can be empty if no icon is associated to the page.
+ * @param aPage
+ * Page to which the icon information applies.
+ * @param aCallback
+ * Function to be notified in all cases.
+ */
+ NotifyIconObservers(const IconData& aIcon,
+ const PageData& aPage,
+ const nsMainThreadPtrHandle<nsIFaviconDataCallback>& aCallback);
+
+private:
+ nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
+ IconData mIcon;
+ PageData mPage;
+
+ void SendGlobalNotifications(nsIURI* aIconURI);
+};
+
+} // namespace places
+} // namespace mozilla
diff --git a/components/places/src/Helpers.cpp b/components/places/src/Helpers.cpp
new file mode 100644
index 000000000..13e040bfd
--- /dev/null
+++ b/components/places/src/Helpers.cpp
@@ -0,0 +1,386 @@
+/* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Helpers.h"
+#include "mozIStorageError.h"
+#include "prio.h"
+#include "nsString.h"
+#include "nsNavHistory.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Services.h"
+
+// The length of guids that are used by history and bookmarks.
+#define GUID_LENGTH 12
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncStatementCallback
+
+NS_IMPL_ISUPPORTS(
+ AsyncStatementCallback
+, mozIStorageStatementCallback
+)
+
+NS_IMETHODIMP
+WeakAsyncStatementCallback::HandleResult(mozIStorageResultSet *aResultSet)
+{
+ MOZ_ASSERT(false, "Was not expecting a resultset, but got it.");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WeakAsyncStatementCallback::HandleCompletion(uint16_t aReason)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WeakAsyncStatementCallback::HandleError(mozIStorageError *aError)
+{
+#ifdef DEBUG
+ int32_t result;
+ nsresult rv = aError->GetResult(&result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString message;
+ rv = aError->GetMessage(message);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString warnMsg;
+ warnMsg.AppendLiteral("An error occurred while executing an async statement: ");
+ warnMsg.AppendInt(result);
+ warnMsg.Append(' ');
+ warnMsg.Append(message);
+ NS_WARNING(warnMsg.get());
+#endif
+
+ return NS_OK;
+}
+
+#define URI_TO_URLCSTRING(uri, spec) \
+ nsAutoCString spec; \
+ if (NS_FAILED(aURI->GetSpec(spec))) { \
+ return NS_ERROR_UNEXPECTED; \
+ }
+
+// Bind URI to statement by index.
+nsresult // static
+URIBinder::Bind(mozIStorageStatement* aStatement,
+ int32_t aIndex,
+ nsIURI* aURI)
+{
+ NS_ASSERTION(aStatement, "Must have non-null statement");
+ NS_ASSERTION(aURI, "Must have non-null uri");
+
+ URI_TO_URLCSTRING(aURI, spec);
+ return URIBinder::Bind(aStatement, aIndex, spec);
+}
+
+// Statement URLCString to statement by index.
+nsresult // static
+URIBinder::Bind(mozIStorageStatement* aStatement,
+ int32_t index,
+ const nsACString& aURLString)
+{
+ NS_ASSERTION(aStatement, "Must have non-null statement");
+ return aStatement->BindUTF8StringByIndex(
+ index, StringHead(aURLString, URI_LENGTH_MAX)
+ );
+}
+
+// Bind URI to statement by name.
+nsresult // static
+URIBinder::Bind(mozIStorageStatement* aStatement,
+ const nsACString& aName,
+ nsIURI* aURI)
+{
+ NS_ASSERTION(aStatement, "Must have non-null statement");
+ NS_ASSERTION(aURI, "Must have non-null uri");
+
+ URI_TO_URLCSTRING(aURI, spec);
+ return URIBinder::Bind(aStatement, aName, spec);
+}
+
+// Bind URLCString to statement by name.
+nsresult // static
+URIBinder::Bind(mozIStorageStatement* aStatement,
+ const nsACString& aName,
+ const nsACString& aURLString)
+{
+ NS_ASSERTION(aStatement, "Must have non-null statement");
+ return aStatement->BindUTF8StringByName(
+ aName, StringHead(aURLString, URI_LENGTH_MAX)
+ );
+}
+
+// Bind URI to params by index.
+nsresult // static
+URIBinder::Bind(mozIStorageBindingParams* aParams,
+ int32_t aIndex,
+ nsIURI* aURI)
+{
+ NS_ASSERTION(aParams, "Must have non-null statement");
+ NS_ASSERTION(aURI, "Must have non-null uri");
+
+ URI_TO_URLCSTRING(aURI, spec);
+ return URIBinder::Bind(aParams, aIndex, spec);
+}
+
+// Bind URLCString to params by index.
+nsresult // static
+URIBinder::Bind(mozIStorageBindingParams* aParams,
+ int32_t index,
+ const nsACString& aURLString)
+{
+ NS_ASSERTION(aParams, "Must have non-null statement");
+ return aParams->BindUTF8StringByIndex(
+ index, StringHead(aURLString, URI_LENGTH_MAX)
+ );
+}
+
+// Bind URI to params by name.
+nsresult // static
+URIBinder::Bind(mozIStorageBindingParams* aParams,
+ const nsACString& aName,
+ nsIURI* aURI)
+{
+ NS_ASSERTION(aParams, "Must have non-null params array");
+ NS_ASSERTION(aURI, "Must have non-null uri");
+
+ URI_TO_URLCSTRING(aURI, spec);
+ return URIBinder::Bind(aParams, aName, spec);
+}
+
+// Bind URLCString to params by name.
+nsresult // static
+URIBinder::Bind(mozIStorageBindingParams* aParams,
+ const nsACString& aName,
+ const nsACString& aURLString)
+{
+ NS_ASSERTION(aParams, "Must have non-null params array");
+
+ nsresult rv = aParams->BindUTF8StringByName(
+ aName, StringHead(aURLString, URI_LENGTH_MAX)
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+#undef URI_TO_URLCSTRING
+
+nsresult
+GetReversedHostname(nsIURI* aURI, nsString& aRevHost)
+{
+ nsAutoCString forward8;
+ nsresult rv = aURI->GetHost(forward8);
+ // Not all URIs have a host.
+ if (NS_FAILED(rv))
+ return rv;
+
+ // can't do reversing in UTF8, better use 16-bit chars
+ GetReversedHostname(NS_ConvertUTF8toUTF16(forward8), aRevHost);
+ return NS_OK;
+}
+
+void
+GetReversedHostname(const nsString& aForward, nsString& aRevHost)
+{
+ ReverseString(aForward, aRevHost);
+ aRevHost.Append(char16_t('.'));
+}
+
+void
+ReverseString(const nsString& aInput, nsString& aReversed)
+{
+ aReversed.Truncate(0);
+ for (int32_t i = aInput.Length() - 1; i >= 0; i--) {
+ aReversed.Append(aInput[i]);
+ }
+}
+
+#ifdef XP_WIN
+} // namespace places
+} // namespace mozilla
+
+// Included here because windows.h conflicts with the use of mozIStorageError
+// above, but make sure that these are not included inside mozilla::places.
+#include <windows.h>
+#include <wincrypt.h>
+
+namespace mozilla {
+namespace places {
+#endif
+
+static
+nsresult
+GenerateRandomBytes(uint32_t aSize,
+ uint8_t* _buffer)
+{
+ // On Windows, we'll use its built-in cryptographic API.
+#if defined(XP_WIN)
+ HCRYPTPROV cryptoProvider;
+ BOOL rc = CryptAcquireContext(&cryptoProvider, 0, 0, PROV_RSA_FULL,
+ CRYPT_VERIFYCONTEXT | CRYPT_SILENT);
+ if (rc) {
+ rc = CryptGenRandom(cryptoProvider, aSize, _buffer);
+ (void)CryptReleaseContext(cryptoProvider, 0);
+ }
+ return rc ? NS_OK : NS_ERROR_FAILURE;
+
+ // On Unix, we'll just read in from /dev/urandom.
+#elif defined(XP_UNIX)
+ NS_ENSURE_ARG_MAX(aSize, INT32_MAX);
+ PRFileDesc* urandom = PR_Open("/dev/urandom", PR_RDONLY, 0);
+ nsresult rv = NS_ERROR_FAILURE;
+ if (urandom) {
+ int32_t bytesRead = PR_Read(urandom, _buffer, aSize);
+ if (bytesRead == static_cast<int32_t>(aSize)) {
+ rv = NS_OK;
+ }
+ (void)PR_Close(urandom);
+ }
+ return rv;
+#endif
+}
+
+nsresult
+GenerateGUID(nsCString& _guid)
+{
+ _guid.Truncate();
+
+ // Request raw random bytes and base64url encode them. For each set of three
+ // bytes, we get one character.
+ const uint32_t kRequiredBytesLength =
+ static_cast<uint32_t>(GUID_LENGTH / 4 * 3);
+
+ uint8_t buffer[kRequiredBytesLength];
+ nsresult rv = GenerateRandomBytes(kRequiredBytesLength, buffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = Base64URLEncode(kRequiredBytesLength, buffer,
+ Base64URLEncodePaddingPolicy::Omit, _guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(_guid.Length() == GUID_LENGTH, "GUID is not the right size!");
+ return NS_OK;
+}
+
+bool
+IsValidGUID(const nsACString& aGUID)
+{
+ nsCString::size_type len = aGUID.Length();
+ if (len != GUID_LENGTH) {
+ return false;
+ }
+
+ for (nsCString::size_type i = 0; i < len; i++ ) {
+ char c = aGUID[i];
+ if ((c >= 'a' && c <= 'z') || // a-z
+ (c >= 'A' && c <= 'Z') || // A-Z
+ (c >= '0' && c <= '9') || // 0-9
+ c == '-' || c == '_') { // - or _
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+void
+TruncateTitle(const nsACString& aTitle, nsACString& aTrimmed)
+{
+ aTrimmed = aTitle;
+ if (aTitle.Length() > TITLE_LENGTH_MAX) {
+ aTrimmed = StringHead(aTitle, TITLE_LENGTH_MAX);
+ }
+}
+
+PRTime
+RoundToMilliseconds(PRTime aTime) {
+ return aTime - (aTime % PR_USEC_PER_MSEC);
+}
+
+PRTime
+RoundedPRNow() {
+ return RoundToMilliseconds(PR_Now());
+}
+
+void
+ForceWALCheckpoint()
+{
+ RefPtr<Database> DB = Database::GetDatabase();
+ if (DB) {
+ nsCOMPtr<mozIStorageAsyncStatement> stmt = DB->GetAsyncStatement(
+ "pragma wal_checkpoint "
+ );
+ if (stmt) {
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ (void)stmt->ExecuteAsync(nullptr, getter_AddRefs(handle));
+ }
+ }
+}
+
+bool
+GetHiddenState(bool aIsRedirect,
+ uint32_t aTransitionType)
+{
+ return aTransitionType == nsINavHistoryService::TRANSITION_FRAMED_LINK ||
+ aTransitionType == nsINavHistoryService::TRANSITION_EMBED ||
+ aIsRedirect;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// PlacesEvent
+
+PlacesEvent::PlacesEvent(const char* aTopic)
+: mTopic(aTopic)
+{
+}
+
+NS_IMETHODIMP
+PlacesEvent::Run()
+{
+ Notify();
+ return NS_OK;
+}
+
+void
+PlacesEvent::Notify()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Must only be used on the main thread!");
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ (void)obs->NotifyObservers(nullptr, mTopic, nullptr);
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(
+ PlacesEvent
+, Runnable
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncStatementCallbackNotifier
+
+NS_IMETHODIMP
+AsyncStatementCallbackNotifier::HandleCompletion(uint16_t aReason)
+{
+ if (aReason != mozIStorageStatementCallback::REASON_FINISHED)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ (void)obs->NotifyObservers(nullptr, mTopic, nullptr);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncStatementCallbackNotifier
+
+} // namespace places
+} // namespace mozilla
diff --git a/components/places/src/Helpers.h b/components/places/src/Helpers.h
new file mode 100644
index 000000000..4e79abc75
--- /dev/null
+++ b/components/places/src/Helpers.h
@@ -0,0 +1,275 @@
+/* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_places_Helpers_h_
+#define mozilla_places_Helpers_h_
+
+/**
+ * This file contains helper classes used by various bits of Places code.
+ */
+
+#include "mozilla/storage.h"
+#include "nsIURI.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "prtime.h"
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Asynchronous Statement Callback Helper
+
+class WeakAsyncStatementCallback : public mozIStorageStatementCallback
+{
+public:
+ NS_DECL_MOZISTORAGESTATEMENTCALLBACK
+ WeakAsyncStatementCallback() {}
+
+protected:
+ virtual ~WeakAsyncStatementCallback() {}
+};
+
+class AsyncStatementCallback : public WeakAsyncStatementCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ AsyncStatementCallback() {}
+
+protected:
+ virtual ~AsyncStatementCallback() {}
+};
+
+/**
+ * Macros to use in place of NS_DECL_MOZISTORAGESTATEMENTCALLBACK to declare the
+ * methods this class assumes silent or notreached.
+ */
+#define NS_DECL_ASYNCSTATEMENTCALLBACK \
+ NS_IMETHOD HandleResult(mozIStorageResultSet *) override; \
+ NS_IMETHOD HandleCompletion(uint16_t) override;
+
+/**
+ * Utils to bind a specified URI (or URL) to a statement or binding params, at
+ * the specified index or name.
+ * @note URIs are always bound as UTF8.
+ */
+class URIBinder // static
+{
+public:
+ // Bind URI to statement by index.
+ static nsresult Bind(mozIStorageStatement* statement,
+ int32_t index,
+ nsIURI* aURI);
+ // Statement URLCString to statement by index.
+ static nsresult Bind(mozIStorageStatement* statement,
+ int32_t index,
+ const nsACString& aURLString);
+ // Bind URI to statement by name.
+ static nsresult Bind(mozIStorageStatement* statement,
+ const nsACString& aName,
+ nsIURI* aURI);
+ // Bind URLCString to statement by name.
+ static nsresult Bind(mozIStorageStatement* statement,
+ const nsACString& aName,
+ const nsACString& aURLString);
+ // Bind URI to params by index.
+ static nsresult Bind(mozIStorageBindingParams* aParams,
+ int32_t index,
+ nsIURI* aURI);
+ // Bind URLCString to params by index.
+ static nsresult Bind(mozIStorageBindingParams* aParams,
+ int32_t index,
+ const nsACString& aURLString);
+ // Bind URI to params by name.
+ static nsresult Bind(mozIStorageBindingParams* aParams,
+ const nsACString& aName,
+ nsIURI* aURI);
+ // Bind URLCString to params by name.
+ static nsresult Bind(mozIStorageBindingParams* aParams,
+ const nsACString& aName,
+ const nsACString& aURLString);
+};
+
+/**
+ * This extracts the hostname from the URI and reverses it in the
+ * form that we use (always ending with a "."). So
+ * "http://microsoft.com/" becomes "moc.tfosorcim."
+ *
+ * The idea behind this is that we can create an index over the items in
+ * the reversed host name column, and then query for as much or as little
+ * of the host name as we feel like.
+ *
+ * For example, the query "host >= 'gro.allizom.' AND host < 'gro.allizom/'
+ * Matches all host names ending in '.mozilla.org', including
+ * 'developer.mozilla.org' and just 'mozilla.org' (since we define all
+ * reversed host names to end in a period, even 'mozilla.org' matches).
+ * The important thing is that this operation uses the index. Any substring
+ * calls in a select statement (even if it's for the beginning of a string)
+ * will bypass any indices and will be slow).
+ *
+ * @param aURI
+ * URI that contains spec to reverse
+ * @param aRevHost
+ * Out parameter
+ */
+nsresult GetReversedHostname(nsIURI* aURI, nsString& aRevHost);
+
+/**
+ * Similar method to GetReversedHostName but for strings
+ */
+void GetReversedHostname(const nsString& aForward, nsString& aRevHost);
+
+/**
+ * Reverses a string.
+ *
+ * @param aInput
+ * The string to be reversed
+ * @param aReversed
+ * Output parameter will contain the reversed string
+ */
+void ReverseString(const nsString& aInput, nsString& aReversed);
+
+/**
+ * Generates an 12 character guid to be used by bookmark and history entries.
+ *
+ * @note This guid uses the characters a-z, A-Z, 0-9, '-', and '_'.
+ */
+nsresult GenerateGUID(nsCString& _guid);
+
+/**
+ * Determines if the string is a valid guid or not.
+ *
+ * @param aGUID
+ * The guid to test.
+ * @return true if it is a valid guid, false otherwise.
+ */
+bool IsValidGUID(const nsACString& aGUID);
+
+/**
+ * Truncates the title if it's longer than TITLE_LENGTH_MAX.
+ *
+ * @param aTitle
+ * The title to truncate (if necessary)
+ * @param aTrimmed
+ * Output parameter to return the trimmed string
+ */
+void TruncateTitle(const nsACString& aTitle, nsACString& aTrimmed);
+
+/**
+ * Round down a PRTime value to milliseconds precision (...000).
+ *
+ * @param aTime
+ * a PRTime value.
+ * @return aTime rounded down to milliseconds precision.
+ */
+PRTime RoundToMilliseconds(PRTime aTime);
+
+/**
+ * Round down PR_Now() to milliseconds precision.
+ *
+ * @return @see PR_Now, RoundToMilliseconds.
+ */
+PRTime RoundedPRNow();
+
+/**
+ * Used to finalize a statementCache on a specified thread.
+ */
+template<typename StatementType>
+class FinalizeStatementCacheProxy : public Runnable
+{
+public:
+ /**
+ * Constructor.
+ *
+ * @param aStatementCache
+ * The statementCache that should be finalized.
+ * @param aOwner
+ * The object that owns the statement cache. This runnable will hold
+ * a strong reference to it so aStatementCache will not disappear from
+ * under us.
+ */
+ FinalizeStatementCacheProxy(
+ mozilla::storage::StatementCache<StatementType>& aStatementCache,
+ nsISupports* aOwner
+ )
+ : mStatementCache(aStatementCache)
+ , mOwner(aOwner)
+ , mCallingThread(do_GetCurrentThread())
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ mStatementCache.FinalizeStatements();
+ // Release the owner back on the calling thread.
+ NS_ProxyRelease(mCallingThread, mOwner.forget());
+ return NS_OK;
+ }
+
+protected:
+ mozilla::storage::StatementCache<StatementType>& mStatementCache;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsIThread> mCallingThread;
+};
+
+/**
+ * Forces a WAL checkpoint. This will cause all transactions stored in the
+ * journal file to be committed to the main database.
+ *
+ * @note The checkpoint will force a fsync/flush.
+ */
+void ForceWALCheckpoint();
+
+/**
+ * Determines if a visit should be marked as hidden given its transition type
+ * and whether or not it was a redirect.
+ *
+ * @param aIsRedirect
+ * True if this visit was a redirect, false otherwise.
+ * @param aTransitionType
+ * The transition type of the visit.
+ * @return true if this visit should be hidden.
+ */
+bool GetHiddenState(bool aIsRedirect,
+ uint32_t aTransitionType);
+
+/**
+ * Notifies a specified topic via the observer service.
+ */
+class PlacesEvent : public Runnable
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+
+ explicit PlacesEvent(const char* aTopic);
+protected:
+ ~PlacesEvent() {}
+ void Notify();
+
+ const char* const mTopic;
+};
+
+/**
+ * Used to notify a topic to system observers on async execute completion.
+ */
+class AsyncStatementCallbackNotifier : public AsyncStatementCallback
+{
+public:
+ explicit AsyncStatementCallbackNotifier(const char* aTopic)
+ : mTopic(aTopic)
+ {
+ }
+
+ NS_IMETHOD HandleCompletion(uint16_t aReason);
+
+private:
+ const char* mTopic;
+};
+
+} // namespace places
+} // namespace mozilla
+
+#endif // mozilla_places_Helpers_h_
diff --git a/components/places/src/History.cpp b/components/places/src/History.cpp
new file mode 100644
index 000000000..fe74e230e
--- /dev/null
+++ b/components/places/src/History.cpp
@@ -0,0 +1,2978 @@
+/* -*- 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 "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "nsXULAppAPI.h"
+
+#include "History.h"
+#include "nsNavHistory.h"
+#include "nsNavBookmarks.h"
+#include "nsAnnotationService.h"
+#include "Helpers.h"
+#include "PlaceInfo.h"
+#include "VisitInfo.h"
+#include "nsPlacesMacros.h"
+
+#include "mozilla/storage.h"
+#include "mozilla/dom/Link.h"
+#include "nsDocShellCID.h"
+#include "mozilla/Services.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsIXPConnect.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h" // for nsAutoScriptBlocker
+#include "nsJSUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsPrintfCString.h"
+#include "nsTHashtable.h"
+#include "jsapi.h"
+
+// Initial size for the cache holding visited status observers.
+#define VISIT_OBSERVERS_INITIAL_CACHE_LENGTH 64
+
+// Initial length for the visits removal hash.
+#define VISITS_REMOVAL_INITIAL_HASH_LENGTH 64
+
+#define NS_LINK_VISITED_EVENT_TOPIC "link-visited"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+using mozilla::Unused;
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Global Defines
+
+#define URI_VISITED "visited"
+#define URI_NOT_VISITED "not visited"
+#define URI_VISITED_RESOLUTION_TOPIC "visited-status-resolution"
+// Observer event fired after a visit has been registered in the DB.
+#define URI_VISIT_SAVED "uri-visit-saved"
+
+#define DESTINATIONFILEURI_ANNO \
+ NS_LITERAL_CSTRING("downloads/destinationFileURI")
+#define DESTINATIONFILENAME_ANNO \
+ NS_LITERAL_CSTRING("downloads/destinationFileName")
+
+////////////////////////////////////////////////////////////////////////////////
+//// VisitData
+
+struct VisitData {
+ VisitData()
+ : placeId(0)
+ , visitId(0)
+ , hidden(true)
+ , shouldUpdateHidden(true)
+ , typed(false)
+ , transitionType(UINT32_MAX)
+ , visitTime(0)
+ , frecency(-1)
+ , lastVisitId(0)
+ , lastVisitTime(0)
+ , visitCount(0)
+ , referrerVisitId(0)
+ , titleChanged(false)
+ , shouldUpdateFrecency(true)
+ {
+ guid.SetIsVoid(true);
+ title.SetIsVoid(true);
+ }
+
+ explicit VisitData(nsIURI* aURI,
+ nsIURI* aReferrer = nullptr)
+ : placeId(0)
+ , visitId(0)
+ , hidden(true)
+ , shouldUpdateHidden(true)
+ , typed(false)
+ , transitionType(UINT32_MAX)
+ , visitTime(0)
+ , frecency(-1)
+ , lastVisitId(0)
+ , lastVisitTime(0)
+ , visitCount(0)
+ , referrerVisitId(0)
+ , titleChanged(false)
+ , shouldUpdateFrecency(true)
+ {
+ MOZ_ASSERT(aURI);
+ if (aURI) {
+ (void)aURI->GetSpec(spec);
+ (void)GetReversedHostname(aURI, revHost);
+ }
+ if (aReferrer) {
+ (void)aReferrer->GetSpec(referrerSpec);
+ }
+ guid.SetIsVoid(true);
+ title.SetIsVoid(true);
+ }
+
+ /**
+ * Sets the transition type of the visit, as well as if it was typed.
+ *
+ * @param aTransitionType
+ * The transition type constant to set. Must be one of the
+ * TRANSITION_ constants on nsINavHistoryService.
+ */
+ void SetTransitionType(uint32_t aTransitionType)
+ {
+ typed = aTransitionType == nsINavHistoryService::TRANSITION_TYPED;
+ transitionType = aTransitionType;
+ }
+
+ int64_t placeId;
+ nsCString guid;
+ int64_t visitId;
+ nsCString spec;
+ nsString revHost;
+ bool hidden;
+ bool shouldUpdateHidden;
+ bool typed;
+ uint32_t transitionType;
+ PRTime visitTime;
+ int32_t frecency;
+ int64_t lastVisitId;
+ PRTime lastVisitTime;
+ uint32_t visitCount;
+
+ /**
+ * Stores the title. If this is empty (IsEmpty() returns true), then the
+ * title should be removed from the Place. If the title is void (IsVoid()
+ * returns true), then no title has been set on this object, and titleChanged
+ * should remain false.
+ */
+ nsString title;
+
+ nsCString referrerSpec;
+ int64_t referrerVisitId;
+
+ // TODO bug 626836 hook up hidden and typed change tracking too!
+ bool titleChanged;
+
+ // Indicates whether frecency should be updated for this visit.
+ bool shouldUpdateFrecency;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// RemoveVisitsFilter
+
+/**
+ * Used to store visit filters for RemoveVisits.
+ */
+struct RemoveVisitsFilter {
+ RemoveVisitsFilter()
+ : transitionType(UINT32_MAX)
+ {
+ }
+
+ uint32_t transitionType;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// PlaceHashKey
+
+class PlaceHashKey : public nsCStringHashKey
+{
+public:
+ explicit PlaceHashKey(const nsACString& aSpec)
+ : nsCStringHashKey(&aSpec)
+ , mVisitCount(0)
+ , mBookmarked(false)
+#ifdef DEBUG
+ , mIsInitialized(false)
+#endif
+ {
+ }
+
+ explicit PlaceHashKey(const nsACString* aSpec)
+ : nsCStringHashKey(aSpec)
+ , mVisitCount(0)
+ , mBookmarked(false)
+#ifdef DEBUG
+ , mIsInitialized(false)
+#endif
+ {
+ }
+
+ PlaceHashKey(const PlaceHashKey& aOther)
+ : nsCStringHashKey(&aOther.GetKey())
+ {
+ MOZ_ASSERT(false, "Do not call me!");
+ }
+
+ void SetProperties(uint32_t aVisitCount, bool aBookmarked)
+ {
+ mVisitCount = aVisitCount;
+ mBookmarked = aBookmarked;
+#ifdef DEBUG
+ mIsInitialized = true;
+#endif
+ }
+
+ uint32_t VisitCount() const
+ {
+#ifdef DEBUG
+ MOZ_ASSERT(mIsInitialized, "PlaceHashKey::mVisitCount not set");
+#endif
+ return mVisitCount;
+ }
+
+ bool IsBookmarked() const
+ {
+#ifdef DEBUG
+ MOZ_ASSERT(mIsInitialized, "PlaceHashKey::mBookmarked not set");
+#endif
+ return mBookmarked;
+ }
+
+ // Array of VisitData objects.
+ nsTArray<VisitData> mVisits;
+private:
+ // Visit count for this place.
+ uint32_t mVisitCount;
+ // Whether this place is bookmarked.
+ bool mBookmarked;
+#ifdef DEBUG
+ // Whether previous attributes are set.
+ bool mIsInitialized;
+#endif
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Anonymous Helpers
+
+namespace {
+
+/**
+ * Convert the given js value to a js array.
+ *
+ * @param [in] aValue
+ * the JS value to convert.
+ * @param [in] aCtx
+ * The JSContext for aValue.
+ * @param [out] _array
+ * the JS array.
+ * @param [out] _arrayLength
+ * _array's length.
+ */
+nsresult
+GetJSArrayFromJSValue(JS::Handle<JS::Value> aValue,
+ JSContext* aCtx,
+ JS::MutableHandle<JSObject*> _array,
+ uint32_t* _arrayLength) {
+ if (aValue.isObjectOrNull()) {
+ JS::Rooted<JSObject*> val(aCtx, aValue.toObjectOrNull());
+ bool isArray;
+ if (!JS_IsArrayObject(aCtx, val, &isArray)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (isArray) {
+ _array.set(val);
+ (void)JS_GetArrayLength(aCtx, _array, _arrayLength);
+ NS_ENSURE_ARG(*_arrayLength > 0);
+ return NS_OK;
+ }
+ }
+
+ // Build a temporary array to store this one item so the code below can
+ // just loop.
+ *_arrayLength = 1;
+ _array.set(JS_NewArrayObject(aCtx, 0));
+ NS_ENSURE_TRUE(_array, NS_ERROR_OUT_OF_MEMORY);
+
+ bool rc = JS_DefineElement(aCtx, _array, 0, aValue, 0);
+ NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
+ return NS_OK;
+}
+
+/**
+ * Attemps to convert a given js value to a nsIURI object.
+ * @param aCtx
+ * The JSContext for aValue.
+ * @param aValue
+ * The JS value to convert.
+ * @return the nsIURI object, or null if aValue is not a nsIURI object.
+ */
+already_AddRefed<nsIURI>
+GetJSValueAsURI(JSContext* aCtx,
+ const JS::Value& aValue) {
+ if (!aValue.isPrimitive()) {
+ nsCOMPtr<nsIXPConnect> xpc = mozilla::services::GetXPConnect();
+
+ nsCOMPtr<nsIXPConnectWrappedNative> wrappedObj;
+ nsresult rv = xpc->GetWrappedNativeOfJSObject(aCtx, aValue.toObjectOrNull(),
+ getter_AddRefs(wrappedObj));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ nsCOMPtr<nsIURI> uri = do_QueryWrappedNative(wrappedObj);
+ return uri.forget();
+ }
+ return nullptr;
+}
+
+/**
+ * Obtains an nsIURI from the "uri" property of a JSObject.
+ *
+ * @param aCtx
+ * The JSContext for aObject.
+ * @param aObject
+ * The JSObject to get the URI from.
+ * @param aProperty
+ * The name of the property to get the URI from.
+ * @return the URI if it exists.
+ */
+already_AddRefed<nsIURI>
+GetURIFromJSObject(JSContext* aCtx,
+ JS::Handle<JSObject *> aObject,
+ const char* aProperty)
+{
+ JS::Rooted<JS::Value> uriVal(aCtx);
+ bool rc = JS_GetProperty(aCtx, aObject, aProperty, &uriVal);
+ NS_ENSURE_TRUE(rc, nullptr);
+ return GetJSValueAsURI(aCtx, uriVal);
+}
+
+/**
+ * Attemps to convert a JS value to a string.
+ * @param aCtx
+ * The JSContext for aObject.
+ * @param aValue
+ * The JS value to convert.
+ * @param _string
+ * The string to populate with the value, or set it to void.
+ */
+void
+GetJSValueAsString(JSContext* aCtx,
+ const JS::Value& aValue,
+ nsString& _string) {
+ if (aValue.isUndefined() ||
+ !(aValue.isNull() || aValue.isString())) {
+ _string.SetIsVoid(true);
+ return;
+ }
+
+ // |null| in JS maps to the empty string.
+ if (aValue.isNull()) {
+ _string.Truncate();
+ return;
+ }
+
+ if (!AssignJSString(aCtx, _string, aValue.toString())) {
+ _string.SetIsVoid(true);
+ }
+}
+
+/**
+ * Obtains the specified property of a JSObject.
+ *
+ * @param aCtx
+ * The JSContext for aObject.
+ * @param aObject
+ * The JSObject to get the string from.
+ * @param aProperty
+ * The property to get the value from.
+ * @param _string
+ * The string to populate with the value, or set it to void.
+ */
+void
+GetStringFromJSObject(JSContext* aCtx,
+ JS::Handle<JSObject *> aObject,
+ const char* aProperty,
+ nsString& _string)
+{
+ JS::Rooted<JS::Value> val(aCtx);
+ bool rc = JS_GetProperty(aCtx, aObject, aProperty, &val);
+ if (!rc) {
+ _string.SetIsVoid(true);
+ return;
+ }
+ else {
+ GetJSValueAsString(aCtx, val, _string);
+ }
+}
+
+/**
+ * Obtains the specified property of a JSObject.
+ *
+ * @param aCtx
+ * The JSContext for aObject.
+ * @param aObject
+ * The JSObject to get the int from.
+ * @param aProperty
+ * The property to get the value from.
+ * @param _int
+ * The integer to populate with the value on success.
+ */
+template <typename IntType>
+nsresult
+GetIntFromJSObject(JSContext* aCtx,
+ JS::Handle<JSObject *> aObject,
+ const char* aProperty,
+ IntType* _int)
+{
+ JS::Rooted<JS::Value> value(aCtx);
+ bool rc = JS_GetProperty(aCtx, aObject, aProperty, &value);
+ NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
+ if (value.isUndefined()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ NS_ENSURE_ARG(value.isPrimitive());
+ NS_ENSURE_ARG(value.isNumber());
+
+ double num;
+ rc = JS::ToNumber(aCtx, value, &num);
+ NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_ARG(IntType(num) == num);
+
+ *_int = IntType(num);
+ return NS_OK;
+}
+
+/**
+ * Obtains the specified property of a JSObject.
+ *
+ * @pre aArray must be an Array object.
+ *
+ * @param aCtx
+ * The JSContext for aArray.
+ * @param aArray
+ * The JSObject to get the object from.
+ * @param aIndex
+ * The index to get the object from.
+ * @param objOut
+ * Set to the JSObject pointer on success.
+ */
+nsresult
+GetJSObjectFromArray(JSContext* aCtx,
+ JS::Handle<JSObject*> aArray,
+ uint32_t aIndex,
+ JS::MutableHandle<JSObject*> objOut)
+{
+ JS::Rooted<JS::Value> value(aCtx);
+ bool rc = JS_GetElement(aCtx, aArray, aIndex, &value);
+ NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_ARG(!value.isPrimitive());
+ objOut.set(&value.toObject());
+ return NS_OK;
+}
+
+class VisitedQuery final : public AsyncStatementCallback,
+ public mozIStorageCompletionCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ static nsresult Start(nsIURI* aURI,
+ mozIVisitedStatusCallback* aCallback=nullptr)
+ {
+ NS_PRECONDITION(aURI, "Null URI");
+
+ // If we are a content process, always remote the request to the
+ // parent process.
+ if (XRE_IsContentProcess()) {
+ URIParams uri;
+ SerializeURI(aURI, uri);
+
+ mozilla::dom::ContentChild* cpc =
+ mozilla::dom::ContentChild::GetSingleton();
+ NS_ASSERTION(cpc, "Content Protocol is NULL!");
+ (void)cpc->SendStartVisitedQuery(uri);
+ return NS_OK;
+ }
+
+ nsMainThreadPtrHandle<mozIVisitedStatusCallback>
+ callback(new nsMainThreadPtrHolder<mozIVisitedStatusCallback>(aCallback));
+
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(navHistory);
+ if (navHistory->hasEmbedVisit(aURI)) {
+ RefPtr<VisitedQuery> cb = new VisitedQuery(aURI, callback, true);
+ NS_ENSURE_TRUE(cb, NS_ERROR_OUT_OF_MEMORY);
+ // As per IHistory contract, we must notify asynchronously.
+ NS_DispatchToMainThread(NewRunnableMethod(cb, &VisitedQuery::NotifyVisitedStatus));
+
+ return NS_OK;
+ }
+
+ History* history = History::GetService();
+ NS_ENSURE_STATE(history);
+ RefPtr<VisitedQuery> cb = new VisitedQuery(aURI, callback);
+ NS_ENSURE_TRUE(cb, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = history->GetIsVisitedStatement(cb);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ // Note: the return value matters here. We call into this method, it's not
+ // just xpcom boilerplate.
+ NS_IMETHOD Complete(nsresult aResult, nsISupports* aStatement) override
+ {
+ NS_ENSURE_SUCCESS(aResult, aResult);
+ nsCOMPtr<mozIStorageAsyncStatement> stmt = do_QueryInterface(aStatement);
+ NS_ENSURE_STATE(stmt);
+ // Bind by index for performance.
+ nsresult rv = URIBinder::Bind(stmt, 0, mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStoragePendingStatement> handle;
+ return stmt->ExecuteAsync(this, getter_AddRefs(handle));
+ }
+
+ NS_IMETHOD HandleResult(mozIStorageResultSet* aResults) override
+ {
+ // If this method is called, we've gotten results, which means we have a
+ // visit.
+ mIsVisited = true;
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleError(mozIStorageError* aError) override
+ {
+ // mIsVisited is already set to false, and that's the assumption we will
+ // make if an error occurred.
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleCompletion(uint16_t aReason) override
+ {
+ if (aReason != mozIStorageStatementCallback::REASON_FINISHED) {
+ return NS_OK;
+ }
+
+ nsresult rv = NotifyVisitedStatus();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ nsresult NotifyVisitedStatus()
+ {
+ // If an external handling callback is provided, just notify through it.
+ if (!!mCallback) {
+ mCallback->IsVisited(mURI, mIsVisited);
+ return NS_OK;
+ }
+
+ if (mIsVisited) {
+ History* history = History::GetService();
+ NS_ENSURE_STATE(history);
+ history->NotifyVisited(mURI);
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ nsAutoString status;
+ if (mIsVisited) {
+ status.AssignLiteral(URI_VISITED);
+ }
+ else {
+ status.AssignLiteral(URI_NOT_VISITED);
+ }
+ (void)observerService->NotifyObservers(mURI,
+ URI_VISITED_RESOLUTION_TOPIC,
+ status.get());
+ }
+
+ return NS_OK;
+ }
+
+private:
+ explicit VisitedQuery(nsIURI* aURI,
+ const nsMainThreadPtrHandle<mozIVisitedStatusCallback>& aCallback,
+ bool aIsVisited=false)
+ : mURI(aURI)
+ , mCallback(aCallback)
+ , mIsVisited(aIsVisited)
+ {
+ }
+
+ ~VisitedQuery()
+ {
+ }
+
+ nsCOMPtr<nsIURI> mURI;
+ nsMainThreadPtrHandle<mozIVisitedStatusCallback> mCallback;
+ bool mIsVisited;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(
+ VisitedQuery
+, AsyncStatementCallback
+, mozIStorageCompletionCallback
+)
+
+/**
+ * Notifies observers about a visit.
+ */
+class NotifyVisitObservers : public Runnable
+{
+public:
+ explicit NotifyVisitObservers(VisitData& aPlace)
+ : mPlace(aPlace)
+ , mHistory(History::GetService())
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ // We are in the main thread, no need to lock.
+ if (mHistory->IsShuttingDown()) {
+ // If we are shutting down, we cannot notify the observers.
+ return NS_OK;
+ }
+
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ if (!navHistory) {
+ NS_WARNING("Trying to notify about a visit but cannot get the history service!");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mPlace.spec));
+ if (!uri) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Notify the visit. Note that TRANSITION_EMBED visits are never added
+ // to the database, thus cannot be queried and we don't notify them.
+ if (mPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) {
+ navHistory->NotifyOnVisit(uri, mPlace.visitId, mPlace.visitTime,
+ mPlace.referrerVisitId, mPlace.transitionType,
+ mPlace.guid, mPlace.hidden,
+ mPlace.visitCount + 1, // Add current visit.
+ static_cast<uint32_t>(mPlace.typed));
+ }
+
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ DebugOnly<nsresult> rv =
+ obsService->NotifyObservers(uri, URI_VISIT_SAVED, nullptr);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Could not notify observers");
+ }
+
+ History* history = History::GetService();
+ NS_ENSURE_STATE(history);
+ history->AppendToRecentlyVisitedURIs(uri);
+ history->NotifyVisited(uri);
+
+ return NS_OK;
+ }
+private:
+ VisitData mPlace;
+ RefPtr<History> mHistory;
+};
+
+/**
+ * Notifies observers about a pages title changing.
+ */
+class NotifyTitleObservers : public Runnable
+{
+public:
+ /**
+ * Notifies observers on the main thread.
+ *
+ * @param aSpec
+ * The spec of the URI to notify about.
+ * @param aTitle
+ * The new title to notify about.
+ */
+ NotifyTitleObservers(const nsCString& aSpec,
+ const nsString& aTitle,
+ const nsCString& aGUID)
+ : mSpec(aSpec)
+ , mTitle(aTitle)
+ , mGUID(aGUID)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mSpec));
+ if (!uri) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ navHistory->NotifyTitleChange(uri, mTitle, mGUID);
+
+ return NS_OK;
+ }
+private:
+ const nsCString mSpec;
+ const nsString mTitle;
+ const nsCString mGUID;
+};
+
+/**
+ * Helper class for methods which notify their callers through the
+ * mozIVisitInfoCallback interface.
+ */
+class NotifyPlaceInfoCallback : public Runnable
+{
+public:
+ NotifyPlaceInfoCallback(const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback,
+ const VisitData& aPlace,
+ bool aIsSingleVisit,
+ nsresult aResult)
+ : mCallback(aCallback)
+ , mPlace(aPlace)
+ , mResult(aResult)
+ , mIsSingleVisit(aIsSingleVisit)
+ {
+ MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ bool hasValidURIs = true;
+ nsCOMPtr<nsIURI> referrerURI;
+ if (!mPlace.referrerSpec.IsEmpty()) {
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(referrerURI), mPlace.referrerSpec));
+ hasValidURIs = !!referrerURI;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mPlace.spec));
+ hasValidURIs = hasValidURIs && !!uri;
+
+ nsCOMPtr<mozIPlaceInfo> place;
+ if (mIsSingleVisit) {
+ nsCOMPtr<mozIVisitInfo> visit =
+ new VisitInfo(mPlace.visitId, mPlace.visitTime, mPlace.transitionType,
+ referrerURI.forget());
+ PlaceInfo::VisitsArray visits;
+ (void)visits.AppendElement(visit);
+
+ // The frecency isn't exposed because it may not reflect the updated value
+ // in the case of InsertVisitedURIs.
+ place =
+ new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
+ -1, visits);
+ }
+ else {
+ // Same as above.
+ place =
+ new PlaceInfo(mPlace.placeId, mPlace.guid, uri.forget(), mPlace.title,
+ -1);
+ }
+
+ if (NS_SUCCEEDED(mResult) && hasValidURIs) {
+ (void)mCallback->HandleResult(place);
+ } else {
+ (void)mCallback->HandleError(mResult, place);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
+ VisitData mPlace;
+ const nsresult mResult;
+ bool mIsSingleVisit;
+};
+
+/**
+ * Notifies a callback object when the operation is complete.
+ */
+class NotifyCompletion : public Runnable
+{
+public:
+ explicit NotifyCompletion(const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback)
+ : mCallback(aCallback)
+ {
+ MOZ_ASSERT(aCallback, "Must pass a non-null callback!");
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (NS_IsMainThread()) {
+ (void)mCallback->HandleCompletion();
+ }
+ else {
+ (void)NS_DispatchToMainThread(this);
+ }
+ return NS_OK;
+ }
+
+private:
+ nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
+};
+
+/**
+ * Checks to see if we can add aURI to history, and dispatches an error to
+ * aCallback (if provided) if we cannot.
+ *
+ * @param aURI
+ * The URI to check.
+ * @param [optional] aGUID
+ * The guid of the URI to check. This is passed back to the callback.
+ * @param [optional] aCallback
+ * The callback to notify if the URI cannot be added to history.
+ * @return true if the URI can be added to history, false otherwise.
+ */
+bool
+CanAddURI(nsIURI* aURI,
+ const nsCString& aGUID = EmptyCString(),
+ mozIVisitInfoCallback* aCallback = nullptr)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(navHistory, false);
+
+ bool canAdd;
+ nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
+ if (NS_SUCCEEDED(rv) && canAdd) {
+ return true;
+ };
+
+ // We cannot add the URI. Notify the callback, if we were given one.
+ if (aCallback) {
+ VisitData place(aURI);
+ place.guid = aGUID;
+ nsMainThreadPtrHandle<mozIVisitInfoCallback>
+ callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(aCallback));
+ nsCOMPtr<nsIRunnable> event =
+ new NotifyPlaceInfoCallback(callback, place, true, NS_ERROR_INVALID_ARG);
+ (void)NS_DispatchToMainThread(event);
+ }
+
+ return false;
+}
+
+/**
+ * Adds a visit to the database.
+ */
+class InsertVisitedURIs final: public Runnable
+{
+public:
+ /**
+ * Adds a visit to the database asynchronously.
+ *
+ * @param aConnection
+ * The database connection to use for these operations.
+ * @param aPlaces
+ * The locations to record visits.
+ * @param [optional] aCallback
+ * The callback to notify about the visit.
+ */
+ static nsresult Start(mozIStorageConnection* aConnection,
+ nsTArray<VisitData>& aPlaces,
+ mozIVisitInfoCallback* aCallback = nullptr)
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+ MOZ_ASSERT(aPlaces.Length() > 0, "Must pass a non-empty array!");
+
+ // Make sure nsNavHistory service is up before proceeding:
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ MOZ_ASSERT(navHistory, "Could not get nsNavHistory?!");
+ if (!navHistory) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsMainThreadPtrHandle<mozIVisitInfoCallback>
+ callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(aCallback));
+ RefPtr<InsertVisitedURIs> event =
+ new InsertVisitedURIs(aConnection, aPlaces, callback);
+
+ // Get the target thread, and then start the work!
+ nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
+ NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+ nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
+
+ // Prevent the main thread from shutting down while this is running.
+ MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
+ if (mHistory->IsShuttingDown()) {
+ // If we were already shutting down, we cannot insert the URIs.
+ return NS_OK;
+ }
+
+ mozStorageTransaction transaction(mDBConn, false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ VisitData* lastFetchedPlace = nullptr;
+ for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
+ VisitData& place = mPlaces.ElementAt(i);
+
+ // Fetching from the database can overwrite this information, so save it
+ // apart.
+ bool typed = place.typed;
+ bool hidden = place.hidden;
+
+ // We can avoid a database lookup if it's the same place as the last
+ // visit we added.
+ bool known = lastFetchedPlace && lastFetchedPlace->spec.Equals(place.spec);
+ if (!known) {
+ nsresult rv = mHistory->FetchPageInfo(place, &known);
+ if (NS_FAILED(rv)) {
+ if (!!mCallback) {
+ nsCOMPtr<nsIRunnable> event =
+ new NotifyPlaceInfoCallback(mCallback, place, true, rv);
+ return NS_DispatchToMainThread(event);
+ }
+ return NS_OK;
+ }
+ lastFetchedPlace = &mPlaces.ElementAt(i);
+ } else {
+ // Copy over the data from the already known place.
+ place.placeId = lastFetchedPlace->placeId;
+ place.guid = lastFetchedPlace->guid;
+ place.lastVisitId = lastFetchedPlace->visitId;
+ place.lastVisitTime = lastFetchedPlace->visitTime;
+ place.titleChanged = !lastFetchedPlace->title.Equals(place.title);
+ place.frecency = lastFetchedPlace->frecency;
+ // Add one visit for the previous loop.
+ place.visitCount = ++(*lastFetchedPlace).visitCount;
+ }
+
+ // If any transition is typed, ensure the page is marked as typed.
+ if (typed != lastFetchedPlace->typed) {
+ place.typed = true;
+ }
+
+ // If any transition is visible, ensure the page is marked as visible.
+ if (hidden != lastFetchedPlace->hidden) {
+ place.hidden = false;
+ }
+
+ // If this is a new page, or the existing page was already visible,
+ // there's no need to try to unhide it.
+ if (!known || !lastFetchedPlace->hidden) {
+ place.shouldUpdateHidden = false;
+ }
+
+ FetchReferrerInfo(place);
+
+ nsresult rv = DoDatabaseInserts(known, place);
+ if (!!mCallback) {
+ nsCOMPtr<nsIRunnable> event =
+ new NotifyPlaceInfoCallback(mCallback, place, true, rv);
+ nsresult rv2 = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(place);
+ rv = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Notify about title change if needed.
+ if ((!known && !place.title.IsVoid()) || place.titleChanged) {
+ event = new NotifyTitleObservers(place.spec, place.title, place.guid);
+ rv = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsresult rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+private:
+ InsertVisitedURIs(mozIStorageConnection* aConnection,
+ nsTArray<VisitData>& aPlaces,
+ const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback)
+ : mDBConn(aConnection)
+ , mCallback(aCallback)
+ , mHistory(History::GetService())
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ mPlaces.SwapElements(aPlaces);
+
+#ifdef DEBUG
+ for (nsTArray<VisitData>::size_type i = 0; i < mPlaces.Length(); i++) {
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ASSERT(NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec)));
+ MOZ_ASSERT(CanAddURI(uri),
+ "Passed a VisitData with a URI we cannot add to history!");
+ }
+#endif
+ }
+
+ /**
+ * Inserts or updates the entry in moz_places for this visit, adds the visit,
+ * and updates the frecency of the place.
+ *
+ * @param aKnown
+ * True if we already have an entry for this place in moz_places, false
+ * otherwise.
+ * @param aPlace
+ * The place we are adding a visit for.
+ */
+ nsresult DoDatabaseInserts(bool aKnown,
+ VisitData& aPlace)
+ {
+ MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
+
+ // If the page was in moz_places, we need to update the entry.
+ nsresult rv;
+ if (aKnown) {
+ rv = mHistory->UpdatePlace(aPlace);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Otherwise, the page was not in moz_places, so now we have to add it.
+ else {
+ rv = mHistory->InsertPlace(aPlace);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aPlace.placeId = nsNavHistory::sLastInsertedPlaceId;
+ }
+ MOZ_ASSERT(aPlace.placeId > 0);
+
+ rv = AddVisit(aPlace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // TODO (bug 623969) we shouldn't update this after each visit, but
+ // rather only for each unique place to save disk I/O.
+
+ // Don't update frecency if the page should not appear in autocomplete.
+ if (aPlace.shouldUpdateFrecency) {
+ rv = UpdateFrecency(aPlace);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+ }
+
+ /**
+ * Fetches information about a referrer for aPlace if it was a recent
+ * visit or not.
+ *
+ * @param aPlace
+ * The VisitData for the visit we will eventually add.
+ *
+ */
+ void FetchReferrerInfo(VisitData& aPlace)
+ {
+ if (aPlace.referrerSpec.IsEmpty()) {
+ return;
+ }
+
+ VisitData referrer;
+ referrer.spec = aPlace.referrerSpec;
+ // If the referrer is the same as the page, we don't need to fetch it.
+ if (aPlace.referrerSpec.Equals(aPlace.spec)) {
+ referrer = aPlace;
+ // The page last visit id is also the referrer visit id.
+ aPlace.referrerVisitId = aPlace.lastVisitId;
+ } else {
+ bool exists = false;
+ if (NS_SUCCEEDED(mHistory->FetchPageInfo(referrer, &exists)) && exists) {
+ // Copy the referrer last visit id.
+ aPlace.referrerVisitId = referrer.lastVisitId;
+ }
+ }
+
+ // Check if the page has effectively been visited recently, otherwise
+ // discard the referrer info.
+ if (!aPlace.referrerVisitId || !referrer.lastVisitTime ||
+ aPlace.visitTime - referrer.lastVisitTime > RECENT_EVENT_THRESHOLD) {
+ // We will not be using the referrer data.
+ aPlace.referrerSpec.Truncate();
+ aPlace.referrerVisitId = 0;
+ }
+ }
+
+ /**
+ * Adds a visit for _place and updates it with the right visit id.
+ *
+ * @param _place
+ * The VisitData for the place we need to know visit information about.
+ */
+ nsresult AddVisit(VisitData& _place)
+ {
+ MOZ_ASSERT(_place.placeId > 0);
+
+ nsresult rv;
+ nsCOMPtr<mozIStorageStatement> stmt;
+ stmt = mHistory->GetStatement(
+ "INSERT INTO moz_historyvisits "
+ "(from_visit, place_id, visit_date, visit_type, session) "
+ "VALUES (:from_visit, :page_id, :visit_date, :visit_type, 0) "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), _place.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("from_visit"),
+ _place.referrerVisitId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("visit_date"),
+ _place.visitTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t transitionType = _place.transitionType;
+ MOZ_ASSERT(transitionType >= nsINavHistoryService::TRANSITION_LINK &&
+ transitionType <= nsINavHistoryService::TRANSITION_RELOAD,
+ "Invalid transition type!");
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("visit_type"),
+ transitionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ _place.visitId = nsNavHistory::sLastInsertedVisitId;
+ MOZ_ASSERT(_place.visitId > 0);
+
+ return NS_OK;
+ }
+
+ /**
+ * Updates the frecency, and possibly the hidden-ness of aPlace.
+ *
+ * @param aPlace
+ * The VisitData for the place we want to update.
+ */
+ nsresult UpdateFrecency(const VisitData& aPlace)
+ {
+ MOZ_ASSERT(aPlace.shouldUpdateFrecency);
+ MOZ_ASSERT(aPlace.placeId > 0);
+
+ nsresult rv;
+ { // First, set our frecency to the proper value.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ stmt = mHistory->GetStatement(
+ "UPDATE moz_places "
+ "SET frecency = NOTIFY_FRECENCY("
+ "CALCULATE_FRECENCY(:page_id), "
+ "url, guid, hidden, last_visit_date"
+ ") "
+ "WHERE id = :page_id"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aPlace.hidden && aPlace.shouldUpdateHidden) {
+ // Mark the page as not hidden if the frecency is now nonzero.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ stmt = mHistory->GetStatement(
+ "UPDATE moz_places "
+ "SET hidden = 0 "
+ "WHERE id = :page_id AND frecency <> 0"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlace.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+ }
+
+ mozIStorageConnection* mDBConn;
+
+ nsTArray<VisitData> mPlaces;
+
+ nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
+
+ /**
+ * Strong reference to the History object because we do not want it to
+ * disappear out from under us.
+ */
+ RefPtr<History> mHistory;
+};
+
+class GetPlaceInfo final : public Runnable {
+public:
+ /**
+ * Get the place info for a given place (by GUID or URI) asynchronously.
+ */
+ static nsresult Start(mozIStorageConnection* aConnection,
+ VisitData& aPlace,
+ mozIVisitInfoCallback* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ nsMainThreadPtrHandle<mozIVisitInfoCallback>
+ callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(aCallback));
+ RefPtr<GetPlaceInfo> event = new GetPlaceInfo(aPlace, callback);
+
+ // Get the target thread, and then start the work!
+ nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
+ NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+ nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
+
+ bool exists;
+ nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists)
+ rv = NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIRunnable> event =
+ new NotifyPlaceInfoCallback(mCallback, mPlace, false, rv);
+
+ rv = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+private:
+ GetPlaceInfo(VisitData& aPlace,
+ const nsMainThreadPtrHandle<mozIVisitInfoCallback>& aCallback)
+ : mPlace(aPlace)
+ , mCallback(aCallback)
+ , mHistory(History::GetService())
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+ }
+
+ VisitData mPlace;
+ nsMainThreadPtrHandle<mozIVisitInfoCallback> mCallback;
+ RefPtr<History> mHistory;
+};
+
+/**
+ * Sets the page title for a page in moz_places (if necessary).
+ */
+class SetPageTitle : public Runnable
+{
+public:
+ /**
+ * Sets a pages title in the database asynchronously.
+ *
+ * @param aConnection
+ * The database connection to use for this operation.
+ * @param aURI
+ * The URI to set the page title on.
+ * @param aTitle
+ * The title to set for the page, if the page exists.
+ */
+ static nsresult Start(mozIStorageConnection* aConnection,
+ nsIURI* aURI,
+ const nsAString& aTitle)
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+ MOZ_ASSERT(aURI, "Must pass a non-null URI object!");
+
+ nsCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<SetPageTitle> event = new SetPageTitle(spec, aTitle);
+
+ // Get the target thread, and then start the work!
+ nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
+ NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+ rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread(), "This should not be called on the main thread");
+
+ // First, see if the page exists in the database (we'll need its id later).
+ bool exists;
+ nsresult rv = mHistory->FetchPageInfo(mPlace, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists || !mPlace.titleChanged) {
+ // We have no record of this page, or we have no title change, so there
+ // is no need to do any further work.
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mPlace.placeId > 0,
+ "We somehow have an invalid place id here!");
+
+ // Now we can update our database record.
+ nsCOMPtr<mozIStorageStatement> stmt =
+ mHistory->GetStatement(
+ "UPDATE moz_places "
+ "SET title = :page_title "
+ "WHERE id = :page_id "
+ );
+ NS_ENSURE_STATE(stmt);
+
+ {
+ mozStorageStatementScoper scoper(stmt);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPlace.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Empty strings should clear the title, just like
+ // nsNavHistory::SetPageTitle.
+ if (mPlace.title.IsEmpty()) {
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_title"));
+ }
+ else {
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("page_title"),
+ StringHead(mPlace.title, TITLE_LENGTH_MAX));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIRunnable> event =
+ new NotifyTitleObservers(mPlace.spec, mPlace.title, mPlace.guid);
+ rv = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+private:
+ SetPageTitle(const nsCString& aSpec,
+ const nsAString& aTitle)
+ : mHistory(History::GetService())
+ {
+ mPlace.spec = aSpec;
+ mPlace.title = aTitle;
+ }
+
+ VisitData mPlace;
+
+ /**
+ * Strong reference to the History object because we do not want it to
+ * disappear out from under us.
+ */
+ RefPtr<History> mHistory;
+};
+
+/**
+ * Adds download-specific annotations to a download page.
+ */
+class SetDownloadAnnotations final : public mozIVisitInfoCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit SetDownloadAnnotations(nsIURI* aDestination)
+ : mDestination(aDestination)
+ , mHistory(History::GetService())
+ {
+ MOZ_ASSERT(mDestination);
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD HandleError(nsresult aResultCode, mozIPlaceInfo *aPlaceInfo) override
+ {
+ // Just don't add the annotations in case the visit isn't added.
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleResult(mozIPlaceInfo *aPlaceInfo) override
+ {
+ // Exit silently if the download destination is not a local file.
+ nsCOMPtr<nsIFileURL> destinationFileURL = do_QueryInterface(mDestination);
+ if (!destinationFileURL) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> source;
+ nsresult rv = aPlaceInfo->GetUri(getter_AddRefs(source));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> destinationFile;
+ rv = destinationFileURL->GetFile(getter_AddRefs(destinationFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString destinationFileName;
+ rv = destinationFile->GetLeafName(destinationFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString destinationURISpec;
+ rv = destinationFileURL->GetSpec(destinationURISpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Use annotations for storing the additional download metadata.
+ nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
+ NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = annosvc->SetPageAnnotationString(
+ source,
+ DESTINATIONFILEURI_ANNO,
+ NS_ConvertUTF8toUTF16(destinationURISpec),
+ 0,
+ nsIAnnotationService::EXPIRE_WITH_HISTORY
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = annosvc->SetPageAnnotationString(
+ source,
+ DESTINATIONFILENAME_ANNO,
+ destinationFileName,
+ 0,
+ nsIAnnotationService::EXPIRE_WITH_HISTORY
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString title;
+ rv = aPlaceInfo->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // In case we are downloading a file that does not correspond to a web
+ // page for which the title is present, we populate the otherwise empty
+ // history title with the name of the destination file, to allow it to be
+ // visible and searchable in history results.
+ if (title.IsEmpty()) {
+ rv = mHistory->SetURITitle(source, destinationFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleCompletion() override
+ {
+ return NS_OK;
+ }
+
+private:
+ ~SetDownloadAnnotations() {}
+
+ nsCOMPtr<nsIURI> mDestination;
+
+ /**
+ * Strong reference to the History object because we do not want it to
+ * disappear out from under us.
+ */
+ RefPtr<History> mHistory;
+};
+NS_IMPL_ISUPPORTS(
+ SetDownloadAnnotations,
+ mozIVisitInfoCallback
+)
+
+/**
+ * Notify removed visits to observers.
+ */
+class NotifyRemoveVisits : public Runnable
+{
+public:
+
+ explicit NotifyRemoveVisits(nsTHashtable<PlaceHashKey>& aPlaces)
+ : mPlaces(VISITS_REMOVAL_INITIAL_HASH_LENGTH)
+ , mHistory(History::GetService())
+ {
+ MOZ_ASSERT(!NS_IsMainThread(),
+ "This should not be called on the main thread");
+ for (auto iter = aPlaces.Iter(); !iter.Done(); iter.Next()) {
+ PlaceHashKey* entry = iter.Get();
+ PlaceHashKey* copy = mPlaces.PutEntry(entry->GetKey());
+ copy->SetProperties(entry->VisitCount(), entry->IsBookmarked());
+ entry->mVisits.SwapElements(copy->mVisits);
+ }
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ // We are in the main thread, no need to lock.
+ if (mHistory->IsShuttingDown()) {
+ // If we are shutting down, we cannot notify the observers.
+ return NS_OK;
+ }
+
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ if (!navHistory) {
+ NS_WARNING("Cannot notify without the history service!");
+ return NS_OK;
+ }
+
+ // Wrap all notifications in a batch, so the view can handle changes in a
+ // more performant way, by initiating a refresh after a limited number of
+ // single changes.
+ (void)navHistory->BeginUpdateBatch();
+ for (auto iter = mPlaces.Iter(); !iter.Done(); iter.Next()) {
+ PlaceHashKey* entry = iter.Get();
+ const nsTArray<VisitData>& visits = entry->mVisits;
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), visits[0].spec));
+ // Notify an expiration only if we have a valid uri, otherwise
+ // the observer couldn't gather any useful data from the notification.
+ // This should be false only if there's a bug in the code preceding us.
+ if (uri) {
+ bool removingPage = visits.Length() == entry->VisitCount() &&
+ !entry->IsBookmarked();
+
+ // FindRemovableVisits only sets the transition type on the VisitData
+ // objects it collects if the visits were filtered by transition type.
+ // RemoveVisitsFilter currently only supports filtering by transition
+ // type, so FindRemovableVisits will either find all visits, or all
+ // visits of a given type. Therefore, if transitionType is set on this
+ // visit, we pass the transition type to NotifyOnPageExpired which in
+ // turns passes it to OnDeleteVisits to indicate that all visits of a
+ // given type were removed.
+ uint32_t transition = visits[0].transitionType < UINT32_MAX
+ ? visits[0].transitionType
+ : 0;
+ navHistory->NotifyOnPageExpired(uri, visits[0].visitTime, removingPage,
+ visits[0].guid,
+ nsINavHistoryObserver::REASON_DELETED,
+ transition);
+ }
+ }
+ (void)navHistory->EndUpdateBatch();
+
+ return NS_OK;
+ }
+
+private:
+ nsTHashtable<PlaceHashKey> mPlaces;
+
+ /**
+ * Strong reference to the History object because we do not want it to
+ * disappear out from under us.
+ */
+ RefPtr<History> mHistory;
+};
+
+/**
+ * Remove visits from history.
+ */
+class RemoveVisits : public Runnable
+{
+public:
+ /**
+ * Asynchronously removes visits from history.
+ *
+ * @param aConnection
+ * The database connection to use for these operations.
+ * @param aFilter
+ * Filter to remove visits.
+ */
+ static nsresult Start(mozIStorageConnection* aConnection,
+ RemoveVisitsFilter& aFilter)
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ RefPtr<RemoveVisits> event = new RemoveVisits(aConnection, aFilter);
+
+ // Get the target thread, and then start the work!
+ nsCOMPtr<nsIEventTarget> target = do_GetInterface(aConnection);
+ NS_ENSURE_TRUE(target, NS_ERROR_UNEXPECTED);
+ nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread(),
+ "This should not be called on the main thread");
+
+ // Prevent the main thread from shutting down while this is running.
+ MutexAutoLock lockedScope(mHistory->GetShutdownMutex());
+ if (mHistory->IsShuttingDown()) {
+ // If we were already shutting down, we cannot remove the visits.
+ return NS_OK;
+ }
+
+ // Find all the visits relative to the current filters and whether their
+ // pages will be removed or not.
+ nsTHashtable<PlaceHashKey> places(VISITS_REMOVAL_INITIAL_HASH_LENGTH);
+ nsresult rv = FindRemovableVisits(places);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (places.Count() == 0)
+ return NS_OK;
+
+ mozStorageTransaction transaction(mDBConn, false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ rv = RemoveVisitsFromDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = RemovePagesFromDatabase(places);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> event = new NotifyRemoveVisits(places);
+ rv = NS_DispatchToMainThread(event);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+private:
+ RemoveVisits(mozIStorageConnection* aConnection,
+ RemoveVisitsFilter& aFilter)
+ : mDBConn(aConnection)
+ , mHasTransitionType(false)
+ , mHistory(History::GetService())
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ // Build query conditions.
+ nsTArray<nsCString> conditions;
+ // TODO: add support for binding params when adding further stuff here.
+ if (aFilter.transitionType < UINT32_MAX) {
+ conditions.AppendElement(nsPrintfCString("visit_type = %d", aFilter.transitionType));
+ mHasTransitionType = true;
+ }
+ if (conditions.Length() > 0) {
+ mWhereClause.AppendLiteral (" WHERE ");
+ for (uint32_t i = 0; i < conditions.Length(); ++i) {
+ if (i > 0)
+ mWhereClause.AppendLiteral(" AND ");
+ mWhereClause.Append(conditions[i]);
+ }
+ }
+ }
+
+ /**
+ * Find the list of entries that may be removed from `moz_places`.
+ *
+ * Calling this method makes sense only if we are not clearing the entire history.
+ */
+ nsresult
+ FindRemovableVisits(nsTHashtable<PlaceHashKey>& aPlaces)
+ {
+ MOZ_ASSERT(!NS_IsMainThread(),
+ "This should not be called on the main thread");
+
+ nsCString query("SELECT h.id, url, guid, visit_date, visit_type, "
+ "(SELECT count(*) FROM moz_historyvisits WHERE place_id = h.id) as full_visit_count, "
+ "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) as bookmarked "
+ "FROM moz_historyvisits "
+ "JOIN moz_places h ON place_id = h.id");
+ query.Append(mWhereClause);
+
+ nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ bool hasResult;
+ nsresult rv;
+ while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
+ VisitData visit;
+ rv = stmt->GetInt64(0, &visit.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetUTF8String(1, visit.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetUTF8String(2, visit.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(3, &visit.visitTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mHasTransitionType) {
+ int32_t transition;
+ rv = stmt->GetInt32(4, &transition);
+ NS_ENSURE_SUCCESS(rv, rv);
+ visit.transitionType = static_cast<uint32_t>(transition);
+ }
+ int32_t visitCount, bookmarked;
+ rv = stmt->GetInt32(5, &visitCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt32(6, &bookmarked);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PlaceHashKey* entry = aPlaces.GetEntry(visit.spec);
+ if (!entry) {
+ entry = aPlaces.PutEntry(visit.spec);
+ }
+ entry->SetProperties(static_cast<uint32_t>(visitCount), static_cast<bool>(bookmarked));
+ entry->mVisits.AppendElement(visit);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ nsresult
+ RemoveVisitsFromDatabase()
+ {
+ MOZ_ASSERT(!NS_IsMainThread(),
+ "This should not be called on the main thread");
+
+ nsCString query("DELETE FROM moz_historyvisits");
+ query.Append(mWhereClause);
+
+ nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ nsresult rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ nsresult
+ RemovePagesFromDatabase(nsTHashtable<PlaceHashKey>& aPlaces)
+ {
+ MOZ_ASSERT(!NS_IsMainThread(),
+ "This should not be called on the main thread");
+
+ nsCString placeIdsToRemove;
+ for (auto iter = aPlaces.Iter(); !iter.Done(); iter.Next()) {
+ PlaceHashKey* entry = iter.Get();
+ const nsTArray<VisitData>& visits = entry->mVisits;
+ // Only orphan ids should be listed.
+ if (visits.Length() == entry->VisitCount() && !entry->IsBookmarked()) {
+ if (!placeIdsToRemove.IsEmpty())
+ placeIdsToRemove.Append(',');
+ placeIdsToRemove.AppendInt(visits[0].placeId);
+ }
+ }
+
+#ifdef DEBUG
+ {
+ // Ensure that we are not removing any problematic entry.
+ nsCString query("SELECT id FROM moz_places h WHERE id IN (");
+ query.Append(placeIdsToRemove);
+ query.AppendLiteral(") AND ("
+ "EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) OR "
+ "EXISTS(SELECT 1 FROM moz_historyvisits WHERE place_id = h.id) OR "
+ "SUBSTR(h.url, 1, 6) = 'place:' "
+ ")");
+ nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ bool hasResult;
+ MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && !hasResult,
+ "Trying to remove a non-oprhan place from the database");
+ }
+#endif
+
+ {
+ nsCString query("DELETE FROM moz_places "
+ "WHERE id IN (");
+ query.Append(placeIdsToRemove);
+ query.Append(')');
+
+ nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ nsresult rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ {
+ // Hosts accumulated during the places delete are updated through a trigger
+ // (see nsPlacesTriggers.h).
+ nsAutoCString query("DELETE FROM moz_updatehosts_temp");
+ nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ nsresult rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+ }
+
+ mozIStorageConnection* mDBConn;
+ bool mHasTransitionType;
+ nsCString mWhereClause;
+
+ /**
+ * Strong reference to the History object because we do not want it to
+ * disappear out from under us.
+ */
+ RefPtr<History> mHistory;
+};
+
+/**
+ * Stores an embed visit, and notifies observers.
+ *
+ * @param aPlace
+ * The VisitData of the visit to store as an embed visit.
+ * @param [optional] aCallback
+ * The mozIVisitInfoCallback to notify, if provided.
+ */
+void
+StoreAndNotifyEmbedVisit(VisitData& aPlace,
+ mozIVisitInfoCallback* aCallback = nullptr)
+{
+ MOZ_ASSERT(aPlace.transitionType == nsINavHistoryService::TRANSITION_EMBED,
+ "Must only pass TRANSITION_EMBED visits to this!");
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aPlace.spec));
+
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ if (!navHistory || !uri) {
+ return;
+ }
+
+ navHistory->registerEmbedVisit(uri, aPlace.visitTime);
+
+ if (!!aCallback) {
+ nsMainThreadPtrHandle<mozIVisitInfoCallback>
+ callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(aCallback));
+ nsCOMPtr<nsIRunnable> event =
+ new NotifyPlaceInfoCallback(callback, aPlace, true, NS_OK);
+ (void)NS_DispatchToMainThread(event);
+ }
+
+ nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace);
+ (void)NS_DispatchToMainThread(event);
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// History
+
+History* History::gService = nullptr;
+
+History::History()
+ : mShuttingDown(false)
+ , mShutdownMutex("History::mShutdownMutex")
+ , mObservers(VISIT_OBSERVERS_INITIAL_CACHE_LENGTH)
+ , mRecentlyVisitedURIs(RECENTLY_VISITED_URIS_SIZE)
+{
+ NS_ASSERTION(!gService, "Ruh-roh! This service has already been created!");
+ gService = this;
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ NS_WARNING_ASSERTION(os, "Observer service was not found!");
+ if (os) {
+ (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, false);
+ }
+}
+
+History::~History()
+{
+ UnregisterWeakMemoryReporter(this);
+
+ gService = nullptr;
+
+ NS_ASSERTION(mObservers.Count() == 0,
+ "Not all Links were removed before we disappear!");
+}
+
+void
+History::InitMemoryReporter()
+{
+ RegisterWeakMemoryReporter(this);
+}
+
+NS_IMETHODIMP
+History::NotifyVisited(nsIURI* aURI)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aURI);
+
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (XRE_IsParentProcess()) {
+ nsTArray<ContentParent*> cplist;
+ ContentParent::GetAll(cplist);
+
+ if (!cplist.IsEmpty()) {
+ URIParams uri;
+ SerializeURI(aURI, uri);
+ for (uint32_t i = 0; i < cplist.Length(); ++i) {
+ Unused << cplist[i]->SendNotifyVisited(uri);
+ }
+ }
+ }
+
+ // If we have no observers for this URI, we have nothing to notify about.
+ KeyClass* key = mObservers.GetEntry(aURI);
+ if (!key) {
+ return NS_OK;
+ }
+
+ // Update status of each Link node.
+ {
+ // RemoveEntry will destroy the array, this iterator should not survive it.
+ ObserverArray::ForwardIterator iter(key->array);
+ while (iter.HasMore()) {
+ Link* link = iter.GetNext();
+ link->SetLinkState(eLinkState_Visited);
+ // Verify that the observers hash doesn't mutate while looping through
+ // the links associated with this URI.
+ MOZ_ASSERT(key == mObservers.GetEntry(aURI),
+ "The URIs hash mutated!");
+ }
+ }
+
+ // All the registered nodes can now be removed for this URI.
+ mObservers.RemoveEntry(key);
+ return NS_OK;
+}
+
+class ConcurrentStatementsHolder final : public mozIStorageCompletionCallback {
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit ConcurrentStatementsHolder(mozIStorageConnection* aDBConn)
+ {
+ DebugOnly<nsresult> rv = aDBConn->AsyncClone(true, this);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ NS_IMETHOD Complete(nsresult aStatus, nsISupports* aConnection) override {
+ if (NS_FAILED(aStatus))
+ return NS_OK;
+ mReadOnlyDBConn = do_QueryInterface(aConnection);
+
+ // Now we can create our cached statements.
+
+ if (!mIsVisitedStatement) {
+ (void)mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "SELECT 1 FROM moz_places h "
+ "WHERE url_hash = hash(?1) AND url = ?1 AND last_visit_date NOTNULL "
+ ), getter_AddRefs(mIsVisitedStatement));
+ MOZ_ASSERT(mIsVisitedStatement);
+ nsresult result = mIsVisitedStatement ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+ for (int32_t i = 0; i < mIsVisitedCallbacks.Count(); ++i) {
+ DebugOnly<nsresult> rv;
+ rv = mIsVisitedCallbacks[i]->Complete(result, mIsVisitedStatement);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ mIsVisitedCallbacks.Clear();
+ }
+
+ return NS_OK;
+ }
+
+ void GetIsVisitedStatement(mozIStorageCompletionCallback* aCallback)
+ {
+ if (mIsVisitedStatement) {
+ DebugOnly<nsresult> rv;
+ rv = aCallback->Complete(NS_OK, mIsVisitedStatement);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ } else {
+ DebugOnly<bool> added = mIsVisitedCallbacks.AppendObject(aCallback);
+ MOZ_ASSERT(added);
+ }
+ }
+
+ void Shutdown() {
+ if (mReadOnlyDBConn) {
+ mIsVisitedCallbacks.Clear();
+ DebugOnly<nsresult> rv;
+ if (mIsVisitedStatement) {
+ rv = mIsVisitedStatement->Finalize();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ rv = mReadOnlyDBConn->AsyncClose(nullptr);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+private:
+ ~ConcurrentStatementsHolder()
+ {
+ }
+
+ nsCOMPtr<mozIStorageAsyncConnection> mReadOnlyDBConn;
+ nsCOMPtr<mozIStorageAsyncStatement> mIsVisitedStatement;
+ nsCOMArray<mozIStorageCompletionCallback> mIsVisitedCallbacks;
+};
+
+NS_IMPL_ISUPPORTS(
+ ConcurrentStatementsHolder
+, mozIStorageCompletionCallback
+)
+
+nsresult
+History::GetIsVisitedStatement(mozIStorageCompletionCallback* aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mShuttingDown)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (!mConcurrentStatementsHolder) {
+ mozIStorageConnection* dbConn = GetDBConn();
+ NS_ENSURE_STATE(dbConn);
+ mConcurrentStatementsHolder = new ConcurrentStatementsHolder(dbConn);
+ }
+ mConcurrentStatementsHolder->GetIsVisitedStatement(aCallback);
+ return NS_OK;
+}
+
+nsresult
+History::InsertPlace(VisitData& aPlace)
+{
+ MOZ_ASSERT(aPlace.placeId == 0, "should not have a valid place id!");
+ MOZ_ASSERT(!aPlace.shouldUpdateHidden, "We should not need to update hidden");
+ MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
+
+ nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
+ "INSERT INTO moz_places "
+ "(url, url_hash, title, rev_host, hidden, typed, frecency, guid) "
+ "VALUES (:url, hash(:url), :title, :rev_host, :hidden, :typed, :frecency, :guid) "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"),
+ aPlace.revHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString title = aPlace.title;
+ // Empty strings should have no title, just like nsNavHistory::SetPageTitle.
+ if (title.IsEmpty()) {
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
+ }
+ else {
+ title.Assign(StringHead(aPlace.title, TITLE_LENGTH_MAX));
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"), title);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // When inserting a page for a first visit that should not appear in
+ // autocomplete, for example an error page, use a zero frecency.
+ int32_t frecency = aPlace.shouldUpdateFrecency ? aPlace.frecency : 0;
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"), frecency);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aPlace.guid.IsVoid()) {
+ rv = GenerateGUID(aPlace.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Post an onFrecencyChanged observer notification.
+ const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
+ NS_ENSURE_STATE(navHistory);
+ navHistory->DispatchFrecencyChangedNotification(aPlace.spec, frecency,
+ aPlace.guid,
+ aPlace.hidden,
+ aPlace.visitTime);
+
+ return NS_OK;
+}
+
+nsresult
+History::UpdatePlace(const VisitData& aPlace)
+{
+ MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
+ MOZ_ASSERT(aPlace.placeId > 0, "must have a valid place id!");
+ MOZ_ASSERT(!aPlace.guid.IsVoid(), "must have a guid!");
+
+ nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
+ "UPDATE moz_places "
+ "SET title = :title, "
+ "hidden = :hidden, "
+ "typed = :typed, "
+ "guid = :guid "
+ "WHERE id = :page_id "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv;
+ // Empty strings should clear the title, just like nsNavHistory::SetPageTitle.
+ if (aPlace.title.IsEmpty()) {
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("title"));
+ }
+ else {
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("title"),
+ StringHead(aPlace.title, TITLE_LENGTH_MAX));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("typed"), aPlace.typed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), aPlace.hidden);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aPlace.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
+ aPlace.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+History::FetchPageInfo(VisitData& _place, bool* _exists)
+{
+ MOZ_ASSERT(!_place.spec.IsEmpty() || !_place.guid.IsEmpty(), "must have either a non-empty spec or guid!");
+ MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
+
+ nsresult rv;
+
+ // URI takes precedence.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ bool selectByURI = !_place.spec.IsEmpty();
+ if (selectByURI) {
+ stmt = GetStatement(
+ "SELECT guid, id, title, hidden, typed, frecency, visit_count, last_visit_date, "
+ "(SELECT id FROM moz_historyvisits "
+ "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS last_visit_id "
+ "FROM moz_places h "
+ "WHERE url_hash = hash(:page_url) AND url = :page_url "
+ );
+ NS_ENSURE_STATE(stmt);
+
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ stmt = GetStatement(
+ "SELECT url, id, title, hidden, typed, frecency, visit_count, last_visit_date, "
+ "(SELECT id FROM moz_historyvisits "
+ "WHERE place_id = h.id AND visit_date = h.last_visit_date) AS last_visit_id "
+ "FROM moz_places h "
+ "WHERE guid = :guid "
+ );
+ NS_ENSURE_STATE(stmt);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _place.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->ExecuteStep(_exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!*_exists) {
+ return NS_OK;
+ }
+
+ if (selectByURI) {
+ if (_place.guid.IsEmpty()) {
+ rv = stmt->GetUTF8String(0, _place.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ else {
+ nsAutoCString spec;
+ rv = stmt->GetUTF8String(0, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ _place.spec = spec;
+ }
+
+ rv = stmt->GetInt64(1, &_place.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString title;
+ rv = stmt->GetString(2, title);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the title we were given was void, that means we did not bother to set
+ // it to anything. As a result, ignore the fact that we may have changed the
+ // title (because we don't want to, that would be empty), and set the title
+ // to what is currently stored in the datbase.
+ if (_place.title.IsVoid()) {
+ _place.title = title;
+ }
+ // Otherwise, just indicate if the title has changed.
+ else {
+ _place.titleChanged = !(_place.title.Equals(title) ||
+ (_place.title.IsEmpty() && title.IsVoid()));
+ }
+
+ int32_t hidden;
+ rv = stmt->GetInt32(3, &hidden);
+ NS_ENSURE_SUCCESS(rv, rv);
+ _place.hidden = !!hidden;
+
+ int32_t typed;
+ rv = stmt->GetInt32(4, &typed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ _place.typed = !!typed;
+
+ rv = stmt->GetInt32(5, &_place.frecency);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t visitCount;
+ rv = stmt->GetInt32(6, &visitCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ _place.visitCount = visitCount;
+ rv = stmt->GetInt64(7, &_place.lastVisitTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(8, &_place.lastVisitId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(HistoryMallocSizeOf)
+
+NS_IMETHODIMP
+History::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ MOZ_COLLECT_REPORT(
+ "explicit/history-links-hashtable", KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(HistoryMallocSizeOf),
+ "Memory used by the hashtable that records changes to the visited state "
+ "of links.");
+
+ return NS_OK;
+}
+
+size_t
+History::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOfThis)
+{
+ return aMallocSizeOfThis(this) +
+ mObservers.SizeOfExcludingThis(aMallocSizeOfThis);
+}
+
+/* static */
+History*
+History::GetService()
+{
+ if (gService) {
+ return gService;
+ }
+
+ nsCOMPtr<IHistory> service(do_GetService(NS_IHISTORY_CONTRACTID));
+ MOZ_ASSERT(service, "Cannot obtain IHistory service!");
+ NS_ASSERTION(gService, "Our constructor was not run?!");
+
+ return gService;
+}
+
+/* static */
+History*
+History::GetSingleton()
+{
+ if (!gService) {
+ gService = new History();
+ NS_ENSURE_TRUE(gService, nullptr);
+ gService->InitMemoryReporter();
+ }
+
+ NS_ADDREF(gService);
+ return gService;
+}
+
+mozIStorageConnection*
+History::GetDBConn()
+{
+ if (mShuttingDown)
+ return nullptr;
+ if (!mDB) {
+ mDB = Database::GetDatabase();
+ NS_ENSURE_TRUE(mDB, nullptr);
+ }
+ return mDB->MainConn();
+}
+
+void
+History::Shutdown()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Prevent other threads from scheduling uses of the DB while we mark
+ // ourselves as shutting down.
+ MutexAutoLock lockedScope(mShutdownMutex);
+ MOZ_ASSERT(!mShuttingDown && "Shutdown was called more than once!");
+
+ mShuttingDown = true;
+
+ if (mConcurrentStatementsHolder) {
+ mConcurrentStatementsHolder->Shutdown();
+ }
+}
+
+void
+History::AppendToRecentlyVisitedURIs(nsIURI* aURI) {
+ // Add a new entry, if necessary.
+ RecentURIKey* entry = mRecentlyVisitedURIs.GetEntry(aURI);
+ if (!entry) {
+ entry = mRecentlyVisitedURIs.PutEntry(aURI);
+ }
+ if (entry) {
+ entry->time = PR_Now();
+ }
+
+ // Remove entries older than RECENTLY_VISITED_URIS_MAX_AGE.
+ for (auto iter = mRecentlyVisitedURIs.Iter(); !iter.Done(); iter.Next()) {
+ RecentURIKey* entry = iter.Get();
+ if ((PR_Now() - entry->time) > RECENTLY_VISITED_URIS_MAX_AGE) {
+ iter.Remove();
+ }
+ }
+}
+
+inline bool
+History::IsRecentlyVisitedURI(nsIURI* aURI) {
+ RecentURIKey* entry = mRecentlyVisitedURIs.GetEntry(aURI);
+ // Check if the entry exists and is younger than RECENTLY_VISITED_URIS_MAX_AGE.
+ return entry && (PR_Now() - entry->time) < RECENTLY_VISITED_URIS_MAX_AGE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// IHistory
+
+NS_IMETHODIMP
+History::VisitURI(nsIURI* aURI,
+ nsIURI* aLastVisitedURI,
+ uint32_t aFlags)
+{
+ NS_ENSURE_ARG(aURI);
+
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+
+ if (XRE_IsContentProcess()) {
+ URIParams uri;
+ SerializeURI(aURI, uri);
+
+ OptionalURIParams lastVisitedURI;
+ SerializeURI(aLastVisitedURI, lastVisitedURI);
+
+ mozilla::dom::ContentChild* cpc =
+ mozilla::dom::ContentChild::GetSingleton();
+ NS_ASSERTION(cpc, "Content Protocol is NULL!");
+ (void)cpc->SendVisitURI(uri, lastVisitedURI, aFlags);
+ return NS_OK;
+ }
+
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
+
+ // Silently return if URI is something we shouldn't add to DB.
+ bool canAdd;
+ nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!canAdd) {
+ return NS_OK;
+ }
+
+ // Do not save a reloaded uri if we have visited the same URI recently.
+ bool reload = false;
+ if (aLastVisitedURI) {
+ rv = aURI->Equals(aLastVisitedURI, &reload);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (reload && IsRecentlyVisitedURI(aURI)) {
+ // Regardless we must update the stored visit time.
+ AppendToRecentlyVisitedURIs(aURI);
+ return NS_OK;
+ }
+ }
+
+ nsTArray<VisitData> placeArray(1);
+ NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aURI, aLastVisitedURI)),
+ NS_ERROR_OUT_OF_MEMORY);
+ VisitData& place = placeArray.ElementAt(0);
+ NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
+
+ place.visitTime = PR_Now();
+
+ // Assigns a type to the edge in the visit linked list. Each type will be
+ // considered differently when weighting the frecency of a location.
+ uint32_t recentFlags = navHistory->GetRecentFlags(aURI);
+ bool isFollowedLink = recentFlags & nsNavHistory::RECENT_ACTIVATED;
+
+ // Embed visits should never be added to the database, and the same is valid
+ // for redirects across frames.
+ // For the above reasoning non-toplevel transitions are handled at first.
+ // if the visit is toplevel or a non-toplevel followed link, then it can be
+ // handled as usual and stored on disk.
+
+ uint32_t transitionType = nsINavHistoryService::TRANSITION_LINK;
+
+ if (!(aFlags & IHistory::TOP_LEVEL) && !isFollowedLink) {
+ // A frame redirected to a new site without user interaction.
+ transitionType = nsINavHistoryService::TRANSITION_EMBED;
+ }
+ else if (aFlags & IHistory::REDIRECT_TEMPORARY) {
+ transitionType = nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY;
+ }
+ else if (aFlags & IHistory::REDIRECT_PERMANENT) {
+ transitionType = nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT;
+ }
+ else if (reload) {
+ transitionType = nsINavHistoryService::TRANSITION_RELOAD;
+ }
+ else if ((recentFlags & nsNavHistory::RECENT_TYPED) &&
+ !(aFlags & IHistory::UNRECOVERABLE_ERROR)) {
+ // Don't mark error pages as typed, even if they were actually typed by
+ // the user. This is useful to limit their score in autocomplete.
+ transitionType = nsINavHistoryService::TRANSITION_TYPED;
+ }
+ else if (recentFlags & nsNavHistory::RECENT_BOOKMARKED) {
+ transitionType = nsINavHistoryService::TRANSITION_BOOKMARK;
+ }
+ else if (!(aFlags & IHistory::TOP_LEVEL) && isFollowedLink) {
+ // User activated a link in a frame.
+ transitionType = nsINavHistoryService::TRANSITION_FRAMED_LINK;
+ }
+
+ place.SetTransitionType(transitionType);
+ place.hidden = GetHiddenState(aFlags & IHistory::REDIRECT_SOURCE,
+ transitionType);
+
+ // Error pages should never be autocompleted.
+ if (aFlags & IHistory::UNRECOVERABLE_ERROR) {
+ place.shouldUpdateFrecency = false;
+ }
+
+ // EMBED visits are session-persistent and should not go through the database.
+ // They exist only to keep track of isVisited status during the session.
+ if (place.transitionType == nsINavHistoryService::TRANSITION_EMBED) {
+ StoreAndNotifyEmbedVisit(place);
+ }
+ else {
+ mozIStorageConnection* dbConn = GetDBConn();
+ NS_ENSURE_STATE(dbConn);
+
+ rv = InsertVisitedURIs::Start(dbConn, placeArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Finally, notify that we've been visited.
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(aURI, NS_LINK_VISITED_EVENT_TOPIC, nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+History::RegisterVisitedCallback(nsIURI* aURI,
+ Link* aLink)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ASSERTION(aURI, "Must pass a non-null URI!");
+ if (XRE_IsContentProcess()) {
+ NS_PRECONDITION(aLink, "Must pass a non-null Link!");
+ }
+
+ // Obtain our array of observers for this URI.
+#ifdef DEBUG
+ bool keyAlreadyExists = !!mObservers.GetEntry(aURI);
+#endif
+ KeyClass* key = mObservers.PutEntry(aURI);
+ NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
+ ObserverArray& observers = key->array;
+
+ if (observers.IsEmpty()) {
+ NS_ASSERTION(!keyAlreadyExists,
+ "An empty key was kept around in our hashtable!");
+
+ // We are the first Link node to ask about this URI, or there are no pending
+ // Links wanting to know about this URI. Therefore, we should query the
+ // database now.
+ nsresult rv = VisitedQuery::Start(aURI);
+
+ // In IPC builds, we are passed a nullptr Link from
+ // ContentParent::RecvStartVisitedQuery. Since we won't be adding a
+ // nullptr entry to our list of observers, and the code after this point
+ // assumes that aLink is non-nullptr, we will need to return now.
+ if (NS_FAILED(rv) || !aLink) {
+ // Remove our array from the hashtable so we don't keep it around.
+ // In some case calling RemoveEntry on the key obtained by PutEntry
+ // crashes for currently unknown reasons. Our suspect is that something
+ // between PutEntry and this call causes a nested loop that either removes
+ // the entry or reallocs the hash.
+ // TODO (Bug 1412647): we must figure the root cause for these issues and
+ // remove this stop-gap crash fix.
+ key = mObservers.GetEntry(aURI);
+ if (key) {
+ mObservers.RemoveEntry(key);
+ }
+ return rv;
+ }
+ }
+ // In IPC builds, we are passed a nullptr Link from
+ // ContentParent::RecvStartVisitedQuery. All of our code after this point
+ // assumes aLink is non-nullptr, so we have to return now.
+ else if (!aLink) {
+ NS_ASSERTION(XRE_IsParentProcess(),
+ "We should only ever get a null Link in the default process!");
+ return NS_OK;
+ }
+
+ // Sanity check that Links are not registered more than once for a given URI.
+ // This will not catch a case where it is registered for two different URIs.
+ NS_ASSERTION(!observers.Contains(aLink),
+ "Already tracking this Link object!");
+
+ // Start tracking our Link.
+ if (!observers.AppendElement(aLink)) {
+ // Curses - unregister and return failure.
+ (void)UnregisterVisitedCallback(aURI, aLink);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+History::UnregisterVisitedCallback(nsIURI* aURI,
+ Link* aLink)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // TODO: aURI is sometimes null - see bug 548685
+ NS_ASSERTION(aURI, "Must pass a non-null URI!");
+ NS_ASSERTION(aLink, "Must pass a non-null Link object!");
+
+ // Get the array, and remove the item from it.
+ KeyClass* key = mObservers.GetEntry(aURI);
+ if (!key) {
+ NS_ERROR("Trying to unregister for a URI that wasn't registered!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ ObserverArray& observers = key->array;
+ if (!observers.RemoveElement(aLink)) {
+ NS_ERROR("Trying to unregister a node that wasn't registered!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // If the array is now empty, we should remove it from the hashtable.
+ if (observers.IsEmpty()) {
+ mObservers.RemoveEntry(aURI);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+History::SetURITitle(nsIURI* aURI, const nsAString& aTitle)
+{
+ NS_ENSURE_ARG(aURI);
+
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+
+ if (XRE_IsContentProcess()) {
+ URIParams uri;
+ SerializeURI(aURI, uri);
+
+ mozilla::dom::ContentChild * cpc =
+ mozilla::dom::ContentChild::GetSingleton();
+ NS_ASSERTION(cpc, "Content Protocol is NULL!");
+ (void)cpc->SendSetURITitle(uri, PromiseFlatString(aTitle));
+ return NS_OK;
+ }
+
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+
+ // At first, it seems like nav history should always be available here, no
+ // matter what.
+ //
+ // nsNavHistory fails to register as a service if there is no profile in
+ // place (for instance, if user is choosing a profile).
+ //
+ // Maybe the correct thing to do is to not register this service if no
+ // profile has been selected?
+ //
+ NS_ENSURE_TRUE(navHistory, NS_ERROR_FAILURE);
+
+ bool canAdd;
+ nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!canAdd) {
+ return NS_OK;
+ }
+
+ // Embed visits don't have a database entry, thus don't set a title on them.
+ if (navHistory->hasEmbedVisit(aURI)) {
+ return NS_OK;
+ }
+
+ mozIStorageConnection* dbConn = GetDBConn();
+ NS_ENSURE_STATE(dbConn);
+
+ rv = SetPageTitle::Start(dbConn, aURI, aTitle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIDownloadHistory
+
+NS_IMETHODIMP
+History::AddDownload(nsIURI* aSource, nsIURI* aReferrer,
+ PRTime aStartTime, nsIURI* aDestination)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aSource);
+
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+
+ if (XRE_IsContentProcess()) {
+ NS_ERROR("Cannot add downloads to history from content process!");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
+
+ // Silently return if URI is something we shouldn't add to DB.
+ bool canAdd;
+ nsresult rv = navHistory->CanAddURI(aSource, &canAdd);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!canAdd) {
+ return NS_OK;
+ }
+
+ nsTArray<VisitData> placeArray(1);
+ NS_ENSURE_TRUE(placeArray.AppendElement(VisitData(aSource, aReferrer)),
+ NS_ERROR_OUT_OF_MEMORY);
+ VisitData& place = placeArray.ElementAt(0);
+ NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
+
+ place.visitTime = aStartTime;
+ place.SetTransitionType(nsINavHistoryService::TRANSITION_DOWNLOAD);
+ place.hidden = false;
+
+ mozIStorageConnection* dbConn = GetDBConn();
+ NS_ENSURE_STATE(dbConn);
+
+ nsMainThreadPtrHandle<mozIVisitInfoCallback> callback;
+ if (aDestination) {
+ callback = new nsMainThreadPtrHolder<mozIVisitInfoCallback>(new SetDownloadAnnotations(aDestination));
+ }
+
+ rv = InsertVisitedURIs::Start(dbConn, placeArray, callback);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally, notify that we've been visited.
+ nsCOMPtr<nsIObserverService> obsService =
+ mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(aSource, NS_LINK_VISITED_EVENT_TOPIC, nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+History::RemoveAllDownloads()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mShuttingDown) {
+ return NS_OK;
+ }
+
+ if (XRE_IsContentProcess()) {
+ NS_ERROR("Cannot remove downloads to history from content process!");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Ensure navHistory is initialized.
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
+ mozIStorageConnection* dbConn = GetDBConn();
+ NS_ENSURE_STATE(dbConn);
+
+ RemoveVisitsFilter filter;
+ filter.transitionType = nsINavHistoryService::TRANSITION_DOWNLOAD;
+
+ nsresult rv = RemoveVisits::Start(dbConn, filter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIAsyncHistory
+
+NS_IMETHODIMP
+History::GetPlacesInfo(JS::Handle<JS::Value> aPlaceIdentifiers,
+ mozIVisitInfoCallback* aCallback,
+ JSContext* aCtx)
+{
+ // Make sure nsNavHistory service is up before proceeding:
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ MOZ_ASSERT(navHistory, "Could not get nsNavHistory?!");
+ if (!navHistory) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t placesIndentifiersLength;
+ JS::Rooted<JSObject*> placesIndentifiers(aCtx);
+ nsresult rv = GetJSArrayFromJSValue(aPlaceIdentifiers, aCtx,
+ &placesIndentifiers,
+ &placesIndentifiersLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<VisitData> placesInfo;
+ placesInfo.SetCapacity(placesIndentifiersLength);
+ for (uint32_t i = 0; i < placesIndentifiersLength; i++) {
+ JS::Rooted<JS::Value> placeIdentifier(aCtx);
+ bool rc = JS_GetElement(aCtx, placesIndentifiers, i, &placeIdentifier);
+ NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
+
+ // GUID
+ nsAutoString fatGUID;
+ GetJSValueAsString(aCtx, placeIdentifier, fatGUID);
+ if (!fatGUID.IsVoid()) {
+ NS_ConvertUTF16toUTF8 guid(fatGUID);
+ if (!IsValidGUID(guid))
+ return NS_ERROR_INVALID_ARG;
+
+ VisitData& placeInfo = *placesInfo.AppendElement(VisitData());
+ placeInfo.guid = guid;
+ }
+ else {
+ nsCOMPtr<nsIURI> uri = GetJSValueAsURI(aCtx, placeIdentifier);
+ if (!uri)
+ return NS_ERROR_INVALID_ARG; // neither a guid, nor a uri.
+ placesInfo.AppendElement(VisitData(uri));
+ }
+ }
+
+ mozIStorageConnection* dbConn = GetDBConn();
+ NS_ENSURE_STATE(dbConn);
+
+ for (nsTArray<VisitData>::size_type i = 0; i < placesInfo.Length(); i++) {
+ nsresult rv = GetPlaceInfo::Start(dbConn, placesInfo.ElementAt(i), aCallback);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Be sure to notify that all of our operations are complete. This
+ // is dispatched to the background thread first and redirected to the
+ // main thread from there to make sure that all database notifications
+ // and all embed or canAddURI notifications have finished.
+ if (aCallback) {
+ nsMainThreadPtrHandle<mozIVisitInfoCallback>
+ callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(aCallback));
+ nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
+ NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIRunnable> event = new NotifyCompletion(callback);
+ return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+History::UpdatePlaces(JS::Handle<JS::Value> aPlaceInfos,
+ mozIVisitInfoCallback* aCallback,
+ JSContext* aCtx)
+{
+ NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(!aPlaceInfos.isPrimitive(), NS_ERROR_INVALID_ARG);
+
+ uint32_t infosLength;
+ JS::Rooted<JSObject*> infos(aCtx);
+ nsresult rv = GetJSArrayFromJSValue(aPlaceInfos, aCtx, &infos, &infosLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<VisitData> visitData;
+ for (uint32_t i = 0; i < infosLength; i++) {
+ JS::Rooted<JSObject*> info(aCtx);
+ nsresult rv = GetJSObjectFromArray(aCtx, infos, i, &info);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri = GetURIFromJSObject(aCtx, info, "uri");
+ nsCString guid;
+ {
+ nsString fatGUID;
+ GetStringFromJSObject(aCtx, info, "guid", fatGUID);
+ if (fatGUID.IsVoid()) {
+ guid.SetIsVoid(true);
+ }
+ else {
+ guid = NS_ConvertUTF16toUTF8(fatGUID);
+ }
+ }
+
+ // Make sure that any uri we are given can be added to history, and if not,
+ // skip it (CanAddURI will notify our callback for us).
+ if (uri && !CanAddURI(uri, guid, aCallback)) {
+ continue;
+ }
+
+ // We must have at least one of uri or guid.
+ NS_ENSURE_ARG(uri || !guid.IsVoid());
+
+ // If we were given a guid, make sure it is valid.
+ bool isValidGUID = IsValidGUID(guid);
+ NS_ENSURE_ARG(guid.IsVoid() || isValidGUID);
+
+ nsString title;
+ GetStringFromJSObject(aCtx, info, "title", title);
+
+ JS::Rooted<JSObject*> visits(aCtx, nullptr);
+ {
+ JS::Rooted<JS::Value> visitsVal(aCtx);
+ bool rc = JS_GetProperty(aCtx, info, "visits", &visitsVal);
+ NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
+ if (!visitsVal.isPrimitive()) {
+ visits = visitsVal.toObjectOrNull();
+ bool isArray;
+ if (!JS_IsArrayObject(aCtx, visits, &isArray)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!isArray) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ }
+ NS_ENSURE_ARG(visits);
+
+ uint32_t visitsLength = 0;
+ if (visits) {
+ (void)JS_GetArrayLength(aCtx, visits, &visitsLength);
+ }
+ NS_ENSURE_ARG(visitsLength > 0);
+
+ // Check each visit, and build our array of VisitData objects.
+ visitData.SetCapacity(visitData.Length() + visitsLength);
+ for (uint32_t j = 0; j < visitsLength; j++) {
+ JS::Rooted<JSObject*> visit(aCtx);
+ rv = GetJSObjectFromArray(aCtx, visits, j, &visit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ VisitData& data = *visitData.AppendElement(VisitData(uri));
+ data.title = title;
+ data.guid = guid;
+
+ // We must have a date and a transaction type!
+ rv = GetIntFromJSObject(aCtx, visit, "visitDate", &data.visitTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t transitionType = 0;
+ rv = GetIntFromJSObject(aCtx, visit, "transitionType", &transitionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_ARG_RANGE(transitionType,
+ nsINavHistoryService::TRANSITION_LINK,
+ nsINavHistoryService::TRANSITION_RELOAD);
+ data.SetTransitionType(transitionType);
+ data.hidden = GetHiddenState(false, transitionType);
+
+ // If the visit is an embed visit, we do not actually add it to the
+ // database.
+ if (transitionType == nsINavHistoryService::TRANSITION_EMBED) {
+ StoreAndNotifyEmbedVisit(data, aCallback);
+ visitData.RemoveElementAt(visitData.Length() - 1);
+ continue;
+ }
+
+ // The referrer is optional.
+ nsCOMPtr<nsIURI> referrer = GetURIFromJSObject(aCtx, visit,
+ "referrerURI");
+ if (referrer) {
+ (void)referrer->GetSpec(data.referrerSpec);
+ }
+ }
+ }
+
+ mozIStorageConnection* dbConn = GetDBConn();
+ NS_ENSURE_STATE(dbConn);
+
+ nsMainThreadPtrHandle<mozIVisitInfoCallback>
+ callback(new nsMainThreadPtrHolder<mozIVisitInfoCallback>(aCallback));
+
+ // It is possible that all of the visits we were passed were dissallowed by
+ // CanAddURI, which isn't an error. If we have no visits to add, however,
+ // we should not call InsertVisitedURIs::Start.
+ if (visitData.Length()) {
+ nsresult rv = InsertVisitedURIs::Start(dbConn, visitData, callback);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Be sure to notify that all of our operations are complete. This
+ // is dispatched to the background thread first and redirected to the
+ // main thread from there to make sure that all database notifications
+ // and all embed or canAddURI notifications have finished.
+ if (aCallback) {
+ nsCOMPtr<nsIEventTarget> backgroundThread = do_GetInterface(dbConn);
+ NS_ENSURE_TRUE(backgroundThread, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIRunnable> event = new NotifyCompletion(callback);
+ return backgroundThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+History::IsURIVisited(nsIURI* aURI,
+ mozIVisitedStatusCallback* aCallback)
+{
+ NS_ENSURE_STATE(NS_IsMainThread());
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG(aCallback);
+
+ nsresult rv = VisitedQuery::Start(aURI, aCallback);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+History::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
+ Shutdown();
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ (void)os->RemoveObserver(this, TOPIC_PLACES_SHUTDOWN);
+ }
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+NS_IMPL_ISUPPORTS(
+ History
+, IHistory
+, nsIDownloadHistory
+, mozIAsyncHistory
+, nsIObserver
+, nsIMemoryReporter
+)
+
+} // namespace places
+} // namespace mozilla
diff --git a/components/places/src/History.h b/components/places/src/History.h
new file mode 100644
index 000000000..504232ca5
--- /dev/null
+++ b/components/places/src/History.h
@@ -0,0 +1,223 @@
+/* -*- 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/. */
+
+#ifndef mozilla_places_History_h_
+#define mozilla_places_History_h_
+
+#include "mozilla/IHistory.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
+#include "mozIAsyncHistory.h"
+#include "nsIDownloadHistory.h"
+#include "Database.h"
+
+#include "mozilla/dom/Link.h"
+#include "nsTHashtable.h"
+#include "nsString.h"
+#include "nsURIHashKey.h"
+#include "nsTObserverArray.h"
+#include "nsDeque.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "mozIStorageConnection.h"
+
+namespace mozilla {
+namespace places {
+
+struct VisitData;
+class ConcurrentStatementsHolder;
+
+#define NS_HISTORYSERVICE_CID \
+ {0x0937a705, 0x91a6, 0x417a, {0x82, 0x92, 0xb2, 0x2e, 0xb1, 0x0d, 0xa8, 0x6c}}
+
+// Initial size of mRecentlyVisitedURIs.
+#define RECENTLY_VISITED_URIS_SIZE 64
+// Microseconds after which a visit can be expired from mRecentlyVisitedURIs.
+// When an URI is reloaded we only take into account the first visit to it, and
+// ignore any subsequent visits, if they happen before this time has elapsed.
+// A commonly found case is to reload a page every 5 minutes, so we pick a time
+// larger than that.
+#define RECENTLY_VISITED_URIS_MAX_AGE 6 * 60 * PR_USEC_PER_SEC
+
+class History final : public IHistory
+ , public nsIDownloadHistory
+ , public mozIAsyncHistory
+ , public nsIObserver
+ , public nsIMemoryReporter
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_IHISTORY
+ NS_DECL_NSIDOWNLOADHISTORY
+ NS_DECL_MOZIASYNCHISTORY
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMEMORYREPORTER
+
+ History();
+
+ /**
+ * Obtains the statement to use to check if a URI is visited or not.
+ */
+ nsresult GetIsVisitedStatement(mozIStorageCompletionCallback* aCallback);
+
+ /**
+ * Adds an entry in moz_places with the data in aVisitData.
+ *
+ * @param aVisitData
+ * The visit data to use to populate a new row in moz_places.
+ */
+ nsresult InsertPlace(VisitData& aVisitData);
+
+ /**
+ * Updates an entry in moz_places with the data in aVisitData.
+ *
+ * @param aVisitData
+ * The visit data to use to update the existing row in moz_places.
+ */
+ nsresult UpdatePlace(const VisitData& aVisitData);
+
+ /**
+ * Loads information about the page into _place from moz_places.
+ *
+ * @param _place
+ * The VisitData for the place we need to know information about.
+ * @param [out] _exists
+ * Whether or the page was recorded in moz_places, false otherwise.
+ */
+ nsresult FetchPageInfo(VisitData& _place, bool* _exists);
+
+ /**
+ * Get the number of bytes of memory this History object is using,
+ * including sizeof(*this))
+ */
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+ /**
+ * Obtains a pointer to this service.
+ */
+ static History* GetService();
+
+ /**
+ * Obtains a pointer that has had AddRef called on it. Used by the service
+ * manager only.
+ */
+ static History* GetSingleton();
+
+ template<int N>
+ already_AddRefed<mozIStorageStatement>
+ GetStatement(const char (&aQuery)[N])
+ {
+ mozIStorageConnection* dbConn = GetDBConn();
+ NS_ENSURE_TRUE(dbConn, nullptr);
+ return mDB->GetStatement(aQuery);
+ }
+
+ already_AddRefed<mozIStorageStatement>
+ GetStatement(const nsACString& aQuery)
+ {
+ mozIStorageConnection* dbConn = GetDBConn();
+ NS_ENSURE_TRUE(dbConn, nullptr);
+ return mDB->GetStatement(aQuery);
+ }
+
+ bool IsShuttingDown() const {
+ return mShuttingDown;
+ }
+ Mutex& GetShutdownMutex() {
+ return mShutdownMutex;
+ }
+
+ /**
+ * Helper function to append a new URI to mRecentlyVisitedURIs. See
+ * mRecentlyVisitedURIs.
+ */
+ void AppendToRecentlyVisitedURIs(nsIURI* aURI);
+
+private:
+ virtual ~History();
+
+ void InitMemoryReporter();
+
+ /**
+ * Obtains a read-write database connection.
+ */
+ mozIStorageConnection* GetDBConn();
+
+ /**
+ * The database handle. This is initialized lazily by the first call to
+ * GetDBConn(), so never use it directly, or, if you really need, always
+ * invoke GetDBConn() before.
+ */
+ RefPtr<mozilla::places::Database> mDB;
+
+ RefPtr<ConcurrentStatementsHolder> mConcurrentStatementsHolder;
+
+ /**
+ * Remove any memory references to tasks and do not take on any more.
+ */
+ void Shutdown();
+
+ static History* gService;
+
+ // Ensures new tasks aren't started on destruction.
+ bool mShuttingDown;
+ // This mutex guards mShuttingDown. Code running in other threads that might
+ // schedule tasks that use the database should grab it and check the value of
+ // mShuttingDown. If we are already shutting down, the code must gracefully
+ // avoid using the db. If we are not, the lock will prevent shutdown from
+ // starting in an unexpected moment.
+ Mutex mShutdownMutex;
+
+ typedef nsTObserverArray<mozilla::dom::Link* > ObserverArray;
+
+ class KeyClass : public nsURIHashKey
+ {
+ public:
+ explicit KeyClass(const nsIURI* aURI)
+ : nsURIHashKey(aURI)
+ {
+ }
+ KeyClass(const KeyClass& aOther)
+ : nsURIHashKey(aOther)
+ {
+ NS_NOTREACHED("Do not call me!");
+ }
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return array.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ ObserverArray array;
+ };
+
+ nsTHashtable<KeyClass> mObservers;
+
+ /**
+ * mRecentlyVisitedURIs remembers URIs which have been recently added to
+ * history, to avoid saving these locations repeatedly in a short period.
+ */
+ class RecentURIKey : public nsURIHashKey
+ {
+ public:
+ explicit RecentURIKey(const nsIURI* aURI) : nsURIHashKey(aURI)
+ {
+ }
+ RecentURIKey(const RecentURIKey& aOther) : nsURIHashKey(aOther)
+ {
+ NS_NOTREACHED("Do not call me!");
+ }
+ MOZ_INIT_OUTSIDE_CTOR PRTime time;
+ };
+ nsTHashtable<RecentURIKey> mRecentlyVisitedURIs;
+ /**
+ * Whether aURI has been visited "recently".
+ * See RECENTLY_VISITED_URIS_MAX_AGE.
+ */
+ bool IsRecentlyVisitedURI(nsIURI* aURI);
+};
+
+} // namespace places
+} // namespace mozilla
+
+#endif // mozilla_places_History_h_
diff --git a/components/places/src/History.jsm b/components/places/src/History.jsm
new file mode 100644
index 000000000..59c24fcc6
--- /dev/null
+++ b/components/places/src/History.jsm
@@ -0,0 +1,1049 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+/**
+ * Asynchronous API for managing history.
+ *
+ *
+ * The API makes use of `PageInfo` and `VisitInfo` objects, defined as follows.
+ *
+ * A `PageInfo` object is any object that contains A SUBSET of the
+ * following properties:
+ * - guid: (string)
+ * The globally unique id of the page.
+ * - url: (URL)
+ * or (nsIURI)
+ * or (string)
+ * The full URI of the page. Note that `PageInfo` values passed as
+ * argument may hold `nsIURI` or `string` values for property `url`,
+ * but `PageInfo` objects returned by this module always hold `URL`
+ * values.
+ * - title: (string)
+ * The title associated with the page, if any.
+ * - frecency: (number)
+ * The frecency of the page, if any.
+ * See https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Places/Frecency_algorithm
+ * Note that this property may not be used to change the actualy frecency
+ * score of a page, only to retrieve it. In other words, any `frecency` field
+ * passed as argument to a function of this API will be ignored.
+ * - visits: (Array<VisitInfo>)
+ * All the visits for this page, if any.
+ *
+ * See the documentation of individual methods to find out which properties
+ * are required for `PageInfo` arguments or returned for `PageInfo` results.
+ *
+ * A `VisitInfo` object is any object that contains A SUBSET of the following
+ * properties:
+ * - date: (Date)
+ * The time the visit occurred.
+ * - transition: (number)
+ * How the user reached the page. See constants `TRANSITIONS.*`
+ * for the possible transition types.
+ * - referrer: (URL)
+ * or (nsIURI)
+ * or (string)
+ * The referring URI of this visit. Note that `VisitInfo` passed
+ * as argument may hold `nsIURI` or `string` values for property `referrer`,
+ * but `VisitInfo` objects returned by this module always hold `URL`
+ * values.
+ * See the documentation of individual methods to find out which properties
+ * are required for `VisitInfo` arguments or returned for `VisitInfo` results.
+ *
+ *
+ *
+ * Each successful operation notifies through the nsINavHistoryObserver
+ * interface. To listen to such notifications you must register using
+ * nsINavHistoryService `addObserver` and `removeObserver` methods.
+ * @see nsINavHistoryObserver
+ */
+
+this.EXPORTED_SYMBOLS = [ "History" ];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+ "resource://gre/modules/AsyncShutdown.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
+ "resource://gre/modules/Sqlite.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+Cu.importGlobalProperties(["URL"]);
+
+/**
+ * Whenever we update or remove numerous pages, it is preferable
+ * to yield time to the main thread every so often to avoid janking.
+ * These constants determine the maximal number of notifications we
+ * may emit before we yield.
+ */
+const NOTIFICATION_CHUNK_SIZE = 300;
+const ONRESULT_CHUNK_SIZE = 300;
+
+// Timers resolution is not always good, it can have a 16ms precision on Win.
+const TIMERS_RESOLUTION_SKEW_MS = 16;
+
+/**
+ * Sends a bookmarks notification through the given observers.
+ *
+ * @param observers
+ * array of nsINavBookmarkObserver objects.
+ * @param notification
+ * the notification name.
+ * @param args
+ * array of arguments to pass to the notification.
+ */
+function notify(observers, notification, args = []) {
+ for (let observer of observers) {
+ try {
+ observer[notification](...args);
+ } catch (ex) {}
+ }
+}
+
+this.History = Object.freeze({
+ /**
+ * Fetch the available information for one page.
+ *
+ * @param guidOrURI: (URL or nsIURI)
+ * The full URI of the page.
+ * or (string)
+ * Either the full URI of the page or the GUID of the page.
+ *
+ * @return (Promise)
+ * A promise resolved once the operation is complete.
+ * @resolves (PageInfo | null) If the page could be found, the information
+ * on that page. Note that this `PageInfo` does NOT contain the visit
+ * data (i.e. `visits` is `undefined`).
+ *
+ * @throws (Error)
+ * If `guidOrURI` does not have the expected type or if it is a string
+ * that may be parsed neither as a valid URL nor as a valid GUID.
+ */
+ fetch: function (guidOrURI) {
+ throw new Error("Method not implemented");
+ },
+
+ /**
+ * Adds a number of visits for a single page.
+ *
+ * Any change may be observed through nsINavHistoryObserver
+ *
+ * @param pageInfo: (PageInfo)
+ * Information on a page. This `PageInfo` MUST contain
+ * - a property `url`, as specified by the definition of `PageInfo`.
+ * - a property `visits`, as specified by the definition of
+ * `PageInfo`, which MUST contain at least one visit.
+ * If a property `title` is provided, the title of the page
+ * is updated.
+ * If the `date` of a visit is not provided, it defaults
+ * to now.
+ * If the `transition` of a visit is not provided, it defaults to
+ * TRANSITION_LINK.
+ *
+ * @return (Promise)
+ * A promise resolved once the operation is complete.
+ * @resolves (PageInfo)
+ * A PageInfo object populated with data after the insert is complete.
+ * @rejects (Error)
+ * Rejects if the insert was unsuccessful.
+ *
+ * @throws (Error)
+ * If the `url` specified was for a protocol that should not be
+ * stored (e.g. "chrome:", "mailbox:", "about:", "imap:", "news:",
+ * "moz-anno:", "view-source:", "resource:", "data:", "wyciwyg:",
+ * "javascript:", "blob:").
+ * @throws (Error)
+ * If `pageInfo` has an unexpected type.
+ * @throws (Error)
+ * If `pageInfo` does not have a `url`.
+ * @throws (Error)
+ * If `pageInfo` does not have a `visits` property or if the
+ * value of `visits` is ill-typed or is an empty array.
+ * @throws (Error)
+ * If an element of `visits` has an invalid `date`.
+ * @throws (Error)
+ * If an element of `visits` has an invalid `transition`.
+ */
+ insert: function (pageInfo) {
+ if (typeof pageInfo != "object" || !pageInfo) {
+ throw new TypeError("pageInfo must be an object");
+ }
+
+ let info = validatePageInfo(pageInfo);
+
+ return PlacesUtils.withConnectionWrapper("History.jsm: insert",
+ db => insert(db, info));
+ },
+
+ /**
+ * Adds a number of visits for a number of pages.
+ *
+ * Any change may be observed through nsINavHistoryObserver
+ *
+ * @param pageInfos: (Array<PageInfo>)
+ * Information on a page. This `PageInfo` MUST contain
+ * - a property `url`, as specified by the definition of `PageInfo`.
+ * - a property `visits`, as specified by the definition of
+ * `PageInfo`, which MUST contain at least one visit.
+ * If a property `title` is provided, the title of the page
+ * is updated.
+ * If the `date` of a visit is not provided, it defaults
+ * to now.
+ * If the `transition` of a visit is not provided, it defaults to
+ * TRANSITION_LINK.
+ * @param onResult: (function(PageInfo))
+ * A callback invoked for each page inserted.
+ * @param onError: (function(PageInfo))
+ * A callback invoked for each page which generated an error
+ * when an insert was attempted.
+ *
+ * @return (Promise)
+ * A promise resolved once the operation is complete.
+ * @resolves (null)
+ * @rejects (Error)
+ * Rejects if all of the inserts were unsuccessful.
+ *
+ * @throws (Error)
+ * If the `url` specified was for a protocol that should not be
+ * stored (e.g. "chrome:", "mailbox:", "about:", "imap:", "news:",
+ * "moz-anno:", "view-source:", "resource:", "data:", "wyciwyg:",
+ * "javascript:", "blob:").
+ * @throws (Error)
+ * If `pageInfos` has an unexpected type.
+ * @throws (Error)
+ * If a `pageInfo` does not have a `url`.
+ * @throws (Error)
+ * If a `PageInfo` does not have a `visits` property or if the
+ * value of `visits` is ill-typed or is an empty array.
+ * @throws (Error)
+ * If an element of `visits` has an invalid `date`.
+ * @throws (Error)
+ * If an element of `visits` has an invalid `transition`.
+ */
+ insertMany: function (pageInfos, onResult, onError) {
+ let infos = [];
+
+ if (!Array.isArray(pageInfos)) {
+ throw new TypeError("pageInfos must be an array");
+ }
+ if (!pageInfos.length) {
+ throw new TypeError("pageInfos may not be an empty array");
+ }
+
+ if (onResult && typeof onResult != "function") {
+ throw new TypeError(`onResult: ${onResult} is not a valid function`);
+ }
+ if (onError && typeof onError != "function") {
+ throw new TypeError(`onError: ${onError} is not a valid function`);
+ }
+
+ for (let pageInfo of pageInfos) {
+ let info = validatePageInfo(pageInfo);
+ infos.push(info);
+ }
+
+ return PlacesUtils.withConnectionWrapper("History.jsm: insertMany",
+ db => insertMany(db, infos, onResult, onError));
+ },
+
+ /**
+ * Remove pages from the database.
+ *
+ * Any change may be observed through nsINavHistoryObserver
+ *
+ *
+ * @param page: (URL or nsIURI)
+ * The full URI of the page.
+ * or (string)
+ * Either the full URI of the page or the GUID of the page.
+ * or (Array<URL|nsIURI|string>)
+ * An array of the above, to batch requests.
+ * @param onResult: (function(PageInfo))
+ * A callback invoked for each page found.
+ *
+ * @return (Promise)
+ * A promise resolved once the operation is complete.
+ * @resolve (bool)
+ * `true` if at least one page was removed, `false` otherwise.
+ * @throws (TypeError)
+ * If `pages` has an unexpected type or if a string provided
+ * is neither a valid GUID nor a valid URI or if `pages`
+ * is an empty array.
+ */
+ remove: function (pages, onResult = null) {
+ // Normalize and type-check arguments
+ if (Array.isArray(pages)) {
+ if (pages.length == 0) {
+ throw new TypeError("Expected at least one page");
+ }
+ } else {
+ pages = [pages];
+ }
+
+ let guids = [];
+ let urls = [];
+ for (let page of pages) {
+ // Normalize to URL or GUID, or throw if `page` cannot
+ // be normalized.
+ let normalized = normalizeToURLOrGUID(page);
+ if (typeof normalized === "string") {
+ guids.push(normalized);
+ } else {
+ urls.push(normalized.href);
+ }
+ }
+ let normalizedPages = {guids: guids, urls: urls};
+
+ // At this stage, we know that either `guids` is not-empty
+ // or `urls` is not-empty.
+
+ if (onResult && typeof onResult != "function") {
+ throw new TypeError("Invalid function: " + onResult);
+ }
+
+ return PlacesUtils.withConnectionWrapper("History.jsm: remove",
+ db => remove(db, normalizedPages, onResult));
+ },
+
+ /**
+ * Remove visits matching specific characteristics.
+ *
+ * Any change may be observed through nsINavHistoryObserver.
+ *
+ * @param filter: (object)
+ * The `object` may contain some of the following
+ * properties:
+ * - beginDate: (Date) Remove visits that have
+ * been added since this date (inclusive).
+ * - endDate: (Date) Remove visits that have
+ * been added before this date (inclusive).
+ * - limit: (Number) Limit the number of visits
+ * we remove to this number
+ * - url: (URL) Only remove visits to this URL
+ * If both `beginDate` and `endDate` are specified,
+ * visits between `beginDate` (inclusive) and `end`
+ * (inclusive) are removed.
+ *
+ * @param onResult: (function(VisitInfo), [optional])
+ * A callback invoked for each visit found and removed.
+ * Note that the referrer property of `VisitInfo`
+ * is NOT populated.
+ *
+ * @return (Promise)
+ * @resolve (bool)
+ * `true` if at least one visit was removed, `false`
+ * otherwise.
+ * @throws (TypeError)
+ * If `filter` does not have the expected type, in
+ * particular if the `object` is empty.
+ */
+ removeVisitsByFilter: function(filter, onResult = null) {
+ if (!filter || typeof filter != "object") {
+ throw new TypeError("Expected a filter");
+ }
+
+ let hasBeginDate = "beginDate" in filter;
+ let hasEndDate = "endDate" in filter;
+ let hasURL = "url" in filter;
+ let hasLimit = "limit" in filter;
+ if (hasBeginDate) {
+ ensureDate(filter.beginDate);
+ }
+ if (hasEndDate) {
+ ensureDate(filter.endDate);
+ }
+ if (hasBeginDate && hasEndDate && filter.beginDate > filter.endDate) {
+ throw new TypeError("`beginDate` should be at least as old as `endDate`");
+ }
+ if (!hasBeginDate && !hasEndDate && !hasURL && !hasLimit) {
+ throw new TypeError("Expected a non-empty filter");
+ }
+
+ if (hasURL && !(filter.url instanceof URL) && typeof filter.url != "string" &&
+ !(filter.url instanceof Ci.nsIURI)) {
+ throw new TypeError("Expected a valid URL for `url`");
+ }
+
+ if (hasLimit &&
+ (typeof filter.limit != "number" ||
+ filter.limit <= 0 ||
+ !Number.isInteger(filter.limit))) {
+ throw new TypeError("Expected a non-zero positive integer as a limit");
+ }
+
+ if (onResult && typeof onResult != "function") {
+ throw new TypeError("Invalid function: " + onResult);
+ }
+
+ return PlacesUtils.withConnectionWrapper("History.jsm: removeVisitsByFilter",
+ db => removeVisitsByFilter(db, filter, onResult)
+ );
+ },
+
+ /**
+ * Determine if a page has been visited.
+ *
+ * @param pages: (URL or nsIURI)
+ * The full URI of the page.
+ * or (string)
+ * The full URI of the page or the GUID of the page.
+ *
+ * @return (Promise)
+ * A promise resolved once the operation is complete.
+ * @resolve (bool)
+ * `true` if the page has been visited, `false` otherwise.
+ * @throws (Error)
+ * If `pages` has an unexpected type or if a string provided
+ * is neither not a valid GUID nor a valid URI.
+ */
+ hasVisits: function(page, onResult) {
+ throw new Error("Method not implemented");
+ },
+
+ /**
+ * Clear all history.
+ *
+ * @return (Promise)
+ * A promise resolved once the operation is complete.
+ */
+ clear() {
+ return PlacesUtils.withConnectionWrapper("History.jsm: clear",
+ clear
+ );
+ },
+
+ /**
+ * Possible values for the `transition` property of `VisitInfo`
+ * objects.
+ */
+
+ TRANSITIONS: {
+ /**
+ * The user followed a link and got a new toplevel window.
+ */
+ LINK: Ci.nsINavHistoryService.TRANSITION_LINK,
+
+ /**
+ * The user typed the page's URL in the URL bar or selected it from
+ * URL bar autocomplete results, clicked on it from a history query
+ * (from the History sidebar, History menu, or history query in the
+ * personal toolbar or Places organizer.
+ */
+ TYPED: Ci.nsINavHistoryService.TRANSITION_TYPED,
+
+ /**
+ * The user followed a bookmark to get to the page.
+ */
+ BOOKMARK: Ci.nsINavHistoryService.TRANSITION_BOOKMARK,
+
+ /**
+ * Some inner content is loaded. This is true of all images on a
+ * page, and the contents of the iframe. It is also true of any
+ * content in a frame if the user did not explicitly follow a link
+ * to get there.
+ */
+ EMBED: Ci.nsINavHistoryService.TRANSITION_EMBED,
+
+ /**
+ * Set when the transition was a permanent redirect.
+ */
+ REDIRECT_PERMANENT: Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT,
+
+ /**
+ * Set when the transition was a temporary redirect.
+ */
+ REDIRECT_TEMPORARY: Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY,
+
+ /**
+ * Set when the transition is a download.
+ */
+ DOWNLOAD: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,
+
+ /**
+ * The user followed a link and got a visit in a frame.
+ */
+ FRAMED_LINK: Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK,
+
+ /**
+ * The user reloaded a page.
+ */
+ RELOAD: Ci.nsINavHistoryService.TRANSITION_RELOAD,
+ },
+});
+
+/**
+ * Validate an input PageInfo object, returning a valid PageInfo object.
+ *
+ * @param pageInfo: (PageInfo)
+ * @return (PageInfo)
+ */
+function validatePageInfo(pageInfo) {
+ let info = {
+ visits: [],
+ };
+
+ if (!pageInfo.url) {
+ throw new TypeError("PageInfo object must have a url property");
+ }
+
+ info.url = normalizeToURLOrGUID(pageInfo.url);
+
+ if (typeof pageInfo.title === "string") {
+ info.title = pageInfo.title;
+ } else if (pageInfo.title != null && pageInfo.title != undefined) {
+ throw new TypeError(`title property of PageInfo object: ${pageInfo.title} must be a string if provided`);
+ }
+
+ if (!pageInfo.visits || !Array.isArray(pageInfo.visits) || !pageInfo.visits.length) {
+ throw new TypeError("PageInfo object must have an array of visits");
+ }
+ for (let inVisit of pageInfo.visits) {
+ let visit = {
+ date: new Date(),
+ transition: inVisit.transition || History.TRANSITIONS.LINK,
+ };
+
+ if (!isValidTransitionType(visit.transition)) {
+ throw new TypeError(`transition: ${visit.transition} is not a valid transition type`);
+ }
+
+ if (inVisit.date) {
+ ensureDate(inVisit.date);
+ if (inVisit.date > (Date.now() + TIMERS_RESOLUTION_SKEW_MS)) {
+ throw new TypeError(`date: ${inVisit.date} cannot be a future date`);
+ }
+ visit.date = inVisit.date;
+ }
+
+ if (inVisit.referrer) {
+ visit.referrer = normalizeToURLOrGUID(inVisit.referrer);
+ }
+ info.visits.push(visit);
+ }
+ return info;
+}
+
+/**
+ * Convert a PageInfo object into the format expected by updatePlaces.
+ *
+ * Note: this assumes that the PageInfo object has already been validated
+ * via validatePageInfo.
+ *
+ * @param pageInfo: (PageInfo)
+ * @return (info)
+ */
+function convertForUpdatePlaces(pageInfo) {
+ let info = {
+ uri: PlacesUtils.toURI(pageInfo.url),
+ title: pageInfo.title,
+ visits: [],
+ };
+
+ for (let inVisit of pageInfo.visits) {
+ let visit = {
+ visitDate: PlacesUtils.toPRTime(inVisit.date),
+ transitionType: inVisit.transition,
+ referrerURI: (inVisit.referrer) ? PlacesUtils.toURI(inVisit.referrer) : undefined,
+ };
+ info.visits.push(visit);
+ }
+ return info;
+}
+
+/**
+ * Is a value a valid transition type?
+ *
+ * @param transitionType: (String)
+ * @return (Boolean)
+ */
+function isValidTransitionType(transitionType) {
+ return Object.values(History.TRANSITIONS).includes(transitionType);
+}
+
+/**
+ * Normalize a key to either a string (if it is a valid GUID) or an
+ * instance of `URL` (if it is a `URL`, `nsIURI`, or a string
+ * representing a valid url).
+ *
+ * @throws (TypeError)
+ * If the key is neither a valid guid nor a valid url.
+ */
+function normalizeToURLOrGUID(key) {
+ if (typeof key === "string") {
+ // A string may be a URL or a guid
+ if (PlacesUtils.isValidGuid(key)) {
+ return key;
+ }
+ return new URL(key);
+ }
+ if (key instanceof URL) {
+ return key;
+ }
+ if (key instanceof Ci.nsIURI) {
+ return new URL(key.spec);
+ }
+ throw new TypeError("Invalid url or guid: " + key);
+}
+
+/**
+ * Throw if an object is not a Date object.
+ */
+function ensureDate(arg) {
+ if (!arg || typeof arg != "object" || arg.constructor.name != "Date") {
+ throw new TypeError("Expected a Date, got " + arg);
+ }
+}
+
+/**
+ * Convert a list of strings or numbers to its SQL
+ * representation as a string.
+ */
+function sqlList(list) {
+ return list.map(JSON.stringify).join();
+}
+
+/**
+ * Invalidate and recompute the frecency of a list of pages,
+ * informing frecency observers.
+ *
+ * @param db: (Sqlite connection)
+ * @param idList: (Array)
+ * The `moz_places` identifiers for the places to invalidate.
+ * @return (Promise)
+ */
+var invalidateFrecencies = Task.async(function*(db, idList) {
+ if (idList.length == 0) {
+ return;
+ }
+ let ids = sqlList(idList);
+ yield db.execute(
+ `UPDATE moz_places
+ SET frecency = NOTIFY_FRECENCY(
+ CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date
+ ) WHERE id in (${ ids })`
+ );
+ yield db.execute(
+ `UPDATE moz_places
+ SET hidden = 0
+ WHERE id in (${ ids })
+ AND frecency <> 0`
+ );
+});
+
+// Inner implementation of History.clear().
+var clear = Task.async(function* (db) {
+ // Remove all history.
+ yield db.execute("DELETE FROM moz_historyvisits");
+
+ // Clear the registered embed visits.
+ PlacesUtils.history.clearEmbedVisits();
+
+ // Expiration will take care of orphans.
+ let observers = PlacesUtils.history.getObservers();
+ notify(observers, "onClearHistory");
+
+ // Invalidate frecencies for the remaining places. This must happen
+ // after the notification to ensure it runs enqueued to expiration.
+ yield db.execute(
+ `UPDATE moz_places SET frecency =
+ (CASE
+ WHEN url_hash BETWEEN hash("place", "prefix_lo") AND
+ hash("place", "prefix_hi")
+ THEN 0
+ ELSE -1
+ END)
+ WHERE frecency > 0`);
+
+ // Notify frecency change observers.
+ notify(observers, "onManyFrecenciesChanged");
+});
+
+/**
+ * Clean up pages whose history has been modified, by either
+ * removing them entirely (if they are marked for removal,
+ * typically because all visits have been removed and there
+ * are no more foreign keys such as bookmarks) or updating
+ * their frecency (otherwise).
+ *
+ * @param db: (Sqlite connection)
+ * The database.
+ * @param pages: (Array of objects)
+ * Pages that have been touched and that need cleaning up.
+ * Each object should have the following properties:
+ * - id: (number) The `moz_places` identifier for the place.
+ * - hasVisits: (boolean) If `true`, there remains at least one
+ * visit to this page, so the page should be kept and its
+ * frecency updated.
+ * - hasForeign: (boolean) If `true`, the page has at least
+ * one foreign reference (i.e. a bookmark), so the page should
+ * be kept and its frecency updated.
+ * @return (Promise)
+ */
+var cleanupPages = Task.async(function*(db, pages) {
+ yield invalidateFrecencies(db, pages.filter(p => p.hasForeign || p.hasVisits).map(p => p.id));
+
+ let pageIdsToRemove = pages.filter(p => !p.hasForeign && !p.hasVisits).map(p => p.id);
+ if (pageIdsToRemove.length > 0) {
+ let idsList = sqlList(pageIdsToRemove);
+ // Note, we are already in a transaction, since callers create it.
+ // Check relations regardless, to avoid creating orphans in case of
+ // async race conditions.
+ yield db.execute(`DELETE FROM moz_places WHERE id IN ( ${ idsList } )
+ AND foreign_count = 0 AND last_visit_date ISNULL`);
+ // Hosts accumulated during the places delete are updated through a trigger
+ // (see nsPlacesTriggers.h).
+ yield db.executeCached(`DELETE FROM moz_updatehosts_temp`);
+
+ // Expire orphans.
+ yield db.executeCached(`
+ DELETE FROM moz_favicons WHERE NOT EXISTS
+ (SELECT 1 FROM moz_places WHERE favicon_id = moz_favicons.id)`);
+ yield db.execute(`DELETE FROM moz_annos
+ WHERE place_id IN ( ${ idsList } )`);
+ yield db.execute(`DELETE FROM moz_inputhistory
+ WHERE place_id IN ( ${ idsList } )`);
+ }
+});
+
+/**
+ * Notify observers that pages have been removed/updated.
+ *
+ * @param db: (Sqlite connection)
+ * The database.
+ * @param pages: (Array of objects)
+ * Pages that have been touched and that need cleaning up.
+ * Each object should have the following properties:
+ * - id: (number) The `moz_places` identifier for the place.
+ * - hasVisits: (boolean) If `true`, there remains at least one
+ * visit to this page, so the page should be kept and its
+ * frecency updated.
+ * - hasForeign: (boolean) If `true`, the page has at least
+ * one foreign reference (i.e. a bookmark), so the page should
+ * be kept and its frecency updated.
+ * @return (Promise)
+ */
+var notifyCleanup = Task.async(function*(db, pages) {
+ let notifiedCount = 0;
+ let observers = PlacesUtils.history.getObservers();
+
+ let reason = Ci.nsINavHistoryObserver.REASON_DELETED;
+
+ for (let page of pages) {
+ let uri = NetUtil.newURI(page.url.href);
+ let guid = page.guid;
+ if (page.hasVisits) {
+ // For the moment, we do not have the necessary observer API
+ // to notify when we remove a subset of visits, see bug 937560.
+ continue;
+ }
+ if (page.hasForeign) {
+ // We have removed all visits, but the page is still alive, e.g.
+ // because of a bookmark.
+ notify(observers, "onDeleteVisits",
+ [uri, /* last visit*/0, guid, reason, -1]);
+ } else {
+ // The page has been entirely removed.
+ notify(observers, "onDeleteURI",
+ [uri, guid, reason]);
+ }
+ if (++notifiedCount % NOTIFICATION_CHUNK_SIZE == 0) {
+ // Every few notifications, yield time back to the main
+ // thread to avoid jank.
+ yield Promise.resolve();
+ }
+ }
+});
+
+/**
+ * Notify an `onResult` callback of a set of operations
+ * that just took place.
+ *
+ * @param data: (Array)
+ * The data to send to the callback.
+ * @param onResult: (function [optional])
+ * If provided, call `onResult` with `data[0]`, `data[1]`, etc.
+ * Otherwise, do nothing.
+ */
+var notifyOnResult = Task.async(function*(data, onResult) {
+ if (!onResult) {
+ return;
+ }
+ let notifiedCount = 0;
+ for (let info of data) {
+ try {
+ onResult(info);
+ } catch (ex) {
+ // Errors should be reported but should not stop the operation.
+ Promise.reject(ex);
+ }
+ if (++notifiedCount % ONRESULT_CHUNK_SIZE == 0) {
+ // Every few notifications, yield time back to the main
+ // thread to avoid jank.
+ yield Promise.resolve();
+ }
+ }
+});
+
+// Inner implementation of History.removeVisitsByFilter.
+var removeVisitsByFilter = Task.async(function*(db, filter, onResult = null) {
+ // 1. Determine visits that took place during the interval. Note
+ // that the database uses microseconds, while JS uses milliseconds,
+ // so we need to *1000 one way and /1000 the other way.
+ let conditions = [];
+ let args = {};
+ if ("beginDate" in filter) {
+ conditions.push("v.visit_date >= :begin * 1000");
+ args.begin = Number(filter.beginDate);
+ }
+ if ("endDate" in filter) {
+ conditions.push("v.visit_date <= :end * 1000");
+ args.end = Number(filter.endDate);
+ }
+ if ("limit" in filter) {
+ args.limit = Number(filter.limit);
+ }
+
+ let optionalJoin = "";
+ if ("url" in filter) {
+ let url = filter.url;
+ if (url instanceof Ci.nsIURI) {
+ url = filter.url.spec;
+ } else {
+ url = new URL(url).href;
+ }
+ optionalJoin = `JOIN moz_places h ON h.id = v.place_id`;
+ conditions.push("h.url_hash = hash(:url)", "h.url = :url");
+ args.url = url;
+ }
+
+
+ let visitsToRemove = [];
+ let pagesToInspect = new Set();
+ let onResultData = onResult ? [] : null;
+
+ yield db.executeCached(
+ `SELECT v.id, place_id, visit_date / 1000 AS date, visit_type FROM moz_historyvisits v
+ ${optionalJoin}
+ WHERE ${ conditions.join(" AND ") }${ args.limit ? " LIMIT :limit" : "" }`,
+ args,
+ row => {
+ let id = row.getResultByName("id");
+ let place_id = row.getResultByName("place_id");
+ visitsToRemove.push(id);
+ pagesToInspect.add(place_id);
+
+ if (onResult) {
+ onResultData.push({
+ date: new Date(row.getResultByName("date")),
+ transition: row.getResultByName("visit_type")
+ });
+ }
+ }
+ );
+
+ try {
+ if (visitsToRemove.length == 0) {
+ // Nothing to do
+ return false;
+ }
+
+ let pages = [];
+ yield db.executeTransaction(function*() {
+ // 2. Remove all offending visits.
+ yield db.execute(`DELETE FROM moz_historyvisits
+ WHERE id IN (${ sqlList(visitsToRemove) } )`);
+
+ // 3. Find out which pages have been orphaned
+ yield db.execute(
+ `SELECT id, url, guid,
+ (foreign_count != 0) AS has_foreign,
+ (last_visit_date NOTNULL) as has_visits
+ FROM moz_places
+ WHERE id IN (${ sqlList([...pagesToInspect]) })`,
+ null,
+ row => {
+ let page = {
+ id: row.getResultByName("id"),
+ guid: row.getResultByName("guid"),
+ hasForeign: row.getResultByName("has_foreign"),
+ hasVisits: row.getResultByName("has_visits"),
+ url: new URL(row.getResultByName("url")),
+ };
+ pages.push(page);
+ });
+
+ // 4. Clean up and notify
+ yield cleanupPages(db, pages);
+ });
+
+ notifyCleanup(db, pages);
+ notifyOnResult(onResultData, onResult); // don't wait
+ } finally {
+ // Ensure we cleanup embed visits, even if we bailed out early.
+ PlacesUtils.history.clearEmbedVisits();
+ }
+
+ return visitsToRemove.length != 0;
+});
+
+
+// Inner implementation of History.remove.
+var remove = Task.async(function*(db, {guids, urls}, onResult = null) {
+ // 1. Find out what needs to be removed
+ let query =
+ `SELECT id, url, guid, foreign_count, title, frecency
+ FROM moz_places
+ WHERE guid IN (${ sqlList(guids) })
+ OR (url_hash IN (${ urls.map(u => "hash(" + JSON.stringify(u) + ")").join(",") })
+ AND url IN (${ sqlList(urls) }))
+ `;
+
+ let onResultData = onResult ? [] : null;
+ let pages = [];
+ let hasPagesToRemove = false;
+ yield db.execute(query, null, Task.async(function*(row) {
+ let hasForeign = row.getResultByName("foreign_count") != 0;
+ if (!hasForeign) {
+ hasPagesToRemove = true;
+ }
+ let id = row.getResultByName("id");
+ let guid = row.getResultByName("guid");
+ let url = row.getResultByName("url");
+ let page = {
+ id,
+ guid,
+ hasForeign,
+ hasVisits: false,
+ url: new URL(url),
+ };
+ pages.push(page);
+ if (onResult) {
+ onResultData.push({
+ guid: guid,
+ title: row.getResultByName("title"),
+ frecency: row.getResultByName("frecency"),
+ url: new URL(url)
+ });
+ }
+ }));
+
+ try {
+ if (pages.length == 0) {
+ // Nothing to do
+ return false;
+ }
+
+ yield db.executeTransaction(function*() {
+ // 2. Remove all visits to these pages.
+ yield db.execute(`DELETE FROM moz_historyvisits
+ WHERE place_id IN (${ sqlList(pages.map(p => p.id)) })
+ `);
+
+ // 3. Clean up and notify
+ yield cleanupPages(db, pages);
+ });
+
+ notifyCleanup(db, pages);
+ notifyOnResult(onResultData, onResult); // don't wait
+ } finally {
+ // Ensure we cleanup embed visits, even if we bailed out early.
+ PlacesUtils.history.clearEmbedVisits();
+ }
+
+ return hasPagesToRemove;
+});
+
+/**
+ * Merges an updateInfo object, as returned by asyncHistory.updatePlaces
+ * into a PageInfo object as defined in this file.
+ *
+ * @param updateInfo: (Object)
+ * An object that represents a page that is generated by
+ * asyncHistory.updatePlaces.
+ * @param pageInfo: (PageInfo)
+ * An PageInfo object into which to merge the data from updateInfo.
+ * Defaults to an empty object so that this method can be used
+ * to simply convert an updateInfo object into a PageInfo object.
+ *
+ * @return (PageInfo)
+ * A PageInfo object populated with data from updateInfo.
+ */
+function mergeUpdateInfoIntoPageInfo(updateInfo, pageInfo={}) {
+ pageInfo.guid = updateInfo.guid;
+ if (!pageInfo.url) {
+ pageInfo.url = new URL(updateInfo.uri.spec);
+ pageInfo.title = updateInfo.title;
+ pageInfo.visits = updateInfo.visits.map(visit => {
+ return {
+ date: PlacesUtils.toDate(visit.visitDate),
+ transition: visit.transitionType,
+ referrer: (visit.referrerURI) ? new URL(visit.referrerURI.spec) : null
+ }
+ });
+ }
+ return pageInfo;
+}
+
+// Inner implementation of History.insert.
+var insert = Task.async(function*(db, pageInfo) {
+ let info = convertForUpdatePlaces(pageInfo);
+
+ return new Promise((resolve, reject) => {
+ PlacesUtils.asyncHistory.updatePlaces(info, {
+ handleError: error => {
+ reject(error);
+ },
+ handleResult: result => {
+ pageInfo = mergeUpdateInfoIntoPageInfo(result, pageInfo);
+ },
+ handleCompletion: () => {
+ resolve(pageInfo);
+ }
+ });
+ });
+});
+
+// Inner implementation of History.insertMany.
+var insertMany = Task.async(function*(db, pageInfos, onResult, onError) {
+ let infos = [];
+ let onResultData = [];
+ let onErrorData = [];
+
+ for (let pageInfo of pageInfos) {
+ let info = convertForUpdatePlaces(pageInfo);
+ infos.push(info);
+ }
+
+ return new Promise((resolve, reject) => {
+ PlacesUtils.asyncHistory.updatePlaces(infos, {
+ handleError: (resultCode, result) => {
+ let pageInfo = mergeUpdateInfoIntoPageInfo(result);
+ onErrorData.push(pageInfo);
+ },
+ handleResult: result => {
+ let pageInfo = mergeUpdateInfoIntoPageInfo(result);
+ onResultData.push(pageInfo);
+ },
+ handleCompletion: () => {
+ notifyOnResult(onResultData, onResult);
+ notifyOnResult(onErrorData, onError);
+ if (onResultData.length) {
+ resolve();
+ } else {
+ reject({message: "No items were added to history."})
+ }
+ }
+ });
+ });
+});
diff --git a/components/places/src/PageIconProtocolHandler.js b/components/places/src/PageIconProtocolHandler.js
new file mode 100644
index 000000000..05e43ccf3
--- /dev/null
+++ b/components/places/src/PageIconProtocolHandler.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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+
+function makeDefaultFaviconChannel(uri, loadInfo) {
+ let channel = Services.io.newChannelFromURIWithLoadInfo(
+ PlacesUtils.favicons.defaultFavicon, loadInfo);
+ channel.originalURI = uri;
+ return channel;
+}
+
+function streamDefaultFavicon(uri, loadInfo, outputStream) {
+ try {
+ // Open up a new channel to get that data, and push it to our output stream.
+ // Create a listener to hand data to the pipe's output stream.
+ let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
+ .createInstance(Ci.nsISimpleStreamListener);
+ listener.init(outputStream, {
+ onStartRequest(request, context) {},
+ onStopRequest(request, context, statusCode) {
+ // We must close the outputStream regardless.
+ outputStream.close();
+ }
+ });
+ let defaultIconChannel = makeDefaultFaviconChannel(uri, loadInfo);
+ defaultIconChannel.asyncOpen2(listener);
+ } catch (ex) {
+ Cu.reportError(ex);
+ outputStream.close();
+ }
+}
+
+function PageIconProtocolHandler() {
+}
+
+PageIconProtocolHandler.prototype = {
+ get scheme() {
+ return "page-icon";
+ },
+
+ get defaultPort() {
+ return -1;
+ },
+
+ get protocolFlags() {
+ return Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_NOAUTH |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE;
+ },
+
+ newURI(spec, originCharset, baseURI) {
+ let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+ uri.spec = spec;
+ return uri;
+ },
+
+ newChannel2(uri, loadInfo) {
+ try {
+ // Create a pipe that will give us an output stream that we can use once
+ // we got all the favicon data.
+ let pipe = Cc["@mozilla.org/pipe;1"]
+ .createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, Ci.nsIFaviconService.MAX_FAVICON_BUFFER_SIZE);
+
+ // Create our channel.
+ let channel = Cc['@mozilla.org/network/input-stream-channel;1']
+ .createInstance(Ci.nsIInputStreamChannel);
+ channel.QueryInterface(Ci.nsIChannel);
+ channel.setURI(uri);
+ channel.contentStream = pipe.inputStream;
+ channel.loadInfo = loadInfo;
+
+ let pageURI = NetUtil.newURI(uri.path);
+ PlacesUtils.favicons.getFaviconDataForPage(pageURI, (iconuri, len, data, mime) => {
+ if (len == 0) {
+ channel.contentType = "image/png";
+ streamDefaultFavicon(uri, loadInfo, pipe.outputStream);
+ return;
+ }
+
+ try {
+ channel.contentType = mime;
+ // Pass the icon data to the output stream.
+ let stream = Cc["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Ci.nsIBinaryOutputStream);
+ stream.setOutputStream(pipe.outputStream);
+ stream.writeByteArray(data, len);
+ stream.close();
+ pipe.outputStream.close();
+ } catch (ex) {
+ channel.contentType = "image/png";
+ streamDefaultFavicon(uri, loadInfo, pipe.outputStream);
+ }
+ });
+
+ return channel;
+ } catch (ex) {
+ return makeDefaultFaviconChannel(uri, loadInfo);
+ }
+ },
+
+ newChannel(uri) {
+ return this.newChannel2(uri, null);
+ },
+
+ allowPort(port, scheme) {
+ return false;
+ },
+
+ classID: Components.ID("{60a1f7c6-4ff9-4a42-84d3-5a185faa6f32}"),
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIProtocolHandler
+ ])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PageIconProtocolHandler]);
diff --git a/components/places/src/PlaceInfo.cpp b/components/places/src/PlaceInfo.cpp
new file mode 100644
index 000000000..760b0e718
--- /dev/null
+++ b/components/places/src/PlaceInfo.cpp
@@ -0,0 +1,137 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "PlaceInfo.h"
+#include "VisitInfo.h"
+#include "nsIURI.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIXPConnect.h"
+#include "mozilla/Services.h"
+#include "jsapi.h"
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// PlaceInfo
+
+PlaceInfo::PlaceInfo(int64_t aId,
+ const nsCString& aGUID,
+ already_AddRefed<nsIURI> aURI,
+ const nsString& aTitle,
+ int64_t aFrecency)
+: mId(aId)
+, mGUID(aGUID)
+, mURI(aURI)
+, mTitle(aTitle)
+, mFrecency(aFrecency)
+, mVisitsAvailable(false)
+{
+ NS_PRECONDITION(mURI, "Must provide a non-null uri!");
+}
+
+PlaceInfo::PlaceInfo(int64_t aId,
+ const nsCString& aGUID,
+ already_AddRefed<nsIURI> aURI,
+ const nsString& aTitle,
+ int64_t aFrecency,
+ const VisitsArray& aVisits)
+: mId(aId)
+, mGUID(aGUID)
+, mURI(aURI)
+, mTitle(aTitle)
+, mFrecency(aFrecency)
+, mVisits(aVisits)
+, mVisitsAvailable(true)
+{
+ NS_PRECONDITION(mURI, "Must provide a non-null uri!");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIPlaceInfo
+
+NS_IMETHODIMP
+PlaceInfo::GetPlaceId(int64_t* _placeId)
+{
+ *_placeId = mId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PlaceInfo::GetGuid(nsACString& _guid)
+{
+ _guid = mGUID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PlaceInfo::GetUri(nsIURI** _uri)
+{
+ NS_ADDREF(*_uri = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PlaceInfo::GetTitle(nsAString& _title)
+{
+ _title = mTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PlaceInfo::GetFrecency(int64_t* _frecency)
+{
+ *_frecency = mFrecency;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PlaceInfo::GetVisits(JSContext* aContext,
+ JS::MutableHandle<JS::Value> _visits)
+{
+ // If the visits data was not provided, return null rather
+ // than an empty array to distinguish this case from the case
+ // of a place without any visit.
+ if (!mVisitsAvailable) {
+ _visits.setNull();
+ return NS_OK;
+ }
+
+ // TODO bug 625913 when we use this in situations that have more than one
+ // visit here, we will likely want to make this cache the value.
+ JS::Rooted<JSObject*> visits(aContext,
+ JS_NewArrayObject(aContext, 0));
+ NS_ENSURE_TRUE(visits, NS_ERROR_OUT_OF_MEMORY);
+
+ JS::Rooted<JSObject*> global(aContext, JS::CurrentGlobalOrNull(aContext));
+ NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIXPConnect> xpc = mozilla::services::GetXPConnect();
+
+ for (VisitsArray::size_type idx = 0; idx < mVisits.Length(); idx++) {
+ JS::RootedObject jsobj(aContext);
+ nsresult rv = xpc->WrapNative(aContext, global, mVisits[idx],
+ NS_GET_IID(mozIVisitInfo),
+ jsobj.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(jsobj);
+
+ bool rc = JS_DefineElement(aContext, visits, idx, jsobj, JSPROP_ENUMERATE);
+ NS_ENSURE_TRUE(rc, NS_ERROR_UNEXPECTED);
+ }
+
+ _visits.setObject(*visits);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+NS_IMPL_ISUPPORTS(
+ PlaceInfo
+, mozIPlaceInfo
+)
+
+} // namespace places
+} // namespace mozilla
diff --git a/components/places/src/PlaceInfo.h b/components/places/src/PlaceInfo.h
new file mode 100644
index 000000000..b1d3c0893
--- /dev/null
+++ b/components/places/src/PlaceInfo.h
@@ -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/. */
+
+#ifndef mozilla_places_PlaceInfo_h__
+#define mozilla_places_PlaceInfo_h__
+
+#include "mozIAsyncHistory.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsIURI;
+class mozIVisitInfo;
+
+namespace mozilla {
+namespace places {
+
+
+class PlaceInfo final : public mozIPlaceInfo
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZIPLACEINFO
+
+ typedef nsTArray< nsCOMPtr<mozIVisitInfo> > VisitsArray;
+
+ PlaceInfo(int64_t aId, const nsCString& aGUID, already_AddRefed<nsIURI> aURI,
+ const nsString& aTitle, int64_t aFrecency);
+ PlaceInfo(int64_t aId, const nsCString& aGUID, already_AddRefed<nsIURI> aURI,
+ const nsString& aTitle, int64_t aFrecency,
+ const VisitsArray& aVisits);
+
+private:
+ ~PlaceInfo() {}
+
+ const int64_t mId;
+ const nsCString mGUID;
+ nsCOMPtr<nsIURI> mURI;
+ const nsString mTitle;
+ const int64_t mFrecency;
+ const VisitsArray mVisits;
+ bool mVisitsAvailable;
+};
+
+} // namespace places
+} // namespace mozilla
+
+#endif // mozilla_places_PlaceInfo_h__
diff --git a/components/places/src/PlacesBackups.jsm b/components/places/src/PlacesBackups.jsm
new file mode 100644
index 000000000..3cf89d530
--- /dev/null
+++ b/components/places/src/PlacesBackups.jsm
@@ -0,0 +1,542 @@
+/* -*- 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/. */
+
+this.EXPORTED_SYMBOLS = ["PlacesBackups"];
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cc = Components.classes;
+
+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/Task.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
+ "resource://gre/modules/BookmarkJSONUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "localFileCtor",
+ () => Components.Constructor("@mozilla.org/file/local;1",
+ "nsILocalFile", "initWithPath"));
+
+XPCOMUtils.defineLazyGetter(this, "filenamesRegex",
+ () => /^bookmarks-([0-9-]+)(?:_([0-9]+)){0,1}(?:_([a-z0-9=+-]{24})){0,1}\.(json(lz4)?)$/i
+);
+
+/**
+ * Appends meta-data information to a given filename.
+ */
+function appendMetaDataToFilename(aFilename, aMetaData) {
+ let matches = aFilename.match(filenamesRegex);
+ return "bookmarks-" + matches[1] +
+ "_" + aMetaData.count +
+ "_" + aMetaData.hash +
+ "." + matches[4];
+}
+
+/**
+ * Gets the hash from a backup filename.
+ *
+ * @return the extracted hash or null.
+ */
+function getHashFromFilename(aFilename) {
+ let matches = aFilename.match(filenamesRegex);
+ if (matches && matches[3])
+ return matches[3];
+ return null;
+}
+
+/**
+ * Given two filenames, checks if they contain the same date.
+ */
+function isFilenameWithSameDate(aSourceName, aTargetName) {
+ let sourceMatches = aSourceName.match(filenamesRegex);
+ let targetMatches = aTargetName.match(filenamesRegex);
+
+ return sourceMatches && targetMatches &&
+ sourceMatches[1] == targetMatches[1];
+}
+
+/**
+ * Given a filename, searches for another backup with the same date.
+ *
+ * @return OS.File path string or null.
+ */
+function getBackupFileForSameDate(aFilename) {
+ return Task.spawn(function* () {
+ let backupFiles = yield PlacesBackups.getBackupFiles();
+ for (let backupFile of backupFiles) {
+ if (isFilenameWithSameDate(OS.Path.basename(backupFile), aFilename))
+ return backupFile;
+ }
+ return null;
+ });
+}
+
+this.PlacesBackups = {
+ /**
+ * Matches the backup filename:
+ * 0: file name
+ * 1: date in form Y-m-d
+ * 2: bookmarks count
+ * 3: contents hash
+ * 4: file extension
+ */
+ get filenamesRegex() {
+ return filenamesRegex;
+ },
+
+ get folder() {
+ Deprecated.warning(
+ "PlacesBackups.folder is deprecated and will be removed in a future version",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=859695");
+ return this._folder;
+ },
+
+ /**
+ * This exists just to avoid spamming deprecate warnings from internal calls
+ * needed to support deprecated methods themselves.
+ */
+ get _folder() {
+ let bookmarksBackupDir = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
+ bookmarksBackupDir.append(this.profileRelativeFolderPath);
+ if (!bookmarksBackupDir.exists()) {
+ bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0700", 8));
+ if (!bookmarksBackupDir.exists())
+ throw ("Unable to create bookmarks backup folder");
+ }
+ delete this._folder;
+ return this._folder = bookmarksBackupDir;
+ },
+
+ /**
+ * Gets backup folder asynchronously.
+ * @return {Promise}
+ * @resolve the folder (the folder string path).
+ */
+ getBackupFolder: function PB_getBackupFolder() {
+ return Task.spawn(function* () {
+ if (this._backupFolder) {
+ return this._backupFolder;
+ }
+ let profileDir = OS.Constants.Path.profileDir;
+ let backupsDirPath = OS.Path.join(profileDir, this.profileRelativeFolderPath);
+ yield OS.File.makeDir(backupsDirPath, { ignoreExisting: true });
+ return this._backupFolder = backupsDirPath;
+ }.bind(this));
+ },
+
+ get profileRelativeFolderPath() {
+ return "bookmarkbackups";
+ },
+
+ /**
+ * Cache current backups in a sorted (by date DESC) array.
+ */
+ get entries() {
+ Deprecated.warning(
+ "PlacesBackups.entries is deprecated and will be removed in a future version",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=859695");
+ return this._entries;
+ },
+
+ /**
+ * This exists just to avoid spamming deprecate warnings from internal calls
+ * needed to support deprecated methods themselves.
+ */
+ get _entries() {
+ delete this._entries;
+ this._entries = [];
+ let files = this._folder.directoryEntries;
+ while (files.hasMoreElements()) {
+ let entry = files.getNext().QueryInterface(Ci.nsIFile);
+ // A valid backup is any file that matches either the localized or
+ // not-localized filename (bug 445704).
+ if (!entry.isHidden() && filenamesRegex.test(entry.leafName)) {
+ // Remove bogus backups in future dates.
+ if (this.getDateForFile(entry) > new Date()) {
+ entry.remove(false);
+ continue;
+ }
+ this._entries.push(entry);
+ }
+ }
+ this._entries.sort((a, b) => {
+ let aDate = this.getDateForFile(a);
+ let bDate = this.getDateForFile(b);
+ return bDate - aDate;
+ });
+ return this._entries;
+ },
+
+ /**
+ * Cache current backups in a sorted (by date DESC) array.
+ * @return {Promise}
+ * @resolve a sorted array of string paths.
+ */
+ getBackupFiles: function PB_getBackupFiles() {
+ return Task.spawn(function* () {
+ if (this._backupFiles)
+ return this._backupFiles;
+
+ this._backupFiles = [];
+
+ let backupFolderPath = yield this.getBackupFolder();
+ let iterator = new OS.File.DirectoryIterator(backupFolderPath);
+ yield iterator.forEach(function(aEntry) {
+ // Since this is a lazy getter and OS.File I/O is serialized, we can
+ // safely remove .tmp files without risking to remove ongoing backups.
+ if (aEntry.name.endsWith(".tmp")) {
+ OS.File.remove(aEntry.path);
+ return undefined;
+ }
+
+ if (filenamesRegex.test(aEntry.name)) {
+ // Remove bogus backups in future dates.
+ let filePath = aEntry.path;
+ if (this.getDateForFile(filePath) > new Date()) {
+ return OS.File.remove(filePath);
+ }
+ this._backupFiles.push(filePath);
+ }
+
+ return undefined;
+ }.bind(this));
+ iterator.close();
+
+ this._backupFiles.sort((a, b) => {
+ let aDate = this.getDateForFile(a);
+ let bDate = this.getDateForFile(b);
+ return bDate - aDate;
+ });
+
+ return this._backupFiles;
+ }.bind(this));
+ },
+
+ /**
+ * Generates a ISO date string (YYYY-MM-DD) from a Date object.
+ *
+ * @param dateObj
+ * The date object to parse.
+ * @return an ISO date string.
+ */
+ toISODateString: function toISODateString(dateObj) {
+ if (!dateObj || dateObj.constructor.name != "Date" || !dateObj.getTime())
+ throw new Error("invalid date object");
+ let padDate = val => ("0" + val).substr(-2, 2);
+ return [
+ dateObj.getFullYear(),
+ padDate(dateObj.getMonth() + 1),
+ padDate(dateObj.getDate())
+ ].join("-");
+ },
+
+ /**
+ * Creates a filename for bookmarks backup files.
+ *
+ * @param [optional] aDateObj
+ * Date object used to build the filename.
+ * Will use current date if empty.
+ * @param [optional] bool - aCompress
+ * Determines if file extension is json or jsonlz4
+ Default is json
+ * @return A bookmarks backup filename.
+ */
+ getFilenameForDate: function PB_getFilenameForDate(aDateObj, aCompress) {
+ let dateObj = aDateObj || new Date();
+ // Use YYYY-MM-DD (ISO 8601) as it doesn't contain illegal characters
+ // and makes the alphabetical order of multiple backup files more useful.
+ return "bookmarks-" + PlacesBackups.toISODateString(dateObj) + ".json" +
+ (aCompress ? "lz4" : "");
+ },
+
+ /**
+ * Creates a Date object from a backup file. The date is the backup
+ * creation date.
+ *
+ * @param aBackupFile
+ * nsIFile or string path of the backup.
+ * @return A Date object for the backup's creation time.
+ */
+ getDateForFile: function PB_getDateForFile(aBackupFile) {
+ let filename = (aBackupFile instanceof Ci.nsIFile) ? aBackupFile.leafName
+ : OS.Path.basename(aBackupFile);
+ let matches = filename.match(filenamesRegex);
+ if (!matches)
+ throw ("Invalid backup file name: " + filename);
+ return new Date(matches[1].replace(/-/g, "/"));
+ },
+
+ /**
+ * Get the most recent backup file.
+ *
+ * @returns nsIFile backup file
+ */
+ getMostRecent: function PB_getMostRecent() {
+ Deprecated.warning(
+ "PlacesBackups.getMostRecent is deprecated and will be removed in a future version",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=859695");
+
+ for (let i = 0; i < this._entries.length; i++) {
+ let rx = /\.json(lz4)?$/;
+ if (this._entries[i].leafName.match(rx))
+ return this._entries[i];
+ }
+ return null;
+ },
+
+ /**
+ * Get the most recent backup file.
+ *
+ * @return {Promise}
+ * @result the path to the file.
+ */
+ getMostRecentBackup: function PB_getMostRecentBackup() {
+ return Task.spawn(function* () {
+ let entries = yield this.getBackupFiles();
+ for (let entry of entries) {
+ let rx = /\.json(lz4)?$/;
+ if (OS.Path.basename(entry).match(rx)) {
+ return entry;
+ }
+ }
+ return null;
+ }.bind(this));
+ },
+
+ /**
+ * Serializes bookmarks using JSON, and writes to the supplied file.
+ * Note: any item that should not be backed up must be annotated with
+ * "places/excludeFromBackup".
+ *
+ * @param aFilePath
+ * OS.File path for the "bookmarks.json" file to be created.
+ * @return {Promise}
+ * @resolves the number of serialized uri nodes.
+ * @deprecated passing an nsIFile is deprecated
+ */
+ saveBookmarksToJSONFile: function PB_saveBookmarksToJSONFile(aFilePath) {
+ if (aFilePath instanceof Ci.nsIFile) {
+ Deprecated.warning("Passing an nsIFile to PlacesBackups.saveBookmarksToJSONFile " +
+ "is deprecated. Please use an OS.File path instead.",
+ "https://developer.mozilla.org/docs/JavaScript_OS.File");
+ aFilePath = aFilePath.path;
+ }
+ return Task.spawn(function* () {
+ let { count: nodeCount, hash: hash } =
+ yield BookmarkJSONUtils.exportToFile(aFilePath);
+
+ let backupFolderPath = yield this.getBackupFolder();
+ if (OS.Path.dirname(aFilePath) == backupFolderPath) {
+ // We are creating a backup in the default backups folder,
+ // so just update the internal cache.
+ this._entries.unshift(new localFileCtor(aFilePath));
+ if (!this._backupFiles) {
+ yield this.getBackupFiles();
+ }
+ this._backupFiles.unshift(aFilePath);
+ } else {
+ // If we are saving to a folder different than our backups folder, then
+ // we also want to create a new compressed version in it.
+ // This way we ensure the latest valid backup is the same saved by the
+ // user. See bug 424389.
+ let mostRecentBackupFile = yield this.getMostRecentBackup();
+ if (!mostRecentBackupFile ||
+ hash != getHashFromFilename(OS.Path.basename(mostRecentBackupFile))) {
+ let name = this.getFilenameForDate(undefined, true);
+ let newFilename = appendMetaDataToFilename(name,
+ { count: nodeCount,
+ hash: hash });
+ let newFilePath = OS.Path.join(backupFolderPath, newFilename);
+ let backupFile = yield getBackupFileForSameDate(name);
+ if (backupFile) {
+ // There is already a backup for today, replace it.
+ yield OS.File.remove(backupFile, { ignoreAbsent: true });
+ if (!this._backupFiles)
+ yield this.getBackupFiles();
+ else
+ this._backupFiles.shift();
+ this._backupFiles.unshift(newFilePath);
+ } else {
+ // There is no backup for today, add the new one.
+ this._entries.unshift(new localFileCtor(newFilePath));
+ if (!this._backupFiles)
+ yield this.getBackupFiles();
+ this._backupFiles.unshift(newFilePath);
+ }
+ let jsonString = yield OS.File.read(aFilePath);
+ yield OS.File.writeAtomic(newFilePath, jsonString, { compression: "lz4" });
+ }
+ }
+
+ return nodeCount;
+ }.bind(this));
+ },
+
+ /**
+ * Creates a dated backup in <profile>/bookmarkbackups.
+ * Stores the bookmarks using a lz4 compressed JSON file.
+ * Note: any item that should not be backed up must be annotated with
+ * "places/excludeFromBackup".
+ *
+ * @param [optional] int aMaxBackups
+ * The maximum number of backups to keep. If set to 0
+ * all existing backups are removed and aForceBackup is
+ * ignored, so a new one won't be created.
+ * @param [optional] bool aForceBackup
+ * Forces creating a backup even if one was already
+ * created that day (overwrites).
+ * @return {Promise}
+ */
+ create: function PB_create(aMaxBackups, aForceBackup) {
+ let limitBackups = function* () {
+ let backupFiles = yield this.getBackupFiles();
+ if (typeof aMaxBackups == "number" && aMaxBackups > -1 &&
+ backupFiles.length >= aMaxBackups) {
+ let numberOfBackupsToDelete = backupFiles.length - aMaxBackups;
+ while (numberOfBackupsToDelete--) {
+ this._entries.pop();
+ let oldestBackup = this._backupFiles.pop();
+ yield OS.File.remove(oldestBackup);
+ }
+ }
+ }.bind(this);
+
+ return Task.spawn(function* () {
+ if (aMaxBackups === 0) {
+ // Backups are disabled, delete any existing one and bail out.
+ yield limitBackups(0);
+ return;
+ }
+
+ // Ensure to initialize _backupFiles
+ if (!this._backupFiles)
+ yield this.getBackupFiles();
+ let newBackupFilename = this.getFilenameForDate(undefined, true);
+ // If we already have a backup for today we should do nothing, unless we
+ // were required to enforce a new backup.
+ let backupFile = yield getBackupFileForSameDate(newBackupFilename);
+ if (backupFile && !aForceBackup)
+ return;
+
+ if (backupFile) {
+ // In case there is a backup for today we should recreate it.
+ this._backupFiles.shift();
+ this._entries.shift();
+ yield OS.File.remove(backupFile, { ignoreAbsent: true });
+ }
+
+ // Now check the hash of the most recent backup, and try to create a new
+ // backup, if that fails due to hash conflict, just rename the old backup.
+ let mostRecentBackupFile = yield this.getMostRecentBackup();
+ let mostRecentHash = mostRecentBackupFile &&
+ getHashFromFilename(OS.Path.basename(mostRecentBackupFile));
+
+ // Save bookmarks to a backup file.
+ let backupFolder = yield this.getBackupFolder();
+ let newBackupFile = OS.Path.join(backupFolder, newBackupFilename);
+ let newFilenameWithMetaData;
+ try {
+ let { count: nodeCount, hash: hash } =
+ yield BookmarkJSONUtils.exportToFile(newBackupFile,
+ { compress: true,
+ failIfHashIs: mostRecentHash });
+ newFilenameWithMetaData = appendMetaDataToFilename(newBackupFilename,
+ { count: nodeCount,
+ hash: hash });
+ } catch (ex) {
+ if (!ex.becauseSameHash) {
+ throw ex;
+ }
+ // The last backup already contained up-to-date information, just
+ // rename it as if it was today's backup.
+ this._backupFiles.shift();
+ this._entries.shift();
+ newBackupFile = mostRecentBackupFile;
+ // Ensure we retain the proper extension when renaming
+ // the most recent backup file.
+ if (/\.json$/.test(OS.Path.basename(mostRecentBackupFile)))
+ newBackupFilename = this.getFilenameForDate();
+ newFilenameWithMetaData = appendMetaDataToFilename(
+ newBackupFilename,
+ { count: this.getBookmarkCountForFile(mostRecentBackupFile),
+ hash: mostRecentHash });
+ }
+
+ // Append metadata to the backup filename.
+ let newBackupFileWithMetadata = OS.Path.join(backupFolder, newFilenameWithMetaData);
+ yield OS.File.move(newBackupFile, newBackupFileWithMetadata);
+ this._entries.unshift(new localFileCtor(newBackupFileWithMetadata));
+ this._backupFiles.unshift(newBackupFileWithMetadata);
+
+ // Limit the number of backups.
+ yield limitBackups(aMaxBackups);
+ }.bind(this));
+ },
+
+ /**
+ * Gets the bookmark count for backup file.
+ *
+ * @param aFilePath
+ * File path The backup file.
+ *
+ * @return the bookmark count or null.
+ */
+ getBookmarkCountForFile: function PB_getBookmarkCountForFile(aFilePath) {
+ let count = null;
+ let filename = OS.Path.basename(aFilePath);
+ let matches = filename.match(filenamesRegex);
+ if (matches && matches[2])
+ count = matches[2];
+ return count;
+ },
+
+ /**
+ * Gets a bookmarks tree representation usable to create backups in different
+ * file formats. The root or the tree is PlacesUtils.placesRootId.
+ * Items annotated with PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO and all of their
+ * descendants are excluded.
+ *
+ * @return an object representing a tree with the places root as its root.
+ * Each bookmark is represented by an object having these properties:
+ * * id: the item id (make this not enumerable after bug 824502)
+ * * title: the title
+ * * guid: unique id
+ * * parent: item id of the parent folder, not enumerable
+ * * index: the position in the parent
+ * * dateAdded: microseconds from the epoch
+ * * lastModified: microseconds from the epoch
+ * * type: type of the originating node as defined in PlacesUtils
+ * The following properties exist only for a subset of bookmarks:
+ * * annos: array of annotations
+ * * uri: url
+ * * iconuri: favicon's url
+ * * keyword: associated keyword
+ * * charset: last known charset
+ * * tags: csv string of tags
+ * * root: string describing whether this represents a root
+ * * children: array of child items in a folder
+ */
+ getBookmarksTree: Task.async(function* () {
+ let startTime = Date.now();
+ let root = yield PlacesUtils.promiseBookmarksTree(PlacesUtils.bookmarks.rootGuid, {
+ excludeItemsCallback: aItem => {
+ return aItem.annos &&
+ aItem.annos.find(a => a.name == PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO);
+ },
+ includeItemIds: true
+ });
+
+ return [root, root.itemsCount];
+ })
+}
+
diff --git a/components/places/src/PlacesCategoriesStarter.js b/components/places/src/PlacesCategoriesStarter.js
new file mode 100644
index 000000000..dd0ff2a25
--- /dev/null
+++ b/components/places/src/PlacesCategoriesStarter.js
@@ -0,0 +1,98 @@
+/* -*- 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/. */
+
+// Constants
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+// Seconds between maintenance runs.
+const MAINTENANCE_INTERVAL_SECONDS = 7 * 86400;
+
+// Imports
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils",
+ "resource://gre/modules/PlacesDBUtils.jsm");
+
+/**
+ * This component can be used as a starter for modules that have to run when
+ * certain categories are invoked.
+ */
+function PlacesCategoriesStarter()
+{
+ Services.obs.addObserver(this, PlacesUtils.TOPIC_SHUTDOWN, false);
+
+ // nsINavBookmarkObserver implementation.
+ let notify = () => {
+ if (!this._notifiedBookmarksSvcReady) {
+ // TODO (bug 1145424): for whatever reason, even if we remove this
+ // component from the category (and thus from the category cache we use
+ // to notify), we keep being notified.
+ this._notifiedBookmarksSvcReady = true;
+ // For perf reasons unregister from the category, since no further
+ // notifications are needed.
+ Cc["@mozilla.org/categorymanager;1"]
+ .getService(Ci.nsICategoryManager)
+ .deleteCategoryEntry("bookmark-observers", "PlacesCategoriesStarter", false);
+ // Directly notify PlacesUtils, to ensure it catches the notification.
+ PlacesUtils.observe(null, "bookmarks-service-ready", null);
+ }
+ };
+
+ [ "onItemAdded", "onItemRemoved", "onItemChanged", "onBeginUpdateBatch",
+ "onEndUpdateBatch", "onItemVisited", "onItemMoved"
+ ].forEach(aMethod => this[aMethod] = notify);
+}
+
+PlacesCategoriesStarter.prototype = {
+ // nsIObserver
+
+ observe: function PCS_observe(aSubject, aTopic, aData)
+ {
+ switch (aTopic) {
+ case PlacesUtils.TOPIC_SHUTDOWN:
+ Services.obs.removeObserver(this, PlacesUtils.TOPIC_SHUTDOWN);
+ let globalObj =
+ Cu.getGlobalForObject(PlacesCategoriesStarter.prototype);
+ let descriptor =
+ Object.getOwnPropertyDescriptor(globalObj, "PlacesDBUtils");
+ if (descriptor.value !== undefined) {
+ PlacesDBUtils.shutdown();
+ }
+ break;
+ case "idle-daily":
+ // Once a week run places.sqlite maintenance tasks.
+ let lastMaintenance =
+ Services.prefs.getIntPref("places.database.lastMaintenance", 0);
+ let nowSeconds = parseInt(Date.now() / 1000);
+ if (lastMaintenance < nowSeconds - MAINTENANCE_INTERVAL_SECONDS) {
+ PlacesDBUtils.maintenanceOnIdle();
+ }
+ break;
+ default:
+ throw new Error("Trying to handle an unknown category.");
+ }
+ },
+
+ // nsISupports
+
+ classID: Components.ID("803938d5-e26d-4453-bf46-ad4b26e41114"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(PlacesCategoriesStarter),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver
+ , Ci.nsINavBookmarkObserver
+ ])
+};
+
+// Module Registration
+
+var components = [PlacesCategoriesStarter];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/components/places/src/PlacesDBUtils.jsm b/components/places/src/PlacesDBUtils.jsm
new file mode 100644
index 000000000..cfb38db70
--- /dev/null
+++ b/components/places/src/PlacesDBUtils.jsm
@@ -0,0 +1,952 @@
+/* -*- 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/Services.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+this.EXPORTED_SYMBOLS = [ "PlacesDBUtils" ];
+
+// Constants
+
+const FINISHED_MAINTENANCE_TOPIC = "places-maintenance-finished";
+
+const BYTES_PER_MEBIBYTE = 1048576;
+
+// Smart getters
+
+XPCOMUtils.defineLazyGetter(this, "DBConn", function() {
+ return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
+});
+
+// PlacesDBUtils
+
+this.PlacesDBUtils = {
+ /**
+ * Executes a list of maintenance tasks.
+ * Once finished it will pass a array log to the callback attached to tasks.
+ * FINISHED_MAINTENANCE_TOPIC is notified through observer service on finish.
+ *
+ * @param aTasks
+ * Tasks object to execute.
+ */
+ _executeTasks: function PDBU__executeTasks(aTasks)
+ {
+ if (PlacesDBUtils._isShuttingDown) {
+ aTasks.log("- We are shutting down. Will not schedule the tasks.");
+ aTasks.clear();
+ }
+
+ let task = aTasks.pop();
+ if (task) {
+ task.call(PlacesDBUtils, aTasks);
+ }
+ else {
+ // All tasks have been completed.
+ if (aTasks.callback) {
+ let scope = aTasks.scope || Cu.getGlobalForObject(aTasks.callback);
+ aTasks.callback.call(scope, aTasks.messages);
+ }
+
+ // Notify observers that maintenance finished.
+ Services.obs.notifyObservers(null, FINISHED_MAINTENANCE_TOPIC, null);
+ }
+ },
+
+ _isShuttingDown : false,
+ shutdown: function PDBU_shutdown() {
+ PlacesDBUtils._isShuttingDown = true;
+ },
+
+ /**
+ * Executes integrity check and common maintenance tasks.
+ *
+ * @param [optional] aCallback
+ * Callback to be invoked when done. The callback will get a array
+ * of log messages.
+ * @param [optional] aScope
+ * Scope for the callback.
+ */
+ maintenanceOnIdle: function PDBU_maintenanceOnIdle(aCallback, aScope)
+ {
+ let tasks = new Tasks([
+ this.checkIntegrity
+ , this.checkCoherence
+ , this._refreshUI
+ ]);
+ tasks.callback = function() {
+ Services.prefs.setIntPref("places.database.lastMaintenance",
+ parseInt(Date.now() / 1000));
+ if (aCallback)
+ aCallback();
+ }
+ tasks.scope = aScope;
+ this._executeTasks(tasks);
+ },
+
+ /**
+ * Executes integrity check, common and advanced maintenance tasks (like
+ * expiration and vacuum). Will also collect statistics on the database.
+ *
+ * @param [optional] aCallback
+ * Callback to be invoked when done. The callback will get a array
+ * of log messages.
+ * @param [optional] aScope
+ * Scope for the callback.
+ */
+ checkAndFixDatabase: function PDBU_checkAndFixDatabase(aCallback, aScope)
+ {
+ let tasks = new Tasks([
+ this.checkIntegrity
+ , this.checkCoherence
+ , this.expire
+ , this.vacuum
+ , this.stats
+ , this._refreshUI
+ ]);
+ tasks.callback = aCallback;
+ tasks.scope = aScope;
+ this._executeTasks(tasks);
+ },
+
+ /**
+ * Forces a full refresh of Places views.
+ *
+ * @param [optional] aTasks
+ * Tasks object to execute.
+ */
+ _refreshUI: function PDBU__refreshUI(aTasks)
+ {
+ let tasks = new Tasks(aTasks);
+
+ // Send batch update notifications to update the UI.
+ PlacesUtils.history.runInBatchMode({
+ runBatched: function (aUserData) {}
+ }, null);
+ PlacesDBUtils._executeTasks(tasks);
+ },
+
+ _handleError: function PDBU__handleError(aError)
+ {
+ Cu.reportError("Async statement execution returned with '" +
+ aError.result + "', '" + aError.message + "'");
+ },
+
+ /**
+ * Tries to execute a REINDEX on the database.
+ *
+ * @param [optional] aTasks
+ * Tasks object to execute.
+ */
+ reindex: function PDBU_reindex(aTasks)
+ {
+ let tasks = new Tasks(aTasks);
+ tasks.log("> Reindex");
+
+ let stmt = DBConn.createAsyncStatement("REINDEX");
+ stmt.executeAsync({
+ handleError: PlacesDBUtils._handleError,
+ handleResult: function () {},
+
+ handleCompletion: function (aReason)
+ {
+ if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ tasks.log("+ The database has been reindexed");
+ }
+ else {
+ tasks.log("- Unable to reindex database");
+ }
+
+ PlacesDBUtils._executeTasks(tasks);
+ }
+ });
+ stmt.finalize();
+ },
+
+ /**
+ * Checks integrity but does not try to fix the database through a reindex.
+ *
+ * @param [optional] aTasks
+ * Tasks object to execute.
+ */
+ _checkIntegritySkipReindex: function PDBU__checkIntegritySkipReindex(aTasks) {
+ return this.checkIntegrity(aTasks, true);
+ },
+
+ /**
+ * Checks integrity and tries to fix the database through a reindex.
+ *
+ * @param [optional] aTasks
+ * Tasks object to execute.
+ * @param [optional] aSkipdReindex
+ * Whether to try to reindex database or not.
+ */
+ checkIntegrity: function PDBU_checkIntegrity(aTasks, aSkipReindex)
+ {
+ let tasks = new Tasks(aTasks);
+ tasks.log("> Integrity check");
+
+ // Run a integrity check, but stop at the first error.
+ let stmt = DBConn.createAsyncStatement("PRAGMA integrity_check(1)");
+ stmt.executeAsync({
+ handleError: PlacesDBUtils._handleError,
+
+ _corrupt: false,
+ handleResult: function (aResultSet)
+ {
+ let row = aResultSet.getNextRow();
+ this._corrupt = row.getResultByIndex(0) != "ok";
+ },
+
+ handleCompletion: function (aReason)
+ {
+ if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ if (this._corrupt) {
+ tasks.log("- The database is corrupt");
+ if (aSkipReindex) {
+ tasks.log("- Unable to fix corruption, database will be replaced on next startup");
+ Services.prefs.setBoolPref("places.database.replaceOnStartup", true);
+ tasks.clear();
+ }
+ else {
+ // Try to reindex, this often fixed simple indices corruption.
+ // We insert from the top of the queue, they will run inverse.
+ tasks.push(PlacesDBUtils._checkIntegritySkipReindex);
+ tasks.push(PlacesDBUtils.reindex);
+ }
+ }
+ else {
+ tasks.log("+ The database is sane");
+ }
+ }
+ else {
+ tasks.log("- Unable to check database status");
+ tasks.clear();
+ }
+
+ PlacesDBUtils._executeTasks(tasks);
+ }
+ });
+ stmt.finalize();
+ },
+
+ /**
+ * Checks data coherence and tries to fix most common errors.
+ *
+ * @param [optional] aTasks
+ * Tasks object to execute.
+ */
+ checkCoherence: function PDBU_checkCoherence(aTasks)
+ {
+ let tasks = new Tasks(aTasks);
+ tasks.log("> Coherence check");
+
+ let stmts = PlacesDBUtils._getBoundCoherenceStatements();
+ DBConn.executeAsync(stmts, stmts.length, {
+ handleError: PlacesDBUtils._handleError,
+ handleResult: function () {},
+
+ handleCompletion: function (aReason)
+ {
+ if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ tasks.log("+ The database is coherent");
+ }
+ else {
+ tasks.log("- Unable to check database coherence");
+ tasks.clear();
+ }
+
+ PlacesDBUtils._executeTasks(tasks);
+ }
+ });
+ stmts.forEach(aStmt => aStmt.finalize());
+ },
+
+ _getBoundCoherenceStatements: function PDBU__getBoundCoherenceStatements()
+ {
+ let cleanupStatements = [];
+
+ // MOZ_ANNO_ATTRIBUTES
+ // A.1 remove obsolete annotations from moz_annos.
+ // The 'weave0' idiom exploits character ordering (0 follows /) to
+ // efficiently select all annos with a 'weave/' prefix.
+ let deleteObsoleteAnnos = DBConn.createAsyncStatement(
+ `DELETE FROM moz_annos
+ WHERE type = 4
+ OR anno_attribute_id IN (
+ SELECT id FROM moz_anno_attributes
+ WHERE name BETWEEN 'weave/' AND 'weave0'
+ )`);
+ cleanupStatements.push(deleteObsoleteAnnos);
+
+ // A.2 remove obsolete annotations from moz_items_annos.
+ let deleteObsoleteItemsAnnos = DBConn.createAsyncStatement(
+ `DELETE FROM moz_items_annos
+ WHERE type = 4
+ OR anno_attribute_id IN (
+ SELECT id FROM moz_anno_attributes
+ WHERE name = 'sync/children'
+ OR name = 'placesInternal/GUID'
+ OR name BETWEEN 'weave/' AND 'weave0'
+ )`);
+ cleanupStatements.push(deleteObsoleteItemsAnnos);
+
+ // A.3 remove unused attributes.
+ let deleteUnusedAnnoAttributes = DBConn.createAsyncStatement(
+ `DELETE FROM moz_anno_attributes WHERE id IN (
+ SELECT id FROM moz_anno_attributes n
+ WHERE NOT EXISTS
+ (SELECT id FROM moz_annos WHERE anno_attribute_id = n.id LIMIT 1)
+ AND NOT EXISTS
+ (SELECT id FROM moz_items_annos WHERE anno_attribute_id = n.id LIMIT 1)
+ )`);
+ cleanupStatements.push(deleteUnusedAnnoAttributes);
+
+ // MOZ_ANNOS
+ // B.1 remove annos with an invalid attribute
+ let deleteInvalidAttributeAnnos = DBConn.createAsyncStatement(
+ `DELETE FROM moz_annos WHERE id IN (
+ SELECT id FROM moz_annos a
+ WHERE NOT EXISTS
+ (SELECT id FROM moz_anno_attributes
+ WHERE id = a.anno_attribute_id LIMIT 1)
+ )`);
+ cleanupStatements.push(deleteInvalidAttributeAnnos);
+
+ // B.2 remove orphan annos
+ let deleteOrphanAnnos = DBConn.createAsyncStatement(
+ `DELETE FROM moz_annos WHERE id IN (
+ SELECT id FROM moz_annos a
+ WHERE NOT EXISTS
+ (SELECT id FROM moz_places WHERE id = a.place_id LIMIT 1)
+ )`);
+ cleanupStatements.push(deleteOrphanAnnos);
+
+ // Bookmarks roots
+ // C.1 fix missing Places root
+ // Bug 477739 shows a case where the root could be wrongly removed
+ // due to an endianness issue. We try to fix broken roots here.
+ let selectPlacesRoot = DBConn.createStatement(
+ "SELECT id FROM moz_bookmarks WHERE id = :places_root");
+ selectPlacesRoot.params["places_root"] = PlacesUtils.placesRootId;
+ if (!selectPlacesRoot.executeStep()) {
+ // We are missing the root, try to recreate it.
+ let createPlacesRoot = DBConn.createAsyncStatement(
+ `INSERT INTO moz_bookmarks (id, type, fk, parent, position, title,
+ guid)
+ VALUES (:places_root, 2, NULL, 0, 0, :title, :guid)`);
+ createPlacesRoot.params["places_root"] = PlacesUtils.placesRootId;
+ createPlacesRoot.params["title"] = "";
+ createPlacesRoot.params["guid"] = PlacesUtils.bookmarks.rootGuid;
+ cleanupStatements.push(createPlacesRoot);
+
+ // Now ensure that other roots are children of Places root.
+ let fixPlacesRootChildren = DBConn.createAsyncStatement(
+ `UPDATE moz_bookmarks SET parent = :places_root WHERE guid IN
+ ( :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid )`);
+ fixPlacesRootChildren.params["places_root"] = PlacesUtils.placesRootId;
+ fixPlacesRootChildren.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
+ fixPlacesRootChildren.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
+ fixPlacesRootChildren.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
+ fixPlacesRootChildren.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
+ cleanupStatements.push(fixPlacesRootChildren);
+ }
+ selectPlacesRoot.finalize();
+
+ // C.2 fix roots titles
+ // some alpha version has wrong roots title, and this also fixes them if
+ // locale has changed.
+ let updateRootTitleSql = `UPDATE moz_bookmarks SET title = :title
+ WHERE id = :root_id AND title <> :title`;
+ // root
+ let fixPlacesRootTitle = DBConn.createAsyncStatement(updateRootTitleSql);
+ fixPlacesRootTitle.params["root_id"] = PlacesUtils.placesRootId;
+ fixPlacesRootTitle.params["title"] = "";
+ cleanupStatements.push(fixPlacesRootTitle);
+ // bookmarks menu
+ let fixBookmarksMenuTitle = DBConn.createAsyncStatement(updateRootTitleSql);
+ fixBookmarksMenuTitle.params["root_id"] = PlacesUtils.bookmarksMenuFolderId;
+ fixBookmarksMenuTitle.params["title"] =
+ PlacesUtils.getString("BookmarksMenuFolderTitle");
+ cleanupStatements.push(fixBookmarksMenuTitle);
+ // bookmarks toolbar
+ let fixBookmarksToolbarTitle = DBConn.createAsyncStatement(updateRootTitleSql);
+ fixBookmarksToolbarTitle.params["root_id"] = PlacesUtils.toolbarFolderId;
+ fixBookmarksToolbarTitle.params["title"] =
+ PlacesUtils.getString("BookmarksToolbarFolderTitle");
+ cleanupStatements.push(fixBookmarksToolbarTitle);
+ // unsorted bookmarks
+ let fixUnsortedBookmarksTitle = DBConn.createAsyncStatement(updateRootTitleSql);
+ fixUnsortedBookmarksTitle.params["root_id"] = PlacesUtils.unfiledBookmarksFolderId;
+ fixUnsortedBookmarksTitle.params["title"] =
+ PlacesUtils.getString("OtherBookmarksFolderTitle");
+ cleanupStatements.push(fixUnsortedBookmarksTitle);
+ // tags
+ let fixTagsRootTitle = DBConn.createAsyncStatement(updateRootTitleSql);
+ fixTagsRootTitle.params["root_id"] = PlacesUtils.tagsFolderId;
+ fixTagsRootTitle.params["title"] =
+ PlacesUtils.getString("TagsFolderTitle");
+ cleanupStatements.push(fixTagsRootTitle);
+
+ // MOZ_BOOKMARKS
+ // D.1 remove items without a valid place
+ // if fk IS NULL we fix them in D.7
+ let deleteNoPlaceItems = DBConn.createAsyncStatement(
+ `DELETE FROM moz_bookmarks WHERE guid NOT IN (
+ :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
+ ) AND id IN (
+ SELECT b.id FROM moz_bookmarks b
+ WHERE fk NOT NULL AND b.type = :bookmark_type
+ AND NOT EXISTS (SELECT url FROM moz_places WHERE id = b.fk LIMIT 1)
+ )`);
+ deleteNoPlaceItems.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
+ deleteNoPlaceItems.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
+ deleteNoPlaceItems.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
+ deleteNoPlaceItems.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
+ deleteNoPlaceItems.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
+ deleteNoPlaceItems.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
+ cleanupStatements.push(deleteNoPlaceItems);
+
+ // D.2 remove items that are not uri bookmarks from tag containers
+ let deleteBogusTagChildren = DBConn.createAsyncStatement(
+ `DELETE FROM moz_bookmarks WHERE guid NOT IN (
+ :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
+ ) AND id IN (
+ SELECT b.id FROM moz_bookmarks b
+ WHERE b.parent IN
+ (SELECT id FROM moz_bookmarks WHERE parent = :tags_folder)
+ AND b.type <> :bookmark_type
+ )`);
+ deleteBogusTagChildren.params["tags_folder"] = PlacesUtils.tagsFolderId;
+ deleteBogusTagChildren.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
+ deleteBogusTagChildren.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
+ deleteBogusTagChildren.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
+ deleteBogusTagChildren.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
+ deleteBogusTagChildren.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
+ deleteBogusTagChildren.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
+ cleanupStatements.push(deleteBogusTagChildren);
+
+ // D.3 remove empty tags
+ let deleteEmptyTags = DBConn.createAsyncStatement(
+ `DELETE FROM moz_bookmarks WHERE guid NOT IN (
+ :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
+ ) AND id IN (
+ SELECT b.id FROM moz_bookmarks b
+ WHERE b.id IN
+ (SELECT id FROM moz_bookmarks WHERE parent = :tags_folder)
+ AND NOT EXISTS
+ (SELECT id from moz_bookmarks WHERE parent = b.id LIMIT 1)
+ )`);
+ deleteEmptyTags.params["tags_folder"] = PlacesUtils.tagsFolderId;
+ deleteEmptyTags.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
+ deleteEmptyTags.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
+ deleteEmptyTags.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
+ deleteEmptyTags.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
+ deleteEmptyTags.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
+ cleanupStatements.push(deleteEmptyTags);
+
+ // D.4 move orphan items to unsorted folder
+ let fixOrphanItems = DBConn.createAsyncStatement(
+ `UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE guid NOT IN (
+ :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
+ ) AND id IN (
+ SELECT b.id FROM moz_bookmarks b
+ WHERE NOT EXISTS
+ (SELECT id FROM moz_bookmarks WHERE id = b.parent LIMIT 1)
+ )`);
+ fixOrphanItems.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId;
+ fixOrphanItems.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
+ fixOrphanItems.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
+ fixOrphanItems.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
+ fixOrphanItems.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
+ fixOrphanItems.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
+ cleanupStatements.push(fixOrphanItems);
+
+ // D.6 fix wrong item types
+ // Folders and separators should not have an fk.
+ // If they have a valid fk convert them to bookmarks. Later in D.9 we
+ // will move eventual children to unsorted bookmarks.
+ let fixBookmarksAsFolders = DBConn.createAsyncStatement(
+ `UPDATE moz_bookmarks SET type = :bookmark_type WHERE guid NOT IN (
+ :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
+ ) AND id IN (
+ SELECT id FROM moz_bookmarks b
+ WHERE type IN (:folder_type, :separator_type)
+ AND fk NOTNULL
+ )`);
+ fixBookmarksAsFolders.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
+ fixBookmarksAsFolders.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
+ fixBookmarksAsFolders.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR;
+ fixBookmarksAsFolders.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
+ fixBookmarksAsFolders.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
+ fixBookmarksAsFolders.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
+ fixBookmarksAsFolders.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
+ fixBookmarksAsFolders.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
+ cleanupStatements.push(fixBookmarksAsFolders);
+
+ // D.7 fix wrong item types
+ // Bookmarks should have an fk, if they don't have any, convert them to
+ // folders.
+ let fixFoldersAsBookmarks = DBConn.createAsyncStatement(
+ `UPDATE moz_bookmarks SET type = :folder_type WHERE guid NOT IN (
+ :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
+ ) AND id IN (
+ SELECT id FROM moz_bookmarks b
+ WHERE type = :bookmark_type
+ AND fk IS NULL
+ )`);
+ fixFoldersAsBookmarks.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
+ fixFoldersAsBookmarks.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
+ fixFoldersAsBookmarks.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
+ fixFoldersAsBookmarks.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
+ fixFoldersAsBookmarks.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
+ fixFoldersAsBookmarks.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
+ fixFoldersAsBookmarks.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
+ cleanupStatements.push(fixFoldersAsBookmarks);
+
+ // D.9 fix wrong parents
+ // Items cannot have separators or other bookmarks
+ // as parent, if they have bad parent move them to unsorted bookmarks.
+ let fixInvalidParents = DBConn.createAsyncStatement(
+ `UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE guid NOT IN (
+ :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
+ ) AND id IN (
+ SELECT id FROM moz_bookmarks b
+ WHERE EXISTS
+ (SELECT id FROM moz_bookmarks WHERE id = b.parent
+ AND type IN (:bookmark_type, :separator_type)
+ LIMIT 1)
+ )`);
+ fixInvalidParents.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId;
+ fixInvalidParents.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
+ fixInvalidParents.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR;
+ fixInvalidParents.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
+ fixInvalidParents.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
+ fixInvalidParents.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
+ fixInvalidParents.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
+ fixInvalidParents.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
+ cleanupStatements.push(fixInvalidParents);
+
+ // D.10 recalculate positions
+ // This requires multiple related statements.
+ // We can detect a folder with bad position values comparing the sum of
+ // all distinct position values (+1 since position is 0-based) with the
+ // triangular numbers obtained by the number of children (n).
+ // SUM(DISTINCT position + 1) == (n * (n + 1) / 2).
+ cleanupStatements.push(DBConn.createAsyncStatement(
+ `CREATE TEMP TABLE IF NOT EXISTS moz_bm_reindex_temp (
+ id INTEGER PRIMARY_KEY
+ , parent INTEGER
+ , position INTEGER
+ )`
+ ));
+ cleanupStatements.push(DBConn.createAsyncStatement(
+ `INSERT INTO moz_bm_reindex_temp
+ SELECT id, parent, 0
+ FROM moz_bookmarks b
+ WHERE parent IN (
+ SELECT parent
+ FROM moz_bookmarks
+ GROUP BY parent
+ HAVING (SUM(DISTINCT position + 1) - (count(*) * (count(*) + 1) / 2)) <> 0
+ )
+ ORDER BY parent ASC, position ASC, ROWID ASC`
+ ));
+ cleanupStatements.push(DBConn.createAsyncStatement(
+ `CREATE INDEX IF NOT EXISTS moz_bm_reindex_temp_index
+ ON moz_bm_reindex_temp(parent)`
+ ));
+ cleanupStatements.push(DBConn.createAsyncStatement(
+ `UPDATE moz_bm_reindex_temp SET position = (
+ ROWID - (SELECT MIN(t.ROWID) FROM moz_bm_reindex_temp t
+ WHERE t.parent = moz_bm_reindex_temp.parent)
+ )`
+ ));
+ cleanupStatements.push(DBConn.createAsyncStatement(
+ `CREATE TEMP TRIGGER IF NOT EXISTS moz_bm_reindex_temp_trigger
+ BEFORE DELETE ON moz_bm_reindex_temp
+ FOR EACH ROW
+ BEGIN
+ UPDATE moz_bookmarks SET position = OLD.position WHERE id = OLD.id;
+ END`
+ ));
+ cleanupStatements.push(DBConn.createAsyncStatement(
+ "DELETE FROM moz_bm_reindex_temp "
+ ));
+ cleanupStatements.push(DBConn.createAsyncStatement(
+ "DROP INDEX moz_bm_reindex_temp_index "
+ ));
+ cleanupStatements.push(DBConn.createAsyncStatement(
+ "DROP TRIGGER moz_bm_reindex_temp_trigger "
+ ));
+ cleanupStatements.push(DBConn.createAsyncStatement(
+ "DROP TABLE moz_bm_reindex_temp "
+ ));
+
+ // D.12 Fix empty-named tags.
+ // Tags were allowed to have empty names due to a UI bug. Fix them
+ // replacing their title with "(notitle)".
+ let fixEmptyNamedTags = DBConn.createAsyncStatement(
+ `UPDATE moz_bookmarks SET title = :empty_title
+ WHERE length(title) = 0 AND type = :folder_type
+ AND parent = :tags_folder`
+ );
+ fixEmptyNamedTags.params["empty_title"] = "(notitle)";
+ fixEmptyNamedTags.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
+ fixEmptyNamedTags.params["tags_folder"] = PlacesUtils.tagsFolderId;
+ cleanupStatements.push(fixEmptyNamedTags);
+
+ // MOZ_FAVICONS
+ // E.1 remove orphan icons
+ let deleteOrphanIcons = DBConn.createAsyncStatement(
+ `DELETE FROM moz_favicons WHERE id IN (
+ SELECT id FROM moz_favicons f
+ WHERE NOT EXISTS
+ (SELECT id FROM moz_places WHERE favicon_id = f.id LIMIT 1)
+ )`);
+ cleanupStatements.push(deleteOrphanIcons);
+
+ // MOZ_HISTORYVISITS
+ // F.1 remove orphan visits
+ let deleteOrphanVisits = DBConn.createAsyncStatement(
+ `DELETE FROM moz_historyvisits WHERE id IN (
+ SELECT id FROM moz_historyvisits v
+ WHERE NOT EXISTS
+ (SELECT id FROM moz_places WHERE id = v.place_id LIMIT 1)
+ )`);
+ cleanupStatements.push(deleteOrphanVisits);
+
+ // MOZ_INPUTHISTORY
+ // G.1 remove orphan input history
+ let deleteOrphanInputHistory = DBConn.createAsyncStatement(
+ `DELETE FROM moz_inputhistory WHERE place_id IN (
+ SELECT place_id FROM moz_inputhistory i
+ WHERE NOT EXISTS
+ (SELECT id FROM moz_places WHERE id = i.place_id LIMIT 1)
+ )`);
+ cleanupStatements.push(deleteOrphanInputHistory);
+
+ // MOZ_ITEMS_ANNOS
+ // H.1 remove item annos with an invalid attribute
+ let deleteInvalidAttributeItemsAnnos = DBConn.createAsyncStatement(
+ `DELETE FROM moz_items_annos WHERE id IN (
+ SELECT id FROM moz_items_annos t
+ WHERE NOT EXISTS
+ (SELECT id FROM moz_anno_attributes
+ WHERE id = t.anno_attribute_id LIMIT 1)
+ )`);
+ cleanupStatements.push(deleteInvalidAttributeItemsAnnos);
+
+ // H.2 remove orphan item annos
+ let deleteOrphanItemsAnnos = DBConn.createAsyncStatement(
+ `DELETE FROM moz_items_annos WHERE id IN (
+ SELECT id FROM moz_items_annos t
+ WHERE NOT EXISTS
+ (SELECT id FROM moz_bookmarks WHERE id = t.item_id LIMIT 1)
+ )`);
+ cleanupStatements.push(deleteOrphanItemsAnnos);
+
+ // MOZ_KEYWORDS
+ // I.1 remove unused keywords
+ let deleteUnusedKeywords = DBConn.createAsyncStatement(
+ `DELETE FROM moz_keywords WHERE id IN (
+ SELECT id FROM moz_keywords k
+ WHERE NOT EXISTS
+ (SELECT 1 FROM moz_places h WHERE k.place_id = h.id)
+ )`);
+ cleanupStatements.push(deleteUnusedKeywords);
+
+ // MOZ_PLACES
+ // L.1 fix wrong favicon ids
+ let fixInvalidFaviconIds = DBConn.createAsyncStatement(
+ `UPDATE moz_places SET favicon_id = NULL WHERE id IN (
+ SELECT id FROM moz_places h
+ WHERE favicon_id NOT NULL
+ AND NOT EXISTS
+ (SELECT id FROM moz_favicons WHERE id = h.favicon_id LIMIT 1)
+ )`);
+ cleanupStatements.push(fixInvalidFaviconIds);
+
+ // L.2 recalculate visit_count and last_visit_date
+ let fixVisitStats = DBConn.createAsyncStatement(
+ `UPDATE moz_places
+ SET visit_count = (SELECT count(*) FROM moz_historyvisits
+ WHERE place_id = moz_places.id AND visit_type NOT IN (0,4,7,8,9)),
+ last_visit_date = (SELECT MAX(visit_date) FROM moz_historyvisits
+ WHERE place_id = moz_places.id)
+ WHERE id IN (
+ SELECT h.id FROM moz_places h
+ WHERE visit_count <> (SELECT count(*) FROM moz_historyvisits v
+ WHERE v.place_id = h.id AND visit_type NOT IN (0,4,7,8,9))
+ OR last_visit_date <> (SELECT MAX(visit_date) FROM moz_historyvisits v
+ WHERE v.place_id = h.id)
+ )`);
+ cleanupStatements.push(fixVisitStats);
+
+ // L.3 recalculate hidden for redirects.
+ let fixRedirectsHidden = DBConn.createAsyncStatement(
+ `UPDATE moz_places
+ SET hidden = 1
+ WHERE id IN (
+ SELECT h.id FROM moz_places h
+ JOIN moz_historyvisits src ON src.place_id = h.id
+ JOIN moz_historyvisits dst ON dst.from_visit = src.id AND dst.visit_type IN (5,6)
+ LEFT JOIN moz_bookmarks on fk = h.id AND fk ISNULL
+ GROUP BY src.place_id HAVING count(*) = visit_count
+ )`);
+ cleanupStatements.push(fixRedirectsHidden);
+
+ // L.4 recalculate foreign_count.
+ let fixForeignCount = DBConn.createAsyncStatement(
+ `UPDATE moz_places SET foreign_count =
+ (SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id ) +
+ (SELECT count(*) FROM moz_keywords WHERE place_id = moz_places.id )`);
+ cleanupStatements.push(fixForeignCount);
+
+ // L.5 recalculate missing hashes.
+ let fixMissingHashes = DBConn.createAsyncStatement(
+ `UPDATE moz_places SET url_hash = hash(url) WHERE url_hash = 0`);
+ cleanupStatements.push(fixMissingHashes);
+
+ // MAINTENANCE STATEMENTS SHOULD GO ABOVE THIS POINT!
+
+ return cleanupStatements;
+ },
+
+ /**
+ * Tries to vacuum the database.
+ *
+ * @param [optional] aTasks
+ * Tasks object to execute.
+ */
+ vacuum: function PDBU_vacuum(aTasks)
+ {
+ let tasks = new Tasks(aTasks);
+ tasks.log("> Vacuum");
+
+ let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
+ DBFile.append("places.sqlite");
+ tasks.log("Initial database size is " +
+ parseInt(DBFile.fileSize / 1024) + " KiB");
+
+ let stmt = DBConn.createAsyncStatement("VACUUM");
+ stmt.executeAsync({
+ handleError: PlacesDBUtils._handleError,
+ handleResult: function () {},
+
+ handleCompletion: function (aReason)
+ {
+ if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ tasks.log("+ The database has been vacuumed");
+ let vacuumedDBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
+ vacuumedDBFile.append("places.sqlite");
+ tasks.log("Final database size is " +
+ parseInt(vacuumedDBFile.fileSize / 1024) + " KiB");
+ }
+ else {
+ tasks.log("- Unable to vacuum database");
+ tasks.clear();
+ }
+
+ PlacesDBUtils._executeTasks(tasks);
+ }
+ });
+ stmt.finalize();
+ },
+
+ /**
+ * Forces a full expiration on the database.
+ *
+ * @param [optional] aTasks
+ * Tasks object to execute.
+ */
+ expire: function PDBU_expire(aTasks)
+ {
+ let tasks = new Tasks(aTasks);
+ tasks.log("> Orphans expiration");
+
+ let expiration = Cc["@mozilla.org/places/expiration;1"].
+ getService(Ci.nsIObserver);
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ Services.obs.removeObserver(arguments.callee, aTopic);
+ tasks.log("+ Database cleaned up");
+ PlacesDBUtils._executeTasks(tasks);
+ }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
+
+ // Force an orphans expiration step.
+ expiration.observe(null, "places-debug-start-expiration", 0);
+ },
+
+ /**
+ * Collects statistical data on the database.
+ *
+ * @param [optional] aTasks
+ * Tasks object to execute.
+ */
+ stats: function PDBU_stats(aTasks)
+ {
+ let tasks = new Tasks(aTasks);
+ tasks.log("> Statistics");
+
+ let DBFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
+ DBFile.append("places.sqlite");
+ tasks.log("Database size is " + parseInt(DBFile.fileSize / 1024) + " KiB");
+
+ [ "user_version"
+ , "page_size"
+ , "cache_size"
+ , "journal_mode"
+ , "synchronous"
+ ].forEach(function (aPragma) {
+ let stmt = DBConn.createStatement("PRAGMA " + aPragma);
+ stmt.executeStep();
+ tasks.log(aPragma + " is " + stmt.getString(0));
+ stmt.finalize();
+ });
+
+ // Get maximum number of unique URIs.
+ try {
+ let limitURIs = Services.prefs.getIntPref(
+ "places.history.expiration.transient_current_max_pages");
+ tasks.log("History can store a maximum of " + limitURIs + " unique pages");
+ } catch (ex) {}
+
+ let stmt = DBConn.createStatement(
+ "SELECT name FROM sqlite_master WHERE type = :type");
+ stmt.params.type = "table";
+ while (stmt.executeStep()) {
+ let tableName = stmt.getString(0);
+ let countStmt = DBConn.createStatement(
+ `SELECT count(*) FROM ${tableName}`);
+ countStmt.executeStep();
+ tasks.log("Table " + tableName + " has " + countStmt.getInt32(0) + " records");
+ countStmt.finalize();
+ }
+ stmt.reset();
+
+ stmt.params.type = "index";
+ while (stmt.executeStep()) {
+ tasks.log("Index " + stmt.getString(0));
+ }
+ stmt.reset();
+
+ stmt.params.type = "trigger";
+ while (stmt.executeStep()) {
+ tasks.log("Trigger " + stmt.getString(0));
+ }
+ stmt.finalize();
+
+ PlacesDBUtils._executeTasks(tasks);
+ },
+
+ /**
+ * Runs a list of tasks, notifying log messages to the callback.
+ *
+ * @param aTasks
+ * Array of tasks to be executed, in form of pointers to methods in
+ * this module.
+ * @param [optional] aCallback
+ * Callback to be invoked when done. It will receive an array of
+ * log messages.
+ */
+ runTasks: function PDBU_runTasks(aTasks, aCallback) {
+ let tasks = new Tasks(aTasks);
+ tasks.callback = aCallback;
+ PlacesDBUtils._executeTasks(tasks);
+ }
+};
+
+/**
+ * LIFO tasks stack.
+ *
+ * @param [optional] aTasks
+ * Array of tasks or another Tasks object to clone.
+ */
+function Tasks(aTasks)
+{
+ if (aTasks) {
+ if (Array.isArray(aTasks)) {
+ this._list = aTasks.slice(0, aTasks.length);
+ }
+ // This supports passing in a Tasks-like object, with a "list" property,
+ // for compatibility reasons.
+ else if (typeof(aTasks) == "object" &&
+ (Tasks instanceof Tasks || "list" in aTasks)) {
+ this._list = aTasks.list;
+ this._log = aTasks.messages;
+ this.callback = aTasks.callback;
+ this.scope = aTasks.scope;
+ }
+ }
+}
+
+Tasks.prototype = {
+ _list: [],
+ _log: [],
+ callback: null,
+ scope: null,
+
+ /**
+ * Adds a task to the top of the list.
+ *
+ * @param aNewElt
+ * Task to be added.
+ */
+ push: function T_push(aNewElt)
+ {
+ this._list.unshift(aNewElt);
+ },
+
+ /**
+ * Returns and consumes next task.
+ *
+ * @return next task or undefined if no task is left.
+ */
+ pop: function T_pop()
+ {
+ return this._list.shift();
+ },
+
+ /**
+ * Removes all tasks.
+ */
+ clear: function T_clear()
+ {
+ this._list.length = 0;
+ },
+
+ /**
+ * Returns array of tasks ordered from the next to be run to the latest.
+ */
+ get list()
+ {
+ return this._list.slice(0, this._list.length);
+ },
+
+ /**
+ * Adds a message to the log.
+ *
+ * @param aMsg
+ * String message to be added.
+ */
+ log: function T_log(aMsg)
+ {
+ this._log.push(aMsg);
+ },
+
+ /**
+ * Returns array of log messages ordered from oldest to newest.
+ */
+ get messages()
+ {
+ return this._log.slice(0, this._log.length);
+ },
+}
diff --git a/components/places/src/PlacesRemoteTabsAutocompleteProvider.jsm b/components/places/src/PlacesRemoteTabsAutocompleteProvider.jsm
new file mode 100644
index 000000000..c3bc501eb
--- /dev/null
+++ b/components/places/src/PlacesRemoteTabsAutocompleteProvider.jsm
@@ -0,0 +1,144 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Provides functions to handle remote tabs (ie, tabs known by Sync) in
+ * the awesomebar.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["PlacesRemoteTabsAutocompleteProvider"];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "weaveXPCService", function() {
+ return Cc["@mozilla.org/weave/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject;
+});
+
+XPCOMUtils.defineLazyGetter(this, "Weave", () => {
+ try {
+ let {Weave} = Cu.import("resource://services-sync/main.js", {});
+ return Weave;
+ } catch (ex) {
+ // The app didn't build Sync.
+ }
+ return null;
+});
+
+// from MDN...
+function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+}
+
+// Build the in-memory structure we use.
+function buildItems() {
+ let clients = new Map(); // keyed by client guid, value is client
+ let tabs = new Map(); // keyed by string URL, value is {clientId, tab}
+
+ // If Sync isn't initialized (either due to lag at startup or due to no user
+ // being signed in), don't reach in to Weave.Service as that may initialize
+ // Sync unnecessarily - we'll get an observer notification later when it
+ // becomes ready and has synced a list of tabs.
+ if (weaveXPCService.ready) {
+ let engine = Weave.Service.engineManager.get("tabs");
+
+ for (let [guid, client] of Object.entries(engine.getAllClients())) {
+ clients.set(guid, client);
+ for (let tab of client.tabs) {
+ let url = tab.urlHistory[0];
+ tabs.set(url, { clientId: guid, tab });
+ }
+ }
+ }
+ return { clients, tabs };
+}
+
+// Manage the cache of the items we use.
+// The cache itself.
+let _items = null;
+
+// Ensure the cache is good.
+function ensureItems() {
+ if (!_items) {
+ _items = buildItems();
+ }
+ return _items;
+}
+
+// A preference used to disable the showing of icons in remote tab records.
+const PREF_SHOW_REMOTE_ICONS = "services.sync.syncedTabs.showRemoteIcons";
+let showRemoteIcons;
+
+// An observer to invalidate _items and watch for changed prefs.
+function observe(subject, topic, data) {
+ switch (topic) {
+ case "weave:engine:sync:finish":
+ if (data == "tabs") {
+ // The tabs engine just finished syncing, so may have a different list
+ // of tabs then we previously cached.
+ _items = null;
+ }
+ break;
+
+ case "weave:service:start-over":
+ // Sync is being reset due to the user disconnecting - we must invalidate
+ // the cache so we don't supply tabs from a different user.
+ _items = null;
+ break;
+
+ case "nsPref:changed":
+ if (data == PREF_SHOW_REMOTE_ICONS) {
+ showRemoteIcons = Services.prefs.getBoolPref(PREF_SHOW_REMOTE_ICONS, true);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+Services.obs.addObserver(observe, "weave:engine:sync:finish", false);
+Services.obs.addObserver(observe, "weave:service:start-over", false);
+
+// Observe the pref for showing remote icons and prime our bool that reflects its value.
+Services.prefs.addObserver(PREF_SHOW_REMOTE_ICONS, observe, false);
+observe(null, "nsPref:changed", PREF_SHOW_REMOTE_ICONS);
+
+// This public object is a static singleton.
+this.PlacesRemoteTabsAutocompleteProvider = {
+ // a promise that resolves with an array of matching remote tabs.
+ getMatches(searchString) {
+ // If Sync isn't configured we bail early.
+ if (Weave === null ||
+ !Services.prefs.prefHasUserValue("services.sync.username")) {
+ return Promise.resolve([]);
+ }
+
+ let re = new RegExp(escapeRegExp(searchString), "i");
+ let matches = [];
+ let { tabs, clients } = ensureItems();
+ for (let [url, { clientId, tab }] of tabs) {
+ let title = tab.title;
+ if (url.match(re) || (title && title.match(re))) {
+ // lookup the client record.
+ let client = clients.get(clientId);
+ let icon = showRemoteIcons ? tab.icon : null;
+ // create the record we return for auto-complete.
+ let record = {
+ url, title, icon,
+ deviceClass: Weave.Service.clientsEngine.isMobile(clientId) ? "mobile" : "desktop",
+ deviceName: client.clientName,
+ };
+ matches.push(record);
+ }
+ }
+ return Promise.resolve(matches);
+ },
+}
diff --git a/components/places/src/PlacesSearchAutocompleteProvider.jsm b/components/places/src/PlacesSearchAutocompleteProvider.jsm
new file mode 100644
index 000000000..f4d8f3973
--- /dev/null
+++ b/components/places/src/PlacesSearchAutocompleteProvider.jsm
@@ -0,0 +1,295 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Provides functions to handle search engine URLs in the browser history.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [ "PlacesSearchAutocompleteProvider" ];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SearchSuggestionController",
+ "resource://gre/modules/SearchSuggestionController.jsm");
+
+const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
+
+const SearchAutocompleteProviderInternal = {
+ /**
+ * Array of objects in the format returned by findMatchByToken.
+ */
+ priorityMatches: null,
+
+ /**
+ * Array of objects in the format returned by findMatchByAlias.
+ */
+ aliasMatches: null,
+
+ /**
+ * Object for the default search match.
+ **/
+ defaultMatch: null,
+
+ initialize: function () {
+ return new Promise((resolve, reject) => {
+ Services.search.init(status => {
+ if (!Components.isSuccessCode(status)) {
+ reject(new Error("Unable to initialize search service."));
+ }
+
+ try {
+ // The initial loading of the search engines must succeed.
+ this._refresh();
+
+ Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, true);
+
+ this.initialized = true;
+ resolve();
+ } catch (ex) {
+ reject(ex);
+ }
+ });
+ });
+ },
+
+ initialized: false,
+
+ observe: function (subject, topic, data) {
+ switch (data) {
+ case "engine-added":
+ case "engine-changed":
+ case "engine-removed":
+ case "engine-current":
+ this._refresh();
+ }
+ },
+
+ _refresh: function () {
+ this.priorityMatches = [];
+ this.aliasMatches = [];
+ this.defaultMatch = null;
+
+ let currentEngine = Services.search.currentEngine;
+ // This can be null in XCPShell.
+ if (currentEngine) {
+ this.defaultMatch = {
+ engineName: currentEngine.name,
+ iconUrl: currentEngine.iconURI ? currentEngine.iconURI.spec : null,
+ }
+ }
+
+ // The search engines will always be processed in the order returned by the
+ // search service, which can be defined by the user.
+ Services.search.getVisibleEngines().forEach(e => this._addEngine(e));
+ },
+
+ _addEngine: function (engine) {
+ if (engine.alias) {
+ this.aliasMatches.push({
+ alias: engine.alias,
+ engineName: engine.name,
+ iconUrl: engine.iconURI ? engine.iconURI.spec : null,
+ });
+ }
+
+ let domain = engine.getResultDomain();
+ if (domain) {
+ this.priorityMatches.push({
+ token: domain,
+ // The searchForm property returns a simple URL for the search engine, but
+ // we may need an URL which includes an affiliate code (bug 990799).
+ url: engine.searchForm,
+ engineName: engine.name,
+ iconUrl: engine.iconURI ? engine.iconURI.spec : null,
+ });
+ }
+ },
+
+ getSuggestionController(searchToken, inPrivateContext, maxResults, userContextId) {
+ let engine = Services.search.currentEngine;
+ if (!engine) {
+ return null;
+ }
+ return new SearchSuggestionControllerWrapper(engine, searchToken,
+ inPrivateContext, maxResults,
+ userContextId);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+}
+
+function SearchSuggestionControllerWrapper(engine, searchToken,
+ inPrivateContext, maxResults,
+ userContextId) {
+ this._controller = new SearchSuggestionController();
+ this._controller.maxLocalResults = 0;
+ this._controller.maxRemoteResults = maxResults;
+ let promise = this._controller.fetch(searchToken, inPrivateContext, engine, userContextId);
+ this._suggestions = [];
+ this._success = false;
+ this._promise = promise.then(results => {
+ this._success = true;
+ this._suggestions = (results ? results.remote : null) || [];
+ }).catch(err => {
+ // fetch() rejects its promise if there's a pending request.
+ });
+}
+
+SearchSuggestionControllerWrapper.prototype = {
+
+ /**
+ * Resolved when all suggestions have been fetched.
+ */
+ get fetchCompletePromise() {
+ return this._promise;
+ },
+
+ /**
+ * Returns one suggestion, if any are available. The returned value is an
+ * array [match, suggestion]. If none are available, returns [null, null].
+ * Note that there are two reasons that suggestions might not be available:
+ * all suggestions may have been fetched and consumed, or the fetch may not
+ * have completed yet.
+ *
+ * @return An array [match, suggestion].
+ */
+ consume() {
+ return !this._suggestions.length ? [null, null] :
+ [SearchAutocompleteProviderInternal.defaultMatch,
+ this._suggestions.shift()];
+ },
+
+ /**
+ * Returns the number of fetched suggestions, or -1 if the fetching was
+ * incomplete or failed.
+ */
+ get resultsCount() {
+ return this._success ? this._suggestions.length : -1;
+ },
+
+ /**
+ * Stops the fetch.
+ */
+ stop() {
+ this._controller.stop();
+ },
+};
+
+var gInitializationPromise = null;
+
+this.PlacesSearchAutocompleteProvider = Object.freeze({
+ /**
+ * Starts initializing the component and returns a promise that is resolved or
+ * rejected when initialization finished. The same promise is returned if
+ * this function is called multiple times.
+ */
+ ensureInitialized: function () {
+ if (!gInitializationPromise) {
+ gInitializationPromise = SearchAutocompleteProviderInternal.initialize();
+ }
+ return gInitializationPromise;
+ },
+
+ /**
+ * Matches a given string to an item that should be included by URL search
+ * components, like autocomplete in the address bar.
+ *
+ * @param searchToken
+ * String containing the first part of the matching domain name.
+ *
+ * @return An object with the following properties, or undefined if the token
+ * does not match any relevant URL:
+ * {
+ * token: The full string used to match the search term to the URL.
+ * url: The URL to navigate to if the match is selected.
+ * engineName: The display name of the search engine.
+ * iconUrl: Icon associated to the match, or null if not available.
+ * }
+ */
+ findMatchByToken: Task.async(function* (searchToken) {
+ yield this.ensureInitialized();
+
+ // Match at the beginning for now. In the future, an "options" argument may
+ // allow the matching behavior to be tuned.
+ return SearchAutocompleteProviderInternal.priorityMatches
+ .find(m => m.token.startsWith(searchToken));
+ }),
+
+ /**
+ * Matches a given search string to an item that should be included by
+ * components wishing to search using search engine aliases, like
+ * autocomple.
+ *
+ * @param searchToken
+ * Search string to match exactly a search engine alias.
+ *
+ * @return An object with the following properties, or undefined if the token
+ * does not match any relevant URL:
+ * {
+ * alias: The matched search engine's alias.
+ * engineName: The display name of the search engine.
+ * iconUrl: Icon associated to the match, or null if not available.
+ * }
+ */
+ findMatchByAlias: Task.async(function* (searchToken) {
+ yield this.ensureInitialized();
+
+ return SearchAutocompleteProviderInternal.aliasMatches
+ .find(m => m.alias.toLocaleLowerCase() == searchToken.toLocaleLowerCase());
+ }),
+
+ getDefaultMatch: Task.async(function* () {
+ yield this.ensureInitialized();
+
+ return SearchAutocompleteProviderInternal.defaultMatch;
+ }),
+
+ /**
+ * Synchronously determines if the provided URL represents results from a
+ * search engine, and provides details about the match.
+ *
+ * @param url
+ * String containing the URL to parse.
+ *
+ * @return An object with the following properties, or null if the URL does
+ * not represent a search result:
+ * {
+ * engineName: The display name of the search engine.
+ * terms: The originally sought terms extracted from the URI.
+ * }
+ *
+ * @remarks The asynchronous ensureInitialized function must be called before
+ * this synchronous method can be used.
+ *
+ * @note This API function needs to be synchronous because it is called inside
+ * a row processing callback of Sqlite.jsm, in UnifiedComplete.js.
+ */
+ parseSubmissionURL: function (url) {
+ if (!SearchAutocompleteProviderInternal.initialized) {
+ throw new Error("The component has not been initialized.");
+ }
+
+ let parseUrlResult = Services.search.parseSubmissionURL(url);
+ return parseUrlResult.engine && {
+ engineName: parseUrlResult.engine.name,
+ terms: parseUrlResult.terms,
+ };
+ },
+
+ getSuggestionController(searchToken, inPrivateContext, maxResults, userContextId) {
+ if (!SearchAutocompleteProviderInternal.initialized) {
+ throw new Error("The component has not been initialized.");
+ }
+ return SearchAutocompleteProviderInternal.getSuggestionController(
+ searchToken, inPrivateContext, maxResults, userContextId);
+ },
+});
diff --git a/components/places/src/PlacesSyncUtils.jsm b/components/places/src/PlacesSyncUtils.jsm
new file mode 100644
index 000000000..15dd412e8
--- /dev/null
+++ b/components/places/src/PlacesSyncUtils.jsm
@@ -0,0 +1,1155 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["PlacesSyncUtils"];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.importGlobalProperties(["URL", "URLSearchParams"]);
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Log",
+ "resource://gre/modules/Log.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+ "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+/**
+ * This module exports functions for Sync to use when applying remote
+ * records. The calls are similar to those in `Bookmarks.jsm` and
+ * `nsINavBookmarksService`, with special handling for smart bookmarks,
+ * tags, keywords, synced annotations, and missing parents.
+ */
+var PlacesSyncUtils = {};
+
+const { SOURCE_SYNC } = Ci.nsINavBookmarksService;
+
+// These are defined as lazy getters to defer initializing the bookmarks
+// service until it's needed.
+XPCOMUtils.defineLazyGetter(this, "ROOT_SYNC_ID_TO_GUID", () => ({
+ menu: PlacesUtils.bookmarks.menuGuid,
+ places: PlacesUtils.bookmarks.rootGuid,
+ tags: PlacesUtils.bookmarks.tagsGuid,
+ toolbar: PlacesUtils.bookmarks.toolbarGuid,
+ unfiled: PlacesUtils.bookmarks.unfiledGuid,
+ mobile: PlacesUtils.bookmarks.mobileGuid,
+}));
+
+XPCOMUtils.defineLazyGetter(this, "ROOT_GUID_TO_SYNC_ID", () => ({
+ [PlacesUtils.bookmarks.menuGuid]: "menu",
+ [PlacesUtils.bookmarks.rootGuid]: "places",
+ [PlacesUtils.bookmarks.tagsGuid]: "tags",
+ [PlacesUtils.bookmarks.toolbarGuid]: "toolbar",
+ [PlacesUtils.bookmarks.unfiledGuid]: "unfiled",
+ [PlacesUtils.bookmarks.mobileGuid]: "mobile",
+}));
+
+XPCOMUtils.defineLazyGetter(this, "ROOTS", () =>
+ Object.keys(ROOT_SYNC_ID_TO_GUID)
+);
+
+const BookmarkSyncUtils = PlacesSyncUtils.bookmarks = Object.freeze({
+ SMART_BOOKMARKS_ANNO: "Places/SmartBookmark",
+ DESCRIPTION_ANNO: "bookmarkProperties/description",
+ SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
+ SYNC_PARENT_ANNO: "sync/parent",
+ SYNC_MOBILE_ROOT_ANNO: "mobile/bookmarksRoot",
+
+ KINDS: {
+ BOOKMARK: "bookmark",
+ // Microsummaries were removed from Places in bug 524091. For now, Sync
+ // treats them identically to bookmarks. Bug 745410 tracks removing them
+ // entirely.
+ MICROSUMMARY: "microsummary",
+ QUERY: "query",
+ FOLDER: "folder",
+ LIVEMARK: "livemark",
+ SEPARATOR: "separator",
+ },
+
+ get ROOTS() {
+ return ROOTS;
+ },
+
+ /**
+ * Converts a Places GUID to a Sync ID. Sync IDs are identical to Places
+ * GUIDs for all items except roots.
+ */
+ guidToSyncId(guid) {
+ return ROOT_GUID_TO_SYNC_ID[guid] || guid;
+ },
+
+ /**
+ * Converts a Sync record ID to a Places GUID.
+ */
+ syncIdToGuid(syncId) {
+ return ROOT_SYNC_ID_TO_GUID[syncId] || syncId;
+ },
+
+ /**
+ * Fetches the sync IDs for a folder's children, ordered by their position
+ * within the folder.
+ */
+ fetchChildSyncIds: Task.async(function* (parentSyncId) {
+ PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(parentSyncId);
+ let parentGuid = BookmarkSyncUtils.syncIdToGuid(parentSyncId);
+
+ let db = yield PlacesUtils.promiseDBConnection();
+ let children = yield fetchAllChildren(db, parentGuid);
+ return children.map(child =>
+ BookmarkSyncUtils.guidToSyncId(child.guid)
+ );
+ }),
+
+ /**
+ * Reorders a folder's children, based on their order in the array of sync
+ * IDs.
+ *
+ * Sync uses this method to reorder all synced children after applying all
+ * incoming records.
+ *
+ */
+ order: Task.async(function* (parentSyncId, childSyncIds) {
+ PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(parentSyncId);
+ if (!childSyncIds.length) {
+ return undefined;
+ }
+ let parentGuid = BookmarkSyncUtils.syncIdToGuid(parentSyncId);
+ if (parentGuid == PlacesUtils.bookmarks.rootGuid) {
+ // Reordering roots doesn't make sense, but Sync will do this on the
+ // first sync.
+ return undefined;
+ }
+ let orderedChildrenGuids = childSyncIds.map(BookmarkSyncUtils.syncIdToGuid);
+ return PlacesUtils.bookmarks.reorder(parentGuid, orderedChildrenGuids,
+ { source: SOURCE_SYNC });
+ }),
+
+ /**
+ * Removes an item from the database. Options are passed through to
+ * PlacesUtils.bookmarks.remove.
+ */
+ remove: Task.async(function* (syncId, options = {}) {
+ let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
+ if (guid in ROOT_GUID_TO_SYNC_ID) {
+ BookmarkSyncLog.warn(`remove: Refusing to remove root ${syncId}`);
+ return null;
+ }
+ return PlacesUtils.bookmarks.remove(guid, Object.assign({}, options, {
+ source: SOURCE_SYNC,
+ }));
+ }),
+
+ /**
+ * Returns true for sync IDs that are considered roots.
+ */
+ isRootSyncID(syncID) {
+ return ROOT_SYNC_ID_TO_GUID.hasOwnProperty(syncID);
+ },
+
+ /**
+ * Changes the GUID of an existing item. This method only allows Places GUIDs
+ * because root sync IDs cannot be changed.
+ *
+ * @return {Promise} resolved once the GUID has been changed.
+ * @resolves to the new GUID.
+ * @rejects if the old GUID does not exist.
+ */
+ changeGuid: Task.async(function* (oldGuid, newGuid) {
+ PlacesUtils.BOOKMARK_VALIDATORS.guid(oldGuid);
+ PlacesUtils.BOOKMARK_VALIDATORS.guid(newGuid);
+
+ let itemId = yield PlacesUtils.promiseItemId(oldGuid);
+ if (PlacesUtils.isRootItem(itemId)) {
+ throw new Error(`Cannot change GUID of Places root ${oldGuid}`);
+ }
+ return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: changeGuid",
+ Task.async(function* (db) {
+ yield db.executeCached(`UPDATE moz_bookmarks SET guid = :newGuid
+ WHERE id = :itemId`, { newGuid, itemId });
+ PlacesUtils.invalidateCachedGuidFor(itemId);
+ return newGuid;
+ })
+ );
+ }),
+
+ /**
+ * Updates a bookmark with synced properties. Only Sync should call this
+ * method; other callers should use `Bookmarks.update`.
+ *
+ * The following properties are supported:
+ * - kind: Optional.
+ * - guid: Required.
+ * - parentGuid: Optional; reparents the bookmark if specified.
+ * - title: Optional.
+ * - url: Optional.
+ * - tags: Optional; replaces all existing tags.
+ * - keyword: Optional.
+ * - description: Optional.
+ * - loadInSidebar: Optional.
+ * - query: Optional.
+ *
+ * @param info
+ * object representing a bookmark-item, as defined above.
+ *
+ * @return {Promise} resolved when the update is complete.
+ * @resolves to an object representing the updated bookmark.
+ * @rejects if it's not possible to update the given bookmark.
+ * @throws if the arguments are invalid.
+ */
+ update: Task.async(function* (info) {
+ let updateInfo = validateSyncBookmarkObject(info,
+ { syncId: { required: true }
+ });
+
+ return updateSyncBookmark(updateInfo);
+ }),
+
+ /**
+ * Inserts a synced bookmark into the tree. Only Sync should call this
+ * method; other callers should use `Bookmarks.insert`.
+ *
+ * The following properties are supported:
+ * - kind: Required.
+ * - guid: Required.
+ * - parentGuid: Required.
+ * - url: Required for bookmarks.
+ * - query: A smart bookmark query string, optional.
+ * - tags: An optional array of tag strings.
+ * - keyword: An optional keyword string.
+ * - description: An optional description string.
+ * - loadInSidebar: An optional boolean; defaults to false.
+ *
+ * Sync doesn't set the index, since it appends and reorders children
+ * after applying all incoming items.
+ *
+ * @param info
+ * object representing a synced bookmark.
+ *
+ * @return {Promise} resolved when the creation is complete.
+ * @resolves to an object representing the created bookmark.
+ * @rejects if it's not possible to create the requested bookmark.
+ * @throws if the arguments are invalid.
+ */
+ insert: Task.async(function* (info) {
+ let insertInfo = validateNewBookmark(info);
+ return insertSyncBookmark(insertInfo);
+ }),
+
+ /**
+ * Fetches a Sync bookmark object for an item in the tree. The object contains
+ * the following properties, depending on the item's kind:
+ *
+ * - kind (all): A string representing the item's kind.
+ * - syncId (all): The item's sync ID.
+ * - parentSyncId (all): The sync ID of the item's parent.
+ * - parentTitle (all): The title of the item's parent, used for de-duping.
+ * Omitted for the Places root and parents with empty titles.
+ * - title ("bookmark", "folder", "livemark", "query"): The item's title.
+ * Omitted if empty.
+ * - url ("bookmark", "query"): The item's URL.
+ * - tags ("bookmark", "query"): An array containing the item's tags.
+ * - keyword ("bookmark"): The bookmark's keyword, if one exists.
+ * - description ("bookmark", "folder", "livemark"): The item's description.
+ * Omitted if one isn't set.
+ * - loadInSidebar ("bookmark", "query"): Whether to load the bookmark in
+ * the sidebar. Always `false` for queries.
+ * - feed ("livemark"): A `URL` object pointing to the livemark's feed URL.
+ * - site ("livemark"): A `URL` object pointing to the livemark's site URL,
+ * or `null` if one isn't set.
+ * - childSyncIds ("folder"): An array containing the sync IDs of the item's
+ * children, used to determine child order.
+ * - folder ("query"): The tag folder name, if this is a tag query.
+ * - query ("query"): The smart bookmark query name, if this is a smart
+ * bookmark.
+ * - index ("separator"): The separator's position within its parent.
+ */
+ fetch: Task.async(function* (syncId) {
+ let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
+ let bookmarkItem = yield PlacesUtils.bookmarks.fetch(guid);
+ if (!bookmarkItem) {
+ return null;
+ }
+
+ // Convert the Places bookmark object to a Sync bookmark and add
+ // kind-specific properties. Titles are required for bookmarks,
+ // folders, and livemarks; optional for queries, and omitted for
+ // separators.
+ let kind = yield getKindForItem(bookmarkItem);
+ let item;
+ switch (kind) {
+ case BookmarkSyncUtils.KINDS.BOOKMARK:
+ case BookmarkSyncUtils.KINDS.MICROSUMMARY:
+ item = yield fetchBookmarkItem(bookmarkItem);
+ break;
+
+ case BookmarkSyncUtils.KINDS.QUERY:
+ item = yield fetchQueryItem(bookmarkItem);
+ break;
+
+ case BookmarkSyncUtils.KINDS.FOLDER:
+ item = yield fetchFolderItem(bookmarkItem);
+ break;
+
+ case BookmarkSyncUtils.KINDS.LIVEMARK:
+ item = yield fetchLivemarkItem(bookmarkItem);
+ break;
+
+ case BookmarkSyncUtils.KINDS.SEPARATOR:
+ item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+ item.index = bookmarkItem.index;
+ break;
+
+ default:
+ throw new Error(`Unknown bookmark kind: ${kind}`);
+ }
+
+ // Sync uses the parent title for de-duping. All Sync bookmark objects
+ // except the Places root should have this property.
+ if (bookmarkItem.parentGuid) {
+ let parent = yield PlacesUtils.bookmarks.fetch(bookmarkItem.parentGuid);
+ item.parentTitle = parent.title || "";
+ }
+
+ return item;
+ }),
+
+ /**
+ * Get the sync record kind for the record with provided sync id.
+ *
+ * @param syncId
+ * Sync ID for the item in question
+ *
+ * @returns {Promise} A promise that resolves with the sync record kind (e.g.
+ * something under `PlacesSyncUtils.bookmarks.KIND`), or
+ * with `null` if no item with that guid exists.
+ * @throws if `guid` is invalid.
+ */
+ getKindForSyncId(syncId) {
+ PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(syncId);
+ let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
+ return PlacesUtils.bookmarks.fetch(guid)
+ .then(item => {
+ if (!item) {
+ return null;
+ }
+ return getKindForItem(item)
+ });
+ },
+});
+
+XPCOMUtils.defineLazyGetter(this, "BookmarkSyncLog", () => {
+ return Log.repository.getLogger("BookmarkSyncUtils");
+});
+
+function validateSyncBookmarkObject(input, behavior) {
+ return PlacesUtils.validateItemProperties(
+ PlacesUtils.SYNC_BOOKMARK_VALIDATORS, input, behavior);
+}
+
+// Similar to the private `fetchBookmarksByParent` implementation in
+// `Bookmarks.jsm`.
+var fetchAllChildren = Task.async(function* (db, parentGuid) {
+ let rows = yield db.executeCached(`
+ SELECT id, parent, position, type, guid
+ FROM moz_bookmarks
+ WHERE parent = (
+ SELECT id FROM moz_bookmarks WHERE guid = :parentGuid
+ )
+ ORDER BY position`,
+ { parentGuid }
+ );
+ return rows.map(row => ({
+ id: row.getResultByName("id"),
+ parentId: row.getResultByName("parent"),
+ index: row.getResultByName("position"),
+ type: row.getResultByName("type"),
+ guid: row.getResultByName("guid"),
+ }));
+});
+
+// A helper for whenever we want to know if a GUID doesn't exist in the places
+// database. Primarily used to detect orphans on incoming records.
+var GUIDMissing = Task.async(function* (guid) {
+ try {
+ yield PlacesUtils.promiseItemId(guid);
+ return false;
+ } catch (ex) {
+ if (ex.message == "no item found for the given GUID") {
+ return true;
+ }
+ throw ex;
+ }
+});
+
+// Tag queries use a `place:` URL that refers to the tag folder ID. When we
+// apply a synced tag query from a remote client, we need to update the URL to
+// point to the local tag folder.
+var updateTagQueryFolder = Task.async(function* (info) {
+ if (info.kind != BookmarkSyncUtils.KINDS.QUERY || !info.folder || !info.url ||
+ info.url.protocol != "place:") {
+ return info;
+ }
+
+ let params = new URLSearchParams(info.url.pathname);
+ let type = +params.get("type");
+
+ if (type != Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) {
+ return info;
+ }
+
+ let id = yield getOrCreateTagFolder(info.folder);
+ BookmarkSyncLog.debug(`updateTagQueryFolder: Tag query folder: ${
+ info.folder} = ${id}`);
+
+ // Rewrite the query to reference the new ID.
+ params.set("folder", id);
+ info.url = new URL(info.url.protocol + params);
+
+ return info;
+});
+
+var annotateOrphan = Task.async(function* (item, requestedParentSyncId) {
+ let guid = BookmarkSyncUtils.syncIdToGuid(item.syncId);
+ let itemId = yield PlacesUtils.promiseItemId(guid);
+ PlacesUtils.annotations.setItemAnnotation(itemId,
+ BookmarkSyncUtils.SYNC_PARENT_ANNO, requestedParentSyncId, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER,
+ SOURCE_SYNC);
+});
+
+var reparentOrphans = Task.async(function* (item) {
+ if (item.kind != BookmarkSyncUtils.KINDS.FOLDER) {
+ return;
+ }
+ let orphanGuids = yield fetchGuidsWithAnno(BookmarkSyncUtils.SYNC_PARENT_ANNO,
+ item.syncId);
+ let folderGuid = BookmarkSyncUtils.syncIdToGuid(item.syncId);
+ BookmarkSyncLog.debug(`reparentOrphans: Reparenting ${
+ JSON.stringify(orphanGuids)} to ${item.syncId}`);
+ for (let i = 0; i < orphanGuids.length; ++i) {
+ let isReparented = false;
+ try {
+ // Reparenting can fail if we have a corrupted or incomplete tree
+ // where an item's parent is one of its descendants.
+ BookmarkSyncLog.trace(`reparentOrphans: Attempting to move item ${
+ orphanGuids[i]} to new parent ${item.syncId}`);
+ yield PlacesUtils.bookmarks.update({
+ guid: orphanGuids[i],
+ parentGuid: folderGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ source: SOURCE_SYNC,
+ });
+ isReparented = true;
+ } catch (ex) {
+ BookmarkSyncLog.error(`reparentOrphans: Failed to reparent item ${
+ orphanGuids[i]} to ${item.syncId}`, ex);
+ }
+ if (isReparented) {
+ // Remove the annotation once we've reparented the item.
+ let orphanId = yield PlacesUtils.promiseItemId(orphanGuids[i]);
+ PlacesUtils.annotations.removeItemAnnotation(orphanId,
+ BookmarkSyncUtils.SYNC_PARENT_ANNO, SOURCE_SYNC);
+ }
+ }
+});
+
+// Inserts a synced bookmark into the database.
+var insertSyncBookmark = Task.async(function* (insertInfo) {
+ let requestedParentSyncId = insertInfo.parentSyncId;
+ let requestedParentGuid =
+ BookmarkSyncUtils.syncIdToGuid(insertInfo.parentSyncId);
+ let isOrphan = yield GUIDMissing(requestedParentGuid);
+
+ // Default to "unfiled" for new bookmarks if the parent doesn't exist.
+ if (!isOrphan) {
+ BookmarkSyncLog.debug(`insertSyncBookmark: Item ${
+ insertInfo.syncId} is not an orphan`);
+ } else {
+ BookmarkSyncLog.debug(`insertSyncBookmark: Item ${
+ insertInfo.syncId} is an orphan: parent ${
+ insertInfo.parentSyncId} doesn't exist; reparenting to unfiled`);
+ insertInfo.parentSyncId = "unfiled";
+ }
+
+ // If we're inserting a tag query, make sure the tag exists and fix the
+ // folder ID to refer to the local tag folder.
+ insertInfo = yield updateTagQueryFolder(insertInfo);
+
+ let newItem;
+ if (insertInfo.kind == BookmarkSyncUtils.KINDS.LIVEMARK) {
+ newItem = yield insertSyncLivemark(insertInfo);
+ } else {
+ let bookmarkInfo = syncBookmarkToPlacesBookmark(insertInfo);
+ let bookmarkItem = yield PlacesUtils.bookmarks.insert(bookmarkInfo);
+ newItem = yield insertBookmarkMetadata(bookmarkItem, insertInfo);
+ }
+
+ if (!newItem) {
+ return null;
+ }
+
+ // If the item is an orphan, annotate it with its real parent sync ID.
+ if (isOrphan) {
+ yield annotateOrphan(newItem, requestedParentSyncId);
+ }
+
+ // Reparent all orphans that expect this folder as the parent.
+ yield reparentOrphans(newItem);
+
+ return newItem;
+});
+
+// Inserts a synced livemark.
+var insertSyncLivemark = Task.async(function* (insertInfo) {
+ if (!insertInfo.feed) {
+ BookmarkSyncLog.debug(`insertSyncLivemark: ${
+ insertInfo.syncId} missing feed URL`);
+ return null;
+ }
+ let livemarkInfo = syncBookmarkToPlacesBookmark(insertInfo);
+ let parentIsLivemark = yield getAnno(livemarkInfo.parentGuid,
+ PlacesUtils.LMANNO_FEEDURI);
+ if (parentIsLivemark) {
+ // A livemark can't be a descendant of another livemark.
+ BookmarkSyncLog.debug(`insertSyncLivemark: Invalid parent ${
+ insertInfo.parentSyncId}; skipping livemark record ${
+ insertInfo.syncId}`);
+ return null;
+ }
+
+ let livemarkItem = yield PlacesUtils.livemarks.addLivemark(livemarkInfo);
+
+ return insertBookmarkMetadata(livemarkItem, insertInfo);
+});
+
+// Sets annotations, keywords, and tags on a new bookmark. Returns a Sync
+// bookmark object.
+var insertBookmarkMetadata = Task.async(function* (bookmarkItem, insertInfo) {
+ let itemId = yield PlacesUtils.promiseItemId(bookmarkItem.guid);
+ let newItem = yield placesBookmarkToSyncBookmark(bookmarkItem);
+
+ if (insertInfo.query) {
+ PlacesUtils.annotations.setItemAnnotation(itemId,
+ BookmarkSyncUtils.SMART_BOOKMARKS_ANNO, insertInfo.query, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER,
+ SOURCE_SYNC);
+ newItem.query = insertInfo.query;
+ }
+
+ try {
+ newItem.tags = yield tagItem(bookmarkItem, insertInfo.tags);
+ } catch (ex) {
+ BookmarkSyncLog.warn(`insertBookmarkMetadata: Error tagging item ${
+ insertInfo.syncId}`, ex);
+ }
+
+ if (insertInfo.keyword) {
+ yield PlacesUtils.keywords.insert({
+ keyword: insertInfo.keyword,
+ url: bookmarkItem.url.href,
+ source: SOURCE_SYNC,
+ });
+ newItem.keyword = insertInfo.keyword;
+ }
+
+ if (insertInfo.description) {
+ PlacesUtils.annotations.setItemAnnotation(itemId,
+ BookmarkSyncUtils.DESCRIPTION_ANNO, insertInfo.description, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER,
+ SOURCE_SYNC);
+ newItem.description = insertInfo.description;
+ }
+
+ if (insertInfo.loadInSidebar) {
+ PlacesUtils.annotations.setItemAnnotation(itemId,
+ BookmarkSyncUtils.SIDEBAR_ANNO, insertInfo.loadInSidebar, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER,
+ SOURCE_SYNC);
+ newItem.loadInSidebar = insertInfo.loadInSidebar;
+ }
+
+ return newItem;
+});
+
+// Determines the Sync record kind for an existing bookmark.
+var getKindForItem = Task.async(function* (item) {
+ switch (item.type) {
+ case PlacesUtils.bookmarks.TYPE_FOLDER: {
+ let isLivemark = yield getAnno(item.guid,
+ PlacesUtils.LMANNO_FEEDURI);
+ return isLivemark ? BookmarkSyncUtils.KINDS.LIVEMARK :
+ BookmarkSyncUtils.KINDS.FOLDER;
+ }
+ case PlacesUtils.bookmarks.TYPE_BOOKMARK:
+ return item.url.protocol == "place:" ?
+ BookmarkSyncUtils.KINDS.QUERY :
+ BookmarkSyncUtils.KINDS.BOOKMARK;
+
+ case PlacesUtils.bookmarks.TYPE_SEPARATOR:
+ return BookmarkSyncUtils.KINDS.SEPARATOR;
+ }
+ return null;
+});
+
+// Returns the `nsINavBookmarksService` bookmark type constant for a Sync
+// record kind.
+function getTypeForKind(kind) {
+ switch (kind) {
+ case BookmarkSyncUtils.KINDS.BOOKMARK:
+ case BookmarkSyncUtils.KINDS.MICROSUMMARY:
+ case BookmarkSyncUtils.KINDS.QUERY:
+ return PlacesUtils.bookmarks.TYPE_BOOKMARK;
+
+ case BookmarkSyncUtils.KINDS.FOLDER:
+ case BookmarkSyncUtils.KINDS.LIVEMARK:
+ return PlacesUtils.bookmarks.TYPE_FOLDER;
+
+ case BookmarkSyncUtils.KINDS.SEPARATOR:
+ return PlacesUtils.bookmarks.TYPE_SEPARATOR;
+ }
+ throw new Error(`Unknown bookmark kind: ${kind}`);
+}
+
+// Determines if a livemark should be reinserted. Returns true if `updateInfo`
+// specifies different feed or site URLs; false otherwise.
+var shouldReinsertLivemark = Task.async(function* (updateInfo) {
+ let hasFeed = updateInfo.hasOwnProperty("feed");
+ let hasSite = updateInfo.hasOwnProperty("site");
+ if (!hasFeed && !hasSite) {
+ return false;
+ }
+ let guid = BookmarkSyncUtils.syncIdToGuid(updateInfo.syncId);
+ let livemark = yield PlacesUtils.livemarks.getLivemark({
+ guid,
+ });
+ if (hasFeed) {
+ let feedURI = PlacesUtils.toURI(updateInfo.feed);
+ if (!livemark.feedURI.equals(feedURI)) {
+ return true;
+ }
+ }
+ if (hasSite) {
+ if (!updateInfo.site) {
+ return !!livemark.siteURI;
+ }
+ let siteURI = PlacesUtils.toURI(updateInfo.site);
+ if (!livemark.siteURI || !siteURI.equals(livemark.siteURI)) {
+ return true;
+ }
+ }
+ return false;
+});
+
+var updateSyncBookmark = Task.async(function* (updateInfo) {
+ let guid = BookmarkSyncUtils.syncIdToGuid(updateInfo.syncId);
+ let oldBookmarkItem = yield PlacesUtils.bookmarks.fetch(guid);
+ if (!oldBookmarkItem) {
+ throw new Error(`Bookmark with sync ID ${
+ updateInfo.syncId} does not exist`);
+ }
+
+ let shouldReinsert = false;
+ let oldKind = yield getKindForItem(oldBookmarkItem);
+ if (updateInfo.hasOwnProperty("kind") && updateInfo.kind != oldKind) {
+ // If the item's aren't the same kind, we can't update the record;
+ // we must remove and reinsert.
+ shouldReinsert = true;
+ if (BookmarkSyncLog.level <= Log.Level.Warn) {
+ let oldSyncId = BookmarkSyncUtils.guidToSyncId(oldBookmarkItem.guid);
+ BookmarkSyncLog.warn(`updateSyncBookmark: Local ${
+ oldSyncId} kind = ${oldKind}; remote ${
+ updateInfo.syncId} kind = ${
+ updateInfo.kind}. Deleting and recreating`);
+ }
+ } else if (oldKind == BookmarkSyncUtils.KINDS.LIVEMARK) {
+ // Similarly, if we're changing a livemark's site or feed URL, we need to
+ // reinsert.
+ shouldReinsert = yield shouldReinsertLivemark(updateInfo);
+ if (BookmarkSyncLog.level <= Log.Level.Debug) {
+ let oldSyncId = BookmarkSyncUtils.guidToSyncId(oldBookmarkItem.guid);
+ BookmarkSyncLog.debug(`updateSyncBookmark: Local ${
+ oldSyncId} and remote ${
+ updateInfo.syncId} livemarks have different URLs`);
+ }
+ }
+
+ if (shouldReinsert) {
+ let newInfo = validateNewBookmark(updateInfo);
+ yield PlacesUtils.bookmarks.remove({
+ guid,
+ source: SOURCE_SYNC,
+ });
+ // A reinsertion likely indicates a confused client, since there aren't
+ // public APIs for changing livemark URLs or an item's kind (e.g., turning
+ // a folder into a separator while preserving its annos and position).
+ // This might be a good case to repair later; for now, we assume Sync has
+ // passed a complete record for the new item, and don't try to merge
+ // `oldBookmarkItem` with `updateInfo`.
+ return insertSyncBookmark(newInfo);
+ }
+
+ let isOrphan = false, requestedParentSyncId;
+ if (updateInfo.hasOwnProperty("parentSyncId")) {
+ requestedParentSyncId = updateInfo.parentSyncId;
+ let oldParentSyncId =
+ BookmarkSyncUtils.guidToSyncId(oldBookmarkItem.parentGuid);
+ if (requestedParentSyncId != oldParentSyncId) {
+ let oldId = yield PlacesUtils.promiseItemId(oldBookmarkItem.guid);
+ if (PlacesUtils.isRootItem(oldId)) {
+ throw new Error(`Cannot move Places root ${oldId}`);
+ }
+ let requestedParentGuid =
+ BookmarkSyncUtils.syncIdToGuid(requestedParentSyncId);
+ isOrphan = yield GUIDMissing(requestedParentGuid);
+ if (!isOrphan) {
+ BookmarkSyncLog.debug(`updateSyncBookmark: Item ${
+ updateInfo.syncId} is not an orphan`);
+ } else {
+ // Don't move the item if the new parent doesn't exist. Instead, mark
+ // the item as an orphan. We'll annotate it with its real parent after
+ // updating.
+ BookmarkSyncLog.trace(`updateSyncBookmark: Item ${
+ updateInfo.syncId} is an orphan: could not find parent ${
+ requestedParentSyncId}`);
+ delete updateInfo.parentSyncId;
+ }
+ } else {
+ // If the parent is the same, just omit it so that `update` doesn't do
+ // extra work.
+ delete updateInfo.parentSyncId;
+ }
+ }
+
+ updateInfo = yield updateTagQueryFolder(updateInfo);
+
+ let bookmarkInfo = syncBookmarkToPlacesBookmark(updateInfo);
+ let newBookmarkItem = shouldUpdateBookmark(bookmarkInfo) ?
+ yield PlacesUtils.bookmarks.update(bookmarkInfo) :
+ oldBookmarkItem;
+ let newItem = yield updateBookmarkMetadata(oldBookmarkItem, newBookmarkItem,
+ updateInfo);
+
+ // If the item is an orphan, annotate it with its real parent sync ID.
+ if (isOrphan) {
+ yield annotateOrphan(newItem, requestedParentSyncId);
+ }
+
+ // Reparent all orphans that expect this folder as the parent.
+ yield reparentOrphans(newItem);
+
+ return newItem;
+});
+
+// Updates tags, keywords, and annotations for an existing bookmark. Returns a
+// Sync bookmark object.
+var updateBookmarkMetadata = Task.async(function* (oldBookmarkItem,
+ newBookmarkItem,
+ updateInfo) {
+ let itemId = yield PlacesUtils.promiseItemId(newBookmarkItem.guid);
+ let newItem = yield placesBookmarkToSyncBookmark(newBookmarkItem);
+
+ try {
+ newItem.tags = yield tagItem(newBookmarkItem, updateInfo.tags);
+ } catch (ex) {
+ BookmarkSyncLog.warn(`updateBookmarkMetadata: Error tagging item ${
+ updateInfo.syncId}`, ex);
+ }
+
+ if (updateInfo.hasOwnProperty("keyword")) {
+ // Unconditionally remove the old keyword.
+ let entry = yield PlacesUtils.keywords.fetch({
+ url: oldBookmarkItem.url.href,
+ });
+ if (entry) {
+ yield PlacesUtils.keywords.remove({
+ keyword: entry.keyword,
+ source: SOURCE_SYNC,
+ });
+ }
+ if (updateInfo.keyword) {
+ yield PlacesUtils.keywords.insert({
+ keyword: updateInfo.keyword,
+ url: newItem.url.href,
+ source: SOURCE_SYNC,
+ });
+ }
+ newItem.keyword = updateInfo.keyword;
+ }
+
+ if (updateInfo.hasOwnProperty("description")) {
+ if (updateInfo.description) {
+ PlacesUtils.annotations.setItemAnnotation(itemId,
+ BookmarkSyncUtils.DESCRIPTION_ANNO, updateInfo.description, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER,
+ SOURCE_SYNC);
+ } else {
+ PlacesUtils.annotations.removeItemAnnotation(itemId,
+ BookmarkSyncUtils.DESCRIPTION_ANNO, SOURCE_SYNC);
+ }
+ newItem.description = updateInfo.description;
+ }
+
+ if (updateInfo.hasOwnProperty("loadInSidebar")) {
+ if (updateInfo.loadInSidebar) {
+ PlacesUtils.annotations.setItemAnnotation(itemId,
+ BookmarkSyncUtils.SIDEBAR_ANNO, updateInfo.loadInSidebar, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER,
+ SOURCE_SYNC);
+ } else {
+ PlacesUtils.annotations.removeItemAnnotation(itemId,
+ BookmarkSyncUtils.SIDEBAR_ANNO, SOURCE_SYNC);
+ }
+ newItem.loadInSidebar = updateInfo.loadInSidebar;
+ }
+
+ if (updateInfo.hasOwnProperty("query")) {
+ PlacesUtils.annotations.setItemAnnotation(itemId,
+ BookmarkSyncUtils.SMART_BOOKMARKS_ANNO, updateInfo.query, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER,
+ SOURCE_SYNC);
+ newItem.query = updateInfo.query;
+ }
+
+ return newItem;
+});
+
+function validateNewBookmark(info) {
+ let insertInfo = validateSyncBookmarkObject(info,
+ { kind: { required: true }
+ , syncId: { required: true }
+ , url: { requiredIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
+ , BookmarkSyncUtils.KINDS.MICROSUMMARY
+ , BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind)
+ , validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
+ , BookmarkSyncUtils.KINDS.MICROSUMMARY
+ , BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) }
+ , parentSyncId: { required: true }
+ , title: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
+ , BookmarkSyncUtils.KINDS.MICROSUMMARY
+ , BookmarkSyncUtils.KINDS.QUERY
+ , BookmarkSyncUtils.KINDS.FOLDER
+ , BookmarkSyncUtils.KINDS.LIVEMARK ].includes(b.kind) }
+ , query: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.QUERY }
+ , folder: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.QUERY }
+ , tags: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
+ , BookmarkSyncUtils.KINDS.MICROSUMMARY
+ , BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) }
+ , keyword: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
+ , BookmarkSyncUtils.KINDS.MICROSUMMARY
+ , BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) }
+ , description: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
+ , BookmarkSyncUtils.KINDS.MICROSUMMARY
+ , BookmarkSyncUtils.KINDS.QUERY
+ , BookmarkSyncUtils.KINDS.FOLDER
+ , BookmarkSyncUtils.KINDS.LIVEMARK ].includes(b.kind) }
+ , loadInSidebar: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
+ , BookmarkSyncUtils.KINDS.MICROSUMMARY
+ , BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) }
+ , feed: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.LIVEMARK }
+ , site: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.LIVEMARK }
+ });
+
+ return insertInfo;
+}
+
+// Returns an array of GUIDs for items that have an `anno` with the given `val`.
+var fetchGuidsWithAnno = Task.async(function* (anno, val) {
+ let db = yield PlacesUtils.promiseDBConnection();
+ let rows = yield db.executeCached(`
+ SELECT b.guid FROM moz_items_annos a
+ JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id
+ JOIN moz_bookmarks b ON b.id = a.item_id
+ WHERE n.name = :anno AND
+ a.content = :val`,
+ { anno, val });
+ return rows.map(row => row.getResultByName("guid"));
+});
+
+// Returns the value of an item's annotation, or `null` if it's not set.
+var getAnno = Task.async(function* (guid, anno) {
+ let db = yield PlacesUtils.promiseDBConnection();
+ let rows = yield db.executeCached(`
+ SELECT a.content FROM moz_items_annos a
+ JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id
+ JOIN moz_bookmarks b ON b.id = a.item_id
+ WHERE b.guid = :guid AND
+ n.name = :anno`,
+ { guid, anno });
+ return rows.length ? rows[0].getResultByName("content") : null;
+});
+
+var tagItem = Task.async(function (item, tags) {
+ if (!item.url) {
+ return [];
+ }
+
+ // Remove leading and trailing whitespace, then filter out empty tags.
+ let newTags = tags.map(tag => tag.trim()).filter(Boolean);
+
+ // Removing the last tagged item will also remove the tag. To preserve
+ // tag IDs, we temporarily tag a dummy URI, ensuring the tags exist.
+ let dummyURI = PlacesUtils.toURI("about:weave#BStore_tagURI");
+ let bookmarkURI = PlacesUtils.toURI(item.url.href);
+ PlacesUtils.tagging.tagURI(dummyURI, newTags, SOURCE_SYNC);
+ PlacesUtils.tagging.untagURI(bookmarkURI, null, SOURCE_SYNC);
+ PlacesUtils.tagging.tagURI(bookmarkURI, newTags, SOURCE_SYNC);
+ PlacesUtils.tagging.untagURI(dummyURI, null, SOURCE_SYNC);
+
+ return newTags;
+});
+
+// `PlacesUtils.bookmarks.update` checks if we've supplied enough properties,
+// but doesn't know about additional livemark properties. We check this to avoid
+// having it throw in case we only pass properties like `{ guid, feedURI }`.
+function shouldUpdateBookmark(bookmarkInfo) {
+ return bookmarkInfo.hasOwnProperty("parentGuid") ||
+ bookmarkInfo.hasOwnProperty("title") ||
+ bookmarkInfo.hasOwnProperty("url");
+}
+
+var getTagFolder = Task.async(function* (tag) {
+ let db = yield PlacesUtils.promiseDBConnection();
+ let results = yield db.executeCached(`SELECT id FROM moz_bookmarks
+ WHERE parent = :tagsFolder AND title = :tag LIMIT 1`,
+ { tagsFolder: PlacesUtils.bookmarks.tagsFolder, tag });
+ return results.length ? results[0].getResultByName("id") : null;
+});
+
+var getOrCreateTagFolder = Task.async(function* (tag) {
+ let id = yield getTagFolder(tag);
+ if (id) {
+ return id;
+ }
+ // Create the tag if it doesn't exist.
+ let item = yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.tagsGuid,
+ title: tag,
+ source: SOURCE_SYNC,
+ });
+ return PlacesUtils.promiseItemId(item.guid);
+});
+
+// Converts a Places bookmark or livemark to a Sync bookmark. This function
+// maps Places GUIDs to sync IDs and filters out extra Places properties like
+// date added, last modified, and index.
+var placesBookmarkToSyncBookmark = Task.async(function* (bookmarkItem) {
+ let item = {};
+
+ for (let prop in bookmarkItem) {
+ switch (prop) {
+ // Sync IDs are identical to Places GUIDs for all items except roots.
+ case "guid":
+ item.syncId = BookmarkSyncUtils.guidToSyncId(bookmarkItem.guid);
+ break;
+
+ case "parentGuid":
+ item.parentSyncId =
+ BookmarkSyncUtils.guidToSyncId(bookmarkItem.parentGuid);
+ break;
+
+ // Sync uses kinds instead of types, which distinguish between folders,
+ // livemarks, bookmarks, and queries.
+ case "type":
+ item.kind = yield getKindForItem(bookmarkItem);
+ break;
+
+ case "title":
+ case "url":
+ item[prop] = bookmarkItem[prop];
+ break;
+
+ // Livemark objects contain additional properties. The feed URL is
+ // required; the site URL is optional.
+ case "feedURI":
+ item.feed = new URL(bookmarkItem.feedURI.spec);
+ break;
+
+ case "siteURI":
+ if (bookmarkItem.siteURI) {
+ item.site = new URL(bookmarkItem.siteURI.spec);
+ }
+ break;
+ }
+ }
+
+ return item;
+});
+
+// Converts a Sync bookmark object to a Places bookmark or livemark object.
+// This function maps sync IDs to Places GUIDs, and filters out extra Sync
+// properties like keywords, tags, and descriptions. Returns an object that can
+// be passed to `PlacesUtils.livemarks.addLivemark` or
+// `PlacesUtils.bookmarks.{insert, update}`.
+function syncBookmarkToPlacesBookmark(info) {
+ let bookmarkInfo = {
+ source: SOURCE_SYNC,
+ };
+
+ for (let prop in info) {
+ switch (prop) {
+ case "kind":
+ bookmarkInfo.type = getTypeForKind(info.kind);
+ break;
+
+ // Convert sync IDs to Places GUIDs for roots.
+ case "syncId":
+ bookmarkInfo.guid = BookmarkSyncUtils.syncIdToGuid(info.syncId);
+ break;
+
+ case "parentSyncId":
+ bookmarkInfo.parentGuid =
+ BookmarkSyncUtils.syncIdToGuid(info.parentSyncId);
+ // Instead of providing an index, Sync reorders children at the end of
+ // the sync using `BookmarkSyncUtils.order`. We explicitly specify the
+ // default index here to prevent `PlacesUtils.bookmarks.update` and
+ // `PlacesUtils.livemarks.addLivemark` from throwing.
+ bookmarkInfo.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
+ break;
+
+ case "title":
+ case "url":
+ bookmarkInfo[prop] = info[prop];
+ break;
+
+ // Livemark-specific properties.
+ case "feed":
+ bookmarkInfo.feedURI = PlacesUtils.toURI(info.feed);
+ break;
+
+ case "site":
+ if (info.site) {
+ bookmarkInfo.siteURI = PlacesUtils.toURI(info.site);
+ }
+ break;
+ }
+ }
+
+ return bookmarkInfo;
+}
+
+// Creates and returns a Sync bookmark object containing the bookmark's
+// tags, keyword, description, and whether it loads in the sidebar.
+var fetchBookmarkItem = Task.async(function* (bookmarkItem) {
+ let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+
+ if (!item.title) {
+ item.title = "";
+ }
+
+ item.tags = PlacesUtils.tagging.getTagsForURI(
+ PlacesUtils.toURI(bookmarkItem.url), {});
+
+ let keywordEntry = yield PlacesUtils.keywords.fetch({
+ url: bookmarkItem.url,
+ });
+ if (keywordEntry) {
+ item.keyword = keywordEntry.keyword;
+ }
+
+ let description = yield getAnno(bookmarkItem.guid,
+ BookmarkSyncUtils.DESCRIPTION_ANNO);
+ if (description) {
+ item.description = description;
+ }
+
+ item.loadInSidebar = !!(yield getAnno(bookmarkItem.guid,
+ BookmarkSyncUtils.SIDEBAR_ANNO));
+
+ return item;
+});
+
+// Creates and returns a Sync bookmark object containing the folder's
+// description and children.
+var fetchFolderItem = Task.async(function* (bookmarkItem) {
+ let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+
+ if (!item.title) {
+ item.title = "";
+ }
+
+ let description = yield getAnno(bookmarkItem.guid,
+ BookmarkSyncUtils.DESCRIPTION_ANNO);
+ if (description) {
+ item.description = description;
+ }
+
+ let db = yield PlacesUtils.promiseDBConnection();
+ let children = yield fetchAllChildren(db, bookmarkItem.guid);
+ item.childSyncIds = children.map(child =>
+ BookmarkSyncUtils.guidToSyncId(child.guid)
+ );
+
+ return item;
+});
+
+// Creates and returns a Sync bookmark object containing the livemark's
+// description, children (none), feed URI, and site URI.
+var fetchLivemarkItem = Task.async(function* (bookmarkItem) {
+ let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+
+ if (!item.title) {
+ item.title = "";
+ }
+
+ let description = yield getAnno(bookmarkItem.guid,
+ BookmarkSyncUtils.DESCRIPTION_ANNO);
+ if (description) {
+ item.description = description;
+ }
+
+ let feedAnno = yield getAnno(bookmarkItem.guid, PlacesUtils.LMANNO_FEEDURI);
+ item.feed = new URL(feedAnno);
+
+ let siteAnno = yield getAnno(bookmarkItem.guid, PlacesUtils.LMANNO_SITEURI);
+ if (siteAnno) {
+ item.site = new URL(siteAnno);
+ }
+
+ return item;
+});
+
+// Creates and returns a Sync bookmark object containing the query's tag
+// folder name and smart bookmark query ID.
+var fetchQueryItem = Task.async(function* (bookmarkItem) {
+ let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+
+ let description = yield getAnno(bookmarkItem.guid,
+ BookmarkSyncUtils.DESCRIPTION_ANNO);
+ if (description) {
+ item.description = description;
+ }
+
+ let folder = null;
+ let params = new URLSearchParams(bookmarkItem.url.pathname);
+ let tagFolderId = +params.get("folder");
+ if (tagFolderId) {
+ try {
+ let tagFolderGuid = yield PlacesUtils.promiseItemGuid(tagFolderId);
+ let tagFolder = yield PlacesUtils.bookmarks.fetch(tagFolderGuid);
+ folder = tagFolder.title;
+ } catch (ex) {
+ BookmarkSyncLog.warn("fetchQueryItem: Query " + bookmarkItem.url.href +
+ " points to nonexistent folder " + tagFolderId, ex);
+ }
+ }
+ if (folder != null) {
+ item.folder = folder;
+ }
+
+ let query = yield getAnno(bookmarkItem.guid,
+ BookmarkSyncUtils.SMART_BOOKMARKS_ANNO);
+ if (query) {
+ item.query = query;
+ }
+
+ return item;
+});
diff --git a/components/places/src/PlacesTransactions.jsm b/components/places/src/PlacesTransactions.jsm
new file mode 100644
index 000000000..c355d92b6
--- /dev/null
+++ b/components/places/src/PlacesTransactions.jsm
@@ -0,0 +1,1645 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["PlacesTransactions"];
+
+/**
+ * Overview
+ * --------
+ * This modules serves as the transactions manager for Places (hereinafter PTM).
+ * It implements all the elementary transactions for its UI commands: creating
+ * items, editing their various properties, and so forth.
+ *
+ * Note that since the effect of invoking a Places command is not limited to the
+ * window in which it was performed (e.g. a folder created in the Library may be
+ * the parent of a bookmark created in some browser window), PTM is a singleton.
+ * It's therefore unnecessary to initialize PTM in any way apart importing this
+ * module.
+ *
+ * PTM shares most of its semantics with common command pattern implementations.
+ * However, the asynchronous design of contemporary and future APIs, combined
+ * with the commitment to serialize all UI operations, does make things a little
+ * bit different. For example, when |undo| is called in order to undo the top
+ * undo entry, the caller cannot tell for sure what entry would it be, because
+ * the execution of some transactions is either in process, or enqueued to be.
+ *
+ * Also note that unlike the nsITransactionManager, for example, this API is by
+ * no means generic. That is, it cannot be used to execute anything but the
+ * elementary transactions implemented here (Please file a bug if you find
+ * anything uncovered). More-complex transactions (e.g. creating a folder and
+ * moving a bookmark into it) may be implemented as a batch (see below).
+ *
+ * A note about GUIDs and item-ids
+ * -------------------------------
+ * There's an ongoing effort (see bug 1071511) to deprecate item-ids in Places
+ * in favor of GUIDs. Both because new APIs (e.g. Bookmark.jsm) expose them to
+ * the minimum necessary, and because GUIDs play much better with implementing
+ * |redo|, this API doesn't support item-ids at all, and only accepts bookmark
+ * GUIDs, both for input (e.g. for setting the parent folder for a new bookmark)
+ * and for output (when the GUID for such a bookmark is propagated).
+ *
+ * When working in conjugation with older Places API which only expose item ids,
+ * use PlacesUtils.promiseItemGuid for converting those to GUIDs (note that
+ * for result nodes, the guid is available through their bookmarkGuid getter).
+ * Should you need to convert GUIDs to item-ids, use PlacesUtils.promiseItemId.
+ *
+ * Constructing transactions
+ * -------------------------
+ * At the bottom of this module you will find transactions for all Places UI
+ * commands. They are exposed as constructors set on the PlacesTransactions
+ * object (e.g. PlacesTransactions.NewFolder). The input for this constructors
+ * is taken in the form of a single argument, a plain object consisting of the
+ * properties for the transaction. Input properties may be either required or
+ * optional (for example, |keyword| is required for the EditKeyword transaction,
+ * but optional for the NewBookmark transaction).
+ *
+ * To make things simple, a given input property has the same basic meaning and
+ * valid values across all transactions which accept it in the input object.
+ * Here is a list of all supported input properties along with their expected
+ * values:
+ * - url: a URL object, an nsIURI object, or a href.
+ * - urls: an array of urls, as above.
+ * - feedUrl: an url (as above), holding the url for a live bookmark.
+ * - siteUrl an url (as above), holding the url for the site with which
+ * a live bookmark is associated.
+ * - tag - a string.
+ * - tags: an array of strings.
+ * - guid, parentGuid, newParentGuid: a valid Places GUID string.
+ * - guids: an array of valid Places GUID strings.
+ * - title: a string
+ * - index, newIndex: the position of an item in its containing folder,
+ * starting from 0.
+ * integer and PlacesUtils.bookmarks.DEFAULT_INDEX
+ * - annotation: see PlacesUtils.setAnnotationsForItem
+ * - annotations: an array of annotation objects as above.
+ * - excludingAnnotation: a string (annotation name).
+ * - excludingAnnotations: an array of string (annotation names).
+ *
+ * If a required property is missing in the input object (e.g. not specifying
+ * parentGuid for NewBookmark), or if the value for any of the input properties
+ * is invalid "on the surface" (e.g. a numeric value for GUID, or a string that
+ * isn't 12-characters long), the transaction constructor throws right way.
+ * More complex errors (e.g. passing a non-existent GUID for parentGuid) only
+ * reveal once the transaction is executed.
+ *
+ * Executing Transactions (the |transact| method of transactions)
+ * --------------------------------------------------------------
+ * Once a transaction is created, you must call its |transact| method for it to
+ * be executed and take effect. |transact| is an asynchronous method that takes
+ * no arguments, and returns a promise that resolves once the transaction is
+ * executed. Executing one of the transactions for creating items (NewBookmark,
+ * NewFolder, NewSeparator or NewLivemark) resolve to the new item's GUID.
+ * There's no resolution value for other transactions.
+ * If a transaction fails to execute, |transact| rejects and the transactions
+ * history is not affected.
+ *
+ * |transact| throws if it's called more than once (successfully or not) on the
+ * same transaction object.
+ *
+ * Batches
+ * -------
+ * Sometimes it is useful to "batch" or "merge" transactions. For example,
+ * something like "Bookmark All Tabs" may be implemented as one NewFolder
+ * transaction followed by numerous NewBookmark transactions - all to be undone
+ * or redone in a single undo or redo command. Use |PlacesTransactions.batch|
+ * in such cases. It can take either an array of transactions which will be
+ * executed in the given order and later be treated a a single entry in the
+ * transactions history, or a generator function that is passed to Task.spawn,
+ * that is to "contain" the batch: once the generator function is called a batch
+ * starts, and it lasts until the asynchronous generator iteration is complete
+ * All transactions executed by |transact| during this time are to be treated as
+ * a single entry in the transactions history.
+ *
+ * In both modes, |PlacesTransactions.batch| returns a promise that is to be
+ * resolved when the batch ends. In the array-input mode, there's no resolution
+ * value. In the generator mode, the resolution value is whatever the generator
+ * function returned (the semantics are the same as in Task.spawn, basically).
+ *
+ * The array-input mode of |PlacesTransactions.batch| is useful for implementing
+ * a batch of mostly-independent transaction (for example, |paste| into a folder
+ * can be implemented as a batch of multiple NewBookmark transactions).
+ * The generator mode is useful when the resolution value of executing one
+ * transaction is the input of one more subsequent transaction.
+ *
+ * In the array-input mode, if any transactions fails to execute, the batch
+ * continues (exceptions are logged). Only transactions that were executed
+ * successfully are added to the transactions history.
+ *
+ * WARNING: "nested" batches are not supported, if you call batch while another
+ * batch is still running, the new batch is enqueued with all other PTM work
+ * and thus not run until the running batch ends. The same goes for undo, redo
+ * and clearTransactionsHistory (note batches cannot be done partially, meaning
+ * undo and redo calls that during a batch are just enqueued).
+ *
+ * *****************************************************************************
+ * IT"S PARTICULARLY IMPORTANT NOT TO YIELD ANY PROMISE RETURNED BY ANY OF
+ * THESE METHODS (undo, redo, clearTransactionsHistory) FROM A BATCH FUNCTION.
+ * UNTIL WE FIND A WAY TO THROW IN THAT CASE (SEE BUG 1091446) DOING SO WILL
+ * COMPLETELY BREAK PTM UNTIL SHUTDOWN, NOT ALLOWING THE EXECUTION OF ANY
+ * TRANSACTION!
+ * *****************************************************************************
+ *
+ * Serialization
+ * -------------
+ * All |PlacesTransaction| operations are serialized. That is, even though the
+ * implementation is asynchronous, the order in which PlacesTransactions methods
+ * is called does guarantee the order in which they are to be invoked.
+ *
+ * The only exception to this rule is |transact| calls done during a batch (see
+ * above). |transact| calls are serialized with each other (and with undo, redo
+ * and clearTransactionsHistory), but they are, of course, not serialized with
+ * batches.
+ *
+ * The transactions-history structure
+ * ----------------------------------
+ * The transactions-history is a two-dimensional stack of transactions: the
+ * transactions are ordered in reverse to the order they were committed.
+ * It's two-dimensional because PTM allows batching transactions together for
+ * the purpose of undo or redo (see Batches above).
+ *
+ * The undoPosition property is set to the index of the top entry. If there is
+ * no entry at that index, there is nothing to undo.
+ * Entries prior to undoPosition, if any, are redo entries, the first one being
+ * the top redo entry.
+ *
+ * [ [2nd redo txn, 1st redo txn], <= 2nd redo entry
+ * [2nd redo txn, 1st redo txn], <= 1st redo entry
+ * [1st undo txn, 2nd undo txn], <= 1st undo entry
+ * [1st undo txn, 2nd undo txn] <= 2nd undo entry ]
+ * undoPostion: 2.
+ *
+ * Note that when a new entry is created, all redo entries are removed.
+ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+
+Components.utils.importGlobalProperties(["URL"]);
+
+var TransactionsHistory = [];
+TransactionsHistory.__proto__ = {
+ __proto__: Array.prototype,
+
+ // The index of the first undo entry (if any) - See the documentation
+ // at the top of this file.
+ _undoPosition: 0,
+ get undoPosition() {
+ return this._undoPosition;
+ },
+
+ // Handy shortcuts
+ get topUndoEntry() {
+ return this.undoPosition < this.length ? this[this.undoPosition] : null;
+ },
+ get topRedoEntry() {
+ return this.undoPosition > 0 ? this[this.undoPosition - 1] : null;
+ },
+
+ // Outside of this module, the API of transactions is inaccessible, and so
+ // are any internal properties. To achieve that, transactions are proxified
+ // in their constructors. This maps the proxies to their respective raw
+ // objects.
+ proxifiedToRaw: new WeakMap(),
+
+ /**
+ * Proxify a transaction object for consumers.
+ * @param aRawTransaction
+ * the raw transaction object.
+ * @return the proxified transaction object.
+ * @see getRawTransaction for retrieving the raw transaction.
+ */
+ proxifyTransaction: function (aRawTransaction) {
+ let proxy = Object.freeze({
+ transact() {
+ return TransactionsManager.transact(this);
+ }
+ });
+ this.proxifiedToRaw.set(proxy, aRawTransaction);
+ return proxy;
+ },
+
+ /**
+ * Check if the given object is a the proxy object for some transaction.
+ * @param aValue
+ * any JS value.
+ * @return true if aValue is the proxy object for some transaction, false
+ * otherwise.
+ */
+ isProxifiedTransactionObject(aValue) {
+ return this.proxifiedToRaw.has(aValue);
+ },
+
+ /**
+ * Get the raw transaction for the given proxy.
+ * @param aProxy
+ * the proxy object
+ * @return the transaction proxified by aProxy; |undefined| is returned if
+ * aProxy is not a proxified transaction.
+ */
+ getRawTransaction(aProxy) {
+ return this.proxifiedToRaw.get(aProxy);
+ },
+
+ /**
+ * Add a transaction either as a new entry, if forced or if there are no undo
+ * entries, or to the top undo entry.
+ *
+ * @param aProxifiedTransaction
+ * the proxified transaction object to be added to the transaction
+ * history.
+ * @param [optional] aForceNewEntry
+ * Force a new entry for the transaction. Default: false.
+ * If false, an entry will we created only if there's no undo entry
+ * to extend.
+ */
+ add(aProxifiedTransaction, aForceNewEntry = false) {
+ if (!this.isProxifiedTransactionObject(aProxifiedTransaction))
+ throw new Error("aProxifiedTransaction is not a proxified transaction");
+
+ if (this.length == 0 || aForceNewEntry) {
+ this.clearRedoEntries();
+ this.unshift([aProxifiedTransaction]);
+ }
+ else {
+ this[this.undoPosition].unshift(aProxifiedTransaction);
+ }
+ },
+
+ /**
+ * Clear all undo entries.
+ */
+ clearUndoEntries() {
+ if (this.undoPosition < this.length)
+ this.splice(this.undoPosition);
+ },
+
+ /**
+ * Clear all redo entries.
+ */
+ clearRedoEntries() {
+ if (this.undoPosition > 0) {
+ this.splice(0, this.undoPosition);
+ this._undoPosition = 0;
+ }
+ },
+
+ /**
+ * Clear all entries.
+ */
+ clearAllEntries() {
+ if (this.length > 0) {
+ this.splice(0);
+ this._undoPosition = 0;
+ }
+ }
+};
+
+
+var PlacesTransactions = {
+ /**
+ * @see Batches in the module documentation.
+ */
+ batch(aToBatch) {
+ if (Array.isArray(aToBatch)) {
+ if (aToBatch.length == 0)
+ throw new Error("aToBatch must not be an empty array");
+
+ if (aToBatch.some(
+ o => !TransactionsHistory.isProxifiedTransactionObject(o))) {
+ throw new Error("aToBatch contains non-transaction element");
+ }
+ return TransactionsManager.batch(function* () {
+ for (let txn of aToBatch) {
+ try {
+ yield txn.transact();
+ }
+ catch (ex) {
+ console.error(ex);
+ }
+ }
+ });
+ }
+ if (typeof(aToBatch) == "function") {
+ return TransactionsManager.batch(aToBatch);
+ }
+
+ throw new Error("aToBatch must be either a function or a transactions array");
+ },
+
+ /**
+ * Asynchronously undo the transaction immediately after the current undo
+ * position in the transactions history in the reverse order, if any, and
+ * adjusts the undo position.
+ *
+ * @return {Promises). The promise always resolves.
+ * @note All undo manager operations are queued. This means that transactions
+ * history may change by the time your request is fulfilled.
+ */
+ undo() {
+ return TransactionsManager.undo();
+ },
+
+ /**
+ * Asynchronously redo the transaction immediately before the current undo
+ * position in the transactions history, if any, and adjusts the undo
+ * position.
+ *
+ * @return {Promises). The promise always resolves.
+ * @note All undo manager operations are queued. This means that transactions
+ * history may change by the time your request is fulfilled.
+ */
+ redo() {
+ return TransactionsManager.redo();
+ },
+
+ /**
+ * Asynchronously clear the undo, redo, or all entries from the transactions
+ * history.
+ *
+ * @param [optional] aUndoEntries
+ * Whether or not to clear undo entries. Default: true.
+ * @param [optional] aRedoEntries
+ * Whether or not to clear undo entries. Default: true.
+ *
+ * @return {Promises). The promise always resolves.
+ * @throws if both aUndoEntries and aRedoEntries are false.
+ * @note All undo manager operations are queued. This means that transactions
+ * history may change by the time your request is fulfilled.
+ */
+ clearTransactionsHistory(aUndoEntries = true, aRedoEntries = true) {
+ return TransactionsManager.clearTransactionsHistory(aUndoEntries, aRedoEntries);
+ },
+
+ /**
+ * The numbers of entries in the transactions history.
+ */
+ get length() {
+ return TransactionsHistory.length;
+ },
+
+ /**
+ * Get the transaction history entry at a given index. Each entry consists
+ * of one or more transaction objects.
+ *
+ * @param aIndex
+ * the index of the entry to retrieve.
+ * @return an array of transaction objects in their undo order (that is,
+ * reversely to the order they were executed).
+ * @throw if aIndex is invalid (< 0 or >= length).
+ * @note the returned array is a clone of the history entry and is not
+ * kept in sync with the original entry if it changes.
+ */
+ entry(aIndex) {
+ if (!Number.isInteger(aIndex) || aIndex < 0 || aIndex >= this.length)
+ throw new Error("Invalid index");
+
+ return TransactionsHistory[aIndex];
+ },
+
+ /**
+ * The index of the top undo entry in the transactions history.
+ * If there are no undo entries, it equals to |length|.
+ * Entries past this point
+ * Entries at and past this point are redo entries.
+ */
+ get undoPosition() {
+ return TransactionsHistory.undoPosition;
+ },
+
+ /**
+ * Shortcut for accessing the top undo entry in the transaction history.
+ */
+ get topUndoEntry() {
+ return TransactionsHistory.topUndoEntry;
+ },
+
+ /**
+ * Shortcut for accessing the top redo entry in the transaction history.
+ */
+ get topRedoEntry() {
+ return TransactionsHistory.topRedoEntry;
+ }
+};
+
+/**
+ * Helper for serializing the calls to TransactionsManager methods. It allows
+ * us to guarantee that the order in which TransactionsManager asynchronous
+ * methods are called also enforces the order in which they're executed, and
+ * that they are never executed in parallel.
+ *
+ * In other words: Enqueuer.enqueue(aFunc1); Enqueuer.enqueue(aFunc2) is roughly
+ * the same as Task.spawn(aFunc1).then(Task.spawn(aFunc2)).
+ */
+function Enqueuer() {
+ this._promise = Promise.resolve();
+}
+Enqueuer.prototype = {
+ /**
+ * Spawn a functions once all previous functions enqueued are done running,
+ * and all promises passed to alsoWaitFor are no longer pending.
+ *
+ * @param aFunc
+ * @see Task.spawn.
+ * @return a promise that resolves once aFunc is done running. The promise
+ * "mirrors" the promise returned by aFunc.
+ */
+ enqueue(aFunc) {
+ let promise = this._promise.then(Task.async(aFunc));
+
+ // Propagate exceptions to the caller, but dismiss them internally.
+ this._promise = promise.catch(console.error);
+ return promise;
+ },
+
+ /**
+ * Same as above, but for a promise returned by a function that already run.
+ * This is useful, for example, for serializing transact calls with undo calls,
+ * even though transact has its own Enqueuer.
+ *
+ * @param aPromise
+ * any promise.
+ */
+ alsoWaitFor(aPromise) {
+ // We don't care if aPromise resolves or rejects, but just that is not
+ // pending anymore.
+ let promise = aPromise.catch(console.error);
+ this._promise = Promise.all([this._promise, promise]);
+ },
+
+ /**
+ * The promise for this queue.
+ */
+ get promise() {
+ return this._promise;
+ }
+};
+
+var TransactionsManager = {
+ // See the documentation at the top of this file. |transact| calls are not
+ // serialized with |batch| calls.
+ _mainEnqueuer: new Enqueuer(),
+ _transactEnqueuer: new Enqueuer(),
+
+ // Is a batch in progress? set when we enter a batch function and unset when
+ // it's execution is done.
+ _batching: false,
+
+ // If a batch started, this indicates if we've already created an entry in the
+ // transactions history for the batch (i.e. if at least one transaction was
+ // executed successfully).
+ _createdBatchEntry: false,
+
+ // Transactions object should never be recycled (that is, |execute| should
+ // only be called once (or not at all) after they're constructed.
+ // This keeps track of all transactions which were executed.
+ _executedTransactions: new WeakSet(),
+
+ transact(aTxnProxy) {
+ let rawTxn = TransactionsHistory.getRawTransaction(aTxnProxy);
+ if (!rawTxn)
+ throw new Error("|transact| was called with an unexpected object");
+
+ if (this._executedTransactions.has(rawTxn))
+ throw new Error("Transactions objects may not be recycled.");
+
+ // Add it in advance so one doesn't accidentally do
+ // sameTxn.transact(); sameTxn.transact();
+ this._executedTransactions.add(rawTxn);
+
+ let promise = this._transactEnqueuer.enqueue(function* () {
+ // Don't try to catch exceptions. If execute fails, we better not add the
+ // transaction to the undo stack.
+ let retval = yield rawTxn.execute();
+
+ let forceNewEntry = !this._batching || !this._createdBatchEntry;
+ TransactionsHistory.add(aTxnProxy, forceNewEntry);
+ if (this._batching)
+ this._createdBatchEntry = true;
+
+ this._updateCommandsOnActiveWindow();
+ return retval;
+ }.bind(this));
+ this._mainEnqueuer.alsoWaitFor(promise);
+ return promise;
+ },
+
+ batch(aTask) {
+ return this._mainEnqueuer.enqueue(function* () {
+ this._batching = true;
+ this._createdBatchEntry = false;
+ let rv;
+ try {
+ // We should return here, but bug 958949 makes that impossible.
+ rv = (yield Task.spawn(aTask));
+ }
+ finally {
+ this._batching = false;
+ this._createdBatchEntry = false;
+ }
+ return rv;
+ }.bind(this));
+ },
+
+ /**
+ * Undo the top undo entry, if any, and update the undo position accordingly.
+ */
+ undo() {
+ let promise = this._mainEnqueuer.enqueue(function* () {
+ let entry = TransactionsHistory.topUndoEntry;
+ if (!entry)
+ return;
+
+ for (let txnProxy of entry) {
+ try {
+ yield TransactionsHistory.getRawTransaction(txnProxy).undo();
+ }
+ catch (ex) {
+ // If one transaction is broken, it's not safe to work with any other
+ // undo entry. Report the error and clear the undo history.
+ console.error(ex,
+ "Couldn't undo a transaction, clearing all undo entries.");
+ TransactionsHistory.clearUndoEntries();
+ return;
+ }
+ }
+ TransactionsHistory._undoPosition++;
+ this._updateCommandsOnActiveWindow();
+ }.bind(this));
+ this._transactEnqueuer.alsoWaitFor(promise);
+ return promise;
+ },
+
+ /**
+ * Redo the top redo entry, if any, and update the undo position accordingly.
+ */
+ redo() {
+ let promise = this._mainEnqueuer.enqueue(function* () {
+ let entry = TransactionsHistory.topRedoEntry;
+ if (!entry)
+ return;
+
+ for (let i = entry.length - 1; i >= 0; i--) {
+ let transaction = TransactionsHistory.getRawTransaction(entry[i]);
+ try {
+ if (transaction.redo)
+ yield transaction.redo();
+ else
+ yield transaction.execute();
+ }
+ catch (ex) {
+ // If one transaction is broken, it's not safe to work with any other
+ // redo entry. Report the error and clear the undo history.
+ console.error(ex,
+ "Couldn't redo a transaction, clearing all redo entries.");
+ TransactionsHistory.clearRedoEntries();
+ return;
+ }
+ }
+ TransactionsHistory._undoPosition--;
+ this._updateCommandsOnActiveWindow();
+ }.bind(this));
+
+ this._transactEnqueuer.alsoWaitFor(promise);
+ return promise;
+ },
+
+ clearTransactionsHistory(aUndoEntries, aRedoEntries) {
+ let promise = this._mainEnqueuer.enqueue(function* () {
+ if (aUndoEntries && aRedoEntries)
+ TransactionsHistory.clearAllEntries();
+ else if (aUndoEntries)
+ TransactionsHistory.clearUndoEntries();
+ else if (aRedoEntries)
+ TransactionsHistory.clearRedoEntries();
+ else
+ throw new Error("either aUndoEntries or aRedoEntries should be true");
+ }.bind(this));
+
+ this._transactEnqueuer.alsoWaitFor(promise);
+ return promise;
+ },
+
+ // Updates commands in the undo group of the active window commands.
+ // Inactive windows commands will be updated on focus.
+ _updateCommandsOnActiveWindow() {
+ // Updating "undo" will cause a group update including "redo".
+ try {
+ let win = Services.focus.activeWindow;
+ if (win)
+ win.updateCommands("undo");
+ }
+ catch (ex) { console.error(ex, "Couldn't update undo commands"); }
+ }
+};
+
+/**
+ * Internal helper for defining the standard transactions and their input.
+ * It takes the required and optional properties, and generates the public
+ * constructor (which takes the input in the form of a plain object) which,
+ * when called, creates the argument-less "public" |execute| method by binding
+ * the input properties to the function arguments (required properties first,
+ * then the optional properties).
+ *
+ * If this seems confusing, look at the consumers.
+ *
+ * This magic serves two purposes:
+ * (1) It completely hides the transactions' internals from the module
+ * consumers.
+ * (2) It keeps each transaction implementation to what is about, bypassing
+ * all this bureaucracy while still validating input appropriately.
+ */
+function DefineTransaction(aRequiredProps = [], aOptionalProps = []) {
+ for (let prop of [...aRequiredProps, ...aOptionalProps]) {
+ if (!DefineTransaction.inputProps.has(prop))
+ throw new Error("Property '" + prop + "' is not defined");
+ }
+
+ let ctor = function (aInput) {
+ // We want to support both syntaxes:
+ // let t = new PlacesTransactions.NewBookmark(),
+ // let t = PlacesTransactions.NewBookmark()
+ if (this == PlacesTransactions)
+ return new ctor(aInput);
+
+ if (aRequiredProps.length > 0 || aOptionalProps.length > 0) {
+ // Bind the input properties to the arguments of execute.
+ let input = DefineTransaction.verifyInput(aInput, aRequiredProps,
+ aOptionalProps);
+ let executeArgs = [this,
+ ...aRequiredProps.map(prop => input[prop]),
+ ...aOptionalProps.map(prop => input[prop])];
+ this.execute = Function.bind.apply(this.execute, executeArgs);
+ }
+ return TransactionsHistory.proxifyTransaction(this);
+ };
+ return ctor;
+}
+
+function simpleValidateFunc(aCheck) {
+ return v => {
+ if (!aCheck(v))
+ throw new Error("Invalid value");
+ return v;
+ };
+}
+
+DefineTransaction.strValidate = simpleValidateFunc(v => typeof(v) == "string");
+DefineTransaction.strOrNullValidate =
+ simpleValidateFunc(v => typeof(v) == "string" || v === null);
+DefineTransaction.indexValidate =
+ simpleValidateFunc(v => Number.isInteger(v) &&
+ v >= PlacesUtils.bookmarks.DEFAULT_INDEX);
+DefineTransaction.guidValidate =
+ simpleValidateFunc(v => /^[a-zA-Z0-9\-_]{12}$/.test(v));
+
+function isPrimitive(v) {
+ return v === null || (typeof(v) != "object" && typeof(v) != "function");
+}
+
+DefineTransaction.annotationObjectValidate = function (obj) {
+ let checkProperty = (aPropName, aRequired, aCheckFunc) => {
+ if (aPropName in obj)
+ return aCheckFunc(obj[aPropName]);
+
+ return !aRequired;
+ };
+
+ if (obj &&
+ checkProperty("name", true, v => typeof(v) == "string" && v.length > 0) &&
+ checkProperty("expires", false, Number.isInteger) &&
+ checkProperty("flags", false, Number.isInteger) &&
+ checkProperty("value", false, isPrimitive) ) {
+ // Nothing else should be set
+ let validKeys = ["name", "value", "flags", "expires"];
+ if (Object.keys(obj).every( (k) => validKeys.includes(k)))
+ return obj;
+ }
+ throw new Error("Invalid annotation object");
+};
+
+DefineTransaction.urlValidate = function(url) {
+ // When this module is updated to use Bookmarks.jsm, we should actually
+ // convert nsIURIs/spec to URL objects.
+ if (url instanceof Components.interfaces.nsIURI)
+ return url;
+ let spec = url instanceof URL ? url.href : url;
+ return NetUtil.newURI(spec);
+};
+
+DefineTransaction.inputProps = new Map();
+DefineTransaction.defineInputProps =
+function (aNames, aValidationFunction, aDefaultValue) {
+ for (let name of aNames) {
+ // Workaround bug 449811.
+ let propName = name;
+ this.inputProps.set(propName, {
+ validateValue: function (aValue) {
+ if (aValue === undefined)
+ return aDefaultValue;
+ try {
+ return aValidationFunction(aValue);
+ }
+ catch (ex) {
+ throw new Error(`Invalid value for input property ${propName}`);
+ }
+ },
+
+ validateInput: function (aInput, aRequired) {
+ if (aRequired && !(propName in aInput))
+ throw new Error(`Required input property is missing: ${propName}`);
+ return this.validateValue(aInput[propName]);
+ },
+
+ isArrayProperty: false
+ });
+ }
+};
+
+DefineTransaction.defineArrayInputProp =
+function (aName, aBasePropertyName) {
+ let baseProp = this.inputProps.get(aBasePropertyName);
+ if (!baseProp)
+ throw new Error(`Unknown input property: ${aBasePropertyName}`);
+
+ this.inputProps.set(aName, {
+ validateValue: function (aValue) {
+ if (aValue == undefined)
+ return [];
+
+ if (!Array.isArray(aValue))
+ throw new Error(`${aName} input property value must be an array`);
+
+ // This also takes care of abandoning the global scope of the input
+ // array (through Array.prototype).
+ return aValue.map(baseProp.validateValue);
+ },
+
+ // We allow setting either the array property itself (e.g. urls), or a
+ // single element of it (url, in that example), that is then transformed
+ // into a single-element array.
+ validateInput: function (aInput, aRequired) {
+ if (aName in aInput) {
+ // It's not allowed to set both though.
+ if (aBasePropertyName in aInput) {
+ throw new Error(`It is not allowed to set both ${aName} and
+ ${aBasePropertyName} as input properties`);
+ }
+ let array = this.validateValue(aInput[aName]);
+ if (aRequired && array.length == 0) {
+ throw new Error(`Empty array passed for required input property:
+ ${aName}`);
+ }
+ return array;
+ }
+ // If the property is required and it's not set as is, check if the base
+ // property is set.
+ if (aRequired && !(aBasePropertyName in aInput))
+ throw new Error(`Required input property is missing: ${aName}`);
+
+ if (aBasePropertyName in aInput)
+ return [baseProp.validateValue(aInput[aBasePropertyName])];
+
+ return [];
+ },
+
+ isArrayProperty: true
+ });
+};
+
+DefineTransaction.validatePropertyValue =
+function (aProp, aInput, aRequired) {
+ return this.inputProps.get(aProp).validateInput(aInput, aRequired);
+};
+
+DefineTransaction.getInputObjectForSingleValue =
+function (aInput, aRequiredProps, aOptionalProps) {
+ // The following input forms may be deduced from a single value:
+ // * a single required property with or without optional properties (the given
+ // value is set to the required property).
+ // * a single optional property with no required properties.
+ if (aRequiredProps.length > 1 ||
+ (aRequiredProps.length == 0 && aOptionalProps.length > 1)) {
+ throw new Error("Transaction input isn't an object");
+ }
+
+ let propName = aRequiredProps.length == 1 ?
+ aRequiredProps[0] : aOptionalProps[0];
+ let propValue =
+ this.inputProps.get(propName).isArrayProperty && !Array.isArray(aInput) ?
+ [aInput] : aInput;
+ return { [propName]: propValue };
+};
+
+DefineTransaction.verifyInput =
+function (aInput, aRequiredProps = [], aOptionalProps = []) {
+ if (aRequiredProps.length == 0 && aOptionalProps.length == 0)
+ return {};
+
+ // If there's just a single required/optional property, we allow passing it
+ // as is, so, for example, one could do PlacesTransactions.RemoveItem(myGuid)
+ // rather than PlacesTransactions.RemoveItem({ guid: myGuid}).
+ // This shortcut isn't supported for "complex" properties - e.g. one cannot
+ // pass an annotation object this way (note there is no use case for this at
+ // the moment anyway).
+ let input = aInput;
+ let isSinglePropertyInput =
+ isPrimitive(aInput) ||
+ Array.isArray(aInput) ||
+ (aInput instanceof Components.interfaces.nsISupports);
+ if (isSinglePropertyInput) {
+ input = this.getInputObjectForSingleValue(aInput,
+ aRequiredProps,
+ aOptionalProps);
+ }
+
+ let fixedInput = { };
+ for (let prop of aRequiredProps) {
+ fixedInput[prop] = this.validatePropertyValue(prop, input, true);
+ }
+ for (let prop of aOptionalProps) {
+ fixedInput[prop] = this.validatePropertyValue(prop, input, false);
+ }
+
+ return fixedInput;
+};
+
+// Update the documentation at the top of this module if you add or
+// remove properties.
+DefineTransaction.defineInputProps(["url", "feedUrl", "siteUrl"],
+ DefineTransaction.urlValidate, null);
+DefineTransaction.defineInputProps(["guid", "parentGuid", "newParentGuid"],
+ DefineTransaction.guidValidate);
+DefineTransaction.defineInputProps(["title"],
+ DefineTransaction.strOrNullValidate, null);
+DefineTransaction.defineInputProps(["keyword", "oldKeyword", "postData", "tag",
+ "excludingAnnotation"],
+ DefineTransaction.strValidate, "");
+DefineTransaction.defineInputProps(["index", "newIndex"],
+ DefineTransaction.indexValidate,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+DefineTransaction.defineInputProps(["annotation"],
+ DefineTransaction.annotationObjectValidate);
+DefineTransaction.defineArrayInputProp("guids", "guid");
+DefineTransaction.defineArrayInputProp("urls", "url");
+DefineTransaction.defineArrayInputProp("tags", "tag");
+DefineTransaction.defineArrayInputProp("annotations", "annotation");
+DefineTransaction.defineArrayInputProp("excludingAnnotations",
+ "excludingAnnotation");
+
+/**
+ * Internal helper for implementing the execute method of NewBookmark, NewFolder
+ * and NewSeparator.
+ *
+ * @param aTransaction
+ * The transaction object
+ * @param aParentGuid
+ * The GUID of the parent folder
+ * @param aCreateItemFunction(aParentId, aGuidToRestore)
+ * The function to be called for creating the item on execute and redo.
+ * It should return the itemId for the new item
+ * - aGuidToRestore - the GUID to set for the item (used for redo).
+ * @param [optional] aOnUndo
+ * an additional function to call after undo
+ * @param [optional] aOnRedo
+ * an additional function to call after redo
+ */
+function* ExecuteCreateItem(aTransaction, aParentGuid, aCreateItemFunction,
+ aOnUndo = null, aOnRedo = null) {
+ let parentId = yield PlacesUtils.promiseItemId(aParentGuid),
+ itemId = yield aCreateItemFunction(parentId, ""),
+ guid = yield PlacesUtils.promiseItemGuid(itemId);
+
+ // On redo, we'll restore the date-added and last-modified properties.
+ let dateAdded = 0, lastModified = 0;
+ aTransaction.undo = function* () {
+ if (dateAdded == 0) {
+ dateAdded = PlacesUtils.bookmarks.getItemDateAdded(itemId);
+ lastModified = PlacesUtils.bookmarks.getItemLastModified(itemId);
+ }
+ PlacesUtils.bookmarks.removeItem(itemId);
+ if (aOnUndo) {
+ yield aOnUndo();
+ }
+ };
+ aTransaction.redo = function* () {
+ parentId = yield PlacesUtils.promiseItemId(aParentGuid);
+ itemId = yield aCreateItemFunction(parentId, guid);
+ if (aOnRedo)
+ yield aOnRedo();
+
+ // aOnRedo is called first to make sure it doesn't override
+ // lastModified.
+ PlacesUtils.bookmarks.setItemDateAdded(itemId, dateAdded);
+ PlacesUtils.bookmarks.setItemLastModified(itemId, lastModified);
+ PlacesUtils.bookmarks.setItemLastModified(parentId, dateAdded);
+ };
+ return guid;
+}
+
+/**
+ * Creates items (all types) from a bookmarks tree representation, as defined
+ * in PlacesUtils.promiseBookmarksTree.
+ *
+ * @param aBookmarksTree
+ * the bookmarks tree object. You may pass either a bookmarks tree
+ * returned by promiseBookmarksTree, or a manually defined one.
+ * @param [optional] aRestoring (default: false)
+ * Whether or not the items are restored. Only in restore mode, are
+ * the guid, dateAdded and lastModified properties honored.
+ * @param [optional] aExcludingAnnotations
+ * Array of annotations names to ignore in aBookmarksTree. This argument
+ * is ignored if aRestoring is set.
+ * @note the id, root and charset properties of items in aBookmarksTree are
+ * always ignored. The index property is ignored for all items but the
+ * root one.
+ * @return {Promise}
+ */
+function* createItemsFromBookmarksTree(aBookmarksTree, aRestoring = false,
+ aExcludingAnnotations = []) {
+ function extractLivemarkDetails(aAnnos) {
+ let feedURI = null, siteURI = null;
+ aAnnos = aAnnos.filter(
+ aAnno => {
+ switch (aAnno.name) {
+ case PlacesUtils.LMANNO_FEEDURI:
+ feedURI = NetUtil.newURI(aAnno.value);
+ return false;
+ case PlacesUtils.LMANNO_SITEURI:
+ siteURI = NetUtil.newURI(aAnno.value);
+ return false;
+ default:
+ return true;
+ }
+ } );
+ return [feedURI, siteURI];
+ }
+
+ function* createItem(aItem,
+ aParentGuid,
+ aIndex = PlacesUtils.bookmarks.DEFAULT_INDEX) {
+ let itemId;
+ let guid = aRestoring ? aItem.guid : undefined;
+ let parentId = yield PlacesUtils.promiseItemId(aParentGuid);
+ let annos = aItem.annos ? [...aItem.annos] : [];
+ switch (aItem.type) {
+ case PlacesUtils.TYPE_X_MOZ_PLACE: {
+ let uri = NetUtil.newURI(aItem.uri);
+ itemId = PlacesUtils.bookmarks.insertBookmark(
+ parentId, uri, aIndex, aItem.title, guid);
+ if ("keyword" in aItem) {
+ yield PlacesUtils.keywords.insert({
+ keyword: aItem.keyword,
+ url: uri.spec
+ });
+ }
+ if ("tags" in aItem) {
+ PlacesUtils.tagging.tagURI(uri, aItem.tags.split(","));
+ }
+ break;
+ }
+ case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER: {
+ // Either a folder or a livemark
+ let [feedURI, siteURI] = extractLivemarkDetails(annos);
+ if (!feedURI) {
+ itemId = PlacesUtils.bookmarks.createFolder(
+ parentId, aItem.title, aIndex, guid);
+ if (guid === undefined)
+ guid = yield PlacesUtils.promiseItemGuid(itemId);
+ if ("children" in aItem) {
+ for (let child of aItem.children) {
+ yield createItem(child, guid);
+ }
+ }
+ }
+ else {
+ let livemark =
+ yield PlacesUtils.livemarks.addLivemark({ title: aItem.title
+ , feedURI: feedURI
+ , siteURI: siteURI
+ , parentId: parentId
+ , index: aIndex
+ , guid: guid});
+ itemId = livemark.id;
+ }
+ break;
+ }
+ case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR: {
+ itemId = PlacesUtils.bookmarks.insertSeparator(parentId, aIndex, guid);
+ break;
+ }
+ }
+ if (annos.length > 0) {
+ if (!aRestoring && aExcludingAnnotations.length > 0) {
+ annos = annos.filter(a => !aExcludingAnnotations.includes(a.name));
+
+ }
+
+ PlacesUtils.setAnnotationsForItem(itemId, annos);
+ }
+
+ if (aRestoring) {
+ if ("dateAdded" in aItem)
+ PlacesUtils.bookmarks.setItemDateAdded(itemId, aItem.dateAdded);
+ if ("lastModified" in aItem)
+ PlacesUtils.bookmarks.setItemLastModified(itemId, aItem.lastModified);
+ }
+ return itemId;
+ }
+ return yield createItem(aBookmarksTree,
+ aBookmarksTree.parentGuid,
+ aBookmarksTree.index);
+}
+
+/** ***************************************************************************
+ * The Standard Places Transactions.
+ *
+ * See the documentation at the top of this file. The valid values for input
+ * are also documented there.
+ *****************************************************************************/
+
+var PT = PlacesTransactions;
+
+/**
+ * Transaction for creating a bookmark.
+ *
+ * Required Input Properties: url, parentGuid.
+ * Optional Input Properties: index, title, keyword, annotations, tags.
+ *
+ * When this transaction is executed, it's resolved to the new bookmark's GUID.
+ */
+PT.NewBookmark = DefineTransaction(["parentGuid", "url"],
+ ["index", "title", "keyword", "postData",
+ "annotations", "tags"]);
+PT.NewBookmark.prototype = Object.seal({
+ execute: function (aParentGuid, aURI, aIndex, aTitle,
+ aKeyword, aPostData, aAnnos, aTags) {
+ return ExecuteCreateItem(this, aParentGuid,
+ function* (parentId, guidToRestore = "") {
+ let itemId = PlacesUtils.bookmarks.insertBookmark(
+ parentId, aURI, aIndex, aTitle, guidToRestore);
+
+ if (aKeyword) {
+ yield PlacesUtils.keywords.insert({
+ url: aURI.spec,
+ keyword: aKeyword,
+ postData: aPostData
+ });
+ }
+ if (aAnnos.length) {
+ PlacesUtils.setAnnotationsForItem(itemId, aAnnos);
+ }
+ if (aTags.length > 0) {
+ let currentTags = PlacesUtils.tagging.getTagsForURI(aURI);
+ aTags = aTags.filter(t => !currentTags.includes(t));
+ PlacesUtils.tagging.tagURI(aURI, aTags);
+ }
+
+ return itemId;
+ },
+ function _additionalOnUndo() {
+ if (aTags.length > 0) {
+ PlacesUtils.tagging.untagURI(aURI, aTags);
+ }
+ });
+ }
+});
+
+/**
+ * Transaction for creating a folder.
+ *
+ * Required Input Properties: title, parentGuid.
+ * Optional Input Properties: index, annotations.
+ *
+ * When this transaction is executed, it's resolved to the new folder's GUID.
+ */
+PT.NewFolder = DefineTransaction(["parentGuid", "title"],
+ ["index", "annotations"]);
+PT.NewFolder.prototype = Object.seal({
+ execute: function (aParentGuid, aTitle, aIndex, aAnnos) {
+ return ExecuteCreateItem(this, aParentGuid,
+ function* (parentId, guidToRestore = "") {
+ let itemId = PlacesUtils.bookmarks.createFolder(
+ parentId, aTitle, aIndex, guidToRestore);
+ if (aAnnos.length > 0)
+ PlacesUtils.setAnnotationsForItem(itemId, aAnnos);
+ return itemId;
+ });
+ }
+});
+
+/**
+ * Transaction for creating a separator.
+ *
+ * Required Input Properties: parentGuid.
+ * Optional Input Properties: index.
+ *
+ * When this transaction is executed, it's resolved to the new separator's
+ * GUID.
+ */
+PT.NewSeparator = DefineTransaction(["parentGuid"], ["index"]);
+PT.NewSeparator.prototype = Object.seal({
+ execute: function (aParentGuid, aIndex) {
+ return ExecuteCreateItem(this, aParentGuid,
+ function* (parentId, guidToRestore = "") {
+ let itemId = PlacesUtils.bookmarks.insertSeparator(
+ parentId, aIndex, guidToRestore);
+ return itemId;
+ });
+ }
+});
+
+/**
+ * Transaction for creating a live bookmark (see mozIAsyncLivemarks for the
+ * semantics).
+ *
+ * Required Input Properties: feedUrl, title, parentGuid.
+ * Optional Input Properties: siteUrl, index, annotations.
+ *
+ * When this transaction is executed, it's resolved to the new livemark's
+ * GUID.
+ */
+PT.NewLivemark = DefineTransaction(["feedUrl", "title", "parentGuid"],
+ ["siteUrl", "index", "annotations"]);
+PT.NewLivemark.prototype = Object.seal({
+ execute: function* (aFeedURI, aTitle, aParentGuid, aSiteURI, aIndex, aAnnos) {
+ let livemarkInfo = { title: aTitle
+ , feedURI: aFeedURI
+ , siteURI: aSiteURI
+ , index: aIndex };
+ let createItem = function* () {
+ livemarkInfo.parentId = yield PlacesUtils.promiseItemId(aParentGuid);
+ let livemark = yield PlacesUtils.livemarks.addLivemark(livemarkInfo);
+ if (aAnnos.length > 0)
+ PlacesUtils.setAnnotationsForItem(livemark.id, aAnnos);
+
+ if ("dateAdded" in livemarkInfo) {
+ PlacesUtils.bookmarks.setItemDateAdded(livemark.id,
+ livemarkInfo.dateAdded);
+ PlacesUtils.bookmarks.setItemLastModified(livemark.id,
+ livemarkInfo.lastModified);
+ }
+ return livemark;
+ };
+
+ let livemark = yield createItem();
+ this.undo = function* () {
+ livemarkInfo.guid = livemark.guid;
+ if (!("dateAdded" in livemarkInfo)) {
+ livemarkInfo.dateAdded =
+ PlacesUtils.bookmarks.getItemDateAdded(livemark.id);
+ livemarkInfo.lastModified =
+ PlacesUtils.bookmarks.getItemLastModified(livemark.id);
+ }
+ yield PlacesUtils.livemarks.removeLivemark(livemark);
+ };
+ this.redo = function* () {
+ livemark = yield createItem();
+ };
+ return livemark.guid;
+ }
+});
+
+/**
+ * Transaction for moving an item.
+ *
+ * Required Input Properties: guid, newParentGuid.
+ * Optional Input Properties newIndex.
+ */
+PT.Move = DefineTransaction(["guid", "newParentGuid"], ["newIndex"]);
+PT.Move.prototype = Object.seal({
+ execute: function* (aGuid, aNewParentGuid, aNewIndex) {
+ let itemId = yield PlacesUtils.promiseItemId(aGuid),
+ oldParentId = PlacesUtils.bookmarks.getFolderIdForItem(itemId),
+ oldIndex = PlacesUtils.bookmarks.getItemIndex(itemId),
+ newParentId = yield PlacesUtils.promiseItemId(aNewParentGuid);
+
+ PlacesUtils.bookmarks.moveItem(itemId, newParentId, aNewIndex);
+
+ let undoIndex = PlacesUtils.bookmarks.getItemIndex(itemId);
+ this.undo = () => {
+ // Moving down in the same parent takes in count removal of the item
+ // so to revert positions we must move to oldIndex + 1
+ if (newParentId == oldParentId && oldIndex > undoIndex)
+ PlacesUtils.bookmarks.moveItem(itemId, oldParentId, oldIndex + 1);
+ else
+ PlacesUtils.bookmarks.moveItem(itemId, oldParentId, oldIndex);
+ };
+ }
+});
+
+/**
+ * Transaction for setting the title for an item.
+ *
+ * Required Input Properties: guid, title.
+ */
+PT.EditTitle = DefineTransaction(["guid", "title"]);
+PT.EditTitle.prototype = Object.seal({
+ execute: function* (aGuid, aTitle) {
+ let itemId = yield PlacesUtils.promiseItemId(aGuid),
+ oldTitle = PlacesUtils.bookmarks.getItemTitle(itemId);
+ PlacesUtils.bookmarks.setItemTitle(itemId, aTitle);
+ this.undo = () => { PlacesUtils.bookmarks.setItemTitle(itemId, oldTitle); };
+ }
+});
+
+/**
+ * Transaction for setting the URI for an item.
+ *
+ * Required Input Properties: guid, url.
+ */
+PT.EditUrl = DefineTransaction(["guid", "url"]);
+PT.EditUrl.prototype = Object.seal({
+ execute: function* (aGuid, aURI) {
+ let itemId = yield PlacesUtils.promiseItemId(aGuid),
+ oldURI = PlacesUtils.bookmarks.getBookmarkURI(itemId),
+ oldURITags = PlacesUtils.tagging.getTagsForURI(oldURI),
+ newURIAdditionalTags = null;
+ PlacesUtils.bookmarks.changeBookmarkURI(itemId, aURI);
+
+ // Move tags from old URI to new URI.
+ if (oldURITags.length > 0) {
+ // Only untag the old URI if this is the only bookmark.
+ if (PlacesUtils.getBookmarksForURI(oldURI, {}).length == 0)
+ PlacesUtils.tagging.untagURI(oldURI, oldURITags);
+
+ let currentNewURITags = PlacesUtils.tagging.getTagsForURI(aURI);
+ newURIAdditionalTags = oldURITags.filter(t => !currentNewURITags.includes(t));
+ if (newURIAdditionalTags)
+ PlacesUtils.tagging.tagURI(aURI, newURIAdditionalTags);
+ }
+
+ this.undo = () => {
+ PlacesUtils.bookmarks.changeBookmarkURI(itemId, oldURI);
+ // Move tags from new URI to old URI.
+ if (oldURITags.length > 0) {
+ // Only untag the new URI if this is the only bookmark.
+ if (newURIAdditionalTags && newURIAdditionalTags.length > 0 &&
+ PlacesUtils.getBookmarksForURI(aURI, {}).length == 0) {
+ PlacesUtils.tagging.untagURI(aURI, newURIAdditionalTags);
+ }
+
+ PlacesUtils.tagging.tagURI(oldURI, oldURITags);
+ }
+ };
+ }
+});
+
+/**
+ * Transaction for setting annotations for an item.
+ *
+ * Required Input Properties: guid, annotationObject
+ */
+PT.Annotate = DefineTransaction(["guids", "annotations"]);
+PT.Annotate.prototype = {
+ *execute(aGuids, aNewAnnos) {
+ let undoAnnosForItem = new Map(); // itemId => undoAnnos;
+ for (let guid of aGuids) {
+ let itemId = yield PlacesUtils.promiseItemId(guid);
+ let currentAnnos = PlacesUtils.getAnnotationsForItem(itemId);
+
+ let undoAnnos = [];
+ for (let newAnno of aNewAnnos) {
+ let currentAnno = currentAnnos.find(a => a.name == newAnno.name);
+ if (currentAnno) {
+ undoAnnos.push(currentAnno);
+ }
+ else {
+ // An unset value removes the annotation.
+ undoAnnos.push({ name: newAnno.name });
+ }
+ }
+ undoAnnosForItem.set(itemId, undoAnnos);
+
+ PlacesUtils.setAnnotationsForItem(itemId, aNewAnnos);
+ }
+
+ this.undo = function() {
+ for (let [itemId, undoAnnos] of undoAnnosForItem) {
+ PlacesUtils.setAnnotationsForItem(itemId, undoAnnos);
+ }
+ };
+ this.redo = function* () {
+ for (let guid of aGuids) {
+ let itemId = yield PlacesUtils.promiseItemId(guid);
+ PlacesUtils.setAnnotationsForItem(itemId, aNewAnnos);
+ }
+ };
+ }
+};
+
+/**
+ * Transaction for setting the keyword for a bookmark.
+ *
+ * Required Input Properties: guid, keyword.
+ */
+PT.EditKeyword = DefineTransaction(["guid", "keyword"],
+ ["postData", "oldKeyword"]);
+PT.EditKeyword.prototype = Object.seal({
+ execute: function* (aGuid, aKeyword, aPostData, aOldKeyword) {
+ let url;
+ let oldKeywordEntry;
+ if (aOldKeyword) {
+ oldKeywordEntry = yield PlacesUtils.keywords.fetch(aOldKeyword);
+ url = oldKeywordEntry.url;
+ yield PlacesUtils.keywords.remove(aOldKeyword);
+ }
+
+ if (aKeyword) {
+ if (!url) {
+ url = (yield PlacesUtils.bookmarks.fetch(aGuid)).url;
+ }
+ yield PlacesUtils.keywords.insert({
+ url: url,
+ keyword: aKeyword,
+ postData: aPostData || (oldKeywordEntry ? oldKeywordEntry.postData : "")
+ });
+ }
+
+ this.undo = function* () {
+ if (aKeyword) {
+ yield PlacesUtils.keywords.remove(aKeyword);
+ }
+ if (oldKeywordEntry) {
+ yield PlacesUtils.keywords.insert(oldKeywordEntry);
+ }
+ };
+ }
+});
+
+/**
+ * Transaction for sorting a folder by name.
+ *
+ * Required Input Properties: guid.
+ */
+PT.SortByName = DefineTransaction(["guid"]);
+PT.SortByName.prototype = {
+ execute: function* (aGuid) {
+ let itemId = yield PlacesUtils.promiseItemId(aGuid),
+ oldOrder = [], // [itemId] = old index
+ contents = PlacesUtils.getFolderContents(itemId, false, false).root,
+ count = contents.childCount;
+
+ // Sort between separators.
+ let newOrder = [], // nodes, in the new order.
+ preSep = []; // Temporary array for sorting each group of nodes.
+ let sortingMethod = (a, b) => {
+ if (PlacesUtils.nodeIsContainer(a) && !PlacesUtils.nodeIsContainer(b))
+ return -1;
+ if (!PlacesUtils.nodeIsContainer(a) && PlacesUtils.nodeIsContainer(b))
+ return 1;
+ return a.title.localeCompare(b.title);
+ };
+
+ for (let i = 0; i < count; ++i) {
+ let node = contents.getChild(i);
+ oldOrder[node.itemId] = i;
+ if (PlacesUtils.nodeIsSeparator(node)) {
+ if (preSep.length > 0) {
+ preSep.sort(sortingMethod);
+ newOrder = newOrder.concat(preSep);
+ preSep.splice(0, preSep.length);
+ }
+ newOrder.push(node);
+ }
+ else
+ preSep.push(node);
+ }
+ contents.containerOpen = false;
+
+ if (preSep.length > 0) {
+ preSep.sort(sortingMethod);
+ newOrder = newOrder.concat(preSep);
+ }
+
+ // Set the nex indexes.
+ let callback = {
+ runBatched: function() {
+ for (let i = 0; i < newOrder.length; ++i) {
+ PlacesUtils.bookmarks.setItemIndex(newOrder[i].itemId, i);
+ }
+ }
+ };
+ PlacesUtils.bookmarks.runInBatchMode(callback, null);
+
+ this.undo = () => {
+ let callback = {
+ runBatched: function() {
+ for (let item in oldOrder) {
+ PlacesUtils.bookmarks.setItemIndex(item, oldOrder[item]);
+ }
+ }
+ };
+ PlacesUtils.bookmarks.runInBatchMode(callback, null);
+ };
+ }
+};
+
+/**
+ * Transaction for removing an item (any type).
+ *
+ * Required Input Properties: guids.
+ */
+PT.Remove = DefineTransaction(["guids"]);
+PT.Remove.prototype = {
+ *execute(aGuids) {
+ function promiseBookmarksTree(guid) {
+ try {
+ return PlacesUtils.promiseBookmarksTree(guid);
+ }
+ catch (ex) {
+ throw new Error("Failed to get info for the specified item (guid: " +
+ guid + "). Ex: " + ex);
+ }
+ }
+
+ let toRestore = [];
+ for (let guid of aGuids) {
+ toRestore.push(yield promiseBookmarksTree(guid));
+ }
+
+ let removeThem = Task.async(function* () {
+ for (let guid of aGuids) {
+ PlacesUtils.bookmarks.removeItem(yield PlacesUtils.promiseItemId(guid));
+ }
+ });
+ yield removeThem();
+
+ this.undo = Task.async(function* () {
+ for (let info of toRestore) {
+ yield createItemsFromBookmarksTree(info, true);
+ }
+ });
+ this.redo = removeThem;
+ }
+};
+
+/**
+ * Transactions for removing all bookmarks for one or more urls.
+ *
+ * Required Input Properties: urls.
+ */
+PT.RemoveBookmarksForUrls = DefineTransaction(["urls"]);
+PT.RemoveBookmarksForUrls.prototype = {
+ *execute(aUrls) {
+ let guids = [];
+ for (let url of aUrls) {
+ yield PlacesUtils.bookmarks.fetch({ url }, info => {
+ guids.push(info.guid);
+ });
+ }
+ let removeTxn = TransactionsHistory.getRawTransaction(PT.Remove(guids));
+ yield removeTxn.execute();
+ this.undo = removeTxn.undo.bind(removeTxn);
+ this.redo = removeTxn.redo.bind(removeTxn);
+ }
+};
+
+/**
+ * Transaction for tagging urls.
+ *
+ * Required Input Properties: urls, tags.
+ */
+PT.Tag = DefineTransaction(["urls", "tags"]);
+PT.Tag.prototype = {
+ execute: function* (aURIs, aTags) {
+ let onUndo = [], onRedo = [];
+ for (let uri of aURIs) {
+ // Workaround bug 449811.
+ let currentURI = uri;
+
+ let promiseIsBookmarked = function* () {
+ let deferred = Promise.defer();
+ PlacesUtils.asyncGetBookmarkIds(
+ currentURI, ids => { deferred.resolve(ids.length > 0); });
+ return deferred.promise;
+ };
+
+ if (yield promiseIsBookmarked(currentURI)) {
+ // Tagging is only allowed for bookmarked URIs (but see 424160).
+ let createTxn = TransactionsHistory.getRawTransaction(
+ PT.NewBookmark({ url: currentURI
+ , tags: aTags
+ , parentGuid: PlacesUtils.bookmarks.unfiledGuid }));
+ yield createTxn.execute();
+ onUndo.unshift(createTxn.undo.bind(createTxn));
+ onRedo.push(createTxn.redo.bind(createTxn));
+ }
+ else {
+ let currentTags = PlacesUtils.tagging.getTagsForURI(currentURI);
+ let newTags = aTags.filter(t => !currentTags.includes(t));
+ PlacesUtils.tagging.tagURI(currentURI, newTags);
+ onUndo.unshift(() => {
+ PlacesUtils.tagging.untagURI(currentURI, newTags);
+ });
+ onRedo.push(() => {
+ PlacesUtils.tagging.tagURI(currentURI, newTags);
+ });
+ }
+ }
+ this.undo = function* () {
+ for (let f of onUndo) {
+ yield f();
+ }
+ };
+ this.redo = function* () {
+ for (let f of onRedo) {
+ yield f();
+ }
+ };
+ }
+};
+
+/**
+ * Transaction for removing tags from a URI.
+ *
+ * Required Input Properties: urls.
+ * Optional Input Properties: tags.
+ *
+ * If |tags| is not set, all tags set for |url| are removed.
+ */
+PT.Untag = DefineTransaction(["urls"], ["tags"]);
+PT.Untag.prototype = {
+ execute: function* (aURIs, aTags) {
+ let onUndo = [], onRedo = [];
+ for (let uri of aURIs) {
+ // Workaround bug 449811.
+ let currentURI = uri;
+ let tagsToRemove;
+ let tagsSet = PlacesUtils.tagging.getTagsForURI(currentURI);
+ if (aTags.length > 0)
+ tagsToRemove = aTags.filter(t => tagsSet.includes(t));
+ else
+ tagsToRemove = tagsSet;
+ PlacesUtils.tagging.untagURI(currentURI, tagsToRemove);
+ onUndo.unshift(() => {
+ PlacesUtils.tagging.tagURI(currentURI, tagsToRemove);
+ });
+ onRedo.push(() => {
+ PlacesUtils.tagging.untagURI(currentURI, tagsToRemove);
+ });
+ }
+ this.undo = function* () {
+ for (let f of onUndo) {
+ yield f();
+ }
+ };
+ this.redo = function* () {
+ for (let f of onRedo) {
+ yield f();
+ }
+ };
+ }
+};
+
+/**
+ * Transaction for copying an item.
+ *
+ * Required Input Properties: guid, newParentGuid
+ * Optional Input Properties: newIndex, excludingAnnotations.
+ */
+PT.Copy = DefineTransaction(["guid", "newParentGuid"],
+ ["newIndex", "excludingAnnotations"]);
+PT.Copy.prototype = {
+ execute: function* (aGuid, aNewParentGuid, aNewIndex, aExcludingAnnotations) {
+ let creationInfo = null;
+ try {
+ creationInfo = yield PlacesUtils.promiseBookmarksTree(aGuid);
+ }
+ catch (ex) {
+ throw new Error("Failed to get info for the specified item (guid: " +
+ aGuid + "). Ex: " + ex);
+ }
+ creationInfo.parentGuid = aNewParentGuid;
+ creationInfo.index = aNewIndex;
+
+ let newItemId =
+ yield createItemsFromBookmarksTree(creationInfo, false,
+ aExcludingAnnotations);
+ let newItemInfo = null;
+ this.undo = function* () {
+ if (!newItemInfo) {
+ let newItemGuid = yield PlacesUtils.promiseItemGuid(newItemId);
+ newItemInfo = yield PlacesUtils.promiseBookmarksTree(newItemGuid);
+ }
+ PlacesUtils.bookmarks.removeItem(newItemId);
+ };
+ this.redo = function* () {
+ newItemId = yield createItemsFromBookmarksTree(newItemInfo, true);
+ }
+
+ return yield PlacesUtils.promiseItemGuid(newItemId);
+ }
+};
diff --git a/components/places/src/PlacesUtils.jsm b/components/places/src/PlacesUtils.jsm
new file mode 100644
index 000000000..ab753ba86
--- /dev/null
+++ b/components/places/src/PlacesUtils.jsm
@@ -0,0 +1,3870 @@
+/* -*- 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/. */
+
+this.EXPORTED_SYMBOLS = [
+ "PlacesUtils"
+, "PlacesAggregatedTransaction"
+, "PlacesCreateFolderTransaction"
+, "PlacesCreateBookmarkTransaction"
+, "PlacesCreateSeparatorTransaction"
+, "PlacesCreateLivemarkTransaction"
+, "PlacesMoveItemTransaction"
+, "PlacesRemoveItemTransaction"
+, "PlacesEditItemTitleTransaction"
+, "PlacesEditBookmarkURITransaction"
+, "PlacesSetItemAnnotationTransaction"
+, "PlacesSetPageAnnotationTransaction"
+, "PlacesEditBookmarkKeywordTransaction"
+, "PlacesEditBookmarkPostDataTransaction"
+, "PlacesEditItemDateAddedTransaction"
+, "PlacesEditItemLastModifiedTransaction"
+, "PlacesSortFolderByNameTransaction"
+, "PlacesTagURITransaction"
+, "PlacesUntagURITransaction"
+];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.importGlobalProperties(["URL"]);
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
+ "resource://gre/modules/Sqlite.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Bookmarks",
+ "resource://gre/modules/Bookmarks.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "History",
+ "resource://gre/modules/History.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+ "resource://gre/modules/AsyncShutdown.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesSyncUtils",
+ "resource://gre/modules/PlacesSyncUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
+ "resource:///modules/PlacesUIUtils.jsm");
+
+// The minimum amount of transactions before starting a batch. Usually we do
+// do incremental updates, a batch will cause views to completely
+// refresh instead.
+const MIN_TRANSACTIONS_FOR_BATCH = 5;
+
+// The transferable system converts "\r\n" to "\n" where needed.
+const NEWLINE = "\r\n";
+
+function QI_node(aNode, aIID) {
+ var result = null;
+ try {
+ result = aNode.QueryInterface(aIID);
+ }
+ catch (e) {
+ }
+ return result;
+}
+function asContainer(aNode) {
+ return QI_node(aNode, Ci.nsINavHistoryContainerResultNode);
+}
+function asQuery(aNode) {
+ return QI_node(aNode, Ci.nsINavHistoryQueryResultNode);
+}
+
+/**
+ * Sends a bookmarks notification through the given observers.
+ *
+ * @param observers
+ * array of nsINavBookmarkObserver objects.
+ * @param notification
+ * the notification name.
+ * @param args
+ * array of arguments to pass to the notification.
+ */
+function notify(observers, notification, args) {
+ for (let observer of observers) {
+ try {
+ observer[notification](...args);
+ } catch (ex) {}
+ }
+}
+
+/**
+ * Sends a keyword change notification.
+ *
+ * @param url
+ * the url to notify about.
+ * @param keyword
+ * The keyword to notify, or empty string if a keyword was removed.
+ */
+function* notifyKeywordChange(url, keyword, source) {
+ // Notify bookmarks about the removal.
+ let bookmarks = [];
+ yield PlacesUtils.bookmarks.fetch({ url }, b => bookmarks.push(b));
+ // We don't want to yield in the gIgnoreKeywordNotifications section.
+ for (let bookmark of bookmarks) {
+ bookmark.id = yield PlacesUtils.promiseItemId(bookmark.guid);
+ bookmark.parentId = yield PlacesUtils.promiseItemId(bookmark.parentGuid);
+ }
+ let observers = PlacesUtils.bookmarks.getObservers();
+ gIgnoreKeywordNotifications = true;
+ for (let bookmark of bookmarks) {
+ notify(observers, "onItemChanged", [ bookmark.id, "keyword", false,
+ keyword,
+ bookmark.lastModified * 1000,
+ bookmark.type,
+ bookmark.parentId,
+ bookmark.guid, bookmark.parentGuid,
+ "", source
+ ]);
+ }
+ gIgnoreKeywordNotifications = false;
+}
+
+/**
+ * Serializes the given node in JSON format.
+ *
+ * @param aNode
+ * An nsINavHistoryResultNode
+ * @param aIsLivemark
+ * Whether the node represents a livemark.
+ */
+function serializeNode(aNode, aIsLivemark) {
+ let data = {};
+
+ data.title = aNode.title;
+ data.id = aNode.itemId;
+ data.livemark = aIsLivemark;
+
+ let guid = aNode.bookmarkGuid;
+ if (guid) {
+ data.itemGuid = guid;
+ if (aNode.parent)
+ data.parent = aNode.parent.itemId;
+ let grandParent = aNode.parent && aNode.parent.parent;
+ if (grandParent)
+ data.grandParentId = grandParent.itemId;
+
+ data.dateAdded = aNode.dateAdded;
+ data.lastModified = aNode.lastModified;
+
+ let annos = PlacesUtils.getAnnotationsForItem(data.id);
+ if (annos.length > 0)
+ data.annos = annos;
+ }
+
+ if (PlacesUtils.nodeIsURI(aNode)) {
+ // Check for url validity.
+ NetUtil.newURI(aNode.uri);
+
+ // Tag root accepts only folder nodes, not URIs.
+ if (data.parent == PlacesUtils.tagsFolderId)
+ throw new Error("Unexpected node type");
+
+ data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
+ data.uri = aNode.uri;
+
+ if (aNode.tags)
+ data.tags = aNode.tags;
+ }
+ else if (PlacesUtils.nodeIsContainer(aNode)) {
+ // Tag containers accept only uri nodes.
+ if (data.grandParentId == PlacesUtils.tagsFolderId)
+ throw new Error("Unexpected node type");
+
+ let concreteId = PlacesUtils.getConcreteItemId(aNode);
+ if (concreteId != -1) {
+ // This is a bookmark or a tag container.
+ if (PlacesUtils.nodeIsQuery(aNode) || concreteId != aNode.itemId) {
+ // This is a folder shortcut.
+ data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
+ data.uri = aNode.uri;
+ data.concreteId = concreteId;
+ }
+ else {
+ // This is a bookmark folder.
+ data.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
+ }
+ }
+ else {
+ // This is a grouped container query, dynamically generated.
+ data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
+ data.uri = aNode.uri;
+ }
+ }
+ else if (PlacesUtils.nodeIsSeparator(aNode)) {
+ // Tag containers don't accept separators.
+ if (data.parent == PlacesUtils.tagsFolderId ||
+ data.grandParentId == PlacesUtils.tagsFolderId)
+ throw new Error("Unexpected node type");
+
+ data.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
+ }
+
+ return JSON.stringify(data);
+}
+
+// Imposed to limit database size.
+const DB_URL_LENGTH_MAX = 65536;
+const DB_TITLE_LENGTH_MAX = 4096;
+
+/**
+ * List of bookmark object validators, one per each known property.
+ * Validators must throw if the property value is invalid and return a fixed up
+ * version of the value, if needed.
+ */
+const BOOKMARK_VALIDATORS = Object.freeze({
+ guid: simpleValidateFunc(v => PlacesUtils.isValidGuid(v)),
+ parentGuid: simpleValidateFunc(v => typeof(v) == "string" &&
+ /^[a-zA-Z0-9\-_]{12}$/.test(v)),
+ index: simpleValidateFunc(v => Number.isInteger(v) &&
+ v >= PlacesUtils.bookmarks.DEFAULT_INDEX),
+ dateAdded: simpleValidateFunc(v => v.constructor.name == "Date"),
+ lastModified: simpleValidateFunc(v => v.constructor.name == "Date"),
+ type: simpleValidateFunc(v => Number.isInteger(v) &&
+ [ PlacesUtils.bookmarks.TYPE_BOOKMARK
+ , PlacesUtils.bookmarks.TYPE_FOLDER
+ , PlacesUtils.bookmarks.TYPE_SEPARATOR ].includes(v)),
+ title: v => {
+ simpleValidateFunc(val => val === null || typeof(val) == "string").call(this, v);
+ if (!v)
+ return null;
+ return v.slice(0, DB_TITLE_LENGTH_MAX);
+ },
+ url: v => {
+ simpleValidateFunc(val => (typeof(val) == "string" && val.length <= DB_URL_LENGTH_MAX) ||
+ (val instanceof Ci.nsIURI && val.spec.length <= DB_URL_LENGTH_MAX) ||
+ (val instanceof URL && val.href.length <= DB_URL_LENGTH_MAX)
+ ).call(this, v);
+ if (typeof(v) === "string")
+ return new URL(v);
+ if (v instanceof Ci.nsIURI)
+ return new URL(v.spec);
+ return v;
+ },
+ source: simpleValidateFunc(v => Number.isInteger(v) &&
+ Object.values(PlacesUtils.bookmarks.SOURCES).includes(v)),
+});
+
+// Sync bookmark records can contain additional properties.
+const SYNC_BOOKMARK_VALIDATORS = Object.freeze({
+ // Sync uses Places GUIDs for all records except roots.
+ syncId: simpleValidateFunc(v => typeof v == "string" && (
+ (PlacesSyncUtils.bookmarks.ROOTS.includes(v) ||
+ PlacesUtils.isValidGuid(v)))),
+ parentSyncId: v => SYNC_BOOKMARK_VALIDATORS.syncId(v),
+ // Sync uses kinds instead of types, which distinguish between livemarks,
+ // queries, and smart bookmarks.
+ kind: simpleValidateFunc(v => typeof v == "string" &&
+ Object.values(PlacesSyncUtils.bookmarks.KINDS).includes(v)),
+ query: simpleValidateFunc(v => v === null || (typeof v == "string" && v)),
+ folder: simpleValidateFunc(v => typeof v == "string" && v &&
+ v.length <= Ci.nsITaggingService.MAX_TAG_LENGTH),
+ tags: v => {
+ if (v === null) {
+ return [];
+ }
+ if (!Array.isArray(v)) {
+ throw new Error("Invalid tag array");
+ }
+ for (let tag of v) {
+ if (typeof tag != "string" || !tag ||
+ tag.length > Ci.nsITaggingService.MAX_TAG_LENGTH) {
+ throw new Error(`Invalid tag: ${tag}`);
+ }
+ }
+ return v;
+ },
+ keyword: simpleValidateFunc(v => v === null || typeof v == "string"),
+ description: simpleValidateFunc(v => v === null || typeof v == "string"),
+ loadInSidebar: simpleValidateFunc(v => v === true || v === false),
+ feed: v => v === null ? v : BOOKMARK_VALIDATORS.url(v),
+ site: v => v === null ? v : BOOKMARK_VALIDATORS.url(v),
+ title: BOOKMARK_VALIDATORS.title,
+ url: BOOKMARK_VALIDATORS.url,
+});
+
+this.PlacesUtils = {
+ // Place entries that are containers, e.g. bookmark folders or queries.
+ TYPE_X_MOZ_PLACE_CONTAINER: "text/x-moz-place-container",
+ // Place entries that are bookmark separators.
+ TYPE_X_MOZ_PLACE_SEPARATOR: "text/x-moz-place-separator",
+ // Place entries that are not containers or separators
+ TYPE_X_MOZ_PLACE: "text/x-moz-place",
+ // Place entries in shortcut url format (url\ntitle)
+ TYPE_X_MOZ_URL: "text/x-moz-url",
+ // Place entries formatted as HTML anchors
+ TYPE_HTML: "text/html",
+ // Place entries as raw URL text
+ TYPE_UNICODE: "text/unicode",
+ // Used to track the action that populated the clipboard.
+ TYPE_X_MOZ_PLACE_ACTION: "text/x-moz-place-action",
+
+ EXCLUDE_FROM_BACKUP_ANNO: "places/excludeFromBackup",
+ LMANNO_FEEDURI: "livemark/feedURI",
+ LMANNO_SITEURI: "livemark/siteURI",
+ POST_DATA_ANNO: "bookmarkProperties/POSTData",
+ READ_ONLY_ANNO: "placesInternal/READ_ONLY",
+ CHARSET_ANNO: "URIProperties/characterSet",
+ MOBILE_ROOT_ANNO: "mobile/bookmarksRoot",
+
+ TOPIC_SHUTDOWN: "places-shutdown",
+ TOPIC_INIT_COMPLETE: "places-init-complete",
+ TOPIC_DATABASE_LOCKED: "places-database-locked",
+ TOPIC_EXPIRATION_FINISHED: "places-expiration-finished",
+ TOPIC_FEEDBACK_UPDATED: "places-autocomplete-feedback-updated",
+ TOPIC_FAVICONS_EXPIRED: "places-favicons-expired",
+ TOPIC_VACUUM_STARTING: "places-vacuum-starting",
+ TOPIC_BOOKMARKS_RESTORE_BEGIN: "bookmarks-restore-begin",
+ TOPIC_BOOKMARKS_RESTORE_SUCCESS: "bookmarks-restore-success",
+ TOPIC_BOOKMARKS_RESTORE_FAILED: "bookmarks-restore-failed",
+
+ asContainer: aNode => asContainer(aNode),
+ asQuery: aNode => asQuery(aNode),
+
+ endl: NEWLINE,
+
+ /**
+ * Makes a URI from a spec.
+ * @param aSpec
+ * The string spec of the URI
+ * @returns A URI object for the spec.
+ */
+ _uri: function PU__uri(aSpec) {
+ return NetUtil.newURI(aSpec);
+ },
+
+ /**
+ * Is a string a valid GUID?
+ *
+ * @param guid: (String)
+ * @return (Boolean)
+ */
+ isValidGuid(guid) {
+ return typeof guid == "string" && guid &&
+ (/^[a-zA-Z0-9\-_]{12}$/.test(guid));
+ },
+
+ /**
+ * Converts a string or n URL object to an nsIURI.
+ *
+ * @param url (URL) or (String)
+ * the URL to convert.
+ * @return nsIURI for the given URL.
+ */
+ toURI(url) {
+ url = (url instanceof URL) ? url.href : url;
+
+ return NetUtil.newURI(url);
+ },
+
+ /**
+ * Convert a Date object to a PRTime (microseconds).
+ *
+ * @param date
+ * the Date object to convert.
+ * @return microseconds from the epoch.
+ */
+ toPRTime(date) {
+ return date * 1000;
+ },
+
+ /**
+ * Convert a PRTime to a Date object.
+ *
+ * @param time
+ * microseconds from the epoch.
+ * @return a Date object.
+ */
+ toDate(time) {
+ return new Date(parseInt(time / 1000));
+ },
+
+ /**
+ * Wraps a string in a nsISupportsString wrapper.
+ * @param aString
+ * The string to wrap.
+ * @returns A nsISupportsString object containing a string.
+ */
+ toISupportsString: function PU_toISupportsString(aString) {
+ let s = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ s.data = aString;
+ return s;
+ },
+
+ getFormattedString: function PU_getFormattedString(key, params) {
+ return bundle.formatStringFromName(key, params, params.length);
+ },
+
+ getString: function PU_getString(key) {
+ return bundle.GetStringFromName(key);
+ },
+
+ /**
+ * Makes a moz-action URI for the given action and set of parameters.
+ *
+ * @param type
+ * The action type.
+ * @param params
+ * A JS object of action params.
+ * @returns A moz-action URI as a string.
+ */
+ mozActionURI(type, params) {
+ let encodedParams = {};
+ for (let key in params) {
+ // Strip null or undefined.
+ // Regardless, don't encode them or they would be converted to a string.
+ if (params[key] === null || params[key] === undefined) {
+ continue;
+ }
+ encodedParams[key] = encodeURIComponent(params[key]);
+ }
+ return "moz-action:" + type + "," + JSON.stringify(encodedParams);
+ },
+
+ /**
+ * Determines whether or not a ResultNode is a Bookmark folder.
+ * @param aNode
+ * A result node
+ * @returns true if the node is a Bookmark folder, false otherwise
+ */
+ nodeIsFolder: function PU_nodeIsFolder(aNode) {
+ return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
+ aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT);
+ },
+
+ /**
+ * Determines whether or not a ResultNode represents a bookmarked URI.
+ * @param aNode
+ * A result node
+ * @returns true if the node represents a bookmarked URI, false otherwise
+ */
+ nodeIsBookmark: function PU_nodeIsBookmark(aNode) {
+ return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI &&
+ aNode.itemId != -1;
+ },
+
+ /**
+ * Determines whether or not a ResultNode is a Bookmark separator.
+ * @param aNode
+ * A result node
+ * @returns true if the node is a Bookmark separator, false otherwise
+ */
+ nodeIsSeparator: function PU_nodeIsSeparator(aNode) {
+ return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR;
+ },
+
+ /**
+ * Determines whether or not a ResultNode is a URL item.
+ * @param aNode
+ * A result node
+ * @returns true if the node is a URL item, false otherwise
+ */
+ nodeIsURI: function PU_nodeIsURI(aNode) {
+ return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
+ },
+
+ /**
+ * Determines whether or not a ResultNode is a Query item.
+ * @param aNode
+ * A result node
+ * @returns true if the node is a Query item, false otherwise
+ */
+ nodeIsQuery: function PU_nodeIsQuery(aNode) {
+ return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
+ },
+
+ /**
+ * Generator for a node's ancestors.
+ * @param aNode
+ * A result node
+ */
+ nodeAncestors: function* PU_nodeAncestors(aNode) {
+ let node = aNode.parent;
+ while (node) {
+ yield node;
+ node = node.parent;
+ }
+ },
+
+ /**
+ * Checks validity of an object, filling up default values for optional
+ * properties.
+ *
+ * @param validators (object)
+ * An object containing input validators. Keys should be field names;
+ * values should be validation functions.
+ * @param props (object)
+ * The object to validate.
+ * @param behavior (object) [optional]
+ * Object defining special behavior for some of the properties.
+ * The following behaviors may be optionally set:
+ * - requiredIf: if the provided condition is satisfied, then this
+ * property is required.
+ * - validIf: if the provided condition is not satisfied, then this
+ * property is invalid.
+ * - defaultValue: an undefined property should default to this value.
+ *
+ * @return a validated and normalized item.
+ * @throws if the object contains invalid data.
+ * @note any unknown properties are pass-through.
+ */
+ validateItemProperties(validators, props, behavior={}) {
+ if (!props)
+ throw new Error("Input should be a valid object");
+ // Make a shallow copy of `props` to avoid mutating the original object
+ // when filling in defaults.
+ let input = Object.assign({}, props);
+ let normalizedInput = {};
+ let required = new Set();
+ for (let prop in behavior) {
+ if (behavior[prop].hasOwnProperty("required") && behavior[prop].required) {
+ required.add(prop);
+ }
+ if (behavior[prop].hasOwnProperty("requiredIf") && behavior[prop].requiredIf(input)) {
+ required.add(prop);
+ }
+ if (behavior[prop].hasOwnProperty("validIf") && input[prop] !== undefined &&
+ !behavior[prop].validIf(input)) {
+ throw new Error(`Invalid value for property '${prop}': ${input[prop]}`);
+ }
+ if (behavior[prop].hasOwnProperty("defaultValue") && input[prop] === undefined) {
+ input[prop] = behavior[prop].defaultValue;
+ }
+ }
+
+ for (let prop in input) {
+ if (required.has(prop)) {
+ required.delete(prop);
+ } else if (input[prop] === undefined) {
+ // Skip undefined properties that are not required.
+ continue;
+ }
+ if (validators.hasOwnProperty(prop)) {
+ try {
+ normalizedInput[prop] = validators[prop](input[prop], input);
+ } catch (ex) {
+ throw new Error(`Invalid value for property '${prop}': ${input[prop]}`);
+ }
+ }
+ }
+ if (required.size > 0)
+ throw new Error(`The following properties were expected: ${[...required].join(", ")}`);
+ return normalizedInput;
+ },
+
+ BOOKMARK_VALIDATORS,
+ SYNC_BOOKMARK_VALIDATORS,
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver
+ , Ci.nsITransactionListener
+ ]),
+
+ _shutdownFunctions: [],
+ registerShutdownFunction: function PU_registerShutdownFunction(aFunc)
+ {
+ // If this is the first registered function, add the shutdown observer.
+ if (this._shutdownFunctions.length == 0) {
+ Services.obs.addObserver(this, this.TOPIC_SHUTDOWN, false);
+ }
+ this._shutdownFunctions.push(aFunc);
+ },
+
+ // nsIObserver
+ observe: function PU_observe(aSubject, aTopic, aData)
+ {
+ switch (aTopic) {
+ case this.TOPIC_SHUTDOWN:
+ Services.obs.removeObserver(this, this.TOPIC_SHUTDOWN);
+ while (this._shutdownFunctions.length > 0) {
+ this._shutdownFunctions.shift().apply(this);
+ }
+ if (this._bookmarksServiceObserversQueue.length > 0) {
+ // Since we are shutting down, there's no reason to add the observers.
+ this._bookmarksServiceObserversQueue.length = 0;
+ }
+ break;
+ case "bookmarks-service-ready":
+ this._bookmarksServiceReady = true;
+ while (this._bookmarksServiceObserversQueue.length > 0) {
+ let observerInfo = this._bookmarksServiceObserversQueue.shift();
+ this.bookmarks.addObserver(observerInfo.observer, observerInfo.weak);
+ }
+
+ // Initialize the keywords cache to start observing bookmarks
+ // notifications. This is needed as far as we support both the old and
+ // the new bookmarking APIs at the same time.
+ gKeywordsCachePromise.catch(Cu.reportError);
+ break;
+ }
+ },
+
+ onPageAnnotationSet: function() {},
+ onPageAnnotationRemoved: function() {},
+
+
+ // nsITransactionListener
+
+ didDo: function PU_didDo(aManager, aTransaction, aDoResult)
+ {
+ updateCommandsOnActiveWindow();
+ },
+
+ didUndo: function PU_didUndo(aManager, aTransaction, aUndoResult)
+ {
+ updateCommandsOnActiveWindow();
+ },
+
+ didRedo: function PU_didRedo(aManager, aTransaction, aRedoResult)
+ {
+ updateCommandsOnActiveWindow();
+ },
+
+ didBeginBatch: function PU_didBeginBatch(aManager, aResult)
+ {
+ // A no-op transaction is pushed to the stack, in order to make safe and
+ // easy to implement "Undo" an unknown number of transactions (including 0),
+ // "above" beginBatch and endBatch. Otherwise,implementing Undo that way
+ // head to dataloss: for example, if no changes were done in the
+ // edit-item panel, the last transaction on the undo stack would be the
+ // initial createItem transaction, or even worse, the batched editing of
+ // some other item.
+ // DO NOT MOVE this to the window scope, that would leak (bug 490068)!
+ this.transactionManager.doTransaction({ doTransaction: function() {},
+ undoTransaction: function() {},
+ redoTransaction: function() {},
+ isTransient: false,
+ merge: function() { return false; }
+ });
+ },
+
+ willDo: function PU_willDo() {},
+ willUndo: function PU_willUndo() {},
+ willRedo: function PU_willRedo() {},
+ willBeginBatch: function PU_willBeginBatch() {},
+ willEndBatch: function PU_willEndBatch() {},
+ didEndBatch: function PU_didEndBatch() {},
+ willMerge: function PU_willMerge() {},
+ didMerge: function PU_didMerge() {},
+
+ /**
+ * Determines whether or not a ResultNode is a host container.
+ * @param aNode
+ * A result node
+ * @returns true if the node is a host container, false otherwise
+ */
+ nodeIsHost: function PU_nodeIsHost(aNode) {
+ return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
+ aNode.parent &&
+ asQuery(aNode.parent).queryOptions.resultType ==
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY;
+ },
+
+ /**
+ * Determines whether or not a ResultNode is a day container.
+ * @param node
+ * A NavHistoryResultNode
+ * @returns true if the node is a day container, false otherwise
+ */
+ nodeIsDay: function PU_nodeIsDay(aNode) {
+ var resultType;
+ return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
+ aNode.parent &&
+ ((resultType = asQuery(aNode.parent).queryOptions.resultType) ==
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY ||
+ resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY);
+ },
+
+ /**
+ * Determines whether or not a result-node is a tag container.
+ * @param aNode
+ * A result-node
+ * @returns true if the node is a tag container, false otherwise
+ */
+ nodeIsTagQuery: function PU_nodeIsTagQuery(aNode) {
+ return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
+ asQuery(aNode).queryOptions.resultType ==
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS;
+ },
+
+ /**
+ * Determines whether or not a ResultNode is a container.
+ * @param aNode
+ * A result node
+ * @returns true if the node is a container item, false otherwise
+ */
+ containerTypes: [Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
+ Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT,
+ Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY],
+ nodeIsContainer: function PU_nodeIsContainer(aNode) {
+ return this.containerTypes.includes(aNode.type);
+ },
+
+ /**
+ * Determines whether or not a ResultNode is an history related container.
+ * @param node
+ * A result node
+ * @returns true if the node is an history related container, false otherwise
+ */
+ nodeIsHistoryContainer: function PU_nodeIsHistoryContainer(aNode) {
+ var resultType;
+ return this.nodeIsQuery(aNode) &&
+ ((resultType = asQuery(aNode).queryOptions.resultType) ==
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY ||
+ resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY ||
+ resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY ||
+ this.nodeIsDay(aNode) ||
+ this.nodeIsHost(aNode));
+ },
+
+ /**
+ * Gets the concrete item-id for the given node. Generally, this is just
+ * node.itemId, but for folder-shortcuts that's node.folderItemId.
+ */
+ getConcreteItemId: function PU_getConcreteItemId(aNode) {
+ if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
+ return asQuery(aNode).folderItemId;
+ else if (PlacesUtils.nodeIsTagQuery(aNode)) {
+ // RESULTS_AS_TAG_CONTENTS queries are similar to folder shortcuts
+ // so we can still get the concrete itemId for them.
+ var queries = aNode.getQueries();
+ var folders = queries[0].getFolders();
+ return folders[0];
+ }
+ return aNode.itemId;
+ },
+
+ /**
+ * Gets the concrete item-guid for the given node. For everything but folder
+ * shortcuts, this is just node.bookmarkGuid. For folder shortcuts, this is
+ * node.targetFolderGuid (see nsINavHistoryService.idl for the semantics).
+ *
+ * @param aNode
+ * a result node.
+ * @return the concrete item-guid for aNode.
+ * @note unlike getConcreteItemId, this doesn't allow retrieving the guid of a
+ * ta container.
+ */
+ getConcreteItemGuid(aNode) {
+ if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
+ return asQuery(aNode).targetFolderGuid;
+ return aNode.bookmarkGuid;
+ },
+
+ /**
+ * Reverse a host based on the moz_places algorithm, that is reverse the host
+ * string and add a trailing period. For example "google.com" becomes
+ * "moc.elgoog.".
+ *
+ * @param url
+ * the URL to generate a rev host for.
+ * @return the reversed host string.
+ */
+ getReversedHost(url) {
+ return url.host.split("").reverse().join("") + ".";
+ },
+
+ /**
+ * String-wraps a result node according to the rules of the specified
+ * content type for copy or move operations.
+ *
+ * @param aNode
+ * The Result node to wrap (serialize)
+ * @param aType
+ * The content type to serialize as
+ * @param [optional] aFeedURI
+ * Used instead of the node's URI if provided.
+ * This is useful for wrapping a livemark as TYPE_X_MOZ_URL,
+ * TYPE_HTML or TYPE_UNICODE.
+ * @return A string serialization of the node
+ */
+ wrapNode(aNode, aType, aFeedURI) {
+ // when wrapping a node, we want all the items, even if the original
+ // query options are excluding them.
+ // This can happen when copying from the left hand pane of the bookmarks
+ // organizer.
+ // @return [node, shouldClose]
+ function gatherDataFromNode(node, gatherDataFunc) {
+ if (PlacesUtils.nodeIsFolder(node) &&
+ node.type != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT &&
+ asQuery(node).queryOptions.excludeItems) {
+ let folderRoot = PlacesUtils.getFolderContents(node.itemId, false, true).root;
+ try {
+ return gatherDataFunc(folderRoot);
+ } finally {
+ folderRoot.containerOpen = false;
+ }
+ }
+ // If we didn't create our own query, do not alter the node's state.
+ return gatherDataFunc(node);
+ }
+
+ function gatherDataHtml(node) {
+ let htmlEscape = s => s.replace(/&/g, "&amp;")
+ .replace(/>/g, "&gt;")
+ .replace(/</g, "&lt;")
+ .replace(/"/g, "&quot;")
+ .replace(/'/g, "&apos;");
+
+ // escape out potential HTML in the title
+ let escapedTitle = node.title ? htmlEscape(node.title) : "";
+
+ if (aFeedURI) {
+ return `<A HREF="${aFeedURI}">${escapedTitle}</A>${NEWLINE}`;
+ }
+
+ if (PlacesUtils.nodeIsContainer(node)) {
+ asContainer(node);
+ let wasOpen = node.containerOpen;
+ if (!wasOpen)
+ node.containerOpen = true;
+
+ let childString = "<DL><DT>" + escapedTitle + "</DT>" + NEWLINE;
+ let cc = node.childCount;
+ for (let i = 0; i < cc; ++i) {
+ childString += "<DD>"
+ + NEWLINE
+ + gatherDataHtml(node.getChild(i))
+ + "</DD>"
+ + NEWLINE;
+ }
+ node.containerOpen = wasOpen;
+ return childString + "</DL>" + NEWLINE;
+ }
+ if (PlacesUtils.nodeIsURI(node))
+ return `<A HREF="${node.uri}">${escapedTitle}</A>${NEWLINE}`;
+ if (PlacesUtils.nodeIsSeparator(node))
+ return "<HR>" + NEWLINE;
+ return "";
+ }
+
+ function gatherDataText(node) {
+ if (aFeedURI) {
+ return aFeedURI;
+ }
+
+ if (PlacesUtils.nodeIsContainer(node)) {
+ asContainer(node);
+ let wasOpen = node.containerOpen;
+ if (!wasOpen)
+ node.containerOpen = true;
+
+ let childString = node.title + NEWLINE;
+ let cc = node.childCount;
+ for (let i = 0; i < cc; ++i) {
+ let child = node.getChild(i);
+ let suffix = i < (cc - 1) ? NEWLINE : "";
+ childString += gatherDataText(child) + suffix;
+ }
+ node.containerOpen = wasOpen;
+ return childString;
+ }
+ if (PlacesUtils.nodeIsURI(node))
+ return node.uri;
+ if (PlacesUtils.nodeIsSeparator(node))
+ return "--------------------";
+ return "";
+ }
+
+ switch (aType) {
+ case this.TYPE_X_MOZ_PLACE:
+ case this.TYPE_X_MOZ_PLACE_SEPARATOR:
+ case this.TYPE_X_MOZ_PLACE_CONTAINER: {
+ // Serialize the node to JSON.
+ return serializeNode(aNode, aFeedURI);
+ }
+ case this.TYPE_X_MOZ_URL: {
+ if (aFeedURI || PlacesUtils.nodeIsURI(aNode))
+ return (aFeedURI || aNode.uri) + NEWLINE + aNode.title;
+ return "";
+ }
+ case this.TYPE_HTML: {
+ return gatherDataFromNode(aNode, gatherDataHtml);
+ }
+ }
+
+ // Otherwise, we wrap as TYPE_UNICODE.
+ return gatherDataFromNode(aNode, gatherDataText);
+ },
+
+ /**
+ * Unwraps data from the Clipboard or the current Drag Session.
+ * @param blob
+ * A blob (string) of data, in some format we potentially know how
+ * to parse.
+ * @param type
+ * The content type of the blob.
+ * @returns An array of objects representing each item contained by the source.
+ * @throws if the blob contains invalid data.
+ */
+ unwrapNodes: function PU_unwrapNodes(blob, type) {
+ // We split on "\n" because the transferable system converts "\r\n" to "\n"
+ var nodes = [];
+ switch (type) {
+ case this.TYPE_X_MOZ_PLACE:
+ case this.TYPE_X_MOZ_PLACE_SEPARATOR:
+ case this.TYPE_X_MOZ_PLACE_CONTAINER:
+ nodes = JSON.parse("[" + blob + "]");
+ break;
+ case this.TYPE_X_MOZ_URL: {
+ let parts = blob.split("\n");
+ // data in this type has 2 parts per entry, so if there are fewer
+ // than 2 parts left, the blob is malformed and we should stop
+ // but drag and drop of files from the shell has parts.length = 1
+ if (parts.length != 1 && parts.length % 2)
+ break;
+ for (let i = 0; i < parts.length; i=i+2) {
+ let uriString = parts[i];
+ let titleString = "";
+ if (parts.length > i+1)
+ titleString = parts[i+1];
+ else {
+ // for drag and drop of files, try to use the leafName as title
+ try {
+ titleString = this._uri(uriString).QueryInterface(Ci.nsIURL)
+ .fileName;
+ }
+ catch (e) {}
+ }
+ // note: this._uri() will throw if uriString is not a valid URI
+ if (this._uri(uriString) && this._uri(uriString).scheme != "place") {
+ nodes.push({ uri: uriString,
+ title: titleString ? titleString : uriString,
+ type: this.TYPE_X_MOZ_URL });
+ }
+ }
+ break;
+ }
+ case this.TYPE_UNICODE: {
+ let parts = blob.split("\n");
+ for (let i = 0; i < parts.length; i++) {
+ let uriString = parts[i];
+ // text/uri-list is converted to TYPE_UNICODE but it could contain
+ // comments line prepended by #, we should skip them, as well as
+ // empty URIs
+ if (uriString.substr(0, 1) == '\x23' || uriString == "")
+ continue;
+ // note: this._uri() will throw if uriString is not a valid URI
+ if (this._uri(uriString).scheme != "place")
+ nodes.push({ uri: uriString,
+ title: uriString,
+ type: this.TYPE_X_MOZ_URL });
+ }
+ break;
+ }
+ default:
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+ return nodes;
+ },
+
+ /**
+ * Generates a nsINavHistoryResult for the contents of a folder.
+ * @param folderId
+ * The folder to open
+ * @param [optional] excludeItems
+ * True to hide all items (individual bookmarks). This is used on
+ * the left places pane so you just get a folder hierarchy.
+ * @param [optional] expandQueries
+ * True to make query items expand as new containers. For managing,
+ * you want this to be false, for menus and such, you want this to
+ * be true.
+ * @returns A nsINavHistoryResult containing the contents of the
+ * folder. The result.root is guaranteed to be open.
+ */
+ getFolderContents:
+ function PU_getFolderContents(aFolderId, aExcludeItems, aExpandQueries) {
+ var query = this.history.getNewQuery();
+ query.setFolders([aFolderId], 1);
+ var options = this.history.getNewQueryOptions();
+ options.excludeItems = aExcludeItems;
+ options.expandQueries = aExpandQueries;
+
+ var result = this.history.executeQuery(query, options);
+ result.root.containerOpen = true;
+ return result;
+ },
+
+ /**
+ * Fetch all annotations for a URI, including all properties of each
+ * annotation which would be required to recreate it.
+ * @param aURI
+ * The URI for which annotations are to be retrieved.
+ * @return Array of objects, each containing the following properties:
+ * name, flags, expires, value
+ */
+ getAnnotationsForURI: function PU_getAnnotationsForURI(aURI) {
+ var annosvc = this.annotations;
+ var annos = [], val = null;
+ var annoNames = annosvc.getPageAnnotationNames(aURI);
+ for (var i = 0; i < annoNames.length; i++) {
+ var flags = {}, exp = {}, storageType = {};
+ annosvc.getPageAnnotationInfo(aURI, annoNames[i], flags, exp, storageType);
+ val = annosvc.getPageAnnotation(aURI, annoNames[i]);
+ annos.push({name: annoNames[i],
+ flags: flags.value,
+ expires: exp.value,
+ value: val});
+ }
+ return annos;
+ },
+
+ /**
+ * Fetch all annotations for an item, including all properties of each
+ * annotation which would be required to recreate it.
+ * @param aItemId
+ * The identifier of the itme for which annotations are to be
+ * retrieved.
+ * @return Array of objects, each containing the following properties:
+ * name, flags, expires, mimeType, type, value
+ */
+ getAnnotationsForItem: function PU_getAnnotationsForItem(aItemId) {
+ var annosvc = this.annotations;
+ var annos = [], val = null;
+ var annoNames = annosvc.getItemAnnotationNames(aItemId);
+ for (var i = 0; i < annoNames.length; i++) {
+ var flags = {}, exp = {}, storageType = {};
+ annosvc.getItemAnnotationInfo(aItemId, annoNames[i], flags, exp, storageType);
+ val = annosvc.getItemAnnotation(aItemId, annoNames[i]);
+ annos.push({name: annoNames[i],
+ flags: flags.value,
+ expires: exp.value,
+ value: val});
+ }
+ return annos;
+ },
+
+ /**
+ * Annotate a URI with a batch of annotations.
+ * @param aURI
+ * The URI for which annotations are to be set.
+ * @param aAnnotations
+ * Array of objects, each containing the following properties:
+ * name, flags, expires.
+ * If the value for an annotation is not set it will be removed.
+ */
+ setAnnotationsForURI: function PU_setAnnotationsForURI(aURI, aAnnos) {
+ var annosvc = this.annotations;
+ aAnnos.forEach(function(anno) {
+ if (anno.value === undefined || anno.value === null) {
+ annosvc.removePageAnnotation(aURI, anno.name);
+ }
+ else {
+ let flags = ("flags" in anno) ? anno.flags : 0;
+ let expires = ("expires" in anno) ?
+ anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER;
+ annosvc.setPageAnnotation(aURI, anno.name, anno.value, flags, expires);
+ }
+ });
+ },
+
+ /**
+ * Annotate an item with a batch of annotations.
+ * @param aItemId
+ * The identifier of the item for which annotations are to be set
+ * @param aAnnotations
+ * Array of objects, each containing the following properties:
+ * name, flags, expires.
+ * If the value for an annotation is not set it will be removed.
+ */
+ setAnnotationsForItem: function PU_setAnnotationsForItem(aItemId, aAnnos, aSource) {
+ var annosvc = this.annotations;
+
+ aAnnos.forEach(function(anno) {
+ if (anno.value === undefined || anno.value === null) {
+ annosvc.removeItemAnnotation(aItemId, anno.name, aSource);
+ }
+ else {
+ let flags = ("flags" in anno) ? anno.flags : 0;
+ let expires = ("expires" in anno) ?
+ anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER;
+ annosvc.setItemAnnotation(aItemId, anno.name, anno.value, flags,
+ expires, aSource);
+ }
+ });
+ },
+
+ // Identifier getters for special folders.
+ // You should use these everywhere PlacesUtils is available to avoid XPCOM
+ // traversal just to get roots' ids.
+ get placesRootId() {
+ delete this.placesRootId;
+ return this.placesRootId = this.bookmarks.placesRoot;
+ },
+
+ get bookmarksMenuFolderId() {
+ delete this.bookmarksMenuFolderId;
+ return this.bookmarksMenuFolderId = this.bookmarks.bookmarksMenuFolder;
+ },
+
+ get toolbarFolderId() {
+ delete this.toolbarFolderId;
+ return this.toolbarFolderId = this.bookmarks.toolbarFolder;
+ },
+
+ get tagsFolderId() {
+ delete this.tagsFolderId;
+ return this.tagsFolderId = this.bookmarks.tagsFolder;
+ },
+
+ get unfiledBookmarksFolderId() {
+ delete this.unfiledBookmarksFolderId;
+ return this.unfiledBookmarksFolderId = this.bookmarks.unfiledBookmarksFolder;
+ },
+
+ get mobileFolderId() {
+ delete this.mobileFolderId;
+ return this.mobileFolderId = this.bookmarks.mobileFolder;
+ },
+
+ /**
+ * Checks if aItemId is a root.
+ *
+ * @param aItemId
+ * item id to look for.
+ * @returns true if aItemId is a root, false otherwise.
+ */
+ isRootItem: function PU_isRootItem(aItemId) {
+ return aItemId == PlacesUtils.bookmarksMenuFolderId ||
+ aItemId == PlacesUtils.toolbarFolderId ||
+ aItemId == PlacesUtils.unfiledBookmarksFolderId ||
+ aItemId == PlacesUtils.tagsFolderId ||
+ aItemId == PlacesUtils.placesRootId ||
+ aItemId == PlacesUtils.mobileFolderId;
+ },
+
+ /**
+ * Set the POST data associated with a bookmark, if any.
+ * Used by POST keywords.
+ * @param aBookmarkId
+ *
+ * @deprecated Use PlacesUtils.keywords.insert() API instead.
+ */
+ setPostDataForBookmark(aBookmarkId, aPostData) {
+ if (!aPostData)
+ throw new Error("Must provide valid POST data");
+ // For now we don't have a unified API to create a keyword with postData,
+ // thus here we can just try to complete a keyword that should already exist
+ // without any post data.
+ let stmt = PlacesUtils.history.DBConnection.createStatement(
+ `UPDATE moz_keywords SET post_data = :post_data
+ WHERE id = (SELECT k.id FROM moz_keywords k
+ JOIN moz_bookmarks b ON b.fk = k.place_id
+ WHERE b.id = :item_id
+ AND post_data ISNULL
+ LIMIT 1)`);
+ stmt.params.item_id = aBookmarkId;
+ stmt.params.post_data = aPostData;
+ try {
+ stmt.execute();
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ // Update the cache.
+ return Task.spawn(function* () {
+ let guid = yield PlacesUtils.promiseItemGuid(aBookmarkId);
+ let bm = yield PlacesUtils.bookmarks.fetch(guid);
+
+ // Fetch keywords for this href.
+ let cache = yield gKeywordsCachePromise;
+ for (let [ , entry ] of cache) {
+ // Set the POST data on keywords not having it.
+ if (entry.url.href == bm.url.href && !entry.postData) {
+ entry.postData = aPostData;
+ }
+ }
+ }).catch(Cu.reportError);
+ },
+
+ /**
+ * Get the POST data associated with a bookmark, if any.
+ * @param aBookmarkId
+ * @returns string of POST data if set for aBookmarkId. null otherwise.
+ *
+ * @deprecated Use PlacesUtils.keywords.fetch() API instead.
+ */
+ getPostDataForBookmark(aBookmarkId) {
+ let stmt = PlacesUtils.history.DBConnection.createStatement(
+ `SELECT k.post_data
+ FROM moz_keywords k
+ JOIN moz_places h ON h.id = k.place_id
+ JOIN moz_bookmarks b ON b.fk = h.id
+ WHERE b.id = :item_id`);
+ stmt.params.item_id = aBookmarkId;
+ try {
+ if (!stmt.executeStep())
+ return null;
+ return stmt.row.post_data;
+ }
+ finally {
+ stmt.finalize();
+ }
+ },
+
+ /**
+ * Get the URI (and any associated POST data) for a given keyword.
+ * @param aKeyword string keyword
+ * @returns an array containing a string URL and a string of POST data
+ *
+ * @deprecated
+ */
+ getURLAndPostDataForKeyword(aKeyword) {
+ Deprecated.warning("getURLAndPostDataForKeyword() is deprecated, please " +
+ "use PlacesUtils.keywords.fetch() instead",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1100294");
+
+ let stmt = PlacesUtils.history.DBConnection.createStatement(
+ `SELECT h.url, k.post_data
+ FROM moz_keywords k
+ JOIN moz_places h ON h.id = k.place_id
+ WHERE k.keyword = :keyword`);
+ stmt.params.keyword = aKeyword.toLowerCase();
+ try {
+ if (!stmt.executeStep())
+ return [ null, null ];
+ return [ stmt.row.url, stmt.row.post_data ];
+ }
+ finally {
+ stmt.finalize();
+ }
+ },
+
+ /**
+ * Get all bookmarks for a URL, excluding items under tags.
+ */
+ getBookmarksForURI:
+ function PU_getBookmarksForURI(aURI) {
+ var bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI);
+
+ // filter the ids list
+ return bmkIds.filter(function(aID) {
+ var parentId = this.bookmarks.getFolderIdForItem(aID);
+ var grandparentId = this.bookmarks.getFolderIdForItem(parentId);
+ // item under a tag container
+ if (grandparentId == this.tagsFolderId)
+ return false;
+ return true;
+ }, this);
+ },
+
+ /**
+ * Get the most recently added/modified bookmark for a URL, excluding items
+ * under tags.
+ *
+ * @param aURI
+ * nsIURI of the page we will look for.
+ * @returns itemId of the found bookmark, or -1 if nothing is found.
+ */
+ getMostRecentBookmarkForURI:
+ function PU_getMostRecentBookmarkForURI(aURI) {
+ var bmkIds = this.bookmarks.getBookmarkIdsForURI(aURI);
+ for (var i = 0; i < bmkIds.length; i++) {
+ // Find the first folder which isn't a tag container
+ var itemId = bmkIds[i];
+ var parentId = this.bookmarks.getFolderIdForItem(itemId);
+ // Optimization: if this is a direct child of a root we don't need to
+ // check if its grandparent is a tag.
+ if (parentId == this.unfiledBookmarksFolderId ||
+ parentId == this.toolbarFolderId ||
+ parentId == this.bookmarksMenuFolderId)
+ return itemId;
+
+ var grandparentId = this.bookmarks.getFolderIdForItem(parentId);
+ if (grandparentId != this.tagsFolderId)
+ return itemId;
+ }
+ return -1;
+ },
+
+ /**
+ * Returns a nsNavHistoryContainerResultNode with forced excludeItems and
+ * expandQueries.
+ * @param aNode
+ * The node to convert
+ * @param [optional] excludeItems
+ * True to hide all items (individual bookmarks). This is used on
+ * the left places pane so you just get a folder hierarchy.
+ * @param [optional] expandQueries
+ * True to make query items expand as new containers. For managing,
+ * you want this to be false, for menus and such, you want this to
+ * be true.
+ * @returns A nsINavHistoryContainerResultNode containing the unfiltered
+ * contents of the container.
+ * @note The returned container node could be open or closed, we don't
+ * guarantee its status.
+ */
+ getContainerNodeWithOptions:
+ function PU_getContainerNodeWithOptions(aNode, aExcludeItems, aExpandQueries) {
+ if (!this.nodeIsContainer(aNode))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // excludeItems is inherited by child containers in an excludeItems view.
+ var excludeItems = asQuery(aNode).queryOptions.excludeItems ||
+ asQuery(aNode.parentResult.root).queryOptions.excludeItems;
+ // expandQueries is inherited by child containers in an expandQueries view.
+ var expandQueries = asQuery(aNode).queryOptions.expandQueries &&
+ asQuery(aNode.parentResult.root).queryOptions.expandQueries;
+
+ // If our options are exactly what we expect, directly return the node.
+ if (excludeItems == aExcludeItems && expandQueries == aExpandQueries)
+ return aNode;
+
+ // Otherwise, get contents manually.
+ var queries = {}, options = {};
+ this.history.queryStringToQueries(aNode.uri, queries, {}, options);
+ options.value.excludeItems = aExcludeItems;
+ options.value.expandQueries = aExpandQueries;
+ return this.history.executeQueries(queries.value,
+ queries.value.length,
+ options.value).root;
+ },
+
+ /**
+ * Returns true if a container has uri nodes in its first level.
+ * Has better performance than (getURLsForContainerNode(node).length > 0).
+ * @param aNode
+ * The container node to search through.
+ * @returns true if the node contains uri nodes, false otherwise.
+ */
+ hasChildURIs: function PU_hasChildURIs(aNode, aMultiple=false) {
+ if (!this.nodeIsContainer(aNode))
+ return false;
+
+ let root = this.getContainerNodeWithOptions(aNode, false, true);
+ let result = root.parentResult;
+ let didSuppressNotifications = false;
+ let wasOpen = root.containerOpen;
+ if (!wasOpen) {
+ didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true;
+
+ root.containerOpen = true;
+ }
+
+ let foundFirst = !aMultiple;
+ let found = false;
+ for (let i = 0; i < root.childCount && !found; i++) {
+ let child = root.getChild(i);
+ if (this.nodeIsURI(child)) {
+ found = foundFirst;
+ foundFirst = true;
+ }
+ }
+
+ if (!wasOpen) {
+ root.containerOpen = false;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+ return found;
+ },
+
+ /**
+ * Returns an array containing all the uris in the first level of the
+ * passed in container.
+ * If you only need to know if the node contains uris, use hasChildURIs.
+ * @param aNode
+ * The container node to search through
+ * @returns array of uris in the first level of the container.
+ */
+ getURLsForContainerNode: function PU_getURLsForContainerNode(aNode) {
+ let urls = [];
+ if (!this.nodeIsContainer(aNode))
+ return urls;
+
+ let root = this.getContainerNodeWithOptions(aNode, false, true);
+ let result = root.parentResult;
+ let wasOpen = root.containerOpen;
+ let didSuppressNotifications = false;
+ if (!wasOpen) {
+ didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true;
+
+ root.containerOpen = true;
+ }
+
+ for (let i = 0; i < root.childCount; ++i) {
+ let child = root.getChild(i);
+ if (this.nodeIsURI(child))
+ urls.push({uri: child.uri, isBookmark: this.nodeIsBookmark(child)});
+ }
+
+ if (!wasOpen) {
+ root.containerOpen = false;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+ return urls;
+ },
+
+ /**
+ * Gets a shared Sqlite.jsm readonly connection to the Places database,
+ * usable only for SELECT queries.
+ *
+ * This is intended to be used mostly internally, components outside of
+ * Places should, when possible, use API calls and file bugs to get proper
+ * APIs, where they are missing.
+ * Keep in mind the Places DB schema is by no means frozen or even stable.
+ * Your custom queries can - and will - break overtime.
+ *
+ * Example:
+ * let db = yield PlacesUtils.promiseDBConnection();
+ * let rows = yield db.executeCached(sql, params);
+ */
+ promiseDBConnection: () => gAsyncDBConnPromised,
+
+ /**
+ * Performs a read/write operation on the Places database through a Sqlite.jsm
+ * wrapped connection to the Places database.
+ *
+ * This is intended to be used only by Places itself, always use APIs if you
+ * need to modify the Places database. Use promiseDBConnection if you need to
+ * SELECT from the database and there's no covering API.
+ * Keep in mind the Places DB schema is by no means frozen or even stable.
+ * Your custom queries can - and will - break overtime.
+ *
+ * As all operations on the Places database are asynchronous, if shutdown
+ * is initiated while an operation is pending, this could cause dataloss.
+ * Using `withConnectionWrapper` ensures that shutdown waits until all
+ * operations are complete before proceeding.
+ *
+ * Example:
+ * yield withConnectionWrapper("Bookmarks: Remove a bookmark", Task.async(function*(db) {
+ * // Proceed with the db, asynchronously.
+ * // Shutdown will not interrupt operations that take place here.
+ * }));
+ *
+ * @param {string} name The name of the operation. Used for debugging, logging
+ * and crash reporting.
+ * @param {function(db)} task A function that takes as argument a Sqlite.jsm
+ * connection and returns a Promise. Shutdown is guaranteed to not interrupt
+ * execution of `task`.
+ */
+ withConnectionWrapper: (name, task) => {
+ if (!name) {
+ throw new TypeError("Expecting a user-readable name");
+ }
+ return Task.spawn(function*() {
+ let db = yield gAsyncDBWrapperPromised;
+ return db.executeBeforeShutdown(name, task);
+ });
+ },
+
+ /**
+ * Given a uri returns list of itemIds associated to it.
+ *
+ * @param aURI
+ * nsIURI or spec of the page.
+ * @param aCallback
+ * Function to be called when done.
+ * The function will receive an array of itemIds associated to aURI and
+ * aURI itself.
+ *
+ * @return A object with a .cancel() method allowing to cancel the request.
+ *
+ * @note Children of live bookmarks folders are excluded. The callback function is
+ * not invoked if the request is cancelled or hits an error.
+ */
+ asyncGetBookmarkIds: function PU_asyncGetBookmarkIds(aURI, aCallback)
+ {
+ let abort = false;
+ let itemIds = [];
+ Task.spawn(function* () {
+ let conn = yield this.promiseDBConnection();
+ const QUERY_STR = `SELECT b.id FROM moz_bookmarks b
+ JOIN moz_places h on h.id = b.fk
+ WHERE h.url_hash = hash(:url) AND h.url = :url`;
+ let spec = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
+ yield conn.executeCached(QUERY_STR, { url: spec }, aRow => {
+ if (abort)
+ throw StopIteration;
+ itemIds.push(aRow.getResultByIndex(0));
+ });
+ if (!abort)
+ aCallback(itemIds, aURI);
+ }.bind(this)).then(null, Cu.reportError);
+ return { cancel: () => { abort = true; } };
+ },
+
+ /**
+ * Lazily adds a bookmarks observer, waiting for the bookmarks service to be
+ * alive before registering the observer. This is especially useful in the
+ * startup path, to avoid initializing the service just to add an observer.
+ *
+ * @param aObserver
+ * Object implementing nsINavBookmarkObserver
+ * @param [optional]aWeakOwner
+ * Whether to use weak ownership.
+ *
+ * @note Correct functionality of lazy observers relies on the fact Places
+ * notifies categories before real observers, and uses
+ * PlacesCategoriesStarter component to kick-off the registration.
+ */
+ _bookmarksServiceReady: false,
+ _bookmarksServiceObserversQueue: [],
+ addLazyBookmarkObserver:
+ function PU_addLazyBookmarkObserver(aObserver, aWeakOwner) {
+ if (this._bookmarksServiceReady) {
+ this.bookmarks.addObserver(aObserver, aWeakOwner === true);
+ return;
+ }
+ this._bookmarksServiceObserversQueue.push({ observer: aObserver,
+ weak: aWeakOwner === true });
+ },
+
+ /**
+ * Removes a bookmarks observer added through addLazyBookmarkObserver.
+ *
+ * @param aObserver
+ * Object implementing nsINavBookmarkObserver
+ */
+ removeLazyBookmarkObserver:
+ function PU_removeLazyBookmarkObserver(aObserver) {
+ if (this._bookmarksServiceReady) {
+ this.bookmarks.removeObserver(aObserver);
+ return;
+ }
+ let index = -1;
+ for (let i = 0;
+ i < this._bookmarksServiceObserversQueue.length && index == -1; i++) {
+ if (this._bookmarksServiceObserversQueue[i].observer === aObserver)
+ index = i;
+ }
+ if (index != -1) {
+ this._bookmarksServiceObserversQueue.splice(index, 1);
+ }
+ },
+
+ /**
+ * Sets the character-set for a URI.
+ *
+ * @param aURI nsIURI
+ * @param aCharset character-set value.
+ * @return {Promise}
+ */
+ setCharsetForURI: function PU_setCharsetForURI(aURI, aCharset) {
+ let deferred = Promise.defer();
+
+ // Delaying to catch issues with asynchronous behavior while waiting
+ // to implement asynchronous annotations in bug 699844.
+ Services.tm.mainThread.dispatch(function() {
+ if (aCharset && aCharset.length > 0) {
+ PlacesUtils.annotations.setPageAnnotation(
+ aURI, PlacesUtils.CHARSET_ANNO, aCharset, 0,
+ Ci.nsIAnnotationService.EXPIRE_NEVER);
+ } else {
+ PlacesUtils.annotations.removePageAnnotation(
+ aURI, PlacesUtils.CHARSET_ANNO);
+ }
+ deferred.resolve();
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Gets the last saved character-set for a URI.
+ *
+ * @param aURI nsIURI
+ * @return {Promise}
+ * @resolve a character-set or null.
+ */
+ getCharsetForURI: function PU_getCharsetForURI(aURI) {
+ let deferred = Promise.defer();
+
+ Services.tm.mainThread.dispatch(function() {
+ let charset = null;
+
+ try {
+ charset = PlacesUtils.annotations.getPageAnnotation(aURI,
+ PlacesUtils.CHARSET_ANNO);
+ } catch (ex) { }
+
+ deferred.resolve(charset);
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Promised wrapper for mozIAsyncHistory::getPlacesInfo for a single place.
+ *
+ * @param aPlaceIdentifier
+ * either an nsIURI or a GUID (@see getPlacesInfo)
+ * @resolves to the place info object handed to handleResult.
+ */
+ promisePlaceInfo: function PU_promisePlaceInfo(aPlaceIdentifier) {
+ let deferred = Promise.defer();
+ PlacesUtils.asyncHistory.getPlacesInfo(aPlaceIdentifier, {
+ _placeInfo: null,
+ handleResult: function handleResult(aPlaceInfo) {
+ this._placeInfo = aPlaceInfo;
+ },
+ handleError: function handleError(aResultCode, aPlaceInfo) {
+ deferred.reject(new Components.Exception("Error", aResultCode));
+ },
+ handleCompletion: function() {
+ deferred.resolve(this._placeInfo);
+ }
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Gets favicon data for a given page url.
+ *
+ * @param aPageUrl url of the page to look favicon for.
+ * @resolves to an object representing a favicon entry, having the following
+ * properties: { uri, dataLen, data, mimeType }
+ * @rejects JavaScript exception if the given url has no associated favicon.
+ */
+ promiseFaviconData: function (aPageUrl) {
+ let deferred = Promise.defer();
+ PlacesUtils.favicons.getFaviconDataForPage(NetUtil.newURI(aPageUrl),
+ function (aURI, aDataLen, aData, aMimeType) {
+ if (aURI) {
+ deferred.resolve({ uri: aURI,
+ dataLen: aDataLen,
+ data: aData,
+ mimeType: aMimeType });
+ } else {
+ deferred.reject();
+ }
+ });
+ return deferred.promise;
+ },
+
+ /**
+ * Gets the favicon link url (moz-anno:) for a given page url.
+ *
+ * @param aPageURL url of the page to lookup the favicon for.
+ * @resolves to the nsIURL of the favicon link
+ * @rejects if the given url has no associated favicon.
+ */
+ promiseFaviconLinkUrl: function (aPageUrl) {
+ let deferred = Promise.defer();
+ if (!(aPageUrl instanceof Ci.nsIURI))
+ aPageUrl = NetUtil.newURI(aPageUrl);
+
+ PlacesUtils.favicons.getFaviconURLForPage(aPageUrl, uri => {
+ if (uri) {
+ uri = PlacesUtils.favicons.getFaviconLinkForIcon(uri);
+ deferred.resolve(uri);
+ } else {
+ deferred.reject("favicon not found for uri");
+ }
+ });
+ return deferred.promise;
+ },
+
+ /**
+ * Get the unique id for an item (a bookmark, a folder or a separator) given
+ * its item id.
+ *
+ * @param aItemId
+ * an item id
+ * @return {Promise}
+ * @resolves to the GUID.
+ * @rejects if aItemId is invalid.
+ */
+ promiseItemGuid(aItemId) {
+ return GuidHelper.getItemGuid(aItemId)
+ },
+
+ /**
+ * Get the item id for an item (a bookmark, a folder or a separator) given
+ * its unique id.
+ *
+ * @param aGuid
+ * an item GUID
+ * @return {Promise}
+ * @resolves to the GUID.
+ * @rejects if there's no item for the given GUID.
+ */
+ promiseItemId(aGuid) {
+ return GuidHelper.getItemId(aGuid)
+ },
+
+ /**
+ * Invalidate the GUID cache for the given itemId.
+ *
+ * @param aItemId
+ * an item id
+ */
+ invalidateCachedGuidFor(aItemId) {
+ GuidHelper.invalidateCacheForItemId(aItemId)
+ },
+
+ /**
+ * Asynchronously retrieve a JS-object representation of a places bookmarks
+ * item (a bookmark, a folder, or a separator) along with all of its
+ * descendants.
+ *
+ * @param [optional] aItemGuid
+ * the (topmost) item to be queried. If it's not passed, the places
+ * root is queried: that is, you get a representation of the entire
+ * bookmarks hierarchy.
+ * @param [optional] aOptions
+ * Options for customizing the query behavior, in the form of a JS
+ * object with any of the following properties:
+ * - excludeItemsCallback: a function for excluding items, along with
+ * their descendants. Given an item object (that has everything set
+ * apart its potential children data), it should return true if the
+ * item should be excluded. Once an item is excluded, the function
+ * isn't called for any of its descendants. This isn't called for
+ * the root item.
+ * WARNING: since the function may be called for each item, using
+ * this option can slow down the process significantly if the
+ * callback does anything that's not relatively trivial. It is
+ * highly recommended to avoid any synchronous I/O or DB queries.
+ * - includeItemIds: opt-in to include the deprecated id property.
+ * Use it if you must. It'll be removed once the switch to GUIDs is
+ * complete.
+ *
+ * @return {Promise}
+ * @resolves to a JS object that represents either a single item or a
+ * bookmarks tree. Each node in the tree has the following properties set:
+ * - guid (string): the item's GUID (same as aItemGuid for the top item).
+ * - [deprecated] id (number): the item's id. This is only if
+ * aOptions.includeItemIds is set.
+ * - type (string): the item's type. @see PlacesUtils.TYPE_X_*
+ * - title (string): the item's title. If it has no title, this property
+ * isn't set.
+ * - dateAdded (number, microseconds from the epoch): the date-added value of
+ * the item.
+ * - lastModified (number, microseconds from the epoch): the last-modified
+ * value of the item.
+ * - annos (see getAnnotationsForItem): the item's annotations. This is not
+ * set if there are no annotations set for the item).
+ * - index: the item's index under it's parent.
+ *
+ * The root object (i.e. the one for aItemGuid) also has the following
+ * properties set:
+ * - parentGuid (string): the GUID of the root's parent. This isn't set if
+ * the root item is the places root.
+ * - itemsCount (number, not enumerable): the number of items, including the
+ * root item itself, which are represented in the resolved object.
+ *
+ * Bookmark items also have the following properties:
+ * - uri (string): the item's url.
+ * - tags (string): csv string of the bookmark's tags.
+ * - charset (string): the last known charset of the bookmark.
+ * - keyword (string): the bookmark's keyword (unset if none).
+ * - postData (string): the bookmark's keyword postData (unset if none).
+ * - iconuri (string): the bookmark's favicon url.
+ * The last four properties are not set at all if they're irrelevant (e.g.
+ * |charset| is not set if no charset was previously set for the bookmark
+ * url).
+ *
+ * Folders may also have the following properties:
+ * - children (array): the folder's children information, each of them
+ * having the same set of properties as above.
+ *
+ * @rejects if the query failed for any reason.
+ * @note if aItemGuid points to a non-existent item, the returned promise is
+ * resolved to null.
+ */
+ promiseBookmarksTree: Task.async(function* (aItemGuid = "", aOptions = {}) {
+ let createItemInfoObject = function* (aRow, aIncludeParentGuid) {
+ let item = {};
+ let copyProps = (...props) => {
+ for (let prop of props) {
+ let val = aRow.getResultByName(prop);
+ if (val !== null)
+ item[prop] = val;
+ }
+ };
+ copyProps("guid", "title", "index", "dateAdded", "lastModified");
+ if (aIncludeParentGuid)
+ copyProps("parentGuid");
+
+ let itemId = aRow.getResultByName("id");
+ if (aOptions.includeItemIds)
+ item.id = itemId;
+
+ // Cache it for promiseItemId consumers regardless.
+ GuidHelper.updateCache(itemId, item.guid);
+
+ let type = aRow.getResultByName("type");
+ if (type == Ci.nsINavBookmarksService.TYPE_BOOKMARK)
+ copyProps("charset", "tags", "iconuri");
+
+ // Add annotations.
+ if (aRow.getResultByName("has_annos")) {
+ try {
+ item.annos = PlacesUtils.getAnnotationsForItem(itemId);
+ } catch (e) {
+ Cu.reportError("Unexpected error while reading annotations " + e);
+ }
+ }
+
+ switch (type) {
+ case Ci.nsINavBookmarksService.TYPE_BOOKMARK:
+ item.type = PlacesUtils.TYPE_X_MOZ_PLACE;
+ // If this throws due to an invalid url, the item will be skipped.
+ item.uri = NetUtil.newURI(aRow.getResultByName("url")).spec;
+ // Keywords are cached, so this should be decently fast.
+ let entry = yield PlacesUtils.keywords.fetch({ url: item.uri });
+ if (entry) {
+ item.keyword = entry.keyword;
+ item.postData = entry.postData;
+ }
+ break;
+ case Ci.nsINavBookmarksService.TYPE_FOLDER:
+ item.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
+ // Mark root folders.
+ if (itemId == PlacesUtils.placesRootId)
+ item.root = "placesRoot";
+ else if (itemId == PlacesUtils.bookmarksMenuFolderId)
+ item.root = "bookmarksMenuFolder";
+ else if (itemId == PlacesUtils.unfiledBookmarksFolderId)
+ item.root = "unfiledBookmarksFolder";
+ else if (itemId == PlacesUtils.toolbarFolderId)
+ item.root = "toolbarFolder";
+ else if (itemId == PlacesUtils.mobileFolderId)
+ item.root = "mobileFolder";
+ break;
+ case Ci.nsINavBookmarksService.TYPE_SEPARATOR:
+ item.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
+ break;
+ default:
+ Cu.reportError("Unexpected bookmark type");
+ break;
+ }
+ return item;
+ }.bind(this);
+
+ const QUERY_STR =
+ `/* do not warn (bug no): cannot use an index */
+ WITH RECURSIVE
+ descendants(fk, level, type, id, guid, parent, parentGuid, position,
+ title, dateAdded, lastModified) AS (
+ SELECT b1.fk, 0, b1.type, b1.id, b1.guid, b1.parent,
+ (SELECT guid FROM moz_bookmarks WHERE id = b1.parent),
+ b1.position, b1.title, b1.dateAdded, b1.lastModified
+ FROM moz_bookmarks b1 WHERE b1.guid=:item_guid
+ UNION ALL
+ SELECT b2.fk, level + 1, b2.type, b2.id, b2.guid, b2.parent,
+ descendants.guid, b2.position, b2.title, b2.dateAdded,
+ b2.lastModified
+ FROM moz_bookmarks b2
+ JOIN descendants ON b2.parent = descendants.id AND b2.id <> :tags_folder)
+ SELECT d.level, d.id, d.guid, d.parent, d.parentGuid, d.type,
+ d.position AS [index], d.title, d.dateAdded, d.lastModified,
+ h.url, f.url AS iconuri,
+ (SELECT GROUP_CONCAT(t.title, ',')
+ FROM moz_bookmarks b2
+ JOIN moz_bookmarks t ON t.id = +b2.parent AND t.parent = :tags_folder
+ WHERE b2.fk = h.id
+ ) AS tags,
+ EXISTS (SELECT 1 FROM moz_items_annos
+ WHERE item_id = d.id LIMIT 1) AS has_annos,
+ (SELECT a.content FROM moz_annos a
+ JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id
+ WHERE place_id = h.id AND n.name = :charset_anno
+ ) AS charset
+ FROM descendants d
+ LEFT JOIN moz_bookmarks b3 ON b3.id = d.parent
+ LEFT JOIN moz_places h ON h.id = d.fk
+ LEFT JOIN moz_favicons f ON f.id = h.favicon_id
+ ORDER BY d.level, d.parent, d.position`;
+
+
+ if (!aItemGuid)
+ aItemGuid = this.bookmarks.rootGuid;
+
+ let hasExcludeItemsCallback =
+ aOptions.hasOwnProperty("excludeItemsCallback");
+ let excludedParents = new Set();
+ let shouldExcludeItem = (aItem, aParentGuid) => {
+ let exclude = excludedParents.has(aParentGuid) ||
+ aOptions.excludeItemsCallback(aItem);
+ if (exclude) {
+ if (aItem.type == this.TYPE_X_MOZ_PLACE_CONTAINER)
+ excludedParents.add(aItem.guid);
+ }
+ return exclude;
+ };
+
+ let rootItem = null;
+ let parentsMap = new Map();
+ let conn = yield this.promiseDBConnection();
+ let rows = yield conn.executeCached(QUERY_STR,
+ { tags_folder: PlacesUtils.tagsFolderId,
+ charset_anno: PlacesUtils.CHARSET_ANNO,
+ item_guid: aItemGuid });
+ let yieldCounter = 0;
+ for (let row of rows) {
+ let item;
+ if (!rootItem) {
+ try {
+ // This is the first row.
+ rootItem = item = yield createItemInfoObject(row, true);
+ Object.defineProperty(rootItem, "itemsCount", { value: 1
+ , writable: true
+ , enumerable: false
+ , configurable: false });
+ } catch (ex) {
+ throw new Error("Failed to fetch the data for the root item " + ex);
+ }
+ } else {
+ try {
+ // Our query guarantees that we always visit parents ahead of their
+ // children.
+ item = yield createItemInfoObject(row, false);
+ let parentGuid = row.getResultByName("parentGuid");
+ if (hasExcludeItemsCallback && shouldExcludeItem(item, parentGuid))
+ continue;
+
+ let parentItem = parentsMap.get(parentGuid);
+ if ("children" in parentItem)
+ parentItem.children.push(item);
+ else
+ parentItem.children = [item];
+
+ rootItem.itemsCount++;
+ } catch (ex) {
+ // This is a bogus child, report and skip it.
+ Cu.reportError("Failed to fetch the data for an item " + ex);
+ continue;
+ }
+ }
+
+ if (item.type == this.TYPE_X_MOZ_PLACE_CONTAINER)
+ parentsMap.set(item.guid, item);
+
+ // With many bookmarks we end up stealing the CPU - even with yielding!
+ // So we let everyone else have a go every few items (bug 1186714).
+ if (++yieldCounter % 50 == 0) {
+ yield new Promise(resolve => {
+ Services.tm.currentThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ }
+ }
+
+ return rootItem;
+ })
+};
+
+XPCOMUtils.defineLazyGetter(PlacesUtils, "history", function() {
+ let hs = Cc["@mozilla.org/browser/nav-history-service;1"]
+ .getService(Ci.nsINavHistoryService)
+ .QueryInterface(Ci.nsIBrowserHistory)
+ .QueryInterface(Ci.nsPIPlacesDatabase);
+ return Object.freeze(new Proxy(hs, {
+ get: function(target, name) {
+ let property, object;
+ if (name in target) {
+ property = target[name];
+ object = target;
+ } else {
+ property = History[name];
+ object = History;
+ }
+ if (typeof property == "function") {
+ return property.bind(object);
+ }
+ return property;
+ }
+ }));
+});
+
+XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "asyncHistory",
+ "@mozilla.org/browser/history;1",
+ "mozIAsyncHistory");
+
+XPCOMUtils.defineLazyGetter(PlacesUtils, "bhistory", function() {
+ return PlacesUtils.history;
+});
+
+XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "favicons",
+ "@mozilla.org/browser/favicon-service;1",
+ "mozIAsyncFavicons");
+
+XPCOMUtils.defineLazyGetter(PlacesUtils, "bookmarks", () => {
+ let bm = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
+ .getService(Ci.nsINavBookmarksService);
+ return Object.freeze(new Proxy(bm, {
+ get: (target, name) => target.hasOwnProperty(name) ? target[name]
+ : Bookmarks[name]
+ }));
+});
+
+XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "annotations",
+ "@mozilla.org/browser/annotation-service;1",
+ "nsIAnnotationService");
+
+XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "tagging",
+ "@mozilla.org/browser/tagging-service;1",
+ "nsITaggingService");
+
+XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "livemarks",
+ "@mozilla.org/browser/livemark-service;2",
+ "mozIAsyncLivemarks");
+
+XPCOMUtils.defineLazyGetter(PlacesUtils, "keywords", () => Keywords);
+
+XPCOMUtils.defineLazyGetter(PlacesUtils, "transactionManager", function() {
+ let tm = Cc["@mozilla.org/transactionmanager;1"].
+ createInstance(Ci.nsITransactionManager);
+ tm.AddListener(PlacesUtils);
+ this.registerShutdownFunction(function () {
+ // Clear all references to local transactions in the transaction manager,
+ // this prevents from leaking it.
+ this.transactionManager.RemoveListener(this);
+ this.transactionManager.clear();
+ });
+
+ // Bug 750269
+ // The transaction manager keeps strong references to transactions, and by
+ // that, also to the global for each transaction. A transaction, however,
+ // could be either the transaction itself (for which the global is this
+ // module) or some js-proxy in another global, usually a window. The later
+ // would leak because the transaction lifetime (in the manager's stacks)
+ // is independent of the global from which doTransaction was called.
+ // To avoid such a leak, we hide the native doTransaction from callers,
+ // and let each doTransaction call go through this module.
+ // Doing so ensures that, as long as the transaction is any of the
+ // PlacesXXXTransaction objects declared in this module, the object
+ // referenced by the transaction manager has the module itself as global.
+ return Object.create(tm, {
+ "doTransaction": {
+ value: function(aTransaction) {
+ tm.doTransaction(aTransaction);
+ }
+ }
+ });
+});
+
+XPCOMUtils.defineLazyGetter(this, "bundle", function() {
+ const PLACES_STRING_BUNDLE_URI = "chrome://places/locale/places.properties";
+ return Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(PLACES_STRING_BUNDLE_URI);
+});
+
+/**
+ * Setup internal databases for closing properly during shutdown.
+ *
+ * 1. Places initiates shutdown.
+ * 2. Before places can move to the step where it closes the low-level connection,
+ * we need to make sure that we have closed `conn`.
+ * 3. Before we can close `conn`, we need to make sure that all external clients
+ * have stopped using `conn`.
+ * 4. Before we can close Sqlite, we need to close `conn`.
+ */
+function setupDbForShutdown(conn, name) {
+ try {
+ let state = "0. Not started.";
+ let promiseClosed = new Promise((resolve, reject) => {
+ // The service initiates shutdown.
+ // Before it can safely close its connection, we need to make sure
+ // that we have closed the high-level connection.
+ try {
+ AsyncShutdown.placesClosingInternalConnection.addBlocker(`${name} closing as part of Places shutdown`,
+ Task.async(function*() {
+ state = "1. Service has initiated shutdown";
+
+ // At this stage, all external clients have finished using the
+ // database. We just need to close the high-level connection.
+ yield conn.close();
+ state = "2. Closed Sqlite.jsm connection.";
+
+ resolve();
+ }),
+ () => state
+ );
+ } catch (ex) {
+ // It's too late to block shutdown, just close the connection.
+ conn.close();
+ reject(ex);
+ }
+ });
+
+ // Make sure that Sqlite.jsm doesn't close until we are done
+ // with the high-level connection.
+ Sqlite.shutdown.addBlocker(`${name} must be closed before Sqlite.jsm`,
+ () => promiseClosed.catch(Cu.reportError),
+ () => state
+ );
+ } catch (ex) {
+ // It's too late to block shutdown, just close the connection.
+ conn.close();
+ throw ex;
+ }
+}
+
+XPCOMUtils.defineLazyGetter(this, "gAsyncDBConnPromised",
+ () => Sqlite.cloneStorageConnection({
+ connection: PlacesUtils.history.DBConnection,
+ readOnly: true
+ }).then(conn => {
+ setupDbForShutdown(conn, "PlacesUtils read-only connection");
+ return conn;
+ }).catch(Cu.reportError)
+);
+
+XPCOMUtils.defineLazyGetter(this, "gAsyncDBWrapperPromised",
+ () => Sqlite.wrapStorageConnection({
+ connection: PlacesUtils.history.DBConnection,
+ }).then(conn => {
+ setupDbForShutdown(conn, "PlacesUtils wrapped connection");
+ return conn;
+ }).catch(Cu.reportError)
+);
+
+/**
+ * Keywords management API.
+ * Sooner or later these keywords will merge with search keywords, this is an
+ * interim API that should then be replaced by a unified one.
+ * Keywords are associated with URLs and can have POST data.
+ * A single URL can have multiple keywords, provided they differ by POST data.
+ */
+var Keywords = {
+ /**
+ * Fetches a keyword entry based on keyword or URL.
+ *
+ * @param keywordOrEntry
+ * Either the keyword to fetch or an entry providing keyword
+ * or url property to find keywords for. If both properties are set,
+ * this returns their intersection.
+ * @param onResult [optional]
+ * Callback invoked for each found entry.
+ * @return {Promise}
+ * @resolves to an object in the form: { keyword, url, postData },
+ * or null if a keyword entry was not found.
+ */
+ fetch(keywordOrEntry, onResult=null) {
+ if (typeof(keywordOrEntry) == "string")
+ keywordOrEntry = { keyword: keywordOrEntry };
+
+ if (keywordOrEntry === null || typeof(keywordOrEntry) != "object" ||
+ (("keyword" in keywordOrEntry) && typeof(keywordOrEntry.keyword) != "string"))
+ throw new Error("Invalid keyword");
+
+ let hasKeyword = "keyword" in keywordOrEntry;
+ let hasUrl = "url" in keywordOrEntry;
+
+ if (!hasKeyword && !hasUrl)
+ throw new Error("At least keyword or url must be provided");
+ if (onResult && typeof onResult != "function")
+ throw new Error("onResult callback must be a valid function");
+
+ if (hasUrl)
+ keywordOrEntry.url = new URL(keywordOrEntry.url);
+ if (hasKeyword)
+ keywordOrEntry.keyword = keywordOrEntry.keyword.trim().toLowerCase();
+
+ let safeOnResult = entry => {
+ if (onResult) {
+ try {
+ onResult(entry);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ };
+
+ return gKeywordsCachePromise.then(cache => {
+ let entries = [];
+ if (hasKeyword) {
+ let entry = cache.get(keywordOrEntry.keyword);
+ if (entry)
+ entries.push(entry);
+ }
+ if (hasUrl) {
+ for (let entry of cache.values()) {
+ if (entry.url.href == keywordOrEntry.url.href)
+ entries.push(entry);
+ }
+ }
+
+ entries = entries.filter(e => {
+ return (!hasUrl || e.url.href == keywordOrEntry.url.href) &&
+ (!hasKeyword || e.keyword == keywordOrEntry.keyword);
+ });
+
+ entries.forEach(safeOnResult);
+ return entries.length ? entries[0] : null;
+ });
+ },
+
+ /**
+ * Adds a new keyword and postData for the given URL.
+ *
+ * @param keywordEntry
+ * An object describing the keyword to insert, in the form:
+ * {
+ * keyword: non-empty string,
+ * URL: URL or href to associate to the keyword,
+ * postData: optional POST data to associate to the keyword
+ * }
+ * @note Do not define a postData property if there isn't any POST data.
+ * @resolves when the addition is complete.
+ */
+ insert(keywordEntry) {
+ if (!keywordEntry || typeof keywordEntry != "object")
+ throw new Error("Input should be a valid object");
+
+ if (!("keyword" in keywordEntry) || !keywordEntry.keyword ||
+ typeof(keywordEntry.keyword) != "string")
+ throw new Error("Invalid keyword");
+ if (("postData" in keywordEntry) && keywordEntry.postData &&
+ typeof(keywordEntry.postData) != "string")
+ throw new Error("Invalid POST data");
+ if (!("url" in keywordEntry))
+ throw new Error("undefined is not a valid URL");
+ let { keyword, url,
+ source = Ci.nsINavBookmarksService.SOURCE_DEFAULT } = keywordEntry;
+ keyword = keyword.trim().toLowerCase();
+ let postData = keywordEntry.postData || null;
+ // This also checks href for validity
+ url = new URL(url);
+
+ return PlacesUtils.withConnectionWrapper("Keywords.insert", Task.async(function*(db) {
+ let cache = yield gKeywordsCachePromise;
+
+ // Trying to set the same keyword is a no-op.
+ let oldEntry = cache.get(keyword);
+ if (oldEntry && oldEntry.url.href == url.href &&
+ oldEntry.postData == keywordEntry.postData) {
+ return;
+ }
+
+ // A keyword can only be associated to a single page.
+ // If another page is using the new keyword, we must update the keyword
+ // entry.
+ // Note we cannot use INSERT OR REPLACE cause it wouldn't invoke the delete
+ // trigger.
+ if (oldEntry) {
+ yield db.executeCached(
+ `UPDATE moz_keywords
+ SET place_id = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
+ post_data = :post_data
+ WHERE keyword = :keyword
+ `, { url: url.href, keyword: keyword, post_data: postData });
+ yield notifyKeywordChange(oldEntry.url.href, "", source);
+ } else {
+ // An entry for the given page could be missing, in such a case we need to
+ // create it. The IGNORE conflict can trigger on `guid`.
+ yield db.executeCached(
+ `INSERT OR IGNORE INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid)
+ VALUES (:url, hash(:url), :rev_host, 0, :frecency,
+ IFNULL((SELECT guid FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
+ GENERATE_GUID()))
+ `, { url: url.href, rev_host: PlacesUtils.getReversedHost(url),
+ frecency: url.protocol == "place:" ? 0 : -1 });
+ yield db.executeCached(
+ `INSERT INTO moz_keywords (keyword, place_id, post_data)
+ VALUES (:keyword, (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url), :post_data)
+ `, { url: url.href, keyword: keyword, post_data: postData });
+ }
+
+ cache.set(keyword, { keyword, url, postData });
+
+ // In any case, notify about the new keyword.
+ yield notifyKeywordChange(url.href, keyword, source);
+ }.bind(this))
+ );
+ },
+
+ /**
+ * Removes a keyword.
+ *
+ * @param keyword
+ * The keyword to remove.
+ * @return {Promise}
+ * @resolves when the removal is complete.
+ */
+ remove(keywordOrEntry) {
+ if (typeof(keywordOrEntry) == "string")
+ keywordOrEntry = { keyword: keywordOrEntry };
+
+ if (keywordOrEntry === null || typeof(keywordOrEntry) != "object" ||
+ !keywordOrEntry.keyword || typeof keywordOrEntry.keyword != "string")
+ throw new Error("Invalid keyword");
+
+ let { keyword,
+ source = Ci.nsINavBookmarksService.SOURCE_DEFAULT } = keywordOrEntry;
+ keyword = keywordOrEntry.keyword.trim().toLowerCase();
+ return PlacesUtils.withConnectionWrapper("Keywords.remove", Task.async(function*(db) {
+ let cache = yield gKeywordsCachePromise;
+ if (!cache.has(keyword))
+ return;
+ let { url } = cache.get(keyword);
+ cache.delete(keyword);
+
+ yield db.execute(`DELETE FROM moz_keywords WHERE keyword = :keyword`,
+ { keyword });
+
+ // Notify bookmarks about the removal.
+ yield notifyKeywordChange(url.href, "", source);
+ }.bind(this))) ;
+ }
+};
+
+// Set by the keywords API to distinguish notifications fired by the old API.
+// Once the old API will be gone, we can remove this and stop observing.
+var gIgnoreKeywordNotifications = false;
+
+XPCOMUtils.defineLazyGetter(this, "gKeywordsCachePromise", () =>
+ PlacesUtils.withConnectionWrapper("PlacesUtils: gKeywordsCachePromise",
+ Task.async(function*(db) {
+ let cache = new Map();
+ let rows = yield db.execute(
+ `SELECT keyword, url, post_data
+ FROM moz_keywords k
+ JOIN moz_places h ON h.id = k.place_id
+ `);
+ for (let row of rows) {
+ let keyword = row.getResultByName("keyword");
+ let entry = { keyword,
+ url: new URL(row.getResultByName("url")),
+ postData: row.getResultByName("post_data") };
+ cache.set(keyword, entry);
+ }
+
+ // Helper to get a keyword from an href.
+ function keywordsForHref(href) {
+ let keywords = [];
+ for (let [ key, val ] of cache) {
+ if (val.url.href == href)
+ keywords.push(key);
+ }
+ return keywords;
+ }
+
+ // Start observing changes to bookmarks. For now we are going to keep that
+ // relation for backwards compatibility reasons, but mostly because we are
+ // lacking a UI to manage keywords directly.
+ let observer = {
+ QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver),
+ onBeginUpdateBatch() {},
+ onEndUpdateBatch() {},
+ onItemAdded() {},
+ onItemVisited() {},
+ onItemMoved() {},
+
+ onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid) {
+ if (itemType != PlacesUtils.bookmarks.TYPE_BOOKMARK)
+ return;
+
+ let keywords = keywordsForHref(uri.spec);
+ // This uri has no keywords associated, so there's nothing to do.
+ if (keywords.length == 0)
+ return;
+
+ Task.spawn(function* () {
+ // If the uri is not bookmarked anymore, we can remove this keyword.
+ let bookmark = yield PlacesUtils.bookmarks.fetch({ url: uri });
+ if (!bookmark) {
+ for (let keyword of keywords) {
+ yield PlacesUtils.keywords.remove(keyword);
+ }
+ }
+ }).catch(Cu.reportError);
+ },
+
+ onItemChanged(id, prop, isAnno, val, lastMod, itemType, parentId, guid,
+ parentGuid, oldVal) {
+ if (gIgnoreKeywordNotifications) {
+ return;
+ }
+
+ if (prop == "keyword") {
+ this._onKeywordChanged(guid, val).catch(Cu.reportError);
+ } else if (prop == "uri") {
+ this._onUrlChanged(guid, val, oldVal).catch(Cu.reportError);
+ }
+ },
+
+ _onKeywordChanged: Task.async(function* (guid, keyword) {
+ let bookmark = yield PlacesUtils.bookmarks.fetch(guid);
+ // Due to mixed sync/async operations, by this time the bookmark could
+ // have disappeared and we already handle removals in onItemRemoved.
+ if (!bookmark) {
+ return;
+ }
+
+ if (keyword.length == 0) {
+ // We are removing a keyword.
+ let keywords = keywordsForHref(bookmark.url.href)
+ for (let kw of keywords) {
+ cache.delete(kw);
+ }
+ } else {
+ // We are adding a new keyword.
+ cache.set(keyword, { keyword, url: bookmark.url });
+ }
+ }),
+
+ _onUrlChanged: Task.async(function* (guid, url, oldUrl) {
+ // Check if the old url is associated with keywords.
+ let entries = [];
+ yield PlacesUtils.keywords.fetch({ url: oldUrl }, e => entries.push(e));
+ if (entries.length == 0) {
+ return;
+ }
+
+ // Move the keywords to the new url.
+ for (let entry of entries) {
+ yield PlacesUtils.keywords.remove(entry.keyword);
+ entry.url = new URL(url);
+ yield PlacesUtils.keywords.insert(entry);
+ }
+ }),
+ };
+
+ PlacesUtils.bookmarks.addObserver(observer, false);
+ PlacesUtils.registerShutdownFunction(() => {
+ PlacesUtils.bookmarks.removeObserver(observer);
+ });
+ return cache;
+ })
+));
+
+// Sometime soon, likely as part of the transition to mozIAsyncBookmarks,
+// itemIds will be deprecated in favour of GUIDs, which play much better
+// with multiple undo/redo operations. Because these GUIDs are already stored,
+// and because we don't want to revise the transactions API once more when this
+// happens, transactions are set to work with GUIDs exclusively, in the sense
+// that they may never expose itemIds, nor do they accept them as input.
+// More importantly, transactions which add or remove items guarantee to
+// restore the GUIDs on undo/redo, so that the following transactions that may
+// done or undo can assume the items they're interested in are stil accessible
+// through the same GUID.
+// The current bookmarks API, however, doesn't expose the necessary means for
+// working with GUIDs. So, until it does, this helper object accesses the
+// Places database directly in order to switch between GUIDs and itemIds, and
+// "restore" GUIDs on items re-created items.
+var GuidHelper = {
+ // Cache for GUID<->itemId paris.
+ guidsForIds: new Map(),
+ idsForGuids: new Map(),
+
+ getItemId: Task.async(function* (aGuid) {
+ let cached = this.idsForGuids.get(aGuid);
+ if (cached !== undefined)
+ return cached;
+
+ let itemId = yield PlacesUtils.withConnectionWrapper("GuidHelper.getItemId",
+ Task.async(function* (db) {
+ let rows = yield db.executeCached(
+ "SELECT b.id, b.guid from moz_bookmarks b WHERE b.guid = :guid LIMIT 1",
+ { guid: aGuid });
+ if (rows.length == 0)
+ throw new Error("no item found for the given GUID");
+
+ return rows[0].getResultByName("id");
+ }));
+
+ this.updateCache(itemId, aGuid);
+ return itemId;
+ }),
+
+ getItemGuid: Task.async(function* (aItemId) {
+ let cached = this.guidsForIds.get(aItemId);
+ if (cached !== undefined)
+ return cached;
+
+ let guid = yield PlacesUtils.withConnectionWrapper("GuidHelper.getItemGuid",
+ Task.async(function* (db) {
+
+ let rows = yield db.executeCached(
+ "SELECT b.id, b.guid from moz_bookmarks b WHERE b.id = :id LIMIT 1",
+ { id: aItemId });
+ if (rows.length == 0)
+ throw new Error("no item found for the given itemId");
+
+ return rows[0].getResultByName("guid");
+ }));
+
+ this.updateCache(aItemId, guid);
+ return guid;
+ }),
+
+ /**
+ * Updates the cache.
+ *
+ * @note This is the only place where the cache should be populated,
+ * invalidation relies on both Maps being populated at the same time.
+ */
+ updateCache(aItemId, aGuid) {
+ if (typeof(aItemId) != "number" || aItemId <= 0)
+ throw new Error("Trying to update the GUIDs cache with an invalid itemId");
+ if (typeof(aGuid) != "string" || !/^[a-zA-Z0-9\-_]{12}$/.test(aGuid))
+ throw new Error("Trying to update the GUIDs cache with an invalid GUID");
+ this.ensureObservingRemovedItems();
+ this.guidsForIds.set(aItemId, aGuid);
+ this.idsForGuids.set(aGuid, aItemId);
+ },
+
+ invalidateCacheForItemId(aItemId) {
+ let guid = this.guidsForIds.get(aItemId);
+ this.guidsForIds.delete(aItemId);
+ this.idsForGuids.delete(guid);
+ },
+
+ ensureObservingRemovedItems: function () {
+ if (!("observer" in this)) {
+ /**
+ * This observers serves two purposes:
+ * (1) Invalidate cached id<->GUID paris on when items are removed.
+ * (2) Cache GUIDs given us free of charge by onItemAdded/onItemRemoved.
+ * So, for exmaple, when the NewBookmark needs the new GUID, we already
+ * have it cached.
+ */
+ this.observer = {
+ onItemAdded: (aItemId, aParentId, aIndex, aItemType, aURI, aTitle,
+ aDateAdded, aGuid, aParentGuid) => {
+ this.updateCache(aItemId, aGuid);
+ this.updateCache(aParentId, aParentGuid);
+ },
+ onItemRemoved:
+ (aItemId, aParentId, aIndex, aItemTyep, aURI, aGuid, aParentGuid) => {
+ this.guidsForIds.delete(aItemId);
+ this.idsForGuids.delete(aGuid);
+ this.updateCache(aParentId, aParentGuid);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver),
+
+ onBeginUpdateBatch: function() {},
+ onEndUpdateBatch: function() {},
+ onItemChanged: function() {},
+ onItemVisited: function() {},
+ onItemMoved: function() {},
+ };
+ PlacesUtils.bookmarks.addObserver(this.observer, false);
+ PlacesUtils.registerShutdownFunction(() => {
+ PlacesUtils.bookmarks.removeObserver(this.observer);
+ });
+ }
+ }
+};
+
+// Transactions handlers.
+
+/**
+ * Updates commands in the undo group of the active window commands.
+ * Inactive windows commands will be updated on focus.
+ */
+function updateCommandsOnActiveWindow()
+{
+ let win = Services.focus.activeWindow;
+ if (win && win instanceof Ci.nsIDOMWindow) {
+ // Updating "undo" will cause a group update including "redo".
+ win.updateCommands("undo");
+ }
+}
+
+
+/**
+ * Used to cache bookmark information in transactions.
+ *
+ * @note To avoid leaks any non-primitive property should be copied.
+ * @note Used internally, DO NOT EXPORT.
+ */
+function TransactionItemCache()
+{
+}
+
+TransactionItemCache.prototype = {
+ set id(v) {
+ this._id = (parseInt(v) > 0 ? v : null);
+ },
+ get id() {
+ return this._id || -1;
+ },
+ set parentId(v) {
+ this._parentId = (parseInt(v) > 0 ? v : null);
+ },
+ get parentId() {
+ return this._parentId || -1;
+ },
+ keyword: null,
+ title: null,
+ dateAdded: null,
+ lastModified: null,
+ postData: null,
+ itemType: null,
+ set uri(v) {
+ this._uri = (v instanceof Ci.nsIURI ? v.clone() : null);
+ },
+ get uri() {
+ return this._uri || null;
+ },
+ set feedURI(v) {
+ this._feedURI = (v instanceof Ci.nsIURI ? v.clone() : null);
+ },
+ get feedURI() {
+ return this._feedURI || null;
+ },
+ set siteURI(v) {
+ this._siteURI = (v instanceof Ci.nsIURI ? v.clone() : null);
+ },
+ get siteURI() {
+ return this._siteURI || null;
+ },
+ set index(v) {
+ this._index = (parseInt(v) >= 0 ? v : null);
+ },
+ // Index can be 0.
+ get index() {
+ return this._index != null ? this._index : PlacesUtils.bookmarks.DEFAULT_INDEX;
+ },
+ set annotations(v) {
+ this._annotations = Array.isArray(v) ? Cu.cloneInto(v, {}) : null;
+ },
+ get annotations() {
+ return this._annotations || null;
+ },
+ set tags(v) {
+ this._tags = (v && Array.isArray(v) ? Array.prototype.slice.call(v) : null);
+ },
+ get tags() {
+ return this._tags || null;
+ },
+};
+
+
+/**
+ * Base transaction implementation.
+ *
+ * @note used internally, DO NOT EXPORT.
+ */
+function BaseTransaction()
+{
+}
+
+BaseTransaction.prototype = {
+ name: null,
+ set childTransactions(v) {
+ this._childTransactions = (Array.isArray(v) ? Array.prototype.slice.call(v) : null);
+ },
+ get childTransactions() {
+ return this._childTransactions || null;
+ },
+ doTransaction: function BTXN_doTransaction() {},
+ redoTransaction: function BTXN_redoTransaction() {
+ return this.doTransaction();
+ },
+ undoTransaction: function BTXN_undoTransaction() {},
+ merge: function BTXN_merge() {
+ return false;
+ },
+ get isTransient() {
+ return false;
+ },
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsITransaction
+ ]),
+};
+
+
+/**
+ * Transaction for performing several Places Transactions in a single batch.
+ *
+ * @param aName
+ * title of the aggregate transactions
+ * @param aTransactions
+ * an array of transactions to perform
+ *
+ * @return nsITransaction object
+ */
+this.PlacesAggregatedTransaction =
+ function PlacesAggregatedTransaction(aName, aTransactions)
+{
+ // Copy the transactions array to decouple it from its prototype, which
+ // otherwise keeps alive its associated global object.
+ this.childTransactions = aTransactions;
+ this.name = aName;
+ this.item = new TransactionItemCache();
+
+ // Check child transactions number. We will batch if we have more than
+ // MIN_TRANSACTIONS_FOR_BATCH total number of transactions.
+ let countTransactions = function(aTransactions, aTxnCount)
+ {
+ for (let i = 0;
+ i < aTransactions.length && aTxnCount < MIN_TRANSACTIONS_FOR_BATCH;
+ ++i, ++aTxnCount) {
+ let txn = aTransactions[i];
+ if (txn.childTransactions && txn.childTransactions.length > 0)
+ aTxnCount = countTransactions(txn.childTransactions, aTxnCount);
+ }
+ return aTxnCount;
+ }
+
+ let txnCount = countTransactions(this.childTransactions, 0);
+ this._useBatch = txnCount >= MIN_TRANSACTIONS_FOR_BATCH;
+}
+
+PlacesAggregatedTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function ATXN_doTransaction()
+ {
+ this._isUndo = false;
+ if (this._useBatch)
+ PlacesUtils.bookmarks.runInBatchMode(this, null);
+ else
+ this.runBatched(false);
+ },
+
+ undoTransaction: function ATXN_undoTransaction()
+ {
+ this._isUndo = true;
+ if (this._useBatch)
+ PlacesUtils.bookmarks.runInBatchMode(this, null);
+ else
+ this.runBatched(true);
+ },
+
+ runBatched: function ATXN_runBatched()
+ {
+ // Use a copy of the transactions array, so we won't reverse the original
+ // one on undoing.
+ let transactions = this.childTransactions.slice(0);
+ if (this._isUndo)
+ transactions.reverse();
+ for (let i = 0; i < transactions.length; ++i) {
+ let txn = transactions[i];
+ if (this.item.parentId != -1)
+ txn.item.parentId = this.item.parentId;
+ if (this._isUndo)
+ txn.undoTransaction();
+ else
+ txn.doTransaction();
+ }
+ }
+};
+
+
+/**
+ * Transaction for creating a new folder.
+ *
+ * @param aTitle
+ * the title for the new folder
+ * @param aParentId
+ * the id of the parent folder in which the new folder should be added
+ * @param [optional] aIndex
+ * the index of the item in aParentId
+ * @param [optional] aAnnotations
+ * array of annotations to set for the new folder
+ * @param [optional] aChildTransactions
+ * array of transactions for items to be created in the new folder
+ *
+ * @return nsITransaction object
+ */
+this.PlacesCreateFolderTransaction =
+ function PlacesCreateFolderTransaction(aTitle, aParentId, aIndex, aAnnotations,
+ aChildTransactions)
+{
+ this.item = new TransactionItemCache();
+ this.item.title = aTitle;
+ this.item.parentId = aParentId;
+ this.item.index = aIndex;
+ this.item.annotations = aAnnotations;
+ this.childTransactions = aChildTransactions;
+}
+
+PlacesCreateFolderTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function CFTXN_doTransaction()
+ {
+ this.item.id = PlacesUtils.bookmarks.createFolder(this.item.parentId,
+ this.item.title,
+ this.item.index);
+ if (this.item.annotations && this.item.annotations.length > 0)
+ PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);
+
+ if (this.childTransactions && this.childTransactions.length > 0) {
+ // Set the new parent id into child transactions.
+ for (let i = 0; i < this.childTransactions.length; ++i) {
+ this.childTransactions[i].item.parentId = this.item.id;
+ }
+
+ let txn = new PlacesAggregatedTransaction("Create folder childTxn",
+ this.childTransactions);
+ txn.doTransaction();
+ }
+ },
+
+ undoTransaction: function CFTXN_undoTransaction()
+ {
+ if (this.childTransactions && this.childTransactions.length > 0) {
+ let txn = new PlacesAggregatedTransaction("Create folder childTxn",
+ this.childTransactions);
+ txn.undoTransaction();
+ }
+
+ // Remove item only after all child transactions have been reverted.
+ PlacesUtils.bookmarks.removeItem(this.item.id);
+ }
+};
+
+
+/**
+ * Transaction for creating a new bookmark.
+ *
+ * @param aURI
+ * the nsIURI of the new bookmark
+ * @param aParentId
+ * the id of the folder in which the bookmark should be added.
+ * @param [optional] aIndex
+ * the index of the item in aParentId
+ * @param [optional] aTitle
+ * the title of the new bookmark
+ * @param [optional] aKeyword
+ * the keyword for the new bookmark
+ * @param [optional] aAnnotations
+ * array of annotations to set for the new bookmark
+ * @param [optional] aChildTransactions
+ * child transactions to commit after creating the bookmark. Prefer
+ * using any of the arguments above if possible. In general, a child
+ * transations should be used only if the change it does has to be
+ * reverted manually when removing the bookmark item.
+ * a child transaction must support setting its bookmark-item
+ * identifier via an "id" js setter.
+ * @param [optional] aPostData
+ * keyword's POST data, if available.
+ *
+ * @return nsITransaction object
+ */
+this.PlacesCreateBookmarkTransaction =
+ function PlacesCreateBookmarkTransaction(aURI, aParentId, aIndex, aTitle,
+ aKeyword, aAnnotations,
+ aChildTransactions, aPostData)
+{
+ this.item = new TransactionItemCache();
+ this.item.uri = aURI;
+ this.item.parentId = aParentId;
+ this.item.index = aIndex;
+ this.item.title = aTitle;
+ this.item.keyword = aKeyword;
+ this.item.postData = aPostData;
+ this.item.annotations = aAnnotations;
+ this.childTransactions = aChildTransactions;
+}
+
+PlacesCreateBookmarkTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function CITXN_doTransaction()
+ {
+ this.item.id = PlacesUtils.bookmarks.insertBookmark(this.item.parentId,
+ this.item.uri,
+ this.item.index,
+ this.item.title);
+ if (this.item.keyword) {
+ PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id,
+ this.item.keyword);
+ if (this.item.postData) {
+ PlacesUtils.setPostDataForBookmark(this.item.id,
+ this.item.postData);
+ }
+ }
+ if (this.item.annotations && this.item.annotations.length > 0)
+ PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);
+
+ if (this.childTransactions && this.childTransactions.length > 0) {
+ // Set the new item id into child transactions.
+ for (let i = 0; i < this.childTransactions.length; ++i) {
+ this.childTransactions[i].item.id = this.item.id;
+ }
+ let txn = new PlacesAggregatedTransaction("Create item childTxn",
+ this.childTransactions);
+ txn.doTransaction();
+ }
+ },
+
+ undoTransaction: function CITXN_undoTransaction()
+ {
+ if (this.childTransactions && this.childTransactions.length > 0) {
+ // Undo transactions should always be done in reverse order.
+ let txn = new PlacesAggregatedTransaction("Create item childTxn",
+ this.childTransactions);
+ txn.undoTransaction();
+ }
+
+ // Remove item only after all child transactions have been reverted.
+ PlacesUtils.bookmarks.removeItem(this.item.id);
+ }
+};
+
+
+/**
+ * Transaction for creating a new separator.
+ *
+ * @param aParentId
+ * the id of the folder in which the separator should be added
+ * @param [optional] aIndex
+ * the index of the item in aParentId
+ *
+ * @return nsITransaction object
+ */
+this.PlacesCreateSeparatorTransaction =
+ function PlacesCreateSeparatorTransaction(aParentId, aIndex)
+{
+ this.item = new TransactionItemCache();
+ this.item.parentId = aParentId;
+ this.item.index = aIndex;
+}
+
+PlacesCreateSeparatorTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function CSTXN_doTransaction()
+ {
+ this.item.id =
+ PlacesUtils.bookmarks.insertSeparator(this.item.parentId, this.item.index);
+ },
+
+ undoTransaction: function CSTXN_undoTransaction()
+ {
+ PlacesUtils.bookmarks.removeItem(this.item.id);
+ }
+};
+
+
+/**
+ * Transaction for creating a new livemark item.
+ *
+ * @see mozIAsyncLivemarks for documentation regarding the arguments.
+ *
+ * @param aFeedURI
+ * nsIURI of the feed
+ * @param [optional] aSiteURI
+ * nsIURI of the page serving the feed
+ * @param aTitle
+ * title for the livemark
+ * @param aParentId
+ * the id of the folder in which the livemark should be added
+ * @param [optional] aIndex
+ * the index of the livemark in aParentId
+ * @param [optional] aAnnotations
+ * array of annotations to set for the new livemark.
+ *
+ * @return nsITransaction object
+ */
+this.PlacesCreateLivemarkTransaction =
+ function PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aTitle, aParentId,
+ aIndex, aAnnotations)
+{
+ this.item = new TransactionItemCache();
+ this.item.feedURI = aFeedURI;
+ this.item.siteURI = aSiteURI;
+ this.item.title = aTitle;
+ this.item.parentId = aParentId;
+ this.item.index = aIndex;
+ this.item.annotations = aAnnotations;
+}
+
+PlacesCreateLivemarkTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function CLTXN_doTransaction()
+ {
+ this._promise = PlacesUtils.livemarks.addLivemark(
+ { title: this.item.title
+ , feedURI: this.item.feedURI
+ , parentId: this.item.parentId
+ , index: this.item.index
+ , siteURI: this.item.siteURI
+ }).then(aLivemark => {
+ this.item.id = aLivemark.id;
+ if (this.item.annotations && this.item.annotations.length > 0) {
+ PlacesUtils.setAnnotationsForItem(this.item.id,
+ this.item.annotations);
+ }
+ }, Cu.reportError);
+ },
+
+ undoTransaction: function CLTXN_undoTransaction()
+ {
+ // The getLivemark callback may fail, but it is used just to serialize,
+ // so it doesn't matter.
+ this._promise = PlacesUtils.livemarks.getLivemark({ id: this.item.id })
+ .then(null, null).then( () => {
+ PlacesUtils.bookmarks.removeItem(this.item.id);
+ });
+ }
+};
+
+
+/**
+ * Transaction for removing a livemark item.
+ *
+ * @param aLivemarkId
+ * the identifier of the folder for the livemark.
+ *
+ * @return nsITransaction object
+ * @note used internally by PlacesRemoveItemTransaction, DO NOT EXPORT.
+ */
+function PlacesRemoveLivemarkTransaction(aLivemarkId)
+{
+ this.item = new TransactionItemCache();
+ this.item.id = aLivemarkId;
+ this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id);
+ this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id);
+
+ let annos = PlacesUtils.getAnnotationsForItem(this.item.id);
+ // Exclude livemark service annotations, those will be recreated automatically
+ let annosToExclude = [PlacesUtils.LMANNO_FEEDURI,
+ PlacesUtils.LMANNO_SITEURI];
+ this.item.annotations = annos.filter(function(aValue, aIndex, aArray) {
+ return !annosToExclude.includes(aValue.name);
+ });
+ this.item.dateAdded = PlacesUtils.bookmarks.getItemDateAdded(this.item.id);
+ this.item.lastModified =
+ PlacesUtils.bookmarks.getItemLastModified(this.item.id);
+}
+
+PlacesRemoveLivemarkTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function RLTXN_doTransaction()
+ {
+ PlacesUtils.livemarks.getLivemark({ id: this.item.id })
+ .then(aLivemark => {
+ this.item.feedURI = aLivemark.feedURI;
+ this.item.siteURI = aLivemark.siteURI;
+ PlacesUtils.bookmarks.removeItem(this.item.id);
+ }, Cu.reportError);
+ },
+
+ undoTransaction: function RLTXN_undoTransaction()
+ {
+ // Undo work must be serialized, otherwise won't be able to know the
+ // feedURI and siteURI of the livemark.
+ // The getLivemark callback is expected to receive a failure status but it
+ // is used just to serialize, so doesn't matter.
+ PlacesUtils.livemarks.getLivemark({ id: this.item.id })
+ .then(null, () => {
+ PlacesUtils.livemarks.addLivemark({ parentId: this.item.parentId
+ , title: this.item.title
+ , siteURI: this.item.siteURI
+ , feedURI: this.item.feedURI
+ , index: this.item.index
+ , lastModified: this.item.lastModified
+ }).then(
+ aLivemark => {
+ let itemId = aLivemark.id;
+ PlacesUtils.bookmarks.setItemDateAdded(itemId, this.item.dateAdded);
+ PlacesUtils.setAnnotationsForItem(itemId, this.item.annotations);
+ }, Cu.reportError);
+ });
+ }
+};
+
+
+/**
+ * Transaction for moving an Item.
+ *
+ * @param aItemId
+ * the id of the item to move
+ * @param aNewParentId
+ * id of the new parent to move to
+ * @param aNewIndex
+ * index of the new position to move to
+ *
+ * @return nsITransaction object
+ */
+this.PlacesMoveItemTransaction =
+ function PlacesMoveItemTransaction(aItemId, aNewParentId, aNewIndex)
+{
+ this.item = new TransactionItemCache();
+ this.item.id = aItemId;
+ this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id);
+ this.new = new TransactionItemCache();
+ this.new.parentId = aNewParentId;
+ this.new.index = aNewIndex;
+}
+
+PlacesMoveItemTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function MITXN_doTransaction()
+ {
+ this.item.index = PlacesUtils.bookmarks.getItemIndex(this.item.id);
+ PlacesUtils.bookmarks.moveItem(this.item.id,
+ this.new.parentId, this.new.index);
+ this._undoIndex = PlacesUtils.bookmarks.getItemIndex(this.item.id);
+ },
+
+ undoTransaction: function MITXN_undoTransaction()
+ {
+ // moving down in the same parent takes in count removal of the item
+ // so to revert positions we must move to oldIndex + 1
+ if (this.new.parentId == this.item.parentId &&
+ this.item.index > this._undoIndex) {
+ PlacesUtils.bookmarks.moveItem(this.item.id, this.item.parentId,
+ this.item.index + 1);
+ }
+ else {
+ PlacesUtils.bookmarks.moveItem(this.item.id, this.item.parentId,
+ this.item.index);
+ }
+ }
+};
+
+
+/**
+ * Transaction for removing an Item
+ *
+ * @param aItemId
+ * id of the item to remove
+ *
+ * @return nsITransaction object
+ */
+this.PlacesRemoveItemTransaction =
+ function PlacesRemoveItemTransaction(aItemId)
+{
+ if (PlacesUtils.isRootItem(aItemId))
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // if the item lives within a tag container, use the tagging transactions
+ let parent = PlacesUtils.bookmarks.getFolderIdForItem(aItemId);
+ let grandparent = PlacesUtils.bookmarks.getFolderIdForItem(parent);
+ if (grandparent == PlacesUtils.tagsFolderId) {
+ let uri = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
+ return new PlacesUntagURITransaction(uri, [parent]);
+ }
+
+ // if the item is a livemark container we will not save its children.
+ if (PlacesUtils.annotations.itemHasAnnotation(aItemId,
+ PlacesUtils.LMANNO_FEEDURI))
+ return new PlacesRemoveLivemarkTransaction(aItemId);
+
+ this.item = new TransactionItemCache();
+ this.item.id = aItemId;
+ this.item.itemType = PlacesUtils.bookmarks.getItemType(this.item.id);
+ if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
+ this.childTransactions = this._getFolderContentsTransactions();
+ // Remove this folder itself.
+ let txn = PlacesUtils.bookmarks.getRemoveFolderTransaction(this.item.id);
+ this.childTransactions.push(txn);
+ }
+ else if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
+ this.item.uri = PlacesUtils.bookmarks.getBookmarkURI(this.item.id);
+ this.item.keyword =
+ PlacesUtils.bookmarks.getKeywordForBookmark(this.item.id);
+ if (this.item.keyword)
+ this.item.postData = PlacesUtils.getPostDataForBookmark(this.item.id);
+ }
+
+ if (this.item.itemType != Ci.nsINavBookmarksService.TYPE_SEPARATOR)
+ this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id);
+
+ this.item.parentId = PlacesUtils.bookmarks.getFolderIdForItem(this.item.id);
+ this.item.annotations = PlacesUtils.getAnnotationsForItem(this.item.id);
+ this.item.dateAdded = PlacesUtils.bookmarks.getItemDateAdded(this.item.id);
+ this.item.lastModified =
+ PlacesUtils.bookmarks.getItemLastModified(this.item.id);
+}
+
+PlacesRemoveItemTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function RITXN_doTransaction()
+ {
+ this.item.index = PlacesUtils.bookmarks.getItemIndex(this.item.id);
+
+ if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
+ let txn = new PlacesAggregatedTransaction("Remove item childTxn",
+ this.childTransactions);
+ txn.doTransaction();
+ }
+ else {
+ // Before removing the bookmark, save its tags.
+ let tags = this.item.uri ?
+ PlacesUtils.tagging.getTagsForURI(this.item.uri) : null;
+
+ PlacesUtils.bookmarks.removeItem(this.item.id);
+
+ // If this was the last bookmark (excluding tag-items) for this url,
+ // persist the tags.
+ if (tags && PlacesUtils.getMostRecentBookmarkForURI(this.item.uri) == -1) {
+ this.item.tags = tags;
+ }
+ }
+ },
+
+ undoTransaction: function RITXN_undoTransaction()
+ {
+ if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
+ this.item.id = PlacesUtils.bookmarks.insertBookmark(this.item.parentId,
+ this.item.uri,
+ this.item.index,
+ this.item.title);
+ if (this.item.tags && this.item.tags.length > 0)
+ PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
+ if (this.item.keyword) {
+ PlacesUtils.bookmarks.setKeywordForBookmark(this.item.id,
+ this.item.keyword);
+ if (this.item.postData) {
+ PlacesUtils.bookmarks.setPostDataForBookmark(this.item.id);
+ }
+ }
+ }
+ else if (this.item.itemType == Ci.nsINavBookmarksService.TYPE_FOLDER) {
+ let txn = new PlacesAggregatedTransaction("Remove item childTxn",
+ this.childTransactions);
+ txn.undoTransaction();
+ }
+ else { // TYPE_SEPARATOR
+ this.item.id = PlacesUtils.bookmarks.insertSeparator(this.item.parentId,
+ this.item.index);
+ }
+
+ if (this.item.annotations && this.item.annotations.length > 0)
+ PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);
+
+ PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.item.dateAdded);
+ PlacesUtils.bookmarks.setItemLastModified(this.item.id,
+ this.item.lastModified);
+ },
+
+ /**
+ * Returns a flat, ordered list of transactions for a depth-first recreation
+ * of items within this folder.
+ */
+ _getFolderContentsTransactions:
+ function RITXN__getFolderContentsTransactions()
+ {
+ let transactions = [];
+ let contents =
+ PlacesUtils.getFolderContents(this.item.id, false, false).root;
+ for (let i = 0; i < contents.childCount; ++i) {
+ let childId = contents.getChild(i).itemId;
+ if (!PlacesUIUtils._isLivemark(childId)) {
+ let txn = new PlacesRemoveItemTransaction(childId);
+ transactions.push(txn);
+ }
+ }
+ contents.containerOpen = false;
+ // Reverse transactions to preserve parent-child relationship.
+ return transactions.reverse();
+ }
+};
+
+
+/**
+ * Transaction for editting a bookmark's title.
+ *
+ * @param aItemId
+ * id of the item to edit
+ * @param aNewTitle
+ * new title for the item to edit
+ *
+ * @return nsITransaction object
+ */
+this.PlacesEditItemTitleTransaction =
+ function PlacesEditItemTitleTransaction(aItemId, aNewTitle)
+{
+ this.item = new TransactionItemCache();
+ this.item.id = aItemId;
+ this.new = new TransactionItemCache();
+ this.new.title = aNewTitle;
+}
+
+PlacesEditItemTitleTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function EITTXN_doTransaction()
+ {
+ this.item.title = PlacesUtils.bookmarks.getItemTitle(this.item.id);
+ PlacesUtils.bookmarks.setItemTitle(this.item.id, this.new.title);
+ },
+
+ undoTransaction: function EITTXN_undoTransaction()
+ {
+ PlacesUtils.bookmarks.setItemTitle(this.item.id, this.item.title);
+ }
+};
+
+
+/**
+ * Transaction for editing a bookmark's uri.
+ *
+ * @param aItemId
+ * id of the bookmark to edit
+ * @param aNewURI
+ * new uri for the bookmark
+ *
+ * @return nsITransaction object
+ */
+this.PlacesEditBookmarkURITransaction =
+ function PlacesEditBookmarkURITransaction(aItemId, aNewURI) {
+ this.item = new TransactionItemCache();
+ this.item.id = aItemId;
+ this.new = new TransactionItemCache();
+ this.new.uri = aNewURI;
+}
+
+PlacesEditBookmarkURITransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function EBUTXN_doTransaction()
+ {
+ this.item.uri = PlacesUtils.bookmarks.getBookmarkURI(this.item.id);
+ PlacesUtils.bookmarks.changeBookmarkURI(this.item.id, this.new.uri);
+ // move tags from old URI to new URI
+ this.item.tags = PlacesUtils.tagging.getTagsForURI(this.item.uri);
+ if (this.item.tags.length > 0) {
+ // only untag the old URI if this is the only bookmark
+ if (PlacesUtils.getBookmarksForURI(this.item.uri, {}).length == 0)
+ PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags);
+ PlacesUtils.tagging.tagURI(this.new.uri, this.item.tags);
+ }
+ },
+
+ undoTransaction: function EBUTXN_undoTransaction()
+ {
+ PlacesUtils.bookmarks.changeBookmarkURI(this.item.id, this.item.uri);
+ // move tags from new URI to old URI
+ if (this.item.tags.length > 0) {
+ // only untag the new URI if this is the only bookmark
+ if (PlacesUtils.getBookmarksForURI(this.new.uri, {}).length == 0)
+ PlacesUtils.tagging.untagURI(this.new.uri, this.item.tags);
+ PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
+ }
+ }
+};
+
+
+/**
+ * Transaction for setting/unsetting an item annotation
+ *
+ * @param aItemId
+ * id of the item where to set annotation
+ * @param aAnnotationObject
+ * Object representing an annotation, containing the following
+ * properties: name, flags, expires, value.
+ * If value is null the annotation will be removed
+ *
+ * @return nsITransaction object
+ */
+this.PlacesSetItemAnnotationTransaction =
+ function PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject)
+{
+ this.item = new TransactionItemCache();
+ this.item.id = aItemId;
+ this.new = new TransactionItemCache();
+ this.new.annotations = [aAnnotationObject];
+}
+
+PlacesSetItemAnnotationTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function SIATXN_doTransaction()
+ {
+ let annoName = this.new.annotations[0].name;
+ if (PlacesUtils.annotations.itemHasAnnotation(this.item.id, annoName)) {
+ // fill the old anno if it is set
+ let flags = {}, expires = {}, type = {};
+ PlacesUtils.annotations.getItemAnnotationInfo(this.item.id, annoName, flags,
+ expires, type);
+ let value = PlacesUtils.annotations.getItemAnnotation(this.item.id,
+ annoName);
+ this.item.annotations = [{ name: annoName,
+ type: type.value,
+ flags: flags.value,
+ value: value,
+ expires: expires.value }];
+ }
+ else {
+ // create an empty old anno
+ this.item.annotations = [{ name: annoName,
+ flags: 0,
+ value: null,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER }];
+ }
+
+ PlacesUtils.setAnnotationsForItem(this.item.id, this.new.annotations);
+ },
+
+ undoTransaction: function SIATXN_undoTransaction()
+ {
+ PlacesUtils.setAnnotationsForItem(this.item.id, this.item.annotations);
+ }
+};
+
+
+/**
+ * Transaction for setting/unsetting a page annotation
+ *
+ * @param aURI
+ * URI of the page where to set annotation
+ * @param aAnnotationObject
+ * Object representing an annotation, containing the following
+ * properties: name, flags, expires, value.
+ * If value is null the annotation will be removed
+ *
+ * @return nsITransaction object
+ */
+this.PlacesSetPageAnnotationTransaction =
+ function PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject)
+{
+ this.item = new TransactionItemCache();
+ this.item.uri = aURI;
+ this.new = new TransactionItemCache();
+ this.new.annotations = [aAnnotationObject];
+}
+
+PlacesSetPageAnnotationTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function SPATXN_doTransaction()
+ {
+ let annoName = this.new.annotations[0].name;
+ if (PlacesUtils.annotations.pageHasAnnotation(this.item.uri, annoName)) {
+ // fill the old anno if it is set
+ let flags = {}, expires = {}, type = {};
+ PlacesUtils.annotations.getPageAnnotationInfo(this.item.uri, annoName, flags,
+ expires, type);
+ let value = PlacesUtils.annotations.getPageAnnotation(this.item.uri,
+ annoName);
+ this.item.annotations = [{ name: annoName,
+ flags: flags.value,
+ value: value,
+ expires: expires.value }];
+ }
+ else {
+ // create an empty old anno
+ this.item.annotations = [{ name: annoName,
+ type: Ci.nsIAnnotationService.TYPE_STRING,
+ flags: 0,
+ value: null,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER }];
+ }
+
+ PlacesUtils.setAnnotationsForURI(this.item.uri, this.new.annotations);
+ },
+
+ undoTransaction: function SPATXN_undoTransaction()
+ {
+ PlacesUtils.setAnnotationsForURI(this.item.uri, this.item.annotations);
+ }
+};
+
+
+/**
+ * Transaction for editing a bookmark's keyword.
+ *
+ * @param aItemId
+ * id of the bookmark to edit
+ * @param aNewKeyword
+ * new keyword for the bookmark
+ * @param aNewPostData [optional]
+ * new keyword's POST data, if available
+ * @param aOldKeyword [optional]
+ * old keyword of the bookmark
+ *
+ * @return nsITransaction object
+ */
+this.PlacesEditBookmarkKeywordTransaction =
+ function PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword,
+ aNewPostData, aOldKeyword) {
+ this.item = new TransactionItemCache();
+ this.item.id = aItemId;
+ this.item.keyword = aOldKeyword;
+ this.item.href = (PlacesUtils.bookmarks.getBookmarkURI(aItemId)).spec;
+ this.new = new TransactionItemCache();
+ this.new.keyword = aNewKeyword;
+ this.new.postData = aNewPostData
+}
+
+PlacesEditBookmarkKeywordTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function EBKTXN_doTransaction()
+ {
+ let done = false;
+ Task.spawn(function* () {
+ if (this.item.keyword) {
+ let oldEntry = yield PlacesUtils.keywords.fetch(this.item.keyword);
+ this.item.postData = oldEntry.postData;
+ yield PlacesUtils.keywords.remove(this.item.keyword);
+ }
+
+ if (this.new.keyword) {
+ yield PlacesUtils.keywords.insert({
+ url: this.item.href,
+ keyword: this.new.keyword,
+ postData: this.new.postData || this.item.postData
+ });
+ }
+ }.bind(this)).catch(Cu.reportError)
+ .then(() => done = true);
+ // TODO: Until we can move to PlacesTransactions.jsm, we must spin the
+ // events loop :(
+ let thread = Services.tm.currentThread;
+ while (!done) {
+ thread.processNextEvent(true);
+ }
+ },
+
+ undoTransaction: function EBKTXN_undoTransaction()
+ {
+
+ let done = false;
+ Task.spawn(function* () {
+ if (this.new.keyword) {
+ yield PlacesUtils.keywords.remove(this.new.keyword);
+ }
+
+ if (this.item.keyword) {
+ yield PlacesUtils.keywords.insert({
+ url: this.item.href,
+ keyword: this.item.keyword,
+ postData: this.item.postData
+ });
+ }
+ }.bind(this)).catch(Cu.reportError)
+ .then(() => done = true);
+ // TODO: Until we can move to PlacesTransactions.jsm, we must spin the
+ // events loop :(
+ let thread = Services.tm.currentThread;
+ while (!done) {
+ thread.processNextEvent(true);
+ }
+ }
+};
+
+
+/**
+ * Transaction for editing the post data associated with a bookmark.
+ *
+ * @param aItemId
+ * id of the bookmark to edit
+ * @param aPostData
+ * post data
+ *
+ * @return nsITransaction object
+ */
+this.PlacesEditBookmarkPostDataTransaction =
+ function PlacesEditBookmarkPostDataTransaction(aItemId, aPostData)
+{
+ this.item = new TransactionItemCache();
+ this.item.id = aItemId;
+ this.new = new TransactionItemCache();
+ this.new.postData = aPostData;
+}
+
+PlacesEditBookmarkPostDataTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction() {
+ // Setting null postData is not supported by the current schema.
+ if (this.new.postData) {
+ this.item.postData = PlacesUtils.getPostDataForBookmark(this.item.id);
+ PlacesUtils.setPostDataForBookmark(this.item.id, this.new.postData);
+ }
+ },
+
+ undoTransaction() {
+ // Setting null postData is not supported by the current schema.
+ if (this.item.postData) {
+ PlacesUtils.setPostDataForBookmark(this.item.id, this.item.postData);
+ }
+ }
+};
+
+
+/**
+ * Transaction for editing an item's date added property.
+ *
+ * @param aItemId
+ * id of the item to edit
+ * @param aNewDateAdded
+ * new date added for the item
+ *
+ * @return nsITransaction object
+ */
+this.PlacesEditItemDateAddedTransaction =
+ function PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded)
+{
+ this.item = new TransactionItemCache();
+ this.item.id = aItemId;
+ this.new = new TransactionItemCache();
+ this.new.dateAdded = aNewDateAdded;
+}
+
+PlacesEditItemDateAddedTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function EIDATXN_doTransaction()
+ {
+ // Child transactions have the id set as parentId.
+ if (this.item.id == -1 && this.item.parentId != -1)
+ this.item.id = this.item.parentId;
+ this.item.dateAdded =
+ PlacesUtils.bookmarks.getItemDateAdded(this.item.id);
+ PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.new.dateAdded);
+ },
+
+ undoTransaction: function EIDATXN_undoTransaction()
+ {
+ PlacesUtils.bookmarks.setItemDateAdded(this.item.id, this.item.dateAdded);
+ }
+};
+
+
+/**
+ * Transaction for editing an item's last modified time.
+ *
+ * @param aItemId
+ * id of the item to edit
+ * @param aNewLastModified
+ * new last modified date for the item
+ *
+ * @return nsITransaction object
+ */
+this.PlacesEditItemLastModifiedTransaction =
+ function PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified)
+{
+ this.item = new TransactionItemCache();
+ this.item.id = aItemId;
+ this.new = new TransactionItemCache();
+ this.new.lastModified = aNewLastModified;
+}
+
+PlacesEditItemLastModifiedTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction:
+ function EILMTXN_doTransaction()
+ {
+ // Child transactions have the id set as parentId.
+ if (this.item.id == -1 && this.item.parentId != -1)
+ this.item.id = this.item.parentId;
+ this.item.lastModified =
+ PlacesUtils.bookmarks.getItemLastModified(this.item.id);
+ PlacesUtils.bookmarks.setItemLastModified(this.item.id,
+ this.new.lastModified);
+ },
+
+ undoTransaction:
+ function EILMTXN_undoTransaction()
+ {
+ PlacesUtils.bookmarks.setItemLastModified(this.item.id,
+ this.item.lastModified);
+ }
+};
+
+
+/**
+ * Transaction for sorting a folder by name
+ *
+ * @param aFolderId
+ * id of the folder to sort
+ *
+ * @return nsITransaction object
+ */
+this.PlacesSortFolderByNameTransaction =
+ function PlacesSortFolderByNameTransaction(aFolderId)
+{
+ this.item = new TransactionItemCache();
+ this.item.id = aFolderId;
+}
+
+PlacesSortFolderByNameTransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function SFBNTXN_doTransaction()
+ {
+ this._oldOrder = [];
+
+ let contents =
+ PlacesUtils.getFolderContents(this.item.id, false, false).root;
+ let count = contents.childCount;
+
+ // sort between separators
+ let newOrder = [];
+ let preSep = []; // temporary array for sorting each group of items
+ let sortingMethod =
+ function (a, b) {
+ if (PlacesUtils.nodeIsContainer(a) && !PlacesUtils.nodeIsContainer(b))
+ return -1;
+ if (!PlacesUtils.nodeIsContainer(a) && PlacesUtils.nodeIsContainer(b))
+ return 1;
+ return a.title.localeCompare(b.title);
+ };
+
+ for (let i = 0; i < count; ++i) {
+ let item = contents.getChild(i);
+ this._oldOrder[item.itemId] = i;
+ if (PlacesUtils.nodeIsSeparator(item)) {
+ if (preSep.length > 0) {
+ preSep.sort(sortingMethod);
+ newOrder = newOrder.concat(preSep);
+ preSep.splice(0, preSep.length);
+ }
+ newOrder.push(item);
+ }
+ else
+ preSep.push(item);
+ }
+ contents.containerOpen = false;
+
+ if (preSep.length > 0) {
+ preSep.sort(sortingMethod);
+ newOrder = newOrder.concat(preSep);
+ }
+
+ // set the nex indexes
+ let callback = {
+ runBatched: function() {
+ for (let i = 0; i < newOrder.length; ++i) {
+ PlacesUtils.bookmarks.setItemIndex(newOrder[i].itemId, i);
+ }
+ }
+ };
+ PlacesUtils.bookmarks.runInBatchMode(callback, null);
+ },
+
+ undoTransaction: function SFBNTXN_undoTransaction()
+ {
+ let callback = {
+ _self: this,
+ runBatched: function() {
+ for (let item in this._self._oldOrder)
+ PlacesUtils.bookmarks.setItemIndex(item, this._self._oldOrder[item]);
+ }
+ };
+ PlacesUtils.bookmarks.runInBatchMode(callback, null);
+ }
+};
+
+
+/**
+ * Transaction for tagging a URL with the given set of tags. Current tags set
+ * for the URL persist. It's the caller's job to check whether or not aURI
+ * was already tagged by any of the tags in aTags, undoing this tags
+ * transaction removes them all from aURL!
+ *
+ * @param aURI
+ * the URL to tag.
+ * @param aTags
+ * Array of tags to set for the given URL.
+ */
+this.PlacesTagURITransaction =
+ function PlacesTagURITransaction(aURI, aTags)
+{
+ this.item = new TransactionItemCache();
+ this.item.uri = aURI;
+ this.item.tags = aTags;
+}
+
+PlacesTagURITransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function TUTXN_doTransaction()
+ {
+ if (PlacesUtils.getMostRecentBookmarkForURI(this.item.uri) == -1) {
+ // There is no bookmark for this uri, but we only allow to tag bookmarks.
+ // Force an unfiled bookmark first.
+ this.item.id =
+ PlacesUtils.bookmarks
+ .insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ this.item.uri,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ PlacesUtils.history.getPageTitle(this.item.uri));
+ }
+ PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
+ },
+
+ undoTransaction: function TUTXN_undoTransaction()
+ {
+ if (this.item.id != -1) {
+ PlacesUtils.bookmarks.removeItem(this.item.id);
+ this.item.id = -1;
+ }
+ PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags);
+ }
+};
+
+
+/**
+ * Transaction for removing tags from a URL. It's the caller's job to check
+ * whether or not aURI isn't tagged by any of the tags in aTags, undoing this
+ * tags transaction adds them all to aURL!
+ *
+ * @param aURI
+ * the URL to un-tag.
+ * @param aTags
+ * Array of tags to unset. pass null to remove all tags from the given
+ * url.
+ */
+this.PlacesUntagURITransaction =
+ function PlacesUntagURITransaction(aURI, aTags)
+{
+ this.item = new TransactionItemCache();
+ this.item.uri = aURI;
+ if (aTags) {
+ // Within this transaction, we cannot rely on tags given by itemId
+ // since the tag containers may be gone after we call untagURI.
+ // Thus, we convert each tag given by its itemId to name.
+ let tags = [];
+ for (let i = 0; i < aTags.length; ++i) {
+ if (typeof(aTags[i]) == "number")
+ tags.push(PlacesUtils.bookmarks.getItemTitle(aTags[i]));
+ else
+ tags.push(aTags[i]);
+ }
+ this.item.tags = tags;
+ }
+}
+
+PlacesUntagURITransaction.prototype = {
+ __proto__: BaseTransaction.prototype,
+
+ doTransaction: function UTUTXN_doTransaction()
+ {
+ // Filter tags existing on the bookmark, otherwise on undo we may try to
+ // set nonexistent tags.
+ let tags = PlacesUtils.tagging.getTagsForURI(this.item.uri);
+ this.item.tags = this.item.tags.filter(function (aTag) {
+ return tags.includes(aTag);
+ });
+ PlacesUtils.tagging.untagURI(this.item.uri, this.item.tags);
+ },
+
+ undoTransaction: function UTUTXN_undoTransaction()
+ {
+ PlacesUtils.tagging.tagURI(this.item.uri, this.item.tags);
+ }
+};
+
+/**
+ * Executes a boolean validate function, throwing if it returns false.
+ *
+ * @param boolValidateFn
+ * A boolean validate function.
+ * @return the input value.
+ * @throws if input doesn't pass the validate function.
+ */
+function simpleValidateFunc(boolValidateFn) {
+ return (v, input) => {
+ if (!boolValidateFn(v, input))
+ throw new Error("Invalid value");
+ return v;
+ };
+}
diff --git a/components/places/src/SQLFunctions.cpp b/components/places/src/SQLFunctions.cpp
new file mode 100644
index 000000000..e3cc7d7f0
--- /dev/null
+++ b/components/places/src/SQLFunctions.cpp
@@ -0,0 +1,941 @@
+/* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/storage.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsEscape.h"
+#include "mozIPlacesAutoComplete.h"
+#include "SQLFunctions.h"
+#include "nsMathUtils.h"
+#include "nsUTF8Utils.h"
+#include "nsINavHistoryService.h"
+#include "nsPrintfCString.h"
+#include "nsNavHistory.h"
+#include "mozilla/Likely.h"
+#include "nsVariant.h"
+#include "mozilla/HashFunctions.h"
+
+// Maximum number of chars to search through.
+// MatchAutoCompleteFunction won't look for matches over this threshold.
+#define MAX_CHARS_TO_SEARCH_THROUGH 255
+
+using namespace mozilla::storage;
+
+// Keep the GUID-related parts of this file in sync with toolkit/downloads/SQLFunctions.cpp!
+
+////////////////////////////////////////////////////////////////////////////////
+//// Anonymous Helpers
+
+namespace {
+
+ typedef nsACString::const_char_iterator const_char_iterator;
+
+ /**
+ * Get a pointer to the word boundary after aStart if aStart points to an
+ * ASCII letter (i.e. [a-zA-Z]). Otherwise, return aNext, which we assume
+ * points to the next character in the UTF-8 sequence.
+ *
+ * We define a word boundary as anything that's not [a-z] -- this lets us
+ * match CamelCase words.
+ *
+ * @param aStart the beginning of the UTF-8 sequence
+ * @param aNext the next character in the sequence
+ * @param aEnd the first byte which is not part of the sequence
+ *
+ * @return a pointer to the next word boundary after aStart
+ */
+ static
+ MOZ_ALWAYS_INLINE const_char_iterator
+ nextWordBoundary(const_char_iterator const aStart,
+ const_char_iterator const aNext,
+ const_char_iterator const aEnd) {
+
+ const_char_iterator cur = aStart;
+ if (('a' <= *cur && *cur <= 'z') ||
+ ('A' <= *cur && *cur <= 'Z')) {
+
+ // Since we'll halt as soon as we see a non-ASCII letter, we can do a
+ // simple byte-by-byte comparison here and avoid the overhead of a
+ // UTF8CharEnumerator.
+ do {
+ cur++;
+ } while (cur < aEnd && 'a' <= *cur && *cur <= 'z');
+ }
+ else {
+ cur = aNext;
+ }
+
+ return cur;
+ }
+
+ enum FindInStringBehavior {
+ eFindOnBoundary,
+ eFindAnywhere
+ };
+
+ /**
+ * findAnywhere and findOnBoundary do almost the same thing, so it's natural
+ * to implement them in terms of a single function. They're both
+ * performance-critical functions, however, and checking aBehavior makes them
+ * a bit slower. Our solution is to define findInString as MOZ_ALWAYS_INLINE
+ * and rely on the compiler to optimize out the aBehavior check.
+ *
+ * @param aToken
+ * The token we're searching for
+ * @param aSourceString
+ * The string in which we're searching
+ * @param aBehavior
+ * eFindOnBoundary if we should only consider matchines which occur on
+ * word boundaries, or eFindAnywhere if we should consider matches
+ * which appear anywhere.
+ *
+ * @return true if aToken was found in aSourceString, false otherwise.
+ */
+ static
+ MOZ_ALWAYS_INLINE bool
+ findInString(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString,
+ FindInStringBehavior aBehavior)
+ {
+ // CaseInsensitiveUTF8CharsEqual assumes that there's at least one byte in
+ // the both strings, so don't pass an empty token here.
+ NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
+
+ // We cannot match anything if there is nothing to search.
+ if (aSourceString.IsEmpty()) {
+ return false;
+ }
+
+ const_char_iterator tokenStart(aToken.BeginReading()),
+ tokenEnd(aToken.EndReading()),
+ sourceStart(aSourceString.BeginReading()),
+ sourceEnd(aSourceString.EndReading());
+
+ do {
+ // We are on a word boundary (if aBehavior == eFindOnBoundary). See if
+ // aToken matches sourceStart.
+
+ // Check whether the first character in the token matches the character
+ // at sourceStart. At the same time, get a pointer to the next character
+ // in both the token and the source.
+ const_char_iterator sourceNext, tokenCur;
+ bool error;
+ if (CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart,
+ sourceEnd, tokenEnd,
+ &sourceNext, &tokenCur, &error)) {
+
+ // We don't need to check |error| here -- if
+ // CaseInsensitiveUTF8CharCompare encounters an error, it'll also
+ // return false and we'll catch the error outside the if.
+
+ const_char_iterator sourceCur = sourceNext;
+ while (true) {
+ if (tokenCur >= tokenEnd) {
+ // We matched the whole token!
+ return true;
+ }
+
+ if (sourceCur >= sourceEnd) {
+ // We ran into the end of source while matching a token. This
+ // means we'll never find the token we're looking for.
+ return false;
+ }
+
+ if (!CaseInsensitiveUTF8CharsEqual(sourceCur, tokenCur,
+ sourceEnd, tokenEnd,
+ &sourceCur, &tokenCur, &error)) {
+ // sourceCur doesn't match tokenCur (or there's an error), so break
+ // out of this loop.
+ break;
+ }
+ }
+ }
+
+ // If something went wrong above, get out of here!
+ if (MOZ_UNLIKELY(error)) {
+ return false;
+ }
+
+ // We didn't match the token. If we're searching for matches on word
+ // boundaries, skip to the next word boundary. Otherwise, advance
+ // forward one character, using the sourceNext pointer we saved earlier.
+
+ if (aBehavior == eFindOnBoundary) {
+ sourceStart = nextWordBoundary(sourceStart, sourceNext, sourceEnd);
+ }
+ else {
+ sourceStart = sourceNext;
+ }
+
+ } while (sourceStart < sourceEnd);
+
+ return false;
+ }
+
+ static
+ MOZ_ALWAYS_INLINE nsDependentCString
+ getSharedString(mozIStorageValueArray* aValues, uint32_t aIndex) {
+ uint32_t len;
+ const char* str = aValues->AsSharedUTF8String(aIndex, &len);
+ if (!str) {
+ return nsDependentCString("", (uint32_t)0);
+ }
+ return nsDependentCString(str, len);
+ }
+
+} // End anonymous namespace
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// AutoComplete Matching Function
+
+ /* static */
+ nsresult
+ MatchAutoCompleteFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<MatchAutoCompleteFunction> function =
+ new MatchAutoCompleteFunction();
+
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("autocomplete_match"), kArgIndexLength, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ /* static */
+ nsDependentCSubstring
+ MatchAutoCompleteFunction::fixupURISpec(const nsACString &aURISpec,
+ int32_t aMatchBehavior,
+ nsACString &aSpecBuf)
+ {
+ nsDependentCSubstring fixedSpec;
+
+ // Try to unescape the string. If that succeeds and yields a different
+ // string which is also valid UTF-8, we'll use it.
+ // Otherwise, we will simply use our original string.
+ bool unescaped = NS_UnescapeURL(aURISpec.BeginReading(),
+ aURISpec.Length(), esc_SkipControl, aSpecBuf);
+ if (unescaped && IsUTF8(aSpecBuf)) {
+ fixedSpec.Rebind(aSpecBuf, 0);
+ } else {
+ fixedSpec.Rebind(aURISpec, 0);
+ }
+
+ if (aMatchBehavior == mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED)
+ return fixedSpec;
+
+ if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("http://"))) {
+ fixedSpec.Rebind(fixedSpec, 7);
+ } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("https://"))) {
+ fixedSpec.Rebind(fixedSpec, 8);
+ } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("ftp://"))) {
+ fixedSpec.Rebind(fixedSpec, 6);
+ }
+
+ if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("www."))) {
+ fixedSpec.Rebind(fixedSpec, 4);
+ }
+
+ return fixedSpec;
+ }
+
+ /* static */
+ bool
+ MatchAutoCompleteFunction::findAnywhere(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString)
+ {
+ // We can't use FindInReadable here; it works only for ASCII.
+
+ return findInString(aToken, aSourceString, eFindAnywhere);
+ }
+
+ /* static */
+ bool
+ MatchAutoCompleteFunction::findOnBoundary(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString)
+ {
+ return findInString(aToken, aSourceString, eFindOnBoundary);
+ }
+
+ /* static */
+ bool
+ MatchAutoCompleteFunction::findBeginning(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString)
+ {
+ NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
+
+ // We can't use StringBeginsWith here, unfortunately. Although it will
+ // happily take a case-insensitive UTF8 comparator, it eventually calls
+ // nsACString::Equals, which checks that the two strings contain the same
+ // number of bytes before calling the comparator. Two characters may be
+ // case-insensitively equal while taking up different numbers of bytes, so
+ // this is not what we want.
+
+ const_char_iterator tokenStart(aToken.BeginReading()),
+ tokenEnd(aToken.EndReading()),
+ sourceStart(aSourceString.BeginReading()),
+ sourceEnd(aSourceString.EndReading());
+
+ bool dummy;
+ while (sourceStart < sourceEnd &&
+ CaseInsensitiveUTF8CharsEqual(sourceStart, tokenStart,
+ sourceEnd, tokenEnd,
+ &sourceStart, &tokenStart, &dummy)) {
+
+ // We found the token!
+ if (tokenStart >= tokenEnd) {
+ return true;
+ }
+ }
+
+ // We don't need to check CaseInsensitiveUTF8CharsEqual's error condition
+ // (stored in |dummy|), since the function will return false if it
+ // encounters an error.
+
+ return false;
+ }
+
+ /* static */
+ bool
+ MatchAutoCompleteFunction::findBeginningCaseSensitive(
+ const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString)
+ {
+ NS_PRECONDITION(!aToken.IsEmpty(), "Don't search for an empty token!");
+
+ return StringBeginsWith(aSourceString, aToken);
+ }
+
+ /* static */
+ MatchAutoCompleteFunction::searchFunctionPtr
+ MatchAutoCompleteFunction::getSearchFunction(int32_t aBehavior)
+ {
+ switch (aBehavior) {
+ case mozIPlacesAutoComplete::MATCH_ANYWHERE:
+ case mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED:
+ return findAnywhere;
+ case mozIPlacesAutoComplete::MATCH_BEGINNING:
+ return findBeginning;
+ case mozIPlacesAutoComplete::MATCH_BEGINNING_CASE_SENSITIVE:
+ return findBeginningCaseSensitive;
+ case mozIPlacesAutoComplete::MATCH_BOUNDARY:
+ default:
+ return findOnBoundary;
+ };
+ }
+
+ NS_IMPL_ISUPPORTS(
+ MatchAutoCompleteFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ MatchAutoCompleteFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ // Macro to make the code a bit cleaner and easier to read. Operates on
+ // searchBehavior.
+ int32_t searchBehavior = aArguments->AsInt32(kArgIndexSearchBehavior);
+ #define HAS_BEHAVIOR(aBitName) \
+ (searchBehavior & mozIPlacesAutoComplete::BEHAVIOR_##aBitName)
+
+ nsDependentCString searchString =
+ getSharedString(aArguments, kArgSearchString);
+ nsDependentCString url =
+ getSharedString(aArguments, kArgIndexURL);
+
+ int32_t matchBehavior = aArguments->AsInt32(kArgIndexMatchBehavior);
+
+ // We only want to filter javascript: URLs if we are not supposed to search
+ // for them, and the search does not start with "javascript:".
+ if (matchBehavior != mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED &&
+ StringBeginsWith(url, NS_LITERAL_CSTRING("javascript:")) &&
+ !HAS_BEHAVIOR(JAVASCRIPT) &&
+ !StringBeginsWith(searchString, NS_LITERAL_CSTRING("javascript:"))) {
+ NS_ADDREF(*_result = new IntegerVariant(0));
+ return NS_OK;
+ }
+
+ int32_t visitCount = aArguments->AsInt32(kArgIndexVisitCount);
+ bool typed = aArguments->AsInt32(kArgIndexTyped) ? true : false;
+ bool bookmark = aArguments->AsInt32(kArgIndexBookmark) ? true : false;
+ nsDependentCString tags = getSharedString(aArguments, kArgIndexTags);
+ int32_t openPageCount = aArguments->AsInt32(kArgIndexOpenPageCount);
+ bool matches = false;
+ if (HAS_BEHAVIOR(RESTRICT)) {
+ // Make sure we match all the filter requirements. If a given restriction
+ // is active, make sure the corresponding condition is not true.
+ matches = (!HAS_BEHAVIOR(HISTORY) || visitCount > 0) &&
+ (!HAS_BEHAVIOR(TYPED) || typed) &&
+ (!HAS_BEHAVIOR(BOOKMARK) || bookmark) &&
+ (!HAS_BEHAVIOR(TAG) || !tags.IsVoid()) &&
+ (!HAS_BEHAVIOR(OPENPAGE) || openPageCount > 0);
+ } else {
+ // Make sure that we match all the filter requirements and that the
+ // corresponding condition is true if at least a given restriction is active.
+ matches = (HAS_BEHAVIOR(HISTORY) && visitCount > 0) ||
+ (HAS_BEHAVIOR(TYPED) && typed) ||
+ (HAS_BEHAVIOR(BOOKMARK) && bookmark) ||
+ (HAS_BEHAVIOR(TAG) && !tags.IsVoid()) ||
+ (HAS_BEHAVIOR(OPENPAGE) && openPageCount > 0);
+ }
+
+ if (!matches) {
+ NS_ADDREF(*_result = new IntegerVariant(0));
+ return NS_OK;
+ }
+
+ // Obtain our search function.
+ searchFunctionPtr searchFunction = getSearchFunction(matchBehavior);
+
+ // Clean up our URI spec and prepare it for searching.
+ nsCString fixedUrlBuf;
+ nsDependentCSubstring fixedUrl =
+ fixupURISpec(url, matchBehavior, fixedUrlBuf);
+ // Limit the number of chars we search through.
+ const nsDependentCSubstring& trimmedUrl =
+ Substring(fixedUrl, 0, MAX_CHARS_TO_SEARCH_THROUGH);
+
+ nsDependentCString title = getSharedString(aArguments, kArgIndexTitle);
+ // Limit the number of chars we search through.
+ const nsDependentCSubstring& trimmedTitle =
+ Substring(title, 0, MAX_CHARS_TO_SEARCH_THROUGH);
+
+ // Determine if every token matches either the bookmark title, tags, page
+ // title, or page URL.
+ nsCWhitespaceTokenizer tokenizer(searchString);
+ while (matches && tokenizer.hasMoreTokens()) {
+ const nsDependentCSubstring &token = tokenizer.nextToken();
+
+ if (HAS_BEHAVIOR(TITLE) && HAS_BEHAVIOR(URL)) {
+ matches = (searchFunction(token, trimmedTitle) ||
+ searchFunction(token, tags)) &&
+ searchFunction(token, trimmedUrl);
+ }
+ else if (HAS_BEHAVIOR(TITLE)) {
+ matches = searchFunction(token, trimmedTitle) ||
+ searchFunction(token, tags);
+ }
+ else if (HAS_BEHAVIOR(URL)) {
+ matches = searchFunction(token, trimmedUrl);
+ }
+ else {
+ matches = searchFunction(token, trimmedTitle) ||
+ searchFunction(token, tags) ||
+ searchFunction(token, trimmedUrl);
+ }
+ }
+
+ NS_ADDREF(*_result = new IntegerVariant(matches ? 1 : 0));
+ return NS_OK;
+ #undef HAS_BEHAVIOR
+ }
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Frecency Calculation Function
+
+ /* static */
+ nsresult
+ CalculateFrecencyFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<CalculateFrecencyFunction> function =
+ new CalculateFrecencyFunction();
+
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("calculate_frecency"), 1, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ CalculateFrecencyFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ CalculateFrecencyFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ // Fetch arguments. Use default values if they were omitted.
+ uint32_t numEntries;
+ nsresult rv = aArguments->GetNumEntries(&numEntries);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(numEntries == 1, "unexpected number of arguments");
+
+ int64_t pageId = aArguments->AsInt64(0);
+ MOZ_ASSERT(pageId > 0, "Should always pass a valid page id");
+ if (pageId <= 0) {
+ NS_ADDREF(*_result = new IntegerVariant(0));
+ return NS_OK;
+ }
+
+ int32_t typed = 0;
+ int32_t visitCount = 0;
+ bool hasBookmark = false;
+ int32_t isQuery = 0;
+ float pointsForSampledVisits = 0.0;
+ int32_t numSampledVisits = 0;
+ int32_t bonus = 0;
+
+ // This is a const version of the history object for thread-safety.
+ const nsNavHistory* history = nsNavHistory::GetConstHistoryService();
+ NS_ENSURE_STATE(history);
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+
+
+ // Fetch the page stats from the database.
+ {
+ RefPtr<mozIStorageStatement> getPageInfo = DB->GetStatement(
+ "SELECT typed, visit_count, foreign_count, "
+ "(substr(url, 0, 7) = 'place:') "
+ "FROM moz_places "
+ "WHERE id = :page_id "
+ );
+ NS_ENSURE_STATE(getPageInfo);
+ mozStorageStatementScoper infoScoper(getPageInfo);
+
+ rv = getPageInfo->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult = false;
+ rv = getPageInfo->ExecuteStep(&hasResult);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_UNEXPECTED);
+
+ rv = getPageInfo->GetInt32(0, &typed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = getPageInfo->GetInt32(1, &visitCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t foreignCount = 0;
+ rv = getPageInfo->GetInt32(2, &foreignCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ hasBookmark = foreignCount > 0;
+ rv = getPageInfo->GetInt32(3, &isQuery);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (visitCount > 0) {
+ // Get a sample of the last visits to the page, to calculate its weight.
+ // In case of a temporary or permanent redirect, calculate the frecency
+ // as if the original page was visited.
+ nsCOMPtr<mozIStorageStatement> getVisits = DB->GetStatement(
+ NS_LITERAL_CSTRING(
+ "/* do not warn (bug 659740 - SQLite may ignore index if few visits exist) */"
+ "SELECT "
+ "ROUND((strftime('%s','now','localtime','utc') - v.visit_date/1000000)/86400), "
+ "IFNULL(r.visit_type, v.visit_type), "
+ "v.visit_date "
+ "FROM moz_historyvisits v "
+ "LEFT JOIN moz_historyvisits r ON r.id = v.from_visit AND v.visit_type BETWEEN "
+ ) + nsPrintfCString("%d AND %d ", nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
+ nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY) +
+ NS_LITERAL_CSTRING(
+ "WHERE v.place_id = :page_id "
+ "ORDER BY v.visit_date DESC "
+ )
+ );
+ NS_ENSURE_STATE(getVisits);
+ mozStorageStatementScoper visitsScoper(getVisits);
+ rv = getVisits->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), pageId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Fetch only a limited number of recent visits.
+ bool hasResult = false;
+ for (int32_t maxVisits = history->GetNumVisitsForFrecency();
+ numSampledVisits < maxVisits &&
+ NS_SUCCEEDED(getVisits->ExecuteStep(&hasResult)) && hasResult;
+ numSampledVisits++) {
+ int32_t visitType;
+ rv = getVisits->GetInt32(1, &visitType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bonus = history->GetFrecencyTransitionBonus(visitType, true);
+
+ // Add the bookmark visit bonus.
+ if (hasBookmark) {
+ bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, true);
+ }
+
+ // If bonus was zero, we can skip the work to determine the weight.
+ if (bonus) {
+ int32_t ageInDays = getVisits->AsInt32(0);
+ int32_t weight = history->GetFrecencyAgedWeight(ageInDays);
+ pointsForSampledVisits += (float)(weight * (bonus / 100.0));
+ }
+ }
+ }
+
+ // If we sampled some visits for this page, use the calculated weight.
+ if (numSampledVisits) {
+ // We were unable to calculate points, maybe cause all the visits in the
+ // sample had a zero bonus. Though, we know the page has some past valid
+ // visit, or visit_count would be zero. Thus we set the frecency to
+ // -1, so they are still shown in autocomplete.
+ if (!pointsForSampledVisits) {
+ NS_ADDREF(*_result = new IntegerVariant(-1));
+ }
+ else {
+ // Estimate frecency using the sampled visits.
+ // Use ceilf() so that we don't round down to 0, which
+ // would cause us to completely ignore the place during autocomplete.
+ NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits) / numSampledVisits)));
+ }
+ return NS_OK;
+ }
+
+ // Otherwise this page has no visits, it may be bookmarked.
+ if (!hasBookmark || isQuery) {
+ NS_ADDREF(*_result = new IntegerVariant(0));
+ return NS_OK;
+ }
+
+ // For unvisited bookmarks, produce a non-zero frecency, so that they show
+ // up in URL bar autocomplete.
+ visitCount = 1;
+
+ // Make it so something bookmarked and typed will have a higher frecency
+ // than something just typed or just bookmarked.
+ bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_BOOKMARK, false);
+ if (typed) {
+ bonus += history->GetFrecencyTransitionBonus(nsINavHistoryService::TRANSITION_TYPED, false);
+ }
+
+ // Assume "now" as our ageInDays, so use the first bucket.
+ pointsForSampledVisits = history->GetFrecencyBucketWeight(1) * (bonus / (float)100.0);
+
+ // use ceilf() so that we don't round down to 0, which
+ // would cause us to completely ignore the place during autocomplete
+ NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits))));
+
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// GUID Creation Function
+
+ /* static */
+ nsresult
+ GenerateGUIDFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<GenerateGUIDFunction> function = new GenerateGUIDFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("generate_guid"), 0, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ GenerateGUIDFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ GenerateGUIDFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ nsAutoCString guid;
+ nsresult rv = GenerateGUID(guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*_result = new UTF8TextVariant(guid));
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Get Unreversed Host Function
+
+ /* static */
+ nsresult
+ GetUnreversedHostFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<GetUnreversedHostFunction> function = new GetUnreversedHostFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("get_unreversed_host"), 1, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ GetUnreversedHostFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ GetUnreversedHostFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ // Must have non-null function arguments.
+ MOZ_ASSERT(aArguments);
+
+ nsAutoString src;
+ aArguments->GetString(0, src);
+
+ RefPtr<nsVariant> result = new nsVariant();
+
+ if (src.Length()>1) {
+ src.Truncate(src.Length() - 1);
+ nsAutoString dest;
+ ReverseString(src, dest);
+ result->SetAsAString(dest);
+ }
+ else {
+ result->SetAsAString(EmptyString());
+ }
+ result.forget(_result);
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Fixup URL Function
+
+ /* static */
+ nsresult
+ FixupURLFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<FixupURLFunction> function = new FixupURLFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("fixup_url"), 1, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ FixupURLFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ FixupURLFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ // Must have non-null function arguments.
+ MOZ_ASSERT(aArguments);
+
+ nsAutoString src;
+ aArguments->GetString(0, src);
+
+ RefPtr<nsVariant> result = new nsVariant();
+
+ if (StringBeginsWith(src, NS_LITERAL_STRING("http://")))
+ src.Cut(0, 7);
+ else if (StringBeginsWith(src, NS_LITERAL_STRING("https://")))
+ src.Cut(0, 8);
+ else if (StringBeginsWith(src, NS_LITERAL_STRING("ftp://")))
+ src.Cut(0, 6);
+
+ // Remove common URL hostname prefixes
+ if (StringBeginsWith(src, NS_LITERAL_STRING("www."))) {
+ src.Cut(0, 4);
+ }
+
+ result->SetAsAString(src);
+ result.forget(_result);
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Frecency Changed Notification Function
+
+ /* static */
+ nsresult
+ FrecencyNotificationFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<FrecencyNotificationFunction> function =
+ new FrecencyNotificationFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("notify_frecency"), 5, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ FrecencyNotificationFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ FrecencyNotificationFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
+ nsIVariant **_result)
+ {
+ uint32_t numArgs;
+ nsresult rv = aArgs->GetNumEntries(&numArgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(numArgs == 5);
+
+ int32_t newFrecency = aArgs->AsInt32(0);
+
+ nsAutoCString spec;
+ rv = aArgs->GetUTF8String(1, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString guid;
+ rv = aArgs->GetUTF8String(2, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hidden = static_cast<bool>(aArgs->AsInt32(3));
+ PRTime lastVisitDate = static_cast<PRTime>(aArgs->AsInt64(4));
+
+ const nsNavHistory* navHistory = nsNavHistory::GetConstHistoryService();
+ NS_ENSURE_STATE(navHistory);
+ navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid,
+ hidden, lastVisitDate);
+
+ RefPtr<nsVariant> result = new nsVariant();
+ rv = result->SetAsInt32(newFrecency);
+ NS_ENSURE_SUCCESS(rv, rv);
+ result.forget(_result);
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Store Last Inserted Id Function
+
+ /* static */
+ nsresult
+ StoreLastInsertedIdFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<StoreLastInsertedIdFunction> function =
+ new StoreLastInsertedIdFunction();
+ nsresult rv = aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("store_last_inserted_id"), 2, function
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ NS_IMPL_ISUPPORTS(
+ StoreLastInsertedIdFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ StoreLastInsertedIdFunction::OnFunctionCall(mozIStorageValueArray *aArgs,
+ nsIVariant **_result)
+ {
+ uint32_t numArgs;
+ nsresult rv = aArgs->GetNumEntries(&numArgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(numArgs == 2);
+
+ nsAutoCString table;
+ rv = aArgs->GetUTF8String(0, table);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t lastInsertedId = aArgs->AsInt64(1);
+
+ MOZ_ASSERT(table.EqualsLiteral("moz_places") ||
+ table.EqualsLiteral("moz_historyvisits") ||
+ table.EqualsLiteral("moz_bookmarks"));
+
+ if (table.EqualsLiteral("moz_bookmarks")) {
+ nsNavBookmarks::StoreLastInsertedId(table, lastInsertedId);
+ } else {
+ nsNavHistory::StoreLastInsertedId(table, lastInsertedId);
+ }
+
+ RefPtr<nsVariant> result = new nsVariant();
+ rv = result->SetAsInt64(lastInsertedId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ result.forget(_result);
+ return NS_OK;
+ }
+
+////////////////////////////////////////////////////////////////////////////////
+//// Hash Function
+
+ /* static */
+ nsresult
+ HashFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<HashFunction> function = new HashFunction();
+ return aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("hash"), -1, function
+ );
+ }
+
+ NS_IMPL_ISUPPORTS(
+ HashFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ HashFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ // Must have non-null function arguments.
+ MOZ_ASSERT(aArguments);
+
+ // Fetch arguments. Use default values if they were omitted.
+ uint32_t numEntries;
+ nsresult rv = aArguments->GetNumEntries(&numEntries);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(numEntries >= 1 && numEntries <= 2, NS_ERROR_FAILURE);
+
+ nsString str;
+ aArguments->GetString(0, str);
+ nsAutoCString mode;
+ if (numEntries > 1) {
+ aArguments->GetUTF8String(1, mode);
+ }
+
+ RefPtr<nsVariant> result = new nsVariant();
+ if (mode.IsEmpty()) {
+ // URI-like strings (having a prefix before a colon), are handled specially,
+ // as a 48 bit hash, where first 16 bits are the prefix hash, while the
+ // other 32 are the string hash.
+ // The 16 bits have been decided based on the fact hashing all of the IANA
+ // known schemes, plus "places", does not generate collisions.
+ nsAString::const_iterator start, tip, end;
+ str.BeginReading(tip);
+ start = tip;
+ str.EndReading(end);
+ if (FindInReadable(NS_LITERAL_STRING(":"), tip, end)) {
+ const nsDependentSubstring& prefix = Substring(start, tip);
+ uint64_t prefixHash = static_cast<uint64_t>(HashString(prefix) & 0x0000FFFF);
+ // The second half of the url is more likely to be unique, so we add it.
+ uint32_t srcHash = HashString(str);
+ uint64_t hash = (prefixHash << 32) + srcHash;
+ result->SetAsInt64(hash);
+ } else {
+ uint32_t hash = HashString(str);
+ result->SetAsInt64(hash);
+ }
+ } else if (mode.Equals(NS_LITERAL_CSTRING("prefix_lo"))) {
+ // Keep only 16 bits.
+ uint64_t hash = static_cast<uint64_t>(HashString(str) & 0x0000FFFF) << 32;
+ result->SetAsInt64(hash);
+ } else if (mode.Equals(NS_LITERAL_CSTRING("prefix_hi"))) {
+ // Keep only 16 bits.
+ uint64_t hash = static_cast<uint64_t>(HashString(str) & 0x0000FFFF) << 32;
+ // Make this a prefix upper bound by filling the lowest 32 bits.
+ hash += 0xFFFFFFFF;
+ result->SetAsInt64(hash);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ result.forget(_result);
+ return NS_OK;
+ }
+
+} // namespace places
+} // namespace mozilla
diff --git a/components/places/src/SQLFunctions.h b/components/places/src/SQLFunctions.h
new file mode 100644
index 000000000..bba159345
--- /dev/null
+++ b/components/places/src/SQLFunctions.h
@@ -0,0 +1,394 @@
+/* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_places_SQLFunctions_h_
+#define mozilla_places_SQLFunctions_h_
+
+/**
+ * This file contains functions that Places adds to the database handle that can
+ * be accessed by SQL queries.
+ *
+ * Keep the GUID-related parts of this file in sync with
+ * toolkit/downloads/SQLFunctions.[h|cpp]!
+ */
+
+#include "mozIStorageFunction.h"
+#include "mozilla/Attributes.h"
+
+class mozIStorageConnection;
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// AutoComplete Matching Function
+
+/**
+ * This function is used to determine if a given set of data should match an
+ * AutoComplete query.
+ *
+ * In SQL, you'd use it in the WHERE clause like so:
+ * WHERE AUTOCOMPLETE_MATCH(aSearchString, aURL, aTitle, aTags, aVisitCount,
+ * aTyped, aBookmark, aOpenPageCount, aMatchBehavior,
+ * aSearchBehavior)
+ *
+ * @param aSearchString
+ * The string to compare against.
+ * @param aURL
+ * The URL to test for an AutoComplete match.
+ * @param aTitle
+ * The title to test for an AutoComplete match.
+ * @param aTags
+ * The tags to test for an AutoComplete match.
+ * @param aVisitCount
+ * The number of visits aURL has.
+ * @param aTyped
+ * Indicates if aURL is a typed URL or not. Treated as a boolean.
+ * @param aBookmark
+ * Indicates if aURL is a bookmark or not. Treated as a boolean.
+ * @param aOpenPageCount
+ * The number of times aURL has been registered as being open. (See
+ * mozIPlacesAutoComplete::registerOpenPage.)
+ * @param aMatchBehavior
+ * The match behavior to use for this search.
+ * @param aSearchBehavior
+ * A bitfield dictating the search behavior.
+ */
+class MatchAutoCompleteFunction final : public mozIStorageFunction
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ /**
+ * Registers the function with the specified database connection.
+ *
+ * @param aDBConn
+ * The database connection to register with.
+ */
+ static nsresult create(mozIStorageConnection *aDBConn);
+
+private:
+ ~MatchAutoCompleteFunction() {}
+
+ /**
+ * Argument Indexes
+ */
+ static const uint32_t kArgSearchString = 0;
+ static const uint32_t kArgIndexURL = 1;
+ static const uint32_t kArgIndexTitle = 2;
+ static const uint32_t kArgIndexTags = 3;
+ static const uint32_t kArgIndexVisitCount = 4;
+ static const uint32_t kArgIndexTyped = 5;
+ static const uint32_t kArgIndexBookmark = 6;
+ static const uint32_t kArgIndexOpenPageCount = 7;
+ static const uint32_t kArgIndexMatchBehavior = 8;
+ static const uint32_t kArgIndexSearchBehavior = 9;
+ static const uint32_t kArgIndexLength = 10;
+
+ /**
+ * Typedefs
+ */
+ typedef bool (*searchFunctionPtr)(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString);
+
+ typedef nsACString::const_char_iterator const_char_iterator;
+
+ /**
+ * Obtains the search function to match on.
+ *
+ * @param aBehavior
+ * The matching behavior to use defined by one of the
+ * mozIPlacesAutoComplete::MATCH_* values.
+ * @return a pointer to the function that will perform the proper search.
+ */
+ static searchFunctionPtr getSearchFunction(int32_t aBehavior);
+
+ /**
+ * Tests if aSourceString starts with aToken.
+ *
+ * @param aToken
+ * The string to search for.
+ * @param aSourceString
+ * The string to search.
+ * @return true if found, false otherwise.
+ */
+ static bool findBeginning(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString);
+
+ /**
+ * Tests if aSourceString starts with aToken in a case sensitive way.
+ *
+ * @param aToken
+ * The string to search for.
+ * @param aSourceString
+ * The string to search.
+ * @return true if found, false otherwise.
+ */
+ static bool findBeginningCaseSensitive(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString);
+
+ /**
+ * Searches aSourceString for aToken anywhere in the string in a case-
+ * insensitive way.
+ *
+ * @param aToken
+ * The string to search for.
+ * @param aSourceString
+ * The string to search.
+ * @return true if found, false otherwise.
+ */
+ static bool findAnywhere(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString);
+
+ /**
+ * Tests if aToken is found on a word boundary in aSourceString.
+ *
+ * @param aToken
+ * The string to search for.
+ * @param aSourceString
+ * The string to search.
+ * @return true if found, false otherwise.
+ */
+ static bool findOnBoundary(const nsDependentCSubstring &aToken,
+ const nsACString &aSourceString);
+
+
+ /**
+ * Fixes a URI's spec such that it is ready to be searched. This includes
+ * unescaping escaped characters and removing certain specs that we do not
+ * care to search for.
+ *
+ * @param aURISpec
+ * The spec of the URI to prepare for searching.
+ * @param aMatchBehavior
+ * The matching behavior to use defined by one of the
+ * mozIPlacesAutoComplete::MATCH_* values.
+ * @param aSpecBuf
+ * A string buffer that the returned slice can point into, if needed.
+ * @return the fixed up string.
+ */
+ static nsDependentCSubstring fixupURISpec(const nsACString &aURISpec,
+ int32_t aMatchBehavior,
+ nsACString &aSpecBuf);
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Frecency Calculation Function
+
+/**
+ * This function is used to calculate frecency for a page.
+ *
+ * In SQL, you'd use it in when setting frecency like:
+ * SET frecency = CALCULATE_FRECENCY(place_id).
+ * Optional parameters must be passed in if the page is not yet in the database,
+ * otherwise they will be fetched from it automatically.
+ *
+ * @param pageId
+ * The id of the page. Pass -1 if the page is being added right now.
+ * @param [optional] typed
+ * Whether the page has been typed in. Default is false.
+ * @param [optional] fullVisitCount
+ * Count of all the visits (All types). Default is 0.
+ * @param [optional] isBookmarked
+ * Whether the page is bookmarked. Default is false.
+ */
+class CalculateFrecencyFunction final : public mozIStorageFunction
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ /**
+ * Registers the function with the specified database connection.
+ *
+ * @param aDBConn
+ * The database connection to register with.
+ */
+ static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~CalculateFrecencyFunction() {}
+};
+
+/**
+ * SQL function to generate a GUID for a place or bookmark item. This is just
+ * a wrapper around GenerateGUID in Helpers.h.
+ *
+ * @return a guid for the item.
+ */
+class GenerateGUIDFunction final : public mozIStorageFunction
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ /**
+ * Registers the function with the specified database connection.
+ *
+ * @param aDBConn
+ * The database connection to register with.
+ */
+ static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~GenerateGUIDFunction() {}
+};
+
+/**
+ * SQL function to unreverse the rev_host of a page.
+ *
+ * @param rev_host
+ * The rev_host value of the page.
+ *
+ * @return the unreversed host of the page.
+ */
+class GetUnreversedHostFunction final : public mozIStorageFunction
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ /**
+ * Registers the function with the specified database connection.
+ *
+ * @param aDBConn
+ * The database connection to register with.
+ */
+ static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~GetUnreversedHostFunction() {}
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Fixup URL Function
+
+/**
+ * Make a given URL more suitable for searches, by removing common prefixes
+ * such as "www."
+ *
+ * @param url
+ * A URL.
+ * @return
+ * The same URL, with redundant parts removed.
+ */
+class FixupURLFunction final : public mozIStorageFunction
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ /**
+ * Registers the function with the specified database connection.
+ *
+ * @param aDBConn
+ * The database connection to register with.
+ */
+ static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~FixupURLFunction() {}
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Frecency Changed Notification Function
+
+/**
+ * For a given place, posts a runnable to the main thread that calls
+ * onFrecencyChanged on nsNavHistory's nsINavHistoryObservers. The passed-in
+ * newFrecency value is returned unchanged.
+ *
+ * @param newFrecency
+ * The place's new frecency.
+ * @param url
+ * The place's URL.
+ * @param guid
+ * The place's GUID.
+ * @param hidden
+ * The place's hidden boolean.
+ * @param lastVisitDate
+ * The place's last visit date.
+ * @return newFrecency
+ */
+class FrecencyNotificationFunction final : public mozIStorageFunction
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ /**
+ * Registers the function with the specified database connection.
+ *
+ * @param aDBConn
+ * The database connection to register with.
+ */
+ static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~FrecencyNotificationFunction() {}
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Store Last Inserted Id Function
+
+/**
+ * Store the last inserted id for reference purpose.
+ *
+ * @param tableName
+ * The table name.
+ * @param id
+ * The last inserted id.
+ * @return null
+ */
+class StoreLastInsertedIdFunction final : public mozIStorageFunction
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ /**
+ * Registers the function with the specified database connection.
+ *
+ * @param aDBConn
+ * The database connection to register with.
+ */
+ static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~StoreLastInsertedIdFunction() {}
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Hash Function
+
+/**
+ * Calculates hash for a given string using the mfbt AddToHash function.
+ *
+ * @param string
+ * A string.
+ * @return
+ * The hash for the string.
+ */
+class HashFunction final : public mozIStorageFunction
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ /**
+ * Registers the function with the specified database connection.
+ *
+ * @param aDBConn
+ * The database connection to register with.
+ */
+ static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~HashFunction() {}
+};
+
+} // namespace places
+} // namespace mozilla
+
+#endif // mozilla_places_SQLFunctions_h_
diff --git a/components/places/src/Shutdown.cpp b/components/places/src/Shutdown.cpp
new file mode 100644
index 000000000..43586542b
--- /dev/null
+++ b/components/places/src/Shutdown.cpp
@@ -0,0 +1,233 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "Shutdown.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace places {
+
+uint16_t PlacesShutdownBlocker::sCounter = 0;
+Atomic<bool> PlacesShutdownBlocker::sIsStarted(false);
+
+PlacesShutdownBlocker::PlacesShutdownBlocker(const nsString& aName)
+ : mName(aName)
+ , mState(NOT_STARTED)
+ , mCounter(sCounter++)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // During tests, we can end up with the Database singleton being resurrected.
+ // Make sure that each instance of DatabaseShutdown has a unique name.
+ if (mCounter > 1) {
+ mName.AppendInt(mCounter);
+ }
+}
+
+// nsIAsyncShutdownBlocker
+NS_IMETHODIMP
+PlacesShutdownBlocker::GetName(nsAString& aName)
+{
+ aName = mName;
+ return NS_OK;
+}
+
+// nsIAsyncShutdownBlocker
+NS_IMETHODIMP
+PlacesShutdownBlocker::GetState(nsIPropertyBag** _state)
+{
+ NS_ENSURE_ARG_POINTER(_state);
+
+ nsCOMPtr<nsIWritablePropertyBag2> bag =
+ do_CreateInstance("@mozilla.org/hash-property-bag;1");
+ NS_ENSURE_TRUE(bag, NS_ERROR_OUT_OF_MEMORY);
+ bag.forget(_state);
+
+ // Put `mState` in field `progress`
+ RefPtr<nsVariant> progress = new nsVariant();
+ nsresult rv = progress->SetAsUint8(mState);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ rv = static_cast<nsIWritablePropertyBag2*>(*_state)->SetPropertyAsInterface(
+ NS_LITERAL_STRING("progress"), progress);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+ // Put `mBarrier`'s state in field `barrier`, if possible
+ if (!mBarrier) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIPropertyBag> barrierState;
+ rv = mBarrier->GetState(getter_AddRefs(barrierState));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ RefPtr<nsVariant> barrier = new nsVariant();
+ rv = barrier->SetAsInterface(NS_GET_IID(nsIPropertyBag), barrierState);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ rv = static_cast<nsIWritablePropertyBag2*>(*_state)->SetPropertyAsInterface(
+ NS_LITERAL_STRING("Barrier"), barrier);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+
+ return NS_OK;
+}
+
+// nsIAsyncShutdownBlocker
+NS_IMETHODIMP
+PlacesShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aParentClient)
+{
+ MOZ_ASSERT(false, "should always be overridden");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMPL_ISUPPORTS(
+ PlacesShutdownBlocker,
+ nsIAsyncShutdownBlocker
+)
+
+////////////////////////////////////////////////////////////////////////////////
+
+ClientsShutdownBlocker::ClientsShutdownBlocker()
+ : PlacesShutdownBlocker(NS_LITERAL_STRING("Places Clients shutdown"))
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // Create a barrier that will be exposed to clients through GetClient(), so
+ // they can block Places shutdown.
+ nsCOMPtr<nsIAsyncShutdownService> asyncShutdown = services::GetAsyncShutdown();
+ MOZ_ASSERT(asyncShutdown);
+ if (asyncShutdown) {
+ nsCOMPtr<nsIAsyncShutdownBarrier> barrier;
+ MOZ_ALWAYS_SUCCEEDS(asyncShutdown->MakeBarrier(mName, getter_AddRefs(barrier)));
+ mBarrier = new nsMainThreadPtrHolder<nsIAsyncShutdownBarrier>(barrier);
+ }
+}
+
+already_AddRefed<nsIAsyncShutdownClient>
+ClientsShutdownBlocker::GetClient()
+{
+ nsCOMPtr<nsIAsyncShutdownClient> client;
+ if (mBarrier) {
+ MOZ_ALWAYS_SUCCEEDS(mBarrier->GetClient(getter_AddRefs(client)));
+ }
+ return client.forget();
+}
+
+// nsIAsyncShutdownBlocker
+NS_IMETHODIMP
+ClientsShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aParentClient)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mParentClient = new nsMainThreadPtrHolder<nsIAsyncShutdownClient>(aParentClient);
+ mState = RECEIVED_BLOCK_SHUTDOWN;
+
+ if (NS_WARN_IF(!mBarrier)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Wait until all the clients have removed their blockers.
+ MOZ_ALWAYS_SUCCEEDS(mBarrier->Wait(this));
+
+ mState = CALLED_WAIT_CLIENTS;
+ return NS_OK;
+}
+
+// nsIAsyncShutdownCompletionCallback
+NS_IMETHODIMP
+ClientsShutdownBlocker::Done()
+{
+ // At this point all the clients are done, we can stop blocking the shutdown
+ // phase.
+ mState = RECEIVED_DONE;
+
+ // mParentClient is nullptr in tests.
+ if (mParentClient) {
+ nsresult rv = mParentClient->RemoveBlocker(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ mParentClient = nullptr;
+ }
+ mBarrier = nullptr;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(
+ ClientsShutdownBlocker,
+ PlacesShutdownBlocker,
+ nsIAsyncShutdownCompletionCallback
+)
+
+////////////////////////////////////////////////////////////////////////////////
+
+ConnectionShutdownBlocker::ConnectionShutdownBlocker(Database* aDatabase)
+ : PlacesShutdownBlocker(NS_LITERAL_STRING("Places Connection shutdown"))
+ , mDatabase(aDatabase)
+{
+ // Do nothing.
+}
+
+// nsIAsyncShutdownBlocker
+NS_IMETHODIMP
+ConnectionShutdownBlocker::BlockShutdown(nsIAsyncShutdownClient* aParentClient)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mParentClient = new nsMainThreadPtrHolder<nsIAsyncShutdownClient>(aParentClient);
+ mState = RECEIVED_BLOCK_SHUTDOWN;
+ // Annotate that Database shutdown started.
+ sIsStarted = true;
+
+ // Fire internal database closing notification.
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ MOZ_ASSERT(os);
+ if (os) {
+ Unused << os->NotifyObservers(nullptr, TOPIC_PLACES_WILL_CLOSE_CONNECTION, nullptr);
+ }
+ mState = NOTIFIED_OBSERVERS_PLACES_WILL_CLOSE_CONNECTION;
+
+ // At this stage, any use of this database is forbidden. Get rid of
+ // `gDatabase`. Note, however, that the database could be
+ // resurrected. This can happen in particular during tests.
+ MOZ_ASSERT(Database::gDatabase == nullptr || Database::gDatabase == mDatabase);
+ Database::gDatabase = nullptr;
+
+ // Database::Shutdown will invoke Complete once the connection is closed.
+ mDatabase->Shutdown();
+ mState = CALLED_STORAGESHUTDOWN;
+ return NS_OK;
+}
+
+// mozIStorageCompletionCallback
+NS_IMETHODIMP
+ConnectionShutdownBlocker::Complete(nsresult, nsISupports*)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mState = RECEIVED_STORAGESHUTDOWN_COMPLETE;
+
+ // The connection is closed, the Database has no more use, so we can break
+ // possible cycles.
+ mDatabase = nullptr;
+
+ // Notify the connection has gone.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ MOZ_ASSERT(os);
+ if (os) {
+ MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr,
+ TOPIC_PLACES_CONNECTION_CLOSED,
+ nullptr));
+ }
+ mState = NOTIFIED_OBSERVERS_PLACES_CONNECTION_CLOSED;
+
+ // mParentClient is nullptr in tests
+ if (mParentClient) {
+ nsresult rv = mParentClient->RemoveBlocker(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) return rv;
+ mParentClient = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(
+ ConnectionShutdownBlocker,
+ PlacesShutdownBlocker,
+ mozIStorageCompletionCallback
+)
+
+} // namespace places
+} // namespace mozilla
diff --git a/components/places/src/Shutdown.h b/components/places/src/Shutdown.h
new file mode 100644
index 000000000..69023c608
--- /dev/null
+++ b/components/places/src/Shutdown.h
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 mozilla_places_Shutdown_h_
+#define mozilla_places_Shutdown_h_
+
+#include "nsIAsyncShutdown.h"
+#include "Database.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace places {
+
+class Database;
+
+/**
+ * This is most of the code responsible for Places shutdown.
+ *
+ * PHASE 1 (Legacy clients shutdown)
+ * The shutdown procedure begins when the Database singleton receives
+ * profile-change-teardown (note that tests will instead notify nsNavHistory,
+ * that forwards the notification to the Database instance).
+ * Database::Observe first of all checks if initialization was completed
+ * properly, to avoid race conditions, then it notifies "places-shutdown" to
+ * legacy clients. Legacy clients are supposed to start and complete any
+ * shutdown critical work in the same tick, since we won't wait for them.
+
+ * PHASE 2 (Modern clients shutdown)
+ * Modern clients should instead register as a blocker by passing a promise to
+ * nsPIPlacesDatabase::shutdownClient (for example see sanitize.js), so they
+ * block Places shutdown until the promise is resolved.
+ * When profile-change-teardown is observed by async shutdown, it calls
+ * ClientsShutdownBlocker::BlockShutdown. This class is registered as a teardown
+ * phase blocker in Database::Init (see Database::mClientsShutdown).
+ * ClientsShutdownBlocker::BlockShudown waits for all the clients registered
+ * through nsPIPlacesDatabase::shutdownClient. When all the clients are done,
+ * its `Done` method is invoked, and it stops blocking the shutdown phase, so
+ * that it can continue.
+ *
+ * PHASE 3 (Connection shutdown)
+ * ConnectionBlocker is registered as a profile-before-change blocker in
+ * Database::Init (see Database::mConnectionShutdown).
+ * When profile-before-change is observer by async shutdown, it calls
+ * ConnectionShutdownBlocker::BlockShutdown.
+ * This is the last chance for any Places internal work, like privacy cleanups,
+ * before the connection is closed. This a places-will-close-connection
+ * notification is sent to legacy clients that must complete any operation in
+ * the same tick, since we won't wait for them.
+ * Then the control is passed to Database::Shutdown, that executes some sanity
+ * checks, clears cached statements and proceeds with asyncClose.
+ * Once the connection is definitely closed, Database will call back
+ * ConnectionBlocker::Complete. At this point a final
+ * places-connection-closed notification is sent, for testing purposes.
+ */
+
+/**
+ * A base AsyncShutdown blocker in charge of shutting down Places.
+ */
+class PlacesShutdownBlocker : public nsIAsyncShutdownBlocker
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIASYNCSHUTDOWNBLOCKER
+
+ explicit PlacesShutdownBlocker(const nsString& aName);
+
+ /**
+ * `true` if we have not started shutdown, i.e. if
+ * `BlockShutdown()` hasn't been called yet, false otherwise.
+ */
+ static bool IsStarted() {
+ return sIsStarted;
+ }
+
+ // The current state, used internally and for forensics/debugging purposes.
+ // Not all the states make sense for all the derived classes.
+ enum States {
+ NOT_STARTED,
+ // Execution of `BlockShutdown` in progress.
+ RECEIVED_BLOCK_SHUTDOWN,
+
+ // Values specific to ClientsShutdownBlocker
+ // a. Set while we are waiting for clients to do their job and unblock us.
+ CALLED_WAIT_CLIENTS,
+ // b. Set when all the clients are done.
+ RECEIVED_DONE,
+
+ // Values specific to ConnectionShutdownBlocker
+ // a. Set after we notified observers that Places is closing the connection.
+ NOTIFIED_OBSERVERS_PLACES_WILL_CLOSE_CONNECTION,
+ // b. Set after we pass control to Database::Shutdown, and wait for it to
+ // close the connection and call our `Complete` method when done.
+ CALLED_STORAGESHUTDOWN,
+ // c. Set when Database has closed the connection and passed control to
+ // us through `Complete`.
+ RECEIVED_STORAGESHUTDOWN_COMPLETE,
+ // d. We have notified observers that Places has closed the connection.
+ NOTIFIED_OBSERVERS_PLACES_CONNECTION_CLOSED,
+ };
+ States State() {
+ return mState;
+ }
+
+protected:
+ // The blocker name, also used as barrier name.
+ nsString mName;
+ // The current state, see States.
+ States mState;
+ // The barrier optionally used to wait for clients.
+ nsMainThreadPtrHandle<nsIAsyncShutdownBarrier> mBarrier;
+ // The parent object who registered this as a blocker.
+ nsMainThreadPtrHandle<nsIAsyncShutdownClient> mParentClient;
+
+ // As tests may resurrect a dead `Database`, we use a counter to
+ // give the instances of `PlacesShutdownBlocker` unique names.
+ uint16_t mCounter;
+ static uint16_t sCounter;
+
+ static Atomic<bool> sIsStarted;
+
+ virtual ~PlacesShutdownBlocker() {}
+};
+
+/**
+ * Blocker also used to wait for clients, through an owned barrier.
+ */
+class ClientsShutdownBlocker final : public PlacesShutdownBlocker
+ , public nsIAsyncShutdownCompletionCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIASYNCSHUTDOWNCOMPLETIONCALLBACK
+
+ explicit ClientsShutdownBlocker();
+
+ NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aParentClient) override;
+
+ already_AddRefed<nsIAsyncShutdownClient> GetClient();
+
+private:
+ ~ClientsShutdownBlocker() {}
+};
+
+/**
+ * Blocker used to wait when closing the database connection.
+ */
+class ConnectionShutdownBlocker final : public PlacesShutdownBlocker
+ , public mozIStorageCompletionCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
+
+ NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aParentClient) override;
+
+ explicit ConnectionShutdownBlocker(mozilla::places::Database* aDatabase);
+
+private:
+ ~ConnectionShutdownBlocker() {}
+
+ // The owning database.
+ // The cycle is broken in method Complete(), once the connection
+ // has been closed by mozStorage.
+ RefPtr<mozilla::places::Database> mDatabase;
+};
+
+} // namespace places
+} // namespace mozilla
+
+#endif // mozilla_places_Shutdown_h_
diff --git a/components/places/src/UnifiedComplete.js b/components/places/src/UnifiedComplete.js
new file mode 100644
index 000000000..3cce88b38
--- /dev/null
+++ b/components/places/src/UnifiedComplete.js
@@ -0,0 +1,2098 @@
+/* -*- 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/. */
+
+"use strict";
+
+// Constants
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+// Match type constants.
+// These indicate what type of search function we should be using.
+const MATCH_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE;
+const MATCH_BOUNDARY_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE;
+const MATCH_BOUNDARY = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY;
+const MATCH_BEGINNING = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING;
+const MATCH_BEGINNING_CASE_SENSITIVE = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING_CASE_SENSITIVE;
+
+const PREF_BRANCH = "browser.urlbar.";
+
+// Prefs are defined as [pref name, default value].
+const PREF_ENABLED = [ "autocomplete.enabled", true ];
+const PREF_AUTOFILL = [ "autoFill", true ];
+const PREF_AUTOFILL_TYPED = [ "autoFill.typed", true ];
+const PREF_AUTOFILL_SEARCHENGINES = [ "autoFill.searchEngines", false ];
+const PREF_RESTYLESEARCHES = [ "restyleSearches", false ];
+const PREF_DELAY = [ "delay", 50 ];
+const PREF_BEHAVIOR = [ "matchBehavior", MATCH_BOUNDARY_ANYWHERE ];
+const PREF_FILTER_JS = [ "filter.javascript", true ];
+const PREF_MAXRESULTS = [ "maxRichResults", 25 ];
+const PREF_RESTRICT_HISTORY = [ "restrict.history", "^" ];
+const PREF_RESTRICT_BOOKMARKS = [ "restrict.bookmark", "*" ];
+const PREF_RESTRICT_TYPED = [ "restrict.typed", "~" ];
+const PREF_RESTRICT_TAG = [ "restrict.tag", "+" ];
+const PREF_RESTRICT_SWITCHTAB = [ "restrict.openpage", "%" ];
+const PREF_RESTRICT_SEARCHES = [ "restrict.searces", "$" ];
+const PREF_MATCH_TITLE = [ "match.title", "#" ];
+const PREF_MATCH_URL = [ "match.url", "@" ];
+
+const PREF_SUGGEST_HISTORY = [ "suggest.history", true ];
+const PREF_SUGGEST_BOOKMARK = [ "suggest.bookmark", true ];
+const PREF_SUGGEST_OPENPAGE = [ "suggest.openpage", true ];
+const PREF_SUGGEST_HISTORY_ONLYTYPED = [ "suggest.history.onlyTyped", false ];
+const PREF_SUGGEST_SEARCHES = [ "suggest.searches", false ];
+
+const PREF_MAX_CHARS_FOR_SUGGEST = [ "maxCharsForSearchSuggestions", 20];
+
+// AutoComplete query type constants.
+// Describes the various types of queries that we can process rows for.
+const QUERYTYPE_FILTERED = 0;
+const QUERYTYPE_AUTOFILL_HOST = 1;
+const QUERYTYPE_AUTOFILL_URL = 2;
+
+// This separator is used as an RTL-friendly way to split the title and tags.
+// It can also be used by an nsIAutoCompleteResult consumer to re-split the
+// "comment" back into the title and the tag.
+const TITLE_TAGS_SEPARATOR = " \u2013 ";
+
+// The default frecency value used when inserting matches with unknown frecency.
+const FRECENCY_DEFAULT = 1000;
+
+// Remote matches are appended when local matches are below a given frecency
+// threshold (FRECENCY_DEFAULT) as soon as they arrive. However we'll
+// always try to have at least MINIMUM_LOCAL_MATCHES local matches.
+const MINIMUM_LOCAL_MATCHES = 6;
+
+// Extensions are allowed to add suggestions if they have registered a keyword
+// with the omnibox API. This is the maximum number of suggestions an extension
+// is allowed to add for a given search string.
+const MAXIMUM_ALLOWED_EXTENSION_MATCHES = 6;
+
+// A regex that matches "single word" hostnames for whitelisting purposes.
+// The hostname will already have been checked for general validity, so we
+// don't need to be exhaustive here, so allow dashes anywhere.
+const REGEXP_SINGLEWORD_HOST = new RegExp("^[a-z0-9-]+$", "i");
+
+// Regex used to match userContextId.
+const REGEXP_USER_CONTEXT_ID = /(?:^| )user-context-id:(\d+)/;
+
+// Regex used to match one or more whitespace.
+const REGEXP_SPACES = /\s+/;
+
+// Sqlite result row index constants.
+const QUERYINDEX_QUERYTYPE = 0;
+const QUERYINDEX_URL = 1;
+const QUERYINDEX_TITLE = 2;
+const QUERYINDEX_ICONURL = 3;
+const QUERYINDEX_BOOKMARKED = 4;
+const QUERYINDEX_BOOKMARKTITLE = 5;
+const QUERYINDEX_TAGS = 6;
+const QUERYINDEX_VISITCOUNT = 7;
+const QUERYINDEX_TYPED = 8;
+const QUERYINDEX_PLACEID = 9;
+const QUERYINDEX_SWITCHTAB = 10;
+const QUERYINDEX_FRECENCY = 11;
+
+// This SQL query fragment provides the following:
+// - whether the entry is bookmarked (QUERYINDEX_BOOKMARKED)
+// - the bookmark title, if it is a bookmark (QUERYINDEX_BOOKMARKTITLE)
+// - the tags associated with a bookmarked entry (QUERYINDEX_TAGS)
+const SQL_BOOKMARK_TAGS_FRAGMENT =
+ `EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked,
+ ( SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL
+ ORDER BY lastModified DESC LIMIT 1
+ ) AS btitle,
+ ( SELECT GROUP_CONCAT(t.title, ', ')
+ FROM moz_bookmarks b
+ JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent
+ WHERE b.fk = h.id
+ ) AS tags`;
+
+// TODO bug 412736: in case of a frecency tie, we might break it with h.typed
+// and h.visit_count. That is slower though, so not doing it yet...
+// NB: as a slight performance optimization, we only evaluate the "btitle"
+// and "tags" queries for bookmarked entries.
+function defaultQuery(conditions = "") {
+ let query =
+ `SELECT :query_type, h.url, h.title, f.url, ${SQL_BOOKMARK_TAGS_FRAGMENT},
+ h.visit_count, h.typed, h.id, t.open_count, h.frecency
+ FROM moz_places h
+ LEFT JOIN moz_favicons f ON f.id = h.favicon_id
+ LEFT JOIN moz_openpages_temp t ON t.url = h.url
+ WHERE h.frecency <> 0
+ AND AUTOCOMPLETE_MATCH(:searchString, h.url,
+ CASE WHEN bookmarked THEN
+ IFNULL(btitle, h.title)
+ ELSE h.title END,
+ CASE WHEN bookmarked THEN
+ tags
+ ELSE '' END,
+ h.visit_count, h.typed,
+ bookmarked, t.open_count,
+ :matchBehavior, :searchBehavior)
+ ${conditions}
+ ORDER BY h.frecency DESC, h.id DESC
+ LIMIT :maxResults`;
+ return query;
+}
+
+const SQL_SWITCHTAB_QUERY =
+ `SELECT :query_type, t.url, t.url, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ t.open_count, NULL
+ FROM moz_openpages_temp t
+ LEFT JOIN moz_places h ON h.url_hash = hash(t.url) AND h.url = t.url
+ WHERE h.id IS NULL
+ AND AUTOCOMPLETE_MATCH(:searchString, t.url, t.url, NULL,
+ NULL, NULL, NULL, t.open_count,
+ :matchBehavior, :searchBehavior)
+ ORDER BY t.ROWID DESC
+ LIMIT :maxResults`;
+
+const SQL_ADAPTIVE_QUERY =
+ `/* do not warn (bug 487789) */
+ SELECT :query_type, h.url, h.title, f.url, ${SQL_BOOKMARK_TAGS_FRAGMENT},
+ h.visit_count, h.typed, h.id, t.open_count, h.frecency
+ FROM (
+ SELECT ROUND(MAX(use_count) * (1 + (input = :search_string)), 1) AS rank,
+ place_id
+ FROM moz_inputhistory
+ WHERE input BETWEEN :search_string AND :search_string || X'FFFF'
+ GROUP BY place_id
+ ) AS i
+ JOIN moz_places h ON h.id = i.place_id
+ LEFT JOIN moz_favicons f ON f.id = h.favicon_id
+ LEFT JOIN moz_openpages_temp t ON t.url = h.url
+ WHERE AUTOCOMPLETE_MATCH(NULL, h.url,
+ IFNULL(btitle, h.title), tags,
+ h.visit_count, h.typed, bookmarked,
+ t.open_count,
+ :matchBehavior, :searchBehavior)
+ ORDER BY rank DESC, h.frecency DESC`;
+
+
+function hostQuery(conditions = "") {
+ let query =
+ `/* do not warn (bug NA): not worth to index on (typed, frecency) */
+ SELECT :query_type, host || '/', IFNULL(prefix, '') || host || '/',
+ ( SELECT f.url FROM moz_favicons f
+ JOIN moz_places h ON h.favicon_id = f.id
+ WHERE rev_host = get_unreversed_host(host || '.') || '.'
+ OR rev_host = get_unreversed_host(host || '.') || '.www.'
+ ) AS favicon_url,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, frecency
+ FROM moz_hosts
+ WHERE host BETWEEN :searchString AND :searchString || X'FFFF'
+ AND frecency <> 0
+ ${conditions}
+ ORDER BY frecency DESC
+ LIMIT 1`;
+ return query;
+}
+
+const SQL_HOST_QUERY = hostQuery();
+
+const SQL_TYPED_HOST_QUERY = hostQuery("AND typed = 1");
+
+function bookmarkedHostQuery(conditions = "") {
+ let query =
+ `/* do not warn (bug NA): not worth to index on (typed, frecency) */
+ SELECT :query_type, host || '/', IFNULL(prefix, '') || host || '/',
+ ( SELECT f.url FROM moz_favicons f
+ JOIN moz_places h ON h.favicon_id = f.id
+ WHERE rev_host = get_unreversed_host(host || '.') || '.'
+ OR rev_host = get_unreversed_host(host || '.') || '.www.'
+ ) AS favicon_url,
+ ( SELECT foreign_count > 0 FROM moz_places
+ WHERE rev_host = get_unreversed_host(host || '.') || '.'
+ OR rev_host = get_unreversed_host(host || '.') || '.www.'
+ ) AS bookmarked, NULL, NULL, NULL, NULL, NULL, NULL, frecency
+ FROM moz_hosts
+ WHERE host BETWEEN :searchString AND :searchString || X'FFFF'
+ AND bookmarked
+ AND frecency <> 0
+ ${conditions}
+ ORDER BY frecency DESC
+ LIMIT 1`;
+ return query;
+}
+
+const SQL_BOOKMARKED_HOST_QUERY = bookmarkedHostQuery();
+
+const SQL_BOOKMARKED_TYPED_HOST_QUERY = bookmarkedHostQuery("AND typed = 1");
+
+function urlQuery(conditions = "") {
+ return `/* do not warn (bug no): cannot use an index to sort */
+ SELECT :query_type, h.url, NULL, f.url AS favicon_url,
+ foreign_count > 0 AS bookmarked,
+ NULL, NULL, NULL, NULL, NULL, NULL, h.frecency
+ FROM moz_places h
+ LEFT JOIN moz_favicons f ON h.favicon_id = f.id
+ WHERE (rev_host = :revHost OR rev_host = :revHost || "www.")
+ AND h.frecency <> 0
+ AND fixup_url(h.url) BETWEEN :searchString AND :searchString || X'FFFF'
+ ${conditions}
+ ORDER BY h.frecency DESC, h.id DESC
+ LIMIT 1`;
+}
+
+const SQL_URL_QUERY = urlQuery();
+
+const SQL_TYPED_URL_QUERY = urlQuery("AND h.typed = 1");
+
+// TODO (bug 1045924): use foreign_count once available.
+const SQL_BOOKMARKED_URL_QUERY = urlQuery("AND bookmarked");
+
+const SQL_BOOKMARKED_TYPED_URL_QUERY = urlQuery("AND bookmarked AND h.typed = 1");
+
+// Getters
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+ "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
+ "resource://gre/modules/Sqlite.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
+ "resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionSearchHandler",
+ "resource://gre/modules/ExtensionSearchHandler.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesSearchAutocompleteProvider",
+ "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesRemoteTabsAutocompleteProvider",
+ "resource://gre/modules/PlacesRemoteTabsAutocompleteProvider.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "textURIService",
+ "@mozilla.org/intl/texttosuburi;1",
+ "nsITextToSubURI");
+
+/**
+ * Storage object for switch-to-tab entries.
+ * This takes care of caching and registering open pages, that will be reused
+ * by switch-to-tab queries. It has an internal cache, so that the Sqlite
+ * store is lazy initialized only on first use.
+ * It has a simple API:
+ * initDatabase(conn): initializes the temporary Sqlite entities to store data
+ * add(uri): adds a given nsIURI to the store
+ * delete(uri): removes a given nsIURI from the store
+ * shutdown(): stops storing data to Sqlite
+ */
+XPCOMUtils.defineLazyGetter(this, "SwitchToTabStorage", () => Object.seal({
+ _conn: null,
+ // Temporary queue used while the database connection is not available.
+ _queue: new Set(),
+ initDatabase: Task.async(function* (conn) {
+ // To reduce IO use an in-memory table for switch-to-tab tracking.
+ // Note: this should be kept up-to-date with the definition in
+ // nsPlacesTables.h.
+ yield conn.execute(
+ `CREATE TEMP TABLE moz_openpages_temp (
+ url TEXT PRIMARY KEY,
+ open_count INTEGER
+ )`);
+
+ // Note: this should be kept up-to-date with the definition in
+ // nsPlacesTriggers.h.
+ yield conn.execute(
+ `CREATE TEMPORARY TRIGGER moz_openpages_temp_afterupdate_trigger
+ AFTER UPDATE OF open_count ON moz_openpages_temp FOR EACH ROW
+ WHEN NEW.open_count = 0
+ BEGIN
+ DELETE FROM moz_openpages_temp
+ WHERE url = NEW.url;
+ END`);
+
+ this._conn = conn;
+
+ // Populate the table with the current cache contents...
+ this._queue.forEach(this.add, this);
+
+ // ...then clear it to avoid double additions.
+ this._queue.clear();
+ }),
+
+ add: function (uri) {
+ if (!this._conn) {
+ this._queue.add(uri);
+ return;
+ }
+ this._conn.executeCached(
+ `INSERT OR REPLACE INTO moz_openpages_temp (url, open_count)
+ VALUES ( :url, IFNULL( (SELECT open_count + 1
+ FROM moz_openpages_temp
+ WHERE url = :url),
+ 1
+ )
+ )`
+ , { url: uri.spec });
+ },
+
+ delete: function (uri) {
+ if (!this._conn) {
+ this._queue.delete(uri);
+ return;
+ }
+ this._conn.executeCached(
+ `UPDATE moz_openpages_temp
+ SET open_count = open_count - 1
+ WHERE url = :url`
+ , { url: uri.spec });
+ },
+
+ shutdown: function () {
+ this._conn = null;
+ this._queue.clear();
+ }
+}));
+
+/**
+ * This helper keeps track of preferences and keeps their values up-to-date.
+ */
+XPCOMUtils.defineLazyGetter(this, "Prefs", () => {
+ let prefs = new Preferences(PREF_BRANCH);
+ let types = ["History", "Bookmark", "Openpage", "Searches"];
+
+ function syncEnabledPref() {
+ loadSyncedPrefs();
+
+ let suggestPrefs = [
+ PREF_SUGGEST_HISTORY,
+ PREF_SUGGEST_BOOKMARK,
+ PREF_SUGGEST_OPENPAGE,
+ PREF_SUGGEST_SEARCHES,
+ ];
+
+ if (store.enabled) {
+ // If the autocomplete preference is active, set to default value all suggest
+ // preferences only if all of them are false.
+ if (types.every(type => store["suggest" + type] == false)) {
+ for (let type of suggestPrefs) {
+ prefs.set(...type);
+ }
+ }
+ } else {
+ // If the preference was deactivated, deactivate all suggest preferences.
+ for (let type of suggestPrefs) {
+ prefs.set(type[0], false);
+ }
+ }
+ }
+
+ function loadSyncedPrefs () {
+ store.enabled = prefs.get(...PREF_ENABLED);
+ store.suggestHistory = prefs.get(...PREF_SUGGEST_HISTORY);
+ store.suggestBookmark = prefs.get(...PREF_SUGGEST_BOOKMARK);
+ store.suggestOpenpage = prefs.get(...PREF_SUGGEST_OPENPAGE);
+ store.suggestTyped = prefs.get(...PREF_SUGGEST_HISTORY_ONLYTYPED);
+ store.suggestSearches = prefs.get(...PREF_SUGGEST_SEARCHES);
+ }
+
+ function loadPrefs(subject, topic, data) {
+ if (data) {
+ // Synchronize suggest.* prefs with autocomplete.enabled.
+ if (data == PREF_BRANCH + PREF_ENABLED[0]) {
+ syncEnabledPref();
+ } else if (data.startsWith(PREF_BRANCH + "suggest.")) {
+ loadSyncedPrefs();
+ prefs.set(PREF_ENABLED[0], types.some(type => store["suggest" + type]));
+ }
+ }
+
+ store.enabled = prefs.get(...PREF_ENABLED);
+ store.autofill = prefs.get(...PREF_AUTOFILL);
+ store.autofillTyped = prefs.get(...PREF_AUTOFILL_TYPED);
+ store.autofillSearchEngines = prefs.get(...PREF_AUTOFILL_SEARCHENGINES);
+ store.restyleSearches = prefs.get(...PREF_RESTYLESEARCHES);
+ store.delay = prefs.get(...PREF_DELAY);
+ store.matchBehavior = prefs.get(...PREF_BEHAVIOR);
+ store.filterJavaScript = prefs.get(...PREF_FILTER_JS);
+ store.maxRichResults = prefs.get(...PREF_MAXRESULTS);
+ store.restrictHistoryToken = prefs.get(...PREF_RESTRICT_HISTORY);
+ store.restrictBookmarkToken = prefs.get(...PREF_RESTRICT_BOOKMARKS);
+ store.restrictTypedToken = prefs.get(...PREF_RESTRICT_TYPED);
+ store.restrictTagToken = prefs.get(...PREF_RESTRICT_TAG);
+ store.restrictOpenPageToken = prefs.get(...PREF_RESTRICT_SWITCHTAB);
+ store.restrictSearchesToken = prefs.get(...PREF_RESTRICT_SEARCHES);
+ store.matchTitleToken = prefs.get(...PREF_MATCH_TITLE);
+ store.matchURLToken = prefs.get(...PREF_MATCH_URL);
+ store.suggestHistory = prefs.get(...PREF_SUGGEST_HISTORY);
+ store.suggestBookmark = prefs.get(...PREF_SUGGEST_BOOKMARK);
+ store.suggestOpenpage = prefs.get(...PREF_SUGGEST_OPENPAGE);
+ store.suggestTyped = prefs.get(...PREF_SUGGEST_HISTORY_ONLYTYPED);
+ store.suggestSearches = prefs.get(...PREF_SUGGEST_SEARCHES);
+ store.maxCharsForSearchSuggestions = prefs.get(...PREF_MAX_CHARS_FOR_SUGGEST);
+ store.keywordEnabled = Services.prefs.getBoolPref("keyword.enabled", true);
+
+ // If history is not set, onlyTyped value should be ignored.
+ if (!store.suggestHistory) {
+ store.suggestTyped = false;
+ }
+ store.defaultBehavior = types.concat("Typed").reduce((memo, type) => {
+ let prefValue = store["suggest" + type];
+ return memo | (prefValue &&
+ Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()]);
+ }, 0);
+
+ // Further restrictions to apply for "empty searches" (i.e. searches for "").
+ // The empty behavior is typed history, if history is enabled. Otherwise,
+ // it is bookmarks, if they are enabled. If both history and bookmarks are disabled,
+ // it defaults to open pages.
+ store.emptySearchDefaultBehavior = Ci.mozIPlacesAutoComplete.BEHAVIOR_RESTRICT;
+ if (store.suggestHistory) {
+ store.emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
+ Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED;
+ } else if (store.suggestBookmark) {
+ store.emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;
+ } else {
+ store.emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE;
+ }
+
+ // Validate matchBehavior; default to MATCH_BOUNDARY_ANYWHERE.
+ if (store.matchBehavior != MATCH_ANYWHERE &&
+ store.matchBehavior != MATCH_BOUNDARY &&
+ store.matchBehavior != MATCH_BEGINNING) {
+ store.matchBehavior = MATCH_BOUNDARY_ANYWHERE;
+ }
+
+ store.tokenToBehaviorMap = new Map([
+ [ store.restrictHistoryToken, "history" ],
+ [ store.restrictBookmarkToken, "bookmark" ],
+ [ store.restrictTagToken, "tag" ],
+ [ store.restrictOpenPageToken, "openpage" ],
+ [ store.matchTitleToken, "title" ],
+ [ store.matchURLToken, "url" ],
+ [ store.restrictTypedToken, "typed" ],
+ [ store.restrictSearchesToken, "searches" ],
+ ]);
+ }
+
+ let store = {
+ _ignoreNotifications: false,
+ observe(subject, topic, data) {
+ // Avoid re-entrancy when flipping linked preferences.
+ if (this._ignoreNotifications)
+ return;
+ this._ignoreNotifications = true;
+ loadPrefs(subject, topic, data);
+ this._ignoreNotifications = false;
+ },
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference ])
+ };
+
+ // Synchronize suggest.* prefs with autocomplete.enabled at initialization
+ syncEnabledPref();
+
+ loadPrefs();
+ prefs.observe("", store);
+ Services.prefs.addObserver("keyword.enabled", store, true);
+
+ return Object.seal(store);
+});
+
+// Helper functions
+
+/**
+ * Used to unescape encoded URI strings and drop information that we do not
+ * care about.
+ *
+ * @param spec
+ * The text to unescape and modify.
+ * @return the modified spec.
+ */
+function fixupSearchText(spec) {
+ return textURIService.unEscapeURIForUI("UTF-8", stripPrefix(spec));
+}
+
+/**
+ * Generates the tokens used in searching from a given string.
+ *
+ * @param searchString
+ * The string to generate tokens from.
+ * @return an array of tokens.
+ * @note Calling split on an empty string will return an array containing one
+ * empty string. We don't want that, as it'll break our logic, so return
+ * an empty array then.
+ */
+function getUnfilteredSearchTokens(searchString) {
+ return searchString.length ? searchString.split(REGEXP_SPACES) : [];
+}
+
+/**
+ * Strip prefixes from the URI that we don't care about for searching.
+ *
+ * @param spec
+ * The text to modify.
+ * @return the modified spec.
+ */
+function stripPrefix(spec)
+{
+ ["http://", "https://", "ftp://"].some(scheme => {
+ // Strip protocol if not directly followed by a space
+ if (spec.startsWith(scheme) && spec[scheme.length] != " ") {
+ spec = spec.slice(scheme.length);
+ return true;
+ }
+ return false;
+ });
+
+ // Strip www. if not directly followed by a space
+ if (spec.startsWith("www.") && spec[4] != " ") {
+ spec = spec.slice(4);
+ }
+ return spec;
+}
+
+/**
+ * Strip http and trailing separators from a spec.
+ *
+ * @param spec
+ * The text to modify.
+ * @return the modified spec.
+ */
+function stripHttpAndTrim(spec) {
+ if (spec.startsWith("http://")) {
+ spec = spec.slice(7);
+ }
+ if (spec.endsWith("?")) {
+ spec = spec.slice(0, -1);
+ }
+ if (spec.endsWith("/")) {
+ spec = spec.slice(0, -1);
+ }
+ return spec;
+}
+
+/**
+ * Returns the key to be used for a URL in a map for the purposes of removing
+ * duplicate entries - any 2 URLs that should be considered the same should
+ * return the same key. For some moz-action URLs this will unwrap the params
+ * and return a key based on the wrapped URL.
+ */
+function makeKeyForURL(actionUrl) {
+ // At this stage we only consider moz-action URLs.
+ if (!actionUrl.startsWith("moz-action:")) {
+ return stripHttpAndTrim(actionUrl);
+ }
+ let [, type, params] = actionUrl.match(/^moz-action:([^,]+),(.*)$/);
+ try {
+ params = JSON.parse(params);
+ } catch (ex) {
+ // This is unexpected in this context, so just return the input.
+ return stripHttpAndTrim(actionUrl);
+ }
+ // For now we only handle these 2 action types and treat them as the same.
+ switch (type) {
+ case "remotetab":
+ case "switchtab":
+ if (params.url) {
+ return "moz-action:tab:" + stripHttpAndTrim(params.url);
+ }
+ break;
+ // TODO (bug 1222435) - "switchtab" should be handled as an "autofill"
+ // entry.
+ default:
+ // do nothing.
+ // TODO (bug 1222436) - extend this method so it can be used instead of
+ // the |placeId| that's also used to remove duplicate entries.
+ }
+ return stripHttpAndTrim(actionUrl);
+}
+
+/**
+ * Returns whether the passed in string looks like a url.
+ */
+function looksLikeUrl(str, ignoreAlphanumericHosts = false) {
+ // Single word not including special chars.
+ return !REGEXP_SPACES.test(str) &&
+ (["/", "@", ":", "["].some(c => str.includes(c)) ||
+ (ignoreAlphanumericHosts ? /(.*\..*){3,}/.test(str) : str.includes(".")));
+}
+
+/**
+ * Manages a single instance of an autocomplete search.
+ *
+ * The first three parameters all originate from the similarly named parameters
+ * of nsIAutoCompleteSearch.startSearch().
+ *
+ * @param searchString
+ * The search string.
+ * @param searchParam
+ * A space-delimited string of search parameters. The following
+ * parameters are supported:
+ * * enable-actions: Include "actions", such as switch-to-tab and search
+ * engine aliases, in the results.
+ * * disable-private-actions: The search is taking place in a private
+ * window outside of permanent private-browsing mode. The search
+ * should exclude privacy-sensitive results as appropriate.
+ * * private-window: The search is taking place in a private window,
+ * possibly in permanent private-browsing mode. The search
+ * should exclude privacy-sensitive results as appropriate.
+ * * user-context-id: The userContextId of the selected tab.
+ * @param autocompleteListener
+ * An nsIAutoCompleteObserver.
+ * @param resultListener
+ * An nsIAutoCompleteSimpleResultListener.
+ * @param autocompleteSearch
+ * An nsIAutoCompleteSearch.
+ * @param prohibitSearchSuggestions
+ * Whether search suggestions are allowed for this search.
+ */
+function Search(searchString, searchParam, autocompleteListener,
+ resultListener, autocompleteSearch, prohibitSearchSuggestions) {
+ // We want to store the original string for case sensitive searches.
+ this._originalSearchString = searchString;
+ this._trimmedOriginalSearchString = searchString.trim();
+ this._searchString = fixupSearchText(this._trimmedOriginalSearchString.toLowerCase());
+
+ this._matchBehavior = Prefs.matchBehavior;
+ // Set the default behavior for this search.
+ this._behavior = this._searchString ? Prefs.defaultBehavior
+ : Prefs.emptySearchDefaultBehavior;
+
+ let params = new Set(searchParam.split(" "));
+ this._enableActions = params.has("enable-actions");
+ this._disablePrivateActions = params.has("disable-private-actions");
+ this._inPrivateWindow = params.has("private-window");
+ this._prohibitAutoFill = params.has("prohibit-autofill");
+
+ let userContextId = searchParam.match(REGEXP_USER_CONTEXT_ID);
+ this._userContextId = userContextId ?
+ parseInt(userContextId[1], 10) :
+ Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
+
+ this._searchTokens =
+ this.filterTokens(getUnfilteredSearchTokens(this._searchString));
+ // The protocol and the host are lowercased by nsIURI, so it's fine to
+ // lowercase the typed prefix, to add it back to the results later.
+ this._strippedPrefix = this._trimmedOriginalSearchString.slice(
+ 0, this._trimmedOriginalSearchString.length - this._searchString.length
+ ).toLowerCase();
+ // The URIs in the database are fixed-up, so we can match on a lowercased
+ // host, but the path must be matched in a case sensitive way.
+ let pathIndex =
+ this._trimmedOriginalSearchString.indexOf("/", this._strippedPrefix.length);
+ this._autofillUrlSearchString = fixupSearchText(
+ this._trimmedOriginalSearchString.slice(0, pathIndex).toLowerCase() +
+ this._trimmedOriginalSearchString.slice(pathIndex)
+ );
+
+ this._prohibitSearchSuggestions = prohibitSearchSuggestions;
+
+ this._listener = autocompleteListener;
+ this._autocompleteSearch = autocompleteSearch;
+
+ // Create a new result to add eventual matches. Note we need a result
+ // regardless having matches.
+ let result = Cc["@mozilla.org/autocomplete/simple-result;1"]
+ .createInstance(Ci.nsIAutoCompleteSimpleResult);
+ result.setSearchString(searchString);
+ result.setListener(resultListener);
+ // Will be set later, if needed.
+ result.setDefaultIndex(-1);
+ this._result = result;
+
+ // These are used to avoid adding duplicate entries to the results.
+ this._usedURLs = new Set();
+ this._usedPlaceIds = new Set();
+
+ // Resolved when all the remote matches have been fetched.
+ this._remoteMatchesPromises = [];
+
+ // The index to insert remote matches at.
+ this._remoteMatchesStartIndex = 0;
+ // The index to insert local matches at.
+
+ this._localMatchesStartIndex = 0;
+
+ // Counts the number of inserted local matches.
+ this._localMatchesCount = 0;
+ // Counts the number of inserted remote matches.
+ this._remoteMatchesCount = 0;
+ // Counts the number of inserted extension matches.
+ this._extensionMatchesCount = 0;
+}
+
+Search.prototype = {
+ /**
+ * Enables the desired AutoComplete behavior.
+ *
+ * @param type
+ * The behavior type to set.
+ */
+ setBehavior: function (type) {
+ type = type.toUpperCase();
+ this._behavior |=
+ Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type];
+
+ // Setting the "typed" behavior should also set the "history" behavior.
+ if (type == "TYPED") {
+ this.setBehavior("history");
+ }
+ },
+
+ /**
+ * Determines if the specified AutoComplete behavior is set.
+ *
+ * @param aType
+ * The behavior type to test for.
+ * @return true if the behavior is set, false otherwise.
+ */
+ hasBehavior: function (type) {
+ let behavior = Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()];
+
+ if (this._disablePrivateActions &&
+ behavior == Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE) {
+ return false;
+ }
+
+ return this._behavior & behavior;
+ },
+
+ /**
+ * Used to delay the most complex queries, to save IO while the user is
+ * typing.
+ */
+ _sleepDeferred: null,
+ _sleep: function (aTimeMs) {
+ // Reuse a single instance to try shaving off some usless work before
+ // the first query.
+ if (!this._sleepTimer)
+ this._sleepTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._sleepDeferred = PromiseUtils.defer();
+ this._sleepTimer.initWithCallback(() => this._sleepDeferred.resolve(),
+ aTimeMs, Ci.nsITimer.TYPE_ONE_SHOT);
+ return this._sleepDeferred.promise;
+ },
+
+ /**
+ * Given an array of tokens, this function determines which query should be
+ * ran. It also removes any special search tokens.
+ *
+ * @param tokens
+ * An array of search tokens.
+ * @return the filtered list of tokens to search with.
+ */
+ filterTokens: function (tokens) {
+ let foundToken = false;
+ // Set the proper behavior while filtering tokens.
+ for (let i = tokens.length - 1; i >= 0; i--) {
+ let behavior = Prefs.tokenToBehaviorMap.get(tokens[i]);
+ // Don't remove the token if it didn't match, or if it's an action but
+ // actions are not enabled.
+ if (behavior && (behavior != "openpage" || this._enableActions)) {
+ // Don't use the suggest preferences if it is a token search and
+ // set the restrict bit to 1 (to intersect the search results).
+ if (!foundToken) {
+ foundToken = true;
+ // Do not take into account previous behavior (e.g.: history, bookmark)
+ this._behavior = 0;
+ this.setBehavior("restrict");
+ }
+ this.setBehavior(behavior);
+ tokens.splice(i, 1);
+ }
+ }
+
+ // Set the right JavaScript behavior based on our preference. Note that the
+ // preference is whether or not we should filter JavaScript, and the
+ // behavior is if we should search it or not.
+ if (!Prefs.filterJavaScript) {
+ this.setBehavior("javascript");
+ }
+
+ return tokens;
+ },
+
+ /**
+ * Stop this search.
+ * After invoking this method, we won't run any more searches or heuristics,
+ * and no new matches may be added to the current result.
+ */
+ stop() {
+ if (this._sleepTimer)
+ this._sleepTimer.cancel();
+ if (this._sleepDeferred) {
+ this._sleepDeferred.resolve();
+ this._sleepDeferred = null;
+ }
+ if (this._searchSuggestionController) {
+ this._searchSuggestionController.stop();
+ this._searchSuggestionController = null;
+ }
+ this.pending = false;
+ },
+
+ /**
+ * Whether this search is active.
+ */
+ pending: true,
+
+ /**
+ * Execute the search and populate results.
+ * @param conn
+ * The Sqlite connection.
+ */
+ execute: Task.async(function* (conn) {
+ // A search might be canceled before it starts.
+ if (!this.pending)
+ return;
+
+ // Since we call the synchronous parseSubmissionURL function later, we must
+ // wait for the initialization of PlacesSearchAutocompleteProvider first.
+ yield PlacesSearchAutocompleteProvider.ensureInitialized();
+ if (!this.pending)
+ return;
+
+ // For any given search, we run many queries/heuristics:
+ // 1) by alias (as defined in SearchService)
+ // 2) inline completion from search engine resultDomains
+ // 3) inline completion for hosts (this._hostQuery) or urls (this._urlQuery)
+ // 4) directly typed in url (ie, can be navigated to as-is)
+ // 5) submission for the current search engine
+ // 6) Places keywords
+ // 7) adaptive learning (this._adaptiveQuery)
+ // 8) open pages not supported by history (this._switchToTabQuery)
+ // 9) query based on match behavior
+ //
+ // (6) only gets ran if we get any filtered tokens, since if there are no
+ // tokens, there is nothing to match. This is the *first* query we check if
+ // we want to run, but it gets queued to be run later.
+ //
+ // (1), (4), (5) only get run if actions are enabled. When actions are
+ // enabled, the first result is always a special result (resulting from one
+ // of the queries between (1) and (6) inclusive). As such, the UI is
+ // expected to auto-select the first result when actions are enabled. If the
+ // first result is an inline completion result, that will also be the
+ // default result and therefore be autofilled (this also happens if actions
+ // are not enabled).
+
+ // Get the final query, based on the tokens found in the search string.
+ let queries = [ this._adaptiveQuery ];
+
+ // "openpage" behavior is supported by the default query.
+ // _switchToTabQuery instead returns only pages not supported by history.
+ if (this.hasBehavior("openpage")) {
+ queries.push(this._switchToTabQuery);
+ }
+ queries.push(this._searchQuery);
+
+ // Add the first heuristic result, if any. Set _addingHeuristicFirstMatch
+ // to true so that when the result is added, "heuristic" can be included in
+ // its style.
+ this._addingHeuristicFirstMatch = true;
+ let hasHeuristic = yield this._matchFirstHeuristicResult(conn);
+ this._addingHeuristicFirstMatch = false;
+ if (!this.pending)
+ return;
+
+ // We sleep a little between adding the heuristicFirstMatch and matching
+ // any other searches so we aren't kicking off potentially expensive
+ // searches on every keystroke.
+ // Though, if there's no heuristic result, we start searching immediately,
+ // since autocomplete may be waiting for us.
+ if (hasHeuristic) {
+ yield this._sleep(Prefs.delay);
+ if (!this.pending)
+ return;
+ }
+
+ if (this._enableActions && this._searchTokens.length > 0) {
+ yield this._matchSearchSuggestions();
+ if (!this.pending)
+ return;
+ }
+
+ for (let [query, params] of queries) {
+ yield conn.executeCached(query, params, this._onResultRow.bind(this));
+ if (!this.pending)
+ return;
+ }
+
+ if (this._enableActions && this.hasBehavior("openpage")) {
+ yield this._matchRemoteTabs();
+ if (!this.pending)
+ return;
+ }
+
+ // If we do not have enough results, and our match type is
+ // MATCH_BOUNDARY_ANYWHERE, search again with MATCH_ANYWHERE to get more
+ // results.
+ if (this._matchBehavior == MATCH_BOUNDARY_ANYWHERE &&
+ this._localMatchesCount < Prefs.maxRichResults) {
+ this._matchBehavior = MATCH_ANYWHERE;
+ for (let [query, params] of [ this._adaptiveQuery,
+ this._searchQuery ]) {
+ yield conn.executeCached(query, params, this._onResultRow.bind(this));
+ if (!this.pending)
+ return;
+ }
+ }
+
+ // Only add extension suggestions if the first token is a registered keyword
+ // and the search string has characters after the first token.
+ if (ExtensionSearchHandler.isKeywordRegistered(this._searchTokens[0]) &&
+ this._originalSearchString.length > this._searchTokens[0].length) {
+ yield this._matchExtensionSuggestions();
+ if (!this.pending)
+ return;
+ } else if (ExtensionSearchHandler.hasActiveInputSession()) {
+ ExtensionSearchHandler.handleInputCancelled();
+ }
+
+ // Ensure to fill any remaining space. Suggestions which come from extensions are
+ // inserted at the beginning, so any suggestions
+ yield Promise.all(this._remoteMatchesPromises);
+ }),
+
+ *_matchFirstHeuristicResult(conn) {
+ // We always try to make the first result a special "heuristic" result. The
+ // heuristics below determine what type of result it will be, if any.
+
+ let hasSearchTerms = this._searchTokens.length > 0;
+
+ if (hasSearchTerms) {
+ // It may be a keyword registered by an extension.
+ let matched = yield this._matchExtensionHeuristicResult();
+ if (matched) {
+ return true;
+ }
+ }
+
+ if (this._enableActions && hasSearchTerms) {
+ // It may be a search engine with an alias - which works like a keyword.
+ let matched = yield this._matchSearchEngineAlias();
+ if (matched) {
+ return true;
+ }
+ }
+
+ if (this.pending && hasSearchTerms) {
+ // It may be a Places keyword.
+ let matched = yield this._matchPlacesKeyword();
+ if (matched) {
+ return true;
+ }
+ }
+
+ let shouldAutofill = this._shouldAutofill;
+ if (this.pending && shouldAutofill) {
+ // It may also look like a URL we know from the database.
+ let matched = yield this._matchKnownUrl(conn);
+ if (matched) {
+ return true;
+ }
+ }
+
+ if (this.pending && shouldAutofill) {
+ // Or it may look like a URL we know about from search engines.
+ let matched = yield this._matchSearchEngineUrl();
+ if (matched) {
+ return true;
+ }
+ }
+
+ if (this.pending && hasSearchTerms && this._enableActions) {
+ // If we don't have a result that matches what we know about, then
+ // we use a fallback for things we don't know about.
+
+ // We may not have auto-filled, but this may still look like a URL.
+ // However, even if the input is a valid URL, we may not want to use
+ // it as such. This can happen if the host would require whitelisting,
+ // but isn't in the whitelist.
+ let matched = yield this._matchUnknownUrl();
+ if (matched) {
+ // Since we can't tell if this is a real URL and
+ // whether the user wants to visit or search for it,
+ // we always provide an alternative searchengine match.
+ try {
+ new URL(this._originalSearchString);
+ } catch (ex) {
+ if (Prefs.keywordEnabled && !looksLikeUrl(this._originalSearchString, true)) {
+ this._addingHeuristicFirstMatch = false;
+ yield this._matchCurrentSearchEngine();
+ this._addingHeuristicFirstMatch = true;
+ }
+ }
+ return true;
+ }
+ }
+
+ if (this.pending && this._enableActions && this._originalSearchString) {
+ // When all else fails, and the search string is non-empty, we search
+ // using the current search engine.
+ let matched = yield this._matchCurrentSearchEngine();
+ if (matched) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ *_matchSearchSuggestions() {
+ // Limit the string sent for search suggestions to a maximum length.
+ let searchString = this._searchTokens.join(" ")
+ .substr(0, Prefs.maxCharsForSearchSuggestions);
+ // Avoid fetching suggestions if they are not required, private browsing
+ // mode is enabled, or the search string may expose sensitive information.
+ if (!this.hasBehavior("searches") || this._inPrivateWindow ||
+ this._prohibitSearchSuggestionsFor(searchString)) {
+ return;
+ }
+
+ this._searchSuggestionController =
+ PlacesSearchAutocompleteProvider.getSuggestionController(
+ searchString,
+ this._inPrivateWindow,
+ Prefs.maxRichResults,
+ this._userContextId
+ );
+ let promise = this._searchSuggestionController.fetchCompletePromise
+ .then(() => {
+ // The search has been canceled already.
+ if (!this._searchSuggestionController)
+ return;
+ if (this._searchSuggestionController.resultsCount >= 0 &&
+ this._searchSuggestionController.resultsCount < 2) {
+ // The original string is used to properly compare with the next search.
+ this._lastLowResultsSearchSuggestion = this._originalSearchString;
+ }
+ while (this.pending && this._remoteMatchesCount < Prefs.maxRichResults) {
+ let [match, suggestion] = this._searchSuggestionController.consume();
+ if (!suggestion)
+ break;
+ if (!looksLikeUrl(suggestion)) {
+ // Don't include the restrict token, if present.
+ let searchString = this._searchTokens.join(" ");
+ this._addSearchEngineMatch(match, searchString, suggestion);
+ }
+ }
+ });
+
+ if (this.hasBehavior("restrict")) {
+ // We're done if we're restricting to search suggestions.
+ yield promise;
+ this.stop();
+ } else {
+ this._remoteMatchesPromises.push(promise);
+ }
+ },
+
+ _prohibitSearchSuggestionsFor(searchString) {
+ if (this._prohibitSearchSuggestions)
+ return true;
+
+ // Suggestions for a single letter are unlikely to be useful.
+ if (searchString.length < 2)
+ return true;
+
+ // The first token may be a whitelisted host.
+ if (this._searchTokens.length == 1 &&
+ REGEXP_SINGLEWORD_HOST.test(this._searchTokens[0]) &&
+ Services.uriFixup.isDomainWhitelisted(this._searchTokens[0], -1)) {
+ return true;
+ }
+
+ // Disallow fetching search suggestions for strings looking like URLs, to
+ // avoid disclosing information about networks or passwords.
+ return this._searchTokens.some(looksLikeUrl);
+ },
+
+ _matchKnownUrl: function* (conn) {
+ // Hosts have no "/" in them.
+ let lastSlashIndex = this._searchString.lastIndexOf("/");
+ // Search only URLs if there's a slash in the search string...
+ if (lastSlashIndex != -1) {
+ // ...but not if it's exactly at the end of the search string.
+ if (lastSlashIndex < this._searchString.length - 1) {
+ // We don't want to execute this query right away because it needs to
+ // search the entire DB without an index, but we need to know if we have
+ // a result as it will influence other heuristics. So we guess by
+ // assuming that if we get a result from a *host* query and it *looks*
+ // like a URL, then we'll probably have a result.
+ let gotResult = false;
+ let [ query, params ] = this._urlQuery;
+ yield conn.executeCached(query, params, row => {
+ gotResult = true;
+ this._onResultRow(row);
+ });
+ return gotResult;
+ }
+ return false;
+ }
+
+ let gotResult = false;
+ let [ query, params ] = this._hostQuery;
+ yield conn.executeCached(query, params, row => {
+ gotResult = true;
+ this._onResultRow(row);
+ });
+ return gotResult;
+ },
+
+ _matchExtensionHeuristicResult: function* () {
+ if (ExtensionSearchHandler.isKeywordRegistered(this._searchTokens[0]) &&
+ this._originalSearchString.length > this._searchTokens[0].length) {
+ let description = ExtensionSearchHandler.getDescription(this._searchTokens[0]);
+ this._addExtensionMatch(this._originalSearchString, description);
+ return true;
+ }
+ return false;
+ },
+
+ _matchPlacesKeyword: function* () {
+ // The first word could be a keyword, so that's what we'll search.
+ let keyword = this._searchTokens[0];
+ let entry = yield PlacesUtils.keywords.fetch(this._searchTokens[0]);
+ if (!entry)
+ return false;
+
+ let searchString = this._trimmedOriginalSearchString.substr(keyword.length + 1);
+
+ let url = null, postData = null;
+ try {
+ [url, postData] =
+ yield BrowserUtils.parseUrlAndPostData(entry.url.href,
+ entry.postData,
+ searchString);
+ } catch (ex) {
+ // It's not possible to bind a param to this keyword.
+ return false;
+ }
+
+ let style = (this._enableActions ? "action " : "") + "keyword";
+ let actionURL = PlacesUtils.mozActionURI("keyword", {
+ url,
+ input: this._originalSearchString,
+ postData,
+ });
+ let value = this._enableActions ? actionURL : url;
+ // The title will end up being "host: queryString"
+ let comment = entry.url.host;
+
+ this._addMatch({ value, comment, style, frecency: FRECENCY_DEFAULT });
+ return true;
+ },
+
+ _matchSearchEngineUrl: function* () {
+ if (!Prefs.autofillSearchEngines)
+ return false;
+
+ let match = yield PlacesSearchAutocompleteProvider.findMatchByToken(
+ this._searchString);
+ if (!match)
+ return false;
+
+ // The match doesn't contain a 'scheme://www.' prefix, but since we have
+ // stripped it from the search string, here we could still be matching
+ // 'https://www.g' to 'google.com'.
+ // There are a couple cases where we don't want to match though:
+ //
+ // * If the protocol differs we should not match. For example if the user
+ // searched https we should not return http.
+ try {
+ let prefixURI = NetUtil.newURI(this._strippedPrefix + match.token);
+ let finalURI = NetUtil.newURI(match.url);
+ if (prefixURI.scheme != finalURI.scheme)
+ return false;
+ } catch (e) {}
+
+ // * If the user typed "www." but the final url doesn't have it, we
+ // should not match as well, the two urls may point to different pages.
+ if (this._strippedPrefix.endsWith("www.") &&
+ !stripHttpAndTrim(match.url).startsWith("www."))
+ return false;
+
+ let value = this._strippedPrefix + match.token;
+
+ // In any case, we should never arrive here with a value that doesn't
+ // match the search string. If this happens there is some case we
+ // are not handling properly yet.
+ if (!value.startsWith(this._originalSearchString)) {
+ Components.utils.reportError(`Trying to inline complete in-the-middle
+ ${this._originalSearchString} to ${value}`);
+ return false;
+ }
+
+ this._result.setDefaultIndex(0);
+ this._addMatch({
+ value: value,
+ comment: match.engineName,
+ icon: match.iconUrl,
+ style: "priority-search",
+ finalCompleteValue: match.url,
+ frecency: FRECENCY_DEFAULT
+ });
+ return true;
+ },
+
+ _matchSearchEngineAlias: function* () {
+ if (this._searchTokens.length < 1)
+ return false;
+
+ let alias = this._searchTokens[0];
+ let match = yield PlacesSearchAutocompleteProvider.findMatchByAlias(alias);
+ if (!match)
+ return false;
+
+ match.engineAlias = alias;
+ let query = this._trimmedOriginalSearchString.substr(alias.length + 1);
+
+ this._addSearchEngineMatch(match, query);
+ return true;
+ },
+
+ _matchCurrentSearchEngine: function* () {
+ let match = yield PlacesSearchAutocompleteProvider.getDefaultMatch();
+ if (!match)
+ return false;
+
+ let query = this._originalSearchString;
+ this._addSearchEngineMatch(match, query);
+ return true;
+ },
+
+ _addExtensionMatch(content, comment) {
+ if (this._extensionMatchesCount >= MAXIMUM_ALLOWED_EXTENSION_MATCHES) {
+ return;
+ }
+
+ this._addMatch({
+ value: PlacesUtils.mozActionURI("extension", {
+ content,
+ keyword: this._searchTokens[0]
+ }),
+ comment,
+ icon: "chrome://browser/content/extension.svg",
+ style: "action extension",
+ frecency: FRECENCY_DEFAULT,
+ extension: true,
+ });
+ },
+
+ _addSearchEngineMatch(match, query, suggestion) {
+ let actionURLParams = {
+ engineName: match.engineName,
+ input: suggestion || this._originalSearchString,
+ searchQuery: query,
+ };
+ if (suggestion)
+ actionURLParams.searchSuggestion = suggestion;
+ if (match.engineAlias) {
+ actionURLParams.alias = match.engineAlias;
+ }
+ let value = PlacesUtils.mozActionURI("searchengine", actionURLParams);
+
+ this._addMatch({
+ value: value,
+ comment: match.engineName,
+ icon: match.iconUrl,
+ style: "action searchengine",
+ frecency: FRECENCY_DEFAULT,
+ remote: !!suggestion
+ });
+ },
+
+ *_matchExtensionSuggestions() {
+ let promise = ExtensionSearchHandler.handleSearch(this._searchTokens[0], this._originalSearchString,
+ suggestions => {
+ suggestions.forEach(suggestion => {
+ let content = `${this._searchTokens[0]} ${suggestion.content}`;
+ this._addExtensionMatch(content, suggestion.description);
+ });
+ }
+ );
+ this._remoteMatchesPromises.push(promise);
+ },
+
+ *_matchRemoteTabs() {
+ let matches = yield PlacesRemoteTabsAutocompleteProvider.getMatches(this._originalSearchString);
+ for (let {url, title, icon, deviceName} of matches) {
+ // It's rare that Sync supplies the icon for the page (but if it does, it
+ // is a string URL)
+ if (!icon) {
+ try {
+ let favicon = yield PlacesUtils.promiseFaviconLinkUrl(url);
+ if (favicon) {
+ icon = favicon.spec;
+ }
+ } catch (ex) {} // no favicon for this URL.
+ } else {
+ icon = PlacesUtils.favicons
+ .getFaviconLinkForIcon(NetUtil.newURI(icon)).spec;
+ }
+
+ let match = {
+ // We include the deviceName in the action URL so we can render it in
+ // the URLBar.
+ value: PlacesUtils.mozActionURI("remotetab", { url, deviceName }),
+ comment: title || url,
+ style: "action remotetab",
+ // we want frecency > FRECENCY_DEFAULT so it doesn't get pushed out
+ // by "remote" matches.
+ frecency: FRECENCY_DEFAULT + 1,
+ icon,
+ }
+ this._addMatch(match);
+ }
+ },
+
+ // TODO (bug 1054814): Use visited URLs to inform which scheme to use, if the
+ // scheme isn't specificed.
+ _matchUnknownUrl: function* () {
+ let flags = Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
+ Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ let fixupInfo = null;
+ try {
+ fixupInfo = Services.uriFixup.getFixupURIInfo(this._originalSearchString,
+ flags);
+ } catch (e) {
+ if (e.result == Cr.NS_ERROR_MALFORMED_URI && !Prefs.keywordEnabled) {
+ let value = PlacesUtils.mozActionURI("visiturl", {
+ url: this._originalSearchString,
+ input: this._originalSearchString,
+ });
+ this._addMatch({
+ value,
+ comment: this._originalSearchString,
+ style: "action visiturl",
+ frecency: 0,
+ });
+
+ return true;
+ }
+ return false;
+ }
+
+ // If the URI cannot be fixed or the preferred URI would do a keyword search,
+ // that basically means this isn't useful to us. Note that
+ // fixupInfo.keywordAsSent will never be true if the keyword.enabled pref
+ // is false or there are no engines, so in that case we will always return
+ // a "visit".
+ if (!fixupInfo.fixedURI || fixupInfo.keywordAsSent)
+ return false;
+
+ let uri = fixupInfo.fixedURI;
+ // Check the host, as "http:///" is a valid nsIURI, but not useful to us.
+ // But, some schemes are expected to have no host. So we check just against
+ // schemes we know should have a host. This allows new schemes to be
+ // implemented without us accidentally blocking access to them.
+ let hostExpected = new Set(["http", "https", "ftp", "chrome", "resource"]);
+ if (hostExpected.has(uri.scheme) && !uri.host)
+ return false;
+
+ // getFixupURIInfo() escaped the URI, so it may not be pretty. Embed the
+ // escaped URL in the action URI since that URL should be "canonical". But
+ // pass the pretty, unescaped URL as the match comment, since it's likely
+ // to be displayed to the user, and in any case the front-end should not
+ // rely on it being canonical.
+ let escapedURL = uri.spec;
+ let displayURL = textURIService.unEscapeURIForUI("UTF-8", uri.spec);
+
+ let value = PlacesUtils.mozActionURI("visiturl", {
+ url: escapedURL,
+ input: this._originalSearchString,
+ });
+
+ let match = {
+ value: value,
+ comment: displayURL,
+ style: "action visiturl",
+ frecency: 0,
+ };
+
+ try {
+ let favicon = yield PlacesUtils.promiseFaviconLinkUrl(uri);
+ if (favicon)
+ match.icon = favicon.spec;
+ } catch (e) {
+ // It's possible we don't have a favicon for this - and that's ok.
+ }
+
+ this._addMatch(match);
+ return true;
+ },
+
+ _onResultRow: function (row) {
+ let queryType = row.getResultByIndex(QUERYINDEX_QUERYTYPE);
+ let match;
+ switch (queryType) {
+ case QUERYTYPE_AUTOFILL_HOST:
+ this._result.setDefaultIndex(0);
+ match = this._processHostRow(row);
+ break;
+ case QUERYTYPE_AUTOFILL_URL:
+ this._result.setDefaultIndex(0);
+ match = this._processUrlRow(row);
+ break;
+ case QUERYTYPE_FILTERED:
+ match = this._processRow(row);
+ break;
+ }
+ this._addMatch(match);
+ // If the search has been canceled by the user or by _addMatch, or we
+ // fetched enough results, we can stop the underlying Sqlite query.
+ if (!this.pending || this._localMatchesCount == Prefs.maxRichResults)
+ throw StopIteration;
+ },
+
+ _maybeRestyleSearchMatch: function (match) {
+ // Return if the URL does not represent a search result.
+ let parseResult =
+ PlacesSearchAutocompleteProvider.parseSubmissionURL(match.value);
+ if (!parseResult) {
+ return;
+ }
+
+ // Do not apply the special style if the user is doing a search from the
+ // location bar but the entered terms match an irrelevant portion of the
+ // URL. For example, "https://www.google.com/search?q=terms&client=firefox"
+ // when searching for "Firefox".
+ let terms = parseResult.terms.toLowerCase();
+ if (this._searchTokens.length > 0 &&
+ this._searchTokens.every(token => !terms.includes(token))) {
+ return;
+ }
+
+ // Turn the match into a searchengine action with a favicon.
+ match.value = PlacesUtils.mozActionURI("searchengine", {
+ engineName: parseResult.engineName,
+ input: parseResult.terms,
+ searchQuery: parseResult.terms,
+ });
+ match.comment = parseResult.engineName;
+ match.icon = match.icon || match.iconUrl;
+ match.style = "action searchengine favicon";
+ },
+
+ _addMatch(match) {
+ // A search could be canceled between a query start and its completion,
+ // in such a case ensure we won't notify any result for it.
+ if (!this.pending)
+ return;
+
+ // Must check both id and url, cause keywords dynamically modify the url.
+ let urlMapKey = makeKeyForURL(match.value);
+ if ((match.placeId && this._usedPlaceIds.has(match.placeId)) ||
+ this._usedURLs.has(urlMapKey)) {
+ return;
+ }
+
+ // Add this to our internal tracker to ensure duplicates do not end up in
+ // the result.
+ // Not all entries have a place id, thus we fallback to the url for them.
+ // We cannot use only the url since keywords entries are modified to
+ // include the search string, and would be returned multiple times. Ids
+ // are faster too.
+ if (match.placeId)
+ this._usedPlaceIds.add(match.placeId);
+ this._usedURLs.add(urlMapKey);
+
+ match.style = match.style || "favicon";
+
+ // Restyle past searches, unless they are bookmarks or special results.
+ if (Prefs.restyleSearches && match.style == "favicon") {
+ this._maybeRestyleSearchMatch(match);
+ }
+
+ if (this._addingHeuristicFirstMatch) {
+ match.style += " heuristic";
+ }
+
+ match.icon = match.icon || "";
+ match.finalCompleteValue = match.finalCompleteValue || "";
+
+ this._result.insertMatchAt(this._getInsertIndexForMatch(match),
+ match.value,
+ match.comment,
+ match.icon,
+ match.style,
+ match.finalCompleteValue);
+
+ this.notifyResults(true);
+ },
+
+ _getInsertIndexForMatch(match) {
+ let index = 0;
+ if (match.remote) {
+ // Append after local matches.
+ index = this._remoteMatchesStartIndex + this._remoteMatchesCount;
+ this._remoteMatchesCount++;
+ } else if (match.extension) {
+ index = this._localMatchesStartIndex;
+ this._localMatchesStartIndex++;
+ this._remoteMatchesStartIndex++;
+ this._extensionMatchesCount++;
+ } else {
+ // This is a local match.
+ if (match.frecency > FRECENCY_DEFAULT ||
+ this._localMatchesCount < MINIMUM_LOCAL_MATCHES) {
+ // Append before remote matches.
+ index = this._remoteMatchesStartIndex;
+ this._remoteMatchesStartIndex++
+ } else {
+ // Append after remote matches.
+ index = this._localMatchesCount + this._remoteMatchesCount;
+ }
+ this._localMatchesCount++;
+ }
+ return index;
+ },
+
+ _processHostRow: function (row) {
+ let match = {};
+ let trimmedHost = row.getResultByIndex(QUERYINDEX_URL);
+ let untrimmedHost = row.getResultByIndex(QUERYINDEX_TITLE);
+ let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);
+ let faviconUrl = row.getResultByIndex(QUERYINDEX_ICONURL);
+
+ // If the untrimmed value doesn't preserve the user's input just
+ // ignore it and complete to the found host.
+ if (untrimmedHost &&
+ !untrimmedHost.toLowerCase().includes(this._trimmedOriginalSearchString.toLowerCase())) {
+ untrimmedHost = null;
+ }
+
+ match.value = this._strippedPrefix + trimmedHost;
+ // Remove the trailing slash.
+ match.comment = stripHttpAndTrim(trimmedHost);
+ match.finalCompleteValue = untrimmedHost;
+ if (faviconUrl) {
+ match.icon = PlacesUtils.favicons
+ .getFaviconLinkForIcon(NetUtil.newURI(faviconUrl)).spec;
+ }
+ // Although this has a frecency, this query is executed before any other
+ // queries that would result in frecency matches.
+ match.frecency = frecency;
+ match.style = "autofill";
+ return match;
+ },
+
+ _processUrlRow: function (row) {
+ let match = {};
+ let value = row.getResultByIndex(QUERYINDEX_URL);
+ let url = fixupSearchText(value);
+ let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);
+ let faviconUrl = row.getResultByIndex(QUERYINDEX_ICONURL);
+
+ let prefix = value.slice(0, value.length - stripPrefix(value).length);
+
+ // We must complete the URL up to the next separator (which is /, ? or #).
+ let separatorIndex = url.slice(this._searchString.length)
+ .search(/[\/\?\#]/);
+ if (separatorIndex != -1) {
+ separatorIndex += this._searchString.length;
+ if (url[separatorIndex] == "/") {
+ separatorIndex++; // Include the "/" separator
+ }
+ url = url.slice(0, separatorIndex);
+ }
+
+ // If the untrimmed value doesn't preserve the user's input just
+ // ignore it and complete to the found url.
+ let untrimmedURL = prefix + url;
+ if (untrimmedURL &&
+ !untrimmedURL.toLowerCase().includes(this._trimmedOriginalSearchString.toLowerCase())) {
+ untrimmedURL = null;
+ }
+
+ match.value = this._strippedPrefix + url;
+ match.comment = url;
+ match.finalCompleteValue = untrimmedURL;
+ if (faviconUrl) {
+ match.icon = PlacesUtils.favicons
+ .getFaviconLinkForIcon(NetUtil.newURI(faviconUrl)).spec;
+ }
+ // Although this has a frecency, this query is executed before any other
+ // queries that would result in frecency matches.
+ match.frecency = frecency;
+ match.style = "autofill";
+ return match;
+ },
+
+ _processRow: function (row) {
+ let match = {};
+ match.placeId = row.getResultByIndex(QUERYINDEX_PLACEID);
+ let escapedURL = row.getResultByIndex(QUERYINDEX_URL);
+ let openPageCount = row.getResultByIndex(QUERYINDEX_SWITCHTAB) || 0;
+ let historyTitle = row.getResultByIndex(QUERYINDEX_TITLE) || "";
+ let iconurl = row.getResultByIndex(QUERYINDEX_ICONURL) || "";
+ let bookmarked = row.getResultByIndex(QUERYINDEX_BOOKMARKED);
+ let bookmarkTitle = bookmarked ?
+ row.getResultByIndex(QUERYINDEX_BOOKMARKTITLE) : null;
+ let tags = row.getResultByIndex(QUERYINDEX_TAGS) || "";
+ let frecency = row.getResultByIndex(QUERYINDEX_FRECENCY);
+
+ // If actions are enabled and the page is open, add only the switch-to-tab
+ // result. Otherwise, add the normal result.
+ let url = escapedURL;
+ let action = null;
+ if (this._enableActions && openPageCount > 0 && this.hasBehavior("openpage")) {
+ url = PlacesUtils.mozActionURI("switchtab", {url: escapedURL});
+ action = "switchtab";
+ }
+
+ // Always prefer the bookmark title unless it is empty
+ let title = bookmarkTitle || historyTitle;
+
+ // We will always prefer to show tags if we have them.
+ let showTags = !!tags;
+
+ // However, we'll act as if a page is not bookmarked if the user wants
+ // only history and not bookmarks and there are no tags.
+ if (this.hasBehavior("history") && !this.hasBehavior("bookmark") &&
+ !showTags) {
+ showTags = false;
+ match.style = "favicon";
+ }
+
+ // If we have tags and should show them, we need to add them to the title.
+ if (showTags) {
+ title += TITLE_TAGS_SEPARATOR + tags;
+ }
+
+ // We have to determine the right style to display. Tags show the tag icon,
+ // bookmarks get the bookmark icon, and keywords get the keyword icon. If
+ // the result does not fall into any of those, it just gets the favicon.
+ if (!match.style) {
+ // It is possible that we already have a style set (from a keyword
+ // search or because of the user's preferences), so only set it if we
+ // haven't already done so.
+ if (showTags) {
+ // If we're not suggesting bookmarks, then this shouldn't
+ // display as one.
+ match.style = this.hasBehavior("bookmark") ? "bookmark-tag" : "tag";
+ }
+ else if (bookmarked) {
+ match.style = "bookmark";
+ }
+ }
+
+ if (action)
+ match.style = "action " + action;
+
+ match.value = url;
+ match.comment = title;
+ if (iconurl) {
+ match.icon = PlacesUtils.favicons
+ .getFaviconLinkForIcon(NetUtil.newURI(iconurl)).spec;
+ }
+ match.frecency = frecency;
+
+ return match;
+ },
+
+ /**
+ * @return a string consisting of the search query to be used based on the
+ * previously set urlbar suggestion preferences.
+ */
+ get _suggestionPrefQuery() {
+ if (!this.hasBehavior("restrict") && this.hasBehavior("history") &&
+ this.hasBehavior("bookmark")) {
+ return this.hasBehavior("typed") ? defaultQuery("AND h.typed = 1")
+ : defaultQuery();
+ }
+ let conditions = [];
+ if (this.hasBehavior("history")) {
+ // Enforce ignoring the visit_count index, since the frecency one is much
+ // faster in this case. ANALYZE helps the query planner to figure out the
+ // faster path, but it may not have up-to-date information yet.
+ conditions.push("+h.visit_count > 0");
+ }
+ if (this.hasBehavior("typed")) {
+ conditions.push("h.typed = 1");
+ }
+ if (this.hasBehavior("bookmark")) {
+ conditions.push("bookmarked");
+ }
+ if (this.hasBehavior("tag")) {
+ conditions.push("tags NOTNULL");
+ }
+
+ return conditions.length ? defaultQuery("AND " + conditions.join(" AND "))
+ : defaultQuery();
+ },
+
+ /**
+ * Obtains the search query to be used based on the previously set search
+ * preferences (accessed by this.hasBehavior).
+ *
+ * @return an array consisting of the correctly optimized query to search the
+ * database with and an object containing the params to bound.
+ */
+ get _searchQuery() {
+ let query = this._suggestionPrefQuery;
+
+ return [
+ query,
+ {
+ parent: PlacesUtils.tagsFolderId,
+ query_type: QUERYTYPE_FILTERED,
+ matchBehavior: this._matchBehavior,
+ searchBehavior: this._behavior,
+ // We only want to search the tokens that we are left with - not the
+ // original search string.
+ searchString: this._searchTokens.join(" "),
+ // Limit the query to the the maximum number of desired results.
+ // This way we can avoid doing more work than needed.
+ maxResults: Prefs.maxRichResults
+ }
+ ];
+ },
+
+ /**
+ * Obtains the query to search for switch-to-tab entries.
+ *
+ * @return an array consisting of the correctly optimized query to search the
+ * database with and an object containing the params to bound.
+ */
+ get _switchToTabQuery() {
+ return [
+ SQL_SWITCHTAB_QUERY,
+ {
+ query_type: QUERYTYPE_FILTERED,
+ matchBehavior: this._matchBehavior,
+ searchBehavior: this._behavior,
+ // We only want to search the tokens that we are left with - not the
+ // original search string.
+ searchString: this._searchTokens.join(" "),
+ maxResults: Prefs.maxRichResults
+ }
+ ];
+ },
+
+ /**
+ * Obtains the query to search for adaptive results.
+ *
+ * @return an array consisting of the correctly optimized query to search the
+ * database with and an object containing the params to bound.
+ */
+ get _adaptiveQuery() {
+ return [
+ SQL_ADAPTIVE_QUERY,
+ {
+ parent: PlacesUtils.tagsFolderId,
+ search_string: this._searchString,
+ query_type: QUERYTYPE_FILTERED,
+ matchBehavior: this._matchBehavior,
+ searchBehavior: this._behavior
+ }
+ ];
+ },
+
+ /**
+ * Whether we should try to autoFill.
+ */
+ get _shouldAutofill() {
+ // First of all, check for the autoFill pref.
+ if (!Prefs.autofill)
+ return false;
+
+ if (this._searchTokens.length != 1)
+ return false;
+
+ // autoFill can only cope with history or bookmarks entries.
+ if (!this.hasBehavior("history") &&
+ !this.hasBehavior("bookmark"))
+ return false;
+
+ // autoFill doesn't search titles or tags.
+ if (this.hasBehavior("title") || this.hasBehavior("tag"))
+ return false;
+
+ // Don't try to autofill if the search term includes any whitespace.
+ // This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH
+ // tokenizer ends up trimming the search string and returning a value
+ // that doesn't match it, or is even shorter.
+ if (REGEXP_SPACES.test(this._originalSearchString))
+ return false;
+
+ if (this._searchString.length == 0)
+ return false;
+
+ if (this._prohibitAutoFill)
+ return false;
+
+ return true;
+ },
+
+ /**
+ * Obtains the query to search for autoFill host results.
+ *
+ * @return an array consisting of the correctly optimized query to search the
+ * database with and an object containing the params to bound.
+ */
+ get _hostQuery() {
+ let typed = Prefs.autofillTyped || this.hasBehavior("typed");
+ let bookmarked = this.hasBehavior("bookmark") && !this.hasBehavior("history");
+
+ let query = [];
+ if (bookmarked) {
+ query.push(typed ? SQL_BOOKMARKED_TYPED_HOST_QUERY
+ : SQL_BOOKMARKED_HOST_QUERY);
+ } else {
+ query.push(typed ? SQL_TYPED_HOST_QUERY
+ : SQL_HOST_QUERY);
+ }
+
+ query.push({
+ query_type: QUERYTYPE_AUTOFILL_HOST,
+ searchString: this._searchString.toLowerCase()
+ });
+
+ return query;
+ },
+
+ /**
+ * Obtains the query to search for autoFill url results.
+ *
+ * @return an array consisting of the correctly optimized query to search the
+ * database with and an object containing the params to bound.
+ */
+ get _urlQuery() {
+ // We expect this to be a full URL, not just a host. We want to extract the
+ // host and use that as a guess for whether we'll get a result from a URL
+ // query.
+ let slashIndex = this._autofillUrlSearchString.indexOf("/");
+ let revHost = this._autofillUrlSearchString.substring(0, slashIndex).toLowerCase()
+ .split("").reverse().join("") + ".";
+
+ let typed = Prefs.autofillTyped || this.hasBehavior("typed");
+ let bookmarked = this.hasBehavior("bookmark") && !this.hasBehavior("history");
+
+ let query = [];
+ if (bookmarked) {
+ query.push(typed ? SQL_BOOKMARKED_TYPED_URL_QUERY
+ : SQL_BOOKMARKED_URL_QUERY);
+ } else {
+ query.push(typed ? SQL_TYPED_URL_QUERY
+ : SQL_URL_QUERY);
+ }
+
+ query.push({
+ query_type: QUERYTYPE_AUTOFILL_URL,
+ searchString: this._autofillUrlSearchString,
+ revHost
+ });
+
+ return query;
+ },
+
+ /**
+ * Notifies the listener about results.
+ *
+ * @param searchOngoing
+ * Indicates whether the search is ongoing.
+ */
+ notifyResults: function (searchOngoing) {
+ let result = this._result;
+ let resultCode = result.matchCount ? "RESULT_SUCCESS" : "RESULT_NOMATCH";
+ if (searchOngoing) {
+ resultCode += "_ONGOING";
+ }
+ result.setSearchResult(Ci.nsIAutoCompleteResult[resultCode]);
+ this._listener.onSearchResult(this._autocompleteSearch, result);
+ },
+}
+
+// UnifiedComplete class
+// component @mozilla.org/autocomplete/search;1?name=unifiedcomplete
+
+function UnifiedComplete() {
+ // Make sure the preferences are initialized as soon as possible.
+ // If the value of browser.urlbar.autocomplete.enabled is set to false,
+ // then all the other suggest preferences for history, bookmarks and
+ // open pages should be set to false.
+ Prefs;
+}
+
+UnifiedComplete.prototype = {
+ // Database handling
+
+ /**
+ * Promise resolved when the database initialization has completed, or null
+ * if it has never been requested.
+ */
+ _promiseDatabase: null,
+
+ /**
+ * Gets a Sqlite database handle.
+ *
+ * @return {Promise}
+ * @resolves to the Sqlite database handle (according to Sqlite.jsm).
+ * @rejects javascript exception.
+ */
+ getDatabaseHandle: function () {
+ if (Prefs.enabled && !this._promiseDatabase) {
+ this._promiseDatabase = Task.spawn(function* () {
+ let conn = yield Sqlite.cloneStorageConnection({
+ connection: PlacesUtils.history.DBConnection,
+ readOnly: true
+ });
+
+ try {
+ Sqlite.shutdown.addBlocker("Places UnifiedComplete.js clone closing",
+ Task.async(function* () {
+ SwitchToTabStorage.shutdown();
+ yield conn.close();
+ }));
+ } catch (ex) {
+ // It's too late to block shutdown, just close the connection.
+ yield conn.close();
+ throw ex;
+ }
+
+ // Autocomplete often fallbacks to a table scan due to lack of text
+ // indices. A larger cache helps reducing IO and improving performance.
+ // The value used here is larger than the default Storage value defined
+ // as MAX_CACHE_SIZE_BYTES in storage/mozStorageConnection.cpp.
+ yield conn.execute("PRAGMA cache_size = -6144"); // 6MiB
+
+ yield SwitchToTabStorage.initDatabase(conn);
+
+ return conn;
+ }.bind(this)).then(null, ex => { dump("Couldn't get database handle: " + ex + "\n");
+ Cu.reportError(ex); });
+ }
+ return this._promiseDatabase;
+ },
+
+ // mozIPlacesAutoComplete
+
+ registerOpenPage: function PAC_registerOpenPage(uri) {
+ SwitchToTabStorage.add(uri);
+ },
+
+ unregisterOpenPage: function PAC_unregisterOpenPage(uri) {
+ SwitchToTabStorage.delete(uri);
+ },
+
+ // nsIAutoCompleteSearch
+
+ startSearch: function (searchString, searchParam, previousResult, listener) {
+ // Stop the search in case the controller has not taken care of it.
+ if (this._currentSearch) {
+ this.stopSearch();
+ }
+
+ // Note: We don't use previousResult to make sure ordering of results are
+ // consistent. See bug 412730 for more details.
+
+ // If the previous search didn't fetch enough search suggestions, it's
+ // unlikely a longer text would do.
+ let prohibitSearchSuggestions =
+ this._lastLowResultsSearchSuggestion &&
+ searchString.length > this._lastLowResultsSearchSuggestion.length &&
+ searchString.startsWith(this._lastLowResultsSearchSuggestion);
+
+ this._currentSearch = new Search(searchString, searchParam, listener,
+ this, this, prohibitSearchSuggestions);
+
+ // If we are not enabled, we need to return now. Notice we need an empty
+ // result regardless, so we still create the Search object.
+ if (!Prefs.enabled) {
+ this.finishSearch(true);
+ return;
+ }
+
+ let search = this._currentSearch;
+ this.getDatabaseHandle().then(conn => search.execute(conn))
+ .then(null, ex => {
+ dump(`Query failed: ${ex}\n`);
+ Cu.reportError(ex);
+ })
+ .then(() => {
+ if (search == this._currentSearch) {
+ this.finishSearch(true);
+ }
+ });
+ },
+
+ stopSearch: function () {
+ if (this._currentSearch) {
+ this._currentSearch.stop();
+ }
+ // Don't notify since we are canceling this search. This also means we
+ // won't fire onSearchComplete for this search.
+ this.finishSearch();
+ },
+
+ /**
+ * Properly cleans up when searching is completed.
+ *
+ * @param notify [optional]
+ * Indicates if we should notify the AutoComplete listener about our
+ * results or not.
+ */
+ finishSearch: function (notify=false) {
+ // Clear state now to avoid race conditions, see below.
+ let search = this._currentSearch;
+ if (!search)
+ return;
+ this._lastLowResultsSearchSuggestion = search._lastLowResultsSearchSuggestion;
+ delete this._currentSearch;
+
+ if (!notify)
+ return;
+
+ // There is a possible race condition here.
+ // When a search completes it calls finishSearch that notifies results
+ // here. When the controller gets the last result it fires
+ // onSearchComplete.
+ // If onSearchComplete immediately starts a new search it will set a new
+ // _currentSearch, and on return the execution will continue here, after
+ // notifyResults.
+ // Thus, ensure that notifyResults is the last call in this method,
+ // otherwise you might be touching the wrong search.
+ search.notifyResults(false);
+ },
+
+ // nsIAutoCompleteSimpleResultListener
+
+ onValueRemoved: function (result, spec, removeFromDB) {
+ if (removeFromDB) {
+ PlacesUtils.history.removePage(NetUtil.newURI(spec));
+ }
+ },
+
+ // nsIAutoCompleteSearchDescriptor
+
+ get searchType() {
+ return Ci.nsIAutoCompleteSearchDescriptor.SEARCH_TYPE_IMMEDIATE;
+ },
+
+ get clearingAutoFillSearchesAgain() {
+ return true;
+ },
+
+ // nsISupports
+
+ classID: Components.ID("f964a319-397a-4d21-8be6-5cdd1ee3e3ae"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(UnifiedComplete),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteSearch,
+ Ci.nsIAutoCompleteSimpleResultListener,
+ Ci.nsIAutoCompleteSearchDescriptor,
+ Ci.mozIPlacesAutoComplete,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference
+ ])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UnifiedComplete]);
diff --git a/components/places/src/VisitInfo.cpp b/components/places/src/VisitInfo.cpp
new file mode 100644
index 000000000..cd3ec2f79
--- /dev/null
+++ b/components/places/src/VisitInfo.cpp
@@ -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 "VisitInfo.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// VisitInfo
+
+VisitInfo::VisitInfo(int64_t aVisitId,
+ PRTime aVisitDate,
+ uint32_t aTransitionType,
+ already_AddRefed<nsIURI> aReferrer)
+: mVisitId(aVisitId)
+, mVisitDate(aVisitDate)
+, mTransitionType(aTransitionType)
+, mReferrer(aReferrer)
+{
+}
+
+VisitInfo::~VisitInfo()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIVisitInfo
+
+NS_IMETHODIMP
+VisitInfo::GetVisitId(int64_t* _visitId)
+{
+ *_visitId = mVisitId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+VisitInfo::GetVisitDate(PRTime* _visitDate)
+{
+ *_visitDate = mVisitDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+VisitInfo::GetTransitionType(uint32_t* _transitionType)
+{
+ *_transitionType = mTransitionType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+VisitInfo::GetReferrerURI(nsIURI** _referrer)
+{
+ NS_IF_ADDREF(*_referrer = mReferrer);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+NS_IMPL_ISUPPORTS(
+ VisitInfo
+, mozIVisitInfo
+)
+
+} // namespace places
+} // namespace mozilla
diff --git a/components/places/src/VisitInfo.h b/components/places/src/VisitInfo.h
new file mode 100644
index 000000000..54b25c686
--- /dev/null
+++ b/components/places/src/VisitInfo.h
@@ -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/. */
+
+#ifndef mozilla_places_VisitInfo_h__
+#define mozilla_places_VisitInfo_h__
+
+#include "mozIAsyncHistory.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+
+class nsIURI;
+
+namespace mozilla {
+namespace places {
+
+class VisitInfo final : public mozIVisitInfo
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZIVISITINFO
+
+ VisitInfo(int64_t aVisitId, PRTime aVisitDate, uint32_t aTransitionType,
+ already_AddRefed<nsIURI> aReferrer);
+
+private:
+ ~VisitInfo();
+ const int64_t mVisitId;
+ const PRTime mVisitDate;
+ const uint32_t mTransitionType;
+ nsCOMPtr<nsIURI> mReferrer;
+};
+
+} // namespace places
+} // namespace mozilla
+
+#endif // mozilla_places_VisitInfo_h__
diff --git a/components/places/src/nsAnnoProtocolHandler.cpp b/components/places/src/nsAnnoProtocolHandler.cpp
new file mode 100644
index 000000000..b98942e33
--- /dev/null
+++ b/components/places/src/nsAnnoProtocolHandler.cpp
@@ -0,0 +1,367 @@
+//* -*- 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/. */
+
+/**
+ * Implementation of moz-anno: URLs for accessing favicons. The urls are sent
+ * to the favicon service. If the favicon service doesn't have the
+ * data, a stream containing the default favicon will be returned.
+ *
+ * The reference to annotations ("moz-anno") is a leftover from previous
+ * iterations of this component. As of now the moz-anno protocol is independent
+ * of annotations.
+ */
+
+#include "nsAnnoProtocolHandler.h"
+#include "nsFaviconService.h"
+#include "nsIChannel.h"
+#include "nsIInputStreamChannel.h"
+#include "nsILoadGroup.h"
+#include "nsIStandardURL.h"
+#include "nsIStringStream.h"
+#include "nsISupportsUtils.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsIOutputStream.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStringStream.h"
+#include "mozilla/storage.h"
+#include "nsIPipe.h"
+#include "Helpers.h"
+
+using namespace mozilla;
+using namespace mozilla::places;
+
+////////////////////////////////////////////////////////////////////////////////
+//// Global Functions
+
+/**
+ * Creates a channel to obtain the default favicon.
+ */
+static
+nsresult
+GetDefaultIcon(nsILoadInfo *aLoadInfo, nsIChannel **aChannel)
+{
+ nsCOMPtr<nsIURI> defaultIconURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(defaultIconURI),
+ NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_NewChannelInternal(aChannel, defaultIconURI, aLoadInfo);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// faviconAsyncLoader
+
+namespace {
+
+/**
+ * An instance of this class is passed to the favicon service as the callback
+ * for getting favicon data from the database. We'll get this data back in
+ * HandleResult, and on HandleCompletion, we'll close our output stream which
+ * will close the original channel for the favicon request.
+ *
+ * However, if an error occurs at any point, we do not set mReturnDefaultIcon to
+ * false, so we will open up another channel to get the default favicon, and
+ * pass that along to our output stream in HandleCompletion. If anything
+ * happens at that point, the world must be against us, so we return nothing.
+ */
+class faviconAsyncLoader : public AsyncStatementCallback
+ , public nsIRequestObserver
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ faviconAsyncLoader(nsIChannel *aChannel, nsIOutputStream *aOutputStream) :
+ mChannel(aChannel)
+ , mOutputStream(aOutputStream)
+ , mReturnDefaultIcon(true)
+ {
+ NS_ASSERTION(aChannel,
+ "Not providing a channel will result in crashes!");
+ NS_ASSERTION(aOutputStream,
+ "Not providing an output stream will result in crashes!");
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// mozIStorageStatementCallback
+
+ NS_IMETHOD HandleResult(mozIStorageResultSet *aResultSet) override
+ {
+ // We will only get one row back in total, so we do not need to loop.
+ nsCOMPtr<mozIStorageRow> row;
+ nsresult rv = aResultSet->GetNextRow(getter_AddRefs(row));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We do not allow favicons without a MIME type, so we'll return the default
+ // icon.
+ nsAutoCString mimeType;
+ (void)row->GetUTF8String(1, mimeType);
+ NS_ENSURE_FALSE(mimeType.IsEmpty(), NS_OK);
+
+ // Set our mimeType now that we know it.
+ rv = mChannel->SetContentType(mimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Obtain the binary blob that contains our favicon data.
+ uint8_t *favicon;
+ uint32_t size = 0;
+ rv = row->GetBlob(0, &size, &favicon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t totalWritten = 0;
+ do {
+ uint32_t bytesWritten;
+ rv = mOutputStream->Write(
+ &(reinterpret_cast<const char *>(favicon)[totalWritten]),
+ size - totalWritten,
+ &bytesWritten
+ );
+ if (NS_FAILED(rv) || !bytesWritten)
+ break;
+ totalWritten += bytesWritten;
+ } while (size != totalWritten);
+ NS_ASSERTION(NS_FAILED(rv) || size == totalWritten,
+ "Failed to write all of our data out to the stream!");
+
+ // Free our favicon array.
+ free(favicon);
+
+ // Handle an error to write if it occurred, but only after we've freed our
+ // favicon.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // At this point, we should have written out all of our data to our stream.
+ // HandleCompletion will close the output stream, so we are done here.
+ mReturnDefaultIcon = false;
+ return NS_OK;
+ }
+
+ NS_IMETHOD HandleCompletion(uint16_t aReason) override
+ {
+ if (!mReturnDefaultIcon)
+ return mOutputStream->Close();
+
+ // We need to return our default icon, so we'll open up a new channel to get
+ // that data, and push it to our output stream. If at any point we get an
+ // error, we can't do anything, so we'll just close our output stream.
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = NS_NewSimpleStreamListener(getter_AddRefs(listener),
+ mOutputStream, this);
+ NS_ENSURE_SUCCESS(rv, mOutputStream->Close());
+
+ // we should pass the loadInfo of the original channel along
+ // to the new channel. Note that mChannel can not be null,
+ // constructor checks that.
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = GetDefaultIcon(loadInfo, getter_AddRefs(newChannel));
+ NS_ENSURE_SUCCESS(rv, mOutputStream->Close());
+
+ rv = newChannel->AsyncOpen2(listener);
+ NS_ENSURE_SUCCESS(rv, mOutputStream->Close());
+
+ return NS_OK;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIRequestObserver
+
+ NS_IMETHOD OnStartRequest(nsIRequest *, nsISupports *) override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest *, nsISupports *, nsresult aStatusCode) override
+ {
+ // We always need to close our output stream, regardless of the status code.
+ (void)mOutputStream->Close();
+
+ // But, we'll warn about it not being successful if it wasn't.
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(aStatusCode),
+ "Got an error when trying to load our default favicon!");
+
+ return NS_OK;
+ }
+
+protected:
+ virtual ~faviconAsyncLoader() {}
+
+private:
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ bool mReturnDefaultIcon;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(
+ faviconAsyncLoader,
+ AsyncStatementCallback,
+ nsIRequestObserver
+)
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsAnnoProtocolHandler
+
+NS_IMPL_ISUPPORTS(nsAnnoProtocolHandler, nsIProtocolHandler)
+
+// nsAnnoProtocolHandler::GetScheme
+
+NS_IMETHODIMP
+nsAnnoProtocolHandler::GetScheme(nsACString& aScheme)
+{
+ aScheme.AssignLiteral("moz-anno");
+ return NS_OK;
+}
+
+
+// nsAnnoProtocolHandler::GetDefaultPort
+//
+// There is no default port for annotation URLs
+
+NS_IMETHODIMP
+nsAnnoProtocolHandler::GetDefaultPort(int32_t *aDefaultPort)
+{
+ *aDefaultPort = -1;
+ return NS_OK;
+}
+
+
+// nsAnnoProtocolHandler::GetProtocolFlags
+
+NS_IMETHODIMP
+nsAnnoProtocolHandler::GetProtocolFlags(uint32_t *aProtocolFlags)
+{
+ *aProtocolFlags = (URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD |
+ URI_IS_LOCAL_RESOURCE);
+ return NS_OK;
+}
+
+
+// nsAnnoProtocolHandler::NewURI
+
+NS_IMETHODIMP
+nsAnnoProtocolHandler::NewURI(const nsACString& aSpec,
+ const char *aOriginCharset,
+ nsIURI *aBaseURI, nsIURI **_retval)
+{
+ nsCOMPtr <nsIURI> uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID);
+ if (!uri)
+ return NS_ERROR_OUT_OF_MEMORY;
+ nsresult rv = uri->SetSpec(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = nullptr;
+ uri.swap(*_retval);
+ return NS_OK;
+}
+
+
+// nsAnnoProtocolHandler::NewChannel
+//
+
+NS_IMETHODIMP
+nsAnnoProtocolHandler::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** _retval)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ // annotation info
+ nsCOMPtr<nsIURI> annoURI;
+ nsAutoCString annoName;
+ nsresult rv = ParseAnnoURI(aURI, getter_AddRefs(annoURI), annoName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only favicon annotation are supported.
+ if (!annoName.EqualsLiteral(FAVICON_ANNOTATION_NAME))
+ return NS_ERROR_INVALID_ARG;
+
+ return NewFaviconChannel(aURI, annoURI, aLoadInfo, _retval);
+}
+
+NS_IMETHODIMP
+nsAnnoProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval)
+{
+ return NewChannel2(aURI, nullptr, _retval);
+}
+
+
+// nsAnnoProtocolHandler::AllowPort
+//
+// Don't override any bans on bad ports.
+
+NS_IMETHODIMP
+nsAnnoProtocolHandler::AllowPort(int32_t port, const char *scheme,
+ bool *_retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+
+// nsAnnoProtocolHandler::ParseAnnoURI
+//
+// Splits an annotation URL into its URI and name parts
+
+nsresult
+nsAnnoProtocolHandler::ParseAnnoURI(nsIURI* aURI,
+ nsIURI** aResultURI, nsCString& aName)
+{
+ nsresult rv;
+ nsAutoCString path;
+ rv = aURI->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t firstColon = path.FindChar(':');
+ if (firstColon <= 0)
+ return NS_ERROR_MALFORMED_URI;
+
+ rv = NS_NewURI(aResultURI, Substring(path, firstColon + 1));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aName = Substring(path, 0, firstColon);
+ return NS_OK;
+}
+
+nsresult
+nsAnnoProtocolHandler::NewFaviconChannel(nsIURI *aURI, nsIURI *aAnnotationURI,
+ nsILoadInfo* aLoadInfo, nsIChannel **_channel)
+{
+ // Create our pipe. This will give us our input stream and output stream
+ // that will be written to when we get data from the database.
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsresult rv = NS_NewPipe(getter_AddRefs(inputStream),
+ getter_AddRefs(outputStream),
+ 0, nsIFaviconService::MAX_FAVICON_BUFFER_SIZE,
+ true, true);
+ NS_ENSURE_SUCCESS(rv, GetDefaultIcon(aLoadInfo, _channel));
+
+ // Create our channel. We'll call SetContentType with the right type when
+ // we know what it actually is.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel),
+ aURI,
+ inputStream,
+ EmptyCString(), // aContentType
+ EmptyCString(), // aContentCharset
+ aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, GetDefaultIcon(aLoadInfo, _channel));
+
+ // Now we go ahead and get our data asynchronously for the favicon.
+ nsCOMPtr<mozIStorageStatementCallback> callback =
+ new faviconAsyncLoader(channel, outputStream);
+ NS_ENSURE_TRUE(callback, GetDefaultIcon(aLoadInfo, _channel));
+ nsFaviconService* faviconService = nsFaviconService::GetFaviconService();
+ NS_ENSURE_TRUE(faviconService, GetDefaultIcon(aLoadInfo, _channel));
+
+ rv = faviconService->GetFaviconDataAsync(aAnnotationURI, callback);
+ NS_ENSURE_SUCCESS(rv, GetDefaultIcon(aLoadInfo, _channel));
+
+ channel.forget(_channel);
+ return NS_OK;
+}
diff --git a/components/places/src/nsAnnoProtocolHandler.h b/components/places/src/nsAnnoProtocolHandler.h
new file mode 100644
index 000000000..8e543c7c5
--- /dev/null
+++ b/components/places/src/nsAnnoProtocolHandler.h
@@ -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/. */
+
+#ifndef nsAnnoProtocolHandler_h___
+#define nsAnnoProtocolHandler_h___
+
+#include "nsCOMPtr.h"
+#include "nsIProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+// {e8b8bdb7-c96c-4d82-9c6f-2b3c585ec7ea}
+#define NS_ANNOPROTOCOLHANDLER_CID \
+{ 0xe8b8bdb7, 0xc96c, 0x4d82, { 0x9c, 0x6f, 0x2b, 0x3c, 0x58, 0x5e, 0xc7, 0xea } }
+
+class nsAnnoProtocolHandler final : public nsIProtocolHandler, public nsSupportsWeakReference
+{
+public:
+ nsAnnoProtocolHandler() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+private:
+ ~nsAnnoProtocolHandler() {}
+
+protected:
+ nsresult ParseAnnoURI(nsIURI* aURI, nsIURI** aResultURI, nsCString& aName);
+
+ /**
+ * Obtains a new channel to be used to get a favicon from the database. This
+ * method is asynchronous.
+ *
+ * @param aURI
+ * The URI the channel will be created for. This is the URI that is
+ * set as the original URI on the channel.
+ * @param aAnnotationURI
+ * The URI that holds the data needed to get the favicon from the
+ * database.
+ * @param aLoadInfo
+ * The loadinfo that requested the resource load.
+ * @returns (via _channel) the channel that will obtain the favicon data.
+ */
+ nsresult NewFaviconChannel(nsIURI *aURI,
+ nsIURI *aAnnotationURI,
+ nsILoadInfo *aLoadInfo,
+ nsIChannel **_channel);
+};
+
+#endif /* nsAnnoProtocolHandler_h___ */
diff --git a/components/places/src/nsAnnotationService.cpp b/components/places/src/nsAnnotationService.cpp
new file mode 100644
index 000000000..9d62bd34a
--- /dev/null
+++ b/components/places/src/nsAnnotationService.cpp
@@ -0,0 +1,1990 @@
+/* -*- 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 "mozilla/ArrayUtils.h"
+
+#include "nsAnnotationService.h"
+#include "nsNavHistory.h"
+#include "nsPlacesTables.h"
+#include "nsPlacesIndexes.h"
+#include "nsPlacesMacros.h"
+#include "Helpers.h"
+
+#include "nsNetUtil.h"
+#include "nsIVariant.h"
+#include "nsString.h"
+#include "nsVariant.h"
+#include "mozilla/storage.h"
+
+#include "GeckoProfiler.h"
+
+#include "nsNetCID.h"
+
+using namespace mozilla;
+using namespace mozilla::places;
+
+#define ENSURE_ANNO_TYPE(_type, _statement) \
+ PR_BEGIN_MACRO \
+ int32_t type = _statement->AsInt32(kAnnoIndex_Type); \
+ NS_ENSURE_TRUE(type == nsIAnnotationService::_type, NS_ERROR_INVALID_ARG); \
+ PR_END_MACRO
+
+#define NOTIFY_ANNOS_OBSERVERS(_notification) \
+ PR_BEGIN_MACRO \
+ for (int32_t i = 0; i < mObservers.Count(); i++) \
+ mObservers[i]->_notification; \
+ PR_END_MACRO
+
+const int32_t nsAnnotationService::kAnnoIndex_ID = 0;
+const int32_t nsAnnotationService::kAnnoIndex_PageOrItem = 1;
+const int32_t nsAnnotationService::kAnnoIndex_NameID = 2;
+const int32_t nsAnnotationService::kAnnoIndex_Content = 3;
+const int32_t nsAnnotationService::kAnnoIndex_Flags = 4;
+const int32_t nsAnnotationService::kAnnoIndex_Expiration = 5;
+const int32_t nsAnnotationService::kAnnoIndex_Type = 6;
+const int32_t nsAnnotationService::kAnnoIndex_DateAdded = 7;
+const int32_t nsAnnotationService::kAnnoIndex_LastModified = 8;
+
+namespace mozilla {
+namespace places {
+
+////////////////////////////////////////////////////////////////////////////////
+//// AnnotatedResult
+
+AnnotatedResult::AnnotatedResult(const nsCString& aGUID,
+ nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aAnnotationName,
+ nsIVariant* aAnnotationValue)
+: mGUID(aGUID)
+, mURI(aURI)
+, mItemId(aItemId)
+, mAnnotationName(aAnnotationName)
+, mAnnotationValue(aAnnotationValue)
+{
+}
+
+AnnotatedResult::~AnnotatedResult()
+{
+}
+
+NS_IMETHODIMP
+AnnotatedResult::GetGuid(nsACString& _guid)
+{
+ _guid = mGUID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AnnotatedResult::GetUri(nsIURI** _uri)
+{
+ NS_IF_ADDREF(*_uri = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AnnotatedResult::GetItemId(int64_t* _itemId)
+{
+ *_itemId = mItemId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AnnotatedResult::GetAnnotationName(nsACString& _annotationName)
+{
+ _annotationName = mAnnotationName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AnnotatedResult::GetAnnotationValue(nsIVariant** _annotationValue)
+{
+ NS_IF_ADDREF(*_annotationValue = mAnnotationValue);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(AnnotatedResult, mozIAnnotatedResult)
+
+} // namespace places
+} // namespace mozilla
+
+PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsAnnotationService, gAnnotationService)
+
+NS_IMPL_ISUPPORTS(nsAnnotationService
+, nsIAnnotationService
+, nsIObserver
+, nsISupportsWeakReference
+)
+
+
+nsAnnotationService::nsAnnotationService()
+ : mHasSessionAnnotations(false)
+{
+ NS_ASSERTION(!gAnnotationService,
+ "Attempting to create two instances of the service!");
+ gAnnotationService = this;
+}
+
+
+nsAnnotationService::~nsAnnotationService()
+{
+ NS_ASSERTION(gAnnotationService == this,
+ "Deleting a non-singleton instance of the service");
+ if (gAnnotationService == this)
+ gAnnotationService = nullptr;
+}
+
+
+nsresult
+nsAnnotationService::Init()
+{
+ mDB = Database::GetDatabase();
+ NS_ENSURE_STATE(mDB);
+
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ (void)obsSvc->AddObserver(this, TOPIC_PLACES_SHUTDOWN, true);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsAnnotationService::SetAnnotationStringInternal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ const nsAString& aValue,
+ int32_t aFlags,
+ uint16_t aExpiration)
+{
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartSetAnnotation(aURI, aItemId, aName, aFlags, aExpiration,
+ nsIAnnotationService::TYPE_STRING,
+ statement);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozStorageStatementScoper scoper(statement);
+
+ rv = statement->BindStringByName(NS_LITERAL_CSTRING("content"), aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::SetPageAnnotation(nsIURI* aURI,
+ const nsACString& aName,
+ nsIVariant* aValue,
+ int32_t aFlags,
+ uint16_t aExpiration)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG(aValue);
+
+ uint16_t dataType;
+ nsresult rv = aValue->GetDataType(&dataType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (dataType) {
+ case nsIDataType::VTYPE_INT8:
+ case nsIDataType::VTYPE_UINT8:
+ case nsIDataType::VTYPE_INT16:
+ case nsIDataType::VTYPE_UINT16:
+ case nsIDataType::VTYPE_INT32:
+ case nsIDataType::VTYPE_UINT32:
+ case nsIDataType::VTYPE_BOOL: {
+ int32_t valueInt;
+ rv = aValue->GetAsInt32(&valueInt);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetPageAnnotationInt32(aURI, aName, valueInt, aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ // Fall through int64_t case otherwise.
+ MOZ_FALLTHROUGH;
+ }
+ case nsIDataType::VTYPE_INT64:
+ case nsIDataType::VTYPE_UINT64: {
+ int64_t valueLong;
+ rv = aValue->GetAsInt64(&valueLong);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetPageAnnotationInt64(aURI, aName, valueLong, aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ // Fall through double case otherwise.
+ MOZ_FALLTHROUGH;
+ }
+ case nsIDataType::VTYPE_FLOAT:
+ case nsIDataType::VTYPE_DOUBLE: {
+ double valueDouble;
+ rv = aValue->GetAsDouble(&valueDouble);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetPageAnnotationDouble(aURI, aName, valueDouble, aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ case nsIDataType::VTYPE_CHAR:
+ case nsIDataType::VTYPE_WCHAR:
+ case nsIDataType::VTYPE_DOMSTRING:
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ case nsIDataType::VTYPE_UTF8STRING:
+ case nsIDataType::VTYPE_CSTRING:
+ case nsIDataType::VTYPE_ASTRING: {
+ nsAutoString stringValue;
+ rv = aValue->GetAsAString(stringValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetPageAnnotationString(aURI, aName, stringValue, aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::SetItemAnnotation(int64_t aItemId,
+ const nsACString& aName,
+ nsIVariant* aValue,
+ int32_t aFlags,
+ uint16_t aExpiration,
+ uint16_t aSource)
+{
+ PROFILER_LABEL("AnnotationService", "SetItemAnnotation",
+ js::ProfileEntry::Category::OTHER);
+
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG(aValue);
+
+ if (aExpiration == EXPIRE_WITH_HISTORY)
+ return NS_ERROR_INVALID_ARG;
+
+ uint16_t dataType;
+ nsresult rv = aValue->GetDataType(&dataType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (dataType) {
+ case nsIDataType::VTYPE_INT8:
+ case nsIDataType::VTYPE_UINT8:
+ case nsIDataType::VTYPE_INT16:
+ case nsIDataType::VTYPE_UINT16:
+ case nsIDataType::VTYPE_INT32:
+ case nsIDataType::VTYPE_UINT32:
+ case nsIDataType::VTYPE_BOOL: {
+ int32_t valueInt;
+ rv = aValue->GetAsInt32(&valueInt);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetItemAnnotationInt32(aItemId, aName, valueInt, aFlags, aExpiration, aSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ // Fall through int64_t case otherwise.
+ MOZ_FALLTHROUGH;
+ }
+ case nsIDataType::VTYPE_INT64:
+ case nsIDataType::VTYPE_UINT64: {
+ int64_t valueLong;
+ rv = aValue->GetAsInt64(&valueLong);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetItemAnnotationInt64(aItemId, aName, valueLong, aFlags, aExpiration, aSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ // Fall through double case otherwise.
+ MOZ_FALLTHROUGH;
+ }
+ case nsIDataType::VTYPE_FLOAT:
+ case nsIDataType::VTYPE_DOUBLE: {
+ double valueDouble;
+ rv = aValue->GetAsDouble(&valueDouble);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetItemAnnotationDouble(aItemId, aName, valueDouble, aFlags, aExpiration, aSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ case nsIDataType::VTYPE_CHAR:
+ case nsIDataType::VTYPE_WCHAR:
+ case nsIDataType::VTYPE_DOMSTRING:
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ case nsIDataType::VTYPE_UTF8STRING:
+ case nsIDataType::VTYPE_CSTRING:
+ case nsIDataType::VTYPE_ASTRING: {
+ nsAutoString stringValue;
+ rv = aValue->GetAsAString(stringValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetItemAnnotationString(aItemId, aName, stringValue, aFlags, aExpiration, aSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::SetPageAnnotationString(nsIURI* aURI,
+ const nsACString& aName,
+ const nsAString& aValue,
+ int32_t aFlags,
+ uint16_t aExpiration)
+{
+ NS_ENSURE_ARG(aURI);
+
+ nsresult rv = SetAnnotationStringInternal(aURI, 0, aName, aValue,
+ aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationSet(aURI, aName));
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::SetItemAnnotationString(int64_t aItemId,
+ const nsACString& aName,
+ const nsAString& aValue,
+ int32_t aFlags,
+ uint16_t aExpiration,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ if (aExpiration == EXPIRE_WITH_HISTORY)
+ return NS_ERROR_INVALID_ARG;
+
+ nsresult rv = SetAnnotationStringInternal(nullptr, aItemId, aName, aValue,
+ aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource));
+
+ return NS_OK;
+}
+
+
+nsresult
+nsAnnotationService::SetAnnotationInt32Internal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ int32_t aValue,
+ int32_t aFlags,
+ uint16_t aExpiration)
+{
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartSetAnnotation(aURI, aItemId, aName, aFlags, aExpiration,
+ nsIAnnotationService::TYPE_INT32,
+ statement);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozStorageStatementScoper scoper(statement);
+
+ rv = statement->BindInt32ByName(NS_LITERAL_CSTRING("content"), aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::SetPageAnnotationInt32(nsIURI* aURI,
+ const nsACString& aName,
+ int32_t aValue,
+ int32_t aFlags,
+ uint16_t aExpiration)
+{
+ NS_ENSURE_ARG(aURI);
+
+ nsresult rv = SetAnnotationInt32Internal(aURI, 0, aName, aValue,
+ aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationSet(aURI, aName));
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::SetItemAnnotationInt32(int64_t aItemId,
+ const nsACString& aName,
+ int32_t aValue,
+ int32_t aFlags,
+ uint16_t aExpiration,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ if (aExpiration == EXPIRE_WITH_HISTORY)
+ return NS_ERROR_INVALID_ARG;
+
+ nsresult rv = SetAnnotationInt32Internal(nullptr, aItemId, aName, aValue,
+ aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource));
+
+ return NS_OK;
+}
+
+
+nsresult
+nsAnnotationService::SetAnnotationInt64Internal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ int64_t aValue,
+ int32_t aFlags,
+ uint16_t aExpiration)
+{
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartSetAnnotation(aURI, aItemId, aName, aFlags, aExpiration,
+ nsIAnnotationService::TYPE_INT64,
+ statement);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozStorageStatementScoper scoper(statement);
+
+ rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("content"), aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::SetPageAnnotationInt64(nsIURI* aURI,
+ const nsACString& aName,
+ int64_t aValue,
+ int32_t aFlags,
+ uint16_t aExpiration)
+{
+ NS_ENSURE_ARG(aURI);
+
+ nsresult rv = SetAnnotationInt64Internal(aURI, 0, aName, aValue,
+ aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationSet(aURI, aName));
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::SetItemAnnotationInt64(int64_t aItemId,
+ const nsACString& aName,
+ int64_t aValue,
+ int32_t aFlags,
+ uint16_t aExpiration,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ if (aExpiration == EXPIRE_WITH_HISTORY)
+ return NS_ERROR_INVALID_ARG;
+
+ nsresult rv = SetAnnotationInt64Internal(nullptr, aItemId, aName, aValue,
+ aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource));
+
+ return NS_OK;
+}
+
+
+nsresult
+nsAnnotationService::SetAnnotationDoubleInternal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ double aValue,
+ int32_t aFlags,
+ uint16_t aExpiration)
+{
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartSetAnnotation(aURI, aItemId, aName, aFlags, aExpiration,
+ nsIAnnotationService::TYPE_DOUBLE,
+ statement);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mozStorageStatementScoper scoper(statement);
+
+ rv = statement->BindDoubleByName(NS_LITERAL_CSTRING("content"), aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::SetPageAnnotationDouble(nsIURI* aURI,
+ const nsACString& aName,
+ double aValue,
+ int32_t aFlags,
+ uint16_t aExpiration)
+{
+ NS_ENSURE_ARG(aURI);
+
+ nsresult rv = SetAnnotationDoubleInternal(aURI, 0, aName, aValue,
+ aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationSet(aURI, aName));
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::SetItemAnnotationDouble(int64_t aItemId,
+ const nsACString& aName,
+ double aValue,
+ int32_t aFlags,
+ uint16_t aExpiration,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ if (aExpiration == EXPIRE_WITH_HISTORY)
+ return NS_ERROR_INVALID_ARG;
+
+ nsresult rv = SetAnnotationDoubleInternal(nullptr, aItemId, aName, aValue,
+ aFlags, aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAnnotationService::GetPageAnnotationString(nsIURI* aURI,
+ const nsACString& aName,
+ nsAString& _retval)
+{
+ NS_ENSURE_ARG(aURI);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(aURI, 0, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ ENSURE_ANNO_TYPE(TYPE_STRING, statement);
+ rv = statement->GetString(kAnnoIndex_Content, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetItemAnnotationString(int64_t aItemId,
+ const nsACString& aName,
+ nsAString& _retval)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ ENSURE_ANNO_TYPE(TYPE_STRING, statement);
+ rv = statement->GetString(kAnnoIndex_Content, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetPageAnnotation(nsIURI* aURI,
+ const nsACString& aName,
+ nsIVariant** _retval)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(aURI, 0, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+
+ nsCOMPtr<nsIWritableVariant> value = new nsVariant();
+ int32_t type = statement->AsInt32(kAnnoIndex_Type);
+ switch (type) {
+ case nsIAnnotationService::TYPE_INT32:
+ case nsIAnnotationService::TYPE_INT64:
+ case nsIAnnotationService::TYPE_DOUBLE: {
+ rv = value->SetAsDouble(statement->AsDouble(kAnnoIndex_Content));
+ break;
+ }
+ case nsIAnnotationService::TYPE_STRING: {
+ nsAutoString valueString;
+ rv = statement->GetString(kAnnoIndex_Content, valueString);
+ if (NS_SUCCEEDED(rv))
+ rv = value->SetAsAString(valueString);
+ break;
+ }
+ default: {
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ value.forget(_retval);
+ }
+
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetItemAnnotation(int64_t aItemId,
+ const nsACString& aName,
+ nsIVariant** _retval)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+
+ nsCOMPtr<nsIWritableVariant> value = new nsVariant();
+ int32_t type = statement->AsInt32(kAnnoIndex_Type);
+ switch (type) {
+ case nsIAnnotationService::TYPE_INT32:
+ case nsIAnnotationService::TYPE_INT64:
+ case nsIAnnotationService::TYPE_DOUBLE: {
+ rv = value->SetAsDouble(statement->AsDouble(kAnnoIndex_Content));
+ break;
+ }
+ case nsIAnnotationService::TYPE_STRING: {
+ nsAutoString valueString;
+ rv = statement->GetString(kAnnoIndex_Content, valueString);
+ if (NS_SUCCEEDED(rv))
+ rv = value->SetAsAString(valueString);
+ break;
+ }
+ default: {
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ value.forget(_retval);
+ }
+
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetPageAnnotationInt32(nsIURI* aURI,
+ const nsACString& aName,
+ int32_t* _retval)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(aURI, 0, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ ENSURE_ANNO_TYPE(TYPE_INT32, statement);
+ *_retval = statement->AsInt32(kAnnoIndex_Content);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetItemAnnotationInt32(int64_t aItemId,
+ const nsACString& aName,
+ int32_t* _retval)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ ENSURE_ANNO_TYPE(TYPE_INT32, statement);
+ *_retval = statement->AsInt32(kAnnoIndex_Content);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetPageAnnotationInt64(nsIURI* aURI,
+ const nsACString& aName,
+ int64_t* _retval)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(aURI, 0, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ ENSURE_ANNO_TYPE(TYPE_INT64, statement);
+ *_retval = statement->AsInt64(kAnnoIndex_Content);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetItemAnnotationInt64(int64_t aItemId,
+ const nsACString& aName,
+ int64_t* _retval)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ ENSURE_ANNO_TYPE(TYPE_INT64, statement);
+ *_retval = statement->AsInt64(kAnnoIndex_Content);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetPageAnnotationType(nsIURI* aURI,
+ const nsACString& aName,
+ uint16_t* _retval)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(aURI, 0, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ *_retval = statement->AsInt32(kAnnoIndex_Type);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetItemAnnotationType(int64_t aItemId,
+ const nsACString& aName,
+ uint16_t* _retval)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ *_retval = statement->AsInt32(kAnnoIndex_Type);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetPageAnnotationDouble(nsIURI* aURI,
+ const nsACString& aName,
+ double* _retval)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(aURI, 0, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ ENSURE_ANNO_TYPE(TYPE_DOUBLE, statement);
+ *_retval = statement->AsDouble(kAnnoIndex_Content);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetItemAnnotationDouble(int64_t aItemId,
+ const nsACString& aName,
+ double* _retval)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ ENSURE_ANNO_TYPE(TYPE_DOUBLE, statement);
+ *_retval = statement->AsDouble(kAnnoIndex_Content);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetPageAnnotationInfo(nsIURI* aURI,
+ const nsACString& aName,
+ int32_t* _flags,
+ uint16_t* _expiration,
+ uint16_t* _storageType)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(_flags);
+ NS_ENSURE_ARG_POINTER(_expiration);
+ NS_ENSURE_ARG_POINTER(_storageType);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(aURI, 0, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ *_flags = statement->AsInt32(kAnnoIndex_Flags);
+ *_expiration = (uint16_t)statement->AsInt32(kAnnoIndex_Expiration);
+ int32_t type = (uint16_t)statement->AsInt32(kAnnoIndex_Type);
+ if (type == 0) {
+ // For annotations created before explicit typing,
+ // we can't determine type, just return as string type.
+ *_storageType = nsIAnnotationService::TYPE_STRING;
+ }
+ else
+ *_storageType = type;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetItemAnnotationInfo(int64_t aItemId,
+ const nsACString& aName,
+ int32_t* _flags,
+ uint16_t* _expiration,
+ uint16_t* _storageType)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_flags);
+ NS_ENSURE_ARG_POINTER(_expiration);
+ NS_ENSURE_ARG_POINTER(_storageType);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv = StartGetAnnotation(nullptr, aItemId, aName, statement);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mozStorageStatementScoper scoper(statement);
+ *_flags = statement->AsInt32(kAnnoIndex_Flags);
+ *_expiration = (uint16_t)statement->AsInt32(kAnnoIndex_Expiration);
+ int32_t type = (uint16_t)statement->AsInt32(kAnnoIndex_Type);
+ if (type == 0) {
+ // For annotations created before explicit typing,
+ // we can't determine type, just return as string type.
+ *_storageType = nsIAnnotationService::TYPE_STRING;
+ }
+ else {
+ *_storageType = type;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetPagesWithAnnotation(const nsACString& aName,
+ uint32_t* _resultCount,
+ nsIURI*** _results)
+{
+ NS_ENSURE_TRUE(!aName.IsEmpty(), NS_ERROR_INVALID_ARG);
+ NS_ENSURE_ARG_POINTER(_resultCount);
+ NS_ENSURE_ARG_POINTER(_results);
+
+ *_resultCount = 0;
+ *_results = nullptr;
+ nsCOMArray<nsIURI> results;
+
+ nsresult rv = GetPagesWithAnnotationCOMArray(aName, &results);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Convert to raw array.
+ if (results.Count() == 0)
+ return NS_OK;
+
+ *_resultCount = results.Count();
+ results.Forget(_results);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsAnnotationService::GetPagesWithAnnotationCOMArray(const nsACString& aName,
+ nsCOMArray<nsIURI>* _results)
+{
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT h.url "
+ "FROM moz_anno_attributes n "
+ "JOIN moz_annos a ON n.id = a.anno_attribute_id "
+ "JOIN moz_places h ON h.id = a.place_id "
+ "WHERE n.name = :anno_name"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasMore)) &&
+ hasMore) {
+ nsAutoCString uristring;
+ rv = stmt->GetUTF8String(0, uristring);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // convert to a URI, in case of some invalid URI, just ignore this row
+ // so we can mostly continue.
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uristring);
+ if (NS_FAILED(rv))
+ continue;
+
+ bool added = _results->AppendObject(uri);
+ NS_ENSURE_TRUE(added, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetItemsWithAnnotation(const nsACString& aName,
+ uint32_t* _resultCount,
+ int64_t** _results)
+{
+ NS_ENSURE_TRUE(!aName.IsEmpty(), NS_ERROR_INVALID_ARG);
+ NS_ENSURE_ARG_POINTER(_resultCount);
+ NS_ENSURE_ARG_POINTER(_results);
+
+ *_resultCount = 0;
+ *_results = nullptr;
+ nsTArray<int64_t> results;
+
+ nsresult rv = GetItemsWithAnnotationTArray(aName, &results);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Convert to raw array.
+ if (results.Length() == 0)
+ return NS_OK;
+
+ *_results = static_cast<int64_t*>
+ (moz_xmalloc(results.Length() * sizeof(int64_t)));
+ NS_ENSURE_TRUE(*_results, NS_ERROR_OUT_OF_MEMORY);
+
+ *_resultCount = results.Length();
+ for (uint32_t i = 0; i < *_resultCount; i ++) {
+ (*_results)[i] = results[i];
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetAnnotationsWithName(const nsACString& aName,
+ uint32_t* _count,
+ mozIAnnotatedResult*** _annotations)
+{
+ NS_ENSURE_ARG(!aName.IsEmpty());
+ NS_ENSURE_ARG_POINTER(_annotations);
+
+ *_count = 0;
+ *_annotations = nullptr;
+ nsCOMArray<mozIAnnotatedResult> annotations;
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT h.guid, h.url, -1, a.type, a.content "
+ "FROM moz_anno_attributes n "
+ "JOIN moz_annos a ON n.id = a.anno_attribute_id "
+ "JOIN moz_places h ON h.id = a.place_id "
+ "WHERE n.name = :anno_name "
+ "UNION ALL "
+ "SELECT b.guid, h.url, b.id, a.type, a.content "
+ "FROM moz_anno_attributes n "
+ "JOIN moz_items_annos a ON n.id = a.anno_attribute_id "
+ "JOIN moz_bookmarks b ON b.id = a.item_id "
+ "LEFT JOIN moz_places h ON h.id = b.fk "
+ "WHERE n.name = :anno_name "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
+ aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&hasMore)) && hasMore) {
+ nsAutoCString guid;
+ rv = stmt->GetUTF8String(0, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ bool uriIsNull = false;
+ rv = stmt->GetIsNull(1, &uriIsNull);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!uriIsNull) {
+ nsAutoCString url;
+ rv = stmt->GetUTF8String(1, url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewURI(getter_AddRefs(uri), url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ int64_t itemId = stmt->AsInt64(2);
+ int32_t type = stmt->AsInt32(3);
+
+ nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
+ switch (type) {
+ case nsIAnnotationService::TYPE_INT32: {
+ rv = variant->SetAsInt32(stmt->AsInt32(4));
+ break;
+ }
+ case nsIAnnotationService::TYPE_INT64: {
+ rv = variant->SetAsInt64(stmt->AsInt64(4));
+ break;
+ }
+ case nsIAnnotationService::TYPE_DOUBLE: {
+ rv = variant->SetAsDouble(stmt->AsDouble(4));
+ break;
+ }
+ case nsIAnnotationService::TYPE_STRING: {
+ nsAutoString valueString;
+ rv = stmt->GetString(4, valueString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = variant->SetAsAString(valueString);
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Unsupported annotation type");
+ // Move to the next result.
+ continue;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIAnnotatedResult> anno = new AnnotatedResult(guid, uri, itemId,
+ aName, variant);
+ NS_ENSURE_TRUE(annotations.AppendObject(anno), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // Convert to raw array.
+ if (annotations.Count() == 0)
+ return NS_OK;
+
+ *_count = annotations.Count();
+ annotations.Forget(_annotations);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsAnnotationService::GetItemsWithAnnotationTArray(const nsACString& aName,
+ nsTArray<int64_t>* _results)
+{
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT a.item_id "
+ "FROM moz_anno_attributes n "
+ "JOIN moz_items_annos a ON n.id = a.anno_attribute_id "
+ "WHERE n.name = :anno_name"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) &&
+ hasMore) {
+ if (!_results->AppendElement(stmt->AsInt64(0)))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetPageAnnotationNames(nsIURI* aURI,
+ uint32_t* _count,
+ nsIVariant*** _result)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(_count);
+ NS_ENSURE_ARG_POINTER(_result);
+
+ *_count = 0;
+ *_result = nullptr;
+
+ nsTArray<nsCString> names;
+ nsresult rv = GetAnnotationNamesTArray(aURI, 0, &names);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (names.Length() == 0)
+ return NS_OK;
+
+ *_result = static_cast<nsIVariant**>
+ (moz_xmalloc(sizeof(nsIVariant*) * names.Length()));
+ NS_ENSURE_TRUE(*_result, NS_ERROR_OUT_OF_MEMORY);
+
+ for (uint32_t i = 0; i < names.Length(); i ++) {
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ if (!var) {
+ // need to release all the variants we've already created
+ for (uint32_t j = 0; j < i; j ++)
+ NS_RELEASE((*_result)[j]);
+ free(*_result);
+ *_result = nullptr;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ var->SetAsAUTF8String(names[i]);
+ NS_ADDREF((*_result)[i] = var);
+ }
+ *_count = names.Length();
+
+ return NS_OK;
+}
+
+
+nsresult
+nsAnnotationService::GetAnnotationNamesTArray(nsIURI* aURI,
+ int64_t aItemId,
+ nsTArray<nsCString>* _result)
+{
+ _result->Clear();
+
+ bool isItemAnnotation = (aItemId > 0);
+ nsCOMPtr<mozIStorageStatement> statement;
+ if (isItemAnnotation) {
+ statement = mDB->GetStatement(
+ "SELECT n.name "
+ "FROM moz_anno_attributes n "
+ "JOIN moz_items_annos a ON a.anno_attribute_id = n.id "
+ "WHERE a.item_id = :item_id"
+ );
+ }
+ else {
+ statement = mDB->GetStatement(
+ "SELECT n.name "
+ "FROM moz_anno_attributes n "
+ "JOIN moz_annos a ON a.anno_attribute_id = n.id "
+ "JOIN moz_places h ON h.id = a.place_id "
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
+ );
+ }
+ NS_ENSURE_STATE(statement);
+ mozStorageStatementScoper scoper(statement);
+
+ nsresult rv;
+ if (isItemAnnotation)
+ rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
+ else
+ rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult = false;
+ while (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) &&
+ hasResult) {
+ nsAutoCString name;
+ rv = statement->GetUTF8String(0, name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!_result->AppendElement(name))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::GetItemAnnotationNames(int64_t aItemId,
+ uint32_t* _count,
+ nsIVariant*** _result)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_count);
+ NS_ENSURE_ARG_POINTER(_result);
+
+ *_count = 0;
+ *_result = nullptr;
+
+ nsTArray<nsCString> names;
+ nsresult rv = GetAnnotationNamesTArray(nullptr, aItemId, &names);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (names.Length() == 0)
+ return NS_OK;
+
+ *_result = static_cast<nsIVariant**>
+ (moz_xmalloc(sizeof(nsIVariant*) * names.Length()));
+ NS_ENSURE_TRUE(*_result, NS_ERROR_OUT_OF_MEMORY);
+
+ for (uint32_t i = 0; i < names.Length(); i ++) {
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ if (!var) {
+ // need to release all the variants we've already created
+ for (uint32_t j = 0; j < i; j ++)
+ NS_RELEASE((*_result)[j]);
+ free(*_result);
+ *_result = nullptr;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ var->SetAsAUTF8String(names[i]);
+ NS_ADDREF((*_result)[i] = var);
+ }
+ *_count = names.Length();
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::PageHasAnnotation(nsIURI* aURI,
+ const nsACString& aName,
+ bool* _retval)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv = HasAnnotationInternal(aURI, 0, aName, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::ItemHasAnnotation(int64_t aItemId,
+ const nsACString& aName,
+ bool* _retval)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv = HasAnnotationInternal(nullptr, aItemId, aName, _retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+/**
+ * @note We don't remove anything from the moz_anno_attributes table. If we
+ * delete the last item of a given name, that item really should go away.
+ * It will be cleaned up by expiration.
+ */
+nsresult
+nsAnnotationService::RemoveAnnotationInternal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName)
+{
+ bool isItemAnnotation = (aItemId > 0);
+ nsCOMPtr<mozIStorageStatement> statement;
+ if (isItemAnnotation) {
+ statement = mDB->GetStatement(
+ "DELETE FROM moz_items_annos "
+ "WHERE item_id = :item_id "
+ "AND anno_attribute_id = "
+ "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)"
+ );
+ }
+ else {
+ statement = mDB->GetStatement(
+ "DELETE FROM moz_annos "
+ "WHERE place_id = "
+ "(SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
+ "AND anno_attribute_id = "
+ "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)"
+ );
+ }
+ NS_ENSURE_STATE(statement);
+ mozStorageStatementScoper scoper(statement);
+
+ nsresult rv;
+ if (isItemAnnotation)
+ rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
+ else
+ rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::RemovePageAnnotation(nsIURI* aURI,
+ const nsACString& aName)
+{
+ NS_ENSURE_ARG(aURI);
+
+ nsresult rv = RemoveAnnotationInternal(aURI, 0, aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationRemoved(aURI, aName));
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::RemoveItemAnnotation(int64_t aItemId,
+ const nsACString& aName,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ nsresult rv = RemoveAnnotationInternal(nullptr, aItemId, aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationRemoved(aItemId, aName, aSource));
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::RemovePageAnnotations(nsIURI* aURI)
+{
+ NS_ENSURE_ARG(aURI);
+
+ // Should this be precompiled or a getter?
+ nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
+ "DELETE FROM moz_annos WHERE place_id = "
+ "(SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)"
+ );
+ NS_ENSURE_STATE(statement);
+ mozStorageStatementScoper scoper(statement);
+
+ nsresult rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Update observers
+ NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationRemoved(aURI, EmptyCString()));
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::RemoveItemAnnotations(int64_t aItemId,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ // Should this be precompiled or a getter?
+ nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
+ "DELETE FROM moz_items_annos WHERE item_id = :item_id"
+ );
+ NS_ENSURE_STATE(statement);
+ mozStorageStatementScoper scoper(statement);
+
+ nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationRemoved(aItemId, EmptyCString(),
+ aSource));
+
+ return NS_OK;
+}
+
+
+/**
+ * @note If we use annotations for some standard items like GeckoFlags, it
+ * might be a good idea to blacklist these standard annotations from this
+ * copy function.
+ */
+NS_IMETHODIMP
+nsAnnotationService::CopyPageAnnotations(nsIURI* aSourceURI,
+ nsIURI* aDestURI,
+ bool aOverwriteDest)
+{
+ NS_ENSURE_ARG(aSourceURI);
+ NS_ENSURE_ARG(aDestURI);
+
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+
+ nsCOMPtr<mozIStorageStatement> sourceStmt = mDB->GetStatement(
+ "SELECT h.id, n.id, n.name, a2.id "
+ "FROM moz_places h "
+ "JOIN moz_annos a ON a.place_id = h.id "
+ "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
+ "LEFT JOIN moz_annos a2 ON a2.place_id = "
+ "(SELECT id FROM moz_places WHERE url_hash = hash(:dest_url) AND url = :dest_url) "
+ "AND a2.anno_attribute_id = n.id "
+ "WHERE url = :source_url"
+ );
+ NS_ENSURE_STATE(sourceStmt);
+ mozStorageStatementScoper sourceScoper(sourceStmt);
+
+ nsresult rv = URIBinder::Bind(sourceStmt, NS_LITERAL_CSTRING("source_url"), aSourceURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = URIBinder::Bind(sourceStmt, NS_LITERAL_CSTRING("dest_url"), aDestURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> copyStmt = mDB->GetStatement(
+ "INSERT INTO moz_annos "
+ "(place_id, anno_attribute_id, content, flags, expiration, "
+ "type, dateAdded, lastModified) "
+ "SELECT (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url), "
+ "anno_attribute_id, content, flags, expiration, type, "
+ ":date, :date "
+ "FROM moz_annos "
+ "WHERE place_id = :page_id "
+ "AND anno_attribute_id = :name_id"
+ );
+ NS_ENSURE_STATE(copyStmt);
+ mozStorageStatementScoper copyScoper(copyStmt);
+
+ bool hasResult;
+ while (NS_SUCCEEDED(sourceStmt->ExecuteStep(&hasResult)) && hasResult) {
+ int64_t sourcePlaceId = sourceStmt->AsInt64(0);
+ int64_t annoNameID = sourceStmt->AsInt64(1);
+ nsAutoCString annoName;
+ rv = sourceStmt->GetUTF8String(2, annoName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t annoExistsOnDest = sourceStmt->AsInt64(3);
+
+ if (annoExistsOnDest) {
+ if (!aOverwriteDest)
+ continue;
+ rv = RemovePageAnnotation(aDestURI, annoName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Copy the annotation.
+ mozStorageStatementScoper scoper(copyStmt);
+ rv = URIBinder::Bind(copyStmt, NS_LITERAL_CSTRING("page_url"), aDestURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), sourcePlaceId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("name_id"), annoNameID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = copyStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationSet(aDestURI, annoName));
+ }
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::CopyItemAnnotations(int64_t aSourceItemId,
+ int64_t aDestItemId,
+ bool aOverwriteDest,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aSourceItemId, 1);
+ NS_ENSURE_ARG_MIN(aDestItemId, 1);
+
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+
+ nsCOMPtr<mozIStorageStatement> sourceStmt = mDB->GetStatement(
+ "SELECT n.id, n.name, a2.id "
+ "FROM moz_bookmarks b "
+ "JOIN moz_items_annos a ON a.item_id = b.id "
+ "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
+ "LEFT JOIN moz_items_annos a2 ON a2.item_id = :dest_item_id "
+ "AND a2.anno_attribute_id = n.id "
+ "WHERE b.id = :source_item_id"
+ );
+ NS_ENSURE_STATE(sourceStmt);
+ mozStorageStatementScoper sourceScoper(sourceStmt);
+
+ nsresult rv = sourceStmt->BindInt64ByName(NS_LITERAL_CSTRING("source_item_id"), aSourceItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sourceStmt->BindInt64ByName(NS_LITERAL_CSTRING("dest_item_id"), aDestItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> copyStmt = mDB->GetStatement(
+ "INSERT OR REPLACE INTO moz_items_annos "
+ "(item_id, anno_attribute_id, content, flags, expiration, "
+ "type, dateAdded, lastModified) "
+ "SELECT :dest_item_id, anno_attribute_id, content, flags, expiration, "
+ "type, :date, :date "
+ "FROM moz_items_annos "
+ "WHERE item_id = :source_item_id "
+ "AND anno_attribute_id = :name_id"
+ );
+ NS_ENSURE_STATE(copyStmt);
+ mozStorageStatementScoper copyScoper(copyStmt);
+
+ bool hasResult;
+ while (NS_SUCCEEDED(sourceStmt->ExecuteStep(&hasResult)) && hasResult) {
+ int64_t annoNameID = sourceStmt->AsInt64(0);
+ nsAutoCString annoName;
+ rv = sourceStmt->GetUTF8String(1, annoName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t annoExistsOnDest = sourceStmt->AsInt64(2);
+
+ if (annoExistsOnDest) {
+ if (!aOverwriteDest)
+ continue;
+ rv = RemoveItemAnnotation(aDestItemId, annoName, aSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Copy the annotation.
+ mozStorageStatementScoper scoper(copyStmt);
+ rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("dest_item_id"), aDestItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("source_item_id"), aSourceItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("name_id"), annoNameID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = copyStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aDestItemId, annoName, aSource));
+ }
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::AddObserver(nsIAnnotationObserver* aObserver)
+{
+ NS_ENSURE_ARG(aObserver);
+
+ if (mObservers.IndexOfObject(aObserver) >= 0)
+ return NS_ERROR_INVALID_ARG; // Already registered.
+ if (!mObservers.AppendObject(aObserver))
+ return NS_ERROR_OUT_OF_MEMORY;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAnnotationService::RemoveObserver(nsIAnnotationObserver* aObserver)
+{
+ NS_ENSURE_ARG(aObserver);
+
+ if (!mObservers.RemoveObject(aObserver))
+ return NS_ERROR_INVALID_ARG;
+ return NS_OK;
+}
+
+nsresult
+nsAnnotationService::HasAnnotationInternal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ bool* _hasAnno)
+{
+ bool isItemAnnotation = (aItemId > 0);
+ nsCOMPtr<mozIStorageStatement> stmt;
+ if (isItemAnnotation) {
+ stmt = mDB->GetStatement(
+ "SELECT b.id, "
+ "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, "
+ "a.id, a.dateAdded "
+ "FROM moz_bookmarks b "
+ "LEFT JOIN moz_items_annos a ON a.item_id = b.id "
+ "AND a.anno_attribute_id = nameid "
+ "WHERE b.id = :item_id"
+ );
+ }
+ else {
+ stmt = mDB->GetStatement(
+ "SELECT h.id, "
+ "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, "
+ "a.id, a.dateAdded "
+ "FROM moz_places h "
+ "LEFT JOIN moz_annos a ON a.place_id = h.id "
+ "AND a.anno_attribute_id = nameid "
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
+ );
+ }
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper checkAnnoScoper(stmt);
+
+ nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isItemAnnotation)
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
+ else
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hasResult) {
+ // We are trying to get an annotation on an invalid bookmarks or
+ // history entry.
+ // Here we preserve the old behavior, returning that we don't have the
+ // annotation, ignoring the fact itemId is invalid.
+ // Otherwise we should return NS_ERROR_INVALID_ARG, but this will somehow
+ // break the API. In future we could want to be pickier.
+ *_hasAnno = false;
+ }
+ else {
+ int64_t annotationId = stmt->AsInt64(2);
+ *_hasAnno = (annotationId > 0);
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * This loads the statement and steps it once so you can get data out of it.
+ *
+ * @note You have to reset the statement when you're done if this succeeds.
+ * @throws NS_ERROR_NOT_AVAILABLE if the annotation is not found.
+ */
+
+nsresult
+nsAnnotationService::StartGetAnnotation(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ nsCOMPtr<mozIStorageStatement>& aStatement)
+{
+ bool isItemAnnotation = (aItemId > 0);
+
+ if (isItemAnnotation) {
+ aStatement = mDB->GetStatement(
+ "SELECT a.id, a.item_id, :anno_name, a.content, a.flags, "
+ "a.expiration, a.type "
+ "FROM moz_anno_attributes n "
+ "JOIN moz_items_annos a ON a.anno_attribute_id = n.id "
+ "WHERE a.item_id = :item_id "
+ "AND n.name = :anno_name"
+ );
+ }
+ else {
+ aStatement = mDB->GetStatement(
+ "SELECT a.id, a.place_id, :anno_name, a.content, a.flags, "
+ "a.expiration, a.type "
+ "FROM moz_anno_attributes n "
+ "JOIN moz_annos a ON n.id = a.anno_attribute_id "
+ "JOIN moz_places h ON h.id = a.place_id "
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url "
+ "AND n.name = :anno_name"
+ );
+ }
+ NS_ENSURE_STATE(aStatement);
+ mozStorageStatementScoper getAnnoScoper(aStatement);
+
+ nsresult rv;
+ if (isItemAnnotation)
+ rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
+ else
+ rv = URIBinder::Bind(aStatement, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStatement->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult = false;
+ rv = aStatement->ExecuteStep(&hasResult);
+ if (NS_FAILED(rv) || !hasResult)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // on success, DON'T reset the statement, the caller needs to read from it,
+ // and it is the caller's job to reset it.
+ getAnnoScoper.Abandon();
+
+ return NS_OK;
+}
+
+
+/**
+ * This does most of the setup work needed to set an annotation, except for
+ * binding the the actual value and executing the statement.
+ * It will either update an existing annotation or insert a new one.
+ *
+ * @note The aStatement RESULT IS NOT ADDREFED. This is just one of the class
+ * vars, which control its scope. DO NOT RELEASE.
+ * The caller must take care of resetting the statement if this succeeds.
+ */
+nsresult
+nsAnnotationService::StartSetAnnotation(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ int32_t aFlags,
+ uint16_t aExpiration,
+ uint16_t aType,
+ nsCOMPtr<mozIStorageStatement>& aStatement)
+{
+ bool isItemAnnotation = (aItemId > 0);
+
+ if (aExpiration == EXPIRE_SESSION) {
+ mHasSessionAnnotations = true;
+ }
+
+ // Ensure the annotation name exists.
+ nsCOMPtr<mozIStorageStatement> addNameStmt = mDB->GetStatement(
+ "INSERT OR IGNORE INTO moz_anno_attributes (name) VALUES (:anno_name)"
+ );
+ NS_ENSURE_STATE(addNameStmt);
+ mozStorageStatementScoper scoper(addNameStmt);
+
+ nsresult rv = addNameStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = addNameStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We have to check 2 things:
+ // - if the annotation already exists we should update it.
+ // - we should not allow setting annotations on invalid URIs or itemIds.
+ // This query will tell us:
+ // - whether the item or page exists.
+ // - whether the annotation already exists.
+ // - the nameID associated with the annotation name.
+ // - the id and dateAdded of the old annotation, if it exists.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ if (isItemAnnotation) {
+ stmt = mDB->GetStatement(
+ "SELECT b.id, "
+ "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, "
+ "a.id, a.dateAdded "
+ "FROM moz_bookmarks b "
+ "LEFT JOIN moz_items_annos a ON a.item_id = b.id "
+ "AND a.anno_attribute_id = nameid "
+ "WHERE b.id = :item_id"
+ );
+ }
+ else {
+ stmt = mDB->GetStatement(
+ "SELECT h.id, "
+ "(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, "
+ "a.id, a.dateAdded "
+ "FROM moz_places h "
+ "LEFT JOIN moz_annos a ON a.place_id = h.id "
+ "AND a.anno_attribute_id = nameid "
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
+ );
+ }
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper checkAnnoScoper(stmt);
+
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isItemAnnotation)
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
+ else
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hasResult) {
+ // We are trying to create an annotation on an invalid bookmark
+ // or history entry.
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int64_t fkId = stmt->AsInt64(0);
+ int64_t nameID = stmt->AsInt64(1);
+ int64_t oldAnnoId = stmt->AsInt64(2);
+ int64_t oldAnnoDate = stmt->AsInt64(3);
+
+ if (isItemAnnotation) {
+ aStatement = mDB->GetStatement(
+ "INSERT OR REPLACE INTO moz_items_annos "
+ "(id, item_id, anno_attribute_id, content, flags, "
+ "expiration, type, dateAdded, lastModified) "
+ "VALUES (:id, :fk, :name_id, :content, :flags, "
+ ":expiration, :type, :date_added, :last_modified)"
+ );
+ }
+ else {
+ aStatement = mDB->GetStatement(
+ "INSERT OR REPLACE INTO moz_annos "
+ "(id, place_id, anno_attribute_id, content, flags, "
+ "expiration, type, dateAdded, lastModified) "
+ "VALUES (:id, :fk, :name_id, :content, :flags, "
+ ":expiration, :type, :date_added, :last_modified)"
+ );
+ }
+ NS_ENSURE_STATE(aStatement);
+ mozStorageStatementScoper setAnnoScoper(aStatement);
+
+ // Don't replace existing annotations.
+ if (oldAnnoId > 0) {
+ rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), oldAnnoId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), oldAnnoDate);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ rv = aStatement->BindNullByName(NS_LITERAL_CSTRING("id"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), RoundedPRNow());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("fk"), fkId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("name_id"), nameID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStatement->BindInt32ByName(NS_LITERAL_CSTRING("flags"), aFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStatement->BindInt32ByName(NS_LITERAL_CSTRING("expiration"), aExpiration);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStatement->BindInt32ByName(NS_LITERAL_CSTRING("type"), aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aStatement->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), RoundedPRNow());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // On success, leave the statement open, the caller will set the value
+ // and execute the statement.
+ setAnnoScoper.Abandon();
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+nsAnnotationService::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+
+ if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
+ // Remove all session annotations, if any.
+ if (mHasSessionAnnotations) {
+ nsCOMPtr<mozIStorageAsyncStatement> pageAnnoStmt = mDB->GetAsyncStatement(
+ "DELETE FROM moz_annos WHERE expiration = :expire_session"
+ );
+ NS_ENSURE_STATE(pageAnnoStmt);
+ nsresult rv = pageAnnoStmt->BindInt32ByName(NS_LITERAL_CSTRING("expire_session"),
+ EXPIRE_SESSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageAsyncStatement> itemAnnoStmt = mDB->GetAsyncStatement(
+ "DELETE FROM moz_items_annos WHERE expiration = :expire_session"
+ );
+ NS_ENSURE_STATE(itemAnnoStmt);
+ rv = itemAnnoStmt->BindInt32ByName(NS_LITERAL_CSTRING("expire_session"),
+ EXPIRE_SESSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozIStorageBaseStatement *stmts[] = {
+ pageAnnoStmt.get()
+ , itemAnnoStmt.get()
+ };
+
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
+ getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/components/places/src/nsAnnotationService.h b/components/places/src/nsAnnotationService.h
new file mode 100644
index 000000000..f1b4921d8
--- /dev/null
+++ b/components/places/src/nsAnnotationService.h
@@ -0,0 +1,161 @@
+//* -*- 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/. */
+
+#ifndef nsAnnotationService_h___
+#define nsAnnotationService_h___
+
+#include "nsIAnnotationService.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWeakReference.h"
+#include "nsToolkitCompsCID.h"
+#include "Database.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace places {
+
+class AnnotatedResult final : public mozIAnnotatedResult
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZIANNOTATEDRESULT
+
+ AnnotatedResult(const nsCString& aGUID, nsIURI* aURI, int64_t aItemd,
+ const nsACString& aAnnotationName,
+ nsIVariant* aAnnotationValue);
+
+private:
+ ~AnnotatedResult();
+
+ const nsCString mGUID;
+ nsCOMPtr<nsIURI> mURI;
+ const int64_t mItemId;
+ const nsCString mAnnotationName;
+ nsCOMPtr<nsIVariant> mAnnotationValue;
+};
+
+} // namespace places
+} // namespace mozilla
+
+class nsAnnotationService final : public nsIAnnotationService
+ , public nsIObserver
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIANNOTATIONSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsAnnotationService();
+
+ /**
+ * Obtains the service's object.
+ */
+ static already_AddRefed<nsAnnotationService> GetSingleton();
+
+ /**
+ * Initializes the service's object. This should only be called once.
+ */
+ nsresult Init();
+
+ /**
+ * Returns a cached pointer to the annotation service for consumers in the
+ * places directory.
+ */
+ static nsAnnotationService* GetAnnotationService()
+ {
+ if (!gAnnotationService) {
+ nsCOMPtr<nsIAnnotationService> serv =
+ do_GetService(NS_ANNOTATIONSERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(serv, nullptr);
+ NS_ASSERTION(gAnnotationService,
+ "Should have static instance pointer now");
+ }
+ return gAnnotationService;
+ }
+
+private:
+ ~nsAnnotationService();
+
+protected:
+ RefPtr<mozilla::places::Database> mDB;
+
+ nsCOMArray<nsIAnnotationObserver> mObservers;
+ bool mHasSessionAnnotations;
+
+ static nsAnnotationService* gAnnotationService;
+
+ static const int kAnnoIndex_ID;
+ static const int kAnnoIndex_PageOrItem;
+ static const int kAnnoIndex_NameID;
+ static const int kAnnoIndex_Content;
+ static const int kAnnoIndex_Flags;
+ static const int kAnnoIndex_Expiration;
+ static const int kAnnoIndex_Type;
+ static const int kAnnoIndex_DateAdded;
+ static const int kAnnoIndex_LastModified;
+
+ nsresult HasAnnotationInternal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ bool* _hasAnno);
+
+ nsresult StartGetAnnotation(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ nsCOMPtr<mozIStorageStatement>& aStatement);
+
+ nsresult StartSetAnnotation(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ int32_t aFlags,
+ uint16_t aExpiration,
+ uint16_t aType,
+ nsCOMPtr<mozIStorageStatement>& aStatement);
+
+ nsresult SetAnnotationStringInternal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ const nsAString& aValue,
+ int32_t aFlags,
+ uint16_t aExpiration);
+ nsresult SetAnnotationInt32Internal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ int32_t aValue,
+ int32_t aFlags,
+ uint16_t aExpiration);
+ nsresult SetAnnotationInt64Internal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ int64_t aValue,
+ int32_t aFlags,
+ uint16_t aExpiration);
+ nsresult SetAnnotationDoubleInternal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName,
+ double aValue,
+ int32_t aFlags,
+ uint16_t aExpiration);
+
+ nsresult RemoveAnnotationInternal(nsIURI* aURI,
+ int64_t aItemId,
+ const nsACString& aName);
+
+public:
+ nsresult GetPagesWithAnnotationCOMArray(const nsACString& aName,
+ nsCOMArray<nsIURI>* _results);
+ nsresult GetItemsWithAnnotationTArray(const nsACString& aName,
+ nsTArray<int64_t>* _result);
+ nsresult GetAnnotationNamesTArray(nsIURI* aURI,
+ int64_t aItemId,
+ nsTArray<nsCString>* _result);
+};
+
+#endif /* nsAnnotationService_h___ */
diff --git a/components/places/src/nsFaviconService.cpp b/components/places/src/nsFaviconService.cpp
new file mode 100644
index 000000000..e310db214
--- /dev/null
+++ b/components/places/src/nsFaviconService.cpp
@@ -0,0 +1,715 @@
+/* -*- 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 is the favicon service, which stores favicons for web pages with your
+ * history as you browse. It is also used to save the favicons for bookmarks.
+ *
+ * DANGER: The history query system makes assumptions about the favicon storage
+ * so that icons can be quickly generated for history/bookmark result sets. If
+ * you change the database layout at all, you will have to update both services.
+ */
+
+#include "nsFaviconService.h"
+
+#include "nsNavHistory.h"
+#include "nsPlacesMacros.h"
+#include "Helpers.h"
+
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "plbase64.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Preferences.h"
+#include "nsILoadInfo.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+#include "nsNullPrincipal.h"
+
+// For large favicons optimization.
+#include "imgITools.h"
+#include "imgIContainer.h"
+
+// The target dimension, in pixels, for favicons we optimize.
+#define OPTIMIZED_FAVICON_DIMENSION 32
+
+#define MAX_FAILED_FAVICONS 256
+#define FAVICON_CACHE_REDUCE_COUNT 64
+
+#define UNASSOCIATED_FAVICONS_LENGTH 32
+
+// When replaceFaviconData is called, we store the icons in an in-memory cache
+// instead of in storage. Icons in the cache are expired according to this
+// interval.
+#define UNASSOCIATED_ICON_EXPIRY_INTERVAL 60000
+
+// The MIME type of the default favicon and favicons created by
+// OptimizeFaviconImage.
+#define DEFAULT_MIME_TYPE "image/png"
+
+using namespace mozilla;
+using namespace mozilla::places;
+
+/**
+ * Used to notify a topic to system observers on async execute completion.
+ * Will throw on error.
+ */
+class ExpireFaviconsStatementCallbackNotifier : public AsyncStatementCallback
+{
+public:
+ ExpireFaviconsStatementCallbackNotifier();
+ NS_IMETHOD HandleCompletion(uint16_t aReason);
+};
+
+
+PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService)
+
+NS_IMPL_CLASSINFO(nsFaviconService, nullptr, 0, NS_FAVICONSERVICE_CID)
+NS_IMPL_ISUPPORTS_CI(
+ nsFaviconService
+, nsIFaviconService
+, mozIAsyncFavicons
+, nsITimerCallback
+)
+
+nsFaviconService::nsFaviconService()
+ : mFailedFaviconSerial(0)
+ , mFailedFavicons(MAX_FAILED_FAVICONS / 2)
+ , mUnassociatedIcons(UNASSOCIATED_FAVICONS_LENGTH)
+{
+ NS_ASSERTION(!gFaviconService,
+ "Attempting to create two instances of the service!");
+ gFaviconService = this;
+}
+
+
+nsFaviconService::~nsFaviconService()
+{
+ NS_ASSERTION(gFaviconService == this,
+ "Deleting a non-singleton instance of the service");
+ if (gFaviconService == this)
+ gFaviconService = nullptr;
+}
+
+
+nsresult
+nsFaviconService::Init()
+{
+ mDB = Database::GetDatabase();
+ NS_ENSURE_STATE(mDB);
+
+ mExpireUnassociatedIconsTimer = do_CreateInstance("@mozilla.org/timer;1");
+ NS_ENSURE_STATE(mExpireUnassociatedIconsTimer);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::ExpireAllFavicons()
+{
+ nsCOMPtr<mozIStorageAsyncStatement> unlinkIconsStmt = mDB->GetAsyncStatement(
+ "UPDATE moz_places "
+ "SET favicon_id = NULL "
+ "WHERE favicon_id NOT NULL"
+ );
+ NS_ENSURE_STATE(unlinkIconsStmt);
+ nsCOMPtr<mozIStorageAsyncStatement> removeIconsStmt = mDB->GetAsyncStatement(
+ "DELETE FROM moz_favicons WHERE id NOT IN ("
+ "SELECT favicon_id FROM moz_places WHERE favicon_id NOT NULL "
+ ")"
+ );
+ NS_ENSURE_STATE(removeIconsStmt);
+
+ mozIStorageBaseStatement* stmts[] = {
+ unlinkIconsStmt.get()
+ , removeIconsStmt.get()
+ };
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ RefPtr<ExpireFaviconsStatementCallbackNotifier> callback =
+ new ExpireFaviconsStatementCallbackNotifier();
+ nsresult rv = mDB->MainConn()->ExecuteAsync(
+ stmts, ArrayLength(stmts), callback, getter_AddRefs(ps)
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsITimerCallback
+
+NS_IMETHODIMP
+nsFaviconService::Notify(nsITimer* timer)
+{
+ if (timer != mExpireUnassociatedIconsTimer.get()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PRTime now = PR_Now();
+ for (auto iter = mUnassociatedIcons.Iter(); !iter.Done(); iter.Next()) {
+ UnassociatedIconHashKey* iconKey = iter.Get();
+ if (now - iconKey->created >= UNASSOCIATED_ICON_EXPIRY_INTERVAL) {
+ iter.Remove();
+ }
+ }
+
+ // Re-init the expiry timer if the cache isn't empty.
+ if (mUnassociatedIcons.Count() > 0) {
+ mExpireUnassociatedIconsTimer->InitWithCallback(
+ this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIFaviconService
+
+NS_IMETHODIMP
+nsFaviconService::GetDefaultFavicon(nsIURI** _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // not found, use default
+ if (!mDefaultIcon) {
+ nsresult rv = NS_NewURI(getter_AddRefs(mDefaultIcon),
+ NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return mDefaultIcon->Clone(_retval);
+}
+
+void
+nsFaviconService::SendFaviconNotifications(nsIURI* aPageURI,
+ nsIURI* aFaviconURI,
+ const nsACString& aGUID)
+{
+ nsAutoCString faviconSpec;
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ if (history && NS_SUCCEEDED(aFaviconURI->GetSpec(faviconSpec))) {
+ history->SendPageChangedNotification(aPageURI,
+ nsINavHistoryObserver::ATTRIBUTE_FAVICON,
+ NS_ConvertUTF8toUTF16(faviconSpec),
+ aGUID);
+ }
+}
+
+NS_IMETHODIMP
+nsFaviconService::SetAndFetchFaviconForPage(nsIURI* aPageURI,
+ nsIURI* aFaviconURI,
+ bool aForceReload,
+ uint32_t aFaviconLoadType,
+ nsIFaviconDataCallback* aCallback,
+ nsIPrincipal* aLoadingPrincipal,
+ mozIPlacesPendingOperation **_canceler)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aPageURI);
+ NS_ENSURE_ARG(aFaviconURI);
+ NS_ENSURE_ARG_POINTER(_canceler);
+
+ // If a favicon is in the failed cache, only load it during a forced reload.
+ bool previouslyFailed;
+ nsresult rv = IsFailedFavicon(aFaviconURI, &previouslyFailed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (previouslyFailed) {
+ if (aForceReload)
+ RemoveFailedFavicon(aFaviconURI);
+ else
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
+ MOZ_ASSERT(loadingPrincipal, "please provide aLoadingPrincipal for this favicon");
+ if (!loadingPrincipal) {
+ // Let's default to the nullPrincipal if no loadingPrincipal is provided.
+ const char16_t* params[] = {
+ u"nsFaviconService::setAndFetchFaviconForPage()",
+ u"nsFaviconService::setAndFetchFaviconForPage(..., [optional aLoadingPrincipal])"
+ };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, ArrayLength(params));
+ loadingPrincipal = nsNullPrincipal::Create();
+ }
+ NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
+
+ // Check if the icon already exists and fetch it from the network, if needed.
+ // Finally associate the icon to the requested page if not yet associated.
+ bool loadPrivate = aFaviconLoadType == nsIFaviconService::FAVICON_LOAD_PRIVATE;
+
+ PageData page;
+ rv = aPageURI->GetSpec(page.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // URIs can arguably miss a host.
+ (void)GetReversedHostname(aPageURI, page.revHost);
+ bool canAddToHistory;
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
+ rv = navHistory->CanAddURI(aPageURI, &canAddToHistory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ page.canAddToHistory = !!canAddToHistory && !loadPrivate;
+
+ IconData icon;
+ UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(aFaviconURI);
+ if (iconKey) {
+ icon = iconKey->iconData;
+ mUnassociatedIcons.RemoveEntry(iconKey);
+ } else {
+ icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING;
+ rv = aFaviconURI->GetSpec(icon.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If the page url points to an image, the icon's url will be the same.
+ // In future evaluate to store a resample of the image. For now avoid that
+ // for database size concerns.
+ // Don't store favicons for error pages too.
+ if (icon.spec.Equals(page.spec) ||
+ icon.spec.Equals(FAVICON_ERRORPAGE_URL)) {
+ return NS_OK;
+ }
+
+ RefPtr<AsyncFetchAndSetIconForPage> event =
+ new AsyncFetchAndSetIconForPage(icon, page, loadPrivate,
+ aCallback, aLoadingPrincipal);
+
+ // Get the target thread and start the work.
+ // DB will be updated and observers notified when data has finished loading.
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ DB->DispatchToAsyncThread(event);
+
+ // Return this event to the caller to allow aborting an eventual fetch.
+ event.forget(_canceler);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::ReplaceFaviconData(nsIURI* aFaviconURI,
+ const uint8_t* aData,
+ uint32_t aDataLen,
+ const nsACString& aMimeType,
+ PRTime aExpiration)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aFaviconURI);
+ NS_ENSURE_ARG(aData);
+ NS_ENSURE_TRUE(aDataLen > 0, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(aMimeType.Length() > 0, NS_ERROR_INVALID_ARG);
+ if (aExpiration == 0) {
+ aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
+ }
+
+ UnassociatedIconHashKey* iconKey = mUnassociatedIcons.PutEntry(aFaviconURI);
+ if (!iconKey) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ iconKey->created = PR_Now();
+
+ // If the cache contains unassociated icons, an expiry timer should already exist, otherwise
+ // there may be a timer left hanging around, so make sure we fire a new one.
+ int32_t unassociatedCount = mUnassociatedIcons.Count();
+ if (unassociatedCount == 1) {
+ mExpireUnassociatedIconsTimer->Cancel();
+ mExpireUnassociatedIconsTimer->InitWithCallback(
+ this, UNASSOCIATED_ICON_EXPIRY_INTERVAL, nsITimer::TYPE_ONE_SHOT);
+ }
+
+ IconData* iconData = &(iconKey->iconData);
+ iconData->expiration = aExpiration;
+ iconData->status = ICON_STATUS_CACHED;
+ iconData->fetchMode = FETCH_NEVER;
+ nsresult rv = aFaviconURI->GetSpec(iconData->spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the page provided a large image for the favicon (eg, a highres image
+ // or a multiresolution .ico file), we don't want to store more data than
+ // needed.
+ if (aDataLen > MAX_FAVICON_FILESIZE) {
+ rv = OptimizeFaviconImage(aData, aDataLen, aMimeType, iconData->data, iconData->mimeType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (iconData->data.Length() > nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
+ // We cannot optimize this favicon size and we are over the maximum size
+ // allowed, so we will not save data to the db to avoid bloating it.
+ mUnassociatedIcons.RemoveEntry(aFaviconURI);
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ iconData->mimeType.Assign(aMimeType);
+ iconData->data.Assign(TO_CHARBUFFER(aData), aDataLen);
+ }
+
+ // If the database contains an icon at the given url, we will update the
+ // database immediately so that the associated pages are kept in sync.
+ // Otherwise, do nothing and let the icon be picked up from the memory hash.
+ RefPtr<AsyncReplaceFaviconData> event = new AsyncReplaceFaviconData(*iconData);
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ DB->DispatchToAsyncThread(event);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::ReplaceFaviconDataFromDataURL(nsIURI* aFaviconURI,
+ const nsAString& aDataURL,
+ PRTime aExpiration,
+ nsIPrincipal* aLoadingPrincipal)
+{
+ NS_ENSURE_ARG(aFaviconURI);
+ NS_ENSURE_TRUE(aDataURL.Length() > 0, NS_ERROR_INVALID_ARG);
+ if (aExpiration == 0) {
+ aExpiration = PR_Now() + MAX_FAVICON_EXPIRATION;
+ }
+
+ nsCOMPtr<nsIURI> dataURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(dataURI), aDataURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Use the data: protocol handler to convert the data.
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIProtocolHandler> protocolHandler;
+ rv = ioService->GetProtocolHandler("data", getter_AddRefs(protocolHandler));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
+ MOZ_ASSERT(loadingPrincipal, "please provide aLoadingPrincipal for this favicon");
+ if (!loadingPrincipal) {
+ // Let's default to the nullPrincipal if no loadingPrincipal is provided.
+ const char16_t* params[] = {
+ u"nsFaviconService::ReplaceFaviconDataFromDataURL()",
+ u"nsFaviconService::ReplaceFaviconDataFromDataURL(..., [optional aLoadingPrincipal])"
+ };
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Security by Default"),
+ nullptr, // aDocument
+ nsContentUtils::eNECKO_PROPERTIES,
+ "APIDeprecationWarning",
+ params, ArrayLength(params));
+
+ loadingPrincipal = nsNullPrincipal::Create();
+ }
+ NS_ENSURE_TRUE(loadingPrincipal, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new mozilla::LoadInfo(loadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ nullptr, // aLoadingNode
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
+ nsILoadInfo::SEC_ALLOW_CHROME |
+ nsILoadInfo::SEC_DISALLOW_SCRIPT,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = protocolHandler->NewChannel2(dataURI, loadInfo, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Blocking stream is OK for data URIs.
+ nsCOMPtr<nsIInputStream> stream;
+ rv = channel->Open2(getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t available64;
+ rv = stream->Available(&available64);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (available64 == 0 || available64 > UINT32_MAX / sizeof(uint8_t))
+ return NS_ERROR_FILE_TOO_BIG;
+ uint32_t available = (uint32_t)available64;
+
+ // Read all the decoded data.
+ uint8_t* buffer = static_cast<uint8_t*>
+ (moz_xmalloc(sizeof(uint8_t) * available));
+ if (!buffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+ uint32_t numRead;
+ rv = stream->Read(TO_CHARBUFFER(buffer), available, &numRead);
+ if (NS_FAILED(rv) || numRead != available) {
+ free(buffer);
+ return rv;
+ }
+
+ nsAutoCString mimeType;
+ rv = channel->GetContentType(mimeType);
+ if (NS_FAILED(rv)) {
+ free(buffer);
+ return rv;
+ }
+
+ // ReplaceFaviconData can now do the dirty work.
+ rv = ReplaceFaviconData(aFaviconURI, buffer, available, mimeType, aExpiration);
+ free(buffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::GetFaviconURLForPage(nsIURI *aPageURI,
+ nsIFaviconDataCallback* aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aPageURI);
+ NS_ENSURE_ARG(aCallback);
+
+ nsAutoCString pageSpec;
+ nsresult rv = aPageURI->GetSpec(pageSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<AsyncGetFaviconURLForPage> event =
+ new AsyncGetFaviconURLForPage(pageSpec, aCallback);
+
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ DB->DispatchToAsyncThread(event);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFaviconService::GetFaviconDataForPage(nsIURI* aPageURI,
+ nsIFaviconDataCallback* aCallback)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aPageURI);
+ NS_ENSURE_ARG(aCallback);
+
+ nsAutoCString pageSpec;
+ nsresult rv = aPageURI->GetSpec(pageSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<AsyncGetFaviconDataForPage> event =
+ new AsyncGetFaviconDataForPage(pageSpec, aCallback);
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ DB->DispatchToAsyncThread(event);
+
+ return NS_OK;
+}
+
+nsresult
+nsFaviconService::GetFaviconLinkForIcon(nsIURI* aFaviconURI,
+ nsIURI** aOutputURI)
+{
+ NS_ENSURE_ARG(aFaviconURI);
+ NS_ENSURE_ARG_POINTER(aOutputURI);
+
+ nsAutoCString spec;
+ if (aFaviconURI) {
+ nsresult rv = aFaviconURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return GetFaviconLinkForIconString(spec, aOutputURI);
+}
+
+
+NS_IMETHODIMP
+nsFaviconService::AddFailedFavicon(nsIURI* aFaviconURI)
+{
+ NS_ENSURE_ARG(aFaviconURI);
+
+ nsAutoCString spec;
+ nsresult rv = aFaviconURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mFailedFavicons.Put(spec, mFailedFaviconSerial);
+ mFailedFaviconSerial ++;
+
+ if (mFailedFavicons.Count() > MAX_FAILED_FAVICONS) {
+ // need to expire some entries, delete the FAVICON_CACHE_REDUCE_COUNT number
+ // of items that are the oldest
+ uint32_t threshold = mFailedFaviconSerial -
+ MAX_FAILED_FAVICONS + FAVICON_CACHE_REDUCE_COUNT;
+ for (auto iter = mFailedFavicons.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.Data() < threshold) {
+ iter.Remove();
+ }
+ }
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsFaviconService::RemoveFailedFavicon(nsIURI* aFaviconURI)
+{
+ NS_ENSURE_ARG(aFaviconURI);
+
+ nsAutoCString spec;
+ nsresult rv = aFaviconURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we silently do nothing and succeed if the icon is not in the cache
+ mFailedFavicons.Remove(spec);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsFaviconService::IsFailedFavicon(nsIURI* aFaviconURI, bool* _retval)
+{
+ NS_ENSURE_ARG(aFaviconURI);
+ nsAutoCString spec;
+ nsresult rv = aFaviconURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t serial;
+ *_retval = mFailedFavicons.Get(spec, &serial);
+ return NS_OK;
+}
+
+
+// nsFaviconService::GetFaviconLinkForIconString
+//
+// This computes a favicon URL with string input and using the cached
+// default one to minimize parsing.
+
+nsresult
+nsFaviconService::GetFaviconLinkForIconString(const nsCString& aSpec,
+ nsIURI** aOutput)
+{
+ if (aSpec.IsEmpty()) {
+ // default icon for empty strings
+ if (! mDefaultIcon) {
+ nsresult rv = NS_NewURI(getter_AddRefs(mDefaultIcon),
+ NS_LITERAL_CSTRING(FAVICON_DEFAULT_URL));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return mDefaultIcon->Clone(aOutput);
+ }
+
+ if (StringBeginsWith(aSpec, NS_LITERAL_CSTRING("chrome:"))) {
+ // pass through for chrome URLs, since they can be referenced without
+ // this service
+ return NS_NewURI(aOutput, aSpec);
+ }
+
+ nsAutoCString annoUri;
+ annoUri.AssignLiteral("moz-anno:" FAVICON_ANNOTATION_NAME ":");
+ annoUri += aSpec;
+ return NS_NewURI(aOutput, annoUri);
+}
+
+
+// nsFaviconService::GetFaviconSpecForIconString
+//
+// This computes a favicon spec for when you don't want a URI object (as in
+// the tree view implementation), sparing all parsing and normalization.
+void
+nsFaviconService::GetFaviconSpecForIconString(const nsCString& aSpec,
+ nsACString& aOutput)
+{
+ if (aSpec.IsEmpty()) {
+ aOutput.AssignLiteral(FAVICON_DEFAULT_URL);
+ } else if (StringBeginsWith(aSpec, NS_LITERAL_CSTRING("chrome:"))) {
+ aOutput = aSpec;
+ } else {
+ aOutput.AssignLiteral("moz-anno:" FAVICON_ANNOTATION_NAME ":");
+ aOutput += aSpec;
+ }
+}
+
+
+// nsFaviconService::OptimizeFaviconImage
+//
+// Given a blob of data (a image file already read into a buffer), optimize
+// its size by recompressing it as a 16x16 PNG.
+nsresult
+nsFaviconService::OptimizeFaviconImage(const uint8_t* aData, uint32_t aDataLen,
+ const nsACString& aMimeType,
+ nsACString& aNewData,
+ nsACString& aNewMimeType)
+{
+ nsresult rv;
+
+ nsCOMPtr<imgITools> imgtool = do_CreateInstance("@mozilla.org/image/tools;1");
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ reinterpret_cast<const char*>(aData), aDataLen,
+ NS_ASSIGNMENT_DEPEND);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // decode image
+ nsCOMPtr<imgIContainer> container;
+ rv = imgtool->DecodeImageData(stream, aMimeType, getter_AddRefs(container));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aNewMimeType.AssignLiteral(DEFAULT_MIME_TYPE);
+
+ // scale and recompress
+ nsCOMPtr<nsIInputStream> iconStream;
+ rv = imgtool->EncodeScaledImage(container, aNewMimeType,
+ OPTIMIZED_FAVICON_DIMENSION,
+ OPTIMIZED_FAVICON_DIMENSION,
+ EmptyString(),
+ getter_AddRefs(iconStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Read the stream into a new buffer.
+ rv = NS_ConsumeStream(iconStream, UINT32_MAX, aNewData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsFaviconService::GetFaviconDataAsync(nsIURI* aFaviconURI,
+ mozIStorageStatementCallback *aCallback)
+{
+ NS_ASSERTION(aCallback, "Doesn't make sense to call this without a callback");
+ nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
+ "SELECT f.data, f.mime_type FROM moz_favicons f WHERE url = :icon_url"
+ );
+ NS_ENSURE_STATE(stmt);
+
+ // Ignore the ref part of the URI before querying the database because
+ // we may have added a media fragment for rendering purposes.
+
+ nsAutoCString faviconURI;
+ aFaviconURI->GetSpecIgnoringRef(faviconURI);
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), faviconURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
+ return stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// ExpireFaviconsStatementCallbackNotifier
+
+ExpireFaviconsStatementCallbackNotifier::ExpireFaviconsStatementCallbackNotifier()
+{
+}
+
+
+NS_IMETHODIMP
+ExpireFaviconsStatementCallbackNotifier::HandleCompletion(uint16_t aReason)
+{
+ // We should dispatch only if expiration has been successful.
+ if (aReason != mozIStorageStatementCallback::REASON_FINISHED)
+ return NS_OK;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ (void)observerService->NotifyObservers(nullptr,
+ NS_PLACES_FAVICONS_EXPIRED_TOPIC_ID,
+ nullptr);
+ }
+
+ return NS_OK;
+}
diff --git a/components/places/src/nsFaviconService.h b/components/places/src/nsFaviconService.h
new file mode 100644
index 000000000..b2fcdbeaa
--- /dev/null
+++ b/components/places/src/nsFaviconService.h
@@ -0,0 +1,147 @@
+/* -*- 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/. */
+
+#ifndef nsFaviconService_h_
+#define nsFaviconService_h_
+
+#include "nsIFaviconService.h"
+#include "mozIAsyncFavicons.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsDataHashtable.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTHashtable.h"
+#include "nsToolkitCompsCID.h"
+#include "nsURIHashKey.h"
+#include "nsITimer.h"
+#include "Database.h"
+#include "mozilla/storage.h"
+#include "mozilla/Attributes.h"
+
+#include "FaviconHelpers.h"
+
+// Favicons bigger than this (in bytes) will not be stored in the database. We
+// expect that most 32x32 PNG favicons will be no larger due to compression.
+#define MAX_FAVICON_FILESIZE 3072 /* 3 KiB */
+
+// forward class definitions
+class mozIStorageStatementCallback;
+
+class UnassociatedIconHashKey : public nsURIHashKey
+{
+public:
+ explicit UnassociatedIconHashKey(const nsIURI* aURI)
+ : nsURIHashKey(aURI)
+ {
+ }
+ UnassociatedIconHashKey(const UnassociatedIconHashKey& aOther)
+ : nsURIHashKey(aOther)
+ {
+ NS_NOTREACHED("Do not call me!");
+ }
+ mozilla::places::IconData iconData;
+ PRTime created;
+};
+
+class nsFaviconService final : public nsIFaviconService
+ , public mozIAsyncFavicons
+ , public nsITimerCallback
+{
+public:
+ nsFaviconService();
+
+ /**
+ * Obtains the service's object.
+ */
+ static already_AddRefed<nsFaviconService> GetSingleton();
+
+ /**
+ * Initializes the service's object. This should only be called once.
+ */
+ nsresult Init();
+
+ /**
+ * Returns a cached pointer to the favicon service for consumers in the
+ * places directory.
+ */
+ static nsFaviconService* GetFaviconService()
+ {
+ if (!gFaviconService) {
+ nsCOMPtr<nsIFaviconService> serv =
+ do_GetService(NS_FAVICONSERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(serv, nullptr);
+ NS_ASSERTION(gFaviconService, "Should have static instance pointer now");
+ }
+ return gFaviconService;
+ }
+
+ // addition to API for strings to prevent excessive parsing of URIs
+ nsresult GetFaviconLinkForIconString(const nsCString& aIcon, nsIURI** aOutput);
+ void GetFaviconSpecForIconString(const nsCString& aIcon, nsACString& aOutput);
+
+ nsresult OptimizeFaviconImage(const uint8_t* aData, uint32_t aDataLen,
+ const nsACString& aMimeType,
+ nsACString& aNewData, nsACString& aNewMimeType);
+
+ /**
+ * Obtains the favicon data asynchronously.
+ *
+ * @param aFaviconURI
+ * The URI representing the favicon we are looking for.
+ * @param aCallback
+ * The callback where results or errors will be dispatch to. In the
+ * returned result, the favicon binary data will be at index 0, and the
+ * mime type will be at index 1.
+ */
+ nsresult GetFaviconDataAsync(nsIURI* aFaviconURI,
+ mozIStorageStatementCallback* aCallback);
+
+ /**
+ * Call to send out favicon changed notifications. Should only be called
+ * when there is data loaded for the favicon.
+ * @param aPageURI
+ * The URI of the page to notify about.
+ * @param aFaviconURI
+ * The moz-anno:favicon URI of the icon.
+ * @param aGUID
+ * The unique ID associated with the page.
+ */
+ void SendFaviconNotifications(nsIURI* aPageURI, nsIURI* aFaviconURI,
+ const nsACString& aGUID);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFAVICONSERVICE
+ NS_DECL_MOZIASYNCFAVICONS
+ NS_DECL_NSITIMERCALLBACK
+
+private:
+ ~nsFaviconService();
+
+ RefPtr<mozilla::places::Database> mDB;
+
+ nsCOMPtr<nsITimer> mExpireUnassociatedIconsTimer;
+
+ static nsFaviconService* gFaviconService;
+
+ /**
+ * A cached URI for the default icon. We return this a lot, and don't want to
+ * re-parse and normalize our unchanging string many times. Important: do
+ * not return this directly; use Clone() since callers may change the object
+ * they get back. May be null, in which case it needs initialization.
+ */
+ nsCOMPtr<nsIURI> mDefaultIcon;
+
+ uint32_t mFailedFaviconSerial;
+ nsDataHashtable<nsCStringHashKey, uint32_t> mFailedFavicons;
+
+ // This class needs access to the icons cache.
+ friend class mozilla::places::AsyncReplaceFaviconData;
+ nsTHashtable<UnassociatedIconHashKey> mUnassociatedIcons;
+};
+
+#define FAVICON_ANNOTATION_NAME "favicon"
+
+#endif // nsFaviconService_h_
diff --git a/components/places/src/nsLivemarkService.js b/components/places/src/nsLivemarkService.js
new file mode 100644
index 000000000..eeca7e139
--- /dev/null
+++ b/components/places/src/nsLivemarkService.js
@@ -0,0 +1,891 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, results: Cr, utils: Cu } = Components;
+
+// Modules and services.
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "asyncHistory", function () {
+ // Lazily add an history observer when it's actually needed.
+ PlacesUtils.history.addObserver(PlacesUtils.livemarks, true);
+ return PlacesUtils.asyncHistory;
+});
+
+// Constants
+
+// Delay between reloads of consecute livemarks.
+const RELOAD_DELAY_MS = 500;
+// Expire livemarks after this time.
+const EXPIRE_TIME_MS = 3600000; // 1 hour.
+// Expire livemarks after this time on error.
+const ONERROR_EXPIRE_TIME_MS = 300000; // 5 minutes.
+
+// Livemarks cache.
+
+XPCOMUtils.defineLazyGetter(this, "CACHE_SQL", () => {
+ function getAnnoSQLFragment(aAnnoParam) {
+ return `SELECT a.content
+ FROM moz_items_annos a
+ JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id
+ WHERE a.item_id = b.id
+ AND n.name = ${aAnnoParam}`;
+ }
+
+ return `SELECT b.id, b.title, b.parent As parentId, b.position AS 'index',
+ b.guid, b.dateAdded, b.lastModified, p.guid AS parentGuid,
+ ( ${getAnnoSQLFragment(":feedURI_anno")} ) AS feedURI,
+ ( ${getAnnoSQLFragment(":siteURI_anno")} ) AS siteURI
+ FROM moz_bookmarks b
+ JOIN moz_bookmarks p ON b.parent = p.id
+ JOIN moz_items_annos a ON a.item_id = b.id
+ JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id
+ WHERE b.type = :folder_type
+ AND n.name = :feedURI_anno`;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gLivemarksCachePromised",
+ Task.async(function* () {
+ let livemarksMap = new Map();
+ let conn = yield PlacesUtils.promiseDBConnection();
+ let rows = yield conn.executeCached(CACHE_SQL,
+ { folder_type: Ci.nsINavBookmarksService.TYPE_FOLDER,
+ feedURI_anno: PlacesUtils.LMANNO_FEEDURI,
+ siteURI_anno: PlacesUtils.LMANNO_SITEURI });
+ for (let row of rows) {
+ let siteURI = row.getResultByName("siteURI");
+ let livemark = new Livemark({
+ id: row.getResultByName("id"),
+ guid: row.getResultByName("guid"),
+ title: row.getResultByName("title"),
+ parentId: row.getResultByName("parentId"),
+ parentGuid: row.getResultByName("parentGuid"),
+ index: row.getResultByName("index"),
+ dateAdded: row.getResultByName("dateAdded"),
+ lastModified: row.getResultByName("lastModified"),
+ feedURI: NetUtil.newURI(row.getResultByName("feedURI")),
+ siteURI: siteURI ? NetUtil.newURI(siteURI) : null
+ });
+ livemarksMap.set(livemark.guid, livemark);
+ }
+ return livemarksMap;
+ })
+);
+
+/**
+ * Convert a Date object to a PRTime (microseconds).
+ *
+ * @param date
+ * the Date object to convert.
+ * @return microseconds from the epoch.
+ */
+function toPRTime(date) {
+ return date * 1000;
+}
+
+/**
+ * Convert a PRTime to a Date object.
+ *
+ * @param time
+ * microseconds from the epoch.
+ * @return a Date object or undefined if time was not defined.
+ */
+function toDate(time) {
+ return time ? new Date(parseInt(time / 1000)) : undefined;
+}
+
+// LivemarkService
+
+function LivemarkService() {
+ // Cleanup on shutdown.
+ Services.obs.addObserver(this, PlacesUtils.TOPIC_SHUTDOWN, true);
+
+ // Observe bookmarks but don't init the service just for that.
+ PlacesUtils.addLazyBookmarkObserver(this, true);
+}
+
+LivemarkService.prototype = {
+ // This is just an helper for code readability.
+ _promiseLivemarksMap: () => gLivemarksCachePromised,
+
+ _reloading: false,
+ _startReloadTimer(livemarksMap, forceUpdate, reloaded) {
+ if (this._reloadTimer) {
+ this._reloadTimer.cancel();
+ }
+ else {
+ this._reloadTimer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+ }
+
+ this._reloading = true;
+ this._reloadTimer.initWithCallback(() => {
+ // Find first livemark to be reloaded.
+ for (let [ guid, livemark ] of livemarksMap) {
+ if (!reloaded.has(guid)) {
+ reloaded.add(guid);
+ livemark.reload(forceUpdate);
+ this._startReloadTimer(livemarksMap, forceUpdate, reloaded);
+ return;
+ }
+ }
+ // All livemarks have been reloaded.
+ this._reloading = false;
+ }, RELOAD_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ // nsIObserver
+
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == PlacesUtils.TOPIC_SHUTDOWN) {
+ if (this._reloadTimer) {
+ this._reloading = false;
+ this._reloadTimer.cancel();
+ delete this._reloadTimer;
+ }
+
+ // Stop any ongoing network fetch.
+ this._promiseLivemarksMap().then(livemarksMap => {
+ for (let livemark of livemarksMap.values()) {
+ livemark.terminate();
+ }
+ });
+ }
+ },
+
+ // mozIAsyncLivemarks
+
+ addLivemark(aLivemarkInfo) {
+ if (!aLivemarkInfo) {
+ throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
+ }
+ let hasParentId = "parentId" in aLivemarkInfo;
+ let hasParentGuid = "parentGuid" in aLivemarkInfo;
+ let hasIndex = "index" in aLivemarkInfo;
+ // Must provide at least non-null parent guid/id, index and feedURI.
+ if ((!hasParentId && !hasParentGuid) ||
+ (hasParentId && aLivemarkInfo.parentId < 1) ||
+ (hasParentGuid &&!/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.parentGuid)) ||
+ (hasIndex && aLivemarkInfo.index < Ci.nsINavBookmarksService.DEFAULT_INDEX) ||
+ !(aLivemarkInfo.feedURI instanceof Ci.nsIURI) ||
+ (aLivemarkInfo.siteURI && !(aLivemarkInfo.siteURI instanceof Ci.nsIURI)) ||
+ (aLivemarkInfo.guid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid))) {
+ throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ return Task.spawn(function* () {
+ if (!aLivemarkInfo.parentGuid)
+ aLivemarkInfo.parentGuid = yield PlacesUtils.promiseItemGuid(aLivemarkInfo.parentId);
+
+ let livemarksMap = yield this._promiseLivemarksMap();
+
+ // Disallow adding a livemark inside another livemark.
+ if (livemarksMap.has(aLivemarkInfo.parentGuid)) {
+ throw new Components.Exception("Cannot create a livemark inside a livemark", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // Create a new livemark.
+ let folder = yield PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: aLivemarkInfo.parentGuid,
+ title: aLivemarkInfo.title,
+ index: aLivemarkInfo.index,
+ guid: aLivemarkInfo.guid,
+ dateAdded: toDate(aLivemarkInfo.dateAdded) || toDate(aLivemarkInfo.lastModified),
+ source: aLivemarkInfo.source,
+ });
+
+ // Set feed and site URI annotations.
+ let id = yield PlacesUtils.promiseItemId(folder.guid);
+
+ // Create the internal Livemark object.
+ let livemark = new Livemark({ id
+ , title: folder.title
+ , parentGuid: folder.parentGuid
+ , parentId: yield PlacesUtils.promiseItemId(folder.parentGuid)
+ , index: folder.index
+ , feedURI: aLivemarkInfo.feedURI
+ , siteURI: aLivemarkInfo.siteURI
+ , guid: folder.guid
+ , dateAdded: toPRTime(folder.dateAdded)
+ , lastModified: toPRTime(folder.lastModified)
+ });
+
+ livemark.writeFeedURI(aLivemarkInfo.feedURI, aLivemarkInfo.source);
+ if (aLivemarkInfo.siteURI) {
+ livemark.writeSiteURI(aLivemarkInfo.siteURI, aLivemarkInfo.source);
+ }
+
+ if (aLivemarkInfo.lastModified) {
+ yield PlacesUtils.bookmarks.update({ guid: folder.guid,
+ lastModified: toDate(aLivemarkInfo.lastModified),
+ source: aLivemarkInfo.source });
+ livemark.lastModified = aLivemarkInfo.lastModified;
+ }
+
+ livemarksMap.set(folder.guid, livemark);
+
+ return livemark;
+ }.bind(this));
+ },
+
+ removeLivemark(aLivemarkInfo) {
+ if (!aLivemarkInfo) {
+ throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
+ }
+ // Accept either a guid or an id.
+ let hasGuid = "guid" in aLivemarkInfo;
+ let hasId = "id" in aLivemarkInfo;
+ if ((hasGuid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) ||
+ (hasId && aLivemarkInfo.id < 1) ||
+ (!hasId && !hasGuid)) {
+ throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ return Task.spawn(function* () {
+ if (!aLivemarkInfo.guid)
+ aLivemarkInfo.guid = yield PlacesUtils.promiseItemGuid(aLivemarkInfo.id);
+
+ let livemarksMap = yield this._promiseLivemarksMap();
+ if (!livemarksMap.has(aLivemarkInfo.guid))
+ throw new Components.Exception("Invalid livemark", Cr.NS_ERROR_INVALID_ARG);
+
+ yield PlacesUtils.bookmarks.remove(aLivemarkInfo.guid,
+ { source: aLivemarkInfo.source });
+ }.bind(this));
+ },
+
+ reloadLivemarks(aForceUpdate) {
+ // Check if there's a currently running reload, to save some useless work.
+ let notWorthRestarting =
+ this._forceUpdate || // We're already forceUpdating.
+ !aForceUpdate; // The caller didn't request a forced update.
+ if (this._reloading && notWorthRestarting) {
+ // Ignore this call.
+ return;
+ }
+
+ this._promiseLivemarksMap().then(livemarksMap => {
+ this._forceUpdate = !!aForceUpdate;
+ // Livemarks reloads happen on a timer for performance reasons.
+ this._startReloadTimer(livemarksMap, this._forceUpdate, new Set());
+ });
+ },
+
+ getLivemark(aLivemarkInfo) {
+ if (!aLivemarkInfo) {
+ throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
+ }
+ // Accept either a guid or an id.
+ let hasGuid = "guid" in aLivemarkInfo;
+ let hasId = "id" in aLivemarkInfo;
+ if ((hasGuid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) ||
+ (hasId && aLivemarkInfo.id < 1) ||
+ (!hasId && !hasGuid)) {
+ throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ return Task.spawn(function*() {
+ if (!aLivemarkInfo.guid)
+ aLivemarkInfo.guid = yield PlacesUtils.promiseItemGuid(aLivemarkInfo.id);
+
+ let livemarksMap = yield this._promiseLivemarksMap();
+ if (!livemarksMap.has(aLivemarkInfo.guid))
+ throw new Components.Exception("Invalid livemark", Cr.NS_ERROR_INVALID_ARG);
+
+ return livemarksMap.get(aLivemarkInfo.guid);
+ }.bind(this));
+ },
+
+ // nsINavBookmarkObserver
+
+ onBeginUpdateBatch() {},
+ onEndUpdateBatch() {},
+ onItemVisited() {},
+ onItemAdded() {},
+
+ onItemChanged(id, property, isAnno, value, lastModified, itemType, parentId,
+ guid, parentGuid) {
+ if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
+ return;
+
+ this._promiseLivemarksMap().then(livemarksMap => {
+ if (livemarksMap.has(guid)) {
+ let livemark = livemarksMap.get(guid);
+ if (property == "title") {
+ livemark.title = value;
+ }
+ livemark.lastModified = lastModified;
+ }
+ });
+ },
+
+ onItemMoved(id, parentId, oldIndex, newParentId, newIndex, itemType, guid,
+ oldParentGuid, newParentGuid) {
+ if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
+ return;
+
+ this._promiseLivemarksMap().then(livemarksMap => {
+ if (livemarksMap.has(guid)) {
+ let livemark = livemarksMap.get(guid);
+ livemark.parentId = newParentId;
+ livemark.parentGuid = newParentGuid;
+ livemark.index = newIndex;
+ }
+ });
+ },
+
+ onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid) {
+ if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
+ return;
+
+ this._promiseLivemarksMap().then(livemarksMap => {
+ if (livemarksMap.has(guid)) {
+ let livemark = livemarksMap.get(guid);
+ livemark.terminate();
+ livemarksMap.delete(guid);
+ }
+ });
+ },
+
+ // nsINavHistoryObserver
+
+ onPageChanged() {},
+ onTitleChanged() {},
+ onDeleteVisits() {},
+
+ onClearHistory() {
+ this._promiseLivemarksMap().then(livemarksMap => {
+ for (let livemark of livemarksMap.values()) {
+ livemark.updateURIVisitedStatus(null, false);
+ }
+ });
+ },
+
+ onDeleteURI(aURI) {
+ this._promiseLivemarksMap().then(livemarksMap => {
+ for (let livemark of livemarksMap.values()) {
+ livemark.updateURIVisitedStatus(aURI, false);
+ }
+ });
+ },
+
+ onVisit(aURI) {
+ this._promiseLivemarksMap().then(livemarksMap => {
+ for (let livemark of livemarksMap.values()) {
+ livemark.updateURIVisitedStatus(aURI, true);
+ }
+ });
+ },
+
+ // nsISupports
+
+ classID: Components.ID("{dca61eb5-c7cd-4df1-b0fb-d0722baba251}"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(LivemarkService),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.mozIAsyncLivemarks
+ , Ci.nsINavBookmarkObserver
+ , Ci.nsINavHistoryObserver
+ , Ci.nsIObserver
+ , Ci.nsISupportsWeakReference
+ ])
+};
+
+// Livemark
+
+/**
+ * Object used internally to represent a livemark.
+ *
+ * @param aLivemarkInfo
+ * Object containing information on the livemark. If the livemark is
+ * not included in the object, a new livemark will be created.
+ *
+ * @note terminate() must be invoked before getting rid of this object.
+ */
+function Livemark(aLivemarkInfo)
+{
+ this.id = aLivemarkInfo.id;
+ this.guid = aLivemarkInfo.guid;
+ this.feedURI = aLivemarkInfo.feedURI;
+ this.siteURI = aLivemarkInfo.siteURI || null;
+ this.title = aLivemarkInfo.title;
+ this.parentId = aLivemarkInfo.parentId;
+ this.parentGuid = aLivemarkInfo.parentGuid;
+ this.index = aLivemarkInfo.index;
+ this.dateAdded = aLivemarkInfo.dateAdded;
+ this.lastModified = aLivemarkInfo.lastModified;
+
+ this._status = Ci.mozILivemark.STATUS_READY;
+
+ // Hash of resultObservers, hashed by container.
+ this._resultObservers = new Map();
+
+ // Sorted array of objects representing livemark children in the form
+ // { uri, title, visited }.
+ this._children = [];
+
+ // Keeps a separate array of nodes for each requesting container, hashed by
+ // the container itself.
+ this._nodes = new Map();
+
+ this.loadGroup = null;
+ this.expireTime = 0;
+}
+
+Livemark.prototype = {
+ get status() {
+ return this._status;
+ },
+ set status(val) {
+ if (this._status != val) {
+ this._status = val;
+ this._invalidateRegisteredContainers();
+ }
+ return this._status;
+ },
+
+ writeFeedURI(aFeedURI, aSource) {
+ PlacesUtils.annotations
+ .setItemAnnotation(this.id, PlacesUtils.LMANNO_FEEDURI,
+ aFeedURI.spec,
+ 0, PlacesUtils.annotations.EXPIRE_NEVER,
+ aSource);
+ this.feedURI = aFeedURI;
+ },
+
+ writeSiteURI(aSiteURI, aSource) {
+ if (!aSiteURI) {
+ PlacesUtils.annotations.removeItemAnnotation(this.id,
+ PlacesUtils.LMANNO_SITEURI,
+ aSource)
+ this.siteURI = null;
+ return;
+ }
+
+ // Security check the site URI against the feed URI principal.
+ let secMan = Services.scriptSecurityManager;
+ let feedPrincipal = secMan.createCodebasePrincipal(this.feedURI, {});
+ try {
+ secMan.checkLoadURIWithPrincipal(feedPrincipal, aSiteURI,
+ Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ }
+ catch (ex) {
+ return;
+ }
+
+ PlacesUtils.annotations
+ .setItemAnnotation(this.id, PlacesUtils.LMANNO_SITEURI,
+ aSiteURI.spec,
+ 0, PlacesUtils.annotations.EXPIRE_NEVER,
+ aSource);
+ this.siteURI = aSiteURI;
+ },
+
+ /**
+ * Tries to updates the livemark if needed.
+ * The update process is asynchronous.
+ *
+ * @param [optional] aForceUpdate
+ * If true will try to update the livemark even if its contents have
+ * not yet expired.
+ */
+ updateChildren(aForceUpdate) {
+ // Check if the livemark is already updating.
+ if (this.status == Ci.mozILivemark.STATUS_LOADING)
+ return;
+
+ // Check the TTL/expiration on this, to check if there is no need to update
+ // this livemark.
+ if (!aForceUpdate && this.children.length && this.expireTime > Date.now())
+ return;
+
+ this.status = Ci.mozILivemark.STATUS_LOADING;
+
+ // Setting the status notifies observers that may remove the livemark.
+ if (this._terminated)
+ return;
+
+ try {
+ // Create a load group for the request. This will allow us to
+ // automatically keep track of redirects, so we can always
+ // cancel the channel.
+ let loadgroup = Cc["@mozilla.org/network/load-group;1"].
+ createInstance(Ci.nsILoadGroup);
+ // Creating a CodeBasePrincipal and using it as the loadingPrincipal
+ // is *not* desired and is only tolerated within this file.
+ // TODO: Find the right OriginAttributes and pass something other
+ // than {} to .createCodeBasePrincipal().
+ let channel = NetUtil.newChannel({
+ uri: this.feedURI,
+ loadingPrincipal: Services.scriptSecurityManager.createCodebasePrincipal(this.feedURI, {}),
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_XMLHTTPREQUEST
+ }).QueryInterface(Ci.nsIHttpChannel);
+ channel.loadGroup = loadgroup;
+ channel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ channel.requestMethod = "GET";
+ channel.setRequestHeader("X-Moz", "livebookmarks", false);
+
+ // Stream the result to the feed parser with this listener
+ let listener = new LivemarkLoadListener(this);
+ channel.notificationCallbacks = listener;
+ channel.asyncOpen2(listener);
+
+ this.loadGroup = loadgroup;
+ }
+ catch (ex) {
+ this.status = Ci.mozILivemark.STATUS_FAILED;
+ }
+ },
+
+ reload(aForceUpdate) {
+ this.updateChildren(aForceUpdate);
+ },
+
+ get children() {
+ return this._children;
+ },
+ set children(val) {
+ this._children = val;
+
+ // Discard the previous cached nodes, new ones should be generated.
+ for (let container of this._resultObservers.keys()) {
+ this._nodes.delete(container);
+ }
+
+ // Update visited status for each entry.
+ for (let child of this._children) {
+ asyncHistory.isURIVisited(child.uri, (aURI, aIsVisited) => {
+ this.updateURIVisitedStatus(aURI, aIsVisited);
+ });
+ }
+
+ return this._children;
+ },
+
+ _isURIVisited(aURI) {
+ return this.children.some(child => child.uri.equals(aURI) && child.visited);
+ },
+
+ getNodesForContainer(aContainerNode) {
+ if (this._nodes.has(aContainerNode)) {
+ return this._nodes.get(aContainerNode);
+ }
+
+ let livemark = this;
+ let nodes = [];
+ let now = Date.now() * 1000;
+ for (let child of this.children) {
+ // Workaround for bug 449811.
+ let localChild = child;
+ let node = {
+ // The QueryInterface is needed cause aContainerNode is a jsval.
+ // This is required to avoid issues with scriptable wrappers that would
+ // not allow the view to correctly set expandos.
+ get parent() {
+ return aContainerNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
+ },
+ get parentResult() {
+ return this.parent.parentResult;
+ },
+ get uri() {
+ return localChild.uri.spec;
+ },
+ get type() {
+ return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
+ },
+ get title() {
+ return localChild.title;
+ },
+ get accessCount() {
+ return Number(livemark._isURIVisited(NetUtil.newURI(this.uri)));
+ },
+ get time() {
+ return 0;
+ },
+ get icon() {
+ return "";
+ },
+ get indentLevel() {
+ return this.parent.indentLevel + 1;
+ },
+ get bookmarkIndex() {
+ return -1;
+ },
+ get itemId() {
+ return -1;
+ },
+ get dateAdded() {
+ return now;
+ },
+ get lastModified() {
+ return now;
+ },
+ get tags() {
+ return PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(this.uri)).join(", ");
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryResultNode])
+ };
+ nodes.push(node);
+ }
+ this._nodes.set(aContainerNode, nodes);
+ return nodes;
+ },
+
+ registerForUpdates(aContainerNode, aResultObserver) {
+ this._resultObservers.set(aContainerNode, aResultObserver);
+ },
+
+ unregisterForUpdates(aContainerNode) {
+ this._resultObservers.delete(aContainerNode);
+ this._nodes.delete(aContainerNode);
+ },
+
+ _invalidateRegisteredContainers() {
+ for (let [ container, observer ] of this._resultObservers) {
+ observer.invalidateContainer(container);
+ }
+ },
+
+ /**
+ * Updates the visited status of nodes observing this livemark.
+ *
+ * @param aURI
+ * If provided will update nodes having the given uri,
+ * otherwise any node.
+ * @param aVisitedStatus
+ * Whether the nodes should be set as visited.
+ */
+ updateURIVisitedStatus(aURI, aVisitedStatus) {
+ for (let child of this.children) {
+ if (!aURI || child.uri.equals(aURI)) {
+ child.visited = aVisitedStatus;
+ }
+ }
+
+ for (let [ container, observer ] of this._resultObservers) {
+ if (this._nodes.has(container)) {
+ let nodes = this._nodes.get(container);
+ for (let node of nodes) {
+ // Workaround for bug 449811.
+ let localObserver = observer;
+ let localNode = node;
+ if (!aURI || node.uri == aURI.spec) {
+ Services.tm.mainThread.dispatch(() => {
+ localObserver.nodeHistoryDetailsChanged(localNode, 0, aVisitedStatus);
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Terminates the livemark entry, cancelling any ongoing load.
+ * Must be invoked before destroying the entry.
+ */
+ terminate() {
+ // Avoid handling any updateChildren request from now on.
+ this._terminated = true;
+ this.abort();
+ },
+
+ /**
+ * Aborts the livemark loading if needed.
+ */
+ abort() {
+ this.status = Ci.mozILivemark.STATUS_FAILED;
+ if (this.loadGroup) {
+ this.loadGroup.cancel(Cr.NS_BINDING_ABORTED);
+ this.loadGroup = null;
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.mozILivemark
+ ])
+}
+
+// LivemarkLoadListener
+
+/**
+ * Object used internally to handle loading a livemark's contents.
+ *
+ * @param aLivemark
+ * The Livemark that is loading.
+ */
+function LivemarkLoadListener(aLivemark) {
+ this._livemark = aLivemark;
+ this._processor = null;
+ this._isAborted = false;
+ this._ttl = EXPIRE_TIME_MS;
+}
+
+LivemarkLoadListener.prototype = {
+ abort(aException) {
+ if (!this._isAborted) {
+ this._isAborted = true;
+ this._livemark.abort();
+ this._setResourceTTL(ONERROR_EXPIRE_TIME_MS);
+ }
+ },
+
+ // nsIFeedResultListener
+ handleResult(aResult) {
+ if (this._isAborted) {
+ return;
+ }
+
+ try {
+ // We need this to make sure the item links are safe
+ let feedPrincipal =
+ Services.scriptSecurityManager
+ .createCodebasePrincipal(this._livemark.feedURI, {});
+
+ // Enforce well-formedness because the existing code does
+ if (!aResult || !aResult.doc || aResult.bozo) {
+ throw new Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+
+ let feed = aResult.doc.QueryInterface(Ci.nsIFeed);
+ let siteURI = this._livemark.siteURI;
+ if (feed.link && (!siteURI || !feed.link.equals(siteURI))) {
+ siteURI = feed.link;
+ this._livemark.writeSiteURI(siteURI);
+ }
+
+ // Insert feed items.
+ let livemarkChildren = [];
+ for (let i = 0; i < feed.items.length; ++i) {
+ let entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
+ let uri = entry.link || siteURI;
+ if (!uri) {
+ continue;
+ }
+
+ try {
+ Services.scriptSecurityManager
+ .checkLoadURIWithPrincipal(feedPrincipal, uri,
+ Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ }
+ catch (ex) {
+ continue;
+ }
+
+ let title = entry.title ? entry.title.plainText() : "";
+ livemarkChildren.push({ uri: uri, title: title, visited: false });
+ }
+
+ this._livemark.children = livemarkChildren;
+ }
+ catch (ex) {
+ this.abort(ex);
+ }
+ finally {
+ this._processor.listener = null;
+ this._processor = null;
+ }
+ },
+
+ onDataAvailable(aRequest, aContext, aInputStream, aSourceOffset, aCount) {
+ if (this._processor) {
+ this._processor.onDataAvailable(aRequest, aContext, aInputStream,
+ aSourceOffset, aCount);
+ }
+ },
+
+ onStartRequest(aRequest, aContext) {
+ if (this._isAborted) {
+ throw new Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
+ }
+
+ let channel = aRequest.QueryInterface(Ci.nsIChannel);
+ try {
+ // 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(aRequest, aContext);
+ }
+ catch (ex) {
+ Components.utils.reportError("Livemark Service: feed processor received an invalid channel for " + channel.URI.spec);
+ this.abort(ex);
+ }
+ },
+
+ onStopRequest(aRequest, aContext, aStatus) {
+ if (!Components.isSuccessCode(aStatus)) {
+ this.abort();
+ return;
+ }
+
+ // Set an expiration on the livemark, to reloading the data in future.
+ try {
+ if (this._processor) {
+ this._processor.onStopRequest(aRequest, aContext, aStatus);
+ }
+
+ // Calculate a new ttl
+ let channel = aRequest.QueryInterface(Ci.nsICachingChannel);
+ if (channel) {
+ let entryInfo = channel.cacheToken.QueryInterface(Ci.nsICacheEntry);
+ if (entryInfo) {
+ // nsICacheEntry returns value as seconds.
+ let expireTime = entryInfo.expirationTime * 1000;
+ let nowTime = Date.now();
+ // Note, expireTime can be 0, see bug 383538.
+ if (expireTime > nowTime) {
+ this._setResourceTTL(Math.max((expireTime - nowTime),
+ EXPIRE_TIME_MS));
+ return;
+ }
+ }
+ }
+ this._setResourceTTL(EXPIRE_TIME_MS);
+ }
+ catch (ex) {
+ this.abort(ex);
+ }
+ finally {
+ if (this._livemark.status == Ci.mozILivemark.STATUS_LOADING) {
+ this._livemark.status = Ci.mozILivemark.STATUS_READY;
+ }
+ this._livemark.locked = false;
+ this._livemark.loadGroup = null;
+ }
+ },
+
+ _setResourceTTL(aMilliseconds) {
+ this._livemark.expireTime = Date.now() + aMilliseconds;
+ },
+
+ // nsIInterfaceRequestor
+ getInterface(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ // nsISupports
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIFeedResultListener
+ , Ci.nsIStreamListener
+ , Ci.nsIRequestObserver
+ , Ci.nsIInterfaceRequestor
+ ])
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LivemarkService]);
diff --git a/components/places/src/nsMaybeWeakPtr.h b/components/places/src/nsMaybeWeakPtr.h
new file mode 100644
index 000000000..ce52e5090
--- /dev/null
+++ b/components/places/src/nsMaybeWeakPtr.h
@@ -0,0 +1,145 @@
+/* -*- 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/. */
+
+#ifndef nsMaybeWeakPtr_h_
+#define nsMaybeWeakPtr_h_
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+#include "nsTArray.h"
+#include "nsCycleCollectionNoteChild.h"
+
+// nsMaybeWeakPtr is a helper object to hold a strong-or-weak reference
+// to the template class. It's pretty minimal, but sufficient.
+
+template<class T>
+class nsMaybeWeakPtr
+{
+public:
+ MOZ_IMPLICIT nsMaybeWeakPtr(nsISupports* aRef) : mPtr(aRef) {}
+ MOZ_IMPLICIT nsMaybeWeakPtr(const nsCOMPtr<nsIWeakReference>& aRef) : mPtr(aRef) {}
+ MOZ_IMPLICIT nsMaybeWeakPtr(const nsCOMPtr<T>& aRef) : mPtr(aRef) {}
+
+ bool operator==(const nsMaybeWeakPtr<T> &other) const {
+ return mPtr == other.mPtr;
+ }
+
+ nsISupports* GetRawValue() const { return mPtr.get(); }
+
+ const nsCOMPtr<T> GetValue() const;
+
+private:
+ nsCOMPtr<nsISupports> mPtr;
+};
+
+// nsMaybeWeakPtrArray is an array of MaybeWeakPtr objects, that knows how to
+// grab a weak reference to a given object if requested. It only allows a
+// given object to appear in the array once.
+
+template<class T>
+class nsMaybeWeakPtrArray : public nsTArray<nsMaybeWeakPtr<T>>
+{
+ typedef nsTArray<nsMaybeWeakPtr<T>> MaybeWeakArray;
+
+public:
+ nsresult AppendWeakElement(T* aElement, bool aOwnsWeak)
+ {
+ nsCOMPtr<nsISupports> ref;
+ if (aOwnsWeak) {
+ ref = do_GetWeakReference(aElement);
+ } else {
+ ref = aElement;
+ }
+
+ if (MaybeWeakArray::Contains(ref.get())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (!MaybeWeakArray::AppendElement(ref)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+ }
+
+ nsresult RemoveWeakElement(T* aElement)
+ {
+ if (MaybeWeakArray::RemoveElement(aElement)) {
+ return NS_OK;
+ }
+
+ // Don't use do_GetWeakReference; it should only be called if we know
+ // the object supports weak references.
+ nsCOMPtr<nsISupportsWeakReference> supWeakRef = do_QueryInterface(aElement);
+ NS_ENSURE_TRUE(supWeakRef, NS_ERROR_INVALID_ARG);
+
+ nsCOMPtr<nsIWeakReference> weakRef;
+ nsresult rv = supWeakRef->GetWeakReference(getter_AddRefs(weakRef));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (MaybeWeakArray::RemoveElement(weakRef)) {
+ return NS_OK;
+ }
+
+ return NS_ERROR_INVALID_ARG;
+ }
+};
+
+template<class T>
+const nsCOMPtr<T>
+nsMaybeWeakPtr<T>::GetValue() const
+{
+ if (!mPtr) {
+ return nullptr;
+ }
+
+ nsresult rv;
+ nsCOMPtr<T> ref = do_QueryInterface(mPtr, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ return ref;
+ }
+
+ nsCOMPtr<nsIWeakReference> weakRef = do_QueryInterface(mPtr);
+ if (weakRef) {
+ ref = do_QueryReferent(weakRef, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ return ref;
+ }
+ }
+
+ return nullptr;
+}
+
+template <typename T>
+inline void
+ImplCycleCollectionUnlink(nsMaybeWeakPtrArray<T>& aField)
+{
+ aField.Clear();
+}
+
+template <typename E>
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
+ nsMaybeWeakPtrArray<E>& aField,
+ const char* aName,
+ uint32_t aFlags = 0)
+{
+ aFlags |= CycleCollectionEdgeNameArrayFlag;
+ size_t length = aField.Length();
+ for (size_t i = 0; i < length; ++i) {
+ CycleCollectionNoteChild(aCallback, aField[i].GetRawValue(), aName, aFlags);
+ }
+}
+
+// Call a method on each element in the array, but only if the element is
+// non-null.
+
+#define ENUMERATE_WEAKARRAY(array, type, method) \
+ for (uint32_t array_idx = 0; array_idx < array.Length(); ++array_idx) { \
+ const nsCOMPtr<type> &e = array.ElementAt(array_idx).GetValue(); \
+ if (e) \
+ e->method; \
+ }
+
+#endif
diff --git a/components/places/src/nsNavBookmarks.cpp b/components/places/src/nsNavBookmarks.cpp
new file mode 100644
index 000000000..693aaf5db
--- /dev/null
+++ b/components/places/src/nsNavBookmarks.cpp
@@ -0,0 +1,2924 @@
+/* -*- 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 "nsNavBookmarks.h"
+
+#include "nsNavHistory.h"
+#include "nsAnnotationService.h"
+#include "nsPlacesMacros.h"
+#include "Helpers.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsNetUtil.h"
+#include "nsUnicharUtils.h"
+#include "nsPrintfCString.h"
+#include "prprf.h"
+#include "mozilla/storage.h"
+
+#include "GeckoProfiler.h"
+
+using namespace mozilla;
+
+// These columns sit to the right of the kGetInfoIndex_* columns.
+const int32_t nsNavBookmarks::kGetChildrenIndex_Guid = 18;
+const int32_t nsNavBookmarks::kGetChildrenIndex_Position = 19;
+const int32_t nsNavBookmarks::kGetChildrenIndex_Type = 20;
+const int32_t nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
+
+using namespace mozilla::places;
+
+PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
+
+#define BOOKMARKS_ANNO_PREFIX "bookmarks/"
+#define BOOKMARKS_TOOLBAR_FOLDER_ANNO NS_LITERAL_CSTRING(BOOKMARKS_ANNO_PREFIX "toolbarFolder")
+#define FEED_URI_ANNO NS_LITERAL_CSTRING("livemark/feedURI")
+
+
+namespace {
+
+#define SKIP_TAGS(condition) ((condition) ? SkipTags : DontSkip)
+
+bool DontSkip(nsCOMPtr<nsINavBookmarkObserver> obs) { return false; }
+bool SkipTags(nsCOMPtr<nsINavBookmarkObserver> obs) {
+ bool skipTags = false;
+ (void) obs->GetSkipTags(&skipTags);
+ return skipTags;
+}
+bool SkipDescendants(nsCOMPtr<nsINavBookmarkObserver> obs) {
+ bool skipDescendantsOnItemRemoval = false;
+ (void) obs->GetSkipTags(&skipDescendantsOnItemRemoval);
+ return skipDescendantsOnItemRemoval;
+}
+
+template<typename Method, typename DataType>
+class AsyncGetBookmarksForURI : public AsyncStatementCallback
+{
+public:
+ AsyncGetBookmarksForURI(nsNavBookmarks* aBookmarksSvc,
+ Method aCallback,
+ const DataType& aData)
+ : mBookmarksSvc(aBookmarksSvc)
+ , mCallback(aCallback)
+ , mData(aData)
+ {
+ }
+
+ void Init()
+ {
+ RefPtr<Database> DB = Database::GetDatabase();
+ if (DB) {
+ nsCOMPtr<mozIStorageAsyncStatement> stmt = DB->GetAsyncStatement(
+ "/* do not warn (bug 1175249) */ "
+ "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
+ "FROM moz_bookmarks b "
+ "JOIN moz_bookmarks t on t.id = b.parent "
+ "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
+ "ORDER BY b.lastModified DESC, b.id DESC "
+ );
+ if (stmt) {
+ (void)URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
+ mData.bookmark.url);
+ nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
+ (void)stmt->ExecuteAsync(this, getter_AddRefs(pendingStmt));
+ }
+ }
+ }
+
+ NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet)
+ {
+ nsCOMPtr<mozIStorageRow> row;
+ while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
+ // Skip tags, for the use-cases of this async getter they are useless.
+ int64_t grandParentId, tagsFolderId;
+ nsresult rv = row->GetInt64(5, &grandParentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mBookmarksSvc->GetTagsFolder(&tagsFolderId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (grandParentId == tagsFolderId) {
+ continue;
+ }
+
+ mData.bookmark.grandParentId = grandParentId;
+ rv = row->GetInt64(0, &mData.bookmark.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = row->GetUTF8String(1, mData.bookmark.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = row->GetInt64(2, &mData.bookmark.parentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // lastModified (3) should not be set for the use-cases of this getter.
+ rv = row->GetUTF8String(4, mData.bookmark.parentGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mCallback) {
+ ((*mBookmarksSvc).*mCallback)(mData);
+ }
+ }
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsNavBookmarks> mBookmarksSvc;
+ Method mCallback;
+ DataType mData;
+};
+
+} // namespace
+
+
+nsNavBookmarks::nsNavBookmarks()
+ : mItemCount(0)
+ , mRoot(0)
+ , mMenuRoot(0)
+ , mTagsRoot(0)
+ , mUnfiledRoot(0)
+ , mToolbarRoot(0)
+ , mMobileRoot(0)
+ , mCanNotify(false)
+ , mCacheObservers("bookmark-observers")
+ , mBatching(false)
+{
+ NS_ASSERTION(!gBookmarksService,
+ "Attempting to create two instances of the service!");
+ gBookmarksService = this;
+}
+
+
+nsNavBookmarks::~nsNavBookmarks()
+{
+ NS_ASSERTION(gBookmarksService == this,
+ "Deleting a non-singleton instance of the service");
+ if (gBookmarksService == this)
+ gBookmarksService = nullptr;
+}
+
+
+NS_IMPL_ISUPPORTS(nsNavBookmarks
+, nsINavBookmarksService
+, nsINavHistoryObserver
+, nsIAnnotationObserver
+, nsIObserver
+, nsISupportsWeakReference
+)
+
+
+Atomic<int64_t> nsNavBookmarks::sLastInsertedItemId(0);
+
+
+void // static
+nsNavBookmarks::StoreLastInsertedId(const nsACString& aTable,
+ const int64_t aLastInsertedId) {
+ MOZ_ASSERT(aTable.EqualsLiteral("moz_bookmarks"));
+ sLastInsertedItemId = aLastInsertedId;
+}
+
+
+nsresult
+nsNavBookmarks::Init()
+{
+ mDB = Database::GetDatabase();
+ NS_ENSURE_STATE(mDB);
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ (void)os->AddObserver(this, TOPIC_PLACES_SHUTDOWN, true);
+ (void)os->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
+ }
+
+ nsresult rv = ReadRoots();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCanNotify = true;
+
+ // Observe annotations.
+ nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
+ NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
+ annosvc->AddObserver(this);
+
+ // Allows us to notify on title changes. MUST BE LAST so it is impossible
+ // to fail after this call, or the history service will have a reference to
+ // us and we won't go away.
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(history);
+ history->AddObserver(this, true);
+
+ // DO NOT PUT STUFF HERE that can fail. See observer comment above.
+
+ return NS_OK;
+}
+
+nsresult
+nsNavBookmarks::ReadRoots()
+{
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mDB->MainConn()->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT guid, id FROM moz_bookmarks WHERE guid IN ( "
+ "'root________', 'menu________', 'toolbar_____', "
+ "'tags________', 'unfiled_____', 'mobile______' )"
+ ), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ nsAutoCString guid;
+ rv = stmt->GetUTF8String(0, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t id;
+ rv = stmt->GetInt64(1, &id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (guid.EqualsLiteral("root________")) {
+ mRoot = id;
+ }
+ else if (guid.EqualsLiteral("menu________")) {
+ mMenuRoot = id;
+ }
+ else if (guid.EqualsLiteral("toolbar_____")) {
+ mToolbarRoot = id;
+ }
+ else if (guid.EqualsLiteral("tags________")) {
+ mTagsRoot = id;
+ }
+ else if (guid.EqualsLiteral("unfiled_____")) {
+ mUnfiledRoot = id;
+ }
+ else if (guid.EqualsLiteral("mobile______")) {
+ mMobileRoot = id;
+ }
+ }
+
+ if (!mRoot || !mMenuRoot || !mToolbarRoot || !mTagsRoot || !mUnfiledRoot ||
+ !mMobileRoot)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+// nsNavBookmarks::IsBookmarkedInDatabase
+//
+// This checks to see if the specified place_id is actually bookmarked.
+
+nsresult
+nsNavBookmarks::IsBookmarkedInDatabase(int64_t aPlaceId,
+ bool* aIsBookmarked)
+{
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT 1 FROM moz_bookmarks WHERE fk = :page_id"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->ExecuteStep(aIsBookmarked);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::AdjustIndices(int64_t aFolderId,
+ int32_t aStartIndex,
+ int32_t aEndIndex,
+ int32_t aDelta)
+{
+ NS_ASSERTION(aStartIndex >= 0 && aEndIndex <= INT32_MAX &&
+ aStartIndex <= aEndIndex, "Bad indices");
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "UPDATE moz_bookmarks SET position = position + :delta "
+ "WHERE parent = :parent "
+ "AND position BETWEEN :from_index AND :to_index"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("from_index"), aStartIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("to_index"), aEndIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetPlacesRoot(int64_t* aRoot)
+{
+ *aRoot = mRoot;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetBookmarksMenuFolder(int64_t* aRoot)
+{
+ *aRoot = mMenuRoot;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetToolbarFolder(int64_t* aFolderId)
+{
+ *aFolderId = mToolbarRoot;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetTagsFolder(int64_t* aRoot)
+{
+ *aRoot = mTagsRoot;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetUnfiledBookmarksFolder(int64_t* aRoot)
+{
+ *aRoot = mUnfiledRoot;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetMobileFolder(int64_t* aRoot)
+{
+ *aRoot = mMobileRoot;
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::InsertBookmarkInDB(int64_t aPlaceId,
+ enum ItemType aItemType,
+ int64_t aParentId,
+ int32_t aIndex,
+ const nsACString& aTitle,
+ PRTime aDateAdded,
+ PRTime aLastModified,
+ const nsACString& aParentGuid,
+ int64_t aGrandParentId,
+ nsIURI* aURI,
+ uint16_t aSource,
+ int64_t* _itemId,
+ nsACString& _guid)
+{
+ // Check for a valid itemId.
+ MOZ_ASSERT(_itemId && (*_itemId == -1 || *_itemId > 0));
+ // Check for a valid placeId.
+ MOZ_ASSERT(aPlaceId && (aPlaceId == -1 || aPlaceId > 0));
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "INSERT INTO moz_bookmarks "
+ "(id, fk, type, parent, position, title, "
+ "dateAdded, lastModified, guid) "
+ "VALUES (:item_id, :page_id, :item_type, :parent, :item_index, "
+ ":item_title, :date_added, :last_modified, "
+ ":item_guid)"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv;
+ if (*_itemId != -1)
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), *_itemId);
+ else
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_id"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aPlaceId != -1)
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
+ else
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_id"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), aItemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aParentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Support NULL titles.
+ if (aTitle.IsVoid())
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_title"));
+ else
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"), aTitle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), aDateAdded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLastModified) {
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"),
+ aLastModified);
+ }
+ else {
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), aDateAdded);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Could use IsEmpty because our callers check for GUID validity,
+ // but it doesn't hurt.
+ if (_guid.Length() == 12) {
+ MOZ_ASSERT(IsValidGUID(_guid));
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), _guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ nsAutoCString guid;
+ rv = GenerateGUID(guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ _guid.Assign(guid);
+ }
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*_itemId == -1) {
+ *_itemId = sLastInsertedItemId;
+ }
+
+ if (aParentId > 0) {
+ // Update last modified date of the ancestors.
+ // TODO (bug 408991): Doing this for all ancestors would be slow without a
+ // nested tree, so for now update only the parent.
+ rv = SetItemDateInternal(LAST_MODIFIED, aParentId, aDateAdded);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Add a cache entry since we know everything about this bookmark.
+ BookmarkData bookmark;
+ bookmark.id = *_itemId;
+ bookmark.guid.Assign(_guid);
+ if (aTitle.IsVoid()) {
+ bookmark.title.SetIsVoid(true);
+ }
+ else {
+ bookmark.title.Assign(aTitle);
+ }
+ bookmark.position = aIndex;
+ bookmark.placeId = aPlaceId;
+ bookmark.parentId = aParentId;
+ bookmark.type = aItemType;
+ bookmark.dateAdded = aDateAdded;
+ if (aLastModified)
+ bookmark.lastModified = aLastModified;
+ else
+ bookmark.lastModified = aDateAdded;
+ if (aURI) {
+ rv = aURI->GetSpec(bookmark.url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ bookmark.parentGuid = aParentGuid;
+ bookmark.grandParentId = aGrandParentId;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavBookmarks::InsertBookmark(int64_t aFolder,
+ nsIURI* aURI,
+ int32_t aIndex,
+ const nsACString& aTitle,
+ const nsACString& aGUID,
+ uint16_t aSource,
+ int64_t* aNewBookmarkId)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(aNewBookmarkId);
+ NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
+
+ if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
+ return NS_ERROR_INVALID_ARG;
+
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ int64_t placeId;
+ nsAutoCString placeGuid;
+ nsresult rv = history->GetOrCreateIdForPage(aURI, &placeId, placeGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the correct index for insertion. This also ensures the parent exists.
+ int32_t index, folderCount;
+ int64_t grandParentId;
+ nsAutoCString folderGuid;
+ rv = FetchFolderInfo(aFolder, &folderCount, folderGuid, &grandParentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
+ aIndex >= folderCount) {
+ index = folderCount;
+ }
+ else {
+ index = aIndex;
+ // Create space for the insertion.
+ rv = AdjustIndices(aFolder, index, INT32_MAX, 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aNewBookmarkId = -1;
+ PRTime dateAdded = RoundedPRNow();
+ nsAutoCString guid(aGUID);
+ nsCString title;
+ TruncateTitle(aTitle, title);
+
+ rv = InsertBookmarkInDB(placeId, BOOKMARK, aFolder, index, title, dateAdded,
+ 0, folderGuid, grandParentId, aURI, aSource,
+ aNewBookmarkId, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If not a tag, recalculate frecency for this entry, since it changed.
+ if (grandParentId != mTagsRoot) {
+ rv = history->UpdateFrecency(placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ SKIP_TAGS(grandParentId == mTagsRoot),
+ OnItemAdded(*aNewBookmarkId, aFolder, index,
+ TYPE_BOOKMARK, aURI, title, dateAdded,
+ guid, folderGuid, aSource));
+
+ // If the bookmark has been added to a tag container, notify all
+ // bookmark-folder result nodes which contain a bookmark for the new
+ // bookmark's url.
+ if (grandParentId == mTagsRoot) {
+ // Notify a tags change to all bookmarks for this URI.
+ nsTArray<BookmarkData> bookmarks;
+ rv = GetBookmarksForURI(aURI, bookmarks);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
+ // Check that bookmarks doesn't include the current tag itemId.
+ MOZ_ASSERT(bookmarks[i].id != *aNewBookmarkId);
+
+ NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ DontSkip,
+ OnItemChanged(bookmarks[i].id,
+ NS_LITERAL_CSTRING("tags"),
+ false,
+ EmptyCString(),
+ bookmarks[i].lastModified,
+ TYPE_BOOKMARK,
+ bookmarks[i].parentId,
+ bookmarks[i].guid,
+ bookmarks[i].parentGuid,
+ EmptyCString(),
+ aSource));
+ }
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::RemoveItem(int64_t aItemId, uint16_t aSource)
+{
+ PROFILER_LABEL("nsNavBookmarks", "RemoveItem",
+ js::ProfileEntry::Category::OTHER);
+
+ NS_ENSURE_ARG(!IsRoot(aItemId));
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+
+ // First, if not a tag, remove item annotations.
+ if (bookmark.parentId != mTagsRoot &&
+ bookmark.grandParentId != mTagsRoot) {
+ nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
+ NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
+ rv = annosvc->RemoveItemAnnotations(bookmark.id, aSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (bookmark.type == TYPE_FOLDER) {
+ // Remove all of the folder's children.
+ rv = RemoveFolderChildren(bookmark.id, aSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "DELETE FROM moz_bookmarks WHERE id = :item_id"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Fix indices in the parent.
+ if (bookmark.position != DEFAULT_INDEX) {
+ rv = AdjustIndices(bookmark.parentId,
+ bookmark.position + 1, INT32_MAX, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bookmark.lastModified = RoundedPRNow();
+ rv = SetItemDateInternal(LAST_MODIFIED, bookmark.parentId,
+ bookmark.lastModified);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ if (bookmark.type == TYPE_BOOKMARK) {
+ // If not a tag, recalculate frecency for this entry, since it changed.
+ if (bookmark.grandParentId != mTagsRoot) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ rv = history->UpdateFrecency(bookmark.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // A broken url should not interrupt the removal process.
+ (void)NS_NewURI(getter_AddRefs(uri), bookmark.url);
+ // We cannot assert since some automated tests are checking this path.
+ NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveItem");
+ }
+
+ NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ SKIP_TAGS(bookmark.parentId == mTagsRoot ||
+ bookmark.grandParentId == mTagsRoot),
+ OnItemRemoved(bookmark.id,
+ bookmark.parentId,
+ bookmark.position,
+ bookmark.type,
+ uri,
+ bookmark.guid,
+ bookmark.parentGuid,
+ aSource));
+
+ if (bookmark.type == TYPE_BOOKMARK && bookmark.grandParentId == mTagsRoot &&
+ uri) {
+ // If the removed bookmark was child of a tag container, notify a tags
+ // change to all bookmarks for this URI.
+ nsTArray<BookmarkData> bookmarks;
+ rv = GetBookmarksForURI(uri, bookmarks);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
+ NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ DontSkip,
+ OnItemChanged(bookmarks[i].id,
+ NS_LITERAL_CSTRING("tags"),
+ false,
+ EmptyCString(),
+ bookmarks[i].lastModified,
+ TYPE_BOOKMARK,
+ bookmarks[i].parentId,
+ bookmarks[i].guid,
+ bookmarks[i].parentGuid,
+ EmptyCString(),
+ aSource));
+ }
+
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::CreateFolder(int64_t aParent, const nsACString& aName,
+ int32_t aIndex, const nsACString& aGUID,
+ uint16_t aSource, int64_t* aNewFolder)
+{
+ // NOTE: aParent can be null for root creation, so not checked
+ NS_ENSURE_ARG_POINTER(aNewFolder);
+
+ if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
+ return NS_ERROR_INVALID_ARG;
+
+ // CreateContainerWithID returns the index of the new folder, but that's not
+ // used here. To avoid any risk of corrupting data should this function
+ // be changed, we'll use a local variable to hold it. The true argument
+ // will cause notifications to be sent to bookmark observers.
+ int32_t localIndex = aIndex;
+ nsresult rv = CreateContainerWithID(-1, aParent, aName, true, &localIndex,
+ aGUID, aSource, aNewFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+bool nsNavBookmarks::IsLivemark(int64_t aFolderId)
+{
+ nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
+ NS_ENSURE_TRUE(annosvc, false);
+ bool isLivemark;
+ nsresult rv = annosvc->ItemHasAnnotation(aFolderId,
+ FEED_URI_ANNO,
+ &isLivemark);
+ NS_ENSURE_SUCCESS(rv, false);
+ return isLivemark;
+}
+
+nsresult
+nsNavBookmarks::CreateContainerWithID(int64_t aItemId,
+ int64_t aParent,
+ const nsACString& aTitle,
+ bool aIsBookmarkFolder,
+ int32_t* aIndex,
+ const nsACString& aGUID,
+ uint16_t aSource,
+ int64_t* aNewFolder)
+{
+ NS_ENSURE_ARG_MIN(*aIndex, nsINavBookmarksService::DEFAULT_INDEX);
+
+ // Get the correct index for insertion. This also ensures the parent exists.
+ int32_t index, folderCount;
+ int64_t grandParentId;
+ nsAutoCString folderGuid;
+ nsresult rv = FetchFolderInfo(aParent, &folderCount, folderGuid, &grandParentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+
+ if (*aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
+ *aIndex >= folderCount) {
+ index = folderCount;
+ } else {
+ index = *aIndex;
+ // Create space for the insertion.
+ rv = AdjustIndices(aParent, index, INT32_MAX, 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aNewFolder = aItemId;
+ PRTime dateAdded = RoundedPRNow();
+ nsAutoCString guid(aGUID);
+ nsCString title;
+ TruncateTitle(aTitle, title);
+
+ rv = InsertBookmarkInDB(-1, FOLDER, aParent, index,
+ title, dateAdded, 0, folderGuid, grandParentId,
+ nullptr, aSource, aNewFolder, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ SKIP_TAGS(aParent == mTagsRoot),
+ OnItemAdded(*aNewFolder, aParent, index, FOLDER,
+ nullptr, title, dateAdded, guid,
+ folderGuid, aSource));
+
+ *aIndex = index;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::InsertSeparator(int64_t aParent,
+ int32_t aIndex,
+ const nsACString& aGUID,
+ uint16_t aSource,
+ int64_t* aNewItemId)
+{
+ NS_ENSURE_ARG_MIN(aParent, 1);
+ NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
+ NS_ENSURE_ARG_POINTER(aNewItemId);
+
+ if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
+ return NS_ERROR_INVALID_ARG;
+
+ // Get the correct index for insertion. This also ensures the parent exists.
+ int32_t index, folderCount;
+ int64_t grandParentId;
+ nsAutoCString folderGuid;
+ nsresult rv = FetchFolderInfo(aParent, &folderCount, folderGuid, &grandParentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+
+ if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
+ aIndex >= folderCount) {
+ index = folderCount;
+ }
+ else {
+ index = aIndex;
+ // Create space for the insertion.
+ rv = AdjustIndices(aParent, index, INT32_MAX, 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aNewItemId = -1;
+ // Set a NULL title rather than an empty string.
+ nsAutoCString guid(aGUID);
+ PRTime dateAdded = RoundedPRNow();
+ rv = InsertBookmarkInDB(-1, SEPARATOR, aParent, index, NullCString(), dateAdded,
+ 0, folderGuid, grandParentId, nullptr, aSource,
+ aNewItemId, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ DontSkip,
+ OnItemAdded(*aNewItemId, aParent, index, TYPE_SEPARATOR,
+ nullptr, NullCString(), dateAdded, guid,
+ folderGuid, aSource));
+
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::GetLastChildId(int64_t aFolderId, int64_t* aItemId)
+{
+ NS_ASSERTION(aFolderId > 0, "Invalid folder id");
+ *aItemId = -1;
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT id FROM moz_bookmarks WHERE parent = :parent "
+ "ORDER BY position DESC LIMIT 1"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool found;
+ rv = stmt->ExecuteStep(&found);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (found) {
+ rv = stmt->GetInt64(0, aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetIdForItemAt(int64_t aFolder,
+ int32_t aIndex,
+ int64_t* aItemId)
+{
+ NS_ENSURE_ARG_MIN(aFolder, 1);
+ NS_ENSURE_ARG_POINTER(aItemId);
+
+ *aItemId = -1;
+
+ nsresult rv;
+ if (aIndex == nsINavBookmarksService::DEFAULT_INDEX) {
+ // Get last item within aFolder.
+ rv = GetLastChildId(aFolder, aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // Get the item in aFolder with position aIndex.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT id, fk, type FROM moz_bookmarks "
+ "WHERE parent = :parent AND position = :item_index"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool found;
+ rv = stmt->ExecuteStep(&found);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (found) {
+ rv = stmt->GetInt64(0, aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsNavBookmarks::RemoveFolderTransaction, nsITransaction)
+
+NS_IMETHODIMP
+nsNavBookmarks::GetRemoveFolderTransaction(int64_t aFolderId, uint16_t aSource,
+ nsITransaction** aResult)
+{
+ NS_ENSURE_ARG_MIN(aFolderId, 1);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // Create and initialize a RemoveFolderTransaction object that can be used to
+ // recreate the folder safely later.
+
+ RemoveFolderTransaction* rft =
+ new RemoveFolderTransaction(aFolderId, aSource);
+ if (!rft)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult = rft);
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::GetDescendantFolders(int64_t aFolderId,
+ nsTArray<int64_t>& aDescendantFoldersArray) {
+ nsresult rv;
+ // New descendant folders will be added from this index on.
+ uint32_t startIndex = aDescendantFoldersArray.Length();
+ {
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT id "
+ "FROM moz_bookmarks "
+ "WHERE parent = :parent "
+ "AND type = :item_type "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), TYPE_FOLDER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
+ int64_t itemId;
+ rv = stmt->GetInt64(0, &itemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aDescendantFoldersArray.AppendElement(itemId);
+ }
+ }
+
+ // Recursively call GetDescendantFolders for added folders.
+ // We start at startIndex since previous folders are checked
+ // by previous calls to this method.
+ uint32_t childCount = aDescendantFoldersArray.Length();
+ for (uint32_t i = startIndex; i < childCount; ++i) {
+ GetDescendantFolders(aDescendantFoldersArray[i], aDescendantFoldersArray);
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::GetDescendantChildren(int64_t aFolderId,
+ const nsACString& aFolderGuid,
+ int64_t aGrandParentId,
+ nsTArray<BookmarkData>& aFolderChildrenArray) {
+ // New children will be added from this index on.
+ uint32_t startIndex = aFolderChildrenArray.Length();
+ nsresult rv;
+ {
+ // Collect children informations.
+ // Select all children of a given folder, sorted by position.
+ // This is a LEFT JOIN because not all bookmarks types have a place.
+ // We construct a result where the first columns exactly match
+ // kGetInfoIndex_* order, and additionally contains columns for position,
+ // item_child, and folder_child from moz_bookmarks.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
+ "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
+ "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
+ "b.guid, b.position, b.type, b.fk "
+ "FROM moz_bookmarks b "
+ "LEFT JOIN moz_places h ON b.fk = h.id "
+ "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE b.parent = :parent "
+ "ORDER BY b.position ASC"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
+ BookmarkData child;
+ rv = stmt->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &child.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ child.parentId = aFolderId;
+ child.grandParentId = aGrandParentId;
+ child.parentGuid = aFolderGuid;
+ rv = stmt->GetInt32(kGetChildrenIndex_Type, &child.type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(kGetChildrenIndex_PlaceID, &child.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt32(kGetChildrenIndex_Position, &child.position);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetUTF8String(kGetChildrenIndex_Guid, child.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (child.type == TYPE_BOOKMARK) {
+ rv = stmt->GetUTF8String(nsNavHistory::kGetInfoIndex_URL, child.url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Append item to children's array.
+ aFolderChildrenArray.AppendElement(child);
+ }
+ }
+
+ // Recursively call GetDescendantChildren for added folders.
+ // We start at startIndex since previous folders are checked
+ // by previous calls to this method.
+ uint32_t childCount = aFolderChildrenArray.Length();
+ for (uint32_t i = startIndex; i < childCount; ++i) {
+ if (aFolderChildrenArray[i].type == TYPE_FOLDER) {
+ // nsTarray assumes that all children can be memmove()d, thus we can't
+ // just pass aFolderChildrenArray[i].guid to a method that will change
+ // the array itself. Otherwise, since it's passed by reference, after a
+ // memmove() it could point to garbage and cause intermittent crashes.
+ nsCString guid = aFolderChildrenArray[i].guid;
+ GetDescendantChildren(aFolderChildrenArray[i].id,
+ guid,
+ aFolderId,
+ aFolderChildrenArray);
+ }
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId, uint16_t aSource)
+{
+ PROFILER_LABEL("nsNavBookmarks", "RemoveFolderChilder",
+ js::ProfileEntry::Category::OTHER);
+
+ NS_ENSURE_ARG_MIN(aFolderId, 1);
+ NS_ENSURE_ARG(aFolderId != mRoot);
+
+ BookmarkData folder;
+ nsresult rv = FetchItemInfo(aFolderId, folder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_ARG(folder.type == TYPE_FOLDER);
+
+ // Fill folder children array recursively.
+ nsTArray<BookmarkData> folderChildrenArray;
+ rv = GetDescendantChildren(folder.id, folder.guid, folder.parentId,
+ folderChildrenArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Build a string of folders whose children will be removed.
+ nsCString foldersToRemove;
+ for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
+ BookmarkData& child = folderChildrenArray[i];
+
+ if (child.type == TYPE_FOLDER) {
+ foldersToRemove.Append(',');
+ foldersToRemove.AppendInt(child.id);
+ }
+ }
+
+ // Delete items from the database now.
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+
+ nsCOMPtr<mozIStorageStatement> deleteStatement = mDB->GetStatement(
+ NS_LITERAL_CSTRING(
+ "DELETE FROM moz_bookmarks "
+ "WHERE parent IN (:parent") + foldersToRemove + NS_LITERAL_CSTRING(")")
+ );
+ NS_ENSURE_STATE(deleteStatement);
+ mozStorageStatementScoper deleteStatementScoper(deleteStatement);
+
+ rv = deleteStatement->BindInt64ByName(NS_LITERAL_CSTRING("parent"), folder.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = deleteStatement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Clean up orphan items annotations.
+ rv = mDB->MainConn()->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING(
+ "DELETE FROM moz_items_annos "
+ "WHERE id IN ("
+ "SELECT a.id from moz_items_annos a "
+ "LEFT JOIN moz_bookmarks b ON a.item_id = b.id "
+ "WHERE b.id ISNULL)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set the lastModified date.
+ rv = SetItemDateInternal(LAST_MODIFIED, folder.id, RoundedPRNow());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Call observers in reverse order to serve children before their parent.
+ for (int32_t i = folderChildrenArray.Length() - 1; i >= 0; --i) {
+ BookmarkData& child = folderChildrenArray[i];
+
+ nsCOMPtr<nsIURI> uri;
+ if (child.type == TYPE_BOOKMARK) {
+ // If not a tag, recalculate frecency for this entry, since it changed.
+ if (child.grandParentId != mTagsRoot) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ rv = history->UpdateFrecency(child.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // A broken url should not interrupt the removal process.
+ (void)NS_NewURI(getter_AddRefs(uri), child.url);
+ // We cannot assert since some automated tests are checking this path.
+ NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveFolderChildren");
+ }
+
+ NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ ((child.grandParentId == mTagsRoot) ? SkipTags : SkipDescendants),
+ OnItemRemoved(child.id,
+ child.parentId,
+ child.position,
+ child.type,
+ uri,
+ child.guid,
+ child.parentGuid,
+ aSource));
+
+ if (child.type == TYPE_BOOKMARK && child.grandParentId == mTagsRoot &&
+ uri) {
+ // If the removed bookmark was a child of a tag container, notify all
+ // bookmark-folder result nodes which contain a bookmark for the removed
+ // bookmark's url.
+ nsTArray<BookmarkData> bookmarks;
+ rv = GetBookmarksForURI(uri, bookmarks);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
+ NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ DontSkip,
+ OnItemChanged(bookmarks[i].id,
+ NS_LITERAL_CSTRING("tags"),
+ false,
+ EmptyCString(),
+ bookmarks[i].lastModified,
+ TYPE_BOOKMARK,
+ bookmarks[i].parentId,
+ bookmarks[i].guid,
+ bookmarks[i].parentGuid,
+ EmptyCString(),
+ aSource));
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::MoveItem(int64_t aItemId,
+ int64_t aNewParent,
+ int32_t aIndex,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG(!IsRoot(aItemId));
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_MIN(aNewParent, 1);
+ // -1 is append, but no other negative number is allowed.
+ NS_ENSURE_ARG_MIN(aIndex, -1);
+ // Disallow making an item its own parent.
+ NS_ENSURE_ARG(aItemId != aNewParent);
+
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if parent and index are the same, nothing to do
+ if (bookmark.parentId == aNewParent && bookmark.position == aIndex)
+ return NS_OK;
+
+ // Make sure aNewParent is not aFolder or a subfolder of aFolder.
+ // TODO: make this performant, maybe with a nested tree (bug 408991).
+ if (bookmark.type == TYPE_FOLDER) {
+ int64_t ancestorId = aNewParent;
+
+ while (ancestorId) {
+ if (ancestorId == bookmark.id) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = GetFolderIdForItem(ancestorId, &ancestorId);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+ }
+
+ // calculate new index
+ int32_t newIndex, folderCount;
+ int64_t grandParentId;
+ nsAutoCString newParentGuid;
+ rv = FetchFolderInfo(aNewParent, &folderCount, newParentGuid, &grandParentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
+ aIndex >= folderCount) {
+ newIndex = folderCount;
+ // If the parent remains the same, then the folder is really being moved
+ // to count - 1 (since it's being removed from the old position)
+ if (bookmark.parentId == aNewParent) {
+ --newIndex;
+ }
+ } else {
+ newIndex = aIndex;
+
+ if (bookmark.parentId == aNewParent && newIndex > bookmark.position) {
+ // when an item is being moved lower in the same folder, the new index
+ // refers to the index before it was removed. Removal causes everything
+ // to shift up.
+ --newIndex;
+ }
+ }
+
+ // this is like the previous check, except this covers if
+ // the specified index was -1 (append), and the calculated
+ // new index is the same as the existing index
+ if (aNewParent == bookmark.parentId && newIndex == bookmark.position) {
+ // Nothing to do!
+ return NS_OK;
+ }
+
+ // adjust indices to account for the move
+ // do this before we update the parent/index fields
+ // or we'll re-adjust the index for the item we are moving
+ if (bookmark.parentId == aNewParent) {
+ // We can optimize the updates if moving within the same container.
+ // We only shift the items between the old and new positions, since the
+ // insertion will offset the deletion.
+ if (bookmark.position > newIndex) {
+ rv = AdjustIndices(bookmark.parentId, newIndex, bookmark.position - 1, 1);
+ }
+ else {
+ rv = AdjustIndices(bookmark.parentId, bookmark.position + 1, newIndex, -1);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // We're moving between containers, so this happens in two steps.
+ // First, fill the hole from the removal from the old parent.
+ rv = AdjustIndices(bookmark.parentId, bookmark.position + 1, INT32_MAX, -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Now, make room in the new parent for the insertion.
+ rv = AdjustIndices(aNewParent, newIndex, INT32_MAX, 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ {
+ // Update parent and position.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "UPDATE moz_bookmarks SET parent = :parent, position = :item_index "
+ "WHERE id = :item_id "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aNewParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), newIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ PRTime now = RoundedPRNow();
+ rv = SetItemDateInternal(LAST_MODIFIED, bookmark.parentId, now);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetItemDateInternal(LAST_MODIFIED, aNewParent, now);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemMoved(bookmark.id,
+ bookmark.parentId,
+ bookmark.position,
+ aNewParent,
+ newIndex,
+ bookmark.type,
+ bookmark.guid,
+ bookmark.parentGuid,
+ newParentGuid,
+ aSource));
+ return NS_OK;
+}
+
+nsresult
+nsNavBookmarks::FetchItemInfo(int64_t aItemId,
+ BookmarkData& _bookmark)
+{
+ // LEFT JOIN since not all bookmarks have an associated place.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
+ "b.dateAdded, b.lastModified, b.guid, t.guid, t.parent "
+ "FROM moz_bookmarks b "
+ "LEFT JOIN moz_bookmarks t ON t.id = b.parent "
+ "LEFT JOIN moz_places h ON h.id = b.fk "
+ "WHERE b.id = :item_id"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hasResult) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ _bookmark.id = aItemId;
+ rv = stmt->GetUTF8String(1, _bookmark.url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isNull;
+ rv = stmt->GetIsNull(2, &isNull);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isNull) {
+ _bookmark.title.SetIsVoid(true);
+ }
+ else {
+ rv = stmt->GetUTF8String(2, _bookmark.title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = stmt->GetInt32(3, &_bookmark.position);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(4, &_bookmark.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(5, &_bookmark.parentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt32(6, &_bookmark.type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(7, reinterpret_cast<int64_t*>(&_bookmark.dateAdded));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(8, reinterpret_cast<int64_t*>(&_bookmark.lastModified));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetUTF8String(9, _bookmark.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Getting properties of the root would show no parent.
+ rv = stmt->GetIsNull(10, &isNull);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isNull) {
+ rv = stmt->GetUTF8String(10, _bookmark.parentGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(11, &_bookmark.grandParentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ _bookmark.grandParentId = -1;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNavBookmarks::SetItemDateInternal(enum BookmarkDate aDateType,
+ int64_t aItemId,
+ PRTime aValue)
+{
+ aValue = RoundToMilliseconds(aValue);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ if (aDateType == DATE_ADDED) {
+ // lastModified is set to the same value as dateAdded. We do this for
+ // performance reasons, since it will allow us to use an index to sort items
+ // by date.
+ stmt = mDB->GetStatement(
+ "UPDATE moz_bookmarks SET dateAdded = :date, lastModified = :date "
+ "WHERE id = :item_id"
+ );
+ }
+ else {
+ stmt = mDB->GetStatement(
+ "UPDATE moz_bookmarks SET lastModified = :date WHERE id = :item_id"
+ );
+ }
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), aValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // note, we are not notifying the observers
+ // that the item has changed.
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::SetItemDateAdded(int64_t aItemId, PRTime aDateAdded,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Round here so that we notify with the right value.
+ bookmark.dateAdded = RoundToMilliseconds(aDateAdded);
+
+ rv = SetItemDateInternal(DATE_ADDED, bookmark.id, bookmark.dateAdded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Note: mDBSetItemDateAdded also sets lastModified to aDateAdded.
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemChanged(bookmark.id,
+ NS_LITERAL_CSTRING("dateAdded"),
+ false,
+ nsPrintfCString("%lld", bookmark.dateAdded),
+ bookmark.dateAdded,
+ bookmark.type,
+ bookmark.parentId,
+ bookmark.guid,
+ bookmark.parentGuid,
+ EmptyCString(),
+ aSource));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetItemDateAdded(int64_t aItemId, PRTime* _dateAdded)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_dateAdded);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_dateAdded = bookmark.dateAdded;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::SetItemLastModified(int64_t aItemId, PRTime aLastModified,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Round here so that we notify with the right value.
+ bookmark.lastModified = RoundToMilliseconds(aLastModified);
+
+ rv = SetItemDateInternal(LAST_MODIFIED, bookmark.id, bookmark.lastModified);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Note: mDBSetItemDateAdded also sets lastModified to aDateAdded.
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemChanged(bookmark.id,
+ NS_LITERAL_CSTRING("lastModified"),
+ false,
+ nsPrintfCString("%lld", bookmark.lastModified),
+ bookmark.lastModified,
+ bookmark.type,
+ bookmark.parentId,
+ bookmark.guid,
+ bookmark.parentGuid,
+ EmptyCString(),
+ aSource));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetItemLastModified(int64_t aItemId, PRTime* _lastModified)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_lastModified);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_lastModified = bookmark.lastModified;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::SetItemTitle(int64_t aItemId, const nsACString& aTitle,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
+ "UPDATE moz_bookmarks SET title = :item_title, lastModified = :date "
+ "WHERE id = :item_id "
+ );
+ NS_ENSURE_STATE(statement);
+ mozStorageStatementScoper scoper(statement);
+
+ nsCString title;
+ TruncateTitle(aTitle, title);
+
+ // Support setting a null title, we support this in insertBookmark.
+ if (title.IsVoid()) {
+ rv = statement->BindNullByName(NS_LITERAL_CSTRING("item_title"));
+ }
+ else {
+ rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
+ title);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ bookmark.lastModified = RoundToMilliseconds(RoundedPRNow());
+ rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("date"),
+ bookmark.lastModified);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemChanged(bookmark.id,
+ NS_LITERAL_CSTRING("title"),
+ false,
+ title,
+ bookmark.lastModified,
+ bookmark.type,
+ bookmark.parentId,
+ bookmark.guid,
+ bookmark.parentGuid,
+ EmptyCString(),
+ aSource));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetItemTitle(int64_t aItemId,
+ nsACString& _title)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ _title = bookmark.title;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetBookmarkURI(int64_t aItemId,
+ nsIURI** _URI)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_URI);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewURI(_URI, bookmark.url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetItemType(int64_t aItemId, uint16_t* _type)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_type);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_type = static_cast<uint16_t>(bookmark.type);
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::ResultNodeForContainer(int64_t aItemId,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode** aNode)
+{
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (bookmark.type == TYPE_FOLDER) { // TYPE_FOLDER
+ *aNode = new nsNavHistoryFolderResultNode(bookmark.title,
+ aOptions,
+ bookmark.id);
+ }
+ else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ (*aNode)->mDateAdded = bookmark.dateAdded;
+ (*aNode)->mLastModified = bookmark.lastModified;
+ (*aNode)->mBookmarkGuid = bookmark.guid;
+ (*aNode)->GetAsFolder()->mTargetFolderGuid = bookmark.guid;
+
+ NS_ADDREF(*aNode);
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::QueryFolderChildren(
+ int64_t aFolderId,
+ nsNavHistoryQueryOptions* aOptions,
+ nsCOMArray<nsNavHistoryResultNode>* aChildren)
+{
+ NS_ENSURE_ARG_POINTER(aOptions);
+ NS_ENSURE_ARG_POINTER(aChildren);
+
+ // Select all children of a given folder, sorted by position.
+ // This is a LEFT JOIN because not all bookmarks types have a place.
+ // We construct a result where the first columns exactly match those returned
+ // by mDBGetURLPageInfo, and additionally contains columns for position,
+ // item_child, and folder_child from moz_bookmarks.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
+ "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
+ "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
+ "b.guid, b.position, b.type, b.fk "
+ "FROM moz_bookmarks b "
+ "LEFT JOIN moz_places h ON b.fk = h.id "
+ "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE b.parent = :parent "
+ "ORDER BY b.position ASC"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t index = -1;
+ bool hasResult;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ rv = ProcessFolderNodeRow(row, aOptions, aChildren, index);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::ProcessFolderNodeRow(
+ mozIStorageValueArray* aRow,
+ nsNavHistoryQueryOptions* aOptions,
+ nsCOMArray<nsNavHistoryResultNode>* aChildren,
+ int32_t& aCurrentIndex)
+{
+ NS_ENSURE_ARG_POINTER(aRow);
+ NS_ENSURE_ARG_POINTER(aOptions);
+ NS_ENSURE_ARG_POINTER(aChildren);
+
+ // The results will be in order of aCurrentIndex. Even if we don't add a node
+ // because it was excluded, we need to count its index, so do that before
+ // doing anything else.
+ aCurrentIndex++;
+
+ int32_t itemType;
+ nsresult rv = aRow->GetInt32(kGetChildrenIndex_Type, &itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t id;
+ rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsNavHistoryResultNode> node;
+
+ if (itemType == TYPE_BOOKMARK) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ rv = history->RowToResult(aRow, aOptions, getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t nodeType;
+ node->GetType(&nodeType);
+ if ((nodeType == nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
+ aOptions->ExcludeQueries()) ||
+ (nodeType != nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
+ nodeType != nsINavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT &&
+ aOptions->ExcludeItems())) {
+ return NS_OK;
+ }
+ }
+ else if (itemType == TYPE_FOLDER) {
+ // ExcludeReadOnlyFolders currently means "ExcludeLivemarks" (to be fixed in
+ // bug 1072833)
+ if (aOptions->ExcludeReadOnlyFolders()) {
+ if (IsLivemark(id))
+ return NS_OK;
+ }
+
+ nsAutoCString title;
+ rv = aRow->GetUTF8String(nsNavHistory::kGetInfoIndex_Title, title);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ node = new nsNavHistoryFolderResultNode(title, aOptions, id);
+
+ rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ node->GetAsFolder()->mTargetFolderGuid = node->mBookmarkGuid;
+
+ rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
+ reinterpret_cast<int64_t*>(&node->mDateAdded));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
+ reinterpret_cast<int64_t*>(&node->mLastModified));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ // This is a separator.
+ if (aOptions->ExcludeItems()) {
+ return NS_OK;
+ }
+ node = new nsNavHistorySeparatorResultNode();
+
+ node->mItemId = id;
+ rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
+ reinterpret_cast<int64_t*>(&node->mDateAdded));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
+ reinterpret_cast<int64_t*>(&node->mLastModified));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Store the index of the node within this container. Note that this is not
+ // moz_bookmarks.position.
+ node->mBookmarkIndex = aCurrentIndex;
+
+ NS_ENSURE_TRUE(aChildren->AppendObject(node), NS_ERROR_OUT_OF_MEMORY);
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::QueryFolderChildrenAsync(
+ nsNavHistoryFolderResultNode* aNode,
+ int64_t aFolderId,
+ mozIStoragePendingStatement** _pendingStmt)
+{
+ NS_ENSURE_ARG_POINTER(aNode);
+ NS_ENSURE_ARG_POINTER(_pendingStmt);
+
+ // Select all children of a given folder, sorted by position.
+ // This is a LEFT JOIN because not all bookmarks types have a place.
+ // We construct a result where the first columns exactly match those returned
+ // by mDBGetURLPageInfo, and additionally contains columns for position,
+ // item_child, and folder_child from moz_bookmarks.
+ nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
+ "SELECT h.id, h.url, IFNULL(b.title, h.title), h.rev_host, h.visit_count, "
+ "h.last_visit_date, f.url, b.id, b.dateAdded, b.lastModified, "
+ "b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
+ "b.guid, b.position, b.type, b.fk "
+ "FROM moz_bookmarks b "
+ "LEFT JOIN moz_places h ON b.fk = h.id "
+ "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE b.parent = :parent "
+ "ORDER BY b.position ASC"
+ );
+ NS_ENSURE_STATE(stmt);
+
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
+ rv = stmt->ExecuteAsync(aNode, getter_AddRefs(pendingStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_pendingStmt = pendingStmt);
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::FetchFolderInfo(int64_t aFolderId,
+ int32_t* _folderCount,
+ nsACString& _guid,
+ int64_t* _parentId)
+{
+ *_folderCount = 0;
+ *_parentId = -1;
+
+ // This query has to always return results, so it can't be written as a join,
+ // though a left join of 2 subqueries would have the same cost.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT count(*), "
+ "(SELECT guid FROM moz_bookmarks WHERE id = :parent), "
+ "(SELECT parent FROM moz_bookmarks WHERE id = :parent) "
+ "FROM moz_bookmarks "
+ "WHERE parent = :parent"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult;
+ rv = stmt->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED);
+
+ // Ensure that the folder we are looking for exists.
+ // Can't rely only on parent, since the root has parent 0, that doesn't exist.
+ bool isNull;
+ rv = stmt->GetIsNull(2, &isNull);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (!isNull || aFolderId == 0),
+ NS_ERROR_INVALID_ARG);
+
+ rv = stmt->GetInt32(0, _folderCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isNull) {
+ rv = stmt->GetUTF8String(1, _guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(2, _parentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::IsBookmarked(nsIURI* aURI, bool* aBookmarked)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(aBookmarked);
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT 1 FROM moz_bookmarks b "
+ "JOIN moz_places h ON b.fk = h.id "
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->ExecuteStep(aBookmarked);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetBookmarkedURIFor(nsIURI* aURI, nsIURI** _retval)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = nullptr;
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ int64_t placeId;
+ nsAutoCString placeGuid;
+ nsresult rv = history->GetIdForPage(aURI, &placeId, placeGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!placeId) {
+ // This URI is unknown, just return null.
+ return NS_OK;
+ }
+
+ // Check if a bookmark exists in the redirects chain for this URI.
+ // The query will also check if the page is directly bookmarked, and return
+ // the first found bookmark in case. The check is directly on moz_bookmarks
+ // without special filtering.
+ // The next query finds the bookmarked ancestors in a redirects chain.
+ // It won't go further than 3 levels of redirects (a->b->c->your_place_id).
+ // To make this path 100% correct (up to any level) we would need either:
+ // - A separate hash, build through recursive querying of the database.
+ // This solution was previously implemented, but it had a negative effect
+ // on startup since at each startup we have to recursively query the
+ // database to rebuild a hash that is always the same across sessions.
+ // It must be updated at each visit and bookmarks change too. The code to
+ // manage it is complex and prone to errors, sometimes causing incorrect
+ // data fetches (for example wrong favicon for a redirected bookmark).
+ // - A better way to track redirects for a visit.
+ // We would need a separate table to track redirects, in the table we would
+ // have visit_id, redirect_session. To get all sources for
+ // a visit then we could just join this table and get all visit_id that
+ // are in the same redirect_session as our visit. This has the drawback
+ // that we can't ensure data integrity in the downgrade -> upgrade path,
+ // since an old version would not update the table on new visits.
+ //
+ // For most cases these levels of redirects should be fine though, it's hard
+ // to hit a page that is 4 or 5 levels of redirects below a bookmarked page.
+ //
+ // As a bonus the query also checks first if place_id is already a bookmark,
+ // so you don't have to check that apart.
+
+ nsCString query = nsPrintfCString(
+ "SELECT url FROM moz_places WHERE id = ( "
+ "SELECT :page_id FROM moz_bookmarks WHERE fk = :page_id "
+ "UNION ALL "
+ "SELECT COALESCE(grandparent.place_id, parent.place_id) AS r_place_id "
+ "FROM moz_historyvisits dest "
+ "LEFT JOIN moz_historyvisits parent ON parent.id = dest.from_visit "
+ "AND dest.visit_type IN (%d, %d) "
+ "LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
+ "AND parent.visit_type IN (%d, %d) "
+ "WHERE dest.place_id = :page_id "
+ "AND EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = r_place_id) "
+ "LIMIT 1 "
+ ")",
+ nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
+ nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY,
+ nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
+ nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
+ );
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(query);
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool hasBookmarkedOrigin;
+ if (NS_SUCCEEDED(stmt->ExecuteStep(&hasBookmarkedOrigin)) &&
+ hasBookmarkedOrigin) {
+ nsAutoCString spec;
+ rv = stmt->GetUTF8String(0, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewURI(_retval, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If there is no bookmarked origin, we will just return null.
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::ChangeBookmarkURI(int64_t aBookmarkId, nsIURI* aNewURI,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aBookmarkId, 1);
+ NS_ENSURE_ARG(aNewURI);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aBookmarkId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_ARG(bookmark.type == TYPE_BOOKMARK);
+
+ mozStorageTransaction transaction(mDB->MainConn(), false);
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ int64_t newPlaceId;
+ nsAutoCString newPlaceGuid;
+ rv = history->GetOrCreateIdForPage(aNewURI, &newPlaceId, newPlaceGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!newPlaceId)
+ return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
+ "UPDATE moz_bookmarks SET fk = :page_id, lastModified = :date "
+ "WHERE id = :item_id "
+ );
+ NS_ENSURE_STATE(statement);
+ mozStorageStatementScoper scoper(statement);
+
+ rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), newPlaceId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bookmark.lastModified = RoundedPRNow();
+ rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("date"),
+ bookmark.lastModified);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = history->UpdateFrecency(newPlaceId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Upon changing the URI for a bookmark, update the frecency for the old
+ // place as well.
+ rv = history->UpdateFrecency(bookmark.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = aNewURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemChanged(bookmark.id,
+ NS_LITERAL_CSTRING("uri"),
+ false,
+ spec,
+ bookmark.lastModified,
+ bookmark.type,
+ bookmark.parentId,
+ bookmark.guid,
+ bookmark.parentGuid,
+ bookmark.url,
+ aSource));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetFolderIdForItem(int64_t aItemId, int64_t* _parentId)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_parentId);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // this should not happen, but see bug #400448 for details
+ NS_ENSURE_TRUE(bookmark.id != bookmark.parentId, NS_ERROR_UNEXPECTED);
+
+ *_parentId = bookmark.parentId;
+ return NS_OK;
+}
+
+
+nsresult
+nsNavBookmarks::GetBookmarkIdsForURITArray(nsIURI* aURI,
+ nsTArray<int64_t>& aResult,
+ bool aSkipTags)
+{
+ NS_ENSURE_ARG(aURI);
+
+ // Double ordering covers possible lastModified ties, that could happen when
+ // importing, syncing or due to extensions.
+ // Note: not using a JOIN is cheaper in this case.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "/* do not warn (bug 1175249) */ "
+ "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
+ "FROM moz_bookmarks b "
+ "JOIN moz_bookmarks t on t.id = b.parent "
+ "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
+ "ORDER BY b.lastModified DESC, b.id DESC "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
+ if (aSkipTags) {
+ // Skip tags, for the use-cases of this async getter they are useless.
+ int64_t grandParentId;
+ nsresult rv = stmt->GetInt64(5, &grandParentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (grandParentId == mTagsRoot) {
+ continue;
+ }
+ }
+ int64_t bookmarkId;
+ rv = stmt->GetInt64(0, &bookmarkId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(aResult.AppendElement(bookmarkId), NS_ERROR_OUT_OF_MEMORY);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsNavBookmarks::GetBookmarksForURI(nsIURI* aURI,
+ nsTArray<BookmarkData>& aBookmarks)
+{
+ NS_ENSURE_ARG(aURI);
+
+ // Double ordering covers possible lastModified ties, that could happen when
+ // importing, syncing or due to extensions.
+ // Note: not using a JOIN is cheaper in this case.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "/* do not warn (bug 1175249) */ "
+ "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
+ "FROM moz_bookmarks b "
+ "JOIN moz_bookmarks t on t.id = b.parent "
+ "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
+ "ORDER BY b.lastModified DESC, b.id DESC "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ nsAutoString tags;
+ while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&more))) && more) {
+ // Skip tags.
+ int64_t grandParentId;
+ nsresult rv = stmt->GetInt64(5, &grandParentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (grandParentId == mTagsRoot) {
+ continue;
+ }
+
+ BookmarkData bookmark;
+ bookmark.grandParentId = grandParentId;
+ rv = stmt->GetInt64(0, &bookmark.id);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetUTF8String(1, bookmark.guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(2, &bookmark.parentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetInt64(3, reinterpret_cast<int64_t*>(&bookmark.lastModified));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetUTF8String(4, bookmark.parentGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(aBookmarks.AppendElement(bookmark), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavBookmarks::GetBookmarkIdsForURI(nsIURI* aURI, uint32_t* aCount,
+ int64_t** aBookmarks)
+{
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(aCount);
+ NS_ENSURE_ARG_POINTER(aBookmarks);
+
+ *aCount = 0;
+ *aBookmarks = nullptr;
+ nsTArray<int64_t> bookmarks;
+
+ // Get the information from the DB as a TArray
+ // TODO (bug 653816): make this API skip tags by default.
+ nsresult rv = GetBookmarkIdsForURITArray(aURI, bookmarks, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy the results into a new array for output
+ if (bookmarks.Length()) {
+ *aBookmarks =
+ static_cast<int64_t*>(moz_xmalloc(sizeof(int64_t) * bookmarks.Length()));
+ if (!*aBookmarks)
+ return NS_ERROR_OUT_OF_MEMORY;
+ for (uint32_t i = 0; i < bookmarks.Length(); i ++)
+ (*aBookmarks)[i] = bookmarks[i];
+ }
+
+ *aCount = bookmarks.Length();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetItemIndex(int64_t aItemId, int32_t* _index)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_POINTER(_index);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ // With respect to the API.
+ if (NS_FAILED(rv)) {
+ *_index = -1;
+ return NS_OK;
+ }
+
+ *_index = bookmark.position;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavBookmarks::SetItemIndex(int64_t aItemId,
+ int32_t aNewIndex,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aItemId, 1);
+ NS_ENSURE_ARG_MIN(aNewIndex, 0);
+
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure we are not going out of range.
+ int32_t folderCount;
+ int64_t grandParentId;
+ nsAutoCString folderGuid;
+ rv = FetchFolderInfo(bookmark.parentId, &folderCount, folderGuid, &grandParentId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(aNewIndex < folderCount, NS_ERROR_INVALID_ARG);
+ // Check the parent's guid is the expected one.
+ MOZ_ASSERT(bookmark.parentGuid == folderGuid);
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "UPDATE moz_bookmarks SET position = :item_index WHERE id = :item_id"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aNewIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemMoved(bookmark.id,
+ bookmark.parentId,
+ bookmark.position,
+ bookmark.parentId,
+ aNewIndex,
+ bookmark.type,
+ bookmark.guid,
+ bookmark.parentGuid,
+ bookmark.parentGuid,
+ aSource));
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
+ const nsAString& aUserCasedKeyword,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG_MIN(aBookmarkId, 1);
+
+ // This also ensures the bookmark is valid.
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aBookmarkId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), bookmark.url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Shortcuts are always lowercased internally.
+ nsAutoString keyword(aUserCasedKeyword);
+ ToLowerCase(keyword);
+
+ // The same URI can be associated to more than one keyword, provided the post
+ // data differs. Check if there are already keywords associated to this uri.
+ nsTArray<nsString> oldKeywords;
+ {
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT keyword FROM moz_keywords WHERE place_id = :place_id"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("place_id"), bookmark.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
+ nsString oldKeyword;
+ rv = stmt->GetString(0, oldKeyword);
+ NS_ENSURE_SUCCESS(rv, rv);
+ oldKeywords.AppendElement(oldKeyword);
+ }
+ }
+
+ // Trying to remove a non-existent keyword is a no-op.
+ if (keyword.IsEmpty() && oldKeywords.Length() == 0) {
+ return NS_OK;
+ }
+
+ if (keyword.IsEmpty()) {
+ // We are removing the existing keywords.
+ for (uint32_t i = 0; i < oldKeywords.Length(); ++i) {
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "DELETE FROM moz_keywords WHERE keyword = :old_keyword"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("old_keyword"),
+ oldKeywords[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsTArray<BookmarkData> bookmarks;
+ rv = GetBookmarksForURI(uri, bookmarks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemChanged(bookmarks[i].id,
+ NS_LITERAL_CSTRING("keyword"),
+ false,
+ EmptyCString(),
+ bookmarks[i].lastModified,
+ TYPE_BOOKMARK,
+ bookmarks[i].parentId,
+ bookmarks[i].guid,
+ bookmarks[i].parentGuid,
+ EmptyCString(),
+ aSource));
+ }
+
+ return NS_OK;
+ }
+
+ // A keyword can only be associated to a single URI. Check if the requested
+ // keyword was already associated, in such a case we will need to notify about
+ // the change.
+ nsCOMPtr<nsIURI> oldUri;
+ {
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT url "
+ "FROM moz_keywords "
+ "JOIN moz_places h ON h.id = place_id "
+ "WHERE keyword = :keyword"
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ if (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
+ nsAutoCString spec;
+ rv = stmt->GetUTF8String(0, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_NewURI(getter_AddRefs(oldUri), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // If another uri is using the new keyword, we must update the keyword entry.
+ // Note we cannot use INSERT OR REPLACE cause it wouldn't invoke the delete
+ // trigger.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ if (oldUri) {
+ // In both cases, notify about the change.
+ nsTArray<BookmarkData> bookmarks;
+ rv = GetBookmarksForURI(oldUri, bookmarks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemChanged(bookmarks[i].id,
+ NS_LITERAL_CSTRING("keyword"),
+ false,
+ EmptyCString(),
+ bookmarks[i].lastModified,
+ TYPE_BOOKMARK,
+ bookmarks[i].parentId,
+ bookmarks[i].guid,
+ bookmarks[i].parentGuid,
+ EmptyCString(),
+ aSource));
+ }
+
+ stmt = mDB->GetStatement(
+ "UPDATE moz_keywords SET place_id = :place_id WHERE keyword = :keyword"
+ );
+ NS_ENSURE_STATE(stmt);
+ }
+ else {
+ stmt = mDB->GetStatement(
+ "INSERT INTO moz_keywords (keyword, place_id) "
+ "VALUES (:keyword, :place_id)"
+ );
+ }
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("place_id"), bookmark.placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // In both cases, notify about the change.
+ nsTArray<BookmarkData> bookmarks;
+ rv = GetBookmarksForURI(uri, bookmarks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemChanged(bookmarks[i].id,
+ NS_LITERAL_CSTRING("keyword"),
+ false,
+ NS_ConvertUTF16toUTF8(keyword),
+ bookmarks[i].lastModified,
+ TYPE_BOOKMARK,
+ bookmarks[i].parentId,
+ bookmarks[i].guid,
+ bookmarks[i].parentGuid,
+ EmptyCString(),
+ aSource));
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetKeywordForBookmark(int64_t aBookmarkId, nsAString& aKeyword)
+{
+ NS_ENSURE_ARG_MIN(aBookmarkId, 1);
+ aKeyword.Truncate(0);
+
+ // We can have multiple keywords for the same uri, here we'll just return the
+ // last created one.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
+ "SELECT k.keyword "
+ "FROM moz_bookmarks b "
+ "JOIN moz_keywords k ON k.place_id = b.fk "
+ "WHERE b.id = :item_id "
+ "ORDER BY k.ROWID DESC "
+ "LIMIT 1"
+ ));
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
+ aBookmarkId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ if (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
+ nsAutoString keyword;
+ rv = stmt->GetString(0, keyword);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aKeyword = keyword;
+ return NS_OK;
+ }
+
+ aKeyword.SetIsVoid(true);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::GetURIForKeyword(const nsAString& aUserCasedKeyword,
+ nsIURI** aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_TRUE(!aUserCasedKeyword.IsEmpty(), NS_ERROR_INVALID_ARG);
+ *aURI = nullptr;
+
+ // Shortcuts are always lowercased internally.
+ nsAutoString keyword(aUserCasedKeyword);
+ ToLowerCase(keyword);
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
+ "SELECT h.url "
+ "FROM moz_places h "
+ "JOIN moz_keywords k ON k.place_id = h.id "
+ "WHERE k.keyword = :keyword"
+ ));
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ if (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
+ nsAutoCString spec;
+ rv = stmt->GetUTF8String(0, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uri.forget(aURI);
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::RunInBatchMode(nsINavHistoryBatchCallback* aCallback,
+ nsISupports* aUserData) {
+ PROFILER_LABEL("nsNavBookmarks", "RunInBatchMode",
+ js::ProfileEntry::Category::OTHER);
+
+ NS_ENSURE_ARG(aCallback);
+
+ mBatching = true;
+
+ // Just forward the request to history. History service must exist for
+ // bookmarks to work and we are observing it, thus batch notifications will be
+ // forwarded to bookmarks observers.
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = history->RunInBatchMode(aCallback, aUserData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::AddObserver(nsINavBookmarkObserver* aObserver,
+ bool aOwnsWeak)
+{
+ NS_ENSURE_ARG(aObserver);
+
+ if (NS_WARN_IF(!mCanNotify))
+ return NS_ERROR_UNEXPECTED;
+
+ return mObservers.AppendWeakElement(aObserver, aOwnsWeak);
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::RemoveObserver(nsINavBookmarkObserver* aObserver)
+{
+ return mObservers.RemoveWeakElement(aObserver);
+}
+
+NS_IMETHODIMP
+nsNavBookmarks::GetObservers(uint32_t* _count,
+ nsINavBookmarkObserver*** _observers)
+{
+ NS_ENSURE_ARG_POINTER(_count);
+ NS_ENSURE_ARG_POINTER(_observers);
+
+ *_count = 0;
+ *_observers = nullptr;
+
+ if (!mCanNotify)
+ return NS_OK;
+
+ nsCOMArray<nsINavBookmarkObserver> observers;
+
+ // First add the category cache observers.
+ mCacheObservers.GetEntries(observers);
+
+ // Then add the other observers.
+ for (uint32_t i = 0; i < mObservers.Length(); ++i) {
+ const nsCOMPtr<nsINavBookmarkObserver> &observer = mObservers.ElementAt(i).GetValue();
+ // Skip nullified weak observers.
+ if (observer)
+ observers.AppendElement(observer);
+ }
+
+ if (observers.Count() == 0)
+ return NS_OK;
+
+ *_count = observers.Count();
+ observers.Forget(_observers);
+
+ return NS_OK;
+}
+
+void
+nsNavBookmarks::NotifyItemVisited(const ItemVisitData& aData)
+{
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aData.bookmark.url));
+ // Notify the visit only if we have a valid uri, otherwise the observer
+ // couldn't gather enough data from the notification.
+ // This should be false only if there's a bug in the code preceding us.
+ if (uri) {
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemVisited(aData.bookmark.id,
+ aData.visitId,
+ aData.time,
+ aData.transitionType,
+ uri,
+ aData.bookmark.parentId,
+ aData.bookmark.guid,
+ aData.bookmark.parentGuid));
+ }
+}
+
+void
+nsNavBookmarks::NotifyItemChanged(const ItemChangeData& aData)
+{
+ // A guid must always be defined.
+ MOZ_ASSERT(!aData.bookmark.guid.IsEmpty());
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemChanged(aData.bookmark.id,
+ aData.property,
+ aData.isAnnotation,
+ aData.newValue,
+ aData.bookmark.lastModified,
+ aData.bookmark.type,
+ aData.bookmark.parentId,
+ aData.bookmark.guid,
+ aData.bookmark.parentGuid,
+ aData.oldValue,
+ // We specify the default source here because
+ // this method is only called for history
+ // visits, and we don't track sources in
+ // history.
+ SOURCE_DEFAULT));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+nsNavBookmarks::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+
+ if (strcmp(aTopic, TOPIC_PLACES_SHUTDOWN) == 0) {
+ // Stop Observing annotations.
+ nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
+ if (annosvc) {
+ annosvc->RemoveObserver(this);
+ }
+ }
+ else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) {
+ // Don't even try to notify observers from this point on, the category
+ // cache would init services that could try to use our APIs.
+ mCanNotify = false;
+ mObservers.Clear();
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsINavHistoryObserver
+
+NS_IMETHODIMP
+nsNavBookmarks::OnBeginUpdateBatch()
+{
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver, OnBeginUpdateBatch());
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnEndUpdateBatch()
+{
+ if (mBatching) {
+ mBatching = false;
+ }
+
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver, OnEndUpdateBatch());
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime,
+ int64_t aSessionID, int64_t aReferringID,
+ uint32_t aTransitionType, const nsACString& aGUID,
+ bool aHidden, uint32_t aVisitCount, uint32_t aTyped)
+{
+ NS_ENSURE_ARG(aURI);
+
+ // If the page is bookmarked, notify observers for each associated bookmark.
+ ItemVisitData visitData;
+ nsresult rv = aURI->GetSpec(visitData.bookmark.url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ visitData.visitId = aVisitId;
+ visitData.time = aTime;
+ visitData.transitionType = aTransitionType;
+
+ RefPtr< AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData> > notifier =
+ new AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData>(this, &nsNavBookmarks::NotifyItemVisited, visitData);
+ notifier->Init();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnDeleteURI(nsIURI* aURI,
+ const nsACString& aGUID,
+ uint16_t aReason)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnClearHistory()
+{
+ // TODO(bryner): we should notify on visited-time change for all URIs
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnTitleChanged(nsIURI* aURI,
+ const nsAString& aPageTitle,
+ const nsACString& aGUID)
+{
+ // NOOP. We don't consume page titles from moz_places anymore.
+ // Title-change notifications are sent from SetItemTitle.
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnFrecencyChanged(nsIURI* aURI,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnManyFrecenciesChanged()
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnPageChanged(nsIURI* aURI,
+ uint32_t aChangedAttribute,
+ const nsAString& aNewValue,
+ const nsACString& aGUID)
+{
+ NS_ENSURE_ARG(aURI);
+
+ nsresult rv;
+ if (aChangedAttribute == nsINavHistoryObserver::ATTRIBUTE_FAVICON) {
+ ItemChangeData changeData;
+ rv = aURI->GetSpec(changeData.bookmark.url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ changeData.property = NS_LITERAL_CSTRING("favicon");
+ changeData.isAnnotation = false;
+ changeData.newValue = NS_ConvertUTF16toUTF8(aNewValue);
+ changeData.bookmark.lastModified = 0;
+ changeData.bookmark.type = TYPE_BOOKMARK;
+
+ // Favicons may be set to either pure URIs or to folder URIs
+ bool isPlaceURI;
+ rv = aURI->SchemeIs("place", &isPlaceURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isPlaceURI) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMArray<nsNavHistoryQuery> queries;
+ nsCOMPtr<nsNavHistoryQueryOptions> options;
+ rv = history->QueryStringToQueryArray(changeData.bookmark.url,
+ &queries, getter_AddRefs(options));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (queries.Count() == 1 && queries[0]->Folders().Length() == 1) {
+ // Fetch missing data.
+ rv = FetchItemInfo(queries[0]->Folders()[0], changeData.bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NotifyItemChanged(changeData);
+ }
+ }
+ else {
+ RefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
+ new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
+ notifier->Init();
+ }
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnDeleteVisits(nsIURI* aURI, PRTime aVisitTime,
+ const nsACString& aGUID,
+ uint16_t aReason, uint32_t aTransitionType)
+{
+ NS_ENSURE_ARG(aURI);
+
+ // Notify "cleartime" only if all visits to the page have been removed.
+ if (!aVisitTime) {
+ // If the page is bookmarked, notify observers for each associated bookmark.
+ ItemChangeData changeData;
+ nsresult rv = aURI->GetSpec(changeData.bookmark.url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ changeData.property = NS_LITERAL_CSTRING("cleartime");
+ changeData.isAnnotation = false;
+ changeData.bookmark.lastModified = 0;
+ changeData.bookmark.type = TYPE_BOOKMARK;
+
+ RefPtr< AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData> > notifier =
+ new AsyncGetBookmarksForURI<ItemChangeMethod, ItemChangeData>(this, &nsNavBookmarks::NotifyItemChanged, changeData);
+ notifier->Init();
+ }
+ return NS_OK;
+}
+
+
+// nsIAnnotationObserver
+
+NS_IMETHODIMP
+nsNavBookmarks::OnPageAnnotationSet(nsIURI* aPage, const nsACString& aName)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnItemAnnotationSet(int64_t aItemId, const nsACString& aName,
+ uint16_t aSource)
+{
+ BookmarkData bookmark;
+ nsresult rv = FetchItemInfo(aItemId, bookmark);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bookmark.lastModified = RoundedPRNow();
+ rv = SetItemDateInternal(LAST_MODIFIED, bookmark.id, bookmark.lastModified);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavBookmarkObserver,
+ OnItemChanged(bookmark.id,
+ aName,
+ true,
+ EmptyCString(),
+ bookmark.lastModified,
+ bookmark.type,
+ bookmark.parentId,
+ bookmark.guid,
+ bookmark.parentGuid,
+ EmptyCString(),
+ aSource));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnPageAnnotationRemoved(nsIURI* aPage, const nsACString& aName)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavBookmarks::OnItemAnnotationRemoved(int64_t aItemId, const nsACString& aName,
+ uint16_t aSource)
+{
+ // As of now this is doing the same as OnItemAnnotationSet, so just forward
+ // the call.
+ nsresult rv = OnItemAnnotationSet(aItemId, aName, aSource);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
diff --git a/components/places/src/nsNavBookmarks.h b/components/places/src/nsNavBookmarks.h
new file mode 100644
index 000000000..d5cc3b5b7
--- /dev/null
+++ b/components/places/src/nsNavBookmarks.h
@@ -0,0 +1,445 @@
+/* -*- 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/. */
+
+#ifndef nsNavBookmarks_h_
+#define nsNavBookmarks_h_
+
+#include "nsINavBookmarksService.h"
+#include "nsIAnnotationService.h"
+#include "nsITransaction.h"
+#include "nsNavHistory.h"
+#include "nsToolkitCompsCID.h"
+#include "nsCategoryCache.h"
+#include "nsTHashtable.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+#include "prtime.h"
+
+class nsNavBookmarks;
+
+namespace mozilla {
+namespace places {
+
+ enum BookmarkStatementId {
+ DB_FIND_REDIRECTED_BOOKMARK = 0
+ , DB_GET_BOOKMARKS_FOR_URI
+ };
+
+ struct BookmarkData {
+ int64_t id;
+ nsCString url;
+ nsCString title;
+ int32_t position;
+ int64_t placeId;
+ int64_t parentId;
+ int64_t grandParentId;
+ int32_t type;
+ nsCString serviceCID;
+ PRTime dateAdded;
+ PRTime lastModified;
+ nsCString guid;
+ nsCString parentGuid;
+ };
+
+ struct ItemVisitData {
+ BookmarkData bookmark;
+ int64_t visitId;
+ uint32_t transitionType;
+ PRTime time;
+ };
+
+ struct ItemChangeData {
+ BookmarkData bookmark;
+ nsCString property;
+ bool isAnnotation;
+ nsCString newValue;
+ nsCString oldValue;
+ };
+
+ typedef void (nsNavBookmarks::*ItemVisitMethod)(const ItemVisitData&);
+ typedef void (nsNavBookmarks::*ItemChangeMethod)(const ItemChangeData&);
+
+ enum BookmarkDate {
+ DATE_ADDED = 0
+ , LAST_MODIFIED
+ };
+
+} // namespace places
+} // namespace mozilla
+
+class nsNavBookmarks final : public nsINavBookmarksService
+ , public nsINavHistoryObserver
+ , public nsIAnnotationObserver
+ , public nsIObserver
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINAVBOOKMARKSSERVICE
+ NS_DECL_NSINAVHISTORYOBSERVER
+ NS_DECL_NSIANNOTATIONOBSERVER
+ NS_DECL_NSIOBSERVER
+
+ nsNavBookmarks();
+
+ /**
+ * Obtains the service's object.
+ */
+ static already_AddRefed<nsNavBookmarks> GetSingleton();
+
+ /**
+ * Initializes the service's object. This should only be called once.
+ */
+ nsresult Init();
+
+ static nsNavBookmarks* GetBookmarksService() {
+ if (!gBookmarksService) {
+ nsCOMPtr<nsINavBookmarksService> serv =
+ do_GetService(NS_NAVBOOKMARKSSERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(serv, nullptr);
+ NS_ASSERTION(gBookmarksService,
+ "Should have static instance pointer now");
+ }
+ return gBookmarksService;
+ }
+
+ typedef mozilla::places::BookmarkData BookmarkData;
+ typedef mozilla::places::ItemVisitData ItemVisitData;
+ typedef mozilla::places::ItemChangeData ItemChangeData;
+ typedef mozilla::places::BookmarkStatementId BookmarkStatementId;
+
+ nsresult ResultNodeForContainer(int64_t aID,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode** aNode);
+
+ // Find all the children of a folder, using the given query and options.
+ // For each child, a ResultNode is created and added to |children|.
+ // The results are ordered by folder position.
+ nsresult QueryFolderChildren(int64_t aFolderId,
+ nsNavHistoryQueryOptions* aOptions,
+ nsCOMArray<nsNavHistoryResultNode>* children);
+
+ /**
+ * Turns aRow into a node and appends it to aChildren if it is appropriate to
+ * do so.
+ *
+ * @param aRow
+ * A Storage statement (in the case of synchronous execution) or row of
+ * a result set (in the case of asynchronous execution).
+ * @param aOptions
+ * The options of the parent folder node.
+ * @param aChildren
+ * The children of the parent folder node.
+ * @param aCurrentIndex
+ * The index of aRow within the results. When called on the first row,
+ * this should be set to -1.
+ */
+ nsresult ProcessFolderNodeRow(mozIStorageValueArray* aRow,
+ nsNavHistoryQueryOptions* aOptions,
+ nsCOMArray<nsNavHistoryResultNode>* aChildren,
+ int32_t& aCurrentIndex);
+
+ /**
+ * The async version of QueryFolderChildren.
+ *
+ * @param aNode
+ * The folder node that will receive the children.
+ * @param _pendingStmt
+ * The Storage pending statement that will be used to control async
+ * execution.
+ */
+ nsresult QueryFolderChildrenAsync(nsNavHistoryFolderResultNode* aNode,
+ int64_t aFolderId,
+ mozIStoragePendingStatement** _pendingStmt);
+
+ /**
+ * @return index of the new folder in aIndex, whether it was passed in or
+ * generated by autoincrement.
+ *
+ * @note If aFolder is -1, uses the autoincrement id for folder index.
+ * @note aTitle will be truncated to TITLE_LENGTH_MAX
+ */
+ nsresult CreateContainerWithID(int64_t aId, int64_t aParent,
+ const nsACString& aTitle,
+ bool aIsBookmarkFolder,
+ int32_t* aIndex,
+ const nsACString& aGUID,
+ uint16_t aSource,
+ int64_t* aNewFolder);
+
+ /**
+ * Fetches information about the specified id from the database.
+ *
+ * @param aItemId
+ * Id of the item to fetch information for.
+ * @param aBookmark
+ * BookmarkData to store the information.
+ */
+ nsresult FetchItemInfo(int64_t aItemId,
+ BookmarkData& _bookmark);
+
+ /**
+ * Notifies that a bookmark has been visited.
+ *
+ * @param aItemId
+ * The visited item id.
+ * @param aData
+ * Details about the new visit.
+ */
+ void NotifyItemVisited(const ItemVisitData& aData);
+
+ /**
+ * Notifies that a bookmark has changed.
+ *
+ * @param aItemId
+ * The changed item id.
+ * @param aData
+ * Details about the change.
+ */
+ void NotifyItemChanged(const ItemChangeData& aData);
+
+ /**
+ * Recursively builds an array of descendant folders inside a given folder.
+ *
+ * @param aFolderId
+ * The folder to fetch descendants from.
+ * @param aDescendantFoldersArray
+ * Output array to put descendant folders id.
+ */
+ nsresult GetDescendantFolders(int64_t aFolderId,
+ nsTArray<int64_t>& aDescendantFoldersArray);
+
+ static const int32_t kGetChildrenIndex_Guid;
+ static const int32_t kGetChildrenIndex_Position;
+ static const int32_t kGetChildrenIndex_Type;
+ static const int32_t kGetChildrenIndex_PlaceID;
+
+ static mozilla::Atomic<int64_t> sLastInsertedItemId;
+ static void StoreLastInsertedId(const nsACString& aTable,
+ const int64_t aLastInsertedId);
+
+private:
+ static nsNavBookmarks* gBookmarksService;
+
+ ~nsNavBookmarks();
+
+ /**
+ * Checks whether or not aFolderId points to a live bookmark.
+ *
+ * @param aFolderId
+ * the item-id of the folder to check.
+ * @return true if aFolderId points to live bookmarks, false otherwise.
+ */
+ bool IsLivemark(int64_t aFolderId);
+
+ /**
+ * Locates the root items in the bookmarks folder hierarchy assigning folder
+ * ids to the root properties that are exposed through the service interface.
+ */
+ nsresult ReadRoots();
+
+ nsresult AdjustIndices(int64_t aFolder,
+ int32_t aStartIndex,
+ int32_t aEndIndex,
+ int32_t aDelta);
+
+ /**
+ * Fetches properties of a folder.
+ *
+ * @param aFolderId
+ * Folder to count children for.
+ * @param _folderCount
+ * Number of children in the folder.
+ * @param _guid
+ * Unique id of the folder.
+ * @param _parentId
+ * Id of the parent of the folder.
+ *
+ * @throws If folder does not exist.
+ */
+ nsresult FetchFolderInfo(int64_t aFolderId,
+ int32_t* _folderCount,
+ nsACString& _guid,
+ int64_t* _parentId);
+
+ nsresult GetLastChildId(int64_t aFolder, int64_t* aItemId);
+
+ /**
+ * This is an handle to the Places database.
+ */
+ RefPtr<mozilla::places::Database> mDB;
+
+ int32_t mItemCount;
+
+ nsMaybeWeakPtrArray<nsINavBookmarkObserver> mObservers;
+
+ int64_t mRoot;
+ int64_t mMenuRoot;
+ int64_t mTagsRoot;
+ int64_t mUnfiledRoot;
+ int64_t mToolbarRoot;
+ int64_t mMobileRoot;
+
+ inline bool IsRoot(int64_t aFolderId) {
+ return aFolderId == mRoot || aFolderId == mMenuRoot ||
+ aFolderId == mTagsRoot || aFolderId == mUnfiledRoot ||
+ aFolderId == mToolbarRoot || aFolderId == mMobileRoot;
+ }
+
+ nsresult IsBookmarkedInDatabase(int64_t aBookmarkID, bool* aIsBookmarked);
+
+ nsresult SetItemDateInternal(enum mozilla::places::BookmarkDate aDateType,
+ int64_t aItemId,
+ PRTime aValue);
+
+ // Recursive method to build an array of folder's children
+ nsresult GetDescendantChildren(int64_t aFolderId,
+ const nsACString& aFolderGuid,
+ int64_t aGrandParentId,
+ nsTArray<BookmarkData>& aFolderChildrenArray);
+
+ enum ItemType {
+ BOOKMARK = TYPE_BOOKMARK,
+ FOLDER = TYPE_FOLDER,
+ SEPARATOR = TYPE_SEPARATOR,
+ };
+
+ /**
+ * Helper to insert a bookmark in the database.
+ *
+ * @param aItemId
+ * The itemId to insert, pass -1 to generate a new one.
+ * @param aPlaceId
+ * The placeId to which this bookmark refers to, pass nullptr for
+ * items that don't refer to an URI (eg. folders, separators, ...).
+ * @param aItemType
+ * The type of the new bookmark, see TYPE_* constants.
+ * @param aParentId
+ * The itemId of the parent folder.
+ * @param aIndex
+ * The position inside the parent folder.
+ * @param aTitle
+ * The title for the new bookmark.
+ * Pass a void string to set a NULL title.
+ * @param aDateAdded
+ * The date for the insertion.
+ * @param [optional] aLastModified
+ * The last modified date for the insertion.
+ * It defaults to aDateAdded.
+ *
+ * @return The new item id that has been inserted.
+ *
+ * @note This will also update last modified date of the parent folder.
+ */
+ nsresult InsertBookmarkInDB(int64_t aPlaceId,
+ enum ItemType aItemType,
+ int64_t aParentId,
+ int32_t aIndex,
+ const nsACString& aTitle,
+ PRTime aDateAdded,
+ PRTime aLastModified,
+ const nsACString& aParentGuid,
+ int64_t aGrandParentId,
+ nsIURI* aURI,
+ uint16_t aSource,
+ int64_t* _itemId,
+ nsACString& _guid);
+
+ /**
+ * TArray version of getBookmarksIdForURI for ease of use in C++ code.
+ * Pass in a reference to a TArray; it will get filled with the
+ * resulting list of bookmark IDs.
+ *
+ * @param aURI
+ * URI to get bookmarks for.
+ * @param aResult
+ * Array of bookmark ids.
+ * @param aSkipTags
+ * If true ids of tags-as-bookmarks entries will be excluded.
+ */
+ nsresult GetBookmarkIdsForURITArray(nsIURI* aURI,
+ nsTArray<int64_t>& aResult,
+ bool aSkipTags);
+
+ nsresult GetBookmarksForURI(nsIURI* aURI,
+ nsTArray<BookmarkData>& _bookmarks);
+
+ int64_t RecursiveFindRedirectedBookmark(int64_t aPlaceId);
+
+ class RemoveFolderTransaction final : public nsITransaction {
+ public:
+ RemoveFolderTransaction(int64_t aID, uint16_t aSource)
+ : mID(aID)
+ , mSource(aSource)
+ {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD DoTransaction() override {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+ BookmarkData folder;
+ nsresult rv = bookmarks->FetchItemInfo(mID, folder);
+ // TODO (Bug 656935): store the BookmarkData struct instead.
+ mParent = folder.parentId;
+ mIndex = folder.position;
+
+ rv = bookmarks->GetItemTitle(mID, mTitle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return bookmarks->RemoveItem(mID, mSource);
+ }
+
+ NS_IMETHOD UndoTransaction() override {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+ int64_t newFolder;
+ return bookmarks->CreateContainerWithID(mID, mParent, mTitle, true,
+ &mIndex, EmptyCString(),
+ mSource, &newFolder);
+ }
+
+ NS_IMETHOD RedoTransaction() override {
+ return DoTransaction();
+ }
+
+ NS_IMETHOD GetIsTransient(bool* aResult) override {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aResult) override {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ private:
+ ~RemoveFolderTransaction() {}
+
+ int64_t mID;
+ uint16_t mSource;
+ MOZ_INIT_OUTSIDE_CTOR int64_t mParent;
+ nsCString mTitle;
+ MOZ_INIT_OUTSIDE_CTOR int32_t mIndex;
+ };
+
+ // Used to enable and disable the observer notifications.
+ bool mCanNotify;
+ nsCategoryCache<nsINavBookmarkObserver> mCacheObservers;
+
+ // Tracks whether we are in batch mode.
+ // Note: this is only tracking bookmarks batches, not history ones.
+ bool mBatching;
+
+ /**
+ * This function must be called every time a bookmark is removed.
+ *
+ * @param aURI
+ * Uri to test.
+ */
+ nsresult UpdateKeywordsForRemovedBookmark(const BookmarkData& aBookmark);
+};
+
+#endif // nsNavBookmarks_h_
diff --git a/components/places/src/nsNavHistory.cpp b/components/places/src/nsNavHistory.cpp
new file mode 100644
index 000000000..cf4edc633
--- /dev/null
+++ b/components/places/src/nsNavHistory.cpp
@@ -0,0 +1,4501 @@
+/* -*- 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 <stdio.h>
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsNavHistory.h"
+
+#include "mozIPlacesAutoComplete.h"
+#include "nsNavBookmarks.h"
+#include "nsAnnotationService.h"
+#include "nsFaviconService.h"
+#include "nsPlacesMacros.h"
+#include "History.h"
+#include "Helpers.h"
+
+#include "nsTArray.h"
+#include "nsCollationCID.h"
+#include "nsILocaleService.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "nsPromiseFlatString.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "prsystem.h"
+#include "prtime.h"
+#include "nsEscape.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIIDNService.h"
+#include "nsThreadUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsMathUtils.h"
+#include "mozilla/storage.h"
+#include "mozilla/Preferences.h"
+#include <algorithm>
+
+#include "nsIAutoCompleteInput.h"
+#include "nsIAutoCompletePopup.h"
+
+using namespace mozilla;
+using namespace mozilla::places;
+
+// The maximum number of things that we will store in the recent events list
+// before calling ExpireNonrecentEvents. This number should be big enough so it
+// is very difficult to get that many unconsumed events (for example, typed but
+// never visited) in the RECENT_EVENT_THRESHOLD. Otherwise, we'll start
+// checking each one for every page visit, which will be somewhat slower.
+#define RECENT_EVENT_QUEUE_MAX_LENGTH 128
+
+// preference ID strings
+#define PREF_HISTORY_ENABLED "places.history.enabled"
+
+#define PREF_FREC_NUM_VISITS "places.frecency.numVisits"
+#define PREF_FREC_NUM_VISITS_DEF 10
+#define PREF_FREC_FIRST_BUCKET_CUTOFF "places.frecency.firstBucketCutoff"
+#define PREF_FREC_FIRST_BUCKET_CUTOFF_DEF 4
+#define PREF_FREC_SECOND_BUCKET_CUTOFF "places.frecency.secondBucketCutoff"
+#define PREF_FREC_SECOND_BUCKET_CUTOFF_DEF 14
+#define PREF_FREC_THIRD_BUCKET_CUTOFF "places.frecency.thirdBucketCutoff"
+#define PREF_FREC_THIRD_BUCKET_CUTOFF_DEF 31
+#define PREF_FREC_FOURTH_BUCKET_CUTOFF "places.frecency.fourthBucketCutoff"
+#define PREF_FREC_FOURTH_BUCKET_CUTOFF_DEF 90
+#define PREF_FREC_FIRST_BUCKET_WEIGHT "places.frecency.firstBucketWeight"
+#define PREF_FREC_FIRST_BUCKET_WEIGHT_DEF 100
+#define PREF_FREC_SECOND_BUCKET_WEIGHT "places.frecency.secondBucketWeight"
+#define PREF_FREC_SECOND_BUCKET_WEIGHT_DEF 70
+#define PREF_FREC_THIRD_BUCKET_WEIGHT "places.frecency.thirdBucketWeight"
+#define PREF_FREC_THIRD_BUCKET_WEIGHT_DEF 50
+#define PREF_FREC_FOURTH_BUCKET_WEIGHT "places.frecency.fourthBucketWeight"
+#define PREF_FREC_FOURTH_BUCKET_WEIGHT_DEF 30
+#define PREF_FREC_DEFAULT_BUCKET_WEIGHT "places.frecency.defaultBucketWeight"
+#define PREF_FREC_DEFAULT_BUCKET_WEIGHT_DEF 10
+#define PREF_FREC_EMBED_VISIT_BONUS "places.frecency.embedVisitBonus"
+#define PREF_FREC_EMBED_VISIT_BONUS_DEF 0
+#define PREF_FREC_FRAMED_LINK_VISIT_BONUS "places.frecency.framedLinkVisitBonus"
+#define PREF_FREC_FRAMED_LINK_VISIT_BONUS_DEF 0
+#define PREF_FREC_LINK_VISIT_BONUS "places.frecency.linkVisitBonus"
+#define PREF_FREC_LINK_VISIT_BONUS_DEF 100
+#define PREF_FREC_TYPED_VISIT_BONUS "places.frecency.typedVisitBonus"
+#define PREF_FREC_TYPED_VISIT_BONUS_DEF 2000
+#define PREF_FREC_BOOKMARK_VISIT_BONUS "places.frecency.bookmarkVisitBonus"
+#define PREF_FREC_BOOKMARK_VISIT_BONUS_DEF 75
+#define PREF_FREC_DOWNLOAD_VISIT_BONUS "places.frecency.downloadVisitBonus"
+#define PREF_FREC_DOWNLOAD_VISIT_BONUS_DEF 0
+#define PREF_FREC_PERM_REDIRECT_VISIT_BONUS "places.frecency.permRedirectVisitBonus"
+#define PREF_FREC_PERM_REDIRECT_VISIT_BONUS_DEF 0
+#define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS "places.frecency.tempRedirectVisitBonus"
+#define PREF_FREC_TEMP_REDIRECT_VISIT_BONUS_DEF 0
+#define PREF_FREC_DEFAULT_VISIT_BONUS "places.frecency.defaultVisitBonus"
+#define PREF_FREC_DEFAULT_VISIT_BONUS_DEF 0
+#define PREF_FREC_UNVISITED_BOOKMARK_BONUS "places.frecency.unvisitedBookmarkBonus"
+#define PREF_FREC_UNVISITED_BOOKMARK_BONUS_DEF 140
+#define PREF_FREC_UNVISITED_TYPED_BONUS "places.frecency.unvisitedTypedBonus"
+#define PREF_FREC_UNVISITED_TYPED_BONUS_DEF 200
+#define PREF_FREC_RELOAD_VISIT_BONUS "places.frecency.reloadVisitBonus"
+#define PREF_FREC_RELOAD_VISIT_BONUS_DEF 0
+
+// In order to avoid calling PR_now() too often we use a cached "now" value
+// for repeating stuff. These are milliseconds between "now" cache refreshes.
+#define RENEW_CACHED_NOW_TIMEOUT ((int32_t)3 * PR_MSEC_PER_SEC)
+
+// character-set annotation
+#define CHARSET_ANNO NS_LITERAL_CSTRING("URIProperties/characterSet")
+
+// These macros are used when splitting history by date.
+// These are the day containers and catch-all final container.
+#define HISTORY_ADDITIONAL_DATE_CONT_NUM 3
+// We use a guess of the number of months considering all of them 30 days
+// long, but we split only the last 6 months.
+#define HISTORY_DATE_CONT_NUM(_daysFromOldestVisit) \
+ (HISTORY_ADDITIONAL_DATE_CONT_NUM + \
+ std::min(6, (int32_t)ceilf((float)_daysFromOldestVisit/30)))
+// Max number of containers, used to initialize the params hash.
+#define HISTORY_DATE_CONT_LENGTH 8
+
+// Initial length of the embed visits cache.
+#define EMBED_VISITS_INITIAL_CACHE_LENGTH 64
+
+// Initial length of the recent events cache.
+#define RECENT_EVENTS_INITIAL_CACHE_LENGTH 64
+
+// Observed topics.
+#define TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING "autocomplete-will-enter-text"
+#define TOPIC_IDLE_DAILY "idle-daily"
+#define TOPIC_PREF_CHANGED "nsPref:changed"
+#define TOPIC_PROFILE_TEARDOWN "profile-change-teardown"
+#define TOPIC_PROFILE_CHANGE "profile-before-change"
+
+static const char* kObservedPrefs[] = {
+ PREF_HISTORY_ENABLED
+, PREF_FREC_NUM_VISITS
+, PREF_FREC_FIRST_BUCKET_CUTOFF
+, PREF_FREC_SECOND_BUCKET_CUTOFF
+, PREF_FREC_THIRD_BUCKET_CUTOFF
+, PREF_FREC_FOURTH_BUCKET_CUTOFF
+, PREF_FREC_FIRST_BUCKET_WEIGHT
+, PREF_FREC_SECOND_BUCKET_WEIGHT
+, PREF_FREC_THIRD_BUCKET_WEIGHT
+, PREF_FREC_FOURTH_BUCKET_WEIGHT
+, PREF_FREC_DEFAULT_BUCKET_WEIGHT
+, PREF_FREC_EMBED_VISIT_BONUS
+, PREF_FREC_FRAMED_LINK_VISIT_BONUS
+, PREF_FREC_LINK_VISIT_BONUS
+, PREF_FREC_TYPED_VISIT_BONUS
+, PREF_FREC_BOOKMARK_VISIT_BONUS
+, PREF_FREC_DOWNLOAD_VISIT_BONUS
+, PREF_FREC_PERM_REDIRECT_VISIT_BONUS
+, PREF_FREC_TEMP_REDIRECT_VISIT_BONUS
+, PREF_FREC_DEFAULT_VISIT_BONUS
+, PREF_FREC_UNVISITED_BOOKMARK_BONUS
+, PREF_FREC_UNVISITED_TYPED_BONUS
+, nullptr
+};
+
+NS_IMPL_ADDREF(nsNavHistory)
+NS_IMPL_RELEASE(nsNavHistory)
+
+NS_IMPL_CLASSINFO(nsNavHistory, nullptr, nsIClassInfo::SINGLETON,
+ NS_NAVHISTORYSERVICE_CID)
+NS_INTERFACE_MAP_BEGIN(nsNavHistory)
+ NS_INTERFACE_MAP_ENTRY(nsINavHistoryService)
+ NS_INTERFACE_MAP_ENTRY(nsIBrowserHistory)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsPIPlacesDatabase)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageVacuumParticipant)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryService)
+ NS_IMPL_QUERY_CLASSINFO(nsNavHistory)
+NS_INTERFACE_MAP_END
+
+// We don't care about flattening everything
+NS_IMPL_CI_INTERFACE_GETTER(nsNavHistory,
+ nsINavHistoryService,
+ nsIBrowserHistory)
+
+namespace {
+
+static int64_t GetSimpleBookmarksQueryFolder(
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions);
+static void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsTArray<nsTArray<nsString>*>* aTerms);
+
+void GetTagsSqlFragment(int64_t aTagsFolder,
+ const nsACString& aRelation,
+ bool aHasSearchTerms,
+ nsACString& _sqlFragment) {
+ if (!aHasSearchTerms)
+ _sqlFragment.AssignLiteral("null");
+ else {
+ // This subquery DOES NOT order tags for performance reasons.
+ _sqlFragment.Assign(NS_LITERAL_CSTRING(
+ "(SELECT GROUP_CONCAT(t_t.title, ',') "
+ "FROM moz_bookmarks b_t "
+ "JOIN moz_bookmarks t_t ON t_t.id = +b_t.parent "
+ "WHERE b_t.fk = ") + aRelation + NS_LITERAL_CSTRING(" "
+ "AND t_t.parent = ") +
+ nsPrintfCString("%lld", aTagsFolder) + NS_LITERAL_CSTRING(" "
+ ")"));
+ }
+
+ _sqlFragment.AppendLiteral(" AS tags ");
+}
+
+/**
+ * This class sets begin/end of batch updates to correspond to C++ scopes so
+ * we can be sure end always gets called.
+ */
+class UpdateBatchScoper
+{
+public:
+ explicit UpdateBatchScoper(nsNavHistory& aNavHistory) : mNavHistory(aNavHistory)
+ {
+ mNavHistory.BeginUpdateBatch();
+ }
+ ~UpdateBatchScoper()
+ {
+ mNavHistory.EndUpdateBatch();
+ }
+protected:
+ nsNavHistory& mNavHistory;
+};
+
+} // namespace
+
+
+// Queries rows indexes to bind or get values, if adding a new one, be sure to
+// update nsNavBookmarks statements and its kGetChildrenIndex_* constants
+const int32_t nsNavHistory::kGetInfoIndex_PageID = 0;
+const int32_t nsNavHistory::kGetInfoIndex_URL = 1;
+const int32_t nsNavHistory::kGetInfoIndex_Title = 2;
+const int32_t nsNavHistory::kGetInfoIndex_RevHost = 3;
+const int32_t nsNavHistory::kGetInfoIndex_VisitCount = 4;
+const int32_t nsNavHistory::kGetInfoIndex_VisitDate = 5;
+const int32_t nsNavHistory::kGetInfoIndex_FaviconURL = 6;
+const int32_t nsNavHistory::kGetInfoIndex_ItemId = 7;
+const int32_t nsNavHistory::kGetInfoIndex_ItemDateAdded = 8;
+const int32_t nsNavHistory::kGetInfoIndex_ItemLastModified = 9;
+const int32_t nsNavHistory::kGetInfoIndex_ItemParentId = 10;
+const int32_t nsNavHistory::kGetInfoIndex_ItemTags = 11;
+const int32_t nsNavHistory::kGetInfoIndex_Frecency = 12;
+const int32_t nsNavHistory::kGetInfoIndex_Hidden = 13;
+const int32_t nsNavHistory::kGetInfoIndex_Guid = 14;
+const int32_t nsNavHistory::kGetInfoIndex_VisitId = 15;
+const int32_t nsNavHistory::kGetInfoIndex_FromVisitId = 16;
+const int32_t nsNavHistory::kGetInfoIndex_VisitType = 17;
+// These columns are followed by corresponding constants in nsNavBookmarks.cpp,
+// which must be kept in sync:
+// nsNavBookmarks::kGetChildrenIndex_Guid = 18;
+// nsNavBookmarks::kGetChildrenIndex_Position = 19;
+// nsNavBookmarks::kGetChildrenIndex_Type = 20;
+// nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
+
+PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavHistory, gHistoryService)
+
+
+nsNavHistory::nsNavHistory()
+ : mBatchLevel(0)
+ , mBatchDBTransaction(nullptr)
+ , mCachedNow(0)
+ , mRecentTyped(RECENT_EVENTS_INITIAL_CACHE_LENGTH)
+ , mRecentLink(RECENT_EVENTS_INITIAL_CACHE_LENGTH)
+ , mRecentBookmark(RECENT_EVENTS_INITIAL_CACHE_LENGTH)
+ , mEmbedVisits(EMBED_VISITS_INITIAL_CACHE_LENGTH)
+ , mHistoryEnabled(true)
+ , mNumVisitsForFrecency(10)
+ , mTagsFolder(-1)
+ , mDaysOfHistory(-1)
+ , mLastCachedStartOfDay(INT64_MAX)
+ , mLastCachedEndOfDay(0)
+ , mCanNotify(true)
+ , mCacheObservers("history-observers")
+{
+ NS_ASSERTION(!gHistoryService,
+ "Attempting to create two instances of the service!");
+ gHistoryService = this;
+}
+
+
+nsNavHistory::~nsNavHistory()
+{
+ // remove the static reference to the service. Check to make sure its us
+ // in case somebody creates an extra instance of the service.
+ NS_ASSERTION(gHistoryService == this,
+ "Deleting a non-singleton instance of the service");
+ if (gHistoryService == this)
+ gHistoryService = nullptr;
+}
+
+
+nsresult
+nsNavHistory::Init()
+{
+ LoadPrefs();
+
+ mDB = Database::GetDatabase();
+ NS_ENSURE_STATE(mDB);
+
+ /*****************************************************************************
+ *** IMPORTANT NOTICE!
+ ***
+ *** Nothing after these add observer calls should return anything but NS_OK.
+ *** If a failure code is returned, this nsNavHistory object will be held onto
+ *** by the observer service and the preference service.
+ ****************************************************************************/
+
+ // Observe preferences changes.
+ Preferences::AddWeakObservers(this, kObservedPrefs);
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ (void)obsSvc->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
+ (void)obsSvc->AddObserver(this, TOPIC_IDLE_DAILY, true);
+ (void)obsSvc->AddObserver(this, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING, true);
+ }
+
+ // Don't add code that can fail here! Do it up above, before we add our
+ // observers.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistory::GetDatabaseStatus(uint16_t *aDatabaseStatus)
+{
+ NS_ENSURE_ARG_POINTER(aDatabaseStatus);
+ *aDatabaseStatus = mDB->GetDatabaseStatus();
+ return NS_OK;
+}
+
+uint32_t
+nsNavHistory::GetRecentFlags(nsIURI *aURI)
+{
+ uint32_t result = 0;
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to get aURI's spec");
+
+ if (NS_SUCCEEDED(rv)) {
+ if (CheckIsRecentEvent(&mRecentTyped, spec))
+ result |= RECENT_TYPED;
+ if (CheckIsRecentEvent(&mRecentLink, spec))
+ result |= RECENT_ACTIVATED;
+ if (CheckIsRecentEvent(&mRecentBookmark, spec))
+ result |= RECENT_BOOKMARKED;
+ }
+
+ return result;
+}
+
+nsresult
+nsNavHistory::GetIdForPage(nsIURI* aURI,
+ int64_t* _pageId,
+ nsCString& _GUID)
+{
+ *_pageId = 0;
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT id, url, title, rev_host, visit_count, guid "
+ "FROM moz_places "
+ "WHERE url_hash = hash(:page_url) AND url = :page_url "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasEntry = false;
+ rv = stmt->ExecuteStep(&hasEntry);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (hasEntry) {
+ rv = stmt->GetInt64(0, _pageId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->GetUTF8String(5, _GUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNavHistory::GetOrCreateIdForPage(nsIURI* aURI,
+ int64_t* _pageId,
+ nsCString& _GUID)
+{
+ nsresult rv = GetIdForPage(aURI, _pageId, _GUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*_pageId != 0) {
+ return NS_OK;
+ }
+
+ // Create a new hidden, untyped and unvisited entry.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "INSERT INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid) "
+ "VALUES (:page_url, hash(:page_url), :rev_host, :hidden, :frecency, :guid) "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // host (reversed with trailing period)
+ nsAutoString revHost;
+ rv = GetReversedHostname(aURI, revHost);
+ // Not all URI types have hostnames, so this is optional.
+ if (NS_SUCCEEDED(rv)) {
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"), revHost);
+ } else {
+ rv = stmt->BindNullByName(NS_LITERAL_CSTRING("rev_host"));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hidden"), 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("frecency"),
+ IsQueryURI(spec) ? 0 : -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString guid;
+ rv = GenerateGUID(_GUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), _GUID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_pageId = sLastInsertedPlaceId;
+
+ return NS_OK;
+}
+
+
+void
+nsNavHistory::LoadPrefs()
+{
+ // History preferences.
+ mHistoryEnabled = Preferences::GetBool(PREF_HISTORY_ENABLED, true);
+
+ // Frecency preferences.
+#define FRECENCY_PREF(_prop, _pref) \
+ _prop = Preferences::GetInt(_pref, _pref##_DEF)
+
+ FRECENCY_PREF(mNumVisitsForFrecency, PREF_FREC_NUM_VISITS);
+ FRECENCY_PREF(mFirstBucketCutoffInDays, PREF_FREC_FIRST_BUCKET_CUTOFF);
+ FRECENCY_PREF(mSecondBucketCutoffInDays, PREF_FREC_SECOND_BUCKET_CUTOFF);
+ FRECENCY_PREF(mThirdBucketCutoffInDays, PREF_FREC_THIRD_BUCKET_CUTOFF);
+ FRECENCY_PREF(mFourthBucketCutoffInDays, PREF_FREC_FOURTH_BUCKET_CUTOFF);
+ FRECENCY_PREF(mEmbedVisitBonus, PREF_FREC_EMBED_VISIT_BONUS);
+ FRECENCY_PREF(mFramedLinkVisitBonus, PREF_FREC_FRAMED_LINK_VISIT_BONUS);
+ FRECENCY_PREF(mLinkVisitBonus, PREF_FREC_LINK_VISIT_BONUS);
+ FRECENCY_PREF(mTypedVisitBonus, PREF_FREC_TYPED_VISIT_BONUS);
+ FRECENCY_PREF(mBookmarkVisitBonus, PREF_FREC_BOOKMARK_VISIT_BONUS);
+ FRECENCY_PREF(mDownloadVisitBonus, PREF_FREC_DOWNLOAD_VISIT_BONUS);
+ FRECENCY_PREF(mPermRedirectVisitBonus, PREF_FREC_PERM_REDIRECT_VISIT_BONUS);
+ FRECENCY_PREF(mTempRedirectVisitBonus, PREF_FREC_TEMP_REDIRECT_VISIT_BONUS);
+ FRECENCY_PREF(mDefaultVisitBonus, PREF_FREC_DEFAULT_VISIT_BONUS);
+ FRECENCY_PREF(mUnvisitedBookmarkBonus, PREF_FREC_UNVISITED_BOOKMARK_BONUS);
+ FRECENCY_PREF(mUnvisitedTypedBonus, PREF_FREC_UNVISITED_TYPED_BONUS);
+ FRECENCY_PREF(mReloadVisitBonus, PREF_FREC_RELOAD_VISIT_BONUS);
+ FRECENCY_PREF(mFirstBucketWeight, PREF_FREC_FIRST_BUCKET_WEIGHT);
+ FRECENCY_PREF(mSecondBucketWeight, PREF_FREC_SECOND_BUCKET_WEIGHT);
+ FRECENCY_PREF(mThirdBucketWeight, PREF_FREC_THIRD_BUCKET_WEIGHT);
+ FRECENCY_PREF(mFourthBucketWeight, PREF_FREC_FOURTH_BUCKET_WEIGHT);
+ FRECENCY_PREF(mDefaultWeight, PREF_FREC_DEFAULT_BUCKET_WEIGHT);
+
+#undef FRECENCY_PREF
+}
+
+
+void
+nsNavHistory::NotifyOnVisit(nsIURI* aURI,
+ int64_t aVisitId,
+ PRTime aTime,
+ int64_t aReferrerVisitId,
+ int32_t aTransitionType,
+ const nsACString& aGuid,
+ bool aHidden,
+ uint32_t aVisitCount,
+ uint32_t aTyped)
+{
+ MOZ_ASSERT(!aGuid.IsEmpty());
+ // If there's no history, this visit will surely add a day. If the visit is
+ // added before or after the last cached day, the day count may have changed.
+ // Otherwise adding multiple visits in the same day should not invalidate
+ // the cache.
+ if (mDaysOfHistory == 0) {
+ mDaysOfHistory = 1;
+ } else if (aTime > mLastCachedEndOfDay || aTime < mLastCachedStartOfDay) {
+ mDaysOfHistory = -1;
+ }
+
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver,
+ OnVisit(aURI, aVisitId, aTime, 0, aReferrerVisitId,
+ aTransitionType, aGuid, aHidden, aVisitCount, aTyped));
+}
+
+void
+nsNavHistory::NotifyTitleChange(nsIURI* aURI,
+ const nsString& aTitle,
+ const nsACString& aGUID)
+{
+ MOZ_ASSERT(!aGUID.IsEmpty());
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver, OnTitleChanged(aURI, aTitle, aGUID));
+}
+
+void
+nsNavHistory::NotifyFrecencyChanged(nsIURI* aURI,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate)
+{
+ MOZ_ASSERT(!aGUID.IsEmpty());
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver,
+ OnFrecencyChanged(aURI, aNewFrecency, aGUID, aHidden,
+ aLastVisitDate));
+}
+
+void
+nsNavHistory::NotifyManyFrecenciesChanged()
+{
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver,
+ OnManyFrecenciesChanged());
+}
+
+namespace {
+
+class FrecencyNotification : public Runnable
+{
+public:
+ FrecencyNotification(const nsACString& aSpec,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate)
+ : mSpec(aSpec)
+ , mNewFrecency(aNewFrecency)
+ , mGUID(aGUID)
+ , mHidden(aHidden)
+ , mLastVisitDate(aLastVisitDate)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
+ if (navHistory) {
+ nsCOMPtr<nsIURI> uri;
+ (void)NS_NewURI(getter_AddRefs(uri), mSpec);
+ // We cannot assert since some automated tests are checking this path.
+ NS_WARNING_ASSERTION(uri, "Invalid URI in FrecencyNotification");
+ // Notify a frecency change only if we have a valid uri, otherwise
+ // the observer couldn't gather any useful data from the notification.
+ if (uri) {
+ navHistory->NotifyFrecencyChanged(uri, mNewFrecency, mGUID, mHidden,
+ mLastVisitDate);
+ }
+ }
+ return NS_OK;
+ }
+
+private:
+ nsCString mSpec;
+ int32_t mNewFrecency;
+ nsCString mGUID;
+ bool mHidden;
+ PRTime mLastVisitDate;
+};
+
+} // namespace
+
+void
+nsNavHistory::DispatchFrecencyChangedNotification(const nsACString& aSpec,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate) const
+{
+ nsCOMPtr<nsIRunnable> notif = new FrecencyNotification(aSpec, aNewFrecency,
+ aGUID, aHidden,
+ aLastVisitDate);
+ (void)NS_DispatchToMainThread(notif);
+}
+
+Atomic<int64_t> nsNavHistory::sLastInsertedPlaceId(0);
+Atomic<int64_t> nsNavHistory::sLastInsertedVisitId(0);
+
+void // static
+nsNavHistory::StoreLastInsertedId(const nsACString& aTable,
+ const int64_t aLastInsertedId) {
+ if (aTable.Equals(NS_LITERAL_CSTRING("moz_places"))) {
+ nsNavHistory::sLastInsertedPlaceId = aLastInsertedId;
+ } else if (aTable.Equals(NS_LITERAL_CSTRING("moz_historyvisits"))) {
+ nsNavHistory::sLastInsertedVisitId = aLastInsertedId;
+ } else {
+ MOZ_ASSERT(false, "Trying to store the insert id for an unknown table?");
+ }
+}
+
+int32_t
+nsNavHistory::GetDaysOfHistory() {
+ MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread");
+
+ if (mDaysOfHistory != -1)
+ return mDaysOfHistory;
+
+ // SQLite doesn't have a CEIL() function, so we must do that later.
+ // We should also take into account timers resolution, that may be as bad as
+ // 16ms on Windows, so in some cases the difference may be 0, if the
+ // check is done near the visit. Thus remember to check for NULL separately.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT CAST(( "
+ "strftime('%s','now','localtime','utc') - "
+ "(SELECT MIN(visit_date)/1000000 FROM moz_historyvisits) "
+ ") AS DOUBLE) "
+ "/86400, "
+ "strftime('%s','now','localtime','+1 day','start of day','utc') * 1000000"
+ );
+ NS_ENSURE_TRUE(stmt, 0);
+ mozStorageStatementScoper scoper(stmt);
+
+ bool hasResult;
+ if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ // If we get NULL, then there are no visits, otherwise there must always be
+ // at least 1 day of history.
+ bool hasNoVisits;
+ (void)stmt->GetIsNull(0, &hasNoVisits);
+ mDaysOfHistory = hasNoVisits ?
+ 0 : std::max(1, static_cast<int32_t>(ceil(stmt->AsDouble(0))));
+ mLastCachedStartOfDay =
+ NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0);
+ mLastCachedEndOfDay = stmt->AsInt64(1) - 1; // Start of tomorrow - 1.
+ }
+
+ return mDaysOfHistory;
+}
+
+PRTime
+nsNavHistory::GetNow()
+{
+ if (!mCachedNow) {
+ mCachedNow = PR_Now();
+ if (!mExpireNowTimer)
+ mExpireNowTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mExpireNowTimer)
+ mExpireNowTimer->InitWithFuncCallback(expireNowTimerCallback, this,
+ RENEW_CACHED_NOW_TIMEOUT,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+ return mCachedNow;
+}
+
+
+void nsNavHistory::expireNowTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ nsNavHistory *history = static_cast<nsNavHistory *>(aClosure);
+ if (history) {
+ history->mCachedNow = 0;
+ history->mExpireNowTimer = nullptr;
+ }
+}
+
+
+/**
+ * Code borrowed from mozilla/xpfe/components/history/src/nsGlobalHistory.cpp
+ * Pass in a pre-normalized now and a date, and we'll find the difference since
+ * midnight on each of the days.
+ */
+static PRTime
+NormalizeTimeRelativeToday(PRTime aTime)
+{
+ // round to midnight this morning
+ PRExplodedTime explodedTime;
+ PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime);
+
+ // set to midnight (0:00)
+ explodedTime.tm_min =
+ explodedTime.tm_hour =
+ explodedTime.tm_sec =
+ explodedTime.tm_usec = 0;
+
+ return PR_ImplodeTime(&explodedTime);
+}
+
+// nsNavHistory::NormalizeTime
+//
+// Converts a nsINavHistoryQuery reference+offset time into a PRTime
+// relative to the epoch.
+//
+// It is important that this function NOT use the current time optimization.
+// It is called to update queries, and we really need to know what right
+// now is because those incoming values will also have current times that
+// we will have to compare against.
+
+PRTime // static
+nsNavHistory::NormalizeTime(uint32_t aRelative, PRTime aOffset)
+{
+ PRTime ref;
+ switch (aRelative)
+ {
+ case nsINavHistoryQuery::TIME_RELATIVE_EPOCH:
+ return aOffset;
+ case nsINavHistoryQuery::TIME_RELATIVE_TODAY:
+ ref = NormalizeTimeRelativeToday(PR_Now());
+ break;
+ case nsINavHistoryQuery::TIME_RELATIVE_NOW:
+ ref = PR_Now();
+ break;
+ default:
+ NS_NOTREACHED("Invalid relative time");
+ return 0;
+ }
+ return ref + aOffset;
+}
+
+// nsNavHistory::GetUpdateRequirements
+//
+// Returns conditions for query update.
+//
+// QUERYUPDATE_TIME:
+// This query is only limited by an inclusive time range on the first
+// query object. The caller can quickly evaluate the time itself if it
+// chooses. This is even simpler than "simple" below.
+// QUERYUPDATE_SIMPLE:
+// This query is evaluatable using EvaluateQueryForNode to do live
+// updating.
+// QUERYUPDATE_COMPLEX:
+// This query is not evaluatable using EvaluateQueryForNode. When something
+// happens that this query updates, you will need to re-run the query.
+// QUERYUPDATE_COMPLEX_WITH_BOOKMARKS:
+// A complex query that additionally has dependence on bookmarks. All
+// bookmark-dependent queries fall under this category.
+//
+// aHasSearchTerms will be set to true if the query has any dependence on
+// keywords. When there is no dependence on keywords, we can handle title
+// change operations as simple instead of complex.
+
+uint32_t
+nsNavHistory::GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions,
+ bool* aHasSearchTerms)
+{
+ NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
+
+ // first check if there are search terms
+ *aHasSearchTerms = false;
+ int32_t i;
+ for (i = 0; i < aQueries.Count(); i ++) {
+ aQueries[i]->GetHasSearchTerms(aHasSearchTerms);
+ if (*aHasSearchTerms)
+ break;
+ }
+
+ bool nonTimeBasedItems = false;
+ bool domainBasedItems = false;
+
+ for (i = 0; i < aQueries.Count(); i ++) {
+ nsNavHistoryQuery* query = aQueries[i];
+
+ if (query->Folders().Length() > 0 ||
+ query->OnlyBookmarked() ||
+ query->Tags().Length() > 0) {
+ return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
+ }
+
+ // Note: we don't currently have any complex non-bookmarked items, but these
+ // are expected to be added. Put detection of these items here.
+ if (!query->SearchTerms().IsEmpty() ||
+ !query->Domain().IsVoid() ||
+ query->Uri() != nullptr)
+ nonTimeBasedItems = true;
+
+ if (! query->Domain().IsVoid())
+ domainBasedItems = true;
+ }
+
+ if (aOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY)
+ return QUERYUPDATE_COMPLEX_WITH_BOOKMARKS;
+
+ // Whenever there is a maximum number of results,
+ // and we are not a bookmark query we must requery. This
+ // is because we can't generally know if any given addition/change causes
+ // the item to be in the top N items in the database.
+ if (aOptions->MaxResults() > 0)
+ return QUERYUPDATE_COMPLEX;
+
+ if (aQueries.Count() == 1 && domainBasedItems)
+ return QUERYUPDATE_HOST;
+ if (aQueries.Count() == 1 && !nonTimeBasedItems)
+ return QUERYUPDATE_TIME;
+
+ return QUERYUPDATE_SIMPLE;
+}
+
+
+// nsNavHistory::EvaluateQueryForNode
+//
+// This runs the node through the given queries to see if satisfies the
+// query conditions. Not every query parameters are handled by this code,
+// but we handle the most common ones so that performance is better.
+//
+// We assume that the time on the node is the time that we want to compare.
+// This is not necessarily true because URL nodes have the last access time,
+// which is not necessarily the same. However, since this is being called
+// to update the list, we assume that the last access time is the current
+// access time that we are being asked to compare so it works out.
+//
+// Returns true if node matches the query, false if not.
+
+bool
+nsNavHistory::EvaluateQueryForNode(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode* aNode)
+{
+ // lazily created from the node's string when we need to match URIs
+ nsCOMPtr<nsIURI> nodeUri;
+
+ // --- hidden ---
+ if (aNode->mHidden && !aOptions->IncludeHidden())
+ return false;
+
+ for (int32_t i = 0; i < aQueries.Count(); i ++) {
+ bool hasIt;
+ nsCOMPtr<nsNavHistoryQuery> query = aQueries[i];
+
+ // --- begin time ---
+ query->GetHasBeginTime(&hasIt);
+ if (hasIt) {
+ PRTime beginTime = NormalizeTime(query->BeginTimeReference(),
+ query->BeginTime());
+ if (aNode->mTime < beginTime)
+ continue; // before our time range
+ }
+
+ // --- end time ---
+ query->GetHasEndTime(&hasIt);
+ if (hasIt) {
+ PRTime endTime = NormalizeTime(query->EndTimeReference(),
+ query->EndTime());
+ if (aNode->mTime > endTime)
+ continue; // after our time range
+ }
+
+ // --- search terms ---
+ if (! query->SearchTerms().IsEmpty()) {
+ // we can use the existing filtering code, just give it our one object in
+ // an array.
+ nsCOMArray<nsNavHistoryResultNode> inputSet;
+ inputSet.AppendObject(aNode);
+ nsCOMArray<nsNavHistoryQuery> queries;
+ queries.AppendObject(query);
+ nsCOMArray<nsNavHistoryResultNode> filteredSet;
+ nsresult rv = FilterResultSet(nullptr, inputSet, &filteredSet, queries, aOptions);
+ if (NS_FAILED(rv))
+ continue;
+ if (! filteredSet.Count())
+ continue; // did not make it through the filter, doesn't match
+ }
+
+ // --- domain/host matching ---
+ query->GetHasDomain(&hasIt);
+ if (hasIt) {
+ if (! nodeUri) {
+ // lazy creation of nodeUri, which might be checked for multiple queries
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI)))
+ continue;
+ }
+ nsAutoCString asciiRequest;
+ if (NS_FAILED(AsciiHostNameFromHostString(query->Domain(), asciiRequest)))
+ continue;
+
+ if (query->DomainIsHost()) {
+ nsAutoCString host;
+ if (NS_FAILED(nodeUri->GetAsciiHost(host)))
+ continue;
+
+ if (! asciiRequest.Equals(host))
+ continue; // host names don't match
+ }
+ // check domain names
+ nsAutoCString domain;
+ DomainNameFromURI(nodeUri, domain);
+ if (! asciiRequest.Equals(domain))
+ continue; // domain names don't match
+ }
+
+ // --- URI matching ---
+ if (query->Uri()) {
+ if (! nodeUri) { // lazy creation of nodeUri
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(nodeUri), aNode->mURI)))
+ continue;
+ }
+
+ bool equals;
+ nsresult rv = query->Uri()->Equals(nodeUri, &equals);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (! equals)
+ continue;
+ }
+
+ // Transitions matching.
+ const nsTArray<uint32_t>& transitions = query->Transitions();
+ if (aNode->mTransitionType > 0 &&
+ transitions.Length() &&
+ !transitions.Contains(aNode->mTransitionType)) {
+ continue; // transition doesn't match.
+ }
+
+ // If we ever make it to the bottom of this loop, that means it passed all
+ // tests for the given query. Since queries are ORed together, that means
+ // it passed everything and we are done.
+ return true;
+ }
+
+ // didn't match any query
+ return false;
+}
+
+
+// nsNavHistory::AsciiHostNameFromHostString
+//
+// We might have interesting encodings and different case in the host name.
+// This will convert that host name into an ASCII host name by sending it
+// through the URI canonicalization. The result can be used for comparison
+// with other ASCII host name strings.
+nsresult // static
+nsNavHistory::AsciiHostNameFromHostString(const nsACString& aHostName,
+ nsACString& aAscii)
+{
+ aAscii.Truncate();
+ if (aHostName.IsEmpty()) {
+ return NS_OK;
+ }
+ // To properly generate a uri we must provide a protocol.
+ nsAutoCString fakeURL("http://");
+ fakeURL.Append(aHostName);
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), fakeURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = uri->GetAsciiHost(aAscii);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+
+// nsNavHistory::DomainNameFromURI
+//
+// This does the www.mozilla.org -> mozilla.org and
+// foo.theregister.co.uk -> theregister.co.uk conversion
+void
+nsNavHistory::DomainNameFromURI(nsIURI *aURI,
+ nsACString& aDomainName)
+{
+ // lazily get the effective tld service
+ if (!mTLDService)
+ mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+
+ if (mTLDService) {
+ // get the base domain for a given hostname.
+ // e.g. for "images.bbc.co.uk", this would be "bbc.co.uk".
+ nsresult rv = mTLDService->GetBaseDomain(aURI, 0, aDomainName);
+ if (NS_SUCCEEDED(rv))
+ return;
+ }
+
+ // just return the original hostname
+ // (it's also possible the host is an IP address)
+ aURI->GetAsciiHost(aDomainName);
+}
+
+
+NS_IMETHODIMP
+nsNavHistory::GetHasHistoryEntries(bool* aHasEntries)
+{
+ NS_ENSURE_ARG_POINTER(aHasEntries);
+ *aHasEntries = GetDaysOfHistory() > 0;
+ return NS_OK;
+}
+
+
+namespace {
+
+class InvalidateAllFrecenciesCallback : public AsyncStatementCallback
+{
+public:
+ InvalidateAllFrecenciesCallback()
+ {
+ }
+
+ NS_IMETHOD HandleCompletion(uint16_t aReason)
+ {
+ if (aReason == REASON_FINISHED) {
+ nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(navHistory);
+ navHistory->NotifyManyFrecenciesChanged();
+ }
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+nsresult
+nsNavHistory::invalidateFrecencies(const nsCString& aPlaceIdsQueryString)
+{
+ // Exclude place: queries by setting their frecency to zero.
+ nsCString invalidFrecenciesSQLFragment(
+ "UPDATE moz_places SET frecency = "
+ );
+ if (!aPlaceIdsQueryString.IsEmpty())
+ invalidFrecenciesSQLFragment.AppendLiteral("NOTIFY_FRECENCY(");
+ invalidFrecenciesSQLFragment.AppendLiteral(
+ "(CASE "
+ "WHEN url_hash BETWEEN hash('place', 'prefix_lo') AND "
+ "hash('place', 'prefix_hi') "
+ "THEN 0 "
+ "ELSE -1 "
+ "END) "
+ );
+ if (!aPlaceIdsQueryString.IsEmpty()) {
+ invalidFrecenciesSQLFragment.AppendLiteral(
+ ", url, guid, hidden, last_visit_date) "
+ );
+ }
+ invalidFrecenciesSQLFragment.AppendLiteral(
+ "WHERE frecency > 0 "
+ );
+ if (!aPlaceIdsQueryString.IsEmpty()) {
+ invalidFrecenciesSQLFragment.AppendLiteral("AND id IN(");
+ invalidFrecenciesSQLFragment.Append(aPlaceIdsQueryString);
+ invalidFrecenciesSQLFragment.Append(')');
+ }
+ RefPtr<InvalidateAllFrecenciesCallback> cb =
+ aPlaceIdsQueryString.IsEmpty() ? new InvalidateAllFrecenciesCallback()
+ : nullptr;
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
+ invalidFrecenciesSQLFragment
+ );
+ NS_ENSURE_STATE(stmt);
+
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ nsresult rv = stmt->ExecuteAsync(cb, getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+// Call this method before visiting a URL in order to help determine the
+// transition type of the visit.
+//
+// @see MarkPageAsTyped
+
+NS_IMETHODIMP
+nsNavHistory::MarkPageAsFollowedBookmark(nsIURI* aURI)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aURI);
+
+ // don't add when history is disabled
+ if (IsHistoryDisabled())
+ return NS_OK;
+
+ nsAutoCString uriString;
+ nsresult rv = aURI->GetSpec(uriString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if URL is already in the bookmark queue, then we need to remove the old one
+ int64_t unusedEventTime;
+ if (mRecentBookmark.Get(uriString, &unusedEventTime))
+ mRecentBookmark.Remove(uriString);
+
+ if (mRecentBookmark.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
+ ExpireNonrecentEvents(&mRecentBookmark);
+
+ mRecentBookmark.Put(uriString, GetNow());
+ return NS_OK;
+}
+
+
+// nsNavHistory::CanAddURI
+//
+// Filter out unwanted URIs such as "chrome:", "mailbox:", etc.
+//
+// The model is if we don't know differently then add which basically means
+// we are suppose to try all the things we know not to allow in and then if
+// we don't bail go on and allow it in.
+
+NS_IMETHODIMP
+nsNavHistory::CanAddURI(nsIURI* aURI, bool* canAdd)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG_POINTER(canAdd);
+
+ // Default to false.
+ *canAdd = false;
+
+ // If history is disabled, don't add any entry.
+ if (IsHistoryDisabled()) {
+ return NS_OK;
+ }
+
+ // If the url length is over a threshold, don't add it.
+ nsCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mDB || spec.Length() > mDB->MaxUrlLength()) {
+ return NS_OK;
+ }
+
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // first check the most common cases (HTTP, HTTPS) to allow in to avoid most
+ // of the work
+ if (scheme.EqualsLiteral("http")) {
+ *canAdd = true;
+ return NS_OK;
+ }
+ if (scheme.EqualsLiteral("https")) {
+ *canAdd = true;
+ return NS_OK;
+ }
+
+ // now check for all bad things
+ if (scheme.EqualsLiteral("about") ||
+ scheme.EqualsLiteral("blob") ||
+ scheme.EqualsLiteral("chrome") ||
+ scheme.EqualsLiteral("data") ||
+ scheme.EqualsLiteral("imap") ||
+ scheme.EqualsLiteral("javascript") ||
+ scheme.EqualsLiteral("mailbox") ||
+ scheme.EqualsLiteral("moz-anno") ||
+ scheme.EqualsLiteral("news") ||
+ scheme.EqualsLiteral("page-icon") ||
+ scheme.EqualsLiteral("resource") ||
+ scheme.EqualsLiteral("view-source") ||
+ scheme.EqualsLiteral("wyciwyg")) {
+ return NS_OK;
+ }
+ *canAdd = true;
+ return NS_OK;
+}
+
+// nsNavHistory::GetNewQuery
+
+NS_IMETHODIMP
+nsNavHistory::GetNewQuery(nsINavHistoryQuery **_retval)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ RefPtr<nsNavHistoryQuery> query = new nsNavHistoryQuery();
+ query.forget(_retval);
+ return NS_OK;
+}
+
+// nsNavHistory::GetNewQueryOptions
+
+NS_IMETHODIMP
+nsNavHistory::GetNewQueryOptions(nsINavHistoryQueryOptions **_retval)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ RefPtr<nsNavHistoryQueryOptions> queryOptions = new nsNavHistoryQueryOptions();
+ queryOptions.forget(_retval);
+ return NS_OK;
+}
+
+// nsNavHistory::ExecuteQuery
+//
+
+NS_IMETHODIMP
+nsNavHistory::ExecuteQuery(nsINavHistoryQuery *aQuery, nsINavHistoryQueryOptions *aOptions,
+ nsINavHistoryResult** _retval)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aQuery);
+ NS_ENSURE_ARG(aOptions);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ return ExecuteQueries(&aQuery, 1, aOptions, _retval);
+}
+
+
+// nsNavHistory::ExecuteQueries
+//
+// This function is actually very simple, we just create the proper root node (either
+// a bookmark folder or a complex query node) and assign it to the result. The node
+// will then populate itself accordingly.
+//
+// Quick overview of query operation: When you call this function, we will construct
+// the correct container node and set the options you give it. This node will then
+// fill itself. Folder nodes will call nsNavBookmarks::QueryFolderChildren, and
+// all other queries will call GetQueryResults. If these results contain other
+// queries, those will be populated when the container is opened.
+
+NS_IMETHODIMP
+nsNavHistory::ExecuteQueries(nsINavHistoryQuery** aQueries, uint32_t aQueryCount,
+ nsINavHistoryQueryOptions *aOptions,
+ nsINavHistoryResult** _retval)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aQueries);
+ NS_ENSURE_ARG(aOptions);
+ NS_ENSURE_ARG(aQueryCount);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+ // concrete options
+ nsCOMPtr<nsNavHistoryQueryOptions> options = do_QueryInterface(aOptions);
+ NS_ENSURE_TRUE(options, NS_ERROR_INVALID_ARG);
+
+ // concrete queries array
+ nsCOMArray<nsNavHistoryQuery> queries;
+ for (uint32_t i = 0; i < aQueryCount; i ++) {
+ nsCOMPtr<nsNavHistoryQuery> query = do_QueryInterface(aQueries[i], &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ queries.AppendElement(query.forget());
+ }
+
+ // Create the root node.
+ RefPtr<nsNavHistoryContainerResultNode> rootNode;
+ int64_t folderId = GetSimpleBookmarksQueryFolder(queries, options);
+ if (folderId) {
+ // In the simple case where we're just querying children of a single
+ // bookmark folder, we can more efficiently generate results.
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+ RefPtr<nsNavHistoryResultNode> tempRootNode;
+ rv = bookmarks->ResultNodeForContainer(folderId, options,
+ getter_AddRefs(tempRootNode));
+ if (NS_SUCCEEDED(rv)) {
+ rootNode = tempRootNode->GetAsContainer();
+ }
+ else {
+ NS_WARNING("Generating a generic empty node for a broken query!");
+ // This is a perf hack to generate an empty query that skips filtering.
+ options->SetExcludeItems(true);
+ }
+ }
+
+ if (!rootNode) {
+ // Either this is not a folder shortcut, or is a broken one. In both cases
+ // just generate a query node.
+ rootNode = new nsNavHistoryQueryResultNode(EmptyCString(), EmptyCString(),
+ queries, options);
+ }
+
+ // Create the result that will hold nodes. Inject batching status into it.
+ RefPtr<nsNavHistoryResult> result;
+ rv = nsNavHistoryResult::NewHistoryResult(aQueries, aQueryCount, options,
+ rootNode, isBatching(),
+ getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ result.forget(_retval);
+ return NS_OK;
+}
+
+// determine from our nsNavHistoryQuery array and nsNavHistoryQueryOptions
+// if this is the place query from the history menu.
+// from browser-menubar.inc, our history menu query is:
+// place:sort=4&maxResults=10
+// note, any maxResult > 0 will still be considered a history menu query
+// or if this is the place query from the "Most Visited" item in the
+// "Smart Bookmarks" folder: place:sort=8&maxResults=10
+// note, any maxResult > 0 will still be considered a Most Visited menu query
+static
+bool IsOptimizableHistoryQuery(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions *aOptions,
+ uint16_t aSortMode)
+{
+ if (aQueries.Count() != 1)
+ return false;
+
+ nsNavHistoryQuery *aQuery = aQueries[0];
+
+ if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
+ return false;
+
+ if (aOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_URI)
+ return false;
+
+ if (aOptions->SortingMode() != aSortMode)
+ return false;
+
+ if (aOptions->MaxResults() <= 0)
+ return false;
+
+ if (aOptions->ExcludeItems())
+ return false;
+
+ if (aOptions->IncludeHidden())
+ return false;
+
+ if (aQuery->MinVisits() != -1 || aQuery->MaxVisits() != -1)
+ return false;
+
+ if (aQuery->BeginTime() || aQuery->BeginTimeReference())
+ return false;
+
+ if (aQuery->EndTime() || aQuery->EndTimeReference())
+ return false;
+
+ if (!aQuery->SearchTerms().IsEmpty())
+ return false;
+
+ if (aQuery->OnlyBookmarked())
+ return false;
+
+ if (aQuery->DomainIsHost() || !aQuery->Domain().IsEmpty())
+ return false;
+
+ if (aQuery->AnnotationIsNot() || !aQuery->Annotation().IsEmpty())
+ return false;
+
+ if (aQuery->Folders().Length() > 0)
+ return false;
+
+ if (aQuery->Tags().Length() > 0)
+ return false;
+
+ if (aQuery->Transitions().Length() > 0)
+ return false;
+
+ return true;
+}
+
+static
+bool NeedToFilterResultSet(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions *aOptions)
+{
+ uint16_t resultType = aOptions->ResultType();
+ return resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS;
+}
+
+// ** Helper class for ConstructQueryString **/
+
+class PlacesSQLQueryBuilder
+{
+public:
+ PlacesSQLQueryBuilder(const nsCString& aConditions,
+ nsNavHistoryQueryOptions* aOptions,
+ bool aUseLimit,
+ nsNavHistory::StringHash& aAddParams,
+ bool aHasSearchTerms);
+
+ nsresult GetQueryString(nsCString& aQueryString);
+
+private:
+ nsresult Select();
+
+ nsresult SelectAsURI();
+ nsresult SelectAsVisit();
+ nsresult SelectAsDay();
+ nsresult SelectAsSite();
+ nsresult SelectAsTag();
+
+ nsresult Where();
+ nsresult GroupBy();
+ nsresult OrderBy();
+ nsresult Limit();
+
+ void OrderByColumnIndexAsc(int32_t aIndex);
+ void OrderByColumnIndexDesc(int32_t aIndex);
+ // Use these if you want a case insensitive sorting.
+ void OrderByTextColumnIndexAsc(int32_t aIndex);
+ void OrderByTextColumnIndexDesc(int32_t aIndex);
+
+ const nsCString& mConditions;
+ bool mUseLimit;
+ bool mHasSearchTerms;
+
+ uint16_t mResultType;
+ uint16_t mQueryType;
+ bool mIncludeHidden;
+ uint16_t mSortingMode;
+ uint32_t mMaxResults;
+
+ nsCString mQueryString;
+ nsCString mGroupBy;
+ bool mHasDateColumns;
+ bool mSkipOrderBy;
+ nsNavHistory::StringHash& mAddParams;
+};
+
+PlacesSQLQueryBuilder::PlacesSQLQueryBuilder(
+ const nsCString& aConditions,
+ nsNavHistoryQueryOptions* aOptions,
+ bool aUseLimit,
+ nsNavHistory::StringHash& aAddParams,
+ bool aHasSearchTerms)
+: mConditions(aConditions)
+, mUseLimit(aUseLimit)
+, mHasSearchTerms(aHasSearchTerms)
+, mResultType(aOptions->ResultType())
+, mQueryType(aOptions->QueryType())
+, mIncludeHidden(aOptions->IncludeHidden())
+, mSortingMode(aOptions->SortingMode())
+, mMaxResults(aOptions->MaxResults())
+, mSkipOrderBy(false)
+, mAddParams(aAddParams)
+{
+ mHasDateColumns = (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
+}
+
+nsresult
+PlacesSQLQueryBuilder::GetQueryString(nsCString& aQueryString)
+{
+ nsresult rv = Select();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = Where();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GroupBy();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = OrderBy();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = Limit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aQueryString = mQueryString;
+ return NS_OK;
+}
+
+nsresult
+PlacesSQLQueryBuilder::Select()
+{
+ nsresult rv;
+
+ switch (mResultType)
+ {
+ case nsINavHistoryQueryOptions::RESULTS_AS_URI:
+ case nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS:
+ rv = SelectAsURI();
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+
+ case nsINavHistoryQueryOptions::RESULTS_AS_VISIT:
+ case nsINavHistoryQueryOptions::RESULTS_AS_FULL_VISIT:
+ rv = SelectAsVisit();
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+
+ case nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY:
+ case nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY:
+ rv = SelectAsDay();
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+
+ case nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY:
+ rv = SelectAsSite();
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+
+ case nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY:
+ rv = SelectAsTag();
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+
+ default:
+ NS_NOTREACHED("Invalid result type");
+ }
+ return NS_OK;
+}
+
+nsresult
+PlacesSQLQueryBuilder::SelectAsURI()
+{
+ nsNavHistory *history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ nsAutoCString tagsSqlFragment;
+
+ switch (mQueryType) {
+ case nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY:
+ GetTagsSqlFragment(history->GetTagsFolder(),
+ NS_LITERAL_CSTRING("h.id"),
+ mHasSearchTerms,
+ tagsSqlFragment);
+
+ mQueryString = NS_LITERAL_CSTRING(
+ "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, "
+ "h.last_visit_date, f.url, null, null, null, null, ") +
+ tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+ "null, null, null "
+ "FROM moz_places h "
+ "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+ // WHERE 1 is a no-op since additonal conditions will start with AND.
+ "WHERE 1 "
+ "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+ "{ADDITIONAL_CONDITIONS} ");
+ break;
+
+ case nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS:
+ if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
+ // Order-by clause is hardcoded because we need to discard duplicates
+ // in FilterResultSet. We will retain only the last modified item,
+ // so we are ordering by place id and last modified to do a faster
+ // filtering.
+ mSkipOrderBy = true;
+
+ GetTagsSqlFragment(history->GetTagsFolder(),
+ NS_LITERAL_CSTRING("b2.fk"),
+ mHasSearchTerms,
+ tagsSqlFragment);
+
+ mQueryString = NS_LITERAL_CSTRING(
+ "SELECT b2.fk, h.url, COALESCE(b2.title, h.title) AS page_title, "
+ "h.rev_host, h.visit_count, h.last_visit_date, f.url, b2.id, "
+ "b2.dateAdded, b2.lastModified, b2.parent, ") +
+ tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+ "null, null, null, b2.guid, b2.position, b2.type, b2.fk "
+ "FROM moz_bookmarks b2 "
+ "JOIN (SELECT b.fk "
+ "FROM moz_bookmarks b "
+ // ADDITIONAL_CONDITIONS will filter on parent.
+ "WHERE b.type = 1 {ADDITIONAL_CONDITIONS} "
+ ") AS seed ON b2.fk = seed.fk "
+ "JOIN moz_places h ON h.id = b2.fk "
+ "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE NOT EXISTS ( "
+ "SELECT id FROM moz_bookmarks WHERE id = b2.parent AND parent = ") +
+ nsPrintfCString("%lld", history->GetTagsFolder()) +
+ NS_LITERAL_CSTRING(") "
+ "ORDER BY b2.fk DESC, b2.lastModified DESC");
+ }
+ else {
+ GetTagsSqlFragment(history->GetTagsFolder(),
+ NS_LITERAL_CSTRING("b.fk"),
+ mHasSearchTerms,
+ tagsSqlFragment);
+ mQueryString = NS_LITERAL_CSTRING(
+ "SELECT b.fk, h.url, COALESCE(b.title, h.title) AS page_title, "
+ "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, "
+ "b.dateAdded, b.lastModified, b.parent, ") +
+ tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid,"
+ "null, null, null, b.guid, b.position, b.type, b.fk "
+ "FROM moz_bookmarks b "
+ "JOIN moz_places h ON b.fk = h.id "
+ "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE NOT EXISTS "
+ "(SELECT id FROM moz_bookmarks "
+ "WHERE id = b.parent AND parent = ") +
+ nsPrintfCString("%lld", history->GetTagsFolder()) +
+ NS_LITERAL_CSTRING(") "
+ "{ADDITIONAL_CONDITIONS}");
+ }
+ break;
+
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ return NS_OK;
+}
+
+nsresult
+PlacesSQLQueryBuilder::SelectAsVisit()
+{
+ nsNavHistory *history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ nsAutoCString tagsSqlFragment;
+ GetTagsSqlFragment(history->GetTagsFolder(),
+ NS_LITERAL_CSTRING("h.id"),
+ mHasSearchTerms,
+ tagsSqlFragment);
+ mQueryString = NS_LITERAL_CSTRING(
+ "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, "
+ "v.visit_date, f.url, null, null, null, null, ") +
+ tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+ "v.id, v.from_visit, v.visit_type "
+ "FROM moz_places h "
+ "JOIN moz_historyvisits v ON h.id = v.place_id "
+ "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+ // WHERE 1 is a no-op since additonal conditions will start with AND.
+ "WHERE 1 "
+ "{QUERY_OPTIONS_VISITS} {QUERY_OPTIONS_PLACES} "
+ "{ADDITIONAL_CONDITIONS} ");
+
+ return NS_OK;
+}
+
+nsresult
+PlacesSQLQueryBuilder::SelectAsDay()
+{
+ mSkipOrderBy = true;
+
+ // Sort child queries based on sorting mode if it's provided, otherwise
+ // fallback to default sort by title ascending.
+ uint16_t sortingMode = nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING;
+ if (mSortingMode != nsINavHistoryQueryOptions::SORT_BY_NONE &&
+ mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY)
+ sortingMode = mSortingMode;
+
+ uint16_t resultType =
+ mResultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ?
+ (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_URI :
+ (uint16_t)nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
+
+ // beginTime will become the node's time property, we don't use endTime
+ // because it could overlap, and we use time to sort containers and find
+ // insert position in a result.
+ mQueryString = nsPrintfCString(
+ "SELECT null, "
+ "'place:type=%ld&sort=%ld&beginTime='||beginTime||'&endTime='||endTime, "
+ "dayTitle, null, null, beginTime, null, null, null, null, null, null, "
+ "null, null, null "
+ "FROM (", // TOUTER BEGIN
+ resultType,
+ sortingMode);
+
+ nsNavHistory *history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(history);
+
+ int32_t daysOfHistory = history->GetDaysOfHistory();
+ for (int32_t i = 0; i <= HISTORY_DATE_CONT_NUM(daysOfHistory); i++) {
+ nsAutoCString dateName;
+ // Timeframes are calculated as BeginTime <= container < EndTime.
+ // Notice times can't be relative to now, since to recognize a query we
+ // must ensure it won't change based on the time it is built.
+ // So, to select till now, we really select till start of tomorrow, that is
+ // a fixed timestamp.
+ // These are used as limits for the inside containers.
+ nsAutoCString sqlFragmentContainerBeginTime, sqlFragmentContainerEndTime;
+ // These are used to query if the container should be visible.
+ nsAutoCString sqlFragmentSearchBeginTime, sqlFragmentSearchEndTime;
+ switch(i) {
+ case 0:
+ // Today
+ history->GetStringFromName(
+ u"finduri-AgeInDays-is-0", dateName);
+ // From start of today
+ sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of day','utc')*1000000)");
+ // To now (tomorrow)
+ sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)");
+ // Search for the same timeframe.
+ sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
+ sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
+ break;
+ case 1:
+ // Yesterday
+ history->GetStringFromName(
+ u"finduri-AgeInDays-is-1", dateName);
+ // From start of yesterday
+ sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of day','-1 day','utc')*1000000)");
+ // To start of today
+ sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of day','utc')*1000000)");
+ // Search for the same timeframe.
+ sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
+ sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
+ break;
+ case 2:
+ // Last 7 days
+ history->GetAgeInDaysString(7,
+ u"finduri-AgeInDays-last-is", dateName);
+ // From start of 7 days ago
+ sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of day','-7 days','utc')*1000000)");
+ // To now (tomorrow)
+ sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)");
+ // This is an overlapped container, but we show it only if there are
+ // visits older than yesterday.
+ sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
+ sqlFragmentSearchEndTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of day','-2 days','utc')*1000000)");
+ break;
+ case 3:
+ // This month
+ history->GetStringFromName(
+ u"finduri-AgeInMonths-is-0", dateName);
+ // From start of this month
+ sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of month','utc')*1000000)");
+ // To now (tomorrow)
+ sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of day','+1 day','utc')*1000000)");
+ // This is an overlapped container, but we show it only if there are
+ // visits older than 7 days ago.
+ sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
+ sqlFragmentSearchEndTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of day','-7 days','utc')*1000000)");
+ break;
+ default:
+ if (i == HISTORY_ADDITIONAL_DATE_CONT_NUM + 6) {
+ // Older than 6 months
+ history->GetAgeInDaysString(6,
+ u"finduri-AgeInMonths-isgreater", dateName);
+ // From start of epoch
+ sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
+ "(datetime(0, 'unixepoch')*1000000)");
+ // To start of 6 months ago ( 5 months + this month).
+ sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of month','-5 months','utc')*1000000)");
+ // Search for the same timeframe.
+ sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
+ sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
+ break;
+ }
+ int32_t MonthIndex = i - HISTORY_ADDITIONAL_DATE_CONT_NUM;
+ // Previous months' titles are month's name if inside this year,
+ // month's name and year for previous years.
+ PRExplodedTime tm;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &tm);
+ uint16_t currentYear = tm.tm_year;
+ // Set day before month, setting month without day could cause issues.
+ // For example setting month to February when today is 30, since
+ // February has not 30 days, will return March instead.
+ // Also, we use day 2 instead of day 1, so that the GMT month is always
+ // the same as the local month. (Bug 603002)
+ tm.tm_mday = 2;
+ tm.tm_month -= MonthIndex;
+ // Notice we use GMTParameters because we just want to get the first
+ // day of each month. Using LocalTimeParameters would instead force us
+ // to apply a DST correction that we don't really need here.
+ PR_NormalizeTime(&tm, PR_GMTParameters);
+ // If the container is for a past year, add the year to its title,
+ // otherwise just show the month name.
+ // Note that tm_month starts from 0, while we need a 1-based index.
+ if (tm.tm_year < currentYear) {
+ history->GetMonthYear(tm.tm_month + 1, tm.tm_year, dateName);
+ }
+ else {
+ history->GetMonthName(tm.tm_month + 1, dateName);
+ }
+
+ // From start of MonthIndex + 1 months ago
+ sqlFragmentContainerBeginTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of month','-");
+ sqlFragmentContainerBeginTime.AppendInt(MonthIndex);
+ sqlFragmentContainerBeginTime.Append(NS_LITERAL_CSTRING(
+ " months','utc')*1000000)"));
+ // To start of MonthIndex months ago
+ sqlFragmentContainerEndTime = NS_LITERAL_CSTRING(
+ "(strftime('%s','now','localtime','start of month','-");
+ sqlFragmentContainerEndTime.AppendInt(MonthIndex - 1);
+ sqlFragmentContainerEndTime.Append(NS_LITERAL_CSTRING(
+ " months','utc')*1000000)"));
+ // Search for the same timeframe.
+ sqlFragmentSearchBeginTime = sqlFragmentContainerBeginTime;
+ sqlFragmentSearchEndTime = sqlFragmentContainerEndTime;
+ break;
+ }
+
+ nsPrintfCString dateParam("dayTitle%d", i);
+ mAddParams.Put(dateParam, dateName);
+
+ nsPrintfCString dayRange(
+ "SELECT :%s AS dayTitle, "
+ "%s AS beginTime, "
+ "%s AS endTime "
+ "WHERE EXISTS ( "
+ "SELECT id FROM moz_historyvisits "
+ "WHERE visit_date >= %s "
+ "AND visit_date < %s "
+ "AND visit_type NOT IN (0,%d,%d) "
+ "{QUERY_OPTIONS_VISITS} "
+ "LIMIT 1 "
+ ") ",
+ dateParam.get(),
+ sqlFragmentContainerBeginTime.get(),
+ sqlFragmentContainerEndTime.get(),
+ sqlFragmentSearchBeginTime.get(),
+ sqlFragmentSearchEndTime.get(),
+ nsINavHistoryService::TRANSITION_EMBED,
+ nsINavHistoryService::TRANSITION_FRAMED_LINK
+ );
+
+ mQueryString.Append(dayRange);
+
+ if (i < HISTORY_DATE_CONT_NUM(daysOfHistory))
+ mQueryString.AppendLiteral(" UNION ALL ");
+ }
+
+ mQueryString.AppendLiteral(") "); // TOUTER END
+
+ return NS_OK;
+}
+
+nsresult
+PlacesSQLQueryBuilder::SelectAsSite()
+{
+ nsAutoCString localFiles;
+
+ nsNavHistory *history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(history);
+
+ history->GetStringFromName(u"localhost", localFiles);
+ mAddParams.Put(NS_LITERAL_CSTRING("localhost"), localFiles);
+
+ // If there are additional conditions the query has to join on visits too.
+ nsAutoCString visitsJoin;
+ nsAutoCString additionalConditions;
+ nsAutoCString timeConstraints;
+ if (!mConditions.IsEmpty()) {
+ visitsJoin.AssignLiteral("JOIN moz_historyvisits v ON v.place_id = h.id ");
+ additionalConditions.AssignLiteral("{QUERY_OPTIONS_VISITS} "
+ "{QUERY_OPTIONS_PLACES} "
+ "{ADDITIONAL_CONDITIONS} ");
+ timeConstraints.AssignLiteral("||'&beginTime='||:begin_time||"
+ "'&endTime='||:end_time");
+ }
+
+ mQueryString = nsPrintfCString(
+ "SELECT null, 'place:type=%ld&sort=%ld&domain=&domainIsHost=true'%s, "
+ ":localhost, :localhost, null, null, null, null, null, null, null, "
+ "null, null, null "
+ "WHERE EXISTS ( "
+ "SELECT h.id FROM moz_places h "
+ "%s "
+ "WHERE h.hidden = 0 "
+ "AND h.visit_count > 0 "
+ "AND h.url_hash BETWEEN hash('file', 'prefix_lo') AND "
+ "hash('file', 'prefix_hi') "
+ "%s "
+ "LIMIT 1 "
+ ") "
+ "UNION ALL "
+ "SELECT null, "
+ "'place:type=%ld&sort=%ld&domain='||host||'&domainIsHost=true'%s, "
+ "host, host, null, null, null, null, null, null, null, "
+ "null, null, null "
+ "FROM ( "
+ "SELECT get_unreversed_host(h.rev_host) AS host "
+ "FROM moz_places h "
+ "%s "
+ "WHERE h.hidden = 0 "
+ "AND h.rev_host <> '.' "
+ "AND h.visit_count > 0 "
+ "%s "
+ "GROUP BY h.rev_host "
+ "ORDER BY host ASC "
+ ") ",
+ nsINavHistoryQueryOptions::RESULTS_AS_URI,
+ mSortingMode,
+ timeConstraints.get(),
+ visitsJoin.get(),
+ additionalConditions.get(),
+ nsINavHistoryQueryOptions::RESULTS_AS_URI,
+ mSortingMode,
+ timeConstraints.get(),
+ visitsJoin.get(),
+ additionalConditions.get()
+ );
+
+ return NS_OK;
+}
+
+nsresult
+PlacesSQLQueryBuilder::SelectAsTag()
+{
+ nsNavHistory *history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(history);
+
+ // This allows sorting by date fields what is not possible with
+ // other history queries.
+ mHasDateColumns = true;
+
+ mQueryString = nsPrintfCString(
+ "SELECT null, 'place:folder=' || id || '&queryType=%d&type=%ld', "
+ "title, null, null, null, null, null, dateAdded, "
+ "lastModified, null, null, null, null, null, null "
+ "FROM moz_bookmarks "
+ "WHERE parent = %lld",
+ nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS,
+ nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS,
+ history->GetTagsFolder()
+ );
+
+ return NS_OK;
+}
+
+nsresult
+PlacesSQLQueryBuilder::Where()
+{
+
+ // Set query options
+ nsAutoCString additionalVisitsConditions;
+ nsAutoCString additionalPlacesConditions;
+
+ if (!mIncludeHidden) {
+ additionalPlacesConditions += NS_LITERAL_CSTRING("AND hidden = 0 ");
+ }
+
+ if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
+ // last_visit_date is updated for any kind of visit, so it's a good
+ // indicator whether the page has visits.
+ additionalPlacesConditions += NS_LITERAL_CSTRING(
+ "AND last_visit_date NOTNULL "
+ );
+ }
+
+ if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI &&
+ !additionalVisitsConditions.IsEmpty()) {
+ // URI results don't join on visits.
+ nsAutoCString tmp = additionalVisitsConditions;
+ additionalVisitsConditions = "AND EXISTS (SELECT 1 FROM moz_historyvisits WHERE place_id = h.id ";
+ additionalVisitsConditions.Append(tmp);
+ additionalVisitsConditions.AppendLiteral("LIMIT 1)");
+ }
+
+ mQueryString.ReplaceSubstring("{QUERY_OPTIONS_VISITS}",
+ additionalVisitsConditions.get());
+ mQueryString.ReplaceSubstring("{QUERY_OPTIONS_PLACES}",
+ additionalPlacesConditions.get());
+
+ // If we used WHERE already, we inject the conditions
+ // in place of {ADDITIONAL_CONDITIONS}
+ if (mQueryString.Find("{ADDITIONAL_CONDITIONS}", 0) != kNotFound) {
+ nsAutoCString innerCondition;
+ // If we have condition AND it
+ if (!mConditions.IsEmpty()) {
+ innerCondition = " AND (";
+ innerCondition += mConditions;
+ innerCondition += ")";
+ }
+ mQueryString.ReplaceSubstring("{ADDITIONAL_CONDITIONS}",
+ innerCondition.get());
+
+ } else if (!mConditions.IsEmpty()) {
+
+ mQueryString += "WHERE ";
+ mQueryString += mConditions;
+
+ }
+ return NS_OK;
+}
+
+nsresult
+PlacesSQLQueryBuilder::GroupBy()
+{
+ mQueryString += mGroupBy;
+ return NS_OK;
+}
+
+nsresult
+PlacesSQLQueryBuilder::OrderBy()
+{
+ if (mSkipOrderBy)
+ return NS_OK;
+
+ // Sort clause: we will sort later, but if it comes out of the DB sorted,
+ // our later sort will be basically free. The DB can sort these for free
+ // most of the time anyway, because it has indices over these items.
+ switch(mSortingMode)
+ {
+ case nsINavHistoryQueryOptions::SORT_BY_NONE:
+ // Ensure sorting does not change based on tables status.
+ if (mResultType == nsINavHistoryQueryOptions::RESULTS_AS_URI) {
+ if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS)
+ mQueryString += NS_LITERAL_CSTRING(" ORDER BY b.id ASC ");
+ else if (mQueryType == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
+ mQueryString += NS_LITERAL_CSTRING(" ORDER BY h.id ASC ");
+ }
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
+ case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
+ // If the user wants few results, we limit them by date, necessitating
+ // a sort by date here (see the IDL definition for maxResults).
+ // Otherwise we will do actual sorting by title, but since we could need
+ // to special sort for some locale we will repeat a second sorting at the
+ // end in nsNavHistoryResult, that should be faster since the list will be
+ // almost ordered.
+ if (mMaxResults > 0)
+ OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate);
+ else if (mSortingMode == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING)
+ OrderByTextColumnIndexAsc(nsNavHistory::kGetInfoIndex_Title);
+ else
+ OrderByTextColumnIndexDesc(nsNavHistory::kGetInfoIndex_Title);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
+ OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitDate);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
+ OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitDate);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
+ OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_URL);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING:
+ OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_URL);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
+ OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_VisitCount);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
+ OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_VisitCount);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
+ if (mHasDateColumns)
+ OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemDateAdded);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
+ if (mHasDateColumns)
+ OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemDateAdded);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
+ if (mHasDateColumns)
+ OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_ItemLastModified);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
+ if (mHasDateColumns)
+ OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_ItemLastModified);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING:
+ case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING:
+ case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_ASCENDING:
+ case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_DESCENDING:
+ break; // Sort later in nsNavHistoryQueryResultNode::FillChildren()
+ case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING:
+ OrderByColumnIndexAsc(nsNavHistory::kGetInfoIndex_Frecency);
+ break;
+ case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING:
+ OrderByColumnIndexDesc(nsNavHistory::kGetInfoIndex_Frecency);
+ break;
+ default:
+ NS_NOTREACHED("Invalid sorting mode");
+ }
+ return NS_OK;
+}
+
+void PlacesSQLQueryBuilder::OrderByColumnIndexAsc(int32_t aIndex)
+{
+ mQueryString += nsPrintfCString(" ORDER BY %d ASC", aIndex+1);
+}
+
+void PlacesSQLQueryBuilder::OrderByColumnIndexDesc(int32_t aIndex)
+{
+ mQueryString += nsPrintfCString(" ORDER BY %d DESC", aIndex+1);
+}
+
+void PlacesSQLQueryBuilder::OrderByTextColumnIndexAsc(int32_t aIndex)
+{
+ mQueryString += nsPrintfCString(" ORDER BY %d COLLATE NOCASE ASC",
+ aIndex+1);
+}
+
+void PlacesSQLQueryBuilder::OrderByTextColumnIndexDesc(int32_t aIndex)
+{
+ mQueryString += nsPrintfCString(" ORDER BY %d COLLATE NOCASE DESC",
+ aIndex+1);
+}
+
+nsresult
+PlacesSQLQueryBuilder::Limit()
+{
+ if (mUseLimit && mMaxResults > 0) {
+ mQueryString += NS_LITERAL_CSTRING(" LIMIT ");
+ mQueryString.AppendInt(mMaxResults);
+ mQueryString.Append(' ');
+ }
+ return NS_OK;
+}
+
+nsresult
+nsNavHistory::ConstructQueryString(
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions,
+ nsCString& queryString,
+ bool& aParamsPresent,
+ nsNavHistory::StringHash& aAddParams)
+{
+ // For information about visit_type see nsINavHistoryService.idl.
+ // visitType == 0 is undefined (see bug #375777 for details).
+ // Some sites, especially Javascript-heavy ones, load things in frames to
+ // display them, resulting in a lot of these entries. This is the reason
+ // why such visits are filtered out.
+ nsresult rv;
+ aParamsPresent = false;
+
+ int32_t sortingMode = aOptions->SortingMode();
+ NS_ASSERTION(sortingMode >= nsINavHistoryQueryOptions::SORT_BY_NONE &&
+ sortingMode <= nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING,
+ "Invalid sortingMode found while building query!");
+
+ bool hasSearchTerms = false;
+ for (int32_t i = 0; i < aQueries.Count() && !hasSearchTerms; i++) {
+ aQueries[i]->GetHasSearchTerms(&hasSearchTerms);
+ }
+
+ nsAutoCString tagsSqlFragment;
+ GetTagsSqlFragment(GetTagsFolder(),
+ NS_LITERAL_CSTRING("h.id"),
+ hasSearchTerms,
+ tagsSqlFragment);
+
+ if (IsOptimizableHistoryQuery(aQueries, aOptions,
+ nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING) ||
+ IsOptimizableHistoryQuery(aQueries, aOptions,
+ nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING)) {
+ // Generate an optimized query for the history menu and most visited
+ // smart bookmark.
+ queryString = NS_LITERAL_CSTRING(
+ "SELECT h.id, h.url, h.title AS page_title, h.rev_host, h.visit_count, h.last_visit_date, "
+ "f.url, null, null, null, null, ") +
+ tagsSqlFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+ "null, null, null "
+ "FROM moz_places h "
+ "LEFT OUTER JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE h.hidden = 0 "
+ "AND EXISTS (SELECT id FROM moz_historyvisits WHERE place_id = h.id "
+ "AND visit_type NOT IN ") +
+ nsPrintfCString("(0,%d,%d) ",
+ nsINavHistoryService::TRANSITION_EMBED,
+ nsINavHistoryService::TRANSITION_FRAMED_LINK) +
+ NS_LITERAL_CSTRING("LIMIT 1) "
+ "{QUERY_OPTIONS} "
+ );
+
+ queryString.AppendLiteral("ORDER BY ");
+ if (sortingMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING)
+ queryString.AppendLiteral("last_visit_date DESC ");
+ else
+ queryString.AppendLiteral("visit_count DESC ");
+
+ queryString.AppendLiteral("LIMIT ");
+ queryString.AppendInt(aOptions->MaxResults());
+
+ nsAutoCString additionalQueryOptions;
+
+ queryString.ReplaceSubstring("{QUERY_OPTIONS}",
+ additionalQueryOptions.get());
+ return NS_OK;
+ }
+
+ nsAutoCString conditions;
+ for (int32_t i = 0; i < aQueries.Count(); i++) {
+ nsCString queryClause;
+ rv = QueryToSelectClause(aQueries[i], aOptions, i, &queryClause);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (! queryClause.IsEmpty()) {
+ aParamsPresent = true;
+ if (! conditions.IsEmpty()) // exists previous clause: multiple ones are ORed
+ conditions += NS_LITERAL_CSTRING(" OR ");
+ conditions += NS_LITERAL_CSTRING("(") + queryClause +
+ NS_LITERAL_CSTRING(")");
+ }
+ }
+
+ // Determine whether we can push maxResults constraints into the queries
+ // as LIMIT, or if we need to do result count clamping later
+ // using FilterResultSet()
+ bool useLimitClause = !NeedToFilterResultSet(aQueries, aOptions);
+
+ PlacesSQLQueryBuilder queryStringBuilder(conditions, aOptions,
+ useLimitClause, aAddParams,
+ hasSearchTerms);
+ rv = queryStringBuilder.GetQueryString(queryString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// nsNavHistory::GetQueryResults
+//
+// Call this to get the results from a complex query. This is used by
+// nsNavHistoryQueryResultNode to populate its children. For simple bookmark
+// queries, use nsNavBookmarks::QueryFolderChildren.
+//
+// THIS DOES NOT DO SORTING. You will need to sort the container yourself
+// when you get the results. This is because sorting depends on tree
+// statistics that will be built from the perspective of the tree. See
+// nsNavHistoryQueryResultNode::FillChildren
+//
+// FIXME: This only does keyword searching for the first query, and does
+// it ANDed with the all the rest of the queries.
+
+nsresult
+nsNavHistory::GetQueryResults(nsNavHistoryQueryResultNode *aResultNode,
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions *aOptions,
+ nsCOMArray<nsNavHistoryResultNode>* aResults)
+{
+ NS_ENSURE_ARG_POINTER(aOptions);
+ NS_ASSERTION(aResults->Count() == 0, "Initial result array must be empty");
+ if (! aQueries.Count())
+ return NS_ERROR_INVALID_ARG;
+
+ nsCString queryString;
+ bool paramsPresent = false;
+ nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_LENGTH);
+ nsresult rv = ConstructQueryString(aQueries, aOptions, queryString,
+ paramsPresent, addParams);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // create statement
+ nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(queryString);
+#ifdef DEBUG
+ if (!statement) {
+ nsAutoCString lastErrorString;
+ (void)mDB->MainConn()->GetLastErrorString(lastErrorString);
+ int32_t lastError = 0;
+ (void)mDB->MainConn()->GetLastError(&lastError);
+ printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n",
+ queryString.get(), lastError, lastErrorString.get());
+ }
+#endif
+ NS_ENSURE_STATE(statement);
+ mozStorageStatementScoper scoper(statement);
+
+ if (paramsPresent) {
+ // bind parameters
+ int32_t i;
+ for (i = 0; i < aQueries.Count(); i++) {
+ rv = BindQueryClauseParameters(statement, i, aQueries[i], aOptions);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ for (auto iter = addParams.Iter(); !iter.Done(); iter.Next()) {
+ nsresult rv = statement->BindUTF8StringByName(iter.Key(), iter.Data());
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+
+ // Optimize the case where there is no need for any post-query filtering.
+ if (NeedToFilterResultSet(aQueries, aOptions)) {
+ // Generate the top-level results.
+ nsCOMArray<nsNavHistoryResultNode> toplevel;
+ rv = ResultsAsList(statement, aOptions, &toplevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ FilterResultSet(aResultNode, toplevel, aResults, aQueries, aOptions);
+ } else {
+ rv = ResultsAsList(statement, aOptions, aResults);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistory::AddObserver(nsINavHistoryObserver* aObserver, bool aOwnsWeak)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aObserver);
+
+ if (NS_WARN_IF(!mCanNotify))
+ return NS_ERROR_UNEXPECTED;
+
+ return mObservers.AppendWeakElement(aObserver, aOwnsWeak);
+}
+
+NS_IMETHODIMP
+nsNavHistory::RemoveObserver(nsINavHistoryObserver* aObserver)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aObserver);
+
+ return mObservers.RemoveWeakElement(aObserver);
+}
+
+NS_IMETHODIMP
+nsNavHistory::GetObservers(uint32_t* _count,
+ nsINavHistoryObserver*** _observers)
+{
+ NS_ENSURE_ARG_POINTER(_count);
+ NS_ENSURE_ARG_POINTER(_observers);
+
+ *_count = 0;
+ *_observers = nullptr;
+
+ // Clear any cached value, cause it's very likely the consumer has made
+ // changes to history and is now trying to notify them.
+ mDaysOfHistory = -1;
+
+ if (!mCanNotify)
+ return NS_OK;
+
+ nsCOMArray<nsINavHistoryObserver> observers;
+
+ // First add the category cache observers.
+ mCacheObservers.GetEntries(observers);
+
+ // Then add the other observers.
+ for (uint32_t i = 0; i < mObservers.Length(); ++i) {
+ const nsCOMPtr<nsINavHistoryObserver> &observer = mObservers.ElementAt(i).GetValue();
+ // Skip nullified weak observers.
+ if (observer)
+ observers.AppendElement(observer);
+ }
+
+ if (observers.Count() == 0)
+ return NS_OK;
+
+ *_count = observers.Count();
+ observers.Forget(_observers);
+
+ return NS_OK;
+}
+
+// See RunInBatchMode
+nsresult
+nsNavHistory::BeginUpdateBatch()
+{
+ if (mBatchLevel++ == 0) {
+ mBatchDBTransaction = new mozStorageTransaction(mDB->MainConn(), false,
+ mozIStorageConnection::TRANSACTION_DEFERRED,
+ true);
+
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver, OnBeginUpdateBatch());
+ }
+ return NS_OK;
+}
+
+// nsNavHistory::EndUpdateBatch
+nsresult
+nsNavHistory::EndUpdateBatch()
+{
+ if (--mBatchLevel == 0) {
+ if (mBatchDBTransaction) {
+ DebugOnly<nsresult> rv = mBatchDBTransaction->Commit();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Batch failed to commit transaction");
+ delete mBatchDBTransaction;
+ mBatchDBTransaction = nullptr;
+ }
+
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver, OnEndUpdateBatch());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistory::RunInBatchMode(nsINavHistoryBatchCallback* aCallback,
+ nsISupports* aUserData)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aCallback);
+
+ UpdateBatchScoper batch(*this);
+ return aCallback->RunBatched(aUserData);
+}
+
+NS_IMETHODIMP
+nsNavHistory::GetHistoryDisabled(bool *_retval)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = IsHistoryDisabled();
+ return NS_OK;
+}
+
+// Browser history *************************************************************
+
+
+// nsNavHistory::RemovePagesInternal
+//
+// Deletes a list of placeIds from history.
+// This is an internal method used by RemovePages, RemovePagesFromHost and
+// RemovePagesByTimeframe.
+// Takes a comma separated list of place ids.
+// This method does not do any observer notification.
+
+nsresult
+nsNavHistory::RemovePagesInternal(const nsCString& aPlaceIdsQueryString)
+{
+ // Return early if there is nothing to delete.
+ if (aPlaceIdsQueryString.IsEmpty())
+ return NS_OK;
+
+ mozStorageTransaction transaction(mDB->MainConn(), false,
+ mozIStorageConnection::TRANSACTION_DEFERRED,
+ true);
+
+ // Delete all visits for the specified place ids.
+ nsresult rv = mDB->MainConn()->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING(
+ "DELETE FROM moz_historyvisits WHERE place_id IN (") +
+ aPlaceIdsQueryString +
+ NS_LITERAL_CSTRING(")")
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CleanupPlacesOnVisitsDelete(aPlaceIdsQueryString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Invalidate the cached value for whether there's history or not.
+ mDaysOfHistory = -1;
+
+ return transaction.Commit();
+}
+
+
+/**
+ * Performs cleanup on places that just had all their visits removed, including
+ * deletion of those places. This is an internal method used by
+ * RemovePagesInternal. This method does not execute in a transaction, so
+ * callers should make sure they begin one if needed.
+ *
+ * @param aPlaceIdsQueryString
+ * A comma-separated list of place IDs, each of which just had all its
+ * visits removed
+ */
+nsresult
+nsNavHistory::CleanupPlacesOnVisitsDelete(const nsCString& aPlaceIdsQueryString)
+{
+ // Return early if there is nothing to delete.
+ if (aPlaceIdsQueryString.IsEmpty())
+ return NS_OK;
+
+ // Collect about-to-be-deleted URIs to notify onDeleteURI.
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
+ "SELECT h.id, h.url, h.guid, "
+ "(SUBSTR(h.url, 1, 6) <> 'place:' "
+ " AND NOT EXISTS (SELECT b.id FROM moz_bookmarks b "
+ "WHERE b.fk = h.id LIMIT 1)) as whole_entry "
+ "FROM moz_places h "
+ "WHERE h.id IN ( ") + aPlaceIdsQueryString + NS_LITERAL_CSTRING(")")
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsCString filteredPlaceIds;
+ nsCOMArray<nsIURI> URIs;
+ nsTArray<nsCString> GUIDs;
+ bool hasMore;
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
+ int64_t placeId;
+ nsresult rv = stmt->GetInt64(0, &placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString URLString;
+ rv = stmt->GetUTF8String(1, URLString);
+ nsCString guid;
+ rv = stmt->GetUTF8String(2, guid);
+ int32_t wholeEntry;
+ rv = stmt->GetInt32(3, &wholeEntry);
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), URLString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (wholeEntry) {
+ if (!filteredPlaceIds.IsEmpty()) {
+ filteredPlaceIds.Append(',');
+ }
+ filteredPlaceIds.AppendInt(placeId);
+ URIs.AppendElement(uri.forget());
+ GUIDs.AppendElement(guid);
+ }
+ else {
+ // Notify that we will delete all visits for this page, but not the page
+ // itself, since it's bookmarked or a place: query.
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver,
+ OnDeleteVisits(uri, 0, guid, nsINavHistoryObserver::REASON_DELETED, 0));
+ }
+ }
+
+ // if the entry is not bookmarked and is not a place: uri
+ // then we can remove it from moz_places.
+ // Note that we do NOT delete favicons. Any unreferenced favicons will be
+ // deleted next time the browser is shut down.
+ nsresult rv = mDB->MainConn()->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING(
+ "DELETE FROM moz_places WHERE id IN ( "
+ ) + filteredPlaceIds + NS_LITERAL_CSTRING(
+ ") "
+ )
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Hosts accumulated during the places delete are updated through a trigger
+ // (see nsPlacesTriggers.h).
+ rv = mDB->MainConn()->ExecuteSimpleSQL(
+ NS_LITERAL_CSTRING("DELETE FROM moz_updatehosts_temp")
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Invalidate frecencies of touched places, since they need recalculation.
+ rv = invalidateFrecencies(aPlaceIdsQueryString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Finally notify about the removed URIs.
+ for (int32_t i = 0; i < URIs.Count(); ++i) {
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver,
+ OnDeleteURI(URIs[i], GUIDs[i], nsINavHistoryObserver::REASON_DELETED));
+ }
+
+ return NS_OK;
+}
+
+
+// nsNavHistory::RemovePages
+//
+// Removes a bunch of uris from history.
+// Has better performance than RemovePage when deleting a lot of history.
+// We don't do duplicates removal, URIs array should be cleaned-up before.
+
+NS_IMETHODIMP
+nsNavHistory::RemovePages(nsIURI **aURIs, uint32_t aLength)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aURIs);
+
+ nsresult rv;
+ // build a list of place ids to delete
+ nsCString deletePlaceIdsQueryString;
+ for (uint32_t i = 0; i < aLength; i++) {
+ int64_t placeId;
+ nsAutoCString guid;
+ if (!aURIs[i])
+ continue;
+ rv = GetIdForPage(aURIs[i], &placeId, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (placeId != 0) {
+ if (!deletePlaceIdsQueryString.IsEmpty())
+ deletePlaceIdsQueryString.Append(',');
+ deletePlaceIdsQueryString.AppendInt(placeId);
+ }
+ }
+
+ UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers
+
+ rv = RemovePagesInternal(deletePlaceIdsQueryString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Clear the registered embed visits.
+ clearEmbedVisits();
+
+ return NS_OK;
+}
+
+
+// nsNavHistory::RemovePage
+//
+// Removes all visits and the main history entry for the given URI.
+// Silently fails if we have no knowledge of the page.
+
+NS_IMETHODIMP
+nsNavHistory::RemovePage(nsIURI *aURI)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aURI);
+
+ // Build a list of place ids to delete.
+ int64_t placeId;
+ nsAutoCString guid;
+ nsresult rv = GetIdForPage(aURI, &placeId, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (placeId == 0) {
+ return NS_OK;
+ }
+ nsAutoCString deletePlaceIdQueryString;
+ deletePlaceIdQueryString.AppendInt(placeId);
+
+ rv = RemovePagesInternal(deletePlaceIdQueryString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Clear the registered embed visits.
+ clearEmbedVisits();
+
+ return NS_OK;
+}
+
+
+// nsNavHistory::RemovePagesFromHost
+//
+// This function will delete all history information about pages from a
+// given host. If aEntireDomain is set, we will also delete pages from
+// sub hosts (so if we are passed in "microsoft.com" we delete
+// "www.microsoft.com", "msdn.microsoft.com", etc.). An empty host name
+// means local files and anything else with no host name. You can also pass
+// in the localized "(local files)" title given to you from a history query.
+//
+// Silently fails if we have no knowledge of the host.
+//
+// This sends onBeginUpdateBatch/onEndUpdateBatch to observers
+
+NS_IMETHODIMP
+nsNavHistory::RemovePagesFromHost(const nsACString& aHost, bool aEntireDomain)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+
+ nsresult rv;
+ // Local files don't have any host name. We don't want to delete all files in
+ // history when we get passed an empty string, so force to exact match
+ if (aHost.IsEmpty())
+ aEntireDomain = false;
+
+ // translate "(local files)" to an empty host name
+ // be sure to use the TitleForDomain to get the localized name
+ nsCString localFiles;
+ TitleForDomain(EmptyCString(), localFiles);
+ nsAutoString host16;
+ if (!aHost.Equals(localFiles))
+ CopyUTF8toUTF16(aHost, host16);
+
+ // see BindQueryClauseParameters for how this host selection works
+ nsAutoString revHostDot;
+ GetReversedHostname(host16, revHostDot);
+ NS_ASSERTION(revHostDot[revHostDot.Length() - 1] == '.', "Invalid rev. host");
+ nsAutoString revHostSlash(revHostDot);
+ revHostSlash.Truncate(revHostSlash.Length() - 1);
+ revHostSlash.Append('/');
+
+ // build condition string based on host selection type
+ nsAutoCString conditionString;
+ if (aEntireDomain)
+ conditionString.AssignLiteral("rev_host >= ?1 AND rev_host < ?2 ");
+ else
+ conditionString.AssignLiteral("rev_host = ?1 ");
+
+ // create statement depending on delete type
+ nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
+ NS_LITERAL_CSTRING("SELECT id FROM moz_places WHERE ") + conditionString
+ );
+ NS_ENSURE_STATE(statement);
+ mozStorageStatementScoper scoper(statement);
+
+ rv = statement->BindStringByIndex(0, revHostDot);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aEntireDomain) {
+ rv = statement->BindStringByIndex(1, revHostSlash);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString hostPlaceIds;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) {
+ if (!hostPlaceIds.IsEmpty())
+ hostPlaceIds.Append(',');
+ int64_t placeId;
+ rv = statement->GetInt64(0, &placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ hostPlaceIds.AppendInt(placeId);
+ }
+
+ // force a full refresh calling onEndUpdateBatch (will call Refresh())
+ UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers
+
+ rv = RemovePagesInternal(hostPlaceIds);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Clear the registered embed visits.
+ clearEmbedVisits();
+
+ return NS_OK;
+}
+
+
+// nsNavHistory::RemovePagesByTimeframe
+//
+// This function will delete all history information about
+// pages for a given timeframe.
+// Limits are included: aBeginTime <= timeframe <= aEndTime
+//
+// This method sends onBeginUpdateBatch/onEndUpdateBatch to observers
+
+NS_IMETHODIMP
+nsNavHistory::RemovePagesByTimeframe(PRTime aBeginTime, PRTime aEndTime)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+
+ nsresult rv;
+ // build a list of place ids to delete
+ nsCString deletePlaceIdsQueryString;
+
+ // we only need to know if a place has a visit into the given timeframe
+ // this query is faster than actually selecting in moz_historyvisits
+ nsCOMPtr<mozIStorageStatement> selectByTime = mDB->GetStatement(
+ "SELECT h.id FROM moz_places h WHERE "
+ "EXISTS "
+ "(SELECT id FROM moz_historyvisits v WHERE v.place_id = h.id "
+ "AND v.visit_date >= :from_date AND v.visit_date <= :to_date LIMIT 1)"
+ );
+ NS_ENSURE_STATE(selectByTime);
+ mozStorageStatementScoper selectByTimeScoper(selectByTime);
+
+ rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("from_date"), aBeginTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = selectByTime->BindInt64ByName(NS_LITERAL_CSTRING("to_date"), aEndTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(selectByTime->ExecuteStep(&hasMore)) && hasMore) {
+ int64_t placeId;
+ rv = selectByTime->GetInt64(0, &placeId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (placeId != 0) {
+ if (!deletePlaceIdsQueryString.IsEmpty())
+ deletePlaceIdsQueryString.Append(',');
+ deletePlaceIdsQueryString.AppendInt(placeId);
+ }
+ }
+
+ // force a full refresh calling onEndUpdateBatch (will call Refresh())
+ UpdateBatchScoper batch(*this); // sends Begin/EndUpdateBatch to observers
+
+ rv = RemovePagesInternal(deletePlaceIdsQueryString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Clear the registered embed visits.
+ clearEmbedVisits();
+
+ return NS_OK;
+}
+
+
+// Call this method before visiting a URL in order to help determine the
+// transition type of the visit.
+//
+// @see MarkPageAsFollowedBookmark
+
+NS_IMETHODIMP
+nsNavHistory::MarkPageAsTyped(nsIURI *aURI)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aURI);
+
+ // don't add when history is disabled
+ if (IsHistoryDisabled())
+ return NS_OK;
+
+ nsAutoCString uriString;
+ nsresult rv = aURI->GetSpec(uriString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if URL is already in the typed queue, then we need to remove the old one
+ int64_t unusedEventTime;
+ if (mRecentTyped.Get(uriString, &unusedEventTime))
+ mRecentTyped.Remove(uriString);
+
+ if (mRecentTyped.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
+ ExpireNonrecentEvents(&mRecentTyped);
+
+ mRecentTyped.Put(uriString, GetNow());
+ return NS_OK;
+}
+
+
+// Call this method before visiting a URL in order to help determine the
+// transition type of the visit.
+//
+// @see MarkPageAsTyped
+
+NS_IMETHODIMP
+nsNavHistory::MarkPageAsFollowedLink(nsIURI *aURI)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aURI);
+
+ // don't add when history is disabled
+ if (IsHistoryDisabled())
+ return NS_OK;
+
+ nsAutoCString uriString;
+ nsresult rv = aURI->GetSpec(uriString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if URL is already in the links queue, then we need to remove the old one
+ int64_t unusedEventTime;
+ if (mRecentLink.Get(uriString, &unusedEventTime))
+ mRecentLink.Remove(uriString);
+
+ if (mRecentLink.Count() > RECENT_EVENT_QUEUE_MAX_LENGTH)
+ ExpireNonrecentEvents(&mRecentLink);
+
+ mRecentLink.Put(uriString, GetNow());
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistory::GetPageTitle(nsIURI* aURI, nsAString& aTitle)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aURI);
+
+ aTitle.Truncate(0);
+
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+ "SELECT id, url, title, rev_host, visit_count, guid "
+ "FROM moz_places "
+ "WHERE url_hash = hash(:page_url) AND url = :page_url "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResults = false;
+ rv = stmt->ExecuteStep(&hasResults);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasResults) {
+ aTitle.SetIsVoid(true);
+ return NS_OK; // Not found, return a void string.
+ }
+
+ rv = stmt->GetString(2, aTitle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageVacuumParticipant
+
+NS_IMETHODIMP
+nsNavHistory::GetDatabaseConnection(mozIStorageConnection** _DBConnection)
+{
+ return GetDBConnection(_DBConnection);
+}
+
+
+NS_IMETHODIMP
+nsNavHistory::GetExpectedDatabasePageSize(int32_t* _expectedPageSize)
+{
+ NS_ENSURE_STATE(mDB);
+ NS_ENSURE_STATE(mDB->MainConn());
+ return mDB->MainConn()->GetDefaultPageSize(_expectedPageSize);
+}
+
+
+NS_IMETHODIMP
+nsNavHistory::OnBeginVacuum(bool* _vacuumGranted)
+{
+ // TODO: Check if we have to deny the vacuum in some heavy-load case.
+ // We could maybe want to do that during batches?
+ *_vacuumGranted = true;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistory::OnEndVacuum(bool aSucceeded)
+{
+ NS_WARNING_ASSERTION(aSucceeded, "Places.sqlite vacuum failed.");
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsPIPlacesDatabase
+
+NS_IMETHODIMP
+nsNavHistory::GetDBConnection(mozIStorageConnection **_DBConnection)
+{
+ NS_ENSURE_ARG_POINTER(_DBConnection);
+ RefPtr<mozIStorageConnection> connection = mDB->MainConn();
+ connection.forget(_DBConnection);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistory::GetShutdownClient(nsIAsyncShutdownClient **_shutdownClient)
+{
+ NS_ENSURE_ARG_POINTER(_shutdownClient);
+ RefPtr<nsIAsyncShutdownClient> client = mDB->GetClientsShutdown();
+ MOZ_ASSERT(client);
+ client.forget(_shutdownClient);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistory::AsyncExecuteLegacyQueries(nsINavHistoryQuery** aQueries,
+ uint32_t aQueryCount,
+ nsINavHistoryQueryOptions* aOptions,
+ mozIStorageStatementCallback* aCallback,
+ mozIStoragePendingStatement** _stmt)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ NS_ENSURE_ARG(aQueries);
+ NS_ENSURE_ARG(aOptions);
+ NS_ENSURE_ARG(aCallback);
+ NS_ENSURE_ARG_POINTER(_stmt);
+
+ nsCOMArray<nsNavHistoryQuery> queries;
+ for (uint32_t i = 0; i < aQueryCount; i ++) {
+ nsCOMPtr<nsNavHistoryQuery> query = do_QueryInterface(aQueries[i]);
+ NS_ENSURE_STATE(query);
+ queries.AppendElement(query.forget());
+ }
+ NS_ENSURE_ARG_MIN(queries.Count(), 1);
+
+ nsCOMPtr<nsNavHistoryQueryOptions> options = do_QueryInterface(aOptions);
+ NS_ENSURE_ARG(options);
+
+ nsCString queryString;
+ bool paramsPresent = false;
+ nsNavHistory::StringHash addParams(HISTORY_DATE_CONT_LENGTH);
+ nsresult rv = ConstructQueryString(queries, options, queryString,
+ paramsPresent, addParams);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<mozIStorageAsyncStatement> statement =
+ mDB->GetAsyncStatement(queryString);
+ NS_ENSURE_STATE(statement);
+
+#ifdef DEBUG
+ if (NS_FAILED(rv)) {
+ nsAutoCString lastErrorString;
+ (void)mDB->MainConn()->GetLastErrorString(lastErrorString);
+ int32_t lastError = 0;
+ (void)mDB->MainConn()->GetLastError(&lastError);
+ printf("Places failed to create a statement from this query:\n%s\nStorage error (%d): %s\n",
+ queryString.get(), lastError, lastErrorString.get());
+ }
+#endif
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (paramsPresent) {
+ // bind parameters
+ int32_t i;
+ for (i = 0; i < queries.Count(); i++) {
+ rv = BindQueryClauseParameters(statement, i, queries[i], options);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ for (auto iter = addParams.Iter(); !iter.Done(); iter.Next()) {
+ nsresult rv = statement->BindUTF8StringByName(iter.Key(), iter.Data());
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+
+ rv = statement->ExecuteAsync(aCallback, _stmt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+nsresult
+nsNavHistory::NotifyOnPageExpired(nsIURI *aURI, PRTime aVisitTime,
+ bool aWholeEntry, const nsACString& aGUID,
+ uint16_t aReason, uint32_t aTransitionType)
+{
+ // Invalidate the cached value for whether there's history or not.
+ mDaysOfHistory = -1;
+
+ MOZ_ASSERT(!aGUID.IsEmpty());
+ if (aWholeEntry) {
+ // Notify our observers that the page has been removed.
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver, OnDeleteURI(aURI, aGUID, aReason));
+ }
+ else {
+ // Notify our observers that some visits for the page have been removed.
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver,
+ OnDeleteVisits(aURI, aVisitTime, aGUID, aReason,
+ aTransitionType));
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+nsNavHistory::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+ if (strcmp(aTopic, TOPIC_PROFILE_TEARDOWN) == 0 ||
+ strcmp(aTopic, TOPIC_PROFILE_CHANGE) == 0 ||
+ strcmp(aTopic, TOPIC_SIMULATE_PLACES_SHUTDOWN) == 0) {
+ // These notifications are used by tests to simulate a Places shutdown.
+ // They should just be forwarded to the Database handle.
+ mDB->Observe(aSubject, aTopic, aData);
+ } else if (strcmp(aTopic, TOPIC_PLACES_CONNECTION_CLOSED) == 0) {
+ // Don't even try to notify observers from this point on, the category
+ // cache would init services that could try to use our APIs.
+ mCanNotify = false;
+ mObservers.Clear();
+ } else if (strcmp(aTopic, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING) == 0) {
+ nsCOMPtr<nsIAutoCompleteInput> input = do_QueryInterface(aSubject);
+ if (!input)
+ return NS_OK;
+
+ // If the source is a private window, don't add any input history.
+ bool isPrivate;
+ nsresult rv = input->GetInPrivateContext(&isPrivate);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isPrivate)
+ return NS_OK;
+
+ nsCOMPtr<nsIAutoCompletePopup> popup;
+ input->GetPopup(getter_AddRefs(popup));
+ if (!popup)
+ return NS_OK;
+
+ nsCOMPtr<nsIAutoCompleteController> controller;
+ input->GetController(getter_AddRefs(controller));
+ if (!controller)
+ return NS_OK;
+
+ // Don't bother if the popup is closed
+ bool open;
+ rv = popup->GetPopupOpen(&open);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!open)
+ return NS_OK;
+
+ // Ignore if nothing selected from the popup
+ int32_t selectedIndex;
+ rv = popup->GetSelectedIndex(&selectedIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (selectedIndex == -1)
+ return NS_OK;
+
+ rv = AutoCompleteFeedback(selectedIndex, controller);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (strcmp(aTopic, TOPIC_PREF_CHANGED) == 0) {
+ LoadPrefs();
+ } else if (strcmp(aTopic, TOPIC_IDLE_DAILY) == 0) {
+ (void)DecayFrecency();
+ }
+
+ return NS_OK;
+}
+
+
+namespace {
+
+class DecayFrecencyCallback : public AsyncStatementCallback
+{
+public:
+ DecayFrecencyCallback()
+ {
+ }
+
+ NS_IMETHOD HandleCompletion(uint16_t aReason)
+ {
+ if (aReason == REASON_FINISHED) {
+ nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(navHistory);
+ navHistory->NotifyManyFrecenciesChanged();
+ }
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+nsresult
+nsNavHistory::DecayFrecency()
+{
+ nsresult rv = FixInvalidFrecencies();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Globally decay places frecency rankings to estimate reduced frecency
+ // values of pages that haven't been visited for a while, i.e., they do
+ // not get an updated frecency. A scaling factor of .975 results in .5 the
+ // original value after 28 days.
+ // When changing the scaling factor, ensure that the barrier in
+ // moz_places_afterupdate_frecency_trigger still ignores these changes.
+ nsCOMPtr<mozIStorageAsyncStatement> decayFrecency = mDB->GetAsyncStatement(
+ "UPDATE moz_places SET frecency = ROUND(frecency * .975) "
+ "WHERE frecency > 0"
+ );
+ NS_ENSURE_STATE(decayFrecency);
+
+ // Decay potentially unused adaptive entries (e.g. those that are at 1)
+ // to allow better chances for new entries that will start at 1.
+ nsCOMPtr<mozIStorageAsyncStatement> decayAdaptive = mDB->GetAsyncStatement(
+ "UPDATE moz_inputhistory SET use_count = use_count * .975"
+ );
+ NS_ENSURE_STATE(decayAdaptive);
+
+ // Delete any adaptive entries that won't help in ordering anymore.
+ nsCOMPtr<mozIStorageAsyncStatement> deleteAdaptive = mDB->GetAsyncStatement(
+ "DELETE FROM moz_inputhistory WHERE use_count < .01"
+ );
+ NS_ENSURE_STATE(deleteAdaptive);
+
+ mozIStorageBaseStatement *stmts[] = {
+ decayFrecency.get(),
+ decayAdaptive.get(),
+ deleteAdaptive.get()
+ };
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ RefPtr<DecayFrecencyCallback> cb = new DecayFrecencyCallback();
+ rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb,
+ getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+// Query stuff *****************************************************************
+
+// Helper class for QueryToSelectClause
+//
+// This class helps to build part of the WHERE clause. It supports
+// multiple queries by appending the query index to the parameter name.
+// For the query with index 0 the parameter name is not altered what
+// allows using this parameter in other situations (see SelectAsSite).
+
+class ConditionBuilder
+{
+public:
+
+ explicit ConditionBuilder(int32_t aQueryIndex): mQueryIndex(aQueryIndex)
+ { }
+
+ ConditionBuilder& Condition(const char* aStr)
+ {
+ if (!mClause.IsEmpty())
+ mClause.AppendLiteral(" AND ");
+ Str(aStr);
+ return *this;
+ }
+
+ ConditionBuilder& Str(const char* aStr)
+ {
+ mClause.Append(' ');
+ mClause.Append(aStr);
+ mClause.Append(' ');
+ return *this;
+ }
+
+ ConditionBuilder& Param(const char* aParam)
+ {
+ mClause.Append(' ');
+ if (!mQueryIndex)
+ mClause.Append(aParam);
+ else
+ mClause += nsPrintfCString("%s%d", aParam, mQueryIndex);
+
+ mClause.Append(' ');
+ return *this;
+ }
+
+ void GetClauseString(nsCString& aResult)
+ {
+ aResult = mClause;
+ }
+
+private:
+
+ int32_t mQueryIndex;
+ nsCString mClause;
+};
+
+
+// nsNavHistory::QueryToSelectClause
+//
+// THE BEHAVIOR SHOULD BE IN SYNC WITH BindQueryClauseParameters
+//
+// I don't check return values from the query object getters because there's
+// no way for those to fail.
+
+nsresult
+nsNavHistory::QueryToSelectClause(nsNavHistoryQuery* aQuery, // const
+ nsNavHistoryQueryOptions* aOptions,
+ int32_t aQueryIndex,
+ nsCString* aClause)
+{
+ bool hasIt;
+ bool excludeQueries = aOptions->ExcludeQueries();
+
+ ConditionBuilder clause(aQueryIndex);
+
+ if ((NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) ||
+ (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)) {
+ clause.Condition("EXISTS (SELECT 1 FROM moz_historyvisits "
+ "WHERE place_id = h.id");
+ // begin time
+ if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt)
+ clause.Condition("visit_date >=").Param(":begin_time");
+ // end time
+ if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt)
+ clause.Condition("visit_date <=").Param(":end_time");
+ clause.Str(" LIMIT 1)");
+ }
+
+ // search terms
+ bool hasSearchTerms;
+ int32_t searchBehavior = mozIPlacesAutoComplete::BEHAVIOR_HISTORY |
+ mozIPlacesAutoComplete::BEHAVIOR_BOOKMARK;
+ if (NS_SUCCEEDED(aQuery->GetHasSearchTerms(&hasSearchTerms)) && hasSearchTerms) {
+ // Re-use the autocomplete_match function. Setting the behavior to match
+ // history or typed history or bookmarks or open pages will match almost
+ // everything.
+ clause.Condition("AUTOCOMPLETE_MATCH(").Param(":search_string")
+ .Str(", h.url, page_title, tags, ")
+ .Str(nsPrintfCString("1, 1, 1, 1, %d, %d)",
+ mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED,
+ searchBehavior).get());
+ // Serching by terms implicitly exclude queries.
+ excludeQueries = true;
+ }
+
+ // min and max visit count
+ if (aQuery->MinVisits() >= 0)
+ clause.Condition("h.visit_count >=").Param(":min_visits");
+
+ if (aQuery->MaxVisits() >= 0)
+ clause.Condition("h.visit_count <=").Param(":max_visits");
+
+ // only bookmarked, has no affect on bookmarks-only queries
+ if (aOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS &&
+ aQuery->OnlyBookmarked())
+ clause.Condition("EXISTS (SELECT b.fk FROM moz_bookmarks b WHERE b.type = ")
+ .Str(nsPrintfCString("%d", nsNavBookmarks::TYPE_BOOKMARK).get())
+ .Str("AND b.fk = h.id)");
+
+ // domain
+ if (NS_SUCCEEDED(aQuery->GetHasDomain(&hasIt)) && hasIt) {
+ bool domainIsHost = false;
+ aQuery->GetDomainIsHost(&domainIsHost);
+ if (domainIsHost)
+ clause.Condition("h.rev_host =").Param(":domain_lower");
+ else
+ // see domain setting in BindQueryClauseParameters for why we do this
+ clause.Condition("h.rev_host >=").Param(":domain_lower")
+ .Condition("h.rev_host <").Param(":domain_upper");
+ }
+
+ // URI
+ if (NS_SUCCEEDED(aQuery->GetHasUri(&hasIt)) && hasIt) {
+ clause.Condition("h.url_hash = hash(").Param(":uri").Str(")")
+ .Condition("h.url =").Param(":uri");
+ }
+
+ // annotation
+ aQuery->GetHasAnnotation(&hasIt);
+ if (hasIt) {
+ clause.Condition("");
+ if (aQuery->AnnotationIsNot())
+ clause.Str("NOT");
+ clause.Str(
+ "EXISTS "
+ "(SELECT h.id "
+ "FROM moz_annos anno "
+ "JOIN moz_anno_attributes annoname "
+ "ON anno.anno_attribute_id = annoname.id "
+ "WHERE anno.place_id = h.id "
+ "AND annoname.name = ").Param(":anno").Str(")");
+ // annotation-based queries don't get the common conditions, so you get
+ // all URLs with that annotation
+ }
+
+ // tags
+ const nsTArray<nsString> &tags = aQuery->Tags();
+ if (tags.Length() > 0) {
+ clause.Condition("h.id");
+ if (aQuery->TagsAreNot())
+ clause.Str("NOT");
+ clause.Str(
+ "IN "
+ "(SELECT bms.fk "
+ "FROM moz_bookmarks bms "
+ "JOIN moz_bookmarks tags ON bms.parent = tags.id "
+ "WHERE tags.parent =").
+ Param(":tags_folder").
+ Str("AND tags.title IN (");
+ for (uint32_t i = 0; i < tags.Length(); ++i) {
+ nsPrintfCString param(":tag%d_", i);
+ clause.Param(param.get());
+ if (i < tags.Length() - 1)
+ clause.Str(",");
+ }
+ clause.Str(")");
+ if (!aQuery->TagsAreNot())
+ clause.Str("GROUP BY bms.fk HAVING count(*) >=").Param(":tag_count");
+ clause.Str(")");
+ }
+
+ // transitions
+ const nsTArray<uint32_t>& transitions = aQuery->Transitions();
+ for (uint32_t i = 0; i < transitions.Length(); ++i) {
+ nsPrintfCString param(":transition%d_", i);
+ clause.Condition("h.id IN (SELECT place_id FROM moz_historyvisits "
+ "WHERE visit_type = ")
+ .Param(param.get())
+ .Str(")");
+ }
+
+ // folders
+ const nsTArray<int64_t>& folders = aQuery->Folders();
+ if (folders.Length() > 0) {
+ aOptions->SetQueryType(nsNavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS);
+
+ nsTArray<int64_t> includeFolders;
+ includeFolders.AppendElements(folders);
+
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_STATE(bookmarks);
+
+ for (nsTArray<int64_t>::size_type i = 0; i < folders.Length(); ++i) {
+ nsTArray<int64_t> subFolders;
+ if (NS_FAILED(bookmarks->GetDescendantFolders(folders[i], subFolders)))
+ continue;
+ includeFolders.AppendElements(subFolders);
+ }
+
+ clause.Condition("b.parent IN(");
+ for (nsTArray<int64_t>::size_type i = 0; i < includeFolders.Length(); ++i) {
+ clause.Str(nsPrintfCString("%lld", includeFolders[i]).get());
+ if (i < includeFolders.Length() - 1) {
+ clause.Str(",");
+ }
+ }
+ clause.Str(")");
+ }
+
+ if (excludeQueries) {
+ // Serching by terms implicitly exclude queries.
+ clause.Condition("NOT h.url_hash BETWEEN hash('place', 'prefix_lo') AND "
+ "hash('place', 'prefix_hi')");
+ }
+
+ clause.GetClauseString(*aClause);
+ return NS_OK;
+}
+
+
+// nsNavHistory::BindQueryClauseParameters
+//
+// THE BEHAVIOR SHOULD BE IN SYNC WITH QueryToSelectClause
+
+nsresult
+nsNavHistory::BindQueryClauseParameters(mozIStorageBaseStatement* statement,
+ int32_t aQueryIndex,
+ nsNavHistoryQuery* aQuery, // const
+ nsNavHistoryQueryOptions* aOptions)
+{
+ nsresult rv;
+
+ bool hasIt;
+ // Append numbered index to param names, to replace them correctly in
+ // case of multiple queries. If we have just one query we don't change the
+ // param name though.
+ nsAutoCString qIndex;
+ if (aQueryIndex > 0)
+ qIndex.AppendInt(aQueryIndex);
+
+ // begin time
+ if (NS_SUCCEEDED(aQuery->GetHasBeginTime(&hasIt)) && hasIt) {
+ PRTime time = NormalizeTime(aQuery->BeginTimeReference(),
+ aQuery->BeginTime());
+ rv = statement->BindInt64ByName(
+ NS_LITERAL_CSTRING("begin_time") + qIndex, time);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // end time
+ if (NS_SUCCEEDED(aQuery->GetHasEndTime(&hasIt)) && hasIt) {
+ PRTime time = NormalizeTime(aQuery->EndTimeReference(),
+ aQuery->EndTime());
+ rv = statement->BindInt64ByName(
+ NS_LITERAL_CSTRING("end_time") + qIndex, time
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // search terms
+ if (NS_SUCCEEDED(aQuery->GetHasSearchTerms(&hasIt)) && hasIt) {
+ rv = statement->BindStringByName(
+ NS_LITERAL_CSTRING("search_string") + qIndex,
+ aQuery->SearchTerms()
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // min and max visit count
+ int32_t visits = aQuery->MinVisits();
+ if (visits >= 0) {
+ rv = statement->BindInt32ByName(
+ NS_LITERAL_CSTRING("min_visits") + qIndex, visits
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ visits = aQuery->MaxVisits();
+ if (visits >= 0) {
+ rv = statement->BindInt32ByName(
+ NS_LITERAL_CSTRING("max_visits") + qIndex, visits
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // domain (see GetReversedHostname for more info on reversed host names)
+ if (NS_SUCCEEDED(aQuery->GetHasDomain(&hasIt)) && hasIt) {
+ nsString revDomain;
+ GetReversedHostname(NS_ConvertUTF8toUTF16(aQuery->Domain()), revDomain);
+
+ if (aQuery->DomainIsHost()) {
+ rv = statement->BindStringByName(
+ NS_LITERAL_CSTRING("domain_lower") + qIndex, revDomain
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // for "mozilla.org" do query >= "gro.allizom." AND < "gro.allizom/"
+ // which will get everything starting with "gro.allizom." while using the
+ // index (using SUBSTRING() causes indexes to be discarded).
+ NS_ASSERTION(revDomain[revDomain.Length() - 1] == '.', "Invalid rev. host");
+ rv = statement->BindStringByName(
+ NS_LITERAL_CSTRING("domain_lower") + qIndex, revDomain
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ revDomain.Truncate(revDomain.Length() - 1);
+ revDomain.Append(char16_t('/'));
+ rv = statement->BindStringByName(
+ NS_LITERAL_CSTRING("domain_upper") + qIndex, revDomain
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // URI
+ if (aQuery->Uri()) {
+ rv = URIBinder::Bind(
+ statement, NS_LITERAL_CSTRING("uri") + qIndex, aQuery->Uri()
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // annotation
+ if (!aQuery->Annotation().IsEmpty()) {
+ rv = statement->BindUTF8StringByName(
+ NS_LITERAL_CSTRING("anno") + qIndex, aQuery->Annotation()
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // tags
+ const nsTArray<nsString> &tags = aQuery->Tags();
+ if (tags.Length() > 0) {
+ for (uint32_t i = 0; i < tags.Length(); ++i) {
+ nsPrintfCString paramName("tag%d_", i);
+ NS_ConvertUTF16toUTF8 tag(tags[i]);
+ rv = statement->BindUTF8StringByName(paramName + qIndex, tag);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ int64_t tagsFolder = GetTagsFolder();
+ rv = statement->BindInt64ByName(
+ NS_LITERAL_CSTRING("tags_folder") + qIndex, tagsFolder
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!aQuery->TagsAreNot()) {
+ rv = statement->BindInt32ByName(
+ NS_LITERAL_CSTRING("tag_count") + qIndex, tags.Length()
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // transitions
+ const nsTArray<uint32_t>& transitions = aQuery->Transitions();
+ if (transitions.Length() > 0) {
+ for (uint32_t i = 0; i < transitions.Length(); ++i) {
+ nsPrintfCString paramName("transition%d_", i);
+ rv = statement->BindInt64ByName(paramName + qIndex, transitions[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+
+// nsNavHistory::ResultsAsList
+//
+
+nsresult
+nsNavHistory::ResultsAsList(mozIStorageStatement* statement,
+ nsNavHistoryQueryOptions* aOptions,
+ nsCOMArray<nsNavHistoryResultNode>* aResults)
+{
+ nsresult rv;
+ nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(statement->ExecuteStep(&hasMore)) && hasMore) {
+ RefPtr<nsNavHistoryResultNode> result;
+ rv = RowToResult(row, aOptions, getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
+ aResults->AppendElement(result.forget());
+ }
+ return NS_OK;
+}
+
+const int64_t UNDEFINED_URN_VALUE = -1;
+
+// Create a urn (like
+// urn:places-persist:place:group=0&group=1&sort=1&type=1,,%28local%20files%29)
+// to be used to persist the open state of this container
+nsresult
+CreatePlacesPersistURN(nsNavHistoryQueryResultNode *aResultNode,
+ int64_t aValue, const nsCString& aTitle, nsCString& aURN)
+{
+ nsAutoCString uri;
+ nsresult rv = aResultNode->GetUri(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aURN.AssignLiteral("urn:places-persist:");
+ aURN.Append(uri);
+
+ aURN.Append(',');
+ if (aValue != UNDEFINED_URN_VALUE)
+ aURN.AppendInt(aValue);
+
+ aURN.Append(',');
+ if (!aTitle.IsEmpty()) {
+ nsAutoCString escapedTitle;
+ bool success = NS_Escape(aTitle, escapedTitle, url_XAlphas);
+ NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+ aURN.Append(escapedTitle);
+ }
+
+ return NS_OK;
+}
+
+int64_t
+nsNavHistory::GetTagsFolder()
+{
+ // cache our tags folder
+ // note, we can't do this in nsNavHistory::Init(),
+ // as getting the bookmarks service would initialize it.
+ if (mTagsFolder == -1) {
+ nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, -1);
+
+ nsresult rv = bookmarks->GetTagsFolder(&mTagsFolder);
+ NS_ENSURE_SUCCESS(rv, -1);
+ }
+ return mTagsFolder;
+}
+
+// nsNavHistory::FilterResultSet
+//
+// This does some post-query-execution filtering:
+// - searching on title, url and tags
+// - limit count
+//
+// Note: changes to filtering in FilterResultSet()
+// may require changes to NeedToFilterResultSet()
+
+nsresult
+nsNavHistory::FilterResultSet(nsNavHistoryQueryResultNode* aQueryNode,
+ const nsCOMArray<nsNavHistoryResultNode>& aSet,
+ nsCOMArray<nsNavHistoryResultNode>* aFiltered,
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions *aOptions)
+{
+ // get the bookmarks service
+ nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+
+ // parse the search terms
+ nsTArray<nsTArray<nsString>*> terms;
+ ParseSearchTermsFromQueries(aQueries, &terms);
+
+ uint16_t resultType = aOptions->ResultType();
+ for (int32_t nodeIndex = 0; nodeIndex < aSet.Count(); nodeIndex++) {
+ // exclude-queries is implicit when searching, we're only looking at
+ // plan URI nodes
+ if (!aSet[nodeIndex]->IsURI())
+ continue;
+
+ // RESULTS_AS_TAG_CONTENTS returns a set ordered by place_id and
+ // lastModified. So, to remove duplicates, we can retain the first result
+ // for each uri.
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS &&
+ nodeIndex > 0 && aSet[nodeIndex]->mURI == aSet[nodeIndex-1]->mURI)
+ continue;
+
+ if (aSet[nodeIndex]->mItemId != -1 && aQueryNode &&
+ aQueryNode->mItemId == aSet[nodeIndex]->mItemId) {
+ continue;
+ }
+
+ // Append the node only if it matches one of the queries.
+ bool appendNode = false;
+ for (int32_t queryIndex = 0;
+ queryIndex < aQueries.Count() && !appendNode; queryIndex++) {
+
+ if (terms[queryIndex]->Length()) {
+ // Filter based on search terms.
+ // Convert title and url for the current node to UTF16 strings.
+ NS_ConvertUTF8toUTF16 nodeTitle(aSet[nodeIndex]->mTitle);
+ // Unescape the URL for search terms matching.
+ nsAutoCString cNodeURL(aSet[nodeIndex]->mURI);
+ NS_ConvertUTF8toUTF16 nodeURL(NS_UnescapeURL(cNodeURL));
+
+ // Determine if every search term matches anywhere in the title, url or
+ // tag.
+ bool matchAll = true;
+ for (int32_t termIndex = terms[queryIndex]->Length() - 1;
+ termIndex >= 0 && matchAll;
+ termIndex--) {
+ nsString& term = terms[queryIndex]->ElementAt(termIndex);
+
+ // True if any of them match; false makes us quit the loop
+ matchAll = CaseInsensitiveFindInReadable(term, nodeTitle) ||
+ CaseInsensitiveFindInReadable(term, nodeURL) ||
+ CaseInsensitiveFindInReadable(term, aSet[nodeIndex]->mTags);
+ }
+
+ // Skip the node if we don't match all terms in the title, url or tag
+ if (!matchAll)
+ continue;
+ }
+
+ // We passed all filters, so we can append the node to filtered results.
+ appendNode = true;
+ }
+
+ if (appendNode)
+ aFiltered->AppendObject(aSet[nodeIndex]);
+
+ // Stop once we have reached max results.
+ if (aOptions->MaxResults() > 0 &&
+ (uint32_t)aFiltered->Count() >= aOptions->MaxResults())
+ break;
+ }
+
+ // De-allocate the temporary matrixes.
+ for (int32_t i = 0; i < aQueries.Count(); i++) {
+ delete terms[i];
+ }
+
+ return NS_OK;
+}
+
+void
+nsNavHistory::registerEmbedVisit(nsIURI* aURI,
+ int64_t aTime)
+{
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+
+ VisitHashKey* visit = mEmbedVisits.PutEntry(aURI);
+ if (!visit) {
+ NS_WARNING("Unable to register a EMBED visit.");
+ return;
+ }
+ visit->visitTime = aTime;
+}
+
+bool
+nsNavHistory::hasEmbedVisit(nsIURI* aURI) {
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+
+ return !!mEmbedVisits.GetEntry(aURI);
+}
+
+void
+nsNavHistory::clearEmbedVisits() {
+ NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
+
+ mEmbedVisits.Clear();
+}
+
+NS_IMETHODIMP
+nsNavHistory::ClearEmbedVisits() {
+ clearEmbedVisits();
+ return NS_OK;
+}
+
+// nsNavHistory::CheckIsRecentEvent
+//
+// Sees if this URL happened "recently."
+//
+// It is always removed from our recent list no matter what. It only counts
+// as "recent" if the event happened more recently than our event
+// threshold ago.
+
+bool
+nsNavHistory::CheckIsRecentEvent(RecentEventHash* hashTable,
+ const nsACString& url)
+{
+ PRTime eventTime;
+ if (hashTable->Get(url, reinterpret_cast<int64_t*>(&eventTime))) {
+ hashTable->Remove(url);
+ if (eventTime > GetNow() - RECENT_EVENT_THRESHOLD)
+ return true;
+ return false;
+ }
+ return false;
+}
+
+
+// nsNavHistory::ExpireNonrecentEvents
+//
+// This goes through our
+
+void
+nsNavHistory::ExpireNonrecentEvents(RecentEventHash* hashTable)
+{
+ int64_t threshold = GetNow() - RECENT_EVENT_THRESHOLD;
+ for (auto iter = hashTable->Iter(); !iter.Done(); iter.Next()) {
+ if (iter.Data() < threshold) {
+ iter.Remove();
+ }
+ }
+}
+
+
+// nsNavHistory::RowToResult
+//
+// Here, we just have a generic row. It could be a query, URL, visit,
+// or full visit.
+
+nsresult
+nsNavHistory::RowToResult(mozIStorageValueArray* aRow,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode** aResult)
+{
+ NS_ASSERTION(aRow && aOptions && aResult, "Null pointer in RowToResult");
+
+ // URL
+ nsAutoCString url;
+ nsresult rv = aRow->GetUTF8String(kGetInfoIndex_URL, url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // title
+ nsAutoCString title;
+ rv = aRow->GetUTF8String(kGetInfoIndex_Title, title);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t accessCount = aRow->AsInt32(kGetInfoIndex_VisitCount);
+ PRTime time = aRow->AsInt64(kGetInfoIndex_VisitDate);
+
+ // favicon
+ nsAutoCString favicon;
+ rv = aRow->GetUTF8String(kGetInfoIndex_FaviconURL, favicon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // itemId
+ int64_t itemId = aRow->AsInt64(kGetInfoIndex_ItemId);
+ int64_t parentId = -1;
+ if (itemId == 0) {
+ // This is not a bookmark. For non-bookmarks we use a -1 itemId value.
+ // Notice ids in sqlite tables start from 1, so itemId cannot ever be 0.
+ itemId = -1;
+ }
+ else {
+ // This is a bookmark, so it has a parent.
+ int64_t itemParentId = aRow->AsInt64(kGetInfoIndex_ItemParentId);
+ if (itemParentId > 0) {
+ // The Places root has parent == 0, but that item id does not really
+ // exist. We want to set the parent only if it's a real one.
+ parentId = itemParentId;
+ }
+ }
+
+ if (IsQueryURI(url)) {
+ // Special case "place:" URIs: turn them into containers.
+ if (itemId != -1) {
+ // We should never expose the history title for query nodes if the
+ // bookmark-item's title is set to null (the history title may be the
+ // query string without the place: prefix). Thus we call getItemTitle
+ // explicitly. Doing this in the SQL query would be less performant since
+ // it should be done for all results rather than only for queries.
+ nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = bookmarks->GetItemTitle(itemId, title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoCString guid;
+ if (itemId != -1) {
+ rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid, guid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ RefPtr<nsNavHistoryResultNode> resultNode;
+ rv = QueryRowToResult(itemId, guid, url, title, accessCount, time, favicon,
+ getter_AddRefs(resultNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (itemId != -1 ||
+ aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
+ // RESULTS_AS_TAG_QUERY has date columns
+ resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
+ resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
+ if (resultNode->IsFolder()) {
+ // If it's a simple folder node (i.e. a shortcut to another folder), apply
+ // our options for it. However, if the parent type was tag query, we do not
+ // apply them, because it would not yield any results.
+ resultNode->GetAsContainer()->mOptions = aOptions;
+ }
+ }
+
+ resultNode.forget(aResult);
+ return rv;
+ } else if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_URI ||
+ aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
+ RefPtr<nsNavHistoryResultNode> resultNode =
+ new nsNavHistoryResultNode(url, title, accessCount, time, favicon);
+
+ if (itemId != -1) {
+ resultNode->mItemId = itemId;
+ resultNode->mFolderId = parentId;
+ resultNode->mDateAdded = aRow->AsInt64(kGetInfoIndex_ItemDateAdded);
+ resultNode->mLastModified = aRow->AsInt64(kGetInfoIndex_ItemLastModified);
+
+ rv = aRow->GetUTF8String(nsNavBookmarks::kGetChildrenIndex_Guid,
+ resultNode->mBookmarkGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ resultNode->mFrecency = aRow->AsInt32(kGetInfoIndex_Frecency);
+ resultNode->mHidden = !!aRow->AsInt32(kGetInfoIndex_Hidden);
+
+ nsAutoString tags;
+ rv = aRow->GetString(kGetInfoIndex_ItemTags, tags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!tags.IsVoid()) {
+ resultNode->mTags.Assign(tags);
+ }
+
+ rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ resultNode.forget(aResult);
+ return NS_OK;
+ }
+
+ if (aOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) {
+ RefPtr<nsNavHistoryResultNode> resultNode =
+ new nsNavHistoryResultNode(url, title, accessCount, time, favicon);
+
+ nsAutoString tags;
+ rv = aRow->GetString(kGetInfoIndex_ItemTags, tags);
+ if (!tags.IsVoid())
+ resultNode->mTags.Assign(tags);
+
+ rv = aRow->GetUTF8String(kGetInfoIndex_Guid, resultNode->mPageGuid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aRow->GetInt64(kGetInfoIndex_VisitId, &resultNode->mVisitId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t fromVisitId;
+ rv = aRow->GetInt64(kGetInfoIndex_FromVisitId, &fromVisitId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fromVisitId > 0) {
+ resultNode->mFromVisitId = fromVisitId;
+ }
+
+ resultNode->mTransitionType = aRow->AsInt32(kGetInfoIndex_VisitType);
+
+ resultNode.forget(aResult);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+
+// nsNavHistory::QueryRowToResult
+//
+// Called by RowToResult when the URI is a place: URI to generate the proper
+// folder or query node.
+
+nsresult
+nsNavHistory::QueryRowToResult(int64_t itemId,
+ const nsACString& aBookmarkGuid,
+ const nsACString& aURI,
+ const nsACString& aTitle,
+ uint32_t aAccessCount, PRTime aTime,
+ const nsACString& aFavicon,
+ nsNavHistoryResultNode** aNode)
+{
+ MOZ_ASSERT((itemId != -1 && !aBookmarkGuid.IsEmpty()) ||
+ (itemId == -1 && aBookmarkGuid.IsEmpty()));
+
+ nsCOMArray<nsNavHistoryQuery> queries;
+ nsCOMPtr<nsNavHistoryQueryOptions> options;
+ nsresult rv = QueryStringToQueryArray(aURI, &queries,
+ getter_AddRefs(options));
+
+ RefPtr<nsNavHistoryResultNode> resultNode;
+ // If this failed the query does not parse correctly, let the error pass and
+ // handle it later.
+ if (NS_SUCCEEDED(rv)) {
+ // Check if this is a folder shortcut, so we can take a faster path.
+ int64_t targetFolderId = GetSimpleBookmarksQueryFolder(queries, options);
+ if (targetFolderId) {
+ nsNavBookmarks *bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = bookmarks->ResultNodeForContainer(targetFolderId, options,
+ getter_AddRefs(resultNode));
+ // If this failed the shortcut is pointing to nowhere, let the error pass
+ // and handle it later.
+ if (NS_SUCCEEDED(rv)) {
+ // At this point the node is set up like a regular folder node. Here
+ // we make the necessary change to make it a folder shortcut.
+ resultNode->GetAsFolder()->mTargetFolderItemId = targetFolderId;
+ resultNode->mItemId = itemId;
+ nsAutoCString targetFolderGuid(resultNode->GetAsFolder()->mBookmarkGuid);
+ resultNode->mBookmarkGuid = aBookmarkGuid;
+ resultNode->GetAsFolder()->mTargetFolderGuid = targetFolderGuid;
+
+ // Use the query item title, unless it's void (in that case use the
+ // concrete folder title).
+ if (!aTitle.IsVoid()) {
+ resultNode->mTitle = aTitle;
+ }
+ }
+ }
+ else {
+ // This is a regular query.
+ resultNode = new nsNavHistoryQueryResultNode(aTitle, EmptyCString(),
+ aTime, queries, options);
+ resultNode->mItemId = itemId;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Generating a generic empty node for a broken query!");
+ // This is a broken query, that either did not parse or points to not
+ // existing data. We don't want to return failure since that will kill the
+ // whole result. Instead make a generic empty query node.
+ resultNode = new nsNavHistoryQueryResultNode(aTitle, aFavicon, aURI);
+ resultNode->mItemId = itemId;
+ // This is a perf hack to generate an empty query that skips filtering.
+ resultNode->GetAsQuery()->Options()->SetExcludeItems(true);
+ }
+
+ resultNode.forget(aNode);
+ return NS_OK;
+}
+
+
+// nsNavHistory::VisitIdToResultNode
+//
+// Used by the query results to create new nodes on the fly when
+// notifications come in. This just creates a node for the given visit ID.
+
+nsresult
+nsNavHistory::VisitIdToResultNode(int64_t visitId,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode** aResult)
+{
+ nsAutoCString tagsFragment;
+ GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
+ true, tagsFragment);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ switch (aOptions->ResultType())
+ {
+ case nsNavHistoryQueryOptions::RESULTS_AS_VISIT:
+ case nsNavHistoryQueryOptions::RESULTS_AS_FULL_VISIT:
+ // visit query - want exact visit time
+ // Should match kGetInfoIndex_* (see GetQueryResults)
+ statement = mDB->GetStatement(NS_LITERAL_CSTRING(
+ "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
+ "v.visit_date, f.url, null, null, null, null, "
+ ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+ "v.id, v.from_visit, v.visit_type "
+ "FROM moz_places h "
+ "JOIN moz_historyvisits v ON h.id = v.place_id "
+ "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE v.id = :visit_id ")
+ );
+ break;
+
+ case nsNavHistoryQueryOptions::RESULTS_AS_URI:
+ // URL results - want last visit time
+ // Should match kGetInfoIndex_* (see GetQueryResults)
+ statement = mDB->GetStatement(NS_LITERAL_CSTRING(
+ "SELECT h.id, h.url, h.title, h.rev_host, h.visit_count, "
+ "h.last_visit_date, f.url, null, null, null, null, "
+ ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+ "null, null, null "
+ "FROM moz_places h "
+ "JOIN moz_historyvisits v ON h.id = v.place_id "
+ "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE v.id = :visit_id ")
+ );
+ break;
+
+ default:
+ // Query base types like RESULTS_AS_*_QUERY handle additions
+ // by registering their own observers when they are expanded.
+ return NS_OK;
+ }
+ NS_ENSURE_STATE(statement);
+ mozStorageStatementScoper scoper(statement);
+
+ nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("visit_id"),
+ visitId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ rv = statement->ExecuteStep(&hasMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (! hasMore) {
+ NS_NOTREACHED("Trying to get a result node for an invalid visit");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(statement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RowToResult(row, aOptions, aResult);
+}
+
+nsresult
+nsNavHistory::BookmarkIdToResultNode(int64_t aBookmarkId, nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode** aResult)
+{
+ nsAutoCString tagsFragment;
+ GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
+ true, tagsFragment);
+ // Should match kGetInfoIndex_*
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
+ "SELECT b.fk, h.url, COALESCE(b.title, h.title), "
+ "h.rev_host, h.visit_count, h.last_visit_date, f.url, b.id, "
+ "b.dateAdded, b.lastModified, b.parent, "
+ ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+ "null, null, null, b.guid, b.position, b.type, b.fk "
+ "FROM moz_bookmarks b "
+ "JOIN moz_places h ON b.fk = h.id "
+ "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE b.id = :item_id ")
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
+ aBookmarkId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ rv = stmt->ExecuteStep(&hasMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hasMore) {
+ NS_NOTREACHED("Trying to get a result node for an invalid bookmark identifier");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RowToResult(row, aOptions, aResult);
+}
+
+nsresult
+nsNavHistory::URIToResultNode(nsIURI* aURI,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode** aResult)
+{
+ nsAutoCString tagsFragment;
+ GetTagsSqlFragment(GetTagsFolder(), NS_LITERAL_CSTRING("h.id"),
+ true, tagsFragment);
+ // Should match kGetInfoIndex_*
+ nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
+ "SELECT h.id, :page_url, COALESCE(b.title, h.title), "
+ "h.rev_host, h.visit_count, h.last_visit_date, f.url, "
+ "b.id, b.dateAdded, b.lastModified, b.parent, "
+ ) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
+ "null, null, null, b.guid, b.position, b.type, b.fk "
+ "FROM moz_places h "
+ "LEFT JOIN moz_bookmarks b ON b.fk = h.id "
+ "LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url ")
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ rv = stmt->ExecuteStep(&hasMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hasMore) {
+ NS_NOTREACHED("Trying to get a result node for an invalid url");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RowToResult(row, aOptions, aResult);
+}
+
+void
+nsNavHistory::SendPageChangedNotification(nsIURI* aURI,
+ uint32_t aChangedAttribute,
+ const nsAString& aNewValue,
+ const nsACString& aGUID)
+{
+ MOZ_ASSERT(!aGUID.IsEmpty());
+ NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
+ nsINavHistoryObserver,
+ OnPageChanged(aURI, aChangedAttribute, aNewValue, aGUID));
+}
+
+// nsNavHistory::TitleForDomain
+//
+// This computes the title for a given domain. Normally, this is just the
+// domain name, but we specially handle empty cases to give you a nice
+// localized string.
+
+void
+nsNavHistory::TitleForDomain(const nsCString& domain, nsACString& aTitle)
+{
+ if (! domain.IsEmpty()) {
+ aTitle = domain;
+ return;
+ }
+
+ // use the localized one instead
+ GetStringFromName(u"localhost", aTitle);
+}
+
+void
+nsNavHistory::GetAgeInDaysString(int32_t aInt, const char16_t *aName,
+ nsACString& aResult)
+{
+ nsIStringBundle *bundle = GetBundle();
+ if (bundle) {
+ nsAutoString intString;
+ intString.AppendInt(aInt);
+ const char16_t* strings[1] = { intString.get() };
+ nsXPIDLString value;
+ nsresult rv = bundle->FormatStringFromName(aName, strings,
+ 1, getter_Copies(value));
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF16toUTF8(value, aResult);
+ return;
+ }
+ }
+ CopyUTF16toUTF8(nsDependentString(aName), aResult);
+}
+
+void
+nsNavHistory::GetStringFromName(const char16_t *aName, nsACString& aResult)
+{
+ nsIStringBundle *bundle = GetBundle();
+ if (bundle) {
+ nsXPIDLString value;
+ nsresult rv = bundle->GetStringFromName(aName, getter_Copies(value));
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF16toUTF8(value, aResult);
+ return;
+ }
+ }
+ CopyUTF16toUTF8(nsDependentString(aName), aResult);
+}
+
+void
+nsNavHistory::GetMonthName(int32_t aIndex, nsACString& aResult)
+{
+ nsIStringBundle *bundle = GetDateFormatBundle();
+ if (bundle) {
+ nsCString name = nsPrintfCString("month.%d.name", aIndex);
+ nsXPIDLString value;
+ nsresult rv = bundle->GetStringFromName(NS_ConvertUTF8toUTF16(name).get(),
+ getter_Copies(value));
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF16toUTF8(value, aResult);
+ return;
+ }
+ }
+ aResult = nsPrintfCString("[%d]", aIndex);
+}
+
+void
+nsNavHistory::GetMonthYear(int32_t aMonth, int32_t aYear, nsACString& aResult)
+{
+ nsIStringBundle *bundle = GetBundle();
+ if (bundle) {
+ nsAutoCString monthName;
+ GetMonthName(aMonth, monthName);
+ nsAutoString yearString;
+ yearString.AppendInt(aYear);
+ const char16_t* strings[2] = {
+ NS_ConvertUTF8toUTF16(monthName).get()
+ , yearString.get()
+ };
+ nsXPIDLString value;
+ if (NS_SUCCEEDED(bundle->FormatStringFromName(
+ u"finduri-MonthYear", strings, 2,
+ getter_Copies(value)
+ ))) {
+ CopyUTF16toUTF8(value, aResult);
+ return;
+ }
+ }
+ aResult.AppendLiteral("finduri-MonthYear");
+}
+
+
+namespace {
+
+// GetSimpleBookmarksQueryFolder
+//
+// Determines if this set of queries is a simple bookmarks query for a
+// folder with no other constraints. In these common cases, we can more
+// efficiently compute the results.
+//
+// A simple bookmarks query will result in a hierarchical tree of
+// bookmark items, folders and separators.
+//
+// Returns the folder ID if it is a simple folder query, 0 if not.
+static int64_t
+GetSimpleBookmarksQueryFolder(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions)
+{
+ if (aQueries.Count() != 1)
+ return 0;
+
+ nsNavHistoryQuery* query = aQueries[0];
+ if (query->Folders().Length() != 1)
+ return 0;
+
+ bool hasIt;
+ query->GetHasBeginTime(&hasIt);
+ if (hasIt)
+ return 0;
+ query->GetHasEndTime(&hasIt);
+ if (hasIt)
+ return 0;
+ query->GetHasDomain(&hasIt);
+ if (hasIt)
+ return 0;
+ query->GetHasUri(&hasIt);
+ if (hasIt)
+ return 0;
+ (void)query->GetHasSearchTerms(&hasIt);
+ if (hasIt)
+ return 0;
+ if (query->Tags().Length() > 0)
+ return 0;
+ if (aOptions->MaxResults() > 0)
+ return 0;
+
+ // RESULTS_AS_TAG_CONTENTS is quite similar to a folder shortcut, but it must
+ // not be treated like that, since it needs all query options.
+ if(aOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS)
+ return 0;
+
+ // Don't care about onlyBookmarked flag, since specifying a bookmark
+ // folder is inferring onlyBookmarked.
+
+ return query->Folders()[0];
+}
+
+
+// ParseSearchTermsFromQueries
+//
+// Construct a matrix of search terms from the given queries array.
+// All of the query objects are ORed together. Within a query, all the terms
+// are ANDed together. See nsINavHistoryService.idl.
+//
+// This just breaks the query up into words. We don't do anything fancy,
+// not even quoting. We do, however, strip quotes, because people might
+// try to input quotes expecting them to do something and get no results
+// back.
+
+inline bool isQueryWhitespace(char16_t ch)
+{
+ return ch == ' ';
+}
+
+void ParseSearchTermsFromQueries(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsTArray<nsTArray<nsString>*>* aTerms)
+{
+ int32_t lastBegin = -1;
+ for (int32_t i = 0; i < aQueries.Count(); i++) {
+ nsTArray<nsString> *queryTerms = new nsTArray<nsString>();
+ bool hasSearchTerms;
+ if (NS_SUCCEEDED(aQueries[i]->GetHasSearchTerms(&hasSearchTerms)) &&
+ hasSearchTerms) {
+ const nsString& searchTerms = aQueries[i]->SearchTerms();
+ for (uint32_t j = 0; j < searchTerms.Length(); j++) {
+ if (isQueryWhitespace(searchTerms[j]) ||
+ searchTerms[j] == '"') {
+ if (lastBegin >= 0) {
+ // found the end of a word
+ queryTerms->AppendElement(Substring(searchTerms, lastBegin,
+ j - lastBegin));
+ lastBegin = -1;
+ }
+ } else {
+ if (lastBegin < 0) {
+ // found the beginning of a word
+ lastBegin = j;
+ }
+ }
+ }
+ // last word
+ if (lastBegin >= 0)
+ queryTerms->AppendElement(Substring(searchTerms, lastBegin));
+ }
+ aTerms->AppendElement(queryTerms);
+ }
+}
+
+} // namespace
+
+
+nsresult
+nsNavHistory::UpdateFrecency(int64_t aPlaceId)
+{
+ nsCOMPtr<mozIStorageAsyncStatement> updateFrecencyStmt = mDB->GetAsyncStatement(
+ "UPDATE moz_places "
+ "SET frecency = NOTIFY_FRECENCY("
+ "CALCULATE_FRECENCY(:page_id), url, guid, hidden, last_visit_date"
+ ") "
+ "WHERE id = :page_id"
+ );
+ NS_ENSURE_STATE(updateFrecencyStmt);
+ nsresult rv = updateFrecencyStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
+ aPlaceId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIStorageAsyncStatement> updateHiddenStmt = mDB->GetAsyncStatement(
+ "UPDATE moz_places "
+ "SET hidden = 0 "
+ "WHERE id = :page_id AND frecency <> 0"
+ );
+ NS_ENSURE_STATE(updateHiddenStmt);
+ rv = updateHiddenStmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"),
+ aPlaceId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozIStorageBaseStatement *stmts[] = {
+ updateFrecencyStmt.get()
+ , updateHiddenStmt.get()
+ };
+
+ RefPtr<AsyncStatementCallbackNotifier> cb =
+ new AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED);
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = mDB->MainConn()->ExecuteAsync(stmts, ArrayLength(stmts), cb,
+ getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+namespace {
+
+class FixInvalidFrecenciesCallback : public AsyncStatementCallbackNotifier
+{
+public:
+ FixInvalidFrecenciesCallback()
+ : AsyncStatementCallbackNotifier(TOPIC_FRECENCY_UPDATED)
+ {
+ }
+
+ NS_IMETHOD HandleCompletion(uint16_t aReason)
+ {
+ nsresult rv = AsyncStatementCallbackNotifier::HandleCompletion(aReason);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aReason == REASON_FINISHED) {
+ nsNavHistory *navHistory = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(navHistory);
+ navHistory->NotifyManyFrecenciesChanged();
+ }
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+nsresult
+nsNavHistory::FixInvalidFrecencies()
+{
+ nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
+ "UPDATE moz_places "
+ "SET frecency = CALCULATE_FRECENCY(id) "
+ "WHERE frecency < 0"
+ );
+ NS_ENSURE_STATE(stmt);
+
+ RefPtr<FixInvalidFrecenciesCallback> callback =
+ new FixInvalidFrecenciesCallback();
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ (void)stmt->ExecuteAsync(callback, getter_AddRefs(ps));
+
+ return NS_OK;
+}
+
+
+nsresult
+nsNavHistory::AutoCompleteFeedback(int32_t aIndex,
+ nsIAutoCompleteController *aController)
+{
+ nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
+ "INSERT OR REPLACE INTO moz_inputhistory "
+ // use_count will asymptotically approach the max of 10.
+ "SELECT h.id, IFNULL(i.input, :input_text), IFNULL(i.use_count, 0) * .9 + 1 "
+ "FROM moz_places h "
+ "LEFT JOIN moz_inputhistory i ON i.place_id = h.id AND i.input = :input_text "
+ "WHERE url_hash = hash(:page_url) AND url = :page_url "
+ );
+ NS_ENSURE_STATE(stmt);
+
+ nsAutoString input;
+ nsresult rv = aController->GetSearchString(input);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = stmt->BindStringByName(NS_LITERAL_CSTRING("input_text"), input);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString url;
+ rv = aController->GetValueAt(aIndex, url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
+ NS_ConvertUTF16toUTF8(url));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We do the update asynchronously and we do not care about failures.
+ RefPtr<AsyncStatementCallbackNotifier> callback =
+ new AsyncStatementCallbackNotifier(TOPIC_AUTOCOMPLETE_FEEDBACK_UPDATED);
+ nsCOMPtr<mozIStoragePendingStatement> canceler;
+ rv = stmt->ExecuteAsync(callback, getter_AddRefs(canceler));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsICollation *
+nsNavHistory::GetCollation()
+{
+ if (mCollation)
+ return mCollation;
+
+ // locale
+ nsCOMPtr<nsILocale> locale;
+ nsCOMPtr<nsILocaleService> ls(do_GetService(NS_LOCALESERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(ls, nullptr);
+ nsresult rv = ls->GetApplicationLocale(getter_AddRefs(locale));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ // collation
+ nsCOMPtr<nsICollationFactory> cfact =
+ do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
+ NS_ENSURE_TRUE(cfact, nullptr);
+ rv = cfact->CreateCollation(locale, getter_AddRefs(mCollation));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return mCollation;
+}
+
+nsIStringBundle *
+nsNavHistory::GetBundle()
+{
+ if (!mBundle) {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, nullptr);
+ nsresult rv = bundleService->CreateBundle(
+ "chrome://places/locale/places.properties",
+ getter_AddRefs(mBundle));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+ return mBundle;
+}
+
+nsIStringBundle *
+nsNavHistory::GetDateFormatBundle()
+{
+ if (!mDateFormatBundle) {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, nullptr);
+ nsresult rv = bundleService->CreateBundle(
+ "chrome://global/locale/dateFormat.properties",
+ getter_AddRefs(mDateFormatBundle));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+ return mDateFormatBundle;
+}
diff --git a/components/places/src/nsNavHistory.h b/components/places/src/nsNavHistory.h
new file mode 100644
index 000000000..c6e1133ff
--- /dev/null
+++ b/components/places/src/nsNavHistory.h
@@ -0,0 +1,655 @@
+/* -*- 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/. */
+
+#ifndef nsNavHistory_h_
+#define nsNavHistory_h_
+
+#include "nsINavHistoryService.h"
+#include "nsPIPlacesDatabase.h"
+#include "nsIBrowserHistory.h"
+#include "nsINavBookmarksService.h"
+#include "nsIFaviconService.h"
+
+#include "nsIObserverService.h"
+#include "nsICollation.h"
+#include "nsIStringBundle.h"
+#include "nsITimer.h"
+#include "nsMaybeWeakPtr.h"
+#include "nsCategoryCache.h"
+#include "nsNetCID.h"
+#include "nsToolkitCompsCID.h"
+#include "nsURIHashKey.h"
+#include "nsTHashtable.h"
+
+#include "nsNavHistoryResult.h"
+#include "nsNavHistoryQuery.h"
+#include "Database.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Atomics.h"
+
+#define QUERYUPDATE_TIME 0
+#define QUERYUPDATE_SIMPLE 1
+#define QUERYUPDATE_COMPLEX 2
+#define QUERYUPDATE_COMPLEX_WITH_BOOKMARKS 3
+#define QUERYUPDATE_HOST 4
+
+// Clamp title and URL to generously large, but not too large, length.
+// See bug 319004 for details.
+#define URI_LENGTH_MAX 65536
+#define TITLE_LENGTH_MAX 4096
+
+// Microsecond timeout for "recent" events such as typed and bookmark following.
+// If you typed it more than this time ago, it's not recent.
+#define RECENT_EVENT_THRESHOLD PRTime((int64_t)15 * 60 * PR_USEC_PER_SEC)
+
+// Fired after autocomplete feedback has been updated.
+#define TOPIC_AUTOCOMPLETE_FEEDBACK_UPDATED "places-autocomplete-feedback-updated"
+
+// Fired after frecency has been updated.
+#define TOPIC_FRECENCY_UPDATED "places-frecency-updated"
+
+class nsNavHistory;
+class QueryKeyValuePair;
+class nsIEffectiveTLDService;
+class nsIIDNService;
+class PlacesSQLQueryBuilder;
+class nsIAutoCompleteController;
+
+// nsNavHistory
+
+class nsNavHistory final : public nsSupportsWeakReference
+ , public nsINavHistoryService
+ , public nsIObserver
+ , public nsIBrowserHistory
+ , public nsPIPlacesDatabase
+ , public mozIStorageVacuumParticipant
+{
+ friend class PlacesSQLQueryBuilder;
+
+public:
+ nsNavHistory();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINAVHISTORYSERVICE
+ NS_DECL_NSIBROWSERHISTORY
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSPIPLACESDATABASE
+ NS_DECL_MOZISTORAGEVACUUMPARTICIPANT
+
+ /**
+ * Obtains the nsNavHistory object.
+ */
+ static already_AddRefed<nsNavHistory> GetSingleton();
+
+ /**
+ * Initializes the nsNavHistory object. This should only be called once.
+ */
+ nsresult Init();
+
+ /**
+ * Used by other components in the places directory such as the annotation
+ * service to get a reference to this history object. Returns a pointer to
+ * the service if it exists. Otherwise creates one. Returns nullptr on error.
+ */
+ static nsNavHistory* GetHistoryService()
+ {
+ if (!gHistoryService) {
+ nsCOMPtr<nsINavHistoryService> serv =
+ do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(serv, nullptr);
+ NS_ASSERTION(gHistoryService, "Should have static instance pointer now");
+ }
+ return gHistoryService;
+ }
+
+ /**
+ * Used by other components in the places directory to get a reference to a
+ * const version of this history object.
+ *
+ * @return A pointer to a const version of the service if it exists,
+ * nullptr otherwise.
+ */
+ static const nsNavHistory* GetConstHistoryService()
+ {
+ const nsNavHistory* const history = gHistoryService;
+ return history;
+ }
+
+ /**
+ * Fetches the database id and the GUID associated to the given URI.
+ *
+ * @param aURI
+ * The page to look for.
+ * @param _pageId
+ * Will be set to the database id associated with the page.
+ * If the page doesn't exist, this will be zero.
+ * @param _GUID
+ * Will be set to the unique id associated with the page.
+ * If the page doesn't exist, this will be empty.
+ * @note This DOES NOT check for bad URLs other than that they're nonempty.
+ */
+ nsresult GetIdForPage(nsIURI* aURI,
+ int64_t* _pageId, nsCString& _GUID);
+
+ /**
+ * Fetches the database id and the GUID associated to the given URI, creating
+ * a new database entry if one doesn't exist yet.
+ *
+ * @param aURI
+ * The page to look for or create.
+ * @param _pageId
+ * Will be set to the database id associated with the page.
+ * @param _GUID
+ * Will be set to the unique id associated with the page.
+ * @note This DOES NOT check for bad URLs other than that they're nonempty.
+ * @note This DOES NOT update frecency of the page.
+ */
+ nsresult GetOrCreateIdForPage(nsIURI* aURI,
+ int64_t* _pageId, nsCString& _GUID);
+
+ /**
+ * Asynchronously recalculates frecency for a given page.
+ *
+ * @param aPlaceId
+ * Place id to recalculate the frecency for.
+ * @note If the new frecency is a non-zero value it will also unhide the page,
+ * otherwise will reuse the old hidden value.
+ */
+ nsresult UpdateFrecency(int64_t aPlaceId);
+
+ /**
+ * Recalculates frecency for all pages requesting that (frecency < 0). Those
+ * may be generated:
+ * * After a "clear private data"
+ * * After removing visits
+ * * After migrating from older versions
+ */
+ nsresult FixInvalidFrecencies();
+
+ /**
+ * Invalidate the frecencies of a list of places, so they will be recalculated
+ * at the first idle-daily notification.
+ *
+ * @param aPlacesIdsQueryString
+ * Query string containing list of places to be invalidated. If it's
+ * an empty string all places will be invalidated.
+ */
+ nsresult invalidateFrecencies(const nsCString& aPlaceIdsQueryString);
+
+ /**
+ * Calls onDeleteVisits and onDeleteURI notifications on registered listeners
+ * with the history service.
+ *
+ * @param aURI
+ * The nsIURI object representing the URI of the page being expired.
+ * @param aVisitTime
+ * The time, in microseconds, that the page being expired was visited.
+ * @param aWholeEntry
+ * Indicates if this is the last visit for this URI.
+ * @param aGUID
+ * The unique ID associated with the page.
+ * @param aReason
+ * Indicates the reason for the removal.
+ * See nsINavHistoryObserver::REASON_* constants.
+ * @param aTransitionType
+ * If it's a valid TRANSITION_* value, all visits of the specified type
+ * have been removed.
+ */
+ nsresult NotifyOnPageExpired(nsIURI *aURI, PRTime aVisitTime,
+ bool aWholeEntry, const nsACString& aGUID,
+ uint16_t aReason, uint32_t aTransitionType);
+
+ /**
+ * These functions return non-owning references to the locale-specific
+ * objects for places components.
+ */
+ nsIStringBundle* GetBundle();
+ nsIStringBundle* GetDateFormatBundle();
+ nsICollation* GetCollation();
+ void GetStringFromName(const char16_t* aName, nsACString& aResult);
+ void GetAgeInDaysString(int32_t aInt, const char16_t *aName,
+ nsACString& aResult);
+ void GetMonthName(int32_t aIndex, nsACString& aResult);
+ void GetMonthYear(int32_t aMonth, int32_t aYear, nsACString& aResult);
+
+ // Returns whether history is enabled or not.
+ bool IsHistoryDisabled() {
+ return !mHistoryEnabled;
+ }
+
+ // Constants for the columns returned by the above statement.
+ static const int32_t kGetInfoIndex_PageID;
+ static const int32_t kGetInfoIndex_URL;
+ static const int32_t kGetInfoIndex_Title;
+ static const int32_t kGetInfoIndex_RevHost;
+ static const int32_t kGetInfoIndex_VisitCount;
+ static const int32_t kGetInfoIndex_VisitDate;
+ static const int32_t kGetInfoIndex_FaviconURL;
+ static const int32_t kGetInfoIndex_ItemId;
+ static const int32_t kGetInfoIndex_ItemDateAdded;
+ static const int32_t kGetInfoIndex_ItemLastModified;
+ static const int32_t kGetInfoIndex_ItemParentId;
+ static const int32_t kGetInfoIndex_ItemTags;
+ static const int32_t kGetInfoIndex_Frecency;
+ static const int32_t kGetInfoIndex_Hidden;
+ static const int32_t kGetInfoIndex_Guid;
+ static const int32_t kGetInfoIndex_VisitId;
+ static const int32_t kGetInfoIndex_FromVisitId;
+ static const int32_t kGetInfoIndex_VisitType;
+
+ int64_t GetTagsFolder();
+
+ // this actually executes a query and gives you results, it is used by
+ // nsNavHistoryQueryResultNode
+ nsresult GetQueryResults(nsNavHistoryQueryResultNode *aResultNode,
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions *aOptions,
+ nsCOMArray<nsNavHistoryResultNode>* aResults);
+
+ // Take a row of kGetInfoIndex_* columns and construct a ResultNode.
+ // The row must contain the full set of columns.
+ nsresult RowToResult(mozIStorageValueArray* aRow,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode** aResult);
+ nsresult QueryRowToResult(int64_t aItemId,
+ const nsACString& aBookmarkGuid,
+ const nsACString& aURI,
+ const nsACString& aTitle,
+ uint32_t aAccessCount, PRTime aTime,
+ const nsACString& aFavicon,
+ nsNavHistoryResultNode** aNode);
+
+ nsresult VisitIdToResultNode(int64_t visitId,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode** aResult);
+
+ nsresult BookmarkIdToResultNode(int64_t aBookmarkId,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode** aResult);
+ nsresult URIToResultNode(nsIURI* aURI,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode** aResult);
+
+ // used by other places components to send history notifications (for example,
+ // when the favicon has changed)
+ void SendPageChangedNotification(nsIURI* aURI, uint32_t aChangedAttribute,
+ const nsAString& aValue,
+ const nsACString& aGUID);
+
+ /**
+ * Returns current number of days stored in history.
+ */
+ int32_t GetDaysOfHistory();
+
+ // used by query result nodes to update: see comment on body of CanLiveUpdateQuery
+ static uint32_t GetUpdateRequirements(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions,
+ bool* aHasSearchTerms);
+ bool EvaluateQueryForNode(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryResultNode* aNode);
+
+ static nsresult AsciiHostNameFromHostString(const nsACString& aHostName,
+ nsACString& aAscii);
+ void DomainNameFromURI(nsIURI* aURI,
+ nsACString& aDomainName);
+ static PRTime NormalizeTime(uint32_t aRelative, PRTime aOffset);
+
+ // Don't use these directly, inside nsNavHistory use UpdateBatchScoper,
+ // else use nsINavHistoryService::RunInBatchMode
+ nsresult BeginUpdateBatch();
+ nsresult EndUpdateBatch();
+
+ // The level of batches' nesting, 0 when no batches are open.
+ int32_t mBatchLevel;
+ // Current active transaction for a batch.
+ mozStorageTransaction* mBatchDBTransaction;
+
+ // better alternative to QueryStringToQueries (in nsNavHistoryQuery.cpp)
+ nsresult QueryStringToQueryArray(const nsACString& aQueryString,
+ nsCOMArray<nsNavHistoryQuery>* aQueries,
+ nsNavHistoryQueryOptions** aOptions);
+
+ typedef nsDataHashtable<nsCStringHashKey, nsCString> StringHash;
+
+ /**
+ * Indicates if it is OK to notify history observers or not.
+ *
+ * @return true if it is OK to notify, false otherwise.
+ */
+ bool canNotify() { return mCanNotify; }
+
+ enum RecentEventFlags {
+ RECENT_TYPED = 1 << 0, // User typed in URL recently
+ RECENT_ACTIVATED = 1 << 1, // User tapped URL link recently
+ RECENT_BOOKMARKED = 1 << 2 // User bookmarked URL recently
+ };
+
+ /**
+ * Returns any recent activity done with a URL.
+ * @return Any recent events associated with this URI. Each bit is set
+ * according to RecentEventFlags enum values.
+ */
+ uint32_t GetRecentFlags(nsIURI *aURI);
+
+ /**
+ * Registers a TRANSITION_EMBED visit for the session.
+ *
+ * @param aURI
+ * URI of the page.
+ * @param aTime
+ * Visit time. Only the last registered visit time is retained.
+ */
+ void registerEmbedVisit(nsIURI* aURI, int64_t aTime);
+
+ /**
+ * Returns whether the specified url has a embed visit.
+ *
+ * @param aURI
+ * URI of the page.
+ * @return whether the page has a embed visit.
+ */
+ bool hasEmbedVisit(nsIURI* aURI);
+
+ /**
+ * Clears all registered embed visits.
+ */
+ void clearEmbedVisits();
+
+ int32_t GetFrecencyAgedWeight(int32_t aAgeInDays) const
+ {
+ if (aAgeInDays <= mFirstBucketCutoffInDays) {
+ return mFirstBucketWeight;
+ }
+ if (aAgeInDays <= mSecondBucketCutoffInDays) {
+ return mSecondBucketWeight;
+ }
+ if (aAgeInDays <= mThirdBucketCutoffInDays) {
+ return mThirdBucketWeight;
+ }
+ if (aAgeInDays <= mFourthBucketCutoffInDays) {
+ return mFourthBucketWeight;
+ }
+ return mDefaultWeight;
+ }
+
+ int32_t GetFrecencyBucketWeight(int32_t aBucketIndex) const
+ {
+ switch(aBucketIndex) {
+ case 1:
+ return mFirstBucketWeight;
+ case 2:
+ return mSecondBucketWeight;
+ case 3:
+ return mThirdBucketWeight;
+ case 4:
+ return mFourthBucketWeight;
+ default:
+ return mDefaultWeight;
+ }
+ }
+
+ int32_t GetFrecencyTransitionBonus(int32_t aTransitionType,
+ bool aVisited) const
+ {
+ switch (aTransitionType) {
+ case nsINavHistoryService::TRANSITION_EMBED:
+ return mEmbedVisitBonus;
+ case nsINavHistoryService::TRANSITION_FRAMED_LINK:
+ return mFramedLinkVisitBonus;
+ case nsINavHistoryService::TRANSITION_LINK:
+ return mLinkVisitBonus;
+ case nsINavHistoryService::TRANSITION_TYPED:
+ return aVisited ? mTypedVisitBonus : mUnvisitedTypedBonus;
+ case nsINavHistoryService::TRANSITION_BOOKMARK:
+ return aVisited ? mBookmarkVisitBonus : mUnvisitedBookmarkBonus;
+ case nsINavHistoryService::TRANSITION_DOWNLOAD:
+ return mDownloadVisitBonus;
+ case nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT:
+ return mPermRedirectVisitBonus;
+ case nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY:
+ return mTempRedirectVisitBonus;
+ case nsINavHistoryService::TRANSITION_RELOAD:
+ return mReloadVisitBonus;
+ default:
+ // 0 == undefined (see bug #375777 for details)
+ NS_WARNING_ASSERTION(!aTransitionType,
+ "new transition but no bonus for frecency");
+ return mDefaultVisitBonus;
+ }
+ }
+
+ int32_t GetNumVisitsForFrecency() const
+ {
+ return mNumVisitsForFrecency;
+ }
+
+ /**
+ * Fires onVisit event to nsINavHistoryService observers
+ */
+ void NotifyOnVisit(nsIURI* aURI,
+ int64_t aVisitId,
+ PRTime aTime,
+ int64_t aReferrerVisitId,
+ int32_t aTransitionType,
+ const nsACString& aGuid,
+ bool aHidden,
+ uint32_t aVisitCount,
+ uint32_t aTyped);
+
+ /**
+ * Fires onTitleChanged event to nsINavHistoryService observers
+ */
+ void NotifyTitleChange(nsIURI* aURI,
+ const nsString& title,
+ const nsACString& aGUID);
+
+ /**
+ * Fires onFrecencyChanged event to nsINavHistoryService observers
+ */
+ void NotifyFrecencyChanged(nsIURI* aURI,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate);
+
+ /**
+ * Fires onManyFrecenciesChanged event to nsINavHistoryService observers
+ */
+ void NotifyManyFrecenciesChanged();
+
+ /**
+ * Posts a runnable to the main thread that calls NotifyFrecencyChanged.
+ */
+ void DispatchFrecencyChangedNotification(const nsACString& aSpec,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate) const;
+
+ /**
+ * Store last insterted id for a table.
+ */
+ static mozilla::Atomic<int64_t> sLastInsertedPlaceId;
+ static mozilla::Atomic<int64_t> sLastInsertedVisitId;
+
+ static void StoreLastInsertedId(const nsACString& aTable,
+ const int64_t aLastInsertedId);
+
+ bool isBatching() {
+ return mBatchLevel > 0;
+ }
+
+private:
+ ~nsNavHistory();
+
+ // used by GetHistoryService
+ static nsNavHistory *gHistoryService;
+
+protected:
+
+ // Database handle.
+ RefPtr<mozilla::places::Database> mDB;
+
+ /**
+ * Decays frecency and inputhistory values. Runs on idle-daily.
+ */
+ nsresult DecayFrecency();
+
+ nsresult RemovePagesInternal(const nsCString& aPlaceIdsQueryString);
+ nsresult CleanupPlacesOnVisitsDelete(const nsCString& aPlaceIdsQueryString);
+
+ /**
+ * Loads all of the preferences that we use into member variables.
+ *
+ * @note If mPrefBranch is nullptr, this does nothing.
+ */
+ void LoadPrefs();
+
+ /**
+ * Calculates and returns value for mCachedNow.
+ * This is an hack to avoid calling PR_Now() too often, as is the case when
+ * we're asked the ageindays of many history entries in a row. A timer is
+ * set which will clear our valid flag after a short timeout.
+ */
+ PRTime GetNow();
+ PRTime mCachedNow;
+ nsCOMPtr<nsITimer> mExpireNowTimer;
+ /**
+ * Called when the cached now value is expired and needs renewal.
+ */
+ static void expireNowTimerCallback(nsITimer* aTimer, void* aClosure);
+
+ nsresult ConstructQueryString(const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions,
+ nsCString& queryString,
+ bool& aParamsPresent,
+ StringHash& aAddParams);
+
+ nsresult QueryToSelectClause(nsNavHistoryQuery* aQuery,
+ nsNavHistoryQueryOptions* aOptions,
+ int32_t aQueryIndex,
+ nsCString* aClause);
+ nsresult BindQueryClauseParameters(mozIStorageBaseStatement* statement,
+ int32_t aQueryIndex,
+ nsNavHistoryQuery* aQuery,
+ nsNavHistoryQueryOptions* aOptions);
+
+ nsresult ResultsAsList(mozIStorageStatement* statement,
+ nsNavHistoryQueryOptions* aOptions,
+ nsCOMArray<nsNavHistoryResultNode>* aResults);
+
+ void TitleForDomain(const nsCString& domain, nsACString& aTitle);
+
+ nsresult FilterResultSet(nsNavHistoryQueryResultNode *aParentNode,
+ const nsCOMArray<nsNavHistoryResultNode>& aSet,
+ nsCOMArray<nsNavHistoryResultNode>* aFiltered,
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions);
+
+ // observers
+ nsMaybeWeakPtrArray<nsINavHistoryObserver> mObservers;
+
+ // effective tld service
+ nsCOMPtr<nsIEffectiveTLDService> mTLDService;
+ nsCOMPtr<nsIIDNService> mIDNService;
+
+ // localization
+ nsCOMPtr<nsIStringBundle> mBundle;
+ nsCOMPtr<nsIStringBundle> mDateFormatBundle;
+ nsCOMPtr<nsICollation> mCollation;
+
+ // recent events
+ typedef nsDataHashtable<nsCStringHashKey, int64_t> RecentEventHash;
+ RecentEventHash mRecentTyped;
+ RecentEventHash mRecentLink;
+ RecentEventHash mRecentBookmark;
+
+ // Embed visits tracking.
+ class VisitHashKey : public nsURIHashKey
+ {
+ public:
+ explicit VisitHashKey(const nsIURI* aURI)
+ : nsURIHashKey(aURI)
+ {
+ }
+ VisitHashKey(const VisitHashKey& aOther)
+ : nsURIHashKey(aOther)
+ {
+ NS_NOTREACHED("Do not call me!");
+ }
+ PRTime visitTime;
+ };
+
+ nsTHashtable<VisitHashKey> mEmbedVisits;
+
+ bool CheckIsRecentEvent(RecentEventHash* hashTable,
+ const nsACString& url);
+ void ExpireNonrecentEvents(RecentEventHash* hashTable);
+
+ nsresult AutoCompleteFeedback(int32_t aIndex,
+ nsIAutoCompleteController *aController);
+
+ // Whether history is enabled or not.
+ // Will mimic value of the places.history.enabled preference.
+ bool mHistoryEnabled;
+
+ // Frecency preferences.
+ int32_t mNumVisitsForFrecency;
+ int32_t mFirstBucketCutoffInDays;
+ int32_t mSecondBucketCutoffInDays;
+ int32_t mThirdBucketCutoffInDays;
+ int32_t mFourthBucketCutoffInDays;
+ int32_t mFirstBucketWeight;
+ int32_t mSecondBucketWeight;
+ int32_t mThirdBucketWeight;
+ int32_t mFourthBucketWeight;
+ int32_t mDefaultWeight;
+ int32_t mEmbedVisitBonus;
+ int32_t mFramedLinkVisitBonus;
+ int32_t mLinkVisitBonus;
+ int32_t mTypedVisitBonus;
+ int32_t mBookmarkVisitBonus;
+ int32_t mDownloadVisitBonus;
+ int32_t mPermRedirectVisitBonus;
+ int32_t mTempRedirectVisitBonus;
+ int32_t mDefaultVisitBonus;
+ int32_t mUnvisitedBookmarkBonus;
+ int32_t mUnvisitedTypedBonus;
+ int32_t mReloadVisitBonus;
+
+ // in nsNavHistoryQuery.cpp
+ nsresult TokensToQueries(const nsTArray<QueryKeyValuePair>& aTokens,
+ nsCOMArray<nsNavHistoryQuery>* aQueries,
+ nsNavHistoryQueryOptions* aOptions);
+
+ int64_t mTagsFolder;
+
+ int32_t mDaysOfHistory;
+ int64_t mLastCachedStartOfDay;
+ int64_t mLastCachedEndOfDay;
+
+ // Used to enable and disable the observer notifications
+ bool mCanNotify;
+ nsCategoryCache<nsINavHistoryObserver> mCacheObservers;
+};
+
+
+#define PLACES_URI_PREFIX "place:"
+
+/* Returns true if the given URI represents a history query. */
+inline bool IsQueryURI(const nsCString &uri)
+{
+ return StringBeginsWith(uri, NS_LITERAL_CSTRING(PLACES_URI_PREFIX));
+}
+
+/* Extracts the query string from a query URI. */
+inline const nsDependentCSubstring QueryURIToQuery(const nsCString &uri)
+{
+ NS_ASSERTION(IsQueryURI(uri), "should only be called for query URIs");
+ return Substring(uri, NS_LITERAL_CSTRING(PLACES_URI_PREFIX).Length());
+}
+
+#endif // nsNavHistory_h_
diff --git a/components/places/src/nsNavHistoryQuery.cpp b/components/places/src/nsNavHistoryQuery.cpp
new file mode 100644
index 000000000..1a7b1c239
--- /dev/null
+++ b/components/places/src/nsNavHistoryQuery.cpp
@@ -0,0 +1,1694 @@
+//* -*- 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 file contains the definitions of nsNavHistoryQuery,
+ * nsNavHistoryQueryOptions, and those functions in nsINavHistory that directly
+ * support queries (specifically QueryStringToQueries and QueriesToQueryString).
+ */
+
+#include "mozilla/DebugOnly.h"
+
+#include "nsNavHistory.h"
+#include "nsNavBookmarks.h"
+#include "nsEscape.h"
+#include "nsCOMArray.h"
+#include "nsNetUtil.h"
+#include "nsTArray.h"
+#include "prprf.h"
+#include "nsVariant.h"
+
+using namespace mozilla;
+
+class QueryKeyValuePair
+{
+public:
+
+ // QueryKeyValuePair
+ //
+ // 01234567890
+ // input : qwerty&key=value&qwerty
+ // ^ ^ ^
+ // aKeyBegin | aPastEnd (may point to null terminator)
+ // aEquals
+ //
+ // Special case: if aKeyBegin == aEquals, then there is only one string
+ // and no equal sign, so we treat the entire thing as a key with no value
+
+ QueryKeyValuePair(const nsCSubstring& aSource, int32_t aKeyBegin,
+ int32_t aEquals, int32_t aPastEnd)
+ {
+ if (aEquals == aKeyBegin)
+ aEquals = aPastEnd;
+ key = Substring(aSource, aKeyBegin, aEquals - aKeyBegin);
+ if (aPastEnd - aEquals > 0)
+ value = Substring(aSource, aEquals + 1, aPastEnd - aEquals - 1);
+ }
+ nsCString key;
+ nsCString value;
+};
+
+static nsresult TokenizeQueryString(const nsACString& aQuery,
+ nsTArray<QueryKeyValuePair>* aTokens);
+static nsresult ParseQueryBooleanString(const nsCString& aString,
+ bool* aValue);
+
+// query getters
+typedef NS_STDCALL_FUNCPROTO(nsresult, BoolQueryGetter, nsINavHistoryQuery,
+ GetOnlyBookmarked, (bool*));
+typedef NS_STDCALL_FUNCPROTO(nsresult, Uint32QueryGetter, nsINavHistoryQuery,
+ GetBeginTimeReference, (uint32_t*));
+typedef NS_STDCALL_FUNCPROTO(nsresult, Int64QueryGetter, nsINavHistoryQuery,
+ GetBeginTime, (int64_t*));
+static void AppendBoolKeyValueIfTrue(nsACString& aString,
+ const nsCString& aName,
+ nsINavHistoryQuery* aQuery,
+ BoolQueryGetter getter);
+static void AppendUint32KeyValueIfNonzero(nsACString& aString,
+ const nsCString& aName,
+ nsINavHistoryQuery* aQuery,
+ Uint32QueryGetter getter);
+static void AppendInt64KeyValueIfNonzero(nsACString& aString,
+ const nsCString& aName,
+ nsINavHistoryQuery* aQuery,
+ Int64QueryGetter getter);
+
+// query setters
+typedef NS_STDCALL_FUNCPROTO(nsresult, BoolQuerySetter, nsINavHistoryQuery,
+ SetOnlyBookmarked, (bool));
+typedef NS_STDCALL_FUNCPROTO(nsresult, Uint32QuerySetter, nsINavHistoryQuery,
+ SetBeginTimeReference, (uint32_t));
+typedef NS_STDCALL_FUNCPROTO(nsresult, Int64QuerySetter, nsINavHistoryQuery,
+ SetBeginTime, (int64_t));
+static void SetQueryKeyBool(const nsCString& aValue, nsINavHistoryQuery* aQuery,
+ BoolQuerySetter setter);
+static void SetQueryKeyUint32(const nsCString& aValue, nsINavHistoryQuery* aQuery,
+ Uint32QuerySetter setter);
+static void SetQueryKeyInt64(const nsCString& aValue, nsINavHistoryQuery* aQuery,
+ Int64QuerySetter setter);
+
+// options setters
+typedef NS_STDCALL_FUNCPROTO(nsresult, BoolOptionsSetter,
+ nsINavHistoryQueryOptions,
+ SetExpandQueries, (bool));
+typedef NS_STDCALL_FUNCPROTO(nsresult, Uint32OptionsSetter,
+ nsINavHistoryQueryOptions,
+ SetMaxResults, (uint32_t));
+typedef NS_STDCALL_FUNCPROTO(nsresult, Uint16OptionsSetter,
+ nsINavHistoryQueryOptions,
+ SetResultType, (uint16_t));
+static void SetOptionsKeyBool(const nsCString& aValue,
+ nsINavHistoryQueryOptions* aOptions,
+ BoolOptionsSetter setter);
+static void SetOptionsKeyUint16(const nsCString& aValue,
+ nsINavHistoryQueryOptions* aOptions,
+ Uint16OptionsSetter setter);
+static void SetOptionsKeyUint32(const nsCString& aValue,
+ nsINavHistoryQueryOptions* aOptions,
+ Uint32OptionsSetter setter);
+
+// Components of a query string.
+// Note that query strings are also generated in nsNavBookmarks::GetFolderURI
+// for performance reasons, so if you change these values, change that, too.
+#define QUERYKEY_BEGIN_TIME "beginTime"
+#define QUERYKEY_BEGIN_TIME_REFERENCE "beginTimeRef"
+#define QUERYKEY_END_TIME "endTime"
+#define QUERYKEY_END_TIME_REFERENCE "endTimeRef"
+#define QUERYKEY_SEARCH_TERMS "terms"
+#define QUERYKEY_MIN_VISITS "minVisits"
+#define QUERYKEY_MAX_VISITS "maxVisits"
+#define QUERYKEY_ONLY_BOOKMARKED "onlyBookmarked"
+#define QUERYKEY_DOMAIN_IS_HOST "domainIsHost"
+#define QUERYKEY_DOMAIN "domain"
+#define QUERYKEY_FOLDER "folder"
+#define QUERYKEY_NOTANNOTATION "!annotation"
+#define QUERYKEY_ANNOTATION "annotation"
+#define QUERYKEY_URI "uri"
+#define QUERYKEY_SEPARATOR "OR"
+#define QUERYKEY_GROUP "group"
+#define QUERYKEY_SORT "sort"
+#define QUERYKEY_SORTING_ANNOTATION "sortingAnnotation"
+#define QUERYKEY_RESULT_TYPE "type"
+#define QUERYKEY_EXCLUDE_ITEMS "excludeItems"
+#define QUERYKEY_EXCLUDE_QUERIES "excludeQueries"
+#define QUERYKEY_EXCLUDE_READ_ONLY_FOLDERS "excludeReadOnlyFolders"
+#define QUERYKEY_EXPAND_QUERIES "expandQueries"
+#define QUERYKEY_FORCE_ORIGINAL_TITLE "originalTitle"
+#define QUERYKEY_INCLUDE_HIDDEN "includeHidden"
+#define QUERYKEY_MAX_RESULTS "maxResults"
+#define QUERYKEY_QUERY_TYPE "queryType"
+#define QUERYKEY_TAG "tag"
+#define QUERYKEY_NOTTAGS "!tags"
+#define QUERYKEY_ASYNC_ENABLED "asyncEnabled"
+#define QUERYKEY_TRANSITION "transition"
+
+inline void AppendAmpersandIfNonempty(nsACString& aString)
+{
+ if (! aString.IsEmpty())
+ aString.Append('&');
+}
+inline void AppendInt16(nsACString& str, int16_t i)
+{
+ nsAutoCString tmp;
+ tmp.AppendInt(i);
+ str.Append(tmp);
+}
+inline void AppendInt32(nsACString& str, int32_t i)
+{
+ nsAutoCString tmp;
+ tmp.AppendInt(i);
+ str.Append(tmp);
+}
+inline void AppendInt64(nsACString& str, int64_t i)
+{
+ nsCString tmp;
+ tmp.AppendInt(i);
+ str.Append(tmp);
+}
+
+namespace PlacesFolderConversion {
+ #define PLACES_ROOT_FOLDER "PLACES_ROOT"
+ #define BOOKMARKS_MENU_FOLDER "BOOKMARKS_MENU"
+ #define TAGS_FOLDER "TAGS"
+ #define UNFILED_BOOKMARKS_FOLDER "UNFILED_BOOKMARKS"
+ #define TOOLBAR_FOLDER "TOOLBAR"
+ #define MOBILE_BOOKMARKS_FOLDER "MOBILE_BOOKMARKS"
+
+ /**
+ * Converts a folder name to a folder id.
+ *
+ * @param aName
+ * The name of the folder to convert to a folder id.
+ * @returns the folder id if aName is a recognizable name, -1 otherwise.
+ */
+ inline int64_t DecodeFolder(const nsCString &aName)
+ {
+ nsNavBookmarks *bs = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bs, false);
+ int64_t folderID = -1;
+
+ if (aName.EqualsLiteral(PLACES_ROOT_FOLDER))
+ (void)bs->GetPlacesRoot(&folderID);
+ else if (aName.EqualsLiteral(BOOKMARKS_MENU_FOLDER))
+ (void)bs->GetBookmarksMenuFolder(&folderID);
+ else if (aName.EqualsLiteral(TAGS_FOLDER))
+ (void)bs->GetTagsFolder(&folderID);
+ else if (aName.EqualsLiteral(UNFILED_BOOKMARKS_FOLDER))
+ (void)bs->GetUnfiledBookmarksFolder(&folderID);
+ else if (aName.EqualsLiteral(TOOLBAR_FOLDER))
+ (void)bs->GetToolbarFolder(&folderID);
+ else if (aName.EqualsLiteral(MOBILE_BOOKMARKS_FOLDER))
+ (void)bs->GetMobileFolder(&folderID);
+
+ return folderID;
+ }
+
+ /**
+ * Converts a folder id to a named constant, or a string representation of the
+ * folder id if there is no named constant for the folder, and appends it to
+ * aQuery.
+ *
+ * @param aQuery
+ * The string to append the folder string to. This is generally a
+ * query string, but could really be anything.
+ * @param aFolderID
+ * The folder ID to convert to the proper named constant.
+ */
+ inline nsresult AppendFolder(nsCString &aQuery, int64_t aFolderID)
+ {
+ nsNavBookmarks *bs = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_STATE(bs);
+ int64_t folderID;
+
+ if (NS_SUCCEEDED(bs->GetPlacesRoot(&folderID)) &&
+ aFolderID == folderID) {
+ aQuery.AppendLiteral(PLACES_ROOT_FOLDER);
+ }
+ else if (NS_SUCCEEDED(bs->GetBookmarksMenuFolder(&folderID)) &&
+ aFolderID == folderID) {
+ aQuery.AppendLiteral(BOOKMARKS_MENU_FOLDER);
+ }
+ else if (NS_SUCCEEDED(bs->GetTagsFolder(&folderID)) &&
+ aFolderID == folderID) {
+ aQuery.AppendLiteral(TAGS_FOLDER);
+ }
+ else if (NS_SUCCEEDED(bs->GetUnfiledBookmarksFolder(&folderID)) &&
+ aFolderID == folderID) {
+ aQuery.AppendLiteral(UNFILED_BOOKMARKS_FOLDER);
+ }
+ else if (NS_SUCCEEDED(bs->GetToolbarFolder(&folderID)) &&
+ aFolderID == folderID) {
+ aQuery.AppendLiteral(TOOLBAR_FOLDER);
+ }
+ else if (NS_SUCCEEDED(bs->GetMobileFolder(&folderID)) &&
+ aFolderID == folderID) {
+ aQuery.AppendLiteral(MOBILE_BOOKMARKS_FOLDER);
+ }
+ else {
+ // It wasn't one of our named constants, so just convert it to a string.
+ aQuery.AppendInt(aFolderID);
+ }
+
+ return NS_OK;
+ }
+} // namespace PlacesFolderConversion
+
+// nsNavHistory::QueryStringToQueries
+//
+// From C++ places code, you should use QueryStringToQueryArray, this is
+// the harder-to-use XPCOM version.
+
+NS_IMETHODIMP
+nsNavHistory::QueryStringToQueries(const nsACString& aQueryString,
+ nsINavHistoryQuery*** aQueries,
+ uint32_t* aResultCount,
+ nsINavHistoryQueryOptions** aOptions)
+{
+ NS_ENSURE_ARG_POINTER(aQueries);
+ NS_ENSURE_ARG_POINTER(aResultCount);
+ NS_ENSURE_ARG_POINTER(aOptions);
+
+ *aQueries = nullptr;
+ *aResultCount = 0;
+ nsCOMPtr<nsNavHistoryQueryOptions> options;
+ nsCOMArray<nsNavHistoryQuery> queries;
+ nsresult rv = QueryStringToQueryArray(aQueryString, &queries,
+ getter_AddRefs(options));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResultCount = queries.Count();
+ if (queries.Count() > 0) {
+ // convert COM array to raw
+ *aQueries = static_cast<nsINavHistoryQuery**>
+ (moz_xmalloc(sizeof(nsINavHistoryQuery*) * queries.Count()));
+ NS_ENSURE_TRUE(*aQueries, NS_ERROR_OUT_OF_MEMORY);
+ for (int32_t i = 0; i < queries.Count(); i ++) {
+ (*aQueries)[i] = queries[i];
+ NS_ADDREF((*aQueries)[i]);
+ }
+ }
+ options.forget(aOptions);
+ return NS_OK;
+}
+
+
+// nsNavHistory::QueryStringToQueryArray
+//
+// An internal version of QueryStringToQueries that fills a COM array for
+// ease-of-use.
+
+nsresult
+nsNavHistory::QueryStringToQueryArray(const nsACString& aQueryString,
+ nsCOMArray<nsNavHistoryQuery>* aQueries,
+ nsNavHistoryQueryOptions** aOptions)
+{
+ nsresult rv;
+ aQueries->Clear();
+ *aOptions = nullptr;
+
+ RefPtr<nsNavHistoryQueryOptions> options(new nsNavHistoryQueryOptions());
+ if (! options)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsTArray<QueryKeyValuePair> tokens;
+ rv = TokenizeQueryString(aQueryString, &tokens);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = TokensToQueries(tokens, aQueries, options);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unable to parse the query string: ");
+ NS_WARNING(PromiseFlatCString(aQueryString).get());
+ return rv;
+ }
+
+ options.forget(aOptions);
+ return NS_OK;
+}
+
+
+// nsNavHistory::QueriesToQueryString
+
+NS_IMETHODIMP
+nsNavHistory::QueriesToQueryString(nsINavHistoryQuery **aQueries,
+ uint32_t aQueryCount,
+ nsINavHistoryQueryOptions* aOptions,
+ nsACString& aQueryString)
+{
+ NS_ENSURE_ARG(aQueries);
+ NS_ENSURE_ARG(aOptions);
+
+ nsCOMPtr<nsNavHistoryQueryOptions> options = do_QueryInterface(aOptions);
+ NS_ENSURE_TRUE(options, NS_ERROR_INVALID_ARG);
+
+ nsAutoCString queryString;
+ for (uint32_t queryIndex = 0; queryIndex < aQueryCount; queryIndex ++) {
+ nsCOMPtr<nsNavHistoryQuery> query = do_QueryInterface(aQueries[queryIndex]);
+ if (queryIndex > 0) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_SEPARATOR);
+ }
+
+ bool hasIt;
+
+ // begin time
+ query->GetHasBeginTime(&hasIt);
+ if (hasIt) {
+ AppendInt64KeyValueIfNonzero(queryString,
+ NS_LITERAL_CSTRING(QUERYKEY_BEGIN_TIME),
+ query, &nsINavHistoryQuery::GetBeginTime);
+ AppendUint32KeyValueIfNonzero(queryString,
+ NS_LITERAL_CSTRING(QUERYKEY_BEGIN_TIME_REFERENCE),
+ query, &nsINavHistoryQuery::GetBeginTimeReference);
+ }
+
+ // end time
+ query->GetHasEndTime(&hasIt);
+ if (hasIt) {
+ AppendInt64KeyValueIfNonzero(queryString,
+ NS_LITERAL_CSTRING(QUERYKEY_END_TIME),
+ query, &nsINavHistoryQuery::GetEndTime);
+ AppendUint32KeyValueIfNonzero(queryString,
+ NS_LITERAL_CSTRING(QUERYKEY_END_TIME_REFERENCE),
+ query, &nsINavHistoryQuery::GetEndTimeReference);
+ }
+
+ // search terms
+ query->GetHasSearchTerms(&hasIt);
+ if (hasIt) {
+ nsAutoString searchTerms;
+ query->GetSearchTerms(searchTerms);
+ nsCString escapedTerms;
+ if (! NS_Escape(NS_ConvertUTF16toUTF8(searchTerms), escapedTerms,
+ url_XAlphas))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_SEARCH_TERMS "=");
+ queryString += escapedTerms;
+ }
+
+ // min and max visits
+ int32_t minVisits;
+ if (NS_SUCCEEDED(query->GetMinVisits(&minVisits)) && minVisits >= 0) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString.AppendLiteral(QUERYKEY_MIN_VISITS "=");
+ AppendInt32(queryString, minVisits);
+ }
+
+ int32_t maxVisits;
+ if (NS_SUCCEEDED(query->GetMaxVisits(&maxVisits)) && maxVisits >= 0) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString.AppendLiteral(QUERYKEY_MAX_VISITS "=");
+ AppendInt32(queryString, maxVisits);
+ }
+
+ // only bookmarked
+ AppendBoolKeyValueIfTrue(queryString,
+ NS_LITERAL_CSTRING(QUERYKEY_ONLY_BOOKMARKED),
+ query, &nsINavHistoryQuery::GetOnlyBookmarked);
+
+ // domain (+ is host), only call if hasDomain, which means non-IsVoid
+ // this means we may get an empty string for the domain in the result,
+ // which is valid
+ query->GetHasDomain(&hasIt);
+ if (hasIt) {
+ AppendBoolKeyValueIfTrue(queryString,
+ NS_LITERAL_CSTRING(QUERYKEY_DOMAIN_IS_HOST),
+ query, &nsINavHistoryQuery::GetDomainIsHost);
+ nsAutoCString domain;
+ nsresult rv = query->GetDomain(domain);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString escapedDomain;
+ bool success = NS_Escape(domain, escapedDomain, url_XAlphas);
+ NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+
+ AppendAmpersandIfNonempty(queryString);
+ queryString.AppendLiteral(QUERYKEY_DOMAIN "=");
+ queryString.Append(escapedDomain);
+ }
+
+ // uri
+ query->GetHasUri(&hasIt);
+ if (hasIt) {
+ nsCOMPtr<nsIURI> uri;
+ query->GetUri(getter_AddRefs(uri));
+ NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); // hasURI should tell is if invalid
+ nsAutoCString uriSpec;
+ nsresult rv = uri->GetSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString escaped;
+ bool success = NS_Escape(uriSpec, escaped, url_XAlphas);
+ NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+
+ AppendAmpersandIfNonempty(queryString);
+ queryString.AppendLiteral(QUERYKEY_URI "=");
+ queryString.Append(escaped);
+ }
+
+ // annotation
+ query->GetHasAnnotation(&hasIt);
+ if (hasIt) {
+ AppendAmpersandIfNonempty(queryString);
+ bool annotationIsNot;
+ query->GetAnnotationIsNot(&annotationIsNot);
+ if (annotationIsNot)
+ queryString.AppendLiteral(QUERYKEY_NOTANNOTATION "=");
+ else
+ queryString.AppendLiteral(QUERYKEY_ANNOTATION "=");
+ nsAutoCString annot;
+ query->GetAnnotation(annot);
+ nsAutoCString escaped;
+ bool success = NS_Escape(annot, escaped, url_XAlphas);
+ NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
+ queryString.Append(escaped);
+ }
+
+ // folders
+ int64_t *folders = nullptr;
+ uint32_t folderCount = 0;
+ query->GetFolders(&folderCount, &folders);
+ for (uint32_t i = 0; i < folderCount; ++i) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_FOLDER "=");
+ nsresult rv = PlacesFolderConversion::AppendFolder(queryString, folders[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ free(folders);
+
+ // tags
+ const nsTArray<nsString> &tags = query->Tags();
+ for (uint32_t i = 0; i < tags.Length(); ++i) {
+ nsAutoCString escapedTag;
+ if (!NS_Escape(NS_ConvertUTF16toUTF8(tags[i]), escapedTag, url_XAlphas))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_TAG "=");
+ queryString += escapedTag;
+ }
+ AppendBoolKeyValueIfTrue(queryString,
+ NS_LITERAL_CSTRING(QUERYKEY_NOTTAGS),
+ query,
+ &nsINavHistoryQuery::GetTagsAreNot);
+
+ // transitions
+ const nsTArray<uint32_t>& transitions = query->Transitions();
+ for (uint32_t i = 0; i < transitions.Length(); ++i) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_TRANSITION "=");
+ AppendInt64(queryString, transitions[i]);
+ }
+ }
+
+ // sorting
+ if (options->SortingMode() != nsINavHistoryQueryOptions::SORT_BY_NONE) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_SORT "=");
+ AppendInt16(queryString, options->SortingMode());
+ if (options->SortingMode() == nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_DESCENDING ||
+ options->SortingMode() == nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_ASCENDING) {
+ // sortingAnnotation
+ nsAutoCString sortingAnnotation;
+ if (NS_SUCCEEDED(options->GetSortingAnnotation(sortingAnnotation))) {
+ nsCString escaped;
+ if (!NS_Escape(sortingAnnotation, escaped, url_XAlphas))
+ return NS_ERROR_OUT_OF_MEMORY;
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_SORTING_ANNOTATION "=");
+ queryString.Append(escaped);
+ }
+ }
+ }
+
+ // result type
+ if (options->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_URI) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_RESULT_TYPE "=");
+ AppendInt16(queryString, options->ResultType());
+ }
+
+ // exclude items
+ if (options->ExcludeItems()) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_EXCLUDE_ITEMS "=1");
+ }
+
+ // exclude queries
+ if (options->ExcludeQueries()) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_EXCLUDE_QUERIES "=1");
+ }
+
+ // exclude read only folders
+ if (options->ExcludeReadOnlyFolders()) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_EXCLUDE_READ_ONLY_FOLDERS "=1");
+ }
+
+ // expand queries
+ if (!options->ExpandQueries()) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_EXPAND_QUERIES "=0");
+ }
+
+ // include hidden
+ if (options->IncludeHidden()) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_INCLUDE_HIDDEN "=1");
+ }
+
+ // max results
+ if (options->MaxResults()) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_MAX_RESULTS "=");
+ AppendInt32(queryString, options->MaxResults());
+ }
+
+ // queryType
+ if (options->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_QUERY_TYPE "=");
+ AppendInt16(queryString, options->QueryType());
+ }
+
+ // async enabled
+ if (options->AsyncEnabled()) {
+ AppendAmpersandIfNonempty(queryString);
+ queryString += NS_LITERAL_CSTRING(QUERYKEY_ASYNC_ENABLED "=1");
+ }
+
+ aQueryString.AssignLiteral("place:");
+ aQueryString.Append(queryString);
+ return NS_OK;
+}
+
+
+// TokenizeQueryString
+
+nsresult
+TokenizeQueryString(const nsACString& aQuery,
+ nsTArray<QueryKeyValuePair>* aTokens)
+{
+ // Strip off the "place:" prefix
+ const uint32_t prefixlen = 6; // = strlen("place:");
+ nsCString query;
+ if (aQuery.Length() >= prefixlen &&
+ Substring(aQuery, 0, prefixlen).EqualsLiteral("place:"))
+ query = Substring(aQuery, prefixlen);
+ else
+ query = aQuery;
+
+ int32_t keyFirstIndex = 0;
+ int32_t equalsIndex = 0;
+ for (uint32_t i = 0; i < query.Length(); i ++) {
+ if (query[i] == '&') {
+ // new clause, save last one
+ if (i - keyFirstIndex > 1) {
+ if (! aTokens->AppendElement(QueryKeyValuePair(query, keyFirstIndex,
+ equalsIndex, i)))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ keyFirstIndex = equalsIndex = i + 1;
+ } else if (query[i] == '=') {
+ equalsIndex = i;
+ }
+ }
+
+ // handle last pair, if any
+ if (query.Length() - keyFirstIndex > 1) {
+ if (! aTokens->AppendElement(QueryKeyValuePair(query, keyFirstIndex,
+ equalsIndex, query.Length())))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+// nsNavHistory::TokensToQueries
+
+nsresult
+nsNavHistory::TokensToQueries(const nsTArray<QueryKeyValuePair>& aTokens,
+ nsCOMArray<nsNavHistoryQuery>* aQueries,
+ nsNavHistoryQueryOptions* aOptions)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsNavHistoryQuery> query(new nsNavHistoryQuery());
+ if (! query)
+ return NS_ERROR_OUT_OF_MEMORY;
+ if (! aQueries->AppendObject(query))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (aTokens.Length() == 0)
+ return NS_OK; // nothing to do
+
+ nsTArray<int64_t> folders;
+ nsTArray<nsString> tags;
+ nsTArray<uint32_t> transitions;
+ for (uint32_t i = 0; i < aTokens.Length(); i ++) {
+ const QueryKeyValuePair& kvp = aTokens[i];
+
+ // begin time
+ if (kvp.key.EqualsLiteral(QUERYKEY_BEGIN_TIME)) {
+ SetQueryKeyInt64(kvp.value, query, &nsINavHistoryQuery::SetBeginTime);
+
+ // begin time reference
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_BEGIN_TIME_REFERENCE)) {
+ SetQueryKeyUint32(kvp.value, query, &nsINavHistoryQuery::SetBeginTimeReference);
+
+ // end time
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_END_TIME)) {
+ SetQueryKeyInt64(kvp.value, query, &nsINavHistoryQuery::SetEndTime);
+
+ // end time reference
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_END_TIME_REFERENCE)) {
+ SetQueryKeyUint32(kvp.value, query, &nsINavHistoryQuery::SetEndTimeReference);
+
+ // search terms
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_SEARCH_TERMS)) {
+ nsCString unescapedTerms = kvp.value;
+ NS_UnescapeURL(unescapedTerms); // modifies input
+ rv = query->SetSearchTerms(NS_ConvertUTF8toUTF16(unescapedTerms));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // min visits
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_MIN_VISITS)) {
+ int32_t visits = kvp.value.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv))
+ query->SetMinVisits(visits);
+ else
+ NS_WARNING("Bad number for minVisits in query");
+
+ // max visits
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_MAX_VISITS)) {
+ int32_t visits = kvp.value.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv))
+ query->SetMaxVisits(visits);
+ else
+ NS_WARNING("Bad number for maxVisits in query");
+
+ // onlyBookmarked flag
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_ONLY_BOOKMARKED)) {
+ SetQueryKeyBool(kvp.value, query, &nsINavHistoryQuery::SetOnlyBookmarked);
+
+ // domainIsHost flag
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_DOMAIN_IS_HOST)) {
+ SetQueryKeyBool(kvp.value, query, &nsINavHistoryQuery::SetDomainIsHost);
+
+ // domain string
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_DOMAIN)) {
+ nsAutoCString unescapedDomain(kvp.value);
+ NS_UnescapeURL(unescapedDomain); // modifies input
+ rv = query->SetDomain(unescapedDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // folders
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_FOLDER)) {
+ int64_t folder;
+ if (PR_sscanf(kvp.value.get(), "%lld", &folder) == 1) {
+ NS_ENSURE_TRUE(folders.AppendElement(folder), NS_ERROR_OUT_OF_MEMORY);
+ } else {
+ folder = PlacesFolderConversion::DecodeFolder(kvp.value);
+ if (folder != -1)
+ NS_ENSURE_TRUE(folders.AppendElement(folder), NS_ERROR_OUT_OF_MEMORY);
+ else
+ NS_WARNING("folders value in query is invalid, ignoring");
+ }
+
+ // uri
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_URI)) {
+ nsAutoCString unescapedUri(kvp.value);
+ NS_UnescapeURL(unescapedUri); // modifies input
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), unescapedUri);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unable to parse URI");
+ }
+ rv = query->SetUri(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // not annotation
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_NOTANNOTATION)) {
+ nsAutoCString unescaped(kvp.value);
+ NS_UnescapeURL(unescaped); // modifies input
+ query->SetAnnotationIsNot(true);
+ query->SetAnnotation(unescaped);
+
+ // annotation
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_ANNOTATION)) {
+ nsAutoCString unescaped(kvp.value);
+ NS_UnescapeURL(unescaped); // modifies input
+ query->SetAnnotationIsNot(false);
+ query->SetAnnotation(unescaped);
+
+ // tag
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_TAG)) {
+ nsAutoCString unescaped(kvp.value);
+ NS_UnescapeURL(unescaped); // modifies input
+ NS_ConvertUTF8toUTF16 tag(unescaped);
+ if (!tags.Contains(tag)) {
+ NS_ENSURE_TRUE(tags.AppendElement(tag), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // not tags
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_NOTTAGS)) {
+ SetQueryKeyBool(kvp.value, query, &nsINavHistoryQuery::SetTagsAreNot);
+
+ // transition
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_TRANSITION)) {
+ uint32_t transition = kvp.value.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ if (!transitions.Contains(transition))
+ NS_ENSURE_TRUE(transitions.AppendElement(transition),
+ NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ NS_WARNING("Invalid Int32 transition value.");
+ }
+
+ // new query component
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_SEPARATOR)) {
+
+ if (folders.Length() != 0) {
+ query->SetFolders(folders.Elements(), folders.Length());
+ folders.Clear();
+ }
+
+ if (tags.Length() > 0) {
+ rv = query->SetTags(tags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ tags.Clear();
+ }
+
+ if (transitions.Length() > 0) {
+ rv = query->SetTransitions(transitions);
+ NS_ENSURE_SUCCESS(rv, rv);
+ transitions.Clear();
+ }
+
+ query = new nsNavHistoryQuery();
+ if (! query)
+ return NS_ERROR_OUT_OF_MEMORY;
+ if (! aQueries->AppendObject(query))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // sorting mode
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_SORT)) {
+ SetOptionsKeyUint16(kvp.value, aOptions,
+ &nsINavHistoryQueryOptions::SetSortingMode);
+ // sorting annotation
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_SORTING_ANNOTATION)) {
+ nsCString sortingAnnotation = kvp.value;
+ NS_UnescapeURL(sortingAnnotation);
+ rv = aOptions->SetSortingAnnotation(sortingAnnotation);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // result type
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_RESULT_TYPE)) {
+ SetOptionsKeyUint16(kvp.value, aOptions,
+ &nsINavHistoryQueryOptions::SetResultType);
+
+ // exclude items
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_EXCLUDE_ITEMS)) {
+ SetOptionsKeyBool(kvp.value, aOptions,
+ &nsINavHistoryQueryOptions::SetExcludeItems);
+
+ // exclude queries
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_EXCLUDE_QUERIES)) {
+ SetOptionsKeyBool(kvp.value, aOptions,
+ &nsINavHistoryQueryOptions::SetExcludeQueries);
+
+ // exclude read only folders
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_EXCLUDE_READ_ONLY_FOLDERS)) {
+ SetOptionsKeyBool(kvp.value, aOptions,
+ &nsINavHistoryQueryOptions::SetExcludeReadOnlyFolders);
+
+ // expand queries
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_EXPAND_QUERIES)) {
+ SetOptionsKeyBool(kvp.value, aOptions,
+ &nsINavHistoryQueryOptions::SetExpandQueries);
+ // include hidden
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_INCLUDE_HIDDEN)) {
+ SetOptionsKeyBool(kvp.value, aOptions,
+ &nsINavHistoryQueryOptions::SetIncludeHidden);
+ // max results
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_MAX_RESULTS)) {
+ SetOptionsKeyUint32(kvp.value, aOptions,
+ &nsINavHistoryQueryOptions::SetMaxResults);
+ // query type
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_QUERY_TYPE)) {
+ SetOptionsKeyUint16(kvp.value, aOptions,
+ &nsINavHistoryQueryOptions::SetQueryType);
+ // async enabled
+ } else if (kvp.key.EqualsLiteral(QUERYKEY_ASYNC_ENABLED)) {
+ SetOptionsKeyBool(kvp.value, aOptions,
+ &nsINavHistoryQueryOptions::SetAsyncEnabled);
+ // unknown key
+ } else {
+ NS_WARNING("TokensToQueries(), ignoring unknown key: ");
+ NS_WARNING(kvp.key.get());
+ }
+ }
+
+ if (folders.Length() != 0)
+ query->SetFolders(folders.Elements(), folders.Length());
+
+ if (tags.Length() > 0) {
+ rv = query->SetTags(tags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (transitions.Length() > 0) {
+ rv = query->SetTransitions(transitions);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+
+// ParseQueryBooleanString
+//
+// Converts a 0/1 or true/false string into a bool
+
+nsresult
+ParseQueryBooleanString(const nsCString& aString, bool* aValue)
+{
+ if (aString.EqualsLiteral("1") || aString.EqualsLiteral("true")) {
+ *aValue = true;
+ return NS_OK;
+ } else if (aString.EqualsLiteral("0") || aString.EqualsLiteral("false")) {
+ *aValue = false;
+ return NS_OK;
+ }
+ return NS_ERROR_INVALID_ARG;
+}
+
+
+// nsINavHistoryQuery **********************************************************
+
+NS_IMPL_ISUPPORTS(nsNavHistoryQuery, nsNavHistoryQuery, nsINavHistoryQuery)
+
+// nsINavHistoryQuery::nsNavHistoryQuery
+//
+// This must initialize the object such that the default values will cause
+// all history to be returned if this query is used. Then the caller can
+// just set the things it's interested in.
+
+nsNavHistoryQuery::nsNavHistoryQuery()
+ : mMinVisits(-1), mMaxVisits(-1), mBeginTime(0),
+ mBeginTimeReference(TIME_RELATIVE_EPOCH),
+ mEndTime(0), mEndTimeReference(TIME_RELATIVE_EPOCH),
+ mOnlyBookmarked(false),
+ mDomainIsHost(false),
+ mAnnotationIsNot(false),
+ mTagsAreNot(false)
+{
+ // differentiate not set (IsVoid) from empty string (local files)
+ mDomain.SetIsVoid(true);
+}
+
+nsNavHistoryQuery::nsNavHistoryQuery(const nsNavHistoryQuery& aOther)
+ : mMinVisits(aOther.mMinVisits), mMaxVisits(aOther.mMaxVisits),
+ mBeginTime(aOther.mBeginTime),
+ mBeginTimeReference(aOther.mBeginTimeReference),
+ mEndTime(aOther.mEndTime), mEndTimeReference(aOther.mEndTimeReference),
+ mSearchTerms(aOther.mSearchTerms), mOnlyBookmarked(aOther.mOnlyBookmarked),
+ mDomainIsHost(aOther.mDomainIsHost), mDomain(aOther.mDomain),
+ mUri(aOther.mUri),
+ mAnnotationIsNot(aOther.mAnnotationIsNot),
+ mAnnotation(aOther.mAnnotation), mTags(aOther.mTags),
+ mTagsAreNot(aOther.mTagsAreNot), mTransitions(aOther.mTransitions)
+{}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetBeginTime(PRTime *aBeginTime)
+{
+ *aBeginTime = mBeginTime;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetBeginTime(PRTime aBeginTime)
+{
+ mBeginTime = aBeginTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetBeginTimeReference(uint32_t* _retval)
+{
+ *_retval = mBeginTimeReference;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetBeginTimeReference(uint32_t aReference)
+{
+ if (aReference > TIME_RELATIVE_NOW)
+ return NS_ERROR_INVALID_ARG;
+ mBeginTimeReference = aReference;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetHasBeginTime(bool* _retval)
+{
+ *_retval = ! (mBeginTimeReference == TIME_RELATIVE_EPOCH && mBeginTime == 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetAbsoluteBeginTime(PRTime* _retval)
+{
+ *_retval = nsNavHistory::NormalizeTime(mBeginTimeReference, mBeginTime);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetEndTime(PRTime *aEndTime)
+{
+ *aEndTime = mEndTime;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetEndTime(PRTime aEndTime)
+{
+ mEndTime = aEndTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetEndTimeReference(uint32_t* _retval)
+{
+ *_retval = mEndTimeReference;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetEndTimeReference(uint32_t aReference)
+{
+ if (aReference > TIME_RELATIVE_NOW)
+ return NS_ERROR_INVALID_ARG;
+ mEndTimeReference = aReference;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetHasEndTime(bool* _retval)
+{
+ *_retval = ! (mEndTimeReference == TIME_RELATIVE_EPOCH && mEndTime == 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetAbsoluteEndTime(PRTime* _retval)
+{
+ *_retval = nsNavHistory::NormalizeTime(mEndTimeReference, mEndTime);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetSearchTerms(nsAString& aSearchTerms)
+{
+ aSearchTerms = mSearchTerms;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetSearchTerms(const nsAString& aSearchTerms)
+{
+ mSearchTerms = aSearchTerms;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::GetHasSearchTerms(bool* _retval)
+{
+ *_retval = (! mSearchTerms.IsEmpty());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetMinVisits(int32_t* _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mMinVisits;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetMinVisits(int32_t aVisits)
+{
+ mMinVisits = aVisits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetMaxVisits(int32_t* _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mMaxVisits;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetMaxVisits(int32_t aVisits)
+{
+ mMaxVisits = aVisits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetOnlyBookmarked(bool *aOnlyBookmarked)
+{
+ *aOnlyBookmarked = mOnlyBookmarked;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetOnlyBookmarked(bool aOnlyBookmarked)
+{
+ mOnlyBookmarked = aOnlyBookmarked;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetDomainIsHost(bool *aDomainIsHost)
+{
+ *aDomainIsHost = mDomainIsHost;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetDomainIsHost(bool aDomainIsHost)
+{
+ mDomainIsHost = aDomainIsHost;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetDomain(nsACString& aDomain)
+{
+ aDomain = mDomain;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetDomain(const nsACString& aDomain)
+{
+ mDomain = aDomain;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::GetHasDomain(bool* _retval)
+{
+ // note that empty but not void is still a valid query (local files)
+ *_retval = (! mDomain.IsVoid());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetUri(nsIURI** aUri)
+{
+ NS_IF_ADDREF(*aUri = mUri);
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetUri(nsIURI* aUri)
+{
+ mUri = aUri;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::GetHasUri(bool* aHasUri)
+{
+ *aHasUri = (mUri != nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetAnnotationIsNot(bool* aIsNot)
+{
+ *aIsNot = mAnnotationIsNot;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetAnnotationIsNot(bool aIsNot)
+{
+ mAnnotationIsNot = aIsNot;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetAnnotation(nsACString& aAnnotation)
+{
+ aAnnotation = mAnnotation;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::SetAnnotation(const nsACString& aAnnotation)
+{
+ mAnnotation = aAnnotation;
+ return NS_OK;
+}
+NS_IMETHODIMP nsNavHistoryQuery::GetHasAnnotation(bool* aHasIt)
+{
+ *aHasIt = ! mAnnotation.IsEmpty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetTags(nsIVariant **aTags)
+{
+ NS_ENSURE_ARG_POINTER(aTags);
+
+ RefPtr<nsVariant> out = new nsVariant();
+
+ uint32_t arrayLen = mTags.Length();
+
+ nsresult rv;
+ if (arrayLen == 0)
+ rv = out->SetAsEmptyArray();
+ else {
+ // Note: The resulting nsIVariant dupes both the array and its elements.
+ const char16_t **array = reinterpret_cast<const char16_t **>
+ (moz_xmalloc(arrayLen * sizeof(char16_t *)));
+ NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+ for (uint32_t i = 0; i < arrayLen; ++i) {
+ array[i] = mTags[i].get();
+ }
+
+ rv = out->SetAsArray(nsIDataType::VTYPE_WCHAR_STR,
+ nullptr,
+ arrayLen,
+ reinterpret_cast<void *>(array));
+ free(array);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ out.forget(aTags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::SetTags(nsIVariant *aTags)
+{
+ NS_ENSURE_ARG(aTags);
+
+ uint16_t dataType;
+ aTags->GetDataType(&dataType);
+
+ // Caller passed in empty array. Easy -- clear our mTags array and return.
+ if (dataType == nsIDataType::VTYPE_EMPTY_ARRAY) {
+ mTags.Clear();
+ return NS_OK;
+ }
+
+ // Before we go any further, make sure caller passed in an array.
+ NS_ENSURE_TRUE(dataType == nsIDataType::VTYPE_ARRAY, NS_ERROR_ILLEGAL_VALUE);
+
+ uint16_t eltType;
+ nsIID eltIID;
+ uint32_t arrayLen;
+ void *array;
+
+ // Convert the nsIVariant to an array. We own the resulting buffer and its
+ // elements.
+ nsresult rv = aTags->GetAsArray(&eltType, &eltIID, &arrayLen, &array);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If element type is not wstring, thanks a lot. Your memory die now.
+ if (eltType != nsIDataType::VTYPE_WCHAR_STR) {
+ switch (eltType) {
+ case nsIDataType::VTYPE_ID:
+ case nsIDataType::VTYPE_CHAR_STR:
+ {
+ char **charArray = reinterpret_cast<char **>(array);
+ for (uint32_t i = 0; i < arrayLen; ++i) {
+ if (charArray[i])
+ free(charArray[i]);
+ }
+ }
+ break;
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ {
+ nsISupports **supportsArray = reinterpret_cast<nsISupports **>(array);
+ for (uint32_t i = 0; i < arrayLen; ++i) {
+ NS_IF_RELEASE(supportsArray[i]);
+ }
+ }
+ break;
+ // The other types are primitives that do not need to be freed.
+ }
+ free(array);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ char16_t **tags = reinterpret_cast<char16_t **>(array);
+ mTags.Clear();
+
+ // Finally, add each passed-in tag to our mTags array and then sort it.
+ for (uint32_t i = 0; i < arrayLen; ++i) {
+
+ // Don't allow nulls.
+ if (!tags[i]) {
+ free(tags);
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsDependentString tag(tags[i]);
+
+ // Don't store duplicate tags. This isn't just to save memory or to be
+ // fancy; the SQL that's built from the tags relies on no dupes.
+ if (!mTags.Contains(tag)) {
+ if (!mTags.AppendElement(tag)) {
+ free(tags[i]);
+ free(tags);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ free(tags[i]);
+ }
+ free(tags);
+
+ mTags.Sort();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetTagsAreNot(bool *aTagsAreNot)
+{
+ NS_ENSURE_ARG_POINTER(aTagsAreNot);
+ *aTagsAreNot = mTagsAreNot;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::SetTagsAreNot(bool aTagsAreNot)
+{
+ mTagsAreNot = aTagsAreNot;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetFolders(uint32_t *aCount,
+ int64_t **aFolders)
+{
+ uint32_t count = mFolders.Length();
+ int64_t *folders = nullptr;
+ if (count > 0) {
+ folders = static_cast<int64_t*>
+ (moz_xmalloc(count * sizeof(int64_t)));
+ NS_ENSURE_TRUE(folders, NS_ERROR_OUT_OF_MEMORY);
+
+ for (uint32_t i = 0; i < count; ++i) {
+ folders[i] = mFolders[i];
+ }
+ }
+ *aCount = count;
+ *aFolders = folders;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetFolderCount(uint32_t *aCount)
+{
+ *aCount = mFolders.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::SetFolders(const int64_t *aFolders,
+ uint32_t aFolderCount)
+{
+ if (!mFolders.ReplaceElementsAt(0, mFolders.Length(),
+ aFolders, aFolderCount)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetTransitions(uint32_t* aCount,
+ uint32_t** aTransitions)
+{
+ uint32_t count = mTransitions.Length();
+ uint32_t* transitions = nullptr;
+ if (count > 0) {
+ transitions = reinterpret_cast<uint32_t*>
+ (moz_xmalloc(count * sizeof(uint32_t)));
+ NS_ENSURE_TRUE(transitions, NS_ERROR_OUT_OF_MEMORY);
+ for (uint32_t i = 0; i < count; ++i) {
+ transitions[i] = mTransitions[i];
+ }
+ }
+ *aCount = count;
+ *aTransitions = transitions;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::GetTransitionCount(uint32_t* aCount)
+{
+ *aCount = mTransitions.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::SetTransitions(const uint32_t* aTransitions,
+ uint32_t aCount)
+{
+ if (!mTransitions.ReplaceElementsAt(0, mTransitions.Length(), aTransitions,
+ aCount))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsNavHistoryQuery::Clone(nsINavHistoryQuery** _retval)
+{
+ *_retval = nullptr;
+
+ RefPtr<nsNavHistoryQuery> clone = new nsNavHistoryQuery(*this);
+ NS_ENSURE_TRUE(clone, NS_ERROR_OUT_OF_MEMORY);
+
+ clone.forget(_retval);
+ return NS_OK;
+}
+
+
+// nsNavHistoryQueryOptions
+NS_IMPL_ISUPPORTS(nsNavHistoryQueryOptions, nsNavHistoryQueryOptions, nsINavHistoryQueryOptions)
+
+// sortingMode
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetSortingMode(uint16_t* aMode)
+{
+ *aMode = mSort;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetSortingMode(uint16_t aMode)
+{
+ if (aMode > SORT_BY_FRECENCY_DESCENDING)
+ return NS_ERROR_INVALID_ARG;
+ mSort = aMode;
+ return NS_OK;
+}
+
+// sortingAnnotation
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetSortingAnnotation(nsACString& _result) {
+ _result.Assign(mSortingAnnotation);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetSortingAnnotation(const nsACString& aSortingAnnotation) {
+ mSortingAnnotation.Assign(aSortingAnnotation);
+ return NS_OK;
+}
+
+// resultType
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetResultType(uint16_t* aType)
+{
+ *aType = mResultType;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetResultType(uint16_t aType)
+{
+ if (aType > RESULTS_AS_TAG_CONTENTS)
+ return NS_ERROR_INVALID_ARG;
+ // Tag queries and containers are bookmarks related, so we set the QueryType
+ // accordingly.
+ if (aType == RESULTS_AS_TAG_QUERY || aType == RESULTS_AS_TAG_CONTENTS)
+ mQueryType = QUERY_TYPE_BOOKMARKS;
+ mResultType = aType;
+ return NS_OK;
+}
+
+// excludeItems
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetExcludeItems(bool* aExclude)
+{
+ *aExclude = mExcludeItems;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetExcludeItems(bool aExclude)
+{
+ mExcludeItems = aExclude;
+ return NS_OK;
+}
+
+// excludeQueries
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetExcludeQueries(bool* aExclude)
+{
+ *aExclude = mExcludeQueries;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetExcludeQueries(bool aExclude)
+{
+ mExcludeQueries = aExclude;
+ return NS_OK;
+}
+
+// excludeReadOnlyFolders
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetExcludeReadOnlyFolders(bool* aExclude)
+{
+ *aExclude = mExcludeReadOnlyFolders;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetExcludeReadOnlyFolders(bool aExclude)
+{
+ mExcludeReadOnlyFolders = aExclude;
+ return NS_OK;
+}
+
+// expandQueries
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetExpandQueries(bool* aExpand)
+{
+ *aExpand = mExpandQueries;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetExpandQueries(bool aExpand)
+{
+ mExpandQueries = aExpand;
+ return NS_OK;
+}
+
+// includeHidden
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetIncludeHidden(bool* aIncludeHidden)
+{
+ *aIncludeHidden = mIncludeHidden;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetIncludeHidden(bool aIncludeHidden)
+{
+ mIncludeHidden = aIncludeHidden;
+ return NS_OK;
+}
+
+// maxResults
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetMaxResults(uint32_t* aMaxResults)
+{
+ *aMaxResults = mMaxResults;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetMaxResults(uint32_t aMaxResults)
+{
+ mMaxResults = aMaxResults;
+ return NS_OK;
+}
+
+// queryType
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetQueryType(uint16_t* _retval)
+{
+ *_retval = mQueryType;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetQueryType(uint16_t aQueryType)
+{
+ // Tag query and containers are forced to QUERY_TYPE_BOOKMARKS when the
+ // resultType is set.
+ if (mResultType == RESULTS_AS_TAG_CONTENTS ||
+ mResultType == RESULTS_AS_TAG_QUERY)
+ return NS_OK;
+ mQueryType = aQueryType;
+ return NS_OK;
+}
+
+// asyncEnabled
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::GetAsyncEnabled(bool* _asyncEnabled)
+{
+ *_asyncEnabled = mAsyncEnabled;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::SetAsyncEnabled(bool aAsyncEnabled)
+{
+ mAsyncEnabled = aAsyncEnabled;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryOptions::Clone(nsINavHistoryQueryOptions** aResult)
+{
+ nsNavHistoryQueryOptions *clone = nullptr;
+ nsresult rv = Clone(&clone);
+ *aResult = clone;
+ return rv;
+}
+
+nsresult
+nsNavHistoryQueryOptions::Clone(nsNavHistoryQueryOptions **aResult)
+{
+ *aResult = nullptr;
+ nsNavHistoryQueryOptions *result = new nsNavHistoryQueryOptions();
+
+ RefPtr<nsNavHistoryQueryOptions> resultHolder(result);
+ result->mSort = mSort;
+ result->mResultType = mResultType;
+ result->mExcludeItems = mExcludeItems;
+ result->mExcludeQueries = mExcludeQueries;
+ result->mExpandQueries = mExpandQueries;
+ result->mMaxResults = mMaxResults;
+ result->mQueryType = mQueryType;
+ result->mParentAnnotationToExclude = mParentAnnotationToExclude;
+ result->mAsyncEnabled = mAsyncEnabled;
+
+ resultHolder.forget(aResult);
+ return NS_OK;
+}
+
+
+// AppendBoolKeyValueIfTrue
+
+void // static
+AppendBoolKeyValueIfTrue(nsACString& aString, const nsCString& aName,
+ nsINavHistoryQuery* aQuery,
+ BoolQueryGetter getter)
+{
+ bool value;
+ DebugOnly<nsresult> rv = (aQuery->*getter)(&value);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failure getting boolean value");
+ if (value) {
+ AppendAmpersandIfNonempty(aString);
+ aString += aName;
+ aString.AppendLiteral("=1");
+ }
+}
+
+
+// AppendUint32KeyValueIfNonzero
+
+void // static
+AppendUint32KeyValueIfNonzero(nsACString& aString,
+ const nsCString& aName,
+ nsINavHistoryQuery* aQuery,
+ Uint32QueryGetter getter)
+{
+ uint32_t value;
+ DebugOnly<nsresult> rv = (aQuery->*getter)(&value);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failure getting value");
+ if (value) {
+ AppendAmpersandIfNonempty(aString);
+ aString += aName;
+
+ // AppendInt requires a concrete string
+ nsAutoCString appendMe("=");
+ appendMe.AppendInt(value);
+ aString.Append(appendMe);
+ }
+}
+
+
+// AppendInt64KeyValueIfNonzero
+
+void // static
+AppendInt64KeyValueIfNonzero(nsACString& aString,
+ const nsCString& aName,
+ nsINavHistoryQuery* aQuery,
+ Int64QueryGetter getter)
+{
+ PRTime value;
+ DebugOnly<nsresult> rv = (aQuery->*getter)(&value);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failure getting value");
+ if (value) {
+ AppendAmpersandIfNonempty(aString);
+ aString += aName;
+ nsAutoCString appendMe("=");
+ appendMe.AppendInt(static_cast<int64_t>(value));
+ aString.Append(appendMe);
+ }
+}
+
+
+// SetQuery/OptionsKeyBool
+
+void // static
+SetQueryKeyBool(const nsCString& aValue, nsINavHistoryQuery* aQuery,
+ BoolQuerySetter setter)
+{
+ bool value;
+ nsresult rv = ParseQueryBooleanString(aValue, &value);
+ if (NS_SUCCEEDED(rv)) {
+ rv = (aQuery->*setter)(value);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error setting boolean key value");
+ }
+ } else {
+ NS_WARNING("Invalid boolean key value in query string.");
+ }
+}
+void // static
+SetOptionsKeyBool(const nsCString& aValue, nsINavHistoryQueryOptions* aOptions,
+ BoolOptionsSetter setter)
+{
+ bool value;
+ nsresult rv = ParseQueryBooleanString(aValue, &value);
+ if (NS_SUCCEEDED(rv)) {
+ rv = (aOptions->*setter)(value);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error setting boolean key value");
+ }
+ } else {
+ NS_WARNING("Invalid boolean key value in query string.");
+ }
+}
+
+
+// SetQuery/OptionsKeyUint32
+
+void // static
+SetQueryKeyUint32(const nsCString& aValue, nsINavHistoryQuery* aQuery,
+ Uint32QuerySetter setter)
+{
+ nsresult rv;
+ uint32_t value = aValue.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = (aQuery->*setter)(value);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error setting Int32 key value");
+ }
+ } else {
+ NS_WARNING("Invalid Int32 key value in query string.");
+ }
+}
+void // static
+SetOptionsKeyUint32(const nsCString& aValue, nsINavHistoryQueryOptions* aOptions,
+ Uint32OptionsSetter setter)
+{
+ nsresult rv;
+ uint32_t value = aValue.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = (aOptions->*setter)(value);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error setting Int32 key value");
+ }
+ } else {
+ NS_WARNING("Invalid Int32 key value in query string.");
+ }
+}
+
+void // static
+SetOptionsKeyUint16(const nsCString& aValue, nsINavHistoryQueryOptions* aOptions,
+ Uint16OptionsSetter setter)
+{
+ nsresult rv;
+ uint16_t value = static_cast<uint16_t>(aValue.ToInteger(&rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = (aOptions->*setter)(value);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error setting Int16 key value");
+ }
+ } else {
+ NS_WARNING("Invalid Int16 key value in query string.");
+ }
+}
+
+
+// SetQueryKeyInt64
+
+void SetQueryKeyInt64(const nsCString& aValue, nsINavHistoryQuery* aQuery,
+ Int64QuerySetter setter)
+{
+ nsresult rv;
+ int64_t value;
+ if (PR_sscanf(aValue.get(), "%lld", &value) == 1) {
+ rv = (aQuery->*setter)(value);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error setting Int64 key value");
+ }
+ } else {
+ NS_WARNING("Invalid Int64 value in query string.");
+ }
+}
diff --git a/components/places/src/nsNavHistoryQuery.h b/components/places/src/nsNavHistoryQuery.h
new file mode 100644
index 000000000..d1a8b759a
--- /dev/null
+++ b/components/places/src/nsNavHistoryQuery.h
@@ -0,0 +1,160 @@
+/* -*- 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 definitions of nsNavHistoryQuery and nsNavHistoryQueryOptions. This
+ * header file should only be included from nsNavHistory.h, include that if
+ * you want these classes.
+ */
+
+#ifndef nsNavHistoryQuery_h_
+#define nsNavHistoryQuery_h_
+
+// nsNavHistoryQuery
+//
+// This class encapsulates the parameters for basic history queries for
+// building UI, trees, lists, etc.
+
+#include "mozilla/Attributes.h"
+
+#define NS_NAVHISTORYQUERY_IID \
+{ 0xb10185e0, 0x86eb, 0x4612, { 0x95, 0x7c, 0x09, 0x34, 0xf2, 0xb1, 0xce, 0xd7 } }
+
+class nsNavHistoryQuery final : public nsINavHistoryQuery
+{
+public:
+ nsNavHistoryQuery();
+ nsNavHistoryQuery(const nsNavHistoryQuery& aOther);
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYQUERY_IID)
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINAVHISTORYQUERY
+
+ int32_t MinVisits() { return mMinVisits; }
+ int32_t MaxVisits() { return mMaxVisits; }
+ PRTime BeginTime() { return mBeginTime; }
+ uint32_t BeginTimeReference() { return mBeginTimeReference; }
+ PRTime EndTime() { return mEndTime; }
+ uint32_t EndTimeReference() { return mEndTimeReference; }
+ const nsString& SearchTerms() { return mSearchTerms; }
+ bool OnlyBookmarked() { return mOnlyBookmarked; }
+ bool DomainIsHost() { return mDomainIsHost; }
+ const nsCString& Domain() { return mDomain; }
+ nsIURI* Uri() { return mUri; } // NOT AddRef-ed!
+ bool AnnotationIsNot() { return mAnnotationIsNot; }
+ const nsCString& Annotation() { return mAnnotation; }
+ const nsTArray<int64_t>& Folders() const { return mFolders; }
+ const nsTArray<nsString>& Tags() const { return mTags; }
+ nsresult SetTags(const nsTArray<nsString>& aTags)
+ {
+ if (!mTags.ReplaceElementsAt(0, mTags.Length(), aTags))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+ }
+ bool TagsAreNot() { return mTagsAreNot; }
+
+ const nsTArray<uint32_t>& Transitions() const { return mTransitions; }
+ nsresult SetTransitions(const nsTArray<uint32_t>& aTransitions)
+ {
+ if (!mTransitions.ReplaceElementsAt(0, mTransitions.Length(),
+ aTransitions))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+ }
+
+private:
+ ~nsNavHistoryQuery() {}
+
+protected:
+
+ int32_t mMinVisits;
+ int32_t mMaxVisits;
+ PRTime mBeginTime;
+ uint32_t mBeginTimeReference;
+ PRTime mEndTime;
+ uint32_t mEndTimeReference;
+ nsString mSearchTerms;
+ bool mOnlyBookmarked;
+ bool mDomainIsHost;
+ nsCString mDomain; // Default is IsVoid, empty string is valid query
+ nsCOMPtr<nsIURI> mUri;
+ bool mAnnotationIsNot;
+ nsCString mAnnotation;
+ nsTArray<int64_t> mFolders;
+ nsTArray<nsString> mTags;
+ bool mTagsAreNot;
+ nsTArray<uint32_t> mTransitions;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHistoryQuery, NS_NAVHISTORYQUERY_IID)
+
+// nsNavHistoryQueryOptions
+
+#define NS_NAVHISTORYQUERYOPTIONS_IID \
+{0x95f8ba3b, 0xd681, 0x4d89, {0xab, 0xd1, 0xfd, 0xae, 0xf2, 0xa3, 0xde, 0x18}}
+
+class nsNavHistoryQueryOptions final : public nsINavHistoryQueryOptions
+{
+public:
+ nsNavHistoryQueryOptions()
+ : mSort(0)
+ , mResultType(0)
+ , mExcludeItems(false)
+ , mExcludeQueries(false)
+ , mExcludeReadOnlyFolders(false)
+ , mExpandQueries(true)
+ , mIncludeHidden(false)
+ , mMaxResults(0)
+ , mQueryType(nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY)
+ , mAsyncEnabled(false)
+ { }
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYQUERYOPTIONS_IID)
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINAVHISTORYQUERYOPTIONS
+
+ uint16_t SortingMode() const { return mSort; }
+ uint16_t ResultType() const { return mResultType; }
+ bool ExcludeItems() const { return mExcludeItems; }
+ bool ExcludeQueries() const { return mExcludeQueries; }
+ bool ExcludeReadOnlyFolders() const { return mExcludeReadOnlyFolders; }
+ bool ExpandQueries() const { return mExpandQueries; }
+ bool IncludeHidden() const { return mIncludeHidden; }
+ uint32_t MaxResults() const { return mMaxResults; }
+ uint16_t QueryType() const { return mQueryType; }
+ bool AsyncEnabled() const { return mAsyncEnabled; }
+
+ nsresult Clone(nsNavHistoryQueryOptions **aResult);
+
+private:
+ ~nsNavHistoryQueryOptions() {}
+ nsNavHistoryQueryOptions(const nsNavHistoryQueryOptions& other) {} // no copy
+
+ // IF YOU ADD MORE ITEMS:
+ // * Add a new getter for C++ above if it makes sense
+ // * Add to the serialization code (see nsNavHistory::QueriesToQueryString())
+ // * Add to the deserialization code (see nsNavHistory::QueryStringToQueries)
+ // * Add to the nsNavHistoryQueryOptions::Clone() function
+ // * Add to the nsNavHistory.cpp::GetSimpleBookmarksQueryFolder function if applicable
+ uint16_t mSort;
+ nsCString mSortingAnnotation;
+ nsCString mParentAnnotationToExclude;
+ uint16_t mResultType;
+ bool mExcludeItems;
+ bool mExcludeQueries;
+ bool mExcludeReadOnlyFolders;
+ bool mExpandQueries;
+ bool mIncludeHidden;
+ uint32_t mMaxResults;
+ uint16_t mQueryType;
+ bool mAsyncEnabled;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHistoryQueryOptions, NS_NAVHISTORYQUERYOPTIONS_IID)
+
+#endif // nsNavHistoryQuery_h_
diff --git a/components/places/src/nsNavHistoryResult.cpp b/components/places/src/nsNavHistoryResult.cpp
new file mode 100644
index 000000000..64d06308f
--- /dev/null
+++ b/components/places/src/nsNavHistoryResult.cpp
@@ -0,0 +1,4812 @@
+/* -*- 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 <stdio.h>
+#include "nsNavHistory.h"
+#include "nsNavBookmarks.h"
+#include "nsFaviconService.h"
+#include "nsITaggingService.h"
+#include "nsAnnotationService.h"
+#include "Helpers.h"
+#include "mozilla/DebugOnly.h"
+#include "nsDebug.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "prtime.h"
+#include "prprf.h"
+#include "nsQueryObject.h"
+
+#include "nsCycleCollectionParticipant.h"
+
+// Thanks, Windows.h :(
+#undef CompareString
+
+#define TO_ICONTAINER(_node) \
+ static_cast<nsINavHistoryContainerResultNode*>(_node)
+
+#define TO_CONTAINER(_node) \
+ static_cast<nsNavHistoryContainerResultNode*>(_node)
+
+#define NOTIFY_RESULT_OBSERVERS_RET(_result, _method, _ret) \
+ PR_BEGIN_MACRO \
+ NS_ENSURE_TRUE(_result, _ret); \
+ if (!_result->mSuppressNotifications) { \
+ ENUMERATE_WEAKARRAY(_result->mObservers, nsINavHistoryResultObserver, \
+ _method) \
+ } \
+ PR_END_MACRO
+
+#define NOTIFY_RESULT_OBSERVERS(_result, _method) \
+ NOTIFY_RESULT_OBSERVERS_RET(_result, _method, NS_ERROR_UNEXPECTED)
+
+// What we want is: NS_INTERFACE_MAP_ENTRY(self) for static IID accessors,
+// but some of our classes (like nsNavHistoryResult) have an ambiguous base
+// class of nsISupports which prevents this from working (the default macro
+// converts it to nsISupports, then addrefs it, then returns it). Therefore, we
+// expand the macro here and change it so that it works. Yuck.
+#define NS_INTERFACE_MAP_STATIC_AMBIGUOUS(_class) \
+ if (aIID.Equals(NS_GET_IID(_class))) { \
+ NS_ADDREF(this); \
+ *aInstancePtr = this; \
+ return NS_OK; \
+ } else
+
+// Number of changes to handle separately in a batch. If more changes are
+// requested the node will switch to full refresh mode.
+#define MAX_BATCH_CHANGES_BEFORE_REFRESH 5
+
+// Emulate string comparison (used for sorting) for PRTime and int.
+inline int32_t ComparePRTime(PRTime a, PRTime b)
+{
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return 1;
+ return 0;
+}
+inline int32_t CompareIntegers(uint32_t a, uint32_t b)
+{
+ return a - b;
+}
+
+using namespace mozilla;
+using namespace mozilla::places;
+
+NS_IMPL_CYCLE_COLLECTION(nsNavHistoryResultNode, mParent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryResultNode)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResultNode)
+ NS_INTERFACE_MAP_ENTRY(nsINavHistoryResultNode)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNavHistoryResultNode)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNavHistoryResultNode)
+
+nsNavHistoryResultNode::nsNavHistoryResultNode(
+ const nsACString& aURI, const nsACString& aTitle, uint32_t aAccessCount,
+ PRTime aTime, const nsACString& aIconURI) :
+ mParent(nullptr),
+ mURI(aURI),
+ mTitle(aTitle),
+ mAreTagsSorted(false),
+ mAccessCount(aAccessCount),
+ mTime(aTime),
+ mFaviconURI(aIconURI),
+ mBookmarkIndex(-1),
+ mItemId(-1),
+ mFolderId(-1),
+ mVisitId(-1),
+ mFromVisitId(-1),
+ mDateAdded(0),
+ mLastModified(0),
+ mIndentLevel(-1),
+ mFrecency(0),
+ mHidden(false),
+ mTransitionType(0)
+{
+ mTags.SetIsVoid(true);
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetIcon(nsACString& aIcon)
+{
+ if (mFaviconURI.IsEmpty()) {
+ aIcon.Truncate();
+ return NS_OK;
+ }
+
+ nsFaviconService* faviconService = nsFaviconService::GetFaviconService();
+ NS_ENSURE_TRUE(faviconService, NS_ERROR_OUT_OF_MEMORY);
+ faviconService->GetFaviconSpecForIconString(mFaviconURI, aIcon);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetParent(nsINavHistoryContainerResultNode** aParent)
+{
+ NS_IF_ADDREF(*aParent = mParent);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetParentResult(nsINavHistoryResult** aResult)
+{
+ *aResult = nullptr;
+ if (IsContainer())
+ NS_IF_ADDREF(*aResult = GetAsContainer()->mResult);
+ else if (mParent)
+ NS_IF_ADDREF(*aResult = mParent->mResult);
+
+ NS_ENSURE_STATE(*aResult);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetTags(nsAString& aTags) {
+ // Only URI-nodes may be associated with tags
+ if (!IsURI()) {
+ aTags.Truncate();
+ return NS_OK;
+ }
+
+ // Initially, the tags string is set to a void string (see constructor). We
+ // then build it the first time this method called is called (and by that,
+ // implicitly unset the void flag). Result observers may re-set the void flag
+ // in order to force rebuilding of the tags string.
+ if (!mTags.IsVoid()) {
+ // If mTags is assigned by a history query it is unsorted for performance
+ // reasons, it must be sorted by name on first read access.
+ if (!mAreTagsSorted) {
+ nsTArray<nsCString> tags;
+ ParseString(NS_ConvertUTF16toUTF8(mTags), ',', tags);
+ tags.Sort();
+ mTags.SetIsVoid(true);
+ for (nsTArray<nsCString>::index_type i = 0; i < tags.Length(); ++i) {
+ AppendUTF8toUTF16(tags[i], mTags);
+ if (i < tags.Length() - 1 )
+ mTags.AppendLiteral(", ");
+ }
+ mAreTagsSorted = true;
+ }
+ aTags.Assign(mTags);
+ return NS_OK;
+ }
+
+ // Fetch the tags
+ RefPtr<Database> DB = Database::GetDatabase();
+ NS_ENSURE_STATE(DB);
+ nsCOMPtr<mozIStorageStatement> stmt = DB->GetStatement(
+ "/* do not warn (bug 487594) */ "
+ "SELECT GROUP_CONCAT(tag_title, ', ') "
+ "FROM ( "
+ "SELECT t.title AS tag_title "
+ "FROM moz_bookmarks b "
+ "JOIN moz_bookmarks t ON t.id = +b.parent "
+ "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
+ "AND t.parent = :tags_folder "
+ "ORDER BY t.title COLLATE NOCASE ASC "
+ ") "
+ );
+ NS_ENSURE_STATE(stmt);
+ mozStorageStatementScoper scoper(stmt);
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_STATE(history);
+ nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("tags_folder"),
+ history->GetTagsFolder());
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasTags = false;
+ if (NS_SUCCEEDED(stmt->ExecuteStep(&hasTags)) && hasTags) {
+ rv = stmt->GetString(0, mTags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aTags.Assign(mTags);
+ mAreTagsSorted = true;
+ }
+
+ // If this node is a child of a history query, we need to make sure changes
+ // to tags are properly live-updated.
+ if (mParent && mParent->IsQuery() &&
+ mParent->mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY) {
+ nsNavHistoryQueryResultNode* query = mParent->GetAsQuery();
+ nsNavHistoryResult* result = query->GetResult();
+ NS_ENSURE_STATE(result);
+ result->AddAllBookmarksObserver(query);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetPageGuid(nsACString& aPageGuid) {
+ aPageGuid = mPageGuid;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetBookmarkGuid(nsACString& aBookmarkGuid) {
+ aBookmarkGuid = mBookmarkGuid;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetVisitId(int64_t* aVisitId) {
+ *aVisitId = mVisitId;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetFromVisitId(int64_t* aFromVisitId) {
+ *aFromVisitId = mFromVisitId;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::GetVisitType(uint32_t* aVisitType) {
+ *aVisitType = mTransitionType;
+ return NS_OK;
+}
+
+
+void
+nsNavHistoryResultNode::OnRemoving()
+{
+ mParent = nullptr;
+}
+
+
+/**
+ * This will find the result for this node. We can ask the nearest container
+ * for this value (either ourselves or our parents should be a container,
+ * and all containers have result pointers).
+ *
+ * @note The result may be null, if the container is detached from the result
+ * who owns it.
+ */
+nsNavHistoryResult*
+nsNavHistoryResultNode::GetResult()
+{
+ nsNavHistoryResultNode* node = this;
+ do {
+ if (node->IsContainer()) {
+ nsNavHistoryContainerResultNode* container = TO_CONTAINER(node);
+ return container->mResult;
+ }
+ node = node->mParent;
+ } while (node);
+ MOZ_ASSERT(false, "No container node found in hierarchy!");
+ return nullptr;
+}
+
+
+/**
+ * Searches up the tree for the closest ancestor node that has an options
+ * structure. This will tell us the options that were used to generate this
+ * node.
+ *
+ * Be careful, this function walks up the tree, so it can not be used when
+ * result nodes are created because they have no parent. Only call this
+ * function after the tree has been built.
+ */
+nsNavHistoryQueryOptions*
+nsNavHistoryResultNode::GetGeneratingOptions()
+{
+ if (!mParent) {
+ // When we have no parent, it either means we haven't built the tree yet,
+ // in which case calling this function is a bug, or this node is the root
+ // of the tree. When we are the root of the tree, our own options are the
+ // generating options.
+ if (IsContainer())
+ return GetAsContainer()->mOptions;
+
+ NS_NOTREACHED("Can't find a generating node for this container, perhaps FillStats has not been called on this tree yet?");
+ return nullptr;
+ }
+
+ // Look up the tree. We want the options that were used to create this node,
+ // and since it has a parent, it's the options of an ancestor, not of the node
+ // itself. So start at the parent.
+ nsNavHistoryContainerResultNode* cur = mParent;
+ while (cur) {
+ if (cur->IsContainer())
+ return cur->GetAsContainer()->mOptions;
+ cur = cur->mParent;
+ }
+
+ // We should always find a container node as an ancestor.
+ NS_NOTREACHED("Can't find a generating node for this container, the tree seemes corrupted.");
+ return nullptr;
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode,
+ mResult,
+ mChildren)
+
+NS_IMPL_ADDREF_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
+NS_IMPL_RELEASE_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsNavHistoryContainerResultNode)
+ NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryContainerResultNode)
+ NS_INTERFACE_MAP_ENTRY(nsINavHistoryContainerResultNode)
+NS_INTERFACE_MAP_END_INHERITING(nsNavHistoryResultNode)
+
+nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
+ const nsACString& aURI, const nsACString& aTitle,
+ const nsACString& aIconURI, uint32_t aContainerType,
+ nsNavHistoryQueryOptions* aOptions) :
+ nsNavHistoryResultNode(aURI, aTitle, 0, 0, aIconURI),
+ mResult(nullptr),
+ mContainerType(aContainerType),
+ mExpanded(false),
+ mOptions(aOptions),
+ mAsyncCanceledState(NOT_CANCELED)
+{
+}
+
+nsNavHistoryContainerResultNode::nsNavHistoryContainerResultNode(
+ const nsACString& aURI, const nsACString& aTitle,
+ PRTime aTime,
+ const nsACString& aIconURI, uint32_t aContainerType,
+ nsNavHistoryQueryOptions* aOptions) :
+ nsNavHistoryResultNode(aURI, aTitle, 0, aTime, aIconURI),
+ mResult(nullptr),
+ mContainerType(aContainerType),
+ mExpanded(false),
+ mOptions(aOptions),
+ mAsyncCanceledState(NOT_CANCELED)
+{
+}
+
+
+nsNavHistoryContainerResultNode::~nsNavHistoryContainerResultNode()
+{
+ // Explicitly clean up array of children of this container. We must ensure
+ // all references are gone and all of their destructors are called.
+ mChildren.Clear();
+}
+
+
+/**
+ * Containers should notify their children that they are being removed when the
+ * container is being removed.
+ */
+void
+nsNavHistoryContainerResultNode::OnRemoving()
+{
+ nsNavHistoryResultNode::OnRemoving();
+ for (int32_t i = 0; i < mChildren.Count(); ++i)
+ mChildren[i]->OnRemoving();
+ mChildren.Clear();
+ mResult = nullptr;
+}
+
+
+bool
+nsNavHistoryContainerResultNode::AreChildrenVisible()
+{
+ nsNavHistoryResult* result = GetResult();
+ if (!result) {
+ NS_NOTREACHED("Invalid result");
+ return false;
+ }
+
+ if (!mExpanded)
+ return false;
+
+ // Now check if any ancestor is closed.
+ nsNavHistoryContainerResultNode* ancestor = mParent;
+ while (ancestor) {
+ if (!ancestor->mExpanded)
+ return false;
+
+ ancestor = ancestor->mParent;
+ }
+
+ return true;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetContainerOpen(bool *aContainerOpen)
+{
+ *aContainerOpen = mExpanded;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::SetContainerOpen(bool aContainerOpen)
+{
+ if (aContainerOpen) {
+ if (!mExpanded) {
+ nsNavHistoryQueryOptions* options = GetGeneratingOptions();
+ if (options && options->AsyncEnabled())
+ OpenContainerAsync();
+ else
+ OpenContainer();
+ }
+ }
+ else {
+ if (mExpanded)
+ CloseContainer();
+ else if (mAsyncPendingStmt)
+ CancelAsyncOpen(false);
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * Notifies the result's observers of a change in the container's state. The
+ * notification includes both the old and new states: The old is aOldState, and
+ * the new is the container's current state.
+ *
+ * @param aOldState
+ * The state being transitioned out of.
+ */
+nsresult
+nsNavHistoryContainerResultNode::NotifyOnStateChange(uint16_t aOldState)
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+
+ nsresult rv;
+ uint16_t currState;
+ rv = GetState(&currState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Notify via the new ContainerStateChanged observer method.
+ NOTIFY_RESULT_OBSERVERS(result,
+ ContainerStateChanged(this, aOldState, currState));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetState(uint16_t* _state)
+{
+ NS_ENSURE_ARG_POINTER(_state);
+
+ *_state = mExpanded ? (uint16_t)STATE_OPENED
+ : mAsyncPendingStmt ? (uint16_t)STATE_LOADING
+ : (uint16_t)STATE_CLOSED;
+
+ return NS_OK;
+}
+
+
+/**
+ * This handles the generic container case. Other container types should
+ * override this to do their own handling.
+ */
+nsresult
+nsNavHistoryContainerResultNode::OpenContainer()
+{
+ NS_ASSERTION(!mExpanded, "Container must not be expanded to open it");
+ mExpanded = true;
+
+ nsresult rv = NotifyOnStateChange(STATE_CLOSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+/**
+ * Unset aSuppressNotifications to notify observers on this change. That is
+ * the normal operation. This is set to false for the recursive calls since the
+ * root container that is being closed will handle recomputation of the visible
+ * elements for its entire subtree.
+ */
+nsresult
+nsNavHistoryContainerResultNode::CloseContainer(bool aSuppressNotifications)
+{
+ NS_ASSERTION((mExpanded && !mAsyncPendingStmt) ||
+ (!mExpanded && mAsyncPendingStmt),
+ "Container must be expanded or loading to close it");
+
+ nsresult rv;
+ uint16_t oldState;
+ rv = GetState(&oldState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mExpanded) {
+ // Recursively close all child containers.
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->IsContainer() &&
+ mChildren[i]->GetAsContainer()->mExpanded)
+ mChildren[i]->GetAsContainer()->CloseContainer(true);
+ }
+
+ mExpanded = false;
+ }
+
+ // Be sure to set this to null before notifying observers. It signifies that
+ // the container is no longer loading (if it was in the first place).
+ mAsyncPendingStmt = nullptr;
+
+ if (!aSuppressNotifications) {
+ rv = NotifyOnStateChange(oldState);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If this is the root container of a result, we can tell the result to stop
+ // observing changes, otherwise the result will stay in memory and updates
+ // itself till it is cycle collected.
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mRootNode == this) {
+ result->StopObserving();
+ // When reopening this node its result will be out of sync.
+ // We must clear our children to ensure we will call FillChildren
+ // again in such a case.
+ if (this->IsQuery())
+ this->GetAsQuery()->ClearChildren(true);
+ else if (this->IsFolder())
+ this->GetAsFolder()->ClearChildren(true);
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * The async version of OpenContainer.
+ */
+nsresult
+nsNavHistoryContainerResultNode::OpenContainerAsync()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Cancels the pending asynchronous Storage execution triggered by
+ * FillChildrenAsync, if it exists. This method doesn't do much, because after
+ * cancelation Storage will call this node's HandleCompletion callback, where
+ * the real work is done.
+ *
+ * @param aRestart
+ * If true, async execution will be restarted by HandleCompletion.
+ */
+void
+nsNavHistoryContainerResultNode::CancelAsyncOpen(bool aRestart)
+{
+ NS_ASSERTION(mAsyncPendingStmt, "Async execution canceled but not pending");
+
+ mAsyncCanceledState = aRestart ? CANCELED_RESTART_NEEDED : CANCELED;
+
+ // Cancel will fail if the pending statement has already been canceled.
+ // That's OK since this method may be called multiple times, and multiple
+ // cancels don't harm anything.
+ (void)mAsyncPendingStmt->Cancel();
+}
+
+
+/**
+ * This builds up tree statistics from the bottom up. Call with a container
+ * and the indent level of that container. To init the full tree, call with
+ * the root container. The default indent level is -1, which is appropriate
+ * for the root level.
+ *
+ * CALL THIS AFTER FILLING ANY CONTAINER to update the parent and result node
+ * pointers, even if you don't care about visit counts and last visit dates.
+ */
+void
+nsNavHistoryContainerResultNode::FillStats()
+{
+ uint32_t accessCount = 0;
+ PRTime newTime = 0;
+
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ nsNavHistoryResultNode* node = mChildren[i];
+ node->mParent = this;
+ node->mIndentLevel = mIndentLevel + 1;
+ if (node->IsContainer()) {
+ nsNavHistoryContainerResultNode* container = node->GetAsContainer();
+ container->mResult = mResult;
+ container->FillStats();
+ }
+ accessCount += node->mAccessCount;
+ // this is how container nodes get sorted by date
+ // The container gets the most recent time of the child nodes.
+ if (node->mTime > newTime)
+ newTime = node->mTime;
+ }
+
+ if (mExpanded) {
+ mAccessCount = accessCount;
+ if (!IsQuery() || newTime > mTime)
+ mTime = newTime;
+ }
+}
+
+
+/**
+ * This is used when one container changes to do a minimal update of the tree
+ * structure. When something changes, you want to call FillStats if necessary
+ * and update this container completely. Then call this function which will
+ * walk up the tree and fill in the previous containers.
+ *
+ * Note that you have to tell us by how much our access count changed. Our
+ * access count should already be set to the new value; this is used tochange
+ * the parents without having to re-count all their children.
+ *
+ * This does NOT update the last visit date downward. Therefore, if you are
+ * deleting a node that has the most recent last visit date, the parents will
+ * not get their last visit dates downshifted accordingly. This is a rather
+ * unusual case: we don't often delete things, and we usually don't even show
+ * the last visit date for folders. Updating would be slower because we would
+ * have to recompute it from scratch.
+ */
+nsresult
+nsNavHistoryContainerResultNode::ReverseUpdateStats(int32_t aAccessCountChange)
+{
+ if (mParent) {
+ nsNavHistoryResult* result = GetResult();
+ bool shouldNotify = result && mParent->mParent &&
+ mParent->mParent->AreChildrenVisible();
+
+ mParent->mAccessCount += aAccessCountChange;
+ bool timeChanged = false;
+ if (mTime > mParent->mTime) {
+ timeChanged = true;
+ mParent->mTime = mTime;
+ }
+
+ if (shouldNotify) {
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeHistoryDetailsChanged(TO_ICONTAINER(mParent),
+ mParent->mTime,
+ mParent->mAccessCount));
+ }
+
+ // check sorting, the stats may have caused this node to move if the
+ // sorting depended on something we are changing.
+ uint16_t sortMode = mParent->GetSortType();
+ bool sortingByVisitCount =
+ sortMode == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
+ sortMode == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING;
+ bool sortingByTime =
+ sortMode == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
+ sortMode == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING;
+
+ if ((sortingByVisitCount && aAccessCountChange != 0) ||
+ (sortingByTime && timeChanged)) {
+ int32_t ourIndex = mParent->FindChild(this);
+ NS_ASSERTION(ourIndex >= 0, "Could not find self in parent");
+ if (ourIndex >= 0)
+ EnsureItemPosition(static_cast<uint32_t>(ourIndex));
+ }
+
+ nsresult rv = mParent->ReverseUpdateStats(aAccessCountChange);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * This walks up the tree until we find a query result node or the root to get
+ * the sorting type.
+ */
+uint16_t
+nsNavHistoryContainerResultNode::GetSortType()
+{
+ if (mParent)
+ return mParent->GetSortType();
+ if (mResult)
+ return mResult->mSortingMode;
+
+ // This is a detached container, just use natural order.
+ return nsINavHistoryQueryOptions::SORT_BY_NONE;
+}
+
+
+nsresult nsNavHistoryContainerResultNode::Refresh() {
+ NS_WARNING("Refresh() is supported by queries or folders, not generic containers.");
+ return NS_OK;
+}
+
+void
+nsNavHistoryContainerResultNode::GetSortingAnnotation(nsACString& aAnnotation)
+{
+ if (mParent)
+ mParent->GetSortingAnnotation(aAnnotation);
+ else if (mResult)
+ aAnnotation.Assign(mResult->mSortingAnnotation);
+}
+
+/**
+ * @return the sorting comparator function for the give sort type, or null if
+ * there is no comparator.
+ */
+nsNavHistoryContainerResultNode::SortComparator
+nsNavHistoryContainerResultNode::GetSortingComparator(uint16_t aSortType)
+{
+ switch (aSortType)
+ {
+ case nsINavHistoryQueryOptions::SORT_BY_NONE:
+ return &SortComparison_Bookmark;
+ case nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING:
+ return &SortComparison_TitleLess;
+ case nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING:
+ return &SortComparison_TitleGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING:
+ return &SortComparison_DateLess;
+ case nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING:
+ return &SortComparison_DateGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_URI_ASCENDING:
+ return &SortComparison_URILess;
+ case nsINavHistoryQueryOptions::SORT_BY_URI_DESCENDING:
+ return &SortComparison_URIGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING:
+ return &SortComparison_VisitCountLess;
+ case nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING:
+ return &SortComparison_VisitCountGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_KEYWORD_ASCENDING:
+ return &SortComparison_KeywordLess;
+ case nsINavHistoryQueryOptions::SORT_BY_KEYWORD_DESCENDING:
+ return &SortComparison_KeywordGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_ASCENDING:
+ return &SortComparison_AnnotationLess;
+ case nsINavHistoryQueryOptions::SORT_BY_ANNOTATION_DESCENDING:
+ return &SortComparison_AnnotationGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_ASCENDING:
+ return &SortComparison_DateAddedLess;
+ case nsINavHistoryQueryOptions::SORT_BY_DATEADDED_DESCENDING:
+ return &SortComparison_DateAddedGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_ASCENDING:
+ return &SortComparison_LastModifiedLess;
+ case nsINavHistoryQueryOptions::SORT_BY_LASTMODIFIED_DESCENDING:
+ return &SortComparison_LastModifiedGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_TAGS_ASCENDING:
+ return &SortComparison_TagsLess;
+ case nsINavHistoryQueryOptions::SORT_BY_TAGS_DESCENDING:
+ return &SortComparison_TagsGreater;
+ case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING:
+ return &SortComparison_FrecencyLess;
+ case nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING:
+ return &SortComparison_FrecencyGreater;
+ default:
+ NS_NOTREACHED("Bad sorting type");
+ return nullptr;
+ }
+}
+
+
+/**
+ * This is used by Result::SetSortingMode and QueryResultNode::FillChildren to
+ * sort the child list.
+ *
+ * This does NOT update any visibility or tree information. The caller will
+ * have to completely rebuild the visible list after this.
+ */
+void
+nsNavHistoryContainerResultNode::RecursiveSort(
+ const char* aData, SortComparator aComparator)
+{
+ void* data = const_cast<void*>(static_cast<const void*>(aData));
+
+ mChildren.Sort(aComparator, data);
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->IsContainer())
+ mChildren[i]->GetAsContainer()->RecursiveSort(aData, aComparator);
+ }
+}
+
+
+/**
+ * @return the index that the given item would fall on if it were to be
+ * inserted using the given sorting.
+ */
+uint32_t
+nsNavHistoryContainerResultNode::FindInsertionPoint(
+ nsNavHistoryResultNode* aNode, SortComparator aComparator,
+ const char* aData, bool* aItemExists)
+{
+ if (aItemExists)
+ (*aItemExists) = false;
+
+ if (mChildren.Count() == 0)
+ return 0;
+
+ void* data = const_cast<void*>(static_cast<const void*>(aData));
+
+ // The common case is the beginning or the end because this is used to insert
+ // new items that are added to history, which is usually sorted by date.
+ int32_t res;
+ res = aComparator(aNode, mChildren[0], data);
+ if (res <= 0) {
+ if (aItemExists && res == 0)
+ (*aItemExists) = true;
+ return 0;
+ }
+ res = aComparator(aNode, mChildren[mChildren.Count() - 1], data);
+ if (res >= 0) {
+ if (aItemExists && res == 0)
+ (*aItemExists) = true;
+ return mChildren.Count();
+ }
+
+ uint32_t beginRange = 0; // inclusive
+ uint32_t endRange = mChildren.Count(); // exclusive
+ while (1) {
+ if (beginRange == endRange)
+ return endRange;
+ uint32_t center = beginRange + (endRange - beginRange) / 2;
+ int32_t res = aComparator(aNode, mChildren[center], data);
+ if (res <= 0) {
+ endRange = center; // left side
+ if (aItemExists && res == 0)
+ (*aItemExists) = true;
+ }
+ else {
+ beginRange = center + 1; // right site
+ }
+ }
+}
+
+
+/**
+ * This checks the child node at the given index to see if its sorting is
+ * correct. This is called when nodes are updated and we need to see whether
+ * we need to move it.
+ *
+ * @returns true if not and it should be resorted.
+*/
+bool
+nsNavHistoryContainerResultNode::DoesChildNeedResorting(uint32_t aIndex,
+ SortComparator aComparator, const char* aData)
+{
+ NS_ASSERTION(aIndex < uint32_t(mChildren.Count()),
+ "Input index out of range");
+ if (mChildren.Count() == 1)
+ return false;
+
+ void* data = const_cast<void*>(static_cast<const void*>(aData));
+
+ if (aIndex > 0) {
+ // compare to previous item
+ if (aComparator(mChildren[aIndex - 1], mChildren[aIndex], data) > 0)
+ return true;
+ }
+ if (aIndex < uint32_t(mChildren.Count()) - 1) {
+ // compare to next item
+ if (aComparator(mChildren[aIndex], mChildren[aIndex + 1], data) > 0)
+ return true;
+ }
+ return false;
+}
+
+
+/* static */
+int32_t nsNavHistoryContainerResultNode::SortComparison_StringLess(
+ const nsAString& a, const nsAString& b) {
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, 0);
+ nsICollation* collation = history->GetCollation();
+ NS_ENSURE_TRUE(collation, 0);
+
+ int32_t res = 0;
+ collation->CompareString(nsICollation::kCollationCaseInSensitive, a, b, &res);
+ return res;
+}
+
+
+/**
+ * When there are bookmark indices, we should never have ties, so we don't
+ * need to worry about tiebreaking. When there are no bookmark indices,
+ * everything will be -1 and we don't worry about sorting.
+ */
+int32_t nsNavHistoryContainerResultNode::SortComparison_Bookmark(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return a->mBookmarkIndex - b->mBookmarkIndex;
+}
+
+/**
+ * These are a little more complicated because they do a localization
+ * conversion. If this is too slow, we can compute the sort keys once in
+ * advance, sort that array, and then reorder the real array accordingly.
+ * This would save some key generations.
+ *
+ * The collation object must be allocated before sorting on title!
+ */
+int32_t nsNavHistoryContainerResultNode::SortComparison_TitleLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ uint32_t aType;
+ a->GetType(&aType);
+
+ int32_t value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ if (value == 0) {
+ // resolve by URI
+ if (a->IsURI()) {
+ value = a->mURI.Compare(b->mURI.get());
+ }
+ if (value == 0) {
+ // resolve by date
+ value = ComparePRTime(a->mTime, b->mTime);
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_TitleGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -SortComparison_TitleLess(a, b, closure);
+}
+
+/**
+ * Equal times will be very unusual, but it is important that there is some
+ * deterministic ordering of the results so they don't move around.
+ */
+int32_t nsNavHistoryContainerResultNode::SortComparison_DateLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = ComparePRTime(a->mTime, b->mTime);
+ if (value == 0) {
+ value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_DateGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_DateLess(a, b, closure);
+}
+
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = ComparePRTime(a->mDateAdded, b->mDateAdded);
+ if (value == 0) {
+ value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_DateAddedGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_DateAddedLess(a, b, closure);
+}
+
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = ComparePRTime(a->mLastModified, b->mLastModified);
+ if (value == 0) {
+ value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_LastModifiedGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_LastModifiedLess(a, b, closure);
+}
+
+
+/**
+ * Certain types of parent nodes are treated specially because URIs are not
+ * valid (like days or hosts).
+ */
+int32_t nsNavHistoryContainerResultNode::SortComparison_URILess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value;
+ if (a->IsURI() && b->IsURI()) {
+ // normal URI or visit
+ value = a->mURI.Compare(b->mURI.get());
+ } else {
+ // for everything else, use title (= host name)
+ value = SortComparison_StringLess(NS_ConvertUTF8toUTF16(a->mTitle),
+ NS_ConvertUTF8toUTF16(b->mTitle));
+ }
+
+ if (value == 0) {
+ value = ComparePRTime(a->mTime, b->mTime);
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_URIGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -SortComparison_URILess(a, b, closure);
+}
+
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_KeywordLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = 0;
+ if (a->mItemId != -1 || b->mItemId != -1) {
+ // compare the keywords
+ nsAutoString keywordA, keywordB;
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, 0);
+
+ nsresult rv;
+ if (a->mItemId != -1) {
+ rv = bookmarks->GetKeywordForBookmark(a->mItemId, keywordA);
+ NS_ENSURE_SUCCESS(rv, 0);
+ }
+ if (b->mItemId != -1) {
+ rv = bookmarks->GetKeywordForBookmark(b->mItemId, keywordB);
+ NS_ENSURE_SUCCESS(rv, 0);
+ }
+
+ value = SortComparison_StringLess(keywordA, keywordB);
+ }
+
+ // Fall back to title sorting.
+ if (value == 0)
+ value = SortComparison_TitleLess(a, b, closure);
+
+ return value;
+}
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_KeywordGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -SortComparison_KeywordLess(a, b, closure);
+}
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_AnnotationLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ nsAutoCString annoName(static_cast<char*>(closure));
+ NS_ENSURE_TRUE(!annoName.IsEmpty(), 0);
+
+ bool a_itemAnno = false;
+ bool b_itemAnno = false;
+
+ // Not used for item annos
+ nsCOMPtr<nsIURI> a_uri, b_uri;
+ if (a->mItemId != -1) {
+ a_itemAnno = true;
+ } else {
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(a->GetUri(spec))){
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(a_uri), spec));
+ }
+ NS_ENSURE_TRUE(a_uri, 0);
+ }
+
+ if (b->mItemId != -1) {
+ b_itemAnno = true;
+ } else {
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(b->GetUri(spec))) {
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(b_uri), spec));
+ }
+ NS_ENSURE_TRUE(b_uri, 0);
+ }
+
+ nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
+ NS_ENSURE_TRUE(annosvc, 0);
+
+ bool a_hasAnno, b_hasAnno;
+ if (a_itemAnno) {
+ NS_ENSURE_SUCCESS(annosvc->ItemHasAnnotation(a->mItemId, annoName,
+ &a_hasAnno), 0);
+ } else {
+ NS_ENSURE_SUCCESS(annosvc->PageHasAnnotation(a_uri, annoName,
+ &a_hasAnno), 0);
+ }
+ if (b_itemAnno) {
+ NS_ENSURE_SUCCESS(annosvc->ItemHasAnnotation(b->mItemId, annoName,
+ &b_hasAnno), 0);
+ } else {
+ NS_ENSURE_SUCCESS(annosvc->PageHasAnnotation(b_uri, annoName,
+ &b_hasAnno), 0);
+ }
+
+ int32_t value = 0;
+ if (a_hasAnno || b_hasAnno) {
+ uint16_t annoType;
+ if (a_hasAnno) {
+ if (a_itemAnno) {
+ NS_ENSURE_SUCCESS(annosvc->GetItemAnnotationType(a->mItemId,
+ annoName,
+ &annoType), 0);
+ } else {
+ NS_ENSURE_SUCCESS(annosvc->GetPageAnnotationType(a_uri, annoName,
+ &annoType), 0);
+ }
+ }
+ if (b_hasAnno) {
+ uint16_t b_type;
+ if (b_itemAnno) {
+ NS_ENSURE_SUCCESS(annosvc->GetItemAnnotationType(b->mItemId,
+ annoName,
+ &b_type), 0);
+ } else {
+ NS_ENSURE_SUCCESS(annosvc->GetPageAnnotationType(b_uri, annoName,
+ &b_type), 0);
+ }
+ // We better make the API not support this state, really
+ // XXXmano: this is actually wrong for double<->int and int64_t<->int32_t
+ if (a_hasAnno && b_type != annoType)
+ return 0;
+ annoType = b_type;
+ }
+
+#define GET_ANNOTATIONS_VALUES(METHOD_ITEM, METHOD_PAGE, A_VAL, B_VAL) \
+ if (a_hasAnno) { \
+ if (a_itemAnno) { \
+ NS_ENSURE_SUCCESS(annosvc->METHOD_ITEM(a->mItemId, annoName, \
+ A_VAL), 0); \
+ } else { \
+ NS_ENSURE_SUCCESS(annosvc->METHOD_PAGE(a_uri, annoName, \
+ A_VAL), 0); \
+ } \
+ } \
+ if (b_hasAnno) { \
+ if (b_itemAnno) { \
+ NS_ENSURE_SUCCESS(annosvc->METHOD_ITEM(b->mItemId, annoName, \
+ B_VAL), 0); \
+ } else { \
+ NS_ENSURE_SUCCESS(annosvc->METHOD_PAGE(b_uri, annoName, \
+ B_VAL), 0); \
+ } \
+ }
+
+ if (annoType == nsIAnnotationService::TYPE_STRING) {
+ nsAutoString a_val, b_val;
+ GET_ANNOTATIONS_VALUES(GetItemAnnotationString,
+ GetPageAnnotationString, a_val, b_val);
+ value = SortComparison_StringLess(a_val, b_val);
+ }
+ else if (annoType == nsIAnnotationService::TYPE_INT32) {
+ int32_t a_val = 0, b_val = 0;
+ GET_ANNOTATIONS_VALUES(GetItemAnnotationInt32,
+ GetPageAnnotationInt32, &a_val, &b_val);
+ value = (a_val < b_val) ? -1 : (a_val > b_val) ? 1 : 0;
+ }
+ else if (annoType == nsIAnnotationService::TYPE_INT64) {
+ int64_t a_val = 0, b_val = 0;
+ GET_ANNOTATIONS_VALUES(GetItemAnnotationInt64,
+ GetPageAnnotationInt64, &a_val, &b_val);
+ value = (a_val < b_val) ? -1 : (a_val > b_val) ? 1 : 0;
+ }
+ else if (annoType == nsIAnnotationService::TYPE_DOUBLE) {
+ double a_val = 0, b_val = 0;
+ GET_ANNOTATIONS_VALUES(GetItemAnnotationDouble,
+ GetPageAnnotationDouble, &a_val, &b_val);
+ value = (a_val < b_val) ? -1 : (a_val > b_val) ? 1 : 0;
+ }
+ }
+
+ // Note we also fall back to the title-sorting route one of the items didn't
+ // have the annotation set or if both had it set but in a different storage
+ // type
+ if (value == 0)
+ return SortComparison_TitleLess(a, b, nullptr);
+
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_AnnotationGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -SortComparison_AnnotationLess(a, b, closure);
+}
+
+/**
+ * Fall back on dates for conflict resolution
+ */
+int32_t nsNavHistoryContainerResultNode::SortComparison_VisitCountLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = CompareIntegers(a->mAccessCount, b->mAccessCount);
+ if (value == 0) {
+ value = ComparePRTime(a->mTime, b->mTime);
+ if (value == 0)
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ return value;
+}
+int32_t nsNavHistoryContainerResultNode::SortComparison_VisitCountGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_VisitCountLess(a, b, closure);
+}
+
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_TagsLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ int32_t value = 0;
+ nsAutoString aTags, bTags;
+
+ nsresult rv = a->GetTags(aTags);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ rv = b->GetTags(bTags);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ value = SortComparison_StringLess(aTags, bTags);
+
+ // fall back to title sorting
+ if (value == 0)
+ value = SortComparison_TitleLess(a, b, closure);
+
+ return value;
+}
+
+int32_t nsNavHistoryContainerResultNode::SortComparison_TagsGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure)
+{
+ return -SortComparison_TagsLess(a, b, closure);
+}
+
+/**
+ * Fall back on date and bookmarked status, for conflict resolution.
+ */
+int32_t
+nsNavHistoryContainerResultNode::SortComparison_FrecencyLess(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure
+)
+{
+ int32_t value = CompareIntegers(a->mFrecency, b->mFrecency);
+ if (value == 0) {
+ value = ComparePRTime(a->mTime, b->mTime);
+ if (value == 0) {
+ value = nsNavHistoryContainerResultNode::SortComparison_Bookmark(a, b, closure);
+ }
+ }
+ return value;
+}
+int32_t
+nsNavHistoryContainerResultNode::SortComparison_FrecencyGreater(
+ nsNavHistoryResultNode* a, nsNavHistoryResultNode* b, void* closure
+)
+{
+ return -nsNavHistoryContainerResultNode::SortComparison_FrecencyLess(a, b, closure);
+}
+
+/**
+ * Searches this folder for a node with the given URI. Returns null if not
+ * found.
+ *
+ * @note Does not addref the node!
+ */
+nsNavHistoryResultNode*
+nsNavHistoryContainerResultNode::FindChildURI(const nsACString& aSpec,
+ uint32_t* aNodeIndex)
+{
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->IsURI()) {
+ if (aSpec.Equals(mChildren[i]->mURI)) {
+ *aNodeIndex = i;
+ return mChildren[i];
+ }
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * This does the work of adding a child to the container. The child can be
+ * either a container or or a single item that may even be collapsed with the
+ * adjacent ones.
+ */
+nsresult
+nsNavHistoryContainerResultNode::InsertChildAt(nsNavHistoryResultNode* aNode,
+ int32_t aIndex)
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+
+ aNode->mParent = this;
+ aNode->mIndentLevel = mIndentLevel + 1;
+ if (aNode->IsContainer()) {
+ // need to update all the new item's children
+ nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
+ container->mResult = result;
+ container->FillStats();
+ }
+
+ if (!mChildren.InsertObjectAt(aNode, aIndex))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Update our stats and notify the result's observers.
+ mAccessCount += aNode->mAccessCount;
+ if (mTime < aNode->mTime)
+ mTime = aNode->mTime;
+ if (!mParent || mParent->AreChildrenVisible()) {
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeHistoryDetailsChanged(TO_ICONTAINER(this),
+ mTime,
+ mAccessCount));
+ }
+
+ nsresult rv = ReverseUpdateStats(aNode->mAccessCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Update tree if we are visible. Note that we could be here and not
+ // expanded, like when there is a bookmark folder being updated because its
+ // parent is visible.
+ if (AreChildrenVisible())
+ NOTIFY_RESULT_OBSERVERS(result, NodeInserted(this, aNode, aIndex));
+
+ return NS_OK;
+}
+
+
+/**
+ * This locates the proper place for insertion according to the current sort
+ * and calls InsertChildAt
+ */
+nsresult
+nsNavHistoryContainerResultNode::InsertSortedChild(
+ nsNavHistoryResultNode* aNode,
+ bool aIgnoreDuplicates)
+{
+
+ if (mChildren.Count() == 0)
+ return InsertChildAt(aNode, 0);
+
+ SortComparator comparator = GetSortingComparator(GetSortType());
+ if (comparator) {
+ // When inserting a new node, it must have proper statistics because we use
+ // them to find the correct insertion point. The insert function will then
+ // recompute these statistics and fill in the proper parents and hierarchy
+ // level. Doing this twice shouldn't be a large performance penalty because
+ // when we are inserting new containers, they typically contain only one
+ // item (because we've browsed a new page).
+ if (aNode->IsContainer()) {
+ // need to update all the new item's children
+ nsNavHistoryContainerResultNode* container = aNode->GetAsContainer();
+ container->mResult = mResult;
+ container->FillStats();
+ }
+
+ nsAutoCString sortingAnnotation;
+ GetSortingAnnotation(sortingAnnotation);
+ bool itemExists;
+ uint32_t position = FindInsertionPoint(aNode, comparator,
+ sortingAnnotation.get(),
+ &itemExists);
+ if (aIgnoreDuplicates && itemExists)
+ return NS_OK;
+
+ return InsertChildAt(aNode, position);
+ }
+ return InsertChildAt(aNode, mChildren.Count());
+}
+
+/**
+ * This checks if the item at aIndex is located correctly given the sorting
+ * move. If it's not, the item is moved, and the result's observers are
+ * notified.
+ *
+ * @return true if the item position has been changed, false otherwise.
+ */
+bool
+nsNavHistoryContainerResultNode::EnsureItemPosition(uint32_t aIndex) {
+ NS_ASSERTION(aIndex < (uint32_t)mChildren.Count(), "Invalid index");
+ if (aIndex >= (uint32_t)mChildren.Count())
+ return false;
+
+ SortComparator comparator = GetSortingComparator(GetSortType());
+ if (!comparator)
+ return false;
+
+ nsAutoCString sortAnno;
+ GetSortingAnnotation(sortAnno);
+ if (!DoesChildNeedResorting(aIndex, comparator, sortAnno.get()))
+ return false;
+
+ RefPtr<nsNavHistoryResultNode> node(mChildren[aIndex]);
+ mChildren.RemoveObjectAt(aIndex);
+
+ uint32_t newIndex = FindInsertionPoint(
+ node, comparator,sortAnno.get(), nullptr);
+ mChildren.InsertObjectAt(node.get(), newIndex);
+
+ if (AreChildrenVisible()) {
+ nsNavHistoryResult* result = GetResult();
+ NOTIFY_RESULT_OBSERVERS_RET(result,
+ NodeMoved(node, this, aIndex, this, newIndex),
+ false);
+ }
+
+ return true;
+}
+
+/**
+ * This does all the work of removing a child from this container, including
+ * updating the tree if necessary. Note that we do not need to be open for
+ * this to work.
+ */
+nsresult
+nsNavHistoryContainerResultNode::RemoveChildAt(int32_t aIndex)
+{
+ NS_ASSERTION(aIndex >= 0 && aIndex < mChildren.Count(), "Invalid index");
+
+ // Hold an owning reference to keep from expiring while we work with it.
+ RefPtr<nsNavHistoryResultNode> oldNode = mChildren[aIndex];
+
+ // Update stats.
+ // XXX This assertion does not reliably pass -- investigate!! (bug 1049797)
+ // MOZ_ASSERT(mAccessCount >= mChildren[aIndex]->mAccessCount,
+ // "Invalid access count while updating!");
+ uint32_t oldAccessCount = mAccessCount;
+ mAccessCount -= mChildren[aIndex]->mAccessCount;
+
+ // Remove it from our list and notify the result's observers.
+ mChildren.RemoveObjectAt(aIndex);
+ if (AreChildrenVisible()) {
+ nsNavHistoryResult* result = GetResult();
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeRemoved(this, oldNode, aIndex));
+ }
+
+ nsresult rv = ReverseUpdateStats(mAccessCount - oldAccessCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ oldNode->OnRemoving();
+ return NS_OK;
+}
+
+
+/**
+ * Searches for matches for the given URI. If aOnlyOne is set, it will
+ * terminate as soon as it finds a single match. This would be used when there
+ * are URI results so there will only ever be one copy of any URI.
+ *
+ * When aOnlyOne is false, it will check all elements. This is for visit
+ * style results that may have multiple copies of any given URI.
+ */
+void
+nsNavHistoryContainerResultNode::RecursiveFindURIs(bool aOnlyOne,
+ nsNavHistoryContainerResultNode* aContainer, const nsCString& aSpec,
+ nsCOMArray<nsNavHistoryResultNode>* aMatches)
+{
+ for (int32_t child = 0; child < aContainer->mChildren.Count(); ++child) {
+ uint32_t type;
+ aContainer->mChildren[child]->GetType(&type);
+ if (nsNavHistoryResultNode::IsTypeURI(type)) {
+ // compare URIs
+ nsNavHistoryResultNode* uriNode = aContainer->mChildren[child];
+ if (uriNode->mURI.Equals(aSpec)) {
+ // found
+ aMatches->AppendObject(uriNode);
+ if (aOnlyOne)
+ return;
+ }
+ }
+ }
+}
+
+
+/**
+ * If aUpdateSort is true, we will also update the sorting of this item.
+ * Normally you want this to be true, but it can be false if the thing you are
+ * changing can not affect sorting (like favicons).
+ *
+ * You should NOT change any child lists as part of the callback function.
+ */
+bool
+nsNavHistoryContainerResultNode::UpdateURIs(bool aRecursive, bool aOnlyOne,
+ bool aUpdateSort, const nsCString& aSpec,
+ nsresult (*aCallback)(nsNavHistoryResultNode*, const void*, const nsNavHistoryResult*),
+ const void* aClosure)
+{
+ const nsNavHistoryResult* result = GetResult();
+ if (!result) {
+ MOZ_ASSERT(false, "Should have a result");
+ return false;
+ }
+
+ // this needs to be owning since sometimes we remove and re-insert nodes
+ // in their parents and we don't want them to go away.
+ nsCOMArray<nsNavHistoryResultNode> matches;
+
+ if (aRecursive) {
+ RecursiveFindURIs(aOnlyOne, this, aSpec, &matches);
+ } else if (aOnlyOne) {
+ uint32_t nodeIndex;
+ nsNavHistoryResultNode* node = FindChildURI(aSpec, &nodeIndex);
+ if (node)
+ matches.AppendObject(node);
+ } else {
+ MOZ_ASSERT(false,
+ "UpdateURIs does not handle nonrecursive updates of multiple items.");
+ // this case easy to add if you need it, just find all the matching URIs
+ // at this level. However, this isn't currently used. History uses
+ // recursive, Bookmarks uses one level and knows that the match is unique.
+ return false;
+ }
+
+ if (matches.Count() == 0)
+ return false;
+
+ // PERFORMANCE: This updates each container for each child in it that
+ // changes. In some cases, many elements have changed inside the same
+ // container. It would be better to compose a list of containers, and
+ // update each one only once for all the items that have changed in it.
+ for (int32_t i = 0; i < matches.Count(); ++i)
+ {
+ nsNavHistoryResultNode* node = matches[i];
+ nsNavHistoryContainerResultNode* parent = node->mParent;
+ if (!parent) {
+ MOZ_ASSERT(false, "All URI nodes being updated must have parents");
+ continue;
+ }
+
+ uint32_t oldAccessCount = node->mAccessCount;
+ PRTime oldTime = node->mTime;
+ aCallback(node, aClosure, result);
+
+ if (oldAccessCount != node->mAccessCount || oldTime != node->mTime) {
+ parent->mAccessCount += node->mAccessCount - oldAccessCount;
+ if (node->mTime > parent->mTime)
+ parent->mTime = node->mTime;
+ if (parent->AreChildrenVisible()) {
+ NOTIFY_RESULT_OBSERVERS_RET(result,
+ NodeHistoryDetailsChanged(
+ TO_ICONTAINER(parent),
+ parent->mTime,
+ parent->mAccessCount),
+ true);
+ }
+ DebugOnly<nsresult> rv = parent->ReverseUpdateStats(node->mAccessCount - oldAccessCount);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "should be able to ReverseUpdateStats");
+ }
+
+ if (aUpdateSort) {
+ int32_t childIndex = parent->FindChild(node);
+ MOZ_ASSERT(childIndex >= 0, "Could not find child we just got a reference to");
+ if (childIndex >= 0)
+ parent->EnsureItemPosition(childIndex);
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * This is used to update the titles in the tree. This is called from both
+ * query and bookmark folder containers to update the tree. Bookmark folders
+ * should be sure to set recursive to false, since child folders will have
+ * their own callbacks registered.
+ */
+static nsresult setTitleCallback(nsNavHistoryResultNode* aNode,
+ const void* aClosure,
+ const nsNavHistoryResult* aResult)
+{
+ const nsACString* newTitle = static_cast<const nsACString*>(aClosure);
+ aNode->mTitle = *newTitle;
+
+ if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
+ NOTIFY_RESULT_OBSERVERS(aResult, NodeTitleChanged(aNode, *newTitle));
+
+ return NS_OK;
+}
+nsresult
+nsNavHistoryContainerResultNode::ChangeTitles(nsIURI* aURI,
+ const nsACString& aNewTitle,
+ bool aRecursive,
+ bool aOnlyOne)
+{
+ // uri string
+ nsAutoCString uriString;
+ nsresult rv = aURI->GetSpec(uriString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The recursive function will update the result's tree nodes, but only if we
+ // give it a non-null pointer. So if there isn't a tree, just pass nullptr
+ // so it doesn't bother trying to call the result.
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+
+ uint16_t sortType = GetSortType();
+ bool updateSorting =
+ (sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING);
+
+ UpdateURIs(aRecursive, aOnlyOne, updateSorting, uriString,
+ setTitleCallback,
+ static_cast<const void*>(&aNewTitle));
+
+ return NS_OK;
+}
+
+
+/**
+ * Complex containers (folders and queries) will override this. Here, we
+ * handle the case of simple containers (like host groups) where the children
+ * are always stored.
+ */
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetHasChildren(bool *aHasChildren)
+{
+ *aHasChildren = (mChildren.Count() > 0);
+ return NS_OK;
+}
+
+
+/**
+ * @throws if this node is closed.
+ */
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetChildCount(uint32_t* aChildCount)
+{
+ if (!mExpanded)
+ return NS_ERROR_NOT_AVAILABLE;
+ *aChildCount = mChildren.Count();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetChild(uint32_t aIndex,
+ nsINavHistoryResultNode** _retval)
+{
+ if (!mExpanded)
+ return NS_ERROR_NOT_AVAILABLE;
+ if (aIndex >= uint32_t(mChildren.Count()))
+ return NS_ERROR_INVALID_ARG;
+ NS_ADDREF(*_retval = mChildren[aIndex]);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::GetChildIndex(nsINavHistoryResultNode* aNode,
+ uint32_t* _retval)
+{
+ if (!mExpanded)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ int32_t nodeIndex = FindChild(static_cast<nsNavHistoryResultNode*>(aNode));
+ if (nodeIndex == -1)
+ return NS_ERROR_INVALID_ARG;
+
+ *_retval = nodeIndex;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryContainerResultNode::FindNodeByDetails(const nsACString& aURIString,
+ PRTime aTime,
+ int64_t aItemId,
+ bool aRecursive,
+ nsINavHistoryResultNode** _retval) {
+ if (!mExpanded)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = nullptr;
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->mURI.Equals(aURIString) &&
+ mChildren[i]->mTime == aTime &&
+ mChildren[i]->mItemId == aItemId) {
+ *_retval = mChildren[i];
+ break;
+ }
+
+ if (aRecursive && mChildren[i]->IsContainer()) {
+ nsNavHistoryContainerResultNode* asContainer =
+ mChildren[i]->GetAsContainer();
+ if (asContainer->mExpanded) {
+ nsresult rv = asContainer->FindNodeByDetails(aURIString, aTime,
+ aItemId,
+ aRecursive,
+ _retval);
+
+ if (NS_SUCCEEDED(rv) && _retval)
+ break;
+ }
+ }
+ }
+ NS_IF_ADDREF(*_retval);
+ return NS_OK;
+}
+
+/**
+ * HOW QUERY UPDATING WORKS
+ *
+ * Queries are different than bookmark folders in that we can not always do
+ * dynamic updates (easily) and updates are more expensive. Therefore, we do
+ * NOT query if we are not open and want to see if we have any children (for
+ * drawing a twisty) and always assume we will.
+ *
+ * When the container is opened, we execute the query and register the
+ * listeners. Like bookmark folders, we stay registered even when closed, and
+ * clear ourselves as soon as a message comes in. This lets us respond quickly
+ * if the user closes and reopens the container.
+ *
+ * We try to handle the most common notifications for the most common query
+ * types dynamically, that is, figuring out what should happen in response to
+ * a message without doing a requery. For complex changes or complex queries,
+ * we give up and requery.
+ */
+NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryQueryResultNode,
+ nsNavHistoryContainerResultNode,
+ nsINavHistoryQueryResultNode)
+
+nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
+ const nsACString& aTitle, const nsACString& aIconURI,
+ const nsACString& aQueryURI) :
+ nsNavHistoryContainerResultNode(aQueryURI, aTitle, aIconURI,
+ nsNavHistoryResultNode::RESULT_TYPE_QUERY,
+ nullptr),
+ mLiveUpdate(QUERYUPDATE_COMPLEX_WITH_BOOKMARKS),
+ mHasSearchTerms(false),
+ mContentsValid(false),
+ mBatchChanges(0)
+{
+}
+
+nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
+ const nsACString& aTitle, const nsACString& aIconURI,
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions) :
+ nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aIconURI,
+ nsNavHistoryResultNode::RESULT_TYPE_QUERY,
+ aOptions),
+ mQueries(aQueries),
+ mContentsValid(false),
+ mBatchChanges(0),
+ mTransitions(mQueries[0]->Transitions())
+{
+ NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ASSERTION(history, "History service missing");
+ if (history) {
+ mLiveUpdate = history->GetUpdateRequirements(mQueries, mOptions,
+ &mHasSearchTerms);
+ }
+
+ // Collect transitions shared by all queries.
+ for (int32_t i = 1; i < mQueries.Count(); ++i) {
+ const nsTArray<uint32_t>& queryTransitions = mQueries[i]->Transitions();
+ for (uint32_t j = 0; j < mTransitions.Length() ; ++j) {
+ uint32_t transition = mTransitions.SafeElementAt(j, 0);
+ if (transition && !queryTransitions.Contains(transition))
+ mTransitions.RemoveElement(transition);
+ }
+ }
+}
+
+nsNavHistoryQueryResultNode::nsNavHistoryQueryResultNode(
+ const nsACString& aTitle, const nsACString& aIconURI,
+ PRTime aTime,
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions) :
+ nsNavHistoryContainerResultNode(EmptyCString(), aTitle, aTime, aIconURI,
+ nsNavHistoryResultNode::RESULT_TYPE_QUERY,
+ aOptions),
+ mQueries(aQueries),
+ mContentsValid(false),
+ mBatchChanges(0),
+ mTransitions(mQueries[0]->Transitions())
+{
+ NS_ASSERTION(aQueries.Count() > 0, "Must have at least one query");
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ASSERTION(history, "History service missing");
+ if (history) {
+ mLiveUpdate = history->GetUpdateRequirements(mQueries, mOptions,
+ &mHasSearchTerms);
+ }
+
+ // Collect transitions shared by all queries.
+ for (int32_t i = 1; i < mQueries.Count(); ++i) {
+ const nsTArray<uint32_t>& queryTransitions = mQueries[i]->Transitions();
+ for (uint32_t j = 0; j < mTransitions.Length() ; ++j) {
+ uint32_t transition = mTransitions.SafeElementAt(j, 0);
+ if (transition && !queryTransitions.Contains(transition))
+ mTransitions.RemoveElement(transition);
+ }
+ }
+}
+
+nsNavHistoryQueryResultNode::~nsNavHistoryQueryResultNode() {
+ // Remove this node from result's observers. We don't need to be notified
+ // anymore.
+ if (mResult && mResult->mAllBookmarksObservers.Contains(this))
+ mResult->RemoveAllBookmarksObserver(this);
+ if (mResult && mResult->mHistoryObservers.Contains(this))
+ mResult->RemoveHistoryObserver(this);
+}
+
+/**
+ * Whoever made us may want non-expanding queries. However, we always expand
+ * when we are the root node, or else asking for non-expanding queries would be
+ * useless. A query node is not expandable if excludeItems is set or if
+ * expandQueries is unset.
+ */
+bool
+nsNavHistoryQueryResultNode::CanExpand()
+{
+ if (IsContainersQuery())
+ return true;
+
+ // If ExcludeItems is set on the root or on the node itself, don't expand.
+ if ((mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+ Options()->ExcludeItems())
+ return false;
+
+ // Check the ancestor container.
+ nsNavHistoryQueryOptions* options = GetGeneratingOptions();
+ if (options) {
+ if (options->ExcludeItems())
+ return false;
+ if (options->ExpandQueries())
+ return true;
+ }
+
+ if (mResult && mResult->mRootNode == this)
+ return true;
+
+ return false;
+}
+
+
+/**
+ * Some query with a particular result type can contain other queries. They
+ * must be always expandable
+ */
+bool
+nsNavHistoryQueryResultNode::IsContainersQuery()
+{
+ uint16_t resultType = Options()->ResultType();
+ return resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY;
+}
+
+
+/**
+ * Here we do not want to call ContainerResultNode::OnRemoving since our own
+ * ClearChildren will do the same thing and more (unregister the observers).
+ * The base ResultNode::OnRemoving will clear some regular node stats, so it
+ * is OK.
+ */
+void
+nsNavHistoryQueryResultNode::OnRemoving()
+{
+ nsNavHistoryResultNode::OnRemoving();
+ ClearChildren(true);
+ mResult = nullptr;
+}
+
+
+/**
+ * Marks the container as open, rebuilding results if they are invalid. We
+ * may still have valid results if the container was previously open and
+ * nothing happened since closing it.
+ *
+ * We do not handle CloseContainer specially. The default one just marks the
+ * container as closed, but doesn't actually mark the results as invalid.
+ * The results will be invalidated by the next history or bookmark
+ * notification that comes in. This means if you open and close the item
+ * without anything happening in between, it will be fast (this actually
+ * happens when results are used as menus).
+ */
+nsresult
+nsNavHistoryQueryResultNode::OpenContainer()
+{
+ NS_ASSERTION(!mExpanded, "Container must be closed to open it");
+ mExpanded = true;
+
+ nsresult rv;
+
+ if (!CanExpand())
+ return NS_OK;
+ if (!mContentsValid) {
+ rv = FillChildren();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = NotifyOnStateChange(STATE_CLOSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+/**
+ * When we have valid results we can always give an exact answer. When we
+ * don't we just assume we'll have results, since actually doing the query
+ * might be hard. This is used to draw twisties on the tree, so precise results
+ * don't matter.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetHasChildren(bool* aHasChildren)
+{
+ *aHasChildren = false;
+
+ if (!CanExpand()) {
+ return NS_OK;
+ }
+
+ uint16_t resultType = mOptions->ResultType();
+
+ // Tags are always populated, otherwise they are removed.
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS) {
+ *aHasChildren = true;
+ return NS_OK;
+ }
+
+ // For tag containers query we must check if we have any tag
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY) {
+ nsCOMPtr<nsITaggingService> tagging =
+ do_GetService(NS_TAGGINGSERVICE_CONTRACTID);
+ if (tagging) {
+ bool hasTags;
+ *aHasChildren = NS_SUCCEEDED(tagging->GetHasTags(&hasTags)) && hasTags;
+ }
+ return NS_OK;
+ }
+
+ // For history containers query we must check if we have any history
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ return history->GetHasHistoryEntries(aHasChildren);
+ }
+
+ //XXX: For other containers queries we must:
+ // 1. If it's open, just check mChildren for containers
+ // 2. Else null the view (keep it in a var), open container, check mChildren
+ // for containers, close container, reset the view
+
+ if (mContentsValid) {
+ *aHasChildren = (mChildren.Count() > 0);
+ return NS_OK;
+ }
+ *aHasChildren = true;
+ return NS_OK;
+}
+
+
+/**
+ * This doesn't just return mURI because in the case of queries that may
+ * be lazily constructed from the query objects.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetUri(nsACString& aURI)
+{
+ nsresult rv = VerifyQueriesSerialized();
+ NS_ENSURE_SUCCESS(rv, rv);
+ aURI = mURI;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetFolderItemId(int64_t* aItemId)
+{
+ *aItemId = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetTargetFolderGuid(nsACString& aGuid) {
+ aGuid = EmptyCString();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetQueries(uint32_t* queryCount,
+ nsINavHistoryQuery*** queries)
+{
+ nsresult rv = VerifyQueriesParsed();
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(mQueries.Count() > 0, "Must have >= 1 query");
+
+ *queries = static_cast<nsINavHistoryQuery**>
+ (moz_xmalloc(mQueries.Count() * sizeof(nsINavHistoryQuery*)));
+ NS_ENSURE_TRUE(*queries, NS_ERROR_OUT_OF_MEMORY);
+
+ for (int32_t i = 0; i < mQueries.Count(); ++i)
+ NS_ADDREF((*queries)[i] = mQueries[i]);
+ *queryCount = mQueries.Count();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetQueryOptions(
+ nsINavHistoryQueryOptions** aQueryOptions)
+{
+ *aQueryOptions = Options();
+ NS_ADDREF(*aQueryOptions);
+ return NS_OK;
+}
+
+/**
+ * Safe options getter, ensures queries are parsed first.
+ */
+nsNavHistoryQueryOptions*
+nsNavHistoryQueryResultNode::Options()
+{
+ nsresult rv = VerifyQueriesParsed();
+ if (NS_FAILED(rv))
+ return nullptr;
+ NS_ASSERTION(mOptions, "Options invalid, cannot generate from URI");
+ return mOptions;
+}
+
+
+nsresult
+nsNavHistoryQueryResultNode::VerifyQueriesParsed()
+{
+ if (mQueries.Count() > 0) {
+ NS_ASSERTION(mOptions, "If a result has queries, it also needs options");
+ return NS_OK;
+ }
+ NS_ASSERTION(!mURI.IsEmpty(),
+ "Query nodes must have either a URI or query/options");
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = history->QueryStringToQueryArray(mURI, &mQueries,
+ getter_AddRefs(mOptions));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mLiveUpdate = history->GetUpdateRequirements(mQueries, mOptions,
+ &mHasSearchTerms);
+ return NS_OK;
+}
+
+
+nsresult
+nsNavHistoryQueryResultNode::VerifyQueriesSerialized()
+{
+ if (!mURI.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_ASSERTION(mQueries.Count() > 0 && mOptions,
+ "Query nodes must have either a URI or query/options");
+
+ nsTArray<nsINavHistoryQuery*> flatQueries;
+ flatQueries.SetCapacity(mQueries.Count());
+ for (int32_t i = 0; i < mQueries.Count(); ++i)
+ flatQueries.AppendElement(static_cast<nsINavHistoryQuery*>
+ (mQueries.ObjectAt(i)));
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = history->QueriesToQueryString(flatQueries.Elements(),
+ flatQueries.Length(),
+ mOptions, mURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(!mURI.IsEmpty());
+ return NS_OK;
+}
+
+
+nsresult
+nsNavHistoryQueryResultNode::FillChildren()
+{
+ NS_ASSERTION(!mContentsValid,
+ "Don't call FillChildren when contents are valid");
+ NS_ASSERTION(mChildren.Count() == 0,
+ "We are trying to fill children when there already are some");
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ // get the results from the history service
+ nsresult rv = VerifyQueriesParsed();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = history->GetQueryResults(this, mQueries, mOptions, &mChildren);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // it is important to call FillStats to fill in the parents on all
+ // nodes and the result node pointers on the containers
+ FillStats();
+
+ uint16_t sortType = GetSortType();
+
+ if (mResult && mResult->mNeedsToApplySortingMode) {
+ // We should repopulate container and then apply sortingMode. To avoid
+ // sorting 2 times we simply do that here.
+ mResult->SetSortingMode(mResult->mSortingMode);
+ }
+ else if (mOptions->QueryType() != nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY ||
+ sortType != nsINavHistoryQueryOptions::SORT_BY_NONE) {
+ // The default SORT_BY_NONE sorts by the bookmark index (position),
+ // which we do not have for history queries.
+ // Once we've computed all tree stats, we can sort, because containers will
+ // then have proper visit counts and dates.
+ SortComparator comparator = GetSortingComparator(GetSortType());
+ if (comparator) {
+ nsAutoCString sortingAnnotation;
+ GetSortingAnnotation(sortingAnnotation);
+ // Usually containers queries results comes already sorted from the
+ // database, but some locales could have special rules to sort by title.
+ // RecursiveSort won't apply these rules to containers in containers
+ // queries because when setting sortingMode on the result we want to sort
+ // contained items (bug 473157).
+ // Base container RecursiveSort will sort both our children and all
+ // descendants, and is used in this case because we have to do manual
+ // title sorting.
+ // Query RecursiveSort will instead only sort descendants if we are a
+ // constinaersQuery, e.g. a grouped query that will return other queries.
+ // For other type of queries it will act as the base one.
+ if (IsContainersQuery() &&
+ sortType == mOptions->SortingMode() &&
+ (sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_TITLE_DESCENDING))
+ nsNavHistoryContainerResultNode::RecursiveSort(sortingAnnotation.get(), comparator);
+ else
+ RecursiveSort(sortingAnnotation.get(), comparator);
+ }
+ }
+
+ // if we are limiting our results remove items from the end of the
+ // mChildren array after sorting. This is done for root node only.
+ // note, if count < max results, we won't do anything.
+ if (!mParent && mOptions->MaxResults()) {
+ while ((uint32_t)mChildren.Count() > mOptions->MaxResults())
+ mChildren.RemoveObjectAt(mChildren.Count() - 1);
+ }
+
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+
+ if (mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY ||
+ mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_UNIFIED) {
+ // Date containers that contain site containers have no reason to observe
+ // history, if the inside site container is expanded it will update,
+ // otherwise we are going to refresh the parent query.
+ if (!mParent || mParent->mOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
+ // register with the result for history updates
+ result->AddHistoryObserver(this);
+ }
+ }
+
+ if (mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_BOOKMARKS ||
+ mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_UNIFIED ||
+ mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS ||
+ mHasSearchTerms) {
+ // register with the result for bookmark updates
+ result->AddAllBookmarksObserver(this);
+ }
+
+ mContentsValid = true;
+ return NS_OK;
+}
+
+
+/**
+ * Call with unregister = false when we are going to update the children (for
+ * example, when the container is open). This will clear the list and notify
+ * all the children that they are going away.
+ *
+ * When the results are becoming invalid and we are not going to refresh them,
+ * set unregister = true, which will unregister the listener from the
+ * result if any. We use unregister = false when we are refreshing the list
+ * immediately so want to stay a notifier.
+ */
+void
+nsNavHistoryQueryResultNode::ClearChildren(bool aUnregister)
+{
+ for (int32_t i = 0; i < mChildren.Count(); ++i)
+ mChildren[i]->OnRemoving();
+ mChildren.Clear();
+
+ if (aUnregister && mContentsValid) {
+ nsNavHistoryResult* result = GetResult();
+ if (result) {
+ result->RemoveHistoryObserver(this);
+ result->RemoveAllBookmarksObserver(this);
+ }
+ }
+ mContentsValid = false;
+}
+
+
+/**
+ * This is called to update the result when something has changed that we
+ * can not incrementally update.
+ */
+nsresult
+nsNavHistoryQueryResultNode::Refresh()
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mBatchInProgress) {
+ result->requestRefresh(this);
+ return NS_OK;
+ }
+
+ // This is not a root node but it does not have a parent - this means that
+ // the node has already been cleared and it is now called, because it was
+ // left in a local copy of the observers array.
+ if (mIndentLevel > -1 && !mParent)
+ return NS_OK;
+
+ // Do not refresh if we are not expanded or if we are child of a query
+ // containing other queries. In this case calling Refresh for each child
+ // query could cause a major slowdown. We should not refresh nested
+ // queries, since we will already refresh the parent one.
+ if (!mExpanded ||
+ (mParent && mParent->IsQuery() &&
+ mParent->GetAsQuery()->IsContainersQuery())) {
+ // Don't update, just invalidate and unhook
+ ClearChildren(true);
+ return NS_OK; // no updates in tree state
+ }
+
+ if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS)
+ ClearChildren(true);
+ else
+ ClearChildren(false);
+
+ // Ignore errors from FillChildren, since we will still want to refresh
+ // the tree (there just might not be anything in it on error).
+ (void)FillChildren();
+
+ NOTIFY_RESULT_OBSERVERS(result, InvalidateContainer(TO_CONTAINER(this)));
+ return NS_OK;
+}
+
+
+/**
+ * Here, we override GetSortType to return the current sorting for this
+ * query. GetSortType is used when dynamically inserting query results so we
+ * can see which comparator we should use to find the proper insertion point
+ * (it shouldn't be called from folder containers which maintain their own
+ * sorting).
+ *
+ * Normally, the container just forwards it up the chain. This is what we want
+ * for host groups, for example. For queries, we often want to use the query's
+ * sorting mode.
+ *
+ * However, we only use this query node's sorting when it is not the root.
+ * When it is the root, we use the result's sorting mode. This is because
+ * there are two cases:
+ * - You are looking at a bookmark hierarchy that contains an embedded
+ * result. We should always use the query's sort ordering since the result
+ * node's headers have nothing to do with us (and are disabled).
+ * - You are looking at a query in the tree. In this case, we want the
+ * result sorting to override ours (it should be initialized to the same
+ * sorting mode).
+ */
+uint16_t
+nsNavHistoryQueryResultNode::GetSortType()
+{
+ if (mParent)
+ return mOptions->SortingMode();
+ if (mResult)
+ return mResult->mSortingMode;
+
+ // This is a detached container, just use natural order.
+ return nsINavHistoryQueryOptions::SORT_BY_NONE;
+}
+
+
+void
+nsNavHistoryQueryResultNode::GetSortingAnnotation(nsACString& aAnnotation) {
+ if (mParent) {
+ // use our sorting, we are not the root
+ mOptions->GetSortingAnnotation(aAnnotation);
+ }
+ else if (mResult) {
+ aAnnotation.Assign(mResult->mSortingAnnotation);
+ }
+}
+
+void
+nsNavHistoryQueryResultNode::RecursiveSort(
+ const char* aData, SortComparator aComparator)
+{
+ void* data = const_cast<void*>(static_cast<const void*>(aData));
+
+ if (!IsContainersQuery())
+ mChildren.Sort(aComparator, data);
+
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->IsContainer())
+ mChildren[i]->GetAsContainer()->RecursiveSort(aData, aComparator);
+ }
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetSkipTags(bool *aSkipTags)
+{
+ *aSkipTags = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::GetSkipDescendantsOnItemRemoval(bool *aSkipDescendantsOnItemRemoval)
+{
+ *aSkipDescendantsOnItemRemoval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnBeginUpdateBatch()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnEndUpdateBatch()
+{
+ // If the query has no children it's possible it's not yet listening to
+ // bookmarks changes, in such a case it's safer to force a refresh to gather
+ // eventual new nodes matching query options.
+ if (mChildren.Count() == 0) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mBatchChanges = 0;
+ return NS_OK;
+}
+
+static nsresult setHistoryDetailsCallback(nsNavHistoryResultNode* aNode,
+ const void* aClosure,
+ const nsNavHistoryResult* aResult)
+{
+ const nsNavHistoryResultNode* updatedNode =
+ static_cast<const nsNavHistoryResultNode*>(aClosure);
+
+ aNode->mAccessCount = updatedNode->mAccessCount;
+ aNode->mTime = updatedNode->mTime;
+ aNode->mFrecency = updatedNode->mFrecency;
+ aNode->mHidden = updatedNode->mHidden;
+
+ return NS_OK;
+}
+
+/**
+ * Here we need to update all copies of the URI we have with the new visit
+ * count, and potentially add a new entry in our query. This is the most
+ * common update operation and it is important that it be as efficient as
+ * possible.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnVisit(nsIURI* aURI, int64_t aVisitId,
+ PRTime aTime, int64_t aSessionId,
+ int64_t aReferringId,
+ uint32_t aTransitionType,
+ const nsACString& aGUID,
+ bool aHidden,
+ uint32_t* aAdded)
+{
+ if (aHidden && !mOptions->IncludeHidden())
+ return NS_OK;
+
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mBatchInProgress &&
+ ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ switch(mLiveUpdate) {
+ case QUERYUPDATE_HOST: {
+ // For these simple yet common cases we can check the host ourselves
+ // before doing the overhead of creating a new result node.
+ MOZ_ASSERT(mQueries.Count() == 1,
+ "Host updated queries can have only one object");
+ RefPtr<nsNavHistoryQuery> query = do_QueryObject(mQueries[0]);
+
+ bool hasDomain;
+ query->GetHasDomain(&hasDomain);
+ if (!hasDomain)
+ return NS_OK;
+
+ nsAutoCString host;
+ if (NS_FAILED(aURI->GetAsciiHost(host)))
+ return NS_OK;
+
+ if (!query->Domain().Equals(host))
+ return NS_OK;
+
+ // Fall through to check the time, if the time is not present it will
+ // still match.
+ MOZ_FALLTHROUGH;
+ }
+
+ case QUERYUPDATE_TIME: {
+ // For these simple yet common cases we can check the time ourselves
+ // before doing the overhead of creating a new result node.
+ MOZ_ASSERT(mQueries.Count() == 1,
+ "Time updated queries can have only one object");
+ RefPtr<nsNavHistoryQuery> query = do_QueryObject(mQueries[0]);
+
+ bool hasIt;
+ query->GetHasBeginTime(&hasIt);
+ if (hasIt) {
+ PRTime beginTime = history->NormalizeTime(query->BeginTimeReference(),
+ query->BeginTime());
+ if (aTime < beginTime)
+ return NS_OK; // before our time range
+ }
+ query->GetHasEndTime(&hasIt);
+ if (hasIt) {
+ PRTime endTime = history->NormalizeTime(query->EndTimeReference(),
+ query->EndTime());
+ if (aTime > endTime)
+ return NS_OK; // after our time range
+ }
+ // Now we know that our visit satisfies the time range, fall through to
+ // the QUERYUPDATE_SIMPLE case below.
+ MOZ_FALLTHROUGH;
+ }
+
+ case QUERYUPDATE_SIMPLE: {
+ // If all of the queries are filtered by some transitions, skip the
+ // update if aTransitionType doesn't match any of them.
+ if (mTransitions.Length() > 0 && !mTransitions.Contains(aTransitionType))
+ return NS_OK;
+
+ // The history service can tell us whether the new item should appear
+ // in the result. We first have to construct a node for it to check.
+ RefPtr<nsNavHistoryResultNode> addition;
+ nsresult rv = history->VisitIdToResultNode(aVisitId, mOptions,
+ getter_AddRefs(addition));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(addition);
+ addition->mTransitionType = aTransitionType;
+ if (!history->EvaluateQueryForNode(mQueries, mOptions, addition))
+ return NS_OK; // don't need to include in our query
+
+ if (mOptions->ResultType() == nsNavHistoryQueryOptions::RESULTS_AS_VISIT) {
+ // If this is a visit type query, just insert the new visit. We never
+ // update visits, only add or remove them.
+ rv = InsertSortedChild(addition);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ uint16_t sortType = GetSortType();
+ bool updateSorting =
+ sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING;
+
+ if (!UpdateURIs(false, true, updateSorting, addition->mURI,
+ setHistoryDetailsCallback,
+ const_cast<void*>(static_cast<void*>(addition.get())))) {
+ // Couldn't find a node to update.
+ rv = InsertSortedChild(addition);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ if (aAdded)
+ ++(*aAdded);
+
+ break;
+ }
+
+ case QUERYUPDATE_COMPLEX:
+ case QUERYUPDATE_COMPLEX_WITH_BOOKMARKS:
+ // need to requery in complex cases
+ return Refresh();
+
+ default:
+ MOZ_ASSERT(false, "Invalid value for mLiveUpdate");
+ return Refresh();
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * Find every node that matches this URI and rename it. We try to do
+ * incremental updates here, even when we are closed, because changing titles
+ * is easier than requerying if we are invalid.
+ *
+ * This actually gets called a lot. Typically, we will get an AddURI message
+ * when the user visits the page, and then the title will be set asynchronously
+ * when the title element of the page is parsed.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnTitleChanged(nsIURI* aURI,
+ const nsAString& aPageTitle,
+ const nsACString& aGUID)
+{
+ if (!mExpanded) {
+ // When we are not expanded, we don't update, just invalidate and unhook.
+ // It would still be pretty easy to traverse the results and update the
+ // titles, but when a title changes, its unlikely that it will be the only
+ // thing. Therefore, we just give up.
+ ClearChildren(true);
+ return NS_OK; // no updates in tree state
+ }
+
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mBatchInProgress &&
+ ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ // compute what the new title should be
+ NS_ConvertUTF16toUTF8 newTitle(aPageTitle);
+
+ bool onlyOneEntry =
+ mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_URI ||
+ mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS;
+
+ // See if our queries have any search term matching.
+ if (mHasSearchTerms) {
+ // Find all matching URI nodes.
+ nsCOMArray<nsNavHistoryResultNode> matches;
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
+ if (matches.Count() == 0) {
+ // This could be a new node matching the query, thus we could need
+ // to add it to the result.
+ RefPtr<nsNavHistoryResultNode> node;
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (history->EvaluateQueryForNode(mQueries, mOptions, node)) {
+ rv = InsertSortedChild(node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ for (int32_t i = 0; i < matches.Count(); ++i) {
+ // For each matched node we check if it passes the query filter, if not
+ // we remove the node from the result, otherwise we'll update the title
+ // later.
+ nsNavHistoryResultNode* node = matches[i];
+ // We must check the node with the new title.
+ node->mTitle = newTitle;
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ if (!history->EvaluateQueryForNode(mQueries, mOptions, node)) {
+ nsNavHistoryContainerResultNode* parent = node->mParent;
+ // URI nodes should always have parents
+ NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
+ int32_t childIndex = parent->FindChild(node);
+ NS_ASSERTION(childIndex >= 0, "Child not found in parent");
+ parent->RemoveChildAt(childIndex);
+ }
+ }
+ }
+
+ return ChangeTitles(aURI, newTitle, true, onlyOneEntry);
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnFrecencyChanged(nsIURI* aURI,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnManyFrecenciesChanged()
+{
+ return NS_OK;
+}
+
+
+/**
+ * Here, we can always live update by just deleting all occurrences of
+ * the given URI.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnDeleteURI(nsIURI* aURI,
+ const nsACString& aGUID,
+ uint16_t aReason)
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mBatchInProgress &&
+ ++mBatchChanges > MAX_BATCH_CHANGES_BEFORE_REFRESH) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ if (IsContainersQuery()) {
+ // Incremental updates of query returning queries are pretty much
+ // complicated. In this case it's possible one of the child queries has
+ // no more children and it should be removed. Unfortunately there is no
+ // way to know that without executing the child query and counting results.
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+ }
+
+ bool onlyOneEntry = (mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_URI ||
+ mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS);
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMArray<nsNavHistoryResultNode> matches;
+ RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
+ for (int32_t i = 0; i < matches.Count(); ++i) {
+ nsNavHistoryResultNode* node = matches[i];
+ nsNavHistoryContainerResultNode* parent = node->mParent;
+ // URI nodes should always have parents
+ NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
+
+ int32_t childIndex = parent->FindChild(node);
+ NS_ASSERTION(childIndex >= 0, "Child not found in parent");
+ parent->RemoveChildAt(childIndex);
+ if (parent->mChildren.Count() == 0 && parent->IsQuery() &&
+ parent->mIndentLevel > -1) {
+ // When query subcontainers (like hosts) get empty we should remove them
+ // as well. If the parent is not the root node, append it to our list
+ // and it will get evaluated later in the loop.
+ matches.AppendObject(parent);
+ }
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnClearHistory()
+{
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+
+static nsresult setFaviconCallback(nsNavHistoryResultNode* aNode,
+ const void* aClosure,
+ const nsNavHistoryResult* aResult)
+{
+ const nsCString* newFavicon = static_cast<const nsCString*>(aClosure);
+ aNode->mFaviconURI = *newFavicon;
+
+ if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
+ NOTIFY_RESULT_OBSERVERS(aResult, NodeIconChanged(aNode));
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnPageChanged(nsIURI* aURI,
+ uint32_t aChangedAttribute,
+ const nsAString& aNewValue,
+ const nsACString& aGUID)
+{
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (aChangedAttribute) {
+ case nsINavHistoryObserver::ATTRIBUTE_FAVICON: {
+ NS_ConvertUTF16toUTF8 newFavicon(aNewValue);
+ bool onlyOneEntry = (mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_URI ||
+ mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS);
+ UpdateURIs(true, onlyOneEntry, false, spec, setFaviconCallback,
+ &newFavicon);
+ break;
+ }
+ default:
+ NS_WARNING("Unknown page changed notification");
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnDeleteVisits(nsIURI* aURI,
+ PRTime aVisitTime,
+ const nsACString& aGUID,
+ uint16_t aReason,
+ uint32_t aTransitionType)
+{
+ NS_PRECONDITION(mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY,
+ "Bookmarks queries should not get a OnDeleteVisits notification");
+ if (aVisitTime == 0) {
+ // All visits for this uri have been removed, but the uri won't be removed
+ // from the databse, most likely because it's a bookmark. For a history
+ // query this is equivalent to a onDeleteURI notification.
+ nsresult rv = OnDeleteURI(aURI, aGUID, aReason);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (aTransitionType > 0) {
+ // All visits for aTransitionType have been removed, if the query is
+ // filtering on such transition type, this is equivalent to an onDeleteURI
+ // notification.
+ if (mTransitions.Length() > 0 && mTransitions.Contains(aTransitionType)) {
+ nsresult rv = OnDeleteURI(aURI, aGUID, aReason);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNavHistoryQueryResultNode::NotifyIfTagsChanged(nsIURI* aURI)
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ nsAutoCString spec;
+ nsresult rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool onlyOneEntry = (mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_URI ||
+ mOptions->ResultType() ==
+ nsINavHistoryQueryOptions::RESULTS_AS_TAG_CONTENTS
+ );
+
+ // Find matching URI nodes.
+ RefPtr<nsNavHistoryResultNode> node;
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+
+ nsCOMArray<nsNavHistoryResultNode> matches;
+ RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
+
+ if (matches.Count() == 0 && mHasSearchTerms) {
+ // A new tag has been added, it's possible it matches our query.
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (history->EvaluateQueryForNode(mQueries, mOptions, node)) {
+ rv = InsertSortedChild(node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ for (int32_t i = 0; i < matches.Count(); ++i) {
+ nsNavHistoryResultNode* node = matches[i];
+ // Force a tags update before checking the node.
+ node->mTags.SetIsVoid(true);
+ nsAutoString tags;
+ rv = node->GetTags(tags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // It's possible now this node does not respect anymore the conditions.
+ // In such a case it should be removed.
+ if (mHasSearchTerms &&
+ !history->EvaluateQueryForNode(mQueries, mOptions, node)) {
+ nsNavHistoryContainerResultNode* parent = node->mParent;
+ // URI nodes should always have parents
+ NS_ENSURE_TRUE(parent, NS_ERROR_UNEXPECTED);
+ int32_t childIndex = parent->FindChild(node);
+ NS_ASSERTION(childIndex >= 0, "Child not found in parent");
+ parent->RemoveChildAt(childIndex);
+ }
+ else {
+ NOTIFY_RESULT_OBSERVERS(result, NodeTagsChanged(node));
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * These are the bookmark observer functions for query nodes. They listen
+ * for bookmark events and refresh the results if we have any dependence on
+ * the bookmark system.
+ */
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnItemAdded(int64_t aItemId,
+ int64_t aParentId,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aTitle,
+ PRTime aDateAdded,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
+ mLiveUpdate != QUERYUPDATE_SIMPLE && mLiveUpdate != QUERYUPDATE_TIME) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnItemRemoved(int64_t aItemId,
+ int64_t aParentId,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
+ mLiveUpdate != QUERYUPDATE_SIMPLE && mLiveUpdate != QUERYUPDATE_TIME) {
+ nsresult rv = Refresh();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnItemChanged(int64_t aItemId,
+ const nsACString& aProperty,
+ bool aIsAnnotationProperty,
+ const nsACString& aNewValue,
+ PRTime aLastModified,
+ uint16_t aItemType,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ const nsACString& aOldValue,
+ uint16_t aSource)
+{
+ // History observers should not get OnItemChanged
+ // but should get the corresponding history notifications instead.
+ // For bookmark queries, "all bookmark" observers should get OnItemChanged.
+ // For example, when a title of a bookmark changes, we want that to refresh.
+
+ if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS) {
+ switch (aItemType) {
+ case nsINavBookmarksService::TYPE_SEPARATOR:
+ // No separators in queries.
+ return NS_OK;
+ case nsINavBookmarksService::TYPE_FOLDER:
+ // Queries never result as "folders", but the tags-query results as
+ // special "tag" containers, which should follow their corresponding
+ // folders titles.
+ if (mOptions->ResultType() != nsINavHistoryQueryOptions::RESULTS_AS_TAG_QUERY)
+ return NS_OK;
+ MOZ_FALLTHROUGH;
+ default:
+ (void)Refresh();
+ }
+ }
+ else {
+ // Some node could observe both bookmarks and history. But a node observing
+ // only history should never get a bookmark notification.
+ NS_WARNING_ASSERTION(
+ mResult && (mResult->mIsAllBookmarksObserver ||
+ mResult->mIsBookmarkFolderObserver),
+ "history observers should not get OnItemChanged, but should get the "
+ "corresponding history notifications instead");
+
+ // Tags in history queries are a special case since tags are per uri and
+ // we filter tags based on searchterms.
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
+ aProperty.EqualsLiteral("tags")) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NotifyIfTagsChanged(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty,
+ aNewValue, aLastModified,
+ aItemType, aParentId, aGUID,
+ aParentGUID, aOldValue, aSource);
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnItemVisited(int64_t aItemId,
+ int64_t aVisitId,
+ PRTime aTime,
+ uint32_t aTransitionType,
+ nsIURI* aURI,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID)
+{
+ // for bookmark queries, "all bookmark" observer should get OnItemVisited
+ // but it is ignored.
+ if (mLiveUpdate != QUERYUPDATE_COMPLEX_WITH_BOOKMARKS)
+ NS_WARNING_ASSERTION(
+ mResult && (mResult->mIsAllBookmarksObserver ||
+ mResult->mIsBookmarkFolderObserver),
+ "history observers should not get OnItemVisited, but should get OnVisit "
+ "instead");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryQueryResultNode::OnItemMoved(int64_t aFolder,
+ int64_t aOldParent,
+ int32_t aOldIndex,
+ int64_t aNewParent,
+ int32_t aNewIndex,
+ uint16_t aItemType,
+ const nsACString& aGUID,
+ const nsACString& aOldParentGUID,
+ const nsACString& aNewParentGUID,
+ uint16_t aSource)
+{
+ // 1. The query cannot be affected by the item's position
+ // 2. For the time being, we cannot optimize this not to update
+ // queries which are not restricted to some folders, due to way
+ // sub-queries are updated (see Refresh)
+ if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS &&
+ aItemType != nsINavBookmarksService::TYPE_SEPARATOR &&
+ aOldParent != aNewParent) {
+ return Refresh();
+ }
+ return NS_OK;
+}
+
+/**
+ * HOW DYNAMIC FOLDER UPDATING WORKS
+ *
+ * When you create a result, it will automatically keep itself in sync with
+ * stuff that happens in the system. For folder nodes, this means changes to
+ * bookmarks.
+ *
+ * A folder will fill its children "when necessary." This means it is being
+ * opened or whether we need to see if it is empty for twisty drawing. It will
+ * then register its ID with the main result object that owns it. This result
+ * object will listen for all bookmark notifications and pass those
+ * notifications to folder nodes that have registered for that specific folder
+ * ID.
+ *
+ * When a bookmark folder is closed, it will not clear its children. Instead,
+ * it will keep them and also stay registered as a listener. This means that
+ * you can more quickly re-open the same folder without doing any work. This
+ * happens a lot for menus, and bookmarks don't change very often.
+ *
+ * When a message comes in and the folder is open, we will do the correct
+ * operations to keep ourselves in sync with the bookmark service. If the
+ * folder is closed, we just clear our list to mark it as invalid and
+ * unregister as a listener. This means we do not have to keep maintaining
+ * an up-to-date list for the entire bookmark menu structure in every place
+ * it is used.
+ */
+NS_IMPL_ISUPPORTS_INHERITED(nsNavHistoryFolderResultNode,
+ nsNavHistoryContainerResultNode,
+ nsINavHistoryQueryResultNode,
+ mozIStorageStatementCallback)
+
+nsNavHistoryFolderResultNode::nsNavHistoryFolderResultNode(
+ const nsACString& aTitle, nsNavHistoryQueryOptions* aOptions,
+ int64_t aFolderId) :
+ nsNavHistoryContainerResultNode(EmptyCString(), aTitle, EmptyCString(),
+ nsNavHistoryResultNode::RESULT_TYPE_FOLDER,
+ aOptions),
+ mContentsValid(false),
+ mTargetFolderItemId(aFolderId),
+ mIsRegisteredFolderObserver(false)
+{
+ mItemId = aFolderId;
+}
+
+nsNavHistoryFolderResultNode::~nsNavHistoryFolderResultNode()
+{
+ if (mIsRegisteredFolderObserver && mResult)
+ mResult->RemoveBookmarkFolderObserver(this, mTargetFolderItemId);
+}
+
+
+/**
+ * Here we do not want to call ContainerResultNode::OnRemoving since our own
+ * ClearChildren will do the same thing and more (unregister the observers).
+ * The base ResultNode::OnRemoving will clear some regular node stats, so it is
+ * OK.
+ */
+void
+nsNavHistoryFolderResultNode::OnRemoving()
+{
+ nsNavHistoryResultNode::OnRemoving();
+ ClearChildren(true);
+ mResult = nullptr;
+}
+
+
+nsresult
+nsNavHistoryFolderResultNode::OpenContainer()
+{
+ NS_ASSERTION(!mExpanded, "Container must be expanded to close it");
+ nsresult rv;
+
+ if (!mContentsValid) {
+ rv = FillChildren();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mExpanded = true;
+
+ rv = NotifyOnStateChange(STATE_CLOSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+/**
+ * The async version of OpenContainer.
+ */
+nsresult
+nsNavHistoryFolderResultNode::OpenContainerAsync()
+{
+ NS_ASSERTION(!mExpanded, "Container already expanded when opening it");
+
+ // If the children are valid, open the container synchronously. This will be
+ // the case when the container has already been opened and any other time
+ // FillChildren or FillChildrenAsync has previously been called.
+ if (mContentsValid)
+ return OpenContainer();
+
+ nsresult rv = FillChildrenAsync();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NotifyOnStateChange(STATE_CLOSED);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+
+/**
+ * @see nsNavHistoryQueryResultNode::HasChildren. The semantics here are a
+ * little different. Querying the contents of a bookmark folder is relatively
+ * fast and it is common to have empty folders. Therefore, we always want to
+ * return the correct result so that twisties are drawn properly.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetHasChildren(bool* aHasChildren)
+{
+ if (!mContentsValid) {
+ nsresult rv = FillChildren();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ *aHasChildren = (mChildren.Count() > 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetFolderItemId(int64_t* aItemId)
+{
+ *aItemId = mTargetFolderItemId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetTargetFolderGuid(nsACString& aGuid) {
+ aGuid = mTargetFolderGuid;
+ return NS_OK;
+}
+
+/**
+ * Lazily computes the URI for this specific folder query with the current
+ * options.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetUri(nsACString& aURI)
+{
+ if (!mURI.IsEmpty()) {
+ aURI = mURI;
+ return NS_OK;
+ }
+
+ uint32_t queryCount;
+ nsINavHistoryQuery** queries;
+ nsresult rv = GetQueries(&queryCount, &queries);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = history->QueriesToQueryString(queries, queryCount, mOptions, aURI);
+ for (uint32_t queryIndex = 0; queryIndex < queryCount; ++queryIndex) {
+ NS_RELEASE(queries[queryIndex]);
+ }
+ free(queries);
+ return rv;
+}
+
+
+/**
+ * @return the queries that give you this bookmarks folder
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetQueries(uint32_t* queryCount,
+ nsINavHistoryQuery*** queries)
+{
+ // get the query object
+ nsCOMPtr<nsINavHistoryQuery> query;
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = history->GetNewQuery(getter_AddRefs(query));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // query just has the folder ID set and nothing else
+ rv = query->SetFolders(&mTargetFolderItemId, 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // make array of our 1 query
+ *queries = static_cast<nsINavHistoryQuery**>
+ (moz_xmalloc(sizeof(nsINavHistoryQuery*)));
+ if (!*queries)
+ return NS_ERROR_OUT_OF_MEMORY;
+ (*queries)[0] = query.forget().take();
+ *queryCount = 1;
+ return NS_OK;
+}
+
+
+/**
+ * Options for the query that gives you this bookmarks folder. This is just
+ * the options for the folder with the current folder ID set.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetQueryOptions(
+ nsINavHistoryQueryOptions** aQueryOptions)
+{
+ NS_ASSERTION(mOptions, "Options invalid");
+
+ *aQueryOptions = mOptions;
+ NS_ADDREF(*aQueryOptions);
+ return NS_OK;
+}
+
+
+nsresult
+nsNavHistoryFolderResultNode::FillChildren()
+{
+ NS_ASSERTION(!mContentsValid,
+ "Don't call FillChildren when contents are valid");
+ NS_ASSERTION(mChildren.Count() == 0,
+ "We are trying to fill children when there already are some");
+
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+
+ // Actually get the folder children from the bookmark service.
+ nsresult rv = bookmarks->QueryFolderChildren(mTargetFolderItemId, mOptions, &mChildren);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // PERFORMANCE: it may be better to also fill any child folders at this point
+ // so that we can draw tree twisties without doing a separate query later.
+ // If we don't end up drawing twisties a lot, it doesn't matter. If we do
+ // this, we should wrap everything in a transaction here on the bookmark
+ // service's connection.
+
+ return OnChildrenFilled();
+}
+
+
+/**
+ * Performs some tasks after all the children of the container have been added.
+ * The container's contents are not valid until this method has been called.
+ */
+nsresult
+nsNavHistoryFolderResultNode::OnChildrenFilled()
+{
+ // It is important to call FillStats to fill in the parents on all
+ // nodes and the result node pointers on the containers.
+ FillStats();
+
+ if (mResult && mResult->mNeedsToApplySortingMode) {
+ // We should repopulate container and then apply sortingMode. To avoid
+ // sorting 2 times we simply do that here.
+ mResult->SetSortingMode(mResult->mSortingMode);
+ }
+ else {
+ // Once we've computed all tree stats, we can sort, because containers will
+ // then have proper visit counts and dates.
+ SortComparator comparator = GetSortingComparator(GetSortType());
+ if (comparator) {
+ nsAutoCString sortingAnnotation;
+ GetSortingAnnotation(sortingAnnotation);
+ RecursiveSort(sortingAnnotation.get(), comparator);
+ }
+ }
+
+ // If we are limiting our results remove items from the end of the
+ // mChildren array after sorting. This is done for root node only.
+ // Note, if count < max results, we won't do anything.
+ if (!mParent && mOptions->MaxResults()) {
+ while ((uint32_t)mChildren.Count() > mOptions->MaxResults())
+ mChildren.RemoveObjectAt(mChildren.Count() - 1);
+ }
+
+ // Register with the result for updates.
+ EnsureRegisteredAsFolderObserver();
+
+ mContentsValid = true;
+ return NS_OK;
+}
+
+
+/**
+ * Registers the node with its result as a folder observer if it is not already
+ * registered.
+ */
+void
+nsNavHistoryFolderResultNode::EnsureRegisteredAsFolderObserver()
+{
+ if (!mIsRegisteredFolderObserver && mResult) {
+ mResult->AddBookmarkFolderObserver(this, mTargetFolderItemId);
+ mIsRegisteredFolderObserver = true;
+ }
+}
+
+
+/**
+ * The async version of FillChildren. This begins asynchronous execution by
+ * calling nsNavBookmarks::QueryFolderChildrenAsync. During execution, this
+ * node's async Storage callbacks, HandleResult and HandleCompletion, will be
+ * called.
+ */
+nsresult
+nsNavHistoryFolderResultNode::FillChildrenAsync()
+{
+ NS_ASSERTION(!mContentsValid, "FillChildrenAsync when contents are valid");
+ NS_ASSERTION(mChildren.Count() == 0, "FillChildrenAsync when children exist");
+
+ // ProcessFolderNodeChild, called in HandleResult, increments this for every
+ // result row it processes. Initialize it here as we begin async execution.
+ mAsyncBookmarkIndex = -1;
+
+ nsNavBookmarks* bmSvc = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bmSvc, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv =
+ bmSvc->QueryFolderChildrenAsync(this, mTargetFolderItemId,
+ getter_AddRefs(mAsyncPendingStmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Register with the result for updates. All updates during async execution
+ // will cause it to be restarted.
+ EnsureRegisteredAsFolderObserver();
+
+ return NS_OK;
+}
+
+
+/**
+ * A mozIStorageStatementCallback method. Called during the async execution
+ * begun by FillChildrenAsync.
+ *
+ * @param aResultSet
+ * The result set containing the data from the database.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::HandleResult(mozIStorageResultSet* aResultSet)
+{
+ NS_ENSURE_ARG_POINTER(aResultSet);
+
+ nsNavBookmarks* bmSvc = nsNavBookmarks::GetBookmarksService();
+ if (!bmSvc) {
+ CancelAsyncOpen(false);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Consume all the currently available rows of the result set.
+ nsCOMPtr<mozIStorageRow> row;
+ while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
+ nsresult rv = bmSvc->ProcessFolderNodeRow(row, mOptions, &mChildren,
+ mAsyncBookmarkIndex);
+ if (NS_FAILED(rv)) {
+ CancelAsyncOpen(false);
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+/**
+ * A mozIStorageStatementCallback method. Called during the async execution
+ * begun by FillChildrenAsync.
+ *
+ * @param aReason
+ * Indicates the final state of execution.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::HandleCompletion(uint16_t aReason)
+{
+ if (aReason == mozIStorageStatementCallback::REASON_FINISHED &&
+ mAsyncCanceledState == NOT_CANCELED) {
+ // Async execution successfully completed. The container is ready to open.
+
+ nsresult rv = OnChildrenFilled();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mExpanded = true;
+ mAsyncPendingStmt = nullptr;
+
+ // Notify observers only after mExpanded and mAsyncPendingStmt are set.
+ rv = NotifyOnStateChange(STATE_LOADING);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ else if (mAsyncCanceledState == CANCELED_RESTART_NEEDED) {
+ // Async execution was canceled and needs to be restarted.
+ mAsyncCanceledState = NOT_CANCELED;
+ ClearChildren(false);
+ FillChildrenAsync();
+ }
+
+ else {
+ // Async execution failed or was canceled without restart. Remove all
+ // children and close the container, notifying observers.
+ mAsyncCanceledState = NOT_CANCELED;
+ ClearChildren(true);
+ CloseContainer();
+ }
+
+ return NS_OK;
+}
+
+
+void
+nsNavHistoryFolderResultNode::ClearChildren(bool unregister)
+{
+ for (int32_t i = 0; i < mChildren.Count(); ++i)
+ mChildren[i]->OnRemoving();
+ mChildren.Clear();
+
+ bool needsUnregister = unregister && (mContentsValid || mAsyncPendingStmt);
+ if (needsUnregister && mResult && mIsRegisteredFolderObserver) {
+ mResult->RemoveBookmarkFolderObserver(this, mTargetFolderItemId);
+ mIsRegisteredFolderObserver = false;
+ }
+ mContentsValid = false;
+}
+
+
+/**
+ * This is called to update the result when something has changed that we
+ * can not incrementally update.
+ */
+nsresult
+nsNavHistoryFolderResultNode::Refresh()
+{
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+ if (result->mBatchInProgress) {
+ result->requestRefresh(this);
+ return NS_OK;
+ }
+
+ ClearChildren(true);
+
+ if (!mExpanded) {
+ // When we are not expanded, we don't update, just invalidate and unhook.
+ return NS_OK;
+ }
+
+ // Ignore errors from FillChildren, since we will still want to refresh
+ // the tree (there just might not be anything in it on error). ClearChildren
+ // has unregistered us as an observer since FillChildren will try to
+ // re-register us.
+ (void)FillChildren();
+
+ NOTIFY_RESULT_OBSERVERS(result, InvalidateContainer(TO_CONTAINER(this)));
+ return NS_OK;
+}
+
+
+/**
+ * Implements the logic described above the constructor. This sees if we
+ * should do an incremental update and returns true if so. If not, it
+ * invalidates our children, unregisters us an observer, and returns false.
+ */
+bool
+nsNavHistoryFolderResultNode::StartIncrementalUpdate()
+{
+ // if any items are excluded, we can not do incremental updates since the
+ // indices from the bookmark service will not be valid
+
+ if (!mOptions->ExcludeItems() &&
+ !mOptions->ExcludeQueries() &&
+ !mOptions->ExcludeReadOnlyFolders()) {
+ // easy case: we are visible, always do incremental update
+ if (mExpanded || AreChildrenVisible())
+ return true;
+
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_TRUE(result, false);
+
+ // When any observers are attached also do incremental updates if our
+ // parent is visible, so that twisties are drawn correctly.
+ if (mParent)
+ return result->mObservers.Length() > 0;
+ }
+
+ // otherwise, we don't do incremental updates, invalidate and unregister
+ (void)Refresh();
+ return false;
+}
+
+
+/**
+ * This function adds aDelta to all bookmark indices between the two endpoints,
+ * inclusive. It is used when items are added or removed from the bookmark
+ * folder.
+ */
+void
+nsNavHistoryFolderResultNode::ReindexRange(int32_t aStartIndex,
+ int32_t aEndIndex,
+ int32_t aDelta)
+{
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ nsNavHistoryResultNode* node = mChildren[i];
+ if (node->mBookmarkIndex >= aStartIndex &&
+ node->mBookmarkIndex <= aEndIndex)
+ node->mBookmarkIndex += aDelta;
+ }
+}
+
+
+/**
+ * Searches this folder for a node with the given id/target-folder-id.
+ *
+ * @return the node if found, null otherwise.
+ * @note Does not addref the node!
+ */
+nsNavHistoryResultNode*
+nsNavHistoryFolderResultNode::FindChildById(int64_t aItemId,
+ uint32_t* aNodeIndex)
+{
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]->mItemId == aItemId ||
+ (mChildren[i]->IsFolder() &&
+ mChildren[i]->GetAsFolder()->mTargetFolderItemId == aItemId)) {
+ *aNodeIndex = i;
+ return mChildren[i];
+ }
+ }
+ return nullptr;
+}
+
+
+// Used by nsNavHistoryFolderResultNode's nsINavBookmarkObserver methods below.
+// If the container is notified of a bookmark event while asynchronous execution
+// is pending, this restarts it and returns.
+#define RESTART_AND_RETURN_IF_ASYNC_PENDING() \
+ if (mAsyncPendingStmt) { \
+ CancelAsyncOpen(true); \
+ return NS_OK; \
+ }
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetSkipTags(bool *aSkipTags)
+{
+ *aSkipTags = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::GetSkipDescendantsOnItemRemoval(bool *aSkipDescendantsOnItemRemoval)
+{
+ *aSkipDescendantsOnItemRemoval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnBeginUpdateBatch()
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnEndUpdateBatch()
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnItemAdded(int64_t aItemId,
+ int64_t aParentFolder,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aTitle,
+ PRTime aDateAdded,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ MOZ_ASSERT(aParentFolder == mTargetFolderItemId, "Got wrong bookmark update");
+
+ RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+ {
+ uint32_t index;
+ nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
+ // Bug 1097528.
+ // It's possible our result registered due to a previous notification, for
+ // example the Library left pane could have refreshed and replaced the
+ // right pane as a consequence. In such a case our contents are already
+ // up-to-date. That's OK.
+ if (node)
+ return NS_OK;
+ }
+
+ bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+ (mParent && mParent->mOptions->ExcludeItems()) ||
+ mOptions->ExcludeItems();
+
+ // here, try to do something reasonable if the bookmark service gives us
+ // a bogus index.
+ if (aIndex < 0) {
+ NS_NOTREACHED("Invalid index for item adding: <0");
+ aIndex = 0;
+ }
+ else if (aIndex > mChildren.Count()) {
+ if (!excludeItems) {
+ // Something wrong happened while updating indexes.
+ NS_NOTREACHED("Invalid index for item adding: greater than count");
+ }
+ aIndex = mChildren.Count();
+ }
+
+ nsresult rv;
+
+ // Check for query URIs, which are bookmarks, but treated as containers
+ // in results and views.
+ bool isQuery = false;
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
+ NS_ASSERTION(aURI, "Got a null URI when we are a bookmark?!");
+ nsAutoCString itemURISpec;
+ rv = aURI->GetSpec(itemURISpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ isQuery = IsQueryURI(itemURISpec);
+ }
+
+ if (aItemType != nsINavBookmarksService::TYPE_FOLDER &&
+ !isQuery && excludeItems) {
+ // don't update items when we aren't displaying them, but we still need
+ // to adjust bookmark indices to account for the insertion
+ ReindexRange(aIndex, INT32_MAX, 1);
+ return NS_OK;
+ }
+
+ if (!StartIncrementalUpdate())
+ return NS_OK; // folder was completely refreshed for us
+
+ // adjust indices to account for insertion
+ ReindexRange(aIndex, INT32_MAX, 1);
+
+ RefPtr<nsNavHistoryResultNode> node;
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ rv = history->BookmarkIdToResultNode(aItemId, mOptions, getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (aItemType == nsINavBookmarksService::TYPE_FOLDER) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+ rv = bookmarks->ResultNodeForContainer(aItemId, mOptions, getter_AddRefs(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (aItemType == nsINavBookmarksService::TYPE_SEPARATOR) {
+ node = new nsNavHistorySeparatorResultNode();
+ NS_ENSURE_TRUE(node, NS_ERROR_OUT_OF_MEMORY);
+ node->mItemId = aItemId;
+ node->mBookmarkGuid = aGUID;
+ node->mDateAdded = aDateAdded;
+ node->mLastModified = aDateAdded;
+ }
+
+ node->mBookmarkIndex = aIndex;
+
+ if (aItemType == nsINavBookmarksService::TYPE_SEPARATOR ||
+ GetSortType() == nsINavHistoryQueryOptions::SORT_BY_NONE) {
+ // insert at natural bookmarks position
+ return InsertChildAt(node, aIndex);
+ }
+
+ // insert at sorted position
+ return InsertSortedChild(node);
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnItemRemoved(int64_t aItemId,
+ int64_t aParentFolder,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ // Folder shortcuts should not be notified removal of the target folder.
+ MOZ_ASSERT_IF(mItemId != mTargetFolderItemId, aItemId != mTargetFolderItemId);
+ // Concrete folders should not be notified their own removal.
+ // Note aItemId may equal mItemId for recursive folder shortcuts.
+ MOZ_ASSERT_IF(mItemId == mTargetFolderItemId, aItemId != mItemId);
+
+ // In any case though, here we only care about the children removal.
+ if (mTargetFolderItemId == aItemId || mItemId == aItemId)
+ return NS_OK;
+
+ MOZ_ASSERT(aParentFolder == mTargetFolderItemId, "Got wrong bookmark update");
+
+ RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+ // don't trust the index from the bookmark service, find it ourselves. The
+ // sorting could be different, or the bookmark services indices and ours might
+ // be out of sync somehow.
+ uint32_t index;
+ nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
+ // Bug 1097528.
+ // It's possible our result registered due to a previous notification, for
+ // example the Library left pane could have refreshed and replaced the
+ // right pane as a consequence. In such a case our contents are already
+ // up-to-date. That's OK.
+ if (!node) {
+ return NS_OK;
+ }
+
+ bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+ (mParent && mParent->mOptions->ExcludeItems()) ||
+ mOptions->ExcludeItems();
+ if ((node->IsURI() || node->IsSeparator()) && excludeItems) {
+ // don't update items when we aren't displaying them, but we do need to
+ // adjust everybody's bookmark indices to account for the removal
+ ReindexRange(aIndex, INT32_MAX, -1);
+ return NS_OK;
+ }
+
+ if (!StartIncrementalUpdate())
+ return NS_OK; // we are completely refreshed
+
+ // shift all following indices down
+ ReindexRange(aIndex + 1, INT32_MAX, -1);
+
+ return RemoveChildAt(index);
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResultNode::OnItemChanged(int64_t aItemId,
+ const nsACString& aProperty,
+ bool aIsAnnotationProperty,
+ const nsACString& aNewValue,
+ PRTime aLastModified,
+ uint16_t aItemType,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ const nsACString& aOldValue,
+ uint16_t aSource)
+{
+ if (aItemId != mItemId)
+ return NS_OK;
+
+ mLastModified = aLastModified;
+
+ nsNavHistoryResult* result = GetResult();
+ NS_ENSURE_STATE(result);
+
+ bool shouldNotify = !mParent || mParent->AreChildrenVisible();
+
+ if (aIsAnnotationProperty) {
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeAnnotationChanged(this, aProperty));
+ }
+ else if (aProperty.EqualsLiteral("title")) {
+ // XXX: what should we do if the new title is void?
+ mTitle = aNewValue;
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeTitleChanged(this, mTitle));
+ }
+ else if (aProperty.EqualsLiteral("uri")) {
+ // clear the tags string as well
+ mTags.SetIsVoid(true);
+ mURI = aNewValue;
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeURIChanged(this, mURI));
+ }
+ else if (aProperty.EqualsLiteral("favicon")) {
+ mFaviconURI = aNewValue;
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeIconChanged(this));
+ }
+ else if (aProperty.EqualsLiteral("cleartime")) {
+ mTime = 0;
+ if (shouldNotify) {
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeHistoryDetailsChanged(this, 0, mAccessCount));
+ }
+ }
+ else if (aProperty.EqualsLiteral("tags")) {
+ mTags.SetIsVoid(true);
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeTagsChanged(this));
+ }
+ else if (aProperty.EqualsLiteral("dateAdded")) {
+ // aNewValue has the date as a string, but we can use aLastModified,
+ // because it's set to the same value when dateAdded is changed.
+ mDateAdded = aLastModified;
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeDateAddedChanged(this, mDateAdded));
+ }
+ else if (aProperty.EqualsLiteral("lastModified")) {
+ if (shouldNotify) {
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeLastModifiedChanged(this, aLastModified));
+ }
+ }
+ else if (aProperty.EqualsLiteral("keyword")) {
+ if (shouldNotify)
+ NOTIFY_RESULT_OBSERVERS(result, NodeKeywordChanged(this, aNewValue));
+ }
+ else
+ NS_NOTREACHED("Unknown bookmark property changing.");
+
+ if (!mParent)
+ return NS_OK;
+
+ // DO NOT OPTIMIZE THIS TO CHECK aProperty
+ // The sorting methods fall back to each other so we need to re-sort the
+ // result even if it's not set to sort by the given property.
+ int32_t ourIndex = mParent->FindChild(this);
+ NS_ASSERTION(ourIndex >= 0, "Could not find self in parent");
+ if (ourIndex >= 0)
+ mParent->EnsureItemPosition(ourIndex);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnItemChanged(int64_t aItemId,
+ const nsACString& aProperty,
+ bool aIsAnnotationProperty,
+ const nsACString& aNewValue,
+ PRTime aLastModified,
+ uint16_t aItemType,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ const nsACString& aOldValue,
+ uint16_t aSource)
+{
+ RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+ return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty,
+ aNewValue, aLastModified,
+ aItemType, aParentId, aGUID,
+ aParentGUID, aOldValue, aSource);
+}
+
+/**
+ * Updates visit count and last visit time and refreshes.
+ */
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnItemVisited(int64_t aItemId,
+ int64_t aVisitId,
+ PRTime aTime,
+ uint32_t aTransitionType,
+ nsIURI* aURI,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID)
+{
+ bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+ (mParent && mParent->mOptions->ExcludeItems()) ||
+ mOptions->ExcludeItems();
+ if (excludeItems)
+ return NS_OK; // don't update items when we aren't displaying them
+
+ RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+ if (!StartIncrementalUpdate())
+ return NS_OK;
+
+ uint32_t nodeIndex;
+ nsNavHistoryResultNode* node = FindChildById(aItemId, &nodeIndex);
+ if (!node)
+ return NS_ERROR_FAILURE;
+
+ // Update node.
+ node->mTime = aTime;
+ ++node->mAccessCount;
+
+ // Update us.
+ int32_t oldAccessCount = mAccessCount;
+ ++mAccessCount;
+ if (aTime > mTime)
+ mTime = aTime;
+ nsresult rv = ReverseUpdateStats(mAccessCount - oldAccessCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Update frecency for proper frecency ordering.
+ // TODO (bug 832617): we may avoid one query here, by providing the new
+ // frecency value in the notification.
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_OK);
+ RefPtr<nsNavHistoryResultNode> visitNode;
+ rv = history->VisitIdToResultNode(aVisitId, mOptions,
+ getter_AddRefs(visitNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(visitNode);
+ node->mFrecency = visitNode->mFrecency;
+
+ if (AreChildrenVisible()) {
+ // Sorting has not changed, just redraw the row if it's visible.
+ nsNavHistoryResult* result = GetResult();
+ NOTIFY_RESULT_OBSERVERS(result,
+ NodeHistoryDetailsChanged(node, mTime, mAccessCount));
+ }
+
+ // Update sorting if necessary.
+ uint32_t sortType = GetSortType();
+ if (sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_VISITCOUNT_DESCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_DATE_DESCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_ASCENDING ||
+ sortType == nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING) {
+ int32_t childIndex = FindChild(node);
+ NS_ASSERTION(childIndex >= 0, "Could not find child we just got a reference to");
+ if (childIndex >= 0) {
+ EnsureItemPosition(childIndex);
+ }
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryFolderResultNode::OnItemMoved(int64_t aItemId,
+ int64_t aOldParent,
+ int32_t aOldIndex,
+ int64_t aNewParent,
+ int32_t aNewIndex,
+ uint16_t aItemType,
+ const nsACString& aGUID,
+ const nsACString& aOldParentGUID,
+ const nsACString& aNewParentGUID,
+ uint16_t aSource)
+{
+ NS_ASSERTION(aOldParent == mTargetFolderItemId || aNewParent == mTargetFolderItemId,
+ "Got a bookmark message that doesn't belong to us");
+
+ RESTART_AND_RETURN_IF_ASYNC_PENDING();
+
+ uint32_t index;
+ nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
+ // Bug 1097528.
+ // It's possible our result registered due to a previous notification, for
+ // example the Library left pane could have refreshed and replaced the
+ // right pane as a consequence. In such a case our contents are already
+ // up-to-date. That's OK.
+ if (node && aNewParent == mTargetFolderItemId && index == static_cast<uint32_t>(aNewIndex))
+ return NS_OK;
+ if (!node && aOldParent == mTargetFolderItemId)
+ return NS_OK;
+
+ bool excludeItems = (mResult && mResult->mRootNode->mOptions->ExcludeItems()) ||
+ (mParent && mParent->mOptions->ExcludeItems()) ||
+ mOptions->ExcludeItems();
+ if (node && excludeItems && (node->IsURI() || node->IsSeparator())) {
+ // Don't update items when we aren't displaying them.
+ return NS_OK;
+ }
+
+ if (!StartIncrementalUpdate())
+ return NS_OK; // entire container was refreshed for us
+
+ if (aOldParent == aNewParent) {
+ // getting moved within the same folder, we don't want to do a remove and
+ // an add because that will lose your tree state.
+
+ // adjust bookmark indices
+ ReindexRange(aOldIndex + 1, INT32_MAX, -1);
+ ReindexRange(aNewIndex, INT32_MAX, 1);
+
+ MOZ_ASSERT(node, "Can't find folder that is moving!");
+ if (!node) {
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_ASSERT(index < uint32_t(mChildren.Count()), "Invalid index!");
+ node->mBookmarkIndex = aNewIndex;
+
+ // adjust position
+ EnsureItemPosition(index);
+ return NS_OK;
+ } else {
+ // moving between two different folders, just do a remove and an add
+ nsCOMPtr<nsIURI> itemURI;
+ nsAutoCString itemTitle;
+ if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
+ nsresult rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(itemURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bookmarks->GetItemTitle(aItemId, itemTitle);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (aOldParent == mTargetFolderItemId) {
+ OnItemRemoved(aItemId, aOldParent, aOldIndex, aItemType, itemURI,
+ aGUID, aOldParentGUID, aSource);
+ }
+ if (aNewParent == mTargetFolderItemId) {
+ OnItemAdded(aItemId, aNewParent, aNewIndex, aItemType, itemURI, itemTitle,
+ RoundedPRNow(), // This is a dummy dateAdded, not the real value.
+ aGUID, aNewParentGUID, aSource);
+ }
+ }
+ return NS_OK;
+}
+
+
+/**
+ * Separator nodes do not hold any data.
+ */
+nsNavHistorySeparatorResultNode::nsNavHistorySeparatorResultNode()
+ : nsNavHistoryResultNode(EmptyCString(), EmptyCString(),
+ 0, 0, EmptyCString())
+{
+}
+
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsNavHistoryResult)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsNavHistoryResult)
+ tmp->StopObserving();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootNode)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers)
+ for (auto it = tmp->mBookmarkFolderObservers.Iter(); !it.Done(); it.Next()) {
+ delete it.Data();
+ it.Remove();
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAllBookmarksObservers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistoryObservers)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNavHistoryResult)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers)
+ for (auto it = tmp->mBookmarkFolderObservers.Iter(); !it.Done(); it.Next()) {
+ nsNavHistoryResult::FolderObserverList*& list = it.Data();
+ for (uint32_t i = 0; i < list->Length(); ++i) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mBookmarkFolderObservers value[i]");
+ nsNavHistoryResultNode* node = list->ElementAt(i);
+ cb.NoteXPCOMChild(node);
+ }
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAllBookmarksObservers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistoryObservers)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNavHistoryResult)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNavHistoryResult)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNavHistoryResult)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsINavHistoryResult)
+ NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsNavHistoryResult)
+ NS_INTERFACE_MAP_ENTRY(nsINavHistoryResult)
+ NS_INTERFACE_MAP_ENTRY(nsINavBookmarkObserver)
+ NS_INTERFACE_MAP_ENTRY(nsINavHistoryObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+nsNavHistoryResult::nsNavHistoryResult(nsNavHistoryContainerResultNode* aRoot)
+ : mRootNode(aRoot)
+ , mNeedsToApplySortingMode(false)
+ , mIsHistoryObserver(false)
+ , mIsBookmarkFolderObserver(false)
+ , mIsAllBookmarksObserver(false)
+ , mBookmarkFolderObservers(64)
+ , mBatchInProgress(false)
+ , mSuppressNotifications(false)
+{
+ mRootNode->mResult = this;
+}
+
+nsNavHistoryResult::~nsNavHistoryResult()
+{
+ // Delete all heap-allocated bookmark folder observer arrays.
+ for (auto it = mBookmarkFolderObservers.Iter(); !it.Done(); it.Next()) {
+ delete it.Data();
+ it.Remove();
+ }
+}
+
+void
+nsNavHistoryResult::StopObserving()
+{
+ if (mIsBookmarkFolderObserver || mIsAllBookmarksObserver) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ if (bookmarks) {
+ bookmarks->RemoveObserver(this);
+ mIsBookmarkFolderObserver = false;
+ mIsAllBookmarksObserver = false;
+ }
+ }
+ if (mIsHistoryObserver) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ if (history) {
+ history->RemoveObserver(this);
+ mIsHistoryObserver = false;
+ }
+ }
+}
+
+/**
+ * @note you must call AddRef before this, since we may do things like
+ * register ourselves.
+ */
+nsresult
+nsNavHistoryResult::Init(nsINavHistoryQuery** aQueries,
+ uint32_t aQueryCount,
+ nsNavHistoryQueryOptions *aOptions)
+{
+ nsresult rv;
+ NS_ASSERTION(aOptions, "Must have valid options");
+ NS_ASSERTION(aQueries && aQueryCount > 0, "Must have >1 query in result");
+
+ // Fill saved source queries with copies of the original (the caller might
+ // change their original objects, and we always want to reflect the source
+ // parameters).
+ for (uint32_t i = 0; i < aQueryCount; ++i) {
+ nsCOMPtr<nsINavHistoryQuery> queryClone;
+ rv = aQueries[i]->Clone(getter_AddRefs(queryClone));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mQueries.AppendObject(queryClone))
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = aOptions->Clone(getter_AddRefs(mOptions));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSortingMode = aOptions->SortingMode();
+ rv = aOptions->GetSortingAnnotation(mSortingAnnotation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(mRootNode->mIndentLevel == -1,
+ "Root node's indent level initialized wrong");
+ mRootNode->FillStats();
+
+ return NS_OK;
+}
+
+
+/**
+ * Constructs a new history result object.
+ */
+nsresult // static
+nsNavHistoryResult::NewHistoryResult(nsINavHistoryQuery** aQueries,
+ uint32_t aQueryCount,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryContainerResultNode* aRoot,
+ bool aBatchInProgress,
+ nsNavHistoryResult** result)
+{
+ *result = new nsNavHistoryResult(aRoot);
+ if (!*result)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*result); // must happen before Init
+ // Correctly set mBatchInProgress for the result based on the root node value.
+ (*result)->mBatchInProgress = aBatchInProgress;
+ nsresult rv = (*result)->Init(aQueries, aQueryCount, aOptions);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(*result);
+ *result = nullptr;
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+
+void
+nsNavHistoryResult::AddHistoryObserver(nsNavHistoryQueryResultNode* aNode)
+{
+ if (!mIsHistoryObserver) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ASSERTION(history, "Can't create history service");
+ history->AddObserver(this, true);
+ mIsHistoryObserver = true;
+ }
+ // Don't add duplicate observers. In some case we don't unregister when
+ // children are cleared (see ClearChildren) and the next FillChildren call
+ // will try to add the observer again.
+ if (mHistoryObservers.IndexOf(aNode) == mHistoryObservers.NoIndex) {
+ mHistoryObservers.AppendElement(aNode);
+ }
+}
+
+
+void
+nsNavHistoryResult::AddAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode)
+{
+ if (!mIsAllBookmarksObserver && !mIsBookmarkFolderObserver) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ if (!bookmarks) {
+ NS_NOTREACHED("Can't create bookmark service");
+ return;
+ }
+ bookmarks->AddObserver(this, true);
+ mIsAllBookmarksObserver = true;
+ }
+ // Don't add duplicate observers. In some case we don't unregister when
+ // children are cleared (see ClearChildren) and the next FillChildren call
+ // will try to add the observer again.
+ if (mAllBookmarksObservers.IndexOf(aNode) == mAllBookmarksObservers.NoIndex) {
+ mAllBookmarksObservers.AppendElement(aNode);
+ }
+}
+
+
+void
+nsNavHistoryResult::AddBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode,
+ int64_t aFolder)
+{
+ if (!mIsBookmarkFolderObserver && !mIsAllBookmarksObserver) {
+ nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
+ if (!bookmarks) {
+ NS_NOTREACHED("Can't create bookmark service");
+ return;
+ }
+ bookmarks->AddObserver(this, true);
+ mIsBookmarkFolderObserver = true;
+ }
+ // Don't add duplicate observers. In some case we don't unregister when
+ // children are cleared (see ClearChildren) and the next FillChildren call
+ // will try to add the observer again.
+ FolderObserverList* list = BookmarkFolderObserversForId(aFolder, true);
+ if (list->IndexOf(aNode) == list->NoIndex) {
+ list->AppendElement(aNode);
+ }
+}
+
+
+void
+nsNavHistoryResult::RemoveHistoryObserver(nsNavHistoryQueryResultNode* aNode)
+{
+ mHistoryObservers.RemoveElement(aNode);
+}
+
+
+void
+nsNavHistoryResult::RemoveAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode)
+{
+ mAllBookmarksObservers.RemoveElement(aNode);
+}
+
+
+void
+nsNavHistoryResult::RemoveBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode,
+ int64_t aFolder)
+{
+ FolderObserverList* list = BookmarkFolderObserversForId(aFolder, false);
+ if (!list)
+ return; // we don't even have an entry for that folder
+ list->RemoveElement(aNode);
+}
+
+
+nsNavHistoryResult::FolderObserverList*
+nsNavHistoryResult::BookmarkFolderObserversForId(int64_t aFolderId, bool aCreate)
+{
+ FolderObserverList* list;
+ if (mBookmarkFolderObservers.Get(aFolderId, &list))
+ return list;
+ if (!aCreate)
+ return nullptr;
+
+ // need to create a new list
+ list = new FolderObserverList;
+ mBookmarkFolderObservers.Put(aFolderId, list);
+ return list;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetSortingMode(uint16_t* aSortingMode)
+{
+ *aSortingMode = mSortingMode;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::SetSortingMode(uint16_t aSortingMode)
+{
+ NS_ENSURE_STATE(mRootNode);
+
+ if (aSortingMode > nsINavHistoryQueryOptions::SORT_BY_FRECENCY_DESCENDING)
+ return NS_ERROR_INVALID_ARG;
+
+ // Keep everything in sync.
+ NS_ASSERTION(mOptions, "Options should always be present for a root query");
+
+ mSortingMode = aSortingMode;
+
+ if (!mRootNode->mExpanded) {
+ // Need to do this later when node will be expanded.
+ mNeedsToApplySortingMode = true;
+ return NS_OK;
+ }
+
+ // Actually do sorting.
+ nsNavHistoryContainerResultNode::SortComparator comparator =
+ nsNavHistoryContainerResultNode::GetSortingComparator(aSortingMode);
+ if (comparator) {
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
+ mRootNode->RecursiveSort(mSortingAnnotation.get(), comparator);
+ }
+
+ NOTIFY_RESULT_OBSERVERS(this, SortingChanged(aSortingMode));
+ NOTIFY_RESULT_OBSERVERS(this, InvalidateContainer(mRootNode));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetSortingAnnotation(nsACString& _result) {
+ _result.Assign(mSortingAnnotation);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::SetSortingAnnotation(const nsACString& aSortingAnnotation) {
+ mSortingAnnotation.Assign(aSortingAnnotation);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::AddObserver(nsINavHistoryResultObserver* aObserver,
+ bool aOwnsWeak)
+{
+ NS_ENSURE_ARG(aObserver);
+ nsresult rv = mObservers.AppendWeakElement(aObserver, aOwnsWeak);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aObserver->SetResult(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we are batching, notify a fake batch start to the observers.
+ // Not doing so would then notify a not coupled batch end.
+ if (mBatchInProgress) {
+ NOTIFY_RESULT_OBSERVERS(this, Batching(true));
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::RemoveObserver(nsINavHistoryResultObserver* aObserver)
+{
+ NS_ENSURE_ARG(aObserver);
+ return mObservers.RemoveWeakElement(aObserver);
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetSuppressNotifications(bool* _retval)
+{
+ *_retval = mSuppressNotifications;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::SetSuppressNotifications(bool aSuppressNotifications)
+{
+ mSuppressNotifications = aSuppressNotifications;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetRoot(nsINavHistoryContainerResultNode** aRoot)
+{
+ if (!mRootNode) {
+ NS_NOTREACHED("Root is null");
+ *aRoot = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<nsNavHistoryContainerResultNode> node(mRootNode);
+ node.forget(aRoot);
+ return NS_OK;
+}
+
+
+void
+nsNavHistoryResult::requestRefresh(nsNavHistoryContainerResultNode* aContainer)
+{
+ // Don't add twice the same container.
+ if (mRefreshParticipants.IndexOf(aContainer) == mRefreshParticipants.NoIndex)
+ mRefreshParticipants.AppendElement(aContainer);
+}
+
+// nsINavBookmarkObserver implementation
+
+// Here, it is important that we create a COPY of the observer array. Some
+// observers will requery themselves, which may cause the observer array to
+// be modified or added to.
+#define ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(_folderId, _functionCall) \
+ PR_BEGIN_MACRO \
+ FolderObserverList* _fol = BookmarkFolderObserversForId(_folderId, false); \
+ if (_fol) { \
+ FolderObserverList _listCopy(*_fol); \
+ for (uint32_t _fol_i = 0; _fol_i < _listCopy.Length(); ++_fol_i) { \
+ if (_listCopy[_fol_i]) \
+ _listCopy[_fol_i]->_functionCall; \
+ } \
+ } \
+ PR_END_MACRO
+#define ENUMERATE_LIST_OBSERVERS(_listType, _functionCall, _observersList, _conditionCall) \
+ PR_BEGIN_MACRO \
+ _listType _listCopy(_observersList); \
+ for (uint32_t _obs_i = 0; _obs_i < _listCopy.Length(); ++_obs_i) { \
+ if (_listCopy[_obs_i] && _listCopy[_obs_i]->_conditionCall) \
+ _listCopy[_obs_i]->_functionCall; \
+ } \
+ PR_END_MACRO
+#define ENUMERATE_QUERY_OBSERVERS(_functionCall, _observersList, _conditionCall) \
+ ENUMERATE_LIST_OBSERVERS(QueryObserverList, _functionCall, _observersList, _conditionCall)
+#define ENUMERATE_ALL_BOOKMARKS_OBSERVERS(_functionCall) \
+ ENUMERATE_QUERY_OBSERVERS(_functionCall, mAllBookmarksObservers, IsQuery())
+#define ENUMERATE_HISTORY_OBSERVERS(_functionCall) \
+ ENUMERATE_QUERY_OBSERVERS(_functionCall, mHistoryObservers, IsQuery())
+
+#define NOTIFY_REFRESH_PARTICIPANTS() \
+ PR_BEGIN_MACRO \
+ ENUMERATE_LIST_OBSERVERS(ContainerObserverList, Refresh(), mRefreshParticipants, IsContainer()); \
+ mRefreshParticipants.Clear(); \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetSkipTags(bool *aSkipTags)
+{
+ *aSkipTags = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryResult::GetSkipDescendantsOnItemRemoval(bool *aSkipDescendantsOnItemRemoval)
+{
+ *aSkipDescendantsOnItemRemoval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnBeginUpdateBatch()
+{
+ // Since we could be observing both history and bookmarks, it's possible both
+ // notify the batch. We can safely ignore nested calls.
+ if (!mBatchInProgress) {
+ mBatchInProgress = true;
+ ENUMERATE_HISTORY_OBSERVERS(OnBeginUpdateBatch());
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnBeginUpdateBatch());
+
+ NOTIFY_RESULT_OBSERVERS(this, Batching(true));
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnEndUpdateBatch()
+{
+ // Since we could be observing both history and bookmarks, it's possible both
+ // notify the batch. We can safely ignore nested calls.
+ // Notice it's possible we are notified OnEndUpdateBatch more times than
+ // onBeginUpdateBatch, since the result could be created in the middle of
+ // nested batches.
+ if (mBatchInProgress) {
+ ENUMERATE_HISTORY_OBSERVERS(OnEndUpdateBatch());
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnEndUpdateBatch());
+
+ // Setting mBatchInProgress before notifying the end of the batch to
+ // observers would make evantual calls to Refresh() directly handled rather
+ // than enqueued. Thus set it just before handling refreshes.
+ mBatchInProgress = false;
+ NOTIFY_REFRESH_PARTICIPANTS();
+ NOTIFY_RESULT_OBSERVERS(this, Batching(false));
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnItemAdded(int64_t aItemId,
+ int64_t aParentId,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aTitle,
+ PRTime aDateAdded,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG(aItemType != nsINavBookmarksService::TYPE_BOOKMARK ||
+ aURI);
+
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
+ OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
+ aGUID, aParentGUID, aSource)
+ );
+ ENUMERATE_HISTORY_OBSERVERS(
+ OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
+ aGUID, aParentGUID, aSource)
+ );
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
+ OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
+ aGUID, aParentGUID, aSource)
+ );
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnItemRemoved(int64_t aItemId,
+ int64_t aParentId,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource)
+{
+ NS_ENSURE_ARG(aItemType != nsINavBookmarksService::TYPE_BOOKMARK ||
+ aURI);
+
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
+ OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
+ aParentGUID, aSource));
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
+ OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
+ aParentGUID, aSource));
+ ENUMERATE_HISTORY_OBSERVERS(
+ OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
+ aParentGUID, aSource));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnItemChanged(int64_t aItemId,
+ const nsACString &aProperty,
+ bool aIsAnnotationProperty,
+ const nsACString &aNewValue,
+ PRTime aLastModified,
+ uint16_t aItemType,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ const nsACString& aOldValue,
+ uint16_t aSource)
+{
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
+ OnItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue,
+ aLastModified, aItemType, aParentId, aGUID, aParentGUID,
+ aOldValue, aSource));
+
+ // Note: folder-nodes set their own bookmark observer only once they're
+ // opened, meaning we cannot optimize this code path for changes done to
+ // folder-nodes.
+
+ FolderObserverList* list = BookmarkFolderObserversForId(aParentId, false);
+ if (!list)
+ return NS_OK;
+
+ for (uint32_t i = 0; i < list->Length(); ++i) {
+ RefPtr<nsNavHistoryFolderResultNode> folder = list->ElementAt(i);
+ if (folder) {
+ uint32_t nodeIndex;
+ RefPtr<nsNavHistoryResultNode> node =
+ folder->FindChildById(aItemId, &nodeIndex);
+ // if ExcludeItems is true we don't update non visible items
+ bool excludeItems = (mRootNode->mOptions->ExcludeItems()) ||
+ folder->mOptions->ExcludeItems();
+ if (node &&
+ (!excludeItems || !(node->IsURI() || node->IsSeparator())) &&
+ folder->StartIncrementalUpdate()) {
+ node->OnItemChanged(aItemId, aProperty, aIsAnnotationProperty,
+ aNewValue, aLastModified, aItemType, aParentId,
+ aGUID, aParentGUID, aOldValue, aSource);
+ }
+ }
+ }
+
+ // Note: we do NOT call history observers in this case. This notification is
+ // the same as other history notification, except that here we know the item
+ // is a bookmark. History observers will handle the history notification
+ // instead.
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnItemVisited(int64_t aItemId,
+ int64_t aVisitId,
+ PRTime aVisitTime,
+ uint32_t aTransitionType,
+ nsIURI* aURI,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID)
+{
+ NS_ENSURE_ARG(aURI);
+
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
+ OnItemVisited(aItemId, aVisitId, aVisitTime, aTransitionType, aURI,
+ aParentId, aGUID, aParentGUID));
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
+ OnItemVisited(aItemId, aVisitId, aVisitTime, aTransitionType, aURI,
+ aParentId, aGUID, aParentGUID));
+ // Note: we do NOT call history observers in this case. This notification is
+ // the same as OnVisit, except that here we know the item is a bookmark.
+ // History observers will handle the history notification instead.
+ return NS_OK;
+}
+
+
+/**
+ * Need to notify both the source and the destination folders (if they are
+ * different).
+ */
+NS_IMETHODIMP
+nsNavHistoryResult::OnItemMoved(int64_t aItemId,
+ int64_t aOldParent,
+ int32_t aOldIndex,
+ int64_t aNewParent,
+ int32_t aNewIndex,
+ uint16_t aItemType,
+ const nsACString& aGUID,
+ const nsACString& aOldParentGUID,
+ const nsACString& aNewParentGUID,
+ uint16_t aSource)
+{
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aOldParent,
+ OnItemMoved(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex,
+ aItemType, aGUID, aOldParentGUID, aNewParentGUID, aSource));
+ if (aNewParent != aOldParent) {
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aNewParent,
+ OnItemMoved(aItemId, aOldParent, aOldIndex, aNewParent, aNewIndex,
+ aItemType, aGUID, aOldParentGUID, aNewParentGUID, aSource));
+ }
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(OnItemMoved(aItemId, aOldParent, aOldIndex,
+ aNewParent, aNewIndex,
+ aItemType, aGUID,
+ aOldParentGUID,
+ aNewParentGUID, aSource));
+ ENUMERATE_HISTORY_OBSERVERS(OnItemMoved(aItemId, aOldParent, aOldIndex,
+ aNewParent, aNewIndex, aItemType,
+ aGUID, aOldParentGUID,
+ aNewParentGUID, aSource));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime,
+ int64_t aSessionId, int64_t aReferringId,
+ uint32_t aTransitionType, const nsACString& aGUID,
+ bool aHidden, uint32_t aVisitCount, uint32_t aTyped)
+{
+ NS_ENSURE_ARG(aURI);
+
+ // Embed visits are never shown in our views.
+ if (aTransitionType == nsINavHistoryService::TRANSITION_EMBED) {
+ return NS_OK;
+ }
+
+ uint32_t added = 0;
+
+ ENUMERATE_HISTORY_OBSERVERS(OnVisit(aURI, aVisitId, aTime, aSessionId,
+ aReferringId, aTransitionType, aGUID,
+ aHidden, &added));
+
+ if (!mRootNode->mExpanded)
+ return NS_OK;
+
+ // If this visit is accepted by an overlapped container, and not all
+ // overlapped containers are visible, we should still call Refresh if the
+ // visit falls into any of them.
+ bool todayIsMissing = false;
+ uint32_t resultType = mRootNode->mOptions->ResultType();
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
+ uint32_t childCount;
+ nsresult rv = mRootNode->GetChildCount(&childCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (childCount) {
+ nsCOMPtr<nsINavHistoryResultNode> firstChild;
+ rv = mRootNode->GetChild(0, getter_AddRefs(firstChild));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString title;
+ rv = firstChild->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsNavHistory* history = nsNavHistory::GetHistoryService();
+ NS_ENSURE_TRUE(history, NS_OK);
+ nsAutoCString todayLabel;
+ history->GetStringFromName(
+ u"finduri-AgeInDays-is-0", todayLabel);
+ todayIsMissing = !todayLabel.Equals(title);
+ }
+ }
+
+ if (!added || todayIsMissing) {
+ // None of registered query observers has accepted our URI. This means,
+ // that a matching query either was not expanded or it does not exist.
+ uint32_t resultType = mRootNode->mOptions->ResultType();
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_QUERY ||
+ resultType == nsINavHistoryQueryOptions::RESULTS_AS_DATE_SITE_QUERY) {
+ // If the visit falls into the Today bucket and the bucket exists, it was
+ // just not expanded, thus there's no reason to update.
+ int64_t beginOfToday =
+ nsNavHistory::NormalizeTime(nsINavHistoryQuery::TIME_RELATIVE_TODAY, 0);
+ if (todayIsMissing || aTime < beginOfToday) {
+ (void)mRootNode->GetAsQuery()->Refresh();
+ }
+ return NS_OK;
+ }
+
+ if (resultType == nsINavHistoryQueryOptions::RESULTS_AS_SITE_QUERY) {
+ (void)mRootNode->GetAsQuery()->Refresh();
+ return NS_OK;
+ }
+
+ // We are result of a folder node, then we should run through history
+ // observers that are containers queries and refresh them.
+ // We use a copy of the observers array since requerying could potentially
+ // cause changes to the array.
+ ENUMERATE_QUERY_OBSERVERS(Refresh(), mHistoryObservers, IsContainersQuery());
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnTitleChanged(nsIURI* aURI,
+ const nsAString& aPageTitle,
+ const nsACString& aGUID)
+{
+ NS_ENSURE_ARG(aURI);
+
+ ENUMERATE_HISTORY_OBSERVERS(OnTitleChanged(aURI, aPageTitle, aGUID));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnFrecencyChanged(nsIURI* aURI,
+ int32_t aNewFrecency,
+ const nsACString& aGUID,
+ bool aHidden,
+ PRTime aLastVisitDate)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnManyFrecenciesChanged()
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnDeleteURI(nsIURI *aURI,
+ const nsACString& aGUID,
+ uint16_t aReason)
+{
+ NS_ENSURE_ARG(aURI);
+
+ ENUMERATE_HISTORY_OBSERVERS(OnDeleteURI(aURI, aGUID, aReason));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnClearHistory()
+{
+ ENUMERATE_HISTORY_OBSERVERS(OnClearHistory());
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsNavHistoryResult::OnPageChanged(nsIURI* aURI,
+ uint32_t aChangedAttribute,
+ const nsAString& aValue,
+ const nsACString& aGUID)
+{
+ NS_ENSURE_ARG(aURI);
+
+ ENUMERATE_HISTORY_OBSERVERS(OnPageChanged(aURI, aChangedAttribute, aValue, aGUID));
+ return NS_OK;
+}
+
+
+/**
+ * Don't do anything when visits expire.
+ */
+NS_IMETHODIMP
+nsNavHistoryResult::OnDeleteVisits(nsIURI* aURI,
+ PRTime aVisitTime,
+ const nsACString& aGUID,
+ uint16_t aReason,
+ uint32_t aTransitionType)
+{
+ NS_ENSURE_ARG(aURI);
+
+ ENUMERATE_HISTORY_OBSERVERS(OnDeleteVisits(aURI, aVisitTime, aGUID, aReason,
+ aTransitionType));
+ return NS_OK;
+}
diff --git a/components/places/src/nsNavHistoryResult.h b/components/places/src/nsNavHistoryResult.h
new file mode 100644
index 000000000..fffe2bf13
--- /dev/null
+++ b/components/places/src/nsNavHistoryResult.h
@@ -0,0 +1,782 @@
+/* -*- 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 definitions of objects that make up a history query result set. This file
+ * should only be included by nsNavHistory.h, include that if you want these
+ * classes.
+ */
+
+#ifndef nsNavHistoryResult_h_
+#define nsNavHistoryResult_h_
+
+#include "nsTArray.h"
+#include "nsInterfaceHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/storage.h"
+#include "Helpers.h"
+
+class nsNavHistory;
+class nsNavHistoryQuery;
+class nsNavHistoryQueryOptions;
+
+class nsNavHistoryContainerResultNode;
+class nsNavHistoryFolderResultNode;
+class nsNavHistoryQueryResultNode;
+
+/**
+ * hashkey wrapper using int64_t KeyType
+ *
+ * @see nsTHashtable::EntryType for specification
+ *
+ * This just truncates the 64-bit int to a 32-bit one for using a hash number.
+ * It is used for bookmark folder IDs, which should be way less than 2^32.
+ */
+class nsTrimInt64HashKey : public PLDHashEntryHdr
+{
+public:
+ typedef const int64_t& KeyType;
+ typedef const int64_t* KeyTypePointer;
+
+ explicit nsTrimInt64HashKey(KeyTypePointer aKey) : mValue(*aKey) { }
+ nsTrimInt64HashKey(const nsTrimInt64HashKey& toCopy) : mValue(toCopy.mValue) { }
+ ~nsTrimInt64HashKey() { }
+
+ KeyType GetKey() const { return mValue; }
+ bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey)
+ { return static_cast<uint32_t>((*aKey) & UINT32_MAX); }
+ enum { ALLOW_MEMMOVE = true };
+
+private:
+ const int64_t mValue;
+};
+
+
+// Declare methods for implementing nsINavBookmarkObserver
+// and nsINavHistoryObserver (some methods, such as BeginUpdateBatch overlap)
+#define NS_DECL_BOOKMARK_HISTORY_OBSERVER_BASE(...) \
+ NS_DECL_NSINAVBOOKMARKOBSERVER \
+ NS_IMETHOD OnTitleChanged(nsIURI* aURI, const nsAString& aPageTitle, \
+ const nsACString& aGUID) __VA_ARGS__; \
+ NS_IMETHOD OnFrecencyChanged(nsIURI* aURI, int32_t aNewFrecency, \
+ const nsACString& aGUID, bool aHidden, \
+ PRTime aLastVisitDate) __VA_ARGS__; \
+ NS_IMETHOD OnManyFrecenciesChanged() __VA_ARGS__; \
+ NS_IMETHOD OnDeleteURI(nsIURI *aURI, const nsACString& aGUID, \
+ uint16_t aReason) __VA_ARGS__; \
+ NS_IMETHOD OnClearHistory() __VA_ARGS__; \
+ NS_IMETHOD OnPageChanged(nsIURI *aURI, uint32_t aChangedAttribute, \
+ const nsAString &aNewValue, \
+ const nsACString &aGUID) __VA_ARGS__; \
+ NS_IMETHOD OnDeleteVisits(nsIURI* aURI, PRTime aVisitTime, \
+ const nsACString& aGUID, uint16_t aReason, \
+ uint32_t aTransitionType) __VA_ARGS__;
+
+// The internal version has an output aAdded parameter, it is incremented by
+// query nodes when the visited uri belongs to them. If no such query exists,
+// the history result creates a new query node dynamically.
+#define NS_DECL_BOOKMARK_HISTORY_OBSERVER_INTERNAL \
+ NS_DECL_BOOKMARK_HISTORY_OBSERVER_BASE() \
+ NS_IMETHOD OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime, \
+ int64_t aSessionId, int64_t aReferringId, \
+ uint32_t aTransitionType, const nsACString& aGUID, \
+ bool aHidden, uint32_t* aAdded);
+
+// The external version is used by results.
+#define NS_DECL_BOOKMARK_HISTORY_OBSERVER_EXTERNAL(...) \
+ NS_DECL_BOOKMARK_HISTORY_OBSERVER_BASE(__VA_ARGS__) \
+ NS_IMETHOD OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime, \
+ int64_t aSessionId, int64_t aReferringId, \
+ uint32_t aTransitionType, const nsACString& aGUID, \
+ bool aHidden, uint32_t aVisitCount, uint32_t aTyped) __VA_ARGS__;
+
+// nsNavHistoryResult
+//
+// nsNavHistory creates this object and fills in mChildren (by getting
+// it through GetTopLevel()). Then FilledAllResults() is called to finish
+// object initialization.
+
+#define NS_NAVHISTORYRESULT_IID \
+ { 0x455d1d40, 0x1b9b, 0x40e6, { 0xa6, 0x41, 0x8b, 0xb7, 0xe8, 0x82, 0x23, 0x87 } }
+
+class nsNavHistoryResult final : public nsSupportsWeakReference,
+ public nsINavHistoryResult,
+ public nsINavBookmarkObserver,
+ public nsINavHistoryObserver
+{
+public:
+ static nsresult NewHistoryResult(nsINavHistoryQuery** aQueries,
+ uint32_t aQueryCount,
+ nsNavHistoryQueryOptions* aOptions,
+ nsNavHistoryContainerResultNode* aRoot,
+ bool aBatchInProgress,
+ nsNavHistoryResult** result);
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYRESULT_IID)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSINAVHISTORYRESULT
+ NS_DECL_BOOKMARK_HISTORY_OBSERVER_EXTERNAL(override)
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsNavHistoryResult, nsINavHistoryResult)
+
+ void AddHistoryObserver(nsNavHistoryQueryResultNode* aNode);
+ void AddBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode, int64_t aFolder);
+ void AddAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode);
+ void RemoveHistoryObserver(nsNavHistoryQueryResultNode* aNode);
+ void RemoveBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode, int64_t aFolder);
+ void RemoveAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode);
+ void StopObserving();
+
+public:
+ // two-stage init, use NewHistoryResult to construct
+ explicit nsNavHistoryResult(nsNavHistoryContainerResultNode* mRoot);
+ nsresult Init(nsINavHistoryQuery** aQueries,
+ uint32_t aQueryCount,
+ nsNavHistoryQueryOptions *aOptions);
+
+ RefPtr<nsNavHistoryContainerResultNode> mRootNode;
+
+ nsCOMArray<nsINavHistoryQuery> mQueries;
+ nsCOMPtr<nsNavHistoryQueryOptions> mOptions;
+
+ // One of nsNavHistoryQueryOptions.SORY_BY_* This is initialized to mOptions.sortingMode,
+ // but may be overridden if the user clicks on one of the columns.
+ uint16_t mSortingMode;
+ // If root node is closed and we try to apply a sortingMode, it would not
+ // work. So we will apply it when the node will be reopened and populated.
+ // This var states the fact we need to apply sortingMode in such a situation.
+ bool mNeedsToApplySortingMode;
+
+ // The sorting annotation to be used for in SORT_BY_ANNOTATION_* modes
+ nsCString mSortingAnnotation;
+
+ // node observers
+ bool mIsHistoryObserver;
+ bool mIsBookmarkFolderObserver;
+ bool mIsAllBookmarksObserver;
+
+ typedef nsTArray< RefPtr<nsNavHistoryQueryResultNode> > QueryObserverList;
+ QueryObserverList mHistoryObservers;
+ QueryObserverList mAllBookmarksObservers;
+
+ typedef nsTArray< RefPtr<nsNavHistoryFolderResultNode> > FolderObserverList;
+ nsDataHashtable<nsTrimInt64HashKey, FolderObserverList*> mBookmarkFolderObservers;
+ FolderObserverList* BookmarkFolderObserversForId(int64_t aFolderId, bool aCreate);
+
+ typedef nsTArray< RefPtr<nsNavHistoryContainerResultNode> > ContainerObserverList;
+
+ void RecursiveExpandCollapse(nsNavHistoryContainerResultNode* aContainer,
+ bool aExpand);
+
+ void InvalidateTree();
+
+ bool mBatchInProgress;
+
+ nsMaybeWeakPtrArray<nsINavHistoryResultObserver> mObservers;
+ bool mSuppressNotifications;
+
+ ContainerObserverList mRefreshParticipants;
+ void requestRefresh(nsNavHistoryContainerResultNode* aContainer);
+
+protected:
+ virtual ~nsNavHistoryResult();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHistoryResult, NS_NAVHISTORYRESULT_IID)
+
+// nsNavHistoryResultNode
+//
+// This is the base class for every node in a result set. The result itself
+// is a node (nsNavHistoryResult inherits from this), as well as every
+// leaf and branch on the tree.
+
+#define NS_NAVHISTORYRESULTNODE_IID \
+ {0x54b61d38, 0x57c1, 0x11da, {0x95, 0xb8, 0x00, 0x13, 0x21, 0xc9, 0xf6, 0x9e}}
+
+// These are all the simple getters, they can be used for the result node
+// implementation and all subclasses. More complex are GetIcon, GetParent
+// (which depends on the definition of container result node), and GetUri
+// (which is overridded for lazy construction for some containers).
+#define NS_IMPLEMENT_SIMPLE_RESULTNODE \
+ NS_IMETHOD GetTitle(nsACString& aTitle) override \
+ { aTitle = mTitle; return NS_OK; } \
+ NS_IMETHOD GetAccessCount(uint32_t* aAccessCount) override \
+ { *aAccessCount = mAccessCount; return NS_OK; } \
+ NS_IMETHOD GetTime(PRTime* aTime) override \
+ { *aTime = mTime; return NS_OK; } \
+ NS_IMETHOD GetIndentLevel(int32_t* aIndentLevel) override \
+ { *aIndentLevel = mIndentLevel; return NS_OK; } \
+ NS_IMETHOD GetBookmarkIndex(int32_t* aIndex) override \
+ { *aIndex = mBookmarkIndex; return NS_OK; } \
+ NS_IMETHOD GetDateAdded(PRTime* aDateAdded) override \
+ { *aDateAdded = mDateAdded; return NS_OK; } \
+ NS_IMETHOD GetLastModified(PRTime* aLastModified) override \
+ { *aLastModified = mLastModified; return NS_OK; } \
+ NS_IMETHOD GetItemId(int64_t* aId) override \
+ { *aId = mItemId; return NS_OK; }
+
+// This is used by the base classes instead of
+// NS_FORWARD_NSINAVHISTORYRESULTNODE(nsNavHistoryResultNode) because they
+// need to redefine GetType and GetUri rather than forwarding them. This
+// implements all the simple getters instead of forwarding because they are so
+// short and we can save a virtual function call.
+//
+// (GetUri is redefined only by QueryResultNode and FolderResultNode because
+// the queries might not necessarily be parsed. The rest just return the node's
+// buffer.)
+#define NS_FORWARD_COMMON_RESULTNODE_TO_BASE \
+ NS_IMPLEMENT_SIMPLE_RESULTNODE \
+ NS_IMETHOD GetIcon(nsACString& aIcon) override \
+ { return nsNavHistoryResultNode::GetIcon(aIcon); } \
+ NS_IMETHOD GetParent(nsINavHistoryContainerResultNode** aParent) override \
+ { return nsNavHistoryResultNode::GetParent(aParent); } \
+ NS_IMETHOD GetParentResult(nsINavHistoryResult** aResult) override \
+ { return nsNavHistoryResultNode::GetParentResult(aResult); } \
+ NS_IMETHOD GetTags(nsAString& aTags) override \
+ { return nsNavHistoryResultNode::GetTags(aTags); } \
+ NS_IMETHOD GetPageGuid(nsACString& aPageGuid) override \
+ { return nsNavHistoryResultNode::GetPageGuid(aPageGuid); } \
+ NS_IMETHOD GetBookmarkGuid(nsACString& aBookmarkGuid) override \
+ { return nsNavHistoryResultNode::GetBookmarkGuid(aBookmarkGuid); } \
+ NS_IMETHOD GetVisitId(int64_t* aVisitId) override \
+ { return nsNavHistoryResultNode::GetVisitId(aVisitId); } \
+ NS_IMETHOD GetFromVisitId(int64_t* aFromVisitId) override \
+ { return nsNavHistoryResultNode::GetFromVisitId(aFromVisitId); } \
+ NS_IMETHOD GetVisitType(uint32_t* aVisitType) override \
+ { return nsNavHistoryResultNode::GetVisitType(aVisitType); }
+
+class nsNavHistoryResultNode : public nsINavHistoryResultNode
+{
+public:
+ nsNavHistoryResultNode(const nsACString& aURI, const nsACString& aTitle,
+ uint32_t aAccessCount, PRTime aTime,
+ const nsACString& aIconURI);
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYRESULTNODE_IID)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsNavHistoryResultNode)
+
+ NS_IMPLEMENT_SIMPLE_RESULTNODE
+ NS_IMETHOD GetIcon(nsACString& aIcon) override;
+ NS_IMETHOD GetParent(nsINavHistoryContainerResultNode** aParent) override;
+ NS_IMETHOD GetParentResult(nsINavHistoryResult** aResult) override;
+ NS_IMETHOD GetType(uint32_t* type) override
+ { *type = nsNavHistoryResultNode::RESULT_TYPE_URI; return NS_OK; }
+ NS_IMETHOD GetUri(nsACString& aURI) override
+ { aURI = mURI; return NS_OK; }
+ NS_IMETHOD GetTags(nsAString& aTags) override;
+ NS_IMETHOD GetPageGuid(nsACString& aPageGuid) override;
+ NS_IMETHOD GetBookmarkGuid(nsACString& aBookmarkGuid) override;
+ NS_IMETHOD GetVisitId(int64_t* aVisitId) override;
+ NS_IMETHOD GetFromVisitId(int64_t* aFromVisitId) override;
+ NS_IMETHOD GetVisitType(uint32_t* aVisitType) override;
+
+ virtual void OnRemoving();
+
+ // Called from result's onItemChanged, see also bookmark observer declaration in
+ // nsNavHistoryFolderResultNode
+ NS_IMETHOD OnItemChanged(int64_t aItemId,
+ const nsACString &aProperty,
+ bool aIsAnnotationProperty,
+ const nsACString &aValue,
+ PRTime aNewLastModified,
+ uint16_t aItemType,
+ int64_t aParentId,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ const nsACString &aOldValue,
+ uint16_t aSource);
+
+protected:
+ virtual ~nsNavHistoryResultNode() {}
+
+public:
+
+ nsNavHistoryResult* GetResult();
+ nsNavHistoryQueryOptions* GetGeneratingOptions();
+
+ // These functions test the type. We don't use a virtual function since that
+ // would take a vtable slot for every one of (potentially very many) nodes.
+ // Note that GetType() already has a vtable slot because its on the iface.
+ bool IsTypeContainer(uint32_t type) {
+ return type == nsINavHistoryResultNode::RESULT_TYPE_QUERY ||
+ type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER ||
+ type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT;
+ }
+ bool IsContainer() {
+ uint32_t type;
+ GetType(&type);
+ return IsTypeContainer(type);
+ }
+ static bool IsTypeURI(uint32_t type) {
+ return type == nsINavHistoryResultNode::RESULT_TYPE_URI;
+ }
+ bool IsURI() {
+ uint32_t type;
+ GetType(&type);
+ return IsTypeURI(type);
+ }
+ static bool IsTypeFolder(uint32_t type) {
+ return type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER ||
+ type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT;
+ }
+ bool IsFolder() {
+ uint32_t type;
+ GetType(&type);
+ return IsTypeFolder(type);
+ }
+ static bool IsTypeQuery(uint32_t type) {
+ return type == nsINavHistoryResultNode::RESULT_TYPE_QUERY;
+ }
+ bool IsQuery() {
+ uint32_t type;
+ GetType(&type);
+ return IsTypeQuery(type);
+ }
+ bool IsSeparator() {
+ uint32_t type;
+ GetType(&type);
+ return type == nsINavHistoryResultNode::RESULT_TYPE_SEPARATOR;
+ }
+ nsNavHistoryContainerResultNode* GetAsContainer() {
+ NS_ASSERTION(IsContainer(), "Not a container");
+ return reinterpret_cast<nsNavHistoryContainerResultNode*>(this);
+ }
+ nsNavHistoryFolderResultNode* GetAsFolder() {
+ NS_ASSERTION(IsFolder(), "Not a folder");
+ return reinterpret_cast<nsNavHistoryFolderResultNode*>(this);
+ }
+ nsNavHistoryQueryResultNode* GetAsQuery() {
+ NS_ASSERTION(IsQuery(), "Not a query");
+ return reinterpret_cast<nsNavHistoryQueryResultNode*>(this);
+ }
+
+ RefPtr<nsNavHistoryContainerResultNode> mParent;
+ nsCString mURI; // not necessarily valid for containers, call GetUri
+ nsCString mTitle;
+ nsString mTags;
+ bool mAreTagsSorted;
+ uint32_t mAccessCount;
+ int64_t mTime;
+ nsCString mFaviconURI;
+ int32_t mBookmarkIndex;
+ int64_t mItemId;
+ int64_t mFolderId;
+ int64_t mVisitId;
+ int64_t mFromVisitId;
+ PRTime mDateAdded;
+ PRTime mLastModified;
+
+ // The indent level of this node. The root node will have a value of -1. The
+ // root's children will have a value of 0, and so on.
+ int32_t mIndentLevel;
+
+ // Frecency of the page. Valid only for URI nodes.
+ int32_t mFrecency;
+
+ // Hidden status of the page. Valid only for URI nodes.
+ bool mHidden;
+
+ // Transition type used when this node represents a single visit.
+ uint32_t mTransitionType;
+
+ // Unique Id of the page.
+ nsCString mPageGuid;
+
+ // Unique Id of the bookmark.
+ nsCString mBookmarkGuid;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHistoryResultNode, NS_NAVHISTORYRESULTNODE_IID)
+
+
+// nsNavHistoryContainerResultNode
+//
+// This is the base class for all nodes that can have children. It is
+// overridden for nodes that are dynamically populated such as queries and
+// folders. It is used directly for simple containers such as host groups
+// in history views.
+
+// derived classes each provide their own implementation of has children and
+// forward the rest to us using this macro
+#define NS_FORWARD_CONTAINERNODE_EXCEPT_HASCHILDREN \
+ NS_IMETHOD GetState(uint16_t* _state) override \
+ { return nsNavHistoryContainerResultNode::GetState(_state); } \
+ NS_IMETHOD GetContainerOpen(bool *aContainerOpen) override \
+ { return nsNavHistoryContainerResultNode::GetContainerOpen(aContainerOpen); } \
+ NS_IMETHOD SetContainerOpen(bool aContainerOpen) override \
+ { return nsNavHistoryContainerResultNode::SetContainerOpen(aContainerOpen); } \
+ NS_IMETHOD GetChildCount(uint32_t *aChildCount) override \
+ { return nsNavHistoryContainerResultNode::GetChildCount(aChildCount); } \
+ NS_IMETHOD GetChild(uint32_t index, nsINavHistoryResultNode **_retval) override \
+ { return nsNavHistoryContainerResultNode::GetChild(index, _retval); } \
+ NS_IMETHOD GetChildIndex(nsINavHistoryResultNode* aNode, uint32_t* _retval) override \
+ { return nsNavHistoryContainerResultNode::GetChildIndex(aNode, _retval); } \
+ NS_IMETHOD FindNodeByDetails(const nsACString& aURIString, PRTime aTime, \
+ int64_t aItemId, bool aRecursive, \
+ nsINavHistoryResultNode** _retval) override \
+ { return nsNavHistoryContainerResultNode::FindNodeByDetails(aURIString, aTime, aItemId, \
+ aRecursive, _retval); }
+
+#define NS_NAVHISTORYCONTAINERRESULTNODE_IID \
+ { 0x6e3bf8d3, 0x22aa, 0x4065, { 0x86, 0xbc, 0x37, 0x46, 0xb5, 0xb3, 0x2c, 0xe8 } }
+
+class nsNavHistoryContainerResultNode : public nsNavHistoryResultNode,
+ public nsINavHistoryContainerResultNode
+{
+public:
+ nsNavHistoryContainerResultNode(
+ const nsACString& aURI, const nsACString& aTitle,
+ const nsACString& aIconURI, uint32_t aContainerType,
+ nsNavHistoryQueryOptions* aOptions);
+ nsNavHistoryContainerResultNode(
+ const nsACString& aURI, const nsACString& aTitle,
+ PRTime aTime,
+ const nsACString& aIconURI, uint32_t aContainerType,
+ nsNavHistoryQueryOptions* aOptions);
+
+ virtual nsresult Refresh();
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYCONTAINERRESULTNODE_IID)
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsNavHistoryContainerResultNode, nsNavHistoryResultNode)
+ NS_FORWARD_COMMON_RESULTNODE_TO_BASE
+ NS_IMETHOD GetType(uint32_t* type) override
+ { *type = mContainerType; return NS_OK; }
+ NS_IMETHOD GetUri(nsACString& aURI) override
+ { aURI = mURI; return NS_OK; }
+ NS_DECL_NSINAVHISTORYCONTAINERRESULTNODE
+
+public:
+
+ virtual void OnRemoving() override;
+
+ bool AreChildrenVisible();
+
+ // Overridded by descendents to populate.
+ virtual nsresult OpenContainer();
+ nsresult CloseContainer(bool aSuppressNotifications = false);
+
+ virtual nsresult OpenContainerAsync();
+
+ // This points to the result that owns this container. All containers have
+ // their result pointer set so we can quickly get to the result without having
+ // to walk the tree. Yet, this also saves us from storing a million pointers
+ // for every leaf node to the result.
+ RefPtr<nsNavHistoryResult> mResult;
+
+ // For example, RESULT_TYPE_QUERY. Query and Folder results override GetType
+ // so this is not used, but is still kept in sync.
+ uint32_t mContainerType;
+
+ // When there are children, this stores the open state in the tree
+ // this is set to the default in the constructor.
+ bool mExpanded;
+
+ // Filled in by the result type generator in nsNavHistory.
+ nsCOMArray<nsNavHistoryResultNode> mChildren;
+
+ nsCOMPtr<nsNavHistoryQueryOptions> mOptions;
+
+ void FillStats();
+ nsresult ReverseUpdateStats(int32_t aAccessCountChange);
+
+ // Sorting methods.
+ typedef nsCOMArray<nsNavHistoryResultNode>::nsCOMArrayComparatorFunc SortComparator;
+ virtual uint16_t GetSortType();
+ virtual void GetSortingAnnotation(nsACString& aSortingAnnotation);
+
+ static SortComparator GetSortingComparator(uint16_t aSortType);
+ virtual void RecursiveSort(const char* aData,
+ SortComparator aComparator);
+ uint32_t FindInsertionPoint(nsNavHistoryResultNode* aNode, SortComparator aComparator,
+ const char* aData, bool* aItemExists);
+ bool DoesChildNeedResorting(uint32_t aIndex, SortComparator aComparator,
+ const char* aData);
+
+ static int32_t SortComparison_StringLess(const nsAString& a, const nsAString& b);
+
+ static int32_t SortComparison_Bookmark(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_TitleLess(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_TitleGreater(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_DateLess(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_DateGreater(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_URILess(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_URIGreater(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_VisitCountLess(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_VisitCountGreater(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_KeywordLess(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_KeywordGreater(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_AnnotationLess(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_AnnotationGreater(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_DateAddedLess(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_DateAddedGreater(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_LastModifiedLess(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_LastModifiedGreater(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_TagsLess(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_TagsGreater(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_FrecencyLess(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+ static int32_t SortComparison_FrecencyGreater(nsNavHistoryResultNode* a,
+ nsNavHistoryResultNode* b,
+ void* closure);
+
+ // finding children: THESE DO NOT ADDREF
+ nsNavHistoryResultNode* FindChildURI(const nsACString& aSpec,
+ uint32_t* aNodeIndex);
+ // returns the index of the given node, -1 if not found
+ int32_t FindChild(nsNavHistoryResultNode* aNode)
+ { return mChildren.IndexOf(aNode); }
+
+ nsresult InsertChildAt(nsNavHistoryResultNode* aNode, int32_t aIndex);
+ nsresult InsertSortedChild(nsNavHistoryResultNode* aNode,
+ bool aIgnoreDuplicates = false);
+ bool EnsureItemPosition(uint32_t aIndex);
+
+ nsresult RemoveChildAt(int32_t aIndex);
+
+ void RecursiveFindURIs(bool aOnlyOne,
+ nsNavHistoryContainerResultNode* aContainer,
+ const nsCString& aSpec,
+ nsCOMArray<nsNavHistoryResultNode>* aMatches);
+ bool UpdateURIs(bool aRecursive, bool aOnlyOne, bool aUpdateSort,
+ const nsCString& aSpec,
+ nsresult (*aCallback)(nsNavHistoryResultNode*, const void*,
+ const nsNavHistoryResult*),
+ const void* aClosure);
+ nsresult ChangeTitles(nsIURI* aURI, const nsACString& aNewTitle,
+ bool aRecursive, bool aOnlyOne);
+
+protected:
+ virtual ~nsNavHistoryContainerResultNode();
+
+ enum AsyncCanceledState {
+ NOT_CANCELED, CANCELED, CANCELED_RESTART_NEEDED
+ };
+
+ void CancelAsyncOpen(bool aRestart);
+ nsresult NotifyOnStateChange(uint16_t aOldState);
+
+ nsCOMPtr<mozIStoragePendingStatement> mAsyncPendingStmt;
+ AsyncCanceledState mAsyncCanceledState;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsNavHistoryContainerResultNode,
+ NS_NAVHISTORYCONTAINERRESULTNODE_IID)
+
+// nsNavHistoryQueryResultNode
+//
+// Overridden container type for complex queries over history and/or
+// bookmarks. This keeps itself in sync by listening to history and
+// bookmark notifications.
+
+class nsNavHistoryQueryResultNode final : public nsNavHistoryContainerResultNode,
+ public nsINavHistoryQueryResultNode,
+ public nsINavBookmarkObserver
+{
+public:
+ nsNavHistoryQueryResultNode(const nsACString& aTitle,
+ const nsACString& aIconURI,
+ const nsACString& aQueryURI);
+ nsNavHistoryQueryResultNode(const nsACString& aTitle,
+ const nsACString& aIconURI,
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions);
+ nsNavHistoryQueryResultNode(const nsACString& aTitle,
+ const nsACString& aIconURI,
+ PRTime aTime,
+ const nsCOMArray<nsNavHistoryQuery>& aQueries,
+ nsNavHistoryQueryOptions* aOptions);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_COMMON_RESULTNODE_TO_BASE
+ NS_IMETHOD GetType(uint32_t* type) override
+ { *type = nsNavHistoryResultNode::RESULT_TYPE_QUERY; return NS_OK; }
+ NS_IMETHOD GetUri(nsACString& aURI) override; // does special lazy creation
+ NS_FORWARD_CONTAINERNODE_EXCEPT_HASCHILDREN
+ NS_IMETHOD GetHasChildren(bool* aHasChildren) override;
+ NS_DECL_NSINAVHISTORYQUERYRESULTNODE
+
+ bool CanExpand();
+ bool IsContainersQuery();
+
+ virtual nsresult OpenContainer() override;
+
+ NS_DECL_BOOKMARK_HISTORY_OBSERVER_INTERNAL
+ virtual void OnRemoving() override;
+
+public:
+ // this constructs lazily mURI from mQueries and mOptions, call
+ // VerifyQueriesSerialized either this or mQueries/mOptions should be valid
+ nsresult VerifyQueriesSerialized();
+
+ // these may be constructed lazily from mURI, call VerifyQueriesParsed
+ // either this or mURI should be valid
+ nsCOMArray<nsNavHistoryQuery> mQueries;
+ uint32_t mLiveUpdate; // one of QUERYUPDATE_* in nsNavHistory.h
+ bool mHasSearchTerms;
+ nsresult VerifyQueriesParsed();
+
+ // safe options getter, ensures queries are parsed
+ nsNavHistoryQueryOptions* Options();
+
+ // this indicates whether the query contents are valid, they don't go away
+ // after the container is closed until a notification comes in
+ bool mContentsValid;
+
+ nsresult FillChildren();
+ void ClearChildren(bool unregister);
+ nsresult Refresh() override;
+
+ virtual uint16_t GetSortType() override;
+ virtual void GetSortingAnnotation(nsACString& aSortingAnnotation) override;
+ virtual void RecursiveSort(const char* aData,
+ SortComparator aComparator) override;
+
+ nsresult NotifyIfTagsChanged(nsIURI* aURI);
+
+ uint32_t mBatchChanges;
+
+ // Tracks transition type filters shared by all mQueries.
+ nsTArray<uint32_t> mTransitions;
+
+protected:
+ virtual ~nsNavHistoryQueryResultNode();
+};
+
+
+// nsNavHistoryFolderResultNode
+//
+// Overridden container type for bookmark folders. It will keep the contents
+// of the folder in sync with the bookmark service.
+
+class nsNavHistoryFolderResultNode final : public nsNavHistoryContainerResultNode,
+ public nsINavHistoryQueryResultNode,
+ public nsINavBookmarkObserver,
+ public mozilla::places::WeakAsyncStatementCallback
+{
+public:
+ nsNavHistoryFolderResultNode(const nsACString& aTitle,
+ nsNavHistoryQueryOptions* options,
+ int64_t aFolderId);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_COMMON_RESULTNODE_TO_BASE
+ NS_IMETHOD GetType(uint32_t* type) override {
+ if (mTargetFolderItemId != mItemId) {
+ *type = nsNavHistoryResultNode::RESULT_TYPE_FOLDER_SHORTCUT;
+ } else {
+ *type = nsNavHistoryResultNode::RESULT_TYPE_FOLDER;
+ }
+ return NS_OK;
+ }
+ NS_IMETHOD GetUri(nsACString& aURI) override;
+ NS_FORWARD_CONTAINERNODE_EXCEPT_HASCHILDREN
+ NS_IMETHOD GetHasChildren(bool* aHasChildren) override;
+ NS_DECL_NSINAVHISTORYQUERYRESULTNODE
+
+ virtual nsresult OpenContainer() override;
+
+ virtual nsresult OpenContainerAsync() override;
+ NS_DECL_ASYNCSTATEMENTCALLBACK
+
+ // This object implements a bookmark observer interface. This is called from the
+ // result's actual observer and it knows all observers are FolderResultNodes
+ NS_DECL_NSINAVBOOKMARKOBSERVER
+
+ virtual void OnRemoving() override;
+
+ // this indicates whether the folder contents are valid, they don't go away
+ // after the container is closed until a notification comes in
+ bool mContentsValid;
+
+ // If the node is generated from a place:folder=X query, this is the target
+ // folder id and GUID. For regular folder nodes, they are set to the same
+ // values as mItemId and mBookmarkGuid. For more complex queries, they are set
+ // to -1/an empty string.
+ int64_t mTargetFolderItemId;
+ nsCString mTargetFolderGuid;
+
+ nsresult FillChildren();
+ void ClearChildren(bool aUnregister);
+ nsresult Refresh() override;
+
+ bool StartIncrementalUpdate();
+ void ReindexRange(int32_t aStartIndex, int32_t aEndIndex, int32_t aDelta);
+
+ nsNavHistoryResultNode* FindChildById(int64_t aItemId,
+ uint32_t* aNodeIndex);
+
+protected:
+ virtual ~nsNavHistoryFolderResultNode();
+
+private:
+
+ nsresult OnChildrenFilled();
+ void EnsureRegisteredAsFolderObserver();
+ nsresult FillChildrenAsync();
+
+ bool mIsRegisteredFolderObserver;
+ int32_t mAsyncBookmarkIndex;
+};
+
+// nsNavHistorySeparatorResultNode
+//
+// Separator result nodes do not hold any data.
+class nsNavHistorySeparatorResultNode : public nsNavHistoryResultNode
+{
+public:
+ nsNavHistorySeparatorResultNode();
+
+ NS_IMETHOD GetType(uint32_t* type)
+ { *type = nsNavHistoryResultNode::RESULT_TYPE_SEPARATOR; return NS_OK; }
+};
+
+#endif // nsNavHistoryResult_h_
diff --git a/components/places/src/nsPlacesAutoComplete.js b/components/places/src/nsPlacesAutoComplete.js
new file mode 100644
index 000000000..88de3a1f9
--- /dev/null
+++ b/components/places/src/nsPlacesAutoComplete.js
@@ -0,0 +1,1756 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// Constants
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+// This SQL query fragment provides the following:
+// - whether the entry is bookmarked (kQueryIndexBookmarked)
+// - the bookmark title, if it is a bookmark (kQueryIndexBookmarkTitle)
+// - the tags associated with a bookmarked entry (kQueryIndexTags)
+const kBookTagSQLFragment =
+ `EXISTS(SELECT 1 FROM moz_bookmarks WHERE fk = h.id) AS bookmarked,
+ (
+ SELECT title FROM moz_bookmarks WHERE fk = h.id AND title NOTNULL
+ ORDER BY lastModified DESC LIMIT 1
+ ) AS btitle,
+ (
+ SELECT GROUP_CONCAT(t.title, ',')
+ FROM moz_bookmarks b
+ JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent
+ WHERE b.fk = h.id
+ ) AS tags`;
+
+// observer topics
+const kTopicShutdown = "places-shutdown";
+const kPrefChanged = "nsPref:changed";
+
+// Match type constants. These indicate what type of search function we should
+// be using.
+const MATCH_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE;
+const MATCH_BOUNDARY_ANYWHERE = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY_ANYWHERE;
+const MATCH_BOUNDARY = Ci.mozIPlacesAutoComplete.MATCH_BOUNDARY;
+const MATCH_BEGINNING = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING;
+const MATCH_BEGINNING_CASE_SENSITIVE = Ci.mozIPlacesAutoComplete.MATCH_BEGINNING_CASE_SENSITIVE;
+
+// AutoComplete index constants. All AutoComplete queries will provide these
+// columns in this order.
+const kQueryIndexURL = 0;
+const kQueryIndexTitle = 1;
+const kQueryIndexFaviconURL = 2;
+const kQueryIndexBookmarked = 3;
+const kQueryIndexBookmarkTitle = 4;
+const kQueryIndexTags = 5;
+const kQueryIndexVisitCount = 6;
+const kQueryIndexTyped = 7;
+const kQueryIndexPlaceId = 8;
+const kQueryIndexQueryType = 9;
+const kQueryIndexOpenPageCount = 10;
+
+// AutoComplete query type constants. Describes the various types of queries
+// that we can process.
+const kQueryTypeKeyword = 0;
+const kQueryTypeFiltered = 1;
+
+// This separator is used as an RTL-friendly way to split the title and tags.
+// It can also be used by an nsIAutoCompleteResult consumer to re-split the
+// "comment" back into the title and the tag.
+const kTitleTagsSeparator = " \u2013 ";
+
+const kBrowserUrlbarBranch = "browser.urlbar.";
+// Toggle autocomplete.
+const kBrowserUrlbarAutocompleteEnabledPref = "autocomplete.enabled";
+// Toggle autoFill.
+const kBrowserUrlbarAutofillPref = "autoFill";
+// Whether to search only typed entries.
+const kBrowserUrlbarAutofillTypedPref = "autoFill.typed";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+XPCOMUtils.defineLazyServiceGetter(this, "gTextURIService",
+ "@mozilla.org/intl/texttosuburi;1",
+ "nsITextToSubURI");
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers
+
+/**
+ * Initializes our temporary table on a given database.
+ *
+ * @param aDatabase
+ * The mozIStorageConnection to set up the temp table on.
+ */
+function initTempTable(aDatabase)
+{
+ // Note: this should be kept up-to-date with the definition in
+ // nsPlacesTables.h.
+ let stmt = aDatabase.createAsyncStatement(
+ `CREATE TEMP TABLE moz_openpages_temp (
+ url TEXT PRIMARY KEY
+ , open_count INTEGER
+ )`
+ );
+ stmt.executeAsync();
+ stmt.finalize();
+
+ // Note: this should be kept up-to-date with the definition in
+ // nsPlacesTriggers.h.
+ stmt = aDatabase.createAsyncStatement(
+ `CREATE TEMPORARY TRIGGER moz_openpages_temp_afterupdate_trigger
+ AFTER UPDATE OF open_count ON moz_openpages_temp FOR EACH ROW
+ WHEN NEW.open_count = 0
+ BEGIN
+ DELETE FROM moz_openpages_temp
+ WHERE url = NEW.url;
+ END`
+ );
+ stmt.executeAsync();
+ stmt.finalize();
+}
+
+/**
+ * Used to unescape encoded URI strings, and drop information that we do not
+ * care about for searching.
+ *
+ * @param aURIString
+ * The text to unescape and modify.
+ * @return the modified uri.
+ */
+function fixupSearchText(aURIString)
+{
+ let uri = stripPrefix(aURIString);
+ return gTextURIService.unEscapeURIForUI("UTF-8", uri);
+}
+
+/**
+ * Strip prefixes from the URI that we don't care about for searching.
+ *
+ * @param aURIString
+ * The text to modify.
+ * @return the modified uri.
+ */
+function stripPrefix(aURIString)
+{
+ let uri = aURIString;
+
+ if (uri.indexOf("http://") == 0) {
+ uri = uri.slice(7);
+ }
+ else if (uri.indexOf("https://") == 0) {
+ uri = uri.slice(8);
+ }
+ else if (uri.indexOf("ftp://") == 0) {
+ uri = uri.slice(6);
+ }
+
+ if (uri.indexOf("www.") == 0) {
+ uri = uri.slice(4);
+ }
+ return uri;
+}
+
+/**
+ * safePrefGetter get the pref with type safety.
+ * This will return the default value provided if no pref is set.
+ *
+ * @param aPrefBranch
+ * The nsIPrefBranch containing the required preference
+ * @param aName
+ * A preference name
+ * @param aDefault
+ * The preference's default value
+ * @return the preference value or provided default
+ */
+
+function safePrefGetter(aPrefBranch, aName, aDefault) {
+ let types = {
+ boolean: "Bool",
+ number: "Int",
+ string: "Char"
+ };
+ let type = types[typeof(aDefault)];
+ if (!type) {
+ throw "Unknown type!";
+ }
+
+ // If the pref isn't set, we want to use the default.
+ if (aPrefBranch.getPrefType(aName) == Ci.nsIPrefBranch.PREF_INVALID) {
+ return aDefault;
+ }
+ try {
+ return aPrefBranch["get" + type + "Pref"](aName);
+ }
+ catch (e) {
+ return aDefault;
+ }
+}
+
+/**
+ * Whether UnifiedComplete is alive.
+ */
+function isUnifiedCompleteInstantiated() {
+ try {
+ return Components.manager.QueryInterface(Ci.nsIServiceManager)
+ .isServiceInstantiated(Cc["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"],
+ Ci.mozIPlacesAutoComplete);
+ } catch (ex) {
+ return false;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// AutoCompleteStatementCallbackWrapper class
+
+/**
+ * Wraps a callback and ensures that handleCompletion is not dispatched if the
+ * query is no longer tracked.
+ *
+ * @param aAutocomplete
+ * A reference to a nsPlacesAutoComplete.
+ * @param aCallback
+ * A reference to a mozIStorageStatementCallback
+ * @param aDBConnection
+ * The database connection to execute the queries on.
+ */
+function AutoCompleteStatementCallbackWrapper(aAutocomplete, aCallback,
+ aDBConnection)
+{
+ this._autocomplete = aAutocomplete;
+ this._callback = aCallback;
+ this._db = aDBConnection;
+}
+
+AutoCompleteStatementCallbackWrapper.prototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// mozIStorageStatementCallback
+
+ handleResult: function ACSCW_handleResult(aResultSet)
+ {
+ this._callback.handleResult.apply(this._callback, arguments);
+ },
+
+ handleError: function ACSCW_handleError(aError)
+ {
+ this._callback.handleError.apply(this._callback, arguments);
+ },
+
+ handleCompletion: function ACSCW_handleCompletion(aReason)
+ {
+ // Only dispatch handleCompletion if we are not done searching and are a
+ // pending search.
+ if (!this._autocomplete.isSearchComplete() &&
+ this._autocomplete.isPendingSearch(this._handle)) {
+ this._callback.handleCompletion.apply(this._callback, arguments);
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// AutoCompleteStatementCallbackWrapper
+
+ /**
+ * Executes the specified query asynchronously. This object will notify
+ * this._callback if we should notify (logic explained in handleCompletion).
+ *
+ * @param aQueries
+ * The queries to execute asynchronously.
+ * @return a mozIStoragePendingStatement that can be used to cancel the
+ * queries.
+ */
+ executeAsync: function ACSCW_executeAsync(aQueries)
+ {
+ return this._handle = this._db.executeAsync(aQueries, aQueries.length,
+ this);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.mozIStorageStatementCallback,
+ ])
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsPlacesAutoComplete class
+//// @mozilla.org/autocomplete/search;1?name=history
+
+function nsPlacesAutoComplete()
+{
+ //////////////////////////////////////////////////////////////////////////////
+ //// Shared Constants for Smart Getters
+
+ // TODO bug 412736 in case of a frecency tie, break it with h.typed and
+ // h.visit_count which is better than nothing. This is slow, so not doing it
+ // yet...
+ function baseQuery(conditions = "") {
+ let query = `SELECT h.url, h.title, f.url, ${kBookTagSQLFragment},
+ h.visit_count, h.typed, h.id, :query_type,
+ t.open_count
+ FROM moz_places h
+ LEFT JOIN moz_favicons f ON f.id = h.favicon_id
+ LEFT JOIN moz_openpages_temp t ON t.url = h.url
+ WHERE h.frecency <> 0
+ AND AUTOCOMPLETE_MATCH(:searchString, h.url,
+ IFNULL(btitle, h.title), tags,
+ h.visit_count, h.typed,
+ bookmarked, t.open_count,
+ :matchBehavior, :searchBehavior)
+ ${conditions}
+ ORDER BY h.frecency DESC, h.id DESC
+ LIMIT :maxResults`;
+ return query;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Smart Getters
+
+ XPCOMUtils.defineLazyGetter(this, "_db", function() {
+ // Get a cloned, read-only version of the database. We'll only ever write
+ // to our own in-memory temp table, and having a cloned copy means we do not
+ // run the risk of our queries taking longer due to the main database
+ // connection performing a long-running task.
+ let db = PlacesUtils.history.DBConnection.clone(true);
+
+ // Autocomplete often fallbacks to a table scan due to lack of text indices.
+ // In such cases a larger cache helps reducing IO. The default Storage
+ // value is MAX_CACHE_SIZE_BYTES in storage/mozStorageConnection.cpp.
+ let stmt = db.createAsyncStatement("PRAGMA cache_size = -6144"); // 6MiB
+ stmt.executeAsync();
+ stmt.finalize();
+
+ // Create our in-memory tables for tab tracking.
+ initTempTable(db);
+
+ // Populate the table with current open pages cache contents.
+ if (this._openPagesCache.length > 0) {
+ // Avoid getter re-entrance from the _registerOpenPageQuery lazy getter.
+ let stmt = this._registerOpenPageQuery =
+ db.createAsyncStatement(this._registerOpenPageQuerySQL);
+ let params = stmt.newBindingParamsArray();
+ for (let i = 0; i < this._openPagesCache.length; i++) {
+ let bp = params.newBindingParams();
+ bp.bindByName("page_url", this._openPagesCache[i]);
+ params.addParams(bp);
+ }
+ stmt.bindParameters(params);
+ stmt.executeAsync();
+ stmt.finalize();
+ delete this._openPagesCache;
+ }
+
+ return db;
+ });
+
+ this._customQuery = (conditions = "") => {
+ return this._db.createAsyncStatement(baseQuery(conditions));
+ };
+
+ XPCOMUtils.defineLazyGetter(this, "_defaultQuery", function() {
+ return this._db.createAsyncStatement(baseQuery());
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_historyQuery", function() {
+ // Enforce ignoring the visit_count index, since the frecency one is much
+ // faster in this case. ANALYZE helps the query planner to figure out the
+ // faster path, but it may not have run yet.
+ return this._db.createAsyncStatement(baseQuery("AND +h.visit_count > 0"));
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_bookmarkQuery", function() {
+ return this._db.createAsyncStatement(baseQuery("AND bookmarked"));
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_tagsQuery", function() {
+ return this._db.createAsyncStatement(baseQuery("AND tags IS NOT NULL"));
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_openPagesQuery", function() {
+ return this._db.createAsyncStatement(
+ `SELECT t.url, t.url, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ :query_type, t.open_count, NULL
+ FROM moz_openpages_temp t
+ LEFT JOIN moz_places h ON h.url = t.url
+ WHERE h.id IS NULL
+ AND AUTOCOMPLETE_MATCH(:searchString, t.url, t.url, NULL,
+ NULL, NULL, NULL, t.open_count,
+ :matchBehavior, :searchBehavior)
+ ORDER BY t.ROWID DESC
+ LIMIT :maxResults`
+ );
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_typedQuery", function() {
+ return this._db.createAsyncStatement(baseQuery("AND h.typed = 1"));
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_adaptiveQuery", function() {
+ return this._db.createAsyncStatement(
+ `/* do not warn (bug 487789) */
+ SELECT h.url, h.title, f.url, ${kBookTagSQLFragment},
+ h.visit_count, h.typed, h.id, :query_type, t.open_count
+ FROM (
+ SELECT ROUND(
+ MAX(use_count) * (1 + (input = :search_string)), 1
+ ) AS rank, place_id
+ FROM moz_inputhistory
+ WHERE input BETWEEN :search_string AND :search_string || X'FFFF'
+ GROUP BY place_id
+ ) AS i
+ JOIN moz_places h ON h.id = i.place_id
+ LEFT JOIN moz_favicons f ON f.id = h.favicon_id
+ LEFT JOIN moz_openpages_temp t ON t.url = h.url
+ WHERE AUTOCOMPLETE_MATCH(NULL, h.url,
+ IFNULL(btitle, h.title), tags,
+ h.visit_count, h.typed, bookmarked,
+ t.open_count,
+ :matchBehavior, :searchBehavior)
+ ORDER BY rank DESC, h.frecency DESC`
+ );
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_keywordQuery", function() {
+ return this._db.createAsyncStatement(
+ `/* do not warn (bug 487787) */
+ SELECT REPLACE(h.url, '%s', :query_string) AS search_url, h.title,
+ IFNULL(f.url, (SELECT f.url
+ FROM moz_places
+ JOIN moz_favicons f ON f.id = favicon_id
+ WHERE rev_host = h.rev_host
+ ORDER BY frecency DESC
+ LIMIT 1)
+ ), 1, NULL, NULL, h.visit_count, h.typed, h.id,
+ :query_type, t.open_count
+ FROM moz_keywords k
+ JOIN moz_places h ON k.place_id = h.id
+ LEFT JOIN moz_favicons f ON f.id = h.favicon_id
+ LEFT JOIN moz_openpages_temp t ON t.url = search_url
+ WHERE k.keyword = LOWER(:keyword)`
+ );
+ });
+
+ this._registerOpenPageQuerySQL =
+ `INSERT OR REPLACE INTO moz_openpages_temp (url, open_count)
+ VALUES (:page_url,
+ IFNULL(
+ (
+ SELECT open_count + 1
+ FROM moz_openpages_temp
+ WHERE url = :page_url
+ ),
+ 1
+ )
+ )`;
+ XPCOMUtils.defineLazyGetter(this, "_registerOpenPageQuery", function() {
+ return this._db.createAsyncStatement(this._registerOpenPageQuerySQL);
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_unregisterOpenPageQuery", function() {
+ return this._db.createAsyncStatement(
+ `UPDATE moz_openpages_temp
+ SET open_count = open_count - 1
+ WHERE url = :page_url`
+ );
+ });
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Initialization
+
+ // load preferences
+ this._prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(kBrowserUrlbarBranch);
+ this._syncEnabledPref();
+ this._loadPrefs(true);
+
+ // register observers
+ this._os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ this._os.addObserver(this, kTopicShutdown, false);
+
+}
+
+nsPlacesAutoComplete.prototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIAutoCompleteSearch
+
+ startSearch: function PAC_startSearch(aSearchString, aSearchParam,
+ aPreviousResult, aListener)
+ {
+ // Stop the search in case the controller has not taken care of it.
+ this.stopSearch();
+
+ // Note: We don't use aPreviousResult to make sure ordering of results are
+ // consistent. See bug 412730 for more details.
+
+ // We want to store the original string with no leading or trailing
+ // whitespace for case sensitive searches.
+ this._originalSearchString = aSearchString.trim();
+
+ this._currentSearchString =
+ fixupSearchText(this._originalSearchString.toLowerCase());
+
+ let params = new Set(aSearchParam.split(" "));
+ this._enableActions = params.has("enable-actions");
+ this._disablePrivateActions = params.has("disable-private-actions");
+
+ this._listener = aListener;
+ let result = Cc["@mozilla.org/autocomplete/simple-result;1"].
+ createInstance(Ci.nsIAutoCompleteSimpleResult);
+ result.setSearchString(aSearchString);
+ result.setListener(this);
+ this._result = result;
+
+ // If we are not enabled, we need to return now.
+ if (!this._enabled) {
+ this._finishSearch(true);
+ return;
+ }
+
+ // Reset our search behavior to the default.
+ if (this._currentSearchString) {
+ this._behavior = this._defaultBehavior;
+ }
+ else {
+ this._behavior = this._emptySearchDefaultBehavior;
+ }
+ // For any given search, we run up to four queries:
+ // 1) keywords (this._keywordQuery)
+ // 2) adaptive learning (this._adaptiveQuery)
+ // 3) open pages not supported by history (this._openPagesQuery)
+ // 4) query from this._getSearch
+ // (1) only gets ran if we get any filtered tokens from this._getSearch,
+ // since if there are no tokens, there is nothing to match, so there is no
+ // reason to run the query).
+ let {query, tokens} =
+ this._getSearch(this._getUnfilteredSearchTokens(this._currentSearchString));
+ let queries = tokens.length ?
+ [this._getBoundKeywordQuery(tokens), this._getBoundAdaptiveQuery()] :
+ [this._getBoundAdaptiveQuery()];
+
+ if (this._hasBehavior("openpage")) {
+ queries.push(this._getBoundOpenPagesQuery(tokens));
+ }
+ queries.push(query);
+
+ // Start executing our queries.
+ this._executeQueries(queries);
+
+ // Set up our persistent state for the duration of the search.
+ this._searchTokens = tokens;
+ this._usedPlaces = {};
+ },
+
+ stopSearch: function PAC_stopSearch()
+ {
+ // We need to cancel our searches so we do not get any [more] results.
+ // However, it's possible we haven't actually started any searches, so this
+ // method may throw because this._pendingQuery may be undefined.
+ if (this._pendingQuery) {
+ this._stopActiveQuery();
+ }
+
+ this._finishSearch(false);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIAutoCompleteSimpleResultListener
+
+ onValueRemoved: function PAC_onValueRemoved(aResult, aURISpec, aRemoveFromDB)
+ {
+ if (aRemoveFromDB) {
+ PlacesUtils.history.removePage(NetUtil.newURI(aURISpec));
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// mozIPlacesAutoComplete
+
+ // If the connection has not yet been started, use this local cache. This
+ // prevents autocomplete from initing the database till the first search.
+ _openPagesCache: [],
+ registerOpenPage: function PAC_registerOpenPage(aURI)
+ {
+ if (!this._databaseInitialized) {
+ this._openPagesCache.push(aURI.spec);
+ return;
+ }
+
+ let stmt = this._registerOpenPageQuery;
+ stmt.params.page_url = aURI.spec;
+ stmt.executeAsync();
+ },
+
+ unregisterOpenPage: function PAC_unregisterOpenPage(aURI)
+ {
+ if (!this._databaseInitialized) {
+ let index = this._openPagesCache.indexOf(aURI.spec);
+ if (index != -1) {
+ this._openPagesCache.splice(index, 1);
+ }
+ return;
+ }
+
+ let stmt = this._unregisterOpenPageQuery;
+ stmt.params.page_url = aURI.spec;
+ stmt.executeAsync();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// mozIStorageStatementCallback
+
+ handleResult: function PAC_handleResult(aResultSet)
+ {
+ let row, haveMatches = false;
+ while ((row = aResultSet.getNextRow())) {
+ let match = this._processRow(row);
+ haveMatches = haveMatches || match;
+
+ if (this._result.matchCount == this._maxRichResults) {
+ // We have enough results, so stop running our search.
+ this._stopActiveQuery();
+
+ // And finish our search.
+ this._finishSearch(true);
+ return;
+ }
+
+ }
+
+ // Notify about results if we've gotten them.
+ if (haveMatches) {
+ this._notifyResults(true);
+ }
+ },
+
+ handleError: function PAC_handleError(aError)
+ {
+ Components.utils.reportError("Places AutoComplete: An async statement encountered an " +
+ "error: " + aError.result + ", '" + aError.message + "'");
+ },
+
+ handleCompletion: function PAC_handleCompletion(aReason)
+ {
+ // If we have already finished our search, we should bail out early.
+ if (this.isSearchComplete()) {
+ return;
+ }
+
+ // If we do not have enough results, and our match type is
+ // MATCH_BOUNDARY_ANYWHERE, search again with MATCH_ANYWHERE to get more
+ // results.
+ if (this._matchBehavior == MATCH_BOUNDARY_ANYWHERE &&
+ this._result.matchCount < this._maxRichResults && !this._secondPass) {
+ this._secondPass = true;
+ let queries = [
+ this._getBoundAdaptiveQuery(MATCH_ANYWHERE),
+ this._getBoundSearchQuery(MATCH_ANYWHERE, this._searchTokens),
+ ];
+ this._executeQueries(queries);
+ return;
+ }
+
+ this._finishSearch(true);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIObserver
+
+ observe: function PAC_observe(aSubject, aTopic, aData)
+ {
+ if (aTopic == kTopicShutdown) {
+ this._os.removeObserver(this, kTopicShutdown);
+
+ // Remove our preference observer.
+ this._prefs.removeObserver("", this);
+ delete this._prefs;
+
+ // Finalize the statements that we have used.
+ let stmts = [
+ "_defaultQuery",
+ "_historyQuery",
+ "_bookmarkQuery",
+ "_tagsQuery",
+ "_openPagesQuery",
+ "_typedQuery",
+ "_adaptiveQuery",
+ "_keywordQuery",
+ "_registerOpenPageQuery",
+ "_unregisterOpenPageQuery",
+ ];
+ for (let i = 0; i < stmts.length; i++) {
+ // We do not want to create any query we haven't already created, so
+ // see if it is a getter first.
+ if (Object.getOwnPropertyDescriptor(this, stmts[i]).value !== undefined) {
+ this[stmts[i]].finalize();
+ }
+ }
+
+ if (this._databaseInitialized) {
+ this._db.asyncClose();
+ }
+ }
+ else if (aTopic == kPrefChanged) {
+ // Avoid re-entrancy when flipping linked preferences.
+ if (this._ignoreNotifications)
+ return;
+ this._ignoreNotifications = true;
+ this._loadPrefs(false, aTopic, aData);
+ this._ignoreNotifications = false;
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsPlacesAutoComplete
+
+ get _databaseInitialized() {
+ return Object.getOwnPropertyDescriptor(this, "_db").value !== undefined;
+ },
+
+ /**
+ * Generates the tokens used in searching from a given string.
+ *
+ * @param aSearchString
+ * The string to generate tokens from.
+ * @return an array of tokens.
+ */
+ _getUnfilteredSearchTokens: function PAC_unfilteredSearchTokens(aSearchString)
+ {
+ // Calling split on an empty string will return an array containing one
+ // empty string. We don't want that, as it'll break our logic, so return an
+ // empty array then.
+ return aSearchString.length ? aSearchString.split(" ") : [];
+ },
+
+ /**
+ * Properly cleans up when searching is completed.
+ *
+ * @param aNotify
+ * Indicates if we should notify the AutoComplete listener about our
+ * results or not.
+ */
+ _finishSearch: function PAC_finishSearch(aNotify)
+ {
+ // Notify about results if we are supposed to.
+ if (aNotify) {
+ this._notifyResults(false);
+ }
+
+ // Clear our state
+ delete this._originalSearchString;
+ delete this._currentSearchString;
+ delete this._strippedPrefix;
+ delete this._searchTokens;
+ delete this._listener;
+ delete this._result;
+ delete this._usedPlaces;
+ delete this._pendingQuery;
+ this._secondPass = false;
+ this._enableActions = false;
+ },
+
+ /**
+ * Executes the given queries asynchronously.
+ *
+ * @param aQueries
+ * The queries to execute.
+ */
+ _executeQueries: function PAC_executeQueries(aQueries)
+ {
+ // Because we might get a handleCompletion for canceled queries, we want to
+ // filter out queries we no longer care about (described in the
+ // handleCompletion implementation of AutoCompleteStatementCallbackWrapper).
+
+ // Create our wrapper object and execute the queries.
+ let wrapper = new AutoCompleteStatementCallbackWrapper(this, this, this._db);
+ this._pendingQuery = wrapper.executeAsync(aQueries);
+ },
+
+ /**
+ * Stops executing our active query.
+ */
+ _stopActiveQuery: function PAC_stopActiveQuery()
+ {
+ this._pendingQuery.cancel();
+ delete this._pendingQuery;
+ },
+
+ /**
+ * Notifies the listener about results.
+ *
+ * @param aSearchOngoing
+ * Indicates if the search is ongoing or not.
+ */
+ _notifyResults: function PAC_notifyResults(aSearchOngoing)
+ {
+ let result = this._result;
+ let resultCode = result.matchCount ? "RESULT_SUCCESS" : "RESULT_NOMATCH";
+ if (aSearchOngoing) {
+ resultCode += "_ONGOING";
+ }
+ result.setSearchResult(Ci.nsIAutoCompleteResult[resultCode]);
+ this._listener.onSearchResult(this, result);
+ },
+
+ /**
+ * Synchronize suggest.* prefs with autocomplete.enabled.
+ */
+ _syncEnabledPref: function PAC_syncEnabledPref()
+ {
+ let suggestPrefs = ["suggest.history", "suggest.bookmark", "suggest.openpage"];
+ let types = ["History", "Bookmark", "Openpage"];
+
+ this._enabled = safePrefGetter(this._prefs, kBrowserUrlbarAutocompleteEnabledPref,
+ true);
+ this._suggestHistory = safePrefGetter(this._prefs, "suggest.history", true);
+ this._suggestBookmark = safePrefGetter(this._prefs, "suggest.bookmark", true);
+ this._suggestOpenpage = safePrefGetter(this._prefs, "suggest.openpage", true);
+
+ if (this._enabled) {
+ // If the autocomplete preference is active, activate all suggest
+ // preferences only if all of them are false.
+ if (types.every(type => this["_suggest" + type] == false)) {
+ for (let type of suggestPrefs) {
+ this._prefs.setBoolPref(type, true);
+ }
+ }
+ } else {
+ // If the preference was deactivated, deactivate all suggest preferences.
+ for (let type of suggestPrefs) {
+ this._prefs.setBoolPref(type, false);
+ }
+ }
+ },
+
+ /**
+ * Loads the preferences that we care about.
+ *
+ * @param [optional] aRegisterObserver
+ * Indicates if the preference observer should be added or not. The
+ * default value is false.
+ * @param [optional] aTopic
+ * Observer's topic, if any.
+ * @param [optional] aSubject
+ * Observer's subject, if any.
+ */
+ _loadPrefs: function PAC_loadPrefs(aRegisterObserver, aTopic, aData)
+ {
+ // Avoid race conditions with UnifiedComplete component.
+ if (aData && !isUnifiedCompleteInstantiated()) {
+ // Synchronize suggest.* prefs with autocomplete.enabled.
+ if (aData == kBrowserUrlbarAutocompleteEnabledPref) {
+ this._syncEnabledPref();
+ } else if (aData.startsWith("suggest.")) {
+ let suggestPrefs = ["suggest.history", "suggest.bookmark", "suggest.openpage"];
+ this._prefs.setBoolPref(kBrowserUrlbarAutocompleteEnabledPref,
+ suggestPrefs.some(pref => safePrefGetter(this._prefs, pref, true)));
+ }
+ }
+
+ this._enabled = safePrefGetter(this._prefs,
+ kBrowserUrlbarAutocompleteEnabledPref,
+ true);
+ this._matchBehavior = safePrefGetter(this._prefs,
+ "matchBehavior",
+ MATCH_BOUNDARY_ANYWHERE);
+ this._filterJavaScript = safePrefGetter(this._prefs, "filter.javascript", true);
+ this._maxRichResults = safePrefGetter(this._prefs, "maxRichResults", 25);
+ this._restrictHistoryToken = safePrefGetter(this._prefs,
+ "restrict.history", "^");
+ this._restrictBookmarkToken = safePrefGetter(this._prefs,
+ "restrict.bookmark", "*");
+ this._restrictTypedToken = safePrefGetter(this._prefs, "restrict.typed", "~");
+ this._restrictTagToken = safePrefGetter(this._prefs, "restrict.tag", "+");
+ this._restrictOpenPageToken = safePrefGetter(this._prefs,
+ "restrict.openpage", "%");
+ this._matchTitleToken = safePrefGetter(this._prefs, "match.title", "#");
+ this._matchURLToken = safePrefGetter(this._prefs, "match.url", "@");
+
+ this._suggestHistory = safePrefGetter(this._prefs, "suggest.history", true);
+ this._suggestBookmark = safePrefGetter(this._prefs, "suggest.bookmark", true);
+ this._suggestOpenpage = safePrefGetter(this._prefs, "suggest.openpage", true);
+ this._suggestTyped = safePrefGetter(this._prefs, "suggest.history.onlyTyped", false);
+
+ // If history is not set, onlyTyped value should be ignored.
+ if (!this._suggestHistory) {
+ this._suggestTyped = false;
+ }
+ let types = ["History", "Bookmark", "Openpage", "Typed"];
+ this._defaultBehavior = types.reduce((memo, type) => {
+ let prefValue = this["_suggest" + type];
+ return memo | (prefValue &&
+ Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()]);
+ }, 0);
+
+ // Further restrictions to apply for "empty searches" (i.e. searches for "").
+ // The empty behavior is typed history, if history is enabled. Otherwise,
+ // it is bookmarks, if they are enabled. If both history and bookmarks are disabled,
+ // it defaults to open pages.
+ this._emptySearchDefaultBehavior = Ci.mozIPlacesAutoComplete.BEHAVIOR_RESTRICT;
+ if (this._suggestHistory) {
+ this._emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
+ Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED;
+ } else if (this._suggestBookmark) {
+ this._emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;
+ } else {
+ this._emptySearchDefaultBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE;
+ }
+
+ // Validate matchBehavior; default to MATCH_BOUNDARY_ANYWHERE.
+ if (this._matchBehavior != MATCH_ANYWHERE &&
+ this._matchBehavior != MATCH_BOUNDARY &&
+ this._matchBehavior != MATCH_BEGINNING) {
+ this._matchBehavior = MATCH_BOUNDARY_ANYWHERE;
+ }
+ // register observer
+ if (aRegisterObserver) {
+ this._prefs.addObserver("", this, false);
+ }
+ },
+
+ /**
+ * Given an array of tokens, this function determines which query should be
+ * ran. It also removes any special search tokens.
+ *
+ * @param aTokens
+ * An array of search tokens.
+ * @return an object with two properties:
+ * query: the correctly optimized, bound query to search the database
+ * with.
+ * tokens: the filtered list of tokens to search with.
+ */
+ _getSearch: function PAC_getSearch(aTokens)
+ {
+ let foundToken = false;
+ let restrict = (behavior) => {
+ if (!foundToken) {
+ this._behavior = 0;
+ this._setBehavior("restrict");
+ foundToken = true;
+ }
+ this._setBehavior(behavior);
+ };
+
+ // Set the proper behavior so our call to _getBoundSearchQuery gives us the
+ // correct query.
+ for (let i = aTokens.length - 1; i >= 0; i--) {
+ switch (aTokens[i]) {
+ case this._restrictHistoryToken:
+ restrict("history");
+ break;
+ case this._restrictBookmarkToken:
+ restrict("bookmark");
+ break;
+ case this._restrictTagToken:
+ restrict("tag");
+ break;
+ case this._restrictOpenPageToken:
+ if (!this._enableActions) {
+ continue;
+ }
+ restrict("openpage");
+ break;
+ case this._matchTitleToken:
+ restrict("title");
+ break;
+ case this._matchURLToken:
+ restrict("url");
+ break;
+ case this._restrictTypedToken:
+ restrict("typed");
+ break;
+ default:
+ // We do not want to remove the token if we did not match.
+ continue;
+ }
+
+ aTokens.splice(i, 1);
+ }
+
+ // Set the right JavaScript behavior based on our preference. Note that the
+ // preference is whether or not we should filter JavaScript, and the
+ // behavior is if we should search it or not.
+ if (!this._filterJavaScript) {
+ this._setBehavior("javascript");
+ }
+
+ return {
+ query: this._getBoundSearchQuery(this._matchBehavior, aTokens),
+ tokens: aTokens
+ };
+ },
+
+ /**
+ * @return a string consisting of the search query to be used based on the
+ * previously set urlbar suggestion preferences.
+ */
+ _getSuggestionPrefQuery: function PAC_getSuggestionPrefQuery()
+ {
+ if (!this._hasBehavior("restrict") && this._hasBehavior("history") &&
+ this._hasBehavior("bookmark")) {
+ return this._hasBehavior("typed") ? this._customQuery("AND h.typed = 1")
+ : this._defaultQuery;
+ }
+ let conditions = [];
+ if (this._hasBehavior("history")) {
+ // Enforce ignoring the visit_count index, since the frecency one is much
+ // faster in this case. ANALYZE helps the query planner to figure out the
+ // faster path, but it may not have up-to-date information yet.
+ conditions.push("+h.visit_count > 0");
+ }
+ if (this._hasBehavior("typed")) {
+ conditions.push("h.typed = 1");
+ }
+ if (this._hasBehavior("bookmark")) {
+ conditions.push("bookmarked");
+ }
+ if (this._hasBehavior("tag")) {
+ conditions.push("tags NOTNULL");
+ }
+
+ return conditions.length ? this._customQuery("AND " + conditions.join(" AND "))
+ : this._defaultQuery;
+ },
+
+ /**
+ * Obtains the search query to be used based on the previously set search
+ * behaviors (accessed by this._hasBehavior). The query is bound and ready to
+ * execute.
+ *
+ * @param aMatchBehavior
+ * How this query should match its tokens to the search string.
+ * @param aTokens
+ * An array of search tokens.
+ * @return the correctly optimized query to search the database with and the
+ * new list of tokens to search with. The query has all the needed
+ * parameters bound, so consumers can execute it without doing any
+ * additional work.
+ */
+ _getBoundSearchQuery: function PAC_getBoundSearchQuery(aMatchBehavior,
+ aTokens)
+ {
+ let query = this._getSuggestionPrefQuery();
+
+ // Bind the needed parameters to the query so consumers can use it.
+ let params = query.params;
+ params.parent = PlacesUtils.tagsFolderId;
+ params.query_type = kQueryTypeFiltered;
+ params.matchBehavior = aMatchBehavior;
+ params.searchBehavior = this._behavior;
+
+ // We only want to search the tokens that we are left with - not the
+ // original search string.
+ params.searchString = aTokens.join(" ");
+
+ // Limit the query to the the maximum number of desired results.
+ // This way we can avoid doing more work than needed.
+ params.maxResults = this._maxRichResults;
+
+ return query;
+ },
+
+ _getBoundOpenPagesQuery: function PAC_getBoundOpenPagesQuery(aTokens)
+ {
+ let query = this._openPagesQuery;
+
+ // Bind the needed parameters to the query so consumers can use it.
+ let params = query.params;
+ params.query_type = kQueryTypeFiltered;
+ params.matchBehavior = this._matchBehavior;
+ params.searchBehavior = this._behavior;
+
+ // We only want to search the tokens that we are left with - not the
+ // original search string.
+ params.searchString = aTokens.join(" ");
+ params.maxResults = this._maxRichResults;
+
+ return query;
+ },
+
+ /**
+ * Obtains the keyword query with the properly bound parameters.
+ *
+ * @param aTokens
+ * The array of search tokens to check against.
+ * @return the bound keyword query.
+ */
+ _getBoundKeywordQuery: function PAC_getBoundKeywordQuery(aTokens)
+ {
+ // The keyword is the first word in the search string, with the parameters
+ // following it.
+ let searchString = this._originalSearchString;
+ let queryString = "";
+ let queryIndex = searchString.indexOf(" ");
+ if (queryIndex != -1) {
+ queryString = searchString.substring(queryIndex + 1);
+ }
+ // We need to escape the parameters as if they were the query in a URL
+ queryString = encodeURIComponent(queryString).replace(/%20/g, "+");
+
+ // The first word could be a keyword, so that's what we'll search.
+ let keyword = aTokens[0];
+
+ let query = this._keywordQuery;
+ let params = query.params;
+ params.keyword = keyword;
+ params.query_string = queryString;
+ params.query_type = kQueryTypeKeyword;
+
+ return query;
+ },
+
+ /**
+ * Obtains the adaptive query with the properly bound parameters.
+ *
+ * @return the bound adaptive query.
+ */
+ _getBoundAdaptiveQuery: function PAC_getBoundAdaptiveQuery(aMatchBehavior)
+ {
+ // If we were not given a match behavior, use the stored match behavior.
+ if (arguments.length == 0) {
+ aMatchBehavior = this._matchBehavior;
+ }
+
+ let query = this._adaptiveQuery;
+ let params = query.params;
+ params.parent = PlacesUtils.tagsFolderId;
+ params.search_string = this._currentSearchString;
+ params.query_type = kQueryTypeFiltered;
+ params.matchBehavior = aMatchBehavior;
+ params.searchBehavior = this._behavior;
+
+ return query;
+ },
+
+ /**
+ * Processes a mozIStorageRow to generate the proper data for the AutoComplete
+ * result. This will add an entry to the current result if it matches the
+ * criteria.
+ *
+ * @param aRow
+ * The row to process.
+ * @return true if the row is accepted, and false if not.
+ */
+ _processRow: function PAC_processRow(aRow)
+ {
+ // Before we do any work, make sure this entry isn't already in our results.
+ let entryId = aRow.getResultByIndex(kQueryIndexPlaceId);
+ let escapedEntryURL = aRow.getResultByIndex(kQueryIndexURL);
+ let openPageCount = aRow.getResultByIndex(kQueryIndexOpenPageCount) || 0;
+
+ // If actions are enabled and the page is open, add only the switch-to-tab
+ // result. Otherwise, add the normal result.
+ let [url, action] = this._enableActions && openPageCount > 0 && this._hasBehavior("openpage") ?
+ ["moz-action:switchtab," + escapedEntryURL, "action "] :
+ [escapedEntryURL, ""];
+
+ if (this._inResults(entryId, url)) {
+ return false;
+ }
+
+ let entryTitle = aRow.getResultByIndex(kQueryIndexTitle) || "";
+ let entryFavicon = aRow.getResultByIndex(kQueryIndexFaviconURL) || "";
+ let entryBookmarked = aRow.getResultByIndex(kQueryIndexBookmarked);
+ let entryBookmarkTitle = entryBookmarked ?
+ aRow.getResultByIndex(kQueryIndexBookmarkTitle) : null;
+ let entryTags = aRow.getResultByIndex(kQueryIndexTags) || "";
+
+ // Always prefer the bookmark title unless it is empty
+ let title = entryBookmarkTitle || entryTitle;
+
+ let style;
+ if (aRow.getResultByIndex(kQueryIndexQueryType) == kQueryTypeKeyword) {
+ style = "keyword";
+ title = NetUtil.newURI(escapedEntryURL).host;
+ }
+
+ // We will always prefer to show tags if we have them.
+ let showTags = !!entryTags;
+
+ // However, we'll act as if a page is not bookmarked if the user wants
+ // only history and not bookmarks and there are no tags.
+ if (this._hasBehavior("history") && !this._hasBehavior("bookmark") &&
+ !showTags) {
+ showTags = false;
+ style = "favicon";
+ }
+
+ // If we have tags and should show them, we need to add them to the title.
+ if (showTags) {
+ title += kTitleTagsSeparator + entryTags;
+ }
+ // We have to determine the right style to display. Tags show the tag icon,
+ // bookmarks get the bookmark icon, and keywords get the keyword icon. If
+ // the result does not fall into any of those, it just gets the favicon.
+ if (!style) {
+ // It is possible that we already have a style set (from a keyword
+ // search or because of the user's preferences), so only set it if we
+ // haven't already done so.
+ if (showTags) {
+ style = "tag";
+ }
+ else if (entryBookmarked) {
+ style = "bookmark";
+ }
+ else {
+ style = "favicon";
+ }
+ }
+
+ this._addToResults(entryId, url, title, entryFavicon, action + style);
+ return true;
+ },
+
+ /**
+ * Checks to see if the given place has already been added to the results.
+ *
+ * @param aPlaceId
+ * The place id to check for, may be null.
+ * @param aUrl
+ * The url to check for.
+ * @return true if the place has been added, false otherwise.
+ *
+ * @note Must check both the id and the url for a negative match, since
+ * autocomplete may run in the middle of a new page addition. In such
+ * a case the switch-to-tab query would hash the page by url, then a
+ * next query, running after the page addition, would hash it by id.
+ * It's not possible to just rely on url though, since keywords
+ * dynamically modify the url to include their search string.
+ */
+ _inResults: function PAC_inResults(aPlaceId, aUrl)
+ {
+ if (aPlaceId && aPlaceId in this._usedPlaces) {
+ return true;
+ }
+ return aUrl in this._usedPlaces;
+ },
+
+ /**
+ * Adds a result to the AutoComplete results. Also tracks that we've added
+ * this place_id into the result set.
+ *
+ * @param aPlaceId
+ * The place_id of the item to be added to the result set. This is
+ * used by _inResults.
+ * @param aURISpec
+ * The URI spec for the entry.
+ * @param aTitle
+ * The title to give the entry.
+ * @param aFaviconSpec
+ * The favicon to give to the entry.
+ * @param aStyle
+ * Indicates how the entry should be styled when displayed.
+ */
+ _addToResults: function PAC_addToResults(aPlaceId, aURISpec, aTitle,
+ aFaviconSpec, aStyle)
+ {
+ // Add this to our internal tracker to ensure duplicates do not end up in
+ // the result. _usedPlaces is an Object that is being used as a set.
+ // Not all entries have a place id, thus we fallback to the url for them.
+ // We cannot use only the url since keywords entries are modified to
+ // include the search string, and would be returned multiple times. Ids
+ // are faster too.
+ this._usedPlaces[aPlaceId || aURISpec] = true;
+
+ // Obtain the favicon for this URI.
+ let favicon;
+ if (aFaviconSpec) {
+ let uri = NetUtil.newURI(aFaviconSpec);
+ favicon = PlacesUtils.favicons.getFaviconLinkForIcon(uri).spec;
+ }
+ favicon = favicon || PlacesUtils.favicons.defaultFavicon.spec;
+
+ this._result.appendMatch(aURISpec, aTitle, favicon, aStyle);
+ },
+
+ /**
+ * Determines if the specified AutoComplete behavior is set.
+ *
+ * @param aType
+ * The behavior type to test for.
+ * @return true if the behavior is set, false otherwise.
+ */
+ _hasBehavior: function PAC_hasBehavior(aType)
+ {
+ let behavior = Ci.mozIPlacesAutoComplete["BEHAVIOR_" + aType.toUpperCase()];
+
+ if (this._disablePrivateActions &&
+ behavior == Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE) {
+ return false;
+ }
+
+ return this._behavior & behavior;
+ },
+
+ /**
+ * Enables the desired AutoComplete behavior.
+ *
+ * @param aType
+ * The behavior type to set.
+ */
+ _setBehavior: function PAC_setBehavior(aType)
+ {
+ this._behavior |=
+ Ci.mozIPlacesAutoComplete["BEHAVIOR_" + aType.toUpperCase()];
+ },
+
+ /**
+ * Determines if we are done searching or not.
+ *
+ * @return true if we have completed searching, false otherwise.
+ */
+ isSearchComplete: function PAC_isSearchComplete()
+ {
+ // If _pendingQuery is null, we should no longer do any work since we have
+ // already called _finishSearch. This means we completed our search.
+ return this._pendingQuery == null;
+ },
+
+ /**
+ * Determines if the given handle of a pending statement is a pending search
+ * or not.
+ *
+ * @param aHandle
+ * A mozIStoragePendingStatement to check and see if we are waiting for
+ * results from it still.
+ * @return true if it is a pending query, false otherwise.
+ */
+ isPendingSearch: function PAC_isPendingSearch(aHandle)
+ {
+ return this._pendingQuery == aHandle;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ classID: Components.ID("d0272978-beab-4adc-a3d4-04b76acfa4e7"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsPlacesAutoComplete),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteSearch,
+ Ci.nsIAutoCompleteSimpleResultListener,
+ Ci.mozIPlacesAutoComplete,
+ Ci.mozIStorageStatementCallback,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ ])
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// urlInlineComplete class
+//// component @mozilla.org/autocomplete/search;1?name=urlinline
+
+function urlInlineComplete()
+{
+ this._loadPrefs(true);
+ Services.obs.addObserver(this, kTopicShutdown, true);
+}
+
+urlInlineComplete.prototype = {
+
+/////////////////////////////////////////////////////////////////////////////////
+//// Database and query getters
+
+ __db: null,
+
+ get _db()
+ {
+ if (!this.__db && this._autofillEnabled) {
+ this.__db = PlacesUtils.history.DBConnection.clone(true);
+ }
+ return this.__db;
+ },
+
+ __hostQuery: null,
+
+ get _hostQuery()
+ {
+ if (!this.__hostQuery) {
+ // Add a trailing slash at the end of the hostname, since we always
+ // want to complete up to and including a URL separator.
+ this.__hostQuery = this._db.createAsyncStatement(
+ `/* do not warn (bug no): could index on (typed,frecency) but not worth it */
+ SELECT host || '/', prefix || host || '/'
+ FROM moz_hosts
+ WHERE host BETWEEN :search_string AND :search_string || X'FFFF'
+ AND frecency <> 0
+ ${this._autofillTyped ? "AND typed = 1" : ""}
+ ORDER BY frecency DESC
+ LIMIT 1`
+ );
+ }
+ return this.__hostQuery;
+ },
+
+ __urlQuery: null,
+
+ get _urlQuery()
+ {
+ if (!this.__urlQuery) {
+ this.__urlQuery = this._db.createAsyncStatement(
+ `/* do not warn (bug no): can't use an index */
+ SELECT h.url
+ FROM moz_places h
+ WHERE h.frecency <> 0
+ ${this._autofillTyped ? "AND h.typed = 1 " : ""}
+ AND AUTOCOMPLETE_MATCH(:searchString, h.url,
+ h.title, '',
+ h.visit_count, h.typed, 0, 0,
+ :matchBehavior, :searchBehavior)
+ ORDER BY h.frecency DESC, h.id DESC
+ LIMIT 1`
+ );
+ }
+ return this.__urlQuery;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIAutoCompleteSearch
+
+ startSearch: function UIC_startSearch(aSearchString, aSearchParam,
+ aPreviousResult, aListener)
+ {
+ // Stop the search in case the controller has not taken care of it.
+ if (this._pendingQuery) {
+ this.stopSearch();
+ }
+
+ let pendingSearch = this._pendingSearch = {};
+
+ // We want to store the original string with no leading or trailing
+ // whitespace for case sensitive searches.
+ this._originalSearchString = aSearchString;
+ this._currentSearchString =
+ fixupSearchText(this._originalSearchString.toLowerCase());
+ // The protocol and the host are lowercased by nsIURI, so it's fine to
+ // lowercase the typed prefix to add it back to the results later.
+ this._strippedPrefix = this._originalSearchString.slice(
+ 0, this._originalSearchString.length - this._currentSearchString.length
+ ).toLowerCase();
+
+ this._result = Cc["@mozilla.org/autocomplete/simple-result;1"].
+ createInstance(Ci.nsIAutoCompleteSimpleResult);
+ this._result.setSearchString(aSearchString);
+ this._result.setTypeAheadResult(true);
+
+ this._listener = aListener;
+
+ Task.spawn(function* () {
+ // Don't autoFill if the search term is recognized as a keyword, otherwise
+ // it will override default keywords behavior. Note that keywords are
+ // hashed on first use, so while the first query may delay a little bit,
+ // next ones will just hit the memory hash.
+ let dontAutoFill = this._currentSearchString.length == 0 || !this._db ||
+ (yield PlacesUtils.keywords.fetch(this._currentSearchString));
+ if (this._pendingSearch != pendingSearch)
+ return;
+ if (dontAutoFill) {
+ this._finishSearch();
+ return;
+ }
+
+ // Don't try to autofill if the search term includes any whitespace.
+ // This may confuse completeDefaultIndex cause the AUTOCOMPLETE_MATCH
+ // tokenizer ends up trimming the search string and returning a value
+ // that doesn't match it, or is even shorter.
+ if (/\s/.test(this._currentSearchString)) {
+ this._finishSearch();
+ return;
+ }
+
+ // Hosts have no "/" in them.
+ let lastSlashIndex = this._currentSearchString.lastIndexOf("/");
+
+ // Search only URLs if there's a slash in the search string...
+ if (lastSlashIndex != -1) {
+ // ...but not if it's exactly at the end of the search string.
+ if (lastSlashIndex < this._currentSearchString.length - 1)
+ this._queryURL();
+ else
+ this._finishSearch();
+ return;
+ }
+
+ // Do a synchronous search on the table of hosts.
+ let query = this._hostQuery;
+ query.params.search_string = this._currentSearchString.toLowerCase();
+ let wrapper = new AutoCompleteStatementCallbackWrapper(this, {
+ handleResult: aResultSet => {
+ if (this._pendingSearch != pendingSearch)
+ return;
+ let row = aResultSet.getNextRow();
+ let trimmedHost = row.getResultByIndex(0);
+ let untrimmedHost = row.getResultByIndex(1);
+ // If the untrimmed value doesn't preserve the user's input just
+ // ignore it and complete to the found host.
+ if (untrimmedHost &&
+ !untrimmedHost.toLowerCase().includes(this._originalSearchString.toLowerCase())) {
+ untrimmedHost = null;
+ }
+
+ this._result.appendMatch(this._strippedPrefix + trimmedHost, "", "", "", untrimmedHost);
+
+ // handleCompletion() will cause the result listener to be called, and
+ // will display the result in the UI.
+ },
+
+ handleError: aError => {
+ Components.utils.reportError(
+ "URL Inline Complete: An async statement encountered an " +
+ "error: " + aError.result + ", '" + aError.message + "'");
+ },
+
+ handleCompletion: aReason => {
+ if (this._pendingSearch != pendingSearch)
+ return;
+ this._finishSearch();
+ }
+ }, this._db);
+ this._pendingQuery = wrapper.executeAsync([query]);
+ }.bind(this));
+ },
+
+ /**
+ * Execute an asynchronous search through places, and complete
+ * up to the next URL separator.
+ */
+ _queryURL: function UIC__queryURL()
+ {
+ // The URIs in the database are fixed up, so we can match on a lowercased
+ // host, but the path must be matched in a case sensitive way.
+ let pathIndex =
+ this._originalSearchString.indexOf("/", this._strippedPrefix.length);
+ this._currentSearchString = fixupSearchText(
+ this._originalSearchString.slice(0, pathIndex).toLowerCase() +
+ this._originalSearchString.slice(pathIndex)
+ );
+
+ // Within the standard autocomplete query, we only search the beginning
+ // of URLs for 1 result.
+ let query = this._urlQuery;
+ let params = query.params;
+ params.matchBehavior = MATCH_BEGINNING_CASE_SENSITIVE;
+ params.searchBehavior |= Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY |
+ Ci.mozIPlacesAutoComplete.BEHAVIOR_TYPED |
+ Ci.mozIPlacesAutoComplete.BEHAVIOR_URL;
+ params.searchString = this._currentSearchString;
+
+ // Execute the query.
+ let wrapper = new AutoCompleteStatementCallbackWrapper(this, {
+ handleResult: aResultSet => {
+ let row = aResultSet.getNextRow();
+ let value = row.getResultByIndex(0);
+ let url = fixupSearchText(value);
+
+ let prefix = value.slice(0, value.length - stripPrefix(value).length);
+
+ // We must complete the URL up to the next separator (which is /, ? or #).
+ let separatorIndex = url.slice(this._currentSearchString.length)
+ .search(/[\/\?\#]/);
+ if (separatorIndex != -1) {
+ separatorIndex += this._currentSearchString.length;
+ if (url[separatorIndex] == "/") {
+ separatorIndex++; // Include the "/" separator
+ }
+ url = url.slice(0, separatorIndex);
+ }
+
+ // Add the result.
+ // If the untrimmed value doesn't preserve the user's input just
+ // ignore it and complete to the found url.
+ let untrimmedURL = prefix + url;
+ if (untrimmedURL &&
+ !untrimmedURL.toLowerCase().includes(this._originalSearchString.toLowerCase())) {
+ untrimmedURL = null;
+ }
+
+ this._result.appendMatch(this._strippedPrefix + url, "", "", "", untrimmedURL);
+
+ // handleCompletion() will cause the result listener to be called, and
+ // will display the result in the UI.
+ },
+
+ handleError: aError => {
+ Components.utils.reportError(
+ "URL Inline Complete: An async statement encountered an " +
+ "error: " + aError.result + ", '" + aError.message + "'");
+ },
+
+ handleCompletion: aReason => {
+ this._finishSearch();
+ }
+ }, this._db);
+ this._pendingQuery = wrapper.executeAsync([query]);
+ },
+
+ stopSearch: function UIC_stopSearch()
+ {
+ delete this._originalSearchString;
+ delete this._currentSearchString;
+ delete this._result;
+ delete this._listener;
+ delete this._pendingSearch;
+
+ if (this._pendingQuery) {
+ this._pendingQuery.cancel();
+ delete this._pendingQuery;
+ }
+ },
+
+ /**
+ * Loads the preferences that we care about.
+ *
+ * @param [optional] aRegisterObserver
+ * Indicates if the preference observer should be added or not. The
+ * default value is false.
+ */
+ _loadPrefs: function UIC_loadPrefs(aRegisterObserver)
+ {
+ let prefBranch = Services.prefs.getBranch(kBrowserUrlbarBranch);
+ let autocomplete = safePrefGetter(prefBranch,
+ kBrowserUrlbarAutocompleteEnabledPref,
+ true);
+ let autofill = safePrefGetter(prefBranch,
+ kBrowserUrlbarAutofillPref,
+ true);
+ this._autofillEnabled = autocomplete && autofill;
+ this._autofillTyped = safePrefGetter(prefBranch,
+ kBrowserUrlbarAutofillTypedPref,
+ true);
+ if (aRegisterObserver) {
+ Services.prefs.addObserver(kBrowserUrlbarBranch, this, true);
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIAutoCompleteSearchDescriptor
+
+ get searchType() {
+ return Ci.nsIAutoCompleteSearchDescriptor.SEARCH_TYPE_IMMEDIATE;
+ },
+
+ get clearingAutoFillSearchesAgain() {
+ return false;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIObserver
+
+ observe: function UIC_observe(aSubject, aTopic, aData)
+ {
+ if (aTopic == kTopicShutdown) {
+ this._closeDatabase();
+ }
+ else if (aTopic == kPrefChanged &&
+ (aData.substr(kBrowserUrlbarBranch.length) == kBrowserUrlbarAutofillPref ||
+ aData.substr(kBrowserUrlbarBranch.length) == kBrowserUrlbarAutocompleteEnabledPref ||
+ aData.substr(kBrowserUrlbarBranch.length) == kBrowserUrlbarAutofillTypedPref)) {
+ let previousAutofillTyped = this._autofillTyped;
+ this._loadPrefs();
+ if (!this._autofillEnabled) {
+ this.stopSearch();
+ this._closeDatabase();
+ }
+ else if (this._autofillTyped != previousAutofillTyped) {
+ // Invalidate the statements to update them for the new typed status.
+ this._invalidateStatements();
+ }
+ }
+ },
+
+ /**
+ * Finalizes and invalidates cached statements.
+ */
+ _invalidateStatements: function UIC_invalidateStatements()
+ {
+ // Finalize the statements that we have used.
+ let stmts = [
+ "__hostQuery",
+ "__urlQuery",
+ ];
+ for (let i = 0; i < stmts.length; i++) {
+ // We do not want to create any query we haven't already created, so
+ // see if it is a getter first.
+ if (this[stmts[i]]) {
+ this[stmts[i]].finalize();
+ this[stmts[i]] = null;
+ }
+ }
+ },
+
+ /**
+ * Closes the database.
+ */
+ _closeDatabase: function UIC_closeDatabase()
+ {
+ this._invalidateStatements();
+ if (this.__db) {
+ this._db.asyncClose();
+ this.__db = null;
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// urlInlineComplete
+
+ _finishSearch: function UIC_finishSearch()
+ {
+ // Notify the result object
+ let result = this._result;
+
+ if (result.matchCount) {
+ result.setDefaultIndex(0);
+ result.setSearchResult(Ci.nsIAutoCompleteResult["RESULT_SUCCESS"]);
+ } else {
+ result.setDefaultIndex(-1);
+ result.setSearchResult(Ci.nsIAutoCompleteResult["RESULT_NOMATCH"]);
+ }
+
+ this._listener.onSearchResult(this, result);
+ this.stopSearch();
+ },
+
+ isSearchComplete: function UIC_isSearchComplete()
+ {
+ return this._pendingQuery == null;
+ },
+
+ isPendingSearch: function UIC_isPendingSearch(aHandle)
+ {
+ return this._pendingQuery == aHandle;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ classID: Components.ID("c88fae2d-25cf-4338-a1f4-64a320ea7440"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(urlInlineComplete),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteSearch,
+ Ci.nsIAutoCompleteSearchDescriptor,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ ])
+};
+
+var components = [nsPlacesAutoComplete, urlInlineComplete];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/components/places/src/nsPlacesExpiration.js b/components/places/src/nsPlacesExpiration.js
new file mode 100644
index 000000000..0f376626d
--- /dev/null
+++ b/components/places/src/nsPlacesExpiration.js
@@ -0,0 +1,1069 @@
+/* -*- 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/. */
+
+/**
+ * This component handles history and orphans expiration through asynchronous
+ * Storage statements.
+ * Expiration runs:
+ * - At idle, but just once, we stop any other kind of expiration during idle
+ * to preserve batteries in portable devices.
+ * - At shutdown, only if the database is dirty, we should still avoid to
+ * expire too heavily on shutdown.
+ * - On ClearHistory we run a full expiration for privacy reasons.
+ * - On a repeating timer we expire in small chunks.
+ *
+ * Expiration algorithm will adapt itself based on:
+ * - Memory size of the device.
+ * - Status of the database (clean or dirty).
+ */
+
+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");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+// Constants
+
+// Last expiration step should run before the final sync.
+const TOPIC_SHUTDOWN = "places-will-close-connection";
+const TOPIC_PREF_CHANGED = "nsPref:changed";
+const TOPIC_DEBUG_START_EXPIRATION = "places-debug-start-expiration";
+const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
+const TOPIC_IDLE_BEGIN = "idle";
+const TOPIC_IDLE_END = "active";
+const TOPIC_IDLE_DAILY = "idle-daily";
+const TOPIC_TESTING_MODE = "testing-mode";
+const TOPIC_TEST_INTERVAL_CHANGED = "test-interval-changed";
+
+// Branch for all expiration preferences.
+const PREF_BRANCH = "places.history.expiration.";
+
+// Max number of unique URIs to retain in history.
+// Notice this is a lazy limit. This means we will start to expire if we will
+// go over it, but we won't ensure that we will stop exactly when we reach it,
+// instead we will stop after the next expiration step that will bring us
+// below it.
+// If this preference does not exist or has a negative value, we will calculate
+// a limit based on current hardware.
+const PREF_MAX_URIS = "max_pages";
+const PREF_MAX_URIS_NOTSET = -1; // Use our internally calculated limit.
+
+// We save the current unique URIs limit to this pref, to make it available to
+// other components without having to duplicate the full logic.
+const PREF_READONLY_CALCULATED_MAX_URIS = "transient_current_max_pages";
+
+// Seconds between each expiration step.
+const PREF_INTERVAL_SECONDS = "interval_seconds";
+const PREF_INTERVAL_SECONDS_NOTSET = 3 * 60;
+
+// We calculate an optimal database size, based on hardware specs.
+// This percentage of memory size is used to protect against calculating a too
+// large database size on systems with small memory.
+const DATABASE_TO_MEMORY_PERC = 4;
+// This percentage of disk size is used to protect against calculating a too
+// large database size on disks with tiny quota or available space.
+const DATABASE_TO_DISK_PERC = 2;
+// Maximum size of the optimal database. High-end hardware has plenty of
+// memory and disk space, but performances don't grow linearly.
+const DATABASE_MAX_SIZE = 73400320; // 70MiB
+// If the physical memory size is bogus, fallback to this.
+const MEMSIZE_FALLBACK_BYTES = 268435456; // 256 MiB
+// If the disk available space is bogus, fallback to this.
+const DISKSIZE_FALLBACK_BYTES = 268435456; // 256 MiB
+
+// Max number of entries to expire at each expiration step.
+// This value is globally used for different kind of data we expire, can be
+// tweaked based on data type. See below in getBoundStatement.
+const EXPIRE_LIMIT_PER_STEP = 6;
+// When we run a large expiration step, the above limit is multiplied by this.
+const EXPIRE_LIMIT_PER_LARGE_STEP_MULTIPLIER = 10;
+
+// When history is clean or dirty enough we will adapt the expiration algorithm
+// to be more lazy or more aggressive.
+// This is done acting on the interval between expiration steps and the number
+// of expirable items.
+// 1. Clean history:
+// We expire at (default interval * EXPIRE_AGGRESSIVITY_MULTIPLIER) the
+// default number of entries.
+// 2. Dirty history:
+// We expire at the default interval, but a greater number of entries
+// (default number of entries * EXPIRE_AGGRESSIVITY_MULTIPLIER).
+const EXPIRE_AGGRESSIVITY_MULTIPLIER = 3;
+
+// This is the average size in bytes of an URI entry in the database.
+// Magic numbers are determined through analysis of the distribution of a ratio
+// between number of unique URIs and database size among our users.
+// Used as a fall back value when it's not possible to calculate the real value.
+const URIENTRY_AVG_SIZE = 600;
+
+// Seconds of idle time before starting a larger expiration step.
+// Notice during idle we stop the expiration timer since we don't want to hurt
+// stand-by or mobile devices batteries.
+const IDLE_TIMEOUT_SECONDS = 5 * 60;
+
+// If a clear history ran just before we shutdown, we will skip most of the
+// expiration at shutdown. This is maximum number of seconds from last
+// clearHistory to decide to skip expiration at shutdown.
+const SHUTDOWN_WITH_RECENT_CLEARHISTORY_TIMEOUT_SECONDS = 10;
+
+// If the pages delta from the last ANALYZE is over this threashold, the tables
+// should be analyzed again.
+const ANALYZE_PAGES_THRESHOLD = 100;
+
+// If the number of pages over history limit is greater than this threshold,
+// expiration will be more aggressive, to bring back history to a saner size.
+const OVERLIMIT_PAGES_THRESHOLD = 1000;
+
+const MSECS_PER_DAY = 86400000;
+const ANNOS_EXPIRE_POLICIES = [
+ { bind: "expire_days",
+ type: Ci.nsIAnnotationService.EXPIRE_DAYS,
+ time: 7 * 1000 * MSECS_PER_DAY },
+ { bind: "expire_weeks",
+ type: Ci.nsIAnnotationService.EXPIRE_WEEKS,
+ time: 30 * 1000 * MSECS_PER_DAY },
+ { bind: "expire_months",
+ type: Ci.nsIAnnotationService.EXPIRE_MONTHS,
+ time: 180 * 1000 * MSECS_PER_DAY },
+];
+
+// When we expire we can use these limits:
+// - SMALL for usual partial expirations, will expire a small chunk.
+// - LARGE for idle or shutdown expirations, will expire a large chunk.
+// - UNLIMITED for clearHistory, will expire everything.
+// - DEBUG will use a known limit, passed along with the debug notification.
+const LIMIT = {
+ SMALL: 0,
+ LARGE: 1,
+ UNLIMITED: 2,
+ DEBUG: 3,
+};
+
+// Represents the status of history database.
+const STATUS = {
+ CLEAN: 0,
+ DIRTY: 1,
+ UNKNOWN: 2,
+};
+
+// Represents actions on which a query will run.
+const ACTION = {
+ TIMED: 1 << 0, // happens every this._interval
+ TIMED_OVERLIMIT: 1 << 1, // like TIMED but only when history is over limits
+ TIMED_ANALYZE: 1 << 2, // happens when ANALYZE statistics should be updated
+ CLEAR_HISTORY: 1 << 3, // happens when history is cleared
+ SHUTDOWN_DIRTY: 1 << 4, // happens at shutdown for DIRTY state
+ IDLE_DIRTY: 1 << 5, // happens on idle for DIRTY state
+ IDLE_DAILY: 1 << 6, // happens once a day on idle
+ DEBUG: 1 << 7, // happens on TOPIC_DEBUG_START_EXPIRATION
+};
+
+// The queries we use to expire.
+const EXPIRATION_QUERIES = {
+
+ // Some visits can be expired more often than others, cause they are less
+ // useful to the user and can pollute awesomebar results:
+ // 1. urls over 255 chars
+ // 2. redirect sources and downloads
+ // Note: due to the REPLACE option, this should be executed before
+ // QUERY_FIND_VISITS_TO_EXPIRE, that has a more complete result.
+ QUERY_FIND_EXOTIC_VISITS_TO_EXPIRE: {
+ sql: `INSERT INTO expiration_notify (v_id, url, guid, visit_date, reason)
+ SELECT v.id, h.url, h.guid, v.visit_date, "exotic"
+ FROM moz_historyvisits v
+ JOIN moz_places h ON h.id = v.place_id
+ WHERE visit_date < strftime('%s','now','localtime','start of day','-60 days','utc') * 1000000
+ AND ( LENGTH(h.url) > 255 OR v.visit_type = 7 )
+ ORDER BY v.visit_date ASC
+ LIMIT :limit_visits`,
+ actions: ACTION.TIMED_OVERLIMIT | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
+ // Finds visits to be expired when history is over the unique pages limit,
+ // otherwise will return nothing.
+ // This explicitly excludes any visits added in the last 7 days, to protect
+ // users with thousands of bookmarks from constantly losing history.
+ QUERY_FIND_VISITS_TO_EXPIRE: {
+ sql: `INSERT INTO expiration_notify
+ (v_id, url, guid, visit_date, expected_results)
+ SELECT v.id, h.url, h.guid, v.visit_date, :limit_visits
+ FROM moz_historyvisits v
+ JOIN moz_places h ON h.id = v.place_id
+ WHERE (SELECT COUNT(*) FROM moz_places) > :max_uris
+ AND visit_date < strftime('%s','now','localtime','start of day','-7 days','utc') * 1000000
+ ORDER BY v.visit_date ASC
+ LIMIT :limit_visits`,
+ actions: ACTION.TIMED_OVERLIMIT | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
+ // Removes the previously found visits.
+ QUERY_EXPIRE_VISITS: {
+ sql: `DELETE FROM moz_historyvisits WHERE id IN (
+ SELECT v_id FROM expiration_notify WHERE v_id NOTNULL
+ )`,
+ actions: ACTION.TIMED_OVERLIMIT | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
+ // Finds orphan URIs in the database.
+ // Notice we won't notify single removed URIs on History.clear(), so we don't
+ // run this query in such a case, but just delete URIs.
+ // This could run in the middle of adding a visit or bookmark to a new page.
+ // In such a case since it is async, could end up expiring the orphan page
+ // before it actually gets the new visit or bookmark.
+ // Thus, since new pages get frecency -1, we filter on that.
+ QUERY_FIND_URIS_TO_EXPIRE: {
+ sql: `INSERT INTO expiration_notify (p_id, url, guid, visit_date)
+ SELECT h.id, h.url, h.guid, h.last_visit_date
+ FROM moz_places h
+ LEFT JOIN moz_historyvisits v ON h.id = v.place_id
+ WHERE h.last_visit_date IS NULL
+ AND h.foreign_count = 0
+ AND v.id IS NULL
+ AND frecency <> -1
+ LIMIT :limit_uris`,
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
+ ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
+ },
+
+ // Expire found URIs from the database.
+ QUERY_EXPIRE_URIS: {
+ sql: `DELETE FROM moz_places WHERE id IN (
+ SELECT p_id FROM expiration_notify WHERE p_id NOTNULL
+ ) AND foreign_count = 0 AND last_visit_date ISNULL`,
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
+ ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
+ },
+
+ // Expire orphan URIs from the database.
+ QUERY_SILENT_EXPIRE_ORPHAN_URIS: {
+ sql: `DELETE FROM moz_places WHERE id IN (
+ SELECT h.id
+ FROM moz_places h
+ LEFT JOIN moz_historyvisits v ON h.id = v.place_id
+ WHERE h.last_visit_date IS NULL
+ AND h.foreign_count = 0
+ AND v.id IS NULL
+ LIMIT :limit_uris
+ )`,
+ actions: ACTION.CLEAR_HISTORY
+ },
+
+ // Hosts accumulated during the places delete are updated through a trigger
+ // (see nsPlacesTriggers.h).
+ QUERY_UPDATE_HOSTS: {
+ sql: `DELETE FROM moz_updatehosts_temp`,
+ actions: ACTION.CLEAR_HISTORY | ACTION.TIMED | ACTION.TIMED_OVERLIMIT |
+ ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
+ // Expire orphan icons from the database.
+ QUERY_EXPIRE_FAVICONS: {
+ sql: `DELETE FROM moz_favicons WHERE id IN (
+ SELECT f.id FROM moz_favicons f
+ LEFT JOIN moz_places h ON f.id = h.favicon_id
+ WHERE h.favicon_id IS NULL
+ LIMIT :limit_favicons
+ )`,
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
+ ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
+ // Expire orphan page annotations from the database.
+ QUERY_EXPIRE_ANNOS: {
+ sql: `DELETE FROM moz_annos WHERE id in (
+ SELECT a.id FROM moz_annos a
+ LEFT JOIN moz_places h ON a.place_id = h.id
+ WHERE h.id IS NULL
+ LIMIT :limit_annos
+ )`,
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
+ ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
+ // Expire page annotations based on expiration policy.
+ QUERY_EXPIRE_ANNOS_WITH_POLICY: {
+ sql: `DELETE FROM moz_annos
+ WHERE (expiration = :expire_days
+ AND :expire_days_time > MAX(lastModified, dateAdded))
+ OR (expiration = :expire_weeks
+ AND :expire_weeks_time > MAX(lastModified, dateAdded))
+ OR (expiration = :expire_months
+ AND :expire_months_time > MAX(lastModified, dateAdded))`,
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
+ ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
+ // Expire items annotations based on expiration policy.
+ QUERY_EXPIRE_ITEMS_ANNOS_WITH_POLICY: {
+ sql: `DELETE FROM moz_items_annos
+ WHERE (expiration = :expire_days
+ AND :expire_days_time > MAX(lastModified, dateAdded))
+ OR (expiration = :expire_weeks
+ AND :expire_weeks_time > MAX(lastModified, dateAdded))
+ OR (expiration = :expire_months
+ AND :expire_months_time > MAX(lastModified, dateAdded))`,
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
+ ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
+ // Expire page annotations based on expiration policy.
+ QUERY_EXPIRE_ANNOS_WITH_HISTORY: {
+ sql: `DELETE FROM moz_annos
+ WHERE expiration = :expire_with_history
+ AND NOT EXISTS (SELECT id FROM moz_historyvisits
+ WHERE place_id = moz_annos.place_id LIMIT 1)`,
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
+ ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
+ // Expire item annos without a corresponding item id.
+ QUERY_EXPIRE_ITEMS_ANNOS: {
+ sql: `DELETE FROM moz_items_annos WHERE id IN (
+ SELECT a.id FROM moz_items_annos a
+ LEFT JOIN moz_bookmarks b ON a.item_id = b.id
+ WHERE b.id IS NULL
+ LIMIT :limit_annos
+ )`,
+ actions: ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG
+ },
+
+ // Expire all annotation names without a corresponding annotation.
+ QUERY_EXPIRE_ANNO_ATTRIBUTES: {
+ sql: `DELETE FROM moz_anno_attributes WHERE id IN (
+ SELECT n.id FROM moz_anno_attributes n
+ LEFT JOIN moz_annos a ON n.id = a.anno_attribute_id
+ LEFT JOIN moz_items_annos t ON n.id = t.anno_attribute_id
+ WHERE a.anno_attribute_id IS NULL
+ AND t.anno_attribute_id IS NULL
+ LIMIT :limit_annos
+ )`,
+ actions: ACTION.CLEAR_HISTORY | ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY |
+ ACTION.IDLE_DAILY | ACTION.DEBUG
+ },
+
+ // Expire orphan inputhistory.
+ QUERY_EXPIRE_INPUTHISTORY: {
+ sql: `DELETE FROM moz_inputhistory WHERE place_id IN (
+ SELECT i.place_id FROM moz_inputhistory i
+ LEFT JOIN moz_places h ON h.id = i.place_id
+ WHERE h.id IS NULL
+ LIMIT :limit_inputhistory
+ )`,
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.CLEAR_HISTORY |
+ ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+ ACTION.DEBUG
+ },
+
+ // Expire all session annotations. Should only be called at shutdown.
+ QUERY_EXPIRE_ANNOS_SESSION: {
+ sql: "DELETE FROM moz_annos WHERE expiration = :expire_session",
+ actions: ACTION.CLEAR_HISTORY | ACTION.DEBUG
+ },
+
+ // Expire all session item annotations. Should only be called at shutdown.
+ QUERY_EXPIRE_ITEMS_ANNOS_SESSION: {
+ sql: "DELETE FROM moz_items_annos WHERE expiration = :expire_session",
+ actions: ACTION.CLEAR_HISTORY | ACTION.DEBUG
+ },
+
+ // Select entries for notifications.
+ // If p_id is set whole_entry = 1, then we have expired the full page.
+ // Either p_id or v_id are always set.
+ QUERY_SELECT_NOTIFICATIONS: {
+ sql: `SELECT url, guid, MAX(visit_date) AS visit_date,
+ MAX(IFNULL(MIN(p_id, 1), MIN(v_id, 0))) AS whole_entry,
+ MAX(expected_results) AS expected_results,
+ (SELECT MAX(visit_date) FROM expiration_notify
+ WHERE reason = "expired" AND url = n.url AND p_id ISNULL
+ ) AS most_recent_expired_visit
+ FROM expiration_notify n
+ GROUP BY url`,
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
+ ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
+ },
+
+ // Empty the notifications table.
+ QUERY_DELETE_NOTIFICATIONS: {
+ sql: "DELETE FROM expiration_notify",
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
+ ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
+ },
+
+ // The following queries are used to adjust the sqlite_stat1 table to help the
+ // query planner create better queries. These should always be run LAST, and
+ // are therefore at the end of the object.
+ // Since also nsNavHistory.cpp executes ANALYZE, the analyzed tables
+ // must be the same in both components. So ensure they are in sync.
+
+ QUERY_ANALYZE_MOZ_PLACES: {
+ sql: "ANALYZE moz_places",
+ actions: ACTION.TIMED_OVERLIMIT | ACTION.TIMED_ANALYZE |
+ ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG
+ },
+ QUERY_ANALYZE_MOZ_BOOKMARKS: {
+ sql: "ANALYZE moz_bookmarks",
+ actions: ACTION.TIMED_ANALYZE | ACTION.IDLE_DAILY | ACTION.DEBUG
+ },
+ QUERY_ANALYZE_MOZ_HISTORYVISITS: {
+ sql: "ANALYZE moz_historyvisits",
+ actions: ACTION.TIMED_OVERLIMIT | ACTION.TIMED_ANALYZE |
+ ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG
+ },
+ QUERY_ANALYZE_MOZ_INPUTHISTORY: {
+ sql: "ANALYZE moz_inputhistory",
+ actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.TIMED_ANALYZE |
+ ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG
+ },
+};
+
+/**
+ * Sends a bookmarks notification through the given observers.
+ *
+ * @param observers
+ * array of nsINavBookmarkObserver objects.
+ * @param notification
+ * the notification name.
+ * @param args
+ * array of arguments to pass to the notification.
+ */
+function notify(observers, notification, args = []) {
+ for (let observer of observers) {
+ try {
+ observer[notification](...args);
+ } catch (ex) {}
+ }
+}
+
+// nsPlacesExpiration definition
+
+function nsPlacesExpiration()
+{
+ // Smart Getters
+
+ XPCOMUtils.defineLazyGetter(this, "_db", function () {
+ let db = Cc["@mozilla.org/browser/nav-history-service;1"].
+ getService(Ci.nsPIPlacesDatabase).
+ DBConnection;
+
+ // Create the temporary notifications table.
+ let stmt = db.createAsyncStatement(
+ `CREATE TEMP TABLE expiration_notify (
+ id INTEGER PRIMARY KEY
+ , v_id INTEGER
+ , p_id INTEGER
+ , url TEXT NOT NULL
+ , guid TEXT NOT NULL
+ , visit_date INTEGER
+ , expected_results INTEGER NOT NULL DEFAULT 0
+ , reason TEXT NOT NULL DEFAULT "expired"
+ )`);
+ stmt.executeAsync();
+ stmt.finalize();
+
+ return db;
+ });
+
+ XPCOMUtils.defineLazyServiceGetter(this, "_idle",
+ "@mozilla.org/widget/idleservice;1",
+ "nsIIdleService");
+
+ this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(PREF_BRANCH);
+
+ this._loadPrefs().then(() => {
+ // Observe our preferences branch for changes.
+ this._prefBranch.addObserver("", this, true);
+
+ // Create our expiration timer.
+ this._newTimer();
+ }, Cu.reportError);
+
+ // Register topic observers.
+ Services.obs.addObserver(this, TOPIC_SHUTDOWN, true);
+ Services.obs.addObserver(this, TOPIC_DEBUG_START_EXPIRATION, true);
+ Services.obs.addObserver(this, TOPIC_IDLE_DAILY, true);
+}
+
+nsPlacesExpiration.prototype = {
+
+ // nsIObserver
+
+ observe: function PEX_observe(aSubject, aTopic, aData)
+ {
+ if (this._shuttingDown) {
+ return;
+ }
+
+ if (aTopic == TOPIC_SHUTDOWN) {
+ this._shuttingDown = true;
+ this.expireOnIdle = false;
+
+ if (this._timer) {
+ this._timer.cancel();
+ this._timer = null;
+ }
+
+ // If we didn't ran a clearHistory recently and database is dirty, we
+ // want to expire some entries, to speed up the expiration process.
+ let hasRecentClearHistory =
+ Date.now() - this._lastClearHistoryTime <
+ SHUTDOWN_WITH_RECENT_CLEARHISTORY_TIMEOUT_SECONDS * 1000;
+ if (!hasRecentClearHistory && this.status == STATUS.DIRTY) {
+ this._expireWithActionAndLimit(ACTION.SHUTDOWN_DIRTY, LIMIT.LARGE);
+ }
+
+ this._finalizeInternalStatements();
+ }
+ else if (aTopic == TOPIC_PREF_CHANGED) {
+ this._loadPrefs().then(() => {
+ if (aData == PREF_INTERVAL_SECONDS) {
+ // Renew the timer with the new interval value.
+ this._newTimer();
+ }
+ }, Cu.reportError);
+ }
+ else if (aTopic == TOPIC_DEBUG_START_EXPIRATION) {
+ // The passed-in limit is the maximum number of visits to expire when
+ // history is over capacity. Mind to correctly handle the NaN value.
+ let limit = parseInt(aData);
+ if (limit == -1) {
+ // Everything should be expired without any limit. If history is over
+ // capacity then all existing visits will be expired.
+ // Should only be used in tests, since may cause dataloss.
+ this._expireWithActionAndLimit(ACTION.DEBUG, LIMIT.UNLIMITED);
+ }
+ else if (limit > 0) {
+ // The number of expired visits is limited by this amount. It may be
+ // used for testing purposes, like checking that limited queries work.
+ this._debugLimit = limit;
+ this._expireWithActionAndLimit(ACTION.DEBUG, LIMIT.DEBUG);
+ }
+ else {
+ // Any other value is intended as a 0 limit, that means no visits
+ // will be expired. Even if this doesn't touch visits, it will remove
+ // any orphan pages, icons, annotations and similar from the database,
+ // so it may be used for cleanup purposes.
+ this._debugLimit = -1;
+ this._expireWithActionAndLimit(ACTION.DEBUG, LIMIT.DEBUG);
+ }
+ }
+ else if (aTopic == TOPIC_IDLE_BEGIN) {
+ // Stop the expiration timer. We don't want to keep up expiring on idle
+ // to preserve batteries on mobile devices and avoid killing stand-by.
+ if (this._timer) {
+ this._timer.cancel();
+ this._timer = null;
+ }
+ if (this.expireOnIdle)
+ this._expireWithActionAndLimit(ACTION.IDLE_DIRTY, LIMIT.LARGE);
+ }
+ else if (aTopic == TOPIC_IDLE_END) {
+ // Restart the expiration timer.
+ if (!this._timer)
+ this._newTimer();
+ }
+ else if (aTopic == TOPIC_IDLE_DAILY) {
+ this._expireWithActionAndLimit(ACTION.IDLE_DAILY, LIMIT.LARGE);
+ }
+ else if (aTopic == TOPIC_TESTING_MODE) {
+ this._testingMode = true;
+ }
+ },
+
+ // nsINavHistoryObserver
+
+ _inBatchMode: false,
+ onBeginUpdateBatch: function PEX_onBeginUpdateBatch()
+ {
+ this._inBatchMode = true;
+
+ // We do not want to expire while we are doing batch work.
+ if (this._timer) {
+ this._timer.cancel();
+ this._timer = null;
+ }
+ },
+
+ onEndUpdateBatch: function PEX_onEndUpdateBatch()
+ {
+ this._inBatchMode = false;
+
+ // Restore timer.
+ if (!this._timer)
+ this._newTimer();
+ },
+
+ _lastClearHistoryTime: 0,
+ onClearHistory: function PEX_onClearHistory() {
+ this._lastClearHistoryTime = Date.now();
+ // Expire orphans. History status is clean after a clear history.
+ this.status = STATUS.CLEAN;
+ this._expireWithActionAndLimit(ACTION.CLEAR_HISTORY, LIMIT.UNLIMITED);
+ },
+
+ onVisit: function() {},
+ onTitleChanged: function() {},
+ onDeleteURI: function() {},
+ onPageChanged: function() {},
+ onDeleteVisits: function() {},
+
+ // nsITimerCallback
+
+ notify: function PEX_timerCallback()
+ {
+ // Check if we are over history capacity, if so visits must be expired.
+ this._getPagesStats((function onPagesCount(aPagesCount, aStatsCount) {
+ let overLimitPages = aPagesCount - this._urisLimit;
+ this._overLimit = overLimitPages > 0;
+
+ let action = this._overLimit ? ACTION.TIMED_OVERLIMIT : ACTION.TIMED;
+ // If the number of pages changed significantly from the last ANALYZE
+ // update SQLite statistics.
+ if (Math.abs(aPagesCount - aStatsCount) >= ANALYZE_PAGES_THRESHOLD) {
+ action = action | ACTION.TIMED_ANALYZE;
+ }
+
+ // Adapt expiration aggressivity to the number of pages over the limit.
+ let limit = overLimitPages > OVERLIMIT_PAGES_THRESHOLD ? LIMIT.LARGE
+ : LIMIT.SMALL;
+
+ this._expireWithActionAndLimit(action, limit);
+ }).bind(this));
+ },
+
+ // mozIStorageStatementCallback
+
+ handleResult: function PEX_handleResult(aResultSet)
+ {
+ // We don't want to notify after shutdown.
+ if (this._shuttingDown)
+ return;
+
+ let row;
+ while ((row = aResultSet.getNextRow())) {
+ // expected_results is set to the number of expected visits by
+ // QUERY_FIND_VISITS_TO_EXPIRE. We decrease that counter for each found
+ // visit and if it reaches zero we mark the database as dirty, since all
+ // the expected visits were expired, so it's likely the next run will
+ // find more.
+ let expectedResults = row.getResultByName("expected_results");
+ if (expectedResults > 0) {
+ if (!("_expectedResultsCount" in this)) {
+ this._expectedResultsCount = expectedResults;
+ }
+ if (this._expectedResultsCount > 0) {
+ this._expectedResultsCount--;
+ }
+ }
+
+ let uri = Services.io.newURI(row.getResultByName("url"), null, null);
+ let guid = row.getResultByName("guid");
+ let visitDate = row.getResultByName("visit_date");
+ let wholeEntry = row.getResultByName("whole_entry");
+ let mostRecentExpiredVisit = row.getResultByName("most_recent_expired_visit");
+ let reason = Ci.nsINavHistoryObserver.REASON_EXPIRED;
+ let observers = PlacesUtils.history.getObservers();
+
+ if (mostRecentExpiredVisit) {
+ let days = parseInt((Date.now() - (mostRecentExpiredVisit / 1000)) / MSECS_PER_DAY);
+ if (!this._mostRecentExpiredVisitDays) {
+ this._mostRecentExpiredVisitDays = days;
+ }
+ else if (days < this._mostRecentExpiredVisitDays) {
+ this._mostRecentExpiredVisitDays = days;
+ }
+ }
+
+ // Dispatch expiration notifications to history.
+ if (wholeEntry) {
+ notify(observers, "onDeleteURI", [uri, guid, reason]);
+ } else {
+ notify(observers, "onDeleteVisits", [uri, visitDate, guid, reason, 0]);
+ }
+ }
+ },
+
+ handleError: function PEX_handleError(aError)
+ {
+ Cu.reportError("Async statement execution returned with '" +
+ aError.result + "', '" + aError.message + "'");
+ },
+
+ handleCompletion: function PEX_handleCompletion(aReason)
+ {
+ if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+
+ if (this._mostRecentExpiredVisitDays) {
+ delete this._mostRecentExpiredVisitDays;
+ }
+
+ if ("_expectedResultsCount" in this) {
+ // Adapt the aggressivity of steps based on the status of history.
+ // A dirty history will return all the entries we are expecting bringing
+ // our countdown to zero, while a clean one will not.
+ let oldStatus = this.status;
+ this.status = this._expectedResultsCount == 0 ? STATUS.DIRTY
+ : STATUS.CLEAN;
+
+ delete this._expectedResultsCount;
+ }
+
+ // Dispatch a notification that expiration has finished.
+ Services.obs.notifyObservers(null, TOPIC_EXPIRATION_FINISHED, null);
+ }
+ },
+
+ // nsPlacesExpiration
+
+ _urisLimit: PREF_MAX_URIS_NOTSET,
+ _interval: PREF_INTERVAL_SECONDS_NOTSET,
+ _shuttingDown: false,
+
+ _status: STATUS.UNKNOWN,
+ set status(aNewStatus) {
+ if (aNewStatus != this._status) {
+ // If status changes we should restart the timer.
+ this._status = aNewStatus;
+ this._newTimer();
+ // If needed add/remove the cleanup step on idle. We want to expire on
+ // idle only if history is dirty, to preserve mobile devices batteries.
+ this.expireOnIdle = aNewStatus == STATUS.DIRTY;
+ }
+ return aNewStatus;
+ },
+ get status() {
+ return this._status;
+ },
+
+ _isIdleObserver: false,
+ _expireOnIdle: false,
+ set expireOnIdle(aExpireOnIdle) {
+ // Observe idle regardless aExpireOnIdle, since we always want to stop
+ // timed expiration on idle, to preserve mobile battery life.
+ if (!this._isIdleObserver && !this._shuttingDown) {
+ this._idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
+ this._isIdleObserver = true;
+ }
+ else if (this._isIdleObserver && this._shuttingDown) {
+ this._idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
+ this._isIdleObserver = false;
+ }
+
+ // If running a debug expiration we need full control of what happens
+ // but idle cleanup could activate in the middle, since tinderboxes are
+ // permanently idle. That would cause unexpected oranges, so disable it.
+ if (this._debugLimit !== undefined)
+ this._expireOnIdle = false;
+ else
+ this._expireOnIdle = aExpireOnIdle;
+ return this._expireOnIdle;
+ },
+ get expireOnIdle() {
+ return this._expireOnIdle;
+ },
+
+ _loadPrefs: Task.async(function* () {
+ // Get the user's limit, if it was set.
+ this._urisLimit = this._prefBranch.getIntPref(PREF_MAX_URIS,
+ PREF_MAX_URIS_NOTSET);
+
+ if (this._urisLimit < 0) {
+ // Some testing code expects a pref change to be synchronous, so
+ // temporarily set this to a large value, while we asynchronously update
+ // to the correct value.
+ this._urisLimit = 300000;
+
+ // The user didn't specify a custom limit, so we calculate the number of
+ // unique places that may fit an optimal database size on this hardware.
+ // Oldest pages over this threshold will be expired.
+ let memSizeBytes = MEMSIZE_FALLBACK_BYTES;
+ try {
+ // Limit the size on systems with small memory.
+ memSizeBytes = Services.sysinfo.getProperty("memsize");
+ } catch (ex) {}
+ if (memSizeBytes <= 0) {
+ memsize = MEMSIZE_FALLBACK_BYTES;
+ }
+
+ let diskAvailableBytes = DISKSIZE_FALLBACK_BYTES;
+ try {
+ // Protect against a full disk or tiny quota.
+ let dbFile = this._db.databaseFile;
+ dbFile.QueryInterface(Ci.nsILocalFile);
+ diskAvailableBytes = dbFile.diskSpaceAvailable;
+ } catch (ex) {}
+ if (diskAvailableBytes <= 0) {
+ diskAvailableBytes = DISKSIZE_FALLBACK_BYTES;
+ }
+
+ let optimalDatabaseSize = Math.min(
+ memSizeBytes * DATABASE_TO_MEMORY_PERC / 100,
+ diskAvailableBytes * DATABASE_TO_DISK_PERC / 100,
+ DATABASE_MAX_SIZE
+ );
+
+ // Calculate avg size of a URI in the database.
+ let db = yield PlacesUtils.promiseDBConnection();
+ let pageSize = (yield db.execute(`PRAGMA page_size`))[0].getResultByIndex(0);
+ let pageCount = (yield db.execute(`PRAGMA page_count`))[0].getResultByIndex(0);
+ let freelistCount = (yield db.execute(`PRAGMA freelist_count`))[0].getResultByIndex(0);
+ let dbSize = (pageCount - freelistCount) * pageSize;
+ let uriCount = (yield db.execute(`SELECT count(*) FROM moz_places`))[0].getResultByIndex(0);
+ let avgURISize = Math.ceil(dbSize / uriCount);
+ // For new profiles this value may be too large, due to the Sqlite header,
+ // or Infinity when there are no pages. Thus we must limit it.
+ if (avgURISize > (URIENTRY_AVG_SIZE * 3)) {
+ avgURISize = URIENTRY_AVG_SIZE;
+ }
+ this._urisLimit = Math.ceil(optimalDatabaseSize / avgURISize);
+ }
+
+ // Expose the calculated limit to other components.
+ this._prefBranch.setIntPref(PREF_READONLY_CALCULATED_MAX_URIS,
+ this._urisLimit);
+
+ // Get the expiration interval value.
+ this._interval = this._prefBranch.getIntPref(PREF_INTERVAL_SECONDS,
+ PREF_INTERVAL_SECONDS_NOTSET);
+ if (this._interval <= 0) {
+ this._interval = PREF_INTERVAL_SECONDS_NOTSET;
+ }
+ }),
+
+ /**
+ * Evaluates the real number of pages in the database and the value currently
+ * used by the SQLite query planner.
+ *
+ * @param aCallback
+ * invoked on success, function (aPagesCount, aStatsCount).
+ */
+ _getPagesStats: function PEX__getPagesStats(aCallback) {
+ if (!this._cachedStatements["LIMIT_COUNT"]) {
+ this._cachedStatements["LIMIT_COUNT"] = this._db.createAsyncStatement(
+ `SELECT (SELECT COUNT(*) FROM moz_places),
+ (SELECT SUBSTR(stat,1,LENGTH(stat)-2) FROM sqlite_stat1
+ WHERE idx = 'moz_places_url_uniqueindex')`
+ );
+ }
+ this._cachedStatements["LIMIT_COUNT"].executeAsync({
+ _pagesCount: 0,
+ _statsCount: 0,
+ handleResult: function(aResults) {
+ let row = aResults.getNextRow();
+ this._pagesCount = row.getResultByIndex(0);
+ this._statsCount = row.getResultByIndex(1);
+ },
+ handleCompletion: function (aReason) {
+ if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ aCallback(this._pagesCount, this._statsCount);
+ }
+ },
+ handleError: function(aError) {
+ Cu.reportError("Async statement execution returned with '" +
+ aError.result + "', '" + aError.message + "'");
+ }
+ });
+ },
+
+ /**
+ * Execute async statements to expire with the specified queries.
+ *
+ * @param aAction
+ * The ACTION we are expiring for. See the ACTION const for values.
+ * @param aLimit
+ * Whether to use small, large or no limits when expiring. See the
+ * LIMIT const for values.
+ */
+ _expireWithActionAndLimit:
+ function PEX__expireWithActionAndLimit(aAction, aLimit)
+ {
+ // Skip expiration during batch mode.
+ if (this._inBatchMode)
+ return;
+ // Don't try to further expire after shutdown.
+ if (this._shuttingDown && aAction != ACTION.SHUTDOWN_DIRTY) {
+ return;
+ }
+
+ let boundStatements = [];
+ for (let queryType in EXPIRATION_QUERIES) {
+ if (EXPIRATION_QUERIES[queryType].actions & aAction)
+ boundStatements.push(this._getBoundStatement(queryType, aLimit, aAction));
+ }
+
+ // Execute statements asynchronously in a transaction.
+ this._db.executeAsync(boundStatements, boundStatements.length, this);
+ },
+
+ /**
+ * Finalizes all of our mozIStorageStatements so we can properly close the
+ * database.
+ */
+ _finalizeInternalStatements: function PEX__finalizeInternalStatements()
+ {
+ for (let queryType in this._cachedStatements) {
+ let stmt = this._cachedStatements[queryType];
+ stmt.finalize();
+ }
+ },
+
+ /**
+ * Generate the statement used for expiration.
+ *
+ * @param aQueryType
+ * Type of the query to build statement for.
+ * @param aLimit
+ * Whether to use small, large or no limits when expiring. See the
+ * LIMIT const for values.
+ * @param aAction
+ * Current action causing the expiration. See the ACTION const.
+ */
+ _cachedStatements: {},
+ _getBoundStatement: function PEX__getBoundStatement(aQueryType, aLimit, aAction)
+ {
+ // Statements creation can be expensive, so we want to cache them.
+ let stmt = this._cachedStatements[aQueryType];
+ if (stmt === undefined) {
+ stmt = this._cachedStatements[aQueryType] =
+ this._db.createAsyncStatement(EXPIRATION_QUERIES[aQueryType].sql);
+ }
+
+ let baseLimit;
+ switch (aLimit) {
+ case LIMIT.UNLIMITED:
+ baseLimit = -1;
+ break;
+ case LIMIT.SMALL:
+ baseLimit = EXPIRE_LIMIT_PER_STEP;
+ break;
+ case LIMIT.LARGE:
+ baseLimit = EXPIRE_LIMIT_PER_STEP * EXPIRE_LIMIT_PER_LARGE_STEP_MULTIPLIER;
+ break;
+ case LIMIT.DEBUG:
+ baseLimit = this._debugLimit;
+ break;
+ }
+ if (this.status == STATUS.DIRTY && aAction != ACTION.DEBUG &&
+ baseLimit > 0) {
+ baseLimit *= EXPIRE_AGGRESSIVITY_MULTIPLIER;
+ }
+
+ // Bind the appropriate parameters.
+ let params = stmt.params;
+ switch (aQueryType) {
+ case "QUERY_FIND_EXOTIC_VISITS_TO_EXPIRE":
+ // Avoid expiring all visits in case of an unlimited debug expiration,
+ // just remove orphans instead.
+ params.limit_visits =
+ aLimit == LIMIT.DEBUG && baseLimit == -1 ? 0 : baseLimit;
+ break;
+ case "QUERY_FIND_VISITS_TO_EXPIRE":
+ params.max_uris = this._urisLimit;
+ // Avoid expiring all visits in case of an unlimited debug expiration,
+ // just remove orphans instead.
+ params.limit_visits =
+ aLimit == LIMIT.DEBUG && baseLimit == -1 ? 0 : baseLimit;
+ break;
+ case "QUERY_FIND_URIS_TO_EXPIRE":
+ params.limit_uris = baseLimit;
+ break;
+ case "QUERY_SILENT_EXPIRE_ORPHAN_URIS":
+ params.limit_uris = baseLimit;
+ break;
+ case "QUERY_EXPIRE_FAVICONS":
+ params.limit_favicons = baseLimit;
+ break;
+ case "QUERY_EXPIRE_ANNOS":
+ // Each page may have multiple annos.
+ params.limit_annos = baseLimit * EXPIRE_AGGRESSIVITY_MULTIPLIER;
+ break;
+ case "QUERY_EXPIRE_ANNOS_WITH_POLICY":
+ case "QUERY_EXPIRE_ITEMS_ANNOS_WITH_POLICY":
+ let microNow = Date.now() * 1000;
+ ANNOS_EXPIRE_POLICIES.forEach(function(policy) {
+ params[policy.bind] = policy.type;
+ params[policy.bind + "_time"] = microNow - policy.time;
+ });
+ break;
+ case "QUERY_EXPIRE_ANNOS_WITH_HISTORY":
+ params.expire_with_history = Ci.nsIAnnotationService.EXPIRE_WITH_HISTORY;
+ break;
+ case "QUERY_EXPIRE_ITEMS_ANNOS":
+ params.limit_annos = baseLimit;
+ break;
+ case "QUERY_EXPIRE_ANNO_ATTRIBUTES":
+ params.limit_annos = baseLimit;
+ break;
+ case "QUERY_EXPIRE_INPUTHISTORY":
+ params.limit_inputhistory = baseLimit;
+ break;
+ case "QUERY_EXPIRE_ANNOS_SESSION":
+ case "QUERY_EXPIRE_ITEMS_ANNOS_SESSION":
+ params.expire_session = Ci.nsIAnnotationService.EXPIRE_SESSION;
+ break;
+ }
+
+ return stmt;
+ },
+
+ /**
+ * Creates a new timer based on this._interval.
+ *
+ * @return a REPEATING_SLACK nsITimer that runs every this._interval.
+ */
+ _newTimer: function PEX__newTimer()
+ {
+ if (this._timer)
+ this._timer.cancel();
+ if (this._shuttingDown)
+ return undefined;
+ let interval = this.status != STATUS.DIRTY ?
+ this._interval * EXPIRE_AGGRESSIVITY_MULTIPLIER : this._interval;
+
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(this, interval * 1000,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+ if (this._testingMode) {
+ Services.obs.notifyObservers(null, TOPIC_TEST_INTERVAL_CHANGED,
+ interval);
+ }
+ return this._timer = timer;
+ },
+
+ // nsISupports
+
+ classID: Components.ID("705a423f-2f69-42f3-b9fe-1517e0dee56f"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(nsPlacesExpiration),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver
+ , Ci.nsINavHistoryObserver
+ , Ci.nsITimerCallback
+ , Ci.mozIStorageStatementCallback
+ , Ci.nsISupportsWeakReference
+ ])
+};
+
+// Module Registration
+
+var components = [nsPlacesExpiration];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/components/places/src/nsPlacesIndexes.h b/components/places/src/nsPlacesIndexes.h
new file mode 100644
index 000000000..9cce5a0aa
--- /dev/null
+++ b/components/places/src/nsPlacesIndexes.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPlacesIndexes_h__
+#define nsPlacesIndexes_h__
+
+#define CREATE_PLACES_IDX(__name, __table, __columns, __type) \
+ NS_LITERAL_CSTRING( \
+ "CREATE " __type " INDEX IF NOT EXISTS " __table "_" __name \
+ " ON " __table " (" __columns ")" \
+ )
+
+/**
+ * moz_places
+ */
+#define CREATE_IDX_MOZ_PLACES_URL_HASH \
+ CREATE_PLACES_IDX( \
+ "url_hashindex", "moz_places", "url_hash", "" \
+ )
+
+#define CREATE_IDX_MOZ_PLACES_FAVICON \
+ CREATE_PLACES_IDX( \
+ "faviconindex", "moz_places", "favicon_id", "" \
+ )
+
+#define CREATE_IDX_MOZ_PLACES_REVHOST \
+ CREATE_PLACES_IDX( \
+ "hostindex", "moz_places", "rev_host", "" \
+ )
+
+#define CREATE_IDX_MOZ_PLACES_VISITCOUNT \
+ CREATE_PLACES_IDX( \
+ "visitcount", "moz_places", "visit_count", "" \
+ )
+
+#define CREATE_IDX_MOZ_PLACES_FRECENCY \
+ CREATE_PLACES_IDX( \
+ "frecencyindex", "moz_places", "frecency", "" \
+ )
+
+#define CREATE_IDX_MOZ_PLACES_LASTVISITDATE \
+ CREATE_PLACES_IDX( \
+ "lastvisitdateindex", "moz_places", "last_visit_date", "" \
+ )
+
+#define CREATE_IDX_MOZ_PLACES_GUID \
+ CREATE_PLACES_IDX( \
+ "guid_uniqueindex", "moz_places", "guid", "UNIQUE" \
+ )
+
+/**
+ * moz_historyvisits
+ */
+
+#define CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE \
+ CREATE_PLACES_IDX( \
+ "placedateindex", "moz_historyvisits", "place_id, visit_date", "" \
+ )
+
+#define CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT \
+ CREATE_PLACES_IDX( \
+ "fromindex", "moz_historyvisits", "from_visit", "" \
+ )
+
+#define CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE \
+ CREATE_PLACES_IDX( \
+ "dateindex", "moz_historyvisits", "visit_date", "" \
+ )
+
+/**
+ * moz_bookmarks
+ */
+
+#define CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE \
+ CREATE_PLACES_IDX( \
+ "itemindex", "moz_bookmarks", "fk, type", "" \
+ )
+
+#define CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION \
+ CREATE_PLACES_IDX( \
+ "parentindex", "moz_bookmarks", "parent, position", "" \
+ )
+
+#define CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED \
+ CREATE_PLACES_IDX( \
+ "itemlastmodifiedindex", "moz_bookmarks", "fk, lastModified", "" \
+ )
+
+#define CREATE_IDX_MOZ_BOOKMARKS_GUID \
+ CREATE_PLACES_IDX( \
+ "guid_uniqueindex", "moz_bookmarks", "guid", "UNIQUE" \
+ )
+
+/**
+ * moz_annos
+ */
+
+#define CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE \
+ CREATE_PLACES_IDX( \
+ "placeattributeindex", "moz_annos", "place_id, anno_attribute_id", "UNIQUE" \
+ )
+
+/**
+ * moz_items_annos
+ */
+
+#define CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE \
+ CREATE_PLACES_IDX( \
+ "itemattributeindex", "moz_items_annos", "item_id, anno_attribute_id", "UNIQUE" \
+ )
+
+/**
+ * moz_keywords
+ */
+
+#define CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA \
+ CREATE_PLACES_IDX( \
+ "placepostdata_uniqueindex", "moz_keywords", "place_id, post_data", "UNIQUE" \
+ )
+
+#endif // nsPlacesIndexes_h__
diff --git a/components/places/src/nsPlacesMacros.h b/components/places/src/nsPlacesMacros.h
new file mode 100644
index 000000000..47ebe17ac
--- /dev/null
+++ b/components/places/src/nsPlacesMacros.h
@@ -0,0 +1,82 @@
+/* -*- 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 "nsIConsoleService.h"
+#include "nsIScriptError.h"
+
+#ifndef __FUNCTION__
+#define __FUNCTION__ __func__
+#endif
+
+// Call a method on each observer in a category cache, then call the same
+// method on the observer array.
+#define NOTIFY_OBSERVERS(canFire, cache, array, type, method) \
+ PR_BEGIN_MACRO \
+ if (canFire) { \
+ nsCOMArray<type> entries; \
+ cache.GetEntries(entries); \
+ for (int32_t idx = 0; idx < entries.Count(); ++idx) \
+ entries[idx]->method; \
+ ENUMERATE_WEAKARRAY(array, type, method) \
+ } \
+ PR_END_MACRO;
+
+#define NOTIFY_BOOKMARKS_OBSERVERS(canFire, cache, array, skipIf, method) \
+ PR_BEGIN_MACRO \
+ if (canFire) { \
+ nsCOMArray<nsINavBookmarkObserver> entries; \
+ cache.GetEntries(entries); \
+ for (int32_t idx = 0; idx < entries.Count(); ++idx) { \
+ if (skipIf(entries[idx])) \
+ continue; \
+ entries[idx]->method; \
+ } \
+ for (uint32_t idx = 0; idx < array.Length(); ++idx) { \
+ const nsCOMPtr<nsINavBookmarkObserver> &e = array.ElementAt(idx).GetValue(); \
+ if (e) { \
+ if (skipIf(e)) \
+ continue; \
+ e->method; \
+ } \
+ } \
+ } \
+ PR_END_MACRO;
+
+#define PLACES_FACTORY_SINGLETON_IMPLEMENTATION(_className, _sInstance) \
+ _className * _className::_sInstance = nullptr; \
+ \
+ already_AddRefed<_className> \
+ _className::GetSingleton() \
+ { \
+ if (_sInstance) { \
+ RefPtr<_className> ret = _sInstance; \
+ return ret.forget(); \
+ } \
+ _sInstance = new _className(); \
+ RefPtr<_className> ret = _sInstance; \
+ if (NS_FAILED(_sInstance->Init())) { \
+ /* Null out ret before _sInstance so the destructor doesn't assert */ \
+ ret = nullptr; \
+ _sInstance = nullptr; \
+ return nullptr; \
+ } \
+ return ret.forget(); \
+ }
+
+#define PLACES_WARN_DEPRECATED() \
+ PR_BEGIN_MACRO \
+ nsCString msg(__FUNCTION__); \
+ msg.AppendLiteral(" is deprecated and will be removed in the next version.");\
+ NS_WARNING(msg.get()); \
+ nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);\
+ if (cs) { \
+ nsCOMPtr<nsIScriptError> e = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); \
+ if (e && NS_SUCCEEDED(e->Init(NS_ConvertUTF8toUTF16(msg), EmptyString(), \
+ EmptyString(), 0, 0, \
+ nsIScriptError::errorFlag, "Places"))) { \
+ cs->LogMessage(e); \
+ } \
+ } \
+ PR_END_MACRO
diff --git a/components/places/src/nsPlacesModule.cpp b/components/places/src/nsPlacesModule.cpp
new file mode 100644
index 000000000..679d460b4
--- /dev/null
+++ b/components/places/src/nsPlacesModule.cpp
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIClassInfoImpl.h"
+
+#include "nsAnnoProtocolHandler.h"
+#include "nsAnnotationService.h"
+#include "nsNavHistory.h"
+#include "nsNavBookmarks.h"
+#include "nsFaviconService.h"
+#include "History.h"
+#include "nsDocShellCID.h"
+
+using namespace mozilla::places;
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNavHistory,
+ nsNavHistory::GetSingleton)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsAnnotationService,
+ nsAnnotationService::GetSingleton)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsNavBookmarks,
+ nsNavBookmarks::GetSingleton)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsFaviconService,
+ nsFaviconService::GetSingleton)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(History, History::GetSingleton)
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAnnoProtocolHandler)
+NS_DEFINE_NAMED_CID(NS_NAVHISTORYSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_ANNOTATIONSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_ANNOPROTOCOLHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_NAVBOOKMARKSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_FAVICONSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_HISTORYSERVICE_CID);
+
+const mozilla::Module::CIDEntry kPlacesCIDs[] = {
+ { &kNS_NAVHISTORYSERVICE_CID, false, nullptr, nsNavHistoryConstructor },
+ { &kNS_ANNOTATIONSERVICE_CID, false, nullptr, nsAnnotationServiceConstructor },
+ { &kNS_ANNOPROTOCOLHANDLER_CID, false, nullptr, nsAnnoProtocolHandlerConstructor },
+ { &kNS_NAVBOOKMARKSSERVICE_CID, false, nullptr, nsNavBookmarksConstructor },
+ { &kNS_FAVICONSERVICE_CID, false, nullptr, nsFaviconServiceConstructor },
+ { &kNS_HISTORYSERVICE_CID, false, nullptr, HistoryConstructor },
+ { nullptr }
+};
+
+const mozilla::Module::ContractIDEntry kPlacesContracts[] = {
+ { NS_NAVHISTORYSERVICE_CONTRACTID, &kNS_NAVHISTORYSERVICE_CID },
+ { NS_ANNOTATIONSERVICE_CONTRACTID, &kNS_ANNOTATIONSERVICE_CID },
+ { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "moz-anno", &kNS_ANNOPROTOCOLHANDLER_CID },
+ { NS_NAVBOOKMARKSSERVICE_CONTRACTID, &kNS_NAVBOOKMARKSSERVICE_CID },
+ { NS_FAVICONSERVICE_CONTRACTID, &kNS_FAVICONSERVICE_CID },
+ { "@mozilla.org/embeddor.implemented/bookmark-charset-resolver;1", &kNS_NAVHISTORYSERVICE_CID },
+ { NS_IHISTORY_CONTRACTID, &kNS_HISTORYSERVICE_CID },
+ { NS_DOWNLOADHISTORY_CONTRACTID, &kNS_HISTORYSERVICE_CID },
+ { nullptr }
+};
+
+const mozilla::Module::CategoryEntry kPlacesCategories[] = {
+ { "vacuum-participant", "Places", NS_NAVHISTORYSERVICE_CONTRACTID },
+ { nullptr }
+};
+
+const mozilla::Module kPlacesModule = {
+ mozilla::Module::kVersion,
+ kPlacesCIDs,
+ kPlacesContracts,
+ kPlacesCategories
+};
+
+NSMODULE_DEFN(nsPlacesModule) = &kPlacesModule;
diff --git a/components/places/src/nsPlacesTables.h b/components/places/src/nsPlacesTables.h
new file mode 100644
index 000000000..0b6e414fb
--- /dev/null
+++ b/components/places/src/nsPlacesTables.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsPlacesTables_h__
+#define __nsPlacesTables_h__
+
+
+#define CREATE_MOZ_PLACES NS_LITERAL_CSTRING( \
+ "CREATE TABLE moz_places ( " \
+ " id INTEGER PRIMARY KEY" \
+ ", url LONGVARCHAR" \
+ ", title LONGVARCHAR" \
+ ", rev_host LONGVARCHAR" \
+ ", visit_count INTEGER DEFAULT 0" \
+ ", hidden INTEGER DEFAULT 0 NOT NULL" \
+ ", typed INTEGER DEFAULT 0 NOT NULL" \
+ ", favicon_id INTEGER" \
+ ", frecency INTEGER DEFAULT -1 NOT NULL" \
+ ", last_visit_date INTEGER " \
+ ", guid TEXT" \
+ ", foreign_count INTEGER DEFAULT 0 NOT NULL" \
+ ", url_hash INTEGER DEFAULT 0 NOT NULL " \
+ ")" \
+)
+
+#define CREATE_MOZ_HISTORYVISITS NS_LITERAL_CSTRING( \
+ "CREATE TABLE moz_historyvisits (" \
+ " id INTEGER PRIMARY KEY" \
+ ", from_visit INTEGER" \
+ ", place_id INTEGER" \
+ ", visit_date INTEGER" \
+ ", visit_type INTEGER" \
+ ", session INTEGER" \
+ ")" \
+)
+
+
+#define CREATE_MOZ_INPUTHISTORY NS_LITERAL_CSTRING( \
+ "CREATE TABLE moz_inputhistory (" \
+ " place_id INTEGER NOT NULL" \
+ ", input LONGVARCHAR NOT NULL" \
+ ", use_count INTEGER" \
+ ", PRIMARY KEY (place_id, input)" \
+ ")" \
+)
+
+#define CREATE_MOZ_ANNOS NS_LITERAL_CSTRING( \
+ "CREATE TABLE moz_annos (" \
+ " id INTEGER PRIMARY KEY" \
+ ", place_id INTEGER NOT NULL" \
+ ", anno_attribute_id INTEGER" \
+ ", mime_type VARCHAR(32) DEFAULT NULL" \
+ ", content LONGVARCHAR" \
+ ", flags INTEGER DEFAULT 0" \
+ ", expiration INTEGER DEFAULT 0" \
+ ", type INTEGER DEFAULT 0" \
+ ", dateAdded INTEGER DEFAULT 0" \
+ ", lastModified INTEGER DEFAULT 0" \
+ ")" \
+)
+
+#define CREATE_MOZ_ANNO_ATTRIBUTES NS_LITERAL_CSTRING( \
+ "CREATE TABLE moz_anno_attributes (" \
+ " id INTEGER PRIMARY KEY" \
+ ", name VARCHAR(32) UNIQUE NOT NULL" \
+ ")" \
+)
+
+#define CREATE_MOZ_ITEMS_ANNOS NS_LITERAL_CSTRING( \
+ "CREATE TABLE moz_items_annos (" \
+ " id INTEGER PRIMARY KEY" \
+ ", item_id INTEGER NOT NULL" \
+ ", anno_attribute_id INTEGER" \
+ ", mime_type VARCHAR(32) DEFAULT NULL" \
+ ", content LONGVARCHAR" \
+ ", flags INTEGER DEFAULT 0" \
+ ", expiration INTEGER DEFAULT 0" \
+ ", type INTEGER DEFAULT 0" \
+ ", dateAdded INTEGER DEFAULT 0" \
+ ", lastModified INTEGER DEFAULT 0" \
+ ")" \
+)
+
+#define CREATE_MOZ_FAVICONS NS_LITERAL_CSTRING( \
+ "CREATE TABLE moz_favicons (" \
+ " id INTEGER PRIMARY KEY" \
+ ", url LONGVARCHAR UNIQUE" \
+ ", data BLOB" \
+ ", mime_type VARCHAR(32)" \
+ ", expiration LONG" \
+ ")" \
+)
+
+#define CREATE_MOZ_BOOKMARKS NS_LITERAL_CSTRING( \
+ "CREATE TABLE moz_bookmarks (" \
+ " id INTEGER PRIMARY KEY" \
+ ", type INTEGER" \
+ ", fk INTEGER DEFAULT NULL" /* place_id */ \
+ ", parent INTEGER" \
+ ", position INTEGER" \
+ ", title LONGVARCHAR" \
+ ", keyword_id INTEGER" \
+ ", folder_type TEXT" \
+ ", dateAdded INTEGER" \
+ ", lastModified INTEGER" \
+ ", guid TEXT" \
+ ")" \
+)
+
+#define CREATE_MOZ_KEYWORDS NS_LITERAL_CSTRING( \
+ "CREATE TABLE moz_keywords (" \
+ " id INTEGER PRIMARY KEY AUTOINCREMENT" \
+ ", keyword TEXT UNIQUE" \
+ ", place_id INTEGER" \
+ ", post_data TEXT" \
+ ")" \
+)
+
+#define CREATE_MOZ_HOSTS NS_LITERAL_CSTRING( \
+ "CREATE TABLE moz_hosts (" \
+ " id INTEGER PRIMARY KEY" \
+ ", host TEXT NOT NULL UNIQUE" \
+ ", frecency INTEGER" \
+ ", typed INTEGER NOT NULL DEFAULT 0" \
+ ", prefix TEXT" \
+ ")" \
+)
+
+// Note: this should be kept up-to-date with the definition in
+// nsPlacesAutoComplete.js.
+#define CREATE_MOZ_OPENPAGES_TEMP NS_LITERAL_CSTRING( \
+ "CREATE TEMP TABLE moz_openpages_temp (" \
+ " url TEXT PRIMARY KEY" \
+ ", open_count INTEGER" \
+ ")" \
+)
+
+// This table is used, along with moz_places_afterdelete_trigger, to update
+// hosts after places removals. During a DELETE FROM moz_places, hosts are
+// accumulated into this table, then a DELETE FROM moz_updatehosts_temp will
+// take care of updating the moz_hosts table for every modified host.
+// See CREATE_PLACES_AFTERDELETE_TRIGGER in nsPlacestriggers.h for details.
+#define CREATE_UPDATEHOSTS_TEMP NS_LITERAL_CSTRING( \
+ "CREATE TEMP TABLE moz_updatehosts_temp (" \
+ " host TEXT PRIMARY KEY " \
+ ") WITHOUT ROWID " \
+)
+
+#endif // __nsPlacesTables_h__
diff --git a/components/places/src/nsPlacesTriggers.h b/components/places/src/nsPlacesTriggers.h
new file mode 100644
index 000000000..37871a3eb
--- /dev/null
+++ b/components/places/src/nsPlacesTriggers.h
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPlacesTables.h"
+
+#ifndef __nsPlacesTriggers_h__
+#define __nsPlacesTriggers_h__
+
+/**
+ * Exclude these visit types:
+ * 0 - invalid
+ * 4 - EMBED
+ * 7 - DOWNLOAD
+ * 8 - FRAMED_LINK
+ * 9 - RELOAD
+ **/
+#define EXCLUDED_VISIT_TYPES "0, 4, 7, 8, 9"
+
+/**
+ * This triggers update visit_count and last_visit_date based on historyvisits
+ * table changes.
+ */
+#define CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_historyvisits_afterinsert_v2_trigger " \
+ "AFTER INSERT ON moz_historyvisits FOR EACH ROW " \
+ "BEGIN " \
+ "SELECT store_last_inserted_id('moz_historyvisits', NEW.id); " \
+ "UPDATE moz_places SET " \
+ "visit_count = visit_count + (SELECT NEW.visit_type NOT IN (" EXCLUDED_VISIT_TYPES ")), "\
+ "last_visit_date = MAX(IFNULL(last_visit_date, 0), NEW.visit_date) " \
+ "WHERE id = NEW.place_id;" \
+ "END" \
+)
+
+#define CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_historyvisits_afterdelete_v2_trigger " \
+ "AFTER DELETE ON moz_historyvisits FOR EACH ROW " \
+ "BEGIN " \
+ "UPDATE moz_places SET " \
+ "visit_count = visit_count - (SELECT OLD.visit_type NOT IN (" EXCLUDED_VISIT_TYPES ")), "\
+ "last_visit_date = (SELECT visit_date FROM moz_historyvisits " \
+ "WHERE place_id = OLD.place_id " \
+ "ORDER BY visit_date DESC LIMIT 1) " \
+ "WHERE id = OLD.place_id;" \
+ "END" \
+)
+
+/**
+ * A predicate matching pages on rev_host, based on a given host value.
+ * 'host' may be either the moz_hosts.host column or an alias representing an
+ * equivalent value.
+ */
+#define HOST_TO_REVHOST_PREDICATE \
+ "rev_host = get_unreversed_host(host || '.') || '.' " \
+ "OR rev_host = get_unreversed_host(host || '.') || '.www.'"
+
+/**
+ * Select the best prefix for a host, based on existing pages registered for it.
+ * Prefixes have a priority, from the top to the bottom, so that secure pages
+ * have higher priority, and more generically "www." prefixed hosts come before
+ * unprefixed ones.
+ * Given a host, examine associated pages and:
+ * - if all of the typed pages start with https://www. return https://www.
+ * - if all of the typed pages start with https:// return https://
+ * - if all of the typed pages start with ftp: return ftp://
+ * - if all of the typed pages start with www. return www.
+ * - otherwise don't use any prefix
+ */
+#define HOSTS_PREFIX_PRIORITY_FRAGMENT \
+ "SELECT CASE " \
+ "WHEN 1 = ( " \
+ "SELECT min(substr(url,1,12) = 'https://www.') FROM moz_places h " \
+ "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
+ ") THEN 'https://www.' " \
+ "WHEN 1 = ( " \
+ "SELECT min(substr(url,1,8) = 'https://') FROM moz_places h " \
+ "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
+ ") THEN 'https://' " \
+ "WHEN 1 = ( " \
+ "SELECT min(substr(url,1,4) = 'ftp:') FROM moz_places h " \
+ "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
+ ") THEN 'ftp://' " \
+ "WHEN 1 = ( " \
+ "SELECT min(substr(url,1,11) = 'http://www.') FROM moz_places h " \
+ "WHERE (" HOST_TO_REVHOST_PREDICATE ") AND +h.typed = 1 " \
+ ") THEN 'www.' " \
+ "END "
+
+/**
+ * These triggers update the hostnames table whenever moz_places changes.
+ */
+#define CREATE_PLACES_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_places_afterinsert_trigger " \
+ "AFTER INSERT ON moz_places FOR EACH ROW " \
+ "BEGIN " \
+ "SELECT store_last_inserted_id('moz_places', NEW.id); " \
+ "INSERT OR REPLACE INTO moz_hosts (id, host, frecency, typed, prefix) " \
+ "SELECT " \
+ "(SELECT id FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), " \
+ "fixup_url(get_unreversed_host(NEW.rev_host)), " \
+ "MAX(IFNULL((SELECT frecency FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), -1), NEW.frecency), " \
+ "MAX(IFNULL((SELECT typed FROM moz_hosts WHERE host = fixup_url(get_unreversed_host(NEW.rev_host))), 0), NEW.typed), " \
+ "(" HOSTS_PREFIX_PRIORITY_FRAGMENT \
+ "FROM ( " \
+ "SELECT fixup_url(get_unreversed_host(NEW.rev_host)) AS host " \
+ ") AS match " \
+ ") " \
+ " WHERE LENGTH(NEW.rev_host) > 1; " \
+ "END" \
+)
+
+// This is a hack to workaround the lack of FOR EACH STATEMENT in Sqlite, until
+// bug 871908 can be fixed properly.
+// We store the modified hosts in a temp table, and after every DELETE FROM
+// moz_places, we issue a DELETE FROM moz_updatehosts_temp. The AFTER DELETE
+// trigger will then take care of updating the moz_hosts table.
+// Note this way we lose atomicity, crashing between the 2 queries may break the
+// hosts table coherency. So it's better to run those DELETE queries in a single
+// transaction.
+// Regardless, this is still better than hanging the browser for several minutes
+// on a fast machine.
+#define CREATE_PLACES_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_places_afterdelete_trigger " \
+ "AFTER DELETE ON moz_places FOR EACH ROW " \
+ "BEGIN " \
+ "INSERT OR IGNORE INTO moz_updatehosts_temp (host)" \
+ "VALUES (fixup_url(get_unreversed_host(OLD.rev_host)));" \
+ "END" \
+)
+
+#define CREATE_UPDATEHOSTS_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_updatehosts_afterdelete_trigger " \
+ "AFTER DELETE ON moz_updatehosts_temp FOR EACH ROW " \
+ "BEGIN " \
+ "DELETE FROM moz_hosts " \
+ "WHERE host = OLD.host " \
+ "AND NOT EXISTS(" \
+ "SELECT 1 FROM moz_places " \
+ "WHERE rev_host = get_unreversed_host(host || '.') || '.' " \
+ "OR rev_host = get_unreversed_host(host || '.') || '.www.' " \
+ "); " \
+ "UPDATE moz_hosts " \
+ "SET prefix = (" HOSTS_PREFIX_PRIORITY_FRAGMENT ") " \
+ "WHERE host = OLD.host; " \
+ "END" \
+)
+
+// For performance reasons the host frecency is updated only when the page
+// frecency changes by a meaningful percentage. This is because the frecency
+// decay algorithm requires to update all the frecencies at once, causing a
+// too high overhead, while leaving the ordering unchanged.
+#define CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_places_afterupdate_frecency_trigger " \
+ "AFTER UPDATE OF frecency ON moz_places FOR EACH ROW " \
+ "WHEN NEW.frecency >= 0 " \
+ "AND ABS(" \
+ "IFNULL((NEW.frecency - OLD.frecency) / CAST(NEW.frecency AS REAL), " \
+ "(NEW.frecency - OLD.frecency))" \
+ ") > .05 " \
+ "BEGIN " \
+ "UPDATE moz_hosts " \
+ "SET frecency = (SELECT MAX(frecency) FROM moz_places " \
+ "WHERE rev_host = get_unreversed_host(host || '.') || '.' " \
+ "OR rev_host = get_unreversed_host(host || '.') || '.www.') " \
+ "WHERE host = fixup_url(get_unreversed_host(NEW.rev_host)); " \
+ "END" \
+)
+
+#define CREATE_PLACES_AFTERUPDATE_TYPED_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_places_afterupdate_typed_trigger " \
+ "AFTER UPDATE OF typed ON moz_places FOR EACH ROW " \
+ "WHEN NEW.typed = 1 " \
+ "BEGIN " \
+ "UPDATE moz_hosts " \
+ "SET typed = 1 " \
+ "WHERE host = fixup_url(get_unreversed_host(NEW.rev_host)); " \
+ "END" \
+)
+
+/**
+ * This trigger removes a row from moz_openpages_temp when open_count reaches 0.
+ *
+ * @note this should be kept up-to-date with the definition in
+ * nsPlacesAutoComplete.js
+ */
+#define CREATE_REMOVEOPENPAGE_CLEANUP_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMPORARY TRIGGER moz_openpages_temp_afterupdate_trigger " \
+ "AFTER UPDATE OF open_count ON moz_openpages_temp FOR EACH ROW " \
+ "WHEN NEW.open_count = 0 " \
+ "BEGIN " \
+ "DELETE FROM moz_openpages_temp " \
+ "WHERE url = NEW.url;" \
+ "END" \
+)
+
+#define CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_bookmarks_foreign_count_afterdelete_trigger " \
+ "AFTER DELETE ON moz_bookmarks FOR EACH ROW " \
+ "BEGIN " \
+ "UPDATE moz_places " \
+ "SET foreign_count = foreign_count - 1 " \
+ "WHERE id = OLD.fk;" \
+ "END" \
+)
+
+#define CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_bookmarks_foreign_count_afterinsert_trigger " \
+ "AFTER INSERT ON moz_bookmarks FOR EACH ROW " \
+ "BEGIN " \
+ "SELECT store_last_inserted_id('moz_bookmarks', NEW.id); " \
+ "UPDATE moz_places " \
+ "SET foreign_count = foreign_count + 1 " \
+ "WHERE id = NEW.fk;" \
+ "END" \
+)
+
+#define CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_bookmarks_foreign_count_afterupdate_trigger " \
+ "AFTER UPDATE OF fk ON moz_bookmarks FOR EACH ROW " \
+ "BEGIN " \
+ "UPDATE moz_places " \
+ "SET foreign_count = foreign_count + 1 " \
+ "WHERE id = NEW.fk;" \
+ "UPDATE moz_places " \
+ "SET foreign_count = foreign_count - 1 " \
+ "WHERE id = OLD.fk;" \
+ "END" \
+)
+
+#define CREATE_KEYWORDS_FOREIGNCOUNT_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_keywords_foreign_count_afterdelete_trigger " \
+ "AFTER DELETE ON moz_keywords FOR EACH ROW " \
+ "BEGIN " \
+ "UPDATE moz_places " \
+ "SET foreign_count = foreign_count - 1 " \
+ "WHERE id = OLD.place_id;" \
+ "END" \
+)
+
+#define CREATE_KEYWORDS_FOREIGNCOUNT_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_keyords_foreign_count_afterinsert_trigger " \
+ "AFTER INSERT ON moz_keywords FOR EACH ROW " \
+ "BEGIN " \
+ "UPDATE moz_places " \
+ "SET foreign_count = foreign_count + 1 " \
+ "WHERE id = NEW.place_id;" \
+ "END" \
+)
+
+#define CREATE_KEYWORDS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER NS_LITERAL_CSTRING( \
+ "CREATE TEMP TRIGGER moz_keywords_foreign_count_afterupdate_trigger " \
+ "AFTER UPDATE OF place_id ON moz_keywords FOR EACH ROW " \
+ "BEGIN " \
+ "UPDATE moz_places " \
+ "SET foreign_count = foreign_count + 1 " \
+ "WHERE id = NEW.place_id; " \
+ "UPDATE moz_places " \
+ "SET foreign_count = foreign_count - 1 " \
+ "WHERE id = OLD.place_id; " \
+ "END" \
+)
+
+#endif // __nsPlacesTriggers_h__
diff --git a/components/places/src/nsTaggingService.js b/components/places/src/nsTaggingService.js
new file mode 100644
index 000000000..e367e6cb3
--- /dev/null
+++ b/components/places/src/nsTaggingService.js
@@ -0,0 +1,713 @@
+/* -*- 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;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+
+const TOPIC_SHUTDOWN = "places-shutdown";
+
+/**
+ * The Places Tagging Service
+ */
+function TaggingService() {
+ // Observe bookmarks changes.
+ PlacesUtils.bookmarks.addObserver(this, false);
+
+ // Cleanup on shutdown.
+ Services.obs.addObserver(this, TOPIC_SHUTDOWN, false);
+}
+
+TaggingService.prototype = {
+ /**
+ * Creates a tag container under the tags-root with the given name.
+ *
+ * @param aTagName
+ * the name for the new tag.
+ * @param aSource
+ * a change source constant from nsINavBookmarksService::SOURCE_*.
+ * @returns the id of the new tag container.
+ */
+ _createTag: function TS__createTag(aTagName, aSource) {
+ var newFolderId = PlacesUtils.bookmarks.createFolder(
+ PlacesUtils.tagsFolderId, aTagName, PlacesUtils.bookmarks.DEFAULT_INDEX,
+ /* aGuid */ null, aSource
+ );
+ // Add the folder to our local cache, so we can avoid doing this in the
+ // observer that would have to check itemType.
+ this._tagFolders[newFolderId] = aTagName;
+
+ return newFolderId;
+ },
+
+ /**
+ * Checks whether the given uri is tagged with the given tag.
+ *
+ * @param [in] aURI
+ * url to check for
+ * @param [in] aTagName
+ * the tag to check for
+ * @returns the item id if the URI is tagged with the given tag, -1
+ * otherwise.
+ */
+ _getItemIdForTaggedURI: function TS__getItemIdForTaggedURI(aURI, aTagName) {
+ var tagId = this._getItemIdForTag(aTagName);
+ if (tagId == -1)
+ return -1;
+ // Using bookmarks service API for this would be a pain.
+ // Until tags implementation becomes sane, go the query way.
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ let stmt = db.createStatement(
+ `SELECT id FROM moz_bookmarks
+ WHERE parent = :tag_id
+ AND fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)`
+ );
+ stmt.params.tag_id = tagId;
+ stmt.params.page_url = aURI.spec;
+ try {
+ if (stmt.executeStep()) {
+ return stmt.row.id;
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+ return -1;
+ },
+
+ /**
+ * Returns the folder id for a tag, or -1 if not found.
+ * @param [in] aTag
+ * string tag to search for
+ * @returns integer id for the bookmark folder for the tag
+ */
+ _getItemIdForTag: function TS_getItemIdForTag(aTagName) {
+ for (var i in this._tagFolders) {
+ if (aTagName.toLowerCase() == this._tagFolders[i].toLowerCase())
+ return parseInt(i);
+ }
+ return -1;
+ },
+
+ /**
+ * Makes a proper array of tag objects like { id: number, name: string }.
+ *
+ * @param aTags
+ * Array of tags. Entries can be tag names or concrete item id.
+ * @param trim [optional]
+ * Whether to trim passed-in named tags. Defaults to false.
+ * @return Array of tag objects like { id: number, name: string }.
+ *
+ * @throws Cr.NS_ERROR_INVALID_ARG if any element of the input array is not
+ * a valid tag.
+ */
+ _convertInputMixedTagsArray(aTags, trim=false) {
+ // Handle sparse array with a .filter.
+ return aTags.filter(tag => tag !== undefined)
+ .map(idOrName => {
+ let tag = {};
+ if (typeof(idOrName) == "number" && this._tagFolders[idOrName]) {
+ // This is a tag folder id.
+ tag.id = idOrName;
+ // We can't know the name at this point, since a previous tag could
+ // want to change it.
+ tag.__defineGetter__("name", () => this._tagFolders[tag.id]);
+ }
+ else if (typeof(idOrName) == "string" && idOrName.length > 0 &&
+ idOrName.length <= Ci.nsITaggingService.MAX_TAG_LENGTH) {
+ // This is a tag name.
+ tag.name = trim ? idOrName.trim() : idOrName;
+ // We can't know the id at this point, since a previous tag could
+ // have created it.
+ tag.__defineGetter__("id", () => this._getItemIdForTag(tag.name));
+ }
+ else {
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+ return tag;
+ });
+ },
+
+ // nsITaggingService
+ tagURI: function TS_tagURI(aURI, aTags, aSource)
+ {
+ if (!aURI || !aTags || !Array.isArray(aTags)) {
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+
+ // This also does some input validation.
+ let tags = this._convertInputMixedTagsArray(aTags, true);
+
+ let taggingFunction = () => {
+ for (let tag of tags) {
+ if (tag.id == -1) {
+ // Tag does not exist yet, create it.
+ this._createTag(tag.name, aSource);
+ }
+
+ if (this._getItemIdForTaggedURI(aURI, tag.name) == -1) {
+ // The provided URI is not yet tagged, add a tag for it.
+ // Note that bookmarks under tag containers must have null titles.
+ PlacesUtils.bookmarks.insertBookmark(
+ tag.id, aURI, PlacesUtils.bookmarks.DEFAULT_INDEX,
+ /* aTitle */ null, /* aGuid */ null, aSource
+ );
+ }
+
+ // Try to preserve user's tag name casing.
+ // Rename the tag container so the Places view matches the most-recent
+ // user-typed value.
+ if (PlacesUtils.bookmarks.getItemTitle(tag.id) != tag.name) {
+ // this._tagFolders is updated by the bookmarks observer.
+ PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name, aSource);
+ }
+ }
+ };
+
+ // Use a batch only if creating more than 2 tags.
+ if (tags.length < 3) {
+ taggingFunction();
+ } else {
+ PlacesUtils.bookmarks.runInBatchMode(taggingFunction, null);
+ }
+ },
+
+ /**
+ * Removes the tag container from the tags root if the given tag is empty.
+ *
+ * @param aTagId
+ * the itemId of the tag element under the tags root
+ * @param aSource
+ * a change source constant from nsINavBookmarksService::SOURCE_*
+ */
+ _removeTagIfEmpty: function TS__removeTagIfEmpty(aTagId, aSource) {
+ let count = 0;
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ let stmt = db.createStatement(
+ `SELECT count(*) AS count FROM moz_bookmarks
+ WHERE parent = :tag_id`
+ );
+ stmt.params.tag_id = aTagId;
+ try {
+ if (stmt.executeStep()) {
+ count = stmt.row.count;
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ if (count == 0) {
+ PlacesUtils.bookmarks.removeItem(aTagId, aSource);
+ }
+ },
+
+ // nsITaggingService
+ untagURI: function TS_untagURI(aURI, aTags, aSource)
+ {
+ if (!aURI || (aTags && !Array.isArray(aTags))) {
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+
+ if (!aTags) {
+ // Passing null should clear all tags for aURI, see the IDL.
+ // XXXmano: write a perf-sensitive version of this code path...
+ aTags = this.getTagsForURI(aURI);
+ }
+
+ // This also does some input validation.
+ let tags = this._convertInputMixedTagsArray(aTags);
+
+ let isAnyTagNotTrimmed = tags.some(tag => /^\s|\s$/.test(tag.name));
+ if (isAnyTagNotTrimmed) {
+ Deprecated.warning("At least one tag passed to untagURI was not trimmed",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=967196");
+ }
+
+ let untaggingFunction = () => {
+ for (let tag of tags) {
+ if (tag.id != -1) {
+ // A tag could exist.
+ let itemId = this._getItemIdForTaggedURI(aURI, tag.name);
+ if (itemId != -1) {
+ // There is a tagged item.
+ PlacesUtils.bookmarks.removeItem(itemId, aSource);
+ }
+ }
+ }
+ };
+
+ // Use a batch only if creating more than 2 tags.
+ if (tags.length < 3) {
+ untaggingFunction();
+ } else {
+ PlacesUtils.bookmarks.runInBatchMode(untaggingFunction, null);
+ }
+ },
+
+ // nsITaggingService
+ getURIsForTag: function TS_getURIsForTag(aTagName) {
+ if (!aTagName || aTagName.length == 0)
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ if (/^\s|\s$/.test(aTagName)) {
+ Deprecated.warning("Tag passed to getURIsForTag was not trimmed",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=967196");
+ }
+
+ let uris = [];
+ let tagId = this._getItemIdForTag(aTagName);
+ if (tagId == -1)
+ return uris;
+
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ let stmt = db.createStatement(
+ `SELECT h.url FROM moz_places h
+ JOIN moz_bookmarks b ON b.fk = h.id
+ WHERE b.parent = :tag_id`
+ );
+ stmt.params.tag_id = tagId;
+ try {
+ while (stmt.executeStep()) {
+ try {
+ uris.push(Services.io.newURI(stmt.row.url, null, null));
+ } catch (ex) {}
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ return uris;
+ },
+
+ // nsITaggingService
+ getTagsForURI: function TS_getTagsForURI(aURI, aCount) {
+ if (!aURI)
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ var tags = [];
+ var bookmarkIds = PlacesUtils.bookmarks.getBookmarkIdsForURI(aURI);
+ for (var i=0; i < bookmarkIds.length; i++) {
+ var folderId = PlacesUtils.bookmarks.getFolderIdForItem(bookmarkIds[i]);
+ if (this._tagFolders[folderId])
+ tags.push(this._tagFolders[folderId]);
+ }
+
+ // sort the tag list
+ tags.sort(function(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ });
+ if (aCount)
+ aCount.value = tags.length;
+ return tags;
+ },
+
+ __tagFolders: null,
+ get _tagFolders() {
+ if (!this.__tagFolders) {
+ this.__tagFolders = [];
+
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ let stmt = db.createStatement(
+ "SELECT id, title FROM moz_bookmarks WHERE parent = :tags_root "
+ );
+ stmt.params.tags_root = PlacesUtils.tagsFolderId;
+ try {
+ while (stmt.executeStep()) {
+ this.__tagFolders[stmt.row.id] = stmt.row.title;
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+ }
+
+ return this.__tagFolders;
+ },
+
+ // nsITaggingService
+ get allTags() {
+ var allTags = [];
+ for (var i in this._tagFolders)
+ allTags.push(this._tagFolders[i]);
+ // sort the tag list
+ allTags.sort(function(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ });
+ return allTags;
+ },
+
+ // nsITaggingService
+ get hasTags() {
+ return this._tagFolders.length > 0;
+ },
+
+ // nsIObserver
+ observe: function TS_observe(aSubject, aTopic, aData) {
+ if (aTopic == TOPIC_SHUTDOWN) {
+ PlacesUtils.bookmarks.removeObserver(this);
+ Services.obs.removeObserver(this, TOPIC_SHUTDOWN);
+ }
+ },
+
+ /**
+ * If the only bookmark items associated with aURI are contained in tag
+ * folders, returns the IDs of those items. This can be the case if
+ * the URI was bookmarked and tagged at some point, but the bookmark was
+ * removed, leaving only the bookmark items in tag folders. If the URI is
+ * either properly bookmarked or not tagged just returns and empty array.
+ *
+ * @param aURI
+ * A URI (string) that may or may not be bookmarked
+ * @returns an array of item ids
+ */
+ _getTaggedItemIdsIfUnbookmarkedURI:
+ function TS__getTaggedItemIdsIfUnbookmarkedURI(aURI) {
+ var itemIds = [];
+ var isBookmarked = false;
+
+ // Using bookmarks service API for this would be a pain.
+ // Until tags implementation becomes sane, go the query way.
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ let stmt = db.createStatement(
+ `SELECT id, parent
+ FROM moz_bookmarks
+ WHERE fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)`
+ );
+ stmt.params.page_url = aURI.spec;
+ try {
+ while (stmt.executeStep() && !isBookmarked) {
+ if (this._tagFolders[stmt.row.parent]) {
+ // This is a tag entry.
+ itemIds.push(stmt.row.id);
+ }
+ else {
+ // This is a real bookmark, so the bookmarked URI is not an orphan.
+ isBookmarked = true;
+ }
+ }
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ return isBookmarked ? [] : itemIds;
+ },
+
+ // nsINavBookmarkObserver
+ onItemAdded: function TS_onItemAdded(aItemId, aFolderId, aIndex, aItemType,
+ aURI, aTitle) {
+ // Nothing to do if this is not a tag.
+ if (aFolderId != PlacesUtils.tagsFolderId ||
+ aItemType != PlacesUtils.bookmarks.TYPE_FOLDER)
+ return;
+
+ this._tagFolders[aItemId] = aTitle;
+ },
+
+ onItemRemoved: function TS_onItemRemoved(aItemId, aFolderId, aIndex,
+ aItemType, aURI, aGuid, aParentGuid,
+ aSource) {
+ // Item is a tag folder.
+ if (aFolderId == PlacesUtils.tagsFolderId && this._tagFolders[aItemId]) {
+ delete this._tagFolders[aItemId];
+ }
+ // Item is a bookmark that was removed from a non-tag folder.
+ else if (aURI && !this._tagFolders[aFolderId]) {
+ // If the only bookmark items now associated with the bookmark's URI are
+ // contained in tag folders, the URI is no longer properly bookmarked, so
+ // untag it.
+ let itemIds = this._getTaggedItemIdsIfUnbookmarkedURI(aURI);
+ for (let i = 0; i < itemIds.length; i++) {
+ try {
+ PlacesUtils.bookmarks.removeItem(itemIds[i], aSource);
+ } catch (ex) {}
+ }
+ }
+ // Item is a tag entry. If this was the last entry for this tag, remove it.
+ else if (aURI && this._tagFolders[aFolderId]) {
+ this._removeTagIfEmpty(aFolderId, aSource);
+ }
+ },
+
+ onItemChanged: function TS_onItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty, aNewValue,
+ aLastModified, aItemType) {
+ if (aProperty == "title" && this._tagFolders[aItemId])
+ this._tagFolders[aItemId] = aNewValue;
+ },
+
+ onItemMoved: function TS_onItemMoved(aItemId, aOldParent, aOldIndex,
+ aNewParent, aNewIndex, aItemType) {
+ if (this._tagFolders[aItemId] && PlacesUtils.tagsFolderId == aOldParent &&
+ PlacesUtils.tagsFolderId != aNewParent)
+ delete this._tagFolders[aItemId];
+ },
+
+ onItemVisited: function () {},
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+
+ // nsISupports
+
+ classID: Components.ID("{bbc23860-2553-479d-8b78-94d9038334f7}"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(TaggingService),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsITaggingService
+ , Ci.nsINavBookmarkObserver
+ , Ci.nsIObserver
+ ])
+};
+
+
+function TagAutoCompleteResult(searchString, searchResult,
+ defaultIndex, errorDescription,
+ results, comments) {
+ this._searchString = searchString;
+ this._searchResult = searchResult;
+ this._defaultIndex = defaultIndex;
+ this._errorDescription = errorDescription;
+ this._results = results;
+ this._comments = comments;
+}
+
+TagAutoCompleteResult.prototype = {
+
+ /**
+ * The original search string
+ */
+ get searchString() {
+ return this._searchString;
+ },
+
+ /**
+ * The result code of this result object, either:
+ * RESULT_IGNORED (invalid searchString)
+ * RESULT_FAILURE (failure)
+ * RESULT_NOMATCH (no matches found)
+ * RESULT_SUCCESS (matches found)
+ */
+ get searchResult() {
+ return this._searchResult;
+ },
+
+ /**
+ * Index of the default item that should be entered if none is selected
+ */
+ get defaultIndex() {
+ return this._defaultIndex;
+ },
+
+ /**
+ * A string describing the cause of a search failure
+ */
+ get errorDescription() {
+ return this._errorDescription;
+ },
+
+ /**
+ * The number of matches
+ */
+ get matchCount() {
+ return this._results.length;
+ },
+
+ get typeAheadResult() {
+ return false;
+ },
+
+ /**
+ * Get the value of the result at the given index
+ */
+ getValueAt: function PTACR_getValueAt(index) {
+ return this._results[index];
+ },
+
+ getLabelAt: function PTACR_getLabelAt(index) {
+ return this.getValueAt(index);
+ },
+
+ /**
+ * Get the comment of the result at the given index
+ */
+ getCommentAt: function PTACR_getCommentAt(index) {
+ return this._comments[index];
+ },
+
+ /**
+ * Get the style hint for the result at the given index
+ */
+ getStyleAt: function PTACR_getStyleAt(index) {
+ if (!this._comments[index])
+ return null; // not a category label, so no special styling
+
+ if (index == 0)
+ return "suggestfirst"; // category label on first line of results
+
+ return "suggesthint"; // category label on any other line of results
+ },
+
+ /**
+ * Get the image for the result at the given index
+ */
+ getImageAt: function PTACR_getImageAt(index) {
+ return null;
+ },
+
+ /**
+ * Get the image for the result at the given index
+ */
+ getFinalCompleteValueAt: function PTACR_getFinalCompleteValueAt(index) {
+ return this.getValueAt(index);
+ },
+
+ /**
+ * Remove the value at the given index from the autocomplete results.
+ * If removeFromDb is set to true, the value should be removed from
+ * persistent storage as well.
+ */
+ removeValueAt: function PTACR_removeValueAt(index, removeFromDb) {
+ this._results.splice(index, 1);
+ this._comments.splice(index, 1);
+ },
+
+ // nsISupports
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteResult
+ ])
+};
+
+// Implements nsIAutoCompleteSearch
+function TagAutoCompleteSearch() {
+ XPCOMUtils.defineLazyServiceGetter(this, "tagging",
+ "@mozilla.org/browser/tagging-service;1",
+ "nsITaggingService");
+}
+
+TagAutoCompleteSearch.prototype = {
+ _stopped : false,
+
+ /*
+ * Search for a given string and notify a listener (either synchronously
+ * or asynchronously) of the result
+ *
+ * @param searchString - The string to search for
+ * @param searchParam - An extra parameter
+ * @param previousResult - A previous result to use for faster searching
+ * @param listener - A listener to notify when the search is complete
+ */
+ startSearch: function PTACS_startSearch(searchString, searchParam, result, listener) {
+ var searchResults = this.tagging.allTags;
+ var results = [];
+ var comments = [];
+ this._stopped = false;
+
+ // only search on characters for the last tag
+ var index = Math.max(searchString.lastIndexOf(","),
+ searchString.lastIndexOf(";"));
+ var before = '';
+ if (index != -1) {
+ before = searchString.slice(0, index+1);
+ searchString = searchString.slice(index+1);
+ // skip past whitespace
+ var m = searchString.match(/\s+/);
+ if (m) {
+ before += m[0];
+ searchString = searchString.slice(m[0].length);
+ }
+ }
+
+ if (!searchString.length) {
+ var newResult = new TagAutoCompleteResult(searchString,
+ Ci.nsIAutoCompleteResult.RESULT_NOMATCH, 0, "", results, comments);
+ listener.onSearchResult(self, newResult);
+ return;
+ }
+
+ var self = this;
+ // generator: if yields true, not done
+ function* doSearch() {
+ var i = 0;
+ while (i < searchResults.length) {
+ if (self._stopped)
+ yield false;
+ // for each match, prepend what the user has typed so far
+ if (searchResults[i].toLowerCase()
+ .indexOf(searchString.toLowerCase()) == 0 &&
+ !comments.includes(searchResults[i])) {
+ results.push(before + searchResults[i]);
+ comments.push(searchResults[i]);
+ }
+
+ ++i;
+
+ /* TODO: bug 481451
+ * For each yield we pass a new result to the autocomplete
+ * listener. The listener appends instead of replacing previous results,
+ * causing invalid matchCount values.
+ *
+ * As a workaround, all tags are searched through in a single batch,
+ * making this synchronous until the above issue is fixed.
+ */
+
+ /*
+ // 100 loops per yield
+ if ((i % 100) == 0) {
+ var newResult = new TagAutoCompleteResult(searchString,
+ Ci.nsIAutoCompleteResult.RESULT_SUCCESS_ONGOING, 0, "", results, comments);
+ listener.onSearchResult(self, newResult);
+ yield true;
+ }
+ */
+ }
+
+ let searchResult = results.length > 0 ?
+ Ci.nsIAutoCompleteResult.RESULT_SUCCESS :
+ Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
+ var newResult = new TagAutoCompleteResult(searchString, searchResult, 0,
+ "", results, comments);
+ listener.onSearchResult(self, newResult);
+ yield false;
+ }
+
+ // chunk the search results via the generator
+ var gen = doSearch();
+ while (gen.next().value);
+ },
+
+ /**
+ * Stop an asynchronous search that is in progress
+ */
+ stopSearch: function PTACS_stopSearch() {
+ this._stopped = true;
+ },
+
+ // nsISupports
+
+ classID: Components.ID("{1dcc23b0-d4cb-11dc-9ad6-479d56d89593}"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(TagAutoCompleteSearch),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteSearch
+ ])
+};
+
+var component = [TaggingService, TagAutoCompleteSearch];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
diff --git a/components/places/toolkitplaces.manifest b/components/places/toolkitplaces.manifest
new file mode 100644
index 000000000..cd9665200
--- /dev/null
+++ b/components/places/toolkitplaces.manifest
@@ -0,0 +1,32 @@
+# nsLivemarkService.js
+component {dca61eb5-c7cd-4df1-b0fb-d0722baba251} nsLivemarkService.js
+contract @mozilla.org/browser/livemark-service;2 {dca61eb5-c7cd-4df1-b0fb-d0722baba251}
+
+# nsTaggingService.js
+component {bbc23860-2553-479d-8b78-94d9038334f7} nsTaggingService.js
+contract @mozilla.org/browser/tagging-service;1 {bbc23860-2553-479d-8b78-94d9038334f7}
+component {1dcc23b0-d4cb-11dc-9ad6-479d56d89593} nsTaggingService.js
+contract @mozilla.org/autocomplete/search;1?name=places-tag-autocomplete {1dcc23b0-d4cb-11dc-9ad6-479d56d89593}
+
+# nsPlacesExpiration.js
+component {705a423f-2f69-42f3-b9fe-1517e0dee56f} nsPlacesExpiration.js
+contract @mozilla.org/places/expiration;1 {705a423f-2f69-42f3-b9fe-1517e0dee56f}
+category history-observers nsPlacesExpiration @mozilla.org/places/expiration;1
+
+# PlacesCategoriesStarter.js
+component {803938d5-e26d-4453-bf46-ad4b26e41114} PlacesCategoriesStarter.js
+contract @mozilla.org/places/categoriesStarter;1 {803938d5-e26d-4453-bf46-ad4b26e41114}
+category idle-daily PlacesCategoriesStarter @mozilla.org/places/categoriesStarter;1
+category bookmark-observers PlacesCategoriesStarter @mozilla.org/places/categoriesStarter;1
+
+# ColorAnalyzer.js
+component {d056186c-28a0-494e-aacc-9e433772b143} ColorAnalyzer.js
+contract @mozilla.org/places/colorAnalyzer;1 {d056186c-28a0-494e-aacc-9e433772b143}
+
+# UnifiedComplete.js
+component {f964a319-397a-4d21-8be6-5cdd1ee3e3ae} UnifiedComplete.js
+contract @mozilla.org/autocomplete/search;1?name=unifiedcomplete {f964a319-397a-4d21-8be6-5cdd1ee3e3ae}
+
+# PageIconProtocolHandler.js
+component {60a1f7c6-4ff9-4a42-84d3-5a185faa6f32} PageIconProtocolHandler.js
+contract @mozilla.org/network/protocol;1?name=page-icon {60a1f7c6-4ff9-4a42-84d3-5a185faa6f32}
diff --git a/components/pluginproblem/content/pluginProblem.xml b/components/pluginproblem/content/pluginProblem.xml
new file mode 100644
index 000000000..d890be900
--- /dev/null
+++ b/components/pluginproblem/content/pluginProblem.xml
@@ -0,0 +1,87 @@
+<?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 % pluginproblemDTD SYSTEM "chrome://pluginproblem/locale/pluginproblem.dtd">
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+ %pluginproblemDTD;
+ %globalDTD;
+ %brandDTD;
+]>
+
+<bindings id="pluginBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<binding id="pluginProblem" inheritstyle="false" chromeOnlyContent="true" bindToUntrustedContent="true">
+ <resources>
+ <stylesheet src="chrome://pluginproblem/content/pluginProblemContent.css"/>
+ <stylesheet src="chrome://mozapps/skin/plugins/pluginProblem.css"/>
+ </resources>
+
+ <content>
+ <html:div class="mainBox" anonid="main" chromedir="&locale.dir;">
+ <html:div class="hoverBox">
+ <html:label>
+ <html:button class="icon" anonid="icon"/>
+ <html:div class="msg msgVulnerabilityStatus" anonid="vulnerabilityStatus"><!-- set at runtime --></html:div>
+ <html:div class="msg msgTapToPlay">&tapToPlayPlugin;</html:div>
+ <html:div class="msg msgClickToPlay" anonid="clickToPlay">&clickToActivatePlugin;</html:div>
+ </html:label>
+
+ <html:div class="msg msgBlocked">&blockedPlugin.label;</html:div>
+ <html:div class="msg msgCrashed">
+ <html:div class="msgCrashedText" anonid="crashedText"><!-- set at runtime --></html:div>
+ <!-- link href set at runtime -->
+ <html:div class="msgReload">&reloadPlugin.pre;<html:a class="reloadLink" anonid="reloadLink" href="">&reloadPlugin.middle;</html:a>&reloadPlugin.post;</html:div>
+ </html:div>
+
+ <html:div class="msg msgManagePlugins"><html:a class="action-link" anonid="managePluginsLink" href="">&managePlugins;</html:a></html:div>
+ <html:div class="submitStatus" anonid="submitStatus">
+ <html:div class="msg msgPleaseSubmit" anonid="pleaseSubmit">
+ <html:textarea class="submitComment"
+ anonid="submitComment"
+ placeholder="&report.comment;"/>
+ <html:div class="submitURLOptInBox">
+ <html:label><html:input class="submitURLOptIn" anonid="submitURLOptIn" type="checkbox"/> &report.pageURL;</html:label>
+ </html:div>
+ <html:div class="submitButtonBox">
+ <html:span class="helpIcon" anonid="helpIcon" role="link"/>
+ <html:input class="submitButton" type="button"
+ anonid="submitButton"
+ value="&report.please;"/>
+ </html:div>
+ </html:div>
+ <html:div class="msg msgSubmitting">&report.submitting;<html:span class="throbber"> </html:span></html:div>
+ <html:div class="msg msgSubmitted">&report.submitted;</html:div>
+ <html:div class="msg msgNotSubmitted">&report.disabled;</html:div>
+ <html:div class="msg msgSubmitFailed">&report.failed;</html:div>
+ <html:div class="msg msgNoCrashReport">&report.unavailable;</html:div>
+ </html:div>
+ <html:div class="msg msgCheckForUpdates"><html:a class="action-link" anonid="checkForUpdatesLink" href="">&checkForUpdates;</html:a></html:div>
+ </html:div>
+ <html:button class="closeIcon" anonid="closeIcon" title="&hidePluginBtn.label;"/>
+ </html:div>
+ <html:div style="display:none;"><children/></html:div>
+ </content>
+ <implementation>
+ <constructor>
+ // Notify browser-plugins.js that we were attached, on a delay because
+ // this binding doesn't complete layout until the constructor
+ // completes.
+ this.dispatchEvent(new CustomEvent("PluginBindingAttached"));
+ </constructor>
+ </implementation>
+</binding>
+
+<binding id="replacement" extends="chrome://pluginproblem/content/pluginProblem.xml#pluginProblem" inheritstyle="false" chromeOnlyContent="true" bindToUntrustedContent="true">
+ <implementation>
+ <constructor>
+ this.dispatchEvent(new CustomEvent("PluginPlaceholderReplaced"));
+ </constructor>
+ </implementation>
+</binding>
+
+</bindings>
diff --git a/components/pluginproblem/content/pluginProblemBinding.css b/components/pluginproblem/content/pluginProblemBinding.css
new file mode 100644
index 000000000..a545e3eba
--- /dev/null
+++ b/components/pluginproblem/content/pluginProblemBinding.css
@@ -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/. */
+
+@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */
+
+embed:-moz-handler-blocked,
+embed:-moz-handler-crashed,
+embed:-moz-handler-clicktoplay,
+embed:-moz-handler-vulnerable-updatable,
+embed:-moz-handler-vulnerable-no-update,
+applet:-moz-handler-blocked,
+applet:-moz-handler-crashed,
+applet:-moz-handler-clicktoplay,
+applet:-moz-handler-vulnerable-updatable,
+applet:-moz-handler-vulnerable-no-update,
+object:-moz-handler-blocked,
+object:-moz-handler-crashed,
+object:-moz-handler-clicktoplay,
+object:-moz-handler-vulnerable-updatable,
+object:-moz-handler-vulnerable-no-update {
+%ifdef MC_PALEMOON
+ /* Initialize the overlay with visibility:hidden to prevent flickering if
+ * the plugin is too small to show the overlay */
+ visibility: hidden;
+%endif
+ display: inline-block;
+ overflow: hidden;
+ opacity: 1 !important;
+ -moz-binding: url('chrome://pluginproblem/content/pluginProblem.xml#pluginProblem') !important;
+}
diff --git a/components/pluginproblem/content/pluginProblemContent.css b/components/pluginproblem/content/pluginProblemContent.css
new file mode 100644
index 000000000..cf8755635
--- /dev/null
+++ b/components/pluginproblem/content/pluginProblemContent.css
@@ -0,0 +1,122 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace html url(http://www.w3.org/1999/xhtml);
+
+/* Do not change this without also changing the appropriate line in
+ * browser-plugins.js (near where this file is mentioned). */
+html|object:not([width]), html|object[width=""],
+html|embed:not([width]), html|embed[width=""],
+html|applet:not([width]), html|applet[width=""] {
+ width: 240px;
+}
+
+/* Do not change this without also changing the appropriate line in
+ * browser-plugins.js (near where this file is mentioned). */
+html|object:not([height]), html|object[height=""],
+html|embed:not([height]), html|embed[height=""],
+html|applet:not([height]), html|applet[height=""] {
+ height: 200px;
+}
+
+a .mainBox,
+:-moz-handler-clicktoplay .mainBox,
+:-moz-handler-vulnerable-updatable .mainBox,
+:-moz-handler-vulnerable-no-update .mainBox,
+:-moz-handler-blocked .mainBox {
+ -moz-user-focus: normal;
+}
+a .mainBox:focus,
+:-moz-handler-clicktoplay .mainBox:focus,
+:-moz-handler-vulnerable-updatable .mainBox:focus,
+:-moz-handler-vulnerable-no-update .mainBox:focus,
+:-moz-handler-blocked .mainBox:focus {
+ outline: 1px dotted;
+}
+
+.mainBox {
+ width: inherit;
+ height: inherit;
+ overflow: hidden;
+ direction: ltr;
+ unicode-bidi: embed;
+ /* used to block inherited properties */
+ text-transform: none;
+ text-indent: 0;
+ cursor: initial;
+ white-space: initial;
+ word-spacing: initial;
+ letter-spacing: initial;
+ line-height: initial;
+}
+
+%ifndef MC_PALEMOON
+/* Initialize the overlay with visibility:hidden to prevent flickering if
+* the plugin is too small to show the overlay */
+.mainBox > .hoverBox,
+.mainBox > .closeIcon {
+ visibility: hidden;
+}
+
+.visible > .hoverBox,
+.visible > .closeIcon {
+ visibility: visible;
+}
+%endif
+
+.mainBox[chromedir="rtl"] {
+ direction: rtl;
+}
+
+a .hoverBox,
+:-moz-handler-clicktoplay .hoverBox,
+:-moz-handler-vulnerable-updatable .hoverBox,
+:-moz-handler-vulnerable-no-update .hoverBox {
+ cursor: pointer;
+}
+
+.hoverBox > label {
+ cursor: inherit;
+}
+.icon {
+ cursor: inherit;
+}
+
+.msg {
+ display: none;
+}
+
+a .msgClickToPlay,
+a .msgTapToPlay,
+:-moz-handler-clicktoplay .msgClickToPlay,
+:-moz-handler-vulnerable-updatable .msgVulnerabilityStatus,
+:-moz-handler-vulnerable-updatable .msgCheckForUpdates,
+:-moz-handler-vulnerable-updatable .msgClickToPlay,
+:-moz-handler-vulnerable-no-update .msgVulnerabilityStatus,
+:-moz-handler-vulnerable-no-update .msgClickToPlay,
+:-moz-handler-clicktoplay .msgTapToPlay,
+:-moz-handler-blocked .msgBlocked,
+:-moz-handler-crashed .msgCrashed {
+ display: block;
+ position: relative;
+ left: 0;
+ top: 0;
+ z-index: 9999;
+}
+
+.submitStatus[status] {
+ display: -moz-box;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ height: 160px;
+}
+
+.submitStatus[status="noReport"] .msgNoCrashReport,
+.submitStatus[status="please"] .msgPleaseSubmit,
+.submitStatus[status="noSubmit"] .msgNotSubmitted,
+.submitStatus[status="submitting"] .msgSubmitting,
+.submitStatus[status="success"] .msgSubmitted,
+.submitStatus[status="failed"] .msgSubmitFailed {
+ display: block;
+}
diff --git a/components/pluginproblem/content/pluginReplaceBinding.css b/components/pluginproblem/content/pluginReplaceBinding.css
new file mode 100644
index 000000000..405a3a4b7
--- /dev/null
+++ b/components/pluginproblem/content/pluginReplaceBinding.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.w3.org/1999/xhtml); /* set default namespace to HTML */
+
+a[href='https://get.adobe.com/flashplayer/'],
+a[href='http://get.adobe.com/flashplayer/'],
+a[href='https://get.adobe.com/flashplayer'],
+a[href='http://get.adobe.com/flashplayer'],
+a[href='https://www.adobe.com/go/getflash/'],
+a[href='http://www.adobe.com/go/getflash/'] {
+ display: inline-block;
+ overflow: hidden;
+ opacity: 1 !important;
+ -moz-binding: url('chrome://pluginproblem/content/pluginProblem.xml#replacement') !important;
+}
diff --git a/components/pluginproblem/jar.mn b/components/pluginproblem/jar.mn
new file mode 100644
index 000000000..c027793de
--- /dev/null
+++ b/components/pluginproblem/jar.mn
@@ -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/.
+
+toolkit.jar:
+% content pluginproblem %pluginproblem/ contentaccessible=yes
+ pluginproblem/pluginProblem.xml (content/pluginProblem.xml)
+* pluginproblem/pluginProblemContent.css (content/pluginProblemContent.css)
+* pluginproblem/pluginProblemBinding.css (content/pluginProblemBinding.css)
+ pluginproblem/pluginReplaceBinding.css (content/pluginReplaceBinding.css)
diff --git a/components/pluginproblem/locale/pluginproblem.dtd b/components/pluginproblem/locale/pluginproblem.dtd
new file mode 100644
index 000000000..841655127
--- /dev/null
+++ b/components/pluginproblem/locale/pluginproblem.dtd
@@ -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/. -->
+
+<!-- LOCALIZATION NOTE (unsupportedPlatform.pre): Mobile only. Flash (the only plugin available on mobile)
+ is not supported on some devices. Include a trailing space as needed. -->
+<!ENTITY unsupportedPlatform.pre "We're very sorry, but &brandShortName; can't play Flash on this device. ">
+<!-- LOCALIZATION NOTE (unsupportedPlatform.learnMore): Mobile only. This text is used to link to a SUMO page explaining why Flash is not
+ supported on this device. Use the unicode ellipsis char, \u2026, or use "..." if \u2026 doesn't suit traditions in your locale. -->
+<!ENTITY unsupportedPlatform.learnMore "Learn More…">
+<!-- LOCALIZATION NOTE (unsupportedPlatform.post): Mobile only. Include text here if needed for your locale. -->
+<!ENTITY unsupportedPlatform.post "">
+
+<!ENTITY missingPlugin "A plugin is needed to display this content.">
+<!-- LOCALIZATION NOTE (tapToPlayPlugin): Mobile (used for touch interfaces) only has one type of plugin possible. -->
+<!ENTITY tapToPlayPlugin "Tap here to activate plugin.">
+<!ENTITY clickToActivatePlugin "Activate plugin.">
+<!ENTITY checkForUpdates "Check for updates…">
+<!ENTITY disabledPlugin "This plugin is disabled.">
+<!ENTITY blockedPlugin.label "This plugin has been blocked for your protection.">
+<!ENTITY hidePluginBtn.label "Hide plugin">
+<!ENTITY managePlugins "Manage plugins…">
+
+<!-- LOCALIZATION NOTE (reloadPlugin.pre): include a trailing space as needed -->
+<!-- LOCALIZATION NOTE (reloadPlugin.middle): avoid leading/trailing spaces, this text is a link -->
+<!-- LOCALIZATION NOTE (reloadPlugin.post): include a starting space as needed -->
+<!ENTITY reloadPlugin.pre "">
+<!ENTITY reloadPlugin.middle "Reload the page">
+<!ENTITY reloadPlugin.post " to try again.">
+<!-- LOCALIZATION NOTE (report.please): This and the other report.* strings should be as short as possible, ideally 2-3 words. -->
+<!ENTITY report.please "Send crash report">
+<!ENTITY report.submitting "Sending report…">
+<!ENTITY report.submitted "Crash report sent.">
+<!ENTITY report.disabled "Crash reporting disabled.">
+<!ENTITY report.failed "Submission failed.">
+<!ENTITY report.unavailable "No report available.">
+<!ENTITY report.comment "Add a comment (comments are publicly visible)">
+<!ENTITY report.pageURL "Include the page’s URL">
+
+<!ENTITY plugin.file "File">
+<!ENTITY plugin.mimeTypes "MIME Types">
+<!ENTITY plugin.flashProtectedMode.label "Enable Adobe Flash protected mode">
diff --git a/components/pluginproblem/moz.build b/components/pluginproblem/moz.build
new file mode 100644
index 000000000..6cbc9e09c
--- /dev/null
+++ b/components/pluginproblem/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'pluginGlue.manifest',
+]
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/pluginproblem/pluginGlue.manifest b/components/pluginproblem/pluginGlue.manifest
new file mode 100644
index 000000000..96ebc9bd4
--- /dev/null
+++ b/components/pluginproblem/pluginGlue.manifest
@@ -0,0 +1 @@
+category agent-style-sheets pluginGlue-pluginProblem chrome://pluginproblem/content/pluginProblemBinding.css
diff --git a/components/preferences/content/changemp.js b/components/preferences/content/changemp.js
new file mode 100644
index 000000000..71664b3e1
--- /dev/null
+++ b/components/preferences/content/changemp.js
@@ -0,0 +1,243 @@
+// -*- tab-width: 2; 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 nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1";
+const nsIPK11TokenDB = Components.interfaces.nsIPK11TokenDB;
+const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock;
+const nsPKCS11ModuleDB = "@mozilla.org/security/pkcs11moduledb;1";
+const nsIPKCS11ModuleDB = Components.interfaces.nsIPKCS11ModuleDB;
+const nsIPKCS11Slot = Components.interfaces.nsIPKCS11Slot;
+const nsIPK11Token = Components.interfaces.nsIPK11Token;
+
+
+var params;
+var tokenName="";
+var pw1;
+
+function init()
+{
+ pw1 = document.getElementById("pw1");
+
+ process();
+}
+
+
+function process()
+{
+ var secmoddb = Components.classes[nsPKCS11ModuleDB].getService(nsIPKCS11ModuleDB);
+ var bundle = document.getElementById("bundlePreferences");
+
+ // If the token is unitialized, don't use the old password box.
+ // Otherwise, do.
+
+ var slot = secmoddb.findSlotByName(tokenName);
+ if (slot) {
+ var oldpwbox = document.getElementById("oldpw");
+ var msgBox = document.getElementById("message");
+ var status = slot.status;
+ if (status == nsIPKCS11Slot.SLOT_UNINITIALIZED
+ || status == nsIPKCS11Slot.SLOT_READY) {
+
+ oldpwbox.setAttribute("hidden", "true");
+ msgBox.setAttribute("value", bundle.getString("password_not_set"));
+ msgBox.setAttribute("hidden", "false");
+
+ if (status == nsIPKCS11Slot.SLOT_READY) {
+ oldpwbox.setAttribute("inited", "empty");
+ } else {
+ oldpwbox.setAttribute("inited", "true");
+ }
+
+ // Select first password field
+ document.getElementById('pw1').focus();
+
+ } else {
+ // Select old password field
+ oldpwbox.setAttribute("hidden", "false");
+ msgBox.setAttribute("hidden", "true");
+ oldpwbox.setAttribute("inited", "false");
+ oldpwbox.focus();
+ }
+ }
+
+ if (params) {
+ // Return value 0 means "canceled"
+ params.SetInt(1, 0);
+ }
+
+ checkPasswords();
+}
+
+function setPassword()
+{
+ var pk11db = Components.classes[nsPK11TokenDB].getService(nsIPK11TokenDB);
+ var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ var token = pk11db.findTokenByName(tokenName);
+ dump("*** TOKEN!!!! (name = |" + token + "|\n");
+
+ var oldpwbox = document.getElementById("oldpw");
+ var initpw = oldpwbox.getAttribute("inited");
+ var bundle = document.getElementById("bundlePreferences");
+
+ var success = false;
+
+ if (initpw == "false" || initpw == "empty") {
+ try {
+ var oldpw = "";
+ var passok = 0;
+
+ if (initpw == "empty") {
+ passok = 1;
+ } else {
+ oldpw = oldpwbox.value;
+ passok = token.checkPassword(oldpw);
+ }
+
+ if (passok) {
+ if (initpw == "empty" && pw1.value == "") {
+ // This makes no sense that we arrive here,
+ // we reached a case that should have been prevented by checkPasswords.
+ } else {
+ if (pw1.value == "") {
+ var secmoddb = Components.classes[nsPKCS11ModuleDB].getService(nsIPKCS11ModuleDB);
+ if (secmoddb.isFIPSEnabled) {
+ // empty passwords are not allowed in FIPS mode
+ promptService.alert(window,
+ bundle.getString("pw_change_failed_title"),
+ bundle.getString("pw_change2empty_in_fips_mode"));
+ passok = 0;
+ }
+ }
+ if (passok) {
+ token.changePassword(oldpw, pw1.value);
+ if (pw1.value == "") {
+ promptService.alert(window,
+ bundle.getString("pw_change_success_title"),
+ bundle.getString("pw_erased_ok")
+ + " " + bundle.getString("pw_empty_warning"));
+ } else {
+ promptService.alert(window,
+ bundle.getString("pw_change_success_title"),
+ bundle.getString("pw_change_ok"));
+ }
+ success = true;
+ }
+ }
+ } else {
+ oldpwbox.focus();
+ oldpwbox.setAttribute("value", "");
+ promptService.alert(window,
+ bundle.getString("pw_change_failed_title"),
+ bundle.getString("incorrect_pw"));
+ }
+ } catch (e) {
+ promptService.alert(window,
+ bundle.getString("pw_change_failed_title"),
+ bundle.getString("failed_pw_change"));
+ }
+ } else {
+ token.initPassword(pw1.value);
+ if (pw1.value == "") {
+ promptService.alert(window,
+ bundle.getString("pw_change_success_title"),
+ bundle.getString("pw_not_wanted")
+ + " " + bundle.getString("pw_empty_warning"));
+ }
+ success = true;
+ }
+
+ // Terminate dialog
+ if (success)
+ window.close();
+}
+
+function setPasswordStrength()
+{
+// Here is how we weigh the quality of the password
+// number of characters
+// numbers
+// non-alpha-numeric chars
+// upper and lower case characters
+
+ var pw=document.getElementById('pw1').value;
+
+// length of the password
+ var pwlength=(pw.length);
+ if (pwlength>10)
+ pwlength=10;
+
+
+// use of numbers in the password
+ var numnumeric = pw.replace (/[0-9]/g, "");
+ var numeric=(pw.length - numnumeric.length);
+ if (numeric>3)
+ numeric=3;
+
+// use of symbols in the password
+ var symbols = pw.replace (/\W/g, "");
+ var numsymbols=(pw.length - symbols.length);
+ if (numsymbols>3)
+ numsymbols=3;
+
+// use of uppercase in the password
+ var numupper = pw.replace (/[A-Z]/g, "");
+ var upper=(pw.length - numupper.length);
+ if (upper>3)
+ upper=3;
+
+
+ var pwstrength=((pwlength*5)-20) + (numeric*10) + (numsymbols*15) + (upper*10);
+
+ // make sure we're give a value between 0 and 100
+ if ( pwstrength < 0 ) {
+ pwstrength = 0;
+ }
+
+ if ( pwstrength > 100 ) {
+ pwstrength = 100;
+ }
+
+ var mymeter=document.getElementById('pwmeter');
+ mymeter.value = pwstrength;
+
+ return;
+}
+
+function checkPasswords()
+{
+ var pw1=document.getElementById('pw1').value;
+ var pw2=document.getElementById('pw2').value;
+ var ok=document.documentElement.getButton("accept");
+
+ var oldpwbox = document.getElementById("oldpw");
+ if (oldpwbox) {
+ var initpw = oldpwbox.getAttribute("inited");
+
+ if (initpw == "empty" && pw1 == "") {
+ // The token has already been initialized, therefore this dialog
+ // was called with the intention to change the password.
+ // The token currently uses an empty password.
+ // We will not allow changing the password from empty to empty.
+ ok.setAttribute("disabled", "true");
+ return;
+ }
+ }
+
+ // Never accept short passwords < 8 chars
+ if (pw1.length < 8) {
+ ok.setAttribute("disabled", "true");
+ return;
+ }
+
+ if (pw1 == pw2) {
+ ok.setAttribute("disabled", "false");
+ } else
+ {
+ ok.setAttribute("disabled", "true");
+ }
+
+}
diff --git a/components/preferences/content/changemp.xul b/components/preferences/content/changemp.xul
new file mode 100644
index 000000000..b316fa42b
--- /dev/null
+++ b/components/preferences/content/changemp.xul
@@ -0,0 +1,68 @@
+<?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 [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+<!ENTITY % changempDTD SYSTEM "chrome://mozapps/locale/preferences/changemp.dtd" >
+%brandDTD;
+%changempDTD;
+]>
+
+<dialog id="changemp" title="&setPassword.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 40em;"
+ ondialogaccept="setPassword();"
+ onload="init()">
+
+ <script type="application/javascript" src="chrome://mozapps/content/preferences/changemp.js"/>
+
+ <stringbundle id="bundlePreferences" src="chrome://mozapps/locale/preferences/preferences.properties"/>
+
+ <description control="pw1">&masterPasswordDescription.label;</description>
+
+ <groupbox>
+ <grid>
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row>
+ <label control="oldpw">&setPassword.oldPassword.label;</label>
+ <textbox id="oldpw" type="password" size="18"/>
+ <!-- This textbox is inserted as a workaround to the fact that making the 'type'
+ & 'disabled' property of the 'oldpw' textbox toggle between ['password' &
+ 'false'] and ['text' & 'true'] - as would be necessary if the menu has more
+ than one tokens, some initialized and some not - does not work properly. So,
+ either the textbox 'oldpw' or the textbox 'message' would be displayed,
+ depending on the state of the token selected
+ -->
+ <textbox id="message" disabled="true" />
+ </row>
+ <row>
+ <label control="pw1">&setPassword.newPassword.label;</label>
+ <textbox id="pw1" type="password" size="18"
+ oninput="setPasswordStrength(); checkPasswords();"/>
+ </row>
+ <row>
+ <label control="pw2">&setPassword.reenterPassword.label;</label>
+ <textbox id="pw2" type="password" size="18"
+ oninput="checkPasswords();"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&setPassword.meter.label;"/>
+ <progressmeter id="pwmeter" mode="determined" value="0"/>
+ </groupbox>
+
+ <description control="pw2" class="header">&masterPasswordWarning.label;</description>
+
+</dialog>
diff --git a/components/preferences/content/fontbuilder.js b/components/preferences/content/fontbuilder.js
new file mode 100644
index 000000000..a76ce6b25
--- /dev/null
+++ b/components/preferences/content/fontbuilder.js
@@ -0,0 +1,126 @@
+// -*- 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 FontBuilder = {
+ _enumerator: null,
+ get enumerator ()
+ {
+ if (!this._enumerator) {
+ this._enumerator = Components.classes["@mozilla.org/gfx/fontenumerator;1"]
+ .createInstance(Components.interfaces.nsIFontEnumerator);
+ }
+ return this._enumerator;
+ },
+
+ _allFonts: null,
+ _langGroupSupported: false,
+ buildFontList: function (aLanguage, aFontType, aMenuList)
+ {
+ // Reset the list
+ while (aMenuList.hasChildNodes())
+ aMenuList.removeChild(aMenuList.firstChild);
+
+ var defaultFont = null;
+ // Load Font Lists
+ var fonts = this.enumerator.EnumerateFonts(aLanguage, aFontType, { } );
+ if (fonts.length > 0)
+ defaultFont = this.enumerator.getDefaultFont(aLanguage, aFontType);
+ else {
+ fonts = this.enumerator.EnumerateFonts(aLanguage, "", { });
+ if (fonts.length > 0)
+ defaultFont = this.enumerator.getDefaultFont(aLanguage, "");
+ }
+
+ if (!this._allFonts)
+ this._allFonts = this.enumerator.EnumerateAllFonts({});
+
+ // Build the UI for the Default Font and Fonts for this CSS type.
+ var popup = document.createElement("menupopup");
+ var separator;
+ if (fonts.length > 0) {
+ if (defaultFont) {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var label = bundlePreferences.getFormattedString("labelDefaultFont", [defaultFont]);
+ var menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label", label);
+ menuitem.setAttribute("value", ""); // Default Font has a blank value
+ popup.appendChild(menuitem);
+
+ separator = document.createElement("menuseparator");
+ popup.appendChild(separator);
+ }
+
+ for (var i = 0; i < fonts.length; ++i) {
+ menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("value", fonts[i]);
+ menuitem.setAttribute("label", fonts[i]);
+ popup.appendChild(menuitem);
+ }
+ }
+
+ // Build the UI for the remaining fonts.
+ if (this._allFonts.length > fonts.length) {
+ this._langGroupSupported = true;
+ // Both lists are sorted, and the Fonts-By-Type list is a subset of the
+ // All-Fonts list, so walk both lists side-by-side, skipping values we've
+ // already created menu items for.
+ var builtItem = separator ? separator.nextSibling : popup.firstChild;
+ var builtItemValue = builtItem ? builtItem.getAttribute("value") : null;
+
+ separator = document.createElement("menuseparator");
+ popup.appendChild(separator);
+
+ for (i = 0; i < this._allFonts.length; ++i) {
+ if (this._allFonts[i] != builtItemValue) {
+ menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("value", this._allFonts[i]);
+ menuitem.setAttribute("label", this._allFonts[i]);
+ popup.appendChild(menuitem);
+ }
+ else {
+ builtItem = builtItem.nextSibling;
+ builtItemValue = builtItem ? builtItem.getAttribute("value") : null;
+ }
+ }
+ }
+ aMenuList.appendChild(popup);
+ },
+
+ readFontSelection(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)
+ let preference = document.getElementById(aElement.getAttribute("preference"));
+ if (preference.value) {
+ let fontItems = aElement.getElementsByAttribute("value", preference.value);
+
+ // There is a setting that actually is in the list. Respect it.
+ if (fontItems.length)
+ return undefined;
+ }
+
+ // The first item will be a reasonable choice only if the font backend
+ // supports language-specific enumaration.
+ let defaultValue = this._langGroupSupported ?
+ aElement.firstChild.firstChild.getAttribute("value") : "";
+ let fontNameList = preference.name.replace(".name.", ".name-list.");
+ let prefFontNameList = document.getElementById(fontNameList);
+ if (!prefFontNameList || !prefFontNameList.value)
+ return defaultValue;
+
+ let fontNames = prefFontNameList.value.split(",");
+
+ for (let i = 0; i < fontNames.length; ++i) {
+ let fontName = this.enumerator.getStandardFamilyName(fontNames[i].trim());
+ let fontItems = aElement.getElementsByAttribute("value", fontName);
+ if (fontItems.length)
+ return fontItems[0].getAttribute("value");
+ }
+ return defaultValue;
+ }
+};
diff --git a/components/preferences/content/removemp.js b/components/preferences/content/removemp.js
new file mode 100644
index 000000000..1f6356eac
--- /dev/null
+++ b/components/preferences/content/removemp.js
@@ -0,0 +1,56 @@
+// -*- 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 gRemovePasswordDialog = {
+ _token : null,
+ _bundle : null,
+ _prompt : null,
+ _okButton : null,
+ _password : null,
+ init: function ()
+ {
+ this._prompt = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ this._bundle = document.getElementById("bundlePreferences");
+
+ this._okButton = document.documentElement.getButton("accept");
+ this._okButton.label = this._bundle.getString("pw_remove_button");
+
+ this._password = document.getElementById("password");
+
+ var pk11db = Components.classes["@mozilla.org/security/pk11tokendb;1"]
+ .getService(Components.interfaces.nsIPK11TokenDB);
+ this._token = pk11db.getInternalKeyToken();
+
+ // Initialize the enabled state of the Remove button by checking the
+ // initial value of the password ("" should be incorrect).
+ this.validateInput();
+ },
+
+ validateInput: function ()
+ {
+ this._okButton.disabled = !this._token.checkPassword(this._password.value);
+ },
+
+ removePassword: function ()
+ {
+ if (this._token.checkPassword(this._password.value)) {
+ this._token.changePassword(this._password.value, "");
+ this._prompt.alert(window,
+ this._bundle.getString("pw_change_success_title"),
+ this._bundle.getString("pw_erased_ok")
+ + " " + this._bundle.getString("pw_empty_warning"));
+ }
+ else {
+ this._password.value = "";
+ this._password.focus();
+ this._prompt.alert(window,
+ this._bundle.getString("pw_change_failed_title"),
+ this._bundle.getString("incorrect_pw"));
+ }
+ },
+};
+
diff --git a/components/preferences/content/removemp.xul b/components/preferences/content/removemp.xul
new file mode 100644
index 000000000..17ab48e39
--- /dev/null
+++ b/components/preferences/content/removemp.xul
@@ -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/. -->
+
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+<!ENTITY % removempDTD SYSTEM "chrome://mozapps/locale/preferences/removemp.dtd" >
+%brandDTD;
+%removempDTD;
+]>
+
+<dialog id="removemp" title="&removePassword.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 35em;"
+ ondialogaccept="gRemovePasswordDialog.removePassword();"
+ onload="gRemovePasswordDialog.init()">
+
+ <script type="application/javascript" src="chrome://mozapps/content/preferences/removemp.js"/>
+
+ <stringbundle id="bundlePreferences" src="chrome://mozapps/locale/preferences/preferences.properties"/>
+
+ <vbox id="warnings">
+ <description>&removeWarning1.label;</description>
+ <description class="header">&removeWarning2.label;</description>
+ </vbox>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <caption label="&removeInfo.label;"/>
+
+ <hbox align="center">
+ <label control="password" value="&setPassword.oldPassword.label;"/>
+ <textbox id="password" type="password"
+ oninput="gRemovePasswordDialog.validateInput();"
+ aria-describedby="warnings"/>
+ </hbox>
+ </groupbox>
+
+ <separator/>
+
+</dialog>
diff --git a/components/preferences/jar.mn b/components/preferences/jar.mn
new file mode 100644
index 000000000..a7dfb958a
--- /dev/null
+++ b/components/preferences/jar.mn
@@ -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/.
+
+toolkit.jar:
+% content mozapps %content/mozapps/
+ content/mozapps/preferences/fontbuilder.js (content/fontbuilder.js)
+ content/mozapps/preferences/changemp.js (content/changemp.js)
+ content/mozapps/preferences/changemp.xul (content/changemp.xul)
+ content/mozapps/preferences/removemp.js (content/removemp.js)
+ content/mozapps/preferences/removemp.xul (content/removemp.xul)
diff --git a/components/preferences/locale/changemp.dtd b/components/preferences/locale/changemp.dtd
new file mode 100644
index 000000000..1b1d5ac55
--- /dev/null
+++ b/components/preferences/locale/changemp.dtd
@@ -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/. -->
+
+<!ENTITY setPassword.title "Change Master Password">
+<!ENTITY setPassword.tokenName.label "Security Device">
+<!ENTITY setPassword.oldPassword.label "Current password:">
+<!ENTITY setPassword.newPassword.label "Enter new password:">
+<!ENTITY setPassword.reenterPassword.label "Re-enter password:">
+<!ENTITY setPassword.meter.label "Password quality meter">
+<!ENTITY setPassword.meter.loading "Loading">
+<!ENTITY masterPasswordDescription.label "A Master Password is used to protect sensitive information like site passwords. If you create a Master Password you will be asked to enter it once per session when &brandShortName; retrieves saved information protected by the password. A master password must be 8 characters or longer; longer is better.">
+<!ENTITY masterPasswordWarning.label "Please make sure you remember the Master Password you have set. If you forget your Master Password, you will be unable to access any of the information protected by it.">
diff --git a/components/preferences/locale/preferences.properties b/components/preferences/locale/preferences.properties
new file mode 100644
index 000000000..53750fcf3
--- /dev/null
+++ b/components/preferences/locale/preferences.properties
@@ -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/.
+
+#### Master Password
+
+password_not_set=(not set)
+failed_pw_change=Unable to change Master Password.
+incorrect_pw=You did not enter the correct current Master Password. Please try again.
+pw_change_ok=Master Password successfully changed.
+pw_erased_ok=You have deleted your Master Password.
+pw_not_wanted=Warning! You have decided not to use a Master Password.
+pw_empty_warning=Your stored web and email passwords, form data, and private keys will not be protected.
+pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
+pw_change_success_title=Password Change Succeeded
+pw_change_failed_title=Password Change Failed
+pw_remove_button=Remove
diff --git a/components/preferences/locale/removemp.dtd b/components/preferences/locale/removemp.dtd
new file mode 100644
index 000000000..24f234032
--- /dev/null
+++ b/components/preferences/locale/removemp.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 removePassword.title "Remove Master Password">
+<!ENTITY removeInfo.label "You must enter your current password to proceed:">
+<!ENTITY removeWarning1.label "Your Master Password is used to protect sensitive information like site passwords.">
+<!ENTITY removeWarning2.label "If you remove your Master Password your information will not be protected if your computer is compromised.">
+<!ENTITY setPassword.oldPassword.label "Current password:">
+
diff --git a/components/preferences/moz.build b/components/preferences/moz.build
new file mode 100644
index 000000000..4bd147384
--- /dev/null
+++ b/components/preferences/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+DEFINES['OS_ARCH'] = CONFIG['OS_ARCH']
+DEFINES['MOZ_WIDGET_TOOLKIT'] = CONFIG['MOZ_WIDGET_TOOLKIT']
+
+XPIDL_SOURCES += [
+ 'public/nsIPrefBranch.idl',
+ 'public/nsIPrefBranch2.idl',
+ 'public/nsIPrefBranchInternal.idl',
+ 'public/nsIPrefLocalizedString.idl',
+ 'public/nsIPrefService.idl',
+ 'public/nsIRelativeFilePref.idl',
+]
+
+EXPORTS.mozilla += ['src/Preferences.h']
+
+SOURCES += [
+ 'src/nsPrefBranch.cpp',
+ 'src/nsPrefsFactory.cpp',
+ 'src/prefapi.cpp',
+ 'src/Preferences.cpp',
+ 'src/prefread.cpp',
+]
+
+XPIDL_MODULE = 'pref'
+FINAL_LIBRARY = 'xul'
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/preferences/public/nsIPrefBranch.idl b/components/preferences/public/nsIPrefBranch.idl
new file mode 100644
index 000000000..900806b42
--- /dev/null
+++ b/components/preferences/public/nsIPrefBranch.idl
@@ -0,0 +1,425 @@
+/* -*- Mode: IDL; 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 nsIObserver;
+
+/**
+ * The nsIPrefBranch interface is used to manipulate the preferences data. This
+ * object may be obtained from the preferences service (nsIPrefService) and
+ * used to get and set default and/or user preferences across the application.
+ *
+ * This object is created with a "root" value which describes the base point in
+ * the preferences "tree" from which this "branch" stems. Preferences are
+ * accessed off of this root by using just the final portion of the preference.
+ * For example, if this object is created with the root "browser.startup.",
+ * the preferences "browser.startup.page", "browser.startup.homepage",
+ * and "browser.startup.homepage_override" can be accessed by simply passing
+ * "page", "homepage", or "homepage_override" to the various Get/Set methods.
+ *
+ * @see nsIPrefService
+ */
+
+[scriptable, uuid(55d25e49-793f-4727-a69f-de8b15f4b985)]
+interface nsIPrefBranch : nsISupports
+{
+
+ /**
+ * Values describing the basic preference types.
+ *
+ * @see getPrefType
+ */
+ const long PREF_INVALID = 0;
+ const long PREF_STRING = 32;
+ const long PREF_INT = 64;
+ const long PREF_BOOL = 128;
+
+ /**
+ * Called to get the root on which this branch is based, such as
+ * "browser.startup."
+ */
+ readonly attribute string root;
+
+ /**
+ * Called to determine the type of a specific preference.
+ *
+ * @param aPrefName The preference to get the type of.
+ *
+ * @return long A value representing the type of the preference. This
+ * value will be PREF_STRING, PREF_INT, or PREF_BOOL.
+ */
+ long getPrefType(in string aPrefName);
+
+ /**
+ * Called to get the state of an individual boolean preference.
+ *
+ * @param aPrefName The boolean preference to get the state of.
+ * @param aDefaultValue The value to return if the preference is not set.
+ *
+ * @return boolean The value of the requested boolean preference.
+ *
+ * @see setBoolPref
+ */
+ [optional_argc,binaryname(GetBoolPrefWithDefault)]
+ boolean getBoolPref(in string aPrefName, [optional] in boolean aDefaultValue);
+ [noscript,binaryname(GetBoolPref)]
+ boolean getBoolPrefXPCOM(in string aPrefName);
+
+ /**
+ * Called to set the state of an individual boolean preference.
+ *
+ * @param aPrefName The boolean preference to set the state of.
+ * @param aValue The boolean value to set the preference to.
+ *
+ * @throws Error if setting failed or the preference has a default
+ value of a type other than boolean.
+ *
+ * @see getBoolPref
+ */
+ void setBoolPref(in string aPrefName, in boolean aValue);
+
+ /**
+ * Called to get the state of an individual floating-point preference.
+ * "Floating point" preferences are really string preferences that
+ * are converted to floating point numbers.
+ *
+ * @param aPrefName The floating point preference to get the state of.
+ * @param aDefaultValue The value to return if the preference is not set.
+ *
+ * @return float The value of the requested floating point preference.
+ *
+ * @see setCharPref
+ */
+ [optional_argc,binaryname(GetFloatPrefWithDefault)]
+ float getFloatPref(in string aPrefName, [optional] in float aDefaultValue);
+ [noscript,binaryname(GetFloatPref)]
+ float getFloatPrefXPCOM(in string aPrefName);
+
+ /**
+ * Called to get the state of an individual string preference.
+ *
+ * @param aPrefName The string preference to retrieve.
+ * @param aDefaultValue The string to return if the preference is not set.
+ *
+ * @return string The value of the requested string preference.
+ *
+ * @see setCharPref
+ */
+ [optional_argc,binaryname(GetCharPrefWithDefault)]
+ string getCharPref(in string aPrefName, [optional] in string aDefaultValue);
+ [noscript,binaryname(GetCharPref)]
+ string getCharPrefXPCOM(in string aPrefName);
+
+ /**
+ * Called to set the state of an individual string preference.
+ *
+ * @param aPrefName The string preference to set.
+ * @param aValue The string value to set the preference to.
+ *
+ * @throws Error if setting failed or the preference has a default
+ value of a type other than string.
+ *
+ * @see getCharPref
+ */
+ void setCharPref(in string aPrefName, in string aValue);
+
+ /**
+ * Called to get the state of an individual integer preference.
+ *
+ * @param aPrefName The integer preference to get the value of.
+ * @param aDefaultValue The value to return if the preference is not set.
+ *
+ * @return long The value of the requested integer preference.
+ *
+ * @see setIntPref
+ */
+ [optional_argc,binaryname(GetIntPrefWithDefault)]
+ long getIntPref(in string aPrefName, [optional] in long aDefaultValue);
+ [noscript,binaryname(GetIntPref)]
+ long getIntPrefXPCOM(in string aPrefName);
+
+ /**
+ * Called to set the state of an individual integer preference.
+ *
+ * @param aPrefName The integer preference to set the value of.
+ * @param aValue The integer value to set the preference to.
+ *
+ * @throws Error if setting failed or the preference has a default
+ value of a type other than integer.
+ *
+ * @see getIntPref
+ */
+ void setIntPref(in string aPrefName, in long aValue);
+
+ /**
+ * Called to get the state of an individual complex preference. A complex
+ * preference is a preference which represents an XPCOM object that can not
+ * be easily represented using a standard boolean, integer or string value.
+ *
+ * @param aPrefName The complex preference to get the value of.
+ * @param aType The XPCOM interface that this complex preference
+ * represents. Interfaces currently supported are:
+ * - nsIFile
+ * - nsISupportsString (UniChar)
+ * - nsIPrefLocalizedString (Localized UniChar)
+ * @param aValue The XPCOM object into which to the complex preference
+ * value should be retrieved.
+ *
+ * @throws Error The value does not exist or is the wrong type.
+ *
+ * @see setComplexValue
+ */
+ void getComplexValue(in string aPrefName, in nsIIDRef aType,
+ [iid_is(aType), retval] out nsQIResult aValue);
+
+ /**
+ * Called to set the state of an individual complex preference. A complex
+ * preference is a preference which represents an XPCOM object that can not
+ * be easily represented using a standard boolean, integer or string value.
+ *
+ * @param aPrefName The complex preference to set the value of.
+ * @param aType The XPCOM interface that this complex preference
+ * represents. Interfaces currently supported are:
+ * - nsIFile
+ * - nsISupportsString (UniChar)
+ * - nsIPrefLocalizedString (Localized UniChar)
+ * @param aValue The XPCOM object from which to set the complex preference
+ * value.
+ *
+ * @throws Error if setting failed or the value is the wrong type.
+ *
+ * @see getComplexValue
+ */
+ void setComplexValue(in string aPrefName, in nsIIDRef aType, in nsISupports aValue);
+
+ /**
+ * Called to clear a user set value from a specific preference. This will, in
+ * effect, reset the value to the default value. If no default value exists
+ * the preference will cease to exist.
+ *
+ * @param aPrefName The preference to be cleared.
+ *
+ * @note
+ * This method does nothing if this object is a default branch.
+ */
+ void clearUserPref(in string aPrefName);
+
+ /**
+ * Called to lock a specific preference. Locking a preference will cause the
+ * preference service to always return the default value regardless of
+ * whether there is a user set value or not.
+ *
+ * @param aPrefName The preference to be locked.
+ *
+ * @note
+ * This method can be called on either a default or user branch but, in
+ * effect, always operates on the default branch.
+ *
+ * @throws Error The preference does not exist or an error occurred.
+ *
+ * @see unlockPref
+ */
+ void lockPref(in string aPrefName);
+
+ /**
+ * Called to check if a specific preference has a user value associated to
+ * it.
+ *
+ * @param aPrefName The preference to be tested.
+ *
+ * @note
+ * This method can be called on either a default or user branch but, in
+ * effect, always operates on the user branch.
+ *
+ * @note
+ * If a preference was manually set to a value that equals the default value,
+ * then the preference no longer has a user set value, i.e. it is
+ * considered reset to its default value.
+ * In particular, this method will return false for such a preference and
+ * the preference will not be saved to a file by nsIPrefService.savePrefFile.
+ *
+ * @return boolean true The preference has a user set value.
+ * false The preference only has a default value.
+ */
+ boolean prefHasUserValue(in string aPrefName);
+
+ /**
+ * Called to check if a specific preference is locked. If a preference is
+ * locked calling its Get method will always return the default value.
+ *
+ * @param aPrefName The preference to be tested.
+ *
+ * @note
+ * This method can be called on either a default or user branch but, in
+ * effect, always operates on the default branch.
+ *
+ * @return boolean true The preference is locked.
+ * false The preference is not locked.
+ *
+ * @see lockPref
+ * @see unlockPref
+ */
+ boolean prefIsLocked(in string aPrefName);
+
+ /**
+ * Called to unlock a specific preference. Unlocking a previously locked
+ * preference allows the preference service to once again return the user set
+ * value of the preference.
+ *
+ * @param aPrefName The preference to be unlocked.
+ *
+ * @note
+ * This method can be called on either a default or user branch but, in
+ * effect, always operates on the default branch.
+ *
+ * @throws Error The preference does not exist or an error occurred.
+ *
+ * @see lockPref
+ */
+ void unlockPref(in string aPrefName);
+
+
+ /**
+ * Called to remove all of the preferences referenced by this branch.
+ *
+ * @param aStartingAt The point on the branch at which to start the deleting
+ * preferences. Pass in "" to remove all preferences
+ * referenced by this branch.
+ *
+ * @note
+ * This method can be called on either a default or user branch but, in
+ * effect, always operates on both.
+ *
+ * @throws Error The preference(s) do not exist or an error occurred.
+ */
+ void deleteBranch(in string aStartingAt);
+
+ /**
+ * Returns an array of strings representing the child preferences of the
+ * root of this branch.
+ *
+ * @param aStartingAt The point on the branch at which to start enumerating
+ * the child preferences. Pass in "" to enumerate all
+ * preferences referenced by this branch.
+ * @param aCount Receives the number of elements in the array.
+ * @param aChildArray Receives the array of child preferences.
+ *
+ * @note
+ * This method can be called on either a default or user branch but, in
+ * effect, always operates on both.
+ *
+ * @throws Error The preference(s) do not exist or an error occurred.
+ */
+ void getChildList(in string aStartingAt,
+ [optional] out unsigned long aCount,
+ [array, size_is(aCount), retval] out string aChildArray);
+
+ /**
+ * Called to reset all of the preferences referenced by this branch to their
+ * default values.
+ *
+ * @param aStartingAt The point on the branch at which to start the resetting
+ * preferences to their default values. Pass in "" to
+ * reset all preferences referenced by this branch.
+ *
+ * @note
+ * This method can be called on either a default or user branch but, in
+ * effect, always operates on the user branch.
+ *
+ * @throws Error The preference(s) do not exist or an error occurred.
+ */
+ void resetBranch(in string aStartingAt);
+
+ /**
+ * Add a preference change observer. On preference changes, the following
+ * arguments will be passed to the nsIObserver.observe() method:
+ * aSubject - The nsIPrefBranch object (this)
+ * aTopic - The string defined by NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
+ * aData - The name of the preference which has changed, relative to
+ * the |root| of the aSubject branch.
+ *
+ * aSubject.get*Pref(aData) will get the new value of the modified
+ * preference. For example, if your observer is registered with
+ * addObserver("bar.", ...) on a branch with root "foo.", modifying
+ * the preference "foo.bar.baz" will trigger the observer, and aData
+ * parameter will be "bar.baz".
+ *
+ * @param aDomain The preference on which to listen for changes. This can
+ * be the name of an entire branch to observe.
+ * e.g. Holding the "root" prefbranch and calling
+ * addObserver("foo.bar.", ...) will observe changes to
+ * foo.bar.baz and foo.bar.bzip
+ * @param aObserver The object to be notified if the preference changes.
+ * @param aHoldWeak true Hold a weak reference to |aObserver|. The object
+ * must implement the nsISupportsWeakReference
+ * interface or this will fail.
+ * false Hold a strong reference to |aObserver|.
+ *
+ * @note
+ * Registering as a preference observer can open an object to potential
+ * cyclical references which will cause memory leaks. These cycles generally
+ * occur because an object both registers itself as an observer (causing the
+ * branch to hold a reference to the observer) and holds a reference to the
+ * branch object for the purpose of getting/setting preference values. There
+ * are 3 approaches which have been implemented in an attempt to avoid these
+ * situations.
+ * 1) The nsPrefBranch object supports nsISupportsWeakReference. Any consumer
+ * may hold a weak reference to it instead of a strong one.
+ * 2) The nsPrefBranch object listens for xpcom-shutdown and frees all of the
+ * objects currently in its observer list. This ensures that long lived
+ * objects (services for example) will be freed correctly.
+ * 3) The observer can request to be held as a weak reference when it is
+ * registered. This insures that shorter lived objects (say one tied to an
+ * open window) will not fall into the cyclical reference trap.
+ *
+ * @note
+ * The list of registered observers may be changed during the dispatch of
+ * nsPref:changed notification. However, the observers are not guaranteed
+ * to be notified in any particular order, so you can't be sure whether the
+ * added/removed observer will be called during the notification when it
+ * is added/removed.
+ *
+ * @note
+ * It is possible to change preferences during the notification.
+ *
+ * @note
+ * It is not safe to change observers during this callback in Gecko
+ * releases before 1.9. If you want a safe way to remove a pref observer,
+ * please use an nsITimer.
+ *
+ * @see nsIObserver
+ * @see removeObserver
+ */
+ void addObserver(in string aDomain, in nsIObserver aObserver,
+ in boolean aHoldWeak);
+
+ /**
+ * Remove a preference change observer.
+ *
+ * @param aDomain The preference which is being observed for changes.
+ * @param aObserver An observer previously registered with addObserver().
+ *
+ * @note
+ * Note that you must call removeObserver() on the same nsIPrefBranch
+ * instance on which you called addObserver() in order to remove aObserver;
+ * otherwise, the observer will not be removed.
+ *
+ * @see nsIObserver
+ * @see addObserver
+ */
+ void removeObserver(in string aDomain, in nsIObserver aObserver);
+};
+
+
+%{C++
+
+#define NS_PREFBRANCH_CONTRACTID "@mozilla.org/preferencesbranch;1"
+/**
+ * Notification sent when a preference changes.
+ */
+#define NS_PREFBRANCH_PREFCHANGE_TOPIC_ID "nsPref:changed"
+
+%}
diff --git a/components/preferences/public/nsIPrefBranch2.idl b/components/preferences/public/nsIPrefBranch2.idl
new file mode 100644
index 000000000..f1087c92d
--- /dev/null
+++ b/components/preferences/public/nsIPrefBranch2.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; 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 "nsIPrefBranch.idl"
+
+/**
+ * An empty interface to provide backwards compatibility for existing code.
+ *
+ * @see nsIPrefBranch
+ */
+[scriptable, uuid(8892016d-07f7-4530-b5c1-d73dfcde4a1c)]
+interface nsIPrefBranch2 : nsIPrefBranch
+{
+};
diff --git a/components/preferences/public/nsIPrefBranchInternal.idl b/components/preferences/public/nsIPrefBranchInternal.idl
new file mode 100644
index 000000000..476e6a59f
--- /dev/null
+++ b/components/preferences/public/nsIPrefBranchInternal.idl
@@ -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/. */
+
+#include "nsIPrefBranch2.idl"
+
+/**
+ * An empty interface to provide backwards compatibility for existing code that
+ * bsmedberg didn't want to break all at once. Don't use me!
+ *
+ * @status NON-FROZEN interface WHICH WILL PROBABLY GO AWAY.
+ */
+
+[scriptable, uuid(355bd1e9-248a-438b-809d-e0db1b287882)]
+interface nsIPrefBranchInternal : nsIPrefBranch2
+{
+};
diff --git a/components/preferences/public/nsIPrefLocalizedString.idl b/components/preferences/public/nsIPrefLocalizedString.idl
new file mode 100644
index 000000000..f99d60f6f
--- /dev/null
+++ b/components/preferences/public/nsIPrefLocalizedString.idl
@@ -0,0 +1,64 @@
+/* -*- Mode: IDL; 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"
+
+/**
+ * The nsIPrefLocalizedString interface is simply a wrapper interface for
+ * nsISupportsString so the preferences service can have a unique identifier
+ * to distinguish between requests for normal wide strings (nsISupportsString)
+ * and "localized" wide strings, which get their default values from properites
+ * files.
+ *
+ * @see nsIPrefBranch
+ * @see nsISupportsString
+ */
+
+[scriptable, uuid(ae419e24-1dd1-11b2-b39a-d3e5e7073802)]
+interface nsIPrefLocalizedString : nsISupports
+{
+ /**
+ * Provides access to string data stored in this property.
+ *
+ * @throws Error An error occurred.
+ */
+ attribute wstring data;
+
+ /**
+ * Used to retrieve the contents of this object into a wide string.
+ *
+ * @return wstring The string containing the data stored within this object.
+ */
+ wstring toString();
+
+ /**
+ * Used to set the contents of this object.
+ *
+ * @param length The length of the string. This value should not include
+ * space for the null terminator, nor should it account for the
+ * size of a character. It should only be the number of
+ * characters for which there is space in the string.
+ * @param data The string data to be stored.
+ *
+ * @note
+ * This makes a copy of the string argument passed in.
+ */
+ void setDataWithLength(in unsigned long length,
+ [size_is(length)] in wstring data);
+};
+
+%{C++
+
+#define NS_PREFLOCALIZEDSTRING_CID \
+ { /* {064d9cee-1dd2-11b2-83e3-d25ab0193c26} */ \
+ 0x064d9cee, \
+ 0x1dd2, \
+ 0x11b2, \
+ { 0x83, 0xe3, 0xd2, 0x5a, 0xb0, 0x19, 0x3c, 0x26 } \
+ }
+
+#define NS_PREFLOCALIZEDSTRING_CONTRACTID "@mozilla.org/pref-localizedstring;1"
+
+%}
diff --git a/components/preferences/public/nsIPrefService.idl b/components/preferences/public/nsIPrefService.idl
new file mode 100644
index 000000000..0db401996
--- /dev/null
+++ b/components/preferences/public/nsIPrefService.idl
@@ -0,0 +1,160 @@
+/* -*- Mode: IDL; 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"
+#include "nsIPrefBranch.idl"
+
+%{C++
+struct PrefTuple;
+#include "nsTArrayForwardDeclare.h"
+%}
+
+[ptr] native nsPreferencesArrayPtr(nsTArray<PrefTuple>);
+[ptr] native nsPreferencePtr(PrefTuple);
+[ptr] native nsPreferencePtrConst(const PrefTuple);
+
+interface nsIFile;
+
+/**
+ * The nsIPrefService interface is the main entry point into the back end
+ * preferences management library. The preference service is directly
+ * responsible for the management of the preferences files and also facilitates
+ * access to the preference branch object which allows the direct manipulation
+ * of the preferences themselves.
+ *
+ * @see nsIPrefBranch
+ */
+
+[scriptable, uuid(1f84fd56-3956-40df-b86a-1ea01402ee96)]
+interface nsIPrefService : nsISupports
+{
+ /**
+ * Called to read in the preferences specified in a user preference file.
+ *
+ * @param aFile The file to be read.
+ *
+ * @note
+ * If nullptr is passed in for the aFile parameter the default preferences
+ * file(s) [prefs.js, user.js] will be read and processed.
+ *
+ * @throws Error File failed to read or contained invalid data.
+ *
+ * @see savePrefFile
+ * @see nsIFile
+ */
+ void readUserPrefs(in nsIFile aFile);
+
+ /**
+ * Called to completely flush and re-initialize the preferences system.
+ *
+ * @throws Error The preference service failed to restart correctly.
+ */
+ void resetPrefs();
+
+ /**
+ * Called to reset all preferences with user set values back to the
+ * application default values.
+ */
+ void resetUserPrefs();
+
+ /**
+ * Called to write current preferences state to a file.
+ *
+ * @param aFile The file to be written.
+ *
+ * @note
+ * If nullptr is passed in for the aFile parameter the preference data is
+ * written out to the current preferences file (usually prefs.js.)
+ *
+ * @throws Error File failed to write.
+ *
+ * @see readUserPrefs
+ * @see nsIFile
+ */
+ void savePrefFile(in nsIFile aFile);
+
+ /**
+ * Call to get a Preferences "Branch" which accesses user preference data.
+ * Using a Set method on this object will always create or set a user
+ * preference value. When using a Get method a user set value will be
+ * returned if one exists, otherwise a default value will be returned.
+ *
+ * @param aPrefRoot The preference "root" on which to base this "branch".
+ * For example, if the root "browser.startup." is used, the
+ * branch will be able to easily access the preferences
+ * "browser.startup.page", "browser.startup.homepage", or
+ * "browser.startup.homepage_override" by simply requesting
+ * "page", "homepage", or "homepage_override". nullptr or ""
+ * may be used to access to the entire preference "tree".
+ *
+ * @return nsIPrefBranch The object representing the requested branch.
+ *
+ * @see getDefaultBranch
+ */
+ nsIPrefBranch getBranch(in string aPrefRoot);
+
+ /**
+ * Call to get a Preferences "Branch" which accesses only the default
+ * preference data. Using a Set method on this object will always create or
+ * set a default preference value. When using a Get method a default value
+ * will always be returned.
+ *
+ * @param aPrefRoot The preference "root" on which to base this "branch".
+ * For example, if the root "browser.startup." is used, the
+ * branch will be able to easily access the preferences
+ * "browser.startup.page", "browser.startup.homepage", or
+ * "browser.startup.homepage_override" by simply requesting
+ * "page", "homepage", or "homepage_override". nullptr or ""
+ * may be used to access to the entire preference "tree".
+ *
+ * @note
+ * Few consumers will want to create default branch objects. Many of the
+ * branch methods do nothing on a default branch because the operations only
+ * make sense when applied to user set preferences.
+ *
+ * @return nsIPrefBranch The object representing the requested default branch.
+ *
+ * @see getBranch
+ */
+ nsIPrefBranch getDefaultBranch(in string aPrefRoot);
+
+ /**
+ * The preference service is 'dirty' if there are changes to user preferences
+ * that have not been written to disk
+ */
+ readonly attribute boolean dirty;
+};
+
+%{C++
+
+#define NS_PREFSERVICE_CID \
+ { /* {1cd91b88-1dd2-11b2-92e1-ed22ed298000} */ \
+ 0x91ca2441, \
+ 0x050f, \
+ 0x4f7c, \
+ { 0x9d, 0xf8, 0x75, 0xb4, 0x0e, 0xa4, 0x01, 0x56 } \
+ }
+
+#define NS_PREFSERVICE_CONTRACTID "@mozilla.org/preferences-service;1"
+
+/**
+ * Notification sent before reading the default user preferences files.
+ */
+#define NS_PREFSERVICE_READ_TOPIC_ID "prefservice:before-read-userprefs"
+
+/**
+ * Notification sent when resetPrefs has been called, but before the actual
+ * reset process occurs.
+ */
+#define NS_PREFSERVICE_RESET_TOPIC_ID "prefservice:before-reset"
+
+/**
+ * Notification sent when after reading app-provided default
+ * preferences, but before user profile override defaults or extension
+ * defaults are loaded.
+ */
+#define NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID "prefservice:after-app-defaults"
+
+%}
diff --git a/components/preferences/public/nsIRelativeFilePref.idl b/components/preferences/public/nsIRelativeFilePref.idl
new file mode 100644
index 000000000..4b86e3755
--- /dev/null
+++ b/components/preferences/public/nsIRelativeFilePref.idl
@@ -0,0 +1,69 @@
+/* -*- 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 "nsISupports.idl"
+interface nsIFile;
+
+/**
+ * The nsIRelativeFilePref interface is a wrapper for an nsIFile and
+ * and a directory service key. When used as a pref value, it stores a
+ * relative path to the file from the location pointed to by the directory
+ * service key. The path has the same syntax across all platforms.
+ *
+ * @see nsIPrefBranch::getComplexValue
+ * @see nsIPrefBranch::setComplexValue
+ *
+ */
+
+[scriptable, uuid(2f977d4e-5485-11d4-87e2-0010a4e75ef2)]
+interface nsIRelativeFilePref : nsISupports
+{
+ /**
+ * file
+ *
+ * The file whose location is stored or retrieved.
+ */
+ attribute nsIFile file;
+
+ /**
+ * relativeToKey
+ *
+ * A directory service key for the directory
+ * from which the file path is relative.
+ */
+ attribute ACString relativeToKey;
+
+};
+
+%{C++
+
+#define NS_RELATIVEFILEPREF_CID \
+ { /* {2f977d4f-5485-11d4-87e2-0010a4e75ef2} */ \
+ 0x2f977d4f, \
+ 0x5485, \
+ 0x11d4, \
+ { 0x87, 0xe2, 0x00, 0x10, 0xa4, 0xe7, 0x5e, 0xf2 } \
+ }
+
+#define NS_RELATIVEFILEPREF_CONTRACTID "@mozilla.org/pref-relativefile;1"
+
+#include "nsComponentManagerUtils.h"
+
+inline nsresult
+NS_NewRelativeFilePref(nsIFile* aFile, const nsACString& relativeToKey, nsIRelativeFilePref** result)
+{
+ nsresult rv;
+ nsCOMPtr<nsIRelativeFilePref> local(do_CreateInstance(NS_RELATIVEFILEPREF_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ (void)local->SetFile(aFile);
+ (void)local->SetRelativeToKey(relativeToKey);
+
+ *result = local;
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+%}
diff --git a/components/preferences/src/Preferences.cpp b/components/preferences/src/Preferences.cpp
new file mode 100644
index 000000000..5f17125da
--- /dev/null
+++ b/components/preferences/src/Preferences.cpp
@@ -0,0 +1,1988 @@
+/* -*- 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 "mozilla/MemoryReporting.h"
+#include "mozilla/dom/ContentChild.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#include "nsXULAppAPI.h"
+
+#include "mozilla/Preferences.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDataHashtable.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsICategoryManager.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsNetUtil.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIObserverService.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIStringEnumerator.h"
+#include "nsIZipReader.h"
+#include "nsPrefBranch.h"
+#include "nsXPIDLString.h"
+#include "nsCRT.h"
+#include "nsCOMArray.h"
+#include "nsXPCOMCID.h"
+#include "nsAutoPtr.h"
+#include "nsPrintfCString.h"
+
+#include "nsQuickSort.h"
+#include "PLDHashTable.h"
+
+#include "prefapi.h"
+#include "prefread.h"
+#include "prefapi_private_data.h"
+
+#include "mozilla/Omnijar.h"
+#include "nsZipArchive.h"
+
+#include "nsTArray.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIMemoryReporter.h"
+#include "nsThreadUtils.h"
+
+#ifdef DEBUG
+#define ENSURE_MAIN_PROCESS(message, pref) do { \
+ if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
+ nsPrintfCString msg("ENSURE_MAIN_PROCESS failed. %s %s", message, pref); \
+ NS_WARNING(msg.get()); \
+ return NS_ERROR_NOT_AVAILABLE; \
+ } \
+} while (0);
+#else
+#define ENSURE_MAIN_PROCESS(message, pref) \
+ if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \
+ return NS_ERROR_NOT_AVAILABLE; \
+ }
+#endif
+
+class PrefCallback;
+
+namespace mozilla {
+
+// Definitions
+#define INITIAL_PREF_FILES 10
+static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
+
+void
+Preferences::DirtyCallback()
+{
+ if (gHashTable && sPreferences && !sPreferences->mDirty) {
+ sPreferences->mDirty = true;
+ }
+}
+
+// Prototypes
+static nsresult openPrefFile(nsIFile* aFile);
+static nsresult pref_InitInitialObjects(void);
+static nsresult pref_LoadPrefsInDirList(const char *listId);
+static nsresult ReadExtensionPrefs(nsIFile *aFile);
+
+static const char kChannelPref[] = "app.update.channel";
+
+static const char kPrefFileHeader[] =
+ "# Mozilla User Preferences"
+ NS_LINEBREAK
+ NS_LINEBREAK
+ "/* Do not edit this file."
+ NS_LINEBREAK
+ " *"
+ NS_LINEBREAK
+ " * If you make changes to this file while the application is running,"
+ NS_LINEBREAK
+ " * the changes will be overwritten when the application exits."
+ NS_LINEBREAK
+ " *"
+ NS_LINEBREAK
+ " * To make a manual change to preferences, you can visit the URL about:config"
+ NS_LINEBREAK
+ " */"
+ NS_LINEBREAK
+ NS_LINEBREAK;
+
+Preferences* Preferences::sPreferences = nullptr;
+nsIPrefBranch* Preferences::sRootBranch = nullptr;
+nsIPrefBranch* Preferences::sDefaultRootBranch = nullptr;
+bool Preferences::sShutdown = false;
+
+class ValueObserverHashKey : public PLDHashEntryHdr {
+public:
+ typedef ValueObserverHashKey* KeyType;
+ typedef const ValueObserverHashKey* KeyTypePointer;
+
+ static const ValueObserverHashKey* KeyToPointer(ValueObserverHashKey *aKey)
+ {
+ return aKey;
+ }
+
+ static PLDHashNumber HashKey(const ValueObserverHashKey *aKey)
+ {
+ PLDHashNumber hash = HashString(aKey->mPrefName);
+ hash = AddToHash(hash, aKey->mMatchKind);
+ return AddToHash(hash, aKey->mCallback);
+ }
+
+ ValueObserverHashKey(const char *aPref, PrefChangedFunc aCallback, Preferences::MatchKind aMatchKind) :
+ mPrefName(aPref), mCallback(aCallback), mMatchKind(aMatchKind) { }
+
+ explicit ValueObserverHashKey(const ValueObserverHashKey *aOther) :
+ mPrefName(aOther->mPrefName),
+ mCallback(aOther->mCallback),
+ mMatchKind(aOther->mMatchKind)
+ { }
+
+ bool KeyEquals(const ValueObserverHashKey *aOther) const
+ {
+ return mCallback == aOther->mCallback &&
+ mPrefName == aOther->mPrefName &&
+ mMatchKind == aOther->mMatchKind;
+ }
+
+ ValueObserverHashKey *GetKey() const
+ {
+ return const_cast<ValueObserverHashKey*>(this);
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ nsCString mPrefName;
+ PrefChangedFunc mCallback;
+ Preferences::MatchKind mMatchKind;
+};
+
+class ValueObserver final : public nsIObserver,
+ public ValueObserverHashKey
+{
+ ~ValueObserver() {
+ Preferences::RemoveObserver(this, mPrefName.get());
+ }
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ ValueObserver(const char *aPref, PrefChangedFunc aCallback, Preferences::MatchKind aMatchKind)
+ : ValueObserverHashKey(aPref, aCallback, aMatchKind) { }
+
+ void AppendClosure(void *aClosure) {
+ mClosures.AppendElement(aClosure);
+ }
+
+ void RemoveClosure(void *aClosure) {
+ mClosures.RemoveElement(aClosure);
+ }
+
+ bool HasNoClosures() {
+ return mClosures.Length() == 0;
+ }
+
+ nsTArray<void*> mClosures;
+};
+
+NS_IMPL_ISUPPORTS(ValueObserver, nsIObserver)
+
+NS_IMETHODIMP
+ValueObserver::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ NS_ASSERTION(!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID),
+ "invalid topic");
+ NS_ConvertUTF16toUTF8 data(aData);
+ if (mMatchKind == Preferences::ExactMatch && !mPrefName.EqualsASCII(data.get())) {
+ return NS_OK;
+ }
+ for (uint32_t i = 0; i < mClosures.Length(); i++) {
+ mCallback(data.get(), mClosures.ElementAt(i));
+ }
+
+ return NS_OK;
+}
+
+struct CacheData {
+ void* cacheLocation;
+ union {
+ bool defaultValueBool;
+ int32_t defaultValueInt;
+ uint32_t defaultValueUint;
+ float defaultValueFloat;
+ };
+};
+
+static nsTArray<nsAutoPtr<CacheData> >* gCacheData = nullptr;
+static nsRefPtrHashtable<ValueObserverHashKey,
+ ValueObserver>* gObserverTable = nullptr;
+
+#ifdef DEBUG
+static bool
+HaveExistingCacheFor(void* aPtr)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (gCacheData) {
+ for (size_t i = 0, count = gCacheData->Length(); i < count; ++i) {
+ if ((*gCacheData)[i]->cacheLocation == aPtr) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static void
+AssertNotAlreadyCached(const char* aPrefType,
+ const char* aPref,
+ void* aPtr)
+{
+ if (HaveExistingCacheFor(aPtr)) {
+ fprintf_stderr(stderr,
+ "Attempt to add a %s pref cache for preference '%s' at address '%p'"
+ "was made. However, a pref was already cached at this address.\n",
+ aPrefType, aPref, aPtr);
+ MOZ_ASSERT(false, "Should not have an existing pref cache for this address");
+ }
+}
+#endif
+
+static void
+ReportToConsole(const char* aMessage, int aLine, bool aError)
+{
+ nsPrintfCString message("** Preference parsing %s (line %d) = %s **\n",
+ (aError ? "error" : "warning"), aLine, aMessage);
+ nsPrefBranch::ReportToConsole(NS_ConvertUTF8toUTF16(message.get()));
+}
+
+// Although this is a member of Preferences, it measures sPreferences and
+// several other global structures.
+/* static */ int64_t
+Preferences::SizeOfIncludingThisAndOtherStuff(mozilla::MallocSizeOf aMallocSizeOf)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), 0);
+
+ size_t n = aMallocSizeOf(sPreferences);
+ if (gHashTable) {
+ // pref keys are allocated in a private arena, which we count elsewhere.
+ // pref stringvals are allocated out of the same private arena.
+ n += gHashTable->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (gCacheData) {
+ n += gCacheData->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ for (uint32_t i = 0, count = gCacheData->Length(); i < count; ++i) {
+ n += aMallocSizeOf((*gCacheData)[i]);
+ }
+ }
+ if (gObserverTable) {
+ n += gObserverTable->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ for (auto iter = gObserverTable->Iter(); !iter.Done(); iter.Next()) {
+ n += iter.Key()->mPrefName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ n += iter.Data()->mClosures.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ }
+ if (sRootBranch) {
+ n += reinterpret_cast<nsPrefBranch*>(sRootBranch)->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (sDefaultRootBranch) {
+ n += reinterpret_cast<nsPrefBranch*>(sDefaultRootBranch)->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ n += pref_SizeOfPrivateData(aMallocSizeOf);
+ return n;
+}
+
+class PreferenceServiceReporter final : public nsIMemoryReporter
+{
+ ~PreferenceServiceReporter() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+protected:
+ static const uint32_t kSuspectReferentCount = 1000;
+};
+
+NS_IMPL_ISUPPORTS(PreferenceServiceReporter, nsIMemoryReporter)
+
+MOZ_DEFINE_MALLOC_SIZE_OF(PreferenceServiceMallocSizeOf)
+
+NS_IMETHODIMP
+PreferenceServiceReporter::CollectReports(
+ nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize)
+{
+ MOZ_COLLECT_REPORT(
+ "explicit/preferences", KIND_HEAP, UNITS_BYTES,
+ Preferences::SizeOfIncludingThisAndOtherStuff(PreferenceServiceMallocSizeOf),
+ "Memory used by the preferences system.");
+
+ nsPrefBranch* rootBranch =
+ static_cast<nsPrefBranch*>(Preferences::GetRootBranch());
+ if (!rootBranch) {
+ return NS_OK;
+ }
+
+ size_t numStrong = 0;
+ size_t numWeakAlive = 0;
+ size_t numWeakDead = 0;
+ nsTArray<nsCString> suspectPreferences;
+ // Count of the number of referents for each preference.
+ nsDataHashtable<nsCStringHashKey, uint32_t> prefCounter;
+
+ for (auto iter = rootBranch->mObservers.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<PrefCallback>& callback = iter.Data();
+ nsPrefBranch* prefBranch = callback->GetPrefBranch();
+ const char* pref = prefBranch->getPrefName(callback->GetDomain().get());
+
+ if (callback->IsWeak()) {
+ nsCOMPtr<nsIObserver> callbackRef = do_QueryReferent(callback->mWeakRef);
+ if (callbackRef) {
+ numWeakAlive++;
+ } else {
+ numWeakDead++;
+ }
+ } else {
+ numStrong++;
+ }
+
+ nsDependentCString prefString(pref);
+ uint32_t oldCount = 0;
+ prefCounter.Get(prefString, &oldCount);
+ uint32_t currentCount = oldCount + 1;
+ prefCounter.Put(prefString, currentCount);
+
+ // Keep track of preferences that have a suspiciously large number of
+ // referents (a symptom of a leak).
+ if (currentCount == kSuspectReferentCount) {
+ suspectPreferences.AppendElement(prefString);
+ }
+ }
+
+ for (uint32_t i = 0; i < suspectPreferences.Length(); i++) {
+ nsCString& suspect = suspectPreferences[i];
+ uint32_t totalReferentCount = 0;
+ prefCounter.Get(suspect, &totalReferentCount);
+
+ nsPrintfCString suspectPath("preference-service-suspect/"
+ "referent(pref=%s)", suspect.get());
+
+ aHandleReport->Callback(
+ /* process = */ EmptyCString(),
+ suspectPath, KIND_OTHER, UNITS_COUNT, totalReferentCount,
+ NS_LITERAL_CSTRING(
+ "A preference with a suspiciously large number referents (symptom of a "
+ "leak)."),
+ aData);
+ }
+
+ MOZ_COLLECT_REPORT(
+ "preference-service/referent/strong", KIND_OTHER, UNITS_COUNT,
+ numStrong,
+ "The number of strong referents held by the preference service.");
+
+ MOZ_COLLECT_REPORT(
+ "preference-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT,
+ numWeakAlive,
+ "The number of weak referents held by the preference service that are "
+ "still alive.");
+
+ MOZ_COLLECT_REPORT(
+ "preference-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT,
+ numWeakDead,
+ "The number of weak referents held by the preference service that are "
+ "dead.");
+
+ return NS_OK;
+}
+
+namespace {
+class AddPreferencesMemoryReporterRunnable : public Runnable
+{
+ NS_IMETHOD Run() override
+ {
+ return RegisterStrongMemoryReporter(new PreferenceServiceReporter());
+ }
+};
+} // namespace
+
+// static
+Preferences*
+Preferences::GetInstanceForService()
+{
+ if (sPreferences) {
+ NS_ADDREF(sPreferences);
+ return sPreferences;
+ }
+
+ NS_ENSURE_TRUE(!sShutdown, nullptr);
+
+ sRootBranch = new nsPrefBranch("", false);
+ NS_ADDREF(sRootBranch);
+ sDefaultRootBranch = new nsPrefBranch("", true);
+ NS_ADDREF(sDefaultRootBranch);
+
+ sPreferences = new Preferences();
+ NS_ADDREF(sPreferences);
+
+ if (NS_FAILED(sPreferences->Init())) {
+ // The singleton instance will delete sRootBranch and sDefaultRootBranch.
+ NS_RELEASE(sPreferences);
+ return nullptr;
+ }
+
+ gCacheData = new nsTArray<nsAutoPtr<CacheData> >();
+
+ gObserverTable = new nsRefPtrHashtable<ValueObserverHashKey, ValueObserver>();
+
+ // Preferences::GetInstanceForService() can be called from GetService(), and
+ // RegisterStrongMemoryReporter calls GetService(nsIMemoryReporter). To
+ // avoid a potential recursive GetService() call, we can't register the
+ // memory reporter here; instead, do it off a runnable.
+ RefPtr<AddPreferencesMemoryReporterRunnable> runnable =
+ new AddPreferencesMemoryReporterRunnable();
+ NS_DispatchToMainThread(runnable);
+
+ NS_ADDREF(sPreferences);
+ return sPreferences;
+}
+
+// static
+bool
+Preferences::IsServiceAvailable()
+{
+ return !!sPreferences;
+}
+
+// static
+bool
+Preferences::InitStaticMembers()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sShutdown && !sPreferences) {
+ nsCOMPtr<nsIPrefService> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ }
+
+ return sPreferences != nullptr;
+}
+
+// static
+void
+Preferences::Shutdown()
+{
+ if (!sShutdown) {
+ sShutdown = true; // Don't create the singleton instance after here.
+
+ // Don't set sPreferences to nullptr here. The instance may be grabbed by
+ // other modules. The utility methods of Preferences should be available
+ // until the singleton instance actually released.
+ if (sPreferences) {
+ sPreferences->Release();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+/*
+ * Constructor/Destructor
+ */
+
+Preferences::Preferences()
+ : mDirty(false)
+{
+}
+
+Preferences::~Preferences()
+{
+ NS_ASSERTION(sPreferences == this, "Isn't this the singleton instance?");
+
+ delete gObserverTable;
+ gObserverTable = nullptr;
+
+ delete gCacheData;
+ gCacheData = nullptr;
+
+ NS_RELEASE(sRootBranch);
+ NS_RELEASE(sDefaultRootBranch);
+
+ sPreferences = nullptr;
+
+ PREF_Cleanup();
+}
+
+
+/*
+ * nsISupports Implementation
+ */
+
+NS_IMPL_ADDREF(Preferences)
+NS_IMPL_RELEASE(Preferences)
+
+NS_INTERFACE_MAP_BEGIN(Preferences)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefService)
+ NS_INTERFACE_MAP_ENTRY(nsIPrefService)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIPrefBranch)
+ NS_INTERFACE_MAP_ENTRY(nsIPrefBranch2)
+ NS_INTERFACE_MAP_ENTRY(nsIPrefBranchInternal)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+
+/*
+ * nsIPrefService Implementation
+ */
+
+nsresult
+Preferences::Init()
+{
+ nsresult rv;
+
+ PREF_SetDirtyCallback(&DirtyCallback);
+ PREF_Init();
+
+ rv = pref_InitInitialObjects();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ using mozilla::dom::ContentChild;
+ if (XRE_IsContentProcess()) {
+ InfallibleTArray<PrefSetting> prefs;
+ ContentChild::GetSingleton()->SendReadPrefsArray(&prefs);
+
+ // Store the array
+ for (uint32_t i = 0; i < prefs.Length(); ++i) {
+ pref_SetPref(prefs[i]);
+ }
+ return NS_OK;
+ }
+
+ nsXPIDLCString lockFileName;
+ /*
+ * The following is a small hack which will allow us to only load the library
+ * which supports the netscape.cfg file if the preference is defined. We
+ * test for the existence of the pref, set in the all.js (mozilla) or
+ * all-ns.js (netscape 6), and if it exists we startup the pref config
+ * category which will do the rest.
+ */
+
+ rv = PREF_CopyCharPref("general.config.filename", getter_Copies(lockFileName), false);
+ if (NS_SUCCEEDED(rv))
+ NS_CreateServicesFromCategory("pref-config-startup",
+ static_cast<nsISupports *>(static_cast<void *>(this)),
+ "pref-config-startup");
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return NS_ERROR_FAILURE;
+
+ rv = observerService->AddObserver(this, "profile-before-change", true);
+
+ observerService->AddObserver(this, "load-extension-defaults", true);
+ observerService->AddObserver(this, "suspend_process_notification", true);
+
+ return(rv);
+}
+
+// static
+nsresult
+Preferences::ResetAndReadUserPrefs()
+{
+ sPreferences->ResetUserPrefs();
+ return sPreferences->ReadUserPrefs(nullptr);
+}
+
+NS_IMETHODIMP
+Preferences::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *someData)
+{
+ if (XRE_IsContentProcess())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = NS_OK;
+
+ if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
+ rv = SavePrefFile(nullptr);
+ } else if (!strcmp(aTopic, "load-extension-defaults")) {
+ pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST);
+ } else if (!nsCRT::strcmp(aTopic, "reload-default-prefs")) {
+ // Reload the default prefs from file.
+ pref_InitInitialObjects();
+ } else if (!nsCRT::strcmp(aTopic, "suspend_process_notification")) {
+ // Our process is being suspended. The OS may wake our process later,
+ // or it may kill the process. In case our process is going to be killed
+ // from the suspended state, we save preferences before suspending.
+ rv = SavePrefFile(nullptr);
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP
+Preferences::ReadUserPrefs(nsIFile *aFile)
+{
+ if (XRE_IsContentProcess()) {
+ NS_ERROR("cannot load prefs from content process");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+
+ if (nullptr == aFile) {
+ rv = UseDefaultPrefFile();
+ // A user pref file is optional.
+ // Ignore all errors related to it, so we retain 'rv' value :-|
+ (void) UseUserPrefFile();
+
+ NotifyServiceObservers(NS_PREFSERVICE_READ_TOPIC_ID);
+ } else {
+ rv = ReadAndOwnUserPrefFile(aFile);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+Preferences::ResetPrefs()
+{
+ if (XRE_IsContentProcess()) {
+ NS_ERROR("cannot reset prefs from content process");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NotifyServiceObservers(NS_PREFSERVICE_RESET_TOPIC_ID);
+ PREF_CleanupPrefs();
+
+ PREF_Init();
+
+ return pref_InitInitialObjects();
+}
+
+NS_IMETHODIMP
+Preferences::ResetUserPrefs()
+{
+ if (XRE_IsContentProcess()) {
+ NS_ERROR("cannot reset user prefs from content process");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ PREF_ClearAllUserPrefs();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Preferences::SavePrefFile(nsIFile *aFile)
+{
+ if (XRE_IsContentProcess()) {
+ NS_ERROR("cannot save pref file from content process");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return SavePrefFileInternal(aFile);
+}
+
+static nsresult
+ReadExtensionPrefs(nsIFile *aFile)
+{
+ nsresult rv;
+ nsCOMPtr<nsIZipReader> reader = do_CreateInstance(kZipReaderCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = reader->Open(aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIUTF8StringEnumerator> files;
+ rv = reader->FindEntries(nsDependentCString("defaults/preferences/*.(J|j)(S|s)$"),
+ getter_AddRefs(files));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char buffer[4096];
+
+ bool more;
+ while (NS_SUCCEEDED(rv = files->HasMore(&more)) && more) {
+ nsAutoCString entry;
+ rv = files->GetNext(entry);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = reader->GetInputStream(entry, getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t avail;
+ uint32_t read;
+
+ PrefParseState ps;
+ PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr);
+ while (NS_SUCCEEDED(rv = stream->Available(&avail)) && avail) {
+ rv = stream->Read(buffer, 4096, &read);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Pref stream read failed");
+ break;
+ }
+
+ PREF_ParseBuf(&ps, buffer, read);
+ }
+ PREF_FinalizeParseState(&ps);
+ }
+ return rv;
+}
+
+void
+Preferences::SetPreference(const PrefSetting& aPref)
+{
+ pref_SetPref(aPref);
+}
+
+void
+Preferences::GetPreference(PrefSetting* aPref)
+{
+ PrefHashEntry *entry = pref_HashTableLookup(aPref->name().get());
+ if (!entry)
+ return;
+
+ if (pref_EntryHasAdvisablySizedValues(entry)) {
+ pref_GetPrefFromEntry(entry, aPref);
+ }
+}
+
+void
+Preferences::GetPreferences(InfallibleTArray<PrefSetting>* aPrefs)
+{
+ aPrefs->SetCapacity(gHashTable->Capacity());
+ for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PrefHashEntry*>(iter.Get());
+
+ if (!pref_EntryHasAdvisablySizedValues(entry)) {
+ continue;
+ }
+
+ dom::PrefSetting *pref = aPrefs->AppendElement();
+ pref_GetPrefFromEntry(entry, pref);
+ }
+}
+
+NS_IMETHODIMP
+Preferences::GetBranch(const char *aPrefRoot, nsIPrefBranch **_retval)
+{
+ nsresult rv;
+
+ if ((nullptr != aPrefRoot) && (*aPrefRoot != '\0')) {
+ // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think)
+ RefPtr<nsPrefBranch> prefBranch = new nsPrefBranch(aPrefRoot, false);
+ prefBranch.forget(_retval);
+ rv = NS_OK;
+ } else {
+ // special case caching the default root
+ nsCOMPtr<nsIPrefBranch> root(sRootBranch);
+ root.forget(_retval);
+ rv = NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+Preferences::GetDefaultBranch(const char *aPrefRoot, nsIPrefBranch **_retval)
+{
+ if (!aPrefRoot || !aPrefRoot[0]) {
+ nsCOMPtr<nsIPrefBranch> root(sDefaultRootBranch);
+ root.forget(_retval);
+ return NS_OK;
+ }
+
+ // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think)
+ RefPtr<nsPrefBranch> prefBranch = new nsPrefBranch(aPrefRoot, true);
+ if (!prefBranch)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ prefBranch.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Preferences::GetDirty(bool *_retval) {
+ *_retval = mDirty;
+ return NS_OK;
+}
+
+nsresult
+Preferences::NotifyServiceObservers(const char *aTopic)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return NS_ERROR_FAILURE;
+
+ nsISupports *subject = (nsISupports *)((nsIPrefService *)this);
+ observerService->NotifyObservers(subject, aTopic, nullptr);
+
+ return NS_OK;
+}
+
+nsresult
+Preferences::UseDefaultPrefFile()
+{
+ nsCOMPtr<nsIFile> aFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE, getter_AddRefs(aFile));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = ReadAndOwnUserPrefFile(aFile);
+ // Most likely cause of failure here is that the file didn't
+ // exist, so save a new one. mUserPrefReadFailed will be
+ // used to catch an error in actually reading the file.
+ if (NS_FAILED(rv)) {
+ if (NS_FAILED(SavePrefFileInternal(aFile)))
+ NS_ERROR("Failed to save new shared pref file");
+ else
+ rv = NS_OK;
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+Preferences::UseUserPrefFile()
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> aFile;
+ nsDependentCString prefsDirProp(NS_APP_PREFS_50_DIR);
+
+ rv = NS_GetSpecialDirectory(prefsDirProp.get(), getter_AddRefs(aFile));
+ if (NS_SUCCEEDED(rv) && aFile) {
+ rv = aFile->AppendNative(NS_LITERAL_CSTRING("user.js"));
+ if (NS_SUCCEEDED(rv)) {
+ bool exists = false;
+ aFile->Exists(&exists);
+ if (exists) {
+ rv = openPrefFile(aFile);
+ } else {
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult
+Preferences::MakeBackupPrefFile(nsIFile *aFile)
+{
+ // Example: this copies "prefs.js" to "Invalidprefs.js" in the same directory.
+ // "Invalidprefs.js" is removed if it exists, prior to making the copy.
+ nsAutoString newFilename;
+ nsresult rv = aFile->GetLeafName(newFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newFilename.Insert(NS_LITERAL_STRING("Invalid"), 0);
+ nsCOMPtr<nsIFile> newFile;
+ rv = aFile->GetParent(getter_AddRefs(newFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = newFile->Append(newFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists = false;
+ newFile->Exists(&exists);
+ if (exists) {
+ rv = newFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = aFile->CopyTo(nullptr, newFilename);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+nsresult
+Preferences::ReadAndOwnUserPrefFile(nsIFile *aFile)
+{
+ NS_ENSURE_ARG(aFile);
+
+ if (mCurrentFile == aFile)
+ return NS_OK;
+ mCurrentFile = aFile;
+
+ nsresult rv = NS_OK;
+ bool exists = false;
+ mCurrentFile->Exists(&exists);
+ if (exists) {
+ rv = openPrefFile(mCurrentFile);
+ if (NS_FAILED(rv)) {
+ // Save a backup copy of the current (invalid) prefs file, since all prefs
+ // from the error line to the end of the file will be lost (bug 361102).
+ // TODO we should notify the user about it (bug 523725).
+ MakeBackupPrefFile(mCurrentFile);
+ }
+ } else {
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ return rv;
+}
+
+nsresult
+Preferences::SavePrefFileInternal(nsIFile *aFile)
+{
+ if (nullptr == aFile) {
+ // the mDirty flag tells us if we should write to mCurrentFile
+ // we only check this flag when the caller wants to write to the default
+ if (!mDirty) {
+ return NS_OK;
+ }
+
+ // It's possible that we never got a prefs file.
+ nsresult rv = NS_OK;
+ if (mCurrentFile)
+ rv = WritePrefFile(mCurrentFile);
+
+ return rv;
+ } else {
+ return WritePrefFile(aFile);
+ }
+}
+
+nsresult
+Preferences::WritePrefFile(nsIFile* aFile)
+{
+ nsCOMPtr<nsIOutputStream> outStreamSink;
+ nsCOMPtr<nsIOutputStream> outStream;
+ uint32_t writeAmount;
+ nsresult rv;
+
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // execute a "safe" save by saving through a tempfile
+ rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink),
+ aFile,
+ -1,
+ 0600);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream), outStreamSink, 4096);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // get the lines that we're supposed to be writing to the file
+ uint32_t prefCount;
+ UniquePtr<char*[]> valueArray = pref_savePrefs(gHashTable, &prefCount);
+
+ /* Sort the preferences to make a readable file on disk */
+ NS_QuickSort(valueArray.get(), prefCount, sizeof(char *),
+ pref_CompareStrings, nullptr);
+
+ // write out the file header
+ outStream->Write(kPrefFileHeader, sizeof(kPrefFileHeader) - 1, &writeAmount);
+
+ for (uint32_t valueIdx = 0; valueIdx < prefCount; valueIdx++) {
+ char*& pref = valueArray[valueIdx];
+ MOZ_ASSERT(pref);
+ outStream->Write(pref, strlen(pref), &writeAmount);
+ outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount);
+ free(pref);
+ pref = nullptr;
+ }
+
+ // tell the safe output stream to overwrite the real prefs file
+ // (it'll abort if there were any errors during writing)
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream);
+ NS_ASSERTION(safeStream, "expected a safe output stream!");
+ if (safeStream) {
+ rv = safeStream->Finish();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to save prefs file! possible data loss");
+ return rv;
+ }
+ }
+
+ mDirty = false;
+ return NS_OK;
+}
+
+static nsresult openPrefFile(nsIFile* aFile)
+{
+ nsCOMPtr<nsIInputStream> inStr;
+
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), aFile);
+ if (NS_FAILED(rv))
+ return rv;
+
+ int64_t fileSize64;
+ rv = aFile->GetFileSize(&fileSize64);
+ if (NS_FAILED(rv))
+ return rv;
+ NS_ENSURE_TRUE(fileSize64 <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
+
+ uint32_t fileSize = (uint32_t)fileSize64;
+ auto fileBuffer = MakeUniqueFallible<char[]>(fileSize);
+ if (fileBuffer == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ PrefParseState ps;
+ PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr);
+
+ // Read is not guaranteed to return a buf the size of fileSize,
+ // but usually will.
+ nsresult rv2 = NS_OK;
+ uint32_t offset = 0;
+ for (;;) {
+ uint32_t amtRead = 0;
+ rv = inStr->Read(fileBuffer.get(), fileSize, &amtRead);
+ if (NS_FAILED(rv) || amtRead == 0)
+ break;
+ if (!PREF_ParseBuf(&ps, fileBuffer.get(), amtRead))
+ rv2 = NS_ERROR_FILE_CORRUPTED;
+ offset += amtRead;
+ if (offset == fileSize) {
+ break;
+ }
+ }
+
+ PREF_FinalizeParseState(&ps);
+
+ return NS_FAILED(rv) ? rv : rv2;
+}
+
+/*
+ * some stuff that gets called from Pref_Init()
+ */
+
+static int
+pref_CompareFileNames(nsIFile* aFile1, nsIFile* aFile2, void* /*unused*/)
+{
+ nsAutoCString filename1, filename2;
+ aFile1->GetNativeLeafName(filename1);
+ aFile2->GetNativeLeafName(filename2);
+
+ return Compare(filename2, filename1);
+}
+
+/**
+ * Load default pref files from a directory. The files in the
+ * directory are sorted reverse-alphabetically; a set of "special file
+ * names" may be specified which are loaded after all the others.
+ */
+static nsresult
+pref_LoadPrefsInDir(nsIFile* aDir, char const *const *aSpecialFiles, uint32_t aSpecialFilesCount)
+{
+ nsresult rv, rv2;
+ bool hasMoreElements;
+
+ nsCOMPtr<nsISimpleEnumerator> dirIterator;
+
+ // this may fail in some normal cases, such as embedders who do not use a GRE
+ rv = aDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
+ if (NS_FAILED(rv)) {
+ // If the directory doesn't exist, then we have no reason to complain. We
+ // loaded everything (and nothing) successfully.
+ if (rv == NS_ERROR_FILE_NOT_FOUND || rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
+ rv = NS_OK;
+ return rv;
+ }
+
+ rv = dirIterator->HasMoreElements(&hasMoreElements);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMArray<nsIFile> prefFiles(INITIAL_PREF_FILES);
+ nsCOMArray<nsIFile> specialFiles(aSpecialFilesCount);
+ nsCOMPtr<nsIFile> prefFile;
+
+ while (hasMoreElements && NS_SUCCEEDED(rv)) {
+ nsAutoCString leafName;
+
+ nsCOMPtr<nsISupports> supports;
+ rv = dirIterator->GetNext(getter_AddRefs(supports));
+ prefFile = do_QueryInterface(supports);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ prefFile->GetNativeLeafName(leafName);
+ NS_ASSERTION(!leafName.IsEmpty(), "Failure in default prefs: directory enumerator returned empty file?");
+
+ // Skip non-js files
+ if (StringEndsWith(leafName, NS_LITERAL_CSTRING(".js"),
+ nsCaseInsensitiveCStringComparator())) {
+ bool shouldParse = true;
+ // separate out special files
+ for (uint32_t i = 0; i < aSpecialFilesCount; ++i) {
+ if (leafName.Equals(nsDependentCString(aSpecialFiles[i]))) {
+ shouldParse = false;
+ // special files should be process in order; we put them into
+ // the array by index; this can make the array sparse
+ specialFiles.ReplaceObjectAt(prefFile, i);
+ }
+ }
+
+ if (shouldParse) {
+ prefFiles.AppendObject(prefFile);
+ }
+ }
+
+ rv = dirIterator->HasMoreElements(&hasMoreElements);
+ }
+
+ if (prefFiles.Count() + specialFiles.Count() == 0) {
+ NS_WARNING("No default pref files found.");
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_SUCCESS_FILE_DIRECTORY_EMPTY;
+ }
+ return rv;
+ }
+
+ prefFiles.Sort(pref_CompareFileNames, nullptr);
+
+ uint32_t arrayCount = prefFiles.Count();
+ uint32_t i;
+ for (i = 0; i < arrayCount; ++i) {
+ rv2 = openPrefFile(prefFiles[i]);
+ if (NS_FAILED(rv2)) {
+ NS_ERROR("Default pref file not parsed successfully.");
+ rv = rv2;
+ }
+ }
+
+ arrayCount = specialFiles.Count();
+ for (i = 0; i < arrayCount; ++i) {
+ // this may be a sparse array; test before parsing
+ nsIFile* file = specialFiles[i];
+ if (file) {
+ rv2 = openPrefFile(file);
+ if (NS_FAILED(rv2)) {
+ NS_ERROR("Special default pref file not parsed successfully.");
+ rv = rv2;
+ }
+ }
+ }
+
+ return rv;
+}
+
+static nsresult pref_LoadPrefsInDirList(const char *listId)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProperties> dirSvc(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> list;
+ dirSvc->Get(listId,
+ NS_GET_IID(nsISimpleEnumerator),
+ getter_AddRefs(list));
+ if (!list)
+ return NS_OK;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(list->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> elem;
+ list->GetNext(getter_AddRefs(elem));
+ if (!elem)
+ continue;
+
+ nsCOMPtr<nsIFile> path = do_QueryInterface(elem);
+ if (!path)
+ continue;
+
+ nsAutoCString leaf;
+ path->GetNativeLeafName(leaf);
+
+ // Do we care if a file provided by this process fails to load?
+ if (Substring(leaf, leaf.Length() - 4).EqualsLiteral(".xpi"))
+ ReadExtensionPrefs(path);
+ else
+ pref_LoadPrefsInDir(path, nullptr, 0);
+ }
+ return NS_OK;
+}
+
+static nsresult pref_ReadPrefFromJar(nsZipArchive* jarReader, const char *name)
+{
+ nsZipItemPtr<char> manifest(jarReader, name, true);
+ NS_ENSURE_TRUE(manifest.Buffer(), NS_ERROR_NOT_AVAILABLE);
+
+ PrefParseState ps;
+ PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr);
+ PREF_ParseBuf(&ps, manifest, manifest.Length());
+ PREF_FinalizeParseState(&ps);
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------------------
+// Initialize default preference JavaScript buffers from
+// appropriate TEXT resources
+//----------------------------------------------------------------------------------------
+static nsresult pref_InitInitialObjects()
+{
+ nsresult rv;
+
+ // In omni.jar case, we load the following prefs:
+ // - jar:$gre/omni.jar!/goanna.js
+ // - jar:$gre/omni.jar!/defaults/pref/*.js
+ // In non omni.jar case, we load:
+ // - $gre/goanna.js
+ //
+ // In both cases, we also load:
+ // - $gre/defaults/pref/*.js
+ // This is kept for bug 591866 (channel-prefs.js should not be in omni.jar)
+ // on $app == $gre case ; we load all files instead of channel-prefs.js only
+ // to have the same behaviour as $app != $gre, where this is required as
+ // a supported location for GRE preferences.
+ //
+ // When $app != $gre, we additionally load, in omni.jar case:
+ // - jar:$app/omni.jar!/defaults/preferences/*.js
+ // - $app/defaults/preferences/*.js
+ // and in non omni.jar case:
+ // - $app/defaults/preferences/*.js
+ // When $app == $gre, we additionally load, in omni.jar case:
+ // - jar:$gre/omni.jar!/defaults/preferences/*.js
+ // Thus, in omni.jar case, we always load app-specific default preferences
+ // from omni.jar, whether or not $app == $gre.
+
+ nsZipFind *findPtr;
+ nsAutoPtr<nsZipFind> find;
+ nsTArray<nsCString> prefEntries;
+ const char *entryName;
+ uint16_t entryNameLen;
+
+ RefPtr<nsZipArchive> jarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
+ if (jarReader) {
+ // Load jar:$gre/omni.jar!/goanna.js
+ rv = pref_ReadPrefFromJar(jarReader, "goanna.js");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Load jar:$gre/omni.jar!/defaults/pref/*.js
+ rv = jarReader->FindInit("defaults/pref/*.js$", &findPtr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ find = findPtr;
+ while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
+ prefEntries.AppendElement(Substring(entryName, entryNameLen));
+ }
+
+ prefEntries.Sort();
+ for (uint32_t i = prefEntries.Length(); i--; ) {
+ rv = pref_ReadPrefFromJar(jarReader, prefEntries[i].get());
+ if (NS_FAILED(rv))
+ NS_WARNING("Error parsing preferences.");
+ }
+ } else {
+ // Load $gre/goanna.js
+ nsCOMPtr<nsIFile> greprefsFile;
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greprefsFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = greprefsFile->AppendNative(NS_LITERAL_CSTRING("goanna.js"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = openPrefFile(greprefsFile);
+ if (NS_FAILED(rv))
+ NS_WARNING("Error parsing GRE default preferences. Is this an old-style embedding app?");
+ }
+
+ // Load $gre/defaults/pref/*.js
+ nsCOMPtr<nsIFile> defaultPrefDir;
+
+ rv = NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR, getter_AddRefs(defaultPrefDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* these pref file names should not be used: we process them after all other application pref files for backwards compatibility */
+ static const char* specialFiles[] = {
+#if defined(XP_WIN)
+ "winpref.js"
+#elif defined(XP_UNIX)
+ "unix.js"
+#endif
+ };
+
+ rv = pref_LoadPrefsInDir(defaultPrefDir, specialFiles, ArrayLength(specialFiles));
+ if (NS_FAILED(rv))
+ NS_WARNING("Error parsing application default preferences.");
+
+ // Load jar:$app/omni.jar!/defaults/preferences/*.js
+ // or jar:$gre/omni.jar!/defaults/preferences/*.js.
+ RefPtr<nsZipArchive> appJarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
+ // GetReader(mozilla::Omnijar::APP) returns null when $app == $gre, in which
+ // case we look for app-specific default preferences in $gre.
+ if (!appJarReader)
+ appJarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
+ if (appJarReader) {
+ rv = appJarReader->FindInit("defaults/preferences/*.js$", &findPtr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ find = findPtr;
+ prefEntries.Clear();
+ while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) {
+ prefEntries.AppendElement(Substring(entryName, entryNameLen));
+ }
+ prefEntries.Sort();
+ for (uint32_t i = prefEntries.Length(); i--; ) {
+ rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get());
+ if (NS_FAILED(rv))
+ NS_WARNING("Error parsing preferences.");
+ }
+ }
+
+ rv = pref_LoadPrefsInDirList(NS_APP_PREFS_DEFAULTS_DIR_LIST);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID,
+ nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService)
+ return NS_ERROR_FAILURE;
+
+ observerService->NotifyObservers(nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, nullptr);
+
+ return pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST);
+}
+
+
+/******************************************************************************
+ *
+ * static utilities
+ *
+ ******************************************************************************/
+
+// static
+nsresult
+Preferences::GetBool(const char* aPref, bool* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_GetBoolPref(aPref, aResult, false);
+}
+
+// static
+nsresult
+Preferences::GetInt(const char* aPref, int32_t* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_GetIntPref(aPref, aResult, false);
+}
+
+// static
+nsresult
+Preferences::GetFloat(const char* aPref, float* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ nsAutoCString result;
+ nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), false);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = result.ToFloat(&rv);
+ }
+
+ return rv;
+}
+
+// static
+nsAdoptingCString
+Preferences::GetCString(const char* aPref)
+{
+ nsAdoptingCString result;
+ PREF_CopyCharPref(aPref, getter_Copies(result), false);
+ return result;
+}
+
+// static
+nsAdoptingString
+Preferences::GetString(const char* aPref)
+{
+ nsAdoptingString result;
+ GetString(aPref, &result);
+ return result;
+}
+
+// static
+nsresult
+Preferences::GetCString(const char* aPref, nsACString* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ nsAutoCString result;
+ nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), false);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = result;
+ }
+ return rv;
+}
+
+// static
+nsresult
+Preferences::GetString(const char* aPref, nsAString* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ nsAutoCString result;
+ nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), false);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(result, *aResult);
+ }
+ return rv;
+}
+
+// static
+nsAdoptingCString
+Preferences::GetLocalizedCString(const char* aPref)
+{
+ nsAdoptingCString result;
+ GetLocalizedCString(aPref, &result);
+ return result;
+}
+
+// static
+nsAdoptingString
+Preferences::GetLocalizedString(const char* aPref)
+{
+ nsAdoptingString result;
+ GetLocalizedString(aPref, &result);
+ return result;
+}
+
+// static
+nsresult
+Preferences::GetLocalizedCString(const char* aPref, nsACString* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ nsAutoString result;
+ nsresult rv = GetLocalizedString(aPref, &result);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF16toUTF8(result, *aResult);
+ }
+ return rv;
+}
+
+// static
+nsresult
+Preferences::GetLocalizedString(const char* aPref, nsAString* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ nsCOMPtr<nsIPrefLocalizedString> prefLocalString;
+ nsresult rv = sRootBranch->GetComplexValue(aPref,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(prefLocalString));
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(prefLocalString, "Succeeded but the result is NULL");
+ prefLocalString->GetData(getter_Copies(*aResult));
+ }
+ return rv;
+}
+
+// static
+nsresult
+Preferences::GetComplex(const char* aPref, const nsIID &aType, void** aResult)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return sRootBranch->GetComplexValue(aPref, aType, aResult);
+}
+
+// static
+nsresult
+Preferences::SetCString(const char* aPref, const char* aValue)
+{
+ ENSURE_MAIN_PROCESS("Cannot SetCString from content process:", aPref);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_SetCharPref(aPref, aValue, false);
+}
+
+// static
+nsresult
+Preferences::SetCString(const char* aPref, const nsACString &aValue)
+{
+ ENSURE_MAIN_PROCESS("Cannot SetCString from content process:", aPref);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_SetCharPref(aPref, PromiseFlatCString(aValue).get(), false);
+}
+
+// static
+nsresult
+Preferences::SetString(const char* aPref, const char16ptr_t aValue)
+{
+ ENSURE_MAIN_PROCESS("Cannot SetString from content process:", aPref);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_SetCharPref(aPref, NS_ConvertUTF16toUTF8(aValue).get(), false);
+}
+
+// static
+nsresult
+Preferences::SetString(const char* aPref, const nsAString &aValue)
+{
+ ENSURE_MAIN_PROCESS("Cannot SetString from content process:", aPref);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_SetCharPref(aPref, NS_ConvertUTF16toUTF8(aValue).get(), false);
+}
+
+// static
+nsresult
+Preferences::SetBool(const char* aPref, bool aValue)
+{
+ ENSURE_MAIN_PROCESS("Cannot SetBool from content process:", aPref);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_SetBoolPref(aPref, aValue, false);
+}
+
+// static
+nsresult
+Preferences::SetInt(const char* aPref, int32_t aValue)
+{
+ ENSURE_MAIN_PROCESS("Cannot SetInt from content process:", aPref);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_SetIntPref(aPref, aValue, false);
+}
+
+// static
+nsresult
+Preferences::SetFloat(const char* aPref, float aValue)
+{
+ return SetCString(aPref, nsPrintfCString("%f", aValue).get());
+}
+
+// static
+nsresult
+Preferences::SetComplex(const char* aPref, const nsIID &aType,
+ nsISupports* aValue)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return sRootBranch->SetComplexValue(aPref, aType, aValue);
+}
+
+// static
+nsresult
+Preferences::ClearUser(const char* aPref)
+{
+ ENSURE_MAIN_PROCESS("Cannot ClearUser from content process:", aPref);
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_ClearUserPref(aPref);
+}
+
+// static
+bool
+Preferences::HasUserValue(const char* aPref)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), false);
+ return PREF_HasUserPref(aPref);
+}
+
+// static
+int32_t
+Preferences::GetType(const char* aPref)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
+ int32_t result;
+ return NS_SUCCEEDED(sRootBranch->GetPrefType(aPref, &result)) ?
+ result : nsIPrefBranch::PREF_INVALID;
+}
+
+// static
+nsresult
+Preferences::AddStrongObserver(nsIObserver* aObserver,
+ const char* aPref)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return sRootBranch->AddObserver(aPref, aObserver, false);
+}
+
+// static
+nsresult
+Preferences::AddWeakObserver(nsIObserver* aObserver,
+ const char* aPref)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return sRootBranch->AddObserver(aPref, aObserver, true);
+}
+
+// static
+nsresult
+Preferences::RemoveObserver(nsIObserver* aObserver,
+ const char* aPref)
+{
+ if (!sPreferences && sShutdown) {
+ return NS_OK; // Observers have been released automatically.
+ }
+ NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
+ return sRootBranch->RemoveObserver(aPref, aObserver);
+}
+
+// static
+nsresult
+Preferences::AddStrongObservers(nsIObserver* aObserver,
+ const char** aPrefs)
+{
+ for (uint32_t i = 0; aPrefs[i]; i++) {
+ nsresult rv = AddStrongObserver(aObserver, aPrefs[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+// static
+nsresult
+Preferences::AddWeakObservers(nsIObserver* aObserver,
+ const char** aPrefs)
+{
+ for (uint32_t i = 0; aPrefs[i]; i++) {
+ nsresult rv = AddWeakObserver(aObserver, aPrefs[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+// static
+nsresult
+Preferences::RemoveObservers(nsIObserver* aObserver,
+ const char** aPrefs)
+{
+ if (!sPreferences && sShutdown) {
+ return NS_OK; // Observers have been released automatically.
+ }
+ NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
+
+ for (uint32_t i = 0; aPrefs[i]; i++) {
+ nsresult rv = RemoveObserver(aObserver, aPrefs[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+// static
+nsresult
+Preferences::RegisterCallback(PrefChangedFunc aCallback,
+ const char* aPref,
+ void* aClosure,
+ MatchKind aMatchKind)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+
+ ValueObserverHashKey hashKey(aPref, aCallback, aMatchKind);
+ RefPtr<ValueObserver> observer;
+ gObserverTable->Get(&hashKey, getter_AddRefs(observer));
+ if (observer) {
+ observer->AppendClosure(aClosure);
+ return NS_OK;
+ }
+
+ observer = new ValueObserver(aPref, aCallback, aMatchKind);
+ observer->AppendClosure(aClosure);
+ nsresult rv = AddStrongObserver(observer, aPref);
+ NS_ENSURE_SUCCESS(rv, rv);
+ gObserverTable->Put(observer, observer);
+ return NS_OK;
+}
+
+// static
+nsresult
+Preferences::RegisterCallbackAndCall(PrefChangedFunc aCallback,
+ const char* aPref,
+ void* aClosure,
+ MatchKind aMatchKind)
+{
+ nsresult rv = RegisterCallback(aCallback, aPref, aClosure, aMatchKind);
+ if (NS_SUCCEEDED(rv)) {
+ (*aCallback)(aPref, aClosure);
+ }
+ return rv;
+}
+
+// static
+nsresult
+Preferences::UnregisterCallback(PrefChangedFunc aCallback,
+ const char* aPref,
+ void* aClosure,
+ MatchKind aMatchKind)
+{
+ if (!sPreferences && sShutdown) {
+ return NS_OK; // Observers have been released automatically.
+ }
+ NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE);
+
+ ValueObserverHashKey hashKey(aPref, aCallback, aMatchKind);
+ RefPtr<ValueObserver> observer;
+ gObserverTable->Get(&hashKey, getter_AddRefs(observer));
+ if (!observer) {
+ return NS_OK;
+ }
+
+ observer->RemoveClosure(aClosure);
+ if (observer->HasNoClosures()) {
+ // Delete the callback since its list of closures is empty.
+ gObserverTable->Remove(observer);
+ }
+ return NS_OK;
+}
+
+static void BoolVarChanged(const char* aPref, void* aClosure)
+{
+ CacheData* cache = static_cast<CacheData*>(aClosure);
+ *((bool*)cache->cacheLocation) =
+ Preferences::GetBool(aPref, cache->defaultValueBool);
+}
+
+// static
+nsresult
+Preferences::AddBoolVarCache(bool* aCache,
+ const char* aPref,
+ bool aDefault)
+{
+ NS_ASSERTION(aCache, "aCache must not be NULL");
+#ifdef DEBUG
+ AssertNotAlreadyCached("bool", aPref, aCache);
+#endif
+ *aCache = GetBool(aPref, aDefault);
+ CacheData* data = new CacheData();
+ data->cacheLocation = aCache;
+ data->defaultValueBool = aDefault;
+ gCacheData->AppendElement(data);
+ return RegisterCallback(BoolVarChanged, aPref, data, ExactMatch);
+}
+
+static void IntVarChanged(const char* aPref, void* aClosure)
+{
+ CacheData* cache = static_cast<CacheData*>(aClosure);
+ *((int32_t*)cache->cacheLocation) =
+ Preferences::GetInt(aPref, cache->defaultValueInt);
+}
+
+// static
+nsresult
+Preferences::AddIntVarCache(int32_t* aCache,
+ const char* aPref,
+ int32_t aDefault)
+{
+ NS_ASSERTION(aCache, "aCache must not be NULL");
+#ifdef DEBUG
+ AssertNotAlreadyCached("int", aPref, aCache);
+#endif
+ *aCache = Preferences::GetInt(aPref, aDefault);
+ CacheData* data = new CacheData();
+ data->cacheLocation = aCache;
+ data->defaultValueInt = aDefault;
+ gCacheData->AppendElement(data);
+ return RegisterCallback(IntVarChanged, aPref, data, ExactMatch);
+}
+
+static void UintVarChanged(const char* aPref, void* aClosure)
+{
+ CacheData* cache = static_cast<CacheData*>(aClosure);
+ *((uint32_t*)cache->cacheLocation) =
+ Preferences::GetUint(aPref, cache->defaultValueUint);
+}
+
+// static
+nsresult
+Preferences::AddUintVarCache(uint32_t* aCache,
+ const char* aPref,
+ uint32_t aDefault)
+{
+ NS_ASSERTION(aCache, "aCache must not be NULL");
+#ifdef DEBUG
+ AssertNotAlreadyCached("uint", aPref, aCache);
+#endif
+ *aCache = Preferences::GetUint(aPref, aDefault);
+ CacheData* data = new CacheData();
+ data->cacheLocation = aCache;
+ data->defaultValueUint = aDefault;
+ gCacheData->AppendElement(data);
+ return RegisterCallback(UintVarChanged, aPref, data, ExactMatch);
+}
+
+template <MemoryOrdering Order>
+static void AtomicUintVarChanged(const char* aPref, void* aClosure)
+{
+ CacheData* cache = static_cast<CacheData*>(aClosure);
+ *((Atomic<uint32_t, Order>*)cache->cacheLocation) =
+ Preferences::GetUint(aPref, cache->defaultValueUint);
+}
+
+template <MemoryOrdering Order>
+// static
+nsresult
+Preferences::AddAtomicUintVarCache(Atomic<uint32_t, Order>* aCache,
+ const char* aPref,
+ uint32_t aDefault)
+{
+ NS_ASSERTION(aCache, "aCache must not be NULL");
+#ifdef DEBUG
+ AssertNotAlreadyCached("uint", aPref, aCache);
+#endif
+ *aCache = Preferences::GetUint(aPref, aDefault);
+ CacheData* data = new CacheData();
+ data->cacheLocation = aCache;
+ data->defaultValueUint = aDefault;
+ gCacheData->AppendElement(data);
+ return RegisterCallback(AtomicUintVarChanged<Order>, aPref, data, ExactMatch);
+}
+
+// Since the definition of this template function is not in a header file,
+// we need to explicitly specify the instantiations that are required.
+// Currently only the order=Relaxed variant is needed.
+template
+nsresult Preferences::AddAtomicUintVarCache(Atomic<uint32_t,Relaxed>*,
+ const char*, uint32_t);
+
+static void FloatVarChanged(const char* aPref, void* aClosure)
+{
+ CacheData* cache = static_cast<CacheData*>(aClosure);
+ *((float*)cache->cacheLocation) =
+ Preferences::GetFloat(aPref, cache->defaultValueFloat);
+}
+
+// static
+nsresult
+Preferences::AddFloatVarCache(float* aCache,
+ const char* aPref,
+ float aDefault)
+{
+ NS_ASSERTION(aCache, "aCache must not be NULL");
+#ifdef DEBUG
+ AssertNotAlreadyCached("float", aPref, aCache);
+#endif
+ *aCache = Preferences::GetFloat(aPref, aDefault);
+ CacheData* data = new CacheData();
+ data->cacheLocation = aCache;
+ data->defaultValueFloat = aDefault;
+ gCacheData->AppendElement(data);
+ return RegisterCallback(FloatVarChanged, aPref, data, ExactMatch);
+}
+
+// static
+nsresult
+Preferences::GetDefaultBool(const char* aPref, bool* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_GetBoolPref(aPref, aResult, true);
+}
+
+// static
+nsresult
+Preferences::GetDefaultInt(const char* aPref, int32_t* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return PREF_GetIntPref(aPref, aResult, true);
+}
+
+// static
+nsresult
+Preferences::GetDefaultCString(const char* aPref, nsACString* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ nsAutoCString result;
+ nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), true);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = result;
+ }
+ return rv;
+}
+
+// static
+nsresult
+Preferences::GetDefaultString(const char* aPref, nsAString* aResult)
+{
+ NS_PRECONDITION(aResult, "aResult must not be NULL");
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ nsAutoCString result;
+ nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), true);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(result, *aResult);
+ }
+ return rv;
+}
+
+// static
+nsresult
+Preferences::GetDefaultLocalizedCString(const char* aPref,
+ nsACString* aResult)
+{
+ nsAutoString result;
+ nsresult rv = GetDefaultLocalizedString(aPref, &result);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF16toUTF8(result, *aResult);
+ }
+ return rv;
+}
+
+// static
+nsresult
+Preferences::GetDefaultLocalizedString(const char* aPref,
+ nsAString* aResult)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ nsCOMPtr<nsIPrefLocalizedString> prefLocalString;
+ nsresult rv =
+ sDefaultRootBranch->GetComplexValue(aPref,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(prefLocalString));
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(prefLocalString, "Succeeded but the result is NULL");
+ prefLocalString->GetData(getter_Copies(*aResult));
+ }
+ return rv;
+}
+
+// static
+nsAdoptingString
+Preferences::GetDefaultString(const char* aPref)
+{
+ nsAdoptingString result;
+ GetDefaultString(aPref, &result);
+ return result;
+}
+
+// static
+nsAdoptingCString
+Preferences::GetDefaultCString(const char* aPref)
+{
+ nsAdoptingCString result;
+ PREF_CopyCharPref(aPref, getter_Copies(result), true);
+ return result;
+}
+
+// static
+nsAdoptingString
+Preferences::GetDefaultLocalizedString(const char* aPref)
+{
+ nsAdoptingString result;
+ GetDefaultLocalizedString(aPref, &result);
+ return result;
+}
+
+// static
+nsAdoptingCString
+Preferences::GetDefaultLocalizedCString(const char* aPref)
+{
+ nsAdoptingCString result;
+ GetDefaultLocalizedCString(aPref, &result);
+ return result;
+}
+
+// static
+nsresult
+Preferences::GetDefaultComplex(const char* aPref, const nsIID &aType,
+ void** aResult)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ return sDefaultRootBranch->GetComplexValue(aPref, aType, aResult);
+}
+
+// static
+int32_t
+Preferences::GetDefaultType(const char* aPref)
+{
+ NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
+ int32_t result;
+ return NS_SUCCEEDED(sDefaultRootBranch->GetPrefType(aPref, &result)) ?
+ result : nsIPrefBranch::PREF_INVALID;
+}
+
+} // namespace mozilla
+
+#undef ENSURE_MAIN_PROCESS
diff --git a/components/preferences/src/Preferences.h b/components/preferences/src/Preferences.h
new file mode 100644
index 000000000..255d2a8d2
--- /dev/null
+++ b/components/preferences/src/Preferences.h
@@ -0,0 +1,408 @@
+/* -*- 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 mozilla_Preferences_h
+#define mozilla_Preferences_h
+
+#ifndef MOZILLA_INTERNAL_API
+#error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)."
+#endif
+
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefBranchInternal.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+#include "mozilla/MemoryReporting.h"
+
+class nsIFile;
+class nsAdoptingString;
+class nsAdoptingCString;
+
+#ifndef have_PrefChangedFunc_typedef
+typedef void (*PrefChangedFunc)(const char *, void *);
+#define have_PrefChangedFunc_typedef
+#endif
+
+namespace mozilla {
+
+namespace dom {
+class PrefSetting;
+} // namespace dom
+
+class Preferences final : public nsIPrefService,
+ public nsIObserver,
+ public nsIPrefBranchInternal,
+ public nsSupportsWeakReference
+{
+public:
+ typedef mozilla::dom::PrefSetting PrefSetting;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPREFSERVICE
+ NS_FORWARD_NSIPREFBRANCH(sRootBranch->)
+ NS_DECL_NSIOBSERVER
+
+ Preferences();
+
+ nsresult Init();
+
+ /**
+ * Returns true if the Preferences service is available, false otherwise.
+ */
+ static bool IsServiceAvailable();
+
+ /**
+ * Reset loaded user prefs then read them
+ */
+ static nsresult ResetAndReadUserPrefs();
+
+ /**
+ * Returns the singleton instance which is addreffed.
+ */
+ static Preferences* GetInstanceForService();
+
+ /**
+ * Finallizes global members.
+ */
+ static void Shutdown();
+
+ /**
+ * Returns shared pref service instance
+ * NOTE: not addreffed.
+ */
+ static nsIPrefService* GetService()
+ {
+ NS_ENSURE_TRUE(InitStaticMembers(), nullptr);
+ return sPreferences;
+ }
+
+ /**
+ * Returns shared pref branch instance.
+ * NOTE: not addreffed.
+ */
+ static nsIPrefBranch* GetRootBranch()
+ {
+ NS_ENSURE_TRUE(InitStaticMembers(), nullptr);
+ return sRootBranch;
+ }
+
+ /**
+ * Returns shared default pref branch instance.
+ * NOTE: not addreffed.
+ */
+ static nsIPrefBranch* GetDefaultRootBranch()
+ {
+ NS_ENSURE_TRUE(InitStaticMembers(), nullptr);
+ return sDefaultRootBranch;
+ }
+
+ /**
+ * Gets int or bool type pref value with default value if failed to get
+ * the pref.
+ */
+ static bool GetBool(const char* aPref, bool aDefault = false)
+ {
+ bool result = aDefault;
+ GetBool(aPref, &result);
+ return result;
+ }
+
+ static int32_t GetInt(const char* aPref, int32_t aDefault = 0)
+ {
+ int32_t result = aDefault;
+ GetInt(aPref, &result);
+ return result;
+ }
+
+ static uint32_t GetUint(const char* aPref, uint32_t aDefault = 0)
+ {
+ uint32_t result = aDefault;
+ GetUint(aPref, &result);
+ return result;
+ }
+
+ static float GetFloat(const char* aPref, float aDefault = 0)
+ {
+ float result = aDefault;
+ GetFloat(aPref, &result);
+ return result;
+ }
+
+ /**
+ * Gets char type pref value directly. If failed, the get() of result
+ * returns nullptr. Even if succeeded but the result was empty string, the
+ * get() does NOT return nullptr. So, you can check whether the method
+ * succeeded or not by:
+ *
+ * nsAdoptingString value = Prefereces::GetString("foo.bar");
+ * if (!value) {
+ * // failed
+ * }
+ *
+ * Be aware. If you wrote as:
+ *
+ * nsAutoString value = Preferences::GetString("foo.bar");
+ * if (!value.get()) {
+ * // the condition is always FALSE!!
+ * }
+ *
+ * The value.get() doesn't return nullptr. You must use nsAdoptingString
+ * when you need to check whether it was failure or not.
+ */
+ static nsAdoptingCString GetCString(const char* aPref);
+ static nsAdoptingString GetString(const char* aPref);
+ static nsAdoptingCString GetLocalizedCString(const char* aPref);
+ static nsAdoptingString GetLocalizedString(const char* aPref);
+
+ /**
+ * Gets int, float, or bool type pref value with raw return value of
+ * nsIPrefBranch.
+ *
+ * @param aPref A pref name.
+ * @param aResult Must not be nullptr. The value is never modified
+ * when these methods fail.
+ */
+ static nsresult GetBool(const char* aPref, bool* aResult);
+ static nsresult GetInt(const char* aPref, int32_t* aResult);
+ static nsresult GetFloat(const char* aPref, float* aResult);
+ static nsresult GetUint(const char* aPref, uint32_t* aResult)
+ {
+ int32_t result;
+ nsresult rv = GetInt(aPref, &result);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = static_cast<uint32_t>(result);
+ }
+ return rv;
+ }
+
+ /**
+ * Gets string type pref value with raw return value of nsIPrefBranch.
+ *
+ * @param aPref A pref name.
+ * @param aResult Must not be nullptr. The value is never modified
+ * when these methods fail.
+ */
+ static nsresult GetCString(const char* aPref, nsACString* aResult);
+ static nsresult GetString(const char* aPref, nsAString* aResult);
+ static nsresult GetLocalizedCString(const char* aPref, nsACString* aResult);
+ static nsresult GetLocalizedString(const char* aPref, nsAString* aResult);
+
+ static nsresult GetComplex(const char* aPref, const nsIID &aType,
+ void** aResult);
+
+ /**
+ * Sets various type pref values.
+ */
+ static nsresult SetBool(const char* aPref, bool aValue);
+ static nsresult SetInt(const char* aPref, int32_t aValue);
+ static nsresult SetUint(const char* aPref, uint32_t aValue)
+ {
+ return SetInt(aPref, static_cast<int32_t>(aValue));
+ }
+ static nsresult SetFloat(const char* aPref, float aValue);
+ static nsresult SetCString(const char* aPref, const char* aValue);
+ static nsresult SetCString(const char* aPref, const nsACString &aValue);
+ static nsresult SetString(const char* aPref, const char16ptr_t aValue);
+ static nsresult SetString(const char* aPref, const nsAString &aValue);
+
+ static nsresult SetComplex(const char* aPref, const nsIID &aType,
+ nsISupports* aValue);
+
+ /**
+ * Clears user set pref.
+ */
+ static nsresult ClearUser(const char* aPref);
+
+ /**
+ * Whether the pref has a user value or not.
+ */
+ static bool HasUserValue(const char* aPref);
+
+ /**
+ * Gets the type of the pref.
+ */
+ static int32_t GetType(const char* aPref);
+
+ /**
+ * Adds/Removes the observer for the root pref branch.
+ * The observer is referenced strongly if AddStrongObserver is used. On the
+ * other hand, it is referenced weakly, if AddWeakObserver is used.
+ * See nsIPrefBranch.idl for details.
+ */
+ static nsresult AddStrongObserver(nsIObserver* aObserver, const char* aPref);
+ static nsresult AddWeakObserver(nsIObserver* aObserver, const char* aPref);
+ static nsresult RemoveObserver(nsIObserver* aObserver, const char* aPref);
+
+ /**
+ * Adds/Removes two or more observers for the root pref branch.
+ * Pass to aPrefs an array of const char* whose last item is nullptr.
+ */
+ static nsresult AddStrongObservers(nsIObserver* aObserver,
+ const char** aPrefs);
+ static nsresult AddWeakObservers(nsIObserver* aObserver,
+ const char** aPrefs);
+ static nsresult RemoveObservers(nsIObserver* aObserver,
+ const char** aPrefs);
+
+ /**
+ * Registers/Unregisters the callback function for the aPref.
+ *
+ * Pass ExactMatch for aMatchKind to only get callbacks for
+ * exact matches and not prefixes.
+ */
+ enum MatchKind {
+ PrefixMatch,
+ ExactMatch,
+ };
+ static nsresult RegisterCallback(PrefChangedFunc aCallback,
+ const char* aPref,
+ void* aClosure = nullptr,
+ MatchKind aMatchKind = PrefixMatch);
+ static nsresult UnregisterCallback(PrefChangedFunc aCallback,
+ const char* aPref,
+ void* aClosure = nullptr,
+ MatchKind aMatchKind = PrefixMatch);
+ // Like RegisterCallback, but also calls the callback immediately for
+ // initialization.
+ static nsresult RegisterCallbackAndCall(PrefChangedFunc aCallback,
+ const char* aPref,
+ void* aClosure = nullptr,
+ MatchKind aMatchKind = PrefixMatch);
+
+ /**
+ * Adds the aVariable to cache table. aVariable must be a pointer for a
+ * static variable. The value will be modified when the pref value is
+ * changed but note that even if you modified it, the value isn't assigned to
+ * the pref.
+ */
+ static nsresult AddBoolVarCache(bool* aVariable,
+ const char* aPref,
+ bool aDefault = false);
+ static nsresult AddIntVarCache(int32_t* aVariable,
+ const char* aPref,
+ int32_t aDefault = 0);
+ static nsresult AddUintVarCache(uint32_t* aVariable,
+ const char* aPref,
+ uint32_t aDefault = 0);
+ template <MemoryOrdering Order>
+ static nsresult AddAtomicUintVarCache(Atomic<uint32_t, Order>* aVariable,
+ const char* aPref,
+ uint32_t aDefault = 0);
+ static nsresult AddFloatVarCache(float* aVariable,
+ const char* aPref,
+ float aDefault = 0.0f);
+
+ /**
+ * Gets the default bool, int or uint value of the pref.
+ * The result is raw result of nsIPrefBranch::Get*Pref().
+ * If the pref could have any value, you needed to use these methods.
+ * If not so, you could use below methods.
+ */
+ static nsresult GetDefaultBool(const char* aPref, bool* aResult);
+ static nsresult GetDefaultInt(const char* aPref, int32_t* aResult);
+ static nsresult GetDefaultUint(const char* aPref, uint32_t* aResult)
+ {
+ return GetDefaultInt(aPref, reinterpret_cast<int32_t*>(aResult));
+ }
+
+ /**
+ * Gets the default bool, int or uint value of the pref directly.
+ * You can set an invalid value of the pref to aFailedResult. If these
+ * methods failed to get the default value, they would return the
+ * aFailedResult value.
+ */
+ static bool GetDefaultBool(const char* aPref, bool aFailedResult)
+ {
+ bool result;
+ return NS_SUCCEEDED(GetDefaultBool(aPref, &result)) ? result :
+ aFailedResult;
+ }
+ static int32_t GetDefaultInt(const char* aPref, int32_t aFailedResult)
+ {
+ int32_t result;
+ return NS_SUCCEEDED(GetDefaultInt(aPref, &result)) ? result : aFailedResult;
+ }
+ static uint32_t GetDefaultUint(const char* aPref, uint32_t aFailedResult)
+ {
+ return static_cast<uint32_t>(
+ GetDefaultInt(aPref, static_cast<int32_t>(aFailedResult)));
+ }
+
+ /**
+ * Gets the default value of the char type pref.
+ * If the get() of the result returned nullptr, that meant the value didn't
+ * have default value.
+ *
+ * See the comment at definition at GetString() and GetCString() for more
+ * details of the result.
+ */
+ static nsAdoptingString GetDefaultString(const char* aPref);
+ static nsAdoptingCString GetDefaultCString(const char* aPref);
+ static nsAdoptingString GetDefaultLocalizedString(const char* aPref);
+ static nsAdoptingCString GetDefaultLocalizedCString(const char* aPref);
+
+ static nsresult GetDefaultCString(const char* aPref, nsACString* aResult);
+ static nsresult GetDefaultString(const char* aPref, nsAString* aResult);
+ static nsresult GetDefaultLocalizedCString(const char* aPref,
+ nsACString* aResult);
+ static nsresult GetDefaultLocalizedString(const char* aPref,
+ nsAString* aResult);
+
+ static nsresult GetDefaultComplex(const char* aPref, const nsIID &aType,
+ void** aResult);
+
+ /**
+ * Gets the type of the pref.
+ */
+ static int32_t GetDefaultType(const char* aPref);
+
+ // Used to synchronise preferences between chrome and content processes.
+ static void GetPreferences(InfallibleTArray<PrefSetting>* aPrefs);
+ static void GetPreference(PrefSetting* aPref);
+ static void SetPreference(const PrefSetting& aPref);
+
+ static int64_t SizeOfIncludingThisAndOtherStuff(mozilla::MallocSizeOf aMallocSizeOf);
+
+ static void DirtyCallback();
+
+protected:
+ virtual ~Preferences();
+
+ nsresult NotifyServiceObservers(const char *aSubject);
+ /**
+ * Reads the default pref file or, if that failed, try to save a new one.
+ *
+ * @return NS_OK if either action succeeded,
+ * or the error code related to the read attempt.
+ */
+ nsresult UseDefaultPrefFile();
+ nsresult UseUserPrefFile();
+ nsresult ReadAndOwnUserPrefFile(nsIFile *aFile);
+ nsresult ReadAndOwnSharedUserPrefFile(nsIFile *aFile);
+ nsresult SavePrefFileInternal(nsIFile* aFile);
+ nsresult WritePrefFile(nsIFile* aFile);
+ nsresult MakeBackupPrefFile(nsIFile *aFile);
+
+private:
+ nsCOMPtr<nsIFile> mCurrentFile;
+ bool mDirty;
+
+ static Preferences* sPreferences;
+ static nsIPrefBranch* sRootBranch;
+ static nsIPrefBranch* sDefaultRootBranch;
+ static bool sShutdown;
+
+ /**
+ * Init static members. TRUE if it succeeded. Otherwise, FALSE.
+ */
+ static bool InitStaticMembers();
+};
+
+} // namespace mozilla
+
+#endif // mozilla_Preferences_h
diff --git a/components/preferences/src/nsPrefBranch.cpp b/components/preferences/src/nsPrefBranch.cpp
new file mode 100644
index 000000000..6107e64d8
--- /dev/null
+++ b/components/preferences/src/nsPrefBranch.cpp
@@ -0,0 +1,943 @@
+/* -*- 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 "mozilla/dom/ContentChild.h"
+#include "nsXULAppAPI.h"
+
+#include "nsPrefBranch.h"
+#include "nsILocalFile.h" // nsILocalFile used for backwards compatibility
+#include "nsIObserverService.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIDirectoryService.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsXPIDLString.h"
+#include "nsPrintfCString.h"
+#include "nsIStringBundle.h"
+#include "prefapi.h"
+#include "PLDHashTable.h"
+
+#include "nsCRT.h"
+#include "mozilla/Services.h"
+
+#include "prefapi_private_data.h"
+
+#include "nsIConsoleService.h"
+
+#ifdef DEBUG
+#define ENSURE_MAIN_PROCESS(message, pref) do { \
+ if (GetContentChild()) { \
+ nsPrintfCString msg("ENSURE_MAIN_PROCESS failed. %s %s", message, pref); \
+ NS_ERROR(msg.get()); \
+ return NS_ERROR_NOT_AVAILABLE; \
+ } \
+} while (0);
+#else
+#define ENSURE_MAIN_PROCESS(message, pref) \
+ if (GetContentChild()) { \
+ return NS_ERROR_NOT_AVAILABLE; \
+ }
+#endif
+
+using mozilla::dom::ContentChild;
+
+static ContentChild*
+GetContentChild()
+{
+ if (XRE_IsContentProcess()) {
+ ContentChild* cpc = ContentChild::GetSingleton();
+ if (!cpc) {
+ NS_RUNTIMEABORT("Content Protocol is NULL! We're going to crash!");
+ }
+ return cpc;
+ }
+ return nullptr;
+}
+
+/*
+ * Constructor/Destructor
+ */
+
+nsPrefBranch::nsPrefBranch(const char *aPrefRoot, bool aDefaultBranch)
+{
+ mPrefRoot = aPrefRoot;
+ mPrefRootLength = mPrefRoot.Length();
+ mIsDefault = aDefaultBranch;
+ mFreeingObserverList = false;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ ++mRefCnt; // Our refcnt must be > 0 when we call this, or we'll get deleted!
+ // add weak so we don't have to clean up at shutdown
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ --mRefCnt;
+ }
+}
+
+nsPrefBranch::~nsPrefBranch()
+{
+ freeObserverList();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+}
+
+
+/*
+ * nsISupports Implementation
+ */
+
+NS_IMPL_ADDREF(nsPrefBranch)
+NS_IMPL_RELEASE(nsPrefBranch)
+
+NS_INTERFACE_MAP_BEGIN(nsPrefBranch)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefBranch)
+ NS_INTERFACE_MAP_ENTRY(nsIPrefBranch)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIPrefBranch2, !mIsDefault)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIPrefBranchInternal, !mIsDefault)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+
+/*
+ * nsIPrefBranch Implementation
+ */
+
+NS_IMETHODIMP nsPrefBranch::GetRoot(char **aRoot)
+{
+ NS_ENSURE_ARG_POINTER(aRoot);
+ mPrefRoot.Truncate(mPrefRootLength);
+ *aRoot = ToNewCString(mPrefRoot);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrefBranch::GetPrefType(const char *aPrefName, int32_t *_retval)
+{
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ switch (PREF_GetPrefType(pref)) {
+ case PrefType::String:
+ *_retval = PREF_STRING;
+ break;
+ case PrefType::Int:
+ *_retval = PREF_INT;
+ break;
+ case PrefType::Bool:
+ *_retval = PREF_BOOL;
+ break;
+ case PrefType::Invalid:
+ default:
+ *_retval = PREF_INVALID;
+ break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrefBranch::GetBoolPrefWithDefault(const char *aPrefName,
+ bool aDefaultValue,
+ uint8_t _argc, bool *_retval)
+{
+ nsresult rv = GetBoolPref(aPrefName, _retval);
+
+ if (NS_FAILED(rv) && _argc == 1) {
+ *_retval = aDefaultValue;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsPrefBranch::GetBoolPref(const char *aPrefName, bool *_retval)
+{
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ return PREF_GetBoolPref(pref, _retval, mIsDefault);
+}
+
+NS_IMETHODIMP nsPrefBranch::SetBoolPref(const char *aPrefName, bool aValue)
+{
+ ENSURE_MAIN_PROCESS("Cannot SetBoolPref from content process:", aPrefName);
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ return PREF_SetBoolPref(pref, aValue, mIsDefault);
+}
+
+NS_IMETHODIMP nsPrefBranch::GetFloatPrefWithDefault(const char *aPrefName,
+ float aDefaultValue,
+ uint8_t _argc, float *_retval)
+{
+ nsresult rv = GetFloatPref(aPrefName, _retval);
+
+ if (NS_FAILED(rv) && _argc == 1) {
+ *_retval = aDefaultValue;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsPrefBranch::GetFloatPref(const char *aPrefName, float *_retval)
+{
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ nsAutoCString stringVal;
+ nsresult rv = GetCharPref(pref, getter_Copies(stringVal));
+ if (NS_SUCCEEDED(rv)) {
+ *_retval = stringVal.ToFloat(&rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsPrefBranch::GetCharPrefWithDefault(const char *aPrefName,
+ const char *aDefaultValue,
+ uint8_t _argc, char **_retval)
+{
+ nsresult rv = GetCharPref(aPrefName, _retval);
+
+ if (NS_FAILED(rv) && _argc == 1) {
+ NS_ENSURE_ARG(aDefaultValue);
+ *_retval = NS_strdup(aDefaultValue);
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsPrefBranch::GetCharPref(const char *aPrefName, char **_retval)
+{
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ return PREF_CopyCharPref(pref, _retval, mIsDefault);
+}
+
+NS_IMETHODIMP nsPrefBranch::SetCharPref(const char *aPrefName, const char *aValue)
+{
+ nsresult rv = CheckSanityOfStringLength(aPrefName, aValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return SetCharPrefInternal(aPrefName, aValue);
+}
+
+nsresult nsPrefBranch::SetCharPrefInternal(const char *aPrefName, const char *aValue)
+
+{
+ ENSURE_MAIN_PROCESS("Cannot SetCharPref from content process:", aPrefName);
+ NS_ENSURE_ARG(aPrefName);
+ NS_ENSURE_ARG(aValue);
+ const char *pref = getPrefName(aPrefName);
+ return PREF_SetCharPref(pref, aValue, mIsDefault);
+}
+
+NS_IMETHODIMP nsPrefBranch::GetIntPrefWithDefault(const char *aPrefName,
+ int32_t aDefaultValue,
+ uint8_t _argc, int32_t *_retval)
+{
+ nsresult rv = GetIntPref(aPrefName, _retval);
+
+ if (NS_FAILED(rv) && _argc == 1) {
+ *_retval = aDefaultValue;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsPrefBranch::GetIntPref(const char *aPrefName, int32_t *_retval)
+{
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ return PREF_GetIntPref(pref, _retval, mIsDefault);
+}
+
+NS_IMETHODIMP nsPrefBranch::SetIntPref(const char *aPrefName, int32_t aValue)
+{
+ ENSURE_MAIN_PROCESS("Cannot SetIntPref from content process:", aPrefName);
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ return PREF_SetIntPref(pref, aValue, mIsDefault);
+}
+
+NS_IMETHODIMP nsPrefBranch::GetComplexValue(const char *aPrefName, const nsIID & aType, void **_retval)
+{
+ NS_ENSURE_ARG(aPrefName);
+
+ nsresult rv;
+ nsXPIDLCString utf8String;
+
+ // we have to do this one first because it's different than all the rest
+ if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) {
+ nsCOMPtr<nsIPrefLocalizedString> theString(do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ const char *pref = getPrefName(aPrefName);
+ bool bNeedDefault = false;
+
+ if (mIsDefault) {
+ bNeedDefault = true;
+ } else {
+ // if there is no user (or locked) value
+ if (!PREF_HasUserPref(pref) && !PREF_PrefIsLocked(pref)) {
+ bNeedDefault = true;
+ }
+ }
+
+ // if we need to fetch the default value, do that instead, otherwise use the
+ // value we pulled in at the top of this function
+ if (bNeedDefault) {
+ nsXPIDLString utf16String;
+ rv = GetDefaultFromPropertiesFile(pref, getter_Copies(utf16String));
+ if (NS_SUCCEEDED(rv)) {
+ theString->SetData(utf16String.get());
+ }
+ } else {
+ rv = GetCharPref(aPrefName, getter_Copies(utf8String));
+ if (NS_SUCCEEDED(rv)) {
+ theString->SetData(NS_ConvertUTF8toUTF16(utf8String).get());
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ theString.forget(reinterpret_cast<nsIPrefLocalizedString**>(_retval));
+ }
+
+ return rv;
+ }
+
+ // if we can't get the pref, there's no point in being here
+ rv = GetCharPref(aPrefName, getter_Copies(utf8String));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // also check nsILocalFile, for backwards compatibility
+ if (aType.Equals(NS_GET_IID(nsIFile)) || aType.Equals(NS_GET_IID(nsILocalFile))) {
+ if (GetContentChild()) {
+ NS_ERROR("cannot get nsIFile pref from content process");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = file->SetPersistentDescriptor(utf8String);
+ if (NS_SUCCEEDED(rv)) {
+ file.forget(reinterpret_cast<nsIFile**>(_retval));
+ return NS_OK;
+ }
+ }
+ return rv;
+ }
+
+ if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) {
+ if (GetContentChild()) {
+ NS_ERROR("cannot get nsIRelativeFilePref from content process");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsACString::const_iterator keyBegin, strEnd;
+ utf8String.BeginReading(keyBegin);
+ utf8String.EndReading(strEnd);
+
+ // The pref has the format: [fromKey]a/b/c
+ if (*keyBegin++ != '[')
+ return NS_ERROR_FAILURE;
+ nsACString::const_iterator keyEnd(keyBegin);
+ if (!FindCharInReadable(']', keyEnd, strEnd))
+ return NS_ERROR_FAILURE;
+ nsAutoCString key(Substring(keyBegin, keyEnd));
+
+ nsCOMPtr<nsIFile> fromFile;
+ nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = directoryService->Get(key.get(), NS_GET_IID(nsIFile), getter_AddRefs(fromFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIFile> theFile;
+ rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(theFile));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = theFile->SetRelativeDescriptor(fromFile, Substring(++keyEnd, strEnd));
+ if (NS_FAILED(rv))
+ return rv;
+ nsCOMPtr<nsIRelativeFilePref> relativePref;
+ rv = NS_NewRelativeFilePref(theFile, key, getter_AddRefs(relativePref));
+ if (NS_FAILED(rv))
+ return rv;
+
+ relativePref.forget(reinterpret_cast<nsIRelativeFilePref**>(_retval));
+ return NS_OK;
+ }
+
+ if (aType.Equals(NS_GET_IID(nsISupportsString))) {
+ nsCOMPtr<nsISupportsString> theString(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ // Debugging to see why we end up with very long strings here with
+ // some addons, see bug 836263.
+ nsAutoString wdata;
+ if (!AppendUTF8toUTF16(utf8String, wdata, mozilla::fallible)) {
+ NS_RUNTIMEABORT("bug836263");
+ }
+ theString->SetData(wdata);
+ theString.forget(reinterpret_cast<nsISupportsString**>(_retval));
+ }
+ return rv;
+ }
+
+ NS_WARNING("nsPrefBranch::GetComplexValue - Unsupported interface type");
+ return NS_NOINTERFACE;
+}
+
+nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName, const char* aValue) {
+ if (!aValue) {
+ return NS_OK;
+ }
+ return CheckSanityOfStringLength(aPrefName, strlen(aValue));
+}
+
+nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName, const nsAString& aValue) {
+ return CheckSanityOfStringLength(aPrefName, aValue.Length());
+}
+
+nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName, const uint32_t aLength) {
+ if (aLength > MAX_PREF_LENGTH) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (aLength <= MAX_ADVISABLE_PREF_LENGTH) {
+ return NS_OK;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIConsoleService> console = do_GetService("@mozilla.org/consoleservice;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsAutoCString message(nsPrintfCString("Warning: attempting to write %d bytes to preference %s. This is bad "
+ "for general performance and memory usage. Such an amount of data "
+ "should rather be written to an external file. This preference will "
+ "not be sent to any content processes.",
+ aLength,
+ getPrefName(aPrefName)));
+ rv = console->LogStringMessage(NS_ConvertUTF8toUTF16(message).get());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_OK;
+}
+
+/*static*/
+void nsPrefBranch::ReportToConsole(const nsAString& aMessage)
+{
+ nsresult rv;
+ nsCOMPtr<nsIConsoleService> console = do_GetService("@mozilla.org/consoleservice;1", &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsAutoString message(aMessage);
+ console->LogStringMessage(message.get());
+}
+
+NS_IMETHODIMP nsPrefBranch::SetComplexValue(const char *aPrefName, const nsIID & aType, nsISupports *aValue)
+{
+ ENSURE_MAIN_PROCESS("Cannot SetComplexValue from content process:", aPrefName);
+ NS_ENSURE_ARG(aPrefName);
+
+ nsresult rv = NS_NOINTERFACE;
+
+ // also check nsILocalFile, for backwards compatibility
+ if (aType.Equals(NS_GET_IID(nsIFile)) || aType.Equals(NS_GET_IID(nsILocalFile))) {
+ nsCOMPtr<nsIFile> file = do_QueryInterface(aValue);
+ if (!file)
+ return NS_NOINTERFACE;
+ nsAutoCString descriptorString;
+
+ rv = file->GetPersistentDescriptor(descriptorString);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetCharPrefInternal(aPrefName, descriptorString.get());
+ }
+ return rv;
+ }
+
+ if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) {
+ nsCOMPtr<nsIRelativeFilePref> relFilePref = do_QueryInterface(aValue);
+ if (!relFilePref)
+ return NS_NOINTERFACE;
+
+ nsCOMPtr<nsIFile> file;
+ relFilePref->GetFile(getter_AddRefs(file));
+ if (!file)
+ return NS_NOINTERFACE;
+ nsAutoCString relativeToKey;
+ (void) relFilePref->GetRelativeToKey(relativeToKey);
+
+ nsCOMPtr<nsIFile> relativeToFile;
+ nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = directoryService->Get(relativeToKey.get(), NS_GET_IID(nsIFile), getter_AddRefs(relativeToFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString relDescriptor;
+ rv = file->GetRelativeDescriptor(relativeToFile, relDescriptor);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString descriptorString;
+ descriptorString.Append('[');
+ descriptorString.Append(relativeToKey);
+ descriptorString.Append(']');
+ descriptorString.Append(relDescriptor);
+ return SetCharPrefInternal(aPrefName, descriptorString.get());
+ }
+
+ if (aType.Equals(NS_GET_IID(nsISupportsString))) {
+ nsCOMPtr<nsISupportsString> theString = do_QueryInterface(aValue);
+
+ if (theString) {
+ nsString wideString;
+
+ rv = theString->GetData(wideString);
+ if (NS_SUCCEEDED(rv)) {
+ // Check sanity of string length before any lengthy conversion
+ rv = CheckSanityOfStringLength(aPrefName, wideString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = SetCharPrefInternal(aPrefName, NS_ConvertUTF16toUTF8(wideString).get());
+ }
+ }
+ return rv;
+ }
+
+ if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) {
+ nsCOMPtr<nsIPrefLocalizedString> theString = do_QueryInterface(aValue);
+
+ if (theString) {
+ nsXPIDLString wideString;
+
+ rv = theString->GetData(getter_Copies(wideString));
+ if (NS_SUCCEEDED(rv)) {
+ // Check sanity of string length before any lengthy conversion
+ rv = CheckSanityOfStringLength(aPrefName, wideString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = SetCharPrefInternal(aPrefName, NS_ConvertUTF16toUTF8(wideString).get());
+ }
+ }
+ return rv;
+ }
+
+ NS_WARNING("nsPrefBranch::SetComplexValue - Unsupported interface type");
+ return NS_NOINTERFACE;
+}
+
+NS_IMETHODIMP nsPrefBranch::ClearUserPref(const char *aPrefName)
+{
+ ENSURE_MAIN_PROCESS("Cannot ClearUserPref from content process:", aPrefName);
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ return PREF_ClearUserPref(pref);
+}
+
+NS_IMETHODIMP nsPrefBranch::PrefHasUserValue(const char *aPrefName, bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ *_retval = PREF_HasUserPref(pref);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrefBranch::LockPref(const char *aPrefName)
+{
+ ENSURE_MAIN_PROCESS("Cannot LockPref from content process:", aPrefName);
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ return PREF_LockPref(pref, true);
+}
+
+NS_IMETHODIMP nsPrefBranch::PrefIsLocked(const char *aPrefName, bool *_retval)
+{
+ ENSURE_MAIN_PROCESS("Cannot check PrefIsLocked from content process:", aPrefName);
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ *_retval = PREF_PrefIsLocked(pref);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrefBranch::UnlockPref(const char *aPrefName)
+{
+ ENSURE_MAIN_PROCESS("Cannot UnlockPref from content process:", aPrefName);
+ NS_ENSURE_ARG(aPrefName);
+ const char *pref = getPrefName(aPrefName);
+ return PREF_LockPref(pref, false);
+}
+
+NS_IMETHODIMP nsPrefBranch::ResetBranch(const char *aStartingAt)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrefBranch::DeleteBranch(const char *aStartingAt)
+{
+ ENSURE_MAIN_PROCESS("Cannot DeleteBranch from content process:", aStartingAt);
+ NS_ENSURE_ARG(aStartingAt);
+ const char *pref = getPrefName(aStartingAt);
+ return PREF_DeleteBranch(pref);
+}
+
+NS_IMETHODIMP nsPrefBranch::GetChildList(const char *aStartingAt, uint32_t *aCount, char ***aChildArray)
+{
+ char **outArray;
+ int32_t numPrefs;
+ int32_t dwIndex;
+ AutoTArray<nsCString, 32> prefArray;
+
+ NS_ENSURE_ARG(aStartingAt);
+ NS_ENSURE_ARG_POINTER(aCount);
+ NS_ENSURE_ARG_POINTER(aChildArray);
+
+ *aChildArray = nullptr;
+ *aCount = 0;
+
+ // this will contain a list of all the pref name strings
+ // allocate on the stack for speed
+
+ const char* parent = getPrefName(aStartingAt);
+ size_t parentLen = strlen(parent);
+ for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PrefHashEntry*>(iter.Get());
+ if (strncmp(entry->key, parent, parentLen) == 0) {
+ prefArray.AppendElement(entry->key);
+ }
+ }
+
+ // now that we've built up the list, run the callback on
+ // all the matching elements
+ numPrefs = prefArray.Length();
+
+ if (numPrefs) {
+ outArray = (char **)moz_xmalloc(numPrefs * sizeof(char *));
+ if (!outArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ for (dwIndex = 0; dwIndex < numPrefs; ++dwIndex) {
+ // we need to lop off mPrefRoot in case the user is planning to pass this
+ // back to us because if they do we are going to add mPrefRoot again.
+ const nsCString& element = prefArray[dwIndex];
+ outArray[dwIndex] = (char *)nsMemory::Clone(
+ element.get() + mPrefRootLength, element.Length() - mPrefRootLength + 1);
+
+ if (!outArray[dwIndex]) {
+ // we ran out of memory... this is annoying
+ NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(dwIndex, outArray);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ *aChildArray = outArray;
+ }
+ *aCount = numPrefs;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrefBranch::AddObserver(const char *aDomain, nsIObserver *aObserver, bool aHoldWeak)
+{
+ PrefCallback *pCallback;
+ const char *pref;
+
+ NS_ENSURE_ARG(aDomain);
+ NS_ENSURE_ARG(aObserver);
+
+ // hold a weak reference to the observer if so requested
+ if (aHoldWeak) {
+ nsCOMPtr<nsISupportsWeakReference> weakRefFactory = do_QueryInterface(aObserver);
+ if (!weakRefFactory) {
+ // the caller didn't give us a object that supports weak reference... tell them
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Construct a PrefCallback with a weak reference to the observer.
+ pCallback = new PrefCallback(aDomain, weakRefFactory, this);
+
+ } else {
+ // Construct a PrefCallback with a strong reference to the observer.
+ pCallback = new PrefCallback(aDomain, aObserver, this);
+ }
+
+ if (mObservers.Get(pCallback)) {
+ NS_WARNING("Ignoring duplicate observer.");
+ delete pCallback;
+ return NS_OK;
+ }
+
+ mObservers.Put(pCallback, pCallback);
+
+ // We must pass a fully qualified preference name to the callback
+ // aDomain == nullptr is the only possible failure, and we trapped it with
+ // NS_ENSURE_ARG above.
+ pref = getPrefName(aDomain);
+ PREF_RegisterCallback(pref, NotifyObserver, pCallback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrefBranch::RemoveObserver(const char *aDomain, nsIObserver *aObserver)
+{
+ NS_ENSURE_ARG(aDomain);
+ NS_ENSURE_ARG(aObserver);
+
+ nsresult rv = NS_OK;
+
+ // If we're in the middle of a call to freeObserverList, don't process this
+ // RemoveObserver call -- the observer in question will be removed soon, if
+ // it hasn't been already.
+ //
+ // It's important that we don't touch mObservers in any way -- even a Get()
+ // which returns null might cause the hashtable to resize itself, which will
+ // break the iteration in freeObserverList.
+ if (mFreeingObserverList)
+ return NS_OK;
+
+ // Remove the relevant PrefCallback from mObservers and get an owning
+ // pointer to it. Unregister the callback first, and then let the owning
+ // pointer go out of scope and destroy the callback.
+ PrefCallback key(aDomain, aObserver, this);
+ nsAutoPtr<PrefCallback> pCallback;
+ mObservers.RemoveAndForget(&key, pCallback);
+ if (pCallback) {
+ // aDomain == nullptr is the only possible failure, trapped above
+ const char *pref = getPrefName(aDomain);
+ rv = PREF_UnregisterCallback(pref, NotifyObserver, pCallback);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsPrefBranch::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ // watch for xpcom shutdown and free our observers to eliminate any cyclic references
+ if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ freeObserverList();
+ }
+ return NS_OK;
+}
+
+/* static */
+void nsPrefBranch::NotifyObserver(const char *newpref, void *data)
+{
+ PrefCallback *pCallback = (PrefCallback *)data;
+
+ nsCOMPtr<nsIObserver> observer = pCallback->GetObserver();
+ if (!observer) {
+ // The observer has expired. Let's remove this callback.
+ pCallback->GetPrefBranch()->RemoveExpiredCallback(pCallback);
+ return;
+ }
+
+ // remove any root this string may contain so as to not confuse the observer
+ // by passing them something other than what they passed us as a topic
+ uint32_t len = pCallback->GetPrefBranch()->GetRootLength();
+ nsAutoCString suffix(newpref + len);
+
+ observer->Observe(static_cast<nsIPrefBranch *>(pCallback->GetPrefBranch()),
+ NS_PREFBRANCH_PREFCHANGE_TOPIC_ID,
+ NS_ConvertASCIItoUTF16(suffix).get());
+}
+
+size_t
+nsPrefBranch::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+{
+ size_t n = aMallocSizeOf(this);
+ n += mPrefRoot.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return n;
+}
+
+void nsPrefBranch::freeObserverList(void)
+{
+ // We need to prevent anyone from modifying mObservers while we're iterating
+ // over it. In particular, some clients will call RemoveObserver() when
+ // they're removed and destructed via the iterator; we set
+ // mFreeingObserverList to keep those calls from touching mObservers.
+ mFreeingObserverList = true;
+ for (auto iter = mObservers.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<PrefCallback>& callback = iter.Data();
+ nsPrefBranch *prefBranch = callback->GetPrefBranch();
+ const char *pref = prefBranch->getPrefName(callback->GetDomain().get());
+ PREF_UnregisterCallback(pref, nsPrefBranch::NotifyObserver, callback);
+ iter.Remove();
+ }
+ mFreeingObserverList = false;
+}
+
+void
+nsPrefBranch::RemoveExpiredCallback(PrefCallback *aCallback)
+{
+ NS_PRECONDITION(aCallback->IsExpired(), "Callback should be expired.");
+ mObservers.Remove(aCallback);
+}
+
+nsresult nsPrefBranch::GetDefaultFromPropertiesFile(const char *aPrefName, char16_t **return_buf)
+{
+ nsresult rv;
+
+ // the default value contains a URL to a .properties file
+
+ nsXPIDLCString propertyFileURL;
+ rv = PREF_CopyCharPref(aPrefName, getter_Copies(propertyFileURL), true);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (!bundleService)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(propertyFileURL,
+ getter_AddRefs(bundle));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // string names are in unicode
+ nsAutoString stringId;
+ stringId.AssignASCII(aPrefName);
+
+ return bundle->GetStringFromName(stringId.get(), return_buf);
+}
+
+const char *nsPrefBranch::getPrefName(const char *aPrefName)
+{
+ NS_ASSERTION(aPrefName, "null pref name!");
+
+ // for speed, avoid strcpy if we can:
+ if (mPrefRoot.IsEmpty())
+ return aPrefName;
+
+ // isn't there a better way to do this? this is really kind of gross.
+ mPrefRoot.Truncate(mPrefRootLength);
+ mPrefRoot.Append(aPrefName);
+ return mPrefRoot.get();
+}
+
+//----------------------------------------------------------------------------
+// nsPrefLocalizedString
+//----------------------------------------------------------------------------
+
+nsPrefLocalizedString::nsPrefLocalizedString()
+{
+}
+
+nsPrefLocalizedString::~nsPrefLocalizedString()
+{
+}
+
+
+/*
+ * nsISupports Implementation
+ */
+
+NS_IMPL_ADDREF(nsPrefLocalizedString)
+NS_IMPL_RELEASE(nsPrefLocalizedString)
+
+NS_INTERFACE_MAP_BEGIN(nsPrefLocalizedString)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefLocalizedString)
+ NS_INTERFACE_MAP_ENTRY(nsIPrefLocalizedString)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsString)
+NS_INTERFACE_MAP_END
+
+nsresult nsPrefLocalizedString::Init()
+{
+ nsresult rv;
+ mUnicodeString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrefLocalizedString::GetData(char16_t **_retval)
+{
+ nsAutoString data;
+
+ nsresult rv = GetData(data);
+ if (NS_FAILED(rv))
+ return rv;
+
+ *_retval = ToNewUnicode(data);
+ if (!*_retval)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrefLocalizedString::SetData(const char16_t *aData)
+{
+ if (!aData)
+ return SetData(EmptyString());
+ return SetData(nsDependentString(aData));
+}
+
+NS_IMETHODIMP
+nsPrefLocalizedString::SetDataWithLength(uint32_t aLength,
+ const char16_t *aData)
+{
+ if (!aData)
+ return SetData(EmptyString());
+ return SetData(Substring(aData, aLength));
+}
+
+//----------------------------------------------------------------------------
+// nsRelativeFilePref
+//----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsRelativeFilePref, nsIRelativeFilePref)
+
+nsRelativeFilePref::nsRelativeFilePref()
+{
+}
+
+nsRelativeFilePref::~nsRelativeFilePref()
+{
+}
+
+NS_IMETHODIMP nsRelativeFilePref::GetFile(nsIFile **aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = mFile;
+ NS_IF_ADDREF(*aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRelativeFilePref::SetFile(nsIFile *aFile)
+{
+ mFile = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRelativeFilePref::GetRelativeToKey(nsACString& aRelativeToKey)
+{
+ aRelativeToKey.Assign(mRelativeToKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsRelativeFilePref::SetRelativeToKey(const nsACString& aRelativeToKey)
+{
+ mRelativeToKey.Assign(aRelativeToKey);
+ return NS_OK;
+}
+
+#undef ENSURE_MAIN_PROCESS
diff --git a/components/preferences/src/nsPrefBranch.h b/components/preferences/src/nsPrefBranch.h
new file mode 100644
index 000000000..37cf5c2c4
--- /dev/null
+++ b/components/preferences/src/nsPrefBranch.h
@@ -0,0 +1,269 @@
+/* -*- 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 nsPrefBranch_h
+#define nsPrefBranch_h
+
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefBranchInternal.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIRelativeFilePref.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+#include "nsClassHashtable.h"
+#include "nsCRT.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace mozilla {
+class PreferenceServiceReporter;
+} // namespace mozilla
+
+class nsPrefBranch;
+
+class PrefCallback : public PLDHashEntryHdr {
+ friend class mozilla::PreferenceServiceReporter;
+
+ public:
+ typedef PrefCallback* KeyType;
+ typedef const PrefCallback* KeyTypePointer;
+
+ static const PrefCallback* KeyToPointer(PrefCallback *aKey)
+ {
+ return aKey;
+ }
+
+ static PLDHashNumber HashKey(const PrefCallback *aKey)
+ {
+ uint32_t hash = mozilla::HashString(aKey->mDomain);
+ return mozilla::AddToHash(hash, aKey->mCanonical);
+ }
+
+
+ public:
+ // Create a PrefCallback with a strong reference to its observer.
+ PrefCallback(const char *aDomain, nsIObserver *aObserver,
+ nsPrefBranch *aBranch)
+ : mDomain(aDomain),
+ mBranch(aBranch),
+ mWeakRef(nullptr),
+ mStrongRef(aObserver)
+ {
+ MOZ_COUNT_CTOR(PrefCallback);
+ nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver);
+ mCanonical = canonical;
+ }
+
+ // Create a PrefCallback with a weak reference to its observer.
+ PrefCallback(const char *aDomain,
+ nsISupportsWeakReference *aObserver,
+ nsPrefBranch *aBranch)
+ : mDomain(aDomain),
+ mBranch(aBranch),
+ mWeakRef(do_GetWeakReference(aObserver)),
+ mStrongRef(nullptr)
+ {
+ MOZ_COUNT_CTOR(PrefCallback);
+ nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver);
+ mCanonical = canonical;
+ }
+
+ // Copy constructor needs to be explicit or the linker complains.
+ explicit PrefCallback(const PrefCallback *&aCopy)
+ : mDomain(aCopy->mDomain),
+ mBranch(aCopy->mBranch),
+ mWeakRef(aCopy->mWeakRef),
+ mStrongRef(aCopy->mStrongRef),
+ mCanonical(aCopy->mCanonical)
+ {
+ MOZ_COUNT_CTOR(PrefCallback);
+ }
+
+ ~PrefCallback()
+ {
+ MOZ_COUNT_DTOR(PrefCallback);
+ }
+
+ bool KeyEquals(const PrefCallback *aKey) const
+ {
+ // We want to be able to look up a weakly-referencing PrefCallback after
+ // its observer has died so we can remove it from the table. Once the
+ // callback's observer dies, its canonical pointer is stale -- in
+ // particular, we may have allocated a new observer in the same spot in
+ // memory! So we can't just compare canonical pointers to determine
+ // whether aKey refers to the same observer as this.
+ //
+ // Our workaround is based on the way we use this hashtable: When we ask
+ // the hashtable to remove a PrefCallback whose weak reference has
+ // expired, we use as the key for removal the same object as was inserted
+ // into the hashtable. Thus we can say that if one of the keys' weak
+ // references has expired, the two keys are equal iff they're the same
+ // object.
+
+ if (IsExpired() || aKey->IsExpired())
+ return this == aKey;
+
+ if (mCanonical != aKey->mCanonical)
+ return false;
+
+ return mDomain.Equals(aKey->mDomain);
+ }
+
+ PrefCallback *GetKey() const
+ {
+ return const_cast<PrefCallback*>(this);
+ }
+
+ // Get a reference to the callback's observer, or null if the observer was
+ // weakly referenced and has been destroyed.
+ already_AddRefed<nsIObserver> GetObserver() const
+ {
+ if (!IsWeak()) {
+ nsCOMPtr<nsIObserver> copy = mStrongRef;
+ return copy.forget();
+ }
+
+ nsCOMPtr<nsIObserver> observer = do_QueryReferent(mWeakRef);
+ return observer.forget();
+ }
+
+ const nsCString& GetDomain() const
+ {
+ return mDomain;
+ }
+
+ nsPrefBranch* GetPrefBranch() const
+ {
+ return mBranch;
+ }
+
+ // Has this callback's weak reference died?
+ bool IsExpired() const
+ {
+ if (!IsWeak())
+ return false;
+
+ nsCOMPtr<nsIObserver> observer(do_QueryReferent(mWeakRef));
+ return !observer;
+ }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ nsCString mDomain;
+ nsPrefBranch *mBranch;
+
+ // Exactly one of mWeakRef and mStrongRef should be non-null.
+ nsWeakPtr mWeakRef;
+ nsCOMPtr<nsIObserver> mStrongRef;
+
+ // We need a canonical nsISupports pointer, per bug 578392.
+ nsISupports *mCanonical;
+
+ bool IsWeak() const
+ {
+ return !!mWeakRef;
+ }
+};
+
+class nsPrefBranch final : public nsIPrefBranchInternal,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+ friend class mozilla::PreferenceServiceReporter;
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPREFBRANCH
+ NS_DECL_NSIPREFBRANCH2
+ NS_DECL_NSIOBSERVER
+
+ nsPrefBranch(const char *aPrefRoot, bool aDefaultBranch);
+
+ int32_t GetRootLength() { return mPrefRootLength; }
+
+ nsresult RemoveObserverFromMap(const char *aDomain, nsISupports *aObserver);
+
+ static void NotifyObserver(const char *newpref, void *data);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
+
+ static void ReportToConsole(const nsAString& aMessage);
+
+protected:
+ virtual ~nsPrefBranch();
+
+ nsPrefBranch() /* disallow use of this constructer */
+ : mPrefRootLength(0)
+ , mIsDefault(false)
+ , mFreeingObserverList(false)
+ {}
+
+ nsresult GetDefaultFromPropertiesFile(const char *aPrefName, char16_t **return_buf);
+ // As SetCharPref, but without any check on the length of |aValue|
+ nsresult SetCharPrefInternal(const char *aPrefName, const char *aValue);
+ // Reject strings that are more than 1Mb, warn if strings are more than 16kb
+ nsresult CheckSanityOfStringLength(const char* aPrefName, const nsAString& aValue);
+ nsresult CheckSanityOfStringLength(const char* aPrefName, const char* aValue);
+ nsresult CheckSanityOfStringLength(const char* aPrefName, const uint32_t aLength);
+ void RemoveExpiredCallback(PrefCallback *aCallback);
+ const char *getPrefName(const char *aPrefName);
+ void freeObserverList(void);
+
+private:
+ int32_t mPrefRootLength;
+ nsCString mPrefRoot;
+ bool mIsDefault;
+
+ bool mFreeingObserverList;
+ nsClassHashtable<PrefCallback, PrefCallback> mObservers;
+};
+
+
+class nsPrefLocalizedString final : public nsIPrefLocalizedString,
+ public nsISupportsString
+{
+public:
+ nsPrefLocalizedString();
+
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_NSISUPPORTSSTRING(mUnicodeString->)
+ NS_FORWARD_NSISUPPORTSPRIMITIVE(mUnicodeString->)
+
+ nsresult Init();
+
+private:
+ virtual ~nsPrefLocalizedString();
+
+ NS_IMETHOD GetData(char16_t**) override;
+ NS_IMETHOD SetData(const char16_t* aData) override;
+ NS_IMETHOD SetDataWithLength(uint32_t aLength, const char16_t *aData) override;
+
+ nsCOMPtr<nsISupportsString> mUnicodeString;
+};
+
+
+class nsRelativeFilePref : public nsIRelativeFilePref
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRELATIVEFILEPREF
+
+ nsRelativeFilePref();
+
+private:
+ virtual ~nsRelativeFilePref();
+
+ nsCOMPtr<nsIFile> mFile;
+ nsCString mRelativeToKey;
+};
+
+#endif
diff --git a/components/preferences/src/nsPrefsFactory.cpp b/components/preferences/src/nsPrefsFactory.cpp
new file mode 100644
index 000000000..35a9885b9
--- /dev/null
+++ b/components/preferences/src/nsPrefsFactory.cpp
@@ -0,0 +1,54 @@
+/* -*- 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/ModuleUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsPrefBranch.h"
+#include "prefapi.h"
+
+using namespace mozilla;
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(Preferences,
+ Preferences::GetInstanceForService)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrefLocalizedString, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsRelativeFilePref)
+
+static NS_DEFINE_CID(kPrefServiceCID, NS_PREFSERVICE_CID);
+static NS_DEFINE_CID(kPrefLocalizedStringCID, NS_PREFLOCALIZEDSTRING_CID);
+static NS_DEFINE_CID(kRelativeFilePrefCID, NS_RELATIVEFILEPREF_CID);
+
+static mozilla::Module::CIDEntry kPrefCIDs[] = {
+ { &kPrefServiceCID, true, nullptr, PreferencesConstructor },
+ { &kPrefLocalizedStringCID, false, nullptr, nsPrefLocalizedStringConstructor },
+ { &kRelativeFilePrefCID, false, nullptr, nsRelativeFilePrefConstructor },
+ { nullptr }
+};
+
+static mozilla::Module::ContractIDEntry kPrefContracts[] = {
+ { NS_PREFSERVICE_CONTRACTID, &kPrefServiceCID },
+ { NS_PREFLOCALIZEDSTRING_CONTRACTID, &kPrefLocalizedStringCID },
+ { NS_RELATIVEFILEPREF_CONTRACTID, &kRelativeFilePrefCID },
+ // compatibility for extension that uses old service
+ { "@mozilla.org/preferences;1", &kPrefServiceCID },
+ { nullptr }
+};
+
+static void
+UnloadPrefsModule()
+{
+ Preferences::Shutdown();
+}
+
+static const mozilla::Module kPrefModule = {
+ mozilla::Module::kVersion,
+ kPrefCIDs,
+ kPrefContracts,
+ nullptr,
+ nullptr,
+ nullptr,
+ UnloadPrefsModule
+};
+
+NSMODULE_DEFN(nsPrefModule) = &kPrefModule;
diff --git a/components/preferences/src/prefapi.cpp b/components/preferences/src/prefapi.cpp
new file mode 100644
index 000000000..8aaf4077b
--- /dev/null
+++ b/components/preferences/src/prefapi.cpp
@@ -0,0 +1,1005 @@
+/* -*- 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 <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+#include "prefapi.h"
+#include "prefapi_private_data.h"
+#include "prefread.h"
+#include "MainThreadUtils.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+
+#define PL_ARENA_CONST_ALIGN_MASK 3
+#include "plarena.h"
+
+#ifdef _WIN32
+ #include "windows.h"
+#endif /* _WIN32 */
+
+#include "plstr.h"
+#include "PLDHashTable.h"
+#include "plbase64.h"
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/PContent.h"
+#include "nsQuickSort.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "prlink.h"
+
+using namespace mozilla;
+
+static void
+clearPrefEntry(PLDHashTable *table, PLDHashEntryHdr *entry)
+{
+ PrefHashEntry *pref = static_cast<PrefHashEntry *>(entry);
+ if (pref->prefFlags.IsTypeString())
+ {
+ if (pref->defaultPref.stringVal)
+ PL_strfree(pref->defaultPref.stringVal);
+ if (pref->userPref.stringVal)
+ PL_strfree(pref->userPref.stringVal);
+ }
+ // don't need to free this as it's allocated in memory owned by
+ // gPrefNameArena
+ pref->key = nullptr;
+ memset(entry, 0, table->EntrySize());
+}
+
+static bool
+matchPrefEntry(const PLDHashEntryHdr* entry, const void* key)
+{
+ const PrefHashEntry *prefEntry =
+ static_cast<const PrefHashEntry*>(entry);
+
+ if (prefEntry->key == key) return true;
+
+ if (!prefEntry->key || !key) return false;
+
+ const char *otherKey = reinterpret_cast<const char*>(key);
+ return (strcmp(prefEntry->key, otherKey) == 0);
+}
+
+PLDHashTable* gHashTable;
+static PLArenaPool gPrefNameArena;
+
+static struct CallbackNode* gCallbacks = nullptr;
+static bool gIsAnyPrefLocked = false;
+// These are only used during the call to pref_DoCallback
+static bool gCallbacksInProgress = false;
+static bool gShouldCleanupDeadNodes = false;
+
+
+static PLDHashTableOps pref_HashTableOps = {
+ PLDHashTable::HashStringKey,
+ matchPrefEntry,
+ PLDHashTable::MoveEntryStub,
+ clearPrefEntry,
+ nullptr,
+};
+
+// PR_ALIGN_OF_WORD is only defined on some platforms. ALIGN_OF_WORD has
+// already been defined to PR_ALIGN_OF_WORD everywhere
+#ifndef PR_ALIGN_OF_WORD
+#define PR_ALIGN_OF_WORD PR_ALIGN_OF_POINTER
+#endif
+
+// making PrefName arena 8k for nice allocation
+#define PREFNAME_ARENA_SIZE 8192
+
+#define WORD_ALIGN_MASK (PR_ALIGN_OF_WORD - 1)
+
+// sanity checking
+#if (PR_ALIGN_OF_WORD & WORD_ALIGN_MASK) != 0
+#error "PR_ALIGN_OF_WORD must be a power of 2!"
+#endif
+
+// equivalent to strdup() - does no error checking,
+// we're assuming we're only called with a valid pointer
+static char *ArenaStrDup(const char* str, PLArenaPool* aArena)
+{
+ void* mem;
+ uint32_t len = strlen(str);
+ PL_ARENA_ALLOCATE(mem, aArena, len+1);
+ if (mem)
+ memcpy(mem, str, len+1);
+ return static_cast<char*>(mem);
+}
+
+static PrefsDirtyFunc gDirtyCallback = nullptr;
+
+inline void MakeDirtyCallback()
+{
+ // Right now the callback function is always set, so we don't need
+ // to complicate the code to cover the scenario where we set the callback
+ // after we've already tried to make it dirty. If this assert triggers
+ // we will add that code.
+ MOZ_ASSERT(gDirtyCallback);
+ if (gDirtyCallback) {
+ gDirtyCallback();
+ }
+}
+
+void PREF_SetDirtyCallback(PrefsDirtyFunc aFunc)
+{
+ gDirtyCallback = aFunc;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static bool pref_ValueChanged(PrefValue oldValue, PrefValue newValue, PrefType type);
+/* -- Privates */
+struct CallbackNode {
+ char* domain;
+ // If someone attempts to remove the node from the callback list while
+ // pref_DoCallback is running, |func| is set to nullptr. Such nodes will
+ // be removed at the end of pref_DoCallback.
+ PrefChangedFunc func;
+ void* data;
+ struct CallbackNode* next;
+};
+
+/* -- Prototypes */
+static nsresult pref_DoCallback(const char* changed_pref);
+
+enum {
+ kPrefSetDefault = 1,
+ kPrefForceSet = 2,
+ kPrefStickyDefault = 4,
+};
+static nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags);
+
+#define PREF_HASHTABLE_INITIAL_LENGTH 1024
+
+void PREF_Init()
+{
+ if (!gHashTable) {
+ gHashTable = new PLDHashTable(&pref_HashTableOps,
+ sizeof(PrefHashEntry),
+ PREF_HASHTABLE_INITIAL_LENGTH);
+
+ PL_INIT_ARENA_POOL(&gPrefNameArena, "PrefNameArena",
+ PREFNAME_ARENA_SIZE);
+ }
+}
+
+/* Frees the callback list. */
+void PREF_Cleanup()
+{
+ NS_ASSERTION(!gCallbacksInProgress,
+ "PREF_Cleanup was called while gCallbacksInProgress is true!");
+ struct CallbackNode* node = gCallbacks;
+ struct CallbackNode* next_node;
+
+ while (node)
+ {
+ next_node = node->next;
+ PL_strfree(node->domain);
+ free(node);
+ node = next_node;
+ }
+ gCallbacks = nullptr;
+
+ PREF_CleanupPrefs();
+}
+
+/* Frees up all the objects except the callback list. */
+void PREF_CleanupPrefs()
+{
+ if (gHashTable) {
+ delete gHashTable;
+ gHashTable = nullptr;
+ PL_FinishArenaPool(&gPrefNameArena);
+ }
+}
+
+// note that this appends to aResult, and does not assign!
+static void str_escape(const char * original, nsAFlatCString& aResult)
+{
+ /* JavaScript does not allow quotes, slashes, or line terminators inside
+ * strings so we must escape them. ECMAScript defines four line
+ * terminators, but we're only worrying about \r and \n here. We currently
+ * feed our pref script to the JS interpreter as Latin-1 so we won't
+ * encounter \u2028 (line separator) or \u2029 (paragraph separator).
+ *
+ * WARNING: There are hints that we may be moving to storing prefs
+ * as utf8. If we ever feed them to the JS compiler as UTF8 then
+ * we'll have to worry about the multibyte sequences that would be
+ * interpreted as \u2028 and \u2029
+ */
+ const char *p;
+
+ if (original == nullptr)
+ return;
+
+ /* Paranoid worst case all slashes will free quickly */
+ for (p=original; *p; ++p)
+ {
+ switch (*p)
+ {
+ case '\n':
+ aResult.AppendLiteral("\\n");
+ break;
+
+ case '\r':
+ aResult.AppendLiteral("\\r");
+ break;
+
+ case '\\':
+ aResult.AppendLiteral("\\\\");
+ break;
+
+ case '\"':
+ aResult.AppendLiteral("\\\"");
+ break;
+
+ default:
+ aResult.Append(*p);
+ break;
+ }
+ }
+}
+
+/*
+** External calls
+*/
+nsresult
+PREF_SetCharPref(const char *pref_name, const char *value, bool set_default)
+{
+ if ((uint32_t)strlen(value) > MAX_PREF_LENGTH) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PrefValue pref;
+ pref.stringVal = (char*)value;
+
+ return pref_HashPref(pref_name, pref, PrefType::String, set_default ? kPrefSetDefault : 0);
+}
+
+nsresult
+PREF_SetIntPref(const char *pref_name, int32_t value, bool set_default)
+{
+ PrefValue pref;
+ pref.intVal = value;
+
+ return pref_HashPref(pref_name, pref, PrefType::Int, set_default ? kPrefSetDefault : 0);
+}
+
+nsresult
+PREF_SetBoolPref(const char *pref_name, bool value, bool set_default)
+{
+ PrefValue pref;
+ pref.boolVal = value;
+
+ return pref_HashPref(pref_name, pref, PrefType::Bool, set_default ? kPrefSetDefault : 0);
+}
+
+enum WhichValue { DEFAULT_VALUE, USER_VALUE };
+static nsresult
+SetPrefValue(const char* aPrefName, const dom::PrefValue& aValue,
+ WhichValue aWhich)
+{
+ bool setDefault = (aWhich == DEFAULT_VALUE);
+ switch (aValue.type()) {
+ case dom::PrefValue::TnsCString:
+ return PREF_SetCharPref(aPrefName, aValue.get_nsCString().get(),
+ setDefault);
+ case dom::PrefValue::Tint32_t:
+ return PREF_SetIntPref(aPrefName, aValue.get_int32_t(),
+ setDefault);
+ case dom::PrefValue::Tbool:
+ return PREF_SetBoolPref(aPrefName, aValue.get_bool(),
+ setDefault);
+ default:
+ MOZ_CRASH();
+ }
+}
+
+nsresult
+pref_SetPref(const dom::PrefSetting& aPref)
+{
+ const char* prefName = aPref.name().get();
+ const dom::MaybePrefValue& defaultValue = aPref.defaultValue();
+ const dom::MaybePrefValue& userValue = aPref.userValue();
+
+ nsresult rv;
+ if (defaultValue.type() == dom::MaybePrefValue::TPrefValue) {
+ rv = SetPrefValue(prefName, defaultValue.get_PrefValue(), DEFAULT_VALUE);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (userValue.type() == dom::MaybePrefValue::TPrefValue) {
+ rv = SetPrefValue(prefName, userValue.get_PrefValue(), USER_VALUE);
+ } else {
+ rv = PREF_ClearUserPref(prefName);
+ }
+
+ // NB: we should never try to clear a default value, that doesn't
+ // make sense
+
+ return rv;
+}
+
+UniquePtr<char*[]>
+pref_savePrefs(PLDHashTable* aTable, uint32_t* aPrefCount)
+{
+ // This function allocates the entries in the savedPrefs array it returns.
+ // It is the callers responsibility to go through the array and free
+ // all of them. The aPrefCount entries will be non-null. Any end padding
+ // is an implementation detail and may change.
+ MOZ_ASSERT(aPrefCount);
+ auto savedPrefs = MakeUnique<char*[]>(aTable->EntryCount());
+
+ // This is not necessary, but leaving it in for now
+ memset(savedPrefs.get(), 0, aTable->EntryCount() * sizeof(char*));
+
+ int32_t j = 0;
+ for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
+ auto pref = static_cast<PrefHashEntry*>(iter.Get());
+
+ nsAutoCString prefValue;
+ nsAutoCString prefPrefix;
+ prefPrefix.AssignLiteral("user_pref(\"");
+
+ // where we're getting our pref from
+ PrefValue* sourcePref;
+
+ if (pref->prefFlags.HasUserValue() &&
+ (pref_ValueChanged(pref->defaultPref,
+ pref->userPref,
+ pref->prefFlags.GetPrefType()) ||
+ !(pref->prefFlags.HasDefault()) ||
+ pref->prefFlags.HasStickyDefault())) {
+ sourcePref = &pref->userPref;
+ } else {
+ // do not save default prefs that haven't changed
+ continue;
+ }
+
+ // strings are in quotes!
+ if (pref->prefFlags.IsTypeString()) {
+ prefValue = '\"';
+ str_escape(sourcePref->stringVal, prefValue);
+ prefValue += '\"';
+
+ } else if (pref->prefFlags.IsTypeInt()) {
+ prefValue.AppendInt(sourcePref->intVal);
+
+ } else if (pref->prefFlags.IsTypeBool()) {
+ prefValue = (sourcePref->boolVal) ? "true" : "false";
+ }
+
+ nsAutoCString prefName;
+ str_escape(pref->key, prefName);
+
+ savedPrefs[j++] = ToNewCString(prefPrefix +
+ prefName +
+ NS_LITERAL_CSTRING("\", ") +
+ prefValue +
+ NS_LITERAL_CSTRING(");"));
+ }
+ *aPrefCount = j;
+
+ return savedPrefs;
+}
+
+bool
+pref_EntryHasAdvisablySizedValues(PrefHashEntry* aHashEntry)
+{
+ if (aHashEntry->prefFlags.GetPrefType() != PrefType::String) {
+ return true;
+ }
+
+ char* stringVal;
+ if (aHashEntry->prefFlags.HasDefault()) {
+ stringVal = aHashEntry->defaultPref.stringVal;
+ if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
+ return false;
+ }
+ }
+
+ if (aHashEntry->prefFlags.HasUserValue()) {
+ stringVal = aHashEntry->userPref.stringVal;
+ if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void
+GetPrefValueFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref,
+ WhichValue aWhich)
+{
+ PrefValue* value;
+ dom::PrefValue* settingValue;
+ if (aWhich == USER_VALUE) {
+ value = &aHashEntry->userPref;
+ aPref->userValue() = dom::PrefValue();
+ settingValue = &aPref->userValue().get_PrefValue();
+ } else {
+ value = &aHashEntry->defaultPref;
+ aPref->defaultValue() = dom::PrefValue();
+ settingValue = &aPref->defaultValue().get_PrefValue();
+ }
+
+ switch (aHashEntry->prefFlags.GetPrefType()) {
+ case PrefType::String:
+ *settingValue = nsDependentCString(value->stringVal);
+ return;
+ case PrefType::Int:
+ *settingValue = value->intVal;
+ return;
+ case PrefType::Bool:
+ *settingValue = !!value->boolVal;
+ return;
+ default:
+ MOZ_CRASH();
+ }
+}
+
+void
+pref_GetPrefFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref)
+{
+ aPref->name() = aHashEntry->key;
+ if (aHashEntry->prefFlags.HasDefault()) {
+ GetPrefValueFromEntry(aHashEntry, aPref, DEFAULT_VALUE);
+ } else {
+ aPref->defaultValue() = null_t();
+ }
+ if (aHashEntry->prefFlags.HasUserValue()) {
+ GetPrefValueFromEntry(aHashEntry, aPref, USER_VALUE);
+ } else {
+ aPref->userValue() = null_t();
+ }
+
+ MOZ_ASSERT(aPref->defaultValue().type() == dom::MaybePrefValue::Tnull_t ||
+ aPref->userValue().type() == dom::MaybePrefValue::Tnull_t ||
+ (aPref->defaultValue().get_PrefValue().type() ==
+ aPref->userValue().get_PrefValue().type()));
+}
+
+
+int
+pref_CompareStrings(const void *v1, const void *v2, void *unused)
+{
+ char *s1 = *(char**) v1;
+ char *s2 = *(char**) v2;
+
+ if (!s1)
+ {
+ if (!s2)
+ return 0;
+ else
+ return -1;
+ }
+ else if (!s2)
+ return 1;
+ else
+ return strcmp(s1, s2);
+}
+
+bool PREF_HasUserPref(const char *pref_name)
+{
+ if (!gHashTable)
+ return false;
+
+ PrefHashEntry *pref = pref_HashTableLookup(pref_name);
+ return pref && pref->prefFlags.HasUserValue();
+}
+
+nsresult
+PREF_CopyCharPref(const char *pref_name, char ** return_buffer, bool get_default)
+{
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ char* stringVal;
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+
+ if (pref && (pref->prefFlags.IsTypeString())) {
+ if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) {
+ stringVal = pref->defaultPref.stringVal;
+ } else {
+ stringVal = pref->userPref.stringVal;
+ }
+
+ if (stringVal) {
+ *return_buffer = NS_strdup(stringVal);
+ rv = NS_OK;
+ }
+ }
+ return rv;
+}
+
+nsresult PREF_GetIntPref(const char *pref_name,int32_t * return_int, bool get_default)
+{
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+ if (pref && (pref->prefFlags.IsTypeInt())) {
+ if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) {
+ int32_t tempInt = pref->defaultPref.intVal;
+ /* check to see if we even had a default */
+ if (!pref->prefFlags.HasDefault()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ *return_int = tempInt;
+ } else {
+ *return_int = pref->userPref.intVal;
+ }
+ rv = NS_OK;
+ }
+ return rv;
+}
+
+nsresult PREF_GetBoolPref(const char *pref_name, bool * return_value, bool get_default)
+{
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+ //NS_ASSERTION(pref, pref_name);
+ if (pref && (pref->prefFlags.IsTypeBool())) {
+ if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) {
+ bool tempBool = pref->defaultPref.boolVal;
+ /* check to see if we even had a default */
+ if (pref->prefFlags.HasDefault()) {
+ *return_value = tempBool;
+ rv = NS_OK;
+ }
+ } else {
+ *return_value = pref->userPref.boolVal;
+ rv = NS_OK;
+ }
+ }
+ return rv;
+}
+
+nsresult
+PREF_DeleteBranch(const char *branch_name)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ int len = (int)strlen(branch_name);
+
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ /* The following check insures that if the branch name already has a "."
+ * at the end, we don't end up with a "..". This fixes an incompatibility
+ * between nsIPref, which needs the period added, and nsIPrefBranch which
+ * does not. When nsIPref goes away this function should be fixed to
+ * never add the period at all.
+ */
+ nsAutoCString branch_dot(branch_name);
+ if ((len > 1) && branch_name[len - 1] != '.')
+ branch_dot += '.';
+
+ /* Delete a branch. Used for deleting mime types */
+ const char *to_delete = branch_dot.get();
+ MOZ_ASSERT(to_delete);
+ len = strlen(to_delete);
+ for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<PrefHashEntry*>(iter.Get());
+
+ /* note if we're deleting "ldap" then we want to delete "ldap.xxx"
+ and "ldap" (if such a leaf node exists) but not "ldap_1.xxx" */
+ if (PL_strncmp(entry->key, to_delete, (uint32_t) len) == 0 ||
+ (len-1 == (int)strlen(entry->key) &&
+ PL_strncmp(entry->key, to_delete, (uint32_t)(len-1)) == 0)) {
+ iter.Remove();
+ }
+ }
+
+ MakeDirtyCallback();
+ return NS_OK;
+}
+
+
+nsresult
+PREF_ClearUserPref(const char *pref_name)
+{
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+ if (pref && pref->prefFlags.HasUserValue()) {
+ pref->prefFlags.SetHasUserValue(false);
+
+ if (!pref->prefFlags.HasDefault()) {
+ gHashTable->RemoveEntry(pref);
+ }
+
+ pref_DoCallback(pref_name);
+ MakeDirtyCallback();
+ }
+ return NS_OK;
+}
+
+nsresult
+PREF_ClearAllUserPrefs()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ std::vector<std::string> prefStrings;
+ for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
+ auto pref = static_cast<PrefHashEntry*>(iter.Get());
+
+ if (pref->prefFlags.HasUserValue()) {
+ prefStrings.push_back(std::string(pref->key));
+
+ pref->prefFlags.SetHasUserValue(false);
+ if (!pref->prefFlags.HasDefault()) {
+ iter.Remove();
+ }
+ }
+ }
+
+ for (std::string& prefString : prefStrings) {
+ pref_DoCallback(prefString.c_str());
+ }
+
+ MakeDirtyCallback();
+ return NS_OK;
+}
+
+nsresult PREF_LockPref(const char *key, bool lockit)
+{
+ if (!gHashTable)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ PrefHashEntry* pref = pref_HashTableLookup(key);
+ if (!pref)
+ return NS_ERROR_UNEXPECTED;
+
+ if (lockit) {
+ if (!pref->prefFlags.IsLocked()) {
+ pref->prefFlags.SetLocked(true);
+ gIsAnyPrefLocked = true;
+ pref_DoCallback(key);
+ }
+ } else {
+ if (pref->prefFlags.IsLocked()) {
+ pref->prefFlags.SetLocked(false);
+ pref_DoCallback(key);
+ }
+ }
+ return NS_OK;
+}
+
+/*
+ * Hash table functions
+ */
+static bool pref_ValueChanged(PrefValue oldValue, PrefValue newValue, PrefType type)
+{
+ bool changed = true;
+ switch(type) {
+ case PrefType::String:
+ if (oldValue.stringVal && newValue.stringVal) {
+ changed = (strcmp(oldValue.stringVal, newValue.stringVal) != 0);
+ }
+ break;
+ case PrefType::Int:
+ changed = oldValue.intVal != newValue.intVal;
+ break;
+ case PrefType::Bool:
+ changed = oldValue.boolVal != newValue.boolVal;
+ break;
+ case PrefType::Invalid:
+ default:
+ changed = false;
+ break;
+ }
+ return changed;
+}
+
+/*
+ * Overwrite the type and value of an existing preference. Caller must
+ * ensure that they are not changing the type of a preference that has
+ * a default value.
+ */
+static PrefTypeFlags pref_SetValue(PrefValue* existingValue, PrefTypeFlags flags,
+ PrefValue newValue, PrefType newType)
+{
+ if (flags.IsTypeString() && existingValue->stringVal) {
+ PL_strfree(existingValue->stringVal);
+ }
+ flags.SetPrefType(newType);
+ if (flags.IsTypeString()) {
+ MOZ_ASSERT(newValue.stringVal);
+ existingValue->stringVal = newValue.stringVal ? PL_strdup(newValue.stringVal) : nullptr;
+ }
+ else {
+ *existingValue = newValue;
+ }
+ return flags;
+}
+
+PrefHashEntry* pref_HashTableLookup(const char *key)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return static_cast<PrefHashEntry*>(gHashTable->Search(key));
+}
+
+nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gHashTable)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ auto pref = static_cast<PrefHashEntry*>(gHashTable->Add(key, fallible));
+ if (!pref)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // new entry, better initialize
+ if (!pref->key) {
+
+ // initialize the pref entry
+ pref->prefFlags.Reset().SetPrefType(type);
+ pref->key = ArenaStrDup(key, &gPrefNameArena);
+ memset(&pref->defaultPref, 0, sizeof(pref->defaultPref));
+ memset(&pref->userPref, 0, sizeof(pref->userPref));
+ } else if (pref->prefFlags.HasDefault() && !pref->prefFlags.IsPrefType(type)) {
+ NS_WARNING(nsPrintfCString("Trying to overwrite value of default pref %s with the wrong type!", key).get());
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool valueChanged = false;
+ if (flags & kPrefSetDefault) {
+ if (!pref->prefFlags.IsLocked()) {
+ /* ?? change of semantics? */
+ if (pref_ValueChanged(pref->defaultPref, value, type) ||
+ !pref->prefFlags.HasDefault()) {
+ pref->prefFlags = pref_SetValue(&pref->defaultPref, pref->prefFlags, value, type).SetHasDefault(true);
+ if (flags & kPrefStickyDefault) {
+ pref->prefFlags.SetHasStickyDefault(true);
+ }
+ if (!pref->prefFlags.HasUserValue()) {
+ valueChanged = true;
+ }
+ }
+ // What if we change the default to be the same as the user value?
+ // Should we clear the user value?
+ }
+ } else {
+ /* If new value is same as the default value and it's not a "sticky"
+ pref, then un-set the user value.
+ Otherwise, set the user value only if it has changed */
+ if ((pref->prefFlags.HasDefault()) &&
+ !(pref->prefFlags.HasStickyDefault()) &&
+ !pref_ValueChanged(pref->defaultPref, value, type) &&
+ !(flags & kPrefForceSet)) {
+ if (pref->prefFlags.HasUserValue()) {
+ /* XXX should we free a user-set string value if there is one? */
+ pref->prefFlags.SetHasUserValue(false);
+ if (!pref->prefFlags.IsLocked()) {
+ MakeDirtyCallback();
+ valueChanged = true;
+ }
+ }
+ } else if (!pref->prefFlags.HasUserValue() ||
+ !pref->prefFlags.IsPrefType(type) ||
+ pref_ValueChanged(pref->userPref, value, type) ) {
+ pref->prefFlags = pref_SetValue(&pref->userPref, pref->prefFlags, value, type).SetHasUserValue(true);
+ if (!pref->prefFlags.IsLocked()) {
+ MakeDirtyCallback();
+ valueChanged = true;
+ }
+ }
+ }
+
+ if (valueChanged) {
+ return pref_DoCallback(key);
+ }
+ return NS_OK;
+}
+
+size_t
+pref_SizeOfPrivateData(MallocSizeOf aMallocSizeOf)
+{
+ size_t n = PL_SizeOfArenaPoolExcludingPool(&gPrefNameArena, aMallocSizeOf);
+ for (struct CallbackNode* node = gCallbacks; node; node = node->next) {
+ n += aMallocSizeOf(node);
+ n += aMallocSizeOf(node->domain);
+ }
+ return n;
+}
+
+PrefType
+PREF_GetPrefType(const char *pref_name)
+{
+ if (gHashTable) {
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+ if (pref) {
+ return pref->prefFlags.GetPrefType();
+ }
+ }
+ return PrefType::Invalid;
+}
+
+/* -- */
+
+bool
+PREF_PrefIsLocked(const char *pref_name)
+{
+ bool result = false;
+ if (gIsAnyPrefLocked && gHashTable) {
+ PrefHashEntry* pref = pref_HashTableLookup(pref_name);
+ if (pref && pref->prefFlags.IsLocked()) {
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+/* Adds a node to the beginning of the callback list. */
+void
+PREF_RegisterCallback(const char *pref_node,
+ PrefChangedFunc callback,
+ void * instance_data)
+{
+ NS_PRECONDITION(pref_node, "pref_node must not be nullptr");
+ NS_PRECONDITION(callback, "callback must not be nullptr");
+
+ struct CallbackNode* node = (struct CallbackNode*) malloc(sizeof(struct CallbackNode));
+ if (node)
+ {
+ node->domain = PL_strdup(pref_node);
+ node->func = callback;
+ node->data = instance_data;
+ node->next = gCallbacks;
+ gCallbacks = node;
+ }
+ return;
+}
+
+/* Removes |node| from gCallbacks list.
+ Returns the node after the deleted one. */
+struct CallbackNode*
+pref_RemoveCallbackNode(struct CallbackNode* node,
+ struct CallbackNode* prev_node)
+{
+ NS_PRECONDITION(!prev_node || prev_node->next == node, "invalid params");
+ NS_PRECONDITION(prev_node || gCallbacks == node, "invalid params");
+
+ NS_ASSERTION(!gCallbacksInProgress,
+ "modifying the callback list while gCallbacksInProgress is true");
+
+ struct CallbackNode* next_node = node->next;
+ if (prev_node)
+ prev_node->next = next_node;
+ else
+ gCallbacks = next_node;
+ PL_strfree(node->domain);
+ free(node);
+ return next_node;
+}
+
+/* Deletes a node from the callback list or marks it for deletion. */
+nsresult
+PREF_UnregisterCallback(const char *pref_node,
+ PrefChangedFunc callback,
+ void * instance_data)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ struct CallbackNode* node = gCallbacks;
+ struct CallbackNode* prev_node = nullptr;
+
+ while (node != nullptr)
+ {
+ if ( node->func == callback &&
+ node->data == instance_data &&
+ strcmp(node->domain, pref_node) == 0)
+ {
+ if (gCallbacksInProgress)
+ {
+ // postpone the node removal until after
+ // gCallbacks enumeration is finished.
+ node->func = nullptr;
+ gShouldCleanupDeadNodes = true;
+ prev_node = node;
+ node = node->next;
+ }
+ else
+ {
+ node = pref_RemoveCallbackNode(node, prev_node);
+ }
+ rv = NS_OK;
+ }
+ else
+ {
+ prev_node = node;
+ node = node->next;
+ }
+ }
+ return rv;
+}
+
+static nsresult pref_DoCallback(const char* changed_pref)
+{
+ nsresult rv = NS_OK;
+ struct CallbackNode* node;
+
+ bool reentered = gCallbacksInProgress;
+ gCallbacksInProgress = true;
+ // Nodes must not be deleted while gCallbacksInProgress is true.
+ // Nodes that need to be deleted are marked for deletion by nulling
+ // out the |func| pointer. We release them at the end of this function
+ // if we haven't reentered.
+
+ for (node = gCallbacks; node != nullptr; node = node->next)
+ {
+ if ( node->func &&
+ PL_strncmp(changed_pref,
+ node->domain,
+ strlen(node->domain)) == 0 )
+ {
+ (*node->func) (changed_pref, node->data);
+ }
+ }
+
+ gCallbacksInProgress = reentered;
+
+ if (gShouldCleanupDeadNodes && !gCallbacksInProgress)
+ {
+ struct CallbackNode* prev_node = nullptr;
+ node = gCallbacks;
+
+ while (node != nullptr)
+ {
+ if (!node->func)
+ {
+ node = pref_RemoveCallbackNode(node, prev_node);
+ }
+ else
+ {
+ prev_node = node;
+ node = node->next;
+ }
+ }
+ gShouldCleanupDeadNodes = false;
+ }
+
+ return rv;
+}
+
+void PREF_ReaderCallback(void *closure,
+ const char *pref,
+ PrefValue value,
+ PrefType type,
+ bool isDefault,
+ bool isStickyDefault)
+
+{
+ uint32_t flags = 0;
+ if (isDefault) {
+ flags |= kPrefSetDefault;
+ if (isStickyDefault) {
+ flags |= kPrefStickyDefault;
+ }
+ } else {
+ flags |= kPrefForceSet;
+ }
+ pref_HashPref(pref, value, type, flags);
+}
diff --git a/components/preferences/src/prefapi.h b/components/preferences/src/prefapi.h
new file mode 100644
index 000000000..9ef014ada
--- /dev/null
+++ b/components/preferences/src/prefapi.h
@@ -0,0 +1,258 @@
+/* -*- Mode: C; 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/. */
+
+/*
+// <pre>
+*/
+#ifndef PREFAPI_H
+#define PREFAPI_H
+
+#include "nscore.h"
+#include "PLDHashTable.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// 1 MB should be enough for everyone.
+static const uint32_t MAX_PREF_LENGTH = 1 * 1024 * 1024;
+// Actually, 4kb should be enough for everyone.
+static const uint32_t MAX_ADVISABLE_PREF_LENGTH = 4 * 1024;
+
+typedef union
+{
+ char* stringVal;
+ int32_t intVal;
+ bool boolVal;
+} PrefValue;
+
+/*
+// <font color=blue>
+// The Init function initializes the preference context and creates
+// the preference hashtable.
+// </font>
+*/
+void PREF_Init();
+
+/*
+// Cleanup should be called at program exit to free the
+// list of registered callbacks.
+*/
+void PREF_Cleanup();
+void PREF_CleanupPrefs();
+
+/*
+// <font color=blue>
+// Preference flags, including the native type of the preference. Changing any of these
+// values will require modifying the code inside of PrefTypeFlags class.
+// </font>
+*/
+
+enum class PrefType {
+ Invalid = 0,
+ String = 1,
+ Int = 2,
+ Bool = 3,
+};
+
+// Keep the type of the preference, as well as the flags guiding its behaviour.
+class PrefTypeFlags
+{
+public:
+ PrefTypeFlags() : mValue(AsInt(PrefType::Invalid)) {}
+ explicit PrefTypeFlags(PrefType aType) : mValue(AsInt(aType)) {}
+ PrefTypeFlags& Reset() { mValue = AsInt(PrefType::Invalid); return *this; }
+
+ bool IsTypeValid() const { return !IsPrefType(PrefType::Invalid); }
+ bool IsTypeString() const { return IsPrefType(PrefType::String); }
+ bool IsTypeInt() const { return IsPrefType(PrefType::Int); }
+ bool IsTypeBool() const { return IsPrefType(PrefType::Bool); }
+ bool IsPrefType(PrefType type) const { return GetPrefType() == type; }
+
+ PrefTypeFlags& SetPrefType(PrefType aType) {
+ mValue = mValue - AsInt(GetPrefType()) + AsInt(aType);
+ return *this;
+ }
+ PrefType GetPrefType() const {
+ return (PrefType)(mValue & (AsInt(PrefType::String) |
+ AsInt(PrefType::Int) |
+ AsInt(PrefType::Bool)));
+ }
+
+ bool HasDefault() const { return mValue & PREF_FLAG_HAS_DEFAULT; }
+ PrefTypeFlags& SetHasDefault(bool aSetOrUnset) { return SetFlag(PREF_FLAG_HAS_DEFAULT, aSetOrUnset); }
+
+ bool HasStickyDefault() const { return mValue & PREF_FLAG_STICKY_DEFAULT; }
+ PrefTypeFlags& SetHasStickyDefault(bool aSetOrUnset) { return SetFlag(PREF_FLAG_STICKY_DEFAULT, aSetOrUnset); }
+
+ bool IsLocked() const { return mValue & PREF_FLAG_LOCKED; }
+ PrefTypeFlags& SetLocked(bool aSetOrUnset) { return SetFlag(PREF_FLAG_LOCKED, aSetOrUnset); }
+
+ bool HasUserValue() const { return mValue & PREF_FLAG_USERSET; }
+ PrefTypeFlags& SetHasUserValue(bool aSetOrUnset) { return SetFlag(PREF_FLAG_USERSET, aSetOrUnset); }
+
+private:
+ static uint16_t AsInt(PrefType aType) { return (uint16_t)aType; }
+
+ PrefTypeFlags& SetFlag(uint16_t aFlag, bool aSetOrUnset) {
+ mValue = aSetOrUnset ? mValue | aFlag : mValue & ~aFlag;
+ return *this;
+ }
+
+ // Pack both the value of type (PrefType) and flags into the same int. This is why
+ // the flag enum starts at 4, as PrefType occupies the bottom two bits.
+ enum {
+ PREF_FLAG_LOCKED = 4,
+ PREF_FLAG_USERSET = 8,
+ PREF_FLAG_CONFIG = 16,
+ PREF_FLAG_REMOTE = 32,
+ PREF_FLAG_LILOCAL = 64,
+ PREF_FLAG_HAS_DEFAULT = 128,
+ PREF_FLAG_STICKY_DEFAULT = 256,
+ };
+ uint16_t mValue;
+};
+
+struct PrefHashEntry : PLDHashEntryHdr
+{
+ PrefTypeFlags prefFlags; // This field goes first to minimize struct size on 64-bit.
+ const char *key;
+ PrefValue defaultPref;
+ PrefValue userPref;
+};
+
+/*
+// <font color=blue>
+// Set the various types of preferences. These functions take a dotted
+// notation of the preference name (e.g. "browser.startup.homepage").
+// Note that this will cause the preference to be saved to the file if
+// it is different from the default. In other words, these are used
+// to set the _user_ preferences.
+//
+// If set_default is set to true however, it sets the default value.
+// This will only affect the program behavior if the user does not have a value
+// saved over it for the particular preference. In addition, these will never
+// be saved out to disk.
+//
+// Each set returns PREF_VALUECHANGED if the user value changed
+// (triggering a callback), or PREF_NOERROR if the value was unchanged.
+// </font>
+*/
+nsresult PREF_SetCharPref(const char *pref,const char* value, bool set_default = false);
+nsresult PREF_SetIntPref(const char *pref,int32_t value, bool set_default = false);
+nsresult PREF_SetBoolPref(const char *pref,bool value, bool set_default = false);
+
+bool PREF_HasUserPref(const char* pref_name);
+
+/*
+// <font color=blue>
+// Get the various types of preferences. These functions take a dotted
+// notation of the preference name (e.g. "browser.startup.homepage")
+//
+// They also take a pointer to fill in with the return value and return an
+// error value. At the moment, this is simply an int but it may
+// be converted to an enum once the global error strategy is worked out.
+//
+// They will perform conversion if the type doesn't match what was requested.
+// (if it is reasonably possible)
+// </font>
+*/
+nsresult PREF_GetIntPref(const char *pref,
+ int32_t * return_int, bool get_default);
+nsresult PREF_GetBoolPref(const char *pref, bool * return_val, bool get_default);
+/*
+// <font color=blue>
+// These functions are similar to the above "Get" version with the significant
+// difference that the preference module will alloc the memory (e.g. XP_STRDUP) and
+// the caller will need to be responsible for freeing it...
+// </font>
+*/
+nsresult PREF_CopyCharPref(const char *pref, char ** return_buf, bool get_default);
+/*
+// <font color=blue>
+// bool function that returns whether or not the preference is locked and therefore
+// cannot be changed.
+// </font>
+*/
+bool PREF_PrefIsLocked(const char *pref_name);
+
+/*
+// <font color=blue>
+// Function that sets whether or not the preference is locked and therefore
+// cannot be changed.
+// </font>
+*/
+nsresult PREF_LockPref(const char *key, bool lockIt);
+
+PrefType PREF_GetPrefType(const char *pref_name);
+
+/*
+ * Delete a branch of the tree
+ */
+nsresult PREF_DeleteBranch(const char *branch_name);
+
+/*
+ * Clears the given pref (reverts it to its default value)
+ */
+nsresult PREF_ClearUserPref(const char *pref_name);
+
+/*
+ * Clears all user prefs
+ */
+nsresult PREF_ClearAllUserPrefs();
+
+
+/*
+// <font color=blue>
+// The callback function will get passed the pref_node which triggered the call
+// and the void * instance_data which was passed to the register callback function.
+// Return a non-zero result (nsresult) to pass an error up to the caller.
+// </font>
+*/
+/* Temporarily conditionally compile PrefChangedFunc typedef.
+** During migration from old libpref to nsIPref we need it in
+** both header files. Eventually prefapi.h will become a private
+** file. The two types need to be in sync for now. Certain
+** compilers were having problems with multiple definitions.
+*/
+#ifndef have_PrefChangedFunc_typedef
+typedef void (*PrefChangedFunc) (const char *, void *);
+#define have_PrefChangedFunc_typedef
+#endif
+
+/*
+// <font color=blue>
+// Register a callback. This takes a node in the preference tree and will
+// call the callback function if anything below that node is modified.
+// Unregister returns PREF_NOERROR if a callback was found that
+// matched all the parameters; otherwise it returns PREF_ERROR.
+// </font>
+*/
+void PREF_RegisterCallback(const char* domain,
+ PrefChangedFunc callback, void* instance_data );
+nsresult PREF_UnregisterCallback(const char* domain,
+ PrefChangedFunc callback, void* instance_data );
+
+/*
+ * Used by nsPrefService as the callback function of the 'pref' parser
+ */
+void PREF_ReaderCallback( void *closure,
+ const char *pref,
+ PrefValue value,
+ PrefType type,
+ bool isDefault,
+ bool isStickyDefault);
+
+
+/*
+ * Callback whenever we change a preference
+ */
+typedef void (*PrefsDirtyFunc) ();
+void PREF_SetDirtyCallback(PrefsDirtyFunc);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/components/preferences/src/prefapi_private_data.h b/components/preferences/src/prefapi_private_data.h
new file mode 100644
index 000000000..f1fa68fdc
--- /dev/null
+++ b/components/preferences/src/prefapi_private_data.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; 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/. */
+
+/* Data shared between prefapi.c and nsPref.cpp */
+
+#ifndef prefapi_private_data_h
+#define prefapi_private_data_h
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+
+extern PLDHashTable* gHashTable;
+
+namespace mozilla {
+namespace dom {
+class PrefSetting;
+} // namespace dom
+} // namespace mozilla
+
+mozilla::UniquePtr<char*[]>
+pref_savePrefs(PLDHashTable* aTable, uint32_t* aPrefCount);
+
+nsresult
+pref_SetPref(const mozilla::dom::PrefSetting& aPref);
+
+int pref_CompareStrings(const void *v1, const void *v2, void* unused);
+PrefHashEntry* pref_HashTableLookup(const char *key);
+
+bool
+pref_EntryHasAdvisablySizedValues(PrefHashEntry* aHashEntry);
+
+void pref_GetPrefFromEntry(PrefHashEntry *aHashEntry,
+ mozilla::dom::PrefSetting* aPref);
+
+size_t
+pref_SizeOfPrivateData(mozilla::MallocSizeOf aMallocSizeOf);
+
+#endif
diff --git a/components/preferences/src/prefread.cpp b/components/preferences/src/prefread.cpp
new file mode 100644
index 000000000..605dcaac6
--- /dev/null
+++ b/components/preferences/src/prefread.cpp
@@ -0,0 +1,657 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "prefread.h"
+#include "nsString.h"
+#include "nsUTF8Utils.h"
+
+#ifdef TEST_PREFREAD
+#include <stdio.h>
+#define NS_WARNING(_s) printf(">>> " _s "!\n")
+#define NS_NOTREACHED(_s) NS_WARNING(_s)
+#else
+#include "nsDebug.h" // for NS_WARNING
+#endif
+
+/* pref parser states */
+enum {
+ PREF_PARSE_INIT,
+ PREF_PARSE_MATCH_STRING,
+ PREF_PARSE_UNTIL_NAME,
+ PREF_PARSE_QUOTED_STRING,
+ PREF_PARSE_UNTIL_COMMA,
+ PREF_PARSE_UNTIL_VALUE,
+ PREF_PARSE_INT_VALUE,
+ PREF_PARSE_COMMENT_MAYBE_START,
+ PREF_PARSE_COMMENT_BLOCK,
+ PREF_PARSE_COMMENT_BLOCK_MAYBE_END,
+ PREF_PARSE_ESC_SEQUENCE,
+ PREF_PARSE_HEX_ESCAPE,
+ PREF_PARSE_UTF16_LOW_SURROGATE,
+ PREF_PARSE_UNTIL_OPEN_PAREN,
+ PREF_PARSE_UNTIL_CLOSE_PAREN,
+ PREF_PARSE_UNTIL_SEMICOLON,
+ PREF_PARSE_UNTIL_EOL
+};
+
+#define UTF16_ESC_NUM_DIGITS 4
+#define HEX_ESC_NUM_DIGITS 2
+#define BITS_PER_HEX_DIGIT 4
+
+static const char kUserPref[] = "user_pref";
+static const char kPref[] = "pref";
+static const char kPrefSticky[] = "sticky_pref";
+static const char kTrue[] = "true";
+static const char kFalse[] = "false";
+
+/**
+ * pref_GrowBuf
+ *
+ * this function will increase the size of the buffer owned
+ * by the given pref parse state. We currently use a simple
+ * doubling algorithm, but the only hard requirement is that
+ * it increase the buffer by at least the size of the ps->esctmp
+ * buffer used for escape processing (currently 6 bytes).
+ *
+ * this buffer is used to store partial pref lines. it is
+ * freed when the parse state is destroyed.
+ *
+ * @param ps
+ * parse state instance
+ *
+ * this function updates all pointers that reference an
+ * address within lb since realloc may relocate the buffer.
+ *
+ * @return false if insufficient memory.
+ */
+static bool
+pref_GrowBuf(PrefParseState *ps)
+{
+ int bufLen, curPos, valPos;
+
+ bufLen = ps->lbend - ps->lb;
+ curPos = ps->lbcur - ps->lb;
+ valPos = ps->vb - ps->lb;
+
+ if (bufLen == 0)
+ bufLen = 128; /* default buffer size */
+ else
+ bufLen <<= 1; /* double buffer size */
+
+#ifdef TEST_PREFREAD
+ fprintf(stderr, ">>> realloc(%d)\n", bufLen);
+#endif
+
+ ps->lb = (char*) realloc(ps->lb, bufLen);
+ if (!ps->lb)
+ return false;
+
+ ps->lbcur = ps->lb + curPos;
+ ps->lbend = ps->lb + bufLen;
+ ps->vb = ps->lb + valPos;
+
+ return true;
+}
+
+/**
+ * Report an error or a warning. If not specified, just dump to stderr.
+ */
+static void
+pref_ReportParseProblem(PrefParseState& ps, const char* aMessage, int aLine, bool aError)
+{
+ if (ps.reporter) {
+ ps.reporter(aMessage, aLine, aError);
+ } else {
+ printf_stderr("**** Preference parsing %s (line %d) = %s **\n",
+ (aError ? "error" : "warning"), aLine, aMessage);
+ }
+}
+
+/**
+ * pref_DoCallback
+ *
+ * this function is called when a complete pref name-value pair has
+ * been extracted from the input data.
+ *
+ * @param ps
+ * parse state instance
+ *
+ * @return false to indicate a fatal error.
+ */
+static bool
+pref_DoCallback(PrefParseState *ps)
+{
+ PrefValue value;
+
+ switch (ps->vtype) {
+ case PrefType::String:
+ value.stringVal = ps->vb;
+ break;
+ case PrefType::Int:
+ if ((ps->vb[0] == '-' || ps->vb[0] == '+') && ps->vb[1] == '\0') {
+ pref_ReportParseProblem(*ps, "invalid integer value", 0, true);
+ NS_WARNING("malformed integer value");
+ return false;
+ }
+ value.intVal = atoi(ps->vb);
+ break;
+ case PrefType::Bool:
+ value.boolVal = (ps->vb == kTrue);
+ break;
+ default:
+ break;
+ }
+ (*ps->reader)(ps->closure, ps->lb, value, ps->vtype, ps->fdefault,
+ ps->fstickydefault);
+ return true;
+}
+
+void
+PREF_InitParseState(PrefParseState *ps, PrefReader reader,
+ PrefParseErrorReporter reporter, void *closure)
+{
+ memset(ps, 0, sizeof(*ps));
+ ps->reader = reader;
+ ps->closure = closure;
+ ps->reporter = reporter;
+}
+
+void
+PREF_FinalizeParseState(PrefParseState *ps)
+{
+ if (ps->lb)
+ free(ps->lb);
+}
+
+/**
+ * Pseudo-BNF
+ * ----------
+ * function = LJUNK function-name JUNK function-args
+ * function-name = "user_pref" | "pref" | "sticky_pref"
+ * function-args = "(" JUNK pref-name JUNK "," JUNK pref-value JUNK ")" JUNK ";"
+ * pref-name = quoted-string
+ * pref-value = quoted-string | "true" | "false" | integer-value
+ * JUNK = *(WS | comment-block | comment-line)
+ * LJUNK = *(WS | comment-block | comment-line | bcomment-line)
+ * WS = SP | HT | LF | VT | FF | CR
+ * SP = <US-ASCII SP, space (32)>
+ * HT = <US-ASCII HT, horizontal-tab (9)>
+ * LF = <US-ASCII LF, linefeed (10)>
+ * VT = <US-ASCII HT, vertical-tab (11)>
+ * FF = <US-ASCII FF, form-feed (12)>
+ * CR = <US-ASCII CR, carriage return (13)>
+ * comment-block = <C/C++ style comment block>
+ * comment-line = <C++ style comment line>
+ * bcomment-line = <bourne-shell style comment line>
+ */
+bool
+PREF_ParseBuf(PrefParseState *ps, const char *buf, int bufLen)
+{
+ const char *end;
+ char c;
+ char udigit;
+ int state;
+
+ // The line number is currently only used for the error/warning reporting.
+ int lineNum = 0;
+
+ state = ps->state;
+ for (end = buf + bufLen; buf != end; ++buf) {
+ c = *buf;
+ if (c == '\r' || c == '\n' || c == 0x1A) {
+ lineNum ++;
+ }
+
+ switch (state) {
+ /* initial state */
+ case PREF_PARSE_INIT:
+ if (ps->lbcur != ps->lb) { /* reset state */
+ ps->lbcur = ps->lb;
+ ps->vb = nullptr;
+ ps->vtype = PrefType::Invalid;
+ ps->fdefault = false;
+ ps->fstickydefault = false;
+ }
+ switch (c) {
+ case '/': /* begin comment block or line? */
+ state = PREF_PARSE_COMMENT_MAYBE_START;
+ break;
+ case '#': /* accept shell style comments */
+ state = PREF_PARSE_UNTIL_EOL;
+ break;
+ case 'u': /* indicating user_pref */
+ case 's': /* indicating sticky_pref */
+ case 'p': /* indicating pref */
+ if (c == 'u') {
+ ps->smatch = kUserPref;
+ } else if (c == 's') {
+ ps->smatch = kPrefSticky;
+ } else {
+ ps->smatch = kPref;
+ }
+ ps->sindex = 1;
+ ps->nextstate = PREF_PARSE_UNTIL_OPEN_PAREN;
+ state = PREF_PARSE_MATCH_STRING;
+ break;
+ /* else skip char */
+ }
+ break;
+
+ /* string matching */
+ case PREF_PARSE_MATCH_STRING:
+ if (c == ps->smatch[ps->sindex++]) {
+ /* if we've matched all characters, then move to next state. */
+ if (ps->smatch[ps->sindex] == '\0') {
+ state = ps->nextstate;
+ ps->nextstate = PREF_PARSE_INIT; /* reset next state */
+ }
+ /* else wait for next char */
+ }
+ else {
+ pref_ReportParseProblem(*ps, "non-matching string", lineNum, true);
+ NS_WARNING("malformed pref file");
+ return false;
+ }
+ break;
+
+ /* quoted string parsing */
+ case PREF_PARSE_QUOTED_STRING:
+ /* we assume that the initial quote has already been consumed */
+ if (ps->lbcur == ps->lbend && !pref_GrowBuf(ps))
+ return false; /* out of memory */
+ if (c == '\\')
+ state = PREF_PARSE_ESC_SEQUENCE;
+ else if (c == ps->quotechar) {
+ *ps->lbcur++ = '\0';
+ state = ps->nextstate;
+ ps->nextstate = PREF_PARSE_INIT; /* reset next state */
+ }
+ else
+ *ps->lbcur++ = c;
+ break;
+
+ /* name parsing */
+ case PREF_PARSE_UNTIL_NAME:
+ if (c == '\"' || c == '\'') {
+ ps->fdefault = (ps->smatch == kPref || ps->smatch == kPrefSticky);
+ ps->fstickydefault = (ps->smatch == kPrefSticky);
+ ps->quotechar = c;
+ ps->nextstate = PREF_PARSE_UNTIL_COMMA; /* return here when done */
+ state = PREF_PARSE_QUOTED_STRING;
+ }
+ else if (c == '/') { /* allow embedded comment */
+ ps->nextstate = state; /* return here when done with comment */
+ state = PREF_PARSE_COMMENT_MAYBE_START;
+ }
+ else if (!isspace(c)) {
+ pref_ReportParseProblem(*ps, "need space, comment or quote", lineNum, true);
+ NS_WARNING("malformed pref file");
+ return false;
+ }
+ break;
+
+ /* parse until we find a comma separating name and value */
+ case PREF_PARSE_UNTIL_COMMA:
+ if (c == ',') {
+ ps->vb = ps->lbcur;
+ state = PREF_PARSE_UNTIL_VALUE;
+ }
+ else if (c == '/') { /* allow embedded comment */
+ ps->nextstate = state; /* return here when done with comment */
+ state = PREF_PARSE_COMMENT_MAYBE_START;
+ }
+ else if (!isspace(c)) {
+ pref_ReportParseProblem(*ps, "need space, comment or comma", lineNum, true);
+ NS_WARNING("malformed pref file");
+ return false;
+ }
+ break;
+
+ /* value parsing */
+ case PREF_PARSE_UNTIL_VALUE:
+ /* the pref value type is unknown. so, we scan for the first
+ * character of the value, and determine the type from that. */
+ if (c == '\"' || c == '\'') {
+ ps->vtype = PrefType::String;
+ ps->quotechar = c;
+ ps->nextstate = PREF_PARSE_UNTIL_CLOSE_PAREN;
+ state = PREF_PARSE_QUOTED_STRING;
+ }
+ else if (c == 't' || c == 'f') {
+ ps->vb = (char *) (c == 't' ? kTrue : kFalse);
+ ps->vtype = PrefType::Bool;
+ ps->smatch = ps->vb;
+ ps->sindex = 1;
+ ps->nextstate = PREF_PARSE_UNTIL_CLOSE_PAREN;
+ state = PREF_PARSE_MATCH_STRING;
+ }
+ else if (isdigit(c) || (c == '-') || (c == '+')) {
+ ps->vtype = PrefType::Int;
+ /* write c to line buffer... */
+ if (ps->lbcur == ps->lbend && !pref_GrowBuf(ps))
+ return false; /* out of memory */
+ *ps->lbcur++ = c;
+ state = PREF_PARSE_INT_VALUE;
+ }
+ else if (c == '/') { /* allow embedded comment */
+ ps->nextstate = state; /* return here when done with comment */
+ state = PREF_PARSE_COMMENT_MAYBE_START;
+ }
+ else if (!isspace(c)) {
+ pref_ReportParseProblem(*ps, "need value, comment or space", lineNum, true);
+ NS_WARNING("malformed pref file");
+ return false;
+ }
+ break;
+ case PREF_PARSE_INT_VALUE:
+ /* grow line buffer if necessary... */
+ if (ps->lbcur == ps->lbend && !pref_GrowBuf(ps))
+ return false; /* out of memory */
+ if (isdigit(c))
+ *ps->lbcur++ = c;
+ else {
+ *ps->lbcur++ = '\0'; /* stomp null terminator; we are done. */
+ if (c == ')')
+ state = PREF_PARSE_UNTIL_SEMICOLON;
+ else if (c == '/') { /* allow embedded comment */
+ ps->nextstate = PREF_PARSE_UNTIL_CLOSE_PAREN;
+ state = PREF_PARSE_COMMENT_MAYBE_START;
+ }
+ else if (isspace(c))
+ state = PREF_PARSE_UNTIL_CLOSE_PAREN;
+ else {
+ pref_ReportParseProblem(*ps, "while parsing integer", lineNum, true);
+ NS_WARNING("malformed pref file");
+ return false;
+ }
+ }
+ break;
+
+ /* comment parsing */
+ case PREF_PARSE_COMMENT_MAYBE_START:
+ switch (c) {
+ case '*': /* comment block */
+ state = PREF_PARSE_COMMENT_BLOCK;
+ break;
+ case '/': /* comment line */
+ state = PREF_PARSE_UNTIL_EOL;
+ break;
+ default:
+ /* pref file is malformed */
+ pref_ReportParseProblem(*ps, "while parsing comment", lineNum, true);
+ NS_WARNING("malformed pref file");
+ return false;
+ }
+ break;
+ case PREF_PARSE_COMMENT_BLOCK:
+ if (c == '*')
+ state = PREF_PARSE_COMMENT_BLOCK_MAYBE_END;
+ break;
+ case PREF_PARSE_COMMENT_BLOCK_MAYBE_END:
+ switch (c) {
+ case '/':
+ state = ps->nextstate;
+ ps->nextstate = PREF_PARSE_INIT;
+ break;
+ case '*': /* stay in this state */
+ break;
+ default:
+ state = PREF_PARSE_COMMENT_BLOCK;
+ }
+ break;
+
+ /* string escape sequence parsing */
+ case PREF_PARSE_ESC_SEQUENCE:
+ /* not necessary to resize buffer here since we should be writing
+ * only one character and the resize check would have been done
+ * for us in the previous state */
+ switch (c) {
+ case '\"':
+ case '\'':
+ case '\\':
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case 'x': /* hex escape -- always interpreted as Latin-1 */
+ case 'u': /* UTF16 escape */
+ ps->esctmp[0] = c;
+ ps->esclen = 1;
+ ps->utf16[0] = ps->utf16[1] = 0;
+ ps->sindex = (c == 'x' ) ?
+ HEX_ESC_NUM_DIGITS :
+ UTF16_ESC_NUM_DIGITS;
+ state = PREF_PARSE_HEX_ESCAPE;
+ continue;
+ default:
+ pref_ReportParseProblem(*ps, "preserving unexpected JS escape sequence",
+ lineNum, false);
+ NS_WARNING("preserving unexpected JS escape sequence");
+ /* Invalid escape sequence so we do have to write more than
+ * one character. Grow line buffer if necessary... */
+ if ((ps->lbcur+1) == ps->lbend && !pref_GrowBuf(ps))
+ return false; /* out of memory */
+ *ps->lbcur++ = '\\'; /* preserve the escape sequence */
+ break;
+ }
+ *ps->lbcur++ = c;
+ state = PREF_PARSE_QUOTED_STRING;
+ break;
+
+ /* parsing a hex (\xHH) or utf16 escape (\uHHHH) */
+ case PREF_PARSE_HEX_ESCAPE:
+ if ( c >= '0' && c <= '9' )
+ udigit = (c - '0');
+ else if ( c >= 'A' && c <= 'F' )
+ udigit = (c - 'A') + 10;
+ else if ( c >= 'a' && c <= 'f' )
+ udigit = (c - 'a') + 10;
+ else {
+ /* bad escape sequence found, write out broken escape as-is */
+ pref_ReportParseProblem(*ps, "preserving invalid or incomplete hex escape",
+ lineNum, false);
+ NS_WARNING("preserving invalid or incomplete hex escape");
+ *ps->lbcur++ = '\\'; /* original escape slash */
+ if ((ps->lbcur + ps->esclen) >= ps->lbend && !pref_GrowBuf(ps))
+ return false;
+ for (int i = 0; i < ps->esclen; ++i)
+ *ps->lbcur++ = ps->esctmp[i];
+
+ /* push the non-hex character back for re-parsing. */
+ /* (++buf at the top of the loop keeps this safe) */
+ --buf;
+ state = PREF_PARSE_QUOTED_STRING;
+ continue;
+ }
+
+ /* have a digit */
+ ps->esctmp[ps->esclen++] = c; /* preserve it */
+ ps->utf16[1] <<= BITS_PER_HEX_DIGIT;
+ ps->utf16[1] |= udigit;
+ ps->sindex--;
+ if (ps->sindex == 0) {
+ /* have the full escape. Convert to UTF8 */
+ int utf16len = 0;
+ if (ps->utf16[0]) {
+ /* already have a high surrogate, this is a two char seq */
+ utf16len = 2;
+ }
+ else if (0xD800 == (0xFC00 & ps->utf16[1])) {
+ /* a high surrogate, can't convert until we have the low */
+ ps->utf16[0] = ps->utf16[1];
+ ps->utf16[1] = 0;
+ state = PREF_PARSE_UTF16_LOW_SURROGATE;
+ break;
+ }
+ else {
+ /* a single utf16 character */
+ ps->utf16[0] = ps->utf16[1];
+ utf16len = 1;
+ }
+
+ /* actual conversion */
+ /* make sure there's room, 6 bytes is max utf8 len (in */
+ /* theory; 4 bytes covers the actual utf16 range) */
+ if (ps->lbcur+6 >= ps->lbend && !pref_GrowBuf(ps))
+ return false;
+
+ ConvertUTF16toUTF8 converter(ps->lbcur);
+ converter.write(ps->utf16, utf16len);
+ ps->lbcur += converter.Size();
+ state = PREF_PARSE_QUOTED_STRING;
+ }
+ break;
+
+ /* looking for beginning of utf16 low surrogate */
+ case PREF_PARSE_UTF16_LOW_SURROGATE:
+ if (ps->sindex == 0 && c == '\\') {
+ ++ps->sindex;
+ }
+ else if (ps->sindex == 1 && c == 'u') {
+ /* escape sequence is correct, now parse hex */
+ ps->sindex = UTF16_ESC_NUM_DIGITS;
+ ps->esctmp[0] = 'u';
+ ps->esclen = 1;
+ state = PREF_PARSE_HEX_ESCAPE;
+ }
+ else {
+ /* didn't find expected low surrogate. Ignore high surrogate
+ * (it would just get converted to nothing anyway) and start
+ * over with this character */
+ --buf;
+ if (ps->sindex == 1)
+ state = PREF_PARSE_ESC_SEQUENCE;
+ else
+ state = PREF_PARSE_QUOTED_STRING;
+ continue;
+ }
+ break;
+
+ /* function open and close parsing */
+ case PREF_PARSE_UNTIL_OPEN_PAREN:
+ /* tolerate only whitespace and embedded comments */
+ if (c == '(')
+ state = PREF_PARSE_UNTIL_NAME;
+ else if (c == '/') {
+ ps->nextstate = state; /* return here when done with comment */
+ state = PREF_PARSE_COMMENT_MAYBE_START;
+ }
+ else if (!isspace(c)) {
+ pref_ReportParseProblem(*ps, "need space, comment or open parentheses",
+ lineNum, true);
+ NS_WARNING("malformed pref file");
+ return false;
+ }
+ break;
+ case PREF_PARSE_UNTIL_CLOSE_PAREN:
+ /* tolerate only whitespace and embedded comments */
+ if (c == ')') {
+ state = PREF_PARSE_UNTIL_SEMICOLON;
+ } else if (c == '/') {
+ ps->nextstate = state; /* return here when done with comment */
+ state = PREF_PARSE_COMMENT_MAYBE_START;
+ } else if (!isspace(c)) {
+ pref_ReportParseProblem(*ps, "need space, comment or closing parentheses",
+ lineNum, true);
+ NS_WARNING("malformed pref file");
+ return false;
+ }
+ break;
+
+ /* function terminator ';' parsing */
+ case PREF_PARSE_UNTIL_SEMICOLON:
+ /* tolerate only whitespace and embedded comments */
+ if (c == ';') {
+ if (!pref_DoCallback(ps))
+ return false;
+ state = PREF_PARSE_INIT;
+ }
+ else if (c == '/') {
+ ps->nextstate = state; /* return here when done with comment */
+ state = PREF_PARSE_COMMENT_MAYBE_START;
+ }
+ else if (!isspace(c)) {
+ pref_ReportParseProblem(*ps, "need space, comment or semicolon",
+ lineNum, true);
+ NS_WARNING("malformed pref file");
+ return false;
+ }
+ break;
+
+ /* eol parsing */
+ case PREF_PARSE_UNTIL_EOL:
+ /* need to handle mac, unix, or dos line endings.
+ * PREF_PARSE_INIT will eat the next \n in case
+ * we have \r\n. */
+ if (c == '\r' || c == '\n' || c == 0x1A) {
+ state = ps->nextstate;
+ ps->nextstate = PREF_PARSE_INIT; /* reset next state */
+ }
+ break;
+ }
+ }
+ ps->state = state;
+ return true;
+}
+
+#ifdef TEST_PREFREAD
+
+static void
+pref_reader(void *closure,
+ const char *pref,
+ PrefValue val,
+ PrefType type,
+ bool defPref)
+{
+ printf("%spref(\"%s\", ", defPref ? "" : "user_", pref);
+ switch (type) {
+ case PREF_STRING:
+ printf("\"%s\");\n", val.stringVal);
+ break;
+ case PREF_INT:
+ printf("%i);\n", val.intVal);
+ break;
+ case PREF_BOOL:
+ printf("%s);\n", val.boolVal == false ? "false" : "true");
+ break;
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ PrefParseState ps;
+ char buf[4096]; /* i/o buffer */
+ FILE *fp;
+ int n;
+
+ if (argc == 1) {
+ printf("usage: prefread file.js\n");
+ return -1;
+ }
+
+ fp = fopen(argv[1], "r");
+ if (!fp) {
+ printf("failed to open file\n");
+ return -1;
+ }
+
+ PREF_InitParseState(&ps, pref_reader, nullptr, nullptr);
+
+ while ((n = fread(buf, 1, sizeof(buf), fp)) > 0)
+ PREF_ParseBuf(&ps, buf, n);
+
+ PREF_FinalizeParseState(&ps);
+
+ fclose(fp);
+ return 0;
+}
+
+#endif /* TEST_PREFREAD */
diff --git a/components/preferences/src/prefread.h b/components/preferences/src/prefread.h
new file mode 100644
index 000000000..2a09b30b6
--- /dev/null
+++ b/components/preferences/src/prefread.h
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 prefread_h__
+#define prefread_h__
+
+#include "prefapi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Callback function used to notify consumer of preference name value pairs.
+ * The pref name and value must be copied by the implementor of the callback
+ * if they are needed beyond the scope of the callback function.
+ *
+ * @param closure
+ * user data passed to PREF_InitParseState
+ * @param pref
+ * preference name
+ * @param val
+ * preference value
+ * @param type
+ * preference type (PREF_STRING, PREF_INT, or PREF_BOOL)
+ * @param defPref
+ * preference type (true: default, false: user preference)
+ * @param stickyPref
+ * default preference marked as a "sticky" pref
+ */
+typedef void (*PrefReader)(void *closure,
+ const char *pref,
+ PrefValue val,
+ PrefType type,
+ bool defPref,
+ bool stickyPref);
+
+/**
+ * Report any errors or warnings we encounter during parsing.
+ */
+typedef void (*PrefParseErrorReporter)(const char* message, int line, bool error);
+
+/* structure fields are private */
+typedef struct PrefParseState {
+ PrefReader reader;
+ PrefParseErrorReporter reporter;
+ void *closure;
+ int state; /* PREF_PARSE_... */
+ int nextstate; /* sometimes used... */
+ const char *smatch; /* string to match */
+ int sindex; /* next char of smatch to check */
+ /* also, counter in \u parsing */
+ char16_t utf16[2]; /* parsing UTF16 (\u) escape */
+ int esclen; /* length in esctmp */
+ char esctmp[6]; /* raw escape to put back if err */
+ char quotechar; /* char delimiter for quotations */
+ char *lb; /* line buffer (only allocation) */
+ char *lbcur; /* line buffer cursor */
+ char *lbend; /* line buffer end */
+ char *vb; /* value buffer (ptr into lb) */
+ PrefType vtype; /* PREF_STRING,INT,BOOL */
+ bool fdefault; /* true if (default) pref */
+ bool fstickydefault; /* true if (sticky) pref */
+} PrefParseState;
+
+/**
+ * PREF_InitParseState
+ *
+ * Called to initialize a PrefParseState instance.
+ *
+ * @param ps
+ * PrefParseState instance.
+ * @param reader
+ * PrefReader callback function, which will be called once for each
+ * preference name value pair extracted.
+ * @param reporter
+ * PrefParseErrorReporter callback function, which will be called if we
+ * encounter any errors (stop) or warnings (continue) during parsing.
+ * @param closure
+ * PrefReader closure.
+ */
+void PREF_InitParseState(PrefParseState *ps, PrefReader reader,
+ PrefParseErrorReporter reporter, void *closure);
+
+/**
+ * PREF_FinalizeParseState
+ *
+ * Called to release any memory in use by the PrefParseState instance.
+ *
+ * @param ps
+ * PrefParseState instance.
+ */
+void PREF_FinalizeParseState(PrefParseState *ps);
+
+/**
+ * PREF_ParseBuf
+ *
+ * Called to parse a buffer containing some portion of a preference file. This
+ * function may be called repeatedly as new data is made available. The
+ * PrefReader callback function passed PREF_InitParseState will be called as
+ * preference name value pairs are extracted from the data.
+ *
+ * @param ps
+ * PrefParseState instance. Must have been initialized.
+ * @param buf
+ * Raw buffer containing data to be parsed.
+ * @param bufLen
+ * Length of buffer.
+ *
+ * @return false if buffer contains malformed content.
+ */
+bool PREF_ParseBuf(PrefParseState *ps, const char *buf, int bufLen);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* prefread_h__ */
diff --git a/components/printing/content/printPageSetup.js b/components/printing/content/printPageSetup.js
new file mode 100644
index 000000000..6bcd97207
--- /dev/null
+++ b/components/printing/content/printPageSetup.js
@@ -0,0 +1,471 @@
+// -*- 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 gDialog;
+var paramBlock;
+var gPrefs = null;
+var gPrintService = null;
+var gPrintSettings = null;
+var gStringBundle = null;
+var gDoingMetric = false;
+
+var gPrintSettingsInterface = Components.interfaces.nsIPrintSettings;
+var gDoDebug = false;
+
+// ---------------------------------------------------
+function initDialog()
+{
+ gDialog = {};
+
+ gDialog.orientation = document.getElementById("orientation");
+ gDialog.portrait = document.getElementById("portrait");
+ gDialog.landscape = document.getElementById("landscape");
+
+ gDialog.printBG = document.getElementById("printBG");
+
+ gDialog.shrinkToFit = document.getElementById("shrinkToFit");
+
+ gDialog.marginGroup = document.getElementById("marginGroup");
+
+ gDialog.marginPage = document.getElementById("marginPage");
+ gDialog.marginTop = document.getElementById("marginTop");
+ gDialog.marginBottom = document.getElementById("marginBottom");
+ gDialog.marginLeft = document.getElementById("marginLeft");
+ gDialog.marginRight = document.getElementById("marginRight");
+
+ gDialog.topInput = document.getElementById("topInput");
+ gDialog.bottomInput = document.getElementById("bottomInput");
+ gDialog.leftInput = document.getElementById("leftInput");
+ gDialog.rightInput = document.getElementById("rightInput");
+
+ gDialog.hLeftOption = document.getElementById("hLeftOption");
+ gDialog.hCenterOption = document.getElementById("hCenterOption");
+ gDialog.hRightOption = document.getElementById("hRightOption");
+
+ gDialog.fLeftOption = document.getElementById("fLeftOption");
+ gDialog.fCenterOption = document.getElementById("fCenterOption");
+ gDialog.fRightOption = document.getElementById("fRightOption");
+
+ gDialog.scalingLabel = document.getElementById("scalingInput");
+ gDialog.scalingInput = document.getElementById("scalingInput");
+
+ gDialog.enabled = false;
+
+ gDialog.strings = new Array;
+ gDialog.strings["marginUnits.inches"] = document.getElementById("marginUnits.inches").childNodes[0].nodeValue;
+ gDialog.strings["marginUnits.metric"] = document.getElementById("marginUnits.metric").childNodes[0].nodeValue;
+ gDialog.strings["customPrompt.title"] = document.getElementById("customPrompt.title").childNodes[0].nodeValue;
+ gDialog.strings["customPrompt.prompt"] = document.getElementById("customPrompt.prompt").childNodes[0].nodeValue;
+
+}
+
+// ---------------------------------------------------
+function isListOfPrinterFeaturesAvailable()
+{
+ return gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".has_special_printerfeatures", false);
+}
+
+// ---------------------------------------------------
+function checkDouble(element)
+{
+ element.value = element.value.replace(/[^.0-9]/g, "");
+}
+
+// Theoretical paper width/height.
+var gPageWidth = 8.5;
+var gPageHeight = 11.0;
+
+// ---------------------------------------------------
+function setOrientation()
+{
+ var selection = gDialog.orientation.selectedItem;
+
+ var style = "background-color:white;";
+ if ((selection == gDialog.portrait && gPageWidth > gPageHeight) ||
+ (selection == gDialog.landscape && gPageWidth < gPageHeight)) {
+ // Swap width/height.
+ var temp = gPageHeight;
+ gPageHeight = gPageWidth;
+ gPageWidth = temp;
+ }
+ var div = gDoingMetric ? 100 : 10;
+ style += "width:" + gPageWidth/div + unitString() + ";height:" + gPageHeight/div + unitString() + ";";
+ gDialog.marginPage.setAttribute( "style", style );
+}
+
+// ---------------------------------------------------
+function unitString()
+{
+ return (gPrintSettings.paperSizeUnit == gPrintSettingsInterface.kPaperSizeInches) ? "in" : "mm";
+}
+
+// ---------------------------------------------------
+function checkMargin( value, max, other )
+{
+ // Don't draw this margin bigger than permitted.
+ return Math.min(value, max - other.value);
+}
+
+// ---------------------------------------------------
+function changeMargin( node )
+{
+ // Correct invalid input.
+ checkDouble(node);
+
+ // Reset the margin height/width for this node.
+ var val = node.value;
+ var nodeToStyle;
+ var attr="width";
+ if ( node == gDialog.topInput ) {
+ nodeToStyle = gDialog.marginTop;
+ val = checkMargin( val, gPageHeight, gDialog.bottomInput );
+ attr = "height";
+ } else if ( node == gDialog.bottomInput ) {
+ nodeToStyle = gDialog.marginBottom;
+ val = checkMargin( val, gPageHeight, gDialog.topInput );
+ attr = "height";
+ } else if ( node == gDialog.leftInput ) {
+ nodeToStyle = gDialog.marginLeft;
+ val = checkMargin( val, gPageWidth, gDialog.rightInput );
+ } else {
+ nodeToStyle = gDialog.marginRight;
+ val = checkMargin( val, gPageWidth, gDialog.leftInput );
+ }
+ var style = attr + ":" + (val/10) + unitString() + ";";
+ nodeToStyle.setAttribute( "style", style );
+}
+
+// ---------------------------------------------------
+function changeMargins()
+{
+ changeMargin( gDialog.topInput );
+ changeMargin( gDialog.bottomInput );
+ changeMargin( gDialog.leftInput );
+ changeMargin( gDialog.rightInput );
+}
+
+// ---------------------------------------------------
+function customize( node )
+{
+ // If selection is now "Custom..." then prompt user for custom setting.
+ if ( node.value == 6 ) {
+ var prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService( Components.interfaces.nsIPromptService );
+ var title = gDialog.strings["customPrompt.title"];
+ var promptText = gDialog.strings["customPrompt.prompt"];
+ var result = { value: node.custom };
+ var ok = prompter.prompt(window, title, promptText, result, null, { value: false } );
+ if ( ok ) {
+ node.custom = result.value;
+ }
+ }
+}
+
+// ---------------------------------------------------
+function setHeaderFooter( node, value )
+{
+ node.value= hfValueToId(value);
+ if (node.value == 6) {
+ // Remember current Custom... value.
+ node.custom = value;
+ } else {
+ // Start with empty Custom... value.
+ node.custom = "";
+ }
+}
+
+var gHFValues = new Array;
+gHFValues["&T"] = 1;
+gHFValues["&U"] = 2;
+gHFValues["&D"] = 3;
+gHFValues["&P"] = 4;
+gHFValues["&PT"] = 5;
+
+function hfValueToId(val)
+{
+ if ( val in gHFValues ) {
+ return gHFValues[val];
+ }
+ if ( val.length ) {
+ return 6; // Custom...
+ }
+ return 0; // --blank--
+}
+
+function hfIdToValue(node)
+{
+ var result = "";
+ switch ( parseInt( node.value ) ) {
+ case 0:
+ break;
+ case 1:
+ result = "&T";
+ break;
+ case 2:
+ result = "&U";
+ break;
+ case 3:
+ result = "&D";
+ break;
+ case 4:
+ result = "&P";
+ break;
+ case 5:
+ result = "&PT";
+ break;
+ case 6:
+ result = node.custom;
+ break;
+ }
+ return result;
+}
+
+function setPrinterDefaultsForSelectedPrinter()
+{
+ if (gPrintSettings.printerName == "") {
+ gPrintSettings.printerName = gPrintService.defaultPrinterName;
+ }
+
+ // First get any defaults from the printer
+ gPrintService.initPrintSettingsFromPrinter(gPrintSettings.printerName, gPrintSettings);
+
+ // now augment them with any values from last time
+ gPrintService.initPrintSettingsFromPrefs(gPrintSettings, true, gPrintSettingsInterface.kInitSaveAll);
+
+ if (gDoDebug) {
+ dump("pagesetup/setPrinterDefaultsForSelectedPrinter: printerName='"+gPrintSettings.printerName+"', orientation='"+gPrintSettings.orientation+"'\n");
+ }
+}
+
+// ---------------------------------------------------
+function loadDialog()
+{
+ var print_orientation = 0;
+ var print_margin_top = 0.5;
+ var print_margin_left = 0.5;
+ var print_margin_bottom = 0.5;
+ var print_margin_right = 0.5;
+
+ try {
+ gPrefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
+
+ gPrintService = Components.classes["@mozilla.org/gfx/printsettings-service;1"];
+ if (gPrintService) {
+ gPrintService = gPrintService.getService();
+ if (gPrintService) {
+ gPrintService = gPrintService.QueryInterface(Components.interfaces.nsIPrintSettingsService);
+ }
+ }
+ } catch (ex) {
+ dump("loadDialog: ex="+ex+"\n");
+ }
+
+ setPrinterDefaultsForSelectedPrinter();
+
+ gDialog.printBG.checked = gPrintSettings.printBGColors || gPrintSettings.printBGImages;
+
+ gDialog.shrinkToFit.checked = gPrintSettings.shrinkToFit;
+
+ gDialog.scalingLabel.disabled = gDialog.scalingInput.disabled = gDialog.shrinkToFit.checked;
+
+ var marginGroupLabel = gDialog.marginGroup.label;
+ if (gPrintSettings.paperSizeUnit == gPrintSettingsInterface.kPaperSizeInches) {
+ marginGroupLabel = marginGroupLabel.replace(/#1/, gDialog.strings["marginUnits.inches"]);
+ gDoingMetric = false;
+ } else {
+ marginGroupLabel = marginGroupLabel.replace(/#1/, gDialog.strings["marginUnits.metric"]);
+ // Also, set global page dimensions for A4 paper, in millimeters (assumes portrait at this point).
+ gPageWidth = 2100;
+ gPageHeight = 2970;
+ gDoingMetric = true;
+ }
+ gDialog.marginGroup.label = marginGroupLabel;
+
+ print_orientation = gPrintSettings.orientation;
+ print_margin_top = convertMarginInchesToUnits(gPrintSettings.marginTop, gDoingMetric);
+ print_margin_left = convertMarginInchesToUnits(gPrintSettings.marginLeft, gDoingMetric);
+ print_margin_right = convertMarginInchesToUnits(gPrintSettings.marginRight, gDoingMetric);
+ print_margin_bottom = convertMarginInchesToUnits(gPrintSettings.marginBottom, gDoingMetric);
+
+ if (gDoDebug) {
+ dump("print_orientation "+print_orientation+"\n");
+
+ dump("print_margin_top "+print_margin_top+"\n");
+ dump("print_margin_left "+print_margin_left+"\n");
+ dump("print_margin_right "+print_margin_right+"\n");
+ dump("print_margin_bottom "+print_margin_bottom+"\n");
+ }
+
+ if (print_orientation == gPrintSettingsInterface.kPortraitOrientation) {
+ gDialog.orientation.selectedItem = gDialog.portrait;
+ } else if (print_orientation == gPrintSettingsInterface.kLandscapeOrientation) {
+ gDialog.orientation.selectedItem = gDialog.landscape;
+ }
+
+ // Set orientation the first time on a timeout so the dialog sizes to the
+ // maximum height specified in the .xul file. Otherwise, if the user switches
+ // from landscape to portrait, the content grows and the buttons are clipped.
+ setTimeout( setOrientation, 0 );
+
+ gDialog.topInput.value = print_margin_top.toFixed(1);
+ gDialog.bottomInput.value = print_margin_bottom.toFixed(1);
+ gDialog.leftInput.value = print_margin_left.toFixed(1);
+ gDialog.rightInput.value = print_margin_right.toFixed(1);
+ changeMargins();
+
+ setHeaderFooter( gDialog.hLeftOption, gPrintSettings.headerStrLeft );
+ setHeaderFooter( gDialog.hCenterOption, gPrintSettings.headerStrCenter );
+ setHeaderFooter( gDialog.hRightOption, gPrintSettings.headerStrRight );
+
+ setHeaderFooter( gDialog.fLeftOption, gPrintSettings.footerStrLeft );
+ setHeaderFooter( gDialog.fCenterOption, gPrintSettings.footerStrCenter );
+ setHeaderFooter( gDialog.fRightOption, gPrintSettings.footerStrRight );
+
+ gDialog.scalingInput.value = (gPrintSettings.scaling * 100).toFixed(0);
+
+ // Enable/disable widgets based in the information whether the selected
+ // printer supports the matching feature or not
+ if (isListOfPrinterFeaturesAvailable()) {
+ if (gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".can_change_orientation"))
+ gDialog.orientation.removeAttribute("disabled");
+ else
+ gDialog.orientation.setAttribute("disabled", "true");
+ }
+
+ // Give initial focus to the orientation radio group.
+ // Done on a timeout due to to bug 103197.
+ setTimeout( function() { gDialog.orientation.focus(); }, 0 );
+}
+
+// ---------------------------------------------------
+function onLoad()
+{
+ // Init gDialog.
+ initDialog();
+
+ if (window.arguments[0] != null) {
+ gPrintSettings = window.arguments[0].QueryInterface(Components.interfaces.nsIPrintSettings);
+ paramBlock = window.arguments[1].QueryInterface(Components.interfaces.nsIDialogParamBlock);
+ } else if (gDoDebug) {
+ alert("window.arguments[0] == null!");
+ }
+
+ // default return value is "cancel"
+ paramBlock.SetInt(0, 0);
+
+ if (gPrintSettings) {
+ loadDialog();
+ } else if (gDoDebug) {
+ alert("Could initialize gDialog, PrintSettings is null!");
+ }
+}
+
+function convertUnitsMarginToInches(aVal, aIsMetric)
+{
+ if (aIsMetric) {
+ return aVal / 25.4;
+ }
+ return aVal;
+}
+
+function convertMarginInchesToUnits(aVal, aIsMetric)
+{
+ if (aIsMetric) {
+ return aVal * 25.4;
+ }
+ return aVal;
+}
+
+// ---------------------------------------------------
+function onAccept()
+{
+
+ if (gPrintSettings) {
+ if ( gDialog.orientation.selectedItem == gDialog.portrait ) {
+ gPrintSettings.orientation = gPrintSettingsInterface.kPortraitOrientation;
+ } else {
+ gPrintSettings.orientation = gPrintSettingsInterface.kLandscapeOrientation;
+ }
+
+ // save these out so they can be picked up by the device spec
+ gPrintSettings.marginTop = convertUnitsMarginToInches(gDialog.topInput.value, gDoingMetric);
+ gPrintSettings.marginLeft = convertUnitsMarginToInches(gDialog.leftInput.value, gDoingMetric);
+ gPrintSettings.marginBottom = convertUnitsMarginToInches(gDialog.bottomInput.value, gDoingMetric);
+ gPrintSettings.marginRight = convertUnitsMarginToInches(gDialog.rightInput.value, gDoingMetric);
+
+ gPrintSettings.headerStrLeft = hfIdToValue(gDialog.hLeftOption);
+ gPrintSettings.headerStrCenter = hfIdToValue(gDialog.hCenterOption);
+ gPrintSettings.headerStrRight = hfIdToValue(gDialog.hRightOption);
+
+ gPrintSettings.footerStrLeft = hfIdToValue(gDialog.fLeftOption);
+ gPrintSettings.footerStrCenter = hfIdToValue(gDialog.fCenterOption);
+ gPrintSettings.footerStrRight = hfIdToValue(gDialog.fRightOption);
+
+ gPrintSettings.printBGColors = gDialog.printBG.checked;
+ gPrintSettings.printBGImages = gDialog.printBG.checked;
+
+ gPrintSettings.shrinkToFit = gDialog.shrinkToFit.checked;
+
+ var scaling = document.getElementById("scalingInput").value;
+ if (scaling < 10.0) {
+ scaling = 10.0;
+ }
+ if (scaling > 500.0) {
+ scaling = 500.0;
+ }
+ scaling /= 100.0;
+ gPrintSettings.scaling = scaling;
+
+ if (gDoDebug) {
+ dump("******* Page Setup Accepting ******\n");
+ dump("print_margin_top "+gDialog.topInput.value+"\n");
+ dump("print_margin_left "+gDialog.leftInput.value+"\n");
+ dump("print_margin_right "+gDialog.bottomInput.value+"\n");
+ dump("print_margin_bottom "+gDialog.rightInput.value+"\n");
+ }
+ }
+
+ // set return value to "ok"
+ if (paramBlock) {
+ paramBlock.SetInt(0, 1);
+ } else {
+ dump("*** FATAL ERROR: No paramBlock\n");
+ }
+
+ var flags = gPrintSettingsInterface.kInitSaveMargins |
+ gPrintSettingsInterface.kInitSaveHeaderLeft |
+ gPrintSettingsInterface.kInitSaveHeaderCenter |
+ gPrintSettingsInterface.kInitSaveHeaderRight |
+ gPrintSettingsInterface.kInitSaveFooterLeft |
+ gPrintSettingsInterface.kInitSaveFooterCenter |
+ gPrintSettingsInterface.kInitSaveFooterRight |
+ gPrintSettingsInterface.kInitSaveBGColors |
+ gPrintSettingsInterface.kInitSaveBGImages |
+ gPrintSettingsInterface.kInitSaveInColor |
+ gPrintSettingsInterface.kInitSaveReversed |
+ gPrintSettingsInterface.kInitSaveOrientation |
+ gPrintSettingsInterface.kInitSaveOddEvenPages |
+ gPrintSettingsInterface.kInitSaveShrinkToFit |
+ gPrintSettingsInterface.kInitSaveScaling;
+
+ gPrintService.savePrintSettingsToPrefs(gPrintSettings, true, flags);
+
+ return true;
+}
+
+// ---------------------------------------------------
+function onCancel()
+{
+ // set return value to "cancel"
+ if (paramBlock) {
+ paramBlock.SetInt(0, 0);
+ } else {
+ dump("*** FATAL ERROR: No paramBlock\n");
+ }
+
+ return true;
+}
+
diff --git a/components/printing/content/printPageSetup.xul b/components/printing/content/printPageSetup.xul
new file mode 100644
index 000000000..a0c3afe17
--- /dev/null
+++ b/components/printing/content/printPageSetup.xul
@@ -0,0 +1,234 @@
+<?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://global/skin/printPageSetup.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://global/locale/printPageSetup.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="printPageSetupDialog"
+ onload="onLoad();"
+ ondialogaccept="return onAccept();"
+ oncancel="return onCancel();"
+ title="&printSetup.title;"
+ persist="screenX screenY"
+ screenX="24" screenY="24">
+
+ <script type="application/javascript" src="chrome://global/content/printPageSetup.js"/>
+
+ <!-- Localizable strings manipulated at run-time. -->
+ <data id="marginUnits.inches">&marginUnits.inches;</data>
+ <data id="marginUnits.metric">&marginUnits.metric;</data>
+ <data id="customPrompt.title">&customPrompt.title;</data>
+ <data id="customPrompt.prompt">&customPrompt.prompt;</data>
+
+ <tabbox flex="1">
+ <tabs>
+ <tab label="&basic.tab;"/>
+ <tab label="&advanced.tab;"/>
+ </tabs>
+ <tabpanels flex="1">
+ <vbox>
+ <groupbox>
+ <caption label="&formatGroup.label;"/>
+ <vbox>
+ <hbox align="center">
+ <label control="orientation" value="&orientation.label;"/>
+ <radiogroup id="orientation" oncommand="setOrientation()">
+ <hbox align="center">
+ <radio id="portrait"
+ class="portrait-page"
+ label="&portrait.label;"
+ accesskey="&portrait.accesskey;"/>
+ <radio id="landscape"
+ class="landscape-page"
+ label="&landscape.label;"
+ accesskey="&landscape.accesskey;"/>
+ </hbox>
+ </radiogroup>
+ </hbox>
+ <separator/>
+ <hbox align="center">
+ <label control="scalingInput"
+ value="&scale.label;"
+ accesskey="&scale.accesskey;"/>
+ <textbox id="scalingInput" size="4" oninput="checkDouble(this)"/>
+ <label value="&scalePercent;"/>
+ <separator/>
+ <checkbox id="shrinkToFit"
+ label="&shrinkToFit.label;"
+ accesskey="&shrinkToFit.accesskey;"
+ oncommand="gDialog.scalingInput.disabled=gDialog.scalingLabel.disabled=this.checked"/>
+ </hbox>
+ </vbox>
+ </groupbox>
+ <groupbox>
+ <caption label="&optionsGroup.label;"/>
+ <checkbox id="printBG"
+ label="&printBG.label;"
+ accesskey="&printBG.accesskey;"/>
+ </groupbox>
+ </vbox>
+ <vbox>
+ <groupbox>
+ <caption id="marginGroup" label="&marginGroup.label;"/>
+ <vbox>
+ <hbox align="center">
+ <spacer flex="1"/>
+ <label control="topInput"
+ value="&marginTop.label;"
+ accesskey="&marginTop.accesskey;"/>
+ <textbox id="topInput" size="5" oninput="changeMargin(this)"/>
+ <!-- This invisible label (with same content as the visible one!) is used
+ to ensure that the <textbox> is centered above the page. The same
+ technique is deployed for the bottom/left/right input fields, below. -->
+ <label value="&marginTop.label;" style="visibility: hidden;"/>
+ <spacer flex="1"/>
+ </hbox>
+ <hbox dir="ltr">
+ <spacer flex="1"/>
+ <vbox>
+ <spacer flex="1"/>
+ <label control="leftInput"
+ value="&marginLeft.label;"
+ accesskey="&marginLeft.accesskey;"/>
+ <textbox id="leftInput" size="5" oninput="changeMargin(this)"/>
+ <label value="&marginLeft.label;" style="visibility: hidden;"/>
+ <spacer flex="1"/>
+ </vbox>
+ <!-- The "margin page" draws a simulated printout page with dashed lines
+ for the margins. The height/width style attributes of the marginTop,
+ marginBottom, marginLeft, and marginRight elements are set by
+ the JS code dynamically based on the user input. -->
+ <vbox id="marginPage" style="height:29.7mm;">
+ <box id="marginTop" style="height:0.05in;"/>
+ <hbox flex="1" dir="ltr">
+ <box id="marginLeft" style="width:0.025in;"/>
+ <box style="border: 1px; border-style: dashed; border-color: gray;" flex="1"/>
+ <box id="marginRight" style="width:0.025in;"/>
+ </hbox>
+ <box id="marginBottom" style="height:0.05in;"/>
+ </vbox>
+ <vbox>
+ <spacer flex="1"/>
+ <label control="rightInput"
+ value="&marginRight.label;"
+ accesskey="&marginRight.accesskey;"/>
+ <textbox id="rightInput" size="5" oninput="changeMargin(this)"/>
+ <label value="&marginRight.label;" style="visibility: hidden;"/>
+ <spacer flex="1"/>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ <hbox align="center">
+ <spacer flex="1"/>
+ <label control="bottomInput"
+ value="&marginBottom.label;"
+ accesskey="&marginBottom.accesskey;"/>
+ <textbox id="bottomInput" size="5" oninput="changeMargin(this)"/>
+ <label value="&marginBottom.label;" style="visibility: hidden;"/>
+ <spacer flex="1"/>
+ </hbox>
+ </vbox>
+ </groupbox>
+ <groupbox>
+ <caption id="headersAndFooters" label="&headerFooter.label;"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row dir="ltr">
+ <menulist id="hLeftOption" oncommand="customize(this)" tooltiptext="&headerLeft.tip;">
+ <menupopup>
+ <menuitem value="0" label="&hfBlank;"/>
+ <menuitem value="1" label="&hfTitle;"/>
+ <menuitem value="2" label="&hfURL;"/>
+ <menuitem value="3" label="&hfDateAndTime;"/>
+ <menuitem value="4" label="&hfPage;"/>
+ <menuitem value="5" label="&hfPageAndTotal;"/>
+ <menuitem value="6" label="&hfCustom;"/>
+ </menupopup>
+ </menulist>
+ <menulist id="hCenterOption" oncommand="customize(this)" tooltiptext="&headerCenter.tip;">
+ <menupopup>
+ <menuitem value="0" label="&hfBlank;"/>
+ <menuitem value="1" label="&hfTitle;"/>
+ <menuitem value="2" label="&hfURL;"/>
+ <menuitem value="3" label="&hfDateAndTime;"/>
+ <menuitem value="4" label="&hfPage;"/>
+ <menuitem value="5" label="&hfPageAndTotal;"/>
+ <menuitem value="6" label="&hfCustom;"/>
+ </menupopup>
+ </menulist>
+ <menulist id="hRightOption" oncommand="customize(this)" tooltiptext="&headerRight.tip;">
+ <menupopup>
+ <menuitem value="0" label="&hfBlank;"/>
+ <menuitem value="1" label="&hfTitle;"/>
+ <menuitem value="2" label="&hfURL;"/>
+ <menuitem value="3" label="&hfDateAndTime;"/>
+ <menuitem value="4" label="&hfPage;"/>
+ <menuitem value="5" label="&hfPageAndTotal;"/>
+ <menuitem value="6" label="&hfCustom;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row dir="ltr">
+ <vbox align="center">
+ <label value="&hfLeft.label;"/>
+ </vbox>
+ <vbox align="center">
+ <label value="&hfCenter.label;"/>
+ </vbox>
+ <vbox align="center">
+ <label value="&hfRight.label;"/>
+ </vbox>
+ </row>
+ <row dir="ltr">
+ <menulist id="fLeftOption" oncommand="customize(this)" tooltiptext="&footerLeft.tip;">
+ <menupopup>
+ <menuitem value="0" label="&hfBlank;"/>
+ <menuitem value="1" label="&hfTitle;"/>
+ <menuitem value="2" label="&hfURL;"/>
+ <menuitem value="3" label="&hfDateAndTime;"/>
+ <menuitem value="4" label="&hfPage;"/>
+ <menuitem value="5" label="&hfPageAndTotal;"/>
+ <menuitem value="6" label="&hfCustom;"/>
+ </menupopup>
+ </menulist>
+ <menulist id="fCenterOption" oncommand="customize(this)" tooltiptext="&footerCenter.tip;">
+ <menupopup>
+ <menuitem value="0" label="&hfBlank;"/>
+ <menuitem value="1" label="&hfTitle;"/>
+ <menuitem value="2" label="&hfURL;"/>
+ <menuitem value="3" label="&hfDateAndTime;"/>
+ <menuitem value="4" label="&hfPage;"/>
+ <menuitem value="5" label="&hfPageAndTotal;"/>
+ <menuitem value="6" label="&hfCustom;"/>
+ </menupopup>
+ </menulist>
+ <menulist id="fRightOption" oncommand="customize(this)" tooltiptext="&footerRight.tip;">
+ <menupopup>
+ <menuitem value="0" label="&hfBlank;"/>
+ <menuitem value="1" label="&hfTitle;"/>
+ <menuitem value="2" label="&hfURL;"/>
+ <menuitem value="3" label="&hfDateAndTime;"/>
+ <menuitem value="4" label="&hfPage;"/>
+ <menuitem value="5" label="&hfPageAndTotal;"/>
+ <menuitem value="6" label="&hfCustom;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </vbox>
+ </tabpanels>
+ </tabbox>
+</dialog>
+
diff --git a/components/printing/content/printPreviewBindings.xml b/components/printing/content/printPreviewBindings.xml
new file mode 100644
index 000000000..c33b22e36
--- /dev/null
+++ b/components/printing/content/printPreviewBindings.xml
@@ -0,0 +1,419 @@
+<?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/. -->
+
+<!-- this file depends on printUtils.js -->
+
+<!DOCTYPE bindings [
+<!ENTITY % printPreviewDTD SYSTEM "chrome://global/locale/printPreview.dtd" >
+%printPreviewDTD;
+]>
+
+<bindings id="printPreviewBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="printpreviewtoolbar"
+ extends="chrome://global/content/bindings/toolbar.xml#toolbar">
+ <resources>
+ <stylesheet src="chrome://global/skin/printPreview.css"/>
+ </resources>
+
+ <content>
+ <xul:button label="&print.label;" accesskey="&print.accesskey;"
+ oncommand="this.parentNode.print();" icon="print"/>
+
+ <xul:button label="&pageSetup.label;" accesskey="&pageSetup.accesskey;"
+ oncommand="this.parentNode.doPageSetup();"/>
+
+ <xul:vbox align="center" pack="center">
+ <xul:label value="&page.label;" accesskey="&page.accesskey;" control="pageNumber"/>
+ </xul:vbox>
+ <xul:toolbarbutton anonid="navigateHome" class="navigate-button tabbable"
+ oncommand="parentNode.navigate(0, 0, 'home');" tooltiptext="&homearrow.tooltip;"/>
+ <xul:toolbarbutton anonid="navigatePrevious" class="navigate-button tabbable"
+ oncommand="parentNode.navigate(-1, 0, 0);" tooltiptext="&previousarrow.tooltip;"/>
+ <xul:hbox align="center" pack="center">
+ <xul:textbox id="pageNumber" size="3" value="1" min="1" type="number"
+ hidespinbuttons="true" onchange="navigate(0, this.valueNumber, 0);"/>
+ <xul:label value="&of.label;"/>
+ <xul:label value="1"/>
+ </xul:hbox>
+ <xul:toolbarbutton anonid="navigateNext" class="navigate-button tabbable"
+ oncommand="parentNode.navigate(1, 0, 0);" tooltiptext="&nextarrow.tooltip;"/>
+ <xul:toolbarbutton anonid="navigateEnd" class="navigate-button tabbable"
+ oncommand="parentNode.navigate(0, 0, 'end');" tooltiptext="&endarrow.tooltip;"/>
+
+ <xul:toolbarseparator class="toolbarseparator-primary"/>
+ <xul:vbox align="center" pack="center">
+ <xul:label value="&scale.label;" accesskey="&scale.accesskey;" control="scale"/>
+ </xul:vbox>
+
+ <xul:hbox align="center" pack="center">
+ <xul:menulist id="scale" crop="none"
+ oncommand="parentNode.parentNode.scale(this.selectedItem.value);">
+ <xul:menupopup>
+ <xul:menuitem value="0.3" label="&p30.label;"/>
+ <xul:menuitem value="0.4" label="&p40.label;"/>
+ <xul:menuitem value="0.5" label="&p50.label;"/>
+ <xul:menuitem value="0.6" label="&p60.label;"/>
+ <xul:menuitem value="0.7" label="&p70.label;"/>
+ <xul:menuitem value="0.8" label="&p80.label;"/>
+ <xul:menuitem value="0.9" label="&p90.label;"/>
+ <xul:menuitem value="1" label="&p100.label;"/>
+ <xul:menuitem value="1.25" label="&p125.label;"/>
+ <xul:menuitem value="1.5" label="&p150.label;"/>
+ <xul:menuitem value="1.75" label="&p175.label;"/>
+ <xul:menuitem value="2" label="&p200.label;"/>
+ <xul:menuseparator/>
+ <xul:menuitem flex="1" value="ShrinkToFit"
+ label="&ShrinkToFit.label;"/>
+ <xul:menuitem value="Custom" label="&Custom.label;"/>
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:hbox>
+
+ <xul:toolbarseparator class="toolbarseparator-primary"/>
+ <xul:hbox align="center" pack="center">
+ <xul:toolbarbutton label="&portrait.label;" checked="true"
+ accesskey="&portrait.accesskey;"
+ type="radio" group="orient" class="toolbar-portrait-page tabbable"
+ oncommand="parentNode.parentNode.orient('portrait');"/>
+ <xul:toolbarbutton label="&landscape.label;"
+ accesskey="&landscape.accesskey;"
+ type="radio" group="orient" class="toolbar-landscape-page tabbable"
+ oncommand="parentNode.parentNode.orient('landscape');"/>
+ </xul:hbox>
+
+ <xul:toolbarseparator class="toolbarseparator-primary"/>
+ <xul:checkbox label="&simplifyPage.label;" checked="false" disabled="true"
+ accesskey="&simplifyPage.accesskey;"
+ tooltiptext-disabled="&simplifyPage.disabled.tooltip;"
+ tooltiptext-enabled="&simplifyPage.enabled.tooltip;"
+ oncommand="this.parentNode.simplify();"/>
+
+ <xul:toolbarseparator class="toolbarseparator-primary"/>
+ <xul:button label="&close.label;" accesskey="&close.accesskey;"
+ oncommand="PrintUtils.exitPrintPreview();" icon="close"/>
+ <xul:data value="&customPrompt.title;"/>
+ </content>
+
+ <implementation implements="nsIMessageListener">
+ <field name="mPrintButton">
+ document.getAnonymousNodes(this)[0]
+ </field>
+ <field name="mPageTextBox">
+ document.getAnonymousNodes(this)[5].childNodes[0]
+ </field>
+ <field name="mTotalPages">
+ document.getAnonymousNodes(this)[5].childNodes[2]
+ </field>
+ <field name="mScaleLabel">
+ document.getAnonymousNodes(this)[9].firstChild
+ </field>
+ <field name="mScaleCombobox">
+ document.getAnonymousNodes(this)[10].firstChild
+ </field>
+ <field name="mOrientButtonsBox">
+ document.getAnonymousNodes(this)[12]
+ </field>
+ <field name="mPortaitButton">
+ this.mOrientButtonsBox.childNodes[0]
+ </field>
+ <field name="mLandscapeButton">
+ this.mOrientButtonsBox.childNodes[1]
+ </field>
+ <field name="mSimplifyPageCheckbox">
+ document.getAnonymousNodes(this)[14]
+ </field>
+ <field name="mSimplifyPageToolbarSeparator">
+ document.getAnonymousNodes(this)[15]
+ </field>
+ <field name="mCustomTitle">
+ document.getAnonymousNodes(this)[17].firstChild
+ </field>
+ <field name="mPrintPreviewObs">
+ </field>
+ <field name="mWebProgress">
+ </field>
+ <field name="mPPBrowser">
+ null
+ </field>
+ <field name="mMessageManager">
+ null
+ </field>
+
+ <method name="initialize">
+ <parameter name="aPPBrowser"/>
+ <body>
+ <![CDATA[
+ let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
+ if (!Services.prefs.getBoolPref("print.use_simplify_page")) {
+ this.mSimplifyPageCheckbox.hidden = true;
+ this.mSimplifyPageToolbarSeparator.hidden = true;
+ }
+ this.mPPBrowser = aPPBrowser;
+ this.mMessageManager = aPPBrowser.messageManager;
+ this.mMessageManager.addMessageListener("Printing:Preview:UpdatePageCount", this);
+ this.updateToolbar();
+
+ let $ = id => document.getAnonymousElementByAttribute(this, "anonid", id);
+ let ltr = document.documentElement.matches(":root:-moz-locale-dir(ltr)");
+#ifdef XP_WIN
+ // Windows 7 doesn't support â® and â­ by default, and fallback doesn't
+ // always work (bug 1343330).
+ let useCompatCharacters = Services.vc.compare(Services.sysinfo.getProperty("version"), "6.1") <= 0;
+#else
+ let useCompatCharacters = false;
+#endif
+
+ let leftEnd = useCompatCharacters ? "âª" : "â®";
+ let rightEnd = useCompatCharacters ? "â©" : "â­";
+ $("navigateHome").label = ltr ? leftEnd : rightEnd;
+ $("navigatePrevious").label = ltr ? "â—‚" : "â–¸";
+ $("navigateNext").label = ltr ? "â–¸" : "â—‚";
+ $("navigateEnd").label = ltr ? rightEnd : leftEnd;
+ ]]>
+ </body>
+ </method>
+
+ <method name="doPageSetup">
+ <body>
+ <![CDATA[
+ var didOK = PrintUtils.showPageSetup();
+ if (didOK) {
+ // the changes that effect the UI
+ this.updateToolbar();
+
+ // Now do PrintPreview
+ PrintUtils.printPreview();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="navigate">
+ <parameter name="aDirection"/>
+ <parameter name="aPageNum"/>
+ <parameter name="aHomeOrEnd"/>
+ <body>
+ <![CDATA[
+ const nsIWebBrowserPrint = Components.interfaces.nsIWebBrowserPrint;
+ let navType, pageNum;
+
+ // we use only one of aHomeOrEnd, aDirection, or aPageNum
+ if (aHomeOrEnd) {
+ // We're going to either the very first page ("home"), or the
+ // very last page ("end").
+ if (aHomeOrEnd == "home") {
+ navType = nsIWebBrowserPrint.PRINTPREVIEW_HOME;
+ this.mPageTextBox.value = 1;
+ } else {
+ navType = nsIWebBrowserPrint.PRINTPREVIEW_END;
+ this.mPageTextBox.value = this.mPageTextBox.max;
+ }
+ pageNum = 0;
+ } else if (aDirection) {
+ // aDirection is either +1 or -1, and allows us to increment
+ // or decrement our currently viewed page.
+ this.mPageTextBox.valueNumber += aDirection;
+ navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
+ pageNum = this.mPageTextBox.value; // TODO: back to valueNumber?
+ } else {
+ // We're going to a specific page (aPageNum)
+ navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
+ pageNum = aPageNum;
+ }
+
+ this.mMessageManager.sendAsyncMessage("Printing:Preview:Navigate", {
+ navType: navType,
+ pageNum: pageNum,
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="print">
+ <body>
+ <![CDATA[
+ PrintUtils.printWindow(this.mPPBrowser.outerWindowID, this.mPPBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <method name="promptForScaleValue">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var value = Math.round(aValue);
+ var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
+ var promptStr = this.mScaleLabel.value;
+ var renameTitle = this.mCustomTitle;
+ var result = {value:value};
+ var confirmed = promptService.prompt(window, renameTitle, promptStr, result, null, {value:value});
+ if (!confirmed || (!result.value) || (result.value == "") || result.value == value) {
+ return -1;
+ }
+ return result.value;
+ ]]>
+ </body>
+ </method>
+
+ <method name="setScaleCombobox">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var scaleValues = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.25, 1.5, 1.75, 2];
+
+ aValue = new Number(aValue);
+
+ for (var i = 0; i < scaleValues.length; i++) {
+ if (aValue == scaleValues[i]) {
+ this.mScaleCombobox.selectedIndex = i;
+ return;
+ }
+ }
+ this.mScaleCombobox.value = "Custom";
+ ]]>
+ </body>
+ </method>
+
+ <method name="scale">
+ <parameter name="aValue"/>
+ <body>
+ <![CDATA[
+ var settings = PrintUtils.getPrintSettings();
+ if (aValue == "ShrinkToFit") {
+ if (!settings.shrinkToFit) {
+ settings.shrinkToFit = true;
+ this.savePrintSettings(settings, settings.kInitSaveShrinkToFit | settings.kInitSaveScaling);
+ PrintUtils.printPreview();
+ }
+ return;
+ }
+
+ if (aValue == "Custom") {
+ aValue = this.promptForScaleValue(settings.scaling * 100.0);
+ if (aValue >= 10) {
+ aValue /= 100.0;
+ } else {
+ if (this.mScaleCombobox.hasAttribute('lastValidInx')) {
+ this.mScaleCombobox.selectedIndex = this.mScaleCombobox.getAttribute('lastValidInx');
+ }
+ return;
+ }
+ }
+
+ this.setScaleCombobox(aValue);
+ this.mScaleCombobox.setAttribute('lastValidInx', this.mScaleCombobox.selectedIndex);
+
+ if (settings.scaling != aValue || settings.shrinkToFit)
+ {
+ settings.shrinkToFit = false;
+ settings.scaling = aValue;
+ this.savePrintSettings(settings, settings.kInitSaveShrinkToFit | settings.kInitSaveScaling);
+ PrintUtils.printPreview();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="orient">
+ <parameter name="aOrientation"/>
+ <body>
+ <![CDATA[
+ const kIPrintSettings = Components.interfaces.nsIPrintSettings;
+ var orientValue = (aOrientation == "portrait") ? kIPrintSettings.kPortraitOrientation :
+ kIPrintSettings.kLandscapeOrientation;
+ var settings = PrintUtils.getPrintSettings();
+ if (settings.orientation != orientValue)
+ {
+ settings.orientation = orientValue;
+ this.savePrintSettings(settings, settings.kInitSaveOrientation);
+ PrintUtils.printPreview();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="simplify">
+ <body>
+ <![CDATA[
+ PrintUtils.setSimplifiedMode(this.mSimplifyPageCheckbox.checked);
+ PrintUtils.printPreview();
+ ]]>
+ </body>
+ </method>
+
+ <method name="enableSimplifyPage">
+ <body>
+ <![CDATA[
+ this.mSimplifyPageCheckbox.disabled = false;
+ this.mSimplifyPageCheckbox.setAttribute("tooltiptext",
+ this.mSimplifyPageCheckbox.getAttribute("tooltiptext-enabled"));
+ ]]>
+ </body>
+ </method>
+
+ <method name="disableSimplifyPage">
+ <body>
+ <![CDATA[
+ this.mSimplifyPageCheckbox.disabled = true;
+ this.mSimplifyPageCheckbox.setAttribute("tooltiptext",
+ this.mSimplifyPageCheckbox.getAttribute("tooltiptext-disabled"));
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateToolbar">
+ <body>
+ <![CDATA[
+ var settings = PrintUtils.getPrintSettings();
+
+ var isPortrait = settings.orientation == Components.interfaces.nsIPrintSettings.kPortraitOrientation;
+
+ this.mPortaitButton.checked = isPortrait;
+ this.mLandscapeButton.checked = !isPortrait;
+
+ if (settings.shrinkToFit) {
+ this.mScaleCombobox.value = "ShrinkToFit";
+ } else {
+ this.setScaleCombobox(settings.scaling);
+ }
+
+ this.mPageTextBox.value = 1;
+
+ this.mMessageManager.sendAsyncMessage("Printing:Preview:UpdatePageCount");
+ ]]>
+ </body>
+ </method>
+
+ <method name="savePrintSettings">
+ <parameter name="settings"/>
+ <parameter name="flags"/>
+ <body><![CDATA[
+ var PSSVC = Components.classes["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Components.interfaces.nsIPrintSettingsService);
+ PSSVC.savePrintSettingsToPrefs(settings, true, flags);
+ ]]></body>
+ </method>
+
+ <!-- nsIMessageListener -->
+ <method name="receiveMessage">
+ <parameter name="message"/>
+ <body>
+ <![CDATA[
+ if (message.name == "Printing:Preview:UpdatePageCount") {
+ let numPages = message.data.numPages;
+ this.mTotalPages.value = numPages;
+ this.mPageTextBox.max = numPages;
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/components/printing/content/printPreviewProgress.js b/components/printing/content/printPreviewProgress.js
new file mode 100644
index 000000000..5c769e50a
--- /dev/null
+++ b/components/printing/content/printPreviewProgress.js
@@ -0,0 +1,154 @@
+// -*- 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/. */
+
+// dialog is just an array we'll use to store various properties from the dialog document...
+var dialog;
+
+// the printProgress is a nsIPrintProgress object
+var printProgress = null;
+
+// random global variables...
+var targetFile;
+
+var docTitle = "";
+var docURL = "";
+var progressParams = null;
+
+function ellipseString(aStr, doFront)
+{
+ if (aStr.length > 3 && (aStr.substr(0, 3) == "..." || aStr.substr(aStr.length-4, 3) == "..."))
+ return aStr;
+
+ var fixedLen = 64;
+ if (aStr.length <= fixedLen)
+ return aStr;
+
+ if (doFront)
+ return "..." + aStr.substr(aStr.length-fixedLen, fixedLen);
+
+ return aStr.substr(0, fixedLen) + "...";
+}
+
+// all progress notifications are done through the nsIWebProgressListener implementation...
+var progressListener = {
+
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
+ window.close();
+ },
+
+ onProgressChange: function (aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
+ {
+ if (!progressParams)
+ return;
+ var docTitleStr = ellipseString(progressParams.docTitle, false);
+ if (docTitleStr != docTitle) {
+ docTitle = docTitleStr;
+ dialog.title.value = docTitle;
+ }
+ var docURLStr = ellipseString(progressParams.docURL, true);
+ if (docURLStr != docURL && dialog.title != null) {
+ docURL = docURLStr;
+ if (docTitle == "")
+ dialog.title.value = docURLStr;
+ }
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocation, aFlags) {},
+ onSecurityChange: function (aWebProgress, aRequest, state) {},
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage)
+ {
+ if (aMessage)
+ dialog.title.setAttribute("value", aMessage);
+ },
+
+ QueryInterface: function (iid)
+ {
+ if (iid.equals(Components.interfaces.nsIWebProgressListener) || iid.equals(Components.interfaces.nsISupportsWeakReference))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+}
+
+function onLoad() {
+ // Set global variables.
+ printProgress = window.arguments[0];
+ if (window.arguments[1]) {
+ progressParams = window.arguments[1].QueryInterface(Components.interfaces.nsIPrintProgressParams)
+ if (progressParams) {
+ docTitle = ellipseString(progressParams.docTitle, false);
+ docURL = ellipseString(progressParams.docURL, true);
+ }
+ }
+
+ if (!printProgress) {
+ dump( "Invalid argument to printPreviewProgress.xul\n" );
+ window.close()
+ return;
+ }
+
+ dialog = {};
+ dialog.strings = new Array;
+ dialog.title = document.getElementById("dialog.title");
+ dialog.titleLabel = document.getElementById("dialog.titleLabel");
+
+ dialog.title.value = docTitle;
+
+ // set our web progress listener on the helper app launcher
+ printProgress.registerListener(progressListener);
+
+ // We need to delay the set title else dom will overwrite it
+ window.setTimeout(doneIniting, 100);
+}
+
+function onUnload()
+{
+ if (!printProgress)
+ return;
+ try {
+ printProgress.unregisterListener(progressListener);
+ printProgress = null;
+ }
+ catch (e) {}
+}
+
+function getString (stringId) {
+ // Check if we've fetched this string already.
+ if (!(stringId in dialog.strings)) {
+ // Try to get it.
+ var elem = document.getElementById( "dialog.strings."+stringId);
+ try {
+ if (elem && elem.childNodes && elem.childNodes[0] &&
+ elem.childNodes[0].nodeValue)
+ dialog.strings[stringId] = elem.childNodes[0].nodeValue;
+ // If unable to fetch string, use an empty string.
+ else
+ dialog.strings[stringId] = "";
+ } catch (e) { dialog.strings[stringId] = ""; }
+ }
+ return dialog.strings[stringId];
+}
+
+// If the user presses cancel, tell the app launcher and close the dialog...
+function onCancel ()
+{
+ // Cancel app launcher.
+ try {
+ printProgress.processCanceledByUser = true;
+ }
+ catch (e) { return true; }
+
+ // don't Close up dialog by returning false, the backend will close the dialog when everything will be aborted.
+ return false;
+}
+
+function doneIniting()
+{
+ // called by function timeout in onLoad
+ printProgress.doneIniting();
+}
diff --git a/components/printing/content/printPreviewProgress.xul b/components/printing/content/printPreviewProgress.xul
new file mode 100644
index 000000000..fa2b9b61d
--- /dev/null
+++ b/components/printing/content/printPreviewProgress.xul
@@ -0,0 +1,42 @@
+<?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 window SYSTEM "chrome://global/locale/printPreviewProgress.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&printWindow.title;"
+ style="width: 36em;"
+ buttons="cancel"
+ oncancel="onCancel()"
+ onload="onLoad()"
+ onunload="onUnload()">
+
+ <script type="application/javascript" src="chrome://global/content/printPreviewProgress.js"/>
+
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows>
+ <row>
+ <hbox pack="end">
+ <label id="dialog.titleLabel" value="&title;"/>
+ </hbox>
+ <label id="dialog.title"/>
+ </row>
+ <row class="thin-separator">
+ <hbox pack="end">
+ <label id="dialog.progressSpaces" value="&progress;"/>
+ </hbox>
+ <label id="dialog.progressLabel" value="&preparing;"/>
+ </row>
+ </rows>
+ </grid>
+</dialog>
diff --git a/components/printing/content/printProgress.js b/components/printing/content/printProgress.js
new file mode 100644
index 000000000..6cadfe45e
--- /dev/null
+++ b/components/printing/content/printProgress.js
@@ -0,0 +1,282 @@
+// -*- tab-width: 2; 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/. */
+
+// dialog is just an array we'll use to store various properties from the dialog document...
+var dialog;
+
+// the printProgress is a nsIPrintProgress object
+var printProgress = null;
+
+// random global variables...
+var targetFile;
+
+var docTitle = "";
+var docURL = "";
+var progressParams = null;
+var switchUI = true;
+
+function ellipseString(aStr, doFront)
+{
+ if (aStr.length > 3 && (aStr.substr(0, 3) == "..." || aStr.substr(aStr.length-4, 3) == "...")) {
+ return aStr;
+ }
+
+ var fixedLen = 64;
+ if (aStr.length > fixedLen) {
+ if (doFront) {
+ var endStr = aStr.substr(aStr.length-fixedLen, fixedLen);
+ return "..." + endStr;
+ }
+ var frontStr = aStr.substr(0, fixedLen);
+ return frontStr + "...";
+ }
+ return aStr;
+}
+
+// all progress notifications are done through the nsIWebProgressListener implementation...
+var progressListener = {
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_START)
+ {
+ // Put progress meter in undetermined mode.
+ // dialog.progress.setAttribute( "value", 0 );
+ dialog.progress.setAttribute( "mode", "undetermined" );
+ }
+
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
+ {
+ // we are done printing
+ // Indicate completion in title area.
+ var msg = getString( "printComplete" );
+ dialog.title.setAttribute("value", msg);
+
+ // Put progress meter at 100%.
+ dialog.progress.setAttribute( "value", 100 );
+ dialog.progress.setAttribute( "mode", "normal" );
+ var percentPrint = getString( "progressText" );
+ percentPrint = replaceInsert( percentPrint, 1, 100 );
+ dialog.progressText.setAttribute("value", percentPrint);
+
+ var fm = Components.classes["@mozilla.org/focus-manager;1"]
+ .getService(Components.interfaces.nsIFocusManager);
+ if (fm && fm.activeWindow == window) {
+ // This progress dialog is the currently active window. In
+ // this case we need to make sure that some other window
+ // gets focus before we close this dialog to work around the
+ // buggy Windows XP Fax dialog, which ends up parenting
+ // itself to the currently focused window and is unable to
+ // survive that window going away. What happens without this
+ // opener.focus() call on Windows XP is that the fax dialog
+ // is opened only to go away when this dialog actually
+ // closes (which can happen asynchronously, so the fax
+ // dialog just flashes once and then goes away), so w/o this
+ // fix, it's impossible to fax on Windows XP w/o manually
+ // switching focus to another window (or holding on to the
+ // progress dialog with the mouse long enough).
+ opener.focus();
+ }
+
+ window.close();
+ }
+ },
+
+ onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
+ {
+ if (switchUI)
+ {
+ dialog.tempLabel.setAttribute("hidden", "true");
+ dialog.progress.setAttribute("hidden", "false");
+
+ var progressLabel = getString("progress");
+ if (progressLabel == "") {
+ progressLabel = "Progress:"; // better than nothing
+ }
+ switchUI = false;
+ }
+
+ if (progressParams)
+ {
+ var docTitleStr = ellipseString(progressParams.docTitle, false);
+ if (docTitleStr != docTitle) {
+ docTitle = docTitleStr;
+ dialog.title.value = docTitle;
+ }
+ var docURLStr = progressParams.docURL;
+ if (docURLStr != docURL && dialog.title != null) {
+ docURL = docURLStr;
+ if (docTitle == "") {
+ dialog.title.value = ellipseString(docURLStr, true);
+ }
+ }
+ }
+
+ // Calculate percentage.
+ var percent;
+ if ( aMaxTotalProgress > 0 )
+ {
+ percent = Math.round( (aCurTotalProgress*100)/aMaxTotalProgress );
+ if ( percent > 100 )
+ percent = 100;
+
+ dialog.progress.removeAttribute( "mode");
+
+ // Advance progress meter.
+ dialog.progress.setAttribute( "value", percent );
+
+ // Update percentage label on progress meter.
+ var percentPrint = getString( "progressText" );
+ percentPrint = replaceInsert( percentPrint, 1, percent );
+ dialog.progressText.setAttribute("value", percentPrint);
+ }
+ else
+ {
+ // Progress meter should be barber-pole in this case.
+ dialog.progress.setAttribute( "mode", "undetermined" );
+ // Update percentage label on progress meter.
+ dialog.progressText.setAttribute("value", "");
+ }
+ },
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ // we can ignore this notification
+ },
+
+ onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ if (aMessage != "")
+ dialog.title.setAttribute("value", aMessage);
+ },
+
+ onSecurityChange: function(aWebProgress, aRequest, state)
+ {
+ // we can ignore this notification
+ },
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Components.interfaces.nsIWebProgressListener) || iid.equals(Components.interfaces.nsISupportsWeakReference))
+ return this;
+
+ throw Components.results.NS_NOINTERFACE;
+ }
+};
+
+function getString( stringId ) {
+ // Check if we've fetched this string already.
+ if (!(stringId in dialog.strings)) {
+ // Try to get it.
+ var elem = document.getElementById( "dialog.strings."+stringId );
+ try {
+ if ( elem
+ &&
+ elem.childNodes
+ &&
+ elem.childNodes[0]
+ &&
+ elem.childNodes[0].nodeValue ) {
+ dialog.strings[stringId] = elem.childNodes[0].nodeValue;
+ } else {
+ // If unable to fetch string, use an empty string.
+ dialog.strings[stringId] = "";
+ }
+ } catch (e) { dialog.strings[stringId] = ""; }
+ }
+ return dialog.strings[stringId];
+}
+
+function loadDialog()
+{
+}
+
+function replaceInsert( text, index, value ) {
+ var result = text;
+ var regExp = new RegExp( "#"+index );
+ result = result.replace( regExp, value );
+ return result;
+}
+
+function onLoad() {
+
+ // Set global variables.
+ printProgress = window.arguments[0];
+ if (window.arguments[1])
+ {
+ progressParams = window.arguments[1].QueryInterface(Components.interfaces.nsIPrintProgressParams)
+ if (progressParams)
+ {
+ docTitle = ellipseString(progressParams.docTitle, false);
+ docURL = ellipseString(progressParams.docURL, true);
+ }
+ }
+
+ if ( !printProgress ) {
+ dump( "Invalid argument to printProgress.xul\n" );
+ window.close()
+ return;
+ }
+
+ dialog = {};
+ dialog.strings = new Array;
+ dialog.title = document.getElementById("dialog.title");
+ dialog.titleLabel = document.getElementById("dialog.titleLabel");
+ dialog.progress = document.getElementById("dialog.progress");
+ dialog.progressText = document.getElementById("dialog.progressText");
+ dialog.progressLabel = document.getElementById("dialog.progressLabel");
+ dialog.tempLabel = document.getElementById("dialog.tempLabel");
+
+ dialog.progress.setAttribute("hidden", "true");
+
+ var progressLabel = getString("preparing");
+ if (progressLabel == "") {
+ progressLabel = "Preparing..."; // better than nothing
+ }
+ dialog.tempLabel.value = progressLabel;
+
+ dialog.title.value = docTitle;
+
+ // Fill dialog.
+ loadDialog();
+
+ // set our web progress listener on the helper app launcher
+ printProgress.registerListener(progressListener);
+ // We need to delay the set title else dom will overwrite it
+ window.setTimeout(doneIniting, 500);
+}
+
+function onUnload()
+{
+ if (printProgress)
+ {
+ try
+ {
+ printProgress.unregisterListener(progressListener);
+ printProgress = null;
+ }
+
+ catch ( exception ) {}
+ }
+}
+
+// If the user presses cancel, tell the app launcher and close the dialog...
+function onCancel ()
+{
+ // Cancel app launcher.
+ try
+ {
+ printProgress.processCanceledByUser = true;
+ }
+ catch ( exception ) { return true; }
+
+ // don't Close up dialog by returning false, the backend will close the dialog when everything will be aborted.
+ return false;
+}
+
+function doneIniting()
+{
+ printProgress.doneIniting();
+}
diff --git a/components/printing/content/printProgress.xul b/components/printing/content/printProgress.xul
new file mode 100644
index 000000000..2d724e54f
--- /dev/null
+++ b/components/printing/content/printProgress.xul
@@ -0,0 +1,60 @@
+<?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 window SYSTEM "chrome://global/locale/printProgress.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons="cancel"
+ title="&printWindow.title;"
+ style="width: 36em;"
+ ondialogcancel="onCancel()"
+ onload="onLoad()"
+ onunload="onUnload()">
+
+ <script type="application/javascript" src="chrome://global/content/printProgress.js"/>
+
+ <!-- This is non-visible content that simply adds translatable string
+ into the document so that it is accessible to JS code.
+
+ XXX-TODO:
+ convert to use string bundles.
+ -->
+
+ <data id="dialog.strings.dialogCloseLabel">&dialogClose.label;</data>
+ <data id="dialog.strings.printComplete">&printComplete;</data>
+ <data id="dialog.strings.progressText">&percentPrint;</data>
+ <data id="dialog.strings.progressLabel">&progress;</data>
+ <data id="dialog.strings.preparing">&preparing;</data>
+
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows>
+ <row>
+ <hbox pack="end">
+ <label id="dialog.titleLabel" value="&title;"/>
+ </hbox>
+ <label id="dialog.title"/>
+ </row>
+ <row class="thin-separator">
+ <hbox pack="end">
+ <label id="dialog.progressLabel" control="dialog.progress" value="&progress;"/>
+ </hbox>
+ <label id="dialog.tempLabel" value="&preparing;"/>
+ <progressmeter id="dialog.progress" mode="normal" value="0"/>
+ <hbox pack="end" style="min-width: 2.5em;">
+ <label id="dialog.progressText"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+</dialog>
diff --git a/components/printing/content/printUtils.js b/components/printing/content/printUtils.js
new file mode 100644
index 000000000..62602396c
--- /dev/null
+++ b/components/printing/content/printUtils.js
@@ -0,0 +1,697 @@
+// -*- tab-width: 2; 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/. */
+
+/**
+ * PrintUtils is a utility for front-end code to trigger common print
+ * operations (printing, show print preview, show page settings).
+ *
+ * Unfortunately, likely due to inconsistencies in how different operating
+ * systems do printing natively, our XPCOM-level printing interfaces
+ * are a bit confusing and the method by which we do something basic
+ * like printing a page is quite circuitous.
+ *
+ * To compound that, we need to support remote browsers, and that means
+ * kicking off the print jobs in the content process. This means we send
+ * messages back and forth to that process. browser-content.js contains
+ * the object that listens and responds to the messages that PrintUtils
+ * sends.
+ *
+ * This also means that <xul:browser>'s that hope to use PrintUtils must have
+ * their type attribute set to either "content", "content-targetable", or
+ * "content-primary".
+ *
+ * PrintUtils sends messages at different points in its implementation, but
+ * their documentation is consolidated here for ease-of-access.
+ *
+ *
+ * Messages sent:
+ *
+ * Printing:Print
+ * Kick off a print job for a nsIDOMWindow, passing the outer window ID as
+ * windowID.
+ *
+ * Printing:Preview:Enter
+ * This message is sent to put content into print preview mode. We pass
+ * the content window of the browser we're showing the preview of, and
+ * the target of the message is the browser that we'll be showing the
+ * preview in.
+ *
+ * Printing:Preview:Exit
+ * This message is sent to take content out of print preview mode.
+ *
+ *
+ * Messages Received
+ *
+ * Printing:Preview:Entered
+ * This message is sent by the content process once it has completed
+ * putting the content into print preview mode. We must wait for that to
+ * to complete before switching the chrome UI to print preview mode,
+ * otherwise we have layout issues.
+ *
+ * Printing:Preview:StateChange, Printing:Preview:ProgressChange
+ * Due to a timing issue resulting in a main-process crash, we have to
+ * manually open the progress dialog for print preview. The progress
+ * dialog is opened here in PrintUtils, and then we listen for update
+ * messages from the child. Bug 1088061 has been filed to investigate
+ * other solutions.
+ *
+ */
+
+var gPrintSettingsAreGlobal = false;
+var gSavePrintSettings = false;
+var gFocusedElement = null;
+
+var PrintUtils = {
+ init() {
+ window.messageManager.addMessageListener("Printing:Error", this);
+ },
+
+ get bundle() {
+ let stringService = Components.classes["@mozilla.org/intl/stringbundle;1"]
+ .getService(Components.interfaces.nsIStringBundleService);
+ delete this.bundle;
+ return this.bundle = stringService.createBundle("chrome://global/locale/printing.properties");
+ },
+
+ /**
+ * Shows the page setup dialog, and saves any settings changed in
+ * that dialog if print.save_print_settings is set to true.
+ *
+ * @return true on success, false on failure
+ */
+ showPageSetup: function () {
+ try {
+ var printSettings = this.getPrintSettings();
+ var PRINTPROMPTSVC = Components.classes["@mozilla.org/embedcomp/printingprompt-service;1"]
+ .getService(Components.interfaces.nsIPrintingPromptService);
+ PRINTPROMPTSVC.showPageSetup(window, printSettings, null);
+ if (gSavePrintSettings) {
+ // Page Setup data is a "native" setting on the Mac
+ var PSSVC = Components.classes["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Components.interfaces.nsIPrintSettingsService);
+ PSSVC.savePrintSettingsToPrefs(printSettings, true, printSettings.kInitSaveNativeData);
+ }
+ } catch (e) {
+ dump("showPageSetup "+e+"\n");
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Starts the process of printing the contents of a window.
+ *
+ * @param aWindowID
+ * The outer window ID of the nsIDOMWindow to print.
+ * @param aBrowser
+ * The <xul:browser> that the nsIDOMWindow for aWindowID belongs to.
+ */
+ printWindow: function (aWindowID, aBrowser)
+ {
+ let mm = aBrowser.messageManager;
+ mm.sendAsyncMessage("Printing:Print", {
+ windowID: aWindowID,
+ simplifiedMode: this._shouldSimplify,
+ });
+ },
+
+ /**
+ * Deprecated.
+ *
+ * Starts the process of printing the contents of window.content.
+ *
+ */
+ print: function ()
+ {
+ if (gBrowser) {
+ return this.printWindow(gBrowser.selectedBrowser.outerWindowID,
+ gBrowser.selectedBrowser);
+ }
+
+ if (this.usingRemoteTabs) {
+ throw new Error("PrintUtils.print cannot be run in windows running with " +
+ "remote tabs. Use PrintUtils.printWindow instead.");
+ }
+
+ let domWindow = window.content;
+ let ifReq = domWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ let browser = ifReq.getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShell)
+ .chromeEventHandler;
+ if (!browser) {
+ throw new Error("PrintUtils.print could not resolve content window " +
+ "to a browser.");
+ }
+
+ let windowID = ifReq.getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .outerWindowID;
+
+ let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
+ let msg = "PrintUtils.print is now deprecated. Please use PrintUtils.printWindow.";
+ let url = "https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Printing";
+ Deprecated.warning(msg, url);
+
+ this.printWindow(windowID, browser);
+ return undefined;
+ },
+
+ /**
+ * Initializes print preview.
+ *
+ * @param aListenerObj
+ * An object that defines the following functions:
+ *
+ * getPrintPreviewBrowser:
+ * Returns the <xul:browser> to display the print preview in. This
+ * <xul:browser> must have its type attribute set to "content",
+ * "content-targetable", or "content-primary".
+ *
+ * getSourceBrowser:
+ * Returns the <xul:browser> that contains the document being
+ * printed. This <xul:browser> must have its type attribute set to
+ * "content", "content-targetable", or "content-primary".
+ *
+ * getNavToolbox:
+ * Returns the primary toolbox for this window.
+ *
+ * onEnter:
+ * Called upon entering print preview.
+ *
+ * onExit:
+ * Called upon exiting print preview.
+ *
+ * These methods must be defined. printPreview can be called
+ * with aListenerObj as null iff this window is already displaying
+ * print preview (in which case, the previous aListenerObj passed
+ * to it will be used).
+ */
+ printPreview: function (aListenerObj)
+ {
+ // if we're already in PP mode, don't set the listener; chances
+ // are it is null because someone is calling printPreview() to
+ // get us to refresh the display.
+ if (!this.inPrintPreview) {
+ this._listener = aListenerObj;
+ this._sourceBrowser = aListenerObj.getSourceBrowser();
+ this._originalTitle = this._sourceBrowser.contentTitle;
+ this._originalURL = this._sourceBrowser.currentURI.spec;
+ } else {
+ // collapse the browser here -- it will be shown in
+ // enterPrintPreview; this forces a reflow which fixes display
+ // issues in bug 267422.
+ // We use the print preview browser as the source browser to avoid
+ // re-initializing print preview with a document that might now have changed.
+ this._sourceBrowser = this._listener.getPrintPreviewBrowser();
+ this._sourceBrowser.collapsed = true;
+
+ // If the user transits too quickly within preview and we have a pending
+ // progress dialog, we will close it before opening a new one.
+ this.ensureProgressDialogClosed();
+ }
+
+ this._webProgressPP = {};
+ let ppParams = {};
+ let notifyOnOpen = {};
+ let printSettings = this.getPrintSettings();
+ // Here we get the PrintingPromptService so we can display the PP Progress from script
+ // For the browser implemented via XUL with the PP toolbar we cannot let it be
+ // automatically opened from the print engine because the XUL scrollbars in the PP window
+ // will layout before the content window and a crash will occur.
+ // Doing it all from script, means it lays out before hand and we can let printing do its own thing
+ let PPROMPTSVC = Components.classes["@mozilla.org/embedcomp/printingprompt-service;1"]
+ .getService(Components.interfaces.nsIPrintingPromptService);
+ // just in case we are already printing,
+ // an error code could be returned if the Progress Dialog is already displayed
+ try {
+ PPROMPTSVC.showProgress(window, null, printSettings, this._obsPP, false,
+ this._webProgressPP, ppParams, notifyOnOpen);
+ if (ppParams.value) {
+ ppParams.value.docTitle = this._originalTitle;
+ ppParams.value.docURL = this._originalURL;
+ }
+
+ // this tells us whether we should continue on with PP or
+ // wait for the callback via the observer
+ if (!notifyOnOpen.value.valueOf() || this._webProgressPP.value == null) {
+ this.enterPrintPreview();
+ }
+ } catch (e) {
+ this.enterPrintPreview();
+ }
+ },
+
+ /**
+ * Returns the nsIWebBrowserPrint associated with some content window.
+ * This method is being kept here for compatibility reasons, but should not
+ * be called by code hoping to support e10s / remote browsers.
+ *
+ * @param aWindow
+ * The window from which to get the nsIWebBrowserPrint from.
+ * @return nsIWebBrowserPrint
+ */
+ getWebBrowserPrint: function (aWindow)
+ {
+ let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
+ let text = "getWebBrowserPrint is now deprecated, and fully unsupported for " +
+ "multi-process browsers. Please use a frame script to get " +
+ "access to nsIWebBrowserPrint from content.";
+ let url = "https://developer.mozilla.org/en-US/docs/Printing_from_a_XUL_App";
+ Deprecated.warning(text, url);
+
+ if (this.usingRemoteTabs) {
+ return {};
+ }
+
+ var contentWindow = aWindow || window.content;
+ return contentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebBrowserPrint);
+ },
+
+ /**
+ * Returns the nsIWebBrowserPrint from the print preview browser's docShell.
+ * This method is being kept here for compatibility reasons, but should not
+ * be called by code hoping to support e10s / remote browsers.
+ *
+ * @return nsIWebBrowserPrint
+ */
+ getPrintPreview: function() {
+ let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
+ let text = "getPrintPreview is now deprecated, and fully unsupported for " +
+ "multi-process browsers. Please use a frame script to get " +
+ "access to nsIWebBrowserPrint from content.";
+ let url = "https://developer.mozilla.org/en-US/docs/Printing_from_a_XUL_App";
+ Deprecated.warning(text, url);
+
+ if (this.usingRemoteTabs) {
+ return {};
+ }
+
+ return this._listener.getPrintPreviewBrowser().docShell.printPreview;
+ },
+
+ get inPrintPreview() {
+ return document.getElementById("print-preview-toolbar") != null;
+ },
+
+ // "private" methods and members. Don't use them.
+
+ _listener: null,
+ _closeHandlerPP: null,
+ _webProgressPP: null,
+ _sourceBrowser: null,
+ _originalTitle: "",
+ _originalURL: "",
+ _shouldSimplify: false,
+
+ get usingRemoteTabs() {
+ // We memoize this, since it's highly unlikely to change over the lifetime
+ // of the window.
+ let usingRemoteTabs =
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext)
+ .useRemoteTabs;
+ delete this.usingRemoteTabs;
+ return this.usingRemoteTabs = usingRemoteTabs;
+ },
+
+ displayPrintingError(nsresult, isPrinting) {
+ // The nsresults from a printing error are mapped to strings that have
+ // similar names to the errors themselves. For example, for error
+ // NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE, the name of the string
+ // for the error message is: PERR_GFX_PRINTER_NO_PRINTER_AVAILABLE. What's
+ // more, if we're in the process of doing a print preview, it's possible
+ // that there are strings specific for print preview for these errors -
+ // if so, the names of those strings have _PP as a suffix. It's possible
+ // that no print preview specific strings exist, in which case it is fine
+ // to fall back to the original string name.
+ const MSG_CODES = [
+ "GFX_PRINTER_NO_PRINTER_AVAILABLE",
+ "GFX_PRINTER_NAME_NOT_FOUND",
+ "GFX_PRINTER_COULD_NOT_OPEN_FILE",
+ "GFX_PRINTER_STARTDOC",
+ "GFX_PRINTER_ENDDOC",
+ "GFX_PRINTER_STARTPAGE",
+ "GFX_PRINTER_DOC_IS_BUSY",
+ "ABORT",
+ "NOT_AVAILABLE",
+ "NOT_IMPLEMENTED",
+ "OUT_OF_MEMORY",
+ "UNEXPECTED",
+ ];
+
+ // PERR_FAILURE is the catch-all error message if we've gotten one that
+ // we don't recognize.
+ msgName = "PERR_FAILURE";
+
+ for (let code of MSG_CODES) {
+ let nsErrorResult = "NS_ERROR_" + code;
+ if (Components.results[nsErrorResult] == nsresult) {
+ msgName = "PERR_" + code;
+ break;
+ }
+ }
+
+ let msg, title;
+
+ if (!isPrinting) {
+ // Try first with _PP suffix.
+ let ppMsgName = msgName + "_PP";
+ try {
+ msg = this.bundle.GetStringFromName(ppMsgName);
+ } catch (e) {
+ // We allow localizers to not have the print preview error string,
+ // and just fall back to the printing error string.
+ }
+ }
+
+ if (!msg) {
+ msg = this.bundle.GetStringFromName(msgName);
+ }
+
+ title = this.bundle.GetStringFromName(isPrinting ? "print_error_dialog_title"
+ : "printpreview_error_dialog_title");
+
+ let promptSvc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ promptSvc.alert(window, title, msg);
+ },
+
+ receiveMessage(aMessage) {
+ if (aMessage.name == "Printing:Error") {
+ this.displayPrintingError(aMessage.data.nsresult,
+ aMessage.data.isPrinting);
+ return undefined;
+ }
+
+ // If we got here, then the message we've received must involve
+ // updating the print progress UI.
+ if (!this._webProgressPP.value) {
+ // We somehow didn't get a nsIWebProgressListener to be updated...
+ // I guess there's nothing to do.
+ return undefined;
+ }
+
+ let listener = this._webProgressPP.value;
+ let mm = aMessage.target.messageManager;
+ let data = aMessage.data;
+
+ switch (aMessage.name) {
+ case "Printing:Preview:ProgressChange": {
+ return listener.onProgressChange(null, null,
+ data.curSelfProgress,
+ data.maxSelfProgress,
+ data.curTotalProgress,
+ data.maxTotalProgress);
+ }
+
+ case "Printing:Preview:StateChange": {
+ if (data.stateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) {
+ // Strangely, the printing engine sends 2 STATE_STOP messages when
+ // print preview is finishing. One has the STATE_IS_DOCUMENT flag,
+ // the other has the STATE_IS_NETWORK flag. However, the webProgressPP
+ // listener stops listening once the first STATE_STOP is sent.
+ // Any subsequent messages result in NS_ERROR_FAILURE errors getting
+ // thrown. This should all get torn out once bug 1088061 is fixed.
+ mm.removeMessageListener("Printing:Preview:StateChange", this);
+ mm.removeMessageListener("Printing:Preview:ProgressChange", this);
+ }
+
+ return listener.onStateChange(null, null,
+ data.stateFlags,
+ data.status);
+ }
+ }
+ return undefined;
+ },
+
+ setPrinterDefaultsForSelectedPrinter: function (aPSSVC, aPrintSettings)
+ {
+ if (!aPrintSettings.printerName)
+ aPrintSettings.printerName = aPSSVC.defaultPrinterName;
+
+ // First get any defaults from the printer
+ aPSSVC.initPrintSettingsFromPrinter(aPrintSettings.printerName, aPrintSettings);
+ // now augment them with any values from last time
+ aPSSVC.initPrintSettingsFromPrefs(aPrintSettings, true, aPrintSettings.kInitSaveAll);
+ },
+
+ getPrintSettings: function ()
+ {
+ var pref = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ if (pref) {
+ gPrintSettingsAreGlobal = pref.getBoolPref("print.use_global_printsettings", false);
+ gSavePrintSettings = pref.getBoolPref("print.save_print_settings", false);
+ }
+
+ var printSettings;
+ try {
+ var PSSVC = Components.classes["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Components.interfaces.nsIPrintSettingsService);
+ if (gPrintSettingsAreGlobal) {
+ printSettings = PSSVC.globalPrintSettings;
+ this.setPrinterDefaultsForSelectedPrinter(PSSVC, printSettings);
+ } else {
+ printSettings = PSSVC.newPrintSettings;
+ }
+ } catch (e) {
+ dump("getPrintSettings: "+e+"\n");
+ }
+ return printSettings;
+ },
+
+ // This observer is called once the progress dialog has been "opened"
+ _obsPP:
+ {
+ observe: function(aSubject, aTopic, aData)
+ {
+ // delay the print preview to show the content of the progress dialog
+ setTimeout(function () { PrintUtils.enterPrintPreview(); }, 0);
+ },
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Components.interfaces.nsIObserver) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+ },
+
+ setSimplifiedMode: function (shouldSimplify)
+ {
+ this._shouldSimplify = shouldSimplify;
+ },
+
+ enterPrintPreview: function ()
+ {
+ // Send a message to the print preview browser to initialize
+ // print preview. If we happen to have gotten a print preview
+ // progress listener from nsIPrintingPromptService.showProgress
+ // in printPreview, we add listeners to feed that progress
+ // listener.
+ let ppBrowser = this._listener.getPrintPreviewBrowser();
+ let mm = ppBrowser.messageManager;
+
+ let sendEnterPreviewMessage = function (browser, simplified) {
+ mm.sendAsyncMessage("Printing:Preview:Enter", {
+ windowID: browser.outerWindowID,
+ simplifiedMode: simplified,
+ });
+ };
+
+ // If we happen to have gotten simplify page checked, we will lazily
+ // instantiate a new tab that parses the original page using ReaderMode
+ // primitives. When it's ready, and in order to enter on preview, we send
+ // over a message to print preview browser passing up the simplified tab as
+ // reference. If not, we pass the original tab instead as content source.
+ if (this._shouldSimplify) {
+ let simplifiedBrowser = this._listener.getSimplifiedSourceBrowser();
+ if (simplifiedBrowser) {
+ sendEnterPreviewMessage(simplifiedBrowser, true);
+ } else {
+ simplifiedBrowser = this._listener.createSimplifiedBrowser();
+
+ // After instantiating the simplified tab, we attach a listener as
+ // callback. Once we discover reader mode has been loaded, we fire
+ // up a message to enter on print preview.
+ let spMM = simplifiedBrowser.messageManager;
+ spMM.addMessageListener("Printing:Preview:ReaderModeReady", function onReaderReady() {
+ spMM.removeMessageListener("Printing:Preview:ReaderModeReady", onReaderReady);
+ sendEnterPreviewMessage(simplifiedBrowser, true);
+ });
+
+ // Here, we send down a message to simplified browser in order to parse
+ // the original page. After we have parsed it, content will tell parent
+ // that the document is ready for print previewing.
+ spMM.sendAsyncMessage("Printing:Preview:ParseDocument", {
+ URL: this._originalURL,
+ windowID: this._sourceBrowser.outerWindowID,
+ });
+ }
+ } else {
+ sendEnterPreviewMessage(this._sourceBrowser, false);
+ }
+
+ if (this._webProgressPP.value) {
+ mm.addMessageListener("Printing:Preview:StateChange", this);
+ mm.addMessageListener("Printing:Preview:ProgressChange", this);
+ }
+
+ let onEntered = (message) => {
+ mm.removeMessageListener("Printing:Preview:Entered", onEntered);
+
+ if (message.data.failed) {
+ // Something went wrong while putting the document into print preview
+ // mode. Bail out.
+ this._listener.onEnter();
+ this._listener.onExit();
+ return;
+ }
+
+ // Stash the focused element so that we can return to it after exiting
+ // print preview.
+ gFocusedElement = document.commandDispatcher.focusedElement;
+
+ let printPreviewTB = document.getElementById("print-preview-toolbar");
+ if (printPreviewTB) {
+ printPreviewTB.updateToolbar();
+ ppBrowser.collapsed = false;
+ ppBrowser.focus();
+ return;
+ }
+
+ // Set the original window as an active window so any mozPrintCallbacks can
+ // run without delayed setTimeouts.
+ if (this._listener.activateBrowser) {
+ this._listener.activateBrowser(this._sourceBrowser);
+ } else {
+ this._sourceBrowser.docShellIsActive = true;
+ }
+
+ // show the toolbar after we go into print preview mode so
+ // that we can initialize the toolbar with total num pages
+ const XUL_NS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ printPreviewTB = document.createElementNS(XUL_NS, "toolbar");
+ printPreviewTB.setAttribute("printpreview", true);
+ printPreviewTB.setAttribute("fullscreentoolbar", true);
+ printPreviewTB.id = "print-preview-toolbar";
+
+ let navToolbox = this._listener.getNavToolbox();
+ navToolbox.parentNode.insertBefore(printPreviewTB, navToolbox);
+ printPreviewTB.initialize(ppBrowser);
+
+ // Enable simplify page checkbox when the page is an article
+ if (this._sourceBrowser.isArticle) {
+ printPreviewTB.enableSimplifyPage();
+ } else {
+ printPreviewTB.disableSimplifyPage();
+ }
+
+ // copy the window close handler
+ if (document.documentElement.hasAttribute("onclose"))
+ this._closeHandlerPP = document.documentElement.getAttribute("onclose");
+ else
+ this._closeHandlerPP = null;
+ document.documentElement.setAttribute("onclose", "PrintUtils.exitPrintPreview(); return false;");
+
+ // disable chrome shortcuts...
+ window.addEventListener("keydown", this.onKeyDownPP, true);
+ window.addEventListener("keypress", this.onKeyPressPP, true);
+
+ ppBrowser.collapsed = false;
+ ppBrowser.focus();
+ // on Enter PP Call back
+ this._listener.onEnter();
+ };
+
+ mm.addMessageListener("Printing:Preview:Entered", onEntered);
+ },
+
+ exitPrintPreview: function ()
+ {
+ let ppBrowser = this._listener.getPrintPreviewBrowser();
+ let browserMM = ppBrowser.messageManager;
+ browserMM.sendAsyncMessage("Printing:Preview:Exit");
+ window.removeEventListener("keydown", this.onKeyDownPP, true);
+ window.removeEventListener("keypress", this.onKeyPressPP, true);
+
+ // restore the old close handler
+ document.documentElement.setAttribute("onclose", this._closeHandlerPP);
+ this._closeHandlerPP = null;
+
+ // remove the print preview toolbar
+ let printPreviewTB = document.getElementById("print-preview-toolbar");
+ this._listener.getNavToolbox().parentNode.removeChild(printPreviewTB);
+
+ let fm = Components.classes["@mozilla.org/focus-manager;1"]
+ .getService(Components.interfaces.nsIFocusManager);
+ if (gFocusedElement)
+ fm.setFocus(gFocusedElement, fm.FLAG_NOSCROLL);
+ else
+ this._sourceBrowser.focus();
+ gFocusedElement = null;
+
+ this.setSimplifiedMode(false);
+
+ this.ensureProgressDialogClosed();
+
+ this._listener.onExit();
+ },
+
+ onKeyDownPP: function (aEvent)
+ {
+ // Esc exits the PP
+ if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+ PrintUtils.exitPrintPreview();
+ }
+ },
+
+ onKeyPressPP: function (aEvent)
+ {
+ var closeKey;
+ try {
+ closeKey = document.getElementById("key_close")
+ .getAttribute("key");
+ closeKey = aEvent["DOM_VK_"+closeKey];
+ } catch (e) {}
+ var isModif = aEvent.ctrlKey || aEvent.metaKey;
+ // Ctrl-W exits the PP
+ if (isModif &&
+ (aEvent.charCode == closeKey || aEvent.charCode == closeKey + 32)) {
+ PrintUtils.exitPrintPreview();
+ }
+ else if (isModif) {
+ var printPreviewTB = document.getElementById("print-preview-toolbar");
+ var printKey = document.getElementById("printKb").getAttribute("key").toUpperCase();
+ var pressedKey = String.fromCharCode(aEvent.charCode).toUpperCase();
+ if (printKey == pressedKey) {
+ printPreviewTB.print();
+ }
+ }
+ // cancel shortkeys
+ if (isModif) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+ },
+
+ /**
+ * If there's a printing or print preview progress dialog displayed, force
+ * it to close now.
+ */
+ ensureProgressDialogClosed() {
+ if (this._webProgressPP && this._webProgressPP.value) {
+ this._webProgressPP.value.onStateChange(null, null,
+ Components.interfaces.nsIWebProgressListener.STATE_STOP, 0);
+ }
+ },
+}
+
+PrintUtils.init();
diff --git a/components/printing/content/printdialog.js b/components/printing/content/printdialog.js
new file mode 100644
index 000000000..f9ed94bad
--- /dev/null
+++ b/components/printing/content/printdialog.js
@@ -0,0 +1,417 @@
+// -*- 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 dialog;
+var printService = null;
+var gOriginalNumCopies = 1;
+
+var paramBlock;
+var gPrefs = null;
+var gPrintSettings = null;
+var gWebBrowserPrint = null;
+var gPrintSetInterface = Components.interfaces.nsIPrintSettings;
+var doDebug = false;
+
+// ---------------------------------------------------
+function initDialog()
+{
+ dialog = {};
+
+ dialog.propertiesButton = document.getElementById("properties");
+ dialog.descText = document.getElementById("descText");
+
+ dialog.printrangeGroup = document.getElementById("printrangeGroup");
+ dialog.allpagesRadio = document.getElementById("allpagesRadio");
+ dialog.rangeRadio = document.getElementById("rangeRadio");
+ dialog.selectionRadio = document.getElementById("selectionRadio");
+ dialog.frompageInput = document.getElementById("frompageInput");
+ dialog.frompageLabel = document.getElementById("frompageLabel");
+ dialog.topageInput = document.getElementById("topageInput");
+ dialog.topageLabel = document.getElementById("topageLabel");
+
+ dialog.numCopiesInput = document.getElementById("numCopiesInput");
+
+ dialog.printframeGroup = document.getElementById("printframeGroup");
+ dialog.aslaidoutRadio = document.getElementById("aslaidoutRadio");
+ dialog.selectedframeRadio = document.getElementById("selectedframeRadio");
+ dialog.eachframesepRadio = document.getElementById("eachframesepRadio");
+ dialog.printframeGroupLabel = document.getElementById("printframeGroupLabel");
+
+ dialog.fileCheck = document.getElementById("fileCheck");
+ dialog.printerLabel = document.getElementById("printerLabel");
+ dialog.printerList = document.getElementById("printerList");
+
+ dialog.printButton = document.documentElement.getButton("accept");
+
+ // <data> elements
+ dialog.printName = document.getElementById("printButton");
+ dialog.fpDialog = document.getElementById("fpDialog");
+
+ dialog.enabled = false;
+}
+
+// ---------------------------------------------------
+function checkInteger(element)
+{
+ var value = element.value;
+ if (value && value.length > 0) {
+ value = value.replace(/[^0-9]/g, "");
+ if (!value) value = "";
+ element.value = value;
+ }
+ if (!value || value < 1 || value > 999)
+ dialog.printButton.setAttribute("disabled", "true");
+ else
+ dialog.printButton.removeAttribute("disabled");
+}
+
+// ---------------------------------------------------
+function stripTrailingWhitespace(element)
+{
+ var value = element.value;
+ value = value.replace(/\s+$/, "");
+ element.value = value;
+}
+
+// ---------------------------------------------------
+function getPrinterDescription(printerName)
+{
+ return gPrefs.getCharPref("print.printer_" + printerName + ".printer_description", "")
+}
+
+// ---------------------------------------------------
+function listElement(aListElement)
+ {
+ this.listElement = aListElement;
+ }
+
+listElement.prototype =
+ {
+ clearList:
+ function ()
+ {
+ // remove the menupopup node child of the menulist.
+ var popup = this.listElement.firstChild;
+ if (popup) {
+ this.listElement.removeChild(popup);
+ }
+ },
+
+ appendPrinterNames:
+ function (aDataObject)
+ {
+ if ((null == aDataObject) || !aDataObject.hasMore()) {
+ // disable dialog
+ this.listElement.setAttribute("value", "");
+ this.listElement.setAttribute("label",
+ document.getElementById("printingBundle")
+ .getString("noprinter"));
+
+ this.listElement.setAttribute("disabled", "true");
+ dialog.printerLabel.setAttribute("disabled", "true");
+ dialog.propertiesButton.setAttribute("disabled", "true");
+ dialog.fileCheck.setAttribute("disabled", "true");
+ dialog.printButton.setAttribute("disabled", "true");
+ }
+ else {
+ // build popup menu from printer names
+ var list = document.getElementById("printerList");
+ do {
+ printerNameStr = aDataObject.getNext();
+ list.appendItem(printerNameStr, printerNameStr, getPrinterDescription(printerNameStr));
+ } while (aDataObject.hasMore());
+ this.listElement.removeAttribute("disabled");
+ }
+ }
+ };
+
+// ---------------------------------------------------
+function getPrinters()
+{
+ var selectElement = new listElement(dialog.printerList);
+ selectElement.clearList();
+
+ var printerEnumerator;
+ try {
+ printerEnumerator =
+ Components.classes["@mozilla.org/gfx/printerenumerator;1"]
+ .getService(Components.interfaces.nsIPrinterEnumerator)
+ .printerNameList;
+ } catch (e) { printerEnumerator = null; }
+
+ selectElement.appendPrinterNames(printerEnumerator);
+ selectElement.listElement.value = printService.defaultPrinterName;
+
+ // make sure we load the prefs for the initially selected printer
+ setPrinterDefaultsForSelectedPrinter();
+}
+
+
+// ---------------------------------------------------
+// update gPrintSettings with the defaults for the selected printer
+function setPrinterDefaultsForSelectedPrinter()
+{
+ gPrintSettings.printerName = dialog.printerList.value;
+
+ dialog.descText.value = getPrinterDescription(gPrintSettings.printerName);
+
+ // First get any defaults from the printer
+ printService.initPrintSettingsFromPrinter(gPrintSettings.printerName, gPrintSettings);
+
+ // now augment them with any values from last time
+ printService.initPrintSettingsFromPrefs(gPrintSettings, true, gPrintSetInterface.kInitSaveAll);
+
+ if (doDebug) {
+ dump("setPrinterDefaultsForSelectedPrinter: printerName='"+gPrintSettings.printerName+"', paperName='"+gPrintSettings.paperName+"'\n");
+ }
+}
+
+// ---------------------------------------------------
+function displayPropertiesDialog()
+{
+ gPrintSettings.numCopies = dialog.numCopiesInput.value;
+ try {
+ var printingPromptService = Components.classes["@mozilla.org/embedcomp/printingprompt-service;1"]
+ .getService(Components.interfaces.nsIPrintingPromptService);
+ if (printingPromptService) {
+ printingPromptService.showPrinterProperties(null, dialog.printerList.value, gPrintSettings);
+ dialog.numCopiesInput.value = gPrintSettings.numCopies;
+ }
+ } catch (e) {
+ dump("problems getting printingPromptService\n");
+ }
+}
+
+// ---------------------------------------------------
+function doPrintRange(inx)
+{
+ if (inx == 1) {
+ dialog.frompageInput.removeAttribute("disabled");
+ dialog.frompageLabel.removeAttribute("disabled");
+ dialog.topageInput.removeAttribute("disabled");
+ dialog.topageLabel.removeAttribute("disabled");
+ } else {
+ dialog.frompageInput.setAttribute("disabled", "true");
+ dialog.frompageLabel.setAttribute("disabled", "true");
+ dialog.topageInput.setAttribute("disabled", "true");
+ dialog.topageLabel.setAttribute("disabled", "true");
+ }
+}
+
+// ---------------------------------------------------
+function loadDialog()
+{
+ var print_copies = 1;
+ var print_selection_radio_enabled = false;
+ var print_frametype = gPrintSetInterface.kSelectedFrame;
+ var print_howToEnableUI = gPrintSetInterface.kFrameEnableNone;
+ var print_tofile = "";
+
+ try {
+ gPrefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
+
+ printService = Components.classes["@mozilla.org/gfx/printsettings-service;1"];
+ if (printService) {
+ printService = printService.getService();
+ if (printService) {
+ printService = printService.QueryInterface(Components.interfaces.nsIPrintSettingsService);
+ }
+ }
+ } catch (e) {}
+
+ // Note: getPrinters sets up the PrintToFile control
+ getPrinters();
+
+ if (gPrintSettings) {
+ print_tofile = gPrintSettings.printToFile;
+ gOriginalNumCopies = gPrintSettings.numCopies;
+
+ print_copies = gPrintSettings.numCopies;
+ print_frametype = gPrintSettings.printFrameType;
+ print_howToEnableUI = gPrintSettings.howToEnableFrameUI;
+ print_selection_radio_enabled = gPrintSettings.GetPrintOptions(gPrintSetInterface.kEnableSelectionRB);
+ }
+
+ if (doDebug) {
+ dump("loadDialog*********************************************\n");
+ dump("print_tofile "+print_tofile+"\n");
+ dump("print_frame "+print_frametype+"\n");
+ dump("print_howToEnableUI "+print_howToEnableUI+"\n");
+ dump("selection_radio_enabled "+print_selection_radio_enabled+"\n");
+ }
+
+ dialog.printrangeGroup.selectedItem = dialog.allpagesRadio;
+ if (print_selection_radio_enabled) {
+ dialog.selectionRadio.removeAttribute("disabled");
+ } else {
+ dialog.selectionRadio.setAttribute("disabled", "true");
+ }
+ doPrintRange(dialog.rangeRadio.selected);
+ dialog.frompageInput.value = 1;
+ dialog.topageInput.value = 1;
+ dialog.numCopiesInput.value = print_copies;
+
+ if (doDebug) {
+ dump("print_howToEnableUI: "+print_howToEnableUI+"\n");
+ }
+
+ // print frame
+ if (print_howToEnableUI == gPrintSetInterface.kFrameEnableAll) {
+ dialog.aslaidoutRadio.removeAttribute("disabled");
+
+ dialog.selectedframeRadio.removeAttribute("disabled");
+ dialog.eachframesepRadio.removeAttribute("disabled");
+ dialog.printframeGroupLabel.removeAttribute("disabled");
+
+ // initialize radio group
+ dialog.printframeGroup.selectedItem = dialog.selectedframeRadio;
+
+ } else if (print_howToEnableUI == gPrintSetInterface.kFrameEnableAsIsAndEach) {
+ dialog.aslaidoutRadio.removeAttribute("disabled"); // enable
+
+ dialog.selectedframeRadio.setAttribute("disabled", "true"); // disable
+ dialog.eachframesepRadio.removeAttribute("disabled"); // enable
+ dialog.printframeGroupLabel.removeAttribute("disabled"); // enable
+
+ // initialize
+ dialog.printframeGroup.selectedItem = dialog.eachframesepRadio;
+
+ } else {
+ dialog.aslaidoutRadio.setAttribute("disabled", "true");
+ dialog.selectedframeRadio.setAttribute("disabled", "true");
+ dialog.eachframesepRadio.setAttribute("disabled", "true");
+ dialog.printframeGroupLabel.setAttribute("disabled", "true");
+ }
+
+ dialog.printButton.label = dialog.printName.getAttribute("label");
+}
+
+// ---------------------------------------------------
+function onLoad()
+{
+ // Init dialog.
+ initDialog();
+
+ // param[0]: nsIPrintSettings object
+ // param[1]: container for return value (1 = print, 0 = cancel)
+
+ gPrintSettings = window.arguments[0].QueryInterface(gPrintSetInterface);
+ gWebBrowserPrint = window.arguments[1].QueryInterface(Components.interfaces.nsIWebBrowserPrint);
+ paramBlock = window.arguments[2].QueryInterface(Components.interfaces.nsIDialogParamBlock);
+
+ // default return value is "cancel"
+ paramBlock.SetInt(0, 0);
+
+ loadDialog();
+}
+
+// ---------------------------------------------------
+function onAccept()
+{
+ if (gPrintSettings != null) {
+ var print_howToEnableUI = gPrintSetInterface.kFrameEnableNone;
+
+ // save these out so they can be picked up by the device spec
+ gPrintSettings.printerName = dialog.printerList.value;
+ print_howToEnableUI = gPrintSettings.howToEnableFrameUI;
+ gPrintSettings.printToFile = dialog.fileCheck.checked;
+
+ if (gPrintSettings.printToFile && !chooseFile())
+ return false;
+
+ if (dialog.allpagesRadio.selected) {
+ gPrintSettings.printRange = gPrintSetInterface.kRangeAllPages;
+ } else if (dialog.rangeRadio.selected) {
+ gPrintSettings.printRange = gPrintSetInterface.kRangeSpecifiedPageRange;
+ } else if (dialog.selectionRadio.selected) {
+ gPrintSettings.printRange = gPrintSetInterface.kRangeSelection;
+ }
+ gPrintSettings.startPageRange = dialog.frompageInput.value;
+ gPrintSettings.endPageRange = dialog.topageInput.value;
+ gPrintSettings.numCopies = dialog.numCopiesInput.value;
+
+ var frametype = gPrintSetInterface.kNoFrames;
+ if (print_howToEnableUI != gPrintSetInterface.kFrameEnableNone) {
+ if (dialog.aslaidoutRadio.selected) {
+ frametype = gPrintSetInterface.kFramesAsIs;
+ } else if (dialog.selectedframeRadio.selected) {
+ frametype = gPrintSetInterface.kSelectedFrame;
+ } else if (dialog.eachframesepRadio.selected) {
+ frametype = gPrintSetInterface.kEachFrameSep;
+ } else {
+ frametype = gPrintSetInterface.kSelectedFrame;
+ }
+ }
+ gPrintSettings.printFrameType = frametype;
+ if (doDebug) {
+ dump("onAccept*********************************************\n");
+ dump("frametype "+frametype+"\n");
+ dump("numCopies "+gPrintSettings.numCopies+"\n");
+ dump("printRange "+gPrintSettings.printRange+"\n");
+ dump("printerName "+gPrintSettings.printerName+"\n");
+ dump("startPageRange "+gPrintSettings.startPageRange+"\n");
+ dump("endPageRange "+gPrintSettings.endPageRange+"\n");
+ dump("printToFile "+gPrintSettings.printToFile+"\n");
+ }
+ }
+
+ var saveToPrefs = false;
+
+ saveToPrefs = gPrefs.getBoolPref("print.save_print_settings");
+
+ if (saveToPrefs && printService != null) {
+ var flags = gPrintSetInterface.kInitSavePaperSize |
+ gPrintSetInterface.kInitSaveEdges |
+ gPrintSetInterface.kInitSaveInColor |
+ gPrintSetInterface.kInitSaveShrinkToFit |
+ gPrintSetInterface.kInitSaveScaling;
+ printService.savePrintSettingsToPrefs(gPrintSettings, true, flags);
+ }
+
+ // set return value to "print"
+ if (paramBlock) {
+ paramBlock.SetInt(0, 1);
+ } else {
+ dump("*** FATAL ERROR: No paramBlock\n");
+ }
+
+ return true;
+}
+
+// ---------------------------------------------------
+function onCancel()
+{
+ // set return value to "cancel"
+ if (paramBlock) {
+ paramBlock.SetInt(0, 0);
+ } else {
+ dump("*** FATAL ERROR: No paramBlock\n");
+ }
+
+ return true;
+}
+
+// ---------------------------------------------------
+const nsIFilePicker = Components.interfaces.nsIFilePicker;
+function chooseFile()
+{
+ try {
+ var fp = Components.classes["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ fp.init(window, dialog.fpDialog.getAttribute("label"), nsIFilePicker.modeSave);
+ fp.appendFilters(nsIFilePicker.filterAll);
+ if (fp.show() != Components.interfaces.nsIFilePicker.returnCancel &&
+ fp.file && fp.file.path) {
+ gPrintSettings.toFileName = fp.file.path;
+ return true;
+ }
+ } catch (ex) {
+ dump(ex);
+ }
+
+ return false;
+}
+
diff --git a/components/printing/content/printdialog.xul b/components/printing/content/printdialog.xul
new file mode 100644
index 000000000..d6452945b
--- /dev/null
+++ b/components/printing/content/printdialog.xul
@@ -0,0 +1,126 @@
+<?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://global/locale/printdialog.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ ondialogaccept="return onAccept();"
+ oncancel="return onCancel();"
+ buttoniconaccept="print"
+ title="&printDialog.title;"
+ persist="screenX screenY"
+ screenX="24" screenY="24">
+
+ <script type="application/javascript" src="chrome://global/content/printdialog.js"/>
+
+ <stringbundle id="printingBundle" src="chrome://global/locale/printing.properties"/>
+
+ <groupbox>
+ <caption label="&printer.label;"/>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ <column/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label id="printerLabel"
+ value="&printerInput.label;"
+ accesskey="&printerInput.accesskey;"
+ control="printerList"/>
+ </hbox>
+ <menulist id="printerList" flex="1" type="description" oncommand="setPrinterDefaultsForSelectedPrinter();"/>
+ <button id="properties"
+ label="&propertiesButton.label;"
+ accesskey="&propertiesButton.accesskey;"
+ icon="properties"
+ oncommand="displayPropertiesDialog();"/>
+ </row>
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label id="descTextLabel" control="descText" value="&descText.label;"/>
+ </hbox>
+ <label id="descText"/>
+ <checkbox id="fileCheck"
+ checked="false"
+ label="&fileCheck.label;"
+ accesskey="&fileCheck.accesskey;"
+ pack="end"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <hbox>
+ <groupbox flex="1">
+ <caption label="&printrangeGroup.label;"/>
+
+ <radiogroup id="printrangeGroup">
+ <radio id="allpagesRadio"
+ label="&allpagesRadio.label;"
+ accesskey="&allpagesRadio.accesskey;"
+ oncommand="doPrintRange(0)"/>
+ <hbox align="center">
+ <radio id="rangeRadio"
+ label="&rangeRadio.label;"
+ accesskey="&rangeRadio.accesskey;"
+ oncommand="doPrintRange(1)"/>
+ <label id="frompageLabel"
+ control="frompageInput"
+ value="&frompageInput.label;"
+ accesskey="&frompageInput.accesskey;"/>
+ <textbox id="frompageInput" style="width:5em;" onkeyup="checkInteger(this)"/>
+ <label id="topageLabel"
+ control="topageInput"
+ value="&topageInput.label;"
+ accesskey="&topageInput.accesskey;"/>
+ <textbox id="topageInput" style="width:5em;" onkeyup="checkInteger(this)"/>
+ </hbox>
+ <radio id="selectionRadio"
+ label="&selectionRadio.label;"
+ accesskey="&selectionRadio.accesskey;"
+ oncommand="doPrintRange(2)"/>
+ </radiogroup>
+ </groupbox>
+
+ <groupbox flex="1">
+ <caption label="&copies.label;"/>
+ <hbox align="center">
+ <label control="numCopiesInput"
+ value="&numCopies.label;"
+ accesskey="&numCopies.accesskey;"/>
+ <textbox id="numCopiesInput" style="width:5em;" onkeyup="checkInteger(this)"/>
+ </hbox>
+ </groupbox>
+ </hbox>
+
+ <groupbox flex="1">
+ <caption label="&printframeGroup.label;" id="printframeGroupLabel"/>
+ <radiogroup id="printframeGroup">
+ <radio id="aslaidoutRadio"
+ label="&aslaidoutRadio.label;"
+ accesskey="&aslaidoutRadio.accesskey;"/>
+ <radio id="selectedframeRadio"
+ label="&selectedframeRadio.label;"
+ accesskey="&selectedframeRadio.accesskey;"/>
+ <radio id="eachframesepRadio"
+ label="&eachframesepRadio.label;"
+ accesskey="&eachframesepRadio.accesskey;"/>
+ </radiogroup>
+ </groupbox>
+
+ <!-- used to store titles and labels -->
+ <data style="display:none;" id="printButton" label="&printButton.label;"/>
+ <data style="display:none;" id="fpDialog" label="&fpDialog.title;"/>
+
+</dialog>
+
diff --git a/components/printing/content/printjoboptions.js b/components/printing/content/printjoboptions.js
new file mode 100644
index 000000000..098358940
--- /dev/null
+++ b/components/printing/content/printjoboptions.js
@@ -0,0 +1,394 @@
+// -*- 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 dialog;
+var gPrintBundle;
+var gPrintSettings = null;
+var gPrintSettingsInterface = Components.interfaces.nsIPrintSettings;
+var gPaperArray;
+var gPrefs;
+
+var gPrintSetInterface = Components.interfaces.nsIPrintSettings;
+var doDebug = true;
+
+// ---------------------------------------------------
+function checkDouble(element, maxVal)
+{
+ var value = element.value;
+ if (value && value.length > 0) {
+ value = value.replace(/[^\.|^0-9]/g, "");
+ if (!value) {
+ element.value = "";
+ } else if (value > maxVal) {
+ element.value = maxVal;
+ } else {
+ element.value = value;
+ }
+ }
+}
+
+// ---------------------------------------------------
+function isListOfPrinterFeaturesAvailable()
+{
+ return gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".has_special_printerfeatures", false);
+}
+
+// ---------------------------------------------------
+function getDoubleStr(val, dec)
+{
+ var str = val.toString();
+ var inx = str.indexOf(".");
+ return str.substring(0, inx+dec+1);
+}
+
+// ---------------------------------------------------
+function initDialog()
+{
+ gPrintBundle = document.getElementById("printBundle");
+
+ dialog = {};
+
+ dialog.paperList = document.getElementById("paperList");
+ dialog.paperGroup = document.getElementById("paperGroup");
+
+ dialog.jobTitleLabel = document.getElementById("jobTitleLabel");
+ dialog.jobTitleGroup = document.getElementById("jobTitleGroup");
+ dialog.jobTitleInput = document.getElementById("jobTitleInput");
+
+ dialog.colorGroup = document.getElementById("colorGroup");
+ dialog.colorRadioGroup = document.getElementById("colorRadioGroup");
+ dialog.colorRadio = document.getElementById("colorRadio");
+ dialog.grayRadio = document.getElementById("grayRadio");
+
+ dialog.topInput = document.getElementById("topInput");
+ dialog.bottomInput = document.getElementById("bottomInput");
+ dialog.leftInput = document.getElementById("leftInput");
+ dialog.rightInput = document.getElementById("rightInput");
+}
+
+// ---------------------------------------------------
+function round10(val)
+{
+ return Math.round(val * 10) / 10;
+}
+
+
+// ---------------------------------------------------
+function paperListElement(aPaperListElement)
+ {
+ this.paperListElement = aPaperListElement;
+ }
+
+paperListElement.prototype =
+ {
+ clearPaperList:
+ function ()
+ {
+ // remove the menupopup node child of the menulist.
+ this.paperListElement.removeChild(this.paperListElement.firstChild);
+ },
+
+ appendPaperNames:
+ function (aDataObject)
+ {
+ var popupNode = document.createElement("menupopup");
+ for (var i=0;i<aDataObject.length;i++) {
+ var paperObj = aDataObject[i];
+ var itemNode = document.createElement("menuitem");
+ var label;
+ try {
+ label = gPrintBundle.getString(paperObj.name);
+ }
+ catch (e) {
+ /* No name in string bundle ? Then build one manually (this
+ * usually happens when gPaperArray was build by createPaperArrayFromPrinterFeatures() ...) */
+ if (paperObj.inches) {
+ label = paperObj.name + " (" + round10(paperObj.width) + "x" + round10(paperObj.height) + " inch)";
+ }
+ else {
+ label = paperObj.name + " (" + paperObj.width + "x" + paperObj.height + " mm)";
+ }
+ }
+ itemNode.setAttribute("label", label);
+ itemNode.setAttribute("value", i);
+ popupNode.appendChild(itemNode);
+ }
+ this.paperListElement.appendChild(popupNode);
+ }
+ };
+
+// ---------------------------------------------------
+function createPaperArrayFromDefaults()
+{
+ var paperNames = ["letterSize", "legalSize", "exectiveSize", "a5Size", "a4Size", "a3Size", "a2Size", "a1Size", "a0Size"];
+ // var paperNames = ["&letterRadio.label;", "&legalRadio.label;", "&exectiveRadio.label;", "&a4Radio.label;", "&a3Radio.label;"];
+ var paperWidths = [ 8.5, 8.5, 7.25, 148.0, 210.0, 287.0, 420.0, 594.0, 841.0];
+ var paperHeights = [11.0, 14.0, 10.50, 210.0, 297.0, 420.0, 594.0, 841.0, 1189.0];
+ var paperInches = [true, true, true, false, false, false, false, false, false];
+
+ gPaperArray = new Array();
+
+ for (var i=0;i<paperNames.length;i++) {
+ var obj = {};
+ obj.name = paperNames[i];
+ obj.width = paperWidths[i];
+ obj.height = paperHeights[i];
+ obj.inches = paperInches[i];
+
+ /* Calculate the width/height in millimeters */
+ if (paperInches[i]) {
+ obj.width_mm = paperWidths[i] * 25.4;
+ obj.height_mm = paperHeights[i] * 25.4;
+ }
+ else {
+ obj.width_mm = paperWidths[i];
+ obj.height_mm = paperHeights[i];
+ }
+ gPaperArray[i] = obj;
+ }
+}
+
+// ---------------------------------------------------
+function createPaperArrayFromPrinterFeatures()
+{
+ var printername = gPrintSettings.printerName;
+ if (doDebug) {
+ dump("createPaperArrayFromPrinterFeatures for " + printername + ".\n");
+ }
+
+ gPaperArray = new Array();
+
+ var numPapers = gPrefs.getIntPref("print.tmp.printerfeatures." + printername + ".paper.count");
+
+ if (doDebug) {
+ dump("processing " + numPapers + " entries...\n");
+ }
+
+ for (var i=0;i<numPapers;i++) {
+ var obj = {};
+ obj.name = gPrefs.getCharPref("print.tmp.printerfeatures." + printername + ".paper." + i + ".name");
+ obj.width_mm = gPrefs.getIntPref("print.tmp.printerfeatures." + printername + ".paper." + i + ".width_mm");
+ obj.height_mm = gPrefs.getIntPref("print.tmp.printerfeatures." + printername + ".paper." + i + ".height_mm");
+ obj.inches = gPrefs.getBoolPref("print.tmp.printerfeatures." + printername + ".paper." + i + ".is_inch");
+
+ /* Calculate the width/height in paper's native units (either inches or millimeters) */
+ if (obj.inches) {
+ obj.width = obj.width_mm / 25.4;
+ obj.height = obj.height_mm / 25.4;
+ }
+ else {
+ obj.width = obj.width_mm;
+ obj.height = obj.height_mm;
+ }
+
+ gPaperArray[i] = obj;
+
+ if (doDebug) {
+ dump("paper index=" + i + ", name=" + obj.name + ", width=" + obj.width + ", height=" + obj.height + ".\n");
+ }
+ }
+}
+
+// ---------------------------------------------------
+function createPaperArray()
+{
+ if (isListOfPrinterFeaturesAvailable()) {
+ createPaperArrayFromPrinterFeatures();
+ }
+ else {
+ createPaperArrayFromDefaults();
+ }
+}
+
+// ---------------------------------------------------
+function createPaperSizeList(selectedInx)
+{
+ var selectElement = new paperListElement(dialog.paperList);
+ selectElement.clearPaperList();
+
+ selectElement.appendPaperNames(gPaperArray);
+
+ if (selectedInx > -1) {
+ selectElement.paperListElement.selectedIndex = selectedInx;
+ }
+
+ // dialog.paperList = selectElement;
+}
+
+// ---------------------------------------------------
+function loadDialog()
+{
+ var print_paper_unit = 0;
+ var print_paper_width = 0.0;
+ var print_paper_height = 0.0;
+ var print_paper_name = "";
+ var print_color = true;
+ var print_jobtitle = "";
+
+ gPrefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
+
+ if (gPrintSettings) {
+ print_paper_unit = gPrintSettings.paperSizeUnit;
+ print_paper_width = gPrintSettings.paperWidth;
+ print_paper_height = gPrintSettings.paperHeight;
+ print_paper_name = gPrintSettings.paperName;
+ print_color = gPrintSettings.printInColor;
+ print_jobtitle = gPrintSettings.title;
+ }
+
+ if (doDebug) {
+ dump("loadDialog******************************\n");
+ dump("paperSizeType "+print_paper_unit+"\n");
+ dump("paperWidth "+print_paper_width+"\n");
+ dump("paperHeight "+print_paper_height+"\n");
+ dump("paperName "+print_paper_name+"\n");
+ dump("print_color "+print_color+"\n");
+ dump("print_jobtitle "+print_jobtitle+"\n");
+ }
+
+ createPaperArray();
+
+ var paperSelectedInx = 0;
+ for (var i=0;i<gPaperArray.length;i++) {
+ if (print_paper_name == gPaperArray[i].name) {
+ paperSelectedInx = i;
+ break;
+ }
+ }
+
+ if (doDebug) {
+ if (i == gPaperArray.length)
+ dump("loadDialog: No paper found.\n");
+ else
+ dump("loadDialog: found paper '"+gPaperArray[paperSelectedInx].name+"'.\n");
+ }
+
+ createPaperSizeList(paperSelectedInx);
+
+ /* Enable/disable and/or hide/unhide widgets based in the information
+ * whether the selected printer and/or print module supports the matching
+ * feature or not */
+ if (isListOfPrinterFeaturesAvailable()) {
+ // job title
+ if (gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".can_change_jobtitle"))
+ dialog.jobTitleInput.removeAttribute("disabled");
+ else
+ dialog.jobTitleInput.setAttribute("disabled", "true");
+ if (gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".supports_jobtitle_change"))
+ dialog.jobTitleGroup.removeAttribute("hidden");
+ else
+ dialog.jobTitleGroup.setAttribute("hidden", "true");
+
+ // paper size
+ if (gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".can_change_paper_size"))
+ dialog.paperList.removeAttribute("disabled");
+ else
+ dialog.paperList.setAttribute("disabled", "true");
+ if (gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".supports_paper_size_change"))
+ dialog.paperGroup.removeAttribute("hidden");
+ else
+ dialog.paperGroup.setAttribute("hidden", "true");
+
+ // color/grayscale radio
+ if (gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".can_change_printincolor"))
+ dialog.colorRadioGroup.removeAttribute("disabled");
+ else
+ dialog.colorRadioGroup.setAttribute("disabled", "true");
+ if (gPrefs.getBoolPref("print.tmp.printerfeatures." + gPrintSettings.printerName + ".supports_printincolor_change"))
+ dialog.colorGroup.removeAttribute("hidden");
+ else
+ dialog.colorGroup.setAttribute("hidden", "true");
+ }
+
+ if (print_color) {
+ dialog.colorRadioGroup.selectedItem = dialog.colorRadio;
+ } else {
+ dialog.colorRadioGroup.selectedItem = dialog.grayRadio;
+ }
+
+ dialog.jobTitleInput.value = print_jobtitle;
+
+ dialog.topInput.value = gPrintSettings.edgeTop.toFixed(2);
+ dialog.bottomInput.value = gPrintSettings.edgeBottom.toFixed(2);
+ dialog.leftInput.value = gPrintSettings.edgeLeft.toFixed(2);
+ dialog.rightInput.value = gPrintSettings.edgeRight.toFixed(2);
+}
+
+// ---------------------------------------------------
+function onLoad()
+{
+ // Init dialog.
+ initDialog();
+
+ gPrintSettings = window.arguments[0].QueryInterface(gPrintSetInterface);
+ paramBlock = window.arguments[1].QueryInterface(Components.interfaces.nsIDialogParamBlock);
+
+ if (doDebug) {
+ if (gPrintSettings == null) alert("PrintSettings is null!");
+ if (paramBlock == null) alert("nsIDialogParam is null!");
+ }
+
+ // default return value is "cancel"
+ paramBlock.SetInt(0, 0);
+
+ loadDialog();
+}
+
+// ---------------------------------------------------
+function onAccept()
+{
+ var print_paper_unit = gPrintSettingsInterface.kPaperSizeInches;
+ var print_paper_width = 0.0;
+ var print_paper_height = 0.0;
+ var print_paper_name = "";
+
+ if (gPrintSettings != null) {
+ var paperSelectedInx = dialog.paperList.selectedIndex;
+ if (gPaperArray[paperSelectedInx].inches) {
+ print_paper_unit = gPrintSettingsInterface.kPaperSizeInches;
+ } else {
+ print_paper_unit = gPrintSettingsInterface.kPaperSizeMillimeters;
+ }
+ print_paper_width = gPaperArray[paperSelectedInx].width;
+ print_paper_height = gPaperArray[paperSelectedInx].height;
+ print_paper_name = gPaperArray[paperSelectedInx].name;
+
+ gPrintSettings.paperSizeUnit = print_paper_unit;
+ gPrintSettings.paperWidth = print_paper_width;
+ gPrintSettings.paperHeight = print_paper_height;
+ gPrintSettings.paperName = print_paper_name;
+
+ // save these out so they can be picked up by the device spec
+ gPrintSettings.printInColor = dialog.colorRadio.selected;
+ gPrintSettings.title = dialog.jobTitleInput.value;
+
+ gPrintSettings.edgeTop = dialog.topInput.value;
+ gPrintSettings.edgeBottom = dialog.bottomInput.value;
+ gPrintSettings.edgeLeft = dialog.leftInput.value;
+ gPrintSettings.edgeRight = dialog.rightInput.value;
+
+ if (doDebug) {
+ dump("onAccept******************************\n");
+ dump("paperSizeUnit "+print_paper_unit+"\n");
+ dump("paperWidth "+print_paper_width+"\n");
+ dump("paperHeight "+print_paper_height+"\n");
+ dump("paperName '"+print_paper_name+"'\n");
+
+ dump("printInColor "+gPrintSettings.printInColor+"\n");
+ }
+ } else {
+ dump("************ onAccept gPrintSettings: "+gPrintSettings+"\n");
+ }
+
+ if (paramBlock) {
+ // set return value to "ok"
+ paramBlock.SetInt(0, 1);
+ } else {
+ dump("*** FATAL ERROR: paramBlock missing\n");
+ }
+
+ return true;
+}
diff --git a/components/printing/content/printjoboptions.xul b/components/printing/content/printjoboptions.xul
new file mode 100644
index 000000000..5726dfe3f
--- /dev/null
+++ b/components/printing/content/printjoboptions.xul
@@ -0,0 +1,110 @@
+<?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://global/locale/printjoboptions.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ ondialogaccept="return onAccept();"
+ title="&printJobOptions.title;"
+ persist="screenX screenY"
+ screenX="24" screenY="24">
+
+ <script type="application/javascript" src="chrome://global/content/printjoboptions.js"/>
+
+ <stringbundle id="printBundle" src="chrome://global/locale/printPageSetup.properties"/>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row id="jobTitleGroup">
+ <hbox align="center" pack="end">
+ <label id="jobTitleLabel"
+ value="&jobTitleInput.label;"
+ accesskey="&jobTitleInput.accesskey;"
+ control="jobTitleInput"/>
+ </hbox>
+ <textbox id="jobTitleInput" flex="1"/>
+ </row>
+
+ <row id="paperGroup">
+ <hbox align="center" pack="end">
+ <label id="paperLabel"
+ value="&paperInput.label;"
+ accesskey="&paperInput.accesskey;"
+ control="paperList"/>
+ </hbox>
+ <menulist id="paperList" flex="1">
+ <menupopup/>
+ </menulist>
+ </row>
+
+ <row id="colorGroup">
+ <hbox align="center" pack="end">
+ <label control="colorRadioGroup" value="&colorGroup.label;"/>
+ </hbox>
+ <radiogroup id="colorRadioGroup" orient="horizontal">
+ <radio id="grayRadio"
+ label="&grayRadio.label;"
+ accesskey="&grayRadio.accesskey;"/>
+ <radio id="colorRadio"
+ label="&colorRadio.label;"
+ accesskey="&colorRadio.accesskey;"/>
+ </radiogroup>
+ </row>
+ </rows>
+ </grid>
+
+ <grid>
+ <columns>
+ <column/>
+ </columns>
+ <rows>
+ <row>
+ <groupbox flex="1">
+ <caption label="&edgeMarginInput.label;"/>
+ <hbox>
+ <hbox align="center">
+ <label id="topLabel"
+ value="&topInput.label;"
+ accesskey="&topInput.accesskey;"
+ control="topInput"/>
+ <textbox id="topInput" style="width:5em;" onkeyup="checkDouble(this, 0.5)"/>
+ </hbox>
+ <hbox align="center">
+ <label id="bottomLabel"
+ value="&bottomInput.label;"
+ accesskey="&bottomInput.accesskey;"
+ control="bottomInput"/>
+ <textbox id="bottomInput" style="width:5em;" onkeyup="checkDouble(this, 0.5)"/>
+ </hbox>
+ <hbox align="center">
+ <label id="leftLabel"
+ value="&leftInput.label;"
+ accesskey="&leftInput.accesskey;"
+ control="leftInput"/>
+ <textbox id="leftInput" style="width:5em;" onkeyup="checkDouble(this, 0.5)"/>
+ </hbox>
+ <hbox align="center">
+ <label id="rightLabel"
+ value="&rightInput.label;"
+ accesskey="&rightInput.accesskey;"
+ control="rightInput"/>
+ <textbox id="rightInput" style="width:5em;" onkeyup="checkDouble(this, 0.5)"/>
+ </hbox>
+ </hbox>
+ </groupbox>
+ </row>
+
+ </rows>
+ </grid>
+
+</dialog>
diff --git a/components/printing/content/simplifyMode.css b/components/printing/content/simplifyMode.css
new file mode 100644
index 000000000..d02f216dc
--- /dev/null
+++ b/components/printing/content/simplifyMode.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/. */
+
+/* This file defines specific rules for print preview when using simplify mode.
+ * These rules already exist on aboutReader.css, however, we decoupled it
+ * from the original file so we don't need to load a bunch of extra queries that
+ * will not take effect when using the simplify page checkbox. This file defines
+ * styling for title and author on the header element. */
+
+.header > h1 {
+ font-size: 1.6em;
+ line-height: 1.25em;
+ margin: 30px 0;
+}
+
+.header > .credits {
+ font-size: 0.9em;
+ line-height: 1.48em;
+ margin: 0 0 30px 0;
+ font-style: italic;
+} \ No newline at end of file
diff --git a/components/printing/jar.mn b/components/printing/jar.mn
new file mode 100644
index 000000000..f77cf1c6a
--- /dev/null
+++ b/components/printing/jar.mn
@@ -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/.
+
+toolkit.jar:
+ content/global/printdialog.js (content/printdialog.js)
+ content/global/printdialog.xul (content/printdialog.xul)
+#ifdef XP_UNIX
+ content/global/printjoboptions.js (content/printjoboptions.js)
+ content/global/printjoboptions.xul (content/printjoboptions.xul)
+#endif
+ content/global/printPageSetup.js (content/printPageSetup.js)
+ content/global/printPageSetup.xul (content/printPageSetup.xul)
+* content/global/printPreviewBindings.xml (content/printPreviewBindings.xml)
+ content/global/printPreviewProgress.js (content/printPreviewProgress.js)
+ content/global/printPreviewProgress.xul (content/printPreviewProgress.xul)
+ content/global/printProgress.js (content/printProgress.js)
+ content/global/printProgress.xul (content/printProgress.xul)
+ content/global/printUtils.js (content/printUtils.js)
+ content/global/simplifyMode.css (content/simplifyMode.css)
diff --git a/components/printing/moz.build b/components/printing/moz.build
new file mode 100644
index 000000000..7b15b051b
--- /dev/null
+++ b/components/printing/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+IPDL_SOURCES += [
+ 'public/PPrinting.ipdl',
+ 'public/PPrintingTypes.ipdlh',
+ 'public/PPrintProgressDialog.ipdl',
+ 'public/PPrintSettingsDialog.ipdl',
+]
+
+EXPORTS += ['src/nsPrintingProxy.h']
+
+EXPORTS.mozilla.embedding.printingui += ['src/PrintingParent.h']
+
+if CONFIG['NS_PRINTING']:
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ DIRS += ['src/win']
+ else:
+ DIRS += ['src/unix']
+
+ SOURCES += [
+ 'src/nsPrintingProxy.cpp',
+ 'src/PrintDataUtils.cpp',
+ 'src/PrintingParent.cpp',
+ 'src/PrintProgressDialogChild.cpp',
+ 'src/PrintProgressDialogParent.cpp',
+ 'src/PrintSettingsDialogChild.cpp',
+ 'src/PrintSettingsDialogParent.cpp',
+ ]
+
+ JAR_MANIFESTS += ['jar.mn']
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/printing/public/PPrintProgressDialog.ipdl b/components/printing/public/PPrintProgressDialog.ipdl
new file mode 100644
index 000000000..1da29ef87
--- /dev/null
+++ b/components/printing/public/PPrintProgressDialog.ipdl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 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 protocol PPrinting;
+
+namespace mozilla {
+namespace embedding {
+
+protocol PPrintProgressDialog
+{
+ manager PPrinting;
+
+parent:
+ async StateChange(long stateFlags,
+ nsresult status);
+
+ async ProgressChange(long curSelfProgress,
+ long maxSelfProgress,
+ long curTotalProgress,
+ long maxTotalProgress);
+
+ async DocTitleChange(nsString newTitle);
+
+ async DocURLChange(nsString newURL);
+
+ async __delete__();
+
+child:
+ async DialogOpened();
+};
+
+} // namespace embedding
+} // namespace mozilla
diff --git a/components/printing/public/PPrintSettingsDialog.ipdl b/components/printing/public/PPrintSettingsDialog.ipdl
new file mode 100644
index 000000000..3e436892e
--- /dev/null
+++ b/components/printing/public/PPrintSettingsDialog.ipdl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 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 PPrintingTypes;
+include protocol PPrinting;
+include protocol PRemotePrintJob;
+
+namespace mozilla {
+namespace embedding {
+
+// A PrintData for success, a failure nsresult for failure.
+union PrintDataOrNSResult
+{
+ PrintData;
+ nsresult;
+};
+
+protocol PPrintSettingsDialog
+{
+ manager PPrinting;
+
+child:
+ async __delete__(PrintDataOrNSResult result);
+};
+
+} // namespace embedding
+} // namespace mozilla
diff --git a/components/printing/public/PPrinting.ipdl b/components/printing/public/PPrinting.ipdl
new file mode 100644
index 000000000..0b4a1cf64
--- /dev/null
+++ b/components/printing/public/PPrinting.ipdl
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 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 PPrintingTypes;
+include protocol PContent;
+include protocol PBrowser;
+include protocol PPrintProgressDialog;
+include protocol PPrintSettingsDialog;
+include protocol PRemotePrintJob;
+
+namespace mozilla {
+namespace embedding {
+
+sync protocol PPrinting
+{
+ manager PContent;
+ manages PPrintProgressDialog;
+ manages PPrintSettingsDialog;
+ manages PRemotePrintJob;
+
+parent:
+ sync ShowProgress(PBrowser browser,
+ PPrintProgressDialog printProgressDialog,
+ nullable PRemotePrintJob remotePrintJob,
+ bool isForPrinting)
+ returns(bool notifyOnOpen,
+ nsresult rv);
+
+ async ShowPrintDialog(PPrintSettingsDialog dialog,
+ nullable PBrowser browser,
+ PrintData settings);
+
+ async PPrintProgressDialog();
+ async PPrintSettingsDialog();
+
+ sync SavePrintSettings(PrintData settings, bool usePrinterNamePrefix,
+ uint32_t flags)
+ returns(nsresult rv);
+
+child:
+ async PRemotePrintJob();
+ async __delete__();
+};
+
+} // namespace embedding
+} // namespace mozilla
diff --git a/components/printing/public/PPrintingTypes.ipdlh b/components/printing/public/PPrintingTypes.ipdlh
new file mode 100644
index 000000000..084047baf
--- /dev/null
+++ b/components/printing/public/PPrintingTypes.ipdlh
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 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 protocol PRemotePrintJob;
+
+namespace mozilla {
+namespace embedding {
+
+struct CStringKeyValue {
+ nsCString key;
+ nsCString value;
+};
+
+struct PrintData {
+ nullable PRemotePrintJob remotePrintJob;
+ int32_t startPageRange;
+ int32_t endPageRange;
+ double edgeTop;
+ double edgeLeft;
+ double edgeBottom;
+ double edgeRight;
+ double marginTop;
+ double marginLeft;
+ double marginBottom;
+ double marginRight;
+ double unwriteableMarginTop;
+ double unwriteableMarginLeft;
+ double unwriteableMarginBottom;
+ double unwriteableMarginRight;
+ double scaling;
+ bool printBGColors;
+ bool printBGImages;
+ short printRange;
+ nsString title;
+ nsString docURL;
+ nsString headerStrLeft;
+ nsString headerStrCenter;
+ nsString headerStrRight;
+ nsString footerStrLeft;
+ nsString footerStrCenter;
+ nsString footerStrRight;
+
+ short howToEnableFrameUI;
+ bool isCancelled;
+ short printFrameTypeUsage;
+ short printFrameType;
+ bool printSilent;
+ bool shrinkToFit;
+ bool showPrintProgress;
+
+ nsString paperName;
+ short paperData;
+ double paperWidth;
+ double paperHeight;
+ short paperSizeUnit;
+ bool printReversed;
+ bool printInColor;
+ int32_t orientation;
+ int32_t numCopies;
+ nsString printerName;
+ bool printToFile;
+ nsString toFileName;
+ short outputFormat;
+ int32_t printPageDelay;
+ int32_t resolution;
+ int32_t duplex;
+ bool isInitializedFromPrinter;
+ bool isInitializedFromPrefs;
+ int32_t optionFlags;
+
+ /* Windows-specific things */
+ nsString driverName;
+ nsString deviceName;
+ double printableWidthInInches;
+ double printableHeightInInches;
+ bool isFramesetDocument;
+ bool isFramesetFrameSelected;
+ bool isIFrameSelected;
+ bool isRangeSelection;
+ uint8_t[] devModeData;
+
+ /**
+ * GTK-specific things. Some of these might look like dupes of the
+ * information we're already passing, but the generalized settings that
+ * we hold in nsIPrintSettings don't map perfectly to GTK's GtkPrintSettings,
+ * so there are some nuances. GtkPrintSettings, for example, stores both an
+ * internal name for paper size, as well as the display name.
+ */
+ CStringKeyValue[] GTKPrintSettings;
+
+ /**
+ * OS X specific things.
+ */
+ nsString printJobName;
+ bool printAllPages;
+ bool mustCollate;
+ nsString disposition;
+ /** TODO: Is there an "unsigned short" primitive? **/
+ short pagesAcross;
+ short pagesDown;
+ double printTime;
+ bool detailedErrorReporting;
+ nsString faxNumber;
+ bool addHeaderAndFooter;
+ bool fileNameExtensionHidden;
+ /*
+ * Holds the scaling factor from the Print dialog when shrink
+ * to fit is not used. This is needed by the child when it
+ * isn't using remote printing. When shrink to fit is enabled
+ * (default), print dialog code ensures this value is 1.0.
+ */
+ float scalingFactor;
+ /*
+ * Scaling factor for converting from OS X native paper size
+ * units to inches.
+ */
+ float widthScale;
+ float heightScale;
+ double adjustedPaperWidth;
+ double adjustedPaperHeight;
+};
+
+} // namespace embedding
+} // namespace mozilla
diff --git a/components/printing/src/PrintDataUtils.cpp b/components/printing/src/PrintDataUtils.cpp
new file mode 100644
index 000000000..c6f1c45ff
--- /dev/null
+++ b/components/printing/src/PrintDataUtils.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 8; 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 "PrintDataUtils.h"
+#include "nsIPrintSettings.h"
+#include "nsIServiceManager.h"
+#include "nsIWebBrowserPrint.h"
+#include "nsXPIDLString.h"
+
+namespace mozilla {
+namespace embedding {
+
+/**
+ * MockWebBrowserPrint is a mostly useless implementation of nsIWebBrowserPrint,
+ * but wraps a PrintData so that it's able to return information to print
+ * settings dialogs that need an nsIWebBrowserPrint to interrogate.
+ */
+
+NS_IMPL_ISUPPORTS(MockWebBrowserPrint, nsIWebBrowserPrint);
+
+MockWebBrowserPrint::MockWebBrowserPrint(const PrintData &aData)
+ : mData(aData)
+{
+ MOZ_COUNT_CTOR(MockWebBrowserPrint);
+}
+
+MockWebBrowserPrint::~MockWebBrowserPrint()
+{
+ MOZ_COUNT_DTOR(MockWebBrowserPrint);
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetGlobalPrintSettings(nsIPrintSettings **aGlobalPrintSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetCurrentPrintSettings(nsIPrintSettings **aCurrentPrintSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetCurrentChildDOMWindow(mozIDOMWindowProxy **aCurrentPrintSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetDoingPrint(bool *aDoingPrint)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetDoingPrintPreview(bool *aDoingPrintPreview)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsFramesetDocument(bool *aIsFramesetDocument)
+{
+ *aIsFramesetDocument = mData.isFramesetDocument();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsFramesetFrameSelected(bool *aIsFramesetFrameSelected)
+{
+ *aIsFramesetFrameSelected = mData.isFramesetFrameSelected();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsIFrameSelected(bool *aIsIFrameSelected)
+{
+ *aIsIFrameSelected = mData.isIFrameSelected();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsRangeSelection(bool *aIsRangeSelection)
+{
+ *aIsRangeSelection = mData.isRangeSelection();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetPrintPreviewNumPages(int32_t *aPrintPreviewNumPages)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::Print(nsIPrintSettings* aThePrintSettings,
+ nsIWebProgressListener* aWPListener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::PrintPreview(nsIPrintSettings* aThePrintSettings,
+ mozIDOMWindowProxy* aChildDOMWin,
+ nsIWebProgressListener* aWPListener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::PrintPreviewNavigate(int16_t aNavType,
+ int32_t aPageNum)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::Cancel()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::EnumerateDocumentNames(uint32_t* aCount,
+ char16_t*** aResult)
+{
+ *aCount = 0;
+ *aResult = nullptr;
+
+ if (mData.printJobName().IsEmpty()) {
+ return NS_OK;
+ }
+
+ // The only consumer that cares about this is the OS X printing
+ // dialog, and even then, it only cares about the first document
+ // name. That's why we only send a single document name through
+ // PrintData.
+ char16_t** array = (char16_t**) moz_xmalloc(sizeof(char16_t*));
+ array[0] = ToNewUnicode(mData.printJobName());
+
+ *aCount = 1;
+ *aResult = array;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::ExitPrintPreview()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace embedding
+} // namespace mozilla
+
diff --git a/components/printing/src/PrintDataUtils.h b/components/printing/src/PrintDataUtils.h
new file mode 100644
index 000000000..df03233b7
--- /dev/null
+++ b/components/printing/src/PrintDataUtils.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_embedding_PrintDataUtils_h
+#define mozilla_embedding_PrintDataUtils_h
+
+#include "mozilla/embedding/PPrinting.h"
+#include "nsIWebBrowserPrint.h"
+
+/**
+ * nsIPrintSettings and nsIWebBrowserPrint information is sent back and forth
+ * across PPrinting via the PrintData struct. These are utilities for
+ * manipulating PrintData that can be used on either side of the communications
+ * channel.
+ */
+
+namespace mozilla {
+namespace embedding {
+
+class MockWebBrowserPrint final : public nsIWebBrowserPrint
+{
+public:
+ explicit MockWebBrowserPrint(const PrintData &aData);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBBROWSERPRINT
+
+private:
+ ~MockWebBrowserPrint();
+ PrintData mData;
+};
+
+} // namespace embedding
+} // namespace mozilla
+
+#endif
diff --git a/components/printing/src/PrintProgressDialogChild.cpp b/components/printing/src/PrintProgressDialogChild.cpp
new file mode 100644
index 000000000..a6dc5de4e
--- /dev/null
+++ b/components/printing/src/PrintProgressDialogChild.cpp
@@ -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/. */
+
+#include "mozilla/Unused.h"
+#include "nsIObserver.h"
+#include "PrintProgressDialogChild.h"
+
+class nsIWebProgress;
+class nsIRequest;
+
+using mozilla::Unused;
+
+namespace mozilla {
+namespace embedding {
+
+NS_IMPL_ISUPPORTS(PrintProgressDialogChild,
+ nsIWebProgressListener,
+ nsIPrintProgressParams)
+
+PrintProgressDialogChild::PrintProgressDialogChild(
+ nsIObserver* aOpenObserver) :
+ mOpenObserver(aOpenObserver)
+{
+ MOZ_COUNT_CTOR(PrintProgressDialogChild);
+}
+
+PrintProgressDialogChild::~PrintProgressDialogChild()
+{
+ // When the printing engine stops supplying information about printing
+ // progress, it'll drop references to us and destroy us. We need to signal
+ // the parent to decrement its refcount, as well as prevent it from attempting
+ // to contact us further.
+ Unused << Send__delete__(this);
+ MOZ_COUNT_DTOR(PrintProgressDialogChild);
+}
+
+bool
+PrintProgressDialogChild::RecvDialogOpened()
+{
+ // nsPrintEngine's observer, which we're reporting to here, doesn't care
+ // what gets passed as the subject, topic or data, so we'll just send
+ // nullptrs.
+ mOpenObserver->Observe(nullptr, nullptr, nullptr);
+ return true;
+}
+
+// nsIWebProgressListener
+
+NS_IMETHODIMP
+PrintProgressDialogChild::OnStateChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ Unused << SendStateChange(aStateFlags, aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PrintProgressDialogChild::OnProgressChange(nsIWebProgress * aProgress,
+ nsIRequest * aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ Unused << SendProgressChange(aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PrintProgressDialogChild::OnLocationChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ nsIURI* aURI,
+ uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PrintProgressDialogChild::OnStatusChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PrintProgressDialogChild::OnSecurityChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ uint32_t aState)
+{
+ return NS_OK;
+}
+
+// nsIPrintProgressParams
+
+NS_IMETHODIMP PrintProgressDialogChild::GetDocTitle(char16_t* *aDocTitle)
+{
+ NS_ENSURE_ARG(aDocTitle);
+
+ *aDocTitle = ToNewUnicode(mDocTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP PrintProgressDialogChild::SetDocTitle(const char16_t* aDocTitle)
+{
+ mDocTitle = aDocTitle;
+ Unused << SendDocTitleChange(nsString(aDocTitle));
+ return NS_OK;
+}
+
+NS_IMETHODIMP PrintProgressDialogChild::GetDocURL(char16_t **aDocURL)
+{
+ NS_ENSURE_ARG(aDocURL);
+
+ *aDocURL = ToNewUnicode(mDocURL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP PrintProgressDialogChild::SetDocURL(const char16_t* aDocURL)
+{
+ mDocURL = aDocURL;
+ Unused << SendDocURLChange(nsString(aDocURL));
+ return NS_OK;
+}
+
+} // namespace embedding
+} // namespace mozilla
diff --git a/components/printing/src/PrintProgressDialogChild.h b/components/printing/src/PrintProgressDialogChild.h
new file mode 100644
index 000000000..b0a7105d7
--- /dev/null
+++ b/components/printing/src/PrintProgressDialogChild.h
@@ -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/. */
+
+#ifndef mozilla_embedding_PrintProgressDialogChild_h
+#define mozilla_embedding_PrintProgressDialogChild_h
+
+#include "mozilla/embedding/PPrintProgressDialogChild.h"
+#include "nsIPrintProgressParams.h"
+#include "nsIWebProgressListener.h"
+
+class nsIObserver;
+
+namespace mozilla {
+namespace embedding {
+
+class PrintProgressDialogChild final : public PPrintProgressDialogChild,
+ public nsIWebProgressListener,
+ public nsIPrintProgressParams
+{
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIPRINTPROGRESSPARAMS
+
+public:
+ MOZ_IMPLICIT PrintProgressDialogChild(nsIObserver* aOpenObserver);
+
+ virtual bool RecvDialogOpened() override;
+
+private:
+ virtual ~PrintProgressDialogChild();
+ nsCOMPtr<nsIObserver> mOpenObserver;
+ nsString mDocTitle;
+ nsString mDocURL;
+};
+
+} // namespace embedding
+} // namespace mozilla
+
+#endif
diff --git a/components/printing/src/PrintProgressDialogParent.cpp b/components/printing/src/PrintProgressDialogParent.cpp
new file mode 100644
index 000000000..6e34704ff
--- /dev/null
+++ b/components/printing/src/PrintProgressDialogParent.cpp
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Unused.h"
+#include "nsIPrintProgressParams.h"
+#include "nsIWebProgressListener.h"
+#include "PrintProgressDialogParent.h"
+
+using mozilla::Unused;
+
+namespace mozilla {
+namespace embedding {
+
+NS_IMPL_ISUPPORTS(PrintProgressDialogParent, nsIObserver)
+
+PrintProgressDialogParent::PrintProgressDialogParent() :
+ mActive(true)
+{
+ MOZ_COUNT_CTOR(PrintProgressDialogParent);
+}
+
+PrintProgressDialogParent::~PrintProgressDialogParent()
+{
+ MOZ_COUNT_DTOR(PrintProgressDialogParent);
+}
+
+void
+PrintProgressDialogParent::SetWebProgressListener(nsIWebProgressListener* aListener)
+{
+ mWebProgressListener = aListener;
+}
+
+void
+PrintProgressDialogParent::SetPrintProgressParams(nsIPrintProgressParams* aParams)
+{
+ mPrintProgressParams = aParams;
+}
+
+bool
+PrintProgressDialogParent::RecvStateChange(const long& stateFlags,
+ const nsresult& status)
+{
+ if (mWebProgressListener) {
+ mWebProgressListener->OnStateChange(nullptr, nullptr, stateFlags, status);
+ }
+ return true;
+}
+
+bool
+PrintProgressDialogParent::RecvProgressChange(const long& curSelfProgress,
+ const long& maxSelfProgress,
+ const long& curTotalProgress,
+ const long& maxTotalProgress)
+{
+ if (mWebProgressListener) {
+ mWebProgressListener->OnProgressChange(nullptr, nullptr, curSelfProgress,
+ maxSelfProgress, curTotalProgress,
+ maxTotalProgress);
+ }
+ return true;
+}
+
+bool
+PrintProgressDialogParent::RecvDocTitleChange(const nsString& newTitle)
+{
+ if (mPrintProgressParams) {
+ mPrintProgressParams->SetDocTitle(newTitle.get());
+ }
+ return true;
+}
+
+bool
+PrintProgressDialogParent::RecvDocURLChange(const nsString& newURL)
+{
+ if (mPrintProgressParams) {
+ mPrintProgressParams->SetDocURL(newURL.get());
+ }
+ return true;
+}
+
+void
+PrintProgressDialogParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
+
+bool
+PrintProgressDialogParent::Recv__delete__()
+{
+ // The child has requested that we tear down the connection, so we set a
+ // member to make sure we don't try to contact it after the fact.
+ mActive = false;
+ return true;
+}
+
+// nsIObserver
+NS_IMETHODIMP
+PrintProgressDialogParent::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ if (mActive) {
+ Unused << SendDialogOpened();
+ } else {
+ NS_WARNING("The print progress dialog finished opening, but communications "
+ "with the child have been closed.");
+ }
+
+ return NS_OK;
+}
+
+
+} // namespace embedding
+} // namespace mozilla
diff --git a/components/printing/src/PrintProgressDialogParent.h b/components/printing/src/PrintProgressDialogParent.h
new file mode 100644
index 000000000..540609162
--- /dev/null
+++ b/components/printing/src/PrintProgressDialogParent.h
@@ -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/. */
+
+#ifndef mozilla_embedding_PrintProgressDialogParent_h
+#define mozilla_embedding_PrintProgressDialogParent_h
+
+#include "mozilla/embedding/PPrintProgressDialogParent.h"
+#include "nsIObserver.h"
+
+class nsIPrintProgressParams;
+class nsIWebProgressListener;
+
+namespace mozilla {
+namespace embedding {
+class PrintProgressDialogParent final : public PPrintProgressDialogParent,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ MOZ_IMPLICIT PrintProgressDialogParent();
+
+ void SetWebProgressListener(nsIWebProgressListener* aListener);
+
+ void SetPrintProgressParams(nsIPrintProgressParams* aParams);
+
+ virtual bool
+ RecvStateChange(
+ const long& stateFlags,
+ const nsresult& status) override;
+
+ virtual bool
+ RecvProgressChange(
+ const long& curSelfProgress,
+ const long& maxSelfProgress,
+ const long& curTotalProgress,
+ const long& maxTotalProgress) override;
+
+ virtual bool
+ RecvDocTitleChange(const nsString& newTitle) override;
+
+ virtual bool
+ RecvDocURLChange(const nsString& newURL) override;
+
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual bool
+ Recv__delete__() override;
+
+private:
+ virtual ~PrintProgressDialogParent();
+
+ nsCOMPtr<nsIWebProgressListener> mWebProgressListener;
+ nsCOMPtr<nsIPrintProgressParams> mPrintProgressParams;
+ bool mActive;
+};
+
+} // namespace embedding
+} // namespace mozilla
+
+#endif
diff --git a/components/printing/src/PrintSettingsDialogChild.cpp b/components/printing/src/PrintSettingsDialogChild.cpp
new file mode 100644
index 000000000..326a3657d
--- /dev/null
+++ b/components/printing/src/PrintSettingsDialogChild.cpp
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Unused.h"
+#include "PrintSettingsDialogChild.h"
+
+using mozilla::Unused;
+
+namespace mozilla {
+namespace embedding {
+
+PrintSettingsDialogChild::PrintSettingsDialogChild()
+: mReturned(false)
+{
+ MOZ_COUNT_CTOR(PrintSettingsDialogChild);
+}
+
+PrintSettingsDialogChild::~PrintSettingsDialogChild()
+{
+ MOZ_COUNT_DTOR(PrintSettingsDialogChild);
+}
+
+bool
+PrintSettingsDialogChild::Recv__delete__(const PrintDataOrNSResult& aData)
+{
+ if (aData.type() == PrintDataOrNSResult::Tnsresult) {
+ mResult = aData.get_nsresult();
+ MOZ_ASSERT(NS_FAILED(mResult), "expected a failure result");
+ } else {
+ mResult = NS_OK;
+ mData = aData.get_PrintData();
+ }
+ mReturned = true;
+ return true;
+}
+
+} // namespace embedding
+} // namespace mozilla
diff --git a/components/printing/src/PrintSettingsDialogChild.h b/components/printing/src/PrintSettingsDialogChild.h
new file mode 100644
index 000000000..6db60e221
--- /dev/null
+++ b/components/printing/src/PrintSettingsDialogChild.h
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_embedding_PrintSettingsDialogChild_h
+#define mozilla_embedding_PrintSettingsDialogChild_h
+
+#include "mozilla/embedding/PPrintSettingsDialogChild.h"
+namespace mozilla {
+namespace embedding {
+
+class PrintSettingsDialogChild final : public PPrintSettingsDialogChild
+{
+ NS_INLINE_DECL_REFCOUNTING(PrintSettingsDialogChild)
+
+public:
+ MOZ_IMPLICIT PrintSettingsDialogChild();
+
+ virtual bool Recv__delete__(const PrintDataOrNSResult& aData) override;
+
+ bool returned() { return mReturned; };
+ nsresult result() { return mResult; };
+ PrintData data() { return mData; };
+
+private:
+ virtual ~PrintSettingsDialogChild();
+ bool mReturned;
+ nsresult mResult;
+ PrintData mData;
+};
+
+} // namespace embedding
+} // namespace mozilla
+
+#endif
diff --git a/components/printing/src/PrintSettingsDialogParent.cpp b/components/printing/src/PrintSettingsDialogParent.cpp
new file mode 100644
index 000000000..016677d96
--- /dev/null
+++ b/components/printing/src/PrintSettingsDialogParent.cpp
@@ -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/. */
+
+#include "PrintSettingsDialogParent.h"
+
+// C++ file contents
+namespace mozilla {
+namespace embedding {
+
+PrintSettingsDialogParent::PrintSettingsDialogParent()
+{
+ MOZ_COUNT_CTOR(PrintSettingsDialogParent);
+}
+
+PrintSettingsDialogParent::~PrintSettingsDialogParent()
+{
+ MOZ_COUNT_DTOR(PrintSettingsDialogParent);
+}
+
+void
+PrintSettingsDialogParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
+
+} // namespace embedding
+} // namespace mozilla
+
diff --git a/components/printing/src/PrintSettingsDialogParent.h b/components/printing/src/PrintSettingsDialogParent.h
new file mode 100644
index 000000000..e655903e7
--- /dev/null
+++ b/components/printing/src/PrintSettingsDialogParent.h
@@ -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/. */
+
+#ifndef mozilla_embedding_PrintSettingsDialogParent_h
+#define mozilla_embedding_PrintSettingsDialogParent_h
+
+#include "mozilla/embedding/PPrintSettingsDialogParent.h"
+
+// Header file contents
+namespace mozilla {
+namespace embedding {
+
+class PrintSettingsDialogParent final : public PPrintSettingsDialogParent
+{
+public:
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy) override;
+
+ MOZ_IMPLICIT PrintSettingsDialogParent();
+
+private:
+ virtual ~PrintSettingsDialogParent();
+};
+
+} // namespace embedding
+} // namespace mozilla
+
+#endif
diff --git a/components/printing/src/PrintingParent.cpp b/components/printing/src/PrintingParent.cpp
new file mode 100644
index 000000000..f81ff780c
--- /dev/null
+++ b/components/printing/src/PrintingParent.cpp
@@ -0,0 +1,335 @@
+/* -*- Mode: C++; tab-width: 8; 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/dom/Element.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIDOMWindow.h"
+#include "nsIPrintingPromptService.h"
+#include "nsIPrintProgressParams.h"
+#include "nsIPrintSettingsService.h"
+#include "nsIServiceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIWebProgressListener.h"
+#include "PrintingParent.h"
+#include "PrintDataUtils.h"
+#include "PrintProgressDialogParent.h"
+#include "PrintSettingsDialogParent.h"
+#include "mozilla/layout/RemotePrintJobParent.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layout;
+
+namespace mozilla {
+namespace embedding {
+bool
+PrintingParent::RecvShowProgress(PBrowserParent* parent,
+ PPrintProgressDialogParent* printProgressDialog,
+ PRemotePrintJobParent* remotePrintJob,
+ const bool& isForPrinting,
+ bool* notifyOnOpen,
+ nsresult* result)
+{
+ *result = NS_ERROR_FAILURE;
+ *notifyOnOpen = false;
+
+ nsCOMPtr<nsPIDOMWindowOuter> parentWin = DOMWindowFromBrowserParent(parent);
+ if (!parentWin) {
+ return true;
+ }
+
+ nsCOMPtr<nsIPrintingPromptService> pps(do_GetService("@mozilla.org/embedcomp/printingprompt-service;1"));
+
+ if (!pps) {
+ return true;
+ }
+
+ PrintProgressDialogParent* dialogParent =
+ static_cast<PrintProgressDialogParent*>(printProgressDialog);
+ nsCOMPtr<nsIObserver> observer = do_QueryInterface(dialogParent);
+
+ nsCOMPtr<nsIWebProgressListener> printProgressListener;
+ nsCOMPtr<nsIPrintProgressParams> printProgressParams;
+
+ *result = pps->ShowProgress(parentWin, nullptr, nullptr, observer,
+ isForPrinting,
+ getter_AddRefs(printProgressListener),
+ getter_AddRefs(printProgressParams),
+ notifyOnOpen);
+ NS_ENSURE_SUCCESS(*result, true);
+
+ if (remotePrintJob) {
+ // If we have a RemotePrintJob use that as a more general forwarder for
+ // print progress listeners.
+ static_cast<RemotePrintJobParent*>(remotePrintJob)
+ ->RegisterListener(printProgressListener);
+ } else {
+ dialogParent->SetWebProgressListener(printProgressListener);
+ }
+
+ dialogParent->SetPrintProgressParams(printProgressParams);
+
+ return true;
+}
+
+nsresult
+PrintingParent::ShowPrintDialog(PBrowserParent* aParent,
+ const PrintData& aData,
+ PrintData* aResult)
+{
+ // If aParent is null this call is just being used to get print settings from
+ // the printer for print preview.
+ bool isPrintPreview = !aParent;
+ nsCOMPtr<nsPIDOMWindowOuter> parentWin;
+ if (aParent) {
+ parentWin = DOMWindowFromBrowserParent(aParent);
+ if (!parentWin) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsCOMPtr<nsIPrintingPromptService> pps(do_GetService("@mozilla.org/embedcomp/printingprompt-service;1"));
+ if (!pps) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // The initSettings we got can be wrapped using
+ // PrintDataUtils' MockWebBrowserPrint, which implements enough of
+ // nsIWebBrowserPrint to keep the dialogs happy.
+ nsCOMPtr<nsIWebBrowserPrint> wbp = new MockWebBrowserPrint(aData);
+
+ // Use the existing RemotePrintJob and its settings, if we have one, to make
+ // sure they stay current.
+ RemotePrintJobParent* remotePrintJob =
+ static_cast<RemotePrintJobParent*>(aData.remotePrintJobParent());
+ nsCOMPtr<nsIPrintSettings> settings;
+ nsresult rv;
+ if (remotePrintJob) {
+ settings = remotePrintJob->GetPrintSettings();
+ } else {
+ rv = mPrintSettingsSvc->GetNewPrintSettings(getter_AddRefs(settings));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We only want to use the print silently setting from the parent.
+ bool printSilently;
+ rv = settings->GetPrintSilent(&printSilently);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mPrintSettingsSvc->DeserializeToPrintSettings(aData, settings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = settings->SetPrintSilent(printSilently);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If this is for print preview or we are printing silently then we just need
+ // to initialize the print settings with anything specific from the printer.
+ if (isPrintPreview || printSilently ||
+ Preferences::GetBool("print.always_print_silent", printSilently)) {
+ nsXPIDLString printerName;
+ rv = settings->GetPrinterName(getter_Copies(printerName));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ settings->SetIsInitializedFromPrinter(false);
+ mPrintSettingsSvc->InitPrintSettingsFromPrinter(printerName, settings);
+ } else {
+ rv = pps->ShowPrintDialog(parentWin, wbp, settings);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (isPrintPreview) {
+ // For print preview we don't want a RemotePrintJob just the settings.
+ rv = mPrintSettingsSvc->SerializeToPrintData(settings, nullptr, aResult);
+ } else {
+ rv = SerializeAndEnsureRemotePrintJob(settings, nullptr, remotePrintJob,
+ aResult);
+ }
+
+ return rv;
+}
+
+bool
+PrintingParent::RecvShowPrintDialog(PPrintSettingsDialogParent* aDialog,
+ PBrowserParent* aParent,
+ const PrintData& aData)
+{
+ PrintData resultData;
+ nsresult rv = ShowPrintDialog(aParent, aData, &resultData);
+
+ // The child has been spinning an event loop while waiting
+ // to hear about the print settings. We return the results
+ // with an async message which frees the child process from
+ // its nested event loop.
+ if (NS_FAILED(rv)) {
+ mozilla::Unused << aDialog->Send__delete__(aDialog, rv);
+ } else {
+ mozilla::Unused << aDialog->Send__delete__(aDialog, resultData);
+ }
+ return true;
+}
+
+bool
+PrintingParent::RecvSavePrintSettings(const PrintData& aData,
+ const bool& aUsePrinterNamePrefix,
+ const uint32_t& aFlags,
+ nsresult* aResult)
+{
+ nsCOMPtr<nsIPrintSettings> settings;
+ *aResult = mPrintSettingsSvc->GetNewPrintSettings(getter_AddRefs(settings));
+ NS_ENSURE_SUCCESS(*aResult, true);
+
+ *aResult = mPrintSettingsSvc->DeserializeToPrintSettings(aData, settings);
+ NS_ENSURE_SUCCESS(*aResult, true);
+
+ *aResult = mPrintSettingsSvc->SavePrintSettingsToPrefs(settings,
+ aUsePrinterNamePrefix,
+ aFlags);
+
+ return true;
+}
+
+PPrintProgressDialogParent*
+PrintingParent::AllocPPrintProgressDialogParent()
+{
+ PrintProgressDialogParent* actor = new PrintProgressDialogParent();
+ NS_ADDREF(actor); // De-ref'd in the __delete__ handler for
+ // PrintProgressDialogParent.
+ return actor;
+}
+
+bool
+PrintingParent::DeallocPPrintProgressDialogParent(PPrintProgressDialogParent* doomed)
+{
+ // We can't just delete the PrintProgressDialogParent since somebody might
+ // still be holding a reference to it as nsIObserver, so just decrement the
+ // refcount instead.
+ PrintProgressDialogParent* actor = static_cast<PrintProgressDialogParent*>(doomed);
+ NS_RELEASE(actor);
+ return true;
+}
+
+PPrintSettingsDialogParent*
+PrintingParent::AllocPPrintSettingsDialogParent()
+{
+ return new PrintSettingsDialogParent();
+}
+
+bool
+PrintingParent::DeallocPPrintSettingsDialogParent(PPrintSettingsDialogParent* aDoomed)
+{
+ delete aDoomed;
+ return true;
+}
+
+PRemotePrintJobParent*
+PrintingParent::AllocPRemotePrintJobParent()
+{
+ MOZ_ASSERT_UNREACHABLE("No default constructors for implementations.");
+ return nullptr;
+}
+
+bool
+PrintingParent::DeallocPRemotePrintJobParent(PRemotePrintJobParent* aDoomed)
+{
+ delete aDoomed;
+ return true;
+}
+
+void
+PrintingParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+}
+
+nsPIDOMWindowOuter*
+PrintingParent::DOMWindowFromBrowserParent(PBrowserParent* parent)
+{
+ if (!parent) {
+ return nullptr;
+ }
+
+ TabParent* tabParent = TabParent::GetFrom(parent);
+ if (!tabParent) {
+ return nullptr;
+ }
+
+ nsCOMPtr<Element> frameElement = tabParent->GetOwnerElement();
+ if (!frameElement) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIContent> frame(do_QueryInterface(frameElement));
+ if (!frame) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> parentWin = frame->OwnerDoc()->GetWindow();
+ if (!parentWin) {
+ return nullptr;
+ }
+
+ return parentWin;
+}
+
+nsresult
+PrintingParent::SerializeAndEnsureRemotePrintJob(
+ nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aListener,
+ layout::RemotePrintJobParent* aRemotePrintJob, PrintData* aPrintData)
+{
+ MOZ_ASSERT(aPrintData);
+
+ nsresult rv;
+ nsCOMPtr<nsIPrintSettings> printSettings;
+ if (aPrintSettings) {
+ printSettings = aPrintSettings;
+ } else {
+ rv = mPrintSettingsSvc->GetNewPrintSettings(getter_AddRefs(printSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = mPrintSettingsSvc->SerializeToPrintData(printSettings, nullptr,
+ aPrintData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RemotePrintJobParent* remotePrintJob;
+ if (aRemotePrintJob) {
+ remotePrintJob = aRemotePrintJob;
+ aPrintData->remotePrintJobParent() = remotePrintJob;
+ } else {
+ remotePrintJob = new RemotePrintJobParent(aPrintSettings);
+ aPrintData->remotePrintJobParent() =
+ SendPRemotePrintJobConstructor(remotePrintJob);
+ }
+ if (aListener) {
+ remotePrintJob->RegisterListener(aListener);
+ }
+
+ return NS_OK;
+}
+
+PrintingParent::PrintingParent()
+{
+ MOZ_COUNT_CTOR(PrintingParent);
+
+ mPrintSettingsSvc =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ MOZ_ASSERT(mPrintSettingsSvc);
+}
+
+PrintingParent::~PrintingParent()
+{
+ MOZ_COUNT_DTOR(PrintingParent);
+}
+
+} // namespace embedding
+} // namespace mozilla
+
diff --git a/components/printing/src/PrintingParent.h b/components/printing/src/PrintingParent.h
new file mode 100644
index 000000000..1a46325bd
--- /dev/null
+++ b/components/printing/src/PrintingParent.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_embedding_PrintingParent_h
+#define mozilla_embedding_PrintingParent_h
+
+#include "mozilla/dom/PBrowserParent.h"
+#include "mozilla/embedding/PPrintingParent.h"
+
+class nsIPrintSettingsService;
+class nsIWebProgressListener;
+class nsPIDOMWindowOuter;
+class PPrintProgressDialogParent;
+class PPrintSettingsDialogParent;
+
+namespace mozilla {
+namespace layout {
+class PRemotePrintJobParent;
+class RemotePrintJobParent;
+}
+
+namespace embedding {
+
+class PrintingParent final : public PPrintingParent
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(PrintingParent)
+
+ virtual bool
+ RecvShowProgress(PBrowserParent* parent,
+ PPrintProgressDialogParent* printProgressDialog,
+ PRemotePrintJobParent* remotePrintJob,
+ const bool& isForPrinting,
+ bool* notifyOnOpen,
+ nsresult* result);
+ virtual bool
+ RecvShowPrintDialog(PPrintSettingsDialogParent* aDialog,
+ PBrowserParent* aParent,
+ const PrintData& aData);
+
+ virtual bool
+ RecvSavePrintSettings(const PrintData& data,
+ const bool& usePrinterNamePrefix,
+ const uint32_t& flags,
+ nsresult* rv);
+
+ virtual PPrintProgressDialogParent*
+ AllocPPrintProgressDialogParent();
+
+ virtual bool
+ DeallocPPrintProgressDialogParent(PPrintProgressDialogParent* aActor);
+
+ virtual PPrintSettingsDialogParent*
+ AllocPPrintSettingsDialogParent();
+
+ virtual bool
+ DeallocPPrintSettingsDialogParent(PPrintSettingsDialogParent* aActor);
+
+ virtual PRemotePrintJobParent*
+ AllocPRemotePrintJobParent();
+
+ virtual bool
+ DeallocPRemotePrintJobParent(PRemotePrintJobParent* aActor);
+
+ virtual void
+ ActorDestroy(ActorDestroyReason aWhy);
+
+ MOZ_IMPLICIT PrintingParent();
+
+ /**
+ * Serialize nsIPrintSettings to PrintData ready for sending to a child
+ * process. A RemotePrintJob will be created and added to the PrintData.
+ * An optional progress listener can be given, which will be registered
+ * with the RemotePrintJob, so that progress can be tracked in the parent.
+ *
+ * @param aPrintSettings optional print settings to serialize, otherwise a
+ * default print settings will be used.
+ * @param aProgressListener optional print progress listener.
+ * @param aRemotePrintJob optional remote print job, so that an existing
+ * one can be used.
+ * @param aPrintData PrintData to populate.
+ */
+ nsresult
+ SerializeAndEnsureRemotePrintJob(nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aListener,
+ layout::RemotePrintJobParent* aRemotePrintJob,
+ PrintData* aPrintData);
+
+private:
+ virtual ~PrintingParent();
+
+ nsPIDOMWindowOuter*
+ DOMWindowFromBrowserParent(PBrowserParent* parent);
+
+ nsresult
+ ShowPrintDialog(PBrowserParent* parent,
+ const PrintData& data,
+ PrintData* result);
+
+ nsCOMPtr<nsIPrintSettingsService> mPrintSettingsSvc;
+};
+
+} // namespace embedding
+} // namespace mozilla
+
+#endif
diff --git a/components/printing/src/nsPrintingProxy.cpp b/components/printing/src/nsPrintingProxy.cpp
new file mode 100644
index 000000000..353be04ee
--- /dev/null
+++ b/components/printing/src/nsPrintingProxy.cpp
@@ -0,0 +1,269 @@
+/* -*- 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 "nsPrintingProxy.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/layout/RemotePrintJobChild.h"
+#include "mozilla/Unused.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIPrintingPromptService.h"
+#include "nsIPrintSession.h"
+#include "nsPIDOMWindow.h"
+#include "nsPrintOptionsImpl.h"
+#include "nsServiceManagerUtils.h"
+#include "PrintDataUtils.h"
+#include "PrintProgressDialogChild.h"
+#include "PrintSettingsDialogChild.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::embedding;
+using namespace mozilla::layout;
+
+static StaticRefPtr<nsPrintingProxy> sPrintingProxyInstance;
+
+NS_IMPL_ISUPPORTS(nsPrintingProxy, nsIPrintingPromptService)
+
+nsPrintingProxy::nsPrintingProxy()
+{
+}
+
+nsPrintingProxy::~nsPrintingProxy()
+{
+}
+
+/* static */
+already_AddRefed<nsPrintingProxy>
+nsPrintingProxy::GetInstance()
+{
+ if (!sPrintingProxyInstance) {
+ sPrintingProxyInstance = new nsPrintingProxy();
+ if (!sPrintingProxyInstance) {
+ return nullptr;
+ }
+ nsresult rv = sPrintingProxyInstance->Init();
+ if (NS_FAILED(rv)) {
+ sPrintingProxyInstance = nullptr;
+ return nullptr;
+ }
+ ClearOnShutdown(&sPrintingProxyInstance);
+ }
+
+ RefPtr<nsPrintingProxy> inst = sPrintingProxyInstance.get();
+ return inst.forget();
+}
+
+nsresult
+nsPrintingProxy::Init()
+{
+ mozilla::Unused << ContentChild::GetSingleton()->SendPPrintingConstructor(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintingProxy::ShowPrintDialog(mozIDOMWindowProxy *parent,
+ nsIWebBrowserPrint *webBrowserPrint,
+ nsIPrintSettings *printSettings)
+{
+ NS_ENSURE_ARG(webBrowserPrint);
+ NS_ENSURE_ARG(printSettings);
+
+ // If parent is null we are just being called to retrieve the print settings
+ // from the printer in the parent for print preview.
+ TabChild* pBrowser = nullptr;
+ if (parent) {
+ // Get the TabChild for this nsIDOMWindow, which we can then pass up to
+ // the parent.
+ nsCOMPtr<nsPIDOMWindowOuter> pwin = nsPIDOMWindowOuter::From(parent);
+ NS_ENSURE_STATE(pwin);
+ nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell();
+ NS_ENSURE_STATE(docShell);
+
+ nsCOMPtr<nsITabChild> tabchild = docShell->GetTabChild();
+ NS_ENSURE_STATE(tabchild);
+
+ pBrowser = static_cast<TabChild*>(tabchild.get());
+ }
+
+ // Next, serialize the nsIWebBrowserPrint and nsIPrintSettings we were given.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PrintData inSettings;
+ rv = printSettingsSvc->SerializeToPrintData(printSettings, webBrowserPrint,
+ &inSettings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now, the waiting game. The parent process should be showing
+ // the printing dialog soon. In the meantime, we need to spin a
+ // nested event loop while we wait for the results of the dialog
+ // to be returned to us.
+
+ RefPtr<PrintSettingsDialogChild> dialog = new PrintSettingsDialogChild();
+ SendPPrintSettingsDialogConstructor(dialog);
+
+ mozilla::Unused << SendShowPrintDialog(dialog, pBrowser, inSettings);
+
+ while(!dialog->returned()) {
+ NS_ProcessNextEvent(nullptr, true);
+ }
+
+ rv = dialog->result();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = printSettingsSvc->DeserializeToPrintSettings(dialog->data(),
+ printSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintingProxy::ShowProgress(mozIDOMWindowProxy* parent,
+ nsIWebBrowserPrint* webBrowserPrint, // ok to be null
+ nsIPrintSettings* printSettings, // ok to be null
+ nsIObserver* openDialogObserver, // ok to be null
+ bool isForPrinting,
+ nsIWebProgressListener** webProgressListener,
+ nsIPrintProgressParams** printProgressParams,
+ bool* notifyOnOpen)
+{
+ NS_ENSURE_ARG(parent);
+ NS_ENSURE_ARG(webProgressListener);
+ NS_ENSURE_ARG(printProgressParams);
+ NS_ENSURE_ARG(notifyOnOpen);
+
+ // Get the TabChild for this nsIDOMWindow, which we can then pass up to
+ // the parent.
+ nsCOMPtr<nsPIDOMWindowOuter> pwin = nsPIDOMWindowOuter::From(parent);
+ NS_ENSURE_STATE(pwin);
+ nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell();
+ NS_ENSURE_STATE(docShell);
+ nsCOMPtr<nsITabChild> tabchild = docShell->GetTabChild();
+ TabChild* pBrowser = static_cast<TabChild*>(tabchild.get());
+
+ RefPtr<PrintProgressDialogChild> dialogChild =
+ new PrintProgressDialogChild(openDialogObserver);
+
+ SendPPrintProgressDialogConstructor(dialogChild);
+
+ // Get the RemotePrintJob if we have one available.
+ RefPtr<mozilla::layout::RemotePrintJobChild> remotePrintJob;
+ if (printSettings) {
+ nsCOMPtr<nsIPrintSession> printSession;
+ nsresult rv = printSettings->GetPrintSession(getter_AddRefs(printSession));
+ if (NS_SUCCEEDED(rv) && printSession) {
+ printSession->GetRemotePrintJob(getter_AddRefs(remotePrintJob));
+ }
+ }
+
+ nsresult rv = NS_OK;
+ mozilla::Unused << SendShowProgress(pBrowser, dialogChild, remotePrintJob,
+ isForPrinting, notifyOnOpen, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If we have a RemotePrintJob that will be being used as a more general
+ // forwarder for print progress listeners. Once we always have one we can
+ // remove the interface from PrintProgressDialogChild.
+ if (!remotePrintJob) {
+ NS_ADDREF(*webProgressListener = dialogChild);
+ }
+ NS_ADDREF(*printProgressParams = dialogChild);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintingProxy::ShowPageSetup(mozIDOMWindowProxy *parent,
+ nsIPrintSettings *printSettings,
+ nsIObserver *aObs)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPrintingProxy::ShowPrinterProperties(mozIDOMWindowProxy *parent,
+ const char16_t *printerName,
+ nsIPrintSettings *printSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsPrintingProxy::SavePrintSettings(nsIPrintSettings* aPS,
+ bool aUsePrinterNamePrefix,
+ uint32_t aFlags)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PrintData settings;
+ rv = printSettingsSvc->SerializeToPrintData(aPS, nullptr, &settings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Unused << SendSavePrintSettings(settings, aUsePrinterNamePrefix, aFlags,
+ &rv);
+ return rv;
+}
+
+PPrintProgressDialogChild*
+nsPrintingProxy::AllocPPrintProgressDialogChild()
+{
+ // The parent process will never initiate the PPrintProgressDialog
+ // protocol connection, so no need to provide an allocator here.
+ NS_NOTREACHED("Allocator for PPrintProgressDialogChild should not be "
+ "called on nsPrintingProxy.");
+ return nullptr;
+}
+
+bool
+nsPrintingProxy::DeallocPPrintProgressDialogChild(PPrintProgressDialogChild* aActor)
+{
+ // The PrintProgressDialogChild implements refcounting, and
+ // will take itself out.
+ return true;
+}
+
+PPrintSettingsDialogChild*
+nsPrintingProxy::AllocPPrintSettingsDialogChild()
+{
+ // The parent process will never initiate the PPrintSettingsDialog
+ // protocol connection, so no need to provide an allocator here.
+ NS_NOTREACHED("Allocator for PPrintSettingsDialogChild should not be "
+ "called on nsPrintingProxy.");
+ return nullptr;
+}
+
+bool
+nsPrintingProxy::DeallocPPrintSettingsDialogChild(PPrintSettingsDialogChild* aActor)
+{
+ // The PrintSettingsDialogChild implements refcounting, and
+ // will take itself out.
+ return true;
+}
+
+PRemotePrintJobChild*
+nsPrintingProxy::AllocPRemotePrintJobChild()
+{
+ RefPtr<RemotePrintJobChild> remotePrintJob = new RemotePrintJobChild();
+ return remotePrintJob.forget().take();
+}
+
+bool
+nsPrintingProxy::DeallocPRemotePrintJobChild(PRemotePrintJobChild* aDoomed)
+{
+ RemotePrintJobChild* remotePrintJob = static_cast<RemotePrintJobChild*>(aDoomed);
+ NS_RELEASE(remotePrintJob);
+ return true;
+}
diff --git a/components/printing/src/nsPrintingProxy.h b/components/printing/src/nsPrintingProxy.h
new file mode 100644
index 000000000..6b4d9cf5a
--- /dev/null
+++ b/components/printing/src/nsPrintingProxy.h
@@ -0,0 +1,57 @@
+/* -*- 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 __nsPrintingProxy_h
+#define __nsPrintingProxy_h
+
+#include "nsIPrintingPromptService.h"
+#include "mozilla/embedding/PPrintingChild.h"
+
+namespace mozilla {
+namespace layout {
+class PRemotePrintJobChild;
+}
+}
+
+class nsPrintingProxy: public nsIPrintingPromptService,
+ public mozilla::embedding::PPrintingChild
+{
+ virtual ~nsPrintingProxy();
+
+public:
+ nsPrintingProxy();
+
+ static already_AddRefed<nsPrintingProxy> GetInstance();
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTINGPROMPTSERVICE
+
+ nsresult SavePrintSettings(nsIPrintSettings* aPS,
+ bool aUsePrinterNamePrefix,
+ uint32_t aFlags);
+
+ virtual PPrintProgressDialogChild*
+ AllocPPrintProgressDialogChild() override;
+
+ virtual bool
+ DeallocPPrintProgressDialogChild(PPrintProgressDialogChild* aActor) override;
+
+ virtual PPrintSettingsDialogChild*
+ AllocPPrintSettingsDialogChild() override;
+
+ virtual bool
+ DeallocPPrintSettingsDialogChild(PPrintSettingsDialogChild* aActor) override;
+
+ virtual PRemotePrintJobChild*
+ AllocPRemotePrintJobChild() override;
+
+ virtual bool
+ DeallocPRemotePrintJobChild(PRemotePrintJobChild* aActor) override;
+};
+
+#endif
+
diff --git a/components/printing/src/unix/moz.build b/components/printing/src/unix/moz.build
new file mode 100644
index 000000000..89e88bead
--- /dev/null
+++ b/components/printing/src/unix/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+SOURCES += [
+ 'nsPrintingPromptService.cpp',
+ 'nsPrintProgress.cpp',
+ 'nsPrintProgressParams.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/printing/src/unix/nsPrintProgress.cpp b/components/printing/src/unix/nsPrintProgress.cpp
new file mode 100644
index 000000000..1a871c008
--- /dev/null
+++ b/components/printing/src/unix/nsPrintProgress.cpp
@@ -0,0 +1,267 @@
+/* -*- 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 "nsPrintProgress.h"
+
+#include "nsArray.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIXULWindow.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIComponentManager.h"
+#include "nsPIDOMWindow.h"
+
+
+NS_IMPL_ADDREF(nsPrintProgress)
+NS_IMPL_RELEASE(nsPrintProgress)
+
+NS_INTERFACE_MAP_BEGIN(nsPrintProgress)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrintStatusFeedback)
+ NS_INTERFACE_MAP_ENTRY(nsIPrintProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIPrintStatusFeedback)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+
+nsPrintProgress::nsPrintProgress(nsIPrintSettings* aPrintSettings)
+{
+ m_closeProgress = false;
+ m_processCanceled = false;
+ m_pendingStateFlags = -1;
+ m_pendingStateValue = NS_OK;
+ m_PrintSetting = aPrintSettings;
+}
+
+nsPrintProgress::~nsPrintProgress()
+{
+ (void)ReleaseListeners();
+}
+
+NS_IMETHODIMP nsPrintProgress::OpenProgressDialog(mozIDOMWindowProxy *parent,
+ const char *dialogURL,
+ nsISupports *parameters,
+ nsIObserver *openDialogObserver,
+ bool *notifyOnOpen)
+{
+ *notifyOnOpen = true;
+ m_observer = openDialogObserver;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (m_dialog)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ if (!dialogURL || !*dialogURL)
+ return NS_ERROR_INVALID_ARG;
+
+ if (parent)
+ {
+ // Set up window.arguments[0]...
+ nsCOMPtr<nsIMutableArray> array = nsArray::Create();
+
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ifptr->SetData(static_cast<nsIPrintProgress*>(this));
+ ifptr->SetDataIID(&NS_GET_IID(nsIPrintProgress));
+
+ array->AppendElement(ifptr, /*weak =*/ false);
+
+ array->AppendElement(parameters, /*weak =*/ false);
+
+ // We will set the opener of the dialog to be the nsIDOMWindow for the
+ // browser XUL window itself, as opposed to the content. That way, the
+ // progress window has access to the opener.
+ auto* pParentWindow = nsPIDOMWindowOuter::From(parent);
+ nsCOMPtr<nsIDocShell> docShell = pParentWindow->GetDocShell();
+ NS_ENSURE_STATE(docShell);
+
+ nsCOMPtr<nsIDocShellTreeOwner> owner;
+ docShell->GetTreeOwner(getter_AddRefs(owner));
+
+ nsCOMPtr<nsIXULWindow> ownerXULWindow = do_GetInterface(owner);
+ nsCOMPtr<mozIDOMWindowProxy> ownerWindow = do_GetInterface(ownerXULWindow);
+ NS_ENSURE_STATE(ownerWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> piOwnerWindow = nsPIDOMWindowOuter::From(ownerWindow);
+
+ // Open the dialog.
+ nsCOMPtr<nsPIDOMWindowOuter> newWindow;
+
+ rv = piOwnerWindow->OpenDialog(NS_ConvertASCIItoUTF16(dialogURL),
+ NS_LITERAL_STRING("_blank"),
+ NS_LITERAL_STRING("chrome,titlebar,dependent,centerscreen"),
+ array, getter_AddRefs(newWindow));
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsPrintProgress::CloseProgressDialog(bool forceClose)
+{
+ m_closeProgress = true;
+ // XXX Invalid cast of bool to nsresult (bug 778106)
+ return OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP,
+ (nsresult)forceClose);
+}
+
+NS_IMETHODIMP nsPrintProgress::GetPrompter(nsIPrompt **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ if (! m_closeProgress && m_dialog) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(m_dialog);
+ MOZ_ASSERT(window);
+ return window->GetPrompter(_retval);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsPrintProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser)
+{
+ NS_ENSURE_ARG_POINTER(aProcessCanceledByUser);
+ *aProcessCanceledByUser = m_processCanceled;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser)
+{
+ if(m_PrintSetting)
+ m_PrintSetting->SetIsCancelled(true);
+ m_processCanceled = aProcessCanceledByUser;
+ OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::RegisterListener(nsIWebProgressListener * listener)
+{
+ if (!listener) //Nothing to do with a null listener!
+ return NS_OK;
+
+ m_listenerList.AppendObject(listener);
+ if (m_closeProgress || m_processCanceled)
+ listener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK);
+ else
+ {
+ listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get());
+ if (m_pendingStateFlags != -1)
+ listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::UnregisterListener(nsIWebProgressListener *listener)
+{
+ if (listener)
+ m_listenerList.RemoveObject(listener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::DoneIniting()
+{
+ if (m_observer) {
+ m_observer->Observe(nullptr, nullptr, nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus)
+{
+ m_pendingStateFlags = aStateFlags;
+ m_pendingStateValue = aStatus;
+
+ uint32_t count = m_listenerList.Count();
+ for (uint32_t i = count - 1; i < count; i --)
+ {
+ nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i);
+ if (progressListener)
+ progressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress)
+{
+ uint32_t count = m_listenerList.Count();
+ for (uint32_t i = count - 1; i < count; i --)
+ {
+ nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i);
+ if (progressListener)
+ progressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage)
+{
+ if (aMessage && *aMessage)
+ m_pendingStatus = aMessage;
+
+ uint32_t count = m_listenerList.Count();
+ for (uint32_t i = count - 1; i < count; i --)
+ {
+ nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i);
+ if (progressListener)
+ progressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state)
+{
+ return NS_OK;
+}
+
+nsresult nsPrintProgress::ReleaseListeners()
+{
+ m_listenerList.Clear();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::ShowStatusString(const char16_t *status)
+{
+ return OnStatusChange(nullptr, nullptr, NS_OK, status);
+}
+
+NS_IMETHODIMP nsPrintProgress::StartMeteors()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::StopMeteors()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::ShowProgress(int32_t percent)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::SetDocShell(nsIDocShell *shell, mozIDOMWindowProxy *window)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::CloseWindow()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
diff --git a/components/printing/src/unix/nsPrintProgress.h b/components/printing/src/unix/nsPrintProgress.h
new file mode 100644
index 000000000..5b60151c0
--- /dev/null
+++ b/components/printing/src/unix/nsPrintProgress.h
@@ -0,0 +1,46 @@
+/* -*- 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 __nsPrintProgress_h
+#define __nsPrintProgress_h
+
+#include "nsIPrintProgress.h"
+#include "nsIPrintingPromptService.h"
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMWindow.h"
+#include "nsIPrintStatusFeedback.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+
+class nsPrintProgress : public nsIPrintProgress, public nsIPrintStatusFeedback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPRINTPROGRESS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIPRINTSTATUSFEEDBACK
+
+ explicit nsPrintProgress(nsIPrintSettings* aPrintSettings);
+
+protected:
+ virtual ~nsPrintProgress();
+
+private:
+ nsresult ReleaseListeners();
+
+ bool m_closeProgress;
+ bool m_processCanceled;
+ nsString m_pendingStatus;
+ int32_t m_pendingStateFlags;
+ nsresult m_pendingStateValue;
+ nsCOMPtr<nsIDOMWindow> m_dialog;
+ nsCOMArray<nsIWebProgressListener> m_listenerList;
+ nsCOMPtr<nsIObserver> m_observer;
+ nsCOMPtr<nsIPrintSettings> m_PrintSetting;
+};
+
+#endif
diff --git a/components/printing/src/unix/nsPrintProgressParams.cpp b/components/printing/src/unix/nsPrintProgressParams.cpp
new file mode 100644
index 000000000..eba86b298
--- /dev/null
+++ b/components/printing/src/unix/nsPrintProgressParams.cpp
@@ -0,0 +1,47 @@
+/* -*- 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 "nsPrintProgressParams.h"
+#include "nsReadableUtils.h"
+
+
+NS_IMPL_ISUPPORTS(nsPrintProgressParams, nsIPrintProgressParams)
+
+nsPrintProgressParams::nsPrintProgressParams()
+{
+}
+
+nsPrintProgressParams::~nsPrintProgressParams()
+{
+}
+
+NS_IMETHODIMP nsPrintProgressParams::GetDocTitle(char16_t * *aDocTitle)
+{
+ NS_ENSURE_ARG(aDocTitle);
+
+ *aDocTitle = ToNewUnicode(mDocTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgressParams::SetDocTitle(const char16_t * aDocTitle)
+{
+ mDocTitle = aDocTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgressParams::GetDocURL(char16_t * *aDocURL)
+{
+ NS_ENSURE_ARG(aDocURL);
+
+ *aDocURL = ToNewUnicode(mDocURL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgressParams::SetDocURL(const char16_t * aDocURL)
+{
+ mDocURL = aDocURL;
+ return NS_OK;
+}
+
diff --git a/components/printing/src/unix/nsPrintProgressParams.h b/components/printing/src/unix/nsPrintProgressParams.h
new file mode 100644
index 000000000..839d0e08e
--- /dev/null
+++ b/components/printing/src/unix/nsPrintProgressParams.h
@@ -0,0 +1,28 @@
+/* -*- 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 __nsPrintProgressParams_h
+#define __nsPrintProgressParams_h
+
+#include "nsIPrintProgressParams.h"
+#include "nsString.h"
+
+class nsPrintProgressParams : public nsIPrintProgressParams
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTPROGRESSPARAMS
+
+ nsPrintProgressParams();
+
+protected:
+ virtual ~nsPrintProgressParams();
+
+private:
+ nsString mDocTitle;
+ nsString mDocURL;
+};
+
+#endif
diff --git a/components/printing/src/unix/nsPrintingPromptService.cpp b/components/printing/src/unix/nsPrintingPromptService.cpp
new file mode 100644
index 000000000..d02f0a58d
--- /dev/null
+++ b/components/printing/src/unix/nsPrintingPromptService.cpp
@@ -0,0 +1,299 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsPrintingPromptService.h"
+
+#include "nsArray.h"
+#include "nsIComponentManager.h"
+#include "nsIDialogParamBlock.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIServiceManager.h"
+#include "nsISupportsUtils.h"
+#include "nsString.h"
+#include "nsIPrintDialogService.h"
+
+// Printing Progress Includes
+#include "nsPrintProgress.h"
+#include "nsPrintProgressParams.h"
+
+static const char *kPrintDialogURL = "chrome://global/content/printdialog.xul";
+static const char *kPrintProgressDialogURL = "chrome://global/content/printProgress.xul";
+static const char *kPrtPrvProgressDialogURL = "chrome://global/content/printPreviewProgress.xul";
+static const char *kPageSetupDialogURL = "chrome://global/content/printPageSetup.xul";
+static const char *kPrinterPropertiesURL = "chrome://global/content/printjoboptions.xul";
+
+/****************************************************************
+ ************************* ParamBlock ***************************
+ ****************************************************************/
+
+class ParamBlock {
+
+public:
+ ParamBlock()
+ {
+ mBlock = 0;
+ }
+ ~ParamBlock()
+ {
+ NS_IF_RELEASE(mBlock);
+ }
+ nsresult Init() {
+ return CallCreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &mBlock);
+ }
+ nsIDialogParamBlock * operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mBlock; }
+ operator nsIDialogParamBlock * () const { return mBlock; }
+
+private:
+ nsIDialogParamBlock *mBlock;
+};
+
+/****************************************************************
+ ***************** nsPrintingPromptService **********************
+ ****************************************************************/
+
+NS_IMPL_ISUPPORTS(nsPrintingPromptService, nsIPrintingPromptService, nsIWebProgressListener)
+
+nsPrintingPromptService::nsPrintingPromptService()
+{
+}
+
+nsPrintingPromptService::~nsPrintingPromptService()
+{
+}
+
+nsresult
+nsPrintingPromptService::Init()
+{
+ nsresult rv;
+ mWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowPrintDialog(mozIDOMWindowProxy *parent,
+ nsIWebBrowserPrint *webBrowserPrint,
+ nsIPrintSettings *printSettings)
+{
+ NS_ENSURE_ARG(webBrowserPrint);
+ NS_ENSURE_ARG(printSettings);
+
+ // Try to access a component dialog
+ nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService(
+ NS_PRINTDIALOGSERVICE_CONTRACTID));
+ if (dlgPrint)
+ return dlgPrint->Show(nsPIDOMWindowOuter::From(parent),
+ printSettings, webBrowserPrint);
+
+ // Show the built-in dialog instead
+ ParamBlock block;
+ nsresult rv = block.Init();
+ if (NS_FAILED(rv))
+ return rv;
+
+ block->SetInt(0, 0);
+ return DoDialog(parent, block, webBrowserPrint, printSettings, kPrintDialogURL);
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowProgress(mozIDOMWindowProxy* parent,
+ nsIWebBrowserPrint* webBrowserPrint, // ok to be null
+ nsIPrintSettings* printSettings, // ok to be null
+ nsIObserver* openDialogObserver, // ok to be null
+ bool isForPrinting,
+ nsIWebProgressListener** webProgressListener,
+ nsIPrintProgressParams** printProgressParams,
+ bool* notifyOnOpen)
+{
+ NS_ENSURE_ARG(webProgressListener);
+ NS_ENSURE_ARG(printProgressParams);
+ NS_ENSURE_ARG(notifyOnOpen);
+
+ *notifyOnOpen = false;
+
+ nsPrintProgress* prtProgress = new nsPrintProgress(printSettings);
+ mPrintProgress = prtProgress;
+ mWebProgressListener = prtProgress;
+
+ nsCOMPtr<nsIPrintProgressParams> prtProgressParams = new nsPrintProgressParams();
+
+ nsCOMPtr<mozIDOMWindowProxy> parentWindow = parent;
+
+ if (mWatcher && !parentWindow) {
+ mWatcher->GetActiveWindow(getter_AddRefs(parentWindow));
+ }
+
+ if (parentWindow) {
+ mPrintProgress->OpenProgressDialog(parentWindow,
+ isForPrinting ? kPrintProgressDialogURL : kPrtPrvProgressDialogURL,
+ prtProgressParams, openDialogObserver, notifyOnOpen);
+ }
+
+ prtProgressParams.forget(printProgressParams);
+ NS_ADDREF(*webProgressListener = this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowPageSetup(mozIDOMWindowProxy *parent,
+ nsIPrintSettings *printSettings,
+ nsIObserver *aObs)
+{
+ NS_ENSURE_ARG(printSettings);
+
+ // Try to access a component dialog
+ nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService(
+ NS_PRINTDIALOGSERVICE_CONTRACTID));
+ if (dlgPrint)
+ return dlgPrint->ShowPageSetup(nsPIDOMWindowOuter::From(parent),
+ printSettings);
+
+ ParamBlock block;
+ nsresult rv = block.Init();
+ if (NS_FAILED(rv))
+ return rv;
+
+ block->SetInt(0, 0);
+ return DoDialog(parent, block, nullptr, printSettings, kPageSetupDialogURL);
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowPrinterProperties(mozIDOMWindowProxy *parent,
+ const char16_t *printerName,
+ nsIPrintSettings *printSettings)
+{
+ /* fixme: We simply ignore the |aPrinter| argument here
+ * We should get the supported printer attributes from the printer and
+ * populate the print job options dialog with these data instead of using
+ * the "default set" here.
+ * However, this requires changes on all platforms and is another big chunk
+ * of patches ... ;-(
+ */
+ NS_ENSURE_ARG(printerName);
+ NS_ENSURE_ARG(printSettings);
+
+ ParamBlock block;
+ nsresult rv = block.Init();
+ if (NS_FAILED(rv))
+ return rv;
+
+ block->SetInt(0, 0);
+ return DoDialog(parent, block, nullptr, printSettings, kPrinterPropertiesURL);
+
+}
+
+nsresult
+nsPrintingPromptService::DoDialog(mozIDOMWindowProxy *aParent,
+ nsIDialogParamBlock *aParamBlock,
+ nsIWebBrowserPrint *aWebBrowserPrint,
+ nsIPrintSettings* aPS,
+ const char *aChromeURL)
+{
+ NS_ENSURE_ARG(aParamBlock);
+ NS_ENSURE_ARG(aPS);
+ NS_ENSURE_ARG(aChromeURL);
+
+ if (!mWatcher)
+ return NS_ERROR_FAILURE;
+
+ // get a parent, if at all possible
+ // (though we'd rather this didn't fail, it's OK if it does. so there's
+ // no failure or null check.)
+ nsCOMPtr<mozIDOMWindowProxy> activeParent;
+ if (!aParent)
+ {
+ mWatcher->GetActiveWindow(getter_AddRefs(activeParent));
+ aParent = activeParent;
+ }
+
+ // create a nsIMutableArray of the parameters
+ // being passed to the window
+ nsCOMPtr<nsIMutableArray> array = nsArray::Create();
+
+ nsCOMPtr<nsISupports> psSupports(do_QueryInterface(aPS));
+ NS_ASSERTION(psSupports, "PrintSettings must be a supports");
+ array->AppendElement(psSupports, /*weak =*/ false);
+
+ if (aWebBrowserPrint) {
+ nsCOMPtr<nsISupports> wbpSupports(do_QueryInterface(aWebBrowserPrint));
+ NS_ASSERTION(wbpSupports, "nsIWebBrowserPrint must be a supports");
+ array->AppendElement(wbpSupports, /*weak =*/ false);
+ }
+
+ nsCOMPtr<nsISupports> blkSupps(do_QueryInterface(aParamBlock));
+ NS_ASSERTION(blkSupps, "IOBlk must be a supports");
+ array->AppendElement(blkSupps, /*weak =*/ false);
+
+ nsCOMPtr<mozIDOMWindowProxy> dialog;
+ nsresult rv = mWatcher->OpenWindow(aParent, aChromeURL, "_blank",
+ "centerscreen,chrome,modal,titlebar", array,
+ getter_AddRefs(dialog));
+
+ // if aWebBrowserPrint is not null then we are printing
+ // so we want to pass back NS_ERROR_ABORT on cancel
+ if (NS_SUCCEEDED(rv) && aWebBrowserPrint)
+ {
+ int32_t status;
+ aParamBlock->GetInt(0, &status);
+ return status == 0?NS_ERROR_ABORT:NS_OK;
+ }
+
+ return rv;
+}
+
+//////////////////////////////////////////////////////////////////////
+// nsIWebProgressListener
+//////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus)
+{
+ if ((aStateFlags & STATE_STOP) && mWebProgressListener) {
+ mWebProgressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
+ if (mPrintProgress) {
+ mPrintProgress->CloseProgressDialog(true);
+ }
+ mPrintProgress = nullptr;
+ mWebProgressListener = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress)
+{
+ if (mWebProgressListener) {
+ return mWebProgressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags)
+{
+ if (mWebProgressListener) {
+ return mWebProgressListener->OnLocationChange(aWebProgress, aRequest, location, aFlags);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage)
+{
+ if (mWebProgressListener) {
+ return mWebProgressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state)
+{
+ if (mWebProgressListener) {
+ return mWebProgressListener->OnSecurityChange(aWebProgress, aRequest, state);
+ }
+ return NS_OK;
+}
diff --git a/components/printing/src/unix/nsPrintingPromptService.h b/components/printing/src/unix/nsPrintingPromptService.h
new file mode 100644
index 000000000..cc0c76929
--- /dev/null
+++ b/components/printing/src/unix/nsPrintingPromptService.h
@@ -0,0 +1,58 @@
+/* -*- 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 __nsPrintingPromptService_h
+#define __nsPrintingPromptService_h
+
+// {E042570C-62DE-4bb6-A6E0-798E3C07B4DF}
+#define NS_PRINTINGPROMPTSERVICE_CID \
+ {0xe042570c, 0x62de, 0x4bb6, { 0xa6, 0xe0, 0x79, 0x8e, 0x3c, 0x7, 0xb4, 0xdf}}
+#define NS_PRINTINGPROMPTSERVICE_CONTRACTID \
+ "@mozilla.org/embedcomp/printingprompt-service;1"
+
+#include "nsCOMPtr.h"
+#include "nsIPrintingPromptService.h"
+#include "nsPIPromptService.h"
+#include "nsIWindowWatcher.h"
+
+// Printing Progress Includes
+#include "nsPrintProgress.h"
+#include "nsPrintProgressParams.h"
+#include "nsIWebProgressListener.h"
+
+class nsIDOMWindow;
+class nsIDialogParamBlock;
+
+class nsPrintingPromptService: public nsIPrintingPromptService,
+ public nsIWebProgressListener
+{
+
+public:
+
+ nsPrintingPromptService();
+
+ nsresult Init();
+
+ NS_DECL_NSIPRINTINGPROMPTSERVICE
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_ISUPPORTS
+
+protected:
+ virtual ~nsPrintingPromptService();
+
+private:
+ nsresult DoDialog(mozIDOMWindowProxy *aParent,
+ nsIDialogParamBlock *aParamBlock,
+ nsIWebBrowserPrint *aWebBrowserPrint,
+ nsIPrintSettings* aPS,
+ const char *aChromeURL);
+
+ nsCOMPtr<nsIWindowWatcher> mWatcher;
+ nsCOMPtr<nsIPrintProgress> mPrintProgress;
+ nsCOMPtr<nsIWebProgressListener> mWebProgressListener;
+};
+
+#endif
+
diff --git a/components/printing/src/win/moz.build b/components/printing/src/win/moz.build
new file mode 100644
index 000000000..b75b55d1f
--- /dev/null
+++ b/components/printing/src/win/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXPORTS += ['nsPrintDialogUtil.h']
+
+SOURCES += [
+ 'nsPrintDialogUtil.cpp',
+ 'nsPrintingPromptService.cpp',
+ 'nsPrintProgress.cpp',
+ 'nsPrintProgressParams.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/printing/src/win/nsPrintDialogUtil.cpp b/components/printing/src/win/nsPrintDialogUtil.cpp
new file mode 100644
index 000000000..896c58e85
--- /dev/null
+++ b/components/printing/src/win/nsPrintDialogUtil.cpp
@@ -0,0 +1,854 @@
+/* -*- 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/. */
+
+/* -------------------------------------------------------------------
+To Build This:
+
+ You need to add this to the the makefile.win in mozilla/dom/base:
+
+ .\$(OBJDIR)\nsFlyOwnPrintDialog.obj \
+
+
+ And this to the makefile.win in mozilla/content/build:
+
+WIN_LIBS= \
+ winspool.lib \
+ comctl32.lib \
+ comdlg32.lib
+
+---------------------------------------------------------------------- */
+
+#include "plstr.h"
+#include <windows.h>
+#include <tchar.h>
+
+#include <unknwn.h>
+#include <commdlg.h>
+
+#include "nsIWebBrowserPrint.h"
+#include "nsString.h"
+#include "nsIServiceManager.h"
+#include "nsReadableUtils.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsWin.h"
+#include "nsIPrinterEnumerator.h"
+
+#include "nsRect.h"
+
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+
+#include "nsCRT.h"
+#include "prenv.h" /* for PR_GetEnv */
+
+#include <windows.h>
+#include <winspool.h>
+
+// For Localization
+#include "nsIStringBundle.h"
+
+// For NS_CopyUnicodeToNative
+#include "nsNativeCharsetUtils.h"
+
+// This is for extending the dialog
+#include <dlgs.h>
+
+#include "nsWindowsHelpers.h"
+#include "WinUtils.h"
+
+// Default labels for the radio buttons
+static const char* kAsLaidOutOnScreenStr = "As &laid out on the screen";
+static const char* kTheSelectedFrameStr = "The selected &frame";
+static const char* kEachFrameSeparately = "&Each frame separately";
+
+
+//-----------------------------------------------
+// Global Data
+//-----------------------------------------------
+// Identifies which new radio btn was cliked on
+static UINT gFrameSelectedRadioBtn = 0;
+
+// Indicates whether the native print dialog was successfully extended
+static bool gDialogWasExtended = false;
+
+#define PRINTDLG_PROPERTIES "chrome://global/locale/printdialog.properties"
+
+static HWND gParentWnd = nullptr;
+
+//----------------------------------------------------------------------------------
+// Return localized bundle for resource strings
+static nsresult
+GetLocalizedBundle(const char * aPropFileName, nsIStringBundle** aStrBundle)
+{
+ NS_ENSURE_ARG_POINTER(aPropFileName);
+ NS_ENSURE_ARG_POINTER(aStrBundle);
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundle> bundle;
+
+
+ // Create bundle
+ nsCOMPtr<nsIStringBundleService> stringService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && stringService) {
+ rv = stringService->CreateBundle(aPropFileName, aStrBundle);
+ }
+
+ return rv;
+}
+
+//--------------------------------------------------------
+// Return localized string
+static nsresult
+GetLocalizedString(nsIStringBundle* aStrBundle, const char* aKey, nsString& oVal)
+{
+ NS_ENSURE_ARG_POINTER(aStrBundle);
+ NS_ENSURE_ARG_POINTER(aKey);
+
+ // Determine default label from string bundle
+ nsXPIDLString valUni;
+ nsAutoString key;
+ key.AssignWithConversion(aKey);
+ nsresult rv = aStrBundle->GetStringFromName(key.get(), getter_Copies(valUni));
+ if (NS_SUCCEEDED(rv) && valUni) {
+ oVal.Assign(valUni);
+ } else {
+ oVal.Truncate();
+ }
+ return rv;
+}
+
+//--------------------------------------------------------
+// Set a multi-byte string in the control
+static void SetTextOnWnd(HWND aControl, const nsString& aStr)
+{
+ nsAutoCString text;
+ if (NS_SUCCEEDED(NS_CopyUnicodeToNative(aStr, text))) {
+ ::SetWindowText(aControl, text.get());
+ }
+}
+
+//--------------------------------------------------------
+// Will get the control and localized string by "key"
+static void SetText(HWND aParent,
+ UINT aId,
+ nsIStringBundle* aStrBundle,
+ const char* aKey)
+{
+ HWND wnd = GetDlgItem (aParent, aId);
+ if (!wnd) {
+ return;
+ }
+ nsAutoString str;
+ nsresult rv = GetLocalizedString(aStrBundle, aKey, str);
+ if (NS_SUCCEEDED(rv)) {
+ SetTextOnWnd(wnd, str);
+ }
+}
+
+//--------------------------------------------------------
+static void SetRadio(HWND aParent,
+ UINT aId,
+ bool aIsSet,
+ bool isEnabled = true)
+{
+ HWND wnd = ::GetDlgItem (aParent, aId);
+ if (!wnd) {
+ return;
+ }
+ if (!isEnabled) {
+ ::EnableWindow(wnd, FALSE);
+ return;
+ }
+ ::EnableWindow(wnd, TRUE);
+ ::SendMessage(wnd, BM_SETCHECK, (WPARAM)aIsSet, (LPARAM)0);
+}
+
+//--------------------------------------------------------
+static void SetRadioOfGroup(HWND aDlg, int aRadId)
+{
+ int radioIds[] = {rad4, rad5, rad6};
+ int numRads = 3;
+
+ for (int i=0;i<numRads;i++) {
+ HWND radWnd = ::GetDlgItem(aDlg, radioIds[i]);
+ if (radWnd != nullptr) {
+ ::SendMessage(radWnd, BM_SETCHECK, (WPARAM)(radioIds[i] == aRadId), (LPARAM)0);
+ }
+ }
+}
+
+//--------------------------------------------------------
+typedef struct {
+ const char * mKeyStr;
+ long mKeyId;
+} PropKeyInfo;
+
+// These are the control ids used in the dialog and
+// defined by MS-Windows in commdlg.h
+static PropKeyInfo gAllPropKeys[] = {
+ {"printFramesTitleWindows", grp3},
+ {"asLaidOutWindows", rad4},
+ {"selectedFrameWindows", rad5},
+ {"separateFramesWindows", rad6},
+ {nullptr, 0}};
+
+//--------------------------------------------------------
+//--------------------------------------------------------
+//--------------------------------------------------------
+//--------------------------------------------------------
+// Get the absolute coords of the child windows relative
+// to its parent window
+static void GetLocalRect(HWND aWnd, RECT& aRect, HWND aParent)
+{
+ ::GetWindowRect(aWnd, &aRect);
+
+ // MapWindowPoints converts screen coordinates to client coordinates.
+ // It works correctly in both left-to-right and right-to-left windows.
+ ::MapWindowPoints(nullptr, aParent, (LPPOINT)&aRect, 2);
+}
+
+//--------------------------------------------------------
+// Show or Hide the control
+static void Show(HWND aWnd, bool bState)
+{
+ if (aWnd) {
+ ::ShowWindow(aWnd, bState?SW_SHOW:SW_HIDE);
+ }
+}
+
+//--------------------------------------------------------
+// Create a child window "control"
+static HWND CreateControl(LPCTSTR aType,
+ DWORD aStyle,
+ HINSTANCE aHInst,
+ HWND aHdlg,
+ int aId,
+ const nsAString& aStr,
+ const nsIntRect& aRect)
+{
+ nsAutoCString str;
+ if (NS_FAILED(NS_CopyUnicodeToNative(aStr, str)))
+ return nullptr;
+
+ HWND hWnd = ::CreateWindow (aType, str.get(),
+ WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | aStyle,
+ aRect.x, aRect.y, aRect.width, aRect.height,
+ (HWND)aHdlg, (HMENU)(intptr_t)aId,
+ aHInst, nullptr);
+ if (hWnd == nullptr) return nullptr;
+
+ // get the native font for the dialog and
+ // set it into the new control
+ HFONT hFont = (HFONT)::SendMessage(aHdlg, WM_GETFONT, (WPARAM)0, (LPARAM)0);
+ if (hFont != nullptr) {
+ ::SendMessage(hWnd, WM_SETFONT, (WPARAM) hFont, (LPARAM)0);
+ }
+ return hWnd;
+}
+
+//--------------------------------------------------------
+// Create a Radio Button
+static HWND CreateRadioBtn(HINSTANCE aHInst,
+ HWND aHdlg,
+ int aId,
+ const char* aStr,
+ const nsIntRect& aRect)
+{
+ nsString cStr;
+ cStr.AssignWithConversion(aStr);
+ return CreateControl("BUTTON", BS_RADIOBUTTON, aHInst, aHdlg, aId, cStr, aRect);
+}
+
+//--------------------------------------------------------
+// Create a Group Box
+static HWND CreateGroupBox(HINSTANCE aHInst,
+ HWND aHdlg,
+ int aId,
+ const nsAString& aStr,
+ const nsIntRect& aRect)
+{
+ return CreateControl("BUTTON", BS_GROUPBOX, aHInst, aHdlg, aId, aStr, aRect);
+}
+
+//--------------------------------------------------------
+// Localizes and initializes the radio buttons and group
+static void InitializeExtendedDialog(HWND hdlg, int16_t aHowToEnableFrameUI)
+{
+ MOZ_ASSERT(aHowToEnableFrameUI != nsIPrintSettings::kFrameEnableNone,
+ "should not be called");
+
+ // Localize the new controls in the print dialog
+ nsCOMPtr<nsIStringBundle> strBundle;
+ if (NS_SUCCEEDED(GetLocalizedBundle(PRINTDLG_PROPERTIES, getter_AddRefs(strBundle)))) {
+ int32_t i = 0;
+ while (gAllPropKeys[i].mKeyStr != nullptr) {
+ SetText(hdlg, gAllPropKeys[i].mKeyId, strBundle, gAllPropKeys[i].mKeyStr);
+ i++;
+ }
+ }
+
+ // Set up radio buttons
+ if (aHowToEnableFrameUI == nsIPrintSettings::kFrameEnableAll) {
+ SetRadio(hdlg, rad4, false);
+ SetRadio(hdlg, rad5, true);
+ SetRadio(hdlg, rad6, false);
+ // set default so user doesn't have to actually press on it
+ gFrameSelectedRadioBtn = rad5;
+
+ } else { // nsIPrintSettings::kFrameEnableAsIsAndEach
+ SetRadio(hdlg, rad4, false);
+ SetRadio(hdlg, rad5, false, false);
+ SetRadio(hdlg, rad6, true);
+ // set default so user doesn't have to actually press on it
+ gFrameSelectedRadioBtn = rad6;
+ }
+}
+
+
+//--------------------------------------------------------
+// Special Hook Procedure for handling the print dialog messages
+static UINT CALLBACK PrintHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
+{
+
+ if (uiMsg == WM_COMMAND) {
+ UINT id = LOWORD(wParam);
+ if (id == rad4 || id == rad5 || id == rad6) {
+ gFrameSelectedRadioBtn = id;
+ SetRadioOfGroup(hdlg, id);
+ }
+
+ } else if (uiMsg == WM_INITDIALOG) {
+ PRINTDLG * printDlg = (PRINTDLG *)lParam;
+ if (printDlg == nullptr) return 0L;
+
+ int16_t howToEnableFrameUI = (int16_t)printDlg->lCustData;
+ // don't add frame options if they would be disabled anyway
+ // because there are no frames
+ if (howToEnableFrameUI == nsIPrintSettings::kFrameEnableNone)
+ return TRUE;
+
+ HINSTANCE hInst = (HINSTANCE)::GetWindowLongPtr(hdlg, GWLP_HINSTANCE);
+ if (hInst == nullptr) return 0L;
+
+ // Start by getting the local rects of several of the controls
+ // so we can calculate where the new controls are
+ HWND wnd = ::GetDlgItem(hdlg, grp1);
+ if (wnd == nullptr) return 0L;
+ RECT dlgRect;
+ GetLocalRect(wnd, dlgRect, hdlg);
+
+ wnd = ::GetDlgItem(hdlg, rad1); // this is the top control "All"
+ if (wnd == nullptr) return 0L;
+ RECT rad1Rect;
+ GetLocalRect(wnd, rad1Rect, hdlg);
+
+ wnd = ::GetDlgItem(hdlg, rad2); // this is the bottom control "Selection"
+ if (wnd == nullptr) return 0L;
+ RECT rad2Rect;
+ GetLocalRect(wnd, rad2Rect, hdlg);
+
+ wnd = ::GetDlgItem(hdlg, rad3); // this is the middle control "Pages"
+ if (wnd == nullptr) return 0L;
+ RECT rad3Rect;
+ GetLocalRect(wnd, rad3Rect, hdlg);
+
+ HWND okWnd = ::GetDlgItem(hdlg, IDOK);
+ if (okWnd == nullptr) return 0L;
+ RECT okRect;
+ GetLocalRect(okWnd, okRect, hdlg);
+
+ wnd = ::GetDlgItem(hdlg, grp4); // this is the "Print range" groupbox
+ if (wnd == nullptr) return 0L;
+ RECT prtRect;
+ GetLocalRect(wnd, prtRect, hdlg);
+
+
+ // calculate various different "gaps" for layout purposes
+
+ int rbGap = rad3Rect.top - rad1Rect.bottom; // gap between radiobtns
+ int grpBotGap = dlgRect.bottom - rad2Rect.bottom; // gap from bottom rb to bottom of grpbox
+ int grpGap = dlgRect.top - prtRect.bottom ; // gap between group boxes
+ int top = dlgRect.bottom + grpGap;
+ int radHgt = rad1Rect.bottom - rad1Rect.top + 1; // top of new group box
+ int y = top+(rad1Rect.top-dlgRect.top); // starting pos of first radio
+ int rbWidth = dlgRect.right - rad1Rect.left - 5; // measure from rb left to the edge of the groupbox
+ // (5 is arbitrary)
+ nsIntRect rect;
+
+ // Create and position the radio buttons
+ //
+ // If any one control cannot be created then
+ // hide the others and bail out
+ //
+ rect.SetRect(rad1Rect.left, y, rbWidth,radHgt);
+ HWND rad4Wnd = CreateRadioBtn(hInst, hdlg, rad4, kAsLaidOutOnScreenStr, rect);
+ if (rad4Wnd == nullptr) return 0L;
+ y += radHgt + rbGap;
+
+ rect.SetRect(rad1Rect.left, y, rbWidth, radHgt);
+ HWND rad5Wnd = CreateRadioBtn(hInst, hdlg, rad5, kTheSelectedFrameStr, rect);
+ if (rad5Wnd == nullptr) {
+ Show(rad4Wnd, FALSE); // hide
+ return 0L;
+ }
+ y += radHgt + rbGap;
+
+ rect.SetRect(rad1Rect.left, y, rbWidth, radHgt);
+ HWND rad6Wnd = CreateRadioBtn(hInst, hdlg, rad6, kEachFrameSeparately, rect);
+ if (rad6Wnd == nullptr) {
+ Show(rad4Wnd, FALSE); // hide
+ Show(rad5Wnd, FALSE); // hide
+ return 0L;
+ }
+ y += radHgt + grpBotGap;
+
+ // Create and position the group box
+ rect.SetRect (dlgRect.left, top, dlgRect.right-dlgRect.left+1, y-top+1);
+ HWND grpBoxWnd = CreateGroupBox(hInst, hdlg, grp3, NS_LITERAL_STRING("Print Frame"), rect);
+ if (grpBoxWnd == nullptr) {
+ Show(rad4Wnd, FALSE); // hide
+ Show(rad5Wnd, FALSE); // hide
+ Show(rad6Wnd, FALSE); // hide
+ return 0L;
+ }
+
+ // Here we figure out the old height of the dlg
+ // then figure its gap from the old grpbx to the bottom
+ // then size the dlg
+ RECT pr, cr;
+ ::GetWindowRect(hdlg, &pr);
+ ::GetClientRect(hdlg, &cr);
+
+ int dlgHgt = (cr.bottom - cr.top) + 1;
+ int bottomGap = dlgHgt - okRect.bottom;
+ pr.bottom += (dlgRect.bottom-dlgRect.top) + grpGap + 1 - (dlgHgt-dlgRect.bottom) + bottomGap;
+
+ ::SetWindowPos(hdlg, nullptr, pr.left, pr.top, pr.right-pr.left+1, pr.bottom-pr.top+1,
+ SWP_NOMOVE|SWP_NOREDRAW|SWP_NOZORDER);
+
+ // figure out the new height of the dialog
+ ::GetClientRect(hdlg, &cr);
+ dlgHgt = (cr.bottom - cr.top) + 1;
+
+ // Reposition the OK and Cancel btns
+ int okHgt = okRect.bottom - okRect.top + 1;
+ ::SetWindowPos(okWnd, nullptr, okRect.left, dlgHgt-bottomGap-okHgt, 0, 0,
+ SWP_NOSIZE|SWP_NOREDRAW|SWP_NOZORDER);
+
+ HWND cancelWnd = ::GetDlgItem(hdlg, IDCANCEL);
+ if (cancelWnd == nullptr) return 0L;
+
+ RECT cancelRect;
+ GetLocalRect(cancelWnd, cancelRect, hdlg);
+ int cancelHgt = cancelRect.bottom - cancelRect.top + 1;
+ ::SetWindowPos(cancelWnd, nullptr, cancelRect.left, dlgHgt-bottomGap-cancelHgt, 0, 0,
+ SWP_NOSIZE|SWP_NOREDRAW|SWP_NOZORDER);
+
+ // localize and initialize the groupbox and radiobuttons
+ InitializeExtendedDialog(hdlg, howToEnableFrameUI);
+
+ // Looks like we were able to extend the dialog
+ gDialogWasExtended = true;
+ return TRUE;
+ }
+ return 0L;
+}
+
+//----------------------------------------------------------------------------------
+// Returns a Global Moveable Memory Handle to a DevMode
+// from the Printer by the name of aPrintName
+//
+// NOTE:
+// This function assumes that aPrintName has already been converted from
+// unicode
+//
+static nsReturnRef<nsHGLOBAL>
+CreateGlobalDevModeAndInit(const nsXPIDLString& aPrintName,
+ nsIPrintSettings* aPS)
+{
+ nsHPRINTER hPrinter = nullptr;
+ // const cast kludge for silly Win32 api's
+ LPWSTR printName = const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName.get()));
+ BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr);
+ if (!status) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ // Make sure hPrinter is closed on all paths
+ nsAutoPrinter autoPrinter(hPrinter);
+
+ // Get the buffer size
+ LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr,
+ nullptr, 0);
+ if (needed < 0) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ // Allocate a buffer of the correct size.
+ nsAutoDevMode newDevMode((LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY,
+ needed));
+ if (!newDevMode) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed);
+ nsAutoGlobalMem globalDevMode(hDevMode);
+ if (!hDevMode) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode,
+ nullptr, DM_OUT_BUFFER);
+ if (ret != IDOK) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ // Lock memory and copy contents from DEVMODE (current printer)
+ // to Global Memory DEVMODE
+ LPDEVMODEW devMode = (DEVMODEW *)::GlobalLock(hDevMode);
+ if (!devMode) {
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ memcpy(devMode, newDevMode.get(), needed);
+ // Initialize values from the PrintSettings
+ nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS);
+ MOZ_ASSERT(psWin);
+ psWin->CopyToNative(devMode);
+
+ // Sets back the changes we made to the DevMode into the Printer Driver
+ ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode,
+ DM_IN_BUFFER | DM_OUT_BUFFER);
+ if (ret != IDOK) {
+ ::GlobalUnlock(hDevMode);
+ return nsReturnRef<nsHGLOBAL>();
+ }
+
+ ::GlobalUnlock(hDevMode);
+
+ return globalDevMode.out();
+}
+
+//------------------------------------------------------------------
+// helper
+static void GetDefaultPrinterNameFromGlobalPrinters(nsXPIDLString &printerName)
+{
+ nsCOMPtr<nsIPrinterEnumerator> prtEnum = do_GetService("@mozilla.org/gfx/printerenumerator;1");
+ if (prtEnum) {
+ prtEnum->GetDefaultPrinterName(getter_Copies(printerName));
+ }
+}
+
+// Determine whether we have a completely native dialog
+// or whether we cshould extend it
+static bool ShouldExtendPrintDialog()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, true);
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, true);
+
+ bool result;
+ rv = prefBranch->GetBoolPref("print.extend_native_print_dialog", &result);
+ NS_ENSURE_SUCCESS(rv, true);
+ return result;
+}
+
+//------------------------------------------------------------------
+// Displays the native Print Dialog
+static nsresult
+ShowNativePrintDialog(HWND aHWnd,
+ nsIPrintSettings* aPrintSettings)
+{
+ //NS_ENSURE_ARG_POINTER(aHWnd);
+ NS_ENSURE_ARG_POINTER(aPrintSettings);
+
+ gDialogWasExtended = false;
+
+ // Get the Print Name to be used
+ nsXPIDLString printerName;
+ aPrintSettings->GetPrinterName(getter_Copies(printerName));
+
+ // If there is no name then use the default printer
+ if (printerName.IsEmpty()) {
+ GetDefaultPrinterNameFromGlobalPrinters(printerName);
+ } else {
+ HANDLE hPrinter = nullptr;
+ if(!::OpenPrinterW(const_cast<wchar_t*>(static_cast<const wchar_t*>(printerName.get())),
+ &hPrinter, nullptr)) {
+ // If the last used printer is not found, we should use default printer.
+ GetDefaultPrinterNameFromGlobalPrinters(printerName);
+ } else {
+ ::ClosePrinter(hPrinter);
+ }
+ }
+
+ // Now create a DEVNAMES struct so the the dialog is initialized correctly.
+
+ uint32_t len = printerName.Length();
+ nsHGLOBAL hDevNames = ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1)
+ + sizeof(DEVNAMES));
+ nsAutoGlobalMem autoDevNames(hDevNames);
+ if (!hDevNames) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames);
+ if (!pDevNames) {
+ return NS_ERROR_FAILURE;
+ }
+ pDevNames->wDriverOffset = sizeof(DEVNAMES)/sizeof(wchar_t);
+ pDevNames->wDeviceOffset = sizeof(DEVNAMES)/sizeof(wchar_t);
+ pDevNames->wOutputOffset = sizeof(DEVNAMES)/sizeof(wchar_t)+len;
+ pDevNames->wDefault = 0;
+
+ memcpy(pDevNames+1, printerName, (len + 1) * sizeof(wchar_t));
+ ::GlobalUnlock(hDevNames);
+
+ // Create a Moveable Memory Object that holds a new DevMode
+ // from the Printer Name
+ // The PRINTDLG.hDevMode requires that it be a moveable memory object
+ // NOTE: autoDevMode is automatically freed when any error occurred
+ nsAutoGlobalMem autoDevMode(CreateGlobalDevModeAndInit(printerName, aPrintSettings));
+
+ // Prepare to Display the Print Dialog
+ PRINTDLGW prntdlg;
+ memset(&prntdlg, 0, sizeof(PRINTDLGW));
+
+ prntdlg.lStructSize = sizeof(prntdlg);
+ prntdlg.hwndOwner = aHWnd;
+ prntdlg.hDevMode = autoDevMode.get();
+ prntdlg.hDevNames = hDevNames;
+ prntdlg.hDC = nullptr;
+ prntdlg.Flags = PD_ALLPAGES | PD_RETURNIC |
+ PD_USEDEVMODECOPIESANDCOLLATE | PD_COLLATE;
+
+ // if there is a current selection then enable the "Selection" radio button
+ int16_t howToEnableFrameUI = nsIPrintSettings::kFrameEnableNone;
+ bool isOn;
+ aPrintSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &isOn);
+ if (!isOn) {
+ prntdlg.Flags |= PD_NOSELECTION;
+ }
+ aPrintSettings->GetHowToEnableFrameUI(&howToEnableFrameUI);
+
+ int32_t pg = 1;
+ aPrintSettings->GetStartPageRange(&pg);
+ prntdlg.nFromPage = pg;
+
+ aPrintSettings->GetEndPageRange(&pg);
+ prntdlg.nToPage = pg;
+
+ prntdlg.nMinPage = 1;
+ prntdlg.nMaxPage = 0xFFFF;
+ prntdlg.nCopies = 1;
+ prntdlg.lpfnSetupHook = nullptr;
+ prntdlg.lpSetupTemplateName = nullptr;
+ prntdlg.hPrintTemplate = nullptr;
+ prntdlg.hSetupTemplate = nullptr;
+
+ prntdlg.hInstance = nullptr;
+ prntdlg.lpPrintTemplateName = nullptr;
+
+ if (!ShouldExtendPrintDialog()) {
+ prntdlg.lCustData = 0;
+ prntdlg.lpfnPrintHook = nullptr;
+ } else {
+ // Set up print dialog "hook" procedure for extending the dialog
+ prntdlg.lCustData = (DWORD)howToEnableFrameUI;
+ prntdlg.lpfnPrintHook = (LPPRINTHOOKPROC)PrintHookProc;
+ prntdlg.Flags |= PD_ENABLEPRINTHOOK;
+ }
+
+ BOOL result;
+ {
+ mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness;
+ result = ::PrintDlgW(&prntdlg);
+ }
+
+ if (TRUE == result) {
+ // check to make sure we don't have any nullptr pointers
+ NS_ENSURE_TRUE(aPrintSettings && prntdlg.hDevMode, NS_ERROR_FAILURE);
+
+ if (prntdlg.hDevNames == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+ // Lock the deviceNames and check for nullptr
+ DEVNAMES *devnames = (DEVNAMES *)::GlobalLock(prntdlg.hDevNames);
+ if (devnames == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char16_t* device = &(((char16_t *)devnames)[devnames->wDeviceOffset]);
+ char16_t* driver = &(((char16_t *)devnames)[devnames->wDriverOffset]);
+
+ // Check to see if the "Print To File" control is checked
+ // then take the name from devNames and set it in the PrintSettings
+ //
+ // NOTE:
+ // As per Microsoft SDK documentation the returned value offset from
+ // devnames->wOutputOffset is either "FILE:" or nullptr
+ // if the "Print To File" checkbox is checked it MUST be "FILE:"
+ // We assert as an extra safety check.
+ if (prntdlg.Flags & PD_PRINTTOFILE) {
+ char16ptr_t fileName = &(((wchar_t *)devnames)[devnames->wOutputOffset]);
+ NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`");
+ aPrintSettings->SetToFileName(fileName);
+ aPrintSettings->SetPrintToFile(true);
+ } else {
+ // clear "print to file" info
+ aPrintSettings->SetPrintToFile(false);
+ aPrintSettings->SetToFileName(nullptr);
+ }
+
+ nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings));
+ if (!psWin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Setup local Data members
+ psWin->SetDeviceName(device);
+ psWin->SetDriverName(driver);
+
+#if defined(DEBUG_rods) || defined(DEBUG_dcone)
+ wprintf(L"printer: driver %s, device %s flags: %d\n", driver, device, prntdlg.Flags);
+#endif
+ // fill the print options with the info from the dialog
+
+ aPrintSettings->SetPrinterName(device);
+
+ if (prntdlg.Flags & PD_SELECTION) {
+ aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeSelection);
+
+ } else if (prntdlg.Flags & PD_PAGENUMS) {
+ aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeSpecifiedPageRange);
+ aPrintSettings->SetStartPageRange(prntdlg.nFromPage);
+ aPrintSettings->SetEndPageRange(prntdlg.nToPage);
+
+ } else { // (prntdlg.Flags & PD_ALLPAGES)
+ aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeAllPages);
+ }
+
+ if (howToEnableFrameUI != nsIPrintSettings::kFrameEnableNone) {
+ // make sure the dialog got extended
+ if (gDialogWasExtended) {
+ // check to see about the frame radio buttons
+ switch (gFrameSelectedRadioBtn) {
+ case rad4:
+ aPrintSettings->SetPrintFrameType(nsIPrintSettings::kFramesAsIs);
+ break;
+ case rad5:
+ aPrintSettings->SetPrintFrameType(nsIPrintSettings::kSelectedFrame);
+ break;
+ case rad6:
+ aPrintSettings->SetPrintFrameType(nsIPrintSettings::kEachFrameSep);
+ break;
+ } // switch
+ } else {
+ // if it didn't get extended then have it default to printing
+ // each frame separately
+ aPrintSettings->SetPrintFrameType(nsIPrintSettings::kEachFrameSep);
+ }
+ } else {
+ aPrintSettings->SetPrintFrameType(nsIPrintSettings::kNoFrames);
+ }
+ // Unlock DeviceNames
+ ::GlobalUnlock(prntdlg.hDevNames);
+
+ // Transfer the settings from the native data to the PrintSettings
+ LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode);
+ if (!devMode || !prntdlg.hDC) {
+ return NS_ERROR_FAILURE;
+ }
+ psWin->SetDevMode(devMode); // copies DevMode
+ psWin->CopyFromNative(prntdlg.hDC, devMode);
+ ::GlobalUnlock(prntdlg.hDevMode);
+ ::DeleteDC(prntdlg.hDC);
+
+#if defined(DEBUG_rods) || defined(DEBUG_dcone)
+ bool printSelection = prntdlg.Flags & PD_SELECTION;
+ bool printAllPages = prntdlg.Flags & PD_ALLPAGES;
+ bool printNumPages = prntdlg.Flags & PD_PAGENUMS;
+ int32_t fromPageNum = 0;
+ int32_t toPageNum = 0;
+
+ if (printNumPages) {
+ fromPageNum = prntdlg.nFromPage;
+ toPageNum = prntdlg.nToPage;
+ }
+ if (printSelection) {
+ printf("Printing the selection\n");
+
+ } else if (printAllPages) {
+ printf("Printing all the pages\n");
+
+ } else {
+ printf("Printing from page no. %d to %d\n", fromPageNum, toPageNum);
+ }
+#endif
+
+ } else {
+ ::SetFocus(aHWnd);
+ aPrintSettings->SetIsCancelled(true);
+ return NS_ERROR_ABORT;
+ }
+
+ return NS_OK;
+}
+
+//------------------------------------------------------------------
+static void
+PrepareForPrintDialog(nsIWebBrowserPrint* aWebBrowserPrint, nsIPrintSettings* aPS)
+{
+ NS_ASSERTION(aWebBrowserPrint, "Can't be null");
+ NS_ASSERTION(aPS, "Can't be null");
+
+ bool isFramesetDocument;
+ bool isFramesetFrameSelected;
+ bool isIFrameSelected;
+ bool isRangeSelection;
+
+ aWebBrowserPrint->GetIsFramesetDocument(&isFramesetDocument);
+ aWebBrowserPrint->GetIsFramesetFrameSelected(&isFramesetFrameSelected);
+ aWebBrowserPrint->GetIsIFrameSelected(&isIFrameSelected);
+ aWebBrowserPrint->GetIsRangeSelection(&isRangeSelection);
+
+ // Setup print options for UI
+ if (isFramesetDocument) {
+ if (isFramesetFrameSelected) {
+ aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableAll);
+ } else {
+ aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableAsIsAndEach);
+ }
+ } else {
+ aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableNone);
+ }
+
+ // Now determine how to set up the Frame print UI
+ aPS->SetPrintOptions(nsIPrintSettings::kEnableSelectionRB, isRangeSelection || isIFrameSelected);
+
+}
+
+//----------------------------------------------------------------------------------
+//-- Show Print Dialog
+//----------------------------------------------------------------------------------
+nsresult NativeShowPrintDialog(HWND aHWnd,
+ nsIWebBrowserPrint* aWebBrowserPrint,
+ nsIPrintSettings* aPrintSettings)
+{
+ PrepareForPrintDialog(aWebBrowserPrint, aPrintSettings);
+
+ nsresult rv = ShowNativePrintDialog(aHWnd, aPrintSettings);
+ if (aHWnd) {
+ ::DestroyWindow(aHWnd);
+ }
+
+ return rv;
+}
+
diff --git a/components/printing/src/win/nsPrintDialogUtil.h b/components/printing/src/win/nsPrintDialogUtil.h
new file mode 100644
index 000000000..ada3da239
--- /dev/null
+++ b/components/printing/src/win/nsPrintDialogUtil.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/. */
+#ifndef nsFlyOwnDialog_h___
+#define nsFlyOwnDialog_h___
+
+nsresult NativeShowPrintDialog(HWND aHWnd,
+ nsIWebBrowserPrint* aWebBrowserPrint,
+ nsIPrintSettings* aPrintSettings);
+
+#endif /* nsFlyOwnDialog_h___ */
diff --git a/components/printing/src/win/nsPrintProgress.cpp b/components/printing/src/win/nsPrintProgress.cpp
new file mode 100644
index 000000000..0b1c10a2c
--- /dev/null
+++ b/components/printing/src/win/nsPrintProgress.cpp
@@ -0,0 +1,293 @@
+/* -*- 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 "nsPrintProgress.h"
+
+#include "nsArray.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIXULWindow.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsPIDOMWindow.h"
+
+#if 0
+NS_IMPL_ADDREF(nsPrintProgress)
+NS_IMPL_RELEASE(nsPrintProgress)
+#else
+NS_IMETHODIMP_(MozExternalRefCountType) nsPrintProgress::AddRef(void)
+{
+ NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt");
+ nsrefcnt count;
+ count = ++mRefCnt;
+ //NS_LOG_ADDREF(this, count, "nsPrintProgress", sizeof(*this));
+ return count;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType) nsPrintProgress::Release(void)
+{
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ //NS_LOG_RELEASE(this, count, "nsPrintProgress");
+ if (0 == count) {
+ mRefCnt = 1; /* stabilize */
+ /* enable this to find non-threadsafe destructors: */
+ /* NS_ASSERT_OWNINGTHREAD(nsPrintProgress); */
+ delete this;
+ return 0;
+ }
+ return count;
+}
+
+#endif
+
+NS_INTERFACE_MAP_BEGIN(nsPrintProgress)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrintStatusFeedback)
+ NS_INTERFACE_MAP_ENTRY(nsIPrintProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIPrintStatusFeedback)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+
+nsPrintProgress::nsPrintProgress()
+{
+ m_closeProgress = false;
+ m_processCanceled = false;
+ m_pendingStateFlags = -1;
+ m_pendingStateValue = NS_OK;
+}
+
+nsPrintProgress::~nsPrintProgress()
+{
+ (void)ReleaseListeners();
+}
+
+NS_IMETHODIMP nsPrintProgress::OpenProgressDialog(mozIDOMWindowProxy *parent,
+ const char *dialogURL,
+ nsISupports *parameters,
+ nsIObserver *openDialogObserver,
+ bool *notifyOnOpen)
+{
+ *notifyOnOpen = true;
+ m_observer = openDialogObserver;
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (m_dialog)
+ return NS_ERROR_ALREADY_INITIALIZED;
+
+ if (!dialogURL || !*dialogURL)
+ return NS_ERROR_INVALID_ARG;
+
+ if (parent)
+ {
+ // Set up window.arguments[0]...
+ nsCOMPtr<nsIMutableArray> array = nsArray::Create();
+
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ifptr->SetData(static_cast<nsIPrintProgress*>(this));
+ ifptr->SetDataIID(&NS_GET_IID(nsIPrintProgress));
+
+ array->AppendElement(ifptr, /*weak =*/ false);
+
+ array->AppendElement(parameters, /*weak = */ false);
+
+ // We will set the opener of the dialog to be the nsIDOMWindow for the
+ // browser XUL window itself, as opposed to the content. That way, the
+ // progress window has access to the opener.
+ nsCOMPtr<nsPIDOMWindowOuter> pParentWindow = nsPIDOMWindowOuter::From(parent);
+ NS_ENSURE_STATE(pParentWindow);
+
+ nsCOMPtr<nsIDocShell> docShell = pParentWindow->GetDocShell();
+ NS_ENSURE_STATE(docShell);
+
+ nsCOMPtr<nsIDocShellTreeOwner> owner;
+ docShell->GetTreeOwner(getter_AddRefs(owner));
+
+ nsCOMPtr<nsIXULWindow> ownerXULWindow = do_GetInterface(owner);
+ nsCOMPtr<mozIDOMWindowProxy> ownerWindow = do_GetInterface(ownerXULWindow);
+ NS_ENSURE_STATE(ownerWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> piOwnerWindow = nsPIDOMWindowOuter::From(ownerWindow);
+
+ // Open the dialog.
+ nsCOMPtr<nsPIDOMWindowOuter> newWindow;
+ rv = piOwnerWindow->OpenDialog(NS_ConvertASCIItoUTF16(dialogURL),
+ NS_LITERAL_STRING("_blank"),
+ NS_LITERAL_STRING("chrome,titlebar,dependent,centerscreen"),
+ array, getter_AddRefs(newWindow));
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsPrintProgress::CloseProgressDialog(bool forceClose)
+{
+ m_closeProgress = true;
+ // XXX Casting from bool to nsresult
+ return OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP,
+ static_cast<nsresult>(forceClose));
+}
+
+NS_IMETHODIMP nsPrintProgress::GetPrompter(nsIPrompt **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ if (! m_closeProgress && m_dialog) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(m_dialog);
+ MOZ_ASSERT(window);
+ return window->GetPrompter(_retval);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsPrintProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser)
+{
+ NS_ENSURE_ARG_POINTER(aProcessCanceledByUser);
+ *aProcessCanceledByUser = m_processCanceled;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser)
+{
+ m_processCanceled = aProcessCanceledByUser;
+ OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::RegisterListener(nsIWebProgressListener * listener)
+{
+ if (!listener) //Nothing to do with a null listener!
+ return NS_OK;
+
+ m_listenerList.AppendObject(listener);
+ if (m_closeProgress || m_processCanceled)
+ listener->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP, NS_OK);
+ else
+ {
+ listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get());
+ if (m_pendingStateFlags != -1)
+ listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::UnregisterListener(nsIWebProgressListener *listener)
+{
+ if (listener)
+ m_listenerList.RemoveObject(listener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::DoneIniting()
+{
+ if (m_observer) {
+ m_observer->Observe(nullptr, nullptr, nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus)
+{
+ m_pendingStateFlags = aStateFlags;
+ m_pendingStateValue = aStatus;
+
+ uint32_t count = m_listenerList.Count();
+ for (uint32_t i = count - 1; i < count; i --)
+ {
+ nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i);
+ if (progressListener)
+ progressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress)
+{
+ uint32_t count = m_listenerList.Count();
+ for (uint32_t i = count - 1; i < count; i --)
+ {
+ nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i);
+ if (progressListener)
+ progressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage)
+{
+ if (aMessage && *aMessage)
+ m_pendingStatus = aMessage;
+
+ uint32_t count = m_listenerList.Count();
+ for (uint32_t i = count - 1; i < count; i --)
+ {
+ nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i);
+ if (progressListener)
+ progressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state)
+{
+ return NS_OK;
+}
+
+nsresult nsPrintProgress::ReleaseListeners()
+{
+ m_listenerList.Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::ShowStatusString(const char16_t *status)
+{
+ return OnStatusChange(nullptr, nullptr, NS_OK, status);
+}
+
+NS_IMETHODIMP nsPrintProgress::StartMeteors()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::StopMeteors()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::ShowProgress(int32_t percent)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::SetDocShell(nsIDocShell *shell, mozIDOMWindowProxy *window)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::CloseWindow()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/components/printing/src/win/nsPrintProgress.h b/components/printing/src/win/nsPrintProgress.h
new file mode 100644
index 000000000..adc5a14cc
--- /dev/null
+++ b/components/printing/src/win/nsPrintProgress.h
@@ -0,0 +1,43 @@
+/* -*- 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 __nsPrintProgress_h
+#define __nsPrintProgress_h
+
+#include "nsIPrintProgress.h"
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMWindow.h"
+#include "nsIPrintStatusFeedback.h"
+#include "nsString.h"
+#include "nsIWindowWatcher.h"
+#include "nsIObserver.h"
+
+class nsPrintProgress : public nsIPrintProgress, public nsIPrintStatusFeedback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPRINTPROGRESS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIPRINTSTATUSFEEDBACK
+
+ nsPrintProgress();
+ virtual ~nsPrintProgress();
+
+private:
+ nsresult ReleaseListeners();
+
+ bool m_closeProgress;
+ bool m_processCanceled;
+ nsString m_pendingStatus;
+ int32_t m_pendingStateFlags;
+ nsresult m_pendingStateValue;
+ nsCOMPtr<nsIDOMWindow> m_dialog;
+ nsCOMArray<nsIWebProgressListener> m_listenerList;
+ nsCOMPtr<nsIObserver> m_observer;
+};
+
+#endif
diff --git a/components/printing/src/win/nsPrintProgressParams.cpp b/components/printing/src/win/nsPrintProgressParams.cpp
new file mode 100644
index 000000000..eba86b298
--- /dev/null
+++ b/components/printing/src/win/nsPrintProgressParams.cpp
@@ -0,0 +1,47 @@
+/* -*- 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 "nsPrintProgressParams.h"
+#include "nsReadableUtils.h"
+
+
+NS_IMPL_ISUPPORTS(nsPrintProgressParams, nsIPrintProgressParams)
+
+nsPrintProgressParams::nsPrintProgressParams()
+{
+}
+
+nsPrintProgressParams::~nsPrintProgressParams()
+{
+}
+
+NS_IMETHODIMP nsPrintProgressParams::GetDocTitle(char16_t * *aDocTitle)
+{
+ NS_ENSURE_ARG(aDocTitle);
+
+ *aDocTitle = ToNewUnicode(mDocTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgressParams::SetDocTitle(const char16_t * aDocTitle)
+{
+ mDocTitle = aDocTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgressParams::GetDocURL(char16_t * *aDocURL)
+{
+ NS_ENSURE_ARG(aDocURL);
+
+ *aDocURL = ToNewUnicode(mDocURL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgressParams::SetDocURL(const char16_t * aDocURL)
+{
+ mDocURL = aDocURL;
+ return NS_OK;
+}
+
diff --git a/components/printing/src/win/nsPrintProgressParams.h b/components/printing/src/win/nsPrintProgressParams.h
new file mode 100644
index 000000000..aebfbff7f
--- /dev/null
+++ b/components/printing/src/win/nsPrintProgressParams.h
@@ -0,0 +1,27 @@
+/* -*- 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 __nsPrintProgressParams_h
+#define __nsPrintProgressParams_h
+
+#include "nsIPrintProgressParams.h"
+#include "nsString.h"
+
+class nsPrintProgressParams : public nsIPrintProgressParams
+{
+ virtual ~nsPrintProgressParams();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTPROGRESSPARAMS
+
+ nsPrintProgressParams();
+
+private:
+ nsString mDocTitle;
+ nsString mDocURL;
+};
+
+#endif
diff --git a/components/printing/src/win/nsPrintingPromptService.cpp b/components/printing/src/win/nsPrintingPromptService.cpp
new file mode 100644
index 000000000..83727ee4e
--- /dev/null
+++ b/components/printing/src/win/nsPrintingPromptService.cpp
@@ -0,0 +1,341 @@
+/* -*- 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 "nsCOMPtr.h"
+
+#include "nsPrintingPromptService.h"
+#include "nsIPrintingPromptService.h"
+#include "nsIFactory.h"
+#include "nsPIDOMWindow.h"
+#include "nsReadableUtils.h"
+#include "nsIEmbeddingSiteWindow.h"
+#include "nsIServiceManager.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIWindowWatcher.h"
+#include "nsPrintDialogUtil.h"
+
+// Printing Progress Includes
+#include "nsPrintProgress.h"
+#include "nsPrintProgressParams.h"
+#include "nsIWebProgressListener.h"
+
+// XP Dialog includes
+#include "nsArray.h"
+#include "nsIDialogParamBlock.h"
+#include "nsISupportsUtils.h"
+
+// Includes need to locate the native Window
+#include "nsIWidget.h"
+#include "nsIBaseWindow.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+
+
+static const char *kPrintProgressDialogURL = "chrome://global/content/printProgress.xul";
+static const char *kPrtPrvProgressDialogURL = "chrome://global/content/printPreviewProgress.xul";
+static const char *kPageSetupDialogURL = "chrome://global/content/printPageSetup.xul";
+
+/****************************************************************
+ ************************* ParamBlock ***************************
+ ****************************************************************/
+
+class ParamBlock {
+
+public:
+ ParamBlock()
+ {
+ mBlock = 0;
+ }
+ ~ParamBlock()
+ {
+ NS_IF_RELEASE(mBlock);
+ }
+ nsresult Init() {
+ return CallCreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &mBlock);
+ }
+ nsIDialogParamBlock * operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mBlock; }
+ operator nsIDialogParamBlock * const () { return mBlock; }
+
+private:
+ nsIDialogParamBlock *mBlock;
+};
+
+//*****************************************************************************
+
+NS_IMPL_ISUPPORTS(nsPrintingPromptService, nsIPrintingPromptService, nsIWebProgressListener)
+
+nsPrintingPromptService::nsPrintingPromptService()
+{
+}
+
+//-----------------------------------------------------------
+nsPrintingPromptService::~nsPrintingPromptService()
+{
+}
+
+//-----------------------------------------------------------
+nsresult
+nsPrintingPromptService::Init()
+{
+ nsresult rv;
+ mWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ return rv;
+}
+
+//-----------------------------------------------------------
+HWND
+nsPrintingPromptService::GetHWNDForDOMWindow(mozIDOMWindowProxy *aWindow)
+{
+ nsCOMPtr<nsIWebBrowserChrome> chrome;
+
+ // We might be embedded so check this path first
+ if (mWatcher) {
+ nsCOMPtr<mozIDOMWindowProxy> fosterParent;
+ if (!aWindow)
+ { // it will be a dependent window. try to find a foster parent.
+ mWatcher->GetActiveWindow(getter_AddRefs(fosterParent));
+ aWindow = fosterParent;
+ }
+ mWatcher->GetChromeForWindow(aWindow, getter_AddRefs(chrome));
+ }
+
+ if (chrome) {
+ nsCOMPtr<nsIEmbeddingSiteWindow> site(do_QueryInterface(chrome));
+ if (site)
+ {
+ HWND w;
+ site->GetSiteWindow(reinterpret_cast<void **>(&w));
+ return w;
+ }
+ }
+
+ // Now we might be the Browser so check this path
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+
+ nsCOMPtr<nsIDocShellTreeItem> treeItem =
+ do_QueryInterface(window->GetDocShell());
+ if (!treeItem) return nullptr;
+
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (!treeOwner) return nullptr;
+
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome(do_GetInterface(treeOwner));
+ if (!webBrowserChrome) return nullptr;
+
+ nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(webBrowserChrome));
+ if (!baseWin) return nullptr;
+
+ nsCOMPtr<nsIWidget> widget;
+ baseWin->GetMainWidget(getter_AddRefs(widget));
+ if (!widget) return nullptr;
+
+ return (HWND)widget->GetNativeData(NS_NATIVE_TMP_WINDOW);
+
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIPrintingPromptService
+
+//-----------------------------------------------------------
+NS_IMETHODIMP
+nsPrintingPromptService::ShowPrintDialog(mozIDOMWindowProxy *parent, nsIWebBrowserPrint *webBrowserPrint, nsIPrintSettings *printSettings)
+{
+ NS_ENSURE_ARG(parent);
+
+ HWND hWnd = GetHWNDForDOMWindow(parent);
+ NS_ASSERTION(hWnd, "Couldn't get native window for PRint Dialog!");
+
+ return NativeShowPrintDialog(hWnd, webBrowserPrint, printSettings);
+}
+
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowProgress(mozIDOMWindowProxy* parent,
+ nsIWebBrowserPrint* webBrowserPrint, // ok to be null
+ nsIPrintSettings* printSettings, // ok to be null
+ nsIObserver* openDialogObserver, // ok to be null
+ bool isForPrinting,
+ nsIWebProgressListener** webProgressListener,
+ nsIPrintProgressParams** printProgressParams,
+ bool* notifyOnOpen)
+{
+ NS_ENSURE_ARG(webProgressListener);
+ NS_ENSURE_ARG(printProgressParams);
+ NS_ENSURE_ARG(notifyOnOpen);
+
+ *notifyOnOpen = false;
+ if (mPrintProgress) {
+ *webProgressListener = nullptr;
+ *printProgressParams = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ nsPrintProgress* prtProgress = new nsPrintProgress();
+ mPrintProgress = prtProgress;
+ mWebProgressListener = prtProgress;
+
+ nsCOMPtr<nsIPrintProgressParams> prtProgressParams = new nsPrintProgressParams();
+
+ nsCOMPtr<mozIDOMWindowProxy> parentWindow = parent;
+
+ if (mWatcher && !parentWindow) {
+ mWatcher->GetActiveWindow(getter_AddRefs(parentWindow));
+ }
+
+ if (parentWindow) {
+ mPrintProgress->OpenProgressDialog(parentWindow,
+ isForPrinting ? kPrintProgressDialogURL : kPrtPrvProgressDialogURL,
+ prtProgressParams, openDialogObserver, notifyOnOpen);
+ }
+
+ prtProgressParams.forget(printProgressParams);
+ NS_ADDREF(*webProgressListener = this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowPageSetup(mozIDOMWindowProxy *parent, nsIPrintSettings *printSettings, nsIObserver *aObs)
+{
+ NS_ENSURE_ARG(printSettings);
+
+ ParamBlock block;
+ nsresult rv = block.Init();
+ if (NS_FAILED(rv))
+ return rv;
+
+ block->SetInt(0, 0);
+ rv = DoDialog(parent, block, printSettings, kPageSetupDialogURL);
+
+ // if aWebBrowserPrint is not null then we are printing
+ // so we want to pass back NS_ERROR_ABORT on cancel
+ if (NS_SUCCEEDED(rv))
+ {
+ int32_t status;
+ block->GetInt(0, &status);
+ return status == 0?NS_ERROR_ABORT:NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowPrinterProperties(mozIDOMWindowProxy *parent, const char16_t *printerName, nsIPrintSettings *printSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------
+// Helper to Fly XP Dialog
+nsresult
+nsPrintingPromptService::DoDialog(mozIDOMWindowProxy *aParent,
+ nsIDialogParamBlock *aParamBlock,
+ nsIPrintSettings* aPS,
+ const char *aChromeURL)
+{
+ NS_ENSURE_ARG(aParamBlock);
+ NS_ENSURE_ARG(aPS);
+ NS_ENSURE_ARG(aChromeURL);
+
+ if (!mWatcher)
+ return NS_ERROR_FAILURE;
+
+ // get a parent, if at all possible
+ // (though we'd rather this didn't fail, it's OK if it does. so there's
+ // no failure or null check.)
+ nsCOMPtr<mozIDOMWindowProxy> activeParent; // retain ownership for method lifetime
+ if (!aParent)
+ {
+ mWatcher->GetActiveWindow(getter_AddRefs(activeParent));
+ aParent = activeParent;
+ }
+
+ // create a nsIMutableArray of the parameters
+ // being passed to the window
+ nsCOMPtr<nsIMutableArray> array = nsArray::Create();
+
+ nsCOMPtr<nsISupports> psSupports(do_QueryInterface(aPS));
+ NS_ASSERTION(psSupports, "PrintSettings must be a supports");
+ array->AppendElement(psSupports, /*weak =*/ false);
+
+ nsCOMPtr<nsISupports> blkSupps(do_QueryInterface(aParamBlock));
+ NS_ASSERTION(blkSupps, "IOBlk must be a supports");
+ array->AppendElement(blkSupps, /*weak =*/ false);
+
+ nsCOMPtr<mozIDOMWindowProxy> dialog;
+ nsresult rv = mWatcher->OpenWindow(aParent, aChromeURL, "_blank",
+ "centerscreen,chrome,modal,titlebar", array,
+ getter_AddRefs(dialog));
+
+ return rv;
+}
+
+//////////////////////////////////////////////////////////////////////
+// nsIWebProgressListener
+//////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus)
+{
+ if ((aStateFlags & STATE_STOP) && mWebProgressListener)
+ {
+ mWebProgressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
+ if (mPrintProgress)
+ {
+ mPrintProgress->CloseProgressDialog(true);
+ }
+ mPrintProgress = nullptr;
+ mWebProgressListener = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress)
+{
+ if (mWebProgressListener)
+ {
+ return mWebProgressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags)
+{
+ if (mWebProgressListener)
+ {
+ return mWebProgressListener->OnLocationChange(aWebProgress, aRequest, location, aFlags);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage)
+{
+ if (mWebProgressListener)
+ {
+ return mWebProgressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state)
+{
+ if (mWebProgressListener)
+ {
+ return mWebProgressListener->OnSecurityChange(aWebProgress, aRequest, state);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+
diff --git a/components/printing/src/win/nsPrintingPromptService.h b/components/printing/src/win/nsPrintingPromptService.h
new file mode 100644
index 000000000..76bc7a804
--- /dev/null
+++ b/components/printing/src/win/nsPrintingPromptService.h
@@ -0,0 +1,58 @@
+/* -*- 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 __nsPrintingPromptService_h
+#define __nsPrintingPromptService_h
+
+#include <windows.h>
+
+// {E042570C-62DE-4bb6-A6E0-798E3C07B4DF}
+#define NS_PRINTINGPROMPTSERVICE_CID \
+ {0xe042570c, 0x62de, 0x4bb6, { 0xa6, 0xe0, 0x79, 0x8e, 0x3c, 0x7, 0xb4, 0xdf}}
+#define NS_PRINTINGPROMPTSERVICE_CONTRACTID \
+ "@mozilla.org/embedcomp/printingprompt-service;1"
+
+#include "nsCOMPtr.h"
+#include "nsIPrintingPromptService.h"
+#include "nsPIPromptService.h"
+#include "nsIWindowWatcher.h"
+
+// Printing Progress Includes
+#include "nsPrintProgress.h"
+#include "nsPrintProgressParams.h"
+#include "nsIWebProgressListener.h"
+
+class nsIDOMWindow;
+class nsIDialogParamBlock;
+
+class nsPrintingPromptService: public nsIPrintingPromptService,
+ public nsIWebProgressListener
+{
+ virtual ~nsPrintingPromptService();
+
+public:
+ nsPrintingPromptService();
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTINGPROMPTSERVICE
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+private:
+ HWND GetHWNDForDOMWindow(mozIDOMWindowProxy *parent);
+ nsresult DoDialog(mozIDOMWindowProxy *aParent,
+ nsIDialogParamBlock *aParamBlock,
+ nsIPrintSettings* aPS,
+ const char *aChromeURL);
+
+ nsCOMPtr<nsIWindowWatcher> mWatcher;
+ nsCOMPtr<nsIPrintProgress> mPrintProgress;
+ nsCOMPtr<nsIWebProgressListener> mWebProgressListener;
+
+};
+
+#endif
+
diff --git a/components/privatebrowsing/PrivateBrowsing.manifest b/components/privatebrowsing/PrivateBrowsing.manifest
new file mode 100644
index 000000000..36b39bb85
--- /dev/null
+++ b/components/privatebrowsing/PrivateBrowsing.manifest
@@ -0,0 +1,2 @@
+component {a319b616-c45d-4037-8d86-01c592b5a9af} PrivateBrowsingTrackingProtectionWhitelist.js
+contract @mozilla.org/pbm-tp-whitelist;1 {a319b616-c45d-4037-8d86-01c592b5a9af}
diff --git a/components/privatebrowsing/PrivateBrowsingTrackingProtectionWhitelist.js b/components/privatebrowsing/PrivateBrowsingTrackingProtectionWhitelist.js
new file mode 100644
index 000000000..5c1c27874
--- /dev/null
+++ b/components/privatebrowsing/PrivateBrowsingTrackingProtectionWhitelist.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function PrivateBrowsingTrackingProtectionWhitelist() {
+ // The list of URIs explicitly excluded from tracking protection.
+ this._allowlist = [];
+
+ Services.obs.addObserver(this, "last-pb-context-exited", true);
+}
+
+PrivateBrowsingTrackingProtectionWhitelist.prototype = {
+ classID: Components.ID("{a319b616-c45d-4037-8d86-01c592b5a9af}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrivateBrowsingTrackingProtectionWhitelist,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports]),
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(PrivateBrowsingTrackingProtectionWhitelist),
+
+ /**
+ * Add the provided URI to the list of allowed tracking sites.
+ *
+ * @param uri nsIURI
+ * The URI to add to the list.
+ */
+ addToAllowList(uri) {
+ if (this._allowlist.indexOf(uri.spec) === -1) {
+ this._allowlist.push(uri.spec);
+ }
+ },
+
+ /**
+ * Remove the provided URI from the list of allowed tracking sites.
+ *
+ * @param uri nsIURI
+ * The URI to add to the list.
+ */
+ removeFromAllowList(uri) {
+ let index = this._allowlist.indexOf(uri.spec);
+ if (index !== -1) {
+ this._allowlist.splice(index, 1);
+ }
+ },
+
+ /**
+ * Check if the provided URI exists in the list of allowed tracking sites.
+ *
+ * @param uri nsIURI
+ * The URI to add to the list.
+ */
+ existsInAllowList(uri) {
+ return this._allowlist.indexOf(uri.spec) !== -1;
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic == "last-pb-context-exited") {
+ this._allowlist = [];
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PrivateBrowsingTrackingProtectionWhitelist]);
diff --git a/components/privatebrowsing/moz.build b/components/privatebrowsing/moz.build
new file mode 100644
index 000000000..834329e1d
--- /dev/null
+++ b/components/privatebrowsing/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'nsIPrivateBrowsingTrackingProtectionWhitelist.idl',
+]
+
+XPIDL_MODULE = 'privatebrowsing'
+
+EXTRA_COMPONENTS += [
+ 'PrivateBrowsing.manifest',
+ 'PrivateBrowsingTrackingProtectionWhitelist.js',
+]
diff --git a/components/privatebrowsing/nsIPrivateBrowsingTrackingProtectionWhitelist.idl b/components/privatebrowsing/nsIPrivateBrowsingTrackingProtectionWhitelist.idl
new file mode 100644
index 000000000..d572b4e7e
--- /dev/null
+++ b/components/privatebrowsing/nsIPrivateBrowsingTrackingProtectionWhitelist.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * The Private Browsing Tracking Protection service checks a URI against an
+ * in-memory list of tracking sites.
+ */
+[scriptable, uuid(c77ddfac-6cd6-43a9-84e8-91682a1a7b18)]
+interface nsIPrivateBrowsingTrackingProtectionWhitelist : nsISupports
+{
+ /**
+ * Add a URI to the list of allowed tracking sites in Private Browsing mode
+ * (essentially a tracking whitelist). This operation will cause the URI to
+ * be registered if it does not currently exist. If it already exists, then
+ * the operation is essentially a no-op.
+ *
+ * @param uri the uri to add to the list
+ */
+ void addToAllowList(in nsIURI uri);
+
+ /**
+ * Remove a URI from the list of allowed tracking sites in Private Browsing
+ * mode (the tracking whitelist). If the URI is not already in the list,
+ * then the operation is essentially a no-op.
+ *
+ * @param uri the uri to remove from the list
+ */
+ void removeFromAllowList(in nsIURI uri);
+
+ /**
+ * Check if a URI exists in the list of allowed tracking sites in Private
+ * Browsing mode (the tracking whitelist).
+ *
+ * @param uri the uri to look for in the list
+ */
+ bool existsInAllowList(in nsIURI uri);
+};
+
+%{ C++
+#define NS_PBTRACKINGPROTECTIONWHITELIST_CONTRACTID "@mozilla.org/pbm-tp-whitelist;1"
+%}
diff --git a/components/processsingleton/ContentProcessSingleton.js b/components/processsingleton/ContentProcessSingleton.js
new file mode 100644
index 000000000..72f5803e1
--- /dev/null
+++ b/components/processsingleton/ContentProcessSingleton.js
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+ "@mozilla.org/childprocessmessagemanager;1",
+ "nsIMessageSender");
+
+/*
+ * The message manager has an upper limit on message sizes that it can
+ * reliably forward to the parent so we limit the size of console log event
+ * messages that we forward here. The web console is local and receives the
+ * full console message, but addons subscribed to console event messages
+ * in the parent receive the truncated version. Due to fragmentation,
+ * messages as small as 1MB have resulted in IPC allocation failures on
+ * 32-bit platforms. To limit IPC allocation sizes, console.log messages
+ * with arguments with total size > MSG_MGR_CONSOLE_MAX_SIZE (bytes) have
+ * their arguments completely truncated. MSG_MGR_CONSOLE_VAR_SIZE is an
+ * approximation of how much space (in bytes) a JS non-string variable will
+ * require in the manager's implementation. For strings, we use 2 bytes per
+ * char. The console message URI and function name are limited to
+ * MSG_MGR_CONSOLE_INFO_MAX characters. We don't attempt to calculate
+ * the exact amount of space the message manager implementation will require
+ * for a given message so this is imperfect.
+ */
+const MSG_MGR_CONSOLE_MAX_SIZE = 1024 * 1024; // 1MB
+const MSG_MGR_CONSOLE_VAR_SIZE = 8;
+const MSG_MGR_CONSOLE_INFO_MAX = 1024;
+
+function ContentProcessSingleton() {}
+ContentProcessSingleton.prototype = {
+ classID: Components.ID("{ca2a8470-45c7-11e4-916c-0800200c9a66}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "app-startup": {
+ Services.obs.addObserver(this, "console-api-log-event", false);
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ cpmm.addMessageListener("DevTools:InitDebuggerServer", this);
+ break;
+ }
+ case "console-api-log-event": {
+ let consoleMsg = subject.wrappedJSObject;
+
+ let msgData = {
+ level: consoleMsg.level,
+ filename: consoleMsg.filename.substring(0, MSG_MGR_CONSOLE_INFO_MAX),
+ lineNumber: consoleMsg.lineNumber,
+ functionName: consoleMsg.functionName.substring(0,
+ MSG_MGR_CONSOLE_INFO_MAX),
+ timeStamp: consoleMsg.timeStamp,
+ arguments: [],
+ };
+
+ // We can't send objects over the message manager, so we sanitize
+ // them out, replacing those arguments with "<unavailable>".
+ let unavailString = "<unavailable>";
+ let unavailStringLength = unavailString.length * 2; // 2-bytes per char
+
+ // When the sum of argument sizes reaches MSG_MGR_CONSOLE_MAX_SIZE,
+ // replace all arguments with "<truncated>".
+ let totalArgLength = 0;
+
+ // Walk through the arguments, checking the type and size.
+ for (let arg of consoleMsg.arguments) {
+ if ((typeof arg == "object" || typeof arg == "function") &&
+ arg !== null) {
+ arg = unavailString;
+ totalArgLength += unavailStringLength;
+ } else if (typeof arg == "string") {
+ totalArgLength += arg.length * 2; // 2-bytes per char
+ } else {
+ totalArgLength += MSG_MGR_CONSOLE_VAR_SIZE;
+ }
+
+ if (totalArgLength <= MSG_MGR_CONSOLE_MAX_SIZE) {
+ msgData.arguments.push(arg);
+ } else {
+ // arguments take up too much space
+ msgData.arguments = ["<truncated>"];
+ break;
+ }
+ }
+
+ cpmm.sendAsyncMessage("Console:Log", msgData);
+ break;
+ }
+
+ case "xpcom-shutdown":
+ Services.obs.removeObserver(this, "console-api-log-event");
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ cpmm.removeMessageListener("DevTools:InitDebuggerServer", this);
+ break;
+ }
+ },
+
+ receiveMessage: function (message) {
+ // load devtools component on-demand
+ // Only reply if we are in a real content process
+ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+ let {init} = Cu.import("resource://devtools/server/content-server.jsm", {});
+ init(message);
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentProcessSingleton]);
diff --git a/components/processsingleton/MainProcessSingleton.js b/components/processsingleton/MainProcessSingleton.js
new file mode 100644
index 000000000..82beff508
--- /dev/null
+++ b/components/processsingleton/MainProcessSingleton.js
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { utils: Cu, interfaces: Ci, classes: Cc, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+
+function MainProcessSingleton() {}
+MainProcessSingleton.prototype = {
+ classID: Components.ID("{0636a680-45cb-11e4-916c-0800200c9a66}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ logConsoleMessage: function(message) {
+ let logMsg = message.data;
+ logMsg.wrappedJSObject = logMsg;
+ Services.obs.notifyObservers(logMsg, "console-api-log-event", null);
+ },
+
+ // Called when a webpage calls window.external.AddSearchProvider
+ addSearchEngine: function({ target: browser, data: { pageURL, engineURL } }) {
+ pageURL = NetUtil.newURI(pageURL);
+ engineURL = NetUtil.newURI(engineURL, null, pageURL);
+
+ let iconURL;
+ let tabbrowser = browser.getTabBrowser();
+ if (browser.mIconURL && (!tabbrowser || tabbrowser.shouldLoadFavIcon(pageURL)))
+ iconURL = NetUtil.newURI(browser.mIconURL);
+
+ try {
+ // Make sure the URLs are HTTP, HTTPS, or FTP.
+ let isWeb = ["https", "http", "ftp"];
+
+ if (isWeb.indexOf(engineURL.scheme) < 0)
+ throw "Unsupported search engine URL: " + engineURL;
+
+ if (iconURL && isWeb.indexOf(iconURL.scheme) < 0)
+ throw "Unsupported search icon URL: " + iconURL;
+ }
+ catch (ex) {
+ Cu.reportError("Invalid argument passed to window.external.AddSearchProvider: " + ex);
+
+ var searchBundle = Services.strings.createBundle("chrome://global/locale/search/search.properties");
+ var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ var brandName = brandBundle.GetStringFromName("brandShortName");
+ var title = searchBundle.GetStringFromName("error_invalid_engine_title");
+ var msg = searchBundle.formatStringFromName("error_invalid_engine_msg",
+ [brandName], 1);
+ Services.ww.getNewPrompter(browser.ownerDocument.defaultView).alert(title, msg);
+ return;
+ }
+
+ Services.search.init(function(status) {
+ if (status != Cr.NS_OK)
+ return;
+
+ Services.search.addEngine(engineURL.spec, null, iconURL ? iconURL.spec : null, true);
+ })
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "app-startup": {
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+
+ // Load this script early so that console.* is initialized
+ // before other frame scripts.
+ Services.mm.loadFrameScript("chrome://global/content/browser-content.js", true);
+ Services.ppmm.loadProcessScript("chrome://global/content/process-content.js", true);
+ Services.ppmm.addMessageListener("Console:Log", this.logConsoleMessage);
+ Services.mm.addMessageListener("Search:AddEngine", this.addSearchEngine);
+ break;
+ }
+
+ case "xpcom-shutdown":
+ Services.ppmm.removeMessageListener("Console:Log", this.logConsoleMessage);
+ Services.mm.removeMessageListener("Search:AddEngine", this.addSearchEngine);
+ break;
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MainProcessSingleton]);
diff --git a/components/processsingleton/ProcessSingleton.manifest b/components/processsingleton/ProcessSingleton.manifest
new file mode 100644
index 000000000..7a882ed7b
--- /dev/null
+++ b/components/processsingleton/ProcessSingleton.manifest
@@ -0,0 +1,7 @@
+component {0636a680-45cb-11e4-916c-0800200c9a66} MainProcessSingleton.js process=main
+contract @mozilla.org/main-process-singleton;1 {0636a680-45cb-11e4-916c-0800200c9a66} process=main
+category app-startup MainProcessSingleton service,@mozilla.org/main-process-singleton;1 process=main
+
+component {ca2a8470-45c7-11e4-916c-0800200c9a66} ContentProcessSingleton.js process=content
+contract @mozilla.org/content-process-singleton;1 {ca2a8470-45c7-11e4-916c-0800200c9a66} process=content
+category app-startup ContentProcessSingleton service,@mozilla.org/content-process-singleton;1 process=content
diff --git a/components/processsingleton/moz.build b/components/processsingleton/moz.build
new file mode 100644
index 000000000..9da775820
--- /dev/null
+++ b/components/processsingleton/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'ContentProcessSingleton.js',
+ 'MainProcessSingleton.js',
+ 'ProcessSingleton.manifest',
+]
diff --git a/components/profile/content/createProfileWizard.js b/components/profile/content/createProfileWizard.js
new file mode 100644
index 000000000..f378f3676
--- /dev/null
+++ b/components/profile/content/createProfileWizard.js
@@ -0,0 +1,220 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 C = Components.classes;
+const I = Components.interfaces;
+
+const ToolkitProfileService = "@mozilla.org/toolkit/profile-service;1";
+
+var gProfileService;
+var gProfileManagerBundle;
+
+var gDefaultProfileParent;
+
+// The directory where the profile will be created.
+var gProfileRoot;
+
+// Text node to display the location and name of the profile to create.
+var gProfileDisplay;
+
+// Called once when the wizard is opened.
+function initWizard()
+{
+ try {
+ gProfileService = C[ToolkitProfileService].getService(I.nsIToolkitProfileService);
+ gProfileManagerBundle = document.getElementById("bundle_profileManager");
+
+ var dirService = C["@mozilla.org/file/directory_service;1"].getService(I.nsIProperties);
+ gDefaultProfileParent = dirService.get("DefProfRt", I.nsIFile);
+
+ // Initialize the profile location display.
+ gProfileDisplay = document.getElementById("profileDisplay").firstChild;
+ setDisplayToDefaultFolder();
+ }
+ catch (e) {
+ window.close();
+ throw (e);
+ }
+}
+
+// Called every time the second wizard page is displayed.
+function initSecondWizardPage()
+{
+ var profileName = document.getElementById("profileName");
+ profileName.select();
+ profileName.focus();
+
+ // Initialize profile name validation.
+ checkCurrentInput(profileName.value);
+}
+
+const kSaltTable = [
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' ];
+
+var kSaltString = "";
+for (var i = 0; i < 8; ++i) {
+ kSaltString += kSaltTable[Math.floor(Math.random() * kSaltTable.length)];
+}
+
+
+function saltName(aName)
+{
+ return kSaltString + "." + aName;
+}
+
+function setDisplayToDefaultFolder()
+{
+ var defaultProfileDir = gDefaultProfileParent.clone();
+ defaultProfileDir.append(saltName(document.getElementById("profileName").value));
+ gProfileRoot = defaultProfileDir;
+ document.getElementById("useDefault").disabled = true;
+}
+
+function updateProfileDisplay()
+{
+ gProfileDisplay.data = gProfileRoot.path;
+}
+
+// Invoke a folder selection dialog for choosing the directory of profile storage.
+function chooseProfileFolder()
+{
+ var newProfileRoot;
+
+ var dirChooser = C["@mozilla.org/filepicker;1"].createInstance(I.nsIFilePicker);
+ dirChooser.init(window, gProfileManagerBundle.getString("chooseFolder"),
+ I.nsIFilePicker.modeGetFolder);
+ dirChooser.appendFilters(I.nsIFilePicker.filterAll);
+
+ // default to the Profiles folder
+ dirChooser.displayDirectory = gDefaultProfileParent;
+
+ dirChooser.show();
+ newProfileRoot = dirChooser.file;
+
+ if (newProfileRoot) {
+ // Disable the "Default Folder..." button when the default profile folder
+ // was selected manually in the File Picker.
+ document.getElementById("useDefault").disabled =
+ (newProfileRoot.parent.equals(gDefaultProfileParent));
+
+ gProfileRoot = newProfileRoot;
+ updateProfileDisplay();
+ }
+}
+
+// Checks the current user input for validity and triggers an error message accordingly.
+function checkCurrentInput(currentInput)
+{
+ var finishButton = document.documentElement.getButton("finish");
+ var finishText = document.getElementById("finishText");
+ var canAdvance;
+
+ var errorMessage = checkProfileName(currentInput);
+
+ if (!errorMessage) {
+ finishText.className = "";
+ finishText.firstChild.data = gProfileManagerBundle.getString("profileFinishText");
+ canAdvance = true;
+ }
+ else {
+ finishText.className = "error";
+ finishText.firstChild.data = errorMessage;
+ canAdvance = false;
+ }
+
+ document.documentElement.canAdvance = canAdvance;
+ finishButton.disabled = !canAdvance;
+
+ updateProfileDisplay();
+
+ return canAdvance;
+}
+
+function updateProfileName(aNewName)
+{
+ if (checkCurrentInput(aNewName)) {
+ gProfileRoot.leafName = saltName(aNewName);
+ updateProfileDisplay();
+ }
+}
+
+// Checks whether the given string is a valid profile name.
+// Returns an error message describing the error in the name or "" when it's valid.
+function checkProfileName(profileNameToCheck)
+{
+ // Check for emtpy profile name.
+ if (!/\S/.test(profileNameToCheck))
+ return gProfileManagerBundle.getString("profileNameEmpty");
+
+ // Check whether all characters in the profile name are allowed.
+ if (/([\\*:?<>|\/\"])/.test(profileNameToCheck))
+ return gProfileManagerBundle.getFormattedString("invalidChar", [RegExp.$1]);
+
+ // Check whether a profile with the same name already exists.
+ if (profileExists(profileNameToCheck))
+ return gProfileManagerBundle.getString("profileExists");
+
+ // profileNameToCheck is valid.
+ return "";
+}
+
+function profileExists(aName)
+{
+ var profiles = gProfileService.profiles;
+ while (profiles.hasMoreElements()) {
+ var profile = profiles.getNext().QueryInterface(I.nsIToolkitProfile);
+ if (profile.name.toLowerCase() == aName.toLowerCase())
+ return true;
+ }
+
+ return false;
+}
+
+// Called when the first wizard page is shown.
+function enableNextButton()
+{
+ document.documentElement.canAdvance = true;
+}
+
+function onFinish()
+{
+ var profileName = document.getElementById("profileName").value;
+ var profile;
+
+ // Create profile named profileName in profileRoot.
+ try {
+ profile = gProfileService.createProfile(gProfileRoot, profileName);
+ }
+ catch (e) {
+ var profileCreationFailed =
+ gProfileManagerBundle.getString("profileCreationFailed");
+ var profileCreationFailedTitle =
+ gProfileManagerBundle.getString("profileCreationFailedTitle");
+ var promptService = C["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(I.nsIPromptService);
+ promptService.alert(window, profileCreationFailedTitle,
+ profileCreationFailed + "\n" + e);
+
+ return false;
+ }
+
+ // window.opener is false if the Create Profile Wizard was opened from the
+ // command line.
+ if (window.opener) {
+ // Add new profile to the list in the Profile Manager.
+ window.opener.CreateProfile(profile);
+ }
+ else {
+ // Use the newly created Profile.
+ var profileLock = profile.lock(null);
+
+ var dialogParams = window.arguments[0].QueryInterface(I.nsIDialogParamBlock);
+ dialogParams.objects.insertElementAt(profileLock, 0, false);
+ }
+
+ // Exit the wizard.
+ return true;
+}
diff --git a/components/profile/content/createProfileWizard.xul b/components/profile/content/createProfileWizard.xul
new file mode 100644
index 000000000..c7e0702a6
--- /dev/null
+++ b/components/profile/content/createProfileWizard.xul
@@ -0,0 +1,70 @@
+<?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 wizard [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % profileDTD SYSTEM "chrome://mozapps/locale/profile/createProfileWizard.dtd">
+%profileDTD;
+]>
+
+<wizard id="createProfileWizard"
+ title="&newprofile.title;"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onwizardfinish="return onFinish();"
+ onload="initWizard();"
+ style="&window.size;">
+
+ <stringbundle id="bundle_profileManager"
+ src="chrome://mozapps/locale/profile/profileSelection.properties"/>
+
+ <script type="application/javascript" src="chrome://mozapps/content/profile/createProfileWizard.js"/>
+
+ <wizardpage id="explanation" onpageshow="enableNextButton();">
+ <description>&profileCreationExplanation_1.text;</description>
+ <description>&profileCreationExplanation_2.text;</description>
+ <description>&profileCreationExplanation_3.text;</description>
+ <spacer flex="1"/>
+#ifdef XP_UNIX
+ <description>&profileCreationExplanation_4Gnome.text;</description>
+#else
+ <description>&profileCreationExplanation_4.text;</description>
+#endif
+ </wizardpage>
+
+ <wizardpage id="createProfile" onpageshow="initSecondWizardPage();">
+ <description>&profileCreationIntro.text;</description>
+
+ <label accesskey="&profilePrompt.accesskey;" control="ProfileName">&profilePrompt.label;</label>
+ <textbox id="profileName" value="&profileDefaultName;"
+ oninput="updateProfileName(this.value);"/>
+
+ <separator/>
+
+ <description>&profileDirectoryExplanation.text;</description>
+
+ <vbox class="indent" flex="1" style="overflow: auto;">
+ <description id="profileDisplay">*</description>
+ </vbox>
+
+ <hbox>
+ <button label="&button.choosefolder.label;" oncommand="chooseProfileFolder();"
+ accesskey="&button.choosefolder.accesskey;"/>
+
+ <button id="useDefault" label="&button.usedefault.label;"
+ oncommand="setDisplayToDefaultFolder(); updateProfileDisplay();"
+ accesskey="&button.usedefault.accesskey;" disabled="true"/>
+ </hbox>
+
+ <separator/>
+
+ <description id="finishText">*</description>
+ </wizardpage>
+
+</wizard>
diff --git a/components/profile/content/profileSelection.js b/components/profile/content/profileSelection.js
new file mode 100644
index 000000000..9fb77dfcd
--- /dev/null
+++ b/components/profile/content/profileSelection.js
@@ -0,0 +1,267 @@
+/* -*- 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/Services.jsm");
+
+const C = Components.classes;
+const I = Components.interfaces;
+
+const ToolkitProfileService = "@mozilla.org/toolkit/profile-service;1";
+
+var gDialogParams;
+var gProfileManagerBundle;
+var gBrandBundle;
+var gProfileService;
+
+function startup()
+{
+ try {
+ gDialogParams = window.arguments[0].
+ QueryInterface(I.nsIDialogParamBlock);
+
+ gProfileService = C[ToolkitProfileService].getService(I.nsIToolkitProfileService);
+
+ gProfileManagerBundle = document.getElementById("bundle_profileManager");
+ gBrandBundle = document.getElementById("bundle_brand");
+
+ document.documentElement.centerWindowOnScreen();
+
+ var profilesElement = document.getElementById("profiles");
+
+ var profileList = gProfileService.profiles;
+ while (profileList.hasMoreElements()) {
+ var profile = profileList.getNext().QueryInterface(I.nsIToolkitProfile);
+
+ var listitem = profilesElement.appendItem(profile.name, "");
+
+ var tooltiptext =
+ gProfileManagerBundle.getFormattedString("profileTooltip", [profile.name, profile.rootDir.path]);
+ listitem.setAttribute("tooltiptext", tooltiptext);
+ listitem.setAttribute("class", "listitem-iconic");
+ listitem.profile = profile;
+ try {
+ if (profile === gProfileService.selectedProfile) {
+ setTimeout(function(a) {
+ profilesElement.ensureElementIsVisible(a);
+ profilesElement.selectItem(a);
+ }, 0, listitem);
+ }
+ }
+ catch (e) { }
+ }
+
+ var autoSelectLastProfile = document.getElementById("autoSelectLastProfile");
+ autoSelectLastProfile.checked = gProfileService.startWithLastProfile;
+ profilesElement.focus();
+ }
+ catch (e) {
+ window.close();
+ throw (e);
+ }
+}
+
+function acceptDialog()
+{
+ var appName = gBrandBundle.getString("brandShortName");
+
+ var profilesElement = document.getElementById("profiles");
+ var selectedProfile = profilesElement.selectedItem;
+ if (!selectedProfile) {
+ var pleaseSelectTitle = gProfileManagerBundle.getString("pleaseSelectTitle");
+ var pleaseSelect =
+ gProfileManagerBundle.getFormattedString("pleaseSelect", [appName]);
+ Services.prompt.alert(window, pleaseSelectTitle, pleaseSelect);
+
+ return false;
+ }
+
+ var profileLock;
+
+ try {
+ profileLock = selectedProfile.profile.lock({ value: null });
+ }
+ catch (e) {
+ if (!selectedProfile.profile.rootDir.exists()) {
+ var missingTitle = gProfileManagerBundle.getString("profileMissingTitle");
+ var missing =
+ gProfileManagerBundle.getFormattedString("profileMissing", [appName]);
+ Services.prompt.alert(window, missingTitle, missing);
+ return false;
+ }
+
+ var lockedTitle = gProfileManagerBundle.getString("profileLockedTitle");
+ var locked =
+ gProfileManagerBundle.getFormattedString("profileLocked2", [appName, selectedProfile.profile.name, appName]);
+ Services.prompt.alert(window, lockedTitle, locked);
+
+ return false;
+ }
+ gDialogParams.objects.insertElementAt(profileLock.nsIProfileLock, 0, false);
+
+ gProfileService.selectedProfile = selectedProfile.profile;
+ gProfileService.defaultProfile = selectedProfile.profile;
+ updateStartupPrefs();
+
+ gDialogParams.SetInt(0, 1);
+
+ gDialogParams.SetString(0, selectedProfile.profile.name);
+
+ return true;
+}
+
+function exitDialog()
+{
+ updateStartupPrefs();
+
+ return true;
+}
+
+function updateStartupPrefs()
+{
+ var autoSelectLastProfile = document.getElementById("autoSelectLastProfile");
+ gProfileService.startWithLastProfile = autoSelectLastProfile.checked;
+
+ /* Bug 257777 */
+ gProfileService.startOffline = document.getElementById("offlineState").checked;
+}
+
+// handle key event on listboxes
+function onProfilesKey(aEvent)
+{
+ switch ( aEvent.keyCode )
+ {
+ case KeyEvent.DOM_VK_BACK_SPACE:
+ break;
+ case KeyEvent.DOM_VK_DELETE:
+ ConfirmDelete();
+ break;
+ case KeyEvent.DOM_VK_F2:
+ RenameProfile();
+ break;
+ }
+}
+
+function onProfilesDblClick(aEvent)
+{
+ if (aEvent.target.localName == "listitem")
+ document.documentElement.acceptDialog();
+}
+
+// invoke the createProfile Wizard
+function CreateProfileWizard()
+{
+ window.openDialog('chrome://mozapps/content/profile/createProfileWizard.xul',
+ '', 'centerscreen,chrome,modal,titlebar', gProfileService);
+}
+
+/**
+ * Called from createProfileWizard to update the display.
+ */
+function CreateProfile(aProfile)
+{
+ var profilesElement = document.getElementById("profiles");
+
+ var listitem = profilesElement.appendItem(aProfile.name, "");
+
+ var tooltiptext =
+ gProfileManagerBundle.getFormattedString("profileTooltip", [aProfile.name, aProfile.rootDir.path]);
+ listitem.setAttribute("tooltiptext", tooltiptext);
+ listitem.setAttribute("class", "listitem-iconic");
+ listitem.profile = aProfile;
+
+ profilesElement.ensureElementIsVisible(listitem);
+ profilesElement.selectItem(listitem);
+}
+
+// rename the selected profile
+function RenameProfile()
+{
+ var profilesElement = document.getElementById("profiles");
+ var selectedItem = profilesElement.selectedItem;
+ if (!selectedItem) {
+ return false;
+ }
+
+ var selectedProfile = selectedItem.profile;
+
+ var oldName = selectedProfile.name;
+ var newName = {value: oldName};
+
+ var dialogTitle = gProfileManagerBundle.getString("renameProfileTitle");
+ var msg =
+ gProfileManagerBundle.getFormattedString("renameProfilePrompt", [oldName]);
+
+ if (Services.prompt.prompt(window, dialogTitle, msg, newName, null, {value:0})) {
+ newName = newName.value;
+
+ // User hasn't changed the profile name. Treat as if cancel was pressed.
+ if (newName == oldName)
+ return false;
+
+ try {
+ selectedProfile.name = newName;
+ }
+ catch (e) {
+ var alTitle = gProfileManagerBundle.getString("profileNameInvalidTitle");
+ var alMsg = gProfileManagerBundle.getFormattedString("profileNameInvalid", [newName]);
+ Services.prompt.alert(window, alTitle, alMsg);
+ return false;
+ }
+
+ selectedItem.label = newName;
+ var tiptext = gProfileManagerBundle.
+ getFormattedString("profileTooltip",
+ [newName, selectedProfile.rootDir.path]);
+ selectedItem.setAttribute("tooltiptext", tiptext);
+
+ return true;
+ }
+
+ return false;
+}
+
+function ConfirmDelete()
+{
+ var deleteButton = document.getElementById("delbutton");
+ var profileList = document.getElementById( "profiles" );
+
+ var selectedItem = profileList.selectedItem;
+ if (!selectedItem) {
+ return false;
+ }
+
+ var selectedProfile = selectedItem.profile;
+ var deleteFiles = false;
+
+ if (selectedProfile.rootDir.exists()) {
+ var dialogTitle = gProfileManagerBundle.getString("deleteTitle");
+ var dialogText =
+ gProfileManagerBundle.getFormattedString("deleteProfileConfirm",
+ [selectedProfile.rootDir.path]);
+
+ var buttonPressed = Services.prompt.confirmEx(window, dialogTitle, dialogText,
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1) +
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2),
+ gProfileManagerBundle.getString("dontDeleteFiles"),
+ null,
+ gProfileManagerBundle.getString("deleteFiles"),
+ null, {value:0});
+ if (buttonPressed == 1)
+ return false;
+
+ if (buttonPressed == 2)
+ deleteFiles = true;
+ }
+
+ selectedProfile.remove(deleteFiles);
+ profileList.removeChild(selectedItem);
+ if (profileList.firstChild != undefined) {
+ profileList.selectItem(profileList.firstChild);
+ }
+
+ return true;
+}
diff --git a/components/profile/content/profileSelection.xul b/components/profile/content/profileSelection.xul
new file mode 100644
index 000000000..e5dfabb42
--- /dev/null
+++ b/components/profile/content/profileSelection.xul
@@ -0,0 +1,70 @@
+<?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://mozapps/skin/profile/profileSelection.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % profileDTD SYSTEM "chrome://mozapps/locale/profile/profileSelection.dtd">
+%profileDTD;
+]>
+
+<dialog
+ id="profileWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ class="non-resizable"
+ title="&windowtitle.label;"
+ orient="vertical"
+ buttons="accept,cancel"
+ style="width: 30em;"
+ onload="startup();"
+ ondialogaccept="return acceptDialog()"
+ ondialogcancel="return exitDialog()"
+ buttonlabelaccept="&start.label;"
+ buttonlabelcancel="&exit.label;">
+
+ <stringbundle id="bundle_profileManager"
+ src="chrome://mozapps/locale/profile/profileSelection.properties"/>
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+
+ <script type="application/javascript" src="chrome://mozapps/content/profile/profileSelection.js"/>
+
+ <description class="label">&pmDescription.label;</description>
+
+ <separator class="thin"/>
+
+ <hbox class="profile-box indent" flex="1">
+
+ <vbox id="managebuttons">
+ <button id="newbutton" label="&newButton.label;"
+ accesskey="&newButton.accesskey;" oncommand="CreateProfileWizard();"/>
+ <button id="renbutton" label="&renameButton.label;"
+ accesskey="&renameButton.accesskey;" oncommand="RenameProfile();"/>
+ <button id="delbutton" label="&deleteButton.label;"
+ accesskey="&deleteButton.accesskey;" oncommand="ConfirmDelete();"/>
+ </vbox>
+
+ <separator flex="1"/>
+
+ <vbox flex="1">
+ <listbox id="profiles" rows="5" seltype="single"
+ ondblclick="onProfilesDblClick(event)"
+ onkeypress="onProfilesKey(event);">
+ </listbox>
+
+ <!-- Bug 257777 -->
+ <checkbox id="offlineState" label="&offlineState.label;" accesskey="&offlineState.accesskey;"/>
+
+ <checkbox id="autoSelectLastProfile" label="&useSelected.label;"
+ accesskey="&useSelected.accesskey;"/>
+ </vbox>
+
+ </hbox>
+</dialog>
diff --git a/components/profile/jar.mn b/components/profile/jar.mn
new file mode 100644
index 000000000..9b7c22266
--- /dev/null
+++ b/components/profile/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/.
+
+toolkit.jar:
+ content/mozapps/profile/createProfileWizard.js (content/createProfileWizard.js)
+* content/mozapps/profile/createProfileWizard.xul (content/createProfileWizard.xul)
+ content/mozapps/profile/profileSelection.js (content/profileSelection.js)
+ content/mozapps/profile/profileSelection.xul (content/profileSelection.xul)
diff --git a/components/profile/locale/createProfileWizard.dtd b/components/profile/locale/createProfileWizard.dtd
new file mode 100644
index 000000000..55384350a
--- /dev/null
+++ b/components/profile/locale/createProfileWizard.dtd
@@ -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/. -->
+
+<!ENTITY newprofile.title "Create Profile Wizard">
+<!ENTITY window.size "width: 45em; height: 32em;">
+
+<!-- First wizard page -->
+<!ENTITY profileCreationExplanation_1.text "&brandShortName; stores information about your settings and preferences in your personal profile.">
+<!ENTITY profileCreationExplanation_2.text "If you are sharing this copy of &brandShortName; with other users, you can use profiles to keep each user’s information separate. To do this, each user should create his or her own profile.">
+<!ENTITY profileCreationExplanation_3.text "If you are the only person using this copy of &brandShortName;, you must have at least one profile. If you would like, you can create multiple profiles for yourself to store different sets of settings and preferences. For example, you may want to have separate profiles for business and personal use.">
+<!ENTITY profileCreationExplanation_4.text "To begin creating your profile, click Next.">
+<!ENTITY profileCreationExplanation_4Mac.text "To begin creating your profile, click Continue.">
+<!ENTITY profileCreationExplanation_4Gnome.text "To begin creating your profile, click Next.">
+
+<!-- Second wizard page -->
+<!ENTITY profileCreationIntro.text "If you create several profiles you can tell them apart by the profile names. You may use the name provided here or use one of your own.">
+<!ENTITY profilePrompt.label "Enter new profile name:">
+<!ENTITY profilePrompt.accesskey "E">
+<!ENTITY profileDirectoryExplanation.text "Your user settings, preferences and other user-related data will be stored in:">
+<!ENTITY profileDefaultName "Default User">
+<!ENTITY button.choosefolder.label "Choose Folder…">
+<!ENTITY button.choosefolder.accesskey "C">
+<!ENTITY button.usedefault.label "Use Default Folder">
+<!ENTITY button.usedefault.accesskey "U">
diff --git a/components/profile/locale/profileSelection.dtd b/components/profile/locale/profileSelection.dtd
new file mode 100644
index 000000000..87741a82e
--- /dev/null
+++ b/components/profile/locale/profileSelection.dtd
@@ -0,0 +1,31 @@
+<!-- -*- 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/. -->
+
+<!ENTITY windowtitle.label "&brandShortName; - Choose User Profile">
+
+<!ENTITY profilename.label "Profile Name:">
+
+<!ENTITY start.label "Start &brandShortName;">
+<!ENTITY exit.label "Exit">
+
+<!ENTITY availprofiles.label "Available Profiles">
+
+<!ENTITY newButton.label "Create Profile…">
+<!ENTITY newButton.accesskey "C">
+<!ENTITY renameButton.label "Rename Profile…">
+<!ENTITY renameButton.accesskey "R">
+<!ENTITY deleteButton.label "Delete Profile…">
+<!ENTITY deleteButton.accesskey "D">
+
+<!-- manager entities -->
+<!ENTITY pmDescription.label "&brandShortName; stores information about your settings, preferences, and other user items in your user profile.">
+
+<!ENTITY offlineState.label "Work offline">
+<!ENTITY offlineState.accesskey "o">
+
+<!ENTITY useSelected.label "Use the selected profile without asking at startup">
+<!ENTITY useSelected.accesskey "s">
diff --git a/components/profile/locale/profileSelection.properties b/components/profile/locale/profileSelection.properties
new file mode 100644
index 000000000..e36353a93
--- /dev/null
+++ b/components/profile/locale/profileSelection.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/.
+
+# LOCALIZATION NOTE: These strings are used for startup/profile problems and the profile manager.
+
+# Application not responding
+# LOCALIZATION NOTE (restartTitle, restartMessageNoUnlocker, restartMessageUnlocker, restartMessageNoUnlockerMac, restartMessageUnlockerMac): Messages displayed when the application is running but is not responding to commands. %S is the application name.
+restartTitle=Close %S
+restartMessageNoUnlocker=%S is already running, but is not responding. To open a new window, you must first close the existing %S process, or restart your system.
+restartMessageUnlocker=%S is already running, but is not responding. The old %S process must be closed to open a new window.
+restartMessageNoUnlockerMac=A copy of %S is already open. Only one copy of %S can be open at a time.
+restartMessageUnlockerMac=A copy of %S is already open. The running copy of %S will quit in order to open this one.
+
+# Profile manager
+# LOCALIZATION NOTE (profileTooltip): First %S is the profile name, second %S is the path to the profile folder.
+profileTooltip=Profile: ‘%S’ - Path: ‘%S’
+
+pleaseSelectTitle=Select Profile
+pleaseSelect=Please select a profile to begin %S, or create a new profile.
+
+profileLockedTitle=Profile In Use
+profileLocked2=%S cannot use the profile “%S†because it is in use.\n\nTo continue, close the running instance of %S or choose a different profile.
+
+renameProfileTitle=Rename Profile
+renameProfilePrompt=Rename the profile “%S†to:
+
+profileNameInvalidTitle=Invalid profile name
+profileNameInvalid=The profile name “%S†is not allowed.
+
+chooseFolder=Choose Profile Folder
+profileNameEmpty=An empty profile name is not allowed.
+invalidChar=The character “%S†is not allowed in profile names. Please choose a different name.
+
+deleteTitle=Delete Profile
+deleteProfileConfirm=Deleting a profile will remove the profile from the list of available profiles and cannot be undone.\nYou may also choose to delete the profile data files, including your settings, certificates and other user-related data. This option will delete the folder “%S†and cannot be undone.\nWould you like to delete the profile data files?
+deleteFiles=Delete Files
+dontDeleteFiles=Don’t Delete Files
+
+profileCreationFailed=Profile couldn’t be created. Probably the chosen folder isn’t writable.
+profileCreationFailedTitle=Profile Creation failed
+profileExists=A profile with this name already exists. Please choose another name.
+profileExistsTitle=Profile Exists
+profileFinishText=Click Finish to create this new profile.
+profileFinishTextMac=Click Done to create this new profile.
+profileMissing=Your %S profile cannot be loaded. It may be missing or inaccessible.
+profileMissingTitle=Profile Missing
+
+# Profile reset
+# LOCALIZATION NOTE (resetBackupDirectory): Directory name for the profile directory backup created during reset. This directory is placed in a location users will see it (ie. their desktop). %S is the application name.
+resetBackupDirectory=Old %S Data
diff --git a/components/profile/mimeTypes.rdf b/components/profile/mimeTypes.rdf
new file mode 100644
index 000000000..10edce3ce
--- /dev/null
+++ b/components/profile/mimeTypes.rdf
@@ -0,0 +1,86 @@
+<?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/. -->
+
+
+<!--
+ This file is used as a persistent data store for helper application
+ information about both MIME type and protocol scheme helpers.
+
+ The root of the data are the two containers
+ <RDF:Seq about="urn:mimetypes:root"/> and <RDF:Seq about="urn:schemes:root"/>.
+
+ These contain one <RDF:li/> entry per MIME type/protocol. Each <RDF:li/> entry
+ corresponds to a "urn:<class>:<type>" resource, where <class> is either
+ "mimetype" or "scheme" and <type> is either a MIME type in "major/minor" format
+ or a scheme. For example, for HTML we would have "urn:mimetype:text/html",
+ while for mailto: we would have "urn:scheme:mailto".
+
+ Typically, this resource will be in the <RDF:Description/> node which has the
+ corresponding "about" attribute.
+
+ Each "urn:<class>:<type>" resource can have the following properties:
+
+ NC:Value - the MIME type or scheme string
+ NC:editable - a "true" or "false" depending on whether this entry is
+ editable
+ NC:description - a description of the type ("HTML Document" for text/html)
+ NC:fileExtensions - for MIME types, there will be one of these properties
+ per extension that corresponds to this MIME type,
+ each one having a single extension as its value.
+ NC:handlerProp - the way the type should be handled. This corresponds to a
+ "urn:<class>:handler:<type>" resource. Eg, the way HTML is
+ handled would be stored in the
+ "urn:mimetype:handler:text/html" resource.
+
+ Each "urn:<class>:handler:<type>" resource can have the following properties:
+
+ NC:useSystemDefault - "true" if we should handle per default OS setting,
+ "false" or not set otherwise
+ NC:saveToDisk - "true" if the data should be saved to disk, "false" or not
+ set otherwise.
+ (Note - if both of these are false, that means "open in helper app")
+ NC:alwaysAsk - "true" if the user should always be prompted before handling
+ data of this type, false otherwise.
+ NC:externalApplication - the preferred helper application to use for this
+ type. This corresponds to a
+ "urn:<class>:externalApplication:<type>" resource.
+ NC:possibleApplication - a helper application that can be used for this type.
+ Since there can be multiple possible applications,
+ there can be multiple assertions in the graph with
+ this property for a given handler resource.
+
+ Each "urn:<class>:externalApplication:<type>" resource, and each resource
+ that represents a possible application, can have the following property:
+
+ NC:prettyName - the "pretty name" of the application ("Acrobat Reader" for
+ /usr/bin/acroread, eg).
+
+ If the resource represents a local application, then it can have the following
+ property:
+
+ NC:path - the path to the application on the local filesystem, for example
+ /usr/bin/test or C:\windows\system32\cmd.exe.
+
+ If the resource represents a web application, then it can have the following
+ property:
+
+ NC:uriTemplate - a URI pointing to the web application to which the type
+ should be handed off, with %s in the template representing
+ the place where the content should be inserted. For example,
+ here is a URI template for a service that lets you email
+ an address in a mailto: link:
+ http://www.example.com/sendmail?link=%s
+-->
+
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:NC="http://home.netscape.com/NC-rdf#">
+
+ <RDF:Description about="urn:mimetypes">
+ <NC:MIME-types>
+ <RDF:Seq about="urn:mimetypes:root">
+ </RDF:Seq>
+ </NC:MIME-types>
+ </RDF:Description>
+</RDF:RDF>
diff --git a/components/profile/moz.build b/components/profile/moz.build
new file mode 100644
index 000000000..c9b3572a4
--- /dev/null
+++ b/components/profile/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsIProfileMigrator.idl',
+ 'public/nsIProfileUnlocker.idl',
+ 'public/nsIToolkitProfile.idl',
+ 'public/nsIToolkitProfileService.idl',
+]
+
+SOURCES += [
+ 'src/nsProfileLock.cpp',
+ 'src/nsToolkitProfileService.cpp'
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += ['src/ProfileUnlockerWin.cpp']
+
+LOCAL_INCLUDES += ['/system/runtime']
+
+FINAL_TARGET_FILES.defaults.profile += ['mimeTypes.rdf']
+
+FINAL_TARGET_FILES.defaults.profile.chrome += [
+ 'userChrome-example.css',
+ 'userContent-example.css',
+]
+
+XPIDL_MODULE = 'toolkitprofile'
+FINAL_LIBRARY = 'xul'
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/profile/notifications.txt b/components/profile/notifications.txt
new file mode 100644
index 000000000..eab1e2c34
--- /dev/null
+++ b/components/profile/notifications.txt
@@ -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/.
+
+nsIObserver topics for profile changing. Profile changing happens in phases
+in the order given below. An observer may register separately for each phase
+of the process depending on its needs.
+
+"profile-change-teardown"
+ All async activity must be stopped in this phase. Typically,
+ the application level observer will close all open windows.
+ This is the last phase in which the subject's vetoChange()
+ method may still be called.
+ The next notification will be either
+ profile-change-teardown-veto or profile-before-change.
+
+"profile-before-change"
+ Called before the profile has changed. Use this notification
+ to prepare for the profile going away. If a component is
+ holding any state which needs to be flushed to a profile-relative
+ location, it should be done here.
+
+"profile-do-change"
+ Called after the profile has changed. Do the work to
+ respond to having a new profile. Any change which
+ affects others must be done in this phase.
+
+"profile-after-change"
+ Called after the profile has changed. Use this notification
+ to make changes that are dependent on what some other listener
+ did during its profile-do-change. For example, to respond to
+ new preferences.
+
+"profile-initial-state"
+ Called after all phases of a change have completed. Typically
+ in this phase, an application level observer will open a new window.
+
+Contexts for profile changes. These are passed as the someData param to the
+observer's Observe() method.
+
+"startup"
+ Going from no profile to a profile.
+ The following topics happen in this context:
+ profile-do-change
+ profile-after-change
+
+"shutdown-persist"
+ The user is logging out and whatever data the observer stores
+ for the current profile should be released from memory and
+ saved to disk.
+ The following topics happen in this context:
+ profile-change-net-teardown
+ profile-change-teardown
+ profile-before-change
+
+See https://wiki.mozilla.org/XPCOM_Shutdown for more details about the shutdown
+process.
+
+NOTE: Long ago there was be a "shutdown-cleanse" version of shutdown which was
+intended to clear profile data. This is no longer sent and observer code should
+remove support for it.
diff --git a/components/profile/public/nsIProfileMigrator.idl b/components/profile/public/nsIProfileMigrator.idl
new file mode 100644
index 000000000..e2351ca9b
--- /dev/null
+++ b/components/profile/public/nsIProfileMigrator.idl
@@ -0,0 +1,69 @@
+/* -*- 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 nsIFile;
+
+/**
+ * Helper interface for nsIProfileMigrator.
+ *
+ * @provider Toolkit (Startup code)
+ * @client Application (Profile-migration code)
+ * @obtainable nsIProfileMigrator.migrate
+ */
+[scriptable, uuid(048e5ca1-0eb7-4bb1-a9a2-a36f7d4e0e3c)]
+interface nsIProfileStartup : nsISupports
+{
+ /**
+ * The root directory of the semi-current profile, during profile migration.
+ * After nsIProfileMigrator.migrate has returned, this object will not be
+ * useful.
+ */
+ readonly attribute nsIFile directory;
+
+ /**
+ * Do profile-startup by setting NS_APP_USER_PROFILE_50_DIR in the directory
+ * service and notifying the profile-startup observer topics.
+ */
+ void doStartup();
+};
+
+/**
+ * Migrate application settings from an outside source.
+ *
+ * @provider Application (Profile-migration code)
+ * @client Toolkit (Startup code)
+ * @obtainable service, contractid("@mozilla.org/toolkit/profile-migrator;1")
+ */
+[scriptable, uuid(3df284a5-2258-4d46-a664-761ecdc04c22)]
+interface nsIProfileMigrator : nsISupports
+{
+ /**
+ * Migrate data from an outside source, if possible. Does nothing
+ * otherwise.
+ *
+ * When this method is called, a default profile has been created;
+ * XPCOM has been initialized such that compreg.dat is in the
+ * profile; the directory service does *not* return a key for
+ * NS_APP_USER_PROFILE_50_DIR or any of the keys depending on an active
+ * profile. To figure out the directory of the "current" profile, use
+ * aStartup.directory.
+ *
+ * If your migrator needs to access services that use the profile (to
+ * set profile prefs or bookmarks, for example), use aStartup.doStartup.
+ *
+ * @param aStartup nsIProfileStartup object to use during migration.
+ * @param aKey optional key of a migrator to use to skip source selection.
+ * @param aProfileName optional name of the profile to use for migration.
+ *
+ * @note The startup code ignores COM exceptions thrown from this method.
+ */
+ void migrate(in nsIProfileStartup aStartup, in ACString aKey,
+ [optional] in ACString aProfileName);
+};
+
+%{C++
+#define NS_PROFILEMIGRATOR_CONTRACTID "@mozilla.org/toolkit/profile-migrator;1"
+%}
diff --git a/components/profile/public/nsIProfileUnlocker.idl b/components/profile/public/nsIProfileUnlocker.idl
new file mode 100644
index 000000000..cd1a71051
--- /dev/null
+++ b/components/profile/public/nsIProfileUnlocker.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(08923af1-e7a3-4fae-ba02-128502193994)]
+interface nsIProfileUnlocker : nsISupports
+{
+ const unsigned long ATTEMPT_QUIT = 0;
+ const unsigned long FORCE_QUIT = 1;
+
+ /**
+ * Try to unlock the specified profile by attempting or forcing the
+ * process that currently holds the lock to quit.
+ *
+ * @param aSeverity either ATTEMPT_QUIT or FORCE_QUIT
+ * @throws NS_ERROR_FAILURE if unlocking failed.
+ */
+ void unlock(in unsigned long aSeverity);
+};
diff --git a/components/profile/public/nsIToolkitProfile.idl b/components/profile/public/nsIToolkitProfile.idl
new file mode 100644
index 000000000..8d0c07c51
--- /dev/null
+++ b/components/profile/public/nsIToolkitProfile.idl
@@ -0,0 +1,89 @@
+/* -*- Mode: IDL; tab-width: 8; 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 "nsISupports.idl"
+
+interface nsIFile;
+interface nsIProfileUnlocker;
+
+/**
+ * Hold on to a profile lock. Once you release the last reference to this
+ * interface, the profile lock is released.
+ */
+[scriptable, uuid(7c58c703-d245-4864-8d75-9648ca4a6139)]
+interface nsIProfileLock : nsISupports
+{
+ /**
+ * The main profile directory.
+ */
+ readonly attribute nsIFile directory;
+
+ /**
+ * A directory corresponding to the main profile directory that exists for
+ * the purpose of storing data on the local filesystem, including cache
+ * files or other data files that may not represent critical user data.
+ * (e.g., this directory may not be included as part of a backup scheme.)
+ *
+ * In some cases, this directory may just be the main profile directory.
+ */
+ readonly attribute nsIFile localDirectory;
+
+ /**
+ * The timestamp of an existing profile lock at lock time.
+ */
+ readonly attribute PRTime replacedLockTime;
+
+ /**
+ * Unlock the profile.
+ */
+ void unlock();
+};
+
+/**
+ * A interface representing a profile.
+ * @note THIS INTERFACE SHOULD BE IMPLEMENTED BY THE TOOLKIT CODE ONLY! DON'T
+ * EVEN THINK ABOUT IMPLEMENTING THIS IN JAVASCRIPT!
+ */
+[scriptable, uuid(7422b090-4a86-4407-972e-75468a625388)]
+interface nsIToolkitProfile : nsISupports
+{
+ /**
+ * The location of the profile directory.
+ */
+ readonly attribute nsIFile rootDir;
+
+ /**
+ * The location of the profile local directory, which may be the same as
+ * the root directory. See nsIProfileLock::localDirectory.
+ */
+ readonly attribute nsIFile localDir;
+
+ /**
+ * The name of the profile.
+ */
+ attribute AUTF8String name;
+
+ /**
+ * Removes the profile from the registry of profiles.
+ *
+ * @param removeFiles
+ * Indicates whether or not the profile directory should be
+ * removed in addition.
+ */
+ void remove(in boolean removeFiles);
+
+ /**
+ * Lock this profile using platform-specific locking methods.
+ *
+ * @param lockFile If locking fails, this may return a lockFile object
+ * which can be used in platform-specific ways to
+ * determine which process has the file locked. Null
+ * may be passed.
+ * @return An interface which holds a profile lock as long as you reference
+ * it.
+ * @throws NS_ERROR_FILE_ACCESS_DENIED if the profile was already locked.
+ */
+ nsIProfileLock lock(out nsIProfileUnlocker aUnlocker);
+};
diff --git a/components/profile/public/nsIToolkitProfileService.idl b/components/profile/public/nsIToolkitProfileService.idl
new file mode 100644
index 000000000..46a5b3cbc
--- /dev/null
+++ b/components/profile/public/nsIToolkitProfileService.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: IDL; tab-width: 8; 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 "nsISupports.idl"
+
+interface nsISimpleEnumerator;
+interface nsIFile;
+interface nsIToolkitProfile;
+interface nsIProfileLock;
+
+[scriptable, uuid(1947899b-f369-48fa-89da-f7c37bb1e6bc)]
+interface nsIToolkitProfileService : nsISupports
+{
+ attribute boolean startWithLastProfile;
+ attribute boolean startOffline;
+
+ readonly attribute nsISimpleEnumerator /*nsIToolkitProfile*/ profiles;
+
+ /**
+ * The currently selected profile (the one used or about to be used by the
+ * browser).
+ */
+ attribute nsIToolkitProfile selectedProfile;
+
+ /**
+ * The default profile (the one used or about to be used by the
+ * browser if no other profile is specified at runtime). This is the profile
+ * marked with Default=1 in profiles.ini and is usually the same as
+ * selectedProfile, except on Developer Edition.
+ *
+ * Developer Edition uses a profile named "dev-edition-default" as the
+ * default profile (which it creates if it doesn't exist), unless a special
+ * empty file named "ignore-dev-edition-profile" is present next to
+ * profiles.ini. In that case Developer Edition behaves the same as any
+ * other build of Firefox.
+ */
+ attribute nsIToolkitProfile defaultProfile;
+
+ /**
+ * Get a profile by name. This is mainly for use by the -P
+ * commandline flag.
+ *
+ * @param aName The profile name to find.
+ */
+ nsIToolkitProfile getProfileByName(in AUTF8String aName);
+
+ /**
+ * Lock an arbitrary path as a profile. If the path does not exist, it
+ * will be created and the defaults copied from the application directory.
+ */
+ nsIProfileLock lockProfilePath(in nsIFile aDirectory,
+ in nsIFile aTempDirectory);
+
+ /**
+ * Create a new profile.
+ *
+ * The profile temporary directory will be chosen based on where the
+ * profile directory is located.
+ *
+ * @param aRootDir
+ * The profile directory. May be null, in which case a suitable
+ * default will be chosen based on the profile name.
+ * @param aName
+ * The profile name.
+ */
+ nsIToolkitProfile createProfile(in nsIFile aRootDir,
+ in AUTF8String aName);
+
+ /**
+ * Create the default profile for an application.
+ *
+ * The profile will be typically in
+ * {Application Data}/.profilename/{salt}.default or
+ * {Application Data}/.appname/{salt}.default
+ * or if aVendorName is provided
+ * {Application Data}/.vendor/appname/{salt}.default
+ *
+ * @note Either aProfileName or aAppName must be non-empty
+ *
+ * @param aProfileName
+ * The name of the profile
+ * @param aAppName
+ * The name of the application
+ * @param aVendorName
+ * The name of the vendor
+ * @return The created profile.
+ */
+ nsIToolkitProfile createDefaultProfileForApp(in AUTF8String aProfileName,
+ in AUTF8String aAppName,
+ in AUTF8String aVendorName);
+
+ /**
+ * Returns the number of profiles.
+ * @return 0, 1, or 2. More than 2 profiles will always return 2.
+ */
+ readonly attribute unsigned long profileCount;
+
+ /**
+ * Flush the profiles list file.
+ */
+ void flush();
+};
+
+%{C++
+#define NS_PROFILESERVICE_CONTRACTID "@mozilla.org/toolkit/profile-service;1"
+%}
diff --git a/components/profile/src/ProfileUnlockerWin.cpp b/components/profile/src/ProfileUnlockerWin.cpp
new file mode 100644
index 000000000..4e0ed7196
--- /dev/null
+++ b/components/profile/src/ProfileUnlockerWin.cpp
@@ -0,0 +1,277 @@
+/* -*- 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 "ProfileUnlockerWin.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsXPCOM.h"
+
+namespace mozilla {
+
+/**
+ * RAII class to obtain and manage a handle to a Restart Manager session.
+ * It opens a new handle upon construction and releases it upon destruction.
+ */
+class MOZ_STACK_CLASS ScopedRestartManagerSession
+{
+public:
+ explicit ScopedRestartManagerSession(ProfileUnlockerWin& aUnlocker)
+ : mError(ERROR_INVALID_HANDLE)
+ , mHandle((DWORD)-1) // 0 is a valid restart manager handle
+ , mUnlocker(aUnlocker)
+ {
+ mError = mUnlocker.StartSession(mHandle);
+ }
+
+ ~ScopedRestartManagerSession()
+ {
+ if (mError == ERROR_SUCCESS) {
+ mUnlocker.EndSession(mHandle);
+ }
+ }
+
+ /**
+ * @return true if the handle is a valid Restart Ranager handle.
+ */
+ inline bool
+ ok()
+ {
+ return mError == ERROR_SUCCESS;
+ }
+
+ /**
+ * @return the Restart Manager handle to pass to other Restart Manager APIs.
+ */
+ inline DWORD
+ handle()
+ {
+ return mHandle;
+ }
+
+private:
+ DWORD mError;
+ DWORD mHandle;
+ ProfileUnlockerWin& mUnlocker;
+};
+
+ProfileUnlockerWin::ProfileUnlockerWin(const nsAString& aFileName)
+ : mRmStartSession(nullptr)
+ , mRmRegisterResources(nullptr)
+ , mRmGetList(nullptr)
+ , mRmEndSession(nullptr)
+ , mQueryFullProcessImageName(nullptr)
+ , mFileName(aFileName)
+{
+}
+
+ProfileUnlockerWin::~ProfileUnlockerWin()
+{
+}
+
+NS_IMPL_ISUPPORTS(ProfileUnlockerWin, nsIProfileUnlocker)
+
+nsresult
+ProfileUnlockerWin::Init()
+{
+ MOZ_ASSERT(!mRestartMgrModule);
+ if (mFileName.IsEmpty()) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsModuleHandle module(::LoadLibraryW(L"Rstrtmgr.dll"));
+ if (!module) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mRmStartSession =
+ reinterpret_cast<RMSTARTSESSION>(::GetProcAddress(module, "RmStartSession"));
+ if (!mRmStartSession) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRmRegisterResources =
+ reinterpret_cast<RMREGISTERRESOURCES>(::GetProcAddress(module,
+ "RmRegisterResources"));
+ if (!mRmRegisterResources) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRmGetList = reinterpret_cast<RMGETLIST>(::GetProcAddress(module,
+ "RmGetList"));
+ if (!mRmGetList) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ mRmEndSession = reinterpret_cast<RMENDSESSION>(::GetProcAddress(module,
+ "RmEndSession"));
+ if (!mRmEndSession) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mQueryFullProcessImageName =
+ reinterpret_cast<QUERYFULLPROCESSIMAGENAME>(::GetProcAddress(
+ ::GetModuleHandleW(L"kernel32.dll"),
+ "QueryFullProcessImageNameW"));
+ if (!mQueryFullProcessImageName) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mRestartMgrModule.steal(module);
+ return NS_OK;
+}
+
+DWORD
+ProfileUnlockerWin::StartSession(DWORD& aHandle)
+{
+ WCHAR sessionKey[CCH_RM_SESSION_KEY + 1] = {0};
+ return mRmStartSession(&aHandle, 0, sessionKey);
+}
+
+void
+ProfileUnlockerWin::EndSession(DWORD aHandle)
+{
+ mRmEndSession(aHandle);
+}
+
+NS_IMETHODIMP
+ProfileUnlockerWin::Unlock(uint32_t aSeverity)
+{
+ if (!mRestartMgrModule) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (aSeverity != FORCE_QUIT) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ ScopedRestartManagerSession session(*this);
+ if (!session.ok()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LPCWSTR resources[] = { mFileName.get() };
+ DWORD error = mRmRegisterResources(session.handle(), 1, resources, 0, nullptr,
+ 0, nullptr);
+ if (error != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Using a AutoTArray here because we expect the required size to be 1.
+ AutoTArray<RM_PROCESS_INFO, 1> info;
+ UINT numEntries;
+ UINT numEntriesNeeded = 1;
+ error = ERROR_MORE_DATA;
+ DWORD reason = RmRebootReasonNone;
+ while (error == ERROR_MORE_DATA) {
+ info.SetLength(numEntriesNeeded);
+ numEntries = numEntriesNeeded;
+ error = mRmGetList(session.handle(), &numEntriesNeeded, &numEntries,
+ &info[0], &reason);
+ }
+ if (error != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ if (numEntries == 0) {
+ // Nobody else is locking the file; the other process must have terminated
+ return NS_OK;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+ for (UINT i = 0; i < numEntries; ++i) {
+ rv = TryToTerminate(info[i].Process);
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ }
+
+ // If nothing could be unlocked then we return the error code of the last
+ // failure that was returned.
+ return rv;
+}
+
+nsresult
+ProfileUnlockerWin::TryToTerminate(RM_UNIQUE_PROCESS& aProcess)
+{
+ // Subtle: If the target process terminated before this call to OpenProcess,
+ // this call will still succeed. This is because the restart manager session
+ // internally retains a handle to the target process. The rules for Windows
+ // PIDs state that the PID of a terminated process remains valid as long as
+ // at least one handle to that process remains open, so when we reach this
+ // point the PID is still valid and the process will open successfully.
+ DWORD accessRights = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE;
+ nsAutoHandle otherProcess(::OpenProcess(accessRights, FALSE,
+ aProcess.dwProcessId));
+ if (!otherProcess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ FILETIME creationTime, exitTime, kernelTime, userTime;
+ if (!::GetProcessTimes(otherProcess, &creationTime, &exitTime, &kernelTime,
+ &userTime)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (::CompareFileTime(&aProcess.ProcessStartTime, &creationTime)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ WCHAR imageName[MAX_PATH];
+ DWORD imageNameLen = MAX_PATH;
+ if (!mQueryFullProcessImageName(otherProcess, 0, imageName, &imageNameLen)) {
+ // The error codes for this function are not very descriptive. There are
+ // actually two failure cases here: Either the call failed because the
+ // process is no longer running, or it failed for some other reason. We
+ // need to know which case that is.
+ DWORD otherProcessExitCode;
+ if (!::GetExitCodeProcess(otherProcess, &otherProcessExitCode) ||
+ otherProcessExitCode == STILL_ACTIVE) {
+ // The other process is still running.
+ return NS_ERROR_FAILURE;
+ }
+ // The other process must have terminated. We should return NS_OK so that
+ // this process may proceed with startup.
+ return NS_OK;
+ }
+ nsCOMPtr<nsIFile> otherProcessImageName;
+ if (NS_FAILED(NS_NewLocalFile(nsDependentString(imageName, imageNameLen),
+ false, getter_AddRefs(otherProcessImageName)))) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoString otherProcessLeafName;
+ if (NS_FAILED(otherProcessImageName->GetLeafName(otherProcessLeafName))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ imageNameLen = MAX_PATH;
+ if (!mQueryFullProcessImageName(::GetCurrentProcess(), 0, imageName,
+ &imageNameLen)) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIFile> thisProcessImageName;
+ if (NS_FAILED(NS_NewLocalFile(nsDependentString(imageName, imageNameLen),
+ false, getter_AddRefs(thisProcessImageName)))) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoString thisProcessLeafName;
+ if (NS_FAILED(thisProcessImageName->GetLeafName(thisProcessLeafName))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure the image leaf names match
+ if (_wcsicmp(otherProcessLeafName.get(), thisProcessLeafName.get())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We know that another process holds the lock and that it shares the same
+ // image name as our process. Let's kill it.
+ // Subtle: TerminateProcess returning ERROR_ACCESS_DENIED is actually an
+ // indicator that the target process managed to shut down on its own. In that
+ // case we should return NS_OK since we may proceed with startup.
+ if (!::TerminateProcess(otherProcess, 1) &&
+ ::GetLastError() != ERROR_ACCESS_DENIED) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
+
diff --git a/components/profile/src/ProfileUnlockerWin.h b/components/profile/src/ProfileUnlockerWin.h
new file mode 100644
index 000000000..0a7dff527
--- /dev/null
+++ b/components/profile/src/ProfileUnlockerWin.h
@@ -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/. */
+
+#ifndef ProfileUnlockerWin_h
+#define ProfileUnlockerWin_h
+
+#include <windows.h>
+#include <restartmanager.h>
+
+#include "nsIProfileUnlocker.h"
+#include "nsProfileStringTypes.h"
+#include "nsWindowsHelpers.h"
+
+namespace mozilla {
+
+class ProfileUnlockerWin final : public nsIProfileUnlocker
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROFILEUNLOCKER
+
+ explicit ProfileUnlockerWin(const nsAString& aFileName);
+
+ nsresult Init();
+
+ DWORD StartSession(DWORD& aHandle);
+ void EndSession(DWORD aHandle);
+
+private:
+ ~ProfileUnlockerWin();
+ nsresult TryToTerminate(RM_UNIQUE_PROCESS& aProcess);
+
+private:
+ typedef DWORD (WINAPI *RMSTARTSESSION)(DWORD*, DWORD, WCHAR[]);
+ typedef DWORD (WINAPI *RMREGISTERRESOURCES)(DWORD, UINT, LPCWSTR[], UINT,
+ RM_UNIQUE_PROCESS[], UINT,
+ LPCWSTR[]);
+ typedef DWORD (WINAPI *RMGETLIST)(DWORD, UINT*, UINT*, RM_PROCESS_INFO[],
+ LPDWORD);
+ typedef DWORD (WINAPI *RMENDSESSION)(DWORD);
+ typedef BOOL (WINAPI *QUERYFULLPROCESSIMAGENAME)(HANDLE, DWORD, LPWSTR, PDWORD);
+
+private:
+ nsModuleHandle mRestartMgrModule;
+ RMSTARTSESSION mRmStartSession;
+ RMREGISTERRESOURCES mRmRegisterResources;
+ RMGETLIST mRmGetList;
+ RMENDSESSION mRmEndSession;
+ QUERYFULLPROCESSIMAGENAME mQueryFullProcessImageName;
+
+ nsString mFileName;
+};
+
+} // namespace mozilla
+
+#endif // ProfileUnlockerWin_h
+
diff --git a/components/profile/src/nsProfileLock.cpp b/components/profile/src/nsProfileLock.cpp
new file mode 100644
index 000000000..5b6fbe0dc
--- /dev/null
+++ b/components/profile/src/nsProfileLock.cpp
@@ -0,0 +1,576 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsProfileStringTypes.h"
+#include "nsProfileLock.h"
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+
+#if defined(XP_WIN)
+#include "ProfileUnlockerWin.h"
+#include "nsAutoPtr.h"
+#endif
+
+#ifdef XP_UNIX
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include "prnetdb.h"
+#include "prsystem.h"
+#include "prprf.h"
+#include "prenv.h"
+#endif
+
+// **********************************************************************
+// class nsProfileLock
+//
+// This code was moved from profile/src/nsProfileAccess.
+// **********************************************************************
+
+#if defined (XP_UNIX)
+static bool sDisableSignalHandling = false;
+#endif
+
+nsProfileLock::nsProfileLock() :
+ mHaveLock(false),
+ mReplacedLockTime(0)
+#if defined (XP_WIN)
+ ,mLockFileHandle(INVALID_HANDLE_VALUE)
+#elif defined (XP_UNIX)
+ ,mPidLockFileName(nullptr)
+ ,mLockFileDesc(-1)
+#endif
+{
+#if defined (XP_UNIX)
+ next = prev = this;
+ sDisableSignalHandling = PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ? true : false;
+#endif
+}
+
+
+nsProfileLock::nsProfileLock(nsProfileLock& src)
+{
+ *this = src;
+}
+
+
+nsProfileLock& nsProfileLock::operator=(nsProfileLock& rhs)
+{
+ Unlock();
+
+ mHaveLock = rhs.mHaveLock;
+ rhs.mHaveLock = false;
+
+#if defined (XP_WIN)
+ mLockFileHandle = rhs.mLockFileHandle;
+ rhs.mLockFileHandle = INVALID_HANDLE_VALUE;
+#elif defined (XP_UNIX)
+ mLockFileDesc = rhs.mLockFileDesc;
+ rhs.mLockFileDesc = -1;
+ mPidLockFileName = rhs.mPidLockFileName;
+ rhs.mPidLockFileName = nullptr;
+ if (mPidLockFileName)
+ {
+ // rhs had a symlink lock, therefore it was on the list.
+ PR_REMOVE_LINK(&rhs);
+ PR_APPEND_LINK(this, &mPidLockList);
+ }
+#endif
+
+ return *this;
+}
+
+
+nsProfileLock::~nsProfileLock()
+{
+ Unlock();
+}
+
+
+#if defined (XP_UNIX)
+
+static int setupPidLockCleanup;
+
+PRCList nsProfileLock::mPidLockList =
+ PR_INIT_STATIC_CLIST(&nsProfileLock::mPidLockList);
+
+void nsProfileLock::RemovePidLockFiles(bool aFatalSignal)
+{
+ while (!PR_CLIST_IS_EMPTY(&mPidLockList))
+ {
+ nsProfileLock *lock = static_cast<nsProfileLock*>(mPidLockList.next);
+ lock->Unlock(aFatalSignal);
+ }
+}
+
+static struct sigaction SIGHUP_oldact;
+static struct sigaction SIGINT_oldact;
+static struct sigaction SIGQUIT_oldact;
+static struct sigaction SIGILL_oldact;
+static struct sigaction SIGABRT_oldact;
+static struct sigaction SIGSEGV_oldact;
+static struct sigaction SIGTERM_oldact;
+
+void nsProfileLock::FatalSignalHandler(int signo
+#ifdef SA_SIGINFO
+ , siginfo_t *info, void *context
+#endif
+ )
+{
+ // Remove any locks still held.
+ RemovePidLockFiles(true);
+
+ // Chain to the old handler, which may exit.
+ struct sigaction *oldact = nullptr;
+
+ switch (signo) {
+ case SIGHUP:
+ oldact = &SIGHUP_oldact;
+ break;
+ case SIGINT:
+ oldact = &SIGINT_oldact;
+ break;
+ case SIGQUIT:
+ oldact = &SIGQUIT_oldact;
+ break;
+ case SIGILL:
+ oldact = &SIGILL_oldact;
+ break;
+ case SIGABRT:
+ oldact = &SIGABRT_oldact;
+ break;
+ case SIGSEGV:
+ oldact = &SIGSEGV_oldact;
+ break;
+ case SIGTERM:
+ oldact = &SIGTERM_oldact;
+ break;
+ default:
+ NS_NOTREACHED("bad signo");
+ break;
+ }
+
+ if (oldact) {
+ if (oldact->sa_handler == SIG_DFL) {
+ // Make sure the default sig handler is executed
+ // We need it to get Mozilla to dump core.
+ sigaction(signo,oldact, nullptr);
+
+ // Now that we've restored the default handler, unmask the
+ // signal and invoke it.
+
+ sigset_t unblock_sigs;
+ sigemptyset(&unblock_sigs);
+ sigaddset(&unblock_sigs, signo);
+
+ sigprocmask(SIG_UNBLOCK, &unblock_sigs, nullptr);
+
+ raise(signo);
+ }
+#ifdef SA_SIGINFO
+ else if (oldact->sa_sigaction &&
+ (oldact->sa_flags & SA_SIGINFO) == SA_SIGINFO) {
+ oldact->sa_sigaction(signo, info, context);
+ }
+#endif
+ else if (oldact->sa_handler && oldact->sa_handler != SIG_IGN)
+ {
+ oldact->sa_handler(signo);
+ }
+ }
+
+ // Backstop exit call, just in case.
+ _exit(signo);
+}
+
+nsresult nsProfileLock::LockWithFcntl(nsIFile *aLockFile)
+{
+ nsresult rv = NS_OK;
+
+ nsAutoCString lockFilePath;
+#ifdef XP_WIN
+ rv = aLockFile->GetPersistentDescriptor(lockFilePath);
+#else
+ rv = aLockFile->GetNativePath(lockFilePath);
+#endif
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Could not get native path");
+ return rv;
+ }
+
+ aLockFile->GetLastModifiedTime(&mReplacedLockTime);
+
+ mLockFileDesc = open(lockFilePath.get(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (mLockFileDesc != -1)
+ {
+ struct flock lock;
+ lock.l_start = 0;
+ lock.l_len = 0; // len = 0 means entire file
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+
+ // If fcntl(F_GETLK) fails then the server does not support/allow fcntl(),
+ // return failure rather than access denied in this case so we fallback
+ // to using a symlink lock, bug 303633.
+ struct flock testlock = lock;
+ if (fcntl(mLockFileDesc, F_GETLK, &testlock) == -1)
+ {
+ close(mLockFileDesc);
+ mLockFileDesc = -1;
+ rv = NS_ERROR_FAILURE;
+ }
+ else if (fcntl(mLockFileDesc, F_SETLK, &lock) == -1)
+ {
+ close(mLockFileDesc);
+ mLockFileDesc = -1;
+
+ // With OS X, on NFS, errno == ENOTSUP
+ // XXX Check for that and return specific rv for it?
+#ifdef DEBUG
+ printf("fcntl(F_SETLK) failed. errno = %d\n", errno);
+#endif
+ if (errno == EAGAIN || errno == EACCES)
+ rv = NS_ERROR_FILE_ACCESS_DENIED;
+ else
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ else
+ {
+ NS_ERROR("Failed to open lock file.");
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+static bool IsSymlinkStaleLock(struct in_addr* aAddr, const char* aFileName,
+ bool aHaveFcntlLock)
+{
+ // the link exists; see if it's from this machine, and if
+ // so if the process is still active
+ char buf[1024];
+ int len = readlink(aFileName, buf, sizeof buf - 1);
+ if (len > 0)
+ {
+ buf[len] = '\0';
+ char *colon = strchr(buf, ':');
+ if (colon)
+ {
+ *colon++ = '\0';
+ unsigned long addr = inet_addr(buf);
+ if (addr != (unsigned long) -1)
+ {
+ if (colon[0] == '+' && aHaveFcntlLock) {
+ // This lock was placed by a Firefox build which would have
+ // taken the fnctl lock, and we've already taken the fcntl lock,
+ // so the process that created this obsolete lock must be gone
+ return true;
+ }
+
+ char *after = nullptr;
+ pid_t pid = strtol(colon, &after, 0);
+ if (pid != 0 && *after == '\0')
+ {
+ if (addr != aAddr->s_addr)
+ {
+ // Remote lock: give up even if stuck.
+ return false;
+ }
+
+ // kill(pid,0) is a neat trick to check if a
+ // process exists
+ if (kill(pid, 0) == 0 || errno != ESRCH)
+ {
+ // Local process appears to be alive, ass-u-me it
+ // is another Mozilla instance, or a compatible
+ // derivative, that's currently using the profile.
+ // XXX need an "are you Mozilla?" protocol
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+nsresult nsProfileLock::LockWithSymlink(nsIFile *aLockFile, bool aHaveFcntlLock)
+{
+ nsresult rv;
+ nsAutoCString lockFilePath;
+#ifdef XP_WIN
+ rv = aLockFile->GetPersistentDescriptor(lockFilePath);
+#else
+ rv = aLockFile->GetNativePath(lockFilePath);
+#endif
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Could not get native path");
+ return rv;
+ }
+
+ // don't replace an existing lock time if fcntl already got one
+ if (!mReplacedLockTime)
+ aLockFile->GetLastModifiedTimeOfLink(&mReplacedLockTime);
+
+ struct in_addr inaddr;
+ inaddr.s_addr = htonl(INADDR_LOOPBACK);
+
+ char hostname[256];
+ PRStatus status = PR_GetSystemInfo(PR_SI_HOSTNAME, hostname, sizeof hostname);
+ if (status == PR_SUCCESS)
+ {
+ char netdbbuf[PR_NETDB_BUF_SIZE];
+ PRHostEnt hostent;
+ status = PR_GetHostByName(hostname, netdbbuf, sizeof netdbbuf, &hostent);
+ if (status == PR_SUCCESS)
+ memcpy(&inaddr, hostent.h_addr, sizeof inaddr);
+ }
+
+ char *signature =
+ PR_smprintf("%s:%s%lu", inet_ntoa(inaddr), aHaveFcntlLock ? "+" : "",
+ (unsigned long)getpid());
+ const char *fileName = lockFilePath.get();
+ int symlink_rv, symlink_errno = 0, tries = 0;
+
+ // use ns4.x-compatible symlinks if the FS supports them
+ while ((symlink_rv = symlink(signature, fileName)) < 0)
+ {
+ symlink_errno = errno;
+ if (symlink_errno != EEXIST)
+ break;
+
+ if (!IsSymlinkStaleLock(&inaddr, fileName, aHaveFcntlLock))
+ break;
+
+ // Lock seems to be bogus: try to claim it. Give up after a large
+ // number of attempts (100 comes from the 4.x codebase).
+ (void) unlink(fileName);
+ if (++tries > 100)
+ break;
+ }
+
+ PR_smprintf_free(signature);
+ signature = nullptr;
+
+ if (symlink_rv == 0)
+ {
+ // We exclusively created the symlink: record its name for eventual
+ // unlock-via-unlink.
+ rv = NS_OK;
+ mPidLockFileName = strdup(fileName);
+ if (mPidLockFileName)
+ {
+ PR_APPEND_LINK(this, &mPidLockList);
+ if (!setupPidLockCleanup++)
+ {
+ // Clean up on normal termination.
+ // This instanciates a dummy class, and will trigger the class
+ // destructor when libxul is unloaded. This is equivalent to atexit(),
+ // but gracefully handles dlclose().
+ static RemovePidLockFilesExiting r;
+
+ // Clean up on abnormal termination, using POSIX sigaction.
+ // Don't arm a handler if the signal is being ignored, e.g.,
+ // because mozilla is run via nohup.
+ if (!sDisableSignalHandling) {
+ struct sigaction act, oldact;
+#ifdef SA_SIGINFO
+ act.sa_sigaction = FatalSignalHandler;
+ act.sa_flags = SA_SIGINFO;
+#else
+ act.sa_handler = FatalSignalHandler;
+#endif
+ sigfillset(&act.sa_mask);
+
+#define CATCH_SIGNAL(signame) \
+PR_BEGIN_MACRO \
+ if (sigaction(signame, nullptr, &oldact) == 0 && \
+ oldact.sa_handler != SIG_IGN) \
+ { \
+ sigaction(signame, &act, &signame##_oldact); \
+ } \
+ PR_END_MACRO
+
+ CATCH_SIGNAL(SIGHUP);
+ CATCH_SIGNAL(SIGINT);
+ CATCH_SIGNAL(SIGQUIT);
+ CATCH_SIGNAL(SIGILL);
+ CATCH_SIGNAL(SIGABRT);
+ CATCH_SIGNAL(SIGSEGV);
+ CATCH_SIGNAL(SIGTERM);
+
+#undef CATCH_SIGNAL
+ }
+ }
+ }
+ }
+ else if (symlink_errno == EEXIST)
+ rv = NS_ERROR_FILE_ACCESS_DENIED;
+ else
+ {
+#ifdef DEBUG
+ printf("symlink() failed. errno = %d\n", errno);
+#endif
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+#endif /* XP_UNIX */
+
+nsresult nsProfileLock::GetReplacedLockTime(PRTime *aResult) {
+ *aResult = mReplacedLockTime;
+ return NS_OK;
+}
+
+nsresult nsProfileLock::Lock(nsIFile* aProfileDir,
+ nsIProfileUnlocker* *aUnlocker)
+{
+#if defined (XP_UNIX)
+ NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "lock");
+ NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock");
+#else
+ NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, "parent.lock");
+#endif
+
+ nsresult rv;
+ if (aUnlocker)
+ *aUnlocker = nullptr;
+
+ NS_ENSURE_STATE(!mHaveLock);
+
+ bool isDir;
+ rv = aProfileDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv))
+ return rv;
+ if (!isDir)
+ return NS_ERROR_FILE_NOT_DIRECTORY;
+
+ nsCOMPtr<nsIFile> lockFile;
+ rv = aProfileDir->Clone(getter_AddRefs(lockFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = lockFile->Append(LOCKFILE_NAME);
+ if (NS_FAILED(rv))
+ return rv;
+
+#if defined(XP_UNIX)
+ // Get the old lockfile name
+ nsCOMPtr<nsIFile> oldLockFile;
+ rv = aProfileDir->Clone(getter_AddRefs(oldLockFile));
+ if (NS_FAILED(rv))
+ return rv;
+ rv = oldLockFile->Append(OLD_LOCKFILE_NAME);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // First, try locking using fcntl. It is more reliable on
+ // a local machine, but may not be supported by an NFS server.
+ rv = LockWithFcntl(lockFile);
+ if (NS_SUCCEEDED(rv)) {
+ // Check to see whether there is a symlink lock held by an older
+ // Firefox build, and also place our own symlink lock --- but
+ // mark it "obsolete" so that other newer builds can break the lock
+ // if they obtain the fcntl lock
+ rv = LockWithSymlink(oldLockFile, true);
+
+ // If the symlink failed for some reason other than it already
+ // exists, then something went wrong e.g. the file system
+ // doesn't support symlinks, or we don't have permission to
+ // create a symlink there. In such cases we should just
+ // continue because it's unlikely there is an old build
+ // running with a symlink there and we've already successfully
+ // placed a fcntl lock.
+ if (rv != NS_ERROR_FILE_ACCESS_DENIED)
+ rv = NS_OK;
+ }
+ else if (rv != NS_ERROR_FILE_ACCESS_DENIED)
+ {
+ // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
+ // assume we tried an NFS that does not support it. Now, try with symlink
+ // using the old symlink path
+ rv = LockWithSymlink(oldLockFile, false);
+ }
+
+#elif defined(XP_WIN)
+ nsAutoString filePath;
+ rv = lockFile->GetPath(filePath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ lockFile->GetLastModifiedTime(&mReplacedLockTime);
+
+ // always create the profile lock and never delete it so we can use its
+ // modification timestamp to detect startup crashes
+ mLockFileHandle = CreateFileW(filePath.get(),
+ GENERIC_READ | GENERIC_WRITE,
+ 0, // no sharing - of course
+ nullptr,
+ CREATE_ALWAYS,
+ 0,
+ nullptr);
+ if (mLockFileHandle == INVALID_HANDLE_VALUE) {
+ if (aUnlocker) {
+ RefPtr<mozilla::ProfileUnlockerWin> unlocker(
+ new mozilla::ProfileUnlockerWin(filePath));
+ if (NS_SUCCEEDED(unlocker->Init())) {
+ nsCOMPtr<nsIProfileUnlocker> unlockerInterface(
+ do_QueryObject(unlocker));
+ unlockerInterface.forget(aUnlocker);
+ }
+ }
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+#endif
+
+ if (NS_SUCCEEDED(rv))
+ mHaveLock = true;
+
+ return rv;
+}
+
+
+nsresult nsProfileLock::Unlock(bool aFatalSignal)
+{
+ nsresult rv = NS_OK;
+
+ if (mHaveLock)
+ {
+#if defined (XP_WIN)
+ if (mLockFileHandle != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(mLockFileHandle);
+ mLockFileHandle = INVALID_HANDLE_VALUE;
+ }
+#elif defined (XP_UNIX)
+ if (mPidLockFileName)
+ {
+ PR_REMOVE_LINK(this);
+ (void) unlink(mPidLockFileName);
+
+ // Only free mPidLockFileName if we're not in the fatal signal
+ // handler. The problem is that a call to free() might be the
+ // cause of this fatal signal. If so, calling free() might cause
+ // us to wait on the malloc implementation's lock. We're already
+ // holding this lock, so we'll deadlock. See bug 522332.
+ if (!aFatalSignal)
+ free(mPidLockFileName);
+ mPidLockFileName = nullptr;
+ }
+ if (mLockFileDesc != -1)
+ {
+ close(mLockFileDesc);
+ mLockFileDesc = -1;
+ // Don't remove it
+ }
+#endif
+
+ mHaveLock = false;
+ }
+
+ return rv;
+}
diff --git a/components/profile/src/nsProfileLock.h b/components/profile/src/nsProfileLock.h
new file mode 100644
index 000000000..e78a3577e
--- /dev/null
+++ b/components/profile/src/nsProfileLock.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef __nsProfileLock_h___
+#define __nsProfileLock_h___
+
+#include "nsIFile.h"
+
+class nsIProfileUnlocker;
+
+#if defined (XP_WIN)
+#include <windows.h>
+#endif
+
+#if defined (XP_UNIX)
+#include <signal.h>
+#include "prclist.h"
+#endif
+
+class nsProfileLock
+#if defined (XP_UNIX)
+ : public PRCList
+#endif
+{
+public:
+ nsProfileLock();
+ nsProfileLock(nsProfileLock& src);
+
+ ~nsProfileLock();
+
+ nsProfileLock& operator=(nsProfileLock& rhs);
+
+ /**
+ * Attempt to lock a profile directory.
+ *
+ * @param aProfileDir [in] The profile directory to lock.
+ * @param aUnlocker [out] Optional. This is only returned when locking
+ * fails with NS_ERROR_FILE_ACCESS_DENIED, and may not
+ * be returned at all.
+ * @throws NS_ERROR_FILE_ACCESS_DENIED if the profile is locked.
+ */
+ nsresult Lock(nsIFile* aProfileDir, nsIProfileUnlocker* *aUnlocker);
+
+ /**
+ * Unlock a profile directory. If you're unlocking the directory because
+ * the application is in the process of shutting down because of a fatal
+ * signal, set aFatalSignal to true.
+ */
+ nsresult Unlock(bool aFatalSignal = false);
+
+ /**
+ * Get the modification time of a replaced profile lock, otherwise 0.
+ */
+ nsresult GetReplacedLockTime(PRTime* aResult);
+
+private:
+ bool mHaveLock;
+ PRTime mReplacedLockTime;
+
+#if defined (XP_WIN)
+ HANDLE mLockFileHandle;
+#elif defined (XP_UNIX)
+
+ struct RemovePidLockFilesExiting {
+ RemovePidLockFilesExiting() {}
+ ~RemovePidLockFilesExiting() {
+ RemovePidLockFiles(false);
+ }
+ };
+
+ static void RemovePidLockFiles(bool aFatalSignal);
+ static void FatalSignalHandler(int signo
+#ifdef SA_SIGINFO
+ , siginfo_t *info, void *context
+#endif
+ );
+ static PRCList mPidLockList;
+
+ nsresult LockWithFcntl(nsIFile *aLockFile);
+
+ /**
+ * @param aHaveFcntlLock if true, we've already acquired an fcntl lock so this
+ * lock is merely an "obsolete" lock to keep out old Firefoxes
+ */
+ nsresult LockWithSymlink(nsIFile *aLockFile, bool aHaveFcntlLock);
+
+ char* mPidLockFileName;
+ int mLockFileDesc;
+#endif
+
+};
+
+#endif /* __nsProfileLock_h___ */
diff --git a/components/profile/src/nsProfileStringTypes.h b/components/profile/src/nsProfileStringTypes.h
new file mode 100644
index 000000000..fddea519b
--- /dev/null
+++ b/components/profile/src/nsProfileStringTypes.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/**
+ * We support two builds of the directory service provider.
+ * One, linked into the profile component, uses the internal
+ * string API. The other can be used by standalone embedding
+ * clients, and uses embed strings.
+ * To keep the code clean, we are using typedefs to equate
+ * embed/internal string types. We are also defining some
+ * internal macros in terms of the embedding strings API.
+ *
+ * When modifying the profile directory service provider, be
+ * sure to use methods supported by both the internal and
+ * embed strings APIs.
+ */
+
+#ifndef MOZILLA_INTERNAL_API
+
+#include "nsEmbedString.h"
+
+typedef nsCString nsPromiseFlatCString;
+typedef nsCString nsAutoCString;
+
+#define PromiseFlatCString nsCString
+
+#else
+#include "nsString.h"
+#include "nsPromiseFlatString.h"
+#endif
diff --git a/components/profile/src/nsToolkitProfileService.cpp b/components/profile/src/nsToolkitProfileService.cpp
new file mode 100644
index 000000000..3380246da
--- /dev/null
+++ b/components/profile/src/nsToolkitProfileService.cpp
@@ -0,0 +1,1043 @@
+/* -*- Mode: C++; tab-width: 8; 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/ArrayUtils.h"
+#include "mozilla/UniquePtr.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <prprf.h>
+#include <prtime.h>
+#include "nsProfileLock.h"
+
+#ifdef XP_WIN
+#include <windows.h>
+#include <shlobj.h>
+#endif
+#ifdef XP_UNIX
+#include <unistd.h>
+#endif
+
+#include "nsIToolkitProfileService.h"
+#include "nsIToolkitProfile.h"
+#include "nsIFactory.h"
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsXULAppAPI.h"
+
+#include "nsINIParser.h"
+#include "nsXREDirProvider.h"
+#include "nsAppRunner.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsNativeCharsetUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+
+using namespace mozilla;
+
+class nsToolkitProfile final : public nsIToolkitProfile
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITOOLKITPROFILE
+
+ friend class nsToolkitProfileService;
+ RefPtr<nsToolkitProfile> mNext;
+ nsToolkitProfile *mPrev;
+
+private:
+ ~nsToolkitProfile() { }
+
+ nsToolkitProfile(const nsACString& aName,
+ nsIFile* aRootDir,
+ nsIFile* aLocalDir,
+ nsToolkitProfile* aPrev,
+ bool aForExternalApp);
+
+ friend class nsToolkitProfileLock;
+
+ nsCString mName;
+ nsCOMPtr<nsIFile> mRootDir;
+ nsCOMPtr<nsIFile> mLocalDir;
+ nsIProfileLock* mLock;
+ bool mForExternalApp;
+};
+
+class nsToolkitProfileLock final : public nsIProfileLock
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROFILELOCK
+
+ nsresult Init(nsToolkitProfile* aProfile, nsIProfileUnlocker* *aUnlocker);
+ nsresult Init(nsIFile* aDirectory, nsIFile* aLocalDirectory,
+ nsIProfileUnlocker* *aUnlocker);
+
+ nsToolkitProfileLock() { }
+
+private:
+ ~nsToolkitProfileLock();
+
+ RefPtr<nsToolkitProfile> mProfile;
+ nsCOMPtr<nsIFile> mDirectory;
+ nsCOMPtr<nsIFile> mLocalDirectory;
+
+ nsProfileLock mLock;
+};
+
+class nsToolkitProfileFactory final : public nsIFactory
+{
+ ~nsToolkitProfileFactory() {}
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFACTORY
+};
+
+class nsToolkitProfileService final : public nsIToolkitProfileService
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITOOLKITPROFILESERVICE
+
+private:
+ friend class nsToolkitProfile;
+ friend class nsToolkitProfileFactory;
+ friend nsresult NS_NewToolkitProfileService(nsIToolkitProfileService**);
+
+ nsToolkitProfileService() :
+ mDirty(false),
+ mStartWithLast(true),
+ mStartOffline(false)
+ {
+ gService = this;
+ }
+ ~nsToolkitProfileService()
+ {
+ gService = nullptr;
+ }
+
+ nsresult Init();
+
+ nsresult CreateTimesInternal(nsIFile *profileDir);
+
+ nsresult CreateProfileInternal(nsIFile* aRootDir,
+ const nsACString& aName,
+ const nsACString* aProfileName,
+ const nsACString* aAppName,
+ const nsACString* aVendorName,
+ bool aForExternalApp,
+ nsIToolkitProfile** aResult);
+
+ RefPtr<nsToolkitProfile> mFirst;
+ nsCOMPtr<nsIToolkitProfile> mChosen;
+ nsCOMPtr<nsIToolkitProfile> mDefault;
+ nsCOMPtr<nsIFile> mAppData;
+ nsCOMPtr<nsIFile> mTempData;
+ nsCOMPtr<nsIFile> mListFile;
+ bool mDirty;
+ bool mStartWithLast;
+ bool mStartOffline;
+
+ static nsToolkitProfileService *gService;
+
+ class ProfileEnumerator final : public nsISimpleEnumerator
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ explicit ProfileEnumerator(nsToolkitProfile *first)
+ { mCurrent = first; }
+ private:
+ ~ProfileEnumerator() { }
+ RefPtr<nsToolkitProfile> mCurrent;
+ };
+};
+
+nsToolkitProfile::nsToolkitProfile(const nsACString& aName,
+ nsIFile* aRootDir,
+ nsIFile* aLocalDir,
+ nsToolkitProfile* aPrev,
+ bool aForExternalApp) :
+ mPrev(aPrev),
+ mName(aName),
+ mRootDir(aRootDir),
+ mLocalDir(aLocalDir),
+ mLock(nullptr),
+ mForExternalApp(aForExternalApp)
+{
+ NS_ASSERTION(aRootDir, "No file!");
+
+ if (!aForExternalApp) {
+ if (aPrev) {
+ aPrev->mNext = this;
+ } else {
+ nsToolkitProfileService::gService->mFirst = this;
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfile, nsIToolkitProfile)
+
+NS_IMETHODIMP
+nsToolkitProfile::GetRootDir(nsIFile* *aResult)
+{
+ NS_ADDREF(*aResult = mRootDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::GetLocalDir(nsIFile* *aResult)
+{
+ NS_ADDREF(*aResult = mLocalDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::GetName(nsACString& aResult)
+{
+ aResult = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::SetName(const nsACString& aName)
+{
+ NS_ASSERTION(nsToolkitProfileService::gService,
+ "Where did my service go?");
+ NS_ENSURE_TRUE(!mForExternalApp, NS_ERROR_NOT_IMPLEMENTED);
+
+ mName = aName;
+ nsToolkitProfileService::gService->mDirty = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::Remove(bool removeFiles)
+{
+ NS_ASSERTION(nsToolkitProfileService::gService,
+ "Whoa, my service is gone.");
+
+ NS_ENSURE_TRUE(!mForExternalApp, NS_ERROR_NOT_IMPLEMENTED);
+
+ if (mLock)
+ return NS_ERROR_FILE_IS_LOCKED;
+
+ if (!mPrev && !mNext && nsToolkitProfileService::gService->mFirst != this)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (removeFiles) {
+ bool equals;
+ nsresult rv = mRootDir->Equals(mLocalDir, &equals);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // The root dir might contain the temp dir, so remove
+ // the temp dir first.
+ if (!equals)
+ mLocalDir->Remove(true);
+
+ mRootDir->Remove(true);
+ }
+
+ if (mPrev)
+ mPrev->mNext = mNext;
+ else
+ nsToolkitProfileService::gService->mFirst = mNext;
+
+ if (mNext)
+ mNext->mPrev = mPrev;
+
+ mPrev = nullptr;
+ mNext = nullptr;
+
+ if (nsToolkitProfileService::gService->mChosen == this)
+ nsToolkitProfileService::gService->mChosen = nullptr;
+
+ nsToolkitProfileService::gService->mDirty = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfile::Lock(nsIProfileUnlocker* *aUnlocker, nsIProfileLock* *aResult)
+{
+ if (mLock) {
+ NS_ADDREF(*aResult = mLock);
+ return NS_OK;
+ }
+
+ RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
+ if (!lock) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = lock->Init(this, aUnlocker);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*aResult = lock);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileLock, nsIProfileLock)
+
+nsresult
+nsToolkitProfileLock::Init(nsToolkitProfile* aProfile, nsIProfileUnlocker* *aUnlocker)
+{
+ nsresult rv;
+ rv = Init(aProfile->mRootDir, aProfile->mLocalDir, aUnlocker);
+ if (NS_SUCCEEDED(rv))
+ mProfile = aProfile;
+
+ return rv;
+}
+
+nsresult
+nsToolkitProfileLock::Init(nsIFile* aDirectory, nsIFile* aLocalDirectory,
+ nsIProfileUnlocker* *aUnlocker)
+{
+ nsresult rv;
+
+ rv = mLock.Lock(aDirectory, aUnlocker);
+
+ if (NS_SUCCEEDED(rv)) {
+ mDirectory = aDirectory;
+ mLocalDirectory = aLocalDirectory;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::GetDirectory(nsIFile* *aResult)
+{
+ if (!mDirectory) {
+ NS_ERROR("Not initialized, or unlocked!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ NS_ADDREF(*aResult = mDirectory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::GetLocalDirectory(nsIFile* *aResult)
+{
+ if (!mLocalDirectory) {
+ NS_ERROR("Not initialized, or unlocked!");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ NS_ADDREF(*aResult = mLocalDirectory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::Unlock()
+{
+ if (!mDirectory) {
+ NS_ERROR("Unlocking a never-locked nsToolkitProfileLock!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mLock.Unlock();
+
+ if (mProfile) {
+ mProfile->mLock = nullptr;
+ mProfile = nullptr;
+ }
+ mDirectory = nullptr;
+ mLocalDirectory = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileLock::GetReplacedLockTime(PRTime *aResult)
+{
+ mLock.GetReplacedLockTime(aResult);
+ return NS_OK;
+}
+
+nsToolkitProfileLock::~nsToolkitProfileLock()
+{
+ if (mDirectory) {
+ Unlock();
+ }
+}
+
+nsToolkitProfileService*
+nsToolkitProfileService::gService = nullptr;
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileService,
+ nsIToolkitProfileService)
+
+nsresult
+nsToolkitProfileService::Init()
+{
+ NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
+ nsresult rv;
+
+ rv = gDirServiceProvider->GetUserAppDataDirectory(getter_AddRefs(mAppData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = gDirServiceProvider->GetUserLocalDataDirectory(getter_AddRefs(mTempData));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mAppData->Clone(getter_AddRefs(mListFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mListFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = mListFile->IsFile(&exists);
+ if (NS_FAILED(rv) || !exists) {
+ return NS_OK;
+ }
+
+ int64_t size;
+ rv = mListFile->GetFileSize(&size);
+ if (NS_FAILED(rv) || !size) {
+ return NS_OK;
+ }
+
+ nsINIParser parser;
+ rv = parser.Init(mListFile);
+ // Init does not fail on parsing errors, only on OOM/really unexpected
+ // conditions.
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString buffer;
+ rv = parser.GetString("General", "StartWithLastProfile", buffer);
+ if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("0"))
+ mStartWithLast = false;
+
+ nsToolkitProfile* currentProfile = nullptr;
+
+ unsigned int c = 0;
+ bool foundAuroraDefault = false;
+ for (c = 0; true; ++c) {
+ nsAutoCString profileID("Profile");
+ profileID.AppendInt(c);
+
+ rv = parser.GetString(profileID.get(), "IsRelative", buffer);
+ if (NS_FAILED(rv)) break;
+
+ bool isRelative = buffer.EqualsLiteral("1");
+
+ nsAutoCString filePath;
+
+ rv = parser.GetString(profileID.get(), "Path", filePath);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Malformed profiles.ini: Path= not found");
+ continue;
+ }
+
+ nsAutoCString name;
+
+ rv = parser.GetString(profileID.get(), "Name", name);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Malformed profiles.ini: Name= not found");
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> rootDir;
+ rv = NS_NewNativeLocalFile(EmptyCString(), true,
+ getter_AddRefs(rootDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isRelative) {
+ rv = rootDir->SetRelativeDescriptor(mAppData, filePath);
+ } else {
+ rv = rootDir->SetPersistentDescriptor(filePath);
+ }
+ if (NS_FAILED(rv)) continue;
+
+ nsCOMPtr<nsIFile> localDir;
+ if (isRelative) {
+ rv = NS_NewNativeLocalFile(EmptyCString(), true,
+ getter_AddRefs(localDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localDir->SetRelativeDescriptor(mTempData, filePath);
+ } else {
+ localDir = rootDir;
+ }
+
+ currentProfile = new nsToolkitProfile(name,
+ rootDir, localDir,
+ currentProfile, false);
+ NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = parser.GetString(profileID.get(), "Default", buffer);
+ if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1") && !foundAuroraDefault) {
+ mChosen = currentProfile;
+ this->SetDefaultProfile(currentProfile);
+ }
+ }
+
+ if (!mChosen && mFirst && !mFirst->mNext) // only one profile
+ mChosen = mFirst;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetStartWithLastProfile(bool aValue)
+{
+ if (mStartWithLast != aValue) {
+ mStartWithLast = aValue;
+ mDirty = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetStartWithLastProfile(bool *aResult)
+{
+ *aResult = mStartWithLast;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetStartOffline(bool *aResult)
+{
+ *aResult = mStartOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetStartOffline(bool aValue)
+{
+ mStartOffline = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfiles(nsISimpleEnumerator* *aResult)
+{
+ *aResult = new ProfileEnumerator(this->mFirst);
+ if (!*aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileService::ProfileEnumerator,
+ nsISimpleEnumerator)
+
+NS_IMETHODIMP
+nsToolkitProfileService::ProfileEnumerator::HasMoreElements(bool* aResult)
+{
+ *aResult = mCurrent ? true : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports* *aResult)
+{
+ if (!mCurrent) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aResult = mCurrent);
+
+ mCurrent = mCurrent->mNext;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetSelectedProfile(nsIToolkitProfile* *aResult)
+{
+ if (!mChosen && mFirst && !mFirst->mNext) // only one profile
+ mChosen = mFirst;
+
+ if (!mChosen) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aResult = mChosen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetSelectedProfile(nsIToolkitProfile* aProfile)
+{
+ if (mChosen != aProfile) {
+ mChosen = aProfile;
+ mDirty = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile* *aResult)
+{
+ if (!mDefault) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aResult = mDefault);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile)
+{
+ if (mDefault != aProfile) {
+ mDefault = aProfile;
+ mDirty = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfileByName(const nsACString& aName,
+ nsIToolkitProfile* *aResult)
+{
+ nsToolkitProfile* curP = mFirst;
+ while (curP) {
+ if (curP->mName.Equals(aName)) {
+ NS_ADDREF(*aResult = curP);
+ return NS_OK;
+ }
+ curP = curP->mNext;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::LockProfilePath(nsIFile* aDirectory,
+ nsIFile* aLocalDirectory,
+ nsIProfileLock* *aResult)
+{
+ return NS_LockProfilePath(aDirectory, aLocalDirectory, nullptr, aResult);
+}
+
+nsresult
+NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
+ nsIProfileUnlocker* *aUnlocker, nsIProfileLock* *aResult)
+{
+ RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
+ if (!lock) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = lock->Init(aPath, aTempPath, aUnlocker);
+ if (NS_FAILED(rv)) return rv;
+
+ lock.forget(aResult);
+ return NS_OK;
+}
+
+static const char kTable[] =
+ { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' };
+
+static void SaltProfileName(nsACString& aName)
+{
+ double fpTime = double(PR_Now());
+
+ // use 1e-6, granularity of PR_Now() on the mac is seconds
+ srand((unsigned int)(fpTime * 1e-6 + 0.5));
+
+ char salt[9];
+
+ int i;
+ for (i = 0; i < 8; ++i)
+ salt[i] = kTable[rand() % ArrayLength(kTable)];
+
+ salt[8] = '.';
+
+ aName.Insert(salt, 0, 9);
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::CreateDefaultProfileForApp(const nsACString& aProfileName,
+ const nsACString& aAppName,
+ const nsACString& aVendorName,
+ nsIToolkitProfile** aResult)
+{
+ NS_ENSURE_STATE(!aProfileName.IsEmpty() || !aAppName.IsEmpty());
+ nsCOMPtr<nsIFile> appData;
+ nsresult rv =
+ gDirServiceProvider->GetUserDataDirectory(getter_AddRefs(appData),
+ false,
+ &aProfileName,
+ &aAppName,
+ &aVendorName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> profilesini;
+ appData->Clone(getter_AddRefs(profilesini));
+ rv = profilesini->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ profilesini->Exists(&exists);
+ NS_ENSURE_FALSE(exists, NS_ERROR_ALREADY_INITIALIZED);
+
+ rv = CreateProfileInternal(nullptr,
+ NS_LITERAL_CSTRING("default"),
+ &aProfileName, &aAppName, &aVendorName,
+ true, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(*aResult);
+
+ nsCOMPtr<nsIFile> rootDir;
+ (*aResult)->GetRootDir(getter_AddRefs(rootDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString profileDir;
+ rv = rootDir->GetRelativeDescriptor(appData, profileDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString ini;
+ ini.SetCapacity(512);
+ ini.AppendLiteral("[General]\n");
+ ini.AppendLiteral("StartWithLastProfile=1\n\n");
+
+ ini.AppendLiteral("[Profile0]\n");
+ ini.AppendLiteral("Name=default\n");
+ ini.AppendLiteral("IsRelative=1\n");
+ ini.AppendLiteral("Path=");
+ ini.Append(profileDir);
+ ini.Append('\n');
+ ini.AppendLiteral("Default=1\n\n");
+
+ FILE* writeFile;
+ rv = profilesini->OpenANSIFileDesc("w", &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fwrite(ini.get(), sizeof(char), ini.Length(), writeFile) !=
+ ini.Length()) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ fclose(writeFile);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
+ const nsACString& aName,
+ nsIToolkitProfile** aResult)
+{
+ return CreateProfileInternal(aRootDir, aName,
+ nullptr, nullptr, nullptr, false, aResult);
+}
+
+nsresult
+nsToolkitProfileService::CreateProfileInternal(nsIFile* aRootDir,
+ const nsACString& aName,
+ const nsACString* aProfileName,
+ const nsACString* aAppName,
+ const nsACString* aVendorName,
+ bool aForExternalApp,
+ nsIToolkitProfile** aResult)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (!aForExternalApp) {
+ rv = GetProfileByName(aName, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIFile> rootDir (aRootDir);
+
+ nsAutoCString dirName;
+ if (!rootDir) {
+ rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir),
+ aProfileName, aAppName,
+ aVendorName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dirName = aName;
+ SaltProfileName(dirName);
+
+ if (NS_IsNativeUTF8()) {
+ rootDir->AppendNative(dirName);
+ } else {
+ rootDir->Append(NS_ConvertUTF8toUTF16(dirName));
+ }
+ }
+
+ nsCOMPtr<nsIFile> localDir;
+
+ bool isRelative;
+ rv = mAppData->Contains(rootDir, &isRelative);
+ if (NS_SUCCEEDED(rv) && isRelative) {
+ nsAutoCString path;
+ rv = rootDir->GetRelativeDescriptor(mAppData, path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewNativeLocalFile(EmptyCString(), true,
+ getter_AddRefs(localDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localDir->SetRelativeDescriptor(mTempData, path);
+ } else {
+ localDir = rootDir;
+ }
+
+ bool exists;
+ rv = rootDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exists) {
+ rv = rootDir->IsDirectory(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists)
+ return NS_ERROR_FILE_NOT_DIRECTORY;
+ }
+ else {
+ nsCOMPtr<nsIFile> profileDirParent;
+ nsAutoString profileDirName;
+
+ rv = rootDir->GetParent(getter_AddRefs(profileDirParent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = rootDir->GetLeafName(profileDirName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // let's ensure that the profile directory exists.
+ rv = rootDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = rootDir->SetPermissions(0700);
+
+ // If the profile is on the sdcard, this will fail but its non-fatal
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ }
+
+ rv = localDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) {
+ rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We created a new profile dir. Let's store a creation timestamp.
+ // Note that this code path does not apply if the profile dir was
+ // created prior to launching.
+ rv = CreateTimesInternal(rootDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsToolkitProfile* last = aForExternalApp ? nullptr : mFirst.get();
+ if (last) {
+ while (last->mNext)
+ last = last->mNext;
+ }
+
+ nsCOMPtr<nsIToolkitProfile> profile =
+ new nsToolkitProfile(aName, rootDir, localDir, last, aForExternalApp);
+ if (!profile) return NS_ERROR_OUT_OF_MEMORY;
+
+ profile.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ nsCOMPtr<nsIFile> creationLog;
+ rv = aProfileDir->Clone(getter_AddRefs(creationLog));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = creationLog->AppendNative(NS_LITERAL_CSTRING("times.json"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ creationLog->Exists(&exists);
+ if (exists) {
+ return NS_OK;
+ }
+
+ rv = creationLog->Create(nsIFile::NORMAL_FILE_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We don't care about microsecond resolution.
+ int64_t msec = PR_Now() / PR_USEC_PER_MSEC;
+
+ // Write it out.
+ PRFileDesc *writeFile;
+ rv = creationLog->OpenNSPRFileDesc(PR_WRONLY, 0700, &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PR_fprintf(writeFile, "{\n\"created\": %lld\n}\n", msec);
+ PR_Close(writeFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::GetProfileCount(uint32_t *aResult)
+{
+ if (!mFirst)
+ *aResult = 0;
+ else if (! mFirst->mNext)
+ *aResult = 1;
+ else
+ *aResult = 2;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsToolkitProfileService::Flush()
+{
+ // Errors during writing might cause unhappy semi-written files.
+ // To avoid this, write the entire thing to a buffer, then write
+ // that buffer to disk.
+
+ nsresult rv;
+ uint32_t pCount = 0;
+ nsToolkitProfile *cur;
+
+ for (cur = mFirst; cur != nullptr; cur = cur->mNext)
+ ++pCount;
+
+ uint32_t length;
+ const int bufsize = 100+MAXPATHLEN*pCount;
+ auto buffer = MakeUnique<char[]>(bufsize);
+
+ char *pos = buffer.get();
+ char *end = pos + bufsize;
+
+ pos += snprintf(pos, end - pos,
+ "[General]\n"
+ "StartWithLastProfile=%s\n\n",
+ mStartWithLast ? "1" : "0");
+
+ nsAutoCString path;
+ cur = mFirst;
+ pCount = 0;
+
+ while (cur) {
+ // if the profile dir is relative to appdir...
+ bool isRelative;
+ rv = mAppData->Contains(cur->mRootDir, &isRelative);
+ if (NS_SUCCEEDED(rv) && isRelative) {
+ // we use a relative descriptor
+ rv = cur->mRootDir->GetRelativeDescriptor(mAppData, path);
+ } else {
+ // otherwise, a persistent descriptor
+ rv = cur->mRootDir->GetPersistentDescriptor(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ pos += snprintf(pos, end - pos,
+ "[Profile%u]\n"
+ "Name=%s\n"
+ "IsRelative=%s\n"
+ "Path=%s\n",
+ pCount, cur->mName.get(),
+ isRelative ? "1" : "0", path.get());
+
+ nsCOMPtr<nsIToolkitProfile> profile;
+ rv = this->GetDefaultProfile(getter_AddRefs(profile));
+ if (NS_SUCCEEDED(rv) && profile == cur) {
+ pos += snprintf(pos, end - pos, "Default=1\n");
+ }
+
+ pos += snprintf(pos, end - pos, "\n");
+
+ cur = cur->mNext;
+ ++pCount;
+ }
+
+ FILE* writeFile;
+ rv = mListFile->OpenANSIFileDesc("w", &writeFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ length = pos - buffer.get();
+
+ if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) {
+ fclose(writeFile);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ fclose(writeFile);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsToolkitProfileFactory, nsIFactory)
+
+NS_IMETHODIMP
+nsToolkitProfileFactory::CreateInstance(nsISupports* aOuter, const nsID& aIID,
+ void** aResult)
+{
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsCOMPtr<nsIToolkitProfileService> profileService =
+ nsToolkitProfileService::gService;
+ if (!profileService) {
+ nsresult rv = NS_NewToolkitProfileService(getter_AddRefs(profileService));
+ if (NS_FAILED(rv))
+ return rv;
+ }
+ return profileService->QueryInterface(aIID, aResult);
+}
+
+NS_IMETHODIMP
+nsToolkitProfileFactory::LockFactory(bool aVal)
+{
+ return NS_OK;
+}
+
+nsresult
+NS_NewToolkitProfileFactory(nsIFactory* *aResult)
+{
+ *aResult = new nsToolkitProfileFactory();
+ if (!*aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+nsresult
+NS_NewToolkitProfileService(nsIToolkitProfileService* *aResult)
+{
+ nsToolkitProfileService* profileService = new nsToolkitProfileService();
+ if (!profileService)
+ return NS_ERROR_OUT_OF_MEMORY;
+ nsresult rv = profileService->Init();
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsToolkitProfileService::Init failed!");
+ delete profileService;
+ return rv;
+ }
+
+ NS_ADDREF(*aResult = profileService);
+ return NS_OK;
+}
+
+nsresult
+XRE_GetFileFromPath(const char *aPath, nsIFile* *aResult)
+{
+#if defined(XP_UNIX)
+ char fullPath[MAXPATHLEN];
+
+ if (!realpath(aPath, fullPath))
+ return NS_ERROR_FAILURE;
+
+ return NS_NewNativeLocalFile(nsDependentCString(fullPath), true,
+ aResult);
+#elif defined(XP_WIN)
+ WCHAR fullPath[MAXPATHLEN];
+
+ if (!_wfullpath(fullPath, NS_ConvertUTF8toUTF16(aPath).get(), MAXPATHLEN))
+ return NS_ERROR_FAILURE;
+
+ return NS_NewLocalFile(nsDependentString(fullPath), true,
+ aResult);
+
+#else
+#error Platform-specific logic needed here.
+#endif
+}
diff --git a/components/profile/userChrome-example.css b/components/profile/userChrome-example.css
new file mode 100644
index 000000000..420ec3fd6
--- /dev/null
+++ b/components/profile/userChrome-example.css
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * 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;
+ * }
+ */
+
+/*
+ * Eliminate the throbber and its annoying movement:
+ *
+ * #throbber-box {
+ * display: none !important;
+ * }
+ */
+
+/*
+ * For more examples see http://www-archive.mozilla.org/unix/customizing.html
+ */
+
diff --git a/components/profile/userContent-example.css b/components/profile/userContent-example.css
new file mode 100644
index 000000000..4528c9871
--- /dev/null
+++ b/components/profile/userContent-example.css
@@ -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/. */
+
+/*
+ * 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: turn off "blink" element blinking
+ *
+ * blink { text-decoration: none ! important; }
+ *
+ */
+
+/*
+ * example: give all tables a 2px border
+ *
+ * table { border: 2px solid; }
+ */
+
+/*
+ * example: turn off "marquee" element
+ *
+ * marquee { -moz-binding: none; }
+ *
+ */
+
+/*
+ * example: make search fields on www.mozilla.org black-on-white
+ *
+ * @-moz-document url-prefix(http://www.mozilla.org/) {
+ * #q { background: white ! important; color: black ! important; }
+ * }
+ */
+
+/*
+ * For more examples see http://www-archive.mozilla.org/unix/customizing.html
+ */
+
diff --git a/components/promiseworker/PromiseWorker.jsm b/components/promiseworker/PromiseWorker.jsm
new file mode 100644
index 000000000..0c6e054a2
--- /dev/null
+++ b/components/promiseworker/PromiseWorker.jsm
@@ -0,0 +1,390 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A wrapper around ChromeWorker with extended capabilities designed
+ * to simplify main thread-to-worker thread asynchronous function calls.
+ *
+ * This wrapper:
+ * - groups requests and responses as a method `post` that returns a `Promise`;
+ * - ensures that exceptions thrown on the worker thread are correctly deserialized;
+ * - provides some utilities for benchmarking various operations.
+ *
+ * Generally, you should use PromiseWorker.jsm along with its worker-side
+ * counterpart PromiseWorker.js.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["BasePromiseWorker"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+/**
+ * An implementation of queues (FIFO).
+ *
+ * The current implementation uses one array, runs in O(n ^ 2), and is optimized
+ * for the case in which queues are generally short.
+ */
+function Queue() {
+ this._array = [];
+}
+Queue.prototype = {
+ pop: function pop() {
+ return this._array.shift();
+ },
+ push: function push(x) {
+ return this._array.push(x);
+ },
+ isEmpty: function isEmpty() {
+ return this._array.length == 0;
+ }
+};
+
+/**
+ * Constructors for decoding standard exceptions received from the
+ * worker.
+ */
+const EXCEPTION_CONSTRUCTORS = {
+ EvalError: function(error) {
+ let result = new EvalError(error.message, error.fileName, error.lineNumber);
+ result.stack = error.stack;
+ return result;
+ },
+ InternalError: function(error) {
+ let result = new InternalError(error.message, error.fileName, error.lineNumber);
+ result.stack = error.stack;
+ return result;
+ },
+ RangeError: function(error) {
+ let result = new RangeError(error.message, error.fileName, error.lineNumber);
+ result.stack = error.stack;
+ return result;
+ },
+ ReferenceError: function(error) {
+ let result = new ReferenceError(error.message, error.fileName, error.lineNumber);
+ result.stack = error.stack;
+ return result;
+ },
+ SyntaxError: function(error) {
+ let result = new SyntaxError(error.message, error.fileName, error.lineNumber);
+ result.stack = error.stack;
+ return result;
+ },
+ TypeError: function(error) {
+ let result = new TypeError(error.message, error.fileName, error.lineNumber);
+ result.stack = error.stack;
+ return result;
+ },
+ URIError: function(error) {
+ let result = new URIError(error.message, error.fileName, error.lineNumber);
+ result.stack = error.stack;
+ return result;
+ },
+ StopIteration: function() {
+ return StopIteration;
+ }
+};
+
+/**
+ * An object responsible for dispatching messages to a chrome worker
+ * and routing the responses.
+ *
+ * Instances of this constructor who need logging may provide a method
+ * `log: function(...args) { ... }` in charge of printing out (or
+ * discarding) logs.
+ *
+ * Instances of this constructor may add exception handlers to
+ * `this.ExceptionHandlers`, if they need to handle custom exceptions.
+ *
+ * @param {string} url The url containing the source code for this worker,
+ * as in constructor ChromeWorker.
+ *
+ * @constructor
+ */
+this.BasePromiseWorker = function(url) {
+ if (typeof url != "string") {
+ throw new TypeError("Expecting a string");
+ }
+ this._url = url;
+
+ /**
+ * A set of methods, with the following
+ *
+ * ConstructorName: function({message, fileName, lineNumber}) {
+ * // Construct a new instance of ConstructorName based on
+ * // `message`, `fileName`, `lineNumber`
+ * }
+ *
+ * By default, this covers EvalError, InternalError, RangeError,
+ * ReferenceError, SyntaxError, TypeError, URIError, StopIteration.
+ */
+ this.ExceptionHandlers = Object.create(EXCEPTION_CONSTRUCTORS);
+
+ /**
+ * The queue of deferred, waiting for the completion of their
+ * respective job by the worker.
+ *
+ * Each item in the list may contain an additional field |closure|,
+ * used to store strong references to value that must not be
+ * garbage-collected before the reply has been received (e.g.
+ * arrays).
+ *
+ * @type {Queue<{deferred:deferred, closure:*=}>}
+ */
+ this._queue = new Queue();
+
+ /**
+ * The number of the current message.
+ *
+ * Used for debugging purposes.
+ */
+ this._id = 0;
+
+ /**
+ * The instant at which the worker was launched.
+ */
+ this.launchTimeStamp = null;
+
+ /**
+ * Timestamps provided by the worker for statistics purposes.
+ */
+ this.workerTimeStamps = null;
+};
+this.BasePromiseWorker.prototype = {
+ log: function() {
+ // By Default, ignore all logs.
+ },
+
+ /**
+ * Instantiate the worker lazily.
+ */
+ get _worker() {
+ delete this._worker;
+ let worker = new ChromeWorker(this._url);
+ Object.defineProperty(this, "_worker", {value:
+ worker
+ });
+
+ // We assume that we call to _worker for the purpose of calling
+ // postMessage().
+ this.launchTimeStamp = Date.now();
+
+ /**
+ * Receive errors that have been serialized by the built-in mechanism
+ * of DOM/Chrome Workers.
+ *
+ * PromiseWorker.js knows how to serialize a number of errors
+ * without losing information. These are treated by
+ * |worker.onmessage|. However, for other errors, we rely on
+ * DOM's mechanism for serializing errors, which transmits these
+ * errors through |worker.onerror|.
+ *
+ * @param {Error} error Some JS error.
+ */
+ worker.onerror = error => {
+ this.log("Received uncaught error from worker", error.message, error.filename, error.lineno);
+ error.preventDefault();
+ let {deferred} = this._queue.pop();
+ deferred.reject(error);
+ };
+
+ /**
+ * Receive messages from the worker, propagate them to the listeners.
+ *
+ * Messages must have one of the following shapes:
+ * - {ok: some_value} in case of success
+ * - {fail: some_error} in case of error, where
+ * some_error is an instance of |PromiseWorker.WorkerError|
+ *
+ * Messages may also contain a field |id| to help
+ * with debugging.
+ *
+ * Messages may also optionally contain a field |durationMs|, holding
+ * the duration of the function call in milliseconds.
+ *
+ * @param {*} msg The message received from the worker.
+ */
+ worker.onmessage = msg => {
+ this.log("Received message from worker", msg.data);
+ let handler = this._queue.pop();
+ let deferred = handler.deferred;
+ let data = msg.data;
+ if (data.id != handler.id) {
+ throw new Error("Internal error: expecting msg " + handler.id + ", " +
+ " got " + data.id + ": " + JSON.stringify(msg.data));
+ }
+ if ("timeStamps" in data) {
+ this.workerTimeStamps = data.timeStamps;
+ }
+ if ("ok" in data) {
+ // Pass the data to the listeners.
+ deferred.resolve(data);
+ } else if ("fail" in data) {
+ // We have received an error that was serialized by the
+ // worker.
+ deferred.reject(new WorkerError(data.fail));
+ }
+ };
+ return worker;
+ },
+
+ /**
+ * Post a message to a worker.
+ *
+ * @param {string} fun The name of the function to call.
+ * @param {Array} args The arguments to pass to `fun`. If any
+ * of the arguments is a Promise, it is resolved before posting the
+ * message. If any of the arguments needs to be transfered instead
+ * of copied, this may be specified by making the argument an instance
+ * of `BasePromiseWorker.Meta` or by using the `transfers` argument.
+ * By convention, the last argument may be an object `options`
+ * with some of the following fields:
+ * - {number|null} outExecutionDuration A parameter to be filled with the
+ * duration of the off main thread execution for this call.
+ * @param {*=} closure An object holding references that should not be
+ * garbage-collected before the message treatment is complete.
+ * @param {Array=} transfers An array of objects that should be transfered
+ * to the worker instead of being copied. If any of the objects is a Promise,
+ * it is resolved before posting the message.
+ *
+ * @return {promise}
+ */
+ post: function(fun, args, closure, transfers) {
+ return Task.spawn(function* postMessage() {
+ // Normalize in case any of the arguments is a promise
+ if (args) {
+ args = yield Promise.resolve(Promise.all(args));
+ }
+ if (transfers) {
+ transfers = yield Promise.resolve(Promise.all(transfers));
+ } else {
+ transfers = [];
+ }
+
+ if (args) {
+ // Extract `Meta` data
+ args = args.map(arg => {
+ if (arg instanceof BasePromiseWorker.Meta) {
+ if (arg.meta && "transfers" in arg.meta) {
+ transfers.push(...arg.meta.transfers);
+ }
+ return arg.data;
+ }
+ return arg;
+ });
+ }
+
+ let id = ++this._id;
+ let message = {fun: fun, args: args, id: id};
+ this.log("Posting message", message);
+ try {
+ this._worker.postMessage(message, ...[transfers]);
+ } catch (ex) {
+ if (typeof ex == "number") {
+ this.log("Could not post message", message, "due to xpcom error", ex);
+ // handle raw xpcom errors (see eg bug 961317)
+ throw new Components.Exception("Error in postMessage", ex);
+ }
+
+ this.log("Could not post message", message, "due to error", ex);
+ throw ex;
+ }
+
+ let deferred = Promise.defer();
+ this._queue.push({deferred:deferred, closure: closure, id: id});
+ this.log("Message posted");
+
+ let reply;
+ try {
+ this.log("Expecting reply");
+ reply = yield deferred.promise;
+ } catch (error) {
+ this.log("Got error", error);
+ reply = error;
+
+ if (error instanceof WorkerError) {
+ // We know how to deserialize most well-known errors
+ throw this.ExceptionHandlers[error.data.exn](error.data);
+ }
+
+ if (error instanceof ErrorEvent) {
+ // Other errors get propagated as instances of ErrorEvent
+ this.log("Error serialized by DOM", error.message, error.filename, error.lineno);
+ throw new Error(error.message, error.filename, error.lineno);
+ }
+
+ // We don't know about this kind of error
+ throw error;
+ }
+
+ // By convention, the last argument may be an object `options`.
+ let options = null;
+ if (args) {
+ options = args[args.length - 1];
+ }
+
+ // Check for duration and return result.
+ if (!options ||
+ typeof options !== "object" ||
+ !("outExecutionDuration" in options)) {
+ return reply.ok;
+ }
+ // If reply.durationMs is not present, just return the result,
+ // without updating durations (there was an error in the method
+ // dispatch).
+ if (!("durationMs" in reply)) {
+ return reply.ok;
+ }
+ // Bug 874425 demonstrates that two successive calls to Date.now()
+ // can actually produce an interval with negative duration.
+ // We assume that this is due to an operation that is so short
+ // that Date.now() is not monotonic, so we round this up to 0.
+ let durationMs = Math.max(0, reply.durationMs);
+ // Accumulate (or initialize) outExecutionDuration
+ if (typeof options.outExecutionDuration == "number") {
+ options.outExecutionDuration += durationMs;
+ } else {
+ options.outExecutionDuration = durationMs;
+ }
+ return reply.ok;
+
+ }.bind(this));
+ }
+};
+
+/**
+ * An error that has been serialized by the worker.
+ *
+ * @constructor
+ */
+function WorkerError(data) {
+ this.data = data;
+}
+
+/**
+ * A constructor used to send data to the worker thread while
+ * with special treatment (e.g. transmitting data instead of
+ * copying it).
+ *
+ * @param {object=} data The data to send to the caller thread.
+ * @param {object=} meta Additional instructions, as an object
+ * that may contain the following fields:
+ * - {Array} transfers An array of objects that should be transferred
+ * instead of being copied.
+ *
+ * @constructor
+ */
+this.BasePromiseWorker.Meta = function(data, meta) {
+ this.data = data;
+ this.meta = meta;
+};
diff --git a/components/promiseworker/moz.build b/components/promiseworker/moz.build
new file mode 100644
index 000000000..f14062b63
--- /dev/null
+++ b/components/promiseworker/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+DIRS += ['worker']
+
+EXTRA_JS_MODULES += ['PromiseWorker.jsm']
diff --git a/components/promiseworker/worker/PromiseWorker.js b/components/promiseworker/worker/PromiseWorker.js
new file mode 100644
index 000000000..ba4408c1a
--- /dev/null
+++ b/components/promiseworker/worker/PromiseWorker.js
@@ -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/. */
+
+/**
+ * A wrapper around `self` with extended capabilities designed
+ * to simplify main thread-to-worker thread asynchronous function calls.
+ *
+ * This wrapper:
+ * - groups requests and responses as a method `post` that returns a `Promise`;
+ * - ensures that exceptions thrown on the worker thread are correctly serialized;
+ * - provides some utilities for benchmarking various operations.
+ *
+ * Generally, you should use PromiseWorker.js along with its main thread-side
+ * counterpart PromiseWorker.jsm.
+ */
+
+"use strict";
+
+if (typeof Components != "undefined") {
+ throw new Error("This module is meant to be used from the worker thread");
+}
+if (typeof require == "undefined" || typeof module == "undefined") {
+ throw new Error("this module is meant to be imported using the implementation of require() at resource://gre/modules/workers/require.js");
+}
+
+importScripts("resource://gre/modules/workers/require.js");
+
+/**
+ * Built-in JavaScript exceptions that may be serialized without
+ * loss of information.
+ */
+const EXCEPTION_NAMES = {
+ EvalError: "EvalError",
+ InternalError: "InternalError",
+ RangeError: "RangeError",
+ ReferenceError: "ReferenceError",
+ SyntaxError: "SyntaxError",
+ TypeError: "TypeError",
+ URIError: "URIError",
+};
+
+/**
+ * A constructor used to return data to the caller thread while
+ * also executing some specific treatment (e.g. shutting down
+ * the current thread, transmitting data instead of copying it).
+ *
+ * @param {object=} data The data to return to the caller thread.
+ * @param {object=} meta Additional instructions, as an object
+ * that may contain the following fields:
+ * - {bool} shutdown If |true|, shut down the current thread after
+ * having sent the result.
+ * - {Array} transfers An array of objects that should be transferred
+ * instead of being copied.
+ *
+ * @constructor
+ */
+function Meta(data, meta) {
+ this.data = data;
+ this.meta = meta;
+}
+exports.Meta = Meta;
+
+/**
+ * Base class for a worker.
+ *
+ * Derived classes are expected to provide the following methods:
+ * {
+ * dispatch: function(method, args) {
+ * // Dispatch a call to method `method` with args `args`
+ * },
+ * log: function(...msg) {
+ * // Log (or discard) messages (optional)
+ * },
+ * postMessage: function(message, ...transfers) {
+ * // Post a message to the main thread
+ * },
+ * close: function() {
+ * // Close the worker
+ * }
+ * }
+ *
+ * By default, the AbstractWorker is not connected to a message port,
+ * hence will not receive anything.
+ *
+ * To connect it, use `onmessage`, as follows:
+ * self.addEventListener("message", msg => myWorkerInstance.handleMessage(msg));
+ */
+function AbstractWorker(agent) {
+ this._agent = agent;
+}
+AbstractWorker.prototype = {
+ // Default logger: discard all messages
+ log: function() {
+ },
+
+ /**
+ * Handle a message.
+ */
+ handleMessage: function(msg) {
+ let data = msg.data;
+ this.log("Received message", data);
+ let id = data.id;
+
+ let start;
+ let options;
+ if (data.args) {
+ options = data.args[data.args.length - 1];
+ }
+ // If |outExecutionDuration| option was supplied, start measuring the
+ // duration of the operation.
+ if (options && typeof options === "object" && "outExecutionDuration" in options) {
+ start = Date.now();
+ }
+
+ let result;
+ let exn;
+ let durationMs;
+ let method = data.fun;
+ try {
+ this.log("Calling method", method);
+ result = this.dispatch(method, data.args);
+ this.log("Method", method, "succeeded");
+ } catch (ex) {
+ exn = ex;
+ this.log("Error while calling agent method", method, exn, exn.moduleStack || exn.stack || "");
+ }
+
+ if (start) {
+ // Record duration
+ durationMs = Date.now() - start;
+ this.log("Method took", durationMs, "ms");
+ }
+
+ // Now, post a reply, possibly as an uncaught error.
+ // We post this message from outside the |try ... catch| block
+ // to avoid capturing errors that take place during |postMessage| and
+ // built-in serialization.
+ if (!exn) {
+ this.log("Sending positive reply", result, "id is", id);
+ if (result instanceof Meta) {
+ if ("transfers" in result.meta) {
+ // Take advantage of zero-copy transfers
+ this.postMessage({ok: result.data, id: id, durationMs: durationMs},
+ result.meta.transfers);
+ } else {
+ this.postMessage({ok: result.data, id:id, durationMs: durationMs});
+ }
+ if (result.meta.shutdown || false) {
+ // Time to close the worker
+ this.close();
+ }
+ } else {
+ this.postMessage({ok: result, id:id, durationMs: durationMs});
+ }
+ } else if (exn.constructor.name in EXCEPTION_NAMES) {
+ // Rather than letting the DOM mechanism [de]serialize built-in
+ // JS errors, which loses lots of information (in particular,
+ // the constructor name, the moduleName and the moduleStack),
+ // we [de]serialize them manually with a little more care.
+ this.log("Sending back exception", exn.constructor.name, "id is", id);
+ let error = {
+ exn: exn.constructor.name,
+ message: exn.message,
+ fileName: exn.moduleName || exn.fileName,
+ lineNumber: exn.lineNumber,
+ stack: exn.moduleStack
+ };
+ this.postMessage({fail: error, id: id, durationMs: durationMs});
+ } else if (exn == StopIteration) {
+ // StopIteration is a well-known singleton, and requires a
+ // slightly different treatment.
+ this.log("Sending back StopIteration, id is", id);
+ let error = {
+ exn: "StopIteration"
+ };
+ this.postMessage({fail: error, id: id, durationMs: durationMs});
+ } else if ("toMsg" in exn) {
+ // Extension mechanism for exception [de]serialization. We
+ // assume that any exception with a method `toMsg()` knows how
+ // to serialize itself. The other side is expected to have
+ // registered a deserializer using the `ExceptionHandlers`
+ // object.
+ this.log("Sending back an error that knows how to serialize itself", exn, "id is", id);
+ let msg = exn.toMsg();
+ this.postMessage({fail: msg, id:id, durationMs: durationMs});
+ } else {
+ // If we encounter an exception for which we have no
+ // serialization mechanism in place, we have no choice but to
+ // let the DOM handle said [de]serialization. We can just
+ // attempt to mitigate the data loss by injecting `moduleName` and
+ // `moduleStack`.
+ this.log("Sending back regular error", exn, exn.moduleStack || exn.stack, "id is", id);
+
+ try {
+ // Attempt to introduce human-readable filename and stack
+ exn.filename = exn.moduleName;
+ exn.stack = exn.moduleStack;
+ } catch (_) {
+ // Nothing we can do
+ }
+ throw exn;
+ }
+ }
+};
+exports.AbstractWorker = AbstractWorker;
diff --git a/components/promiseworker/worker/moz.build b/components/promiseworker/worker/moz.build
new file mode 100644
index 000000000..abac4be46
--- /dev/null
+++ b/components/promiseworker/worker/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_JS_MODULES.workers = [
+ 'PromiseWorker.js',
+]
diff --git a/components/prompts/content/commonDialog.css b/components/prompts/content/commonDialog.css
new file mode 100644
index 000000000..89f88db7a
--- /dev/null
+++ b/components/prompts/content/commonDialog.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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
+
+#infoContainer {
+ max-width: 45em;
+}
+
+#info\.body {
+ -moz-user-focus: normal;
+ -moz-user-select: text;
+ cursor: text !important;
+ white-space: pre-wrap;
+ unicode-bidi: plaintext;
+}
+
+#loginLabel, #password1Label {
+ text-align: right;
+}
+
diff --git a/components/prompts/content/commonDialog.js b/components/prompts/content/commonDialog.js
new file mode 100644
index 000000000..70b373d1f
--- /dev/null
+++ b/components/prompts/content/commonDialog.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/. */
+
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/CommonDialog.jsm");
+
+var propBag, args, Dialog;
+
+function commonDialogOnLoad() {
+ propBag = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2)
+ .QueryInterface(Ci.nsIWritablePropertyBag);
+ // Convert to a JS object
+ args = {};
+ let propEnum = propBag.enumerator;
+ while (propEnum.hasMoreElements()) {
+ let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
+ args[prop.name] = prop.value;
+ }
+
+ let dialog = document.documentElement;
+
+ let ui = {
+ prompt : window,
+ loginContainer : document.getElementById("loginContainer"),
+ loginTextbox : document.getElementById("loginTextbox"),
+ loginLabel : document.getElementById("loginLabel"),
+ password1Container : document.getElementById("password1Container"),
+ password1Textbox : document.getElementById("password1Textbox"),
+ password1Label : document.getElementById("password1Label"),
+ infoBody : document.getElementById("info.body"),
+ infoTitle : document.getElementById("info.title"),
+ infoIcon : document.getElementById("info.icon"),
+ checkbox : document.getElementById("checkbox"),
+ checkboxContainer : document.getElementById("checkboxContainer"),
+ button3 : dialog.getButton("extra2"),
+ button2 : dialog.getButton("extra1"),
+ button1 : dialog.getButton("cancel"),
+ button0 : dialog.getButton("accept"),
+ focusTarget : window,
+ };
+
+ // limit the dialog to the screen width
+ document.getElementById("filler").maxWidth = screen.availWidth;
+
+ Dialog = new CommonDialog(args, ui);
+ Dialog.onLoad(dialog);
+ window.getAttention();
+}
+
+function commonDialogOnUnload() {
+ // Convert args back into property bag
+ for (let propName in args)
+ propBag.setProperty(propName, args[propName]);
+}
diff --git a/components/prompts/content/commonDialog.xul b/components/prompts/content/commonDialog.xul
new file mode 100644
index 000000000..a7621ccdf
--- /dev/null
+++ b/components/prompts/content/commonDialog.xul
@@ -0,0 +1,87 @@
+<?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://global/content/commonDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://global/skin/commonDialog.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://global/locale/commonDialog.dtd">
+
+<dialog id="commonDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ aria-describedby="info.body"
+ onunload="commonDialogOnUnload();"
+ ondialogaccept="Dialog.onButton0(); return true;"
+ ondialogcancel="Dialog.onButton1(); return true;"
+ ondialogextra1="Dialog.onButton2(); window.close();"
+ ondialogextra2="Dialog.onButton3(); window.close();"
+ buttonpack="center">
+
+ <script type="application/javascript" src="chrome://global/content/commonDialog.js"/>
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript">
+ document.addEventListener("DOMContentLoaded", function() {
+ commonDialogOnLoad();
+ });
+ </script>
+
+ <commandset id="selectEditMenuItems">
+ <command id="cmd_copy" oncommand="goDoCommand('cmd_copy')" disabled="true"/>
+ <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/>
+ </commandset>
+
+ <popupset id="contentAreaContextSet">
+ <menupopup id="contentAreaContextMenu"
+ onpopupshowing="goUpdateCommand('cmd_copy')">
+ <menuitem id="context-copy"
+ label="&copyCmd.label;"
+ accesskey="&copyCmd.accesskey;"
+ command="cmd_copy"
+ disabled="true"/>
+ <menuitem id="context-selectall"
+ label="&selectAllCmd.label;"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+ </menupopup>
+ </popupset>
+
+ <hbox id="filler" style="min-width: 0%;">
+ <spacer style="width: 29em;"/>
+ </hbox>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row>
+ <hbox id="iconContainer" align="start">
+ <image id="info.icon" class="spaced"/>
+ </hbox>
+ <vbox id="infoContainer" pack="center">
+ <description id="info.title"
+ hidden="true"
+ />
+ <description id="info.body" context="contentAreaContextMenu" noinitialfocus="true"/>
+ </vbox>
+ </row>
+ <row id="loginContainer" hidden="true" align="center">
+ <label id="loginLabel" value="&editfield0.label;" control="loginTextbox"/>
+ <textbox id="loginTextbox"/>
+ </row>
+ <row id ="password1Container" hidden="true" align="center">
+ <label id="password1Label" value="&editfield1.label;" control="password1Textbox"/>
+ <textbox type="password" id="password1Textbox"/>
+ </row>
+ <row id="checkboxContainer" hidden="true">
+ <spacer/>
+ <checkbox id="checkbox" oncommand="Dialog.onCheckbox()"/>
+ </row>
+ </rows>
+ </grid>
+
+</dialog>
diff --git a/components/prompts/content/selectDialog.js b/components/prompts/content/selectDialog.js
new file mode 100644
index 000000000..7628dc8d9
--- /dev/null
+++ b/components/prompts/content/selectDialog.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+var gArgs, listBox;
+
+function dialogOnLoad() {
+ gArgs = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2)
+ .QueryInterface(Ci.nsIWritablePropertyBag);
+
+ let promptType = gArgs.getProperty("promptType");
+ if (promptType != "select") {
+ Cu.reportError("selectDialog opened for unknown type: " + promptType);
+ window.close();
+ }
+
+ // Default to canceled.
+ gArgs.setProperty("ok", false);
+
+ document.title = gArgs.getProperty("title");
+
+ let text = gArgs.getProperty("text");
+ document.getElementById("info.txt").setAttribute("value", text);
+
+ let items = gArgs.getProperty("list");
+ listBox = document.getElementById("list");
+
+ for (let i = 0; i < items.length; i++) {
+ let str = items[i];
+ if (str == "")
+ str = "<>";
+ listBox.appendItem(str);
+ listBox.getItemAtIndex(i).addEventListener("dblclick", dialogDoubleClick, false);
+ }
+ listBox.selectedIndex = 0;
+ listBox.focus();
+
+ // resize the window to the content
+ window.sizeToContent();
+
+ // Move to the right location
+ moveToAlertPosition();
+ centerWindowOnScreen();
+
+ // play sound
+ try {
+ Cc["@mozilla.org/sound;1"].
+ createInstance(Ci.nsISound).
+ playEventSound(Ci.nsISound.EVENT_SELECT_DIALOG_OPEN);
+ } catch (e) { }
+}
+
+function dialogOK() {
+ gArgs.setProperty("selected", listBox.selectedIndex);
+ gArgs.setProperty("ok", true);
+ return true;
+}
+
+function dialogDoubleClick() {
+ dialogOK();
+ window.close();
+}
diff --git a/components/prompts/content/selectDialog.xul b/components/prompts/content/selectDialog.xul
new file mode 100644
index 000000000..9b72bcfb0
--- /dev/null
+++ b/components/prompts/content/selectDialog.xul
@@ -0,0 +1,22 @@
+<?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/global.css" type="text/css"?>
+<!DOCTYPE dialog SYSTEM "chrome://global/locale/commonDialog.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="dialogOnLoad()"
+ ondialogaccept="return dialogOK();">
+
+ <script type="application/javascript" src="chrome://global/content/selectDialog.js" />
+ <keyset id="dialogKeys"/>
+ <vbox style="width: 24em;margin: 5px;">
+ <label id="info.txt"/>
+ <vbox>
+ <listbox id="list" rows="4" flex="1"/>
+ </vbox>
+ </vbox>
+</dialog>
diff --git a/components/prompts/content/tabprompts.css b/components/prompts/content/tabprompts.css
new file mode 100644
index 000000000..c4b0f7593
--- /dev/null
+++ b/components/prompts/content/tabprompts.css
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Tab Modal Prompt boxes */
+tabmodalprompt {
+ width: 100%;
+ height: 100%;
+ -moz-box-pack: center;
+ -moz-box-orient: vertical;
+}
+
+.mainContainer {
+ min-width: 20em;
+ min-height: 12em;
+ -moz-user-focus: normal;
+}
+
+.info\.title {
+ margin-bottom: 1em !important;
+ font-weight: bold;
+}
+
+.info\.body {
+ margin: 0 !important;
+ -moz-user-focus: normal;
+ -moz-user-select: text;
+ cursor: text !important;
+ white-space: pre-wrap;
+ unicode-bidi: plaintext;
+}
+
+label[value=""] {
+ visibility: collapse;
+}
diff --git a/components/prompts/content/tabprompts.xml b/components/prompts/content/tabprompts.xml
new file mode 100644
index 000000000..5355bb4cf
--- /dev/null
+++ b/components/prompts/content/tabprompts.xml
@@ -0,0 +1,339 @@
+<?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 % commonDialogDTD SYSTEM "chrome://global/locale/commonDialog.dtd">
+<!ENTITY % dialogOverlayDTD SYSTEM "chrome://global/locale/dialogOverlay.dtd">
+%commonDialogDTD;
+%dialogOverlayDTD;
+]>
+
+<bindings id="tabPrompts"
+ 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="tabmodalprompt">
+
+ <resources>
+ <stylesheet src="chrome://global/content/tabprompts.css"/>
+ <stylesheet src="chrome://global/skin/tabprompts.css"/>
+ </resources>
+
+ <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ role="dialog"
+ aria-describedby="info.body">
+
+ <!-- This is based on the guts of commonDialog.xul -->
+ <spacer flex="1"/>
+ <hbox pack="center">
+ <vbox anonid="mainContainer" class="mainContainer">
+ <grid class="topContainer" flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <vbox anonid="infoContainer" align="center" pack="center" flex="1">
+ <description anonid="info.title" class="info.title" hidden="true" />
+ <description anonid="info.body" class="info.body"/>
+ </vbox>
+
+ <row anonid="loginContainer" hidden="true" align="center">
+ <label anonid="loginLabel" value="&editfield0.label;" control="loginTextbox"/>
+ <textbox anonid="loginTextbox"/>
+ </row>
+
+ <row anonid="password1Container" hidden="true" align="center">
+ <label anonid="password1Label" value="&editfield1.label;" control="password1Textbox"/>
+ <textbox anonid="password1Textbox" type="password"/>
+ </row>
+
+ <row anonid="checkboxContainer" hidden="true">
+ <spacer/>
+ <checkbox anonid="checkbox"/>
+ </row>
+
+ <xbl:children includes="row"/>
+ </rows>
+ </grid>
+ <xbl:children/>
+ <hbox class="buttonContainer">
+#ifdef XP_UNIX
+ <button anonid="button3" hidden="true"/>
+ <button anonid="button2" hidden="true"/>
+ <spacer anonid="buttonSpacer" flex="1"/>
+ <button anonid="button1" label="&cancelButton.label;"/>
+ <button anonid="button0" label="&okButton.label;"/>
+#else
+ <button anonid="button3" hidden="true"/>
+ <spacer anonid="buttonSpacer" flex="1"/>
+ <button anonid="button0" label="&okButton.label;"/>
+ <button anonid="button2" hidden="true"/>
+ <button anonid="button1" label="&cancelButton.label;"/>
+#endif
+ </hbox>
+ </vbox>
+ </hbox>
+ <spacer flex="2"/>
+ </xbl:content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor>
+ <![CDATA[
+ let self = this;
+ function getElement(anonid) {
+ return document.getAnonymousElementByAttribute(self, "anonid", anonid);
+ }
+
+ this.ui = {
+ prompt : this,
+ loginContainer : getElement("loginContainer"),
+ loginTextbox : getElement("loginTextbox"),
+ loginLabel : getElement("loginLabel"),
+ password1Container : getElement("password1Container"),
+ password1Textbox : getElement("password1Textbox"),
+ password1Label : getElement("password1Label"),
+ infoBody : getElement("info.body"),
+ infoTitle : getElement("info.title"),
+ infoIcon : null,
+ checkbox : getElement("checkbox"),
+ checkboxContainer : getElement("checkboxContainer"),
+ button3 : getElement("button3"),
+ button2 : getElement("button2"),
+ button1 : getElement("button1"),
+ button0 : getElement("button0"),
+ // focusTarget (for BUTTON_DELAY_ENABLE) not yet supported
+ };
+
+ this.ui.button0.addEventListener("command", this.onButtonClick.bind(this, 0), false);
+ this.ui.button1.addEventListener("command", this.onButtonClick.bind(this, 1), false);
+ this.ui.button2.addEventListener("command", this.onButtonClick.bind(this, 2), false);
+ this.ui.button3.addEventListener("command", this.onButtonClick.bind(this, 3), false);
+ // Anonymous wrapper used here because |Dialog| doesn't exist until init() is called!
+ this.ui.checkbox.addEventListener("command", function() { self.Dialog.onCheckbox(); }, false);
+ this.isLive = false;
+ ]]>
+ </constructor>
+ <destructor>
+ <![CDATA[
+ if (this.isLive) {
+ this.abortPrompt();
+ }
+ ]]>
+ </destructor>
+
+ <field name="ui"/>
+ <field name="args"/>
+ <field name="linkedTab"/>
+ <field name="onCloseCallback"/>
+ <field name="Dialog"/>
+ <field name="isLive"/>
+ <field name="availWidth"/>
+ <field name="availHeight"/>
+ <field name="minWidth"/>
+ <field name="minHeight"/>
+
+ <method name="init">
+ <parameter name="args"/>
+ <parameter name="linkedTab"/>
+ <parameter name="onCloseCallback"/>
+ <body>
+ <![CDATA[
+ this.args = args;
+ this.linkedTab = linkedTab;
+ this.onCloseCallback = onCloseCallback;
+
+ if (args.enableDelay)
+ throw "BUTTON_DELAY_ENABLE not yet supported for tab-modal prompts";
+
+ // We need to remove the prompt when the tab or browser window is closed or
+ // the page navigates, else we never unwind the event loop and that's sad times.
+ // Remember to cleanup in shutdownPrompt()!
+ this.isLive = true;
+ window.addEventListener("resize", this, false);
+ window.addEventListener("unload", this, false);
+ linkedTab.addEventListener("TabClose", this, false);
+ // Note:
+ // nsPrompter.js or in e10s mode browser-parent.js call abortPrompt,
+ // when the domWindow, for which the prompt was created, generates
+ // a "pagehide" event.
+
+ let tmp = {};
+ Components.utils.import("resource://gre/modules/CommonDialog.jsm", tmp);
+ this.Dialog = new tmp.CommonDialog(args, this.ui);
+ this.Dialog.onLoad(null);
+
+ // Display the tabprompt title that shows the prompt origin when
+ // the prompt origin is not the same as that of the top window.
+ if (!args.showAlertOrigin)
+ this.ui.infoTitle.removeAttribute("hidden");
+
+ // TODO: should unhide buttonSpacer on Windows when there are 4 buttons.
+ // Better yet, just drop support for 4-button dialogs. (bug 609510)
+
+ this.onResize();
+ ]]>
+ </body>
+ </method>
+
+ <method name="shutdownPrompt">
+ <body>
+ <![CDATA[
+ // remove our event listeners
+ try {
+ window.removeEventListener("resize", this, false);
+ window.removeEventListener("unload", this, false);
+ this.linkedTab.removeEventListener("TabClose", this, false);
+ } catch (e) { }
+ this.isLive = false;
+ // invoke callback
+ this.onCloseCallback();
+ ]]>
+ </body>
+ </method>
+
+ <method name="abortPrompt">
+ <body>
+ <![CDATA[
+ // Called from other code when the page changes.
+ this.Dialog.abortPrompt();
+ this.shutdownPrompt();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ switch (aEvent.type) {
+ case "resize":
+ this.onResize();
+ break;
+ case "unload":
+ case "TabClose":
+ this.abortPrompt();
+ break;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="onResize">
+ <body>
+ <![CDATA[
+ let availWidth = this.clientWidth;
+ let availHeight = this.clientHeight;
+ if (availWidth == this.availWidth && availHeight == this.availHeight)
+ return;
+ this.availWidth = availWidth;
+ this.availHeight = availHeight;
+
+ let self = this;
+ function getElement(anonid) {
+ return document.getAnonymousElementByAttribute(self, "anonid", anonid);
+ }
+ let main = getElement("mainContainer");
+ let info = getElement("infoContainer");
+ let body = this.ui.infoBody;
+
+ // cap prompt dimensions at 60% width and 60% height of content area
+ if (!this.minWidth)
+ this.minWidth = parseInt(window.getComputedStyle(main).minWidth);
+ if (!this.minHeight)
+ this.minHeight = parseInt(window.getComputedStyle(main).minHeight);
+ let maxWidth = Math.max(Math.floor(availWidth * 0.6), this.minWidth) +
+ info.clientWidth - main.clientWidth;
+ let maxHeight = Math.max(Math.floor(availHeight * 0.6), this.minHeight) +
+ info.clientHeight - main.clientHeight;
+ body.style.maxWidth = maxWidth + "px";
+ info.style.overflow = info.style.width = info.style.height = "";
+
+ // when prompt text is too long, use scrollbars
+ if (info.clientWidth > maxWidth) {
+ info.style.overflow = "auto";
+ info.style.width = maxWidth + "px";
+ }
+ if (info.clientHeight > maxHeight) {
+ info.style.overflow = "auto";
+ info.style.height = maxHeight + "px";
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="onButtonClick">
+ <parameter name="buttonNum"/>
+ <body>
+ <![CDATA[
+ // We want to do all the work her asynchronously off a Gecko
+ // runnable, because of situations like the one described in
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1167575#c35 : we
+ // get here off processing of an OS event and will also process
+ // one more Gecko runnable before we break out of the event loop
+ // spin whoever posted the prompt is doing. If we do all our
+ // work sync, we will exit modal state _before_ processing that
+ // runnable, and if exiting moral state posts a runnable we will
+ // incorrectly process that runnable before leaving our event
+ // loop spin.
+ Services.tm.mainThread.dispatch(() => {
+ this.Dialog["onButton" + buttonNum]();
+ this.shutdownPrompt();
+ },
+ Ci.nsIThread.DISPATCH_NORMAL);
+ ]]>
+ </body>
+ </method>
+
+ <method name="onKeyAction">
+ <parameter name="action"/>
+ <parameter name="event"/>
+ <body>
+ <![CDATA[
+ if (event.defaultPrevented)
+ return;
+
+ event.stopPropagation();
+ if (action == "default") {
+ let bnum = this.args.defaultButtonNum || 0;
+ this.onButtonClick(bnum);
+ } else { // action == "cancel"
+ this.onButtonClick(1); // Cancel button
+ }
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <!-- Based on dialog.xml handlers -->
+ <handler event="keypress" keycode="VK_RETURN"
+ group="system" action="this.onKeyAction('default', event);"/>
+ <handler event="keypress" keycode="VK_ESCAPE"
+ group="system" action="this.onKeyAction('cancel', event);"/>
+ <handler event="focus" phase="capturing">
+ let bnum = this.args.defaultButtonNum || 0;
+ let defaultButton = this.ui["button" + bnum];
+
+ // The default button is only marked as such when no other button has focus.
+ // XUL buttons will react to pressing enter as a command, so you can't trigger
+ // the default without tabbing to it or something that isn't a button.
+ let focusedDefault = (event.originalTarget == defaultButton);
+ let someButtonFocused = event.originalTarget instanceof Ci.nsIDOMXULButtonElement;
+ defaultButton.setAttribute("default", focusedDefault || !someButtonFocused);
+ </handler>
+ <handler event="blur">
+ // If focus shifted to somewhere else in the browser, don't make
+ // the default button look active.
+ let bnum = this.args.defaultButtonNum || 0;
+ let button = this.ui["button" + bnum];
+ button.setAttribute("default", false);
+ </handler>
+ </handlers>
+
+ </binding>
+</bindings>
diff --git a/components/prompts/jar.mn b/components/prompts/jar.mn
new file mode 100644
index 000000000..1b5da3f80
--- /dev/null
+++ b/components/prompts/jar.mn
@@ -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/.
+
+toolkit.jar:
+ content/global/commonDialog.js (content/commonDialog.js)
+ content/global/commonDialog.xul (content/commonDialog.xul)
+ content/global/commonDialog.css (content/commonDialog.css)
+ content/global/selectDialog.js (content/selectDialog.js)
+ content/global/selectDialog.xul (content/selectDialog.xul)
+ content/global/tabprompts.css (content/tabprompts.css)
+* content/global/tabprompts.xml (content/tabprompts.xml)
diff --git a/components/prompts/moz.build b/components/prompts/moz.build
new file mode 100644
index 000000000..bce24006e
--- /dev/null
+++ b/components/prompts/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+DIRS += ['src']
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/prompts/src/CommonDialog.jsm b/components/prompts/src/CommonDialog.jsm
new file mode 100644
index 000000000..c4200feb3
--- /dev/null
+++ b/components/prompts/src/CommonDialog.jsm
@@ -0,0 +1,308 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["CommonDialog"];
+
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "EnableDelayHelper",
+ "resource://gre/modules/SharedPromptUtils.jsm");
+
+
+this.CommonDialog = function CommonDialog(args, ui) {
+ this.args = args;
+ this.ui = ui;
+}
+
+CommonDialog.prototype = {
+ args : null,
+ ui : null,
+
+ hasInputField : true,
+ numButtons : undefined,
+ iconClass : undefined,
+ soundID : undefined,
+ focusTimer : null,
+
+ onLoad : function(xulDialog) {
+ switch (this.args.promptType) {
+ case "alert":
+ case "alertCheck":
+ this.hasInputField = false;
+ this.numButtons = 1;
+ this.iconClass = ["alert-icon"];
+ this.soundID = Ci.nsISound.EVENT_ALERT_DIALOG_OPEN;
+ break;
+ case "confirmCheck":
+ case "confirm":
+ this.hasInputField = false;
+ this.numButtons = 2;
+ this.iconClass = ["question-icon"];
+ this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
+ break;
+ case "confirmEx":
+ var numButtons = 0;
+ if (this.args.button0Label)
+ numButtons++;
+ if (this.args.button1Label)
+ numButtons++;
+ if (this.args.button2Label)
+ numButtons++;
+ if (this.args.button3Label)
+ numButtons++;
+ if (numButtons == 0)
+ throw "A dialog with no buttons? Can not haz.";
+ this.numButtons = numButtons;
+ this.hasInputField = false;
+ this.iconClass = ["question-icon"];
+ this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
+ break;
+ case "prompt":
+ this.numButtons = 2;
+ this.iconClass = ["question-icon"];
+ this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
+ this.initTextbox("login", this.args.value);
+ // Clear the label, since this isn't really a username prompt.
+ this.ui.loginLabel.setAttribute("value", "");
+ break;
+ case "promptUserAndPass":
+ this.numButtons = 2;
+ this.iconClass = ["authentication-icon", "question-icon"];
+ this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
+ this.initTextbox("login", this.args.user);
+ this.initTextbox("password1", this.args.pass);
+ break;
+ case "promptPassword":
+ this.numButtons = 2;
+ this.iconClass = ["authentication-icon", "question-icon"];
+ this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
+ this.initTextbox("password1", this.args.pass);
+ // Clear the label, since the message presumably indicates its purpose.
+ this.ui.password1Label.setAttribute("value", "");
+ break;
+ default:
+ Cu.reportError("commonDialog opened for unknown type: " + this.args.promptType);
+ throw "unknown dialog type";
+ }
+
+ // set the document title
+ let title = this.args.title;
+ // OS X doesn't have a title on modal dialogs, this is hidden on other platforms.
+ let infoTitle = this.ui.infoTitle;
+ infoTitle.appendChild(infoTitle.ownerDocument.createTextNode(title));
+ if (xulDialog)
+ xulDialog.ownerDocument.title = title;
+
+ // Set button labels and visibility
+ //
+ // This assumes that button0 defaults to a visible "ok" button, and
+ // button1 defaults to a visible "cancel" button. The other 2 buttons
+ // have no default labels (and are hidden).
+ switch (this.numButtons) {
+ case 4:
+ this.setLabelForNode(this.ui.button3, this.args.button3Label);
+ this.ui.button3.hidden = false;
+ // fall through
+ case 3:
+ this.setLabelForNode(this.ui.button2, this.args.button2Label);
+ this.ui.button2.hidden = false;
+ // fall through
+ case 2:
+ // Defaults to a visible "cancel" button
+ if (this.args.button1Label)
+ this.setLabelForNode(this.ui.button1, this.args.button1Label);
+ break;
+
+ case 1:
+ this.ui.button1.hidden = true;
+ break;
+ }
+ // Defaults to a visible "ok" button
+ if (this.args.button0Label)
+ this.setLabelForNode(this.ui.button0, this.args.button0Label);
+
+ // display the main text
+ let croppedMessage = "";
+ if (this.args.text) {
+ // Bug 317334 - crop string length as a workaround.
+ croppedMessage = this.args.text.substr(0, 10000);
+ }
+ let infoBody = this.ui.infoBody;
+ infoBody.appendChild(infoBody.ownerDocument.createTextNode(croppedMessage));
+
+ let label = this.args.checkLabel;
+ if (label) {
+ // Only show the checkbox if label has a value.
+ this.ui.checkboxContainer.hidden = false;
+ this.setLabelForNode(this.ui.checkbox, label);
+ this.ui.checkbox.checked = this.args.checked;
+ }
+
+ // set the icon
+ let icon = this.ui.infoIcon;
+ if (icon)
+ this.iconClass.forEach((el, idx, arr) => icon.classList.add(el));
+
+ // set default result to cancelled
+ this.args.ok = false;
+ this.args.buttonNumClicked = 1;
+
+
+ // Set the default button
+ let b = (this.args.defaultButtonNum || 0);
+ let button = this.ui["button" + b];
+
+ if (xulDialog)
+ xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b];
+ else
+ button.setAttribute("default", "true");
+
+ // Set default focus / selection.
+ this.setDefaultFocus(true);
+
+ if (this.args.enableDelay) {
+ this.delayHelper = new EnableDelayHelper({
+ disableDialog: () => this.setButtonsEnabledState(false),
+ enableDialog: () => this.setButtonsEnabledState(true),
+ focusTarget: this.ui.focusTarget
+ });
+ }
+
+ // Play a sound (unless we're tab-modal -- don't want those to feel like OS prompts).
+ try {
+ if (xulDialog && this.soundID) {
+ Cc["@mozilla.org/sound;1"].
+ createInstance(Ci.nsISound).
+ playEventSound(this.soundID);
+ }
+ } catch (e) {
+ Cu.reportError("Couldn't play common dialog event sound: " + e);
+ }
+
+ let topic = "common-dialog-loaded";
+ if (!xulDialog)
+ topic = "tabmodal-dialog-loaded";
+ Services.obs.notifyObservers(this.ui.prompt, topic, null);
+ },
+
+ setLabelForNode: function(aNode, aLabel) {
+ // This is for labels which may contain embedded access keys.
+ // If we end in (&X) where X represents the access key, optionally preceded
+ // by spaces and/or followed by the ':' character, store the access key and
+ // remove the access key placeholder + leading spaces from the label.
+ // Otherwise a character preceded by one but not two &s is the access key.
+ // Store it and remove the &.
+
+ // Note that if you change the following code, see the comment of
+ // nsTextBoxFrame::UpdateAccessTitle.
+ var accessKey = null;
+ if (/ *\(\&([^&])\)(:?)$/.test(aLabel)) {
+ aLabel = RegExp.leftContext + RegExp.$2;
+ accessKey = RegExp.$1;
+ } else if (/^([^&]*)\&(([^&]).*$)/.test(aLabel)) {
+ aLabel = RegExp.$1 + RegExp.$2;
+ accessKey = RegExp.$3;
+ }
+
+ // && is the magic sequence to embed an & in your label.
+ aLabel = aLabel.replace(/\&\&/g, "&");
+ aNode.label = aLabel;
+
+ // XXXjag bug 325251
+ // Need to set this after aNode.setAttribute("value", aLabel);
+ if (accessKey)
+ aNode.accessKey = accessKey;
+ },
+
+
+ initTextbox : function (aName, aValue) {
+ this.ui[aName + "Container"].hidden = false;
+ this.ui[aName + "Textbox"].setAttribute("value",
+ aValue !== null ? aValue : "");
+ },
+
+ setButtonsEnabledState : function(enabled) {
+ this.ui.button0.disabled = !enabled;
+ // button1 (cancel) remains enabled.
+ this.ui.button2.disabled = !enabled;
+ this.ui.button3.disabled = !enabled;
+ },
+
+ setDefaultFocus : function(isInitialLoad) {
+ let b = (this.args.defaultButtonNum || 0);
+ let button = this.ui["button" + b];
+
+ if (!this.hasInputField) {
+ let isOSX = ("nsILocalFileMac" in Components.interfaces);
+ if (isOSX)
+ this.ui.infoBody.focus();
+ else
+ button.focus();
+ } else if (this.args.promptType == "promptPassword") {
+ // When the prompt is initialized, focus and select the textbox
+ // contents. Afterwards, only focus the textbox.
+ if (isInitialLoad)
+ this.ui.password1Textbox.select();
+ else
+ this.ui.password1Textbox.focus();
+ } else if (isInitialLoad) {
+ this.ui.loginTextbox.select();
+ } else {
+ this.ui.loginTextbox.focus();
+ }
+ },
+
+ onCheckbox : function() {
+ this.args.checked = this.ui.checkbox.checked;
+ },
+
+ onButton0 : function() {
+ this.args.promptActive = false;
+ this.args.ok = true;
+ this.args.buttonNumClicked = 0;
+
+ let username = this.ui.loginTextbox.value;
+ let password = this.ui.password1Textbox.value;
+
+ // Return textfield values
+ switch (this.args.promptType) {
+ case "prompt":
+ this.args.value = username;
+ break;
+ case "promptUserAndPass":
+ this.args.user = username;
+ this.args.pass = password;
+ break;
+ case "promptPassword":
+ this.args.pass = password;
+ break;
+ }
+ },
+
+ onButton1 : function() {
+ this.args.promptActive = false;
+ this.args.buttonNumClicked = 1;
+ },
+
+ onButton2 : function() {
+ this.args.promptActive = false;
+ this.args.buttonNumClicked = 2;
+ },
+
+ onButton3 : function() {
+ this.args.promptActive = false;
+ this.args.buttonNumClicked = 3;
+ },
+
+ abortPrompt : function() {
+ this.args.promptActive = false;
+ this.args.promptAborted = true;
+ },
+
+};
diff --git a/components/prompts/src/SharedPromptUtils.jsm b/components/prompts/src/SharedPromptUtils.jsm
new file mode 100644
index 000000000..b27096ac2
--- /dev/null
+++ b/components/prompts/src/SharedPromptUtils.jsm
@@ -0,0 +1,157 @@
+this.EXPORTED_SYMBOLS = [ "PromptUtils", "EnableDelayHelper" ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.PromptUtils = {
+ // Fire a dialog open/close event. Used by tabbrowser to focus the
+ // tab which is triggering a prompt.
+ // For remote dialogs, we pass in a different DOM window and a separate
+ // target. If the caller doesn't pass in the target, then we'll simply use
+ // the passed-in DOM window.
+ // The detail may contain information about the principal on which the
+ // prompt is triggered, as well as whether or not this is a tabprompt
+ // (ie tabmodal alert/prompt/confirm and friends)
+ fireDialogEvent : function (domWin, eventName, maybeTarget, detail) {
+ let target = maybeTarget || domWin;
+ let eventOptions = {cancelable: true, bubbles: true};
+ if (detail) {
+ eventOptions.detail = detail;
+ }
+ let event = new domWin.CustomEvent(eventName, eventOptions);
+ let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ winUtils.dispatchEventToChromeOnly(target, event);
+ },
+
+ objectToPropBag : function (obj) {
+ let bag = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag2);
+ bag.QueryInterface(Ci.nsIWritablePropertyBag);
+
+ for (let propName in obj)
+ bag.setProperty(propName, obj[propName]);
+
+ return bag;
+ },
+
+ propBagToObject : function (propBag, obj) {
+ // Here we iterate over the object's original properties, not the bag
+ // (ie, the prompt can't return more/different properties than were
+ // passed in). This just helps ensure that the caller provides default
+ // values, lest the prompt forget to set them.
+ for (let propName in obj)
+ obj[propName] = propBag.getProperty(propName);
+ },
+};
+
+/**
+ * This helper handles the enabling/disabling of dialogs that might
+ * be subject to fast-clicking attacks. It handles the initial delayed
+ * enabling of the dialog, as well as disabling it on blur and reapplying
+ * the delay when the dialog regains focus.
+ *
+ * @param enableDialog A custom function to be called when the dialog
+ * is to be enabled.
+ * @param diableDialog A custom function to be called when the dialog
+ * is to be disabled.
+ * @param focusTarget The window used to watch focus/blur events.
+ */
+this.EnableDelayHelper = function({enableDialog, disableDialog, focusTarget}) {
+ this.enableDialog = makeSafe(enableDialog);
+ this.disableDialog = makeSafe(disableDialog);
+ this.focusTarget = focusTarget;
+
+ this.disableDialog();
+
+ this.focusTarget.addEventListener("blur", this, false);
+ this.focusTarget.addEventListener("focus", this, false);
+ this.focusTarget.document.addEventListener("unload", this, false);
+
+ this.startOnFocusDelay();
+};
+
+this.EnableDelayHelper.prototype = {
+ get delayTime() {
+ return Services.prefs.getIntPref("security.dialog_enable_delay");
+ },
+
+ handleEvent : function(event) {
+ if (event.target != this.focusTarget &&
+ event.target != this.focusTarget.document)
+ return;
+
+ switch (event.type) {
+ case "blur":
+ this.onBlur();
+ break;
+
+ case "focus":
+ this.onFocus();
+ break;
+
+ case "unload":
+ this.onUnload();
+ break;
+ }
+ },
+
+ onBlur : function () {
+ this.disableDialog();
+ // If we blur while waiting to enable the buttons, just cancel the
+ // timer to ensure the delay doesn't fire while not focused.
+ if (this._focusTimer) {
+ this._focusTimer.cancel();
+ this._focusTimer = null;
+ }
+ },
+
+ onFocus : function () {
+ this.startOnFocusDelay();
+ },
+
+ onUnload: function() {
+ this.focusTarget.removeEventListener("blur", this, false);
+ this.focusTarget.removeEventListener("focus", this, false);
+ this.focusTarget.document.removeEventListener("unload", this, false);
+
+ if (this._focusTimer) {
+ this._focusTimer.cancel();
+ this._focusTimer = null;
+ }
+
+ this.focusTarget = this.enableDialog = this.disableDialog = null;
+ },
+
+ startOnFocusDelay : function() {
+ if (this._focusTimer)
+ return;
+
+ this._focusTimer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+ this._focusTimer.initWithCallback(
+ () => { this.onFocusTimeout(); },
+ this.delayTime,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+ },
+
+ onFocusTimeout : function() {
+ this._focusTimer = null;
+ this.enableDialog();
+ },
+};
+
+function makeSafe(fn) {
+ return function () {
+ // The dialog could be gone by now (if the user closed it),
+ // which makes it likely that the given fn might throw.
+ try {
+ fn();
+ } catch (e) { }
+ };
+}
diff --git a/components/prompts/src/moz.build b/components/prompts/src/moz.build
new file mode 100644
index 000000000..5311f1ed3
--- /dev/null
+++ b/components/prompts/src/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'nsPrompter.js',
+ 'nsPrompter.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'CommonDialog.jsm',
+ 'SharedPromptUtils.jsm',
+]
+
diff --git a/components/prompts/src/nsPrompter.js b/components/prompts/src/nsPrompter.js
new file mode 100644
index 000000000..0503b5925
--- /dev/null
+++ b/components/prompts/src/nsPrompter.js
@@ -0,0 +1,964 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 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/SharedPromptUtils.jsm");
+
+function Prompter() {
+ // Note that EmbedPrompter clones this implementation.
+}
+
+Prompter.prototype = {
+ classID : Components.ID("{1c978d25-b37f-43a8-a2d6-0c7a239ead87}"),
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
+
+
+ /* ---------- private members ---------- */
+
+ pickPrompter : function (domWin) {
+ return new ModalPrompter(domWin);
+ },
+
+
+ /* ---------- nsIPromptFactory ---------- */
+
+
+ getPrompt : function (domWin, iid) {
+ // This is still kind of dumb; the C++ code delegated to login manager
+ // here, which in turn calls back into us via nsIPromptService2.
+ if (iid.equals(Ci.nsIAuthPrompt2) || iid.equals(Ci.nsIAuthPrompt)) {
+ try {
+ let pwmgr = Cc["@mozilla.org/passwordmanager/authpromptfactory;1"].
+ getService(Ci.nsIPromptFactory);
+ return pwmgr.getPrompt(domWin, iid);
+ } catch (e) {
+ Cu.reportError("nsPrompter: Delegation to password manager failed: " + e);
+ }
+ }
+
+ let p = new ModalPrompter(domWin);
+ p.QueryInterface(iid);
+ return p;
+ },
+
+
+ /* ---------- nsIPromptService ---------- */
+
+
+ alert : function (domWin, title, text) {
+ let p = this.pickPrompter(domWin);
+ p.alert(title, text);
+ },
+
+ alertCheck : function (domWin, title, text, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ p.alertCheck(title, text, checkLabel, checkValue);
+ },
+
+ confirm : function (domWin, title, text) {
+ let p = this.pickPrompter(domWin);
+ return p.confirm(title, text);
+ },
+
+ confirmCheck : function (domWin, title, text, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.confirmCheck(title, text, checkLabel, checkValue);
+ },
+
+ confirmEx : function (domWin, title, text, flags, button0, button1, button2, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.confirmEx(title, text, flags, button0, button1, button2, checkLabel, checkValue);
+ },
+
+ prompt : function (domWin, title, text, value, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.nsIPrompt_prompt(title, text, value, checkLabel, checkValue);
+ },
+
+ promptUsernameAndPassword : function (domWin, title, text, user, pass, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.nsIPrompt_promptUsernameAndPassword(title, text, user, pass, checkLabel, checkValue);
+ },
+
+ promptPassword : function (domWin, title, text, pass, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.nsIPrompt_promptPassword(title, text, pass, checkLabel, checkValue);
+ },
+
+ select : function (domWin, title, text, count, list, selected) {
+ let p = this.pickPrompter(domWin);
+ return p.select(title, text, count, list, selected);
+ },
+
+
+ /* ---------- nsIPromptService2 ---------- */
+
+
+ promptAuth : function (domWin, channel, level, authInfo, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.promptAuth(channel, level, authInfo, checkLabel, checkValue);
+ },
+
+ asyncPromptAuth : function (domWin, channel, callback, context, level, authInfo, checkLabel, checkValue) {
+ let p = this.pickPrompter(domWin);
+ return p.asyncPromptAuth(channel, callback, context, level, authInfo, checkLabel, checkValue);
+ },
+
+};
+
+
+// Common utils not specific to a particular prompter style.
+var PromptUtilsTemp = {
+ __proto__ : PromptUtils,
+
+ getLocalizedString : function (key, formatArgs) {
+ if (formatArgs)
+ return this.strBundle.formatStringFromName(key, formatArgs, formatArgs.length);
+ return this.strBundle.GetStringFromName(key);
+ },
+
+ confirmExHelper : function (flags, button0, button1, button2) {
+ const BUTTON_DEFAULT_MASK = 0x03000000;
+ let defaultButtonNum = (flags & BUTTON_DEFAULT_MASK) >> 24;
+ let isDelayEnabled = (flags & Ci.nsIPrompt.BUTTON_DELAY_ENABLE);
+
+ // Flags can be used to select a specific pre-defined button label or
+ // a caller-supplied string (button0/button1/button2). If no flags are
+ // set for a button, then the button won't be shown.
+ let argText = [button0, button1, button2];
+ let buttonLabels = [null, null, null];
+ for (let i = 0; i < 3; i++) {
+ let buttonLabel;
+ switch (flags & 0xff) {
+ case Ci.nsIPrompt.BUTTON_TITLE_OK:
+ buttonLabel = PromptUtils.getLocalizedString("OK");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_CANCEL:
+ buttonLabel = PromptUtils.getLocalizedString("Cancel");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_YES:
+ buttonLabel = PromptUtils.getLocalizedString("Yes");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_NO:
+ buttonLabel = PromptUtils.getLocalizedString("No");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_SAVE:
+ buttonLabel = PromptUtils.getLocalizedString("Save");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE:
+ buttonLabel = PromptUtils.getLocalizedString("DontSave");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_REVERT:
+ buttonLabel = PromptUtils.getLocalizedString("Revert");
+ break;
+ case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING:
+ buttonLabel = argText[i];
+ break;
+ }
+ if (buttonLabel)
+ buttonLabels[i] = buttonLabel;
+ flags >>= 8;
+ }
+
+ return [buttonLabels[0], buttonLabels[1], buttonLabels[2], defaultButtonNum, isDelayEnabled];
+ },
+
+ getAuthInfo : function (authInfo) {
+ let username, password;
+
+ let flags = authInfo.flags;
+ if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && authInfo.domain)
+ username = authInfo.domain + "\\" + authInfo.username;
+ else
+ username = authInfo.username;
+
+ password = authInfo.password;
+
+ return [username, password];
+ },
+
+ setAuthInfo : function (authInfo, username, password) {
+ let flags = authInfo.flags;
+ if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
+ // Domain is separated from username by a backslash
+ let idx = username.indexOf("\\");
+ if (idx == -1) {
+ authInfo.username = username;
+ } else {
+ authInfo.domain = username.substring(0, idx);
+ authInfo.username = username.substring(idx+1);
+ }
+ } else {
+ authInfo.username = username;
+ }
+ authInfo.password = password;
+ },
+
+ /**
+ * Strip out things like userPass and path for display.
+ */
+ getFormattedHostname : function (uri) {
+ return uri.scheme + "://" + uri.hostPort;
+ },
+
+ // Copied from login manager
+ getAuthTarget : function (aChannel, aAuthInfo) {
+ let hostname, realm;
+
+ // If our proxy is demanding authentication, don't use the
+ // channel's actual destination.
+ if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
+ if (!(aChannel instanceof Ci.nsIProxiedChannel))
+ throw "proxy auth needs nsIProxiedChannel";
+
+ let info = aChannel.proxyInfo;
+ if (!info)
+ throw "proxy auth needs nsIProxyInfo";
+
+ // Proxies don't have a scheme, but we'll use "moz-proxy://"
+ // so that it's more obvious what the login is for.
+ let idnService = Cc["@mozilla.org/network/idn-service;1"].
+ getService(Ci.nsIIDNService);
+ hostname = "moz-proxy://" +
+ idnService.convertUTF8toACE(info.host) +
+ ":" + info.port;
+ realm = aAuthInfo.realm;
+ if (!realm)
+ realm = hostname;
+
+ return [hostname, realm];
+ }
+
+ hostname = this.getFormattedHostname(aChannel.URI);
+
+ // If a HTTP WWW-Authenticate header specified a realm, that value
+ // will be available here. If it wasn't set or wasn't HTTP, we'll use
+ // the formatted hostname instead.
+ realm = aAuthInfo.realm;
+ if (!realm)
+ realm = hostname;
+
+ return [hostname, realm];
+ },
+
+
+ makeAuthMessage : function (channel, authInfo) {
+ let isProxy = (authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY);
+ let isPassOnly = (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD);
+ let isCrossOrig = (authInfo.flags &
+ Ci.nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE);
+
+ let username = authInfo.username;
+ let [displayHost, realm] = this.getAuthTarget(channel, authInfo);
+
+ // Suppress "the site says: $realm" when we synthesized a missing realm.
+ if (!authInfo.realm && !isProxy)
+ realm = "";
+
+ // Trim obnoxiously long realms.
+ if (realm.length > 150) {
+ realm = realm.substring(0, 150);
+ // Append "..." (or localized equivalent).
+ realm += this.ellipsis;
+ }
+
+ let text;
+ if (isProxy) {
+ text = PromptUtils.getLocalizedString("EnterLoginForProxy3", [realm, displayHost]);
+ } else if (isPassOnly) {
+ text = PromptUtils.getLocalizedString("EnterPasswordFor", [username, displayHost]);
+ } else if (isCrossOrig) {
+ text = PromptUtils.getLocalizedString("EnterUserPasswordForCrossOrigin2", [displayHost]);
+ } else if (!realm) {
+ text = PromptUtils.getLocalizedString("EnterUserPasswordFor2", [displayHost]);
+ } else {
+ text = PromptUtils.getLocalizedString("EnterLoginForRealm3", [realm, displayHost]);
+ }
+
+ return text;
+ },
+
+ getTabModalPrompt : function (domWin) {
+ var promptBox = null;
+
+ try {
+ // Get the topmost window, in case we're in a frame.
+ var promptWin = domWin.top;
+
+ // Get the chrome window for the content window we're using.
+ // (Unwrap because we need a non-IDL property below.)
+ var chromeWin = promptWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler.ownerDocument
+ .defaultView.wrappedJSObject;
+
+ if (chromeWin.getTabModalPromptBox)
+ promptBox = chromeWin.getTabModalPromptBox(promptWin);
+ } catch (e) {
+ // If any errors happen, just assume no tabmodal prompter.
+ }
+
+ return promptBox;
+ },
+};
+
+PromptUtils = PromptUtilsTemp;
+
+XPCOMUtils.defineLazyGetter(PromptUtils, "strBundle", function () {
+ let bunService = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService);
+ let bundle = bunService.createBundle("chrome://global/locale/commonDialogs.properties");
+ if (!bundle)
+ throw "String bundle for Prompter not present!";
+ return bundle;
+});
+
+XPCOMUtils.defineLazyGetter(PromptUtils, "ellipsis", function () {
+ let ellipsis = "\u2026";
+ try {
+ ellipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
+ } catch (e) { }
+ return ellipsis;
+});
+
+
+
+function openModalWindow(domWin, uri, args) {
+ // There's an implied contract that says modal prompts should still work
+ // when no "parent" window is passed for the dialog (eg, the "Master
+ // Password" dialog does this). These prompts must be shown even if there
+ // are *no* visible windows at all.
+ // There's also a requirement for prompts to be blocked if a window is
+ // passed and that window is hidden (eg, auth prompts are supressed if the
+ // passed window is the hidden window).
+ // See bug 875157 comment 30 for more...
+ if (domWin) {
+ // a domWin was passed, so we can apply the check for it being hidden.
+ let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ if (winUtils && !winUtils.isParentWindowMainWidgetVisible) {
+ throw Components.Exception("Cannot call openModalWindow on a hidden window",
+ Cr.NS_ERROR_NOT_AVAILABLE);
+ }
+ } else {
+ // We try and find a window to use as the parent, but don't consider
+ // if that is visible before showing the prompt.
+ domWin = Services.ww.activeWindow;
+ // domWin may still be null here if there are _no_ windows open.
+ }
+ // Note that we don't need to fire DOMWillOpenModalDialog and
+ // DOMModalDialogClosed events here, wwatcher's OpenWindowInternal
+ // will do that. Similarly for enterModalState / leaveModalState.
+
+ Services.ww.openWindow(domWin, uri, "_blank", "centerscreen,chrome,modal,titlebar", args);
+}
+
+function openTabPrompt(domWin, tabPrompt, args) {
+ let docShell = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+ let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload;
+ let eventDetail = Cu.cloneInto({tabPrompt: true, inPermitUnload}, domWin);
+ let allowFocusSwitch = true;
+ try {
+ allowFocusSwitch = Services.prefs.getBoolPref("prompts.tab_modal.focusSwitch");
+ } catch(e) {}
+
+ if (allowFocusSwitch)
+ PromptUtils.fireDialogEvent(domWin, "DOMWillOpenModalDialog", null, eventDetail);
+
+ let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ winUtils.enterModalState();
+
+ let frameMM = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+ frameMM.QueryInterface(Ci.nsIDOMEventTarget);
+
+ // We provide a callback so the prompt can close itself. We don't want to
+ // wait for this event loop to return... Otherwise the presence of other
+ // prompts on the call stack would in this dialog appearing unresponsive
+ // until the other prompts had been closed.
+ let callbackInvoked = false;
+ let newPrompt;
+ function onPromptClose(forceCleanup) {
+ if (!newPrompt && !forceCleanup)
+ return;
+ callbackInvoked = true;
+ if (newPrompt)
+ tabPrompt.removePrompt(newPrompt);
+
+ frameMM.removeEventListener("pagehide", pagehide, true);
+
+ winUtils.leaveModalState();
+ if (allowFocusSwitch)
+ PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");
+ }
+
+ frameMM.addEventListener("pagehide", pagehide, true);
+ function pagehide(e) {
+ // Check whether the event relates to our window or its ancestors
+ let window = domWin;
+ let eventWindow = e.target.defaultView;
+ while (window != eventWindow && window.parent != window) {
+ window = window.parent;
+ }
+ if (window != eventWindow) {
+ return;
+ }
+ frameMM.removeEventListener("pagehide", pagehide, true);
+
+ if (newPrompt) {
+ newPrompt.abortPrompt();
+ }
+ }
+
+ try {
+ let topPrincipal = domWin.top.document.nodePrincipal;
+ let promptPrincipal = domWin.document.nodePrincipal;
+ args.showAlertOrigin = topPrincipal.equals(promptPrincipal);
+ args.promptActive = true;
+
+ newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
+
+ // TODO since we don't actually open a window, need to check if
+ // there's other stuff in nsWindowWatcher::OpenWindowInternal
+ // that we might need to do here as well.
+
+ let thread = Services.tm.currentThread;
+ while (args.promptActive)
+ thread.processNextEvent(true);
+ delete args.promptActive;
+
+ if (args.promptAborted)
+ throw Components.Exception("prompt aborted by user", Cr.NS_ERROR_NOT_AVAILABLE);
+ } finally {
+ // If the prompt unexpectedly failed to invoke the callback, do so here.
+ if (!callbackInvoked)
+ onPromptClose(true);
+ }
+}
+
+function openRemotePrompt(domWin, args, tabPrompt) {
+ let docShell = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+ let messageManager = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsITabChild)
+ .messageManager;
+
+ let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload;
+ let eventDetail = Cu.cloneInto({tabPrompt, inPermitUnload}, domWin);
+ PromptUtils.fireDialogEvent(domWin, "DOMWillOpenModalDialog", null, eventDetail);
+
+ let winUtils = domWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ winUtils.enterModalState();
+ let closed = false;
+
+ let frameMM = docShell.getInterface(Ci.nsIContentFrameMessageManager);
+ frameMM.QueryInterface(Ci.nsIDOMEventTarget);
+
+ // It should be hard or impossible to cause a window to create multiple
+ // prompts, but just in case, give our prompt an ID.
+ let id = "id" + Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+
+ messageManager.addMessageListener("Prompt:Close", function listener(message) {
+ if (message.data._remoteId !== id) {
+ return;
+ }
+
+ messageManager.removeMessageListener("Prompt:Close", listener);
+ frameMM.removeEventListener("pagehide", pagehide, true);
+
+ winUtils.leaveModalState();
+ PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");
+
+ // Copy the response from the closed prompt into our args, it will be
+ // read by our caller.
+ if (message.data) {
+ for (let key in message.data) {
+ args[key] = message.data[key];
+ }
+ }
+
+ // Exit our nested event loop when we unwind.
+ closed = true;
+ });
+
+ frameMM.addEventListener("pagehide", pagehide, true);
+ function pagehide(e) {
+ // Check whether the event relates to our window or its ancestors
+ let window = domWin;
+ let eventWindow = e.target.defaultView;
+ while (window != eventWindow && window.parent != window) {
+ window = window.parent;
+ }
+ if (window != eventWindow) {
+ return;
+ }
+ frameMM.removeEventListener("pagehide", pagehide, true);
+ messageManager.sendAsyncMessage("Prompt:ForceClose", { _remoteId: id });
+ }
+
+ let topPrincipal = domWin.top.document.nodePrincipal;
+ let promptPrincipal = domWin.document.nodePrincipal;
+ args.promptPrincipal = promptPrincipal;
+ args.showAlertOrigin = topPrincipal.equals(promptPrincipal);
+ args.inPermitUnload = inPermitUnload;
+
+ args._remoteId = id;
+
+ messageManager.sendAsyncMessage("Prompt:Open", args, {});
+
+ let thread = Services.tm.currentThread;
+ while (!closed) {
+ thread.processNextEvent(true);
+ }
+}
+
+function ModalPrompter(domWin) {
+ this.domWin = domWin;
+}
+ModalPrompter.prototype = {
+ domWin : null,
+ /*
+ * Default to not using a tab-modal prompt, unless the caller opts in by
+ * QIing to nsIWritablePropertyBag and setting the value of this property
+ * to true.
+ */
+ allowTabModal : false,
+
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIPrompt, Ci.nsIAuthPrompt,
+ Ci.nsIAuthPrompt2,
+ Ci.nsIWritablePropertyBag2]),
+
+
+ /* ---------- internal methods ---------- */
+
+
+ openPrompt : function (args) {
+ // Check pref, if false/missing do not ever allow tab-modal prompts.
+ const prefName = "prompts.tab_modal.enabled";
+ let prefValue = false;
+ if (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_BOOL)
+ prefValue = Services.prefs.getBoolPref(prefName);
+
+ let allowTabModal = this.allowTabModal && prefValue;
+
+ if (allowTabModal && this.domWin) {
+ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+ openRemotePrompt(this.domWin, args, true);
+ return;
+ }
+
+ let tabPrompt = PromptUtils.getTabModalPrompt(this.domWin);
+ if (tabPrompt) {
+ openTabPrompt(this.domWin, tabPrompt, args);
+ return;
+ }
+ }
+
+ // If we can't do a tab modal prompt, fallback to using a window-modal dialog.
+ const COMMON_DIALOG = "chrome://global/content/commonDialog.xul";
+ const SELECT_DIALOG = "chrome://global/content/selectDialog.xul";
+
+ let uri = (args.promptType == "select") ? SELECT_DIALOG : COMMON_DIALOG;
+
+ if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
+ args.uri = uri;
+ openRemotePrompt(this.domWin, args);
+ return;
+ }
+
+ let propBag = PromptUtils.objectToPropBag(args);
+ openModalWindow(this.domWin, uri, propBag);
+ PromptUtils.propBagToObject(propBag, args);
+ },
+
+
+
+ /*
+ * ---------- interface disambiguation ----------
+ *
+ * nsIPrompt and nsIAuthPrompt share 3 method names with slightly
+ * different arguments. All but prompt() have the same number of
+ * arguments, so look at the arg types to figure out how we're being
+ * called. :-(
+ */
+ prompt : function() {
+ // also, the nsIPrompt flavor has 5 args instead of 6.
+ if (typeof arguments[2] == "object")
+ return this.nsIPrompt_prompt.apply(this, arguments);
+ return this.nsIAuthPrompt_prompt.apply(this, arguments);
+ },
+
+ promptUsernameAndPassword : function() {
+ // Both have 6 args, so use types.
+ if (typeof arguments[2] == "object")
+ return this.nsIPrompt_promptUsernameAndPassword.apply(this, arguments);
+ return this.nsIAuthPrompt_promptUsernameAndPassword.apply(this, arguments);
+ },
+
+ promptPassword : function() {
+ // Both have 5 args, so use types.
+ if (typeof arguments[2] == "object")
+ return this.nsIPrompt_promptPassword.apply(this, arguments);
+ return this.nsIAuthPrompt_promptPassword.apply(this, arguments);
+ },
+
+
+ /* ---------- nsIPrompt ---------- */
+
+
+ alert : function (title, text) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("Alert");
+
+ let args = {
+ promptType: "alert",
+ title: title,
+ text: text,
+ };
+
+ this.openPrompt(args);
+ },
+
+ alertCheck : function (title, text, checkLabel, checkValue) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("Alert");
+
+ let args = {
+ promptType: "alertCheck",
+ title: title,
+ text: text,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ };
+
+ this.openPrompt(args);
+
+ // Checkbox state always returned, even if cancel clicked.
+ checkValue.value = args.checked;
+ },
+
+ confirm : function (title, text) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("Confirm");
+
+ let args = {
+ promptType: "confirm",
+ title: title,
+ text: text,
+ ok: false,
+ };
+
+ this.openPrompt(args);
+
+ // Did user click Ok or Cancel?
+ return args.ok;
+ },
+
+ confirmCheck : function (title, text, checkLabel, checkValue) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("ConfirmCheck");
+
+ let args = {
+ promptType: "confirmCheck",
+ title: title,
+ text: text,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ ok: false,
+ };
+
+ this.openPrompt(args);
+
+ // Checkbox state always returned, even if cancel clicked.
+ checkValue.value = args.checked;
+
+ // Did user click Ok or Cancel?
+ return args.ok;
+ },
+
+ confirmEx : function (title, text, flags, button0, button1, button2,
+ checkLabel, checkValue) {
+
+ if (!title)
+ title = PromptUtils.getLocalizedString("Confirm");
+
+ let args = {
+ promptType: "confirmEx",
+ title: title,
+ text: text,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ ok: false,
+ buttonNumClicked: 1,
+ };
+
+ let [label0, label1, label2, defaultButtonNum, isDelayEnabled] =
+ PromptUtils.confirmExHelper(flags, button0, button1, button2);
+
+ args.defaultButtonNum = defaultButtonNum;
+ args.enableDelay = isDelayEnabled;
+
+ if (label0) {
+ args.button0Label = label0;
+ if (label1) {
+ args.button1Label = label1;
+ if (label2) {
+ args.button2Label = label2;
+ }
+ }
+ }
+
+ this.openPrompt(args);
+
+ // Checkbox state always returned, even if cancel clicked.
+ checkValue.value = args.checked;
+
+ // Get the number of the button the user clicked.
+ return args.buttonNumClicked;
+ },
+
+ nsIPrompt_prompt : function (title, text, value, checkLabel, checkValue) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("Prompt");
+
+ let args = {
+ promptType: "prompt",
+ title: title,
+ text: text,
+ value: value.value,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ ok: false,
+ };
+
+ this.openPrompt(args);
+
+ // Did user click Ok or Cancel?
+ let ok = args.ok;
+ if (ok) {
+ checkValue.value = args.checked;
+ value.value = args.value;
+ }
+
+ return ok;
+ },
+
+ nsIPrompt_promptUsernameAndPassword : function (title, text, user, pass, checkLabel, checkValue) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("PromptUsernameAndPassword2");
+
+ let args = {
+ promptType: "promptUserAndPass",
+ title: title,
+ text: text,
+ user: user.value,
+ pass: pass.value,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ ok: false,
+ };
+
+ this.openPrompt(args);
+
+ // Did user click Ok or Cancel?
+ let ok = args.ok;
+ if (ok) {
+ checkValue.value = args.checked;
+ user.value = args.user;
+ pass.value = args.pass;
+ }
+
+ return ok;
+ },
+
+ nsIPrompt_promptPassword : function (title, text, pass, checkLabel, checkValue) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("PromptPassword2");
+
+ let args = {
+ promptType: "promptPassword",
+ title: title,
+ text: text,
+ pass: pass.value,
+ checkLabel: checkLabel,
+ checked: checkValue.value,
+ ok: false,
+ }
+
+ this.openPrompt(args);
+
+ // Did user click Ok or Cancel?
+ let ok = args.ok;
+ if (ok) {
+ checkValue.value = args.checked;
+ pass.value = args.pass;
+ }
+
+ return ok;
+ },
+
+ select : function (title, text, count, list, selected) {
+ if (!title)
+ title = PromptUtils.getLocalizedString("Select");
+
+ let args = {
+ promptType: "select",
+ title: title,
+ text: text,
+ list: list,
+ selected: -1,
+ ok: false,
+ };
+
+ this.openPrompt(args);
+
+ // Did user click Ok or Cancel?
+ let ok = args.ok;
+ if (ok)
+ selected.value = args.selected;
+
+ return ok;
+ },
+
+
+ /* ---------- nsIAuthPrompt ---------- */
+
+
+ nsIAuthPrompt_prompt : function (title, text, passwordRealm, savePassword, defaultText, result) {
+ // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
+ if (defaultText)
+ result.value = defaultText;
+ return this.nsIPrompt_prompt(title, text, result, null, {});
+ },
+
+ nsIAuthPrompt_promptUsernameAndPassword : function (title, text, passwordRealm, savePassword, user, pass) {
+ // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
+ return this.nsIPrompt_promptUsernameAndPassword(title, text, user, pass, null, {});
+ },
+
+ nsIAuthPrompt_promptPassword : function (title, text, passwordRealm, savePassword, pass) {
+ // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
+ return this.nsIPrompt_promptPassword(title, text, pass, null, {});
+ },
+
+
+ /* ---------- nsIAuthPrompt2 ---------- */
+
+
+ promptAuth : function (channel, level, authInfo, checkLabel, checkValue) {
+ let message = PromptUtils.makeAuthMessage(channel, authInfo);
+
+ let [username, password] = PromptUtils.getAuthInfo(authInfo);
+
+ let userParam = { value: username };
+ let passParam = { value: password };
+
+ let ok;
+ if (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
+ ok = this.nsIPrompt_promptPassword(null, message, passParam, checkLabel, checkValue);
+ else
+ ok = this.nsIPrompt_promptUsernameAndPassword(null, message, userParam, passParam, checkLabel, checkValue);
+
+ if (ok)
+ PromptUtils.setAuthInfo(authInfo, userParam.value, passParam.value);
+ return ok;
+ },
+
+ asyncPromptAuth : function (channel, callback, context, level, authInfo, checkLabel, checkValue) {
+ // Nothing calls this directly; netwerk ends up going through
+ // nsIPromptService::GetPrompt, which delegates to login manager.
+ // Login manger handles the async bits itself, and only calls out
+ // promptAuth, never asyncPromptAuth.
+ //
+ // Bug 565582 will change this.
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /* ---------- nsIWritablePropertyBag2 ---------- */
+
+ // Only a partial implementation, for one specific use case...
+
+ setPropertyAsBool : function(name, value) {
+ if (name == "allowTabModal")
+ this.allowTabModal = value;
+ else
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ },
+};
+
+
+function AuthPromptAdapterFactory() {
+}
+AuthPromptAdapterFactory.prototype = {
+ classID : Components.ID("{6e134924-6c3a-4d86-81ac-69432dd971dc}"),
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPromptAdapterFactory]),
+
+ /* ---------- nsIAuthPromptAdapterFactory ---------- */
+
+ createAdapter : function (oldPrompter) {
+ return new AuthPromptAdapter(oldPrompter);
+ }
+};
+
+
+// Takes an nsIAuthPrompt implementation, wraps it with a nsIAuthPrompt2 shell.
+function AuthPromptAdapter(oldPrompter) {
+ this.oldPrompter = oldPrompter;
+}
+AuthPromptAdapter.prototype = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPrompt2]),
+ oldPrompter : null,
+
+ /* ---------- nsIAuthPrompt2 ---------- */
+
+ promptAuth : function (channel, level, authInfo, checkLabel, checkValue) {
+ let message = PromptUtils.makeAuthMessage(channel, authInfo);
+
+ let [username, password] = PromptUtils.getAuthInfo(authInfo);
+ let userParam = { value: username };
+ let passParam = { value: password };
+
+ let [host, realm] = PromptUtils.getAuthTarget(channel, authInfo);
+ let authTarget = host + " (" + realm + ")";
+
+ let ok;
+ if (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD)
+ ok = this.oldPrompter.promptPassword(null, message, authTarget, Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, passParam);
+ else
+ ok = this.oldPrompter.promptUsernameAndPassword(null, message, authTarget, Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, userParam, passParam);
+
+ if (ok)
+ PromptUtils.setAuthInfo(authInfo, userParam.value, passParam.value);
+ return ok;
+ },
+
+ asyncPromptAuth : function (channel, callback, context, level, authInfo, checkLabel, checkValue) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+
+// Wrapper using the old embedding contractID, since it's already common in
+// the addon ecosystem.
+function EmbedPrompter() {
+}
+EmbedPrompter.prototype = new Prompter();
+EmbedPrompter.prototype.classID = Components.ID("{7ad1b327-6dfa-46ec-9234-f2a620ea7e00}");
+
+var component = [Prompter, EmbedPrompter, AuthPromptAdapterFactory];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
diff --git a/components/prompts/src/nsPrompter.manifest b/components/prompts/src/nsPrompter.manifest
new file mode 100644
index 000000000..582f6bf59
--- /dev/null
+++ b/components/prompts/src/nsPrompter.manifest
@@ -0,0 +1,6 @@
+component {1c978d25-b37f-43a8-a2d6-0c7a239ead87} nsPrompter.js
+contract @mozilla.org/prompter;1 {1c978d25-b37f-43a8-a2d6-0c7a239ead87}
+component {6e134924-6c3a-4d86-81ac-69432dd971dc} nsPrompter.js
+contract @mozilla.org/network/authprompt-adapter-factory;1 {6e134924-6c3a-4d86-81ac-69432dd971dc}
+component {7ad1b327-6dfa-46ec-9234-f2a620ea7e00} nsPrompter.js
+contract @mozilla.org/embedcomp/prompt-service;1 {7ad1b327-6dfa-46ec-9234-f2a620ea7e00}
diff --git a/components/proxy/moz.build b/components/proxy/moz.build
new file mode 100644
index 000000000..2d20820d4
--- /dev/null
+++ b/components/proxy/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
+ if CONFIG['MOZ_ENABLE_LIBPROXY']:
+ SOURCES += ['src/nsLibProxySettings.cpp']
+ CXXFLAGS += CONFIG['MOZ_LIBPROXY_CFLAGS']
+ else:
+ SOURCES += ['src/nsUnixSystemProxySettings.cpp']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ SOURCES += [
+ 'src/nsWindowsSystemProxySettings.cpp',
+ 'src/ProxyUtils.cpp'
+ ]
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/proxy/src/ProxyUtils.cpp b/components/proxy/src/ProxyUtils.cpp
new file mode 100644
index 000000000..4e59f226a
--- /dev/null
+++ b/components/proxy/src/ProxyUtils.cpp
@@ -0,0 +1,182 @@
+/* -*- 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 "ProxyUtils.h"
+#include "nsTArray.h"
+#include "prnetdb.h"
+#include "prtypes.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+/**
+ * Normalize the short IP form into the complete form.
+ * For example, it converts "192.168" into "192.168.0.0"
+ */
+static bool
+NormalizeAddr(const nsACString& aAddr, nsCString& aNormalized)
+{
+ nsTArray<nsCString> addr;
+ if (!ParseString(aAddr, '.', addr)) {
+ return false;
+ }
+ aNormalized = "";
+ for (uint32_t i = 0; i < 4; ++i) {
+ if (i != 0) {
+ aNormalized.Append(".");
+ }
+ if (i < addr.Length()) {
+ aNormalized.Append(addr[i]);
+ } else {
+ aNormalized.Append("0");
+ }
+ }
+ return true;
+}
+
+static PRUint32
+MaskIPv4Addr(PRUint32 aAddr, uint16_t aMaskLen)
+{
+ if (aMaskLen == 32) {
+ return aAddr;
+ }
+ return PR_htonl(PR_ntohl(aAddr) & (~0L << (32 - aMaskLen)));
+}
+
+static void
+MaskIPv6Addr(PRIPv6Addr& aAddr, uint16_t aMaskLen)
+{
+ if (aMaskLen == 128) {
+ return;
+ }
+
+ if (aMaskLen > 96) {
+ aAddr.pr_s6_addr32[3] = PR_htonl(
+ PR_ntohl(aAddr.pr_s6_addr32[3]) & (~0L << (128 - aMaskLen)));
+ } else if (aMaskLen > 64) {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] = PR_htonl(
+ PR_ntohl(aAddr.pr_s6_addr32[2]) & (~0L << (96 - aMaskLen)));
+ } else if (aMaskLen > 32) {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] = 0;
+ aAddr.pr_s6_addr32[1] = PR_htonl(
+ PR_ntohl(aAddr.pr_s6_addr32[1]) & (~0L << (64 - aMaskLen)));
+ } else {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] = 0;
+ aAddr.pr_s6_addr32[1] = 0;
+ aAddr.pr_s6_addr32[0] = PR_htonl(
+ PR_ntohl(aAddr.pr_s6_addr32[0]) & (~0L << (32 - aMaskLen)));
+ }
+
+ return;
+}
+
+static bool
+IsMatchMask(const nsACString& aHost, const nsACString& aOverride)
+{
+ nsresult rv;
+
+ auto tokenEnd = aOverride.FindChar('/');
+ if (tokenEnd == -1) {
+ return false;
+ }
+
+ nsAutoCString prefixStr(Substring(aOverride,
+ tokenEnd + 1,
+ aOverride.Length() - tokenEnd - 1));
+ auto maskLen = prefixStr.ToInteger(&rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsAutoCString override(aOverride);
+ if (!NormalizeAddr(Substring(aOverride, 0, tokenEnd), override)) {
+ return false;
+ }
+
+ PRNetAddr prAddrHost;
+ PRNetAddr prAddrOverride;
+ if (PR_SUCCESS != PR_StringToNetAddr(PromiseFlatCString(aHost).get(),
+ &prAddrHost) ||
+ PR_SUCCESS != PR_StringToNetAddr(override.get(),
+ &prAddrOverride)) {
+ return false;
+ }
+
+ if (prAddrHost.raw.family == PR_AF_INET &&
+ prAddrOverride.raw.family == PR_AF_INET) {
+ return MaskIPv4Addr(prAddrHost.inet.ip, maskLen) ==
+ MaskIPv4Addr(prAddrOverride.inet.ip, maskLen);
+ }
+ else if (prAddrHost.raw.family == PR_AF_INET6 &&
+ prAddrOverride.raw.family == PR_AF_INET6) {
+ MaskIPv6Addr(prAddrHost.ipv6.ip, maskLen);
+ MaskIPv6Addr(prAddrOverride.ipv6.ip, maskLen);
+
+ return memcmp(&prAddrHost.ipv6.ip,
+ &prAddrOverride.ipv6.ip,
+ sizeof(PRIPv6Addr)) == 0;
+ }
+
+ return false;
+}
+
+static bool
+IsMatchWildcard(const nsACString& aHost, const nsACString& aOverride)
+{
+ nsAutoCString host(aHost);
+ nsAutoCString override(aOverride);
+
+ int32_t overrideLength = override.Length();
+ int32_t tokenStart = 0;
+ int32_t offset = 0;
+ bool star = false;
+
+ while (tokenStart < overrideLength) {
+ int32_t tokenEnd = override.FindChar('*', tokenStart);
+ if (tokenEnd == tokenStart) {
+ // Star is the first character in the token.
+ star = true;
+ tokenStart++;
+ // If the character following the '*' is a '.' character then skip
+ // it so that "*.foo.com" allows "foo.com".
+ if (override.FindChar('.', tokenStart) == tokenStart) {
+ nsAutoCString token(Substring(override,
+ tokenStart + 1,
+ overrideLength - tokenStart - 1));
+ if (host.Equals(token)) {
+ return true;
+ }
+ }
+ } else {
+ if (tokenEnd == -1) {
+ tokenEnd = overrideLength; // no '*' char, match rest of string
+ }
+ nsAutoCString token(Substring(override, tokenStart, tokenEnd - tokenStart));
+ offset = host.Find(token, offset);
+ if (offset == -1 || (!star && offset)) {
+ return false;
+ }
+ star = false;
+ tokenStart = tokenEnd;
+ offset += token.Length();
+ }
+ }
+
+ return (star || (offset == static_cast<int32_t>(host.Length())));
+}
+
+bool
+IsHostProxyEntry(const nsACString& aHost, const nsACString& aOverride)
+{
+ return IsMatchMask(aHost, aOverride) || IsMatchWildcard(aHost, aOverride);
+}
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
diff --git a/components/proxy/src/ProxyUtils.h b/components/proxy/src/ProxyUtils.h
new file mode 100644
index 000000000..7d6ae220f
--- /dev/null
+++ b/components/proxy/src/ProxyUtils.h
@@ -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/. */
+
+#ifndef mozilla_toolkit_system_windowsproxy_ProxyUtils_h
+#define mozilla_toolkit_system_windowsproxy_ProxyUtils_h
+
+#include "nsStringGlue.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+bool IsHostProxyEntry(const nsACString& aHost, const nsACString& aOverride);
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
+
+#endif // mozilla_toolkit_system_windowsproxy_ProxyUtils_h
diff --git a/components/proxy/src/nsLibProxySettings.cpp b/components/proxy/src/nsLibProxySettings.cpp
new file mode 100644
index 000000000..e9179c114
--- /dev/null
+++ b/components/proxy/src/nsLibProxySettings.cpp
@@ -0,0 +1,141 @@
+/* -*- 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 "nsISystemProxySettings.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsIServiceManager.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nspr.h"
+
+extern "C" {
+#include <proxy.h>
+}
+
+class nsUnixSystemProxySettings : public nsISystemProxySettings {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ nsUnixSystemProxySettings() { mProxyFactory = nullptr; }
+ nsresult Init();
+
+private:
+ ~nsUnixSystemProxySettings() {
+ if (mProxyFactory)
+ px_proxy_factory_free(mProxyFactory);
+ }
+
+ pxProxyFactory *mProxyFactory;
+};
+
+NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings)
+
+NS_IMETHODIMP
+nsUnixSystemProxySettings::GetMainThreadOnly(bool *aMainThreadOnly)
+{
+ *aMainThreadOnly = false;
+ return NS_OK;
+}
+
+nsresult
+nsUnixSystemProxySettings::Init()
+{
+ return NS_OK;
+}
+
+nsresult
+nsUnixSystemProxySettings::GetPACURI(nsACString& aResult)
+{
+ // Make sure we return an empty result.
+ aResult.Truncate();
+ return NS_OK;
+}
+
+nsresult
+nsUnixSystemProxySettings::GetProxyForURI(const nsACString & aSpec,
+ const nsACString & aScheme,
+ const nsACString & aHost,
+ const int32_t aPort,
+ nsACString & aResult)
+{
+ nsresult rv;
+
+ if (!mProxyFactory) {
+ mProxyFactory = px_proxy_factory_new();
+ }
+ NS_ENSURE_TRUE(mProxyFactory, NS_ERROR_NOT_AVAILABLE);
+
+ char **proxyArray = nullptr;
+ proxyArray = px_proxy_factory_get_proxies(mProxyFactory,
+ PromiseFlatCString(aSpec).get());
+ NS_ENSURE_TRUE(proxyArray, NS_ERROR_NOT_AVAILABLE);
+
+ // Translate libproxy's output to PAC string as expected
+ // libproxy returns an array of proxies in the format:
+ // <procotol>://[username:password@]proxy:port
+ // or
+ // direct://
+ //
+ // PAC format: "PROXY proxy1.foo.com:8080; PROXY proxy2.foo.com:8080; DIRECT"
+ // but nsISystemProxySettings allows "PROXY http://proxy.foo.com:8080" as well.
+
+ int c = 0;
+ while (proxyArray[c] != nullptr) {
+ if (!aResult.IsEmpty()) {
+ aResult.AppendLiteral("; ");
+ }
+
+ // figure out the scheme, and we can't use nsIIOService::NewURI because
+ // this is not the main thread.
+ char *colon = strchr (proxyArray[c], ':');
+ uint32_t schemelen = colon ? colon - proxyArray[c] : 0;
+ if (schemelen < 1) {
+ c++;
+ continue;
+ }
+
+ if (schemelen == 6 && !strncasecmp(proxyArray[c], "direct", 6)) {
+ aResult.AppendLiteral("DIRECT");
+ }
+ else {
+ aResult.AppendLiteral("PROXY ");
+ aResult.Append(proxyArray[c]);
+ }
+
+ c++;
+ }
+
+ PR_Free(proxyArray);
+ return NS_OK;
+}
+
+#define NS_UNIXSYSTEMPROXYSERVICE_CID /* 0fa3158c-d5a7-43de-9181-a285e74cf1d4 */\
+ { 0x0fa3158c, 0xd5a7, 0x43de, \
+ {0x91, 0x81, 0xa2, 0x85, 0xe7, 0x4c, 0xf1, 0xd4 } }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUnixSystemProxySettings, Init)
+NS_DEFINE_NAMED_CID(NS_UNIXSYSTEMPROXYSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kUnixProxyCIDs[] = {
+ { &kNS_UNIXSYSTEMPROXYSERVICE_CID, false, nullptr, nsUnixSystemProxySettingsConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kUnixProxyContracts[] = {
+ { NS_SYSTEMPROXYSETTINGS_CONTRACTID, &kNS_UNIXSYSTEMPROXYSERVICE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kUnixProxyModule = {
+ mozilla::Module::kVersion,
+ kUnixProxyCIDs,
+ kUnixProxyContracts
+};
+
+NSMODULE_DEFN(nsUnixProxyModule) = &kUnixProxyModule;
+
diff --git a/components/proxy/src/nsUnixSystemProxySettings.cpp b/components/proxy/src/nsUnixSystemProxySettings.cpp
new file mode 100644
index 000000000..c190da91d
--- /dev/null
+++ b/components/proxy/src/nsUnixSystemProxySettings.cpp
@@ -0,0 +1,543 @@
+/* -*- 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 "nsISystemProxySettings.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsIServiceManager.h"
+#include "nsIGConfService.h"
+#include "nsIURI.h"
+#include "nsReadableUtils.h"
+#include "nsArrayUtils.h"
+#include "prnetdb.h"
+#include "prenv.h"
+#include "nsPrintfCString.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIGSettingsService.h"
+#include "nsInterfaceHashtable.h"
+#include "mozilla/Attributes.h"
+#include "nsIURI.h"
+
+class nsUnixSystemProxySettings final : public nsISystemProxySettings {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ nsUnixSystemProxySettings()
+ : mSchemeProxySettings(4)
+ {
+ }
+ nsresult Init();
+
+private:
+ ~nsUnixSystemProxySettings() {}
+
+ nsCOMPtr<nsIGConfService> mGConf;
+ nsCOMPtr<nsIGSettingsService> mGSettings;
+ nsCOMPtr<nsIGSettingsCollection> mProxySettings;
+ nsInterfaceHashtable<nsCStringHashKey, nsIGSettingsCollection> mSchemeProxySettings;
+ bool IsProxyMode(const char* aMode);
+ nsresult SetProxyResultFromGConf(const char* aKeyBase, const char* aType, nsACString& aResult);
+ nsresult GetProxyFromGConf(const nsACString& aScheme, const nsACString& aHost, int32_t aPort, nsACString& aResult);
+ nsresult GetProxyFromGSettings(const nsACString& aScheme, const nsACString& aHost, int32_t aPort, nsACString& aResult);
+ nsresult SetProxyResultFromGSettings(const char* aKeyBase, const char* aType, nsACString& aResult);
+};
+
+NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings)
+
+NS_IMETHODIMP
+nsUnixSystemProxySettings::GetMainThreadOnly(bool *aMainThreadOnly)
+{
+ // dbus prevents us from being threadsafe, but this routine should not block anyhow
+ *aMainThreadOnly = true;
+ return NS_OK;
+}
+
+nsresult
+nsUnixSystemProxySettings::Init()
+{
+ mGSettings = do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (mGSettings) {
+ mGSettings->GetCollectionForSchema(NS_LITERAL_CSTRING("org.gnome.system.proxy"),
+ getter_AddRefs(mProxySettings));
+ }
+ if (!mProxySettings) {
+ mGConf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ }
+
+ return NS_OK;
+}
+
+bool
+nsUnixSystemProxySettings::IsProxyMode(const char* aMode)
+{
+ nsAutoCString mode;
+ return NS_SUCCEEDED(mGConf->GetString(NS_LITERAL_CSTRING("/system/proxy/mode"), mode)) &&
+ mode.EqualsASCII(aMode);
+}
+
+nsresult
+nsUnixSystemProxySettings::GetPACURI(nsACString& aResult)
+{
+ if (mProxySettings) {
+ nsCString proxyMode;
+ // Check if mode is auto
+ nsresult rv = mProxySettings->GetString(NS_LITERAL_CSTRING("mode"), proxyMode);
+ if (rv == NS_OK && proxyMode.EqualsLiteral("auto")) {
+ return mProxySettings->GetString(NS_LITERAL_CSTRING("autoconfig-url"), aResult);
+ }
+ /* The org.gnome.system.proxy schema has been found, but auto mode is not set.
+ * Don't try the GConf and return empty string. */
+ aResult.Truncate();
+ return NS_OK;
+ }
+
+ if (mGConf && IsProxyMode("auto")) {
+ return mGConf->GetString(NS_LITERAL_CSTRING("/system/proxy/autoconfig_url"),
+ aResult);
+ }
+ // Return an empty string when auto mode is not set.
+ aResult.Truncate();
+ return NS_OK;
+}
+
+static bool
+IsInNoProxyList(const nsACString& aHost, int32_t aPort, const char* noProxyVal)
+{
+ NS_ASSERTION(aPort >= 0, "Negative port?");
+
+ nsAutoCString noProxy(noProxyVal);
+ if (noProxy.EqualsLiteral("*"))
+ return true;
+
+ noProxy.StripWhitespace();
+
+ nsReadingIterator<char> pos;
+ nsReadingIterator<char> end;
+ noProxy.BeginReading(pos);
+ noProxy.EndReading(end);
+ while (pos != end) {
+ nsReadingIterator<char> last = pos;
+ nsReadingIterator<char> nextPos;
+ if (FindCharInReadable(',', last, end)) {
+ nextPos = last;
+ ++nextPos;
+ } else {
+ last = end;
+ nextPos = end;
+ }
+
+ nsReadingIterator<char> colon = pos;
+ int32_t port = -1;
+ if (FindCharInReadable(':', colon, last)) {
+ ++colon;
+ nsDependentCSubstring portStr(colon, last);
+ nsAutoCString portStr2(portStr); // We need this for ToInteger. String API's suck.
+ nsresult err;
+ port = portStr2.ToInteger(&err);
+ if (NS_FAILED(err)) {
+ port = -2; // don't match any port, so we ignore this pattern
+ }
+ --colon;
+ } else {
+ colon = last;
+ }
+
+ if (port == -1 || port == aPort) {
+ nsDependentCSubstring hostStr(pos, colon);
+ // By using StringEndsWith instead of an equality comparator, we can include sub-domains
+ if (StringEndsWith(aHost, hostStr, nsCaseInsensitiveCStringComparator()))
+ return true;
+ }
+
+ pos = nextPos;
+ }
+
+ return false;
+}
+
+static void SetProxyResult(const char* aType, const nsACString& aHost,
+ int32_t aPort, nsACString& aResult)
+{
+ aResult.AppendASCII(aType);
+ aResult.Append(' ');
+ aResult.Append(aHost);
+ if (aPort > 0) {
+ aResult.Append(':');
+ aResult.Append(nsPrintfCString("%d", aPort));
+ }
+}
+
+static nsresult
+GetProxyFromEnvironment(const nsACString& aScheme,
+ const nsACString& aHost,
+ int32_t aPort,
+ nsACString& aResult)
+{
+ nsAutoCString envVar;
+ envVar.Append(aScheme);
+ envVar.AppendLiteral("_proxy");
+ const char* proxyVal = PR_GetEnv(envVar.get());
+ if (!proxyVal) {
+ proxyVal = PR_GetEnv("all_proxy");
+ if (!proxyVal) {
+ // Return failure so that the caller can detect the failure and
+ // fall back to other proxy detection (e.g., WPAD)
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ const char* noProxyVal = PR_GetEnv("no_proxy");
+ if (noProxyVal && IsInNoProxyList(aHost, aPort, noProxyVal)) {
+ aResult.AppendLiteral("DIRECT");
+ return NS_OK;
+ }
+
+ // Use our URI parser to crack the proxy URI
+ nsCOMPtr<nsIURI> proxyURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(proxyURI), proxyVal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Is there a way to specify "socks://" or something in these environment
+ // variables? I can't find any documentation.
+ bool isHTTP;
+ rv = proxyURI->SchemeIs("http", &isHTTP);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isHTTP)
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+
+ nsAutoCString proxyHost;
+ rv = proxyURI->GetHost(proxyHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t proxyPort;
+ rv = proxyURI->GetPort(&proxyPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetProxyResult("PROXY", proxyHost, proxyPort, aResult);
+ return NS_OK;
+}
+
+nsresult
+nsUnixSystemProxySettings::SetProxyResultFromGConf(const char* aKeyBase, const char* aType,
+ nsACString& aResult)
+{
+ nsAutoCString hostKey;
+ hostKey.AppendASCII(aKeyBase);
+ hostKey.AppendLiteral("host");
+ nsAutoCString host;
+ nsresult rv = mGConf->GetString(hostKey, host);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (host.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString portKey;
+ portKey.AppendASCII(aKeyBase);
+ portKey.AppendLiteral("port");
+ int32_t port;
+ rv = mGConf->GetInt(portKey, &port);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* When port is 0, proxy is not considered as enabled even if host is set. */
+ if (port == 0)
+ return NS_ERROR_FAILURE;
+
+ SetProxyResult(aType, host, port, aResult);
+ return NS_OK;
+}
+
+nsresult
+nsUnixSystemProxySettings::SetProxyResultFromGSettings(const char* aKeyBase, const char* aType,
+ nsACString& aResult)
+{
+ nsDependentCString key(aKeyBase);
+
+ nsCOMPtr<nsIGSettingsCollection> proxy_settings = mSchemeProxySettings.Get(key);
+ nsresult rv;
+ if (!proxy_settings) {
+ rv = mGSettings->GetCollectionForSchema(key, getter_AddRefs(proxy_settings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSchemeProxySettings.Put(key, proxy_settings);
+ }
+
+ nsAutoCString host;
+ rv = proxy_settings->GetString(NS_LITERAL_CSTRING("host"), host);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (host.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ int32_t port;
+ rv = proxy_settings->GetInt(NS_LITERAL_CSTRING("port"), &port);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* When port is 0, proxy is not considered as enabled even if host is set. */
+ if (port == 0)
+ return NS_ERROR_FAILURE;
+
+ SetProxyResult(aType, host, port, aResult);
+ return NS_OK;
+}
+
+/* copied from nsProtocolProxyService.cpp --- we should share this! */
+static void
+proxy_MaskIPv6Addr(PRIPv6Addr &addr, uint16_t mask_len)
+{
+ if (mask_len == 128)
+ return;
+
+ if (mask_len > 96) {
+ addr.pr_s6_addr32[3] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[3]) & (~0L << (128 - mask_len)));
+ }
+ else if (mask_len > 64) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[2]) & (~0L << (96 - mask_len)));
+ }
+ else if (mask_len > 32) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[1]) & (~0L << (64 - mask_len)));
+ }
+ else {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] = 0;
+ addr.pr_s6_addr32[0] = PR_htonl(
+ PR_ntohl(addr.pr_s6_addr32[0]) & (~0L << (32 - mask_len)));
+ }
+}
+
+static bool ConvertToIPV6Addr(const nsACString& aName,
+ PRIPv6Addr* aAddr, int32_t* aMask)
+{
+ PRNetAddr addr;
+ // try to convert hostname to IP
+ if (PR_StringToNetAddr(PromiseFlatCString(aName).get(), &addr) != PR_SUCCESS)
+ return false;
+
+ // convert parsed address to IPv6
+ if (addr.raw.family == PR_AF_INET) {
+ // convert to IPv4-mapped address
+ PR_ConvertIPv4AddrToIPv6(addr.inet.ip, aAddr);
+ if (aMask) {
+ if (*aMask <= 32)
+ *aMask += 96;
+ else
+ return false;
+ }
+ } else if (addr.raw.family == PR_AF_INET6) {
+ // copy the address
+ memcpy(aAddr, &addr.ipv6.ip, sizeof(PRIPv6Addr));
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+static bool HostIgnoredByProxy(const nsACString& aIgnore,
+ const nsACString& aHost)
+{
+ if (aIgnore.Equals(aHost, nsCaseInsensitiveCStringComparator()))
+ return true;
+
+ if (aIgnore.First() == '*' &&
+ StringEndsWith(aHost, nsDependentCSubstring(aIgnore, 1),
+ nsCaseInsensitiveCStringComparator()))
+ return true;
+
+ int32_t mask = 128;
+ nsReadingIterator<char> start;
+ nsReadingIterator<char> slash;
+ nsReadingIterator<char> end;
+ aIgnore.BeginReading(start);
+ aIgnore.BeginReading(slash);
+ aIgnore.EndReading(end);
+ if (FindCharInReadable('/', slash, end)) {
+ ++slash;
+ nsDependentCSubstring maskStr(slash, end);
+ nsAutoCString maskStr2(maskStr);
+ nsresult err;
+ mask = maskStr2.ToInteger(&err);
+ if (NS_FAILED(err)) {
+ mask = 128;
+ }
+ --slash;
+ } else {
+ slash = end;
+ }
+
+ nsDependentCSubstring ignoreStripped(start, slash);
+ PRIPv6Addr ignoreAddr, hostAddr;
+ if (!ConvertToIPV6Addr(ignoreStripped, &ignoreAddr, &mask) ||
+ !ConvertToIPV6Addr(aHost, &hostAddr, nullptr))
+ return false;
+
+ proxy_MaskIPv6Addr(ignoreAddr, mask);
+ proxy_MaskIPv6Addr(hostAddr, mask);
+
+ return memcmp(&ignoreAddr, &hostAddr, sizeof(PRIPv6Addr)) == 0;
+}
+
+nsresult
+nsUnixSystemProxySettings::GetProxyFromGConf(const nsACString& aScheme,
+ const nsACString& aHost,
+ int32_t aPort,
+ nsACString& aResult)
+{
+ bool masterProxySwitch = false;
+ mGConf->GetBool(NS_LITERAL_CSTRING("/system/http_proxy/use_http_proxy"), &masterProxySwitch);
+ // if no proxy is set in GConf return NS_ERROR_FAILURE
+ if (!(IsProxyMode("manual") || masterProxySwitch)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIArray> ignoreList;
+ if (NS_SUCCEEDED(mGConf->GetStringList(NS_LITERAL_CSTRING("/system/http_proxy/ignore_hosts"),
+ getter_AddRefs(ignoreList))) && ignoreList) {
+ uint32_t len = 0;
+ ignoreList->GetLength(&len);
+ for (uint32_t i = 0; i < len; ++i) {
+ nsCOMPtr<nsISupportsString> str = do_QueryElementAt(ignoreList, i);
+ if (str) {
+ nsAutoString s;
+ if (NS_SUCCEEDED(str->GetData(s)) && !s.IsEmpty()) {
+ if (HostIgnoredByProxy(NS_ConvertUTF16toUTF8(s), aHost)) {
+ aResult.AppendLiteral("DIRECT");
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+
+ bool useHttpProxyForAll = false;
+ // This setting sometimes doesn't exist, don't bail on failure
+ mGConf->GetBool(NS_LITERAL_CSTRING("/system/http_proxy/use_same_proxy"), &useHttpProxyForAll);
+
+ nsresult rv;
+ if (!useHttpProxyForAll) {
+ rv = SetProxyResultFromGConf("/system/proxy/socks_", "SOCKS", aResult);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+
+ if (aScheme.LowerCaseEqualsLiteral("http") || useHttpProxyForAll) {
+ rv = SetProxyResultFromGConf("/system/http_proxy/", "PROXY", aResult);
+ } else if (aScheme.LowerCaseEqualsLiteral("https")) {
+ rv = SetProxyResultFromGConf("/system/proxy/secure_", "PROXY", aResult);
+ } else if (aScheme.LowerCaseEqualsLiteral("ftp")) {
+ rv = SetProxyResultFromGConf("/system/proxy/ftp_", "PROXY", aResult);
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult
+nsUnixSystemProxySettings::GetProxyFromGSettings(const nsACString& aScheme,
+ const nsACString& aHost,
+ int32_t aPort,
+ nsACString& aResult)
+{
+ nsCString proxyMode;
+ nsresult rv = mProxySettings->GetString(NS_LITERAL_CSTRING("mode"), proxyMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // return NS_ERROR_FAILURE when no proxy is set
+ if (!proxyMode.EqualsLiteral("manual")) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIArray> ignoreList;
+ if (NS_SUCCEEDED(mProxySettings->GetStringList(NS_LITERAL_CSTRING("ignore-hosts"),
+ getter_AddRefs(ignoreList))) && ignoreList) {
+ uint32_t len = 0;
+ ignoreList->GetLength(&len);
+ for (uint32_t i = 0; i < len; ++i) {
+ nsCOMPtr<nsISupportsCString> str = do_QueryElementAt(ignoreList, i);
+ if (str) {
+ nsCString s;
+ if (NS_SUCCEEDED(str->GetData(s)) && !s.IsEmpty()) {
+ if (HostIgnoredByProxy(s, aHost)) {
+ aResult.AppendLiteral("DIRECT");
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+
+ if (aScheme.LowerCaseEqualsLiteral("http")) {
+ rv = SetProxyResultFromGSettings("org.gnome.system.proxy.http", "PROXY", aResult);
+ } else if (aScheme.LowerCaseEqualsLiteral("https")) {
+ rv = SetProxyResultFromGSettings("org.gnome.system.proxy.https", "PROXY", aResult);
+ /* Try to use HTTP proxy when HTTPS proxy is not explicitly defined */
+ if (rv != NS_OK)
+ rv = SetProxyResultFromGSettings("org.gnome.system.proxy.http", "PROXY", aResult);
+ } else if (aScheme.LowerCaseEqualsLiteral("ftp")) {
+ rv = SetProxyResultFromGSettings("org.gnome.system.proxy.ftp", "PROXY", aResult);
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+ if (rv != NS_OK) {
+ /* If proxy for scheme is not specified, use SOCKS proxy for all schemes */
+ rv = SetProxyResultFromGSettings("org.gnome.system.proxy.socks", "SOCKS", aResult);
+ }
+
+ if (NS_FAILED(rv)) {
+ aResult.AppendLiteral("DIRECT");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsUnixSystemProxySettings::GetProxyForURI(const nsACString & aSpec,
+ const nsACString & aScheme,
+ const nsACString & aHost,
+ const int32_t aPort,
+ nsACString & aResult)
+{
+ if (mProxySettings) {
+ nsresult rv = GetProxyFromGSettings(aScheme, aHost, aPort, aResult);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+ if (mGConf)
+ return GetProxyFromGConf(aScheme, aHost, aPort, aResult);
+
+ return GetProxyFromEnvironment(aScheme, aHost, aPort, aResult);
+}
+
+#define NS_UNIXSYSTEMPROXYSERVICE_CID /* 0fa3158c-d5a7-43de-9181-a285e74cf1d4 */\
+ { 0x0fa3158c, 0xd5a7, 0x43de, \
+ {0x91, 0x81, 0xa2, 0x85, 0xe7, 0x4c, 0xf1, 0xd4 } }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUnixSystemProxySettings, Init)
+NS_DEFINE_NAMED_CID(NS_UNIXSYSTEMPROXYSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kUnixProxyCIDs[] = {
+ { &kNS_UNIXSYSTEMPROXYSERVICE_CID, false, nullptr, nsUnixSystemProxySettingsConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kUnixProxyContracts[] = {
+ { NS_SYSTEMPROXYSETTINGS_CONTRACTID, &kNS_UNIXSYSTEMPROXYSERVICE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kUnixProxyModule = {
+ mozilla::Module::kVersion,
+ kUnixProxyCIDs,
+ kUnixProxyContracts
+};
+
+NSMODULE_DEFN(nsUnixProxyModule) = &kUnixProxyModule;
diff --git a/components/proxy/src/nsWindowsSystemProxySettings.cpp b/components/proxy/src/nsWindowsSystemProxySettings.cpp
new file mode 100644
index 000000000..bb5f72b69
--- /dev/null
+++ b/components/proxy/src/nsWindowsSystemProxySettings.cpp
@@ -0,0 +1,304 @@
+/* -*- 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 <windows.h>
+#include <ras.h>
+#include <wininet.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "nsISystemProxySettings.h"
+#include "nsIServiceManager.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsPrintfCString.h"
+#include "nsNetCID.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIURI.h"
+#include "GeckoProfiler.h"
+#include "prnetdb.h"
+#include "ProxyUtils.h"
+
+class nsWindowsSystemProxySettings final : public nsISystemProxySettings
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ nsWindowsSystemProxySettings() {};
+ nsresult Init();
+
+private:
+ ~nsWindowsSystemProxySettings() {};
+
+ bool MatchOverride(const nsACString& aHost);
+ bool PatternMatch(const nsACString& aHost, const nsACString& aOverride);
+};
+
+NS_IMPL_ISUPPORTS(nsWindowsSystemProxySettings, nsISystemProxySettings)
+
+NS_IMETHODIMP
+nsWindowsSystemProxySettings::GetMainThreadOnly(bool *aMainThreadOnly)
+{
+ *aMainThreadOnly = false;
+ return NS_OK;
+}
+
+
+nsresult
+nsWindowsSystemProxySettings::Init()
+{
+ return NS_OK;
+}
+
+static void SetProxyResult(const char* aType, const nsACString& aHostPort,
+ nsACString& aResult)
+{
+ aResult.AssignASCII(aType);
+ aResult.Append(' ');
+ aResult.Append(aHostPort);
+}
+
+static void SetProxyResultDirect(nsACString& aResult)
+{
+ // For whatever reason, a proxy is not to be used.
+ aResult.AssignASCII("DIRECT");
+}
+
+static nsresult ReadInternetOption(uint32_t aOption, uint32_t& aFlags,
+ nsAString& aValue)
+{
+ DWORD connFlags = 0;
+ WCHAR connName[RAS_MaxEntryName + 1];
+ MOZ_SEH_TRY {
+ InternetGetConnectedStateExW(&connFlags, connName,
+ mozilla::ArrayLength(connName), 0);
+ } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ return NS_ERROR_FAILURE;
+ }
+
+ INTERNET_PER_CONN_OPTIONW options[2];
+ options[0].dwOption = INTERNET_PER_CONN_FLAGS_UI;
+ options[1].dwOption = aOption;
+
+ INTERNET_PER_CONN_OPTION_LISTW list;
+ list.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LISTW);
+ list.pszConnection = connFlags & INTERNET_CONNECTION_MODEM ?
+ connName : nullptr;
+ list.dwOptionCount = mozilla::ArrayLength(options);
+ list.dwOptionError = 0;
+ list.pOptions = options;
+
+ unsigned long size = sizeof(INTERNET_PER_CONN_OPTION_LISTW);
+ if (!InternetQueryOptionW(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION,
+ &list, &size)) {
+ if (GetLastError() != ERROR_INVALID_PARAMETER) {
+ return NS_ERROR_FAILURE;
+ }
+ options[0].dwOption = INTERNET_PER_CONN_FLAGS;
+ size = sizeof(INTERNET_PER_CONN_OPTION_LISTW);
+ MOZ_SEH_TRY {
+ if (!InternetQueryOptionW(nullptr,
+ INTERNET_OPTION_PER_CONNECTION_OPTION,
+ &list, &size)) {
+ return NS_ERROR_FAILURE;
+ }
+ } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ aFlags = options[0].Value.dwValue;
+ aValue.Assign(options[1].Value.pszValue);
+ GlobalFree(options[1].Value.pszValue);
+
+ return NS_OK;
+}
+
+bool
+nsWindowsSystemProxySettings::MatchOverride(const nsACString& aHost)
+{
+ nsresult rv;
+ uint32_t flags = 0;
+ nsAutoString buf;
+
+ rv = ReadInternetOption(INTERNET_PER_CONN_PROXY_BYPASS, flags, buf);
+ if (NS_FAILED(rv))
+ return false;
+
+ NS_ConvertUTF16toUTF8 cbuf(buf);
+
+ nsAutoCString host(aHost);
+ int32_t start = 0;
+ int32_t end = cbuf.Length();
+
+ // Windows formats its proxy override list in the form:
+ // server;server;server where 'server' is a server name pattern or IP
+ // address, or "<local>". "<local>" must be translated to
+ // "localhost;127.0.0.1".
+ // In a server name pattern, a '*' character matches any substring and
+ // all other characters must match themselves; the whole pattern must match
+ // the whole hostname.
+ while (true) {
+ int32_t delimiter = cbuf.FindCharInSet(" ;", start);
+ if (delimiter == -1)
+ delimiter = end;
+
+ if (delimiter != start) {
+ const nsAutoCString override(Substring(cbuf, start,
+ delimiter - start));
+ if (override.EqualsLiteral("<local>")) {
+ PRNetAddr addr;
+ bool isIpAddr = (PR_StringToNetAddr(host.get(), &addr) == PR_SUCCESS);
+
+ // Don't use proxy for local hosts (plain hostname, no dots)
+ if (!isIpAddr && !host.Contains('.')) {
+ return true;
+ }
+
+ if (host.EqualsLiteral("127.0.0.1") ||
+ host.EqualsLiteral("::1")) {
+ return true;
+ }
+ } else if (PatternMatch(host, override)) {
+ return true;
+ }
+ }
+
+ if (delimiter == end)
+ break;
+ start = ++delimiter;
+ }
+
+ return false;
+}
+
+bool
+nsWindowsSystemProxySettings::PatternMatch(const nsACString& aHost,
+ const nsACString& aOverride)
+{
+ return mozilla::toolkit::system::IsHostProxyEntry(aHost, aOverride);
+}
+
+nsresult
+nsWindowsSystemProxySettings::GetPACURI(nsACString& aResult)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::STORAGE);
+ nsresult rv;
+ uint32_t flags = 0;
+ nsAutoString buf;
+
+ rv = ReadInternetOption(INTERNET_PER_CONN_AUTOCONFIG_URL, flags, buf);
+ if (!(flags & PROXY_TYPE_AUTO_PROXY_URL)) {
+ aResult.Truncate();
+ return rv;
+ }
+
+ if (NS_SUCCEEDED(rv))
+ aResult = NS_ConvertUTF16toUTF8(buf);
+ return rv;
+}
+
+nsresult
+nsWindowsSystemProxySettings::GetProxyForURI(const nsACString & aSpec,
+ const nsACString & aScheme,
+ const nsACString & aHost,
+ const int32_t aPort,
+ nsACString & aResult)
+{
+ nsresult rv;
+ uint32_t flags = 0;
+ nsAutoString buf;
+
+ rv = ReadInternetOption(INTERNET_PER_CONN_PROXY_SERVER, flags, buf);
+ if (NS_FAILED(rv) || !(flags & PROXY_TYPE_PROXY)) {
+ SetProxyResultDirect(aResult);
+ return NS_OK;
+ }
+
+ if (MatchOverride(aHost)) {
+ SetProxyResultDirect(aResult);
+ return NS_OK;
+ }
+
+ NS_ConvertUTF16toUTF8 cbuf(buf);
+
+ nsAutoCString prefix;
+ ToLowerCase(aScheme, prefix);
+
+ prefix.Append('=');
+
+ nsAutoCString specificProxy;
+ nsAutoCString defaultProxy;
+ nsAutoCString socksProxy;
+ int32_t start = 0;
+ int32_t end = cbuf.Length();
+
+ while (true) {
+ int32_t delimiter = cbuf.FindCharInSet(" ;", start);
+ if (delimiter == -1)
+ delimiter = end;
+
+ if (delimiter != start) {
+ const nsAutoCString proxy(Substring(cbuf, start,
+ delimiter - start));
+ if (proxy.FindChar('=') == -1) {
+ // If a proxy name is listed by itself, it is used as the
+ // default proxy for any protocols that do not have a specific
+ // proxy specified.
+ // (http://msdn.microsoft.com/en-us/library/aa383996%28VS.85%29.aspx)
+ defaultProxy = proxy;
+ } else if (proxy.Find(prefix) == 0) {
+ // To list a proxy for a specific protocol, the string must
+ // follow the format "<protocol>=<protocol>://<proxy_name>".
+ // (http://msdn.microsoft.com/en-us/library/aa383996%28VS.85%29.aspx)
+ specificProxy = Substring(proxy, prefix.Length());
+ break;
+ } else if (proxy.Find("socks=") == 0) {
+ // SOCKS proxy.
+ socksProxy = Substring(proxy, 5); // "socks=" length.
+ }
+ }
+
+ if (delimiter == end)
+ break;
+ start = ++delimiter;
+ }
+
+ if (!specificProxy.IsEmpty())
+ SetProxyResult("PROXY", specificProxy, aResult); // Protocol-specific proxy.
+ else if (!defaultProxy.IsEmpty())
+ SetProxyResult("PROXY", defaultProxy, aResult); // Default proxy.
+ else if (!socksProxy.IsEmpty())
+ SetProxyResult("SOCKS", socksProxy, aResult); // SOCKS proxy.
+ else
+ SetProxyResultDirect(aResult); // Direct connection.
+
+ return NS_OK;
+}
+
+#define NS_WINDOWSSYSTEMPROXYSERVICE_CID /* 4e22d3ea-aaa2-436e-ada4-9247de57d367 */\
+ { 0x4e22d3ea, 0xaaa2, 0x436e, \
+ {0xad, 0xa4, 0x92, 0x47, 0xde, 0x57, 0xd3, 0x67 } }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowsSystemProxySettings, Init)
+NS_DEFINE_NAMED_CID(NS_WINDOWSSYSTEMPROXYSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kSysProxyCIDs[] = {
+ { &kNS_WINDOWSSYSTEMPROXYSERVICE_CID, false, nullptr, nsWindowsSystemProxySettingsConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kSysProxyContracts[] = {
+ { NS_SYSTEMPROXYSETTINGS_CONTRACTID, &kNS_WINDOWSSYSTEMPROXYSERVICE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kSysProxyModule = {
+ mozilla::Module::kVersion,
+ kSysProxyCIDs,
+ kSysProxyContracts
+};
+
+NSMODULE_DEFN(nsWindowsProxyModule) = &kSysProxyModule;
diff --git a/components/rdf/moz.build b/components/rdf/moz.build
new file mode 100644
index 000000000..3d08b2ee2
--- /dev/null
+++ b/components/rdf/moz.build
@@ -0,0 +1,63 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+DIRS += ['util']
+
+XPIDL_SOURCES += [
+ 'public/nsIRDFCompositeDataSource.idl',
+ 'public/nsIRDFContainer.idl',
+ 'public/nsIRDFContainerUtils.idl',
+ 'public/nsIRDFDataSource.idl',
+ 'public/nsIRDFDelegateFactory.idl',
+ 'public/nsIRDFInferDataSource.idl',
+ 'public/nsIRDFInMemoryDataSource.idl',
+ 'public/nsIRDFLiteral.idl',
+ 'public/nsIRDFNode.idl',
+ 'public/nsIRDFObserver.idl',
+ 'public/nsIRDFPropagatableDataSource.idl',
+ 'public/nsIRDFPurgeableDataSource.idl',
+ 'public/nsIRDFRemoteDataSource.idl',
+ 'public/nsIRDFResource.idl',
+ 'public/nsIRDFService.idl',
+ 'public/nsIRDFXMLParser.idl',
+ 'public/nsIRDFXMLSerializer.idl',
+ 'public/nsIRDFXMLSink.idl',
+ 'public/nsIRDFXMLSource.idl',
+ 'public/rdfIDataSource.idl',
+ 'public/rdfISerializer.idl',
+ 'public/rdfITripleVisitor.idl',
+]
+
+EXPORTS += [
+ 'src/nsILocalStore.h',
+ 'src/nsIRDFContentSink.h',
+ 'src/rdf.h',
+]
+
+UNIFIED_SOURCES += [
+ 'src/nsCompositeDataSource.cpp',
+ 'src/nsContainerEnumerator.cpp',
+ 'src/nsDefaultResourceFactory.cpp',
+ 'src/nsFileSystemDataSource.cpp',
+ 'src/nsInMemoryDataSource.cpp',
+ 'src/nsLocalStore.cpp',
+ 'src/nsNameSpaceMap.cpp',
+ 'src/nsRDFContainer.cpp',
+ 'src/nsRDFContainerUtils.cpp',
+ 'src/nsRDFContentSink.cpp',
+ 'src/nsRDFService.cpp',
+ 'src/nsRDFXMLDataSource.cpp',
+ 'src/nsRDFXMLParser.cpp',
+ 'src/nsRDFXMLSerializer.cpp',
+ 'src/rdfTriplesSerializer.cpp',
+ 'src/rdfutil.cpp',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+XPIDL_MODULE = 'rdf'
+FINAL_LIBRARY = 'xul'
+
diff --git a/components/rdf/public/nsIRDFCompositeDataSource.idl b/components/rdf/public/nsIRDFCompositeDataSource.idl
new file mode 100644
index 000000000..071b8f3ac
--- /dev/null
+++ b/components/rdf/public/nsIRDFCompositeDataSource.idl
@@ -0,0 +1,68 @@
+/* -*- 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 "nsIRDFDataSource.idl"
+
+interface nsISimpleEnumerator;
+
+/**
+ * An nsIRDFCompositeDataSource composes individual data sources, providing
+ * the illusion of a single, coherent RDF graph.
+ */
+[scriptable, uuid(96343820-307C-11D2-BC15-00805F912FE7)]
+interface nsIRDFCompositeDataSource : nsIRDFDataSource {
+
+ /**
+ *
+ * Set this value to <code>true</code> if the composite datasource
+ * may contains at least one datasource that has <em>negative</em>
+ * assertions. (This is the default.)
+ *
+ * Set this value to <code>false</code> if none of the datasources
+ * being composed contains a negative assertion. This allows the
+ * composite datasource to perform some query optimizations.
+ *
+ * By default, this value is <code>true</true>.
+ */
+ attribute boolean allowNegativeAssertions;
+
+ /**
+ * Set to <code>true</code> if the composite datasource should
+ * take care to coalesce duplicate arcs when returning values from
+ * queries. (This is the default.)
+ *
+ * Set to <code>false</code> if the composite datasource shouldn't
+ * bother to check for duplicates. This allows the composite
+ * datasource to more efficiently answer queries.
+ *
+ * By default, this value is <code>true</code>.
+ */
+ attribute boolean coalesceDuplicateArcs;
+
+ /**
+ * Add a datasource the the composite data source.
+ * @param aDataSource the datasource to add to composite
+ */
+ void AddDataSource(in nsIRDFDataSource aDataSource);
+
+ /**
+ * Remove a datasource from the composite data source.
+ * @param aDataSource the datasource to remove from the composite
+ */
+ void RemoveDataSource(in nsIRDFDataSource aDataSource);
+
+ /**
+ * Retrieve the datasources in the composite data source.
+ * @return an nsISimpleEnumerator that will enumerate each
+ * of the datasources in the composite
+ */
+ nsISimpleEnumerator GetDataSources();
+};
+
+%{C++
+extern nsresult
+NS_NewRDFCompositeDataSource(nsIRDFCompositeDataSource** result);
+%}
+
diff --git a/components/rdf/public/nsIRDFContainer.idl b/components/rdf/public/nsIRDFContainer.idl
new file mode 100644
index 000000000..78352b2b3
--- /dev/null
+++ b/components/rdf/public/nsIRDFContainer.idl
@@ -0,0 +1,94 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIRDFDataSource.idl"
+#include "nsIRDFResource.idl"
+#include "nsIRDFNode.idl"
+#include "nsISimpleEnumerator.idl"
+
+// A wrapper for manipulating RDF containers
+[scriptable, uuid(D4214E90-FB94-11D2-BDD8-00104BDE6048)]
+interface nsIRDFContainer : nsISupports {
+ readonly attribute nsIRDFDataSource DataSource;
+ readonly attribute nsIRDFResource Resource;
+
+ /**
+ * Initialize the container wrapper to the specified resource
+ * using the specified datasource for context.
+ */
+ void Init(in nsIRDFDataSource aDataSource, in nsIRDFResource aContainer);
+
+ /**
+ * Return the number of elements in the container. Note that this
+ * may not always be accurate due to aggregation.
+ */
+ long GetCount();
+
+ /**
+ * Return an enumerator that can be used to enumerate the contents
+ * of the container in ascending order.
+ */
+ nsISimpleEnumerator GetElements();
+
+ /**
+ * Append an element to the container, assigning it the next
+ * available ordinal.
+ */
+ void AppendElement(in nsIRDFNode aElement);
+
+ /**
+ * Remove the first occurence of the specified element from the
+ * container. If aRenumber is 'true', then the underlying RDF graph
+ * will be 're-numbered' to account for the removal.
+ */
+ void RemoveElement(in nsIRDFNode aElement, in boolean aRenumber);
+
+ /**
+ * Insert aElement at the specified index. If aRenumber is 'true', then
+ * the underlying RDF graph will be 're-numbered' to accomodate the new
+ * element.
+ */
+ void InsertElementAt(in nsIRDFNode aElement, in long aIndex, in boolean aRenumber);
+
+ /**
+ * Remove the element at the specified index. If aRenumber is 'true', then
+ * the underlying RDF graph will be 're-numbered' to account for the
+ * removal.
+ *
+ * @return the element that was removed.
+ */
+ nsIRDFNode RemoveElementAt(in long aIndex, in boolean aRenumber);
+
+ /**
+ * Determine the index of an element in the container.
+ *
+ * @return The index of the specified element in the container. If
+ * the element is not contained in the container, this function
+ * returns '-1'.
+ */
+ long IndexOf(in nsIRDFNode aElement);
+};
+
+%{C++
+nsresult
+NS_NewRDFContainer(nsIRDFContainer** aResult);
+
+nsresult
+NS_NewRDFContainer(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aResource,
+ nsIRDFContainer** aResult);
+
+/**
+ * Create a cursor on a container that enumerates its contents in
+ * order
+ */
+nsresult
+NS_NewContainerEnumerator(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aContainer,
+ nsISimpleEnumerator** aResult);
+
+
+%}
diff --git a/components/rdf/public/nsIRDFContainerUtils.idl b/components/rdf/public/nsIRDFContainerUtils.idl
new file mode 100644
index 000000000..21cb0b625
--- /dev/null
+++ b/components/rdf/public/nsIRDFContainerUtils.idl
@@ -0,0 +1,82 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIRDFContainer.idl"
+#include "nsIRDFResource.idl"
+
+
+// Container utilities
+[scriptable, uuid(D4214E91-FB94-11D2-BDD8-00104BDE6048)]
+interface nsIRDFContainerUtils : nsISupports {
+ /**
+ * Returns 'true' if the property is an RDF ordinal property.
+ */
+ boolean IsOrdinalProperty(in nsIRDFResource aProperty);
+
+ /**
+ * Convert the specified index to an ordinal property.
+ */
+ nsIRDFResource IndexToOrdinalResource(in long aIndex);
+
+ /**
+ * Convert the specified ordinal property into an index
+ */
+ long OrdinalResourceToIndex(in nsIRDFResource aOrdinal);
+
+ /**
+ * Return 'true' if the specified resource is a container
+ */
+ boolean IsContainer(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource);
+
+ /**
+ * Return 'true' if the specified resource is a container and it is empty
+ */
+ boolean IsEmpty(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource);
+
+ /**
+ * Return 'true' if the specified resource is a bag
+ */
+ boolean IsBag(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource);
+
+ /**
+ * Return 'true' if the specified resource is a sequence
+ */
+ boolean IsSeq(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource);
+
+ /**
+ * Return 'true' if the specified resource is an alternation
+ */
+ boolean IsAlt(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource);
+
+ /**
+ * Decorates the specified resource appropriately to make it
+ * usable as an empty bag in the specified data source.
+ */
+ nsIRDFContainer MakeBag(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource);
+
+ /**
+ * Decorates the specified resource appropriately to make it
+ * usable as an empty sequence in the specified data source.
+ */
+ nsIRDFContainer MakeSeq(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource);
+
+ /**
+ * Decorates the specified resource appropriately to make it
+ * usable as an empty alternation in the specified data source.
+ */
+ nsIRDFContainer MakeAlt(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource);
+
+ /**
+ * Retrieve the index of element in the container. Returns -1 if
+ * the element is not in the container.
+ */
+ long indexOf(in nsIRDFDataSource aDataSource, in nsIRDFResource aContainer, in nsIRDFNode aElement);
+};
+
+%{C++
+extern nsresult
+NS_NewRDFContainerUtils(nsIRDFContainerUtils** aResult);
+%}
diff --git a/components/rdf/public/nsIRDFDataSource.idl b/components/rdf/public/nsIRDFDataSource.idl
new file mode 100644
index 000000000..2915425c8
--- /dev/null
+++ b/components/rdf/public/nsIRDFDataSource.idl
@@ -0,0 +1,181 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIRDFResource.idl"
+#include "nsIRDFNode.idl"
+#include "nsISimpleEnumerator.idl"
+#include "nsIRDFObserver.idl"
+
+[scriptable, uuid(0F78DA58-8321-11d2-8EAC-00805F29F370)]
+interface nsIRDFDataSource : nsISupports
+{
+ /** The "URI" of the data source. This used by the RDF service's
+ * |GetDataSource()| method to cache datasources.
+ */
+ readonly attribute string URI;
+
+ /** Find an RDF resource that points to a given node over the
+ * specified arc & truth value
+ *
+ * @throws NS_RDF_NO_VALUE if there is no source that leads
+ * to the target with the specified property.
+ */
+ nsIRDFResource GetSource(in nsIRDFResource aProperty,
+ in nsIRDFNode aTarget,
+ in boolean aTruthValue);
+
+ /**
+ * Find all RDF resources that point to a given node over the
+ * specified arc & truth value
+ */
+ nsISimpleEnumerator GetSources(in nsIRDFResource aProperty,
+ in nsIRDFNode aTarget,
+ in boolean aTruthValue);
+
+ /**
+ * Find a child of that is related to the source by the given arc
+ * arc and truth value
+ *
+ * @throws NS_RDF_NO_VALUE if there is no target accessible from the
+ * source via the specified property.
+ */
+ nsIRDFNode GetTarget(in nsIRDFResource aSource,
+ in nsIRDFResource aProperty,
+ in boolean aTruthValue);
+
+ /**
+ * Find all children of that are related to the source by the given arc
+ * arc and truth value.
+ */
+ nsISimpleEnumerator GetTargets(in nsIRDFResource aSource,
+ in nsIRDFResource aProperty,
+ in boolean aTruthValue);
+
+ /**
+ * Add an assertion to the graph.
+ */
+ void Assert(in nsIRDFResource aSource,
+ in nsIRDFResource aProperty,
+ in nsIRDFNode aTarget,
+ in boolean aTruthValue);
+
+ /**
+ * Remove an assertion from the graph.
+ */
+ void Unassert(in nsIRDFResource aSource,
+ in nsIRDFResource aProperty,
+ in nsIRDFNode aTarget);
+
+ /**
+ * Change an assertion from
+ *
+ * [aSource]--[aProperty]-->[aOldTarget]
+ *
+ * to
+ *
+ * [aSource]--[aProperty]-->[aNewTarget]
+ */
+ void Change(in nsIRDFResource aSource,
+ in nsIRDFResource aProperty,
+ in nsIRDFNode aOldTarget,
+ in nsIRDFNode aNewTarget);
+
+ /**
+ * 'Move' an assertion from
+ *
+ * [aOldSource]--[aProperty]-->[aTarget]
+ *
+ * to
+ *
+ * [aNewSource]--[aProperty]-->[aTarget]
+ */
+ void Move(in nsIRDFResource aOldSource,
+ in nsIRDFResource aNewSource,
+ in nsIRDFResource aProperty,
+ in nsIRDFNode aTarget);
+
+ /**
+ * Query whether an assertion exists in this graph.
+ */
+ boolean HasAssertion(in nsIRDFResource aSource,
+ in nsIRDFResource aProperty,
+ in nsIRDFNode aTarget,
+ in boolean aTruthValue);
+
+ /**
+ * Add an observer to this data source. If the datasource
+ * supports observers, the datasource source should hold a strong
+ * reference to the observer.
+ */
+ void AddObserver(in nsIRDFObserver aObserver);
+
+ /**
+ * Remove an observer from this data source.
+ */
+ void RemoveObserver(in nsIRDFObserver aObserver);
+
+ /**
+ * Get a cursor to iterate over all the arcs that point into a node.
+ */
+ nsISimpleEnumerator ArcLabelsIn(in nsIRDFNode aNode);
+
+ /**
+ * Get a cursor to iterate over all the arcs that originate in
+ * a resource.
+ */
+ nsISimpleEnumerator ArcLabelsOut(in nsIRDFResource aSource);
+
+ /**
+ * Retrieve all of the resources that the data source currently
+ * refers to.
+ */
+ nsISimpleEnumerator GetAllResources();
+
+ /**
+ * Returns whether a given command is enabled for a set of sources.
+ */
+ boolean IsCommandEnabled(in nsISupports aSources,
+ in nsIRDFResource aCommand,
+ in nsISupports aArguments);
+
+ /**
+ * Perform the specified command on set of sources.
+ */
+ void DoCommand(in nsISupports aSources,
+ in nsIRDFResource aCommand,
+ in nsISupports aArguments);
+
+ /**
+ * Returns the set of all commands defined for a given source.
+ */
+ nsISimpleEnumerator GetAllCmds(in nsIRDFResource aSource);
+
+ /**
+ * Returns true if the specified node is pointed to by the specified arc.
+ * Equivalent to enumerating ArcLabelsIn and comparing for the specified arc.
+ */
+ boolean hasArcIn(in nsIRDFNode aNode, in nsIRDFResource aArc);
+
+ /**
+ * Returns true if the specified node has the specified outward arc.
+ * Equivalent to enumerating ArcLabelsOut and comparing for the specified arc.
+ */
+ boolean hasArcOut(in nsIRDFResource aSource, in nsIRDFResource aArc);
+
+ /**
+ * Notify observers that the datasource is about to send several
+ * notifications at once.
+ * This must be followed by calling endUpdateBatch(), otherwise
+ * viewers will get out of sync.
+ */
+ void beginUpdateBatch();
+
+ /**
+ * Notify observers that the datasource has completed issuing
+ * a notification group.
+ */
+ void endUpdateBatch();
+};
diff --git a/components/rdf/public/nsIRDFDelegateFactory.idl b/components/rdf/public/nsIRDFDelegateFactory.idl
new file mode 100644
index 000000000..6fbc42fd5
--- /dev/null
+++ b/components/rdf/public/nsIRDFDelegateFactory.idl
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+/*
+
+ An interface used for runtime pseudo-aggregation of RDF delegate
+ objects.
+
+*/
+
+#include "nsrootidl.idl"
+#include "nsISupports.idl"
+interface nsIRDFResource;
+
+/**
+ * This interface should be implemented by an XPCOM factory that
+ * is registered to handle "@mozilla.org/rdf/delegate-factory/[key]/[scheme];1"
+ * ContractIDs.
+ *
+ * The factory will be invoked to create delegate objects from
+ * nsIRDFResource::GetDelegate().
+ */
+[scriptable, uuid(A1B89470-A124-11d3-BE59-0020A6361667)]
+interface nsIRDFDelegateFactory : nsISupports
+{
+ /**
+ * Create a delegate for the specified RDF resource.
+ *
+ * The created delegate should forward AddRef() and Release()
+ * calls to the aOuter object.
+ */
+ void CreateDelegate(in nsIRDFResource aOuter,
+ in string aKey,
+ in nsIIDRef aIID,
+ [retval, iid_is(aIID)] out nsQIResult aResult);
+};
+
+
diff --git a/components/rdf/public/nsIRDFInMemoryDataSource.idl b/components/rdf/public/nsIRDFInMemoryDataSource.idl
new file mode 100644
index 000000000..35734efe6
--- /dev/null
+++ b/components/rdf/public/nsIRDFInMemoryDataSource.idl
@@ -0,0 +1,14 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIRDFResource.idl"
+#include "nsIRDFNode.idl"
+
+[scriptable, uuid(17C4E0AA-1DD2-11B2-8029-BF6F668DE500)]
+interface nsIRDFInMemoryDataSource : nsISupports
+{
+ void EnsureFastContainment(in nsIRDFResource aSource);
+};
diff --git a/components/rdf/public/nsIRDFInferDataSource.idl b/components/rdf/public/nsIRDFInferDataSource.idl
new file mode 100644
index 000000000..07bafcfff
--- /dev/null
+++ b/components/rdf/public/nsIRDFInferDataSource.idl
@@ -0,0 +1,28 @@
+/* -*- 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 "nsIRDFDataSource.idl"
+
+/**
+ * An nsIRDFInferDataSource is implemented by a infer engine. This
+ * engine mimics assertions in addition to those in the baseDataSource
+ * according to a particular vocabulary.
+ * Infer engines have contract IDs in the form of
+ * "@mozilla.org/rdf/infer-datasource;1?engine="
+ */
+
+[scriptable, uuid(2b04860f-4017-40f6-8a57-784a1e35077a)]
+interface nsIRDFInferDataSource : nsIRDFDataSource {
+ /**
+ *
+ * The wrapped datasource.
+ *
+ * The InferDataSource contains all arcs from the wrapped
+ * datasource plus those infered by the vocabulary implemented
+ * by the InferDataSource.
+ */
+ attribute nsIRDFDataSource baseDataSource;
+};
+
diff --git a/components/rdf/public/nsIRDFLiteral.idl b/components/rdf/public/nsIRDFLiteral.idl
new file mode 100644
index 000000000..3d289c4fe
--- /dev/null
+++ b/components/rdf/public/nsIRDFLiteral.idl
@@ -0,0 +1,68 @@
+/* -*- 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 "nsIRDFNode.idl"
+
+%{C++
+#include "nscore.h" // for char16_t
+%}
+
+[ptr] native const_octet_ptr(const uint8_t);
+
+/**
+ * A literal node in the graph, whose value is a string.
+ */
+[scriptable, uuid(E0C493D2-9542-11d2-8EB8-00805F29F370)]
+interface nsIRDFLiteral : nsIRDFNode {
+ /**
+ * The Unicode string value of the literal.
+ */
+ readonly attribute wstring Value;
+
+ /**
+ * An unscriptable version used to avoid a string copy. Meant
+ * for use as a performance optimization.
+ */
+ [noscript] void GetValueConst([shared] out wstring aConstValue);
+};
+
+/**
+ * A literal node in the graph, whose value is a date
+ */
+[scriptable, uuid(E13A24E1-C77A-11d2-80BE-006097B76B8E)]
+interface nsIRDFDate : nsIRDFNode {
+ /**
+ * The date value of the literal
+ */
+ readonly attribute PRTime Value;
+};
+
+/**
+ * A literal node in the graph, whose value is an integer
+ */
+[scriptable, uuid(E13A24E3-C77A-11d2-80BE-006097B76B8E)]
+interface nsIRDFInt : nsIRDFNode {
+ /**
+ * The integer value of the literal
+ */
+ readonly attribute long Value;
+};
+
+/**
+ * A literal node in the graph, whose value is arbitrary
+ * binary data.
+ */
+[scriptable, uuid(237f85a2-1dd2-11b2-94af-8122582fc45e)]
+interface nsIRDFBlob : nsIRDFNode {
+ /**
+ * The binary data.
+ */
+ [noscript] readonly attribute const_octet_ptr value;
+
+ /**
+ * The data's length.
+ */
+ readonly attribute long length;
+};
diff --git a/components/rdf/public/nsIRDFNode.idl b/components/rdf/public/nsIRDFNode.idl
new file mode 100644
index 000000000..2bd7d4967
--- /dev/null
+++ b/components/rdf/public/nsIRDFNode.idl
@@ -0,0 +1,15 @@
+/* -*- 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 "nsISupports.idl"
+
+// An nsIRDFNode object is an abstract placeholder for a node in the
+// RDF data model. Its concreted implementations (e.g., nsIRDFResource
+// or nsIRDFLiteral) are the actual objects that populate the graph.
+[scriptable, uuid(0F78DA50-8321-11d2-8EAC-00805F29F370)]
+interface nsIRDFNode : nsISupports {
+ // Determine if two nodes are identical
+ boolean EqualsNode(in nsIRDFNode aNode);
+};
diff --git a/components/rdf/public/nsIRDFObserver.idl b/components/rdf/public/nsIRDFObserver.idl
new file mode 100644
index 000000000..1923c5b38
--- /dev/null
+++ b/components/rdf/public/nsIRDFObserver.idl
@@ -0,0 +1,94 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIRDFResource.idl"
+#include "nsIRDFNode.idl"
+
+interface nsIRDFDataSource;
+
+// An nsIRDFObserver object is an observer that will be notified
+// when assertions are made or removed from a datasource
+[scriptable, uuid(3CC75360-484A-11D2-BC16-00805F912FE7)]
+interface nsIRDFObserver : nsISupports {
+ /**
+ * This method is called whenever a new assertion is made
+ * in the data source
+ * @param aDataSource the datasource that is issuing
+ * the notification.
+ * @param aSource the subject of the assertion
+ * @param aProperty the predicate of the assertion
+ * @param aTarget the object of the assertion
+ */
+ void onAssert(in nsIRDFDataSource aDataSource,
+ in nsIRDFResource aSource,
+ in nsIRDFResource aProperty,
+ in nsIRDFNode aTarget);
+
+ /**
+ * This method is called whenever an assertion is removed
+ * from the data source
+ * @param aDataSource the datasource that is issuing
+ * the notification.
+ * @param aSource the subject of the assertion
+ * @param aProperty the predicate of the assertion
+ * @param aTarget the object of the assertion
+ */
+ void onUnassert(in nsIRDFDataSource aDataSource,
+ in nsIRDFResource aSource,
+ in nsIRDFResource aProperty,
+ in nsIRDFNode aTarget);
+
+ /**
+ * This method is called when the object of an assertion
+ * changes from one value to another.
+ * @param aDataSource the datasource that is issuing
+ * the notification.
+ * @param aSource the subject of the assertion
+ * @param aProperty the predicate of the assertion
+ * @param aOldTarget the old object of the assertion
+ * @param aNewTarget the new object of the assertion
+ */
+ void onChange(in nsIRDFDataSource aDataSource,
+ in nsIRDFResource aSource,
+ in nsIRDFResource aProperty,
+ in nsIRDFNode aOldTarget,
+ in nsIRDFNode aNewTarget);
+
+ /**
+ * This method is called when the subject of an assertion
+ * changes from one value to another.
+ * @param aDataSource the datasource that is issuing
+ * the notification.
+ * @param aOldSource the old subject of the assertion
+ * @param aNewSource the new subject of the assertion
+ * @param aProperty the predicate of the assertion
+ * @param aTarget the object of the assertion
+ */
+ void onMove(in nsIRDFDataSource aDataSource,
+ in nsIRDFResource aOldSource,
+ in nsIRDFResource aNewSource,
+ in nsIRDFResource aProperty,
+ in nsIRDFNode aTarget);
+
+ /**
+ * This method is called when a datasource is about to
+ * send several notifications at once. The observer can
+ * use this as a cue to optimize its behavior. The observer
+ * can expect the datasource to call endUpdateBatch() when
+ * the group of notifications has completed.
+ * @param aDataSource the datasource that is going to
+ * be issuing the notifications.
+ */
+ void onBeginUpdateBatch(in nsIRDFDataSource aDataSource);
+
+ /**
+ * This method is called when a datasource has completed
+ * issuing a notification group.
+ * @param aDataSource the datasource that has finished
+ * issuing a group of notifications
+ */
+ void onEndUpdateBatch(in nsIRDFDataSource aDataSource);
+};
diff --git a/components/rdf/public/nsIRDFPropagatableDataSource.idl b/components/rdf/public/nsIRDFPropagatableDataSource.idl
new file mode 100644
index 000000000..dfe510e9c
--- /dev/null
+++ b/components/rdf/public/nsIRDFPropagatableDataSource.idl
@@ -0,0 +1,27 @@
+/* -*- Mode: idl; 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"
+
+/**
+ * An nsIRDFPropagatableDataSource provides an ability to suppress
+ * synchronization notifications.
+ */
+[scriptable, uuid(5a9b4770-9fcb-4307-a12e-4b6708e78b97)]
+interface nsIRDFPropagatableDataSource: nsISupports {
+
+ /**
+ * Set this value to <code>true</code> to enable synchronization
+ * notifications.
+ *
+ * Set this value to <code>false</code> to disable synchronization
+ * notifications.
+ *
+ * By default, this value is <code>true</code>.
+ */
+ attribute boolean propagateChanges;
+
+};
diff --git a/components/rdf/public/nsIRDFPurgeableDataSource.idl b/components/rdf/public/nsIRDFPurgeableDataSource.idl
new file mode 100644
index 000000000..05973df0e
--- /dev/null
+++ b/components/rdf/public/nsIRDFPurgeableDataSource.idl
@@ -0,0 +1,19 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIRDFResource.idl"
+#include "nsIRDFNode.idl"
+
+[scriptable, uuid(951700F0-FED0-11D2-BDD9-00104BDE6048)]
+interface nsIRDFPurgeableDataSource : nsISupports
+{
+ boolean Mark(in nsIRDFResource aSource,
+ in nsIRDFResource aProperty,
+ in nsIRDFNode aTarget,
+ in boolean aTruthValue);
+
+ void Sweep();
+};
diff --git a/components/rdf/public/nsIRDFRemoteDataSource.idl b/components/rdf/public/nsIRDFRemoteDataSource.idl
new file mode 100644
index 000000000..fd2cd1b0b
--- /dev/null
+++ b/components/rdf/public/nsIRDFRemoteDataSource.idl
@@ -0,0 +1,44 @@
+/* -*- 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 "nsISupports.idl"
+
+/**
+ * A datasource that may load asynchronously
+ */
+[scriptable, uuid(1D297320-27F7-11d3-BE01-000064657374)]
+interface nsIRDFRemoteDataSource : nsISupports
+{
+ /**
+ * This value is <code>true</code> when the datasource has
+ * fully loaded itself.
+ */
+ readonly attribute boolean loaded;
+
+ /**
+ * Specify the URI for the data source: this is the prefix
+ * that will be used to register the data source in the
+ * data source registry.
+ * @param aURI the URI to load
+ */
+ void Init(in string aURI);
+
+ /**
+ * Refresh the remote datasource, re-loading its contents
+ * from the URI.
+ *
+ * @param aBlocking If <code>true</code>, the call will block
+ * until the datasource has completely reloaded.
+ */
+ void Refresh(in boolean aBlocking);
+
+ /**
+ * Request that a data source write its contents out to
+ * permanent storage, if applicable.
+ */
+ void Flush();
+ void FlushTo(in string aURI);
+};
+
diff --git a/components/rdf/public/nsIRDFResource.idl b/components/rdf/public/nsIRDFResource.idl
new file mode 100644
index 000000000..df4d4b8ff
--- /dev/null
+++ b/components/rdf/public/nsIRDFResource.idl
@@ -0,0 +1,81 @@
+/* -*- 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 "nsrootidl.idl"
+#include "nsIRDFNode.idl"
+
+
+/**
+ * An nsIRDFResource is an object that has unique identity in the
+ * RDF data model. The object's identity is determined by its URI.
+ */
+[scriptable, uuid(fb9686a7-719a-49dc-9107-10dea5739341)]
+interface nsIRDFResource : nsIRDFNode {
+ /**
+ * The single-byte string value of the resource.
+ * @note THIS IS OBSOLETE. C++ should use GetValueConst and script
+ * should use .valueUTF8
+ */
+ readonly attribute string Value;
+
+ /**
+ * The UTF-8 URI of the resource.
+ */
+ readonly attribute AUTF8String ValueUTF8;
+
+ /**
+ * An unscriptable version used to avoid a string copy. Meant
+ * for use as a performance optimization. The string is encoded
+ * in UTF-8.
+ */
+ [noscript] void GetValueConst([shared] out string aConstValue);
+
+ /**
+ * This method is called by the nsIRDFService after constructing
+ * a resource object to initialize its URI. You would not normally
+ * call this method directly
+ */
+ void Init(in string uri);
+
+ /**
+ * Determine if the resource has the given URI.
+ */
+ boolean EqualsString(in string aURI);
+
+ /**
+ * Retrieve the "delegate" object for this resource. A resource
+ * may have several delegate objects, each of whose lifetimes is
+ * bound to the life of the resource object.
+ *
+ * This method will return the delegate for the given key after
+ * QueryInterface()-ing it to the requested IID.
+ *
+ * If no delegate exists for the specified key, this method will
+ * attempt to create one using the component manager. Specifically,
+ * it will combine aKey with the resource's URI scheme to produce
+ * a ContractID as follows:
+ *
+ * component:/rdf/delegate-factory/[key]/[scheme]
+ *
+ * This ContractID will be used to locate a factory using the
+ * FindFactory() method of nsIComponentManager. If the nsIFactory
+ * exists, it will be used to create a "delegate factory"; that
+ * is, an object that supports nsIRDFDelegateFactory. The delegate
+ * factory will be used to construct the delegate object.
+ */
+ void GetDelegate(in string aKey, in nsIIDRef aIID,
+ [iid_is(aIID),retval] out nsQIResult aResult);
+
+ /**
+ * Force a delegate to be "unbound" from the resource.
+ *
+ * Normally, a delegate object's lifetime will be identical to
+ * that of the resource to which it is bound; this method allows a
+ * delegate to unlink itself from an RDF resource prematurely.
+ */
+ void ReleaseDelegate(in string aKey);
+};
+
+
diff --git a/components/rdf/public/nsIRDFService.idl b/components/rdf/public/nsIRDFService.idl
new file mode 100644
index 000000000..08023feab
--- /dev/null
+++ b/components/rdf/public/nsIRDFService.idl
@@ -0,0 +1,146 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIRDFResource.idl"
+#include "nsIRDFLiteral.idl"
+#include "nsIRDFDataSource.idl"
+
+
+/**
+ * The RDF service interface. This is a singleton object which should be
+ * obtained from the <code>nsServiceManager</code>.
+ */
+[scriptable, uuid(BFD05261-834C-11d2-8EAC-00805F29F370)]
+interface nsIRDFService : nsISupports {
+ /**
+ * Construct an RDF resource from a single-byte URI. <code>nsIRDFService</code>
+ * caches resources that are in-use, so multiple calls to <code>GetResource()</code>
+ * for the same <code>uri</code> will return identical pointers. FindResource
+ * is used to find out whether there already exists a resource corresponding to that url.
+ */
+ nsIRDFResource GetResource(in AUTF8String aURI);
+
+ /**
+ * Construct an RDF resource from a Unicode URI. This is provided
+ * as a convenience method, allowing automatic, in-line C++
+ * conversion from <code>nsString</code> objects. The <code>uri</code> will
+ * be converted to a single-byte representation internally.
+ */
+ nsIRDFResource GetUnicodeResource(in AString aURI);
+
+ nsIRDFResource GetAnonymousResource();
+
+ /**
+ * Construct an RDF literal from a Unicode string.
+ */
+ nsIRDFLiteral GetLiteral(in wstring aValue);
+
+ /**
+ * Construct an RDF literal from a PRTime.
+ */
+ nsIRDFDate GetDateLiteral(in PRTime aValue);
+
+ /**
+ * Construct an RDF literal from an int.
+ */
+ nsIRDFInt GetIntLiteral(in long aValue);
+
+ /**
+ * Construct an RDF literal from a data blob
+ */
+ [noscript] nsIRDFBlob getBlobLiteral(in const_octet_ptr aValue, in long aLength);
+
+ boolean IsAnonymousResource(in nsIRDFResource aResource);
+
+ /**
+ * Registers a resource with the RDF system, making it unique w.r.t.
+ * GetResource.
+ *
+ * An implementation of nsIRDFResource should call this in its
+ * Init() method if it wishes the resource to be globally unique
+ * (which is usually the case).
+ *
+ * @note that the resource will <i>not</i> be ref-counted by the
+ * RDF service: the assumption is that the resource implementation
+ * will call nsIRDFService::UnregisterResource() when the last
+ * reference to the resource is released.
+ *
+ * @note that the nsIRDFService implementation may choose to
+ * maintain a reference to the resource's URI; therefore, the
+ * resource implementation should ensure that the resource's URI
+ * (accessible via nsIRDFResource::GetValue(const char* *aURI)) is
+ * valid before calling RegisterResource(). Furthermore, the
+ * resource implementation should ensure that this pointer
+ * <i>remains</i> valid for the lifetime of the resource. (The
+ * implementation of the resource cache in nsIRDFService uses the
+ * URI maintained "internally" in the resource as a key into the
+ * cache rather than copying the resource URI itself.)
+ */
+ void RegisterResource(in nsIRDFResource aResource, in boolean aReplace);
+
+ /**
+ * Called to notify the resource manager that a resource is no
+ * longer in use. This method should only be called from the
+ * destructor of a "custom" resource implementation to notify the
+ * RDF service that the last reference to the resource has been
+ * released, so the resource is no longer valid.
+ *
+ * @note As mentioned in nsIRDFResourceFactory::CreateResource(),
+ * the RDF service will use the result of
+ * nsIRDFResource::GetValue() as a key into its cache. For this
+ * reason, you must always un-cache the resource <b>before</b>
+ * releasing the storage for the <code>const char*</code> URI.
+ */
+ void UnregisterResource(in nsIRDFResource aResource);
+
+ /**
+ * Register a <i>named data source</i>. The RDF service will call
+ * <code>nsIRDFDataSource::GetURI()</code> to determine the URI under
+ * which to register the data source.
+ *
+ * @note that the data source will <i>not</i> be refcounted by the
+ * RDF service! The assumption is that an RDF data source
+ * registers with the service once it is initialized (via
+ * <code>nsIRDFDataSource::Init()</code>), and unregisters when the
+ * last reference to the data source is released.
+ */
+ void RegisterDataSource(in nsIRDFDataSource aDataSource,
+ in boolean aReplace);
+
+ /**
+ * Unregister a <i>named data source</i>. The RDF service will call
+ * <code>nsIRDFDataSource::GetURI()</code> to determine the URI under which the
+ * data source was registered.
+ */
+ void UnregisterDataSource(in nsIRDFDataSource aDataSource);
+
+ /**
+ * Get the <i>named data source</i> corresponding to the URI. If a data
+ * source has been registered via <code>RegisterDataSource()</code>, that
+ * data source will be returned.
+ *
+ * If no data source is currently
+ * registered for the specified URI, and a data source <i>constructor</i>
+ * function has been registered via <code>RegisterDatasourceConstructor()</code>,
+ * the RDF service will call the constructor to attempt to construct a
+ * new data source. If construction is successful, the data source will
+ * be initialized via <code>nsIRDFDataSource::Init()</code>.
+ */
+ nsIRDFDataSource GetDataSource(in string aURI);
+
+ /**
+ * Same as GetDataSource, but if a remote/XML data source needs to be
+ * constructed, then this method will issue a <b>blocking</b> Refresh
+ * call on that data source.
+ */
+ nsIRDFDataSource GetDataSourceBlocking(in string aURI);
+};
+
+%{C++
+extern nsresult
+NS_NewRDFService(nsIRDFService** result);
+%}
+
diff --git a/components/rdf/public/nsIRDFXMLParser.idl b/components/rdf/public/nsIRDFXMLParser.idl
new file mode 100644
index 000000000..1e7dde51f
--- /dev/null
+++ b/components/rdf/public/nsIRDFXMLParser.idl
@@ -0,0 +1,33 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIRDFDataSource.idl"
+#include "nsIStreamListener.idl"
+#include "nsIURI.idl"
+
+[scriptable, uuid(1831dd2e-1dd2-11b2-bdb3-86b7b50b70b5)]
+interface nsIRDFXMLParser : nsISupports
+{
+ /**
+ * Create a stream listener that can be used to asynchronously
+ * parse RDF/XML.
+ * @param aSink the RDF datasource the will receive the data
+ * @param aBaseURI the base URI used to resolve relative
+ * references in the RDF/XML
+ * @return an nsIStreamListener object to handle the data
+ */
+ nsIStreamListener parseAsync(in nsIRDFDataSource aSink, in nsIURI aBaseURI);
+
+ /**
+ * Parse a string of RDF/XML
+ * @param aSink the RDF datasource that will receive the data
+ * @param aBaseURI the base URI used to resolve relative
+ * references in the RDF/XML
+ * @param aSource a UTF8 string containing RDF/XML data.
+ */
+ void parseString(in nsIRDFDataSource aSink, in nsIURI aBaseURI, in AUTF8String aSource);
+};
diff --git a/components/rdf/public/nsIRDFXMLSerializer.idl b/components/rdf/public/nsIRDFXMLSerializer.idl
new file mode 100644
index 000000000..e16ef2a2a
--- /dev/null
+++ b/components/rdf/public/nsIRDFXMLSerializer.idl
@@ -0,0 +1,27 @@
+/* -*- 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 "nsIAtom.idl"
+#include "nsISupports.idl"
+#include "nsIRDFDataSource.idl"
+
+[scriptable, uuid(8ae1fbf8-1dd2-11b2-bd21-d728069cca92)]
+interface nsIRDFXMLSerializer : nsISupports
+{
+ /**
+ * Initialize the serializer with the specified datasource.
+ * @param aDataSource the datasource from which data will be
+ * serialized
+ */
+ void init(in nsIRDFDataSource aDataSource);
+
+ /**
+ * Add the specified namespace to the serializer.
+ * @param aPrefix the attribute namespace prefix
+ * @param aURI the namespace URI
+ */
+ void addNameSpace(in nsIAtom aPrefix, in DOMString aURI);
+};
diff --git a/components/rdf/public/nsIRDFXMLSink.idl b/components/rdf/public/nsIRDFXMLSink.idl
new file mode 100644
index 000000000..8c9d9461c
--- /dev/null
+++ b/components/rdf/public/nsIRDFXMLSink.idl
@@ -0,0 +1,130 @@
+/* -*- 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/. */
+
+/*
+
+ Interfaces for the RDF/XML sink, which parses RDF/XML into
+ a graph representation.
+
+*/
+
+#include "nsISupports.idl"
+
+// XXX Until these get scriptable. See nsIRDFXMLSink::AddNameSpace()
+[ptr] native nsIAtomPtr(nsIAtom);
+[ref] native nsStringRef(nsString);
+%{C++
+class nsIAtom;
+class nsString;
+%}
+
+interface nsIRDFXMLSink;
+
+/**
+ * An observer that is notified as progress is made on the load
+ * of an RDF/XML document in an <code>nsIRDFXMLSink</code>.
+ */
+[scriptable, uuid(EB1A5D30-AB33-11D2-8EC6-00805F29F370)]
+interface nsIRDFXMLSinkObserver : nsISupports
+{
+ /**
+ * Called when the load begins.
+ * @param aSink the RDF/XML sink on which the load is beginning.
+ */
+ void onBeginLoad(in nsIRDFXMLSink aSink);
+
+ /**
+ * Called when the load is suspended (e.g., for network quantization).
+ * @param aSink the RDF/XML sink that is being interrupted.
+ */
+ void onInterrupt(in nsIRDFXMLSink aSink);
+
+ /**
+ * Called when a suspended load is resuming.
+ * @param aSink the RDF/XML sink that is resuming.
+ */
+ void onResume(in nsIRDFXMLSink aSink);
+
+ /**
+ * Called when an RDF/XML load completes successfully.
+ * @param aSink the RDF/XML sink that has finished loading.
+ */
+ void onEndLoad(in nsIRDFXMLSink aSink);
+
+ /**
+ * Called when an error occurs during the load
+ * @param aSink the RDF/XML sink in which the error occurred
+ * @param aStatus the networking result code
+ * @param aErrorMsg an error message, if applicable
+ */
+ void onError(in nsIRDFXMLSink aSink, in nsresult aStatus, in wstring aErrorMsg);
+};
+
+
+
+/**
+ * A "sink" that receives and processes RDF/XML. This interface is used
+ * by the RDF/XML parser.
+ */
+[scriptable, uuid(EB1A5D31-AB33-11D2-8EC6-00805F29F370)]
+interface nsIRDFXMLSink : nsISupports
+{
+ /**
+ * Set to <code>true</code> if the sink is read-only and cannot
+ * be modified
+ */
+ attribute boolean readOnly;
+
+ /**
+ * Initiate the RDF/XML load.
+ */
+ void beginLoad();
+
+ /**
+ * Suspend the RDF/XML load.
+ */
+ void interrupt();
+
+ /**
+ * Resume the RDF/XML load.
+ */
+ void resume();
+
+ /**
+ * Complete the RDF/XML load.
+ */
+ void endLoad();
+
+ /**
+ * Add namespace information to the RDF/XML sink.
+ * @param aPrefix the namespace prefix
+ * @param aURI the namespace URI
+ */
+ [noscript] void addNameSpace(in nsIAtomPtr aPrefix,
+ [const] in nsStringRef aURI);
+
+ /**
+ * Add an observer that will be notified as the RDF/XML load
+ * progresses.
+ * <p>
+ *
+ * Note that the sink will acquire a strong reference to the
+ * observer, so care should be taken to avoid cyclical references
+ * that cannot be released (i.e., if the observer holds a
+ * reference to the sink, it should be sure that it eventually
+ * clears the reference).
+ *
+ * @param aObserver the observer to add to the sink's set of
+ * load observers.
+ */
+ void addXMLSinkObserver(in nsIRDFXMLSinkObserver aObserver);
+
+ /**
+ * Remove an observer from the sink's set of observers.
+ * @param aObserver the observer to remove.
+ */
+ void removeXMLSinkObserver(in nsIRDFXMLSinkObserver aObserver);
+};
+
diff --git a/components/rdf/public/nsIRDFXMLSource.idl b/components/rdf/public/nsIRDFXMLSource.idl
new file mode 100644
index 000000000..794d8b662
--- /dev/null
+++ b/components/rdf/public/nsIRDFXMLSource.idl
@@ -0,0 +1,20 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIOutputStream.idl"
+
+[scriptable, uuid(4DA56F10-99FE-11d2-8EBB-00805F29F370)]
+interface nsIRDFXMLSource : nsISupports
+{
+ /**
+ * Serialize the contents of the datasource to aStream.
+ * @param aStream the output stream the will receive the
+ * RDF/XML. Currently, the output stream need only
+ * implement the |write()| method.
+ */
+ void Serialize(in nsIOutputStream aStream);
+};
+
diff --git a/components/rdf/public/rdfIDataSource.idl b/components/rdf/public/rdfIDataSource.idl
new file mode 100644
index 000000000..848cbca11
--- /dev/null
+++ b/components/rdf/public/rdfIDataSource.idl
@@ -0,0 +1,38 @@
+/* -*- Mode: IDL; 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 "nsISupports.idl"
+
+interface rdfITripleVisitor;
+
+/**
+ * Interface used in RDF to describe data sources.
+ *
+ * @status PLASMA
+ */
+
+[scriptable, uuid(ebce86bd-1568-4a34-a808-9ccf9cde8087)]
+interface rdfIDataSource : nsISupports
+{
+ /**
+ * Visit all the subject resources in the datasource. The order is
+ * intederminate and may change from one invocation to the next.
+ * The subjects will be in the aSubject argument in calls into
+ * aVisitor, aPredicate and aObject will be null.
+ * @note Implementations may throw NS_ERROR_NOT_IMPLEMENTED for
+ * this method, but in this case RDF serializations of this
+ * datasource will not be possible.
+ */
+ void visitAllSubjects(in rdfITripleVisitor aVisitor);
+
+ /**
+ * Visit all the triples in the datasource. The order is
+ * intederminate and may change from one invocation to the next.
+ * @note Implementations may throw NS_ERROR_NOT_IMPLEMENTED for
+ * this method, but in this case RDF serializations of this
+ * datasource will not be possible.
+ */
+ void visitAllTriples(in rdfITripleVisitor aVisitor);
+};
diff --git a/components/rdf/public/rdfISerializer.idl b/components/rdf/public/rdfISerializer.idl
new file mode 100644
index 000000000..9ec6b69ea
--- /dev/null
+++ b/components/rdf/public/rdfISerializer.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; 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 "nsISupports.idl"
+
+interface rdfIDataSource;
+interface nsIOutputStream;
+
+/**
+ * Interface used to serialize RDF.
+ *
+ * @status PLASMA
+ */
+
+[scriptable, uuid(f0edfcdd-8bca-4d32-9226-7421001396a4)]
+interface rdfISerializer : nsISupports
+{
+ /**
+ * Synchronously serialize the given datasource to the outputstream.
+ *
+ * Implementations are not required to implement any buffering or
+ * other stream-based optimizations.
+ *
+ * @param aDataSource The RDF data source to be serialized.
+ * @param aOut The output stream to use.
+ */
+ void serialize(in rdfIDataSource aDataSource, in nsIOutputStream aOut);
+};
diff --git a/components/rdf/public/rdfITripleVisitor.idl b/components/rdf/public/rdfITripleVisitor.idl
new file mode 100644
index 000000000..ecac14871
--- /dev/null
+++ b/components/rdf/public/rdfITripleVisitor.idl
@@ -0,0 +1,31 @@
+/* -*- Mode: IDL; 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 "nsISupports.idl"
+
+interface nsIRDFResource;
+interface nsIRDFNode;
+
+/**
+ * Interface used in RDF to enumerate triples.
+ * Also used by rdfIDataSource::getAllSubjects, then aPredicate,
+ * aObject and aTruthValue are ignored.
+ *
+ * @status PLASMA
+ */
+
+[scriptable, function, uuid(aafea151-c271-4505-9978-a100d292800c)]
+interface rdfITripleVisitor : nsISupports
+{
+ /**
+ * Callback function for returning query results.
+ *
+ * @param aSubject, aPredicate, aObject describe the (sub-)arc
+ * @returnCode NS_RDF_STOP_VISIT to stop iterating over the query result.
+ * Any error code will stop the iteration as well.
+ */
+ void visit(in nsIRDFNode aSubject, in nsIRDFResource aPredicate,
+ in nsIRDFNode aObject, in boolean aTruthValue);
+};
diff --git a/components/rdf/src/nsCompositeDataSource.cpp b/components/rdf/src/nsCompositeDataSource.cpp
new file mode 100644
index 000000000..37167d356
--- /dev/null
+++ b/components/rdf/src/nsCompositeDataSource.cpp
@@ -0,0 +1,1358 @@
+/* -*- 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/. */
+
+/*
+
+ A simple composite data source implementation. A composit data
+ source is just a strategy for combining individual data sources into
+ a collective graph.
+
+
+ 1) A composite data source holds a sequence of data sources. The set
+ of data sources can be specified during creation of the
+ database. Data sources can also be added/deleted from a database
+ later.
+
+ 2) The aggregation mechanism is based on simple super-positioning of
+ the graphs from the datasources. If there is a conflict (i.e.,
+ data source A has a true arc from foo to bar while data source B
+ has a false arc from foo to bar), the data source that it earlier
+ in the sequence wins.
+
+ The implementation below doesn't really do this and needs to be
+ fixed.
+
+*/
+
+#include "xpcom-config.h"
+#include "nsCOMPtr.h"
+#include "nsIComponentManager.h"
+#include "nsIRDFCompositeDataSource.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFObserver.h"
+#include "nsIRDFRemoteDataSource.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsXPIDLString.h"
+#include "rdf.h"
+#include "nsCycleCollectionParticipant.h"
+
+#include "nsEnumeratorUtils.h"
+
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include <stdio.h>
+mozilla::LazyLogModule nsRDFLog("RDF");
+
+//----------------------------------------------------------------------
+//
+// CompositeDataSourceImpl
+//
+
+class CompositeEnumeratorImpl;
+class CompositeArcsInOutEnumeratorImpl;
+class CompositeAssertionEnumeratorImpl;
+
+class CompositeDataSourceImpl : public nsIRDFCompositeDataSource,
+ public nsIRDFObserver
+{
+public:
+ CompositeDataSourceImpl(void);
+ explicit CompositeDataSourceImpl(char** dataSources);
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(CompositeDataSourceImpl,
+ nsIRDFCompositeDataSource)
+
+ // nsIRDFDataSource interface
+ NS_DECL_NSIRDFDATASOURCE
+
+ // nsIRDFCompositeDataSource interface
+ NS_DECL_NSIRDFCOMPOSITEDATASOURCE
+
+ // nsIRDFObserver interface
+ NS_DECL_NSIRDFOBSERVER
+
+ bool HasAssertionN(int n, nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv);
+
+protected:
+ nsCOMArray<nsIRDFObserver> mObservers;
+ nsCOMArray<nsIRDFDataSource> mDataSources;
+
+ bool mAllowNegativeAssertions;
+ bool mCoalesceDuplicateArcs;
+ int32_t mUpdateBatchNest;
+
+ virtual ~CompositeDataSourceImpl() {}
+
+ friend class CompositeEnumeratorImpl;
+ friend class CompositeArcsInOutEnumeratorImpl;
+ friend class CompositeAssertionEnumeratorImpl;
+};
+
+//----------------------------------------------------------------------
+//
+// CompositeEnumeratorImpl
+//
+
+class CompositeEnumeratorImpl : public nsISimpleEnumerator
+{
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator interface
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // pure abstract methods to be overridden
+ virtual nsresult
+ GetEnumerator(nsIRDFDataSource* aDataSource, nsISimpleEnumerator** aResult) = 0;
+
+ virtual nsresult
+ HasNegation(nsIRDFDataSource* aDataSource, nsIRDFNode* aNode, bool* aResult) = 0;
+
+protected:
+ CompositeEnumeratorImpl(CompositeDataSourceImpl* aCompositeDataSource,
+ bool aAllowNegativeAssertions,
+ bool aCoalesceDuplicateArcs);
+
+ virtual ~CompositeEnumeratorImpl();
+
+ CompositeDataSourceImpl* mCompositeDataSource;
+
+ nsISimpleEnumerator* mCurrent;
+ nsIRDFNode* mResult;
+ int32_t mNext;
+ AutoTArray<nsCOMPtr<nsIRDFNode>, 8> mAlreadyReturned;
+ bool mAllowNegativeAssertions;
+ bool mCoalesceDuplicateArcs;
+};
+
+
+CompositeEnumeratorImpl::CompositeEnumeratorImpl(CompositeDataSourceImpl* aCompositeDataSource,
+ bool aAllowNegativeAssertions,
+ bool aCoalesceDuplicateArcs)
+ : mCompositeDataSource(aCompositeDataSource),
+ mCurrent(nullptr),
+ mResult(nullptr),
+ mNext(0),
+ mAllowNegativeAssertions(aAllowNegativeAssertions),
+ mCoalesceDuplicateArcs(aCoalesceDuplicateArcs)
+{
+ NS_ADDREF(mCompositeDataSource);
+}
+
+
+CompositeEnumeratorImpl::~CompositeEnumeratorImpl(void)
+{
+ NS_IF_RELEASE(mCurrent);
+ NS_IF_RELEASE(mResult);
+ NS_RELEASE(mCompositeDataSource);
+}
+
+NS_IMPL_ADDREF(CompositeEnumeratorImpl)
+NS_IMPL_RELEASE(CompositeEnumeratorImpl)
+NS_IMPL_QUERY_INTERFACE(CompositeEnumeratorImpl, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+CompositeEnumeratorImpl::HasMoreElements(bool* aResult)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ // If we've already queued up a next target, then yep, there are
+ // more elements.
+ if (mResult) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ // Otherwise, we'll need to find a next target, switching cursors
+ // if necessary.
+ for ( ; mNext < mCompositeDataSource->mDataSources.Count(); ++mNext) {
+ if (! mCurrent) {
+ // We don't have a current enumerator, so create a new one on
+ // the next data source.
+ nsIRDFDataSource* datasource =
+ mCompositeDataSource->mDataSources[mNext];
+
+ rv = GetEnumerator(datasource, &mCurrent);
+ if (NS_FAILED(rv)) return rv;
+ if (rv == NS_RDF_NO_VALUE)
+ continue;
+
+ NS_ASSERTION(mCurrent != nullptr, "you're always supposed to return an enumerator from GetEnumerator, punk.");
+ if (! mCurrent)
+ continue;
+ }
+
+ do {
+ int32_t i;
+
+ bool hasMore;
+ rv = mCurrent->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+
+ // Is the current enumerator depleted?
+ if (! hasMore) {
+ NS_RELEASE(mCurrent);
+ break;
+ }
+
+ // Even if the current enumerator has more elements, we still
+ // need to check that the current element isn't masked by
+ // a negation in an earlier data source.
+
+ // "Peek" ahead and pull out the next target.
+ nsCOMPtr<nsISupports> result;
+ rv = mCurrent->GetNext(getter_AddRefs(result));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = result->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) &mResult);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mAllowNegativeAssertions)
+ {
+ // See if any previous data source negates this
+ bool hasNegation = false;
+ for (i = mNext - 1; i >= 0; --i)
+ {
+ nsIRDFDataSource* datasource =
+ mCompositeDataSource->mDataSources[i];
+
+ rv = HasNegation(datasource, mResult, &hasNegation);
+ if (NS_FAILED(rv)) return rv;
+
+ if (hasNegation)
+ break;
+ }
+
+ // if so, we've gotta keep looking
+ if (hasNegation)
+ {
+ NS_RELEASE(mResult);
+ continue;
+ }
+ }
+
+ if (mCoalesceDuplicateArcs)
+ {
+ // Now see if we've returned it once already.
+ // XXX N.B. performance here...may want to hash if things get large?
+ bool alreadyReturned = false;
+ for (i = mAlreadyReturned.Length() - 1; i >= 0; --i)
+ {
+ if (mAlreadyReturned[i] == mResult)
+ {
+ alreadyReturned = true;
+ break;
+ }
+ }
+ if (alreadyReturned)
+ {
+ NS_RELEASE(mResult);
+ continue;
+ }
+ }
+
+ // If we get here, then we've really found one. It'll
+ // remain cached in mResult until GetNext() sucks it out.
+ *aResult = true;
+
+ // Remember that we returned it, so we don't return duplicates.
+
+ // XXX I wonder if we should make unique-checking be
+ // optional. This could get to be pretty expensive (this
+ // implementation turns iteration into O(n^2)).
+
+ if (mCoalesceDuplicateArcs)
+ {
+ mAlreadyReturned.AppendElement(mResult);
+ }
+
+ return NS_OK;
+ } while (1);
+ }
+
+ // if we get here, there aren't any elements left.
+ *aResult = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+CompositeEnumeratorImpl::GetNext(nsISupports** aResult)
+{
+ nsresult rv;
+
+ bool hasMore;
+ rv = HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasMore)
+ return NS_ERROR_UNEXPECTED;
+
+ // Don't AddRef: we "transfer" ownership to the caller
+ *aResult = mResult;
+ mResult = nullptr;
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// CompositeArcsInOutEnumeratorImpl
+//
+//
+
+class CompositeArcsInOutEnumeratorImpl : public CompositeEnumeratorImpl
+{
+public:
+ enum Type { eArcsIn, eArcsOut };
+
+ virtual ~CompositeArcsInOutEnumeratorImpl();
+
+ virtual nsresult
+ GetEnumerator(nsIRDFDataSource* aDataSource, nsISimpleEnumerator** aResult);
+
+ virtual nsresult
+ HasNegation(nsIRDFDataSource* aDataSource, nsIRDFNode* aNode, bool* aResult);
+
+ CompositeArcsInOutEnumeratorImpl(CompositeDataSourceImpl* aCompositeDataSource,
+ nsIRDFNode* aNode,
+ Type aType,
+ bool aAllowNegativeAssertions,
+ bool aCoalesceDuplicateArcs);
+
+private:
+ nsIRDFNode* mNode;
+ Type mType;
+};
+
+
+CompositeArcsInOutEnumeratorImpl::CompositeArcsInOutEnumeratorImpl(
+ CompositeDataSourceImpl* aCompositeDataSource,
+ nsIRDFNode* aNode,
+ Type aType,
+ bool aAllowNegativeAssertions,
+ bool aCoalesceDuplicateArcs)
+ : CompositeEnumeratorImpl(aCompositeDataSource, aAllowNegativeAssertions, aCoalesceDuplicateArcs),
+ mNode(aNode),
+ mType(aType)
+{
+ NS_ADDREF(mNode);
+}
+
+CompositeArcsInOutEnumeratorImpl::~CompositeArcsInOutEnumeratorImpl()
+{
+ NS_RELEASE(mNode);
+}
+
+
+nsresult
+CompositeArcsInOutEnumeratorImpl::GetEnumerator(
+ nsIRDFDataSource* aDataSource,
+ nsISimpleEnumerator** aResult)
+{
+ if (mType == eArcsIn) {
+ return aDataSource->ArcLabelsIn(mNode, aResult);
+ }
+ else {
+ nsCOMPtr<nsIRDFResource> resource( do_QueryInterface(mNode) );
+ return aDataSource->ArcLabelsOut(resource, aResult);
+ }
+}
+
+nsresult
+CompositeArcsInOutEnumeratorImpl::HasNegation(
+ nsIRDFDataSource* aDataSource,
+ nsIRDFNode* aNode,
+ bool* aResult)
+{
+ *aResult = false;
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------
+//
+// CompositeAssertionEnumeratorImpl
+//
+
+class CompositeAssertionEnumeratorImpl : public CompositeEnumeratorImpl
+{
+public:
+ virtual nsresult
+ GetEnumerator(nsIRDFDataSource* aDataSource, nsISimpleEnumerator** aResult);
+
+ virtual nsresult
+ HasNegation(nsIRDFDataSource* aDataSource, nsIRDFNode* aNode, bool* aResult);
+
+ CompositeAssertionEnumeratorImpl(CompositeDataSourceImpl* aCompositeDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue,
+ bool aAllowNegativeAssertions,
+ bool aCoalesceDuplicateArcs);
+
+ virtual ~CompositeAssertionEnumeratorImpl();
+
+private:
+ nsIRDFResource* mSource;
+ nsIRDFResource* mProperty;
+ nsIRDFNode* mTarget;
+ bool mTruthValue;
+};
+
+
+CompositeAssertionEnumeratorImpl::CompositeAssertionEnumeratorImpl(
+ CompositeDataSourceImpl* aCompositeDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue,
+ bool aAllowNegativeAssertions,
+ bool aCoalesceDuplicateArcs)
+ : CompositeEnumeratorImpl(aCompositeDataSource, aAllowNegativeAssertions, aCoalesceDuplicateArcs),
+ mSource(aSource),
+ mProperty(aProperty),
+ mTarget(aTarget),
+ mTruthValue(aTruthValue)
+{
+ NS_IF_ADDREF(mSource);
+ NS_ADDREF(mProperty); // always must be specified
+ NS_IF_ADDREF(mTarget);
+}
+
+CompositeAssertionEnumeratorImpl::~CompositeAssertionEnumeratorImpl()
+{
+ NS_IF_RELEASE(mSource);
+ NS_RELEASE(mProperty);
+ NS_IF_RELEASE(mTarget);
+}
+
+
+nsresult
+CompositeAssertionEnumeratorImpl::GetEnumerator(
+ nsIRDFDataSource* aDataSource,
+ nsISimpleEnumerator** aResult)
+{
+ if (mSource) {
+ return aDataSource->GetTargets(mSource, mProperty, mTruthValue, aResult);
+ }
+ else {
+ return aDataSource->GetSources(mProperty, mTarget, mTruthValue, aResult);
+ }
+}
+
+nsresult
+CompositeAssertionEnumeratorImpl::HasNegation(
+ nsIRDFDataSource* aDataSource,
+ nsIRDFNode* aNode,
+ bool* aResult)
+{
+ if (mSource) {
+ return aDataSource->HasAssertion(mSource, mProperty, aNode, !mTruthValue, aResult);
+ }
+ else {
+ nsCOMPtr<nsIRDFResource> source( do_QueryInterface(aNode) );
+ return aDataSource->HasAssertion(source, mProperty, mTarget, !mTruthValue, aResult);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult
+NS_NewRDFCompositeDataSource(nsIRDFCompositeDataSource** result)
+{
+ CompositeDataSourceImpl* db = new CompositeDataSourceImpl();
+ if (! db)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ *result = db;
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+
+CompositeDataSourceImpl::CompositeDataSourceImpl(void)
+ : mAllowNegativeAssertions(true),
+ mCoalesceDuplicateArcs(true),
+ mUpdateBatchNest(0)
+{
+}
+
+//----------------------------------------------------------------------
+//
+// nsISupports interface
+//
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CompositeDataSourceImpl)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CompositeDataSourceImpl)
+ uint32_t i, count = tmp->mDataSources.Count();
+ for (i = count; i > 0; --i) {
+ tmp->mDataSources[i - 1]->RemoveObserver(tmp);
+ tmp->mDataSources.RemoveObjectAt(i - 1);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CompositeDataSourceImpl)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDataSources)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CompositeDataSourceImpl)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CompositeDataSourceImpl)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompositeDataSourceImpl)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFCompositeDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFCompositeDataSource)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRDFCompositeDataSource)
+NS_INTERFACE_MAP_END
+
+
+//----------------------------------------------------------------------
+//
+// nsIRDFDataSource interface
+//
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::GetURI(char* *uri)
+{
+ *uri = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::GetSource(nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ nsIRDFResource** source)
+{
+ if (!mAllowNegativeAssertions && !tv)
+ return(NS_RDF_NO_VALUE);
+
+ int32_t count = mDataSources.Count();
+ for (int32_t i = 0; i < count; ++i) {
+ nsresult rv;
+ rv = mDataSources[i]->GetSource(property, target, tv, source);
+ if (NS_FAILED(rv)) return rv;
+
+ if (rv == NS_RDF_NO_VALUE)
+ continue;
+
+ if (!mAllowNegativeAssertions) return(NS_OK);
+
+ // okay, found it. make sure we don't have the opposite
+ // asserted in a more local data source
+ if (!HasAssertionN(count-1, *source, property, target, !tv))
+ return NS_OK;
+
+ NS_RELEASE(*source);
+ return NS_RDF_NO_VALUE;
+ }
+ return NS_RDF_NO_VALUE;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::GetSources(nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue,
+ nsISimpleEnumerator** aResult)
+{
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ if (! mAllowNegativeAssertions && ! aTruthValue)
+ return(NS_RDF_NO_VALUE);
+
+ *aResult = new CompositeAssertionEnumeratorImpl(this, nullptr, aProperty,
+ aTarget, aTruthValue,
+ mAllowNegativeAssertions,
+ mCoalesceDuplicateArcs);
+
+ if (! *aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::GetTarget(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ bool aTruthValue,
+ nsIRDFNode** aResult)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ if (! mAllowNegativeAssertions && ! aTruthValue)
+ return(NS_RDF_NO_VALUE);
+
+ int32_t count = mDataSources.Count();
+ for (int32_t i = 0; i < count; ++i) {
+ nsresult rv;
+ rv = mDataSources[i]->GetTarget(aSource, aProperty, aTruthValue,
+ aResult);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (rv == NS_OK) {
+ // okay, found it. make sure we don't have the opposite
+ // asserted in an earlier data source
+
+ if (mAllowNegativeAssertions) {
+ if (HasAssertionN(count-1, aSource, aProperty, *aResult, !aTruthValue)) {
+ // whoops, it's been negated.
+ NS_RELEASE(*aResult);
+ return NS_RDF_NO_VALUE;
+ }
+ }
+ return NS_OK;
+ }
+ }
+
+ // Otherwise, we couldn't find it at all.
+ return NS_RDF_NO_VALUE;
+}
+
+bool
+CompositeDataSourceImpl::HasAssertionN(int n,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue)
+{
+ nsresult rv;
+ for (int32_t m = 0; m < n; ++m) {
+ bool result;
+ rv = mDataSources[m]->HasAssertion(aSource, aProperty, aTarget,
+ aTruthValue, &result);
+ if (NS_FAILED(rv))
+ return false;
+
+ // found it!
+ if (result)
+ return true;
+ }
+ return false;
+}
+
+
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::GetTargets(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ bool aTruthValue,
+ nsISimpleEnumerator** aResult)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ if (! mAllowNegativeAssertions && ! aTruthValue)
+ return(NS_RDF_NO_VALUE);
+
+ *aResult =
+ new CompositeAssertionEnumeratorImpl(this,
+ aSource, aProperty, nullptr,
+ aTruthValue,
+ mAllowNegativeAssertions,
+ mCoalesceDuplicateArcs);
+
+ if (! *aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::Assert(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ if (! mAllowNegativeAssertions && ! aTruthValue)
+ return(NS_RDF_ASSERTION_REJECTED);
+
+ nsresult rv;
+
+ // XXX Need to add back the stuff for unblocking ...
+
+ // We iterate backwards from the last data source which was added
+ // ("the most remote") to the first ("the most local"), trying to
+ // apply the assertion in each.
+ for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) {
+ rv = mDataSources[i]->Assert(aSource, aProperty, aTarget, aTruthValue);
+ if (NS_RDF_ASSERTION_ACCEPTED == rv)
+ return rv;
+
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // nobody wanted to accept it
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::Unassert(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ // Iterate through each of the datasources, starting with "the
+ // most local" and moving to "the most remote". If _any_ of the
+ // datasources have the assertion, attempt to unassert it.
+ bool unasserted = true;
+ int32_t i;
+ int32_t count = mDataSources.Count();
+ for (i = 0; i < count; ++i) {
+ nsIRDFDataSource* ds = mDataSources[i];
+
+ bool hasAssertion;
+ rv = ds->HasAssertion(aSource, aProperty, aTarget, true, &hasAssertion);
+ if (NS_FAILED(rv)) return rv;
+
+ if (hasAssertion) {
+ rv = ds->Unassert(aSource, aProperty, aTarget);
+ if (NS_FAILED(rv)) return rv;
+
+ if (rv != NS_RDF_ASSERTION_ACCEPTED) {
+ unasserted = false;
+ break;
+ }
+ }
+ }
+
+ // Either none of the datasources had it, or they were all willing
+ // to let it be unasserted.
+ if (unasserted)
+ return NS_RDF_ASSERTION_ACCEPTED;
+
+ // If we get here, one of the datasources already had the
+ // assertion, and was adamant about not letting us remove
+ // it. Iterate from the "most local" to the "most remote"
+ // attempting to assert the negation...
+ for (i = 0; i < count; ++i) {
+ rv = mDataSources[i]->Assert(aSource, aProperty, aTarget, false);
+ if (NS_FAILED(rv)) return rv;
+
+ // Did it take?
+ if (rv == NS_RDF_ASSERTION_ACCEPTED)
+ return rv;
+ }
+
+ // Couln't get anyone to accept the negation, either.
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::Change(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aOldTarget != nullptr, "null ptr");
+ if (! aOldTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aNewTarget != nullptr, "null ptr");
+ if (! aNewTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ // XXX So we're assuming that a datasource _must_ accept the
+ // atomic change; i.e., we can't split it up across two
+ // datasources. That sucks.
+
+ // We iterate backwards from the last data source which was added
+ // ("the most remote") to the first ("the most local"), trying to
+ // apply the change in each.
+ for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) {
+ rv = mDataSources[i]->Change(aSource, aProperty, aOldTarget, aNewTarget);
+ if (NS_RDF_ASSERTION_ACCEPTED == rv)
+ return rv;
+
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // nobody wanted to accept it
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::Move(nsIRDFResource* aOldSource,
+ nsIRDFResource* aNewSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ NS_PRECONDITION(aOldSource != nullptr, "null ptr");
+ if (! aOldSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aNewSource != nullptr, "null ptr");
+ if (! aNewSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ // XXX So we're assuming that a datasource _must_ accept the
+ // atomic move; i.e., we can't split it up across two
+ // datasources. That sucks.
+
+ // We iterate backwards from the last data source which was added
+ // ("the most remote") to the first ("the most local"), trying to
+ // apply the assertion in each.
+ for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) {
+ rv = mDataSources[i]->Move(aOldSource, aNewSource, aProperty, aTarget);
+ if (NS_RDF_ASSERTION_ACCEPTED == rv)
+ return rv;
+
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ // nobody wanted to accept it
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::HasAssertion(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue,
+ bool* aResult)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ if (! mAllowNegativeAssertions && ! aTruthValue)
+ {
+ *aResult = false;
+ return(NS_OK);
+ }
+
+ nsresult rv;
+
+ // Otherwise, look through all the data sources to see if anyone
+ // has the positive...
+ int32_t count = mDataSources.Count();
+ for (int32_t i = 0; i < count; ++i) {
+ nsIRDFDataSource* datasource = mDataSources[i];
+ rv = datasource->HasAssertion(aSource, aProperty, aTarget, aTruthValue, aResult);
+ if (NS_FAILED(rv)) return rv;
+
+ if (*aResult)
+ return NS_OK;
+
+ if (mAllowNegativeAssertions)
+ {
+ bool hasNegation;
+ rv = datasource->HasAssertion(aSource, aProperty, aTarget, !aTruthValue, &hasNegation);
+ if (NS_FAILED(rv)) return rv;
+
+ if (hasNegation)
+ {
+ *aResult = false;
+ return NS_OK;
+ }
+ }
+ }
+
+ // If we get here, nobody had the assertion at all
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::AddObserver(nsIRDFObserver* aObserver)
+{
+ NS_PRECONDITION(aObserver != nullptr, "null ptr");
+ if (! aObserver)
+ return NS_ERROR_NULL_POINTER;
+
+ // XXX ensure uniqueness?
+ mObservers.AppendObject(aObserver);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::RemoveObserver(nsIRDFObserver* aObserver)
+{
+ NS_PRECONDITION(aObserver != nullptr, "null ptr");
+ if (! aObserver)
+ return NS_ERROR_NULL_POINTER;
+
+ mObservers.RemoveObject(aObserver);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result)
+{
+ nsresult rv;
+ *result = false;
+ int32_t count = mDataSources.Count();
+ for (int32_t i = 0; i < count; ++i) {
+ rv = mDataSources[i]->HasArcIn(aNode, aArc, result);
+ if (NS_FAILED(rv)) return rv;
+ if (*result)
+ return NS_OK;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result)
+{
+ nsresult rv;
+ *result = false;
+ int32_t count = mDataSources.Count();
+ for (int32_t i = 0; i < count; ++i) {
+ rv = mDataSources[i]->HasArcOut(aSource, aArc, result);
+ if (NS_FAILED(rv)) return rv;
+ if (*result)
+ return NS_OK;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::ArcLabelsIn(nsIRDFNode* aTarget, nsISimpleEnumerator** aResult)
+{
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ nsISimpleEnumerator* result =
+ new CompositeArcsInOutEnumeratorImpl(this, aTarget,
+ CompositeArcsInOutEnumeratorImpl::eArcsIn,
+ mAllowNegativeAssertions,
+ mCoalesceDuplicateArcs);
+
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(result);
+ *aResult = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::ArcLabelsOut(nsIRDFResource* aSource,
+ nsISimpleEnumerator** aResult)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ nsISimpleEnumerator* result =
+ new CompositeArcsInOutEnumeratorImpl(this, aSource,
+ CompositeArcsInOutEnumeratorImpl::eArcsOut,
+ mAllowNegativeAssertions,
+ mCoalesceDuplicateArcs);
+
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(result);
+ *aResult = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::GetAllResources(nsISimpleEnumerator** aResult)
+{
+ NS_NOTYETIMPLEMENTED("CompositeDataSourceImpl::GetAllResources");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::GetAllCmds(nsIRDFResource* source,
+ nsISimpleEnumerator/*<nsIRDFResource>*/** result)
+{
+ nsresult rv;
+ nsCOMPtr<nsISimpleEnumerator> set;
+
+ for (int32_t i = 0; i < mDataSources.Count(); i++)
+ {
+ nsCOMPtr<nsISimpleEnumerator> dsCmds;
+
+ rv = mDataSources[i]->GetAllCmds(source, getter_AddRefs(dsCmds));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsISimpleEnumerator> tmp;
+ rv = NS_NewUnionEnumerator(getter_AddRefs(tmp), set, dsCmds);
+ set.swap(tmp);
+ if (NS_FAILED(rv)) return(rv);
+ }
+ }
+
+ set.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::IsCommandEnabled(nsISupports/* nsIRDFResource container */* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports/* nsIRDFResource container */* aArguments,
+ bool* aResult)
+{
+ nsresult rv;
+ for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) {
+ bool enabled = true;
+ rv = mDataSources[i]->IsCommandEnabled(aSources, aCommand, aArguments, &enabled);
+ if (NS_FAILED(rv) && (rv != NS_ERROR_NOT_IMPLEMENTED))
+ {
+ return(rv);
+ }
+
+ if (! enabled) {
+ *aResult = false;
+ return(NS_OK);
+ }
+ }
+ *aResult = true;
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::DoCommand(nsISupports/* nsIRDFResource container */* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports/* nsIRDFResource container */* aArguments)
+{
+ for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) {
+ nsresult rv = mDataSources[i]->DoCommand(aSources, aCommand, aArguments);
+ if (NS_FAILED(rv) && (rv != NS_ERROR_NOT_IMPLEMENTED))
+ {
+ return(rv); // all datasources must succeed
+ }
+ }
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::BeginUpdateBatch()
+{
+ for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) {
+ mDataSources[i]->BeginUpdateBatch();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::EndUpdateBatch()
+{
+ for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) {
+ mDataSources[i]->EndUpdateBatch();
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+// nsIRDFCompositeDataSource methods
+// XXX rvg We should make this take an additional argument specifying where
+// in the sequence of data sources (of the db), the new data source should
+// fit in. Right now, the new datasource gets stuck at the end.
+// need to add the observers of the CompositeDataSourceImpl to the new data source.
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::GetAllowNegativeAssertions(bool *aAllowNegativeAssertions)
+{
+ *aAllowNegativeAssertions = mAllowNegativeAssertions;
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::SetAllowNegativeAssertions(bool aAllowNegativeAssertions)
+{
+ mAllowNegativeAssertions = aAllowNegativeAssertions;
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::GetCoalesceDuplicateArcs(bool *aCoalesceDuplicateArcs)
+{
+ *aCoalesceDuplicateArcs = mCoalesceDuplicateArcs;
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::SetCoalesceDuplicateArcs(bool aCoalesceDuplicateArcs)
+{
+ mCoalesceDuplicateArcs = aCoalesceDuplicateArcs;
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::AddDataSource(nsIRDFDataSource* aDataSource)
+{
+ NS_ASSERTION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ mDataSources.AppendObject(aDataSource);
+ aDataSource->AddObserver(this);
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::RemoveDataSource(nsIRDFDataSource* aDataSource)
+{
+ NS_ASSERTION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+
+ if (mDataSources.IndexOf(aDataSource) >= 0) {
+ aDataSource->RemoveObserver(this);
+ mDataSources.RemoveObject(aDataSource);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::GetDataSources(nsISimpleEnumerator** _result)
+{
+ // NS_NewArrayEnumerator for an nsCOMArray takes a snapshot of the
+ // current state.
+ return NS_NewArrayEnumerator(_result, mDataSources);
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::OnAssert(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ // Make sure that the assertion isn't masked by another
+ // datasource.
+ //
+ // XXX We could make this more efficient if we knew _which_
+ // datasource actually served up the OnAssert(): we could use
+ // HasAssertionN() to only search datasources _before_ the
+ // datasource that coughed up the assertion.
+ nsresult rv = NS_OK;
+
+ if (mAllowNegativeAssertions)
+ {
+ bool hasAssertion;
+ rv = HasAssertion(aSource, aProperty, aTarget, true, &hasAssertion);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasAssertion)
+ return(NS_OK);
+ }
+
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ mObservers[i]->OnAssert(this, aSource, aProperty, aTarget);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::OnUnassert(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ // Make sure that the un-assertion doesn't just unmask the
+ // same assertion in a different datasource.
+ //
+ // XXX We could make this more efficient if we knew _which_
+ // datasource actually served up the OnAssert(): we could use
+ // HasAssertionN() to only search datasources _before_ the
+ // datasource that coughed up the assertion.
+ nsresult rv;
+
+ if (mAllowNegativeAssertions)
+ {
+ bool hasAssertion;
+ rv = HasAssertion(aSource, aProperty, aTarget, true, &hasAssertion);
+ if (NS_FAILED(rv)) return rv;
+
+ if (hasAssertion)
+ return NS_OK;
+ }
+
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ mObservers[i]->OnUnassert(this, aSource, aProperty, aTarget);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::OnChange(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget)
+{
+ // Make sure that the change is actually visible, and not hidden
+ // by an assertion in a different datasource.
+ //
+ // XXX Because of aggregation, this could actually mutate into a
+ // variety of OnAssert or OnChange notifications, which we'll
+ // ignore for now :-/.
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ mObservers[i]->OnChange(this, aSource, aProperty,
+ aOldTarget, aNewTarget);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::OnMove(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aOldSource,
+ nsIRDFResource* aNewSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ // Make sure that the move is actually visible, and not hidden
+ // by an assertion in a different datasource.
+ //
+ // XXX Because of aggregation, this could actually mutate into a
+ // variety of OnAssert or OnMove notifications, which we'll
+ // ignore for now :-/.
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ mObservers[i]->OnMove(this, aOldSource, aNewSource,
+ aProperty, aTarget);
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::OnBeginUpdateBatch(nsIRDFDataSource* aDataSource)
+{
+ if (mUpdateBatchNest++ == 0) {
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ mObservers[i]->OnBeginUpdateBatch(this);
+ }
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+CompositeDataSourceImpl::OnEndUpdateBatch(nsIRDFDataSource* aDataSource)
+{
+ NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch");
+ if (--mUpdateBatchNest == 0) {
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ mObservers[i]->OnEndUpdateBatch(this);
+ }
+ }
+ return NS_OK;
+}
diff --git a/components/rdf/src/nsContainerEnumerator.cpp b/components/rdf/src/nsContainerEnumerator.cpp
new file mode 100644
index 000000000..f1bfc6454
--- /dev/null
+++ b/components/rdf/src/nsContainerEnumerator.cpp
@@ -0,0 +1,263 @@
+/* -*- 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/. */
+
+/*
+
+ A simple cursor that enumerates the elements of an RDF container
+ (RDF:Bag, RDF:Seq, or RDF:Alt).
+
+ Caveats
+ -------
+
+ 1. This uses an implementation-specific detail to determine the
+ index of the last element in the container; specifically, the RDF
+ utilities maintain a counter attribute on the container that
+ holds the numeric value of the next value that is to be
+ assigned. So, this cursor will bust if you use it with a bag that
+ hasn't been created using the RDF utility routines.
+
+ */
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFService.h"
+#include "nsIServiceManager.h"
+#include "nsRDFCID.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "mozilla/Logging.h"
+#include "rdf.h"
+#include "rdfutil.h"
+
+////////////////////////////////////////////////////////////////////////
+
+class ContainerEnumeratorImpl : public nsISimpleEnumerator {
+private:
+ // pseudo-constants
+ static nsrefcnt gRefCnt;
+ static nsIRDFResource* kRDF_nextVal;
+ static nsIRDFContainerUtils* gRDFC;
+
+ nsCOMPtr<nsIRDFDataSource> mDataSource;
+ nsCOMPtr<nsIRDFResource> mContainer;
+ nsCOMPtr<nsIRDFResource> mOrdinalProperty;
+
+ nsCOMPtr<nsISimpleEnumerator> mCurrent;
+ nsCOMPtr<nsIRDFNode> mResult;
+ int32_t mNextIndex;
+
+ virtual ~ContainerEnumeratorImpl();
+
+public:
+ ContainerEnumeratorImpl(nsIRDFDataSource* ds, nsIRDFResource* container);
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+};
+
+nsrefcnt ContainerEnumeratorImpl::gRefCnt;
+nsIRDFResource* ContainerEnumeratorImpl::kRDF_nextVal;
+nsIRDFContainerUtils* ContainerEnumeratorImpl::gRDFC;
+
+
+ContainerEnumeratorImpl::ContainerEnumeratorImpl(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aContainer)
+ : mDataSource(aDataSource),
+ mContainer(aContainer),
+ mNextIndex(1)
+{
+}
+
+nsresult
+ContainerEnumeratorImpl::Init()
+{
+ if (gRefCnt++ == 0) {
+ nsresult rv;
+
+ NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+ nsCOMPtr<nsIRDFService> rdf = do_GetService(kRDFServiceCID);
+ NS_ASSERTION(rdf != nullptr, "unable to acquire resource manager");
+ if (! rdf)
+ return NS_ERROR_FAILURE;
+
+ rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"), &kRDF_nextVal);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get resource");
+ if (NS_FAILED(rv)) return rv;
+
+ NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID);
+ rv = CallGetService(kRDFContainerUtilsCID, &gRDFC);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+
+ContainerEnumeratorImpl::~ContainerEnumeratorImpl()
+{
+ if (--gRefCnt == 0) {
+ NS_IF_RELEASE(kRDF_nextVal);
+ NS_IF_RELEASE(gRDFC);
+ }
+}
+
+NS_IMPL_ISUPPORTS(ContainerEnumeratorImpl, nsISimpleEnumerator)
+
+
+NS_IMETHODIMP
+ContainerEnumeratorImpl::HasMoreElements(bool* aResult)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ // If we've already queued up a next value, then we know there are more elements.
+ if (mResult) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ // Otherwise, we need to grovel
+
+ // Figure out the upper bound so we'll know when we're done. Since it's
+ // possible that we're targeting a composite datasource, we'll need to
+ // "GetTargets()" and take the maximum value of "nextVal" to know the
+ // upper bound.
+ //
+ // Remember that since nextVal is the next index that we'd assign
+ // to an element in a container, it's *one more* than count of
+ // elements in the container.
+ int32_t max = 0;
+
+ nsCOMPtr<nsISimpleEnumerator> targets;
+ rv = mDataSource->GetTargets(mContainer, kRDF_nextVal, true, getter_AddRefs(targets));
+ if (NS_FAILED(rv)) return rv;
+
+ while (1) {
+ bool hasmore;
+ targets->HasMoreElements(&hasmore);
+ if (! hasmore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ targets->GetNext(getter_AddRefs(isupports));
+
+ nsCOMPtr<nsIRDFLiteral> nextValLiteral = do_QueryInterface(isupports);
+ if (! nextValLiteral)
+ continue;
+
+ const char16_t *nextValStr;
+ nextValLiteral->GetValueConst(&nextValStr);
+
+ nsresult err;
+ int32_t nextVal = nsAutoString(nextValStr).ToInteger(&err);
+
+ if (nextVal > max)
+ max = nextVal;
+ }
+
+ // Now pre-fetch our next value into mResult.
+ while (mCurrent || mNextIndex < max) {
+
+ // If mCurrent has been depleted, then conjure up a new one
+ if (! mCurrent) {
+ rv = gRDFC->IndexToOrdinalResource(mNextIndex, getter_AddRefs(mOrdinalProperty));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mDataSource->GetTargets(mContainer, mOrdinalProperty, true, getter_AddRefs(mCurrent));
+ if (NS_FAILED(rv)) return rv;
+
+ ++mNextIndex;
+ }
+
+ if (mCurrent) {
+ bool hasMore;
+ rv = mCurrent->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+
+ // Is the current enumerator depleted? If so, iterate to
+ // the next index.
+ if (! hasMore) {
+ mCurrent = nullptr;
+ continue;
+ }
+
+ // "Peek" ahead and pull out the next target.
+ nsCOMPtr<nsISupports> result;
+ rv = mCurrent->GetNext(getter_AddRefs(result));
+ if (NS_FAILED(rv)) return rv;
+
+ mResult = do_QueryInterface(result, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+
+ // If we get here, we ran out of elements. The cursor is empty.
+ *aResult = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+ContainerEnumeratorImpl::GetNext(nsISupports** aResult)
+{
+ nsresult rv;
+
+ bool hasMore;
+ rv = HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasMore)
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ADDREF(*aResult = mResult);
+ mResult = nullptr;
+
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult
+NS_NewContainerEnumerator(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aContainer,
+ nsISimpleEnumerator** aResult)
+{
+ NS_PRECONDITION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aContainer != nullptr, "null ptr");
+ if (! aContainer)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ ContainerEnumeratorImpl* result = new ContainerEnumeratorImpl(aDataSource, aContainer);
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(result);
+
+ nsresult rv = result->Init();
+ if (NS_FAILED(rv))
+ NS_RELEASE(result);
+
+ *aResult = result;
+ return rv;
+}
diff --git a/components/rdf/src/nsDefaultResourceFactory.cpp b/components/rdf/src/nsDefaultResourceFactory.cpp
new file mode 100644
index 000000000..448822a9d
--- /dev/null
+++ b/components/rdf/src/nsDefaultResourceFactory.cpp
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+/*
+
+ The default resource factory implementation. This resource factory
+ produces nsIRDFResource objects for any URI prefix that is not
+ covered by some other factory.
+
+ */
+
+#include "nsRDFResource.h"
+
+nsresult
+NS_NewDefaultResource(nsIRDFResource** aResult)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ nsRDFResource* resource = new nsRDFResource();
+ if (! resource)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(resource);
+ *aResult = resource;
+ return NS_OK;
+}
diff --git a/components/rdf/src/nsFileSystemDataSource.cpp b/components/rdf/src/nsFileSystemDataSource.cpp
new file mode 100644
index 000000000..c67656cd9
--- /dev/null
+++ b/components/rdf/src/nsFileSystemDataSource.cpp
@@ -0,0 +1,1328 @@
+/* -*- 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/. */
+
+/*
+ Implementation for a file system RDF data store.
+ */
+
+#include "nsFileSystemDataSource.h"
+
+#include <ctype.h> // for toupper()
+#include <stdio.h>
+#include "nsArrayEnumerator.h"
+#include "nsCOMArray.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFObserver.h"
+#include "nsIServiceManager.h"
+#include "nsXPIDLString.h"
+#include "nsRDFCID.h"
+#include "rdfutil.h"
+#include "rdf.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIURL.h"
+#include "nsIFileURL.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsIFile.h"
+#include "nsEscape.h"
+#include "nsCRTGlue.h"
+#include "nsAutoPtr.h"
+#include "prtime.h"
+
+#ifdef XP_WIN
+#include "windef.h"
+#include "winbase.h"
+#include "nsILineInputStream.h"
+#include "nsDirectoryServiceDefs.h"
+#endif
+
+#define NS_MOZICON_SCHEME "moz-icon:"
+
+static const char kFileProtocol[] = "file://";
+
+bool
+FileSystemDataSource::isFileURI(nsIRDFResource *r)
+{
+ bool isFileURIFlag = false;
+ const char *uri = nullptr;
+
+ r->GetValueConst(&uri);
+ if ((uri) && (!strncmp(uri, kFileProtocol, sizeof(kFileProtocol) - 1)))
+ {
+ // XXX HACK HACK HACK
+ if (!strchr(uri, '#'))
+ {
+ isFileURIFlag = true;
+ }
+ }
+ return(isFileURIFlag);
+}
+
+
+
+bool
+FileSystemDataSource::isDirURI(nsIRDFResource* source)
+{
+ nsresult rv;
+ const char *uri = nullptr;
+
+ rv = source->GetValueConst(&uri);
+ if (NS_FAILED(rv)) return(false);
+
+ nsCOMPtr<nsIFile> aDir;
+
+ rv = NS_GetFileFromURLSpec(nsDependentCString(uri), getter_AddRefs(aDir));
+ if (NS_FAILED(rv)) return(false);
+
+ bool isDirFlag = false;
+
+ rv = aDir->IsDirectory(&isDirFlag);
+ if (NS_FAILED(rv)) return(false);
+
+ return(isDirFlag);
+}
+
+
+nsresult
+FileSystemDataSource::Init()
+{
+ nsresult rv;
+
+ mRDFService = do_GetService("@mozilla.org/rdf/rdf-service;1");
+ NS_ENSURE_TRUE(mRDFService, NS_ERROR_FAILURE);
+
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING("NC:FilesRoot"),
+ getter_AddRefs(mNC_FileSystemRoot));
+ nsresult tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"),
+ getter_AddRefs(mNC_Child));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"),
+ getter_AddRefs(mNC_Name));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "URL"),
+ getter_AddRefs(mNC_URL));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Icon"),
+ getter_AddRefs(mNC_Icon));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Content-Length"),
+ getter_AddRefs(mNC_Length));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "IsDirectory"),
+ getter_AddRefs(mNC_IsDirectory));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(WEB_NAMESPACE_URI "LastModifiedDate"),
+ getter_AddRefs(mWEB_LastMod));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "FileSystemObject"),
+ getter_AddRefs(mNC_FileSystemObject));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "pulse"),
+ getter_AddRefs(mNC_pulse));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"),
+ getter_AddRefs(mRDF_InstanceOf));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"),
+ getter_AddRefs(mRDF_type));
+
+ static const char16_t kTrue[] = {'t','r','u','e','\0'};
+ static const char16_t kFalse[] = {'f','a','l','s','e','\0'};
+
+ tmp = mRDFService->GetLiteral(kTrue, getter_AddRefs(mLiteralTrue));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = mRDFService->GetLiteral(kFalse, getter_AddRefs(mLiteralFalse));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+#ifdef USE_NC_EXTENSION
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "extension"),
+ getter_AddRefs(mNC_extension));
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+
+#ifdef XP_WIN
+ rv = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "IEFavorite"),
+ getter_AddRefs(mNC_IEFavoriteObject));
+ tmp = mRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "IEFavoriteFolder"),
+ getter_AddRefs(mNC_IEFavoriteFolder));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIFile> file;
+ NS_GetSpecialDirectory(NS_WIN_FAVORITES_DIR, getter_AddRefs(file));
+ if (file)
+ {
+ nsCOMPtr<nsIURI> furi;
+ NS_NewFileURI(getter_AddRefs(furi), file);
+ NS_ENSURE_TRUE(furi, NS_ERROR_FAILURE);
+
+ file->GetPersistentDescriptor(ieFavoritesDir);
+ }
+#endif
+
+ return NS_OK;
+}
+
+//static
+nsresult
+FileSystemDataSource::Create(nsISupports* aOuter, const nsIID& aIID, void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ RefPtr<FileSystemDataSource> self = new FileSystemDataSource();
+ if (!self)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = self->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return self->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS(FileSystemDataSource, nsIRDFDataSource)
+
+NS_IMETHODIMP
+FileSystemDataSource::GetURI(char **uri)
+{
+ NS_PRECONDITION(uri != nullptr, "null ptr");
+ if (! uri)
+ return NS_ERROR_NULL_POINTER;
+
+ if ((*uri = NS_strdup("rdf:files")) == nullptr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::GetSource(nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ nsIRDFResource** source /* out */)
+{
+ NS_PRECONDITION(property != nullptr, "null ptr");
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(target != nullptr, "null ptr");
+ if (! target)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ *source = nullptr;
+ return NS_RDF_NO_VALUE;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::GetSources(nsIRDFResource *property,
+ nsIRDFNode *target,
+ bool tv,
+ nsISimpleEnumerator **sources /* out */)
+{
+// NS_NOTYETIMPLEMENTED("write me");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::GetTarget(nsIRDFResource *source,
+ nsIRDFResource *property,
+ bool tv,
+ nsIRDFNode **target /* out */)
+{
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(property != nullptr, "null ptr");
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(target != nullptr, "null ptr");
+ if (! target)
+ return NS_ERROR_NULL_POINTER;
+
+ *target = nullptr;
+
+ nsresult rv = NS_RDF_NO_VALUE;
+
+ // we only have positive assertions in the file system data source.
+ if (! tv)
+ return NS_RDF_NO_VALUE;
+
+ if (source == mNC_FileSystemRoot)
+ {
+ if (property == mNC_pulse)
+ {
+ nsIRDFLiteral *pulseLiteral;
+ mRDFService->GetLiteral(u"12", &pulseLiteral);
+ *target = pulseLiteral;
+ return NS_OK;
+ }
+ }
+ else if (isFileURI(source))
+ {
+ if (property == mNC_Name)
+ {
+ nsCOMPtr<nsIRDFLiteral> name;
+ rv = GetName(source, getter_AddRefs(name));
+ if (NS_FAILED(rv)) return(rv);
+ if (!name) rv = NS_RDF_NO_VALUE;
+ if (rv == NS_RDF_NO_VALUE) return(rv);
+ return name->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+ else if (property == mNC_URL)
+ {
+ nsCOMPtr<nsIRDFLiteral> url;
+ rv = GetURL(source, nullptr, getter_AddRefs(url));
+ if (NS_FAILED(rv)) return(rv);
+ if (!url) rv = NS_RDF_NO_VALUE;
+ if (rv == NS_RDF_NO_VALUE) return(rv);
+
+ return url->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+ else if (property == mNC_Icon)
+ {
+ nsCOMPtr<nsIRDFLiteral> url;
+ bool isFavorite = false;
+ rv = GetURL(source, &isFavorite, getter_AddRefs(url));
+ if (NS_FAILED(rv)) return(rv);
+ if (isFavorite || !url) rv = NS_RDF_NO_VALUE;
+ if (rv == NS_RDF_NO_VALUE) return(rv);
+
+ const char16_t *uni = nullptr;
+ url->GetValueConst(&uni);
+ if (!uni) return(NS_RDF_NO_VALUE);
+ nsAutoString urlStr;
+ urlStr.AssignLiteral(NS_MOZICON_SCHEME);
+ urlStr.Append(uni);
+
+ rv = mRDFService->GetLiteral(urlStr.get(), getter_AddRefs(url));
+ if (NS_FAILED(rv) || !url) return(NS_RDF_NO_VALUE);
+ return url->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+ else if (property == mNC_Length)
+ {
+ nsCOMPtr<nsIRDFInt> fileSize;
+ rv = GetFileSize(source, getter_AddRefs(fileSize));
+ if (NS_FAILED(rv)) return(rv);
+ if (!fileSize) rv = NS_RDF_NO_VALUE;
+ if (rv == NS_RDF_NO_VALUE) return(rv);
+
+ return fileSize->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+ else if (property == mNC_IsDirectory)
+ {
+ *target = (isDirURI(source)) ? mLiteralTrue : mLiteralFalse;
+ NS_ADDREF(*target);
+ return NS_OK;
+ }
+ else if (property == mWEB_LastMod)
+ {
+ nsCOMPtr<nsIRDFDate> lastMod;
+ rv = GetLastMod(source, getter_AddRefs(lastMod));
+ if (NS_FAILED(rv)) return(rv);
+ if (!lastMod) rv = NS_RDF_NO_VALUE;
+ if (rv == NS_RDF_NO_VALUE) return(rv);
+
+ return lastMod->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+ else if (property == mRDF_type)
+ {
+ nsCString type;
+ rv = mNC_FileSystemObject->GetValueUTF8(type);
+ if (NS_FAILED(rv)) return(rv);
+
+#ifdef XP_WIN
+ // under Windows, if its an IE favorite, return that type
+ if (!ieFavoritesDir.IsEmpty())
+ {
+ nsCString uri;
+ rv = source->GetValueUTF8(uri);
+ if (NS_FAILED(rv)) return(rv);
+
+ NS_ConvertUTF8toUTF16 theURI(uri);
+
+ if (theURI.Find(ieFavoritesDir) == 0)
+ {
+ if (theURI[theURI.Length() - 1] == '/')
+ {
+ rv = mNC_IEFavoriteFolder->GetValueUTF8(type);
+ }
+ else
+ {
+ rv = mNC_IEFavoriteObject->GetValueUTF8(type);
+ }
+ if (NS_FAILED(rv)) return(rv);
+ }
+ }
+#endif
+
+ NS_ConvertUTF8toUTF16 url(type);
+ nsCOMPtr<nsIRDFLiteral> literal;
+ mRDFService->GetLiteral(url.get(), getter_AddRefs(literal));
+ rv = literal->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ return(rv);
+ }
+ else if (property == mNC_pulse)
+ {
+ nsCOMPtr<nsIRDFLiteral> pulseLiteral;
+ mRDFService->GetLiteral(u"12", getter_AddRefs(pulseLiteral));
+ rv = pulseLiteral->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ return(rv);
+ }
+ else if (property == mNC_Child)
+ {
+ // Oh this is evil. Somebody kill me now.
+ nsCOMPtr<nsISimpleEnumerator> children;
+ rv = GetFolderList(source, false, true, getter_AddRefs(children));
+ if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE)) return(rv);
+
+ bool hasMore;
+ rv = children->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return(rv);
+
+ if (hasMore)
+ {
+ nsCOMPtr<nsISupports> isupports;
+ rv = children->GetNext(getter_AddRefs(isupports));
+ if (NS_FAILED(rv)) return(rv);
+
+ return isupports->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+ }
+#ifdef USE_NC_EXTENSION
+ else if (property == mNC_extension)
+ {
+ nsCOMPtr<nsIRDFLiteral> extension;
+ rv = GetExtension(source, getter_AddRefs(extension));
+ if (!extension) rv = NS_RDF_NO_VALUE;
+ if (rv == NS_RDF_NO_VALUE) return(rv);
+ return extension->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) target);
+ }
+#endif
+ }
+
+ return(NS_RDF_NO_VALUE);
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::GetTargets(nsIRDFResource *source,
+ nsIRDFResource *property,
+ bool tv,
+ nsISimpleEnumerator **targets /* out */)
+{
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(property != nullptr, "null ptr");
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(targets != nullptr, "null ptr");
+ if (! targets)
+ return NS_ERROR_NULL_POINTER;
+
+ *targets = nullptr;
+
+ // we only have positive assertions in the file system data source.
+ if (! tv)
+ return NS_RDF_NO_VALUE;
+
+ nsresult rv;
+
+ if (source == mNC_FileSystemRoot)
+ {
+ if (property == mNC_Child)
+ {
+ return GetVolumeList(targets);
+ }
+ else if (property == mNC_pulse)
+ {
+ nsCOMPtr<nsIRDFLiteral> pulseLiteral;
+ mRDFService->GetLiteral(u"12",
+ getter_AddRefs(pulseLiteral));
+ return NS_NewSingletonEnumerator(targets, pulseLiteral);
+ }
+ }
+ else if (isFileURI(source))
+ {
+ if (property == mNC_Child)
+ {
+ return GetFolderList(source, false, false, targets);
+ }
+ else if (property == mNC_Name)
+ {
+ nsCOMPtr<nsIRDFLiteral> name;
+ rv = GetName(source, getter_AddRefs(name));
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_NewSingletonEnumerator(targets, name);
+ }
+ else if (property == mNC_URL)
+ {
+ nsCOMPtr<nsIRDFLiteral> url;
+ rv = GetURL(source, nullptr, getter_AddRefs(url));
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_NewSingletonEnumerator(targets, url);
+ }
+ else if (property == mRDF_type)
+ {
+ nsCString uri;
+ rv = mNC_FileSystemObject->GetValueUTF8(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ConvertUTF8toUTF16 url(uri);
+
+ nsCOMPtr<nsIRDFLiteral> literal;
+ rv = mRDFService->GetLiteral(url.get(), getter_AddRefs(literal));
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_NewSingletonEnumerator(targets, literal);
+ }
+ else if (property == mNC_pulse)
+ {
+ nsCOMPtr<nsIRDFLiteral> pulseLiteral;
+ rv = mRDFService->GetLiteral(u"12",
+ getter_AddRefs(pulseLiteral));
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_NewSingletonEnumerator(targets, pulseLiteral);
+ }
+ }
+
+ return NS_NewEmptyEnumerator(targets);
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::Assert(nsIRDFResource *source,
+ nsIRDFResource *property,
+ nsIRDFNode *target,
+ bool tv)
+{
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::Unassert(nsIRDFResource *source,
+ nsIRDFResource *property,
+ nsIRDFNode *target)
+{
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::Change(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget)
+{
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::Move(nsIRDFResource* aOldSource,
+ nsIRDFResource* aNewSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ return NS_RDF_ASSERTION_REJECTED;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::HasAssertion(nsIRDFResource *source,
+ nsIRDFResource *property,
+ nsIRDFNode *target,
+ bool tv,
+ bool *hasAssertion /* out */)
+{
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(property != nullptr, "null ptr");
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(target != nullptr, "null ptr");
+ if (! target)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(hasAssertion != nullptr, "null ptr");
+ if (! hasAssertion)
+ return NS_ERROR_NULL_POINTER;
+
+ // we only have positive assertions in the file system data source.
+ *hasAssertion = false;
+
+ if (! tv) {
+ return NS_OK;
+ }
+
+ if ((source == mNC_FileSystemRoot) || isFileURI(source))
+ {
+ if (property == mRDF_type)
+ {
+ nsCOMPtr<nsIRDFResource> resource( do_QueryInterface(target) );
+ if (resource.get() == mRDF_type)
+ {
+ *hasAssertion = true;
+ }
+ }
+#ifdef USE_NC_EXTENSION
+ else if (property == mNC_extension)
+ {
+ // Cheat just a little here by making dirs always match
+ if (isDirURI(source))
+ {
+ *hasAssertion = true;
+ }
+ else
+ {
+ nsCOMPtr<nsIRDFLiteral> extension;
+ GetExtension(source, getter_AddRefs(extension));
+ if (extension.get() == target)
+ {
+ *hasAssertion = true;
+ }
+ }
+ }
+#endif
+ else if (property == mNC_IsDirectory)
+ {
+ bool isDir = isDirURI(source);
+ bool isEqual = false;
+ target->EqualsNode(mLiteralTrue, &isEqual);
+ if (isEqual)
+ {
+ *hasAssertion = isDir;
+ }
+ else
+ {
+ target->EqualsNode(mLiteralFalse, &isEqual);
+ if (isEqual)
+ *hasAssertion = !isDir;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result)
+{
+ *result = false;
+
+ if (aSource == mNC_FileSystemRoot)
+ {
+ *result = (aArc == mNC_Child || aArc == mNC_pulse);
+ }
+ else if (isFileURI(aSource))
+ {
+ if (aArc == mNC_pulse)
+ {
+ *result = true;
+ }
+ else if (isDirURI(aSource))
+ {
+#ifdef XP_WIN
+ *result = isValidFolder(aSource);
+#else
+ *result = true;
+#endif
+ }
+ else if (aArc == mNC_pulse || aArc == mNC_Name || aArc == mNC_Icon ||
+ aArc == mNC_URL || aArc == mNC_Length || aArc == mWEB_LastMod ||
+ aArc == mNC_FileSystemObject || aArc == mRDF_InstanceOf ||
+ aArc == mRDF_type)
+ {
+ *result = true;
+ }
+ }
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::ArcLabelsIn(nsIRDFNode *node,
+ nsISimpleEnumerator ** labels /* out */)
+{
+// NS_NOTYETIMPLEMENTED("write me");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::ArcLabelsOut(nsIRDFResource *source,
+ nsISimpleEnumerator **labels /* out */)
+{
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(labels != nullptr, "null ptr");
+ if (! labels)
+ return NS_ERROR_NULL_POINTER;
+
+ if (source == mNC_FileSystemRoot)
+ {
+ nsCOMArray<nsIRDFResource> resources;
+ resources.SetCapacity(2);
+
+ resources.AppendObject(mNC_Child);
+ resources.AppendObject(mNC_pulse);
+
+ return NS_NewArrayEnumerator(labels, resources);
+ }
+ else if (isFileURI(source))
+ {
+ nsCOMArray<nsIRDFResource> resources;
+ resources.SetCapacity(2);
+
+ if (isDirURI(source))
+ {
+#ifdef XP_WIN
+ if (isValidFolder(source))
+ {
+ resources.AppendObject(mNC_Child);
+ }
+#else
+ resources.AppendObject(mNC_Child);
+#endif
+ resources.AppendObject(mNC_pulse);
+ }
+
+ return NS_NewArrayEnumerator(labels, resources);
+ }
+
+ return NS_NewEmptyEnumerator(labels);
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::GetAllResources(nsISimpleEnumerator** aCursor)
+{
+ NS_NOTYETIMPLEMENTED("sorry!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::AddObserver(nsIRDFObserver *n)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::RemoveObserver(nsIRDFObserver *n)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::GetAllCmds(nsIRDFResource* source,
+ nsISimpleEnumerator/*<nsIRDFResource>*/** commands)
+{
+ return(NS_NewEmptyEnumerator(commands));
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::IsCommandEnabled(nsISupports/*<nsIRDFResource>*/* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports/*<nsIRDFResource>*/* aArguments,
+ bool* aResult)
+{
+ return(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::DoCommand(nsISupports/*<nsIRDFResource>*/* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports/*<nsIRDFResource>*/* aArguments)
+{
+ return(NS_ERROR_NOT_IMPLEMENTED);
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::BeginUpdateBatch()
+{
+ return NS_OK;
+}
+
+
+
+NS_IMETHODIMP
+FileSystemDataSource::EndUpdateBatch()
+{
+ return NS_OK;
+}
+
+
+
+nsresult
+FileSystemDataSource::GetVolumeList(nsISimpleEnumerator** aResult)
+{
+ nsCOMArray<nsIRDFResource> volumes;
+ nsCOMPtr<nsIRDFResource> vol;
+
+#ifdef XP_WIN
+
+ int32_t driveType;
+ wchar_t drive[32];
+ int32_t volNum;
+
+ for (volNum = 0; volNum < 26; volNum++)
+ {
+ swprintf_s(drive, 32, L"%c:\\", volNum + (char16_t)'A');
+
+ driveType = GetDriveTypeW(drive);
+ if (driveType != DRIVE_UNKNOWN && driveType != DRIVE_NO_ROOT_DIR)
+ {
+ nsAutoCString url;
+ url.AppendPrintf("file:///%c|/", volNum + 'A');
+ nsresult rv = mRDFService->GetResource(url, getter_AddRefs(vol));
+ if (NS_FAILED(rv))
+ return rv;
+
+ volumes.AppendObject(vol);
+ }
+ }
+#endif
+
+#ifdef XP_UNIX
+ mRDFService->GetResource(NS_LITERAL_CSTRING("file:///"), getter_AddRefs(vol));
+ volumes.AppendObject(vol);
+#endif
+
+ return NS_NewArrayEnumerator(aResult, volumes);
+}
+
+
+
+#ifdef XP_WIN
+bool
+FileSystemDataSource::isValidFolder(nsIRDFResource *source)
+{
+ bool isValid = true;
+ if (ieFavoritesDir.IsEmpty()) return(isValid);
+
+ nsresult rv;
+ nsCString uri;
+ rv = source->GetValueUTF8(uri);
+ if (NS_FAILED(rv)) return(isValid);
+
+ NS_ConvertUTF8toUTF16 theURI(uri);
+ if (theURI.Find(ieFavoritesDir) == 0)
+ {
+ isValid = false;
+
+ nsCOMPtr<nsISimpleEnumerator> folderEnum;
+ if (NS_SUCCEEDED(rv = GetFolderList(source, true, false, getter_AddRefs(folderEnum))))
+ {
+ bool hasAny = false, hasMore;
+ while (NS_SUCCEEDED(folderEnum->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ hasAny = true;
+
+ nsCOMPtr<nsISupports> isupports;
+ if (NS_FAILED(rv = folderEnum->GetNext(getter_AddRefs(isupports))))
+ break;
+ nsCOMPtr<nsIRDFResource> res = do_QueryInterface(isupports);
+ if (!res) break;
+
+ nsCOMPtr<nsIRDFLiteral> nameLiteral;
+ if (NS_FAILED(rv = GetName(res, getter_AddRefs(nameLiteral))))
+ break;
+
+ const char16_t *uniName;
+ if (NS_FAILED(rv = nameLiteral->GetValueConst(&uniName)))
+ break;
+ nsAutoString name(uniName);
+
+ // An empty folder, or a folder that contains just "desktop.ini",
+ // is considered to be a IE Favorite; otherwise, its a folder
+ if (!name.LowerCaseEqualsLiteral("desktop.ini"))
+ {
+ isValid = true;
+ break;
+ }
+ }
+ if (!hasAny) isValid = true;
+ }
+ }
+ return(isValid);
+}
+#endif
+
+
+
+nsresult
+FileSystemDataSource::GetFolderList(nsIRDFResource *source, bool allowHidden,
+ bool onlyFirst, nsISimpleEnumerator** aResult)
+{
+ if (!isDirURI(source))
+ return(NS_RDF_NO_VALUE);
+
+ nsresult rv;
+
+ const char *parentURI = nullptr;
+ rv = source->GetValueConst(&parentURI);
+ if (NS_FAILED(rv))
+ return(rv);
+ if (!parentURI)
+ return(NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIURI> aIURI;
+ if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(aIURI), nsDependentCString(parentURI))))
+ return(rv);
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aIURI);
+ if (!fileURL)
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> aDir;
+ if (NS_FAILED(rv = fileURL->GetFile(getter_AddRefs(aDir))))
+ return(rv);
+
+ // ensure that we DO NOT resolve aliases
+ aDir->SetFollowLinks(false);
+
+ nsCOMPtr<nsISimpleEnumerator> dirContents;
+ if (NS_FAILED(rv = aDir->GetDirectoryEntries(getter_AddRefs(dirContents))))
+ return(rv);
+ if (!dirContents)
+ return(NS_ERROR_UNEXPECTED);
+
+ nsCOMArray<nsIRDFResource> resources;
+ bool hasMore;
+ while(NS_SUCCEEDED(rv = dirContents->HasMoreElements(&hasMore)) &&
+ hasMore)
+ {
+ nsCOMPtr<nsISupports> isupports;
+ if (NS_FAILED(rv = dirContents->GetNext(getter_AddRefs(isupports))))
+ break;
+
+ nsCOMPtr<nsIFile> aFile = do_QueryInterface(isupports);
+ if (!aFile)
+ break;
+
+ if (!allowHidden)
+ {
+ bool hiddenFlag = false;
+ if (NS_FAILED(rv = aFile->IsHidden(&hiddenFlag)))
+ break;
+ if (hiddenFlag)
+ continue;
+ }
+
+ nsAutoString leafStr;
+ if (NS_FAILED(rv = aFile->GetLeafName(leafStr)))
+ break;
+ if (leafStr.IsEmpty())
+ continue;
+
+ nsAutoCString fullURI;
+ fullURI.Assign(parentURI);
+ if (fullURI.Last() != '/')
+ {
+ fullURI.Append('/');
+ }
+
+ nsAutoCString leaf;
+ bool escaped = NS_Escape(NS_ConvertUTF16toUTF8(leafStr), leaf, url_Path);
+ leafStr.Truncate();
+
+ if (!escaped) {
+ continue;
+ }
+
+ // using nsEscape() [above] doesn't escape slashes, so do that by hand
+ int32_t aOffset;
+ while ((aOffset = leaf.FindChar('/')) >= 0)
+ {
+ leaf.Cut((uint32_t)aOffset, 1);
+ leaf.Insert("%2F", (uint32_t)aOffset);
+ }
+
+ // append the encoded name
+ fullURI.Append(leaf);
+
+ bool dirFlag = false;
+ rv = aFile->IsDirectory(&dirFlag);
+ if (NS_SUCCEEDED(rv) && dirFlag)
+ {
+ fullURI.Append('/');
+ }
+
+ nsCOMPtr<nsIRDFResource> fileRes;
+ mRDFService->GetResource(fullURI, getter_AddRefs(fileRes));
+
+ resources.AppendObject(fileRes);
+
+ if (onlyFirst)
+ break;
+ }
+
+ return NS_NewArrayEnumerator(aResult, resources);
+}
+
+nsresult
+FileSystemDataSource::GetLastMod(nsIRDFResource *source, nsIRDFDate **aResult)
+{
+ *aResult = nullptr;
+
+ nsresult rv;
+ const char *uri = nullptr;
+
+ rv = source->GetValueConst(&uri);
+ if (NS_FAILED(rv)) return(rv);
+ if (!uri)
+ return(NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIURI> aIURI;
+ if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(aIURI), nsDependentCString(uri))))
+ return(rv);
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aIURI);
+ if (!fileURL)
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> aFile;
+ if (NS_FAILED(rv = fileURL->GetFile(getter_AddRefs(aFile))))
+ return(rv);
+ if (!aFile)
+ return(NS_ERROR_UNEXPECTED);
+
+ // ensure that we DO NOT resolve aliases
+ aFile->SetFollowLinks(false);
+
+ PRTime lastModDate;
+ if (NS_FAILED(rv = aFile->GetLastModifiedTime(&lastModDate)))
+ return(rv);
+
+ // convert from milliseconds to seconds
+ mRDFService->GetDateLiteral(lastModDate * PR_MSEC_PER_SEC, aResult);
+
+ return(NS_OK);
+}
+
+
+
+nsresult
+FileSystemDataSource::GetFileSize(nsIRDFResource *source, nsIRDFInt **aResult)
+{
+ *aResult = nullptr;
+
+ nsresult rv;
+ const char *uri = nullptr;
+
+ rv = source->GetValueConst(&uri);
+ if (NS_FAILED(rv))
+ return(rv);
+ if (!uri)
+ return(NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIURI> aIURI;
+ if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(aIURI), nsDependentCString(uri))))
+ return(rv);
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aIURI);
+ if (!fileURL)
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> aFile;
+ if (NS_FAILED(rv = fileURL->GetFile(getter_AddRefs(aFile))))
+ return(rv);
+ if (!aFile)
+ return(NS_ERROR_UNEXPECTED);
+
+ // ensure that we DO NOT resolve aliases
+ aFile->SetFollowLinks(false);
+
+ // don't do anything with directories
+ bool isDir = false;
+ if (NS_FAILED(rv = aFile->IsDirectory(&isDir)))
+ return(rv);
+ if (isDir)
+ return(NS_RDF_NO_VALUE);
+
+ int64_t aFileSize64;
+ if (NS_FAILED(rv = aFile->GetFileSize(&aFileSize64)))
+ return(rv);
+
+ // convert 64bits to 32bits
+ int32_t aFileSize32 = int32_t(aFileSize64);
+ mRDFService->GetIntLiteral(aFileSize32, aResult);
+
+ return(NS_OK);
+}
+
+
+
+nsresult
+FileSystemDataSource::GetName(nsIRDFResource *source, nsIRDFLiteral **aResult)
+{
+ nsresult rv;
+ const char *uri = nullptr;
+
+ rv = source->GetValueConst(&uri);
+ if (NS_FAILED(rv))
+ return(rv);
+ if (!uri)
+ return(NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIURI> aIURI;
+ if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(aIURI), nsDependentCString(uri))))
+ return(rv);
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aIURI);
+ if (!fileURL)
+ return NS_OK;
+
+ nsCOMPtr<nsIFile> aFile;
+ if (NS_FAILED(rv = fileURL->GetFile(getter_AddRefs(aFile))))
+ return(rv);
+ if (!aFile)
+ return(NS_ERROR_UNEXPECTED);
+
+ // ensure that we DO NOT resolve aliases
+ aFile->SetFollowLinks(false);
+
+ nsAutoString name;
+ if (NS_FAILED(rv = aFile->GetLeafName(name)))
+ return(rv);
+ if (name.IsEmpty())
+ return(NS_ERROR_UNEXPECTED);
+
+#ifdef XP_WIN
+ // special hack for IE favorites under Windows; strip off the
+ // trailing ".url" or ".lnk" at the end of IE favorites names
+ int32_t nameLen = name.Length();
+ if ((strncmp(uri, ieFavoritesDir.get(), ieFavoritesDir.Length()) == 0) && (nameLen > 4))
+ {
+ nsAutoString extension;
+ name.Right(extension, 4);
+ if (extension.LowerCaseEqualsLiteral(".url") ||
+ extension.LowerCaseEqualsLiteral(".lnk"))
+ {
+ name.Truncate(nameLen - 4);
+ }
+ }
+#endif
+
+ mRDFService->GetLiteral(name.get(), aResult);
+
+ return NS_OK;
+}
+
+
+
+#ifdef USE_NC_EXTENSION
+nsresult
+FileSystemDataSource::GetExtension(nsIRDFResource *source, nsIRDFLiteral **aResult)
+{
+ nsCOMPtr<nsIRDFLiteral> name;
+ nsresult rv = GetName(source, getter_AddRefs(name));
+ if (NS_FAILED(rv))
+ return rv;
+
+ const char16_t* unicodeLeafName;
+ rv = name->GetValueConst(&unicodeLeafName);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoString filename(unicodeLeafName);
+ int32_t lastDot = filename.RFindChar('.');
+ if (lastDot == -1)
+ {
+ mRDFService->GetLiteral(EmptyString().get(), aResult);
+ }
+ else
+ {
+ nsAutoString extension;
+ filename.Right(extension, (filename.Length() - lastDot));
+ mRDFService->GetLiteral(extension.get(), aResult);
+ }
+
+ return NS_OK;
+}
+#endif
+
+#ifdef XP_WIN
+nsresult
+FileSystemDataSource::getIEFavoriteURL(nsIRDFResource *source, nsString aFileURL, nsIRDFLiteral **urlLiteral)
+{
+ nsresult rv = NS_OK;
+
+ *urlLiteral = nullptr;
+
+ nsCOMPtr<nsIFile> f;
+ NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileURL), getter_AddRefs(f));
+
+ bool value;
+
+ if (NS_SUCCEEDED(f->IsDirectory(&value)) && value)
+ {
+ if (isValidFolder(source))
+ return(NS_RDF_NO_VALUE);
+ aFileURL.AppendLiteral("desktop.ini");
+ }
+ else if (aFileURL.Length() > 4)
+ {
+ nsAutoString extension;
+
+ aFileURL.Right(extension, 4);
+ if (!extension.LowerCaseEqualsLiteral(".url"))
+ {
+ return(NS_RDF_NO_VALUE);
+ }
+ }
+
+ nsCOMPtr<nsIInputStream> strm;
+ NS_NewLocalFileInputStream(getter_AddRefs(strm),f);
+ nsCOMPtr<nsILineInputStream> linereader = do_QueryInterface(strm, &rv);
+
+ nsAutoString line;
+ nsAutoCString cLine;
+ while(NS_SUCCEEDED(rv))
+ {
+ bool isEOF;
+ rv = linereader->ReadLine(cLine, &isEOF);
+ CopyASCIItoUTF16(cLine, line);
+
+ if (isEOF)
+ {
+ if (line.Find("URL=", true) == 0)
+ {
+ line.Cut(0, 4);
+ rv = mRDFService->GetLiteral(line.get(), urlLiteral);
+ break;
+ }
+ else if (line.Find("CDFURL=", true) == 0)
+ {
+ line.Cut(0, 7);
+ rv = mRDFService->GetLiteral(line.get(), urlLiteral);
+ break;
+ }
+ line.Truncate();
+ }
+ }
+
+ return(rv);
+}
+#endif
+
+
+
+nsresult
+FileSystemDataSource::GetURL(nsIRDFResource *source, bool *isFavorite, nsIRDFLiteral** aResult)
+{
+ if (isFavorite) *isFavorite = false;
+
+ nsresult rv;
+ nsCString uri;
+
+ rv = source->GetValueUTF8(uri);
+ if (NS_FAILED(rv))
+ return(rv);
+
+ NS_ConvertUTF8toUTF16 url(uri);
+
+#ifdef XP_WIN
+ // under Windows, if its an IE favorite, munge the URL
+ if (!ieFavoritesDir.IsEmpty())
+ {
+ if (url.Find(ieFavoritesDir) == 0)
+ {
+ if (isFavorite) *isFavorite = true;
+ rv = getIEFavoriteURL(source, url, aResult);
+ return(rv);
+ }
+ }
+#endif
+
+ // if we fall through to here, its not any type of bookmark
+ // stored in the platform native file system, so just set the URL
+
+ mRDFService->GetLiteral(url.get(), aResult);
+
+ return(NS_OK);
+}
diff --git a/components/rdf/src/nsFileSystemDataSource.h b/components/rdf/src/nsFileSystemDataSource.h
new file mode 100644
index 000000000..09fa17400
--- /dev/null
+++ b/components/rdf/src/nsFileSystemDataSource.h
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+#ifndef nsFileSystemDataSource_h__
+#define nsFileSystemDataSource_h__
+
+#include "nsIRDFDataSource.h"
+#include "nsIRDFLiteral.h"
+#include "nsIRDFResource.h"
+#include "nsIRDFService.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+#if defined(XP_UNIX) || defined(XP_WIN)
+#define USE_NC_EXTENSION
+#endif
+
+class FileSystemDataSource final : public nsIRDFDataSource
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRDFDATASOURCE
+
+ static nsresult Create(nsISupports* aOuter,
+ const nsIID& aIID, void **aResult);
+
+ nsresult Init();
+
+private:
+ FileSystemDataSource() { }
+ ~FileSystemDataSource() { }
+
+ // helper methods
+ bool isFileURI(nsIRDFResource* aResource);
+ bool isDirURI(nsIRDFResource* aSource);
+ nsresult GetVolumeList(nsISimpleEnumerator **aResult);
+ nsresult GetFolderList(nsIRDFResource *source, bool allowHidden, bool onlyFirst, nsISimpleEnumerator **aResult);
+ nsresult GetName(nsIRDFResource *source, nsIRDFLiteral** aResult);
+ nsresult GetURL(nsIRDFResource *source, bool *isFavorite, nsIRDFLiteral** aResult);
+ nsresult GetFileSize(nsIRDFResource *source, nsIRDFInt** aResult);
+ nsresult GetLastMod(nsIRDFResource *source, nsIRDFDate** aResult);
+
+ nsCOMPtr<nsIRDFService> mRDFService;
+
+ // pseudo-constants
+ nsCOMPtr<nsIRDFResource> mNC_FileSystemRoot;
+ nsCOMPtr<nsIRDFResource> mNC_Child;
+ nsCOMPtr<nsIRDFResource> mNC_Name;
+ nsCOMPtr<nsIRDFResource> mNC_URL;
+ nsCOMPtr<nsIRDFResource> mNC_Icon;
+ nsCOMPtr<nsIRDFResource> mNC_Length;
+ nsCOMPtr<nsIRDFResource> mNC_IsDirectory;
+ nsCOMPtr<nsIRDFResource> mWEB_LastMod;
+ nsCOMPtr<nsIRDFResource> mNC_FileSystemObject;
+ nsCOMPtr<nsIRDFResource> mNC_pulse;
+ nsCOMPtr<nsIRDFResource> mRDF_InstanceOf;
+ nsCOMPtr<nsIRDFResource> mRDF_type;
+
+ nsCOMPtr<nsIRDFLiteral> mLiteralTrue;
+ nsCOMPtr<nsIRDFLiteral> mLiteralFalse;
+
+#ifdef USE_NC_EXTENSION
+ nsresult GetExtension(nsIRDFResource *source, nsIRDFLiteral** aResult);
+ nsCOMPtr<nsIRDFResource> mNC_extension;
+#endif
+
+#ifdef XP_WIN
+ bool isValidFolder(nsIRDFResource *source);
+ nsresult getIEFavoriteURL(nsIRDFResource *source, nsString aFileURL, nsIRDFLiteral **urlLiteral);
+ nsCOMPtr<nsIRDFResource> mNC_IEFavoriteObject;
+ nsCOMPtr<nsIRDFResource> mNC_IEFavoriteFolder;
+ nsCString ieFavoritesDir;
+#endif
+};
+
+#endif // nsFileSystemDataSource_h__
diff --git a/components/rdf/src/nsILocalStore.h b/components/rdf/src/nsILocalStore.h
new file mode 100644
index 000000000..b41226593
--- /dev/null
+++ b/components/rdf/src/nsILocalStore.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+#ifndef nsILocalStore_h__
+#define nsILocalStore_h__
+
+#include "rdf.h"
+#include "nsISupports.h"
+
+// {DF71C6F1-EC53-11d2-BDCA-000064657374}
+#define NS_ILOCALSTORE_IID \
+{ 0xdf71c6f1, 0xec53, 0x11d2, { 0xbd, 0xca, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } }
+
+// {DF71C6F0-EC53-11d2-BDCA-000064657374}
+#define NS_LOCALSTORE_CID \
+{ 0xdf71c6f0, 0xec53, 0x11d2, { 0xbd, 0xca, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } }
+
+#define NS_LOCALSTORE_CONTRACTID NS_RDF_DATASOURCE_CONTRACTID_PREFIX "local-store"
+
+class nsILocalStore : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ILOCALSTORE_IID)
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsILocalStore, NS_ILOCALSTORE_IID)
+
+extern nsresult
+NS_NewLocalStore(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+
+#endif // nsILocalStore_h__
diff --git a/components/rdf/src/nsIRDFContentSink.h b/components/rdf/src/nsIRDFContentSink.h
new file mode 100644
index 000000000..5abb536de
--- /dev/null
+++ b/components/rdf/src/nsIRDFContentSink.h
@@ -0,0 +1,62 @@
+/* -*- 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/. */
+
+/*
+
+ An RDF-specific content sink. The content sink is targeted by the
+ parser for building the RDF content model.
+
+ */
+
+#ifndef nsIRDFContentSink_h___
+#define nsIRDFContentSink_h___
+
+#include "nsIXMLContentSink.h"
+class nsIRDFDataSource;
+class nsIURI;
+
+// {3a7459d7-d723-483c-aef0-404fc48e09b8}
+#define NS_IRDFCONTENTSINK_IID \
+{ 0x3a7459d7, 0xd723, 0x483c, { 0xae, 0xf0, 0x40, 0x4f, 0xc4, 0x8e, 0x09, 0xb8 } }
+
+/**
+ * This interface represents a content sink for RDF files.
+ */
+
+class nsIRDFContentSink : public nsIXMLContentSink {
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IRDFCONTENTSINK_IID)
+
+ /**
+ * Initialize the content sink.
+ */
+ NS_IMETHOD Init(nsIURI* aURL) = 0;
+
+ /**
+ * Set the content sink's RDF Data source
+ */
+ NS_IMETHOD SetDataSource(nsIRDFDataSource* aDataSource) = 0;
+
+ /**
+ * Retrieve the content sink's RDF data source.
+ */
+ NS_IMETHOD GetDataSource(nsIRDFDataSource*& rDataSource) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIRDFContentSink, NS_IRDFCONTENTSINK_IID)
+
+/**
+ * This constructs a content sink that can be used without a
+ * document, say, to create a stand-alone in-memory graph.
+ */
+nsresult
+NS_NewRDFContentSink(nsIRDFContentSink** aResult);
+
+class nsRDFAtoms {
+public:
+ static void RegisterAtoms();
+};
+
+#endif // nsIRDFContentSink_h___
diff --git a/components/rdf/src/nsIRDFFTP.h b/components/rdf/src/nsIRDFFTP.h
new file mode 100644
index 000000000..8965b4a38
--- /dev/null
+++ b/components/rdf/src/nsIRDFFTP.h
@@ -0,0 +1,33 @@
+/* -*- 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/. */
+
+#ifndef nsIRDFFTP_h__
+#define nsIRDFFTP_h__
+
+#include "nscore.h"
+#include "nsISupports.h"
+#include "nsIRDFNode.h"
+
+
+
+#define NS_IRDFFTPDATAOURCE_IID \
+{ 0x1222e6f0, 0xa5e3, 0x11d2, { 0x8b, 0x7c, 0x00, 0x80, 0x5f, 0x8a, 0x7d, 0xb7 } }
+
+class nsIRDFFTPDataSource : public nsIRDFDataSource
+{
+public:
+};
+
+
+#define NS_IRDFFTPDATASOURCECALLBACK_IID \
+{ 0x204a1a00, 0xa5e4, 0x11d2, { 0x8b, 0x7c, 0x00, 0x80, 0x5f, 0x8a, 0x7d, 0xb8 } }
+
+class nsIRDFFTPDataSourceCallback : public nsIStreamListener
+{
+public:
+};
+
+
+#endif // nsIRDFFTP_h__
diff --git a/components/rdf/src/nsInMemoryDataSource.cpp b/components/rdf/src/nsInMemoryDataSource.cpp
new file mode 100644
index 000000000..9bdd6b4fb
--- /dev/null
+++ b/components/rdf/src/nsInMemoryDataSource.cpp
@@ -0,0 +1,1964 @@
+/* -*- 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/.
+ *
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+/*
+
+ Implementation for an in-memory RDF data store.
+
+ TO DO
+
+ 1) Instrument this code to gather space and time performance
+ characteristics.
+
+ 2) Optimize lookups for datasources which have a small number
+ of properties + fanning out to a large number of targets.
+
+ 3) Complete implementation of thread-safety; specifically, make
+ assertions be reference counted objects (so that a cursor can
+ still refer to an assertion that gets removed from the graph).
+
+ */
+
+#include "nsAgg.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsArrayEnumerator.h"
+#include "nsIOutputStream.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFLiteral.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFObserver.h"
+#include "nsIRDFInMemoryDataSource.h"
+#include "nsIRDFPropagatableDataSource.h"
+#include "nsIRDFPurgeableDataSource.h"
+#include "nsIRDFService.h"
+#include "nsIServiceManager.h"
+#include "nsCOMArray.h"
+#include "nsEnumeratorUtils.h"
+#include "nsTArray.h"
+#include "nsCRT.h"
+#include "nsRDFCID.h"
+#include "nsRDFBaseDataSources.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsXPIDLString.h"
+#include "rdfutil.h"
+#include "PLDHashTable.h"
+#include "plstr.h"
+#include "mozilla/Logging.h"
+#include "rdf.h"
+
+#include "rdfIDataSource.h"
+#include "rdfITripleVisitor.h"
+
+using mozilla::LogLevel;
+
+// This struct is used as the slot value in the forward and reverse
+// arcs hash tables.
+//
+// Assertion objects are reference counted, because each Assertion's
+// ownership is shared between the datasource and any enumerators that
+// are currently iterating over the datasource.
+//
+class Assertion
+{
+public:
+ Assertion(nsIRDFResource* aSource, // normal assertion
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue);
+ explicit Assertion(nsIRDFResource* aSource); // PLDHashTable assertion variant
+
+private:
+ ~Assertion();
+
+public:
+ void AddRef() {
+ if (mRefCnt == UINT16_MAX) {
+ NS_WARNING("refcount overflow, leaking Assertion");
+ return;
+ }
+ ++mRefCnt;
+ }
+
+ void Release() {
+ if (mRefCnt == UINT16_MAX) {
+ NS_WARNING("refcount overflow, leaking Assertion");
+ return;
+ }
+ if (--mRefCnt == 0)
+ delete this;
+ }
+
+ // For nsIRDFPurgeableDataSource
+ inline void Mark() { u.as.mMarked = true; }
+ inline bool IsMarked() { return u.as.mMarked; }
+ inline void Unmark() { u.as.mMarked = false; }
+
+ // public for now, because I'm too lazy to go thru and clean this up.
+
+ // These are shared between hash/as (see the union below)
+ nsIRDFResource* mSource;
+ Assertion* mNext;
+
+ union
+ {
+ struct hash
+ {
+ PLDHashTable* mPropertyHash;
+ } hash;
+ struct as
+ {
+ nsIRDFResource* mProperty;
+ nsIRDFNode* mTarget;
+ Assertion* mInvNext;
+ // make sure bool are final elements
+ bool mTruthValue;
+ bool mMarked;
+ } as;
+ } u;
+
+ // also shared between hash/as (see the union above)
+ // but placed after union definition to ensure that
+ // all 32-bit entries are long aligned
+ uint16_t mRefCnt;
+ bool mHashEntry;
+};
+
+
+struct Entry : PLDHashEntryHdr {
+ nsIRDFNode* mNode;
+ Assertion* mAssertions;
+};
+
+
+Assertion::Assertion(nsIRDFResource* aSource)
+ : mSource(aSource),
+ mNext(nullptr),
+ mRefCnt(0),
+ mHashEntry(true)
+{
+ MOZ_COUNT_CTOR(Assertion);
+
+ NS_ADDREF(mSource);
+
+ u.hash.mPropertyHash =
+ new PLDHashTable(PLDHashTable::StubOps(), sizeof(Entry));
+}
+
+Assertion::Assertion(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue)
+ : mSource(aSource),
+ mNext(nullptr),
+ mRefCnt(0),
+ mHashEntry(false)
+{
+ MOZ_COUNT_CTOR(Assertion);
+
+ u.as.mProperty = aProperty;
+ u.as.mTarget = aTarget;
+
+ NS_ADDREF(mSource);
+ NS_ADDREF(u.as.mProperty);
+ NS_ADDREF(u.as.mTarget);
+
+ u.as.mInvNext = nullptr;
+ u.as.mTruthValue = aTruthValue;
+ u.as.mMarked = false;
+}
+
+Assertion::~Assertion()
+{
+ if (mHashEntry && u.hash.mPropertyHash) {
+ for (auto i = u.hash.mPropertyHash->Iter(); !i.Done(); i.Next()) {
+ auto entry = static_cast<Entry*>(i.Get());
+ Assertion* as = entry->mAssertions;
+ while (as) {
+ Assertion* doomed = as;
+ as = as->mNext;
+
+ // Unlink, and release the datasource's reference.
+ doomed->mNext = doomed->u.as.mInvNext = nullptr;
+ doomed->Release();
+ }
+ }
+ delete u.hash.mPropertyHash;
+ u.hash.mPropertyHash = nullptr;
+ }
+
+ MOZ_COUNT_DTOR(Assertion);
+#ifdef DEBUG_REFS
+ --gInstanceCount;
+ fprintf(stdout, "%d - RDF: Assertion\n", gInstanceCount);
+#endif
+
+ NS_RELEASE(mSource);
+ if (!mHashEntry)
+ {
+ NS_RELEASE(u.as.mProperty);
+ NS_RELEASE(u.as.mTarget);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// InMemoryDataSource
+class InMemoryArcsEnumeratorImpl;
+class InMemoryAssertionEnumeratorImpl;
+class InMemoryResourceEnumeratorImpl;
+
+class InMemoryDataSource : public nsIRDFDataSource,
+ public nsIRDFInMemoryDataSource,
+ public nsIRDFPropagatableDataSource,
+ public nsIRDFPurgeableDataSource,
+ public rdfIDataSource
+{
+protected:
+ // These hash tables are keyed on pointers to nsIRDFResource
+ // objects (the nsIRDFService ensures that there is only ever one
+ // nsIRDFResource object per unique URI). The value of an entry is
+ // an Assertion struct, which is a linked list of (subject
+ // predicate object) triples.
+ PLDHashTable mForwardArcs;
+ PLDHashTable mReverseArcs;
+
+ nsCOMArray<nsIRDFObserver> mObservers;
+ uint32_t mNumObservers;
+
+ // VisitFoo needs to block writes, [Un]Assert only allowed
+ // during mReadCount == 0
+ uint32_t mReadCount;
+
+ friend class InMemoryArcsEnumeratorImpl;
+ friend class InMemoryAssertionEnumeratorImpl;
+ friend class InMemoryResourceEnumeratorImpl; // b/c it needs to enumerate mForwardArcs
+
+ // Thread-safe writer implementation methods.
+ nsresult
+ LockedAssert(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv);
+
+ nsresult
+ LockedUnassert(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target);
+
+ explicit InMemoryDataSource(nsISupports* aOuter);
+ virtual ~InMemoryDataSource();
+
+ friend nsresult
+ NS_NewRDFInMemoryDataSource(nsISupports* aOuter, const nsIID& aIID, void** aResult);
+
+public:
+ NS_DECL_CYCLE_COLLECTING_AGGREGATED
+ NS_DECL_AGGREGATED_CYCLE_COLLECTION_CLASS(InMemoryDataSource)
+
+ // nsIRDFDataSource methods
+ NS_DECL_NSIRDFDATASOURCE
+
+ // nsIRDFInMemoryDataSource methods
+ NS_DECL_NSIRDFINMEMORYDATASOURCE
+
+ // nsIRDFPropagatableDataSource methods
+ NS_DECL_NSIRDFPROPAGATABLEDATASOURCE
+
+ // nsIRDFPurgeableDataSource methods
+ NS_DECL_NSIRDFPURGEABLEDATASOURCE
+
+ // rdfIDataSource methods
+ NS_DECL_RDFIDATASOURCE
+
+protected:
+ struct SweepInfo {
+ Assertion* mUnassertList;
+ PLDHashTable* mReverseArcs;
+ };
+
+ static void
+ SweepForwardArcsEntries(PLDHashTable* aTable, SweepInfo* aArg);
+
+public:
+ // Implementation methods
+ Assertion*
+ GetForwardArcs(nsIRDFResource* u) {
+ PLDHashEntryHdr* hdr = mForwardArcs.Search(u);
+ return hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ }
+
+ Assertion*
+ GetReverseArcs(nsIRDFNode* v) {
+ PLDHashEntryHdr* hdr = mReverseArcs.Search(v);
+ return hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ }
+
+ void
+ SetForwardArcs(nsIRDFResource* u, Assertion* as) {
+ if (as) {
+ auto entry =
+ static_cast<Entry*>(mForwardArcs.Add(u, mozilla::fallible));
+ if (entry) {
+ entry->mNode = u;
+ entry->mAssertions = as;
+ }
+ }
+ else {
+ mForwardArcs.Remove(u);
+ }
+ }
+
+ void
+ SetReverseArcs(nsIRDFNode* v, Assertion* as) {
+ if (as) {
+ auto entry =
+ static_cast<Entry*>(mReverseArcs.Add(v, mozilla::fallible));
+ if (entry) {
+ entry->mNode = v;
+ entry->mAssertions = as;
+ }
+ }
+ else {
+ mReverseArcs.Remove(v);
+ }
+ }
+
+ void
+ LogOperation(const char* aOperation,
+ nsIRDFResource* asource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue = true);
+
+ bool mPropagateChanges;
+
+private:
+ static mozilla::LazyLogModule gLog;
+};
+
+mozilla::LazyLogModule InMemoryDataSource::gLog("InMemoryDataSource");
+
+//----------------------------------------------------------------------
+//
+// InMemoryAssertionEnumeratorImpl
+//
+
+/**
+ * InMemoryAssertionEnumeratorImpl
+ */
+class InMemoryAssertionEnumeratorImpl : public nsISimpleEnumerator
+{
+private:
+ InMemoryDataSource* mDataSource;
+ nsIRDFResource* mSource;
+ nsIRDFResource* mProperty;
+ nsIRDFNode* mTarget;
+ nsIRDFNode* mValue;
+ bool mTruthValue;
+ Assertion* mNextAssertion;
+
+ virtual ~InMemoryAssertionEnumeratorImpl();
+
+public:
+ InMemoryAssertionEnumeratorImpl(InMemoryDataSource* aDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue);
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator interface
+ NS_DECL_NSISIMPLEENUMERATOR
+};
+
+////////////////////////////////////////////////////////////////////////
+
+
+InMemoryAssertionEnumeratorImpl::InMemoryAssertionEnumeratorImpl(
+ InMemoryDataSource* aDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue)
+ : mDataSource(aDataSource),
+ mSource(aSource),
+ mProperty(aProperty),
+ mTarget(aTarget),
+ mValue(nullptr),
+ mTruthValue(aTruthValue),
+ mNextAssertion(nullptr)
+{
+ NS_ADDREF(mDataSource);
+ NS_IF_ADDREF(mSource);
+ NS_ADDREF(mProperty);
+ NS_IF_ADDREF(mTarget);
+
+ if (mSource) {
+ mNextAssertion = mDataSource->GetForwardArcs(mSource);
+
+ if (mNextAssertion && mNextAssertion->mHashEntry) {
+ // its our magical HASH_ENTRY forward hash for assertions
+ PLDHashEntryHdr* hdr =
+ mNextAssertion->u.hash.mPropertyHash->Search(aProperty);
+ mNextAssertion =
+ hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ }
+ }
+ else {
+ mNextAssertion = mDataSource->GetReverseArcs(mTarget);
+ }
+
+ // Add an owning reference from the enumerator
+ if (mNextAssertion)
+ mNextAssertion->AddRef();
+}
+
+InMemoryAssertionEnumeratorImpl::~InMemoryAssertionEnumeratorImpl()
+{
+#ifdef DEBUG_REFS
+ --gInstanceCount;
+ fprintf(stdout, "%d - RDF: InMemoryAssertionEnumeratorImpl\n", gInstanceCount);
+#endif
+
+ if (mNextAssertion)
+ mNextAssertion->Release();
+
+ NS_IF_RELEASE(mDataSource);
+ NS_IF_RELEASE(mSource);
+ NS_IF_RELEASE(mProperty);
+ NS_IF_RELEASE(mTarget);
+ NS_IF_RELEASE(mValue);
+}
+
+NS_IMPL_ADDREF(InMemoryAssertionEnumeratorImpl)
+NS_IMPL_RELEASE(InMemoryAssertionEnumeratorImpl)
+NS_IMPL_QUERY_INTERFACE(InMemoryAssertionEnumeratorImpl, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+InMemoryAssertionEnumeratorImpl::HasMoreElements(bool* aResult)
+{
+ if (mValue) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ while (mNextAssertion) {
+ bool foundIt = false;
+ if ((mProperty == mNextAssertion->u.as.mProperty) &&
+ (mTruthValue == mNextAssertion->u.as.mTruthValue)) {
+ if (mSource) {
+ mValue = mNextAssertion->u.as.mTarget;
+ NS_ADDREF(mValue);
+ }
+ else {
+ mValue = mNextAssertion->mSource;
+ NS_ADDREF(mValue);
+ }
+ foundIt = true;
+ }
+
+ // Remember the last assertion we were holding on to
+ Assertion* as = mNextAssertion;
+
+ // iterate
+ mNextAssertion = (mSource) ? mNextAssertion->mNext : mNextAssertion->u.as.mInvNext;
+
+ // grab an owning reference from the enumerator to the next assertion
+ if (mNextAssertion)
+ mNextAssertion->AddRef();
+
+ // ...and release the reference from the enumerator to the old one.
+ as->Release();
+
+ if (foundIt) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+
+ *aResult = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+InMemoryAssertionEnumeratorImpl::GetNext(nsISupports** aResult)
+{
+ nsresult rv;
+
+ bool hasMore;
+ rv = HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasMore)
+ return NS_ERROR_UNEXPECTED;
+
+ // Don't AddRef: we "transfer" ownership to the caller
+ *aResult = mValue;
+ mValue = nullptr;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//
+
+/**
+ * This class is a little bit bizarre in that it implements both the
+ * <tt>nsIRDFArcsOutCursor</tt> and <tt>nsIRDFArcsInCursor</tt> interfaces.
+ * Because the structure of the in-memory graph is pretty flexible, it's
+ * fairly easy to parameterize this class. The only funky thing to watch
+ * out for is the multiple inheritance clashes.
+ */
+
+class InMemoryArcsEnumeratorImpl : public nsISimpleEnumerator
+{
+private:
+ InMemoryDataSource* mDataSource;
+ nsIRDFResource* mSource;
+ nsIRDFNode* mTarget;
+ AutoTArray<nsCOMPtr<nsIRDFResource>, 8> mAlreadyReturned;
+ nsIRDFResource* mCurrent;
+ Assertion* mAssertion;
+ nsCOMArray<nsIRDFNode>* mHashArcs;
+
+ virtual ~InMemoryArcsEnumeratorImpl();
+
+public:
+ InMemoryArcsEnumeratorImpl(InMemoryDataSource* aDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFNode* aTarget);
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator interface
+ NS_DECL_NSISIMPLEENUMERATOR
+};
+
+
+InMemoryArcsEnumeratorImpl::InMemoryArcsEnumeratorImpl(InMemoryDataSource* aDataSource,
+ nsIRDFResource* aSource,
+ nsIRDFNode* aTarget)
+ : mDataSource(aDataSource),
+ mSource(aSource),
+ mTarget(aTarget),
+ mCurrent(nullptr),
+ mHashArcs(nullptr)
+{
+ NS_ADDREF(mDataSource);
+ NS_IF_ADDREF(mSource);
+ NS_IF_ADDREF(mTarget);
+
+ if (mSource) {
+ // cast okay because it's a closed system
+ mAssertion = mDataSource->GetForwardArcs(mSource);
+
+ if (mAssertion && mAssertion->mHashEntry) {
+ // its our magical HASH_ENTRY forward hash for assertions
+ mHashArcs = new nsCOMArray<nsIRDFNode>();
+ for (auto i = mAssertion->u.hash.mPropertyHash->Iter();
+ !i.Done();
+ i.Next()) {
+ auto entry = static_cast<Entry*>(i.Get());
+ mHashArcs->AppendElement(entry->mNode);
+ }
+ mAssertion = nullptr;
+ }
+ }
+ else {
+ mAssertion = mDataSource->GetReverseArcs(mTarget);
+ }
+}
+
+InMemoryArcsEnumeratorImpl::~InMemoryArcsEnumeratorImpl()
+{
+#ifdef DEBUG_REFS
+ --gInstanceCount;
+ fprintf(stdout, "%d - RDF: InMemoryArcsEnumeratorImpl\n", gInstanceCount);
+#endif
+
+ NS_RELEASE(mDataSource);
+ NS_IF_RELEASE(mSource);
+ NS_IF_RELEASE(mTarget);
+ NS_IF_RELEASE(mCurrent);
+ delete mHashArcs;
+}
+
+NS_IMPL_ADDREF(InMemoryArcsEnumeratorImpl)
+NS_IMPL_RELEASE(InMemoryArcsEnumeratorImpl)
+NS_IMPL_QUERY_INTERFACE(InMemoryArcsEnumeratorImpl, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+InMemoryArcsEnumeratorImpl::HasMoreElements(bool* aResult)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mCurrent) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ if (mHashArcs) {
+ if (!mHashArcs->IsEmpty()) {
+ const uint32_t last = mHashArcs->Length() - 1;
+ nsCOMPtr<nsIRDFResource> tmp(do_QueryInterface(mHashArcs->ObjectAt(last)));
+ tmp.forget(&mCurrent);
+ mHashArcs->RemoveElementAt(last);
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+ else
+ while (mAssertion) {
+ nsIRDFResource* next = mAssertion->u.as.mProperty;
+
+ // "next" is the property arc we are tentatively going to return
+ // in a subsequent GetNext() call. It is important to do two
+ // things, however, before that can happen:
+ // 1) Make sure it's not an arc we've already returned.
+ // 2) Make sure that |mAssertion| is not left pointing to
+ // another assertion that has the same property as this one.
+ // The first is a practical concern; the second a defense against
+ // an obscure crash and other erratic behavior. To ensure the
+ // second condition, skip down the chain until we find the next
+ // assertion with a property that doesn't match the current one.
+ // (All these assertions would be skipped via mAlreadyReturned
+ // checks anyways; this is even a bit faster.)
+
+ do {
+ mAssertion = (mSource ? mAssertion->mNext :
+ mAssertion->u.as.mInvNext);
+ }
+ while (mAssertion && (next == mAssertion->u.as.mProperty));
+
+ bool alreadyReturned = false;
+ for (int32_t i = mAlreadyReturned.Length() - 1; i >= 0; --i) {
+ if (mAlreadyReturned[i] == next) {
+ alreadyReturned = true;
+ break;
+ }
+ }
+
+ if (! alreadyReturned) {
+ mCurrent = next;
+ NS_ADDREF(mCurrent);
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+
+ *aResult = false;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+InMemoryArcsEnumeratorImpl::GetNext(nsISupports** aResult)
+{
+ nsresult rv;
+
+ bool hasMore;
+ rv = HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+
+ if (! hasMore)
+ return NS_ERROR_UNEXPECTED;
+
+ // Add this to the set of things we've already returned so that we
+ // can ensure uniqueness
+ mAlreadyReturned.AppendElement(mCurrent);
+
+ // Don't AddRef: we "transfer" ownership to the caller
+ *aResult = mCurrent;
+ mCurrent = nullptr;
+
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// InMemoryDataSource
+
+nsresult
+NS_NewRDFInMemoryDataSource(nsISupports* aOuter, const nsIID& aIID, void** aResult)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+ *aResult = nullptr;
+
+ if (aOuter && !aIID.Equals(NS_GET_IID(nsISupports))) {
+ NS_ERROR("aggregation requires nsISupports");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ InMemoryDataSource* datasource = new InMemoryDataSource(aOuter);
+ NS_ADDREF(datasource);
+
+ datasource->fAggregated.AddRef();
+ nsresult rv = datasource->AggregatedQueryInterface(aIID, aResult); // This'll AddRef()
+ datasource->fAggregated.Release();
+
+ NS_RELEASE(datasource);
+ return rv;
+}
+
+
+InMemoryDataSource::InMemoryDataSource(nsISupports* aOuter)
+ : mForwardArcs(PLDHashTable::StubOps(), sizeof(Entry))
+ , mReverseArcs(PLDHashTable::StubOps(), sizeof(Entry))
+ , mNumObservers(0)
+ , mReadCount(0)
+{
+ NS_INIT_AGGREGATED(aOuter);
+
+ mPropagateChanges = true;
+ MOZ_COUNT_CTOR(InMemoryDataSource);
+}
+
+
+InMemoryDataSource::~InMemoryDataSource()
+{
+#ifdef DEBUG_REFS
+ --gInstanceCount;
+ fprintf(stdout, "%d - RDF: InMemoryDataSource\n", gInstanceCount);
+#endif
+
+ if (mForwardArcs.EntryCount() > 0) {
+ // This'll release all of the Assertion objects that are
+ // associated with this data source. We only need to do this
+ // for the forward arcs, because the reverse arcs table
+ // indexes the exact same set of resources.
+ for (auto iter = mForwardArcs.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<Entry*>(iter.Get());
+ Assertion* as = entry->mAssertions;
+ while (as) {
+ Assertion* doomed = as;
+ as = as->mNext;
+
+ // Unlink, and release the datasource's reference.
+ doomed->mNext = doomed->u.as.mInvNext = nullptr;
+ doomed->Release();
+ }
+ }
+ }
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("InMemoryDataSource(%p): destroyed.", this));
+
+ MOZ_COUNT_DTOR(InMemoryDataSource);
+}
+
+
+////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(InMemoryDataSource)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(InMemoryDataSource)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_AGGREGATED(InMemoryDataSource)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_AGGREGATED(InMemoryDataSource)
+NS_INTERFACE_MAP_BEGIN_AGGREGATED(InMemoryDataSource)
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION_AGGREGATED(InMemoryDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFInMemoryDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFPropagatableDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFPurgeableDataSource)
+ NS_INTERFACE_MAP_ENTRY(rdfIDataSource)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////////////
+
+
+void
+InMemoryDataSource::LogOperation(const char* aOperation,
+ nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue)
+{
+ if (! MOZ_LOG_TEST(gLog, LogLevel::Debug))
+ return;
+
+ nsXPIDLCString uri;
+ aSource->GetValue(getter_Copies(uri));
+ PR_LogPrint
+ ("InMemoryDataSource(%p): %s", this, aOperation);
+
+ PR_LogPrint
+ (" [(%p)%s]--", aSource, (const char*) uri);
+
+ aProperty->GetValue(getter_Copies(uri));
+
+ char tv = (aTruthValue ? '-' : '!');
+ PR_LogPrint
+ (" --%c[(%p)%s]--", tv, aProperty, (const char*) uri);
+
+ nsCOMPtr<nsIRDFResource> resource;
+ nsCOMPtr<nsIRDFLiteral> literal;
+
+ if ((resource = do_QueryInterface(aTarget)) != nullptr) {
+ resource->GetValue(getter_Copies(uri));
+ PR_LogPrint
+ (" -->[(%p)%s]", aTarget, (const char*) uri);
+ }
+ else if ((literal = do_QueryInterface(aTarget)) != nullptr) {
+ nsXPIDLString value;
+ literal->GetValue(getter_Copies(value));
+ nsAutoString valueStr(value);
+ char* valueCStr = ToNewCString(valueStr);
+
+ PR_LogPrint
+ (" -->(\"%s\")\n", valueCStr);
+
+ free(valueCStr);
+ }
+ else {
+ PR_LogPrint
+ (" -->(unknown-type)\n");
+ }
+}
+
+
+NS_IMETHODIMP
+InMemoryDataSource::GetURI(char* *uri)
+{
+ NS_PRECONDITION(uri != nullptr, "null ptr");
+ if (! uri)
+ return NS_ERROR_NULL_POINTER;
+
+ *uri = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::GetSource(nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ nsIRDFResource** source)
+{
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(property != nullptr, "null ptr");
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(target != nullptr, "null ptr");
+ if (! target)
+ return NS_ERROR_NULL_POINTER;
+
+ for (Assertion* as = GetReverseArcs(target); as; as = as->u.as.mInvNext) {
+ if ((property == as->u.as.mProperty) && (tv == as->u.as.mTruthValue)) {
+ *source = as->mSource;
+ NS_ADDREF(*source);
+ return NS_OK;
+ }
+ }
+ *source = nullptr;
+ return NS_RDF_NO_VALUE;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::GetTarget(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsIRDFNode** target)
+{
+ NS_PRECONDITION(source != nullptr, "null ptr");
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(property != nullptr, "null ptr");
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(target != nullptr, "null ptr");
+ if (! target)
+ return NS_ERROR_NULL_POINTER;
+
+ Assertion *as = GetForwardArcs(source);
+ if (as && as->mHashEntry) {
+ PLDHashEntryHdr* hdr = as->u.hash.mPropertyHash->Search(property);
+ Assertion* val = hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ while (val) {
+ if (tv == val->u.as.mTruthValue) {
+ *target = val->u.as.mTarget;
+ NS_IF_ADDREF(*target);
+ return NS_OK;
+ }
+ val = val->mNext;
+ }
+ }
+ else
+ for (; as != nullptr; as = as->mNext) {
+ if ((property == as->u.as.mProperty) && (tv == (as->u.as.mTruthValue))) {
+ *target = as->u.as.mTarget;
+ NS_ADDREF(*target);
+ return NS_OK;
+ }
+ }
+
+ // If we get here, then there was no target with for the specified
+ // property & truth value.
+ *target = nullptr;
+ return NS_RDF_NO_VALUE;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::HasAssertion(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ bool* hasAssertion)
+{
+ if (! source)
+ return NS_ERROR_NULL_POINTER;
+
+ if (! property)
+ return NS_ERROR_NULL_POINTER;
+
+ if (! target)
+ return NS_ERROR_NULL_POINTER;
+
+ Assertion *as = GetForwardArcs(source);
+ if (as && as->mHashEntry) {
+ PLDHashEntryHdr* hdr = as->u.hash.mPropertyHash->Search(property);
+ Assertion* val = hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ while (val) {
+ if ((val->u.as.mTarget == target) && (tv == (val->u.as.mTruthValue))) {
+ *hasAssertion = true;
+ return NS_OK;
+ }
+ val = val->mNext;
+ }
+ }
+ else
+ for (; as != nullptr; as = as->mNext) {
+ // check target first as its most unique
+ if (target != as->u.as.mTarget)
+ continue;
+
+ if (property != as->u.as.mProperty)
+ continue;
+
+ if (tv != (as->u.as.mTruthValue))
+ continue;
+
+ // found it!
+ *hasAssertion = true;
+ return NS_OK;
+ }
+
+ // If we get here, we couldn't find the assertion
+ *hasAssertion = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::GetSources(nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue,
+ nsISimpleEnumerator** aResult)
+{
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ InMemoryAssertionEnumeratorImpl* result =
+ new InMemoryAssertionEnumeratorImpl(this, nullptr, aProperty,
+ aTarget, aTruthValue);
+
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(result);
+ *aResult = result;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::GetTargets(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ bool aTruthValue,
+ nsISimpleEnumerator** aResult)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ InMemoryAssertionEnumeratorImpl* result =
+ new InMemoryAssertionEnumeratorImpl(this, aSource, aProperty,
+ nullptr, aTruthValue);
+
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(result);
+ *aResult = result;
+
+ return NS_OK;
+}
+
+
+nsresult
+InMemoryDataSource::LockedAssert(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue)
+{
+ LogOperation("ASSERT", aSource, aProperty, aTarget, aTruthValue);
+
+ Assertion* next = GetForwardArcs(aSource);
+ Assertion* prev = next;
+ Assertion* as = nullptr;
+
+ bool haveHash = (next) ? next->mHashEntry : false;
+ if (haveHash) {
+ PLDHashEntryHdr* hdr = next->u.hash.mPropertyHash->Search(aProperty);
+ Assertion* val = hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ while (val) {
+ if (val->u.as.mTarget == aTarget) {
+ // Wow, we already had the assertion. Make sure that the
+ // truth values are correct and bail.
+ val->u.as.mTruthValue = aTruthValue;
+ return NS_OK;
+ }
+ val = val->mNext;
+ }
+ }
+ else
+ {
+ while (next) {
+ // check target first as its most unique
+ if (aTarget == next->u.as.mTarget) {
+ if (aProperty == next->u.as.mProperty) {
+ // Wow, we already had the assertion. Make sure that the
+ // truth values are correct and bail.
+ next->u.as.mTruthValue = aTruthValue;
+ return NS_OK;
+ }
+ }
+
+ prev = next;
+ next = next->mNext;
+ }
+ }
+
+ as = new Assertion(aSource, aProperty, aTarget, aTruthValue);
+ if (! as)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // Add the datasource's owning reference.
+ as->AddRef();
+
+ if (haveHash)
+ {
+ PLDHashEntryHdr* hdr = next->u.hash.mPropertyHash->Search(aProperty);
+ Assertion *asRef =
+ hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ if (asRef)
+ {
+ as->mNext = asRef->mNext;
+ asRef->mNext = as;
+ }
+ else
+ {
+ hdr = next->u.hash.mPropertyHash->Add(aProperty, mozilla::fallible);
+ if (hdr)
+ {
+ Entry* entry = static_cast<Entry*>(hdr);
+ entry->mNode = aProperty;
+ entry->mAssertions = as;
+ }
+ }
+ }
+ else
+ {
+ // Link it in to the "forward arcs" table
+ if (!prev) {
+ SetForwardArcs(aSource, as);
+ } else {
+ prev->mNext = as;
+ }
+ }
+
+ // Link it in to the "reverse arcs" table
+
+ next = GetReverseArcs(aTarget);
+ as->u.as.mInvNext = next;
+ next = as;
+ SetReverseArcs(aTarget, next);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::Assert(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mReadCount) {
+ NS_WARNING("Writing to InMemoryDataSource during read\n");
+ return NS_RDF_ASSERTION_REJECTED;
+ }
+
+ nsresult rv;
+ rv = LockedAssert(aSource, aProperty, aTarget, aTruthValue);
+ if (NS_FAILED(rv)) return rv;
+
+ // notify observers
+ for (int32_t i = (int32_t)mNumObservers - 1; mPropagateChanges && i >= 0; --i) {
+ nsIRDFObserver* obs = mObservers[i];
+
+ // XXX this should never happen, but it does, and we can't figure out why.
+ NS_ASSERTION(obs, "observer array corrupted!");
+ if (! obs)
+ continue;
+
+ obs->OnAssert(this, aSource, aProperty, aTarget);
+ // XXX ignore return value?
+ }
+
+ return NS_RDF_ASSERTION_ACCEPTED;
+}
+
+
+nsresult
+InMemoryDataSource::LockedUnassert(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ LogOperation("UNASSERT", aSource, aProperty, aTarget);
+
+ Assertion* next = GetForwardArcs(aSource);
+ Assertion* prev = next;
+ Assertion* root = next;
+ Assertion* as = nullptr;
+
+ bool haveHash = (next) ? next->mHashEntry : false;
+ if (haveHash) {
+ PLDHashEntryHdr* hdr = next->u.hash.mPropertyHash->Search(aProperty);
+ prev = next = hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ bool first = true;
+ while (next) {
+ if (aTarget == next->u.as.mTarget) {
+ break;
+ }
+ first = false;
+ prev = next;
+ next = next->mNext;
+ }
+ // We don't even have the assertion, so just bail.
+ if (!next)
+ return NS_OK;
+
+ as = next;
+
+ if (first) {
+ root->u.hash.mPropertyHash->RawRemove(hdr);
+
+ if (next && next->mNext) {
+ PLDHashEntryHdr* hdr =
+ root->u.hash.mPropertyHash->Add(aProperty,
+ mozilla::fallible);
+ if (hdr) {
+ Entry* entry = static_cast<Entry*>(hdr);
+ entry->mNode = aProperty;
+ entry->mAssertions = next->mNext;
+ }
+ }
+ else {
+ // If this second-level hash empties out, clean it up.
+ if (!root->u.hash.mPropertyHash->EntryCount()) {
+ root->Release();
+ SetForwardArcs(aSource, nullptr);
+ }
+ }
+ }
+ else {
+ prev->mNext = next->mNext;
+ }
+ }
+ else
+ {
+ while (next) {
+ // check target first as its most unique
+ if (aTarget == next->u.as.mTarget) {
+ if (aProperty == next->u.as.mProperty) {
+ if (prev == next) {
+ SetForwardArcs(aSource, next->mNext);
+ } else {
+ prev->mNext = next->mNext;
+ }
+ as = next;
+ break;
+ }
+ }
+
+ prev = next;
+ next = next->mNext;
+ }
+ }
+ // We don't even have the assertion, so just bail.
+ if (!as)
+ return NS_OK;
+
+#ifdef DEBUG
+ bool foundReverseArc = false;
+#endif
+
+ next = prev = GetReverseArcs(aTarget);
+ while (next) {
+ if (next == as) {
+ if (prev == next) {
+ SetReverseArcs(aTarget, next->u.as.mInvNext);
+ } else {
+ prev->u.as.mInvNext = next->u.as.mInvNext;
+ }
+#ifdef DEBUG
+ foundReverseArc = true;
+#endif
+ break;
+ }
+ prev = next;
+ next = next->u.as.mInvNext;
+ }
+
+#ifdef DEBUG
+ NS_ASSERTION(foundReverseArc, "in-memory db corrupted: unable to find reverse arc");
+#endif
+
+ // Unlink, and release the datasource's reference
+ as->mNext = as->u.as.mInvNext = nullptr;
+ as->Release();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::Unassert(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mReadCount) {
+ NS_WARNING("Writing to InMemoryDataSource during read\n");
+ return NS_RDF_ASSERTION_REJECTED;
+ }
+
+ nsresult rv;
+
+ rv = LockedUnassert(aSource, aProperty, aTarget);
+ if (NS_FAILED(rv)) return rv;
+
+ // Notify the world
+ for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) {
+ nsIRDFObserver* obs = mObservers[i];
+
+ // XXX this should never happen, but it does, and we can't figure out why.
+ NS_ASSERTION(obs, "observer array corrupted!");
+ if (! obs)
+ continue;
+
+ obs->OnUnassert(this, aSource, aProperty, aTarget);
+ // XXX ignore return value?
+ }
+
+ return NS_RDF_ASSERTION_ACCEPTED;
+}
+
+
+NS_IMETHODIMP
+InMemoryDataSource::Change(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aOldTarget != nullptr, "null ptr");
+ if (! aOldTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aNewTarget != nullptr, "null ptr");
+ if (! aNewTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mReadCount) {
+ NS_WARNING("Writing to InMemoryDataSource during read\n");
+ return NS_RDF_ASSERTION_REJECTED;
+ }
+
+ nsresult rv;
+
+ // XXX We can implement LockedChange() if we decide that this
+ // is a performance bottleneck.
+
+ rv = LockedUnassert(aSource, aProperty, aOldTarget);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = LockedAssert(aSource, aProperty, aNewTarget, true);
+ if (NS_FAILED(rv)) return rv;
+
+ // Notify the world
+ for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) {
+ nsIRDFObserver* obs = mObservers[i];
+
+ // XXX this should never happen, but it does, and we can't figure out why.
+ NS_ASSERTION(obs, "observer array corrupted!");
+ if (! obs)
+ continue;
+
+ obs->OnChange(this, aSource, aProperty, aOldTarget, aNewTarget);
+ // XXX ignore return value?
+ }
+
+ return NS_RDF_ASSERTION_ACCEPTED;
+}
+
+
+NS_IMETHODIMP
+InMemoryDataSource::Move(nsIRDFResource* aOldSource,
+ nsIRDFResource* aNewSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ NS_PRECONDITION(aOldSource != nullptr, "null ptr");
+ if (! aOldSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aNewSource != nullptr, "null ptr");
+ if (! aNewSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ if (mReadCount) {
+ NS_WARNING("Writing to InMemoryDataSource during read\n");
+ return NS_RDF_ASSERTION_REJECTED;
+ }
+
+ nsresult rv;
+
+ // XXX We can implement LockedMove() if we decide that this
+ // is a performance bottleneck.
+
+ rv = LockedUnassert(aOldSource, aProperty, aTarget);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = LockedAssert(aNewSource, aProperty, aTarget, true);
+ if (NS_FAILED(rv)) return rv;
+
+ // Notify the world
+ for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) {
+ nsIRDFObserver* obs = mObservers[i];
+
+ // XXX this should never happen, but it does, and we can't figure out why.
+ NS_ASSERTION(obs, "observer array corrupted!");
+ if (! obs)
+ continue;
+
+ obs->OnMove(this, aOldSource, aNewSource, aProperty, aTarget);
+ // XXX ignore return value?
+ }
+
+ return NS_RDF_ASSERTION_ACCEPTED;
+}
+
+
+NS_IMETHODIMP
+InMemoryDataSource::AddObserver(nsIRDFObserver* aObserver)
+{
+ NS_PRECONDITION(aObserver != nullptr, "null ptr");
+ if (! aObserver)
+ return NS_ERROR_NULL_POINTER;
+
+ mObservers.AppendObject(aObserver);
+ mNumObservers = mObservers.Count();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::RemoveObserver(nsIRDFObserver* aObserver)
+{
+ NS_PRECONDITION(aObserver != nullptr, "null ptr");
+ if (! aObserver)
+ return NS_ERROR_NULL_POINTER;
+
+ mObservers.RemoveObject(aObserver);
+ // note: use Count() instead of just decrementing
+ // in case aObserver wasn't in list, for example
+ mNumObservers = mObservers.Count();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result)
+{
+ Assertion* ass = GetReverseArcs(aNode);
+ while (ass) {
+ nsIRDFResource* elbow = ass->u.as.mProperty;
+ if (elbow == aArc) {
+ *result = true;
+ return NS_OK;
+ }
+ ass = ass->u.as.mInvNext;
+ }
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result)
+{
+ Assertion* ass = GetForwardArcs(aSource);
+ if (ass && ass->mHashEntry) {
+ PLDHashEntryHdr* hdr = ass->u.hash.mPropertyHash->Search(aArc);
+ Assertion* val = hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ if (val) {
+ *result = true;
+ return NS_OK;
+ }
+ ass = ass->mNext;
+ }
+ while (ass) {
+ nsIRDFResource* elbow = ass->u.as.mProperty;
+ if (elbow == aArc) {
+ *result = true;
+ return NS_OK;
+ }
+ ass = ass->mNext;
+ }
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::ArcLabelsIn(nsIRDFNode* aTarget, nsISimpleEnumerator** aResult)
+{
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ InMemoryArcsEnumeratorImpl* result =
+ new InMemoryArcsEnumeratorImpl(this, nullptr, aTarget);
+
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(result);
+ *aResult = result;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::ArcLabelsOut(nsIRDFResource* aSource, nsISimpleEnumerator** aResult)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ InMemoryArcsEnumeratorImpl* result =
+ new InMemoryArcsEnumeratorImpl(this, aSource, nullptr);
+
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(result);
+ *aResult = result;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+InMemoryDataSource::GetAllResources(nsISimpleEnumerator** aResult)
+{
+ nsCOMArray<nsIRDFNode> nodes;
+ nodes.SetCapacity(mForwardArcs.EntryCount());
+
+ // Get all of our entries into an nsCOMArray
+ for (auto iter = mForwardArcs.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<Entry*>(iter.Get());
+ nodes.AppendObject(entry->mNode);
+ }
+ return NS_NewArrayEnumerator(aResult, nodes);
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::GetAllCmds(nsIRDFResource* source,
+ nsISimpleEnumerator/*<nsIRDFResource>*/** commands)
+{
+ return(NS_NewEmptyEnumerator(commands));
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::IsCommandEnabled(nsISupports* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports* aArguments,
+ bool* aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::DoCommand(nsISupports* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports* aArguments)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::BeginUpdateBatch()
+{
+ for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) {
+ nsIRDFObserver* obs = mObservers[i];
+ obs->OnBeginUpdateBatch(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::EndUpdateBatch()
+{
+ for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) {
+ nsIRDFObserver* obs = mObservers[i];
+ obs->OnEndUpdateBatch(this);
+ }
+ return NS_OK;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////
+// nsIRDFInMemoryDataSource methods
+
+NS_IMETHODIMP
+InMemoryDataSource::EnsureFastContainment(nsIRDFResource* aSource)
+{
+ Assertion *as = GetForwardArcs(aSource);
+ bool haveHash = (as) ? as->mHashEntry : false;
+
+ // if its already a hash, then nothing to do
+ if (haveHash) return(NS_OK);
+
+ // convert aSource in forward hash into a hash
+ Assertion *hashAssertion = new Assertion(aSource);
+ NS_ASSERTION(hashAssertion, "unable to create Assertion");
+ if (!hashAssertion) return(NS_ERROR_OUT_OF_MEMORY);
+
+ // Add the datasource's owning reference.
+ hashAssertion->AddRef();
+
+ Assertion *first = GetForwardArcs(aSource);
+ SetForwardArcs(aSource, hashAssertion);
+
+ // mutate references of existing forward assertions into this hash
+ PLDHashTable *table = hashAssertion->u.hash.mPropertyHash;
+ Assertion *nextRef;
+ while(first) {
+ nextRef = first->mNext;
+ nsIRDFResource *prop = first->u.as.mProperty;
+
+ PLDHashEntryHdr* hdr = table->Search(prop);
+ Assertion* val = hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ if (val) {
+ first->mNext = val->mNext;
+ val->mNext = first;
+ }
+ else {
+ PLDHashEntryHdr* hdr = table->Add(prop, mozilla::fallible);
+ if (hdr) {
+ Entry* entry = static_cast<Entry*>(hdr);
+ entry->mNode = prop;
+ entry->mAssertions = first;
+ first->mNext = nullptr;
+ }
+ }
+ first = nextRef;
+ }
+ return(NS_OK);
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// nsIRDFPropagatableDataSource methods
+NS_IMETHODIMP
+InMemoryDataSource::GetPropagateChanges(bool* aPropagateChanges)
+{
+ *aPropagateChanges = mPropagateChanges;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::SetPropagateChanges(bool aPropagateChanges)
+{
+ mPropagateChanges = aPropagateChanges;
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// nsIRDFPurgeableDataSource methods
+
+NS_IMETHODIMP
+InMemoryDataSource::Mark(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue,
+ bool* aDidMark)
+{
+ NS_PRECONDITION(aSource != nullptr, "null ptr");
+ if (! aSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aTarget != nullptr, "null ptr");
+ if (! aTarget)
+ return NS_ERROR_NULL_POINTER;
+
+ Assertion *as = GetForwardArcs(aSource);
+ if (as && as->mHashEntry) {
+ PLDHashEntryHdr* hdr = as->u.hash.mPropertyHash->Search(aProperty);
+ Assertion* val = hdr ? static_cast<Entry*>(hdr)->mAssertions : nullptr;
+ while (val) {
+ if ((val->u.as.mTarget == aTarget) &&
+ (aTruthValue == (val->u.as.mTruthValue))) {
+
+ // found it! so mark it.
+ as->Mark();
+ *aDidMark = true;
+
+ LogOperation("MARK", aSource, aProperty, aTarget, aTruthValue);
+
+ return NS_OK;
+ }
+ val = val->mNext;
+ }
+ }
+ else for (; as != nullptr; as = as->mNext) {
+ // check target first as its most unique
+ if (aTarget != as->u.as.mTarget)
+ continue;
+
+ if (aProperty != as->u.as.mProperty)
+ continue;
+
+ if (aTruthValue != (as->u.as.mTruthValue))
+ continue;
+
+ // found it! so mark it.
+ as->Mark();
+ *aDidMark = true;
+
+ LogOperation("MARK", aSource, aProperty, aTarget, aTruthValue);
+
+ return NS_OK;
+ }
+
+ // If we get here, we couldn't find the assertion
+ *aDidMark = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::Sweep()
+{
+ SweepInfo info = { nullptr, &mReverseArcs };
+
+ // Remove all the assertions, but don't notify anyone.
+ SweepForwardArcsEntries(&mForwardArcs, &info);
+
+ // Now do the notification.
+ Assertion* as = info.mUnassertList;
+ while (as) {
+ LogOperation("SWEEP", as->mSource, as->u.as.mProperty, as->u.as.mTarget, as->u.as.mTruthValue);
+ if (!(as->mHashEntry))
+ {
+ for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) {
+ nsIRDFObserver* obs = mObservers[i];
+ // XXXbz other loops over mObservers null-check |obs| here!
+ obs->OnUnassert(this, as->mSource, as->u.as.mProperty, as->u.as.mTarget);
+ // XXX ignore return value?
+ }
+ }
+
+ Assertion* doomed = as;
+ as = as->mNext;
+
+ // Unlink, and release the datasource's reference
+ doomed->mNext = doomed->u.as.mInvNext = nullptr;
+ doomed->Release();
+ }
+
+ return NS_OK;
+}
+
+
+void
+InMemoryDataSource::SweepForwardArcsEntries(PLDHashTable* aTable,
+ SweepInfo* aInfo)
+{
+ for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<Entry*>(iter.Get());
+
+ Assertion* as = entry->mAssertions;
+ if (as && (as->mHashEntry)) {
+ // Stuff in sub-hashes must be swept recursively (max depth: 1)
+ SweepForwardArcsEntries(as->u.hash.mPropertyHash, aInfo);
+
+ // If the sub-hash is now empty, clean it up.
+ if (!as->u.hash.mPropertyHash->EntryCount()) {
+ as->Release();
+ iter.Remove();
+ }
+ continue;
+ }
+
+ Assertion* prev = nullptr;
+ while (as) {
+ if (as->IsMarked()) {
+ prev = as;
+ as->Unmark();
+ as = as->mNext;
+ }
+ else {
+ // remove from the list of assertions in the datasource
+ Assertion* next = as->mNext;
+ if (prev) {
+ prev->mNext = next;
+ }
+ else {
+ // it's the first one. update the hashtable entry.
+ entry->mAssertions = next;
+ }
+
+ // remove from the reverse arcs
+ PLDHashEntryHdr* hdr =
+ aInfo->mReverseArcs->Search(as->u.as.mTarget);
+ NS_ASSERTION(hdr, "no assertion in reverse arcs");
+
+ Entry* rentry = static_cast<Entry*>(hdr);
+ Assertion* ras = rentry->mAssertions;
+ Assertion* rprev = nullptr;
+ while (ras) {
+ if (ras == as) {
+ if (rprev) {
+ rprev->u.as.mInvNext = ras->u.as.mInvNext;
+ }
+ else {
+ // it's the first one. update the hashtable entry.
+ rentry->mAssertions = ras->u.as.mInvNext;
+ }
+ as->u.as.mInvNext = nullptr; // for my sanity.
+ break;
+ }
+ rprev = ras;
+ ras = ras->u.as.mInvNext;
+ }
+
+ // Wow, it was the _only_ one. Unhash it.
+ if (! rentry->mAssertions) {
+ aInfo->mReverseArcs->RawRemove(hdr);
+ }
+
+ // add to the list of assertions to unassert
+ as->mNext = aInfo->mUnassertList;
+ aInfo->mUnassertList = as;
+
+ // Advance to the next assertion
+ as = next;
+ }
+ }
+
+ // if no more assertions exist for this resource, then unhash it.
+ if (! entry->mAssertions) {
+ iter.Remove();
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// rdfIDataSource methods
+
+NS_IMETHODIMP
+InMemoryDataSource::VisitAllSubjects(rdfITripleVisitor *aVisitor)
+{
+ // Lock datasource against writes
+ ++mReadCount;
+
+ // Enumerate all of our entries.
+ nsresult rv = NS_OK;
+ for (auto iter = mForwardArcs.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<Entry*>(iter.Get());
+ nsresult rv2;
+ nsCOMPtr<nsIRDFNode> subject = do_QueryInterface(entry->mNode, &rv2);
+ if (NS_FAILED(rv2)) {
+ NS_WARNING("QI to nsIRDFNode failed");
+ continue;
+ }
+ rv = aVisitor->Visit(subject, nullptr, nullptr, true);
+ if (NS_FAILED(rv) || rv == NS_RDF_STOP_VISIT) {
+ break;
+ }
+ }
+
+ // Unlock datasource
+ --mReadCount;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+InMemoryDataSource::VisitAllTriples(rdfITripleVisitor *aVisitor)
+{
+ // Lock datasource against writes
+ ++mReadCount;
+
+ // Enumerate all of our entries.
+ nsresult rv = NS_OK;
+ for (auto iter = mForwardArcs.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<Entry*>(iter.Get());
+
+ nsresult rv2;
+ nsCOMPtr<nsIRDFNode> subject = do_QueryInterface(entry->mNode, &rv2);
+ if (NS_FAILED(rv2)) {
+ NS_WARNING("QI to nsIRDFNode failed");
+
+ } else if (entry->mAssertions->mHashEntry) {
+ for (auto iter = entry->mAssertions->u.hash.mPropertyHash->Iter();
+ !iter.Done();
+ iter.Next()) {
+ auto entry = static_cast<Entry*>(iter.Get());
+ Assertion* assertion = entry->mAssertions;
+ while (assertion) {
+ NS_ASSERTION(!assertion->mHashEntry, "shouldn't have to hashes");
+ rv = aVisitor->Visit(subject, assertion->u.as.mProperty,
+ assertion->u.as.mTarget,
+ assertion->u.as.mTruthValue);
+ if (NS_FAILED(rv)) {
+ goto end;
+ }
+ if (rv == NS_RDF_STOP_VISIT) {
+ goto inner_end;
+ }
+ assertion = assertion->mNext;
+ }
+ }
+
+ } else {
+ Assertion* assertion = entry->mAssertions;
+ while (assertion) {
+ NS_ASSERTION(!assertion->mHashEntry, "shouldn't have to hashes");
+ rv = aVisitor->Visit(subject, assertion->u.as.mProperty,
+ assertion->u.as.mTarget,
+ assertion->u.as.mTruthValue);
+ if (NS_FAILED(rv) || rv == NS_RDF_STOP_VISIT) {
+ goto end;
+ }
+ assertion = assertion->mNext;
+ }
+ }
+
+ inner_end:
+ (void) 0;
+ }
+
+ end:
+ // Unlock datasource
+ --mReadCount;
+
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////
+
diff --git a/components/rdf/src/nsLocalStore.cpp b/components/rdf/src/nsLocalStore.cpp
new file mode 100644
index 000000000..d5750ff70
--- /dev/null
+++ b/components/rdf/src/nsLocalStore.cpp
@@ -0,0 +1,480 @@
+/* -*- 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/. */
+
+/*
+
+ Implementation for the local store
+
+ */
+
+#include "nsNetUtil.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsIIOService.h"
+#include "nsIOutputStream.h"
+#include "nsIComponentManager.h"
+#include "nsILocalStore.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFRemoteDataSource.h"
+#include "nsIRDFService.h"
+#include "nsIServiceManager.h"
+#include "nsRDFCID.h"
+#include "nsXPIDLString.h"
+#include "plstr.h"
+#include "rdf.h"
+#include "nsCOMPtr.h"
+#include "nsWeakPtr.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsWeakReference.h"
+#include "nsCRTGlue.h"
+#include "nsCRT.h"
+#include "nsEnumeratorUtils.h"
+#include "nsCycleCollectionParticipant.h"
+
+////////////////////////////////////////////////////////////////////////
+
+class LocalStoreImpl : public nsILocalStore,
+ public nsIRDFDataSource,
+ public nsIRDFRemoteDataSource,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+protected:
+ nsCOMPtr<nsIRDFDataSource> mInner;
+
+ LocalStoreImpl();
+ virtual ~LocalStoreImpl();
+ nsresult Init();
+ nsresult CreateLocalStore(nsIFile* aFile);
+ nsresult LoadData();
+
+ friend nsresult
+ NS_NewLocalStore(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ nsCOMPtr<nsIRDFService> mRDFService;
+
+public:
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(LocalStoreImpl, nsILocalStore)
+
+ // nsILocalStore interface
+
+ // nsIRDFDataSource interface. Most of these are just delegated to
+ // the inner, in-memory datasource.
+ NS_IMETHOD GetURI(char* *aURI) override;
+
+ NS_IMETHOD GetSource(nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue,
+ nsIRDFResource** aSource) override {
+ return mInner->GetSource(aProperty, aTarget, aTruthValue, aSource);
+ }
+
+ NS_IMETHOD GetSources(nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue,
+ nsISimpleEnumerator** aSources) override {
+ return mInner->GetSources(aProperty, aTarget, aTruthValue, aSources);
+ }
+
+ NS_IMETHOD GetTarget(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ bool aTruthValue,
+ nsIRDFNode** aTarget) override {
+ return mInner->GetTarget(aSource, aProperty, aTruthValue, aTarget);
+ }
+
+ NS_IMETHOD GetTargets(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ bool aTruthValue,
+ nsISimpleEnumerator** aTargets) override {
+ return mInner->GetTargets(aSource, aProperty, aTruthValue, aTargets);
+ }
+
+ NS_IMETHOD Assert(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue) override {
+ return mInner->Assert(aSource, aProperty, aTarget, aTruthValue);
+ }
+
+ NS_IMETHOD Unassert(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget) override {
+ return mInner->Unassert(aSource, aProperty, aTarget);
+ }
+
+ NS_IMETHOD Change(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget) override {
+ return mInner->Change(aSource, aProperty, aOldTarget, aNewTarget);
+ }
+
+ NS_IMETHOD Move(nsIRDFResource* aOldSource,
+ nsIRDFResource* aNewSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget) override {
+ return mInner->Move(aOldSource, aNewSource, aProperty, aTarget);
+ }
+
+ NS_IMETHOD HasAssertion(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue,
+ bool* hasAssertion) override {
+ return mInner->HasAssertion(aSource, aProperty, aTarget, aTruthValue, hasAssertion);
+ }
+
+ NS_IMETHOD AddObserver(nsIRDFObserver* aObserver) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD RemoveObserver(nsIRDFObserver* aObserver) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *_retval) override {
+ return mInner->HasArcIn(aNode, aArc, _retval);
+ }
+
+ NS_IMETHOD HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *_retval) override {
+ return mInner->HasArcOut(aSource, aArc, _retval);
+ }
+
+ NS_IMETHOD ArcLabelsIn(nsIRDFNode* aNode,
+ nsISimpleEnumerator** aLabels) override {
+ return mInner->ArcLabelsIn(aNode, aLabels);
+ }
+
+ NS_IMETHOD ArcLabelsOut(nsIRDFResource* aSource,
+ nsISimpleEnumerator** aLabels) override {
+ return mInner->ArcLabelsOut(aSource, aLabels);
+ }
+
+ NS_IMETHOD GetAllResources(nsISimpleEnumerator** aResult) override {
+ return mInner->GetAllResources(aResult);
+ }
+
+ NS_IMETHOD GetAllCmds(nsIRDFResource* aSource,
+ nsISimpleEnumerator/*<nsIRDFResource>*/** aCommands) override;
+
+ NS_IMETHOD IsCommandEnabled(nsISupports* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports* aArguments,
+ bool* aResult) override;
+
+ NS_IMETHOD DoCommand(nsISupports* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports* aArguments) override;
+
+ NS_IMETHOD BeginUpdateBatch() override {
+ return mInner->BeginUpdateBatch();
+ }
+
+ NS_IMETHOD EndUpdateBatch() override {
+ return mInner->EndUpdateBatch();
+ }
+
+ NS_IMETHOD GetLoaded(bool* _result) override;
+ NS_IMETHOD Init(const char *uri) override;
+ NS_IMETHOD Flush() override;
+ NS_IMETHOD FlushTo(const char *aURI) override;
+ NS_IMETHOD Refresh(bool sync) override;
+
+ // nsIObserver
+ NS_DECL_NSIOBSERVER
+};
+
+////////////////////////////////////////////////////////////////////////
+
+
+LocalStoreImpl::LocalStoreImpl(void)
+{
+}
+
+LocalStoreImpl::~LocalStoreImpl(void)
+{
+ if (mRDFService)
+ mRDFService->UnregisterDataSource(this);
+}
+
+
+nsresult
+NS_NewLocalStore(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+ NS_PRECONDITION(aOuter == nullptr, "no aggregation");
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ LocalStoreImpl* impl = new LocalStoreImpl();
+ if (! impl)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(impl);
+
+ nsresult rv;
+ rv = impl->Init();
+ if (NS_SUCCEEDED(rv)) {
+ // Set up the result pointer
+ rv = impl->QueryInterface(aIID, aResult);
+ }
+
+ NS_RELEASE(impl);
+ return rv;
+}
+
+NS_IMPL_CYCLE_COLLECTION(LocalStoreImpl, mInner)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LocalStoreImpl)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LocalStoreImpl)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LocalStoreImpl)
+ NS_INTERFACE_MAP_ENTRY(nsILocalStore)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFRemoteDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsILocalStore)
+NS_INTERFACE_MAP_END
+
+// nsILocalStore interface
+
+// nsIRDFDataSource interface
+
+NS_IMETHODIMP
+LocalStoreImpl::GetLoaded(bool* _result)
+{
+ nsCOMPtr<nsIRDFRemoteDataSource> remote = do_QueryInterface(mInner);
+ NS_ASSERTION(remote != nullptr, "not an nsIRDFRemoteDataSource");
+ if (! remote)
+ return NS_ERROR_UNEXPECTED;
+
+ return remote->GetLoaded(_result);
+}
+
+
+NS_IMETHODIMP
+LocalStoreImpl::Init(const char *uri)
+{
+ return(NS_OK);
+}
+
+NS_IMETHODIMP
+LocalStoreImpl::Flush()
+{
+ nsCOMPtr<nsIRDFRemoteDataSource> remote = do_QueryInterface(mInner);
+ // FIXME Bug 340242: Temporarily make this a warning rather than an
+ // assertion until we sort out the ordering of how we write
+ // everything to the localstore, flush it, and disconnect it when
+ // we're getting profile-change notifications.
+ NS_WARNING_ASSERTION(remote != nullptr, "not an nsIRDFRemoteDataSource");
+ if (! remote)
+ return NS_ERROR_UNEXPECTED;
+
+ return remote->Flush();
+}
+
+NS_IMETHODIMP
+LocalStoreImpl::FlushTo(const char *aURI)
+{
+ // Do not ever implement this (security)
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStoreImpl::Refresh(bool sync)
+{
+ nsCOMPtr<nsIRDFRemoteDataSource> remote = do_QueryInterface(mInner);
+ NS_ASSERTION(remote != nullptr, "not an nsIRDFRemoteDataSource");
+ if (! remote)
+ return NS_ERROR_UNEXPECTED;
+
+ return remote->Refresh(sync);
+}
+
+nsresult
+LocalStoreImpl::Init()
+{
+ nsresult rv;
+
+ rv = LoadData();
+ if (NS_FAILED(rv)) return rv;
+
+ // register this as a named data source with the RDF service
+ mRDFService = do_GetService(NS_RDF_CONTRACTID "/rdf-service;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ mRDFService->RegisterDataSource(this, false);
+
+ // Register as an observer of profile changes
+ nsCOMPtr<nsIObserverService> obs =
+ do_GetService("@mozilla.org/observer-service;1");
+
+ if (obs) {
+ obs->AddObserver(this, "profile-before-change", true);
+ obs->AddObserver(this, "profile-do-change", true);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+LocalStoreImpl::CreateLocalStore(nsIFile* aFile)
+{
+ nsresult rv;
+
+ rv = aFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> outStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStream), aFile);
+ if (NS_FAILED(rv)) return rv;
+
+ const char defaultRDF[] =
+ "<?xml version=\"1.0\"?>\n" \
+ "<RDF:RDF xmlns:RDF=\"" RDF_NAMESPACE_URI "\"\n" \
+ " xmlns:NC=\"" NC_NAMESPACE_URI "\">\n" \
+ " <!-- Empty -->\n" \
+ "</RDF:RDF>\n";
+
+ uint32_t count;
+ rv = outStream->Write(defaultRDF, sizeof(defaultRDF)-1, &count);
+ if (NS_FAILED(rv)) return rv;
+
+ if (count != sizeof(defaultRDF)-1)
+ return NS_ERROR_UNEXPECTED;
+
+ // Okay, now see if the file exists _for real_. If it's still
+ // not there, it could be that the profile service gave us
+ // back a read-only directory. Whatever.
+ bool fileExistsFlag = false;
+ aFile->Exists(&fileExistsFlag);
+ if (!fileExistsFlag)
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+nsresult
+LocalStoreImpl::LoadData()
+{
+ nsresult rv;
+
+ // Look for localstore.rdf in the current profile
+ // directory. Bomb if we can't find it.
+
+ nsCOMPtr<nsIFile> aFile;
+ rv = NS_GetSpecialDirectory(NS_APP_LOCALSTORE_50_FILE, getter_AddRefs(aFile));
+ if (NS_FAILED(rv)) return rv;
+
+ bool fileExistsFlag = false;
+ (void)aFile->Exists(&fileExistsFlag);
+ if (!fileExistsFlag) {
+ // if file doesn't exist, create it
+ rv = CreateLocalStore(aFile);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ mInner = do_CreateInstance(NS_RDF_DATASOURCE_CONTRACTID_PREFIX "xml-datasource", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFRemoteDataSource> remote = do_QueryInterface(mInner, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> aURI;
+ rv = NS_NewFileURI(getter_AddRefs(aURI), aFile);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = remote->Init(spec.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // Read the datasource synchronously.
+ rv = remote->Refresh(true);
+
+ if (NS_FAILED(rv)) {
+ // Load failed, delete and recreate a fresh localstore
+ aFile->Remove(true);
+ rv = CreateLocalStore(aFile);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = remote->Refresh(true);
+ }
+
+ return rv;
+}
+
+
+NS_IMETHODIMP
+LocalStoreImpl::GetURI(char* *aURI)
+{
+ NS_PRECONDITION(aURI != nullptr, "null ptr");
+ if (! aURI)
+ return NS_ERROR_NULL_POINTER;
+
+ *aURI = NS_strdup("rdf:local-store");
+ if (! *aURI)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+LocalStoreImpl::GetAllCmds(nsIRDFResource* aSource,
+ nsISimpleEnumerator/*<nsIRDFResource>*/** aCommands)
+{
+ return(NS_NewEmptyEnumerator(aCommands));
+}
+
+NS_IMETHODIMP
+LocalStoreImpl::IsCommandEnabled(nsISupports* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports* aArguments,
+ bool* aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStoreImpl::DoCommand(nsISupports* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports* aArguments)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LocalStoreImpl::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ nsresult rv = NS_OK;
+
+ if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
+ // Write out the old datasource's contents.
+ if (mInner) {
+ nsCOMPtr<nsIRDFRemoteDataSource> remote = do_QueryInterface(mInner);
+ if (remote)
+ remote->Flush();
+ }
+
+ // Create an in-memory datasource for use while we're
+ // profile-less.
+ mInner = do_CreateInstance(NS_RDF_DATASOURCE_CONTRACTID_PREFIX "in-memory-datasource");
+ }
+ else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
+ rv = LoadData();
+ }
+ return rv;
+}
diff --git a/components/rdf/src/nsNameSpaceMap.cpp b/components/rdf/src/nsNameSpaceMap.cpp
new file mode 100644
index 000000000..b486a233d
--- /dev/null
+++ b/components/rdf/src/nsNameSpaceMap.cpp
@@ -0,0 +1,64 @@
+/* -*- 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 "nsNameSpaceMap.h"
+#include "nsReadableUtils.h"
+
+nsNameSpaceMap::nsNameSpaceMap()
+ : mEntries(nullptr)
+{
+ MOZ_COUNT_CTOR(nsNameSpaceMap);
+}
+
+nsNameSpaceMap::~nsNameSpaceMap()
+{
+ MOZ_COUNT_DTOR(nsNameSpaceMap);
+
+ while (mEntries) {
+ Entry* doomed = mEntries;
+ mEntries = mEntries->mNext;
+ delete doomed;
+ }
+}
+
+nsresult
+nsNameSpaceMap::Put(const nsAString& aURI, nsIAtom* aPrefix)
+{
+ nsCString uriUTF8;
+ AppendUTF16toUTF8(aURI, uriUTF8);
+ return Put(uriUTF8, aPrefix);
+}
+
+nsresult
+nsNameSpaceMap::Put(const nsCSubstring& aURI, nsIAtom* aPrefix)
+{
+ Entry* entry;
+
+ // Make sure we're not adding a duplicate
+ for (entry = mEntries; entry != nullptr; entry = entry->mNext) {
+ if (entry->mURI == aURI || entry->mPrefix == aPrefix)
+ return NS_ERROR_FAILURE;
+ }
+
+ entry = new Entry(aURI, aPrefix);
+ if (! entry)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ entry->mNext = mEntries;
+ mEntries = entry;
+ return NS_OK;
+}
+
+nsNameSpaceMap::const_iterator
+nsNameSpaceMap::GetNameSpaceOf(const nsCSubstring& aURI) const
+{
+ for (Entry* entry = mEntries; entry != nullptr; entry = entry->mNext) {
+ if (StringBeginsWith(aURI, entry->mURI))
+ return const_iterator(entry);
+ }
+
+ return last();
+}
diff --git a/components/rdf/src/nsNameSpaceMap.h b/components/rdf/src/nsNameSpaceMap.h
new file mode 100644
index 000000000..bc7c02029
--- /dev/null
+++ b/components/rdf/src/nsNameSpaceMap.h
@@ -0,0 +1,98 @@
+/* -*- 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/. */
+
+#ifndef nsNameSpaceMap_h__
+#define nsNameSpaceMap_h__
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIAtom.h"
+
+class nsNameSpaceMap
+{
+public:
+ class Entry {
+ public:
+ Entry(const nsCSubstring& aURI, nsIAtom* aPrefix)
+ : mURI(aURI), mPrefix(aPrefix), mNext(nullptr) {
+ MOZ_COUNT_CTOR(nsNameSpaceMap::Entry); }
+
+ ~Entry() { MOZ_COUNT_DTOR(nsNameSpaceMap::Entry); }
+
+ nsCString mURI;
+ nsCOMPtr<nsIAtom> mPrefix;
+
+ Entry* mNext;
+ };
+
+ nsNameSpaceMap();
+ ~nsNameSpaceMap();
+
+ nsresult
+ Put(const nsAString& aURI, nsIAtom* aPrefix);
+
+ nsresult
+ Put(const nsCSubstring& aURI, nsIAtom* aPrefix);
+
+ class const_iterator {
+ protected:
+ friend class nsNameSpaceMap;
+
+ explicit const_iterator(const Entry* aCurrent)
+ : mCurrent(aCurrent) {}
+
+ const Entry* mCurrent;
+
+ public:
+ const_iterator()
+ : mCurrent(nullptr) {}
+
+ const_iterator(const const_iterator& iter)
+ : mCurrent(iter.mCurrent) {}
+
+ const_iterator&
+ operator=(const const_iterator& iter) {
+ mCurrent = iter.mCurrent;
+ return *this; }
+
+ const_iterator&
+ operator++() {
+ mCurrent = mCurrent->mNext;
+ return *this; }
+
+ const_iterator
+ operator++(int) {
+ const_iterator tmp(*this);
+ mCurrent = mCurrent->mNext;
+ return tmp; }
+
+ const Entry* operator->() const { return mCurrent; }
+
+ const Entry& operator*() const { return *mCurrent; }
+
+ bool
+ operator==(const const_iterator& iter) const {
+ return mCurrent == iter.mCurrent; }
+
+ bool
+ operator!=(const const_iterator& iter) const {
+ return ! iter.operator==(*this); }
+ };
+
+ const_iterator first() const {
+ return const_iterator(mEntries); }
+
+ const_iterator last() const {
+ return const_iterator(nullptr); }
+
+ const_iterator GetNameSpaceOf(const nsCSubstring& aURI) const;
+
+protected:
+ Entry* mEntries;
+};
+
+
+#endif // nsNameSpaceMap_h__
diff --git a/components/rdf/src/nsRDFBaseDataSources.h b/components/rdf/src/nsRDFBaseDataSources.h
new file mode 100644
index 000000000..0243e1359
--- /dev/null
+++ b/components/rdf/src/nsRDFBaseDataSources.h
@@ -0,0 +1,32 @@
+/* -*- 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/. */
+
+/*
+
+ This header file just contains prototypes for the factory methods
+ for "builtin" data sources that are included in rdf.dll.
+
+ Each of these data sources is exposed to the external world via its
+ CID in ../include/nsRDFCID.h.
+
+*/
+
+#ifndef nsBaseDataSources_h__
+#define nsBaseDataSources_h__
+
+#include "nsError.h"
+class nsIRDFDataSource;
+
+// in nsInMemoryDataSource.cpp
+nsresult
+NS_NewRDFInMemoryDataSource(nsISupports* aOuter, const nsIID& aIID, void** aResult);
+
+// in nsRDFXMLDataSource.cpp
+extern nsresult
+NS_NewRDFXMLDataSource(nsIRDFDataSource** aResult);
+
+#endif // nsBaseDataSources_h__
+
+
diff --git a/components/rdf/src/nsRDFBuiltInDataSources.h b/components/rdf/src/nsRDFBuiltInDataSources.h
new file mode 100644
index 000000000..297fe7164
--- /dev/null
+++ b/components/rdf/src/nsRDFBuiltInDataSources.h
@@ -0,0 +1,27 @@
+/* -*- 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/. */
+
+/*
+
+ This header file just contains prototypes for the factory methods
+ for "builtin" data sources that are included in rdf.dll.
+
+ Each of these data sources is exposed to the external world via its
+ CID in ../include/nsRDFCID.h.
+
+ */
+
+#ifndef nsBuiltinDataSources_h__
+#define nsBuiltinDataSources_h__
+
+#include "nsError.h"
+
+class nsIRDFDataSource;
+
+// in nsFileSystemDataSource.cpp
+nsresult NS_NewRDFFileSystemDataSource(nsIRDFDataSource** result);
+
+#endif // nsBuiltinDataSources_h__
+
diff --git a/components/rdf/src/nsRDFContainer.cpp b/components/rdf/src/nsRDFContainer.cpp
new file mode 100644
index 000000000..6000c70d5
--- /dev/null
+++ b/components/rdf/src/nsRDFContainer.cpp
@@ -0,0 +1,726 @@
+/* -*- 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/. */
+
+/*
+
+ Implementation for the RDF container.
+
+ Notes
+ -----
+
+ 1. RDF containers are one-indexed. This means that a lot of the loops
+ that you'd normally think you'd write like this:
+
+ for (i = 0; i < count; ++i) {}
+
+ You've gotta write like this:
+
+ for (i = 1; i <= count; ++i) {}
+
+ "Sure, right, yeah, of course.", you say. Well maybe I'm just
+ thick, but it's easy to slip up.
+
+ 2. The RDF:nextVal property on the container is an
+ implementation-level hack that is used to quickly compute the
+ next value for appending to the container. It will no doubt
+ become royally screwed up in the case of aggregation.
+
+ 3. The RDF:nextVal property is also used to retrieve the count of
+ elements in the container.
+
+ */
+
+
+#include "nsCOMPtr.h"
+#include "nsIRDFContainer.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIRDFInMemoryDataSource.h"
+#include "nsIRDFPropagatableDataSource.h"
+#include "nsIRDFService.h"
+#include "nsIServiceManager.h"
+#include "nsRDFCID.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "rdf.h"
+
+#define RDF_SEQ_LIST_LIMIT 8
+
+class RDFContainerImpl : public nsIRDFContainer
+{
+public:
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsIRDFContainer interface
+ NS_DECL_NSIRDFCONTAINER
+
+private:
+ friend nsresult NS_NewRDFContainer(nsIRDFContainer** aResult);
+
+ RDFContainerImpl();
+ virtual ~RDFContainerImpl();
+
+ nsresult Init();
+
+ nsresult Renumber(int32_t aStartIndex, int32_t aIncrement);
+ nsresult SetNextValue(int32_t aIndex);
+ nsresult GetNextValue(nsIRDFResource** aResult);
+
+ nsIRDFDataSource* mDataSource;
+ nsIRDFResource* mContainer;
+
+ // pseudo constants
+ static int32_t gRefCnt;
+ static nsIRDFService* gRDFService;
+ static nsIRDFContainerUtils* gRDFContainerUtils;
+ static nsIRDFResource* kRDF_nextVal;
+};
+
+
+int32_t RDFContainerImpl::gRefCnt = 0;
+nsIRDFService* RDFContainerImpl::gRDFService;
+nsIRDFContainerUtils* RDFContainerImpl::gRDFContainerUtils;
+nsIRDFResource* RDFContainerImpl::kRDF_nextVal;
+
+////////////////////////////////////////////////////////////////////////
+// nsISupports interface
+
+NS_IMPL_ISUPPORTS(RDFContainerImpl, nsIRDFContainer)
+
+
+
+////////////////////////////////////////////////////////////////////////
+// nsIRDFContainer interface
+
+NS_IMETHODIMP
+RDFContainerImpl::GetDataSource(nsIRDFDataSource** _retval)
+{
+ *_retval = mDataSource;
+ NS_IF_ADDREF(*_retval);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerImpl::GetResource(nsIRDFResource** _retval)
+{
+ *_retval = mContainer;
+ NS_IF_ADDREF(*_retval);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerImpl::Init(nsIRDFDataSource *aDataSource, nsIRDFResource *aContainer)
+{
+ NS_PRECONDITION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aContainer != nullptr, "null ptr");
+ if (! aContainer)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ bool isContainer;
+ rv = gRDFContainerUtils->IsContainer(aDataSource, aContainer, &isContainer);
+ if (NS_FAILED(rv)) return rv;
+
+ // ``throw'' if we can't create a container on the specified
+ // datasource/resource combination.
+ if (! isContainer)
+ return NS_ERROR_FAILURE;
+
+ NS_IF_RELEASE(mDataSource);
+ mDataSource = aDataSource;
+ NS_ADDREF(mDataSource);
+
+ NS_IF_RELEASE(mContainer);
+ mContainer = aContainer;
+ NS_ADDREF(mContainer);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerImpl::GetCount(int32_t *aCount)
+{
+ if (!mDataSource || !mContainer)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ // Get the next value, which hangs off of the bag via the
+ // RDF:nextVal property. This is the _next value_ that will get
+ // assigned in a one-indexed array. So, it's actually _one more_
+ // than the actual count of elements in the container.
+ //
+ // XXX To handle aggregation, this should probably be a
+ // GetTargets() that enumerates all of the values and picks the
+ // largest one.
+ nsCOMPtr<nsIRDFNode> nextValNode;
+ rv = mDataSource->GetTarget(mContainer, kRDF_nextVal, true, getter_AddRefs(nextValNode));
+ if (NS_FAILED(rv)) return rv;
+
+ if (rv == NS_RDF_NO_VALUE)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIRDFLiteral> nextValLiteral;
+ rv = nextValNode->QueryInterface(NS_GET_IID(nsIRDFLiteral), getter_AddRefs(nextValLiteral));
+ if (NS_FAILED(rv)) return rv;
+
+ const char16_t *s;
+ rv = nextValLiteral->GetValueConst( &s );
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString nextValStr(s);
+
+ int32_t nextVal;
+ nsresult err;
+ nextVal = nextValStr.ToInteger(&err);
+ if (NS_FAILED(err))
+ return NS_ERROR_UNEXPECTED;
+
+ *aCount = nextVal - 1;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerImpl::GetElements(nsISimpleEnumerator **_retval)
+{
+ if (!mDataSource || !mContainer)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return NS_NewContainerEnumerator(mDataSource, mContainer, _retval);
+}
+
+
+NS_IMETHODIMP
+RDFContainerImpl::AppendElement(nsIRDFNode *aElement)
+{
+ if (!mDataSource || !mContainer)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NS_PRECONDITION(aElement != nullptr, "null ptr");
+ if (! aElement)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIRDFResource> nextVal;
+ rv = GetNextValue(getter_AddRefs(nextVal));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mDataSource->Assert(mContainer, nextVal, aElement, true);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerImpl::RemoveElement(nsIRDFNode *aElement, bool aRenumber)
+{
+ if (!mDataSource || !mContainer)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NS_PRECONDITION(aElement != nullptr, "null ptr");
+ if (! aElement)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ int32_t idx;
+ rv = IndexOf(aElement, &idx);
+ if (NS_FAILED(rv)) return rv;
+
+ if (idx < 0)
+ return NS_OK;
+
+ // Remove the element.
+ nsCOMPtr<nsIRDFResource> ordinal;
+ rv = gRDFContainerUtils->IndexToOrdinalResource(idx,
+ getter_AddRefs(ordinal));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mDataSource->Unassert(mContainer, ordinal, aElement);
+ if (NS_FAILED(rv)) return rv;
+
+ if (aRenumber) {
+ // Now slide the rest of the collection backwards to fill in
+ // the gap. This will have the side effect of completely
+ // renumber the container from index to the end.
+ rv = Renumber(idx + 1, -1);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerImpl::InsertElementAt(nsIRDFNode *aElement, int32_t aIndex, bool aRenumber)
+{
+ if (!mDataSource || !mContainer)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NS_PRECONDITION(aElement != nullptr, "null ptr");
+ if (! aElement)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aIndex >= 1, "illegal value");
+ if (aIndex < 1)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ nsresult rv;
+
+ int32_t count;
+ rv = GetCount(&count);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(aIndex <= count + 1, "illegal value");
+ if (aIndex > count + 1)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (aRenumber) {
+ // Make a hole for the element. This will have the side effect of
+ // completely renumbering the container from 'aIndex' to 'count',
+ // and will spew assertions.
+ rv = Renumber(aIndex, +1);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIRDFResource> ordinal;
+ rv = gRDFContainerUtils->IndexToOrdinalResource(aIndex, getter_AddRefs(ordinal));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mDataSource->Assert(mContainer, ordinal, aElement, true);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContainerImpl::RemoveElementAt(int32_t aIndex, bool aRenumber, nsIRDFNode** _retval)
+{
+ if (!mDataSource || !mContainer)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ *_retval = nullptr;
+
+ if (aIndex< 1)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ nsresult rv;
+
+ int32_t count;
+ rv = GetCount(&count);
+ if (NS_FAILED(rv)) return rv;
+
+ if (aIndex > count)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ nsCOMPtr<nsIRDFResource> ordinal;
+ rv = gRDFContainerUtils->IndexToOrdinalResource(aIndex, getter_AddRefs(ordinal));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFNode> old;
+ rv = mDataSource->GetTarget(mContainer, ordinal, true, getter_AddRefs(old));
+ if (NS_FAILED(rv)) return rv;
+
+ if (rv == NS_OK) {
+ rv = mDataSource->Unassert(mContainer, ordinal, old);
+ if (NS_FAILED(rv)) return rv;
+
+ if (aRenumber) {
+ // Now slide the rest of the collection backwards to fill in
+ // the gap. This will have the side effect of completely
+ // renumber the container from index to the end.
+ rv = Renumber(aIndex + 1, -1);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ old.swap(*_retval);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContainerImpl::IndexOf(nsIRDFNode *aElement, int32_t *aIndex)
+{
+ if (!mDataSource || !mContainer)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return gRDFContainerUtils->IndexOf(mDataSource, mContainer,
+ aElement, aIndex);
+}
+
+
+////////////////////////////////////////////////////////////////////////
+
+
+RDFContainerImpl::RDFContainerImpl()
+ : mDataSource(nullptr), mContainer(nullptr)
+{
+}
+
+
+nsresult
+RDFContainerImpl::Init()
+{
+ if (gRefCnt++ == 0) {
+ nsresult rv;
+
+ NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+ rv = CallGetService(kRDFServiceCID, &gRDFService);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("unable to get RDF service");
+ return rv;
+ }
+
+ rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"),
+ &kRDF_nextVal);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID);
+ rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("unable to get RDF container utils service");
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+RDFContainerImpl::~RDFContainerImpl()
+{
+#ifdef DEBUG_REFS
+ --gInstanceCount;
+ fprintf(stdout, "%d - RDF: RDFContainerImpl\n", gInstanceCount);
+#endif
+
+ NS_IF_RELEASE(mContainer);
+ NS_IF_RELEASE(mDataSource);
+
+ if (--gRefCnt == 0) {
+ NS_IF_RELEASE(gRDFContainerUtils);
+ NS_IF_RELEASE(gRDFService);
+ NS_IF_RELEASE(kRDF_nextVal);
+ }
+}
+
+
+nsresult
+NS_NewRDFContainer(nsIRDFContainer** aResult)
+{
+ RDFContainerImpl* result = new RDFContainerImpl();
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+ rv = result->Init();
+ if (NS_FAILED(rv)) {
+ delete result;
+ return rv;
+ }
+
+ NS_ADDREF(result);
+ *aResult = result;
+ return NS_OK;
+}
+
+
+nsresult
+NS_NewRDFContainer(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aResource,
+ nsIRDFContainer** aResult)
+{
+ nsresult rv;
+ rv = NS_NewRDFContainer(aResult);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = (*aResult)->Init(aDataSource, aResource);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(*aResult);
+ }
+ return rv;
+}
+
+
+nsresult
+RDFContainerImpl::Renumber(int32_t aStartIndex, int32_t aIncrement)
+{
+ if (!mDataSource || !mContainer)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Renumber the elements in the container starting with
+ // aStartIndex, updating each element's index by aIncrement. For
+ // example,
+ //
+ // (1:a 2:b 3:c)
+ // Renumber(2, +1);
+ // (1:a 3:b 4:c)
+ // Renumber(3, -1);
+ // (1:a 2:b 3:c)
+ //
+ nsresult rv;
+
+ if (! aIncrement)
+ return NS_OK;
+
+ int32_t count;
+ rv = GetCount(&count);
+ if (NS_FAILED(rv)) return rv;
+
+ if (aIncrement > 0) {
+ // Update the container's nextVal to reflect the
+ // renumbering. We do this now if aIncrement > 0 because we'll
+ // want to be able to acknowledge that new elements are in the
+ // container.
+ rv = SetNextValue(count + aIncrement + 1);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ int32_t i;
+ if (aIncrement < 0) {
+ i = aStartIndex;
+ }
+ else {
+ i = count; // we're one-indexed.
+ }
+
+ // Note: once we disable notifications, don't exit this method until
+ // enabling notifications
+ nsCOMPtr<nsIRDFPropagatableDataSource> propagatable =
+ do_QueryInterface(mDataSource);
+ if (propagatable) {
+ propagatable->SetPropagateChanges(false);
+ }
+
+ bool err = false;
+ while (!err && ((aIncrement < 0) ? (i <= count) : (i >= aStartIndex)))
+ {
+ nsCOMPtr<nsIRDFResource> oldOrdinal;
+ rv = gRDFContainerUtils->IndexToOrdinalResource(i, getter_AddRefs(oldOrdinal));
+ if (NS_FAILED(rv))
+ {
+ err = true;
+ continue;
+ }
+
+ nsCOMPtr<nsIRDFResource> newOrdinal;
+ rv = gRDFContainerUtils->IndexToOrdinalResource(i + aIncrement, getter_AddRefs(newOrdinal));
+ if (NS_FAILED(rv))
+ {
+ err = true;
+ continue;
+ }
+
+ // Because of aggregation, we need to be paranoid about the
+ // possibility that >1 element may be present per ordinal. If
+ // there _is_ in fact more than one element, they'll all get
+ // assigned to the same new ordinal; i.e., we don't make any
+ // attempt to "clean up" the duplicate numbering. (Doing so
+ // would require two passes.)
+ nsCOMPtr<nsISimpleEnumerator> targets;
+ rv = mDataSource->GetTargets(mContainer, oldOrdinal, true, getter_AddRefs(targets));
+ if (NS_FAILED(rv))
+ {
+ err = true;
+ continue;
+ }
+
+ while (1) {
+ bool hasMore;
+ rv = targets->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv))
+ {
+ err = true;
+ break;
+ }
+
+ if (! hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ rv = targets->GetNext(getter_AddRefs(isupports));
+ if (NS_FAILED(rv))
+ {
+ err = true;
+ break;
+ }
+
+ nsCOMPtr<nsIRDFNode> element( do_QueryInterface(isupports) );
+ NS_ASSERTION(element != nullptr, "something funky in the enumerator");
+ if (! element)
+ {
+ err = true;
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ rv = mDataSource->Unassert(mContainer, oldOrdinal, element);
+ if (NS_FAILED(rv))
+ {
+ err = true;
+ break;
+ }
+
+ rv = mDataSource->Assert(mContainer, newOrdinal, element, true);
+ if (NS_FAILED(rv))
+ {
+ err = true;
+ break;
+ }
+ }
+
+ i -= aIncrement;
+ }
+
+ if (!err && (aIncrement < 0))
+ {
+ // Update the container's nextVal to reflect the
+ // renumbering. We do this now if aIncrement < 0 because, up
+ // until this point, we'll want people to be able to find
+ // things that are still "at the end".
+ rv = SetNextValue(count + aIncrement + 1);
+ if (NS_FAILED(rv))
+ {
+ err = true;
+ }
+ }
+
+ // Note: MUST enable notifications before exiting this method
+ if (propagatable) {
+ propagatable->SetPropagateChanges(true);
+ }
+
+ if (err) return(rv);
+
+ return NS_OK;
+}
+
+
+
+nsresult
+RDFContainerImpl::SetNextValue(int32_t aIndex)
+{
+ if (!mDataSource || !mContainer)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ // Remove the current value of nextVal, if there is one.
+ nsCOMPtr<nsIRDFNode> nextValNode;
+ if (NS_SUCCEEDED(rv = mDataSource->GetTarget(mContainer,
+ kRDF_nextVal,
+ true,
+ getter_AddRefs(nextValNode)))) {
+ if (NS_FAILED(rv = mDataSource->Unassert(mContainer, kRDF_nextVal, nextValNode))) {
+ NS_ERROR("unable to update nextVal");
+ return rv;
+ }
+ }
+
+ nsAutoString s;
+ s.AppendInt(aIndex, 10);
+
+ nsCOMPtr<nsIRDFLiteral> nextVal;
+ if (NS_FAILED(rv = gRDFService->GetLiteral(s.get(), getter_AddRefs(nextVal)))) {
+ NS_ERROR("unable to get nextVal literal");
+ return rv;
+ }
+
+ rv = mDataSource->Assert(mContainer, kRDF_nextVal, nextVal, true);
+ if (rv != NS_RDF_ASSERTION_ACCEPTED) {
+ NS_ERROR("unable to update nextVal");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+RDFContainerImpl::GetNextValue(nsIRDFResource** aResult)
+{
+ if (!mDataSource || !mContainer)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ // Get the next value, which hangs off of the bag via the
+ // RDF:nextVal property.
+ nsCOMPtr<nsIRDFNode> nextValNode;
+ rv = mDataSource->GetTarget(mContainer, kRDF_nextVal, true, getter_AddRefs(nextValNode));
+ if (NS_FAILED(rv)) return rv;
+
+ if (rv == NS_RDF_NO_VALUE)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIRDFLiteral> nextValLiteral;
+ rv = nextValNode->QueryInterface(NS_GET_IID(nsIRDFLiteral), getter_AddRefs(nextValLiteral));
+ if (NS_FAILED(rv)) return rv;
+
+ const char16_t* s;
+ rv = nextValLiteral->GetValueConst(&s);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t nextVal = 0;
+ {
+ for (const char16_t* p = s; *p != 0; ++p) {
+ NS_ASSERTION(*p >= '0' && *p <= '9', "not a digit");
+ if (*p < '0' || *p > '9')
+ break;
+
+ nextVal *= 10;
+ nextVal += *p - '0';
+ }
+ }
+
+ static const char kRDFNameSpaceURI[] = RDF_NAMESPACE_URI;
+ char buf[sizeof(kRDFNameSpaceURI) + 16];
+ nsFixedCString nextValStr(buf, sizeof(buf), 0);
+ nextValStr = kRDFNameSpaceURI;
+ nextValStr.Append('_');
+ nextValStr.AppendInt(nextVal, 10);
+
+ rv = gRDFService->GetResource(nextValStr, aResult);
+ if (NS_FAILED(rv)) return rv;
+
+ // Now increment the RDF:nextVal property.
+ rv = mDataSource->Unassert(mContainer, kRDF_nextVal, nextValLiteral);
+ if (NS_FAILED(rv)) return rv;
+
+ ++nextVal;
+ nextValStr.Truncate();
+ nextValStr.AppendInt(nextVal, 10);
+
+ rv = gRDFService->GetLiteral(NS_ConvertASCIItoUTF16(nextValStr).get(), getter_AddRefs(nextValLiteral));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mDataSource->Assert(mContainer, kRDF_nextVal, nextValLiteral, true);
+ if (NS_FAILED(rv)) return rv;
+
+ if (RDF_SEQ_LIST_LIMIT == nextVal)
+ {
+ // focal point for RDF container mutation;
+ // basically, provide a hint to allow for fast access
+ nsCOMPtr<nsIRDFInMemoryDataSource> inMem = do_QueryInterface(mDataSource);
+ if (inMem)
+ {
+ // ignore error; failure just means slower access
+ (void)inMem->EnsureFastContainment(mContainer);
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/components/rdf/src/nsRDFContainerUtils.cpp b/components/rdf/src/nsRDFContainerUtils.cpp
new file mode 100644
index 000000000..299722d4b
--- /dev/null
+++ b/components/rdf/src/nsRDFContainerUtils.cpp
@@ -0,0 +1,515 @@
+/* -*- 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/. */
+
+/*
+
+ Implementation for the RDF container utils.
+
+ */
+
+
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsIRDFContainer.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "plstr.h"
+#include "prprf.h"
+#include "rdf.h"
+#include "rdfutil.h"
+
+class RDFContainerUtilsImpl : public nsIRDFContainerUtils
+{
+public:
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsIRDFContainerUtils interface
+ NS_DECL_NSIRDFCONTAINERUTILS
+
+private:
+ friend nsresult NS_NewRDFContainerUtils(nsIRDFContainerUtils** aResult);
+
+ RDFContainerUtilsImpl();
+ virtual ~RDFContainerUtilsImpl();
+
+ nsresult MakeContainer(nsIRDFDataSource* aDataSource,
+ nsIRDFResource* aResource,
+ nsIRDFResource* aType,
+ nsIRDFContainer** aResult);
+
+ bool IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType);
+
+ // pseudo constants
+ static int32_t gRefCnt;
+ static nsIRDFService* gRDFService;
+ static nsIRDFResource* kRDF_instanceOf;
+ static nsIRDFResource* kRDF_nextVal;
+ static nsIRDFResource* kRDF_Bag;
+ static nsIRDFResource* kRDF_Seq;
+ static nsIRDFResource* kRDF_Alt;
+ static nsIRDFLiteral* kOne;
+ static const char kRDFNameSpaceURI[];
+};
+
+int32_t RDFContainerUtilsImpl::gRefCnt = 0;
+nsIRDFService* RDFContainerUtilsImpl::gRDFService;
+nsIRDFResource* RDFContainerUtilsImpl::kRDF_instanceOf;
+nsIRDFResource* RDFContainerUtilsImpl::kRDF_nextVal;
+nsIRDFResource* RDFContainerUtilsImpl::kRDF_Bag;
+nsIRDFResource* RDFContainerUtilsImpl::kRDF_Seq;
+nsIRDFResource* RDFContainerUtilsImpl::kRDF_Alt;
+nsIRDFLiteral* RDFContainerUtilsImpl::kOne;
+const char RDFContainerUtilsImpl::kRDFNameSpaceURI[] = RDF_NAMESPACE_URI;
+
+////////////////////////////////////////////////////////////////////////
+// nsISupports interface
+
+NS_IMPL_ISUPPORTS(RDFContainerUtilsImpl, nsIRDFContainerUtils)
+
+////////////////////////////////////////////////////////////////////////
+// nsIRDFContainerUtils interface
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::IsOrdinalProperty(nsIRDFResource *aProperty, bool *_retval)
+{
+ NS_PRECONDITION(aProperty != nullptr, "null ptr");
+ if (! aProperty)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ const char *propertyStr;
+ rv = aProperty->GetValueConst( &propertyStr );
+ if (NS_FAILED(rv)) return rv;
+
+ if (PL_strncmp(propertyStr, kRDFNameSpaceURI, sizeof(kRDFNameSpaceURI) - 1) != 0) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ const char* s = propertyStr;
+ s += sizeof(kRDFNameSpaceURI) - 1;
+ if (*s != '_') {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ ++s;
+ while (*s) {
+ if (*s < '0' || *s > '9') {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ ++s;
+ }
+
+ *_retval = true;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::IndexToOrdinalResource(int32_t aIndex, nsIRDFResource **aOrdinal)
+{
+ NS_PRECONDITION(aIndex > 0, "illegal value");
+ if (aIndex <= 0)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ nsAutoCString uri(kRDFNameSpaceURI);
+ uri.Append('_');
+ uri.AppendInt(aIndex);
+
+ nsresult rv = gRDFService->GetResource(uri, aOrdinal);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get ordinal resource");
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::OrdinalResourceToIndex(nsIRDFResource *aOrdinal, int32_t *aIndex)
+{
+ NS_PRECONDITION(aOrdinal != nullptr, "null ptr");
+ if (! aOrdinal)
+ return NS_ERROR_NULL_POINTER;
+
+ const char *ordinalStr;
+ if (NS_FAILED(aOrdinal->GetValueConst( &ordinalStr )))
+ return NS_ERROR_FAILURE;
+
+ const char* s = ordinalStr;
+ if (PL_strncmp(s, kRDFNameSpaceURI, sizeof(kRDFNameSpaceURI) - 1) != 0) {
+ NS_ERROR("not an ordinal");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ s += sizeof(kRDFNameSpaceURI) - 1;
+ if (*s != '_') {
+ NS_ERROR("not an ordinal");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int32_t idx = 0;
+
+ ++s;
+ while (*s) {
+ if (*s < '0' || *s > '9') {
+ NS_ERROR("not an ordinal");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ idx *= 10;
+ idx += (*s - '0');
+
+ ++s;
+ }
+
+ *aIndex = idx;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::IsContainer(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval)
+{
+ NS_PRECONDITION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResource != nullptr, "null ptr");
+ if (! aResource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (! _retval)
+ return NS_ERROR_NULL_POINTER;
+
+ if (IsA(aDataSource, aResource, kRDF_Seq) ||
+ IsA(aDataSource, aResource, kRDF_Bag) ||
+ IsA(aDataSource, aResource, kRDF_Alt)) {
+ *_retval = true;
+ }
+ else {
+ *_retval = false;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::IsEmpty(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, bool* _retval)
+{
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ // By default, say that we're an empty container. Even if we're not
+ // really even a container.
+ *_retval = true;
+
+ nsCOMPtr<nsIRDFNode> nextValNode;
+ rv = aDataSource->GetTarget(aResource, kRDF_nextVal, true, getter_AddRefs(nextValNode));
+ if (NS_FAILED(rv)) return rv;
+
+ if (rv == NS_RDF_NO_VALUE)
+ return NS_OK;
+
+ nsCOMPtr<nsIRDFLiteral> nextValLiteral;
+ rv = nextValNode->QueryInterface(NS_GET_IID(nsIRDFLiteral), getter_AddRefs(nextValLiteral));
+ if (NS_FAILED(rv)) return rv;
+
+ if (nextValLiteral.get() != kOne)
+ *_retval = false;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::IsBag(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval)
+{
+ NS_PRECONDITION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResource != nullptr, "null ptr");
+ if (! aResource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (! _retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = IsA(aDataSource, aResource, kRDF_Bag);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::IsSeq(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval)
+{
+ NS_PRECONDITION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResource != nullptr, "null ptr");
+ if (! aResource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (! _retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = IsA(aDataSource, aResource, kRDF_Seq);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::IsAlt(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval)
+{
+ NS_PRECONDITION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResource != nullptr, "null ptr");
+ if (! aResource)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(_retval != nullptr, "null ptr");
+ if (! _retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = IsA(aDataSource, aResource, kRDF_Alt);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::MakeBag(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, nsIRDFContainer **_retval)
+{
+ return MakeContainer(aDataSource, aResource, kRDF_Bag, _retval);
+}
+
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::MakeSeq(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, nsIRDFContainer **_retval)
+{
+ return MakeContainer(aDataSource, aResource, kRDF_Seq, _retval);
+}
+
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::MakeAlt(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, nsIRDFContainer **_retval)
+{
+ return MakeContainer(aDataSource, aResource, kRDF_Alt, _retval);
+}
+
+
+
+////////////////////////////////////////////////////////////////////////
+
+
+RDFContainerUtilsImpl::RDFContainerUtilsImpl()
+{
+ if (gRefCnt++ == 0) {
+ nsresult rv;
+
+ NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+ rv = CallGetService(kRDFServiceCID, &gRDFService);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
+ if (NS_SUCCEEDED(rv)) {
+ gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"),
+ &kRDF_instanceOf);
+ gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"),
+ &kRDF_nextVal);
+ gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"),
+ &kRDF_Bag);
+ gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"),
+ &kRDF_Seq);
+ gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"),
+ &kRDF_Alt);
+ gRDFService->GetLiteral(u"1", &kOne);
+ }
+ }
+}
+
+
+RDFContainerUtilsImpl::~RDFContainerUtilsImpl()
+{
+#ifdef DEBUG_REFS
+ --gInstanceCount;
+ fprintf(stdout, "%d - RDF: RDFContainerUtilsImpl\n", gInstanceCount);
+#endif
+
+ if (--gRefCnt == 0) {
+ NS_IF_RELEASE(gRDFService);
+ NS_IF_RELEASE(kRDF_instanceOf);
+ NS_IF_RELEASE(kRDF_nextVal);
+ NS_IF_RELEASE(kRDF_Bag);
+ NS_IF_RELEASE(kRDF_Seq);
+ NS_IF_RELEASE(kRDF_Alt);
+ NS_IF_RELEASE(kOne);
+ }
+}
+
+
+
+nsresult
+NS_NewRDFContainerUtils(nsIRDFContainerUtils** aResult)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ RDFContainerUtilsImpl* result =
+ new RDFContainerUtilsImpl();
+
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(result);
+ *aResult = result;
+ return NS_OK;
+}
+
+
+nsresult
+RDFContainerUtilsImpl::MakeContainer(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType, nsIRDFContainer** aResult)
+{
+ NS_PRECONDITION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource) return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResource != nullptr, "null ptr");
+ if (! aResource) return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aType != nullptr, "null ptr");
+ if (! aType) return NS_ERROR_NULL_POINTER;
+
+ if (aResult) *aResult = nullptr;
+
+ nsresult rv;
+
+ // Check to see if somebody has already turned it into a container; if so
+ // don't try to do it again.
+ bool isContainer;
+ rv = IsContainer(aDataSource, aResource, &isContainer);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!isContainer)
+ {
+ rv = aDataSource->Assert(aResource, kRDF_instanceOf, aType, true);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = aDataSource->Assert(aResource, kRDF_nextVal, kOne, true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ if (aResult) {
+ rv = NS_NewRDFContainer(aResult);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = (*aResult)->Init(aDataSource, aResource);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+bool
+RDFContainerUtilsImpl::IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType)
+{
+ if (!aDataSource || !aResource || !aType) {
+ NS_WARNING("Unexpected null argument");
+ return false;
+ }
+
+ nsresult rv;
+
+ bool result;
+ rv = aDataSource->HasAssertion(aResource, kRDF_instanceOf, aType, true, &result);
+ if (NS_FAILED(rv))
+ return false;
+
+ return result;
+}
+
+NS_IMETHODIMP
+RDFContainerUtilsImpl::IndexOf(nsIRDFDataSource* aDataSource, nsIRDFResource* aContainer, nsIRDFNode* aElement, int32_t* aIndex)
+{
+ if (!aDataSource || !aContainer)
+ return NS_ERROR_NULL_POINTER;
+
+ // Assume we can't find it.
+ *aIndex = -1;
+
+ // If the resource is null, bail quietly
+ if (! aElement)
+ return NS_OK;
+
+ // We'll assume that fan-out is much higher than fan-in, so grovel
+ // through the inbound arcs, look for an ordinal resource, and
+ // decode it.
+ nsCOMPtr<nsISimpleEnumerator> arcsIn;
+ aDataSource->ArcLabelsIn(aElement, getter_AddRefs(arcsIn));
+ if (! arcsIn)
+ return NS_OK;
+
+ while (1) {
+ bool hasMoreArcs = false;
+ arcsIn->HasMoreElements(&hasMoreArcs);
+ if (! hasMoreArcs)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ arcsIn->GetNext(getter_AddRefs(isupports));
+ if (! isupports)
+ break;
+
+ nsCOMPtr<nsIRDFResource> property =
+ do_QueryInterface(isupports);
+
+ if (! property)
+ continue;
+
+ bool isOrdinal;
+ IsOrdinalProperty(property, &isOrdinal);
+ if (! isOrdinal)
+ continue;
+
+ nsCOMPtr<nsISimpleEnumerator> sources;
+ aDataSource->GetSources(property, aElement, true, getter_AddRefs(sources));
+ if (! sources)
+ continue;
+
+ while (1) {
+ bool hasMoreSources = false;
+ sources->HasMoreElements(&hasMoreSources);
+ if (! hasMoreSources)
+ break;
+
+ nsCOMPtr<nsISupports> isupports2;
+ sources->GetNext(getter_AddRefs(isupports2));
+ if (! isupports2)
+ break;
+
+ nsCOMPtr<nsIRDFResource> source =
+ do_QueryInterface(isupports2);
+
+ if (source == aContainer)
+ // Found it.
+ return OrdinalResourceToIndex(property, aIndex);
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/components/rdf/src/nsRDFContentSink.cpp b/components/rdf/src/nsRDFContentSink.cpp
new file mode 100644
index 000000000..ae05a9381
--- /dev/null
+++ b/components/rdf/src/nsRDFContentSink.cpp
@@ -0,0 +1,1476 @@
+/* -*- 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/. */
+
+/*
+
+ An implementation for an NGLayout-style content sink that knows how
+ to build an RDF content model from XML-serialized RDF.
+
+ For more information on the RDF/XML syntax,
+ see http://www.w3.org/TR/REC-rdf-syntax/
+
+ This code is based on the final W3C Recommendation,
+ http://www.w3.org/TR/1999/REC-rdf-syntax-19990222.
+
+ Open Issues ------------------
+
+ 1) factoring code with nsXMLContentSink - There's some amount of
+ common code between this and the HTML content sink. This will
+ increase as we support more and more HTML elements. How can code
+ from XML/HTML be factored?
+
+ 2) We don't support the `parseType' attribute on the Description
+ tag; therefore, it is impossible to "inline" raw XML in this
+ implemenation.
+
+ 3) We don't build the reifications at parse time due to the
+ footprint overhead it would incur for large RDF documents. (It
+ may be possible to attach a "reification" wrapper datasource that
+ would present this information at query-time.) Because of this,
+ the `bagID' attribute is not processed correctly.
+
+ 4) No attempt is made to `resolve URIs' to a canonical form (the
+ specification hints that an implementation should do this). This
+ is omitted for the obvious reason that we can ill afford to
+ resolve each URI reference.
+
+*/
+
+#include "nsCOMPtr.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIContentSink.h"
+#include "nsIRDFContainer.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIRDFContentSink.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFService.h"
+#include "nsIRDFXMLSink.h"
+#include "nsIServiceManager.h"
+#include "nsIURL.h"
+#include "nsIXMLContentSink.h"
+#include "nsRDFCID.h"
+#include "nsTArray.h"
+#include "nsXPIDLString.h"
+#include "mozilla/Logging.h"
+#include "rdf.h"
+#include "rdfutil.h"
+#include "nsReadableUtils.h"
+#include "nsIExpatSink.h"
+#include "nsCRT.h"
+#include "nsIAtom.h"
+#include "nsStaticAtom.h"
+#include "nsIScriptError.h"
+#include "nsIDTD.h"
+
+using namespace mozilla;
+
+///////////////////////////////////////////////////////////////////////
+
+enum RDFContentSinkState {
+ eRDFContentSinkState_InProlog,
+ eRDFContentSinkState_InDocumentElement,
+ eRDFContentSinkState_InDescriptionElement,
+ eRDFContentSinkState_InContainerElement,
+ eRDFContentSinkState_InPropertyElement,
+ eRDFContentSinkState_InMemberElement,
+ eRDFContentSinkState_InEpilog
+};
+
+enum RDFContentSinkParseMode {
+ eRDFContentSinkParseMode_Resource,
+ eRDFContentSinkParseMode_Literal,
+ eRDFContentSinkParseMode_Int,
+ eRDFContentSinkParseMode_Date
+};
+
+typedef
+NS_STDCALL_FUNCPROTO(nsresult,
+ nsContainerTestFn,
+ nsIRDFContainerUtils, IsAlt,
+ (nsIRDFDataSource*, nsIRDFResource*, bool*));
+
+typedef
+NS_STDCALL_FUNCPROTO(nsresult,
+ nsMakeContainerFn,
+ nsIRDFContainerUtils, MakeAlt,
+ (nsIRDFDataSource*, nsIRDFResource*, nsIRDFContainer**));
+
+class RDFContentSinkImpl : public nsIRDFContentSink,
+ public nsIExpatSink
+{
+public:
+ RDFContentSinkImpl();
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEXPATSINK
+
+ // nsIContentSink
+ NS_IMETHOD WillParse(void) override;
+ NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override;
+ NS_IMETHOD DidBuildModel(bool aTerminated) override;
+ NS_IMETHOD WillInterrupt(void) override;
+ NS_IMETHOD WillResume(void) override;
+ NS_IMETHOD SetParser(nsParserBase* aParser) override;
+ virtual void FlushPendingNotifications(mozFlushType aType) override { }
+ NS_IMETHOD SetDocumentCharset(nsACString& aCharset) override { return NS_OK; }
+ virtual nsISupports *GetTarget() override { return nullptr; }
+
+ // nsIRDFContentSink
+ NS_IMETHOD Init(nsIURI* aURL) override;
+ NS_IMETHOD SetDataSource(nsIRDFDataSource* aDataSource) override;
+ NS_IMETHOD GetDataSource(nsIRDFDataSource*& aDataSource) override;
+
+ // pseudo constants
+ static int32_t gRefCnt;
+ static nsIRDFService* gRDFService;
+ static nsIRDFContainerUtils* gRDFContainerUtils;
+ static nsIRDFResource* kRDF_type;
+ static nsIRDFResource* kRDF_instanceOf; // XXX should be RDF:type
+ static nsIRDFResource* kRDF_Alt;
+ static nsIRDFResource* kRDF_Bag;
+ static nsIRDFResource* kRDF_Seq;
+ static nsIRDFResource* kRDF_nextVal;
+
+#define RDF_ATOM(name_, value_) static nsIAtom* name_;
+#include "nsRDFContentSinkAtomList.h"
+#undef RDF_ATOM
+
+ typedef struct ContainerInfo {
+ nsIRDFResource** mType;
+ nsContainerTestFn mTestFn;
+ nsMakeContainerFn mMakeFn;
+ } ContainerInfo;
+
+protected:
+ virtual ~RDFContentSinkImpl();
+
+ // Text management
+ void ParseText(nsIRDFNode **aResult);
+
+ nsresult FlushText();
+ nsresult AddText(const char16_t* aText, int32_t aLength);
+
+ // RDF-specific parsing
+ nsresult OpenRDF(const char16_t* aName);
+ nsresult OpenObject(const char16_t* aName ,const char16_t** aAttributes);
+ nsresult OpenProperty(const char16_t* aName, const char16_t** aAttributes);
+ nsresult OpenMember(const char16_t* aName, const char16_t** aAttributes);
+ nsresult OpenValue(const char16_t* aName, const char16_t** aAttributes);
+
+ nsresult GetIdAboutAttribute(const char16_t** aAttributes, nsIRDFResource** aResource, bool* aIsAnonymous = nullptr);
+ nsresult GetResourceAttribute(const char16_t** aAttributes, nsIRDFResource** aResource);
+ nsresult AddProperties(const char16_t** aAttributes, nsIRDFResource* aSubject, int32_t* aCount = nullptr);
+ void SetParseMode(const char16_t **aAttributes);
+
+ char16_t* mText;
+ int32_t mTextLength;
+ int32_t mTextSize;
+
+ /**
+ * From the set of given attributes, this method extracts the
+ * namespace definitions and feeds them to the datasource.
+ * These can then be suggested to the serializer to be used again.
+ * Hopefully, this will keep namespace definitions intact in a
+ * parse - serialize cycle.
+ */
+ void RegisterNamespaces(const char16_t **aAttributes);
+
+ /**
+ * Extracts the localname from aExpatName, the name that the Expat parser
+ * passes us.
+ * aLocalName will contain the localname in aExpatName.
+ * The return value is a dependent string containing just the namespace.
+ */
+ const nsDependentSubstring SplitExpatName(const char16_t *aExpatName,
+ nsIAtom **aLocalName);
+
+ enum eContainerType { eBag, eSeq, eAlt };
+ nsresult InitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer);
+ nsresult ReinitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer);
+
+ // The datasource in which we're assigning assertions
+ nsCOMPtr<nsIRDFDataSource> mDataSource;
+
+ // A hash of all the node IDs referred to
+ nsInterfaceHashtable<nsStringHashKey, nsIRDFResource> mNodeIDMap;
+
+ // The current state of the content sink
+ RDFContentSinkState mState;
+ RDFContentSinkParseMode mParseMode;
+
+ // content stack management
+ int32_t
+ PushContext(nsIRDFResource *aContext,
+ RDFContentSinkState aState,
+ RDFContentSinkParseMode aParseMode);
+
+ nsresult
+ PopContext(nsIRDFResource *&aContext,
+ RDFContentSinkState &aState,
+ RDFContentSinkParseMode &aParseMode);
+
+ nsIRDFResource* GetContextElement(int32_t ancestor = 0);
+
+
+ struct RDFContextStackElement {
+ nsCOMPtr<nsIRDFResource> mResource;
+ RDFContentSinkState mState;
+ RDFContentSinkParseMode mParseMode;
+ };
+
+ AutoTArray<RDFContextStackElement, 8>* mContextStack;
+
+ nsCOMPtr<nsIURI> mDocumentURL;
+
+private:
+ static mozilla::LazyLogModule gLog;
+};
+
+int32_t RDFContentSinkImpl::gRefCnt = 0;
+nsIRDFService* RDFContentSinkImpl::gRDFService;
+nsIRDFContainerUtils* RDFContentSinkImpl::gRDFContainerUtils;
+nsIRDFResource* RDFContentSinkImpl::kRDF_type;
+nsIRDFResource* RDFContentSinkImpl::kRDF_instanceOf;
+nsIRDFResource* RDFContentSinkImpl::kRDF_Alt;
+nsIRDFResource* RDFContentSinkImpl::kRDF_Bag;
+nsIRDFResource* RDFContentSinkImpl::kRDF_Seq;
+nsIRDFResource* RDFContentSinkImpl::kRDF_nextVal;
+
+mozilla::LazyLogModule RDFContentSinkImpl::gLog("nsRDFContentSink");
+
+////////////////////////////////////////////////////////////////////////
+
+#define RDF_ATOM(name_, value_) nsIAtom* RDFContentSinkImpl::name_;
+#include "nsRDFContentSinkAtomList.h"
+#undef RDF_ATOM
+
+#define RDF_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_)
+#include "nsRDFContentSinkAtomList.h"
+#undef RDF_ATOM
+
+static const nsStaticAtom rdf_atoms[] = {
+#define RDF_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &RDFContentSinkImpl::name_),
+#include "nsRDFContentSinkAtomList.h"
+#undef RDF_ATOM
+};
+
+// static
+void
+nsRDFAtoms::RegisterAtoms()
+{
+ NS_RegisterStaticAtoms(rdf_atoms);
+}
+
+RDFContentSinkImpl::RDFContentSinkImpl()
+ : mText(nullptr),
+ mTextLength(0),
+ mTextSize(0),
+ mState(eRDFContentSinkState_InProlog),
+ mParseMode(eRDFContentSinkParseMode_Literal),
+ mContextStack(nullptr)
+{
+ if (gRefCnt++ == 0) {
+ NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+ nsresult rv = CallGetService(kRDFServiceCID, &gRDFService);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
+ if (NS_SUCCEEDED(rv)) {
+ rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"),
+ &kRDF_type);
+ rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"),
+ &kRDF_instanceOf);
+ rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"),
+ &kRDF_Alt);
+ rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"),
+ &kRDF_Bag);
+ rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"),
+ &kRDF_Seq);
+ rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"),
+ &kRDF_nextVal);
+ }
+
+ NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID);
+ rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils);
+ }
+}
+
+
+RDFContentSinkImpl::~RDFContentSinkImpl()
+{
+#ifdef DEBUG_REFS
+ --gInstanceCount;
+ fprintf(stdout, "%d - RDF: RDFContentSinkImpl\n", gInstanceCount);
+#endif
+
+ if (mContextStack) {
+ MOZ_LOG(gLog, LogLevel::Warning,
+ ("rdfxml: warning! unclosed tag"));
+
+ // XXX we should never need to do this, but, we'll write the
+ // code all the same. If someone left the content stack dirty,
+ // pop all the elements off the stack and release them.
+ int32_t i = mContextStack->Length();
+ while (0 < i--) {
+ nsIRDFResource* resource = nullptr;
+ RDFContentSinkState state;
+ RDFContentSinkParseMode parseMode;
+ PopContext(resource, state, parseMode);
+
+ // print some fairly useless debugging info
+ // XXX we should save line numbers on the context stack: this'd
+ // be about 1000x more helpful.
+ if (resource && MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ nsXPIDLCString uri;
+ resource->GetValue(getter_Copies(uri));
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfxml: uri=%s", (const char*) uri));
+ }
+
+ NS_IF_RELEASE(resource);
+ }
+
+ delete mContextStack;
+ }
+ free(mText);
+
+
+ if (--gRefCnt == 0) {
+ NS_IF_RELEASE(gRDFService);
+ NS_IF_RELEASE(gRDFContainerUtils);
+ NS_IF_RELEASE(kRDF_type);
+ NS_IF_RELEASE(kRDF_instanceOf);
+ NS_IF_RELEASE(kRDF_Alt);
+ NS_IF_RELEASE(kRDF_Bag);
+ NS_IF_RELEASE(kRDF_Seq);
+ NS_IF_RELEASE(kRDF_nextVal);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// nsISupports interface
+
+NS_IMPL_ADDREF(RDFContentSinkImpl)
+NS_IMPL_RELEASE(RDFContentSinkImpl)
+
+NS_IMETHODIMP
+RDFContentSinkImpl::QueryInterface(REFNSIID iid, void** result)
+{
+ NS_PRECONDITION(result, "null ptr");
+ if (! result)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_DEFINE_IID(kIContentSinkIID, NS_ICONTENT_SINK_IID);
+ NS_DEFINE_IID(kIExpatSinkIID, NS_IEXPATSINK_IID);
+ NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
+ NS_DEFINE_IID(kIXMLContentSinkIID, NS_IXMLCONTENT_SINK_IID);
+ NS_DEFINE_IID(kIRDFContentSinkIID, NS_IRDFCONTENTSINK_IID);
+
+ *result = nullptr;
+ if (iid.Equals(kIRDFContentSinkIID) ||
+ iid.Equals(kIXMLContentSinkIID) ||
+ iid.Equals(kIContentSinkIID) ||
+ iid.Equals(kISupportsIID)) {
+ *result = static_cast<nsIXMLContentSink*>(this);
+ AddRef();
+ return NS_OK;
+ }
+ else if (iid.Equals(kIExpatSinkIID)) {
+ *result = static_cast<nsIExpatSink*>(this);
+ AddRef();
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::HandleStartElement(const char16_t *aName,
+ const char16_t **aAtts,
+ uint32_t aAttsCount,
+ uint32_t aLineNumber)
+{
+ FlushText();
+
+ nsresult rv = NS_ERROR_UNEXPECTED; // XXX
+
+ RegisterNamespaces(aAtts);
+
+ switch (mState) {
+ case eRDFContentSinkState_InProlog:
+ rv = OpenRDF(aName);
+ break;
+
+ case eRDFContentSinkState_InDocumentElement:
+ rv = OpenObject(aName,aAtts);
+ break;
+
+ case eRDFContentSinkState_InDescriptionElement:
+ rv = OpenProperty(aName,aAtts);
+ break;
+
+ case eRDFContentSinkState_InContainerElement:
+ rv = OpenMember(aName,aAtts);
+ break;
+
+ case eRDFContentSinkState_InPropertyElement:
+ case eRDFContentSinkState_InMemberElement:
+ rv = OpenValue(aName,aAtts);
+ break;
+
+ case eRDFContentSinkState_InEpilog:
+ MOZ_LOG(gLog, LogLevel::Warning,
+ ("rdfxml: unexpected content in epilog at line %d",
+ aLineNumber));
+ break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::HandleEndElement(const char16_t *aName)
+{
+ FlushText();
+
+ nsIRDFResource* resource;
+ if (NS_FAILED(PopContext(resource, mState, mParseMode))) {
+ // XXX parser didn't catch unmatched tags?
+ if (MOZ_LOG_TEST(gLog, LogLevel::Warning)) {
+ nsAutoString tagStr(aName);
+ char* tagCStr = ToNewCString(tagStr);
+
+ PR_LogPrint
+ ("rdfxml: extra close tag '%s' at line %d",
+ tagCStr, 0/*XXX fix me */);
+
+ free(tagCStr);
+ }
+
+ return NS_ERROR_UNEXPECTED; // XXX
+ }
+
+ // If we've just popped a member or property element, _now_ is the
+ // time to add that element to the graph.
+ switch (mState) {
+ case eRDFContentSinkState_InMemberElement:
+ {
+ nsCOMPtr<nsIRDFContainer> container;
+ NS_NewRDFContainer(getter_AddRefs(container));
+ container->Init(mDataSource, GetContextElement(1));
+ container->AppendElement(resource);
+ }
+ break;
+
+ case eRDFContentSinkState_InPropertyElement:
+ {
+ mDataSource->Assert(GetContextElement(1), GetContextElement(0), resource, true);
+ } break;
+ default:
+ break;
+ }
+
+ if (mContextStack->IsEmpty())
+ mState = eRDFContentSinkState_InEpilog;
+
+ NS_IF_RELEASE(resource);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::HandleComment(const char16_t *aName)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::HandleCDataSection(const char16_t *aData,
+ uint32_t aLength)
+{
+ return aData ? AddText(aData, aLength) : NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::HandleDoctypeDecl(const nsAString & aSubset,
+ const nsAString & aName,
+ const nsAString & aSystemId,
+ const nsAString & aPublicId,
+ nsISupports* aCatalogData)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::HandleCharacterData(const char16_t *aData,
+ uint32_t aLength)
+{
+ return aData ? AddText(aData, aLength) : NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::HandleProcessingInstruction(const char16_t *aTarget,
+ const char16_t *aData)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::HandleXMLDeclaration(const char16_t *aVersion,
+ const char16_t *aEncoding,
+ int32_t aStandalone)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::ReportError(const char16_t* aErrorText,
+ const char16_t* aSourceText,
+ nsIScriptError *aError,
+ bool *_retval)
+{
+ NS_PRECONDITION(aError && aSourceText && aErrorText, "Check arguments!!!");
+
+ // The expat driver should report the error.
+ *_retval = true;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+// nsIContentSink interface
+
+NS_IMETHODIMP
+RDFContentSinkImpl::WillParse(void)
+{
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContentSinkImpl::WillBuildModel(nsDTDMode)
+{
+ if (mDataSource) {
+ nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
+ if (sink)
+ return sink->BeginLoad();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::DidBuildModel(bool aTerminated)
+{
+ if (mDataSource) {
+ nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
+ if (sink)
+ return sink->EndLoad();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::WillInterrupt(void)
+{
+ if (mDataSource) {
+ nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
+ if (sink)
+ return sink->Interrupt();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::WillResume(void)
+{
+ if (mDataSource) {
+ nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
+ if (sink)
+ return sink->Resume();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::SetParser(nsParserBase* aParser)
+{
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+// nsIRDFContentSink interface
+
+NS_IMETHODIMP
+RDFContentSinkImpl::Init(nsIURI* aURL)
+{
+ NS_PRECONDITION(aURL != nullptr, "null ptr");
+ if (! aURL)
+ return NS_ERROR_NULL_POINTER;
+
+ mDocumentURL = aURL;
+ mState = eRDFContentSinkState_InProlog;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFContentSinkImpl::SetDataSource(nsIRDFDataSource* aDataSource)
+{
+ NS_PRECONDITION(aDataSource != nullptr, "SetDataSource null ptr");
+ mDataSource = aDataSource;
+ NS_ASSERTION(mDataSource != nullptr,"Couldn't QI RDF DataSource");
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFContentSinkImpl::GetDataSource(nsIRDFDataSource*& aDataSource)
+{
+ aDataSource = mDataSource;
+ NS_IF_ADDREF(aDataSource);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+// Text buffering
+
+static bool
+rdf_IsDataInBuffer(char16_t* buffer, int32_t length)
+{
+ for (int32_t i = 0; i < length; ++i) {
+ if (buffer[i] == ' ' ||
+ buffer[i] == '\t' ||
+ buffer[i] == '\n' ||
+ buffer[i] == '\r')
+ continue;
+
+ return true;
+ }
+ return false;
+}
+
+void
+RDFContentSinkImpl::ParseText(nsIRDFNode **aResult)
+{
+ // XXXwaterson wasteful, but we'd need to make a copy anyway to be
+ // able to call nsIRDFService::Get[Resource|Literal|...]().
+ nsAutoString value;
+ value.Append(mText, mTextLength);
+ value.Trim(" \t\n\r");
+
+ switch (mParseMode) {
+ case eRDFContentSinkParseMode_Literal:
+ {
+ nsIRDFLiteral *result;
+ gRDFService->GetLiteral(value.get(), &result);
+ *aResult = result;
+ }
+ break;
+
+ case eRDFContentSinkParseMode_Resource:
+ {
+ nsIRDFResource *result;
+ gRDFService->GetUnicodeResource(value, &result);
+ *aResult = result;
+ }
+ break;
+
+ case eRDFContentSinkParseMode_Int:
+ {
+ nsresult err;
+ int32_t i = value.ToInteger(&err);
+ nsIRDFInt *result;
+ gRDFService->GetIntLiteral(i, &result);
+ *aResult = result;
+ }
+ break;
+
+ case eRDFContentSinkParseMode_Date:
+ {
+ PRTime t = rdf_ParseDate(nsDependentCString(NS_LossyConvertUTF16toASCII(value).get(), value.Length()));
+ nsIRDFDate *result;
+ gRDFService->GetDateLiteral(t, &result);
+ *aResult = result;
+ }
+ break;
+
+ default:
+ NS_NOTREACHED("unknown parse type");
+ break;
+ }
+}
+
+nsresult
+RDFContentSinkImpl::FlushText()
+{
+ nsresult rv = NS_OK;
+ if (0 != mTextLength) {
+ if (rdf_IsDataInBuffer(mText, mTextLength)) {
+ // XXX if there's anything but whitespace, then we'll
+ // create a text node.
+
+ switch (mState) {
+ case eRDFContentSinkState_InMemberElement: {
+ nsCOMPtr<nsIRDFNode> node;
+ ParseText(getter_AddRefs(node));
+
+ nsCOMPtr<nsIRDFContainer> container;
+ NS_NewRDFContainer(getter_AddRefs(container));
+ container->Init(mDataSource, GetContextElement(1));
+
+ container->AppendElement(node);
+ } break;
+
+ case eRDFContentSinkState_InPropertyElement: {
+ nsCOMPtr<nsIRDFNode> node;
+ ParseText(getter_AddRefs(node));
+
+ mDataSource->Assert(GetContextElement(1), GetContextElement(0), node, true);
+ } break;
+
+ default:
+ // just ignore it
+ break;
+ }
+ }
+ mTextLength = 0;
+ }
+ return rv;
+}
+
+
+nsresult
+RDFContentSinkImpl::AddText(const char16_t* aText, int32_t aLength)
+{
+ // Create buffer when we first need it
+ if (0 == mTextSize) {
+ mText = (char16_t *) malloc(sizeof(char16_t) * 4096);
+ if (!mText) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mTextSize = 4096;
+ }
+
+ // Copy data from string into our buffer; grow the buffer as needed.
+ // It never shrinks, but since the content sink doesn't stick around,
+ // this shouldn't be a bloat issue.
+ int32_t amount = mTextSize - mTextLength;
+ if (amount < aLength) {
+ // Grow the buffer by at least a factor of two to prevent thrashing.
+ // Since PR_REALLOC will leave mText intact if the call fails,
+ // don't clobber mText or mTextSize until the new mem is allocated.
+ int32_t newSize = (2 * mTextSize > (mTextSize + aLength)) ?
+ (2 * mTextSize) : (mTextSize + aLength);
+ char16_t* newText =
+ (char16_t *) realloc(mText, sizeof(char16_t) * newSize);
+ if (!newText)
+ return NS_ERROR_OUT_OF_MEMORY;
+ mTextSize = newSize;
+ mText = newText;
+ }
+ memcpy(&mText[mTextLength], aText, sizeof(char16_t) * aLength);
+ mTextLength += aLength;
+
+ return NS_OK;
+}
+
+bool
+rdf_RequiresAbsoluteURI(const nsString& uri)
+{
+ // cheap shot at figuring out if this requires an absolute url translation
+ return !(StringBeginsWith(uri, NS_LITERAL_STRING("urn:")) ||
+ StringBeginsWith(uri, NS_LITERAL_STRING("chrome:")));
+}
+
+nsresult
+RDFContentSinkImpl::GetIdAboutAttribute(const char16_t** aAttributes,
+ nsIRDFResource** aResource,
+ bool* aIsAnonymous)
+{
+ // This corresponds to the dirty work of production [6.5]
+ nsresult rv = NS_OK;
+
+ nsAutoString nodeID;
+
+ nsCOMPtr<nsIAtom> localName;
+ for (; *aAttributes; aAttributes += 2) {
+ const nsDependentSubstring& nameSpaceURI =
+ SplitExpatName(aAttributes[0], getter_AddRefs(localName));
+
+ // We'll accept either `ID' or `rdf:ID' (ibid with `about' or
+ // `rdf:about') in the spirit of being liberal towards the
+ // input that we receive.
+ if (!nameSpaceURI.IsEmpty() &&
+ !nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) {
+ continue;
+ }
+
+ // XXX you can't specify both, but we'll just pick up the
+ // first thing that was specified and ignore the other.
+
+ if (localName == kAboutAtom) {
+ if (aIsAnonymous)
+ *aIsAnonymous = false;
+
+ nsAutoString relURI(aAttributes[1]);
+ if (rdf_RequiresAbsoluteURI(relURI)) {
+ nsAutoCString uri;
+ rv = mDocumentURL->Resolve(NS_ConvertUTF16toUTF8(aAttributes[1]), uri);
+ if (NS_FAILED(rv)) return rv;
+
+ return gRDFService->GetResource(uri,
+ aResource);
+ }
+ return gRDFService->GetResource(NS_ConvertUTF16toUTF8(aAttributes[1]),
+ aResource);
+ }
+ else if (localName == kIdAtom) {
+ if (aIsAnonymous)
+ *aIsAnonymous = false;
+ // In the spirit of leniency, we do not bother trying to
+ // enforce that this be a valid "XML Name" (see
+ // http://www.w3.org/TR/REC-xml#NT-Nmtoken), as per
+ // 6.21. If we wanted to, this would be where to do it.
+
+ // Construct an in-line resource whose URI is the
+ // document's URI plus the XML name specified in the ID
+ // attribute.
+ nsAutoCString name;
+ nsAutoCString ref('#');
+ AppendUTF16toUTF8(aAttributes[1], ref);
+
+ rv = mDocumentURL->Resolve(ref, name);
+ if (NS_FAILED(rv)) return rv;
+
+ return gRDFService->GetResource(name, aResource);
+ }
+ else if (localName == kNodeIdAtom) {
+ nodeID.Assign(aAttributes[1]);
+ }
+ else if (localName == kAboutEachAtom) {
+ // XXX we don't deal with aboutEach...
+ //MOZ_LOG(gLog, LogLevel::Warning,
+ // ("rdfxml: ignoring aboutEach at line %d",
+ // aNode.GetSourceLineNumber()));
+ }
+ }
+
+ // Otherwise, we couldn't find anything, so just gensym one...
+ if (aIsAnonymous)
+ *aIsAnonymous = true;
+
+ // If nodeID is present, check if we already know about it. If we've seen
+ // the nodeID before, use the same resource, otherwise generate a new one.
+ if (!nodeID.IsEmpty()) {
+ mNodeIDMap.Get(nodeID,aResource);
+
+ if (!*aResource) {
+ rv = gRDFService->GetAnonymousResource(aResource);
+ mNodeIDMap.Put(nodeID,*aResource);
+ }
+ }
+ else {
+ rv = gRDFService->GetAnonymousResource(aResource);
+ }
+
+ return rv;
+}
+
+nsresult
+RDFContentSinkImpl::GetResourceAttribute(const char16_t** aAttributes,
+ nsIRDFResource** aResource)
+{
+ nsCOMPtr<nsIAtom> localName;
+
+ nsAutoString nodeID;
+
+ for (; *aAttributes; aAttributes += 2) {
+ const nsDependentSubstring& nameSpaceURI =
+ SplitExpatName(aAttributes[0], getter_AddRefs(localName));
+
+ // We'll accept `resource' or `rdf:resource', under the spirit
+ // that we should be liberal towards the input that we
+ // receive.
+ if (!nameSpaceURI.IsEmpty() &&
+ !nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) {
+ continue;
+ }
+
+ // XXX you can't specify both, but we'll just pick up the
+ // first thing that was specified and ignore the other.
+
+ if (localName == kResourceAtom) {
+ // XXX Take the URI and make it fully qualified by
+ // sticking it into the document's URL. This may not be
+ // appropriate...
+ nsAutoString relURI(aAttributes[1]);
+ if (rdf_RequiresAbsoluteURI(relURI)) {
+ nsresult rv;
+ nsAutoCString uri;
+
+ rv = mDocumentURL->Resolve(NS_ConvertUTF16toUTF8(aAttributes[1]), uri);
+ if (NS_FAILED(rv)) return rv;
+
+ return gRDFService->GetResource(uri, aResource);
+ }
+ return gRDFService->GetResource(NS_ConvertUTF16toUTF8(aAttributes[1]),
+ aResource);
+ }
+ else if (localName == kNodeIdAtom) {
+ nodeID.Assign(aAttributes[1]);
+ }
+ }
+
+ // If nodeID is present, check if we already know about it. If we've seen
+ // the nodeID before, use the same resource, otherwise generate a new one.
+ if (!nodeID.IsEmpty()) {
+ mNodeIDMap.Get(nodeID,aResource);
+
+ if (!*aResource) {
+ nsresult rv;
+ rv = gRDFService->GetAnonymousResource(aResource);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mNodeIDMap.Put(nodeID,*aResource);
+ }
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+RDFContentSinkImpl::AddProperties(const char16_t** aAttributes,
+ nsIRDFResource* aSubject,
+ int32_t* aCount)
+{
+ if (aCount)
+ *aCount = 0;
+
+ nsCOMPtr<nsIAtom> localName;
+ for (; *aAttributes; aAttributes += 2) {
+ const nsDependentSubstring& nameSpaceURI =
+ SplitExpatName(aAttributes[0], getter_AddRefs(localName));
+
+ // skip 'xmlns' directives, these are "meta" information
+ if (nameSpaceURI.EqualsLiteral("http://www.w3.org/2000/xmlns/")) {
+ continue;
+ }
+
+ // skip `about', `ID', `resource', and 'nodeID' attributes (either with or
+ // without the `rdf:' prefix); these are all "special" and
+ // should've been dealt with by the caller.
+ if (localName == kAboutAtom || localName == kIdAtom ||
+ localName == kResourceAtom || localName == kNodeIdAtom) {
+ if (nameSpaceURI.IsEmpty() ||
+ nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI))
+ continue;
+ }
+
+ // Skip `parseType', `RDF:parseType', and `NC:parseType'. This
+ // is meta-information that will be handled in SetParseMode.
+ if (localName == kParseTypeAtom) {
+ if (nameSpaceURI.IsEmpty() ||
+ nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) ||
+ nameSpaceURI.EqualsLiteral(NC_NAMESPACE_URI)) {
+ continue;
+ }
+ }
+
+ NS_ConvertUTF16toUTF8 propertyStr(nameSpaceURI);
+ propertyStr.Append(nsAtomCString(localName));
+
+ // Add the assertion to RDF
+ nsCOMPtr<nsIRDFResource> property;
+ gRDFService->GetResource(propertyStr, getter_AddRefs(property));
+
+ nsCOMPtr<nsIRDFLiteral> target;
+ gRDFService->GetLiteral(aAttributes[1],
+ getter_AddRefs(target));
+
+ mDataSource->Assert(aSubject, property, target, true);
+ }
+ return NS_OK;
+}
+
+void
+RDFContentSinkImpl::SetParseMode(const char16_t **aAttributes)
+{
+ nsCOMPtr<nsIAtom> localName;
+ for (; *aAttributes; aAttributes += 2) {
+ const nsDependentSubstring& nameSpaceURI =
+ SplitExpatName(aAttributes[0], getter_AddRefs(localName));
+
+ if (localName == kParseTypeAtom) {
+ nsDependentString v(aAttributes[1]);
+
+ if (nameSpaceURI.IsEmpty() ||
+ nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) {
+ if (v.EqualsLiteral("Resource"))
+ mParseMode = eRDFContentSinkParseMode_Resource;
+
+ break;
+ }
+ else if (nameSpaceURI.EqualsLiteral(NC_NAMESPACE_URI)) {
+ if (v.EqualsLiteral("Date"))
+ mParseMode = eRDFContentSinkParseMode_Date;
+ else if (v.EqualsLiteral("Integer"))
+ mParseMode = eRDFContentSinkParseMode_Int;
+
+ break;
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// RDF-specific routines used to build the model
+
+nsresult
+RDFContentSinkImpl::OpenRDF(const char16_t* aName)
+{
+ // ensure that we're actually reading RDF by making sure that the
+ // opening tag is <rdf:RDF>, where "rdf:" corresponds to whatever
+ // they've declared the standard RDF namespace to be.
+ nsCOMPtr<nsIAtom> localName;
+ const nsDependentSubstring& nameSpaceURI =
+ SplitExpatName(aName, getter_AddRefs(localName));
+
+ if (!nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) || localName != kRDFAtom) {
+ // MOZ_LOG(gLog, LogLevel::Info,
+ // ("rdfxml: expected RDF:RDF at line %d",
+ // aNode.GetSourceLineNumber()));
+
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ PushContext(nullptr, mState, mParseMode);
+ mState = eRDFContentSinkState_InDocumentElement;
+ return NS_OK;
+}
+
+nsresult
+RDFContentSinkImpl::OpenObject(const char16_t* aName,
+ const char16_t** aAttributes)
+{
+ // an "object" non-terminal is either a "description", a "typed
+ // node", or a "container", so this change the content sink's
+ // state appropriately.
+ nsCOMPtr<nsIAtom> localName;
+ const nsDependentSubstring& nameSpaceURI =
+ SplitExpatName(aName, getter_AddRefs(localName));
+
+ // Figure out the URI of this object, and create an RDF node for it.
+ nsCOMPtr<nsIRDFResource> source;
+ GetIdAboutAttribute(aAttributes, getter_AddRefs(source));
+
+ // If there is no `ID' or `about', then there's not much we can do.
+ if (! source)
+ return NS_ERROR_FAILURE;
+
+ // Push the element onto the context stack
+ PushContext(source, mState, mParseMode);
+
+ // Now figure out what kind of state transition we need to
+ // make. We'll either be going into a mode where we parse a
+ // description or a container.
+ bool isaTypedNode = true;
+
+ if (nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) {
+ isaTypedNode = false;
+
+ if (localName == kDescriptionAtom) {
+ // it's a description
+ mState = eRDFContentSinkState_InDescriptionElement;
+ }
+ else if (localName == kBagAtom) {
+ // it's a bag container
+ InitContainer(kRDF_Bag, source);
+ mState = eRDFContentSinkState_InContainerElement;
+ }
+ else if (localName == kSeqAtom) {
+ // it's a seq container
+ InitContainer(kRDF_Seq, source);
+ mState = eRDFContentSinkState_InContainerElement;
+ }
+ else if (localName == kAltAtom) {
+ // it's an alt container
+ InitContainer(kRDF_Alt, source);
+ mState = eRDFContentSinkState_InContainerElement;
+ }
+ else {
+ // heh, that's not *in* the RDF namespace: just treat it
+ // like a typed node
+ isaTypedNode = true;
+ }
+ }
+
+ if (isaTypedNode) {
+ NS_ConvertUTF16toUTF8 typeStr(nameSpaceURI);
+ typeStr.Append(nsAtomCString(localName));
+
+ nsCOMPtr<nsIRDFResource> type;
+ nsresult rv = gRDFService->GetResource(typeStr, getter_AddRefs(type));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mDataSource->Assert(source, kRDF_type, type, true);
+ if (NS_FAILED(rv)) return rv;
+
+ mState = eRDFContentSinkState_InDescriptionElement;
+ }
+
+ AddProperties(aAttributes, source);
+ return NS_OK;
+}
+
+nsresult
+RDFContentSinkImpl::OpenProperty(const char16_t* aName, const char16_t** aAttributes)
+{
+ nsresult rv;
+
+ // an "object" non-terminal is either a "description", a "typed
+ // node", or a "container", so this change the content sink's
+ // state appropriately.
+ nsCOMPtr<nsIAtom> localName;
+ const nsDependentSubstring& nameSpaceURI =
+ SplitExpatName(aName, getter_AddRefs(localName));
+
+ NS_ConvertUTF16toUTF8 propertyStr(nameSpaceURI);
+ propertyStr.Append(nsAtomCString(localName));
+
+ nsCOMPtr<nsIRDFResource> property;
+ rv = gRDFService->GetResource(propertyStr, getter_AddRefs(property));
+ if (NS_FAILED(rv)) return rv;
+
+ // See if they've specified a 'resource' attribute, in which case
+ // they mean *that* to be the object of this property.
+ nsCOMPtr<nsIRDFResource> target;
+ GetResourceAttribute(aAttributes, getter_AddRefs(target));
+
+ bool isAnonymous = false;
+
+ if (! target) {
+ // See if an 'ID' attribute has been specified, in which case
+ // this corresponds to the fourth form of [6.12].
+
+ // XXX strictly speaking, we should reject the RDF/XML as
+ // invalid if they've specified both an 'ID' and a 'resource'
+ // attribute. Bah.
+
+ // XXX strictly speaking, 'about=' isn't allowed here, but
+ // what the hell.
+ GetIdAboutAttribute(aAttributes, getter_AddRefs(target), &isAnonymous);
+ }
+
+ if (target) {
+ // They specified an inline resource for the value of this
+ // property. Create an RDF resource for the inline resource
+ // URI, add the properties to it, and attach the inline
+ // resource to its parent.
+ int32_t count;
+ rv = AddProperties(aAttributes, target, &count);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "problem adding properties");
+ if (NS_FAILED(rv)) return rv;
+
+ if (count || !isAnonymous) {
+ // If the resource was "anonymous" (i.e., they hadn't
+ // explicitly set an ID or resource attribute), then we'll
+ // only assert this property from the context element *if*
+ // there were properties specified on the anonymous
+ // resource.
+ rv = mDataSource->Assert(GetContextElement(0), property, target, true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // XXX Technically, we should _not_ fall through here and push
+ // the element onto the stack: this is supposed to be a closed
+ // node. But right now I'm lazy and the code will just Do The
+ // Right Thing so long as the RDF is well-formed.
+ }
+
+ // Push the element onto the context stack and change state.
+ PushContext(property, mState, mParseMode);
+ mState = eRDFContentSinkState_InPropertyElement;
+ SetParseMode(aAttributes);
+
+ return NS_OK;
+}
+
+nsresult
+RDFContentSinkImpl::OpenMember(const char16_t* aName,
+ const char16_t** aAttributes)
+{
+ // ensure that we're actually reading a member element by making
+ // sure that the opening tag is <rdf:li>, where "rdf:" corresponds
+ // to whatever they've declared the standard RDF namespace to be.
+ nsresult rv;
+
+ nsCOMPtr<nsIAtom> localName;
+ const nsDependentSubstring& nameSpaceURI =
+ SplitExpatName(aName, getter_AddRefs(localName));
+
+ if (!nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) ||
+ localName != kLiAtom) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("rdfxml: expected RDF:li at line %d",
+ -1)); // XXX pass in line number
+
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // The parent element is the container.
+ nsIRDFResource* container = GetContextElement(0);
+ if (! container)
+ return NS_ERROR_NULL_POINTER;
+
+ nsIRDFResource* resource;
+ if (NS_SUCCEEDED(rv = GetResourceAttribute(aAttributes, &resource))) {
+ // Okay, this node has an RDF:resource="..." attribute. That
+ // means that it's a "referenced item," as covered in [6.29].
+ nsCOMPtr<nsIRDFContainer> c;
+ NS_NewRDFContainer(getter_AddRefs(c));
+ c->Init(mDataSource, container);
+ c->AppendElement(resource);
+
+ // XXX Technically, we should _not_ fall through here and push
+ // the element onto the stack: this is supposed to be a closed
+ // node. But right now I'm lazy and the code will just Do The
+ // Right Thing so long as the RDF is well-formed.
+ NS_RELEASE(resource);
+ }
+
+ // Change state. Pushing a null context element is a bit weird,
+ // but the idea is that there really is _no_ context "property".
+ // The contained element will use nsIRDFContainer::AppendElement() to add
+ // the element to the container, which requires only the container
+ // and the element to be added.
+ PushContext(nullptr, mState, mParseMode);
+ mState = eRDFContentSinkState_InMemberElement;
+ SetParseMode(aAttributes);
+
+ return NS_OK;
+}
+
+
+nsresult
+RDFContentSinkImpl::OpenValue(const char16_t* aName, const char16_t** aAttributes)
+{
+ // a "value" can either be an object or a string: we'll only get
+ // *here* if it's an object, as raw text is added as a leaf.
+ return OpenObject(aName,aAttributes);
+}
+
+////////////////////////////////////////////////////////////////////////
+// namespace resolution
+void
+RDFContentSinkImpl::RegisterNamespaces(const char16_t **aAttributes)
+{
+ nsCOMPtr<nsIRDFXMLSink> sink = do_QueryInterface(mDataSource);
+ if (!sink) {
+ return;
+ }
+ NS_NAMED_LITERAL_STRING(xmlns, "http://www.w3.org/2000/xmlns/");
+ for (; *aAttributes; aAttributes += 2) {
+ // check the namespace
+ const char16_t* attr = aAttributes[0];
+ const char16_t* xmlnsP = xmlns.BeginReading();
+ while (*attr == *xmlnsP) {
+ ++attr;
+ ++xmlnsP;
+ }
+ if (*attr != 0xFFFF ||
+ xmlnsP != xmlns.EndReading()) {
+ continue;
+ }
+ // get the localname (or "xmlns" for the default namespace)
+ const char16_t* endLocal = ++attr;
+ while (*endLocal && *endLocal != 0xFFFF) {
+ ++endLocal;
+ }
+ nsDependentSubstring lname(attr, endLocal);
+ nsCOMPtr<nsIAtom> preferred = NS_Atomize(lname);
+ if (preferred == kXMLNSAtom) {
+ preferred = nullptr;
+ }
+ sink->AddNameSpace(preferred, nsDependentString(aAttributes[1]));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// Qualified name resolution
+
+const nsDependentSubstring
+RDFContentSinkImpl::SplitExpatName(const char16_t *aExpatName,
+ nsIAtom **aLocalName)
+{
+ /**
+ * Expat can send the following:
+ * localName
+ * namespaceURI<separator>localName
+ * namespaceURI<separator>localName<separator>prefix
+ *
+ * and we use 0xFFFF for the <separator>.
+ *
+ */
+
+ const char16_t *uriEnd = aExpatName;
+ const char16_t *nameStart = aExpatName;
+ const char16_t *pos;
+ for (pos = aExpatName; *pos; ++pos) {
+ if (*pos == 0xFFFF) {
+ if (uriEnd != aExpatName) {
+ break;
+ }
+
+ uriEnd = pos;
+ nameStart = pos + 1;
+ }
+ }
+
+ const nsDependentSubstring& nameSpaceURI = Substring(aExpatName, uriEnd);
+ *aLocalName = NS_Atomize(Substring(nameStart, pos)).take();
+ return nameSpaceURI;
+}
+
+nsresult
+RDFContentSinkImpl::InitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer)
+{
+ // Do the right kind of initialization based on the container
+ // 'type' resource, and the state of the container (i.e., 'make' a
+ // new container vs. 'reinitialize' the container).
+ nsresult rv;
+
+ static const ContainerInfo gContainerInfo[] = {
+ { &RDFContentSinkImpl::kRDF_Alt, &nsIRDFContainerUtils::IsAlt, &nsIRDFContainerUtils::MakeAlt },
+ { &RDFContentSinkImpl::kRDF_Bag, &nsIRDFContainerUtils::IsBag, &nsIRDFContainerUtils::MakeBag },
+ { &RDFContentSinkImpl::kRDF_Seq, &nsIRDFContainerUtils::IsSeq, &nsIRDFContainerUtils::MakeSeq },
+ { 0, 0, 0 },
+ };
+
+ for (const ContainerInfo* info = gContainerInfo; info->mType != 0; ++info) {
+ if (*info->mType != aContainerType)
+ continue;
+
+ bool isContainer;
+ rv = (gRDFContainerUtils->*(info->mTestFn))(mDataSource, aContainer, &isContainer);
+ if (isContainer) {
+ rv = ReinitContainer(aContainerType, aContainer);
+ }
+ else {
+ rv = (gRDFContainerUtils->*(info->mMakeFn))(mDataSource, aContainer, nullptr);
+ }
+ return rv;
+ }
+
+ NS_NOTREACHED("not an RDF container type");
+ return NS_ERROR_FAILURE;
+}
+
+
+
+nsresult
+RDFContentSinkImpl::ReinitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer)
+{
+ // Mega-kludge to deal with the fact that Make[Seq|Alt|Bag] is
+ // idempotent, and as such, containers will have state (e.g.,
+ // RDF:nextVal) maintained in the graph across loads. This
+ // re-initializes each container's RDF:nextVal to '1', and 'marks'
+ // the container as such.
+ nsresult rv;
+
+ nsCOMPtr<nsIRDFLiteral> one;
+ rv = gRDFService->GetLiteral(u"1", getter_AddRefs(one));
+ if (NS_FAILED(rv)) return rv;
+
+ // Re-initialize the 'nextval' property
+ nsCOMPtr<nsIRDFNode> nextval;
+ rv = mDataSource->GetTarget(aContainer, kRDF_nextVal, true, getter_AddRefs(nextval));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mDataSource->Change(aContainer, kRDF_nextVal, nextval, one);
+ if (NS_FAILED(rv)) return rv;
+
+ // Re-mark as a container. XXX should be kRDF_type
+ rv = mDataSource->Assert(aContainer, kRDF_instanceOf, aContainerType, true);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to mark container as such");
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+// Content stack management
+
+nsIRDFResource*
+RDFContentSinkImpl::GetContextElement(int32_t ancestor /* = 0 */)
+{
+ if ((nullptr == mContextStack) ||
+ (uint32_t(ancestor) >= mContextStack->Length())) {
+ return nullptr;
+ }
+
+ return mContextStack->ElementAt(
+ mContextStack->Length()-ancestor-1).mResource;
+}
+
+int32_t
+RDFContentSinkImpl::PushContext(nsIRDFResource *aResource,
+ RDFContentSinkState aState,
+ RDFContentSinkParseMode aParseMode)
+{
+ if (! mContextStack) {
+ mContextStack = new AutoTArray<RDFContextStackElement, 8>();
+ if (! mContextStack)
+ return 0;
+ }
+
+ RDFContextStackElement* e = mContextStack->AppendElement();
+ if (! e)
+ return mContextStack->Length();
+
+ e->mResource = aResource;
+ e->mState = aState;
+ e->mParseMode = aParseMode;
+
+ return mContextStack->Length();
+}
+
+nsresult
+RDFContentSinkImpl::PopContext(nsIRDFResource *&aResource,
+ RDFContentSinkState &aState,
+ RDFContentSinkParseMode &aParseMode)
+{
+ if ((nullptr == mContextStack) ||
+ (mContextStack->IsEmpty())) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ uint32_t i = mContextStack->Length() - 1;
+ RDFContextStackElement &e = mContextStack->ElementAt(i);
+
+ aResource = e.mResource;
+ NS_IF_ADDREF(aResource);
+ aState = e.mState;
+ aParseMode = e.mParseMode;
+
+ mContextStack->RemoveElementAt(i);
+ return NS_OK;
+}
+
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult
+NS_NewRDFContentSink(nsIRDFContentSink** aResult)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ RDFContentSinkImpl* sink = new RDFContentSinkImpl();
+ if (! sink)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(sink);
+ *aResult = sink;
+ return NS_OK;
+}
diff --git a/components/rdf/src/nsRDFContentSinkAtomList.h b/components/rdf/src/nsRDFContentSinkAtomList.h
new file mode 100644
index 000000000..5ef4f7b4e
--- /dev/null
+++ b/components/rdf/src/nsRDFContentSinkAtomList.h
@@ -0,0 +1,18 @@
+/* -*- 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/. */
+
+RDF_ATOM(kAboutAtom, "about")
+RDF_ATOM(kIdAtom, "ID")
+RDF_ATOM(kNodeIdAtom, "nodeID")
+RDF_ATOM(kAboutEachAtom, "aboutEach")
+RDF_ATOM(kResourceAtom, "resource")
+RDF_ATOM(kRDFAtom, "RDF")
+RDF_ATOM(kDescriptionAtom, "Description")
+RDF_ATOM(kBagAtom, "Bag")
+RDF_ATOM(kSeqAtom, "Seq")
+RDF_ATOM(kAltAtom, "Alt")
+RDF_ATOM(kLiAtom, "li")
+RDF_ATOM(kXMLNSAtom, "xmlns")
+RDF_ATOM(kParseTypeAtom, "parseType")
diff --git a/components/rdf/src/nsRDFService.cpp b/components/rdf/src/nsRDFService.cpp
new file mode 100644
index 000000000..13a5e7195
--- /dev/null
+++ b/components/rdf/src/nsRDFService.cpp
@@ -0,0 +1,1551 @@
+/* -*- 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/.
+ *
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+/*
+
+ This file provides the implementation for the RDF service manager.
+
+ TO DO
+ -----
+
+ 1) Implement the CreateDataBase() methods.
+
+ 2) Cache date and int literals.
+
+ */
+
+#include "nsRDFService.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsMemory.h"
+#include "nsIAtom.h"
+#include "nsIComponentManager.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFRemoteDataSource.h"
+#include "nsIServiceManager.h"
+#include "nsIFactory.h"
+#include "nsRDFCID.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsNetUtil.h"
+#include "nsIURI.h"
+#include "PLDHashTable.h"
+#include "plhash.h"
+#include "plstr.h"
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "rdf.h"
+#include "nsCRT.h"
+#include "nsCRTGlue.h"
+#include "mozilla/HashFunctions.h"
+
+using namespace mozilla;
+
+////////////////////////////////////////////////////////////////////////
+
+static NS_DEFINE_CID(kRDFXMLDataSourceCID, NS_RDFXMLDATASOURCE_CID);
+static NS_DEFINE_CID(kRDFDefaultResourceCID, NS_RDFDEFAULTRESOURCE_CID);
+
+static NS_DEFINE_IID(kIRDFLiteralIID, NS_IRDFLITERAL_IID);
+static NS_DEFINE_IID(kIRDFDateIID, NS_IRDFDATE_IID);
+static NS_DEFINE_IID(kIRDFIntIID, NS_IRDFINT_IID);
+static NS_DEFINE_IID(kIRDFNodeIID, NS_IRDFNODE_IID);
+static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
+
+static LazyLogModule gLog("nsRDFService");
+
+class BlobImpl;
+
+// These functions are copied from nsprpub/lib/ds/plhash.c, with one
+// change to free the key in DataSourceFreeEntry.
+// XXX sigh, why were DefaultAllocTable et. al. declared static, anyway?
+
+static void *
+DataSourceAllocTable(void *pool, size_t size)
+{
+ return PR_MALLOC(size);
+}
+
+static void
+DataSourceFreeTable(void *pool, void *item)
+{
+ PR_Free(item);
+}
+
+static PLHashEntry *
+DataSourceAllocEntry(void *pool, const void *key)
+{
+ return PR_NEW(PLHashEntry);
+}
+
+static void
+DataSourceFreeEntry(void *pool, PLHashEntry *he, unsigned flag)
+{
+ if (flag == HT_FREE_ENTRY) {
+ PL_strfree((char*) he->key);
+ PR_Free(he);
+ }
+}
+
+static PLHashAllocOps dataSourceHashAllocOps = {
+ DataSourceAllocTable, DataSourceFreeTable,
+ DataSourceAllocEntry, DataSourceFreeEntry
+};
+
+//----------------------------------------------------------------------
+//
+// For the mResources hashtable.
+//
+
+struct ResourceHashEntry : public PLDHashEntryHdr {
+ const char *mKey;
+ nsIRDFResource *mResource;
+
+ static PLDHashNumber
+ HashKey(const void *key)
+ {
+ return HashString(static_cast<const char *>(key));
+ }
+
+ static bool
+ MatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+ {
+ const ResourceHashEntry *entry =
+ static_cast<const ResourceHashEntry *>(hdr);
+
+ return 0 == nsCRT::strcmp(static_cast<const char *>(key),
+ entry->mKey);
+ }
+};
+
+static const PLDHashTableOps gResourceTableOps = {
+ ResourceHashEntry::HashKey,
+ ResourceHashEntry::MatchEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr
+};
+
+// ----------------------------------------------------------------------
+//
+// For the mLiterals hashtable.
+//
+
+struct LiteralHashEntry : public PLDHashEntryHdr {
+ nsIRDFLiteral *mLiteral;
+ const char16_t *mKey;
+
+ static PLDHashNumber
+ HashKey(const void *key)
+ {
+ return HashString(static_cast<const char16_t *>(key));
+ }
+
+ static bool
+ MatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+ {
+ const LiteralHashEntry *entry =
+ static_cast<const LiteralHashEntry *>(hdr);
+
+ return 0 == nsCRT::strcmp(static_cast<const char16_t *>(key),
+ entry->mKey);
+ }
+};
+
+static const PLDHashTableOps gLiteralTableOps = {
+ LiteralHashEntry::HashKey,
+ LiteralHashEntry::MatchEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr
+};
+
+// ----------------------------------------------------------------------
+//
+// For the mInts hashtable.
+//
+
+struct IntHashEntry : public PLDHashEntryHdr {
+ nsIRDFInt *mInt;
+ int32_t mKey;
+
+ static PLDHashNumber
+ HashKey(const void *key)
+ {
+ return PLDHashNumber(*static_cast<const int32_t *>(key));
+ }
+
+ static bool
+ MatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+ {
+ const IntHashEntry *entry =
+ static_cast<const IntHashEntry *>(hdr);
+
+ return *static_cast<const int32_t *>(key) == entry->mKey;
+ }
+};
+
+static const PLDHashTableOps gIntTableOps = {
+ IntHashEntry::HashKey,
+ IntHashEntry::MatchEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr
+};
+
+// ----------------------------------------------------------------------
+//
+// For the mDates hashtable.
+//
+
+struct DateHashEntry : public PLDHashEntryHdr {
+ nsIRDFDate *mDate;
+ PRTime mKey;
+
+ static PLDHashNumber
+ HashKey(const void *key)
+ {
+ // xor the low 32 bits with the high 32 bits.
+ PRTime t = *static_cast<const PRTime *>(key);
+ int32_t h32 = int32_t(t >> 32);
+ int32_t l32 = int32_t(0xffffffff & t);
+ return PLDHashNumber(l32 ^ h32);
+ }
+
+ static bool
+ MatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+ {
+ const DateHashEntry *entry =
+ static_cast<const DateHashEntry *>(hdr);
+
+ return *static_cast<const PRTime *>(key) == entry->mKey;
+ }
+};
+
+static const PLDHashTableOps gDateTableOps = {
+ DateHashEntry::HashKey,
+ DateHashEntry::MatchEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr
+};
+
+class BlobImpl : public nsIRDFBlob
+{
+public:
+ struct Data {
+ int32_t mLength;
+ uint8_t *mBytes;
+ };
+
+ BlobImpl(const uint8_t *aBytes, int32_t aLength)
+ {
+ mData.mLength = aLength;
+ mData.mBytes = new uint8_t[aLength];
+ memcpy(mData.mBytes, aBytes, aLength);
+ NS_ADDREF(RDFServiceImpl::gRDFService);
+ RDFServiceImpl::gRDFService->RegisterBlob(this);
+ }
+
+protected:
+ virtual ~BlobImpl()
+ {
+ RDFServiceImpl::gRDFService->UnregisterBlob(this);
+ // Use NS_RELEASE2() here, because we want to decrease the
+ // refcount, but not null out the gRDFService pointer (which is
+ // what a vanilla NS_RELEASE() would do).
+ nsrefcnt refcnt;
+ NS_RELEASE2(RDFServiceImpl::gRDFService, refcnt);
+ delete[] mData.mBytes;
+ }
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRDFNODE
+ NS_DECL_NSIRDFBLOB
+
+ Data mData;
+};
+
+NS_IMPL_ISUPPORTS(BlobImpl, nsIRDFNode, nsIRDFBlob)
+
+NS_IMETHODIMP
+BlobImpl::EqualsNode(nsIRDFNode *aNode, bool *aEquals)
+{
+ nsCOMPtr<nsIRDFBlob> blob = do_QueryInterface(aNode);
+ if (blob) {
+ int32_t length;
+ blob->GetLength(&length);
+
+ if (length == mData.mLength) {
+ const uint8_t *bytes;
+ blob->GetValue(&bytes);
+
+ if (0 == memcmp(bytes, mData.mBytes, length)) {
+ *aEquals = true;
+ return NS_OK;
+ }
+ }
+ }
+
+ *aEquals = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BlobImpl::GetValue(const uint8_t **aResult)
+{
+ *aResult = mData.mBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BlobImpl::GetLength(int32_t *aResult)
+{
+ *aResult = mData.mLength;
+ return NS_OK;
+}
+
+// ----------------------------------------------------------------------
+//
+// For the mBlobs hashtable.
+//
+
+struct BlobHashEntry : public PLDHashEntryHdr {
+ BlobImpl *mBlob;
+
+ static PLDHashNumber
+ HashKey(const void *key)
+ {
+ const BlobImpl::Data *data =
+ static_cast<const BlobImpl::Data *>(key);
+ return HashBytes(data->mBytes, data->mLength);
+ }
+
+ static bool
+ MatchEntry(const PLDHashEntryHdr *hdr, const void *key)
+ {
+ const BlobHashEntry *entry =
+ static_cast<const BlobHashEntry *>(hdr);
+
+ const BlobImpl::Data *left = &entry->mBlob->mData;
+
+ const BlobImpl::Data *right =
+ static_cast<const BlobImpl::Data *>(key);
+
+ return (left->mLength == right->mLength)
+ && 0 == memcmp(left->mBytes, right->mBytes, right->mLength);
+ }
+};
+
+static const PLDHashTableOps gBlobTableOps = {
+ BlobHashEntry::HashKey,
+ BlobHashEntry::MatchEntry,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub,
+ nullptr
+};
+
+////////////////////////////////////////////////////////////////////////
+// LiteralImpl
+//
+// Currently, all literals are implemented exactly the same way;
+// i.e., there is are no resource factories to allow you to generate
+// customer resources. I doubt that makes sense, anyway.
+//
+class LiteralImpl : public nsIRDFLiteral {
+public:
+ static nsresult
+ Create(const char16_t* aValue, nsIRDFLiteral** aResult);
+
+ // nsISupports
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIRDFNode
+ NS_DECL_NSIRDFNODE
+
+ // nsIRDFLiteral
+ NS_DECL_NSIRDFLITERAL
+
+protected:
+ explicit LiteralImpl(const char16_t* s);
+ virtual ~LiteralImpl();
+
+ const char16_t* GetValue() const {
+ size_t objectSize = ((sizeof(LiteralImpl) + sizeof(char16_t) - 1) / sizeof(char16_t)) * sizeof(char16_t);
+ return reinterpret_cast<const char16_t*>(reinterpret_cast<const unsigned char*>(this) + objectSize);
+ }
+};
+
+
+nsresult
+LiteralImpl::Create(const char16_t* aValue, nsIRDFLiteral** aResult)
+{
+ // Goofy math to get alignment right. Copied from nsSharedString.h.
+ size_t objectSize = ((sizeof(LiteralImpl) + sizeof(char16_t) - 1) / sizeof(char16_t)) * sizeof(char16_t);
+ size_t stringLen = nsCharTraits<char16_t>::length(aValue);
+ size_t stringSize = (stringLen + 1) * sizeof(char16_t);
+
+ void* objectPtr = operator new(objectSize + stringSize);
+ if (! objectPtr)
+ return NS_ERROR_NULL_POINTER;
+
+ char16_t* buf = reinterpret_cast<char16_t*>(static_cast<unsigned char*>(objectPtr) + objectSize);
+ nsCharTraits<char16_t>::copy(buf, aValue, stringLen + 1);
+
+ NS_ADDREF(*aResult = new (objectPtr) LiteralImpl(buf));
+ return NS_OK;
+}
+
+
+LiteralImpl::LiteralImpl(const char16_t* s)
+{
+ RDFServiceImpl::gRDFService->RegisterLiteral(this);
+ NS_ADDREF(RDFServiceImpl::gRDFService);
+}
+
+LiteralImpl::~LiteralImpl()
+{
+ RDFServiceImpl::gRDFService->UnregisterLiteral(this);
+
+ // Use NS_RELEASE2() here, because we want to decrease the
+ // refcount, but not null out the gRDFService pointer (which is
+ // what a vanilla NS_RELEASE() would do).
+ nsrefcnt refcnt;
+ NS_RELEASE2(RDFServiceImpl::gRDFService, refcnt);
+}
+
+NS_IMPL_ADDREF(LiteralImpl)
+NS_IMPL_RELEASE(LiteralImpl)
+
+nsresult
+LiteralImpl::QueryInterface(REFNSIID iid, void** result)
+{
+ if (! result)
+ return NS_ERROR_NULL_POINTER;
+
+ *result = nullptr;
+ if (iid.Equals(kIRDFLiteralIID) ||
+ iid.Equals(kIRDFNodeIID) ||
+ iid.Equals(kISupportsIID)) {
+ *result = static_cast<nsIRDFLiteral*>(this);
+ AddRef();
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+}
+
+NS_IMETHODIMP
+LiteralImpl::EqualsNode(nsIRDFNode* aNode, bool* aResult)
+{
+ nsresult rv;
+ nsIRDFLiteral* literal;
+ rv = aNode->QueryInterface(kIRDFLiteralIID, (void**) &literal);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = (static_cast<nsIRDFLiteral*>(this) == literal);
+ NS_RELEASE(literal);
+ return NS_OK;
+ }
+ else if (rv == NS_NOINTERFACE) {
+ *aResult = false;
+ return NS_OK;
+ }
+ else {
+ return rv;
+ }
+}
+
+NS_IMETHODIMP
+LiteralImpl::GetValue(char16_t* *value)
+{
+ NS_ASSERTION(value, "null ptr");
+ if (! value)
+ return NS_ERROR_NULL_POINTER;
+
+ const char16_t *temp = GetValue();
+ *value = temp? NS_strdup(temp) : 0;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+LiteralImpl::GetValueConst(const char16_t** aValue)
+{
+ *aValue = GetValue();
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+// DateImpl
+//
+
+class DateImpl : public nsIRDFDate {
+public:
+ explicit DateImpl(const PRTime s);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIRDFNode
+ NS_DECL_NSIRDFNODE
+
+ // nsIRDFDate
+ NS_IMETHOD GetValue(PRTime *value) override;
+
+private:
+ virtual ~DateImpl();
+
+ nsresult EqualsDate(nsIRDFDate* date, bool* result);
+ PRTime mValue;
+};
+
+
+DateImpl::DateImpl(const PRTime s)
+ : mValue(s)
+{
+ RDFServiceImpl::gRDFService->RegisterDate(this);
+ NS_ADDREF(RDFServiceImpl::gRDFService);
+}
+
+DateImpl::~DateImpl()
+{
+ RDFServiceImpl::gRDFService->UnregisterDate(this);
+
+ // Use NS_RELEASE2() here, because we want to decrease the
+ // refcount, but not null out the gRDFService pointer (which is
+ // what a vanilla NS_RELEASE() would do).
+ nsrefcnt refcnt;
+ NS_RELEASE2(RDFServiceImpl::gRDFService, refcnt);
+}
+
+NS_IMPL_ADDREF(DateImpl)
+NS_IMPL_RELEASE(DateImpl)
+
+nsresult
+DateImpl::QueryInterface(REFNSIID iid, void** result)
+{
+ if (! result)
+ return NS_ERROR_NULL_POINTER;
+
+ *result = nullptr;
+ if (iid.Equals(kIRDFDateIID) ||
+ iid.Equals(kIRDFNodeIID) ||
+ iid.Equals(kISupportsIID)) {
+ *result = static_cast<nsIRDFDate*>(this);
+ AddRef();
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+}
+
+NS_IMETHODIMP
+DateImpl::EqualsNode(nsIRDFNode* node, bool* result)
+{
+ nsresult rv;
+ nsIRDFDate* date;
+ if (NS_SUCCEEDED(node->QueryInterface(kIRDFDateIID, (void**) &date))) {
+ rv = EqualsDate(date, result);
+ NS_RELEASE(date);
+ }
+ else {
+ *result = false;
+ rv = NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+DateImpl::GetValue(PRTime *value)
+{
+ NS_ASSERTION(value, "null ptr");
+ if (! value)
+ return NS_ERROR_NULL_POINTER;
+
+ *value = mValue;
+ return NS_OK;
+}
+
+
+nsresult
+DateImpl::EqualsDate(nsIRDFDate* date, bool* result)
+{
+ NS_ASSERTION(date && result, "null ptr");
+ if (!date || !result)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ PRTime p;
+ if (NS_FAILED(rv = date->GetValue(&p)))
+ return rv;
+
+ *result = p == mValue;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+// IntImpl
+//
+
+class IntImpl : public nsIRDFInt {
+public:
+ explicit IntImpl(int32_t s);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIRDFNode
+ NS_DECL_NSIRDFNODE
+
+ // nsIRDFInt
+ NS_IMETHOD GetValue(int32_t *value) override;
+
+private:
+ virtual ~IntImpl();
+
+ nsresult EqualsInt(nsIRDFInt* value, bool* result);
+ int32_t mValue;
+};
+
+
+IntImpl::IntImpl(int32_t s)
+ : mValue(s)
+{
+ RDFServiceImpl::gRDFService->RegisterInt(this);
+ NS_ADDREF(RDFServiceImpl::gRDFService);
+}
+
+IntImpl::~IntImpl()
+{
+ RDFServiceImpl::gRDFService->UnregisterInt(this);
+
+ // Use NS_RELEASE2() here, because we want to decrease the
+ // refcount, but not null out the gRDFService pointer (which is
+ // what a vanilla NS_RELEASE() would do).
+ nsrefcnt refcnt;
+ NS_RELEASE2(RDFServiceImpl::gRDFService, refcnt);
+}
+
+NS_IMPL_ADDREF(IntImpl)
+NS_IMPL_RELEASE(IntImpl)
+
+nsresult
+IntImpl::QueryInterface(REFNSIID iid, void** result)
+{
+ if (! result)
+ return NS_ERROR_NULL_POINTER;
+
+ *result = nullptr;
+ if (iid.Equals(kIRDFIntIID) ||
+ iid.Equals(kIRDFNodeIID) ||
+ iid.Equals(kISupportsIID)) {
+ *result = static_cast<nsIRDFInt*>(this);
+ AddRef();
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+}
+
+NS_IMETHODIMP
+IntImpl::EqualsNode(nsIRDFNode* node, bool* result)
+{
+ nsresult rv;
+ nsIRDFInt* intValue;
+ if (NS_SUCCEEDED(node->QueryInterface(kIRDFIntIID, (void**) &intValue))) {
+ rv = EqualsInt(intValue, result);
+ NS_RELEASE(intValue);
+ }
+ else {
+ *result = false;
+ rv = NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+IntImpl::GetValue(int32_t *value)
+{
+ NS_ASSERTION(value, "null ptr");
+ if (! value)
+ return NS_ERROR_NULL_POINTER;
+
+ *value = mValue;
+ return NS_OK;
+}
+
+
+nsresult
+IntImpl::EqualsInt(nsIRDFInt* intValue, bool* result)
+{
+ NS_ASSERTION(intValue && result, "null ptr");
+ if (!intValue || !result)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ int32_t p;
+ if (NS_FAILED(rv = intValue->GetValue(&p)))
+ return rv;
+
+ *result = (p == mValue);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+// RDFServiceImpl
+
+RDFServiceImpl*
+RDFServiceImpl::gRDFService;
+
+RDFServiceImpl::RDFServiceImpl()
+ : mNamedDataSources(nullptr)
+ , mResources(&gResourceTableOps, sizeof(ResourceHashEntry))
+ , mLiterals(&gLiteralTableOps, sizeof(LiteralHashEntry))
+ , mInts(&gIntTableOps, sizeof(IntHashEntry))
+ , mDates(&gDateTableOps, sizeof(DateHashEntry))
+ , mBlobs(&gBlobTableOps, sizeof(BlobHashEntry))
+{
+ gRDFService = this;
+}
+
+nsresult
+RDFServiceImpl::Init()
+{
+ nsresult rv;
+
+ mNamedDataSources = PL_NewHashTable(23,
+ PL_HashString,
+ PL_CompareStrings,
+ PL_CompareValues,
+ &dataSourceHashAllocOps, nullptr);
+
+ if (! mNamedDataSources)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mDefaultResourceFactory = do_GetClassObject(kRDFDefaultResourceCID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get default resource factory");
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+
+RDFServiceImpl::~RDFServiceImpl()
+{
+ if (mNamedDataSources) {
+ PL_HashTableDestroy(mNamedDataSources);
+ mNamedDataSources = nullptr;
+ }
+ gRDFService = nullptr;
+}
+
+
+// static
+nsresult
+RDFServiceImpl::CreateSingleton(nsISupports* aOuter,
+ const nsIID& aIID, void **aResult)
+{
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ if (gRDFService) {
+ NS_ERROR("Trying to create RDF serviec twice.");
+ return gRDFService->QueryInterface(aIID, aResult);
+ }
+
+ RefPtr<RDFServiceImpl> serv = new RDFServiceImpl();
+ nsresult rv = serv->Init();
+ if (NS_FAILED(rv))
+ return rv;
+
+ return serv->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS(RDFServiceImpl, nsIRDFService, nsISupportsWeakReference)
+
+// Per RFC2396.
+static const uint8_t
+kLegalSchemeChars[] = {
+ // ASCII Bits Ordered Hex
+ // 01234567 76543210
+ 0x00, // 00-07
+ 0x00, // 08-0F
+ 0x00, // 10-17
+ 0x00, // 18-1F
+ 0x00, // 20-27 !"#$%&' 00000000 00000000
+ 0x28, // 28-2F ()*+,-./ 00010100 00101000 0x28
+ 0xff, // 30-37 01234567 11111111 11111111 0xFF
+ 0x03, // 38-3F 89:;<=>? 11000000 00000011 0x03
+ 0xfe, // 40-47 @ABCDEFG 01111111 11111110 0xFE
+ 0xff, // 48-4F HIJKLMNO 11111111 11111111 0xFF
+ 0xff, // 50-57 PQRSTUVW 11111111 11111111 0xFF
+ 0x87, // 58-5F XYZ[\]^_ 11100001 10000111 0x87
+ 0xfe, // 60-67 `abcdefg 01111111 11111110 0xFE
+ 0xff, // 68-6F hijklmno 11111111 11111111 0xFF
+ 0xff, // 70-77 pqrstuvw 11111111 11111111 0xFF
+ 0x07, // 78-7F xyz{|}~ 11100000 00000111 0x07
+ 0x00, 0x00, 0x00, 0x00, // >= 80
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+};
+
+static inline bool
+IsLegalSchemeCharacter(const char aChar)
+{
+ uint8_t mask = kLegalSchemeChars[aChar >> 3];
+ uint8_t bit = 1u << (aChar & 0x7);
+ return bool((mask & bit) != 0);
+}
+
+
+NS_IMETHODIMP
+RDFServiceImpl::GetResource(const nsACString& aURI, nsIRDFResource** aResource)
+{
+ // Sanity checks
+ NS_PRECONDITION(aResource != nullptr, "null ptr");
+ NS_PRECONDITION(!aURI.IsEmpty(), "URI is empty");
+ if (! aResource)
+ return NS_ERROR_NULL_POINTER;
+ if (aURI.IsEmpty())
+ return NS_ERROR_INVALID_ARG;
+
+ const nsAFlatCString& flatURI = PromiseFlatCString(aURI);
+ MOZ_LOG(gLog, LogLevel::Debug, ("rdfserv get-resource %s", flatURI.get()));
+
+ // First, check the cache to see if we've already created and
+ // registered this thing.
+ PLDHashEntryHdr *hdr = mResources.Search(flatURI.get());
+ if (hdr) {
+ ResourceHashEntry *entry = static_cast<ResourceHashEntry *>(hdr);
+ NS_ADDREF(*aResource = entry->mResource);
+ return NS_OK;
+ }
+
+ // Nope. So go to the repository to create it.
+
+ // Compute the scheme of the URI. Scan forward until we either:
+ //
+ // 1. Reach the end of the string
+ // 2. Encounter a non-alpha character
+ // 3. Encouter a colon.
+ //
+ // If we encounter a colon _before_ encountering a non-alpha
+ // character, then assume it's the scheme.
+ //
+ // XXX Although it's really not correct, we'll allow underscore
+ // characters ('_'), too.
+ nsACString::const_iterator p, end;
+ aURI.BeginReading(p);
+ aURI.EndReading(end);
+ while (p != end && IsLegalSchemeCharacter(*p))
+ ++p;
+
+ nsresult rv;
+ nsCOMPtr<nsIFactory> factory;
+
+ nsACString::const_iterator begin;
+ aURI.BeginReading(begin);
+ if (*p == ':') {
+ // There _was_ a scheme. First see if it's the same scheme
+ // that we just tried to use...
+ if (mLastFactory && mLastURIPrefix.Equals(Substring(begin, p)))
+ factory = mLastFactory;
+ else {
+ // Try to find a factory using the component manager.
+ nsACString::const_iterator begin;
+ aURI.BeginReading(begin);
+ nsAutoCString contractID;
+ contractID = NS_LITERAL_CSTRING(NS_RDF_RESOURCE_FACTORY_CONTRACTID_PREFIX) +
+ Substring(begin, p);
+
+ factory = do_GetClassObject(contractID.get());
+ if (factory) {
+ // Store the factory in our one-element cache.
+ if (p != begin) {
+ mLastFactory = factory;
+ mLastURIPrefix = Substring(begin, p);
+ }
+ }
+ }
+ }
+
+ if (! factory) {
+ // fall through to using the "default" resource factory if either:
+ //
+ // 1. The URI didn't have a scheme, or
+ // 2. There was no resource factory registered for the scheme.
+ factory = mDefaultResourceFactory;
+
+ // Store the factory in our one-element cache.
+ if (p != begin) {
+ mLastFactory = factory;
+ mLastURIPrefix = Substring(begin, p);
+ }
+ }
+
+ nsIRDFResource *result;
+ rv = factory->CreateInstance(nullptr, NS_GET_IID(nsIRDFResource), (void**) &result);
+ if (NS_FAILED(rv)) return rv;
+
+ // Now initialize it with its URI. At this point, the resource
+ // implementation should register itself with the RDF service.
+ rv = result->Init(flatURI.get());
+ if (NS_FAILED(rv)) {
+ NS_ERROR("unable to initialize resource");
+ NS_RELEASE(result);
+ return rv;
+ }
+
+ *aResource = result; // already refcounted from repository
+ return rv;
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::GetUnicodeResource(const nsAString& aURI, nsIRDFResource** aResource)
+{
+ return GetResource(NS_ConvertUTF16toUTF8(aURI), aResource);
+}
+
+
+NS_IMETHODIMP
+RDFServiceImpl::GetAnonymousResource(nsIRDFResource** aResult)
+{
+static uint32_t gCounter = 0;
+static char gChars[] = "0123456789abcdef"
+ "ghijklmnopqrstuv"
+ "wxyzABCDEFGHIJKL"
+ "MNOPQRSTUVWXYZ.+";
+
+static int32_t kMask = 0x003f;
+static int32_t kShift = 6;
+
+ if (! gCounter) {
+ // Start it at a semi-unique value, just to minimize the
+ // chance that we get into a situation where
+ //
+ // 1. An anonymous resource gets serialized out in a graph
+ // 2. Reboot
+ // 3. The same anonymous resource gets requested, and refers
+ // to something completely different.
+ // 4. The serialization is read back in.
+ gCounter = uint32_t(PR_Now());
+ }
+
+ nsresult rv;
+ nsAutoCString s;
+
+ do {
+ // Ugh, this is a really sloppy way to do this; I copied the
+ // implementation from the days when it lived outside the RDF
+ // service. Now that it's a member we can be more cleverer.
+
+ s.Truncate();
+ s.AppendLiteral("rdf:#$");
+
+ uint32_t id = ++gCounter;
+ while (id) {
+ char ch = gChars[(id & kMask)];
+ s.Append(ch);
+ id >>= kShift;
+ }
+
+ nsIRDFResource* resource;
+ rv = GetResource(s, &resource);
+ if (NS_FAILED(rv)) return rv;
+
+ // XXX an ugly but effective way to make sure that this
+ // resource is really unique in the world.
+ resource->AddRef();
+ nsrefcnt refcnt = resource->Release();
+
+ if (refcnt == 1) {
+ *aResult = resource;
+ break;
+ }
+
+ NS_RELEASE(resource);
+ } while (1);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFServiceImpl::GetLiteral(const char16_t* aValue, nsIRDFLiteral** aLiteral)
+{
+ NS_PRECONDITION(aValue != nullptr, "null ptr");
+ if (! aValue)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aLiteral != nullptr, "null ptr");
+ if (! aLiteral)
+ return NS_ERROR_NULL_POINTER;
+
+ // See if we have one already cached
+ PLDHashEntryHdr *hdr = mLiterals.Search(aValue);
+ if (hdr) {
+ LiteralHashEntry *entry = static_cast<LiteralHashEntry *>(hdr);
+ NS_ADDREF(*aLiteral = entry->mLiteral);
+ return NS_OK;
+ }
+
+ // Nope. Create a new one
+ return LiteralImpl::Create(aValue, aLiteral);
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::GetDateLiteral(PRTime aTime, nsIRDFDate** aResult)
+{
+ // See if we have one already cached
+ PLDHashEntryHdr *hdr = mDates.Search(&aTime);
+ if (hdr) {
+ DateHashEntry *entry = static_cast<DateHashEntry *>(hdr);
+ NS_ADDREF(*aResult = entry->mDate);
+ return NS_OK;
+ }
+
+ DateImpl* result = new DateImpl(aTime);
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult = result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::GetIntLiteral(int32_t aInt, nsIRDFInt** aResult)
+{
+ // See if we have one already cached
+ PLDHashEntryHdr *hdr = mInts.Search(&aInt);
+ if (hdr) {
+ IntHashEntry *entry = static_cast<IntHashEntry *>(hdr);
+ NS_ADDREF(*aResult = entry->mInt);
+ return NS_OK;
+ }
+
+ IntImpl* result = new IntImpl(aInt);
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult = result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::GetBlobLiteral(const uint8_t *aBytes, int32_t aLength,
+ nsIRDFBlob **aResult)
+{
+ BlobImpl::Data key = { aLength, const_cast<uint8_t *>(aBytes) };
+
+ PLDHashEntryHdr *hdr = mBlobs.Search(&key);
+ if (hdr) {
+ BlobHashEntry *entry = static_cast<BlobHashEntry *>(hdr);
+ NS_ADDREF(*aResult = entry->mBlob);
+ return NS_OK;
+ }
+
+ BlobImpl *result = new BlobImpl(aBytes, aLength);
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ NS_ADDREF(*aResult = result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::IsAnonymousResource(nsIRDFResource* aResource, bool* _result)
+{
+ NS_PRECONDITION(aResource != nullptr, "null ptr");
+ if (! aResource)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ const char* uri;
+ rv = aResource->GetValueConst(&uri);
+ if (NS_FAILED(rv)) return rv;
+
+ if ((uri[0] == 'r') &&
+ (uri[1] == 'd') &&
+ (uri[2] == 'f') &&
+ (uri[3] == ':') &&
+ (uri[4] == '#') &&
+ (uri[5] == '$')) {
+ *_result = true;
+ }
+ else {
+ *_result = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::RegisterResource(nsIRDFResource* aResource, bool aReplace)
+{
+ NS_PRECONDITION(aResource != nullptr, "null ptr");
+ if (! aResource)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ const char* uri;
+ rv = aResource->GetValueConst(&uri);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get URI from resource");
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(uri != nullptr, "resource has no URI");
+ if (! uri)
+ return NS_ERROR_NULL_POINTER;
+
+ PLDHashEntryHdr *hdr = mResources.Search(uri);
+ if (hdr) {
+ if (!aReplace) {
+ NS_WARNING("resource already registered, and replace not specified");
+ return NS_ERROR_FAILURE; // already registered
+ }
+
+ // N.B., we do _not_ release the original resource because we
+ // only ever held a weak reference to it. We simply replace
+ // it.
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv replace-resource [%p] <-- [%p] %s",
+ static_cast<ResourceHashEntry *>(hdr)->mResource,
+ aResource, (const char*) uri));
+ }
+ else {
+ hdr = mResources.Add(uri, fallible);
+ if (! hdr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv register-resource [%p] %s",
+ aResource, (const char*) uri));
+ }
+
+ // N.B., we only hold a weak reference to the resource: that way,
+ // the resource can be destroyed when the last refcount goes
+ // away. The single addref that the CreateResource() call made
+ // will be owned by the callee.
+ ResourceHashEntry *entry = static_cast<ResourceHashEntry *>(hdr);
+ entry->mResource = aResource;
+ entry->mKey = uri;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::UnregisterResource(nsIRDFResource* aResource)
+{
+ NS_PRECONDITION(aResource != nullptr, "null ptr");
+ if (! aResource)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ const char* uri;
+ rv = aResource->GetValueConst(&uri);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ASSERTION(uri != nullptr, "resource has no URI");
+ if (! uri)
+ return NS_ERROR_UNEXPECTED;
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv unregister-resource [%p] %s",
+ aResource, (const char*) uri));
+
+#ifdef DEBUG
+ if (!mResources.Search(uri))
+ NS_WARNING("resource was never registered");
+#endif
+
+ mResources.Remove(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::RegisterDataSource(nsIRDFDataSource* aDataSource, bool aReplace)
+{
+ NS_PRECONDITION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ nsXPIDLCString uri;
+ rv = aDataSource->GetURI(getter_Copies(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ PLHashEntry** hep =
+ PL_HashTableRawLookup(mNamedDataSources, (*mNamedDataSources->keyHash)(uri), uri);
+
+ if (*hep) {
+ if (! aReplace)
+ return NS_ERROR_FAILURE; // already registered
+
+ // N.B., we only hold a weak reference to the datasource, so
+ // just replace the old with the new and don't touch any
+ // refcounts.
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv replace-datasource [%p] <-- [%p] %s",
+ (*hep)->value, aDataSource, (const char*) uri));
+
+ (*hep)->value = aDataSource;
+ }
+ else {
+ const char* key = PL_strdup(uri);
+ if (! key)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ PL_HashTableAdd(mNamedDataSources, key, aDataSource);
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv register-datasource [%p] %s",
+ aDataSource, (const char*) uri));
+
+ // N.B., we only hold a weak reference to the datasource, so don't
+ // addref.
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::UnregisterDataSource(nsIRDFDataSource* aDataSource)
+{
+ NS_PRECONDITION(aDataSource != nullptr, "null ptr");
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ nsXPIDLCString uri;
+ rv = aDataSource->GetURI(getter_Copies(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ //NS_ASSERTION(uri != nullptr, "datasource has no URI");
+ if (! uri)
+ return NS_ERROR_UNEXPECTED;
+
+ PLHashEntry** hep =
+ PL_HashTableRawLookup(mNamedDataSources, (*mNamedDataSources->keyHash)(uri), uri);
+
+ // It may well be that this datasource was never registered. If
+ // so, don't unregister it.
+ if (! *hep || ((*hep)->value != aDataSource))
+ return NS_OK;
+
+ // N.B., we only held a weak reference to the datasource, so we
+ // don't release here.
+ PL_HashTableRawRemove(mNamedDataSources, hep, *hep);
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv unregister-datasource [%p] %s",
+ aDataSource, (const char*) uri));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::GetDataSource(const char* aURI, nsIRDFDataSource** aDataSource)
+{
+ // Use the other GetDataSource and ask for a non-blocking Refresh.
+ // If you wanted it loaded synchronously, then you should've tried to do it
+ // yourself, or used GetDataSourceBlocking.
+ return GetDataSource( aURI, false, aDataSource );
+}
+
+NS_IMETHODIMP
+RDFServiceImpl::GetDataSourceBlocking(const char* aURI, nsIRDFDataSource** aDataSource)
+{
+ // Use GetDataSource and ask for a blocking Refresh.
+ return GetDataSource( aURI, true, aDataSource );
+}
+
+nsresult
+RDFServiceImpl::GetDataSource(const char* aURI, bool aBlock, nsIRDFDataSource** aDataSource)
+{
+ NS_PRECONDITION(aURI != nullptr, "null ptr");
+ if (! aURI)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+
+ // Attempt to canonify the URI before we look for it in the
+ // cache. We won't bother doing this on `rdf:' URIs to avoid
+ // useless (and expensive) protocol handler lookups.
+ nsAutoCString spec(aURI);
+
+ if (!StringBeginsWith(spec, NS_LITERAL_CSTRING("rdf:"))) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), spec);
+ if (uri) {
+ rv = uri->GetSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ // First, check the cache to see if we already have this
+ // datasource loaded and initialized.
+ {
+ nsIRDFDataSource* cached =
+ static_cast<nsIRDFDataSource*>(PL_HashTableLookup(mNamedDataSources, spec.get()));
+
+ if (cached) {
+ NS_ADDREF(cached);
+ *aDataSource = cached;
+ return NS_OK;
+ }
+ }
+
+ // Nope. So go to the repository to try to create it.
+ nsCOMPtr<nsIRDFDataSource> ds;
+ if (StringBeginsWith(spec, NS_LITERAL_CSTRING("rdf:"))) {
+ // It's a built-in data source. Convert it to a contract ID.
+ nsAutoCString contractID(
+ NS_LITERAL_CSTRING(NS_RDF_DATASOURCE_CONTRACTID_PREFIX) +
+ Substring(spec, 4, spec.Length() - 4));
+
+ // Strip params to get ``base'' contractID for data source.
+ int32_t p = contractID.FindChar(char16_t('&'));
+ if (p >= 0)
+ contractID.Truncate(p);
+
+ ds = do_GetService(contractID.get(), &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFRemoteDataSource> remote = do_QueryInterface(ds);
+ if (remote) {
+ rv = remote->Init(spec.get());
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ else {
+ // Try to load this as an RDF/XML data source
+ ds = do_CreateInstance(kRDFXMLDataSourceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFRemoteDataSource> remote(do_QueryInterface(ds));
+ NS_ASSERTION(remote, "not a remote RDF/XML data source!");
+ if (! remote) return NS_ERROR_UNEXPECTED;
+
+ rv = remote->Init(spec.get());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = remote->Refresh(aBlock);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ *aDataSource = ds;
+ NS_ADDREF(*aDataSource);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult
+RDFServiceImpl::RegisterLiteral(nsIRDFLiteral* aLiteral)
+{
+ const char16_t* value;
+ aLiteral->GetValueConst(&value);
+
+ NS_ASSERTION(!mLiterals.Search(value), "literal already registered");
+
+ PLDHashEntryHdr *hdr = mLiterals.Add(value, fallible);
+ if (! hdr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ LiteralHashEntry *entry = static_cast<LiteralHashEntry *>(hdr);
+
+ // N.B., we only hold a weak reference to the literal: that
+ // way, the literal can be destroyed when the last refcount
+ // goes away. The single addref that the CreateLiteral() call
+ // made will be owned by the callee.
+ entry->mLiteral = aLiteral;
+ entry->mKey = value;
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv register-literal [%p] %s",
+ aLiteral, (const char16_t*) value));
+
+ return NS_OK;
+}
+
+
+nsresult
+RDFServiceImpl::UnregisterLiteral(nsIRDFLiteral* aLiteral)
+{
+ const char16_t* value;
+ aLiteral->GetValueConst(&value);
+
+ NS_ASSERTION(mLiterals.Search(value), "literal was never registered");
+
+ mLiterals.Remove(value);
+
+ // N.B. that we _don't_ release the literal: we only held a weak
+ // reference to it in the hashtable.
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv unregister-literal [%p] %s",
+ aLiteral, (const char16_t*) value));
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+nsresult
+RDFServiceImpl::RegisterInt(nsIRDFInt* aInt)
+{
+ int32_t value;
+ aInt->GetValue(&value);
+
+ NS_ASSERTION(!mInts.Search(&value), "int already registered");
+
+ PLDHashEntryHdr *hdr = mInts.Add(&value, fallible);
+ if (! hdr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ IntHashEntry *entry = static_cast<IntHashEntry *>(hdr);
+
+ // N.B., we only hold a weak reference to the literal: that
+ // way, the literal can be destroyed when the last refcount
+ // goes away. The single addref that the CreateInt() call
+ // made will be owned by the callee.
+ entry->mInt = aInt;
+ entry->mKey = value;
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv register-int [%p] %d",
+ aInt, value));
+
+ return NS_OK;
+}
+
+
+nsresult
+RDFServiceImpl::UnregisterInt(nsIRDFInt* aInt)
+{
+ int32_t value;
+ aInt->GetValue(&value);
+
+ NS_ASSERTION(mInts.Search(&value), "int was never registered");
+
+ mInts.Remove(&value);
+
+ // N.B. that we _don't_ release the literal: we only held a weak
+ // reference to it in the hashtable.
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv unregister-int [%p] %d",
+ aInt, value));
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+
+nsresult
+RDFServiceImpl::RegisterDate(nsIRDFDate* aDate)
+{
+ PRTime value;
+ aDate->GetValue(&value);
+
+ NS_ASSERTION(!mDates.Search(&value), "date already registered");
+
+ PLDHashEntryHdr *hdr = mDates.Add(&value, fallible);
+ if (! hdr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ DateHashEntry *entry = static_cast<DateHashEntry *>(hdr);
+
+ // N.B., we only hold a weak reference to the literal: that
+ // way, the literal can be destroyed when the last refcount
+ // goes away. The single addref that the CreateDate() call
+ // made will be owned by the callee.
+ entry->mDate = aDate;
+ entry->mKey = value;
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv register-date [%p] %ld",
+ aDate, value));
+
+ return NS_OK;
+}
+
+
+nsresult
+RDFServiceImpl::UnregisterDate(nsIRDFDate* aDate)
+{
+ PRTime value;
+ aDate->GetValue(&value);
+
+ NS_ASSERTION(mDates.Search(&value), "date was never registered");
+
+ mDates.Remove(&value);
+
+ // N.B. that we _don't_ release the literal: we only held a weak
+ // reference to it in the hashtable.
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv unregister-date [%p] %ld",
+ aDate, value));
+
+ return NS_OK;
+}
+
+nsresult
+RDFServiceImpl::RegisterBlob(BlobImpl *aBlob)
+{
+ NS_ASSERTION(!mBlobs.Search(&aBlob->mData), "blob already registered");
+
+ PLDHashEntryHdr *hdr = mBlobs.Add(&aBlob->mData, fallible);
+ if (! hdr)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ BlobHashEntry *entry = static_cast<BlobHashEntry *>(hdr);
+
+ // N.B., we only hold a weak reference to the literal: that
+ // way, the literal can be destroyed when the last refcount
+ // goes away. The single addref that the CreateInt() call
+ // made will be owned by the callee.
+ entry->mBlob = aBlob;
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv register-blob [%p] %s",
+ aBlob, aBlob->mData.mBytes));
+
+ return NS_OK;
+}
+
+nsresult
+RDFServiceImpl::UnregisterBlob(BlobImpl *aBlob)
+{
+ NS_ASSERTION(mBlobs.Search(&aBlob->mData), "blob was never registered");
+
+ mBlobs.Remove(&aBlob->mData);
+
+ // N.B. that we _don't_ release the literal: we only held a weak
+ // reference to it in the hashtable.
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfserv unregister-blob [%p] %s",
+ aBlob, aBlob->mData.mBytes));
+
+ return NS_OK;
+}
diff --git a/components/rdf/src/nsRDFService.h b/components/rdf/src/nsRDFService.h
new file mode 100644
index 000000000..0cc8c79ce
--- /dev/null
+++ b/components/rdf/src/nsRDFService.h
@@ -0,0 +1,79 @@
+/* -*- 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/.
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+#ifndef nsRDFService_h__
+#define nsRDFService_h__
+
+#include "nsIRDFService.h"
+#include "nsWeakReference.h"
+#include "nsIFactory.h"
+#include "nsCOMPtr.h"
+#include "PLDHashTable.h"
+#include "nsString.h"
+
+struct PLHashTable;
+class nsIRDFLiteral;
+class nsIRDFInt;
+class nsIRDFDate;
+class BlobImpl;
+
+class RDFServiceImpl final : public nsIRDFService,
+ public nsSupportsWeakReference
+{
+protected:
+ PLHashTable* mNamedDataSources;
+ PLDHashTable mResources;
+ PLDHashTable mLiterals;
+ PLDHashTable mInts;
+ PLDHashTable mDates;
+ PLDHashTable mBlobs;
+
+ nsCString mLastURIPrefix;
+ nsCOMPtr<nsIFactory> mLastFactory;
+ nsCOMPtr<nsIFactory> mDefaultResourceFactory;
+
+ RDFServiceImpl();
+ nsresult Init();
+ virtual ~RDFServiceImpl();
+
+public:
+ static RDFServiceImpl *gRDFService NS_VISIBILITY_HIDDEN;
+ static nsresult CreateSingleton(nsISupports* aOuter,
+ const nsIID& aIID, void **aResult);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ // nsIRDFService
+ NS_DECL_NSIRDFSERVICE
+
+ // Implementation methods
+ nsresult RegisterLiteral(nsIRDFLiteral* aLiteral);
+ nsresult UnregisterLiteral(nsIRDFLiteral* aLiteral);
+ nsresult RegisterInt(nsIRDFInt* aInt);
+ nsresult UnregisterInt(nsIRDFInt* aInt);
+ nsresult RegisterDate(nsIRDFDate* aDate);
+ nsresult UnregisterDate(nsIRDFDate* aDate);
+ nsresult RegisterBlob(BlobImpl* aBlob);
+ nsresult UnregisterBlob(BlobImpl* aBlob);
+
+ nsresult GetDataSource(const char *aURI, bool aBlock, nsIRDFDataSource **aDataSource );
+};
+
+#endif // nsRDFService_h__
diff --git a/components/rdf/src/nsRDFXMLDataSource.cpp b/components/rdf/src/nsRDFXMLDataSource.cpp
new file mode 100644
index 000000000..0e9127420
--- /dev/null
+++ b/components/rdf/src/nsRDFXMLDataSource.cpp
@@ -0,0 +1,1179 @@
+/* -*- 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/. */
+
+/*
+
+ A data source that can read itself from and write itself to an
+ RDF/XML stream.
+
+ For more information on the RDF/XML syntax,
+ see http://www.w3.org/TR/REC-rdf-syntax/.
+
+ This code is based on the final W3C Recommendation,
+ http://www.w3.org/TR/1999/REC-rdf-syntax-19990222.
+
+
+ TO DO
+ -----
+
+ 1) Right now, the only kind of stream data sources that are _really_
+ writable are "file:" URIs. (In fact, _all_ "file:" URIs are
+ writable, modulo file system permissions; this may lead to some
+ surprising behavior.) Eventually, it'd be great if we could open
+ an arbitrary nsIOutputStream on *any* URL, and Netlib could just
+ do the magic.
+
+ 2) Implement a more terse output for "typed" nodes; that is, instead
+ of "RDF:Description type='ns:foo'", just output "ns:foo".
+
+ 3) When re-serializing, we "cheat" for Descriptions that talk about
+ inline resources (i.e.., using the `ID' attribute specified in
+ [6.21]). Instead of writing an `ID="foo"' for the first instance,
+ and then `about="#foo"' for each subsequent instance, we just
+ _always_ write `about="#foo"'.
+
+ We do this so that we can handle the case where an RDF container
+ has been assigned arbitrary properties: the spec says we can't
+ dangle the attributes directly off the container, so we need to
+ refer to it. Of course, with a little cleverness, we could fix
+ this. But who cares?
+
+ 4) When re-serializing containers. We have to cheat on some
+ containers, and use an illegal "about=" construct. We do this to
+ handle containers that have been assigned URIs outside of the
+ local document.
+
+
+ Logging
+ -------
+
+ To turn on logging for this module, set
+
+ MOZ_LOG=nsRDFXMLDataSource:5
+
+ */
+
+#include "nsIFileStreams.h"
+#include "nsIOutputStream.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIDTD.h"
+#include "nsIRDFPurgeableDataSource.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFRemoteDataSource.h"
+#include "nsIRDFService.h"
+#include "nsIRDFXMLParser.h"
+#include "nsIRDFXMLSerializer.h"
+#include "nsIRDFXMLSink.h"
+#include "nsIRDFXMLSource.h"
+#include "nsISafeOutputStream.h"
+#include "nsIServiceManager.h"
+#include "nsIStreamListener.h"
+#include "nsIURL.h"
+#include "nsIFileURL.h"
+#include "nsISafeOutputStream.h"
+#include "nsIChannel.h"
+#include "nsRDFCID.h"
+#include "nsRDFBaseDataSources.h"
+#include "nsCOMArray.h"
+#include "nsXPIDLString.h"
+#include "plstr.h"
+#include "prio.h"
+#include "prthread.h"
+#include "rdf.h"
+#include "rdfutil.h"
+#include "mozilla/Logging.h"
+#include "nsNameSpaceMap.h"
+#include "nsCRT.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIChannelEventSink.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsNetUtil.h"
+#include "nsIContentPolicy.h"
+#include "nsContentUtils.h"
+
+#include "rdfIDataSource.h"
+
+//----------------------------------------------------------------------
+//
+// RDFXMLDataSourceImpl
+//
+
+class RDFXMLDataSourceImpl : public nsIRDFDataSource,
+ public nsIRDFRemoteDataSource,
+ public nsIRDFXMLSink,
+ public nsIRDFXMLSource,
+ public nsIStreamListener,
+ public rdfIDataSource,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink
+{
+protected:
+ enum LoadState {
+ eLoadState_Unloaded,
+ eLoadState_Pending,
+ eLoadState_Loading,
+ eLoadState_Loaded
+ };
+
+ nsCOMPtr<nsIRDFDataSource> mInner;
+ bool mIsWritable; // true if the document can be written back
+ bool mIsDirty; // true if the document should be written back
+ LoadState mLoadState; // what we're doing now
+ nsCOMArray<nsIRDFXMLSinkObserver> mObservers;
+ nsCOMPtr<nsIURI> mURL;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsNameSpaceMap mNameSpaces;
+
+ // pseudo-constants
+ static int32_t gRefCnt;
+ static nsIRDFService* gRDFService;
+
+ static mozilla::LazyLogModule gLog;
+
+ nsresult Init();
+ RDFXMLDataSourceImpl(void);
+ virtual ~RDFXMLDataSourceImpl(void);
+ nsresult rdfXMLFlush(nsIURI *aURI);
+
+ friend nsresult
+ NS_NewRDFXMLDataSource(nsIRDFDataSource** aResult);
+
+ inline bool IsLoading() {
+ return (mLoadState == eLoadState_Pending) ||
+ (mLoadState == eLoadState_Loading);
+ }
+
+public:
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(RDFXMLDataSourceImpl,
+ nsIRDFDataSource)
+
+ // nsIRDFDataSource
+ NS_IMETHOD GetURI(char* *uri) override;
+
+ NS_IMETHOD GetSource(nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ nsIRDFResource** source) override {
+ return mInner->GetSource(property, target, tv, source);
+ }
+
+ NS_IMETHOD GetSources(nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ nsISimpleEnumerator** sources) override {
+ return mInner->GetSources(property, target, tv, sources);
+ }
+
+ NS_IMETHOD GetTarget(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsIRDFNode** target) override {
+ return mInner->GetTarget(source, property, tv, target);
+ }
+
+ NS_IMETHOD GetTargets(nsIRDFResource* source,
+ nsIRDFResource* property,
+ bool tv,
+ nsISimpleEnumerator** targets) override {
+ return mInner->GetTargets(source, property, tv, targets);
+ }
+
+ NS_IMETHOD Assert(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool tv) override;
+
+ NS_IMETHOD Unassert(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target) override;
+
+ NS_IMETHOD Change(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget) override;
+
+ NS_IMETHOD Move(nsIRDFResource* aOldSource,
+ nsIRDFResource* aNewSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget) override;
+
+ NS_IMETHOD HasAssertion(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target,
+ bool tv,
+ bool* hasAssertion) override {
+ return mInner->HasAssertion(source, property, target, tv, hasAssertion);
+ }
+
+ NS_IMETHOD AddObserver(nsIRDFObserver* aObserver) override {
+ return mInner->AddObserver(aObserver);
+ }
+
+ NS_IMETHOD RemoveObserver(nsIRDFObserver* aObserver) override {
+ return mInner->RemoveObserver(aObserver);
+ }
+
+ NS_IMETHOD HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *_retval) override {
+ return mInner->HasArcIn(aNode, aArc, _retval);
+ }
+
+ NS_IMETHOD HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *_retval) override {
+ return mInner->HasArcOut(aSource, aArc, _retval);
+ }
+
+ NS_IMETHOD ArcLabelsIn(nsIRDFNode* node,
+ nsISimpleEnumerator** labels) override {
+ return mInner->ArcLabelsIn(node, labels);
+ }
+
+ NS_IMETHOD ArcLabelsOut(nsIRDFResource* source,
+ nsISimpleEnumerator** labels) override {
+ return mInner->ArcLabelsOut(source, labels);
+ }
+
+ NS_IMETHOD GetAllResources(nsISimpleEnumerator** aResult) override {
+ return mInner->GetAllResources(aResult);
+ }
+
+ NS_IMETHOD GetAllCmds(nsIRDFResource* source,
+ nsISimpleEnumerator/*<nsIRDFResource>*/** commands) override {
+ return mInner->GetAllCmds(source, commands);
+ }
+
+ NS_IMETHOD IsCommandEnabled(nsISupports* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports* aArguments,
+ bool* aResult) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD DoCommand(nsISupports* aSources,
+ nsIRDFResource* aCommand,
+ nsISupports* aArguments) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD BeginUpdateBatch() override {
+ return mInner->BeginUpdateBatch();
+ }
+
+ NS_IMETHOD EndUpdateBatch() override {
+ return mInner->EndUpdateBatch();
+ }
+
+ // nsIRDFRemoteDataSource interface
+ NS_DECL_NSIRDFREMOTEDATASOURCE
+
+ // nsIRDFXMLSink interface
+ NS_DECL_NSIRDFXMLSINK
+
+ // nsIRDFXMLSource interface
+ NS_DECL_NSIRDFXMLSOURCE
+
+ // nsIRequestObserver
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // nsIStreamListener
+ NS_DECL_NSISTREAMLISTENER
+
+ // nsIInterfaceRequestor
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ // nsIChannelEventSink
+ NS_DECL_NSICHANNELEVENTSINK
+
+ // rdfIDataSource
+ NS_IMETHOD VisitAllSubjects(rdfITripleVisitor *aVisitor) override {
+ nsresult rv;
+ nsCOMPtr<rdfIDataSource> rdfds = do_QueryInterface(mInner, &rv);
+ if (NS_FAILED(rv)) return rv;
+ return rdfds->VisitAllSubjects(aVisitor);
+ }
+
+ NS_IMETHOD VisitAllTriples(rdfITripleVisitor *aVisitor) override {
+ nsresult rv;
+ nsCOMPtr<rdfIDataSource> rdfds = do_QueryInterface(mInner, &rv);
+ if (NS_FAILED(rv)) return rv;
+ return rdfds->VisitAllTriples(aVisitor);
+ }
+
+ // Implementation methods
+ bool
+ MakeQName(nsIRDFResource* aResource,
+ nsString& property,
+ nsString& nameSpacePrefix,
+ nsString& nameSpaceURI);
+
+ nsresult
+ SerializeAssertion(nsIOutputStream* aStream,
+ nsIRDFResource* aResource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aValue);
+
+ nsresult
+ SerializeProperty(nsIOutputStream* aStream,
+ nsIRDFResource* aResource,
+ nsIRDFResource* aProperty);
+
+ bool
+ IsContainerProperty(nsIRDFResource* aProperty);
+
+ nsresult
+ SerializeDescription(nsIOutputStream* aStream,
+ nsIRDFResource* aResource);
+
+ nsresult
+ SerializeMember(nsIOutputStream* aStream,
+ nsIRDFResource* aContainer,
+ nsIRDFNode* aMember);
+
+ nsresult
+ SerializeContainer(nsIOutputStream* aStream,
+ nsIRDFResource* aContainer);
+
+ nsresult
+ SerializePrologue(nsIOutputStream* aStream);
+
+ nsresult
+ SerializeEpilogue(nsIOutputStream* aStream);
+
+ bool
+ IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType);
+
+protected:
+ nsresult
+ BlockingParse(nsIURI* aURL, nsIStreamListener* aConsumer);
+};
+
+int32_t RDFXMLDataSourceImpl::gRefCnt = 0;
+nsIRDFService* RDFXMLDataSourceImpl::gRDFService;
+
+mozilla::LazyLogModule RDFXMLDataSourceImpl::gLog("nsRDFXMLDataSource");
+
+static const char kFileURIPrefix[] = "file:";
+static const char kResourceURIPrefix[] = "resource:";
+
+
+//----------------------------------------------------------------------
+
+nsresult
+NS_NewRDFXMLDataSource(nsIRDFDataSource** aResult)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ RDFXMLDataSourceImpl* datasource = new RDFXMLDataSourceImpl();
+ if (! datasource)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+ rv = datasource->Init();
+
+ if (NS_FAILED(rv)) {
+ delete datasource;
+ return rv;
+ }
+
+ NS_ADDREF(datasource);
+ *aResult = datasource;
+ return NS_OK;
+}
+
+
+RDFXMLDataSourceImpl::RDFXMLDataSourceImpl(void)
+ : mIsWritable(true),
+ mIsDirty(false),
+ mLoadState(eLoadState_Unloaded)
+{
+}
+
+
+nsresult
+RDFXMLDataSourceImpl::Init()
+{
+ nsresult rv;
+ NS_DEFINE_CID(kRDFInMemoryDataSourceCID, NS_RDFINMEMORYDATASOURCE_CID);
+ mInner = do_CreateInstance(kRDFInMemoryDataSourceCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ if (gRefCnt++ == 0) {
+ NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+ rv = CallGetService(kRDFServiceCID, &gRDFService);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+
+RDFXMLDataSourceImpl::~RDFXMLDataSourceImpl(void)
+{
+ // Unregister first so that nobody else tries to get us.
+ (void) gRDFService->UnregisterDataSource(this);
+
+ // Now flush contents
+ (void) Flush();
+
+ // Release RDF/XML sink observers
+ mObservers.Clear();
+
+ if (--gRefCnt == 0)
+ NS_IF_RELEASE(gRDFService);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(RDFXMLDataSourceImpl)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_0(RDFXMLDataSourceImpl)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RDFXMLDataSourceImpl)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RDFXMLDataSourceImpl)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RDFXMLDataSourceImpl)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RDFXMLDataSourceImpl)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFRemoteDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFXMLSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFXMLSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(rdfIDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRDFDataSource)
+NS_INTERFACE_MAP_END
+
+// nsIInterfaceRequestor
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::GetInterface(const nsIID& aIID, void** aSink)
+{
+ return QueryInterface(aIID, aSink);
+}
+
+nsresult
+RDFXMLDataSourceImpl::BlockingParse(nsIURI* aURL, nsIStreamListener* aConsumer)
+{
+ nsresult rv;
+
+ // XXX I really hate the way that we're spoon-feeding this stuff
+ // to the parser: it seems like this is something that netlib
+ // should be able to do by itself.
+
+ nsCOMPtr<nsIChannel> channel;
+
+ // Null LoadGroup ?
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ aURL,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<nsIInputStream> in;
+ rv = channel->Open2(getter_AddRefs(in));
+
+ // Report success if the file doesn't exist, but propagate other errors.
+ if (rv == NS_ERROR_FILE_NOT_FOUND) return NS_OK;
+ if (NS_FAILED(rv)) return rv;
+
+ if (! in) {
+ NS_ERROR("no input stream");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Wrap the channel's input stream in a buffered stream to ensure that
+ // ReadSegments is implemented (which OnDataAvailable expects).
+ nsCOMPtr<nsIInputStream> bufStream;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), in,
+ 4096 /* buffer size */);
+ if (NS_FAILED(rv)) return rv;
+
+ // Notify load observers
+ int32_t i;
+ for (i = mObservers.Count() - 1; i >= 0; --i) {
+ // Make sure to hold a strong reference to the observer so
+ // that it doesn't go away in this call if it removes itself
+ // as an observer
+ nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i];
+
+ if (obs) {
+ obs->OnBeginLoad(this);
+ }
+ }
+
+ rv = aConsumer->OnStartRequest(channel, nullptr);
+
+ uint64_t offset = 0;
+ while (NS_SUCCEEDED(rv)) {
+ // Skip ODA if the channel is canceled
+ channel->GetStatus(&rv);
+ if (NS_FAILED(rv))
+ break;
+
+ uint64_t avail;
+ if (NS_FAILED(rv = bufStream->Available(&avail)))
+ break; // error
+
+ if (avail == 0)
+ break; // eof
+
+ if (avail > UINT32_MAX)
+ avail = UINT32_MAX;
+
+ rv = aConsumer->OnDataAvailable(channel, nullptr, bufStream, offset, (uint32_t)avail);
+ if (NS_SUCCEEDED(rv))
+ offset += avail;
+ }
+
+ if (NS_FAILED(rv))
+ channel->Cancel(rv);
+
+ channel->GetStatus(&rv);
+ aConsumer->OnStopRequest(channel, nullptr, rv);
+
+ // Notify load observers
+ for (i = mObservers.Count() - 1; i >= 0; --i) {
+ // Make sure to hold a strong reference to the observer so
+ // that it doesn't go away in this call if it removes itself
+ // as an observer
+ nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i];
+
+ if (obs) {
+ if (NS_FAILED(rv))
+ obs->OnError(this, rv, nullptr);
+
+ obs->OnEndLoad(this);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::GetLoaded(bool* _result)
+{
+ *_result = (mLoadState == eLoadState_Loaded);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::Init(const char* uri)
+{
+ NS_PRECONDITION(mInner != nullptr, "not initialized");
+ if (! mInner)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+
+ rv = NS_NewURI(getter_AddRefs(mURL), nsDependentCString(uri));
+ if (NS_FAILED(rv)) return rv;
+
+ // XXX this is a hack: any "file:" URI is considered writable. All
+ // others are considered read-only.
+ if ((PL_strncmp(uri, kFileURIPrefix, sizeof(kFileURIPrefix) - 1) != 0) &&
+ (PL_strncmp(uri, kResourceURIPrefix, sizeof(kResourceURIPrefix) - 1) != 0)) {
+ mIsWritable = false;
+ }
+
+ rv = gRDFService->RegisterDataSource(this, false);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::GetURI(char* *aURI)
+{
+ *aURI = nullptr;
+ if (!mURL) {
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ nsresult rv = mURL->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aURI = ToNewCString(spec);
+ if (!*aURI) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::Assert(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget,
+ bool aTruthValue)
+{
+ // We don't accept assertions unless we're writable (except in the
+ // case that we're actually _reading_ the datasource in).
+ nsresult rv;
+
+ if (IsLoading()) {
+ bool hasAssertion = false;
+
+ nsCOMPtr<nsIRDFPurgeableDataSource> gcable = do_QueryInterface(mInner);
+ if (gcable) {
+ rv = gcable->Mark(aSource, aProperty, aTarget, aTruthValue, &hasAssertion);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = NS_RDF_ASSERTION_ACCEPTED;
+
+ if (! hasAssertion) {
+ rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue);
+
+ if (NS_SUCCEEDED(rv) && gcable) {
+ // Now mark the new assertion, so it doesn't get
+ // removed when we sweep. Ignore rv, because we want
+ // to return what mInner->Assert() gave us.
+ bool didMark;
+ (void) gcable->Mark(aSource, aProperty, aTarget, aTruthValue, &didMark);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return rv;
+ }
+ else if (mIsWritable) {
+ rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue);
+
+ if (rv == NS_RDF_ASSERTION_ACCEPTED)
+ mIsDirty = true;
+
+ return rv;
+ }
+ else {
+ return NS_RDF_ASSERTION_REJECTED;
+ }
+}
+
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::Unassert(nsIRDFResource* source,
+ nsIRDFResource* property,
+ nsIRDFNode* target)
+{
+ // We don't accept assertions unless we're writable (except in the
+ // case that we're actually _reading_ the datasource in).
+ nsresult rv;
+
+ if (IsLoading() || mIsWritable) {
+ rv = mInner->Unassert(source, property, target);
+ if (!IsLoading() && rv == NS_RDF_ASSERTION_ACCEPTED)
+ mIsDirty = true;
+ }
+ else {
+ rv = NS_RDF_ASSERTION_REJECTED;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::Change(nsIRDFResource* aSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aOldTarget,
+ nsIRDFNode* aNewTarget)
+{
+ nsresult rv;
+
+ if (IsLoading() || mIsWritable) {
+ rv = mInner->Change(aSource, aProperty, aOldTarget, aNewTarget);
+
+ if (!IsLoading() && rv == NS_RDF_ASSERTION_ACCEPTED)
+ mIsDirty = true;
+ }
+ else {
+ rv = NS_RDF_ASSERTION_REJECTED;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::Move(nsIRDFResource* aOldSource,
+ nsIRDFResource* aNewSource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aTarget)
+{
+ nsresult rv;
+
+ if (IsLoading() || mIsWritable) {
+ rv = mInner->Move(aOldSource, aNewSource, aProperty, aTarget);
+ if (!IsLoading() && rv == NS_RDF_ASSERTION_ACCEPTED)
+ mIsDirty = true;
+ }
+ else {
+ rv = NS_RDF_ASSERTION_REJECTED;
+ }
+
+ return rv;
+}
+
+
+nsresult
+RDFXMLDataSourceImpl::rdfXMLFlush(nsIURI *aURI)
+{
+
+ nsresult rv;
+
+ {
+ // Quick and dirty check to see if we're in XPCOM shutdown. If
+ // we are, we're screwed: it's too late to serialize because
+ // many of the services that we'll need to acquire to properly
+ // write the file will be unaquirable.
+ NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+ nsCOMPtr<nsIRDFService> dummy = do_GetService(kRDFServiceCID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to Flush() dirty datasource during XPCOM shutdown");
+ return rv;
+ }
+ }
+
+ // Is it a file? If so, we can write to it. Some day, it'd be nice
+ // if we didn't care what kind of stream this was...
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI);
+
+ if (fileURL) {
+ nsCOMPtr<nsIFile> file;
+ fileURL->GetFile(getter_AddRefs(file));
+ if (file) {
+ // get a safe output stream, so we don't clobber the datasource file unless
+ // all the writes succeeded.
+ nsCOMPtr<nsIOutputStream> out;
+ rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(out),
+ file,
+ PR_WRONLY | PR_CREATE_FILE,
+ /*octal*/ 0666,
+ 0);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIOutputStream> bufferedOut;
+ rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 4096);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = Serialize(bufferedOut);
+ if (NS_FAILED(rv)) return rv;
+
+ // All went ok. Maybe except for problems in Write(), but the stream detects
+ // that for us
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(bufferedOut, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = safeStream->Finish();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to save datasource file! possible dataloss");
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::FlushTo(const char *aURI)
+{
+ NS_PRECONDITION(aURI != nullptr, "not initialized");
+ if (!aURI)
+ return NS_ERROR_NULL_POINTER;
+
+ // XXX this is a hack: any "file:" URI is considered writable. All
+ // others are considered read-only.
+ if ((PL_strncmp(aURI, kFileURIPrefix, sizeof(kFileURIPrefix) - 1) != 0) &&
+ (PL_strncmp(aURI, kResourceURIPrefix, sizeof(kResourceURIPrefix) - 1) != 0))
+ {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = NS_NewURI(getter_AddRefs(url), aURI);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = rdfXMLFlush(url);
+ return rv;
+}
+
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::Flush(void)
+{
+ if (!mIsWritable || !mIsDirty)
+ return NS_OK;
+
+ // while it is not fatal if mURL is not set,
+ // indicate failure since we can't flush back to an unknown origin
+ if (! mURL)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfxml[%p] flush(%s)", this, mURL->GetSpecOrDefault().get()));
+ }
+
+ nsresult rv;
+ if (NS_SUCCEEDED(rv = rdfXMLFlush(mURL)))
+ {
+ mIsDirty = false;
+ }
+ return rv;
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsIRDFXMLDataSource methods
+//
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::GetReadOnly(bool* aIsReadOnly)
+{
+ *aIsReadOnly = !mIsWritable;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::SetReadOnly(bool aIsReadOnly)
+{
+ if (mIsWritable && aIsReadOnly)
+ mIsWritable = false;
+
+ return NS_OK;
+}
+
+// nsIChannelEventSink
+
+// This code is copied from nsSameOriginChecker::OnChannelRedirect. See
+// bug 475940 on providing this code in a shared location.
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback *cb)
+{
+ NS_PRECONDITION(aNewChannel, "Redirecting to null channel?");
+
+ nsresult rv;
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> oldPrincipal;
+ secMan->GetChannelResultPrincipal(aOldChannel, getter_AddRefs(oldPrincipal));
+
+ nsCOMPtr<nsIURI> newURI;
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+ nsCOMPtr<nsIURI> newOriginalURI;
+ aNewChannel->GetOriginalURI(getter_AddRefs(newOriginalURI));
+
+ NS_ENSURE_STATE(oldPrincipal && newURI && newOriginalURI);
+
+ rv = oldPrincipal->CheckMayLoad(newURI, false, false);
+ if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) {
+ rv = oldPrincipal->CheckMayLoad(newOriginalURI, false, false);
+ }
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ cb->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::Refresh(bool aBlocking)
+{
+ nsAutoCString spec;
+ if (mURL) {
+ spec = mURL->GetSpecOrDefault();
+ }
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfxml[%p] refresh(%s) %sblocking", this, spec.get(), (aBlocking ? "" : "non")));
+
+ // If an asynchronous load is already pending, then just let it do
+ // the honors.
+ if (IsLoading()) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfxml[%p] refresh(%s) a load was pending", this, spec.get()));
+
+ if (aBlocking) {
+ NS_WARNING("blocking load requested when async load pending");
+ return NS_ERROR_FAILURE;
+ }
+ else {
+ return NS_OK;
+ }
+ }
+
+ if (! mURL)
+ return NS_ERROR_FAILURE;
+ nsCOMPtr<nsIRDFXMLParser> parser = do_CreateInstance("@mozilla.org/rdf/xml-parser;1");
+ if (! parser)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = parser->ParseAsync(this, mURL, getter_AddRefs(mListener));
+ if (NS_FAILED(rv)) return rv;
+
+ if (aBlocking) {
+ rv = BlockingParse(mURL, this);
+
+ mListener = nullptr; // release the parser
+
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ // Null LoadGroup ?
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ mURL,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aLoadGroup
+ this); // aCallbacks
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = channel->AsyncOpen2(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // So we don't try to issue two asynchronous loads at once.
+ mLoadState = eLoadState_Pending;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::BeginLoad(void)
+{
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfxml[%p] begin-load(%s)", this,
+ mURL ? mURL->GetSpecOrDefault().get() : ""));
+ }
+
+ mLoadState = eLoadState_Loading;
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ // Make sure to hold a strong reference to the observer so
+ // that it doesn't go away in this call if it removes itself
+ // as an observer
+ nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i];
+
+ if (obs) {
+ obs->OnBeginLoad(this);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::Interrupt(void)
+{
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfxml[%p] interrupt(%s)", this,
+ mURL ? mURL->GetSpecOrDefault().get() : ""));
+ }
+
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ // Make sure to hold a strong reference to the observer so
+ // that it doesn't go away in this call if it removes itself
+ // as an observer
+ nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i];
+
+ if (obs) {
+ obs->OnInterrupt(this);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::Resume(void)
+{
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfxml[%p] resume(%s)", this,
+ mURL ? mURL->GetSpecOrDefault().get() : ""));
+ }
+
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ // Make sure to hold a strong reference to the observer so
+ // that it doesn't go away in this call if it removes itself
+ // as an observer
+ nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i];
+
+ if (obs) {
+ obs->OnResume(this);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::EndLoad(void)
+{
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("rdfxml[%p] end-load(%s)", this,
+ mURL ? mURL->GetSpecOrDefault().get() : ""));
+ }
+
+ mLoadState = eLoadState_Loaded;
+
+ // Clear out any unmarked assertions from the datasource.
+ nsCOMPtr<nsIRDFPurgeableDataSource> gcable = do_QueryInterface(mInner);
+ if (gcable) {
+ gcable->Sweep();
+ }
+
+ // Notify load observers
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ // Make sure to hold a strong reference to the observer so
+ // that it doesn't go away in this call if it removes itself
+ // as an observer
+ nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i];
+
+ if (obs) {
+ obs->OnEndLoad(this);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::AddNameSpace(nsIAtom* aPrefix, const nsString& aURI)
+{
+ mNameSpaces.Put(aURI, aPrefix);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::AddXMLSinkObserver(nsIRDFXMLSinkObserver* aObserver)
+{
+ if (! aObserver)
+ return NS_ERROR_NULL_POINTER;
+
+ mObservers.AppendObject(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::RemoveXMLSinkObserver(nsIRDFXMLSinkObserver* aObserver)
+{
+ if (! aObserver)
+ return NS_ERROR_NULL_POINTER;
+
+ mObservers.RemoveObject(aObserver);
+
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------
+//
+// nsIRequestObserver
+//
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
+{
+ return mListener->OnStartRequest(request, ctxt);
+}
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::OnStopRequest(nsIRequest *request,
+ nsISupports *ctxt,
+ nsresult status)
+{
+ if (NS_FAILED(status)) {
+ for (int32_t i = mObservers.Count() - 1; i >= 0; --i) {
+ // Make sure to hold a strong reference to the observer so
+ // that it doesn't go away in this call if it removes
+ // itself as an observer
+ nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i];
+
+ if (obs) {
+ obs->OnError(this, status, nullptr);
+ }
+ }
+ }
+
+ nsresult rv;
+ rv = mListener->OnStopRequest(request, ctxt, status);
+
+ mListener = nullptr; // release the parser
+
+ return rv;
+}
+
+//----------------------------------------------------------------------
+//
+// nsIStreamListener
+//
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::OnDataAvailable(nsIRequest *request,
+ nsISupports *ctxt,
+ nsIInputStream *inStr,
+ uint64_t sourceOffset,
+ uint32_t count)
+{
+ return mListener->OnDataAvailable(request, ctxt, inStr, sourceOffset, count);
+}
+
+//----------------------------------------------------------------------
+//
+// nsIRDFXMLSource
+//
+
+NS_IMETHODIMP
+RDFXMLDataSourceImpl::Serialize(nsIOutputStream* aStream)
+{
+ nsresult rv;
+ nsCOMPtr<nsIRDFXMLSerializer> serializer
+ = do_CreateInstance("@mozilla.org/rdf/xml-serializer;1", &rv);
+
+ if (! serializer)
+ return rv;
+
+ rv = serializer->Init(this);
+ if (NS_FAILED(rv)) return rv;
+
+ // Add any namespace information that we picked up when reading
+ // the RDF/XML
+ nsNameSpaceMap::const_iterator last = mNameSpaces.last();
+ for (nsNameSpaceMap::const_iterator iter = mNameSpaces.first();
+ iter != last; ++iter) {
+ // We might wanna change nsIRDFXMLSerializer to nsACString and
+ // use a heap allocated buffer here in the future.
+ NS_ConvertUTF8toUTF16 uri(iter->mURI);
+ serializer->AddNameSpace(iter->mPrefix, uri);
+ }
+
+ // Serialize!
+ nsCOMPtr<nsIRDFXMLSource> source = do_QueryInterface(serializer);
+ if (! source)
+ return NS_ERROR_FAILURE;
+
+ return source->Serialize(aStream);
+}
diff --git a/components/rdf/src/nsRDFXMLParser.cpp b/components/rdf/src/nsRDFXMLParser.cpp
new file mode 100644
index 000000000..67de90820
--- /dev/null
+++ b/components/rdf/src/nsRDFXMLParser.cpp
@@ -0,0 +1,137 @@
+/* -*- 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 "nsRDFXMLParser.h"
+
+#include "nsIComponentManager.h"
+#include "nsIParser.h"
+#include "nsCharsetSource.h"
+#include "nsIRDFContentSink.h"
+#include "nsParserCIID.h"
+#include "nsStringStream.h"
+#include "nsNetUtil.h"
+#include "nsNullPrincipal.h"
+
+static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);
+
+NS_IMPL_ISUPPORTS(nsRDFXMLParser, nsIRDFXMLParser)
+
+nsresult
+nsRDFXMLParser::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsRDFXMLParser* result = new nsRDFXMLParser();
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv;
+ NS_ADDREF(result);
+ rv = result->QueryInterface(aIID, aResult);
+ NS_RELEASE(result);
+ return rv;
+}
+
+nsRDFXMLParser::nsRDFXMLParser()
+{
+ MOZ_COUNT_CTOR(nsRDFXMLParser);
+}
+
+nsRDFXMLParser::~nsRDFXMLParser()
+{
+ MOZ_COUNT_DTOR(nsRDFXMLParser);
+}
+
+NS_IMETHODIMP
+nsRDFXMLParser::ParseAsync(nsIRDFDataSource* aSink, nsIURI* aBaseURI, nsIStreamListener** aResult)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIRDFContentSink> sink =
+ do_CreateInstance("@mozilla.org/rdf/content-sink;1", &rv);
+
+ if (NS_FAILED(rv)) return rv;
+
+ rv = sink->Init(aBaseURI);
+ if (NS_FAILED(rv)) return rv;
+
+ // We set the content sink's data source directly to our in-memory
+ // store. This allows the initial content to be generated "directly".
+ rv = sink->SetDataSource(aSink);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ parser->SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"),
+ kCharsetFromDocTypeDefault);
+ parser->SetContentSink(sink);
+
+ rv = parser->Parse(aBaseURI);
+ if (NS_FAILED(rv)) return rv;
+
+ return CallQueryInterface(parser, aResult);
+}
+
+NS_IMETHODIMP
+nsRDFXMLParser::ParseString(nsIRDFDataSource* aSink, nsIURI* aBaseURI, const nsACString& aString)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIRDFContentSink> sink =
+ do_CreateInstance("@mozilla.org/rdf/content-sink;1", &rv);
+
+ if (NS_FAILED(rv)) return rv;
+
+ rv = sink->Init(aBaseURI);
+ if (NS_FAILED(rv)) return rv;
+
+ // We set the content sink's data source directly to our in-memory
+ // store. This allows the initial content to be generated "directly".
+ rv = sink->SetDataSource(aSink);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ parser->SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"),
+ kCharsetFromOtherComponent);
+ parser->SetContentSink(sink);
+
+ rv = parser->Parse(aBaseURI);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamListener> listener =
+ do_QueryInterface(parser);
+
+ if (! listener)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(stream), aString);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
+
+ // The following channel is never openend, so it does not matter what
+ // securityFlags we pass; let's follow the principle of least privilege.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
+ aBaseURI,
+ stream,
+ nullPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_OTHER,
+ NS_LITERAL_CSTRING("text/xml"));
+ if (NS_FAILED(rv)) return rv;
+
+ listener->OnStartRequest(channel, nullptr);
+ listener->OnDataAvailable(channel, nullptr, stream, 0, aString.Length());
+ listener->OnStopRequest(channel, nullptr, NS_OK);
+
+ return NS_OK;
+}
diff --git a/components/rdf/src/nsRDFXMLParser.h b/components/rdf/src/nsRDFXMLParser.h
new file mode 100644
index 000000000..d35c873fb
--- /dev/null
+++ b/components/rdf/src/nsRDFXMLParser.h
@@ -0,0 +1,31 @@
+/* -*- 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/. */
+
+#ifndef nsRDFParser_h__
+#define nsRDFParser_h__
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIRDFXMLParser.h"
+#include "nsIRDFDataSource.h"
+
+/**
+ * A helper class that is used to parse RDF/XML.
+ */
+class nsRDFXMLParser : public nsIRDFXMLParser {
+public:
+ static nsresult
+ Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRDFXMLPARSER
+
+protected:
+ nsRDFXMLParser();
+ virtual ~nsRDFXMLParser();
+};
+
+#endif // nsRDFParser_h__
diff --git a/components/rdf/src/nsRDFXMLSerializer.cpp b/components/rdf/src/nsRDFXMLSerializer.cpp
new file mode 100644
index 000000000..3b9018068
--- /dev/null
+++ b/components/rdf/src/nsRDFXMLSerializer.cpp
@@ -0,0 +1,1126 @@
+/* -*- 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 "nsRDFXMLSerializer.h"
+
+#include "nsIAtom.h"
+#include "nsIOutputStream.h"
+#include "nsIRDFService.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIServiceManager.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsTArray.h"
+#include "rdf.h"
+#include "rdfutil.h"
+#include "mozilla/Attributes.h"
+
+#include "rdfIDataSource.h"
+
+int32_t nsRDFXMLSerializer::gRefCnt = 0;
+nsIRDFContainerUtils* nsRDFXMLSerializer::gRDFC;
+nsIRDFResource* nsRDFXMLSerializer::kRDF_instanceOf;
+nsIRDFResource* nsRDFXMLSerializer::kRDF_type;
+nsIRDFResource* nsRDFXMLSerializer::kRDF_nextVal;
+nsIRDFResource* nsRDFXMLSerializer::kRDF_Bag;
+nsIRDFResource* nsRDFXMLSerializer::kRDF_Seq;
+nsIRDFResource* nsRDFXMLSerializer::kRDF_Alt;
+
+static const char kRDFDescriptionOpen[] = " <RDF:Description";
+static const char kIDAttr[] = " RDF:ID=\"";
+static const char kAboutAttr[] = " RDF:about=\"";
+static const char kRDFDescriptionClose[] = " </RDF:Description>\n";
+static const char kRDFResource1[] = " RDF:resource=\"";
+static const char kRDFResource2[] = "\"/>\n";
+static const char kRDFParseTypeInteger[] = " NC:parseType=\"Integer\">";
+static const char kRDFParseTypeDate[] = " NC:parseType=\"Date\">";
+static const char kRDFUnknown[] = "><!-- unknown node type -->";
+
+nsresult
+nsRDFXMLSerializer::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+ if (aOuter)
+ return NS_ERROR_NO_AGGREGATION;
+
+ nsCOMPtr<nsIRDFXMLSerializer> result = new nsRDFXMLSerializer();
+ if (! result)
+ return NS_ERROR_OUT_OF_MEMORY;
+ // The serializer object is here, addref gRefCnt so that the
+ // destructor can safely release it.
+ gRefCnt++;
+
+ nsresult rv;
+ rv = result->QueryInterface(aIID, aResult);
+
+ if (NS_FAILED(rv)) return rv;
+
+ if (gRefCnt == 1) do {
+ nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ if (NS_FAILED(rv)) break;
+
+ rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"),
+ &kRDF_instanceOf);
+ if (NS_FAILED(rv)) break;
+
+ rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"),
+ &kRDF_type);
+ if (NS_FAILED(rv)) break;
+
+ rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"),
+ &kRDF_nextVal);
+ if (NS_FAILED(rv)) break;
+
+ rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"),
+ &kRDF_Bag);
+ if (NS_FAILED(rv)) break;
+
+ rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"),
+ &kRDF_Seq);
+ if (NS_FAILED(rv)) break;
+
+ rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"),
+ &kRDF_Alt);
+ if (NS_FAILED(rv)) break;
+
+ rv = CallGetService("@mozilla.org/rdf/container-utils;1", &gRDFC);
+ if (NS_FAILED(rv)) break;
+ } while (0);
+
+ return rv;
+}
+
+nsRDFXMLSerializer::nsRDFXMLSerializer()
+{
+ MOZ_COUNT_CTOR(nsRDFXMLSerializer);
+}
+
+nsRDFXMLSerializer::~nsRDFXMLSerializer()
+{
+ MOZ_COUNT_DTOR(nsRDFXMLSerializer);
+
+ if (--gRefCnt == 0) {
+ NS_IF_RELEASE(kRDF_Bag);
+ NS_IF_RELEASE(kRDF_Seq);
+ NS_IF_RELEASE(kRDF_Alt);
+ NS_IF_RELEASE(kRDF_instanceOf);
+ NS_IF_RELEASE(kRDF_type);
+ NS_IF_RELEASE(kRDF_nextVal);
+ NS_IF_RELEASE(gRDFC);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsRDFXMLSerializer, nsIRDFXMLSerializer, nsIRDFXMLSource)
+
+NS_IMETHODIMP
+nsRDFXMLSerializer::Init(nsIRDFDataSource* aDataSource)
+{
+ if (! aDataSource)
+ return NS_ERROR_NULL_POINTER;
+
+ mDataSource = aDataSource;
+ mDataSource->GetURI(getter_Copies(mBaseURLSpec));
+
+ // Add the ``RDF'' prefix, by default.
+ nsCOMPtr<nsIAtom> prefix;
+
+ prefix = NS_Atomize("RDF");
+ AddNameSpace(prefix, NS_LITERAL_STRING("http://www.w3.org/1999/02/22-rdf-syntax-ns#"));
+
+ prefix = NS_Atomize("NC");
+ AddNameSpace(prefix, NS_LITERAL_STRING("http://home.netscape.com/NC-rdf#"));
+
+ mPrefixID = 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRDFXMLSerializer::AddNameSpace(nsIAtom* aPrefix, const nsAString& aURI)
+{
+ nsCOMPtr<nsIAtom> prefix = aPrefix;
+ if (!prefix) {
+ // Make up a prefix, we don't want default namespaces, so
+ // that we can use QNames for elements and attributes alike.
+ prefix = EnsureNewPrefix();
+ }
+ mNameSpaces.Put(aURI, prefix);
+ return NS_OK;
+}
+
+static nsresult
+rdf_BlockingWrite(nsIOutputStream* stream, const char* buf, uint32_t size)
+{
+ uint32_t written = 0;
+ uint32_t remaining = size;
+ while (remaining > 0) {
+ nsresult rv;
+ uint32_t cb;
+
+ if (NS_FAILED(rv = stream->Write(buf + written, remaining, &cb)))
+ return rv;
+
+ written += cb;
+ remaining -= cb;
+ }
+ return NS_OK;
+}
+
+static nsresult
+rdf_BlockingWrite(nsIOutputStream* stream, const nsCSubstring& s)
+{
+ return rdf_BlockingWrite(stream, s.BeginReading(), s.Length());
+}
+
+static nsresult
+rdf_BlockingWrite(nsIOutputStream* stream, const nsAString& s)
+{
+ NS_ConvertUTF16toUTF8 utf8(s);
+ return rdf_BlockingWrite(stream, utf8.get(), utf8.Length());
+}
+
+already_AddRefed<nsIAtom>
+nsRDFXMLSerializer::EnsureNewPrefix()
+{
+ nsAutoString qname;
+ nsCOMPtr<nsIAtom> prefix;
+ bool isNewPrefix;
+ do {
+ isNewPrefix = true;
+ qname.AssignLiteral("NS");
+ qname.AppendInt(++mPrefixID, 10);
+ prefix = NS_Atomize(qname);
+ nsNameSpaceMap::const_iterator iter = mNameSpaces.first();
+ while (iter != mNameSpaces.last() && isNewPrefix) {
+ isNewPrefix = (iter->mPrefix != prefix);
+ ++iter;
+ }
+ } while (!isNewPrefix);
+ return prefix.forget();
+}
+
+// This converts a property resource (like
+// "http://www.w3.org/TR/WD-rdf-syntax#Description") into a QName
+// ("RDF:Description"), and registers the namespace, if it's made up.
+
+nsresult
+nsRDFXMLSerializer::RegisterQName(nsIRDFResource* aResource)
+{
+ nsAutoCString uri, qname;
+ aResource->GetValueUTF8(uri);
+
+ nsNameSpaceMap::const_iterator iter = mNameSpaces.GetNameSpaceOf(uri);
+ if (iter != mNameSpaces.last()) {
+ NS_ENSURE_TRUE(iter->mPrefix, NS_ERROR_UNEXPECTED);
+ iter->mPrefix->ToUTF8String(qname);
+ qname.Append(':');
+ qname += StringTail(uri, uri.Length() - iter->mURI.Length());
+ mQNames.Put(aResource, qname);
+ return NS_OK;
+ }
+
+ // Okay, so we don't have it in our map. Try to make one up. This
+ // is very bogus.
+ int32_t i = uri.RFindChar('#'); // first try a '#'
+ if (i == -1) {
+ i = uri.RFindChar('/');
+ if (i == -1) {
+ // Okay, just punt and assume there is _no_ namespace on
+ // this thing...
+ mQNames.Put(aResource, uri);
+ return NS_OK;
+ }
+ }
+
+ // Take whatever is to the right of the '#' or '/' and call it the
+ // local name, make up a prefix.
+ nsCOMPtr<nsIAtom> prefix = EnsureNewPrefix();
+ mNameSpaces.Put(StringHead(uri, i+1), prefix);
+ prefix->ToUTF8String(qname);
+ qname.Append(':');
+ qname += StringTail(uri, uri.Length() - (i + 1));
+
+ mQNames.Put(aResource, qname);
+ return NS_OK;
+}
+
+nsresult
+nsRDFXMLSerializer::GetQName(nsIRDFResource* aResource, nsCString& aQName)
+{
+ return mQNames.Get(aResource, &aQName) ? NS_OK : NS_ERROR_UNEXPECTED;
+}
+
+bool
+nsRDFXMLSerializer::IsContainerProperty(nsIRDFResource* aProperty)
+{
+ // Return `true' if the property is an internal property related
+ // to being a container.
+ if (aProperty == kRDF_instanceOf)
+ return true;
+
+ if (aProperty == kRDF_nextVal)
+ return true;
+
+ bool isOrdinal = false;
+ gRDFC->IsOrdinalProperty(aProperty, &isOrdinal);
+ if (isOrdinal)
+ return true;
+
+ return false;
+}
+
+
+// convert '&', '<', and '>' into "&amp;", "&lt;", and "&gt", respectively.
+static const char amp[] = "&amp;";
+static const char lt[] = "&lt;";
+static const char gt[] = "&gt;";
+static const char quot[] = "&quot;";
+
+static void
+rdf_EscapeAmpersandsAndAngleBrackets(nsCString& s)
+{
+ uint32_t newLength, origLength;
+ newLength = origLength = s.Length();
+
+ // Compute the length of the result string.
+ const char* start = s.BeginReading();
+ const char* end = s.EndReading();
+ const char* c = start;
+ while (c != end) {
+ switch (*c) {
+ case '&' :
+ newLength += sizeof(amp) - 2;
+ break;
+ case '<':
+ case '>':
+ newLength += sizeof(gt) - 2;
+ break;
+ default:
+ break;
+ }
+ ++c;
+ }
+ if (newLength == origLength) {
+ // nothing to escape
+ return;
+ }
+
+ // escape the chars from the end back to the front.
+ s.SetLength(newLength);
+
+ // Buffer might have changed, get the pointers again
+ start = s.BeginReading(); // begin of string
+ c = start + origLength - 1; // last char in original string
+ char* w = s.EndWriting() - 1; // last char in grown buffer
+ while (c >= start) {
+ switch (*c) {
+ case '&' :
+ w -= 4;
+ nsCharTraits<char>::copy(w, amp, sizeof(amp) - 1);
+ break;
+ case '<':
+ w -= 3;
+ nsCharTraits<char>::copy(w, lt, sizeof(lt) - 1);
+ break;
+ case '>':
+ w -= 3;
+ nsCharTraits<char>::copy(w, gt, sizeof(gt) - 1);
+ break;
+ default:
+ *w = *c;
+ }
+ --w;
+ --c;
+ }
+}
+
+// convert '"' to "&quot;"
+static void
+rdf_EscapeQuotes(nsCString& s)
+{
+ int32_t i = 0;
+ while ((i = s.FindChar('"', i)) != -1) {
+ s.Replace(i, 1, quot, sizeof(quot) - 1);
+ i += sizeof(quot) - 2;
+ }
+}
+
+static void
+rdf_EscapeAttributeValue(nsCString& s)
+{
+ rdf_EscapeAmpersandsAndAngleBrackets(s);
+ rdf_EscapeQuotes(s);
+}
+
+
+nsresult
+nsRDFXMLSerializer::SerializeInlineAssertion(nsIOutputStream* aStream,
+ nsIRDFResource* aResource,
+ nsIRDFResource* aProperty,
+ nsIRDFLiteral* aValue)
+{
+ nsresult rv;
+ nsCString qname;
+ rv = GetQName(aProperty, qname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = rdf_BlockingWrite(aStream,
+ NS_LITERAL_CSTRING("\n "));
+ if (NS_FAILED(rv)) return rv;
+
+ const char16_t* value;
+ aValue->GetValueConst(&value);
+ NS_ConvertUTF16toUTF8 s(value);
+
+ rdf_EscapeAttributeValue(s);
+
+ rv = rdf_BlockingWrite(aStream, qname);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, "=\"", 2);
+ if (NS_FAILED(rv)) return rv;
+ s.Append('"');
+ return rdf_BlockingWrite(aStream, s);
+}
+
+nsresult
+nsRDFXMLSerializer::SerializeChildAssertion(nsIOutputStream* aStream,
+ nsIRDFResource* aResource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aValue)
+{
+ nsCString qname;
+ nsresult rv = GetQName(aProperty, qname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = rdf_BlockingWrite(aStream, " <", 5);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, qname);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFResource> resource;
+ nsCOMPtr<nsIRDFLiteral> literal;
+ nsCOMPtr<nsIRDFInt> number;
+ nsCOMPtr<nsIRDFDate> date;
+
+ if ((resource = do_QueryInterface(aValue)) != nullptr) {
+ nsAutoCString uri;
+ resource->GetValueUTF8(uri);
+
+ rdf_MakeRelativeRef(mBaseURLSpec, uri);
+ rdf_EscapeAttributeValue(uri);
+
+ rv = rdf_BlockingWrite(aStream, kRDFResource1,
+ sizeof(kRDFResource1) - 1);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, uri);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, kRDFResource2,
+ sizeof(kRDFResource2) - 1);
+ if (NS_FAILED(rv)) return rv;
+
+ goto no_close_tag;
+ }
+ else if ((literal = do_QueryInterface(aValue)) != nullptr) {
+ const char16_t *value;
+ literal->GetValueConst(&value);
+ NS_ConvertUTF16toUTF8 s(value);
+
+ rdf_EscapeAmpersandsAndAngleBrackets(s);
+
+ rv = rdf_BlockingWrite(aStream, ">", 1);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, s);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else if ((number = do_QueryInterface(aValue)) != nullptr) {
+ int32_t value;
+ number->GetValue(&value);
+
+ nsAutoCString n;
+ n.AppendInt(value);
+
+ rv = rdf_BlockingWrite(aStream, kRDFParseTypeInteger,
+ sizeof(kRDFParseTypeInteger) - 1);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, n);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else if ((date = do_QueryInterface(aValue)) != nullptr) {
+ PRTime value;
+ date->GetValue(&value);
+
+ nsAutoCString s;
+ rdf_FormatDate(value, s);
+
+ rv = rdf_BlockingWrite(aStream, kRDFParseTypeDate,
+ sizeof(kRDFParseTypeDate) - 1);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, s);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ // XXX it doesn't support nsIRDFResource _or_ nsIRDFLiteral???
+ // We should serialize nsIRDFInt, nsIRDFDate, etc...
+ NS_WARNING("unknown RDF node type");
+
+ rv = rdf_BlockingWrite(aStream, kRDFUnknown, sizeof(kRDFUnknown) - 1);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = rdf_BlockingWrite(aStream, "</", 2);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, qname);
+ if (NS_FAILED(rv)) return rv;
+ return rdf_BlockingWrite(aStream, ">\n", 2);
+
+ no_close_tag:
+ return NS_OK;
+}
+
+nsresult
+nsRDFXMLSerializer::SerializeProperty(nsIOutputStream* aStream,
+ nsIRDFResource* aResource,
+ nsIRDFResource* aProperty,
+ bool aInline,
+ int32_t* aSkipped)
+{
+ nsresult rv = NS_OK;
+
+ int32_t skipped = 0;
+
+ nsCOMPtr<nsISimpleEnumerator> assertions;
+ mDataSource->GetTargets(aResource, aProperty, true, getter_AddRefs(assertions));
+ if (! assertions)
+ return NS_ERROR_FAILURE;
+
+ // Serializing the assertion inline is ok as long as the property has
+ // only one target value, and it is a literal that doesn't include line
+ // breaks.
+ bool needsChild = false;
+
+ while (1) {
+ bool hasMore = false;
+ assertions->HasMoreElements(&hasMore);
+ if (! hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ assertions->GetNext(getter_AddRefs(isupports));
+ nsCOMPtr<nsIRDFLiteral> literal = do_QueryInterface(isupports);
+ needsChild |= (!literal);
+
+ if (!needsChild) {
+ assertions->HasMoreElements(&needsChild);
+ if (!needsChild) {
+ const char16_t* literalVal = nullptr;
+ literal->GetValueConst(&literalVal);
+ if (literalVal) {
+ for (; *literalVal; literalVal++) {
+ if (*literalVal == char16_t('\n') ||
+ *literalVal == char16_t('\r')) {
+ needsChild = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (aInline && !needsChild) {
+ rv = SerializeInlineAssertion(aStream, aResource, aProperty, literal);
+ }
+ else if (!aInline && needsChild) {
+ nsCOMPtr<nsIRDFNode> value = do_QueryInterface(isupports);
+ rv = SerializeChildAssertion(aStream, aResource, aProperty, value);
+ }
+ else {
+ ++skipped;
+ rv = NS_OK;
+ }
+
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ *aSkipped += skipped;
+ return rv;
+}
+
+
+nsresult
+nsRDFXMLSerializer::SerializeDescription(nsIOutputStream* aStream,
+ nsIRDFResource* aResource)
+{
+ nsresult rv;
+
+ bool isTypedNode = false;
+ nsCString typeQName;
+
+ nsCOMPtr<nsIRDFNode> typeNode;
+ mDataSource->GetTarget(aResource, kRDF_type, true, getter_AddRefs(typeNode));
+ if (typeNode) {
+ nsCOMPtr<nsIRDFResource> type = do_QueryInterface(typeNode, &rv);
+ if (type) {
+ // Try to get a namespace prefix. If none is available,
+ // just treat the description as if it weren't a typed node
+ // after all and emit rdf:type as a normal property. This
+ // seems preferable to using a bogus (invented) prefix.
+ isTypedNode = NS_SUCCEEDED(GetQName(type, typeQName));
+ }
+ }
+
+ nsAutoCString uri;
+ rv = aResource->GetValueUTF8(uri);
+ if (NS_FAILED(rv)) return rv;
+
+ rdf_MakeRelativeRef(mBaseURLSpec, uri);
+ rdf_EscapeAttributeValue(uri);
+
+ // Emit an open tag and the subject
+ if (isTypedNode) {
+ rv = rdf_BlockingWrite(aStream, NS_LITERAL_STRING(" <"));
+ if (NS_FAILED(rv)) return rv;
+ // Watch out for the default namespace!
+ rv = rdf_BlockingWrite(aStream, typeQName);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ rv = rdf_BlockingWrite(aStream, kRDFDescriptionOpen,
+ sizeof(kRDFDescriptionOpen) - 1);
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (uri[0] == char16_t('#')) {
+ uri.Cut(0, 1);
+ rv = rdf_BlockingWrite(aStream, kIDAttr, sizeof(kIDAttr) - 1);
+ }
+ else {
+ rv = rdf_BlockingWrite(aStream, kAboutAttr, sizeof(kAboutAttr) - 1);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ uri.Append('"');
+ rv = rdf_BlockingWrite(aStream, uri);
+ if (NS_FAILED(rv)) return rv;
+
+ // Any value that's a literal we can write out as an inline
+ // attribute on the RDF:Description
+ AutoTArray<nsIRDFResource*, 8> visited;
+ int32_t skipped = 0;
+
+ nsCOMPtr<nsISimpleEnumerator> arcs;
+ mDataSource->ArcLabelsOut(aResource, getter_AddRefs(arcs));
+
+ if (arcs) {
+ // Don't re-serialize rdf:type later on
+ if (isTypedNode)
+ visited.AppendElement(kRDF_type);
+
+ while (1) {
+ bool hasMore = false;
+ arcs->HasMoreElements(&hasMore);
+ if (! hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ arcs->GetNext(getter_AddRefs(isupports));
+
+ nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports);
+ if (! property)
+ continue;
+
+ // Ignore properties that pertain to containers; we may be
+ // called from SerializeContainer() if the container resource
+ // has been assigned non-container properties.
+ if (IsContainerProperty(property))
+ continue;
+
+ // Only serialize values for the property once.
+ if (visited.Contains(property.get()))
+ continue;
+
+ visited.AppendElement(property.get());
+
+ SerializeProperty(aStream, aResource, property, true, &skipped);
+ }
+ }
+
+ if (skipped) {
+ // Close the RDF:Description tag.
+ rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(">\n"));
+ if (NS_FAILED(rv)) return rv;
+
+ // Now write out resources (which might have their own
+ // substructure) as children.
+ mDataSource->ArcLabelsOut(aResource, getter_AddRefs(arcs));
+
+ if (arcs) {
+ // Forget that we've visited anything
+ visited.Clear();
+ // ... except for rdf:type
+ if (isTypedNode)
+ visited.AppendElement(kRDF_type);
+
+ while (1) {
+ bool hasMore = false;
+ arcs->HasMoreElements(&hasMore);
+ if (! hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ arcs->GetNext(getter_AddRefs(isupports));
+
+ nsCOMPtr<nsIRDFResource> property = do_QueryInterface(isupports);
+ if (! property)
+ continue;
+
+ // Ignore properties that pertain to containers; we may be
+ // called from SerializeContainer() if the container
+ // resource has been assigned non-container properties.
+ if (IsContainerProperty(property))
+ continue;
+
+ // have we already seen this property? If so, don't write it
+ // out again; serialize property will write each instance.
+ if (visited.Contains(property.get()))
+ continue;
+
+ visited.AppendElement(property.get());
+
+ SerializeProperty(aStream, aResource, property, false, &skipped);
+ }
+ }
+
+ // Emit a proper close-tag.
+ if (isTypedNode) {
+ rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(" </"));
+ if (NS_FAILED(rv)) return rv;
+ // Watch out for the default namespace!
+ rdf_BlockingWrite(aStream, typeQName);
+ if (NS_FAILED(rv)) return rv;
+ rdf_BlockingWrite(aStream, ">\n", 2);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ rv = rdf_BlockingWrite(aStream, kRDFDescriptionClose,
+ sizeof(kRDFDescriptionClose) - 1);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ else {
+ // If we saw _no_ child properties, then we can don't need a
+ // close-tag.
+ rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(" />\n"));
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsRDFXMLSerializer::SerializeMember(nsIOutputStream* aStream,
+ nsIRDFResource* aContainer,
+ nsIRDFNode* aMember)
+{
+ // If it's a resource, then output a "<RDF:li RDF:resource=... />"
+ // tag, because we'll be dumping the resource separately. (We
+ // iterate thru all the resources in the datasource,
+ // remember?) Otherwise, output the literal value.
+
+ nsCOMPtr<nsIRDFResource> resource;
+ nsCOMPtr<nsIRDFLiteral> literal;
+ nsCOMPtr<nsIRDFInt> number;
+ nsCOMPtr<nsIRDFDate> date;
+
+static const char kRDFLIOpen[] = " <RDF:li";
+ nsresult rv = rdf_BlockingWrite(aStream, kRDFLIOpen,
+ sizeof(kRDFLIOpen) - 1);
+ if (NS_FAILED(rv)) return rv;
+
+ if ((resource = do_QueryInterface(aMember)) != nullptr) {
+ nsAutoCString uri;
+ resource->GetValueUTF8(uri);
+
+ rdf_MakeRelativeRef(mBaseURLSpec, uri);
+ rdf_EscapeAttributeValue(uri);
+
+ rv = rdf_BlockingWrite(aStream, kRDFResource1,
+ sizeof(kRDFResource1) - 1);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, uri);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, kRDFResource2,
+ sizeof(kRDFResource2) - 1);
+ if (NS_FAILED(rv)) return rv;
+
+ goto no_close_tag;
+ }
+ else if ((literal = do_QueryInterface(aMember)) != nullptr) {
+ const char16_t *value;
+ literal->GetValueConst(&value);
+static const char kRDFLIOpenGT[] = ">";
+ // close the '<RDF:LI' before adding the literal
+ rv = rdf_BlockingWrite(aStream, kRDFLIOpenGT,
+ sizeof(kRDFLIOpenGT) - 1);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ConvertUTF16toUTF8 s(value);
+ rdf_EscapeAmpersandsAndAngleBrackets(s);
+
+ rv = rdf_BlockingWrite(aStream, s);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else if ((number = do_QueryInterface(aMember)) != nullptr) {
+ int32_t value;
+ number->GetValue(&value);
+
+ nsAutoCString n;
+ n.AppendInt(value);
+
+ rv = rdf_BlockingWrite(aStream, kRDFParseTypeInteger,
+ sizeof(kRDFParseTypeInteger) - 1);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, n);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else if ((date = do_QueryInterface(aMember)) != nullptr) {
+ PRTime value;
+ date->GetValue(&value);
+
+ nsAutoCString s;
+ rdf_FormatDate(value, s);
+
+ rv = rdf_BlockingWrite(aStream, kRDFParseTypeDate,
+ sizeof(kRDFParseTypeDate) - 1);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, s);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ // XXX it doesn't support nsIRDFResource _or_ nsIRDFLiteral???
+ // We should serialize nsIRDFInt, nsIRDFDate, etc...
+ NS_WARNING("unknown RDF node type");
+
+ rv = rdf_BlockingWrite(aStream, kRDFUnknown, sizeof(kRDFUnknown) - 1);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ {
+static const char kRDFLIClose[] = "</RDF:li>\n";
+ rv = rdf_BlockingWrite(aStream, kRDFLIClose, sizeof(kRDFLIClose) - 1);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ no_close_tag:
+ return NS_OK;
+}
+
+
+nsresult
+nsRDFXMLSerializer::SerializeContainer(nsIOutputStream* aStream,
+ nsIRDFResource* aContainer)
+{
+ nsresult rv;
+ nsAutoCString tag;
+
+ // Decide if it's a sequence, bag, or alternation, and print the
+ // appropriate tag-open sequence
+
+ if (IsA(mDataSource, aContainer, kRDF_Bag)) {
+ tag.AssignLiteral("RDF:Bag");
+ }
+ else if (IsA(mDataSource, aContainer, kRDF_Seq)) {
+ tag.AssignLiteral("RDF:Seq");
+ }
+ else if (IsA(mDataSource, aContainer, kRDF_Alt)) {
+ tag.AssignLiteral("RDF:Alt");
+ }
+ else {
+ NS_ASSERTION(false, "huh? this is _not_ a container.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = rdf_BlockingWrite(aStream, " <", 3);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, tag);
+ if (NS_FAILED(rv)) return rv;
+
+
+ // Unfortunately, we always need to print out the identity of the
+ // resource, even if was constructed "anonymously". We need to do
+ // this because we never really know who else might be referring
+ // to it...
+
+ nsAutoCString uri;
+ if (NS_SUCCEEDED(aContainer->GetValueUTF8(uri))) {
+ rdf_MakeRelativeRef(mBaseURLSpec, uri);
+
+ rdf_EscapeAttributeValue(uri);
+
+ if (uri.First() == '#') {
+ // Okay, it's actually identified as an element in the
+ // current document, not trying to decorate some absolute
+ // URI. We can use the 'ID=' attribute...
+
+ uri.Cut(0, 1); // chop the '#'
+ rv = rdf_BlockingWrite(aStream, kIDAttr, sizeof(kIDAttr) - 1);
+ if (NS_FAILED(rv)) return rv;
+ }
+ else {
+ // We need to cheat and spit out an illegal 'about=' on
+ // the sequence.
+ rv = rdf_BlockingWrite(aStream, kAboutAttr,
+ sizeof(kAboutAttr) - 1);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = rdf_BlockingWrite(aStream, uri);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, "\"", 1);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = rdf_BlockingWrite(aStream, ">\n", 2);
+ if (NS_FAILED(rv)) return rv;
+
+ // First iterate through each of the ordinal elements (the RDF/XML
+ // syntax doesn't allow us to place properties on RDF container
+ // elements).
+ nsCOMPtr<nsISimpleEnumerator> elements;
+ rv = NS_NewContainerEnumerator(mDataSource, aContainer, getter_AddRefs(elements));
+
+ if (NS_SUCCEEDED(rv)) {
+ while (1) {
+ bool hasMore;
+ rv = elements->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) break;
+
+ if (! hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ elements->GetNext(getter_AddRefs(isupports));
+
+ nsCOMPtr<nsIRDFNode> element = do_QueryInterface(isupports);
+ NS_ASSERTION(element != nullptr, "not an nsIRDFNode");
+ if (! element)
+ continue;
+
+ SerializeMember(aStream, aContainer, element);
+ }
+ }
+
+ // close the container tag
+ rv = rdf_BlockingWrite(aStream, " </", 4);
+ if (NS_FAILED(rv)) return rv;
+ tag.Append(">\n", 2);
+ rv = rdf_BlockingWrite(aStream, tag);
+ if (NS_FAILED(rv)) return rv;
+
+ // Now, we iterate through _all_ of the arcs, in case someone has
+ // applied properties to the bag itself. These'll be placed in a
+ // separate RDF:Description element.
+ nsCOMPtr<nsISimpleEnumerator> arcs;
+ mDataSource->ArcLabelsOut(aContainer, getter_AddRefs(arcs));
+
+ bool wroteDescription = false;
+ while (! wroteDescription) {
+ bool hasMore = false;
+ rv = arcs->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) break;
+
+ if (! hasMore)
+ break;
+
+ nsIRDFResource* property;
+ rv = arcs->GetNext((nsISupports**) &property);
+ if (NS_FAILED(rv)) break;
+
+ // If it's a membership property, then output a "LI"
+ // tag. Otherwise, output a property.
+ if (! IsContainerProperty(property)) {
+ rv = SerializeDescription(aStream, aContainer);
+ wroteDescription = true;
+ }
+
+ NS_RELEASE(property);
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+nsRDFXMLSerializer::SerializePrologue(nsIOutputStream* aStream)
+{
+static const char kXMLVersion[] = "<?xml version=\"1.0\"?>\n";
+
+ nsresult rv;
+ rv = rdf_BlockingWrite(aStream, kXMLVersion, sizeof(kXMLVersion) - 1);
+ if (NS_FAILED(rv)) return rv;
+
+ // global name space declarations
+ rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("<RDF:RDF "));
+ if (NS_FAILED(rv)) return rv;
+
+ nsNameSpaceMap::const_iterator first = mNameSpaces.first();
+ nsNameSpaceMap::const_iterator last = mNameSpaces.last();
+ for (nsNameSpaceMap::const_iterator entry = first; entry != last; ++entry) {
+ if (entry != first) {
+ rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("\n "));
+ if (NS_FAILED(rv)) return rv;
+ }
+ rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("xmlns"));
+ if (NS_FAILED(rv)) return rv;
+
+ if (entry->mPrefix) {
+ rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(":"));
+ if (NS_FAILED(rv)) return rv;
+ nsAutoCString prefix;
+ entry->mPrefix->ToUTF8String(prefix);
+ rv = rdf_BlockingWrite(aStream, prefix);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("=\""));
+ if (NS_FAILED(rv)) return rv;
+ nsAutoCString uri(entry->mURI);
+ rdf_EscapeAttributeValue(uri);
+ rv = rdf_BlockingWrite(aStream, uri);
+ if (NS_FAILED(rv)) return rv;
+ rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("\""));
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(">\n"));
+}
+
+
+nsresult
+nsRDFXMLSerializer::SerializeEpilogue(nsIOutputStream* aStream)
+{
+ return rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("</RDF:RDF>\n"));
+}
+
+class QNameCollector final : public rdfITripleVisitor {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_RDFITRIPLEVISITOR
+ explicit QNameCollector(nsRDFXMLSerializer* aParent)
+ : mParent(aParent){}
+private:
+ ~QNameCollector() {}
+ nsRDFXMLSerializer* mParent;
+};
+
+NS_IMPL_ISUPPORTS(QNameCollector, rdfITripleVisitor)
+nsresult
+QNameCollector::Visit(nsIRDFNode* aSubject, nsIRDFResource* aPredicate,
+ nsIRDFNode* aObject, bool aTruthValue)
+{
+ if (aPredicate == mParent->kRDF_type) {
+ // try to get a type QName for aObject, should be a resource
+ nsCOMPtr<nsIRDFResource> resType = do_QueryInterface(aObject);
+ if (!resType) {
+ // ignore error
+ return NS_OK;
+ }
+ if (mParent->mQNames.Get(resType, nullptr)) {
+ return NS_OK;
+ }
+ mParent->RegisterQName(resType);
+ return NS_OK;
+ }
+
+ if (mParent->mQNames.Get(aPredicate, nullptr)) {
+ return NS_OK;
+ }
+ if (aPredicate == mParent->kRDF_instanceOf ||
+ aPredicate == mParent->kRDF_nextVal)
+ return NS_OK;
+ bool isOrdinal = false;
+ mParent->gRDFC->IsOrdinalProperty(aPredicate, &isOrdinal);
+ if (isOrdinal)
+ return NS_OK;
+
+ mParent->RegisterQName(aPredicate);
+
+ return NS_OK;
+}
+
+nsresult
+nsRDFXMLSerializer::CollectNamespaces()
+{
+ // Iterate over all Triples to get namespaces for subject resource types
+ // and Predicates and cache all the QNames we want to use.
+ nsCOMPtr<rdfITripleVisitor> collector =
+ new QNameCollector(this);
+ nsCOMPtr<rdfIDataSource> ds = do_QueryInterface(mDataSource); // XXX API
+ NS_ENSURE_TRUE(collector && ds, NS_ERROR_FAILURE);
+ return ds->VisitAllTriples(collector);
+}
+
+//----------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsRDFXMLSerializer::Serialize(nsIOutputStream* aStream)
+{
+ nsresult rv;
+
+ rv = CollectNamespaces();
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> resources;
+ rv = mDataSource->GetAllResources(getter_AddRefs(resources));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = SerializePrologue(aStream);
+ if (NS_FAILED(rv))
+ return rv;
+
+ while (1) {
+ bool hasMore = false;
+ resources->HasMoreElements(&hasMore);
+ if (! hasMore)
+ break;
+
+ nsCOMPtr<nsISupports> isupports;
+ resources->GetNext(getter_AddRefs(isupports));
+
+ nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(isupports);
+ if (! resource)
+ continue;
+
+ if (IsA(mDataSource, resource, kRDF_Bag) ||
+ IsA(mDataSource, resource, kRDF_Seq) ||
+ IsA(mDataSource, resource, kRDF_Alt)) {
+ rv = SerializeContainer(aStream, resource);
+ }
+ else {
+ rv = SerializeDescription(aStream, resource);
+ }
+
+ if (NS_FAILED(rv))
+ break;
+ }
+
+ rv = SerializeEpilogue(aStream);
+
+ return rv;
+}
+
+
+bool
+nsRDFXMLSerializer::IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType)
+{
+ nsresult rv;
+
+ bool result;
+ rv = aDataSource->HasAssertion(aResource, kRDF_instanceOf, aType, true, &result);
+ if (NS_FAILED(rv)) return false;
+
+ return result;
+}
diff --git a/components/rdf/src/nsRDFXMLSerializer.h b/components/rdf/src/nsRDFXMLSerializer.h
new file mode 100644
index 000000000..b31a088cd
--- /dev/null
+++ b/components/rdf/src/nsRDFXMLSerializer.h
@@ -0,0 +1,117 @@
+/* -*- 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/. */
+
+#ifndef nsRDFXMLSerializer_h__
+#define nsRDFXMLSerializer_h__
+
+#include "nsIRDFLiteral.h"
+#include "nsIRDFXMLSerializer.h"
+#include "nsIRDFXMLSource.h"
+#include "nsNameSpaceMap.h"
+#include "nsXPIDLString.h"
+
+#include "nsDataHashtable.h"
+#include "rdfITripleVisitor.h"
+
+class nsIOutputStream;
+class nsIRDFContainerUtils;
+
+/**
+ * A helper class that can serialize RDF/XML from a
+ * datasource. Implements both nsIRDFXMLSerializer and
+ * nsIRDFXMLSource.
+ */
+class nsRDFXMLSerializer : public nsIRDFXMLSerializer,
+ public nsIRDFXMLSource
+{
+public:
+ static nsresult
+ Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIRDFXMLSERIALIZER
+ NS_DECL_NSIRDFXMLSOURCE
+
+protected:
+ nsRDFXMLSerializer();
+ virtual ~nsRDFXMLSerializer();
+
+ // Implementation methods
+ nsresult
+ RegisterQName(nsIRDFResource* aResource);
+ nsresult
+ GetQName(nsIRDFResource* aResource, nsCString& aQName);
+ already_AddRefed<nsIAtom>
+ EnsureNewPrefix();
+
+ nsresult
+ SerializeInlineAssertion(nsIOutputStream* aStream,
+ nsIRDFResource* aResource,
+ nsIRDFResource* aProperty,
+ nsIRDFLiteral* aValue);
+
+ nsresult
+ SerializeChildAssertion(nsIOutputStream* aStream,
+ nsIRDFResource* aResource,
+ nsIRDFResource* aProperty,
+ nsIRDFNode* aValue);
+
+ nsresult
+ SerializeProperty(nsIOutputStream* aStream,
+ nsIRDFResource* aResource,
+ nsIRDFResource* aProperty,
+ bool aInline,
+ int32_t* aSkipped);
+
+ bool
+ IsContainerProperty(nsIRDFResource* aProperty);
+
+ nsresult
+ SerializeDescription(nsIOutputStream* aStream,
+ nsIRDFResource* aResource);
+
+ nsresult
+ SerializeMember(nsIOutputStream* aStream,
+ nsIRDFResource* aContainer,
+ nsIRDFNode* aMember);
+
+ nsresult
+ SerializeContainer(nsIOutputStream* aStream,
+ nsIRDFResource* aContainer);
+
+ nsresult
+ SerializePrologue(nsIOutputStream* aStream);
+
+ nsresult
+ SerializeEpilogue(nsIOutputStream* aStream);
+
+ nsresult
+ CollectNamespaces();
+
+ bool
+ IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType);
+
+ nsCOMPtr<nsIRDFDataSource> mDataSource;
+ nsNameSpaceMap mNameSpaces;
+ nsXPIDLCString mBaseURLSpec;
+
+ // hash mapping resources to utf8-encoded QNames
+ nsDataHashtable<nsISupportsHashKey, nsCString> mQNames;
+ friend class QNameCollector;
+
+ uint32_t mPrefixID;
+
+ static int32_t gRefCnt;
+ static nsIRDFResource* kRDF_instanceOf;
+ static nsIRDFResource* kRDF_type;
+ static nsIRDFResource* kRDF_nextVal;
+ static nsIRDFResource* kRDF_Bag;
+ static nsIRDFResource* kRDF_Seq;
+ static nsIRDFResource* kRDF_Alt;
+ static nsIRDFContainerUtils* gRDFC;
+};
+
+#endif // nsRDFXMLSerializer_h__
diff --git a/components/rdf/src/rdf.h b/components/rdf/src/rdf.h
new file mode 100644
index 000000000..a863884a0
--- /dev/null
+++ b/components/rdf/src/rdf.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; 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/. */
+
+/*
+
+ A catch-all header file for miscellaneous RDF stuff. Currently
+ contains error codes and vocabulary macros.
+
+ */
+
+#ifndef rdf_h___
+#define rdf_h___
+
+#include "nsError.h"
+
+/**
+ * The following macros are to aid in vocabulary definition. They
+ * creates const char*'s for "kURI[prefix]_[name]", appropriate
+ * complete namespace qualification on the URI, e.g.,
+ *
+ * #define RDF_NAMESPACE_URI "http://www.w3.org/TR/WD-rdf-syntax#"
+ * DEFINE_RDF_ELEMENT(RDF_NAMESPACE_URI, RDF, ID);
+ *
+ * will define:
+ *
+ * kURIRDF_ID to be "http://www.w3.org/TR/WD-rdf-syntax#ID"
+ */
+
+#define DEFINE_RDF_VOCAB(ns, prefix, name) \
+static const char kURI##prefix##_##name[] = ns #name
+
+/**
+ * Core RDF vocabularies that we use to define semantics
+ */
+
+#define RDF_NAMESPACE_URI "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define WEB_NAMESPACE_URI "http://home.netscape.com/WEB-rdf#"
+#define NC_NAMESPACE_URI "http://home.netscape.com/NC-rdf#"
+#define DEVMO_NAMESPACE_URI_PREFIX "http://developer.mozilla.org/rdf/vocabulary/"
+
+
+/* ContractID prefixes for RDF DLL registration. */
+#define NS_RDF_CONTRACTID "@mozilla.org/rdf"
+#define NS_RDF_DATASOURCE_CONTRACTID NS_RDF_CONTRACTID "/datasource;1"
+#define NS_RDF_DATASOURCE_CONTRACTID_PREFIX NS_RDF_DATASOURCE_CONTRACTID "?name="
+#define NS_RDF_RESOURCE_FACTORY_CONTRACTID "@mozilla.org/rdf/resource-factory;1"
+#define NS_RDF_RESOURCE_FACTORY_CONTRACTID_PREFIX NS_RDF_RESOURCE_FACTORY_CONTRACTID "?name="
+#define NS_RDF_INFER_DATASOURCE_CONTRACTID_PREFIX NS_RDF_CONTRACTID "/infer-datasource;1?engine="
+
+#define NS_RDF_SERIALIZER NS_RDF_CONTRACTID "/serializer;1?format="
+
+// contract ID is in the form
+// @mozilla.org/rdf/delegate-factory;1?key=<key>&scheme=<scheme>
+#define NS_RDF_DELEGATEFACTORY_CONTRACTID "@mozilla.org/rdf/delegate-factory;1"
+#define NS_RDF_DELEGATEFACTORY_CONTRACTID_PREFIX NS_RDF_DELEGATEFACTORY_CONTRACTID "?key="
+
+/*@}*/
+
+#endif /* rdf_h___ */
diff --git a/components/rdf/src/rdfTriplesSerializer.cpp b/components/rdf/src/rdfTriplesSerializer.cpp
new file mode 100644
index 000000000..f419c7612
--- /dev/null
+++ b/components/rdf/src/rdfTriplesSerializer.cpp
@@ -0,0 +1,151 @@
+/* -*- 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 "nsIOutputStream.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "nsIBufferedStreams.h"
+#include "nsNetCID.h"
+#include "nsComponentManagerUtils.h"
+
+#include "rdfISerializer.h"
+#include "rdfIDataSource.h"
+#include "rdfITripleVisitor.h"
+
+#include "nsIRDFResource.h"
+#include "nsIRDFLiteral.h"
+#include "mozilla/Attributes.h"
+
+class TriplesVisitor final : public rdfITripleVisitor
+{
+public:
+ explicit TriplesVisitor(nsIOutputStream* aOut) : mOut(aOut) {}
+ NS_DECL_RDFITRIPLEVISITOR
+ NS_DECL_ISUPPORTS
+protected:
+ ~TriplesVisitor() {}
+ nsresult writeResource(nsIRDFResource* aResource);
+ nsIOutputStream* mOut;
+};
+
+NS_IMPL_ISUPPORTS(TriplesVisitor, rdfITripleVisitor)
+
+nsresult
+TriplesVisitor::writeResource(nsIRDFResource *aResource)
+{
+ nsCString res;
+ uint32_t writeCount, wroteCount;
+ mOut->Write("<", 1, &wroteCount);
+ NS_ENSURE_TRUE(wroteCount == 1, NS_ERROR_FAILURE);
+ nsresult rv = aResource->GetValueUTF8(res);
+ NS_ENSURE_SUCCESS(rv, rv);
+ writeCount = res.Length();
+ mOut->Write(res.get(), writeCount, &wroteCount);
+ NS_ENSURE_TRUE(writeCount == wroteCount, NS_ERROR_FAILURE);
+ mOut->Write("> ", 2, &wroteCount);
+ NS_ENSURE_TRUE(wroteCount == 2, NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TriplesVisitor::Visit(nsIRDFNode *aSubject, nsIRDFResource *aPredicate,
+ nsIRDFNode *aObject, bool aTruthValue)
+{
+ nsCOMPtr<nsIRDFResource> subjectRes = do_QueryInterface(aSubject);
+ nsresult rv = NS_OK;
+ if (subjectRes) {
+ rv = writeResource(subjectRes);
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = writeResource(aPredicate);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsIRDFResource> res = do_QueryInterface(aObject);
+ nsCOMPtr<nsIRDFLiteral> lit;
+ nsCOMPtr<nsIRDFInt> intLit;
+ uint32_t wroteCount;
+ if (res) {
+ rv = writeResource(res);
+ } else if ((lit = do_QueryInterface(aObject)) != nullptr) {
+ const char16_t *value;
+ lit->GetValueConst(&value);
+ nsAutoCString object;
+ object.Append('"');
+ AppendUTF16toUTF8(value, object);
+ object.AppendLiteral("\" ");
+ uint32_t writeCount = object.Length();
+ rv = mOut->Write(object.get(), writeCount, &wroteCount);
+ NS_ENSURE_TRUE(writeCount == wroteCount, NS_ERROR_FAILURE);
+ } else if ((intLit = do_QueryInterface(aObject)) != nullptr) {
+ int32_t value;
+ intLit->GetValue(&value);
+ nsPrintfCString
+ object("\"%i\"^^<http://www.w3.org/2001/XMLSchema#integer> ",
+ value);
+ uint32_t writeCount = object.Length();
+ rv = mOut->Write(object.get(), writeCount, &wroteCount);
+ NS_ENSURE_TRUE(writeCount == wroteCount, NS_ERROR_FAILURE);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mOut->Write(".\n", 2, &wroteCount);
+}
+
+class rdfTriplesSerializer final : public rdfISerializer
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_RDFISERIALIZER
+
+ rdfTriplesSerializer();
+
+private:
+ ~rdfTriplesSerializer();
+
+};
+
+nsresult
+NS_NewTriplesSerializer(rdfISerializer** aResult)
+{
+ NS_PRECONDITION(aResult != nullptr, "null ptr");
+ if (! aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ *aResult = new rdfTriplesSerializer();
+ if (! *aResult)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(rdfTriplesSerializer, rdfISerializer)
+
+rdfTriplesSerializer::rdfTriplesSerializer()
+{
+}
+
+rdfTriplesSerializer::~rdfTriplesSerializer()
+{
+}
+
+NS_IMETHODIMP
+rdfTriplesSerializer::Serialize(rdfIDataSource *aDataSource,
+ nsIOutputStream *aOut)
+{
+ nsresult rv;
+ nsCOMPtr<nsIBufferedOutputStream> bufout =
+ do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bufout->Init(aOut, 1024);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<rdfITripleVisitor> tv = new TriplesVisitor(bufout);
+ NS_ENSURE_TRUE(tv, NS_ERROR_OUT_OF_MEMORY);
+ return aDataSource->VisitAllTriples(tv);
+}
diff --git a/components/rdf/src/rdfutil.cpp b/components/rdf/src/rdfutil.cpp
new file mode 100644
index 000000000..849072145
--- /dev/null
+++ b/components/rdf/src/rdfutil.cpp
@@ -0,0 +1,111 @@
+/* -*- 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/. */
+
+/*
+
+ Implementations for a bunch of useful RDF utility routines. Many of
+ these will eventually be exported outside of RDF.DLL via the
+ nsIRDFService interface.
+
+ TO DO
+
+ 1) Make this so that it doesn't permanently leak the RDF service
+ object.
+
+ 2) Make container functions thread-safe. They currently don't ensure
+ that the RDF:nextVal property is maintained safely.
+
+ */
+
+#include "nsCOMPtr.h"
+#include "nsIRDFDataSource.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFService.h"
+#include "nsIServiceManager.h"
+#include "nsIURL.h"
+#include "nsIIOService.h"
+#include "nsIURL.h"
+#include "nsRDFCID.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsUnicharUtils.h"
+#include "rdfutil.h"
+#include "prtime.h"
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult
+rdf_MakeRelativeRef(const nsCSubstring& aBaseURI, nsCString& aURI)
+{
+ // This implementation is extremely simple: e.g., it can't compute
+ // relative paths, or anything fancy like that. If the context URI
+ // is not a prefix of the URI in question, we'll just bail.
+ uint32_t prefixLen = aBaseURI.Length();
+ if (prefixLen != 0 && StringBeginsWith(aURI, aBaseURI)) {
+ if (prefixLen < aURI.Length() && aURI.CharAt(prefixLen) == '/')
+ ++prefixLen; // chop the leading slash so it's not `absolute'
+
+ aURI.Cut(0, prefixLen);
+ }
+
+ return NS_OK;
+}
+
+void
+rdf_FormatDate(PRTime aTime, nsACString &aResult)
+{
+ // Outputs Unixish date in GMT plus usecs; e.g.,
+ // Wed Jan 9 19:15:13 2002 +002441
+ //
+ PRExplodedTime t;
+ PR_ExplodeTime(aTime, PR_GMTParameters, &t);
+
+ char buf[256];
+ PR_FormatTimeUSEnglish(buf, sizeof buf, "%a %b %d %H:%M:%S %Y", &t);
+ aResult.Append(buf);
+
+ // usecs
+ aResult.AppendLiteral(" +");
+ int32_t usec = t.tm_usec;
+ for (int32_t digit = 100000; digit > 1; digit /= 10) {
+ aResult.Append(char('0' + (usec / digit)));
+ usec %= digit;
+ }
+ aResult.Append(char('0' + usec));
+}
+
+PRTime
+rdf_ParseDate(const nsACString &aTime)
+{
+ PRTime t;
+ PR_ParseTimeString(PromiseFlatCString(aTime).get(), true, &t);
+
+ int32_t usec = 0;
+
+ nsACString::const_iterator begin, digit, end;
+ aTime.BeginReading(begin);
+ aTime.EndReading(end);
+
+ // Walk backwards until we find a `+', run out of string, or a
+ // non-numeric character.
+ digit = end;
+ while (--digit != begin && *digit != '+') {
+ if (*digit < '0' || *digit > '9')
+ break;
+ }
+
+ if (digit != begin && *digit == '+') {
+ // There's a usec field specified (or, at least, something
+ // that looks close enough. Parse it, and add it to the time.
+ while (++digit != end) {
+ usec *= 10;
+ usec += *digit - '0';
+ }
+
+ t += usec;
+ }
+
+ return t;
+}
diff --git a/components/rdf/src/rdfutil.h b/components/rdf/src/rdfutil.h
new file mode 100644
index 000000000..c11581a4a
--- /dev/null
+++ b/components/rdf/src/rdfutil.h
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+
+/*
+
+ A bunch of useful RDF utility routines. Many of these will
+ eventually be exported outside of RDF.DLL via the nsIRDFService
+ interface.
+
+ TO DO
+
+ 1) Move the anonymous resource stuff to nsIRDFService?
+
+ 2) All that's left is rdf_PossiblyMakeRelative() and
+ -Absolute(). Maybe those go on nsIRDFService, too.
+
+ */
+
+#ifndef rdfutil_h__
+#define rdfutil_h__
+
+
+class nsACString;
+class nsCString;
+
+nsresult
+rdf_MakeRelativeRef(const nsCSubstring& aBaseURI, nsCString& aURI);
+
+void
+rdf_FormatDate(PRTime aTime, nsACString &aResult);
+
+PRTime
+rdf_ParseDate(const nsACString &aTime);
+
+#endif // rdfutil_h__
+
+
diff --git a/components/rdf/util/internal/moz.build b/components/rdf/util/internal/moz.build
new file mode 100644
index 000000000..65e6c6fe0
--- /dev/null
+++ b/components/rdf/util/internal/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+include('../objs.mozbuild')
+
+SOURCES += rdf_util_src_cppsrcs
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/rdf/util/moz.build b/components/rdf/util/moz.build
new file mode 100644
index 000000000..b5c43bff9
--- /dev/null
+++ b/components/rdf/util/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+# This file builds the rdfutil_external_s library which should be used
+# by frozen (dependent) linkage components. The internal-linkage code
+# should use rdfutil_s which is built in the internal/ subdirectory.
+
+DIRS += ['internal']
+
+EXPORTS += ['nsRDFResource.h']
+
+include('objs.mozbuild')
+
+SOURCES += rdf_util_src_cppsrcs
+
+Library('rdfutil_external_s')
+
+# we don't want the shared lib, but we want to force the creation of a static lib.
+FORCE_STATIC_LIB = True
+
+DIST_INSTALL = True
diff --git a/components/rdf/util/nsRDFResource.cpp b/components/rdf/util/nsRDFResource.cpp
new file mode 100644
index 000000000..0e6f01f14
--- /dev/null
+++ b/components/rdf/util/nsRDFResource.cpp
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsRDFResource.h"
+#include "nsIServiceManager.h"
+#include "nsIRDFDelegateFactory.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "mozilla/Logging.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+nsIRDFService* nsRDFResource::gRDFService = nullptr;
+nsrefcnt nsRDFResource::gRDFServiceRefCnt = 0;
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsRDFResource::nsRDFResource(void)
+ : mDelegates(nullptr)
+{
+}
+
+nsRDFResource::~nsRDFResource(void)
+{
+ // Release all of the delegate objects
+ while (mDelegates) {
+ DelegateEntry* doomed = mDelegates;
+ mDelegates = mDelegates->mNext;
+ delete doomed;
+ }
+
+ if (!gRDFService)
+ return;
+
+ gRDFService->UnregisterResource(this);
+
+ if (--gRDFServiceRefCnt == 0)
+ NS_RELEASE(gRDFService);
+}
+
+NS_IMPL_ISUPPORTS(nsRDFResource, nsIRDFResource, nsIRDFNode)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRDFNode methods:
+
+NS_IMETHODIMP
+nsRDFResource::EqualsNode(nsIRDFNode* aNode, bool* aResult)
+{
+ NS_PRECONDITION(aNode != nullptr, "null ptr");
+ if (! aNode)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ nsIRDFResource* resource;
+ rv = aNode->QueryInterface(NS_GET_IID(nsIRDFResource), (void**)&resource);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = (static_cast<nsIRDFResource*>(this) == resource);
+ NS_RELEASE(resource);
+ return NS_OK;
+ }
+ else if (rv == NS_NOINTERFACE) {
+ *aResult = false;
+ return NS_OK;
+ }
+ else {
+ return rv;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRDFResource methods:
+
+NS_IMETHODIMP
+nsRDFResource::Init(const char* aURI)
+{
+ NS_PRECONDITION(aURI != nullptr, "null ptr");
+ if (! aURI)
+ return NS_ERROR_NULL_POINTER;
+
+ mURI = aURI;
+
+ if (gRDFServiceRefCnt++ == 0) {
+ nsresult rv = CallGetService(kRDFServiceCID, &gRDFService);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // don't replace an existing resource with the same URI automatically
+ return gRDFService->RegisterResource(this, true);
+}
+
+NS_IMETHODIMP
+nsRDFResource::GetValue(char* *aURI)
+{
+ NS_ASSERTION(aURI, "Null out param.");
+
+ *aURI = ToNewCString(mURI);
+
+ if (!*aURI)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRDFResource::GetValueUTF8(nsACString& aResult)
+{
+ aResult = mURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRDFResource::GetValueConst(const char** aURI)
+{
+ *aURI = mURI.get();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRDFResource::EqualsString(const char* aURI, bool* aResult)
+{
+ NS_PRECONDITION(aURI != nullptr, "null ptr");
+ if (! aURI)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_PRECONDITION(aResult, "null ptr");
+
+ *aResult = mURI.Equals(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRDFResource::GetDelegate(const char* aKey, REFNSIID aIID, void** aResult)
+{
+ NS_PRECONDITION(aKey != nullptr, "null ptr");
+ if (! aKey)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv;
+ *aResult = nullptr;
+
+ DelegateEntry* entry = mDelegates;
+ while (entry) {
+ if (entry->mKey.Equals(aKey)) {
+ rv = entry->mDelegate->QueryInterface(aIID, aResult);
+ return rv;
+ }
+
+ entry = entry->mNext;
+ }
+
+ // Construct a ContractID of the form "@mozilla.org/rdf/delegate/[key]/[scheme];1
+ nsAutoCString contractID(NS_RDF_DELEGATEFACTORY_CONTRACTID_PREFIX);
+ contractID.Append(aKey);
+ contractID.AppendLiteral("&scheme=");
+
+ int32_t i = mURI.FindChar(':');
+ contractID += StringHead(mURI, i);
+
+ nsCOMPtr<nsIRDFDelegateFactory> delegateFactory =
+ do_CreateInstance(contractID.get(), &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = delegateFactory->CreateDelegate(this, aKey, aIID, aResult);
+ if (NS_FAILED(rv)) return rv;
+
+ // Okay, we've successfully created a delegate. Let's remember it.
+ entry = new DelegateEntry;
+ if (! entry) {
+ NS_RELEASE(*reinterpret_cast<nsISupports**>(aResult));
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ entry->mKey = aKey;
+ entry->mDelegate = do_QueryInterface(*reinterpret_cast<nsISupports**>(aResult), &rv);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("nsRDFResource::GetDelegate(): can't QI to nsISupports!");
+
+ delete entry;
+ NS_RELEASE(*reinterpret_cast<nsISupports**>(aResult));
+ return NS_ERROR_FAILURE;
+ }
+
+ entry->mNext = mDelegates;
+
+ mDelegates = entry;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRDFResource::ReleaseDelegate(const char* aKey)
+{
+ NS_PRECONDITION(aKey != nullptr, "null ptr");
+ if (! aKey)
+ return NS_ERROR_NULL_POINTER;
+
+ DelegateEntry* entry = mDelegates;
+ DelegateEntry** link = &mDelegates;
+
+ while (entry) {
+ if (entry->mKey.Equals(aKey)) {
+ *link = entry->mNext;
+ delete entry;
+ return NS_OK;
+ }
+
+ link = &(entry->mNext);
+ entry = entry->mNext;
+ }
+
+ NS_WARNING("nsRDFResource::ReleaseDelegate() no delegate found");
+ return NS_OK;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/components/rdf/util/nsRDFResource.h b/components/rdf/util/nsRDFResource.h
new file mode 100644
index 000000000..7556b323c
--- /dev/null
+++ b/components/rdf/util/nsRDFResource.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsRDFResource_h__
+#define nsRDFResource_h__
+
+#include "nsCOMPtr.h"
+#include "nsIRDFNode.h"
+#include "nsIRDFResource.h"
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "rdf.h"
+
+class nsIRDFService;
+
+/**
+ * This simple base class implements nsIRDFResource, and can be used as a
+ * superclass for more sophisticated resource implementations.
+ */
+class nsRDFResource : public nsIRDFResource {
+public:
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIRDFNode methods:
+ NS_IMETHOD EqualsNode(nsIRDFNode* aNode, bool* aResult) override;
+
+ // nsIRDFResource methods:
+ NS_IMETHOD Init(const char* aURI) override;
+ NS_IMETHOD GetValue(char* *aURI) override;
+ NS_IMETHOD GetValueUTF8(nsACString& aResult) override;
+ NS_IMETHOD GetValueConst(const char** aURI) override;
+ NS_IMETHOD EqualsString(const char* aURI, bool* aResult) override;
+ NS_IMETHOD GetDelegate(const char* aKey, REFNSIID aIID, void** aResult) override;
+ NS_IMETHOD ReleaseDelegate(const char* aKey) override;
+
+ // nsRDFResource methods:
+ nsRDFResource(void);
+
+protected:
+ virtual ~nsRDFResource(void);
+ static nsIRDFService* gRDFService;
+ static nsrefcnt gRDFServiceRefCnt;
+
+protected:
+ nsCString mURI;
+
+ struct DelegateEntry {
+ nsCString mKey;
+ nsCOMPtr<nsISupports> mDelegate;
+ DelegateEntry* mNext;
+ };
+
+ DelegateEntry* mDelegates;
+};
+
+#endif // nsRDFResource_h__
diff --git a/components/rdf/util/objs.mozbuild b/components/rdf/util/objs.mozbuild
new file mode 100644
index 000000000..ae220f776
--- /dev/null
+++ b/components/rdf/util/objs.mozbuild
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+rdf_util_src_cppsrcs = [
+ '/components/rdf/util/nsRDFResource.cpp',
+]
diff --git a/components/reader/.eslintrc.js b/components/reader/.eslintrc.js
new file mode 100644
index 000000000..617c1f9cf
--- /dev/null
+++ b/components/reader/.eslintrc.js
@@ -0,0 +1,196 @@
+"use strict";
+
+module.exports = {
+ "rules": {
+ // Braces only needed for multi-line arrow function blocks
+ // "arrow-body-style": ["error", "as-needed"],
+
+ // Require spacing around =>
+ // "arrow-spacing": "error",
+
+ // Always require spacing around a single line block
+ // "block-spacing": "warn",
+
+ // No newline before open brace for a block
+ "brace-style": "error",
+
+ // No space before always a space after a comma
+ "comma-spacing": ["error", {"before": false, "after": true}],
+
+ // Commas at the end of the line not the start
+ // "comma-style": "error",
+
+ // Don't require spaces around computed properties
+ // "computed-property-spacing": ["error", "never"],
+
+ // Functions must always return something or nothing
+ "consistent-return": "error",
+
+ // Require braces around blocks that start a new line
+ // Note that this rule is likely to be overridden on a per-directory basis
+ // very frequently.
+ // "curly": ["error", "multi-line"],
+
+ // Always require a trailing EOL
+ "eol-last": "error",
+
+ // Require function* name()
+ // "generator-star-spacing": ["error", {"before": false, "after": true}],
+
+ // Two space indent
+ "indent": ["error", 2, { "SwitchCase": 1 }],
+
+ // Space after colon not before in property declarations
+ "key-spacing": ["error", { "beforeColon": false, "afterColon": true, "mode": "minimum" }],
+
+ // Unix linebreaks
+ "linebreak-style": ["error", "unix"],
+
+ // Always require parenthesis for new calls
+ "new-parens": "error",
+
+ // Use [] instead of Array()
+ // "no-array-constructor": "error",
+
+ // No duplicate arguments in function declarations
+ "no-dupe-args": "error",
+
+ // No duplicate keys in object declarations
+ "no-dupe-keys": "error",
+
+ // No duplicate cases in switch statements
+ "no-duplicate-case": "error",
+
+ // No labels
+ "no-labels": "error",
+
+ // If an if block ends with a return no need for an else block
+ "no-else-return": "error",
+
+ // No empty statements
+ "no-empty": "error",
+
+ // No empty character classes in regex
+ "no-empty-character-class": "error",
+
+ // Disallow empty destructuring
+ "no-empty-pattern": "error",
+
+ // No assiging to exception variable
+ // "no-ex-assign": "error",
+
+ // No using !! where casting to boolean is already happening
+ // "no-extra-boolean-cast": "error",
+
+ // No double semicolon
+ "no-extra-semi": "error",
+
+ // No overwriting defined functions
+ "no-func-assign": "error",
+
+ // Declarations in Program or Function Body
+ "no-inner-declarations": "error",
+
+ // No invalid regular expresions
+ "no-invalid-regexp": "error",
+
+ // No odd whitespace characters
+ "no-irregular-whitespace": "error",
+
+ // No single if block inside an else block
+ "no-lonely-if": "error",
+
+ // No mixing spaces and tabs in indent
+ "no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
+
+ // No unnecessary spacing
+ "no-multi-spaces": ["error", { exceptions: { "AssignmentExpression": true, "VariableDeclarator": true, "ArrayExpression": true, "ObjectExpression": true } }],
+
+ // No reassigning native JS objects
+ "no-native-reassign": "error",
+
+ // Nested ternary statements are confusing
+ "no-nested-ternary": "error",
+
+ // Use {} instead of new Object()
+ // "no-new-object": "error",
+
+ // No Math() or JSON()
+ "no-obj-calls": "error",
+
+ // No octal literals
+ "no-octal": "error",
+
+ // No redeclaring variables
+ "no-redeclare": "error",
+
+ // No unnecessary comparisons
+ "no-self-compare": "error",
+
+ // No declaring variables from an outer scope
+ "no-shadow": "error",
+
+ // No declaring variables that hide things like arguments
+ "no-shadow-restricted-names": "error",
+
+ // No spaces between function name and parentheses
+ "no-spaced-func": "error",
+
+ // No trailing whitespace
+ "no-trailing-spaces": "error",
+
+ // No using undeclared variables
+ // "no-undef": "error",
+
+ // Error on newline where a semicolon is needed
+ "no-unexpected-multiline": "error",
+
+ // No unreachable statements
+ "no-unreachable": "error",
+
+ // No expressions where a statement is expected
+ // "no-unused-expressions": "error",
+
+ // No declaring variables that are never used
+ "no-unused-vars": ["error", {"vars": "all", "args": "none"}],
+
+ // No using variables before defined
+ // "no-use-before-define": ["error", "nofunc"],
+
+ // No using with
+ "no-with": "error",
+
+ // Always require semicolon at end of statement
+ "semi": ["error", "always"],
+
+ // Require space after keywords
+ "keyword-spacing": "error",
+
+ // Require space before blocks
+ "space-before-blocks": "error",
+
+ // Never use spaces before function parentheses
+ // "space-before-function-paren": ["error", { "anonymous": "always", "named": "never" }],
+
+ // Require spaces before finally, catch, etc.
+ // "space-before-keywords": ["error", "always"],
+
+ // No space padding in parentheses
+ // "space-in-parens": ["error", "never"],
+
+ // Require spaces around operators
+ // "space-infix-ops": "error",
+
+ // Require spaces after return, throw and case
+ // "space-return-throw-case": "error",
+
+ // ++ and -- should not need spacing
+ // "space-unary-ops": ["error", { "words": true, "nonwords": false }],
+
+ // No comparisons to NaN
+ "use-isnan": "error",
+
+ // Only check typeof against valid results
+ "valid-typeof": "error",
+ },
+}
diff --git a/components/reader/AboutReader.jsm b/components/reader/AboutReader.jsm
new file mode 100644
index 000000000..663e88050
--- /dev/null
+++ b/components/reader/AboutReader.jsm
@@ -0,0 +1,935 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, Cc = Components.classes, Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "AboutReader" ];
+
+Cu.import("resource://gre/modules/ReaderMode.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncPrefs", "resource://gre/modules/AsyncPrefs.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NarrateControls", "resource://gre/modules/narrate/NarrateControls.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
+
+var gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
+
+var AboutReader = function(win, articlePromise) {
+ let url = this._getOriginalUrl(win);
+ if (!(url.startsWith("http://") || url.startsWith("https://"))) {
+ let errorMsg = "Only http:// and https:// URLs can be loaded in about:reader.";
+ if (Services.prefs.getBoolPref("reader.errors.includeURLs"))
+ errorMsg += " Tried to load: " + url + ".";
+ Cu.reportError(errorMsg);
+ win.location.href = "about:blank";
+ return;
+ }
+
+ let doc = win.document;
+
+ this._docRef = Cu.getWeakReference(doc);
+ this._winRef = Cu.getWeakReference(win);
+ this._innerWindowId = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+
+ this._article = null;
+ this._languagePromise = new Promise(resolve => {
+ this._foundLanguage = resolve;
+ });
+
+ if (articlePromise) {
+ this._articlePromise = articlePromise;
+ }
+
+ this._headerElementRef = Cu.getWeakReference(doc.querySelector(".reader-header"));
+ this._domainElementRef = Cu.getWeakReference(doc.querySelector(".reader-domain"));
+ this._titleElementRef = Cu.getWeakReference(doc.querySelector(".reader-title"));
+ this._readTimeElementRef = Cu.getWeakReference(doc.querySelector(".reader-estimated-time"));
+ this._creditsElementRef = Cu.getWeakReference(doc.querySelector(".reader-credits"));
+ this._contentElementRef = Cu.getWeakReference(doc.querySelector(".moz-reader-content"));
+ this._toolbarElementRef = Cu.getWeakReference(doc.querySelector(".reader-toolbar"));
+ this._messageElementRef = Cu.getWeakReference(doc.querySelector(".reader-message"));
+ this._containerElementRef = Cu.getWeakReference(doc.querySelector(".container"));
+
+ this._scrollOffset = win.pageYOffset;
+
+ doc.addEventListener("mousedown", this);
+ doc.addEventListener("click", this);
+
+ win.addEventListener("pagehide", this);
+ win.addEventListener("scroll", this);
+ win.addEventListener("resize", this);
+
+ win.addEventListener("AboutReaderAddButton", this, false, true);
+ win.addEventListener("AboutReaderRemoveButton", this, false, true);
+
+ Services.obs.addObserver(this, "inner-window-destroyed", false);
+
+ this._setupStyleDropdown();
+ this._setupButton("close-button", this._onReaderClose.bind(this), "aboutReader.toolbar.close");
+
+ // we're ready for any external setup, send a signal for that.
+ doc.dispatchEvent(
+ new win.CustomEvent("AboutReaderOnSetup", { bubbles: true, cancelable: false }));
+
+ let colorSchemeValues = JSON.parse(Services.prefs.getCharPref("reader.color_scheme.values"));
+ let colorSchemeOptions = colorSchemeValues.map((value) => {
+ return {
+ name: gStrings.GetStringFromName("aboutReader.colorScheme." + value),
+ value,
+ itemClass: value + "-button"
+ };
+ });
+
+ let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
+ this._setupSegmentedButton("color-scheme-buttons", colorSchemeOptions, colorScheme, this._setColorSchemePref.bind(this));
+ this._setColorSchemePref(colorScheme);
+
+ let fontTypeSample = gStrings.GetStringFromName("aboutReader.fontTypeSample");
+ let fontTypeOptions = [
+ { name: fontTypeSample,
+ description: gStrings.GetStringFromName("aboutReader.fontType.sans-serif"),
+ value: "sans-serif",
+ itemClass: "sans-serif-button"
+ },
+ { name: fontTypeSample,
+ description: gStrings.GetStringFromName("aboutReader.fontType.serif"),
+ value: "serif",
+ itemClass: "serif-button" },
+ ];
+
+ let fontType = Services.prefs.getCharPref("reader.font_type");
+ this._setupSegmentedButton("font-type-buttons", fontTypeOptions, fontType, this._setFontType.bind(this));
+ this._setFontType(fontType);
+
+ this._setupFontSizeButtons();
+
+ this._setupContentWidthButtons();
+
+ this._setupLineHeightButtons();
+
+ if (win.speechSynthesis && Services.prefs.getBoolPref("narrate.enabled")) {
+ new NarrateControls(win, this._languagePromise);
+ }
+
+ this._loadArticle();
+
+ let dropdown = this._toolbarElement;
+
+ let elemL10nMap = {
+ ".minus-button": "minus",
+ ".plus-button": "plus",
+ ".content-width-minus-button": "contentwidthminus",
+ ".content-width-plus-button": "contentwidthplus",
+ ".line-height-minus-button": "lineheightminus",
+ ".line-height-plus-button": "lineheightplus",
+ ".light-button": "colorschemelight",
+ ".dark-button": "colorschemedark",
+ ".sepia-button": "colorschemesepia",
+ };
+
+ for (let [selector, stringID] of Object.entries(elemL10nMap)) {
+ dropdown.querySelector(selector).setAttribute("title",
+ gStrings.GetStringFromName("aboutReader.toolbar." + stringID));
+ }
+};
+
+AboutReader.prototype = {
+ _BLOCK_IMAGES_SELECTOR: ".content p > img:only-child, " +
+ ".content p > a:only-child > img:only-child, " +
+ ".content .wp-caption img, " +
+ ".content figure img",
+
+ get _doc() {
+ return this._docRef.get();
+ },
+
+ get _win() {
+ return this._winRef.get();
+ },
+
+ get _headerElement() {
+ return this._headerElementRef.get();
+ },
+
+ get _domainElement() {
+ return this._domainElementRef.get();
+ },
+
+ get _titleElement() {
+ return this._titleElementRef.get();
+ },
+
+ get _readTimeElement() {
+ return this._readTimeElementRef.get();
+ },
+
+ get _creditsElement() {
+ return this._creditsElementRef.get();
+ },
+
+ get _contentElement() {
+ return this._contentElementRef.get();
+ },
+
+ get _toolbarElement() {
+ return this._toolbarElementRef.get();
+ },
+
+ get _messageElement() {
+ return this._messageElementRef.get();
+ },
+
+ get _containerElement() {
+ return this._containerElementRef.get();
+ },
+
+ get _isToolbarVertical() {
+ if (this._toolbarVertical !== undefined) {
+ return this._toolbarVertical;
+ }
+ return this._toolbarVertical = Services.prefs.getBoolPref("reader.toolbar.vertical");
+ },
+
+ // Provides unique view Id.
+ get viewId() {
+ let _viewId = Cc["@mozilla.org/uuid-generator;1"].
+ getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+ Object.defineProperty(this, "viewId", { value: _viewId });
+
+ return _viewId;
+ },
+
+ handleEvent(aEvent) {
+ if (!aEvent.isTrusted)
+ return;
+
+ let target = aEvent.target;
+ switch (aEvent.type) {
+ case "mousedown":
+ if (!target.closest(".dropdown-popup")) {
+ this._closeDropdowns();
+ }
+ break;
+ case "click":
+ if (target.classList.contains("dropdown-toggle")) {
+ this._toggleDropdownClicked(aEvent);
+ }
+ break;
+ case "scroll":
+ this._closeDropdowns(true);
+ this._scrollOffset = aEvent.pageY;
+ break;
+ case "resize":
+ this._updateImageMargins();
+ if (this._isToolbarVertical) {
+ this._win.setTimeout(() => {
+ for (let dropdown of this._doc.querySelectorAll(".dropdown.open")) {
+ this._updatePopupPosition(dropdown);
+ }
+ }, 0);
+ }
+ break;
+
+ case "pagehide":
+ // Close the Banners Font-dropdown, cleanup BackPressListener.
+ this._closeDropdowns();
+ this._windowUnloaded = true;
+ break;
+
+ case "AboutReaderAddButton": {
+ if (aEvent.detail.id && aEvent.detail.image &&
+ !this._doc.getElementById(aEvent.detail.id)) {
+ let btn = this._doc.createElement("button");
+ btn.setAttribute("class", "button " + aEvent.detail.id);
+ btn.setAttribute("style", "background-image: url('" + aEvent.detail.image + "')");
+ btn.setAttribute("id", aEvent.detail.id);
+ if (aEvent.detail.title)
+ btn.setAttribute("title", aEvent.detail.title);
+ if (aEvent.detail.text)
+ btn.textContent = aEvent.detail.text;
+ let tb = this._toolbarElement;
+ tb.appendChild(btn);
+ this._setupButton(aEvent.detail.id, button => {
+ var data = { article: this._article };
+ this._doc.dispatchEvent(
+ new this._win.CustomEvent("AboutReaderButtonClicked-" + button.getAttribute("id"), {detail: data, bubbles: true, cancelable: false}));
+ });
+ }
+ break;
+ }
+
+ case "AboutReaderRemoveButton": {
+ if (aEvent.detail.id) {
+ let btn = this._doc.getElementById(aEvent.detail.id);
+ if (btn)
+ btn.remove();
+ }
+ break;
+ }
+ }
+ },
+
+ observe(subject, topic, data) {
+ if (subject.QueryInterface(Ci.nsISupportsPRUint64).data != this._innerWindowId) {
+ return;
+ }
+
+ Services.obs.removeObserver(this, "inner-window-destroyed");
+ this._windowUnloaded = true;
+ },
+
+ _onReaderClose() {
+ ReaderMode.leaveReaderMode(this._win.document.docShell, this._win);
+ },
+
+ _setFontSize(newFontSize) {
+ this._fontSize = newFontSize;
+ let size = (10 + 2 * this._fontSize) + "px";
+
+ this._containerElement.style.setProperty("--font-size", size);
+ return AsyncPrefs.set("reader.font_size", this._fontSize);
+ },
+
+ _setupFontSizeButtons() {
+ const FONT_SIZE_MIN = 1;
+ const FONT_SIZE_MAX = 9;
+
+ let currentSize = Services.prefs.getIntPref("reader.font_size");
+ currentSize = Math.max(FONT_SIZE_MIN, Math.min(FONT_SIZE_MAX, currentSize));
+
+ let plusButton = this._doc.querySelector(".plus-button");
+ let minusButton = this._doc.querySelector(".minus-button");
+
+ function updateControls() {
+ if (currentSize === FONT_SIZE_MIN) {
+ minusButton.setAttribute("disabled", true);
+ } else {
+ minusButton.removeAttribute("disabled");
+ }
+ if (currentSize === FONT_SIZE_MAX) {
+ plusButton.setAttribute("disabled", true);
+ } else {
+ plusButton.removeAttribute("disabled");
+ }
+ }
+
+ updateControls();
+ this._setFontSize(currentSize);
+
+ plusButton.addEventListener("click", (event) => {
+ if (!event.isTrusted) {
+ return;
+ }
+ event.stopPropagation();
+
+ if (currentSize >= FONT_SIZE_MAX) {
+ return;
+ }
+
+ currentSize++;
+ updateControls();
+ this._setFontSize(currentSize);
+ }, true);
+
+ minusButton.addEventListener("click", (event) => {
+ if (!event.isTrusted) {
+ return;
+ }
+ event.stopPropagation();
+
+ if (currentSize <= FONT_SIZE_MIN) {
+ return;
+ }
+
+ currentSize--;
+ updateControls();
+ this._setFontSize(currentSize);
+ }, true);
+ },
+
+ _setContentWidth(newContentWidth) {
+ let containerClasses = this._containerElement.classList;
+
+ if (this._contentWidth > 0)
+ containerClasses.remove("content-width" + this._contentWidth);
+
+ this._contentWidth = newContentWidth;
+ containerClasses.add("content-width" + this._contentWidth);
+ return AsyncPrefs.set("reader.content_width", this._contentWidth);
+ },
+
+ _setupContentWidthButtons() {
+ const CONTENT_WIDTH_MIN = 1;
+ const CONTENT_WIDTH_MAX = 9;
+
+ let currentContentWidth = Services.prefs.getIntPref("reader.content_width");
+ currentContentWidth = Math.max(CONTENT_WIDTH_MIN, Math.min(CONTENT_WIDTH_MAX, currentContentWidth));
+
+ let plusButton = this._doc.querySelector(".content-width-plus-button");
+ let minusButton = this._doc.querySelector(".content-width-minus-button");
+
+ function updateControls() {
+ if (currentContentWidth === CONTENT_WIDTH_MIN) {
+ minusButton.setAttribute("disabled", true);
+ } else {
+ minusButton.removeAttribute("disabled");
+ }
+ if (currentContentWidth === CONTENT_WIDTH_MAX) {
+ plusButton.setAttribute("disabled", true);
+ } else {
+ plusButton.removeAttribute("disabled");
+ }
+ }
+
+ updateControls();
+ this._setContentWidth(currentContentWidth);
+
+ plusButton.addEventListener("click", (event) => {
+ if (!event.isTrusted) {
+ return;
+ }
+ event.stopPropagation();
+
+ if (currentContentWidth >= CONTENT_WIDTH_MAX) {
+ return;
+ }
+
+ currentContentWidth++;
+ updateControls();
+ this._setContentWidth(currentContentWidth);
+ }, true);
+
+ minusButton.addEventListener("click", (event) => {
+ if (!event.isTrusted) {
+ return;
+ }
+ event.stopPropagation();
+
+ if (currentContentWidth <= CONTENT_WIDTH_MIN) {
+ return;
+ }
+
+ currentContentWidth--;
+ updateControls();
+ this._setContentWidth(currentContentWidth);
+ }, true);
+ },
+
+ _setLineHeight(newLineHeight) {
+ let contentClasses = this._contentElement.classList;
+
+ if (this._lineHeight > 0)
+ contentClasses.remove("line-height" + this._lineHeight);
+
+ this._lineHeight = newLineHeight;
+ contentClasses.add("line-height" + this._lineHeight);
+ return AsyncPrefs.set("reader.line_height", this._lineHeight);
+ },
+
+ _setupLineHeightButtons() {
+ const LINE_HEIGHT_MIN = 1;
+ const LINE_HEIGHT_MAX = 9;
+
+ let currentLineHeight = Services.prefs.getIntPref("reader.line_height");
+ currentLineHeight = Math.max(LINE_HEIGHT_MIN, Math.min(LINE_HEIGHT_MAX, currentLineHeight));
+
+ let plusButton = this._doc.querySelector(".line-height-plus-button");
+ let minusButton = this._doc.querySelector(".line-height-minus-button");
+
+ function updateControls() {
+ if (currentLineHeight === LINE_HEIGHT_MIN) {
+ minusButton.setAttribute("disabled", true);
+ } else {
+ minusButton.removeAttribute("disabled");
+ }
+ if (currentLineHeight === LINE_HEIGHT_MAX) {
+ plusButton.setAttribute("disabled", true);
+ } else {
+ plusButton.removeAttribute("disabled");
+ }
+ }
+
+ updateControls();
+ this._setLineHeight(currentLineHeight);
+
+ plusButton.addEventListener("click", (event) => {
+ if (!event.isTrusted) {
+ return;
+ }
+ event.stopPropagation();
+
+ if (currentLineHeight >= LINE_HEIGHT_MAX) {
+ return;
+ }
+
+ currentLineHeight++;
+ updateControls();
+ this._setLineHeight(currentLineHeight);
+ }, true);
+
+ minusButton.addEventListener("click", (event) => {
+ if (!event.isTrusted) {
+ return;
+ }
+ event.stopPropagation();
+
+ if (currentLineHeight <= LINE_HEIGHT_MIN) {
+ return;
+ }
+
+ currentLineHeight--;
+ updateControls();
+ this._setLineHeight(currentLineHeight);
+ }, true);
+ },
+
+ _setColorScheme(newColorScheme) {
+ if (this._colorScheme === newColorScheme)
+ return;
+
+ let bodyClasses = this._doc.body.classList;
+
+ if (this._colorScheme)
+ bodyClasses.remove(this._colorScheme);
+
+ this._colorScheme = newColorScheme;
+ bodyClasses.add(this._colorScheme);
+ },
+
+ // Pref values include "dark", "light", and "sepia".
+ _setColorSchemePref(colorSchemePref) {
+ this._setColorScheme(colorSchemePref);
+
+ AsyncPrefs.set("reader.color_scheme", colorSchemePref);
+ },
+
+ _setFontType(newFontType) {
+ if (this._fontType === newFontType)
+ return;
+
+ let bodyClasses = this._doc.body.classList;
+
+ if (this._fontType)
+ bodyClasses.remove(this._fontType);
+
+ this._fontType = newFontType;
+ bodyClasses.add(this._fontType);
+
+ AsyncPrefs.set("reader.font_type", this._fontType);
+ },
+
+ _setToolbarVisibility(visible) {
+ let tb = this._toolbarElement;
+
+ if (visible) {
+ if (tb.style.opacity != "1") {
+ tb.removeAttribute("hidden");
+ tb.style.opacity = "1";
+ }
+ } else if (tb.style.opacity != "0") {
+ tb.addEventListener("transitionend", evt => {
+ if (tb.style.opacity == "0") {
+ tb.setAttribute("hidden", "");
+ }
+ }, { once: true });
+ tb.style.opacity = "0";
+ }
+ },
+
+ async _loadArticle() {
+ let url = this._getOriginalUrl();
+ this._showProgressDelayed();
+
+ let article;
+ if (this._articlePromise) {
+ article = await this._articlePromise;
+ } else {
+ try {
+ article = await this._getArticle(url);
+ } catch (e) {
+ if (e && e.newURL) {
+ let readerURL = "about:reader?url=" + encodeURIComponent(e.newURL);
+ this._win.location.replace(readerURL);
+ return;
+ }
+ }
+ }
+
+ if (this._windowUnloaded) {
+ return;
+ }
+
+ // Replace the loading message with an error message if there's a failure.
+ // Users are supposed to navigate away by themselves (because we cannot
+ // remove ourselves from session history.)
+ if (!article) {
+ this._showError();
+ return;
+ }
+
+ this._showContent(article);
+ },
+
+ _getArticle(url) {
+ return ReaderMode.downloadAndParseDocument(url);
+ },
+
+ _requestFavicon() {
+ let faviconUrl = PlacesUtils.promiseFaviconLinkUrl(this._article.url);
+ var self = this;
+ faviconUrl.then(function onResolution(favicon) {
+ self._loadFavicon(self._article.url, favicon.path.replace(/^favicon:/, ""));
+ },
+ function onRejection(reason) {
+ Cu.reportError("Error requesting favicon URL for about:reader content: " + reason);
+ }).catch(Cu.reportError);
+ },
+
+ _loadFavicon(url, faviconUrl) {
+ if (this._article.url !== url)
+ return;
+
+ let doc = this._doc;
+
+ let link = doc.createElement("link");
+ link.rel = "shortcut icon";
+ link.href = faviconUrl;
+
+ doc.getElementsByTagName("head")[0].appendChild(link);
+ },
+
+ _updateImageMargins() {
+ let windowWidth = this._win.innerWidth;
+ let bodyWidth = this._doc.body.clientWidth;
+
+ let setImageMargins = function(img) {
+ // If the image is at least as wide as the window, make it fill edge-to-edge on mobile.
+ if (img.naturalWidth >= windowWidth) {
+ img.setAttribute("moz-reader-full-width", true);
+ } else {
+ img.removeAttribute("moz-reader-full-width");
+ }
+
+ // If the image is at least half as wide as the body, center it on desktop.
+ if (img.naturalWidth >= bodyWidth / 2) {
+ img.setAttribute("moz-reader-center", true);
+ } else {
+ img.removeAttribute("moz-reader-center");
+ }
+ };
+
+ let imgs = this._doc.querySelectorAll(this._BLOCK_IMAGES_SELECTOR);
+ for (let i = imgs.length; --i >= 0;) {
+ let img = imgs[i];
+
+ if (img.naturalWidth > 0) {
+ setImageMargins(img);
+ } else {
+ img.onload = function() {
+ setImageMargins(img);
+ };
+ }
+ }
+ },
+
+ _maybeSetTextDirection: function Read_maybeSetTextDirection(article) {
+ if (article.dir) {
+ // Set "dir" attribute on content
+ this._contentElement.setAttribute("dir", article.dir);
+ this._headerElement.setAttribute("dir", article.dir);
+
+ // The native locale could be set differently than the article's text direction.
+ var localeDirection = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
+ this._readTimeElement.setAttribute("dir", localeDirection);
+ this._readTimeElement.style.textAlign = article.dir == "rtl" ? "right" : "left";
+ }
+ },
+
+ _fixLocalLinks() {
+ // We need to do this because preprocessing the content through nsIParserUtils
+ // gives back a DOM with a <base> element. That influences how these URLs get
+ // resolved, making them no longer match the document URI (which is
+ // about:reader?url=...). To fix this, make all the hash URIs absolute. This
+ // is hacky, but the alternative of removing the base element has potential
+ // security implications if Readability has not successfully made all the URLs
+ // absolute, so we pick just fixing these in-document links explicitly.
+ let localLinks = this._contentElement.querySelectorAll("a[href^='#']");
+ for (let localLink of localLinks) {
+ // Have to get the attribute because .href provides an absolute URI.
+ localLink.href = this._doc.documentURI + localLink.getAttribute("href");
+ }
+ },
+
+ _formatReadTime(slowEstimate, fastEstimate) {
+ let displayStringKey = "aboutReader.estimatedReadTimeRange1";
+
+ // only show one reading estimate when they are the same value
+ if (slowEstimate == fastEstimate) {
+ displayStringKey = "aboutReader.estimatedReadTimeValue1";
+ }
+
+ return PluralForm.get(slowEstimate, gStrings.GetStringFromName(displayStringKey))
+ .replace("#1", fastEstimate)
+ .replace("#2", slowEstimate);
+ },
+
+ _showError() {
+ this._headerElement.style.display = "none";
+ this._contentElement.style.display = "none";
+
+ let errorMessage = gStrings.GetStringFromName("aboutReader.loadError");
+ this._messageElement.textContent = errorMessage;
+ this._messageElement.style.display = "block";
+
+ this._doc.title = errorMessage;
+
+ this._doc.documentElement.dataset.isError = true;
+
+ this._error = true;
+
+ this._doc.dispatchEvent(
+ new this._win.CustomEvent("AboutReaderContentError", { bubbles: true, cancelable: false }));
+ },
+
+ // This function is the JS version of Java's StringUtils.stripCommonSubdomains.
+ _stripHost(host) {
+ if (!host)
+ return host;
+
+ let start = 0;
+
+ if (host.startsWith("www."))
+ start = 4;
+ else if (host.startsWith("m."))
+ start = 2;
+ else if (host.startsWith("mobile."))
+ start = 7;
+
+ return host.substring(start);
+ },
+
+ _showContent(article) {
+ this._messageElement.style.display = "none";
+
+ this._article = article;
+
+ this._domainElement.href = article.url;
+ let articleUri = Services.io.newURI(article.url);
+ this._domainElement.textContent = this._stripHost(articleUri.host);
+ this._creditsElement.textContent = article.byline;
+
+ this._titleElement.textContent = article.title;
+ this._readTimeElement.textContent = this._formatReadTime(article.readingTimeMinsSlow, article.readingTimeMinsFast);
+ this._doc.title = article.title;
+
+ this._headerElement.style.display = "block";
+
+ let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
+ let contentFragment = parserUtils.parseFragment(article.content,
+ Ci.nsIParserUtils.SanitizerDropForms | Ci.nsIParserUtils.SanitizerAllowStyle,
+ false, articleUri, this._contentElement);
+ this._contentElement.innerHTML = "";
+ this._contentElement.appendChild(contentFragment);
+ this._fixLocalLinks();
+ this._maybeSetTextDirection(article);
+ this._foundLanguage(article.language);
+
+ this._contentElement.style.display = "block";
+ this._updateImageMargins();
+
+ this._requestFavicon();
+ this._doc.body.classList.add("loaded");
+
+ this._goToReference(articleUri.ref);
+
+ Services.obs.notifyObservers(this._win, "AboutReader:Ready", "");
+
+ this._doc.dispatchEvent(
+ new this._win.CustomEvent("AboutReaderContentReady", { bubbles: true, cancelable: false }));
+ },
+
+ _hideContent() {
+ this._headerElement.style.display = "none";
+ this._contentElement.style.display = "none";
+ },
+
+ _showProgressDelayed() {
+ this._win.setTimeout(() => {
+ // No need to show progress if the article has been loaded,
+ // if the window has been unloaded, or if there was an error
+ // trying to load the article.
+ if (this._article || this._windowUnloaded || this._error) {
+ return;
+ }
+
+ this._headerElement.style.display = "none";
+ this._contentElement.style.display = "none";
+
+ this._messageElement.textContent = gStrings.GetStringFromName("aboutReader.loading2");
+ this._messageElement.style.display = "block";
+ }, 300);
+ },
+
+ /**
+ * Returns the original article URL for this about:reader view.
+ */
+ _getOriginalUrl(win) {
+ let url = win ? win.location.href : this._win.location.href;
+ return ReaderMode.getOriginalUrl(url) || url;
+ },
+
+ _setupSegmentedButton(id, options, initialValue, callback) {
+ let doc = this._doc;
+ let segmentedButton = doc.getElementsByClassName(id)[0];
+
+ for (let i = 0; i < options.length; i++) {
+ let option = options[i];
+
+ let item = doc.createElement("button");
+
+ // Put the name in a div so that it can be hidden if desired.
+ let div = doc.createElement("div");
+ div.textContent = option.name;
+ div.classList.add("name");
+ item.appendChild(div);
+
+ if (option.itemClass !== undefined)
+ item.classList.add(option.itemClass);
+
+ if (option.description !== undefined) {
+ let description = doc.createElement("div");
+ description.textContent = option.description;
+ description.classList.add("description");
+ item.appendChild(description);
+ }
+
+ segmentedButton.appendChild(item);
+
+ item.addEventListener("click", function(aEvent) {
+ if (!aEvent.isTrusted)
+ return;
+
+ aEvent.stopPropagation();
+
+ let items = segmentedButton.children;
+ for (let j = items.length - 1; j >= 0; j--) {
+ items[j].classList.remove("selected");
+ }
+
+ item.classList.add("selected");
+ callback(option.value);
+ }, true);
+
+ if (option.value === initialValue)
+ item.classList.add("selected");
+ }
+ },
+
+ _setupButton(id, callback, titleEntity, textEntity) {
+ if (titleEntity) {
+ this._setButtonTip(id, titleEntity);
+ }
+
+ let button = this._doc.getElementsByClassName(id)[0];
+ if (textEntity) {
+ button.textContent = gStrings.GetStringFromName(textEntity);
+ }
+ button.removeAttribute("hidden");
+ button.addEventListener("click", function(aEvent) {
+ if (!aEvent.isTrusted)
+ return;
+
+ aEvent.stopPropagation();
+ let btn = aEvent.target;
+ callback(btn);
+ }, true);
+ },
+
+ /**
+ * Sets a toolTip for a button. Performed at initial button setup
+ * and dynamically as button state changes.
+ * @param Localizable string providing UI element usage tip.
+ */
+ _setButtonTip(id, titleEntity) {
+ let button = this._doc.getElementsByClassName(id)[0];
+ button.setAttribute("title", gStrings.GetStringFromName(titleEntity));
+ },
+
+ _setupStyleDropdown() {
+ let dropdownToggle = this._doc.querySelector(".style-dropdown .dropdown-toggle");
+ dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
+ },
+
+ _updatePopupPosition(dropdown) {
+ let dropdownToggle = dropdown.querySelector(".dropdown-toggle");
+ let dropdownPopup = dropdown.querySelector(".dropdown-popup");
+
+ let toggleHeight = dropdownToggle.offsetHeight;
+ let toggleTop = dropdownToggle.offsetTop;
+ let popupTop = toggleTop - toggleHeight / 2;
+
+ dropdownPopup.style.top = popupTop + "px";
+ },
+
+ _toggleDropdownClicked(event) {
+ let dropdown = event.target.closest(".dropdown");
+
+ if (!dropdown)
+ return;
+
+ event.stopPropagation();
+
+ if (dropdown.classList.contains("open")) {
+ this._closeDropdowns();
+ } else {
+ this._openDropdown(dropdown);
+ if (this._isToolbarVertical) {
+ this._updatePopupPosition(dropdown);
+ }
+ }
+ },
+
+ /*
+ * If the ReaderView banner font-dropdown is closed, open it.
+ */
+ _openDropdown(dropdown) {
+ if (dropdown.classList.contains("open")) {
+ return;
+ }
+
+ this._closeDropdowns();
+ dropdown.classList.add("open");
+ },
+
+ /*
+ * If the ReaderView has open dropdowns, close them. If we are closing the
+ * dropdowns because the page is scrolling, allow popups to stay open with
+ * the keep-open class.
+ */
+ _closeDropdowns(scrolling) {
+ let selector = ".dropdown.open";
+ if (scrolling) {
+ selector += ":not(.keep-open)";
+ }
+
+ let openDropdowns = this._doc.querySelectorAll(selector);
+ for (let dropdown of openDropdowns) {
+ dropdown.classList.remove("open");
+ }
+ },
+
+ /*
+ * Scroll reader view to a reference
+ */
+ _goToReference(ref) {
+ if (ref) {
+ this._win.location.hash = ref;
+ }
+ }
+};
diff --git a/components/reader/JSDOMParser.js b/components/reader/JSDOMParser.js
new file mode 100644
index 000000000..2d3d6f156
--- /dev/null
+++ b/components/reader/JSDOMParser.js
@@ -0,0 +1,1196 @@
+/*eslint-env es6:false*/
+/*
+ * DO NOT MODIFY THIS FILE DIRECTLY!
+ *
+ * This is a shared library that is maintained in an external repo:
+ * https://github.com/mozilla/readability
+ */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 is a relatively lightweight DOMParser that is safe to use in a web
+ * worker. This is far from a complete DOM implementation; however, it should
+ * contain the minimal set of functionality necessary for Readability.js.
+ *
+ * Aside from not implementing the full DOM API, there are other quirks to be
+ * aware of when using the JSDOMParser:
+ *
+ * 1) Properly formed HTML/XML must be used. This means you should be extra
+ * careful when using this parser on anything received directly from an
+ * XMLHttpRequest. Providing a serialized string from an XMLSerializer,
+ * however, should be safe (since the browser's XMLSerializer should
+ * generate valid HTML/XML). Therefore, if parsing a document from an XHR,
+ * the recommended approach is to do the XHR in the main thread, use
+ * XMLSerializer.serializeToString() on the responseXML, and pass the
+ * resulting string to the worker.
+ *
+ * 2) Live NodeLists are not supported. DOM methods and properties such as
+ * getElementsByTagName() and childNodes return standard arrays. If you
+ * want these lists to be updated when nodes are removed or added to the
+ * document, you must take care to manually update them yourself.
+ */
+(function (global) {
+
+ // XML only defines these and the numeric ones:
+
+ var entityTable = {
+ "lt": "<",
+ "gt": ">",
+ "amp": "&",
+ "quot": '"',
+ "apos": "'",
+ };
+
+ var reverseEntityTable = {
+ "<": "&lt;",
+ ">": "&gt;",
+ "&": "&amp;",
+ '"': "&quot;",
+ "'": "&apos;",
+ };
+
+ function encodeTextContentHTML(s) {
+ return s.replace(/[&<>]/g, function(x) {
+ return reverseEntityTable[x];
+ });
+ }
+
+ function encodeHTML(s) {
+ return s.replace(/[&<>'"]/g, function(x) {
+ return reverseEntityTable[x];
+ });
+ }
+
+ function decodeHTML(str) {
+ return str.replace(/&(quot|amp|apos|lt|gt);/g, function(match, tag) {
+ return entityTable[tag];
+ }).replace(/&#(?:x([0-9a-z]{1,4})|([0-9]{1,4}));/gi, function(match, hex, numStr) {
+ var num = parseInt(hex || numStr, hex ? 16 : 10); // read num
+ return String.fromCharCode(num);
+ });
+ }
+
+ // When a style is set in JS, map it to the corresponding CSS attribute
+ var styleMap = {
+ "alignmentBaseline": "alignment-baseline",
+ "background": "background",
+ "backgroundAttachment": "background-attachment",
+ "backgroundClip": "background-clip",
+ "backgroundColor": "background-color",
+ "backgroundImage": "background-image",
+ "backgroundOrigin": "background-origin",
+ "backgroundPosition": "background-position",
+ "backgroundPositionX": "background-position-x",
+ "backgroundPositionY": "background-position-y",
+ "backgroundRepeat": "background-repeat",
+ "backgroundRepeatX": "background-repeat-x",
+ "backgroundRepeatY": "background-repeat-y",
+ "backgroundSize": "background-size",
+ "baselineShift": "baseline-shift",
+ "border": "border",
+ "borderBottom": "border-bottom",
+ "borderBottomColor": "border-bottom-color",
+ "borderBottomLeftRadius": "border-bottom-left-radius",
+ "borderBottomRightRadius": "border-bottom-right-radius",
+ "borderBottomStyle": "border-bottom-style",
+ "borderBottomWidth": "border-bottom-width",
+ "borderCollapse": "border-collapse",
+ "borderColor": "border-color",
+ "borderImage": "border-image",
+ "borderImageOutset": "border-image-outset",
+ "borderImageRepeat": "border-image-repeat",
+ "borderImageSlice": "border-image-slice",
+ "borderImageSource": "border-image-source",
+ "borderImageWidth": "border-image-width",
+ "borderLeft": "border-left",
+ "borderLeftColor": "border-left-color",
+ "borderLeftStyle": "border-left-style",
+ "borderLeftWidth": "border-left-width",
+ "borderRadius": "border-radius",
+ "borderRight": "border-right",
+ "borderRightColor": "border-right-color",
+ "borderRightStyle": "border-right-style",
+ "borderRightWidth": "border-right-width",
+ "borderSpacing": "border-spacing",
+ "borderStyle": "border-style",
+ "borderTop": "border-top",
+ "borderTopColor": "border-top-color",
+ "borderTopLeftRadius": "border-top-left-radius",
+ "borderTopRightRadius": "border-top-right-radius",
+ "borderTopStyle": "border-top-style",
+ "borderTopWidth": "border-top-width",
+ "borderWidth": "border-width",
+ "bottom": "bottom",
+ "boxShadow": "box-shadow",
+ "boxSizing": "box-sizing",
+ "captionSide": "caption-side",
+ "clear": "clear",
+ "clip": "clip",
+ "clipPath": "clip-path",
+ "clipRule": "clip-rule",
+ "color": "color",
+ "colorInterpolation": "color-interpolation",
+ "colorInterpolationFilters": "color-interpolation-filters",
+ "colorProfile": "color-profile",
+ "colorRendering": "color-rendering",
+ "content": "content",
+ "counterIncrement": "counter-increment",
+ "counterReset": "counter-reset",
+ "cursor": "cursor",
+ "direction": "direction",
+ "display": "display",
+ "dominantBaseline": "dominant-baseline",
+ "emptyCells": "empty-cells",
+ "enableBackground": "enable-background",
+ "fill": "fill",
+ "fillOpacity": "fill-opacity",
+ "fillRule": "fill-rule",
+ "filter": "filter",
+ "cssFloat": "float",
+ "floodColor": "flood-color",
+ "floodOpacity": "flood-opacity",
+ "font": "font",
+ "fontFamily": "font-family",
+ "fontSize": "font-size",
+ "fontStretch": "font-stretch",
+ "fontStyle": "font-style",
+ "fontVariant": "font-variant",
+ "fontWeight": "font-weight",
+ "glyphOrientationHorizontal": "glyph-orientation-horizontal",
+ "glyphOrientationVertical": "glyph-orientation-vertical",
+ "height": "height",
+ "imageRendering": "image-rendering",
+ "kerning": "kerning",
+ "left": "left",
+ "letterSpacing": "letter-spacing",
+ "lightingColor": "lighting-color",
+ "lineHeight": "line-height",
+ "listStyle": "list-style",
+ "listStyleImage": "list-style-image",
+ "listStylePosition": "list-style-position",
+ "listStyleType": "list-style-type",
+ "margin": "margin",
+ "marginBottom": "margin-bottom",
+ "marginLeft": "margin-left",
+ "marginRight": "margin-right",
+ "marginTop": "margin-top",
+ "marker": "marker",
+ "markerEnd": "marker-end",
+ "markerMid": "marker-mid",
+ "markerStart": "marker-start",
+ "mask": "mask",
+ "maxHeight": "max-height",
+ "maxWidth": "max-width",
+ "minHeight": "min-height",
+ "minWidth": "min-width",
+ "opacity": "opacity",
+ "orphans": "orphans",
+ "outline": "outline",
+ "outlineColor": "outline-color",
+ "outlineOffset": "outline-offset",
+ "outlineStyle": "outline-style",
+ "outlineWidth": "outline-width",
+ "overflow": "overflow",
+ "overflowX": "overflow-x",
+ "overflowY": "overflow-y",
+ "padding": "padding",
+ "paddingBottom": "padding-bottom",
+ "paddingLeft": "padding-left",
+ "paddingRight": "padding-right",
+ "paddingTop": "padding-top",
+ "page": "page",
+ "pageBreakAfter": "page-break-after",
+ "pageBreakBefore": "page-break-before",
+ "pageBreakInside": "page-break-inside",
+ "pointerEvents": "pointer-events",
+ "position": "position",
+ "quotes": "quotes",
+ "resize": "resize",
+ "right": "right",
+ "shapeRendering": "shape-rendering",
+ "size": "size",
+ "speak": "speak",
+ "src": "src",
+ "stopColor": "stop-color",
+ "stopOpacity": "stop-opacity",
+ "stroke": "stroke",
+ "strokeDasharray": "stroke-dasharray",
+ "strokeDashoffset": "stroke-dashoffset",
+ "strokeLinecap": "stroke-linecap",
+ "strokeLinejoin": "stroke-linejoin",
+ "strokeMiterlimit": "stroke-miterlimit",
+ "strokeOpacity": "stroke-opacity",
+ "strokeWidth": "stroke-width",
+ "tableLayout": "table-layout",
+ "textAlign": "text-align",
+ "textAnchor": "text-anchor",
+ "textDecoration": "text-decoration",
+ "textIndent": "text-indent",
+ "textLineThrough": "text-line-through",
+ "textLineThroughColor": "text-line-through-color",
+ "textLineThroughMode": "text-line-through-mode",
+ "textLineThroughStyle": "text-line-through-style",
+ "textLineThroughWidth": "text-line-through-width",
+ "textOverflow": "text-overflow",
+ "textOverline": "text-overline",
+ "textOverlineColor": "text-overline-color",
+ "textOverlineMode": "text-overline-mode",
+ "textOverlineStyle": "text-overline-style",
+ "textOverlineWidth": "text-overline-width",
+ "textRendering": "text-rendering",
+ "textShadow": "text-shadow",
+ "textTransform": "text-transform",
+ "textUnderline": "text-underline",
+ "textUnderlineColor": "text-underline-color",
+ "textUnderlineMode": "text-underline-mode",
+ "textUnderlineStyle": "text-underline-style",
+ "textUnderlineWidth": "text-underline-width",
+ "top": "top",
+ "unicodeBidi": "unicode-bidi",
+ "unicodeRange": "unicode-range",
+ "vectorEffect": "vector-effect",
+ "verticalAlign": "vertical-align",
+ "visibility": "visibility",
+ "whiteSpace": "white-space",
+ "widows": "widows",
+ "width": "width",
+ "wordBreak": "word-break",
+ "wordSpacing": "word-spacing",
+ "wordWrap": "word-wrap",
+ "writingMode": "writing-mode",
+ "zIndex": "z-index",
+ "zoom": "zoom",
+ };
+
+ // Elements that can be self-closing
+ var voidElems = {
+ "area": true,
+ "base": true,
+ "br": true,
+ "col": true,
+ "command": true,
+ "embed": true,
+ "hr": true,
+ "img": true,
+ "input": true,
+ "link": true,
+ "meta": true,
+ "param": true,
+ "source": true,
+ "wbr": true
+ };
+
+ var whitespace = [" ", "\t", "\n", "\r"];
+
+ // See http://www.w3schools.com/dom/dom_nodetype.asp
+ var nodeTypes = {
+ ELEMENT_NODE: 1,
+ ATTRIBUTE_NODE: 2,
+ TEXT_NODE: 3,
+ CDATA_SECTION_NODE: 4,
+ ENTITY_REFERENCE_NODE: 5,
+ ENTITY_NODE: 6,
+ PROCESSING_INSTRUCTION_NODE: 7,
+ COMMENT_NODE: 8,
+ DOCUMENT_NODE: 9,
+ DOCUMENT_TYPE_NODE: 10,
+ DOCUMENT_FRAGMENT_NODE: 11,
+ NOTATION_NODE: 12
+ };
+
+ function getElementsByTagName(tag) {
+ tag = tag.toUpperCase();
+ var elems = [];
+ var allTags = (tag === "*");
+ function getElems(node) {
+ var length = node.children.length;
+ for (var i = 0; i < length; i++) {
+ var child = node.children[i];
+ if (allTags || (child.tagName === tag))
+ elems.push(child);
+ getElems(child);
+ }
+ }
+ getElems(this);
+ elems._isLiveNodeList = true;
+ return elems;
+ }
+
+ var Node = function () {};
+
+ Node.prototype = {
+ attributes: null,
+ childNodes: null,
+ localName: null,
+ nodeName: null,
+ parentNode: null,
+ textContent: null,
+ nextSibling: null,
+ previousSibling: null,
+
+ get firstChild() {
+ return this.childNodes[0] || null;
+ },
+
+ get firstElementChild() {
+ return this.children[0] || null;
+ },
+
+ get lastChild() {
+ return this.childNodes[this.childNodes.length - 1] || null;
+ },
+
+ get lastElementChild() {
+ return this.children[this.children.length - 1] || null;
+ },
+
+ appendChild: function (child) {
+ if (child.parentNode) {
+ child.parentNode.removeChild(child);
+ }
+
+ var last = this.lastChild;
+ if (last)
+ last.nextSibling = child;
+ child.previousSibling = last;
+
+ if (child.nodeType === Node.ELEMENT_NODE) {
+ child.previousElementSibling = this.children[this.children.length - 1] || null;
+ this.children.push(child);
+ child.previousElementSibling && (child.previousElementSibling.nextElementSibling = child);
+ }
+ this.childNodes.push(child);
+ child.parentNode = this;
+ },
+
+ removeChild: function (child) {
+ var childNodes = this.childNodes;
+ var childIndex = childNodes.indexOf(child);
+ if (childIndex === -1) {
+ throw "removeChild: node not found";
+ } else {
+ child.parentNode = null;
+ var prev = child.previousSibling;
+ var next = child.nextSibling;
+ if (prev)
+ prev.nextSibling = next;
+ if (next)
+ next.previousSibling = prev;
+
+ if (child.nodeType === Node.ELEMENT_NODE) {
+ prev = child.previousElementSibling;
+ next = child.nextElementSibling;
+ if (prev)
+ prev.nextElementSibling = next;
+ if (next)
+ next.previousElementSibling = prev;
+ this.children.splice(this.children.indexOf(child), 1);
+ }
+
+ child.previousSibling = child.nextSibling = null;
+ child.previousElementSibling = child.nextElementSibling = null;
+
+ return childNodes.splice(childIndex, 1)[0];
+ }
+ },
+
+ replaceChild: function (newNode, oldNode) {
+ var childNodes = this.childNodes;
+ var childIndex = childNodes.indexOf(oldNode);
+ if (childIndex === -1) {
+ throw "replaceChild: node not found";
+ } else {
+ // This will take care of updating the new node if it was somewhere else before:
+ if (newNode.parentNode)
+ newNode.parentNode.removeChild(newNode);
+
+ childNodes[childIndex] = newNode;
+
+ // update the new node's sibling properties, and its new siblings' sibling properties
+ newNode.nextSibling = oldNode.nextSibling;
+ newNode.previousSibling = oldNode.previousSibling;
+ if (newNode.nextSibling)
+ newNode.nextSibling.previousSibling = newNode;
+ if (newNode.previousSibling)
+ newNode.previousSibling.nextSibling = newNode;
+
+ newNode.parentNode = this;
+
+ // Now deal with elements before we clear out those values for the old node,
+ // because it can help us take shortcuts here:
+ if (newNode.nodeType === Node.ELEMENT_NODE) {
+ if (oldNode.nodeType === Node.ELEMENT_NODE) {
+ // Both were elements, which makes this easier, we just swap things out:
+ newNode.previousElementSibling = oldNode.previousElementSibling;
+ newNode.nextElementSibling = oldNode.nextElementSibling;
+ if (newNode.previousElementSibling)
+ newNode.previousElementSibling.nextElementSibling = newNode;
+ if (newNode.nextElementSibling)
+ newNode.nextElementSibling.previousElementSibling = newNode;
+ this.children[this.children.indexOf(oldNode)] = newNode;
+ } else {
+ // Hard way:
+ newNode.previousElementSibling = (function() {
+ for (var i = childIndex - 1; i >= 0; i--) {
+ if (childNodes[i].nodeType === Node.ELEMENT_NODE)
+ return childNodes[i];
+ }
+ return null;
+ })();
+ if (newNode.previousElementSibling) {
+ newNode.nextElementSibling = newNode.previousElementSibling.nextElementSibling;
+ } else {
+ newNode.nextElementSibling = (function() {
+ for (var i = childIndex + 1; i < childNodes.length; i++) {
+ if (childNodes[i].nodeType === Node.ELEMENT_NODE)
+ return childNodes[i];
+ }
+ return null;
+ })();
+ }
+ if (newNode.previousElementSibling)
+ newNode.previousElementSibling.nextElementSibling = newNode;
+ if (newNode.nextElementSibling)
+ newNode.nextElementSibling.previousElementSibling = newNode;
+
+ if (newNode.nextElementSibling)
+ this.children.splice(this.children.indexOf(newNode.nextElementSibling), 0, newNode);
+ else
+ this.children.push(newNode);
+ }
+ } else if (oldNode.nodeType === Node.ELEMENT_NODE) {
+ // new node is not an element node.
+ // if the old one was, update its element siblings:
+ if (oldNode.previousElementSibling)
+ oldNode.previousElementSibling.nextElementSibling = oldNode.nextElementSibling;
+ if (oldNode.nextElementSibling)
+ oldNode.nextElementSibling.previousElementSibling = oldNode.previousElementSibling;
+ this.children.splice(this.children.indexOf(oldNode), 1);
+
+ // If the old node wasn't an element, neither the new nor the old node was an element,
+ // and the children array and its members shouldn't need any updating.
+ }
+
+
+ oldNode.parentNode = null;
+ oldNode.previousSibling = null;
+ oldNode.nextSibling = null;
+ if (oldNode.nodeType === Node.ELEMENT_NODE) {
+ oldNode.previousElementSibling = null;
+ oldNode.nextElementSibling = null;
+ }
+ return oldNode;
+ }
+ },
+
+ __JSDOMParser__: true,
+ };
+
+ for (var nodeType in nodeTypes) {
+ Node[nodeType] = Node.prototype[nodeType] = nodeTypes[nodeType];
+ }
+
+ var Attribute = function (name, value) {
+ this.name = name;
+ this._value = value;
+ };
+
+ Attribute.prototype = {
+ get value() {
+ return this._value;
+ },
+ setValue: function(newValue) {
+ this._value = newValue;
+ },
+ getEncodedValue: function() {
+ return encodeHTML(this._value);
+ },
+ };
+
+ var Comment = function () {
+ this.childNodes = [];
+ };
+
+ Comment.prototype = {
+ __proto__: Node.prototype,
+
+ nodeName: "#comment",
+ nodeType: Node.COMMENT_NODE
+ };
+
+ var Text = function () {
+ this.childNodes = [];
+ };
+
+ Text.prototype = {
+ __proto__: Node.prototype,
+
+ nodeName: "#text",
+ nodeType: Node.TEXT_NODE,
+ get textContent() {
+ if (typeof this._textContent === "undefined") {
+ this._textContent = decodeHTML(this._innerHTML || "");
+ }
+ return this._textContent;
+ },
+ get innerHTML() {
+ if (typeof this._innerHTML === "undefined") {
+ this._innerHTML = encodeTextContentHTML(this._textContent || "");
+ }
+ return this._innerHTML;
+ },
+
+ set innerHTML(newHTML) {
+ this._innerHTML = newHTML;
+ delete this._textContent;
+ },
+ set textContent(newText) {
+ this._textContent = newText;
+ delete this._innerHTML;
+ },
+ };
+
+ var Document = function (url) {
+ this.documentURI = url;
+ this.styleSheets = [];
+ this.childNodes = [];
+ this.children = [];
+ };
+
+ Document.prototype = {
+ __proto__: Node.prototype,
+
+ nodeName: "#document",
+ nodeType: Node.DOCUMENT_NODE,
+ title: "",
+
+ getElementsByTagName: getElementsByTagName,
+
+ getElementById: function (id) {
+ function getElem(node) {
+ var length = node.children.length;
+ if (node.id === id)
+ return node;
+ for (var i = 0; i < length; i++) {
+ var el = getElem(node.children[i]);
+ if (el)
+ return el;
+ }
+ return null;
+ }
+ return getElem(this);
+ },
+
+ createElement: function (tag) {
+ var node = new Element(tag);
+ return node;
+ },
+
+ createTextNode: function (text) {
+ var node = new Text();
+ node.textContent = text;
+ return node;
+ },
+
+ get baseURI() {
+ if (!this.hasOwnProperty("_baseURI")) {
+ this._baseURI = this.documentURI;
+ var baseElements = this.getElementsByTagName("base");
+ var href = baseElements[0] && baseElements[0].getAttribute("href");
+ if (href) {
+ try {
+ this._baseURI = (new URL(href, this._baseURI)).href;
+ } catch (ex) {/* Just fall back to documentURI */}
+ }
+ }
+ return this._baseURI;
+ },
+ };
+
+ var Element = function (tag) {
+ // We use this to find the closing tag.
+ this._matchingTag = tag;
+ // We're explicitly a non-namespace aware parser, we just pretend it's all HTML.
+ var lastColonIndex = tag.lastIndexOf(":");
+ if (lastColonIndex != -1) {
+ tag = tag.substring(lastColonIndex + 1);
+ }
+ this.attributes = [];
+ this.childNodes = [];
+ this.children = [];
+ this.nextElementSibling = this.previousElementSibling = null;
+ this.localName = tag.toLowerCase();
+ this.tagName = tag.toUpperCase();
+ this.style = new Style(this);
+ };
+
+ Element.prototype = {
+ __proto__: Node.prototype,
+
+ nodeType: Node.ELEMENT_NODE,
+
+ getElementsByTagName: getElementsByTagName,
+
+ get className() {
+ return this.getAttribute("class") || "";
+ },
+
+ set className(str) {
+ this.setAttribute("class", str);
+ },
+
+ get id() {
+ return this.getAttribute("id") || "";
+ },
+
+ set id(str) {
+ this.setAttribute("id", str);
+ },
+
+ get href() {
+ return this.getAttribute("href") || "";
+ },
+
+ set href(str) {
+ this.setAttribute("href", str);
+ },
+
+ get src() {
+ return this.getAttribute("src") || "";
+ },
+
+ set src(str) {
+ this.setAttribute("src", str);
+ },
+
+ get srcset() {
+ return this.getAttribute("srcset") || "";
+ },
+
+ set srcset(str) {
+ this.setAttribute("srcset", str);
+ },
+
+ get nodeName() {
+ return this.tagName;
+ },
+
+ get innerHTML() {
+ function getHTML(node) {
+ var i = 0;
+ for (i = 0; i < node.childNodes.length; i++) {
+ var child = node.childNodes[i];
+ if (child.localName) {
+ arr.push("<" + child.localName);
+
+ // serialize attribute list
+ for (var j = 0; j < child.attributes.length; j++) {
+ var attr = child.attributes[j];
+ // the attribute value will be HTML escaped.
+ var val = attr.getEncodedValue();
+ var quote = (val.indexOf('"') === -1 ? '"' : "'");
+ arr.push(" " + attr.name + "=" + quote + val + quote);
+ }
+
+ if (child.localName in voidElems && !child.childNodes.length) {
+ // if this is a self-closing element, end it here
+ arr.push("/>");
+ } else {
+ // otherwise, add its children
+ arr.push(">");
+ getHTML(child);
+ arr.push("</" + child.localName + ">");
+ }
+ } else {
+ // This is a text node, so asking for innerHTML won't recurse.
+ arr.push(child.innerHTML);
+ }
+ }
+ }
+
+ // Using Array.join() avoids the overhead from lazy string concatenation.
+ // See http://blog.cdleary.com/2012/01/string-representation-in-spidermonkey/#ropes
+ var arr = [];
+ getHTML(this);
+ return arr.join("");
+ },
+
+ set innerHTML(html) {
+ var parser = new JSDOMParser();
+ var node = parser.parse(html);
+ var i;
+ for (i = this.childNodes.length; --i >= 0;) {
+ this.childNodes[i].parentNode = null;
+ }
+ this.childNodes = node.childNodes;
+ this.children = node.children;
+ for (i = this.childNodes.length; --i >= 0;) {
+ this.childNodes[i].parentNode = this;
+ }
+ },
+
+ set textContent(text) {
+ // clear parentNodes for existing children
+ for (var i = this.childNodes.length; --i >= 0;) {
+ this.childNodes[i].parentNode = null;
+ }
+
+ var node = new Text();
+ this.childNodes = [ node ];
+ this.children = [];
+ node.textContent = text;
+ node.parentNode = this;
+ },
+
+ get textContent() {
+ function getText(node) {
+ var nodes = node.childNodes;
+ for (var i = 0; i < nodes.length; i++) {
+ var child = nodes[i];
+ if (child.nodeType === 3) {
+ text.push(child.textContent);
+ } else {
+ getText(child);
+ }
+ }
+ }
+
+ // Using Array.join() avoids the overhead from lazy string concatenation.
+ // See http://blog.cdleary.com/2012/01/string-representation-in-spidermonkey/#ropes
+ var text = [];
+ getText(this);
+ return text.join("");
+ },
+
+ getAttribute: function (name) {
+ for (var i = this.attributes.length; --i >= 0;) {
+ var attr = this.attributes[i];
+ if (attr.name === name) {
+ return attr.value;
+ }
+ }
+ return undefined;
+ },
+
+ setAttribute: function (name, value) {
+ for (var i = this.attributes.length; --i >= 0;) {
+ var attr = this.attributes[i];
+ if (attr.name === name) {
+ attr.setValue(value);
+ return;
+ }
+ }
+ this.attributes.push(new Attribute(name, value));
+ },
+
+ removeAttribute: function (name) {
+ for (var i = this.attributes.length; --i >= 0;) {
+ var attr = this.attributes[i];
+ if (attr.name === name) {
+ this.attributes.splice(i, 1);
+ break;
+ }
+ }
+ },
+
+ hasAttribute: function (name) {
+ return this.attributes.some(function (attr) {
+ return attr.name == name;
+ });
+ },
+ };
+
+ var Style = function (node) {
+ this.node = node;
+ };
+
+ // getStyle() and setStyle() use the style attribute string directly. This
+ // won't be very efficient if there are a lot of style manipulations, but
+ // it's the easiest way to make sure the style attribute string and the JS
+ // style property stay in sync. Readability.js doesn't do many style
+ // manipulations, so this should be okay.
+ Style.prototype = {
+ getStyle: function (styleName) {
+ var attr = this.node.getAttribute("style");
+ if (!attr)
+ return undefined;
+
+ var styles = attr.split(";");
+ for (var i = 0; i < styles.length; i++) {
+ var style = styles[i].split(":");
+ var name = style[0].trim();
+ if (name === styleName)
+ return style[1].trim();
+ }
+
+ return undefined;
+ },
+
+ setStyle: function (styleName, styleValue) {
+ var value = this.node.getAttribute("style") || "";
+ var index = 0;
+ do {
+ var next = value.indexOf(";", index) + 1;
+ var length = next - index - 1;
+ var style = (length > 0 ? value.substr(index, length) : value.substr(index));
+ if (style.substr(0, style.indexOf(":")).trim() === styleName) {
+ value = value.substr(0, index).trim() + (next ? " " + value.substr(next).trim() : "");
+ break;
+ }
+ index = next;
+ } while (index);
+
+ value += " " + styleName + ": " + styleValue + ";";
+ this.node.setAttribute("style", value.trim());
+ }
+ };
+
+ // For each item in styleMap, define a getter and setter on the style
+ // property.
+ for (var jsName in styleMap) {
+ (function (cssName) {
+ Style.prototype.__defineGetter__(jsName, function () {
+ return this.getStyle(cssName);
+ });
+ Style.prototype.__defineSetter__(jsName, function (value) {
+ this.setStyle(cssName, value);
+ });
+ })(styleMap[jsName]);
+ }
+
+ var JSDOMParser = function () {
+ this.currentChar = 0;
+
+ // In makeElementNode() we build up many strings one char at a time. Using
+ // += for this results in lots of short-lived intermediate strings. It's
+ // better to build an array of single-char strings and then join() them
+ // together at the end. And reusing a single array (i.e. |this.strBuf|)
+ // over and over for this purpose uses less memory than using a new array
+ // for each string.
+ this.strBuf = [];
+
+ // Similarly, we reuse this array to return the two arguments from
+ // makeElementNode(), which saves us from having to allocate a new array
+ // every time.
+ this.retPair = [];
+
+ this.errorState = "";
+ };
+
+ JSDOMParser.prototype = {
+ error: function(m) {
+ dump("JSDOMParser error: " + m + "\n");
+ this.errorState += m + "\n";
+ },
+
+ /**
+ * Look at the next character without advancing the index.
+ */
+ peekNext: function () {
+ return this.html[this.currentChar];
+ },
+
+ /**
+ * Get the next character and advance the index.
+ */
+ nextChar: function () {
+ return this.html[this.currentChar++];
+ },
+
+ /**
+ * Called after a quote character is read. This finds the next quote
+ * character and returns the text string in between.
+ */
+ readString: function (quote) {
+ var str;
+ var n = this.html.indexOf(quote, this.currentChar);
+ if (n === -1) {
+ this.currentChar = this.html.length;
+ str = null;
+ } else {
+ str = this.html.substring(this.currentChar, n);
+ this.currentChar = n + 1;
+ }
+
+ return str;
+ },
+
+ /**
+ * Called when parsing a node. This finds the next name/value attribute
+ * pair and adds the result to the attributes list.
+ */
+ readAttribute: function (node) {
+ var name = "";
+
+ var n = this.html.indexOf("=", this.currentChar);
+ if (n === -1) {
+ this.currentChar = this.html.length;
+ } else {
+ // Read until a '=' character is hit; this will be the attribute key
+ name = this.html.substring(this.currentChar, n);
+ this.currentChar = n + 1;
+ }
+
+ if (!name)
+ return;
+
+ // After a '=', we should see a '"' for the attribute value
+ var c = this.nextChar();
+ if (c !== '"' && c !== "'") {
+ this.error("Error reading attribute " + name + ", expecting '\"'");
+ return;
+ }
+
+ // Read the attribute value (and consume the matching quote)
+ var value = this.readString(c);
+
+ node.attributes.push(new Attribute(name, decodeHTML(value)));
+
+ return;
+ },
+
+ /**
+ * Parses and returns an Element node. This is called after a '<' has been
+ * read.
+ *
+ * @returns an array; the first index of the array is the parsed node;
+ * the second index is a boolean indicating whether this is a void
+ * Element
+ */
+ makeElementNode: function (retPair) {
+ var c = this.nextChar();
+
+ // Read the Element tag name
+ var strBuf = this.strBuf;
+ strBuf.length = 0;
+ while (whitespace.indexOf(c) == -1 && c !== ">" && c !== "/") {
+ if (c === undefined)
+ return false;
+ strBuf.push(c);
+ c = this.nextChar();
+ }
+ var tag = strBuf.join("");
+
+ if (!tag)
+ return false;
+
+ var node = new Element(tag);
+
+ // Read Element attributes
+ while (c !== "/" && c !== ">") {
+ if (c === undefined)
+ return false;
+ while (whitespace.indexOf(this.html[this.currentChar++]) != -1) {
+ // Advance cursor to first non-whitespace char.
+ }
+ this.currentChar--;
+ c = this.nextChar();
+ if (c !== "/" && c !== ">") {
+ --this.currentChar;
+ this.readAttribute(node);
+ }
+ }
+
+ // If this is a self-closing tag, read '/>'
+ var closed = false;
+ if (c === "/") {
+ closed = true;
+ c = this.nextChar();
+ if (c !== ">") {
+ this.error("expected '>' to close " + tag);
+ return false;
+ }
+ }
+
+ retPair[0] = node;
+ retPair[1] = closed;
+ return true;
+ },
+
+ /**
+ * If the current input matches this string, advance the input index;
+ * otherwise, do nothing.
+ *
+ * @returns whether input matched string
+ */
+ match: function (str) {
+ var strlen = str.length;
+ if (this.html.substr(this.currentChar, strlen).toLowerCase() === str.toLowerCase()) {
+ this.currentChar += strlen;
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Searches the input until a string is found and discards all input up to
+ * and including the matched string.
+ */
+ discardTo: function (str) {
+ var index = this.html.indexOf(str, this.currentChar) + str.length;
+ if (index === -1)
+ this.currentChar = this.html.length;
+ this.currentChar = index;
+ },
+
+ /**
+ * Reads child nodes for the given node.
+ */
+ readChildren: function (node) {
+ var child;
+ while ((child = this.readNode())) {
+ // Don't keep Comment nodes
+ if (child.nodeType !== 8) {
+ node.appendChild(child);
+ }
+ }
+ },
+
+ discardNextComment: function() {
+ if (this.match("--")) {
+ this.discardTo("-->");
+ } else {
+ var c = this.nextChar();
+ while (c !== ">") {
+ if (c === undefined)
+ return null;
+ if (c === '"' || c === "'")
+ this.readString(c);
+ c = this.nextChar();
+ }
+ }
+ return new Comment();
+ },
+
+
+ /**
+ * Reads the next child node from the input. If we're reading a closing
+ * tag, or if we've reached the end of input, return null.
+ *
+ * @returns the node
+ */
+ readNode: function () {
+ var c = this.nextChar();
+
+ if (c === undefined)
+ return null;
+
+ // Read any text as Text node
+ var textNode;
+ if (c !== "<") {
+ --this.currentChar;
+ textNode = new Text();
+ var n = this.html.indexOf("<", this.currentChar);
+ if (n === -1) {
+ textNode.innerHTML = this.html.substring(this.currentChar, this.html.length);
+ this.currentChar = this.html.length;
+ } else {
+ textNode.innerHTML = this.html.substring(this.currentChar, n);
+ this.currentChar = n;
+ }
+ return textNode;
+ }
+
+ if (this.match("![CDATA[")) {
+ var endChar = this.html.indexOf("]]>", this.currentChar);
+ if (endChar === -1) {
+ this.error("unclosed CDATA section");
+ return null;
+ }
+ textNode = new Text();
+ textNode.textContent = this.html.substring(this.currentChar, endChar);
+ this.currentChar = endChar + ("]]>").length;
+ return textNode;
+ }
+
+ c = this.peekNext();
+
+ // Read Comment node. Normally, Comment nodes know their inner
+ // textContent, but we don't really care about Comment nodes (we throw
+ // them away in readChildren()). So just returning an empty Comment node
+ // here is sufficient.
+ if (c === "!" || c === "?") {
+ // We're still before the ! or ? that is starting this comment:
+ this.currentChar++;
+ return this.discardNextComment();
+ }
+
+ // If we're reading a closing tag, return null. This means we've reached
+ // the end of this set of child nodes.
+ if (c === "/") {
+ --this.currentChar;
+ return null;
+ }
+
+ // Otherwise, we're looking at an Element node
+ var result = this.makeElementNode(this.retPair);
+ if (!result)
+ return null;
+
+ var node = this.retPair[0];
+ var closed = this.retPair[1];
+ var localName = node.localName;
+
+ // If this isn't a void Element, read its child nodes
+ if (!closed) {
+ this.readChildren(node);
+ var closingTag = "</" + node._matchingTag + ">";
+ if (!this.match(closingTag)) {
+ this.error("expected '" + closingTag + "' and got " + this.html.substr(this.currentChar, closingTag.length));
+ return null;
+ }
+ }
+
+ // Only use the first title, because SVG might have other
+ // title elements which we don't care about (medium.com
+ // does this, at least).
+ if (localName === "title" && !this.doc.title) {
+ this.doc.title = node.textContent.trim();
+ } else if (localName === "head") {
+ this.doc.head = node;
+ } else if (localName === "body") {
+ this.doc.body = node;
+ } else if (localName === "html") {
+ this.doc.documentElement = node;
+ }
+
+ return node;
+ },
+
+ /**
+ * Parses an HTML string and returns a JS implementation of the Document.
+ */
+ parse: function (html, url) {
+ this.html = html;
+ var doc = this.doc = new Document(url);
+ this.readChildren(doc);
+
+ // If this is an HTML document, remove root-level children except for the
+ // <html> node
+ if (doc.documentElement) {
+ for (var i = doc.childNodes.length; --i >= 0;) {
+ var child = doc.childNodes[i];
+ if (child !== doc.documentElement) {
+ doc.removeChild(child);
+ }
+ }
+ }
+
+ return doc;
+ }
+ };
+
+ // Attach the standard DOM types to the global scope
+ global.Node = Node;
+ global.Comment = Comment;
+ global.Document = Document;
+ global.Element = Element;
+ global.Text = Text;
+
+ // Attach JSDOMParser to the global scope
+ global.JSDOMParser = JSDOMParser;
+
+})(this);
diff --git a/components/reader/Readability-readerable.js b/components/reader/Readability-readerable.js
new file mode 100644
index 000000000..839d9fbf7
--- /dev/null
+++ b/components/reader/Readability-readerable.js
@@ -0,0 +1,107 @@
+/* eslint-env es6:false */
+/* globals exports */
+/*
+ * DO NOT MODIFY THIS FILE DIRECTLY!
+ *
+ * This is a shared library that is maintained in an external repo:
+ * https://github.com/mozilla/readability
+ */
+
+/*
+ * Copyright (c) 2010 Arc90 Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This code is heavily based on Arc90's readability.js (1.7.1) script
+ * available at: http://code.google.com/p/arc90labs-readability
+ */
+
+var REGEXPS = {
+ // NOTE: These two regular expressions are duplicated in
+ // Readability.js. Please keep both copies in sync.
+ unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
+ okMaybeItsACandidate: /and|article|body|column|content|main|shadow/i,
+};
+
+function isNodeVisible(node) {
+ // Have to null-check node.style and node.className.indexOf to deal with SVG and MathML nodes.
+ return (!node.style || node.style.display != "none")
+ && !node.hasAttribute("hidden")
+ //check for "fallback-image" so that wikimedia math images are displayed
+ && (!node.hasAttribute("aria-hidden") || node.getAttribute("aria-hidden") != "true" || (node.className && node.className.indexOf && node.className.indexOf("fallback-image") !== -1));
+}
+
+/**
+ * Decides whether or not the document is reader-able without parsing the whole thing.
+ *
+ * @return boolean Whether or not we suspect Readability.parse() will suceeed at returning an article object.
+ */
+function isProbablyReaderable(doc, isVisible) {
+ if (!isVisible) {
+ isVisible = isNodeVisible;
+ }
+
+ var nodes = doc.querySelectorAll("p, pre");
+
+ // Get <div> nodes which have <br> node(s) and append them into the `nodes` variable.
+ // Some articles' DOM structures might look like
+ // <div>
+ // Sentences<br>
+ // <br>
+ // Sentences<br>
+ // </div>
+ var brNodes = doc.querySelectorAll("div > br");
+ if (brNodes.length) {
+ var set = new Set(nodes);
+ [].forEach.call(brNodes, function(node) {
+ set.add(node.parentNode);
+ });
+ nodes = Array.from(set);
+ }
+
+ var score = 0;
+ // This is a little cheeky, we use the accumulator 'score' to decide what to return from
+ // this callback:
+ return [].some.call(nodes, function(node) {
+ if (!isVisible(node))
+ return false;
+
+ var matchString = node.className + " " + node.id;
+ if (REGEXPS.unlikelyCandidates.test(matchString) &&
+ !REGEXPS.okMaybeItsACandidate.test(matchString)) {
+ return false;
+ }
+
+ if (node.matches("li p")) {
+ return false;
+ }
+
+ var textContentLength = node.textContent.trim().length;
+ if (textContentLength < 140) {
+ return false;
+ }
+
+ score += Math.sqrt(textContentLength - 140);
+
+ if (score > 20) {
+ return true;
+ }
+ return false;
+ });
+}
+
+if (typeof exports === "object") {
+ exports.isProbablyReaderable = isProbablyReaderable;
+}
diff --git a/components/reader/Readability.js b/components/reader/Readability.js
new file mode 100644
index 000000000..4a3689885
--- /dev/null
+++ b/components/reader/Readability.js
@@ -0,0 +1,2078 @@
+/*eslint-env es6:false*/
+/*
+ * DO NOT MODIFY THIS FILE DIRECTLY!
+ *
+ * This is a shared library that is maintained in an external repo:
+ * https://github.com/mozilla/readability
+ */
+
+/*
+ * Copyright (c) 2010 Arc90 Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This code is heavily based on Arc90's readability.js (1.7.1) script
+ * available at: http://code.google.com/p/arc90labs-readability
+ */
+
+/**
+ * Public constructor.
+ * @param {HTMLDocument} doc The document to parse.
+ * @param {Object} options The options object.
+ */
+function Readability(doc, options) {
+ // In some older versions, people passed a URI as the first argument. Cope:
+ if (options && options.documentElement) {
+ doc = options;
+ options = arguments[2];
+ } else if (!doc || !doc.documentElement) {
+ throw new Error("First argument to Readability constructor should be a document object.");
+ }
+ options = options || {};
+
+ this._doc = doc;
+ this._docJSDOMParser = this._doc.firstChild.__JSDOMParser__;
+ this._articleTitle = null;
+ this._articleByline = null;
+ this._articleDir = null;
+ this._articleSiteName = null;
+ this._attempts = [];
+
+ // Configurable options
+ this._debug = !!options.debug;
+ this._maxElemsToParse = options.maxElemsToParse || this.DEFAULT_MAX_ELEMS_TO_PARSE;
+ this._nbTopCandidates = options.nbTopCandidates || this.DEFAULT_N_TOP_CANDIDATES;
+ this._charThreshold = options.charThreshold || this.DEFAULT_CHAR_THRESHOLD;
+ this._classesToPreserve = this.CLASSES_TO_PRESERVE.concat(options.classesToPreserve || []);
+ this._keepClasses = !!options.keepClasses;
+
+ // Start with all flags set
+ this._flags = this.FLAG_STRIP_UNLIKELYS |
+ this.FLAG_WEIGHT_CLASSES |
+ this.FLAG_CLEAN_CONDITIONALLY;
+
+ var logEl;
+
+ // Control whether log messages are sent to the console
+ if (this._debug) {
+ logEl = function(e) {
+ var rv = e.nodeName + " ";
+ if (e.nodeType == e.TEXT_NODE) {
+ return rv + '("' + e.textContent + '")';
+ }
+ var classDesc = e.className && ("." + e.className.replace(/ /g, "."));
+ var elDesc = "";
+ if (e.id)
+ elDesc = "(#" + e.id + classDesc + ")";
+ else if (classDesc)
+ elDesc = "(" + classDesc + ")";
+ return rv + elDesc;
+ };
+ this.log = function () {
+ if (typeof dump !== "undefined") {
+ var msg = Array.prototype.map.call(arguments, function(x) {
+ return (x && x.nodeName) ? logEl(x) : x;
+ }).join(" ");
+ dump("Reader: (Readability) " + msg + "\n");
+ } else if (typeof console !== "undefined") {
+ var args = ["Reader: (Readability) "].concat(arguments);
+ console.log.apply(console, args);
+ }
+ };
+ } else {
+ this.log = function () {};
+ }
+}
+
+Readability.prototype = {
+ FLAG_STRIP_UNLIKELYS: 0x1,
+ FLAG_WEIGHT_CLASSES: 0x2,
+ FLAG_CLEAN_CONDITIONALLY: 0x4,
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
+ ELEMENT_NODE: 1,
+ TEXT_NODE: 3,
+
+ // Max number of nodes supported by this parser. Default: 0 (no limit)
+ DEFAULT_MAX_ELEMS_TO_PARSE: 0,
+
+ // The number of top candidates to consider when analysing how
+ // tight the competition is among candidates.
+ DEFAULT_N_TOP_CANDIDATES: 5,
+
+ // Element tags to score by default.
+ DEFAULT_TAGS_TO_SCORE: "section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","),
+
+ // The default number of chars an article must have in order to return a result
+ DEFAULT_CHAR_THRESHOLD: 500,
+
+ // All of the regular expressions in use within readability.
+ // Defined up here so we don't instantiate them repeatedly in loops.
+ REGEXPS: {
+ // NOTE: These two regular expressions are duplicated in
+ // Readability-readerable.js. Please keep both copies in sync.
+ unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
+ okMaybeItsACandidate: /and|article|body|column|content|main|shadow/i,
+
+ positive: /article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story/i,
+ negative: /hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i,
+ extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i,
+ byline: /byline|author|dateline|writtenby|p-author/i,
+ replaceFonts: /<(\/?)font[^>]*>/gi,
+ normalize: /\s{2,}/g,
+ videos: /\/\/(www\.)?((dailymotion|youtube|youtube-nocookie|player\.vimeo|v\.qq)\.com|(archive|upload\.wikimedia)\.org|player\.twitch\.tv)/i,
+ shareElements: /(\b|_)(share|sharedaddy)(\b|_)/i,
+ nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i,
+ prevLink: /(prev|earl|old|new|<|«)/i,
+ whitespace: /^\s*$/,
+ hasContent: /\S$/,
+ srcsetUrl: /(\S+)(\s+[\d.]+[xw])?(\s*(?:,|$))/g,
+ b64DataUrl: /^data:\s*([^\s;,]+)\s*;\s*base64\s*,/i
+ },
+
+ DIV_TO_P_ELEMS: [ "A", "BLOCKQUOTE", "DL", "DIV", "IMG", "OL", "P", "PRE", "TABLE", "UL", "SELECT" ],
+
+ ALTER_TO_DIV_EXCEPTIONS: ["DIV", "ARTICLE", "SECTION", "P"],
+
+ PRESENTATIONAL_ATTRIBUTES: [ "align", "background", "bgcolor", "border", "cellpadding", "cellspacing", "frame", "hspace", "rules", "style", "valign", "vspace" ],
+
+ DEPRECATED_SIZE_ATTRIBUTE_ELEMS: [ "TABLE", "TH", "TD", "HR", "PRE" ],
+
+ // The commented out elements qualify as phrasing content but tend to be
+ // removed by readability when put into paragraphs, so we ignore them here.
+ PHRASING_ELEMS: [
+ // "CANVAS", "IFRAME", "SVG", "VIDEO",
+ "ABBR", "AUDIO", "B", "BDO", "BR", "BUTTON", "CITE", "CODE", "DATA",
+ "DATALIST", "DFN", "EM", "EMBED", "I", "IMG", "INPUT", "KBD", "LABEL",
+ "MARK", "MATH", "METER", "NOSCRIPT", "OBJECT", "OUTPUT", "PROGRESS", "Q",
+ "RUBY", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "SUB",
+ "SUP", "TEXTAREA", "TIME", "VAR", "WBR"
+ ],
+
+ // These are the classes that readability sets itself.
+ CLASSES_TO_PRESERVE: [ "page" ],
+
+ // These are the list of HTML entities that need to be escaped.
+ HTML_ESCAPE_MAP: {
+ "lt": "<",
+ "gt": ">",
+ "amp": "&",
+ "quot": '"',
+ "apos": "'",
+ },
+
+ /**
+ * Run any post-process modifications to article content as necessary.
+ *
+ * @param Element
+ * @return void
+ **/
+ _postProcessContent: function(articleContent) {
+ // Readability cannot open relative uris so we convert them to absolute uris.
+ this._fixRelativeUris(articleContent);
+
+ if (!this._keepClasses) {
+ // Remove classes.
+ this._cleanClasses(articleContent);
+ }
+ },
+
+ /**
+ * Iterates over a NodeList, calls `filterFn` for each node and removes node
+ * if function returned `true`.
+ *
+ * If function is not passed, removes all the nodes in node list.
+ *
+ * @param NodeList nodeList The nodes to operate on
+ * @param Function filterFn the function to use as a filter
+ * @return void
+ */
+ _removeNodes: function(nodeList, filterFn) {
+ // Avoid ever operating on live node lists.
+ if (this._docJSDOMParser && nodeList._isLiveNodeList) {
+ throw new Error("Do not pass live node lists to _removeNodes");
+ }
+ for (var i = nodeList.length - 1; i >= 0; i--) {
+ var node = nodeList[i];
+ var parentNode = node.parentNode;
+ if (parentNode) {
+ if (!filterFn || filterFn.call(this, node, i, nodeList)) {
+ parentNode.removeChild(node);
+ }
+ }
+ }
+ },
+
+ /**
+ * Iterates over a NodeList, and calls _setNodeTag for each node.
+ *
+ * @param NodeList nodeList The nodes to operate on
+ * @param String newTagName the new tag name to use
+ * @return void
+ */
+ _replaceNodeTags: function(nodeList, newTagName) {
+ // Avoid ever operating on live node lists.
+ if (this._docJSDOMParser && nodeList._isLiveNodeList) {
+ throw new Error("Do not pass live node lists to _replaceNodeTags");
+ }
+ for (var i = nodeList.length - 1; i >= 0; i--) {
+ var node = nodeList[i];
+ this._setNodeTag(node, newTagName);
+ }
+ },
+
+ /**
+ * Iterate over a NodeList, which doesn't natively fully implement the Array
+ * interface.
+ *
+ * For convenience, the current object context is applied to the provided
+ * iterate function.
+ *
+ * @param NodeList nodeList The NodeList.
+ * @param Function fn The iterate function.
+ * @return void
+ */
+ _forEachNode: function(nodeList, fn) {
+ Array.prototype.forEach.call(nodeList, fn, this);
+ },
+
+ /**
+ * Iterate over a NodeList, return true if any of the provided iterate
+ * function calls returns true, false otherwise.
+ *
+ * For convenience, the current object context is applied to the
+ * provided iterate function.
+ *
+ * @param NodeList nodeList The NodeList.
+ * @param Function fn The iterate function.
+ * @return Boolean
+ */
+ _someNode: function(nodeList, fn) {
+ return Array.prototype.some.call(nodeList, fn, this);
+ },
+
+ /**
+ * Iterate over a NodeList, return true if all of the provided iterate
+ * function calls return true, false otherwise.
+ *
+ * For convenience, the current object context is applied to the
+ * provided iterate function.
+ *
+ * @param NodeList nodeList The NodeList.
+ * @param Function fn The iterate function.
+ * @return Boolean
+ */
+ _everyNode: function(nodeList, fn) {
+ return Array.prototype.every.call(nodeList, fn, this);
+ },
+
+ /**
+ * Concat all nodelists passed as arguments.
+ *
+ * @return ...NodeList
+ * @return Array
+ */
+ _concatNodeLists: function() {
+ var slice = Array.prototype.slice;
+ var args = slice.call(arguments);
+ var nodeLists = args.map(function(list) {
+ return slice.call(list);
+ });
+ return Array.prototype.concat.apply([], nodeLists);
+ },
+
+ _getAllNodesWithTag: function(node, tagNames) {
+ if (node.querySelectorAll) {
+ return node.querySelectorAll(tagNames.join(","));
+ }
+ return [].concat.apply([], tagNames.map(function(tag) {
+ var collection = node.getElementsByTagName(tag);
+ return Array.isArray(collection) ? collection : Array.from(collection);
+ }));
+ },
+
+ /**
+ * Removes the class="" attribute from every element in the given
+ * subtree, except those that match CLASSES_TO_PRESERVE and
+ * the classesToPreserve array from the options object.
+ *
+ * @param Element
+ * @return void
+ */
+ _cleanClasses: function(node) {
+ var classesToPreserve = this._classesToPreserve;
+ var className = (node.getAttribute("class") || "")
+ .split(/\s+/)
+ .filter(function(cls) {
+ return classesToPreserve.indexOf(cls) != -1;
+ })
+ .join(" ");
+
+ if (className) {
+ node.setAttribute("class", className);
+ } else {
+ node.removeAttribute("class");
+ }
+
+ for (node = node.firstElementChild; node; node = node.nextElementSibling) {
+ this._cleanClasses(node);
+ }
+ },
+
+ /**
+ * Converts each <a> and <img> uri in the given element to an absolute URI,
+ * ignoring #ref URIs.
+ *
+ * @param Element
+ * @return void
+ */
+ _fixRelativeUris: function(articleContent) {
+ var baseURI = this._doc.baseURI;
+ var documentURI = this._doc.documentURI;
+ function toAbsoluteURI(uri) {
+ // Leave hash links alone if the base URI matches the document URI:
+ if (baseURI == documentURI && uri.charAt(0) == "#") {
+ return uri;
+ }
+
+ // Otherwise, resolve against base URI:
+ try {
+ return new URL(uri, baseURI).href;
+ } catch (ex) {
+ // Something went wrong, just return the original:
+ }
+ return uri;
+ }
+
+ var links = this._getAllNodesWithTag(articleContent, ["a"]);
+ this._forEachNode(links, function(link) {
+ var href = link.getAttribute("href");
+ if (href) {
+ // Remove links with javascript: URIs, since
+ // they won't work after scripts have been removed from the page.
+ if (href.indexOf("javascript:") === 0) {
+ // if the link only contains simple text content, it can be converted to a text node
+ if (link.childNodes.length === 1 && link.childNodes[0].nodeType === this.TEXT_NODE) {
+ var text = this._doc.createTextNode(link.textContent);
+ link.parentNode.replaceChild(text, link);
+ } else {
+ // if the link has multiple children, they should all be preserved
+ var container = this._doc.createElement("span");
+ while (link.childNodes.length > 0) {
+ container.appendChild(link.childNodes[0]);
+ }
+ link.parentNode.replaceChild(container, link);
+ }
+ } else {
+ link.setAttribute("href", toAbsoluteURI(href));
+ }
+ }
+ });
+
+ var medias = this._getAllNodesWithTag(articleContent, [
+ "img", "picture", "figure", "video", "audio", "source"
+ ]);
+
+ this._forEachNode(medias, function(media) {
+ var src = media.getAttribute("src");
+ var poster = media.getAttribute("poster");
+ var srcset = media.getAttribute("srcset");
+
+ if (src) {
+ media.setAttribute("src", toAbsoluteURI(src));
+ }
+
+ if (poster) {
+ media.setAttribute("poster", toAbsoluteURI(poster));
+ }
+
+ if (srcset) {
+ var newSrcset = srcset.replace(this.REGEXPS.srcsetUrl, function(_, p1, p2, p3) {
+ return toAbsoluteURI(p1) + (p2 || "") + p3;
+ });
+
+ media.setAttribute("srcset", newSrcset);
+ }
+ });
+ },
+
+ /**
+ * Get the article title as an H1.
+ *
+ * @return void
+ **/
+ _getArticleTitle: function() {
+ var doc = this._doc;
+ var curTitle = "";
+ var origTitle = "";
+
+ try {
+ curTitle = origTitle = doc.title.trim();
+
+ // If they had an element with id "title" in their HTML
+ if (typeof curTitle !== "string")
+ curTitle = origTitle = this._getInnerText(doc.getElementsByTagName("title")[0]);
+ } catch (e) {/* ignore exceptions setting the title. */}
+
+ var titleHadHierarchicalSeparators = false;
+ function wordCount(str) {
+ return str.split(/\s+/).length;
+ }
+
+ // If there's a separator in the title, first remove the final part
+ if ((/ [\|\-\\\/>»] /).test(curTitle)) {
+ titleHadHierarchicalSeparators = / [\\\/>»] /.test(curTitle);
+ curTitle = origTitle.replace(/(.*)[\|\-\\\/>»] .*/gi, "$1");
+
+ // If the resulting title is too short (3 words or fewer), remove
+ // the first part instead:
+ if (wordCount(curTitle) < 3)
+ curTitle = origTitle.replace(/[^\|\-\\\/>»]*[\|\-\\\/>»](.*)/gi, "$1");
+ } else if (curTitle.indexOf(": ") !== -1) {
+ // Check if we have an heading containing this exact string, so we
+ // could assume it's the full title.
+ var headings = this._concatNodeLists(
+ doc.getElementsByTagName("h1"),
+ doc.getElementsByTagName("h2")
+ );
+ var trimmedTitle = curTitle.trim();
+ var match = this._someNode(headings, function(heading) {
+ return heading.textContent.trim() === trimmedTitle;
+ });
+
+ // If we don't, let's extract the title out of the original title string.
+ if (!match) {
+ curTitle = origTitle.substring(origTitle.lastIndexOf(":") + 1);
+
+ // If the title is now too short, try the first colon instead:
+ if (wordCount(curTitle) < 3) {
+ curTitle = origTitle.substring(origTitle.indexOf(":") + 1);
+ // But if we have too many words before the colon there's something weird
+ // with the titles and the H tags so let's just use the original title instead
+ } else if (wordCount(origTitle.substr(0, origTitle.indexOf(":"))) > 5) {
+ curTitle = origTitle;
+ }
+ }
+ } else if (curTitle.length > 150 || curTitle.length < 15) {
+ var hOnes = doc.getElementsByTagName("h1");
+
+ if (hOnes.length === 1)
+ curTitle = this._getInnerText(hOnes[0]);
+ }
+
+ curTitle = curTitle.trim().replace(this.REGEXPS.normalize, " ");
+ // If we now have 4 words or fewer as our title, and either no
+ // 'hierarchical' separators (\, /, > or ») were found in the original
+ // title or we decreased the number of words by more than 1 word, use
+ // the original title.
+ var curTitleWordCount = wordCount(curTitle);
+ if (curTitleWordCount <= 4 &&
+ (!titleHadHierarchicalSeparators ||
+ curTitleWordCount != wordCount(origTitle.replace(/[\|\-\\\/>»]+/g, "")) - 1)) {
+ curTitle = origTitle;
+ }
+
+ return curTitle;
+ },
+
+ /**
+ * Prepare the HTML document for readability to scrape it.
+ * This includes things like stripping javascript, CSS, and handling terrible markup.
+ *
+ * @return void
+ **/
+ _prepDocument: function() {
+ var doc = this._doc;
+
+ // Remove all style tags in head
+ this._removeNodes(this._getAllNodesWithTag(doc, ["style"]));
+
+ if (doc.body) {
+ this._replaceBrs(doc.body);
+ }
+
+ this._replaceNodeTags(this._getAllNodesWithTag(doc, ["font"]), "SPAN");
+ },
+
+ /**
+ * Finds the next element, starting from the given node, and ignoring
+ * whitespace in between. If the given node is an element, the same node is
+ * returned.
+ */
+ _nextElement: function (node) {
+ var next = node;
+ while (next
+ && (next.nodeType != this.ELEMENT_NODE)
+ && this.REGEXPS.whitespace.test(next.textContent)) {
+ next = next.nextSibling;
+ }
+ return next;
+ },
+
+ /**
+ * Replaces 2 or more successive <br> elements with a single <p>.
+ * Whitespace between <br> elements are ignored. For example:
+ * <div>foo<br>bar<br> <br><br>abc</div>
+ * will become:
+ * <div>foo<br>bar<p>abc</p></div>
+ */
+ _replaceBrs: function (elem) {
+ this._forEachNode(this._getAllNodesWithTag(elem, ["br"]), function(br) {
+ var next = br.nextSibling;
+
+ // Whether 2 or more <br> elements have been found and replaced with a
+ // <p> block.
+ var replaced = false;
+
+ // If we find a <br> chain, remove the <br>s until we hit another element
+ // or non-whitespace. This leaves behind the first <br> in the chain
+ // (which will be replaced with a <p> later).
+ while ((next = this._nextElement(next)) && (next.tagName == "BR")) {
+ replaced = true;
+ var brSibling = next.nextSibling;
+ next.parentNode.removeChild(next);
+ next = brSibling;
+ }
+
+ // If we removed a <br> chain, replace the remaining <br> with a <p>. Add
+ // all sibling nodes as children of the <p> until we hit another <br>
+ // chain.
+ if (replaced) {
+ var p = this._doc.createElement("p");
+ br.parentNode.replaceChild(p, br);
+
+ next = p.nextSibling;
+ while (next) {
+ // If we've hit another <br><br>, we're done adding children to this <p>.
+ if (next.tagName == "BR") {
+ var nextElem = this._nextElement(next.nextSibling);
+ if (nextElem && nextElem.tagName == "BR")
+ break;
+ }
+
+ if (!this._isPhrasingContent(next))
+ break;
+
+ // Otherwise, make this node a child of the new <p>.
+ var sibling = next.nextSibling;
+ p.appendChild(next);
+ next = sibling;
+ }
+
+ while (p.lastChild && this._isWhitespace(p.lastChild)) {
+ p.removeChild(p.lastChild);
+ }
+
+ if (p.parentNode.tagName === "P")
+ this._setNodeTag(p.parentNode, "DIV");
+ }
+ });
+ },
+
+ _setNodeTag: function (node, tag) {
+ this.log("_setNodeTag", node, tag);
+ if (this._docJSDOMParser) {
+ node.localName = tag.toLowerCase();
+ node.tagName = tag.toUpperCase();
+ return node;
+ }
+
+ var replacement = node.ownerDocument.createElement(tag);
+ while (node.firstChild) {
+ replacement.appendChild(node.firstChild);
+ }
+ node.parentNode.replaceChild(replacement, node);
+ if (node.readability)
+ replacement.readability = node.readability;
+
+ for (var i = 0; i < node.attributes.length; i++) {
+ try {
+ replacement.setAttribute(node.attributes[i].name, node.attributes[i].value);
+ } catch (ex) {
+ /* it's possible for setAttribute() to throw if the attribute name
+ * isn't a valid XML Name. Such attributes can however be parsed from
+ * source in HTML docs, see https://github.com/whatwg/html/issues/4275,
+ * so we can hit them here and then throw. We don't care about such
+ * attributes so we ignore them.
+ */
+ }
+ }
+ return replacement;
+ },
+
+ /**
+ * Prepare the article node for display. Clean out any inline styles,
+ * iframes, forms, strip extraneous <p> tags, etc.
+ *
+ * @param Element
+ * @return void
+ **/
+ _prepArticle: function(articleContent) {
+ this._cleanStyles(articleContent);
+
+ // Check for data tables before we continue, to avoid removing items in
+ // those tables, which will often be isolated even though they're
+ // visually linked to other content-ful elements (text, images, etc.).
+ this._markDataTables(articleContent);
+
+ this._fixLazyImages(articleContent);
+
+ // Clean out junk from the article content
+ this._cleanConditionally(articleContent, "form");
+ this._cleanConditionally(articleContent, "fieldset");
+ this._clean(articleContent, "object");
+ this._clean(articleContent, "embed");
+ this._clean(articleContent, "h1");
+ this._clean(articleContent, "footer");
+ this._clean(articleContent, "link");
+ this._clean(articleContent, "aside");
+
+ // Clean out elements with little content that have "share" in their id/class combinations from final top candidates,
+ // which means we don't remove the top candidates even they have "share".
+
+ var shareElementThreshold = this.DEFAULT_CHAR_THRESHOLD;
+
+ this._forEachNode(articleContent.children, function (topCandidate) {
+ this._cleanMatchedNodes(topCandidate, function (node, matchString) {
+ return this.REGEXPS.shareElements.test(matchString) && node.textContent.length < shareElementThreshold;
+ });
+ });
+
+ // If there is only one h2 and its text content substantially equals article title,
+ // they are probably using it as a header and not a subheader,
+ // so remove it since we already extract the title separately.
+ var h2 = articleContent.getElementsByTagName("h2");
+ if (h2.length === 1) {
+ var lengthSimilarRate = (h2[0].textContent.length - this._articleTitle.length) / this._articleTitle.length;
+ if (Math.abs(lengthSimilarRate) < 0.5) {
+ var titlesMatch = false;
+ if (lengthSimilarRate > 0) {
+ titlesMatch = h2[0].textContent.includes(this._articleTitle);
+ } else {
+ titlesMatch = this._articleTitle.includes(h2[0].textContent);
+ }
+ if (titlesMatch) {
+ this._clean(articleContent, "h2");
+ }
+ }
+ }
+
+ this._clean(articleContent, "iframe");
+ this._clean(articleContent, "input");
+ this._clean(articleContent, "textarea");
+ this._clean(articleContent, "select");
+ this._clean(articleContent, "button");
+ this._cleanHeaders(articleContent);
+
+ // Do these last as the previous stuff may have removed junk
+ // that will affect these
+ this._cleanConditionally(articleContent, "table");
+ this._cleanConditionally(articleContent, "ul");
+ this._cleanConditionally(articleContent, "div");
+
+ // Remove extra paragraphs
+ this._removeNodes(this._getAllNodesWithTag(articleContent, ["p"]), function (paragraph) {
+ var imgCount = paragraph.getElementsByTagName("img").length;
+ var embedCount = paragraph.getElementsByTagName("embed").length;
+ var objectCount = paragraph.getElementsByTagName("object").length;
+ // At this point, nasty iframes have been removed, only remain embedded video ones.
+ var iframeCount = paragraph.getElementsByTagName("iframe").length;
+ var totalCount = imgCount + embedCount + objectCount + iframeCount;
+
+ return totalCount === 0 && !this._getInnerText(paragraph, false);
+ });
+
+ this._forEachNode(this._getAllNodesWithTag(articleContent, ["br"]), function(br) {
+ var next = this._nextElement(br.nextSibling);
+ if (next && next.tagName == "P")
+ br.parentNode.removeChild(br);
+ });
+
+ // Remove single-cell tables
+ this._forEachNode(this._getAllNodesWithTag(articleContent, ["table"]), function(table) {
+ var tbody = this._hasSingleTagInsideElement(table, "TBODY") ? table.firstElementChild : table;
+ if (this._hasSingleTagInsideElement(tbody, "TR")) {
+ var row = tbody.firstElementChild;
+ if (this._hasSingleTagInsideElement(row, "TD")) {
+ var cell = row.firstElementChild;
+ cell = this._setNodeTag(cell, this._everyNode(cell.childNodes, this._isPhrasingContent) ? "P" : "DIV");
+ table.parentNode.replaceChild(cell, table);
+ }
+ }
+ });
+ },
+
+ /**
+ * Initialize a node with the readability object. Also checks the
+ * className/id for special names to add to its score.
+ *
+ * @param Element
+ * @return void
+ **/
+ _initializeNode: function(node) {
+ node.readability = {"contentScore": 0};
+
+ switch (node.tagName) {
+ case "DIV":
+ node.readability.contentScore += 5;
+ break;
+
+ case "PRE":
+ case "TD":
+ case "BLOCKQUOTE":
+ node.readability.contentScore += 3;
+ break;
+
+ case "ADDRESS":
+ case "OL":
+ case "UL":
+ case "DL":
+ case "DD":
+ case "DT":
+ case "LI":
+ case "FORM":
+ node.readability.contentScore -= 3;
+ break;
+
+ case "H1":
+ case "H2":
+ case "H3":
+ case "H4":
+ case "H5":
+ case "H6":
+ case "TH":
+ node.readability.contentScore -= 5;
+ break;
+ }
+
+ node.readability.contentScore += this._getClassWeight(node);
+ },
+
+ _removeAndGetNext: function(node) {
+ var nextNode = this._getNextNode(node, true);
+ node.parentNode.removeChild(node);
+ return nextNode;
+ },
+
+ /**
+ * Traverse the DOM from node to node, starting at the node passed in.
+ * Pass true for the second parameter to indicate this node itself
+ * (and its kids) are going away, and we want the next node over.
+ *
+ * Calling this in a loop will traverse the DOM depth-first.
+ */
+ _getNextNode: function(node, ignoreSelfAndKids) {
+ // First check for kids if those aren't being ignored
+ if (!ignoreSelfAndKids && node.firstElementChild) {
+ return node.firstElementChild;
+ }
+ // Then for siblings...
+ if (node.nextElementSibling) {
+ return node.nextElementSibling;
+ }
+ // And finally, move up the parent chain *and* find a sibling
+ // (because this is depth-first traversal, we will have already
+ // seen the parent nodes themselves).
+ do {
+ node = node.parentNode;
+ } while (node && !node.nextElementSibling);
+ return node && node.nextElementSibling;
+ },
+
+ _checkByline: function(node, matchString) {
+ if (this._articleByline) {
+ return false;
+ }
+
+ if (node.getAttribute !== undefined) {
+ var rel = node.getAttribute("rel");
+ var itemprop = node.getAttribute("itemprop");
+ }
+
+ if ((rel === "author" || (itemprop && itemprop.indexOf("author") !== -1) || this.REGEXPS.byline.test(matchString)) && this._isValidByline(node.textContent)) {
+ this._articleByline = node.textContent.trim();
+ return true;
+ }
+
+ return false;
+ },
+
+ _getNodeAncestors: function(node, maxDepth) {
+ maxDepth = maxDepth || 0;
+ var i = 0, ancestors = [];
+ while (node.parentNode) {
+ ancestors.push(node.parentNode);
+ if (maxDepth && ++i === maxDepth)
+ break;
+ node = node.parentNode;
+ }
+ return ancestors;
+ },
+
+ /***
+ * grabArticle - Using a variety of metrics (content score, classname, element types), find the content that is
+ * most likely to be the stuff a user wants to read. Then return it wrapped up in a div.
+ *
+ * @param page a document to run upon. Needs to be a full document, complete with body.
+ * @return Element
+ **/
+ _grabArticle: function (page) {
+ this.log("**** grabArticle ****");
+ var doc = this._doc;
+ var isPaging = (page !== null ? true: false);
+ page = page ? page : this._doc.body;
+
+ // We can't grab an article if we don't have a page!
+ if (!page) {
+ this.log("No body found in document. Abort.");
+ return null;
+ }
+
+ var pageCacheHtml = page.innerHTML;
+
+ while (true) {
+ var stripUnlikelyCandidates = this._flagIsActive(this.FLAG_STRIP_UNLIKELYS);
+
+ // First, node prepping. Trash nodes that look cruddy (like ones with the
+ // class name "comment", etc), and turn divs into P tags where they have been
+ // used inappropriately (as in, where they contain no other block level elements.)
+ var elementsToScore = [];
+ var node = this._doc.documentElement;
+
+ while (node) {
+ var matchString = node.className + " " + node.id;
+
+ if (!this._isProbablyVisible(node)) {
+ this.log("Removing hidden node - " + matchString);
+ node = this._removeAndGetNext(node);
+ continue;
+ }
+
+ // Check to see if this node is a byline, and remove it if it is.
+ if (this._checkByline(node, matchString)) {
+ node = this._removeAndGetNext(node);
+ continue;
+ }
+
+ // Remove unlikely candidates
+ if (stripUnlikelyCandidates) {
+ if (this.REGEXPS.unlikelyCandidates.test(matchString) &&
+ !this.REGEXPS.okMaybeItsACandidate.test(matchString) &&
+ !this._hasAncestorTag(node, "table") &&
+ node.tagName !== "BODY" &&
+ node.tagName !== "A") {
+ this.log("Removing unlikely candidate - " + matchString);
+ node = this._removeAndGetNext(node);
+ continue;
+ }
+
+ if (node.getAttribute("role") == "complementary") {
+ this.log("Removing complementary content - " + matchString);
+ node = this._removeAndGetNext(node);
+ continue;
+ }
+ }
+
+ // Remove DIV, SECTION, and HEADER nodes without any content(e.g. text, image, video, or iframe).
+ if ((node.tagName === "DIV" || node.tagName === "SECTION" || node.tagName === "HEADER" ||
+ node.tagName === "H1" || node.tagName === "H2" || node.tagName === "H3" ||
+ node.tagName === "H4" || node.tagName === "H5" || node.tagName === "H6") &&
+ this._isElementWithoutContent(node)) {
+ node = this._removeAndGetNext(node);
+ continue;
+ }
+
+ if (this.DEFAULT_TAGS_TO_SCORE.indexOf(node.tagName) !== -1) {
+ elementsToScore.push(node);
+ }
+
+ // Turn all divs that don't have children block level elements into p's
+ if (node.tagName === "DIV") {
+ // Put phrasing content into paragraphs.
+ var p = null;
+ var childNode = node.firstChild;
+ while (childNode) {
+ var nextSibling = childNode.nextSibling;
+ if (this._isPhrasingContent(childNode)) {
+ if (p !== null) {
+ p.appendChild(childNode);
+ } else if (!this._isWhitespace(childNode)) {
+ p = doc.createElement("p");
+ node.replaceChild(p, childNode);
+ p.appendChild(childNode);
+ }
+ } else if (p !== null) {
+ while (p.lastChild && this._isWhitespace(p.lastChild)) {
+ p.removeChild(p.lastChild);
+ }
+ p = null;
+ }
+ childNode = nextSibling;
+ }
+
+ // Sites like http://mobile.slate.com encloses each paragraph with a DIV
+ // element. DIVs with only a P element inside and no text content can be
+ // safely converted into plain P elements to avoid confusing the scoring
+ // algorithm with DIVs with are, in practice, paragraphs.
+ if (this._hasSingleTagInsideElement(node, "P") && this._getLinkDensity(node) < 0.25) {
+ var newNode = node.children[0];
+ node.parentNode.replaceChild(newNode, node);
+ node = newNode;
+ elementsToScore.push(node);
+ } else if (!this._hasChildBlockElement(node)) {
+ node = this._setNodeTag(node, "P");
+ elementsToScore.push(node);
+ }
+ }
+ node = this._getNextNode(node);
+ }
+
+ /**
+ * Loop through all paragraphs, and assign a score to them based on how content-y they look.
+ * Then add their score to their parent node.
+ *
+ * A score is determined by things like number of commas, class names, etc. Maybe eventually link density.
+ **/
+ var candidates = [];
+ this._forEachNode(elementsToScore, function(elementToScore) {
+ if (!elementToScore.parentNode || typeof(elementToScore.parentNode.tagName) === "undefined")
+ return;
+
+ // If this paragraph is less than 25 characters, don't even count it.
+ var innerText = this._getInnerText(elementToScore);
+ if (innerText.length < 25)
+ return;
+
+ // Exclude nodes with no ancestor.
+ var ancestors = this._getNodeAncestors(elementToScore, 3);
+ if (ancestors.length === 0)
+ return;
+
+ var contentScore = 0;
+
+ // Add a point for the paragraph itself as a base.
+ contentScore += 1;
+
+ // Add points for any commas within this paragraph.
+ contentScore += innerText.split(",").length;
+
+ // For every 100 characters in this paragraph, add another point. Up to 3 points.
+ contentScore += Math.min(Math.floor(innerText.length / 100), 3);
+
+ // Initialize and score ancestors.
+ this._forEachNode(ancestors, function(ancestor, level) {
+ if (!ancestor.tagName || !ancestor.parentNode || typeof(ancestor.parentNode.tagName) === "undefined")
+ return;
+
+ if (typeof(ancestor.readability) === "undefined") {
+ this._initializeNode(ancestor);
+ candidates.push(ancestor);
+ }
+
+ // Node score divider:
+ // - parent: 1 (no division)
+ // - grandparent: 2
+ // - great grandparent+: ancestor level * 3
+ if (level === 0)
+ var scoreDivider = 1;
+ else if (level === 1)
+ scoreDivider = 2;
+ else
+ scoreDivider = level * 3;
+ ancestor.readability.contentScore += contentScore / scoreDivider;
+ });
+ });
+
+ // After we've calculated scores, loop through all of the possible
+ // candidate nodes we found and find the one with the highest score.
+ var topCandidates = [];
+ for (var c = 0, cl = candidates.length; c < cl; c += 1) {
+ var candidate = candidates[c];
+
+ // Scale the final candidates score based on link density. Good content
+ // should have a relatively small link density (5% or less) and be mostly
+ // unaffected by this operation.
+ var candidateScore = candidate.readability.contentScore * (1 - this._getLinkDensity(candidate));
+ candidate.readability.contentScore = candidateScore;
+
+ this.log("Candidate:", candidate, "with score " + candidateScore);
+
+ for (var t = 0; t < this._nbTopCandidates; t++) {
+ var aTopCandidate = topCandidates[t];
+
+ if (!aTopCandidate || candidateScore > aTopCandidate.readability.contentScore) {
+ topCandidates.splice(t, 0, candidate);
+ if (topCandidates.length > this._nbTopCandidates)
+ topCandidates.pop();
+ break;
+ }
+ }
+ }
+
+ var topCandidate = topCandidates[0] || null;
+ var neededToCreateTopCandidate = false;
+ var parentOfTopCandidate;
+
+ // If we still have no top candidate, just use the body as a last resort.
+ // We also have to copy the body node so it is something we can modify.
+ if (topCandidate === null || topCandidate.tagName === "BODY") {
+ // Move all of the page's children into topCandidate
+ topCandidate = doc.createElement("DIV");
+ neededToCreateTopCandidate = true;
+ // Move everything (not just elements, also text nodes etc.) into the container
+ // so we even include text directly in the body:
+ var kids = page.childNodes;
+ while (kids.length) {
+ this.log("Moving child out:", kids[0]);
+ topCandidate.appendChild(kids[0]);
+ }
+
+ page.appendChild(topCandidate);
+
+ this._initializeNode(topCandidate);
+ } else if (topCandidate) {
+ // Find a better top candidate node if it contains (at least three) nodes which belong to `topCandidates` array
+ // and whose scores are quite closed with current `topCandidate` node.
+ var alternativeCandidateAncestors = [];
+ for (var i = 1; i < topCandidates.length; i++) {
+ if (topCandidates[i].readability.contentScore / topCandidate.readability.contentScore >= 0.75) {
+ alternativeCandidateAncestors.push(this._getNodeAncestors(topCandidates[i]));
+ }
+ }
+ var MINIMUM_TOPCANDIDATES = 3;
+ if (alternativeCandidateAncestors.length >= MINIMUM_TOPCANDIDATES) {
+ parentOfTopCandidate = topCandidate.parentNode;
+ while (parentOfTopCandidate.tagName !== "BODY") {
+ var listsContainingThisAncestor = 0;
+ for (var ancestorIndex = 0; ancestorIndex < alternativeCandidateAncestors.length && listsContainingThisAncestor < MINIMUM_TOPCANDIDATES; ancestorIndex++) {
+ listsContainingThisAncestor += Number(alternativeCandidateAncestors[ancestorIndex].includes(parentOfTopCandidate));
+ }
+ if (listsContainingThisAncestor >= MINIMUM_TOPCANDIDATES) {
+ topCandidate = parentOfTopCandidate;
+ break;
+ }
+ parentOfTopCandidate = parentOfTopCandidate.parentNode;
+ }
+ }
+ if (!topCandidate.readability) {
+ this._initializeNode(topCandidate);
+ }
+
+ // Because of our bonus system, parents of candidates might have scores
+ // themselves. They get half of the node. There won't be nodes with higher
+ // scores than our topCandidate, but if we see the score going *up* in the first
+ // few steps up the tree, that's a decent sign that there might be more content
+ // lurking in other places that we want to unify in. The sibling stuff
+ // below does some of that - but only if we've looked high enough up the DOM
+ // tree.
+ parentOfTopCandidate = topCandidate.parentNode;
+ var lastScore = topCandidate.readability.contentScore;
+ // The scores shouldn't get too low.
+ var scoreThreshold = lastScore / 3;
+ while (parentOfTopCandidate.tagName !== "BODY") {
+ if (!parentOfTopCandidate.readability) {
+ parentOfTopCandidate = parentOfTopCandidate.parentNode;
+ continue;
+ }
+ var parentScore = parentOfTopCandidate.readability.contentScore;
+ if (parentScore < scoreThreshold)
+ break;
+ if (parentScore > lastScore) {
+ // Alright! We found a better parent to use.
+ topCandidate = parentOfTopCandidate;
+ break;
+ }
+ lastScore = parentOfTopCandidate.readability.contentScore;
+ parentOfTopCandidate = parentOfTopCandidate.parentNode;
+ }
+
+ // If the top candidate is the only child, use parent instead. This will help sibling
+ // joining logic when adjacent content is actually located in parent's sibling node.
+ parentOfTopCandidate = topCandidate.parentNode;
+ while (parentOfTopCandidate.tagName != "BODY" && parentOfTopCandidate.children.length == 1) {
+ topCandidate = parentOfTopCandidate;
+ parentOfTopCandidate = topCandidate.parentNode;
+ }
+ if (!topCandidate.readability) {
+ this._initializeNode(topCandidate);
+ }
+ }
+
+ // Now that we have the top candidate, look through its siblings for content
+ // that might also be related. Things like preambles, content split by ads
+ // that we removed, etc.
+ var articleContent = doc.createElement("DIV");
+ if (isPaging)
+ articleContent.id = "readability-content";
+
+ var siblingScoreThreshold = Math.max(10, topCandidate.readability.contentScore * 0.2);
+ // Keep potential top candidate's parent node to try to get text direction of it later.
+ parentOfTopCandidate = topCandidate.parentNode;
+ var siblings = parentOfTopCandidate.children;
+
+ for (var s = 0, sl = siblings.length; s < sl; s++) {
+ var sibling = siblings[s];
+ var append = false;
+
+ this.log("Looking at sibling node:", sibling, sibling.readability ? ("with score " + sibling.readability.contentScore) : "");
+ this.log("Sibling has score", sibling.readability ? sibling.readability.contentScore : "Unknown");
+
+ if (sibling === topCandidate) {
+ append = true;
+ } else {
+ var contentBonus = 0;
+
+ // Give a bonus if sibling nodes and top candidates have the example same classname
+ if (sibling.className === topCandidate.className && topCandidate.className !== "")
+ contentBonus += topCandidate.readability.contentScore * 0.2;
+
+ if (sibling.readability &&
+ ((sibling.readability.contentScore + contentBonus) >= siblingScoreThreshold)) {
+ append = true;
+ } else if (sibling.nodeName === "P") {
+ var linkDensity = this._getLinkDensity(sibling);
+ var nodeContent = this._getInnerText(sibling);
+ var nodeLength = nodeContent.length;
+
+ if (nodeLength > 80 && linkDensity < 0.25) {
+ append = true;
+ } else if (nodeLength < 80 && nodeLength > 0 && linkDensity === 0 &&
+ nodeContent.search(/\.( |$)/) !== -1) {
+ append = true;
+ }
+ }
+ }
+
+ if (append) {
+ this.log("Appending node:", sibling);
+
+ if (this.ALTER_TO_DIV_EXCEPTIONS.indexOf(sibling.nodeName) === -1) {
+ // We have a node that isn't a common block level element, like a form or td tag.
+ // Turn it into a div so it doesn't get filtered out later by accident.
+ this.log("Altering sibling:", sibling, "to div.");
+
+ sibling = this._setNodeTag(sibling, "DIV");
+ }
+
+ articleContent.appendChild(sibling);
+ // siblings is a reference to the children array, and
+ // sibling is removed from the array when we call appendChild().
+ // As a result, we must revisit this index since the nodes
+ // have been shifted.
+ s -= 1;
+ sl -= 1;
+ }
+ }
+
+ if (this._debug)
+ this.log("Article content pre-prep: " + articleContent.innerHTML);
+ // So we have all of the content that we need. Now we clean it up for presentation.
+ this._prepArticle(articleContent);
+ if (this._debug)
+ this.log("Article content post-prep: " + articleContent.innerHTML);
+
+ if (neededToCreateTopCandidate) {
+ // We already created a fake div thing, and there wouldn't have been any siblings left
+ // for the previous loop, so there's no point trying to create a new div, and then
+ // move all the children over. Just assign IDs and class names here. No need to append
+ // because that already happened anyway.
+ topCandidate.id = "readability-page-1";
+ topCandidate.className = "page";
+ } else {
+ var div = doc.createElement("DIV");
+ div.id = "readability-page-1";
+ div.className = "page";
+ var children = articleContent.childNodes;
+ while (children.length) {
+ div.appendChild(children[0]);
+ }
+ articleContent.appendChild(div);
+ }
+
+ if (this._debug)
+ this.log("Article content after paging: " + articleContent.innerHTML);
+
+ var parseSuccessful = true;
+
+ // Now that we've gone through the full algorithm, check to see if
+ // we got any meaningful content. If we didn't, we may need to re-run
+ // grabArticle with different flags set. This gives us a higher likelihood of
+ // finding the content, and the sieve approach gives us a higher likelihood of
+ // finding the -right- content.
+ var textLength = this._getInnerText(articleContent, true).length;
+ if (textLength < this._charThreshold) {
+ parseSuccessful = false;
+ page.innerHTML = pageCacheHtml;
+
+ if (this._flagIsActive(this.FLAG_STRIP_UNLIKELYS)) {
+ this._removeFlag(this.FLAG_STRIP_UNLIKELYS);
+ this._attempts.push({articleContent: articleContent, textLength: textLength});
+ } else if (this._flagIsActive(this.FLAG_WEIGHT_CLASSES)) {
+ this._removeFlag(this.FLAG_WEIGHT_CLASSES);
+ this._attempts.push({articleContent: articleContent, textLength: textLength});
+ } else if (this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY)) {
+ this._removeFlag(this.FLAG_CLEAN_CONDITIONALLY);
+ this._attempts.push({articleContent: articleContent, textLength: textLength});
+ } else {
+ this._attempts.push({articleContent: articleContent, textLength: textLength});
+ // No luck after removing flags, just return the longest text we found during the different loops
+ this._attempts.sort(function (a, b) {
+ return b.textLength - a.textLength;
+ });
+
+ // But first check if we actually have something
+ if (!this._attempts[0].textLength) {
+ return null;
+ }
+
+ articleContent = this._attempts[0].articleContent;
+ parseSuccessful = true;
+ }
+ }
+
+ if (parseSuccessful) {
+ // Find out text direction from ancestors of final top candidate.
+ var ancestors = [parentOfTopCandidate, topCandidate].concat(this._getNodeAncestors(parentOfTopCandidate));
+ this._someNode(ancestors, function(ancestor) {
+ if (!ancestor.tagName)
+ return false;
+ var articleDir = ancestor.getAttribute("dir");
+ if (articleDir) {
+ this._articleDir = articleDir;
+ return true;
+ }
+ return false;
+ });
+ return articleContent;
+ }
+ }
+ },
+
+ /**
+ * Check whether the input string could be a byline.
+ * This verifies that the input is a string, and that the length
+ * is less than 100 chars.
+ *
+ * @param possibleByline {string} - a string to check whether its a byline.
+ * @return Boolean - whether the input string is a byline.
+ */
+ _isValidByline: function(byline) {
+ if (typeof byline == "string" || byline instanceof String) {
+ byline = byline.trim();
+ return (byline.length > 0) && (byline.length < 100);
+ }
+ return false;
+ },
+
+ /**
+ * Converts some of the common HTML entities in string to their corresponding characters.
+ *
+ * @param str {string} - a string to unescape.
+ * @return string without HTML entity.
+ */
+ _unescapeHtmlEntities: function(str) {
+ if (!str) {
+ return str;
+ }
+
+ var htmlEscapeMap = this.HTML_ESCAPE_MAP;
+ return str.replace(/&(quot|amp|apos|lt|gt);/g, function(_, tag) {
+ return htmlEscapeMap[tag];
+ }).replace(/&#(?:x([0-9a-z]{1,4})|([0-9]{1,4}));/gi, function(_, hex, numStr) {
+ var num = parseInt(hex || numStr, hex ? 16 : 10);
+ return String.fromCharCode(num);
+ });
+ },
+
+ /**
+ * Attempts to get excerpt and byline metadata for the article.
+ *
+ * @return Object with optional "excerpt" and "byline" properties
+ */
+ _getArticleMetadata: function() {
+ var metadata = {};
+ var values = {};
+ var metaElements = this._doc.getElementsByTagName("meta");
+
+ // property is a space-separated list of values
+ var propertyPattern = /\s*(dc|dcterm|og|twitter)\s*:\s*(author|creator|description|title|site_name)\s*/gi;
+
+ // name is a single value
+ var namePattern = /^\s*(?:(dc|dcterm|og|twitter|weibo:(article|webpage))\s*[\.:]\s*)?(author|creator|description|title|site_name)\s*$/i;
+
+ // Find description tags.
+ this._forEachNode(metaElements, function(element) {
+ var elementName = element.getAttribute("name");
+ var elementProperty = element.getAttribute("property");
+ var content = element.getAttribute("content");
+ if (!content) {
+ return;
+ }
+ var matches = null;
+ var name = null;
+
+ if (elementProperty) {
+ matches = elementProperty.match(propertyPattern);
+ if (matches) {
+ for (var i = matches.length - 1; i >= 0; i--) {
+ // Convert to lowercase, and remove any whitespace
+ // so we can match below.
+ name = matches[i].toLowerCase().replace(/\s/g, "");
+ // multiple authors
+ values[name] = content.trim();
+ }
+ }
+ }
+ if (!matches && elementName && namePattern.test(elementName)) {
+ name = elementName;
+ if (content) {
+ // Convert to lowercase, remove any whitespace, and convert dots
+ // to colons so we can match below.
+ name = name.toLowerCase().replace(/\s/g, "").replace(/\./g, ":");
+ values[name] = content.trim();
+ }
+ }
+ });
+
+ // get title
+ metadata.title = values["dc:title"] ||
+ values["dcterm:title"] ||
+ values["og:title"] ||
+ values["weibo:article:title"] ||
+ values["weibo:webpage:title"] ||
+ values["title"] ||
+ values["twitter:title"];
+
+ if (!metadata.title) {
+ metadata.title = this._getArticleTitle();
+ }
+
+ // get author
+ metadata.byline = values["dc:creator"] ||
+ values["dcterm:creator"] ||
+ values["author"];
+
+ // get description
+ metadata.excerpt = values["dc:description"] ||
+ values["dcterm:description"] ||
+ values["og:description"] ||
+ values["weibo:article:description"] ||
+ values["weibo:webpage:description"] ||
+ values["description"] ||
+ values["twitter:description"];
+
+ // get site name
+ metadata.siteName = values["og:site_name"];
+
+ // in many sites the meta value is escaped with HTML entities,
+ // so here we need to unescape it
+ metadata.title = this._unescapeHtmlEntities(metadata.title);
+ metadata.byline = this._unescapeHtmlEntities(metadata.byline);
+ metadata.excerpt = this._unescapeHtmlEntities(metadata.excerpt);
+ metadata.siteName = this._unescapeHtmlEntities(metadata.siteName);
+
+ return metadata;
+ },
+
+ /**
+ * Check if node is image, or if node contains exactly only one image
+ * whether as a direct child or as its descendants.
+ *
+ * @param Element
+ **/
+ _isSingleImage: function(node) {
+ if (node.tagName === "IMG") {
+ return true;
+ }
+
+ if (node.children.length !== 1 || node.textContent.trim() !== "") {
+ return false;
+ }
+
+ return this._isSingleImage(node.children[0]);
+ },
+
+ /**
+ * Find all <noscript> that are located after <img> nodes, and which contain only one
+ * <img> element. Replace the first image with the image from inside the <noscript> tag,
+ * and remove the <noscript> tag. This improves the quality of the images we use on
+ * some sites (e.g. Medium).
+ *
+ * @param Element
+ **/
+ _unwrapNoscriptImages: function(doc) {
+ // Find img without source or attributes that might contains image, and remove it.
+ // This is done to prevent a placeholder img is replaced by img from noscript in next step.
+ var imgs = Array.from(doc.getElementsByTagName("img"));
+ this._forEachNode(imgs, function(img) {
+ for (var i = 0; i < img.attributes.length; i++) {
+ var attr = img.attributes[i];
+ switch (attr.name) {
+ case "src":
+ case "srcset":
+ case "data-src":
+ case "data-srcset":
+ return;
+ }
+
+ if (/\.(jpg|jpeg|png|webp)/i.test(attr.value)) {
+ return;
+ }
+ }
+
+ img.parentNode.removeChild(img);
+ });
+
+ // Next find noscript and try to extract its image
+ var noscripts = Array.from(doc.getElementsByTagName("noscript"));
+ this._forEachNode(noscripts, function(noscript) {
+ // Parse content of noscript and make sure it only contains image
+ var tmp = doc.createElement("div");
+ tmp.innerHTML = noscript.innerHTML;
+ if (!this._isSingleImage(tmp)) {
+ return;
+ }
+
+ // If noscript has previous sibling and it only contains image,
+ // replace it with noscript content. However we also keep old
+ // attributes that might contains image.
+ var prevElement = noscript.previousElementSibling;
+ if (prevElement && this._isSingleImage(prevElement)) {
+ var prevImg = prevElement;
+ if (prevImg.tagName !== "IMG") {
+ prevImg = prevElement.getElementsByTagName("img")[0];
+ }
+
+ var newImg = tmp.getElementsByTagName("img")[0];
+ for (var i = 0; i < prevImg.attributes.length; i++) {
+ var attr = prevImg.attributes[i];
+ if (attr.value === "") {
+ continue;
+ }
+
+ if (attr.name === "src" || attr.name === "srcset" || /\.(jpg|jpeg|png|webp)/i.test(attr.value)) {
+ if (newImg.getAttribute(attr.name) === attr.value) {
+ continue;
+ }
+
+ var attrName = attr.name;
+ if (newImg.hasAttribute(attrName)) {
+ attrName = "data-old-" + attrName;
+ }
+
+ newImg.setAttribute(attrName, attr.value);
+ }
+ }
+
+ noscript.parentNode.replaceChild(tmp.firstElementChild, prevElement);
+ }
+ });
+ },
+
+ /**
+ * Removes script tags from the document.
+ *
+ * @param Element
+ **/
+ _removeScripts: function(doc) {
+ this._removeNodes(this._getAllNodesWithTag(doc, ["script"]), function(scriptNode) {
+ scriptNode.nodeValue = "";
+ scriptNode.removeAttribute("src");
+ return true;
+ });
+ this._removeNodes(this._getAllNodesWithTag(doc, ["noscript"]));
+ },
+
+ /**
+ * Check if this node has only whitespace and a single element with given tag
+ * Returns false if the DIV node contains non-empty text nodes
+ * or if it contains no element with given tag or more than 1 element.
+ *
+ * @param Element
+ * @param string tag of child element
+ **/
+ _hasSingleTagInsideElement: function(element, tag) {
+ // There should be exactly 1 element child with given tag
+ if (element.children.length != 1 || element.children[0].tagName !== tag) {
+ return false;
+ }
+
+ // And there should be no text nodes with real content
+ return !this._someNode(element.childNodes, function(node) {
+ return node.nodeType === this.TEXT_NODE &&
+ this.REGEXPS.hasContent.test(node.textContent);
+ });
+ },
+
+ _isElementWithoutContent: function(node) {
+ return node.nodeType === this.ELEMENT_NODE &&
+ node.textContent.trim().length == 0 &&
+ (node.children.length == 0 ||
+ node.children.length == node.getElementsByTagName("br").length + node.getElementsByTagName("hr").length);
+ },
+
+ /**
+ * Determine whether element has any children block level elements.
+ *
+ * @param Element
+ */
+ _hasChildBlockElement: function (element) {
+ return this._someNode(element.childNodes, function(node) {
+ return this.DIV_TO_P_ELEMS.indexOf(node.tagName) !== -1 ||
+ this._hasChildBlockElement(node);
+ });
+ },
+
+ /***
+ * Determine if a node qualifies as phrasing content.
+ * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content
+ **/
+ _isPhrasingContent: function(node) {
+ return node.nodeType === this.TEXT_NODE || this.PHRASING_ELEMS.indexOf(node.tagName) !== -1 ||
+ ((node.tagName === "A" || node.tagName === "DEL" || node.tagName === "INS") &&
+ this._everyNode(node.childNodes, this._isPhrasingContent));
+ },
+
+ _isWhitespace: function(node) {
+ return (node.nodeType === this.TEXT_NODE && node.textContent.trim().length === 0) ||
+ (node.nodeType === this.ELEMENT_NODE && node.tagName === "BR");
+ },
+
+ /**
+ * Get the inner text of a node - cross browser compatibly.
+ * This also strips out any excess whitespace to be found.
+ *
+ * @param Element
+ * @param Boolean normalizeSpaces (default: true)
+ * @return string
+ **/
+ _getInnerText: function(e, normalizeSpaces) {
+ normalizeSpaces = (typeof normalizeSpaces === "undefined") ? true : normalizeSpaces;
+ var textContent = e.textContent.trim();
+
+ if (normalizeSpaces) {
+ return textContent.replace(this.REGEXPS.normalize, " ");
+ }
+ return textContent;
+ },
+
+ /**
+ * Get the number of times a string s appears in the node e.
+ *
+ * @param Element
+ * @param string - what to split on. Default is ","
+ * @return number (integer)
+ **/
+ _getCharCount: function(e, s) {
+ s = s || ",";
+ return this._getInnerText(e).split(s).length - 1;
+ },
+
+ /**
+ * Remove the style attribute on every e and under.
+ * TODO: Test if getElementsByTagName(*) is faster.
+ *
+ * @param Element
+ * @return void
+ **/
+ _cleanStyles: function(e) {
+ if (!e || e.tagName.toLowerCase() === "svg")
+ return;
+
+ // Remove `style` and deprecated presentational attributes
+ for (var i = 0; i < this.PRESENTATIONAL_ATTRIBUTES.length; i++) {
+ e.removeAttribute(this.PRESENTATIONAL_ATTRIBUTES[i]);
+ }
+
+ if (this.DEPRECATED_SIZE_ATTRIBUTE_ELEMS.indexOf(e.tagName) !== -1) {
+ e.removeAttribute("width");
+ e.removeAttribute("height");
+ }
+
+ var cur = e.firstElementChild;
+ while (cur !== null) {
+ this._cleanStyles(cur);
+ cur = cur.nextElementSibling;
+ }
+ },
+
+ /**
+ * Get the density of links as a percentage of the content
+ * This is the amount of text that is inside a link divided by the total text in the node.
+ *
+ * @param Element
+ * @return number (float)
+ **/
+ _getLinkDensity: function(element) {
+ var textLength = this._getInnerText(element).length;
+ if (textLength === 0)
+ return 0;
+
+ var linkLength = 0;
+
+ // XXX implement _reduceNodeList?
+ this._forEachNode(element.getElementsByTagName("a"), function(linkNode) {
+ linkLength += this._getInnerText(linkNode).length;
+ });
+
+ return linkLength / textLength;
+ },
+
+ /**
+ * Get an elements class/id weight. Uses regular expressions to tell if this
+ * element looks good or bad.
+ *
+ * @param Element
+ * @return number (Integer)
+ **/
+ _getClassWeight: function(e) {
+ if (!this._flagIsActive(this.FLAG_WEIGHT_CLASSES))
+ return 0;
+
+ var weight = 0;
+
+ // Look for a special classname
+ if (typeof(e.className) === "string" && e.className !== "") {
+ if (this.REGEXPS.negative.test(e.className))
+ weight -= 25;
+
+ if (this.REGEXPS.positive.test(e.className))
+ weight += 25;
+ }
+
+ // Look for a special ID
+ if (typeof(e.id) === "string" && e.id !== "") {
+ if (this.REGEXPS.negative.test(e.id))
+ weight -= 25;
+
+ if (this.REGEXPS.positive.test(e.id))
+ weight += 25;
+ }
+
+ return weight;
+ },
+
+ /**
+ * Clean a node of all elements of type "tag".
+ * (Unless it's a youtube/vimeo video. People love movies.)
+ *
+ * @param Element
+ * @param string tag to clean
+ * @return void
+ **/
+ _clean: function(e, tag) {
+ var isEmbed = ["object", "embed", "iframe"].indexOf(tag) !== -1;
+
+ this._removeNodes(this._getAllNodesWithTag(e, [tag]), function(element) {
+ // Allow youtube and vimeo videos through as people usually want to see those.
+ if (isEmbed) {
+ // First, check the elements attributes to see if any of them contain youtube or vimeo
+ for (var i = 0; i < element.attributes.length; i++) {
+ if (this.REGEXPS.videos.test(element.attributes[i].value)) {
+ return false;
+ }
+ }
+
+ // For embed with <object> tag, check inner HTML as well.
+ if (element.tagName === "object" && this.REGEXPS.videos.test(element.innerHTML)) {
+ return false;
+ }
+ }
+
+ return true;
+ });
+ },
+
+ /**
+ * Check if a given node has one of its ancestor tag name matching the
+ * provided one.
+ * @param HTMLElement node
+ * @param String tagName
+ * @param Number maxDepth
+ * @param Function filterFn a filter to invoke to determine whether this node 'counts'
+ * @return Boolean
+ */
+ _hasAncestorTag: function(node, tagName, maxDepth, filterFn) {
+ maxDepth = maxDepth || 3;
+ tagName = tagName.toUpperCase();
+ var depth = 0;
+ while (node.parentNode) {
+ if (maxDepth > 0 && depth > maxDepth)
+ return false;
+ if (node.parentNode.tagName === tagName && (!filterFn || filterFn(node.parentNode)))
+ return true;
+ node = node.parentNode;
+ depth++;
+ }
+ return false;
+ },
+
+ /**
+ * Return an object indicating how many rows and columns this table has.
+ */
+ _getRowAndColumnCount: function(table) {
+ var rows = 0;
+ var columns = 0;
+ var trs = table.getElementsByTagName("tr");
+ for (var i = 0; i < trs.length; i++) {
+ var rowspan = trs[i].getAttribute("rowspan") || 0;
+ if (rowspan) {
+ rowspan = parseInt(rowspan, 10);
+ }
+ rows += (rowspan || 1);
+
+ // Now look for column-related info
+ var columnsInThisRow = 0;
+ var cells = trs[i].getElementsByTagName("td");
+ for (var j = 0; j < cells.length; j++) {
+ var colspan = cells[j].getAttribute("colspan") || 0;
+ if (colspan) {
+ colspan = parseInt(colspan, 10);
+ }
+ columnsInThisRow += (colspan || 1);
+ }
+ columns = Math.max(columns, columnsInThisRow);
+ }
+ return {rows: rows, columns: columns};
+ },
+
+ /**
+ * Look for 'data' (as opposed to 'layout') tables, for which we use
+ * similar checks as
+ * https://dxr.mozilla.org/mozilla-central/rev/71224049c0b52ab190564d3ea0eab089a159a4cf/accessible/html/HTMLTableAccessible.cpp#920
+ */
+ _markDataTables: function(root) {
+ var tables = root.getElementsByTagName("table");
+ for (var i = 0; i < tables.length; i++) {
+ var table = tables[i];
+ var role = table.getAttribute("role");
+ if (role == "presentation") {
+ table._readabilityDataTable = false;
+ continue;
+ }
+ var datatable = table.getAttribute("datatable");
+ if (datatable == "0") {
+ table._readabilityDataTable = false;
+ continue;
+ }
+ var summary = table.getAttribute("summary");
+ if (summary) {
+ table._readabilityDataTable = true;
+ continue;
+ }
+
+ var caption = table.getElementsByTagName("caption")[0];
+ if (caption && caption.childNodes.length > 0) {
+ table._readabilityDataTable = true;
+ continue;
+ }
+
+ // If the table has a descendant with any of these tags, consider a data table:
+ var dataTableDescendants = ["col", "colgroup", "tfoot", "thead", "th"];
+ var descendantExists = function(tag) {
+ return !!table.getElementsByTagName(tag)[0];
+ };
+ if (dataTableDescendants.some(descendantExists)) {
+ this.log("Data table because found data-y descendant");
+ table._readabilityDataTable = true;
+ continue;
+ }
+
+ // Nested tables indicate a layout table:
+ if (table.getElementsByTagName("table")[0]) {
+ table._readabilityDataTable = false;
+ continue;
+ }
+
+ var sizeInfo = this._getRowAndColumnCount(table);
+ if (sizeInfo.rows >= 10 || sizeInfo.columns > 4) {
+ table._readabilityDataTable = true;
+ continue;
+ }
+ // Now just go by size entirely:
+ table._readabilityDataTable = sizeInfo.rows * sizeInfo.columns > 10;
+ }
+ },
+
+ /* convert images and figures that have properties like data-src into images that can be loaded without JS */
+ _fixLazyImages: function (root) {
+ this._forEachNode(this._getAllNodesWithTag(root, ["img", "picture", "figure"]), function (elem) {
+ // In some sites (e.g. Kotaku), they put 1px square image as base64 data uri in the src attribute.
+ // So, here we check if the data uri is too short, just might as well remove it.
+ if (elem.src && this.REGEXPS.b64DataUrl.test(elem.src)) {
+ // Make sure it's not SVG, because SVG can have a meaningful image in under 133 bytes.
+ var parts = this.REGEXPS.b64DataUrl.exec(elem.src);
+ if (parts[1] === "image/svg+xml") {
+ return;
+ }
+
+ // Make sure this element has other attributes which contains image.
+ // If it doesn't, then this src is important and shouldn't be removed.
+ var srcCouldBeRemoved = false;
+ for (var i = 0; i < elem.attributes.length; i++) {
+ var attr = elem.attributes[i];
+ if (attr.name === "src") {
+ continue;
+ }
+
+ if (/\.(jpg|jpeg|png|webp)/i.test(attr.value)) {
+ srcCouldBeRemoved = true;
+ break;
+ }
+ }
+
+ // Here we assume if image is less than 100 bytes (or 133B after encoded to base64)
+ // it will be too small, therefore it might be placeholder image.
+ if (srcCouldBeRemoved) {
+ var b64starts = elem.src.search(/base64\s*/i) + 7;
+ var b64length = elem.src.length - b64starts;
+ if (b64length < 133) {
+ elem.removeAttribute("src");
+ }
+ }
+ }
+
+ // also check for "null" to work around https://github.com/jsdom/jsdom/issues/2580
+ if ((elem.src || (elem.srcset && elem.srcset != "null")) && elem.className.toLowerCase().indexOf("lazy") === -1) {
+ return;
+ }
+
+ for (var j = 0; j < elem.attributes.length; j++) {
+ attr = elem.attributes[j];
+ if (attr.name === "src" || attr.name === "srcset") {
+ continue;
+ }
+ var copyTo = null;
+ if (/\.(jpg|jpeg|png|webp)\s+\d/.test(attr.value)) {
+ copyTo = "srcset";
+ } else if (/^\s*\S+\.(jpg|jpeg|png|webp)\S*\s*$/.test(attr.value)) {
+ copyTo = "src";
+ }
+ if (copyTo) {
+ //if this is an img or picture, set the attribute directly
+ if (elem.tagName === "IMG" || elem.tagName === "PICTURE") {
+ elem.setAttribute(copyTo, attr.value);
+ } else if (elem.tagName === "FIGURE" && !this._getAllNodesWithTag(elem, ["img", "picture"]).length) {
+ //if the item is a <figure> that does not contain an image or picture, create one and place it inside the figure
+ //see the nytimes-3 testcase for an example
+ var img = this._doc.createElement("img");
+ img.setAttribute(copyTo, attr.value);
+ elem.appendChild(img);
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * Clean an element of all tags of type "tag" if they look fishy.
+ * "Fishy" is an algorithm based on content length, classnames, link density, number of images & embeds, etc.
+ *
+ * @return void
+ **/
+ _cleanConditionally: function(e, tag) {
+ if (!this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY))
+ return;
+
+ var isList = tag === "ul" || tag === "ol";
+
+ // Gather counts for other typical elements embedded within.
+ // Traverse backwards so we can remove nodes at the same time
+ // without effecting the traversal.
+ //
+ // TODO: Consider taking into account original contentScore here.
+ this._removeNodes(this._getAllNodesWithTag(e, [tag]), function(node) {
+ // First check if this node IS data table, in which case don't remove it.
+ var isDataTable = function(t) {
+ return t._readabilityDataTable;
+ };
+
+ if (tag === "table" && isDataTable(node)) {
+ return false;
+ }
+
+ // Next check if we're inside a data table, in which case don't remove it as well.
+ if (this._hasAncestorTag(node, "table", -1, isDataTable)) {
+ return false;
+ }
+
+ var weight = this._getClassWeight(node);
+ var contentScore = 0;
+
+ this.log("Cleaning Conditionally", node);
+
+ if (weight + contentScore < 0) {
+ return true;
+ }
+
+ if (this._getCharCount(node, ",") < 10) {
+ // If there are not very many commas, and the number of
+ // non-paragraph elements is more than paragraphs or other
+ // ominous signs, remove the element.
+ var p = node.getElementsByTagName("p").length;
+ var img = node.getElementsByTagName("img").length;
+ var li = node.getElementsByTagName("li").length - 100;
+ var input = node.getElementsByTagName("input").length;
+
+ var embedCount = 0;
+ var embeds = this._getAllNodesWithTag(node, ["object", "embed", "iframe"]);
+
+ for (var i = 0; i < embeds.length; i++) {
+ // If this embed has attribute that matches video regex, don't delete it.
+ for (var j = 0; j < embeds[i].attributes.length; j++) {
+ if (this.REGEXPS.videos.test(embeds[i].attributes[j].value)) {
+ return false;
+ }
+ }
+
+ // For embed with <object> tag, check inner HTML as well.
+ if (embeds[i].tagName === "object" && this.REGEXPS.videos.test(embeds[i].innerHTML)) {
+ return false;
+ }
+
+ embedCount++;
+ }
+
+ var linkDensity = this._getLinkDensity(node);
+ var contentLength = this._getInnerText(node).length;
+
+ var haveToRemove =
+ (img > 1 && p / img < 0.5 && !this._hasAncestorTag(node, "figure")) ||
+ (!isList && li > p) ||
+ (input > Math.floor(p/3)) ||
+ (!isList && contentLength < 25 && (img === 0 || img > 2) && !this._hasAncestorTag(node, "figure")) ||
+ (!isList && weight < 25 && linkDensity > 0.2) ||
+ (weight >= 25 && linkDensity > 0.5) ||
+ ((embedCount === 1 && contentLength < 75) || embedCount > 1);
+ return haveToRemove;
+ }
+ return false;
+ });
+ },
+
+ /**
+ * Clean out elements that match the specified conditions
+ *
+ * @param Element
+ * @param Function determines whether a node should be removed
+ * @return void
+ **/
+ _cleanMatchedNodes: function(e, filter) {
+ var endOfSearchMarkerNode = this._getNextNode(e, true);
+ var next = this._getNextNode(e);
+ while (next && next != endOfSearchMarkerNode) {
+ if (filter.call(this, next, next.className + " " + next.id)) {
+ next = this._removeAndGetNext(next);
+ } else {
+ next = this._getNextNode(next);
+ }
+ }
+ },
+
+ /**
+ * Clean out spurious headers from an Element. Checks things like classnames and link density.
+ *
+ * @param Element
+ * @return void
+ **/
+ _cleanHeaders: function(e) {
+ this._removeNodes(this._getAllNodesWithTag(e, ["h1", "h2"]), function (header) {
+ return this._getClassWeight(header) < 0;
+ });
+ },
+
+ _flagIsActive: function(flag) {
+ return (this._flags & flag) > 0;
+ },
+
+ _removeFlag: function(flag) {
+ this._flags = this._flags & ~flag;
+ },
+
+ _isProbablyVisible: function(node) {
+ // Have to null-check node.style and node.className.indexOf to deal with SVG and MathML nodes.
+ return (!node.style || node.style.display != "none")
+ && !node.hasAttribute("hidden")
+ //check for "fallback-image" so that wikimedia math images are displayed
+ && (!node.hasAttribute("aria-hidden") || node.getAttribute("aria-hidden") != "true" || (node.className && node.className.indexOf && node.className.indexOf("fallback-image") !== -1));
+ },
+
+ /**
+ * Runs readability.
+ *
+ * Workflow:
+ * 1. Prep the document by removing script tags, css, etc.
+ * 2. Build readability's DOM tree.
+ * 3. Grab the article content from the current dom tree.
+ * 4. Replace the current DOM tree with the new one.
+ * 5. Read peacefully.
+ *
+ * @return void
+ **/
+ parse: function () {
+ // Avoid parsing too large documents, as per configuration option
+ if (this._maxElemsToParse > 0) {
+ var numTags = this._doc.getElementsByTagName("*").length;
+ if (numTags > this._maxElemsToParse) {
+ throw new Error("Aborting parsing document; " + numTags + " elements found");
+ }
+ }
+
+ // Unwrap image from noscript
+ this._unwrapNoscriptImages(this._doc);
+
+ // Remove script tags from the document.
+ this._removeScripts(this._doc);
+
+ this._prepDocument();
+
+ var metadata = this._getArticleMetadata();
+ this._articleTitle = metadata.title;
+
+ var articleContent = this._grabArticle();
+ if (!articleContent)
+ return null;
+
+ this.log("Grabbed: " + articleContent.innerHTML);
+
+ this._postProcessContent(articleContent);
+
+ // If we haven't found an excerpt in the article's metadata, use the article's
+ // first paragraph as the excerpt. This is used for displaying a preview of
+ // the article's content.
+ if (!metadata.excerpt) {
+ var paragraphs = articleContent.getElementsByTagName("p");
+ if (paragraphs.length > 0) {
+ metadata.excerpt = paragraphs[0].textContent.trim();
+ }
+ }
+
+ var textContent = articleContent.textContent;
+ return {
+ title: this._articleTitle,
+ byline: metadata.byline || this._articleByline,
+ dir: this._articleDir,
+ content: articleContent.innerHTML,
+ textContent: textContent,
+ length: textContent.length,
+ excerpt: metadata.excerpt,
+ siteName: metadata.siteName || this._articleSiteName
+ };
+ }
+};
+
+if (typeof module === "object") {
+ module.exports = Readability;
+}
diff --git a/components/reader/ReaderMode.jsm b/components/reader/ReaderMode.jsm
new file mode 100644
index 000000000..a1d2c5643
--- /dev/null
+++ b/components/reader/ReaderMode.jsm
@@ -0,0 +1,590 @@
+// -*- 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/. */
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["ReaderMode"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+// Class names to preserve in the readerized output. We preserve these class
+// names so that rules in aboutReader.css can match them.
+const CLASSES_TO_PRESERVE = [
+ "caption",
+ "emoji",
+ "hidden",
+ "invisible",
+ "sr-only",
+ "visually-hidden",
+ "visuallyhidden",
+ "wp-caption",
+ "wp-caption-text",
+ "wp-smiley",
+];
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+Cu.importGlobalProperties(["XMLHttpRequest"]);
+
+XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", "resource://services-common/utils.js");
+XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher", "resource://gre/modules/Messaging.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderWorker", "resource://gre/modules/reader/ReaderWorker.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Readerable", "resource://gre/modules/Readerable.jsm");
+
+this.ReaderMode = {
+ // Version of the cache schema.
+ CACHE_VERSION: 1,
+
+ DEBUG: 0,
+
+ /**
+ * Enter the reader mode by going forward one step in history if applicable,
+ * if not, append the about:reader page in the history instead.
+ */
+ enterReaderMode(docShell, win) {
+ let url = win.document.location.href;
+ let readerURL = "about:reader?url=" + encodeURIComponent(url);
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let sh = webNav.sessionHistory;
+ if (webNav.canGoForward) {
+ let forwardEntry = sh.getEntryAtIndex(sh.index + 1, false);
+ let forwardURL = forwardEntry.URI.spec;
+ if (forwardURL && (forwardURL == readerURL || !readerURL)) {
+ webNav.goForward();
+ return;
+ }
+ }
+
+ win.document.location = readerURL;
+ },
+
+ /**
+ * Exit the reader mode by going back one step in history if applicable,
+ * if not, append the original page in the history instead.
+ */
+ leaveReaderMode(docShell, win) {
+ let url = win.document.location.href;
+ let originalURL = this.getOriginalUrl(url);
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let sh = webNav.sessionHistory;
+ if (webNav.canGoBack) {
+ let prevEntry = sh.getEntryAtIndex(sh.index - 1, false);
+ let prevURL = prevEntry.URI.spec;
+ if (prevURL && (prevURL == originalURL || !originalURL)) {
+ webNav.goBack();
+ return;
+ }
+ }
+
+ win.document.location = originalURL;
+ },
+
+ /**
+ * Returns original URL from an about:reader URL.
+ *
+ * @param url An about:reader URL.
+ * @return The original URL for the article, or null if we did not find
+ * a properly formatted about:reader URL.
+ */
+ getOriginalUrl(url) {
+ if (!url.startsWith("about:reader?")) {
+ return null;
+ }
+
+ let outerHash = "";
+ try {
+ let uriObj = Services.io.newURI(url);
+ url = uriObj.specIgnoringRef;
+ outerHash = uriObj.ref;
+ } catch (ex) { /* ignore, use the raw string */ }
+
+ let searchParams = new URLSearchParams(url.substring("about:reader?".length));
+ if (!searchParams.has("url")) {
+ return null;
+ }
+ let originalUrl = searchParams.get("url");
+ if (outerHash) {
+ try {
+ let uriObj = Services.io.newURI(originalUrl);
+ uriObj = Services.io.newURI("#" + outerHash, null, uriObj);
+ originalUrl = uriObj.spec;
+ } catch (ex) {}
+ }
+ return originalUrl;
+ },
+
+ getOriginalUrlObjectForDisplay(url) {
+ let originalUrl = this.getOriginalUrl(url);
+ if (originalUrl) {
+ let uriObj;
+ try {
+ uriObj = Services.uriFixup.createFixupURI(originalUrl, Services.uriFixup.FIXUP_FLAG_NONE);
+ } catch (ex) {
+ return null;
+ }
+ try {
+ return Services.uriFixup.createExposableURI(uriObj);
+ } catch (ex) {
+ return null;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Gets an article from a loaded browser's document. This method will not attempt
+ * to parse certain URIs (e.g. about: URIs).
+ *
+ * @param doc A document to parse.
+ * @return {Promise}
+ * @resolves JS object representing the article, or null if no article is found.
+ */
+ parseDocument(doc) {
+ if (!Readerable.shouldCheckUri(doc.documentURIObject) ||
+ !Readerable.shouldCheckUri(doc.baseURIObject, true)) {
+ this.log("Reader mode disabled for URI");
+ return null;
+ }
+
+ return this._readerParse(doc);
+ },
+
+ /**
+ * Downloads and parses a document from a URL.
+ *
+ * @param url URL to download and parse.
+ * @return {Promise}
+ * @resolves JS object representing the article, or null if no article is found.
+ */
+ async downloadAndParseDocument(url) {
+ let doc = await this._downloadDocument(url);
+ if (!doc) {
+ return null;
+ }
+ if (!Readerable.shouldCheckUri(doc.documentURIObject) ||
+ !Readerable.shouldCheckUri(doc.baseURIObject, true)) {
+ this.log("Reader mode disabled for URI");
+ return null;
+ }
+
+ return await this._readerParse(doc);
+ },
+
+ _downloadDocument(url) {
+ try {
+ if (!Readerable.shouldCheckUri(Services.io.newURI(url))) {
+ return null;
+ }
+ } catch (ex) {
+ Cu.reportError(new Error(`Couldn't create URI from ${url} to download: ${ex}`));
+ return null;
+ }
+ return new Promise((resolve, reject) => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true);
+ xhr.onerror = evt => reject(evt.error);
+ xhr.responseType = "document";
+ xhr.onload = evt => {
+ if (xhr.status !== 200) {
+ reject("Reader mode XHR failed with status: " + xhr.status);
+ return;
+ }
+
+ let doc = xhr.responseXML;
+ if (!doc) {
+ reject("Reader mode XHR didn't return a document");
+ return;
+ }
+
+ // Manually follow a meta refresh tag if one exists.
+ let meta = doc.querySelector("meta[http-equiv=refresh]");
+ if (meta) {
+ let content = meta.getAttribute("content");
+ if (content) {
+ let urlIndex = content.toUpperCase().indexOf("URL=");
+ if (urlIndex > -1) {
+ let baseURI = Services.io.newURI(url);
+ let newURI = Services.io.newURI(content.substring(urlIndex + 4), null, baseURI);
+ let newURL = newURI.spec;
+ let ssm = Services.scriptSecurityManager;
+ let flags = ssm.LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ ssm.DISALLOW_INHERIT_PRINCIPAL;
+ try {
+ ssm.checkLoadURIStrWithPrincipal(doc.nodePrincipal, newURL, flags);
+ } catch (ex) {
+ let errorMsg = "Reader mode disallowed meta refresh (reason: " + ex + ").";
+
+ if (Services.prefs.getBoolPref("reader.errors.includeURLs"))
+ errorMsg += " Refresh target URI: '" + newURL + "'.";
+ reject(errorMsg);
+ return;
+ }
+ // Otherwise, pass an object indicating our new URL:
+ if (!baseURI.equalsExceptRef(newURI)) {
+ reject({newURL});
+ return;
+ }
+ }
+ }
+ }
+ let responseURL = xhr.responseURL;
+ let givenURL = url;
+ // Convert these to real URIs to make sure the escaping (or lack
+ // thereof) is identical:
+ try {
+ responseURL = Services.io.newURI(responseURL).specIgnoringRef;
+ } catch (ex) { /* Ignore errors - we'll use what we had before */ }
+ try {
+ givenURL = Services.io.newURI(givenURL).specIgnoringRef;
+ } catch (ex) { /* Ignore errors - we'll use what we had before */ }
+
+ if (responseURL != givenURL) {
+ // We were redirected without a meta refresh tag.
+ // Force redirect to the correct place:
+ reject({newURL: xhr.responseURL});
+ return;
+ }
+ resolve(doc);
+ };
+ xhr.send();
+ });
+ },
+
+
+ /**
+ * Retrieves an article from the cache given an article URI.
+ *
+ * @param url The article URL.
+ * @return {Promise}
+ * @resolves JS object representing the article, or null if no article is found.
+ * @rejects OS.File.Error
+ */
+ async getArticleFromCache(url) {
+ let path = this._toHashedPath(url);
+ try {
+ let array = await OS.File.read(path);
+ return JSON.parse(new TextDecoder().decode(array));
+ } catch (e) {
+ if (!(e instanceof OS.File.Error) || !e.becauseNoSuchFile)
+ throw e;
+ return null;
+ }
+ },
+
+ /**
+ * Stores an article in the cache.
+ *
+ * @param article JS object representing article.
+ * @return {Promise}
+ * @resolves When the article is stored.
+ * @rejects OS.File.Error
+ */
+ async storeArticleInCache(article) {
+ let array = new TextEncoder().encode(JSON.stringify(article));
+ let path = this._toHashedPath(article.url);
+ await this._ensureCacheDir();
+ return OS.File.writeAtomic(path, array, { tmpPath: path + ".tmp" })
+ .then(success => {
+ OS.File.stat(path).then(info => {
+ return EventDispatcher.instance.sendRequest({
+ type: "Reader:AddedToCache",
+ url: article.url,
+ size: info.size,
+ path: path,
+ });
+ });
+ });
+ },
+
+ /**
+ * Removes an article from the cache given an article URI.
+ *
+ * @param url The article URL.
+ * @return {Promise}
+ * @resolves When the article is removed.
+ * @rejects OS.File.Error
+ */
+ async removeArticleFromCache(url) {
+ let path = this._toHashedPath(url);
+ await OS.File.remove(path);
+ },
+
+ log(msg) {
+ if (this.DEBUG)
+ dump("Reader: " + msg);
+ },
+
+ /**
+ * Attempts to parse a document into an article. Heavy lifting happens
+ * in readerWorker.js.
+ *
+ * @param doc The document to parse.
+ * @return {Promise}
+ * @resolves JS object representing the article, or null if no article is found.
+ */
+ async _readerParse(doc) {
+ if (this.parseNodeLimit) {
+ let numTags = doc.getElementsByTagName("*").length;
+ if (numTags > this.parseNodeLimit) {
+ this.log("Aborting parse for " + doc.baseURIObject.spec + "; " + numTags + " elements found");
+ return null;
+ }
+ }
+
+ // Fetch this here before we send `doc` off to the worker thread, as later on the
+ // document might be nuked but we will still want the URI.
+ let {documentURI} = doc;
+
+ let uriParam = {
+ spec: doc.baseURIObject.spec,
+ host: doc.baseURIObject.host,
+ prePath: doc.baseURIObject.prePath,
+ scheme: doc.baseURIObject.scheme,
+ pathBase: Services.io.newURI(".", null, doc.baseURIObject).spec
+ };
+
+ // convert text/plain document, if any, to XHTML format
+ if (this._isDocumentPlainText(doc)) {
+ doc = this._convertPlainTextDocument(doc);
+ }
+
+ let langAttributes = {
+ charset: doc.characterSet,
+ lang: doc.documentElement.lang
+ };
+
+ let serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
+ createInstance(Ci.nsIDOMSerializer);
+ let serializedDoc = serializer.serializeToString(doc);
+
+ let options = {
+ classesToPreserve: CLASSES_TO_PRESERVE,
+ };
+
+ let article = null;
+ try {
+ article = await ReaderWorker.post("parseDocument", [uriParam, serializedDoc, options]);
+ } catch (e) {
+ Cu.reportError("Error in ReaderWorker: " + e);
+ }
+
+ // Explicitly null out doc to make it clear it might not be available from this
+ // point on.
+ doc = null;
+
+ if (!article) {
+ this.log("Worker did not return an article");
+ return null;
+ }
+
+ // Readability returns a URI object based on the baseURI, but we only care
+ // about the original document's URL from now on. This also avoids spoofing
+ // attempts where the baseURI doesn't match the domain of the documentURI
+ article.url = documentURI;
+ delete article.uri;
+
+ let flags = Ci.nsIDocumentEncoder.OutputSelectionOnly | Ci.nsIDocumentEncoder.OutputAbsoluteLinks;
+ article.title = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils)
+ .convertToPlainText(article.title, flags, 0);
+
+ await this._assignLanguage(article, langAttributes);
+ this._maybeAssignTextDirection(article);
+
+ this._assignReadTime(article);
+
+ return article;
+ },
+
+ get _cryptoHash() {
+ delete this._cryptoHash;
+ return this._cryptoHash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+ },
+
+ get _unicodeConverter() {
+ delete this._unicodeConverter;
+ this._unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ this._unicodeConverter.charset = "utf8";
+ return this._unicodeConverter;
+ },
+
+ /**
+ * Calculate the hashed path for a stripped article URL.
+ *
+ * @param url The article URL. This should have referrers removed.
+ * @return The file path to the cached article.
+ */
+ _toHashedPath(url) {
+ let value = this._unicodeConverter.convertToByteArray(url);
+ this._cryptoHash.init(this._cryptoHash.MD5);
+ this._cryptoHash.update(value, value.length);
+
+ let hash = CommonUtils.encodeBase32(this._cryptoHash.finish(false));
+ let fileName = hash.substring(0, hash.indexOf("=")) + ".json";
+ return OS.Path.join(OS.Constants.Path.profileDir, "readercache", fileName);
+ },
+
+ /**
+ * Ensures the cache directory exists.
+ *
+ * @return Promise
+ * @resolves When the cache directory exists.
+ * @rejects OS.File.Error
+ */
+ _ensureCacheDir() {
+ let dir = OS.Path.join(OS.Constants.Path.profileDir, "readercache");
+ return OS.File.exists(dir).then(exists => {
+ if (!exists) {
+ return OS.File.makeDir(dir);
+ }
+ return undefined;
+ });
+ },
+
+ /**
+ * Sets a global language string value if possible. If langauge detection is
+ * available, use that. Otherwise, revert to a simpler mechanism using the
+ * document's lang attribute or charset.
+ *
+ * @return Promise
+ * @resolves when the language is detected
+ */
+ _assignLanguage(article, attributes) {
+ try {
+ Cu.import("resource://modules/translation/LanguageDetector.jsm");
+ return LanguageDetector.detectLanguage(article.textContent).then(result => {
+ article.language = result.confident ? result.language : null;
+ });
+ } catch(ex) {
+ return new Promise((resolve) => {
+ resolve(this._assignSimpleLanguage(attributes));
+ }).then(result => {
+ article.language = result;
+ });
+ }
+ },
+
+ _assignSimpleLanguage(attributes) {
+ var lang = attributes.lang.substring(0,2);
+ if (lang) {
+ return lang;
+ }
+
+ // If there is no lang attribute, try the charset.
+ // We can only use this for charsets that are specific to one language.
+ const charsetLang = new Map([
+ [ "us-ascii", "en" ],
+ [ "iso-8859-6", "ar" ],
+ [ "iso-8859-7", "el" ],
+ [ "iso-8859-8", "he" ],
+ [ "iso-8859-9", "tr" ],
+ [ "iso-8859-11", "th" ],
+ [ "jis_x0201", "ja" ],
+ [ "shift_jis", "ja" ],
+ [ "euc-jp", "ja" ]
+ ]);
+
+ return charsetLang.get(attributes.charset);
+ },
+
+ _maybeAssignTextDirection(article) {
+ // TODO: Remove the hardcoded language codes below once bug 1320265 is resolved.
+ if (!article.dir && ["ar", "fa", "he", "ug", "ur"].includes(article.language)) {
+ article.dir = "rtl";
+ }
+ },
+
+ /**
+ * Assigns the estimated reading time range of the article to the article object.
+ *
+ * @param article the article object to assign the reading time estimate to.
+ */
+ _assignReadTime(article) {
+ let lang = article.language || "en";
+ const readingSpeed = this._getReadingSpeedForLanguage(lang);
+ const charactersPerMinuteLow = readingSpeed.cpm - readingSpeed.variance;
+ const charactersPerMinuteHigh = readingSpeed.cpm + readingSpeed.variance;
+ const length = article.length;
+
+ article.readingTimeMinsSlow = Math.ceil(length / charactersPerMinuteLow);
+ article.readingTimeMinsFast = Math.ceil(length / charactersPerMinuteHigh);
+ },
+
+ /**
+ * Returns the reading speed of a selection of languages with likely variance.
+ *
+ * Reading speed estimated from a study done on reading speeds in various languages.
+ * study can be found here: http://iovs.arvojournals.org/article.aspx?articleid=2166061
+ *
+ * @return object with characters per minute and variance. Defaults to English
+ * if no suitable language is found in the collection.
+ */
+ _getReadingSpeedForLanguage(lang) {
+ const readingSpeed = new Map([
+ [ "en", {cpm: 987, variance: 118 } ],
+ [ "ar", {cpm: 612, variance: 88 } ],
+ [ "de", {cpm: 920, variance: 86 } ],
+ [ "es", {cpm: 1025, variance: 127 } ],
+ [ "fi", {cpm: 1078, variance: 121 } ],
+ [ "fr", {cpm: 998, variance: 126 } ],
+ [ "he", {cpm: 833, variance: 130 } ],
+ [ "it", {cpm: 950, variance: 140 } ],
+ [ "jw", {cpm: 357, variance: 56 } ],
+ [ "nl", {cpm: 978, variance: 143 } ],
+ [ "pl", {cpm: 916, variance: 126 } ],
+ [ "pt", {cpm: 913, variance: 145 } ],
+ [ "ru", {cpm: 986, variance: 175 } ],
+ [ "sk", {cpm: 885, variance: 145 } ],
+ [ "sv", {cpm: 917, variance: 156 } ],
+ [ "tr", {cpm: 1054, variance: 156 } ],
+ [ "zh", {cpm: 255, variance: 29 } ],
+ ]);
+
+ return readingSpeed.get(lang) || readingSpeed.get("en");
+ },
+
+ /**
+ *
+ * Check if the document to be parsed is text document.
+ * @param doc the doc object to be parsed.
+ * @return boolean
+ *
+ */
+ _isDocumentPlainText(doc) {
+ return doc.contentType == "text/plain";
+ },
+
+ /**
+ *
+ * The document to be parsed is text document and is converted to HTML format.
+ * @param doc the doc object to be parsed.
+ * @return doc
+ *
+ */
+ _convertPlainTextDocument(doc) {
+ let preTag = doc.querySelector("pre");
+ let docFrag = doc.createDocumentFragment();
+ let content = preTag.textContent;
+ let paragraphs = content.split(/\r?\n\r?\n/);
+ for (let para of paragraphs) {
+ let pElem = doc.createElement("p");
+ let lines = para.split(/\n/);
+ for (let line of lines) {
+ pElem.append(line);
+ let brElem = doc.createElement("br");
+ pElem.append(brElem);
+ }
+ docFrag.append(pElem);
+ }
+ preTag.parentNode.replaceChild(docFrag, preTag);
+ return doc;
+ },
+};
+
+// Don't try to parse the page if it has too many elements (for memory and
+// performance reasons)
+XPCOMUtils.defineLazyPreferenceGetter(
+ ReaderMode, "parseNodeLimit", "reader.parse-node-limit", 0);
diff --git a/components/reader/ReaderWorker.js b/components/reader/ReaderWorker.js
new file mode 100644
index 000000000..9cc684e9b
--- /dev/null
+++ b/components/reader/ReaderWorker.js
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint-env mozilla/chrome-worker */
+
+"use strict";
+
+/**
+ * A worker dedicated to handle parsing documents for reader view.
+ */
+
+importScripts("resource://gre/modules/workers/require.js",
+ "resource://gre/modules/reader/JSDOMParser.js",
+ "resource://gre/modules/reader/Readability.js");
+
+var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");
+
+const DEBUG = false;
+
+var worker = new PromiseWorker.AbstractWorker();
+worker.dispatch = function(method, args = []) {
+ return Agent[method](...args);
+};
+worker.postMessage = function(result, ...transfers) {
+ self.postMessage(result, ...transfers);
+};
+worker.close = function() {
+ self.close();
+};
+worker.log = function(...args) {
+ if (DEBUG) {
+ dump("ReaderWorker: " + args.join(" ") + "\n");
+ }
+};
+
+self.addEventListener("message", msg => worker.handleMessage(msg));
+
+var Agent = {
+ /**
+ * Parses structured article data from a document.
+ *
+ * @param {object} uri URI data for the document.
+ * @param {string} serializedDoc The serialized document.
+ * @param {object} options Options object to pass to Readability.
+ *
+ * @return {object} Article object returned from Readability.
+ */
+ parseDocument(uri, serializedDoc, options) {
+ let doc = new JSDOMParser().parse(serializedDoc, uri.spec);
+ return new Readability(doc, options).parse();
+ },
+};
diff --git a/components/reader/ReaderWorker.jsm b/components/reader/ReaderWorker.jsm
new file mode 100644
index 000000000..ed0ea9aea
--- /dev/null
+++ b/components/reader/ReaderWorker.jsm
@@ -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/. */
+
+"use strict";
+
+/**
+ * Interface to a dedicated thread handling readability parsing.
+ */
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
+
+this.EXPORTED_SYMBOLS = ["ReaderWorker"];
+
+this.ReaderWorker = new BasePromiseWorker("resource://gre/modules/reader/ReaderWorker.js");
diff --git a/components/reader/Readerable.js b/components/reader/Readerable.js
new file mode 100644
index 000000000..cee8adc08
--- /dev/null
+++ b/components/reader/Readerable.js
@@ -0,0 +1,96 @@
+// -*- 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/. */
+"use strict";
+
+// This file and Readability-readerable.js are merged together into
+// Readerable.jsm.
+
+/* exported Readerable */
+/* import-globals-from Readability-readerable.js */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function isNodeVisible(node) {
+ return node.clientHeight > 0 && node.clientWidth > 0;
+}
+
+var Readerable = {
+ DEBUG: 0,
+
+ get isEnabledForParseOnLoad() {
+ return this.isEnabled || this.isForceEnabled;
+ },
+
+ log(msg) {
+ if (this.DEBUG)
+ dump("Reader: " + msg);
+ },
+
+ /**
+ * Decides whether or not a document is reader-able without parsing the whole thing.
+ *
+ * @param doc A document to parse.
+ * @return boolean Whether or not we should show the reader mode button.
+ */
+ isProbablyReaderable(doc) {
+ // Only care about 'real' HTML documents:
+ if (doc.mozSyntheticDocument || !(doc instanceof doc.defaultView.HTMLDocument)) {
+ return false;
+ }
+
+ let uri = Services.io.newURI(doc.location.href);
+ if (!this.shouldCheckUri(uri)) {
+ return false;
+ }
+
+ return isProbablyReaderable(doc, isNodeVisible);
+ },
+
+ _blockedHosts: [
+ "amazon.com",
+ "basilisk-browser.org",
+ "github.com",
+ "mail.google.com",
+ "palemoon.org",
+ "pinterest.com",
+ "reddit.com",
+ "twitter.com",
+ "youtube.com",
+ ],
+
+ shouldCheckUri(uri, isBaseUri = false) {
+ if (!(uri.schemeIs("http") || uri.schemeIs("https"))) {
+ this.log("Not parsing URI scheme: " + uri.scheme);
+ return false;
+ }
+
+ try {
+ uri.QueryInterface(Ci.nsIURL);
+ } catch (ex) {
+ // If this doesn't work, presumably the URL is not well-formed or something
+ return false;
+ }
+ // Sadly, some high-profile pages have false positives, so bail early for those:
+ let asciiHost = uri.asciiHost;
+ if (!isBaseUri && this._blockedHosts.some(blockedHost => asciiHost.endsWith(blockedHost))) {
+ return false;
+ }
+
+ if (!isBaseUri && (!uri.filePath || uri.filePath == "/")) {
+ this.log("Not parsing home page: " + uri.spec);
+ return false;
+ }
+
+ return true;
+ },
+};
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ Readerable, "isEnabled", "reader.parse-on-load.enabled", true);
+XPCOMUtils.defineLazyPreferenceGetter(
+ Readerable, "isForceEnabled", "reader.parse-on-load.force-enabled", false);
diff --git a/components/reader/Readerable.jsm b/components/reader/Readerable.jsm
new file mode 100644
index 000000000..2268487e4
--- /dev/null
+++ b/components/reader/Readerable.jsm
@@ -0,0 +1,10 @@
+// -*- 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/. */
+"use strict";
+
+var EXPORTED_SYMBOLS = ["Readerable"];
+
+#include Readability-readerable.js
+#include Readerable.js
diff --git a/components/reader/content/aboutReader.html b/components/reader/content/aboutReader.html
new file mode 100644
index 000000000..58006c0a5
--- /dev/null
+++ b/components/reader/content/aboutReader.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+ <meta name="viewport" content="width=device-width; user-scalable=0" />
+
+ <link rel="stylesheet" href="chrome://global/skin/aboutReader.css" type="text/css"/>
+
+ <script type="text/javascript" src="chrome://global/content/reader/aboutReader.js"></script>
+</head>
+
+<body>
+ <div class="container">
+ <div class="header reader-header">
+ <a class="domain reader-domain"></a>
+ <div class="domain-border"></div>
+ <h1 class="reader-title"></h1>
+ <div class="credits reader-credits"></div>
+ <div class="meta-data">
+ <div class="reader-estimated-time"></div>
+ </div>
+ </div>
+
+ <hr>
+
+ <div class="content">
+ <div class="moz-reader-content"></div>
+ </div>
+
+ <div>
+ <div class="reader-message"></div>
+ </div>
+ </div>
+
+ <ul class="toolbar reader-toolbar">
+ <li><button class="button close-button"/></li>
+ <ul class="dropdown style-dropdown">
+ <li><button class="dropdown-toggle button style-button"/></li>
+ <li class="dropdown-popup">
+ <div class="font-type-buttons"></div>
+ <hr>
+ <div class="font-size-buttons">
+ <button class="minus-button"/>
+ <button class="plus-button"/>
+ </div>
+ <hr>
+ <div class="content-width-buttons">
+ <button class="content-width-minus-button"/>
+ <button class="content-width-plus-button"/>
+ </div>
+ <hr>
+ <div class="line-height-buttons">
+ <button class="line-height-minus-button"/>
+ <button class="line-height-plus-button"/>
+ </div>
+ <hr>
+ <div class="color-scheme-buttons"></div>
+ <div class="dropdown-arrow"/>
+ </li>
+ </ul>
+ </ul>
+
+</body>
+
+</html>
diff --git a/components/reader/content/aboutReader.js b/components/reader/content/aboutReader.js
new file mode 100644
index 000000000..6c963382e
--- /dev/null
+++ b/components/reader/content/aboutReader.js
@@ -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/. */
+
+"use strict";
+
+window.addEventListener("DOMContentLoaded", function() {
+ document.dispatchEvent(new CustomEvent("AboutReaderContentLoaded", { bubbles: true }));
+});
diff --git a/components/reader/jar.mn b/components/reader/jar.mn
new file mode 100644
index 000000000..241f1e693
--- /dev/null
+++ b/components/reader/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/.
+
+toolkit.jar:
+ content/global/reader/aboutReader.html (content/aboutReader.html)
+ content/global/reader/aboutReader.js (content/aboutReader.js)
diff --git a/components/reader/moz.build b/components/reader/moz.build
new file mode 100644
index 000000000..322952b9f
--- /dev/null
+++ b/components/reader/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+ 'AboutReader.jsm',
+ 'ReaderMode.jsm'
+]
+
+EXTRA_PP_JS_MODULES += ['Readerable.jsm']
+
+EXTRA_JS_MODULES.reader = [
+ 'JSDOMParser.js',
+ 'Readability.js',
+ 'ReaderWorker.js',
+ 'ReaderWorker.jsm'
+]
diff --git a/components/reflect/moz.build b/components/reflect/moz.build
new file mode 100644
index 000000000..9a3f27116
--- /dev/null
+++ b/components/reflect/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+SOURCES += [
+ 'reflect.cpp',
+]
+
+EXTRA_JS_MODULES += [
+ 'reflect.jsm',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/reflect/reflect.cpp b/components/reflect/reflect.cpp
new file mode 100644
index 000000000..cd46baf7c
--- /dev/null
+++ b/components/reflect/reflect.cpp
@@ -0,0 +1,77 @@
+/* -*- 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 "reflect.h"
+#include "jsapi.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsMemory.h"
+#include "nsString.h"
+#include "nsNativeCharsetUtils.h"
+#include "xpc_make_class.h"
+
+#define JSREFLECT_CONTRACTID \
+ "@mozilla.org/jsreflect;1"
+
+#define JSREFLECT_CID \
+{ 0x1a817186, 0x357a, 0x47cd, { 0x8a, 0xea, 0x28, 0x50, 0xd6, 0x0e, 0x95, 0x9e } }
+
+namespace mozilla {
+namespace reflect {
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(Module)
+
+NS_IMPL_ISUPPORTS(Module, nsIXPCScriptable)
+
+Module::Module()
+{
+}
+
+Module::~Module()
+{
+}
+
+#define XPC_MAP_CLASSNAME Module
+#define XPC_MAP_QUOTED_CLASSNAME "Module"
+#define XPC_MAP_WANT_CALL
+#define XPC_MAP_FLAGS nsIXPCScriptable::WANT_CALL
+#include "xpc_map_end.h"
+
+NS_IMETHODIMP
+Module::Call(nsIXPConnectWrappedNative* wrapper,
+ JSContext* cx,
+ JSObject* obj,
+ const JS::CallArgs& args,
+ bool* _retval)
+{
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ if (!global)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *_retval = JS_InitReflectParse(cx, global);
+ return NS_OK;
+}
+
+} // namespace reflect
+} // namespace mozilla
+
+NS_DEFINE_NAMED_CID(JSREFLECT_CID);
+
+static const mozilla::Module::CIDEntry kReflectCIDs[] = {
+ { &kJSREFLECT_CID, false, nullptr, mozilla::reflect::ModuleConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kReflectContracts[] = {
+ { JSREFLECT_CONTRACTID, &kJSREFLECT_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kReflectModule = {
+ mozilla::Module::kVersion,
+ kReflectCIDs,
+ kReflectContracts
+};
+
+NSMODULE_DEFN(jsreflect) = &kReflectModule;
diff --git a/components/reflect/reflect.h b/components/reflect/reflect.h
new file mode 100644
index 000000000..6f5237104
--- /dev/null
+++ b/components/reflect/reflect.h
@@ -0,0 +1,30 @@
+/* -*- 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 COMPONENTS_REFLECT_H
+#define COMPONENTS_REFLECT_H
+
+#include "nsIXPCScriptable.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace reflect {
+
+class Module final : public nsIXPCScriptable
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIXPCSCRIPTABLE
+
+ Module();
+
+private:
+ ~Module();
+};
+
+} // namespace reflect
+} // namespace mozilla
+
+#endif
diff --git a/components/reflect/reflect.jsm b/components/reflect/reflect.jsm
new file mode 100644
index 000000000..fd1729dd9
--- /dev/null
+++ b/components/reflect/reflect.jsm
@@ -0,0 +1,24 @@
+/* -*- 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/. */
+
+this.EXPORTED_SYMBOLS = [ "Reflect" ];
+
+/*
+ * This is the js module for Reflect. Import it like so:
+ * Components.utils.import("resource://gre/modules/reflect.jsm");
+ *
+ * This will create a 'Reflect' object, which provides an interface to the
+ * SpiderMonkey parser API.
+ *
+ * For documentation on the API, see:
+ * https://developer.mozilla.org/en/SpiderMonkey/Parser_API
+ *
+ */
+
+
+// Initialize the ctypes object. You do not need to do this yourself.
+const init = Components.classes["@mozilla.org/jsreflect;1"].createInstance();
+init();
+this.Reflect = Reflect;
diff --git a/components/registry/moz.build b/components/registry/moz.build
new file mode 100644
index 000000000..684e37802
--- /dev/null
+++ b/components/registry/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsIChromeRegistry.idl',
+ 'public/nsIToolkitChromeRegistry.idl',
+]
+
+EXPORTS.mozilla.chrome += ['src/RegistryMessageUtils.h']
+
+SOURCES += [
+ 'src/nsChromeProtocolHandler.cpp',
+ 'src/nsChromeRegistry.cpp',
+ 'src/nsChromeRegistryChrome.cpp',
+ 'src/nsChromeRegistryContent.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '!/xpcom',
+ '/dom/base',
+ '/system/network/base',
+ '/system/network/protocol/res',
+ '/xpcom/components'
+]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+
+XPIDL_MODULE = 'chrome'
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild') \ No newline at end of file
diff --git a/components/registry/public/nsIChromeRegistry.idl b/components/registry/public/nsIChromeRegistry.idl
new file mode 100644
index 000000000..8df3a8bdf
--- /dev/null
+++ b/components/registry/public/nsIChromeRegistry.idl
@@ -0,0 +1,116 @@
+/* -*- 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 nsIURI;
+
+[scriptable, uuid(249fb5ad-ae29-4e2c-a728-ba5cf464d188)]
+interface nsIChromeRegistry : nsISupports
+{
+ const int32_t NONE = 0;
+ const int32_t PARTIAL = 1;
+ const int32_t FULL = 2;
+
+ /**
+ * Resolve a chrome URL to an loadable URI using the information in the
+ * registry. Does not modify aChromeURL.
+ *
+ * Chrome URLs are allowed to be specified in "shorthand", leaving the
+ * "file" portion off. In that case, the URL is expanded to:
+ *
+ * chrome://package/provider/package.ext
+ *
+ * where "ext" is:
+ *
+ * "xul" for a "content" package,
+ * "css" for a "skin" package, and
+ * "dtd" for a "locale" package.
+ *
+ * @param aChromeURL the URL that is to be converted.
+ */
+ nsIURI convertChromeURL(in nsIURI aChromeURL);
+
+ /**
+ * refresh the chrome list at runtime, looking for new packages/etc
+ */
+ void checkForNewChrome();
+
+ /**
+ * returns whether XPCNativeWrappers are enabled for aURI.
+ */
+ [notxpcom] boolean wrappersEnabled(in nsIURI aURI);
+};
+
+[scriptable, uuid(93251ddf-5e85-4172-ac2a-31780562974f)]
+interface nsIXULChromeRegistry : nsIChromeRegistry
+{
+ /* Should be called when locales change to reload all chrome (including XUL). */
+ void reloadChrome();
+
+ // If the optional asBCP47 parameter is true, the locale code will be
+ // converted to a BCP47 language tag; in particular, this means that
+ // "ja-JP-mac" will be returned as "ja-JP-x-lvariant-mac", which can be
+ // passed to ECMA402 Intl API methods without throwing a RangeError.
+ ACString getSelectedLocale(in ACString packageName,
+ [optional] in boolean asBCP47);
+
+ // Get the direction of the locale via the intl.uidirection.<locale> pref
+ boolean isLocaleRTL(in ACString package);
+
+ /* Should be called when skins change. Reloads only stylesheets. */
+ void refreshSkins();
+
+ /**
+ * Installable skin XBL is not always granted the same privileges as other
+ * chrome. This asks the chrome registry whether scripts are allowed to be
+ * run for a particular chrome URI. Do not pass non-chrome URIs to this
+ * method.
+ */
+ boolean allowScriptsForPackage(in nsIURI url);
+
+ /**
+ * Content should only be allowed to load chrome JS from certain packages.
+ * This method reflects the contentaccessible flag on packages.
+ * Do not pass non-chrome URIs to this method.
+ */
+ boolean allowContentToAccess(in nsIURI url);
+
+ /**
+ * Returns true if the passed chrome URL is allowed to be loaded in a remote
+ * process. This reflects the remoteenabled flag on packages.
+ * Do not pass non-chrome URIs to this method.
+ */
+ boolean canLoadURLRemotely(in nsIURI url);
+
+ /**
+ * Returns true if the passed chrome URL must be loaded in a remote process.
+ * This reflects the remoterequired flag on packages.
+ * Do not pass non-chrome URIs to this method.
+ */
+ boolean mustLoadURLRemotely(in nsIURI url);
+};
+
+%{ C++
+
+#define NS_CHROMEREGISTRY_CONTRACTID \
+ "@mozilla.org/chrome/chrome-registry;1"
+
+/**
+ * Chrome registry will notify various caches that all chrome files need
+ * flushing.
+ */
+#define NS_CHROME_FLUSH_TOPIC \
+ "chrome-flush-caches"
+
+/**
+ * Chrome registry will notify various caches that skin files need flushing.
+ * If "chrome-flush-caches" is notified, this topic will *not* be notified.
+ */
+#define NS_CHROME_FLUSH_SKINS_TOPIC \
+ "chrome-flush-skin-caches"
+
+%}
diff --git a/components/registry/public/nsIToolkitChromeRegistry.idl b/components/registry/public/nsIToolkitChromeRegistry.idl
new file mode 100644
index 000000000..3a9884517
--- /dev/null
+++ b/components/registry/public/nsIToolkitChromeRegistry.idl
@@ -0,0 +1,28 @@
+/* -*- 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 "nsIChromeRegistry.idl"
+
+interface nsIURI;
+interface nsIUTF8StringEnumerator;
+
+[scriptable, uuid(8727651c-9530-45a0-b81e-0e0690c30c50)]
+interface nsIToolkitChromeRegistry : nsIXULChromeRegistry
+{
+ /**
+ * If the OS has a "high-visibility" or "disabled-friendly" theme set,
+ * we want to force mozilla into the classic theme, which (for the most part
+ * obeys the system color/font settings. We cannot do this at initialization,
+ * because it depends on the toolkit (GTK2) being initialized, which is
+ * not the case in some embedding situations. Embedders have to manually
+ * call this method during the startup process.
+ */
+ void checkForOSAccessibility();
+
+ /**
+ * Get a list of locales available for the specified package.
+ */
+ nsIUTF8StringEnumerator getLocalesForPackage(in AUTF8String aPackage);
+};
diff --git a/components/registry/src/RegistryMessageUtils.h b/components/registry/src/RegistryMessageUtils.h
new file mode 100644
index 000000000..a9dc81331
--- /dev/null
+++ b/components/registry/src/RegistryMessageUtils.h
@@ -0,0 +1,209 @@
+/* -*- 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/. */
+
+#ifndef mozilla_RegistryMessageUtils_h
+#define mozilla_RegistryMessageUtils_h
+
+#include "ipc/IPCMessageUtils.h"
+#include "nsString.h"
+
+struct SerializedURI
+{
+ nsCString spec;
+ nsCString charset;
+
+ bool operator ==(const SerializedURI& rhs) const
+ {
+ return spec.Equals(rhs.spec) &&
+ charset.Equals(rhs.charset);
+ }
+};
+
+struct ChromePackage
+{
+ nsCString package;
+ SerializedURI contentBaseURI;
+ SerializedURI localeBaseURI;
+ SerializedURI skinBaseURI;
+ uint32_t flags;
+
+ bool operator ==(const ChromePackage& rhs) const
+ {
+ return package.Equals(rhs.package) &&
+ contentBaseURI == rhs.contentBaseURI &&
+ localeBaseURI == rhs.localeBaseURI &&
+ skinBaseURI == rhs.skinBaseURI &&
+ flags == rhs.flags;
+ }
+};
+
+struct SubstitutionMapping
+{
+ nsCString scheme;
+ nsCString path;
+ SerializedURI resolvedURI;
+
+ bool operator ==(const SubstitutionMapping& rhs) const
+ {
+ return scheme.Equals(rhs.scheme) &&
+ path.Equals(rhs.path) &&
+ resolvedURI == rhs.resolvedURI;
+ }
+};
+
+struct OverrideMapping
+{
+ SerializedURI originalURI;
+ SerializedURI overrideURI;
+
+ bool operator==(const OverrideMapping& rhs) const
+ {
+ return originalURI == rhs.originalURI &&
+ overrideURI == rhs.overrideURI;
+ }
+};
+
+namespace IPC {
+
+template<>
+struct ParamTraits<SerializedURI>
+{
+ typedef SerializedURI paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.spec);
+ WriteParam(aMsg, aParam.charset);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ nsCString spec, charset;
+ if (ReadParam(aMsg, aIter, &spec) &&
+ ReadParam(aMsg, aIter, &charset)) {
+ aResult->spec = spec;
+ aResult->charset = charset;
+ return true;
+ }
+ return false;
+ }
+};
+
+template <>
+struct ParamTraits<ChromePackage>
+{
+ typedef ChromePackage paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.package);
+ WriteParam(aMsg, aParam.contentBaseURI);
+ WriteParam(aMsg, aParam.localeBaseURI);
+ WriteParam(aMsg, aParam.skinBaseURI);
+ WriteParam(aMsg, aParam.flags);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ nsCString package;
+ SerializedURI contentBaseURI, localeBaseURI, skinBaseURI;
+ uint32_t flags;
+
+ if (ReadParam(aMsg, aIter, &package) &&
+ ReadParam(aMsg, aIter, &contentBaseURI) &&
+ ReadParam(aMsg, aIter, &localeBaseURI) &&
+ ReadParam(aMsg, aIter, &skinBaseURI) &&
+ ReadParam(aMsg, aIter, &flags)) {
+ aResult->package = package;
+ aResult->contentBaseURI = contentBaseURI;
+ aResult->localeBaseURI = localeBaseURI;
+ aResult->skinBaseURI = skinBaseURI;
+ aResult->flags = flags;
+ return true;
+ }
+ return false;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog)
+ {
+ aLog->append(StringPrintf(L"[%s, %s, %s, %s, %u]", aParam.package.get(),
+ aParam.contentBaseURI.spec.get(),
+ aParam.localeBaseURI.spec.get(),
+ aParam.skinBaseURI.spec.get(), aParam.flags));
+ }
+};
+
+template <>
+struct ParamTraits<SubstitutionMapping>
+{
+ typedef SubstitutionMapping paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.scheme);
+ WriteParam(aMsg, aParam.path);
+ WriteParam(aMsg, aParam.resolvedURI);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ nsCString scheme, path;
+ SerializedURI resolvedURI;
+
+ if (ReadParam(aMsg, aIter, &scheme) &&
+ ReadParam(aMsg, aIter, &path) &&
+ ReadParam(aMsg, aIter, &resolvedURI)) {
+ aResult->scheme = scheme;
+ aResult->path = path;
+ aResult->resolvedURI = resolvedURI;
+ return true;
+ }
+ return false;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog)
+ {
+ aLog->append(StringPrintf(L"[%s://%s, %s, %u]",
+ aParam.scheme.get(),
+ aParam.path.get(),
+ aParam.resolvedURI.spec.get()));
+ }
+};
+
+template <>
+struct ParamTraits<OverrideMapping>
+{
+ typedef OverrideMapping paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ WriteParam(aMsg, aParam.originalURI);
+ WriteParam(aMsg, aParam.overrideURI);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ SerializedURI originalURI;
+ SerializedURI overrideURI;
+
+ if (ReadParam(aMsg, aIter, &originalURI) &&
+ ReadParam(aMsg, aIter, &overrideURI)) {
+ aResult->originalURI = originalURI;
+ aResult->overrideURI = overrideURI;
+ return true;
+ }
+ return false;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog)
+ {
+ aLog->append(StringPrintf(L"[%s, %s, %u]", aParam.originalURI.spec.get(),
+ aParam.overrideURI.spec.get()));
+ }
+};
+
+} // namespace IPC
+
+#endif // RegistryMessageUtils_h
diff --git a/components/registry/src/nsChromeProtocolHandler.cpp b/components/registry/src/nsChromeProtocolHandler.cpp
new file mode 100644
index 000000000..58a8cb7e3
--- /dev/null
+++ b/components/registry/src/nsChromeProtocolHandler.cpp
@@ -0,0 +1,214 @@
+/* -*- 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/. */
+
+/*
+
+ A protocol handler for ``chrome:''
+
+*/
+
+#include "nsChromeProtocolHandler.h"
+#include "nsChromeRegistry.h"
+#include "nsCOMPtr.h"
+#include "nsThreadUtils.h"
+#include "nsIChannel.h"
+#include "nsIChromeRegistry.h"
+#include "nsIFile.h"
+#include "nsIFileChannel.h"
+#include "nsIIOService.h"
+#include "nsILoadGroup.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIStandardURL.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIURL.h"
+#include "nsString.h"
+#include "nsStandardURL.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsChromeProtocolHandler,
+ nsIProtocolHandler,
+ nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsChromeProtocolHandler::GetScheme(nsACString &result)
+{
+ result.AssignLiteral("chrome");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeProtocolHandler::GetDefaultPort(int32_t *result)
+{
+ *result = -1; // no port for chrome: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval)
+{
+ // don't override anything.
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeProtocolHandler::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeProtocolHandler::NewURI(const nsACString &aSpec,
+ const char *aCharset,
+ nsIURI *aBaseURI,
+ nsIURI **result)
+{
+
+ // Chrome: URLs (currently) have no additional structure beyond that provided
+ // by standard URLs, so there is no "outer" given to CreateInstance
+
+ RefPtr<mozilla::net::nsStandardURL> surl = new mozilla::net::nsStandardURL();
+
+ nsresult rv = surl->Init(nsIStandardURL::URLTYPE_STANDARD, -1, aSpec,
+ aCharset, aBaseURI);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Canonify the "chrome:" URL; e.g., so that we collapse
+ // "chrome://navigator/content/" and "chrome://navigator/content"
+ // and "chrome://navigator/content/navigator.xul".
+
+ rv = nsChromeRegistry::Canonify(surl);
+ if (NS_FAILED(rv))
+ return rv;
+
+ surl->SetMutable(false);
+
+ surl.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeProtocolHandler::NewChannel2(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** aResult)
+{
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_PRECONDITION(aResult, "Null out param");
+
+#ifdef DEBUG
+ // Check that the uri we got is already canonified
+ nsresult debug_rv;
+ nsCOMPtr<nsIURI> debugClone;
+ debug_rv = aURI->Clone(getter_AddRefs(debugClone));
+ if (NS_SUCCEEDED(debug_rv)) {
+ nsCOMPtr<nsIURL> debugURL (do_QueryInterface(debugClone));
+ debug_rv = nsChromeRegistry::Canonify(debugURL);
+ if (NS_SUCCEEDED(debug_rv)) {
+ bool same;
+ debug_rv = aURI->Equals(debugURL, &same);
+ if (NS_SUCCEEDED(debug_rv)) {
+ NS_ASSERTION(same, "Non-canonified chrome uri passed to nsChromeProtocolHandler::NewChannel!");
+ }
+ }
+ }
+#endif
+
+ nsCOMPtr<nsIChannel> result;
+
+ if (!nsChromeRegistry::gChromeRegistry) {
+ // We don't actually want this ref, we just want the service to
+ // initialize if it hasn't already.
+ nsCOMPtr<nsIChromeRegistry> reg =
+ mozilla::services::GetChromeRegistryService();
+ NS_ENSURE_TRUE(nsChromeRegistry::gChromeRegistry, NS_ERROR_FAILURE);
+ }
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ rv = nsChromeRegistry::gChromeRegistry->ConvertChromeURL(aURI, getter_AddRefs(resolvedURI));
+ if (NS_FAILED(rv)) {
+#ifdef DEBUG
+ printf("Couldn't convert chrome URL: %s\n",
+ aURI->GetSpecOrDefault().get());
+#endif
+ return rv;
+ }
+
+ rv = NS_NewChannelInternal(getter_AddRefs(result),
+ resolvedURI,
+ aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef DEBUG
+ nsCOMPtr<nsIFileChannel> fileChan(do_QueryInterface(result));
+ if (fileChan) {
+ nsCOMPtr<nsIFile> file;
+ fileChan->GetFile(getter_AddRefs(file));
+
+ bool exists = false;
+ file->Exists(&exists);
+ if (!exists) {
+ nsAutoCString path;
+ file->GetPersistentDescriptor(path);
+ printf("Chrome file doesn't exist: %s\n", path.get());
+ }
+ }
+#endif
+
+ // Make sure that the channel remembers where it was
+ // originally loaded from.
+ nsLoadFlags loadFlags = 0;
+ result->GetLoadFlags(&loadFlags);
+ result->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE);
+ rv = result->SetOriginalURI(aURI);
+ if (NS_FAILED(rv)) return rv;
+
+ // Get a system principal for content files and set the owner
+ // property of the result
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+ nsAutoCString path;
+ rv = url->GetPath(path);
+ if (StringBeginsWith(path, NS_LITERAL_CSTRING("/content/")))
+ {
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = securityManager->GetSystemPrincipal(getter_AddRefs(principal));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISupports> owner = do_QueryInterface(principal);
+ result->SetOwner(owner);
+ }
+
+ // XXX Removed dependency-tracking code from here, because we're not
+ // tracking them anyways (with fastload we checked only in DEBUG
+ // and with startupcache not at all), but this is where we would start
+ // if we need to re-add.
+ // See bug 531886, bug 533038.
+ result->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
+
+ *aResult = result;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeProtocolHandler::NewChannel(nsIURI* aURI,
+ nsIChannel* *aResult)
+{
+ return NewChannel2(aURI, nullptr, aResult);
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/components/registry/src/nsChromeProtocolHandler.h b/components/registry/src/nsChromeProtocolHandler.h
new file mode 100644
index 000000000..42c3f52be
--- /dev/null
+++ b/components/registry/src/nsChromeProtocolHandler.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsChromeProtocolHandler_h___
+#define nsChromeProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+#define NS_CHROMEPROTOCOLHANDLER_CID \
+{ /* 61ba33c0-3031-11d3-8cd0-0060b0fc14a3 */ \
+ 0x61ba33c0, \
+ 0x3031, \
+ 0x11d3, \
+ {0x8c, 0xd0, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \
+}
+
+class nsChromeProtocolHandler final : public nsIProtocolHandler,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIProtocolHandler methods:
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ // nsChromeProtocolHandler methods:
+ nsChromeProtocolHandler() {}
+
+private:
+ ~nsChromeProtocolHandler() {}
+};
+
+#endif /* nsChromeProtocolHandler_h___ */
diff --git a/components/registry/src/nsChromeRegistry.cpp b/components/registry/src/nsChromeRegistry.cpp
new file mode 100644
index 000000000..ef2cb79ab
--- /dev/null
+++ b/components/registry/src/nsChromeRegistry.cpp
@@ -0,0 +1,730 @@
+/* -*- Mode: C++; 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/. */
+
+#include "nsChromeRegistry.h"
+#include "nsChromeRegistryChrome.h"
+#include "nsChromeRegistryContent.h"
+
+#include "prprf.h"
+
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsEscape.h"
+#include "nsNetUtil.h"
+#include "nsIURL.h"
+#include "nsString.h"
+#include "nsQueryObject.h"
+
+#include "mozilla/dom/URL.h"
+#include "nsIConsoleService.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMLocation.h"
+#include "nsIDOMWindowCollection.h"
+#include "nsIDOMWindow.h"
+#include "nsIObserverService.h"
+#include "nsIPresShell.h"
+#include "nsIScriptError.h"
+#include "nsIWindowMediator.h"
+#include "nsIPrefService.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+
+#include "unicode/uloc.h"
+
+nsChromeRegistry* nsChromeRegistry::gChromeRegistry;
+
+// DO NOT use namespace mozilla; it'll break due to a naming conflict between
+// mozilla::TextRange and a TextRange in OSX headers.
+using mozilla::StyleSheet;
+using mozilla::dom::IsChromeURI;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void
+nsChromeRegistry::LogMessage(const char* aMsg, ...)
+{
+ nsCOMPtr<nsIConsoleService> console
+ (do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (!console)
+ return;
+
+ va_list args;
+ va_start(args, aMsg);
+ char* formatted = PR_vsmprintf(aMsg, args);
+ va_end(args);
+ if (!formatted)
+ return;
+
+ console->LogStringMessage(NS_ConvertUTF8toUTF16(formatted).get());
+ PR_smprintf_free(formatted);
+}
+
+void
+nsChromeRegistry::LogMessageWithContext(nsIURI* aURL, uint32_t aLineNumber, uint32_t flags,
+ const char* aMsg, ...)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIConsoleService> console
+ (do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+
+ nsCOMPtr<nsIScriptError> error
+ (do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ if (!console || !error)
+ return;
+
+ va_list args;
+ va_start(args, aMsg);
+ char* formatted = PR_vsmprintf(aMsg, args);
+ va_end(args);
+ if (!formatted)
+ return;
+
+ nsCString spec;
+ if (aURL)
+ aURL->GetSpec(spec);
+
+ rv = error->Init(NS_ConvertUTF8toUTF16(formatted),
+ NS_ConvertUTF8toUTF16(spec),
+ EmptyString(),
+ aLineNumber, 0, flags, "chrome registration");
+ PR_smprintf_free(formatted);
+
+ if (NS_FAILED(rv))
+ return;
+
+ console->LogMessage(error);
+}
+
+nsChromeRegistry::~nsChromeRegistry()
+{
+ gChromeRegistry = nullptr;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsChromeRegistry)
+ NS_INTERFACE_MAP_ENTRY(nsIChromeRegistry)
+ NS_INTERFACE_MAP_ENTRY(nsIXULChromeRegistry)
+ NS_INTERFACE_MAP_ENTRY(nsIToolkitChromeRegistry)
+ NS_INTERFACE_MAP_ENTRY(nsIXULOverlayProvider)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChromeRegistry)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsChromeRegistry)
+NS_IMPL_RELEASE(nsChromeRegistry)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIChromeRegistry methods:
+
+already_AddRefed<nsIChromeRegistry>
+nsChromeRegistry::GetService()
+{
+ if (!gChromeRegistry)
+ {
+ // We don't actually want this ref, we just want the service to
+ // initialize if it hasn't already.
+ nsCOMPtr<nsIChromeRegistry> reg(
+ do_GetService(NS_CHROMEREGISTRY_CONTRACTID));
+ if (!gChromeRegistry)
+ return nullptr;
+ }
+ nsCOMPtr<nsIChromeRegistry> registry = gChromeRegistry;
+ return registry.forget();
+}
+
+nsresult
+nsChromeRegistry::Init()
+{
+ // This initialization process is fairly complicated and may cause reentrant
+ // getservice calls to resolve chrome URIs (especially locale files). We
+ // don't want that, so we inform the protocol handler about our existence
+ // before we are actually fully initialized.
+ gChromeRegistry = this;
+
+ mInitialized = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsChromeRegistry::GetProviderAndPath(nsIURL* aChromeURL,
+ nsACString& aProvider, nsACString& aPath)
+{
+ nsresult rv;
+
+#ifdef DEBUG
+ bool isChrome;
+ aChromeURL->SchemeIs("chrome", &isChrome);
+ NS_ASSERTION(isChrome, "Non-chrome URI?");
+#endif
+
+ nsAutoCString path;
+ rv = aChromeURL->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (path.Length() < 3) {
+ LogMessage("Invalid chrome URI: %s", path.get());
+ return NS_ERROR_FAILURE;
+ }
+
+ path.SetLength(nsUnescapeCount(path.BeginWriting()));
+ NS_ASSERTION(path.First() == '/', "Path should always begin with a slash!");
+
+ int32_t slash = path.FindChar('/', 1);
+ if (slash == 1) {
+ LogMessage("Invalid chrome URI: %s", path.get());
+ return NS_ERROR_FAILURE;
+ }
+
+ if (slash == -1) {
+ aPath.Truncate();
+ }
+ else {
+ if (slash == (int32_t) path.Length() - 1)
+ aPath.Truncate();
+ else
+ aPath.Assign(path.get() + slash + 1, path.Length() - slash - 1);
+
+ --slash;
+ }
+
+ aProvider.Assign(path.get() + 1, slash);
+ return NS_OK;
+}
+
+
+nsresult
+nsChromeRegistry::Canonify(nsIURL* aChromeURL)
+{
+ NS_NAMED_LITERAL_CSTRING(kSlash, "/");
+
+ nsresult rv;
+
+ nsAutoCString provider, path;
+ rv = GetProviderAndPath(aChromeURL, provider, path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (path.IsEmpty()) {
+ nsAutoCString package;
+ rv = aChromeURL->GetHost(package);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we re-use the "path" local string to build a new URL path
+ path.Assign(kSlash + provider + kSlash + package);
+ if (provider.EqualsLiteral("content")) {
+ path.AppendLiteral(".xul");
+ }
+ else if (provider.EqualsLiteral("locale")) {
+ path.AppendLiteral(".dtd");
+ }
+ else if (provider.EqualsLiteral("skin")) {
+ path.AppendLiteral(".css");
+ }
+ else {
+ return NS_ERROR_INVALID_ARG;
+ }
+ aChromeURL->SetPath(path);
+ }
+ else {
+ // path is already unescaped once, but uris can get unescaped twice
+ const char* pos = path.BeginReading();
+ const char* end = path.EndReading();
+ if (*pos == '/' || *pos == ' ') {
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ while (pos < end) {
+ switch (*pos) {
+ case ':':
+ return NS_ERROR_DOM_BAD_URI;
+ case '.':
+ // prevent directory traversals ("..")
+ if (pos[1] == '.')
+ return NS_ERROR_DOM_BAD_URI;
+ break;
+ case '%':
+ // chrome: URIs with double-escapes are trying to trick us.
+ // watch for %2e, and %25 in case someone triple unescapes
+ if (pos[1] == '2' &&
+ ( pos[2] == 'e' || pos[2] == 'E' ||
+ pos[2] == '5' ))
+ return NS_ERROR_DOM_BAD_URI;
+ break;
+ case '?':
+ case '#':
+ pos = end;
+ continue;
+ }
+ ++pos;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeRegistry::ConvertChromeURL(nsIURI* aChromeURI, nsIURI* *aResult)
+{
+ nsresult rv;
+ if (NS_WARN_IF(!aChromeURI)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mOverrideTable.Get(aChromeURI, aResult))
+ return NS_OK;
+
+ nsCOMPtr<nsIURL> chromeURL (do_QueryInterface(aChromeURI));
+ NS_ENSURE_TRUE(chromeURL, NS_NOINTERFACE);
+
+ nsAutoCString package, provider, path;
+ rv = chromeURL->GetHostPort(package);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetProviderAndPath(chromeURL, provider, path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIURI* baseURI = GetBaseURIFromPackage(package, provider, path);
+
+ uint32_t flags;
+ rv = GetFlagsFromPackage(package, &flags);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (flags & PLATFORM_PACKAGE) {
+#if defined(XP_WIN)
+ path.Insert("win/", 0);
+#else
+ path.Insert("unix/", 0);
+#endif
+ }
+
+ if (!baseURI) {
+ LogMessage("No chrome package registered for chrome://%s/%s/%s",
+ package.get(), provider.get(), path.get());
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ return NS_NewURI(aResult, path, nullptr, baseURI);
+}
+
+////////////////////////////////////////////////////////////////////////
+
+// theme stuff
+
+
+static void FlushSkinBindingsForWindow(nsPIDOMWindowOuter* aWindow)
+{
+ // Get the document.
+ nsCOMPtr<nsIDocument> document = aWindow->GetDoc();
+ if (!document)
+ return;
+
+ // Annihilate all XBL bindings.
+ document->FlushSkinBindings();
+}
+
+// XXXbsmedberg: move this to nsIWindowMediator
+NS_IMETHODIMP nsChromeRegistry::RefreshSkins()
+{
+ nsCOMPtr<nsIWindowMediator> windowMediator
+ (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (!windowMediator)
+ return NS_OK;
+
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+ windowMediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
+ bool more;
+ windowEnumerator->HasMoreElements(&more);
+ while (more) {
+ nsCOMPtr<nsISupports> protoWindow;
+ windowEnumerator->GetNext(getter_AddRefs(protoWindow));
+ if (protoWindow) {
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = do_QueryInterface(protoWindow);
+ if (domWindow)
+ FlushSkinBindingsForWindow(domWindow);
+ }
+ windowEnumerator->HasMoreElements(&more);
+ }
+
+ FlushSkinCaches();
+
+ windowMediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
+ windowEnumerator->HasMoreElements(&more);
+ while (more) {
+ nsCOMPtr<nsISupports> protoWindow;
+ windowEnumerator->GetNext(getter_AddRefs(protoWindow));
+ if (protoWindow) {
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = do_QueryInterface(protoWindow);
+ if (domWindow)
+ RefreshWindow(domWindow);
+ }
+ windowEnumerator->HasMoreElements(&more);
+ }
+
+ return NS_OK;
+}
+
+void
+nsChromeRegistry::FlushSkinCaches()
+{
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ NS_ASSERTION(obsSvc, "Couldn't get observer service.");
+
+ obsSvc->NotifyObservers(static_cast<nsIChromeRegistry*>(this),
+ NS_CHROME_FLUSH_SKINS_TOPIC, nullptr);
+}
+
+// XXXbsmedberg: move this to windowmediator
+nsresult nsChromeRegistry::RefreshWindow(nsPIDOMWindowOuter* aWindow)
+{
+ // Deal with our subframes first.
+ nsCOMPtr<nsIDOMWindowCollection> frames = aWindow->GetFrames();
+ uint32_t length;
+ frames->GetLength(&length);
+ uint32_t j;
+ for (j = 0; j < length; j++) {
+ nsCOMPtr<mozIDOMWindowProxy> childWin;
+ frames->Item(j, getter_AddRefs(childWin));
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(childWin);
+ RefreshWindow(piWindow);
+ }
+
+ nsresult rv;
+ // Get the document.
+ nsCOMPtr<nsIDocument> document = aWindow->GetDoc();
+ if (!document)
+ return NS_OK;
+
+ // Deal with the agent sheets first. Have to do all the style sets by hand.
+ nsCOMPtr<nsIPresShell> shell = document->GetShell();
+ if (shell) {
+ // Reload only the chrome URL agent style sheets.
+ nsTArray<RefPtr<StyleSheet>> agentSheets;
+ rv = shell->GetAgentStyleSheets(agentSheets);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<StyleSheet>> newAgentSheets;
+ for (StyleSheet* sheet : agentSheets) {
+ nsIURI* uri = sheet->GetSheetURI();
+
+ if (IsChromeURI(uri)) {
+ // Reload the sheet.
+ RefPtr<StyleSheet> newSheet;
+ rv = document->LoadChromeSheetSync(uri, true, &newSheet);
+ if (NS_FAILED(rv)) return rv;
+ if (newSheet) {
+ rv = newAgentSheets.AppendElement(newSheet) ? NS_OK : NS_ERROR_FAILURE;
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ else { // Just use the same sheet.
+ rv = newAgentSheets.AppendElement(sheet) ? NS_OK : NS_ERROR_FAILURE;
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+
+ rv = shell->SetAgentStyleSheets(newAgentSheets);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ size_t count = document->SheetCount();
+
+ // Build an array of style sheets we need to reload.
+ nsTArray<RefPtr<StyleSheet>> oldSheets(count);
+ nsTArray<RefPtr<StyleSheet>> newSheets(count);
+
+ // Iterate over the style sheets.
+ for (size_t i = 0; i < count; i++) {
+ // Get the style sheet
+ oldSheets.AppendElement(document->SheetAt(i));
+ }
+
+ // Iterate over our old sheets and kick off a sync load of the new
+ // sheet if and only if it's a non-inline sheet with a chrome URL.
+ for (StyleSheet* sheet : oldSheets) {
+ MOZ_ASSERT(sheet, "SheetAt shouldn't return nullptr for "
+ "in-range sheet indexes");
+ nsIURI* uri = sheet->GetSheetURI();
+
+ if (!sheet->IsInline() && IsChromeURI(uri)) {
+ // Reload the sheet.
+ RefPtr<StyleSheet> newSheet;
+ // XXX what about chrome sheets that have a title or are disabled? This
+ // only works by sheer dumb luck.
+ document->LoadChromeSheetSync(uri, false, &newSheet);
+ // Even if it's null, we put in in there.
+ newSheets.AppendElement(newSheet);
+ } else {
+ // Just use the same sheet.
+ newSheets.AppendElement(sheet);
+ }
+ }
+
+ // Now notify the document that multiple sheets have been added and removed.
+ document->UpdateStyleSheets(oldSheets, newSheets);
+ return NS_OK;
+}
+
+void
+nsChromeRegistry::FlushAllCaches()
+{
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ NS_ASSERTION(obsSvc, "Couldn't get observer service.");
+
+ obsSvc->NotifyObservers((nsIChromeRegistry*) this,
+ NS_CHROME_FLUSH_TOPIC, nullptr);
+}
+
+// xxxbsmedberg Move me to nsIWindowMediator
+NS_IMETHODIMP
+nsChromeRegistry::ReloadChrome()
+{
+ UpdateSelectedLocale();
+ FlushAllCaches();
+ // Do a reload of all top level windows.
+ nsresult rv = NS_OK;
+
+ // Get the window mediator
+ nsCOMPtr<nsIWindowMediator> windowMediator
+ (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (windowMediator) {
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+
+ rv = windowMediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
+ if (NS_SUCCEEDED(rv)) {
+ // Get each dom window
+ bool more;
+ rv = windowEnumerator->HasMoreElements(&more);
+ if (NS_FAILED(rv)) return rv;
+ while (more) {
+ nsCOMPtr<nsISupports> protoWindow;
+ rv = windowEnumerator->GetNext(getter_AddRefs(protoWindow));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = do_QueryInterface(protoWindow);
+ if (domWindow) {
+ nsIDOMLocation* location = domWindow->GetLocation();
+ if (location) {
+ rv = location->Reload(false);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ }
+ rv = windowEnumerator->HasMoreElements(&more);
+ if (NS_FAILED(rv)) return rv;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsChromeRegistry::AllowScriptsForPackage(nsIURI* aChromeURI, bool *aResult)
+{
+ nsresult rv;
+ *aResult = false;
+
+#ifdef DEBUG
+ bool isChrome;
+ aChromeURI->SchemeIs("chrome", &isChrome);
+ NS_ASSERTION(isChrome, "Non-chrome URI passed to AllowScriptsForPackage!");
+#endif
+
+ nsCOMPtr<nsIURL> url (do_QueryInterface(aChromeURI));
+ NS_ENSURE_TRUE(url, NS_NOINTERFACE);
+
+ nsAutoCString provider, file;
+ rv = GetProviderAndPath(url, provider, file);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!provider.EqualsLiteral("skin"))
+ *aResult = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeRegistry::AllowContentToAccess(nsIURI *aURI, bool *aResult)
+{
+ nsresult rv;
+
+ *aResult = false;
+
+#ifdef DEBUG
+ bool isChrome;
+ aURI->SchemeIs("chrome", &isChrome);
+ NS_ASSERTION(isChrome, "Non-chrome URI passed to AllowContentToAccess!");
+#endif
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+ if (!url) {
+ NS_ERROR("Chrome URL doesn't implement nsIURL.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoCString package;
+ rv = url->GetHostPort(package);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags;
+ rv = GetFlagsFromPackage(package, &flags);
+
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = !!(flags & CONTENT_ACCESSIBLE);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeRegistry::CanLoadURLRemotely(nsIURI *aURI, bool *aResult)
+{
+ nsresult rv;
+
+ *aResult = false;
+
+#ifdef DEBUG
+ bool isChrome;
+ aURI->SchemeIs("chrome", &isChrome);
+ NS_ASSERTION(isChrome, "Non-chrome URI passed to CanLoadURLRemotely!");
+#endif
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+ if (!url) {
+ NS_ERROR("Chrome URL doesn't implement nsIURL.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoCString package;
+ rv = url->GetHostPort(package);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags;
+ rv = GetFlagsFromPackage(package, &flags);
+
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = !!(flags & REMOTE_ALLOWED);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeRegistry::MustLoadURLRemotely(nsIURI *aURI, bool *aResult)
+{
+ nsresult rv;
+
+ *aResult = false;
+
+#ifdef DEBUG
+ bool isChrome;
+ aURI->SchemeIs("chrome", &isChrome);
+ NS_ASSERTION(isChrome, "Non-chrome URI passed to MustLoadURLRemotely!");
+#endif
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+ if (!url) {
+ NS_ERROR("Chrome URL doesn't implement nsIURL.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoCString package;
+ rv = url->GetHostPort(package);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags;
+ rv = GetFlagsFromPackage(package, &flags);
+
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = !!(flags & REMOTE_REQUIRED);
+ }
+ return NS_OK;
+}
+
+bool
+nsChromeRegistry::GetDirectionForLocale(const nsACString& aLocale)
+{
+ // first check the intl.uidirection.<locale> preference, and if that is not
+ // set, check the same preference but with just the first two characters of
+ // the locale. If that isn't set, default to left-to-right.
+ nsAutoCString prefString = NS_LITERAL_CSTRING("intl.uidirection.") + aLocale;
+ nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (!prefBranch) {
+ return false;
+ }
+
+ nsXPIDLCString dir;
+ prefBranch->GetCharPref(prefString.get(), getter_Copies(dir));
+ if (dir.IsEmpty()) {
+ int32_t hyphen = prefString.FindChar('-');
+ if (hyphen >= 1) {
+ nsAutoCString shortPref(Substring(prefString, 0, hyphen));
+ prefBranch->GetCharPref(shortPref.get(), getter_Copies(dir));
+ }
+ }
+
+ return dir.EqualsLiteral("rtl");
+}
+
+NS_IMETHODIMP_(bool)
+nsChromeRegistry::WrappersEnabled(nsIURI *aURI)
+{
+ nsCOMPtr<nsIURL> chromeURL (do_QueryInterface(aURI));
+ if (!chromeURL)
+ return false;
+
+ bool isChrome = false;
+ nsresult rv = chromeURL->SchemeIs("chrome", &isChrome);
+ if (NS_FAILED(rv) || !isChrome)
+ return false;
+
+ nsAutoCString package;
+ rv = chromeURL->GetHostPort(package);
+ if (NS_FAILED(rv))
+ return false;
+
+ uint32_t flags;
+ rv = GetFlagsFromPackage(package, &flags);
+ return NS_SUCCEEDED(rv) && (flags & XPCNATIVEWRAPPERS);
+}
+
+already_AddRefed<nsChromeRegistry>
+nsChromeRegistry::GetSingleton()
+{
+ if (gChromeRegistry) {
+ RefPtr<nsChromeRegistry> registry = gChromeRegistry;
+ return registry.forget();
+ }
+
+ RefPtr<nsChromeRegistry> cr;
+ if (GeckoProcessType_Content == XRE_GetProcessType())
+ cr = new nsChromeRegistryContent();
+ else
+ cr = new nsChromeRegistryChrome();
+
+ if (NS_FAILED(cr->Init()))
+ return nullptr;
+
+ return cr.forget();
+}
+
+void
+nsChromeRegistry::SanitizeForBCP47(nsACString& aLocale)
+{
+ // Currently, the only locale code we use that's not BCP47-conformant is
+ // "ja-JP-mac" on OS X, but let's try to be more general than just
+ // hard-coding that here.
+ const int32_t LANG_TAG_CAPACITY = 128;
+ char langTag[LANG_TAG_CAPACITY];
+ nsAutoCString locale(aLocale);
+ UErrorCode err = U_ZERO_ERROR;
+ // This is a fail-safe method that will set langTag to "und" if it cannot
+ // match any part of the input locale code.
+ int32_t len = uloc_toLanguageTag(locale.get(), langTag, LANG_TAG_CAPACITY,
+ false, &err);
+ if (U_SUCCESS(err) && len > 0) {
+ aLocale.Assign(langTag, len);
+ }
+}
diff --git a/components/registry/src/nsChromeRegistry.h b/components/registry/src/nsChromeRegistry.h
new file mode 100644
index 000000000..27b45791f
--- /dev/null
+++ b/components/registry/src/nsChromeRegistry.h
@@ -0,0 +1,166 @@
+/* -*- 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 nsChromeRegistry_h
+#define nsChromeRegistry_h
+
+#include "nsIToolkitChromeRegistry.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsIXULOverlayProvider.h"
+#include "nsString.h"
+#include "nsURIHashKey.h"
+#include "nsInterfaceHashtable.h"
+#include "nsXULAppAPI.h"
+#include "nsIXPConnect.h"
+
+#include "mozilla/FileLocation.h"
+
+class nsPIDOMWindowOuter;
+class nsIPrefBranch;
+class nsIURL;
+
+// The chrome registry is actually split between nsChromeRegistryChrome and
+// nsChromeRegistryContent. The work/data that is common to both resides in
+// the shared nsChromeRegistry implementation, with operations that only make
+// sense for one side erroring out in the other.
+
+// for component registration
+// {47049e42-1d87-482a-984d-56ae185e367a}
+#define NS_CHROMEREGISTRY_CID \
+{ 0x47049e42, 0x1d87, 0x482a, { 0x98, 0x4d, 0x56, 0xae, 0x18, 0x5e, 0x36, 0x7a } }
+
+class nsChromeRegistry : public nsIToolkitChromeRegistry,
+ public nsIXULOverlayProvider,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIXULChromeRegistry methods:
+ NS_IMETHOD ReloadChrome() override;
+ NS_IMETHOD RefreshSkins() override;
+ NS_IMETHOD AllowScriptsForPackage(nsIURI* url,
+ bool* _retval) override;
+ NS_IMETHOD AllowContentToAccess(nsIURI* url,
+ bool* _retval) override;
+ NS_IMETHOD CanLoadURLRemotely(nsIURI* url,
+ bool* _retval) override;
+ NS_IMETHOD MustLoadURLRemotely(nsIURI* url,
+ bool* _retval) override;
+
+ // nsIChromeRegistry methods:
+ NS_IMETHOD_(bool) WrappersEnabled(nsIURI *aURI) override;
+ NS_IMETHOD ConvertChromeURL(nsIURI* aChromeURI, nsIURI* *aResult) override;
+
+ // nsChromeRegistry methods:
+ nsChromeRegistry() : mInitialized(false) { }
+
+ virtual nsresult Init();
+
+ static already_AddRefed<nsIChromeRegistry> GetService();
+
+ static nsChromeRegistry* gChromeRegistry;
+
+ static nsresult Canonify(nsIURL* aChromeURL);
+
+protected:
+ virtual ~nsChromeRegistry();
+
+ void FlushSkinCaches();
+ void FlushAllCaches();
+
+ // Update the selected locale used by the chrome registry, and fire a
+ // notification about this change
+ virtual nsresult UpdateSelectedLocale() = 0;
+
+ static void LogMessage(const char* aMsg, ...);
+ static void LogMessageWithContext(nsIURI* aURL, uint32_t aLineNumber, uint32_t flags,
+ const char* aMsg, ...);
+
+ virtual nsIURI* GetBaseURIFromPackage(const nsCString& aPackage,
+ const nsCString& aProvider,
+ const nsCString& aPath) = 0;
+ virtual nsresult GetFlagsFromPackage(const nsCString& aPackage,
+ uint32_t* aFlags) = 0;
+
+ nsresult SelectLocaleFromPref(nsIPrefBranch* prefs);
+
+ static nsresult RefreshWindow(nsPIDOMWindowOuter* aWindow);
+ static nsresult GetProviderAndPath(nsIURL* aChromeURL,
+ nsACString& aProvider, nsACString& aPath);
+
+ bool GetDirectionForLocale(const nsACString& aLocale);
+
+ void SanitizeForBCP47(nsACString& aLocale);
+
+public:
+ static already_AddRefed<nsChromeRegistry> GetSingleton();
+
+ struct ManifestProcessingContext
+ {
+ ManifestProcessingContext(NSLocationType aType, mozilla::FileLocation &aFile)
+ : mType(aType)
+ , mFile(aFile)
+ { }
+
+ ~ManifestProcessingContext()
+ { }
+
+ nsIURI* GetManifestURI();
+ nsIXPConnect* GetXPConnect();
+
+ already_AddRefed<nsIURI> ResolveURI(const char* uri);
+
+ NSLocationType mType;
+ mozilla::FileLocation mFile;
+ nsCOMPtr<nsIURI> mManifestURI;
+ nsCOMPtr<nsIXPConnect> mXPConnect;
+ };
+
+ virtual void ManifestContent(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) = 0;
+ virtual void ManifestLocale(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) = 0;
+ virtual void ManifestSkin(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) = 0;
+ virtual void ManifestOverlay(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) = 0;
+ virtual void ManifestStyle(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) = 0;
+ virtual void ManifestOverride(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) = 0;
+ virtual void ManifestResource(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) = 0;
+
+ // Available flags
+ enum {
+ // This is a "platform" package (e.g. chrome://global-platform/).
+ // Appends one of win/ unix/ mac/ to the base URI.
+ PLATFORM_PACKAGE = 1 << 0,
+
+ // This package should use the new XPCNativeWrappers to separate
+ // content from chrome. This flag is currently unused (because we call
+ // into xpconnect at registration time).
+ XPCNATIVEWRAPPERS = 1 << 1,
+
+ // Content script may access files in this package
+ CONTENT_ACCESSIBLE = 1 << 2,
+
+ // Package may be loaded remotely
+ REMOTE_ALLOWED = 1 << 3,
+
+ // Package must be loaded remotely
+ REMOTE_REQUIRED = 1 << 4,
+ };
+
+ bool mInitialized;
+
+ // "Override" table (chrome URI string -> real URI)
+ nsInterfaceHashtable<nsURIHashKey, nsIURI> mOverrideTable;
+};
+
+#endif // nsChromeRegistry_h
diff --git a/components/registry/src/nsChromeRegistryChrome.cpp b/components/registry/src/nsChromeRegistryChrome.cpp
new file mode 100644
index 000000000..c115280fd
--- /dev/null
+++ b/components/registry/src/nsChromeRegistryChrome.cpp
@@ -0,0 +1,986 @@
+/* -*- 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 "mozilla/dom/ContentParent.h"
+#include "RegistryMessageUtils.h"
+#include "nsResProtocolHandler.h"
+
+#include "nsChromeRegistryChrome.h"
+
+#if defined(XP_WIN)
+#include <windows.h>
+#endif
+
+#include "nsArrayEnumerator.h"
+#include "nsComponentManager.h"
+#include "nsEnumeratorUtils.h"
+#include "nsNetUtil.h"
+#include "nsStringEnumerator.h"
+#include "nsTextFormatter.h"
+#include "nsXPCOMCIDInternal.h"
+
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Unused.h"
+
+#include "nsICommandLine.h"
+#include "nsILocaleService.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "mozilla/Preferences.h"
+#include "nsIResProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsIXPConnect.h"
+#include "nsIXULRuntime.h"
+
+#define UILOCALE_CMD_LINE_ARG "UILocale"
+
+#define MATCH_OS_LOCALE_PREF "intl.locale.matchOS"
+#define SELECTED_LOCALE_PREF "general.useragent.locale"
+#define SELECTED_SKIN_PREF "general.skins.selectedSkin"
+#define PACKAGE_OVERRIDE_BRANCH "chrome.override_package."
+
+using namespace mozilla;
+using mozilla::dom::ContentParent;
+using mozilla::dom::PContentParent;
+
+// We use a "best-fit" algorithm for matching locales and themes.
+// 1) the exact selected locale/theme
+// 2) (locales only) same language, different country
+// e.g. en-GB is the selected locale, only en-US is available
+// 3) any available locale/theme
+
+/**
+ * Match the language-part of two lang-COUNTRY codes, hopefully but
+ * not guaranteed to be in the form ab-CD or abz-CD. "ab" should also
+ * work, any other garbage-in will produce undefined results as long
+ * as it does not crash.
+ */
+static bool
+LanguagesMatch(const nsACString& a, const nsACString& b)
+{
+ if (a.Length() < 2 || b.Length() < 2)
+ return false;
+
+ nsACString::const_iterator as, ae, bs, be;
+ a.BeginReading(as);
+ a.EndReading(ae);
+ b.BeginReading(bs);
+ b.EndReading(be);
+
+ while (*as == *bs) {
+ if (*as == '-')
+ return true;
+
+ ++as; ++bs;
+
+ // reached the end
+ if (as == ae && bs == be)
+ return true;
+
+ // "a" is short
+ if (as == ae)
+ return (*bs == '-');
+
+ // "b" is short
+ if (bs == be)
+ return (*as == '-');
+ }
+
+ return false;
+}
+
+nsChromeRegistryChrome::nsChromeRegistryChrome()
+ : mProfileLoaded(false)
+ , mDynamicRegistration(true)
+{
+}
+
+nsChromeRegistryChrome::~nsChromeRegistryChrome()
+{
+}
+
+nsresult
+nsChromeRegistryChrome::Init()
+{
+ nsresult rv = nsChromeRegistry::Init();
+ if (NS_FAILED(rv))
+ return rv;
+
+ mSelectedLocale = NS_LITERAL_CSTRING("en-US");
+ mSelectedSkin = NS_LITERAL_CSTRING("classic/1.0");
+
+ bool safeMode = false;
+ nsCOMPtr<nsIXULRuntime> xulrun (do_GetService(XULAPPINFO_SERVICE_CONTRACTID));
+ if (xulrun)
+ xulrun->GetInSafeMode(&safeMode);
+
+ nsCOMPtr<nsIPrefService> prefserv (do_GetService(NS_PREFSERVICE_CONTRACTID));
+ nsCOMPtr<nsIPrefBranch> prefs;
+
+ if (prefserv) {
+ if (safeMode) {
+ prefserv->GetDefaultBranch(nullptr, getter_AddRefs(prefs));
+ } else {
+ prefs = do_QueryInterface(prefserv);
+ }
+ }
+
+ if (!prefs) {
+ NS_WARNING("Could not get pref service!");
+ } else {
+ nsXPIDLCString provider;
+ rv = prefs->GetCharPref(SELECTED_SKIN_PREF, getter_Copies(provider));
+ if (NS_SUCCEEDED(rv))
+ mSelectedSkin = provider;
+
+ SelectLocaleFromPref(prefs);
+
+ rv = prefs->AddObserver(MATCH_OS_LOCALE_PREF, this, true);
+ rv = prefs->AddObserver(SELECTED_LOCALE_PREF, this, true);
+ rv = prefs->AddObserver(SELECTED_SKIN_PREF, this, true);
+ }
+
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->AddObserver(this, "command-line-startup", true);
+ obsService->AddObserver(this, "profile-initial-state", true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeRegistryChrome::CheckForOSAccessibility()
+{
+ int32_t useAccessibilityTheme =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_UseAccessibilityTheme, 0);
+
+ if (useAccessibilityTheme) {
+ /* Set the skin to classic and remove pref observers */
+ if (!mSelectedSkin.EqualsLiteral("classic/1.0")) {
+ mSelectedSkin.AssignLiteral("classic/1.0");
+ RefreshSkins();
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs (do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ prefs->RemoveObserver(SELECTED_SKIN_PREF, this);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeRegistryChrome::GetLocalesForPackage(const nsACString& aPackage,
+ nsIUTF8StringEnumerator* *aResult)
+{
+ nsCString realpackage;
+ nsresult rv = OverrideLocalePackage(aPackage, realpackage);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsTArray<nsCString> *a = new nsTArray<nsCString>;
+ if (!a)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ PackageEntry* entry;
+ if (mPackagesHash.Get(realpackage, &entry)) {
+ entry->locales.EnumerateToArray(a);
+ }
+
+ rv = NS_NewAdoptingUTF8StringEnumerator(aResult, a);
+ if (NS_FAILED(rv))
+ delete a;
+
+ return rv;
+}
+
+static nsresult
+getUILangCountry(nsACString& aUILang)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsILocaleService> localeService = do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString uiLang;
+ rv = localeService->GetLocaleComponentForUserAgent(uiLang);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF16toUTF8(uiLang, aUILang);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeRegistryChrome::IsLocaleRTL(const nsACString& package, bool *aResult)
+{
+ *aResult = false;
+
+ nsAutoCString locale;
+ GetSelectedLocale(package, false, locale);
+ if (locale.Length() < 2)
+ return NS_OK;
+
+ *aResult = GetDirectionForLocale(locale);
+ return NS_OK;
+}
+
+nsresult
+nsChromeRegistryChrome::GetSelectedLocale(const nsACString& aPackage,
+ bool aAsBCP47,
+ nsACString& aLocale)
+{
+ nsCString realpackage;
+ nsresult rv = OverrideLocalePackage(aPackage, realpackage);
+ if (NS_FAILED(rv))
+ return rv;
+ PackageEntry* entry;
+ if (!mPackagesHash.Get(realpackage, &entry))
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ aLocale = entry->locales.GetSelected(mSelectedLocale, nsProviderArray::LOCALE);
+ if (aLocale.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ if (aAsBCP47) {
+ SanitizeForBCP47(aLocale);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsChromeRegistryChrome::OverrideLocalePackage(const nsACString& aPackage,
+ nsACString& aOverride)
+{
+ const nsACString& pref = NS_LITERAL_CSTRING(PACKAGE_OVERRIDE_BRANCH) + aPackage;
+ nsAdoptingCString override = mozilla::Preferences::GetCString(PromiseFlatCString(pref).get());
+ if (override) {
+ aOverride = override;
+ }
+ else {
+ aOverride = aPackage;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsChromeRegistryChrome::SelectLocaleFromPref(nsIPrefBranch* prefs)
+{
+ nsresult rv;
+ bool matchOSLocale = false;
+ rv = prefs->GetBoolPref(MATCH_OS_LOCALE_PREF, &matchOSLocale);
+
+ if (NS_SUCCEEDED(rv) && matchOSLocale) {
+ // compute lang and region code only when needed!
+ nsAutoCString uiLocale;
+ rv = getUILangCountry(uiLocale);
+ if (NS_SUCCEEDED(rv))
+ mSelectedLocale = uiLocale;
+ }
+ else {
+ nsXPIDLCString provider;
+ rv = prefs->GetCharPref(SELECTED_LOCALE_PREF, getter_Copies(provider));
+ if (NS_SUCCEEDED(rv)) {
+ mSelectedLocale = provider;
+ }
+ }
+
+ if (NS_FAILED(rv))
+ NS_ERROR("Couldn't select locale from pref!");
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsChromeRegistryChrome::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *someData)
+{
+ nsresult rv = NS_OK;
+
+ if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) {
+ nsCOMPtr<nsIPrefBranch> prefs (do_QueryInterface(aSubject));
+ NS_ASSERTION(prefs, "Bad observer call!");
+
+ NS_ConvertUTF16toUTF8 pref(someData);
+
+ if (pref.EqualsLiteral(MATCH_OS_LOCALE_PREF) ||
+ pref.EqualsLiteral(SELECTED_LOCALE_PREF)) {
+ rv = UpdateSelectedLocale();
+ if (NS_SUCCEEDED(rv) && mProfileLoaded)
+ FlushAllCaches();
+ }
+ else if (pref.EqualsLiteral(SELECTED_SKIN_PREF)) {
+ nsXPIDLCString provider;
+ rv = prefs->GetCharPref(pref.get(), getter_Copies(provider));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Couldn't get new skin pref!");
+ return rv;
+ }
+
+ mSelectedSkin = provider;
+ RefreshSkins();
+ } else {
+ NS_ERROR("Unexpected pref!");
+ }
+ }
+ else if (!strcmp("command-line-startup", aTopic)) {
+ nsCOMPtr<nsICommandLine> cmdLine (do_QueryInterface(aSubject));
+ if (cmdLine) {
+ nsAutoString uiLocale;
+ rv = cmdLine->HandleFlagWithParam(NS_LITERAL_STRING(UILOCALE_CMD_LINE_ARG),
+ false, uiLocale);
+ if (NS_SUCCEEDED(rv) && !uiLocale.IsEmpty()) {
+ CopyUTF16toUTF8(uiLocale, mSelectedLocale);
+ nsCOMPtr<nsIPrefBranch> prefs (do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ prefs->RemoveObserver(SELECTED_LOCALE_PREF, this);
+ }
+ }
+ }
+ }
+ else if (!strcmp("profile-initial-state", aTopic)) {
+ mProfileLoaded = true;
+ }
+ else {
+ NS_ERROR("Unexpected observer topic!");
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsChromeRegistryChrome::CheckForNewChrome()
+{
+ mPackagesHash.Clear();
+ mOverlayHash.Clear();
+ mStyleHash.Clear();
+ mOverrideTable.Clear();
+
+ mDynamicRegistration = false;
+
+ nsComponentManagerImpl::gComponentManager->RereadChromeManifests();
+
+ mDynamicRegistration = true;
+
+ SendRegisteredChrome(nullptr);
+ return NS_OK;
+}
+
+nsresult nsChromeRegistryChrome::UpdateSelectedLocale()
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ rv = SelectLocaleFromPref(prefs);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ NS_ASSERTION(obsSvc, "Couldn't get observer service.");
+ obsSvc->NotifyObservers((nsIChromeRegistry*) this,
+ "selected-locale-has-changed", nullptr);
+ }
+ }
+
+ return rv;
+}
+
+static void
+SerializeURI(nsIURI* aURI,
+ SerializedURI& aSerializedURI)
+{
+ if (!aURI)
+ return;
+
+ aURI->GetSpec(aSerializedURI.spec);
+ aURI->GetOriginCharset(aSerializedURI.charset);
+}
+
+void
+nsChromeRegistryChrome::SendRegisteredChrome(
+ mozilla::dom::PContentParent* aParent)
+{
+ InfallibleTArray<ChromePackage> packages;
+ InfallibleTArray<SubstitutionMapping> resources;
+ InfallibleTArray<OverrideMapping> overrides;
+
+ for (auto iter = mPackagesHash.Iter(); !iter.Done(); iter.Next()) {
+ ChromePackage chromePackage;
+ ChromePackageFromPackageEntry(iter.Key(), iter.UserData(), &chromePackage,
+ mSelectedLocale, mSelectedSkin);
+ packages.AppendElement(chromePackage);
+ }
+
+ // If we were passed a parent then a new child process has been created and
+ // has requested all of the chrome so send it the resources too. Otherwise
+ // resource mappings are sent by the resource protocol handler dynamically.
+ if (aParent) {
+ nsCOMPtr<nsIIOService> io (do_GetIOService());
+ NS_ENSURE_TRUE_VOID(io);
+
+ nsCOMPtr<nsIProtocolHandler> ph;
+ nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIResProtocolHandler> irph (do_QueryInterface(ph));
+ nsResProtocolHandler* rph = static_cast<nsResProtocolHandler*>(irph.get());
+ rv = rph->CollectSubstitutions(resources);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+
+ for (auto iter = mOverrideTable.Iter(); !iter.Done(); iter.Next()) {
+ SerializedURI chromeURI, overrideURI;
+
+ SerializeURI(iter.Key(), chromeURI);
+ SerializeURI(iter.UserData(), overrideURI);
+
+ OverrideMapping override = { chromeURI, overrideURI };
+ overrides.AppendElement(override);
+ }
+
+ if (aParent) {
+ bool success = aParent->SendRegisterChrome(packages, resources, overrides,
+ mSelectedLocale, false);
+ NS_ENSURE_TRUE_VOID(success);
+ } else {
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ if (!parents.Length())
+ return;
+
+ for (uint32_t i = 0; i < parents.Length(); i++) {
+ DebugOnly<bool> success =
+ parents[i]->SendRegisterChrome(packages, resources, overrides,
+ mSelectedLocale, true);
+ NS_WARNING_ASSERTION(success,
+ "couldn't reset a child's registered chrome");
+ }
+ }
+}
+
+/* static */ void
+nsChromeRegistryChrome::ChromePackageFromPackageEntry(const nsACString& aPackageName,
+ PackageEntry* aPackage,
+ ChromePackage* aChromePackage,
+ const nsCString& aSelectedLocale,
+ const nsCString& aSelectedSkin)
+{
+ SerializeURI(aPackage->baseURI, aChromePackage->contentBaseURI);
+ SerializeURI(aPackage->locales.GetBase(aSelectedLocale,
+ nsProviderArray::LOCALE),
+ aChromePackage->localeBaseURI);
+ SerializeURI(aPackage->skins.GetBase(aSelectedSkin, nsProviderArray::ANY),
+ aChromePackage->skinBaseURI);
+ aChromePackage->package = aPackageName;
+ aChromePackage->flags = aPackage->flags;
+}
+
+static bool
+CanLoadResource(nsIURI* aResourceURI)
+{
+ bool isLocalResource = false;
+ (void)NS_URIChainHasFlags(aResourceURI,
+ nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
+ &isLocalResource);
+ return isLocalResource;
+}
+
+nsIURI*
+nsChromeRegistryChrome::GetBaseURIFromPackage(const nsCString& aPackage,
+ const nsCString& aProvider,
+ const nsCString& aPath)
+{
+ PackageEntry* entry;
+ if (!mPackagesHash.Get(aPackage, &entry)) {
+ if (!mInitialized)
+ return nullptr;
+
+ LogMessage("No chrome package registered for chrome://%s/%s/%s",
+ aPackage.get(), aProvider.get(), aPath.get());
+
+ return nullptr;
+ }
+
+ if (aProvider.EqualsLiteral("locale")) {
+ return entry->locales.GetBase(mSelectedLocale, nsProviderArray::LOCALE);
+ }
+ else if (aProvider.EqualsLiteral("skin")) {
+ return entry->skins.GetBase(mSelectedSkin, nsProviderArray::ANY);
+ }
+ else if (aProvider.EqualsLiteral("content")) {
+ return entry->baseURI;
+ }
+ return nullptr;
+}
+
+nsresult
+nsChromeRegistryChrome::GetFlagsFromPackage(const nsCString& aPackage,
+ uint32_t* aFlags)
+{
+ PackageEntry* entry;
+ if (!mPackagesHash.Get(aPackage, &entry))
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ *aFlags = entry->flags;
+ return NS_OK;
+}
+
+nsChromeRegistryChrome::ProviderEntry*
+nsChromeRegistryChrome::nsProviderArray::GetProvider(const nsACString& aPreferred, MatchType aType)
+{
+ size_t i = mArray.Length();
+ if (!i)
+ return nullptr;
+
+ ProviderEntry* found = nullptr; // Only set if we find a partial-match locale
+ ProviderEntry* entry = nullptr;
+
+ while (i--) {
+ entry = &mArray[i];
+ if (aPreferred.Equals(entry->provider))
+ return entry;
+
+ if (aType != LOCALE)
+ continue;
+
+ if (LanguagesMatch(aPreferred, entry->provider)) {
+ found = entry;
+ continue;
+ }
+
+ if (!found && entry->provider.EqualsLiteral("en-US"))
+ found = entry;
+ }
+
+ if (!found && aType != EXACT)
+ return entry;
+
+ return found;
+}
+
+nsIURI*
+nsChromeRegistryChrome::nsProviderArray::GetBase(const nsACString& aPreferred, MatchType aType)
+{
+ ProviderEntry* provider = GetProvider(aPreferred, aType);
+
+ if (!provider)
+ return nullptr;
+
+ return provider->baseURI;
+}
+
+const nsACString&
+nsChromeRegistryChrome::nsProviderArray::GetSelected(const nsACString& aPreferred, MatchType aType)
+{
+ ProviderEntry* entry = GetProvider(aPreferred, aType);
+
+ if (entry)
+ return entry->provider;
+
+ return EmptyCString();
+}
+
+void
+nsChromeRegistryChrome::nsProviderArray::SetBase(const nsACString& aProvider, nsIURI* aBaseURL)
+{
+ ProviderEntry* provider = GetProvider(aProvider, EXACT);
+
+ if (provider) {
+ provider->baseURI = aBaseURL;
+ return;
+ }
+
+ // no existing entries, add a new one
+ mArray.AppendElement(ProviderEntry(aProvider, aBaseURL));
+}
+
+void
+nsChromeRegistryChrome::nsProviderArray::EnumerateToArray(nsTArray<nsCString> *a)
+{
+ int32_t i = mArray.Length();
+ while (i--) {
+ a->AppendElement(mArray[i].provider);
+ }
+}
+
+void
+nsChromeRegistryChrome::OverlayListEntry::AddURI(nsIURI* aURI)
+{
+ int32_t i = mArray.Count();
+ while (i--) {
+ bool equals;
+ if (NS_SUCCEEDED(aURI->Equals(mArray[i], &equals)) && equals)
+ return;
+ }
+
+ mArray.AppendObject(aURI);
+}
+
+void
+nsChromeRegistryChrome::OverlayListHash::Add(nsIURI* aBase, nsIURI* aOverlay)
+{
+ OverlayListEntry* entry = mTable.PutEntry(aBase);
+ if (entry)
+ entry->AddURI(aOverlay);
+}
+
+const nsCOMArray<nsIURI>*
+nsChromeRegistryChrome::OverlayListHash::GetArray(nsIURI* aBase)
+{
+ OverlayListEntry* entry = mTable.GetEntry(aBase);
+ if (!entry)
+ return nullptr;
+
+ return &entry->mArray;
+}
+
+NS_IMETHODIMP
+nsChromeRegistryChrome::GetStyleOverlays(nsIURI *aChromeURL,
+ nsISimpleEnumerator **aResult)
+{
+ nsCOMPtr<nsIURI> chromeURLWithoutHash;
+ if (aChromeURL) {
+ aChromeURL->CloneIgnoringRef(getter_AddRefs(chromeURLWithoutHash));
+ }
+ const nsCOMArray<nsIURI>* parray = mStyleHash.GetArray(chromeURLWithoutHash);
+ if (!parray)
+ return NS_NewEmptyEnumerator(aResult);
+
+ return NS_NewArrayEnumerator(aResult, *parray);
+}
+
+NS_IMETHODIMP
+nsChromeRegistryChrome::GetXULOverlays(nsIURI *aChromeURL,
+ nsISimpleEnumerator **aResult)
+{
+ nsCOMPtr<nsIURI> chromeURLWithoutHash;
+ if (aChromeURL) {
+ aChromeURL->CloneIgnoringRef(getter_AddRefs(chromeURLWithoutHash));
+ }
+ const nsCOMArray<nsIURI>* parray = mOverlayHash.GetArray(chromeURLWithoutHash);
+ if (!parray)
+ return NS_NewEmptyEnumerator(aResult);
+
+ return NS_NewArrayEnumerator(aResult, *parray);
+}
+
+nsIURI*
+nsChromeRegistry::ManifestProcessingContext::GetManifestURI()
+{
+ if (!mManifestURI) {
+ nsCString uri;
+ mFile.GetURIString(uri);
+ NS_NewURI(getter_AddRefs(mManifestURI), uri);
+ }
+ return mManifestURI;
+}
+
+nsIXPConnect*
+nsChromeRegistry::ManifestProcessingContext::GetXPConnect()
+{
+ if (!mXPConnect)
+ mXPConnect = do_GetService("@mozilla.org/js/xpc/XPConnect;1");
+
+ return mXPConnect;
+}
+
+already_AddRefed<nsIURI>
+nsChromeRegistry::ManifestProcessingContext::ResolveURI(const char* uri)
+{
+ nsIURI* baseuri = GetManifestURI();
+ if (!baseuri)
+ return nullptr;
+
+ nsCOMPtr<nsIURI> resolved;
+ nsresult rv = NS_NewURI(getter_AddRefs(resolved), uri, baseuri);
+ if (NS_FAILED(rv))
+ return nullptr;
+
+ return resolved.forget();
+}
+
+static void
+EnsureLowerCase(char *aBuf)
+{
+ for (; *aBuf; ++aBuf) {
+ char ch = *aBuf;
+ if (ch >= 'A' && ch <= 'Z')
+ *aBuf = ch + 'a' - 'A';
+ }
+}
+
+static void
+SendManifestEntry(const ChromeRegistryItem &aItem)
+{
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ if (!parents.Length())
+ return;
+
+ for (uint32_t i = 0; i < parents.Length(); i++) {
+ Unused << parents[i]->SendRegisterChromeItem(aItem);
+ }
+}
+
+void
+nsChromeRegistryChrome::ManifestContent(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags)
+{
+ char* package = argv[0];
+ char* uri = argv[1];
+
+ EnsureLowerCase(package);
+
+ nsCOMPtr<nsIURI> resolved = cx.ResolveURI(uri);
+ if (!resolved) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "During chrome registration, unable to create URI '%s'.", uri);
+ return;
+ }
+
+ if (!CanLoadResource(resolved)) {
+ LogMessageWithContext(resolved, lineno, nsIScriptError::warningFlag,
+ "During chrome registration, cannot register non-local URI '%s' as content.",
+ uri);
+ return;
+ }
+
+ nsDependentCString packageName(package);
+ PackageEntry* entry = mPackagesHash.LookupOrAdd(packageName);
+ entry->baseURI = resolved;
+ entry->flags = flags;
+
+ if (mDynamicRegistration) {
+ ChromePackage chromePackage;
+ ChromePackageFromPackageEntry(packageName, entry, &chromePackage,
+ mSelectedLocale, mSelectedSkin);
+ SendManifestEntry(chromePackage);
+ }
+}
+
+void
+nsChromeRegistryChrome::ManifestLocale(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags)
+{
+ char* package = argv[0];
+ char* provider = argv[1];
+ char* uri = argv[2];
+
+ EnsureLowerCase(package);
+
+ nsCOMPtr<nsIURI> resolved = cx.ResolveURI(uri);
+ if (!resolved) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "During chrome registration, unable to create URI '%s'.", uri);
+ return;
+ }
+
+ if (!CanLoadResource(resolved)) {
+ LogMessageWithContext(resolved, lineno, nsIScriptError::warningFlag,
+ "During chrome registration, cannot register non-local URI '%s' as content.",
+ uri);
+ return;
+ }
+
+ nsDependentCString packageName(package);
+ PackageEntry* entry = mPackagesHash.LookupOrAdd(packageName);
+ entry->locales.SetBase(nsDependentCString(provider), resolved);
+
+ if (mDynamicRegistration) {
+ ChromePackage chromePackage;
+ ChromePackageFromPackageEntry(packageName, entry, &chromePackage,
+ mSelectedLocale, mSelectedSkin);
+ SendManifestEntry(chromePackage);
+ }
+}
+
+void
+nsChromeRegistryChrome::ManifestSkin(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags)
+{
+ char* package = argv[0];
+ char* provider = argv[1];
+ char* uri = argv[2];
+
+ EnsureLowerCase(package);
+
+ nsCOMPtr<nsIURI> resolved = cx.ResolveURI(uri);
+ if (!resolved) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "During chrome registration, unable to create URI '%s'.", uri);
+ return;
+ }
+
+ if (!CanLoadResource(resolved)) {
+ LogMessageWithContext(resolved, lineno, nsIScriptError::warningFlag,
+ "During chrome registration, cannot register non-local URI '%s' as content.",
+ uri);
+ return;
+ }
+
+ nsDependentCString packageName(package);
+ PackageEntry* entry = mPackagesHash.LookupOrAdd(packageName);
+ entry->skins.SetBase(nsDependentCString(provider), resolved);
+
+ if (mDynamicRegistration) {
+ ChromePackage chromePackage;
+ ChromePackageFromPackageEntry(packageName, entry, &chromePackage,
+ mSelectedLocale, mSelectedSkin);
+ SendManifestEntry(chromePackage);
+ }
+}
+
+void
+nsChromeRegistryChrome::ManifestOverlay(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags)
+{
+ char* base = argv[0];
+ char* overlay = argv[1];
+
+ nsCOMPtr<nsIURI> baseuri = cx.ResolveURI(base);
+ nsCOMPtr<nsIURI> overlayuri = cx.ResolveURI(overlay);
+ if (!baseuri || !overlayuri) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "During chrome registration, unable to create URI.");
+ return;
+ }
+
+ if (!CanLoadResource(overlayuri)) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "Cannot register non-local URI '%s' as an overlay.", overlay);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> baseuriWithoutHash;
+ baseuri->CloneIgnoringRef(getter_AddRefs(baseuriWithoutHash));
+
+ mOverlayHash.Add(baseuriWithoutHash, overlayuri);
+}
+
+void
+nsChromeRegistryChrome::ManifestStyle(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags)
+{
+ char* base = argv[0];
+ char* overlay = argv[1];
+
+ nsCOMPtr<nsIURI> baseuri = cx.ResolveURI(base);
+ nsCOMPtr<nsIURI> overlayuri = cx.ResolveURI(overlay);
+ if (!baseuri || !overlayuri) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "During chrome registration, unable to create URI.");
+ return;
+ }
+
+ if (!CanLoadResource(overlayuri)) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "Cannot register non-local URI '%s' as a style overlay.", overlay);
+ return;
+ }
+
+ nsCOMPtr<nsIURI> baseuriWithoutHash;
+ baseuri->CloneIgnoringRef(getter_AddRefs(baseuriWithoutHash));
+
+ mStyleHash.Add(baseuriWithoutHash, overlayuri);
+}
+
+void
+nsChromeRegistryChrome::ManifestOverride(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags)
+{
+ char* chrome = argv[0];
+ char* resolved = argv[1];
+
+ nsCOMPtr<nsIURI> chromeuri = cx.ResolveURI(chrome);
+ nsCOMPtr<nsIURI> resolveduri = cx.ResolveURI(resolved);
+ if (!chromeuri || !resolveduri) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "During chrome registration, unable to create URI.");
+ return;
+ }
+
+ if (cx.mType == NS_SKIN_LOCATION) {
+ bool chromeSkinOnly = false;
+ nsresult rv = chromeuri->SchemeIs("chrome", &chromeSkinOnly);
+ chromeSkinOnly = chromeSkinOnly && NS_SUCCEEDED(rv);
+ if (chromeSkinOnly) {
+ rv = resolveduri->SchemeIs("chrome", &chromeSkinOnly);
+ chromeSkinOnly = chromeSkinOnly && NS_SUCCEEDED(rv);
+ }
+ if (chromeSkinOnly) {
+ nsAutoCString chromePath, resolvedPath;
+ chromeuri->GetPath(chromePath);
+ resolveduri->GetPath(resolvedPath);
+ chromeSkinOnly = StringBeginsWith(chromePath, NS_LITERAL_CSTRING("/skin/")) &&
+ StringBeginsWith(resolvedPath, NS_LITERAL_CSTRING("/skin/"));
+ }
+ if (!chromeSkinOnly) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "Cannot register non-chrome://.../skin/ URIs '%s' and '%s' as overrides and/or to be overridden from a skin manifest.",
+ chrome, resolved);
+ return;
+ }
+ }
+
+ if (!CanLoadResource(resolveduri)) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "Cannot register non-local URI '%s' for an override.", resolved);
+ return;
+ }
+ mOverrideTable.Put(chromeuri, resolveduri);
+
+ if (mDynamicRegistration) {
+ SerializedURI serializedChrome;
+ SerializedURI serializedOverride;
+
+ SerializeURI(chromeuri, serializedChrome);
+ SerializeURI(resolveduri, serializedOverride);
+
+ OverrideMapping override = { serializedChrome, serializedOverride };
+ SendManifestEntry(override);
+ }
+}
+
+void
+nsChromeRegistryChrome::ManifestResource(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags)
+{
+ char* package = argv[0];
+ char* uri = argv[1];
+
+ EnsureLowerCase(package);
+ nsDependentCString host(package);
+
+ nsCOMPtr<nsIIOService> io = mozilla::services::GetIOService();
+ if (!io) {
+ NS_WARNING("No IO service trying to process chrome manifests");
+ return;
+ }
+
+ nsCOMPtr<nsIProtocolHandler> ph;
+ nsresult rv = io->GetProtocolHandler("resource", getter_AddRefs(ph));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIResProtocolHandler> rph = do_QueryInterface(ph);
+
+ nsCOMPtr<nsIURI> resolved = cx.ResolveURI(uri);
+ if (!resolved) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "During chrome registration, unable to create URI '%s'.", uri);
+ return;
+ }
+
+ if (!CanLoadResource(resolved)) {
+ LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
+ "Warning: cannot register non-local URI '%s' as a resource.",
+ uri);
+ return;
+ }
+
+ rph->SetSubstitution(host, resolved);
+}
diff --git a/components/registry/src/nsChromeRegistryChrome.h b/components/registry/src/nsChromeRegistryChrome.h
new file mode 100644
index 000000000..60e93274e
--- /dev/null
+++ b/components/registry/src/nsChromeRegistryChrome.h
@@ -0,0 +1,180 @@
+/* -*- 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/. */
+
+#ifndef nsChromeRegistryChrome_h
+#define nsChromeRegistryChrome_h
+
+#include "nsCOMArray.h"
+#include "nsChromeRegistry.h"
+#include "nsTArray.h"
+#include "mozilla/Move.h"
+#include "nsClassHashtable.h"
+
+namespace mozilla {
+namespace dom {
+class PContentParent;
+} // namespace dom
+} // namespace mozilla
+
+class nsIPrefBranch;
+struct ChromePackage;
+
+class nsChromeRegistryChrome : public nsChromeRegistry
+{
+ public:
+ nsChromeRegistryChrome();
+ ~nsChromeRegistryChrome();
+
+ nsresult Init() override;
+
+ NS_IMETHOD CheckForNewChrome() override;
+ NS_IMETHOD CheckForOSAccessibility() override;
+ NS_IMETHOD GetLocalesForPackage(const nsACString& aPackage,
+ nsIUTF8StringEnumerator* *aResult) override;
+ NS_IMETHOD IsLocaleRTL(const nsACString& package,
+ bool *aResult) override;
+ NS_IMETHOD GetSelectedLocale(const nsACString& aPackage,
+ bool aAsBCP47,
+ nsACString& aLocale) override;
+ NS_IMETHOD Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *someData) override;
+
+ NS_IMETHOD GetXULOverlays(nsIURI *aURI,
+ nsISimpleEnumerator **_retval) override;
+ NS_IMETHOD GetStyleOverlays(nsIURI *aURI,
+ nsISimpleEnumerator **_retval) override;
+
+ // If aChild is non-null then it is a new child to notify. If aChild is
+ // null, then we have installed new chrome and we are resetting all of our
+ // children's registered chrome.
+ void SendRegisteredChrome(mozilla::dom::PContentParent* aChild);
+
+ private:
+ struct PackageEntry;
+ static void ChromePackageFromPackageEntry(const nsACString& aPackageName,
+ PackageEntry* aPackage,
+ ChromePackage* aChromePackage,
+ const nsCString& aSelectedLocale,
+ const nsCString& aSelectedSkin);
+
+ nsresult OverrideLocalePackage(const nsACString& aPackage,
+ nsACString& aOverride);
+ nsresult SelectLocaleFromPref(nsIPrefBranch* prefs);
+ nsresult UpdateSelectedLocale() override;
+ nsIURI* GetBaseURIFromPackage(const nsCString& aPackage,
+ const nsCString& aProvider,
+ const nsCString& aPath) override;
+ nsresult GetFlagsFromPackage(const nsCString& aPackage,
+ uint32_t* aFlags) override;
+
+ struct ProviderEntry
+ {
+ ProviderEntry(const nsACString& aProvider, nsIURI* aBase) :
+ provider(aProvider),
+ baseURI(aBase) { }
+
+ nsCString provider;
+ nsCOMPtr<nsIURI> baseURI;
+ };
+
+ class nsProviderArray
+ {
+ public:
+ nsProviderArray() :
+ mArray(1) { }
+ ~nsProviderArray() { }
+
+ // When looking up locales and skins, the "selected" locale is not always
+ // available. This enum identifies what kind of match is desired/found.
+ enum MatchType {
+ EXACT = 0,
+ LOCALE = 1, // "en-GB" is selected, we found "en-US"
+ ANY = 2
+ };
+
+ nsIURI* GetBase(const nsACString& aPreferred, MatchType aType);
+ const nsACString& GetSelected(const nsACString& aPreferred, MatchType aType);
+ void SetBase(const nsACString& aProvider, nsIURI* base);
+ void EnumerateToArray(nsTArray<nsCString> *a);
+
+ private:
+ ProviderEntry* GetProvider(const nsACString& aPreferred, MatchType aType);
+
+ nsTArray<ProviderEntry> mArray;
+ };
+
+ struct PackageEntry : public PLDHashEntryHdr
+ {
+ PackageEntry()
+ : flags(0) { }
+ ~PackageEntry() { }
+
+ nsCOMPtr<nsIURI> baseURI;
+ uint32_t flags;
+ nsProviderArray locales;
+ nsProviderArray skins;
+ };
+
+ class OverlayListEntry : public nsURIHashKey
+ {
+ public:
+ typedef nsURIHashKey::KeyType KeyType;
+ typedef nsURIHashKey::KeyTypePointer KeyTypePointer;
+
+ explicit OverlayListEntry(KeyTypePointer aKey) : nsURIHashKey(aKey) { }
+ OverlayListEntry(OverlayListEntry&& toMove) : nsURIHashKey(mozilla::Move(toMove)),
+ mArray(mozilla::Move(toMove.mArray)) { }
+ ~OverlayListEntry() { }
+
+ void AddURI(nsIURI* aURI);
+
+ nsCOMArray<nsIURI> mArray;
+ };
+
+ class OverlayListHash
+ {
+ public:
+ OverlayListHash() { }
+ ~OverlayListHash() { }
+
+ void Add(nsIURI* aBase, nsIURI* aOverlay);
+ void Clear() { mTable.Clear(); }
+ const nsCOMArray<nsIURI>* GetArray(nsIURI* aBase);
+
+ private:
+ nsTHashtable<OverlayListEntry> mTable;
+ };
+
+ // Hashes on the file to be overlaid (chrome://browser/content/browser.xul)
+ // to a list of overlays/stylesheets
+ OverlayListHash mOverlayHash;
+ OverlayListHash mStyleHash;
+
+ bool mProfileLoaded;
+ bool mDynamicRegistration;
+
+ nsCString mSelectedLocale;
+ nsCString mSelectedSkin;
+
+ // Hash of package names ("global") to PackageEntry objects
+ nsClassHashtable<nsCStringHashKey, PackageEntry> mPackagesHash;
+
+ virtual void ManifestContent(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestLocale(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestSkin(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestOverlay(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestStyle(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestOverride(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestResource(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+};
+
+#endif // nsChromeRegistryChrome_h
diff --git a/components/registry/src/nsChromeRegistryContent.cpp b/components/registry/src/nsChromeRegistryContent.cpp
new file mode 100644
index 000000000..97e8cba48
--- /dev/null
+++ b/components/registry/src/nsChromeRegistryContent.cpp
@@ -0,0 +1,317 @@
+/* -*- 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 "RegistryMessageUtils.h"
+#include "nsChromeRegistryContent.h"
+#include "nsString.h"
+#include "nsNetUtil.h"
+#include "nsIResProtocolHandler.h"
+
+nsChromeRegistryContent::nsChromeRegistryContent()
+{
+}
+
+void
+nsChromeRegistryContent::RegisterRemoteChrome(
+ const InfallibleTArray<ChromePackage>& aPackages,
+ const InfallibleTArray<SubstitutionMapping>& aSubstitutions,
+ const InfallibleTArray<OverrideMapping>& aOverrides,
+ const nsACString& aLocale,
+ bool aReset)
+{
+ MOZ_ASSERT(aReset || mLocale.IsEmpty(),
+ "RegisterChrome twice?");
+
+ if (aReset) {
+ mPackagesHash.Clear();
+ mOverrideTable.Clear();
+ // XXX Can't clear resources.
+ }
+
+ for (uint32_t i = aPackages.Length(); i > 0; ) {
+ --i;
+ RegisterPackage(aPackages[i]);
+ }
+
+ for (uint32_t i = aSubstitutions.Length(); i > 0; ) {
+ --i;
+ RegisterSubstitution(aSubstitutions[i]);
+ }
+
+ for (uint32_t i = aOverrides.Length(); i > 0; ) {
+ --i;
+ RegisterOverride(aOverrides[i]);
+ }
+
+ mLocale = aLocale;
+}
+
+void
+nsChromeRegistryContent::RegisterPackage(const ChromePackage& aPackage)
+{
+ nsCOMPtr<nsIIOService> io (do_GetIOService());
+ if (!io)
+ return;
+
+ nsCOMPtr<nsIURI> content, locale, skin;
+
+ if (aPackage.contentBaseURI.spec.Length()) {
+ nsresult rv = NS_NewURI(getter_AddRefs(content),
+ aPackage.contentBaseURI.spec,
+ aPackage.contentBaseURI.charset.get(),
+ nullptr, io);
+ if (NS_FAILED(rv))
+ return;
+ }
+ if (aPackage.localeBaseURI.spec.Length()) {
+ nsresult rv = NS_NewURI(getter_AddRefs(locale),
+ aPackage.localeBaseURI.spec,
+ aPackage.localeBaseURI.charset.get(),
+ nullptr, io);
+ if (NS_FAILED(rv))
+ return;
+ }
+ if (aPackage.skinBaseURI.spec.Length()) {
+ nsresult rv = NS_NewURI(getter_AddRefs(skin),
+ aPackage.skinBaseURI.spec,
+ aPackage.skinBaseURI.charset.get(),
+ nullptr, io);
+ if (NS_FAILED(rv))
+ return;
+ }
+
+ PackageEntry* entry = new PackageEntry;
+ entry->flags = aPackage.flags;
+ entry->contentBaseURI = content;
+ entry->localeBaseURI = locale;
+ entry->skinBaseURI = skin;
+
+ mPackagesHash.Put(aPackage.package, entry);
+}
+
+void
+nsChromeRegistryContent::RegisterSubstitution(const SubstitutionMapping& aSubstitution)
+{
+ nsCOMPtr<nsIIOService> io (do_GetIOService());
+ if (!io)
+ return;
+
+ nsCOMPtr<nsIProtocolHandler> ph;
+ nsresult rv = io->GetProtocolHandler(aSubstitution.scheme.get(), getter_AddRefs(ph));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsISubstitutingProtocolHandler> sph (do_QueryInterface(ph));
+ if (!sph)
+ return;
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ if (aSubstitution.resolvedURI.spec.Length()) {
+ rv = NS_NewURI(getter_AddRefs(resolvedURI),
+ aSubstitution.resolvedURI.spec,
+ aSubstitution.resolvedURI.charset.get(),
+ nullptr, io);
+ if (NS_FAILED(rv))
+ return;
+ }
+
+ rv = sph->SetSubstitution(aSubstitution.path, resolvedURI);
+ if (NS_FAILED(rv))
+ return;
+}
+
+void
+nsChromeRegistryContent::RegisterOverride(const OverrideMapping& aOverride)
+{
+ nsCOMPtr<nsIIOService> io (do_GetIOService());
+ if (!io)
+ return;
+
+ nsCOMPtr<nsIURI> chromeURI, overrideURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(chromeURI),
+ aOverride.originalURI.spec,
+ aOverride.originalURI.charset.get(),
+ nullptr, io);
+ if (NS_FAILED(rv))
+ return;
+
+ rv = NS_NewURI(getter_AddRefs(overrideURI), aOverride.overrideURI.spec,
+ aOverride.overrideURI.charset.get(), nullptr, io);
+ if (NS_FAILED(rv))
+ return;
+
+ mOverrideTable.Put(chromeURI, overrideURI);
+}
+
+nsIURI*
+nsChromeRegistryContent::GetBaseURIFromPackage(const nsCString& aPackage,
+ const nsCString& aProvider,
+ const nsCString& aPath)
+{
+ PackageEntry* entry;
+ if (!mPackagesHash.Get(aPackage, &entry)) {
+ return nullptr;
+ }
+
+ if (aProvider.EqualsLiteral("locale")) {
+ return entry->localeBaseURI;
+ }
+ else if (aProvider.EqualsLiteral("skin")) {
+ return entry->skinBaseURI;
+ }
+ else if (aProvider.EqualsLiteral("content")) {
+ return entry->contentBaseURI;
+ }
+ return nullptr;
+}
+
+nsresult
+nsChromeRegistryContent::GetFlagsFromPackage(const nsCString& aPackage,
+ uint32_t* aFlags)
+{
+ PackageEntry* entry;
+ if (!mPackagesHash.Get(aPackage, &entry)) {
+ return NS_ERROR_FAILURE;
+ }
+ *aFlags = entry->flags;
+ return NS_OK;
+}
+
+// All functions following only make sense in chrome, and therefore assert
+
+#define CONTENT_NOTREACHED() \
+ NS_NOTREACHED("Content should not be calling this")
+
+#define CONTENT_NOT_IMPLEMENTED() \
+ CONTENT_NOTREACHED(); \
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+NS_IMETHODIMP
+nsChromeRegistryContent::GetLocalesForPackage(const nsACString& aPackage,
+ nsIUTF8StringEnumerator* *aResult)
+{
+ CONTENT_NOT_IMPLEMENTED();
+}
+
+NS_IMETHODIMP
+nsChromeRegistryContent::CheckForOSAccessibility()
+{
+ CONTENT_NOT_IMPLEMENTED();
+}
+
+NS_IMETHODIMP
+nsChromeRegistryContent::CheckForNewChrome()
+{
+ CONTENT_NOT_IMPLEMENTED();
+}
+
+NS_IMETHODIMP
+nsChromeRegistryContent::IsLocaleRTL(const nsACString& aPackage,
+ bool *aResult)
+{
+ if (aPackage != nsDependentCString("global")) {
+ NS_ERROR("Packages other than global unavailable");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aResult = GetDirectionForLocale(mLocale);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeRegistryContent::GetSelectedLocale(const nsACString& aPackage,
+ bool aAsBCP47,
+ nsACString& aLocale)
+{
+ if (aPackage != nsDependentCString("global")) {
+ NS_ERROR("Uh-oh, caller wanted something other than 'some local'");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aLocale = mLocale;
+ if (aAsBCP47) {
+ SanitizeForBCP47(aLocale);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChromeRegistryContent::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ CONTENT_NOT_IMPLEMENTED();
+}
+
+NS_IMETHODIMP
+nsChromeRegistryContent::GetStyleOverlays(nsIURI *aChromeURL,
+ nsISimpleEnumerator **aResult)
+{
+ CONTENT_NOT_IMPLEMENTED();
+}
+
+NS_IMETHODIMP
+nsChromeRegistryContent::GetXULOverlays(nsIURI *aChromeURL,
+ nsISimpleEnumerator **aResult)
+{
+ CONTENT_NOT_IMPLEMENTED();
+}
+
+nsresult nsChromeRegistryContent::UpdateSelectedLocale()
+{
+ CONTENT_NOT_IMPLEMENTED();
+}
+
+void
+nsChromeRegistryContent::ManifestContent(ManifestProcessingContext& cx,
+ int lineno, char *const * argv,
+ int flags)
+{
+ CONTENT_NOTREACHED();
+}
+
+void
+nsChromeRegistryContent::ManifestLocale(ManifestProcessingContext& cx,
+ int lineno,
+ char *const * argv, int flags)
+{
+ CONTENT_NOTREACHED();
+}
+
+void
+nsChromeRegistryContent::ManifestSkin(ManifestProcessingContext& cx,
+ int lineno,
+ char *const * argv, int flags)
+{
+ CONTENT_NOTREACHED();
+}
+
+void
+nsChromeRegistryContent::ManifestOverlay(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags)
+{
+ CONTENT_NOTREACHED();
+}
+
+void
+nsChromeRegistryContent::ManifestStyle(ManifestProcessingContext& cx,
+ int lineno,
+ char *const * argv, int flags)
+{
+ CONTENT_NOTREACHED();
+}
+
+void
+nsChromeRegistryContent::ManifestOverride(ManifestProcessingContext& cx,
+ int lineno,
+ char *const * argv, int flags)
+{
+ CONTENT_NOTREACHED();
+}
+
+void
+nsChromeRegistryContent::ManifestResource(ManifestProcessingContext& cx,
+ int lineno,
+ char *const * argv, int flags)
+{
+ CONTENT_NOTREACHED();
+}
diff --git a/components/registry/src/nsChromeRegistryContent.h b/components/registry/src/nsChromeRegistryContent.h
new file mode 100644
index 000000000..0a74d13db
--- /dev/null
+++ b/components/registry/src/nsChromeRegistryContent.h
@@ -0,0 +1,84 @@
+/* -*- 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/. */
+
+#ifndef nsChromeRegistryContent_h
+#define nsChromeRegistryContent_h
+
+#include "nsChromeRegistry.h"
+#include "nsClassHashtable.h"
+
+struct ChromePackage;
+struct SubstitutionMapping;
+struct OverrideMapping;
+
+class nsChromeRegistryContent : public nsChromeRegistry
+{
+ public:
+ nsChromeRegistryContent();
+
+ void RegisterRemoteChrome(const InfallibleTArray<ChromePackage>& aPackages,
+ const InfallibleTArray<SubstitutionMapping>& aResources,
+ const InfallibleTArray<OverrideMapping>& aOverrides,
+ const nsACString& aLocale,
+ bool aReset);
+
+ NS_IMETHOD GetLocalesForPackage(const nsACString& aPackage,
+ nsIUTF8StringEnumerator* *aResult) override;
+ NS_IMETHOD CheckForNewChrome() override;
+ NS_IMETHOD CheckForOSAccessibility() override;
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override;
+ NS_IMETHOD IsLocaleRTL(const nsACString& package,
+ bool *aResult) override;
+ NS_IMETHOD GetSelectedLocale(const nsACString& aPackage,
+ bool aAsBCP47,
+ nsACString& aLocale) override;
+ NS_IMETHOD GetStyleOverlays(nsIURI *aChromeURL,
+ nsISimpleEnumerator **aResult) override;
+ NS_IMETHOD GetXULOverlays(nsIURI *aChromeURL,
+ nsISimpleEnumerator **aResult) override;
+
+ void RegisterPackage(const ChromePackage& aPackage);
+ void RegisterOverride(const OverrideMapping& aOverride);
+ void RegisterSubstitution(const SubstitutionMapping& aResource);
+
+ private:
+ struct PackageEntry
+ {
+ PackageEntry() : flags(0) { }
+ ~PackageEntry() { }
+
+ nsCOMPtr<nsIURI> contentBaseURI;
+ nsCOMPtr<nsIURI> localeBaseURI;
+ nsCOMPtr<nsIURI> skinBaseURI;
+ uint32_t flags;
+ };
+
+ nsresult UpdateSelectedLocale() override;
+ nsIURI* GetBaseURIFromPackage(const nsCString& aPackage,
+ const nsCString& aProvider,
+ const nsCString& aPath) override;
+ nsresult GetFlagsFromPackage(const nsCString& aPackage, uint32_t* aFlags) override;
+
+ nsClassHashtable<nsCStringHashKey, PackageEntry> mPackagesHash;
+ nsCString mLocale;
+
+ virtual void ManifestContent(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestLocale(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestSkin(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestOverlay(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestStyle(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestOverride(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+ virtual void ManifestResource(ManifestProcessingContext& cx, int lineno,
+ char *const * argv, int flags) override;
+};
+
+#endif // nsChromeRegistryContent_h
diff --git a/components/remote/moz.build b/components/remote/moz.build
new file mode 100644
index 000000000..75926d9d9
--- /dev/null
+++ b/components/remote/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'nsIRemoteService.idl',
+]
+
+XPIDL_MODULE = 'toolkitremote'
+
+SOURCES += [
+ 'nsXRemoteService.cpp',
+]
+
+if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ SOURCES += [
+ 'nsGTKRemoteService.cpp',
+ ]
+
+FINAL_LIBRARY = 'xul'
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
diff --git a/components/remote/nsGTKRemoteService.cpp b/components/remote/nsGTKRemoteService.cpp
new file mode 100644
index 000000000..860efe015
--- /dev/null
+++ b/components/remote/nsGTKRemoteService.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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 "nsGTKRemoteService.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsIServiceManager.h"
+#include "nsIWeakReference.h"
+#include "nsIWidget.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+
+#include "nsCOMPtr.h"
+
+#include "nsGTKToolkit.h"
+
+NS_IMPL_ISUPPORTS(nsGTKRemoteService,
+ nsIRemoteService,
+ nsIObserver)
+
+NS_IMETHODIMP
+nsGTKRemoteService::Startup(const char* aAppName, const char* aProfileName)
+{
+ NS_ASSERTION(aAppName, "Don't pass a null appname!");
+ sRemoteImplementation = this;
+
+ if (mServerWindow) return NS_ERROR_ALREADY_INITIALIZED;
+
+ XRemoteBaseStartup(aAppName, aProfileName);
+
+ mServerWindow = gtk_invisible_new();
+ gtk_widget_realize(mServerWindow);
+ HandleCommandsFor(mServerWindow, nullptr);
+
+ for (auto iter = mWindows.Iter(); !iter.Done(); iter.Next()) {
+ HandleCommandsFor(iter.Key(), iter.UserData());
+ }
+
+ return NS_OK;
+}
+
+static nsIWidget* GetMainWidget(nsPIDOMWindowInner* aWindow)
+{
+ // get the native window for this instance
+ nsCOMPtr<nsIBaseWindow> baseWindow
+ (do_QueryInterface(aWindow->GetDocShell()));
+ NS_ENSURE_TRUE(baseWindow, nullptr);
+
+ nsCOMPtr<nsIWidget> mainWidget;
+ baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
+ return mainWidget;
+}
+
+NS_IMETHODIMP
+nsGTKRemoteService::RegisterWindow(mozIDOMWindow* aWindow)
+{
+ nsIWidget* mainWidget = GetMainWidget(nsPIDOMWindowInner::From(aWindow));
+ NS_ENSURE_TRUE(mainWidget, NS_ERROR_FAILURE);
+
+ GtkWidget* widget =
+ (GtkWidget*) mainWidget->GetNativeData(NS_NATIVE_SHELLWIDGET);
+ NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(aWindow);
+ NS_ENSURE_TRUE(weak, NS_ERROR_FAILURE);
+
+ mWindows.Put(widget, weak);
+
+ // If Startup() has already been called, immediately register this window.
+ if (mServerWindow) {
+ HandleCommandsFor(widget, weak);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGTKRemoteService::Shutdown()
+{
+ if (!mServerWindow)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ gtk_widget_destroy(mServerWindow);
+ mServerWindow = nullptr;
+ return NS_OK;
+}
+
+// Set desktop startup ID to the passed ID, if there is one, so that any created
+// windows get created with the right window manager metadata, and any windows
+// that get new tabs and are activated also get the right WM metadata.
+// The timestamp will be used if there is no desktop startup ID, or if we're
+// raising an existing window rather than showing a new window for the first time.
+void
+nsGTKRemoteService::SetDesktopStartupIDOrTimestamp(const nsACString& aDesktopStartupID,
+ uint32_t aTimestamp) {
+ nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit();
+ if (!toolkit)
+ return;
+
+ if (!aDesktopStartupID.IsEmpty()) {
+ toolkit->SetDesktopStartupID(aDesktopStartupID);
+ }
+
+ toolkit->SetFocusTimestamp(aTimestamp);
+}
+
+
+void
+nsGTKRemoteService::HandleCommandsFor(GtkWidget* widget,
+ nsIWeakReference* aWindow)
+{
+ g_signal_connect(G_OBJECT(widget), "property_notify_event",
+ G_CALLBACK(HandlePropertyChange), aWindow);
+
+ gtk_widget_add_events(widget, GDK_PROPERTY_CHANGE_MASK);
+
+#if (MOZ_WIDGET_GTK == 2)
+ Window window = GDK_WINDOW_XWINDOW(widget->window);
+#else
+ Window window = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
+#endif
+ nsXRemoteService::HandleCommandsFor(window);
+
+}
+
+gboolean
+nsGTKRemoteService::HandlePropertyChange(GtkWidget *aWidget,
+ GdkEventProperty *pevent,
+ nsIWeakReference *aThis)
+{
+ if (pevent->state == GDK_PROPERTY_NEW_VALUE) {
+ Atom changedAtom = gdk_x11_atom_to_xatom(pevent->atom);
+
+#if (MOZ_WIDGET_GTK == 2)
+ XID window = GDK_WINDOW_XWINDOW(pevent->window);
+#else
+ XID window = gdk_x11_window_get_xid(gtk_widget_get_window(aWidget));
+#endif
+ return HandleNewProperty(window,
+ GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
+ pevent->time, changedAtom, aThis);
+ }
+ return FALSE;
+}
+
+
+// {C0773E90-5799-4eff-AD03-3EBCD85624AC}
+#define NS_REMOTESERVICE_CID \
+ { 0xc0773e90, 0x5799, 0x4eff, { 0xad, 0x3, 0x3e, 0xbc, 0xd8, 0x56, 0x24, 0xac } }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsGTKRemoteService)
+NS_DEFINE_NAMED_CID(NS_REMOTESERVICE_CID);
+
+static const mozilla::Module::CIDEntry kRemoteCIDs[] = {
+ { &kNS_REMOTESERVICE_CID, false, nullptr, nsGTKRemoteServiceConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kRemoteContracts[] = {
+ { "@mozilla.org/toolkit/remote-service;1", &kNS_REMOTESERVICE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kRemoteModule = {
+ mozilla::Module::kVersion,
+ kRemoteCIDs,
+ kRemoteContracts
+};
+
+NSMODULE_DEFN(RemoteServiceModule) = &kRemoteModule;
diff --git a/components/remote/nsGTKRemoteService.h b/components/remote/nsGTKRemoteService.h
new file mode 100644
index 000000000..034a77a24
--- /dev/null
+++ b/components/remote/nsGTKRemoteService.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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 __nsGTKRemoteService_h__
+#define __nsGTKRemoteService_h__
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "nsInterfaceHashtable.h"
+#include "nsXRemoteService.h"
+#include "mozilla/Attributes.h"
+
+class nsGTKRemoteService final : public nsXRemoteService
+{
+public:
+ // We will be a static singleton, so don't use the ordinary methods.
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREMOTESERVICE
+
+
+ nsGTKRemoteService() :
+ mServerWindow(nullptr) { }
+
+private:
+ ~nsGTKRemoteService() { }
+
+ void HandleCommandsFor(GtkWidget* aWidget,
+ nsIWeakReference* aWindow);
+
+
+ static gboolean HandlePropertyChange(GtkWidget *widget,
+ GdkEventProperty *event,
+ nsIWeakReference* aThis);
+
+
+ virtual void SetDesktopStartupIDOrTimestamp(const nsACString& aDesktopStartupID,
+ uint32_t aTimestamp) override;
+
+ nsInterfaceHashtable<nsPtrHashKey<GtkWidget>, nsIWeakReference> mWindows;
+ GtkWidget* mServerWindow;
+};
+
+#endif // __nsGTKRemoteService_h__
diff --git a/components/remote/nsIRemoteService.idl b/components/remote/nsIRemoteService.idl
new file mode 100644
index 000000000..e8ba2f1c8
--- /dev/null
+++ b/components/remote/nsIRemoteService.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindow;
+
+/**
+ * Start and stop the remote service (xremote/phremote), and register
+ * windows with the service for backwards compatibility with old xremote
+ * clients.
+ *
+ * @status FLUID This interface is not frozen and is not intended for embedders
+ * who want a frozen API. If you are an embedder and need this
+ * functionality, contact Benjamin Smedberg about the possibility
+ * of freezing the functionality you need.
+ */
+
+[scriptable, uuid(bf23f1c3-7012-42dd-b0bb-a84060ccc52e)]
+interface nsIRemoteService : nsISupports
+{
+ /**
+ * Start the remote service. This should not be done until app startup
+ * appears to have been successful.
+ *
+ * @param appName (Required) Sets a window property identifying the
+ * application.
+ * @param profileName (May be null) Sets a window property identifying the
+ * profile name.
+ */
+ void startup(in string appName, in string profileName);
+
+ /**
+ * Register a XUL window with the xremote service. The window will be
+ * configured to accept incoming remote requests. If this method is called
+ * before startup(), the registration will happen once startup() is called.
+ */
+ void registerWindow(in mozIDOMWindow aWindow);
+
+ /**
+ * Stop the remote service from accepting additional requests.
+ */
+ void shutdown();
+};
diff --git a/components/remote/nsXRemoteService.cpp b/components/remote/nsXRemoteService.cpp
new file mode 100644
index 000000000..41a40e471
--- /dev/null
+++ b/components/remote/nsXRemoteService.cpp
@@ -0,0 +1,324 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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 "mozilla/ArrayUtils.h"
+
+#include "nsXRemoteService.h"
+#include "nsIObserverService.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsICommandLineRunner.h"
+#include "nsICommandLine.h"
+
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIFile.h"
+#include "nsIServiceManager.h"
+#include "nsIWeakReference.h"
+#include "nsIWidget.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/X11Util.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "prprf.h"
+#include "prenv.h"
+#include "nsCRT.h"
+
+#include "nsXULAppAPI.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+using namespace mozilla;
+
+#define MOZILLA_VERSION_PROP "_MOZILLA_VERSION"
+#define MOZILLA_LOCK_PROP "_MOZILLA_LOCK"
+#define MOZILLA_RESPONSE_PROP "_MOZILLA_RESPONSE"
+#define MOZILLA_USER_PROP "_MOZILLA_USER"
+#define MOZILLA_PROFILE_PROP "_MOZILLA_PROFILE"
+#define MOZILLA_PROGRAM_PROP "_MOZILLA_PROGRAM"
+#define MOZILLA_COMMANDLINE_PROP "_MOZILLA_COMMANDLINE"
+
+const unsigned char kRemoteVersion[] = "5.1";
+
+#ifdef IS_BIG_ENDIAN
+#define TO_LITTLE_ENDIAN32(x) \
+ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
+#else
+#define TO_LITTLE_ENDIAN32(x) (x)
+#endif
+
+// Minimize the roundtrips to the X server by getting all the atoms at once
+static const char *XAtomNames[] = {
+ MOZILLA_VERSION_PROP,
+ MOZILLA_LOCK_PROP,
+ MOZILLA_RESPONSE_PROP,
+ MOZILLA_USER_PROP,
+ MOZILLA_PROFILE_PROP,
+ MOZILLA_PROGRAM_PROP,
+ MOZILLA_COMMANDLINE_PROP
+};
+static Atom XAtoms[MOZ_ARRAY_LENGTH(XAtomNames)];
+
+Atom nsXRemoteService::sMozVersionAtom;
+Atom nsXRemoteService::sMozLockAtom;
+Atom nsXRemoteService::sMozResponseAtom;
+Atom nsXRemoteService::sMozUserAtom;
+Atom nsXRemoteService::sMozProfileAtom;
+Atom nsXRemoteService::sMozProgramAtom;
+Atom nsXRemoteService::sMozCommandLineAtom;
+
+nsXRemoteService * nsXRemoteService::sRemoteImplementation = 0;
+
+
+static bool
+FindExtensionParameterInCommand(const char* aParameterName,
+ const nsACString& aCommand,
+ char aSeparator,
+ nsACString* aValue)
+{
+ nsAutoCString searchFor;
+ searchFor.Append(aSeparator);
+ searchFor.Append(aParameterName);
+ searchFor.Append('=');
+
+ nsACString::const_iterator start, end;
+ aCommand.BeginReading(start);
+ aCommand.EndReading(end);
+ if (!FindInReadable(searchFor, start, end))
+ return false;
+
+ nsACString::const_iterator charStart, charEnd;
+ charStart = end;
+ aCommand.EndReading(charEnd);
+ nsACString::const_iterator idStart = charStart, idEnd;
+ if (FindCharInReadable(aSeparator, charStart, charEnd)) {
+ idEnd = charStart;
+ } else {
+ idEnd = charEnd;
+ }
+ *aValue = nsDependentCSubstring(idStart, idEnd);
+ return true;
+}
+
+
+nsXRemoteService::nsXRemoteService()
+{
+}
+
+void
+nsXRemoteService::XRemoteBaseStartup(const char *aAppName, const char *aProfileName)
+{
+ EnsureAtoms();
+
+ mAppName = aAppName;
+ ToLowerCase(mAppName);
+
+ mProfileName = aProfileName;
+
+ nsCOMPtr<nsIObserverService> obs(do_GetService("@mozilla.org/observer-service;1"));
+ if (obs) {
+ obs->AddObserver(this, "xpcom-shutdown", false);
+ obs->AddObserver(this, "quit-application", false);
+ }
+}
+
+void
+nsXRemoteService::HandleCommandsFor(Window aWindowId)
+{
+ // set our version
+ XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozVersionAtom, XA_STRING,
+ 8, PropModeReplace, kRemoteVersion, sizeof(kRemoteVersion) - 1);
+
+ // get our username
+ unsigned char *logname;
+ logname = (unsigned char*) PR_GetEnv("LOGNAME");
+ if (logname) {
+ // set the property on the window if it's available
+ XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozUserAtom, XA_STRING,
+ 8, PropModeReplace, logname, strlen((char*) logname));
+ }
+
+ XChangeProperty(mozilla::DefaultXDisplay(), aWindowId, sMozProgramAtom, XA_STRING,
+ 8, PropModeReplace, (unsigned char*) mAppName.get(), mAppName.Length());
+
+ if (!mProfileName.IsEmpty()) {
+ XChangeProperty(mozilla::DefaultXDisplay(),
+ aWindowId, sMozProfileAtom, XA_STRING,
+ 8, PropModeReplace,
+ (unsigned char*) mProfileName.get(), mProfileName.Length());
+ }
+
+}
+
+NS_IMETHODIMP
+nsXRemoteService::Observe(nsISupports* aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ // This can be xpcom-shutdown or quit-application, but it's the same either
+ // way.
+ Shutdown();
+ return NS_OK;
+}
+
+bool
+nsXRemoteService::HandleNewProperty(XID aWindowId, Display* aDisplay,
+ Time aEventTime,
+ Atom aChangedAtom,
+ nsIWeakReference* aDomWindow)
+{
+
+ nsCOMPtr<nsIDOMWindow> window (do_QueryReferent(aDomWindow));
+
+ if (aChangedAtom == sMozCommandLineAtom) {
+ // We got a new command atom.
+ int result;
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ char *data = 0;
+
+ result = XGetWindowProperty (aDisplay,
+ aWindowId,
+ aChangedAtom,
+ 0, /* long_offset */
+ (65536 / sizeof (long)), /* long_length */
+ True, /* atomic delete after */
+ XA_STRING, /* req_type */
+ &actual_type, /* actual_type return */
+ &actual_format, /* actual_format_return */
+ &nitems, /* nitems_return */
+ &bytes_after, /* bytes_after_return */
+ (unsigned char **)&data); /* prop_return
+ (we only care
+ about the first ) */
+
+ // Failed to get property off the window?
+ if (result != Success)
+ return false;
+
+ // Failed to get the data off the window or it was the wrong type?
+ if (!data || !TO_LITTLE_ENDIAN32(*reinterpret_cast<int32_t*>(data)))
+ return false;
+
+ // cool, we got the property data.
+ const char *response = HandleCommandLine(data, window, aEventTime);
+
+ // put the property onto the window as the response
+ XChangeProperty (aDisplay, aWindowId,
+ sMozResponseAtom, XA_STRING,
+ 8, PropModeReplace,
+ (const unsigned char *)response,
+ strlen (response));
+ XFree(data);
+ return true;
+ }
+
+ else if (aChangedAtom == sMozResponseAtom) {
+ // client accepted the response. party on wayne.
+ return true;
+ }
+
+ else if (aChangedAtom == sMozLockAtom) {
+ // someone locked the window
+ return true;
+ }
+
+ return false;
+}
+
+const char*
+nsXRemoteService::HandleCommandLine(char* aBuffer, nsIDOMWindow* aWindow,
+ uint32_t aTimestamp)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsICommandLineRunner> cmdline
+ (do_CreateInstance("@mozilla.org/toolkit/command-line;1", &rv));
+ if (NS_FAILED(rv))
+ return "509 internal error";
+
+ // the commandline property is constructed as an array of int32_t
+ // followed by a series of null-terminated strings:
+ //
+ // [argc][offsetargv0][offsetargv1...]<workingdir>\0<argv[0]>\0argv[1]...\0
+ // (offset is from the beginning of the buffer)
+
+ int32_t argc = TO_LITTLE_ENDIAN32(*reinterpret_cast<int32_t*>(aBuffer));
+ char *wd = aBuffer + ((argc + 1) * sizeof(int32_t));
+
+ nsCOMPtr<nsIFile> lf;
+ rv = NS_NewNativeLocalFile(nsDependentCString(wd), true,
+ getter_AddRefs(lf));
+ if (NS_FAILED(rv))
+ return "509 internal error";
+
+ nsAutoCString desktopStartupID;
+
+ char **argv = (char**) malloc(sizeof(char*) * argc);
+ if (!argv) return "509 internal error";
+
+ int32_t *offset = reinterpret_cast<int32_t*>(aBuffer) + 1;
+
+ for (int i = 0; i < argc; ++i) {
+ argv[i] = aBuffer + TO_LITTLE_ENDIAN32(offset[i]);
+
+ if (i == 0) {
+ nsDependentCString cmd(argv[0]);
+ FindExtensionParameterInCommand("DESKTOP_STARTUP_ID",
+ cmd, ' ',
+ &desktopStartupID);
+ }
+ }
+
+ rv = cmdline->Init(argc, argv, lf, nsICommandLine::STATE_REMOTE_AUTO);
+
+ free (argv);
+ if (NS_FAILED(rv)) {
+ return "509 internal error";
+ }
+
+ if (aWindow)
+ cmdline->SetWindowContext(aWindow);
+
+ if (sRemoteImplementation)
+ sRemoteImplementation->SetDesktopStartupIDOrTimestamp(desktopStartupID, aTimestamp);
+
+ rv = cmdline->Run();
+
+ if (NS_ERROR_ABORT == rv)
+ return "500 command not parseable";
+
+ if (NS_FAILED(rv))
+ return "509 internal error";
+
+ return "200 executed command";
+}
+
+void
+nsXRemoteService::EnsureAtoms(void)
+{
+ if (sMozVersionAtom)
+ return;
+
+ XInternAtoms(mozilla::DefaultXDisplay(), const_cast<char**>(XAtomNames),
+ ArrayLength(XAtomNames), False, XAtoms);
+
+ int i = 0;
+ sMozVersionAtom = XAtoms[i++];
+ sMozLockAtom = XAtoms[i++];
+ sMozResponseAtom = XAtoms[i++];
+ sMozUserAtom = XAtoms[i++];
+ sMozProfileAtom = XAtoms[i++];
+ sMozProgramAtom = XAtoms[i++];
+ sMozCommandLineAtom = XAtoms[i++];
+}
diff --git a/components/remote/nsXRemoteService.h b/components/remote/nsXRemoteService.h
new file mode 100644
index 000000000..718633675
--- /dev/null
+++ b/components/remote/nsXRemoteService.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#ifndef NSXREMOTESERVICE_H
+#define NSXREMOTESERVICE_H
+
+#include "nsString.h"
+
+#include "nsIRemoteService.h"
+#include "nsIObserver.h"
+#include <X11/Xlib.h>
+#include <X11/X.h>
+
+class nsIDOMWindow;
+class nsIWeakReference;
+
+/**
+ Base class for GTK/Qt remote service
+*/
+class nsXRemoteService : public nsIRemoteService,
+ public nsIObserver
+{
+public:
+ NS_DECL_NSIOBSERVER
+
+
+protected:
+ nsXRemoteService();
+
+ static bool HandleNewProperty(Window aWindowId,Display* aDisplay,
+ Time aEventTime, Atom aChangedAtom,
+ nsIWeakReference* aDomWindow);
+
+ void XRemoteBaseStartup(const char *aAppName, const char *aProfileName);
+
+ void HandleCommandsFor(Window aWindowId);
+ static nsXRemoteService *sRemoteImplementation;
+private:
+ void EnsureAtoms();
+ static const char* HandleCommandLine(char* aBuffer, nsIDOMWindow* aWindow,
+ uint32_t aTimestamp);
+
+ virtual void SetDesktopStartupIDOrTimestamp(const nsACString& aDesktopStartupID,
+ uint32_t aTimestamp) = 0;
+
+ nsCString mAppName;
+ nsCString mProfileName;
+
+ static Atom sMozVersionAtom;
+ static Atom sMozLockAtom;
+ static Atom sMozResponseAtom;
+ static Atom sMozUserAtom;
+ static Atom sMozProfileAtom;
+ static Atom sMozProgramAtom;
+ static Atom sMozCommandLineAtom;
+};
+
+#endif // NSXREMOTESERVICE_H
diff --git a/components/remotebrowserutils/RemoteWebNavigation.js b/components/remotebrowserutils/RemoteWebNavigation.js
new file mode 100644
index 000000000..5790c0004
--- /dev/null
+++ b/components/remotebrowserutils/RemoteWebNavigation.js
@@ -0,0 +1,139 @@
+// -*- 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 { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+
+function makeURI(url)
+{
+ return Services.io.newURI(url, null, null);
+}
+
+function readInputStreamToString(aStream)
+{
+ return NetUtil.readInputStreamToString(aStream, aStream.available());
+}
+
+function RemoteWebNavigation()
+{
+ this.wrappedJSObject = this;
+}
+
+RemoteWebNavigation.prototype = {
+ classDescription: "nsIWebNavigation for remote browsers",
+ classID: Components.ID("{4b56964e-cdf3-4bb8-830c-0e2dad3f4ebd}"),
+ contractID: "@mozilla.org/remote-web-navigation;1",
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebNavigation, Ci.nsISupports]),
+
+ swapBrowser: function(aBrowser) {
+ this._browser = aBrowser;
+ },
+
+ LOAD_FLAGS_MASK: 65535,
+ LOAD_FLAGS_NONE: 0,
+ LOAD_FLAGS_IS_REFRESH: 16,
+ LOAD_FLAGS_IS_LINK: 32,
+ LOAD_FLAGS_BYPASS_HISTORY: 64,
+ LOAD_FLAGS_REPLACE_HISTORY: 128,
+ LOAD_FLAGS_BYPASS_CACHE: 256,
+ LOAD_FLAGS_BYPASS_PROXY: 512,
+ LOAD_FLAGS_CHARSET_CHANGE: 1024,
+ LOAD_FLAGS_STOP_CONTENT: 2048,
+ LOAD_FLAGS_FROM_EXTERNAL: 4096,
+ LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP: 8192,
+ LOAD_FLAGS_FIRST_LOAD: 16384,
+ LOAD_FLAGS_ALLOW_POPUPS: 32768,
+ LOAD_FLAGS_BYPASS_CLASSIFIER: 65536,
+ LOAD_FLAGS_FORCE_ALLOW_COOKIES: 131072,
+
+ STOP_NETWORK: 1,
+ STOP_CONTENT: 2,
+ STOP_ALL: 3,
+
+ canGoBack: false,
+ canGoForward: false,
+ goBack: function() {
+ this._sendMessage("WebNavigation:GoBack", {});
+ },
+ goForward: function() {
+ this._sendMessage("WebNavigation:GoForward", {});
+ },
+ gotoIndex: function(aIndex) {
+ this._sendMessage("WebNavigation:GotoIndex", {index: aIndex});
+ },
+ loadURI: function(aURI, aLoadFlags, aReferrer, aPostData, aHeaders) {
+ this.loadURIWithOptions(aURI, aLoadFlags, aReferrer,
+ Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ aPostData, aHeaders, null);
+ },
+ loadURIWithOptions: function(aURI, aLoadFlags, aReferrer, aReferrerPolicy,
+ aPostData, aHeaders, aBaseURI) {
+ this._sendMessage("WebNavigation:LoadURI", {
+ uri: aURI,
+ flags: aLoadFlags,
+ referrer: aReferrer ? aReferrer.spec : null,
+ referrerPolicy: aReferrerPolicy,
+ postData: aPostData ? readInputStreamToString(aPostData) : null,
+ headers: aHeaders ? readInputStreamToString(aHeaders) : null,
+ baseURI: aBaseURI ? aBaseURI.spec : null,
+ });
+ },
+ setOriginAttributesBeforeLoading: function(aOriginAttributes) {
+ this._sendMessage("WebNavigation:SetOriginAttributes", {
+ originAttributes: aOriginAttributes,
+ });
+ },
+ reload: function(aReloadFlags) {
+ this._sendMessage("WebNavigation:Reload", {flags: aReloadFlags});
+ },
+ stop: function(aStopFlags) {
+ this._sendMessage("WebNavigation:Stop", {flags: aStopFlags});
+ },
+
+ get document() {
+ return this._browser.contentDocument;
+ },
+
+ _currentURI: null,
+ get currentURI() {
+ if (!this._currentURI) {
+ this._currentURI = makeURI("about:blank");
+ }
+
+ return this._currentURI;
+ },
+ set currentURI(aURI) {
+ this.loadURI(aURI.spec, null, null, null);
+ },
+
+ referringURI: null,
+
+ // Bug 1233803 - accessing the sessionHistory of remote browsers should be
+ // done in content scripts.
+ get sessionHistory() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ set sessionHistory(aValue) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ _sendMessage: function(aMessage, aData) {
+ try {
+ this._browser.messageManager.sendAsyncMessage(aMessage, aData);
+ }
+ catch (e) {
+ Cu.reportError(e);
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RemoteWebNavigation]);
diff --git a/components/remotebrowserutils/moz.build b/components/remotebrowserutils/moz.build
new file mode 100644
index 000000000..b9fa9c4b5
--- /dev/null
+++ b/components/remotebrowserutils/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'remotebrowserutils.manifest',
+ 'RemoteWebNavigation.js',
+]
diff --git a/components/remotebrowserutils/remotebrowserutils.manifest b/components/remotebrowserutils/remotebrowserutils.manifest
new file mode 100644
index 000000000..d762d65a0
--- /dev/null
+++ b/components/remotebrowserutils/remotebrowserutils.manifest
@@ -0,0 +1,2 @@
+component {4b56964e-cdf3-4bb8-830c-0e2dad3f4ebd} RemoteWebNavigation.js process=main
+contract @mozilla.org/remote-web-navigation;1 {4b56964e-cdf3-4bb8-830c-0e2dad3f4ebd} process=main \ No newline at end of file
diff --git a/components/satchel/AutoCompletePopup.jsm b/components/satchel/AutoCompletePopup.jsm
new file mode 100644
index 000000000..7604e7bd5
--- /dev/null
+++ b/components/satchel/AutoCompletePopup.jsm
@@ -0,0 +1,317 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"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");
+
+// AutoCompleteResultView is an abstraction around a list of results
+// we got back up from browser-content.js. It implements enough of
+// nsIAutoCompleteController and nsIAutoCompleteInput to make the
+// richlistbox popup work.
+var AutoCompleteResultView = {
+ // nsISupports
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteController,
+ Ci.nsIAutoCompleteInput]),
+
+ // Private variables
+ results: [],
+
+ // nsIAutoCompleteController
+ get matchCount() {
+ return this.results.length;
+ },
+
+ getValueAt(index) {
+ return this.results[index].value;
+ },
+
+ getLabelAt(index) {
+ // Unused by richlist autocomplete - see getCommentAt.
+ return "";
+ },
+
+ getCommentAt(index) {
+ // The richlist autocomplete popup uses comment for its main
+ // display of an item, which is why we're returning the label
+ // here instead.
+ return this.results[index].label;
+ },
+
+ getStyleAt(index) {
+ return this.results[index].style;
+ },
+
+ getImageAt(index) {
+ return this.results[index].image;
+ },
+
+ handleEnter: function(aIsPopupSelection) {
+ AutoCompletePopup.handleEnter(aIsPopupSelection);
+ },
+
+ stopSearch: function() {},
+
+ searchString: "",
+
+ // nsIAutoCompleteInput
+ get controller() {
+ return this;
+ },
+
+ get popup() {
+ return null;
+ },
+
+ _focus() {
+ AutoCompletePopup.requestFocus();
+ },
+
+ // 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": {
+ AutoCompleteResultView.clearResults();
+ this.sendMessageToBrowser("FormAutoComplete:PopupClosed");
+ // adjustHeight clears the height from the popup so that
+ // we don't have a big shrink effect if we closed with a
+ // large list, and then open on a small one.
+ this.openedPopup.adjustHeight();
+ 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;
+
+ AutoCompleteResultView.setResults(results);
+ this.openedPopup.view = AutoCompleteResultView;
+ this.openedPopup.selectedIndex = -1;
+
+ if (results.length) {
+ // Reset fields that were set from the last time the search popup was open
+ this.openedPopup.mInput = AutoCompleteResultView;
+ 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);
+ this.openedPopup.invalidate();
+ } else {
+ this.closePopup();
+ }
+ },
+
+ invalidate(results) {
+ if (!this.openedPopup) {
+ return;
+ }
+
+ if (!results.length) {
+ this.closePopup();
+ } else {
+ AutoCompleteResultView.setResults(results);
+ 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();
+ }
+ },
+
+ 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": {
+ if (this.openedPopup) {
+ 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.
+ AutoCompleteResultView.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() {},
+
+ /**
+ * Sends a message to the browser requesting that the input
+ * that the AutoCompletePopup is open for be focused.
+ */
+ requestFocus: function() {
+ if (this.openedPopup) {
+ this.sendMessageToBrowser("FormAutoComplete:Focus");
+ }
+ },
+}
diff --git a/components/satchel/FormHistory.jsm b/components/satchel/FormHistory.jsm
new file mode 100644
index 000000000..2182fbfb7
--- /dev/null
+++ b/components/satchel/FormHistory.jsm
@@ -0,0 +1,1118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * FormHistory
+ *
+ * Used to store values that have been entered into forms which may later
+ * be used to automatically fill in the values when the form is visited again.
+ *
+ * search(terms, queryData, callback)
+ * Look up values that have been previously stored.
+ * terms - array of terms to return data for
+ * queryData - object that contains the query terms
+ * The query object contains properties for each search criteria to match, where the value
+ * of the property specifies the value that term must have. For example,
+ * { term1: value1, term2: value2 }
+ * callback - callback that is called when results are available or an error occurs.
+ * The callback is passed a result array containing each found entry. Each element in
+ * the array is an object containing a property for each search term specified by 'terms'.
+ * count(queryData, callback)
+ * Find the number of stored entries that match the given criteria.
+ * queryData - array of objects that indicate the query. See the search method for details.
+ * callback - callback that is called when results are available or an error occurs.
+ * The callback is passed the number of found entries.
+ * update(changes, callback)
+ * Write data to form history storage.
+ * changes - an array of changes to be made. If only one change is to be made, it
+ * may be passed as an object rather than a one-element array.
+ * Each change object is of the form:
+ * { op: operation, term1: value1, term2: value2, ... }
+ * Valid operations are:
+ * add - add a new entry
+ * update - update an existing entry
+ * remove - remove an entry
+ * bump - update the last accessed time on an entry
+ * The terms specified allow matching of one or more specific entries. If no terms
+ * are specified then all entries are matched. This means that { op: "remove" } is
+ * used to remove all entries and clear the form history.
+ * callback - callback that is called when results have been stored.
+ * getAutoCompeteResults(searchString, params, callback)
+ * Retrieve an array of form history values suitable for display in an autocomplete list.
+ * Returns an mozIStoragePendingStatement that can be used to cancel the operation if
+ * needed.
+ * searchString - the string to search for, typically the entered value of a textbox
+ * params - zero or more filter arguments:
+ * fieldname - form field name
+ * agedWeight
+ * bucketSize
+ * expiryDate
+ * maxTimeGroundings
+ * timeGroupingSize
+ * prefixWeight
+ * boundaryWeight
+ * callback - callback that is called with the array of results. Each result in the array
+ * is an object with four arguments:
+ * text, textLowerCase, frecency, totalScore
+ * schemaVersion
+ * This property holds the version of the database schema
+ *
+ * Terms:
+ * guid - entry identifier. For 'add', a guid will be generated.
+ * fieldname - form field name
+ * value - form value
+ * timesUsed - the number of times the entry has been accessed
+ * firstUsed - the time the the entry was first created
+ * lastUsed - the time the entry was last accessed
+ * firstUsedStart - search for entries created after or at this time
+ * firstUsedEnd - search for entries created before or at this time
+ * lastUsedStart - search for entries last accessed after or at this time
+ * lastUsedEnd - search for entries last accessed before or at this time
+ * newGuid - a special case valid only for 'update' and allows the guid for
+ * an existing record to be updated. The 'guid' term is the only
+ * other term which can be used (ie, you can not also specify a
+ * fieldname, value etc) and indicates the guid of the existing
+ * record that should be updated.
+ *
+ * In all of the above methods, the callback argument should be an object with
+ * handleResult(result), handleFailure(error) and handleCompletion(reason) functions.
+ * For search and getAutoCompeteResults, result is an object containing the desired
+ * properties. For count, result is the integer count. For, update, handleResult is
+ * not called. For handleCompletion, reason is either 0 if successful or 1 if
+ * an error occurred.
+ */
+
+this.EXPORTED_SYMBOLS = ["FormHistory"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "uuidService",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+const DB_SCHEMA_VERSION = 4;
+const DAY_IN_MS = 86400000; // 1 day in milliseconds
+const MAX_SEARCH_TOKENS = 10;
+const NOOP = function noop() {};
+
+var supportsDeletedTable = false;
+
+var Prefs = {
+ initialized: false,
+
+ get debug() { this.ensureInitialized(); return this._debug; },
+ get enabled() { this.ensureInitialized(); return this._enabled; },
+ get expireDays() { this.ensureInitialized(); return this._expireDays; },
+
+ ensureInitialized: function() {
+ if (this.initialized)
+ return;
+
+ this.initialized = true;
+
+ this._debug = Services.prefs.getBoolPref("browser.formfill.debug");
+ this._enabled = Services.prefs.getBoolPref("browser.formfill.enable");
+ this._expireDays = Services.prefs.getIntPref("browser.formfill.expire_days");
+ }
+};
+
+function log(aMessage) {
+ if (Prefs.debug) {
+ Services.console.logStringMessage("FormHistory: " + aMessage);
+ }
+}
+
+function sendNotification(aType, aData) {
+ if (typeof aData == "string") {
+ let strWrapper = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ strWrapper.data = aData;
+ aData = strWrapper;
+ }
+ else if (typeof aData == "number") {
+ let intWrapper = Cc["@mozilla.org/supports-PRInt64;1"].
+ createInstance(Ci.nsISupportsPRInt64);
+ intWrapper.data = aData;
+ aData = intWrapper;
+ }
+ else if (aData) {
+ throw Components.Exception("Invalid type " + (typeof aType) + " passed to sendNotification",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ Services.obs.notifyObservers(aData, "satchel-storage-changed", aType);
+}
+
+/**
+ * Current database schema
+ */
+
+const dbSchema = {
+ tables : {
+ moz_formhistory : {
+ "id" : "INTEGER PRIMARY KEY",
+ "fieldname" : "TEXT NOT NULL",
+ "value" : "TEXT NOT NULL",
+ "timesUsed" : "INTEGER",
+ "firstUsed" : "INTEGER",
+ "lastUsed" : "INTEGER",
+ "guid" : "TEXT",
+ },
+ moz_deleted_formhistory: {
+ "id" : "INTEGER PRIMARY KEY",
+ "timeDeleted" : "INTEGER",
+ "guid" : "TEXT"
+ }
+ },
+ indices : {
+ moz_formhistory_index : {
+ table : "moz_formhistory",
+ columns : [ "fieldname" ]
+ },
+ moz_formhistory_lastused_index : {
+ table : "moz_formhistory",
+ columns : [ "lastUsed" ]
+ },
+ moz_formhistory_guid_index : {
+ table : "moz_formhistory",
+ columns : [ "guid" ]
+ },
+ }
+};
+
+/**
+ * Validating and processing API querying data
+ */
+
+const validFields = [
+ "fieldname",
+ "value",
+ "timesUsed",
+ "firstUsed",
+ "lastUsed",
+ "guid",
+];
+
+const searchFilters = [
+ "firstUsedStart",
+ "firstUsedEnd",
+ "lastUsedStart",
+ "lastUsedEnd",
+];
+
+function validateOpData(aData, aDataType) {
+ let thisValidFields = validFields;
+ // A special case to update the GUID - in this case there can be a 'newGuid'
+ // field and of the normally valid fields, only 'guid' is accepted.
+ if (aDataType == "Update" && "newGuid" in aData) {
+ thisValidFields = ["guid", "newGuid"];
+ }
+ for (let field in aData) {
+ if (field != "op" && thisValidFields.indexOf(field) == -1) {
+ throw Components.Exception(
+ aDataType + " query contains an unrecognized field: " + field,
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+ }
+ return aData;
+}
+
+function validateSearchData(aData, aDataType) {
+ for (let field in aData) {
+ if (field != "op" && validFields.indexOf(field) == -1 && searchFilters.indexOf(field) == -1) {
+ throw Components.Exception(
+ aDataType + " query contains an unrecognized field: " + field,
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+ }
+}
+
+function makeQueryPredicates(aQueryData, delimiter = ' AND ') {
+ return Object.keys(aQueryData).map(function(field) {
+ if (field == "firstUsedStart") {
+ return "firstUsed >= :" + field;
+ } else if (field == "firstUsedEnd") {
+ return "firstUsed <= :" + field;
+ } else if (field == "lastUsedStart") {
+ return "lastUsed >= :" + field;
+ } else if (field == "lastUsedEnd") {
+ return "lastUsed <= :" + field;
+ }
+ return field + " = :" + field;
+ }).join(delimiter);
+}
+
+/**
+ * Storage statement creation and parameter binding
+ */
+
+function makeCountStatement(aSearchData) {
+ let query = "SELECT COUNT(*) AS numEntries FROM moz_formhistory";
+ let queryTerms = makeQueryPredicates(aSearchData);
+ if (queryTerms) {
+ query += " WHERE " + queryTerms;
+ }
+ return dbCreateAsyncStatement(query, aSearchData);
+}
+
+function makeSearchStatement(aSearchData, aSelectTerms) {
+ let query = "SELECT " + aSelectTerms.join(", ") + " FROM moz_formhistory";
+ let queryTerms = makeQueryPredicates(aSearchData);
+ if (queryTerms) {
+ query += " WHERE " + queryTerms;
+ }
+
+ return dbCreateAsyncStatement(query, aSearchData);
+}
+
+function makeAddStatement(aNewData, aNow, aBindingArrays) {
+ let query = "INSERT INTO moz_formhistory (fieldname, value, timesUsed, firstUsed, lastUsed, guid) " +
+ "VALUES (:fieldname, :value, :timesUsed, :firstUsed, :lastUsed, :guid)";
+
+ aNewData.timesUsed = aNewData.timesUsed || 1;
+ aNewData.firstUsed = aNewData.firstUsed || aNow;
+ aNewData.lastUsed = aNewData.lastUsed || aNow;
+ return dbCreateAsyncStatement(query, aNewData, aBindingArrays);
+}
+
+function makeBumpStatement(aGuid, aNow, aBindingArrays) {
+ let query = "UPDATE moz_formhistory SET timesUsed = timesUsed + 1, lastUsed = :lastUsed WHERE guid = :guid";
+ let queryParams = {
+ lastUsed : aNow,
+ guid : aGuid,
+ };
+
+ return dbCreateAsyncStatement(query, queryParams, aBindingArrays);
+}
+
+function makeRemoveStatement(aSearchData, aBindingArrays) {
+ let query = "DELETE FROM moz_formhistory";
+ let queryTerms = makeQueryPredicates(aSearchData);
+
+ if (queryTerms) {
+ log("removeEntries");
+ query += " WHERE " + queryTerms;
+ } else {
+ log("removeAllEntries");
+ // Not specifying any fields means we should remove all entries. We
+ // won't need to modify the query in this case.
+ }
+
+ return dbCreateAsyncStatement(query, aSearchData, aBindingArrays);
+}
+
+function makeUpdateStatement(aGuid, aNewData, aBindingArrays) {
+ let query = "UPDATE moz_formhistory SET ";
+ let queryTerms = makeQueryPredicates(aNewData, ', ');
+
+ if (!queryTerms) {
+ throw Components.Exception("Update query must define fields to modify.",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ query += queryTerms + " WHERE guid = :existing_guid";
+ aNewData["existing_guid"] = aGuid;
+
+ return dbCreateAsyncStatement(query, aNewData, aBindingArrays);
+}
+
+function makeMoveToDeletedStatement(aGuid, aNow, aData, aBindingArrays) {
+ if (supportsDeletedTable) {
+ let query = "INSERT INTO moz_deleted_formhistory (guid, timeDeleted)";
+ let queryTerms = makeQueryPredicates(aData);
+
+ if (aGuid) {
+ query += " VALUES (:guid, :timeDeleted)";
+ } else {
+ // TODO: Add these items to the deleted items table once we've sorted
+ // out the issues from bug 756701
+ if (!queryTerms)
+ return undefined;
+
+ query += " SELECT guid, :timeDeleted FROM moz_formhistory WHERE " + queryTerms;
+ }
+
+ aData.timeDeleted = aNow;
+
+ return dbCreateAsyncStatement(query, aData, aBindingArrays);
+ }
+
+ return null;
+}
+
+function generateGUID() {
+ // string like: "{f60d9eac-9421-4abc-8491-8e8322b063d4}"
+ let uuid = uuidService.generateUUID().toString();
+ let raw = ""; // A string with the low bytes set to random values
+ let bytes = 0;
+ for (let i = 1; bytes < 12 ; i+= 2) {
+ // Skip dashes
+ if (uuid[i] == "-")
+ i++;
+ let hexVal = parseInt(uuid[i] + uuid[i + 1], 16);
+ raw += String.fromCharCode(hexVal);
+ bytes++;
+ }
+ return btoa(raw);
+}
+
+/**
+ * Database creation and access
+ */
+
+var _dbConnection = null;
+XPCOMUtils.defineLazyGetter(this, "dbConnection", function() {
+ let dbFile;
+
+ try {
+ dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
+ dbFile.append("formhistory.sqlite");
+ log("Opening database at " + dbFile.path);
+
+ _dbConnection = Services.storage.openUnsharedDatabase(dbFile);
+ dbInit();
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_CORRUPTED)
+ throw e;
+ dbCleanup(dbFile);
+ _dbConnection = Services.storage.openUnsharedDatabase(dbFile);
+ dbInit();
+ }
+
+ return _dbConnection;
+});
+
+
+var dbStmts = new Map();
+
+/*
+ * dbCreateAsyncStatement
+ *
+ * Creates a statement, wraps it, and then does parameter replacement
+ */
+function dbCreateAsyncStatement(aQuery, aParams, aBindingArrays) {
+ if (!aQuery)
+ return null;
+
+ let stmt = dbStmts.get(aQuery);
+ if (!stmt) {
+ log("Creating new statement for query: " + aQuery);
+ stmt = dbConnection.createAsyncStatement(aQuery);
+ dbStmts.set(aQuery, stmt);
+ }
+
+ if (aBindingArrays) {
+ let bindingArray = aBindingArrays.get(stmt);
+ if (!bindingArray) {
+ // first time using a particular statement in update
+ bindingArray = stmt.newBindingParamsArray();
+ aBindingArrays.set(stmt, bindingArray);
+ }
+
+ if (aParams) {
+ let bindingParams = bindingArray.newBindingParams();
+ for (let field in aParams) {
+ bindingParams.bindByName(field, aParams[field]);
+ }
+ bindingArray.addParams(bindingParams);
+ }
+ } else if (aParams) {
+ for (let field in aParams) {
+ stmt.params[field] = aParams[field];
+ }
+ }
+
+ return stmt;
+}
+
+/**
+ * dbInit
+ *
+ * Attempts to initialize the database. This creates the file if it doesn't
+ * exist, performs any migrations, etc.
+ */
+function dbInit() {
+ log("Initializing Database");
+
+ if (!_dbConnection.tableExists("moz_formhistory")) {
+ dbCreate();
+ return;
+ }
+
+ // When FormHistory is released, we will no longer support the various schema versions prior to
+ // this release that nsIFormHistory2 once did.
+ let version = _dbConnection.schemaVersion;
+ if (version < 3) {
+ throw Components.Exception("DB version is unsupported.",
+ Cr.NS_ERROR_FILE_CORRUPTED);
+ } else if (version != DB_SCHEMA_VERSION) {
+ dbMigrate(version);
+ }
+}
+
+function dbCreate() {
+ log("Creating DB -- tables");
+ for (let name in dbSchema.tables) {
+ let table = dbSchema.tables[name];
+ let tSQL = Object.keys(table).map(col => [col, table[col]].join(" ")).join(", ");
+ log("Creating table " + name + " with " + tSQL);
+ _dbConnection.createTable(name, tSQL);
+ }
+
+ log("Creating DB -- indices");
+ for (let name in dbSchema.indices) {
+ let index = dbSchema.indices[name];
+ let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
+ "(" + index.columns.join(", ") + ")";
+ _dbConnection.executeSimpleSQL(statement);
+ }
+
+ _dbConnection.schemaVersion = DB_SCHEMA_VERSION;
+}
+
+function dbMigrate(oldVersion) {
+ log("Attempting to migrate from version " + oldVersion);
+
+ if (oldVersion > DB_SCHEMA_VERSION) {
+ log("Downgrading to version " + DB_SCHEMA_VERSION);
+ // User's DB is newer. Sanity check that our expected columns are
+ // present, and if so mark the lower version and merrily continue
+ // on. If the columns are borked, something is wrong so blow away
+ // the DB and start from scratch. [Future incompatible upgrades
+ // should switch to a different table or file.]
+
+ if (!dbAreExpectedColumnsPresent()) {
+ throw Components.Exception("DB is missing expected columns",
+ Cr.NS_ERROR_FILE_CORRUPTED);
+ }
+
+ // Change the stored version to the current version. If the user
+ // runs the newer code again, it will see the lower version number
+ // and re-upgrade (to fixup any entries the old code added).
+ _dbConnection.schemaVersion = DB_SCHEMA_VERSION;
+ return;
+ }
+
+ // Note that migration is currently performed synchronously.
+ _dbConnection.beginTransaction();
+
+ try {
+ for (let v = oldVersion + 1; v <= DB_SCHEMA_VERSION; v++) {
+ this.log("Upgrading to version " + v + "...");
+ Migrators["dbMigrateToVersion" + v]();
+ }
+ } catch (e) {
+ this.log("Migration failed: " + e);
+ this.dbConnection.rollbackTransaction();
+ throw e;
+ }
+
+ _dbConnection.schemaVersion = DB_SCHEMA_VERSION;
+ _dbConnection.commitTransaction();
+
+ log("DB migration completed.");
+}
+
+var Migrators = {
+ /*
+ * Updates the DB schema to v3 (bug 506402).
+ * Adds deleted form history table.
+ */
+ dbMigrateToVersion4: function dbMigrateToVersion4() {
+ if (!_dbConnection.tableExists("moz_deleted_formhistory")) {
+ let table = dbSchema.tables["moz_deleted_formhistory"];
+ let tSQL = Object.keys(table).map(col => [col, table[col]].join(" ")).join(", ");
+ _dbConnection.createTable("moz_deleted_formhistory", tSQL);
+ }
+ }
+};
+
+/**
+ * dbAreExpectedColumnsPresent
+ *
+ * Sanity check to ensure that the columns this version of the code expects
+ * are present in the DB we're using.
+ */
+function dbAreExpectedColumnsPresent() {
+ for (let name in dbSchema.tables) {
+ let table = dbSchema.tables[name];
+ let query = "SELECT " +
+ Object.keys(table).join(", ") +
+ " FROM " + name;
+ try {
+ let stmt = _dbConnection.createStatement(query);
+ // (no need to execute statement, if it compiled we're good)
+ stmt.finalize();
+ } catch (e) {
+ return false;
+ }
+ }
+
+ log("verified that expected columns are present in DB.");
+ return true;
+}
+
+/**
+ * dbCleanup
+ *
+ * Called when database creation fails. Finalizes database statements,
+ * closes the database connection, deletes the database file.
+ */
+function dbCleanup(dbFile) {
+ log("Cleaning up DB file - close & remove & backup");
+
+ // Create backup file
+ let backupFile = dbFile.leafName + ".corrupt";
+ Services.storage.backupDatabaseFile(dbFile, backupFile);
+
+ dbClose(false);
+ dbFile.remove(false);
+}
+
+function dbClose(aShutdown) {
+ log("dbClose(" + aShutdown + ")");
+
+ if (aShutdown) {
+ sendNotification("formhistory-shutdown", null);
+ }
+
+ // Connection may never have been created if say open failed but we still
+ // end up calling dbClose as part of the rest of dbCleanup.
+ if (!_dbConnection) {
+ return;
+ }
+
+ log("dbClose finalize statements");
+ for (let stmt of dbStmts.values()) {
+ stmt.finalize();
+ }
+
+ dbStmts = new Map();
+
+ let closed = false;
+ _dbConnection.asyncClose(() => closed = true);
+
+ if (!aShutdown) {
+ let thread = Services.tm.currentThread;
+ while (!closed) {
+ thread.processNextEvent(true);
+ }
+ }
+}
+
+/**
+ * updateFormHistoryWrite
+ *
+ * Constructs and executes database statements from a pre-processed list of
+ * inputted changes.
+ */
+function updateFormHistoryWrite(aChanges, aCallbacks) {
+ log("updateFormHistoryWrite " + aChanges.length);
+
+ // pass 'now' down so that every entry in the batch has the same timestamp
+ let now = Date.now() * 1000;
+
+ // for each change, we either create and append a new storage statement to
+ // stmts or bind a new set of parameters to an existing storage statement.
+ // stmts and bindingArrays are updated when makeXXXStatement eventually
+ // calls dbCreateAsyncStatement.
+ let stmts = [];
+ let notifications = [];
+ let bindingArrays = new Map();
+
+ for (let change of aChanges) {
+ let operation = change.op;
+ delete change.op;
+ let stmt;
+ switch (operation) {
+ case "remove":
+ log("Remove from form history " + change);
+ let delStmt = makeMoveToDeletedStatement(change.guid, now, change, bindingArrays);
+ if (delStmt && stmts.indexOf(delStmt) == -1)
+ stmts.push(delStmt);
+ if ("timeDeleted" in change)
+ delete change.timeDeleted;
+ stmt = makeRemoveStatement(change, bindingArrays);
+ notifications.push([ "formhistory-remove", change.guid ]);
+ break;
+ case "update":
+ log("Update form history " + change);
+ let guid = change.guid;
+ delete change.guid;
+ // a special case for updating the GUID - the new value can be
+ // specified in newGuid.
+ if (change.newGuid) {
+ change.guid = change.newGuid
+ delete change.newGuid;
+ }
+ stmt = makeUpdateStatement(guid, change, bindingArrays);
+ notifications.push([ "formhistory-update", guid ]);
+ break;
+ case "bump":
+ log("Bump form history " + change);
+ if (change.guid) {
+ stmt = makeBumpStatement(change.guid, now, bindingArrays);
+ notifications.push([ "formhistory-update", change.guid ]);
+ } else {
+ change.guid = generateGUID();
+ stmt = makeAddStatement(change, now, bindingArrays);
+ notifications.push([ "formhistory-add", change.guid ]);
+ }
+ break;
+ case "add":
+ log("Add to form history " + change);
+ change.guid = generateGUID();
+ stmt = makeAddStatement(change, now, bindingArrays);
+ notifications.push([ "formhistory-add", change.guid ]);
+ break;
+ default:
+ // We should've already guaranteed that change.op is one of the above
+ throw Components.Exception("Invalid operation " + operation,
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ // As identical statements are reused, only add statements if they aren't already present.
+ if (stmt && stmts.indexOf(stmt) == -1) {
+ stmts.push(stmt);
+ }
+ }
+
+ for (let stmt of stmts) {
+ stmt.bindParameters(bindingArrays.get(stmt));
+ }
+
+ let handlers = {
+ handleCompletion : function(aReason) {
+ if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ for (let [notification, param] of notifications) {
+ // We're either sending a GUID or nothing at all.
+ sendNotification(notification, param);
+ }
+ }
+
+ if (aCallbacks && aCallbacks.handleCompletion) {
+ aCallbacks.handleCompletion(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED ? 0 : 1);
+ }
+ },
+ handleError : function(aError) {
+ if (aCallbacks && aCallbacks.handleError) {
+ aCallbacks.handleError(aError);
+ }
+ },
+ handleResult : NOOP
+ };
+
+ dbConnection.executeAsync(stmts, stmts.length, handlers);
+}
+
+/**
+ * Functions that expire entries in form history and shrinks database
+ * afterwards as necessary initiated by expireOldEntries.
+ */
+
+/**
+ * expireOldEntriesDeletion
+ *
+ * Removes entries from database.
+ */
+function expireOldEntriesDeletion(aExpireTime, aBeginningCount) {
+ log("expireOldEntriesDeletion(" + aExpireTime + "," + aBeginningCount + ")");
+
+ FormHistory.update([
+ {
+ op: "remove",
+ lastUsedEnd : aExpireTime,
+ }], {
+ handleCompletion: function() {
+ expireOldEntriesVacuum(aExpireTime, aBeginningCount);
+ },
+ handleError: function(aError) {
+ log("expireOldEntriesDeletionFailure");
+ }
+ });
+}
+
+/**
+ * expireOldEntriesVacuum
+ *
+ * Counts number of entries removed and shrinks database as necessary.
+ */
+function expireOldEntriesVacuum(aExpireTime, aBeginningCount) {
+ FormHistory.count({}, {
+ handleResult: function(aEndingCount) {
+ if (aBeginningCount - aEndingCount > 500) {
+ log("expireOldEntriesVacuum");
+
+ let stmt = dbCreateAsyncStatement("VACUUM");
+ stmt.executeAsync({
+ handleResult : NOOP,
+ handleError : function(aError) {
+ log("expireVacuumError");
+ },
+ handleCompletion : NOOP
+ });
+ }
+
+ sendNotification("formhistory-expireoldentries", aExpireTime);
+ },
+ handleError: function(aError) {
+ log("expireEndCountFailure");
+ }
+ });
+}
+
+this.FormHistory = {
+ get enabled() {
+ return Prefs.enabled;
+ },
+
+ search : function formHistorySearch(aSelectTerms, aSearchData, aCallbacks) {
+ // if no terms selected, select everything
+ aSelectTerms = (aSelectTerms) ? aSelectTerms : validFields;
+ validateSearchData(aSearchData, "Search");
+
+ let stmt = makeSearchStatement(aSearchData, aSelectTerms);
+
+ let handlers = {
+ handleResult : function(aResultSet) {
+ for (let row = aResultSet.getNextRow(); row; row = aResultSet.getNextRow()) {
+ let result = {};
+ for (let field of aSelectTerms) {
+ result[field] = row.getResultByName(field);
+ }
+
+ if (aCallbacks && aCallbacks.handleResult) {
+ aCallbacks.handleResult(result);
+ }
+ }
+ },
+
+ handleError : function(aError) {
+ if (aCallbacks && aCallbacks.handleError) {
+ aCallbacks.handleError(aError);
+ }
+ },
+
+ handleCompletion : function searchCompletionHandler(aReason) {
+ if (aCallbacks && aCallbacks.handleCompletion) {
+ aCallbacks.handleCompletion(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED ? 0 : 1);
+ }
+ }
+ };
+
+ stmt.executeAsync(handlers);
+ },
+
+ count : function formHistoryCount(aSearchData, aCallbacks) {
+ validateSearchData(aSearchData, "Count");
+ let stmt = makeCountStatement(aSearchData);
+ let handlers = {
+ handleResult : function countResultHandler(aResultSet) {
+ let row = aResultSet.getNextRow();
+ let count = row.getResultByName("numEntries");
+ if (aCallbacks && aCallbacks.handleResult) {
+ aCallbacks.handleResult(count);
+ }
+ },
+
+ handleError : function(aError) {
+ if (aCallbacks && aCallbacks.handleError) {
+ aCallbacks.handleError(aError);
+ }
+ },
+
+ handleCompletion : function searchCompletionHandler(aReason) {
+ if (aCallbacks && aCallbacks.handleCompletion) {
+ aCallbacks.handleCompletion(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED ? 0 : 1);
+ }
+ }
+ };
+
+ stmt.executeAsync(handlers);
+ },
+
+ update : function formHistoryUpdate(aChanges, aCallbacks) {
+ // Used to keep track of how many searches have been started. When that number
+ // are finished, updateFormHistoryWrite can be called.
+ let numSearches = 0;
+ let completedSearches = 0;
+ let searchFailed = false;
+
+ function validIdentifier(change) {
+ // The identifier is only valid if one of either the guid or the (fieldname/value) are set
+ return Boolean(change.guid) != Boolean(change.fieldname && change.value);
+ }
+
+ if (!("length" in aChanges))
+ aChanges = [aChanges];
+
+ let isRemoveOperation = aChanges.every(change => change && change.op && change.op == "remove");
+ if (!Prefs.enabled && !isRemoveOperation) {
+ if (aCallbacks && aCallbacks.handleError) {
+ aCallbacks.handleError({
+ message: "Form history is disabled, only remove operations are allowed",
+ result: Ci.mozIStorageError.MISUSE
+ });
+ }
+ if (aCallbacks && aCallbacks.handleCompletion) {
+ aCallbacks.handleCompletion(1);
+ }
+ return;
+ }
+
+ for (let change of aChanges) {
+ switch (change.op) {
+ case "remove":
+ validateSearchData(change, "Remove");
+ continue;
+ case "update":
+ if (validIdentifier(change)) {
+ validateOpData(change, "Update");
+ if (change.guid) {
+ continue;
+ }
+ } else {
+ throw Components.Exception(
+ "update op='update' does not correctly reference a entry.",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+ break;
+ case "bump":
+ if (validIdentifier(change)) {
+ validateOpData(change, "Bump");
+ if (change.guid) {
+ continue;
+ }
+ } else {
+ throw Components.Exception(
+ "update op='bump' does not correctly reference a entry.",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+ break;
+ case "add":
+ if (change.guid) {
+ throw Components.Exception(
+ "op='add' cannot contain field 'guid'. Either use op='update' " +
+ "explicitly or make 'guid' undefined.",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ } else if (change.fieldname && change.value) {
+ validateOpData(change, "Add");
+ }
+ break;
+ default:
+ throw Components.Exception(
+ "update does not recognize op='" + change.op + "'",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ numSearches++;
+ let changeToUpdate = change;
+ FormHistory.search(
+ [ "guid" ],
+ {
+ fieldname : change.fieldname,
+ value : change.value
+ }, {
+ foundResult : false,
+ handleResult : function(aResult) {
+ if (this.foundResult) {
+ log("Database contains multiple entries with the same fieldname/value pair.");
+ if (aCallbacks && aCallbacks.handleError) {
+ aCallbacks.handleError({
+ message :
+ "Database contains multiple entries with the same fieldname/value pair.",
+ result : 19 // Constraint violation
+ });
+ }
+
+ searchFailed = true;
+ return;
+ }
+
+ this.foundResult = true;
+ changeToUpdate.guid = aResult["guid"];
+ },
+
+ handleError : function(aError) {
+ if (aCallbacks && aCallbacks.handleError) {
+ aCallbacks.handleError(aError);
+ }
+ },
+
+ handleCompletion : function(aReason) {
+ completedSearches++;
+ if (completedSearches == numSearches) {
+ if (!aReason && !searchFailed) {
+ updateFormHistoryWrite(aChanges, aCallbacks);
+ }
+ else if (aCallbacks && aCallbacks.handleCompletion) {
+ aCallbacks.handleCompletion(1);
+ }
+ }
+ }
+ });
+ }
+
+ if (numSearches == 0) {
+ // We don't have to wait for any statements to return.
+ updateFormHistoryWrite(aChanges, aCallbacks);
+ }
+ },
+
+ getAutoCompleteResults: function getAutoCompleteResults(searchString, params, aCallbacks) {
+ // only do substring matching when the search string contains more than one character
+ let searchTokens;
+ let where = ""
+ let boundaryCalc = "";
+ if (searchString.length > 1) {
+ searchTokens = searchString.split(/\s+/);
+
+ // build up the word boundary and prefix match bonus calculation
+ boundaryCalc = "MAX(1, :prefixWeight * (value LIKE :valuePrefix ESCAPE '/') + (";
+ // for each word, calculate word boundary weights for the SELECT clause and
+ // add word to the WHERE clause of the query
+ let tokenCalc = [];
+ let searchTokenCount = Math.min(searchTokens.length, MAX_SEARCH_TOKENS);
+ for (let i = 0; i < searchTokenCount; i++) {
+ tokenCalc.push("(value LIKE :tokenBegin" + i + " ESCAPE '/') + " +
+ "(value LIKE :tokenBoundary" + i + " ESCAPE '/')");
+ where += "AND (value LIKE :tokenContains" + i + " ESCAPE '/') ";
+ }
+ // add more weight if we have a traditional prefix match and
+ // multiply boundary bonuses by boundary weight
+ boundaryCalc += tokenCalc.join(" + ") + ") * :boundaryWeight)";
+ } else if (searchString.length == 1) {
+ where = "AND (value LIKE :valuePrefix ESCAPE '/') ";
+ boundaryCalc = "1";
+ delete params.prefixWeight;
+ delete params.boundaryWeight;
+ } else {
+ where = "";
+ boundaryCalc = "1";
+ delete params.prefixWeight;
+ delete params.boundaryWeight;
+ }
+
+ params.now = Date.now() * 1000; // convert from ms to microseconds
+
+ /* Three factors in the frecency calculation for an entry (in order of use in calculation):
+ * 1) average number of times used - items used more are ranked higher
+ * 2) how recently it was last used - items used recently are ranked higher
+ * 3) additional weight for aged entries surviving expiry - these entries are relevant
+ * since they have been used multiple times over a large time span so rank them higher
+ * The score is then divided by the bucket size and we round the result so that entries
+ * with a very similar frecency are bucketed together with an alphabetical sort. This is
+ * to reduce the amount of moving around by entries while typing.
+ */
+
+ let query = "/* do not warn (bug 496471): can't use an index */ " +
+ "SELECT value, " +
+ "ROUND( " +
+ "timesUsed / MAX(1.0, (lastUsed - firstUsed) / :timeGroupingSize) * " +
+ "MAX(1.0, :maxTimeGroupings - (:now - lastUsed) / :timeGroupingSize) * "+
+ "MAX(1.0, :agedWeight * (firstUsed < :expiryDate)) / " +
+ ":bucketSize "+
+ ", 3) AS frecency, " +
+ boundaryCalc + " AS boundaryBonuses " +
+ "FROM moz_formhistory " +
+ "WHERE fieldname=:fieldname " + where +
+ "ORDER BY ROUND(frecency * boundaryBonuses) DESC, UPPER(value) ASC";
+
+ let stmt = dbCreateAsyncStatement(query, params);
+
+ // Chicken and egg problem: Need the statement to escape the params we
+ // pass to the function that gives us the statement. So, fix it up now.
+ if (searchString.length >= 1)
+ stmt.params.valuePrefix = stmt.escapeStringForLIKE(searchString, "/") + "%";
+ if (searchString.length > 1) {
+ let searchTokenCount = Math.min(searchTokens.length, MAX_SEARCH_TOKENS);
+ for (let i = 0; i < searchTokenCount; i++) {
+ let escapedToken = stmt.escapeStringForLIKE(searchTokens[i], "/");
+ stmt.params["tokenBegin" + i] = escapedToken + "%";
+ stmt.params["tokenBoundary" + i] = "% " + escapedToken + "%";
+ stmt.params["tokenContains" + i] = "%" + escapedToken + "%";
+ }
+ } else {
+ // no additional params need to be substituted into the query when the
+ // length is zero or one
+ }
+
+ let pending = stmt.executeAsync({
+ handleResult : function (aResultSet) {
+ for (let row = aResultSet.getNextRow(); row; row = aResultSet.getNextRow()) {
+ let value = row.getResultByName("value");
+ let frecency = row.getResultByName("frecency");
+ let entry = {
+ text : value,
+ textLowerCase : value.toLowerCase(),
+ frecency : frecency,
+ totalScore : Math.round(frecency * row.getResultByName("boundaryBonuses"))
+ };
+ if (aCallbacks && aCallbacks.handleResult) {
+ aCallbacks.handleResult(entry);
+ }
+ }
+ },
+
+ handleError : function (aError) {
+ if (aCallbacks && aCallbacks.handleError) {
+ aCallbacks.handleError(aError);
+ }
+ },
+
+ handleCompletion : function (aReason) {
+ if (aCallbacks && aCallbacks.handleCompletion) {
+ aCallbacks.handleCompletion(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED ? 0 : 1);
+ }
+ }
+ });
+ return pending;
+ },
+
+ get schemaVersion() {
+ return dbConnection.schemaVersion;
+ },
+
+ // This is used only so that the test can verify deleted table support.
+ get _supportsDeletedTable() {
+ return supportsDeletedTable;
+ },
+ set _supportsDeletedTable(val) {
+ supportsDeletedTable = val;
+ },
+
+ // The remaining methods are called by FormHistoryStartup.js
+ updatePrefs: function updatePrefs() {
+ Prefs.initialized = false;
+ },
+
+ expireOldEntries: function expireOldEntries() {
+ log("expireOldEntries");
+
+ // Determine how many days of history we're supposed to keep.
+ // Calculate expireTime in microseconds
+ let expireTime = (Date.now() - Prefs.expireDays * DAY_IN_MS) * 1000;
+
+ sendNotification("formhistory-beforeexpireoldentries", expireTime);
+
+ FormHistory.count({}, {
+ handleResult: function(aBeginningCount) {
+ expireOldEntriesDeletion(expireTime, aBeginningCount);
+ },
+ handleError: function(aError) {
+ log("expireStartCountFailure");
+ }
+ });
+ },
+
+ shutdown: function shutdown() { dbClose(true); }
+};
+
+// Prevent add-ons from redefining this API
+Object.freeze(FormHistory);
diff --git a/components/satchel/FormHistoryStartup.js b/components/satchel/FormHistoryStartup.js
new file mode 100644
index 000000000..05b654560
--- /dev/null
+++ b/components/satchel/FormHistoryStartup.js
@@ -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/. */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+
+function FormHistoryStartup() { }
+
+FormHistoryStartup.prototype = {
+ classID: Components.ID("{3A0012EB-007F-4BB8-AA81-A07385F77A25}"),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIFrameMessageListener
+ ]),
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ FormHistory.updatePrefs();
+ break;
+ case "idle-daily":
+ case "formhistory-expire-now":
+ FormHistory.expireOldEntries();
+ break;
+ case "profile-before-change":
+ FormHistory.shutdown();
+ break;
+ case "profile-after-change":
+ this.init();
+ default:
+ break;
+ }
+ },
+
+ inited: false,
+ pendingQuery: null,
+
+ init: function()
+ {
+ if (this.inited)
+ return;
+ this.inited = true;
+
+ Services.prefs.addObserver("browser.formfill.", this, true);
+
+ // triggers needed service cleanup and db shutdown
+ Services.obs.addObserver(this, "profile-before-change", true);
+ Services.obs.addObserver(this, "formhistory-expire-now", true);
+
+ let messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
+ getService(Ci.nsIMessageListenerManager);
+ messageManager.loadFrameScript("chrome://satchel/content/formSubmitListener.js", true);
+ messageManager.addMessageListener("FormHistory:FormSubmitEntries", this);
+
+ // For each of these messages, we could receive them from content,
+ // or we might receive them from the ppmm if the searchbar is
+ // having its history queried.
+ for (let manager of [messageManager, Services.ppmm]) {
+ manager.addMessageListener("FormHistory:AutoCompleteSearchAsync", this);
+ manager.addMessageListener("FormHistory:RemoveEntry", this);
+ }
+ },
+
+ receiveMessage: function(message) {
+ switch (message.name) {
+ case "FormHistory:FormSubmitEntries": {
+ let entries = message.data;
+ let changes = entries.map(function(entry) {
+ return {
+ op : "bump",
+ fieldname : entry.name,
+ value : entry.value,
+ }
+ });
+
+ FormHistory.update(changes);
+ break;
+ }
+
+ case "FormHistory:AutoCompleteSearchAsync": {
+ let { id, searchString, params } = message.data;
+
+ if (this.pendingQuery) {
+ this.pendingQuery.cancel();
+ this.pendingQuery = null;
+ }
+
+ let mm;
+ if (message.target instanceof Ci.nsIMessageListenerManager) {
+ // The target is the PPMM, meaning that the parent process
+ // is requesting FormHistory data on the searchbar.
+ mm = message.target;
+ } else {
+ // Otherwise, the target is a <xul:browser>.
+ mm = message.target.messageManager;
+ }
+
+ let results = [];
+ let processResults = {
+ handleResult: aResult => {
+ results.push(aResult);
+ },
+ handleCompletion: aReason => {
+ // Check that the current query is still the one we created. Our
+ // query might have been canceled shortly before completing, in
+ // that case we don't want to call the callback anymore.
+ if (query == this.pendingQuery) {
+ this.pendingQuery = null;
+ if (!aReason) {
+ mm.sendAsyncMessage("FormHistory:AutoCompleteSearchResults",
+ { id, results });
+ }
+ }
+ }
+ };
+
+ let query = FormHistory.getAutoCompleteResults(searchString, params,
+ processResults);
+ this.pendingQuery = query;
+ break;
+ }
+
+ case "FormHistory:RemoveEntry": {
+ let { inputName, value } = message.data;
+ FormHistory.update({
+ op: "remove",
+ fieldname: inputName,
+ value,
+ });
+ break;
+ }
+
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FormHistoryStartup]);
diff --git a/components/satchel/formSubmitListener.js b/components/satchel/formSubmitListener.js
new file mode 100644
index 000000000..ec2c18f6c
--- /dev/null
+++ b/components/satchel/formSubmitListener.js
@@ -0,0 +1,190 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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() {
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+var satchelFormListener = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver,
+ Ci.nsIDOMEventListener,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ debug : true,
+ enabled : true,
+ saveHttpsForms : true,
+
+ init : function() {
+ Services.obs.addObserver(this, "earlyformsubmit", false);
+ Services.prefs.addObserver("browser.formfill.", this, false);
+ this.updatePrefs();
+ addEventListener("unload", this, false);
+ },
+
+ updatePrefs : function () {
+ this.debug = Services.prefs.getBoolPref("browser.formfill.debug");
+ this.enabled = Services.prefs.getBoolPref("browser.formfill.enable");
+ this.saveHttpsForms = Services.prefs.getBoolPref("browser.formfill.saveHttpsForms");
+ },
+
+ // Implements the Luhn checksum algorithm as described at
+ // http://wikipedia.org/wiki/Luhn_algorithm
+ isValidCCNumber : function (ccNumber) {
+ // Remove dashes and whitespace
+ ccNumber = ccNumber.replace(/[\-\s]/g, '');
+
+ let len = ccNumber.length;
+ if (len != 9 && len != 15 && len != 16)
+ return false;
+
+ if (!/^\d+$/.test(ccNumber))
+ return false;
+
+ let total = 0;
+ for (let i = 0; i < len; i++) {
+ let ch = parseInt(ccNumber[len - i - 1]);
+ if (i % 2 == 1) {
+ // Double it, add digits together if > 10
+ ch *= 2;
+ if (ch > 9)
+ ch -= 9;
+ }
+ total += ch;
+ }
+ return total % 10 == 0;
+ },
+
+ log : function (message) {
+ if (!this.debug)
+ return;
+ dump("satchelFormListener: " + message + "\n");
+ Services.console.logStringMessage("satchelFormListener: " + message);
+ },
+
+ /* ---- dom event handler ---- */
+
+ handleEvent: function(e) {
+ switch (e.type) {
+ case "unload":
+ Services.obs.removeObserver(this, "earlyformsubmit");
+ Services.prefs.removeObserver("browser.formfill.", this);
+ break;
+
+ default:
+ this.log("Oops! Unexpected event: " + e.type);
+ break;
+ }
+ },
+
+ /* ---- nsIObserver interface ---- */
+
+ observe : function (subject, topic, data) {
+ if (topic == "nsPref:changed")
+ this.updatePrefs();
+ else
+ this.log("Oops! Unexpected notification: " + topic);
+ },
+
+ /* ---- nsIFormSubmitObserver interfaces ---- */
+
+ notify : function(form, domWin, actionURI, cancelSubmit) {
+ try {
+ // Even though the global context is for a specific browser, we
+ // can receive observer events from other tabs! Ensure this event
+ // is about our content.
+ if (domWin.top != content)
+ return;
+ if (!this.enabled)
+ return;
+
+ if (PrivateBrowsingUtils.isContentWindowPrivate(domWin))
+ return;
+
+ this.log("Form submit observer notified.");
+
+ if (!this.saveHttpsForms) {
+ if (actionURI.schemeIs("https"))
+ return;
+ if (form.ownerDocument.documentURIObject.schemeIs("https"))
+ return;
+ }
+
+ if (form.hasAttribute("autocomplete") &&
+ form.getAttribute("autocomplete").toLowerCase() == "off")
+ return;
+
+ let entries = [];
+ for (let i = 0; i < form.elements.length; i++) {
+ let input = form.elements[i];
+ if (!(input instanceof Ci.nsIDOMHTMLInputElement))
+ continue;
+
+ // Only use inputs that hold text values (not including type="password")
+ if (!input.mozIsTextField(true))
+ continue;
+
+ // Bug 394612: If Login Manager marked this input, don't save it.
+ // The login manager will deal with remembering it.
+
+ // Don't save values when autocomplete=off is present.
+ if (input.hasAttribute("autocomplete") &&
+ input.getAttribute("autocomplete").toLowerCase() == "off")
+ continue;
+
+ let value = input.value.trim();
+
+ // Don't save empty or unchanged values.
+ if (!value || value == input.defaultValue.trim())
+ continue;
+
+ // Don't save credit card numbers.
+ if (this.isValidCCNumber(value)) {
+ this.log("skipping saving a credit card number");
+ continue;
+ }
+
+ let name = input.name || input.id;
+ if (!name)
+ continue;
+
+ if (name == 'searchbar-history') {
+ this.log('addEntry for input name "' + name + '" is denied')
+ continue;
+ }
+
+ // Limit stored data to 200 characters.
+ if (name.length > 200 || value.length > 200) {
+ this.log("skipping input that has a name/value too large");
+ continue;
+ }
+
+ // Limit number of fields stored per form.
+ if (entries.length >= 100) {
+ this.log("not saving any more entries for this form.");
+ break;
+ }
+
+ entries.push({ name: name, value: value });
+ }
+
+ if (entries.length) {
+ this.log("sending entries to parent process for form " + form.id);
+ sendAsyncMessage("FormHistory:FormSubmitEntries", entries);
+ }
+ }
+ catch (e) {
+ this.log("notify failed: " + e);
+ }
+ }
+};
+
+satchelFormListener.init();
+
+})();
diff --git a/components/satchel/jar.mn b/components/satchel/jar.mn
new file mode 100644
index 000000000..4b37d5dc5
--- /dev/null
+++ b/components/satchel/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/.
+
+toolkit.jar:
+% content satchel %content/satchel/
+ content/satchel/formSubmitListener.js
diff --git a/components/satchel/moz.build b/components/satchel/moz.build
new file mode 100644
index 000000000..cff8b594a
--- /dev/null
+++ b/components/satchel/moz.build
@@ -0,0 +1,35 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'nsIFormAutoComplete.idl',
+ 'nsIFormFillController.idl',
+ 'nsIFormHistory.idl',
+ 'nsIInputListAutoComplete.idl',
+]
+
+XPIDL_MODULE = 'satchel'
+
+SOURCES += ['nsFormFillController.cpp']
+
+LOCAL_INCLUDES += ['../build']
+
+EXTRA_COMPONENTS += [
+ 'FormHistoryStartup.js',
+ 'nsFormAutoComplete.js',
+ 'nsFormHistory.js',
+ 'nsInputListAutoComplete.js',
+ 'satchel.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'AutoCompletePopup.jsm',
+ 'FormHistory.jsm',
+ 'nsFormAutoCompleteResult.jsm',
+]
+
+FINAL_LIBRARY = 'xul'
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/satchel/nsFormAutoComplete.js b/components/satchel/nsFormAutoComplete.js
new file mode 100644
index 000000000..41f7425bb
--- /dev/null
+++ b/components/satchel/nsFormAutoComplete.js
@@ -0,0 +1,623 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+
+function isAutocompleteDisabled(aField) {
+ if (aField.autocomplete !== "") {
+ return aField.autocomplete === "off";
+ }
+
+ return aField.form && aField.form.autocomplete === "off";
+}
+
+/**
+ * An abstraction to talk with the FormHistory database over
+ * the message layer. FormHistoryClient will take care of
+ * figuring out the most appropriate message manager to use,
+ * and what things to send.
+ *
+ * It is assumed that nsFormAutoComplete will only ever use
+ * one instance at a time, and will not attempt to perform more
+ * than one search request with the same instance at a time.
+ * However, nsFormAutoComplete might call remove() any number of
+ * times with the same instance of the client.
+ *
+ * @param Object with the following properties:
+ *
+ * formField (DOM node):
+ * A DOM node that we're requesting form history for.
+ *
+ * inputName (string):
+ * The name of the input to do the FormHistory look-up
+ * with. If this is searchbar-history, then formField
+ * needs to be null, otherwise constructing will throw.
+ */
+function FormHistoryClient({ formField, inputName }) {
+ if (formField && inputName != this.SEARCHBAR_ID) {
+ let window = formField.ownerDocument.defaultView;
+ let topDocShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .sameTypeRootTreeItem
+ .QueryInterface(Ci.nsIDocShell);
+ this.mm = topDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+ } else {
+ if (inputName == this.SEARCHBAR_ID) {
+ if (formField) {
+ throw new Error("FormHistoryClient constructed with both a " +
+ "formField and an inputName. This is not " +
+ "supported, and only empty results will be " +
+ "returned.");
+ }
+ }
+ this.mm = Services.cpmm;
+ }
+
+ this.inputName = inputName;
+ this.id = FormHistoryClient.nextRequestID++;
+}
+
+FormHistoryClient.prototype = {
+ SEARCHBAR_ID: "searchbar-history",
+
+ // It is assumed that nsFormAutoComplete only uses / cares about
+ // one FormHistoryClient at a time, and won't attempt to have
+ // multiple in-flight searches occurring with the same FormHistoryClient.
+ // We use an ID number per instantiated FormHistoryClient to make
+ // sure we only respond to messages that were meant for us.
+ id: 0,
+ callback: null,
+ inputName: "",
+ mm: null,
+
+ /**
+ * Query FormHistory for some results.
+ *
+ * @param searchString (string)
+ * The string to search FormHistory for. See
+ * FormHistory.getAutoCompleteResults.
+ * @param params (object)
+ * An Object with search properties. See
+ * FormHistory.getAutoCompleteResults.
+ * @param callback
+ * A callback function that will take a single
+ * argument (the found entries).
+ */
+ requestAutoCompleteResults(searchString, params, callback) {
+ this.mm.sendAsyncMessage("FormHistory:AutoCompleteSearchAsync", {
+ id: this.id,
+ searchString,
+ params,
+ });
+
+ this.mm.addMessageListener("FormHistory:AutoCompleteSearchResults",
+ this);
+ this.callback = callback;
+ },
+
+ /**
+ * Cancel an in-flight results request. This ensures that the
+ * callback that requestAutoCompleteResults was passed is never
+ * called from this FormHistoryClient.
+ */
+ cancel() {
+ this.clearListeners();
+ },
+
+ /**
+ * Remove an item from FormHistory.
+ *
+ * @param value (string)
+ *
+ * The value to remove for this particular
+ * field.
+ */
+ remove(value) {
+ this.mm.sendAsyncMessage("FormHistory:RemoveEntry", {
+ inputName: this.inputName,
+ value,
+ });
+ },
+
+ // Private methods
+
+ receiveMessage(msg) {
+ let { id, results } = msg.data;
+ if (id != this.id) {
+ return;
+ }
+ if (!this.callback) {
+ Cu.reportError("FormHistoryClient received message with no " +
+ "callback");
+ return;
+ }
+ this.callback(results);
+ this.clearListeners();
+ },
+
+ clearListeners() {
+ this.mm.removeMessageListener("FormHistory:AutoCompleteSearchResults",
+ this);
+ this.callback = null;
+ },
+};
+
+FormHistoryClient.nextRequestID = 1;
+
+
+function FormAutoComplete() {
+ this.init();
+}
+
+/**
+ * FormAutoComplete
+ *
+ * Implements the nsIFormAutoComplete interface in the main process.
+ */
+FormAutoComplete.prototype = {
+ classID : Components.ID("{c11c21b2-71c9-4f87-a0f8-5e13f50495fd}"),
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormAutoComplete, Ci.nsISupportsWeakReference]),
+
+ _prefBranch : null,
+ _debug : true, // mirrors browser.formfill.debug
+ _enabled : true, // mirrors browser.formfill.enable preference
+ _agedWeight : 2,
+ _bucketSize : 1,
+ _maxTimeGroupings : 25,
+ _timeGroupingSize : 7 * 24 * 60 * 60 * 1000 * 1000,
+ _expireDays : null,
+ _boundaryWeight : 25,
+ _prefixWeight : 5,
+
+ // Only one query via FormHistoryClient is performed at a time, and the
+ // most recent FormHistoryClient which will be stored in _pendingClient
+ // while the query is being performed. It will be cleared when the query
+ // finishes, is cancelled, or an error occurs. If a new query occurs while
+ // one is already pending, the existing one is cancelled.
+ _pendingClient : null,
+
+ init : function() {
+ // Preferences. Add observer so we get notified of changes.
+ this._prefBranch = Services.prefs.getBranch("browser.formfill.");
+ this._prefBranch.addObserver("", this.observer, true);
+ this.observer._self = this;
+
+ this._debug = this._prefBranch.getBoolPref("debug");
+ this._enabled = this._prefBranch.getBoolPref("enable");
+ this._agedWeight = this._prefBranch.getIntPref("agedWeight");
+ this._bucketSize = this._prefBranch.getIntPref("bucketSize");
+ this._maxTimeGroupings = this._prefBranch.getIntPref("maxTimeGroupings");
+ this._timeGroupingSize = this._prefBranch.getIntPref("timeGroupingSize") * 1000 * 1000;
+ this._expireDays = this._prefBranch.getIntPref("expire_days");
+ },
+
+ observer : {
+ _self : null,
+
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ observe : function (subject, topic, data) {
+ let self = this._self;
+ if (topic == "nsPref:changed") {
+ let prefName = data;
+ self.log("got change to " + prefName + " preference");
+
+ switch (prefName) {
+ case "agedWeight":
+ self._agedWeight = self._prefBranch.getIntPref(prefName);
+ break;
+ case "debug":
+ self._debug = self._prefBranch.getBoolPref(prefName);
+ break;
+ case "enable":
+ self._enabled = self._prefBranch.getBoolPref(prefName);
+ break;
+ case "maxTimeGroupings":
+ self._maxTimeGroupings = self._prefBranch.getIntPref(prefName);
+ break;
+ case "timeGroupingSize":
+ self._timeGroupingSize = self._prefBranch.getIntPref(prefName) * 1000 * 1000;
+ break;
+ case "bucketSize":
+ self._bucketSize = self._prefBranch.getIntPref(prefName);
+ break;
+ case "boundaryWeight":
+ self._boundaryWeight = self._prefBranch.getIntPref(prefName);
+ break;
+ case "prefixWeight":
+ self._prefixWeight = self._prefBranch.getIntPref(prefName);
+ break;
+ default:
+ self.log("Oops! Pref not handled, change ignored.");
+ }
+ }
+ }
+ },
+
+ // AutoCompleteE10S needs to be able to call autoCompleteSearchAsync without
+ // going through IDL in order to pass a mock DOM object field.
+ get wrappedJSObject() {
+ return this;
+ },
+
+ /*
+ * log
+ *
+ * Internal function for logging debug messages to the Error Console
+ * window
+ */
+ log : function (message) {
+ if (!this._debug)
+ return;
+ dump("FormAutoComplete: " + message + "\n");
+ Services.console.logStringMessage("FormAutoComplete: " + message);
+ },
+
+ /*
+ * autoCompleteSearchAsync
+ *
+ * aInputName -- |name| attribute from the form input being autocompleted.
+ * aUntrimmedSearchString -- current value of the input
+ * aField -- nsIDOMHTMLInputElement being autocompleted (may be null if from chrome)
+ * aPreviousResult -- previous search result, if any.
+ * aDatalistResult -- results from list=datalist for aField.
+ * aListener -- nsIFormAutoCompleteObserver that listens for the nsIAutoCompleteResult
+ * that may be returned asynchronously.
+ */
+ autoCompleteSearchAsync : function (aInputName,
+ aUntrimmedSearchString,
+ aField,
+ aPreviousResult,
+ aDatalistResult,
+ aListener) {
+ function sortBytotalScore (a, b) {
+ return b.totalScore - a.totalScore;
+ }
+
+ // Guard against void DOM strings filtering into this code.
+ if (typeof aInputName === "object") {
+ aInputName = "";
+ }
+ if (typeof aUntrimmedSearchString === "object") {
+ aUntrimmedSearchString = "";
+ }
+
+ let client = new FormHistoryClient({ formField: aField, inputName: aInputName });
+
+ // If we have datalist results, they become our "empty" result.
+ let emptyResult = aDatalistResult ||
+ new FormAutoCompleteResult(client, [],
+ aInputName,
+ aUntrimmedSearchString,
+ null);
+ if (!this._enabled) {
+ if (aListener) {
+ aListener.onSearchCompletion(emptyResult);
+ }
+ return;
+ }
+
+ // don't allow form inputs (aField != null) to get results from search bar history
+ if (aInputName == 'searchbar-history' && aField) {
+ this.log('autoCompleteSearch for input name "' + aInputName + '" is denied');
+ if (aListener) {
+ aListener.onSearchCompletion(emptyResult);
+ }
+ return;
+ }
+
+ if (aField && isAutocompleteDisabled(aField)) {
+ this.log('autoCompleteSearch not allowed due to autcomplete=off');
+ if (aListener) {
+ aListener.onSearchCompletion(emptyResult);
+ }
+ return;
+ }
+
+ this.log("AutoCompleteSearch invoked. Search is: " + aUntrimmedSearchString);
+ let searchString = aUntrimmedSearchString.trim().toLowerCase();
+
+ // reuse previous results if:
+ // a) length greater than one character (others searches are special cases) AND
+ // b) the the new results will be a subset of the previous results
+ if (aPreviousResult && aPreviousResult.searchString.trim().length > 1 &&
+ searchString.indexOf(aPreviousResult.searchString.trim().toLowerCase()) >= 0) {
+ this.log("Using previous autocomplete result");
+ let result = aPreviousResult;
+ let wrappedResult = result.wrappedJSObject;
+ wrappedResult.searchString = aUntrimmedSearchString;
+
+ // Leaky abstraction alert: it would be great to be able to split
+ // this code between nsInputListAutoComplete and here but because of
+ // the way we abuse the formfill autocomplete API in e10s, we have
+ // to deal with the <datalist> results here as well (and down below
+ // in mergeResults).
+ // If there were datalist results result is a FormAutoCompleteResult
+ // as defined in nsFormAutoCompleteResult.jsm with the entire list
+ // of results in wrappedResult._values and only the results from
+ // form history in wrappedResult.entries.
+ // First, grab the entire list of old results.
+ let allResults = wrappedResult._labels;
+ let datalistResults, datalistLabels;
+ if (allResults) {
+ // We have datalist results, extract them from the values array.
+ // Both allResults and values arrays are in the form of:
+ // |--wR.entries--|
+ // <history entries><datalist entries>
+ let oldLabels = allResults.slice(wrappedResult.entries.length);
+ let oldValues = wrappedResult._values.slice(wrappedResult.entries.length);
+
+ datalistLabels = [];
+ datalistResults = [];
+ for (let i = 0; i < oldLabels.length; ++i) {
+ if (oldLabels[i].toLowerCase().includes(searchString)) {
+ datalistLabels.push(oldLabels[i]);
+ datalistResults.push(oldValues[i]);
+ }
+ }
+ }
+
+ let searchTokens = searchString.split(/\s+/);
+ // We have a list of results for a shorter search string, so just
+ // filter them further based on the new search string and add to a new array.
+ let entries = wrappedResult.entries;
+ let filteredEntries = [];
+ for (let i = 0; i < entries.length; i++) {
+ let entry = entries[i];
+ // Remove results that do not contain the token
+ // XXX bug 394604 -- .toLowerCase can be wrong for some intl chars
+ if (searchTokens.some(tok => entry.textLowerCase.indexOf(tok) < 0))
+ continue;
+ this._calculateScore(entry, searchString, searchTokens);
+ this.log("Reusing autocomplete entry '" + entry.text +
+ "' (" + entry.frecency +" / " + entry.totalScore + ")");
+ filteredEntries.push(entry);
+ }
+ filteredEntries.sort(sortBytotalScore);
+ wrappedResult.entries = filteredEntries;
+
+ // If we had datalistResults, re-merge them back into the filtered
+ // entries.
+ if (datalistResults) {
+ filteredEntries = filteredEntries.map(elt => elt.text);
+
+ let comments = new Array(filteredEntries.length + datalistResults.length).fill("");
+ comments[filteredEntries.length] = "separator";
+
+ // History entries don't have labels (their labels would be read
+ // from their values). Pad out the labels array so the datalist
+ // results (which do have separate values and labels) line up.
+ datalistLabels = new Array(filteredEntries.length).fill("").concat(datalistLabels);
+ wrappedResult._values = filteredEntries.concat(datalistResults);
+ wrappedResult._labels = datalistLabels;
+ wrappedResult._comments = comments;
+ }
+
+ if (aListener) {
+ aListener.onSearchCompletion(result);
+ }
+ } else {
+ this.log("Creating new autocomplete search result.");
+
+ // Start with an empty list.
+ let result = aDatalistResult ?
+ new FormAutoCompleteResult(client, [], aInputName, aUntrimmedSearchString, null) :
+ emptyResult;
+
+ let processEntry = (aEntries) => {
+ if (aField && aField.maxLength > -1) {
+ result.entries =
+ aEntries.filter(function (el) { return el.text.length <= aField.maxLength; });
+ } else {
+ result.entries = aEntries;
+ }
+
+ if (aDatalistResult && aDatalistResult.matchCount > 0) {
+ result = this.mergeResults(result, aDatalistResult);
+ }
+
+ if (aListener) {
+ aListener.onSearchCompletion(result);
+ }
+ }
+
+ this.getAutoCompleteValues(client, aInputName, searchString, processEntry);
+ }
+ },
+
+ mergeResults(historyResult, datalistResult) {
+ let values = datalistResult.wrappedJSObject._values;
+ let labels = datalistResult.wrappedJSObject._labels;
+ let comments = new Array(values.length).fill("");
+
+ // historyResult will be null if form autocomplete is disabled. We
+ // still want the list values to display.
+ let entries = historyResult.wrappedJSObject.entries;
+ let historyResults = entries.map(entry => entry.text);
+ let historyComments = new Array(entries.length).fill("");
+
+ // now put the history results above the datalist suggestions
+ let finalValues = historyResults.concat(values);
+ let finalLabels = historyResults.concat(labels);
+ let finalComments = historyComments.concat(comments);
+
+ // This is ugly: there are two FormAutoCompleteResult classes in the
+ // tree, one in a module and one in this file. Datalist results need to
+ // use the one defined in the module but the rest of this file assumes
+ // that we use the one defined here. To get around that, we explicitly
+ // import the module here, out of the way of the other uses of
+ // FormAutoCompleteResult.
+ let {FormAutoCompleteResult} = Cu.import("resource://gre/modules/nsFormAutoCompleteResult.jsm", {});
+ return new FormAutoCompleteResult(datalistResult.searchString,
+ Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
+ 0, "", finalValues, finalLabels,
+ finalComments, historyResult);
+ },
+
+ stopAutoCompleteSearch : function () {
+ if (this._pendingClient) {
+ this._pendingClient.cancel();
+ this._pendingClient = null;
+ }
+ },
+
+ /*
+ * Get the values for an autocomplete list given a search string.
+ *
+ * client - a FormHistoryClient instance to perform the search with
+ * fieldName - fieldname field within form history (the form input name)
+ * searchString - string to search for
+ * callback - called when the values are available. Passed an array of objects,
+ * containing properties for each result. The callback is only called
+ * when successful.
+ */
+ getAutoCompleteValues : function (client, fieldName, searchString, callback) {
+ let params = {
+ agedWeight: this._agedWeight,
+ bucketSize: this._bucketSize,
+ expiryDate: 1000 * (Date.now() - this._expireDays * 24 * 60 * 60 * 1000),
+ fieldname: fieldName,
+ maxTimeGroupings: this._maxTimeGroupings,
+ timeGroupingSize: this._timeGroupingSize,
+ prefixWeight: this._prefixWeight,
+ boundaryWeight: this._boundaryWeight
+ }
+
+ this.stopAutoCompleteSearch();
+ client.requestAutoCompleteResults(searchString, params, (entries) => {
+ this._pendingClient = null;
+ callback(entries);
+ });
+ this._pendingClient = client;
+ },
+
+ /*
+ * _calculateScore
+ *
+ * entry -- an nsIAutoCompleteResult entry
+ * aSearchString -- current value of the input (lowercase)
+ * searchTokens -- array of tokens of the search string
+ *
+ * Returns: an int
+ */
+ _calculateScore : function (entry, aSearchString, searchTokens) {
+ let boundaryCalc = 0;
+ // for each word, calculate word boundary weights
+ for (let token of searchTokens) {
+ boundaryCalc += (entry.textLowerCase.indexOf(token) == 0);
+ boundaryCalc += (entry.textLowerCase.indexOf(" " + token) >= 0);
+ }
+ boundaryCalc = boundaryCalc * this._boundaryWeight;
+ // now add more weight if we have a traditional prefix match and
+ // multiply boundary bonuses by boundary weight
+ boundaryCalc += this._prefixWeight *
+ (entry.textLowerCase.
+ indexOf(aSearchString) == 0);
+ entry.totalScore = Math.round(entry.frecency * Math.max(1, boundaryCalc));
+ }
+
+}; // end of FormAutoComplete implementation
+
+// nsIAutoCompleteResult implementation
+function FormAutoCompleteResult(client,
+ entries,
+ fieldName,
+ searchString,
+ messageManager) {
+ this.client = client;
+ this.entries = entries;
+ this.fieldName = fieldName;
+ this.searchString = searchString;
+ this.messageManager = messageManager;
+}
+
+FormAutoCompleteResult.prototype = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult,
+ Ci.nsISupportsWeakReference]),
+
+ // private
+ client : null,
+ entries : null,
+ fieldName : null,
+
+ _checkIndexBounds : function (index) {
+ if (index < 0 || index >= this.entries.length)
+ throw Components.Exception("Index out of range.", Cr.NS_ERROR_ILLEGAL_VALUE);
+ },
+
+ // Allow autoCompleteSearch to get at the JS object so it can
+ // modify some readonly properties for internal use.
+ get wrappedJSObject() {
+ return this;
+ },
+
+ // Interfaces from idl...
+ searchString : "",
+ errorDescription : "",
+ get defaultIndex() {
+ if (this.entries.length == 0)
+ return -1;
+ return 0;
+ },
+ get searchResult() {
+ if (this.entries.length == 0)
+ return Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
+ return Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
+ },
+ get matchCount() {
+ return this.entries.length;
+ },
+
+ getValueAt : function (index) {
+ this._checkIndexBounds(index);
+ return this.entries[index].text;
+ },
+
+ getLabelAt: function(index) {
+ return this.getValueAt(index);
+ },
+
+ getCommentAt : function (index) {
+ this._checkIndexBounds(index);
+ return "";
+ },
+
+ getStyleAt : function (index) {
+ this._checkIndexBounds(index);
+ return "";
+ },
+
+ getImageAt : function (index) {
+ this._checkIndexBounds(index);
+ return "";
+ },
+
+ getFinalCompleteValueAt : function (index) {
+ return this.getValueAt(index);
+ },
+
+ removeValueAt : function (index, removeFromDB) {
+ this._checkIndexBounds(index);
+
+ let [removedEntry] = this.entries.splice(index, 1);
+
+ if (removeFromDB) {
+ this.client.remove(removedEntry.text);
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FormAutoComplete]);
diff --git a/components/satchel/nsFormAutoCompleteResult.jsm b/components/satchel/nsFormAutoCompleteResult.jsm
new file mode 100644
index 000000000..07ef15fca
--- /dev/null
+++ b/components/satchel/nsFormAutoCompleteResult.jsm
@@ -0,0 +1,187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = [ "FormAutoCompleteResult" ];
+
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+this.FormAutoCompleteResult =
+ function FormAutoCompleteResult(searchString,
+ searchResult,
+ defaultIndex,
+ errorDescription,
+ values,
+ labels,
+ comments,
+ prevResult) {
+ this.searchString = searchString;
+ this._searchResult = searchResult;
+ this._defaultIndex = defaultIndex;
+ this._errorDescription = errorDescription;
+ this._values = values;
+ this._labels = labels;
+ this._comments = comments;
+ this._formHistResult = prevResult;
+
+ if (prevResult) {
+ this.entries = prevResult.wrappedJSObject.entries;
+ } else {
+ this.entries = [];
+ }
+}
+
+FormAutoCompleteResult.prototype = {
+
+ // The user's query string
+ searchString: "",
+
+ // The result code of this result object, see |get searchResult| for possible values.
+ _searchResult: 0,
+
+ // The default item that should be entered if none is selected
+ _defaultIndex: 0,
+
+ // The reason the search failed
+ _errorDescription: "",
+
+ /**
+ * A reference to the form history nsIAutocompleteResult that we're wrapping.
+ * We use this to forward removeEntryAt calls as needed.
+ */
+ _formHistResult: null,
+
+ entries: null,
+
+ get wrappedJSObject() {
+ return this;
+ },
+
+ /**
+ * @return the result code of this result object, either:
+ * RESULT_IGNORED (invalid searchString)
+ * RESULT_FAILURE (failure)
+ * RESULT_NOMATCH (no matches found)
+ * RESULT_SUCCESS (matches found)
+ */
+ get searchResult() {
+ return this._searchResult;
+ },
+
+ /**
+ * @return the default item that should be entered if none is selected
+ */
+ get defaultIndex() {
+ return this._defaultIndex;
+ },
+
+ /**
+ * @return the reason the search failed
+ */
+ get errorDescription() {
+ return this._errorDescription;
+ },
+
+ /**
+ * @return the number of results
+ */
+ get matchCount() {
+ return this._values.length;
+ },
+
+ _checkIndexBounds : function (index) {
+ if (index < 0 || index >= this._values.length) {
+ throw Components.Exception("Index out of range.", Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+ },
+
+ /**
+ * Retrieves a result
+ * @param index the index of the result requested
+ * @return the result at the specified index
+ */
+ getValueAt: function(index) {
+ this._checkIndexBounds(index);
+ return this._values[index];
+ },
+
+ getLabelAt: function(index) {
+ this._checkIndexBounds(index);
+ return this._labels[index] || this._values[index];
+ },
+
+ /**
+ * Retrieves a comment (metadata instance)
+ * @param index the index of the comment requested
+ * @return the comment at the specified index
+ */
+ getCommentAt: function(index) {
+ this._checkIndexBounds(index);
+ return this._comments[index];
+ },
+
+ /**
+ * Retrieves a style hint specific to a particular index.
+ * @param index the index of the style hint requested
+ * @return the style hint at the specified index
+ */
+ getStyleAt: function(index) {
+ this._checkIndexBounds(index);
+
+ if (this._formHistResult && index < this._formHistResult.matchCount) {
+ return "fromhistory";
+ }
+
+ if (this._formHistResult &&
+ this._formHistResult.matchCount > 0 &&
+ index == this._formHistResult.matchCount) {
+ return "datalist-first";
+ }
+
+ return null;
+ },
+
+ /**
+ * Retrieves an image url.
+ * @param index the index of the image url requested
+ * @return the image url at the specified index
+ */
+ getImageAt: function(index) {
+ this._checkIndexBounds(index);
+ return "";
+ },
+
+ /**
+ * Retrieves a result
+ * @param index the index of the result requested
+ * @return the result at the specified index
+ */
+ getFinalCompleteValueAt: function(index) {
+ return this.getValueAt(index);
+ },
+
+ /**
+ * Removes a result from the resultset
+ * @param index the index of the result to remove
+ */
+ removeValueAt: function(index, removeFromDatabase) {
+ this._checkIndexBounds(index);
+ // Forward the removeValueAt call to the underlying result if we have one
+ // Note: this assumes that the form history results were added to the top
+ // of our arrays.
+ if (removeFromDatabase && this._formHistResult &&
+ index < this._formHistResult.matchCount) {
+ // Delete the history result from the DB
+ this._formHistResult.removeValueAt(index, true);
+ }
+ this._values.splice(index, 1);
+ this._labels.splice(index, 1);
+ this._comments.splice(index, 1);
+ },
+
+ // nsISupports
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult])
+};
diff --git a/components/satchel/nsFormFillController.cpp b/components/satchel/nsFormFillController.cpp
new file mode 100644
index 000000000..880ca79b2
--- /dev/null
+++ b/components/satchel/nsFormFillController.cpp
@@ -0,0 +1,1363 @@
+/* -*- 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 "nsFormFillController.h"
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
+#include "nsIFormAutoComplete.h"
+#include "nsIInputListAutoComplete.h"
+#include "nsIAutoCompleteSimpleResult.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIServiceManager.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWebNavigation.h"
+#include "nsIContentViewer.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIFormControl.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIPresShell.h"
+#include "nsRect.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsILoginManager.h"
+#include "nsIDOMMouseEvent.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsToolkitCompsCID.h"
+#include "nsEmbedCID.h"
+#include "nsIDOMNSEditableElement.h"
+#include "nsContentUtils.h"
+#include "nsILoadContext.h"
+#include "nsIFrame.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsFocusManager.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION(nsFormFillController,
+ mController, mLoginManager, mFocusedPopup, mDocShells,
+ mPopups, mLastListener, mLastFormAutoComplete)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFormFillController)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFormFillController)
+ NS_INTERFACE_MAP_ENTRY(nsIFormFillController)
+ NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteInput)
+ NS_INTERFACE_MAP_ENTRY(nsIAutoCompleteSearch)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIFormAutoCompleteObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFormFillController)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFormFillController)
+
+
+
+nsFormFillController::nsFormFillController() :
+ mFocusedInput(nullptr),
+ mFocusedInputNode(nullptr),
+ mListNode(nullptr),
+ mTimeout(50),
+ mMinResultsForPopup(1),
+ mMaxRows(0),
+ mContextMenuFiredBeforeFocus(false),
+ mDisableAutoComplete(false),
+ mCompleteDefaultIndex(false),
+ mCompleteSelectedIndex(false),
+ mForceComplete(false),
+ mSuppressOnInput(false)
+{
+ mController = do_GetService("@mozilla.org/autocomplete/controller;1");
+ MOZ_ASSERT(mController);
+}
+
+nsFormFillController::~nsFormFillController()
+{
+ if (mListNode) {
+ mListNode->RemoveMutationObserver(this);
+ mListNode = nullptr;
+ }
+ if (mFocusedInputNode) {
+ MaybeRemoveMutationObserver(mFocusedInputNode);
+ mFocusedInputNode = nullptr;
+ mFocusedInput = nullptr;
+ }
+ RemoveForDocument(nullptr);
+
+ // Remove ourselves as a focus listener from all cached docShells
+ uint32_t count = mDocShells.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindowForDocShell(mDocShells[i]);
+ RemoveWindowListeners(window);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIMutationObserver
+//
+
+void
+nsFormFillController::AttributeChanged(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ if ((aAttribute == nsGkAtoms::type || aAttribute == nsGkAtoms::readonly ||
+ aAttribute == nsGkAtoms::autocomplete) &&
+ aNameSpaceID == kNameSpaceID_None) {
+ nsCOMPtr<nsIDOMHTMLInputElement> focusedInput(mFocusedInput);
+ // Reset the current state of the controller, unconditionally.
+ StopControllingInput();
+ // Then restart based on the new values. We have to delay this
+ // to avoid ending up in an endless loop due to re-registering our
+ // mutation observer (which would notify us again for *this* event).
+ nsCOMPtr<nsIRunnable> event =
+ mozilla::NewRunnableMethod<nsCOMPtr<nsIDOMHTMLInputElement>>
+ (this, &nsFormFillController::MaybeStartControllingInput, focusedInput);
+ NS_DispatchToCurrentThread(event);
+ }
+
+ if (mListNode && mListNode->Contains(aElement)) {
+ RevalidateDataList();
+ }
+}
+
+void
+nsFormFillController::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ if (mListNode && mListNode->Contains(aContainer)) {
+ RevalidateDataList();
+ }
+}
+
+void
+nsFormFillController::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ if (mListNode && mListNode->Contains(aContainer)) {
+ RevalidateDataList();
+ }
+}
+
+void
+nsFormFillController::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ if (mListNode && mListNode->Contains(aContainer)) {
+ RevalidateDataList();
+ }
+}
+
+void
+nsFormFillController::CharacterDataWillChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+void
+nsFormFillController::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+void
+nsFormFillController::AttributeWillChange(nsIDocument* aDocument,
+ mozilla::dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+}
+
+void
+nsFormFillController::NativeAnonymousChildListChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ bool aIsRemove)
+{
+}
+
+void
+nsFormFillController::ParentChainChanged(nsIContent* aContent)
+{
+}
+
+void
+nsFormFillController::NodeWillBeDestroyed(const nsINode* aNode)
+{
+ mPwmgrInputs.Remove(aNode);
+ if (aNode == mListNode) {
+ mListNode = nullptr;
+ RevalidateDataList();
+ } else if (aNode == mFocusedInputNode) {
+ mFocusedInputNode = nullptr;
+ mFocusedInput = nullptr;
+ }
+}
+
+void
+nsFormFillController::MaybeRemoveMutationObserver(nsINode* aNode)
+{
+ // Nodes being tracked in mPwmgrInputs will have their observers removed when
+ // they stop being tracked.
+ if (!mPwmgrInputs.Get(aNode)) {
+ aNode->RemoveMutationObserver(this);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIFormFillController
+
+NS_IMETHODIMP
+nsFormFillController::AttachToBrowser(nsIDocShell *aDocShell, nsIAutoCompletePopup *aPopup)
+{
+ NS_ENSURE_TRUE(aDocShell && aPopup, NS_ERROR_ILLEGAL_VALUE);
+
+ mDocShells.AppendElement(aDocShell);
+ mPopups.AppendElement(aPopup);
+
+ // Listen for focus events on the domWindow of the docShell
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindowForDocShell(aDocShell);
+ AddWindowListeners(window);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::DetachFromBrowser(nsIDocShell *aDocShell)
+{
+ int32_t index = GetIndexOfDocShell(aDocShell);
+ NS_ENSURE_TRUE(index >= 0, NS_ERROR_FAILURE);
+
+ // Stop listening for focus events on the domWindow of the docShell
+ nsCOMPtr<nsPIDOMWindowOuter> window =
+ GetWindowForDocShell(mDocShells.SafeElementAt(index));
+ RemoveWindowListeners(window);
+
+ mDocShells.RemoveElementAt(index);
+ mPopups.RemoveElementAt(index);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsFormFillController::MarkAsLoginManagerField(nsIDOMHTMLInputElement *aInput)
+{
+ /*
+ * The Login Manager can supply autocomplete results for username fields,
+ * when a user has multiple logins stored for a site. It uses this
+ * interface to indicate that the form manager shouldn't handle the
+ * autocomplete. The form manager also checks for this tag when saving
+ * form history (so it doesn't save usernames).
+ */
+ nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
+ NS_ENSURE_STATE(node);
+
+ // If the field was already marked, we don't want to show the popup again.
+ if (mPwmgrInputs.Get(node)) {
+ return NS_OK;
+ }
+
+ mPwmgrInputs.Put(node, true);
+ node->AddMutationObserverUnlessExists(this);
+
+ nsFocusManager *fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent();
+ if (SameCOMIdentity(focusedContent, node)) {
+ nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(node);
+ if (!mFocusedInput) {
+ MaybeStartControllingInput(input);
+ }
+ }
+ }
+
+ if (!mLoginManager)
+ mLoginManager = do_GetService("@mozilla.org/login-manager;1");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetFocusedInput(nsIDOMHTMLInputElement **aInput) {
+ *aInput = mFocusedInput;
+ NS_IF_ADDREF(*aInput);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIAutoCompleteInput
+
+NS_IMETHODIMP
+nsFormFillController::GetPopup(nsIAutoCompletePopup **aPopup)
+{
+ *aPopup = mFocusedPopup;
+ NS_IF_ADDREF(*aPopup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetController(nsIAutoCompleteController **aController)
+{
+ *aController = mController;
+ NS_IF_ADDREF(*aController);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetPopupOpen(bool *aPopupOpen)
+{
+ if (mFocusedPopup)
+ mFocusedPopup->GetPopupOpen(aPopupOpen);
+ else
+ *aPopupOpen = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetPopupOpen(bool aPopupOpen)
+{
+ if (mFocusedPopup) {
+ if (aPopupOpen) {
+ // make sure input field is visible before showing popup (bug 320938)
+ nsCOMPtr<nsIContent> content = do_QueryInterface(mFocusedInput);
+ NS_ENSURE_STATE(content);
+ nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(mFocusedInput);
+ NS_ENSURE_STATE(docShell);
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+ NS_ENSURE_STATE(presShell);
+ presShell->ScrollContentIntoView(content,
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_MINIMUM,
+ nsIPresShell::SCROLL_IF_NOT_VISIBLE),
+ nsIPresShell::ScrollAxis(
+ nsIPresShell::SCROLL_MINIMUM,
+ nsIPresShell::SCROLL_IF_NOT_VISIBLE),
+ nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
+ // mFocusedPopup can be destroyed after ScrollContentIntoView, see bug 420089
+ if (mFocusedPopup) {
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
+ mFocusedPopup->OpenAutocompletePopup(this, element);
+ }
+ } else
+ mFocusedPopup->ClosePopup();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetDisableAutoComplete(bool *aDisableAutoComplete)
+{
+ *aDisableAutoComplete = mDisableAutoComplete;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetDisableAutoComplete(bool aDisableAutoComplete)
+{
+ mDisableAutoComplete = aDisableAutoComplete;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetCompleteDefaultIndex(bool *aCompleteDefaultIndex)
+{
+ *aCompleteDefaultIndex = mCompleteDefaultIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetCompleteDefaultIndex(bool aCompleteDefaultIndex)
+{
+ mCompleteDefaultIndex = aCompleteDefaultIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetCompleteSelectedIndex(bool *aCompleteSelectedIndex)
+{
+ *aCompleteSelectedIndex = mCompleteSelectedIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetCompleteSelectedIndex(bool aCompleteSelectedIndex)
+{
+ mCompleteSelectedIndex = aCompleteSelectedIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetForceComplete(bool *aForceComplete)
+{
+ *aForceComplete = mForceComplete;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::SetForceComplete(bool aForceComplete)
+{
+ mForceComplete = aForceComplete;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetMinResultsForPopup(uint32_t *aMinResultsForPopup)
+{
+ *aMinResultsForPopup = mMinResultsForPopup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::SetMinResultsForPopup(uint32_t aMinResultsForPopup)
+{
+ mMinResultsForPopup = aMinResultsForPopup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetMaxRows(uint32_t *aMaxRows)
+{
+ *aMaxRows = mMaxRows;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetMaxRows(uint32_t aMaxRows)
+{
+ mMaxRows = aMaxRows;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetShowImageColumn(bool *aShowImageColumn)
+{
+ *aShowImageColumn = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::SetShowImageColumn(bool aShowImageColumn)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsFormFillController::GetShowCommentColumn(bool *aShowCommentColumn)
+{
+ *aShowCommentColumn = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::SetShowCommentColumn(bool aShowCommentColumn)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetTimeout(uint32_t *aTimeout)
+{
+ *aTimeout = mTimeout;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFormFillController::SetTimeout(uint32_t aTimeout)
+{
+ mTimeout = aTimeout;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetSearchParam(const nsAString &aSearchParam)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSearchParam(nsAString &aSearchParam)
+{
+ if (!mFocusedInput) {
+ NS_WARNING("mFocusedInput is null for some reason! avoiding a crash. should find out why... - ben");
+ return NS_ERROR_FAILURE; // XXX why? fix me.
+ }
+
+ mFocusedInput->GetName(aSearchParam);
+ if (aSearchParam.IsEmpty()) {
+ nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(mFocusedInput);
+ element->GetId(aSearchParam);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSearchCount(uint32_t *aSearchCount)
+{
+ *aSearchCount = 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSearchAt(uint32_t index, nsACString & _retval)
+{
+ _retval.AssignLiteral("form-history");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetTextValue(nsAString & aTextValue)
+{
+ if (mFocusedInput) {
+ nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
+ input->GetValue(aTextValue);
+ } else {
+ aTextValue.Truncate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetTextValue(const nsAString & aTextValue)
+{
+ nsCOMPtr<nsIDOMNSEditableElement> editable = do_QueryInterface(mFocusedInput);
+ if (editable) {
+ mSuppressOnInput = true;
+ editable->SetUserInput(aTextValue);
+ mSuppressOnInput = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SetTextValueWithReason(const nsAString & aTextValue,
+ uint16_t aReason)
+{
+ return SetTextValue(aTextValue);
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSelectionStart(int32_t *aSelectionStart)
+{
+ if (mFocusedInput) {
+ nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
+ input->GetSelectionStart(aSelectionStart);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetSelectionEnd(int32_t *aSelectionEnd)
+{
+ if (mFocusedInput) {
+ nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
+ input->GetSelectionEnd(aSelectionEnd);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::SelectTextRange(int32_t aStartIndex, int32_t aEndIndex)
+{
+ if (mFocusedInput) {
+ nsCOMPtr<nsIDOMHTMLInputElement> input = mFocusedInput;
+ input->SetSelectionRange(aStartIndex, aEndIndex, EmptyString());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::OnSearchBegin()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::OnSearchComplete()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::OnTextEntered(nsIDOMEvent* aEvent,
+ bool* aPrevent)
+{
+ NS_ENSURE_ARG(aPrevent);
+ NS_ENSURE_TRUE(mFocusedInput, NS_OK);
+ // Fire off a DOMAutoComplete event
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
+ element->GetOwnerDocument(getter_AddRefs(domDoc));
+ NS_ENSURE_STATE(domDoc);
+
+ nsCOMPtr<nsIDOMEvent> event;
+ domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
+ NS_ENSURE_STATE(event);
+
+ event->InitEvent(NS_LITERAL_STRING("DOMAutoComplete"), true, true);
+
+ // XXXjst: We mark this event as a trusted event, it's up to the
+ // callers of this to ensure that it's only called from trusted
+ // code.
+ event->SetTrusted(true);
+
+ nsCOMPtr<EventTarget> targ = do_QueryInterface(mFocusedInput);
+
+ bool defaultActionEnabled;
+ targ->DispatchEvent(event, &defaultActionEnabled);
+ *aPrevent = !defaultActionEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::OnTextReverted(bool *_retval)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetConsumeRollupEvent(bool *aConsumeRollupEvent)
+{
+ *aConsumeRollupEvent = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetInPrivateContext(bool *aInPrivateContext)
+{
+ if (!mFocusedInput) {
+ *aInPrivateContext = false;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMDocument> inputDoc;
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mFocusedInput);
+ element->GetOwnerDocument(getter_AddRefs(inputDoc));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(inputDoc);
+ nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
+ *aInPrivateContext = loadContext && loadContext->UsePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetNoRollupOnCaretMove(bool *aNoRollupOnCaretMove)
+{
+ *aNoRollupOnCaretMove = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFormFillController::GetUserContextId(uint32_t* aUserContextId)
+{
+ *aUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIAutoCompleteSearch
+
+NS_IMETHODIMP
+nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAString &aSearchParam,
+ nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
+
+ // If the login manager has indicated it's responsible for this field, let it
+ // handle the autocomplete. Otherwise, handle with form history.
+ // This method is sometimes called in unit tests and from XUL without a focused node.
+ if (mFocusedInputNode && (mPwmgrInputs.Get(mFocusedInputNode) ||
+ formControl->GetType() == NS_FORM_INPUT_PASSWORD)) {
+
+ // Handle the case where a password field is focused but
+ // MarkAsLoginManagerField wasn't called because password manager is disabled.
+ if (!mLoginManager) {
+ mLoginManager = do_GetService("@mozilla.org/login-manager;1");
+ }
+
+ if (NS_WARN_IF(!mLoginManager)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // XXX aPreviousResult shouldn't ever be a historyResult type, since we're not letting
+ // satchel manage the field?
+ mLastListener = aListener;
+ rv = mLoginManager->AutoCompleteSearchAsync(aSearchString,
+ aPreviousResult,
+ mFocusedInput,
+ this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mLastListener = aListener;
+
+ nsCOMPtr<nsIAutoCompleteResult> datalistResult;
+ if (mFocusedInput) {
+ rv = PerformInputListAutoComplete(aSearchString,
+ getter_AddRefs(datalistResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
+ do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ formAutoComplete->AutoCompleteSearchAsync(aSearchParam,
+ aSearchString,
+ mFocusedInput,
+ aPreviousResult,
+ datalistResult,
+ this);
+ mLastFormAutoComplete = formAutoComplete;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsFormFillController::PerformInputListAutoComplete(const nsAString& aSearch,
+ nsIAutoCompleteResult** aResult)
+{
+ // If an <input> is focused, check if it has a list="<datalist>" which can
+ // provide the list of suggestions.
+
+ MOZ_ASSERT(!mPwmgrInputs.Get(mFocusedInputNode));
+ nsresult rv;
+
+ nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
+ do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = inputListAutoComplete->AutoCompleteSearch(aSearch,
+ mFocusedInput,
+ aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFocusedInput) {
+ nsCOMPtr<nsIDOMHTMLElement> list;
+ mFocusedInput->GetList(getter_AddRefs(list));
+
+ // Add a mutation observer to check for changes to the items in the <datalist>
+ // and update the suggestions accordingly.
+ nsCOMPtr<nsINode> node = do_QueryInterface(list);
+ if (mListNode != node) {
+ if (mListNode) {
+ mListNode->RemoveMutationObserver(this);
+ mListNode = nullptr;
+ }
+ if (node) {
+ node->AddMutationObserverUnlessExists(this);
+ mListNode = node;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+class UpdateSearchResultRunnable : public mozilla::Runnable
+{
+public:
+ UpdateSearchResultRunnable(nsIAutoCompleteObserver* aObserver,
+ nsIAutoCompleteSearch* aSearch,
+ nsIAutoCompleteResult* aResult)
+ : mObserver(aObserver)
+ , mSearch(aSearch)
+ , mResult(aResult)
+ {
+ MOZ_ASSERT(mResult, "Should have a valid result");
+ MOZ_ASSERT(mObserver, "You shouldn't call this runnable with a null observer!");
+ }
+
+ NS_IMETHOD Run() override {
+ mObserver->OnUpdateSearchResult(mSearch, mResult);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIAutoCompleteObserver> mObserver;
+ nsCOMPtr<nsIAutoCompleteSearch> mSearch;
+ nsCOMPtr<nsIAutoCompleteResult> mResult;
+};
+
+void nsFormFillController::RevalidateDataList()
+{
+ if (!mLastListener) {
+ return;
+ }
+
+ if (XRE_IsContentProcess()) {
+ nsCOMPtr<nsIAutoCompleteController> controller(do_QueryInterface(mLastListener));
+ if (!controller) {
+ return;
+ }
+
+ controller->StartSearch(mLastSearchString);
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr <nsIInputListAutoComplete> inputListAutoComplete =
+ do_GetService("@mozilla.org/satchel/inputlist-autocomplete;1", &rv);
+
+ nsCOMPtr<nsIAutoCompleteResult> result;
+
+ rv = inputListAutoComplete->AutoCompleteSearch(mLastSearchString,
+ mFocusedInput,
+ getter_AddRefs(result));
+
+ nsCOMPtr<nsIRunnable> event =
+ new UpdateSearchResultRunnable(mLastListener, this, result);
+ NS_DispatchToCurrentThread(event);
+}
+
+NS_IMETHODIMP
+nsFormFillController::StopSearch()
+{
+ // Make sure to stop and clear this, otherwise the controller will prevent
+ // mLastFormAutoComplete from being deleted.
+ if (mLastFormAutoComplete) {
+ mLastFormAutoComplete->StopAutoCompleteSearch();
+ mLastFormAutoComplete = nullptr;
+ } else if (mLoginManager) {
+ mLoginManager->StopSearch();
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIFormAutoCompleteObserver
+
+NS_IMETHODIMP
+nsFormFillController::OnSearchCompletion(nsIAutoCompleteResult *aResult)
+{
+ nsAutoString searchString;
+ aResult->GetSearchString(searchString);
+
+ mLastSearchString = searchString;
+
+ if (mLastListener) {
+ mLastListener->OnSearchResult(this, aResult);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsIDOMEventListener
+
+NS_IMETHODIMP
+nsFormFillController::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsAutoString type;
+ aEvent->GetType(type);
+
+ if (type.EqualsLiteral("focus")) {
+ return Focus(aEvent);
+ }
+ if (type.EqualsLiteral("mousedown")) {
+ return MouseDown(aEvent);
+ }
+ if (type.EqualsLiteral("keypress")) {
+ return KeyPress(aEvent);
+ }
+ if (type.EqualsLiteral("input")) {
+ bool unused = false;
+ return (!mSuppressOnInput && mController && mFocusedInput) ?
+ mController->HandleText(&unused) : NS_OK;
+ }
+ if (type.EqualsLiteral("blur")) {
+ if (mFocusedInput)
+ StopControllingInput();
+ return NS_OK;
+ }
+ if (type.EqualsLiteral("compositionstart")) {
+ NS_ASSERTION(mController, "should have a controller!");
+ if (mController && mFocusedInput)
+ mController->HandleStartComposition();
+ return NS_OK;
+ }
+ if (type.EqualsLiteral("compositionend")) {
+ NS_ASSERTION(mController, "should have a controller!");
+ if (mController && mFocusedInput)
+ mController->HandleEndComposition();
+ return NS_OK;
+ }
+ if (type.EqualsLiteral("contextmenu")) {
+ mContextMenuFiredBeforeFocus = true;
+ if (mFocusedPopup)
+ mFocusedPopup->ClosePopup();
+ return NS_OK;
+ }
+ if (type.EqualsLiteral("pagehide")) {
+
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(
+ aEvent->InternalDOMEvent()->GetTarget());
+ if (!doc)
+ return NS_OK;
+
+ if (mFocusedInput) {
+ if (doc == mFocusedInputNode->OwnerDoc())
+ StopControllingInput();
+ }
+
+ RemoveForDocument(doc);
+ }
+
+ return NS_OK;
+}
+
+void
+nsFormFillController::RemoveForDocument(nsIDocument* aDoc)
+{
+ for (auto iter = mPwmgrInputs.Iter(); !iter.Done(); iter.Next()) {
+ const nsINode* key = iter.Key();
+ if (key && (!aDoc || key->OwnerDoc() == aDoc)) {
+ // mFocusedInputNode's observer is tracked separately, so don't remove it
+ // here.
+ if (key != mFocusedInputNode) {
+ const_cast<nsINode*>(key)->RemoveMutationObserver(this);
+ }
+ iter.Remove();
+ }
+ }
+}
+
+void
+nsFormFillController::MaybeStartControllingInput(nsIDOMHTMLInputElement* aInput)
+{
+ nsCOMPtr<nsINode> inputNode = do_QueryInterface(aInput);
+ if (!inputNode)
+ return;
+
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aInput);
+ if (!formControl || !formControl->IsSingleLineTextControl(false))
+ return;
+
+ bool isReadOnly = false;
+ aInput->GetReadOnly(&isReadOnly);
+ if (isReadOnly)
+ return;
+
+ bool autocomplete = nsContentUtils::IsAutocompleteEnabled(aInput);
+
+ nsCOMPtr<nsIDOMHTMLElement> datalist;
+ aInput->GetList(getter_AddRefs(datalist));
+ bool hasList = datalist != nullptr;
+
+ bool isPwmgrInput = false;
+ if (mPwmgrInputs.Get(inputNode) ||
+ formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
+ isPwmgrInput = true;
+ }
+
+ if (isPwmgrInput || hasList || autocomplete) {
+ StartControllingInput(aInput);
+ }
+}
+
+nsresult
+nsFormFillController::Focus(nsIDOMEvent* aEvent)
+{
+ nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(
+ aEvent->InternalDOMEvent()->GetTarget());
+ MaybeStartControllingInput(input);
+
+ // Bail if we didn't start controlling the input.
+ if (!mFocusedInputNode) {
+ mContextMenuFiredBeforeFocus = false;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
+ MOZ_ASSERT(formControl);
+
+ // If this focus doesn't immediately follow a contextmenu event then show
+ // the autocomplete popup for all password fields.
+ if (!mContextMenuFiredBeforeFocus
+ && formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
+ ShowPopup();
+ }
+
+ mContextMenuFiredBeforeFocus = false;
+ return NS_OK;
+}
+
+nsresult
+nsFormFillController::KeyPress(nsIDOMEvent* aEvent)
+{
+ NS_ASSERTION(mController, "should have a controller!");
+ if (!mFocusedInput || !mController)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
+ if (!keyEvent)
+ return NS_ERROR_FAILURE;
+
+ bool cancel = false;
+ bool unused = false;
+
+ uint32_t k;
+ keyEvent->GetKeyCode(&k);
+ switch (k) {
+ case nsIDOMKeyEvent::DOM_VK_DELETE:
+ mController->HandleDelete(&cancel);
+ break;
+ case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
+ mController->HandleText(&unused);
+ break;
+ case nsIDOMKeyEvent::DOM_VK_PAGE_UP:
+ case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
+ {
+ bool isCtrl, isAlt, isMeta;
+ keyEvent->GetCtrlKey(&isCtrl);
+ keyEvent->GetAltKey(&isAlt);
+ keyEvent->GetMetaKey(&isMeta);
+ if (isCtrl || isAlt || isMeta)
+ break;
+ }
+ MOZ_FALLTHROUGH;
+ case nsIDOMKeyEvent::DOM_VK_UP:
+ case nsIDOMKeyEvent::DOM_VK_DOWN:
+ case nsIDOMKeyEvent::DOM_VK_LEFT:
+ case nsIDOMKeyEvent::DOM_VK_RIGHT:
+ {
+ // Get the writing-mode of the relevant input element,
+ // so that we can remap arrow keys if necessary.
+ mozilla::WritingMode wm;
+ if (mFocusedInputNode && mFocusedInputNode->IsElement()) {
+ mozilla::dom::Element *elem = mFocusedInputNode->AsElement();
+ nsIFrame *frame = elem->GetPrimaryFrame();
+ if (frame) {
+ wm = frame->GetWritingMode();
+ }
+ }
+ if (wm.IsVertical()) {
+ switch (k) {
+ case nsIDOMKeyEvent::DOM_VK_LEFT:
+ k = wm.IsVerticalLR() ? nsIDOMKeyEvent::DOM_VK_UP
+ : nsIDOMKeyEvent::DOM_VK_DOWN;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_RIGHT:
+ k = wm.IsVerticalLR() ? nsIDOMKeyEvent::DOM_VK_DOWN
+ : nsIDOMKeyEvent::DOM_VK_UP;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_UP:
+ k = nsIDOMKeyEvent::DOM_VK_LEFT;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_DOWN:
+ k = nsIDOMKeyEvent::DOM_VK_RIGHT;
+ break;
+ }
+ }
+ }
+ mController->HandleKeyNavigation(k, &cancel);
+ break;
+ case nsIDOMKeyEvent::DOM_VK_ESCAPE:
+ mController->HandleEscape(&cancel);
+ break;
+ case nsIDOMKeyEvent::DOM_VK_TAB:
+ mController->HandleTab();
+ cancel = false;
+ break;
+ case nsIDOMKeyEvent::DOM_VK_RETURN:
+ mController->HandleEnter(false, aEvent, &cancel);
+ break;
+ }
+
+ if (cancel) {
+ aEvent->PreventDefault();
+ // Don't let the page see the RETURN event when the popup is open
+ // (indicated by cancel=true) so sites don't manually submit forms
+ // (e.g. via submit.click()) without the autocompleted value being filled.
+ // Bug 286933 will fix this for other key events.
+ if (k == nsIDOMKeyEvent::DOM_VK_RETURN) {
+ aEvent->StopPropagation();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsFormFillController::MouseDown(nsIDOMEvent* aEvent)
+{
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aEvent));
+ if (!mouseEvent)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDOMHTMLInputElement> targetInput = do_QueryInterface(
+ aEvent->InternalDOMEvent()->GetTarget());
+ if (!targetInput)
+ return NS_OK;
+
+ int16_t button;
+ mouseEvent->GetButton(&button);
+ if (button != 0)
+ return NS_OK;
+
+ return ShowPopup();
+}
+
+NS_IMETHODIMP
+nsFormFillController::ShowPopup()
+{
+ bool isOpen = false;
+ GetPopupOpen(&isOpen);
+ if (isOpen) {
+ return SetPopupOpen(false);
+ }
+
+ nsCOMPtr<nsIAutoCompleteInput> input;
+ mController->GetInput(getter_AddRefs(input));
+ if (!input)
+ return NS_OK;
+
+ nsAutoString value;
+ input->GetTextValue(value);
+ if (value.Length() > 0) {
+ // Show the popup with a filtered result set
+ mController->SetSearchString(EmptyString());
+ bool unused = false;
+ mController->HandleText(&unused);
+ } else {
+ // Show the popup with the complete result set. Can't use HandleText()
+ // because it doesn't display the popup if the input is blank.
+ bool cancel = false;
+ mController->HandleKeyNavigation(nsIDOMKeyEvent::DOM_VK_DOWN, &cancel);
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////
+//// nsFormFillController
+
+void
+nsFormFillController::AddWindowListeners(nsPIDOMWindowOuter* aWindow)
+{
+ if (!aWindow)
+ return;
+
+ EventTarget* target = aWindow->GetChromeEventHandler();
+
+ if (!target)
+ return;
+
+ target->AddEventListener(NS_LITERAL_STRING("focus"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("blur"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("pagehide"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("mousedown"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("input"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("keypress"), this, true, false);
+ target->AddEventListener(NS_LITERAL_STRING("compositionstart"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("compositionend"), this,
+ true, false);
+ target->AddEventListener(NS_LITERAL_STRING("contextmenu"), this,
+ true, false);
+
+ // Note that any additional listeners added should ensure that they ignore
+ // untrusted events, which might be sent by content that's up to no good.
+}
+
+void
+nsFormFillController::RemoveWindowListeners(nsPIDOMWindowOuter* aWindow)
+{
+ if (!aWindow)
+ return;
+
+ StopControllingInput();
+
+ nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
+ RemoveForDocument(doc);
+
+ EventTarget* target = aWindow->GetChromeEventHandler();
+
+ if (!target)
+ return;
+
+ target->RemoveEventListener(NS_LITERAL_STRING("focus"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("pagehide"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("mousedown"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("input"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
+ target->RemoveEventListener(NS_LITERAL_STRING("compositionstart"), this,
+ true);
+ target->RemoveEventListener(NS_LITERAL_STRING("compositionend"), this,
+ true);
+ target->RemoveEventListener(NS_LITERAL_STRING("contextmenu"), this, true);
+}
+
+void
+nsFormFillController::StartControllingInput(nsIDOMHTMLInputElement *aInput)
+{
+ // Make sure we're not still attached to an input
+ StopControllingInput();
+
+ if (!mController) {
+ return;
+ }
+
+ // Find the currently focused docShell
+ nsCOMPtr<nsIDocShell> docShell = GetDocShellForInput(aInput);
+ int32_t index = GetIndexOfDocShell(docShell);
+ if (index < 0)
+ return;
+
+ // Cache the popup for the focused docShell
+ mFocusedPopup = mPopups.SafeElementAt(index);
+
+ nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
+ if (!node) {
+ return;
+ }
+
+ node->AddMutationObserverUnlessExists(this);
+ mFocusedInputNode = node;
+ mFocusedInput = aInput;
+
+ nsCOMPtr<nsIDOMHTMLElement> list;
+ mFocusedInput->GetList(getter_AddRefs(list));
+ nsCOMPtr<nsINode> listNode = do_QueryInterface(list);
+ if (listNode) {
+ listNode->AddMutationObserverUnlessExists(this);
+ mListNode = listNode;
+ }
+
+ mController->SetInput(this);
+}
+
+void
+nsFormFillController::StopControllingInput()
+{
+ if (mListNode) {
+ mListNode->RemoveMutationObserver(this);
+ mListNode = nullptr;
+ }
+
+ if (mController) {
+ // Reset the controller's input, but not if it has been switched
+ // to another input already, which might happen if the user switches
+ // focus by clicking another autocomplete textbox
+ nsCOMPtr<nsIAutoCompleteInput> input;
+ mController->GetInput(getter_AddRefs(input));
+ if (input == this)
+ mController->SetInput(nullptr);
+ }
+
+ if (mFocusedInputNode) {
+ MaybeRemoveMutationObserver(mFocusedInputNode);
+
+ nsresult rv;
+ nsCOMPtr <nsIFormAutoComplete> formAutoComplete =
+ do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv);
+ if (formAutoComplete) {
+ formAutoComplete->StopControllingInput(mFocusedInput);
+ }
+
+ mFocusedInputNode = nullptr;
+ mFocusedInput = nullptr;
+ }
+
+ if (mFocusedPopup) {
+ mFocusedPopup->ClosePopup();
+ }
+ mFocusedPopup = nullptr;
+}
+
+nsIDocShell *
+nsFormFillController::GetDocShellForInput(nsIDOMHTMLInputElement *aInput)
+{
+ nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
+ NS_ENSURE_TRUE(node, nullptr);
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = node->OwnerDoc()->GetWindow();
+ NS_ENSURE_TRUE(win, nullptr);
+
+ return win->GetDocShell();
+}
+
+nsPIDOMWindowOuter*
+nsFormFillController::GetWindowForDocShell(nsIDocShell *aDocShell)
+{
+ nsCOMPtr<nsIContentViewer> contentViewer;
+ aDocShell->GetContentViewer(getter_AddRefs(contentViewer));
+ NS_ENSURE_TRUE(contentViewer, nullptr);
+
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ contentViewer->GetDOMDocument(getter_AddRefs(domDoc));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+ NS_ENSURE_TRUE(doc, nullptr);
+
+ return doc->GetWindow();
+}
+
+int32_t
+nsFormFillController::GetIndexOfDocShell(nsIDocShell *aDocShell)
+{
+ if (!aDocShell)
+ return -1;
+
+ // Loop through our cached docShells looking for the given docShell
+ uint32_t count = mDocShells.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ if (mDocShells[i] == aDocShell)
+ return i;
+ }
+
+ // Recursively check the parent docShell of this one
+ nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(aDocShell);
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ treeItem->GetParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ nsCOMPtr<nsIDocShell> parentShell = do_QueryInterface(parentItem);
+ return GetIndexOfDocShell(parentShell);
+ }
+
+ return -1;
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFormFillController)
+
+NS_DEFINE_NAMED_CID(NS_FORMFILLCONTROLLER_CID);
+
+static const mozilla::Module::CIDEntry kSatchelCIDs[] = {
+ { &kNS_FORMFILLCONTROLLER_CID, false, nullptr, nsFormFillControllerConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kSatchelContracts[] = {
+ { "@mozilla.org/satchel/form-fill-controller;1", &kNS_FORMFILLCONTROLLER_CID },
+ { NS_FORMHISTORYAUTOCOMPLETE_CONTRACTID, &kNS_FORMFILLCONTROLLER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kSatchelModule = {
+ mozilla::Module::kVersion,
+ kSatchelCIDs,
+ kSatchelContracts
+};
+
+NSMODULE_DEFN(satchel) = &kSatchelModule;
diff --git a/components/satchel/nsFormFillController.h b/components/satchel/nsFormFillController.h
new file mode 100644
index 000000000..27fb1edbd
--- /dev/null
+++ b/components/satchel/nsFormFillController.h
@@ -0,0 +1,125 @@
+/* -*- 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 __nsFormFillController__
+#define __nsFormFillController__
+
+#include "nsIFormFillController.h"
+#include "nsIAutoCompleteInput.h"
+#include "nsIAutoCompleteSearch.h"
+#include "nsIAutoCompleteController.h"
+#include "nsIAutoCompletePopup.h"
+#include "nsIFormAutoComplete.h"
+#include "nsIDOMEventListener.h"
+#include "nsCOMPtr.h"
+#include "nsDataHashtable.h"
+#include "nsIDocShell.h"
+#include "nsIDOMHTMLInputElement.h"
+#include "nsILoginManager.h"
+#include "nsIMutationObserver.h"
+#include "nsTArray.h"
+#include "nsCycleCollectionParticipant.h"
+
+// X.h defines KeyPress
+#ifdef KeyPress
+#undef KeyPress
+#endif
+
+class nsFormHistory;
+class nsINode;
+class nsPIDOMWindowOuter;
+
+class nsFormFillController final : public nsIFormFillController,
+ public nsIAutoCompleteInput,
+ public nsIAutoCompleteSearch,
+ public nsIDOMEventListener,
+ public nsIFormAutoCompleteObserver,
+ public nsIMutationObserver
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSIFORMFILLCONTROLLER
+ NS_DECL_NSIAUTOCOMPLETESEARCH
+ NS_DECL_NSIAUTOCOMPLETEINPUT
+ NS_DECL_NSIFORMAUTOCOMPLETEOBSERVER
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIMUTATIONOBSERVER
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFormFillController, nsIFormFillController)
+
+ nsresult Focus(nsIDOMEvent* aEvent);
+ nsresult KeyPress(nsIDOMEvent* aKeyEvent);
+ nsresult MouseDown(nsIDOMEvent* aMouseEvent);
+
+ nsFormFillController();
+
+protected:
+ virtual ~nsFormFillController();
+
+ void AddWindowListeners(nsPIDOMWindowOuter* aWindow);
+ void RemoveWindowListeners(nsPIDOMWindowOuter* aWindow);
+
+ void AddKeyListener(nsINode* aInput);
+ void RemoveKeyListener();
+
+ void StartControllingInput(nsIDOMHTMLInputElement *aInput);
+ void StopControllingInput();
+ /**
+ * Checks that aElement is a type of element we want to fill, then calls
+ * StartControllingInput on it.
+ */
+ void MaybeStartControllingInput(nsIDOMHTMLInputElement* aElement);
+
+ nsresult PerformInputListAutoComplete(const nsAString& aSearch,
+ nsIAutoCompleteResult** aResult);
+
+ void RevalidateDataList();
+ bool RowMatch(nsFormHistory *aHistory, uint32_t aIndex, const nsAString &aInputName, const nsAString &aInputValue);
+
+ inline nsIDocShell *GetDocShellForInput(nsIDOMHTMLInputElement *aInput);
+ inline nsPIDOMWindowOuter *GetWindowForDocShell(nsIDocShell *aDocShell);
+ inline int32_t GetIndexOfDocShell(nsIDocShell *aDocShell);
+
+ void MaybeRemoveMutationObserver(nsINode* aNode);
+
+ void RemoveForDocument(nsIDocument* aDoc);
+ bool IsEventTrusted(nsIDOMEvent *aEvent);
+ // members //////////////////////////////////////////
+
+ nsCOMPtr<nsIAutoCompleteController> mController;
+ nsCOMPtr<nsILoginManager> mLoginManager;
+ nsIDOMHTMLInputElement* mFocusedInput;
+ nsINode* mFocusedInputNode;
+
+ // mListNode is a <datalist> element which, is set, has the form fill controller
+ // as a mutation observer for it.
+ nsINode* mListNode;
+ nsCOMPtr<nsIAutoCompletePopup> mFocusedPopup;
+
+ nsTArray<nsCOMPtr<nsIDocShell> > mDocShells;
+ nsTArray<nsCOMPtr<nsIAutoCompletePopup> > mPopups;
+
+ // The observer passed to StartSearch. It will be notified when the search is
+ // complete or the data from a datalist changes.
+ nsCOMPtr<nsIAutoCompleteObserver> mLastListener;
+
+ // This is cleared by StopSearch().
+ nsCOMPtr<nsIFormAutoComplete> mLastFormAutoComplete;
+ nsString mLastSearchString;
+
+ nsDataHashtable<nsPtrHashKey<const nsINode>, bool> mPwmgrInputs;
+
+ uint32_t mTimeout;
+ uint32_t mMinResultsForPopup;
+ uint32_t mMaxRows;
+ bool mContextMenuFiredBeforeFocus;
+ bool mDisableAutoComplete;
+ bool mCompleteDefaultIndex;
+ bool mCompleteSelectedIndex;
+ bool mForceComplete;
+ bool mSuppressOnInput;
+};
+
+#endif // __nsFormFillController__
diff --git a/components/satchel/nsFormHistory.js b/components/satchel/nsFormHistory.js
new file mode 100644
index 000000000..f940e104d
--- /dev/null
+++ b/components/satchel/nsFormHistory.js
@@ -0,0 +1,872 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+
+const DB_VERSION = 4;
+const DAY_IN_MS = 86400000; // 1 day in milliseconds
+
+function FormHistory() {
+ Deprecated.warning(
+ "nsIFormHistory2 is deprecated and will be removed in a future version",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=879118");
+ this.init();
+}
+
+FormHistory.prototype = {
+ classID : Components.ID("{0c1bb408-71a2-403f-854a-3a0659829ded}"),
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormHistory2,
+ Ci.nsIObserver,
+ Ci.nsIMessageListener,
+ Ci.nsISupportsWeakReference,
+ ]),
+
+ debug : true,
+ enabled : true,
+
+ // The current database schema.
+ dbSchema : {
+ tables : {
+ moz_formhistory: {
+ "id" : "INTEGER PRIMARY KEY",
+ "fieldname" : "TEXT NOT NULL",
+ "value" : "TEXT NOT NULL",
+ "timesUsed" : "INTEGER",
+ "firstUsed" : "INTEGER",
+ "lastUsed" : "INTEGER",
+ "guid" : "TEXT"
+ },
+ moz_deleted_formhistory: {
+ "id" : "INTEGER PRIMARY KEY",
+ "timeDeleted" : "INTEGER",
+ "guid" : "TEXT"
+ }
+ },
+ indices : {
+ moz_formhistory_index : {
+ table : "moz_formhistory",
+ columns : ["fieldname"]
+ },
+ moz_formhistory_lastused_index : {
+ table : "moz_formhistory",
+ columns : ["lastUsed"]
+ },
+ moz_formhistory_guid_index : {
+ table : "moz_formhistory",
+ columns : ["guid"]
+ },
+ }
+ },
+ dbStmts : null, // Database statements for memoization
+ dbFile : null,
+
+ _uuidService: null,
+ get uuidService() {
+ if (!this._uuidService)
+ this._uuidService = Cc["@mozilla.org/uuid-generator;1"].
+ getService(Ci.nsIUUIDGenerator);
+ return this._uuidService;
+ },
+
+ log : function log(message) {
+ if (!this.debug)
+ return;
+ dump("FormHistory: " + message + "\n");
+ Services.console.logStringMessage("FormHistory: " + message);
+ },
+
+
+ init : function init() {
+ this.updatePrefs();
+
+ this.dbStmts = {};
+
+ // Add observer
+ Services.obs.addObserver(this, "profile-before-change", true);
+ },
+
+ /* ---- nsIFormHistory2 interfaces ---- */
+
+
+ get hasEntries() {
+ return (this.countAllEntries() > 0);
+ },
+
+
+ addEntry : function addEntry(name, value) {
+ if (!this.enabled)
+ return;
+
+ this.log("addEntry for " + name + "=" + value);
+
+ let now = Date.now() * 1000; // microseconds
+
+ let [id, guid] = this.getExistingEntryID(name, value);
+ let stmt;
+
+ if (id != -1) {
+ // Update existing entry.
+ let query = "UPDATE moz_formhistory SET timesUsed = timesUsed + 1, lastUsed = :lastUsed WHERE id = :id";
+ let params = {
+ lastUsed : now,
+ id : id
+ };
+
+ try {
+ stmt = this.dbCreateStatement(query, params);
+ stmt.execute();
+ this.sendStringNotification("modifyEntry", name, value, guid);
+ } catch (e) {
+ this.log("addEntry (modify) failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+
+ } else {
+ // Add new entry.
+ guid = this.generateGUID();
+
+ let query = "INSERT INTO moz_formhistory (fieldname, value, timesUsed, firstUsed, lastUsed, guid) " +
+ "VALUES (:fieldname, :value, :timesUsed, :firstUsed, :lastUsed, :guid)";
+ let params = {
+ fieldname : name,
+ value : value,
+ timesUsed : 1,
+ firstUsed : now,
+ lastUsed : now,
+ guid : guid
+ };
+
+ try {
+ stmt = this.dbCreateStatement(query, params);
+ stmt.execute();
+ this.sendStringNotification("addEntry", name, value, guid);
+ } catch (e) {
+ this.log("addEntry (create) failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ }
+ },
+
+
+ removeEntry : function removeEntry(name, value) {
+ this.log("removeEntry for " + name + "=" + value);
+
+ let [id, guid] = this.getExistingEntryID(name, value);
+ this.sendStringNotification("before-removeEntry", name, value, guid);
+
+ let stmt;
+ let query = "DELETE FROM moz_formhistory WHERE id = :id";
+ let params = { id : id };
+ let existingTransactionInProgress;
+
+ try {
+ // Don't start a transaction if one is already in progress since we can't nest them.
+ existingTransactionInProgress = this.dbConnection.transactionInProgress;
+ if (!existingTransactionInProgress)
+ this.dbConnection.beginTransaction();
+ this.moveToDeletedTable("VALUES (:guid, :timeDeleted)", {
+ guid: guid,
+ timeDeleted: Date.now()
+ });
+
+ // remove from the formhistory database
+ stmt = this.dbCreateStatement(query, params);
+ stmt.execute();
+ this.sendStringNotification("removeEntry", name, value, guid);
+ } catch (e) {
+ if (!existingTransactionInProgress)
+ this.dbConnection.rollbackTransaction();
+ this.log("removeEntry failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ if (!existingTransactionInProgress)
+ this.dbConnection.commitTransaction();
+ },
+
+
+ removeEntriesForName : function removeEntriesForName(name) {
+ this.log("removeEntriesForName with name=" + name);
+
+ this.sendStringNotification("before-removeEntriesForName", name);
+
+ let stmt;
+ let query = "DELETE FROM moz_formhistory WHERE fieldname = :fieldname";
+ let params = { fieldname : name };
+ let existingTransactionInProgress;
+
+ try {
+ // Don't start a transaction if one is already in progress since we can't nest them.
+ existingTransactionInProgress = this.dbConnection.transactionInProgress;
+ if (!existingTransactionInProgress)
+ this.dbConnection.beginTransaction();
+ this.moveToDeletedTable(
+ "SELECT guid, :timeDeleted FROM moz_formhistory " +
+ "WHERE fieldname = :fieldname", {
+ fieldname: name,
+ timeDeleted: Date.now()
+ });
+
+ stmt = this.dbCreateStatement(query, params);
+ stmt.execute();
+ this.sendStringNotification("removeEntriesForName", name);
+ } catch (e) {
+ if (!existingTransactionInProgress)
+ this.dbConnection.rollbackTransaction();
+ this.log("removeEntriesForName failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ if (!existingTransactionInProgress)
+ this.dbConnection.commitTransaction();
+ },
+
+
+ removeAllEntries : function removeAllEntries() {
+ this.log("removeAllEntries");
+
+ this.sendNotification("before-removeAllEntries", null);
+
+ let stmt;
+ let query = "DELETE FROM moz_formhistory";
+ let existingTransactionInProgress;
+
+ try {
+ // Don't start a transaction if one is already in progress since we can't nest them.
+ existingTransactionInProgress = this.dbConnection.transactionInProgress;
+ if (!existingTransactionInProgress)
+ this.dbConnection.beginTransaction();
+ // TODO: Add these items to the deleted items table once we've sorted
+ // out the issues from bug 756701
+ stmt = this.dbCreateStatement(query);
+ stmt.execute();
+ this.sendNotification("removeAllEntries", null);
+ } catch (e) {
+ if (!existingTransactionInProgress)
+ this.dbConnection.rollbackTransaction();
+ this.log("removeAllEntries failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ if (!existingTransactionInProgress)
+ this.dbConnection.commitTransaction();
+ },
+
+
+ nameExists : function nameExists(name) {
+ this.log("nameExists for name=" + name);
+ let stmt;
+ let query = "SELECT COUNT(1) AS numEntries FROM moz_formhistory WHERE fieldname = :fieldname";
+ let params = { fieldname : name };
+ try {
+ stmt = this.dbCreateStatement(query, params);
+ stmt.executeStep();
+ return (stmt.row.numEntries > 0);
+ } catch (e) {
+ this.log("nameExists failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ },
+
+ entryExists : function entryExists(name, value) {
+ this.log("entryExists for " + name + "=" + value);
+ let [id] = this.getExistingEntryID(name, value);
+ this.log("entryExists: id=" + id);
+ return (id != -1);
+ },
+
+ removeEntriesByTimeframe : function removeEntriesByTimeframe(beginTime, endTime) {
+ this.log("removeEntriesByTimeframe for " + beginTime + " to " + endTime);
+
+ this.sendIntNotification("before-removeEntriesByTimeframe", beginTime, endTime);
+
+ let stmt;
+ let query = "DELETE FROM moz_formhistory WHERE firstUsed >= :beginTime AND firstUsed <= :endTime";
+ let params = {
+ beginTime : beginTime,
+ endTime : endTime
+ };
+ let existingTransactionInProgress;
+
+ try {
+ // Don't start a transaction if one is already in progress since we can't nest them.
+ existingTransactionInProgress = this.dbConnection.transactionInProgress;
+ if (!existingTransactionInProgress)
+ this.dbConnection.beginTransaction();
+ this.moveToDeletedTable(
+ "SELECT guid, :timeDeleted FROM moz_formhistory " +
+ "WHERE firstUsed >= :beginTime AND firstUsed <= :endTime", {
+ beginTime: beginTime,
+ endTime: endTime
+ });
+
+ stmt = this.dbCreateStatement(query, params);
+ stmt.executeStep();
+ this.sendIntNotification("removeEntriesByTimeframe", beginTime, endTime);
+ } catch (e) {
+ if (!existingTransactionInProgress)
+ this.dbConnection.rollbackTransaction();
+ this.log("removeEntriesByTimeframe failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ if (!existingTransactionInProgress)
+ this.dbConnection.commitTransaction();
+ },
+
+ moveToDeletedTable : function moveToDeletedTable(values, params) {
+ },
+
+ get dbConnection() {
+ // Make sure dbConnection can't be called from now to prevent infinite loops.
+ delete FormHistory.prototype.dbConnection;
+
+ try {
+ this.dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
+ this.dbFile.append("formhistory.sqlite");
+ this.log("Opening database at " + this.dbFile.path);
+
+ FormHistory.prototype.dbConnection = this.dbOpen();
+ this.dbInit();
+ } catch (e) {
+ this.log("Initialization failed: " + e);
+ // If dbInit fails...
+ if (e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
+ this.dbCleanup();
+ FormHistory.prototype.dbConnection = this.dbOpen();
+ this.dbInit();
+ } else {
+ throw "Initialization failed";
+ }
+ }
+
+ return FormHistory.prototype.dbConnection;
+ },
+
+ get DBConnection() {
+ return this.dbConnection;
+ },
+
+
+ /* ---- nsIObserver interface ---- */
+
+
+ observe : function observe(subject, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ this.updatePrefs();
+ break;
+ case "profile-before-change":
+ this._dbClose(false);
+ break;
+ default:
+ this.log("Oops! Unexpected notification: " + topic);
+ break;
+ }
+ },
+
+
+ /* ---- helpers ---- */
+
+
+ generateGUID : function() {
+ // string like: "{f60d9eac-9421-4abc-8491-8e8322b063d4}"
+ let uuid = this.uuidService.generateUUID().toString();
+ let raw = ""; // A string with the low bytes set to random values
+ let bytes = 0;
+ for (let i = 1; bytes < 12 ; i+= 2) {
+ // Skip dashes
+ if (uuid[i] == "-")
+ i++;
+ let hexVal = parseInt(uuid[i] + uuid[i + 1], 16);
+ raw += String.fromCharCode(hexVal);
+ bytes++;
+ }
+ return btoa(raw);
+ },
+
+
+ sendStringNotification : function (changeType, str1, str2, str3) {
+ function wrapit(str) {
+ let wrapper = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ wrapper.data = str;
+ return wrapper;
+ }
+
+ let strData;
+ if (arguments.length == 2) {
+ // Just 1 string, no need to put it in an array
+ strData = wrapit(str1);
+ } else {
+ // 3 strings, put them in an array.
+ strData = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ strData.appendElement(wrapit(str1), false);
+ strData.appendElement(wrapit(str2), false);
+ strData.appendElement(wrapit(str3), false);
+ }
+ this.sendNotification(changeType, strData);
+ },
+
+
+ sendIntNotification : function (changeType, int1, int2) {
+ function wrapit(int) {
+ let wrapper = Cc["@mozilla.org/supports-PRInt64;1"].
+ createInstance(Ci.nsISupportsPRInt64);
+ wrapper.data = int;
+ return wrapper;
+ }
+
+ let intData;
+ if (arguments.length == 2) {
+ // Just 1 int, no need for an array
+ intData = wrapit(int1);
+ } else {
+ // 2 ints, put them in an array.
+ intData = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ intData.appendElement(wrapit(int1), false);
+ intData.appendElement(wrapit(int2), false);
+ }
+ this.sendNotification(changeType, intData);
+ },
+
+
+ sendNotification : function (changeType, data) {
+ Services.obs.notifyObservers(data, "satchel-storage-changed", changeType);
+ },
+
+
+ getExistingEntryID : function (name, value) {
+ let id = -1, guid = null;
+ let stmt;
+ let query = "SELECT id, guid FROM moz_formhistory WHERE fieldname = :fieldname AND value = :value";
+ let params = {
+ fieldname : name,
+ value : value
+ };
+ try {
+ stmt = this.dbCreateStatement(query, params);
+ if (stmt.executeStep()) {
+ id = stmt.row.id;
+ guid = stmt.row.guid;
+ }
+ } catch (e) {
+ this.log("getExistingEntryID failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+
+ return [id, guid];
+ },
+
+
+ countAllEntries : function () {
+ let query = "SELECT COUNT(1) AS numEntries FROM moz_formhistory";
+
+ let stmt, numEntries;
+ try {
+ stmt = this.dbCreateStatement(query, null);
+ stmt.executeStep();
+ numEntries = stmt.row.numEntries;
+ } catch (e) {
+ this.log("countAllEntries failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+
+ this.log("countAllEntries: counted entries: " + numEntries);
+ return numEntries;
+ },
+
+
+ updatePrefs : function () {
+ this.debug = Services.prefs.getBoolPref("browser.formfill.debug");
+ this.enabled = Services.prefs.getBoolPref("browser.formfill.enable");
+ },
+
+ // Database Creation & Access
+
+ /*
+ * dbCreateStatement
+ *
+ * Creates a statement, wraps it, and then does parameter replacement
+ * Will use memoization so that statements can be reused.
+ */
+ dbCreateStatement : function (query, params) {
+ let stmt = this.dbStmts[query];
+ // Memoize the statements
+ if (!stmt) {
+ this.log("Creating new statement for query: " + query);
+ stmt = this.dbConnection.createStatement(query);
+ this.dbStmts[query] = stmt;
+ }
+ // Replace parameters, must be done 1 at a time
+ if (params)
+ for (let i in params)
+ stmt.params[i] = params[i];
+ return stmt;
+ },
+
+ /*
+ * dbOpen
+ *
+ * Open a connection with the database and returns it.
+ *
+ * @returns a db connection object.
+ */
+ dbOpen : function () {
+ this.log("Open Database");
+
+ let storage = Cc["@mozilla.org/storage/service;1"].
+ getService(Ci.mozIStorageService);
+ return storage.openDatabase(this.dbFile);
+ },
+
+ /*
+ * dbInit
+ *
+ * Attempts to initialize the database. This creates the file if it doesn't
+ * exist, performs any migrations, etc.
+ */
+ dbInit : function () {
+ this.log("Initializing Database");
+
+ let version = this.dbConnection.schemaVersion;
+
+ // Note: Firefox 3 didn't set a schema value, so it started from 0.
+ // So we can't depend on a simple version == 0 check
+ if (version == 0 && !this.dbConnection.tableExists("moz_formhistory"))
+ this.dbCreate();
+ else if (version != DB_VERSION)
+ this.dbMigrate(version);
+ },
+
+
+ dbCreate: function () {
+ this.log("Creating DB -- tables");
+ for (let name in this.dbSchema.tables) {
+ let table = this.dbSchema.tables[name];
+ this.dbCreateTable(name, table);
+ }
+
+ this.log("Creating DB -- indices");
+ for (let name in this.dbSchema.indices) {
+ let index = this.dbSchema.indices[name];
+ let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
+ "(" + index.columns.join(", ") + ")";
+ this.dbConnection.executeSimpleSQL(statement);
+ }
+
+ this.dbConnection.schemaVersion = DB_VERSION;
+ },
+
+ dbCreateTable: function(name, table) {
+ let tSQL = Object.keys(table).map(col => [col, table[col]].join(" ")).join(", ");
+ this.log("Creating table " + name + " with " + tSQL);
+ this.dbConnection.createTable(name, tSQL);
+ },
+
+ dbMigrate : function (oldVersion) {
+ this.log("Attempting to migrate from version " + oldVersion);
+
+ if (oldVersion > DB_VERSION) {
+ this.log("Downgrading to version " + DB_VERSION);
+ // User's DB is newer. Sanity check that our expected columns are
+ // present, and if so mark the lower version and merrily continue
+ // on. If the columns are borked, something is wrong so blow away
+ // the DB and start from scratch. [Future incompatible upgrades
+ // should swtich to a different table or file.]
+
+ if (!this.dbAreExpectedColumnsPresent())
+ throw Components.Exception("DB is missing expected columns",
+ Cr.NS_ERROR_FILE_CORRUPTED);
+
+ // Change the stored version to the current version. If the user
+ // runs the newer code again, it will see the lower version number
+ // and re-upgrade (to fixup any entries the old code added).
+ this.dbConnection.schemaVersion = DB_VERSION;
+ return;
+ }
+
+ // Upgrade to newer version...
+
+ this.dbConnection.beginTransaction();
+
+ try {
+ for (let v = oldVersion + 1; v <= DB_VERSION; v++) {
+ this.log("Upgrading to version " + v + "...");
+ let migrateFunction = "dbMigrateToVersion" + v;
+ this[migrateFunction]();
+ }
+ } catch (e) {
+ this.log("Migration failed: " + e);
+ this.dbConnection.rollbackTransaction();
+ throw e;
+ }
+
+ this.dbConnection.schemaVersion = DB_VERSION;
+ this.dbConnection.commitTransaction();
+ this.log("DB migration completed.");
+ },
+
+
+ /*
+ * dbMigrateToVersion1
+ *
+ * Updates the DB schema to v1 (bug 463154).
+ * Adds firstUsed, lastUsed, timesUsed columns.
+ */
+ dbMigrateToVersion1 : function () {
+ // Check to see if the new columns already exist (could be a v1 DB that
+ // was downgraded to v0). If they exist, we don't need to add them.
+ let query;
+ ["timesUsed", "firstUsed", "lastUsed"].forEach(function(column) {
+ if (!this.dbColumnExists(column)) {
+ query = "ALTER TABLE moz_formhistory ADD COLUMN " + column + " INTEGER";
+ this.dbConnection.executeSimpleSQL(query);
+ }
+ }, this);
+
+ // Set the default values for the new columns.
+ //
+ // Note that we set the timestamps to 24 hours in the past. We want a
+ // timestamp that's recent (so that "keep form history for 90 days"
+ // doesn't expire things surprisingly soon), but not so recent that
+ // "forget the last hour of stuff" deletes all freshly migrated data.
+ let stmt;
+ query = "UPDATE moz_formhistory " +
+ "SET timesUsed = 1, firstUsed = :time, lastUsed = :time " +
+ "WHERE timesUsed isnull OR firstUsed isnull or lastUsed isnull";
+ let params = { time: (Date.now() - DAY_IN_MS) * 1000 }
+ try {
+ stmt = this.dbCreateStatement(query, params);
+ stmt.execute();
+ } catch (e) {
+ this.log("Failed setting timestamps: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ },
+
+
+ /*
+ * dbMigrateToVersion2
+ *
+ * Updates the DB schema to v2 (bug 243136).
+ * Adds lastUsed index, removes moz_dummy_table
+ */
+ dbMigrateToVersion2 : function () {
+ let query = "DROP TABLE IF EXISTS moz_dummy_table";
+ this.dbConnection.executeSimpleSQL(query);
+
+ query = "CREATE INDEX IF NOT EXISTS moz_formhistory_lastused_index ON moz_formhistory (lastUsed)";
+ this.dbConnection.executeSimpleSQL(query);
+ },
+
+
+ /*
+ * dbMigrateToVersion3
+ *
+ * Updates the DB schema to v3 (bug 506402).
+ * Adds guid column and index.
+ */
+ dbMigrateToVersion3 : function () {
+ // Check to see if GUID column already exists, add if needed
+ let query;
+ if (!this.dbColumnExists("guid")) {
+ query = "ALTER TABLE moz_formhistory ADD COLUMN guid TEXT";
+ this.dbConnection.executeSimpleSQL(query);
+
+ query = "CREATE INDEX IF NOT EXISTS moz_formhistory_guid_index ON moz_formhistory (guid)";
+ this.dbConnection.executeSimpleSQL(query);
+ }
+
+ // Get a list of IDs for existing logins
+ let ids = [];
+ query = "SELECT id FROM moz_formhistory WHERE guid isnull";
+ let stmt;
+ try {
+ stmt = this.dbCreateStatement(query);
+ while (stmt.executeStep())
+ ids.push(stmt.row.id);
+ } catch (e) {
+ this.log("Failed getting IDs: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+
+ // Generate a GUID for each login and update the DB.
+ query = "UPDATE moz_formhistory SET guid = :guid WHERE id = :id";
+ for (let id of ids) {
+ let params = {
+ id : id,
+ guid : this.generateGUID()
+ };
+
+ try {
+ stmt = this.dbCreateStatement(query, params);
+ stmt.execute();
+ } catch (e) {
+ this.log("Failed setting GUID: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ }
+ },
+
+ dbMigrateToVersion4 : function () {
+ if (!this.dbConnection.tableExists("moz_deleted_formhistory")) {
+ this.dbCreateTable("moz_deleted_formhistory", this.dbSchema.tables.moz_deleted_formhistory);
+ }
+ },
+
+ /*
+ * dbAreExpectedColumnsPresent
+ *
+ * Sanity check to ensure that the columns this version of the code expects
+ * are present in the DB we're using.
+ */
+ dbAreExpectedColumnsPresent : function () {
+ for (let name in this.dbSchema.tables) {
+ let table = this.dbSchema.tables[name];
+ let query = "SELECT " +
+ Object.keys(table).join(", ") +
+ " FROM " + name;
+ try {
+ let stmt = this.dbConnection.createStatement(query);
+ // (no need to execute statement, if it compiled we're good)
+ stmt.finalize();
+ } catch (e) {
+ return false;
+ }
+ }
+
+ this.log("verified that expected columns are present in DB.");
+ return true;
+ },
+
+
+ /*
+ * dbColumnExists
+ *
+ * Checks to see if the named column already exists.
+ */
+ dbColumnExists : function (columnName) {
+ let query = "SELECT " + columnName + " FROM moz_formhistory";
+ try {
+ let stmt = this.dbConnection.createStatement(query);
+ // (no need to execute statement, if it compiled we're good)
+ stmt.finalize();
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+
+ /**
+ * _dbClose
+ *
+ * Finalize all statements and close the connection.
+ *
+ * @param aBlocking - Should we spin the loop waiting for the db to be
+ * closed.
+ */
+ _dbClose : function FH__dbClose(aBlocking) {
+ for (let query in this.dbStmts) {
+ let stmt = this.dbStmts[query];
+ stmt.finalize();
+ }
+ this.dbStmts = {};
+
+ let connectionDescriptor = Object.getOwnPropertyDescriptor(FormHistory.prototype, "dbConnection");
+ // Return if the database hasn't been opened.
+ if (!connectionDescriptor || connectionDescriptor.value === undefined)
+ return;
+
+ let completed = false;
+ try {
+ this.dbConnection.asyncClose(function () { completed = true; });
+ } catch (e) {
+ completed = true;
+ Components.utils.reportError(e);
+ }
+
+ let thread = Services.tm.currentThread;
+ while (aBlocking && !completed) {
+ thread.processNextEvent(true);
+ }
+ },
+
+ /*
+ * dbCleanup
+ *
+ * Called when database creation fails. Finalizes database statements,
+ * closes the database connection, deletes the database file.
+ */
+ dbCleanup : function () {
+ this.log("Cleaning up DB file - close & remove & backup")
+
+ // Create backup file
+ let storage = Cc["@mozilla.org/storage/service;1"].
+ getService(Ci.mozIStorageService);
+ let backupFile = this.dbFile.leafName + ".corrupt";
+ storage.backupDatabaseFile(this.dbFile, backupFile);
+
+ this._dbClose(true);
+ this.dbFile.remove(false);
+ }
+};
+
+var component = [FormHistory];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
diff --git a/components/satchel/nsIFormAutoComplete.idl b/components/satchel/nsIFormAutoComplete.idl
new file mode 100644
index 000000000..6ce8563be
--- /dev/null
+++ b/components/satchel/nsIFormAutoComplete.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 nsIAutoCompleteResult;
+interface nsIFormAutoCompleteObserver;
+interface nsIDOMHTMLInputElement;
+
+[scriptable, uuid(bfd9b82b-0ab3-4b6b-9e54-aa961ff4b732)]
+interface nsIFormAutoComplete: nsISupports {
+ /**
+ * Generate results for a form input autocomplete menu asynchronously.
+ */
+ void autoCompleteSearchAsync(in AString aInputName,
+ in AString aSearchString,
+ in nsIDOMHTMLInputElement aField,
+ in nsIAutoCompleteResult aPreviousResult,
+ in nsIAutoCompleteResult aDatalistResult,
+ in nsIFormAutoCompleteObserver aListener);
+
+ /**
+ * If a search is in progress, stop it. Otherwise, do nothing. This is used
+ * to cancel an existing search, for example, in preparation for a new search.
+ */
+ void stopAutoCompleteSearch();
+
+ /**
+ * Since the controller is disconnecting, any related data must be cleared.
+ */
+ void stopControllingInput(in nsIDOMHTMLInputElement aField);
+};
+
+[scriptable, function, uuid(604419ab-55a0-4831-9eca-1b9e67cc4751)]
+interface nsIFormAutoCompleteObserver : nsISupports
+{
+ /*
+ * Called when a search is complete and the results are ready even if the
+ * result set is empty. If the search is cancelled or a new search is
+ * started, this is not called.
+ *
+ * @param result - The search result object
+ */
+ void onSearchCompletion(in nsIAutoCompleteResult result);
+};
diff --git a/components/satchel/nsIFormFillController.idl b/components/satchel/nsIFormFillController.idl
new file mode 100644
index 000000000..34104c91f
--- /dev/null
+++ b/components/satchel/nsIFormFillController.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIDocShell;
+interface nsIAutoCompletePopup;
+interface nsIDOMHTMLInputElement;
+
+/*
+ * nsIFormFillController is an interface for controlling form fill behavior
+ * on HTML documents. Any number of docShells can be controller concurrently.
+ * While a docShell is attached, all HTML documents that are loaded within it
+ * will have a focus listener attached that will listen for when a text input
+ * is focused. When this happens, the input will be bound to the
+ * global nsIAutoCompleteController service.
+ */
+
+[scriptable, uuid(07f0a0dc-f6e9-4cdd-a55f-56d770523a4c)]
+interface nsIFormFillController : nsISupports
+{
+ /*
+ * The input element the form fill controller is currently bound to.
+ */
+ readonly attribute nsIDOMHTMLInputElement focusedInput;
+
+ /*
+ * Start controlling form fill behavior for the given browser
+ *
+ * @param docShell - The docShell to attach to
+ * @param popup - The popup to show when autocomplete results are available
+ */
+ void attachToBrowser(in nsIDocShell docShell, in nsIAutoCompletePopup popup);
+
+ /*
+ * Stop controlling form fill behavior for the given browser
+ *
+ * @param docShell - The docShell to detach from
+ */
+ void detachFromBrowser(in nsIDocShell docShell);
+
+ /*
+ * Mark the specified <input> element as being managed by password manager.
+ * Autocomplete requests will be handed off to the password manager, and will
+ * not be stored in form history.
+ *
+ * @param aInput - The HTML <input> element to tag
+ */
+ void markAsLoginManagerField(in nsIDOMHTMLInputElement aInput);
+
+ /*
+ * Open the autocomplete popup, if possible.
+ */
+ void showPopup();
+};
diff --git a/components/satchel/nsIFormHistory.idl b/components/satchel/nsIFormHistory.idl
new file mode 100644
index 000000000..ac78451e9
--- /dev/null
+++ b/components/satchel/nsIFormHistory.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+interface nsIFile;
+interface mozIStorageConnection;
+
+/**
+ * The nsIFormHistory object is a service which holds a set of name/value
+ * pairs. The names correspond to form field names, and the values correspond
+ * to values the user has submitted. So, several values may exist for a single
+ * name.
+ *
+ * Note: this interface provides no means to access stored values.
+ * Stored values are used by the FormFillController to generate
+ * autocomplete matches.
+ *
+ * @deprecated use FormHistory.jsm instead.
+ */
+
+[scriptable, uuid(5d7d84d1-9798-4016-bf61-a32acf09b29d)]
+interface nsIFormHistory2 : nsISupports
+{
+ /**
+ * Returns true if the form history has any entries.
+ */
+ readonly attribute boolean hasEntries;
+
+ /**
+ * Adds a name and value pair to the form history.
+ */
+ void addEntry(in AString name, in AString value);
+
+ /**
+ * Removes a name and value pair from the form history.
+ */
+ void removeEntry(in AString name, in AString value);
+
+ /**
+ * Removes all entries that are paired with a name.
+ */
+ void removeEntriesForName(in AString name);
+
+ /**
+ * Removes all entries in the entire form history.
+ */
+ void removeAllEntries();
+
+ /**
+ * Returns true if there is no entry that is paired with a name.
+ */
+ boolean nameExists(in AString name);
+
+ /**
+ * Gets whether a name and value pair exists in the form history.
+ */
+ boolean entryExists(in AString name, in AString value);
+
+ /**
+ * Removes entries that were created between the specified times.
+ *
+ * @param aBeginTime
+ * The beginning of the timeframe, in microseconds
+ * @param aEndTime
+ * The end of the timeframe, in microseconds
+ */
+ void removeEntriesByTimeframe(in long long aBeginTime, in long long aEndTime);
+
+ /**
+ * Returns the underlying DB connection the form history module is using.
+ */
+ readonly attribute mozIStorageConnection DBConnection;
+};
diff --git a/components/satchel/nsIInputListAutoComplete.idl b/components/satchel/nsIInputListAutoComplete.idl
new file mode 100644
index 000000000..6f0f492b0
--- /dev/null
+++ b/components/satchel/nsIInputListAutoComplete.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIAutoCompleteResult;
+interface nsIDOMHTMLInputElement;
+
+[scriptable, uuid(0e33de3e-4faf-4a1a-b96e-24115b8bfd45)]
+interface nsIInputListAutoComplete: nsISupports {
+ /**
+ * Generate results for a form input autocomplete menu.
+ */
+ nsIAutoCompleteResult autoCompleteSearch(in AString aSearchString,
+ in nsIDOMHTMLInputElement aField);
+};
diff --git a/components/satchel/nsInputListAutoComplete.js b/components/satchel/nsInputListAutoComplete.js
new file mode 100644
index 000000000..f42427862
--- /dev/null
+++ b/components/satchel/nsInputListAutoComplete.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/nsFormAutoCompleteResult.jsm");
+
+function InputListAutoComplete() {}
+
+InputListAutoComplete.prototype = {
+ classID : Components.ID("{bf1e01d0-953e-11df-981c-0800200c9a66}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIInputListAutoComplete]),
+
+ autoCompleteSearch : function (aUntrimmedSearchString, aField) {
+ let [values, labels] = this.getListSuggestions(aField);
+ let searchResult = values.length > 0 ? Ci.nsIAutoCompleteResult.RESULT_SUCCESS
+ : Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
+ let defaultIndex = values.length > 0 ? 0 : -1;
+ return new FormAutoCompleteResult(aUntrimmedSearchString,
+ searchResult, defaultIndex, "",
+ values, labels, [], null);
+ },
+
+ getListSuggestions : function (aField) {
+ let values = [];
+ let labels = [];
+
+ if (aField) {
+ let filter = !aField.hasAttribute("mozNoFilter");
+ let lowerFieldValue = aField.value.toLowerCase();
+
+ if (aField.list) {
+ let options = aField.list.options;
+ let length = options.length;
+ for (let i = 0; i < length; i++) {
+ let item = options.item(i);
+ let label = "";
+ if (item.label) {
+ label = item.label;
+ } else if (item.text) {
+ label = item.text;
+ } else {
+ label = item.value;
+ }
+
+ if (filter && label.toLowerCase().indexOf(lowerFieldValue) == -1) {
+ continue;
+ }
+
+ labels.push(label);
+ values.push(item.value);
+ }
+ }
+ }
+
+ return [values, labels];
+ }
+};
+
+var component = [InputListAutoComplete];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
diff --git a/components/satchel/satchel.manifest b/components/satchel/satchel.manifest
new file mode 100644
index 000000000..5afc0a38c
--- /dev/null
+++ b/components/satchel/satchel.manifest
@@ -0,0 +1,10 @@
+component {0c1bb408-71a2-403f-854a-3a0659829ded} nsFormHistory.js
+contract @mozilla.org/satchel/form-history;1 {0c1bb408-71a2-403f-854a-3a0659829ded}
+component {c11c21b2-71c9-4f87-a0f8-5e13f50495fd} nsFormAutoComplete.js
+contract @mozilla.org/satchel/form-autocomplete;1 {c11c21b2-71c9-4f87-a0f8-5e13f50495fd}
+component {bf1e01d0-953e-11df-981c-0800200c9a66} nsInputListAutoComplete.js
+contract @mozilla.org/satchel/inputlist-autocomplete;1 {bf1e01d0-953e-11df-981c-0800200c9a66}
+component {3a0012eb-007f-4bb8-aa81-a07385f77a25} FormHistoryStartup.js
+contract @mozilla.org/satchel/form-history-startup;1 {3a0012eb-007f-4bb8-aa81-a07385f77a25}
+category profile-after-change formHistoryStartup @mozilla.org/satchel/form-history-startup;1
+category idle-daily formHistoryStartup @mozilla.org/satchel/form-history-startup;1
diff --git a/components/satchel/towel b/components/satchel/towel
new file mode 100644
index 000000000..c26c7a8b2
--- /dev/null
+++ b/components/satchel/towel
@@ -0,0 +1,5 @@
+"Any man who can hitch the length and breadth of the galaxy, rough it,
+slum it, struggle against terrible odds, win through, and still knows
+where his towel is is clearly a man to be reckoned with."
+
+ - Douglas Adams
diff --git a/components/scache/moz.build b/components/scache/moz.build
new file mode 100644
index 000000000..c451588bb
--- /dev/null
+++ b/components/scache/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['public/nsIStartupCache.idl']
+
+EXPORTS.mozilla.scache += [
+ 'src/StartupCache.h',
+ 'src/StartupCacheUtils.h',
+]
+
+SOURCES += [
+ 'src/StartupCache.cpp',
+ 'src/StartupCacheModule.cpp',
+ 'src/StartupCacheUtils.cpp',
+]
+
+XPIDL_MODULE = 'startupcache'
+FINAL_LIBRARY = 'xul'
diff --git a/components/scache/public/nsIStartupCache.idl b/components/scache/public/nsIStartupCache.idl
new file mode 100644
index 000000000..0b629f261
--- /dev/null
+++ b/components/scache/public/nsIStartupCache.idl
@@ -0,0 +1,61 @@
+/* -*- Mode: IDL; 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 "nsIInputStream.idl"
+#include "nsISupports.idl"
+#include "nsIObserver.idl"
+#include "nsIObjectOutputStream.idl"
+
+%{C++
+#include "mozilla/UniquePtr.h"
+%}
+
+[uuid(25957820-90a1-428c-8739-b0845d3cc534)]
+interface nsIStartupCache : nsISupports
+{
+
+ /** This interface is provided for testing purposes only, basically
+ * just to solve link vagaries. See docs in StartupCache.h
+ * GetBuffer, PutBuffer, and InvalidateCache act as described
+ * in that file. */
+
+ uint32_t getBuffer(in string aID, out charPtr aBuffer);
+%{C++
+ /* A more convenient interface for using from C++. */
+ nsresult GetBuffer(const char* id, mozilla::UniquePtr<char[]>* outbuf, uint32_t* length)
+ {
+ char* buf;
+ nsresult rv = GetBuffer(id, &buf, length);
+ NS_ENSURE_SUCCESS(rv, rv);
+ outbuf->reset(buf);
+ return rv;
+ }
+%}
+
+ void putBuffer(in string aID, in string aBuffer,
+ in uint32_t aLength);
+
+ void invalidateCache();
+
+ void ignoreDiskCache();
+
+ /** In debug builds, wraps this object output stream with a stream that will
+ * detect and prevent the write of a multiply-referenced non-singleton object
+ * during serialization. In non-debug, returns an add-ref'd pointer to
+ * original stream, unwrapped. */
+ nsIObjectOutputStream getDebugObjectOutputStream(in nsIObjectOutputStream aStream);
+
+ /* Allows clients to check whether the one-time writeout after startup
+ * has finished yet, and also to set this variable as needed (so test
+ * code can fire mulitple startup writes if needed).
+ */
+ boolean startupWriteComplete();
+ void resetStartupWriteTimer();
+
+ /* Allows clients to simulate the behavior of ObserverService. */
+ readonly attribute nsIObserver observer;
+};
+
diff --git a/components/scache/src/StartupCache.cpp b/components/scache/src/StartupCache.cpp
new file mode 100644
index 000000000..4eec8daed
--- /dev/null
+++ b/components/scache/src/StartupCache.cpp
@@ -0,0 +1,795 @@
+/* -*- 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 "prio.h"
+#include "PLDHashTable.h"
+#include "nsXPCOMStrings.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/scache/StartupCache.h"
+
+#include "nsAutoPtr.h"
+#include "nsClassHashtable.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIClassInfo.h"
+#include "nsIFile.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIOutputStream.h"
+#include "nsIStartupCache.h"
+#include "nsIStorageStream.h"
+#include "nsIStreamBufferAccess.h"
+#include "nsIStringStream.h"
+#include "nsISupports.h"
+#include "nsITimer.h"
+#include "nsIZipWriter.h"
+#include "nsIZipReader.h"
+#include "nsWeakReference.h"
+#include "nsZipArchive.h"
+#include "mozilla/Omnijar.h"
+#include "prenv.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "nsIProtocolHandler.h"
+
+#include "GeckoProfiler.h"
+
+#ifdef IS_BIG_ENDIAN
+#define SC_ENDIAN "big"
+#else
+#define SC_ENDIAN "little"
+#endif
+
+#if PR_BYTES_PER_WORD == 4
+#define SC_WORDSIZE "4"
+#else
+#define SC_WORDSIZE "8"
+#endif
+
+namespace mozilla {
+namespace scache {
+
+MOZ_DEFINE_MALLOC_SIZE_OF(StartupCacheMallocSizeOf)
+
+NS_IMETHODIMP
+StartupCache::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize)
+{
+ MOZ_COLLECT_REPORT(
+ "explicit/startup-cache/mapping", KIND_NONHEAP, UNITS_BYTES,
+ SizeOfMapping(),
+ "Memory used to hold the mapping of the startup cache from file. "
+ "This memory is likely to be swapped out shortly after start-up.");
+
+ MOZ_COLLECT_REPORT(
+ "explicit/startup-cache/data", KIND_HEAP, UNITS_BYTES,
+ HeapSizeOfIncludingThis(StartupCacheMallocSizeOf),
+ "Memory used by the startup cache for things other than the file mapping.");
+
+ return NS_OK;
+}
+
+#define STARTUP_CACHE_NAME "startupCache." SC_WORDSIZE "." SC_ENDIAN
+
+StartupCache*
+StartupCache::GetSingleton()
+{
+ if (!gStartupCache) {
+ if (!XRE_IsParentProcess()) {
+ return nullptr;
+ }
+#ifdef MOZ_DISABLE_STARTUPCACHE
+ return nullptr;
+#else
+ StartupCache::InitSingleton();
+#endif
+ }
+
+ return StartupCache::gStartupCache;
+}
+
+void
+StartupCache::DeleteSingleton()
+{
+ StartupCache::gStartupCache = nullptr;
+}
+
+nsresult
+StartupCache::InitSingleton()
+{
+ nsresult rv;
+ StartupCache::gStartupCache = new StartupCache();
+
+ rv = StartupCache::gStartupCache->Init();
+ if (NS_FAILED(rv)) {
+ StartupCache::gStartupCache = nullptr;
+ }
+ return rv;
+}
+
+StaticRefPtr<StartupCache> StartupCache::gStartupCache;
+bool StartupCache::gShutdownInitiated;
+bool StartupCache::gIgnoreDiskCache;
+enum StartupCache::TelemetrifyAge StartupCache::gPostFlushAgeAction = StartupCache::IGNORE_AGE;
+
+NS_IMPL_ISUPPORTS(StartupCache, nsIMemoryReporter)
+
+StartupCache::StartupCache()
+ : mArchive(nullptr), mStartupWriteInitiated(false), mWriteThread(nullptr)
+{ }
+
+StartupCache::~StartupCache()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+
+ // Generally, the in-memory table should be empty here,
+ // but an early shutdown means either mTimer didn't run
+ // or the write thread is still running.
+ WaitOnWriteThread();
+
+ // If we shutdown quickly timer wont have fired. Instead of writing
+ // it on the main thread and block the shutdown we simply wont update
+ // the startup cache. Always do this if the file doesn't exist since
+ // we use it part of the package step.
+ if (!mArchive) {
+ WriteToDisk();
+ }
+
+ UnregisterWeakMemoryReporter(this);
+}
+
+nsresult
+StartupCache::Init()
+{
+ // workaround for bug 653936
+ nsCOMPtr<nsIProtocolHandler> jarInitializer(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "jar"));
+
+ nsresult rv;
+
+ // This allows to override the startup cache filename
+ // which is useful from xpcshell, when there is no ProfLDS directory to keep cache in.
+ char *env = PR_GetEnv("MOZ_STARTUP_CACHE");
+ if (env) {
+ rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, getter_AddRefs(mFile));
+ } else {
+ nsCOMPtr<nsIFile> file;
+ rv = NS_GetSpecialDirectory("ProfLDS",
+ getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ // return silently, this will fail in mochitests's xpcshell process.
+ return rv;
+ }
+
+ nsCOMPtr<nsIFile> profDir;
+ NS_GetSpecialDirectory("ProfDS", getter_AddRefs(profDir));
+ if (profDir) {
+ bool same;
+ if (NS_SUCCEEDED(profDir->Equals(file, &same)) && !same) {
+ // We no longer store the startup cache in the main profile
+ // directory, so we should cleanup the old one.
+ if (NS_SUCCEEDED(
+ profDir->AppendNative(NS_LITERAL_CSTRING("startupCache")))) {
+ profDir->Remove(true);
+ }
+ }
+ }
+
+ rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Try to create the directory if it's not there yet
+ rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
+ return rv;
+
+ rv = file->AppendNative(NS_LITERAL_CSTRING(STARTUP_CACHE_NAME));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mFile = do_QueryInterface(file);
+ }
+
+ NS_ENSURE_TRUE(mFile, NS_ERROR_UNEXPECTED);
+
+ mObserverService = do_GetService("@mozilla.org/observer-service;1");
+
+ if (!mObserverService) {
+ NS_WARNING("Could not get observerService.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mListener = new StartupCacheListener();
+ rv = mObserverService->AddObserver(mListener, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mObserverService->AddObserver(mListener, "startupcache-invalidate",
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = LoadArchive(RECORD_AGE);
+
+ // Sometimes we don't have a cache yet, that's ok.
+ // If it's corrupted, just remove it and start over.
+ if (gIgnoreDiskCache || (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND)) {
+ NS_WARNING("Failed to load startupcache file correctly, removing!");
+ InvalidateCache();
+ }
+
+ RegisterWeakMemoryReporter(this);
+
+ return NS_OK;
+}
+
+/**
+ * LoadArchive can be called from the main thread or while reloading cache on write thread.
+ */
+nsresult
+StartupCache::LoadArchive(enum TelemetrifyAge flag)
+{
+ if (gIgnoreDiskCache)
+ return NS_ERROR_FAILURE;
+
+ bool exists;
+ mArchive = nullptr;
+ nsresult rv = mFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ mArchive = new nsZipArchive();
+ rv = mArchive->OpenArchive(mFile);
+
+ return rv;
+}
+
+namespace {
+
+nsresult
+GetBufferFromZipArchive(nsZipArchive *zip, bool doCRC, const char* id,
+ UniquePtr<char[]>* outbuf, uint32_t* length)
+{
+ if (!zip)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsZipItemPtr<char> zipItem(zip, id, doCRC);
+ if (!zipItem)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ *outbuf = zipItem.Forget();
+ *length = zipItem.Length();
+ return NS_OK;
+}
+
+} /* anonymous namespace */
+
+// NOTE: this will not find a new entry until it has been written to disk!
+// Consumer should take ownership of the resulting buffer.
+nsresult
+StartupCache::GetBuffer(const char* id, UniquePtr<char[]>* outbuf, uint32_t* length)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
+
+ NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread");
+
+ WaitOnWriteThread();
+ if (!mStartupWriteInitiated) {
+ CacheEntry* entry;
+ nsDependentCString idStr(id);
+ mTable.Get(idStr, &entry);
+ if (entry) {
+ *outbuf = MakeUnique<char[]>(entry->size);
+ memcpy(outbuf->get(), entry->data.get(), entry->size);
+ *length = entry->size;
+ return NS_OK;
+ }
+ }
+
+ nsresult rv = GetBufferFromZipArchive(mArchive, true, id, outbuf, length);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ RefPtr<nsZipArchive> omnijar = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP);
+ // no need to checksum omnijarred entries
+ rv = GetBufferFromZipArchive(omnijar, false, id, outbuf, length);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ omnijar = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE);
+ // no need to checksum omnijarred entries
+ return GetBufferFromZipArchive(omnijar, false, id, outbuf, length);
+}
+
+// Makes a copy of the buffer, client retains ownership of inbuf.
+nsresult
+StartupCache::PutBuffer(const char* id, const char* inbuf, uint32_t len)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread");
+ WaitOnWriteThread();
+ if (StartupCache::gShutdownInitiated) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ auto data = MakeUnique<char[]>(len);
+ memcpy(data.get(), inbuf, len);
+
+ nsCString idStr(id);
+ // Cache it for now, we'll write all together later.
+ CacheEntry* entry;
+
+ if (mTable.Get(idStr)) {
+ NS_WARNING("Existing entry in StartupCache.");
+ // Double-caching is undesirable but not an error.
+ return NS_OK;
+ }
+
+#ifdef DEBUG
+ if (mArchive) {
+ nsZipItem* zipItem = mArchive->GetItem(id);
+ NS_ASSERTION(zipItem == nullptr, "Existing entry in disk StartupCache.");
+ }
+#endif
+
+ entry = new CacheEntry(Move(data), len);
+ mTable.Put(idStr, entry);
+ mPendingWrites.AppendElement(idStr);
+ return ResetStartupWriteTimer();
+}
+
+size_t
+StartupCache::SizeOfMapping()
+{
+ return mArchive ? mArchive->SizeOfMapping() : 0;
+}
+
+size_t
+StartupCache::HeapSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ // This function could measure more members, but they haven't been found by
+ // DMD to be significant. They can be added later if necessary.
+
+ size_t n = aMallocSizeOf(this);
+
+ n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mTable.ConstIter(); !iter.Done(); iter.Next()) {
+ n += iter.Data()->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ n += mPendingWrites.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ return n;
+}
+
+struct CacheWriteHolder
+{
+ nsCOMPtr<nsIZipWriter> writer;
+ nsCOMPtr<nsIStringInputStream> stream;
+ PRTime time;
+};
+
+static void
+CacheCloseHelper(const nsACString& key, const CacheEntry* data,
+ const CacheWriteHolder* holder)
+{
+ MOZ_ASSERT(data); // assert key was found in mTable.
+
+ nsresult rv;
+ nsIStringInputStream* stream = holder->stream;
+ nsIZipWriter* writer = holder->writer;
+
+ stream->ShareData(data->data.get(), data->size);
+
+#ifdef DEBUG
+ bool hasEntry;
+ rv = writer->HasEntry(key, &hasEntry);
+ NS_ASSERTION(NS_SUCCEEDED(rv) && hasEntry == false,
+ "Existing entry in disk StartupCache.");
+#endif
+ rv = writer->AddEntryStream(key, holder->time, true, stream, false);
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("cache entry deleted but not written to disk.");
+ }
+}
+
+
+/**
+ * WriteToDisk writes the cache out to disk. Callers of WriteToDisk need to call WaitOnWriteThread
+ * to make sure there isn't a write happening on another thread
+ */
+void
+StartupCache::WriteToDisk()
+{
+ nsresult rv;
+ mStartupWriteInitiated = true;
+
+ if (mTable.Count() == 0)
+ return;
+
+ nsCOMPtr<nsIZipWriter> zipW = do_CreateInstance("@mozilla.org/zipwriter;1");
+ if (!zipW)
+ return;
+
+ rv = zipW->Open(mFile, PR_RDWR | PR_CREATE_FILE);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("could not open zipfile for write");
+ return;
+ }
+
+ // If we didn't have an mArchive member, that means that we failed to
+ // open the startup cache for reading. Therefore, we need to record
+ // the time of creation in a zipfile comment.
+ PRTime now = PR_Now();
+ if (!mArchive) {
+ nsCString comment;
+ comment.Assign((char *)&now, sizeof(now));
+ zipW->SetComment(comment);
+ }
+
+ nsCOMPtr<nsIStringInputStream> stream
+ = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Couldn't create string input stream.");
+ return;
+ }
+
+ CacheWriteHolder holder;
+ holder.stream = stream;
+ holder.writer = zipW;
+ holder.time = now;
+
+ for (auto key = mPendingWrites.begin(); key != mPendingWrites.end(); key++) {
+ CacheCloseHelper(*key, mTable.Get(*key), &holder);
+ }
+ mPendingWrites.Clear();
+ mTable.Clear();
+
+ // Close the archive so Windows doesn't choke.
+ mArchive = nullptr;
+ zipW->Close();
+
+ // We succesfully wrote the archive to disk; mark the disk file as trusted
+ gIgnoreDiskCache = false;
+
+ // Our reader's view of the archive is outdated now, reload it.
+ LoadArchive(gPostFlushAgeAction);
+
+ return;
+}
+
+void
+StartupCache::InvalidateCache()
+{
+ WaitOnWriteThread();
+ mPendingWrites.Clear();
+ mTable.Clear();
+ mArchive = nullptr;
+ nsresult rv = mFile->Remove(false);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
+ rv != NS_ERROR_FILE_NOT_FOUND) {
+ gIgnoreDiskCache = true;
+ return;
+ }
+ gIgnoreDiskCache = false;
+ LoadArchive(gPostFlushAgeAction);
+}
+
+void
+StartupCache::IgnoreDiskCache()
+{
+ gIgnoreDiskCache = true;
+ if (gStartupCache)
+ gStartupCache->InvalidateCache();
+}
+
+/*
+ * WaitOnWriteThread() is called from a main thread to wait for the worker
+ * thread to finish. However since the same code is used in the worker thread and
+ * main thread, the worker thread can also call WaitOnWriteThread() which is a no-op.
+ */
+void
+StartupCache::WaitOnWriteThread()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Startup cache should only wait for io thread on main thread");
+ if (!mWriteThread || mWriteThread == PR_GetCurrentThread())
+ return;
+
+ PR_JoinThread(mWriteThread);
+ mWriteThread = nullptr;
+}
+
+void
+StartupCache::ThreadedWrite(void *aClosure)
+{
+ PR_SetCurrentThreadName("StartupCache");
+ mozilla::IOInterposer::RegisterCurrentThread();
+ /*
+ * It is safe to use the pointer passed in aClosure to reference the
+ * StartupCache object because the thread's lifetime is tightly coupled to
+ * the lifetime of the StartupCache object; this thread is joined in the
+ * StartupCache destructor, guaranteeing that this function runs if and only
+ * if the StartupCache object is valid.
+ */
+ StartupCache* startupCacheObj = static_cast<StartupCache*>(aClosure);
+ startupCacheObj->WriteToDisk();
+ mozilla::IOInterposer::UnregisterCurrentThread();
+}
+
+/*
+ * The write-thread is spawned on a timeout(which is reset with every write). This
+ * can avoid a slow shutdown. After writing out the cache, the zipreader is
+ * reloaded on the worker thread.
+ */
+void
+StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure)
+{
+ /*
+ * It is safe to use the pointer passed in aClosure to reference the
+ * StartupCache object because the timer's lifetime is tightly coupled to
+ * the lifetime of the StartupCache object; this timer is canceled in the
+ * StartupCache destructor, guaranteeing that this function runs if and only
+ * if the StartupCache object is valid.
+ */
+ StartupCache* startupCacheObj = static_cast<StartupCache*>(aClosure);
+ startupCacheObj->mWriteThread = PR_CreateThread(PR_USER_THREAD,
+ StartupCache::ThreadedWrite,
+ startupCacheObj,
+ PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD,
+ PR_JOINABLE_THREAD,
+ 0);
+}
+
+// We don't want to refcount StartupCache, so we'll just
+// hold a ref to this and pass it to observerService instead.
+NS_IMPL_ISUPPORTS(StartupCacheListener, nsIObserver)
+
+nsresult
+StartupCacheListener::Observe(nsISupports *subject, const char* topic, const char16_t* data)
+{
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc)
+ return NS_OK;
+
+ if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ // Do not leave the thread running past xpcom shutdown
+ sc->WaitOnWriteThread();
+ StartupCache::gShutdownInitiated = true;
+ } else if (strcmp(topic, "startupcache-invalidate") == 0) {
+ sc->InvalidateCache();
+ }
+ return NS_OK;
+}
+
+nsresult
+StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream* aStream,
+ nsIObjectOutputStream** aOutStream)
+{
+ NS_ENSURE_ARG_POINTER(aStream);
+#ifdef DEBUG
+ StartupCacheDebugOutputStream* stream
+ = new StartupCacheDebugOutputStream(aStream, &mWriteObjectMap);
+ NS_ADDREF(*aOutStream = stream);
+#else
+ NS_ADDREF(*aOutStream = aStream);
+#endif
+
+ return NS_OK;
+}
+
+nsresult
+StartupCache::ResetStartupWriteTimer()
+{
+ mStartupWriteInitiated = false;
+ nsresult rv;
+ if (!mTimer)
+ mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ else
+ rv = mTimer->Cancel();
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Wait for 10 seconds, then write out the cache.
+ mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, this, 60000,
+ nsITimer::TYPE_ONE_SHOT);
+ return NS_OK;
+}
+
+// StartupCacheDebugOutputStream implementation
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS(StartupCacheDebugOutputStream, nsIObjectOutputStream,
+ nsIBinaryOutputStream, nsIOutputStream)
+
+bool
+StartupCacheDebugOutputStream::CheckReferences(nsISupports* aObject)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject);
+ if (!classInfo) {
+ NS_ERROR("aObject must implement nsIClassInfo");
+ return false;
+ }
+
+ uint32_t flags;
+ rv = classInfo->GetFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (flags & nsIClassInfo::SINGLETON)
+ return true;
+
+ nsISupportsHashKey* key = mObjectMap->GetEntry(aObject);
+ if (key) {
+ NS_ERROR("non-singleton aObject is referenced multiple times in this"
+ "serialization, we don't support that.");
+ return false;
+ }
+
+ mObjectMap->PutEntry(aObject);
+ return true;
+}
+
+// nsIObjectOutputStream implementation
+nsresult
+StartupCacheDebugOutputStream::WriteObject(nsISupports* aObject, bool aIsStrongRef)
+{
+ nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
+
+ NS_ASSERTION(rootObject.get() == aObject,
+ "bad call to WriteObject -- call WriteCompoundObject!");
+ bool check = CheckReferences(aObject);
+ NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
+ return mBinaryStream->WriteObject(aObject, aIsStrongRef);
+}
+
+nsresult
+StartupCacheDebugOutputStream::WriteSingleRefObject(nsISupports* aObject)
+{
+ nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
+
+ NS_ASSERTION(rootObject.get() == aObject,
+ "bad call to WriteSingleRefObject -- call WriteCompoundObject!");
+ bool check = CheckReferences(aObject);
+ NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
+ return mBinaryStream->WriteSingleRefObject(aObject);
+}
+
+nsresult
+StartupCacheDebugOutputStream::WriteCompoundObject(nsISupports* aObject,
+ const nsIID& aIID,
+ bool aIsStrongRef)
+{
+ nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
+
+ nsCOMPtr<nsISupports> roundtrip;
+ rootObject->QueryInterface(aIID, getter_AddRefs(roundtrip));
+ NS_ASSERTION(roundtrip.get() == aObject,
+ "bad aggregation or multiple inheritance detected by call to "
+ "WriteCompoundObject!");
+
+ bool check = CheckReferences(aObject);
+ NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
+ return mBinaryStream->WriteCompoundObject(aObject, aIID, aIsStrongRef);
+}
+
+nsresult
+StartupCacheDebugOutputStream::WriteID(nsID const& aID)
+{
+ return mBinaryStream->WriteID(aID);
+}
+
+char*
+StartupCacheDebugOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask)
+{
+ return mBinaryStream->GetBuffer(aLength, aAlignMask);
+}
+
+void
+StartupCacheDebugOutputStream::PutBuffer(char* aBuffer, uint32_t aLength)
+{
+ mBinaryStream->PutBuffer(aBuffer, aLength);
+}
+#endif //DEBUG
+
+StartupCacheWrapper* StartupCacheWrapper::gStartupCacheWrapper = nullptr;
+
+NS_IMPL_ISUPPORTS(StartupCacheWrapper, nsIStartupCache)
+
+StartupCacheWrapper::~StartupCacheWrapper()
+{
+ MOZ_ASSERT(gStartupCacheWrapper == this);
+ gStartupCacheWrapper = nullptr;
+}
+
+StartupCacheWrapper* StartupCacheWrapper::GetSingleton()
+{
+ if (!gStartupCacheWrapper)
+ gStartupCacheWrapper = new StartupCacheWrapper();
+
+ NS_ADDREF(gStartupCacheWrapper);
+ return gStartupCacheWrapper;
+}
+
+nsresult
+StartupCacheWrapper::GetBuffer(const char* id, char** outbuf, uint32_t* length)
+{
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ UniquePtr<char[]> buf;
+ nsresult rv = sc->GetBuffer(id, &buf, length);
+ *outbuf = buf.release();
+ return rv;
+}
+
+nsresult
+StartupCacheWrapper::PutBuffer(const char* id, const char* inbuf, uint32_t length)
+{
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return sc->PutBuffer(id, inbuf, length);
+}
+
+nsresult
+StartupCacheWrapper::InvalidateCache()
+{
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ sc->InvalidateCache();
+ return NS_OK;
+}
+
+nsresult
+StartupCacheWrapper::IgnoreDiskCache()
+{
+ StartupCache::IgnoreDiskCache();
+ return NS_OK;
+}
+
+nsresult
+StartupCacheWrapper::GetDebugObjectOutputStream(nsIObjectOutputStream* stream,
+ nsIObjectOutputStream** outStream)
+{
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return sc->GetDebugObjectOutputStream(stream, outStream);
+}
+
+nsresult
+StartupCacheWrapper::StartupWriteComplete(bool *complete)
+{
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ sc->WaitOnWriteThread();
+ *complete = sc->mStartupWriteInitiated && sc->mTable.Count() == 0;
+ return NS_OK;
+}
+
+nsresult
+StartupCacheWrapper::ResetStartupWriteTimer()
+{
+ StartupCache* sc = StartupCache::GetSingleton();
+ return sc ? sc->ResetStartupWriteTimer() : NS_ERROR_NOT_INITIALIZED;
+}
+
+nsresult
+StartupCacheWrapper::GetObserver(nsIObserver** obv) {
+ StartupCache* sc = StartupCache::GetSingleton();
+ if (!sc) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ NS_ADDREF(*obv = sc->mListener);
+ return NS_OK;
+}
+
+} // namespace scache
+} // namespace mozilla
diff --git a/components/scache/src/StartupCache.h b/components/scache/src/StartupCache.h
new file mode 100644
index 000000000..1a2ce0e0b
--- /dev/null
+++ b/components/scache/src/StartupCache.h
@@ -0,0 +1,228 @@
+/* -*- 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 StartupCache_h_
+#define StartupCache_h_
+
+#include "nsClassHashtable.h"
+#include "nsComponentManagerUtils.h"
+#include "nsTArray.h"
+#include "nsZipArchive.h"
+#include "nsIStartupCache.h"
+#include "nsITimer.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsIOutputStream.h"
+#include "nsIFile.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
+
+/**
+ * The StartupCache is a persistent cache of simple key-value pairs,
+ * where the keys are null-terminated c-strings and the values are
+ * arbitrary data, passed as a (char*, size) tuple.
+ *
+ * Clients should use the GetSingleton() static method to access the cache. It
+ * will be available from the end of XPCOM init (NS_InitXPCOM3 in XPCOMInit.cpp),
+ * until XPCOM shutdown begins. The GetSingleton() method will return null if the cache
+ * is unavailable. The cache is only provided for libxul builds --
+ * it will fail to link in non-libxul builds. The XPCOM interface is provided
+ * only to allow compiled-code tests; clients should avoid using it.
+ *
+ * The API provided is very simple: GetBuffer() returns a buffer that was previously
+ * stored in the cache (if any), and PutBuffer() inserts a buffer into the cache.
+ * GetBuffer returns a new buffer, and the caller must take ownership of it.
+ * PutBuffer will assert if the client attempts to insert a buffer with the same name as
+ * an existing entry. The cache makes a copy of the passed-in buffer, so client
+ * retains ownership.
+ *
+ * InvalidateCache() may be called if a client suspects data corruption
+ * or wishes to invalidate for any other reason. This will remove all existing cache data.
+ * Additionally, the static method IgnoreDiskCache() can be called if it is
+ * believed that the on-disk cache file is itself corrupt. This call implicitly
+ * calls InvalidateCache (if the singleton has been initialized) to ensure any
+ * data already read from disk is discarded. The cache will not load data from
+ * the disk file until a successful write occurs.
+ *
+ * Finally, getDebugObjectOutputStream() allows debug code to wrap an objectstream
+ * with a debug objectstream, to check for multiply-referenced objects. These will
+ * generally fail to deserialize correctly, unless they are stateless singletons or the
+ * client maintains their own object data map for deserialization.
+ *
+ * Writes before the final-ui-startup notification are placed in an intermediate
+ * cache in memory, then written out to disk at a later time, to get writes off the
+ * startup path. In any case, clients should not rely on being able to GetBuffer()
+ * data that is written to the cache, since it may not have been written to disk or
+ * another client may have invalidated the cache. In other words, it should be used as
+ * a cache only, and not a reliable persistent store.
+ *
+ * Some utility functions are provided in StartupCacheUtils. These functions wrap the
+ * buffers into object streams, which may be useful for serializing objects. Note
+ * the above caution about multiply-referenced objects, though -- the streams are just
+ * as 'dumb' as the underlying buffers about multiply-referenced objects. They just
+ * provide some convenience in writing out data.
+ */
+
+namespace mozilla {
+
+namespace scache {
+
+struct CacheEntry
+{
+ UniquePtr<char[]> data;
+ uint32_t size;
+
+ CacheEntry() : size(0) { }
+
+ // Takes possession of buf
+ CacheEntry(UniquePtr<char[]> buf, uint32_t len) : data(Move(buf)), size(len) { }
+
+ ~CacheEntry()
+ {
+ }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
+ return mallocSizeOf(this) + mallocSizeOf(data.get());
+ }
+};
+
+// We don't want to refcount StartupCache, and ObserverService wants to
+// refcount its listeners, so we'll let it refcount this instead.
+class StartupCacheListener final : public nsIObserver
+{
+ ~StartupCacheListener() {}
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+};
+
+class StartupCache : public nsIMemoryReporter
+{
+
+friend class StartupCacheListener;
+friend class StartupCacheWrapper;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ // StartupCache methods. See above comments for a more detailed description.
+
+ // Returns a buffer that was previously stored, caller takes ownership.
+ nsresult GetBuffer(const char* id, UniquePtr<char[]>* outbuf, uint32_t* length);
+
+ // Stores a buffer. Caller keeps ownership, we make a copy.
+ nsresult PutBuffer(const char* id, const char* inbuf, uint32_t length);
+
+ // Removes the cache file.
+ void InvalidateCache();
+
+ // Signal that data should not be loaded from the cache file
+ static void IgnoreDiskCache();
+
+ // In DEBUG builds, returns a stream that will attempt to check for
+ // and disallow multiple writes of the same object.
+ nsresult GetDebugObjectOutputStream(nsIObjectOutputStream* aStream,
+ nsIObjectOutputStream** outStream);
+
+ static StartupCache* GetSingleton();
+ static void DeleteSingleton();
+
+ // This measures all the heap memory used by the StartupCache, i.e. it
+ // excludes the mapping.
+ size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ size_t SizeOfMapping();
+
+private:
+ StartupCache();
+ virtual ~StartupCache();
+
+ enum TelemetrifyAge {
+ IGNORE_AGE = 0,
+ RECORD_AGE = 1
+ };
+ static enum TelemetrifyAge gPostFlushAgeAction;
+
+ nsresult LoadArchive(enum TelemetrifyAge flag);
+ nsresult Init();
+ void WriteToDisk();
+ nsresult ResetStartupWriteTimer();
+ void WaitOnWriteThread();
+
+ static nsresult InitSingleton();
+ static void WriteTimeout(nsITimer *aTimer, void *aClosure);
+ static void ThreadedWrite(void *aClosure);
+
+ nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
+ nsTArray<nsCString> mPendingWrites;
+ RefPtr<nsZipArchive> mArchive;
+ nsCOMPtr<nsIFile> mFile;
+
+ nsCOMPtr<nsIObserverService> mObserverService;
+ RefPtr<StartupCacheListener> mListener;
+ nsCOMPtr<nsITimer> mTimer;
+
+ bool mStartupWriteInitiated;
+
+ static StaticRefPtr<StartupCache> gStartupCache;
+ static bool gShutdownInitiated;
+ static bool gIgnoreDiskCache;
+ PRThread *mWriteThread;
+#ifdef DEBUG
+ nsTHashtable<nsISupportsHashKey> mWriteObjectMap;
+#endif
+};
+
+// This debug outputstream attempts to detect if clients are writing multiple
+// references to the same object. We only support that if that object
+// is a singleton.
+#ifdef DEBUG
+class StartupCacheDebugOutputStream final
+ : public nsIObjectOutputStream
+{
+ ~StartupCacheDebugOutputStream() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBJECTOUTPUTSTREAM
+
+ StartupCacheDebugOutputStream (nsIObjectOutputStream* binaryStream,
+ nsTHashtable<nsISupportsHashKey>* objectMap)
+ : mBinaryStream(binaryStream), mObjectMap(objectMap) { }
+
+ NS_FORWARD_SAFE_NSIBINARYOUTPUTSTREAM(mBinaryStream)
+ NS_FORWARD_SAFE_NSIOUTPUTSTREAM(mBinaryStream)
+
+ bool CheckReferences(nsISupports* aObject);
+
+ nsCOMPtr<nsIObjectOutputStream> mBinaryStream;
+ nsTHashtable<nsISupportsHashKey> *mObjectMap;
+};
+#endif // DEBUG
+
+// XPCOM wrapper interface provided for tests only.
+#define NS_STARTUPCACHE_CID \
+ {0xae4505a9, 0x87ab, 0x477c, \
+ {0xb5, 0x77, 0xf9, 0x23, 0x57, 0xed, 0xa8, 0x84}}
+// contract id: "@mozilla.org/startupcache/cache;1"
+
+class StartupCacheWrapper final
+ : public nsIStartupCache
+{
+ ~StartupCacheWrapper();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTARTUPCACHE
+
+ static StartupCacheWrapper* GetSingleton();
+ static StartupCacheWrapper *gStartupCacheWrapper;
+};
+
+} // namespace scache
+} // namespace mozilla
+
+#endif //StartupCache_h_
diff --git a/components/scache/src/StartupCacheModule.cpp b/components/scache/src/StartupCacheModule.cpp
new file mode 100644
index 000000000..aa9f08e33
--- /dev/null
+++ b/components/scache/src/StartupCacheModule.cpp
@@ -0,0 +1,45 @@
+/* -*- 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 <string.h>
+
+#include "nscore.h"
+
+#include "nsID.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "nsIModule.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/scache/StartupCache.h"
+
+using namespace mozilla::scache;
+
+// XXX Need help with guard for ENABLE_TEST
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(StartupCacheWrapper,
+ StartupCacheWrapper::GetSingleton)
+NS_DEFINE_NAMED_CID(NS_STARTUPCACHE_CID);
+
+static const mozilla::Module::CIDEntry kStartupCacheCIDs[] = {
+ { &kNS_STARTUPCACHE_CID, false, nullptr, StartupCacheWrapperConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kStartupCacheContracts[] = {
+ { "@mozilla.org/startupcache/cache;1", &kNS_STARTUPCACHE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kStartupCacheModule = {
+ mozilla::Module::kVersion,
+ kStartupCacheCIDs,
+ kStartupCacheContracts,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr
+};
+
+NSMODULE_DEFN(StartupCacheModule) = &kStartupCacheModule;
diff --git a/components/scache/src/StartupCacheUtils.cpp b/components/scache/src/StartupCacheUtils.cpp
new file mode 100644
index 000000000..19a6b4e1b
--- /dev/null
+++ b/components/scache/src/StartupCacheUtils.cpp
@@ -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/. */
+
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsIStringStream.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsIJARURI.h"
+#include "nsIResProtocolHandler.h"
+#include "nsIChromeRegistry.h"
+#include "nsAutoPtr.h"
+#include "StartupCacheUtils.h"
+#include "mozilla/scache/StartupCache.h"
+#include "mozilla/Omnijar.h"
+
+namespace mozilla {
+namespace scache {
+
+NS_EXPORT nsresult
+NewObjectInputStreamFromBuffer(UniquePtr<char[]> buffer, uint32_t len,
+ nsIObjectInputStream** stream)
+{
+ nsCOMPtr<nsIStringInputStream> stringStream =
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1");
+ NS_ENSURE_TRUE(stringStream, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIObjectInputStream> objectInput =
+ do_CreateInstance("@mozilla.org/binaryinputstream;1");
+ NS_ENSURE_TRUE(objectInput, NS_ERROR_FAILURE);
+
+ stringStream->AdoptData(buffer.release(), len);
+ objectInput->SetInputStream(stringStream);
+
+ objectInput.forget(stream);
+ return NS_OK;
+}
+
+NS_EXPORT nsresult
+NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream,
+ nsIStorageStream** stream,
+ bool wantDebugStream)
+{
+ nsCOMPtr<nsIStorageStream> storageStream;
+
+ nsresult rv = NS_NewStorageStream(256, UINT32_MAX, getter_AddRefs(storageStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObjectOutputStream> objectOutput
+ = do_CreateInstance("@mozilla.org/binaryoutputstream;1");
+ nsCOMPtr<nsIOutputStream> outputStream
+ = do_QueryInterface(storageStream);
+
+ objectOutput->SetOutputStream(outputStream);
+
+#ifdef DEBUG
+ if (wantDebugStream) {
+ // Wrap in debug stream to detect unsupported writes of
+ // multiply-referenced non-singleton objects
+ StartupCache* sc = StartupCache::GetSingleton();
+ NS_ENSURE_TRUE(sc, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIObjectOutputStream> debugStream;
+ sc->GetDebugObjectOutputStream(objectOutput, getter_AddRefs(debugStream));
+ debugStream.forget(wrapperStream);
+ } else {
+ objectOutput.forget(wrapperStream);
+ }
+#else
+ objectOutput.forget(wrapperStream);
+#endif
+
+ storageStream.forget(stream);
+ return NS_OK;
+}
+
+NS_EXPORT nsresult
+NewBufferFromStorageStream(nsIStorageStream *storageStream,
+ UniquePtr<char[]>* buffer, uint32_t* len)
+{
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t avail64;
+ rv = inputStream->Available(&avail64);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(avail64 <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
+
+ uint32_t avail = (uint32_t)avail64;
+ auto temp = MakeUnique<char[]>(avail);
+ uint32_t read;
+ rv = inputStream->Read(temp.get(), avail, &read);
+ if (NS_SUCCEEDED(rv) && avail != read)
+ rv = NS_ERROR_UNEXPECTED;
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *len = avail;
+ *buffer = Move(temp);
+ return NS_OK;
+}
+
+static const char baseName[2][5] = { "gre/", "app/" };
+
+static inline bool
+canonicalizeBase(nsAutoCString &spec,
+ nsACString &out)
+{
+ nsAutoCString greBase, appBase;
+ nsresult rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, greBase);
+ if (NS_FAILED(rv) || !greBase.Length())
+ return false;
+
+ rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, appBase);
+ if (NS_FAILED(rv))
+ return false;
+
+ bool underGre = !greBase.Compare(spec.get(), false, greBase.Length());
+ bool underApp = appBase.Length() &&
+ !appBase.Compare(spec.get(), false, appBase.Length());
+
+ if (!underGre && !underApp)
+ return false;
+
+ /**
+ * At this point, if both underGre and underApp are true, it can be one
+ * of the two following cases:
+ * - the GRE directory points to a subdirectory of the APP directory,
+ * meaning spec points under GRE.
+ * - the APP directory points to a subdirectory of the GRE directory,
+ * meaning spec points under APP.
+ * Checking the GRE and APP path length is enough to know in which case
+ * we are.
+ */
+ if (underGre && underApp && greBase.Length() < appBase.Length())
+ underGre = false;
+
+ out.AppendLiteral("/resource/");
+ out.Append(baseName[underGre ? mozilla::Omnijar::GRE : mozilla::Omnijar::APP]);
+ out.Append(Substring(spec, underGre ? greBase.Length() : appBase.Length()));
+ return true;
+}
+
+/**
+ * PathifyURI transforms uris into useful zip paths
+ * to make it easier to manipulate startup cache entries
+ * using standard zip tools.
+ * Transformations applied:
+ * * resource:// URIs are resolved to their corresponding file/jar URI to
+ * canonicalize resources URIs other than gre and app.
+ * * Paths under GRE or APP directory have their base path replaced with
+ * resource/gre or resource/app to avoid depending on install location.
+ * * jar:file:///path/to/file.jar!/sub/path urls are replaced with
+ * /path/to/file.jar/sub/path
+ *
+ * The result is appended to the string passed in. Adding a prefix before
+ * calling is recommended to avoid colliding with other cache users.
+ *
+ * For example, in the js loader (string is prefixed with jsloader by caller):
+ * resource://gre/modules/XPCOMUtils.jsm or
+ * file://$GRE_DIR/modules/XPCOMUtils.jsm or
+ * jar:file://$GRE_DIR/omni.jar!/modules/XPCOMUtils.jsm becomes
+ * jsloader/resource/gre/modules/XPCOMUtils.jsm
+ * file://$PROFILE_DIR/extensions/{uuid}/components/component.js becomes
+ * jsloader/$PROFILE_DIR/extensions/%7Buuid%7D/components/component.js
+ * jar:file://$PROFILE_DIR/extensions/some.xpi!/components/component.js becomes
+ * jsloader/$PROFILE_DIR/extensions/some.xpi/components/component.js
+ */
+NS_EXPORT nsresult
+PathifyURI(nsIURI *in, nsACString &out)
+{
+ bool equals;
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri = in;
+ nsAutoCString spec;
+
+ // Resolve resource:// URIs. At the end of this if/else block, we
+ // have both spec and uri variables identifying the same URI.
+ if (NS_SUCCEEDED(in->SchemeIs("resource", &equals)) && equals) {
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIProtocolHandler> ph;
+ rv = ioService->GetProtocolHandler("resource", getter_AddRefs(ph));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIResProtocolHandler> irph(do_QueryInterface(ph, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = irph->ResolveURI(in, spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ioService->NewURI(spec, nullptr, nullptr, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ if (NS_SUCCEEDED(in->SchemeIs("chrome", &equals)) && equals) {
+ nsCOMPtr<nsIChromeRegistry> chromeReg =
+ mozilla::services::GetChromeRegistryService();
+ if (!chromeReg)
+ return NS_ERROR_UNEXPECTED;
+
+ rv = chromeReg->ConvertChromeURL(in, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!canonicalizeBase(spec, out)) {
+ if (NS_SUCCEEDED(uri->SchemeIs("file", &equals)) && equals) {
+ nsCOMPtr<nsIFileURL> baseFileURL;
+ baseFileURL = do_QueryInterface(uri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString path;
+ rv = baseFileURL->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ out.Append(path);
+ } else if (NS_SUCCEEDED(uri->SchemeIs("jar", &equals)) && equals) {
+ nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> jarFileURI;
+ rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = PathifyURI(jarFileURI, out);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString path;
+ rv = jarURI->GetJAREntry(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out.Append('/');
+ out.Append(path);
+ } else { // Very unlikely
+ rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ out.Append('/');
+ out.Append(spec);
+ }
+ }
+ return NS_OK;
+}
+
+} // namespace scache
+} // namespace mozilla
diff --git a/components/scache/src/StartupCacheUtils.h b/components/scache/src/StartupCacheUtils.h
new file mode 100644
index 000000000..7a4da2543
--- /dev/null
+++ b/components/scache/src/StartupCacheUtils.h
@@ -0,0 +1,43 @@
+/* -*- 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 nsStartupCacheUtils_h_
+#define nsStartupCacheUtils_h_
+
+#include "nsIStorageStream.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace scache {
+
+NS_EXPORT nsresult
+NewObjectInputStreamFromBuffer(UniquePtr<char[]> buffer, uint32_t len,
+ nsIObjectInputStream** stream);
+
+// We can't retrieve the wrapped stream from the objectOutputStream later,
+// so we return it here. We give callers in debug builds the option
+// to wrap the outputstream in a debug stream, which will detect if
+// non-singleton objects are written out multiple times during a serialization.
+// This could cause them to be deserialized incorrectly (as multiple copies
+// instead of references).
+NS_EXPORT nsresult
+NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream,
+ nsIStorageStream** stream,
+ bool wantDebugStream);
+
+// Creates a buffer for storing the stream into the cache. The buffer is
+// allocated with 'new []'. After calling this function, the caller would
+// typically call nsIStartupCache::PutBuffer with the returned buffer.
+NS_EXPORT nsresult
+NewBufferFromStorageStream(nsIStorageStream *storageStream,
+ UniquePtr<char[]>* buffer, uint32_t* len);
+
+NS_EXPORT nsresult
+PathifyURI(nsIURI *in, nsACString &out);
+} // namespace scache
+} // namespace mozilla
+
+#endif //nsStartupCacheUtils_h_
diff --git a/components/search/locale/search.properties b/components/search/locale/search.properties
new file mode 100644
index 000000000..8aeed6a6c
--- /dev/null
+++ b/components/search/locale/search.properties
@@ -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/.
+
+addEngineConfirmTitle=Add Search Engine
+addEngineConfirmation=Add “%S†to the list of engines available in the search bar?\n\nFrom: %S
+addEngineAsCurrentText=Make this the c&urrent search engine
+addEngineAddButtonLabel=Add
+
+error_loading_engine_title=Download Error
+# LOCALIZATION NOTE (error_loading_engine_msg2): %1$S = brandShortName, %2$S = location
+error_loading_engine_msg2=%S could not download the search plugin from:\n%S
+error_duplicate_engine_msg=%S could not install the search plugin from “%S†because an engine with the same name already exists.
+
+error_invalid_engine_title=Install Error
+# LOCALIZATION NOTE (error_invalid_engine_msg): %S = brandShortName
+error_invalid_engine_msg=This search engine isn’t supported by %S and can’t be installed.
+
+suggestion_label=Suggestions
+
diff --git a/components/search/moz.build b/components/search/moz.build
new file mode 100644
index 000000000..e2feafa17
--- /dev/null
+++ b/components/search/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += ['src/nsSearchSuggestions.js']
+
+if CONFIG['MOZ_PHOENIX'] or CONFIG['MOZ_XULRUNNER']:
+ DEFINES['HAVE_SIDEBAR'] = True
+ EXTRA_COMPONENTS += ['src/nsSidebar.js']
+
+EXTRA_PP_COMPONENTS += [
+ 'src/nsSearchService.js',
+ 'toolkitsearch.manifest',
+]
+
+EXTRA_JS_MODULES += [
+ 'src/SearchStaticData.jsm',
+ 'src/SearchSuggestionController.jsm',
+]
+
diff --git a/components/search/src/SearchStaticData.jsm b/components/search/src/SearchStaticData.jsm
new file mode 100644
index 000000000..de2be695c
--- /dev/null
+++ b/components/search/src/SearchStaticData.jsm
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This module contains additional data about default search engines that is the
+ * same across all languages. This information is defined outside of the actual
+ * search engine definition files, so that localizers don't need to update them
+ * when a change is made.
+ *
+ * This separate module is also easily overridable, in case a hotfix is needed.
+ * No high-level processing logic is applied here.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "SearchStaticData",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+// To update this list of known alternate domains, just cut-and-paste from
+// https://www.google.com/supported_domains
+const gGoogleDomainsSource = ".google.com .google.ad .google.ae .google.com.af .google.com.ag .google.com.ai .google.al .google.am .google.co.ao .google.com.ar .google.as .google.at .google.com.au .google.az .google.ba .google.com.bd .google.be .google.bf .google.bg .google.com.bh .google.bi .google.bj .google.com.bn .google.com.bo .google.com.br .google.bs .google.bt .google.co.bw .google.by .google.com.bz .google.ca .google.cd .google.cf .google.cg .google.ch .google.ci .google.co.ck .google.cl .google.cm .google.cn .google.com.co .google.co.cr .google.com.cu .google.cv .google.com.cy .google.cz .google.de .google.dj .google.dk .google.dm .google.com.do .google.dz .google.com.ec .google.ee .google.com.eg .google.es .google.com.et .google.fi .google.com.fj .google.fm .google.fr .google.ga .google.ge .google.gg .google.com.gh .google.com.gi .google.gl .google.gm .google.gp .google.gr .google.com.gt .google.gy .google.com.hk .google.hn .google.hr .google.ht .google.hu .google.co.id .google.ie .google.co.il .google.im .google.co.in .google.iq .google.is .google.it .google.je .google.com.jm .google.jo .google.co.jp .google.co.ke .google.com.kh .google.ki .google.kg .google.co.kr .google.com.kw .google.kz .google.la .google.com.lb .google.li .google.lk .google.co.ls .google.lt .google.lu .google.lv .google.com.ly .google.co.ma .google.md .google.me .google.mg .google.mk .google.ml .google.com.mm .google.mn .google.ms .google.com.mt .google.mu .google.mv .google.mw .google.com.mx .google.com.my .google.co.mz .google.com.na .google.com.nf .google.com.ng .google.com.ni .google.ne .google.nl .google.no .google.com.np .google.nr .google.nu .google.co.nz .google.com.om .google.com.pa .google.com.pe .google.com.pg .google.com.ph .google.com.pk .google.pl .google.pn .google.com.pr .google.ps .google.pt .google.com.py .google.com.qa .google.ro .google.ru .google.rw .google.com.sa .google.com.sb .google.sc .google.se .google.com.sg .google.sh .google.si .google.sk .google.com.sl .google.sn .google.so .google.sm .google.sr .google.st .google.com.sv .google.td .google.tg .google.co.th .google.com.tj .google.tk .google.tl .google.tm .google.tn .google.to .google.com.tr .google.tt .google.com.tw .google.co.tz .google.com.ua .google.co.ug .google.co.uk .google.com.uy .google.co.uz .google.com.vc .google.co.ve .google.vg .google.co.vi .google.com.vn .google.vu .google.ws .google.rs .google.co.za .google.co.zm .google.co.zw .google.cat";
+const gGoogleDomains = gGoogleDomainsSource.split(" ").map(d => "www" + d);
+
+this.SearchStaticData = {
+ /**
+ * Returns a list of alternate domains for a given search engine domain.
+ *
+ * @param aDomain
+ * Lowercase host name to look up. For example, if this argument is
+ * "www.google.com" or "www.google.co.uk", the function returns the
+ * full list of supported Google domains.
+ *
+ * @return Array containing one entry for each alternate host name, or empty
+ * array if none is known. The returned array should not be modified.
+ */
+ getAlternateDomains: function (aDomain) {
+ return gGoogleDomains.indexOf(aDomain) == -1 ? [] : gGoogleDomains;
+ },
+};
diff --git a/components/search/src/SearchSuggestionController.jsm b/components/search/src/SearchSuggestionController.jsm
new file mode 100644
index 000000000..0858974bf
--- /dev/null
+++ b/components/search/src/SearchSuggestionController.jsm
@@ -0,0 +1,396 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["SearchSuggestionController"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NS_ASSERT", "resource://gre/modules/debug.js");
+
+const SEARCH_RESPONSE_SUGGESTION_JSON = "application/x-suggestions+json";
+const DEFAULT_FORM_HISTORY_PARAM = "searchbar-history";
+const HTTP_OK = 200;
+const REMOTE_TIMEOUT = 500; // maximum time (ms) to wait before giving up on a remote suggestions
+const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
+
+/**
+ * Remote search suggestions will be shown if gRemoteSuggestionsEnabled
+ * is true. Global because only one pref observer is needed for all instances.
+ */
+var gRemoteSuggestionsEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF);
+Services.prefs.addObserver(BROWSER_SUGGEST_PREF, function(aSubject, aTopic, aData) {
+ gRemoteSuggestionsEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF);
+}, false);
+
+/**
+ * SearchSuggestionController.jsm exists as a helper module to allow multiple consumers to request and display
+ * search suggestions from a given engine, regardless of the base implementation. Much of this
+ * code was originally in nsSearchSuggestions.js until it was refactored to separate it from the
+ * nsIAutoCompleteSearch dependency.
+ * One instance of SearchSuggestionController should be used per field since form history results are cached.
+ */
+
+/**
+ * @param {function} [callback] - Callback for search suggestion results. You can use the promise
+ * returned by the search method instead if you prefer.
+ * @constructor
+ */
+this.SearchSuggestionController = function SearchSuggestionController(callback = null) {
+ this._callback = callback;
+};
+
+this.SearchSuggestionController.prototype = {
+ /**
+ * The maximum number of local form history results to return. This limit is
+ * only enforced if remote results are also returned.
+ */
+ maxLocalResults: 5,
+
+ /**
+ * The maximum number of remote search engine results to return.
+ * We'll actually only display at most
+ * maxRemoteResults - <displayed local results count> remote results.
+ */
+ maxRemoteResults: 10,
+
+ /**
+ * The maximum time (ms) to wait before giving up on a remote suggestions.
+ */
+ remoteTimeout: REMOTE_TIMEOUT,
+
+ /**
+ * The additional parameter used when searching form history.
+ */
+ formHistoryParam: DEFAULT_FORM_HISTORY_PARAM,
+
+ // Private properties
+ /**
+ * The last form history result used to improve the performance of subsequent searches.
+ * This shouldn't be used for any other purpose as it is never cleared and therefore could be stale.
+ */
+ _formHistoryResult: null,
+
+ /**
+ * The remote server timeout timer, if applicable. The timer starts when form history
+ * search is completed.
+ */
+ _remoteResultTimer: null,
+
+ /**
+ * The deferred for the remote results before its promise is resolved.
+ */
+ _deferredRemoteResult: null,
+
+ /**
+ * The optional result callback registered from the constructor.
+ */
+ _callback: null,
+
+ /**
+ * The XMLHttpRequest object for remote results.
+ */
+ _request: null,
+
+ // Public methods
+
+ /**
+ * Fetch search suggestions from all of the providers. Fetches in progress will be stopped and
+ * results from them will not be provided.
+ *
+ * @param {string} searchTerm - the term to provide suggestions for
+ * @param {bool} privateMode - whether the request is being made in the context of private browsing
+ * @param {nsISearchEngine} engine - search engine for the suggestions.
+ *
+ * @return {Promise} resolving to an object containing results or null.
+ */
+ fetch: function(searchTerm, privateMode, engine) {
+ // There is no smart filtering from previous results here (as there is when looking through
+ // history/form data) because the result set returned by the server is different for every typed
+ // value - e.g. "ocean breathes" does not return a subset of the results returned for "ocean".
+
+ this.stop();
+
+ if (!Services.search.isInitialized) {
+ throw new Error("Search not initialized yet (how did you get here?)");
+ }
+ if (typeof privateMode === "undefined") {
+ throw new Error("The privateMode argument is required to avoid unintentional privacy leaks");
+ }
+ if (!(engine instanceof Ci.nsISearchEngine)) {
+ throw new Error("Invalid search engine");
+ }
+ if (!this.maxLocalResults && !this.maxRemoteResults) {
+ throw new Error("Zero results expected, what are you trying to do?");
+ }
+ if (this.maxLocalResults < 0 || this.maxRemoteResults < 0) {
+ throw new Error("Number of requested results must be positive");
+ }
+
+ // Array of promises to resolve before returning results.
+ let promises = [];
+ this._searchString = searchTerm;
+
+ // Remote results
+ if (searchTerm && gRemoteSuggestionsEnabled && this.maxRemoteResults &&
+ engine.supportsResponseType(SEARCH_RESPONSE_SUGGESTION_JSON)) {
+ this._deferredRemoteResult = this._fetchRemote(searchTerm, engine, privateMode);
+ promises.push(this._deferredRemoteResult.promise);
+ }
+
+ // Local results from form history
+ if (this.maxLocalResults) {
+ let deferredHistoryResult = this._fetchFormHistory(searchTerm);
+ promises.push(deferredHistoryResult.promise);
+ }
+
+ function handleRejection(reason) {
+ if (reason == "HTTP request aborted") {
+ // Do nothing since this is normal.
+ return null;
+ }
+ Cu.reportError("SearchSuggestionController rejection: " + reason);
+ return null;
+ }
+ return Promise.all(promises).then(this._dedupeAndReturnResults.bind(this), handleRejection);
+ },
+
+ /**
+ * Stop pending fetches so no results are returned from them.
+ *
+ * Note: If there was no remote results fetched, the fetching cannot be stopped and local results
+ * will still be returned because stopping relies on aborting the XMLHTTPRequest to reject the
+ * promise for Promise.all.
+ */
+ stop: function() {
+ if (this._request) {
+ this._request.abort();
+ } else if (!this.maxRemoteResults) {
+ Cu.reportError("SearchSuggestionController: Cannot stop fetching if remote results were not "+
+ "requested");
+ }
+ this._reset();
+ },
+
+ // Private methods
+
+ _fetchFormHistory: function(searchTerm) {
+ let deferredFormHistory = Promise.defer();
+
+ let acSearchObserver = {
+ // Implements nsIAutoCompleteSearch
+ onSearchResult: (search, result) => {
+ this._formHistoryResult = result;
+
+ if (this._request) {
+ this._remoteResultTimer = Cc["@mozilla.org/timer;1"].
+ createInstance(Ci.nsITimer);
+ this._remoteResultTimer.initWithCallback(this._onRemoteTimeout.bind(this),
+ this.remoteTimeout || REMOTE_TIMEOUT,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+
+ switch (result.searchResult) {
+ case Ci.nsIAutoCompleteResult.RESULT_SUCCESS:
+ case Ci.nsIAutoCompleteResult.RESULT_NOMATCH:
+ if (result.searchString !== this._searchString) {
+ deferredFormHistory.resolve("Unexpected response, this._searchString does not match form history response");
+ return;
+ }
+ let fhEntries = [];
+ for (let i = 0; i < result.matchCount; ++i) {
+ fhEntries.push(result.getValueAt(i));
+ }
+ deferredFormHistory.resolve({
+ result: fhEntries,
+ formHistoryResult: result,
+ });
+ break;
+ case Ci.nsIAutoCompleteResult.RESULT_FAILURE:
+ case Ci.nsIAutoCompleteResult.RESULT_IGNORED:
+ deferredFormHistory.resolve("Form History returned RESULT_FAILURE or RESULT_IGNORED");
+ break;
+ }
+ },
+ };
+
+ let formHistory = Cc["@mozilla.org/autocomplete/search;1?name=form-history"].
+ createInstance(Ci.nsIAutoCompleteSearch);
+ formHistory.startSearch(searchTerm, this.formHistoryParam || DEFAULT_FORM_HISTORY_PARAM,
+ this._formHistoryResult,
+ acSearchObserver);
+ return deferredFormHistory;
+ },
+
+ /**
+ * Fetch suggestions from the search engine over the network.
+ */
+ _fetchRemote: function(searchTerm, engine, privateMode) {
+ let deferredResponse = Promise.defer();
+ this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsIXMLHttpRequest);
+ let submission = engine.getSubmission(searchTerm,
+ SEARCH_RESPONSE_SUGGESTION_JSON);
+ let method = (submission.postData ? "POST" : "GET");
+ this._request.open(method, submission.uri.spec, true);
+ if (this._request.channel instanceof Ci.nsIPrivateBrowsingChannel) {
+ this._request.channel.setPrivate(privateMode);
+ }
+ this._request.mozBackgroundRequest = true; // suppress dialogs and fail silently
+
+ this._request.addEventListener("load", this._onRemoteLoaded.bind(this, deferredResponse));
+ this._request.addEventListener("error", (evt) => deferredResponse.resolve("HTTP error"));
+ // Reject for an abort assuming it's always from .stop() in which case we shouldn't return local
+ // or remote results for existing searches.
+ this._request.addEventListener("abort", (evt) => deferredResponse.reject("HTTP request aborted"));
+
+ this._request.send(submission.postData);
+
+ return deferredResponse;
+ },
+
+ /**
+ * Called when the request completed successfully (thought the HTTP status could be anything)
+ * so we can handle the response data.
+ * @private
+ */
+ _onRemoteLoaded: function(deferredResponse) {
+ if (!this._request) {
+ deferredResponse.resolve("Got HTTP response after the request was cancelled");
+ return;
+ }
+
+ let status, serverResults;
+ try {
+ status = this._request.status;
+ } catch (e) {
+ // The XMLHttpRequest can throw NS_ERROR_NOT_AVAILABLE.
+ deferredResponse.resolve("Unknown HTTP status: " + e);
+ return;
+ }
+
+ if (status != HTTP_OK || this._request.responseText == "") {
+ deferredResponse.resolve("Non-200 status or empty HTTP response: " + status);
+ return;
+ }
+
+ try {
+ serverResults = JSON.parse(this._request.responseText);
+ } catch(ex) {
+ deferredResponse.resolve("Failed to parse suggestion JSON: " + ex);
+ return;
+ }
+
+ if (!serverResults[0] ||
+ this._searchString.localeCompare(serverResults[0], undefined,
+ { sensitivity: "base" })) {
+ // something is wrong here so drop remote results
+ deferredResponse.resolve("Unexpected response, this._searchString does not match remote response");
+ return;
+ }
+ let results = serverResults[1] || [];
+ deferredResponse.resolve({ result: results });
+ },
+
+ /**
+ * Called when this._remoteResultTimer fires indicating the remote request took too long.
+ */
+ _onRemoteTimeout: function () {
+ this._request = null;
+
+ // FIXME: bug 387341
+ // Need to break the cycle between us and the timer.
+ this._remoteResultTimer = null;
+
+ // The XMLHTTPRequest for suggest results is taking too long
+ // so send out the form history results and cancel the request.
+ if (this._deferredRemoteResult) {
+ this._deferredRemoteResult.resolve("HTTP Timeout");
+ this._deferredRemoteResult = null;
+ }
+ },
+
+ /**
+ * @param {Array} suggestResults - an array of result objects from different sources (local or remote)
+ * @return {Object}
+ */
+ _dedupeAndReturnResults: function(suggestResults) {
+ if (this._searchString === null) {
+ // _searchString can be null if stop() was called and remote suggestions
+ // were disabled (stopping if we are fetching remote suggestions will
+ // cause a promise rejection before we reach _dedupeAndReturnResults).
+ return null;
+ }
+
+ let results = {
+ term: this._searchString,
+ remote: [],
+ local: [],
+ formHistoryResult: null,
+ };
+
+ for (let result of suggestResults) {
+ if (typeof result === "string") { // Failure message
+ Cu.reportError("SearchSuggestionController: " + result);
+ } else if (result.formHistoryResult) { // Local results have a formHistoryResult property.
+ results.formHistoryResult = result.formHistoryResult;
+ results.local = result.result || [];
+ } else { // Remote result
+ results.remote = result.result || [];
+ }
+ }
+
+ // If we have remote results, cap the number of local results
+ if (results.remote.length) {
+ results.local = results.local.slice(0, this.maxLocalResults);
+ }
+
+ // We don't want things to appear in both history and suggestions so remove entries from
+ // remote results that are already in local.
+ if (results.remote.length && results.local.length) {
+ for (let i = 0; i < results.local.length; ++i) {
+ let term = results.local[i];
+ let dupIndex = results.remote.indexOf(term);
+ if (dupIndex != -1) {
+ results.remote.splice(dupIndex, 1);
+ }
+ }
+ }
+
+ // Trim the number of results to the maximum requested (now that we've pruned dupes).
+ results.remote =
+ results.remote.slice(0, this.maxRemoteResults - results.local.length);
+
+ if (this._callback) {
+ this._callback(results);
+ }
+ this._reset();
+
+ return results;
+ },
+
+ _reset: function() {
+ this._request = null;
+ if (this._remoteResultTimer) {
+ this._remoteResultTimer.cancel();
+ this._remoteResultTimer = null;
+ }
+ this._deferredRemoteResult = null;
+ this._searchString = null;
+ },
+};
+
+/**
+ * Determines whether the given engine offers search suggestions.
+ *
+ * @param {nsISearchEngine} engine - The search engine
+ * @return {boolean} True if the engine offers suggestions and false otherwise.
+ */
+this.SearchSuggestionController.engineOffersSuggestions = function(engine) {
+ return engine.supportsResponseType(SEARCH_RESPONSE_SUGGESTION_JSON);
+};
diff --git a/components/search/src/nsSearchService.js b/components/search/src/nsSearchService.js
new file mode 100644
index 000000000..d85146e08
--- /dev/null
+++ b/components/search/src/nsSearchService.js
@@ -0,0 +1,4524 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/Promise.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+ "resource://gre/modules/AsyncShutdown.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+ "resource://gre/modules/DeferredTask.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SearchStaticData",
+ "resource://gre/modules/SearchStaticData.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
+ "resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
+ "resource://gre/modules/Timer.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gTextToSubURI",
+ "@mozilla.org/intl/texttosuburi;1",
+ "nsITextToSubURI");
+
+Cu.importGlobalProperties(["XMLHttpRequest"]);
+
+// A text encoder to UTF8, used whenever we commit the
+// engine metadata to disk.
+XPCOMUtils.defineLazyGetter(this, "gEncoder",
+ function() {
+ return new TextEncoder();
+ });
+
+const MODE_RDONLY = 0x01;
+const MODE_WRONLY = 0x02;
+const MODE_CREATE = 0x08;
+const MODE_APPEND = 0x10;
+const MODE_TRUNCATE = 0x20;
+
+// Directory service keys
+const NS_APP_SEARCH_DIR_LIST = "SrchPluginsDL";
+const NS_APP_DISTRIBUTION_SEARCH_DIR_LIST = "SrchPluginsDistDL";
+const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns";
+const NS_APP_SEARCH_DIR = "SrchPlugns";
+const NS_APP_USER_PROFILE_50_DIR = "ProfD";
+
+// Loading plugins from NS_APP_SEARCH_DIR is no longer supported.
+// Instead, we now load plugins from APP_SEARCH_PREFIX, where a
+// list.txt file needs to exist to list available engines.
+const APP_SEARCH_PREFIX = "resource://search-plugins/";
+
+// Search engine "locations". If this list is changed, be sure to update
+// the engine's _isDefault function accordingly.
+const SEARCH_APP_DIR = 1;
+const SEARCH_PROFILE_DIR = 2;
+const SEARCH_IN_EXTENSION = 3;
+const SEARCH_JAR = 4;
+
+// See documentation in nsIBrowserSearchService.idl.
+const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
+const QUIT_APPLICATION_TOPIC = "quit-application";
+
+const SEARCH_ENGINE_REMOVED = "engine-removed";
+const SEARCH_ENGINE_ADDED = "engine-added";
+const SEARCH_ENGINE_CHANGED = "engine-changed";
+const SEARCH_ENGINE_LOADED = "engine-loaded";
+const SEARCH_ENGINE_CURRENT = "engine-current";
+const SEARCH_ENGINE_DEFAULT = "engine-default";
+
+// The following constants are left undocumented in nsIBrowserSearchService.idl
+// For the moment, they are meant for testing/debugging purposes only.
+
+/**
+ * Topic used for events involving the service itself.
+ */
+const SEARCH_SERVICE_TOPIC = "browser-search-service";
+
+/**
+ * Sent whenever metadata is fully written to disk.
+ */
+const SEARCH_SERVICE_METADATA_WRITTEN = "write-metadata-to-disk-complete";
+
+/**
+ * Sent whenever the cache is fully written to disk.
+ */
+const SEARCH_SERVICE_CACHE_WRITTEN = "write-cache-to-disk-complete";
+
+// Delay for lazy serialization (ms)
+const LAZY_SERIALIZE_DELAY = 100;
+
+// Delay for batching invalidation of the JSON cache (ms)
+const CACHE_INVALIDATION_DELAY = 1000;
+
+// Current cache version. This should be incremented if the format of the cache
+// file is modified.
+const CACHE_VERSION = 7;
+
+const ICON_DATAURL_PREFIX = "data:image/x-icon;base64,";
+
+const NEW_LINES = /(\r\n|\r|\n)/;
+
+// Set an arbitrary cap on the maximum icon size. Without this, large icons can
+// cause big delays when loading them at startup.
+const MAX_ICON_SIZE = 32768;
+
+// Default charset to use for sending search parameters. ISO-8859-1 is used to
+// match previous nsInternetSearchService behavior.
+const DEFAULT_QUERY_CHARSET = "ISO-8859-1";
+
+const SEARCH_BUNDLE = "chrome://global/locale/search/search.properties";
+const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";
+
+const OPENSEARCH_NS_10 = "http://a9.com/-/spec/opensearch/1.0/";
+const OPENSEARCH_NS_11 = "http://a9.com/-/spec/opensearch/1.1/";
+
+// Although the specification at http://opensearch.a9.com/spec/1.1/description/
+// gives the namespace names defined above, many existing OpenSearch engines
+// are using the following versions. We therefore allow either.
+const OPENSEARCH_NAMESPACES = [
+ OPENSEARCH_NS_11, OPENSEARCH_NS_10,
+ "http://a9.com/-/spec/opensearchdescription/1.1/",
+ "http://a9.com/-/spec/opensearchdescription/1.0/"
+];
+
+const OPENSEARCH_LOCALNAME = "OpenSearchDescription";
+
+const MOZSEARCH_NS_10 = "http://www.mozilla.org/2006/browser/search/";
+const MOZSEARCH_LOCALNAME = "SearchPlugin";
+
+const URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
+const URLTYPE_SEARCH_HTML = "text/html";
+const URLTYPE_OPENSEARCH = "application/opensearchdescription+xml";
+
+// Empty base document used to serialize engines to file.
+const EMPTY_DOC = "<?xml version=\"1.0\"?>\n" +
+ "<" + MOZSEARCH_LOCALNAME +
+ " xmlns=\"" + MOZSEARCH_NS_10 + "\"" +
+ " xmlns:os=\"" + OPENSEARCH_NS_11 + "\"" +
+ "/>";
+
+const BROWSER_SEARCH_PREF = "browser.search.";
+const LOCALE_PREF = "general.useragent.locale";
+
+const USER_DEFINED = "{searchTerms}";
+
+// Custom search parameters
+#ifdef MOZ_OFFICIAL_BRANDING
+const MOZ_OFFICIAL = "official";
+#else
+const MOZ_OFFICIAL = "unofficial";
+#endif
+#expand const MOZ_DISTRIBUTION_ID = __MOZ_DISTRIBUTION_ID__;
+
+const MOZ_PARAM_LOCALE = /\{moz:locale\}/g;
+const MOZ_PARAM_DIST_ID = /\{moz:distributionID\}/g;
+const MOZ_PARAM_OFFICIAL = /\{moz:official\}/g;
+
+// Supported OpenSearch parameters
+// See http://opensearch.a9.com/spec/1.1/querysyntax/#core
+const OS_PARAM_USER_DEFINED = /\{searchTerms\??\}/g;
+const OS_PARAM_INPUT_ENCODING = /\{inputEncoding\??\}/g;
+const OS_PARAM_LANGUAGE = /\{language\??\}/g;
+const OS_PARAM_OUTPUT_ENCODING = /\{outputEncoding\??\}/g;
+
+// Default values
+const OS_PARAM_LANGUAGE_DEF = "*";
+const OS_PARAM_OUTPUT_ENCODING_DEF = "UTF-8";
+const OS_PARAM_INPUT_ENCODING_DEF = "UTF-8";
+
+// "Unsupported" OpenSearch parameters. For example, we don't support
+// page-based results, so if the engine requires that we send the "page index"
+// parameter, we'll always send "1".
+const OS_PARAM_COUNT = /\{count\??\}/g;
+const OS_PARAM_START_INDEX = /\{startIndex\??\}/g;
+const OS_PARAM_START_PAGE = /\{startPage\??\}/g;
+
+// Default values
+const OS_PARAM_COUNT_DEF = "20"; // 20 results
+const OS_PARAM_START_INDEX_DEF = "1"; // start at 1st result
+const OS_PARAM_START_PAGE_DEF = "1"; // 1st page
+
+// Optional parameter
+const OS_PARAM_OPTIONAL = /\{(?:\w+:)?\w+\?\}/g;
+
+// A array of arrays containing parameters that we don't fully support, and
+// their default values. We will only send values for these parameters if
+// required, since our values are just really arbitrary "guesses" that should
+// give us the output we want.
+var OS_UNSUPPORTED_PARAMS = [
+ [OS_PARAM_COUNT, OS_PARAM_COUNT_DEF],
+ [OS_PARAM_START_INDEX, OS_PARAM_START_INDEX_DEF],
+ [OS_PARAM_START_PAGE, OS_PARAM_START_PAGE_DEF],
+];
+
+// The default engine update interval, in days. This is only used if an engine
+// specifies an updateURL, but not an updateInterval.
+const SEARCH_DEFAULT_UPDATE_INTERVAL = 7;
+
+// The default interval before checking again for the name of the
+// default engine for the region, in seconds. Only used if the response
+// from the server doesn't specify an interval.
+const SEARCH_GEO_DEFAULT_UPDATE_INTERVAL = 2592000; // 30 days.
+
+this.__defineGetter__("FileUtils", function() {
+ delete this.FileUtils;
+ Components.utils.import("resource://gre/modules/FileUtils.jsm");
+ return FileUtils;
+});
+
+this.__defineGetter__("NetUtil", function() {
+ delete this.NetUtil;
+ Components.utils.import("resource://gre/modules/NetUtil.jsm");
+ return NetUtil;
+});
+
+this.__defineGetter__("gChromeReg", function() {
+ delete this.gChromeReg;
+ return this.gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Ci.nsIChromeRegistry);
+});
+
+/**
+ * Prefixed to all search debug output.
+ */
+const SEARCH_LOG_PREFIX = "*** Search: ";
+
+/**
+ * Outputs aText to the JavaScript console as well as to stdout.
+ */
+function DO_LOG(aText) {
+ dump(SEARCH_LOG_PREFIX + aText + "\n");
+ Services.console.logStringMessage(aText);
+}
+
+#ifdef DEBUG
+/**
+ * In debug builds, use a live, pref-based (browser.search.log) LOG function
+ * to allow enabling/disabling without a restart.
+ */
+function PREF_LOG(aText) {
+ if (Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "log", false))
+ DO_LOG(aText);
+}
+var LOG = PREF_LOG;
+
+#else
+
+/**
+ * Otherwise, don't log at all by default. This can be overridden at startup
+ * by the pref, see SearchService's _init method.
+ */
+var LOG = function(){};
+
+#endif
+
+/**
+ * Presents an assertion dialog in non-release builds and throws.
+ * @param message
+ * A message to display
+ * @param resultCode
+ * The NS_ERROR_* value to throw.
+ * @throws resultCode
+ */
+function ERROR(message, resultCode) {
+ NS_ASSERT(false, SEARCH_LOG_PREFIX + message);
+ throw Components.Exception(message, resultCode);
+}
+
+/**
+ * Logs the failure message (if browser.search.log is enabled) and throws.
+ * @param message
+ * A message to display
+ * @param resultCode
+ * The NS_ERROR_* value to throw.
+ * @throws resultCode or NS_ERROR_INVALID_ARG if resultCode isn't specified.
+ */
+function FAIL(message, resultCode) {
+ LOG(message);
+ throw Components.Exception(message, resultCode || Cr.NS_ERROR_INVALID_ARG);
+}
+
+/**
+ * Truncates big blobs of (data-)URIs to console-friendly sizes
+ * @param str
+ * String to tone down
+ * @param len
+ * Maximum length of the string to return. Defaults to the length of a tweet.
+ */
+function limitURILength(str, len) {
+ len = len || 140;
+ if (str.length > len)
+ return str.slice(0, len) + "...";
+ return str;
+}
+
+/**
+ * Ensures an assertion is met before continuing. Should be used to indicate
+ * fatal errors.
+ * @param assertion
+ * An assertion that must be met
+ * @param message
+ * A message to display if the assertion is not met
+ * @param resultCode
+ * The NS_ERROR_* value to throw if the assertion is not met
+ * @throws resultCode
+ */
+function ENSURE_WARN(assertion, message, resultCode) {
+ NS_ASSERT(assertion, SEARCH_LOG_PREFIX + message);
+ if (!assertion)
+ throw Components.Exception(message, resultCode);
+}
+
+function loadListener(aChannel, aEngine, aCallback) {
+ this._channel = aChannel;
+ this._bytes = [];
+ this._engine = aEngine;
+ this._callback = aCallback;
+}
+loadListener.prototype = {
+ _callback: null,
+ _channel: null,
+ _countRead: 0,
+ _engine: null,
+ _stream: null,
+
+ QueryInterface: function SRCH_loadQI(aIID) {
+ if (aIID.equals(Ci.nsISupports) ||
+ aIID.equals(Ci.nsIRequestObserver) ||
+ aIID.equals(Ci.nsIStreamListener) ||
+ aIID.equals(Ci.nsIChannelEventSink) ||
+ aIID.equals(Ci.nsIInterfaceRequestor) ||
+ // See FIXME comment below
+ aIID.equals(Ci.nsIHttpEventSink) ||
+ aIID.equals(Ci.nsIProgressEventSink) ||
+ false)
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIRequestObserver
+ onStartRequest: function SRCH_loadStartR(aRequest, aContext) {
+ LOG("loadListener: Starting request: " + aRequest.name);
+ this._stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ },
+
+ onStopRequest: function SRCH_loadStopR(aRequest, aContext, aStatusCode) {
+ LOG("loadListener: Stopping request: " + aRequest.name);
+
+ var requestFailed = !Components.isSuccessCode(aStatusCode);
+ if (!requestFailed && (aRequest instanceof Ci.nsIHttpChannel))
+ requestFailed = !aRequest.requestSucceeded;
+
+ if (requestFailed || this._countRead == 0) {
+ LOG("loadListener: request failed!");
+ // send null so the callback can deal with the failure
+ this._callback(null, this._engine);
+ } else
+ this._callback(this._bytes, this._engine);
+ this._channel = null;
+ this._engine = null;
+ },
+
+ // nsIStreamListener
+ onDataAvailable: function SRCH_loadDAvailable(aRequest, aContext,
+ aInputStream, aOffset,
+ aCount) {
+ this._stream.setInputStream(aInputStream);
+
+ // Get a byte array of the data
+ this._bytes = this._bytes.concat(this._stream.readByteArray(aCount));
+ this._countRead += aCount;
+ },
+
+ // nsIChannelEventSink
+ asyncOnChannelRedirect: function SRCH_loadCRedirect(aOldChannel, aNewChannel,
+ aFlags, callback) {
+ this._channel = aNewChannel;
+ callback.onRedirectVerifyCallback(Components.results.NS_OK);
+ },
+
+ // nsIInterfaceRequestor
+ getInterface: function SRCH_load_GI(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ // FIXME: bug 253127
+ // nsIHttpEventSink
+ onRedirect: function (aChannel, aNewChannel) {},
+ // nsIProgressEventSink
+ onProgress: function (aRequest, aContext, aProgress, aProgressMax) {},
+ onStatus: function (aRequest, aContext, aStatus, aStatusArg) {}
+}
+
+function isPartnerBuild() {
+ try {
+ let distroID = Services.prefs.getCharPref("distribution.id");
+
+ // Mozilla-provided builds (i.e. funnelcake) are not partner builds
+ if (distroID && !distroID.startsWith("mozilla")) {
+ return true;
+ }
+ } catch (e) {}
+
+ return false;
+}
+
+function getVerificationHash(aName) {
+ let disclaimer = "By modifying this file, I agree that I am doing so " +
+ "only within $appName itself, using official, user-driven search " +
+ "engine selection processes, and in a way which does not circumvent " +
+ "user consent. I acknowledge that any attempt to change this file " +
+ "from outside of $appName is a malicious act, and will be responded " +
+ "to accordingly."
+
+ let salt = OS.Path.basename(OS.Constants.Path.profileDir) + aName +
+ disclaimer.replace(/\$appName/g, Services.appinfo.name);
+
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+
+ // Data is an array of bytes.
+ let data = converter.convertToByteArray(salt, {});
+ let hasher = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ hasher.init(hasher.SHA256);
+ hasher.update(data, data.length);
+
+ return hasher.finish(true);
+}
+
+/**
+ * Safely close a nsISafeOutputStream.
+ * @param aFOS
+ * The file output stream to close.
+ */
+function closeSafeOutputStream(aFOS) {
+ if (aFOS instanceof Ci.nsISafeOutputStream) {
+ try {
+ aFOS.finish();
+ return;
+ } catch (e) { }
+ }
+ aFOS.close();
+}
+
+/**
+ * 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) {
+ try {
+ return NetUtil.newURI(aURLSpec, aCharset);
+ } catch (ex) { }
+
+ return null;
+}
+
+/**
+ * Wrapper function for nsIIOService::newChannel2.
+ * @param url
+ * The URL string from which to create an nsIChannel.
+ * @returns an nsIChannel object, or null if the url is invalid.
+ */
+function makeChannel(url) {
+ try {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
+ } catch (ex) { }
+
+ return null;
+}
+
+/**
+ * Gets a directory from the directory service.
+ * @param aKey
+ * The directory service key indicating the directory to get.
+ */
+function getDir(aKey, aIFace) {
+ if (!aKey)
+ FAIL("getDir requires a directory key!");
+
+ return Services.dirsvc.get(aKey, aIFace || Ci.nsIFile);
+}
+
+/**
+ * Gets the current value of the locale. It's possible for this preference to
+ * be localized, so we have to do a little extra work here. Similar code
+ * exists in nsHttpHandler.cpp when building the UA string.
+ */
+function getLocale() {
+ let locale = getLocalizedPref(LOCALE_PREF);
+ if (locale)
+ return locale;
+
+ // Not localized.
+ return Services.prefs.getCharPref(LOCALE_PREF);
+}
+
+/**
+ * Wrapper for nsIPrefBranch::getComplexValue.
+ * @param aPrefName
+ * The name of the pref to get.
+ * @returns aDefault if the requested pref doesn't exist.
+ */
+function getLocalizedPref(aPrefName, aDefault) {
+ const nsIPLS = Ci.nsIPrefLocalizedString;
+ try {
+ return Services.prefs.getComplexValue(aPrefName, nsIPLS).data;
+ } catch (ex) {}
+
+ return aDefault;
+}
+
+/**
+ * Wrapper for nsIPrefBranch::setComplexValue.
+ * @param aPrefName
+ * The name of the pref to set.
+ */
+function setLocalizedPref(aPrefName, aValue) {
+ const nsIPLS = Ci.nsIPrefLocalizedString;
+ try {
+ var pls = Components.classes["@mozilla.org/pref-localizedstring;1"]
+ .createInstance(Ci.nsIPrefLocalizedString);
+ pls.data = aValue;
+ Services.prefs.setComplexValue(aPrefName, nsIPLS, pls);
+ } catch (ex) {}
+}
+
+/**
+ * Get a unique nsIFile object with a sanitized name, based on the engine name.
+ * @param aName
+ * A name to "sanitize". Can be an empty string, in which case a random
+ * 8 character filename will be produced.
+ * @returns A nsIFile object in the user's search engines directory with a
+ * unique sanitized name.
+ */
+function getSanitizedFile(aName) {
+ var fileName = sanitizeName(aName) + ".xml";
+ var file = getDir(NS_APP_USER_SEARCH_DIR);
+ file.append(fileName);
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ return file;
+}
+
+/**
+ * @return a sanitized name to be used as a filename, or a random name
+ * if a sanitized name cannot be obtained (if aName contains
+ * no valid characters).
+ */
+function sanitizeName(aName) {
+ const maxLength = 60;
+ const minLength = 1;
+ var name = aName.toLowerCase();
+ name = name.replace(/\s+/g, "-");
+ name = name.replace(/[^-a-z0-9]/g, "");
+
+ // Use a random name if our input had no valid characters.
+ if (name.length < minLength)
+ name = Math.random().toString(36).replace(/^.*\./, '');
+
+ // Force max length.
+ return name.substring(0, maxLength);
+}
+
+/**
+ * Retrieve a pref from the search param branch.
+ *
+ * @param prefName
+ * The name of the pref.
+ **/
+function getMozParamPref(prefName) {
+ return Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "param." + prefName);
+}
+
+/**
+ * Notifies watchers of SEARCH_ENGINE_TOPIC about changes to an engine or to
+ * the state of the search service.
+ *
+ * @param aEngine
+ * The nsISearchEngine object to which the change applies.
+ * @param aVerb
+ * A verb describing the change.
+ *
+ * @see nsIBrowserSearchService.idl
+ */
+var gInitialized = false;
+function notifyAction(aEngine, aVerb) {
+ if (gInitialized) {
+ LOG("NOTIFY: Engine: \"" + aEngine.name + "\"; Verb: \"" + aVerb + "\"");
+ Services.obs.notifyObservers(aEngine, SEARCH_ENGINE_TOPIC, aVerb);
+ }
+}
+
+function parseJsonFromStream(aInputStream) {
+ const json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+ const data = json.decodeFromStream(aInputStream, aInputStream.available());
+ return data;
+}
+
+/**
+ * Simple object representing a name/value pair.
+ */
+function QueryParameter(aName, aValue, aPurpose) {
+ if (!aName || (aValue == null))
+ FAIL("missing name or value for QueryParameter!");
+
+ this.name = aName;
+ this.value = aValue;
+ this.purpose = aPurpose;
+}
+
+/**
+ * Perform OpenSearch parameter substitution on aParamValue.
+ *
+ * @param aParamValue
+ * A string containing OpenSearch search parameters.
+ * @param aSearchTerms
+ * The user-provided search terms. This string will inserted into
+ * aParamValue as the value of the OS_PARAM_USER_DEFINED parameter.
+ * This value must already be escaped appropriately - it is inserted
+ * as-is.
+ * @param aEngine
+ * The engine which owns the string being acted on.
+ *
+ * @see http://opensearch.a9.com/spec/1.1/querysyntax/#core
+ */
+function ParamSubstitution(aParamValue, aSearchTerms, aEngine) {
+ var value = aParamValue;
+
+ var distributionID = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "distributionID",
+ MOZ_DISTRIBUTION_ID || "");
+ var official = MOZ_OFFICIAL;
+ try {
+ if (Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "official"))
+ official = "official";
+ else
+ official = "unofficial";
+ }
+ catch (ex) { }
+
+ // Custom search parameters. These are only available to default search
+ // engines.
+ if (aEngine._isDefault) {
+ value = value.replace(MOZ_PARAM_LOCALE, getLocale());
+ value = value.replace(MOZ_PARAM_DIST_ID, distributionID);
+ value = value.replace(MOZ_PARAM_OFFICIAL, official);
+ }
+
+ // Insert the OpenSearch parameters we're confident about
+ value = value.replace(OS_PARAM_USER_DEFINED, aSearchTerms);
+ value = value.replace(OS_PARAM_INPUT_ENCODING, aEngine.queryCharset);
+ value = value.replace(OS_PARAM_LANGUAGE,
+ getLocale() || OS_PARAM_LANGUAGE_DEF);
+ value = value.replace(OS_PARAM_OUTPUT_ENCODING,
+ OS_PARAM_OUTPUT_ENCODING_DEF);
+
+ // Replace any optional parameters
+ value = value.replace(OS_PARAM_OPTIONAL, "");
+
+ // Insert any remaining required params with our default values
+ for (var i = 0; i < OS_UNSUPPORTED_PARAMS.length; ++i) {
+ value = value.replace(OS_UNSUPPORTED_PARAMS[i][0],
+ OS_UNSUPPORTED_PARAMS[i][1]);
+ }
+
+ return value;
+}
+
+/**
+ * Creates an engineURL object, which holds the query URL and all parameters.
+ *
+ * @param aType
+ * A string containing the name of the MIME type of the search results
+ * returned by this URL.
+ * @param aMethod
+ * The HTTP request method. Must be a case insensitive value of either
+ * "GET" or "POST".
+ * @param aTemplate
+ * The URL to which search queries should be sent. For GET requests,
+ * must contain the string "{searchTerms}", to indicate where the user
+ * entered search terms should be inserted.
+ * @param aResultDomain
+ * The root domain for this URL. Defaults to the template's host.
+ *
+ * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
+ *
+ * @throws NS_ERROR_NOT_IMPLEMENTED if aType is unsupported.
+ */
+function EngineURL(aType, aMethod, aTemplate, aResultDomain) {
+ if (!aType || !aMethod || !aTemplate)
+ FAIL("missing type, method or template for EngineURL!");
+
+ var method = aMethod.toUpperCase();
+ var type = aType.toLowerCase();
+
+ if (method != "GET" && method != "POST")
+ FAIL("method passed to EngineURL must be \"GET\" or \"POST\"");
+
+ this.type = type;
+ this.method = method;
+ this.params = [];
+ this.rels = [];
+ // Don't serialize expanded mozparams
+ this.mozparams = {};
+
+ var templateURI = makeURI(aTemplate);
+ if (!templateURI)
+ FAIL("new EngineURL: template is not a valid URI!", Cr.NS_ERROR_FAILURE);
+
+ switch (templateURI.scheme) {
+ case "http":
+ case "https":
+ // Disable these for now, see bug 295018
+ // case "file":
+ // case "resource":
+ this.template = aTemplate;
+ break;
+ default:
+ FAIL("new EngineURL: template uses invalid scheme!", Cr.NS_ERROR_FAILURE);
+ }
+
+ // If no resultDomain was specified in the engine definition file, use the
+ // host from the template.
+ this.resultDomain = aResultDomain || templateURI.host;
+ // We never want to return a "www." prefix, so eventually strip it.
+ if (this.resultDomain.startsWith("www.")) {
+ this.resultDomain = this.resultDomain.substr(4);
+ }
+}
+EngineURL.prototype = {
+
+ addParam: function SRCH_EURL_addParam(aName, aValue, aPurpose) {
+ this.params.push(new QueryParameter(aName, aValue, aPurpose));
+ },
+
+ // Note: This method requires that aObj has a unique name or the previous MozParams entry with
+ // that name will be overwritten.
+ _addMozParam: function SRCH_EURL__addMozParam(aObj) {
+ aObj.mozparam = true;
+ this.mozparams[aObj.name] = aObj;
+ },
+
+ getSubmission: function SRCH_EURL_getSubmission(aSearchTerms, aEngine, aPurpose) {
+ var url = ParamSubstitution(this.template, aSearchTerms, aEngine);
+ // Default to an empty string if the purpose is not provided so that default purpose params
+ // (purpose="") work consistently rather than having to define "null" and "" purposes.
+ var purpose = aPurpose || "";
+
+ // If the 'system' purpose isn't defined in the plugin, fallback to 'searchbar'.
+ if (purpose == "system" && !this.params.some(p => p.purpose == "system"))
+ purpose = "searchbar";
+
+ // Create an application/x-www-form-urlencoded representation of our params
+ // (name=value&name=value&name=value)
+ var dataString = "";
+ for (var i = 0; i < this.params.length; ++i) {
+ var param = this.params[i];
+
+ // If this parameter has a purpose, only add it if the purpose matches
+ if (param.purpose !== undefined && param.purpose != purpose)
+ continue;
+
+ var value = ParamSubstitution(param.value, aSearchTerms, aEngine);
+
+ dataString += (i > 0 ? "&" : "") + param.name + "=" + value;
+ }
+
+ var postData = null;
+ let postDataString = null;
+ if (this.method == "GET") {
+ // GET method requests have no post data, and append the encoded
+ // query string to the url...
+ if (url.indexOf("?") == -1 && dataString)
+ url += "?";
+ url += dataString;
+ } else if (this.method == "POST") {
+ postDataString = dataString;
+ // POST method requests must wrap the encoded text in a MIME
+ // stream and supply that as POSTDATA.
+ var stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stringStream.data = dataString;
+
+ postData = Cc["@mozilla.org/network/mime-input-stream;1"].
+ createInstance(Ci.nsIMIMEInputStream);
+ postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
+ postData.addContentLength = true;
+ postData.setData(stringStream);
+ }
+
+ return new Submission(makeURI(url), postData, postDataString);
+ },
+
+ _getTermsParameterName: function SRCH_EURL__getTermsParameterName() {
+ let queryParam = this.params.find(p => p.value == USER_DEFINED);
+ return queryParam ? queryParam.name : "";
+ },
+
+ _hasRelation: function SRC_EURL__hasRelation(aRel) {
+ return this.rels.some(e => e == aRel.toLowerCase());
+ },
+
+ _initWithJSON: function SRC_EURL__initWithJSON(aJson, aEngine) {
+ if (!aJson.params)
+ return;
+
+ this.rels = aJson.rels;
+
+ for (let i = 0; i < aJson.params.length; ++i) {
+ let param = aJson.params[i];
+ if (param.mozparam) {
+ if (param.condition == "pref") {
+ let value = getMozParamPref(param.pref);
+ this.addParam(param.name, value);
+ }
+ this._addMozParam(param);
+ }
+ else
+ this.addParam(param.name, param.value, param.purpose);
+ }
+ },
+
+ /**
+ * Creates a JavaScript object that represents this URL.
+ * @returns An object suitable for serialization as JSON.
+ **/
+ toJSON: function SRCH_EURL_toJSON() {
+ var json = {
+ template: this.template,
+ rels: this.rels,
+ resultDomain: this.resultDomain
+ };
+
+ if (this.type != URLTYPE_SEARCH_HTML)
+ json.type = this.type;
+ if (this.method != "GET")
+ json.method = this.method;
+
+ function collapseMozParams(aParam) {
+ return this.mozparams[aParam.name] || aParam;
+ }
+ json.params = this.params.map(collapseMozParams, this);
+
+ return json;
+ },
+
+ /**
+ * Serializes the engine object to a OpenSearch Url element.
+ * @param aDoc
+ * The document to use to create the Url element.
+ * @param aElement
+ * The element to which the created Url element is appended.
+ *
+ * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
+ */
+ _serializeToElement: function SRCH_EURL_serializeToEl(aDoc, aElement) {
+ var url = aDoc.createElementNS(OPENSEARCH_NS_11, "Url");
+ url.setAttribute("type", this.type);
+ url.setAttribute("method", this.method);
+ url.setAttribute("template", this.template);
+ if (this.rels.length)
+ url.setAttribute("rel", this.rels.join(" "));
+ if (this.resultDomain)
+ url.setAttribute("resultDomain", this.resultDomain);
+
+ for (var i = 0; i < this.params.length; ++i) {
+ var param = aDoc.createElementNS(OPENSEARCH_NS_11, "Param");
+ param.setAttribute("name", this.params[i].name);
+ param.setAttribute("value", this.params[i].value);
+ url.appendChild(aDoc.createTextNode("\n "));
+ url.appendChild(param);
+ }
+ url.appendChild(aDoc.createTextNode("\n"));
+ aElement.appendChild(url);
+ }
+};
+
+/**
+ * nsISearchEngine constructor.
+ * @param aLocation
+ * A nsILocalFile or nsIURI object representing the location of the
+ * search engine data file.
+ * @param aIsReadOnly
+ * Boolean indicating whether the engine should be treated as read-only.
+ * Read only engines cannot be serialized to file.
+ */
+function Engine(aLocation, aIsReadOnly) {
+ this._readOnly = aIsReadOnly;
+ this._urls = [];
+
+ if (aLocation.type) {
+ if (aLocation.type == "filePath")
+ this._file = aLocation.value;
+ else if (aLocation.type == "uri")
+ this._uri = aLocation.value;
+ } else if (aLocation instanceof Ci.nsILocalFile) {
+ // we already have a file (e.g. loading engines from disk)
+ this._file = aLocation;
+ } else if (aLocation instanceof Ci.nsIURI) {
+ switch (aLocation.scheme) {
+ case "https":
+ case "http":
+ case "ftp":
+ case "data":
+ case "file":
+ case "resource":
+ case "chrome":
+ this._uri = aLocation;
+ break;
+ default:
+ ERROR("Invalid URI passed to the nsISearchEngine constructor",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+ } else
+ ERROR("Engine location is neither a File nor a URI object",
+ Cr.NS_ERROR_INVALID_ARG);
+}
+
+Engine.prototype = {
+ // The engine's alias (can be null). Initialized to |undefined| to indicate
+ // not-initialized-from-engineMetadataService.
+ _alias: undefined,
+ // A distribution-unique identifier for the engine. Either null or set
+ // when loaded. See getter.
+ _identifier: undefined,
+ // The data describing the engine, in the form of an XML document element.
+ _data: null,
+ // Whether or not the engine is readonly.
+ _readOnly: true,
+ // The engine's description
+ _description: "",
+ // Used to store the engine to replace, if we're an update to an existing
+ // engine.
+ _engineToUpdate: null,
+ // The file from which the plugin was loaded.
+ __file: null,
+ get _file() {
+ if (this.__file && !(this.__file instanceof Ci.nsILocalFile)) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.persistentDescriptor = this.__file;
+ return this.__file = file;
+ }
+ return this.__file;
+ },
+ set _file(aValue) {
+ this.__file = aValue;
+ },
+ // Set to true if the engine has a preferred icon (an icon that should not be
+ // overridden by a non-preferred icon).
+ _hasPreferredIcon: null,
+ // The engine's name.
+ _name: null,
+ // The name of the charset used to submit the search terms.
+ _queryCharset: null,
+ // The engine's raw SearchForm value (URL string pointing to a search form).
+ __searchForm: null,
+ get _searchForm() {
+ return this.__searchForm;
+ },
+ set _searchForm(aValue) {
+ if (/^https?:/i.test(aValue))
+ this.__searchForm = aValue;
+ else
+ LOG("_searchForm: Invalid URL dropped for " + this._name ||
+ "the current engine");
+ },
+ // The URI object from which the engine was retrieved.
+ // This is null for engines loaded from disk, but present for engines loaded
+ // from chrome:// URIs.
+ __uri: null,
+ get _uri() {
+ if (this.__uri && !(this.__uri instanceof Ci.nsIURI))
+ this.__uri = makeURI(this.__uri);
+
+ return this.__uri;
+ },
+ set _uri(aValue) {
+ this.__uri = aValue;
+ },
+ // Whether to obtain user confirmation before adding the engine. This is only
+ // used when the engine is first added to the list.
+ _confirm: false,
+ // Whether to set this as the current engine as soon as it is loaded. This
+ // is only used when the engine is first added to the list.
+ _useNow: false,
+ // A function to be invoked when this engine object's addition completes (or
+ // fails). Only used for installation via addEngine.
+ _installCallback: null,
+ // Where the engine was loaded from. Can be one of: SEARCH_APP_DIR,
+ // SEARCH_PROFILE_DIR, SEARCH_IN_EXTENSION.
+ __installLocation: null,
+ // The number of days between update checks for new versions
+ _updateInterval: null,
+ // The url to check at for a new update
+ _updateURL: null,
+ // The url to check for a new icon
+ _iconUpdateURL: null,
+ /* Deferred serialization task. */
+ _lazySerializeTask: null,
+ /* The extension ID if added by an extension. */
+ _extensionID: null,
+
+ /**
+ * Retrieves the data from the engine's file.
+ * The document element is placed in the engine's data field.
+ */
+ _initFromFile: function SRCH_ENG_initFromFile() {
+ if (!this._file || !this._file.exists())
+ FAIL("File must exist before calling initFromFile!", Cr.NS_ERROR_UNEXPECTED);
+
+ var fileInStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+
+ fileInStream.init(this._file, MODE_RDONLY, FileUtils.PERMS_FILE, false);
+
+ var domParser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+ var doc = domParser.parseFromStream(fileInStream, "UTF-8",
+ this._file.fileSize,
+ "text/xml");
+
+ this._data = doc.documentElement;
+ fileInStream.close();
+
+ // Now that the data is loaded, initialize the engine object
+ this._initFromData();
+ },
+
+ /**
+ * Retrieves the data from the engine's file asynchronously.
+ * The document element is placed in the engine's data field.
+ *
+ * @returns {Promise} A promise, resolved successfully if initializing from
+ * data succeeds, rejected if it fails.
+ */
+ _asyncInitFromFile: function SRCH_ENG__asyncInitFromFile() {
+ return Task.spawn(function() {
+ if (!this._file || !(yield OS.File.exists(this._file.path)))
+ FAIL("File must exist before calling initFromFile!", Cr.NS_ERROR_UNEXPECTED);
+
+ let fileURI = NetUtil.ioService.newFileURI(this._file);
+ yield this._retrieveSearchXMLData(fileURI.spec);
+
+ // Now that the data is loaded, initialize the engine object
+ this._initFromData();
+ }.bind(this));
+ },
+
+ /**
+ * Retrieves the engine data from a URI. Initializes the engine, flushes to
+ * disk, and notifies the search service once initialization is complete.
+ */
+ _initFromURIAndLoad: function SRCH_ENG_initFromURIAndLoad() {
+ ENSURE_WARN(this._uri instanceof Ci.nsIURI,
+ "Must have URI when calling _initFromURIAndLoad!",
+ Cr.NS_ERROR_UNEXPECTED);
+
+ LOG("_initFromURIAndLoad: Downloading engine from: \"" + this._uri.spec + "\".");
+
+ var chan = NetUtil.ioService.newChannelFromURI2(this._uri,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_NORMAL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+
+ if (this._engineToUpdate && (chan instanceof Ci.nsIHttpChannel)) {
+ var lastModified = engineMetadataService.getAttr(this._engineToUpdate,
+ "updatelastmodified");
+ if (lastModified)
+ chan.setRequestHeader("If-Modified-Since", lastModified, false);
+ }
+ var listener = new loadListener(chan, this, this._onLoad);
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen(listener, null);
+ },
+
+ /**
+ * Retrieves the engine data from a URI asynchronously and initializes it.
+ *
+ * @returns {Promise} A promise, resolved successfully if retrieveing data
+ * succeeds.
+ */
+ _asyncInitFromURI: function SRCH_ENG__asyncInitFromURI() {
+ return Task.spawn(function() {
+ LOG("_asyncInitFromURI: Loading engine from: \"" + this._uri.spec + "\".");
+ yield this._retrieveSearchXMLData(this._uri.spec);
+ // Now that the data is loaded, initialize the engine object
+ this._initFromData();
+ }.bind(this));
+ },
+
+ /**
+ * Retrieves the engine data for a given URI asynchronously.
+ *
+ * @returns {Promise} A promise, resolved successfully if retrieveing data
+ * succeeds.
+ */
+ _retrieveSearchXMLData: function SRCH_ENG__retrieveSearchXMLData(aURL) {
+ let deferred = Promise.defer();
+ let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsIXMLHttpRequest);
+ request.overrideMimeType("text/xml");
+ request.onload = (aEvent) => {
+ let responseXML = aEvent.target.responseXML;
+ this._data = responseXML.documentElement;
+ deferred.resolve();
+ };
+ request.onerror = function(aEvent) {
+ deferred.resolve();
+ };
+ request.open("GET", aURL, true);
+ request.send();
+
+ return deferred.promise;
+ },
+
+ _initFromURISync: function SRCH_ENG_initFromURISync() {
+ ENSURE_WARN(this._uri instanceof Ci.nsIURI,
+ "Must have URI when calling _initFromURISync!",
+ Cr.NS_ERROR_UNEXPECTED);
+
+ ENSURE_WARN(this._uri.schemeIs("resource"), "_initFromURISync called for non-resource URI",
+ Cr.NS_ERROR_FAILURE);
+
+ LOG("_initFromURISync: Loading engine from: \"" + this._uri.spec + "\".");
+
+ var chan = NetUtil.ioService.newChannelFromURI2(this._uri,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_NORMAL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+
+ var stream = chan.open();
+ var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+ var doc = parser.parseFromStream(stream, "UTF-8", stream.available(), "text/xml");
+
+ this._data = doc.documentElement;
+
+ // Now that the data is loaded, initialize the engine object
+ this._initFromData();
+ },
+
+ /**
+ * Attempts to find an EngineURL object in the set of EngineURLs for
+ * this Engine that has the given type string. (This corresponds to the
+ * "type" attribute in the "Url" node in the OpenSearch spec.)
+ * This method will return the first matching URL object found, or null
+ * if no matching URL is found.
+ *
+ * @param aType string to match the EngineURL's type attribute
+ * @param aRel [optional] only return URLs that with this rel value
+ */
+ _getURLOfType: function SRCH_ENG__getURLOfType(aType, aRel) {
+ for (var i = 0; i < this._urls.length; ++i) {
+ if (this._urls[i].type == aType && (!aRel || this._urls[i]._hasRelation(aRel)))
+ return this._urls[i];
+ }
+
+ return null;
+ },
+
+ _confirmAddEngine: function SRCH_SVC_confirmAddEngine() {
+ var stringBundle = Services.strings.createBundle(SEARCH_BUNDLE);
+ var titleMessage = stringBundle.GetStringFromName("addEngineConfirmTitle");
+
+ // Display only the hostname portion of the URL.
+ var dialogMessage =
+ stringBundle.formatStringFromName("addEngineConfirmation",
+ [this._name, this._uri.host], 2);
+ var checkboxMessage = null;
+ if (!Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "noCurrentEngine", false))
+ checkboxMessage = stringBundle.GetStringFromName("addEngineAsCurrentText");
+
+ var addButtonLabel =
+ stringBundle.GetStringFromName("addEngineAddButtonLabel");
+
+ var ps = Services.prompt;
+ var buttonFlags = (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0) +
+ (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1) +
+ ps.BUTTON_POS_0_DEFAULT;
+
+ var checked = {value: false};
+ // confirmEx returns the index of the button that was pressed. Since "Add"
+ // is button 0, we want to return the negation of that value.
+ var confirm = !ps.confirmEx(null,
+ titleMessage,
+ dialogMessage,
+ buttonFlags,
+ addButtonLabel,
+ null, null, // button 1 & 2 names not used
+ checkboxMessage,
+ checked);
+
+ return {confirmed: confirm, useNow: checked.value};
+ },
+
+ /**
+ * Handle the successful download of an engine. Initializes the engine and
+ * triggers parsing of the data. The engine is then flushed to disk. Notifies
+ * the search service once initialization is complete.
+ */
+ _onLoad: function SRCH_ENG_onLoad(aBytes, aEngine) {
+ /**
+ * Handle an error during the load of an engine by notifying the engine's
+ * error callback, if any.
+ */
+ function onError(errorCode = Ci.nsISearchInstallCallback.ERROR_UNKNOWN_FAILURE) {
+ // Notify the callback of the failure
+ if (aEngine._installCallback) {
+ aEngine._installCallback(errorCode);
+ }
+ }
+
+ function promptError(strings = {}, error = undefined) {
+ onError(error);
+
+ if (aEngine._engineToUpdate) {
+ // We're in an update, so just fail quietly
+ LOG("updating " + aEngine._engineToUpdate.name + " failed");
+ return;
+ }
+ var brandBundle = Services.strings.createBundle(BRAND_BUNDLE);
+ var brandName = brandBundle.GetStringFromName("brandShortName");
+
+ var searchBundle = Services.strings.createBundle(SEARCH_BUNDLE);
+ var msgStringName = strings.error || "error_loading_engine_msg2";
+ var titleStringName = strings.title || "error_loading_engine_title";
+ var title = searchBundle.GetStringFromName(titleStringName);
+ var text = searchBundle.formatStringFromName(msgStringName,
+ [brandName, aEngine._location],
+ 2);
+
+ Services.ww.getNewPrompter(null).alert(title, text);
+ }
+
+ if (!aBytes) {
+ promptError();
+ return;
+ }
+
+ var engineToUpdate = null;
+ if (aEngine._engineToUpdate) {
+ engineToUpdate = aEngine._engineToUpdate.wrappedJSObject;
+
+ // Make this new engine use the old engine's file.
+ aEngine._file = engineToUpdate._file;
+ }
+
+ var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+ var doc = parser.parseFromBuffer(aBytes, aBytes.length, "text/xml");
+ aEngine._data = doc.documentElement;
+
+ try {
+ // Initialize the engine from the obtained data
+ aEngine._initFromData();
+ } catch (ex) {
+ LOG("_onLoad: Failed to init engine!\n" + ex);
+ // Report an error to the user
+ promptError();
+ return;
+ }
+
+ // Check that when adding a new engine (e.g., not updating an
+ // existing one), a duplicate engine does not already exist.
+ if (!engineToUpdate) {
+ if (Services.search.getEngineByName(aEngine.name)) {
+ // If we're confirming the engine load, then display a "this is a
+ // duplicate engine" prompt; otherwise, fail silently.
+ if (aEngine._confirm) {
+ promptError({ error: "error_duplicate_engine_msg",
+ title: "error_invalid_engine_title"
+ }, Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE);
+ } else {
+ onError(Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE);
+ }
+ LOG("_onLoad: duplicate engine found, bailing");
+ return;
+ }
+ }
+
+ // If requested, confirm the addition now that we have the title.
+ // This property is only ever true for engines added via
+ // nsIBrowserSearchService::addEngine.
+ if (aEngine._confirm) {
+ var confirmation = aEngine._confirmAddEngine();
+ LOG("_onLoad: confirm is " + confirmation.confirmed +
+ "; useNow is " + confirmation.useNow);
+ if (!confirmation.confirmed) {
+ onError();
+ return;
+ }
+ aEngine._useNow = confirmation.useNow;
+ }
+
+ // If we don't yet have a file, get one now. The only case where we would
+ // already have a file is if this is an update and _file was set above.
+ if (!aEngine._file)
+ aEngine._file = getSanitizedFile(aEngine.name);
+
+ if (engineToUpdate) {
+ // Keep track of the last modified date, so that we can make conditional
+ // requests for future updates.
+ engineMetadataService.setAttr(aEngine, "updatelastmodified",
+ (new Date()).toUTCString());
+
+ // If we're updating an app-shipped engine, ensure that the updateURLs
+ // are the same.
+ if (engineToUpdate._isInAppDir) {
+ let oldUpdateURL = engineToUpdate._updateURL;
+ let newUpdateURL = aEngine._updateURL;
+ let oldSelfURL = engineToUpdate._getURLOfType(URLTYPE_OPENSEARCH, "self");
+ if (oldSelfURL) {
+ oldUpdateURL = oldSelfURL.template;
+ let newSelfURL = aEngine._getURLOfType(URLTYPE_OPENSEARCH, "self");
+ if (!newSelfURL) {
+ LOG("_onLoad: updateURL missing in updated engine for " +
+ aEngine.name + " aborted");
+ onError();
+ return;
+ }
+ newUpdateURL = newSelfURL.template;
+ }
+
+ if (oldUpdateURL != newUpdateURL) {
+ LOG("_onLoad: updateURLs do not match! Update of " + aEngine.name + " aborted");
+ onError();
+ return;
+ }
+ }
+
+ // Set the new engine's icon, if it doesn't yet have one.
+ if (!aEngine._iconURI && engineToUpdate._iconURI)
+ aEngine._iconURI = engineToUpdate._iconURI;
+ }
+
+ // Write the engine to file. For readOnly engines, they'll be stored in the
+ // cache following the notification below.
+ if (!aEngine._readOnly)
+ aEngine._serializeToFile();
+
+ // Notify the search service of the successful load. It will deal with
+ // updates by checking aEngine._engineToUpdate.
+ notifyAction(aEngine, SEARCH_ENGINE_LOADED);
+
+ // Notify the callback if needed
+ if (aEngine._installCallback) {
+ aEngine._installCallback();
+ }
+ },
+
+ /**
+ * Creates a key by serializing an object that contains the icon's width
+ * and height.
+ *
+ * @param aWidth
+ * Width of the icon.
+ * @param aHeight
+ * Height of the icon.
+ * @returns key string
+ */
+ _getIconKey: function SRCH_ENG_getIconKey(aWidth, aHeight) {
+ let keyObj = {
+ width: aWidth,
+ height: aHeight
+ };
+
+ return JSON.stringify(keyObj);
+ },
+
+ /**
+ * Add an icon to the icon map used by getIconURIBySize() and getIcons().
+ *
+ * @param aWidth
+ * Width of the icon.
+ * @param aHeight
+ * Height of the icon.
+ * @param aURISpec
+ * String with the icon's URI.
+ */
+ _addIconToMap: function SRCH_ENG_addIconToMap(aWidth, aHeight, aURISpec) {
+ // Use an object instead of a Map() because it needs to be serializable.
+ this._iconMapObj = this._iconMapObj || {};
+ let key = this._getIconKey(aWidth, aHeight);
+ this._iconMapObj[key] = aURISpec;
+ },
+
+ /**
+ * Sets the .iconURI property of the engine. If both aWidth and aHeight are
+ * provided an entry will be added to _iconMapObj that will enable accessing
+ * icon's data through getIcons() and getIconURIBySize() APIs.
+ *
+ * @param aIconURL
+ * A URI string pointing to the engine's icon. Must have a http[s],
+ * ftp, or data scheme. Icons with HTTP[S] or FTP schemes will be
+ * downloaded and converted to data URIs for storage in the engine
+ * XML files, if the engine is not readonly.
+ * @param aIsPreferred
+ * Whether or not this icon is to be preferred. Preferred icons can
+ * override non-preferred icons.
+ * @param aWidth (optional)
+ * Width of the icon.
+ * @param aHeight (optional)
+ * Height of the icon.
+ */
+ _setIcon: function SRCH_ENG_setIcon(aIconURL, aIsPreferred, aWidth, aHeight) {
+ var uri = makeURI(aIconURL);
+
+ // Ignore bad URIs
+ if (!uri)
+ return;
+
+ LOG("_setIcon: Setting icon url \"" + limitURILength(uri.spec) + "\" for engine \""
+ + this.name + "\".");
+ // Only accept remote icons from http[s] or ftp
+ switch (uri.scheme) {
+ case "data":
+ if (!this._hasPreferredIcon || aIsPreferred) {
+ this._iconURI = uri;
+ notifyAction(this, SEARCH_ENGINE_CHANGED);
+ this._hasPreferredIcon = aIsPreferred;
+ }
+
+ if (aWidth && aHeight) {
+ this._addIconToMap(aWidth, aHeight, aIconURL)
+ }
+ break;
+ case "http":
+ case "https":
+ case "ftp":
+ // No use downloading the icon if the engine file is read-only
+ LOG("_setIcon: Downloading icon: \"" + uri.spec +
+ "\" for engine: \"" + this.name + "\"");
+ var chan = NetUtil.ioService.newChannelFromURI2(uri,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_NORMAL,
+ Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE);
+
+ let iconLoadCallback = function (aByteArray, aEngine) {
+ // This callback may run after we've already set a preferred icon,
+ // so check again.
+ if (aEngine._hasPreferredIcon && !aIsPreferred)
+ return;
+
+ if (!aByteArray || aByteArray.length > MAX_ICON_SIZE) {
+ LOG("iconLoadCallback: load failed, or the icon was too large!");
+ return;
+ }
+
+ var str = btoa(String.fromCharCode.apply(null, aByteArray));
+ let dataURL = ICON_DATAURL_PREFIX + str;
+ aEngine._iconURI = makeURI(dataURL);
+
+ if (aWidth && aHeight) {
+ aEngine._addIconToMap(aWidth, aHeight, dataURL)
+ }
+
+ // The engine might not have a file yet, if it's being downloaded,
+ // because the request for the engine file itself (_onLoad) may not
+ // yet be complete. In that case, this change will be written to
+ // file when _onLoad is called. For readonly engines, we'll store
+ // the changes in the cache once notified below.
+ if (aEngine._file && !aEngine._readOnly)
+ aEngine._serializeToFile();
+
+ notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
+ aEngine._hasPreferredIcon = aIsPreferred;
+ }
+
+ // If we're currently acting as an "update engine", then the callback
+ // should set the icon on the engine we're updating and not us, since
+ // |this| might be gone by the time the callback runs.
+ var engineToSet = this._engineToUpdate || this;
+
+ var listener = new loadListener(chan, engineToSet, iconLoadCallback);
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen(listener, null);
+ break;
+ }
+ },
+
+ /**
+ * Initialize this Engine object from the collected data.
+ */
+ _initFromData: function SRCH_ENG_initFromData() {
+ ENSURE_WARN(this._data, "Can't init an engine with no data!",
+ Cr.NS_ERROR_UNEXPECTED);
+
+ // Ensure we have a supported engine type before attempting to parse it.
+ let element = this._data;
+ if ((element.localName == MOZSEARCH_LOCALNAME &&
+ element.namespaceURI == MOZSEARCH_NS_10) ||
+ (element.localName == OPENSEARCH_LOCALNAME &&
+ OPENSEARCH_NAMESPACES.indexOf(element.namespaceURI) != -1)) {
+ LOG("_init: Initing search plugin from " + this._location);
+
+ this._parse();
+
+ } else
+ FAIL(this._location + " is not a valid search plugin.", Cr.NS_ERROR_FAILURE);
+
+ // No need to keep a ref to our data (which in some cases can be a document
+ // element) past this point
+ this._data = null;
+ },
+
+ /**
+ * Initialize this Engine object from a collection of metadata.
+ */
+ _initFromMetadata: function SRCH_ENG_initMetaData(aName, aIconURL, aAlias,
+ aDescription, aMethod,
+ aTemplate, aExtensionID) {
+ ENSURE_WARN(!this._readOnly,
+ "Can't call _initFromMetaData on a readonly engine!",
+ Cr.NS_ERROR_FAILURE);
+
+ this._urls.push(new EngineURL(URLTYPE_SEARCH_HTML, aMethod, aTemplate));
+
+ this._name = aName;
+ this.alias = aAlias;
+ this._description = aDescription;
+ this._setIcon(aIconURL, true);
+ this._extensionID = aExtensionID;
+
+ this._serializeToFile();
+ },
+
+ /**
+ * Extracts data from an OpenSearch URL element and creates an EngineURL
+ * object which is then added to the engine's list of URLs.
+ *
+ * @throws NS_ERROR_FAILURE if a URL object could not be created.
+ *
+ * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag.
+ * @see EngineURL()
+ */
+ _parseURL: function SRCH_ENG_parseURL(aElement) {
+ var type = aElement.getAttribute("type");
+ // According to the spec, method is optional, defaulting to "GET" if not
+ // specified
+ var method = aElement.getAttribute("method") || "GET";
+ var template = aElement.getAttribute("template");
+ var resultDomain = aElement.getAttribute("resultdomain");
+
+ try {
+ var url = new EngineURL(type, method, template, resultDomain);
+ } catch (ex) {
+ FAIL("_parseURL: failed to add " + template + " as a URL",
+ Cr.NS_ERROR_FAILURE);
+ }
+
+ if (aElement.hasAttribute("rel"))
+ url.rels = aElement.getAttribute("rel").toLowerCase().split(/\s+/);
+
+ for (var i = 0; i < aElement.childNodes.length; ++i) {
+ var param = aElement.childNodes[i];
+ if (param.localName == "Param") {
+ try {
+ url.addParam(param.getAttribute("name"), param.getAttribute("value"));
+ } catch (ex) {
+ // Ignore failure
+ LOG("_parseURL: Url element has an invalid param");
+ }
+ } else if (param.localName == "MozParam" &&
+ // We only support MozParams for default search engines
+ this._isDefault) {
+ var value;
+ let condition = param.getAttribute("condition");
+
+ // MozParams must have a condition to be valid
+ if (!condition) {
+ let engineLoc = this._location;
+ let paramName = param.getAttribute("name");
+ LOG("_parseURL: MozParam (" + paramName + ") without a condition attribute found parsing engine: " + engineLoc);
+ continue;
+ }
+
+ switch (condition) {
+ case "purpose":
+ url.addParam(param.getAttribute("name"),
+ param.getAttribute("value"),
+ param.getAttribute("purpose"));
+ // _addMozParam is not needed here since it can be serialized fine without. _addMozParam
+ // also requires a unique "name" which is not normally the case when @purpose is used.
+ break;
+ case "pref":
+ try {
+ value = getMozParamPref(param.getAttribute("pref"), value);
+ url.addParam(param.getAttribute("name"), value);
+ url._addMozParam({"pref": param.getAttribute("pref"),
+ "name": param.getAttribute("name"),
+ "condition": "pref"});
+ } catch (e) { }
+ break;
+ default:
+ let engineLoc = this._location;
+ let paramName = param.getAttribute("name");
+ LOG("_parseURL: MozParam (" + paramName + ") has an unknown condition: " + condition + ". Found parsing engine: " + engineLoc);
+ break;
+ }
+ }
+ }
+
+ this._urls.push(url);
+ },
+
+ /**
+ * Get the icon from an OpenSearch Image element.
+ * @see http://opensearch.a9.com/spec/1.1/description/#image
+ */
+ _parseImage: function SRCH_ENG_parseImage(aElement) {
+ LOG("_parseImage: Image textContent: \"" + limitURILength(aElement.textContent) + "\"");
+
+ let width = parseInt(aElement.getAttribute("width"), 10);
+ let height = parseInt(aElement.getAttribute("height"), 10);
+ let isPrefered = width == 16 && height == 16;
+
+ if (isNaN(width) || isNaN(height) || width <= 0 || height <=0) {
+ LOG("OpenSearch image element must have positive width and height.");
+ return;
+ }
+
+ this._setIcon(aElement.textContent, isPrefered, width, height);
+ },
+
+ /**
+ * Extract search engine information from the collected data to initialize
+ * the engine object.
+ */
+ _parse: function SRCH_ENG_parse() {
+ var doc = this._data;
+
+ // The OpenSearch spec sets a default value for the input encoding.
+ this._queryCharset = OS_PARAM_INPUT_ENCODING_DEF;
+
+ for (var i = 0; i < doc.childNodes.length; ++i) {
+ var child = doc.childNodes[i];
+ switch (child.localName) {
+ case "ShortName":
+ this._name = child.textContent;
+ break;
+ case "Description":
+ this._description = child.textContent;
+ break;
+ case "Url":
+ try {
+ this._parseURL(child);
+ } catch (ex) {
+ // Parsing of the element failed, just skip it.
+ LOG("_parse: failed to parse URL child: " + ex);
+ }
+ break;
+ case "Image":
+ this._parseImage(child);
+ break;
+ case "InputEncoding":
+ this._queryCharset = child.textContent.toUpperCase();
+ break;
+
+ // Non-OpenSearch elements
+ case "SearchForm":
+ this._searchForm = child.textContent;
+ break;
+ case "UpdateUrl":
+ this._updateURL = child.textContent;
+ break;
+ case "UpdateInterval":
+ this._updateInterval = parseInt(child.textContent);
+ break;
+ case "IconUpdateUrl":
+ this._iconUpdateURL = child.textContent;
+ break;
+ case "ExtensionID":
+ this._extensionID = child.textContent;
+ break;
+ }
+ }
+ if (!this.name || (this._urls.length == 0))
+ FAIL("_parse: No name, or missing URL!", Cr.NS_ERROR_FAILURE);
+ if (!this.supportsResponseType(URLTYPE_SEARCH_HTML))
+ FAIL("_parse: No text/html result type!", Cr.NS_ERROR_FAILURE);
+ },
+
+ /**
+ * Init from a JSON record.
+ **/
+ _initWithJSON: function SRCH_ENG__initWithJSON(aJson) {
+ this.__id = aJson._id;
+ this._name = aJson._name;
+ this._description = aJson.description;
+ if (aJson._hasPreferredIcon == undefined)
+ this._hasPreferredIcon = true;
+ else
+ this._hasPreferredIcon = false;
+ this._queryCharset = aJson.queryCharset || DEFAULT_QUERY_CHARSET;
+ this.__searchForm = aJson.__searchForm;
+ this.__installLocation = aJson._installLocation || SEARCH_APP_DIR;
+ this._updateInterval = aJson._updateInterval || null;
+ this._updateURL = aJson._updateURL || null;
+ this._iconUpdateURL = aJson._iconUpdateURL || null;
+ if (aJson._readOnly == undefined)
+ this._readOnly = true;
+ else
+ this._readOnly = false;
+ this._iconURI = makeURI(aJson._iconURL);
+ this._iconMapObj = aJson._iconMapObj;
+ if (aJson.extensionID) {
+ this._extensionID = aJson.extensionID;
+ }
+ for (let i = 0; i < aJson._urls.length; ++i) {
+ let url = aJson._urls[i];
+ let engineURL = new EngineURL(url.type || URLTYPE_SEARCH_HTML,
+ url.method || "GET", url.template,
+ url.resultDomain);
+ engineURL._initWithJSON(url, this);
+ this._urls.push(engineURL);
+ }
+ },
+
+ /**
+ * Creates a JavaScript object that represents this engine.
+ * @returns An object suitable for serialization as JSON.
+ **/
+ toJSON: function SRCH_ENG_toJSON() {
+ var json = {
+ _id: this._id,
+ _name: this._name,
+ description: this.description,
+ __searchForm: this.__searchForm,
+ _iconURL: this._iconURL,
+ _iconMapObj: this._iconMapObj,
+ _urls: this._urls
+ };
+
+ if (this._file instanceof Ci.nsILocalFile)
+ json.filePath = this._file.persistentDescriptor;
+ if (this._uri)
+ json._url = this._uri.spec;
+ if (this._installLocation != SEARCH_APP_DIR)
+ json._installLocation = this._installLocation;
+ if (this._updateInterval)
+ json._updateInterval = this._updateInterval;
+ if (this._updateURL)
+ json._updateURL = this._updateURL;
+ if (this._iconUpdateURL)
+ json._iconUpdateURL = this._iconUpdateURL;
+ if (!this._hasPreferredIcon)
+ json._hasPreferredIcon = this._hasPreferredIcon;
+ if (this.queryCharset != DEFAULT_QUERY_CHARSET)
+ json.queryCharset = this.queryCharset;
+ if (!this._readOnly)
+ json._readOnly = this._readOnly;
+ if (this._extensionID) {
+ json.extensionID = this._extensionID;
+ }
+
+ return json;
+ },
+
+ /**
+ * Returns an XML document object containing the search plugin information,
+ * which can later be used to reload the engine.
+ */
+ _serializeToElement: function SRCH_ENG_serializeToEl() {
+ function appendTextNode(aNameSpace, aLocalName, aValue) {
+ if (!aValue)
+ return null;
+ var node = doc.createElementNS(aNameSpace, aLocalName);
+ node.appendChild(doc.createTextNode(aValue));
+ docElem.appendChild(node);
+ docElem.appendChild(doc.createTextNode("\n"));
+ return node;
+ }
+
+ var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+
+ var doc = parser.parseFromString(EMPTY_DOC, "text/xml");
+ var docElem = doc.documentElement;
+
+ docElem.appendChild(doc.createTextNode("\n"));
+
+ appendTextNode(OPENSEARCH_NS_11, "ShortName", this.name);
+ appendTextNode(OPENSEARCH_NS_11, "Description", this._description);
+ appendTextNode(OPENSEARCH_NS_11, "InputEncoding", this._queryCharset);
+
+ if (this._iconURI) {
+ var imageNode = appendTextNode(OPENSEARCH_NS_11, "Image",
+ this._iconURI.spec);
+ if (imageNode) {
+ imageNode.setAttribute("width", "16");
+ imageNode.setAttribute("height", "16");
+ }
+ }
+
+ appendTextNode(MOZSEARCH_NS_10, "UpdateInterval", this._updateInterval);
+ appendTextNode(MOZSEARCH_NS_10, "UpdateUrl", this._updateURL);
+ appendTextNode(MOZSEARCH_NS_10, "IconUpdateUrl", this._iconUpdateURL);
+ appendTextNode(MOZSEARCH_NS_10, "SearchForm", this._searchForm);
+
+ if (this._extensionID) {
+ appendTextNode(MOZSEARCH_NS_10, "ExtensionID", this._extensionID);
+ }
+
+ for (var i = 0; i < this._urls.length; ++i)
+ this._urls[i]._serializeToElement(doc, docElem);
+ docElem.appendChild(doc.createTextNode("\n"));
+
+ return doc;
+ },
+
+ get lazySerializeTask() {
+ if (!this._lazySerializeTask) {
+ let task = function taskCallback() {
+ // This check should be done by caller, but it is better to be safe than sorry.
+ if (!this._readOnly && this._file) {
+ this._serializeToFile();
+ }
+ }.bind(this);
+ this._lazySerializeTask = new DeferredTask(task, LAZY_SERIALIZE_DELAY);
+ }
+
+ return this._lazySerializeTask;
+ },
+
+ // This API is required by some search engine management extensions, so let's restore it.
+ // Old API was using a timer to do its work, but this can lead us too far. If extension is
+ // rely on such subtle internal details, that extension should be fixed, not browser.
+ _lazySerializeToFile: function SRCH_ENG_lazySerializeToFile() {
+ // This check should be done by caller, but it is better to be safe than sorry.
+ // Besides, we don't have to create a task for r/o or non-file engines.
+ if (!this._readOnly && this._file) {
+ this.lazySerializeTask.arm();
+ }
+ },
+
+ /**
+ * Serializes the engine object to file.
+ */
+ _serializeToFile: function SRCH_ENG_serializeToFile() {
+ var file = this._file;
+ ENSURE_WARN(!this._readOnly, "Can't serialize a read only engine!",
+ Cr.NS_ERROR_FAILURE);
+ ENSURE_WARN(file && file.exists(), "Can't serialize: file doesn't exist!",
+ Cr.NS_ERROR_UNEXPECTED);
+
+ var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+
+ // Serialize the engine first - we don't want to overwrite a good file
+ // if this somehow fails.
+ var doc = this._serializeToElement();
+
+ fos.init(file, (MODE_WRONLY | MODE_TRUNCATE), FileUtils.PERMS_FILE, 0);
+
+ try {
+ var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
+ createInstance(Ci.nsIDOMSerializer);
+ serializer.serializeToStream(doc.documentElement, fos, null);
+ } catch (e) {
+ LOG("_serializeToFile: Error serializing engine:\n" + e);
+ }
+
+ closeSafeOutputStream(fos);
+
+ Services.obs.notifyObservers(file.clone(), SEARCH_SERVICE_TOPIC,
+ "write-engine-to-disk-complete");
+ },
+
+ /**
+ * Remove the engine's file from disk. The search service calls this once it
+ * removes the engine from its internal store. This function will throw if
+ * the file cannot be removed.
+ */
+ _remove: function SRCH_ENG_remove() {
+ if (this._readOnly)
+ FAIL("Can't remove read only engine!", Cr.NS_ERROR_FAILURE);
+ if (!this._file || !this._file.exists())
+ FAIL("Can't remove engine: file doesn't exist!", Cr.NS_ERROR_FILE_NOT_FOUND);
+
+ this._file.remove(false);
+ },
+
+ // nsISearchEngine
+ get alias() {
+ if (this._alias === undefined)
+ this._alias = engineMetadataService.getAttr(this, "alias");
+
+ return this._alias;
+ },
+ set alias(val) {
+ this._alias = val;
+ engineMetadataService.setAttr(this, "alias", val);
+ notifyAction(this, SEARCH_ENGINE_CHANGED);
+ },
+
+ /**
+ * Return the built-in identifier of app-provided engines.
+ *
+ * Note that this identifier is substantially similar to _id, with the
+ * following exceptions:
+ *
+ * * There is no trailing file extension.
+ * * There is no [app] prefix.
+ *
+ * @return a string identifier, or null.
+ */
+ get identifier() {
+ if (this._identifier !== undefined) {
+ return this._identifier;
+ }
+
+ // No identifier if If the engine isn't app-provided
+ if (!this._isInAppDir && !this._isInJAR) {
+ return this._identifier = null;
+ }
+
+ let leaf = this._getLeafName();
+ ENSURE_WARN(leaf, "identifier: app-provided engine has no leafName");
+
+ // Strip file extension.
+ let ext = leaf.lastIndexOf(".");
+ if (ext == -1) {
+ return this._identifier = leaf;
+ }
+ return this._identifier = leaf.substring(0, ext);
+ },
+
+ get description() {
+ return this._description;
+ },
+
+ get hidden() {
+ return engineMetadataService.getAttr(this, "hidden") || false;
+ },
+ set hidden(val) {
+ var value = !!val;
+ if (value != this.hidden) {
+ engineMetadataService.setAttr(this, "hidden", value);
+ notifyAction(this, SEARCH_ENGINE_CHANGED);
+ }
+ },
+
+ get iconURI() {
+ if (this._iconURI)
+ return this._iconURI;
+ return null;
+ },
+
+ get _iconURL() {
+ if (!this._iconURI)
+ return "";
+ return this._iconURI.spec;
+ },
+
+ // Where the engine is being loaded from: will return the URI's spec if the
+ // engine is being downloaded and does not yet have a file. This is only used
+ // for logging and error messages.
+ get _location() {
+ if (this._file)
+ return this._file.path;
+
+ if (this._uri)
+ return this._uri.spec;
+
+ return "";
+ },
+
+ /**
+ * @return the leaf name of the filename or URI of this plugin,
+ * or null if no file or URI is known.
+ */
+ _getLeafName: function () {
+ if (this._file) {
+ return this._file.leafName;
+ }
+ if (this._uri && this._uri instanceof Ci.nsIURL) {
+ return this._uri.fileName;
+ }
+ return null;
+ },
+
+ // The file that the plugin is loaded from is a unique identifier for it. We
+ // use this as the identifier to store data in the sqlite database
+ __id: null,
+ get _id() {
+ if (this.__id) {
+ return this.__id;
+ }
+
+ let leafName = this._getLeafName();
+
+ // Treat engines loaded from JARs the same way we treat app shipped
+ // engines.
+ // Theoretically, these could also come from extensions, but there's no
+ // real way for extensions to register their chrome locations at the
+ // moment, so let's not deal with that case.
+ // This means we're vulnerable to conflicts if a file loaded from a JAR
+ // has the same filename as a file loaded from the app dir, but with a
+ // different engine name. People using the JAR functionality should be
+ // careful not to do that!
+ if (this._isInAppDir || this._isInJAR) {
+ // App dir and JAR engines should always have leafNames
+ ENSURE_WARN(leafName, "_id: no leafName for appDir or JAR engine",
+ Cr.NS_ERROR_UNEXPECTED);
+ return this.__id = "[app]/" + leafName;
+ }
+
+ if (this._isInProfile) {
+ ENSURE_WARN(leafName, "_id: no leafName for profile engine",
+ Cr.NS_ERROR_UNEXPECTED);
+ return this.__id = "[profile]/" + leafName;
+ }
+
+ // If the engine isn't a JAR engine, it should have a file.
+ ENSURE_WARN(this._file, "_id: no _file for non-JAR engine",
+ Cr.NS_ERROR_UNEXPECTED);
+
+ // We're not in the profile or appdir, so this must be an extension-shipped
+ // plugin. Use the full filename.
+ return this.__id = this._file.path;
+ },
+
+ // This indicates where we found the .xml file to load the engine,
+ // and attempts to hide user-identifiable data (such as username).
+ get _anonymizedLoadPath() {
+ /* Examples of expected output:
+ * jar:[app]/omni.ja!browser/engine.xml
+ * 'browser' here is the name of the chrome package, not a folder.
+ * [profile]/searchplugins/engine.xml
+ * [distribution]/searchplugins/common/engine.xml
+ * [other]/engine.xml
+ */
+
+ let leafName = this._getLeafName();
+ if (!leafName)
+ return "null";
+
+ let prefix = "", suffix = "";
+ let file = this._file;
+ if (!file) {
+ let uri = this._uri;
+ if (uri.schemeIs("resource")) {
+ uri = makeURI(Services.io.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsISubstitutingProtocolHandler)
+ .resolveURI(uri));
+ }
+ if (uri.schemeIs("chrome")) {
+ let packageName = uri.hostPort;
+ uri = gChromeReg.convertChromeURL(uri);
+ if (uri instanceof Ci.nsINestedURI) {
+ prefix = "jar:";
+ suffix = "!" + packageName + "/" + leafName;
+ uri = uri.innermostURI;
+ }
+ uri.QueryInterface(Ci.nsIFileURL)
+ file = uri.file;
+ } else {
+ return "[" + uri.scheme + "]/" + leafName;
+ }
+ }
+
+ let id;
+ let enginePath = file.path;
+
+ const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
+ const NS_APP_USER_PROFILE_50_DIR = "ProfD";
+ const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
+
+ const knownDirs = {
+ app: NS_XPCOM_CURRENT_PROCESS_DIR,
+ profile: NS_APP_USER_PROFILE_50_DIR,
+ distribution: XRE_APP_DISTRIBUTION_DIR
+ };
+
+ for (let key in knownDirs) {
+ let path;
+ try {
+ path = getDir(knownDirs[key]).path;
+ } catch(e) {
+ // Getting XRE_APP_DISTRIBUTION_DIR throws during unit tests.
+ continue;
+ }
+ if (enginePath.startsWith(path)) {
+ id = "[" + key + "]" + enginePath.slice(path.length).replace(/\\/g, "/");
+ break;
+ }
+ }
+
+ // If the folder doesn't have a known ancestor, don't record its path to
+ // avoid leaking user identifiable data.
+ if (!id)
+ id = "[other]/" + file.leafName;
+
+ return prefix + id + suffix;
+ },
+
+ get _installLocation() {
+ if (this.__installLocation === null) {
+ if (!this._file) {
+ ENSURE_WARN(this._uri, "Engines without files must have URIs",
+ Cr.NS_ERROR_UNEXPECTED);
+ this.__installLocation = SEARCH_JAR;
+ }
+ else if (this._file.parent.equals(getDir(NS_APP_SEARCH_DIR)))
+ this.__installLocation = SEARCH_APP_DIR;
+ else if (this._file.parent.equals(getDir(NS_APP_USER_SEARCH_DIR)))
+ this.__installLocation = SEARCH_PROFILE_DIR;
+ else
+ this.__installLocation = SEARCH_IN_EXTENSION;
+ }
+
+ return this.__installLocation;
+ },
+
+ get _isInJAR() {
+ return this._installLocation == SEARCH_JAR;
+ },
+ get _isInAppDir() {
+ return this._installLocation == SEARCH_APP_DIR;
+ },
+ get _isInProfile() {
+ return this._installLocation == SEARCH_PROFILE_DIR;
+ },
+
+ get _isDefault() {
+ // For now, our concept of a "default engine" is "one that is not in the
+ // user's profile directory", which is currently equivalent to "is app- or
+ // extension-shipped".
+ return !this._isInProfile;
+ },
+
+ get _hasUpdates() {
+ // Whether or not the engine has an update URL
+ let selfURL = this._getURLOfType(URLTYPE_OPENSEARCH, "self");
+ return !!(this._updateURL || this._iconUpdateURL || selfURL);
+ },
+
+ get name() {
+ return this._name;
+ },
+
+ get searchForm() {
+ return this._getSearchFormWithPurpose();
+ },
+
+ _getSearchFormWithPurpose(aPurpose = "") {
+ // First look for a <Url rel="searchform">
+ var searchFormURL = this._getURLOfType(URLTYPE_SEARCH_HTML, "searchform");
+ if (searchFormURL) {
+ let submission = searchFormURL.getSubmission("", this, aPurpose);
+
+ // If the rel=searchform URL is not type="get" (i.e. has postData),
+ // ignore it, since we can only return a URL.
+ if (!submission.postData)
+ return submission.uri.spec;
+ }
+
+ if (!this._searchForm) {
+ // No SearchForm specified in the engine definition file, use the prePath
+ // (e.g. https://foo.com for https://foo.com/search.php?q=bar).
+ var htmlUrl = this._getURLOfType(URLTYPE_SEARCH_HTML);
+ ENSURE_WARN(htmlUrl, "Engine has no HTML URL!", Cr.NS_ERROR_UNEXPECTED);
+ this._searchForm = makeURI(htmlUrl.template).prePath;
+ }
+
+ return ParamSubstitution(this._searchForm, "", this);
+ },
+
+ get queryCharset() {
+ if (this._queryCharset)
+ return this._queryCharset;
+ return this._queryCharset = "windows-1252"; // the default
+ },
+
+ // from nsISearchEngine
+ addParam: function SRCH_ENG_addParam(aName, aValue, aResponseType) {
+ if (!aName || (aValue == null))
+ FAIL("missing name or value for nsISearchEngine::addParam!");
+ ENSURE_WARN(!this._readOnly,
+ "called nsISearchEngine::addParam on a read-only engine!",
+ Cr.NS_ERROR_FAILURE);
+ if (!aResponseType)
+ aResponseType = URLTYPE_SEARCH_HTML;
+
+ var url = this._getURLOfType(aResponseType);
+ if (!url)
+ FAIL("Engine object has no URL for response type " + aResponseType,
+ Cr.NS_ERROR_FAILURE);
+
+ url.addParam(aName, aValue);
+
+ // Serialize the changes to file lazily
+ this.lazySerializeTask.arm();
+ },
+
+ // from nsISearchEngine
+ getSubmission: function SRCH_ENG_getSubmission(aData, aResponseType, aPurpose) {
+ if (!aResponseType) {
+ aResponseType = URLTYPE_SEARCH_HTML;
+ }
+
+ var url = this._getURLOfType(aResponseType);
+
+ if (!url)
+ return null;
+
+ if (!aData) {
+ // Return a dummy submission object with our searchForm attribute
+ return new Submission(makeURI(this._getSearchFormWithPurpose(aPurpose)), null);
+ }
+
+ LOG("getSubmission: In data: \"" + aData + "\"; Purpose: \"" + aPurpose + "\"");
+ var data = "";
+ try {
+ data = gTextToSubURI.ConvertAndEscape(this.queryCharset, aData);
+ } catch (ex) {
+ LOG("getSubmission: Falling back to default queryCharset!");
+ data = gTextToSubURI.ConvertAndEscape(DEFAULT_QUERY_CHARSET, aData);
+ }
+ LOG("getSubmission: Out data: \"" + data + "\"");
+ return url.getSubmission(data, this, aPurpose);
+ },
+
+ // from nsISearchEngine
+ supportsResponseType: function SRCH_ENG_supportsResponseType(type) {
+ return (this._getURLOfType(type) != null);
+ },
+
+ // from nsISearchEngine
+ getResultDomain: function SRCH_ENG_getResultDomain(aResponseType) {
+ if (!aResponseType) {
+ aResponseType = URLTYPE_SEARCH_HTML;
+ }
+
+ LOG("getResultDomain: responseType: \"" + aResponseType + "\"");
+
+ let url = this._getURLOfType(aResponseType);
+ if (url)
+ return url.resultDomain;
+ return "";
+ },
+
+ /**
+ * Returns URL parsing properties used by _buildParseSubmissionMap.
+ */
+ getURLParsingInfo: function () {
+ let responseType = URLTYPE_SEARCH_HTML;
+
+ LOG("getURLParsingInfo: responseType: \"" + responseType + "\"");
+
+ let url = this._getURLOfType(responseType);
+ if (!url || url.method != "GET") {
+ return null;
+ }
+
+ let termsParameterName = url._getTermsParameterName();
+ if (!termsParameterName) {
+ return null;
+ }
+
+ let templateUrl = NetUtil.newURI(url.template).QueryInterface(Ci.nsIURL);
+ return {
+ mainDomain: templateUrl.host,
+ path: templateUrl.filePath.toLowerCase(),
+ termsParameterName: termsParameterName,
+ };
+ },
+
+ // nsISupports
+ QueryInterface: function SRCH_ENG_QI(aIID) {
+ if (aIID.equals(Ci.nsISearchEngine) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ get wrappedJSObject() {
+ return this;
+ },
+
+ /**
+ * Returns a string with the URL to an engine's icon matching both width and
+ * height. Returns null if icon with specified dimensions is not found.
+ *
+ * @param width
+ * Width of the requested icon.
+ * @param height
+ * Height of the requested icon.
+ */
+ getIconURLBySize: function SRCH_ENG_getIconURLBySize(aWidth, aHeight) {
+ if (!this._iconMapObj)
+ return null;
+
+ let key = this._getIconKey(aWidth, aHeight);
+ if (key in this._iconMapObj) {
+ return this._iconMapObj[key];
+ }
+ return null;
+ },
+
+ /**
+ * Gets an array of all available icons. Each entry is an object with
+ * width, height and url properties. width and height are numeric and
+ * represent the icon's dimensions. url is a string with the URL for
+ * the icon.
+ */
+ getIcons: function SRCH_ENG_getIcons() {
+ let result = [];
+
+ if (!this._iconMapObj)
+ return result;
+
+ for (let key of Object.keys(this._iconMapObj)) {
+ let iconSize = JSON.parse(key);
+ result.push({
+ width: iconSize.width,
+ height: iconSize.height,
+ url: this._iconMapObj[key]
+ });
+ }
+
+ return result;
+ },
+
+ /**
+ * Opens a speculative connection to the engine's search URI
+ * (and suggest URI, if different) to reduce request latency
+ *
+ * @param options
+ * An object that must contain the following fields:
+ * {window} the content window for the window performing the search
+ *
+ * @throws NS_ERROR_INVALID_ARG if options is omitted or lacks required
+ * elemeents
+ */
+ speculativeConnect: function SRCH_ENG_speculativeConnect(options) {
+ if (!options || !options.window) {
+ Cu.reportError("invalid options arg passed to nsISearchEngine.speculativeConnect");
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+ let connector =
+ Services.io.QueryInterface(Components.interfaces.nsISpeculativeConnect);
+
+ let searchURI = this.getSubmission("dummy").uri;
+
+ let callbacks = options.window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext);
+
+ connector.speculativeConnect(searchURI, callbacks);
+
+ if (this.supportsResponseType(URLTYPE_SUGGEST_JSON)) {
+ let suggestURI = this.getSubmission("dummy", URLTYPE_SUGGEST_JSON).uri;
+ if (suggestURI.prePath != searchURI.prePath)
+ connector.speculativeConnect(suggestURI, callbacks);
+ }
+ },
+};
+
+// nsISearchSubmission
+function Submission(aURI, aPostData = null, aPostDataString = null) {
+ this._uri = aURI;
+ this._postData = aPostData;
+ this._postDataString = aPostDataString;
+}
+Submission.prototype = {
+ get uri() {
+ return this._uri;
+ },
+ get postData() {
+ return this._postData;
+ },
+ get postDataString() {
+ return this._postDataString;
+ },
+ QueryInterface: function SRCH_SUBM_QI(aIID) {
+ if (aIID.equals(Ci.nsISearchSubmission) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+}
+
+// nsISearchParseSubmissionResult
+function ParseSubmissionResult(aEngine, aTerms, aTermsOffset, aTermsLength) {
+ this._engine = aEngine;
+ this._terms = aTerms;
+ this._termsOffset = aTermsOffset;
+ this._termsLength = aTermsLength;
+}
+ParseSubmissionResult.prototype = {
+ get engine() {
+ return this._engine;
+ },
+ get terms() {
+ return this._terms;
+ },
+ get termsOffset() {
+ return this._termsOffset;
+ },
+ get termsLength() {
+ return this._termsLength;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISearchParseSubmissionResult]),
+}
+
+const gEmptyParseSubmissionResult =
+ Object.freeze(new ParseSubmissionResult(null, "", -1, 0));
+
+function executeSoon(func) {
+ Services.tm.mainThread.dispatch(func, Ci.nsIThread.DISPATCH_NORMAL);
+}
+
+/**
+ * Check for sync initialization has completed or not.
+ *
+ * @param {aPromise} A promise.
+ *
+ * @returns the value returned by the invoked method.
+ * @throws NS_ERROR_ALREADY_INITIALIZED if sync initialization has completed.
+ */
+function checkForSyncCompletion(aPromise) {
+ return aPromise.then(function(aValue) {
+ if (gInitialized) {
+ throw Components.Exception("Synchronous fallback was called and has " +
+ "finished so no need to pursue asynchronous " +
+ "initialization",
+ Cr.NS_ERROR_ALREADY_INITIALIZED);
+ }
+ return aValue;
+ });
+}
+
+// nsIBrowserSearchService
+function SearchService() {
+ // Replace empty LOG function with the useful one if the log pref is set.
+ if (Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "log", false))
+ LOG = DO_LOG;
+
+ this._initObservers = Promise.defer();
+}
+
+SearchService.prototype = {
+ classID: Components.ID("{7319788a-fe93-4db3-9f39-818cf08f4256}"),
+
+ // The current status of initialization. Note that it does not determine if
+ // initialization is complete, only if an error has been encountered so far.
+ _initRV: Cr.NS_OK,
+
+ // The boolean indicates that the initialization has started or not.
+ _initStarted: null,
+
+ // If initialization has not been completed yet, perform synchronous
+ // initialization.
+ // Throws in case of initialization error.
+ _ensureInitialized: function SRCH_SVC__ensureInitialized() {
+ if (gInitialized) {
+ if (!Components.isSuccessCode(this._initRV)) {
+ LOG("_ensureInitialized: failure");
+ throw this._initRV;
+ }
+ return;
+ }
+
+ let performanceWarning =
+ "Search service falling back to synchronous initialization. " +
+ "This is generally the consequence of an add-on using a deprecated " +
+ "search service API.";
+ Deprecated.perfWarning(performanceWarning, "https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIBrowserSearchService#async_warning");
+ LOG(performanceWarning);
+
+ engineMetadataService.syncInit();
+ this._syncInit();
+ if (!Components.isSuccessCode(this._initRV)) {
+ throw this._initRV;
+ }
+ },
+
+ // Synchronous implementation of the initializer.
+ // Used by |_ensureInitialized| as a fallback if initialization is not
+ // complete.
+ _syncInit: function SRCH_SVC__syncInit() {
+ LOG("_syncInit start");
+ this._initStarted = true;
+ try {
+ this._syncLoadEngines();
+ } catch (ex) {
+ this._initRV = Cr.NS_ERROR_FAILURE;
+ LOG("_syncInit: failure loading engines: " + ex);
+ }
+ this._addObservers();
+
+ gInitialized = true;
+
+ this._initObservers.resolve(this._initRV);
+
+ Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
+
+ LOG("_syncInit end");
+ },
+
+ /**
+ * Asynchronous implementation of the initializer.
+ *
+ * @returns {Promise} A promise, resolved successfully if the initialization
+ * succeeds.
+ */
+ _asyncInit: function SRCH_SVC__asyncInit() {
+ return Task.spawn(function() {
+ LOG("_asyncInit start");
+ try {
+ yield checkForSyncCompletion(this._asyncLoadEngines());
+ } catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
+ this._initRV = Cr.NS_ERROR_FAILURE;
+ LOG("_asyncInit: failure loading engines: " + ex);
+ }
+ this._addObservers();
+ gInitialized = true;
+ this._initObservers.resolve(this._initRV);
+ Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
+
+ LOG("_asyncInit: Completed _asyncInit");
+ }.bind(this));
+ },
+
+
+ _engines: { },
+ __sortedEngines: null,
+ _visibleDefaultEngines: [],
+ get _sortedEngines() {
+ if (!this.__sortedEngines)
+ return this._buildSortedEngineList();
+ return this.__sortedEngines;
+ },
+
+ // Get the original Engine object that is the default for this region,
+ // ignoring changes the user may have subsequently made.
+ get _originalDefaultEngine() {
+ let defaultEngine = engineMetadataService.getGlobalAttr("searchDefault");
+ if (defaultEngine &&
+ engineMetadataService.getGlobalAttr("searchDefaultHash") != getVerificationHash(defaultEngine)) {
+ LOG("get _originalDefaultEngine, invalid searchDefaultHash for: " + defaultEngine);
+ defaultEngine = "";
+ }
+
+ if (!defaultEngine) {
+ let defaultPrefB = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
+ let nsIPLS = Ci.nsIPrefLocalizedString;
+
+ try {
+ defaultEngine = defaultPrefB.getComplexValue("defaultenginename", nsIPLS).data;
+ } catch (ex) {
+ // If the default pref is invalid (e.g. an add-on set it to a bogus value)
+ // getEngineByName will just return null, which is the best we can do.
+ }
+ }
+
+ return this.getEngineByName(defaultEngine);
+ },
+
+ resetToOriginalDefaultEngine: function SRCH_SVC__resetToOriginalDefaultEngine() {
+ this.currentEngine = this._originalDefaultEngine;
+ },
+
+ _buildCache: function SRCH_SVC__buildCache() {
+ let cache = {};
+ let locale = getLocale();
+ let buildID = Services.appinfo.platformBuildID;
+
+ // Allows us to force a cache refresh should the cache format change.
+ cache.version = CACHE_VERSION;
+ // We don't want to incur the costs of stat()ing each plugin on every
+ // startup when the only (supported) time they will change is during
+ // runtime (where we refresh for changes through the API) and app updates
+ // (where the buildID is obviously going to change).
+ // Extension-shipped plugins are the only exception to this, but their
+ // directories are blown away during updates, so we'll detect their changes.
+ cache.buildID = buildID;
+ cache.locale = locale;
+
+ cache.directories = {};
+ cache.visibleDefaultEngines = this._visibleDefaultEngines;
+
+ let getParent = engine => {
+ if (engine._file)
+ return engine._file.parent;
+
+ let uri = engine._uri;
+ if (!uri.schemeIs("resource")) {
+ LOG("getParent: engine URI must be a resource URI if it has no file");
+ return null;
+ }
+
+ // use the underlying JAR file, for resource URIs
+ let chan = makeChannel(uri.spec);
+ if (chan)
+ return this._convertChannelToFile(chan);
+
+ LOG("getParent: couldn't map resource:// URI to a file");
+ return null;
+ };
+
+ for (let name in this._engines) {
+ let engine = this._engines[name];
+ let parent = getParent(engine);
+ if (!parent) {
+ LOG("Error: no parent for engine " + engine._location + ", failing to cache it");
+
+ continue;
+ }
+
+ // Write out serialized search engine files when rebuilding cache.
+ // Do it lazily, to: 1) reuse existing API; 2) make browser interface more responsive
+ engine._lazySerializeToFile();
+
+ let cacheKey = parent.path;
+ if (!cache.directories[cacheKey]) {
+ let cacheEntry = {};
+ cacheEntry.lastModifiedTime = parent.lastModifiedTime;
+ cacheEntry.engines = [];
+ cache.directories[cacheKey] = cacheEntry;
+ }
+ cache.directories[cacheKey].engines.push(engine);
+ }
+
+ try {
+ LOG("_buildCache: Writing to cache file.");
+ let path = OS.Path.join(OS.Constants.Path.profileDir, "search.json");
+ let data = gEncoder.encode(JSON.stringify(cache));
+ let promise = OS.File.writeAtomic(path, data, { tmpPath: path + ".tmp"});
+
+ promise.then(
+ function onSuccess() {
+ Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, SEARCH_SERVICE_CACHE_WRITTEN);
+ },
+ function onError(e) {
+ LOG("_buildCache: failure during writeAtomic: " + e);
+ }
+ );
+ } catch (ex) {
+ LOG("_buildCache: Could not write to cache file: " + ex);
+ }
+ },
+
+ _syncLoadEngines: function SRCH_SVC__syncLoadEngines() {
+ LOG("_syncLoadEngines: start");
+ // See if we have a cache file so we don't have to parse a bunch of XML.
+ let cache = {};
+ let cacheFile = getDir(NS_APP_USER_PROFILE_50_DIR);
+ cacheFile.append("search.json");
+ if (cacheFile.exists())
+ cache = this._readCacheFile(cacheFile);
+
+ let [chromeFiles, chromeURIs] = this._findJAREngines();
+
+ let distDirs = [];
+ let locations;
+ try {
+ locations = getDir(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
+ Ci.nsISimpleEnumerator);
+ } catch (e) {
+ // NS_APP_DISTRIBUTION_SEARCH_DIR_LIST is defined by each app
+ // so this throws during unit tests (but not xpcshell tests).
+ locations = {hasMoreElements: () => false};
+ }
+ while (locations.hasMoreElements()) {
+ let dir = locations.getNext().QueryInterface(Ci.nsIFile);
+ if (dir.directoryEntries.hasMoreElements())
+ distDirs.push(dir);
+ }
+
+ let otherDirs = [];
+ locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
+ while (locations.hasMoreElements()) {
+ let dir = locations.getNext().QueryInterface(Ci.nsIFile);
+ if (dir.directoryEntries.hasMoreElements())
+ otherDirs.push(dir);
+ }
+
+ let toLoad = chromeFiles.concat(distDirs, otherDirs);
+
+ function modifiedDir(aDir) {
+ return (!cache.directories || !cache.directories[aDir.path] ||
+ cache.directories[aDir.path].lastModifiedTime != aDir.lastModifiedTime);
+ }
+
+ function notInCachePath(aPathToLoad) {
+ return cachePaths.indexOf(aPathToLoad.path) == -1;
+ }
+ function notInCacheVisibleEngines(aEngineName) {
+ return cache.visibleDefaultEngines.indexOf(aEngineName) == -1;
+ }
+
+ let buildID = Services.appinfo.platformBuildID;
+ // Gecko44: let cachePaths = [path for (path in cache.directories)];
+ let cachePaths = [];
+ for (path in cache.directories) {
+ cachePaths.push(path);
+ }
+
+
+ let rebuildCache = !cache.directories ||
+ cache.version != CACHE_VERSION ||
+ cache.locale != getLocale() ||
+ cache.buildID != buildID ||
+ cachePaths.length != toLoad.length ||
+ toLoad.some(notInCachePath) ||
+ cache.visibleDefaultEngines.length != this._visibleDefaultEngines.length ||
+ this._visibleDefaultEngines.some(notInCacheVisibleEngines) ||
+ toLoad.some(modifiedDir);
+
+ if (rebuildCache) {
+ LOG("_loadEngines: Absent or outdated cache. Loading engines from disk.");
+ distDirs.forEach(this._loadEnginesFromDir, this);
+
+ this._loadFromChromeURLs(chromeURIs);
+
+ otherDirs.forEach(this._loadEnginesFromDir, this);
+
+ this._buildCache();
+ return;
+ }
+
+ LOG("_loadEngines: loading from cache directories");
+ for (let cacheKey in cache.directories) {
+ let dir = cache.directories[cacheKey];
+ this._loadEnginesFromCache(dir);
+ }
+
+ LOG("_loadEngines: done");
+ },
+
+ /**
+ * Loads engines asynchronously.
+ *
+ * @returns {Promise} A promise, resolved successfully if loading data
+ * succeeds.
+ */
+ _asyncLoadEngines: function SRCH_SVC__asyncLoadEngines() {
+ return Task.spawn(function() {
+ LOG("_asyncLoadEngines: start");
+ // See if we have a cache file so we don't have to parse a bunch of XML.
+ let cache = {};
+ let cacheFilePath = OS.Path.join(OS.Constants.Path.profileDir, "search.json");
+ cache = yield checkForSyncCompletion(this._asyncReadCacheFile(cacheFilePath));
+
+ Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "find-jar-engines");
+ let [chromeFiles, chromeURIs] =
+ yield checkForSyncCompletion(this._asyncFindJAREngines());
+
+ // Get the non-empty distribution directories into distDirs...
+ let distDirs = [];
+ let locations;
+ try {
+ locations = getDir(NS_APP_DISTRIBUTION_SEARCH_DIR_LIST,
+ Ci.nsISimpleEnumerator);
+ } catch (e) {
+ // NS_APP_DISTRIBUTION_SEARCH_DIR_LIST is defined by each app
+ // so this throws during unit tests (but not xpcshell tests).
+ locations = {hasMoreElements: () => false};
+ }
+ while (locations.hasMoreElements()) {
+ let dir = locations.getNext().QueryInterface(Ci.nsIFile);
+ let iterator = new OS.File.DirectoryIterator(dir.path,
+ { winPattern: "*.xml" });
+ try {
+ // Add dir to distDirs if it contains any files.
+ yield checkForSyncCompletion(iterator.next());
+ distDirs.push(dir);
+ } catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
+ // Catch for StopIteration exception.
+ } finally {
+ iterator.close();
+ }
+ }
+
+ // Add the non-empty directories of NS_APP_SEARCH_DIR_LIST to
+ // otherDirs...
+ let otherDirs = [];
+ locations = getDir(NS_APP_SEARCH_DIR_LIST, Ci.nsISimpleEnumerator);
+ while (locations.hasMoreElements()) {
+ let dir = locations.getNext().QueryInterface(Ci.nsIFile);
+ let iterator = new OS.File.DirectoryIterator(dir.path,
+ { winPattern: "*.xml" });
+ try {
+ // Add dir to otherDirs if it contains any files.
+ yield checkForSyncCompletion(iterator.next());
+ otherDirs.push(dir);
+ } catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
+ // Catch for StopIteration exception.
+ } finally {
+ iterator.close();
+ }
+ }
+
+ let toLoad = chromeFiles.concat(distDirs, otherDirs);
+ function hasModifiedDir(aList) {
+ return Task.spawn(function() {
+ let modifiedDir = false;
+
+ for (let dir of aList) {
+ if (!cache.directories || !cache.directories[dir.path]) {
+ modifiedDir = true;
+ break;
+ }
+
+ let info = yield OS.File.stat(dir.path);
+ if (cache.directories[dir.path].lastModifiedTime !=
+ info.lastModificationDate.getTime()) {
+ modifiedDir = true;
+ break;
+ }
+ }
+ throw new Task.Result(modifiedDir);
+ });
+ }
+
+ function notInCachePath(aPathToLoad) {
+ return cachePaths.indexOf(aPathToLoad.path) == -1;
+ }
+ function notInCacheVisibleEngines(aEngineName) {
+ return cache.visibleDefaultEngines.indexOf(aEngineName) == -1;
+ }
+
+ let buildID = Services.appinfo.platformBuildID;
+ // Gecko44: let cachePaths = [path for (path in cache.directories)];
+ let cachePaths = [];
+ for (path in cache.directories) {
+ cachePaths.push(path);
+ }
+
+ let rebuildCache = !cache.directories ||
+ cache.version != CACHE_VERSION ||
+ cache.locale != getLocale() ||
+ cache.buildID != buildID ||
+ cachePaths.length != toLoad.length ||
+ toLoad.some(notInCachePath) ||
+ cache.visibleDefaultEngines.length != this._visibleDefaultEngines.length ||
+ this._visibleDefaultEngines.some(notInCacheVisibleEngines) ||
+ (yield checkForSyncCompletion(hasModifiedDir(toLoad)));
+
+ if (rebuildCache) {
+ LOG("_asyncLoadEngines: Absent or outdated cache. Loading engines from disk.");
+ let engines = [];
+ for (let loadDir of distDirs) {
+ let enginesFromDir =
+ yield checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
+ engines = engines.concat(enginesFromDir);
+ }
+ let enginesFromURLs =
+ yield checkForSyncCompletion(this._asyncLoadFromChromeURLs(chromeURIs));
+ engines = engines.concat(enginesFromURLs);
+ for (let loadDir of otherDirs) {
+ let enginesFromDir =
+ yield checkForSyncCompletion(this._asyncLoadEnginesFromDir(loadDir));
+ engines = engines.concat(enginesFromDir);
+ }
+
+ for (let engine of engines) {
+ this._addEngineToStore(engine);
+ }
+ this._buildCache();
+ return;
+ }
+
+ LOG("_asyncLoadEngines: loading from cache directories");
+ for (let cacheKey in cache.directories) {
+ let dir = cache.directories[cacheKey];
+ this._loadEnginesFromCache(dir);
+ }
+
+ LOG("_asyncLoadEngines: done");
+ }.bind(this));
+ },
+
+ _asyncReInit: function () {
+ LOG("_asyncReInit");
+ // Start by clearing the initialized state, so we don't abort early.
+ gInitialized = false;
+
+ // Clear the engines, too, so we don't stick with the stale ones.
+ this._engines = {};
+ this.__sortedEngines = null;
+ this._currentEngine = null;
+ this._defaultEngine = null;
+ this._visibleDefaultEngines = [];
+
+ // Clear the metadata service.
+ engineMetadataService._initialized = false;
+ engineMetadataService._initializer = null;
+
+ Task.spawn(function* () {
+ try {
+ LOG("Restarting engineMetadataService");
+ yield engineMetadataService.init();
+ yield this._asyncLoadEngines();
+
+ // Typically we'll re-init as a result of a pref observer,
+ // so signal to 'callers' that we're done.
+ Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "reinit-complete");
+ gInitialized = true;
+ } catch (err) {
+ LOG("Reinit failed: " + err);
+ Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "reinit-failed");
+ }
+ }.bind(this));
+ },
+
+ _readCacheFile: function SRCH_SVC__readCacheFile(aFile) {
+ let stream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+
+ try {
+ stream.init(aFile, MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+ return json.decodeFromStream(stream, stream.available());
+ } catch (ex) {
+ LOG("_readCacheFile: Error reading cache file: " + ex);
+ } finally {
+ stream.close();
+ }
+ return false;
+ },
+
+ /**
+ * Read from a given cache file asynchronously.
+ *
+ * @param aPath the file path.
+ *
+ * @returns {Promise} A promise, resolved successfully if retrieveing data
+ * succeeds.
+ */
+ _asyncReadCacheFile: function SRCH_SVC__asyncReadCacheFile(aPath) {
+ return Task.spawn(function() {
+ let json;
+ try {
+ let bytes = yield OS.File.read(aPath);
+ json = JSON.parse(new TextDecoder().decode(bytes));
+ } catch (ex) {
+ LOG("_asyncReadCacheFile: Error reading cache file: " + ex);
+ json = {};
+ }
+ throw new Task.Result(json);
+ });
+ },
+
+ _batchTask: null,
+ get batchTask() {
+ if (!this._batchTask) {
+ let task = function taskCallback() {
+ LOG("batchTask: Invalidating engine cache");
+ this._buildCache();
+ }.bind(this);
+ this._batchTask = new DeferredTask(task, CACHE_INVALIDATION_DELAY);
+ }
+ return this._batchTask;
+ },
+
+ _addEngineToStore: function SRCH_SVC_addEngineToStore(aEngine) {
+ LOG("_addEngineToStore: Adding engine: \"" + aEngine.name + "\"");
+
+ // See if there is an existing engine with the same name. However, if this
+ // engine is updating another engine, it's allowed to have the same name.
+ var hasSameNameAsUpdate = (aEngine._engineToUpdate &&
+ aEngine.name == aEngine._engineToUpdate.name);
+ if (aEngine.name in this._engines && !hasSameNameAsUpdate) {
+ LOG("_addEngineToStore: Duplicate engine found, aborting!");
+ return;
+ }
+
+ if (aEngine._engineToUpdate) {
+ // We need to replace engineToUpdate with the engine that just loaded.
+ var oldEngine = aEngine._engineToUpdate;
+
+ // Remove the old engine from the hash, since it's keyed by name, and our
+ // name might change (the update might have a new name).
+ delete this._engines[oldEngine.name];
+
+ // Hack: we want to replace the old engine with the new one, but since
+ // people may be holding refs to the nsISearchEngine objects themselves,
+ // we'll just copy over all "private" properties (those without a getter
+ // or setter) from one object to the other.
+ for (var p in aEngine) {
+ if (!(aEngine.__lookupGetter__(p) || aEngine.__lookupSetter__(p)))
+ oldEngine[p] = aEngine[p];
+ }
+ aEngine = oldEngine;
+ aEngine._engineToUpdate = null;
+
+ // Add the engine back
+ this._engines[aEngine.name] = aEngine;
+ notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
+ } else {
+ // Not an update, just add the new engine.
+ this._engines[aEngine.name] = aEngine;
+ // Only add the engine to the list of sorted engines if the initial list
+ // has already been built (i.e. if this.__sortedEngines is non-null). If
+ // it hasn't, we're loading engines from disk and the sorted engine list
+ // will be built once we need it.
+ if (this.__sortedEngines) {
+ this.__sortedEngines.push(aEngine);
+ this._saveSortedEngineList();
+ }
+ notifyAction(aEngine, SEARCH_ENGINE_ADDED);
+ }
+
+ if (aEngine._hasUpdates) {
+ // Schedule the engine's next update, if it isn't already.
+ if (!engineMetadataService.getAttr(aEngine, "updateexpir"))
+ engineUpdateService.scheduleNextUpdate(aEngine);
+ }
+ },
+
+ _loadEnginesFromCache: function SRCH_SVC__loadEnginesFromCache(aDir) {
+ let engines = aDir.engines;
+ LOG("_loadEnginesFromCache: Loading from cache. " + engines.length + " engines to load.");
+ for (let i = 0; i < engines.length; i++) {
+ let json = engines[i];
+
+ try {
+ let engine;
+ if (json.filePath)
+ engine = new Engine({type: "filePath", value: json.filePath},
+ json._readOnly);
+ else if (json._url)
+ engine = new Engine({type: "uri", value: json._url}, json._readOnly);
+
+ engine._initWithJSON(json);
+ this._addEngineToStore(engine);
+ } catch (ex) {
+ LOG("Failed to load " + engines[i]._name + " from cache: " + ex);
+ LOG("Engine JSON: " + engines[i].toSource());
+ }
+ }
+ },
+
+ _loadEnginesFromDir: function SRCH_SVC__loadEnginesFromDir(aDir) {
+ LOG("_loadEnginesFromDir: Searching in " + aDir.path + " for search engines.");
+
+ // Check whether aDir is the user profile dir
+ var isInProfile = aDir.equals(getDir(NS_APP_USER_SEARCH_DIR));
+
+ var files = aDir.directoryEntries
+ .QueryInterface(Ci.nsIDirectoryEnumerator);
+
+ while (files.hasMoreElements()) {
+ var file = files.nextFile;
+
+ // Ignore hidden and empty files, and directories
+ if (!file.isFile() || file.fileSize == 0 || file.isHidden())
+ continue;
+
+ var fileURL = NetUtil.ioService.newFileURI(file).QueryInterface(Ci.nsIURL);
+ var fileExtension = fileURL.fileExtension.toLowerCase();
+ var isWritable = isInProfile && file.isWritable();
+
+ if (fileExtension != "xml") {
+ // Not an engine
+ continue;
+ }
+
+ var addedEngine = null;
+ try {
+ addedEngine = new Engine(file, !isWritable);
+ addedEngine._initFromFile();
+ } catch (ex) {
+ LOG("_loadEnginesFromDir: Failed to load " + file.path + "!\n" + ex);
+ continue;
+ }
+
+ this._addEngineToStore(addedEngine);
+ }
+ },
+
+ /**
+ * Loads engines from a given directory asynchronously.
+ *
+ * @param aDir the directory.
+ *
+ * @returns {Promise} A promise, resolved successfully if retrieveing data
+ * succeeds.
+ */
+ _asyncLoadEnginesFromDir: function SRCH_SVC__asyncLoadEnginesFromDir(aDir) {
+ LOG("_asyncLoadEnginesFromDir: Searching in " + aDir.path + " for search engines.");
+
+ // Check whether aDir is the user profile dir
+ let isInProfile = aDir.equals(getDir(NS_APP_USER_SEARCH_DIR));
+ let iterator = new OS.File.DirectoryIterator(aDir.path);
+ return Task.spawn(function() {
+ let osfiles = yield iterator.nextBatch();
+ iterator.close();
+
+ let engines = [];
+ for (let osfile of osfiles) {
+ if (osfile.isDir || osfile.isSymLink)
+ continue;
+
+ let fileInfo = yield OS.File.stat(osfile.path);
+ if (fileInfo.size == 0)
+ continue;
+
+ let parts = osfile.path.split(".");
+ if (parts.length <= 1 || (parts.pop()).toLowerCase() != "xml") {
+ // Not an engine
+ continue;
+ }
+
+ let addedEngine = null;
+ try {
+ let file = new FileUtils.File(osfile.path);
+ let isWritable = isInProfile;
+ addedEngine = new Engine(file, !isWritable);
+ yield checkForSyncCompletion(addedEngine._asyncInitFromFile());
+ } catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
+ LOG("_asyncLoadEnginesFromDir: Failed to load " + osfile.path + "!\n" + ex);
+ continue;
+ }
+ engines.push(addedEngine);
+ }
+ throw new Task.Result(engines);
+ }.bind(this));
+ },
+
+ _loadFromChromeURLs: function SRCH_SVC_loadFromChromeURLs(aURLs) {
+ aURLs.forEach(function (url) {
+ try {
+ LOG("_loadFromChromeURLs: loading engine from chrome url: " + url);
+
+ let engine = new Engine(makeURI(url), true);
+
+ engine._initFromURISync();
+
+ this._addEngineToStore(engine);
+ } catch (ex) {
+ LOG("_loadFromChromeURLs: failed to load engine: " + ex);
+ }
+ }, this);
+ },
+
+ /**
+ * Loads engines from Chrome URLs asynchronously.
+ *
+ * @param aURLs a list of URLs.
+ *
+ * @returns {Promise} A promise, resolved successfully if loading data
+ * succeeds.
+ */
+ _asyncLoadFromChromeURLs: function SRCH_SVC__asyncLoadFromChromeURLs(aURLs) {
+ return Task.spawn(function() {
+ let engines = [];
+ for (let url of aURLs) {
+ try {
+ LOG("_asyncLoadFromChromeURLs: loading engine from chrome url: " + url);
+ let engine = new Engine(NetUtil.newURI(url), true);
+ yield checkForSyncCompletion(engine._asyncInitFromURI());
+ engines.push(engine);
+ } catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
+ LOG("_asyncLoadFromChromeURLs: failed to load engine: " + ex);
+ }
+ }
+ throw new Task.Result(engines);
+ }.bind(this));
+ },
+
+ _convertChannelToFile: function(chan) {
+ let fileURI = chan.URI;
+ while (fileURI instanceof Ci.nsIJARURI)
+ fileURI = fileURI.JARFile;
+ fileURI.QueryInterface(Ci.nsIFileURL);
+
+ return fileURI.file;
+ },
+
+ _findJAREngines: function SRCH_SVC_findJAREngines() {
+ LOG("_findJAREngines: looking for engines in JARs")
+
+ let chan = makeChannel(APP_SEARCH_PREFIX + "list.txt");
+ if (!chan) {
+ LOG("_findJAREngines: " + APP_SEARCH_PREFIX + " isn't registered");
+ return [[], []];
+ }
+
+ let uris = [];
+ let chromeFiles = [];
+
+ // Find the underlying JAR file (_loadEngines uses it to determine
+ // whether it needs to invalidate the cache)
+ let jarPackaging = false;
+ if (chan.URI instanceof Ci.nsIJARURI) {
+ chromeFiles.push(this._convertChannelToFile(chan));
+ jarPackaging = true;
+ }
+
+ let sis = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ sis.init(chan.open());
+ this._parseListTxt(sis.read(sis.available()), jarPackaging,
+ chromeFiles, uris);
+ return [chromeFiles, uris];
+ },
+
+ /**
+ * Loads jar engines asynchronously.
+ *
+ * @returns {Promise} A promise, resolved successfully if finding jar engines
+ * succeeds.
+ */
+ _asyncFindJAREngines: function SRCH_SVC__asyncFindJAREngines() {
+ return Task.spawn(function() {
+ LOG("_asyncFindJAREngines: looking for engines in JARs")
+
+ let listURL = APP_SEARCH_PREFIX + "list.txt";
+ let chan = makeChannel(listURL);
+ if (!chan) {
+ LOG("_asyncFindJAREngines: " + APP_SEARCH_PREFIX + " isn't registered");
+ throw new Task.Result([[], []]);
+ }
+
+ let uris = [];
+ let chromeFiles = [];
+
+ // Find the underlying JAR file (_loadEngines uses it to determine
+ // whether it needs to invalidate the cache)
+ let jarPackaging = false;
+ if (chan.URI instanceof Ci.nsIJARURI) {
+ chromeFiles.push(this._convertChannelToFile(chan));
+ jarPackaging = true;
+ }
+
+ // Read list.txt to find the engines we need to load.
+ let deferred = Promise.defer();
+ let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsIXMLHttpRequest);
+ request.overrideMimeType("text/plain");
+ request.onload = function(aEvent) {
+ deferred.resolve(aEvent.target.responseText);
+ };
+ request.onerror = function(aEvent) {
+ LOG("_asyncFindJAREngines: failed to read " + listURL);
+ deferred.resolve("");
+ };
+ request.open("GET", NetUtil.newURI(listURL).spec, true);
+ request.send();
+ let list = yield deferred.promise;
+
+ this._parseListTxt(list, jarPackaging, chromeFiles, uris);
+ throw new Task.Result([chromeFiles, uris]);
+ }.bind(this));
+ },
+
+ _parseListTxt: function SRCH_SVC_parseListTxt(list, jarPackaging,
+ chromeFiles, uris) {
+ let names = list.split("\n").filter(n => !!n);
+ // This maps the names of our built-in engines to a boolean
+ // indicating whether it should be hidden by default.
+ let jarNames = new Map();
+ for (let name of names) {
+ if (name.endsWith(":hidden")) {
+ name = name.split(":")[0];
+ jarNames.set(name, true);
+ } else {
+ jarNames.set(name, false);
+ }
+ }
+
+ // Check if we have a useable country specific list of visible default engines.
+ let engineNames;
+ let visibleDefaultEngines =
+ engineMetadataService.getGlobalAttr("visibleDefaultEngines");
+ if (visibleDefaultEngines &&
+ engineMetadataService.getGlobalAttr("visibleDefaultEnginesHash") == getVerificationHash(visibleDefaultEngines)) {
+ engineNames = visibleDefaultEngines.split(",");
+
+ for (let engineName of engineNames) {
+ // If all engineName values are part of jarNames,
+ // then we can use the country specific list, otherwise ignore it.
+ // The visibleDefaultEngines string containing the name of an engine we
+ // don't ship indicates the server is misconfigured to answer requests
+ // from the specific Firefox version we are running, so ignoring the
+ // value altogether is safer.
+ if (!jarNames.has(engineName)) {
+ LOG("_parseListTxt: ignoring visibleDefaultEngines value because " +
+ engineName + " is not in the jar engines we have found");
+ engineNames = null;
+ break;
+ }
+ }
+ }
+
+ // Fallback to building a list based on the :hidden suffixes found in list.txt.
+ if (!engineNames) {
+ engineNames = [];
+ for (let [name, hidden] of jarNames) {
+ if (!hidden)
+ engineNames.push(name);
+ }
+ }
+
+ for (let name of engineNames) {
+ let uri = APP_SEARCH_PREFIX + name + ".xml";
+ uris.push(uri);
+ if (!jarPackaging) {
+ // Flat packaging requires that _loadEngines checks the modification
+ // time of each engine file.
+ let chan = makeChannel(uri);
+ if (chan)
+ chromeFiles.push(this._convertChannelToFile(chan));
+ else
+ LOG("_findJAREngines: couldn't resolve " + uri);
+ }
+ }
+
+ // Store this so that it can be used while writing the cache file.
+ this._visibleDefaultEngines = engineNames;
+ },
+
+
+ _saveSortedEngineList: function SRCH_SVC_saveSortedEngineList() {
+ LOG("SRCH_SVC_saveSortedEngineList: starting");
+
+ // Set the useDB pref to indicate that from now on we should use the order
+ // information stored in the database.
+ Services.prefs.setBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", true);
+
+ var engines = this._getSortedEngines(true);
+
+ let instructions = [];
+ for (var i = 0; i < engines.length; ++i) {
+ instructions.push(
+ {key: "order",
+ value: i+1,
+ engine: engines[i]
+ });
+ }
+
+ engineMetadataService.setAttrs(instructions);
+ LOG("SRCH_SVC_saveSortedEngineList: done");
+ },
+
+ _buildSortedEngineList: function SRCH_SVC_buildSortedEngineList() {
+ LOG("_buildSortedEngineList: building list");
+ var addedEngines = { };
+ this.__sortedEngines = [];
+ var engine;
+
+ // If the user has specified a custom engine order, read the order
+ // information from the engineMetadataService instead of the default
+ // prefs.
+ if (Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", false)) {
+ LOG("_buildSortedEngineList: using db for order");
+
+ // Flag to keep track of whether or not we need to call _saveSortedEngineList.
+ let needToSaveEngineList = false;
+
+ for (let name in this._engines) {
+ let engine = this._engines[name];
+ var orderNumber = engineMetadataService.getAttr(engine, "order");
+
+ // Since the DB isn't regularly cleared, and engine files may disappear
+ // without us knowing, we may already have an engine in this slot. If
+ // that happens, we just skip it - it will be added later on as an
+ // unsorted engine.
+ if (orderNumber && !this.__sortedEngines[orderNumber-1]) {
+ this.__sortedEngines[orderNumber-1] = engine;
+ addedEngines[engine.name] = engine;
+ } else {
+ // We need to call _saveSortedEngineList so this gets sorted out.
+ needToSaveEngineList = true;
+ }
+ }
+
+ // Filter out any nulls for engines that may have been removed
+ var filteredEngines = this.__sortedEngines.filter(function(a) { return !!a; });
+ if (this.__sortedEngines.length != filteredEngines.length)
+ needToSaveEngineList = true;
+ this.__sortedEngines = filteredEngines;
+
+ if (needToSaveEngineList)
+ this._saveSortedEngineList();
+ } else {
+ // The DB isn't being used, so just read the engine order from the prefs
+ var i = 0;
+ var engineName;
+ var prefName;
+
+ try {
+ var extras =
+ Services.prefs.getChildList(BROWSER_SEARCH_PREF + "order.extra.");
+
+ for (prefName of extras) {
+ engineName = Services.prefs.getCharPref(prefName);
+
+ engine = this._engines[engineName];
+ if (!engine || engine.name in addedEngines)
+ continue;
+
+ this.__sortedEngines.push(engine);
+ addedEngines[engine.name] = engine;
+ }
+ }
+ catch (e) { }
+
+ while (true) {
+ engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + (++i));
+ if (!engineName)
+ break;
+
+ engine = this._engines[engineName];
+ if (!engine || engine.name in addedEngines)
+ continue;
+
+ this.__sortedEngines.push(engine);
+ addedEngines[engine.name] = engine;
+ }
+ }
+
+ // Array for the remaining engines, alphabetically sorted.
+ let alphaEngines = [];
+
+ for (let name in this._engines) {
+ let engine = this._engines[name];
+ if (!(engine.name in addedEngines))
+ alphaEngines.push(this._engines[engine.name]);
+ }
+
+ let locale = Cc["@mozilla.org/intl/nslocaleservice;1"]
+ .getService(Ci.nsILocaleService)
+ .newLocale(getLocale());
+ let collation = Cc["@mozilla.org/intl/collation-factory;1"]
+ .createInstance(Ci.nsICollationFactory)
+ .CreateCollation(locale);
+ const strength = Ci.nsICollation.kCollationCaseInsensitiveAscii;
+ let comparator = (a, b) => collation.compareString(strength, a.name, b.name);
+ alphaEngines.sort(comparator);
+ return this.__sortedEngines = this.__sortedEngines.concat(alphaEngines);
+ },
+
+ /**
+ * Get a sorted array of engines.
+ * @param aWithHidden
+ * True if hidden plugins should be included in the result.
+ */
+ _getSortedEngines: function SRCH_SVC_getSorted(aWithHidden) {
+ if (aWithHidden)
+ return this._sortedEngines;
+
+ return this._sortedEngines.filter(function (engine) {
+ return !engine.hidden;
+ });
+ },
+
+ // nsIBrowserSearchService
+ init: function SRCH_SVC_init(observer) {
+ LOG("SearchService.init");
+ let self = this;
+ if (!this._initStarted) {
+ this._initStarted = true;
+ Task.spawn(function task() {
+ try {
+ yield checkForSyncCompletion(engineMetadataService.init());
+ // Complete initialization by calling asynchronous initializer.
+ yield self._asyncInit();
+ } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
+ // No need to pursue asynchronous because synchronous fallback was
+ // called and has finished.
+ } catch (ex) {
+ self._initObservers.reject(ex);
+ }
+ });
+ }
+ if (observer) {
+ this._initObservers.promise.then(
+ function onSuccess() {
+ try {
+ observer.onInitComplete(self._initRV);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+ function onError(aReason) {
+ Cu.reportError("Internal error while initializing SearchService: " + aReason);
+ observer.onInitComplete(Components.results.NS_ERROR_UNEXPECTED);
+ }
+ );
+ }
+ },
+
+ get isInitialized() {
+ return gInitialized;
+ },
+
+ getEngines: function SRCH_SVC_getEngines(aCount) {
+ this._ensureInitialized();
+ LOG("getEngines: getting all engines");
+ var engines = this._getSortedEngines(true);
+ aCount.value = engines.length;
+ return engines;
+ },
+
+ getVisibleEngines: function SRCH_SVC_getVisible(aCount) {
+ this._ensureInitialized();
+ LOG("getVisibleEngines: getting all visible engines");
+ var engines = this._getSortedEngines(false);
+ aCount.value = engines.length;
+ return engines;
+ },
+
+ getDefaultEngines: function SRCH_SVC_getDefault(aCount) {
+ this._ensureInitialized();
+ function isDefault(engine) {
+ return engine._isDefault;
+ };
+ var engines = this._sortedEngines.filter(isDefault);
+ var engineOrder = {};
+ var engineName;
+ var i = 1;
+
+ // Build a list of engines which we have ordering information for.
+ // We're rebuilding the list here because _sortedEngines contain the
+ // current order, but we want the original order.
+
+ // First, look at the "browser.search.order.extra" branch.
+ try {
+ var extras = Services.prefs.getChildList(BROWSER_SEARCH_PREF + "order.extra.");
+
+ for (var prefName of extras) {
+ engineName = Services.prefs.getCharPref(prefName);
+
+ if (!(engineName in engineOrder))
+ engineOrder[engineName] = i++;
+ }
+ } catch (e) {
+ LOG("Getting extra order prefs failed: " + e);
+ }
+
+ // Now look through the "browser.search.order" branch.
+ for (var j = 1; ; j++) {
+ engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + j);
+ if (!engineName)
+ break;
+
+ if (!(engineName in engineOrder))
+ engineOrder[engineName] = i++;
+ }
+
+ LOG("getDefaultEngines: engineOrder: " + engineOrder.toSource());
+
+ function compareEngines (a, b) {
+ var aIdx = engineOrder[a.name];
+ var bIdx = engineOrder[b.name];
+
+ if (aIdx && bIdx)
+ return aIdx - bIdx;
+ if (aIdx)
+ return -1;
+ if (bIdx)
+ return 1;
+
+ return a.name.localeCompare(b.name);
+ }
+ engines.sort(compareEngines);
+
+ aCount.value = engines.length;
+ return engines;
+ },
+
+ getEngineByName: function SRCH_SVC_getEngineByName(aEngineName) {
+ this._ensureInitialized();
+ return this._engines[aEngineName] || null;
+ },
+
+ getEngineByAlias: function SRCH_SVC_getEngineByAlias(aAlias) {
+ this._ensureInitialized();
+ for (var engineName in this._engines) {
+ var engine = this._engines[engineName];
+ if (engine && engine.alias == aAlias)
+ return engine;
+ }
+ return null;
+ },
+
+ addEngineWithDetails: function SRCH_SVC_addEWD(aName, aIconURL, aAlias,
+ aDescription, aMethod,
+ aTemplate, aExtensionID) {
+ this._ensureInitialized();
+ if (!aName)
+ FAIL("Invalid name passed to addEngineWithDetails!");
+ if (!aMethod)
+ FAIL("Invalid method passed to addEngineWithDetails!");
+ if (!aTemplate)
+ FAIL("Invalid template passed to addEngineWithDetails!");
+ if (this._engines[aName])
+ FAIL("An engine with that name already exists!", Cr.NS_ERROR_FILE_ALREADY_EXISTS);
+
+ var engine = new Engine(getSanitizedFile(aName), false);
+ engine._initFromMetadata(aName, aIconURL, aAlias, aDescription,
+ aMethod, aTemplate, aExtensionID);
+ this._addEngineToStore(engine);
+ },
+
+ addEngine: function SRCH_SVC_addEngine(aEngineURL, aDataType, aIconURL,
+ aConfirm, aCallback) {
+ LOG("addEngine: Adding \"" + aEngineURL + "\".");
+ this._ensureInitialized();
+ try {
+ var uri = makeURI(aEngineURL);
+ var engine = new Engine(uri, false);
+ if (aCallback) {
+ engine._installCallback = function (errorCode) {
+ try {
+ if (errorCode == null)
+ aCallback.onSuccess(engine);
+ else
+ aCallback.onError(errorCode);
+ } catch (ex) {
+ Cu.reportError("Error invoking addEngine install callback: " + ex);
+ }
+ // Clear the reference to the callback now that it's been invoked.
+ engine._installCallback = null;
+ };
+ }
+ engine._initFromURIAndLoad();
+ } catch (ex) {
+ // Drop the reference to the callback, if set
+ if (engine)
+ engine._installCallback = null;
+ FAIL("addEngine: Error adding engine:\n" + ex, Cr.NS_ERROR_FAILURE);
+ }
+ engine._setIcon(aIconURL, false);
+ engine._confirm = aConfirm;
+ },
+
+ removeEngine: function SRCH_SVC_removeEngine(aEngine) {
+ this._ensureInitialized();
+ if (!aEngine)
+ FAIL("no engine passed to removeEngine!");
+
+ var engineToRemove = null;
+ for (var e in this._engines) {
+ if (aEngine.wrappedJSObject == this._engines[e])
+ engineToRemove = this._engines[e];
+ }
+
+ if (!engineToRemove)
+ FAIL("removeEngine: Can't find engine to remove!", Cr.NS_ERROR_FILE_NOT_FOUND);
+
+ if (engineToRemove == this.currentEngine) {
+ this._currentEngine = null;
+ }
+
+ if (engineToRemove == this.defaultEngine) {
+ this._defaultEngine = null;
+ }
+
+ if (engineToRemove._readOnly) {
+ // Just hide it (the "hidden" setter will notify) and remove its alias to
+ // avoid future conflicts with other engines.
+ engineToRemove.hidden = true;
+ engineToRemove.alias = null;
+ } else {
+ // Cancel the serialized task if it's pending. Since the task is a
+ // synchronous function, we don't need to wait on the "finalize" method.
+ if (engineToRemove._lazySerializeTask) {
+ engineToRemove._lazySerializeTask.disarm();
+ engineToRemove._lazySerializeTask = null;
+ }
+
+ // Remove the engine file from disk (this might throw)
+ engineToRemove._remove();
+ engineToRemove._file = null;
+
+ // Remove the engine from _sortedEngines
+ var index = this._sortedEngines.indexOf(engineToRemove);
+ if (index == -1)
+ FAIL("Can't find engine to remove in _sortedEngines!", Cr.NS_ERROR_FAILURE);
+ this.__sortedEngines.splice(index, 1);
+
+ // Remove the engine from the internal store
+ delete this._engines[engineToRemove.name];
+
+ notifyAction(engineToRemove, SEARCH_ENGINE_REMOVED);
+
+ // Since we removed an engine, we need to update the preferences.
+ this._saveSortedEngineList();
+ }
+ },
+
+ moveEngine: function SRCH_SVC_moveEngine(aEngine, aNewIndex) {
+ this._ensureInitialized();
+ if ((aNewIndex > this._sortedEngines.length) || (aNewIndex < 0))
+ FAIL("SRCH_SVC_moveEngine: Index out of bounds!");
+ if (!(aEngine instanceof Ci.nsISearchEngine))
+ FAIL("SRCH_SVC_moveEngine: Invalid engine passed to moveEngine!");
+ if (aEngine.hidden)
+ FAIL("moveEngine: Can't move a hidden engine!", Cr.NS_ERROR_FAILURE);
+
+ var engine = aEngine.wrappedJSObject;
+
+ var currentIndex = this._sortedEngines.indexOf(engine);
+ if (currentIndex == -1)
+ FAIL("moveEngine: Can't find engine to move!", Cr.NS_ERROR_UNEXPECTED);
+
+ // Our callers only take into account non-hidden engines when calculating
+ // aNewIndex, but we need to move it in the array of all engines, so we
+ // need to adjust aNewIndex accordingly. To do this, we count the number
+ // of hidden engines in the list before the engine that we're taking the
+ // place of. We do this by first finding newIndexEngine (the engine that
+ // we were supposed to replace) and then iterating through the complete
+ // engine list until we reach it, increasing aNewIndex for each hidden
+ // engine we find on our way there.
+ //
+ // This could be further simplified by having our caller pass in
+ // newIndexEngine directly instead of aNewIndex.
+ var newIndexEngine = this._getSortedEngines(false)[aNewIndex];
+ if (!newIndexEngine)
+ FAIL("moveEngine: Can't find engine to replace!", Cr.NS_ERROR_UNEXPECTED);
+
+ for (var i = 0; i < this._sortedEngines.length; ++i) {
+ if (newIndexEngine == this._sortedEngines[i])
+ break;
+ if (this._sortedEngines[i].hidden)
+ aNewIndex++;
+ }
+
+ if (currentIndex == aNewIndex)
+ return; // nothing to do!
+
+ // Move the engine
+ var movedEngine = this.__sortedEngines.splice(currentIndex, 1)[0];
+ this.__sortedEngines.splice(aNewIndex, 0, movedEngine);
+
+ notifyAction(engine, SEARCH_ENGINE_CHANGED);
+
+ // Since we moved an engine, we need to update the preferences.
+ this._saveSortedEngineList();
+ },
+
+ restoreDefaultEngines: function SRCH_SVC_resetDefaultEngines() {
+ this._ensureInitialized();
+ for (let name in this._engines) {
+ let e = this._engines[name];
+ // Unhide all default engines
+ if (e.hidden && e._isDefault)
+ e.hidden = false;
+ }
+ },
+
+ get defaultEngine() {
+ this._ensureInitialized();
+ if (!this._defaultEngine) {
+ let defaultEngine = this.getEngineByName(getLocalizedPref(BROWSER_SEARCH_PREF + "defaultenginename", ""))
+ if (!defaultEngine)
+ defaultEngine = this._getSortedEngines(false)[0] || null;
+ this._defaultEngine = defaultEngine;
+ }
+ if (this._defaultEngine.hidden)
+ return this._getSortedEngines(false)[0];
+ return this._defaultEngine;
+ },
+
+ set defaultEngine(val) {
+ this._ensureInitialized();
+ // Sometimes we get wrapped nsISearchEngine objects (external XPCOM callers),
+ // and sometimes we get raw Engine JS objects (callers in this file), so
+ // handle both.
+ if (!(val instanceof Ci.nsISearchEngine) && !(val instanceof Engine))
+ FAIL("Invalid argument passed to defaultEngine setter");
+
+ let newDefaultEngine = this.getEngineByName(val.name);
+ if (!newDefaultEngine)
+ FAIL("Can't find engine in store!", Cr.NS_ERROR_UNEXPECTED);
+
+ if (newDefaultEngine == this._defaultEngine)
+ return;
+
+ this._defaultEngine = newDefaultEngine;
+
+ // If we change the default engine in the future, that change should impact
+ // users who have switched away from and then back to the build's "default"
+ // engine. So clear the user pref when the defaultEngine is set to the
+ // build's default engine, so that the defaultEngine getter falls back to
+ // whatever the default is.
+ if (this._defaultEngine == this._originalDefaultEngine) {
+ Services.prefs.clearUserPref(BROWSER_SEARCH_PREF + "defaultenginename");
+ }
+ else {
+ setLocalizedPref(BROWSER_SEARCH_PREF + "defaultenginename", this._defaultEngine.name);
+ }
+
+ notifyAction(this._defaultEngine, SEARCH_ENGINE_DEFAULT);
+ },
+
+ get currentEngine() {
+ this._ensureInitialized();
+ if (!this._currentEngine) {
+ let name = engineMetadataService.getGlobalAttr("current");
+ if (engineMetadataService.getGlobalAttr("hash") == getVerificationHash(name)) {
+ this._currentEngine = this.getEngineByName(name);
+ }
+ }
+
+ if (!this._currentEngine || this._currentEngine.hidden)
+ this._currentEngine = this._originalDefaultEngine;
+ if (!this._currentEngine || this._currentEngine.hidden)
+ this._currentEngine = this._getSortedEngines(false)[0];
+
+ if (!this._currentEngine) {
+ // Last resort fallback: unhide the original default engine.
+ this._currentEngine = this._originalDefaultEngine;
+ if (this._currentEngine)
+ this._currentEngine.hidden = false;
+ }
+
+ return this._currentEngine;
+ },
+
+ set currentEngine(val) {
+ this._ensureInitialized();
+ // Sometimes we get wrapped nsISearchEngine objects (external XPCOM callers),
+ // and sometimes we get raw Engine JS objects (callers in this file), so
+ // handle both.
+ if (!(val instanceof Ci.nsISearchEngine) && !(val instanceof Engine))
+ FAIL("Invalid argument passed to currentEngine setter");
+
+ var newCurrentEngine = this.getEngineByName(val.name);
+ if (!newCurrentEngine)
+ FAIL("Can't find engine in store!", Cr.NS_ERROR_UNEXPECTED);
+
+ if (newCurrentEngine == this._currentEngine)
+ return;
+
+ this._currentEngine = newCurrentEngine;
+
+ // If we change the default engine in the future, that change should impact
+ // users who have switched away from and then back to the build's "default"
+ // engine. So clear the user pref when the currentEngine is set to the
+ // build's default engine, so that the currentEngine getter falls back to
+ // whatever the default is.
+ let newName = this._currentEngine.name;
+ if (this._currentEngine == this._originalDefaultEngine) {
+ newName = "";
+ }
+
+ engineMetadataService.setGlobalAttr("current", newName);
+ engineMetadataService.setGlobalAttr("hash", getVerificationHash(newName));
+
+ notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
+ },
+
+ getDefaultEngineInfo() {
+ let result = {};
+
+ let engine;
+ try {
+ engine = this.defaultEngine;
+ } catch(e) {
+ // The defaultEngine getter will throw if there's no engine at all,
+ // which shouldn't happen unless an add-on or a test deleted all of them.
+ // Our preferences UI doesn't let users do that.
+ Cu.reportError("getDefaultEngineInfo: No default engine");
+ }
+
+ if (!engine) {
+ result.name = "NONE";
+ } else {
+ if (engine.name)
+ result.name = engine.name;
+
+ result.loadPath = engine._anonymizedLoadPath;
+
+ // For privacy, we only collect the submission URL for engines
+ // from the application or distribution folder...
+ let sendSubmissionURL =
+ /^(?:jar:)?(?:\[app\]|\[distribution\])/.test(result.loadPath);
+
+ // ... or engines sorted by default near the top of the list.
+ if (!sendSubmissionURL) {
+ let extras =
+ Services.prefs.getChildList(BROWSER_SEARCH_PREF + "order.extra.");
+
+ for (let prefName of extras) {
+ try {
+ if (result.name == Services.prefs.getCharPref(prefName)) {
+ sendSubmissionURL = true;
+ break;
+ }
+ } catch(e) {}
+ }
+
+ let i = 0;
+ while (!sendSubmissionURL) {
+ let engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + (++i));
+ if (!engineName)
+ break;
+ if (result.name == engineName) {
+ sendSubmissionURL = true;
+ break;
+ }
+ }
+ }
+
+ if (sendSubmissionURL) {
+ let uri = engine._getURLOfType("text/html")
+ .getSubmission("", engine, "searchbar").uri;
+ uri.userPass = ""; // Avoid reporting a username or password.
+ result.submissionURL = uri.spec;
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * This map is built lazily after the available search engines change. It
+ * allows quick parsing of an URL representing a search submission into the
+ * search engine name and original terms.
+ *
+ * The keys are strings containing the domain name and lowercase path of the
+ * engine submission, for example "www.google.com/search".
+ *
+ * The values are objects with these properties:
+ * {
+ * engine: The associated nsISearchEngine.
+ * termsParameterName: Name of the URL parameter containing the search
+ * terms, for example "q".
+ * }
+ */
+ _parseSubmissionMap: null,
+
+ _buildParseSubmissionMap: function SRCH_SVC__buildParseSubmissionMap() {
+ LOG("_buildParseSubmissionMap");
+ this._parseSubmissionMap = new Map();
+
+ // Used only while building the map, indicates which entries do not refer to
+ // the main domain of the engine but to an alternate domain, for example
+ // "www.google.fr" for the "www.google.com" search engine.
+ let keysOfAlternates = new Set();
+
+ for (let engine of this._sortedEngines) {
+ LOG("Processing engine: " + engine.name);
+
+ if (engine.hidden) {
+ LOG("Engine is hidden.");
+ continue;
+ }
+
+ let urlParsingInfo = engine.getURLParsingInfo();
+ if (!urlParsingInfo) {
+ LOG("Engine does not support URL parsing.");
+ continue;
+ }
+
+ // Store the same object on each matching map key, as an optimization.
+ let mapValueForEngine = {
+ engine: engine,
+ termsParameterName: urlParsingInfo.termsParameterName,
+ };
+
+ let processDomain = (domain, isAlternate) => {
+ let key = domain + urlParsingInfo.path;
+
+ // Apply the logic for which main domains take priority over alternate
+ // domains, even if they are found later in the ordered engine list.
+ let existingEntry = this._parseSubmissionMap.get(key);
+ if (!existingEntry) {
+ LOG("Adding new entry: " + key);
+ if (isAlternate) {
+ keysOfAlternates.add(key);
+ }
+ } else if (!isAlternate && keysOfAlternates.has(key)) {
+ LOG("Overriding alternate entry: " + key +
+ " (" + existingEntry.engine.name + ")");
+ keysOfAlternates.delete(key);
+ } else {
+ LOG("Keeping existing entry: " + key +
+ " (" + existingEntry.engine.name + ")");
+ return;
+ }
+
+ this._parseSubmissionMap.set(key, mapValueForEngine);
+ };
+
+ processDomain(urlParsingInfo.mainDomain, false);
+ SearchStaticData.getAlternateDomains(urlParsingInfo.mainDomain)
+ .forEach(d => processDomain(d, true));
+ }
+ },
+
+ parseSubmissionURL: function SRCH_SVC_parseSubmissionURL(aURL) {
+ this._ensureInitialized();
+ LOG("parseSubmissionURL: Parsing \"" + aURL + "\".");
+
+ if (!this._parseSubmissionMap) {
+ this._buildParseSubmissionMap();
+ }
+
+ // Extract the elements of the provided URL first.
+ let soughtKey, soughtQuery;
+ try {
+ let soughtUrl = NetUtil.newURI(aURL).QueryInterface(Ci.nsIURL);
+
+ // Exclude any URL that is not HTTP or HTTPS from the beginning.
+ if (soughtUrl.scheme != "http" && soughtUrl.scheme != "https") {
+ LOG("The URL scheme is not HTTP or HTTPS.");
+ return gEmptyParseSubmissionResult;
+ }
+
+ // Reading these URL properties may fail and raise an exception.
+ soughtKey = soughtUrl.host + soughtUrl.filePath.toLowerCase();
+ soughtQuery = soughtUrl.query;
+ } catch (ex) {
+ // Errors while parsing the URL or accessing the properties are not fatal.
+ LOG("The value does not look like a structured URL.");
+ return gEmptyParseSubmissionResult;
+ }
+
+ // Look up the domain and path in the map to identify the search engine.
+ let mapEntry = this._parseSubmissionMap.get(soughtKey);
+ if (!mapEntry) {
+ LOG("No engine associated with domain and path: " + soughtKey);
+ return gEmptyParseSubmissionResult;
+ }
+
+ // Extract the search terms from the parameter, for example "caff%C3%A8"
+ // from the URL "https://www.google.com/search?q=caff%C3%A8&client=firefox".
+ let encodedTerms = null;
+ for (let param of soughtQuery.split("&")) {
+ let equalPos = param.indexOf("=");
+ if (equalPos != -1 &&
+ param.substr(0, equalPos) == mapEntry.termsParameterName) {
+ // This is the parameter we are looking for.
+ encodedTerms = param.substr(equalPos + 1);
+ break;
+ }
+ }
+ if (encodedTerms === null) {
+ LOG("Missing terms parameter: " + mapEntry.termsParameterName);
+ return gEmptyParseSubmissionResult;
+ }
+
+ let length = 0;
+ let offset = aURL.indexOf("?") + 1;
+ let query = aURL.slice(offset);
+ // Iterate a second time over the original input string to determine the
+ // correct search term offset and length in the original encoding.
+ for (let param of query.split("&")) {
+ let equalPos = param.indexOf("=");
+ if (equalPos != -1 &&
+ param.substr(0, equalPos) == mapEntry.termsParameterName) {
+ // This is the parameter we are looking for.
+ offset += equalPos + 1;
+ length = param.length - equalPos - 1;
+ break;
+ }
+ offset += param.length + 1;
+ }
+
+ // Decode the terms using the charset defined in the search engine.
+ let terms;
+ try {
+ terms = gTextToSubURI.UnEscapeAndConvert(
+ mapEntry.engine.queryCharset,
+ encodedTerms.replace(/\+/g, " "));
+ } catch (ex) {
+ // Decoding errors will cause this match to be ignored.
+ LOG("Parameter decoding failed. Charset: " +
+ mapEntry.engine.queryCharset);
+ return gEmptyParseSubmissionResult;
+ }
+
+ LOG("Match found. Terms: " + terms);
+ return new ParseSubmissionResult(mapEntry.engine, terms, offset, length);
+ },
+
+ // nsIObserver
+ observe: function SRCH_SVC_observe(aEngine, aTopic, aVerb) {
+ switch (aTopic) {
+ case SEARCH_ENGINE_TOPIC:
+ switch (aVerb) {
+ case SEARCH_ENGINE_LOADED:
+ var engine = aEngine.QueryInterface(Ci.nsISearchEngine);
+ LOG("nsSearchService::observe: Done installation of " + engine.name
+ + ".");
+ this._addEngineToStore(engine.wrappedJSObject);
+ if (engine.wrappedJSObject._useNow) {
+ LOG("nsSearchService::observe: setting current");
+ this.currentEngine = aEngine;
+ }
+ // The addition of the engine to the store always triggers an ADDED
+ // or a CHANGED notification, that will trigger the task below.
+ break;
+ case SEARCH_ENGINE_ADDED:
+ case SEARCH_ENGINE_CHANGED:
+ case SEARCH_ENGINE_REMOVED:
+ this.batchTask.disarm();
+ this.batchTask.arm();
+ // Invalidate the map used to parse URLs to search engines.
+ this._parseSubmissionMap = null;
+ break;
+ }
+ break;
+
+ case QUIT_APPLICATION_TOPIC:
+ this._removeObservers();
+ break;
+
+ case "nsPref:changed":
+ if (aVerb == LOCALE_PREF) {
+ // Locale changed. Re-init. We rely on observers, because we can't
+ // return this promise to anyone.
+ this._asyncReInit();
+ break;
+ }
+ }
+ },
+
+ // nsITimerCallback
+ notify: function SRCH_SVC_notify(aTimer) {
+ LOG("_notify: checking for updates");
+
+ if (!Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "update", true))
+ return;
+
+ // Our timer has expired, but unfortunately, we can't get any data from it.
+ // Therefore, we need to walk our engine-list, looking for expired engines
+ var currentTime = Date.now();
+ LOG("currentTime: " + currentTime);
+ for (let name in this._engines) {
+ let engine = this._engines[name].wrappedJSObject;
+ if (!engine._hasUpdates)
+ continue;
+
+ LOG("checking " + engine.name);
+
+ var expirTime = engineMetadataService.getAttr(engine, "updateexpir");
+ LOG("expirTime: " + expirTime + "\nupdateURL: " + engine._updateURL +
+ "\niconUpdateURL: " + engine._iconUpdateURL);
+
+ var engineExpired = expirTime <= currentTime;
+
+ if (!expirTime || !engineExpired) {
+ LOG("skipping engine");
+ continue;
+ }
+
+ LOG(engine.name + " has expired");
+
+ engineUpdateService.update(engine);
+
+ // Schedule the next update
+ engineUpdateService.scheduleNextUpdate(engine);
+
+ } // end engine iteration
+ },
+
+ _addObservers: function SRCH_SVC_addObservers() {
+ Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, false);
+ Services.obs.addObserver(this, QUIT_APPLICATION_TOPIC, false);
+
+ // The current stage of shutdown. Used to help analyze crash
+ // signatures in case of shutdown timeout.
+ let shutdownState = {
+ step: "Not started",
+ latestError: {
+ message: undefined,
+ stack: undefined
+ }
+ };
+ OS.File.profileBeforeChange.addBlocker(
+ "Search service: shutting down",
+ () => Task.spawn(function* () {
+ if (this._batchTask) {
+ shutdownState.step = "Finalizing batched task";
+ try {
+ yield this._batchTask.finalize();
+ shutdownState.step = "Batched task finalized";
+ } catch (ex) {
+ shutdownState.step = "Batched task failed to finalize";
+
+ shutdownState.latestError.message = "" + ex;
+ if (ex && typeof ex == "object") {
+ shutdownState.latestError.stack = ex.stack || undefined;
+ }
+
+ // Ensure that error is reported and that it causes tests
+ // to fail.
+ Promise.reject(ex);
+ }
+ }
+
+ shutdownState.step = "Finalizing engine metadata service";
+ yield engineMetadataService.finalize();
+ shutdownState.step = "Engine metadata service finalized";
+
+ }.bind(this)),
+
+ () => shutdownState
+ );
+ },
+
+ _removeObservers: function SRCH_SVC_removeObservers() {
+ Services.obs.removeObserver(this, SEARCH_ENGINE_TOPIC);
+ Services.obs.removeObserver(this, QUIT_APPLICATION_TOPIC);
+ },
+
+ QueryInterface: function SRCH_SVC_QI(aIID) {
+ if (aIID.equals(Ci.nsIBrowserSearchService) ||
+ aIID.equals(Ci.nsIObserver) ||
+ aIID.equals(Ci.nsITimerCallback) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+var engineMetadataService = {
+ _jsonFile: OS.Path.join(OS.Constants.Path.profileDir, "search-metadata.json"),
+
+ // Boolean flag that is true if initialization was successful.
+ _initialized: false,
+
+ // A promise fulfilled once initialization is complete
+ _initializer: null,
+
+ /**
+ * Asynchronous initializer
+ *
+ * Note: In the current implementation, initialization never fails.
+ */
+ init: function epsInit() {
+ if (!this._initializer) {
+ // Launch asynchronous initialization
+ let initializer = this._initializer = Promise.defer();
+ Task.spawn((function task_init() {
+ LOG("metadata init: starting");
+ if (this._initialized) {
+ throw new Error("metadata init: invalid state, _initialized is " +
+ "true but initialization promise has not been " +
+ "resolved");
+ }
+ // 1. Load json file if it exists
+ try {
+ let contents = yield OS.File.read(this._jsonFile);
+ if (this._initialized) {
+ // No need to pursue asynchronous initialization,
+ // synchronous fallback was called and has finished.
+ return;
+ }
+ this._store = JSON.parse(new TextDecoder().decode(contents));
+ } catch (ex) {
+ if (this._initialized) {
+ // No need to pursue asynchronous initialization,
+ // synchronous fallback was called and has finished.
+ return;
+ }
+ // Couldn't load json, use an empty store
+ LOG("metadata init: could not load JSON file " + ex);
+ this._store = {};
+ }
+
+ this._initialized = true;
+ LOG("metadata init: complete");
+ }).bind(this)).then(
+ // 3. Inform any observers
+ function onSuccess() {
+ initializer.resolve();
+ },
+ function onError() {
+ initializer.reject();
+ }
+ );
+ }
+ return this._initializer.promise;
+ },
+
+ /**
+ * Synchronous implementation of initializer
+ *
+ * This initializer is able to pick wherever the async initializer
+ * is waiting. The asynchronous initializer is expected to stop
+ * if it detects that the synchronous initializer has completed
+ * initialization.
+ */
+ syncInit: function epsSyncInit() {
+ LOG("metadata syncInit start");
+ if (this._initialized) {
+ return;
+ }
+ let jsonFile = new FileUtils.File(this._jsonFile);
+ // 1. Load json file if it exists
+ if (jsonFile.exists()) {
+ try {
+ let uri = Services.io.newFileURI(jsonFile);
+ let stream = Services.io.newChannelFromURI2(uri,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_NORMAL,
+ Ci.nsIContentPolicy.TYPE_OTHER).open();
+ this._store = parseJsonFromStream(stream);
+ } catch (x) {
+ LOG("metadata syncInit: could not load JSON file " + x);
+ this._store = {};
+ }
+ } else {
+ LOG("metadata syncInit: using an empty store");
+ this._store = {};
+ }
+
+ this._initialized = true;
+
+ // 3. Inform any observers
+ if (this._initializer) {
+ this._initializer.resolve();
+ } else {
+ this._initializer = Promise.resolve();
+ }
+ LOG("metadata syncInit end");
+ },
+
+ getAttr: function epsGetAttr(engine, name) {
+ let record = this._store[engine._id];
+ if (!record) {
+ return null;
+ }
+
+ // attr names must be lower case
+ let aName = name.toLowerCase();
+ if (!record[aName])
+ return null;
+ return record[aName];
+ },
+
+ _globalFakeEngine: {_id: "[global]"},
+ getGlobalAttr: function epsGetGlobalAttr(name) {
+ return this.getAttr(this._globalFakeEngine, name);
+ },
+
+ _setAttr: function epsSetAttr(engine, name, value) {
+ // attr names must be lower case
+ name = name.toLowerCase();
+ let db = this._store;
+ let record = db[engine._id];
+ if (!record) {
+ record = db[engine._id] = {};
+ }
+ if (!record[name] || (record[name] != value)) {
+ record[name] = value;
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Set one metadata attribute for an engine.
+ *
+ * If an actual change has taken place, the attribute is committed
+ * automatically (and lazily), using this._commit.
+ *
+ * @param {nsISearchEngine} engine The engine to update.
+ * @param {string} key The name of the attribute. Case-insensitive. In
+ * the current implementation, this _must not_ conflict with properties
+ * of |Object|.
+ * @param {*} value A value to store.
+ */
+ setAttr: function epsSetAttr(engine, key, value) {
+ if (this._setAttr(engine, key, value)) {
+ this._commit();
+ }
+ },
+
+ setGlobalAttr: function epsGetGlobalAttr(key, value) {
+ this.setAttr(this._globalFakeEngine, key, value);
+ },
+
+ /**
+ * Bulk set metadata attributes for a number of engines.
+ *
+ * If actual changes have taken place, the store is committed
+ * automatically (and lazily), using this._commit.
+ *
+ * @param {Array.<{engine: nsISearchEngine, key: string, value: *}>} changes
+ * The list of changes to effect. See |setAttr| for the documentation of
+ * |engine|, |key|, |value|.
+ */
+ setAttrs: function epsSetAttrs(changes) {
+ let self = this;
+ let changed = false;
+ changes.forEach(function(change) {
+ changed |= self._setAttr(change.engine, change.key, change.value);
+ });
+ if (changed) {
+ this._commit();
+ }
+ },
+
+ /**
+ * Flush any waiting write.
+ */
+ finalize: function () {
+ return this._lazyWriter ? this._lazyWriter.finalize()
+ : Promise.resolve();
+ },
+
+ /**
+ * Commit changes to disk, asynchronously.
+ *
+ * Calls to this function are actually delayed by LAZY_SERIALIZE_DELAY
+ * (= 100ms). If the function is called again before the expiration of
+ * the delay, commits are merged and the function is again delayed by
+ * the same amount of time.
+ */
+ _commit: function epsCommit() {
+ LOG("metadata _commit: start");
+ if (!this._store) {
+ LOG("metadata _commit: nothing to do");
+ return;
+ }
+
+ if (!this._lazyWriter) {
+ LOG("metadata _commit: initializing lazy writer");
+ let writeCommit = function () {
+ LOG("metadata writeCommit: start");
+ let data = gEncoder.encode(JSON.stringify(engineMetadataService._store));
+ let path = engineMetadataService._jsonFile;
+ LOG("metadata writeCommit: path " + path);
+ let promise = OS.File.writeAtomic(path, data, { tmpPath: path + ".tmp" });
+ promise = promise.then(
+ function onSuccess() {
+ Services.obs.notifyObservers(null,
+ SEARCH_SERVICE_TOPIC,
+ SEARCH_SERVICE_METADATA_WRITTEN);
+ LOG("metadata writeCommit: done");
+ }
+ );
+ return promise;
+ }
+ this._lazyWriter = new DeferredTask(writeCommit, LAZY_SERIALIZE_DELAY);
+ }
+ LOG("metadata _commit: (re)setting timer");
+ this._lazyWriter.disarm();
+ this._lazyWriter.arm();
+ },
+ _lazyWriter: null
+};
+
+engineMetadataService._initialized = false;
+
+const SEARCH_UPDATE_LOG_PREFIX = "*** Search update: ";
+
+/**
+ * Outputs aText to the JavaScript console as well as to stdout, if the search
+ * logging pref (browser.search.update.log) is set to true.
+ */
+function ULOG(aText) {
+ if (Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "update.log", false)) {
+ dump(SEARCH_UPDATE_LOG_PREFIX + aText + "\n");
+ Services.console.logStringMessage(aText);
+ }
+}
+
+var engineUpdateService = {
+ scheduleNextUpdate: function eus_scheduleNextUpdate(aEngine) {
+ var interval = aEngine._updateInterval || SEARCH_DEFAULT_UPDATE_INTERVAL;
+ var milliseconds = interval * 86400000; // |interval| is in days
+ engineMetadataService.setAttr(aEngine, "updateexpir",
+ Date.now() + milliseconds);
+ },
+
+ update: function eus_Update(aEngine) {
+ let engine = aEngine.wrappedJSObject;
+ ULOG("update called for " + aEngine._name);
+ if (!Services.prefs.getBoolPref(BROWSER_SEARCH_PREF + "update", true) || !engine._hasUpdates)
+ return;
+
+ let testEngine = null;
+ let updateURL = engine._getURLOfType(URLTYPE_OPENSEARCH);
+ let updateURI = (updateURL && updateURL._hasRelation("self")) ?
+ updateURL.getSubmission("", engine).uri :
+ makeURI(engine._updateURL);
+ if (updateURI) {
+ if (engine._isDefault && !updateURI.schemeIs("https")) {
+ ULOG("Invalid scheme for default engine update");
+ return;
+ }
+
+ ULOG("updating " + engine.name + " from " + updateURI.spec);
+ testEngine = new Engine(updateURI, false);
+ testEngine._engineToUpdate = engine;
+ testEngine._initFromURIAndLoad();
+ } else
+ ULOG("invalid updateURI");
+
+ if (engine._iconUpdateURL) {
+ // If we're updating the engine too, use the new engine object,
+ // otherwise use the existing engine object.
+ (testEngine || engine)._setIcon(engine._iconUpdateURL, true);
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SearchService]);
+
+#include ../../../modules/debug.js
diff --git a/components/search/src/nsSearchSuggestions.js b/components/search/src/nsSearchSuggestions.js
new file mode 100644
index 000000000..a05d8b4b4
--- /dev/null
+++ b/components/search/src/nsSearchSuggestions.js
@@ -0,0 +1,197 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/nsFormAutoCompleteResult.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SearchSuggestionController",
+ "resource://gre/modules/SearchSuggestionController.jsm");
+
+/**
+ * SuggestAutoComplete is a base class that implements nsIAutoCompleteSearch
+ * and can collect results for a given search by using this._suggestionController.
+ * We do it this way since the AutoCompleteController in Mozilla requires a
+ * unique XPCOM Service for every search provider, even if the logic for two
+ * providers is identical.
+ * @constructor
+ */
+function SuggestAutoComplete() {
+ this._init();
+}
+SuggestAutoComplete.prototype = {
+
+ _init: function() {
+ this._suggestionController = new SearchSuggestionController(obj => this.onResultsReturned(obj));
+ this._suggestionController.maxLocalResults = this._historyLimit;
+ },
+
+ get _suggestionLabel() {
+ let bundle = Services.strings.createBundle("chrome://global/locale/search/search.properties");
+ let label = bundle.GetStringFromName("suggestion_label");
+ Object.defineProperty(SuggestAutoComplete.prototype, "_suggestionLabel", {value: label});
+ return label;
+ },
+
+ /**
+ * The object implementing nsIAutoCompleteObserver that we notify when
+ * we have found results
+ * @private
+ */
+ _listener: null,
+
+ /**
+ * Maximum number of history items displayed. This is capped at 7
+ * because the primary consumer (Firefox search bar) displays 10 rows
+ * by default, and so we want to leave some space for suggestions
+ * to be visible.
+ */
+ _historyLimit: 7,
+
+ /**
+ * Callback for handling results from SearchSuggestionController.jsm
+ * @private
+ */
+ onResultsReturned: function(results) {
+ let finalResults = [];
+ let finalComments = [];
+
+ // If form history has results, add them to the list.
+ for (let i = 0; i < results.local.length; ++i) {
+ finalResults.push(results.local[i]);
+ finalComments.push("");
+ }
+
+ // If there are remote matches, add them.
+ if (results.remote.length) {
+ // "comments" column values for suggestions starts as empty strings
+ let comments = new Array(results.remote.length).fill("", 1);
+ comments[0] = this._suggestionLabel;
+ // now put the history results above the suggestions
+ finalResults = finalResults.concat(results.remote);
+ finalComments = finalComments.concat(comments);
+ }
+
+ // Notify the FE of our new results
+ this.onResultsReady(results.term, finalResults, finalComments, results.formHistoryResult);
+ },
+
+ /**
+ * Notifies the front end of new results.
+ * @param searchString the user's query string
+ * @param results an array of results to the search
+ * @param comments an array of metadata corresponding to the results
+ * @private
+ */
+ onResultsReady: function(searchString, results, comments, formHistoryResult) {
+ if (this._listener) {
+ // Create a copy of the results array to use as labels, since
+ // FormAutoCompleteResult doesn't like being passed the same array
+ // for both.
+ let labels = results.slice();
+ let result = new FormAutoCompleteResult(
+ searchString,
+ Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
+ 0,
+ "",
+ results,
+ labels,
+ comments,
+ formHistoryResult);
+
+ this._listener.onSearchResult(this, result);
+
+ // Null out listener to make sure we don't notify it twice
+ this._listener = null;
+ }
+ },
+
+ /**
+ * Initiates the search result gathering process. Part of
+ * nsIAutoCompleteSearch implementation.
+ *
+ * @param searchString the user's query string
+ * @param searchParam unused, "an extra parameter"; even though
+ * this parameter and the next are unused, pass
+ * them through in case the form history
+ * service wants them
+ * @param previousResult unused, a client-cached store of the previous
+ * generated resultset for faster searching.
+ * @param listener object implementing nsIAutoCompleteObserver which
+ * we notify when results are ready.
+ */
+ startSearch: function(searchString, searchParam, previousResult, listener) {
+ // Don't reuse a previous form history result when it no longer applies.
+ if (!previousResult)
+ this._formHistoryResult = null;
+
+ var formHistorySearchParam = searchParam.split("|")[0];
+
+ // Receive the information about the privacy mode of the window to which
+ // this search box belongs. The front-end's search.xml bindings passes this
+ // information in the searchParam parameter. The alternative would have
+ // been to modify nsIAutoCompleteSearch to add an argument to startSearch
+ // and patch all of autocomplete to be aware of this, but the searchParam
+ // argument is already an opaque argument, so this solution is hopefully
+ // less hackish (although still gross.)
+ var privacyMode = (searchParam.split("|")[1] == "private");
+
+ // Start search immediately if possible, otherwise once the search
+ // service is initialized
+ if (Services.search.isInitialized) {
+ this._triggerSearch(searchString, formHistorySearchParam, listener, privacyMode);
+ return;
+ }
+
+ Services.search.init((function startSearch_cb(aResult) {
+ if (!Components.isSuccessCode(aResult)) {
+ Cu.reportError("Could not initialize search service, bailing out: " + aResult);
+ return;
+ }
+ this._triggerSearch(searchString, formHistorySearchParam, listener, privacyMode);
+ }).bind(this));
+ },
+
+ /**
+ * Actual implementation of search.
+ */
+ _triggerSearch: function(searchString, searchParam, listener, privacyMode) {
+ this._listener = listener;
+ this._suggestionController.fetch(searchString,
+ privacyMode,
+ Services.search.currentEngine);
+ },
+
+ /**
+ * Ends the search result gathering process. Part of nsIAutoCompleteSearch
+ * implementation.
+ */
+ stopSearch: function() {
+ this._suggestionController.stop();
+ },
+
+ // nsISupports
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch,
+ Ci.nsIAutoCompleteObserver])
+};
+
+/**
+ * SearchSuggestAutoComplete is a service implementation that handles suggest
+ * results specific to web searches.
+ * @constructor
+ */
+function SearchSuggestAutoComplete() {
+ // This calls _init() in the parent class (SuggestAutoComplete) via the
+ // prototype, below.
+ this._init();
+}
+SearchSuggestAutoComplete.prototype = {
+ classID: Components.ID("{aa892eb4-ffbf-477d-9f9a-06c995ae9f27}"),
+ __proto__: SuggestAutoComplete.prototype,
+ serviceURL: ""
+};
+
+var component = [SearchSuggestAutoComplete];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);
diff --git a/components/search/src/nsSidebar.js b/components/search/src/nsSidebar.js
new file mode 100644
index 000000000..deb455734
--- /dev/null
+++ b/components/search/src/nsSidebar.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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 { interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// File extension for Sherlock search plugin description files
+const SHERLOCK_FILE_EXT_REGEXP = /\.src$/i;
+
+function nsSidebar() {
+}
+
+nsSidebar.prototype = {
+ init: function(window) {
+ this.window = window;
+ this.mm = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+ },
+
+ // Deprecated, only left here to avoid breaking old browser-detection scripts.
+ addSearchEngine: function(engineURL, iconURL, suggestedTitle, suggestedCategory) {
+ if (SHERLOCK_FILE_EXT_REGEXP.test(engineURL)) {
+ Cu.reportError("Installing Sherlock search plugins is no longer supported.");
+ return;
+ }
+
+ this.AddSearchProvider(engineURL);
+ },
+
+ // This function implements window.external.AddSearchProvider().
+ // The capitalization, although nonstandard here, is to match other browsers'
+ // APIs and is therefore important.
+ AddSearchProvider: function(engineURL) {
+ this.mm.sendAsyncMessage("Search:AddEngine", {
+ pageURL: this.window.document.documentURIObject.spec,
+ engineURL
+ });
+ },
+
+ // This function exists to implement window.external.IsSearchProviderInstalled(),
+ // for compatibility with other browsers. The function has been deprecated
+ // and so will not be implemented.
+ IsSearchProviderInstalled: function(engineURL) {
+ return 0;
+ },
+
+ classID: Components.ID("{22117140-9c6e-11d3-aaf1-00805f8a4905}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+ Ci.nsIDOMGlobalPropertyInitializer])
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSidebar]);
diff --git a/components/search/toolkitsearch.manifest b/components/search/toolkitsearch.manifest
new file mode 100644
index 000000000..b7c55da0e
--- /dev/null
+++ b/components/search/toolkitsearch.manifest
@@ -0,0 +1,10 @@
+component {7319788a-fe93-4db3-9f39-818cf08f4256} nsSearchService.js process=main
+contract @mozilla.org/browser/search-service;1 {7319788a-fe93-4db3-9f39-818cf08f4256} process=main
+# 21600 == 6 hours
+category update-timer nsSearchService @mozilla.org/browser/search-service;1,getService,search-engine-update-timer,browser.search.update.interval,21600
+component {aa892eb4-ffbf-477d-9f9a-06c995ae9f27} nsSearchSuggestions.js
+contract @mozilla.org/autocomplete/search;1?name=search-autocomplete {aa892eb4-ffbf-477d-9f9a-06c995ae9f27}
+#ifdef HAVE_SIDEBAR
+component {22117140-9c6e-11d3-aaf1-00805f8a4905} nsSidebar.js
+contract @mozilla.org/sidebar;1 {22117140-9c6e-11d3-aaf1-00805f8a4905}
+#endif
diff --git a/components/startup/moz.build b/components/startup/moz.build
new file mode 100644
index 000000000..0d6e502be
--- /dev/null
+++ b/components/startup/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['public/nsIAppStartup.idl']
+
+if CONFIG['MOZ_USERINFO']:
+ XPIDL_SOURCES += ['public/nsIUserInfo.idl']
+
+EXPORTS += ['src/nsIAppStartupNotifier.h']
+EXPORTS.mozilla += ['src/StartupTimeline.h']
+
+SOURCES += [
+ 'src/nsAppStartup.cpp',
+ 'src/nsAppStartupNotifier.cpp',
+ 'src/StartupTimeline.cpp',
+]
+
+if CONFIG['MOZ_USERINFO']:
+ if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += ['src/nsUserInfoWin.cpp']
+ else:
+ SOURCES += ['src/nsUserInfoUnix.cpp']
+
+XPIDL_MODULE = 'appstartup'
+FINAL_LIBRARY = 'xul'
diff --git a/components/startup/mozprofilerprobe.mof b/components/startup/mozprofilerprobe.mof
new file mode 100644
index 000000000..03379ba1d
--- /dev/null
+++ b/components/startup/mozprofilerprobe.mof
@@ -0,0 +1,29 @@
+#pragma namespace("\\\\.\\root\\wmi")
+#pragma autorecover
+
+[dynamic: ToInstance, Description("Mozilla Generic Provider"),
+ Guid("{509962E0-406B-46F4-99BA-5A009F8D2225}")]
+class MozillaProvider : EventTrace
+{
+};
+
+[dynamic: ToInstance, Description("Mozilla Event: Places Init is complete."): Amended,
+ Guid("{A3DA04E0-57D7-482A-A1C1-61DA5F95BACB}"),
+ EventType(1)]
+class MozillaEventPlacesInit : MozillaProvider
+{
+};
+
+[dynamic: ToInstance, Description("Mozilla Event: Session Store Window Restored."): Amended,
+ Guid("{917B96B1-ECAD-4DAB-A760-8D49027748AE}"),
+ EventType(1)]
+class MozillaEventSessionStoreWindowRestored : MozillaProvider
+{
+};
+
+[dynamic: ToInstance, Description("Mozilla Event: XPCOM Shutdown."): Amended,
+ Guid("{26D1E091-0AE7-4F49-A554-4214445C505C}"),
+ EventType(1)]
+class MozillaEventXPCOMShutdown : MozillaProvider
+{
+};
diff --git a/components/startup/public/nsIAppStartup.idl b/components/startup/public/nsIAppStartup.idl
new file mode 100644
index 000000000..34705d39f
--- /dev/null
+++ b/components/startup/public/nsIAppStartup.idl
@@ -0,0 +1,195 @@
+/* -*- Mode: IDL; tab-width: 8; 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 "nsISupports.idl"
+
+interface nsICmdLineService;
+interface nsIToolkitProfile;
+
+[scriptable, uuid(6621f6d5-6c04-4a0e-9e74-447db221484e)]
+
+interface nsIAppStartup : nsISupports
+{
+ /**
+ * Create the hidden window.
+ */
+ void createHiddenWindow();
+
+ /**
+ * Destroys the hidden window. This will have no effect if the hidden window
+ * has not yet been created.
+ */
+ void destroyHiddenWindow();
+
+ /**
+ * Runs an application event loop: normally the main event pump which
+ * defines the lifetime of the application. If there are no windows open
+ * and no outstanding calls to enterLastWindowClosingSurvivalArea this
+ * method will exit immediately.
+ *
+ * @returnCode NS_SUCCESS_RESTART_APP
+ * This return code indicates that the application should be
+ * restarted because quit was called with the eRestart flag.
+
+ * @returnCode NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE
+ * This return code indicates that the application should be
+ * restarted without necessarily using the same profile because
+ * quit was called with the eRestartNotSameProfile flag.
+ */
+ void run();
+
+ /**
+ * There are situations where all application windows will be
+ * closed but we don't want to take this as a signal to quit the
+ * app. Bracket the code where the last window could close with
+ * these.
+ */
+ void enterLastWindowClosingSurvivalArea();
+ void exitLastWindowClosingSurvivalArea();
+
+ /**
+ * Startup Crash Detection
+ *
+ * Keeps track of application startup begining and success using flags to
+ * determine whether the application is crashing on startup.
+ * When the number of crashes crosses the acceptable threshold, safe mode
+ * or other repair procedures are performed.
+ */
+
+ /**
+ * Whether automatic safe mode is necessary at this time. This gets set
+ * in trackStartupCrashBegin.
+ *
+ * @see trackStartupCrashBegin
+ */
+ readonly attribute boolean automaticSafeModeNecessary;
+
+ /**
+ * Restart the application in safe mode
+ * @param aQuitMode
+ * This parameter modifies how the app is shutdown.
+ * @see nsIAppStartup::quit
+ */
+ void restartInSafeMode(in uint32_t aQuitMode);
+
+ /**
+ * Run a new instance of this app with a specified profile
+ * @param aProfile
+ * The profile we want to use.
+ * @see nsIAppStartup::quit
+ */
+ void createInstanceWithProfile(in nsIToolkitProfile aProfile);
+
+ /**
+ * If the last startup crashed then increment a counter.
+ * Set a flag so on next startup we can detect whether TrackStartupCrashEnd
+ * was called (and therefore the application crashed).
+ * @return whether safe mode is necessary
+ */
+ bool trackStartupCrashBegin();
+
+ /**
+ * We have succesfully started without crashing. Clear flags that were
+ * tracking past crashes.
+ */
+ void trackStartupCrashEnd();
+
+ /**
+ * The following flags may be passed as the aMode parameter to the quit
+ * method. One and only one of the "Quit" flags must be specified. The
+ * eRestart flag may be bit-wise combined with one of the "Quit" flags to
+ * cause the application to restart after it quits.
+ */
+
+ /**
+ * Attempt to quit if all windows are closed.
+ */
+ const uint32_t eConsiderQuit = 0x01;
+
+ /**
+ * Try to close all windows, then quit if successful.
+ */
+ const uint32_t eAttemptQuit = 0x02;
+
+ /**
+ * Quit, damnit!
+ */
+ const uint32_t eForceQuit = 0x03;
+
+ /**
+ * Restart the application after quitting. The application will be
+ * restarted with the same profile and an empty command line.
+ */
+ const uint32_t eRestart = 0x10;
+
+ /**
+ * When restarting attempt to start in the i386 architecture. Only supported
+ * on OSX.
+ */
+ const uint32_t eRestarti386 = 0x20;
+
+ /**
+ * When restarting attempt to start in the x86_64 architecture. Only
+ * supported on OSX.
+ */
+ const uint32_t eRestartx86_64 = 0x40;
+
+ /**
+ * Restart the application after quitting. The application will be
+ * restarted with an empty command line and the normal profile selection
+ * process will take place on startup.
+ */
+ const uint32_t eRestartNotSameProfile = 0x100;
+
+ /**
+ * Exit the event loop, and shut down the app.
+ *
+ * @param aMode
+ * This parameter modifies how the app is shutdown, and it is
+ * constructed from the constants defined above.
+ */
+ void quit(in uint32_t aMode);
+
+ /**
+ * True if the application is in the process of shutting down.
+ */
+ readonly attribute boolean shuttingDown;
+
+ /**
+ * True if the application is in the process of starting up.
+ *
+ * Startup is complete once all observers of final-ui-startup have returned.
+ */
+ readonly attribute boolean startingUp;
+
+ /**
+ * Mark the startup as completed.
+ *
+ * Called at the end of startup by nsAppRunner.
+ */
+ [noscript] void doneStartingUp();
+
+ /**
+ * True if the application is being restarted
+ */
+ readonly attribute boolean restarting;
+
+ /**
+ * True if this is the startup following restart, i.e. if the application
+ * was restarted using quit(eRestart*).
+ */
+ readonly attribute boolean wasRestarted;
+
+ /**
+ * Returns an object with main, process, firstPaint, sessionRestored properties.
+ * Properties may not be available depending on platform or application
+ */
+ [implicit_jscontext] jsval getStartupInfo();
+
+ /**
+ * True if startup was interrupted by an interactive prompt.
+ */
+ attribute boolean interrupted;
+};
diff --git a/components/startup/public/nsIUserInfo.idl b/components/startup/public/nsIUserInfo.idl
new file mode 100644
index 000000000..1838cc69c
--- /dev/null
+++ b/components/startup/public/nsIUserInfo.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(6c1034f0-1dd2-11b2-aa14-e6657ed7bb0b)]
+interface nsIUserInfo : nsISupports
+{
+ /* these are things the system may know about the current user */
+
+ readonly attribute wstring fullname;
+
+ readonly attribute string emailAddress;
+
+ /* should this be a wstring? */
+ readonly attribute string username;
+
+ readonly attribute string domain;
+};
+
+%{C++
+
+// 14c13684-1dd2-11b2-9463-bb10ba742554
+#define NS_USERINFO_CID \
+{ 0x14c13684, 0x1dd2, 0x11b2, \
+ {0x94, 0x63, 0xbb, 0x10, 0xba, 0x74, 0x25, 0x54}}
+
+#define NS_USERINFO_CONTRACTID "@mozilla.org/userinfo;1"
+
+%}
diff --git a/components/startup/src/StartupTimeline.cpp b/components/startup/src/StartupTimeline.cpp
new file mode 100644
index 000000000..3b6785780
--- /dev/null
+++ b/components/startup/src/StartupTimeline.cpp
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StartupTimeline.h"
+#include "mozilla/TimeStamp.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+
+TimeStamp StartupTimeline::sStartupTimeline[StartupTimeline::MAX_EVENT_ID];
+const char *StartupTimeline::sStartupTimelineDesc[StartupTimeline::MAX_EVENT_ID] = {
+#define mozilla_StartupTimeline_Event(ev, desc) desc,
+#include "StartupTimeline.h"
+#undef mozilla_StartupTimeline_Event
+};
+
+} /* namespace mozilla */
+
+using mozilla::StartupTimeline;
+using mozilla::TimeStamp;
+
+/**
+ * The XRE_StartupTimeline_Record function is to be used by embedding
+ * applications that can't use mozilla::StartupTimeline::Record() directly.
+ *
+ * @param aEvent The event to be recorded, must correspond to an element of the
+ * mozilla::StartupTimeline::Event enumartion
+ * @param aWhen The time at which the event happened
+ */
+void
+XRE_StartupTimelineRecord(int aEvent, TimeStamp aWhen)
+{
+ StartupTimeline::Record((StartupTimeline::Event)aEvent, aWhen);
+}
diff --git a/components/startup/src/StartupTimeline.h b/components/startup/src/StartupTimeline.h
new file mode 100644
index 000000000..0cdc5fce6
--- /dev/null
+++ b/components/startup/src/StartupTimeline.h
@@ -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/. */
+
+#ifdef mozilla_StartupTimeline_Event
+mozilla_StartupTimeline_Event(PROCESS_CREATION, "process")
+mozilla_StartupTimeline_Event(START, "start")
+mozilla_StartupTimeline_Event(MAIN, "main")
+mozilla_StartupTimeline_Event(SELECT_PROFILE, "selectProfile")
+mozilla_StartupTimeline_Event(AFTER_PROFILE_LOCKED, "afterProfileLocked")
+// Record the beginning and end of startup crash detection to compare with crash stats to know whether
+// detection should be improved to start or end sooner.
+mozilla_StartupTimeline_Event(STARTUP_CRASH_DETECTION_BEGIN, "startupCrashDetectionBegin")
+mozilla_StartupTimeline_Event(STARTUP_CRASH_DETECTION_END, "startupCrashDetectionEnd")
+mozilla_StartupTimeline_Event(FIRST_PAINT, "firstPaint")
+mozilla_StartupTimeline_Event(SESSION_RESTORE_INIT, "sessionRestoreInit")
+mozilla_StartupTimeline_Event(SESSION_RESTORED, "sessionRestored")
+mozilla_StartupTimeline_Event(CREATE_TOP_LEVEL_WINDOW, "createTopLevelWindow")
+mozilla_StartupTimeline_Event(LINKER_INITIALIZED, "linkerInitialized")
+mozilla_StartupTimeline_Event(LIBRARIES_LOADED, "librariesLoaded")
+mozilla_StartupTimeline_Event(FIRST_LOAD_URI, "firstLoadURI")
+
+// The following are actually shutdown events, used to monitor the duration of shutdown
+mozilla_StartupTimeline_Event(QUIT_APPLICATION, "quitApplication")
+mozilla_StartupTimeline_Event(PROFILE_BEFORE_CHANGE, "profileBeforeChange")
+#else
+
+#ifndef mozilla_StartupTimeline
+#define mozilla_StartupTimeline
+
+#include "mozilla/TimeStamp.h"
+#include "nscore.h"
+#include "GeckoProfiler.h"
+
+namespace mozilla {
+
+class StartupTimeline {
+public:
+ enum Event {
+ #define mozilla_StartupTimeline_Event(ev, z) ev,
+ #include "StartupTimeline.h"
+ #undef mozilla_StartupTimeline_Event
+ MAX_EVENT_ID
+ };
+
+ static TimeStamp Get(Event ev) {
+ return sStartupTimeline[ev];
+ }
+
+ static const char *Describe(Event ev) {
+ return sStartupTimelineDesc[ev];
+ }
+
+ static void Record(Event ev) {
+ PROFILER_MARKER(Describe(ev));
+ Record(ev, TimeStamp::Now());
+ }
+
+ static void Record(Event ev, TimeStamp when) {
+ sStartupTimeline[ev] = when;
+ }
+
+ static void RecordOnce(Event ev) {
+ if (!HasRecord(ev))
+ Record(ev);
+ }
+
+ static bool HasRecord(Event ev) {
+ return !sStartupTimeline[ev].IsNull();
+ }
+
+private:
+ static NS_EXTERNAL_VIS_(TimeStamp) sStartupTimeline[MAX_EVENT_ID];
+ static NS_EXTERNAL_VIS_(const char *) sStartupTimelineDesc[MAX_EVENT_ID];
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_StartupTimeline */
+
+#endif /* mozilla_StartupTimeline_Event */
diff --git a/components/startup/src/nsAppStartup.cpp b/components/startup/src/nsAppStartup.cpp
new file mode 100644
index 000000000..83ada5209
--- /dev/null
+++ b/components/startup/src/nsAppStartup.cpp
@@ -0,0 +1,970 @@
+/* -*- 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 "nsAppStartup.h"
+
+#include "nsIAppShellService.h"
+#include "nsPIDOMWindow.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIProcess.h"
+#include "nsIPromptService.h"
+#include "nsIStringBundle.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIToolkitProfile.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIWindowMediator.h"
+#include "nsIWindowWatcher.h"
+#include "nsIXULRuntime.h"
+#include "nsIXULWindow.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsThreadUtils.h"
+#include "nsAutoPtr.h"
+#include "nsString.h"
+#include "mozilla/Preferences.h"
+#include "GeckoProfiler.h"
+
+#include "prprf.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsWidgetsCID.h"
+#include "nsAppRunner.h"
+#include "nsAppShellCID.h"
+#include "nsXPCOMCIDInternal.h"
+#include "mozilla/Services.h"
+#include "nsIXPConnect.h"
+#include "jsapi.h"
+#include "js/Date.h"
+#include "prenv.h"
+#include "nsAppDirectoryServiceDefs.h"
+
+#if defined(XP_WIN)
+// Prevent collisions with nsAppStartup::GetStartupInfo()
+#undef GetStartupInfo
+#endif
+
+#include "mozilla/IOInterposer.h"
+#include "mozilla/StartupTimeline.h"
+
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+
+#define kPrefLastSuccess "toolkit.startup.last_success"
+#define kPrefMaxResumedCrashes "toolkit.startup.max_resumed_crashes"
+#define kPrefRecentCrashes "toolkit.startup.recent_crashes"
+#define kPrefAlwaysUseSafeMode "toolkit.startup.always_use_safe_mode"
+
+#if defined(XP_WIN)
+#include "mozilla/perfprobe.h"
+/**
+ * Events sent to the system for profiling purposes
+ */
+//Keep them syncronized with the .mof file
+
+//Process-wide GUID, used by the OS to differentiate sources
+// {509962E0-406B-46F4-99BA-5A009F8D2225}
+//Keep it synchronized with the .mof file
+#define NS_APPLICATION_TRACING_CID \
+ { 0x509962E0, 0x406B, 0x46F4, \
+ { 0x99, 0xBA, 0x5A, 0x00, 0x9F, 0x8D, 0x22, 0x25} }
+
+//Event-specific GUIDs, used by the OS to differentiate events
+// {A3DA04E0-57D7-482A-A1C1-61DA5F95BACB}
+#define NS_PLACES_INIT_COMPLETE_EVENT_CID \
+ { 0xA3DA04E0, 0x57D7, 0x482A, \
+ { 0xA1, 0xC1, 0x61, 0xDA, 0x5F, 0x95, 0xBA, 0xCB} }
+// {917B96B1-ECAD-4DAB-A760-8D49027748AE}
+#define NS_SESSION_STORE_WINDOW_RESTORED_EVENT_CID \
+ { 0x917B96B1, 0xECAD, 0x4DAB, \
+ { 0xA7, 0x60, 0x8D, 0x49, 0x02, 0x77, 0x48, 0xAE} }
+// {26D1E091-0AE7-4F49-A554-4214445C505C}
+#define NS_XPCOM_SHUTDOWN_EVENT_CID \
+ { 0x26D1E091, 0x0AE7, 0x4F49, \
+ { 0xA5, 0x54, 0x42, 0x14, 0x44, 0x5C, 0x50, 0x5C} }
+
+static NS_DEFINE_CID(kApplicationTracingCID,
+ NS_APPLICATION_TRACING_CID);
+static NS_DEFINE_CID(kPlacesInitCompleteCID,
+ NS_PLACES_INIT_COMPLETE_EVENT_CID);
+static NS_DEFINE_CID(kSessionStoreWindowRestoredCID,
+ NS_SESSION_STORE_WINDOW_RESTORED_EVENT_CID);
+static NS_DEFINE_CID(kXPCOMShutdownCID,
+ NS_XPCOM_SHUTDOWN_EVENT_CID);
+#endif //defined(XP_WIN)
+
+using namespace mozilla;
+
+class nsAppExitEvent : public mozilla::Runnable {
+private:
+ RefPtr<nsAppStartup> mService;
+
+public:
+ explicit nsAppExitEvent(nsAppStartup *service) : mService(service) {}
+
+ NS_IMETHOD Run() override {
+ // Tell the appshell to exit
+ mService->mAppShell->Exit();
+
+ mService->mRunning = false;
+ return NS_OK;
+ }
+};
+
+/**
+ * Computes an approximation of the absolute time represented by @a stamp
+ * which is comparable to those obtained via PR_Now(). If the current absolute
+ * time varies a lot (e.g. DST adjustments) since the first call then the
+ * resulting times may be inconsistent.
+ *
+ * @param stamp The timestamp to be converted
+ * @returns The converted timestamp
+ */
+uint64_t ComputeAbsoluteTimestamp(PRTime prnow, TimeStamp now, TimeStamp stamp)
+{
+ static PRTime sAbsoluteNow = PR_Now();
+ static TimeStamp sMonotonicNow = TimeStamp::Now();
+
+ return sAbsoluteNow - (sMonotonicNow - stamp).ToMicroseconds();
+}
+
+//
+// nsAppStartup
+//
+
+nsAppStartup::nsAppStartup() :
+ mConsiderQuitStopper(0),
+ mRunning(false),
+ mShuttingDown(false),
+ mStartingUp(true),
+ mAttemptingQuit(false),
+ mRestart(false),
+ mInterrupted(false),
+ mIsSafeModeNecessary(false),
+ mStartupCrashTrackingEnded(false),
+ mRestartNotSameProfile(false)
+{ }
+
+
+nsresult
+nsAppStartup::Init()
+{
+ nsresult rv;
+
+ // Create widget application shell
+ mAppShell = do_GetService(kAppShellCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserverService> os =
+ mozilla::services::GetObserverService();
+ if (!os)
+ return NS_ERROR_FAILURE;
+
+ os->AddObserver(this, "quit-application", true);
+ os->AddObserver(this, "quit-application-forced", true);
+ os->AddObserver(this, "sessionstore-init-started", true);
+ os->AddObserver(this, "sessionstore-windows-restored", true);
+ os->AddObserver(this, "profile-change-teardown", true);
+ os->AddObserver(this, "xul-window-registered", true);
+ os->AddObserver(this, "xul-window-destroyed", true);
+ os->AddObserver(this, "profile-before-change", true);
+ os->AddObserver(this, "xpcom-shutdown", true);
+
+#if defined(XP_WIN)
+ os->AddObserver(this, "places-init-complete", true);
+ // This last event is only interesting to us for xperf-based measures
+
+ // Initialize interaction with profiler
+ mProbesManager =
+ new ProbeManager(
+ kApplicationTracingCID,
+ NS_LITERAL_CSTRING("Application startup probe"));
+ // Note: The operation is meant mostly for in-house profiling.
+ // Therefore, we do not warn if probes manager cannot be initialized
+
+ if (mProbesManager) {
+ mPlacesInitCompleteProbe =
+ mProbesManager->
+ GetProbe(kPlacesInitCompleteCID,
+ NS_LITERAL_CSTRING("places-init-complete"));
+ NS_WARNING_ASSERTION(mPlacesInitCompleteProbe,
+ "Cannot initialize probe 'places-init-complete'");
+
+ mSessionWindowRestoredProbe =
+ mProbesManager->
+ GetProbe(kSessionStoreWindowRestoredCID,
+ NS_LITERAL_CSTRING("sessionstore-windows-restored"));
+ NS_WARNING_ASSERTION(
+ mSessionWindowRestoredProbe,
+ "Cannot initialize probe 'sessionstore-windows-restored'");
+
+ mXPCOMShutdownProbe =
+ mProbesManager->
+ GetProbe(kXPCOMShutdownCID,
+ NS_LITERAL_CSTRING("xpcom-shutdown"));
+ NS_WARNING_ASSERTION(mXPCOMShutdownProbe,
+ "Cannot initialize probe 'xpcom-shutdown'");
+
+ rv = mProbesManager->StartSession();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Cannot initialize system probe manager");
+ }
+#endif //defined(XP_WIN)
+
+ return NS_OK;
+}
+
+
+//
+// nsAppStartup->nsISupports
+//
+
+NS_IMPL_ISUPPORTS(nsAppStartup,
+ nsIAppStartup,
+ nsIWindowCreator,
+ nsIWindowCreator2,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+
+//
+// nsAppStartup->nsIAppStartup
+//
+
+NS_IMETHODIMP
+nsAppStartup::CreateHiddenWindow()
+{
+ nsCOMPtr<nsIAppShellService> appShellService
+ (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE);
+
+ return appShellService->CreateHiddenWindow();
+}
+
+
+NS_IMETHODIMP
+nsAppStartup::DestroyHiddenWindow()
+{
+ nsCOMPtr<nsIAppShellService> appShellService
+ (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(appShellService, NS_ERROR_FAILURE);
+
+ return appShellService->DestroyHiddenWindow();
+}
+
+NS_IMETHODIMP
+nsAppStartup::Run(void)
+{
+ NS_ASSERTION(!mRunning, "Reentrant appstartup->Run()");
+
+ // If we have no windows open and no explicit calls to
+ // enterLastWindowClosingSurvivalArea, or somebody has explicitly called
+ // quit, don't bother running the event loop which would probably leave us
+ // with a zombie process.
+
+ if (!mShuttingDown && mConsiderQuitStopper != 0) {
+ mRunning = true;
+
+ nsresult rv = mAppShell->Run();
+ if (NS_FAILED(rv))
+ return rv;
+ }
+
+ nsresult retval = NS_OK;
+ if (mRestart) {
+ retval = NS_SUCCESS_RESTART_APP;
+ } else if (mRestartNotSameProfile) {
+ retval = NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE;
+ }
+
+ return retval;
+}
+
+
+
+NS_IMETHODIMP
+nsAppStartup::Quit(uint32_t aMode)
+{
+ uint32_t ferocity = (aMode & 0xF);
+
+ // Quit the application. We will asynchronously call the appshell's
+ // Exit() method via nsAppExitEvent to allow one last pass
+ // through any events in the queue. This guarantees a tidy cleanup.
+ nsresult rv = NS_OK;
+ bool postedExitEvent = false;
+
+ if (mShuttingDown)
+ return NS_OK;
+
+ // If we're considering quitting, we will only do so if:
+ if (ferocity == eConsiderQuit) {
+ if (mConsiderQuitStopper == 0) {
+ // there are no windows...
+ ferocity = eAttemptQuit;
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> obsService;
+ if (ferocity == eAttemptQuit || ferocity == eForceQuit) {
+
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+ nsCOMPtr<nsIWindowMediator> mediator (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (mediator) {
+ mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
+ if (windowEnumerator) {
+ bool more;
+ windowEnumerator->HasMoreElements(&more);
+ // If we reported no windows, we definitely shouldn't be
+ // iterating any here.
+ MOZ_ASSERT_IF(!mConsiderQuitStopper, !more);
+
+ while (more) {
+ nsCOMPtr<nsISupports> window;
+ windowEnumerator->GetNext(getter_AddRefs(window));
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow(do_QueryInterface(window));
+ if (domWindow) {
+ MOZ_ASSERT(domWindow->IsOuterWindow());
+ if (!domWindow->CanClose())
+ return NS_OK;
+ }
+ windowEnumerator->HasMoreElements(&more);
+ }
+ }
+ }
+
+ PROFILER_MARKER("Shutdown start");
+ mShuttingDown = true;
+ if (!mRestart) {
+ mRestart = (aMode & eRestart) != 0;
+ }
+
+ if (!mRestartNotSameProfile) {
+ mRestartNotSameProfile = (aMode & eRestartNotSameProfile) != 0;
+ }
+
+ if (mRestart || mRestartNotSameProfile) {
+ // Mark the next startup as a restart.
+ PR_SetEnv("MOZ_APP_RESTART=1");
+
+ /* Firefox-restarts reuse the process so regular process start-time isn't
+ a useful indicator of startup time anymore. */
+ TimeStamp::RecordProcessRestart();
+ }
+
+ obsService = mozilla::services::GetObserverService();
+
+ if (!mAttemptingQuit) {
+ mAttemptingQuit = true;
+ if (obsService)
+ obsService->NotifyObservers(nullptr, "quit-application-granted", nullptr);
+ }
+
+ /* Enumerate through each open window and close it. It's important to do
+ this before we forcequit because this can control whether we really quit
+ at all. e.g. if one of these windows has an unload handler that
+ opens a new window. Ugh. I know. */
+ CloseAllWindows();
+
+ if (mediator) {
+ if (ferocity == eAttemptQuit) {
+ ferocity = eForceQuit; // assume success
+
+ /* Were we able to immediately close all windows? if not, eAttemptQuit
+ failed. This could happen for a variety of reasons; in fact it's
+ very likely. Perhaps we're being called from JS and the window->Close
+ method hasn't had a chance to wrap itself up yet. So give up.
+ We'll return (with eConsiderQuit) as the remaining windows are
+ closed. */
+ mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
+ if (windowEnumerator) {
+ bool more;
+ while (windowEnumerator->HasMoreElements(&more), more) {
+ /* we can't quit immediately. we'll try again as the last window
+ finally closes. */
+ ferocity = eAttemptQuit;
+ nsCOMPtr<nsISupports> window;
+ windowEnumerator->GetNext(getter_AddRefs(window));
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = do_QueryInterface(window);
+ if (domWindow) {
+ if (!domWindow->Closed()) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (ferocity == eForceQuit) {
+ // do it!
+
+ // No chance of the shutdown being cancelled from here on; tell people
+ // we're shutting down for sure while all services are still available.
+ if (obsService) {
+ NS_NAMED_LITERAL_STRING(shutdownStr, "shutdown");
+ NS_NAMED_LITERAL_STRING(restartStr, "restart");
+ obsService->NotifyObservers(nullptr, "quit-application",
+ (mRestart || mRestartNotSameProfile) ?
+ restartStr.get() : shutdownStr.get());
+ }
+
+ if (!mRunning) {
+ postedExitEvent = true;
+ }
+ else {
+ // no matter what, make sure we send the exit event. If
+ // worst comes to worst, we'll do a leaky shutdown but we WILL
+ // shut down. Well, assuming that all *this* stuff works ;-).
+ nsCOMPtr<nsIRunnable> event = new nsAppExitEvent(this);
+ rv = NS_DispatchToCurrentThread(event);
+ if (NS_SUCCEEDED(rv)) {
+ postedExitEvent = true;
+ }
+ else {
+ NS_WARNING("failed to dispatch nsAppExitEvent");
+ }
+ }
+ }
+
+ // turn off the reentrancy check flag, but not if we have
+ // more asynchronous work to do still.
+ if (!postedExitEvent)
+ mShuttingDown = false;
+ return rv;
+}
+
+
+void
+nsAppStartup::CloseAllWindows()
+{
+ nsCOMPtr<nsIWindowMediator> mediator
+ (do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+
+ mediator->GetEnumerator(nullptr, getter_AddRefs(windowEnumerator));
+
+ if (!windowEnumerator)
+ return;
+
+ bool more;
+ while (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> isupports;
+ if (NS_FAILED(windowEnumerator->GetNext(getter_AddRefs(isupports))))
+ break;
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(isupports);
+ NS_ASSERTION(window, "not an nsPIDOMWindow");
+ if (window) {
+ MOZ_ASSERT(window->IsOuterWindow());
+ window->ForceClose();
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsAppStartup::EnterLastWindowClosingSurvivalArea(void)
+{
+ ++mConsiderQuitStopper;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsAppStartup::ExitLastWindowClosingSurvivalArea(void)
+{
+ NS_ASSERTION(mConsiderQuitStopper > 0, "consider quit stopper out of bounds");
+ --mConsiderQuitStopper;
+
+ if (mRunning)
+ Quit(eConsiderQuit);
+
+ return NS_OK;
+}
+
+//
+// nsAppStartup->nsIAppStartup2
+//
+
+NS_IMETHODIMP
+nsAppStartup::GetShuttingDown(bool *aResult)
+{
+ *aResult = mShuttingDown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppStartup::GetStartingUp(bool *aResult)
+{
+ *aResult = mStartingUp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppStartup::DoneStartingUp()
+{
+ // This must be called once at most
+ MOZ_ASSERT(mStartingUp);
+
+ mStartingUp = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppStartup::GetRestarting(bool *aResult)
+{
+ *aResult = mRestart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppStartup::GetWasRestarted(bool *aResult)
+{
+ char *mozAppRestart = PR_GetEnv("MOZ_APP_RESTART");
+
+ /* When calling PR_SetEnv() with an empty value the existing variable may
+ * be unset or set to the empty string depending on the underlying platform
+ * thus we have to check if the variable is present and not empty. */
+ *aResult = mozAppRestart && (strcmp(mozAppRestart, "") != 0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppStartup::SetInterrupted(bool aInterrupted)
+{
+ mInterrupted = aInterrupted;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppStartup::GetInterrupted(bool *aInterrupted)
+{
+ *aInterrupted = mInterrupted;
+ return NS_OK;
+}
+
+//
+// nsAppStartup->nsIWindowCreator
+//
+
+NS_IMETHODIMP
+nsAppStartup::CreateChromeWindow(nsIWebBrowserChrome *aParent,
+ uint32_t aChromeFlags,
+ nsIWebBrowserChrome **_retval)
+{
+ bool cancel;
+ return CreateChromeWindow2(aParent, aChromeFlags, 0, nullptr, nullptr, &cancel, _retval);
+}
+
+
+//
+// nsAppStartup->nsIWindowCreator2
+//
+
+NS_IMETHODIMP
+nsAppStartup::SetScreenId(uint32_t aScreenId)
+{
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ if (!appShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return appShell->SetScreenId(aScreenId);
+}
+
+NS_IMETHODIMP
+nsAppStartup::CreateChromeWindow2(nsIWebBrowserChrome *aParent,
+ uint32_t aChromeFlags,
+ uint32_t aContextFlags,
+ nsITabParent *aOpeningTab,
+ mozIDOMWindowProxy* aOpener,
+ bool *aCancel,
+ nsIWebBrowserChrome **_retval)
+{
+ NS_ENSURE_ARG_POINTER(aCancel);
+ NS_ENSURE_ARG_POINTER(_retval);
+ *aCancel = false;
+ *_retval = 0;
+
+ // Non-modal windows cannot be opened if we are attempting to quit
+ if (mAttemptingQuit && (aChromeFlags & nsIWebBrowserChrome::CHROME_MODAL) == 0)
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+
+ nsCOMPtr<nsIXULWindow> newWindow;
+
+ if (aParent) {
+ nsCOMPtr<nsIXULWindow> xulParent(do_GetInterface(aParent));
+ NS_ASSERTION(xulParent, "window created using non-XUL parent. that's unexpected, but may work.");
+
+ if (xulParent)
+ xulParent->CreateNewWindow(aChromeFlags, aOpeningTab, aOpener, getter_AddRefs(newWindow));
+ // And if it fails, don't try again without a parent. It could fail
+ // intentionally (bug 115969).
+ } else { // try using basic methods:
+ /* You really shouldn't be making dependent windows without a parent.
+ But unparented modal (and therefore dependent) windows happen
+ in our codebase, so we allow it after some bellyaching: */
+ if (aChromeFlags & nsIWebBrowserChrome::CHROME_DEPENDENT)
+ NS_WARNING("dependent window created without a parent");
+
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ if (!appShell)
+ return NS_ERROR_FAILURE;
+
+ appShell->CreateTopLevelWindow(0, 0, aChromeFlags,
+ nsIAppShellService::SIZE_TO_CONTENT,
+ nsIAppShellService::SIZE_TO_CONTENT,
+ aOpeningTab, aOpener,
+ getter_AddRefs(newWindow));
+ }
+
+ // if anybody gave us anything to work with, use it
+ if (newWindow) {
+ newWindow->SetContextFlags(aContextFlags);
+ nsCOMPtr<nsIInterfaceRequestor> thing(do_QueryInterface(newWindow));
+ if (thing)
+ CallGetInterface(thing.get(), _retval);
+ }
+
+ return *_retval ? NS_OK : NS_ERROR_FAILURE;
+}
+
+
+//
+// nsAppStartup->nsIObserver
+//
+
+NS_IMETHODIMP
+nsAppStartup::Observe(nsISupports *aSubject,
+ const char *aTopic, const char16_t *aData)
+{
+ NS_ASSERTION(mAppShell, "appshell service notified before appshell built");
+ if (!strcmp(aTopic, "quit-application-forced")) {
+ mShuttingDown = true;
+ }
+ else if (!strcmp(aTopic, "profile-change-teardown")) {
+ if (!mShuttingDown) {
+ EnterLastWindowClosingSurvivalArea();
+ CloseAllWindows();
+ ExitLastWindowClosingSurvivalArea();
+ }
+ } else if (!strcmp(aTopic, "xul-window-registered")) {
+ EnterLastWindowClosingSurvivalArea();
+ } else if (!strcmp(aTopic, "xul-window-destroyed")) {
+ ExitLastWindowClosingSurvivalArea();
+ } else if (!strcmp(aTopic, "sessionstore-windows-restored")) {
+ StartupTimeline::Record(StartupTimeline::SESSION_RESTORED);
+ IOInterposer::EnteringNextStage();
+#if defined(XP_WIN)
+ if (mSessionWindowRestoredProbe) {
+ mSessionWindowRestoredProbe->Trigger();
+ }
+ } else if (!strcmp(aTopic, "places-init-complete")) {
+ if (mPlacesInitCompleteProbe) {
+ mPlacesInitCompleteProbe->Trigger();
+ }
+#endif //defined(XP_WIN)
+ } else if (!strcmp(aTopic, "sessionstore-init-started")) {
+ StartupTimeline::Record(StartupTimeline::SESSION_RESTORE_INIT);
+ } else if (!strcmp(aTopic, "xpcom-shutdown")) {
+ IOInterposer::EnteringNextStage();
+#if defined(XP_WIN)
+ if (mXPCOMShutdownProbe) {
+ mXPCOMShutdownProbe->Trigger();
+ }
+#endif // defined(XP_WIN)
+ } else if (!strcmp(aTopic, "quit-application")) {
+ StartupTimeline::Record(StartupTimeline::QUIT_APPLICATION);
+ } else if (!strcmp(aTopic, "profile-before-change")) {
+ StartupTimeline::Record(StartupTimeline::PROFILE_BEFORE_CHANGE);
+ } else {
+ NS_ERROR("Unexpected observer topic.");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppStartup::GetStartupInfo(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval)
+{
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+ aRetval.setObject(*obj);
+
+ TimeStamp procTime = StartupTimeline::Get(StartupTimeline::PROCESS_CREATION);
+ TimeStamp now = TimeStamp::Now();
+ PRTime absNow = PR_Now();
+
+ if (procTime.IsNull()) {
+ bool error = false;
+
+ procTime = TimeStamp::ProcessCreation(error);
+
+ StartupTimeline::Record(StartupTimeline::PROCESS_CREATION, procTime);
+ }
+
+ for (int i = StartupTimeline::PROCESS_CREATION;
+ i < StartupTimeline::MAX_EVENT_ID;
+ ++i)
+ {
+ StartupTimeline::Event ev = static_cast<StartupTimeline::Event>(i);
+ TimeStamp stamp = StartupTimeline::Get(ev);
+
+ if (stamp.IsNull() && (ev == StartupTimeline::MAIN)) {
+ // Always define main to aid with bug 689256.
+ stamp = procTime;
+ MOZ_ASSERT(!stamp.IsNull());
+ }
+
+ if (!stamp.IsNull()) {
+ if (stamp >= procTime) {
+ PRTime prStamp = ComputeAbsoluteTimestamp(absNow, now, stamp)
+ / PR_USEC_PER_MSEC;
+ JS::Rooted<JSObject*> date(aCx, JS::NewDateObject(aCx, JS::TimeClip(prStamp)));
+ JS_DefineProperty(aCx, obj, StartupTimeline::Describe(ev), date, JSPROP_ENUMERATE);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppStartup::GetAutomaticSafeModeNecessary(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ bool alwaysSafe = false;
+ Preferences::GetBool(kPrefAlwaysUseSafeMode, &alwaysSafe);
+
+ if (!alwaysSafe) {
+#if DEBUG
+ mIsSafeModeNecessary = false;
+#else
+ mIsSafeModeNecessary &= !PR_GetEnv("MOZ_DISABLE_AUTO_SAFE_MODE");
+#endif
+ }
+
+ *_retval = mIsSafeModeNecessary;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppStartup::TrackStartupCrashBegin(bool *aIsSafeModeNecessary)
+{
+ const int32_t MAX_TIME_SINCE_STARTUP = 6 * 60 * 60 * 1000;
+ const int32_t MAX_STARTUP_BUFFER = 10;
+ nsresult rv;
+
+ mStartupCrashTrackingEnded = false;
+
+ StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_BEGIN);
+
+ bool hasLastSuccess = Preferences::HasUserValue(kPrefLastSuccess);
+ if (!hasLastSuccess) {
+ // Clear so we don't get stuck with SafeModeNecessary returning true if we
+ // have had too many recent crashes and the last success pref is missing.
+ Preferences::ClearUser(kPrefRecentCrashes);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ bool inSafeMode = false;
+ nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(xr, NS_ERROR_FAILURE);
+
+ xr->GetInSafeMode(&inSafeMode);
+
+ PRTime replacedLockTime;
+ rv = xr->GetReplacedLockTime(&replacedLockTime);
+
+ if (NS_FAILED(rv) || !replacedLockTime) {
+ if (!inSafeMode)
+ Preferences::ClearUser(kPrefRecentCrashes);
+ GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
+ return NS_OK;
+ }
+
+ // check whether safe mode is necessary
+ int32_t maxResumedCrashes = -1;
+ rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ int32_t recentCrashes = 0;
+ Preferences::GetInt(kPrefRecentCrashes, &recentCrashes);
+ mIsSafeModeNecessary = (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1);
+
+ // Bug 731613 - Don't check if the last startup was a crash if XRE_PROFILE_PATH is set. After
+ // profile manager, the profile lock's mod. time has been changed so can't be used on this startup.
+ // After a restart, it's safe to assume the last startup was successful.
+ char *xreProfilePath = PR_GetEnv("XRE_PROFILE_PATH");
+ if (xreProfilePath) {
+ GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // time of last successful startup
+ int32_t lastSuccessfulStartup;
+ rv = Preferences::GetInt(kPrefLastSuccess, &lastSuccessfulStartup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t lockSeconds = (int32_t)(replacedLockTime / PR_MSEC_PER_SEC);
+
+ // started close enough to good startup so call it good
+ if (lockSeconds <= lastSuccessfulStartup + MAX_STARTUP_BUFFER
+ && lockSeconds >= lastSuccessfulStartup - MAX_STARTUP_BUFFER) {
+ GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
+ return NS_OK;
+ }
+
+ // sanity check that the pref set at last success is not greater than the current time
+ if (PR_Now() / PR_USEC_PER_SEC <= lastSuccessfulStartup)
+ return NS_ERROR_FAILURE;
+
+ if (inSafeMode) {
+ GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
+ return NS_OK;
+ }
+
+ PRTime now = (PR_Now() / PR_USEC_PER_MSEC);
+ // if the last startup attempt which crashed was in the last 6 hours
+ if (replacedLockTime >= now - MAX_TIME_SINCE_STARTUP) {
+ NS_WARNING("Last startup was detected as a crash.");
+ recentCrashes++;
+ rv = Preferences::SetInt(kPrefRecentCrashes, recentCrashes);
+ } else {
+ // Otherwise ignore that crash and all previous since it may not be applicable anymore
+ // and we don't want someone to get stuck in safe mode if their prefs are read-only.
+ rv = Preferences::ClearUser(kPrefRecentCrashes);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // recalculate since recent crashes count may have changed above
+ mIsSafeModeNecessary = (recentCrashes > maxResumedCrashes && maxResumedCrashes != -1);
+
+ nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
+ rv = prefs->SavePrefFile(nullptr); // flush prefs to disk since we are tracking crashes
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ GetAutomaticSafeModeNecessary(aIsSafeModeNecessary);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAppStartup::TrackStartupCrashEnd()
+{
+ bool inSafeMode = false;
+ nsCOMPtr<nsIXULRuntime> xr = do_GetService(XULRUNTIME_SERVICE_CONTRACTID);
+ if (xr)
+ xr->GetInSafeMode(&inSafeMode);
+
+ // return if we already ended or we're restarting into safe mode
+ if (mStartupCrashTrackingEnded || (mIsSafeModeNecessary && !inSafeMode))
+ return NS_OK;
+ mStartupCrashTrackingEnded = true;
+
+ StartupTimeline::Record(StartupTimeline::STARTUP_CRASH_DETECTION_END);
+
+ // Use the timestamp of XRE_main as an approximation for the lock file timestamp.
+ // See MAX_STARTUP_BUFFER for the buffer time period.
+ TimeStamp mainTime = StartupTimeline::Get(StartupTimeline::MAIN);
+ TimeStamp now = TimeStamp::Now();
+ PRTime prNow = PR_Now();
+ nsresult rv;
+
+ if (mainTime.IsNull()) {
+ NS_WARNING("Could not get StartupTimeline::MAIN time.");
+ } else {
+ uint64_t lockFileTime = ComputeAbsoluteTimestamp(prNow, now, mainTime);
+
+ rv = Preferences::SetInt(kPrefLastSuccess,
+ (int32_t)(lockFileTime / PR_USEC_PER_SEC));
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Could not set startup crash detection pref.");
+ }
+
+ if (inSafeMode && mIsSafeModeNecessary) {
+ // On a successful startup in automatic safe mode, allow the user one more crash
+ // in regular mode before returning to safe mode.
+ int32_t maxResumedCrashes = 0;
+ int32_t prefType;
+ rv = Preferences::GetDefaultRootBranch()->GetPrefType(kPrefMaxResumedCrashes, &prefType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (prefType == nsIPrefBranch::PREF_INT) {
+ rv = Preferences::GetInt(kPrefMaxResumedCrashes, &maxResumedCrashes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = Preferences::SetInt(kPrefRecentCrashes, maxResumedCrashes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (!inSafeMode) {
+ // clear the count of recent crashes after a succesful startup when not in safe mode
+ rv = Preferences::ClearUser(kPrefRecentCrashes);
+ if (NS_FAILED(rv)) NS_WARNING("Could not clear startup crash count.");
+ }
+ nsCOMPtr<nsIPrefService> prefs = Preferences::GetService();
+ rv = prefs->SavePrefFile(nullptr); // flush prefs to disk since we are tracking crashes
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAppStartup::RestartInSafeMode(uint32_t aQuitMode)
+{
+ PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
+ this->Quit(aQuitMode | nsIAppStartup::eRestart);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppStartup::CreateInstanceWithProfile(nsIToolkitProfile* aProfile)
+{
+ if (NS_WARN_IF(!aProfile)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(gAbsoluteArgv0Path.IsEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> execPath;
+ nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(gAbsoluteArgv0Path),
+ true, getter_AddRefs(execPath));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIProcess> process = do_CreateInstance(NS_PROCESS_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = process->Init(execPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsAutoCString profileName;
+ rv = aProfile->GetName(profileName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#if defined(XP_WIN)
+ const char *args[] = { "-no-remote", "-P", profileName.get() };
+ rv = process->Run(false, args, 3);
+#else
+ const char *args[] = { "-P", profileName.get() };
+ rv = process->Run(false, args, 2);
+#endif
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
diff --git a/components/startup/src/nsAppStartup.h b/components/startup/src/nsAppStartup.h
new file mode 100644
index 000000000..44c406ee4
--- /dev/null
+++ b/components/startup/src/nsAppStartup.h
@@ -0,0 +1,76 @@
+/* -*- 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 nsAppStartup_h__
+#define nsAppStartup_h__
+
+#include "nsIAppStartup.h"
+#include "nsIWindowCreator2.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+#include "nsINativeAppSupport.h"
+#include "nsIAppShell.h"
+#include "mozilla/Attributes.h"
+
+#if defined(XP_WIN)
+//XPerf-backed probes
+#include "mozilla/perfprobe.h"
+#include "nsAutoPtr.h"
+#endif //defined(XP_WIN)
+
+
+// {7DD4D320-C84B-4624-8D45-7BB9B2356977}
+#define NS_TOOLKIT_APPSTARTUP_CID \
+{ 0x7dd4d320, 0xc84b, 0x4624, { 0x8d, 0x45, 0x7b, 0xb9, 0xb2, 0x35, 0x69, 0x77 } }
+
+
+class nsAppStartup final : public nsIAppStartup,
+ public nsIWindowCreator2,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIAPPSTARTUP
+ NS_DECL_NSIWINDOWCREATOR
+ NS_DECL_NSIWINDOWCREATOR2
+ NS_DECL_NSIOBSERVER
+
+ nsAppStartup();
+ nsresult Init();
+
+private:
+ ~nsAppStartup() { }
+
+ void CloseAllWindows();
+
+ friend class nsAppExitEvent;
+
+ nsCOMPtr<nsIAppShell> mAppShell;
+
+ int32_t mConsiderQuitStopper; // if > 0, Quit(eConsiderQuit) fails
+ bool mRunning; // Have we started the main event loop?
+ bool mShuttingDown; // Quit method reentrancy check
+ bool mStartingUp; // Have we passed final-ui-startup?
+ bool mAttemptingQuit; // Quit(eAttemptQuit) still trying
+ bool mRestart; // Quit(eRestart)
+ bool mInterrupted; // Was startup interrupted by an interactive prompt?
+ bool mIsSafeModeNecessary; // Whether safe mode is necessary
+ bool mStartupCrashTrackingEnded; // Whether startup crash tracking has already ended
+ bool mRestartNotSameProfile; // Quit(eRestartNotSameProfile)
+
+#if defined(XP_WIN)
+ //Interaction with OS-provided profiling probes
+ typedef mozilla::probes::ProbeManager ProbeManager;
+ typedef mozilla::probes::Probe Probe;
+ RefPtr<ProbeManager> mProbesManager;
+ RefPtr<Probe> mPlacesInitCompleteProbe;
+ RefPtr<Probe> mSessionWindowRestoredProbe;
+ RefPtr<Probe> mXPCOMShutdownProbe;
+#endif
+};
+
+#endif // nsAppStartup_h__
diff --git a/components/startup/src/nsAppStartupNotifier.cpp b/components/startup/src/nsAppStartupNotifier.cpp
new file mode 100644
index 000000000..4ef38fb8d
--- /dev/null
+++ b/components/startup/src/nsAppStartupNotifier.cpp
@@ -0,0 +1,90 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsIServiceManager.h"
+#include "nsICategoryManager.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsAppStartupNotifier.h"
+#include "nsISimpleEnumerator.h"
+
+NS_IMPL_ISUPPORTS(nsAppStartupNotifier, nsIObserver)
+
+nsAppStartupNotifier::nsAppStartupNotifier()
+{
+}
+
+nsAppStartupNotifier::~nsAppStartupNotifier()
+{
+}
+
+NS_IMETHODIMP nsAppStartupNotifier::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ NS_ENSURE_ARG(aTopic);
+ nsresult rv;
+
+ // now initialize all startup listeners
+ nsCOMPtr<nsICategoryManager> categoryManager =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = categoryManager->EnumerateCategory(aTopic,
+ getter_AddRefs(enumerator));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISupports> entry;
+ while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(entry)))) {
+ nsCOMPtr<nsISupportsCString> category = do_QueryInterface(entry, &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString categoryEntry;
+ rv = category->GetData(categoryEntry);
+
+ nsXPIDLCString contractId;
+ categoryManager->GetCategoryEntry(aTopic,
+ categoryEntry.get(),
+ getter_Copies(contractId));
+
+ if (NS_SUCCEEDED(rv)) {
+
+ // If we see the word "service," in the beginning
+ // of the contractId then we create it as a service
+ // if not we do a createInstance
+
+ nsCOMPtr<nsISupports> startupInstance;
+ if (Substring(contractId, 0, 8).EqualsLiteral("service,"))
+ startupInstance = do_GetService(contractId.get() + 8, &rv);
+ else
+ startupInstance = do_CreateInstance(contractId, &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ // Try to QI to nsIObserver
+ nsCOMPtr<nsIObserver> startupObserver =
+ do_QueryInterface(startupInstance, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = startupObserver->Observe(nullptr, aTopic, nullptr);
+
+ // mainly for debugging if you want to know if your observer worked.
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Startup Observer failed!\n");
+ }
+ }
+ else {
+ #ifdef DEBUG
+ nsAutoCString warnStr("Cannot create startup observer : ");
+ warnStr += contractId.get();
+ NS_WARNING(warnStr.get());
+ #endif
+ }
+
+ }
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/components/startup/src/nsAppStartupNotifier.h b/components/startup/src/nsAppStartupNotifier.h
new file mode 100644
index 000000000..31077ed38
--- /dev/null
+++ b/components/startup/src/nsAppStartupNotifier.h
@@ -0,0 +1,30 @@
+/* -*- 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 nsAppStartupNotifier_h___
+#define nsAppStartupNotifier_h___
+
+#include "nsIAppStartupNotifier.h"
+
+// {1F59B001-02C9-11d5-AE76-CC92F7DB9E03}
+#define NS_APPSTARTUPNOTIFIER_CID \
+ { 0x1f59b001, 0x2c9, 0x11d5, { 0xae, 0x76, 0xcc, 0x92, 0xf7, 0xdb, 0x9e, 0x3 } }
+
+class nsAppStartupNotifier : public nsIObserver
+{
+public:
+ NS_DEFINE_STATIC_CID_ACCESSOR( NS_APPSTARTUPNOTIFIER_CID )
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsAppStartupNotifier();
+
+protected:
+ virtual ~nsAppStartupNotifier();
+};
+
+#endif /* nsAppStartupNotifier_h___ */
+
diff --git a/components/startup/src/nsIAppStartupNotifier.h b/components/startup/src/nsIAppStartupNotifier.h
new file mode 100644
index 000000000..113bfa171
--- /dev/null
+++ b/components/startup/src/nsIAppStartupNotifier.h
@@ -0,0 +1,59 @@
+/* -*- 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/. */
+
+#ifndef nsIAppStartupNotifier_h___
+#define nsIAppStartupNotifier_h___
+
+#include "nsIObserver.h"
+
+/*
+ Some components need to be run at the startup of mozilla or embedding - to
+ start new services etc.
+
+ This interface provides a generic way to start up arbitrary components
+ without requiring them to hack into main1() (or into NS_InitEmbedding) as
+ it's currently being done for services such as wallet, command line handlers
+ etc.
+
+ We will have a category called "app-startup" which components register
+ themselves in using the CategoryManager.
+
+ Components can also (optionally) add the word "service," as a prefix
+ to the "value" they pass in during a call to AddCategoryEntry() as
+ shown below:
+
+ categoryManager->AddCategoryEntry(APPSTARTUP_CATEGORY, "testcomp",
+ "service," NS_WALLETSERVICE_CONTRACTID
+ true, true,
+ getter_Copies(previous));
+
+ Presence of the "service" keyword indicates the components desire to
+ be started as a service. When the "service" keyword is not present
+ we just do a do_CreateInstance.
+
+ When mozilla starts (and when NS_InitEmbedding()) is invoked
+ we create an instance of the AppStartupNotifier component (which
+ implements nsIObserver) and invoke its Observe() method.
+
+ Observe() will enumerate the components registered into the
+ APPSTARTUP_CATEGORY and notify them that startup has begun
+ and release them.
+*/
+
+#define NS_APPSTARTUPNOTIFIER_CONTRACTID "@mozilla.org/embedcomp/appstartup-notifier;1"
+
+#define APPSTARTUP_CATEGORY "app-startup"
+#define APPSTARTUP_TOPIC "app-startup"
+
+
+/*
+ Please note that there's not a new interface in this file.
+ We're just leveraging nsIObserver instead of creating a
+ new one
+
+ This file exists solely to provide the defines above
+*/
+
+#endif /* nsIAppStartupNotifier_h___ */
diff --git a/components/startup/src/nsUserInfo.h b/components/startup/src/nsUserInfo.h
new file mode 100644
index 000000000..49e86c64a
--- /dev/null
+++ b/components/startup/src/nsUserInfo.h
@@ -0,0 +1,23 @@
+/* -*- 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 __nsUserInfo_h
+#define __nsUserInfo_h
+
+#include "nsIUserInfo.h"
+
+class nsUserInfo: public nsIUserInfo
+
+{
+public:
+ nsUserInfo(void);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUSERINFO
+
+protected:
+ virtual ~nsUserInfo();
+};
+
+#endif /* __nsUserInfo_h */
diff --git a/components/startup/src/nsUserInfoUnix.cpp b/components/startup/src/nsUserInfoUnix.cpp
new file mode 100644
index 000000000..71bc46da2
--- /dev/null
+++ b/components/startup/src/nsUserInfoUnix.cpp
@@ -0,0 +1,167 @@
+/* -*- 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 "nsUserInfo.h"
+#include "nsCRT.h"
+
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+#include "nsNativeCharsetUtils.h"
+
+/* Some UNIXy platforms don't have pw_gecos. In this case we use pw_name */
+#if defined(NO_PW_GECOS)
+#define PW_GECOS pw_name
+#else
+#define PW_GECOS pw_gecos
+#endif
+
+nsUserInfo::nsUserInfo()
+{
+}
+
+nsUserInfo::~nsUserInfo()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsUserInfo,nsIUserInfo)
+
+NS_IMETHODIMP
+nsUserInfo::GetFullname(char16_t **aFullname)
+{
+ struct passwd *pw = nullptr;
+
+ pw = getpwuid (geteuid());
+
+ if (!pw || !pw->PW_GECOS) return NS_ERROR_FAILURE;
+
+#ifdef DEBUG_sspitzer
+ printf("fullname = %s\n", pw->PW_GECOS);
+#endif
+
+ nsAutoCString fullname(pw->PW_GECOS);
+
+ // now try to parse the GECOS information, which will be in the form
+ // Full Name, <other stuff> - eliminate the ", <other stuff>
+ // also, sometimes GECOS uses "&" to mean "the user name" so do
+ // the appropriate substitution
+
+ // truncate at first comma (field delimiter)
+ int32_t index;
+ if ((index = fullname.Find(",")) != kNotFound)
+ fullname.Truncate(index);
+
+ // replace ampersand with username
+ if (pw->pw_name) {
+ nsAutoCString username(pw->pw_name);
+ if (!username.IsEmpty() && nsCRT::IsLower(username.CharAt(0)))
+ username.SetCharAt(nsCRT::ToUpper(username.CharAt(0)), 0);
+
+ fullname.ReplaceSubstring("&", username.get());
+ }
+
+ nsAutoString unicodeFullname;
+ NS_CopyNativeToUnicode(fullname, unicodeFullname);
+
+ *aFullname = ToNewUnicode(unicodeFullname);
+
+ if (*aFullname)
+ return NS_OK;
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetUsername(char * *aUsername)
+{
+ struct passwd *pw = nullptr;
+
+ // is this portable? those are POSIX compliant calls, but I need to check
+ pw = getpwuid(geteuid());
+
+ if (!pw || !pw->pw_name) return NS_ERROR_FAILURE;
+
+#ifdef DEBUG_sspitzer
+ printf("username = %s\n", pw->pw_name);
+#endif
+
+ *aUsername = strdup(pw->pw_name);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetDomain(char * *aDomain)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ struct utsname buf;
+ char *domainname = nullptr;
+
+ if (uname(&buf) < 0) {
+ return rv;
+ }
+
+#if defined(__linux__)
+ domainname = buf.domainname;
+#endif
+
+ if (domainname && domainname[0]) {
+ *aDomain = strdup(domainname);
+ rv = NS_OK;
+ }
+ else {
+ // try to get the hostname from the nodename
+ // on machines that use DHCP, domainname may not be set
+ // but the nodename might.
+ if (buf.nodename[0]) {
+ // if the nodename is foo.bar.org, use bar.org as the domain
+ char *pos = strchr(buf.nodename,'.');
+ if (pos) {
+ *aDomain = strdup(pos+1);
+ rv = NS_OK;
+ }
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetEmailAddress(char * *aEmailAddress)
+{
+ // use username + "@" + domain for the email address
+
+ nsresult rv;
+
+ nsAutoCString emailAddress;
+ nsXPIDLCString username;
+ nsXPIDLCString domain;
+
+ rv = GetUsername(getter_Copies(username));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = GetDomain(getter_Copies(domain));
+ if (NS_FAILED(rv)) return rv;
+
+ if (!username.IsEmpty() && !domain.IsEmpty()) {
+ emailAddress = (const char *)username;
+ emailAddress += "@";
+ emailAddress += (const char *)domain;
+ }
+ else {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aEmailAddress = ToNewCString(emailAddress);
+
+ return NS_OK;
+}
+
diff --git a/components/startup/src/nsUserInfoWin.cpp b/components/startup/src/nsUserInfoWin.cpp
new file mode 100644
index 000000000..b27a2c483
--- /dev/null
+++ b/components/startup/src/nsUserInfoWin.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsUserInfo.h"
+
+#include "mozilla/ArrayUtils.h" // ArrayLength
+#include "nsString.h"
+#include "windows.h"
+#include "nsCRT.h"
+#include "nsXPIDLString.h"
+
+#define SECURITY_WIN32
+#include "lm.h"
+#include "security.h"
+
+nsUserInfo::nsUserInfo()
+{
+}
+
+nsUserInfo::~nsUserInfo()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsUserInfo, nsIUserInfo)
+
+NS_IMETHODIMP
+nsUserInfo::GetUsername(char **aUsername)
+{
+ NS_ENSURE_ARG_POINTER(aUsername);
+ *aUsername = nullptr;
+
+ // ULEN is the max username length as defined in lmcons.h
+ wchar_t username[UNLEN +1];
+ DWORD size = mozilla::ArrayLength(username);
+ if (!GetUserNameW(username, &size))
+ return NS_ERROR_FAILURE;
+
+ *aUsername = ToNewUTF8String(nsDependentString(username));
+ return (*aUsername) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetFullname(char16_t **aFullname)
+{
+ NS_ENSURE_ARG_POINTER(aFullname);
+ *aFullname = nullptr;
+
+ wchar_t fullName[512];
+ DWORD size = mozilla::ArrayLength(fullName);
+
+ if (GetUserNameExW(NameDisplay, fullName, &size)) {
+ *aFullname = ToNewUnicode(nsDependentString(fullName));
+ } else {
+ DWORD getUsernameError = GetLastError();
+
+ // Try to use the net APIs regardless of the error because it may be
+ // able to obtain the information.
+ wchar_t username[UNLEN + 1];
+ size = mozilla::ArrayLength(username);
+ if (!GetUserNameW(username, &size)) {
+ // ERROR_NONE_MAPPED means the user info is not filled out on this computer
+ return getUsernameError == ERROR_NONE_MAPPED ?
+ NS_ERROR_NOT_AVAILABLE : NS_ERROR_FAILURE;
+ }
+
+ const DWORD level = 2;
+ LPBYTE info;
+ // If the NetUserGetInfo function has no full name info it will return
+ // success with an empty string.
+ NET_API_STATUS status = NetUserGetInfo(nullptr, username, level, &info);
+ if (status != NERR_Success) {
+ // We have an error with NetUserGetInfo but we know the info is not
+ // filled in because GetUserNameExW returned ERROR_NONE_MAPPED.
+ return getUsernameError == ERROR_NONE_MAPPED ?
+ NS_ERROR_NOT_AVAILABLE : NS_ERROR_FAILURE;
+ }
+
+ nsDependentString fullName =
+ nsDependentString(reinterpret_cast<USER_INFO_2 *>(info)->usri2_full_name);
+
+ // NetUserGetInfo returns an empty string if the full name is not filled out
+ if (fullName.Length() == 0) {
+ NetApiBufferFree(info);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aFullname = ToNewUnicode(fullName);
+ NetApiBufferFree(info);
+ }
+
+ return (*aFullname) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetDomain(char **aDomain)
+{
+ NS_ENSURE_ARG_POINTER(aDomain);
+ *aDomain = nullptr;
+
+ const DWORD level = 100;
+ LPBYTE info;
+ NET_API_STATUS status = NetWkstaGetInfo(nullptr, level, &info);
+ if (status == NERR_Success) {
+ *aDomain =
+ ToNewUTF8String(nsDependentString(reinterpret_cast<WKSTA_INFO_100 *>(info)->
+ wki100_langroup));
+ NetApiBufferFree(info);
+ }
+
+ return (*aDomain) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetEmailAddress(char **aEmailAddress)
+{
+ NS_ENSURE_ARG_POINTER(aEmailAddress);
+ *aEmailAddress = nullptr;
+
+ // RFC3696 says max length of an email address is 254
+ wchar_t emailAddress[255];
+ DWORD size = mozilla::ArrayLength(emailAddress);
+
+ if (!GetUserNameExW(NameUserPrincipal, emailAddress, &size)) {
+ DWORD getUsernameError = GetLastError();
+ return getUsernameError == ERROR_NONE_MAPPED ?
+ NS_ERROR_NOT_AVAILABLE : NS_ERROR_FAILURE;
+ }
+
+ *aEmailAddress = ToNewUTF8String(nsDependentString(emailAddress));
+ return (*aEmailAddress) ? NS_OK : NS_ERROR_FAILURE;
+}
diff --git a/components/statusfilter/moz.build b/components/statusfilter/moz.build
new file mode 100644
index 000000000..94b334c7a
--- /dev/null
+++ b/components/statusfilter/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+SOURCES += [
+ 'nsBrowserStatusFilter.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/statusfilter/nsBrowserStatusFilter.cpp b/components/statusfilter/nsBrowserStatusFilter.cpp
new file mode 100644
index 000000000..f607e01b4
--- /dev/null
+++ b/components/statusfilter/nsBrowserStatusFilter.cpp
@@ -0,0 +1,392 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsBrowserStatusFilter.h"
+#include "nsIChannel.h"
+#include "nsITimer.h"
+#include "nsIServiceManager.h"
+#include "nsString.h"
+
+// XXX
+// XXX DO NOT TOUCH THIS CODE UNLESS YOU KNOW WHAT YOU'RE DOING !!!
+// XXX
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter <public>
+//-----------------------------------------------------------------------------
+
+nsBrowserStatusFilter::nsBrowserStatusFilter()
+ : mCurProgress(0)
+ , mMaxProgress(0)
+ , mStatusIsDirty(true)
+ , mCurrentPercentage(0)
+ , mTotalRequests(0)
+ , mFinishedRequests(0)
+ , mUseRealProgressFlag(false)
+ , mDelayedStatus(false)
+ , mDelayedProgress(false)
+{
+}
+
+nsBrowserStatusFilter::~nsBrowserStatusFilter()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsBrowserStatusFilter,
+ nsIWebProgress,
+ nsIWebProgressListener,
+ nsIWebProgressListener2,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter::nsIWebProgress
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::AddProgressListener(nsIWebProgressListener *aListener,
+ uint32_t aNotifyMask)
+{
+ mListener = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::RemoveProgressListener(nsIWebProgressListener *aListener)
+{
+ if (aListener == mListener)
+ mListener = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::GetDOMWindow(mozIDOMWindowProxy **aResult)
+{
+ NS_NOTREACHED("nsBrowserStatusFilter::GetDOMWindow");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::GetDOMWindowID(uint64_t *aResult)
+{
+ *aResult = 0;
+ NS_NOTREACHED("nsBrowserStatusFilter::GetDOMWindowID");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::GetIsTopLevel(bool *aIsTopLevel)
+{
+ *aIsTopLevel = false;
+ NS_NOTREACHED("nsBrowserStatusFilter::GetIsTopLevel");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::GetIsLoadingDocument(bool *aIsLoadingDocument)
+{
+ NS_NOTREACHED("nsBrowserStatusFilter::GetIsLoadingDocument");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::GetLoadType(uint32_t *aLoadType)
+{
+ *aLoadType = 0;
+ NS_NOTREACHED("nsBrowserStatusFilter::GetLoadType");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter::nsIWebProgressListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnStateChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ if (!mListener)
+ return NS_OK;
+
+ if (aStateFlags & STATE_START) {
+ if (aStateFlags & STATE_IS_NETWORK) {
+ ResetMembers();
+ }
+ if (aStateFlags & STATE_IS_REQUEST) {
+ ++mTotalRequests;
+
+ // if the total requests exceeds 1, then we'll base our progress
+ // notifications on the percentage of completed requests.
+ // otherwise, progress for the single request will be reported.
+ mUseRealProgressFlag = (mTotalRequests == 1);
+ }
+ }
+ else if (aStateFlags & STATE_STOP) {
+ if (aStateFlags & STATE_IS_REQUEST) {
+ ++mFinishedRequests;
+ // Note: Do not return from here. This is necessary so that the
+ // STATE_STOP can still be relayed to the listener if needed
+ // (bug 209330)
+ if (!mUseRealProgressFlag && mTotalRequests)
+ OnProgressChange(nullptr, nullptr, 0, 0,
+ mFinishedRequests, mTotalRequests);
+ }
+ }
+ else if (aStateFlags & STATE_TRANSFERRING) {
+ if (aStateFlags & STATE_IS_REQUEST) {
+ if (!mUseRealProgressFlag && mTotalRequests)
+ return OnProgressChange(nullptr, nullptr, 0, 0,
+ mFinishedRequests, mTotalRequests);
+ }
+
+ // no need to forward this state change
+ return NS_OK;
+ } else {
+ // no need to forward this state change
+ return NS_OK;
+ }
+
+ // If we're here, we have either STATE_START or STATE_STOP. The
+ // listener only cares about these in certain conditions.
+ bool isLoadingDocument = false;
+ if ((aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK ||
+ (aStateFlags & nsIWebProgressListener::STATE_IS_REQUEST &&
+ mFinishedRequests == mTotalRequests &&
+ (aWebProgress->GetIsLoadingDocument(&isLoadingDocument),
+ !isLoadingDocument)))) {
+ if (mTimer && (aStateFlags & nsIWebProgressListener::STATE_STOP)) {
+ mTimer->Cancel();
+ ProcessTimeout();
+ }
+
+ return mListener->OnStateChange(aWebProgress, aRequest, aStateFlags,
+ aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnProgressChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ if (!mListener)
+ return NS_OK;
+
+ if (!mUseRealProgressFlag && aRequest)
+ return NS_OK;
+
+ //
+ // limit frequency of calls to OnProgressChange
+ //
+
+ mCurProgress = (int64_t)aCurTotalProgress;
+ mMaxProgress = (int64_t)aMaxTotalProgress;
+
+ if (mDelayedProgress)
+ return NS_OK;
+
+ if (!mDelayedStatus) {
+ MaybeSendProgress();
+ StartDelayTimer();
+ }
+
+ mDelayedProgress = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnLocationChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ nsIURI *aLocation,
+ uint32_t aFlags)
+{
+ if (!mListener)
+ return NS_OK;
+
+ return mListener->OnLocationChange(aWebProgress, aRequest, aLocation,
+ aFlags);
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnStatusChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ nsresult aStatus,
+ const char16_t *aMessage)
+{
+ if (!mListener)
+ return NS_OK;
+
+ //
+ // limit frequency of calls to OnStatusChange
+ //
+ if (mStatusIsDirty || !mCurrentStatusMsg.Equals(aMessage)) {
+ mStatusIsDirty = true;
+ mStatusMsg = aMessage;
+ }
+
+ if (mDelayedStatus)
+ return NS_OK;
+
+ if (!mDelayedProgress) {
+ MaybeSendStatus();
+ StartDelayTimer();
+ }
+
+ mDelayedStatus = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnSecurityChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ uint32_t aState)
+{
+ if (!mListener)
+ return NS_OK;
+
+ return mListener->OnSecurityChange(aWebProgress, aRequest, aState);
+}
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter::nsIWebProgressListener2
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnProgressChange64(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ int64_t aCurSelfProgress,
+ int64_t aMaxSelfProgress,
+ int64_t aCurTotalProgress,
+ int64_t aMaxTotalProgress)
+{
+ // XXX truncates 64-bit to 32-bit
+ return OnProgressChange(aWebProgress, aRequest,
+ (int32_t)aCurSelfProgress,
+ (int32_t)aMaxSelfProgress,
+ (int32_t)aCurTotalProgress,
+ (int32_t)aMaxTotalProgress);
+}
+
+NS_IMETHODIMP
+nsBrowserStatusFilter::OnRefreshAttempted(nsIWebProgress *aWebProgress,
+ nsIURI *aUri,
+ int32_t aDelay,
+ bool aSameUri,
+ bool *allowRefresh)
+{
+ nsCOMPtr<nsIWebProgressListener2> listener =
+ do_QueryInterface(mListener);
+ if (!listener) {
+ *allowRefresh = true;
+ return NS_OK;
+ }
+
+ return listener->OnRefreshAttempted(aWebProgress, aUri, aDelay, aSameUri,
+ allowRefresh);
+}
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter <private>
+//-----------------------------------------------------------------------------
+
+void
+nsBrowserStatusFilter::ResetMembers()
+{
+ mTotalRequests = 0;
+ mFinishedRequests = 0;
+ mUseRealProgressFlag = false;
+ mMaxProgress = 0;
+ mCurProgress = 0;
+ mCurrentPercentage = 0;
+ mStatusIsDirty = true;
+}
+
+void
+nsBrowserStatusFilter::MaybeSendProgress()
+{
+ if (mCurProgress > mMaxProgress || mCurProgress <= 0)
+ return;
+
+ // check our percentage
+ int32_t percentage = (int32_t) double(mCurProgress) * 100 / mMaxProgress;
+
+ // The progress meter only updates for increases greater than 3 percent
+ if (percentage > (mCurrentPercentage + 3)) {
+ mCurrentPercentage = percentage;
+ // XXX truncates 64-bit to 32-bit
+ mListener->OnProgressChange(nullptr, nullptr, 0, 0,
+ (int32_t)mCurProgress,
+ (int32_t)mMaxProgress);
+ }
+}
+
+void
+nsBrowserStatusFilter::MaybeSendStatus()
+{
+ if (mStatusIsDirty) {
+ mListener->OnStatusChange(nullptr, nullptr, NS_OK, mStatusMsg.get());
+ mCurrentStatusMsg = mStatusMsg;
+ mStatusIsDirty = false;
+ }
+}
+
+nsresult
+nsBrowserStatusFilter::StartDelayTimer()
+{
+ NS_ASSERTION(!DelayInEffect(), "delay should not be in effect");
+
+ mTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mTimer)
+ return NS_ERROR_FAILURE;
+
+ return mTimer->InitWithNamedFuncCallback(
+ TimeoutHandler, this, 160, nsITimer::TYPE_ONE_SHOT,
+ "nsBrowserStatusFilter::TimeoutHandler");
+}
+
+void
+nsBrowserStatusFilter::ProcessTimeout()
+{
+ mTimer = nullptr;
+
+ if (!mListener)
+ return;
+
+ if (mDelayedStatus) {
+ mDelayedStatus = false;
+ MaybeSendStatus();
+ }
+
+ if (mDelayedProgress) {
+ mDelayedProgress = false;
+ MaybeSendProgress();
+ }
+}
+
+void
+nsBrowserStatusFilter::TimeoutHandler(nsITimer *aTimer, void *aClosure)
+{
+ nsBrowserStatusFilter *self = reinterpret_cast<nsBrowserStatusFilter *>(aClosure);
+ if (!self) {
+ NS_ERROR("no self");
+ return;
+ }
+
+ self->ProcessTimeout();
+}
diff --git a/components/statusfilter/nsBrowserStatusFilter.h b/components/statusfilter/nsBrowserStatusFilter.h
new file mode 100644
index 000000000..0eb6364fd
--- /dev/null
+++ b/components/statusfilter/nsBrowserStatusFilter.h
@@ -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/. */
+
+#ifndef nsBrowserStatusFilter_h__
+#define nsBrowserStatusFilter_h__
+
+#include "nsIWebProgressListener.h"
+#include "nsIWebProgressListener2.h"
+#include "nsIWebProgress.h"
+#include "nsWeakReference.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+//-----------------------------------------------------------------------------
+// nsBrowserStatusFilter - a web progress listener implementation designed to
+// sit between nsDocLoader and nsBrowserStatusHandler to filter out and limit
+// the frequency of certain events to improve page load performance.
+//-----------------------------------------------------------------------------
+
+class nsBrowserStatusFilter : public nsIWebProgress
+ , public nsIWebProgressListener2
+ , public nsSupportsWeakReference
+{
+public:
+ nsBrowserStatusFilter();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBPROGRESS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIWEBPROGRESSLISTENER2
+
+protected:
+ virtual ~nsBrowserStatusFilter();
+
+private:
+ nsresult StartDelayTimer();
+ void ProcessTimeout();
+ void MaybeSendProgress();
+ void MaybeSendStatus();
+ void ResetMembers();
+ bool DelayInEffect() { return mDelayedStatus || mDelayedProgress; }
+
+ static void TimeoutHandler(nsITimer *aTimer, void *aClosure);
+
+private:
+ nsCOMPtr<nsIWebProgressListener> mListener;
+ nsCOMPtr<nsITimer> mTimer;
+
+ // delayed values
+ nsString mStatusMsg;
+ int64_t mCurProgress;
+ int64_t mMaxProgress;
+
+ nsString mCurrentStatusMsg;
+ bool mStatusIsDirty;
+ int32_t mCurrentPercentage;
+
+ // used to convert OnStart/OnStop notifications into progress notifications
+ int32_t mTotalRequests;
+ int32_t mFinishedRequests;
+ bool mUseRealProgressFlag;
+
+ // indicates whether a timeout is pending
+ bool mDelayedStatus;
+ bool mDelayedProgress;
+};
+
+#define NS_BROWSERSTATUSFILTER_CONTRACTID \
+ "@mozilla.org/appshell/component/browser-status-filter;1"
+#define NS_BROWSERSTATUSFILTER_CID \
+{ /* 6356aa16-7916-4215-a825-cbc2692ca87a */ \
+ 0x6356aa16, \
+ 0x7916, \
+ 0x4215, \
+ {0xa8, 0x25, 0xcb, 0xc2, 0x69, 0x2c, 0xa8, 0x7a} \
+}
+
+#endif // !nsBrowserStatusFilter_h__
diff --git a/components/storage/.eslintrc.js b/components/storage/.eslintrc.js
new file mode 100644
index 000000000..69afc2f3c
--- /dev/null
+++ b/components/storage/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../toolkit/.eslintrc.js"
+ ]
+};
diff --git a/components/storage/moz.build b/components/storage/moz.build
new file mode 100644
index 000000000..457f38622
--- /dev/null
+++ b/components/storage/moz.build
@@ -0,0 +1,95 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+# Note: On Windows our sqlite build assumes we use jemalloc. If you disable
+# MOZ_STORAGE_MEMORY on Windows, you will also need to change the "ifdef
+# MOZ_MEMORY" options in db/sqlite3/src/Makefile.in.
+if CONFIG['MOZ_MEMORY']:
+ DEFINES['MOZ_STORAGE_MEMORY'] = True
+
+# This is the default value. If we ever change it when compiling sqlite, we
+# will need to change it here as well.
+DEFINES['SQLITE_MAX_LIKE_PATTERN_LENGTH'] = 50000
+
+# See Sqlite moz.build for reasoning about TEMP_STORE.
+
+XPIDL_SOURCES += [
+ 'public/mozIStorageAggregateFunction.idl',
+ 'public/mozIStorageAsyncConnection.idl',
+ 'public/mozIStorageAsyncStatement.idl',
+ 'public/mozIStorageBaseStatement.idl',
+ 'public/mozIStorageBindingParams.idl',
+ 'public/mozIStorageBindingParamsArray.idl',
+ 'public/mozIStorageCompletionCallback.idl',
+ 'public/mozIStorageConnection.idl',
+ 'public/mozIStorageError.idl',
+ 'public/mozIStorageFunction.idl',
+ 'public/mozIStoragePendingStatement.idl',
+ 'public/mozIStorageProgressHandler.idl',
+ 'public/mozIStorageResultSet.idl',
+ 'public/mozIStorageRow.idl',
+ 'public/mozIStorageService.idl',
+ 'public/mozIStorageStatement.idl',
+ 'public/mozIStorageStatementCallback.idl',
+ 'public/mozIStorageStatementParams.idl',
+ 'public/mozIStorageStatementRow.idl',
+ 'public/mozIStorageVacuumParticipant.idl',
+ 'public/mozIStorageValueArray.idl',
+]
+
+EXPORTS += [
+ 'src/mozStorageCID.h',
+ 'src/mozStorageHelper.h',
+]
+
+EXPORTS.mozilla += ['src/storage.h']
+
+# NOTE When adding something to this list, you probably need to add it to the
+# storage.h file too.
+EXPORTS.mozilla.storage += [
+ 'src/StatementCache.h',
+ 'src/Variant.h',
+ 'src/Variant_inl.h',
+]
+# SEE ABOVE NOTE!
+
+SOURCES += [
+ 'src/FileSystemModule.cpp',
+ 'src/mozStorageArgValueArray.cpp',
+ 'src/mozStorageAsyncStatement.cpp',
+ 'src/mozStorageAsyncStatementExecution.cpp',
+ 'src/mozStorageAsyncStatementJSHelper.cpp',
+ 'src/mozStorageAsyncStatementParams.cpp',
+ 'src/mozStorageBindingParams.cpp',
+ 'src/mozStorageBindingParamsArray.cpp',
+ 'src/mozStorageConnection.cpp',
+ 'src/mozStorageError.cpp',
+ 'src/mozStorageModule.cpp',
+ 'src/mozStoragePrivateHelpers.cpp',
+ 'src/mozStorageResultSet.cpp',
+ 'src/mozStorageRow.cpp',
+ 'src/mozStorageService.cpp',
+ 'src/mozStorageSQLFunctions.cpp',
+ 'src/mozStorageStatement.cpp',
+ 'src/mozStorageStatementJSHelper.cpp',
+ 'src/mozStorageStatementParams.cpp',
+ 'src/mozStorageStatementRow.cpp',
+ 'src/SQLCollations.cpp',
+ 'src/StorageBaseStatementInternal.cpp',
+ 'src/TelemetryVFS.cpp',
+ 'src/VacuumManager.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/libs/sqlite3/src',
+]
+
+CXXFLAGS += CONFIG['SQLITE_CFLAGS']
+
+XPIDL_MODULE = 'storage'
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild') \ No newline at end of file
diff --git a/components/storage/public/mozIStorageAggregateFunction.idl b/components/storage/public/mozIStorageAggregateFunction.idl
new file mode 100644
index 000000000..28579318d
--- /dev/null
+++ b/components/storage/public/mozIStorageAggregateFunction.idl
@@ -0,0 +1,38 @@
+/* -*- 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 mozIStorageConnection;
+interface mozIStorageValueArray;
+interface nsIArray;
+interface nsIVariant;
+
+/**
+ * mozIStorageAggregateFunction represents aggregate SQL function.
+ * Common examples of aggregate functions are SUM() and COUNT().
+ *
+ * An aggregate function calculates one result for a given set of data, where
+ * a set of data is a group of tuples. There can be one group
+ * per request or many of them, if GROUP BY clause is used or not.
+ */
+[scriptable, uuid(763217b7-3123-11da-918d-000347412e16)]
+interface mozIStorageAggregateFunction : nsISupports {
+ /**
+ * onStep is called when next value should be passed to
+ * a custom function.
+ *
+ * @param aFunctionArguments The arguments passed in to the function
+ */
+ void onStep(in mozIStorageValueArray aFunctionArguments);
+
+ /**
+ * Called when all tuples in a group have been processed and the engine
+ * needs the aggregate function's value.
+ *
+ * @returns aggregate result as Variant.
+ */
+ nsIVariant onFinal();
+};
diff --git a/components/storage/public/mozIStorageAsyncConnection.idl b/components/storage/public/mozIStorageAsyncConnection.idl
new file mode 100644
index 000000000..aeb2bf1b5
--- /dev/null
+++ b/components/storage/public/mozIStorageAsyncConnection.idl
@@ -0,0 +1,220 @@
+/* -*- Mode: idl; 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 mozIStorageAggregateFunction;
+interface mozIStorageCompletionCallback;
+interface mozIStorageFunction;
+interface mozIStorageProgressHandler;
+interface mozIStorageBaseStatement;
+interface mozIStorageStatement;
+interface mozIStorageAsyncStatement;
+interface mozIStorageStatementCallback;
+interface mozIStoragePendingStatement;
+interface nsIFile;
+
+/**
+ * mozIStorageAsyncConnection represents an asynchronous database
+ * connection attached to a specific file or to an in-memory data
+ * storage. It is the primary interface for interacting with a
+ * database from the main thread, including creating prepared
+ * statements, executing SQL, and examining database errors.
+ */
+[scriptable, uuid(8bfd34d5-4ddf-4e4b-89dd-9b14f33534c6)]
+interface mozIStorageAsyncConnection : nsISupports {
+ /**
+ * Close this database connection, allowing all pending statements
+ * to complete first.
+ *
+ * @param aCallback [optional]
+ * A callback that will be notified when the close is completed,
+ * with the following arguments:
+ * - status: the status of the call
+ * - value: |null|
+ *
+ * @throws NS_ERROR_NOT_SAME_THREAD
+ * If called on a thread other than the one that opened it. The
+ * callback will not be dispatched.
+ * @throws NS_ERROR_NOT_INITIALIZED
+ * If called on a connection that has already been closed or was
+ * never properly opened. The callback will still be dispatched
+ * to the main thread despite the returned error.
+ */
+ void asyncClose([optional] in mozIStorageCompletionCallback aCallback);
+
+ /**
+ * Clone a database and make the clone read only if needed.
+ * SQL Functions and attached on-disk databases are applied to the new clone.
+ *
+ * @param aReadOnly
+ * If true, the returned database should be put into read-only mode.
+ *
+ * @param aCallback
+ * A callback that will be notified when the operation is complete,
+ * with the following arguments:
+ * - status: the status of the operation
+ * - value: in case of success, an intance of
+ * mozIStorageAsyncConnection cloned from this one.
+ *
+ * @throws NS_ERROR_NOT_SAME_THREAD
+ * If is called on a thread other than the one that opened it.
+ * @throws NS_ERROR_UNEXPECTED
+ * If this connection is a memory database.
+ *
+ * @note If your connection is already read-only, you will get a read-only
+ * clone.
+ * @note Due to a bug in SQLite, if you use the shared cache
+ * (see mozIStorageService), you end up with the same privileges as the
+ * first connection opened regardless of what is specified in aReadOnly.
+ * @note The following pragmas are copied over to a read-only clone:
+ * - cache_size
+ * - temp_store
+ * The following pragmas are copied over to a writeable clone:
+ * - cache_size
+ * - temp_store
+ * - foreign_keys
+ * - journal_size_limit
+ * - synchronous
+ * - wal_autocheckpoint
+ */
+ void asyncClone(in boolean aReadOnly,
+ in mozIStorageCompletionCallback aCallback);
+
+ /**
+ * The current database nsIFile. Null if the database
+ * connection refers to an in-memory database.
+ */
+ readonly attribute nsIFile databaseFile;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Statement creation
+
+ /**
+ * Create an asynchronous statement for the given SQL. An
+ * asynchronous statement can only be used to dispatch asynchronous
+ * requests to the asynchronous execution thread and cannot be used
+ * to take any synchronous actions on the database.
+ *
+ * The expression may use ? to indicate sequential numbered arguments,
+ * ?1, ?2 etc. to indicate specific numbered arguments or :name and
+ * $var to indicate named arguments.
+ *
+ * @param aSQLStatement
+ * The SQL statement to execute.
+ * @return a new mozIStorageAsyncStatement
+ * @note The statement is created lazily on first execution.
+ */
+ mozIStorageAsyncStatement createAsyncStatement(in AUTF8String aSQLStatement);
+
+ /**
+ * Execute an array of statements created with this connection using
+ * any currently bound parameters. When the array contains multiple
+ * statements, the execution is wrapped in a single
+ * transaction. These statements can be reused immediately, and
+ * reset does not need to be called.
+ *
+ * @param aStatements
+ * The array of statements to execute asynchronously, in the order they
+ * are given in the array.
+ * @param aNumStatements
+ * The number of statements in aStatements.
+ * @param aCallback [optional]
+ * The callback object that will be notified of progress, errors, and
+ * completion.
+ * @return an object that can be used to cancel the statements execution.
+ *
+ * @note If you have any custom defined functions, they must be
+ * re-entrant since they can be called on multiple threads.
+ */
+ mozIStoragePendingStatement executeAsync(
+ [array, size_is(aNumStatements)] in mozIStorageBaseStatement aStatements,
+ in unsigned long aNumStatements,
+ [optional] in mozIStorageStatementCallback aCallback
+ );
+
+ /**
+ * Execute asynchronously an SQL expression, expecting no arguments.
+ *
+ * @param aSQLStatement
+ * The SQL statement to execute
+ * @param aCallback [optional]
+ * The callback object that will be notified of progress, errors, and
+ * completion.
+ * @return an object that can be used to cancel the statement execution.
+ */
+ mozIStoragePendingStatement executeSimpleSQLAsync(
+ in AUTF8String aSQLStatement,
+ [optional] in mozIStorageStatementCallback aCallback);
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Functions
+
+ /**
+ * Create a new SQL function. If you use your connection on multiple threads,
+ * your function needs to be threadsafe, or it should only be called on one
+ * thread.
+ *
+ * @param aFunctionName
+ * The name of function to create, as seen in SQL.
+ * @param aNumArguments
+ * The number of arguments the function takes. Pass -1 for
+ * variable-argument functions.
+ * @param aFunction
+ * The instance of mozIStorageFunction, which implements the function
+ * in question.
+ */
+ void createFunction(in AUTF8String aFunctionName,
+ in long aNumArguments,
+ in mozIStorageFunction aFunction);
+
+ /**
+ * Create a new SQL aggregate function. If you use your connection on
+ * multiple threads, your function needs to be threadsafe, or it should only
+ * be called on one thread.
+ *
+ * @param aFunctionName
+ * The name of aggregate function to create, as seen in SQL.
+ * @param aNumArguments
+ * The number of arguments the function takes. Pass -1 for
+ * variable-argument functions.
+ * @param aFunction
+ * The instance of mozIStorageAggreagteFunction, which implements the
+ * function in question.
+ */
+ void createAggregateFunction(in AUTF8String aFunctionName,
+ in long aNumArguments,
+ in mozIStorageAggregateFunction aFunction);
+ /**
+ * Delete custom SQL function (simple or aggregate one).
+ *
+ * @param aFunctionName
+ * The name of function to remove.
+ */
+ void removeFunction(in AUTF8String aFunctionName);
+
+ /**
+ * Sets a progress handler. Only one handler can be registered at a time.
+ * If you need more than one, you need to chain them yourself. This progress
+ * handler should be threadsafe if you use this connection object on more than
+ * one thread.
+ *
+ * @param aGranularity
+ * The number of SQL virtual machine steps between progress handler
+ * callbacks.
+ * @param aHandler
+ * The instance of mozIStorageProgressHandler.
+ * @return previous registered handler.
+ */
+ mozIStorageProgressHandler setProgressHandler(in int32_t aGranularity,
+ in mozIStorageProgressHandler aHandler);
+
+ /**
+ * Remove a progress handler.
+ *
+ * @return previous registered handler.
+ */
+ mozIStorageProgressHandler removeProgressHandler();
+};
diff --git a/components/storage/public/mozIStorageAsyncStatement.idl b/components/storage/public/mozIStorageAsyncStatement.idl
new file mode 100644
index 000000000..41d03f122
--- /dev/null
+++ b/components/storage/public/mozIStorageAsyncStatement.idl
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozIStorageBaseStatement.idl"
+
+/**
+ * An asynchronous SQL statement. This differs from mozIStorageStatement by
+ * only being usable for asynchronous execution. (mozIStorageStatement can
+ * be used for both synchronous and asynchronous purposes.) This specialization
+ * for asynchronous operation allows us to avoid needing to acquire
+ * synchronization primitives also used by the asynchronous execution thread.
+ * In contrast, mozIStorageStatement may need to acquire the primitives and
+ * consequently can cause the main thread to lock for extended intervals while
+ * the asynchronous thread performs some long-running operation.
+ */
+[scriptable, uuid(52e49370-3b2e-4a27-a3fc-79e20ad4056b)]
+interface mozIStorageAsyncStatement : mozIStorageBaseStatement {
+ /*
+ * 'params' provides a magic JS helper that lets you assign parameters by
+ * name. Unlike the helper on mozIStorageStatement, you cannot enumerate
+ * in order to find out what parameters are legal.
+ *
+ * This does not work for BLOBs. You must use an explicit binding API for
+ * that.
+ *
+ * example:
+ * stmt.params.foo = 1;
+ * stmt.params["bar"] = 2;
+ * let argName = "baz";
+ * stmt.params[argName] = 3;
+ *
+ * readonly attribute nsIMagic params;
+ */
+};
diff --git a/components/storage/public/mozIStorageBaseStatement.idl b/components/storage/public/mozIStorageBaseStatement.idl
new file mode 100644
index 000000000..52cd30500
--- /dev/null
+++ b/components/storage/public/mozIStorageBaseStatement.idl
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "mozIStorageBindingParams.idl"
+
+interface mozIStorageConnection;
+interface mozIStorageStatementCallback;
+interface mozIStoragePendingStatement;
+interface mozIStorageBindingParams;
+interface mozIStorageBindingParamsArray;
+
+/**
+ * The base interface for both pure asynchronous storage statements
+ * (mozIStorageAsyncStatement) and 'classic' storage statements
+ * (mozIStorageStatement) that can be used for both synchronous and asynchronous
+ * purposes.
+ */
+[scriptable, uuid(16ca67aa-1325-43e2-aac7-859afd1590b2)]
+interface mozIStorageBaseStatement : mozIStorageBindingParams {
+ /**
+ * Finalizes a statement so you can successfully close a database connection.
+ * Once a statement has been finalized it can no longer be used for any
+ * purpose.
+ *
+ * Statements are implicitly finalized when their reference counts hits zero.
+ * If you are a native (C++) caller this is accomplished by setting all of
+ * your nsCOMPtr instances to be NULL. If you are operating from JavaScript
+ * code then you cannot rely on this behavior because of the involvement of
+ * garbage collection.
+ *
+ * When finalizing an asynchronous statement you do not need to worry about
+ * whether the statement has actually been executed by the asynchronous
+ * thread; you just need to call finalize after your last call to executeAsync
+ * involving the statement. However, you do need to use asyncClose instead of
+ * close on the connection if any statements have been used asynchronously.
+ */
+ void finalize();
+
+ /**
+ * Bind the given value at the given numeric index.
+ *
+ * @param aParamIndex
+ * 0-based index, 0 corresponding to the first numbered argument or
+ * "?1".
+ * @param aValue
+ * Argument value.
+ * @param aValueSize
+ * Length of aValue in bytes.
+ * @{
+ */
+ [deprecated] void bindUTF8StringParameter(in unsigned long aParamIndex,
+ in AUTF8String aValue);
+ [deprecated] void bindStringParameter(in unsigned long aParamIndex,
+ in AString aValue);
+ [deprecated] void bindDoubleParameter(in unsigned long aParamIndex,
+ in double aValue);
+ [deprecated] void bindInt32Parameter(in unsigned long aParamIndex,
+ in long aValue);
+ [deprecated] void bindInt64Parameter(in unsigned long aParamIndex,
+ in long long aValue);
+ [deprecated] void bindNullParameter(in unsigned long aParamIndex);
+ [deprecated] void bindBlobParameter(
+ in unsigned long aParamIndex,
+ [array,const,size_is(aValueSize)] in octet aValue,
+ in unsigned long aValueSize);
+ [deprecated] void bindStringAsBlobParameter(
+ in unsigned long aParamIndex,
+ in AString aValue);
+ [deprecated] void bindUTF8StringAsBlobParameter(
+ in unsigned long aParamIndex,
+ in AUTF8String aValue);
+ [deprecated] void bindAdoptedBlobParameter(
+ in unsigned long aParamIndex,
+ [array,size_is(aValueSize)] in octet aValue,
+ in unsigned long aValueSize);
+ /**@}*/
+
+ /**
+ * Binds the array of parameters to the statement. When executeAsync is
+ * called, all the parameters in aParameters are bound and then executed.
+ *
+ * @param aParameters
+ * The array of parameters to bind to the statement upon execution.
+ *
+ * @note This is only works on statements being used asynchronously.
+ */
+ void bindParameters(in mozIStorageBindingParamsArray aParameters);
+
+ /**
+ * Creates a new mozIStorageBindingParamsArray that can be used to bind
+ * multiple sets of data to a statement with bindParameters.
+ *
+ * @return a mozIStorageBindingParamsArray that multiple sets of parameters
+ * can be bound to.
+ *
+ * @note This is only useful for statements being used asynchronously.
+ */
+ mozIStorageBindingParamsArray newBindingParamsArray();
+
+ /**
+ * Execute a query asynchronously using any currently bound parameters. This
+ * statement can be reused immediately, and reset does not need to be called.
+ *
+ * @note If you have any custom defined functions, they must be re-entrant
+ * since they can be called on multiple threads.
+ *
+ * @param aCallback [optional]
+ * The callback object that will be notified of progress, errors, and
+ * completion.
+ * @return an object that can be used to cancel the statements execution.
+ */
+ mozIStoragePendingStatement executeAsync(
+ [optional] in mozIStorageStatementCallback aCallback
+ );
+
+ /**
+ * The statement is not usable, either because it failed to initialize or
+ * was explicitly finalized.
+ */
+ const long MOZ_STORAGE_STATEMENT_INVALID = 0;
+ /**
+ * The statement is usable.
+ */
+ const long MOZ_STORAGE_STATEMENT_READY = 1;
+ /**
+ * Indicates that the statement is executing and the row getters may be used.
+ *
+ * @note This is only relevant for mozIStorageStatement instances being used
+ * in a synchronous fashion.
+ */
+ const long MOZ_STORAGE_STATEMENT_EXECUTING = 2;
+
+ /**
+ * Find out whether the statement is usable (has not been finalized).
+ */
+ readonly attribute long state;
+
+ /**
+ * Escape a string for SQL LIKE search.
+ *
+ * @note Consumers will have to use same escape char when doing statements
+ * such as: ...LIKE '?1' ESCAPE '/'...
+ *
+ * @param aValue
+ * The string to escape for SQL LIKE.
+ * @param aEscapeChar
+ * The escape character.
+ * @return an AString of an escaped version of aValue
+ * (%, _ and the escape char are escaped with the escape char)
+ * For example, we will convert "foo/bar_baz%20cheese"
+ * into "foo//bar/_baz/%20cheese" (if the escape char is '/').
+ */
+ AString escapeStringForLIKE(in AString aValue, in wchar aEscapeChar);
+};
diff --git a/components/storage/public/mozIStorageBindingParams.idl b/components/storage/public/mozIStorageBindingParams.idl
new file mode 100644
index 000000000..2c537aaf0
--- /dev/null
+++ b/components/storage/public/mozIStorageBindingParams.idl
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIVariant;
+
+[scriptable, uuid(2d09f42f-966e-4663-b4b3-b0c8676bf2bf)]
+interface mozIStorageBindingParams : nsISupports {
+ /**
+ * Binds aValue to the parameter with the name aName.
+ *
+ * @param aName
+ * The name of the parameter to bind aValue to.
+ * @param aValue
+ * The value to bind.
+ */
+ void bindByName(in AUTF8String aName,
+ in nsIVariant aValue);
+ [noscript] void bindUTF8StringByName(in AUTF8String aName,
+ in AUTF8String aValue);
+ [noscript] void bindStringByName(in AUTF8String aName,
+ in AString aValue);
+ [noscript] void bindDoubleByName(in AUTF8String aName,
+ in double aValue);
+ [noscript] void bindInt32ByName(in AUTF8String aName,
+ in long aValue);
+ [noscript] void bindInt64ByName(in AUTF8String aName,
+ in long long aValue);
+ [noscript] void bindNullByName(in AUTF8String aName);
+ void bindBlobByName(in AUTF8String aName,
+ [array, const, size_is(aValueSize)] in octet aValue,
+ in unsigned long aValueSize);
+
+ // Convenience routines for storing strings as blobs.
+ void bindStringAsBlobByName(in AUTF8String aName, in AString aValue);
+ void bindUTF8StringAsBlobByName(in AUTF8String aName, in AUTF8String aValue);
+
+ // The function adopts the storage for the provided blob. After calling
+ // this function, mozStorage will ensure that free is called on the
+ // underlying pointer.
+ [noscript]
+ void bindAdoptedBlobByName(in AUTF8String aName,
+ [array, size_is(aValueSize)] in octet aValue,
+ in unsigned long aValueSize);
+
+ /**
+ * Binds aValue to the parameter with the index aIndex.
+ *
+ * @param aIndex
+ * The zero-based index of the parameter to bind aValue to.
+ * @param aValue
+ * The value to bind.
+ */
+ void bindByIndex(in unsigned long aIndex,
+ in nsIVariant aValue);
+ [noscript] void bindUTF8StringByIndex(in unsigned long aIndex,
+ in AUTF8String aValue);
+ [noscript] void bindStringByIndex(in unsigned long aIndex,
+ in AString aValue);
+ [noscript] void bindDoubleByIndex(in unsigned long aIndex,
+ in double aValue);
+ [noscript] void bindInt32ByIndex(in unsigned long aIndex,
+ in long aValue);
+ [noscript] void bindInt64ByIndex(in unsigned long aIndex,
+ in long long aValue);
+ [noscript] void bindNullByIndex(in unsigned long aIndex);
+ void bindBlobByIndex(in unsigned long aIndex,
+ [array, const, size_is(aValueSize)] in octet aValue,
+ in unsigned long aValueSize);
+
+ // Convenience routines for storing strings as blobs.
+ void bindStringAsBlobByIndex(in unsigned long aIndex, in AString aValue);
+ void bindUTF8StringAsBlobByIndex(in unsigned long aIndex, in AUTF8String aValue);
+
+ // The function adopts the storage for the provided blob. After calling
+ // this function, mozStorage will ensure that free is called on the
+ // underlying pointer.
+ [noscript]
+ void bindAdoptedBlobByIndex(in unsigned long aIndex,
+ [array, size_is(aValueSize)] in octet aValue,
+ in unsigned long aValueSize);
+};
diff --git a/components/storage/public/mozIStorageBindingParamsArray.idl b/components/storage/public/mozIStorageBindingParamsArray.idl
new file mode 100644
index 000000000..5f504c051
--- /dev/null
+++ b/components/storage/public/mozIStorageBindingParamsArray.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIStorageBindingParams;
+
+[scriptable, uuid(67eea5c3-4881-41ff-b0fe-09f2356aeadb)]
+interface mozIStorageBindingParamsArray : nsISupports {
+ /**
+ * Creates a new mozIStorageBindingParams object that can be added to this
+ * array.
+ *
+ * @return a mozIStorageBindingParams object that can be used to specify
+ * parameters that need to be bound.
+ */
+ mozIStorageBindingParams newBindingParams();
+
+ /**
+ * Adds the parameters to the end of this array.
+ *
+ * @param aParameters
+ * The parameters to add to this array.
+ */
+ void addParams(in mozIStorageBindingParams aParameters);
+
+ /**
+ * The number of mozIStorageBindingParams this object contains.
+ */
+ readonly attribute unsigned long length;
+};
diff --git a/components/storage/public/mozIStorageCompletionCallback.idl b/components/storage/public/mozIStorageCompletionCallback.idl
new file mode 100644
index 000000000..1c31cc2c2
--- /dev/null
+++ b/components/storage/public/mozIStorageCompletionCallback.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, function, uuid(8cbf2dc2-91e0-44bc-984f-553638412071)]
+interface mozIStorageCompletionCallback : nsISupports {
+ /**
+ * Indicates that the event this callback was passed in for has completed.
+ *
+ * @param status
+ * The status of the call. Generally NS_OK if the operation
+ * completed successfully.
+ * @param value
+ * If the operation produces a result, the result. Otherwise,
+ * |null|.
+ *
+ * @see The calling method for expected values.
+ */
+ void complete(in nsresult status, [optional] in nsISupports value);
+};
diff --git a/components/storage/public/mozIStorageConnection.idl b/components/storage/public/mozIStorageConnection.idl
new file mode 100644
index 000000000..11d8aa5ac
--- /dev/null
+++ b/components/storage/public/mozIStorageConnection.idl
@@ -0,0 +1,268 @@
+/* -*- Mode: idl; 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"
+#include "mozIStorageAsyncConnection.idl"
+
+%{C++
+namespace mozilla {
+namespace dom {
+namespace quota {
+class QuotaObject;
+}
+}
+}
+
+%}
+
+[ptr] native QuotaObject(mozilla::dom::quota::QuotaObject);
+
+interface mozIStorageAggregateFunction;
+interface mozIStorageCompletionCallback;
+interface mozIStorageFunction;
+interface mozIStorageProgressHandler;
+interface mozIStorageBaseStatement;
+interface mozIStorageStatement;
+interface mozIStorageAsyncStatement;
+interface mozIStorageStatementCallback;
+interface mozIStoragePendingStatement;
+interface nsIFile;
+
+/**
+ * mozIStorageConnection represents a database connection attached to
+ * a specific file or to the in-memory data storage. It is the
+ * primary interface for interacting with a database, including
+ * creating prepared statements, executing SQL, and examining database
+ * errors.
+ *
+ * @note From the main thread, you should rather use mozIStorageAsyncConnection.
+ *
+ * @threadsafe
+ */
+[scriptable, uuid(4aa2ac47-8d24-4004-9b31-ec0bd85f0cc3)]
+interface mozIStorageConnection : mozIStorageAsyncConnection {
+ /**
+ * Closes a database connection. Callers must finalize all statements created
+ * for this connection prior to calling this method. It is illegal to use
+ * call this method if any asynchronous statements have been executed on this
+ * connection.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * If any statement has been executed asynchronously on this object.
+ * @throws NS_ERROR_UNEXPECTED
+ * If is called on a thread other than the one that opened it.
+ */
+ void close();
+
+ /**
+ * Clones a database connection and makes the clone read only if needed.
+ * SQL Functions and attached on-disk databases are applied to the new clone.
+ *
+ * @param aReadOnly
+ * If true, the returned database should be put into read-only mode.
+ * Defaults to false.
+ * @return the cloned database connection.
+ *
+ * @throws NS_ERROR_UNEXPECTED
+ * If this connection is a memory database.
+ * @note If your connection is already read-only, you will get a read-only
+ * clone.
+ * @note Due to a bug in SQLite, if you use the shared cache (openDatabase),
+ * you end up with the same privileges as the first connection opened
+ * regardless of what is specified in aReadOnly.
+ * @note The following pragmas are copied over to a read-only clone:
+ * - cache_size
+ * - temp_store
+ * The following pragmas are copied over to a writeable clone:
+ * - cache_size
+ * - temp_store
+ * - foreign_keys
+ * - journal_size_limit
+ * - synchronous
+ * - wal_autocheckpoint
+ *
+ */
+ mozIStorageConnection clone([optional] in boolean aReadOnly);
+
+ /**
+ * The default size for SQLite database pages used by mozStorage for new
+ * databases.
+ */
+ readonly attribute long defaultPageSize;
+
+ /**
+ * Indicates if the connection is open and ready to use. This will be false
+ * if the connection failed to open, or it has been closed.
+ */
+ readonly attribute boolean connectionReady;
+
+ /**
+ * lastInsertRowID returns the row ID from the last INSERT
+ * operation.
+ */
+ readonly attribute long long lastInsertRowID;
+
+ /**
+ * affectedRows returns the number of database rows that were changed or
+ * inserted or deleted by last operation.
+ */
+ readonly attribute long affectedRows;
+
+ /**
+ * The last error SQLite error code.
+ */
+ readonly attribute long lastError;
+
+ /**
+ * The last SQLite error as a string (in english, straight from the
+ * sqlite library).
+ */
+ readonly attribute AUTF8String lastErrorString;
+
+ /**
+ * The schema version of the database. This should not be used until the
+ * database is ready. The schema will be reported as zero if it is not set.
+ */
+ attribute long schemaVersion;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Statement creation
+
+ /**
+ * Create a mozIStorageStatement for the given SQL expression. The
+ * expression may use ? to indicate sequential numbered arguments,
+ * ?1, ?2 etc. to indicate specific numbered arguments or :name and
+ * $var to indicate named arguments.
+ *
+ * @param aSQLStatement
+ * The SQL statement to execute.
+ * @return a new mozIStorageStatement
+ */
+ mozIStorageStatement createStatement(in AUTF8String aSQLStatement);
+
+ /**
+ * Execute a SQL expression, expecting no arguments.
+ *
+ * @param aSQLStatement The SQL statement to execute
+ */
+ void executeSimpleSQL(in AUTF8String aSQLStatement);
+
+ /**
+ * Check if the given table exists.
+ *
+ * @param aTableName
+ * The table to check
+ * @return TRUE if table exists, FALSE otherwise.
+ */
+ boolean tableExists(in AUTF8String aTableName);
+
+ /**
+ * Check if the given index exists.
+ *
+ * @param aIndexName The index to check
+ * @return TRUE if the index exists, FALSE otherwise.
+ */
+ boolean indexExists(in AUTF8String aIndexName);
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Transactions
+
+ /**
+ * Returns true if a transaction is active on this connection.
+ */
+ readonly attribute boolean transactionInProgress;
+
+ /**
+ * Begin a new transaction. sqlite default transactions are deferred.
+ * If a transaction is active, throws an error.
+ */
+ void beginTransaction();
+
+ /**
+ * Begins a new transaction with the given type.
+ */
+ const int32_t TRANSACTION_DEFERRED = 0;
+ const int32_t TRANSACTION_IMMEDIATE = 1;
+ const int32_t TRANSACTION_EXCLUSIVE = 2;
+ void beginTransactionAs(in int32_t transactionType);
+
+ /**
+ * Commits the current transaction. If no transaction is active,
+ * @throws NS_ERROR_UNEXPECTED.
+ * @throws NS_ERROR_NOT_INITIALIZED.
+ */
+ void commitTransaction();
+
+ /**
+ * Rolls back the current transaction. If no transaction is active,
+ * @throws NS_ERROR_UNEXPECTED.
+ * @throws NS_ERROR_NOT_INITIALIZED.
+ */
+ void rollbackTransaction();
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Tables
+
+ /**
+ * Create the table with the given name and schema.
+ *
+ * If the table already exists, NS_ERROR_FAILURE is thrown.
+ * (XXX at some point in the future it will check if the schema is
+ * the same as what is specified, but that doesn't happen currently.)
+ *
+ * @param aTableName
+ * The table name to be created, consisting of [A-Za-z0-9_], and
+ * beginning with a letter.
+ * @param aTableSchema
+ * The schema of the table; what would normally go between the parens
+ * in a CREATE TABLE statement: e.g., "foo INTEGER, bar STRING".
+ *
+ * @throws NS_ERROR_FAILURE
+ * If the table already exists or could not be created for any other
+ * reason.
+ */
+ void createTable(in string aTableName,
+ in string aTableSchema);
+
+ /**
+ * Controls SQLITE_FCNTL_CHUNK_SIZE setting in sqlite. This helps avoid fragmentation
+ * by growing/shrinking the database file in SQLITE_FCNTL_CHUNK_SIZE increments. To
+ * conserve memory on systems short on storage space, this function will have no effect
+ * on mobile devices or if less than 500MiB of space is left available.
+ *
+ * @param aIncrement
+ * The database file will grow in multiples of chunkSize.
+ * @param aDatabaseName
+ * Sqlite database name. "" means pass NULL for zDbName to sqlite3_file_control.
+ * See http://sqlite.org/c3ref/file_control.html for more details.
+ * @throws NS_ERROR_FILE_TOO_BIG
+ * If the system is short on storage space.
+ */
+ void setGrowthIncrement(in int32_t aIncrement, in AUTF8String aDatabaseName);
+
+ /**
+ * Enable a predefined virtual table implementation.
+ *
+ * @param aModuleName
+ * The module to enable. Only "filesystem" is currently supported.
+ *
+ * @throws NS_ERROR_FAILURE
+ * For unknown module names.
+ */
+ [noscript] void enableModule(in ACString aModuleName);
+
+ /**
+ * Get quota objects.
+ *
+ * @param[out] aDatabaseQuotaObject
+ * The QuotaObject associated with the database file.
+ * @param[out] aJournalQuotaObject
+ * The QuotaObject associated with the journal file.
+ *
+ * @throws NS_ERROR_NOT_INITIALIZED.
+ */
+ [noscript] void getQuotaObjects(out QuotaObject aDatabaseQuotaObject,
+ out QuotaObject aJournalQuotaObject);
+};
diff --git a/components/storage/public/mozIStorageError.idl b/components/storage/public/mozIStorageError.idl
new file mode 100644
index 000000000..7707a81dc
--- /dev/null
+++ b/components/storage/public/mozIStorageError.idl
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"
+
+%{C++
+#ifdef ERROR
+#undef ERROR
+#endif
+%}
+
+[scriptable, uuid(1f350f96-7023-434a-8864-40a1c493aac1)]
+interface mozIStorageError : nsISupports {
+
+ /**
+ * General SQL error or missing database.
+ */
+ const long ERROR = 1;
+
+ /**
+ * Internal logic error.
+ */
+ const long INTERNAL = 2;
+
+ /**
+ * Access permission denied.
+ */
+ const long PERM = 3;
+
+ /**
+ * A callback routine requested an abort.
+ */
+ const long ABORT = 4;
+
+ /**
+ * The database file is locked.
+ */
+ const long BUSY = 5;
+
+ /**
+ * A table in the database is locked.
+ */
+ const long LOCKED = 6;
+
+ /**
+ * An allocation failed.
+ */
+ const long NOMEM = 7;
+
+ /**
+ * Attempt to write to a readonly database.
+ */
+ const long READONLY = 8;
+
+ /**
+ * Operation was terminated by an interrupt.
+ */
+ const long INTERRUPT = 9;
+
+ /**
+ * Some kind of disk I/O error occurred.
+ */
+ const long IOERR = 10;
+
+ /**
+ * The database disk image is malformed.
+ */
+ const long CORRUPT = 11;
+
+ /**
+ * An insertion failed because the database is full.
+ */
+ const long FULL = 13;
+
+ /**
+ * Unable to open the database file.
+ */
+ const long CANTOPEN = 14;
+
+ /**
+ * The database is empty.
+ */
+ const long EMPTY = 16;
+
+ /**
+ * The database scheme changed.
+ */
+ const long SCHEMA = 17;
+
+ /**
+ * A string or blob exceeds the size limit.
+ */
+ const long TOOBIG = 18;
+
+ /**
+ * Abort due to a constraint violation.
+ */
+ const long CONSTRAINT = 19;
+
+ /**
+ * Data type mismatch.
+ */
+ const long MISMATCH = 20;
+
+ /**
+ * Library used incorrectly.
+ */
+ const long MISUSE = 21;
+
+ /**
+ * Uses OS features not supported on the host system.
+ */
+ const long NOLFS = 22;
+
+ /**
+ * Authorization denied.
+ */
+ const long AUTH = 23;
+
+ /**
+ * Auxiliary database format error.
+ */
+ const long FORMAT = 24;
+
+ /**
+ * Attempt to bind a parameter using an out-of-range index or nonexistent
+ * named parameter name.
+ */
+ const long RANGE = 25;
+
+ /**
+ * File opened that is not a database file.
+ */
+ const long NOTADB = 26;
+
+
+ /**
+ * Indicates what type of error occurred.
+ */
+ readonly attribute long result;
+
+ /**
+ * An error string the gives more details, if available.
+ */
+ readonly attribute AUTF8String message;
+};
diff --git a/components/storage/public/mozIStorageFunction.idl b/components/storage/public/mozIStorageFunction.idl
new file mode 100644
index 000000000..7f9878377
--- /dev/null
+++ b/components/storage/public/mozIStorageFunction.idl
@@ -0,0 +1,40 @@
+/* -*- 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"
+
+#include "mozIStorageValueArray.idl"
+
+interface mozIStorageConnection;
+interface nsIArray;
+interface nsIVariant;
+
+/**
+ * mozIStorageFunction is to be implemented by storage consumers that
+ * wish to receive callbacks during the request execution.
+ *
+ * SQL can apply functions to values from tables. Examples of
+ * such functions are MIN(a1,a2) or SQRT(num). Many functions are
+ * implemented in SQL engine.
+ *
+ * This interface allows consumers to implement their own,
+ * problem-specific functions.
+ * These functions can be called from triggers, too.
+ *
+ */
+[scriptable, function, uuid(9ff02465-21cb-49f3-b975-7d5b38ceec73)]
+interface mozIStorageFunction : nsISupports {
+ /**
+ * onFunctionCall is called when execution of a custom
+ * function should occur.
+ *
+ * @param aNumArguments The number of arguments
+ * @param aFunctionArguments The arguments passed in to the function
+ *
+ * @returns any value as Variant type.
+ */
+
+ nsIVariant onFunctionCall(in mozIStorageValueArray aFunctionArguments);
+};
diff --git a/components/storage/public/mozIStoragePendingStatement.idl b/components/storage/public/mozIStoragePendingStatement.idl
new file mode 100644
index 000000000..a72ce96b9
--- /dev/null
+++ b/components/storage/public/mozIStoragePendingStatement.idl
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"
+
+[scriptable, uuid(00da7d20-3768-4398-bedc-e310c324b3f0)]
+interface mozIStoragePendingStatement : nsISupports {
+
+ /**
+ * Cancels a pending statement, if possible. This will only fail if you try
+ * cancel more than once.
+ *
+ * @note For read statements (such as SELECT), you will no longer receive any
+ * notifications about results once cancel is called.
+ */
+ void cancel();
+};
diff --git a/components/storage/public/mozIStorageProgressHandler.idl b/components/storage/public/mozIStorageProgressHandler.idl
new file mode 100644
index 000000000..14ebb55ed
--- /dev/null
+++ b/components/storage/public/mozIStorageProgressHandler.idl
@@ -0,0 +1,26 @@
+/* -*- 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 mozIStorageConnection;
+
+/**
+ * mozIProgressHandler is to be implemented by storage consumers that
+ * wish to receive callbacks during the request execution.
+ */
+[scriptable, uuid(a3a6fcd4-bf89-4208-a837-bf2a73afd30c)]
+interface mozIStorageProgressHandler : nsISupports {
+ /**
+ * onProgress is invoked periodically during long running calls.
+ *
+ * @param aConnection connection, for which progress handler is
+ * invoked.
+ *
+ * @return true to abort request, false to continue work.
+ */
+
+ boolean onProgress(in mozIStorageConnection aConnection);
+};
diff --git a/components/storage/public/mozIStorageResultSet.idl b/components/storage/public/mozIStorageResultSet.idl
new file mode 100644
index 000000000..de63b297b
--- /dev/null
+++ b/components/storage/public/mozIStorageResultSet.idl
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 mozIStorageRow;
+
+[scriptable, uuid(18dd7953-076d-4598-8105-3e32ad26ab24)]
+interface mozIStorageResultSet : nsISupports {
+
+ /**
+ * Obtains the next row from the result set from the statement that was
+ * executed.
+ *
+ * @returns the next row from the result set. This will be null when there
+ * are no more results.
+ */
+ mozIStorageRow getNextRow();
+};
diff --git a/components/storage/public/mozIStorageRow.idl b/components/storage/public/mozIStorageRow.idl
new file mode 100644
index 000000000..ce12d77cc
--- /dev/null
+++ b/components/storage/public/mozIStorageRow.idl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozIStorageValueArray.idl"
+interface nsIVariant;
+
+[scriptable, uuid(62d1b6bd-cbfe-4f9b-aee1-0ead4af4e6dc)]
+interface mozIStorageRow : mozIStorageValueArray {
+
+ /**
+ * Obtains the result of a given column specified by aIndex.
+ *
+ * @param aIndex
+ * Zero-based index of the result to get from the tuple.
+ * @returns the result of the specified column.
+ */
+ nsIVariant getResultByIndex(in unsigned long aIndex);
+
+ /**
+ * Obtains the result of a given column specified by aName.
+ *
+ * @param aName
+ * Name of the result to get from the tuple.
+ * @returns the result of the specified column.
+ * @note The name of a result column is the value of the "AS" clause for that
+ * column. If there is no AS clause then the name of the column is
+ * unspecified and may change from one release to the next.
+ */
+ nsIVariant getResultByName(in AUTF8String aName);
+};
diff --git a/components/storage/public/mozIStorageService.idl b/components/storage/public/mozIStorageService.idl
new file mode 100644
index 000000000..56d2a127e
--- /dev/null
+++ b/components/storage/public/mozIStorageService.idl
@@ -0,0 +1,193 @@
+/* -*- Mode: idl; 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 mozIStorageConnection;
+interface nsIFile;
+interface nsIFileURL;
+interface nsIPropertyBag2;
+interface nsIVariant;
+interface mozIStorageCompletionCallback;
+
+/**
+ * The mozIStorageService interface is intended to be implemented by
+ * a service that can create storage connections (mozIStorageConnection)
+ * to either a well-known profile database or to a specific database file.
+ *
+ * This is the only way to open a database connection.
+ *
+ * @note The first reference to mozIStorageService must be made on the main
+ * thread.
+ */
+[scriptable, uuid(07b6b2f5-6d97-47b4-9584-e65bc467fe9e)]
+interface mozIStorageService : nsISupports {
+ /**
+ * Open an asynchronous connection to a database.
+ *
+ * This method MUST be called from the main thread. The connection object
+ * returned by this function is not threadsafe. You MUST use it only from
+ * the main thread.
+ *
+ * If you have more than one connection to a file, you MUST use the EXACT
+ * SAME NAME for the file each time, including case. The sqlite code uses
+ * a simple string compare to see if there is already a connection. Opening
+ * a connection to "Foo.sqlite" and "foo.sqlite" will CORRUPT YOUR DATABASE.
+ *
+ * @param aDatabaseStore Either a nsIFile representing the file that contains
+ * the database or a special string to open a special database. The special
+ * string may be:
+ * - "memory" to open an in-memory database.
+ *
+ * @param aOptions A set of options (may be null). Options may contain:
+ * - bool shared (defaults to |false|).
+ * -- If |true|, opens the database with a shared-cache. The
+ * shared-cache mode is more memory-efficient when many
+ * connections to the same database are expected, though, the
+ * connections will contend the cache resource. In any cases
+ * where performance matter, working without a shared-cache will
+ * improve concurrency. @see openUnsharedDatabase
+ *
+ * - int growthIncrement (defaults to none).
+ * -- Set the growth increment for the main database. This hints SQLite to
+ * grow the database file by a given chunk size and may reduce
+ * filesystem fragmentation on large databases.
+ * @see mozIStorageConnection::setGrowthIncrement
+ *
+ * @param aCallback A callback that will receive the result of the operation.
+ * In case of error, it may receive as status:
+ * - NS_ERROR_OUT_OF_MEMORY if allocating a new storage object fails.
+ * - NS_ERROR_FILE_CORRUPTED if the database file is corrupted.
+ * In case of success, it receives as argument the new database
+ * connection, as an instance of |mozIStorageAsyncConnection|.
+ *
+ * @throws NS_ERROR_INVALID_ARG if |aDatabaseStore| is neither a file nor
+ * one of the special strings understood by this method, or if one of
+ * the options passed through |aOptions| does not have the right type.
+ * @throws NS_ERROR_NOT_SAME_THREAD if called from a thread other than the
+ * main thread.
+ */
+ void openAsyncDatabase(in nsIVariant aDatabaseStore,
+ [optional] in nsIPropertyBag2 aOptions,
+ in mozIStorageCompletionCallback aCallback);
+ /**
+ * Get a connection to a named special database storage.
+ *
+ * @param aStorageKey a string key identifying the type of storage
+ * requested. Valid values include: "memory".
+ *
+ * @see openDatabase for restrictions on how database connections may be
+ * used. For the profile database, you should only access it from the main
+ * thread since other callers may also have connections.
+ *
+ * @returns a new mozIStorageConnection for the requested
+ * storage database.
+ *
+ * @throws NS_ERROR_INVALID_ARG if aStorageKey is invalid.
+ */
+ mozIStorageConnection openSpecialDatabase(in string aStorageKey);
+
+ /**
+ * Open a connection to the specified file.
+ *
+ * Consumers should check mozIStorageConnection::connectionReady to ensure
+ * that they can use the database. If this value is false, it is strongly
+ * recommended that the database be backed up with
+ * mozIStorageConnection::backupDB so user data is not lost.
+ *
+ * ==========
+ * DANGER
+ * ==========
+ *
+ * If you have more than one connection to a file, you MUST use the EXACT
+ * SAME NAME for the file each time, including case. The sqlite code uses
+ * a simple string compare to see if there is already a connection. Opening
+ * a connection to "Foo.sqlite" and "foo.sqlite" will CORRUPT YOUR DATABASE.
+ *
+ * The connection object returned by this function is not threadsafe. You must
+ * use it only from the thread you created it from.
+ *
+ * @param aDatabaseFile
+ * A nsIFile that represents the database that is to be opened..
+ *
+ * @returns a mozIStorageConnection for the requested database file.
+ *
+ * @throws NS_ERROR_OUT_OF_MEMORY
+ * If allocating a new storage object fails.
+ * @throws NS_ERROR_FILE_CORRUPTED
+ * If the database file is corrupted.
+ */
+ mozIStorageConnection openDatabase(in nsIFile aDatabaseFile);
+
+ /**
+ * Open a connection to the specified file that doesn't share a sqlite cache.
+ *
+ * Without a shared-cache, each connection uses its own pages cache, which
+ * may be memory inefficient with a large number of connections, in such a
+ * case so you should use openDatabase instead. On the other side, if cache
+ * contention may be an issue, for instance when concurrency is important to
+ * ensure responsiveness, using unshared connections may be a performance win.
+ *
+ * ==========
+ * DANGER
+ * ==========
+ *
+ * If you have more than one connection to a file, you MUST use the EXACT
+ * SAME NAME for the file each time, including case. The sqlite code uses
+ * a simple string compare to see if there is already a connection. Opening
+ * a connection to "Foo.sqlite" and "foo.sqlite" will CORRUPT YOUR DATABASE.
+ *
+ * The connection object returned by this function is not threadsafe. You must
+ * use it only from the thread you created it from.
+ *
+ * @param aDatabaseFile
+ * A nsIFile that represents the database that is to be opened.
+ *
+ * @returns a mozIStorageConnection for the requested database file.
+ *
+ * @throws NS_ERROR_OUT_OF_MEMORY
+ * If allocating a new storage object fails.
+ * @throws NS_ERROR_FILE_CORRUPTED
+ * If the database file is corrupted.
+ */
+ mozIStorageConnection openUnsharedDatabase(in nsIFile aDatabaseFile);
+
+ /**
+ * See openDatabase(). Exactly the same only initialized with a file URL.
+ * Custom parameters can be passed to SQLite and VFS implementations through
+ * the query part of the URL.
+ *
+ * @param aURL
+ * A nsIFileURL that represents the database that is to be opened.
+ */
+ mozIStorageConnection openDatabaseWithFileURL(in nsIFileURL aFileURL);
+
+ /*
+ * Utilities
+ */
+
+ /**
+ * Copies the specified database file to the specified parent directory with
+ * the specified file name. If the parent directory is not specified, it
+ * places the backup in the same directory as the current file. This function
+ * ensures that the file being created is unique.
+ *
+ * @param aDBFile
+ * The database file that will be backed up.
+ * @param aBackupFileName
+ * The name of the new backup file to create.
+ * @param [optional] aBackupParentDirectory
+ * The directory you'd like the backup file to be placed.
+ * @return The nsIFile representing the backup file.
+ */
+ nsIFile backupDatabaseFile(in nsIFile aDBFile, in AString aBackupFileName,
+ [optional] in nsIFile aBackupParentDirectory);
+};
+
+%{C++
+
+#define MOZ_STORAGE_MEMORY_STORAGE_KEY "memory"
+
+%}
diff --git a/components/storage/public/mozIStorageStatement.idl b/components/storage/public/mozIStorageStatement.idl
new file mode 100644
index 000000000..a264cfdfa
--- /dev/null
+++ b/components/storage/public/mozIStorageStatement.idl
@@ -0,0 +1,307 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "mozIStorageBaseStatement.idl"
+%{C++
+#include "mozilla/DebugOnly.h"
+%}
+
+[ptr] native octetPtr(uint8_t);
+
+/**
+ * A SQL statement that can be used for both synchronous and asynchronous
+ * purposes.
+ */
+[scriptable, uuid(5f567c35-6c32-4140-828c-683ea49cfd3a)]
+interface mozIStorageStatement : mozIStorageBaseStatement {
+ /**
+ * Create a clone of this statement, by initializing a new statement
+ * with the same connection and same SQL statement as this one. It
+ * does not preserve statement state; that is, if a statement is
+ * being executed when it is cloned, the new statement will not be
+ * executing.
+ */
+ mozIStorageStatement clone();
+
+ /*
+ * Number of parameters
+ */
+ readonly attribute unsigned long parameterCount;
+
+ /**
+ * Name of nth parameter, if given
+ */
+ AUTF8String getParameterName(in unsigned long aParamIndex);
+
+ /**
+ * Returns the index of the named parameter.
+ *
+ * @param aName
+ * The name of the parameter you want the index for. This does not
+ * include the leading ':'.
+ * @return the index of the named parameter.
+ */
+ unsigned long getParameterIndex(in AUTF8String aName);
+
+ /**
+ * Number of columns returned
+ */
+ readonly attribute unsigned long columnCount;
+
+ /**
+ * Name of nth column
+ */
+ AUTF8String getColumnName(in unsigned long aColumnIndex);
+
+ /**
+ * Obtains the index of the column with the specified name.
+ *
+ * @param aName
+ * The name of the column.
+ * @return The index of the column with the specified name.
+ */
+ unsigned long getColumnIndex(in AUTF8String aName);
+
+ /**
+ * Reset parameters/statement execution
+ */
+ void reset();
+
+ /**
+ * Execute the query, ignoring any results. This is accomplished by
+ * calling executeStep() once, and then calling reset().
+ *
+ * Error and last insert info, etc. are available from
+ * the mozStorageConnection.
+ */
+ void execute();
+
+ /**
+ * Execute a query, using any currently-bound parameters. Reset
+ * must be called on the statement after the last call of
+ * executeStep.
+ *
+ * @return a boolean indicating whether there are more rows or not;
+ * row data may be accessed using mozIStorageValueArray methods on
+ * the statement.
+ */
+ boolean executeStep();
+
+ /**
+ * Execute a query, using any currently-bound parameters. Reset is called
+ * when no more data is returned. This method is only available to JavaScript
+ * consumers.
+ *
+ * @deprecated As of Mozilla 1.9.2 in favor of executeStep().
+ *
+ * @return a boolean indicating whether there are more rows or not.
+ *
+ * [deprecated] boolean step();
+ */
+
+ /**
+ * Obtains the current list of named parameters, which are settable. This
+ * property is only available to JavaScript consumers.
+ *
+ * readonly attribute mozIStorageStatementParams params;
+ */
+
+ /**
+ * Obtains the current row, with access to all the data members by name. This
+ * property is only available to JavaScript consumers.
+ *
+ * readonly attribute mozIStorageStatementRow row;
+ */
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Copied contents of mozIStorageValueArray
+
+ /**
+ * These type values are returned by getTypeOfIndex
+ * to indicate what type of value is present at
+ * a given column.
+ */
+ const long VALUE_TYPE_NULL = 0;
+ const long VALUE_TYPE_INTEGER = 1;
+ const long VALUE_TYPE_FLOAT = 2;
+ const long VALUE_TYPE_TEXT = 3;
+ const long VALUE_TYPE_BLOB = 4;
+
+ /**
+ * The number of entries in the array (each corresponding to a column in the
+ * database row)
+ */
+ readonly attribute unsigned long numEntries;
+
+ /**
+ * Indicate the data type of the current result row for the the given column.
+ * SQLite will perform type conversion if you ask for a value as a different
+ * type than it is stored as.
+ *
+ * @param aIndex
+ * 0-based column index.
+ * @return The type of the value at the given column index; one of
+ * VALUE_TYPE_NULL, VALUE_TYPE_INTEGER, VALUE_TYPE_FLOAT,
+ * VALUE_TYPE_TEXT, VALUE_TYPE_BLOB.
+ */
+ long getTypeOfIndex(in unsigned long aIndex);
+
+ /**
+ * Retrieve the contents of a column from the current result row as an
+ * integer.
+ *
+ * @param aIndex
+ * 0-based colummn index.
+ * @return Column value interpreted as an integer per type conversion rules.
+ * @{
+ */
+ long getInt32(in unsigned long aIndex);
+ long long getInt64(in unsigned long aIndex);
+ /** @} */
+ /**
+ * Retrieve the contents of a column from the current result row as a
+ * floating point double.
+ *
+ * @param aIndex
+ * 0-based colummn index.
+ * @return Column value interpreted as a double per type conversion rules.
+ */
+ double getDouble(in unsigned long aIndex);
+ /**
+ * Retrieve the contents of a column from the current result row as a
+ * string.
+ *
+ * @param aIndex
+ * 0-based colummn index.
+ * @return The value for the result column interpreted as a string. If the
+ * stored value was NULL, you will get an empty string with IsVoid set
+ * to distinguish it from an explicitly set empty string.
+ * @{
+ */
+ AUTF8String getUTF8String(in unsigned long aIndex);
+ AString getString(in unsigned long aIndex);
+ /** @} */
+
+ /**
+ * Retrieve the contents of a column from the current result row as a
+ * blob.
+ *
+ * @param aIndex
+ * 0-based colummn index.
+ * @param[out] aDataSize
+ * The number of bytes in the blob.
+ * @param[out] aData
+ * The contents of the BLOB. This will be NULL if aDataSize == 0.
+ */
+ void getBlob(in unsigned long aIndex, out unsigned long aDataSize, [array,size_is(aDataSize)] out octet aData);
+
+ /**
+ * Retrieve the contents of a Blob column from the current result row as a
+ * string.
+ *
+ * @param aIndex
+ * 0-based colummn index.
+ * @return The value for the result Blob column interpreted as a String.
+ * No encoding conversion is performed.
+ */
+ AString getBlobAsString(in unsigned long aIndex);
+
+ /**
+ * Retrieve the contents of a Blob column from the current result row as a
+ * UTF8 string.
+ *
+ * @param aIndex
+ * 0-based colummn index.
+ * @return The value for the result Blob column interpreted as a UTF8 String.
+ * No encoding conversion is performed.
+ */
+ AUTF8String getBlobAsUTF8String(in unsigned long aIndex);
+
+ /**
+ * Check whether the given column in the current result row is NULL.
+ *
+ * @param aIndex
+ * 0-based colummn index.
+ * @return true if the value for the result column is null.
+ */
+ boolean getIsNull(in unsigned long aIndex);
+
+ /**
+ * Returns a shared string pointer
+ */
+ [noscript] void getSharedUTF8String(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out string aResult);
+ [noscript] void getSharedString(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out wstring aResult);
+ [noscript] void getSharedBlob(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out octetPtr aResult);
+
+%{C++
+ /**
+ * Getters for native code that return their values as
+ * the return type, for convenience and sanity.
+ *
+ * Not virtual; no vtable bloat.
+ */
+
+ inline int32_t AsInt32(uint32_t idx) {
+ int32_t v = 0;
+ mozilla::DebugOnly<nsresult> rv = GetInt32(idx, &v);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return v;
+ }
+
+ inline int64_t AsInt64(uint32_t idx) {
+ int64_t v = 0;
+ mozilla::DebugOnly<nsresult> rv = GetInt64(idx, &v);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return v;
+ }
+
+ inline double AsDouble(uint32_t idx) {
+ double v = 0.0;
+ mozilla::DebugOnly<nsresult> rv = GetDouble(idx, &v);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return v;
+ }
+
+ inline const char* AsSharedUTF8String(uint32_t idx, uint32_t *len) {
+ const char *str = nullptr;
+ *len = 0;
+ mozilla::DebugOnly<nsresult> rv = GetSharedUTF8String(idx, len, &str);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return str;
+ }
+
+ inline const char16_t* AsSharedWString(uint32_t idx, uint32_t *len) {
+ const char16_t *str = nullptr;
+ *len = 0;
+ mozilla::DebugOnly<nsresult> rv = GetSharedString(idx, len, &str);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return str;
+ }
+
+ inline const uint8_t* AsSharedBlob(uint32_t idx, uint32_t *len) {
+ const uint8_t *blob = nullptr;
+ *len = 0;
+ mozilla::DebugOnly<nsresult> rv = GetSharedBlob(idx, len, &blob);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return blob;
+ }
+
+ inline bool IsNull(uint32_t idx) {
+ bool b = false;
+ mozilla::DebugOnly<nsresult> rv = GetIsNull(idx, &b);
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "Getting value failed, wrong column index?");
+ return b;
+ }
+
+%}
+};
diff --git a/components/storage/public/mozIStorageStatementCallback.idl b/components/storage/public/mozIStorageStatementCallback.idl
new file mode 100644
index 000000000..3c7bd6f6f
--- /dev/null
+++ b/components/storage/public/mozIStorageStatementCallback.idl
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 mozIStorageResultSet;
+interface mozIStorageError;
+
+[scriptable, uuid(29383d00-d8c4-4ddd-9f8b-c2feb0f2fcfa)]
+interface mozIStorageStatementCallback : nsISupports {
+
+ /**
+ * Called when some result is obtained from the database. This function can
+ * be called more than once with a different storageIResultSet each time for
+ * any given asynchronous statement.
+ *
+ * @param aResultSet
+ * The result set containing the data from the database.
+ */
+ void handleResult(in mozIStorageResultSet aResultSet);
+
+ /**
+ * Called when some error occurs while executing the statement. This function
+ * may be called more than once with a different storageIError each time for
+ * any given asynchronous statement.
+ *
+ * @param aError
+ * An object containing information about the error.
+ */
+ void handleError(in mozIStorageError aError);
+
+ /**
+ * Called when the statement has finished executing. This function will only
+ * be called once for any given asynchronous statement.
+ *
+ * @param aReason
+ * Indicates if the statement is no longer executing because it either
+ * finished (REASON_FINISHED), was canceled (REASON_CANCELED), or
+ * a fatal error occurred (REASON_ERROR).
+ */
+ const unsigned short REASON_FINISHED = 0;
+ const unsigned short REASON_CANCELED = 1;
+ const unsigned short REASON_ERROR = 2;
+ void handleCompletion(in unsigned short aReason);
+};
diff --git a/components/storage/public/mozIStorageStatementParams.idl b/components/storage/public/mozIStorageStatementParams.idl
new file mode 100644
index 000000000..efeee9772
--- /dev/null
+++ b/components/storage/public/mozIStorageStatementParams.idl
@@ -0,0 +1,11 @@
+/* -*- 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"
+
+[scriptable, uuid(e65fe6e2-2643-463c-97e2-27665efe2386)]
+interface mozIStorageStatementParams : nsISupports {
+ // Magic interface for parameter setting that implements nsIXPCScriptable.
+};
diff --git a/components/storage/public/mozIStorageStatementRow.idl b/components/storage/public/mozIStorageStatementRow.idl
new file mode 100644
index 000000000..8be1da7f1
--- /dev/null
+++ b/components/storage/public/mozIStorageStatementRow.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(02eeaf95-c3db-4182-9340-222c29f68f02)]
+interface mozIStorageStatementRow : nsISupports {
+ // Magic interface we return that implements nsIXPCScriptable, to allow
+ // for by-name access to rows.
+};
diff --git a/components/storage/public/mozIStorageVacuumParticipant.idl b/components/storage/public/mozIStorageVacuumParticipant.idl
new file mode 100644
index 000000000..a4e0f3a71
--- /dev/null
+++ b/components/storage/public/mozIStorageVacuumParticipant.idl
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIStorageConnection;
+
+/**
+ * This interface contains the information that the Storage service needs to
+ * vacuum a database. This interface is created as a service through the
+ * category manager with the category "vacuum-participant".
+ * Please see https://developer.mozilla.org/en/mozIStorageVacuumParticipant for
+ * more information.
+ */
+[scriptable, uuid(8f367508-1d9a-4d3f-be0c-ac11b6dd7dbf)]
+interface mozIStorageVacuumParticipant : nsISupports {
+ /**
+ * The expected page size in bytes for the database. The vacuum manager will
+ * try to correct the page size during idle based on this value.
+ *
+ * @note If the database is using the WAL journal mode, the page size won't
+ * be changed to the requested value. See bug 634374.
+ * @note Valid page size values are powers of 2 between 512 and 65536.
+ * The suggested value is mozIStorageConnection::defaultPageSize.
+ */
+ readonly attribute long expectedDatabasePageSize;
+
+ /**
+ * Connection to the database file to be vacuumed.
+ */
+ readonly attribute mozIStorageConnection databaseConnection;
+
+ /**
+ * Notifies when a vacuum operation begins. Listeners should avoid using the
+ * database till onEndVacuum is received.
+ *
+ * @return true to proceed with the vacuum, false if the participant wants to
+ * opt-out for now, it will be retried later. Useful when participant
+ * is running some other heavy operation that can't be interrupted.
+ *
+ * @note When a vacuum operation starts or ends it will also dispatch a global
+ * "heavy-io-task" notification through the observer service with the
+ * data argument being either "vacuum-begin" or "vacuum-end".
+ */
+ boolean onBeginVacuum();
+
+ /**
+ * Notifies when a vacuum operation ends.
+ *
+ * @param aSucceeded
+ * reports if the vacuum succeeded or failed.
+ */
+ void onEndVacuum(in boolean aSucceeded);
+};
diff --git a/components/storage/public/mozIStorageValueArray.idl b/components/storage/public/mozIStorageValueArray.idl
new file mode 100644
index 000000000..3dbf75285
--- /dev/null
+++ b/components/storage/public/mozIStorageValueArray.idl
@@ -0,0 +1,145 @@
+/* -*- 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"
+%{C++
+#include "mozilla/DebugOnly.h"
+%}
+
+[ptr] native octetPtr(uint8_t);
+
+/**
+ * mozIStorageValueArray wraps an array of SQL values, such as a single database
+ * row.
+ */
+[scriptable, uuid(6e6306f4-ffa7-40f5-96ca-36159ce8f431)]
+interface mozIStorageValueArray : nsISupports {
+ /**
+ * These type values are returned by getTypeOfIndex
+ * to indicate what type of value is present at
+ * a given column.
+ */
+ const long VALUE_TYPE_NULL = 0;
+ const long VALUE_TYPE_INTEGER = 1;
+ const long VALUE_TYPE_FLOAT = 2;
+ const long VALUE_TYPE_TEXT = 3;
+ const long VALUE_TYPE_BLOB = 4;
+
+ /**
+ * numEntries
+ *
+ * number of entries in the array (each corresponding to a column
+ * in the database row)
+ */
+ readonly attribute unsigned long numEntries;
+
+ /**
+ * Returns the type of the value at the given column index;
+ * one of VALUE_TYPE_NULL, VALUE_TYPE_INTEGER, VALUE_TYPE_FLOAT,
+ * VALUE_TYPE_TEXT, VALUE_TYPE_BLOB.
+ */
+ long getTypeOfIndex(in unsigned long aIndex);
+
+ /**
+ * Obtain a value for the given entry (column) index.
+ * Due to SQLite's type conversion rules, any of these are valid
+ * for any column regardless of the column's data type. However,
+ * if the specific type matters, getTypeOfIndex should be used
+ * first to identify the column type, and then the appropriate
+ * get method should be called.
+ *
+ * If you ask for a string value for a NULL column, you will get an empty
+ * string with IsVoid set to distinguish it from an explicitly set empty
+ * string.
+ */
+ long getInt32(in unsigned long aIndex);
+ long long getInt64(in unsigned long aIndex);
+ double getDouble(in unsigned long aIndex);
+ AUTF8String getUTF8String(in unsigned long aIndex);
+ AString getString(in unsigned long aIndex);
+
+ // data will be NULL if dataSize = 0
+ void getBlob(in unsigned long aIndex, out unsigned long aDataSize, [array,size_is(aDataSize)] out octet aData);
+ AString getBlobAsString(in unsigned long aIndex);
+ AUTF8String getBlobAsUTF8String(in unsigned long aIndex);
+ boolean getIsNull(in unsigned long aIndex);
+
+ /**
+ * Returns a shared string pointer
+ */
+ [noscript] void getSharedUTF8String(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out string aResult);
+ [noscript] void getSharedString(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out wstring aResult);
+ [noscript] void getSharedBlob(in unsigned long aIndex, out unsigned long aLength, [shared,retval] out octetPtr aResult);
+
+%{C++
+ /**
+ * Getters for native code that return their values as
+ * the return type, for convenience and sanity.
+ *
+ * Not virtual; no vtable bloat.
+ */
+
+ inline int32_t AsInt32(uint32_t idx) {
+ int32_t v = 0;
+ mozilla::DebugOnly<nsresult> rv = GetInt32(idx, &v);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return v;
+ }
+
+ inline int64_t AsInt64(uint32_t idx) {
+ int64_t v = 0;
+ mozilla::DebugOnly<nsresult> rv = GetInt64(idx, &v);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return v;
+ }
+
+ inline double AsDouble(uint32_t idx) {
+ double v = 0.0;
+ mozilla::DebugOnly<nsresult> rv = GetDouble(idx, &v);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return v;
+ }
+
+ inline const char* AsSharedUTF8String(uint32_t idx, uint32_t *len) {
+ const char *str = nullptr;
+ *len = 0;
+ mozilla::DebugOnly<nsresult> rv = GetSharedUTF8String(idx, len, &str);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return str;
+ }
+
+ inline const char16_t* AsSharedWString(uint32_t idx, uint32_t *len) {
+ const char16_t *str = nullptr;
+ *len = 0;
+ mozilla::DebugOnly<nsresult> rv = GetSharedString(idx, len, &str);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return str;
+ }
+
+ inline const uint8_t* AsSharedBlob(uint32_t idx, uint32_t *len) {
+ const uint8_t *blob = nullptr;
+ *len = 0;
+ mozilla::DebugOnly<nsresult> rv = GetSharedBlob(idx, len, &blob);
+ MOZ_ASSERT(NS_SUCCEEDED(rv) || IsNull(idx),
+ "Getting value failed, wrong column index?");
+ return blob;
+ }
+
+ inline bool IsNull(uint32_t idx) {
+ bool b = false;
+ mozilla::DebugOnly<nsresult> rv = GetIsNull(idx, &b);
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "Getting value failed, wrong column index?");
+ return b;
+ }
+
+%}
+
+};
diff --git a/components/storage/src/FileSystemModule.cpp b/components/storage/src/FileSystemModule.cpp
new file mode 100644
index 000000000..ed2f8cdef
--- /dev/null
+++ b/components/storage/src/FileSystemModule.cpp
@@ -0,0 +1,304 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileSystemModule.h"
+
+#include "sqlite3.h"
+#include "nsString.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIFile.h"
+
+namespace {
+
+struct VirtualTableCursorBase
+{
+ VirtualTableCursorBase()
+ {
+ memset(&mBase, 0, sizeof(mBase));
+ }
+
+ sqlite3_vtab_cursor mBase;
+};
+
+struct VirtualTableCursor : public VirtualTableCursorBase
+{
+public:
+ VirtualTableCursor()
+ : mRowId(-1)
+ {
+ mCurrentFileName.SetIsVoid(true);
+ }
+
+ const nsString& DirectoryPath() const
+ {
+ return mDirectoryPath;
+ }
+
+ const nsString& CurrentFileName() const
+ {
+ return mCurrentFileName;
+ }
+
+ int64_t RowId() const
+ {
+ return mRowId;
+ }
+
+ nsresult Init(const nsAString& aPath);
+ nsresult NextFile();
+
+private:
+ nsCOMPtr<nsISimpleEnumerator> mEntries;
+
+ nsString mDirectoryPath;
+ nsString mCurrentFileName;
+
+ int64_t mRowId;
+};
+
+nsresult
+VirtualTableCursor::Init(const nsAString& aPath)
+{
+ nsCOMPtr<nsIFile> directory =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE);
+
+ nsresult rv = directory->InitWithPath(aPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->GetPath(mDirectoryPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->GetDirectoryEntries(getter_AddRefs(mEntries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NextFile();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+VirtualTableCursor::NextFile()
+{
+ bool hasMore;
+ nsresult rv = mEntries->HasMoreElements(&hasMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasMore) {
+ mCurrentFileName.SetIsVoid(true);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupports> entry;
+ rv = mEntries->GetNext(getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+ NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
+
+ rv = file->GetLeafName(mCurrentFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRowId++;
+
+ return NS_OK;
+}
+
+int Connect(sqlite3* aDB, void* aAux, int aArgc, const char* const* aArgv,
+ sqlite3_vtab** aVtab, char** aErr)
+{
+ static const char virtualTableSchema[] =
+ "CREATE TABLE fs ("
+ "name TEXT, "
+ "path TEXT"
+ ")";
+
+ int rc = sqlite3_declare_vtab(aDB, virtualTableSchema);
+ if (rc != SQLITE_OK) {
+ return rc;
+ }
+
+ sqlite3_vtab* vt = new sqlite3_vtab();
+ memset(vt, 0, sizeof(*vt));
+
+ *aVtab = vt;
+
+ return SQLITE_OK;
+}
+
+int Disconnect(sqlite3_vtab* aVtab )
+{
+ delete aVtab;
+
+ return SQLITE_OK;
+}
+
+int BestIndex(sqlite3_vtab* aVtab, sqlite3_index_info* aInfo)
+{
+ // Here we specify what index constraints we want to handle. That is, there
+ // might be some columns with particular constraints in which we can help
+ // SQLite narrow down the result set.
+ //
+ // For example, take the "path = x" where x is a directory. In this case,
+ // we can narrow our search to just this directory instead of the entire file
+ // system. This can be a significant optimization. So, we want to handle that
+ // constraint. To do so, we would look for two specific input conditions:
+ //
+ // 1. aInfo->aConstraint[i].iColumn == 1
+ // 2. aInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ
+ //
+ // The first states that the path column is being used in one of the input
+ // constraints and the second states that the constraint involves the equal
+ // operator.
+ //
+ // An even more specific search would be for name='xxx', in which case we
+ // can limit the search to a single file, if it exists.
+ //
+ // What we have to do here is look for all of our index searches and select
+ // the narrowest. We can only pick one, so obviously we want the one that
+ // is the most specific, which leads to the smallest result set.
+
+ for(int i = 0; i < aInfo->nConstraint; i++) {
+ if (aInfo->aConstraint[i].iColumn == 1 && aInfo->aConstraint[i].usable) {
+ if (aInfo->aConstraint[i].op & SQLITE_INDEX_CONSTRAINT_EQ) {
+ aInfo->aConstraintUsage[i].argvIndex = 1;
+ }
+ break;
+ }
+
+ // TODO: handle single files (constrained also by the name column)
+ }
+
+ return SQLITE_OK;
+}
+
+int Open(sqlite3_vtab* aVtab, sqlite3_vtab_cursor** aCursor)
+{
+ VirtualTableCursor* cursor = new VirtualTableCursor();
+
+ *aCursor = reinterpret_cast<sqlite3_vtab_cursor*>(cursor);
+
+ return SQLITE_OK;
+}
+
+int Close(sqlite3_vtab_cursor* aCursor)
+{
+ VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+
+ delete cursor;
+
+ return SQLITE_OK;
+}
+
+int Filter(sqlite3_vtab_cursor* aCursor, int aIdxNum, const char* aIdxStr,
+ int aArgc, sqlite3_value** aArgv)
+{
+ VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+
+ if(aArgc <= 0) {
+ return SQLITE_OK;
+ }
+
+ nsDependentString path(
+ reinterpret_cast<const char16_t*>(::sqlite3_value_text16(aArgv[0])));
+
+ nsresult rv = cursor->Init(path);
+ NS_ENSURE_SUCCESS(rv, SQLITE_ERROR);
+
+ return SQLITE_OK;
+}
+
+int Next(sqlite3_vtab_cursor* aCursor)
+{
+ VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+
+ nsresult rv = cursor->NextFile();
+ NS_ENSURE_SUCCESS(rv, SQLITE_ERROR);
+
+ return SQLITE_OK;
+}
+
+int Eof(sqlite3_vtab_cursor* aCursor)
+{
+ VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+ return cursor->CurrentFileName().IsVoid() ? 1 : 0;
+}
+
+int Column(sqlite3_vtab_cursor* aCursor, sqlite3_context* aContext,
+ int aColumnIndex)
+{
+ VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+
+ switch (aColumnIndex) {
+ // name
+ case 0: {
+ const nsString& name = cursor->CurrentFileName();
+ sqlite3_result_text16(aContext, name.get(),
+ name.Length() * sizeof(char16_t),
+ SQLITE_TRANSIENT);
+ break;
+ }
+
+ // path
+ case 1: {
+ const nsString& path = cursor->DirectoryPath();
+ sqlite3_result_text16(aContext, path.get(),
+ path.Length() * sizeof(char16_t),
+ SQLITE_TRANSIENT);
+ break;
+ }
+ default:
+ NS_NOTREACHED("Unsupported column!");
+ }
+
+ return SQLITE_OK;
+}
+
+int RowId(sqlite3_vtab_cursor* aCursor, sqlite3_int64* aRowid)
+{
+ VirtualTableCursor* cursor = reinterpret_cast<VirtualTableCursor*>(aCursor);
+
+ *aRowid = cursor->RowId();
+
+ return SQLITE_OK;
+}
+
+} // namespace
+
+namespace mozilla {
+namespace storage {
+
+int RegisterFileSystemModule(sqlite3* aDB, const char* aName)
+{
+ static sqlite3_module module = {
+ 1,
+ Connect,
+ Connect,
+ BestIndex,
+ Disconnect,
+ Disconnect,
+ Open,
+ Close,
+ Filter,
+ Next,
+ Eof,
+ Column,
+ RowId,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr
+ };
+
+ return sqlite3_create_module(aDB, aName, &module, nullptr);
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/FileSystemModule.h b/components/storage/src/FileSystemModule.h
new file mode 100644
index 000000000..40c3a77db
--- /dev/null
+++ b/components/storage/src/FileSystemModule.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_FileSystemModule_h
+#define mozilla_storage_FileSystemModule_h
+
+#include "nscore.h"
+
+struct sqlite3;
+
+namespace mozilla {
+namespace storage {
+
+int RegisterFileSystemModule(sqlite3* aDB, const char* aName);
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozilla_storage_FileSystemModule_h
diff --git a/components/storage/src/IStorageBindingParamsInternal.h b/components/storage/src/IStorageBindingParamsInternal.h
new file mode 100644
index 000000000..e02778680
--- /dev/null
+++ b/components/storage/src/IStorageBindingParamsInternal.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 mozilla_storage_IStorageBindingParamsInternal_h_
+#define mozilla_storage_IStorageBindingParamsInternal_h_
+
+#include "nsISupports.h"
+
+struct sqlite3_stmt;
+class mozIStorageError;
+
+namespace mozilla {
+namespace storage {
+
+#define ISTORAGEBINDINGPARAMSINTERNAL_IID \
+ {0x4c43d33a, 0xc620, 0x41b8, {0xba, 0x1d, 0x50, 0xc5, 0xb1, 0xe9, 0x1a, 0x04}}
+
+/**
+ * Implementation-only interface for mozIStorageBindingParams. This defines the
+ * set of methods required by the asynchronous execution code in order to
+ * consume the contents stored in mozIStorageBindingParams instances.
+ */
+class IStorageBindingParamsInternal : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(ISTORAGEBINDINGPARAMSINTERNAL_IID)
+
+ /**
+ * Binds our stored data to the statement.
+ *
+ * @param aStatement
+ * The statement to bind our data to.
+ * @return nullptr on success, or a mozIStorageError object if an error
+ * occurred.
+ */
+ virtual already_AddRefed<mozIStorageError> bind(sqlite3_stmt *aStatement) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(IStorageBindingParamsInternal,
+ ISTORAGEBINDINGPARAMSINTERNAL_IID)
+
+#define NS_DECL_ISTORAGEBINDINGPARAMSINTERNAL \
+ already_AddRefed<mozIStorageError> bind(sqlite3_stmt *aStatement) override;
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozilla_storage_IStorageBindingParamsInternal_h_
diff --git a/components/storage/src/SQLCollations.cpp b/components/storage/src/SQLCollations.cpp
new file mode 100644
index 000000000..392bcd804
--- /dev/null
+++ b/components/storage/src/SQLCollations.cpp
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "SQLCollations.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Local Helper Functions
+
+namespace {
+
+/**
+ * Helper function for the UTF-8 locale collations.
+ *
+ * @param aService
+ * The Service that owns the nsICollation used by this collation.
+ * @param aLen1
+ * The number of bytes in aStr1.
+ * @param aStr1
+ * The string to be compared against aStr2 as provided by SQLite. It
+ * must be a non-null-terminated char* buffer.
+ * @param aLen2
+ * The number of bytes in aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1 as provided by SQLite. It
+ * must be a non-null-terminated char* buffer.
+ * @param aComparisonStrength
+ * The sorting strength, one of the nsICollation constants.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number.
+ * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2,
+ * returns 0.
+ */
+int
+localeCollationHelper8(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2,
+ int32_t aComparisonStrength)
+{
+ NS_ConvertUTF8toUTF16 str1(static_cast<const char *>(aStr1), aLen1);
+ NS_ConvertUTF8toUTF16 str2(static_cast<const char *>(aStr2), aLen2);
+ Service *serv = static_cast<Service *>(aService);
+ return serv->localeCompareStrings(str1, str2, aComparisonStrength);
+}
+
+/**
+ * Helper function for the UTF-16 locale collations.
+ *
+ * @param aService
+ * The Service that owns the nsICollation used by this collation.
+ * @param aLen1
+ * The number of bytes (not characters) in aStr1.
+ * @param aStr1
+ * The string to be compared against aStr2 as provided by SQLite. It
+ * must be a non-null-terminated char16_t* buffer.
+ * @param aLen2
+ * The number of bytes (not characters) in aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1 as provided by SQLite. It
+ * must be a non-null-terminated char16_t* buffer.
+ * @param aComparisonStrength
+ * The sorting strength, one of the nsICollation constants.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number.
+ * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2,
+ * returns 0.
+ */
+int
+localeCollationHelper16(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2,
+ int32_t aComparisonStrength)
+{
+ const char16_t *buf1 = static_cast<const char16_t *>(aStr1);
+ const char16_t *buf2 = static_cast<const char16_t *>(aStr2);
+
+ // The second argument to the nsDependentSubstring constructor is exclusive:
+ // It points to the char16_t immediately following the last one in the target
+ // substring. Since aLen1 and aLen2 are in bytes, divide by sizeof(char16_t)
+ // so that the pointer arithmetic is correct.
+ nsDependentSubstring str1(buf1, buf1 + (aLen1 / sizeof(char16_t)));
+ nsDependentSubstring str2(buf2, buf2 + (aLen2 / sizeof(char16_t)));
+ Service *serv = static_cast<Service *>(aService);
+ return serv->localeCompareStrings(str1, str2, aComparisonStrength);
+}
+
+// This struct is used only by registerCollations below, but ISO C++98 forbids
+// instantiating a template dependent on a locally-defined type. Boo-urns!
+struct Collations {
+ const char *zName;
+ int enc;
+ int(*xCompare)(void*, int, const void*, int, const void*);
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// Exposed Functions
+
+int
+registerCollations(sqlite3 *aDB,
+ Service *aService)
+{
+ Collations collations[] = {
+ {"locale",
+ SQLITE_UTF8,
+ localeCollation8},
+ {"locale_case_sensitive",
+ SQLITE_UTF8,
+ localeCollationCaseSensitive8},
+ {"locale_accent_sensitive",
+ SQLITE_UTF8,
+ localeCollationAccentSensitive8},
+ {"locale_case_accent_sensitive",
+ SQLITE_UTF8,
+ localeCollationCaseAccentSensitive8},
+ {"locale",
+ SQLITE_UTF16,
+ localeCollation16},
+ {"locale_case_sensitive",
+ SQLITE_UTF16,
+ localeCollationCaseSensitive16},
+ {"locale_accent_sensitive",
+ SQLITE_UTF16,
+ localeCollationAccentSensitive16},
+ {"locale_case_accent_sensitive",
+ SQLITE_UTF16,
+ localeCollationCaseAccentSensitive16},
+ };
+
+ int rv = SQLITE_OK;
+ for (size_t i = 0; SQLITE_OK == rv && i < ArrayLength(collations); ++i) {
+ struct Collations *p = &collations[i];
+ rv = ::sqlite3_create_collation(aDB, p->zName, p->enc, aService,
+ p->xCompare);
+ }
+
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// SQL Collations
+
+int
+localeCollation8(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2)
+{
+ return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2,
+ nsICollation::kCollationCaseInSensitive);
+}
+
+int
+localeCollationCaseSensitive8(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2)
+{
+ return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2,
+ nsICollation::kCollationAccentInsenstive);
+}
+
+int
+localeCollationAccentSensitive8(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2)
+{
+ return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2,
+ nsICollation::kCollationCaseInsensitiveAscii);
+}
+
+int
+localeCollationCaseAccentSensitive8(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2)
+{
+ return localeCollationHelper8(aService, aLen1, aStr1, aLen2, aStr2,
+ nsICollation::kCollationCaseSensitive);
+}
+
+int
+localeCollation16(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2)
+{
+ return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2,
+ nsICollation::kCollationCaseInSensitive);
+}
+
+int
+localeCollationCaseSensitive16(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2)
+{
+ return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2,
+ nsICollation::kCollationAccentInsenstive);
+}
+
+int
+localeCollationAccentSensitive16(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2)
+{
+ return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2,
+ nsICollation::kCollationCaseInsensitiveAscii);
+}
+
+int
+localeCollationCaseAccentSensitive16(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2)
+{
+ return localeCollationHelper16(aService, aLen1, aStr1, aLen2, aStr2,
+ nsICollation::kCollationCaseSensitive);
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/SQLCollations.h b/components/storage/src/SQLCollations.h
new file mode 100644
index 000000000..d6d0d4562
--- /dev/null
+++ b/components/storage/src/SQLCollations.h
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_SQLCollations_h
+#define mozilla_storage_SQLCollations_h
+
+#include "mozStorageService.h"
+#include "nscore.h"
+#include "nsString.h"
+
+#include "sqlite3.h"
+
+namespace mozilla {
+namespace storage {
+
+/**
+ * Registers the collating sequences declared here with the specified
+ * database and Service.
+ *
+ * @param aDB
+ * The database we'll be registering the collations with.
+ * @param aService
+ * The Service that owns the nsICollation used by our collations.
+ * @return the SQLite status code indicating success or failure.
+ */
+int registerCollations(sqlite3 *aDB, Service *aService);
+
+////////////////////////////////////////////////////////////////////////////////
+//// Predefined Functions
+
+/**
+ * Custom UTF-8 collating sequence that respects the application's locale.
+ * Comparison is case- and accent-insensitive. This is called by SQLite.
+ *
+ * @param aService
+ * The Service that owns the nsICollation used by this collation.
+ * @param aLen1
+ * The number of bytes in aStr1.
+ * @param aStr1
+ * The string to be compared against aStr2. It will be passed in by
+ * SQLite as a non-null-terminated char* buffer.
+ * @param aLen2
+ * The number of bytes in aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1. It will be passed in by
+ * SQLite as a non-null-terminated char* buffer.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number.
+ * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2,
+ * returns 0.
+ */
+int localeCollation8(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2);
+
+/**
+ * Custom UTF-8 collating sequence that respects the application's locale.
+ * Comparison is case-sensitive and accent-insensitive. This is called by
+ * SQLite.
+ *
+ * @param aService
+ * The Service that owns the nsICollation used by this collation.
+ * @param aLen1
+ * The number of bytes in aStr1.
+ * @param aStr1
+ * The string to be compared against aStr2. It will be passed in by
+ * SQLite as a non-null-terminated char* buffer.
+ * @param aLen2
+ * The number of bytes in aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1. It will be passed in by
+ * SQLite as a non-null-terminated char* buffer.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number.
+ * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2,
+ * returns 0.
+ */
+int localeCollationCaseSensitive8(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2);
+
+/**
+ * Custom UTF-8 collating sequence that respects the application's locale.
+ * Comparison is case-insensitive and accent-sensitive. This is called by
+ * SQLite.
+ *
+ * @param aService
+ * The Service that owns the nsICollation used by this collation.
+ * @param aLen1
+ * The number of bytes in aStr1.
+ * @param aStr1
+ * The string to be compared against aStr2. It will be passed in by
+ * SQLite as a non-null-terminated char* buffer.
+ * @param aLen2
+ * The number of bytes in aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1. It will be passed in by
+ * SQLite as a non-null-terminated char* buffer.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number.
+ * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2,
+ * returns 0.
+ */
+int localeCollationAccentSensitive8(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2);
+
+/**
+ * Custom UTF-8 collating sequence that respects the application's locale.
+ * Comparison is case- and accent-sensitive. This is called by SQLite.
+ *
+ * @param aService
+ * The Service that owns the nsICollation used by this collation.
+ * @param aLen1
+ * The number of bytes in aStr1.
+ * @param aStr1
+ * The string to be compared against aStr2. It will be passed in by
+ * SQLite as a non-null-terminated char* buffer.
+ * @param aLen2
+ * The number of bytes in aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1. It will be passed in by
+ * SQLite as a non-null-terminated char* buffer.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number.
+ * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2,
+ * returns 0.
+ */
+int localeCollationCaseAccentSensitive8(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2);
+
+/**
+ * Custom UTF-16 collating sequence that respects the application's locale.
+ * Comparison is case- and accent-insensitive. This is called by SQLite.
+ *
+ * @param aService
+ * The Service that owns the nsICollation used by this collation.
+ * @param aLen1
+ * The number of bytes (not characters) in aStr1.
+ * @param aStr1
+ * The string to be compared against aStr2. It will be passed in by
+ * SQLite as a non-null-terminated char16_t* buffer.
+ * @param aLen2
+ * The number of bytes (not characters) in aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1. It will be passed in by
+ * SQLite as a non-null-terminated char16_t* buffer.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number.
+ * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2,
+ * returns 0.
+ */
+int localeCollation16(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2);
+
+/**
+ * Custom UTF-16 collating sequence that respects the application's locale.
+ * Comparison is case-sensitive and accent-insensitive. This is called by
+ * SQLite.
+ *
+ * @param aService
+ * The Service that owns the nsICollation used by this collation.
+ * @param aLen1
+ * The number of bytes (not characters) in aStr1.
+ * @param aStr1
+ * The string to be compared against aStr2. It will be passed in by
+ * SQLite as a non-null-terminated char16_t* buffer.
+ * @param aLen2
+ * The number of bytes (not characters) in aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1. It will be passed in by
+ * SQLite as a non-null-terminated char16_t* buffer.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number.
+ * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2,
+ * returns 0.
+ */
+int localeCollationCaseSensitive16(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2);
+
+/**
+ * Custom UTF-16 collating sequence that respects the application's locale.
+ * Comparison is case-insensitive and accent-sensitive. This is called by
+ * SQLite.
+ *
+ * @param aService
+ * The Service that owns the nsICollation used by this collation.
+ * @param aLen1
+ * The number of bytes (not characters) in aStr1.
+ * @param aStr1
+ * The string to be compared against aStr2. It will be passed in by
+ * SQLite as a non-null-terminated char16_t* buffer.
+ * @param aLen2
+ * The number of bytes (not characters) in aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1. It will be passed in by
+ * SQLite as a non-null-terminated char16_t* buffer.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number.
+ * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2,
+ * returns 0.
+ */
+int localeCollationAccentSensitive16(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2);
+
+/**
+ * Custom UTF-16 collating sequence that respects the application's locale.
+ * Comparison is case- and accent-sensitive. This is called by SQLite.
+ *
+ * @param aService
+ * The Service that owns the nsICollation used by this collation.
+ * @param aLen1
+ * The number of bytes (not characters) in aStr1.
+ * @param aStr1
+ * The string to be compared against aStr2. It will be passed in by
+ * SQLite as a non-null-terminated char16_t* buffer.
+ * @param aLen2
+ * The number of bytes (not characters) in aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1. It will be passed in by
+ * SQLite as a non-null-terminated char16_t* buffer.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative number.
+ * If aStr1 > aStr2, returns a positive number. If aStr1 == aStr2,
+ * returns 0.
+ */
+int localeCollationCaseAccentSensitive16(void *aService,
+ int aLen1,
+ const void *aStr1,
+ int aLen2,
+ const void *aStr2);
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozilla_storage_SQLCollations_h
diff --git a/components/storage/src/SQLiteMutex.h b/components/storage/src/SQLiteMutex.h
new file mode 100644
index 000000000..eaa69eab1
--- /dev/null
+++ b/components/storage/src/SQLiteMutex.h
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_SQLiteMutex_h_
+#define mozilla_storage_SQLiteMutex_h_
+
+#include "mozilla/BlockingResourceBase.h"
+#include "sqlite3.h"
+
+namespace mozilla {
+namespace storage {
+
+/**
+ * Wrapper class for sqlite3_mutexes. To be used whenever we want to use a
+ * sqlite3_mutex.
+ *
+ * @warning Never EVER wrap the same sqlite3_mutex with a different SQLiteMutex.
+ * If you do this, you void the deadlock detector's warranty!
+ */
+class SQLiteMutex : private BlockingResourceBase
+{
+public:
+ /**
+ * Constructs a wrapper for a sqlite3_mutex that has deadlock detecting.
+ *
+ * @param aName
+ * A name which can be used to reference this mutex.
+ */
+ explicit SQLiteMutex(const char *aName)
+ : BlockingResourceBase(aName, eMutex)
+ , mMutex(nullptr)
+ {
+ }
+
+ /**
+ * Sets the mutex that we are wrapping. We generally do not have access to
+ * our mutex at class construction, so we have to set it once we get access to
+ * it.
+ *
+ * @param aMutex
+ * The sqlite3_mutex that we are going to wrap.
+ */
+ void initWithMutex(sqlite3_mutex *aMutex)
+ {
+ NS_ASSERTION(aMutex, "You must pass in a valid mutex!");
+ NS_ASSERTION(!mMutex, "A mutex has already been set for this!");
+ mMutex = aMutex;
+ }
+
+#if !defined(DEBUG)
+ /**
+ * Acquires the mutex.
+ */
+ void lock()
+ {
+ sqlite3_mutex_enter(mMutex);
+ }
+
+ /**
+ * Releases the mutex.
+ */
+ void unlock()
+ {
+ sqlite3_mutex_leave(mMutex);
+ }
+
+ /**
+ * Asserts that the current thread owns the mutex.
+ */
+ void assertCurrentThreadOwns()
+ {
+ }
+
+ /**
+ * Asserts that the current thread does not own the mutex.
+ */
+ void assertNotCurrentThreadOwns()
+ {
+ }
+
+#else
+ void lock()
+ {
+ NS_ASSERTION(mMutex, "No mutex associated with this wrapper!");
+
+ // While SQLite Mutexes may be recursive, in our own code we do not want to
+ // treat them as such.
+
+ CheckAcquire();
+ sqlite3_mutex_enter(mMutex);
+ Acquire(); // Call is protected by us holding the mutex.
+ }
+
+ void unlock()
+ {
+ NS_ASSERTION(mMutex, "No mutex associated with this wrapper!");
+
+ // While SQLite Mutexes may be recursive, in our own code we do not want to
+ // treat them as such.
+ Release(); // Call is protected by us holding the mutex.
+ sqlite3_mutex_leave(mMutex);
+ }
+
+ void assertCurrentThreadOwns()
+ {
+ NS_ASSERTION(mMutex, "No mutex associated with this wrapper!");
+ NS_ASSERTION(sqlite3_mutex_held(mMutex),
+ "Mutex is not held, but we expect it to be!");
+ }
+
+ void assertNotCurrentThreadOwns()
+ {
+ NS_ASSERTION(mMutex, "No mutex associated with this wrapper!");
+ NS_ASSERTION(sqlite3_mutex_notheld(mMutex),
+ "Mutex is held, but we expect it to not be!");
+ }
+#endif // ifndef DEBUG
+
+private:
+ sqlite3_mutex *mMutex;
+};
+
+/**
+ * Automatically acquires the mutex when it enters scope, and releases it when
+ * it leaves scope.
+ */
+class MOZ_STACK_CLASS SQLiteMutexAutoLock
+{
+public:
+ explicit SQLiteMutexAutoLock(SQLiteMutex &aMutex)
+ : mMutex(aMutex)
+ {
+ mMutex.lock();
+ }
+
+ ~SQLiteMutexAutoLock()
+ {
+ mMutex.unlock();
+ }
+
+private:
+ SQLiteMutex &mMutex;
+};
+
+/**
+ * Automatically releases the mutex when it enters scope, and acquires it when
+ * it leaves scope.
+ */
+class MOZ_STACK_CLASS SQLiteMutexAutoUnlock
+{
+public:
+ explicit SQLiteMutexAutoUnlock(SQLiteMutex &aMutex)
+ : mMutex(aMutex)
+ {
+ mMutex.unlock();
+ }
+
+ ~SQLiteMutexAutoUnlock()
+ {
+ mMutex.lock();
+ }
+
+private:
+ SQLiteMutex &mMutex;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozilla_storage_SQLiteMutex_h_
diff --git a/components/storage/src/StatementCache.h b/components/storage/src/StatementCache.h
new file mode 100644
index 000000000..ed7714799
--- /dev/null
+++ b/components/storage/src/StatementCache.h
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_StatementCache_h
+#define mozilla_storage_StatementCache_h
+
+#include "mozIStorageConnection.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageAsyncStatement.h"
+
+#include "nsAutoPtr.h"
+#include "nsHashKeys.h"
+#include "nsInterfaceHashtable.h"
+
+namespace mozilla {
+namespace storage {
+
+/**
+ * Class used to cache statements (mozIStorageStatement or
+ * mozIStorageAsyncStatement).
+ */
+template<typename StatementType>
+class StatementCache {
+public:
+ /**
+ * Constructor for the cache.
+ *
+ * @note a connection can have more than one cache.
+ *
+ * @param aConnection
+ * A reference to the nsCOMPtr for the connection this cache is to be
+ * used for. This nsCOMPtr must at least live as long as this class,
+ * otherwise crashes will happen.
+ */
+ explicit StatementCache(nsCOMPtr<mozIStorageConnection>& aConnection)
+ : mConnection(aConnection)
+ {
+ }
+
+ /**
+ * Obtains a cached statement. If this statement is not yet created, it will
+ * be created and stored for later use.
+ *
+ * @param aQuery
+ * The SQL string (either a const char [] or nsACString) to get a
+ * cached query for.
+ * @return the cached statement, or null upon error.
+ */
+ inline
+ already_AddRefed<StatementType>
+ GetCachedStatement(const nsACString& aQuery)
+ {
+ nsCOMPtr<StatementType> stmt;
+ if (!mCachedStatements.Get(aQuery, getter_AddRefs(stmt))) {
+ stmt = CreateStatement(aQuery);
+ NS_ENSURE_TRUE(stmt, nullptr);
+
+ mCachedStatements.Put(aQuery, stmt);
+ }
+ return stmt.forget();
+ }
+
+ template<int N>
+ MOZ_ALWAYS_INLINE already_AddRefed<StatementType>
+ GetCachedStatement(const char (&aQuery)[N])
+ {
+ nsDependentCString query(aQuery, N - 1);
+ return GetCachedStatement(query);
+ }
+
+ /**
+ * Finalizes all cached statements so the database can be safely closed. The
+ * behavior of this cache is unspecified after this method is called.
+ */
+ inline
+ void
+ FinalizeStatements()
+ {
+ for (auto iter = mCachedStatements.Iter(); !iter.Done(); iter.Next()) {
+ (void)iter.Data()->Finalize();
+ }
+
+ // Clear the cache at this time too!
+ (void)mCachedStatements.Clear();
+ }
+
+private:
+ inline
+ already_AddRefed<StatementType>
+ CreateStatement(const nsACString& aQuery);
+
+ nsInterfaceHashtable<nsCStringHashKey, StatementType> mCachedStatements;
+ nsCOMPtr<mozIStorageConnection>& mConnection;
+};
+
+template< >
+inline
+already_AddRefed<mozIStorageStatement>
+StatementCache<mozIStorageStatement>::CreateStatement(const nsACString& aQuery)
+{
+ NS_ENSURE_TRUE(mConnection, nullptr);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mConnection->CreateStatement(aQuery, getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) {
+ nsCString error;
+ error.AppendLiteral("The statement '");
+ error.Append(aQuery);
+ error.AppendLiteral("' failed to compile with the error message '");
+ nsCString msg;
+ (void)mConnection->GetLastErrorString(msg);
+ error.Append(msg);
+ error.AppendLiteral("'.");
+ NS_ERROR(error.get());
+ }
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return stmt.forget();
+}
+
+template< >
+inline
+already_AddRefed<mozIStorageAsyncStatement>
+StatementCache<mozIStorageAsyncStatement>::CreateStatement(const nsACString& aQuery)
+{
+ NS_ENSURE_TRUE(mConnection, nullptr);
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ nsresult rv = mConnection->CreateAsyncStatement(aQuery, getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return stmt.forget();
+}
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozilla_storage_StatementCache_h
diff --git a/components/storage/src/StorageBaseStatementInternal.cpp b/components/storage/src/StorageBaseStatementInternal.cpp
new file mode 100644
index 000000000..d6545fcb4
--- /dev/null
+++ b/components/storage/src/StorageBaseStatementInternal.cpp
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "StorageBaseStatementInternal.h"
+
+#include "nsProxyRelease.h"
+
+#include "mozStorageBindingParamsArray.h"
+#include "mozStorageStatementData.h"
+#include "mozStorageAsyncStatementExecution.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Local Classes
+
+/**
+ * Used to finalize an asynchronous statement on the background thread.
+ */
+class AsyncStatementFinalizer : public Runnable
+{
+public:
+ /**
+ * Constructor for the event.
+ *
+ * @param aStatement
+ * We need the AsyncStatement to be able to get at the sqlite3_stmt;
+ * we only access/create it on the async thread.
+ * @param aConnection
+ * We need the connection to know what thread to release the statement
+ * on. We release the statement on that thread since releasing the
+ * statement might end up releasing the connection too.
+ */
+ AsyncStatementFinalizer(StorageBaseStatementInternal *aStatement,
+ Connection *aConnection)
+ : mStatement(aStatement)
+ , mConnection(aConnection)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mStatement->mAsyncStatement) {
+ sqlite3_finalize(mStatement->mAsyncStatement);
+ mStatement->mAsyncStatement = nullptr;
+ }
+
+ nsCOMPtr<nsIThread> targetThread(mConnection->threadOpenedOn);
+ NS_ProxyRelease(targetThread, mStatement.forget());
+ return NS_OK;
+ }
+private:
+ RefPtr<StorageBaseStatementInternal> mStatement;
+ RefPtr<Connection> mConnection;
+};
+
+/**
+ * Finalize a sqlite3_stmt on the background thread for a statement whose
+ * destructor was invoked and the statement was non-null.
+ */
+class LastDitchSqliteStatementFinalizer : public Runnable
+{
+public:
+ /**
+ * Event constructor.
+ *
+ * @param aConnection
+ * Used to keep the connection alive. If we failed to do this, it
+ * is possible that the statement going out of scope invoking us
+ * might have the last reference to the connection and so trigger
+ * an attempt to close the connection which is doomed to fail
+ * (because the asynchronous execution thread must exist which will
+ * trigger the failure case).
+ * @param aStatement
+ * The sqlite3_stmt to finalize. This object takes ownership /
+ * responsibility for the instance and all other references to it
+ * should be forgotten.
+ */
+ LastDitchSqliteStatementFinalizer(RefPtr<Connection> &aConnection,
+ sqlite3_stmt *aStatement)
+ : mConnection(aConnection)
+ , mAsyncStatement(aStatement)
+ {
+ NS_PRECONDITION(aConnection, "You must provide a Connection");
+ }
+
+ NS_IMETHOD Run() override
+ {
+ (void)::sqlite3_finalize(mAsyncStatement);
+ mAsyncStatement = nullptr;
+
+ nsCOMPtr<nsIThread> target(mConnection->threadOpenedOn);
+ (void)::NS_ProxyRelease(target, mConnection.forget());
+ return NS_OK;
+ }
+private:
+ RefPtr<Connection> mConnection;
+ sqlite3_stmt *mAsyncStatement;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// StorageBaseStatementInternal
+
+StorageBaseStatementInternal::StorageBaseStatementInternal()
+: mAsyncStatement(nullptr)
+{
+}
+
+void
+StorageBaseStatementInternal::asyncFinalize()
+{
+ nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget();
+ if (target) {
+ // Attempt to finalize asynchronously
+ nsCOMPtr<nsIRunnable> event =
+ new AsyncStatementFinalizer(this, mDBConnection);
+
+ // Dispatch. Note that dispatching can fail, typically if
+ // we have a race condition with asyncClose(). It's ok,
+ // let asyncClose() win.
+ (void)target->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ // If we cannot get the background thread,
+ // mozStorageConnection::AsyncClose() has already been called and
+ // the statement either has been or will be cleaned up by
+ // internalClose().
+}
+
+void
+StorageBaseStatementInternal::destructorAsyncFinalize()
+{
+ if (!mAsyncStatement)
+ return;
+
+ // If we reach this point, our owner has not finalized this
+ // statement, yet we are being destructed. If possible, we want to
+ // auto-finalize it early, to release the resources early.
+ nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget();
+ if (target) {
+ // If we can get the async execution target, we can indeed finalize
+ // the statement, as the connection is still open.
+ bool isAsyncThread = false;
+ (void)target->IsOnCurrentThread(&isAsyncThread);
+
+ nsCOMPtr<nsIRunnable> event =
+ new LastDitchSqliteStatementFinalizer(mDBConnection, mAsyncStatement);
+ if (isAsyncThread) {
+ (void)event->Run();
+ } else {
+ (void)target->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ }
+
+ // We might not be able to dispatch to the background thread,
+ // presumably because it is being shutdown. Since said shutdown will
+ // finalize the statement, we just need to clean-up around here.
+ mAsyncStatement = nullptr;
+}
+
+NS_IMETHODIMP
+StorageBaseStatementInternal::NewBindingParamsArray(
+ mozIStorageBindingParamsArray **_array
+)
+{
+ nsCOMPtr<mozIStorageBindingParamsArray> array = new BindingParamsArray(this);
+ NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+ array.forget(_array);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StorageBaseStatementInternal::ExecuteAsync(
+ mozIStorageStatementCallback *aCallback,
+ mozIStoragePendingStatement **_stmt
+)
+{
+ // We used to call Connection::ExecuteAsync but it takes a
+ // mozIStorageBaseStatement signature because it is also a public API. Since
+ // our 'this' has no static concept of mozIStorageBaseStatement and Connection
+ // would just QI it back across to a StorageBaseStatementInternal and the
+ // actual logic is very simple, we now roll our own.
+ nsTArray<StatementData> stmts(1);
+ StatementData data;
+ nsresult rv = getAsynchronousStatementData(data);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(stmts.AppendElement(data), NS_ERROR_OUT_OF_MEMORY);
+
+ // Dispatch to the background
+ return AsyncExecuteStatements::execute(stmts, mDBConnection,
+ mNativeConnection, aCallback, _stmt);
+}
+
+NS_IMETHODIMP
+StorageBaseStatementInternal::EscapeStringForLIKE(
+ const nsAString &aValue,
+ const char16_t aEscapeChar,
+ nsAString &_escapedString
+)
+{
+ const char16_t MATCH_ALL('%');
+ const char16_t MATCH_ONE('_');
+
+ _escapedString.Truncate(0);
+
+ for (uint32_t i = 0; i < aValue.Length(); i++) {
+ if (aValue[i] == aEscapeChar || aValue[i] == MATCH_ALL ||
+ aValue[i] == MATCH_ONE) {
+ _escapedString += aEscapeChar;
+ }
+ _escapedString += aValue[i];
+ }
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/StorageBaseStatementInternal.h b/components/storage/src/StorageBaseStatementInternal.h
new file mode 100644
index 000000000..97e68e6b5
--- /dev/null
+++ b/components/storage/src/StorageBaseStatementInternal.h
@@ -0,0 +1,353 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 mozilla_storage_StorageBaseStatementInternal_h_
+#define mozilla_storage_StorageBaseStatementInternal_h_
+
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+
+struct sqlite3;
+struct sqlite3_stmt;
+class mozIStorageBindingParamsArray;
+class mozIStorageBindingParams;
+class mozIStorageStatementCallback;
+class mozIStoragePendingStatement;
+
+namespace mozilla {
+namespace storage {
+
+#define STORAGEBASESTATEMENTINTERNAL_IID \
+ {0xd18856c9, 0xbf07, 0x4ae2, {0x94, 0x5b, 0x1a, 0xdd, 0x49, 0x19, 0x55, 0x2a}}
+
+class Connection;
+class StatementData;
+
+class AsyncStatementFinalizer;
+
+/**
+ * Implementation-only interface and shared logix mix-in corresponding to
+ * mozIStorageBaseStatement. Both Statement and AsyncStatement inherit from
+ * this. The interface aspect makes them look the same to implementation innards
+ * that aren't publicly accessible. The mix-in avoids code duplication in
+ * common implementations of mozIStorageBaseStatement, albeit with some minor
+ * performance/space overhead because we have to use defines to officially
+ * implement the methods on Statement/AsyncStatement (and proxy to this base
+ * class.)
+ */
+class StorageBaseStatementInternal : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(STORAGEBASESTATEMENTINTERNAL_IID)
+
+ /**
+ * @return the connection that this statement belongs to.
+ */
+ Connection *getOwner()
+ {
+ return mDBConnection;
+ }
+
+ /**
+ * Return the asynchronous statement, creating it if required.
+ *
+ * This is for use by the asynchronous execution code for StatementData
+ * created by AsyncStatements. Statement internally uses this method to
+ * prepopulate StatementData with the sqlite3_stmt.
+ *
+ * @param[out] stmt
+ * The sqlite3_stmt for asynchronous use.
+ * @return The SQLite result code for creating the statement if created,
+ * SQLITE_OK if creation was not required.
+ */
+ virtual int getAsyncStatement(sqlite3_stmt **_stmt) = 0;
+
+ /**
+ * Obtains the StatementData needed for asynchronous execution.
+ *
+ * This is for use by Connection to retrieve StatementData from statements
+ * when executeAsync is invoked.
+ *
+ * @param[out] _data
+ * A reference to a StatementData object that will be populated
+ * upon successful execution of this method.
+ * @return NS_OK if we were able to assemble the data, failure otherwise.
+ */
+ virtual nsresult getAsynchronousStatementData(StatementData &_data) = 0;
+
+ /**
+ * Construct a new BindingParams to be owned by the provided binding params
+ * array. This method exists so that BindingParamsArray does not need
+ * factory logic to determine what type of BindingParams to instantiate.
+ *
+ * @param aOwner
+ * The binding params array to own the newly created binding params.
+ * @return The new mozIStorageBindingParams instance appropriate to the
+ * underlying statement type.
+ */
+ virtual already_AddRefed<mozIStorageBindingParams> newBindingParams(
+ mozIStorageBindingParamsArray *aOwner
+ ) = 0;
+
+protected: // mix-in bits are protected
+ StorageBaseStatementInternal();
+
+ RefPtr<Connection> mDBConnection;
+ sqlite3 *mNativeConnection;
+
+ /**
+ * Our asynchronous statement.
+ *
+ * For Statement this is populated by the first invocation to
+ * getAsyncStatement.
+ *
+ * For AsyncStatement, this is null at creation time and initialized by the
+ * async thread when it calls getAsyncStatement the first time the statement
+ * is executed. (Or in the event of badly formed SQL, every time.)
+ */
+ sqlite3_stmt *mAsyncStatement;
+
+ /**
+ * Initiate asynchronous finalization by dispatching an event to the
+ * asynchronous thread to finalize mAsyncStatement. This acquires a reference
+ * to this statement and proxies it back to the connection's owning thread
+ * for release purposes.
+ *
+ * In the event the asynchronous thread is already gone or we otherwise fail
+ * to dispatch an event to it we failover to invoking internalAsyncFinalize
+ * directly. (That's what the asynchronous finalizer would have called.)
+ *
+ * @note You must not call this method from your destructor because its
+ * operation assumes we are still alive. Call internalAsyncFinalize
+ * directly in that case.
+ */
+ void asyncFinalize();
+
+ /**
+ * Cleanup the async sqlite3_stmt stored in mAsyncStatement if it exists by
+ * attempting to dispatch to the asynchronous thread if available, finalizing
+ * on this thread if it is not.
+ *
+ * @note Call this from your destructor, call asyncFinalize otherwise.
+ */
+ void destructorAsyncFinalize();
+
+ NS_IMETHOD NewBindingParamsArray(mozIStorageBindingParamsArray **_array);
+ NS_IMETHOD ExecuteAsync(mozIStorageStatementCallback *aCallback,
+ mozIStoragePendingStatement **_stmt);
+ NS_IMETHOD EscapeStringForLIKE(const nsAString &aValue,
+ char16_t aEscapeChar,
+ nsAString &_escapedString);
+
+ // Needs access to internalAsyncFinalize
+ friend class AsyncStatementFinalizer;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(StorageBaseStatementInternal,
+ STORAGEBASESTATEMENTINTERNAL_IID)
+
+#define NS_DECL_STORAGEBASESTATEMENTINTERNAL \
+ virtual Connection *getOwner(); \
+ virtual int getAsyncStatement(sqlite3_stmt **_stmt) override; \
+ virtual nsresult getAsynchronousStatementData(StatementData &_data) override; \
+ virtual already_AddRefed<mozIStorageBindingParams> newBindingParams( \
+ mozIStorageBindingParamsArray *aOwner) override;
+
+/**
+ * Helper macro to implement the proxying implementations. Because we are
+ * implementing methods that are part of mozIStorageBaseStatement and the
+ * implementation classes already use NS_DECL_MOZISTORAGEBASESTATEMENT we don't
+ * need to provide declaration support.
+ */
+#define MIX_IMPL(_class, _optionalGuard, _method, _declArgs, _invokeArgs) \
+ NS_IMETHODIMP _class::_method _declArgs \
+ { \
+ _optionalGuard \
+ return StorageBaseStatementInternal::_method _invokeArgs; \
+ }
+
+
+/**
+ * Define proxying implementation for the given _class. If a state invariant
+ * needs to be checked and an early return possibly performed, pass the clause
+ * to use as _optionalGuard.
+ */
+#define MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(_class, _optionalGuard) \
+ MIX_IMPL(_class, _optionalGuard, \
+ NewBindingParamsArray, \
+ (mozIStorageBindingParamsArray **_array), \
+ (_array)) \
+ MIX_IMPL(_class, _optionalGuard, \
+ ExecuteAsync, \
+ (mozIStorageStatementCallback *aCallback, \
+ mozIStoragePendingStatement **_stmt), \
+ (aCallback, _stmt)) \
+ MIX_IMPL(_class, _optionalGuard, \
+ EscapeStringForLIKE, \
+ (const nsAString &aValue, char16_t aEscapeChar, \
+ nsAString &_escapedString), \
+ (aValue, aEscapeChar, _escapedString))
+
+/**
+ * Name-building helper for BIND_GEN_IMPL.
+ */
+#define BIND_NAME_CONCAT(_nameBit, _concatBit) \
+ Bind##_nameBit##_concatBit
+
+/**
+ * We have type-specific convenience methods for C++ implementations in
+ * 3 different forms; 2 by index, 1 by name. The following macro allows
+ * us to avoid having to define repetitive things by hand.
+ *
+ * Because of limitations of macros and our desire to avoid requiring special
+ * permutations for the null and blob cases (whose argument count varies),
+ * we require that the argument declarations and corresponding invocation
+ * usages are passed in.
+ *
+ * @param _class
+ * The class name.
+ * @param _guard
+ * The guard clause to inject.
+ * @param _declName
+ * The argument list (with parens) for the ByName variants.
+ * @param _declIndex
+ * The argument list (with parens) for the index variants.
+ * @param _invArgs
+ * The invocation argumment list.
+ */
+#define BIND_GEN_IMPL(_class, _guard, _name, _declName, _declIndex, _invArgs) \
+ NS_IMETHODIMP _class::BIND_NAME_CONCAT(_name, ByName) _declName \
+ { \
+ _guard \
+ mozIStorageBindingParams *params = getParams(); \
+ NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); \
+ return params->BIND_NAME_CONCAT(_name, ByName) _invArgs; \
+ } \
+ NS_IMETHODIMP _class::BIND_NAME_CONCAT(_name, ByIndex) _declIndex \
+ { \
+ _guard \
+ mozIStorageBindingParams *params = getParams(); \
+ NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); \
+ return params->BIND_NAME_CONCAT(_name, ByIndex) _invArgs; \
+ } \
+ NS_IMETHODIMP _class::BIND_NAME_CONCAT(_name, Parameter) _declIndex \
+ { \
+ _guard \
+ mozIStorageBindingParams *params = getParams(); \
+ NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); \
+ return params->BIND_NAME_CONCAT(_name, ByIndex) _invArgs; \
+ }
+
+/**
+ * Implement BindByName/BindByIndex for the given class.
+ *
+ * @param _class The class name.
+ * @param _optionalGuard The guard clause to inject.
+ */
+#define BIND_BASE_IMPLS(_class, _optionalGuard) \
+ NS_IMETHODIMP _class::BindByName(const nsACString &aName, \
+ nsIVariant *aValue) \
+ { \
+ _optionalGuard \
+ mozIStorageBindingParams *params = getParams(); \
+ NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); \
+ return params->BindByName(aName, aValue); \
+ } \
+ NS_IMETHODIMP _class::BindByIndex(uint32_t aIndex, \
+ nsIVariant *aValue) \
+ { \
+ _optionalGuard \
+ mozIStorageBindingParams *params = getParams(); \
+ NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY); \
+ return params->BindByIndex(aIndex, aValue); \
+ }
+
+/**
+ * Define the various Bind*Parameter, Bind*ByIndex, Bind*ByName stubs that just
+ * end up proxying to the params object.
+ */
+#define BOILERPLATE_BIND_PROXIES(_class, _optionalGuard) \
+ BIND_BASE_IMPLS(_class, _optionalGuard) \
+ BIND_GEN_IMPL(_class, _optionalGuard, \
+ UTF8String, \
+ (const nsACString &aWhere, \
+ const nsACString &aValue), \
+ (uint32_t aWhere, \
+ const nsACString &aValue), \
+ (aWhere, aValue)) \
+ BIND_GEN_IMPL(_class, _optionalGuard, \
+ String, \
+ (const nsACString &aWhere, \
+ const nsAString &aValue), \
+ (uint32_t aWhere, \
+ const nsAString &aValue), \
+ (aWhere, aValue)) \
+ BIND_GEN_IMPL(_class, _optionalGuard, \
+ Double, \
+ (const nsACString &aWhere, \
+ double aValue), \
+ (uint32_t aWhere, \
+ double aValue), \
+ (aWhere, aValue)) \
+ BIND_GEN_IMPL(_class, _optionalGuard, \
+ Int32, \
+ (const nsACString &aWhere, \
+ int32_t aValue), \
+ (uint32_t aWhere, \
+ int32_t aValue), \
+ (aWhere, aValue)) \
+ BIND_GEN_IMPL(_class, _optionalGuard, \
+ Int64, \
+ (const nsACString &aWhere, \
+ int64_t aValue), \
+ (uint32_t aWhere, \
+ int64_t aValue), \
+ (aWhere, aValue)) \
+ BIND_GEN_IMPL(_class, _optionalGuard, \
+ Null, \
+ (const nsACString &aWhere), \
+ (uint32_t aWhere), \
+ (aWhere)) \
+ BIND_GEN_IMPL(_class, _optionalGuard, \
+ Blob, \
+ (const nsACString &aWhere, \
+ const uint8_t *aValue, \
+ uint32_t aValueSize), \
+ (uint32_t aWhere, \
+ const uint8_t *aValue, \
+ uint32_t aValueSize), \
+ (aWhere, aValue, aValueSize)) \
+ BIND_GEN_IMPL(_class, _optionalGuard, \
+ StringAsBlob, \
+ (const nsACString &aWhere, \
+ const nsAString& aValue), \
+ (uint32_t aWhere, \
+ const nsAString& aValue), \
+ (aWhere, aValue)) \
+ BIND_GEN_IMPL(_class, _optionalGuard, \
+ UTF8StringAsBlob, \
+ (const nsACString &aWhere, \
+ const nsACString& aValue), \
+ (uint32_t aWhere, \
+ const nsACString& aValue), \
+ (aWhere, aValue)) \
+ BIND_GEN_IMPL(_class, _optionalGuard, \
+ AdoptedBlob, \
+ (const nsACString &aWhere, \
+ uint8_t *aValue, \
+ uint32_t aValueSize), \
+ (uint32_t aWhere, \
+ uint8_t *aValue, \
+ uint32_t aValueSize), \
+ (aWhere, aValue, aValueSize))
+
+
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozilla_storage_StorageBaseStatementInternal_h_
diff --git a/components/storage/src/TelemetryVFS.cpp b/components/storage/src/TelemetryVFS.cpp
new file mode 100644
index 000000000..060255ba4
--- /dev/null
+++ b/components/storage/src/TelemetryVFS.cpp
@@ -0,0 +1,827 @@
+/* -*- 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/. */
+
+/* This is a passthrough module initially set up to record telemetry via
+ an IO interposer. While the interposing plumbing is still intact to
+ avoid storage issues, telemetry recording has been removed
+ MCFIXME: Rewrite calls to go directly to SQLite and no longer through
+ this plumbing... */
+
+#include <string.h>
+#include "mozilla/Preferences.h"
+#include "sqlite3.h"
+#include "nsThreadUtils.h"
+#include "mozilla/dom/quota/PersistenceType.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/dom/quota/QuotaObject.h"
+#include "mozilla/IOInterposer.h"
+
+// The last VFS version for which this file has been updated.
+#define LAST_KNOWN_VFS_VERSION 3
+
+// The last io_methods version for which this file has been updated.
+#define LAST_KNOWN_IOMETHODS_VERSION 3
+
+/**
+ * This preference is a workaround to allow users/sysadmins to identify
+ * that the profile exists on an NFS share whose implementation
+ * is incompatible with SQLite's default locking implementation.
+ * Bug 433129 attempted to automatically identify such file-systems,
+ * but a reliable way was not found and it was determined that the fallback
+ * locking is slower than POSIX locking, so we do not want to do it by default.
+*/
+#define PREF_NFS_FILESYSTEM "storage.nfs_filesystem"
+
+namespace {
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+struct Histograms {
+ const char *name;
+};
+
+#define SQLITE_TELEMETRY(FILENAME, HGRAM) \
+ { FILENAME, \
+ }
+
+Histograms gHistograms[] = {
+ SQLITE_TELEMETRY("places.sqlite", PLACES),
+ SQLITE_TELEMETRY("cookies.sqlite", COOKIES),
+ SQLITE_TELEMETRY("webappsstore.sqlite", WEBAPPS),
+ SQLITE_TELEMETRY(nullptr, OTHER)
+};
+#undef SQLITE_TELEMETRY
+
+/** RAII class for measuring how long io takes on/off main thread
+ */
+class IOThreadAutoTimer {
+public:
+ /**
+ * IOThreadAutoTimer measures time spent in IO. Additionally it
+ * automatically determines whether IO is happening on the main
+ * thread and picks an appropriate histogram.
+ *
+ * @param aOp optionally takes an IO operation to report through the
+ * IOInterposer. Filename will be reported as NULL, and reference will be
+ * either "sqlite-mainthread" or "sqlite-otherthread".
+ */
+ explicit IOThreadAutoTimer(IOInterposeObserver::Operation aOp)
+ : start(TimeStamp::Now()),
+ op(aOp)
+ {
+ }
+
+ ~IOThreadAutoTimer()
+ {
+ // We don't report SQLite I/O on Windows because we have a comprehensive
+ // mechanism for intercepting I/O on that platform that captures a superset
+ // of the data captured here.
+ }
+
+private:
+ const TimeStamp start;
+ IOInterposeObserver::Operation op;
+};
+
+struct telemetry_file {
+ // Base class. Must be first
+ sqlite3_file base;
+
+ // histograms pertaining to this file
+ Histograms *histograms;
+
+ // quota object for this file
+ RefPtr<QuotaObject> quotaObject;
+
+ // The chunk size for this file. See the documentation for
+ // sqlite3_file_control() and FCNTL_CHUNK_SIZE.
+ int fileChunkSize;
+
+ // This contains the vfs that actually does work
+ sqlite3_file pReal[1];
+};
+
+const char*
+DatabasePathFromWALPath(const char *zWALName)
+{
+ /**
+ * Do some sketchy pointer arithmetic to find the parameter key. The WAL
+ * filename is in the middle of a big allocated block that contains:
+ *
+ * - Random Values
+ * - Main Database Path
+ * - \0
+ * - Multiple URI components consisting of:
+ * - Key
+ * - \0
+ * - Value
+ * - \0
+ * - \0
+ * - Journal Path
+ * - \0
+ * - WAL Path (zWALName)
+ * - \0
+ *
+ * Because the main database path is preceded by a random value we have to be
+ * careful when trying to figure out when we should terminate this loop.
+ */
+ MOZ_ASSERT(zWALName);
+
+ nsDependentCSubstring dbPath(zWALName, strlen(zWALName));
+
+ // Chop off the "-wal" suffix.
+ NS_NAMED_LITERAL_CSTRING(kWALSuffix, "-wal");
+ MOZ_ASSERT(StringEndsWith(dbPath, kWALSuffix));
+
+ dbPath.Rebind(zWALName, dbPath.Length() - kWALSuffix.Length());
+ MOZ_ASSERT(!dbPath.IsEmpty());
+
+ // We want to scan to the end of the key/value URI pairs. Skip the preceding
+ // null and go to the last char of the journal path.
+ const char* cursor = zWALName - 2;
+
+ // Make sure we just skipped a null.
+ MOZ_ASSERT(!*(cursor + 1));
+
+ // Walk backwards over the journal path.
+ while (*cursor) {
+ cursor--;
+ }
+
+ // There should be another null here.
+ cursor--;
+ MOZ_ASSERT(!*cursor);
+
+ // Back up one more char to the last char of the previous string. It may be
+ // the database path or it may be a key/value URI pair.
+ cursor--;
+
+#ifdef DEBUG
+ {
+ // Verify that we just walked over the journal path. Account for the two
+ // nulls we just skipped.
+ const char *journalStart = cursor + 3;
+
+ nsDependentCSubstring journalPath(journalStart,
+ strlen(journalStart));
+
+ // Chop off the "-journal" suffix.
+ NS_NAMED_LITERAL_CSTRING(kJournalSuffix, "-journal");
+ MOZ_ASSERT(StringEndsWith(journalPath, kJournalSuffix));
+
+ journalPath.Rebind(journalStart,
+ journalPath.Length() - kJournalSuffix.Length());
+ MOZ_ASSERT(!journalPath.IsEmpty());
+
+ // Make sure that the database name is a substring of the journal name.
+ MOZ_ASSERT(journalPath == dbPath);
+ }
+#endif
+
+ // Now we're either at the end of the key/value URI pairs or we're at the
+ // end of the database path. Carefully walk backwards one character at a
+ // time to do this safely without running past the beginning of the database
+ // path.
+ const char *const dbPathStart = dbPath.BeginReading();
+ const char *dbPathCursor = dbPath.EndReading() - 1;
+ bool isDBPath = true;
+
+ while (true) {
+ MOZ_ASSERT(*dbPathCursor, "dbPathCursor should never see a null char!");
+
+ if (isDBPath) {
+ isDBPath = dbPathStart <= dbPathCursor &&
+ *dbPathCursor == *cursor &&
+ *cursor;
+ }
+
+ if (!isDBPath) {
+ // This isn't the database path so it must be a value. Scan past it and
+ // the key also.
+ for (size_t stringCount = 0; stringCount < 2; stringCount++) {
+ // Scan past the string to the preceding null character.
+ while (*cursor) {
+ cursor--;
+ }
+
+ // Back up one more char to the last char of preceding string.
+ cursor--;
+ }
+
+ // Reset and start again.
+ dbPathCursor = dbPath.EndReading() - 1;
+ isDBPath = true;
+
+ continue;
+ }
+
+ MOZ_ASSERT(isDBPath);
+ MOZ_ASSERT(*cursor);
+
+ if (dbPathStart == dbPathCursor) {
+ // Found the full database path, we're all done.
+ MOZ_ASSERT(nsDependentCString(cursor) == dbPath);
+ return cursor;
+ }
+
+ // Change the cursors and go through the loop again.
+ cursor--;
+ dbPathCursor--;
+ }
+
+ MOZ_CRASH("Should never get here!");
+}
+
+already_AddRefed<QuotaObject>
+GetQuotaObjectFromNameAndParameters(const char *zName,
+ const char *zURIParameterKey)
+{
+ MOZ_ASSERT(zName);
+ MOZ_ASSERT(zURIParameterKey);
+
+ const char *persistenceType =
+ sqlite3_uri_parameter(zURIParameterKey, "persistenceType");
+ if (!persistenceType) {
+ return nullptr;
+ }
+
+ const char *group = sqlite3_uri_parameter(zURIParameterKey, "group");
+ if (!group) {
+ NS_WARNING("SQLite URI had 'persistenceType' but not 'group'?!");
+ return nullptr;
+ }
+
+ const char *origin = sqlite3_uri_parameter(zURIParameterKey, "origin");
+ if (!origin) {
+ NS_WARNING("SQLite URI had 'persistenceType' and 'group' but not "
+ "'origin'?!");
+ return nullptr;
+ }
+
+ QuotaManager *quotaManager = QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ return quotaManager->GetQuotaObject(
+ PersistenceTypeFromText(nsDependentCString(persistenceType)),
+ nsDependentCString(group),
+ nsDependentCString(origin),
+ NS_ConvertUTF8toUTF16(zName));
+}
+
+void
+MaybeEstablishQuotaControl(const char *zName,
+ telemetry_file *pFile,
+ int flags)
+{
+ MOZ_ASSERT(pFile);
+ MOZ_ASSERT(!pFile->quotaObject);
+
+ if (!(flags & (SQLITE_OPEN_URI | SQLITE_OPEN_WAL))) {
+ return;
+ }
+
+ MOZ_ASSERT(zName);
+
+ const char *zURIParameterKey = (flags & SQLITE_OPEN_WAL) ?
+ DatabasePathFromWALPath(zName) :
+ zName;
+
+ MOZ_ASSERT(zURIParameterKey);
+
+ pFile->quotaObject =
+ GetQuotaObjectFromNameAndParameters(zName, zURIParameterKey);
+}
+
+/*
+** Close a telemetry_file.
+*/
+int
+xClose(sqlite3_file *pFile)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ { // Scope for IOThreadAutoTimer
+ IOThreadAutoTimer ioTimer(IOInterposeObserver::OpClose);
+ rc = p->pReal->pMethods->xClose(p->pReal);
+ }
+ if( rc==SQLITE_OK ){
+ delete p->base.pMethods;
+ p->base.pMethods = nullptr;
+ p->quotaObject = nullptr;
+#ifdef DEBUG
+ p->fileChunkSize = 0;
+#endif
+ }
+ return rc;
+}
+
+/*
+** Read data from a telemetry_file.
+*/
+int
+xRead(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
+ // sqlite likes to read from empty files, this is normal, ignore it.
+ return rc;
+}
+
+/*
+** Return the current file-size of a telemetry_file.
+*/
+int
+xFileSize(sqlite3_file *pFile, sqlite_int64 *pSize)
+{
+ IOThreadAutoTimer ioTimer(IOInterposeObserver::OpStat);
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
+ return rc;
+}
+
+/*
+** Write data to a telemetry_file.
+*/
+int
+xWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ if (p->quotaObject) {
+ MOZ_ASSERT(INT64_MAX - iOfst >= iAmt);
+ if (!p->quotaObject->MaybeUpdateSize(iOfst + iAmt, /* aTruncate */ false)) {
+ return SQLITE_FULL;
+ }
+ }
+ rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
+ if (p->quotaObject && rc != SQLITE_OK) {
+ NS_WARNING("xWrite failed on a quota-controlled file, attempting to "
+ "update its current size...");
+ sqlite_int64 currentSize;
+ if (xFileSize(pFile, &currentSize) == SQLITE_OK) {
+ p->quotaObject->MaybeUpdateSize(currentSize, /* aTruncate */ true);
+ }
+ }
+ return rc;
+}
+
+/*
+** Truncate a telemetry_file.
+*/
+int
+xTruncate(sqlite3_file *pFile, sqlite_int64 size)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ if (p->quotaObject) {
+ if (p->fileChunkSize > 0) {
+ // Round up to the smallest multiple of the chunk size that will hold all
+ // the data.
+ size =
+ ((size + p->fileChunkSize - 1) / p->fileChunkSize) * p->fileChunkSize;
+ }
+ if (!p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true)) {
+ return SQLITE_FULL;
+ }
+ }
+ rc = p->pReal->pMethods->xTruncate(p->pReal, size);
+ if (p->quotaObject) {
+ if (rc != SQLITE_OK) {
+ NS_WARNING("xTruncate failed on a quota-controlled file, attempting to "
+ "update its current size...");
+ if (xFileSize(pFile, &size) == SQLITE_OK) {
+ p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true);
+ }
+ }
+ }
+ return rc;
+}
+
+/*
+** Sync a telemetry_file.
+*/
+int
+xSync(sqlite3_file *pFile, int flags)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ return p->pReal->pMethods->xSync(p->pReal, flags);
+}
+
+/*
+** Lock a telemetry_file.
+*/
+int
+xLock(sqlite3_file *pFile, int eLock)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ rc = p->pReal->pMethods->xLock(p->pReal, eLock);
+ return rc;
+}
+
+/*
+** Unlock a telemetry_file.
+*/
+int
+xUnlock(sqlite3_file *pFile, int eLock)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ rc = p->pReal->pMethods->xUnlock(p->pReal, eLock);
+ return rc;
+}
+
+/*
+** Check if another file-handle holds a RESERVED lock on a telemetry_file.
+*/
+int
+xCheckReservedLock(sqlite3_file *pFile, int *pResOut)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
+ return rc;
+}
+
+/*
+** File control method. For custom operations on a telemetry_file.
+*/
+int
+xFileControl(sqlite3_file *pFile, int op, void *pArg)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ // Hook SQLITE_FCNTL_SIZE_HINT for quota-controlled files and do the necessary
+ // work before passing to the SQLite VFS.
+ if (op == SQLITE_FCNTL_SIZE_HINT && p->quotaObject) {
+ sqlite3_int64 hintSize = *static_cast<sqlite3_int64*>(pArg);
+ sqlite3_int64 currentSize;
+ rc = xFileSize(pFile, &currentSize);
+ if (rc != SQLITE_OK) {
+ return rc;
+ }
+ if (hintSize > currentSize) {
+ rc = xTruncate(pFile, hintSize);
+ if (rc != SQLITE_OK) {
+ return rc;
+ }
+ }
+ }
+ rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
+ // Grab the file chunk size after the SQLite VFS has approved.
+ if (op == SQLITE_FCNTL_CHUNK_SIZE && rc == SQLITE_OK) {
+ p->fileChunkSize = *static_cast<int*>(pArg);
+ }
+ return rc;
+}
+
+/*
+** Return the sector-size in bytes for a telemetry_file.
+*/
+int
+xSectorSize(sqlite3_file *pFile)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ rc = p->pReal->pMethods->xSectorSize(p->pReal);
+ return rc;
+}
+
+/*
+** Return the device characteristic flags supported by a telemetry_file.
+*/
+int
+xDeviceCharacteristics(sqlite3_file *pFile)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
+ return rc;
+}
+
+/*
+** Shared-memory operations.
+*/
+int
+xShmLock(sqlite3_file *pFile, int ofst, int n, int flags)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ return p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags);
+}
+
+int
+xShmMap(sqlite3_file *pFile, int iRegion, int szRegion, int isWrite, void volatile **pp)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp);
+ return rc;
+}
+
+void
+xShmBarrier(sqlite3_file *pFile){
+ telemetry_file *p = (telemetry_file *)pFile;
+ p->pReal->pMethods->xShmBarrier(p->pReal);
+}
+
+int
+xShmUnmap(sqlite3_file *pFile, int delFlag){
+ telemetry_file *p = (telemetry_file *)pFile;
+ int rc;
+ rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag);
+ return rc;
+}
+
+int
+xFetch(sqlite3_file *pFile, sqlite3_int64 iOff, int iAmt, void **pp)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ MOZ_ASSERT(p->pReal->pMethods->iVersion >= 3);
+ return p->pReal->pMethods->xFetch(p->pReal, iOff, iAmt, pp);
+}
+
+int
+xUnfetch(sqlite3_file *pFile, sqlite3_int64 iOff, void *pResOut)
+{
+ telemetry_file *p = (telemetry_file *)pFile;
+ MOZ_ASSERT(p->pReal->pMethods->iVersion >= 3);
+ return p->pReal->pMethods->xUnfetch(p->pReal, iOff, pResOut);
+}
+
+int
+xOpen(sqlite3_vfs* vfs, const char *zName, sqlite3_file* pFile,
+ int flags, int *pOutFlags)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ int rc;
+ telemetry_file *p = (telemetry_file *)pFile;
+ Histograms *h = nullptr;
+ // check if the filename is one we are probing for
+ for(size_t i = 0;i < sizeof(gHistograms)/sizeof(gHistograms[0]);i++) {
+ h = &gHistograms[i];
+ // last probe is the fallback probe
+ if (!h->name)
+ break;
+ if (!zName)
+ continue;
+ const char *match = strstr(zName, h->name);
+ if (!match)
+ continue;
+ char c = match[strlen(h->name)];
+ // include -wal/-journal too
+ if (!c || c == '-')
+ break;
+ }
+ p->histograms = h;
+
+ MaybeEstablishQuotaControl(zName, p, flags);
+
+ rc = orig_vfs->xOpen(orig_vfs, zName, p->pReal, flags, pOutFlags);
+ if( rc != SQLITE_OK )
+ return rc;
+ if( p->pReal->pMethods ){
+ sqlite3_io_methods *pNew = new sqlite3_io_methods;
+ const sqlite3_io_methods *pSub = p->pReal->pMethods;
+ memset(pNew, 0, sizeof(*pNew));
+ // If the io_methods version is higher than the last known one, you should
+ // update this VFS adding appropriate IO methods for any methods added in
+ // the version change.
+ pNew->iVersion = pSub->iVersion;
+ MOZ_ASSERT(pNew->iVersion <= LAST_KNOWN_IOMETHODS_VERSION);
+ pNew->xClose = xClose;
+ pNew->xRead = xRead;
+ pNew->xWrite = xWrite;
+ pNew->xTruncate = xTruncate;
+ pNew->xSync = xSync;
+ pNew->xFileSize = xFileSize;
+ pNew->xLock = xLock;
+ pNew->xUnlock = xUnlock;
+ pNew->xCheckReservedLock = xCheckReservedLock;
+ pNew->xFileControl = xFileControl;
+ pNew->xSectorSize = xSectorSize;
+ pNew->xDeviceCharacteristics = xDeviceCharacteristics;
+ if (pNew->iVersion >= 2) {
+ // Methods added in version 2.
+ pNew->xShmMap = pSub->xShmMap ? xShmMap : 0;
+ pNew->xShmLock = pSub->xShmLock ? xShmLock : 0;
+ pNew->xShmBarrier = pSub->xShmBarrier ? xShmBarrier : 0;
+ pNew->xShmUnmap = pSub->xShmUnmap ? xShmUnmap : 0;
+ }
+ if (pNew->iVersion >= 3) {
+ // Methods added in version 3.
+ // SQLite 3.7.17 calls these methods without checking for nullptr first,
+ // so we always define them. Verify that we're not going to call
+ // nullptrs, though.
+ MOZ_ASSERT(pSub->xFetch);
+ pNew->xFetch = xFetch;
+ MOZ_ASSERT(pSub->xUnfetch);
+ pNew->xUnfetch = xUnfetch;
+ }
+ pFile->pMethods = pNew;
+ }
+ return rc;
+}
+
+int
+xDelete(sqlite3_vfs* vfs, const char *zName, int syncDir)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ int rc;
+ RefPtr<QuotaObject> quotaObject;
+
+ if (StringEndsWith(nsDependentCString(zName), NS_LITERAL_CSTRING("-wal"))) {
+ const char *zURIParameterKey = DatabasePathFromWALPath(zName);
+ MOZ_ASSERT(zURIParameterKey);
+
+ quotaObject = GetQuotaObjectFromNameAndParameters(zName, zURIParameterKey);
+ }
+
+ rc = orig_vfs->xDelete(orig_vfs, zName, syncDir);
+ if (rc == SQLITE_OK && quotaObject) {
+ MOZ_ALWAYS_TRUE(quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
+ }
+
+ return rc;
+}
+
+int
+xAccess(sqlite3_vfs *vfs, const char *zName, int flags, int *pResOut)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xAccess(orig_vfs, zName, flags, pResOut);
+}
+
+int
+xFullPathname(sqlite3_vfs *vfs, const char *zName, int nOut, char *zOut)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xFullPathname(orig_vfs, zName, nOut, zOut);
+}
+
+void*
+xDlOpen(sqlite3_vfs *vfs, const char *zFilename)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xDlOpen(orig_vfs, zFilename);
+}
+
+void
+xDlError(sqlite3_vfs *vfs, int nByte, char *zErrMsg)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ orig_vfs->xDlError(orig_vfs, nByte, zErrMsg);
+}
+
+void
+(*xDlSym(sqlite3_vfs *vfs, void *pHdle, const char *zSym))(void){
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xDlSym(orig_vfs, pHdle, zSym);
+}
+
+void
+xDlClose(sqlite3_vfs *vfs, void *pHandle)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ orig_vfs->xDlClose(orig_vfs, pHandle);
+}
+
+int
+xRandomness(sqlite3_vfs *vfs, int nByte, char *zOut)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xRandomness(orig_vfs, nByte, zOut);
+}
+
+int
+xSleep(sqlite3_vfs *vfs, int microseconds)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xSleep(orig_vfs, microseconds);
+}
+
+int
+xCurrentTime(sqlite3_vfs *vfs, double *prNow)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xCurrentTime(orig_vfs, prNow);
+}
+
+int
+xGetLastError(sqlite3_vfs *vfs, int nBuf, char *zBuf)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xGetLastError(orig_vfs, nBuf, zBuf);
+}
+
+int
+xCurrentTimeInt64(sqlite3_vfs *vfs, sqlite3_int64 *piNow)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xCurrentTimeInt64(orig_vfs, piNow);
+}
+
+static
+int
+xSetSystemCall(sqlite3_vfs *vfs, const char *zName, sqlite3_syscall_ptr pFunc)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xSetSystemCall(orig_vfs, zName, pFunc);
+}
+
+static
+sqlite3_syscall_ptr
+xGetSystemCall(sqlite3_vfs *vfs, const char *zName)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xGetSystemCall(orig_vfs, zName);
+}
+
+static
+const char *
+xNextSystemCall(sqlite3_vfs *vfs, const char *zName)
+{
+ sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
+ return orig_vfs->xNextSystemCall(orig_vfs, zName);
+}
+
+} // namespace
+
+namespace mozilla {
+namespace storage {
+
+sqlite3_vfs* ConstructTelemetryVFS()
+{
+#if defined(XP_WIN)
+#define EXPECTED_VFS "win32"
+#define EXPECTED_VFS_NFS "win32"
+#else
+#define EXPECTED_VFS "unix"
+#define EXPECTED_VFS_NFS "unix-excl"
+#endif
+
+ bool expected_vfs;
+ sqlite3_vfs *vfs;
+ if (Preferences::GetBool(PREF_NFS_FILESYSTEM)) {
+ vfs = sqlite3_vfs_find(EXPECTED_VFS_NFS);
+ expected_vfs = (vfs != nullptr);
+ }
+ else {
+ vfs = sqlite3_vfs_find(nullptr);
+ expected_vfs = vfs->zName && !strcmp(vfs->zName, EXPECTED_VFS);
+ }
+ if (!expected_vfs) {
+ return nullptr;
+ }
+
+ sqlite3_vfs *tvfs = new ::sqlite3_vfs;
+ memset(tvfs, 0, sizeof(::sqlite3_vfs));
+ // If the VFS version is higher than the last known one, you should update
+ // this VFS adding appropriate methods for any methods added in the version
+ // change.
+ tvfs->iVersion = vfs->iVersion;
+ MOZ_ASSERT(vfs->iVersion <= LAST_KNOWN_VFS_VERSION);
+ tvfs->szOsFile = sizeof(telemetry_file) - sizeof(sqlite3_file) + vfs->szOsFile;
+ tvfs->mxPathname = vfs->mxPathname;
+ tvfs->zName = "telemetry-vfs";
+ tvfs->pAppData = vfs;
+ tvfs->xOpen = xOpen;
+ tvfs->xDelete = xDelete;
+ tvfs->xAccess = xAccess;
+ tvfs->xFullPathname = xFullPathname;
+ tvfs->xDlOpen = xDlOpen;
+ tvfs->xDlError = xDlError;
+ tvfs->xDlSym = xDlSym;
+ tvfs->xDlClose = xDlClose;
+ tvfs->xRandomness = xRandomness;
+ tvfs->xSleep = xSleep;
+ tvfs->xCurrentTime = xCurrentTime;
+ tvfs->xGetLastError = xGetLastError;
+ if (tvfs->iVersion >= 2) {
+ // Methods added in version 2.
+ tvfs->xCurrentTimeInt64 = xCurrentTimeInt64;
+ }
+ if (tvfs->iVersion >= 3) {
+ // Methods added in version 3.
+ tvfs->xSetSystemCall = xSetSystemCall;
+ tvfs->xGetSystemCall = xGetSystemCall;
+ tvfs->xNextSystemCall = xNextSystemCall;
+ }
+ return tvfs;
+}
+
+already_AddRefed<QuotaObject>
+GetQuotaObjectForFile(sqlite3_file *pFile)
+{
+ MOZ_ASSERT(pFile);
+
+ telemetry_file *p = (telemetry_file *)pFile;
+ RefPtr<QuotaObject> result = p->quotaObject;
+ return result.forget();
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/VacuumManager.cpp b/components/storage/src/VacuumManager.cpp
new file mode 100644
index 000000000..f35ded2d6
--- /dev/null
+++ b/components/storage/src/VacuumManager.cpp
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/DebugOnly.h"
+
+#include "VacuumManager.h"
+
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "nsIObserverService.h"
+#include "nsIFile.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Logging.h"
+#include "prtime.h"
+
+#include "mozStorageConnection.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageAsyncStatement.h"
+#include "mozIStoragePendingStatement.h"
+#include "mozIStorageError.h"
+#include "mozStorageHelper.h"
+#include "nsXULAppAPI.h"
+
+#define OBSERVER_TOPIC_IDLE_DAILY "idle-daily"
+#define OBSERVER_TOPIC_XPCOM_SHUTDOWN "xpcom-shutdown"
+
+// Used to notify begin and end of a heavy IO task.
+#define OBSERVER_TOPIC_HEAVY_IO "heavy-io-task"
+#define OBSERVER_DATA_VACUUM_BEGIN NS_LITERAL_STRING("vacuum-begin")
+#define OBSERVER_DATA_VACUUM_END NS_LITERAL_STRING("vacuum-end")
+
+// This preferences root will contain last vacuum timestamps (in seconds) for
+// each database. The database filename is used as a key.
+#define PREF_VACUUM_BRANCH "storage.vacuum.last."
+
+// Time between subsequent vacuum calls for a certain database.
+#define VACUUM_INTERVAL_SECONDS 30 * 86400 // 30 days.
+
+extern mozilla::LazyLogModule gStorageLog;
+
+namespace mozilla {
+namespace storage {
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+//// BaseCallback
+
+class BaseCallback : public mozIStorageStatementCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGESTATEMENTCALLBACK
+ BaseCallback() {}
+protected:
+ virtual ~BaseCallback() {}
+};
+
+NS_IMETHODIMP
+BaseCallback::HandleError(mozIStorageError *aError)
+{
+#ifdef DEBUG
+ int32_t result;
+ nsresult rv = aError->GetResult(&result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString message;
+ rv = aError->GetMessage(message);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString warnMsg;
+ warnMsg.AppendLiteral("An error occured during async execution: ");
+ warnMsg.AppendInt(result);
+ warnMsg.Append(' ');
+ warnMsg.Append(message);
+ NS_WARNING(warnMsg.get());
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseCallback::HandleResult(mozIStorageResultSet *aResultSet)
+{
+ // We could get results from PRAGMA statements, but we don't mind them.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseCallback::HandleCompletion(uint16_t aReason)
+{
+ // By default BaseCallback will just be silent on completion.
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(
+ BaseCallback
+, mozIStorageStatementCallback
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// Vacuumer declaration.
+
+class Vacuumer : public BaseCallback
+{
+public:
+ NS_DECL_MOZISTORAGESTATEMENTCALLBACK
+
+ explicit Vacuumer(mozIStorageVacuumParticipant *aParticipant);
+
+ bool execute();
+ nsresult notifyCompletion(bool aSucceeded);
+
+private:
+ nsCOMPtr<mozIStorageVacuumParticipant> mParticipant;
+ nsCString mDBFilename;
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Vacuumer implementation.
+
+Vacuumer::Vacuumer(mozIStorageVacuumParticipant *aParticipant)
+ : mParticipant(aParticipant)
+{
+}
+
+bool
+Vacuumer::execute()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Must be running on the main thread!");
+
+ // Get the connection and check its validity.
+ nsresult rv = mParticipant->GetDatabaseConnection(getter_AddRefs(mDBConn));
+ NS_ENSURE_SUCCESS(rv, false);
+ bool ready = false;
+ if (!mDBConn || NS_FAILED(mDBConn->GetConnectionReady(&ready)) || !ready) {
+ NS_WARNING("Unable to get a connection to vacuum database");
+ return false;
+ }
+
+ // Ask for the expected page size. Vacuum can change the page size, unless
+ // the database is using WAL journaling.
+ // TODO Bug 634374: figure out a strategy to fix page size with WAL.
+ int32_t expectedPageSize = 0;
+ rv = mParticipant->GetExpectedDatabasePageSize(&expectedPageSize);
+ if (NS_FAILED(rv) || !Service::pageSizeIsValid(expectedPageSize)) {
+ NS_WARNING("Invalid page size requested for database, will use default ");
+ NS_WARNING(mDBFilename.get());
+ expectedPageSize = Service::getDefaultPageSize();
+ }
+
+ // Get the database filename. Last vacuum time is stored under this name
+ // in PREF_VACUUM_BRANCH.
+ nsCOMPtr<nsIFile> databaseFile;
+ mDBConn->GetDatabaseFile(getter_AddRefs(databaseFile));
+ if (!databaseFile) {
+ NS_WARNING("Trying to vacuum a in-memory database!");
+ return false;
+ }
+ nsAutoString databaseFilename;
+ rv = databaseFile->GetLeafName(databaseFilename);
+ NS_ENSURE_SUCCESS(rv, false);
+ mDBFilename = NS_ConvertUTF16toUTF8(databaseFilename);
+ MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
+
+ // Check interval from last vacuum.
+ int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
+ int32_t lastVacuum;
+ nsAutoCString prefName(PREF_VACUUM_BRANCH);
+ prefName += mDBFilename;
+ rv = Preferences::GetInt(prefName.get(), &lastVacuum);
+ if (NS_SUCCEEDED(rv) && (now - lastVacuum) < VACUUM_INTERVAL_SECONDS) {
+ // This database was vacuumed recently, skip it.
+ return false;
+ }
+
+ // Notify that we are about to start vacuuming. The participant can opt-out
+ // if it cannot handle a vacuum at this time, and then we'll move to the next
+ // one.
+ bool vacuumGranted = false;
+ rv = mParticipant->OnBeginVacuum(&vacuumGranted);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (!vacuumGranted) {
+ return false;
+ }
+
+ // Notify a heavy IO task is about to start.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ rv =
+ os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO,
+ OBSERVER_DATA_VACUUM_BEGIN.get());
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to notify");
+ }
+
+ // Execute the statements separately, since the pragma may conflict with the
+ // vacuum, if they are executed in the same transaction.
+ nsCOMPtr<mozIStorageAsyncStatement> pageSizeStmt;
+ nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
+ "PRAGMA page_size = ");
+ pageSizeQuery.AppendInt(expectedPageSize);
+ rv = mDBConn->CreateAsyncStatement(pageSizeQuery,
+ getter_AddRefs(pageSizeStmt));
+ NS_ENSURE_SUCCESS(rv, false);
+ RefPtr<BaseCallback> callback = new BaseCallback();
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = pageSizeStmt->ExecuteAsync(callback, getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "VACUUM"
+ ), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = stmt->ExecuteAsync(this, getter_AddRefs(ps));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageStatementCallback
+
+NS_IMETHODIMP
+Vacuumer::HandleError(mozIStorageError *aError)
+{
+ int32_t result;
+ nsresult rv;
+ nsAutoCString message;
+
+#ifdef DEBUG
+ rv = aError->GetResult(&result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aError->GetMessage(message);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString warnMsg;
+ warnMsg.AppendLiteral("Unable to vacuum database: ");
+ warnMsg.Append(mDBFilename);
+ warnMsg.AppendLiteral(" - ");
+ warnMsg.AppendInt(result);
+ warnMsg.Append(' ');
+ warnMsg.Append(message);
+ NS_WARNING(warnMsg.get());
+#endif
+
+ if (MOZ_LOG_TEST(gStorageLog, LogLevel::Error)) {
+ rv = aError->GetResult(&result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aError->GetMessage(message);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_LOG(gStorageLog, LogLevel::Error,
+ ("Vacuum failed with error: %d '%s'. Database was: '%s'",
+ result, message.get(), mDBFilename.get()));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Vacuumer::HandleResult(mozIStorageResultSet *aResultSet)
+{
+ NS_NOTREACHED("Got a resultset from a vacuum?");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Vacuumer::HandleCompletion(uint16_t aReason)
+{
+ if (aReason == REASON_FINISHED) {
+ // Update last vacuum time.
+ int32_t now = static_cast<int32_t>(PR_Now() / PR_USEC_PER_SEC);
+ MOZ_ASSERT(!mDBFilename.IsEmpty(), "Database filename cannot be empty");
+ nsAutoCString prefName(PREF_VACUUM_BRANCH);
+ prefName += mDBFilename;
+ DebugOnly<nsresult> rv = Preferences::SetInt(prefName.get(), now);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
+ }
+
+ notifyCompletion(aReason == REASON_FINISHED);
+
+ return NS_OK;
+}
+
+nsresult
+Vacuumer::notifyCompletion(bool aSucceeded)
+{
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(nullptr, OBSERVER_TOPIC_HEAVY_IO,
+ OBSERVER_DATA_VACUUM_END.get());
+ }
+
+ nsresult rv = mParticipant->OnEndVacuum(aSucceeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// VacuumManager
+
+NS_IMPL_ISUPPORTS(
+ VacuumManager
+, nsIObserver
+)
+
+VacuumManager *
+VacuumManager::gVacuumManager = nullptr;
+
+VacuumManager *
+VacuumManager::getSingleton()
+{
+ //Don't allocate it in the child Process.
+ if (!XRE_IsParentProcess()) {
+ return nullptr;
+ }
+
+ if (gVacuumManager) {
+ NS_ADDREF(gVacuumManager);
+ return gVacuumManager;
+ }
+ gVacuumManager = new VacuumManager();
+ if (gVacuumManager) {
+ NS_ADDREF(gVacuumManager);
+ }
+ return gVacuumManager;
+}
+
+VacuumManager::VacuumManager()
+ : mParticipants("vacuum-participant")
+{
+ MOZ_ASSERT(!gVacuumManager,
+ "Attempting to create two instances of the service!");
+ gVacuumManager = this;
+}
+
+VacuumManager::~VacuumManager()
+{
+ // Remove the static reference to the service. Check to make sure its us
+ // in case somebody creates an extra instance of the service.
+ MOZ_ASSERT(gVacuumManager == this,
+ "Deleting a non-singleton instance of the service");
+ if (gVacuumManager == this) {
+ gVacuumManager = nullptr;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+VacuumManager::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ if (strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY) == 0) {
+ // Try to run vacuum on all registered entries. Will stop at the first
+ // successful one.
+ nsCOMArray<mozIStorageVacuumParticipant> entries;
+ mParticipants.GetEntries(entries);
+ // If there are more entries than what a month can contain, we could end up
+ // skipping some, since we run daily. So we use a starting index.
+ static const char* kPrefName = PREF_VACUUM_BRANCH "index";
+ int32_t startIndex = Preferences::GetInt(kPrefName, 0);
+ if (startIndex >= entries.Count()) {
+ startIndex = 0;
+ }
+ int32_t index;
+ for (index = startIndex; index < entries.Count(); ++index) {
+ RefPtr<Vacuumer> vacuum = new Vacuumer(entries[index]);
+ // Only vacuum one database per day.
+ if (vacuum->execute()) {
+ break;
+ }
+ }
+ DebugOnly<nsresult> rv = Preferences::SetInt(kPrefName, index);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Should be able to set a preference");
+ }
+
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/VacuumManager.h b/components/storage/src/VacuumManager.h
new file mode 100644
index 000000000..12603deb6
--- /dev/null
+++ b/components/storage/src/VacuumManager.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_VacuumManager_h__
+#define mozilla_storage_VacuumManager_h__
+
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "mozIStorageStatementCallback.h"
+#include "mozIStorageVacuumParticipant.h"
+#include "nsCategoryCache.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace storage {
+
+class VacuumManager final : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ VacuumManager();
+
+ /**
+ * Obtains the VacuumManager object.
+ */
+ static VacuumManager * getSingleton();
+
+private:
+ ~VacuumManager();
+
+ static VacuumManager *gVacuumManager;
+
+ // Cache of components registered in "vacuum-participant" category.
+ nsCategoryCache<mozIStorageVacuumParticipant> mParticipants;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif
diff --git a/components/storage/src/Variant.h b/components/storage/src/Variant.h
new file mode 100644
index 000000000..265abb02a
--- /dev/null
+++ b/components/storage/src/Variant.h
@@ -0,0 +1,446 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_Variant_h__
+#define mozilla_storage_Variant_h__
+
+#include <utility>
+
+#include "nsIVariant.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#define VARIANT_BASE_IID \
+{ /* 78888042-0fa3-4f7a-8b19-7996f99bf1aa */ \
+ 0x78888042, 0x0fa3, 0x4f7a, \
+ { 0x8b, 0x19, 0x79, 0x96, 0xf9, 0x9b, 0xf1, 0xaa } \
+}
+
+/**
+ * This class is used by the storage module whenever an nsIVariant needs to be
+ * returned. We provide traits for the basic sqlite types to make use easier.
+ * The following types map to the indicated sqlite type:
+ * int64_t -> INTEGER (use IntegerVariant)
+ * double -> FLOAT (use FloatVariant)
+ * nsString -> TEXT (use TextVariant)
+ * nsCString -> TEXT (use UTF8TextVariant)
+ * uint8_t[] -> BLOB (use BlobVariant)
+ * nullptr -> NULL (use NullVariant)
+ */
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Base Class
+
+class Variant_base : public nsIVariant
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIVARIANT
+ NS_DECLARE_STATIC_IID_ACCESSOR(VARIANT_BASE_IID)
+
+protected:
+ virtual ~Variant_base() { }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Variant_base,
+ VARIANT_BASE_IID)
+
+////////////////////////////////////////////////////////////////////////////////
+//// Traits
+
+/**
+ * Generics
+ */
+
+template <typename DataType>
+struct variant_traits
+{
+ static inline uint16_t type() { return nsIDataType::VTYPE_EMPTY; }
+};
+
+template <typename DataType, bool Adopting=false>
+struct variant_storage_traits
+{
+ typedef DataType ConstructorType;
+ typedef DataType StorageType;
+ static inline void storage_conversion(const ConstructorType aData, StorageType* _storage)
+ {
+ *_storage = aData;
+ }
+
+ static inline void destroy(const StorageType& _storage)
+ { }
+};
+
+#define NO_CONVERSION return NS_ERROR_CANNOT_CONVERT_DATA;
+
+template <typename DataType, bool Adopting=false>
+struct variant_integer_traits
+{
+ typedef typename variant_storage_traits<DataType, Adopting>::StorageType StorageType;
+ static inline nsresult asInt32(const StorageType &, int32_t *) { NO_CONVERSION }
+ static inline nsresult asInt64(const StorageType &, int64_t *) { NO_CONVERSION }
+};
+
+template <typename DataType, bool Adopting=false>
+struct variant_float_traits
+{
+ typedef typename variant_storage_traits<DataType, Adopting>::StorageType StorageType;
+ static inline nsresult asDouble(const StorageType &, double *) { NO_CONVERSION }
+};
+
+template <typename DataType, bool Adopting=false>
+struct variant_text_traits
+{
+ typedef typename variant_storage_traits<DataType, Adopting>::StorageType StorageType;
+ static inline nsresult asUTF8String(const StorageType &, nsACString &) { NO_CONVERSION }
+ static inline nsresult asString(const StorageType &, nsAString &) { NO_CONVERSION }
+};
+
+template <typename DataType, bool Adopting=false>
+struct variant_blob_traits
+{
+ typedef typename variant_storage_traits<DataType, Adopting>::StorageType StorageType;
+ static inline nsresult asArray(const StorageType &, uint16_t *, uint32_t *, void **)
+ { NO_CONVERSION }
+};
+
+#undef NO_CONVERSION
+
+/**
+ * INTEGER types
+ */
+
+template < >
+struct variant_traits<int64_t>
+{
+ static inline uint16_t type() { return nsIDataType::VTYPE_INT64; }
+};
+template < >
+struct variant_integer_traits<int64_t>
+{
+ static inline nsresult asInt32(int64_t aValue,
+ int32_t *_result)
+ {
+ if (aValue > INT32_MAX || aValue < INT32_MIN)
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+
+ *_result = static_cast<int32_t>(aValue);
+ return NS_OK;
+ }
+ static inline nsresult asInt64(int64_t aValue,
+ int64_t *_result)
+ {
+ *_result = aValue;
+ return NS_OK;
+ }
+};
+// xpcvariant just calls get double for integers...
+template < >
+struct variant_float_traits<int64_t>
+{
+ static inline nsresult asDouble(int64_t aValue,
+ double *_result)
+ {
+ *_result = double(aValue);
+ return NS_OK;
+ }
+};
+
+/**
+ * FLOAT types
+ */
+
+template < >
+struct variant_traits<double>
+{
+ static inline uint16_t type() { return nsIDataType::VTYPE_DOUBLE; }
+};
+template < >
+struct variant_float_traits<double>
+{
+ static inline nsresult asDouble(double aValue,
+ double *_result)
+ {
+ *_result = aValue;
+ return NS_OK;
+ }
+};
+
+/**
+ * TEXT types
+ */
+
+template < >
+struct variant_traits<nsString>
+{
+ static inline uint16_t type() { return nsIDataType::VTYPE_ASTRING; }
+};
+template < >
+struct variant_storage_traits<nsString>
+{
+ typedef const nsAString & ConstructorType;
+ typedef nsString StorageType;
+ static inline void storage_conversion(ConstructorType aText, StorageType* _outData)
+ {
+ *_outData = aText;
+ }
+ static inline void destroy(const StorageType& _outData)
+ { }
+};
+template < >
+struct variant_text_traits<nsString>
+{
+ static inline nsresult asUTF8String(const nsString &aValue,
+ nsACString &_result)
+ {
+ CopyUTF16toUTF8(aValue, _result);
+ return NS_OK;
+ }
+ static inline nsresult asString(const nsString &aValue,
+ nsAString &_result)
+ {
+ _result = aValue;
+ return NS_OK;
+ }
+};
+
+template < >
+struct variant_traits<nsCString>
+{
+ static inline uint16_t type() { return nsIDataType::VTYPE_UTF8STRING; }
+};
+template < >
+struct variant_storage_traits<nsCString>
+{
+ typedef const nsACString & ConstructorType;
+ typedef nsCString StorageType;
+ static inline void storage_conversion(ConstructorType aText, StorageType* _outData)
+ {
+ *_outData = aText;
+ }
+ static inline void destroy(const StorageType &aData)
+ { }
+};
+template < >
+struct variant_text_traits<nsCString>
+{
+ static inline nsresult asUTF8String(const nsCString &aValue,
+ nsACString &_result)
+ {
+ _result = aValue;
+ return NS_OK;
+ }
+ static inline nsresult asString(const nsCString &aValue,
+ nsAString &_result)
+ {
+ CopyUTF8toUTF16(aValue, _result);
+ return NS_OK;
+ }
+};
+
+/**
+ * BLOB types
+ */
+
+template < >
+struct variant_traits<uint8_t[]>
+{
+ static inline uint16_t type() { return nsIDataType::VTYPE_ARRAY; }
+};
+template < >
+struct variant_storage_traits<uint8_t[], false>
+{
+ typedef std::pair<const void *, int> ConstructorType;
+ typedef FallibleTArray<uint8_t> StorageType;
+ static inline void storage_conversion(ConstructorType aBlob, StorageType* _outData)
+ {
+ _outData->Clear();
+ (void)_outData->AppendElements(static_cast<const uint8_t *>(aBlob.first),
+ aBlob.second, fallible);
+ }
+ static inline void destroy(const StorageType& _outData)
+ { }
+};
+template < >
+struct variant_storage_traits<uint8_t[], true>
+{
+ typedef std::pair<uint8_t *, int> ConstructorType;
+ typedef std::pair<uint8_t *, int> StorageType;
+ static inline void storage_conversion(ConstructorType aBlob, StorageType* _outData)
+ {
+ *_outData = aBlob;
+ }
+ static inline void destroy(StorageType &aData)
+ {
+ if (aData.first) {
+ free(aData.first);
+ aData.first = nullptr;
+ }
+ }
+};
+template < >
+struct variant_blob_traits<uint8_t[], false>
+{
+ static inline nsresult asArray(FallibleTArray<uint8_t> &aData,
+ uint16_t *_type,
+ uint32_t *_size,
+ void **_result)
+ {
+ // For empty blobs, we return nullptr.
+ if (aData.Length() == 0) {
+ *_result = nullptr;
+ *_type = nsIDataType::VTYPE_UINT8;
+ *_size = 0;
+ return NS_OK;
+ }
+
+ // Otherwise, we copy the array.
+ *_result = nsMemory::Clone(aData.Elements(), aData.Length() * sizeof(uint8_t));
+ NS_ENSURE_TRUE(*_result, NS_ERROR_OUT_OF_MEMORY);
+
+ // Set type and size
+ *_type = nsIDataType::VTYPE_UINT8;
+ *_size = aData.Length();
+ return NS_OK;
+ }
+};
+
+template < >
+struct variant_blob_traits<uint8_t[], true>
+{
+ static inline nsresult asArray(std::pair<uint8_t *, int> &aData,
+ uint16_t *_type,
+ uint32_t *_size,
+ void **_result)
+ {
+ // For empty blobs, we return nullptr.
+ if (aData.second == 0) {
+ *_result = nullptr;
+ *_type = nsIDataType::VTYPE_UINT8;
+ *_size = 0;
+ return NS_OK;
+ }
+
+ // Otherwise, transfer the data out.
+ *_result = aData.first;
+ aData.first = nullptr;
+ MOZ_ASSERT(*_result); // We asked for it twice, better not use adopting!
+
+ // Set type and size
+ *_type = nsIDataType::VTYPE_UINT8;
+ *_size = aData.second;
+ return NS_OK;
+ }
+};
+
+/**
+ * nullptr type
+ */
+
+class NullVariant : public Variant_base
+{
+public:
+ NS_IMETHOD GetDataType(uint16_t *_type)
+ {
+ NS_ENSURE_ARG_POINTER(_type);
+ *_type = nsIDataType::VTYPE_EMPTY;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetAsAUTF8String(nsACString &_str)
+ {
+ // Return a void string.
+ _str.SetIsVoid(true);
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetAsAString(nsAString &_str)
+ {
+ // Return a void string.
+ _str.SetIsVoid(true);
+ return NS_OK;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Template Implementation
+
+template <typename DataType, bool Adopting=false>
+class Variant final : public Variant_base
+{
+ ~Variant()
+ {
+ variant_storage_traits<DataType, Adopting>::destroy(mData);
+ }
+
+public:
+ explicit Variant(const typename variant_storage_traits<DataType, Adopting>::ConstructorType aData)
+ {
+ variant_storage_traits<DataType, Adopting>::storage_conversion(aData, &mData);
+ }
+
+ NS_IMETHOD GetDataType(uint16_t *_type)
+ {
+ *_type = variant_traits<DataType>::type();
+ return NS_OK;
+ }
+ NS_IMETHOD GetAsInt32(int32_t *_integer)
+ {
+ return variant_integer_traits<DataType, Adopting>::asInt32(mData, _integer);
+ }
+
+ NS_IMETHOD GetAsInt64(int64_t *_integer)
+ {
+ return variant_integer_traits<DataType, Adopting>::asInt64(mData, _integer);
+ }
+
+ NS_IMETHOD GetAsDouble(double *_double)
+ {
+ return variant_float_traits<DataType, Adopting>::asDouble(mData, _double);
+ }
+
+ NS_IMETHOD GetAsAUTF8String(nsACString &_str)
+ {
+ return variant_text_traits<DataType, Adopting>::asUTF8String(mData, _str);
+ }
+
+ NS_IMETHOD GetAsAString(nsAString &_str)
+ {
+ return variant_text_traits<DataType, Adopting>::asString(mData, _str);
+ }
+
+ NS_IMETHOD GetAsArray(uint16_t *_type,
+ nsIID *,
+ uint32_t *_size,
+ void **_data)
+ {
+ return variant_blob_traits<DataType, Adopting>::asArray(mData, _type, _size, _data);
+ }
+
+private:
+ typename variant_storage_traits<DataType, Adopting>::StorageType mData;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Handy typedefs! Use these for the right mapping.
+
+typedef Variant<int64_t> IntegerVariant;
+typedef Variant<double> FloatVariant;
+typedef Variant<nsString> TextVariant;
+typedef Variant<nsCString> UTF8TextVariant;
+typedef Variant<uint8_t[], false> BlobVariant;
+typedef Variant<uint8_t[], true> AdoptedBlobVariant;
+
+} // namespace storage
+} // namespace mozilla
+
+#include "Variant_inl.h"
+
+#endif // mozilla_storage_Variant_h__
diff --git a/components/storage/src/Variant_inl.h b/components/storage/src/Variant_inl.h
new file mode 100644
index 000000000..2e0571ae2
--- /dev/null
+++ b/components/storage/src/Variant_inl.h
@@ -0,0 +1,229 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Note: This file is included by Variant.h.
+ */
+
+#ifndef mozilla_storage_Variant_h__
+#error "Do not include this file directly!"
+#endif
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Variant_base
+
+inline NS_IMPL_ADDREF(Variant_base)
+inline NS_IMPL_RELEASE(Variant_base)
+inline NS_IMPL_QUERY_INTERFACE(
+ Variant_base,
+ nsIVariant
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIVariant
+
+inline
+NS_IMETHODIMP
+Variant_base::GetDataType(uint16_t *_type)
+{
+ NS_ENSURE_ARG_POINTER(_type);
+ *_type = nsIDataType::VTYPE_VOID;
+ return NS_OK;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsInt32(int32_t *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsInt64(int64_t *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsDouble(double *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsAUTF8String(nsACString &)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsAString(nsAString &)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsArray(uint16_t *,
+ nsIID *,
+ uint32_t *,
+ void **)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsInt8(uint8_t *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsInt16(int16_t *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsUint8(uint8_t *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsUint16(uint16_t *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsUint32(uint32_t *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsUint64(uint64_t *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsFloat(float *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsBool(bool *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsChar(char *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsWChar(char16_t *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsID(nsID *)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsDOMString(nsAString &)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsString(char **)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsWString(char16_t **)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsISupports(nsISupports **)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsInterface(nsIID **,
+ void **)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsACString(nsACString &)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsStringWithSize(uint32_t *,
+ char **)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsWStringWithSize(uint32_t *,
+ char16_t **)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+inline
+NS_IMETHODIMP
+Variant_base::GetAsJSVal(JS::MutableHandle<JS::Value>)
+{
+ return NS_ERROR_CANNOT_CONVERT_DATA;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageArgValueArray.cpp b/components/storage/src/mozStorageArgValueArray.cpp
new file mode 100644
index 000000000..40d67a4cd
--- /dev/null
+++ b/components/storage/src/mozStorageArgValueArray.cpp
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsError.h"
+#include "nsMemory.h"
+#include "nsString.h"
+
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageArgValueArray.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// ArgValueArray
+
+ArgValueArray::ArgValueArray(int32_t aArgc,
+ sqlite3_value **aArgv)
+: mArgc(aArgc)
+, mArgv(aArgv)
+{
+}
+
+NS_IMPL_ISUPPORTS(
+ ArgValueArray,
+ mozIStorageValueArray
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageValueArray
+
+NS_IMETHODIMP
+ArgValueArray::GetNumEntries(uint32_t *_size)
+{
+ *_size = mArgc;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetTypeOfIndex(uint32_t aIndex,
+ int32_t *_type)
+{
+ ENSURE_INDEX_VALUE(aIndex, mArgc);
+
+ int t = ::sqlite3_value_type(mArgv[aIndex]);
+ switch (t) {
+ case SQLITE_INTEGER:
+ *_type = VALUE_TYPE_INTEGER;
+ break;
+ case SQLITE_FLOAT:
+ *_type = VALUE_TYPE_FLOAT;
+ break;
+ case SQLITE_TEXT:
+ *_type = VALUE_TYPE_TEXT;
+ break;
+ case SQLITE_BLOB:
+ *_type = VALUE_TYPE_BLOB;
+ break;
+ case SQLITE_NULL:
+ *_type = VALUE_TYPE_NULL;
+ break;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetInt32(uint32_t aIndex,
+ int32_t *_value)
+{
+ ENSURE_INDEX_VALUE(aIndex, mArgc);
+
+ *_value = ::sqlite3_value_int(mArgv[aIndex]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetInt64(uint32_t aIndex,
+ int64_t *_value)
+{
+ ENSURE_INDEX_VALUE(aIndex, mArgc);
+
+ *_value = ::sqlite3_value_int64(mArgv[aIndex]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetDouble(uint32_t aIndex,
+ double *_value)
+{
+ ENSURE_INDEX_VALUE(aIndex, mArgc);
+
+ *_value = ::sqlite3_value_double(mArgv[aIndex]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetUTF8String(uint32_t aIndex,
+ nsACString &_value)
+{
+ ENSURE_INDEX_VALUE(aIndex, mArgc);
+
+ if (::sqlite3_value_type(mArgv[aIndex]) == SQLITE_NULL) {
+ // NULL columns should have IsVoid set to distinguish them from an empty
+ // string.
+ _value.SetIsVoid(true);
+ }
+ else {
+ _value.Assign(reinterpret_cast<const char *>(::sqlite3_value_text(mArgv[aIndex])),
+ ::sqlite3_value_bytes(mArgv[aIndex]));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetString(uint32_t aIndex,
+ nsAString &_value)
+{
+ ENSURE_INDEX_VALUE(aIndex, mArgc);
+
+ if (::sqlite3_value_type(mArgv[aIndex]) == SQLITE_NULL) {
+ // NULL columns should have IsVoid set to distinguish them from an empty
+ // string.
+ _value.SetIsVoid(true);
+ } else {
+ _value.Assign(static_cast<const char16_t *>(::sqlite3_value_text16(mArgv[aIndex])),
+ ::sqlite3_value_bytes16(mArgv[aIndex]) / 2);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetBlob(uint32_t aIndex,
+ uint32_t *_size,
+ uint8_t **_blob)
+{
+ ENSURE_INDEX_VALUE(aIndex, mArgc);
+
+ int size = ::sqlite3_value_bytes(mArgv[aIndex]);
+ void *blob = nsMemory::Clone(::sqlite3_value_blob(mArgv[aIndex]), size);
+ NS_ENSURE_TRUE(blob, NS_ERROR_OUT_OF_MEMORY);
+
+ *_blob = static_cast<uint8_t *>(blob);
+ *_size = size;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetBlobAsString(uint32_t aIndex, nsAString& aValue)
+{
+ return DoGetBlobAsString(this, aIndex, aValue);
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetBlobAsUTF8String(uint32_t aIndex, nsACString& aValue)
+{
+ return DoGetBlobAsString(this, aIndex, aValue);
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetIsNull(uint32_t aIndex,
+ bool *_isNull)
+{
+ // GetTypeOfIndex will check aIndex for us, so we don't have to.
+ int32_t type;
+ nsresult rv = GetTypeOfIndex(aIndex, &type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_isNull = (type == VALUE_TYPE_NULL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetSharedUTF8String(uint32_t aIndex,
+ uint32_t *_length,
+ const char **_string)
+{
+ if (_length)
+ *_length = ::sqlite3_value_bytes(mArgv[aIndex]);
+
+ *_string = reinterpret_cast<const char *>(::sqlite3_value_text(mArgv[aIndex]));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetSharedString(uint32_t aIndex,
+ uint32_t *_length,
+ const char16_t **_string)
+{
+ if (_length)
+ *_length = ::sqlite3_value_bytes(mArgv[aIndex]);
+
+ *_string = static_cast<const char16_t *>(::sqlite3_value_text16(mArgv[aIndex]));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArgValueArray::GetSharedBlob(uint32_t aIndex,
+ uint32_t *_size,
+ const uint8_t **_blob)
+{
+ *_size = ::sqlite3_value_bytes(mArgv[aIndex]);
+ *_blob = static_cast<const uint8_t *>(::sqlite3_value_blob(mArgv[aIndex]));
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageArgValueArray.h b/components/storage/src/mozStorageArgValueArray.h
new file mode 100644
index 000000000..5a14957ba
--- /dev/null
+++ b/components/storage/src/mozStorageArgValueArray.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStorageArgValueArray_h
+#define mozStorageArgValueArray_h
+
+#include "mozIStorageValueArray.h"
+#include "mozilla/Attributes.h"
+
+#include "sqlite3.h"
+
+namespace mozilla {
+namespace storage {
+
+class ArgValueArray final : public mozIStorageValueArray
+{
+public:
+ ArgValueArray(int32_t aArgc, sqlite3_value **aArgv);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEVALUEARRAY
+
+private:
+ ~ArgValueArray() {}
+
+ uint32_t mArgc;
+ sqlite3_value **mArgv;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStorageArgValueArray_h
diff --git a/components/storage/src/mozStorageAsyncStatement.cpp b/components/storage/src/mozStorageAsyncStatement.cpp
new file mode 100644
index 000000000..d0a3eec04
--- /dev/null
+++ b/components/storage/src/mozStorageAsyncStatement.cpp
@@ -0,0 +1,383 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <limits.h>
+#include <stdio.h>
+
+#include "nsError.h"
+#include "nsMemory.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "Variant.h"
+
+#include "mozIStorageError.h"
+
+#include "mozStorageBindingParams.h"
+#include "mozStorageConnection.h"
+#include "mozStorageAsyncStatementJSHelper.h"
+#include "mozStorageAsyncStatementParams.h"
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageStatementRow.h"
+#include "mozStorageStatement.h"
+#include "nsDOMClassInfo.h"
+
+#include "mozilla/Logging.h"
+
+extern mozilla::LazyLogModule gStorageLog;
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIClassInfo
+
+NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement,
+ mozIStorageAsyncStatement,
+ mozIStorageBaseStatement,
+ mozIStorageBindingParams,
+ mozilla::storage::StorageBaseStatementInternal)
+
+class AsyncStatementClassInfo : public nsIClassInfo
+{
+public:
+ constexpr AsyncStatementClassInfo() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD
+ GetInterfaces(uint32_t *_count, nsIID ***_array) override
+ {
+ return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_count, _array);
+ }
+
+ NS_IMETHOD
+ GetScriptableHelper(nsIXPCScriptable **_helper) override
+ {
+ static AsyncStatementJSHelper sJSHelper;
+ *_helper = &sJSHelper;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetContractID(char **_contractID) override
+ {
+ *_contractID = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetClassDescription(char **_desc) override
+ {
+ *_desc = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetClassID(nsCID **_id) override
+ {
+ *_id = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetFlags(uint32_t *_flags) override
+ {
+ *_flags = 0;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetClassIDNoAlloc(nsCID *_cid) override
+ {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+};
+
+NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::AddRef() { return 2; }
+NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::Release() { return 1; }
+NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo, nsIClassInfo)
+
+static AsyncStatementClassInfo sAsyncStatementClassInfo;
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncStatement
+
+AsyncStatement::AsyncStatement()
+: StorageBaseStatementInternal()
+, mFinalized(false)
+{
+}
+
+nsresult
+AsyncStatement::initialize(Connection *aDBConnection,
+ sqlite3 *aNativeConnection,
+ const nsACString &aSQLStatement)
+{
+ MOZ_ASSERT(aDBConnection, "No database connection given!");
+ MOZ_ASSERT(!aDBConnection->isClosed(), "Database connection should be valid");
+ MOZ_ASSERT(aNativeConnection, "No native connection given!");
+
+ mDBConnection = aDBConnection;
+ mNativeConnection = aNativeConnection;
+ mSQLString = aSQLStatement;
+
+ MOZ_LOG(gStorageLog, LogLevel::Debug, ("Inited async statement '%s' (0x%p)",
+ mSQLString.get()));
+
+#ifdef DEBUG
+ // We want to try and test for LIKE and that consumers are using
+ // escapeStringForLIKE instead of just trusting user input. The idea to
+ // check to see if they are binding a parameter after like instead of just
+ // using a string. We only do this in debug builds because it's expensive!
+ const nsCaseInsensitiveCStringComparator c;
+ nsACString::const_iterator start, end, e;
+ aSQLStatement.BeginReading(start);
+ aSQLStatement.EndReading(end);
+ e = end;
+ while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) {
+ // We have a LIKE in here, so we perform our tests
+ // FindInReadable moves the iterator, so we have to get a new one for
+ // each test we perform.
+ nsACString::const_iterator s1, s2, s3;
+ s1 = s2 = s3 = start;
+
+ if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) ||
+ ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) ||
+ ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) {
+ // At this point, we didn't find a LIKE statement followed by ?, :,
+ // or @, all of which are valid characters for binding a parameter.
+ // We will warn the consumer that they may not be safely using LIKE.
+ NS_WARNING("Unsafe use of LIKE detected! Please ensure that you "
+ "are using mozIStorageAsyncStatement::escapeStringForLIKE "
+ "and that you are binding that result to the statement "
+ "to prevent SQL injection attacks.");
+ }
+
+ // resetting start and e
+ start = e;
+ e = end;
+ }
+#endif
+
+ return NS_OK;
+}
+
+mozIStorageBindingParams *
+AsyncStatement::getParams()
+{
+ nsresult rv;
+
+ // If we do not have an array object yet, make it.
+ if (!mParamsArray) {
+ nsCOMPtr<mozIStorageBindingParamsArray> array;
+ rv = NewBindingParamsArray(getter_AddRefs(array));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ mParamsArray = static_cast<BindingParamsArray *>(array.get());
+ }
+
+ // If there isn't already any rows added, we'll have to add one to use.
+ if (mParamsArray->length() == 0) {
+ RefPtr<AsyncBindingParams> params(new AsyncBindingParams(mParamsArray));
+ NS_ENSURE_TRUE(params, nullptr);
+
+ rv = mParamsArray->AddParams(params);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ // We have to unlock our params because AddParams locks them. This is safe
+ // because no reference to the params object was, or ever will be given out.
+ params->unlock(nullptr);
+
+ // We also want to lock our array at this point - we don't want anything to
+ // be added to it.
+ mParamsArray->lock();
+ }
+
+ return *mParamsArray->begin();
+}
+
+/**
+ * If we are here then we know there are no pending async executions relying on
+ * us (StatementData holds a reference to us; this also goes for our own
+ * AsyncStatementFinalizer which proxies its release to the calling thread) and
+ * so it is always safe to destroy our sqlite3_stmt if one exists. We can be
+ * destroyed on the caller thread by garbage-collection/reference counting or on
+ * the async thread by the last execution of a statement that already lost its
+ * main-thread refs.
+ */
+AsyncStatement::~AsyncStatement()
+{
+ destructorAsyncFinalize();
+
+ // If we are getting destroyed on the wrong thread, proxy the connection
+ // release to the right thread. I'm not sure why we do this.
+ bool onCallingThread = false;
+ (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onCallingThread);
+ if (!onCallingThread) {
+ // NS_ProxyRelase only magic forgets for us if mDBConnection is an
+ // nsCOMPtr. Which it is not; it's an nsRefPtr.
+ nsCOMPtr<nsIThread> targetThread(mDBConnection->threadOpenedOn);
+ NS_ProxyRelease(targetThread, mDBConnection.forget());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+NS_IMPL_ADDREF(AsyncStatement)
+NS_IMPL_RELEASE(AsyncStatement)
+
+NS_INTERFACE_MAP_BEGIN(AsyncStatement)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams)
+ NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal)
+ if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
+ foundInterface = static_cast<nsIClassInfo *>(&sAsyncStatementClassInfo);
+ }
+ else
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement)
+NS_INTERFACE_MAP_END
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// StorageBaseStatementInternal
+
+Connection *
+AsyncStatement::getOwner()
+{
+ return mDBConnection;
+}
+
+int
+AsyncStatement::getAsyncStatement(sqlite3_stmt **_stmt)
+{
+#ifdef DEBUG
+ // Make sure we are never called on the connection's owning thread.
+ bool onOpenedThread = false;
+ (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
+ NS_ASSERTION(!onOpenedThread,
+ "We should only be called on the async thread!");
+#endif
+
+ if (!mAsyncStatement) {
+ int rc = mDBConnection->prepareStatement(mNativeConnection, mSQLString,
+ &mAsyncStatement);
+ if (rc != SQLITE_OK) {
+ MOZ_LOG(gStorageLog, LogLevel::Error,
+ ("Sqlite statement prepare error: %d '%s'", rc,
+ ::sqlite3_errmsg(mNativeConnection)));
+ MOZ_LOG(gStorageLog, LogLevel::Error,
+ ("Statement was: '%s'", mSQLString.get()));
+ *_stmt = nullptr;
+ return rc;
+ }
+ MOZ_LOG(gStorageLog, LogLevel::Debug, ("Initialized statement '%s' (0x%p)",
+ mSQLString.get(),
+ mAsyncStatement));
+ }
+
+ *_stmt = mAsyncStatement;
+ return SQLITE_OK;
+}
+
+nsresult
+AsyncStatement::getAsynchronousStatementData(StatementData &_data)
+{
+ if (mFinalized)
+ return NS_ERROR_UNEXPECTED;
+
+ // Pass null for the sqlite3_stmt; it will be requested on demand from the
+ // async thread.
+ _data = StatementData(nullptr, bindingParamsArray(), this);
+
+ return NS_OK;
+}
+
+already_AddRefed<mozIStorageBindingParams>
+AsyncStatement::newBindingParams(mozIStorageBindingParamsArray *aOwner)
+{
+ if (mFinalized)
+ return nullptr;
+
+ nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner));
+ return params.forget();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageAsyncStatement
+
+// (nothing is specific to mozIStorageAsyncStatement)
+
+////////////////////////////////////////////////////////////////////////////////
+//// StorageBaseStatementInternal
+
+// proxy to StorageBaseStatementInternal using its define helper.
+MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
+ AsyncStatement,
+ if (mFinalized) return NS_ERROR_UNEXPECTED;)
+
+NS_IMETHODIMP
+AsyncStatement::Finalize()
+{
+ if (mFinalized)
+ return NS_OK;
+
+ mFinalized = true;
+
+ MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s'",
+ mSQLString.get()));
+
+ asyncFinalize();
+
+ // Release the params holder, so it can release the reference to us.
+ mStatementParamsHolder = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncStatement::BindParameters(mozIStorageBindingParamsArray *aParameters)
+{
+ if (mFinalized)
+ return NS_ERROR_UNEXPECTED;
+
+ BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters);
+ if (array->getOwner() != this)
+ return NS_ERROR_UNEXPECTED;
+
+ if (array->length() == 0)
+ return NS_ERROR_UNEXPECTED;
+
+ mParamsArray = array;
+ mParamsArray->lock();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncStatement::GetState(int32_t *_state)
+{
+ if (mFinalized)
+ *_state = MOZ_STORAGE_STATEMENT_INVALID;
+ else
+ *_state = MOZ_STORAGE_STATEMENT_READY;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageBindingParams
+
+BOILERPLATE_BIND_PROXIES(
+ AsyncStatement,
+ if (mFinalized) return NS_ERROR_UNEXPECTED;
+)
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageAsyncStatement.h b/components/storage/src/mozStorageAsyncStatement.h
new file mode 100644
index 000000000..4fac36d30
--- /dev/null
+++ b/components/storage/src/mozStorageAsyncStatement.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_mozStorageAsyncStatement_h_
+#define mozilla_storage_mozStorageAsyncStatement_h_
+
+#include "nsAutoPtr.h"
+#include "nsString.h"
+
+#include "nsTArray.h"
+
+#include "mozStorageBindingParamsArray.h"
+#include "mozStorageStatementData.h"
+#include "mozIStorageAsyncStatement.h"
+#include "StorageBaseStatementInternal.h"
+#include "mozilla/Attributes.h"
+
+class nsIXPConnectJSObjectHolder;
+
+namespace mozilla {
+namespace storage {
+
+class AsyncStatementJSHelper;
+class Connection;
+
+class AsyncStatement final : public mozIStorageAsyncStatement
+ , public StorageBaseStatementInternal
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEASYNCSTATEMENT
+ NS_DECL_MOZISTORAGEBASESTATEMENT
+ NS_DECL_MOZISTORAGEBINDINGPARAMS
+ NS_DECL_STORAGEBASESTATEMENTINTERNAL
+
+ AsyncStatement();
+
+ /**
+ * Initializes the object on aDBConnection by preparing the SQL statement
+ * given by aSQLStatement.
+ *
+ * @param aDBConnection
+ * The Connection object this statement is associated with.
+ * @param aNativeConnection
+ * The native Sqlite connection this statement is associated with.
+ * @param aSQLStatement
+ * The SQL statement to prepare that this object will represent.
+ */
+ nsresult initialize(Connection *aDBConnection,
+ sqlite3 *aNativeConnection,
+ const nsACString &aSQLStatement);
+
+ /**
+ * Obtains and transfers ownership of the array of parameters that are bound
+ * to this statment. This can be null.
+ */
+ inline already_AddRefed<BindingParamsArray> bindingParamsArray()
+ {
+ return mParamsArray.forget();
+ }
+
+
+private:
+ ~AsyncStatement();
+
+ /**
+ * @return a pointer to the BindingParams object to use with our Bind*
+ * method.
+ */
+ mozIStorageBindingParams *getParams();
+
+ /**
+ * The SQL string as passed by the user. We store it because we create the
+ * async statement on-demand on the async thread.
+ */
+ nsCString mSQLString;
+
+ /**
+ * Holds the array of parameters to bind to this statement when we execute
+ * it asynchronously.
+ */
+ RefPtr<BindingParamsArray> mParamsArray;
+
+ /**
+ * Caches the JS 'params' helper for this statement.
+ */
+ nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementParamsHolder;
+
+ /**
+ * Have we been explicitly finalized by the user?
+ */
+ bool mFinalized;
+
+ /**
+ * Required for access to private mStatementParamsHolder field by
+ * AsyncStatementJSHelper::getParams.
+ */
+ friend class AsyncStatementJSHelper;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozilla_storage_mozStorageAsyncStatement_h_
diff --git a/components/storage/src/mozStorageAsyncStatementExecution.cpp b/components/storage/src/mozStorageAsyncStatementExecution.cpp
new file mode 100644
index 000000000..ec9f380bf
--- /dev/null
+++ b/components/storage/src/mozStorageAsyncStatementExecution.cpp
@@ -0,0 +1,580 @@
+/* -*- 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 "nsAutoPtr.h"
+
+#include "sqlite3.h"
+
+#include "mozIStorageStatementCallback.h"
+#include "mozStorageBindingParams.h"
+#include "mozStorageHelper.h"
+#include "mozStorageResultSet.h"
+#include "mozStorageRow.h"
+#include "mozStorageConnection.h"
+#include "mozStorageError.h"
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageStatementData.h"
+#include "mozStorageAsyncStatementExecution.h"
+
+#include "mozilla/DebugOnly.h"
+
+namespace mozilla {
+namespace storage {
+
+/**
+ * The following constants help batch rows into result sets.
+ * MAX_MILLISECONDS_BETWEEN_RESULTS was chosen because any user-based task that
+ * takes less than 200 milliseconds is considered to feel instantaneous to end
+ * users. MAX_ROWS_PER_RESULT was arbitrarily chosen to reduce the number of
+ * dispatches to calling thread, while also providing reasonably-sized sets of
+ * data for consumers. Both of these constants are used because we assume that
+ * consumers are trying to avoid blocking their execution thread for long
+ * periods of time, and dispatching many small events to the calling thread will
+ * end up blocking it.
+ */
+#define MAX_MILLISECONDS_BETWEEN_RESULTS 75
+#define MAX_ROWS_PER_RESULT 15
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncExecuteStatements
+
+/* static */
+nsresult
+AsyncExecuteStatements::execute(StatementDataArray &aStatements,
+ Connection *aConnection,
+ sqlite3 *aNativeConnection,
+ mozIStorageStatementCallback *aCallback,
+ mozIStoragePendingStatement **_stmt)
+{
+ // Create our event to run in the background
+ RefPtr<AsyncExecuteStatements> event =
+ new AsyncExecuteStatements(aStatements, aConnection, aNativeConnection,
+ aCallback);
+ NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
+
+ // Dispatch it to the background
+ nsIEventTarget *target = aConnection->getAsyncExecutionTarget();
+
+ // If we don't have a valid target, this is a bug somewhere else. In the past,
+ // this assert found cases where a Run method would schedule a new statement
+ // without checking if asyncClose had been called. The caller must prevent
+ // that from happening or, if the work is not critical, just avoid creating
+ // the new statement during shutdown. See bug 718449 for an example.
+ MOZ_ASSERT(target);
+ if (!target) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Return it as the pending statement object and track it.
+ event.forget(_stmt);
+ return NS_OK;
+}
+
+AsyncExecuteStatements::AsyncExecuteStatements(StatementDataArray &aStatements,
+ Connection *aConnection,
+ sqlite3 *aNativeConnection,
+ mozIStorageStatementCallback *aCallback)
+: mConnection(aConnection)
+, mNativeConnection(aNativeConnection)
+, mHasTransaction(false)
+, mCallback(aCallback)
+, mCallingThread(::do_GetCurrentThread())
+, mMaxWait(TimeDuration::FromMilliseconds(MAX_MILLISECONDS_BETWEEN_RESULTS))
+, mIntervalStart(TimeStamp::Now())
+, mState(PENDING)
+, mCancelRequested(false)
+, mMutex(aConnection->sharedAsyncExecutionMutex)
+, mDBMutex(aConnection->sharedDBMutex)
+, mRequestStartDate(TimeStamp::Now())
+{
+ (void)mStatements.SwapElements(aStatements);
+ NS_ASSERTION(mStatements.Length(), "We weren't given any statements!");
+}
+
+AsyncExecuteStatements::~AsyncExecuteStatements()
+{
+ MOZ_ASSERT(!mCallback, "Never called the Completion callback!");
+ MOZ_ASSERT(!mHasTransaction, "There should be no transaction at this point");
+ if (mCallback) {
+ NS_ProxyRelease(mCallingThread, mCallback.forget());
+ }
+}
+
+bool
+AsyncExecuteStatements::shouldNotify()
+{
+#ifdef DEBUG
+ mMutex.AssertNotCurrentThreadOwns();
+
+ bool onCallingThread = false;
+ (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
+ NS_ASSERTION(onCallingThread, "runEvent not running on the calling thread!");
+#endif
+
+ // We do not need to acquire mMutex here because it can only ever be written
+ // to on the calling thread, and the only thread that can call us is the
+ // calling thread, so we know that our access is serialized.
+ return !mCancelRequested;
+}
+
+bool
+AsyncExecuteStatements::bindExecuteAndProcessStatement(StatementData &aData,
+ bool aLastStatement)
+{
+ mMutex.AssertNotCurrentThreadOwns();
+
+ sqlite3_stmt *aStatement = nullptr;
+ // This cannot fail; we are only called if it's available.
+ (void)aData.getSqliteStatement(&aStatement);
+ NS_ASSERTION(aStatement, "You broke the code; do not call here like that!");
+ BindingParamsArray *paramsArray(aData);
+
+ // Iterate through all of our parameters, bind them, and execute.
+ bool continueProcessing = true;
+ BindingParamsArray::iterator itr = paramsArray->begin();
+ BindingParamsArray::iterator end = paramsArray->end();
+ while (itr != end && continueProcessing) {
+ // Bind the data to our statement.
+ nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
+ do_QueryInterface(*itr);
+ nsCOMPtr<mozIStorageError> error = bindingInternal->bind(aStatement);
+ if (error) {
+ // Set our error state.
+ mState = ERROR;
+
+ // And notify.
+ (void)notifyError(error);
+ return false;
+ }
+
+ // Advance our iterator, execute, and then process the statement.
+ itr++;
+ bool lastStatement = aLastStatement && itr == end;
+ continueProcessing = executeAndProcessStatement(aStatement, lastStatement);
+
+ // Always reset our statement.
+ (void)::sqlite3_reset(aStatement);
+ }
+
+ return continueProcessing;
+}
+
+bool
+AsyncExecuteStatements::executeAndProcessStatement(sqlite3_stmt *aStatement,
+ bool aLastStatement)
+{
+ mMutex.AssertNotCurrentThreadOwns();
+
+ // Execute our statement
+ bool hasResults;
+ do {
+ hasResults = executeStatement(aStatement);
+
+ // If we had an error, bail.
+ if (mState == ERROR)
+ return false;
+
+ // If we have been canceled, there is no point in going on...
+ {
+ MutexAutoLock lockedScope(mMutex);
+ if (mCancelRequested) {
+ mState = CANCELED;
+ return false;
+ }
+ }
+
+ // Build our result set and notify if we got anything back and have a
+ // callback to notify.
+ if (mCallback && hasResults &&
+ NS_FAILED(buildAndNotifyResults(aStatement))) {
+ // We had an error notifying, so we notify on error and stop processing.
+ mState = ERROR;
+
+ // Notify, and stop processing statements.
+ (void)notifyError(mozIStorageError::ERROR,
+ "An error occurred while notifying about results");
+
+ return false;
+ }
+ } while (hasResults);
+
+#ifndef MOZ_STORAGE_SORTWARNING_SQL_DUMP
+ if (MOZ_LOG_TEST(gStorageLog, LogLevel::Warning))
+#endif
+ {
+ // Check to make sure that this statement was smart about what it did.
+ checkAndLogStatementPerformance(aStatement);
+ }
+
+ // If we are done, we need to set our state accordingly while we still hold
+ // our mutex. We would have already returned if we were canceled or had
+ // an error at this point.
+ if (aLastStatement)
+ mState = COMPLETED;
+
+ return true;
+}
+
+bool
+AsyncExecuteStatements::executeStatement(sqlite3_stmt *aStatement)
+{
+ mMutex.AssertNotCurrentThreadOwns();
+ while (true) {
+ // lock the sqlite mutex so sqlite3_errmsg cannot change
+ SQLiteMutexAutoLock lockedScope(mDBMutex);
+
+ int rc = mConnection->stepStatement(mNativeConnection, aStatement);
+ // Stop if we have no more results.
+ if (rc == SQLITE_DONE)
+ {
+ return false;
+ }
+
+ // If we got results, we can return now.
+ if (rc == SQLITE_ROW)
+ {
+ return true;
+ }
+
+ // Some errors are not fatal, and we can handle them and continue.
+ if (rc == SQLITE_BUSY) {
+ // Don't hold the lock while we call outside our module.
+ SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
+
+ // Yield, and try again
+ (void)::PR_Sleep(PR_INTERVAL_NO_WAIT);
+ continue;
+ }
+
+ // Set an error state.
+ mState = ERROR;
+
+ // Construct the error message before giving up the mutex (which we cannot
+ // hold during the call to notifyError).
+ nsCOMPtr<mozIStorageError> errorObj(
+ new Error(rc, ::sqlite3_errmsg(mNativeConnection))
+ );
+ // We cannot hold the DB mutex while calling notifyError.
+ SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
+ (void)notifyError(errorObj);
+
+ // Finally, indicate that we should stop processing.
+ return false;
+ }
+}
+
+nsresult
+AsyncExecuteStatements::buildAndNotifyResults(sqlite3_stmt *aStatement)
+{
+ NS_ASSERTION(mCallback, "Trying to dispatch results without a callback!");
+ mMutex.AssertNotCurrentThreadOwns();
+
+ // Build result object if we need it.
+ if (!mResultSet)
+ mResultSet = new ResultSet();
+ NS_ENSURE_TRUE(mResultSet, NS_ERROR_OUT_OF_MEMORY);
+
+ RefPtr<Row> row(new Row());
+ NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = row->initialize(aStatement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mResultSet->add(row);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we have hit our maximum number of allowed results, or if we have hit
+ // the maximum amount of time we want to wait for results, notify the
+ // calling thread about it.
+ TimeStamp now = TimeStamp::Now();
+ TimeDuration delta = now - mIntervalStart;
+ if (mResultSet->rows() >= MAX_ROWS_PER_RESULT || delta > mMaxWait) {
+ // Notify the caller
+ rv = notifyResults();
+ if (NS_FAILED(rv))
+ return NS_OK; // we'll try again with the next result
+
+ // Reset our start time
+ mIntervalStart = now;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+AsyncExecuteStatements::notifyComplete()
+{
+ mMutex.AssertNotCurrentThreadOwns();
+ NS_ASSERTION(mState != PENDING,
+ "Still in a pending state when calling Complete!");
+
+ // Reset our statements before we try to commit or rollback. If we are
+ // canceling and have statements that think they have pending work, the
+ // rollback will fail.
+ for (uint32_t i = 0; i < mStatements.Length(); i++)
+ mStatements[i].reset();
+
+ // Release references to the statement data as soon as possible. If this
+ // is the last reference, statements will be finalized immediately on the
+ // async thread, hence avoiding several bounces between threads and possible
+ // race conditions with AsyncClose().
+ mStatements.Clear();
+
+ // Handle our transaction, if we have one
+ if (mHasTransaction) {
+ if (mState == COMPLETED) {
+ nsresult rv = mConnection->commitTransactionInternal(mNativeConnection);
+ if (NS_FAILED(rv)) {
+ mState = ERROR;
+ (void)notifyError(mozIStorageError::ERROR,
+ "Transaction failed to commit");
+ }
+ }
+ else {
+ DebugOnly<nsresult> rv =
+ mConnection->rollbackTransactionInternal(mNativeConnection);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Transaction failed to rollback");
+ }
+ mHasTransaction = false;
+ }
+
+ // This will take ownership of mCallback and make sure its destruction will
+ // happen on the owner thread.
+ Unused << mCallingThread->Dispatch(
+ NewRunnableMethod(this, &AsyncExecuteStatements::notifyCompleteOnCallingThread),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+nsresult
+AsyncExecuteStatements::notifyCompleteOnCallingThread() {
+#ifdef DEBUG
+ bool onCallingThread = false;
+ (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
+ MOZ_ASSERT(onCallingThread);
+#endif
+ // Take ownership of mCallback and responsibility for freeing it when we
+ // release it. Any notifyResultsOnCallingThread and notifyErrorOnCallingThread
+ // calls on the stack spinning the event loop have guaranteed their safety by
+ // creating their own strong reference before invoking the callback.
+ nsCOMPtr<mozIStorageStatementCallback> callback = mCallback.forget();
+ if (callback) {
+ Unused << callback->HandleCompletion(mState);
+ }
+ return NS_OK;
+}
+
+nsresult
+AsyncExecuteStatements::notifyError(int32_t aErrorCode,
+ const char *aMessage)
+{
+ mMutex.AssertNotCurrentThreadOwns();
+ mDBMutex.assertNotCurrentThreadOwns();
+
+ if (!mCallback)
+ return NS_OK;
+
+ nsCOMPtr<mozIStorageError> errorObj(new Error(aErrorCode, aMessage));
+ NS_ENSURE_TRUE(errorObj, NS_ERROR_OUT_OF_MEMORY);
+
+ return notifyError(errorObj);
+}
+
+nsresult
+AsyncExecuteStatements::notifyError(mozIStorageError *aError)
+{
+ mMutex.AssertNotCurrentThreadOwns();
+ mDBMutex.assertNotCurrentThreadOwns();
+
+ if (!mCallback)
+ return NS_OK;
+
+ Unused << mCallingThread->Dispatch(
+ NewRunnableMethod<nsCOMPtr<mozIStorageError>>(this, &AsyncExecuteStatements::notifyErrorOnCallingThread, aError),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+nsresult
+AsyncExecuteStatements::notifyErrorOnCallingThread(mozIStorageError *aError) {
+#ifdef DEBUG
+ bool onCallingThread = false;
+ (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
+ MOZ_ASSERT(onCallingThread);
+#endif
+ // Acquire our own strong reference so that if the callback spins a nested
+ // event loop and notifyCompleteOnCallingThread is executed, forgetting
+ // mCallback, we still have a valid/strong reference that won't be freed until
+ // we exit.
+ nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
+ if (shouldNotify() && callback) {
+ Unused << callback->HandleError(aError);
+ }
+ return NS_OK;
+}
+
+nsresult
+AsyncExecuteStatements::notifyResults()
+{
+ mMutex.AssertNotCurrentThreadOwns();
+ MOZ_ASSERT(mCallback, "notifyResults called without a callback!");
+
+ // This takes ownership of mResultSet, a new one will be generated in
+ // buildAndNotifyResults() when further results will arrive.
+ Unused << mCallingThread->Dispatch(
+ NewRunnableMethod<RefPtr<ResultSet>>(this, &AsyncExecuteStatements::notifyResultsOnCallingThread, mResultSet.forget()),
+ NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+nsresult
+AsyncExecuteStatements::notifyResultsOnCallingThread(ResultSet *aResultSet)
+{
+#ifdef DEBUG
+ bool onCallingThread = false;
+ (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
+ MOZ_ASSERT(onCallingThread);
+#endif
+ // Acquire our own strong reference so that if the callback spins a nested
+ // event loop and notifyCompleteOnCallingThread is executed, forgetting
+ // mCallback, we still have a valid/strong reference that won't be freed until
+ // we exit.
+ nsCOMPtr<mozIStorageStatementCallback> callback = mCallback;
+ if (shouldNotify() && callback) {
+ Unused << callback->HandleResult(aResultSet);
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(
+ AsyncExecuteStatements,
+ nsIRunnable,
+ mozIStoragePendingStatement
+)
+
+bool
+AsyncExecuteStatements::statementsNeedTransaction()
+{
+ // If there is more than one write statement, run in a transaction.
+ // Additionally, if we have only one statement but it needs a transaction, due
+ // to multiple BindingParams, we will wrap it in one.
+ for (uint32_t i = 0, transactionsCount = 0; i < mStatements.Length(); ++i) {
+ transactionsCount += mStatements[i].needsTransaction();
+ if (transactionsCount > 1) {
+ return true;
+ }
+ }
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStoragePendingStatement
+
+NS_IMETHODIMP
+AsyncExecuteStatements::Cancel()
+{
+#ifdef DEBUG
+ bool onCallingThread = false;
+ (void)mCallingThread->IsOnCurrentThread(&onCallingThread);
+ NS_ASSERTION(onCallingThread, "Not canceling from the calling thread!");
+#endif
+
+ // If we have already canceled, we have an error, but always indicate that
+ // we are trying to cancel.
+ NS_ENSURE_FALSE(mCancelRequested, NS_ERROR_UNEXPECTED);
+
+ {
+ MutexAutoLock lockedScope(mMutex);
+
+ // We need to indicate that we want to try and cancel now.
+ mCancelRequested = true;
+ }
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIRunnable
+
+NS_IMETHODIMP
+AsyncExecuteStatements::Run()
+{
+ MOZ_ASSERT(!mConnection->isClosed());
+
+ // Do not run if we have been canceled.
+ {
+ MutexAutoLock lockedScope(mMutex);
+ if (mCancelRequested)
+ mState = CANCELED;
+ }
+ if (mState == CANCELED)
+ return notifyComplete();
+
+ if (statementsNeedTransaction() && mConnection->getAutocommit()) {
+ if (NS_SUCCEEDED(mConnection->beginTransactionInternal(mNativeConnection,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE))) {
+ mHasTransaction = true;
+ }
+#ifdef DEBUG
+ else {
+ NS_WARNING("Unable to create a transaction for async execution.");
+ }
+#endif
+ }
+
+ // Execute each statement, giving the callback results if it returns any.
+ for (uint32_t i = 0; i < mStatements.Length(); i++) {
+ bool finished = (i == (mStatements.Length() - 1));
+
+ sqlite3_stmt *stmt;
+ { // lock the sqlite mutex so sqlite3_errmsg cannot change
+ SQLiteMutexAutoLock lockedScope(mDBMutex);
+
+ int rc = mStatements[i].getSqliteStatement(&stmt);
+ if (rc != SQLITE_OK) {
+ // Set our error state.
+ mState = ERROR;
+
+ // Build the error object; can't call notifyError with the lock held
+ nsCOMPtr<mozIStorageError> errorObj(
+ new Error(rc, ::sqlite3_errmsg(mNativeConnection))
+ );
+ {
+ // We cannot hold the DB mutex and call notifyError.
+ SQLiteMutexAutoUnlock unlockedScope(mDBMutex);
+ (void)notifyError(errorObj);
+ }
+ break;
+ }
+ }
+
+ // If we have parameters to bind, bind them, execute, and process.
+ if (mStatements[i].hasParametersToBeBound()) {
+ if (!bindExecuteAndProcessStatement(mStatements[i], finished))
+ break;
+ }
+ // Otherwise, just execute and process the statement.
+ else if (!executeAndProcessStatement(stmt, finished)) {
+ break;
+ }
+ }
+
+ // If we still have results that we haven't notified about, take care of
+ // them now.
+ if (mResultSet)
+ (void)notifyResults();
+
+ // Notify about completion
+ return notifyComplete();
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageAsyncStatementExecution.h b/components/storage/src/mozStorageAsyncStatementExecution.h
new file mode 100644
index 000000000..14ea49c2d
--- /dev/null
+++ b/components/storage/src/mozStorageAsyncStatementExecution.h
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStorageAsyncStatementExecution_h
+#define mozStorageAsyncStatementExecution_h
+
+#include "nscore.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+#include "nsIRunnable.h"
+
+#include "SQLiteMutex.h"
+#include "mozIStoragePendingStatement.h"
+#include "mozIStorageStatementCallback.h"
+#include "mozStorageHelper.h"
+
+struct sqlite3_stmt;
+
+namespace mozilla {
+namespace storage {
+
+class Connection;
+class ResultSet;
+class StatementData;
+
+class AsyncExecuteStatements final : public nsIRunnable
+ , public mozIStoragePendingStatement
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_MOZISTORAGEPENDINGSTATEMENT
+
+ /**
+ * Describes the state of execution.
+ */
+ enum ExecutionState {
+ PENDING = -1,
+ COMPLETED = mozIStorageStatementCallback::REASON_FINISHED,
+ CANCELED = mozIStorageStatementCallback::REASON_CANCELED,
+ ERROR = mozIStorageStatementCallback::REASON_ERROR
+ };
+
+ typedef nsTArray<StatementData> StatementDataArray;
+
+ /**
+ * Executes a statement in the background, and passes results back to the
+ * caller.
+ *
+ * @param aStatements
+ * The statements to execute and possibly bind in the background.
+ * Ownership is transfered from the caller.
+ * @param aConnection
+ * The connection that created the statements to execute.
+ * @param aNativeConnection
+ * The native Sqlite connection that created the statements to execute.
+ * @param aCallback
+ * The callback that is notified of results, completion, and errors.
+ * @param _stmt
+ * The handle to control the execution of the statements.
+ */
+ static nsresult execute(StatementDataArray &aStatements,
+ Connection *aConnection,
+ sqlite3 *aNativeConnection,
+ mozIStorageStatementCallback *aCallback,
+ mozIStoragePendingStatement **_stmt);
+
+ /**
+ * Indicates when events on the calling thread should run or not. Certain
+ * events posted back to the calling thread should call this see if they
+ * should run or not.
+ *
+ * @pre mMutex is not held
+ *
+ * @returns true if the event should notify still, false otherwise.
+ */
+ bool shouldNotify();
+
+ /**
+ * Used by notifyComplete(), notifyError() and notifyResults() to notify on
+ * the calling thread.
+ */
+ nsresult notifyCompleteOnCallingThread();
+ nsresult notifyErrorOnCallingThread(mozIStorageError *aError);
+ nsresult notifyResultsOnCallingThread(ResultSet *aResultSet);
+
+private:
+ AsyncExecuteStatements(StatementDataArray &aStatements,
+ Connection *aConnection,
+ sqlite3 *aNativeConnection,
+ mozIStorageStatementCallback *aCallback);
+ ~AsyncExecuteStatements();
+
+ /**
+ * Binds and then executes a given statement until completion, an error
+ * occurs, or we are canceled. If aLastStatement is true, we should set
+ * mState accordingly.
+ *
+ * @pre mMutex is not held
+ *
+ * @param aData
+ * The StatementData to bind, execute, and then process.
+ * @param aLastStatement
+ * Indicates if this is the last statement or not. If it is, we have
+ * to set the proper state.
+ * @returns true if we should continue to process statements, false otherwise.
+ */
+ bool bindExecuteAndProcessStatement(StatementData &aData,
+ bool aLastStatement);
+
+ /**
+ * Executes a given statement until completion, an error occurs, or we are
+ * canceled. If aLastStatement is true, we should set mState accordingly.
+ *
+ * @pre mMutex is not held
+ *
+ * @param aStatement
+ * The statement to execute and then process.
+ * @param aLastStatement
+ * Indicates if this is the last statement or not. If it is, we have
+ * to set the proper state.
+ * @returns true if we should continue to process statements, false otherwise.
+ */
+ bool executeAndProcessStatement(sqlite3_stmt *aStatement,
+ bool aLastStatement);
+
+ /**
+ * Executes a statement to completion, properly handling any error conditions.
+ *
+ * @pre mMutex is not held
+ *
+ * @param aStatement
+ * The statement to execute to completion.
+ * @returns true if results were obtained, false otherwise.
+ */
+ bool executeStatement(sqlite3_stmt *aStatement);
+
+ /**
+ * Builds a result set up with a row from a given statement. If we meet the
+ * right criteria, go ahead and notify about this results too.
+ *
+ * @pre mMutex is not held
+ *
+ * @param aStatement
+ * The statement to get the row data from.
+ */
+ nsresult buildAndNotifyResults(sqlite3_stmt *aStatement);
+
+ /**
+ * Notifies callback about completion, and does any necessary cleanup.
+ *
+ * @pre mMutex is not held
+ */
+ nsresult notifyComplete();
+
+ /**
+ * Notifies callback about an error.
+ *
+ * @pre mMutex is not held
+ * @pre mDBMutex is not held
+ *
+ * @param aErrorCode
+ * The error code defined in mozIStorageError for the error.
+ * @param aMessage
+ * The error string, if any.
+ * @param aError
+ * The error object to notify the caller with.
+ */
+ nsresult notifyError(int32_t aErrorCode, const char *aMessage);
+ nsresult notifyError(mozIStorageError *aError);
+
+ /**
+ * Notifies the callback about a result set.
+ *
+ * @pre mMutex is not held
+ */
+ nsresult notifyResults();
+
+ /**
+ * Tests whether the current statements should be wrapped in an explicit
+ * transaction.
+ *
+ * @return true if an explicit transaction is needed, false otherwise.
+ */
+ bool statementsNeedTransaction();
+
+ StatementDataArray mStatements;
+ RefPtr<Connection> mConnection;
+ sqlite3 *mNativeConnection;
+ bool mHasTransaction;
+ // Note, this may not be a threadsafe object - never addref/release off
+ // the calling thread. We take a reference when this is created, and
+ // release it in the CompletionNotifier::Run() call back to this thread.
+ nsCOMPtr<mozIStorageStatementCallback> mCallback;
+ nsCOMPtr<nsIThread> mCallingThread;
+ RefPtr<ResultSet> mResultSet;
+
+ /**
+ * The maximum amount of time we want to wait between results. Defined by
+ * MAX_MILLISECONDS_BETWEEN_RESULTS and set at construction.
+ */
+ const TimeDuration mMaxWait;
+
+ /**
+ * The start time since our last set of results.
+ */
+ TimeStamp mIntervalStart;
+
+ /**
+ * Indicates our state of execution.
+ */
+ ExecutionState mState;
+
+ /**
+ * Indicates if we should try to cancel at a cancelation point.
+ */
+ bool mCancelRequested;
+
+ /**
+ * This is the mutex that protects our state from changing between threads.
+ * This includes the following variables:
+ * - mCancelRequested is only set on the calling thread while the lock is
+ * held. It is always read from within the lock on the background thread,
+ * but not on the calling thread (see shouldNotify for why).
+ */
+ Mutex &mMutex;
+
+ /**
+ * The wrapped SQLite recursive connection mutex. We use it whenever we call
+ * sqlite3_step and care about having reliable error messages. By taking it
+ * prior to the call and holding it until the point where we no longer care
+ * about the error message, the user gets reliable error messages.
+ */
+ SQLiteMutex &mDBMutex;
+
+ /**
+ * The instant at which the request was started.
+ *
+ * Used by telemetry.
+ */
+ TimeStamp mRequestStartDate;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStorageAsyncStatementExecution_h
diff --git a/components/storage/src/mozStorageAsyncStatementJSHelper.cpp b/components/storage/src/mozStorageAsyncStatementJSHelper.cpp
new file mode 100644
index 000000000..321d37884
--- /dev/null
+++ b/components/storage/src/mozStorageAsyncStatementJSHelper.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIXPConnect.h"
+#include "mozStorageAsyncStatement.h"
+#include "mozStorageService.h"
+
+#include "nsMemory.h"
+#include "nsString.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozStorageAsyncStatementJSHelper.h"
+
+#include "mozStorageAsyncStatementParams.h"
+
+#include "jsapi.h"
+
+#include "xpc_make_class.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncStatementJSHelper
+
+nsresult
+AsyncStatementJSHelper::getParams(AsyncStatement *aStatement,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ JS::Value *_params)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv;
+
+#ifdef DEBUG
+ int32_t state;
+ (void)aStatement->GetState(&state);
+ NS_ASSERTION(state == mozIStorageAsyncStatement::MOZ_STORAGE_STATEMENT_READY,
+ "Invalid state to get the params object - all calls will fail!");
+#endif
+
+ if (!aStatement->mStatementParamsHolder) {
+ nsCOMPtr<mozIStorageStatementParams> params =
+ new AsyncStatementParams(aStatement);
+ NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
+
+ JS::RootedObject scope(aCtx, aScopeObj);
+ nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
+ nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
+ rv = xpc->WrapNativeHolder(
+ aCtx,
+ ::JS_GetGlobalForObject(aCtx, scope),
+ params,
+ NS_GET_IID(mozIStorageStatementParams),
+ getter_AddRefs(holder)
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ RefPtr<AsyncStatementParamsHolder> paramsHolder =
+ new AsyncStatementParamsHolder(holder);
+ aStatement->mStatementParamsHolder =
+ new nsMainThreadPtrHolder<nsIXPConnectJSObjectHolder>(paramsHolder);
+ }
+
+ JS::Rooted<JSObject*> obj(aCtx);
+ obj = aStatement->mStatementParamsHolder->GetJSObject();
+ NS_ENSURE_STATE(obj);
+
+ _params->setObject(*obj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementJSHelper::AddRef() { return 2; }
+NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementJSHelper::Release() { return 1; }
+NS_INTERFACE_MAP_BEGIN(AsyncStatementJSHelper)
+ NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIXPCScriptable
+
+#define XPC_MAP_CLASSNAME AsyncStatementJSHelper
+#define XPC_MAP_QUOTED_CLASSNAME "AsyncStatementJSHelper"
+#define XPC_MAP_WANT_GETPROPERTY
+#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE
+#include "xpc_map_end.h"
+
+NS_IMETHODIMP
+AsyncStatementJSHelper::GetProperty(nsIXPConnectWrappedNative *aWrapper,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ jsid aId,
+ JS::Value *_result,
+ bool *_retval)
+{
+ if (!JSID_IS_STRING(aId))
+ return NS_OK;
+
+ // Cast to async via mozI* since direct from nsISupports is ambiguous.
+ JS::RootedObject scope(aCtx, aScopeObj);
+ JS::RootedId id(aCtx, aId);
+ mozIStorageAsyncStatement *iAsyncStmt =
+ static_cast<mozIStorageAsyncStatement *>(aWrapper->Native());
+ AsyncStatement *stmt = static_cast<AsyncStatement *>(iAsyncStmt);
+
+#ifdef DEBUG
+ {
+ nsISupports *supp = aWrapper->Native();
+ nsCOMPtr<mozIStorageAsyncStatement> isStatement(do_QueryInterface(supp));
+ NS_ASSERTION(isStatement, "How is this not an async statement?!");
+ }
+#endif
+
+ if (::JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "params"))
+ return getParams(stmt, aCtx, scope, _result);
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncStatementParamsHolder
+
+NS_IMPL_ISUPPORTS(AsyncStatementParamsHolder, nsIXPConnectJSObjectHolder);
+
+JSObject*
+AsyncStatementParamsHolder::GetJSObject()
+{
+ return mHolder->GetJSObject();
+}
+
+AsyncStatementParamsHolder::AsyncStatementParamsHolder(nsIXPConnectJSObjectHolder* aHolder)
+ : mHolder(aHolder)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mHolder);
+}
+
+AsyncStatementParamsHolder::~AsyncStatementParamsHolder()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // We are considered dead at this point, so any wrappers for row or params
+ // need to lose their reference to the statement.
+ nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder);
+ nsCOMPtr<mozIStorageStatementParams> iObj = do_QueryWrappedNative(wrapper);
+ AsyncStatementParams *obj = static_cast<AsyncStatementParams *>(iObj.get());
+ obj->mStatement = nullptr;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageAsyncStatementJSHelper.h b/components/storage/src/mozStorageAsyncStatementJSHelper.h
new file mode 100644
index 000000000..df28225de
--- /dev/null
+++ b/components/storage/src/mozStorageAsyncStatementJSHelper.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_mozStorageAsyncStatementJSHelper_h_
+#define mozilla_storage_mozStorageAsyncStatementJSHelper_h_
+
+#include "nsIXPCScriptable.h"
+#include "nsIXPConnect.h"
+
+class AsyncStatement;
+
+namespace mozilla {
+namespace storage {
+
+/**
+ * A modified version of StatementJSHelper that only exposes the async-specific
+ * 'params' helper. We do not expose 'row' or 'step' as they do not apply to
+ * us.
+ */
+class AsyncStatementJSHelper : public nsIXPCScriptable
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIXPCSCRIPTABLE
+
+private:
+ nsresult getParams(AsyncStatement *, JSContext *, JSObject *, JS::Value *);
+};
+
+/**
+ * Wrapper used to clean up the references JS helpers hold to the statement.
+ * For cycle-avoidance reasons they do not hold reference-counted references,
+ * so it is important we do this.
+ */
+class AsyncStatementParamsHolder final : public nsIXPConnectJSObjectHolder
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIXPCONNECTJSOBJECTHOLDER
+
+ explicit AsyncStatementParamsHolder(nsIXPConnectJSObjectHolder* aHolder);
+
+private:
+ virtual ~AsyncStatementParamsHolder();
+ nsCOMPtr<nsIXPConnectJSObjectHolder> mHolder;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozilla_storage_mozStorageAsyncStatementJSHelper_h_
diff --git a/components/storage/src/mozStorageAsyncStatementParams.cpp b/components/storage/src/mozStorageAsyncStatementParams.cpp
new file mode 100644
index 000000000..5e2d8c604
--- /dev/null
+++ b/components/storage/src/mozStorageAsyncStatementParams.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMemory.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsJSUtils.h"
+
+#include "jsapi.h"
+
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageAsyncStatement.h"
+#include "mozStorageAsyncStatementParams.h"
+#include "mozIStorageStatement.h"
+
+#include "xpc_make_class.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncStatementParams
+
+AsyncStatementParams::AsyncStatementParams(AsyncStatement *aStatement)
+: mStatement(aStatement)
+{
+ NS_ASSERTION(mStatement != nullptr, "mStatement is null");
+}
+
+NS_IMPL_ISUPPORTS(
+ AsyncStatementParams
+, mozIStorageStatementParams
+, nsIXPCScriptable
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIXPCScriptable
+
+#define XPC_MAP_CLASSNAME AsyncStatementParams
+#define XPC_MAP_QUOTED_CLASSNAME "AsyncStatementParams"
+#define XPC_MAP_WANT_SETPROPERTY
+#define XPC_MAP_WANT_RESOLVE
+#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE
+#include "xpc_map_end.h"
+
+NS_IMETHODIMP
+AsyncStatementParams::SetProperty(
+ nsIXPConnectWrappedNative *aWrapper,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ jsid aId,
+ JS::Value *_vp,
+ bool *_retval
+)
+{
+ NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
+
+ if (JSID_IS_INT(aId)) {
+ int idx = JSID_TO_INT(aId);
+
+ nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp));
+ NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED);
+ nsresult rv = mStatement->BindByIndex(idx, variant);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (JSID_IS_STRING(aId)) {
+ JSString *str = JSID_TO_STRING(aId);
+ nsAutoJSString autoStr;
+ if (!autoStr.init(aCtx, str)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ConvertUTF16toUTF8 name(autoStr);
+
+ nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp));
+ NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED);
+ nsresult rv = mStatement->BindByName(name, variant);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncStatementParams::Resolve(nsIXPConnectWrappedNative *aWrapper,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ jsid aId,
+ bool *aResolvedp,
+ bool *_retval)
+{
+ JS::Rooted<JSObject*> scopeObj(aCtx, aScopeObj);
+
+ NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
+ // We do not throw at any point after this because we want to allow the
+ // prototype chain to be checked for the property.
+
+ bool resolved = false;
+ bool ok = true;
+ if (JSID_IS_INT(aId)) {
+ uint32_t idx = JSID_TO_INT(aId);
+ // All indexes are good because we don't know how many parameters there
+ // really are.
+ ok = ::JS_DefineElement(aCtx, scopeObj, idx, JS::UndefinedHandleValue,
+ JSPROP_RESOLVING);
+ resolved = true;
+ }
+ else if (JSID_IS_STRING(aId)) {
+ // We are unable to tell if there's a parameter with this name and so
+ // we must assume that there is. This screws the rest of the prototype
+ // chain, but people really shouldn't be depending on this anyways.
+ JS::Rooted<jsid> id(aCtx, aId);
+ ok = ::JS_DefinePropertyById(aCtx, scopeObj, id, JS::UndefinedHandleValue,
+ JSPROP_RESOLVING);
+ resolved = true;
+ }
+
+ *_retval = ok;
+ *aResolvedp = resolved && ok;
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageAsyncStatementParams.h b/components/storage/src/mozStorageAsyncStatementParams.h
new file mode 100644
index 000000000..f753c6399
--- /dev/null
+++ b/components/storage/src/mozStorageAsyncStatementParams.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_mozStorageAsyncStatementParams_h_
+#define mozilla_storage_mozStorageAsyncStatementParams_h_
+
+#include "mozIStorageStatementParams.h"
+#include "nsIXPCScriptable.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace storage {
+
+class AsyncStatement;
+
+/*
+ * Since mozIStorageStatementParams is just a tagging interface we do not have
+ * an async variant.
+ */
+class AsyncStatementParams final : public mozIStorageStatementParams
+ , public nsIXPCScriptable
+{
+public:
+ explicit AsyncStatementParams(AsyncStatement *aStatement);
+
+ // interfaces
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGESTATEMENTPARAMS
+ NS_DECL_NSIXPCSCRIPTABLE
+
+protected:
+ virtual ~AsyncStatementParams() {}
+
+ AsyncStatement *mStatement;
+
+ friend class AsyncStatementParamsHolder;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozilla_storage_mozStorageAsyncStatementParams_h_
diff --git a/components/storage/src/mozStorageBindingParams.cpp b/components/storage/src/mozStorageBindingParams.cpp
new file mode 100644
index 000000000..98e114420
--- /dev/null
+++ b/components/storage/src/mozStorageBindingParams.cpp
@@ -0,0 +1,525 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <limits.h>
+
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsString.h"
+
+#include "mozStorageError.h"
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageBindingParams.h"
+#include "mozStorageBindingParamsArray.h"
+#include "Variant.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Local Helper Objects
+
+namespace {
+
+struct BindingColumnData
+{
+ BindingColumnData(sqlite3_stmt *aStmt,
+ int aColumn)
+ : stmt(aStmt)
+ , column(aColumn)
+ {
+ }
+ sqlite3_stmt *stmt;
+ int column;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Variant Specialization Functions (variantToSQLiteT)
+
+int
+sqlite3_T_int(BindingColumnData aData,
+ int aValue)
+{
+ return ::sqlite3_bind_int(aData.stmt, aData.column + 1, aValue);
+}
+
+int
+sqlite3_T_int64(BindingColumnData aData,
+ sqlite3_int64 aValue)
+{
+ return ::sqlite3_bind_int64(aData.stmt, aData.column + 1, aValue);
+}
+
+int
+sqlite3_T_double(BindingColumnData aData,
+ double aValue)
+{
+ return ::sqlite3_bind_double(aData.stmt, aData.column + 1, aValue);
+}
+
+int
+sqlite3_T_text(BindingColumnData aData,
+ const nsCString& aValue)
+{
+ return ::sqlite3_bind_text(aData.stmt,
+ aData.column + 1,
+ aValue.get(),
+ aValue.Length(),
+ SQLITE_TRANSIENT);
+}
+
+int
+sqlite3_T_text16(BindingColumnData aData,
+ const nsString& aValue)
+{
+ return ::sqlite3_bind_text16(aData.stmt,
+ aData.column + 1,
+ aValue.get(),
+ aValue.Length() * 2, // Length in bytes!
+ SQLITE_TRANSIENT);
+}
+
+int
+sqlite3_T_null(BindingColumnData aData)
+{
+ return ::sqlite3_bind_null(aData.stmt, aData.column + 1);
+}
+
+int
+sqlite3_T_blob(BindingColumnData aData,
+ const void *aBlob,
+ int aSize)
+{
+ return ::sqlite3_bind_blob(aData.stmt, aData.column + 1, aBlob, aSize,
+ free);
+
+}
+
+#include "variantToSQLiteT_impl.h"
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// BindingParams
+
+BindingParams::BindingParams(mozIStorageBindingParamsArray *aOwningArray,
+ Statement *aOwningStatement)
+: mLocked(false)
+, mOwningArray(aOwningArray)
+, mOwningStatement(aOwningStatement)
+, mParamCount(0)
+{
+ (void)mOwningStatement->GetParameterCount(&mParamCount);
+ mParameters.SetCapacity(mParamCount);
+}
+
+BindingParams::BindingParams(mozIStorageBindingParamsArray *aOwningArray)
+: mLocked(false)
+, mOwningArray(aOwningArray)
+, mOwningStatement(nullptr)
+, mParamCount(0)
+{
+}
+
+AsyncBindingParams::AsyncBindingParams(
+ mozIStorageBindingParamsArray *aOwningArray
+)
+: BindingParams(aOwningArray)
+{
+}
+
+void
+BindingParams::lock()
+{
+ NS_ASSERTION(mLocked == false, "Parameters have already been locked!");
+ mLocked = true;
+
+ // We no longer need to hold a reference to our statement or our owning array.
+ // The array owns us at this point, and it will own a reference to the
+ // statement.
+ mOwningStatement = nullptr;
+ mOwningArray = nullptr;
+}
+
+void
+BindingParams::unlock(Statement *aOwningStatement)
+{
+ NS_ASSERTION(mLocked == true, "Parameters were not yet locked!");
+ mLocked = false;
+ mOwningStatement = aOwningStatement;
+}
+
+const mozIStorageBindingParamsArray *
+BindingParams::getOwner() const
+{
+ return mOwningArray;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+NS_IMPL_ISUPPORTS(
+ BindingParams
+, mozIStorageBindingParams
+, IStorageBindingParamsInternal
+)
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// IStorageBindingParamsInternal
+
+already_AddRefed<mozIStorageError>
+BindingParams::bind(sqlite3_stmt *aStatement)
+{
+ // Iterate through all of our stored data, and bind it.
+ for (size_t i = 0; i < mParameters.Length(); i++) {
+ int rc = variantToSQLiteT(BindingColumnData(aStatement, i), mParameters[i]);
+ if (rc != SQLITE_OK) {
+ // We had an error while trying to bind. Now we need to create an error
+ // object with the right message. Note that we special case
+ // SQLITE_MISMATCH, but otherwise get the message from SQLite.
+ const char *msg = "Could not covert nsIVariant to SQLite type.";
+ if (rc != SQLITE_MISMATCH)
+ msg = ::sqlite3_errmsg(::sqlite3_db_handle(aStatement));
+
+ nsCOMPtr<mozIStorageError> err(new Error(rc, msg));
+ return err.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<mozIStorageError>
+AsyncBindingParams::bind(sqlite3_stmt * aStatement)
+{
+ // We should bind by index using the super-class if there is nothing in our
+ // hashtable.
+ if (!mNamedParameters.Count())
+ return BindingParams::bind(aStatement);
+
+ nsCOMPtr<mozIStorageError> err;
+
+ for (auto iter = mNamedParameters.Iter(); !iter.Done(); iter.Next()) {
+ const nsACString &key = iter.Key();
+
+ // We do not accept any forms of names other than ":name", but we need to
+ // add the colon for SQLite.
+ nsAutoCString name(":");
+ name.Append(key);
+ int oneIdx = ::sqlite3_bind_parameter_index(aStatement, name.get());
+
+ if (oneIdx == 0) {
+ nsAutoCString errMsg(key);
+ errMsg.AppendLiteral(" is not a valid named parameter.");
+ err = new Error(SQLITE_RANGE, errMsg.get());
+ break;
+ }
+
+ // XPCVariant's AddRef and Release are not thread-safe and so we must not
+ // do anything that would invoke them here on the async thread. As such we
+ // can't cram aValue into mParameters using ReplaceObjectAt so that
+ // we can freeload off of the BindingParams::Bind implementation.
+ int rc = variantToSQLiteT(BindingColumnData(aStatement, oneIdx - 1),
+ iter.UserData());
+ if (rc != SQLITE_OK) {
+ // We had an error while trying to bind. Now we need to create an error
+ // object with the right message. Note that we special case
+ // SQLITE_MISMATCH, but otherwise get the message from SQLite.
+ const char *msg = "Could not covert nsIVariant to SQLite type.";
+ if (rc != SQLITE_MISMATCH) {
+ msg = ::sqlite3_errmsg(::sqlite3_db_handle(aStatement));
+ }
+ err = new Error(rc, msg);
+ break;
+ }
+ }
+
+ return err.forget();
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+//// mozIStorageBindingParams
+
+NS_IMETHODIMP
+BindingParams::BindByName(const nsACString &aName,
+ nsIVariant *aValue)
+{
+ NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+
+ // Get the column index that we need to store this at.
+ uint32_t index;
+ nsresult rv = mOwningStatement->GetParameterIndex(aName, &index);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return BindByIndex(index, aValue);
+}
+
+NS_IMETHODIMP
+AsyncBindingParams::BindByName(const nsACString &aName,
+ nsIVariant *aValue)
+{
+ NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+
+ RefPtr<Variant_base> variant = convertVariantToStorageVariant(aValue);
+ if (!variant)
+ return NS_ERROR_UNEXPECTED;
+
+ mNamedParameters.Put(aName, variant);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+BindingParams::BindUTF8StringByName(const nsACString &aName,
+ const nsACString &aValue)
+{
+ nsCOMPtr<nsIVariant> value(new UTF8TextVariant(aValue));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindStringByName(const nsACString &aName,
+ const nsAString &aValue)
+{
+ nsCOMPtr<nsIVariant> value(new TextVariant(aValue));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindDoubleByName(const nsACString &aName,
+ double aValue)
+{
+ nsCOMPtr<nsIVariant> value(new FloatVariant(aValue));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindInt32ByName(const nsACString &aName,
+ int32_t aValue)
+{
+ nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindInt64ByName(const nsACString &aName,
+ int64_t aValue)
+{
+ nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindNullByName(const nsACString &aName)
+{
+ nsCOMPtr<nsIVariant> value(new NullVariant());
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindBlobByName(const nsACString &aName,
+ const uint8_t *aValue,
+ uint32_t aValueSize)
+{
+ NS_ENSURE_ARG_MAX(aValueSize, INT_MAX);
+ std::pair<const void *, int> data(
+ static_cast<const void *>(aValue),
+ int(aValueSize)
+ );
+ nsCOMPtr<nsIVariant> value(new BlobVariant(data));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindStringAsBlobByName(const nsACString& aName,
+ const nsAString& aValue)
+{
+ return DoBindStringAsBlobByName(this, aName, aValue);
+}
+
+NS_IMETHODIMP
+BindingParams::BindUTF8StringAsBlobByName(const nsACString& aName,
+ const nsACString& aValue)
+{
+ return DoBindStringAsBlobByName(this, aName, aValue);
+}
+
+
+NS_IMETHODIMP
+BindingParams::BindAdoptedBlobByName(const nsACString &aName,
+ uint8_t *aValue,
+ uint32_t aValueSize)
+{
+ UniqueFreePtr<uint8_t> uniqueValue(aValue);
+ NS_ENSURE_ARG_MAX(aValueSize, INT_MAX);
+ std::pair<uint8_t *, int> data(uniqueValue.release(), int(aValueSize));
+ nsCOMPtr<nsIVariant> value(new AdoptedBlobVariant(data));
+
+ return BindByName(aName, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindByIndex(uint32_t aIndex,
+ nsIVariant *aValue)
+{
+ NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+ ENSURE_INDEX_VALUE(aIndex, mParamCount);
+
+ // Store the variant for later use.
+ RefPtr<Variant_base> variant = convertVariantToStorageVariant(aValue);
+ if (!variant)
+ return NS_ERROR_UNEXPECTED;
+ if (mParameters.Length() <= aIndex) {
+ (void)mParameters.SetLength(aIndex);
+ (void)mParameters.AppendElement(variant);
+ }
+ else {
+ NS_ENSURE_TRUE(mParameters.ReplaceElementAt(aIndex, variant),
+ NS_ERROR_OUT_OF_MEMORY);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncBindingParams::BindByIndex(uint32_t aIndex,
+ nsIVariant *aValue)
+{
+ NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+ // In the asynchronous case we do not know how many parameters there are to
+ // bind to, so we cannot check the validity of aIndex.
+
+ RefPtr<Variant_base> variant = convertVariantToStorageVariant(aValue);
+ if (!variant)
+ return NS_ERROR_UNEXPECTED;
+ if (mParameters.Length() <= aIndex) {
+ mParameters.SetLength(aIndex);
+ mParameters.AppendElement(variant);
+ }
+ else {
+ NS_ENSURE_TRUE(mParameters.ReplaceElementAt(aIndex, variant),
+ NS_ERROR_OUT_OF_MEMORY);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BindingParams::BindUTF8StringByIndex(uint32_t aIndex,
+ const nsACString &aValue)
+{
+ nsCOMPtr<nsIVariant> value(new UTF8TextVariant(aValue));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindStringByIndex(uint32_t aIndex,
+ const nsAString &aValue)
+{
+ nsCOMPtr<nsIVariant> value(new TextVariant(aValue));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindDoubleByIndex(uint32_t aIndex,
+ double aValue)
+{
+ nsCOMPtr<nsIVariant> value(new FloatVariant(aValue));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindInt32ByIndex(uint32_t aIndex,
+ int32_t aValue)
+{
+ nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindInt64ByIndex(uint32_t aIndex,
+ int64_t aValue)
+{
+ nsCOMPtr<nsIVariant> value(new IntegerVariant(aValue));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindNullByIndex(uint32_t aIndex)
+{
+ nsCOMPtr<nsIVariant> value(new NullVariant());
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindBlobByIndex(uint32_t aIndex,
+ const uint8_t *aValue,
+ uint32_t aValueSize)
+{
+ NS_ENSURE_ARG_MAX(aValueSize, INT_MAX);
+ std::pair<const void *, int> data(
+ static_cast<const void *>(aValue),
+ int(aValueSize)
+ );
+ nsCOMPtr<nsIVariant> value(new BlobVariant(data));
+ NS_ENSURE_TRUE(value, NS_ERROR_OUT_OF_MEMORY);
+
+ return BindByIndex(aIndex, value);
+}
+
+NS_IMETHODIMP
+BindingParams::BindStringAsBlobByIndex(uint32_t aIndex, const nsAString& aValue)
+{
+ return DoBindStringAsBlobByIndex(this, aIndex, aValue);
+}
+
+NS_IMETHODIMP
+BindingParams::BindUTF8StringAsBlobByIndex(uint32_t aIndex,
+ const nsACString& aValue)
+{
+ return DoBindStringAsBlobByIndex(this, aIndex, aValue);
+}
+
+NS_IMETHODIMP
+BindingParams::BindAdoptedBlobByIndex(uint32_t aIndex,
+ uint8_t *aValue,
+ uint32_t aValueSize)
+{
+ UniqueFreePtr<uint8_t> uniqueValue(aValue);
+ NS_ENSURE_ARG_MAX(aValueSize, INT_MAX);
+ std::pair<uint8_t *, int> data(uniqueValue.release(), int(aValueSize));
+ nsCOMPtr<nsIVariant> value(new AdoptedBlobVariant(data));
+
+ return BindByIndex(aIndex, value);
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageBindingParams.h b/components/storage/src/mozStorageBindingParams.h
new file mode 100644
index 000000000..86d00b02b
--- /dev/null
+++ b/components/storage/src/mozStorageBindingParams.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStorageBindingParams_h
+#define mozStorageBindingParams_h
+
+#include "nsCOMArray.h"
+#include "nsIVariant.h"
+#include "nsInterfaceHashtable.h"
+
+#include "mozStorageBindingParamsArray.h"
+#include "mozStorageStatement.h"
+#include "mozStorageAsyncStatement.h"
+#include "Variant.h"
+
+#include "mozIStorageBindingParams.h"
+#include "IStorageBindingParamsInternal.h"
+
+namespace mozilla {
+namespace storage {
+
+class BindingParams : public mozIStorageBindingParams
+ , public IStorageBindingParamsInternal
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEBINDINGPARAMS
+ NS_DECL_ISTORAGEBINDINGPARAMSINTERNAL
+
+ /**
+ * Locks the parameters and prevents further modification to it (such as
+ * binding more elements to it).
+ */
+ void lock();
+
+ /**
+ * Unlocks the parameters and allows modification to it again.
+ *
+ * @param aOwningStatement
+ * The statement that owns us. We cleared this when we were locked,
+ * and our invariant requires us to have this, so you need to tell us
+ * again.
+ */
+ void unlock(Statement *aOwningStatement);
+
+ /**
+ * @returns the pointer to the owning BindingParamsArray. Used by a
+ * BindingParamsArray to verify that we belong to it when added.
+ */
+ const mozIStorageBindingParamsArray *getOwner() const;
+
+ BindingParams(mozIStorageBindingParamsArray *aOwningArray,
+ Statement *aOwningStatement);
+protected:
+ virtual ~BindingParams() {}
+
+ explicit BindingParams(mozIStorageBindingParamsArray *aOwningArray);
+ // Note that this is managed as a sparse array, so particular caution should
+ // be used for out-of-bounds usage.
+ nsTArray<RefPtr<Variant_base> > mParameters;
+ bool mLocked;
+
+private:
+
+ /**
+ * Track the BindingParamsArray that created us until we are added to it.
+ * (Once we are added we are locked and no one needs to look up our owner.)
+ * Ref-counted since there is no invariant that guarantees it stays alive
+ * otherwise. This keeps mOwningStatement alive for us too since the array
+ * also holds a reference.
+ */
+ nsCOMPtr<mozIStorageBindingParamsArray> mOwningArray;
+ /**
+ * Used in the synchronous binding case to map parameter names to indices.
+ * Not reference-counted because this is only non-null as long as mOwningArray
+ * is non-null and mOwningArray also holds a statement reference.
+ */
+ Statement *mOwningStatement;
+ uint32_t mParamCount;
+};
+
+/**
+ * Adds late resolution of named parameters so they don't get resolved until we
+ * try and bind the parameters on the async thread. We also stop checking
+ * parameter indices for being too big since we just just don't know how many
+ * there are.
+ *
+ * We support *either* binding by name or binding by index. Trying to do both
+ * results in only binding by name at sqlite3_stmt bind time.
+ */
+class AsyncBindingParams : public BindingParams
+{
+public:
+ NS_IMETHOD BindByName(const nsACString & aName,
+ nsIVariant *aValue);
+ NS_IMETHOD BindByIndex(uint32_t aIndex, nsIVariant *aValue);
+
+ virtual already_AddRefed<mozIStorageError> bind(sqlite3_stmt * aStatement);
+
+ explicit AsyncBindingParams(mozIStorageBindingParamsArray *aOwningArray);
+ virtual ~AsyncBindingParams() {}
+
+private:
+ nsInterfaceHashtable<nsCStringHashKey, nsIVariant> mNamedParameters;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStorageBindingParams_h
diff --git a/components/storage/src/mozStorageBindingParamsArray.cpp b/components/storage/src/mozStorageBindingParamsArray.cpp
new file mode 100644
index 000000000..fb7c9f14a
--- /dev/null
+++ b/components/storage/src/mozStorageBindingParamsArray.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozStorageBindingParamsArray.h"
+#include "mozStorageBindingParams.h"
+#include "StorageBaseStatementInternal.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// BindingParamsArray
+
+BindingParamsArray::BindingParamsArray(
+ StorageBaseStatementInternal *aOwningStatement
+)
+: mOwningStatement(aOwningStatement)
+, mLocked(false)
+{
+}
+
+void
+BindingParamsArray::lock()
+{
+ NS_ASSERTION(mLocked == false, "Array has already been locked!");
+ mLocked = true;
+
+ // We also no longer need to hold a reference to our statement since it owns
+ // us.
+ mOwningStatement = nullptr;
+}
+
+const StorageBaseStatementInternal *
+BindingParamsArray::getOwner() const
+{
+ return mOwningStatement;
+}
+
+NS_IMPL_ISUPPORTS(
+ BindingParamsArray,
+ mozIStorageBindingParamsArray
+)
+
+///////////////////////////////////////////////////////////////////////////////
+//// mozIStorageBindingParamsArray
+
+NS_IMETHODIMP
+BindingParamsArray::NewBindingParams(mozIStorageBindingParams **_params)
+{
+ NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<mozIStorageBindingParams> params(
+ mOwningStatement->newBindingParams(this));
+ NS_ENSURE_TRUE(params, NS_ERROR_UNEXPECTED);
+
+ params.forget(_params);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BindingParamsArray::AddParams(mozIStorageBindingParams *aParameters)
+{
+ NS_ENSURE_FALSE(mLocked, NS_ERROR_UNEXPECTED);
+
+ BindingParams *params = static_cast<BindingParams *>(aParameters);
+
+ // Check to make sure that this set of parameters was created with us.
+ if (params->getOwner() != this)
+ return NS_ERROR_UNEXPECTED;
+
+ NS_ENSURE_TRUE(mArray.AppendElement(params), NS_ERROR_OUT_OF_MEMORY);
+
+ // Lock the parameters only after we've successfully added them.
+ params->lock();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BindingParamsArray::GetLength(uint32_t *_length)
+{
+ *_length = length();
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageBindingParamsArray.h b/components/storage/src/mozStorageBindingParamsArray.h
new file mode 100644
index 000000000..4626ab55f
--- /dev/null
+++ b/components/storage/src/mozStorageBindingParamsArray.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStorageBindingParamsArray_h
+#define mozStorageBindingParamsArray_h
+
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+
+#include "mozIStorageBindingParamsArray.h"
+
+namespace mozilla {
+namespace storage {
+
+class StorageBaseStatementInternal;
+
+class BindingParamsArray final : public mozIStorageBindingParamsArray
+{
+ typedef nsTArray< nsCOMPtr<mozIStorageBindingParams> > array_type;
+
+ ~BindingParamsArray() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEBINDINGPARAMSARRAY
+
+ explicit BindingParamsArray(StorageBaseStatementInternal *aOwningStatement);
+
+ typedef array_type::size_type size_type;
+
+ /**
+ * Locks the array and prevents further modification to it (such as adding
+ * more elements to it).
+ */
+ void lock();
+
+ /**
+ * @return the pointer to the owning BindingParamsArray.
+ */
+ const StorageBaseStatementInternal *getOwner() const;
+
+ /**
+ * @return the number of elemets the array contains.
+ */
+ size_type length() const { return mArray.Length(); }
+
+ class iterator {
+ public:
+ iterator(BindingParamsArray *aArray,
+ uint32_t aIndex)
+ : mArray(aArray)
+ , mIndex(aIndex)
+ {
+ }
+
+ iterator &operator++(int)
+ {
+ mIndex++;
+ return *this;
+ }
+
+ bool operator==(const iterator &aOther) const
+ {
+ return mIndex == aOther.mIndex;
+ }
+ bool operator!=(const iterator &aOther) const
+ {
+ return !(*this == aOther);
+ }
+ mozIStorageBindingParams *operator*()
+ {
+ NS_ASSERTION(mIndex < mArray->length(),
+ "Dereferenceing an invalid value!");
+ return mArray->mArray[mIndex].get();
+ }
+ private:
+ void operator--() { }
+ BindingParamsArray *mArray;
+ uint32_t mIndex;
+ };
+
+ /**
+ * Obtains an iterator pointing to the beginning of the array.
+ */
+ inline iterator begin()
+ {
+ NS_ASSERTION(length() != 0,
+ "Obtaining an iterator to the beginning with no elements!");
+ return iterator(this, 0);
+ }
+
+ /**
+ * Obtains an iterator pointing to the end of the array.
+ */
+ inline iterator end()
+ {
+ NS_ASSERTION(mLocked,
+ "Obtaining an iterator to the end when we are not locked!");
+ return iterator(this, length());
+ }
+private:
+ nsCOMPtr<StorageBaseStatementInternal> mOwningStatement;
+ array_type mArray;
+ bool mLocked;
+
+ friend class iterator;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStorageBindingParamsArray_h
diff --git a/components/storage/src/mozStorageCID.h b/components/storage/src/mozStorageCID.h
new file mode 100644
index 000000000..c682d07dd
--- /dev/null
+++ b/components/storage/src/mozStorageCID.h
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+#ifndef MOZSTORAGECID_H
+#define MOZSTORAGECID_H
+
+#define MOZ_STORAGE_CONTRACTID_PREFIX "@mozilla.org/storage"
+
+
+/* b71a1f84-3a70-4d37-a348-f1ba0e27eead */
+#define MOZ_STORAGE_CONNECTION_CID \
+{ 0xb71a1f84, 0x3a70, 0x4d37, {0xa3, 0x48, 0xf1, 0xba, 0x0e, 0x27, 0xee, 0xad} }
+
+#define MOZ_STORAGE_CONNECTION_CONTRACTID MOZ_STORAGE_CONTRACTID_PREFIX "/connection;1"
+
+/* bbbb1d61-438f-4436-92ed-8308e5830fb0 */
+#define MOZ_STORAGE_SERVICE_CID \
+{ 0xbbbb1d61, 0x438f, 0x4436, {0x92, 0xed, 0x83, 0x08, 0xe5, 0x83, 0x0f, 0xb0} }
+
+#define MOZ_STORAGE_SERVICE_CONTRACTID MOZ_STORAGE_CONTRACTID_PREFIX "/service;1"
+
+/* 3b667ee0-d2da-4ccc-9c3d-95f2ca6a8b4c */
+#define VACUUMMANAGER_CID \
+{ 0x3b667ee0, 0xd2da, 0x4ccc, { 0x9c, 0x3d, 0x95, 0xf2, 0xca, 0x6a, 0x8b, 0x4c } }
+
+#define VACUUMMANAGER_CONTRACTID MOZ_STORAGE_CONTRACTID_PREFIX "/vacuum;1"
+
+#endif /* MOZSTORAGECID_H */
diff --git a/components/storage/src/mozStorageConnection.cpp b/components/storage/src/mozStorageConnection.cpp
new file mode 100644
index 000000000..c1d5374c7
--- /dev/null
+++ b/components/storage/src/mozStorageConnection.cpp
@@ -0,0 +1,1994 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+
+#include "nsError.h"
+#include "nsIMutableArray.h"
+#include "nsAutoPtr.h"
+#include "nsIMemoryReporter.h"
+#include "nsThreadUtils.h"
+#include "nsIFile.h"
+#include "nsIFileURL.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/quota/QuotaObject.h"
+
+#include "mozIStorageAggregateFunction.h"
+#include "mozIStorageCompletionCallback.h"
+#include "mozIStorageFunction.h"
+
+#include "mozStorageAsyncStatementExecution.h"
+#include "mozStorageSQLFunctions.h"
+#include "mozStorageConnection.h"
+#include "mozStorageService.h"
+#include "mozStorageStatement.h"
+#include "mozStorageAsyncStatement.h"
+#include "mozStorageArgValueArray.h"
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageStatementData.h"
+#include "StorageBaseStatementInternal.h"
+#include "SQLCollations.h"
+#include "FileSystemModule.h"
+#include "mozStorageHelper.h"
+#include "GeckoProfiler.h"
+
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include "nsProxyRelease.h"
+#include <algorithm>
+
+#define MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH 524288000 // 500 MiB
+
+// Maximum size of the pages cache per connection.
+#define MAX_CACHE_SIZE_KIBIBYTES 2048 // 2 MiB
+
+mozilla::LazyLogModule gStorageLog("mozStorage");
+
+// Checks that the protected code is running on the main-thread only if the
+// connection was also opened on it.
+#ifdef DEBUG
+#define CHECK_MAINTHREAD_ABUSE() \
+ do { \
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); \
+ NS_WARNING_ASSERTION( \
+ threadOpenedOn == mainThread || !NS_IsMainThread(), \
+ "Using Storage synchronous API on main-thread, but the connection was " \
+ "opened on another thread."); \
+ } while(0)
+#else
+#define CHECK_MAINTHREAD_ABUSE() do { /* Nothing */ } while(0)
+#endif
+
+namespace mozilla {
+namespace storage {
+
+using mozilla::dom::quota::QuotaObject;
+
+namespace {
+
+int
+nsresultToSQLiteResult(nsresult aXPCOMResultCode)
+{
+ if (NS_SUCCEEDED(aXPCOMResultCode)) {
+ return SQLITE_OK;
+ }
+
+ switch (aXPCOMResultCode) {
+ case NS_ERROR_FILE_CORRUPTED:
+ return SQLITE_CORRUPT;
+ case NS_ERROR_FILE_ACCESS_DENIED:
+ return SQLITE_CANTOPEN;
+ case NS_ERROR_STORAGE_BUSY:
+ return SQLITE_BUSY;
+ case NS_ERROR_FILE_IS_LOCKED:
+ return SQLITE_LOCKED;
+ case NS_ERROR_FILE_READ_ONLY:
+ return SQLITE_READONLY;
+ case NS_ERROR_STORAGE_IOERR:
+ return SQLITE_IOERR;
+ case NS_ERROR_FILE_NO_DEVICE_SPACE:
+ return SQLITE_FULL;
+ case NS_ERROR_OUT_OF_MEMORY:
+ return SQLITE_NOMEM;
+ case NS_ERROR_UNEXPECTED:
+ return SQLITE_MISUSE;
+ case NS_ERROR_ABORT:
+ return SQLITE_ABORT;
+ case NS_ERROR_STORAGE_CONSTRAINT:
+ return SQLITE_CONSTRAINT;
+ default:
+ return SQLITE_ERROR;
+ }
+
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Must return in switch above!");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Variant Specialization Functions (variantToSQLiteT)
+
+int
+sqlite3_T_int(sqlite3_context *aCtx,
+ int aValue)
+{
+ ::sqlite3_result_int(aCtx, aValue);
+ return SQLITE_OK;
+}
+
+int
+sqlite3_T_int64(sqlite3_context *aCtx,
+ sqlite3_int64 aValue)
+{
+ ::sqlite3_result_int64(aCtx, aValue);
+ return SQLITE_OK;
+}
+
+int
+sqlite3_T_double(sqlite3_context *aCtx,
+ double aValue)
+{
+ ::sqlite3_result_double(aCtx, aValue);
+ return SQLITE_OK;
+}
+
+int
+sqlite3_T_text(sqlite3_context *aCtx,
+ const nsCString &aValue)
+{
+ ::sqlite3_result_text(aCtx,
+ aValue.get(),
+ aValue.Length(),
+ SQLITE_TRANSIENT);
+ return SQLITE_OK;
+}
+
+int
+sqlite3_T_text16(sqlite3_context *aCtx,
+ const nsString &aValue)
+{
+ ::sqlite3_result_text16(aCtx,
+ aValue.get(),
+ aValue.Length() * 2, // Number of bytes.
+ SQLITE_TRANSIENT);
+ return SQLITE_OK;
+}
+
+int
+sqlite3_T_null(sqlite3_context *aCtx)
+{
+ ::sqlite3_result_null(aCtx);
+ return SQLITE_OK;
+}
+
+int
+sqlite3_T_blob(sqlite3_context *aCtx,
+ const void *aData,
+ int aSize)
+{
+ ::sqlite3_result_blob(aCtx, aData, aSize, free);
+ return SQLITE_OK;
+}
+
+#include "variantToSQLiteT_impl.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//// Modules
+
+struct Module
+{
+ const char* name;
+ int (*registerFunc)(sqlite3*, const char*);
+};
+
+Module gModules[] = {
+ { "filesystem", RegisterFileSystemModule }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Local Functions
+
+int tracefunc (unsigned aReason, void *aClosure, void *aP, void *aX)
+{
+ switch (aReason) {
+ case SQLITE_TRACE_STMT: {
+ // aP is a pointer to the prepared statement.
+ sqlite3_stmt* stmt = static_cast<sqlite3_stmt*>(aP);
+ // aX is a pointer to a string containing the unexpanded SQL or a comment,
+ // starting with "--"" in case of a trigger.
+ char* expanded = static_cast<char*>(aX);
+ // Simulate what sqlite_trace was doing.
+ if (!::strncmp(expanded, "--", 2)) {
+ MOZ_LOG(gStorageLog, LogLevel::Debug,
+ ("TRACE_STMT on %p: '%s'", aClosure, expanded));
+ } else {
+ char* sql = ::sqlite3_expanded_sql(stmt);
+ MOZ_LOG(gStorageLog, LogLevel::Debug,
+ ("TRACE_STMT on %p: '%s'", aClosure, sql));
+ ::sqlite3_free(sql);
+ }
+ break;
+ }
+ case SQLITE_TRACE_PROFILE: {
+ // aX is pointer to a 64bit integer containing nanoseconds it took to
+ // execute the last command.
+ sqlite_int64 time = *(static_cast<sqlite_int64*>(aX)) / 1000000;
+ if (time > 0) {
+ MOZ_LOG(gStorageLog, LogLevel::Debug,
+ ("TRACE_TIME on %p: %dms", aClosure, time));
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+void
+basicFunctionHelper(sqlite3_context *aCtx,
+ int aArgc,
+ sqlite3_value **aArgv)
+{
+ void *userData = ::sqlite3_user_data(aCtx);
+
+ mozIStorageFunction *func = static_cast<mozIStorageFunction *>(userData);
+
+ RefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv));
+ if (!arguments)
+ return;
+
+ nsCOMPtr<nsIVariant> result;
+ nsresult rv = func->OnFunctionCall(arguments, getter_AddRefs(result));
+ if (NS_FAILED(rv)) {
+ nsAutoCString errorMessage;
+ GetErrorName(rv, errorMessage);
+ errorMessage.InsertLiteral("User function returned ", 0);
+ errorMessage.Append('!');
+
+ NS_WARNING(errorMessage.get());
+
+ ::sqlite3_result_error(aCtx, errorMessage.get(), -1);
+ ::sqlite3_result_error_code(aCtx, nsresultToSQLiteResult(rv));
+ return;
+ }
+ int retcode = variantToSQLiteT(aCtx, result);
+ if (retcode == SQLITE_IGNORE) {
+ ::sqlite3_result_int(aCtx, SQLITE_IGNORE);
+ } else if (retcode != SQLITE_OK) {
+ NS_WARNING("User function returned invalid data type!");
+ ::sqlite3_result_error(aCtx,
+ "User function returned invalid data type",
+ -1);
+ }
+}
+
+void
+aggregateFunctionStepHelper(sqlite3_context *aCtx,
+ int aArgc,
+ sqlite3_value **aArgv)
+{
+ void *userData = ::sqlite3_user_data(aCtx);
+ mozIStorageAggregateFunction *func =
+ static_cast<mozIStorageAggregateFunction *>(userData);
+
+ RefPtr<ArgValueArray> arguments(new ArgValueArray(aArgc, aArgv));
+ if (!arguments)
+ return;
+
+ if (NS_FAILED(func->OnStep(arguments)))
+ NS_WARNING("User aggregate step function returned error code!");
+}
+
+void
+aggregateFunctionFinalHelper(sqlite3_context *aCtx)
+{
+ void *userData = ::sqlite3_user_data(aCtx);
+ mozIStorageAggregateFunction *func =
+ static_cast<mozIStorageAggregateFunction *>(userData);
+
+ RefPtr<nsIVariant> result;
+ if (NS_FAILED(func->OnFinal(getter_AddRefs(result)))) {
+ NS_WARNING("User aggregate final function returned error code!");
+ ::sqlite3_result_error(aCtx,
+ "User aggregate final function returned error code",
+ -1);
+ return;
+ }
+
+ if (variantToSQLiteT(aCtx, result) != SQLITE_OK) {
+ NS_WARNING("User aggregate final function returned invalid data type!");
+ ::sqlite3_result_error(aCtx,
+ "User aggregate final function returned invalid data type",
+ -1);
+ }
+}
+
+/**
+ * This code is heavily based on the sample at:
+ * http://www.sqlite.org/unlock_notify.html
+ */
+class UnlockNotification
+{
+public:
+ UnlockNotification()
+ : mMutex("UnlockNotification mMutex")
+ , mCondVar(mMutex, "UnlockNotification condVar")
+ , mSignaled(false)
+ {
+ }
+
+ void Wait()
+ {
+ MutexAutoLock lock(mMutex);
+ while (!mSignaled) {
+ (void)mCondVar.Wait();
+ }
+ }
+
+ void Signal()
+ {
+ MutexAutoLock lock(mMutex);
+ mSignaled = true;
+ (void)mCondVar.Notify();
+ }
+
+private:
+ Mutex mMutex;
+ CondVar mCondVar;
+ bool mSignaled;
+};
+
+void
+UnlockNotifyCallback(void **aArgs,
+ int aArgsSize)
+{
+ for (int i = 0; i < aArgsSize; i++) {
+ UnlockNotification *notification =
+ static_cast<UnlockNotification *>(aArgs[i]);
+ notification->Signal();
+ }
+}
+
+int
+WaitForUnlockNotify(sqlite3* aDatabase)
+{
+ UnlockNotification notification;
+ int srv = ::sqlite3_unlock_notify(aDatabase, UnlockNotifyCallback,
+ &notification);
+ MOZ_ASSERT(srv == SQLITE_LOCKED || srv == SQLITE_OK);
+ if (srv == SQLITE_OK) {
+ notification.Wait();
+ }
+
+ return srv;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// Local Classes
+
+namespace {
+
+class AsyncCloseConnection final: public Runnable
+{
+public:
+ AsyncCloseConnection(Connection *aConnection,
+ sqlite3 *aNativeConnection,
+ nsIRunnable *aCallbackEvent,
+ already_AddRefed<nsIThread> aAsyncExecutionThread)
+ : mConnection(aConnection)
+ , mNativeConnection(aNativeConnection)
+ , mCallbackEvent(aCallbackEvent)
+ , mAsyncExecutionThread(aAsyncExecutionThread)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+#ifdef DEBUG
+ // This code is executed on the background thread
+ bool onAsyncThread = false;
+ (void)mAsyncExecutionThread->IsOnCurrentThread(&onAsyncThread);
+ MOZ_ASSERT(onAsyncThread);
+#endif // DEBUG
+
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod<nsCOMPtr<nsIThread>>
+ (mConnection, &Connection::shutdownAsyncThread, mAsyncExecutionThread);
+ (void)NS_DispatchToMainThread(event);
+
+ // Internal close.
+ (void)mConnection->internalClose(mNativeConnection);
+
+ // Callback
+ if (mCallbackEvent) {
+ nsCOMPtr<nsIThread> thread;
+ (void)NS_GetMainThread(getter_AddRefs(thread));
+ (void)thread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL);
+ }
+
+ return NS_OK;
+ }
+
+ ~AsyncCloseConnection() {
+ NS_ReleaseOnMainThread(mConnection.forget());
+ NS_ReleaseOnMainThread(mCallbackEvent.forget());
+ }
+private:
+ RefPtr<Connection> mConnection;
+ sqlite3 *mNativeConnection;
+ nsCOMPtr<nsIRunnable> mCallbackEvent;
+ nsCOMPtr<nsIThread> mAsyncExecutionThread;
+};
+
+/**
+ * An event used to initialize the clone of a connection.
+ *
+ * Must be executed on the clone's async execution thread.
+ */
+class AsyncInitializeClone final: public Runnable
+{
+public:
+ /**
+ * @param aConnection The connection being cloned.
+ * @param aClone The clone.
+ * @param aReadOnly If |true|, the clone is read only.
+ * @param aCallback A callback to trigger once initialization
+ * is complete. This event will be called on
+ * aClone->threadOpenedOn.
+ */
+ AsyncInitializeClone(Connection* aConnection,
+ Connection* aClone,
+ const bool aReadOnly,
+ mozIStorageCompletionCallback* aCallback)
+ : mConnection(aConnection)
+ , mClone(aClone)
+ , mReadOnly(aReadOnly)
+ , mCallback(aCallback)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT (NS_GetCurrentThread() == mConnection->getAsyncExecutionTarget());
+
+ nsresult rv = mConnection->initializeClone(mClone, mReadOnly);
+ if (NS_FAILED(rv)) {
+ return Dispatch(rv, nullptr);
+ }
+ return Dispatch(NS_OK,
+ NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mClone));
+ }
+
+private:
+ nsresult Dispatch(nsresult aResult, nsISupports* aValue) {
+ RefPtr<CallbackComplete> event = new CallbackComplete(aResult,
+ aValue,
+ mCallback.forget());
+ return mClone->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+
+ ~AsyncInitializeClone() {
+ nsCOMPtr<nsIThread> thread;
+ DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Handle ambiguous nsISupports inheritance.
+ NS_ProxyRelease(thread, mConnection.forget());
+ NS_ProxyRelease(thread, mClone.forget());
+
+ // Generally, the callback will be released by CallbackComplete.
+ // However, if for some reason Run() is not executed, we still
+ // need to ensure that it is released here.
+ NS_ProxyRelease(thread, mCallback.forget());
+ }
+
+ RefPtr<Connection> mConnection;
+ RefPtr<Connection> mClone;
+ const bool mReadOnly;
+ nsCOMPtr<mozIStorageCompletionCallback> mCallback;
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// Connection
+
+Connection::Connection(Service *aService,
+ int aFlags,
+ bool aAsyncOnly,
+ bool aIgnoreLockingMode)
+: sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex")
+, sharedDBMutex("Connection::sharedDBMutex")
+, threadOpenedOn(do_GetCurrentThread())
+, mDBConn(nullptr)
+, mAsyncExecutionThreadShuttingDown(false)
+#ifdef DEBUG
+, mAsyncExecutionThreadIsAlive(false)
+#endif
+, mConnectionClosed(false)
+, mTransactionInProgress(false)
+, mProgressHandler(nullptr)
+, mFlags(aFlags)
+, mIgnoreLockingMode(aIgnoreLockingMode)
+, mStorageService(aService)
+, mAsyncOnly(aAsyncOnly)
+{
+ MOZ_ASSERT(!mIgnoreLockingMode || mFlags & SQLITE_OPEN_READONLY,
+ "Can't ignore locking for a non-readonly connection!");
+ mStorageService->registerConnection(this);
+}
+
+Connection::~Connection()
+{
+ (void)Close();
+
+ MOZ_ASSERT(!mAsyncExecutionThread,
+ "AsyncClose has not been invoked on this connection!");
+ MOZ_ASSERT(!mAsyncExecutionThreadIsAlive,
+ "The async execution thread should have been shutdown!");
+}
+
+NS_IMPL_ADDREF(Connection)
+
+NS_INTERFACE_MAP_BEGIN(Connection)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncConnection)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(mozIStorageConnection, !mAsyncOnly)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageConnection)
+NS_INTERFACE_MAP_END
+
+// This is identical to what NS_IMPL_RELEASE provides, but with the
+// extra |1 == count| case.
+NS_IMETHODIMP_(MozExternalRefCountType) Connection::Release(void)
+{
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "Connection");
+ if (1 == count) {
+ // If the refcount is 1, the single reference must be from
+ // gService->mConnections (in class |Service|). Which means we can
+ // unregister it safely.
+ mStorageService->unregisterConnection(this);
+ } else if (0 == count) {
+ mRefCnt = 1; /* stabilize */
+#if 0 /* enable this to find non-threadsafe destructors: */
+ NS_ASSERT_OWNINGTHREAD(Connection);
+#endif
+ delete (this);
+ return 0;
+ }
+ return count;
+}
+
+int32_t
+Connection::getSqliteRuntimeStatus(int32_t aStatusOption, int32_t* aMaxValue)
+{
+ MOZ_ASSERT(mDBConn, "A connection must exist at this point");
+ int curr = 0, max = 0;
+ DebugOnly<int> rc = ::sqlite3_db_status(mDBConn, aStatusOption, &curr, &max, 0);
+ MOZ_ASSERT(NS_SUCCEEDED(convertResultCode(rc)));
+ if (aMaxValue)
+ *aMaxValue = max;
+ return curr;
+}
+
+nsIEventTarget *
+Connection::getAsyncExecutionTarget()
+{
+ MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+
+ // If we are shutting down the asynchronous thread, don't hand out any more
+ // references to the thread.
+ if (mAsyncExecutionThreadShuttingDown)
+ return nullptr;
+
+ if (!mAsyncExecutionThread) {
+ nsresult rv = ::NS_NewThread(getter_AddRefs(mAsyncExecutionThread));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create async thread.");
+ return nullptr;
+ }
+ static nsThreadPoolNaming naming;
+ naming.SetThreadPoolName(NS_LITERAL_CSTRING("mozStorage"),
+ mAsyncExecutionThread);
+ }
+
+#ifdef DEBUG
+ mAsyncExecutionThreadIsAlive = true;
+#endif
+
+ return mAsyncExecutionThread;
+}
+
+nsresult
+Connection::initialize()
+{
+ NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
+ MOZ_ASSERT(!mIgnoreLockingMode, "Can't ignore locking on an in-memory db.");
+ PROFILER_LABEL("mozStorageConnection", "initialize",
+ js::ProfileEntry::Category::STORAGE);
+
+ // in memory database requested, sqlite uses a magic file name
+ int srv = ::sqlite3_open_v2(":memory:", &mDBConn, mFlags, nullptr);
+ if (srv != SQLITE_OK) {
+ mDBConn = nullptr;
+ return convertResultCode(srv);
+ }
+
+ // Do not set mDatabaseFile or mFileURL here since this is a "memory"
+ // database.
+
+ nsresult rv = initializeInternal();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Connection::initialize(nsIFile *aDatabaseFile)
+{
+ NS_ASSERTION (aDatabaseFile, "Passed null file!");
+ NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
+ PROFILER_LABEL("mozStorageConnection", "initialize",
+ js::ProfileEntry::Category::STORAGE);
+
+ mDatabaseFile = aDatabaseFile;
+
+ nsAutoString path;
+ nsresult rv = aDatabaseFile->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef XP_WIN
+ static const char* sIgnoreLockingVFS = "win32-none";
+#else
+ static const char* sIgnoreLockingVFS = "unix-none";
+#endif
+ const char* vfs = mIgnoreLockingMode ? sIgnoreLockingVFS : nullptr;
+
+ int srv = ::sqlite3_open_v2(NS_ConvertUTF16toUTF8(path).get(), &mDBConn,
+ mFlags, vfs);
+ if (srv != SQLITE_OK) {
+ mDBConn = nullptr;
+ return convertResultCode(srv);
+ }
+
+ // Do not set mFileURL here since this is database does not have an associated
+ // URL.
+ mDatabaseFile = aDatabaseFile;
+
+ rv = initializeInternal();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Connection::initialize(nsIFileURL *aFileURL)
+{
+ NS_ASSERTION (aFileURL, "Passed null file URL!");
+ NS_ASSERTION (!mDBConn, "Initialize called on already opened database!");
+ PROFILER_LABEL("mozStorageConnection", "initialize",
+ js::ProfileEntry::Category::STORAGE);
+
+ nsCOMPtr<nsIFile> databaseFile;
+ nsresult rv = aFileURL->GetFile(getter_AddRefs(databaseFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = aFileURL->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int srv = ::sqlite3_open_v2(spec.get(), &mDBConn, mFlags, nullptr);
+ if (srv != SQLITE_OK) {
+ mDBConn = nullptr;
+ return convertResultCode(srv);
+ }
+
+ // Set both mDatabaseFile and mFileURL here.
+ mFileURL = aFileURL;
+ mDatabaseFile = databaseFile;
+
+ rv = initializeInternal();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+Connection::initializeInternal()
+{
+ MOZ_ASSERT(mDBConn);
+
+ if (mFileURL) {
+ const char* dbPath = ::sqlite3_db_filename(mDBConn, "main");
+ MOZ_ASSERT(dbPath);
+ }
+
+ // Properly wrap the database handle's mutex.
+ sharedDBMutex.initWithMutex(sqlite3_db_mutex(mDBConn));
+
+ int64_t pageSize = Service::getDefaultPageSize();
+
+ // Set page_size to the preferred default value. This is effective only if
+ // the database has just been created, otherwise, if the database does not
+ // use WAL journal mode, a VACUUM operation will updated its page_size.
+ nsAutoCString pageSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
+ "PRAGMA page_size = ");
+ pageSizeQuery.AppendInt(pageSize);
+ nsresult rv = ExecuteSimpleSQL(pageSizeQuery);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Setting the cache_size forces the database open, verifying if it is valid
+ // or corrupt. So this is executed regardless it being actually needed.
+ // The cache_size is calculated from the actual page_size, to save memory.
+ nsAutoCString cacheSizeQuery(MOZ_STORAGE_UNIQUIFY_QUERY_STR
+ "PRAGMA cache_size = ");
+ cacheSizeQuery.AppendInt(-MAX_CACHE_SIZE_KIBIBYTES);
+ int srv = executeSql(mDBConn, cacheSizeQuery.get());
+ if (srv != SQLITE_OK) {
+ ::sqlite3_close(mDBConn);
+ mDBConn = nullptr;
+ return convertResultCode(srv);
+ }
+
+#if defined(MOZ_MEMORY_TEMP_STORE_PRAGMA)
+ (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA temp_store = 2;"));
+#endif
+
+ // Register our built-in SQL functions.
+ srv = registerFunctions(mDBConn);
+ if (srv != SQLITE_OK) {
+ ::sqlite3_close(mDBConn);
+ mDBConn = nullptr;
+ return convertResultCode(srv);
+ }
+
+ // Register our built-in SQL collating sequences.
+ srv = registerCollations(mDBConn, mStorageService);
+ if (srv != SQLITE_OK) {
+ ::sqlite3_close(mDBConn);
+ mDBConn = nullptr;
+ return convertResultCode(srv);
+ }
+
+ // Set the synchronous PRAGMA, according to the preference.
+ switch (Service::getSynchronousPref()) {
+ case 2:
+ (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA synchronous = FULL;"));
+ break;
+ case 0:
+ (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA synchronous = OFF;"));
+ break;
+ case 1:
+ default:
+ (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "PRAGMA synchronous = NORMAL;"));
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+Connection::databaseElementExists(enum DatabaseElementType aElementType,
+ const nsACString &aElementName,
+ bool *_exists)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ // When constructing the query, make sure to SELECT the correct db's sqlite_master
+ // if the user is prefixing the element with a specific db. ex: sample.test
+ nsCString query("SELECT name FROM (SELECT * FROM ");
+ nsDependentCSubstring element;
+ int32_t ind = aElementName.FindChar('.');
+ if (ind == kNotFound) {
+ element.Assign(aElementName);
+ }
+ else {
+ nsDependentCSubstring db(Substring(aElementName, 0, ind + 1));
+ element.Assign(Substring(aElementName, ind + 1, aElementName.Length()));
+ query.Append(db);
+ }
+ query.AppendLiteral("sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = '");
+
+ switch (aElementType) {
+ case INDEX:
+ query.AppendLiteral("index");
+ break;
+ case TABLE:
+ query.AppendLiteral("table");
+ break;
+ }
+ query.AppendLiteral("' AND name ='");
+ query.Append(element);
+ query.Append('\'');
+
+ sqlite3_stmt *stmt;
+ int srv = prepareStatement(mDBConn, query, &stmt);
+ if (srv != SQLITE_OK)
+ return convertResultCode(srv);
+
+ srv = stepStatement(mDBConn, stmt);
+ // we just care about the return value from step
+ (void)::sqlite3_finalize(stmt);
+
+ if (srv == SQLITE_ROW) {
+ *_exists = true;
+ return NS_OK;
+ }
+ if (srv == SQLITE_DONE) {
+ *_exists = false;
+ return NS_OK;
+ }
+
+ return convertResultCode(srv);
+}
+
+bool
+Connection::findFunctionByInstance(nsISupports *aInstance)
+{
+ sharedDBMutex.assertCurrentThreadOwns();
+
+ for (auto iter = mFunctions.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.UserData().function == aInstance) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */ int
+Connection::sProgressHelper(void *aArg)
+{
+ Connection *_this = static_cast<Connection *>(aArg);
+ return _this->progressHandler();
+}
+
+int
+Connection::progressHandler()
+{
+ sharedDBMutex.assertCurrentThreadOwns();
+ if (mProgressHandler) {
+ bool result;
+ nsresult rv = mProgressHandler->OnProgress(this, &result);
+ if (NS_FAILED(rv)) return 0; // Don't break request
+ return result ? 1 : 0;
+ }
+ return 0;
+}
+
+nsresult
+Connection::setClosedState()
+{
+ // Ensure that we are on the correct thread to close the database.
+ bool onOpenedThread;
+ nsresult rv = threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!onOpenedThread) {
+ NS_ERROR("Must close the database on the thread that you opened it with!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Flag that we are shutting down the async thread, so that
+ // getAsyncExecutionTarget knows not to expose/create the async thread.
+ {
+ MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+ NS_ENSURE_FALSE(mAsyncExecutionThreadShuttingDown, NS_ERROR_UNEXPECTED);
+ mAsyncExecutionThreadShuttingDown = true;
+ }
+
+ // Set the property to null before closing the connection, otherwise the other
+ // functions in the module may try to use the connection after it is closed.
+ mDBConn = nullptr;
+
+ return NS_OK;
+}
+
+bool
+Connection::connectionReady()
+{
+ return mDBConn != nullptr;
+}
+
+bool
+Connection::isClosing()
+{
+ bool shuttingDown = false;
+ {
+ MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+ shuttingDown = mAsyncExecutionThreadShuttingDown;
+ }
+ return shuttingDown && !isClosed();
+}
+
+bool
+Connection::isClosed()
+{
+ MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+ return mConnectionClosed;
+}
+
+void
+Connection::shutdownAsyncThread(nsIThread *aThread) {
+ MOZ_ASSERT(!mAsyncExecutionThread);
+ MOZ_ASSERT(mAsyncExecutionThreadIsAlive);
+ MOZ_ASSERT(mAsyncExecutionThreadShuttingDown);
+
+ DebugOnly<nsresult> rv = aThread->Shutdown();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+#ifdef DEBUG
+ mAsyncExecutionThreadIsAlive = false;
+#endif
+}
+
+nsresult
+Connection::internalClose(sqlite3 *aNativeConnection)
+{
+ // Sanity checks to make sure we are in the proper state before calling this.
+ // aNativeConnection can be null if OpenAsyncDatabase failed and is now just
+ // cleaning up the async thread.
+ MOZ_ASSERT(!isClosed());
+
+#ifdef DEBUG
+ { // Make sure we have marked our async thread as shutting down.
+ MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+ NS_ASSERTION(mAsyncExecutionThreadShuttingDown,
+ "Did not call setClosedState!");
+ }
+#endif // DEBUG
+
+ if (MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) {
+ nsAutoCString leafName(":memory");
+ if (mDatabaseFile)
+ (void)mDatabaseFile->GetNativeLeafName(leafName);
+ MOZ_LOG(gStorageLog, LogLevel::Debug, ("Closing connection to '%s'",
+ leafName.get()));
+ }
+
+ // At this stage, we may still have statements that need to be
+ // finalized. Attempt to close the database connection. This will
+ // always disconnect any virtual tables and cleanly finalize their
+ // internal statements. Once this is done, closing may fail due to
+ // unfinalized client statements, in which case we need to finalize
+ // these statements and close again.
+ {
+ MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+ mConnectionClosed = true;
+ }
+
+ // Nothing else needs to be done if we don't have a connection here.
+ if (!aNativeConnection)
+ return NS_OK;
+
+ int srv = sqlite3_close(aNativeConnection);
+
+ if (srv == SQLITE_BUSY) {
+ // We still have non-finalized statements. Finalize them.
+
+ sqlite3_stmt *stmt = nullptr;
+ while ((stmt = ::sqlite3_next_stmt(aNativeConnection, stmt))) {
+ MOZ_LOG(gStorageLog, LogLevel::Debug,
+ ("Auto-finalizing SQL statement '%s' (%x)",
+ ::sqlite3_sql(stmt),
+ stmt));
+
+#ifdef DEBUG
+ char *msg = ::PR_smprintf("SQL statement '%s' (%x) should have been finalized before closing the connection",
+ ::sqlite3_sql(stmt),
+ stmt);
+ NS_WARNING(msg);
+ ::PR_smprintf_free(msg);
+ msg = nullptr;
+#endif // DEBUG
+
+ srv = ::sqlite3_finalize(stmt);
+
+#ifdef DEBUG
+ if (srv != SQLITE_OK) {
+ msg = ::PR_smprintf("Could not finalize SQL statement '%s' (%x)",
+ ::sqlite3_sql(stmt),
+ stmt);
+ NS_WARNING(msg);
+ ::PR_smprintf_free(msg);
+ msg = nullptr;
+ }
+#endif // DEBUG
+
+ // Ensure that the loop continues properly, whether closing has succeeded
+ // or not.
+ if (srv == SQLITE_OK) {
+ stmt = nullptr;
+ }
+ }
+
+ // Now that all statements have been finalized, we
+ // should be able to close.
+ srv = ::sqlite3_close(aNativeConnection);
+
+ }
+
+ if (srv != SQLITE_OK) {
+ MOZ_ASSERT(srv == SQLITE_OK,
+ "sqlite3_close failed. There are probably outstanding statements that are listed above!");
+ }
+
+ return convertResultCode(srv);
+}
+
+nsCString
+Connection::getFilename()
+{
+ nsCString leafname(":memory:");
+ if (mDatabaseFile) {
+ (void)mDatabaseFile->GetNativeLeafName(leafname);
+ }
+ return leafname;
+}
+
+int
+Connection::stepStatement(sqlite3 *aNativeConnection, sqlite3_stmt *aStatement)
+{
+ MOZ_ASSERT(aStatement);
+ bool checkedMainThread = false;
+ TimeStamp startTime = TimeStamp::Now();
+
+ // The connection may have been closed if the executing statement has been
+ // created and cached after a call to asyncClose() but before the actual
+ // sqlite3_close(). This usually happens when other tasks using cached
+ // statements are asynchronously scheduled for execution and any of them ends
+ // up after asyncClose. See bug 728653 for details.
+ if (isClosed())
+ return SQLITE_MISUSE;
+
+ (void)::sqlite3_extended_result_codes(aNativeConnection, 1);
+
+ int srv;
+ while ((srv = ::sqlite3_step(aStatement)) == SQLITE_LOCKED_SHAREDCACHE) {
+ if (!checkedMainThread) {
+ checkedMainThread = true;
+ if (::NS_IsMainThread()) {
+ NS_WARNING("We won't allow blocking on the main thread!");
+ break;
+ }
+ }
+
+ srv = WaitForUnlockNotify(aNativeConnection);
+ if (srv != SQLITE_OK) {
+ break;
+ }
+
+ ::sqlite3_reset(aStatement);
+ }
+
+ (void)::sqlite3_extended_result_codes(aNativeConnection, 0);
+ // Drop off the extended result bits of the result code.
+ return srv & 0xFF;
+}
+
+int
+Connection::prepareStatement(sqlite3 *aNativeConnection, const nsCString &aSQL,
+ sqlite3_stmt **_stmt)
+{
+ // We should not even try to prepare statements after the connection has
+ // been closed.
+ if (isClosed())
+ return SQLITE_MISUSE;
+
+ bool checkedMainThread = false;
+
+ (void)::sqlite3_extended_result_codes(aNativeConnection, 1);
+
+ int srv;
+ while((srv = ::sqlite3_prepare_v2(aNativeConnection,
+ aSQL.get(),
+ -1,
+ _stmt,
+ nullptr)) == SQLITE_LOCKED_SHAREDCACHE) {
+ if (!checkedMainThread) {
+ checkedMainThread = true;
+ if (::NS_IsMainThread()) {
+ NS_WARNING("We won't allow blocking on the main thread!");
+ break;
+ }
+ }
+
+ srv = WaitForUnlockNotify(aNativeConnection);
+ if (srv != SQLITE_OK) {
+ break;
+ }
+ }
+
+ if (srv != SQLITE_OK) {
+ nsCString warnMsg;
+ warnMsg.AppendLiteral("The SQL statement '");
+ warnMsg.Append(aSQL);
+ warnMsg.AppendLiteral("' could not be compiled due to an error: ");
+ warnMsg.Append(::sqlite3_errmsg(aNativeConnection));
+
+#ifdef DEBUG
+ NS_WARNING(warnMsg.get());
+#endif
+ MOZ_LOG(gStorageLog, LogLevel::Error, ("%s", warnMsg.get()));
+ }
+
+ (void)::sqlite3_extended_result_codes(aNativeConnection, 0);
+ // Drop off the extended result bits of the result code.
+ int rc = srv & 0xFF;
+ // sqlite will return OK on a comment only string and set _stmt to nullptr.
+ // The callers of this function are used to only checking the return value,
+ // so it is safer to return an error code.
+ if (rc == SQLITE_OK && *_stmt == nullptr) {
+ return SQLITE_MISUSE;
+ }
+
+ return rc;
+}
+
+
+int
+Connection::executeSql(sqlite3 *aNativeConnection, const char *aSqlString)
+{
+ if (isClosed())
+ return SQLITE_MISUSE;
+
+ TimeStamp startTime = TimeStamp::Now();
+ int srv = ::sqlite3_exec(aNativeConnection, aSqlString, nullptr, nullptr,
+ nullptr);
+
+ return srv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+Connection::GetInterface(const nsIID &aIID,
+ void **_result)
+{
+ if (aIID.Equals(NS_GET_IID(nsIEventTarget))) {
+ nsIEventTarget *background = getAsyncExecutionTarget();
+ NS_IF_ADDREF(background);
+ *_result = background;
+ return NS_OK;
+ }
+ return NS_ERROR_NO_INTERFACE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageConnection
+
+NS_IMETHODIMP
+Connection::Close()
+{
+ if (!mDBConn)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ { // Make sure we have not executed any asynchronous statements.
+ // If this fails, the mDBConn will be left open, resulting in a leak.
+ // Ideally we'd schedule some code to destroy the mDBConn once all its
+ // async statements have finished executing; see bug 704030.
+ MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+ bool asyncCloseWasCalled = !mAsyncExecutionThread;
+ NS_ENSURE_TRUE(asyncCloseWasCalled, NS_ERROR_UNEXPECTED);
+ }
+
+ // setClosedState nullifies our connection pointer, so we take a raw pointer
+ // off it, to pass it through the close procedure.
+ sqlite3 *nativeConn = mDBConn;
+ nsresult rv = setClosedState();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return internalClose(nativeConn);
+}
+
+NS_IMETHODIMP
+Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
+{
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ // The two relevant factors at this point are whether we have a database
+ // connection and whether we have an async execution thread. Here's what the
+ // states mean and how we handle them:
+ //
+ // - (mDBConn && asyncThread): The expected case where we are either an
+ // async connection or a sync connection that has been used asynchronously.
+ // Either way the caller must call us and not Close(). Nothing surprising
+ // about this. We'll dispatch AsyncCloseConnection to the already-existing
+ // async thread.
+ //
+ // - (mDBConn && !asyncThread): A somewhat unusual case where the caller
+ // opened the connection synchronously and was planning to use it
+ // asynchronously, but never got around to using it asynchronously before
+ // needing to shutdown. This has been observed to happen for the cookie
+ // service in a case where Firefox shuts itself down almost immediately
+ // after startup (for unknown reasons). In the Firefox shutdown case,
+ // we may also fail to create a new async execution thread if one does not
+ // already exist. (nsThreadManager will refuse to create new threads when
+ // it has already been told to shutdown.) As such, we need to handle a
+ // failure to create the async execution thread by falling back to
+ // synchronous Close() and also dispatching the completion callback because
+ // at least Places likes to spin a nested event loop that depends on the
+ // callback being invoked.
+ //
+ // Note that we have considered not trying to spin up the async execution
+ // thread in this case if it does not already exist, but the overhead of
+ // thread startup (if successful) is significantly less expensive than the
+ // worst-case potential I/O hit of synchronously closing a database when we
+ // could close it asynchronously.
+ //
+ // - (!mDBConn && asyncThread): This happens in some but not all cases where
+ // OpenAsyncDatabase encountered a problem opening the database. If it
+ // happened in all cases AsyncInitDatabase would just shut down the thread
+ // directly and we would avoid this case. But it doesn't, so for simplicity
+ // and consistency AsyncCloseConnection knows how to handle this and we
+ // act like this was the (mDBConn && asyncThread) case in this method.
+ //
+ // - (!mDBConn && !asyncThread): The database was never successfully opened or
+ // Close() or AsyncClose() has already been called (at least) once. This is
+ // undeniably a misuse case by the caller. We could optimize for this
+ // case by adding an additional check of mAsyncExecutionThread without using
+ // getAsyncExecutionTarget() to avoid wastefully creating a thread just to
+ // shut it down. But this complicates the method for broken caller code
+ // whereas we're still correct and safe without the special-case.
+ nsIEventTarget *asyncThread = getAsyncExecutionTarget();
+
+ // Create our callback event if we were given a callback. This will
+ // eventually be dispatched in all cases, even if we fall back to Close() and
+ // the database wasn't open and we return an error. The rationale is that
+ // no existing consumer checks our return value and several of them like to
+ // spin nested event loops until the callback fires. Given that, it seems
+ // preferable for us to dispatch the callback in all cases. (Except the
+ // wrong thread misuse case we bailed on up above. But that's okay because
+ // that is statically wrong whereas these edge cases are dynamic.)
+ nsCOMPtr<nsIRunnable> completeEvent;
+ if (aCallback) {
+ completeEvent = newCompletionEvent(aCallback);
+ }
+
+ if (!asyncThread) {
+ // We were unable to create an async thread, so we need to fall back to
+ // using normal Close(). Since there is no async thread, Close() will
+ // not complain about that. (Close() may, however, complain if the
+ // connection is closed, but that's okay.)
+ if (completeEvent) {
+ // Closing the database is more important than returning an error code
+ // about a failure to dispatch, especially because all existing native
+ // callers ignore our return value.
+ Unused << NS_DispatchToMainThread(completeEvent.forget());
+ }
+ return Close();
+ }
+
+ // setClosedState nullifies our connection pointer, so we take a raw pointer
+ // off it, to pass it through the close procedure.
+ sqlite3 *nativeConn = mDBConn;
+ nsresult rv = setClosedState();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create and dispatch our close event to the background thread.
+ nsCOMPtr<nsIRunnable> closeEvent;
+ {
+ // We need to lock because we're modifying mAsyncExecutionThread
+ MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
+ closeEvent = new AsyncCloseConnection(this,
+ nativeConn,
+ completeEvent,
+ mAsyncExecutionThread.forget());
+ }
+
+ rv = asyncThread->Dispatch(closeEvent, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::AsyncClone(bool aReadOnly,
+ mozIStorageCompletionCallback *aCallback)
+{
+ PROFILER_LABEL("mozStorageConnection", "AsyncClone",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+ if (!mDBConn)
+ return NS_ERROR_NOT_INITIALIZED;
+ if (!mDatabaseFile)
+ return NS_ERROR_UNEXPECTED;
+
+ int flags = mFlags;
+ if (aReadOnly) {
+ // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY.
+ flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY;
+ // Turn off SQLITE_OPEN_CREATE.
+ flags = (~SQLITE_OPEN_CREATE & flags);
+ }
+
+ RefPtr<Connection> clone = new Connection(mStorageService, flags,
+ mAsyncOnly);
+
+ RefPtr<AsyncInitializeClone> initEvent =
+ new AsyncInitializeClone(this, clone, aReadOnly, aCallback);
+ // Dispatch to our async thread, since the originating connection must remain
+ // valid and open for the whole cloning process. This also ensures we are
+ // properly serialized with a `close` operation, rather than race with it.
+ nsCOMPtr<nsIEventTarget> target = getAsyncExecutionTarget();
+ if (!target) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return target->Dispatch(initEvent, NS_DISPATCH_NORMAL);
+}
+
+nsresult
+Connection::initializeClone(Connection* aClone, bool aReadOnly)
+{
+ nsresult rv = mFileURL ? aClone->initialize(mFileURL)
+ : aClone->initialize(mDatabaseFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Re-attach on-disk databases that were attached to the original connection.
+ {
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = CreateStatement(NS_LITERAL_CSTRING("PRAGMA database_list"),
+ getter_AddRefs(stmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ bool hasResult = false;
+ while (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ nsAutoCString name;
+ rv = stmt->GetUTF8String(1, name);
+ if (NS_SUCCEEDED(rv) && !name.Equals(NS_LITERAL_CSTRING("main")) &&
+ !name.Equals(NS_LITERAL_CSTRING("temp"))) {
+ nsCString path;
+ rv = stmt->GetUTF8String(2, path);
+ if (NS_SUCCEEDED(rv) && !path.IsEmpty()) {
+ rv = aClone->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ATTACH DATABASE '") +
+ path + NS_LITERAL_CSTRING("' AS ") + name);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "couldn't re-attach database to cloned connection");
+ }
+ }
+ }
+ }
+
+ // Copy over pragmas from the original connection.
+ static const char * pragmas[] = {
+ "cache_size",
+ "temp_store",
+ "foreign_keys",
+ "journal_size_limit",
+ "synchronous",
+ "wal_autocheckpoint",
+ "busy_timeout"
+ };
+ for (uint32_t i = 0; i < ArrayLength(pragmas); ++i) {
+ // Read-only connections just need cache_size and temp_store pragmas.
+ if (aReadOnly && ::strcmp(pragmas[i], "cache_size") != 0 &&
+ ::strcmp(pragmas[i], "temp_store") != 0) {
+ continue;
+ }
+
+ nsAutoCString pragmaQuery("PRAGMA ");
+ pragmaQuery.Append(pragmas[i]);
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = CreateStatement(pragmaQuery, getter_AddRefs(stmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ bool hasResult = false;
+ if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ pragmaQuery.AppendLiteral(" = ");
+ pragmaQuery.AppendInt(stmt->AsInt32(0));
+ rv = aClone->ExecuteSimpleSQL(pragmaQuery);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // Copy any functions that have been added to this connection.
+ SQLiteMutexAutoLock lockedScope(sharedDBMutex);
+ for (auto iter = mFunctions.Iter(); !iter.Done(); iter.Next()) {
+ const nsACString &key = iter.Key();
+ Connection::FunctionInfo data = iter.UserData();
+
+ MOZ_ASSERT(data.type == Connection::FunctionInfo::SIMPLE ||
+ data.type == Connection::FunctionInfo::AGGREGATE,
+ "Invalid function type!");
+
+ if (data.type == Connection::FunctionInfo::SIMPLE) {
+ mozIStorageFunction *function =
+ static_cast<mozIStorageFunction *>(data.function.get());
+ rv = aClone->CreateFunction(key, data.numArgs, function);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to copy function to cloned connection");
+ }
+
+ } else {
+ mozIStorageAggregateFunction *function =
+ static_cast<mozIStorageAggregateFunction *>(data.function.get());
+ rv = aClone->CreateAggregateFunction(key, data.numArgs, function);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to copy aggregate function to cloned connection");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::Clone(bool aReadOnly,
+ mozIStorageConnection **_connection)
+{
+ MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
+
+ PROFILER_LABEL("mozStorageConnection", "Clone",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (!mDBConn)
+ return NS_ERROR_NOT_INITIALIZED;
+ if (!mDatabaseFile)
+ return NS_ERROR_UNEXPECTED;
+
+ int flags = mFlags;
+ if (aReadOnly) {
+ // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY.
+ flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY;
+ // Turn off SQLITE_OPEN_CREATE.
+ flags = (~SQLITE_OPEN_CREATE & flags);
+ }
+
+ RefPtr<Connection> clone = new Connection(mStorageService, flags,
+ mAsyncOnly);
+
+ nsresult rv = initializeClone(clone, aReadOnly);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ NS_IF_ADDREF(*_connection = clone);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetDefaultPageSize(int32_t *_defaultPageSize)
+{
+ *_defaultPageSize = Service::getDefaultPageSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetConnectionReady(bool *_ready)
+{
+ *_ready = connectionReady();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetDatabaseFile(nsIFile **_dbFile)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ NS_IF_ADDREF(*_dbFile = mDatabaseFile);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetLastInsertRowID(int64_t *_id)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ sqlite_int64 id = ::sqlite3_last_insert_rowid(mDBConn);
+ *_id = id;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetAffectedRows(int32_t *_rows)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ *_rows = ::sqlite3_changes(mDBConn);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetLastError(int32_t *_error)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ *_error = ::sqlite3_errcode(mDBConn);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetLastErrorString(nsACString &_errorString)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ const char *serr = ::sqlite3_errmsg(mDBConn);
+ _errorString.Assign(serr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetSchemaVersion(int32_t *_version)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ (void)CreateStatement(NS_LITERAL_CSTRING("PRAGMA user_version"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY);
+
+ *_version = 0;
+ bool hasResult;
+ if (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult)
+ *_version = stmt->AsInt32(0);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::SetSchemaVersion(int32_t aVersion)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ nsAutoCString stmt(NS_LITERAL_CSTRING("PRAGMA user_version = "));
+ stmt.AppendInt(aVersion);
+
+ return ExecuteSimpleSQL(stmt);
+}
+
+NS_IMETHODIMP
+Connection::CreateStatement(const nsACString &aSQLStatement,
+ mozIStorageStatement **_stmt)
+{
+ NS_ENSURE_ARG_POINTER(_stmt);
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<Statement> statement(new Statement());
+ NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = statement->initialize(this, mDBConn, aSQLStatement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Statement *rawPtr;
+ statement.forget(&rawPtr);
+ *_stmt = rawPtr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::CreateAsyncStatement(const nsACString &aSQLStatement,
+ mozIStorageAsyncStatement **_stmt)
+{
+ NS_ENSURE_ARG_POINTER(_stmt);
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<AsyncStatement> statement(new AsyncStatement());
+ NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = statement->initialize(this, mDBConn, aSQLStatement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AsyncStatement *rawPtr;
+ statement.forget(&rawPtr);
+ *_stmt = rawPtr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::ExecuteSimpleSQL(const nsACString &aSQLStatement)
+{
+ CHECK_MAINTHREAD_ABUSE();
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ int srv = executeSql(mDBConn, PromiseFlatCString(aSQLStatement).get());
+ return convertResultCode(srv);
+}
+
+NS_IMETHODIMP
+Connection::ExecuteAsync(mozIStorageBaseStatement **aStatements,
+ uint32_t aNumStatements,
+ mozIStorageStatementCallback *aCallback,
+ mozIStoragePendingStatement **_handle)
+{
+ nsTArray<StatementData> stmts(aNumStatements);
+ for (uint32_t i = 0; i < aNumStatements; i++) {
+ nsCOMPtr<StorageBaseStatementInternal> stmt =
+ do_QueryInterface(aStatements[i]);
+
+ // Obtain our StatementData.
+ StatementData data;
+ nsresult rv = stmt->getAsynchronousStatementData(data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(stmt->getOwner() == this,
+ "Statement must be from this database connection!");
+
+ // Now append it to our array.
+ NS_ENSURE_TRUE(stmts.AppendElement(data), NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // Dispatch to the background
+ return AsyncExecuteStatements::execute(stmts, this, mDBConn, aCallback,
+ _handle);
+}
+
+NS_IMETHODIMP
+Connection::ExecuteSimpleSQLAsync(const nsACString &aSQLStatement,
+ mozIStorageStatementCallback *aCallback,
+ mozIStoragePendingStatement **_handle)
+{
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ nsresult rv = CreateAsyncStatement(aSQLStatement, getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
+ rv = stmt->ExecuteAsync(aCallback, getter_AddRefs(pendingStatement));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ pendingStatement.forget(_handle);
+ return rv;
+}
+
+NS_IMETHODIMP
+Connection::TableExists(const nsACString &aTableName,
+ bool *_exists)
+{
+ return databaseElementExists(TABLE, aTableName, _exists);
+}
+
+NS_IMETHODIMP
+Connection::IndexExists(const nsACString &aIndexName,
+ bool* _exists)
+{
+ return databaseElementExists(INDEX, aIndexName, _exists);
+}
+
+NS_IMETHODIMP
+Connection::GetTransactionInProgress(bool *_inProgress)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ SQLiteMutexAutoLock lockedScope(sharedDBMutex);
+ *_inProgress = mTransactionInProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::BeginTransaction()
+{
+ return BeginTransactionAs(mozIStorageConnection::TRANSACTION_DEFERRED);
+}
+
+NS_IMETHODIMP
+Connection::BeginTransactionAs(int32_t aTransactionType)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ return beginTransactionInternal(mDBConn, aTransactionType);
+}
+
+nsresult
+Connection::beginTransactionInternal(sqlite3 *aNativeConnection,
+ int32_t aTransactionType)
+{
+ SQLiteMutexAutoLock lockedScope(sharedDBMutex);
+ if (mTransactionInProgress)
+ return NS_ERROR_FAILURE;
+ nsresult rv;
+ switch(aTransactionType) {
+ case TRANSACTION_DEFERRED:
+ rv = convertResultCode(executeSql(aNativeConnection, "BEGIN DEFERRED"));
+ break;
+ case TRANSACTION_IMMEDIATE:
+ rv = convertResultCode(executeSql(aNativeConnection, "BEGIN IMMEDIATE"));
+ break;
+ case TRANSACTION_EXCLUSIVE:
+ rv = convertResultCode(executeSql(aNativeConnection, "BEGIN EXCLUSIVE"));
+ break;
+ default:
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (NS_SUCCEEDED(rv))
+ mTransactionInProgress = true;
+ return rv;
+}
+
+NS_IMETHODIMP
+Connection::CommitTransaction()
+{
+ if (!mDBConn)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return commitTransactionInternal(mDBConn);
+}
+
+nsresult
+Connection::commitTransactionInternal(sqlite3 *aNativeConnection)
+{
+ SQLiteMutexAutoLock lockedScope(sharedDBMutex);
+ if (!mTransactionInProgress)
+ return NS_ERROR_UNEXPECTED;
+ nsresult rv =
+ convertResultCode(executeSql(aNativeConnection, "COMMIT TRANSACTION"));
+ if (NS_SUCCEEDED(rv))
+ mTransactionInProgress = false;
+ return rv;
+}
+
+NS_IMETHODIMP
+Connection::RollbackTransaction()
+{
+ if (!mDBConn)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ return rollbackTransactionInternal(mDBConn);
+}
+
+nsresult
+Connection::rollbackTransactionInternal(sqlite3 *aNativeConnection)
+{
+ SQLiteMutexAutoLock lockedScope(sharedDBMutex);
+ if (!mTransactionInProgress)
+ return NS_ERROR_UNEXPECTED;
+
+ nsresult rv =
+ convertResultCode(executeSql(aNativeConnection, "ROLLBACK TRANSACTION"));
+ if (NS_SUCCEEDED(rv))
+ mTransactionInProgress = false;
+ return rv;
+}
+
+NS_IMETHODIMP
+Connection::CreateTable(const char *aTableName,
+ const char *aTableSchema)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ char *buf = ::PR_smprintf("CREATE TABLE %s (%s)", aTableName, aTableSchema);
+ if (!buf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ int srv = executeSql(mDBConn, buf);
+ ::PR_smprintf_free(buf);
+
+ return convertResultCode(srv);
+}
+
+NS_IMETHODIMP
+Connection::CreateFunction(const nsACString &aFunctionName,
+ int32_t aNumArguments,
+ mozIStorageFunction *aFunction)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ // Check to see if this function is already defined. We only check the name
+ // because a function can be defined with the same body but different names.
+ SQLiteMutexAutoLock lockedScope(sharedDBMutex);
+ NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE);
+
+ int srv = ::sqlite3_create_function(mDBConn,
+ nsPromiseFlatCString(aFunctionName).get(),
+ aNumArguments,
+ SQLITE_ANY,
+ aFunction,
+ basicFunctionHelper,
+ nullptr,
+ nullptr);
+ if (srv != SQLITE_OK)
+ return convertResultCode(srv);
+
+ FunctionInfo info = { aFunction,
+ Connection::FunctionInfo::SIMPLE,
+ aNumArguments };
+ mFunctions.Put(aFunctionName, info);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::CreateAggregateFunction(const nsACString &aFunctionName,
+ int32_t aNumArguments,
+ mozIStorageAggregateFunction *aFunction)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ // Check to see if this function name is already defined.
+ SQLiteMutexAutoLock lockedScope(sharedDBMutex);
+ NS_ENSURE_FALSE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE);
+
+ // Because aggregate functions depend on state across calls, you cannot have
+ // the same instance use the same name. We want to enumerate all functions
+ // and make sure this instance is not already registered.
+ NS_ENSURE_FALSE(findFunctionByInstance(aFunction), NS_ERROR_FAILURE);
+
+ int srv = ::sqlite3_create_function(mDBConn,
+ nsPromiseFlatCString(aFunctionName).get(),
+ aNumArguments,
+ SQLITE_ANY,
+ aFunction,
+ nullptr,
+ aggregateFunctionStepHelper,
+ aggregateFunctionFinalHelper);
+ if (srv != SQLITE_OK)
+ return convertResultCode(srv);
+
+ FunctionInfo info = { aFunction,
+ Connection::FunctionInfo::AGGREGATE,
+ aNumArguments };
+ mFunctions.Put(aFunctionName, info);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::RemoveFunction(const nsACString &aFunctionName)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ SQLiteMutexAutoLock lockedScope(sharedDBMutex);
+ NS_ENSURE_TRUE(mFunctions.Get(aFunctionName, nullptr), NS_ERROR_FAILURE);
+
+ int srv = ::sqlite3_create_function(mDBConn,
+ nsPromiseFlatCString(aFunctionName).get(),
+ 0,
+ SQLITE_ANY,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr);
+ if (srv != SQLITE_OK)
+ return convertResultCode(srv);
+
+ mFunctions.Remove(aFunctionName);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::SetProgressHandler(int32_t aGranularity,
+ mozIStorageProgressHandler *aHandler,
+ mozIStorageProgressHandler **_oldHandler)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ // Return previous one
+ SQLiteMutexAutoLock lockedScope(sharedDBMutex);
+ NS_IF_ADDREF(*_oldHandler = mProgressHandler);
+
+ if (!aHandler || aGranularity <= 0) {
+ aHandler = nullptr;
+ aGranularity = 0;
+ }
+ mProgressHandler = aHandler;
+ ::sqlite3_progress_handler(mDBConn, aGranularity, sProgressHelper, this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::RemoveProgressHandler(mozIStorageProgressHandler **_oldHandler)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ // Return previous one
+ SQLiteMutexAutoLock lockedScope(sharedDBMutex);
+ NS_IF_ADDREF(*_oldHandler = mProgressHandler);
+
+ mProgressHandler = nullptr;
+ ::sqlite3_progress_handler(mDBConn, 0, nullptr, nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::SetGrowthIncrement(int32_t aChunkSize, const nsACString &aDatabaseName)
+{
+ // Don't preallocate if less than 500MiB is available.
+ int64_t bytesAvailable;
+ nsresult rv = mDatabaseFile->GetDiskSpaceAvailable(&bytesAvailable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (bytesAvailable < MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ (void)::sqlite3_file_control(mDBConn,
+ aDatabaseName.Length() ? nsPromiseFlatCString(aDatabaseName).get()
+ : nullptr,
+ SQLITE_FCNTL_CHUNK_SIZE,
+ &aChunkSize);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::EnableModule(const nsACString& aModuleName)
+{
+ if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
+
+ for (size_t i = 0; i < ArrayLength(gModules); i++) {
+ struct Module* m = &gModules[i];
+ if (aModuleName.Equals(m->name)) {
+ int srv = m->registerFunc(mDBConn, m->name);
+ if (srv != SQLITE_OK)
+ return convertResultCode(srv);
+
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+// Implemented in TelemetryVFS.cpp
+already_AddRefed<QuotaObject>
+GetQuotaObjectForFile(sqlite3_file *pFile);
+
+NS_IMETHODIMP
+Connection::GetQuotaObjects(QuotaObject** aDatabaseQuotaObject,
+ QuotaObject** aJournalQuotaObject)
+{
+ MOZ_ASSERT(aDatabaseQuotaObject);
+ MOZ_ASSERT(aJournalQuotaObject);
+
+ if (!mDBConn) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ sqlite3_file* file;
+ int srv = ::sqlite3_file_control(mDBConn,
+ nullptr,
+ SQLITE_FCNTL_FILE_POINTER,
+ &file);
+ if (srv != SQLITE_OK) {
+ return convertResultCode(srv);
+ }
+
+ RefPtr<QuotaObject> databaseQuotaObject = GetQuotaObjectForFile(file);
+ if (NS_WARN_IF(!databaseQuotaObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ srv = ::sqlite3_file_control(mDBConn,
+ nullptr,
+ SQLITE_FCNTL_JOURNAL_POINTER,
+ &file);
+ if (srv != SQLITE_OK) {
+ return convertResultCode(srv);
+ }
+
+ RefPtr<QuotaObject> journalQuotaObject = GetQuotaObjectForFile(file);
+ if (NS_WARN_IF(!journalQuotaObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ databaseQuotaObject.forget(aDatabaseQuotaObject);
+ journalQuotaObject.forget(aJournalQuotaObject);
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageConnection.h b/components/storage/src/mozStorageConnection.h
new file mode 100644
index 000000000..979ac6436
--- /dev/null
+++ b/components/storage/src/mozStorageConnection.h
@@ -0,0 +1,439 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_Connection_h
+#define mozilla_storage_Connection_h
+
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Mutex.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsIInterfaceRequestor.h"
+
+#include "nsDataHashtable.h"
+#include "mozIStorageProgressHandler.h"
+#include "SQLiteMutex.h"
+#include "mozIStorageConnection.h"
+#include "mozStorageService.h"
+#include "mozIStorageAsyncConnection.h"
+#include "mozIStorageCompletionCallback.h"
+
+#include "nsIMutableArray.h"
+#include "mozilla/Attributes.h"
+
+#include "sqlite3.h"
+
+class nsIFile;
+class nsIFileURL;
+class nsIEventTarget;
+class nsIThread;
+
+namespace mozilla {
+namespace storage {
+
+class Connection final : public mozIStorageConnection
+ , public nsIInterfaceRequestor
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEASYNCCONNECTION
+ NS_DECL_MOZISTORAGECONNECTION
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ /**
+ * Structure used to describe user functions on the database connection.
+ */
+ struct FunctionInfo {
+ enum FunctionType {
+ SIMPLE,
+ AGGREGATE
+ };
+
+ nsCOMPtr<nsISupports> function;
+ FunctionType type;
+ int32_t numArgs;
+ };
+
+ /**
+ * @param aService
+ * Pointer to the storage service. Held onto for the lifetime of the
+ * connection.
+ * @param aFlags
+ * The flags to pass to sqlite3_open_v2.
+ * @param aAsyncOnly
+ * If |true|, the Connection only implements asynchronous interface:
+ * - |mozIStorageAsyncConnection|;
+ * If |false|, the result also implements synchronous interface:
+ * - |mozIStorageConnection|.
+ * @param aIgnoreLockingMode
+ * If |true|, ignore locks in force on the file. Only usable with
+ * read-only connections. Defaults to false.
+ * Use with extreme caution. If sqlite ignores locks, reads may fail
+ * indicating database corruption (the database won't actually be
+ * corrupt) or produce wrong results without any indication that has
+ * happened.
+ */
+ Connection(Service *aService, int aFlags, bool aAsyncOnly,
+ bool aIgnoreLockingMode = false);
+
+ /**
+ * Creates the connection to an in-memory database.
+ */
+ nsresult initialize();
+
+ /**
+ * Creates the connection to the database.
+ *
+ * @param aDatabaseFile
+ * The nsIFile of the location of the database to open, or create if it
+ * does not exist.
+ */
+ nsresult initialize(nsIFile *aDatabaseFile);
+
+ /**
+ * Creates the connection to the database.
+ *
+ * @param aFileURL
+ * The nsIFileURL of the location of the database to open, or create if it
+ * does not exist.
+ */
+ nsresult initialize(nsIFileURL *aFileURL);
+
+ /**
+ * Fetches runtime status information for this connection.
+ *
+ * @param aStatusOption One of the SQLITE_DBSTATUS options defined at
+ * http://www.sqlite.org/c3ref/c_dbstatus_options.html
+ * @param [optional] aMaxValue if provided, will be set to the highest
+ * istantaneous value.
+ * @return the current value for the specified option.
+ */
+ int32_t getSqliteRuntimeStatus(int32_t aStatusOption,
+ int32_t* aMaxValue=nullptr);
+ /**
+ * Registers/unregisters a commit hook callback.
+ *
+ * @param aCallbackFn a callback function to be invoked on transactions
+ * commit. Pass nullptr to unregister the current callback.
+ * @param [optional] aData if provided, will be passed to the callback.
+ * @see http://sqlite.org/c3ref/commit_hook.html
+ */
+ void setCommitHook(int (*aCallbackFn)(void *) , void *aData=nullptr) {
+ MOZ_ASSERT(mDBConn, "A connection must exist at this point");
+ ::sqlite3_commit_hook(mDBConn, aCallbackFn, aData);
+ };
+
+ /**
+ * Gets autocommit status.
+ */
+ bool getAutocommit() {
+ return mDBConn && static_cast<bool>(::sqlite3_get_autocommit(mDBConn));
+ };
+
+ /**
+ * Lazily creates and returns a background execution thread. In the future,
+ * the thread may be re-claimed if left idle, so you should call this
+ * method just before you dispatch and not save the reference.
+ *
+ * @returns an event target suitable for asynchronous statement execution.
+ */
+ nsIEventTarget *getAsyncExecutionTarget();
+
+ /**
+ * Mutex used by asynchronous statements to protect state. The mutex is
+ * declared on the connection object because there is no contention between
+ * asynchronous statements (they are serialized on mAsyncExecutionThread).
+ * Currently protects:
+ * - Connection.mAsyncExecutionThreadShuttingDown
+ * - Connection.mAsyncExecutionThread
+ * - Connection.mConnectionClosed
+ * - AsyncExecuteStatements.mCancelRequested
+ */
+ Mutex sharedAsyncExecutionMutex;
+
+ /**
+ * Wraps the mutex that SQLite gives us from sqlite3_db_mutex. This is public
+ * because we already expose the sqlite3* native connection and proper
+ * operation of the deadlock detector requires everyone to use the same single
+ * SQLiteMutex instance for correctness.
+ */
+ SQLiteMutex sharedDBMutex;
+
+ /**
+ * References the thread this database was opened on. This MUST be thread it is
+ * closed on.
+ */
+ const nsCOMPtr<nsIThread> threadOpenedOn;
+
+ /**
+ * Closes the SQLite database, and warns about any non-finalized statements.
+ */
+ nsresult internalClose(sqlite3 *aDBConn);
+
+ /**
+ * Shuts down the passed-in async thread.
+ */
+ void shutdownAsyncThread(nsIThread *aAsyncThread);
+
+ /**
+ * Obtains the filename of the connection. Useful for logging.
+ */
+ nsCString getFilename();
+
+ /**
+ * Creates an sqlite3 prepared statement object from an SQL string.
+ *
+ * @param aNativeConnection
+ * The underlying Sqlite connection to prepare the statement with.
+ * @param aSQL
+ * The SQL statement string to compile.
+ * @param _stmt
+ * New sqlite3_stmt object.
+ * @return the result from sqlite3_prepare_v2.
+ */
+ int prepareStatement(sqlite3* aNativeConnection,
+ const nsCString &aSQL, sqlite3_stmt **_stmt);
+
+ /**
+ * Performs a sqlite3_step on aStatement, while properly handling SQLITE_LOCKED
+ * when not on the main thread by waiting until we are notified.
+ *
+ * @param aNativeConnection
+ * The underlying Sqlite connection to step the statement with.
+ * @param aStatement
+ * A pointer to a sqlite3_stmt object.
+ * @return the result from sqlite3_step.
+ */
+ int stepStatement(sqlite3* aNativeConnection, sqlite3_stmt* aStatement);
+
+ /**
+ * Raw connection transaction management.
+ *
+ * @see BeginTransactionAs, CommitTransaction, RollbackTransaction.
+ */
+ nsresult beginTransactionInternal(sqlite3 *aNativeConnection,
+ int32_t aTransactionType=TRANSACTION_DEFERRED);
+ nsresult commitTransactionInternal(sqlite3 *aNativeConnection);
+ nsresult rollbackTransactionInternal(sqlite3 *aNativeConnection);
+
+ bool connectionReady();
+
+ /**
+ * True if this connection is shutting down but not yet closed.
+ */
+ bool isClosing();
+
+ /**
+ * True if the underlying connection is closed.
+ * Any sqlite resources may be lost when this returns true, so nothing should
+ * try to use them.
+ */
+ bool isClosed();
+
+ nsresult initializeClone(Connection *aClone, bool aReadOnly);
+
+private:
+ ~Connection();
+ nsresult initializeInternal();
+
+ /**
+ * Sets the database into a closed state so no further actions can be
+ * performed.
+ *
+ * @note mDBConn is set to nullptr in this method.
+ */
+ nsresult setClosedState();
+
+ /**
+ * Helper for calls to sqlite3_exec. Reports long delays to Telemetry.
+ *
+ * @param aNativeConnection
+ * The underlying Sqlite connection to execute the query with.
+ * @param aSqlString
+ * SQL string to execute
+ * @return the result from sqlite3_exec.
+ */
+ int executeSql(sqlite3 *aNativeConnection, const char *aSqlString);
+
+ /**
+ * Describes a certain primitive type in the database.
+ *
+ * Possible Values Are:
+ * INDEX - To check for the existence of an index
+ * TABLE - To check for the existence of a table
+ */
+ enum DatabaseElementType {
+ INDEX,
+ TABLE
+ };
+
+ /**
+ * Determines if the specified primitive exists.
+ *
+ * @param aElementType
+ * The type of element to check the existence of
+ * @param aElementName
+ * The name of the element to check for
+ * @returns true if element exists, false otherwise
+ */
+ nsresult databaseElementExists(enum DatabaseElementType aElementType,
+ const nsACString& aElementName,
+ bool *_exists);
+
+ bool findFunctionByInstance(nsISupports *aInstance);
+
+ static int sProgressHelper(void *aArg);
+ // Generic progress handler
+ // Dispatch call to registered progress handler,
+ // if there is one. Do nothing in other cases.
+ int progressHandler();
+
+ sqlite3 *mDBConn;
+ nsCOMPtr<nsIFileURL> mFileURL;
+ nsCOMPtr<nsIFile> mDatabaseFile;
+
+ /**
+ * The filename that will be reported to telemetry for this connection. By
+ * default this will be the leaf of the path to the database file.
+ */
+ nsCString mTelemetryFilename;
+
+ /**
+ * Lazily created thread for asynchronous statement execution. Consumers
+ * should use getAsyncExecutionTarget rather than directly accessing this
+ * field.
+ */
+ nsCOMPtr<nsIThread> mAsyncExecutionThread;
+
+ /**
+ * Set to true by Close() or AsyncClose() prior to shutdown.
+ *
+ * If false, we guarantee both that the underlying sqlite3 database
+ * connection is still open and that getAsyncExecutionTarget() can
+ * return a thread. Once true, either the sqlite3 database
+ * connection is being shutdown or it has been
+ * shutdown. Additionally, once true, getAsyncExecutionTarget()
+ * returns null.
+ *
+ * This variable should be accessed while holding the
+ * sharedAsyncExecutionMutex.
+ */
+ bool mAsyncExecutionThreadShuttingDown;
+
+ /**
+ * Tracks whether the async thread has been initialized and Shutdown() has
+ * not yet been invoked on it.
+ */
+#ifdef DEBUG
+ bool mAsyncExecutionThreadIsAlive;
+#endif
+
+ /**
+ * Set to true just prior to calling sqlite3_close on the
+ * connection.
+ *
+ * This variable should be accessed while holding the
+ * sharedAsyncExecutionMutex.
+ */
+ bool mConnectionClosed;
+
+ /**
+ * Tracks if we have a transaction in progress or not. Access protected by
+ * sharedDBMutex.
+ */
+ bool mTransactionInProgress;
+
+ /**
+ * Stores the mapping of a given function by name to its instance. Access is
+ * protected by sharedDBMutex.
+ */
+ nsDataHashtable<nsCStringHashKey, FunctionInfo> mFunctions;
+
+ /**
+ * Stores the registered progress handler for the database connection. Access
+ * is protected by sharedDBMutex.
+ */
+ nsCOMPtr<mozIStorageProgressHandler> mProgressHandler;
+
+ /**
+ * Stores the flags we passed to sqlite3_open_v2.
+ */
+ const int mFlags;
+
+ /**
+ * Stores whether we should ask sqlite3_open_v2 to ignore locking.
+ */
+ const bool mIgnoreLockingMode;
+
+ // This is here for two reasons: 1) It's used to make sure that the
+ // connections do not outlive the service. 2) Our custom collating functions
+ // call its localeCompareStrings() method.
+ RefPtr<Service> mStorageService;
+
+ /**
+ * If |false|, this instance supports synchronous operations
+ * and it can be cast to |mozIStorageConnection|.
+ */
+ const bool mAsyncOnly;
+};
+
+
+/**
+ * A Runnable designed to call a mozIStorageCompletionCallback on
+ * the appropriate thread.
+ */
+class CallbackComplete final : public Runnable
+{
+public:
+ /**
+ * @param aValue The result to pass to the callback. It must
+ * already be owned by the main thread.
+ * @param aCallback The callback. It must already be owned by the
+ * main thread.
+ */
+ CallbackComplete(nsresult aStatus,
+ nsISupports* aValue,
+ already_AddRefed<mozIStorageCompletionCallback> aCallback)
+ : mStatus(aStatus)
+ , mValue(aValue)
+ , mCallback(aCallback)
+ {
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = mCallback->Complete(mStatus, mValue);
+
+ // Ensure that we release on the main thread
+ mValue = nullptr;
+ mCallback = nullptr;
+ return rv;
+ }
+
+private:
+ nsresult mStatus;
+ nsCOMPtr<nsISupports> mValue;
+ // This is a RefPtr<T> and not a nsCOMPtr<T> because
+ // nsCOMP<T> would cause an off-main thread QI, which
+ // is not a good idea (and crashes XPConnect).
+ RefPtr<mozIStorageCompletionCallback> mCallback;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+/**
+ * Casting Connection to nsISupports is ambiguous.
+ * This method handles that.
+ */
+inline nsISupports*
+ToSupports(mozilla::storage::Connection* p)
+{
+ return NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, p);
+}
+
+#endif // mozilla_storage_Connection_h
diff --git a/components/storage/src/mozStorageError.cpp b/components/storage/src/mozStorageError.cpp
new file mode 100644
index 000000000..1ddf25314
--- /dev/null
+++ b/components/storage/src/mozStorageError.cpp
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozStorageError.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Error
+
+Error::Error(int aResult,
+ const char *aMessage)
+: mResult(aResult)
+, mMessage(aMessage)
+{
+}
+
+/**
+ * Note: This object is only ever accessed on one thread at a time. It it not
+ * threadsafe, but it does need threadsafe AddRef and Release.
+ */
+NS_IMPL_ISUPPORTS(
+ Error,
+ mozIStorageError
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageError
+
+NS_IMETHODIMP
+Error::GetResult(int32_t *_result)
+{
+ *_result = mResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Error::GetMessage(nsACString &_message)
+{
+ _message = mMessage;
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageError.h b/components/storage/src/mozStorageError.h
new file mode 100644
index 000000000..07963cf13
--- /dev/null
+++ b/components/storage/src/mozStorageError.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStorageError_h
+#define mozStorageError_h
+
+#include "mozIStorageError.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace storage {
+
+class Error final : public mozIStorageError
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEERROR
+
+ Error(int aResult, const char *aMessage);
+
+private:
+ ~Error() {}
+
+ int mResult;
+ nsCString mMessage;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStorageError_h
diff --git a/components/storage/src/mozStorageHelper.h b/components/storage/src/mozStorageHelper.h
new file mode 100644
index 000000000..1b4fde799
--- /dev/null
+++ b/components/storage/src/mozStorageHelper.h
@@ -0,0 +1,209 @@
+/* -*- 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/. */
+
+#ifndef MOZSTORAGEHELPER_H
+#define MOZSTORAGEHELPER_H
+
+#include "nsAutoPtr.h"
+#include "nsStringGlue.h"
+#include "mozilla/DebugOnly.h"
+
+#include "mozIStorageAsyncConnection.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageStatement.h"
+#include "mozIStoragePendingStatement.h"
+#include "nsError.h"
+
+/**
+ * This class wraps a transaction inside a given C++ scope, guaranteeing that
+ * the transaction will be completed even if you have an exception or
+ * return early.
+ *
+ * A common use is to create an instance with aCommitOnComplete = false (rollback),
+ * then call Commit() on this object manually when your function completes
+ * successfully.
+ *
+ * @note nested transactions are not supported by Sqlite, so if a transaction
+ * is already in progress, this object does nothing. Note that in this case,
+ * you may not get the transaction type you asked for, and you won't be able
+ * to rollback.
+ *
+ * @param aConnection
+ * The connection to create the transaction on.
+ * @param aCommitOnComplete
+ * Controls whether the transaction is committed or rolled back when
+ * this object goes out of scope.
+ * @param aType [optional]
+ * The transaction type, as defined in mozIStorageConnection. Defaults
+ * to TRANSACTION_DEFERRED.
+ * @param aAsyncCommit [optional]
+ * Whether commit should be executed asynchronously on the helper thread.
+ * This is a special option introduced as an interim solution to reduce
+ * main-thread fsyncs in Places. Can only be used on main-thread.
+ *
+ * WARNING: YOU SHOULD _NOT_ WRITE NEW MAIN-THREAD CODE USING THIS!
+ *
+ * Notice that async commit might cause synchronous statements to fail
+ * with SQLITE_BUSY. A possible mitigation strategy is to use
+ * PRAGMA busy_timeout, but notice that might cause main-thread jank.
+ * Finally, if the database is using WAL journaling mode, other
+ * connections won't see the changes done in async committed transactions
+ * until commit is complete.
+ *
+ * For all of the above reasons, this should only be used as an interim
+ * solution and avoided completely if possible.
+ */
+class mozStorageTransaction
+{
+public:
+ mozStorageTransaction(mozIStorageConnection* aConnection,
+ bool aCommitOnComplete,
+ int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED,
+ bool aAsyncCommit = false)
+ : mConnection(aConnection),
+ mHasTransaction(false),
+ mCommitOnComplete(aCommitOnComplete),
+ mCompleted(false),
+ mAsyncCommit(aAsyncCommit)
+ {
+ if (mConnection) {
+ nsAutoCString query("BEGIN");
+ switch(aType) {
+ case mozIStorageConnection::TRANSACTION_IMMEDIATE:
+ query.AppendLiteral(" IMMEDIATE");
+ break;
+ case mozIStorageConnection::TRANSACTION_EXCLUSIVE:
+ query.AppendLiteral(" EXCLUSIVE");
+ break;
+ case mozIStorageConnection::TRANSACTION_DEFERRED:
+ query.AppendLiteral(" DEFERRED");
+ break;
+ default:
+ MOZ_ASSERT(false, "Unknown transaction type");
+ }
+ // If a transaction is already in progress, this will fail, since Sqlite
+ // doesn't support nested transactions.
+ mHasTransaction = NS_SUCCEEDED(mConnection->ExecuteSimpleSQL(query));
+ }
+ }
+
+ ~mozStorageTransaction()
+ {
+ if (mConnection && mHasTransaction && !mCompleted) {
+ if (mCommitOnComplete) {
+ mozilla::DebugOnly<nsresult> rv = Commit();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "A transaction didn't commit correctly");
+ }
+ else {
+ mozilla::DebugOnly<nsresult> rv = Rollback();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "A transaction didn't rollback correctly");
+ }
+ }
+ }
+
+ /**
+ * Commits the transaction if one is in progress. If one is not in progress,
+ * this is a NOP since the actual owner of the transaction outside of our
+ * scope is in charge of finally committing or rolling back the transaction.
+ */
+ nsresult Commit()
+ {
+ if (!mConnection || mCompleted || !mHasTransaction)
+ return NS_OK;
+ mCompleted = true;
+
+ // TODO (bug 559659): this might fail with SQLITE_BUSY, but we don't handle
+ // it, thus the transaction might stay open until the next COMMIT.
+ nsresult rv;
+ if (mAsyncCommit) {
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ rv = mConnection->ExecuteSimpleSQLAsync(NS_LITERAL_CSTRING("COMMIT"),
+ nullptr, getter_AddRefs(ps));
+ }
+ else {
+ rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("COMMIT"));
+ }
+
+ if (NS_SUCCEEDED(rv))
+ mHasTransaction = false;
+
+ return rv;
+ }
+
+ /**
+ * Rolls back the transaction if one is in progress. If one is not in progress,
+ * this is a NOP since the actual owner of the transaction outside of our
+ * scope is in charge of finally rolling back the transaction.
+ */
+ nsresult Rollback()
+ {
+ if (!mConnection || mCompleted || !mHasTransaction)
+ return NS_OK;
+ mCompleted = true;
+
+ // TODO (bug 1062823): from Sqlite 3.7.11 on, rollback won't ever return
+ // a busy error, so this handling can be removed.
+ nsresult rv = NS_OK;
+ do {
+ rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK"));
+ if (rv == NS_ERROR_STORAGE_BUSY)
+ (void)PR_Sleep(PR_INTERVAL_NO_WAIT);
+ } while (rv == NS_ERROR_STORAGE_BUSY);
+
+ if (NS_SUCCEEDED(rv))
+ mHasTransaction = false;
+
+ return rv;
+ }
+
+protected:
+ nsCOMPtr<mozIStorageConnection> mConnection;
+ bool mHasTransaction;
+ bool mCommitOnComplete;
+ bool mCompleted;
+ bool mAsyncCommit;
+};
+
+/**
+ * This class wraps a statement so that it is guaraneed to be reset when
+ * this object goes out of scope.
+ *
+ * Note that this always just resets the statement. If the statement doesn't
+ * need resetting, the reset operation is inexpensive.
+ */
+class MOZ_STACK_CLASS mozStorageStatementScoper
+{
+public:
+ explicit mozStorageStatementScoper(mozIStorageStatement* aStatement)
+ : mStatement(aStatement)
+ {
+ }
+ ~mozStorageStatementScoper()
+ {
+ if (mStatement)
+ mStatement->Reset();
+ }
+
+ /**
+ * Call this to make the statement not reset. You might do this if you know
+ * that the statement has been reset.
+ */
+ void Abandon()
+ {
+ mStatement = nullptr;
+ }
+
+protected:
+ nsCOMPtr<mozIStorageStatement> mStatement;
+};
+
+// Use this to make queries uniquely identifiable in telemetry
+// statistics, especially PRAGMAs. We don't include __LINE__ so that
+// queries are stable in the face of source code changes.
+#define MOZ_STORAGE_UNIQUIFY_QUERY_STR "/* " __FILE__ " */ "
+
+#endif /* MOZSTORAGEHELPER_H */
diff --git a/components/storage/src/mozStorageModule.cpp b/components/storage/src/mozStorageModule.cpp
new file mode 100644
index 000000000..ba77e4c62
--- /dev/null
+++ b/components/storage/src/mozStorageModule.cpp
@@ -0,0 +1,53 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "mozilla/ModuleUtils.h"
+
+#include "mozStorageService.h"
+#include "mozStorageConnection.h"
+#include "VacuumManager.h"
+
+#include "mozStorageCID.h"
+
+namespace mozilla {
+namespace storage {
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(Service,
+ Service::getSingleton)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(VacuumManager,
+ VacuumManager::getSingleton)
+
+} // namespace storage
+} // namespace mozilla
+
+NS_DEFINE_NAMED_CID(MOZ_STORAGE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(VACUUMMANAGER_CID);
+
+static const mozilla::Module::CIDEntry kStorageCIDs[] = {
+ { &kMOZ_STORAGE_SERVICE_CID, false, nullptr, mozilla::storage::ServiceConstructor },
+ { &kVACUUMMANAGER_CID, false, nullptr, mozilla::storage::VacuumManagerConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kStorageContracts[] = {
+ { MOZ_STORAGE_SERVICE_CONTRACTID, &kMOZ_STORAGE_SERVICE_CID },
+ { VACUUMMANAGER_CONTRACTID, &kVACUUMMANAGER_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kStorageCategories[] = {
+ { "idle-daily", "MozStorage Vacuum Manager", VACUUMMANAGER_CONTRACTID },
+ { nullptr }
+};
+
+static const mozilla::Module kStorageModule = {
+ mozilla::Module::kVersion,
+ kStorageCIDs,
+ kStorageContracts,
+ kStorageCategories
+};
+
+NSMODULE_DEFN(mozStorageModule) = &kStorageModule;
diff --git a/components/storage/src/mozStoragePrivateHelpers.cpp b/components/storage/src/mozStoragePrivateHelpers.cpp
new file mode 100644
index 000000000..91924204f
--- /dev/null
+++ b/components/storage/src/mozStoragePrivateHelpers.cpp
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sqlite3.h"
+
+#include "jsfriendapi.h"
+
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsError.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
+#include "nsQueryObject.h"
+#include "nsThreadUtils.h"
+#include "nsJSUtils.h"
+
+#include "Variant.h"
+#include "mozStoragePrivateHelpers.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageCompletionCallback.h"
+#include "mozIStorageBindingParams.h"
+
+#include "mozilla/Logging.h"
+extern mozilla::LazyLogModule gStorageLog;
+
+namespace mozilla {
+namespace storage {
+
+nsresult
+convertResultCode(int aSQLiteResultCode)
+{
+ // Drop off the extended result bits of the result code.
+ int rc = aSQLiteResultCode & 0xFF;
+
+ switch (rc) {
+ case SQLITE_OK:
+ case SQLITE_ROW:
+ case SQLITE_DONE:
+ return NS_OK;
+ case SQLITE_CORRUPT:
+ case SQLITE_NOTADB:
+ return NS_ERROR_FILE_CORRUPTED;
+ case SQLITE_PERM:
+ case SQLITE_CANTOPEN:
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ case SQLITE_BUSY:
+ return NS_ERROR_STORAGE_BUSY;
+ case SQLITE_LOCKED:
+ return NS_ERROR_FILE_IS_LOCKED;
+ case SQLITE_READONLY:
+ return NS_ERROR_FILE_READ_ONLY;
+ case SQLITE_IOERR:
+ return NS_ERROR_STORAGE_IOERR;
+ case SQLITE_FULL:
+ case SQLITE_TOOBIG:
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ case SQLITE_NOMEM:
+ return NS_ERROR_OUT_OF_MEMORY;
+ case SQLITE_MISUSE:
+ return NS_ERROR_UNEXPECTED;
+ case SQLITE_ABORT:
+ case SQLITE_INTERRUPT:
+ return NS_ERROR_ABORT;
+ case SQLITE_CONSTRAINT:
+ return NS_ERROR_STORAGE_CONSTRAINT;
+ }
+
+ // generic error
+#ifdef DEBUG
+ nsAutoCString message;
+ message.AppendLiteral("SQLite returned error code ");
+ message.AppendInt(rc);
+ message.AppendLiteral(" , Storage will convert it to NS_ERROR_FAILURE");
+ NS_WARNING_ASSERTION(rc == SQLITE_ERROR, message.get());
+#endif
+ return NS_ERROR_FAILURE;
+}
+
+void
+checkAndLogStatementPerformance(sqlite3_stmt *aStatement)
+{
+ // Check to see if the query performed sorting operations or not. If it
+ // did, it may need to be optimized!
+ int count = ::sqlite3_stmt_status(aStatement, SQLITE_STMTSTATUS_SORT, 1);
+ if (count <= 0)
+ return;
+
+ const char *sql = ::sqlite3_sql(aStatement);
+
+ // Check to see if this is marked to not warn
+ if (::strstr(sql, "/* do not warn (bug "))
+ return;
+
+ // CREATE INDEX always sorts (sorting is a necessary step in creating
+ // an index). So ignore the warning there.
+ if (::strstr(sql, "CREATE INDEX") || ::strstr(sql, "CREATE UNIQUE INDEX"))
+ return;
+
+ nsAutoCString message("Suboptimal indexes for the SQL statement ");
+#ifdef MOZ_STORAGE_SORTWARNING_SQL_DUMP
+ message.Append('`');
+ message.Append(sql);
+ message.AppendLiteral("` [");
+ message.AppendInt(count);
+ message.AppendLiteral(" sort operation(s)]");
+#else
+ nsPrintfCString address("0x%p", aStatement);
+ message.Append(address);
+#endif
+ message.AppendLiteral(" (http://mzl.la/1FuID0j).");
+ NS_WARNING(message.get());
+}
+
+nsIVariant *
+convertJSValToVariant(
+ JSContext *aCtx,
+ const JS::Value& aValue)
+{
+ if (aValue.isInt32())
+ return new IntegerVariant(aValue.toInt32());
+
+ if (aValue.isDouble())
+ return new FloatVariant(aValue.toDouble());
+
+ if (aValue.isString()) {
+ nsAutoJSString value;
+ if (!value.init(aCtx, aValue.toString()))
+ return nullptr;
+ return new TextVariant(value);
+ }
+
+ if (aValue.isBoolean())
+ return new IntegerVariant(aValue.isTrue() ? 1 : 0);
+
+ if (aValue.isNull())
+ return new NullVariant();
+
+ if (aValue.isObject()) {
+ JS::Rooted<JSObject*> obj(aCtx, &aValue.toObject());
+ // We only support Date instances, all others fail.
+ bool valid;
+ if (!js::DateIsValid(aCtx, obj, &valid) || !valid)
+ return nullptr;
+
+ double msecd;
+ if (!js::DateGetMsecSinceEpoch(aCtx, obj, &msecd))
+ return nullptr;
+
+ msecd *= 1000.0;
+ int64_t msec = msecd;
+
+ return new IntegerVariant(msec);
+ }
+
+ return nullptr;
+}
+
+Variant_base *
+convertVariantToStorageVariant(nsIVariant* aVariant)
+{
+ RefPtr<Variant_base> variant = do_QueryObject(aVariant);
+ if (variant) {
+ // JS helpers already convert the JS representation to a Storage Variant,
+ // in such a case there's nothing left to do here, so just pass-through.
+ return variant;
+ }
+
+ if (!aVariant)
+ return new NullVariant();
+
+ uint16_t dataType;
+ nsresult rv = aVariant->GetDataType(&dataType);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ switch (dataType) {
+ case nsIDataType::VTYPE_BOOL:
+ case nsIDataType::VTYPE_INT8:
+ case nsIDataType::VTYPE_INT16:
+ case nsIDataType::VTYPE_INT32:
+ case nsIDataType::VTYPE_UINT8:
+ case nsIDataType::VTYPE_UINT16:
+ case nsIDataType::VTYPE_UINT32:
+ case nsIDataType::VTYPE_INT64:
+ case nsIDataType::VTYPE_UINT64: {
+ int64_t v;
+ rv = aVariant->GetAsInt64(&v);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return new IntegerVariant(v);
+ }
+ case nsIDataType::VTYPE_FLOAT:
+ case nsIDataType::VTYPE_DOUBLE: {
+ double v;
+ rv = aVariant->GetAsDouble(&v);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return new FloatVariant(v);
+ }
+ case nsIDataType::VTYPE_CHAR:
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ case nsIDataType::VTYPE_UTF8STRING:
+ case nsIDataType::VTYPE_CSTRING: {
+ nsCString v;
+ rv = aVariant->GetAsAUTF8String(v);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return new UTF8TextVariant(v);
+ }
+ case nsIDataType::VTYPE_WCHAR:
+ case nsIDataType::VTYPE_DOMSTRING:
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ case nsIDataType::VTYPE_ASTRING: {
+ nsString v;
+ rv = aVariant->GetAsAString(v);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return new TextVariant(v);
+ }
+ case nsIDataType::VTYPE_ARRAY: {
+ uint16_t type;
+ nsIID iid;
+ uint32_t len;
+ void *rawArray;
+ // Note this copies the array data.
+ rv = aVariant->GetAsArray(&type, &iid, &len, &rawArray);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ if (type == nsIDataType::VTYPE_UINT8) {
+ std::pair<uint8_t *, int> v(static_cast<uint8_t *>(rawArray), len);
+ // Take ownership of the data avoiding a further copy.
+ return new AdoptedBlobVariant(v);
+ }
+ MOZ_FALLTHROUGH;
+ }
+ case nsIDataType::VTYPE_EMPTY:
+ case nsIDataType::VTYPE_EMPTY_ARRAY:
+ case nsIDataType::VTYPE_VOID:
+ return new NullVariant();
+ case nsIDataType::VTYPE_ID:
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ default:
+ NS_WARNING("Unsupported variant type");
+ return nullptr;
+ }
+
+ return nullptr;
+}
+
+namespace {
+class CallbackEvent : public Runnable
+{
+public:
+ explicit CallbackEvent(mozIStorageCompletionCallback *aCallback)
+ : mCallback(aCallback)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ (void)mCallback->Complete(NS_OK, nullptr);
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<mozIStorageCompletionCallback> mCallback;
+};
+} // namespace
+already_AddRefed<nsIRunnable>
+newCompletionEvent(mozIStorageCompletionCallback *aCallback)
+{
+ NS_ASSERTION(aCallback, "Passing a null callback is a no-no!");
+ nsCOMPtr<nsIRunnable> event = new CallbackEvent(aCallback);
+ return event.forget();
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStoragePrivateHelpers.h b/components/storage/src/mozStoragePrivateHelpers.h
new file mode 100644
index 000000000..cfec6ff7f
--- /dev/null
+++ b/components/storage/src/mozStoragePrivateHelpers.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStoragePrivateHelpers_h
+#define mozStoragePrivateHelpers_h
+
+/**
+ * This file contains convenience methods for mozStorage.
+ */
+
+#include "sqlite3.h"
+#include "nsIVariant.h"
+#include "nsError.h"
+#include "nsAutoPtr.h"
+#include "js/TypeDecls.h"
+#include "Variant.h"
+
+class mozIStorageCompletionCallback;
+class nsIRunnable;
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Macros
+
+#define ENSURE_INDEX_VALUE(aIndex, aCount) \
+ NS_ENSURE_TRUE(aIndex < aCount, NS_ERROR_INVALID_ARG)
+
+////////////////////////////////////////////////////////////////////////////////
+//// Functions
+
+/**
+ * Converts a SQLite return code to an nsresult return code.
+ *
+ * @param aSQLiteResultCode
+ * The SQLite return code to convert.
+ * @returns the corresponding nsresult code for aSQLiteResultCode.
+ */
+nsresult convertResultCode(int aSQLiteResultCode);
+
+/**
+ * Checks the performance of a SQLite statement and logs a warning with
+ * NS_WARNING. Currently this only checks the number of sort operations done
+ * on a statement, and if more than zero have been done, the statement can be
+ * made faster with the careful use of an index.
+ *
+ * @param aStatement
+ * The sqlite3_stmt object to check.
+ */
+void checkAndLogStatementPerformance(sqlite3_stmt *aStatement);
+
+/**
+ * Convert the provided JS::Value into a variant representation if possible.
+ *
+ * @param aCtx
+ * The JSContext the value is from.
+ * @param aValue
+ * The JavaScript value to convert. All primitive types are supported,
+ * but only Date objects are supported from the Date family. Date
+ * objects are coerced to PRTime (nanoseconds since epoch) values.
+ * @return the variant if conversion was successful, nullptr if conversion
+ * failed. The caller is responsible for addref'ing if non-null.
+ */
+nsIVariant *convertJSValToVariant(JSContext *aCtx, const JS::Value& aValue);
+
+/**
+ * Convert a provided nsIVariant implementation to our own thread-safe
+ * refcounting implementation, if needed.
+ *
+ * @param aValue
+ * The original nsIVariant to be converted.
+ * @return a thread-safe refcounting nsIVariant implementation.
+ */
+Variant_base *convertVariantToStorageVariant(nsIVariant *aVariant);
+
+/**
+ * Obtains an event that will notify a completion callback about completion.
+ *
+ * @param aCallback
+ * The callback to be notified.
+ * @return an nsIRunnable that can be dispatched to the calling thread.
+ */
+already_AddRefed<nsIRunnable> newCompletionEvent(
+ mozIStorageCompletionCallback *aCallback
+);
+
+/**
+ * Utility method to get a Blob as a string value. The string expects
+ * the interface exposed by nsAString/nsACString/etc.
+ */
+template<class T, class V>
+nsresult
+DoGetBlobAsString(T* aThis, uint32_t aIndex, V& aValue)
+{
+ typedef typename V::char_type char_type;
+
+ uint32_t size;
+ char_type* blob;
+ nsresult rv =
+ aThis->GetBlob(aIndex, &size, reinterpret_cast<uint8_t**>(&blob));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aValue.Assign(blob, size / sizeof(char_type));
+ delete[] blob;
+ return NS_OK;
+}
+
+/**
+ * Utility method to bind a string value as a Blob. The string expects
+ * the interface exposed by nsAString/nsACString/etc.
+ */
+template<class T, class V>
+nsresult
+DoBindStringAsBlobByName(T* aThis, const nsACString& aName, const V& aValue)
+{
+ typedef typename V::char_type char_type;
+ return aThis->BindBlobByName(aName,
+ reinterpret_cast<const uint8_t*>(aValue.BeginReading()),
+ aValue.Length() * sizeof(char_type));
+}
+
+/**
+ * Utility method to bind a string value as a Blob. The string expects
+ * the interface exposed by nsAString/nsACString/etc.
+ */
+template<class T, class V>
+nsresult
+DoBindStringAsBlobByIndex(T* aThis, uint32_t aIndex, const V& aValue)
+{
+ typedef typename V::char_type char_type;
+ return aThis->BindBlobByIndex(aIndex,
+ reinterpret_cast<const uint8_t*>(aValue.BeginReading()),
+ aValue.Length() * sizeof(char_type));
+}
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStoragePrivateHelpers_h
diff --git a/components/storage/src/mozStorageResultSet.cpp b/components/storage/src/mozStorageResultSet.cpp
new file mode 100644
index 000000000..e4f3d1f35
--- /dev/null
+++ b/components/storage/src/mozStorageResultSet.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozStorageRow.h"
+#include "mozStorageResultSet.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// ResultSet
+
+ResultSet::ResultSet()
+: mCurrentIndex(0)
+{
+}
+
+ResultSet::~ResultSet()
+{
+ mData.Clear();
+}
+
+nsresult
+ResultSet::add(mozIStorageRow *aRow)
+{
+ return mData.AppendObject(aRow) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+/**
+ * Note: This object is only ever accessed on one thread at a time. It it not
+ * threadsafe, but it does need threadsafe AddRef and Release.
+ */
+NS_IMPL_ISUPPORTS(
+ ResultSet,
+ mozIStorageResultSet
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageResultSet
+
+NS_IMETHODIMP
+ResultSet::GetNextRow(mozIStorageRow **_row)
+{
+ NS_ENSURE_ARG_POINTER(_row);
+
+ if (mCurrentIndex >= mData.Count()) {
+ // Just return null here
+ return NS_OK;
+ }
+
+ NS_ADDREF(*_row = mData.ObjectAt(mCurrentIndex++));
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageResultSet.h b/components/storage/src/mozStorageResultSet.h
new file mode 100644
index 000000000..07a861c52
--- /dev/null
+++ b/components/storage/src/mozStorageResultSet.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStorageResultSet_h
+#define mozStorageResultSet_h
+
+#include "mozIStorageResultSet.h"
+#include "nsCOMArray.h"
+#include "mozilla/Attributes.h"
+class mozIStorageRow;
+
+namespace mozilla {
+namespace storage {
+
+class ResultSet final : public mozIStorageResultSet
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGERESULTSET
+
+ ResultSet();
+
+ /**
+ * Adds a tuple to this result set.
+ */
+ nsresult add(mozIStorageRow *aTuple);
+
+ /**
+ * @returns the number of rows this result set holds.
+ */
+ int32_t rows() const { return mData.Count(); }
+
+private:
+ ~ResultSet();
+
+ /**
+ * Stores the current index of the active result set.
+ */
+ int32_t mCurrentIndex;
+
+ /**
+ * Stores the tuples.
+ */
+ nsCOMArray<mozIStorageRow> mData;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStorageResultSet_h
diff --git a/components/storage/src/mozStorageRow.cpp b/components/storage/src/mozStorageRow.cpp
new file mode 100644
index 000000000..7bcac4c30
--- /dev/null
+++ b/components/storage/src/mozStorageRow.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+
+#include "sqlite3.h"
+#include "mozStoragePrivateHelpers.h"
+#include "Variant.h"
+#include "mozStorageRow.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Row
+
+nsresult
+Row::initialize(sqlite3_stmt *aStatement)
+{
+ // Get the number of results
+ mNumCols = ::sqlite3_column_count(aStatement);
+
+ // Start copying over values
+ for (uint32_t i = 0; i < mNumCols; i++) {
+ // Store the value
+ nsIVariant *variant = nullptr;
+ int type = ::sqlite3_column_type(aStatement, i);
+ switch (type) {
+ case SQLITE_INTEGER:
+ variant = new IntegerVariant(::sqlite3_column_int64(aStatement, i));
+ break;
+ case SQLITE_FLOAT:
+ variant = new FloatVariant(::sqlite3_column_double(aStatement, i));
+ break;
+ case SQLITE_TEXT:
+ {
+ nsDependentString str(
+ static_cast<const char16_t *>(::sqlite3_column_text16(aStatement, i))
+ );
+ variant = new TextVariant(str);
+ break;
+ }
+ case SQLITE_NULL:
+ variant = new NullVariant();
+ break;
+ case SQLITE_BLOB:
+ {
+ int size = ::sqlite3_column_bytes(aStatement, i);
+ const void *data = ::sqlite3_column_blob(aStatement, i);
+ variant = new BlobVariant(std::pair<const void *, int>(data, size));
+ break;
+ }
+ default:
+ return NS_ERROR_UNEXPECTED;
+ }
+ NS_ENSURE_TRUE(variant, NS_ERROR_OUT_OF_MEMORY);
+
+ // Insert into our storage array
+ NS_ENSURE_TRUE(mData.InsertObjectAt(variant, i), NS_ERROR_OUT_OF_MEMORY);
+
+ // Associate the name (if any) with the index
+ const char *name = ::sqlite3_column_name(aStatement, i);
+ if (!name) break;
+ nsAutoCString colName(name);
+ mNameHashtable.Put(colName, i);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Note: This object is only ever accessed on one thread at a time. It it not
+ * threadsafe, but it does need threadsafe AddRef and Release.
+ */
+NS_IMPL_ISUPPORTS(
+ Row,
+ mozIStorageRow,
+ mozIStorageValueArray
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageRow
+
+NS_IMETHODIMP
+Row::GetResultByIndex(uint32_t aIndex,
+ nsIVariant **_result)
+{
+ ENSURE_INDEX_VALUE(aIndex, mNumCols);
+ NS_ADDREF(*_result = mData.ObjectAt(aIndex));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Row::GetResultByName(const nsACString &aName,
+ nsIVariant **_result)
+{
+ uint32_t index;
+ NS_ENSURE_TRUE(mNameHashtable.Get(aName, &index), NS_ERROR_NOT_AVAILABLE);
+ return GetResultByIndex(index, _result);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageValueArray
+
+NS_IMETHODIMP
+Row::GetNumEntries(uint32_t *_entries)
+{
+ *_entries = mNumCols;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Row::GetTypeOfIndex(uint32_t aIndex,
+ int32_t *_type)
+{
+ ENSURE_INDEX_VALUE(aIndex, mNumCols);
+
+ uint16_t type;
+ (void)mData.ObjectAt(aIndex)->GetDataType(&type);
+ switch (type) {
+ case nsIDataType::VTYPE_INT32:
+ case nsIDataType::VTYPE_INT64:
+ *_type = mozIStorageValueArray::VALUE_TYPE_INTEGER;
+ break;
+ case nsIDataType::VTYPE_DOUBLE:
+ *_type = mozIStorageValueArray::VALUE_TYPE_FLOAT;
+ break;
+ case nsIDataType::VTYPE_ASTRING:
+ *_type = mozIStorageValueArray::VALUE_TYPE_TEXT;
+ break;
+ case nsIDataType::VTYPE_ARRAY:
+ *_type = mozIStorageValueArray::VALUE_TYPE_BLOB;
+ break;
+ default:
+ *_type = mozIStorageValueArray::VALUE_TYPE_NULL;
+ break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Row::GetInt32(uint32_t aIndex,
+ int32_t *_value)
+{
+ ENSURE_INDEX_VALUE(aIndex, mNumCols);
+ return mData.ObjectAt(aIndex)->GetAsInt32(_value);
+}
+
+NS_IMETHODIMP
+Row::GetInt64(uint32_t aIndex,
+ int64_t *_value)
+{
+ ENSURE_INDEX_VALUE(aIndex, mNumCols);
+ return mData.ObjectAt(aIndex)->GetAsInt64(_value);
+}
+
+NS_IMETHODIMP
+Row::GetDouble(uint32_t aIndex,
+ double *_value)
+{
+ ENSURE_INDEX_VALUE(aIndex, mNumCols);
+ return mData.ObjectAt(aIndex)->GetAsDouble(_value);
+}
+
+NS_IMETHODIMP
+Row::GetUTF8String(uint32_t aIndex,
+ nsACString &_value)
+{
+ ENSURE_INDEX_VALUE(aIndex, mNumCols);
+ return mData.ObjectAt(aIndex)->GetAsAUTF8String(_value);
+}
+
+NS_IMETHODIMP
+Row::GetString(uint32_t aIndex,
+ nsAString &_value)
+{
+ ENSURE_INDEX_VALUE(aIndex, mNumCols);
+ return mData.ObjectAt(aIndex)->GetAsAString(_value);
+}
+
+NS_IMETHODIMP
+Row::GetBlob(uint32_t aIndex,
+ uint32_t *_size,
+ uint8_t **_blob)
+{
+ ENSURE_INDEX_VALUE(aIndex, mNumCols);
+
+ uint16_t type;
+ nsIID interfaceIID;
+ return mData.ObjectAt(aIndex)->GetAsArray(&type, &interfaceIID, _size,
+ reinterpret_cast<void **>(_blob));
+}
+
+NS_IMETHODIMP
+Row::GetBlobAsString(uint32_t aIndex, nsAString& aValue)
+{
+ return DoGetBlobAsString(this, aIndex, aValue);
+}
+
+NS_IMETHODIMP
+Row::GetBlobAsUTF8String(uint32_t aIndex, nsACString& aValue)
+{
+ return DoGetBlobAsString(this, aIndex, aValue);
+}
+
+NS_IMETHODIMP
+Row::GetIsNull(uint32_t aIndex,
+ bool *_isNull)
+{
+ ENSURE_INDEX_VALUE(aIndex, mNumCols);
+ NS_ENSURE_ARG_POINTER(_isNull);
+
+ uint16_t type;
+ (void)mData.ObjectAt(aIndex)->GetDataType(&type);
+ *_isNull = type == nsIDataType::VTYPE_EMPTY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Row::GetSharedUTF8String(uint32_t,
+ uint32_t *,
+ char const **)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Row::GetSharedString(uint32_t,
+ uint32_t *,
+ const char16_t **)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+Row::GetSharedBlob(uint32_t,
+ uint32_t *,
+ const uint8_t **)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageRow.h b/components/storage/src/mozStorageRow.h
new file mode 100644
index 000000000..9145c40a5
--- /dev/null
+++ b/components/storage/src/mozStorageRow.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStorageRow_h
+#define mozStorageRow_h
+
+#include "mozIStorageRow.h"
+#include "nsCOMArray.h"
+#include "nsDataHashtable.h"
+#include "mozilla/Attributes.h"
+class nsIVariant;
+struct sqlite3_stmt;
+
+namespace mozilla {
+namespace storage {
+
+class Row final : public mozIStorageRow
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEROW
+ NS_DECL_MOZISTORAGEVALUEARRAY
+
+ Row() : mNumCols(0) {}
+
+ /**
+ * Initializes the object with the given statement. Copies the values from
+ * the statement.
+ *
+ * @param aStatement
+ * The sqlite statement to pull results from.
+ */
+ nsresult initialize(sqlite3_stmt *aStatement);
+
+private:
+ ~Row() {}
+
+ /**
+ * The number of columns in this tuple.
+ */
+ uint32_t mNumCols;
+
+ /**
+ * Stores the data in the tuple.
+ */
+ nsCOMArray<nsIVariant> mData;
+
+ /**
+ * Maps a given name to a column index.
+ */
+ nsDataHashtable<nsCStringHashKey, uint32_t> mNameHashtable;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStorageRow_h
diff --git a/components/storage/src/mozStorageSQLFunctions.cpp b/components/storage/src/mozStorageSQLFunctions.cpp
new file mode 100644
index 000000000..8b3148e21
--- /dev/null
+++ b/components/storage/src/mozStorageSQLFunctions.cpp
@@ -0,0 +1,406 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "nsTArray.h"
+#include "mozStorageSQLFunctions.h"
+#include "nsUnicharUtils.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Local Helper Functions
+
+namespace {
+
+/**
+ * Performs the LIKE comparison of a string against a pattern. For more detail
+ * see http://www.sqlite.org/lang_expr.html#like.
+ *
+ * @param aPatternItr
+ * An iterator at the start of the pattern to check for.
+ * @param aPatternEnd
+ * An iterator at the end of the pattern to check for.
+ * @param aStringItr
+ * An iterator at the start of the string to check for the pattern.
+ * @param aStringEnd
+ * An iterator at the end of the string to check for the pattern.
+ * @param aEscapeChar
+ * The character to use for escaping symbols in the pattern.
+ * @return 1 if the pattern is found, 0 otherwise.
+ */
+int
+likeCompare(nsAString::const_iterator aPatternItr,
+ nsAString::const_iterator aPatternEnd,
+ nsAString::const_iterator aStringItr,
+ nsAString::const_iterator aStringEnd,
+ char16_t aEscapeChar)
+{
+ const char16_t MATCH_ALL('%');
+ const char16_t MATCH_ONE('_');
+
+ bool lastWasEscape = false;
+ while (aPatternItr != aPatternEnd) {
+ /**
+ * What we do in here is take a look at each character from the input
+ * pattern, and do something with it. There are 4 possibilities:
+ * 1) character is an un-escaped match-all character
+ * 2) character is an un-escaped match-one character
+ * 3) character is an un-escaped escape character
+ * 4) character is not any of the above
+ */
+ if (!lastWasEscape && *aPatternItr == MATCH_ALL) {
+ // CASE 1
+ /**
+ * Now we need to skip any MATCH_ALL or MATCH_ONE characters that follow a
+ * MATCH_ALL character. For each MATCH_ONE character, skip one character
+ * in the pattern string.
+ */
+ while (*aPatternItr == MATCH_ALL || *aPatternItr == MATCH_ONE) {
+ if (*aPatternItr == MATCH_ONE) {
+ // If we've hit the end of the string we are testing, no match
+ if (aStringItr == aStringEnd)
+ return 0;
+ aStringItr++;
+ }
+ aPatternItr++;
+ }
+
+ // If we've hit the end of the pattern string, match
+ if (aPatternItr == aPatternEnd)
+ return 1;
+
+ while (aStringItr != aStringEnd) {
+ if (likeCompare(aPatternItr, aPatternEnd, aStringItr, aStringEnd,
+ aEscapeChar)) {
+ // we've hit a match, so indicate this
+ return 1;
+ }
+ aStringItr++;
+ }
+
+ // No match
+ return 0;
+ }
+ else if (!lastWasEscape && *aPatternItr == MATCH_ONE) {
+ // CASE 2
+ if (aStringItr == aStringEnd) {
+ // If we've hit the end of the string we are testing, no match
+ return 0;
+ }
+ aStringItr++;
+ lastWasEscape = false;
+ }
+ else if (!lastWasEscape && *aPatternItr == aEscapeChar) {
+ // CASE 3
+ lastWasEscape = true;
+ }
+ else {
+ // CASE 4
+ if (::ToUpperCase(*aStringItr) != ::ToUpperCase(*aPatternItr)) {
+ // If we've hit a point where the strings don't match, there is no match
+ return 0;
+ }
+ aStringItr++;
+ lastWasEscape = false;
+ }
+
+ aPatternItr++;
+ }
+
+ return aStringItr == aStringEnd;
+}
+
+/**
+ * Compute the Levenshtein Edit Distance between two strings.
+ *
+ * @param aStringS
+ * a string
+ * @param aStringT
+ * another string
+ * @param _result
+ * an outparam that will receive the edit distance between the arguments
+ * @return a Sqlite result code, e.g. SQLITE_OK, SQLITE_NOMEM, etc.
+ */
+int
+levenshteinDistance(const nsAString &aStringS,
+ const nsAString &aStringT,
+ int *_result)
+{
+ // Set the result to a non-sensical value in case we encounter an error.
+ *_result = -1;
+
+ const uint32_t sLen = aStringS.Length();
+ const uint32_t tLen = aStringT.Length();
+
+ if (sLen == 0) {
+ *_result = tLen;
+ return SQLITE_OK;
+ }
+ if (tLen == 0) {
+ *_result = sLen;
+ return SQLITE_OK;
+ }
+
+ // Notionally, Levenshtein Distance is computed in a matrix. If we
+ // assume s = "span" and t = "spam", the matrix would look like this:
+ // s -->
+ // t s p a n
+ // | 0 1 2 3 4
+ // V s 1 * * * *
+ // p 2 * * * *
+ // a 3 * * * *
+ // m 4 * * * *
+ //
+ // Note that the row width is sLen + 1 and the column height is tLen + 1,
+ // where sLen is the length of the string "s" and tLen is the length of "t".
+ // The first row and the first column are initialized as shown, and
+ // the algorithm computes the remaining cells row-by-row, and
+ // left-to-right within each row. The computation only requires that
+ // we be able to see the current row and the previous one.
+
+ // Allocate memory for two rows.
+ AutoTArray<int, nsAutoString::kDefaultStorageSize> row1;
+ AutoTArray<int, nsAutoString::kDefaultStorageSize> row2;
+
+ // Declare the raw pointers that will actually be used to access the memory.
+ int *prevRow = row1.AppendElements(sLen + 1);
+ int *currRow = row2.AppendElements(sLen + 1);
+
+ // Initialize the first row.
+ for (uint32_t i = 0; i <= sLen; i++)
+ prevRow[i] = i;
+
+ const char16_t *s = aStringS.BeginReading();
+ const char16_t *t = aStringT.BeginReading();
+
+ // Compute the empty cells in the "matrix" row-by-row, starting with
+ // the second row.
+ for (uint32_t ti = 1; ti <= tLen; ti++) {
+
+ // Initialize the first cell in this row.
+ currRow[0] = ti;
+
+ // Get the character from "t" that corresponds to this row.
+ const char16_t tch = t[ti - 1];
+
+ // Compute the remaining cells in this row, left-to-right,
+ // starting at the second column (and first character of "s").
+ for (uint32_t si = 1; si <= sLen; si++) {
+
+ // Get the character from "s" that corresponds to this column,
+ // compare it to the t-character, and compute the "cost".
+ const char16_t sch = s[si - 1];
+ int cost = (sch == tch) ? 0 : 1;
+
+ // ............ We want to calculate the value of cell "d" from
+ // ...ab....... the previously calculated (or initialized) cells
+ // ...cd....... "a", "b", and "c", where d = min(a', b', c').
+ // ............
+ int aPrime = prevRow[si - 1] + cost;
+ int bPrime = prevRow[si] + 1;
+ int cPrime = currRow[si - 1] + 1;
+ currRow[si] = std::min(aPrime, std::min(bPrime, cPrime));
+ }
+
+ // Advance to the next row. The current row becomes the previous
+ // row and we recycle the old previous row as the new current row.
+ // We don't need to re-initialize the new current row since we will
+ // rewrite all of its cells anyway.
+ int *oldPrevRow = prevRow;
+ prevRow = currRow;
+ currRow = oldPrevRow;
+ }
+
+ // The final result is the value of the last cell in the last row.
+ // Note that that's now in the "previous" row, since we just swapped them.
+ *_result = prevRow[sLen];
+ return SQLITE_OK;
+}
+
+// This struct is used only by registerFunctions below, but ISO C++98 forbids
+// instantiating a template dependent on a locally-defined type. Boo-urns!
+struct Functions {
+ const char *zName;
+ int nArg;
+ int enc;
+ void *pContext;
+ void (*xFunc)(::sqlite3_context*, int, sqlite3_value**);
+};
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+//// Exposed Functions
+
+int
+registerFunctions(sqlite3 *aDB)
+{
+ Functions functions[] = {
+ {"lower",
+ 1,
+ SQLITE_UTF16,
+ 0,
+ caseFunction},
+ {"lower",
+ 1,
+ SQLITE_UTF8,
+ 0,
+ caseFunction},
+ {"upper",
+ 1,
+ SQLITE_UTF16,
+ (void*)1,
+ caseFunction},
+ {"upper",
+ 1,
+ SQLITE_UTF8,
+ (void*)1,
+ caseFunction},
+
+ {"like",
+ 2,
+ SQLITE_UTF16,
+ 0,
+ likeFunction},
+ {"like",
+ 2,
+ SQLITE_UTF8,
+ 0,
+ likeFunction},
+ {"like",
+ 3,
+ SQLITE_UTF16,
+ 0,
+ likeFunction},
+ {"like",
+ 3,
+ SQLITE_UTF8,
+ 0,
+ likeFunction},
+
+ {"levenshteinDistance",
+ 2,
+ SQLITE_UTF16,
+ 0,
+ levenshteinDistanceFunction},
+ {"levenshteinDistance",
+ 2,
+ SQLITE_UTF8,
+ 0,
+ levenshteinDistanceFunction},
+ };
+
+ int rv = SQLITE_OK;
+ for (size_t i = 0; SQLITE_OK == rv && i < ArrayLength(functions); ++i) {
+ struct Functions *p = &functions[i];
+ rv = ::sqlite3_create_function(aDB, p->zName, p->nArg, p->enc, p->pContext,
+ p->xFunc, nullptr, nullptr);
+ }
+
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// SQL Functions
+
+void
+caseFunction(sqlite3_context *aCtx,
+ int aArgc,
+ sqlite3_value **aArgv)
+{
+ NS_ASSERTION(1 == aArgc, "Invalid number of arguments!");
+
+ nsAutoString data(static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[0])));
+ bool toUpper = ::sqlite3_user_data(aCtx) ? true : false;
+
+ if (toUpper)
+ ::ToUpperCase(data);
+ else
+ ::ToLowerCase(data);
+
+ // Set the result.
+ ::sqlite3_result_text16(aCtx, data.get(), -1, SQLITE_TRANSIENT);
+}
+
+/**
+ * This implements the like() SQL function. This is used by the LIKE operator.
+ * The SQL statement 'A LIKE B' is implemented as 'like(B, A)', and if there is
+ * an escape character, say E, it is implemented as 'like(B, A, E)'.
+ */
+void
+likeFunction(sqlite3_context *aCtx,
+ int aArgc,
+ sqlite3_value **aArgv)
+{
+ NS_ASSERTION(2 == aArgc || 3 == aArgc, "Invalid number of arguments!");
+
+ if (::sqlite3_value_bytes(aArgv[0]) > SQLITE_MAX_LIKE_PATTERN_LENGTH) {
+ ::sqlite3_result_error(aCtx, "LIKE or GLOB pattern too complex",
+ SQLITE_TOOBIG);
+ return;
+ }
+
+ if (!::sqlite3_value_text16(aArgv[0]) || !::sqlite3_value_text16(aArgv[1]))
+ return;
+
+ nsDependentString A(static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[1])));
+ nsDependentString B(static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[0])));
+ NS_ASSERTION(!B.IsEmpty(), "LIKE string must not be null!");
+
+ char16_t E = 0;
+ if (3 == aArgc)
+ E = static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[2]))[0];
+
+ nsAString::const_iterator itrString, endString;
+ A.BeginReading(itrString);
+ A.EndReading(endString);
+ nsAString::const_iterator itrPattern, endPattern;
+ B.BeginReading(itrPattern);
+ B.EndReading(endPattern);
+ ::sqlite3_result_int(aCtx, likeCompare(itrPattern, endPattern, itrString,
+ endString, E));
+}
+
+void levenshteinDistanceFunction(sqlite3_context *aCtx,
+ int aArgc,
+ sqlite3_value **aArgv)
+{
+ NS_ASSERTION(2 == aArgc, "Invalid number of arguments!");
+
+ // If either argument is a SQL NULL, then return SQL NULL.
+ if (::sqlite3_value_type(aArgv[0]) == SQLITE_NULL ||
+ ::sqlite3_value_type(aArgv[1]) == SQLITE_NULL) {
+ ::sqlite3_result_null(aCtx);
+ return;
+ }
+
+ int aLen = ::sqlite3_value_bytes16(aArgv[0]) / sizeof(char16_t);
+ const char16_t *a = static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[0]));
+
+ int bLen = ::sqlite3_value_bytes16(aArgv[1]) / sizeof(char16_t);
+ const char16_t *b = static_cast<const char16_t *>(::sqlite3_value_text16(aArgv[1]));
+
+ // Compute the Levenshtein Distance, and return the result (or error).
+ int distance = -1;
+ const nsDependentString A(a, aLen);
+ const nsDependentString B(b, bLen);
+ int status = levenshteinDistance(A, B, &distance);
+ if (status == SQLITE_OK) {
+ ::sqlite3_result_int(aCtx, distance);
+ }
+ else if (status == SQLITE_NOMEM) {
+ ::sqlite3_result_error_nomem(aCtx);
+ }
+ else {
+ ::sqlite3_result_error(aCtx, "User function returned error code", -1);
+ }
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageSQLFunctions.h b/components/storage/src/mozStorageSQLFunctions.h
new file mode 100644
index 000000000..556a4a7c1
--- /dev/null
+++ b/components/storage/src/mozStorageSQLFunctions.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStorageSQLFunctions_h
+#define mozStorageSQLFunctions_h
+
+#include "sqlite3.h"
+#include "nscore.h"
+
+namespace mozilla {
+namespace storage {
+
+/**
+ * Registers the functions declared here with the specified database.
+ *
+ * @param aDB
+ * The database we'll be registering the functions with.
+ * @return the SQLite status code indicating success or failure.
+ */
+int registerFunctions(sqlite3 *aDB);
+
+////////////////////////////////////////////////////////////////////////////////
+//// Predefined Functions
+
+/**
+ * Overridden function to perform the SQL functions UPPER and LOWER. These
+ * support unicode, which the default implementations do not do.
+ *
+ * @param aCtx
+ * The sqlite_context that this function is being called on.
+ * @param aArgc
+ * The number of arguments the function is being called with.
+ * @param aArgv
+ * An array of the arguments the functions is being called with.
+ */
+void caseFunction(sqlite3_context *aCtx,
+ int aArgc,
+ sqlite3_value **aArgv);
+
+/**
+ * Overridden function to perform the SQL function LIKE. This supports unicode,
+ * which the default implementation does not do.
+ *
+ * @param aCtx
+ * The sqlite_context that this function is being called on.
+ * @param aArgc
+ * The number of arguments the function is being called with.
+ * @param aArgv
+ * An array of the arguments the functions is being called with.
+ */
+void likeFunction(sqlite3_context *aCtx,
+ int aArgc,
+ sqlite3_value **aArgv);
+
+/**
+ * An implementation of the Levenshtein Edit Distance algorithm for use in
+ * Sqlite queries.
+ *
+ * @param aCtx
+ * The sqlite_context that this function is being called on.
+ * @param aArgc
+ * The number of arguments the function is being called with.
+ * @param aArgv
+ * An array of the arguments the functions is being called with.
+ */
+void levenshteinDistanceFunction(sqlite3_context *aCtx,
+ int aArgc,
+ sqlite3_value **aArgv);
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStorageSQLFunctions_h
diff --git a/components/storage/src/mozStorageService.cpp b/components/storage/src/mozStorageService.cpp
new file mode 100644
index 000000000..8c6f65232
--- /dev/null
+++ b/components/storage/src/mozStorageService.cpp
@@ -0,0 +1,930 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+
+#include "mozStorageService.h"
+#include "mozStorageConnection.h"
+#include "nsAutoPtr.h"
+#include "nsCollationCID.h"
+#include "nsEmbedCID.h"
+#include "nsThreadUtils.h"
+#include "mozStoragePrivateHelpers.h"
+#include "nsILocale.h"
+#include "nsILocaleService.h"
+#include "nsIXPConnect.h"
+#include "nsIObserverService.h"
+#include "nsIPropertyBag2.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/LateWriteChecks.h"
+#include "mozIStorageCompletionCallback.h"
+#include "mozIStoragePendingStatement.h"
+
+#include "sqlite3.h"
+
+#ifdef SQLITE_OS_WIN
+// "windows.h" was included and it can #define lots of things we care about...
+#undef CompareString
+#endif
+
+#include "nsIPromptService.h"
+
+#ifdef MOZ_STORAGE_MEMORY
+# include "mozmemory.h"
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+//// Defines
+
+#define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
+#define PREF_TS_SYNCHRONOUS_DEFAULT 1
+
+#define PREF_TS_PAGESIZE "toolkit.storage.pageSize"
+
+// This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in
+// db/sqlite3/src/Makefile.in.
+#define PREF_TS_PAGESIZE_DEFAULT 32768
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Memory Reporting
+
+static int64_t
+StorageSQLiteDistinguishedAmount()
+{
+ return ::sqlite3_memory_used();
+}
+
+/**
+ * Passes a single SQLite memory statistic to a memory reporter callback.
+ *
+ * @param aHandleReport
+ * The callback.
+ * @param aData
+ * The data for the callback.
+ * @param aConn
+ * The SQLite connection.
+ * @param aPathHead
+ * Head of the path for the memory report.
+ * @param aKind
+ * The memory report statistic kind, one of "stmt", "cache" or
+ * "schema".
+ * @param aDesc
+ * The memory report description.
+ * @param aOption
+ * The SQLite constant for getting the measurement.
+ * @param aTotal
+ * The accumulator for the measurement.
+ */
+static void
+ReportConn(nsIHandleReportCallback *aHandleReport,
+ nsISupports *aData,
+ Connection *aConn,
+ const nsACString &aPathHead,
+ const nsACString &aKind,
+ const nsACString &aDesc,
+ int32_t aOption,
+ size_t *aTotal)
+{
+ nsCString path(aPathHead);
+ path.Append(aKind);
+ path.AppendLiteral("-used");
+
+ int32_t val = aConn->getSqliteRuntimeStatus(aOption);
+ aHandleReport->Callback(EmptyCString(), path,
+ nsIMemoryReporter::KIND_HEAP,
+ nsIMemoryReporter::UNITS_BYTES,
+ int64_t(val), aDesc, aData);
+ *aTotal += val;
+}
+
+// Warning: To get a Connection's measurements requires holding its lock.
+// There may be a delay getting the lock if another thread is accessing the
+// Connection. This isn't very nice if CollectReports is called from the main
+// thread! But at the time of writing this function is only called when
+// about:memory is loaded (not, for example, when telemetry pings occur) and
+// any delays in that case aren't so bad.
+NS_IMETHODIMP
+Service::CollectReports(nsIHandleReportCallback *aHandleReport,
+ nsISupports *aData, bool aAnonymize)
+{
+ size_t totalConnSize = 0;
+ {
+ nsTArray<RefPtr<Connection> > connections;
+ getConnections(connections);
+
+ for (uint32_t i = 0; i < connections.Length(); i++) {
+ RefPtr<Connection> &conn = connections[i];
+
+ // Someone may have closed the Connection, in which case we skip it.
+ bool isReady;
+ (void)conn->GetConnectionReady(&isReady);
+ if (!isReady) {
+ continue;
+ }
+
+ nsCString pathHead("explicit/storage/sqlite/");
+ // This filename isn't privacy-sensitive, and so is never anonymized.
+ pathHead.Append(conn->getFilename());
+ pathHead.Append('/');
+
+ SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
+
+ NS_NAMED_LITERAL_CSTRING(stmtDesc,
+ "Memory (approximate) used by all prepared statements used by "
+ "connections to this database.");
+ ReportConn(aHandleReport, aData, conn, pathHead,
+ NS_LITERAL_CSTRING("stmt"), stmtDesc,
+ SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
+
+ NS_NAMED_LITERAL_CSTRING(cacheDesc,
+ "Memory (approximate) used by all pager caches used by connections "
+ "to this database.");
+ ReportConn(aHandleReport, aData, conn, pathHead,
+ NS_LITERAL_CSTRING("cache"), cacheDesc,
+ SQLITE_DBSTATUS_CACHE_USED_SHARED, &totalConnSize);
+
+ NS_NAMED_LITERAL_CSTRING(schemaDesc,
+ "Memory (approximate) used to store the schema for all databases "
+ "associated with connections to this database.");
+ ReportConn(aHandleReport, aData, conn, pathHead,
+ NS_LITERAL_CSTRING("schema"), schemaDesc,
+ SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
+ }
+ }
+
+ int64_t other = ::sqlite3_memory_used() - totalConnSize;
+
+ MOZ_COLLECT_REPORT(
+ "explicit/storage/sqlite/other", KIND_HEAP, UNITS_BYTES, other,
+ "All unclassified sqlite memory.");
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Service
+
+NS_IMPL_ISUPPORTS(
+ Service,
+ mozIStorageService,
+ nsIObserver,
+ nsIMemoryReporter
+)
+
+Service *Service::gService = nullptr;
+
+Service *
+Service::getSingleton()
+{
+ if (gService) {
+ NS_ADDREF(gService);
+ return gService;
+ }
+
+ // Ensure that we are using the same version of SQLite that we compiled with
+ // or newer. Our configure check ensures we are using a new enough version
+ // at compile time.
+ if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) {
+ nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
+ if (ps) {
+ nsAutoString title, message;
+ title.AppendLiteral("SQLite Version Error");
+ message.AppendLiteral("The application has been updated, but the SQLite "
+ "library wasn't updated properly and the application "
+ "cannot run. Please try to launch the application again. "
+ "If that should still fail, please try reinstalling "
+ "it, or visit https://support.mozilla.org/.");
+ (void)ps->Alert(nullptr, title.get(), message.get());
+ }
+ MOZ_CRASH("SQLite Version Error");
+ }
+
+ // The first reference to the storage service must be obtained on the
+ // main thread.
+ NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
+ gService = new Service();
+ if (gService) {
+ NS_ADDREF(gService);
+ if (NS_FAILED(gService->initialize()))
+ NS_RELEASE(gService);
+ }
+
+ return gService;
+}
+
+nsIXPConnect *Service::sXPConnect = nullptr;
+
+// static
+already_AddRefed<nsIXPConnect>
+Service::getXPConnect()
+{
+ NS_PRECONDITION(NS_IsMainThread(),
+ "Must only get XPConnect on the main thread!");
+ NS_PRECONDITION(gService,
+ "Can not get XPConnect without an instance of our service!");
+
+ // If we've been shutdown, sXPConnect will be null. To prevent leaks, we do
+ // not cache the service after this point.
+ nsCOMPtr<nsIXPConnect> xpc(sXPConnect);
+ if (!xpc)
+ xpc = do_GetService(nsIXPConnect::GetCID());
+ NS_ASSERTION(xpc, "Could not get XPConnect!");
+ return xpc.forget();
+}
+
+int32_t Service::sSynchronousPref;
+
+// static
+int32_t
+Service::getSynchronousPref()
+{
+ return sSynchronousPref;
+}
+
+int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT;
+
+Service::Service()
+: mMutex("Service::mMutex")
+, mSqliteVFS(nullptr)
+, mRegistrationMutex("Service::mRegistrationMutex")
+, mConnections()
+{
+}
+
+Service::~Service()
+{
+ mozilla::UnregisterWeakMemoryReporter(this);
+ mozilla::UnregisterStorageSQLiteDistinguishedAmount();
+
+ int rc = sqlite3_vfs_unregister(mSqliteVFS);
+ if (rc != SQLITE_OK)
+ NS_WARNING("Failed to unregister sqlite vfs wrapper.");
+
+ // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but
+ // there is nothing actionable we can do in that case.
+ rc = ::sqlite3_shutdown();
+ if (rc != SQLITE_OK)
+ NS_WARNING("sqlite3 did not shutdown cleanly.");
+
+ DebugOnly<bool> shutdownObserved = !sXPConnect;
+ NS_ASSERTION(shutdownObserved, "Shutdown was not observed!");
+
+ gService = nullptr;
+ delete mSqliteVFS;
+ mSqliteVFS = nullptr;
+}
+
+void
+Service::registerConnection(Connection *aConnection)
+{
+ mRegistrationMutex.AssertNotCurrentThreadOwns();
+ MutexAutoLock mutex(mRegistrationMutex);
+ (void)mConnections.AppendElement(aConnection);
+}
+
+void
+Service::unregisterConnection(Connection *aConnection)
+{
+ // If this is the last Connection it might be the only thing keeping Service
+ // alive. So ensure that Service is destroyed only after the Connection is
+ // cleanly unregistered and destroyed.
+ RefPtr<Service> kungFuDeathGrip(this);
+ {
+ mRegistrationMutex.AssertNotCurrentThreadOwns();
+ MutexAutoLock mutex(mRegistrationMutex);
+
+ for (uint32_t i = 0 ; i < mConnections.Length(); ++i) {
+ if (mConnections[i] == aConnection) {
+ nsCOMPtr<nsIThread> thread = mConnections[i]->threadOpenedOn;
+
+ // Ensure the connection is released on its opening thread. Note, we
+ // must use .forget().take() so that we can manually cast to an
+ // unambiguous nsISupports type.
+ NS_ProxyRelease(thread, mConnections[i].forget());
+
+ mConnections.RemoveElementAt(i);
+ return;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Attempt to unregister unknown storage connection!");
+ }
+}
+
+void
+Service::getConnections(/* inout */ nsTArray<RefPtr<Connection> >& aConnections)
+{
+ mRegistrationMutex.AssertNotCurrentThreadOwns();
+ MutexAutoLock mutex(mRegistrationMutex);
+ aConnections.Clear();
+ aConnections.AppendElements(mConnections);
+}
+
+void
+Service::minimizeMemory()
+{
+ nsTArray<RefPtr<Connection> > connections;
+ getConnections(connections);
+
+ for (uint32_t i = 0; i < connections.Length(); i++) {
+ RefPtr<Connection> conn = connections[i];
+ if (!conn->connectionReady())
+ continue;
+
+ NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
+ nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
+ NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn));
+ bool onOpenedThread = false;
+
+ if (!syncConn) {
+ // This is a mozIStorageAsyncConnection, it can only be used on the main
+ // thread, so we can do a straight API call.
+ nsCOMPtr<mozIStoragePendingStatement> ps;
+ DebugOnly<nsresult> rv =
+ conn->ExecuteSimpleSQLAsync(shrinkPragma, nullptr, getter_AddRefs(ps));
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
+ } else if (NS_SUCCEEDED(conn->threadOpenedOn->IsOnCurrentThread(&onOpenedThread)) &&
+ onOpenedThread) {
+ // We are on the opener thread, so we can just proceed.
+ conn->ExecuteSimpleSQL(shrinkPragma);
+ } else {
+ // We are on the wrong thread, the query should be executed on the
+ // opener thread, so we must dispatch to it.
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod<const nsCString>(
+ conn, &Connection::ExecuteSimpleSQL, shrinkPragma);
+ conn->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ }
+}
+
+void
+Service::shutdown()
+{
+ NS_IF_RELEASE(sXPConnect);
+}
+
+sqlite3_vfs *ConstructTelemetryVFS();
+
+#ifdef MOZ_STORAGE_MEMORY
+
+namespace {
+
+// By default, SQLite tracks the size of all its heap blocks by adding an extra
+// 8 bytes at the start of the block to hold the size. Unfortunately, this
+// causes a lot of 2^N-sized allocations to be rounded up by jemalloc
+// allocator, wasting memory. For example, a request for 1024 bytes has 8
+// bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up
+// to 2048 bytes, wasting 1012 bytes. (See bug 676189 for more details.)
+//
+// So we register jemalloc as the malloc implementation, which avoids this
+// 8-byte overhead, and thus a lot of waste. This requires us to provide a
+// function, sqliteMemRoundup(), which computes the actual size that will be
+// allocated for a given request. SQLite uses this function before all
+// allocations, and may be able to use any excess bytes caused by the rounding.
+//
+// Note: the wrappers for malloc, realloc and moz_malloc_usable_size are
+// necessary because the sqlite_mem_methods type signatures differ slightly
+// from the standard ones -- they use int instead of size_t. But we don't need
+// a wrapper for free.
+
+static void *sqliteMemMalloc(int n)
+{
+ void* p = ::malloc(n);
+ return p;
+}
+
+static void sqliteMemFree(void *p)
+{
+ ::free(p);
+}
+
+static void *sqliteMemRealloc(void *p, int n)
+{
+ return ::realloc(p, n);
+}
+
+static int sqliteMemSize(void *p)
+{
+ return ::moz_malloc_usable_size(p);
+}
+
+static int sqliteMemRoundup(int n)
+{
+ n = malloc_good_size(n);
+
+ // jemalloc can return blocks of size 2 and 4, but SQLite requires that all
+ // allocations be 8-aligned. So we round up sub-8 requests to 8. This
+ // wastes a small amount of memory but is obviously safe.
+ return n <= 8 ? 8 : n;
+}
+
+static int sqliteMemInit(void *p)
+{
+ return 0;
+}
+
+static void sqliteMemShutdown(void *p)
+{
+}
+
+const sqlite3_mem_methods memMethods = {
+ &sqliteMemMalloc,
+ &sqliteMemFree,
+ &sqliteMemRealloc,
+ &sqliteMemSize,
+ &sqliteMemRoundup,
+ &sqliteMemInit,
+ &sqliteMemShutdown,
+ nullptr
+};
+
+} // namespace
+
+#endif // MOZ_STORAGE_MEMORY
+
+static const char* sObserverTopics[] = {
+ "memory-pressure",
+ "xpcom-shutdown",
+ "xpcom-shutdown-threads"
+};
+
+nsresult
+Service::initialize()
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread");
+
+ int rc;
+
+#ifdef MOZ_STORAGE_MEMORY
+ rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods);
+ if (rc != SQLITE_OK)
+ return convertResultCode(rc);
+#endif
+
+ // TODO (bug 1191405): do not preallocate the connections caches until we
+ // have figured the impact on our consumers and memory.
+ sqlite3_config(SQLITE_CONFIG_PAGECACHE, NULL, 0, 0);
+
+ // Explicitly initialize sqlite3. Although this is implicitly called by
+ // various sqlite3 functions (and the sqlite3_open calls in our case),
+ // the documentation suggests calling this directly. So we do.
+ rc = ::sqlite3_initialize();
+ if (rc != SQLITE_OK)
+ return convertResultCode(rc);
+
+ mSqliteVFS = ConstructTelemetryVFS();
+ if (mSqliteVFS) {
+ rc = sqlite3_vfs_register(mSqliteVFS, 1);
+ if (rc != SQLITE_OK)
+ return convertResultCode(rc);
+ } else {
+ NS_WARNING("Failed to register telemetry VFS");
+ }
+
+ // Register for xpcom-shutdown so we can cleanup after ourselves. The
+ // observer service can only be used on the main thread.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
+
+ for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
+ nsresult rv = os->AddObserver(this, sObserverTopics[i], false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // We cache XPConnect for our language helpers. XPConnect can only be
+ // used on the main thread.
+ (void)CallGetService(nsIXPConnect::GetCID(), &sXPConnect);
+
+ // We need to obtain the toolkit.storage.synchronous preferences on the main
+ // thread because the preference service can only be accessed there. This
+ // is cached in the service for all future Open[Unshared]Database calls.
+ sSynchronousPref =
+ Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT);
+
+ // We need to obtain the toolkit.storage.pageSize preferences on the main
+ // thread because the preference service can only be accessed there. This
+ // is cached in the service for all future Open[Unshared]Database calls.
+ sDefaultPageSize =
+ Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT);
+
+ mozilla::RegisterWeakMemoryReporter(this);
+ mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount);
+
+ return NS_OK;
+}
+
+int
+Service::localeCompareStrings(const nsAString &aStr1,
+ const nsAString &aStr2,
+ int32_t aComparisonStrength)
+{
+ // The implementation of nsICollation.CompareString() is platform-dependent.
+ // On Linux it's not thread-safe. It may not be on Windows and OS X either,
+ // but it's more difficult to tell. We therefore synchronize this method.
+ MutexAutoLock mutex(mMutex);
+
+ nsICollation *coll = getLocaleCollation();
+ if (!coll) {
+ NS_ERROR("Storage service has no collation");
+ return 0;
+ }
+
+ int32_t res;
+ nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Collation compare string failed");
+ return 0;
+ }
+
+ return res;
+}
+
+nsICollation *
+Service::getLocaleCollation()
+{
+ mMutex.AssertCurrentThreadOwns();
+
+ if (mLocaleCollation)
+ return mLocaleCollation;
+
+ nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID));
+ if (!svc) {
+ NS_WARNING("Could not get locale service");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsILocale> appLocale;
+ nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not get application locale");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsICollationFactory> collFact =
+ do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
+ if (!collFact) {
+ NS_WARNING("Could not create collation factory");
+ return nullptr;
+ }
+
+ rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not create collation");
+ return nullptr;
+ }
+
+ return mLocaleCollation;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageService
+
+
+NS_IMETHODIMP
+Service::OpenSpecialDatabase(const char *aStorageKey,
+ mozIStorageConnection **_connection)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> storageFile;
+ if (::strcmp(aStorageKey, "memory") == 0) {
+ // just fall through with nullptr storageFile, this will cause the storage
+ // connection to use a memory DB.
+ }
+ else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false);
+
+ rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msc.forget(_connection);
+ return NS_OK;
+
+}
+
+namespace {
+
+class AsyncInitDatabase final : public Runnable
+{
+public:
+ AsyncInitDatabase(Connection* aConnection,
+ nsIFile* aStorageFile,
+ int32_t aGrowthIncrement,
+ mozIStorageCompletionCallback* aCallback)
+ : mConnection(aConnection)
+ , mStorageFile(aStorageFile)
+ , mGrowthIncrement(aGrowthIncrement)
+ , mCallback(aCallback)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile)
+ : mConnection->initialize();
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIRunnable> closeRunnable =
+ NewRunnableMethod<mozIStorageCompletionCallback*>(
+ mConnection.get(),
+ &Connection::AsyncClose,
+ nullptr);
+ MOZ_ASSERT(closeRunnable);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(closeRunnable));
+
+ return DispatchResult(rv, nullptr);
+ }
+
+ if (mGrowthIncrement >= 0) {
+ // Ignore errors. In the future, we might wish to log them.
+ (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
+ }
+
+ return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*,
+ mConnection));
+ }
+
+private:
+ nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
+ RefPtr<CallbackComplete> event =
+ new CallbackComplete(aStatus,
+ aValue,
+ mCallback.forget());
+ return NS_DispatchToMainThread(event);
+ }
+
+ ~AsyncInitDatabase()
+ {
+ NS_ReleaseOnMainThread(mStorageFile.forget());
+ NS_ReleaseOnMainThread(mConnection.forget());
+
+ // Generally, the callback will be released by CallbackComplete.
+ // However, if for some reason Run() is not executed, we still
+ // need to ensure that it is released here.
+ NS_ReleaseOnMainThread(mCallback.forget());
+ }
+
+ RefPtr<Connection> mConnection;
+ nsCOMPtr<nsIFile> mStorageFile;
+ int32_t mGrowthIncrement;
+ RefPtr<mozIStorageCompletionCallback> mCallback;
+};
+
+} // namespace
+
+NS_IMETHODIMP
+Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore,
+ nsIPropertyBag2 *aOptions,
+ mozIStorageCompletionCallback *aCallback)
+{
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+ NS_ENSURE_ARG(aDatabaseStore);
+ NS_ENSURE_ARG(aCallback);
+
+ nsresult rv;
+ bool shared = false;
+ bool readOnly = false;
+ bool ignoreLockingMode = false;
+ int32_t growthIncrement = -1;
+
+#define FAIL_IF_SET_BUT_INVALID(rv)\
+ if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { \
+ return NS_ERROR_INVALID_ARG; \
+ }
+
+ // Deal with options first:
+ if (aOptions) {
+ rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("readOnly"), &readOnly);
+ FAIL_IF_SET_BUT_INVALID(rv);
+
+ rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("ignoreLockingMode"),
+ &ignoreLockingMode);
+ FAIL_IF_SET_BUT_INVALID(rv);
+ // Specifying ignoreLockingMode will force use of the readOnly flag:
+ if (ignoreLockingMode) {
+ readOnly = true;
+ }
+
+ rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared);
+ FAIL_IF_SET_BUT_INVALID(rv);
+
+ // NB: we re-set to -1 if we don't have a storage file later on.
+ rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"),
+ &growthIncrement);
+ FAIL_IF_SET_BUT_INVALID(rv);
+ }
+ int flags = readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE;
+
+ nsCOMPtr<nsIFile> storageFile;
+ nsCOMPtr<nsISupports> dbStore;
+ rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
+ if (NS_SUCCEEDED(rv)) {
+ // Generally, aDatabaseStore holds the database nsIFile.
+ storageFile = do_QueryInterface(dbStore, &rv);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ rv = storageFile->Clone(getter_AddRefs(storageFile));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (!readOnly) {
+ // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
+ flags |= SQLITE_OPEN_CREATE;
+ }
+
+ // Apply the shared-cache option.
+ flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
+ } else {
+ // Sometimes, however, it's a special database name.
+ nsAutoCString keyString;
+ rv = aDatabaseStore->GetAsACString(keyString);
+ if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Just fall through with nullptr storageFile, this will cause the storage
+ // connection to use a memory DB.
+ }
+
+ if (!storageFile && growthIncrement >= 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Create connection on this thread, but initialize it on its helper thread.
+ RefPtr<Connection> msc = new Connection(this, flags, true,
+ ignoreLockingMode);
+ nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
+ MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already");
+
+ RefPtr<AsyncInitDatabase> asyncInit =
+ new AsyncInitDatabase(msc,
+ storageFile,
+ growthIncrement,
+ aCallback);
+ return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+Service::OpenDatabase(nsIFile *aDatabaseFile,
+ mozIStorageConnection **_connection)
+{
+ NS_ENSURE_ARG(aDatabaseFile);
+
+ // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
+ // reasons.
+ int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
+ SQLITE_OPEN_CREATE;
+ RefPtr<Connection> msc = new Connection(this, flags, false);
+
+ nsresult rv = msc->initialize(aDatabaseFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msc.forget(_connection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
+ mozIStorageConnection **_connection)
+{
+ NS_ENSURE_ARG(aDatabaseFile);
+
+ // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
+ // reasons.
+ int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
+ SQLITE_OPEN_CREATE;
+ RefPtr<Connection> msc = new Connection(this, flags, false);
+
+ nsresult rv = msc->initialize(aDatabaseFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msc.forget(_connection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL,
+ mozIStorageConnection **_connection)
+{
+ NS_ENSURE_ARG(aFileURL);
+
+ // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
+ // reasons.
+ int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
+ SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
+ RefPtr<Connection> msc = new Connection(this, flags, false);
+
+ nsresult rv = msc->initialize(aFileURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msc.forget(_connection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Service::BackupDatabaseFile(nsIFile *aDBFile,
+ const nsAString &aBackupFileName,
+ nsIFile *aBackupParentDirectory,
+ nsIFile **backup)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
+ if (!parentDir) {
+ // This argument is optional, and defaults to the same parent directory
+ // as the current file.
+ rv = aDBFile->GetParent(getter_AddRefs(parentDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIFile> backupDB;
+ rv = parentDir->Clone(getter_AddRefs(backupDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = backupDB->Append(aBackupFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString fileName;
+ rv = backupDB->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = backupDB->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ backupDB.forget(backup);
+
+ return aDBFile->CopyTo(parentDir, fileName);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIObserver
+
+NS_IMETHODIMP
+Service::Observe(nsISupports *, const char *aTopic, const char16_t *)
+{
+ if (strcmp(aTopic, "memory-pressure") == 0) {
+ minimizeMemory();
+ } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
+ shutdown();
+ } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
+ nsCOMPtr<nsIObserverService> os =
+ mozilla::services::GetObserverService();
+
+ for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
+ (void)os->RemoveObserver(this, sObserverTopics[i]);
+ }
+
+ bool anyOpen = false;
+ do {
+ nsTArray<RefPtr<Connection> > connections;
+ getConnections(connections);
+ anyOpen = false;
+ for (uint32_t i = 0; i < connections.Length(); i++) {
+ RefPtr<Connection> &conn = connections[i];
+ if (conn->isClosing()) {
+ anyOpen = true;
+ break;
+ }
+ }
+ if (anyOpen) {
+ nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
+ NS_ProcessNextEvent(thread);
+ }
+ } while (anyOpen);
+
+ if (gShutdownChecks == SCM_CRASH) {
+ nsTArray<RefPtr<Connection> > connections;
+ getConnections(connections);
+ for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
+ if (!connections[i]->isClosed()) {
+ MOZ_CRASH();
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageService.h b/components/storage/src/mozStorageService.h
new file mode 100644
index 000000000..effd330b1
--- /dev/null
+++ b/components/storage/src/mozStorageService.h
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZSTORAGESERVICE_H
+#define MOZSTORAGESERVICE_H
+
+#include "nsCOMPtr.h"
+#include "nsICollation.h"
+#include "nsIFile.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsTArray.h"
+#include "mozilla/Mutex.h"
+
+#include "mozIStorageService.h"
+
+class nsIMemoryReporter;
+class nsIXPConnect;
+struct sqlite3_vfs;
+
+namespace mozilla {
+namespace storage {
+
+class Connection;
+class Service : public mozIStorageService
+ , public nsIObserver
+ , public nsIMemoryReporter
+{
+public:
+ /**
+ * Initializes the service. This must be called before any other function!
+ */
+ nsresult initialize();
+
+ /**
+ * Compares two strings using the Service's locale-aware collation.
+ *
+ * @param aStr1
+ * The string to be compared against aStr2.
+ * @param aStr2
+ * The string to be compared against aStr1.
+ * @param aComparisonStrength
+ * The sorting strength, one of the nsICollation constants.
+ * @return aStr1 - aStr2. That is, if aStr1 < aStr2, returns a negative
+ * number. If aStr1 > aStr2, returns a positive number. If
+ * aStr1 == aStr2, returns 0.
+ */
+ int localeCompareStrings(const nsAString &aStr1,
+ const nsAString &aStr2,
+ int32_t aComparisonStrength);
+
+ static Service *getSingleton();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGESERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMEMORYREPORTER
+
+ /**
+ * Obtains an already AddRefed pointer to XPConnect. This is used by
+ * language helpers.
+ */
+ static already_AddRefed<nsIXPConnect> getXPConnect();
+
+ /**
+ * Obtains the cached data for the toolkit.storage.synchronous preference.
+ */
+ static int32_t getSynchronousPref();
+
+ /**
+ * Obtains the default page size for this platform. The default value is
+ * specified in the SQLite makefile (SQLITE_DEFAULT_PAGE_SIZE) but it may be
+ * overriden with the PREF_TS_PAGESIZE hidden preference.
+ */
+ static int32_t getDefaultPageSize()
+ {
+ return sDefaultPageSize;
+ }
+
+ /**
+ * Returns a boolean value indicating whether or not the given page size is
+ * valid (currently understood as a power of 2 between 512 and 65536).
+ */
+ static bool pageSizeIsValid(int32_t aPageSize)
+ {
+ return aPageSize == 512 || aPageSize == 1024 || aPageSize == 2048 ||
+ aPageSize == 4096 || aPageSize == 8192 || aPageSize == 16384 ||
+ aPageSize == 32768 || aPageSize == 65536;
+ }
+
+ /**
+ * Registers the connection with the storage service. Connections are
+ * registered so they can be iterated over.
+ *
+ * @pre mRegistrationMutex is not held
+ *
+ * @param aConnection
+ * The connection to register.
+ */
+ void registerConnection(Connection *aConnection);
+
+ /**
+ * Unregisters the connection with the storage service.
+ *
+ * @pre mRegistrationMutex is not held
+ *
+ * @param aConnection
+ * The connection to unregister.
+ */
+ void unregisterConnection(Connection *aConnection);
+
+ /**
+ * Gets the list of open connections. Note that you must test each
+ * connection with mozIStorageConnection::connectionReady before doing
+ * anything with it, and skip it if it's not ready.
+ *
+ * @pre mRegistrationMutex is not held
+ *
+ * @param aConnections
+ * An inout param; it is cleared and the connections are appended to
+ * it.
+ * @return The open connections.
+ */
+ void getConnections(nsTArray<RefPtr<Connection> >& aConnections);
+
+private:
+ Service();
+ virtual ~Service();
+
+ /**
+ * Used for 1) locking around calls when initializing connections so that we
+ * can ensure that the state of sqlite3_enable_shared_cache is sane and 2)
+ * synchronizing access to mLocaleCollation.
+ */
+ Mutex mMutex;
+
+ sqlite3_vfs *mSqliteVFS;
+
+ /**
+ * Protects mConnections.
+ */
+ Mutex mRegistrationMutex;
+
+ /**
+ * The list of connections we have created. Modifications to it are
+ * protected by |mRegistrationMutex|.
+ */
+ nsTArray<RefPtr<Connection> > mConnections;
+
+ /**
+ * Frees as much heap memory as possible from all of the known open
+ * connections.
+ */
+ void minimizeMemory();
+
+ /**
+ * Shuts down the storage service, freeing all of the acquired resources.
+ */
+ void shutdown();
+
+ /**
+ * Lazily creates and returns a collation created from the application's
+ * locale that all statements of all Connections of this Service may use.
+ * Since the collation's lifetime is that of the Service and no statement may
+ * execute outside the lifetime of the Service, this method returns a raw
+ * pointer.
+ */
+ nsICollation *getLocaleCollation();
+
+ /**
+ * Lazily created collation that all statements of all Connections of this
+ * Service may use. The collation is created from the application's locale.
+ *
+ * @note Collation implementations are platform-dependent and in general not
+ * thread-safe. Access to this collation should be synchronized.
+ */
+ nsCOMPtr<nsICollation> mLocaleCollation;
+
+ nsCOMPtr<nsIFile> mProfileStorageFile;
+
+ nsCOMPtr<nsIMemoryReporter> mStorageSQLiteReporter;
+
+ static Service *gService;
+
+ static nsIXPConnect *sXPConnect;
+
+ static int32_t sSynchronousPref;
+ static int32_t sDefaultPageSize;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif /* MOZSTORAGESERVICE_H */
diff --git a/components/storage/src/mozStorageStatement.cpp b/components/storage/src/mozStorageStatement.cpp
new file mode 100644
index 000000000..7210274d0
--- /dev/null
+++ b/components/storage/src/mozStorageStatement.cpp
@@ -0,0 +1,889 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <limits.h>
+#include <stdio.h>
+
+#include "nsError.h"
+#include "nsMemory.h"
+#include "nsThreadUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "Variant.h"
+
+#include "mozIStorageError.h"
+
+#include "mozStorageBindingParams.h"
+#include "mozStorageConnection.h"
+#include "mozStorageStatementJSHelper.h"
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageStatementParams.h"
+#include "mozStorageStatementRow.h"
+#include "mozStorageStatement.h"
+#include "GeckoProfiler.h"
+#include "nsDOMClassInfo.h"
+
+#include "mozilla/Logging.h"
+
+
+extern mozilla::LazyLogModule gStorageLog;
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIClassInfo
+
+NS_IMPL_CI_INTERFACE_GETTER(Statement,
+ mozIStorageStatement,
+ mozIStorageBaseStatement,
+ mozIStorageBindingParams,
+ mozIStorageValueArray,
+ mozilla::storage::StorageBaseStatementInternal)
+
+class StatementClassInfo : public nsIClassInfo
+{
+public:
+ constexpr StatementClassInfo() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD
+ GetInterfaces(uint32_t *_count, nsIID ***_array) override
+ {
+ return NS_CI_INTERFACE_GETTER_NAME(Statement)(_count, _array);
+ }
+
+ NS_IMETHOD
+ GetScriptableHelper(nsIXPCScriptable **_helper) override
+ {
+ static StatementJSHelper sJSHelper;
+ *_helper = &sJSHelper;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetContractID(char **_contractID) override
+ {
+ *_contractID = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetClassDescription(char **_desc) override
+ {
+ *_desc = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetClassID(nsCID **_id) override
+ {
+ *_id = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetFlags(uint32_t *_flags) override
+ {
+ *_flags = 0;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetClassIDNoAlloc(nsCID *_cid) override
+ {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+};
+
+NS_IMETHODIMP_(MozExternalRefCountType) StatementClassInfo::AddRef() { return 2; }
+NS_IMETHODIMP_(MozExternalRefCountType) StatementClassInfo::Release() { return 1; }
+NS_IMPL_QUERY_INTERFACE(StatementClassInfo, nsIClassInfo)
+
+static StatementClassInfo sStatementClassInfo;
+
+////////////////////////////////////////////////////////////////////////////////
+//// Statement
+
+Statement::Statement()
+: StorageBaseStatementInternal()
+, mDBStatement(nullptr)
+, mColumnNames()
+, mExecuting(false)
+{
+}
+
+nsresult
+Statement::initialize(Connection *aDBConnection,
+ sqlite3 *aNativeConnection,
+ const nsACString &aSQLStatement)
+{
+ MOZ_ASSERT(aDBConnection, "No database connection given!");
+ MOZ_ASSERT(!aDBConnection->isClosed(), "Database connection should be valid");
+ MOZ_ASSERT(!mDBStatement, "Statement already initialized!");
+ MOZ_ASSERT(aNativeConnection, "No native connection given!");
+
+ int srv = aDBConnection->prepareStatement(aNativeConnection,
+ PromiseFlatCString(aSQLStatement),
+ &mDBStatement);
+ if (srv != SQLITE_OK) {
+ MOZ_LOG(gStorageLog, LogLevel::Error,
+ ("Sqlite statement prepare error: %d '%s'", srv,
+ ::sqlite3_errmsg(aNativeConnection)));
+ MOZ_LOG(gStorageLog, LogLevel::Error,
+ ("Statement was: '%s'", PromiseFlatCString(aSQLStatement).get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_LOG(gStorageLog, LogLevel::Debug, ("Initialized statement '%s' (0x%p)",
+ PromiseFlatCString(aSQLStatement).get(),
+ mDBStatement));
+
+ mDBConnection = aDBConnection;
+ mNativeConnection = aNativeConnection;
+ mParamCount = ::sqlite3_bind_parameter_count(mDBStatement);
+ mResultColumnCount = ::sqlite3_column_count(mDBStatement);
+ mColumnNames.Clear();
+
+ nsCString* columnNames = mColumnNames.AppendElements(mResultColumnCount);
+ for (uint32_t i = 0; i < mResultColumnCount; i++) {
+ const char *name = ::sqlite3_column_name(mDBStatement, i);
+ columnNames[i].Assign(name);
+ }
+
+#ifdef DEBUG
+ // We want to try and test for LIKE and that consumers are using
+ // escapeStringForLIKE instead of just trusting user input. The idea to
+ // check to see if they are binding a parameter after like instead of just
+ // using a string. We only do this in debug builds because it's expensive!
+ const nsCaseInsensitiveCStringComparator c;
+ nsACString::const_iterator start, end, e;
+ aSQLStatement.BeginReading(start);
+ aSQLStatement.EndReading(end);
+ e = end;
+ while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) {
+ // We have a LIKE in here, so we perform our tests
+ // FindInReadable moves the iterator, so we have to get a new one for
+ // each test we perform.
+ nsACString::const_iterator s1, s2, s3;
+ s1 = s2 = s3 = start;
+
+ if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) ||
+ ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) ||
+ ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) {
+ // At this point, we didn't find a LIKE statement followed by ?, :,
+ // or @, all of which are valid characters for binding a parameter.
+ // We will warn the consumer that they may not be safely using LIKE.
+ NS_WARNING("Unsafe use of LIKE detected! Please ensure that you "
+ "are using mozIStorageStatement::escapeStringForLIKE "
+ "and that you are binding that result to the statement "
+ "to prevent SQL injection attacks.");
+ }
+
+ // resetting start and e
+ start = e;
+ e = end;
+ }
+#endif
+
+ return NS_OK;
+}
+
+mozIStorageBindingParams *
+Statement::getParams()
+{
+ nsresult rv;
+
+ // If we do not have an array object yet, make it.
+ if (!mParamsArray) {
+ nsCOMPtr<mozIStorageBindingParamsArray> array;
+ rv = NewBindingParamsArray(getter_AddRefs(array));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ mParamsArray = static_cast<BindingParamsArray *>(array.get());
+ }
+
+ // If there isn't already any rows added, we'll have to add one to use.
+ if (mParamsArray->length() == 0) {
+ RefPtr<BindingParams> params(new BindingParams(mParamsArray, this));
+ NS_ENSURE_TRUE(params, nullptr);
+
+ rv = mParamsArray->AddParams(params);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ // We have to unlock our params because AddParams locks them. This is safe
+ // because no reference to the params object was, or ever will be given out.
+ params->unlock(this);
+
+ // We also want to lock our array at this point - we don't want anything to
+ // be added to it. Nothing has, or will ever get a reference to it, but we
+ // will get additional safety checks via assertions by doing this.
+ mParamsArray->lock();
+ }
+
+ return *mParamsArray->begin();
+}
+
+Statement::~Statement()
+{
+ (void)internalFinalize(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+NS_IMPL_ADDREF(Statement)
+NS_IMPL_RELEASE(Statement)
+
+NS_INTERFACE_MAP_BEGIN(Statement)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageStatement)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageValueArray)
+ NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal)
+ if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
+ foundInterface = static_cast<nsIClassInfo *>(&sStatementClassInfo);
+ }
+ else
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageStatement)
+NS_INTERFACE_MAP_END
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// StorageBaseStatementInternal
+
+Connection *
+Statement::getOwner()
+{
+ return mDBConnection;
+}
+
+int
+Statement::getAsyncStatement(sqlite3_stmt **_stmt)
+{
+ // If we have no statement, we shouldn't be calling this method!
+ NS_ASSERTION(mDBStatement != nullptr, "We have no statement to clone!");
+
+ // If we do not yet have a cached async statement, clone our statement now.
+ if (!mAsyncStatement) {
+ nsDependentCString sql(::sqlite3_sql(mDBStatement));
+ int rc = mDBConnection->prepareStatement(mNativeConnection, sql,
+ &mAsyncStatement);
+ if (rc != SQLITE_OK) {
+ *_stmt = nullptr;
+ return rc;
+ }
+
+ MOZ_LOG(gStorageLog, LogLevel::Debug,
+ ("Cloned statement 0x%p to 0x%p", mDBStatement, mAsyncStatement));
+ }
+
+ *_stmt = mAsyncStatement;
+ return SQLITE_OK;
+}
+
+nsresult
+Statement::getAsynchronousStatementData(StatementData &_data)
+{
+ if (!mDBStatement)
+ return NS_ERROR_UNEXPECTED;
+
+ sqlite3_stmt *stmt;
+ int rc = getAsyncStatement(&stmt);
+ if (rc != SQLITE_OK)
+ return convertResultCode(rc);
+
+ _data = StatementData(stmt, bindingParamsArray(), this);
+
+ return NS_OK;
+}
+
+already_AddRefed<mozIStorageBindingParams>
+Statement::newBindingParams(mozIStorageBindingParamsArray *aOwner)
+{
+ nsCOMPtr<mozIStorageBindingParams> params = new BindingParams(aOwner, this);
+ return params.forget();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageStatement
+
+// proxy to StorageBaseStatementInternal using its define helper.
+MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(Statement, (void)0;)
+
+NS_IMETHODIMP
+Statement::Clone(mozIStorageStatement **_statement)
+{
+ RefPtr<Statement> statement(new Statement());
+ NS_ENSURE_TRUE(statement, NS_ERROR_OUT_OF_MEMORY);
+
+ nsAutoCString sql(::sqlite3_sql(mDBStatement));
+ nsresult rv = statement->initialize(mDBConnection, mNativeConnection, sql);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ statement.forget(_statement);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::Finalize()
+{
+ return internalFinalize(false);
+}
+
+nsresult
+Statement::internalFinalize(bool aDestructing)
+{
+ if (!mDBStatement)
+ return NS_OK;
+
+ int srv = SQLITE_OK;
+
+ if (!mDBConnection->isClosed()) {
+ //
+ // The connection is still open. While statement finalization and
+ // closing may, in some cases, take place in two distinct threads,
+ // we have a guarantee that the connection will remain open until
+ // this method terminates:
+ //
+ // a. The connection will be closed synchronously. In this case,
+ // there is no race condition, as everything takes place on the
+ // same thread.
+ //
+ // b. The connection is closed asynchronously and this code is
+ // executed on the opener thread. In this case, asyncClose() has
+ // not been called yet and will not be called before we return
+ // from this function.
+ //
+ // c. The connection is closed asynchronously and this code is
+ // executed on the async execution thread. In this case,
+ // AsyncCloseConnection::Run() has not been called yet and will
+ // not be called before we return from this function.
+ //
+ // In either case, the connection is still valid, hence closing
+ // here is safe.
+ //
+ MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s' during garbage-collection",
+ ::sqlite3_sql(mDBStatement)));
+ srv = ::sqlite3_finalize(mDBStatement);
+ }
+#ifdef DEBUG
+ else {
+ //
+ // The database connection is either closed or closing. The sqlite
+ // statement has either been finalized already by the connection
+ // or is about to be finalized by the connection.
+ //
+ // Finalizing it here would be useless and segfaultish.
+ //
+
+ char *msg = ::PR_smprintf("SQL statement (%x) should have been finalized"
+ " before garbage-collection. For more details on this statement, set"
+ " NSPR_LOG_MESSAGES=mozStorage:5 .",
+ mDBStatement);
+
+ //
+ // Note that we can't display the statement itself, as the data structure
+ // is not valid anymore. However, the address shown here should help
+ // developers correlate with the more complete debug message triggered
+ // by AsyncClose().
+ //
+
+#if 0
+ // Deactivate the warning until we have fixed the exising culprit
+ // (see bug 914070).
+ NS_WARNING(msg);
+#endif // 0
+
+ MOZ_LOG(gStorageLog, LogLevel::Warning, (msg));
+
+ ::PR_smprintf_free(msg);
+ }
+
+#endif
+
+ mDBStatement = nullptr;
+
+ if (mAsyncStatement) {
+ // If the destructor called us, there are no pending async statements (they
+ // hold a reference to us) and we can/must just kill the statement directly.
+ if (aDestructing)
+ destructorAsyncFinalize();
+ else
+ asyncFinalize();
+ }
+
+ // Release the holders, so they can release the reference to us.
+ mStatementParamsHolder = nullptr;
+ mStatementRowHolder = nullptr;
+
+ return convertResultCode(srv);
+}
+
+NS_IMETHODIMP
+Statement::GetParameterCount(uint32_t *_parameterCount)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ *_parameterCount = mParamCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetParameterName(uint32_t aParamIndex,
+ nsACString &_name)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+ ENSURE_INDEX_VALUE(aParamIndex, mParamCount);
+
+ const char *name = ::sqlite3_bind_parameter_name(mDBStatement,
+ aParamIndex + 1);
+ if (name == nullptr) {
+ // this thing had no name, so fake one
+ nsAutoCString fakeName(":");
+ fakeName.AppendInt(aParamIndex);
+ _name.Assign(fakeName);
+ }
+ else {
+ _name.Assign(nsDependentCString(name));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetParameterIndex(const nsACString &aName,
+ uint32_t *_index)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // We do not accept any forms of names other than ":name", but we need to add
+ // the colon for SQLite.
+ nsAutoCString name(":");
+ name.Append(aName);
+ int ind = ::sqlite3_bind_parameter_index(mDBStatement, name.get());
+ if (ind == 0) // Named parameter not found.
+ return NS_ERROR_INVALID_ARG;
+
+ *_index = ind - 1; // SQLite indexes are 1-based, we are 0-based.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetColumnCount(uint32_t *_columnCount)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ *_columnCount = mResultColumnCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetColumnName(uint32_t aColumnIndex,
+ nsACString &_name)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+ ENSURE_INDEX_VALUE(aColumnIndex, mResultColumnCount);
+
+ const char *cname = ::sqlite3_column_name(mDBStatement, aColumnIndex);
+ _name.Assign(nsDependentCString(cname));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetColumnIndex(const nsACString &aName,
+ uint32_t *_index)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Surprisingly enough, SQLite doesn't provide an API for this. We have to
+ // determine it ourselves sadly.
+ for (uint32_t i = 0; i < mResultColumnCount; i++) {
+ if (mColumnNames[i].Equals(aName)) {
+ *_index = i;
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+Statement::Reset()
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+#ifdef DEBUG
+ MOZ_LOG(gStorageLog, LogLevel::Debug, ("Resetting statement: '%s'",
+ ::sqlite3_sql(mDBStatement)));
+
+ checkAndLogStatementPerformance(mDBStatement);
+#endif
+
+ mParamsArray = nullptr;
+ (void)sqlite3_reset(mDBStatement);
+ (void)sqlite3_clear_bindings(mDBStatement);
+
+ mExecuting = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::BindParameters(mozIStorageBindingParamsArray *aParameters)
+{
+ NS_ENSURE_ARG_POINTER(aParameters);
+
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters);
+ if (array->getOwner() != this)
+ return NS_ERROR_UNEXPECTED;
+
+ if (array->length() == 0)
+ return NS_ERROR_UNEXPECTED;
+
+ mParamsArray = array;
+ mParamsArray->lock();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::Execute()
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ bool ret;
+ nsresult rv = ExecuteStep(&ret);
+ nsresult rv2 = Reset();
+
+ return NS_FAILED(rv) ? rv : rv2;
+}
+
+NS_IMETHODIMP
+Statement::ExecuteStep(bool *_moreResults)
+{
+ PROFILER_LABEL("Statement", "ExecuteStep",
+ js::ProfileEntry::Category::STORAGE);
+
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ // Bind any parameters first before executing.
+ if (mParamsArray) {
+ // If we have more than one row of parameters to bind, they shouldn't be
+ // calling this method (and instead use executeAsync).
+ if (mParamsArray->length() != 1)
+ return NS_ERROR_UNEXPECTED;
+
+ BindingParamsArray::iterator row = mParamsArray->begin();
+ nsCOMPtr<IStorageBindingParamsInternal> bindingInternal =
+ do_QueryInterface(*row);
+ nsCOMPtr<mozIStorageError> error = bindingInternal->bind(mDBStatement);
+ if (error) {
+ int32_t srv;
+ (void)error->GetResult(&srv);
+ return convertResultCode(srv);
+ }
+
+ // We have bound, so now we can clear our array.
+ mParamsArray = nullptr;
+ }
+ int srv = mDBConnection->stepStatement(mNativeConnection, mDBStatement);
+
+ if (srv != SQLITE_ROW && srv != SQLITE_DONE && MOZ_LOG_TEST(gStorageLog, LogLevel::Debug)) {
+ nsAutoCString errStr;
+ (void)mDBConnection->GetLastErrorString(errStr);
+ MOZ_LOG(gStorageLog, LogLevel::Debug,
+ ("Statement::ExecuteStep error: %s", errStr.get()));
+ }
+
+ // SQLITE_ROW and SQLITE_DONE are non-errors
+ if (srv == SQLITE_ROW) {
+ // we got a row back
+ mExecuting = true;
+ *_moreResults = true;
+ return NS_OK;
+ }
+ else if (srv == SQLITE_DONE) {
+ // statement is done (no row returned)
+ mExecuting = false;
+ *_moreResults = false;
+ return NS_OK;
+ }
+ else if (srv == SQLITE_BUSY || srv == SQLITE_MISUSE) {
+ mExecuting = false;
+ }
+ else if (mExecuting) {
+ MOZ_LOG(gStorageLog, LogLevel::Error,
+ ("SQLite error after mExecuting was true!"));
+ mExecuting = false;
+ }
+
+ return convertResultCode(srv);
+}
+
+NS_IMETHODIMP
+Statement::GetState(int32_t *_state)
+{
+ if (!mDBStatement)
+ *_state = MOZ_STORAGE_STATEMENT_INVALID;
+ else if (mExecuting)
+ *_state = MOZ_STORAGE_STATEMENT_EXECUTING;
+ else
+ *_state = MOZ_STORAGE_STATEMENT_READY;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageValueArray (now part of mozIStorageStatement too)
+
+NS_IMETHODIMP
+Statement::GetNumEntries(uint32_t *_length)
+{
+ *_length = mResultColumnCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetTypeOfIndex(uint32_t aIndex,
+ int32_t *_type)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ ENSURE_INDEX_VALUE(aIndex, mResultColumnCount);
+
+ if (!mExecuting)
+ return NS_ERROR_UNEXPECTED;
+
+ int t = ::sqlite3_column_type(mDBStatement, aIndex);
+ switch (t) {
+ case SQLITE_INTEGER:
+ *_type = mozIStorageStatement::VALUE_TYPE_INTEGER;
+ break;
+ case SQLITE_FLOAT:
+ *_type = mozIStorageStatement::VALUE_TYPE_FLOAT;
+ break;
+ case SQLITE_TEXT:
+ *_type = mozIStorageStatement::VALUE_TYPE_TEXT;
+ break;
+ case SQLITE_BLOB:
+ *_type = mozIStorageStatement::VALUE_TYPE_BLOB;
+ break;
+ case SQLITE_NULL:
+ *_type = mozIStorageStatement::VALUE_TYPE_NULL;
+ break;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetInt32(uint32_t aIndex,
+ int32_t *_value)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ ENSURE_INDEX_VALUE(aIndex, mResultColumnCount);
+
+ if (!mExecuting)
+ return NS_ERROR_UNEXPECTED;
+
+ *_value = ::sqlite3_column_int(mDBStatement, aIndex);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetInt64(uint32_t aIndex,
+ int64_t *_value)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ ENSURE_INDEX_VALUE(aIndex, mResultColumnCount);
+
+ if (!mExecuting)
+ return NS_ERROR_UNEXPECTED;
+
+ *_value = ::sqlite3_column_int64(mDBStatement, aIndex);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetDouble(uint32_t aIndex,
+ double *_value)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ ENSURE_INDEX_VALUE(aIndex, mResultColumnCount);
+
+ if (!mExecuting)
+ return NS_ERROR_UNEXPECTED;
+
+ *_value = ::sqlite3_column_double(mDBStatement, aIndex);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetUTF8String(uint32_t aIndex,
+ nsACString &_value)
+{
+ // Get type of Index will check aIndex for us, so we don't have to.
+ int32_t type;
+ nsresult rv = GetTypeOfIndex(aIndex, &type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (type == mozIStorageStatement::VALUE_TYPE_NULL) {
+ // NULL columns should have IsVoid set to distinguish them from the empty
+ // string.
+ _value.SetIsVoid(true);
+ }
+ else {
+ const char *value =
+ reinterpret_cast<const char *>(::sqlite3_column_text(mDBStatement,
+ aIndex));
+ _value.Assign(value, ::sqlite3_column_bytes(mDBStatement, aIndex));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetString(uint32_t aIndex,
+ nsAString &_value)
+{
+ // Get type of Index will check aIndex for us, so we don't have to.
+ int32_t type;
+ nsresult rv = GetTypeOfIndex(aIndex, &type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (type == mozIStorageStatement::VALUE_TYPE_NULL) {
+ // NULL columns should have IsVoid set to distinguish them from the empty
+ // string.
+ _value.SetIsVoid(true);
+ } else {
+ const char16_t *value =
+ static_cast<const char16_t *>(::sqlite3_column_text16(mDBStatement,
+ aIndex));
+ _value.Assign(value, ::sqlite3_column_bytes16(mDBStatement, aIndex) / 2);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetBlob(uint32_t aIndex,
+ uint32_t *_size,
+ uint8_t **_blob)
+{
+ if (!mDBStatement)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ ENSURE_INDEX_VALUE(aIndex, mResultColumnCount);
+
+ if (!mExecuting)
+ return NS_ERROR_UNEXPECTED;
+
+ int size = ::sqlite3_column_bytes(mDBStatement, aIndex);
+ void *blob = nullptr;
+ if (size) {
+ blob = nsMemory::Clone(::sqlite3_column_blob(mDBStatement, aIndex), size);
+ NS_ENSURE_TRUE(blob, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ *_blob = static_cast<uint8_t *>(blob);
+ *_size = size;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetBlobAsString(uint32_t aIndex, nsAString& aValue)
+{
+ return DoGetBlobAsString(this, aIndex, aValue);
+}
+
+NS_IMETHODIMP
+Statement::GetBlobAsUTF8String(uint32_t aIndex, nsACString& aValue)
+{
+ return DoGetBlobAsString(this, aIndex, aValue);
+}
+
+NS_IMETHODIMP
+Statement::GetSharedUTF8String(uint32_t aIndex,
+ uint32_t *_length,
+ const char **_value)
+{
+ if (_length)
+ *_length = ::sqlite3_column_bytes(mDBStatement, aIndex);
+
+ *_value = reinterpret_cast<const char *>(::sqlite3_column_text(mDBStatement,
+ aIndex));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetSharedString(uint32_t aIndex,
+ uint32_t *_length,
+ const char16_t **_value)
+{
+ if (_length)
+ *_length = ::sqlite3_column_bytes16(mDBStatement, aIndex);
+
+ *_value = static_cast<const char16_t *>(::sqlite3_column_text16(mDBStatement,
+ aIndex));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetSharedBlob(uint32_t aIndex,
+ uint32_t *_size,
+ const uint8_t **_blob)
+{
+ *_size = ::sqlite3_column_bytes(mDBStatement, aIndex);
+ *_blob = static_cast<const uint8_t *>(::sqlite3_column_blob(mDBStatement,
+ aIndex));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Statement::GetIsNull(uint32_t aIndex,
+ bool *_isNull)
+{
+ // Get type of Index will check aIndex for us, so we don't have to.
+ int32_t type;
+ nsresult rv = GetTypeOfIndex(aIndex, &type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *_isNull = (type == mozIStorageStatement::VALUE_TYPE_NULL);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageBindingParams
+
+BOILERPLATE_BIND_PROXIES(
+ Statement,
+ if (!mDBStatement) return NS_ERROR_NOT_INITIALIZED;
+)
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageStatement.h b/components/storage/src/mozStorageStatement.h
new file mode 100644
index 000000000..69b69c58d
--- /dev/null
+++ b/components/storage/src/mozStorageStatement.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStorageStatement_h
+#define mozStorageStatement_h
+
+#include "nsAutoPtr.h"
+#include "nsString.h"
+
+#include "nsTArray.h"
+
+#include "mozStorageBindingParamsArray.h"
+#include "mozStorageStatementData.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageValueArray.h"
+#include "StorageBaseStatementInternal.h"
+#include "mozilla/Attributes.h"
+
+class nsIXPConnectJSObjectHolder;
+struct sqlite3_stmt;
+
+namespace mozilla {
+namespace storage {
+class StatementJSHelper;
+class Connection;
+
+class Statement final : public mozIStorageStatement
+ , public mozIStorageValueArray
+ , public StorageBaseStatementInternal
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGESTATEMENT
+ NS_DECL_MOZISTORAGEBASESTATEMENT
+ NS_DECL_MOZISTORAGEBINDINGPARAMS
+ // NS_DECL_MOZISTORAGEVALUEARRAY (methods in mozIStorageStatement)
+ NS_DECL_STORAGEBASESTATEMENTINTERNAL
+
+ Statement();
+
+ /**
+ * Initializes the object on aDBConnection by preparing the SQL statement
+ * given by aSQLStatement.
+ *
+ * @param aDBConnection
+ * The Connection object this statement is associated with.
+ * @param aNativeConnection
+ * The native Sqlite connection this statement is associated with.
+ * @param aSQLStatement
+ * The SQL statement to prepare that this object will represent.
+ */
+ nsresult initialize(Connection *aDBConnection,
+ sqlite3* aNativeConnection,
+ const nsACString &aSQLStatement);
+
+
+ /**
+ * Obtains the native statement pointer.
+ */
+ inline sqlite3_stmt *nativeStatement() { return mDBStatement; }
+
+ /**
+ * Obtains and transfers ownership of the array of parameters that are bound
+ * to this statment. This can be null.
+ */
+ inline already_AddRefed<BindingParamsArray> bindingParamsArray()
+ {
+ return mParamsArray.forget();
+ }
+
+private:
+ ~Statement();
+
+ sqlite3_stmt *mDBStatement;
+ uint32_t mParamCount;
+ uint32_t mResultColumnCount;
+ nsTArray<nsCString> mColumnNames;
+ bool mExecuting;
+
+ /**
+ * @return a pointer to the BindingParams object to use with our Bind*
+ * method.
+ */
+ mozIStorageBindingParams *getParams();
+
+ /**
+ * Holds the array of parameters to bind to this statement when we execute
+ * it asynchronously.
+ */
+ RefPtr<BindingParamsArray> mParamsArray;
+
+ /**
+ * The following two members are only used with the JS helper. They cache
+ * the row and params objects.
+ */
+ nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementParamsHolder;
+ nsMainThreadPtrHandle<nsIXPConnectJSObjectHolder> mStatementRowHolder;
+
+ /**
+ * Internal version of finalize that allows us to tell it if it is being
+ * called from the destructor so it can know not to dispatch events that
+ * require a reference to us.
+ *
+ * @param aDestructing
+ * Is the destructor calling?
+ */
+ nsresult internalFinalize(bool aDestructing);
+
+ friend class StatementJSHelper;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStorageStatement_h
diff --git a/components/storage/src/mozStorageStatementData.h b/components/storage/src/mozStorageStatementData.h
new file mode 100644
index 000000000..8baaf2fa7
--- /dev/null
+++ b/components/storage/src/mozStorageStatementData.h
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozStorageStatementData_h
+#define mozStorageStatementData_h
+
+#include "sqlite3.h"
+
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+#include "nsIEventTarget.h"
+#include "MainThreadUtils.h"
+
+#include "mozStorageBindingParamsArray.h"
+#include "mozIStorageBaseStatement.h"
+#include "mozStorageConnection.h"
+#include "StorageBaseStatementInternal.h"
+
+struct sqlite3_stmt;
+
+namespace mozilla {
+namespace storage {
+
+class StatementData
+{
+public:
+ StatementData(sqlite3_stmt *aStatement,
+ already_AddRefed<BindingParamsArray> aParamsArray,
+ StorageBaseStatementInternal *aStatementOwner)
+ : mStatement(aStatement)
+ , mParamsArray(aParamsArray)
+ , mStatementOwner(aStatementOwner)
+ {
+ NS_PRECONDITION(mStatementOwner, "Must have a statement owner!");
+ }
+ StatementData(const StatementData &aSource)
+ : mStatement(aSource.mStatement)
+ , mParamsArray(aSource.mParamsArray)
+ , mStatementOwner(aSource.mStatementOwner)
+ {
+ NS_PRECONDITION(mStatementOwner, "Must have a statement owner!");
+ }
+ StatementData()
+ : mStatement(nullptr)
+ {
+ }
+ ~StatementData()
+ {
+ // We need to ensure that mParamsArray is released on the main thread,
+ // as the binding arguments may be XPConnect values, which are safe
+ // to release only on the main thread.
+ NS_ReleaseOnMainThread(mParamsArray.forget());
+ }
+
+ /**
+ * Return the sqlite statement, fetching it from the storage statement. In
+ * the case of AsyncStatements this may actually create the statement
+ */
+ inline int getSqliteStatement(sqlite3_stmt **_stmt)
+ {
+ if (!mStatement) {
+ int rc = mStatementOwner->getAsyncStatement(&mStatement);
+ NS_ENSURE_TRUE(rc == SQLITE_OK, rc);
+ }
+ *_stmt = mStatement;
+ return SQLITE_OK;
+ }
+
+ operator BindingParamsArray *() const { return mParamsArray; }
+
+ /**
+ * NULLs out our sqlite3_stmt (it is held by the owner) after reseting it and
+ * clear all bindings to it. This is expected to occur on the async thread.
+ */
+ inline void reset()
+ {
+ NS_PRECONDITION(mStatementOwner, "Must have a statement owner!");
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIEventTarget> asyncThread =
+ mStatementOwner->getOwner()->getAsyncExecutionTarget();
+ // It's possible that we are shutting down the async thread, and this
+ // method would return nullptr as a result.
+ if (asyncThread) {
+ bool onAsyncThread;
+ NS_ASSERTION(NS_SUCCEEDED(asyncThread->IsOnCurrentThread(&onAsyncThread)) && onAsyncThread,
+ "This should only be running on the async thread!");
+ }
+ }
+#endif
+ // In the AsyncStatement case we may never have populated mStatement if the
+ // AsyncExecuteStatements got canceled or a failure occurred in constructing
+ // the statement.
+ if (mStatement) {
+ (void)::sqlite3_reset(mStatement);
+ (void)::sqlite3_clear_bindings(mStatement);
+ mStatement = nullptr;
+ }
+ }
+
+ /**
+ * Indicates if this statement has parameters to be bound before it is
+ * executed.
+ *
+ * @return true if the statement has parameters to bind against, false
+ * otherwise.
+ */
+ inline bool hasParametersToBeBound() const { return !!mParamsArray; }
+ /**
+ * Indicates the number of implicit statements generated by this statement
+ * requiring a transaction for execution. For example a single statement
+ * with N BindingParams will execute N implicit staments.
+ *
+ * @return number of statements requiring a transaction for execution.
+ *
+ * @note In the case of AsyncStatements this may actually create the
+ * statement.
+ */
+ inline uint32_t needsTransaction()
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+ // Be sure to use the getSqliteStatement helper, since sqlite3_stmt_readonly
+ // can only analyze prepared statements and AsyncStatements are prepared
+ // lazily.
+ sqlite3_stmt *stmt;
+ int rc = getSqliteStatement(&stmt);
+ if (SQLITE_OK != rc || ::sqlite3_stmt_readonly(stmt)) {
+ return 0;
+ }
+ return mParamsArray ? mParamsArray->length() : 1;
+ }
+
+private:
+ sqlite3_stmt *mStatement;
+ RefPtr<BindingParamsArray> mParamsArray;
+
+ /**
+ * We hold onto a reference of the statement's owner so it doesn't get
+ * destroyed out from under us.
+ */
+ nsCOMPtr<StorageBaseStatementInternal> mStatementOwner;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // mozStorageStatementData_h
diff --git a/components/storage/src/mozStorageStatementJSHelper.cpp b/components/storage/src/mozStorageStatementJSHelper.cpp
new file mode 100644
index 000000000..37e3bf517
--- /dev/null
+++ b/components/storage/src/mozStorageStatementJSHelper.cpp
@@ -0,0 +1,287 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIXPConnect.h"
+#include "mozStorageStatement.h"
+#include "mozStorageService.h"
+
+#include "nsMemory.h"
+#include "nsString.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozStorageStatementJSHelper.h"
+
+#include "mozStorageStatementRow.h"
+#include "mozStorageStatementParams.h"
+
+#include "jsapi.h"
+
+#include "xpc_make_class.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// Global Functions
+
+static
+bool
+stepFunc(JSContext *aCtx,
+ uint32_t,
+ JS::Value *_vp)
+{
+ nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
+ nsCOMPtr<nsIXPConnectWrappedNative> wrapper;
+ JSObject *obj = JS_THIS_OBJECT(aCtx, _vp);
+ if (!obj) {
+ return false;
+ }
+
+ nsresult rv =
+ xpc->GetWrappedNativeOfJSObject(aCtx, obj, getter_AddRefs(wrapper));
+ if (NS_FAILED(rv)) {
+ ::JS_ReportErrorASCII(aCtx, "mozIStorageStatement::step() could not obtain native statement");
+ return false;
+ }
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<mozIStorageStatement> isStatement(
+ do_QueryInterface(wrapper->Native())
+ );
+ NS_ASSERTION(isStatement, "How is this not a statement?!");
+ }
+#endif
+
+ Statement *stmt = static_cast<Statement *>(
+ static_cast<mozIStorageStatement *>(wrapper->Native())
+ );
+
+ bool hasMore = false;
+ rv = stmt->ExecuteStep(&hasMore);
+ if (NS_SUCCEEDED(rv) && !hasMore) {
+ _vp->setBoolean(false);
+ (void)stmt->Reset();
+ return true;
+ }
+
+ if (NS_FAILED(rv)) {
+ ::JS_ReportErrorASCII(aCtx, "mozIStorageStatement::step() returned an error");
+ return false;
+ }
+
+ _vp->setBoolean(hasMore);
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// StatementJSHelper
+
+nsresult
+StatementJSHelper::getRow(Statement *aStatement,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ JS::Value *_row)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv;
+
+#ifdef DEBUG
+ int32_t state;
+ (void)aStatement->GetState(&state);
+ NS_ASSERTION(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_EXECUTING,
+ "Invalid state to get the row object - all calls will fail!");
+#endif
+
+ if (!aStatement->mStatementRowHolder) {
+ JS::RootedObject scope(aCtx, aScopeObj);
+ nsCOMPtr<mozIStorageStatementRow> row(new StatementRow(aStatement));
+ NS_ENSURE_TRUE(row, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
+ nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
+ rv = xpc->WrapNativeHolder(
+ aCtx,
+ ::JS_GetGlobalForObject(aCtx, scope),
+ row,
+ NS_GET_IID(mozIStorageStatementRow),
+ getter_AddRefs(holder)
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ RefPtr<StatementRowHolder> rowHolder = new StatementRowHolder(holder);
+ aStatement->mStatementRowHolder =
+ new nsMainThreadPtrHolder<nsIXPConnectJSObjectHolder>(rowHolder);
+ }
+
+ JS::Rooted<JSObject*> obj(aCtx);
+ obj = aStatement->mStatementRowHolder->GetJSObject();
+ NS_ENSURE_STATE(obj);
+
+ _row->setObject(*obj);
+ return NS_OK;
+}
+
+nsresult
+StatementJSHelper::getParams(Statement *aStatement,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ JS::Value *_params)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv;
+
+#ifdef DEBUG
+ int32_t state;
+ (void)aStatement->GetState(&state);
+ NS_ASSERTION(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_READY,
+ "Invalid state to get the params object - all calls will fail!");
+#endif
+
+ if (!aStatement->mStatementParamsHolder) {
+ JS::RootedObject scope(aCtx, aScopeObj);
+ nsCOMPtr<mozIStorageStatementParams> params =
+ new StatementParams(aStatement);
+ NS_ENSURE_TRUE(params, NS_ERROR_OUT_OF_MEMORY);
+
+ nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
+ nsCOMPtr<nsIXPConnect> xpc(Service::getXPConnect());
+ rv = xpc->WrapNativeHolder(
+ aCtx,
+ ::JS_GetGlobalForObject(aCtx, scope),
+ params,
+ NS_GET_IID(mozIStorageStatementParams),
+ getter_AddRefs(holder)
+ );
+ NS_ENSURE_SUCCESS(rv, rv);
+ RefPtr<StatementParamsHolder> paramsHolder =
+ new StatementParamsHolder(holder);
+ aStatement->mStatementParamsHolder =
+ new nsMainThreadPtrHolder<nsIXPConnectJSObjectHolder>(paramsHolder);
+ }
+
+ JS::Rooted<JSObject*> obj(aCtx);
+ obj = aStatement->mStatementParamsHolder->GetJSObject();
+ NS_ENSURE_STATE(obj);
+
+ _params->setObject(*obj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType) StatementJSHelper::AddRef() { return 2; }
+NS_IMETHODIMP_(MozExternalRefCountType) StatementJSHelper::Release() { return 1; }
+NS_INTERFACE_MAP_BEGIN(StatementJSHelper)
+ NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIXPCScriptable
+
+#define XPC_MAP_CLASSNAME StatementJSHelper
+#define XPC_MAP_QUOTED_CLASSNAME "StatementJSHelper"
+#define XPC_MAP_WANT_GETPROPERTY
+#define XPC_MAP_WANT_RESOLVE
+#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE
+#include "xpc_map_end.h"
+
+NS_IMETHODIMP
+StatementJSHelper::GetProperty(nsIXPConnectWrappedNative *aWrapper,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ jsid aId,
+ JS::Value *_result,
+ bool *_retval)
+{
+ if (!JSID_IS_STRING(aId))
+ return NS_OK;
+
+ JS::Rooted<JSObject*> scope(aCtx, aScopeObj);
+ JS::Rooted<jsid> id(aCtx, aId);
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<mozIStorageStatement> isStatement(
+ do_QueryInterface(aWrapper->Native()));
+ NS_ASSERTION(isStatement, "How is this not a statement?!");
+ }
+#endif
+
+ Statement *stmt = static_cast<Statement *>(
+ static_cast<mozIStorageStatement *>(aWrapper->Native())
+ );
+
+ JSFlatString *str = JSID_TO_FLAT_STRING(id);
+ if (::JS_FlatStringEqualsAscii(str, "row"))
+ return getRow(stmt, aCtx, scope, _result);
+
+ if (::JS_FlatStringEqualsAscii(str, "params"))
+ return getParams(stmt, aCtx, scope, _result);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+StatementJSHelper::Resolve(nsIXPConnectWrappedNative *aWrapper,
+ JSContext *aCtx, JSObject *aScopeObj,
+ jsid aId, bool *aResolvedp,
+ bool *_retval)
+{
+ if (!JSID_IS_STRING(aId))
+ return NS_OK;
+
+ JS::RootedObject scope(aCtx, aScopeObj);
+ if (::JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(aId), "step")) {
+ *_retval = ::JS_DefineFunction(aCtx, scope, "step", stepFunc,
+ 0, JSPROP_RESOLVING) != nullptr;
+ *aResolvedp = true;
+ return NS_OK;
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// StatementJSObjectHolder
+
+NS_IMPL_ISUPPORTS(StatementJSObjectHolder, nsIXPConnectJSObjectHolder);
+
+JSObject*
+StatementJSObjectHolder::GetJSObject()
+{
+ return mHolder->GetJSObject();
+}
+
+StatementJSObjectHolder::StatementJSObjectHolder(nsIXPConnectJSObjectHolder* aHolder)
+ : mHolder(aHolder)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mHolder);
+}
+
+StatementParamsHolder::~StatementParamsHolder()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // We are considered dead at this point, so any wrappers for row or params
+ // need to lose their reference to the statement.
+ nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder);
+ nsCOMPtr<mozIStorageStatementParams> iObj = do_QueryWrappedNative(wrapper);
+ StatementParams *obj = static_cast<StatementParams *>(iObj.get());
+ obj->mStatement = nullptr;
+}
+
+StatementRowHolder::~StatementRowHolder()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // We are considered dead at this point, so any wrappers for row or params
+ // need to lose their reference to the statement.
+ nsCOMPtr<nsIXPConnectWrappedNative> wrapper = do_QueryInterface(mHolder);
+ nsCOMPtr<mozIStorageStatementRow> iObj = do_QueryWrappedNative(wrapper);
+ StatementRow *obj = static_cast<StatementRow *>(iObj.get());
+ obj->mStatement = nullptr;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageStatementJSHelper.h b/components/storage/src/mozStorageStatementJSHelper.h
new file mode 100644
index 000000000..c7948bfa8
--- /dev/null
+++ b/components/storage/src/mozStorageStatementJSHelper.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZSTORAGESTATEMENTJSHELPER_H
+#define MOZSTORAGESTATEMENTJSHELPER_H
+
+#include "nsIXPCScriptable.h"
+#include "nsIXPConnect.h"
+
+class Statement;
+
+namespace mozilla {
+namespace storage {
+
+class StatementJSHelper : public nsIXPCScriptable
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIXPCSCRIPTABLE
+
+private:
+ nsresult getRow(Statement *, JSContext *, JSObject *, JS::Value *);
+ nsresult getParams(Statement *, JSContext *, JSObject *, JS::Value *);
+};
+
+/**
+ * Wrappers used to clean up the references JS helpers hold to the statement.
+ * For cycle-avoidance reasons they do not hold reference-counted references,
+ * so it is important we do this.
+ */
+class StatementJSObjectHolder : public nsIXPConnectJSObjectHolder
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIXPCONNECTJSOBJECTHOLDER
+
+ explicit StatementJSObjectHolder(nsIXPConnectJSObjectHolder* aHolder);
+
+protected:
+ virtual ~StatementJSObjectHolder() {};
+ nsCOMPtr<nsIXPConnectJSObjectHolder> mHolder;
+};
+
+class StatementParamsHolder final: public StatementJSObjectHolder {
+public:
+ explicit StatementParamsHolder(nsIXPConnectJSObjectHolder* aHolder)
+ : StatementJSObjectHolder(aHolder) {
+ }
+
+private:
+ virtual ~StatementParamsHolder();
+};
+
+class StatementRowHolder final: public StatementJSObjectHolder {
+public:
+ explicit StatementRowHolder(nsIXPConnectJSObjectHolder* aHolder)
+ : StatementJSObjectHolder(aHolder) {
+ }
+
+private:
+ virtual ~StatementRowHolder();
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif // MOZSTORAGESTATEMENTJSHELPER_H
diff --git a/components/storage/src/mozStorageStatementParams.cpp b/components/storage/src/mozStorageStatementParams.cpp
new file mode 100644
index 000000000..de4ace78a
--- /dev/null
+++ b/components/storage/src/mozStorageStatementParams.cpp
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsJSUtils.h"
+#include "nsMemory.h"
+#include "nsString.h"
+
+#include "jsapi.h"
+
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageStatementParams.h"
+#include "mozIStorageStatement.h"
+
+#include "xpc_make_class.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// StatementParams
+
+StatementParams::StatementParams(mozIStorageStatement *aStatement) :
+ mStatement(aStatement),
+ mParamCount(0)
+{
+ NS_ASSERTION(mStatement != nullptr, "mStatement is null");
+ (void)mStatement->GetParameterCount(&mParamCount);
+}
+
+NS_IMPL_ISUPPORTS(
+ StatementParams,
+ mozIStorageStatementParams,
+ nsIXPCScriptable
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIXPCScriptable
+
+#define XPC_MAP_CLASSNAME StatementParams
+#define XPC_MAP_QUOTED_CLASSNAME "StatementParams"
+#define XPC_MAP_WANT_SETPROPERTY
+#define XPC_MAP_WANT_NEWENUMERATE
+#define XPC_MAP_WANT_RESOLVE
+#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE
+#include "xpc_map_end.h"
+
+NS_IMETHODIMP
+StatementParams::SetProperty(nsIXPConnectWrappedNative *aWrapper,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ jsid aId,
+ JS::Value *_vp,
+ bool *_retval)
+{
+ NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
+
+ if (JSID_IS_INT(aId)) {
+ int idx = JSID_TO_INT(aId);
+
+ nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp));
+ NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED);
+ nsresult rv = mStatement->BindByIndex(idx, variant);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else if (JSID_IS_STRING(aId)) {
+ JSString *str = JSID_TO_STRING(aId);
+ nsAutoJSString autoStr;
+ if (!autoStr.init(aCtx, str)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ConvertUTF16toUTF8 name(autoStr);
+
+ // check to see if there's a parameter with this name
+ nsCOMPtr<nsIVariant> variant(convertJSValToVariant(aCtx, *_vp));
+ NS_ENSURE_TRUE(variant, NS_ERROR_UNEXPECTED);
+ nsresult rv = mStatement->BindByName(name, variant);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StatementParams::NewEnumerate(nsIXPConnectWrappedNative *aWrapper,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ JS::AutoIdVector &aProperties,
+ bool *_retval)
+{
+ NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
+ JS::RootedObject scope(aCtx, aScopeObj);
+
+ if (!aProperties.reserve(mParamCount)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < mParamCount; i++) {
+ // Get the name of our parameter.
+ nsAutoCString name;
+ nsresult rv = mStatement->GetParameterName(i, name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // But drop the first character, which is going to be a ':'.
+ JS::RootedString jsname(aCtx, ::JS_NewStringCopyN(aCtx, &(name.get()[1]),
+ name.Length() - 1));
+ NS_ENSURE_TRUE(jsname, NS_ERROR_OUT_OF_MEMORY);
+
+ // Set our name.
+ JS::Rooted<jsid> id(aCtx);
+ if (!::JS_StringToId(aCtx, jsname, &id)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ aProperties.infallibleAppend(id);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StatementParams::Resolve(nsIXPConnectWrappedNative *aWrapper,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ jsid aId,
+ bool *resolvedp,
+ bool *_retval)
+{
+ NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
+ // We do not throw at any point after this unless our index is out of range
+ // because we want to allow the prototype chain to be checked for the
+ // property.
+
+ JS::RootedObject scope(aCtx, aScopeObj);
+ JS::RootedId id(aCtx, aId);
+ bool resolved = false;
+ bool ok = true;
+ if (JSID_IS_INT(id)) {
+ uint32_t idx = JSID_TO_INT(id);
+
+ // Ensure that our index is within range. We do not care about the
+ // prototype chain being checked here.
+ if (idx >= mParamCount)
+ return NS_ERROR_INVALID_ARG;
+
+ ok = ::JS_DefineElement(aCtx, scope, idx, JS::UndefinedHandleValue,
+ JSPROP_ENUMERATE | JSPROP_RESOLVING);
+ resolved = true;
+ }
+ else if (JSID_IS_STRING(id)) {
+ JSString *str = JSID_TO_STRING(id);
+ nsAutoJSString autoStr;
+ if (!autoStr.init(aCtx, str)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Check to see if there's a parameter with this name, and if not, let
+ // the rest of the prototype chain be checked.
+ NS_ConvertUTF16toUTF8 name(autoStr);
+ uint32_t idx;
+ nsresult rv = mStatement->GetParameterIndex(name, &idx);
+ if (NS_SUCCEEDED(rv)) {
+ ok = ::JS_DefinePropertyById(aCtx, scope, id, JS::UndefinedHandleValue,
+ JSPROP_ENUMERATE | JSPROP_RESOLVING);
+ resolved = true;
+ }
+ }
+
+ *_retval = ok;
+ *resolvedp = resolved && ok;
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageStatementParams.h b/components/storage/src/mozStorageStatementParams.h
new file mode 100644
index 000000000..2627f8aa1
--- /dev/null
+++ b/components/storage/src/mozStorageStatementParams.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZSTORAGESTATEMENTPARAMS_H
+#define MOZSTORAGESTATEMENTPARAMS_H
+
+#include "mozIStorageStatementParams.h"
+#include "nsIXPCScriptable.h"
+#include "mozilla/Attributes.h"
+
+class mozIStorageStatement;
+
+namespace mozilla {
+namespace storage {
+
+class StatementParams final : public mozIStorageStatementParams
+ , public nsIXPCScriptable
+{
+public:
+ explicit StatementParams(mozIStorageStatement *aStatement);
+
+ // interfaces
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGESTATEMENTPARAMS
+ NS_DECL_NSIXPCSCRIPTABLE
+
+protected:
+ ~StatementParams() {}
+
+ mozIStorageStatement *mStatement;
+ uint32_t mParamCount;
+
+ friend class StatementParamsHolder;
+ friend class StatementRowHolder;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif /* MOZSTORAGESTATEMENTPARAMS_H */
diff --git a/components/storage/src/mozStorageStatementRow.cpp b/components/storage/src/mozStorageStatementRow.cpp
new file mode 100644
index 000000000..6ace04bbf
--- /dev/null
+++ b/components/storage/src/mozStorageStatementRow.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMemory.h"
+#include "nsString.h"
+
+#include "mozStorageStatementRow.h"
+#include "mozStorageStatement.h"
+
+#include "jsapi.h"
+
+#include "xpc_make_class.h"
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// StatementRow
+
+StatementRow::StatementRow(Statement *aStatement)
+: mStatement(aStatement)
+{
+}
+
+NS_IMPL_ISUPPORTS(
+ StatementRow,
+ mozIStorageStatementRow,
+ nsIXPCScriptable
+)
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIXPCScriptable
+
+#define XPC_MAP_CLASSNAME StatementRow
+#define XPC_MAP_QUOTED_CLASSNAME "StatementRow"
+#define XPC_MAP_WANT_GETPROPERTY
+#define XPC_MAP_WANT_RESOLVE
+#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE
+#include "xpc_map_end.h"
+
+NS_IMETHODIMP
+StatementRow::GetProperty(nsIXPConnectWrappedNative *aWrapper,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ jsid aId,
+ JS::Value *_vp,
+ bool *_retval)
+{
+ NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
+
+ JS::RootedObject scope(aCtx, aScopeObj);
+ if (JSID_IS_STRING(aId)) {
+ ::JSAutoByteString idBytes(aCtx, JSID_TO_STRING(aId));
+ NS_ENSURE_TRUE(!!idBytes, NS_ERROR_OUT_OF_MEMORY);
+ nsDependentCString jsid(idBytes.ptr());
+
+ uint32_t idx;
+ nsresult rv = mStatement->GetColumnIndex(jsid, &idx);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t type;
+ rv = mStatement->GetTypeOfIndex(idx, &type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (type == mozIStorageValueArray::VALUE_TYPE_INTEGER ||
+ type == mozIStorageValueArray::VALUE_TYPE_FLOAT) {
+ double dval;
+ rv = mStatement->GetDouble(idx, &dval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *_vp = ::JS_NumberValue(dval);
+ }
+ else if (type == mozIStorageValueArray::VALUE_TYPE_TEXT) {
+ uint32_t bytes;
+ const char16_t *sval = reinterpret_cast<const char16_t *>(
+ static_cast<mozIStorageStatement *>(mStatement)->
+ AsSharedWString(idx, &bytes)
+ );
+ JSString *str = ::JS_NewUCStringCopyN(aCtx, sval, bytes / 2);
+ if (!str) {
+ *_retval = false;
+ return NS_OK;
+ }
+ _vp->setString(str);
+ }
+ else if (type == mozIStorageValueArray::VALUE_TYPE_BLOB) {
+ uint32_t length;
+ const uint8_t *blob = static_cast<mozIStorageStatement *>(mStatement)->
+ AsSharedBlob(idx, &length);
+ JSObject *obj = ::JS_NewArrayObject(aCtx, length);
+ if (!obj) {
+ *_retval = false;
+ return NS_OK;
+ }
+ _vp->setObject(*obj);
+
+ // Copy the blob over to the JS array.
+ for (uint32_t i = 0; i < length; i++) {
+ if (!::JS_DefineElement(aCtx, scope, i, blob[i], JSPROP_ENUMERATE)) {
+ *_retval = false;
+ return NS_OK;
+ }
+ }
+ }
+ else if (type == mozIStorageValueArray::VALUE_TYPE_NULL) {
+ _vp->setNull();
+ }
+ else {
+ NS_ERROR("unknown column type returned, what's going on?");
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StatementRow::Resolve(nsIXPConnectWrappedNative *aWrapper,
+ JSContext *aCtx,
+ JSObject *aScopeObj,
+ jsid aId,
+ bool *aResolvedp,
+ bool *_retval)
+{
+ JS::Rooted<JSObject*> scopeObj(aCtx, aScopeObj);
+
+ NS_ENSURE_TRUE(mStatement, NS_ERROR_NOT_INITIALIZED);
+ // We do not throw at any point after this because we want to allow the
+ // prototype chain to be checked for the property.
+
+ if (JSID_IS_STRING(aId)) {
+ ::JSAutoByteString idBytes(aCtx, JSID_TO_STRING(aId));
+ NS_ENSURE_TRUE(!!idBytes, NS_ERROR_OUT_OF_MEMORY);
+ nsDependentCString name(idBytes.ptr());
+
+ uint32_t idx;
+ nsresult rv = mStatement->GetColumnIndex(name, &idx);
+ if (NS_FAILED(rv)) {
+ // It's highly likely that the name doesn't exist, so let the JS engine
+ // check the prototype chain and throw if that doesn't have the property
+ // either.
+ *aResolvedp = false;
+ return NS_OK;
+ }
+
+ JS::Rooted<jsid> id(aCtx, aId);
+ *_retval = ::JS_DefinePropertyById(aCtx, scopeObj, id, JS::UndefinedHandleValue,
+ JSPROP_RESOLVING);
+ *aResolvedp = true;
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+} // namespace storage
+} // namespace mozilla
diff --git a/components/storage/src/mozStorageStatementRow.h b/components/storage/src/mozStorageStatementRow.h
new file mode 100644
index 000000000..ea9e40348
--- /dev/null
+++ b/components/storage/src/mozStorageStatementRow.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZSTORAGESTATEMENTROW_H
+#define MOZSTORAGESTATEMENTROW_H
+
+#include "mozIStorageStatementRow.h"
+#include "nsIXPCScriptable.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace storage {
+
+class Statement;
+
+class StatementRow final : public mozIStorageStatementRow
+ , public nsIXPCScriptable
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGESTATEMENTROW
+ NS_DECL_NSIXPCSCRIPTABLE
+
+ explicit StatementRow(Statement *aStatement);
+protected:
+
+ ~StatementRow() {}
+
+ Statement *mStatement;
+
+ friend class StatementRowHolder;
+};
+
+} // namespace storage
+} // namespace mozilla
+
+#endif /* MOZSTORAGESTATEMENTROW_H */
diff --git a/components/storage/src/storage.h b/components/storage/src/storage.h
new file mode 100644
index 000000000..ec8037983
--- /dev/null
+++ b/components/storage/src/storage.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_h_
+#define mozilla_storage_h_
+
+////////////////////////////////////////////////////////////////////////////////
+//// Public Interfaces
+
+#include "mozStorageCID.h"
+#include "mozIStorageAggregateFunction.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageError.h"
+#include "mozIStorageFunction.h"
+#include "mozIStoragePendingStatement.h"
+#include "mozIStorageProgressHandler.h"
+#include "mozIStorageResultSet.h"
+#include "mozIStorageRow.h"
+#include "mozIStorageService.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageStatementCallback.h"
+#include "mozIStorageBindingParamsArray.h"
+#include "mozIStorageBindingParams.h"
+#include "mozIStorageVacuumParticipant.h"
+#include "mozIStorageCompletionCallback.h"
+#include "mozIStorageAsyncStatement.h"
+#include "mozIStorageAsyncConnection.h"
+
+////////////////////////////////////////////////////////////////////////////////
+//// Native Language Helpers
+
+#include "mozStorageHelper.h"
+#include "mozilla/storage/StatementCache.h"
+#include "mozilla/storage/Variant.h"
+
+#endif // mozilla_storage_h_
diff --git a/components/storage/src/variantToSQLiteT_impl.h b/components/storage/src/variantToSQLiteT_impl.h
new file mode 100644
index 000000000..5e55a261f
--- /dev/null
+++ b/components/storage/src/variantToSQLiteT_impl.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Note: we are already in the namepace mozilla::storage
+
+// Note 2: whoever #includes this file must provide implementations of
+// sqlite3_T_* prior.
+
+////////////////////////////////////////////////////////////////////////////////
+//// variantToSQLiteT Implementation
+
+template <typename T>
+int
+variantToSQLiteT(T aObj,
+ nsIVariant *aValue)
+{
+ // Allow to return nullptr not wrapped to nsIVariant for speed.
+ if (!aValue)
+ return sqlite3_T_null(aObj);
+
+ uint16_t valueType;
+ aValue->GetDataType(&valueType);
+ switch (valueType) {
+ case nsIDataType::VTYPE_INT8:
+ case nsIDataType::VTYPE_INT16:
+ case nsIDataType::VTYPE_INT32:
+ case nsIDataType::VTYPE_UINT8:
+ case nsIDataType::VTYPE_UINT16:
+ {
+ int32_t value;
+ nsresult rv = aValue->GetAsInt32(&value);
+ NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH);
+ return sqlite3_T_int(aObj, value);
+ }
+ case nsIDataType::VTYPE_UINT32: // Try to preserve full range
+ case nsIDataType::VTYPE_INT64:
+ // Data loss possible, but there is no unsigned types in SQLite
+ case nsIDataType::VTYPE_UINT64:
+ {
+ int64_t value;
+ nsresult rv = aValue->GetAsInt64(&value);
+ NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH);
+ return sqlite3_T_int64(aObj, value);
+ }
+ case nsIDataType::VTYPE_FLOAT:
+ case nsIDataType::VTYPE_DOUBLE:
+ {
+ double value;
+ nsresult rv = aValue->GetAsDouble(&value);
+ NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH);
+ return sqlite3_T_double(aObj, value);
+ }
+ case nsIDataType::VTYPE_BOOL:
+ {
+ bool value;
+ nsresult rv = aValue->GetAsBool(&value);
+ NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH);
+ return sqlite3_T_int(aObj, value ? 1 : 0);
+ }
+ case nsIDataType::VTYPE_CHAR:
+ case nsIDataType::VTYPE_CHAR_STR:
+ case nsIDataType::VTYPE_STRING_SIZE_IS:
+ case nsIDataType::VTYPE_UTF8STRING:
+ case nsIDataType::VTYPE_CSTRING:
+ {
+ nsAutoCString value;
+ // GetAsAUTF8String should never perform conversion when coming from
+ // 8-bit string types, and thus can accept strings with arbitrary encoding
+ // (including UTF8 and ASCII).
+ nsresult rv = aValue->GetAsAUTF8String(value);
+ NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH);
+ return sqlite3_T_text(aObj, value);
+ }
+ case nsIDataType::VTYPE_WCHAR:
+ case nsIDataType::VTYPE_DOMSTRING:
+ case nsIDataType::VTYPE_WCHAR_STR:
+ case nsIDataType::VTYPE_WSTRING_SIZE_IS:
+ case nsIDataType::VTYPE_ASTRING:
+ {
+ nsAutoString value;
+ // GetAsAString does proper conversion to UCS2 from all string-like types.
+ // It can be used universally without problems (unless someone implements
+ // their own variant, but that's their problem).
+ nsresult rv = aValue->GetAsAString(value);
+ NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH);
+ return sqlite3_T_text16(aObj, value);
+ }
+ case nsIDataType::VTYPE_VOID:
+ case nsIDataType::VTYPE_EMPTY:
+ case nsIDataType::VTYPE_EMPTY_ARRAY:
+ return sqlite3_T_null(aObj);
+ case nsIDataType::VTYPE_ARRAY:
+ {
+ uint16_t arrayType;
+ nsIID iid;
+ uint32_t count;
+ void *data;
+ nsresult rv = aValue->GetAsArray(&arrayType, &iid, &count, &data);
+ NS_ENSURE_SUCCESS(rv, SQLITE_MISMATCH);
+
+ // Check to make sure it's a supported type.
+ NS_ASSERTION(arrayType == nsIDataType::VTYPE_UINT8,
+ "Invalid type passed! You may leak!");
+ if (arrayType != nsIDataType::VTYPE_UINT8) {
+ // Technically this could leak with certain data types, but somebody was
+ // being stupid passing us this anyway.
+ free(data);
+ return SQLITE_MISMATCH;
+ }
+
+ // Finally do our thing. The function should free the array accordingly!
+ int rc = sqlite3_T_blob(aObj, data, count);
+ return rc;
+ }
+ // Maybe, it'll be possible to convert these
+ // in future too.
+ case nsIDataType::VTYPE_ID:
+ case nsIDataType::VTYPE_INTERFACE:
+ case nsIDataType::VTYPE_INTERFACE_IS:
+ default:
+ return SQLITE_MISMATCH;
+ }
+ return SQLITE_OK;
+}
diff --git a/components/storage/style.txt b/components/storage/style.txt
new file mode 100644
index 000000000..03652e606
--- /dev/null
+++ b/components/storage/style.txt
@@ -0,0 +1,141 @@
+Storage Module Style Guidelines
+
+These guidelines should be followed for all new code in this module. Reviewers
+will be enforcing them, so please obey them!
+
+* All code should be contained within the namespace mozilla::storage at a
+ minimum. The use of namespaces is strongly encouraged.
+
+* All functions being called in the global namespace should be prefixed with
+ "::" to indicate that they are in the global namespace.
+
+* The indentation level to use in source code is two spaces. No tabs, please!
+
+* All files should have the following emacs and vim mode lines:
+ -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+
+* All functions that are not XPCOM should start with a lowercase letter.
+
+* Function arguments that are not out parameters should be prefixed with a (for
+ pArameter), and use CamelCase.
+
+* Function arguments that are out parameters should be prefixed with an
+ underscore and have a descriptive name.
+
+* Function declarations should include javadoc style comments.
+
+* Javadoc @param tags should have the parameter description start on a new line
+ aligned with the variable name. See the example below.
+
+* Javadoc @return (note: non-plural) continuation lines should be lined up with
+ the initial comment. See the example below.
+
+* Javadoc @throws, like @param, should have the exception type on the same line
+ as the @throws and the description on a new line indented to line up with
+ the type of the exception.
+
+* For function implementations, each argument should be on its own line.
+
+* All variables should use camelCase.
+
+* The use of bool is encouraged whenever the variable does not have the
+ potential to go through xpconnect.
+
+* For pointer variable types, include a space after the type before the asterisk
+ and no space between the asterisk and variable name.
+
+* If any part of an if-else block requires braces, all blocks need braces.
+
+* Every else should be on a newline after a brace.
+
+* Bracing should start on the line after a function and class definition. This
+ goes for JavaScript code as well as C++ code.
+
+* If a return value is not going to be checked, the return value should be
+ explicitly casted to void (C style cast).
+
+
+BIG EXAMPLE:
+
+*** Header ***
+
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_storage_FILENAME_h_
+#define mozilla_storage_FILENAME_h_
+
+namespace mozilla {
+namespace storage {
+
+class Foo : public Bar
+ , public Baz
+{
+public:
+ /**
+ * Brief function summary.
+ *
+ * @param aArg1
+ * Description description description description description etc etc
+ * next line of description.
+ * @param aArg2
+ * Description description description.
+ * @return Description description description description description etc etc
+ * next line of description.
+ *
+ * @throws NS_ERROR_FAILURE
+ * Okay, so this is for JavaScript code, but you probably get the
+ * idea.
+ */
+ int chew(int aArg1, int aArg2);
+};
+
+} // storage
+} // mozilla
+
+#endif // mozilla_storage_FILENAME_h_
+
+
+*** Implementation ***
+
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+NS_IMPL_ISUPPORTS(
+ Foo
+, IBar
+, IBaz
+)
+
+Foo::Foo(
+ LongArgumentLineThatWouldOtherwiseOverflow *aArgument1
+)
+: mField1(0)
+, mField2(0)
+{
+ someMethodWithLotsOfParamsOrJustLongParameters(
+ mLongFieldNameThatIsJustified,
+ mMaybeThisOneIsLessJustifiedButBoyIsItLong,
+ 15
+ );
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Separate sections of the file like this
+
+int
+Foo::chew(int aArg1, int aArg2)
+{
+ (void)functionReturningAnIgnoredValue();
+
+ ::functionFromGlobalNamespaceWithVoidReturnValue();
+
+ return 0;
+}
diff --git a/components/terminator/moz.build b/components/terminator/moz.build
new file mode 100644
index 000000000..59dcb1a09
--- /dev/null
+++ b/components/terminator/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXPORTS += ['nsTerminator.h']
+
+SOURCES += ['nsTerminator.cpp']
+
+EXTRA_COMPONENTS += [
+ 'terminator.manifest',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/components/terminator/nsTerminator.cpp b/components/terminator/nsTerminator.cpp
new file mode 100644
index 000000000..045703bb2
--- /dev/null
+++ b/components/terminator/nsTerminator.cpp
@@ -0,0 +1,303 @@
+/* -*- 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/. */
+
+/**
+ * A watchdog designed to terminate shutdown if it lasts too long.
+ *
+ * This watchdog is designed as a worst-case problem container for the
+ * common case in which Firefox just won't shutdown.
+ *
+ * We spawn a thread during quit-application. If any of the shutdown
+ * steps takes more than n milliseconds (63000 by default), kill the
+ * process as fast as possible, without any cleanup.
+ */
+
+#include "nsTerminator.h"
+
+#include "prthread.h"
+#include "prmon.h"
+#include "plstr.h"
+#include "prio.h"
+
+#include "nsString.h"
+#include "nsServiceManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+
+#ifdef XP_WIN
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/MemoryChecking.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+
+// Normally, the number of milliseconds that AsyncShutdown waits until
+// it decides to crash is specified as a preference. We use the
+// following value as a fallback if for some reason the preference is
+// absent.
+#define FALLBACK_ASYNCSHUTDOWN_CRASH_AFTER_MS 60000
+
+// Additional number of milliseconds to wait until we decide to exit
+// forcefully.
+#define ADDITIONAL_WAIT_BEFORE_CRASH_MS 3000
+
+namespace mozilla {
+
+namespace {
+
+// Utility function: create a thread that is non-joinable,
+// does not prevent the process from terminating, is never
+// cooperatively scheduled, and uses a default stack size.
+PRThread* CreateSystemThread(void (*start)(void* arg),
+ void* arg)
+{
+ PRThread* thread = PR_CreateThread(
+ PR_SYSTEM_THREAD, /* This thread will not prevent the process from terminating */
+ start,
+ arg,
+ PR_PRIORITY_LOW,
+ PR_GLOBAL_THREAD /* Make sure that the thread is never cooperatively scheduled */,
+ PR_UNJOINABLE_THREAD,
+ 0 /* Use default stack size */
+ );
+ MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(thread); // This pointer will never be deallocated.
+ return thread;
+}
+
+
+////////////////////////////////////////////
+//
+// The watchdog
+//
+// This nspr thread is in charge of crashing the process if any stage of shutdown
+// lasts more than some predefined duration. As a side-effect, it measures the
+// duration of each stage of shutdown.
+//
+
+// The heartbeat of the operation.
+//
+// Main thread:
+//
+// * Whenever a shutdown step has been completed, the main thread
+// swaps gHeartbeat to 0 to mark that the shutdown process is still
+// progressing. The value swapped away indicates the number of ticks
+// it took for the shutdown step to advance.
+//
+// Watchdog thread:
+//
+// * Every tick, the watchdog thread increments gHearbeat atomically.
+//
+// A note about precision:
+// Since gHeartbeat is generally reset to 0 between two ticks, this means
+// that gHeartbeat stays at 0 less than one tick. Consequently, values
+// extracted from gHeartbeat must be considered rounded up.
+Atomic<uint32_t> gHeartbeat(0);
+
+struct Options {
+ /**
+ * How many ticks before we should crash the process.
+ */
+ uint32_t crashAfterTicks;
+};
+
+/**
+ * Entry point for the watchdog thread
+ */
+void
+RunWatchdog(void* arg)
+{
+ PR_SetCurrentThreadName("Shutdown Hang Terminator");
+
+ // Let's copy and deallocate options, that's one less leak to worry
+ // about.
+ UniquePtr<Options> options((Options*)arg);
+ uint32_t crashAfterTicks = options->crashAfterTicks;
+ options = nullptr;
+
+ const uint32_t timeToLive = crashAfterTicks;
+ while (true) {
+ //
+ // We do not want to sleep for the entire duration,
+ // as putting the computer to sleep would suddenly
+ // cause us to timeout on wakeup.
+ //
+ // Rather, we prefer sleeping for at most 1 second
+ // at a time. If the computer sleeps then wakes up,
+ // we have lost at most one second, which is much
+ // more reasonable.
+ //
+#if defined(XP_WIN)
+ Sleep(1000 /* ms */);
+#else
+ usleep(1000000 /* usec */);
+#endif
+
+ if (gHeartbeat++ < timeToLive) {
+ continue;
+ }
+
+ // Shutdown is apparently dead. Crash the process.
+ MOZ_CRASH("Shutdown too long, probably frozen, causing a crash.");
+ }
+}
+
+/**
+ * A step during shutdown.
+ *
+ * Shutdown is divided in steps, which all map to an observer
+ * notification. The duration of a step is defined as the number of
+ * ticks between the time we receive a notification and the next one.
+ */
+struct ShutdownStep
+{
+ char const* const mTopic;
+ int mTicks;
+
+ constexpr explicit ShutdownStep(const char *const topic)
+ : mTopic(topic)
+ , mTicks(-1)
+ {}
+
+};
+
+static ShutdownStep sShutdownSteps[] = {
+ ShutdownStep("quit-application"),
+ ShutdownStep("profile-change-teardown"),
+ ShutdownStep("profile-before-change"),
+ ShutdownStep("xpcom-will-shutdown"),
+ ShutdownStep("xpcom-shutdown"),
+};
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(nsTerminator, nsIObserver)
+
+nsTerminator::nsTerminator()
+ : mInitialized(false)
+ , mCurrentStep(-1)
+{
+}
+
+// During startup, register as an observer for all interesting topics.
+nsresult
+nsTerminator::SelfInit()
+{
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (!os) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ for (size_t i = 0; i < ArrayLength(sShutdownSteps); ++i) {
+ DebugOnly<nsresult> rv = os->AddObserver(this, sShutdownSteps[i].mTopic, false);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AddObserver failed");
+ }
+
+ return NS_OK;
+}
+
+// Actually launch the thread. This takes place at the first sign of shutdown.
+void
+nsTerminator::Start()
+{
+ MOZ_ASSERT(!mInitialized);
+ StartWatchdog();
+ mInitialized = true;
+}
+
+// Prepare, allocate and start the watchdog thread.
+// By design, it will never finish, nor be deallocated.
+void
+nsTerminator::StartWatchdog()
+{
+ int32_t crashAfterMS =
+ Preferences::GetInt("toolkit.asyncshutdown.crash_timeout",
+ FALLBACK_ASYNCSHUTDOWN_CRASH_AFTER_MS);
+ // Ignore negative values
+ if (crashAfterMS <= 0) {
+ crashAfterMS = FALLBACK_ASYNCSHUTDOWN_CRASH_AFTER_MS;
+ }
+
+ // Add a little padding, to ensure that we do not crash before
+ // AsyncShutdown.
+ if (crashAfterMS > INT32_MAX - ADDITIONAL_WAIT_BEFORE_CRASH_MS) {
+ // Defend against overflow
+ crashAfterMS = INT32_MAX;
+ } else {
+ crashAfterMS += ADDITIONAL_WAIT_BEFORE_CRASH_MS;
+ }
+
+ UniquePtr<Options> options(new Options());
+#ifdef XP_SOLARIS
+ const PRIntervalTime ticksDuration = PR_MillisecondsToInterval(1000);
+#else
+ const PRIntervalTime ticksDuration = 1000;
+#endif
+ options->crashAfterTicks = crashAfterMS / ticksDuration;
+
+ DebugOnly<PRThread*> watchdogThread = CreateSystemThread(RunWatchdog,
+ options.release());
+ MOZ_ASSERT(watchdogThread);
+}
+
+NS_IMETHODIMP
+nsTerminator::Observe(nsISupports *, const char *aTopic, const char16_t *)
+{
+ if (strcmp(aTopic, "profile-after-change") == 0) {
+ return SelfInit();
+ }
+
+ // Other notifications are shutdown-related.
+
+ // As we have seen examples in the wild of shutdown notifications
+ // not being sent (or not being sent in the expected order), we do
+ // not assume a specific order.
+ if (!mInitialized) {
+ Start();
+ }
+
+ UpdateHeartbeat(aTopic);
+
+ // Perform a little cleanup
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ MOZ_RELEASE_ASSERT(os);
+ (void)os->RemoveObserver(this, aTopic);
+
+ return NS_OK;
+}
+
+void
+nsTerminator::UpdateHeartbeat(const char* aTopic)
+{
+ // Reset the clock, find out how long the current phase has lasted.
+ uint32_t ticks = gHeartbeat.exchange(0);
+ if (mCurrentStep > 0) {
+ sShutdownSteps[mCurrentStep].mTicks = ticks;
+ }
+
+ // Find out where we now are in the current shutdown.
+ // Don't assume that shutdown takes place in the expected order.
+ int nextStep = -1;
+ for (size_t i = 0; i < ArrayLength(sShutdownSteps); ++i) {
+ if (strcmp(sShutdownSteps[i].mTopic, aTopic) == 0) {
+ nextStep = i;
+ break;
+ }
+ }
+ MOZ_ASSERT(nextStep != -1);
+ mCurrentStep = nextStep;
+}
+
+} // namespace mozilla
diff --git a/components/terminator/nsTerminator.h b/components/terminator/nsTerminator.h
new file mode 100644
index 000000000..de6da9532
--- /dev/null
+++ b/components/terminator/nsTerminator.h
@@ -0,0 +1,40 @@
+/* -*- 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 nsTerminator_h__
+#define nsTerminator_h__
+
+#include "nsISupports.h"
+#include "nsIObserver.h"
+
+namespace mozilla {
+
+class nsTerminator final: public nsIObserver {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsTerminator();
+
+private:
+ nsresult SelfInit();
+ void Start();
+ void StartWatchdog();
+
+ void UpdateHeartbeat(const char* aTopic);
+
+ ~nsTerminator() {}
+
+ bool mInitialized;
+ int32_t mCurrentStep;
+};
+
+} // namespace mozilla
+
+#define NS_TOOLKIT_TERMINATOR_CID { 0x2e59cc70, 0xf83a, 0x412f, \
+ { 0x89, 0xd4, 0x45, 0x38, 0x85, 0x83, 0x72, 0x17 } }
+#define NS_TOOLKIT_TERMINATOR_CONTRACTID "@mozilla.org/toolkit/shutdown-terminator;1"
+
+#endif // nsTerminator_h__
diff --git a/components/terminator/terminator.manifest b/components/terminator/terminator.manifest
new file mode 100644
index 000000000..33cb143de
--- /dev/null
+++ b/components/terminator/terminator.manifest
@@ -0,0 +1 @@
+category profile-after-change nsTerminator @mozilla.org/toolkit/shutdown-terminator;1
diff --git a/components/thumbnails/BackgroundPageThumbs.jsm b/components/thumbnails/BackgroundPageThumbs.jsm
new file mode 100644
index 000000000..4c7ca90e1
--- /dev/null
+++ b/components/thumbnails/BackgroundPageThumbs.jsm
@@ -0,0 +1,416 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 EXPORTED_SYMBOLS = [
+ "BackgroundPageThumbs",
+];
+
+const DEFAULT_CAPTURE_TIMEOUT = 30000; // ms
+const DESTROY_BROWSER_TIMEOUT = 60000; // ms
+const FRAME_SCRIPT_URL = "chrome://global/content/backgroundPageThumbsContent.js";
+
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+const global = this;
+
+// contains base64 version of a placeholder thumbnail
+#include blankthumb.inc
+
+const BackgroundPageThumbs = {
+
+ /**
+ * Asynchronously captures a thumbnail of the given URL.
+ *
+ * The page is loaded anonymously, and plug-ins are disabled.
+ *
+ * @param url The URL to capture.
+ * @param options An optional object that configures the capture. Its
+ * properties are the following, and all are optional:
+ * @opt onDone A function that will be asynchronously called when the
+ * capture is complete or times out. It's called as
+ * onDone(url),
+ * where `url` is the captured URL.
+ * @opt timeout The capture will time out after this many milliseconds have
+ * elapsed after the capture has progressed to the head of
+ * the queue and started. Defaults to 30000 (30 seconds).
+ */
+ capture: function (url, options={}) {
+ if (!PageThumbs._prefEnabled()) {
+ if (options.onDone)
+ schedule(() => options.onDone(url));
+ return;
+ }
+ this._captureQueue = this._captureQueue || [];
+ this._capturesByURL = this._capturesByURL || new Map();
+
+ // We want to avoid duplicate captures for the same URL. If there is an
+ // existing one, we just add the callback to that one and we are done.
+ let existing = this._capturesByURL.get(url);
+ if (existing) {
+ if (options.onDone)
+ existing.doneCallbacks.push(options.onDone);
+ // The queue is already being processed, so nothing else to do...
+ return;
+ }
+ let cap = new Capture(url, this._onCaptureOrTimeout.bind(this), options);
+ this._captureQueue.push(cap);
+ this._capturesByURL.set(url, cap);
+ this._processCaptureQueue();
+ },
+
+ /**
+ * Asynchronously captures a thumbnail of the given URL if one does not
+ * already exist. Otherwise does nothing.
+ *
+ * @param url The URL to capture.
+ * @param options An optional object that configures the capture. See
+ * capture() for description.
+ * @return {Promise} A Promise that resolves when this task completes
+ */
+ captureIfMissing: Task.async(function* (url, options={}) {
+ // The fileExistsForURL call is an optimization, potentially but unlikely
+ // incorrect, and no big deal when it is. After the capture is done, we
+ // atomically test whether the file exists before writing it.
+ let exists = yield PageThumbsStorage.fileExistsForURL(url);
+ if (exists) {
+ if (options.onDone) {
+ options.onDone(url);
+ }
+ return url;
+ }
+ let thumbPromise = new Promise((resolve, reject) => {
+ function observe(subject, topic, data) { // jshint ignore:line
+ if (data === url) {
+ switch (topic) {
+ case "page-thumbnail:create":
+ resolve();
+ break;
+ case "page-thumbnail:error":
+ reject(new Error("page-thumbnail:error"));
+ break;
+ }
+ Services.obs.removeObserver(observe, "page-thumbnail:create");
+ Services.obs.removeObserver(observe, "page-thumbnail:error");
+ }
+ }
+ Services.obs.addObserver(observe, "page-thumbnail:create", false);
+ Services.obs.addObserver(observe, "page-thumbnail:error", false);
+ });
+ try {
+ this.capture(url, options);
+ yield thumbPromise;
+ } catch (err) {
+ if (options.onDone) {
+ options.onDone(url);
+ }
+ throw err;
+ }
+ return url;
+ }),
+
+ /**
+ * Ensures that initialization of the thumbnail browser's parent window has
+ * begun.
+ *
+ * @return True if the parent window is completely initialized and can be
+ * used, and false if initialization has started but not completed.
+ */
+ _ensureParentWindowReady: function () {
+ if (this._parentWin)
+ // Already fully initialized.
+ return true;
+ if (this._startedParentWinInit)
+ // Already started initializing.
+ return false;
+
+ this._startedParentWinInit = true;
+
+ // Create an html:iframe, stick it in the parent document, and
+ // use it to host the browser. about:blank will not have the system
+ // principal, so it can't host, but a document with a chrome URI will.
+ let hostWindow = Services.appShell.hiddenDOMWindow;
+ let iframe = hostWindow.document.createElementNS(HTML_NS, "iframe");
+ iframe.setAttribute("src", "chrome://global/content/mozilla.xhtml");
+ let onLoad = function onLoadFn() {
+ iframe.removeEventListener("load", onLoad, true);
+ this._parentWin = iframe.contentWindow;
+ this._processCaptureQueue();
+ }.bind(this);
+ iframe.addEventListener("load", onLoad, true);
+ hostWindow.document.documentElement.appendChild(iframe);
+ this._hostIframe = iframe;
+
+ return false;
+ },
+
+ /**
+ * Destroys the service. Queued and pending captures will never complete, and
+ * their consumer callbacks will never be called.
+ */
+ _destroy: function () {
+ if (this._captureQueue)
+ this._captureQueue.forEach(cap => cap.destroy());
+ this._destroyBrowser();
+ if (this._hostIframe)
+ this._hostIframe.remove();
+ delete this._captureQueue;
+ delete this._hostIframe;
+ delete this._startedParentWinInit;
+ delete this._parentWin;
+ },
+
+ /**
+ * Creates the thumbnail browser if it doesn't already exist.
+ */
+ _ensureBrowser: function () {
+ if (this._thumbBrowser)
+ return;
+
+ let browser = this._parentWin.document.createElementNS(XUL_NS, "browser");
+ browser.setAttribute("type", "content");
+ browser.setAttribute("disableglobalhistory", "true");
+
+ // Size the browser. Make its aspect ratio the same as the canvases' that
+ // the thumbnails are drawn into; the canvases' aspect ratio is the same as
+ // the screen's, so use that. Aim for a size in the ballpark of 1024x768.
+ let [swidth, sheight] = [{}, {}];
+ Cc["@mozilla.org/gfx/screenmanager;1"].
+ getService(Ci.nsIScreenManager).
+ primaryScreen.
+ GetRectDisplayPix({}, {}, swidth, sheight);
+ let bwidth = Math.min(1024, swidth.value);
+ // Setting the width and height attributes doesn't work -- the resulting
+ // thumbnails are blank and transparent -- but setting the style does.
+ browser.style.width = bwidth + "px";
+ browser.style.height = (bwidth * sheight.value / swidth.value) + "px";
+
+ this._parentWin.document.documentElement.appendChild(browser);
+
+ // an event that is sent if the remote process crashes - no need to remove
+ // it as we want it to be there as long as the browser itself lives.
+ browser.addEventListener("oop-browser-crashed", () => {
+ Cu.reportError("BackgroundThumbnails remote process crashed - recovering");
+ this._destroyBrowser();
+ let curCapture = this._captureQueue.length ? this._captureQueue[0] : null;
+ // we could retry the pending capture, but it's possible the crash
+ // was due directly to it, so trying again might just crash again.
+ // We could keep a flag to indicate if it previously crashed, but
+ // "resetting" the capture requires more work - so for now, we just
+ // discard it.
+ if (curCapture && curCapture.pending) {
+ // Continue queue processing by calling curCapture._done(). Do it after
+ // this crashed listener returns, though. A new browser will be created
+ // immediately (on the same stack as the _done call stack) if there are
+ // any more queued-up captures, and that seems to mess up the new
+ // browser's message manager if it happens on the same stack as the
+ // listener. Trying to send a message to the manager in that case
+ // throws NS_ERROR_NOT_INITIALIZED.
+ Services.tm.currentThread.dispatch(() => {
+ curCapture._done(null);
+ }, Ci.nsIEventTarget.DISPATCH_NORMAL);
+ }
+ // else: we must have been idle and not currently doing a capture (eg,
+ // maybe a GC or similar crashed) - so there's no need to attempt a
+ // queue restart - the next capture request will set everything up.
+ });
+
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
+ this._thumbBrowser = browser;
+ },
+
+ _destroyBrowser: function () {
+ if (!this._thumbBrowser)
+ return;
+ this._thumbBrowser.remove();
+ delete this._thumbBrowser;
+ },
+
+ /**
+ * Starts the next capture if the queue is not empty and the service is fully
+ * initialized.
+ */
+ _processCaptureQueue: function () {
+ if (!this._captureQueue.length ||
+ this._captureQueue[0].pending ||
+ !this._ensureParentWindowReady())
+ return;
+
+ // Ready to start the first capture in the queue.
+ this._ensureBrowser();
+ this._captureQueue[0].start(this._thumbBrowser.messageManager);
+ if (this._destroyBrowserTimer) {
+ this._destroyBrowserTimer.cancel();
+ delete this._destroyBrowserTimer;
+ }
+ },
+
+ /**
+ * Called when the current capture completes or fails (eg, times out, remote
+ * process crashes.)
+ */
+ _onCaptureOrTimeout: function (capture) {
+ // Since timeouts start as an item is being processed, only the first
+ // item in the queue can be passed to this method.
+ if (capture !== this._captureQueue[0])
+ throw new Error("The capture should be at the head of the queue.");
+ this._captureQueue.shift();
+ this._capturesByURL.delete(capture.url);
+
+ // Start the destroy-browser timer *before* processing the capture queue.
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(this._destroyBrowser.bind(this),
+ this._destroyBrowserTimeout,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ this._destroyBrowserTimer = timer;
+
+ this._processCaptureQueue();
+ },
+
+ _destroyBrowserTimeout: DESTROY_BROWSER_TIMEOUT,
+};
+
+Object.defineProperty(this, "BackgroundPageThumbs", {
+ value: BackgroundPageThumbs,
+ enumerable: true,
+ writable: false
+});
+
+/**
+ * Represents a single capture request in the capture queue.
+ *
+ * @param url The URL to capture.
+ * @param captureCallback A function you want called when the capture
+ * completes.
+ * @param options The capture options.
+ */
+function Capture(url, captureCallback, options) {
+ this.url = url;
+ this.captureCallback = captureCallback;
+ this.options = options;
+ this.id = Capture.nextID++;
+ this.creationDate = new Date();
+ this.doneCallbacks = [];
+ if (options.onDone)
+ this.doneCallbacks.push(options.onDone);
+}
+
+Capture.prototype = {
+
+ get pending() {
+ return !!this._msgMan;
+ },
+
+ /**
+ * Sends a message to the content script to start the capture.
+ *
+ * @param messageManager The nsIMessageSender of the thumbnail browser.
+ */
+ start: function (messageManager) {
+ this.startDate = new Date();
+
+ // timeout timer
+ let timeout;
+ if (this.options && typeof(this.options.timeout) == "number") {
+ timeout = this.options.timeout;
+ } else {
+ timeout = DEFAULT_CAPTURE_TIMEOUT;
+ }
+ this._timeoutTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._timeoutTimer.initWithCallback(this, timeout,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+
+ // didCapture registration
+ this._msgMan = messageManager;
+ this._msgMan.sendAsyncMessage("BackgroundPageThumbs:capture",
+ { id: this.id, url: this.url });
+ this._msgMan.addMessageListener("BackgroundPageThumbs:didCapture", this);
+ },
+
+ /**
+ * The only intended external use of this method is by the service when it's
+ * uninitializing and doing things like destroying the thumbnail browser. In
+ * that case the consumer's completion callback will never be called.
+ */
+ destroy: function () {
+ // This method may be called for captures that haven't started yet, so
+ // guard against not yet having _timeoutTimer, _msgMan etc properties...
+ if (this._timeoutTimer) {
+ this._timeoutTimer.cancel();
+ delete this._timeoutTimer;
+ }
+ if (this._msgMan) {
+ this._msgMan.removeMessageListener("BackgroundPageThumbs:didCapture",
+ this);
+ delete this._msgMan;
+ }
+ delete this.captureCallback;
+ delete this.doneCallbacks;
+ delete this.options;
+ },
+
+ // Called when the didCapture message is received.
+ receiveMessage: function (msg) {
+ // A different timed-out capture may have finally successfully completed, so
+ // discard messages that aren't meant for this capture.
+ if (msg.data.id != this.id)
+ return;
+
+ if (msg.data.failReason) {
+ this._done(null);
+ return;
+ }
+
+ this._done(msg.data);
+ },
+
+ // Called when the timeout timer fires.
+ notify: function () {
+ this._done(null);
+ },
+
+ _done: function (data) {
+ // Note that _done will be called only once, by either receiveMessage or
+ // notify, since it calls destroy here, which cancels the timeout timer and
+ // removes the didCapture message listener.
+ let { captureCallback, doneCallbacks, options } = this;
+ this.destroy();
+
+ let done = () => {
+ captureCallback(this);
+ for (let callback of doneCallbacks) {
+ try {
+ callback.call(options, this.url);
+ }
+ catch (err) {
+ Cu.reportError(err);
+ }
+ }
+ };
+
+ if (!data) {
+ // If this background attempt failed, cause a placeholder file to be saved, so
+ // that gets loaded instead of attempting again (and again).
+ PageThumbs._store(this.url, this.url, atob(BLANKTHUMB), true)
+ .then(done, done);
+ return;
+ }
+
+ PageThumbs._store(this.url, data.finalURL, data.imageData, true)
+ .then(done, done);
+ },
+};
+
+Capture.nextID = 0;
+
+function schedule(callback) {
+ Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
+}
diff --git a/components/thumbnails/BrowserPageThumbs.manifest b/components/thumbnails/BrowserPageThumbs.manifest
new file mode 100644
index 000000000..8dfc0597b
--- /dev/null
+++ b/components/thumbnails/BrowserPageThumbs.manifest
@@ -0,0 +1,2 @@
+component {5a4ae9b5-f475-48ae-9dce-0b4c1d347884} PageThumbsProtocol.js
+contract @mozilla.org/network/protocol;1?name=moz-page-thumb {5a4ae9b5-f475-48ae-9dce-0b4c1d347884}
diff --git a/components/thumbnails/PageThumbUtils.jsm b/components/thumbnails/PageThumbUtils.jsm
new file mode 100644
index 000000000..f91b36480
--- /dev/null
+++ b/components/thumbnails/PageThumbUtils.jsm
@@ -0,0 +1,343 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Common thumbnailing routines used by various consumers, including
+ * PageThumbs and backgroundPageThumbsContent.
+ */
+
+this.EXPORTED_SYMBOLS = ["PageThumbUtils"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+
+this.PageThumbUtils = {
+ // The default background color for page thumbnails.
+ THUMBNAIL_BG_COLOR: "#fff",
+ // The namespace for thumbnail canvas elements.
+ HTML_NAMESPACE: "http://www.w3.org/1999/xhtml",
+
+ /**
+ * Creates a new canvas element in the context of aWindow, or if aWindow
+ * is undefined, in the context of hiddenDOMWindow.
+ *
+ * @param aWindow (optional) The document of this window will be used to
+ * create the canvas. If not given, the hidden window will be used.
+ * @param aWidth (optional) width of the canvas to create
+ * @param aHeight (optional) height of the canvas to create
+ * @return The newly created canvas.
+ */
+ createCanvas: function (aWindow, aWidth = 0, aHeight = 0) {
+ let doc = (aWindow || Services.appShell.hiddenDOMWindow).document;
+ let canvas = doc.createElementNS(this.HTML_NAMESPACE, "canvas");
+ canvas.mozOpaque = true;
+ canvas.imageSmoothingEnabled = true;
+ let [thumbnailWidth, thumbnailHeight] = this.getThumbnailSize(aWindow);
+ canvas.width = aWidth ? aWidth : thumbnailWidth;
+ canvas.height = aHeight ? aHeight : thumbnailHeight;
+ return canvas;
+ },
+
+ /**
+ * Calculates a preferred initial thumbnail size based based on newtab.css
+ * sizes or a preference for other applications. The sizes should be the same
+ * as set for the tile sizes in newtab.
+ *
+ * @param aWindow (optional) aWindow that is used to calculate the scaling size.
+ * @return The calculated thumbnail size or a default if unable to calculate.
+ */
+ getThumbnailSize: function (aWindow = null) {
+ if (!this._thumbnailWidth || !this._thumbnailHeight) {
+ let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
+ .getService(Ci.nsIScreenManager);
+ let left = {}, top = {}, screenWidth = {}, screenHeight = {};
+ screenManager.primaryScreen.GetRectDisplayPix(left, top, screenWidth, screenHeight);
+
+ /** *
+ * The system default scale might be different than
+ * what is reported by the window. For example,
+ * retina displays have 1:1 system scales, but 2:1 window
+ * scale as 1 pixel system wide == 2 device pixels.
+ * To get the best image quality, query both and take the highest one.
+ */
+ let systemScale = screenManager.systemDefaultScale;
+ let windowScale = aWindow ? aWindow.devicePixelRatio : systemScale;
+ let scale = Math.max(systemScale, windowScale);
+
+ /** *
+ * THESE VALUES ARE DEFINED IN newtab.css and hard coded.
+ * If you change these values from the prefs,
+ * ALSO CHANGE THEM IN newtab.css
+ */
+ let prefWidth = Services.prefs.getIntPref("toolkit.pageThumbs.minWidth");
+ let prefHeight = Services.prefs.getIntPref("toolkit.pageThumbs.minHeight");
+ let divisor = Services.prefs.getIntPref("toolkit.pageThumbs.screenSizeDivisor");
+
+ prefWidth *= scale;
+ prefHeight *= scale;
+
+ this._thumbnailWidth = Math.max(Math.round(screenWidth.value / divisor), prefWidth);
+ this._thumbnailHeight = Math.max(Math.round(screenHeight.value / divisor), prefHeight);
+ }
+
+ return [this._thumbnailWidth, this._thumbnailHeight];
+ },
+
+ /** *
+ * Given a browser window, return the size of the content
+ * minus the scroll bars.
+ */
+ getContentSize: function(aWindow) {
+ let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ // aWindow may be a cpow, add exposed props security values.
+ let sbWidth = {}, sbHeight = {};
+
+ try {
+ utils.getScrollbarSize(false, sbWidth, sbHeight);
+ } catch (e) {
+ // This might fail if the window does not have a presShell.
+ Cu.reportError("Unable to get scrollbar size in determineCropSize.");
+ sbWidth.value = sbHeight.value = 0;
+ }
+
+ // Even in RTL mode, scrollbars are always on the right.
+ // So there's no need to determine a left offset.
+ let width = aWindow.innerWidth - sbWidth.value;
+ let height = aWindow.innerHeight - sbHeight.value;
+
+ return [width, height];
+ },
+
+ /** *
+ * Given a browser window, this creates a snapshot of the content
+ * and returns a canvas with the resulting snapshot of the content
+ * at the thumbnail size. It has to do this through a two step process:
+ *
+ * 1) Render the content at the window size to a canvas that is 2x the thumbnail size
+ * 2) Downscale the canvas from (1) down to the thumbnail size
+ *
+ * This is because the thumbnail size is too small to render at directly,
+ * causing pages to believe the browser is a small resolution. Also,
+ * at that resolution, graphical artifacts / text become very jagged.
+ * It's actually better to the eye to have small blurry text than sharp
+ * jagged pixels to represent text.
+ *
+ * @params aWindow - the window to create a snapshot of.
+ * @params aDestCanvas destination canvas to draw the final
+ * snapshot to. Can be null.
+ * @param aArgs (optional) Additional named parameters:
+ * fullScale - request that a non-downscaled image be returned.
+ * @return Canvas with a scaled thumbnail of the window.
+ */
+ createSnapshotThumbnail: function(aWindow, aDestCanvas, aArgs) {
+ if (Cu.isCrossProcessWrapper(aWindow)) {
+ throw new Error('Do not pass cpows here.');
+ }
+ let fullScale = aArgs ? aArgs.fullScale : false;
+ let [contentWidth, contentHeight] = this.getContentSize(aWindow);
+ let [thumbnailWidth, thumbnailHeight] = aDestCanvas ?
+ [aDestCanvas.width, aDestCanvas.height] :
+ this.getThumbnailSize(aWindow);
+
+ // If the caller wants a fullscale image, set the desired thumbnail dims
+ // to the dims of content and (if provided) size the incoming canvas to
+ // support our results.
+ if (fullScale) {
+ thumbnailWidth = contentWidth;
+ thumbnailHeight = contentHeight;
+ if (aDestCanvas) {
+ aDestCanvas.width = contentWidth;
+ aDestCanvas.height = contentHeight;
+ }
+ }
+
+ let intermediateWidth = thumbnailWidth * 2;
+ let intermediateHeight = thumbnailHeight * 2;
+ let skipDownscale = false;
+
+ // If the intermediate thumbnail is larger than content dims (hiDPI
+ // devices can experience this) or a full preview is requested render
+ // at the final thumbnail size.
+ if ((intermediateWidth >= contentWidth ||
+ intermediateHeight >= contentHeight) || fullScale) {
+ intermediateWidth = thumbnailWidth;
+ intermediateHeight = thumbnailHeight;
+ skipDownscale = true;
+ }
+
+ // Create an intermediate surface
+ let snapshotCanvas = this.createCanvas(aWindow, intermediateWidth,
+ intermediateHeight);
+
+ // Step 1: capture the image at the intermediate dims. For thumbnails
+ // this is twice the thumbnail size, for fullScale images this is at
+ // content dims.
+ // Also by default, canvas does not draw the scrollbars, so no need to
+ // remove the scrollbar sizes.
+ let scale = Math.min(Math.max(intermediateWidth / contentWidth,
+ intermediateHeight / contentHeight), 1);
+
+ let snapshotCtx = snapshotCanvas.getContext("2d");
+ snapshotCtx.save();
+ snapshotCtx.scale(scale, scale);
+ snapshotCtx.drawWindow(aWindow, 0, 0, contentWidth, contentHeight,
+ PageThumbUtils.THUMBNAIL_BG_COLOR,
+ snapshotCtx.DRAWWINDOW_DO_NOT_FLUSH);
+ snapshotCtx.restore();
+
+ // Part 2: Downscale from our intermediate dims to the final thumbnail
+ // dims and copy the result to aDestCanvas. If the caller didn't
+ // provide a target canvas, create a new canvas and return it.
+ let finalCanvas = aDestCanvas ||
+ this.createCanvas(aWindow, thumbnailWidth, thumbnailHeight);
+
+ let finalCtx = finalCanvas.getContext("2d");
+ finalCtx.save();
+ if (!skipDownscale) {
+ finalCtx.scale(0.5, 0.5);
+ }
+ finalCtx.drawImage(snapshotCanvas, 0, 0);
+ finalCtx.restore();
+
+ return finalCanvas;
+ },
+
+ /**
+ * Determine a good thumbnail crop size and scale for a given content
+ * window.
+ *
+ * @param aWindow The content window.
+ * @param aCanvas The target canvas.
+ * @return An array containing width, height and scale.
+ */
+ determineCropSize: function (aWindow, aCanvas) {
+ if (Cu.isCrossProcessWrapper(aWindow)) {
+ throw new Error('Do not pass cpows here.');
+ }
+ let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ // aWindow may be a cpow, add exposed props security values.
+ let sbWidth = {}, sbHeight = {};
+
+ try {
+ utils.getScrollbarSize(false, sbWidth, sbHeight);
+ } catch (e) {
+ // This might fail if the window does not have a presShell.
+ Cu.reportError("Unable to get scrollbar size in determineCropSize.");
+ sbWidth.value = sbHeight.value = 0;
+ }
+
+ // Even in RTL mode, scrollbars are always on the right.
+ // So there's no need to determine a left offset.
+ let width = aWindow.innerWidth - sbWidth.value;
+ let height = aWindow.innerHeight - sbHeight.value;
+
+ let {width: thumbnailWidth, height: thumbnailHeight} = aCanvas;
+ let scale = Math.min(Math.max(thumbnailWidth / width, thumbnailHeight / height), 1);
+ let scaledWidth = width * scale;
+ let scaledHeight = height * scale;
+
+ if (scaledHeight > thumbnailHeight)
+ height -= Math.floor(Math.abs(scaledHeight - thumbnailHeight) * scale);
+
+ if (scaledWidth > thumbnailWidth)
+ width -= Math.floor(Math.abs(scaledWidth - thumbnailWidth) * scale);
+
+ return [width, height, scale];
+ },
+
+ shouldStoreContentThumbnail: function (aDocument, aDocShell) {
+ if (BrowserUtils.isToolbarVisible(aDocShell, "findbar")) {
+ return false;
+ }
+
+ // FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as
+ // that currently regresses Talos SVG tests.
+ if (aDocument instanceof Ci.nsIDOMXMLDocument) {
+ return false;
+ }
+
+ let webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation);
+
+ // Don't take screenshots of about: pages.
+ if (webNav.currentURI.schemeIs("about")) {
+ return false;
+ }
+
+ // There's no point in taking screenshot of loading pages.
+ if (aDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) {
+ return false;
+ }
+
+ let channel = aDocShell.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") &&
+ !Services.prefs.getBoolPref("browser.cache.disk_cache_ssl")) {
+ return false;
+ }
+ } // httpChannel
+ return true;
+ },
+
+ /**
+ * Given a channel, returns true if it should be considered an "error
+ * response", false otherwise.
+ */
+ isChannelErrorResponse: function(channel) {
+ // No valid document channel sounds like an error to me!
+ if (!channel)
+ return true;
+ if (!(channel instanceof Ci.nsIHttpChannel))
+ // it might be FTP etc, so assume it's ok.
+ return false;
+ try {
+ return !channel.requestSucceeded;
+ } catch (_) {
+ // not being able to determine success is surely failure!
+ return true;
+ }
+ },
+};
diff --git a/components/thumbnails/PageThumbs.jsm b/components/thumbnails/PageThumbs.jsm
new file mode 100644
index 000000000..714bbbb77
--- /dev/null
+++ b/components/thumbnails/PageThumbs.jsm
@@ -0,0 +1,902 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["PageThumbs", "PageThumbsStorage"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
+const LATEST_STORAGE_VERSION = 3;
+
+const EXPIRATION_MIN_CHUNK_SIZE = 50;
+const EXPIRATION_INTERVAL_SECS = 3600;
+
+var gRemoteThumbId = 0;
+
+// If a request for a thumbnail comes in and we find one that is "stale"
+// (or don't find one at all) we automatically queue a request to generate a
+// new one.
+const MAX_THUMBNAIL_AGE_SECS = 172800; // 2 days == 60*60*24*2 == 172800 secs.
+
+/**
+ * Name of the directory in the profile that contains the thumbnails.
+ */
+const THUMBNAIL_DIRECTORY = "thumbnails";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
+Cu.import("resource://gre/modules/Promise.jsm", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+
+Cu.importGlobalProperties(['FileReader']);
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gUpdateTimerManager",
+ "@mozilla.org/updates/timer-manager;1", "nsIUpdateTimerManager");
+
+XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function () {
+ return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function () {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = 'utf8';
+ return converter;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+ "resource://gre/modules/AsyncShutdown.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils",
+ "resource://gre/modules/PageThumbUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+/**
+ * Utilities for dealing with promises and Task.jsm
+ */
+const TaskUtils = {
+ /**
+ * Read the bytes from a blob, asynchronously.
+ *
+ * @return {Promise}
+ * @resolve {ArrayBuffer} In case of success, the bytes contained in the blob.
+ * @reject {DOMError} In case of error, the underlying DOMError.
+ */
+ readBlob: function readBlob(blob) {
+ let deferred = Promise.defer();
+ let reader = new FileReader();
+ reader.onloadend = function onloadend() {
+ if (reader.readyState != FileReader.DONE) {
+ deferred.reject(reader.error);
+ } else {
+ deferred.resolve(reader.result);
+ }
+ };
+ reader.readAsArrayBuffer(blob);
+ return deferred.promise;
+ }
+};
+
+
+
+
+/**
+ * Singleton providing functionality for capturing web page thumbnails and for
+ * accessing them if already cached.
+ */
+this.PageThumbs = {
+ _initialized: false,
+
+ /**
+ * The calculated width and height of the thumbnails.
+ */
+ _thumbnailWidth : 0,
+ _thumbnailHeight : 0,
+
+ /**
+ * The scheme to use for thumbnail urls.
+ */
+ get scheme() {
+ return "moz-page-thumb";
+ },
+
+ /**
+ * The static host to use for thumbnail urls.
+ */
+ get staticHost() {
+ return "thumbnail";
+ },
+
+ /**
+ * The thumbnails' image type.
+ */
+ get contentType() {
+ return "image/png";
+ },
+
+ init: function PageThumbs_init() {
+ if (!this._initialized) {
+ this._initialized = true;
+ PlacesUtils.history.addObserver(PageThumbsHistoryObserver, true);
+
+ // Migrate the underlying storage, if needed.
+ PageThumbsStorageMigrator.migrate();
+ PageThumbsExpiration.init();
+ }
+ },
+
+ uninit: function PageThumbs_uninit() {
+ if (this._initialized) {
+ this._initialized = false;
+ }
+ },
+
+ /**
+ * Gets the thumbnail image's url for a given web page's url.
+ * @param aUrl The web page's url that is depicted in the thumbnail.
+ * @return The thumbnail image's url.
+ */
+ getThumbnailURL: function PageThumbs_getThumbnailURL(aUrl) {
+ return this.scheme + "://" + this.staticHost +
+ "/?url=" + encodeURIComponent(aUrl) +
+ "&revision=" + PageThumbsStorage.getRevision(aUrl);
+ },
+
+ /**
+ * Gets the path of the thumbnail file for a given web page's
+ * url. This file may or may not exist depending on whether the
+ * thumbnail has been captured or not.
+ *
+ * @param aUrl The web page's url.
+ * @return The path of the thumbnail file.
+ */
+ getThumbnailPath: function PageThumbs_getThumbnailPath(aUrl) {
+ return PageThumbsStorage.getFilePathForURL(aUrl);
+ },
+
+ /**
+ * Asynchronously returns a thumbnail as a blob for the given
+ * window.
+ *
+ * @param aBrowser The <browser> to capture a thumbnail from.
+ * @return {Promise}
+ * @resolve {Blob} The thumbnail, as a Blob.
+ */
+ captureToBlob: function PageThumbs_captureToBlob(aBrowser) {
+ if (!this._prefEnabled()) {
+ return null;
+ }
+
+ let deferred = Promise.defer();
+
+ let canvas = this.createCanvas(aBrowser.contentWindow);
+ this.captureToCanvas(aBrowser, canvas, () => {
+ canvas.toBlob(blob => {
+ deferred.resolve(blob, this.contentType);
+ });
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Captures a thumbnail from a given window and draws it to the given canvas.
+ * Note, when dealing with remote content, this api draws into the passed
+ * canvas asynchronously. Pass aCallback to receive an async callback after
+ * canvas painting has completed.
+ * @param aBrowser The browser to capture a thumbnail from.
+ * @param aCanvas The canvas to draw to. The thumbnail will be scaled to match
+ * the dimensions of this canvas. If callers pass a 0x0 canvas, the canvas
+ * will be resized to default thumbnail dimensions just prior to painting.
+ * @param aCallback (optional) A callback invoked once the thumbnail has been
+ * rendered to aCanvas.
+ * @param aArgs (optional) Additional named parameters:
+ * fullScale - request that a non-downscaled image be returned.
+ */
+ captureToCanvas: function (aBrowser, aCanvas, aCallback, aArgs) {
+ let args = {
+ fullScale: aArgs ? aArgs.fullScale : false
+ };
+ this._captureToCanvas(aBrowser, aCanvas, args, (aCanvas) => {
+ if (aCallback) {
+ aCallback(aCanvas);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously check the state of aBrowser to see if it passes a set of
+ * predefined security checks. Consumers should refrain from storing
+ * thumbnails if these checks fail. Note the final result of this call is
+ * transitory as it is based on current navigation state and the type of
+ * content being displayed.
+ *
+ * @param aBrowser The target browser
+ * @param aCallback(aResult) A callback invoked once security checks have
+ * completed. aResult is a boolean indicating the combined result of the
+ * security checks performed.
+ */
+ shouldStoreThumbnail: function (aBrowser, aCallback) {
+ // Don't capture in private browsing mode.
+ if (PrivateBrowsingUtils.isBrowserPrivate(aBrowser)) {
+ aCallback(false);
+ return;
+ }
+ if (aBrowser.isRemoteBrowser) {
+ let mm = aBrowser.messageManager;
+ let resultFunc = function (aMsg) {
+ mm.removeMessageListener("Browser:Thumbnail:CheckState:Response", resultFunc);
+ aCallback(aMsg.data.result);
+ }
+ mm.addMessageListener("Browser:Thumbnail:CheckState:Response", resultFunc);
+ try {
+ mm.sendAsyncMessage("Browser:Thumbnail:CheckState");
+ } catch (ex) {
+ Cu.reportError(ex);
+ // If the message manager is not able send our message, taking a content
+ // screenshot is also not going to work: return false.
+ resultFunc({ data: { result: false } });
+ }
+ } else {
+ aCallback(PageThumbUtils.shouldStoreContentThumbnail(aBrowser.contentDocument,
+ aBrowser.docShell));
+ }
+ },
+
+ // The background thumbnail service captures to canvas but doesn't want to
+ // participate in this service's telemetry, which is why this method exists.
+ _captureToCanvas: function (aBrowser, aCanvas, aArgs, aCallback) {
+ if (aBrowser.isRemoteBrowser) {
+ Task.spawn(function* () {
+ let data =
+ yield this._captureRemoteThumbnail(aBrowser, aCanvas.width,
+ aCanvas.height, aArgs);
+ let canvas = data.thumbnail;
+ let ctx = canvas.getContext("2d");
+ let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+ aCanvas.width = canvas.width;
+ aCanvas.height = canvas.height;
+ aCanvas.getContext("2d").putImageData(imgData, 0, 0);
+ if (aCallback) {
+ aCallback(aCanvas);
+ }
+ }.bind(this));
+ return;
+ }
+ // The content is a local page, grab a thumbnail sync.
+ PageThumbUtils.createSnapshotThumbnail(aBrowser.contentWindow,
+ aCanvas,
+ aArgs);
+
+ if (aCallback) {
+ aCallback(aCanvas);
+ }
+ },
+
+ /**
+ * Asynchrnously render an appropriately scaled thumbnail to canvas.
+ *
+ * @param aBrowser The browser to capture a thumbnail from.
+ * @param aWidth The desired canvas width.
+ * @param aHeight The desired canvas height.
+ * @param aArgs (optional) Additional named parameters:
+ * fullScale - request that a non-downscaled image be returned.
+ * @return a promise
+ */
+ _captureRemoteThumbnail: function (aBrowser, aWidth, aHeight, aArgs) {
+ let deferred = Promise.defer();
+
+ // The index we send with the request so we can identify the
+ // correct response.
+ let index = gRemoteThumbId++;
+
+ // Thumbnail request response handler
+ let mm = aBrowser.messageManager;
+
+ // Browser:Thumbnail:Response handler
+ let thumbFunc = function (aMsg) {
+ // Ignore events unrelated to our request
+ if (aMsg.data.id != index) {
+ return;
+ }
+
+ mm.removeMessageListener("Browser:Thumbnail:Response", thumbFunc);
+ let imageBlob = aMsg.data.thumbnail;
+ let doc = aBrowser.parentElement.ownerDocument;
+ let reader = new FileReader();
+ reader.addEventListener("loadend", function() {
+ let image = doc.createElementNS(PageThumbUtils.HTML_NAMESPACE, "img");
+ image.onload = function () {
+ let thumbnail = doc.createElementNS(PageThumbUtils.HTML_NAMESPACE, "canvas");
+ thumbnail.width = image.naturalWidth;
+ thumbnail.height = image.naturalHeight;
+ let ctx = thumbnail.getContext("2d");
+ ctx.drawImage(image, 0, 0);
+ deferred.resolve({
+ thumbnail: thumbnail
+ });
+ }
+ image.src = reader.result;
+ });
+ // xxx wish there was a way to skip this encoding step
+ reader.readAsDataURL(imageBlob);
+ }
+
+ // Send a thumbnail request
+ mm.addMessageListener("Browser:Thumbnail:Response", thumbFunc);
+ mm.sendAsyncMessage("Browser:Thumbnail:Request", {
+ canvasWidth: aWidth,
+ canvasHeight: aHeight,
+ background: PageThumbUtils.THUMBNAIL_BG_COLOR,
+ id: index,
+ additionalArgs: aArgs
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Captures a thumbnail for the given browser and stores it to the cache.
+ * @param aBrowser The browser to capture a thumbnail for.
+ * @param aCallback The function to be called when finished (optional).
+ */
+ captureAndStore: function PageThumbs_captureAndStore(aBrowser, aCallback) {
+ if (!this._prefEnabled()) {
+ return;
+ }
+
+ let url = aBrowser.currentURI.spec;
+ let originalURL;
+ let channelError = false;
+
+ Task.spawn((function* task() {
+ if (!aBrowser.isRemoteBrowser) {
+ let channel = aBrowser.docShell.currentDocumentChannel;
+ originalURL = channel.originalURI.spec;
+ // see if this was an error response.
+ channelError = PageThumbUtils.isChannelErrorResponse(channel);
+ } else {
+ let resp = yield new Promise(resolve => {
+ let mm = aBrowser.messageManager;
+ let respName = "Browser:Thumbnail:GetOriginalURL:Response";
+ mm.addMessageListener(respName, function onResp(msg) {
+ mm.removeMessageListener(respName, onResp);
+ resolve(msg.data);
+ });
+ mm.sendAsyncMessage("Browser:Thumbnail:GetOriginalURL");
+ });
+ originalURL = resp.originalURL || url;
+ channelError = resp.channelError;
+ }
+
+ let isSuccess = true;
+ try {
+ let blob = yield this.captureToBlob(aBrowser);
+ let buffer = yield TaskUtils.readBlob(blob);
+ yield this._store(originalURL, url, buffer, channelError);
+ } catch (ex) {
+ Components.utils.reportError("Exception thrown during thumbnail capture: '" + ex + "'");
+ isSuccess = false;
+ }
+ if (aCallback) {
+ aCallback(isSuccess);
+ }
+ }).bind(this));
+ },
+
+ /**
+ * Checks if an existing thumbnail for the specified URL is either missing
+ * or stale, and if so, captures and stores it. Once the thumbnail is stored,
+ * an observer service notification will be sent, so consumers should observe
+ * such notifications if they want to be notified of an updated thumbnail.
+ *
+ * @param aBrowser The content window of this browser will be captured.
+ * @param aCallback The function to be called when finished (optional).
+ */
+ captureAndStoreIfStale: function PageThumbs_captureAndStoreIfStale(aBrowser, aCallback) {
+ let url = aBrowser.currentURI.spec;
+ PageThumbsStorage.isFileRecentForURL(url).then(recent => {
+ if (!recent &&
+ // Careful, the call to PageThumbsStorage is async, so the browser may
+ // have navigated away from the URL or even closed.
+ aBrowser.currentURI &&
+ aBrowser.currentURI.spec == url) {
+ this.captureAndStore(aBrowser, aCallback);
+ } else if (aCallback) {
+ aCallback(true);
+ }
+ }, err => {
+ if (aCallback)
+ aCallback(false);
+ });
+ },
+
+ /**
+ * Stores data to disk for the given URLs.
+ *
+ * NB: The background thumbnail service calls this, too.
+ *
+ * @param aOriginalURL The URL with which the capture was initiated.
+ * @param aFinalURL The URL to which aOriginalURL ultimately resolved.
+ * @param aData An ArrayBuffer containing the image data.
+ * @param aNoOverwrite If true and files for the URLs already exist, the files
+ * will not be overwritten.
+ * @return {Promise}
+ */
+ _store: function PageThumbs__store(aOriginalURL, aFinalURL, aData, aNoOverwrite) {
+ return Task.spawn(function* () {
+ yield PageThumbsStorage.writeData(aFinalURL, aData, aNoOverwrite);
+
+ Services.obs.notifyObservers(null, "page-thumbnail:create", aFinalURL);
+ // We've been redirected. Create a copy of the current thumbnail for
+ // the redirect source. We need to do this because:
+ //
+ // 1) Users can drag any kind of links onto the newtab page. If those
+ // links redirect to a different URL then we want to be able to
+ // provide thumbnails for both of them.
+ //
+ // 2) The newtab page should actually display redirect targets, only.
+ // Because of bug 559175 this information can get lost when using
+ // Sync and therefore also redirect sources appear on the newtab
+ // page. We also want thumbnails for those.
+ if (aFinalURL != aOriginalURL) {
+ yield PageThumbsStorage.copy(aFinalURL, aOriginalURL, aNoOverwrite);
+ Services.obs.notifyObservers(null, "page-thumbnail:create", aOriginalURL);
+ }
+ });
+ },
+
+ /**
+ * Register an expiration filter.
+ *
+ * When thumbnails are going to expire, each registered filter is asked for a
+ * list of thumbnails to keep.
+ *
+ * The filter (if it is a callable) or its filterForThumbnailExpiration method
+ * (if the filter is an object) is called with a single argument. The
+ * argument is a callback function. The filter must call the callback
+ * function and pass it an array of zero or more URLs. (It may do so
+ * asynchronously.) Thumbnails for those URLs will be except from expiration.
+ *
+ * @param aFilter callable, or object with filterForThumbnailExpiration method
+ */
+ addExpirationFilter: function PageThumbs_addExpirationFilter(aFilter) {
+ PageThumbsExpiration.addFilter(aFilter);
+ },
+
+ /**
+ * Unregister an expiration filter.
+ * @param aFilter A filter that was previously passed to addExpirationFilter.
+ */
+ removeExpirationFilter: function PageThumbs_removeExpirationFilter(aFilter) {
+ PageThumbsExpiration.removeFilter(aFilter);
+ },
+
+ /**
+ * Creates a new hidden canvas element.
+ * @param aWindow The document of this window will be used to create the
+ * canvas. If not given, the hidden window will be used.
+ * @return The newly created canvas.
+ */
+ createCanvas: function PageThumbs_createCanvas(aWindow) {
+ return PageThumbUtils.createCanvas(aWindow);
+ },
+
+ _prefEnabled: function PageThumbs_prefEnabled() {
+ try {
+ return !Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled");
+ }
+ catch (e) {
+ return true;
+ }
+ },
+};
+
+this.PageThumbsStorage = {
+ // The path for the storage
+ _path: null,
+ get path() {
+ if (!this._path) {
+ this._path = OS.Path.join(OS.Constants.Path.localProfileDir, THUMBNAIL_DIRECTORY);
+ }
+ return this._path;
+ },
+
+ ensurePath: function Storage_ensurePath() {
+ // Create the directory (ignore any error if the directory
+ // already exists). As all writes are done from the PageThumbsWorker
+ // thread, which serializes its operations, this ensures that
+ // future operations can proceed without having to check whether
+ // the directory exists.
+ return PageThumbsWorker.post("makeDir",
+ [this.path, {ignoreExisting: true}]).then(
+ null,
+ function onError(aReason) {
+ Components.utils.reportError("Could not create thumbnails directory" + aReason);
+ });
+ },
+
+ getLeafNameForURL: function Storage_getLeafNameForURL(aURL) {
+ if (typeof aURL != "string") {
+ throw new TypeError("Expecting a string");
+ }
+ let hash = this._calculateMD5Hash(aURL);
+ return hash + ".png";
+ },
+
+ getFilePathForURL: function Storage_getFilePathForURL(aURL) {
+ return OS.Path.join(this.path, this.getLeafNameForURL(aURL));
+ },
+
+ _revisionTable: {},
+
+ // Generate an arbitrary revision tag, i.e. one that can't be used to
+ // infer URL frecency.
+ _updateRevision(aURL) {
+ // Initialize with a random value and increment on each update. Wrap around
+ // modulo _revisionRange, so that even small values carry no meaning.
+ let rev = this._revisionTable[aURL];
+ if (rev == null)
+ rev = Math.floor(Math.random() * this._revisionRange);
+ this._revisionTable[aURL] = (rev + 1) % this._revisionRange;
+ },
+
+ // If two thumbnails with the same URL and revision are in cache at the
+ // same time, the image loader may pick the stale thumbnail in some cases.
+ // Therefore _revisionRange must be large enough to prevent this, e.g.
+ // in the pathological case image.cache.size (5MB by default) could fill
+ // with (abnormally small) 10KB thumbnail images if the browser session
+ // runs long enough (though this is unlikely as thumbnails are usually
+ // only updated every MAX_THUMBNAIL_AGE_SECS).
+ _revisionRange: 8192,
+
+ /**
+ * Return a revision tag for the thumbnail stored for a given URL.
+ *
+ * @param aURL The URL spec string
+ * @return A revision tag for the corresponding thumbnail. Returns a changed
+ * value whenever the stored thumbnail changes.
+ */
+ getRevision(aURL) {
+ let rev = this._revisionTable[aURL];
+ if (rev == null) {
+ this._updateRevision(aURL);
+ rev = this._revisionTable[aURL];
+ }
+ return rev;
+ },
+
+ /**
+ * Write the contents of a thumbnail, off the main thread.
+ *
+ * @param {string} aURL The url for which to store a thumbnail.
+ * @param {ArrayBuffer} aData The data to store in the thumbnail, as
+ * an ArrayBuffer. This array buffer will be detached and cannot be
+ * reused after the copy.
+ * @param {boolean} aNoOverwrite If true and the thumbnail's file already
+ * exists, the file will not be overwritten.
+ *
+ * @return {Promise}
+ */
+ writeData: function Storage_writeData(aURL, aData, aNoOverwrite) {
+ let path = this.getFilePathForURL(aURL);
+ this.ensurePath();
+
+ // XXX: We try/catch here since 'null' isn't accepted until we implement
+ // ES2017's new Uint8Array(); allowance.
+ try {
+ aData = new Uint8Array(aData);
+ } catch(e) {
+ aData = new Uint8Array(0);
+ }
+
+ let msg = [
+ path,
+ aData,
+ {
+ tmpPath: path + ".tmp",
+ bytes: aData.byteLength,
+ noOverwrite: aNoOverwrite,
+ flush: false /* thumbnails do not require the level of guarantee provided by flush*/
+ }];
+ return PageThumbsWorker.post("writeAtomic", msg,
+ msg /* we don't want that message garbage-collected,
+ as OS.Shared.Type.void_t.in_ptr.toMsg uses C-level
+ memory tricks to enforce zero-copy*/).
+ then(() => this._updateRevision(aURL), this._eatNoOverwriteError(aNoOverwrite));
+ },
+
+ /**
+ * Copy a thumbnail, off the main thread.
+ *
+ * @param {string} aSourceURL The url of the thumbnail to copy.
+ * @param {string} aTargetURL The url of the target thumbnail.
+ * @param {boolean} aNoOverwrite If true and the target file already exists,
+ * the file will not be overwritten.
+ *
+ * @return {Promise}
+ */
+ copy: function Storage_copy(aSourceURL, aTargetURL, aNoOverwrite) {
+ this.ensurePath();
+ let sourceFile = this.getFilePathForURL(aSourceURL);
+ let targetFile = this.getFilePathForURL(aTargetURL);
+ let options = { noOverwrite: aNoOverwrite };
+ return PageThumbsWorker.post("copy", [sourceFile, targetFile, options]).
+ then(() => this._updateRevision(aTargetURL), this._eatNoOverwriteError(aNoOverwrite));
+ },
+
+ /**
+ * Remove a single thumbnail, off the main thread.
+ *
+ * @return {Promise}
+ */
+ remove: function Storage_remove(aURL) {
+ return PageThumbsWorker.post("remove", [this.getFilePathForURL(aURL)]);
+ },
+
+ /**
+ * Remove all thumbnails, off the main thread.
+ *
+ * @return {Promise}
+ */
+ wipe: Task.async(function* Storage_wipe() {
+ //
+ // This operation may be launched during shutdown, so we need to
+ // take a few precautions to ensure that:
+ //
+ // 1. it is not interrupted by shutdown, in which case we
+ // could be leaving privacy-sensitive files on disk;
+ // 2. it is not launched too late during shutdown, in which
+ // case this could cause shutdown freezes (see bug 1005487,
+ // which will eventually be fixed by bug 965309)
+ //
+
+ let blocker = () => promise;
+
+ // The following operation will rise an error if we have already
+ // reached profileBeforeChange, in which case it is too late
+ // to clear the thumbnail wipe.
+ AsyncShutdown.profileBeforeChange.addBlocker(
+ "PageThumbs: removing all thumbnails",
+ blocker);
+
+ // Start the work only now that `profileBeforeChange` has had
+ // a chance to throw an error.
+
+ let promise = PageThumbsWorker.post("wipe", [this.path]);
+ try {
+ yield promise;
+ } finally {
+ // Generally, we will be done much before profileBeforeChange,
+ // so let's not hoard blockers.
+ if ("removeBlocker" in AsyncShutdown.profileBeforeChange) {
+ // `removeBlocker` was added with bug 985655. In the interest
+ // of backporting, let's degrade gracefully if `removeBlocker`
+ // doesn't exist.
+ AsyncShutdown.profileBeforeChange.removeBlocker(blocker);
+ }
+ }
+ }),
+
+ fileExistsForURL: function Storage_fileExistsForURL(aURL) {
+ return PageThumbsWorker.post("exists", [this.getFilePathForURL(aURL)]);
+ },
+
+ isFileRecentForURL: function Storage_isFileRecentForURL(aURL) {
+ return PageThumbsWorker.post("isFileRecent",
+ [this.getFilePathForURL(aURL),
+ MAX_THUMBNAIL_AGE_SECS]);
+ },
+
+ _calculateMD5Hash: function Storage_calculateMD5Hash(aValue) {
+ let hash = gCryptoHash;
+ let value = gUnicodeConverter.convertToByteArray(aValue);
+
+ hash.init(hash.MD5);
+ hash.update(value, value.length);
+ return this._convertToHexString(hash.finish(false));
+ },
+
+ _convertToHexString: function Storage_convertToHexString(aData) {
+ let hex = "";
+ for (let i = 0; i < aData.length; i++)
+ hex += ("0" + aData.charCodeAt(i).toString(16)).slice(-2);
+ return hex;
+ },
+
+ /**
+ * For functions that take a noOverwrite option, OS.File throws an error if
+ * the target file exists and noOverwrite is true. We don't consider that an
+ * error, and we don't want such errors propagated.
+ *
+ * @param {aNoOverwrite} The noOverwrite option used in the OS.File operation.
+ *
+ * @return {function} A function that should be passed as the second argument
+ * to then() (the `onError` argument).
+ */
+ _eatNoOverwriteError: function Storage__eatNoOverwriteError(aNoOverwrite) {
+ return function onError(err) {
+ if (!aNoOverwrite ||
+ !(err instanceof OS.File.Error) ||
+ !err.becauseExists) {
+ throw err;
+ }
+ };
+ },
+
+ // Deprecated, please do not use
+ getFileForURL: function Storage_getFileForURL_DEPRECATED(aURL) {
+ Deprecated.warning("PageThumbs.getFileForURL is deprecated. Please use PageThumbs.getFilePathForURL and OS.File",
+ "https://developer.mozilla.org/docs/JavaScript_OS.File");
+ // Note: Once this method has been removed, we can get rid of the dependency towards FileUtils
+ return new FileUtils.File(PageThumbsStorage.getFilePathForURL(aURL));
+ }
+};
+
+var PageThumbsStorageMigrator = {
+ get currentVersion() {
+ try {
+ return Services.prefs.getIntPref(PREF_STORAGE_VERSION);
+ } catch (e) {
+ // The pref doesn't exist, yet. Return version 0.
+ return 0;
+ }
+ },
+
+ set currentVersion(aVersion) {
+ Services.prefs.setIntPref(PREF_STORAGE_VERSION, aVersion);
+ },
+
+ migrate: function Migrator_migrate() {
+ let version = this.currentVersion;
+
+ // Storage version 1 never made it to beta.
+ // At the time of writing only Windows had (ProfD != ProfLD) and we
+ // needed to move thumbnails from the roaming profile to the locale
+ // one so that they're not needlessly included in backups and/or
+ // written via SMB.
+
+ // Storage version 2 also never made it to beta.
+ // The thumbnail folder structure has been changed and old thumbnails
+ // were not migrated. Instead, we just renamed the current folder to
+ // "<name>-old" and will remove it later.
+
+ if (version < 3) {
+ this.migrateToVersion3();
+ }
+
+ this.currentVersion = LATEST_STORAGE_VERSION;
+ },
+
+ /**
+ * Bug 239254 added support for having the disk cache and thumbnail
+ * directories on a local path (i.e. ~/.cache/) under Linux. We'll first
+ * try to move the old thumbnails to their new location. If that's not
+ * possible (because ProfD might be on a different file system than
+ * ProfLD) we'll just discard them.
+ *
+ * @param {string*} local The path to the local profile directory.
+ * Used for testing. Default argument is good for all non-testing uses.
+ * @param {string*} roaming The path to the roaming profile directory.
+ * Used for testing. Default argument is good for all non-testing uses.
+ */
+ migrateToVersion3: function Migrator_migrateToVersion3(
+ local = OS.Constants.Path.localProfileDir,
+ roaming = OS.Constants.Path.profileDir) {
+ PageThumbsWorker.post(
+ "moveOrDeleteAllThumbnails",
+ [OS.Path.join(roaming, THUMBNAIL_DIRECTORY),
+ OS.Path.join(local, THUMBNAIL_DIRECTORY)]
+ );
+ }
+};
+
+var PageThumbsExpiration = {
+ _filters: [],
+
+ init: function Expiration_init() {
+ gUpdateTimerManager.registerTimer("browser-cleanup-thumbnails", this,
+ EXPIRATION_INTERVAL_SECS);
+ },
+
+ addFilter: function Expiration_addFilter(aFilter) {
+ this._filters.push(aFilter);
+ },
+
+ removeFilter: function Expiration_removeFilter(aFilter) {
+ let index = this._filters.indexOf(aFilter);
+ if (index > -1)
+ this._filters.splice(index, 1);
+ },
+
+ notify: function Expiration_notify(aTimer) {
+ let urls = [];
+ let filtersToWaitFor = this._filters.length;
+
+ let expire = function expire() {
+ this.expireThumbnails(urls);
+ }.bind(this);
+
+ // No registered filters.
+ if (!filtersToWaitFor) {
+ expire();
+ return;
+ }
+
+ function filterCallback(aURLs) {
+ urls = urls.concat(aURLs);
+ if (--filtersToWaitFor == 0)
+ expire();
+ }
+
+ for (let filter of this._filters) {
+ if (typeof filter == "function")
+ filter(filterCallback)
+ else
+ filter.filterForThumbnailExpiration(filterCallback);
+ }
+ },
+
+ expireThumbnails: function Expiration_expireThumbnails(aURLsToKeep) {
+ let keep = aURLsToKeep.map(url => PageThumbsStorage.getLeafNameForURL(url));
+ let msg = [
+ PageThumbsStorage.path,
+ keep,
+ EXPIRATION_MIN_CHUNK_SIZE
+ ];
+
+ return PageThumbsWorker.post(
+ "expireFilesInDirectory",
+ msg
+ );
+ }
+};
+
+/**
+ * Interface to a dedicated thread handling I/O
+ */
+var PageThumbsWorker = new BasePromiseWorker("resource://gre/modules/PageThumbsWorker.js");
+// As the PageThumbsWorker performs I/O, we can receive instances of
+// OS.File.Error, so we need to install a decoder.
+PageThumbsWorker.ExceptionHandlers["OS.File.Error"] = OS.File.Error.fromMsg;
+
+var PageThumbsHistoryObserver = {
+ onDeleteURI(aURI, aGUID) {
+ PageThumbsStorage.remove(aURI.spec);
+ },
+
+ onClearHistory() {
+ PageThumbsStorage.wipe();
+ },
+
+ onTitleChanged: function () {},
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onVisit: function () {},
+ onPageChanged: function () {},
+ onDeleteVisits: function () {},
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver,
+ Ci.nsISupportsWeakReference])
+};
diff --git a/components/thumbnails/PageThumbsProtocol.js b/components/thumbnails/PageThumbsProtocol.js
new file mode 100644
index 000000000..41dfe96be
--- /dev/null
+++ b/components/thumbnails/PageThumbsProtocol.js
@@ -0,0 +1,154 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * PageThumbsProtocol.js
+ *
+ * This file implements the moz-page-thumb:// protocol and the corresponding
+ * channel delivering cached thumbnails.
+ *
+ * URL structure:
+ *
+ * moz-page-thumb://thumbnail/?url=http%3A%2F%2Fwww.mozilla.org%2F&revision=XX
+ *
+ * This URL requests an image for 'http://www.mozilla.org/'.
+ * The value of the revision key may change when the stored thumbnail changes.
+ */
+
+"use strict";
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+
+const SUBSTITUTING_URL_CID = "{dea9657c-18cf-4984-bde9-ccef5d8ab473}";
+
+/**
+ * Implements the thumbnail protocol handler responsible for moz-page-thumb: URLs.
+ */
+function Protocol() {
+}
+
+Protocol.prototype = {
+ /**
+ * The scheme used by this protocol.
+ */
+ get scheme() {
+ return PageThumbs.scheme;
+ },
+
+ /**
+ * The default port for this protocol (we don't support ports).
+ */
+ get defaultPort() {
+ return -1;
+ },
+
+ /**
+ * The flags specific to this protocol implementation.
+ */
+ get protocolFlags() {
+ return Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_NOAUTH;
+ },
+
+ /**
+ * Creates a new URI object that is suitable for loading by this protocol.
+ * @param aSpec The URI string in UTF8 encoding.
+ * @param aOriginCharset The charset of the document from which the URI originated.
+ * @return The newly created URI.
+ */
+ newURI: function Proto_newURI(aSpec, aOriginCharset) {
+ let uri = Components.classesByID[SUBSTITUTING_URL_CID].createInstance(Ci.nsIURL);
+ uri.spec = aSpec;
+ return uri;
+ },
+
+ /**
+ * Constructs a new channel from the given URI for this protocol handler.
+ * @param aURI The URI for which to construct a channel.
+ * @param aLoadInfo The Loadinfo which to use on the channel.
+ * @return The newly created channel.
+ */
+ newChannel2: function Proto_newChannel2(aURI, aLoadInfo) {
+ let {file} = aURI.QueryInterface(Ci.nsIFileURL);
+ let fileuri = Services.io.newFileURI(file);
+ let channel = Services.io.newChannelFromURIWithLoadInfo(fileuri, aLoadInfo);
+ channel.originalURI = aURI;
+ return channel;
+ },
+
+ newChannel: function Proto_newChannel(aURI) {
+ return this.newChannel2(aURI, null);
+ },
+
+ /**
+ * Decides whether to allow a blacklisted port.
+ * @return Always false, we'll never allow ports.
+ */
+ allowPort: () => false,
+
+ // nsISubstitutingProtocolHandler methods
+
+ /*
+ * Substituting the scheme and host isn't enough, we also transform the path.
+ * So declare no-op implementations for (get|set|has)Substitution methods and
+ * do all the work in resolveURI.
+ */
+
+ setSubstitution(root, baseURI) {},
+
+ getSubstitution(root) {
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ },
+
+ hasSubstitution(root) {
+ return false;
+ },
+
+ resolveURI(resURI) {
+ let {url} = parseURI(resURI);
+ let path = PageThumbsStorage.getFilePathForURL(url);
+ return OS.Path.toFileURI(path);
+ },
+
+ // xpcom machinery
+ classID: Components.ID("{5a4ae9b5-f475-48ae-9dce-0b4c1d347884}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler,
+ Ci.nsISubstitutingProtocolHandler])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Protocol]);
+
+/**
+ * Parses a given URI and extracts all parameters relevant to this protocol.
+ * @param aURI The URI to parse.
+ * @return The parsed parameters.
+ */
+function parseURI(aURI) {
+ if (aURI.host != PageThumbs.staticHost)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ let {query} = aURI.QueryInterface(Ci.nsIURL);
+ let params = {};
+
+ query.split("&").forEach(function (aParam) {
+ let [key, value] = aParam.split("=").map(decodeURIComponent);
+ params[key.toLowerCase()] = value;
+ });
+
+ return params;
+}
diff --git a/components/thumbnails/PageThumbsWorker.js b/components/thumbnails/PageThumbsWorker.js
new file mode 100644
index 000000000..83171c91f
--- /dev/null
+++ b/components/thumbnails/PageThumbsWorker.js
@@ -0,0 +1,176 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A worker dedicated for the I/O component of PageThumbs storage.
+ *
+ * Do not rely on the API of this worker. In a future version, it might be
+ * fully replaced by a OS.File global I/O worker.
+ */
+
+"use strict";
+
+importScripts("resource://gre/modules/osfile.jsm");
+
+var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");
+
+var File = OS.File;
+var Type = OS.Shared.Type;
+
+var worker = new PromiseWorker.AbstractWorker();
+worker.dispatch = function(method, args = []) {
+ return Agent[method](...args);
+};
+worker.postMessage = function(message, ...transfers) {
+ self.postMessage(message, ...transfers);
+};
+worker.close = function() {
+ self.close();
+};
+
+self.addEventListener("message", msg => worker.handleMessage(msg));
+
+
+var Agent = {
+ // Checks if the specified file exists and has an age less than as
+ // specifed (in seconds).
+ isFileRecent: function Agent_isFileRecent(path, maxAge) {
+ try {
+ let stat = OS.File.stat(path);
+ let maxDate = new Date();
+ maxDate.setSeconds(maxDate.getSeconds() - maxAge);
+ return stat.lastModificationDate > maxDate;
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error)) {
+ throw ex;
+ }
+ // file doesn't exist (or can't be stat'd) - must be stale.
+ return false;
+ }
+ },
+
+ remove: function Agent_removeFile(path) {
+ try {
+ OS.File.remove(path);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+
+ expireFilesInDirectory:
+ function Agent_expireFilesInDirectory(path, filesToKeep, minChunkSize) {
+ let entries = this.getFileEntriesInDirectory(path, filesToKeep);
+ let limit = Math.max(minChunkSize, Math.round(entries.length / 2));
+
+ for (let entry of entries) {
+ this.remove(entry.path);
+
+ // Check if we reached the limit of files to remove.
+ if (--limit <= 0) {
+ break;
+ }
+ }
+
+ return true;
+ },
+
+ getFileEntriesInDirectory:
+ function Agent_getFileEntriesInDirectory(path, skipFiles) {
+ let iter = new OS.File.DirectoryIterator(path);
+ try {
+ if (!iter.exists()) {
+ return [];
+ }
+
+ let skip = new Set(skipFiles);
+
+ let entries = [];
+ for (let entry in iter) {
+ if (!entry.isDir && !entry.isSymLink && !skip.has(entry.name)) {
+ entries.push(entry);
+ }
+ }
+ return entries;
+ } finally {
+ iter.close();
+ }
+ },
+
+ moveOrDeleteAllThumbnails:
+ function Agent_moveOrDeleteAllThumbnails(pathFrom, pathTo) {
+ OS.File.makeDir(pathTo, {ignoreExisting: true});
+ if (pathFrom == pathTo) {
+ return true;
+ }
+ let iter = new OS.File.DirectoryIterator(pathFrom);
+ if (iter.exists()) {
+ for (let entry in iter) {
+ if (entry.isDir || entry.isSymLink) {
+ continue;
+ }
+
+
+ let from = OS.Path.join(pathFrom, entry.name);
+ let to = OS.Path.join(pathTo, entry.name);
+
+ try {
+ OS.File.move(from, to, {noOverwrite: true, noCopy: true});
+ } catch (e) {
+ OS.File.remove(from);
+ }
+ }
+ }
+ iter.close();
+
+ try {
+ OS.File.removeEmptyDir(pathFrom);
+ } catch (e) {
+ // This could fail if there's something in
+ // the folder we're not permitted to remove.
+ }
+
+ return true;
+ },
+
+ writeAtomic: function Agent_writeAtomic(path, buffer, options) {
+ return File.writeAtomic(path,
+ buffer,
+ options);
+ },
+
+ makeDir: function Agent_makeDir(path, options) {
+ return File.makeDir(path, options);
+ },
+
+ copy: function Agent_copy(source, dest, options) {
+ return File.copy(source, dest, options);
+ },
+
+ wipe: function Agent_wipe(path) {
+ let iterator = new File.DirectoryIterator(path);
+ try {
+ for (let entry in iterator) {
+ try {
+ File.remove(entry.path);
+ } catch (ex) {
+ // If a file cannot be removed, we should still continue.
+ // This can happen at least for any of the following reasons:
+ // - access denied;
+ // - file has been removed recently during a previous wipe
+ // and the file system has not flushed that yet (yes, this
+ // can happen under Windows);
+ // - file has been removed by the user or another process.
+ }
+ }
+ } finally {
+ iterator.close();
+ }
+ },
+
+ exists: function Agent_exists(path) {
+ return File.exists(path);
+ },
+};
+
diff --git a/components/thumbnails/blankthumb.inc b/components/thumbnails/blankthumb.inc
new file mode 100644
index 000000000..06577c19c
--- /dev/null
+++ b/components/thumbnails/blankthumb.inc
@@ -0,0 +1,87 @@
+const BLANKTHUMB =
+"iVBORw0KGgoAAAANSUhEUgAAASwAAADsCAAAAADricSpAAASn0lEQVR4XuzQQREAAAwCoPWvaolV" +
+"8O1BBC41RrNkyZIlC1myZMmSJQtZsmTJkiULWbJkyZIlC1myZMmSJQtZsmTJkiULWbJkyZIliyfv" +
+"7HYbV5Ik/EUWScl2zwL7svvSc7MXp9uWyKrY2W0iUShI/tmLM6I7SJMpCrrw1xFZCZMNTTyQbNum" +
+"QSlGVEkSD6MHgWVaq81uNAT8eDL4v9eQFCVC6E+HlX6qW22GolkRAr3MBr+sdnPdTJmKQn86LLda" +
+"t4piKaVk6jgFwH+ul2a7uW3bVSVK0R8Mq9W1birLXEpIpHYoCgpgt8W1trqF5n+nv/TPf6Op1mtj" +
+"+pcKOykhCXQqALS3igEDdq3NrWqJ+INgpamulHkuRYAQIQlkxbTjuLZcIQ3QtuZWPZXQnwTL67qq" +
+"zEuEAP0WCChlR8W2CedI0YDfa2aFufwxsLy9Vc3zHBKgkIR2VDOpWsG5u9n7imA3Tf8Gd5X/4u+W" +
+"69ubl/N5DoEiIpJVzBMpooHETlLa0UYBUZG+/2pYrxfmZSoCpJBA+8YsegkALMAIyc2IuWy1eC1F" +
+"3zuGbb20aV5CAikEInlpphetgcEYzL41Y6hbo/E3ZzH4W1V/vXJ6Opc9UDulnRWjYtpDKJFbBIKy" +
+"zJK26m/rLF8vbZ7nAoKQROcqECUYVNNX3daMoW3VUMp3dJZpr798Oi8BoJAAAULS70NjFPub/UYI" +
+"sOY58Lb5G8Ji+3mZzqeiHUCSIktpTFUD0LgRIQRlLlLd/O1gef25zeelKJ0E9BkEQRtZST2t5Eso" +
+"R422tm8Gy5dfXs6zoG9UiC6L3JRET0kAiB33tAiv9VvBam+vLOcpf31BYiKxjcCI/WqyTGxIAkQs" +
+"AVv9RrDa25tOpwLc+rUh0ziKRJRezGuSAGIusG720WElq4tOS5AZpM9fl8ZRBqlzYU8ur2oqZqv6" +
+"HrDa60XnJRiIDMa6ZXQJRmulkqOmCbbNPj4s2uWi0yySjRIZQxWMEiBlyQAXhH7TWquOD8stYll0" +
+"iwLKWvdsLgEasdJHGpiKudbDw7Jj/o9TmuQmqvel0U8MJwxTiee5HfuGhXFjPr1V7ilK8fb/dLkB" +
+"Y4TiyY1mHRmWKpRTq51/RArHXD5hLXNTCsBIisBe23S1dNwYVlsnXVBywqSYTgXAAhx8Rv2SGMvT" +
+"/2oiMBSYDtyz3IClZOc1vdDe9wmDCp+Us9jeLpa2X68XBDGhsA8Ky7i5zFxFCmcSPU+5lpUSfF34" +
+"+vO6vjU2N0MUgoPCMq2hkzpDOU7PT4FHo4U+AT5h09mnvf2q2o+UIOwjwrLc4BQwGwDm5x8v52Ue" +
+"Wz2fBGWMx8tCgNfaMJoUavbxYNGaXSay7c5P8ySb07lgjILPaMQ0EgMQUQBrImgHc1aGcAETYEwR" +
+"BphoACzBF5XDxiizFANEgO2jwaIZpgCIZTk9Pb3MBjDXNxtrmfmcbHIrp2UanQZQFhDAJNn4WEOp" +
+"sYkZLDgLCWEBvjahcpr4lNxtLEo8uK/qOjWMYGpqFOtAsExt1iRSlgUmXKSYi6xPkcpjN9Xm9XyD" +
+"y8X2MkNEc5OO5axmogw8mtr1PJ0gZLA+vQpm8kYlroZpBijVcliHgWUqsLvHwrKs+mp4exGYj9WS" +
+"RdKihkDJzj1TPE0YSqlu0oEafHNzKYAh2azVpAz+iJVtcLLC17eLmeeREwDl/FwAKGE3fBRYphmV" +
+"HgmoXoXjjD+BimrbgI2dwKCu6PS0aAfFLADMcl52qBFy4zjOcjMRol/7qQ1YSmcs30W1OWVssm25" +
+"GmJ5kjFGp8kYOyY1AwCToMrHgGU1Q1LZDy6TmWd6T/n+7L8L9h877WUQCKC1RQDTKTLbVrHbcWK4" +
+"QShJGQDi6fn5SYYuiDY3lFcTVzYuZozt+XySwS2ens/z03lKmqAivB1jNTQbpggsLIzAsqLkuCXy" +
+"nZtyX+Hc8QQGzfLp1QCTi3e8FnvXqmB0CGc1EwLnjgFs08l3kxh79npjYWwc+TnmE1ZhfV27uBtA" +
+"Ac34CA/gtgqlIPVPQCKRLxnuQY91a+p5duPWOXMMhXaa6l91NepQ2VBBgR7eWabZxOAdZ6DSfODU" +
+"0L2is5WdPR4LdwxPLzMgLq+mH/ORcPURGryrkQz97wruNuqvi+22vr5u6YYupWVWUux7vAPb6bd6" +
+"+XVZh6hjMCWvPHSDN24Q2W4RgGUBe+Xt1WHqtUpNBQRGYEAARRcDozMRHYH2q7KBYZGBznuSvZ4O" +
+"4KyahsX2+HD2/mCbJq6vVdDW5GGT2VQxIZPmssGIjK+5VsDE8rT0McRYgkM0+BXHhCQQjA9ng9+u" +
+"6FzeVgTAdPOefNE0xwaGPDjmvhXWBp6eT1OAjeltWCUCPXQMTcMEYISRtWeQbDRVzPy0sDDNWACG" +
+"3g8TVmzCGW80q2PlebpcNAnj3lYAhEx7/BhWg8i4gH8jula3Zq+vG2Z7bblAbsbO2DqxG6boBq6y" +
+"nCdwN05omS0bgw0ZQmOE8YP/pwHz1tAcICRrD+B1s6SXaOuKyIACIp6V+RvFdUOhQhgEHsb67XrG" +
+"AG1nZAO2WSuay2PHkIbVhc7C8luVsN9iy1Syn612WXIqE4OmoshF1ePy6Dh1Q0mfQ4Qx1uPGMDNg" +
+"cA5ZXDdso/Wav1OXUl9fK3bmsE+io8hOuwDYkGFDGMYI2rbAfvSe1Qy9s4DtgrMLkxx3tkD9dYUe" +
+"0q5Ek+ds78nIbrbdv9UABDQ/+OhQK5RIYBJcGuq23AV7xaZy956+xzJZtVrX9fqvbasNkcsi7KSK" +
+"0COPDg2ERU7rWjewchMWRgYQBkAXzQOau8yyO7XXLUNtlangvmvBo8fQDaMuJvhqcss+Ru5g47bZ" +
+"3NOQ0Z0J25awoV1f32oGGxCmPTYs7L7CbNU9K8Bg5+4dADhJdjLuSNF/sO7kbbw/rJXvGhk/NCyT" +
+"axVGGNpqOloMzsp5MrqM9SJBjZ+F3q7G+PViZ1Dh0Z1lgwDWaoPZNkhaNhh7QGU7Jj6Q3X8C23Np" +
+"Nv3GZcu+JaD5sWFhMN4urxuGtifDzrPNXueP4xy2/Q4o08PF2HqacecuDBfDQR5mS+PXK1xW8KV1" +
+"tkqUtntbEU9TQklm3YXBWUnr/H+4csjFrhcAm/TzI48OYHBtgmtVa6IbHACQkfczBuGia4tZ95d8" +
+"d4fcDVNchyBeveRwgg2PC8uNTpVdOynthSx+nxEY1tXIpx3mgEnueI20mC/0rNDa5rBAAI8LK2Ux" +
+"aEfDzko5lBoEDWT7tq+cpAZaYFjWiunnq1qFibI1lceHJSjbAMz0MSSL5GXplrFg8JUM7ry11rRV" +
+"ykCt1dTT48Iy2MhynK5560/5prAMyMLIpN+S6N2G1YUvS4v6lrbq4YJA4Ie+yXo1FERQEYxeEQhl" +
+"SQqZafyA7/V3dkJb29bBVlnZWMsEetAYprNEyHlNQ+dKUyGMMMhc4wQg8N0Y9qjw62oZPCRwrB8W" +
+"lrIR95AgM5mrW56zvsxC+F4Kxzv51NUebDUUB4CFRb0CVmcqelx9c+8iNKC6ay6DYfNe3UB0CFhp" +
+"AStNMTb67O/JCwNeZEAjp5EUxkBb39q92BmwHx6WLUxvllu4cn7vvVVm80EMnUHk+tYMvofqIDG0" +
+"TCdrqHtcdOf5KT6ModNjtGqZD1ipgB4UlkyADfZoqLH2jnZHqNMpLL+b7Cww2O+vfD5Gz8JGhju4" +
+"UJYkMJ0XGindXNzu/13PN0oDioeGVQAsu0eEBXfsJQPlubxnjzGJWOv6PitjC+mhYcnCFlh4oHXz" +
+"lYHyErj3lN9LIYb1tX1oK7AeOoYKLAwK31gK77ySvfOT3+vvea63WY2EKQ8NC6IZW4VttBAMhsrX" +
+"tjHj3MA9x1jXqpHUyNNgBeiRYxhVGBPRLEYLjQwEOJbomXRQfW/eeh9VnqZHdxaWLSgY3+ztY1ae" +
+"Jsuj7XoNeMxSq0aUQ2EePYayCmALmKBVj/Fj5EV7RfOsDGZiQTvgIV7YUQ0fjaQU6ZEfZjPXn4Yp" +
+"AARsDUBG0UimjOJp6XsWbJdqlqebXd6XKx+hcgXOPwI98u37CKA5u0YYcCz/UjKyGdVwu7y+bfnU" +
+"9ma4VvJ+WPe59gGrrCY9es+KCgYsjFTc0FwExesYspQbbK9I5RwAbgIC3+reK518v1QR4pGdpQDc" +
+"vBOzpjItBbPvKWyTatfLBeF92tSCcUQaq7Okt9uuMinTgFJ4aGfJTFdD97yBZrBwa9WkhnavbUMC" +
+"tF3OgM7zddWCRy5GWxtIpQaGJR7+0e5JTmcZgw3G6+r3nvfIhWttgJmeXl6KBxva0K4eWY2+cras" +
+"B+9ZRKkGC/LJZCPbSisNch4Rcg0AU24377Xqzrg6DBgxgx76VpjAK4AEaN9BNCcmfUCbUSbVLtZ7" +
+"rJw+m54lPXIM0/3uH8MCrH5pM+/IY0ztvgb7ZgDHa7MeP4bEfBG2wXlrQiOCdwwWBkAeCfZhpd0A" +
+"i5OViRPo0WGJ+WrRBFhG+PcO1khMd43lWhtYUaJngUqNk8R1Fb7d/GQDU4FHd5bMVDbLFuBEhQEj" +
+"PuRloG21GgDiST0JplBgcQcVxgY4Sw8PCyjThmmF/i4hupM/M/T91bTWQACmXU49DVQwuLTmWzHN" +
+"9j4f4atkjNe/GjDJglwSayWF+IKW+eati9oNbu5PzcDzi6SHH0oFcwFo4O7OqIYl7PPaavrGeTSi" +
+"y11P0kAs0iG+WE1oBSyliRDYYpT4hLxtlnogBup1DOCuBrC8IPHwzkIwT0C/urv5polt86Hstr5t" +
+"wxPe19W+z0rn43xln7RagKA7prW+aDABOHooeDMaW1lvLB0khghtDbAgmWWb4SvEnEVJNgakmNRu" +
+"sbKwfkziKD0LsRqhACUImay/rqIejBVyy9fDyvH0LKGDfC2DWGYwrXUdGSkXs68L9U0rPTayagjK" +
+"WSCOEkMU6w4IlBftfPFFWdOQOaPmm+uFzk+SOAgsEOEtI5m8hJPTF5lNcWPpayOrBjD/IzgQLCHK" +
+"3uPHb6wy4su8zKSBlLH2esjkj1kSB3JWjg+WoMeF7zAS95dDl3LrFn4CpOuG5+cQR4IForRqhAN6" +
+"XPbXM+iYhAcL5eyWrBrA9GNC4kiwBIqtJaU8uPH1EGoKek8lNomBlV5OEseCBUJlNWBEAmzta9O7" +
+"AZUSJBISlAGFFOr+Fc4/ktWRYpgrYk+r8ZUh3iDFFEpwowxCktywYPpHSDrcNzrJcK4XgIaUGRL3" +
+"ZQYpJL37KFa+CAPxo0hwOFgI4qVuAC3E1xWSkH2D6IjKzUjoZZHQAWGBiJe/6u4tEDRjxKeUjSo1" +
+"1j0rAJ3OgIBj9axsW7F265b9hXXQBt0Kp1GaqmfF6YckcUhYCJh2Wk48n+fVjBjl0Vo5jSw/QuKo" +
+"sEAwsXZwrC8A04gYj0Wy2hdCxFFhCcEUqxG2pYHWh9zsGJM4lm4GwfSjSIiDwkpa2kAChNxRkbLk" +
+"kwg9VLYNgvKPSUIcE9ZIiyGEcoQQ+gBZDJhutXYxdawON2elZMFT/GzkCJHOcHqhmbvy3Qumue/t" +
+"cGRYSevMTstW4rKcfd/ipizfS6G8o0I7K8GhY5hJnOpOyyQXZdUQt2SkkdO4CqKnl2R1cGeBLLzE" +
+"zysAuIYYexgGxCC/k8NmwIJ4OufMcHBnpbdiZoM+i5+6eXFn0GoNMEjl+SwF4tiwRlpTVCu90edG" +
+"mDuSelCJaq/F/DTnnyYOH8N+eD/Fr95c+sxDNQIjkzJ2omJZAiHg8M4aeTGpmqFhCewvfd1ygoxl" +
+"joiQ9L1gCRkTkWCUTd1G9+GQ0QV3qIh5Ljur7wQrZRThW44SNyVjAEZSaJpLKCJCgfhGPQuQZQUO" +
+"xVbbyEt+lzAeoqqYQlJm8Ls5K6UIPMzohvei2AZUZZpDUuy0vp2zkEUA2IpWN/c3TGlKYBpvERpl" +
+"rd1VSNLA6uiwRinADYpK25qlZGJQTwiylAyJqpT8a8Xe3PUNYyi0HyWkKIVPSV0V01y0eyoiFOI7" +
+"wkpnsEcIaSrxBVgqZZ6LFFLsuJLV94MlEGiHJYSilAjQR6wUMS+l7F09dmPpb2WF/snfKYNxChtj" +
+"XFuzLTOKvZGrgBAZYoWEBOJbwkpcJlntsPpvrSUloZAU6hKctpQQiG8MC7PjoveWAZMFJB4QjLYS" +
+"Uj9mfM/RAYQRIGyTuMAC32l0CSv3pPitnZXuwtBba98Z5nnte26CzlXf11mju0CMrDzYqo9jnhLV" +
+"93fW+E2+N1BZPa/OTAmZPwXWyAvTH0Zv6Ub1J8FKeeA2agQkgD8V1sjIDNJQ/tGwUuYdiQfQxMNI" +
+"N6CJh9L/tEPHBAAAMAiA1r+qJVbBzwcikBrzLFmykCVLlixZspAlS5YsWbKQJUuWLFmykCVLlixZ" +
+"spAlS5YsWbKQJUuWLFmyeEoXbr5fYDBbAAAAAElFTkSuQmCC";
+
diff --git a/components/thumbnails/content/backgroundPageThumbsContent.js b/components/thumbnails/content/backgroundPageThumbsContent.js
new file mode 100644
index 000000000..ab39e73f7
--- /dev/null
+++ b/components/thumbnails/content/backgroundPageThumbsContent.js
@@ -0,0 +1,201 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 } = Components;
+
+Cu.importGlobalProperties(['Blob', 'FileReader']);
+
+Cu.import("resource://gre/modules/PageThumbUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const STATE_LOADING = 1;
+const STATE_CAPTURING = 2;
+const STATE_CANCELED = 3;
+
+// NOTE: Copied from nsSandboxFlags.h
+/**
+ * This flag prevents content from creating new auxiliary browsing contexts,
+ * e.g. using the target attribute, the window.open() method, or the
+ * showModalDialog() method.
+ */
+const SANDBOXED_AUXILIARY_NAVIGATION = 0x2;
+
+const backgroundPageThumbsContent = {
+
+ init: function () {
+ Services.obs.addObserver(this, "document-element-inserted", true);
+
+ // We want a low network priority for this service - lower than b/g tabs
+ // etc - so set it to the lowest priority available.
+ this._webNav.QueryInterface(Ci.nsIDocumentLoader).
+ loadGroup.QueryInterface(Ci.nsISupportsPriority).
+ priority = Ci.nsISupportsPriority.PRIORITY_LOWEST;
+
+ docShell.allowMedia = false;
+ docShell.allowPlugins = false;
+ docShell.allowContentRetargeting = false;
+ let defaultFlags = Ci.nsIRequest.LOAD_ANONYMOUS |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE |
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY;
+ docShell.defaultLoadFlags = defaultFlags;
+ docShell.sandboxFlags |= SANDBOXED_AUXILIARY_NAVIGATION;
+
+ addMessageListener("BackgroundPageThumbs:capture",
+ this._onCapture.bind(this));
+ docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
+ },
+
+ observe: function (subj, topic, data) {
+ // Arrange to prevent (most) popup dialogs for this window - popups done
+ // in the parent (eg, auth) aren't prevented, but alert() etc are.
+ // disableDialogs only works on the current inner window, so it has
+ // to be called every page load, but before scripts run.
+ if (content && subj == content.document) {
+ content.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ disableDialogs();
+ }
+ },
+
+ get _webNav() {
+ return docShell.QueryInterface(Ci.nsIWebNavigation);
+ },
+
+ _onCapture: function (msg) {
+ this._nextCapture = {
+ id: msg.data.id,
+ url: msg.data.url,
+ };
+ if (this._currentCapture) {
+ if (this._state == STATE_LOADING) {
+ // Cancel the current capture.
+ this._state = STATE_CANCELED;
+ this._loadAboutBlank();
+ }
+ // Let the current capture finish capturing, or if it was just canceled,
+ // wait for onStateChange due to the about:blank load.
+ return;
+ }
+ this._startNextCapture();
+ },
+
+ _startNextCapture: function () {
+ if (!this._nextCapture)
+ return;
+ this._currentCapture = this._nextCapture;
+ delete this._nextCapture;
+ this._state = STATE_LOADING;
+ this._currentCapture.pageLoadStartDate = new Date();
+
+ try {
+ this._webNav.loadURI(this._currentCapture.url,
+ Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
+ null, null, null);
+ } catch (e) {
+ this._failCurrentCapture("BAD_URI");
+ delete this._currentCapture;
+ this._startNextCapture();
+ }
+ },
+
+ onStateChange: function (webProgress, req, flags, status) {
+ if (webProgress.isTopLevel &&
+ (flags & Ci.nsIWebProgressListener.STATE_STOP) &&
+ this._currentCapture) {
+ if (req.name == "about:blank") {
+ if (this._state == STATE_CAPTURING) {
+ // about:blank has loaded, ending the current capture.
+ this._finishCurrentCapture();
+ delete this._currentCapture;
+ this._startNextCapture();
+ }
+ else if (this._state == STATE_CANCELED) {
+ delete this._currentCapture;
+ this._startNextCapture();
+ }
+ }
+ else if (this._state == STATE_LOADING &&
+ Components.isSuccessCode(status)) {
+ // The requested page has loaded. Capture it.
+ this._state = STATE_CAPTURING;
+ this._captureCurrentPage();
+ }
+ else if (this._state != STATE_CANCELED) {
+ // Something went wrong. Cancel the capture. Loading about:blank
+ // while onStateChange is still on the stack does not actually stop
+ // the request if it redirects, so do it asyncly.
+ this._state = STATE_CANCELED;
+ if (!this._cancelTimer) {
+ this._cancelTimer =
+ Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._cancelTimer.init(() => {
+ this._loadAboutBlank();
+ delete this._cancelTimer;
+ }, 0, Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ }
+ }
+ },
+
+ _captureCurrentPage: function () {
+ let capture = this._currentCapture;
+ capture.finalURL = this._webNav.currentURI.spec;
+ capture.pageLoadTime = new Date() - capture.pageLoadStartDate;
+
+ let canvasDrawDate = new Date();
+
+ let finalCanvas = PageThumbUtils.createSnapshotThumbnail(content, null);
+ capture.canvasDrawTime = new Date() - canvasDrawDate;
+
+ finalCanvas.toBlob(blob => {
+ capture.imageBlob = new Blob([blob]);
+ // Load about:blank to finish the capture and wait for onStateChange.
+ this._loadAboutBlank();
+ });
+ },
+
+ _finishCurrentCapture: function () {
+ let capture = this._currentCapture;
+ let fileReader = new FileReader();
+ fileReader.onloadend = () => {
+ sendAsyncMessage("BackgroundPageThumbs:didCapture", {
+ id: capture.id,
+ imageData: fileReader.result,
+ finalURL: capture.finalURL,
+ });
+ };
+ fileReader.readAsArrayBuffer(capture.imageBlob);
+ },
+
+ _failCurrentCapture: function (reason) {
+ let capture = this._currentCapture;
+ sendAsyncMessage("BackgroundPageThumbs:didCapture", {
+ id: capture.id,
+ failReason: reason,
+ });
+ },
+
+ // We load about:blank to finish all captures, even canceled captures. Two
+ // reasons: GC the captured page, and ensure it can't possibly load any more
+ // resources.
+ _loadAboutBlank: function _loadAboutBlank() {
+ this._webNav.loadURI("about:blank",
+ Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
+ null, null, null);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver,
+ ]),
+};
+
+backgroundPageThumbsContent.init();
diff --git a/components/thumbnails/jar.mn b/components/thumbnails/jar.mn
new file mode 100644
index 000000000..c83c64e48
--- /dev/null
+++ b/components/thumbnails/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/.
+
+toolkit.jar:
+ content/global/backgroundPageThumbsContent.js (content/backgroundPageThumbsContent.js)
diff --git a/components/thumbnails/moz.build b/components/thumbnails/moz.build
new file mode 100644
index 000000000..957bc7df0
--- /dev/null
+++ b/components/thumbnails/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'BrowserPageThumbs.manifest',
+ 'PageThumbsProtocol.js',
+]
+
+EXTRA_JS_MODULES += [
+ 'PageThumbs.jsm',
+ 'PageThumbsWorker.js',
+ 'PageThumbUtils.jsm',
+]
+
+EXTRA_PP_JS_MODULES += [
+ 'BackgroundPageThumbs.jsm',
+]
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/timermanager/moz.build b/components/timermanager/moz.build
new file mode 100644
index 000000000..c75945528
--- /dev/null
+++ b/components/timermanager/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_MODULE = 'update'
+
+XPIDL_SOURCES += ['nsIUpdateTimerManager.idl']
+
+EXTRA_COMPONENTS += [
+ 'nsUpdateTimerManager.js',
+ 'nsUpdateTimerManager.manifest',
+]
diff --git a/components/timermanager/nsIUpdateTimerManager.idl b/components/timermanager/nsIUpdateTimerManager.idl
new file mode 100644
index 000000000..6f9e2d169
--- /dev/null
+++ b/components/timermanager/nsIUpdateTimerManager.idl
@@ -0,0 +1,54 @@
+/* -*- Mode: IDL; 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 nsITimerCallback;
+
+/**
+ * An interface describing a global application service that allows long
+ * duration (e.g. 1-7 or more days, weeks or months) timers to be registered
+ * and then fired.
+ */
+[scriptable, uuid(0765c92c-6145-4253-9db4-594d8023087e)]
+interface nsIUpdateTimerManager : nsISupports
+{
+ /**
+ * Register an interval with the timer manager. The timer manager
+ * periodically checks to see if the interval has expired and if it has
+ * calls the specified callback. This is persistent across application
+ * restarts and can handle intervals of long durations.
+ * @param id
+ * An id that identifies the interval, used for persistence
+ * @param callback
+ * A nsITimerCallback object that is notified when the interval
+ * expires
+ * @param interval
+ * The length of time, in seconds, of the interval
+ *
+ * Note: to avoid having to instantiate a component to call registerTimer
+ * the component can intead register an update-timer category with comma
+ * separated values as a single string representing the timer as follows.
+ *
+ * _xpcom_categories: [{ category: "update-timer",
+ * value: "contractID," +
+ * "method," +
+ * "id," +
+ * "preference," +
+ * "interval" }],
+ * the values are as follows
+ * contractID : the contract ID for the component.
+ * method : the method used to instantiate the interface. This should be
+ * either getService or createInstance depending on your
+ * component.
+ * id : the id that identifies the interval, used for persistence.
+ * preference : the preference to for timer interval. This value can be
+ * optional by specifying an empty string for the value.
+ * interval : the default interval in seconds for the timer.
+ */
+ void registerTimer(in AString id,
+ in nsITimerCallback callback,
+ in unsigned long interval);
+};
diff --git a/components/timermanager/nsUpdateTimerManager.js b/components/timermanager/nsUpdateTimerManager.js
new file mode 100644
index 000000000..3ba82f8c0
--- /dev/null
+++ b/components/timermanager/nsUpdateTimerManager.js
@@ -0,0 +1,319 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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", this);
+Components.utils.import("resource://gre/modules/Services.jsm", this);
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const PREF_APP_UPDATE_LASTUPDATETIME_FMT = "app.update.lastUpdateTime.%ID%";
+const PREF_APP_UPDATE_TIMERMINIMUMDELAY = "app.update.timerMinimumDelay";
+const PREF_APP_UPDATE_TIMERFIRSTINTERVAL = "app.update.timerFirstInterval";
+const PREF_APP_UPDATE_LOG = "app.update.log";
+
+const CATEGORY_UPDATE_TIMER = "update-timer";
+
+XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function tm_gLogEnabled() {
+ return Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false);
+});
+
+/**
+ * Logs a string to the error console.
+ * @param string
+ * The string to write to the error console.
+ */
+function LOG(string) {
+ if (gLogEnabled) {
+ dump("*** UTM:SVC " + string + "\n");
+ Services.console.logStringMessage("UTM:SVC " + string);
+ }
+}
+
+/**
+ * A manager for timers. Manages timers that fire over long periods of time
+ * (e.g. days, weeks, months).
+ * @constructor
+ */
+function TimerManager() {
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+}
+TimerManager.prototype = {
+ /**
+ * The Checker Timer
+ */
+ _timer: null,
+
+ /**
+ * The Checker Timer minimum delay interval as specified by the
+ * app.update.timerMinimumDelay pref. If the app.update.timerMinimumDelay
+ * pref doesn't exist this will default to 120000.
+ */
+ _timerMinimumDelay: null,
+
+ /**
+ * The set of registered timers.
+ */
+ _timers: { },
+
+ /**
+ * See nsIObserver.idl
+ */
+ observe: function TM_observe(aSubject, aTopic, aData) {
+ // Prevent setting the timer interval to a value of less than 30 seconds.
+ var minInterval = 30000;
+ // Prevent setting the first timer interval to a value of less than 10
+ // seconds.
+ var minFirstInterval = 10000;
+ switch (aTopic) {
+ case "utm-test-init":
+ // Enforce a minimum timer interval of 500 ms for tests and fall through
+ // to profile-after-change to initialize the timer.
+ minInterval = 500;
+ minFirstInterval = 500;
+ case "profile-after-change":
+ this._timerMinimumDelay = Math.max(1000 * Services.prefs.getIntPref(PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
+ minInterval);
+ // Prevent the timer delay between notifications to other consumers from
+ // being greater than 5 minutes which is 300000 milliseconds.
+ this._timerMinimumDelay = Math.min(this._timerMinimumDelay, 300000);
+ // Prevent the first interval from being less than the value of minFirstInterval
+ let firstInterval = Math.max(Services.prefs.getIntPref(PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
+ 30000), minFirstInterval);
+ // Prevent the first interval from being greater than 2 minutes which is
+ // 120000 milliseconds.
+ firstInterval = Math.min(firstInterval, 120000);
+ // Cancel the timer if it has already been initialized. This is primarily
+ // for tests.
+ this._canEnsureTimer = true;
+ this._ensureTimer(firstInterval);
+ break;
+ case "xpcom-shutdown":
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+
+ // Release everything we hold onto.
+ this._cancelTimer();
+ for (var timerID in this._timers) {
+ delete this._timers[timerID];
+ }
+ this._timers = null;
+ break;
+ }
+ },
+
+ /**
+ * Called when the checking timer fires.
+ *
+ * We only fire one notification each time, so that the operations are
+ * staggered. We don't want too many to happen at once, which could
+ * negatively impact responsiveness.
+ *
+ * @param timer
+ * The checking timer that fired.
+ */
+ notify: function TM_notify(timer) {
+ var nextDelay = null;
+ function updateNextDelay(delay) {
+ if (nextDelay === null || delay < nextDelay) {
+ nextDelay = delay;
+ }
+ }
+
+ // Each timer calls tryFire(), which figures out which is the one that
+ // wanted to be called earliest. That one will be fired; the others are
+ // skipped and will be done later.
+ var now = Math.round(Date.now() / 1000);
+
+ var callbackToFire = null;
+ var earliestIntendedTime = null;
+ var skippedFirings = false;
+ var lastUpdateTime = null;
+ function tryFire(callback, intendedTime) {
+ var selected = false;
+ if (intendedTime <= now) {
+ if (intendedTime < earliestIntendedTime ||
+ earliestIntendedTime === null) {
+ callbackToFire = callback;
+ earliestIntendedTime = intendedTime;
+ selected = true;
+ } else if (earliestIntendedTime !== null) {
+ skippedFirings = true;
+ }
+ }
+ // We do not need to updateNextDelay for the timer that actually fires;
+ // we'll update right after it fires, with the proper intended time.
+ // Note that we might select one, then select another later (with an
+ // earlier intended time); it is still ok that we did not update for
+ // the first one, since if we have skipped firings, the next delay
+ // will be the minimum delay anyhow.
+ if (!selected) {
+ updateNextDelay(intendedTime - now);
+ }
+ }
+
+ var catMan = Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager);
+ var entries = catMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
+ let value = catMan.getCategoryEntry(CATEGORY_UPDATE_TIMER, entry);
+ let [cid, method, timerID, prefInterval, defaultInterval, maxInterval] = value.split(",");
+
+ defaultInterval = parseInt(defaultInterval);
+ // cid and method are validated below when calling notify.
+ if (!timerID || !defaultInterval || isNaN(defaultInterval)) {
+ LOG("TimerManager:notify - update-timer category registered" +
+ (cid ? " for " + cid : "") + " without required parameters - " +
+ "skipping");
+ continue;
+ }
+
+ let interval = Services.prefs.getIntPref(prefInterval, defaultInterval);
+ // Allow the update-timer category to specify a maximum value to prevent
+ // values larger than desired.
+ maxInterval = parseInt(maxInterval);
+ if (maxInterval && !isNaN(maxInterval)) {
+ interval = Math.min(interval, maxInterval);
+ }
+ let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/,
+ timerID);
+ // Initialize the last update time to 0 when the preference isn't set so
+ // the timer will be notified soon after a new profile's first use.
+ lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate, 0);
+
+ // If the last update time is greater than the current time then reset
+ // it to 0 and the timer manager will correct the value when it fires
+ // next for this consumer.
+ if (lastUpdateTime > now) {
+ lastUpdateTime = 0;
+ }
+
+ if (lastUpdateTime == 0) {
+ Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
+ }
+
+ tryFire(function () {
+ try {
+ Components.classes[cid][method](Ci.nsITimerCallback).notify(timer);
+ LOG("TimerManager:notify - notified " + cid);
+ } catch (e) {
+ LOG("TimerManager:notify - error notifying component id: " +
+ cid + " ,error: " + e);
+ }
+ lastUpdateTime = now;
+ Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
+ updateNextDelay(lastUpdateTime + interval - now);
+ }, lastUpdateTime + interval);
+ }
+
+ for (let _timerID in this._timers) {
+ let timerID = _timerID; // necessary for the closure to work properly
+ let timerData = this._timers[timerID];
+ // If the last update time is greater than the current time then reset
+ // it to 0 and the timer manager will correct the value when it fires
+ // next for this consumer.
+ if (timerData.lastUpdateTime > now) {
+ let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
+ timerData.lastUpdateTime = 0;
+ Services.prefs.setIntPref(prefLastUpdate, timerData.lastUpdateTime);
+ }
+ tryFire(function () {
+ if (timerData.callback && timerData.callback.notify) {
+ try {
+ timerData.callback.notify(timer);
+ LOG("TimerManager:notify - notified timerID: " + timerID);
+ } catch (e) {
+ LOG("TimerManager:notify - error notifying timerID: " + timerID +
+ ", error: " + e);
+ }
+ } else {
+ LOG("TimerManager:notify - timerID: " + timerID + " doesn't " +
+ "implement nsITimerCallback - skipping");
+ }
+ lastUpdateTime = now;
+ timerData.lastUpdateTime = lastUpdateTime;
+ let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, timerID);
+ Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
+ updateNextDelay(timerData.lastUpdateTime + timerData.interval - now);
+ }, timerData.lastUpdateTime + timerData.interval);
+ }
+
+ if (callbackToFire) {
+ callbackToFire();
+ }
+
+ if (nextDelay !== null) {
+ if (skippedFirings) {
+ timer.delay = this._timerMinimumDelay;
+ } else {
+ timer.delay = Math.max(nextDelay * 1000, this._timerMinimumDelay);
+ }
+ this.lastTimerReset = Date.now();
+ } else {
+ this._cancelTimer();
+ }
+ },
+
+ /**
+ * Starts the timer, if necessary, and ensures that it will fire soon enough
+ * to happen after time |interval| (in milliseconds).
+ */
+ _ensureTimer: function (interval) {
+ if (!this._canEnsureTimer) {
+ return;
+ }
+ if (!this._timer) {
+ this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._timer.initWithCallback(this, interval,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+ this.lastTimerReset = Date.now();
+ } else if (Date.now() + interval < this.lastTimerReset + this._timer.delay) {
+ this._timer.delay = Math.max(this.lastTimerReset + interval - Date.now(), 0);
+ }
+ },
+
+ /**
+ * Stops the timer, if it is running.
+ */
+ _cancelTimer: function () {
+ if (this._timer) {
+ this._timer.cancel();
+ this._timer = null;
+ }
+ },
+
+ /**
+ * See nsIUpdateTimerManager.idl
+ */
+ registerTimer: function TM_registerTimer(id, callback, interval) {
+ LOG("TimerManager:registerTimer - id: " + id);
+ if (id in this._timers && callback != this._timers[id].callback) {
+ LOG("TimerManager:registerTimer - Ignoring second registration for " + id);
+ return;
+ }
+ let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
+ // Initialize the last update time to 0 when the preference isn't set so
+ // the timer will be notified soon after a new profile's first use.
+ let lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate, 0);
+ let now = Math.round(Date.now() / 1000);
+ if (lastUpdateTime > now) {
+ lastUpdateTime = 0;
+ }
+ if (lastUpdateTime == 0) {
+ Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
+ }
+ this._timers[id] = {callback: callback,
+ interval: interval,
+ lastUpdateTime: lastUpdateTime};
+
+ this._ensureTimer(interval * 1000);
+ },
+
+ classID: Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateTimerManager,
+ Ci.nsITimerCallback,
+ Ci.nsIObserver])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TimerManager]);
diff --git a/components/timermanager/nsUpdateTimerManager.manifest b/components/timermanager/nsUpdateTimerManager.manifest
new file mode 100644
index 000000000..73cb11e35
--- /dev/null
+++ b/components/timermanager/nsUpdateTimerManager.manifest
@@ -0,0 +1,3 @@
+component {B322A5C0-A419-484E-96BA-D7182163899F} nsUpdateTimerManager.js
+contract @mozilla.org/updates/timer-manager;1 {B322A5C0-A419-484E-96BA-D7182163899F}
+category profile-after-change nsUpdateTimerManager @mozilla.org/updates/timer-manager;1
diff --git a/components/tooltiptext/TooltipTextProvider.js b/components/tooltiptext/TooltipTextProvider.js
new file mode 100644
index 000000000..a63ab83ad
--- /dev/null
+++ b/components/tooltiptext/TooltipTextProvider.js
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function TooltipTextProvider() {}
+
+TooltipTextProvider.prototype = {
+ getNodeText(tipElement, textOut, directionOut) {
+ // Don't show the tooltip if the tooltip node is a document, browser, or disconnected.
+ if (!tipElement || !tipElement.ownerDocument ||
+ tipElement.localName == "browser" ||
+ (tipElement.ownerDocument.compareDocumentPosition(tipElement) &
+ tipElement.ownerDocument.DOCUMENT_POSITION_DISCONNECTED)) {
+ return false;
+ }
+
+ var defView = tipElement.ownerDocument.defaultView;
+ // XXX Work around bug 350679:
+ // "Tooltips can be fired in documents with no view".
+ if (!defView)
+ return false;
+
+ const XLinkNS = "http://www.w3.org/1999/xlink";
+ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var titleText = null;
+ var XLinkTitleText = null;
+ var SVGTitleText = null;
+ var XULtooltiptextText = null;
+ var lookingForSVGTitle = true;
+ var direction = tipElement.ownerDocument.dir;
+
+ // If the element is invalid per HTML5 Forms specifications and has no title,
+ // show the constraint validation error message.
+ if ((tipElement instanceof defView.HTMLInputElement ||
+ tipElement instanceof defView.HTMLTextAreaElement ||
+ tipElement instanceof defView.HTMLSelectElement ||
+ tipElement instanceof defView.HTMLButtonElement) &&
+ !tipElement.hasAttribute('title') &&
+ (!tipElement.form || !tipElement.form.noValidate)) {
+ // If the element is barred from constraint validation or valid,
+ // the validation message will be the empty string.
+ titleText = tipElement.validationMessage || null;
+ }
+
+ // If the element is an <input type='file'> without a title, we should show
+ // the current file selection.
+ if (!titleText &&
+ tipElement instanceof defView.HTMLInputElement &&
+ tipElement.type == 'file' &&
+ !tipElement.hasAttribute('title')) {
+ let files = tipElement.files;
+
+ try {
+ var bundle =
+ Services.strings.createBundle("chrome://global/locale/layout/HtmlForm.properties");
+ if (files.length == 0) {
+ if (tipElement.multiple) {
+ titleText = bundle.GetStringFromName("NoFilesSelected");
+ } else {
+ titleText = bundle.GetStringFromName("NoFileSelected");
+ }
+ } else {
+ titleText = files[0].name;
+ // For UX and performance (jank) reasons we cap the number of
+ // files that we list in the tooltip to 20 plus a "and xxx more"
+ // line, or to 21 if exactly 21 files were picked.
+ const TRUNCATED_FILE_COUNT = 20;
+ let count = Math.min(files.length, TRUNCATED_FILE_COUNT);
+ for (let i = 1; i < count; ++i) {
+ titleText += "\n" + files[i].name;
+ }
+ if (files.length == TRUNCATED_FILE_COUNT + 1) {
+ titleText += "\n" + files[TRUNCATED_FILE_COUNT].name;
+ } else if (files.length > TRUNCATED_FILE_COUNT + 1) {
+ let xmoreStr = bundle.GetStringFromName("AndNMoreFiles");
+ let xmoreNum = files.length - TRUNCATED_FILE_COUNT;
+ let tmp = {};
+ Cu.import("resource://gre/modules/PluralForm.jsm", tmp);
+ let andXMoreStr = tmp.PluralForm.get(xmoreNum, xmoreStr).replace("#1", xmoreNum);
+ titleText += "\n" + andXMoreStr;
+ }
+ }
+ } catch (e) {}
+ }
+
+ // Check texts against null so that title="" can be used to undefine a
+ // title on a child element.
+ while (tipElement &&
+ (titleText == null) && (XLinkTitleText == null) &&
+ (SVGTitleText == null) && (XULtooltiptextText == null)) {
+
+ if (tipElement.nodeType == defView.Node.ELEMENT_NODE) {
+ if (tipElement.namespaceURI == XULNS)
+ XULtooltiptextText = tipElement.getAttribute("tooltiptext");
+ else if (!(tipElement instanceof defView.SVGElement))
+ titleText = tipElement.getAttribute("title");
+
+ if ((tipElement instanceof defView.HTMLAnchorElement ||
+ tipElement instanceof defView.HTMLAreaElement ||
+ tipElement instanceof defView.HTMLLinkElement ||
+ tipElement instanceof defView.SVGAElement) && tipElement.href) {
+ XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title");
+ }
+ if (lookingForSVGTitle &&
+ (!(tipElement instanceof defView.SVGElement) ||
+ tipElement.parentNode.nodeType == defView.Node.DOCUMENT_NODE)) {
+ lookingForSVGTitle = false;
+ }
+ if (lookingForSVGTitle) {
+ for (let childNode of tipElement.childNodes) {
+ if (childNode instanceof defView.SVGTitleElement) {
+ SVGTitleText = childNode.textContent;
+ break;
+ }
+ }
+ }
+
+ direction = defView.getComputedStyle(tipElement, "")
+ .getPropertyValue("direction");
+ }
+
+ tipElement = tipElement.parentNode;
+ }
+
+ return [titleText, XLinkTitleText, SVGTitleText, XULtooltiptextText].some(function (t) {
+ if (t && /\S/.test(t)) {
+ // Make CRLF and CR render one line break each.
+ textOut.value = t.replace(/\r\n?/g, '\n');
+ directionOut.value = direction;
+ return true;
+ }
+
+ return false;
+ });
+ },
+
+ classID : Components.ID("{f376627f-0bbc-47b8-887e-fc92574cc91f}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITooltipTextProvider]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TooltipTextProvider]);
+
diff --git a/components/tooltiptext/TooltipTextProvider.manifest b/components/tooltiptext/TooltipTextProvider.manifest
new file mode 100644
index 000000000..a7dac6cd9
--- /dev/null
+++ b/components/tooltiptext/TooltipTextProvider.manifest
@@ -0,0 +1,2 @@
+component {f376627f-0bbc-47b8-887e-fc92574cc91f} TooltipTextProvider.js
+contract @mozilla.org/embedcomp/default-tooltiptextprovider;1 {f376627f-0bbc-47b8-887e-fc92574cc91f}
diff --git a/components/tooltiptext/moz.build b/components/tooltiptext/moz.build
new file mode 100644
index 000000000..f3576c88f
--- /dev/null
+++ b/components/tooltiptext/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'TooltipTextProvider.js',
+ 'TooltipTextProvider.manifest',
+]
diff --git a/components/typeaheadfind/content/notfound.wav b/components/typeaheadfind/content/notfound.wav
new file mode 100644
index 000000000..c6fd5cb86
--- /dev/null
+++ b/components/typeaheadfind/content/notfound.wav
Binary files differ
diff --git a/components/typeaheadfind/jar.mn b/components/typeaheadfind/jar.mn
new file mode 100644
index 000000000..173caf77a
--- /dev/null
+++ b/components/typeaheadfind/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/.
+
+toolkit.jar:
+ content/global/notfound.wav (content/notfound.wav)
diff --git a/components/typeaheadfind/moz.build b/components/typeaheadfind/moz.build
new file mode 100644
index 000000000..e8fe4c9a8
--- /dev/null
+++ b/components/typeaheadfind/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'nsITypeAheadFind.idl',
+]
+
+XPIDL_MODULE = 'fastfind'
+
+SOURCES += [
+ 'nsTypeAheadFind.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+JAR_MANIFESTS += ['jar.mn']
+
+with Files('**'):
+ BUG_COMPONENT = ('Toolkit', 'Find Toolbar')
diff --git a/components/typeaheadfind/nsITypeAheadFind.idl b/components/typeaheadfind/nsITypeAheadFind.idl
new file mode 100644
index 000000000..379d2c2a2
--- /dev/null
+++ b/components/typeaheadfind/nsITypeAheadFind.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/. */
+
+
+/********************************* #includes *********************************/
+
+#include "domstubs.idl" // nsIDOMElement, nsIDOMWindow
+#include "nsISupports.idl" // nsISupports
+
+
+/******************************** Declarations *******************************/
+
+interface mozIDOMWindow;
+interface nsIDocShell;
+
+
+/****************************** nsTypeAheadFind ******************************/
+
+[scriptable, uuid(ae501e28-c57f-4692-ac74-410e1bed98b7)]
+interface nsITypeAheadFind : nsISupports
+{
+ /****************************** Initializer ******************************/
+
+ /* Necessary initialization that can't happen in the constructor, either
+ * because function calls here may fail, or because the docShell is
+ * required. */
+ void init(in nsIDocShell aDocShell);
+
+
+ /***************************** Core functions ****************************/
+
+ /* Find aSearchString in page. If aLinksOnly is true, only search the page's
+ * hyperlinks for the string. */
+ unsigned short find(in AString aSearchString, in boolean aLinksOnly);
+
+ /* Find another match in the page. */
+ unsigned short findAgain(in boolean findBackwards, in boolean aLinksOnly);
+
+ /* Return the range of the most recent match. */
+ nsIDOMRange getFoundRange();
+
+
+ /**************************** Helper functions ***************************/
+
+ /* Change searched docShell. This happens when e.g. we use the same
+ * nsITypeAheadFind object to search different tabs. */
+ void setDocShell(in nsIDocShell aDocShell);
+
+ /* Change the look of the the "found match" selection to aToggle, and repaint
+ * the selection. */
+ void setSelectionModeAndRepaint(in short toggle);
+
+ /* Collapse the "found match" selection to its start. Because not all
+ * matches are owned by the same selection controller, this doesn't
+ * necessarily happen automatically. */
+ void collapseSelection();
+
+ /* Check if a range is visible */
+ boolean isRangeVisible(in nsIDOMRange aRange, in boolean aMustBeInViewPort);
+
+ /******************************* Attributes ******************************/
+
+ readonly attribute AString searchString;
+ // Most recent search string
+ attribute boolean caseSensitive; // Searches are case sensitive
+ attribute boolean entireWord; // Search for whole words only
+ readonly attribute nsIDOMElement foundLink;
+ // Most recent elem found, if a link
+ readonly attribute nsIDOMElement foundEditable;
+ // Most recent elem found, if editable
+ readonly attribute mozIDOMWindow currentWindow;
+ // Window of most recent match
+
+
+ /******************************* Constants *******************************/
+
+ /* Find return codes */
+ const unsigned short FIND_FOUND = 0;
+ // Successful find
+ const unsigned short FIND_NOTFOUND = 1;
+ // Unsuccessful find
+ const unsigned short FIND_WRAPPED = 2;
+ // Successful find, but wrapped around
+ const unsigned short FIND_PENDING = 3;
+ // Unknown status, find has not finished
+
+
+ /*************************************************************************/
+
+};
+
+
+/*****************************************************************************/
diff --git a/components/typeaheadfind/nsTypeAheadFind.cpp b/components/typeaheadfind/nsTypeAheadFind.cpp
new file mode 100644
index 000000000..4690383ab
--- /dev/null
+++ b/components/typeaheadfind/nsTypeAheadFind.cpp
@@ -0,0 +1,1328 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsMemory.h"
+#include "nsIServiceManager.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Services.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsCURILoader.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsNetUtil.h"
+#include "nsIURL.h"
+#include "nsIURI.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsISimpleEnumerator.h"
+#include "nsPIDOMWindow.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsString.h"
+#include "nsCRT.h"
+
+#include "nsIDOMNode.h"
+#include "mozilla/dom/Element.h"
+#include "nsIFrame.h"
+#include "nsFrameTraversal.h"
+#include "nsIImageDocument.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDocument.h"
+#include "nsISelection.h"
+#include "nsTextFragment.h"
+#include "nsIDOMNSEditableElement.h"
+#include "nsIEditor.h"
+
+#include "nsIDocShellTreeItem.h"
+#include "nsIWebNavigation.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsContentCID.h"
+#include "nsLayoutCID.h"
+#include "nsWidgetsCID.h"
+#include "nsIFormControl.h"
+#include "nsNameSpaceManager.h"
+#include "nsIWindowWatcher.h"
+#include "nsIObserverService.h"
+#include "nsFocusManager.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Link.h"
+#include "nsRange.h"
+#include "nsXBLBinding.h"
+
+#include "nsTypeAheadFind.h"
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTypeAheadFind)
+ NS_INTERFACE_MAP_ENTRY(nsITypeAheadFind)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITypeAheadFind)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTypeAheadFind)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTypeAheadFind)
+
+NS_IMPL_CYCLE_COLLECTION(nsTypeAheadFind, mFoundLink, mFoundEditable,
+ mCurrentWindow, mStartFindRange, mSearchRange,
+ mStartPointRange, mEndPointRange, mSoundInterface,
+ mFind, mFoundRange)
+
+static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
+
+#define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1"
+
+nsTypeAheadFind::nsTypeAheadFind():
+ mStartLinksOnlyPref(false),
+ mCaretBrowsingOn(false),
+ mDidAddObservers(false),
+ mLastFindLength(0),
+ mIsSoundInitialized(false),
+ mCaseSensitive(false),
+ mEntireWord(false)
+{
+}
+
+nsTypeAheadFind::~nsTypeAheadFind()
+{
+ nsCOMPtr<nsIPrefBranch> prefInternal(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefInternal) {
+ prefInternal->RemoveObserver("accessibility.typeaheadfind", this);
+ prefInternal->RemoveObserver("accessibility.browsewithcaret", this);
+ }
+}
+
+nsresult
+nsTypeAheadFind::Init(nsIDocShell* aDocShell)
+{
+ nsCOMPtr<nsIPrefBranch> prefInternal(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ mSearchRange = nullptr;
+ mStartPointRange = nullptr;
+ mEndPointRange = nullptr;
+ if (!prefInternal || !EnsureFind())
+ return NS_ERROR_FAILURE;
+
+ SetDocShell(aDocShell);
+
+ if (!mDidAddObservers) {
+ mDidAddObservers = true;
+ // ----------- Listen to prefs ------------------
+ nsresult rv = prefInternal->AddObserver("accessibility.browsewithcaret", this, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ----------- Get initial preferences ----------
+ PrefsReset();
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsTypeAheadFind::PrefsReset()
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(prefBranch, NS_ERROR_FAILURE);
+
+ prefBranch->GetBoolPref("accessibility.typeaheadfind.startlinksonly",
+ &mStartLinksOnlyPref);
+
+ bool isSoundEnabled = true;
+ prefBranch->GetBoolPref("accessibility.typeaheadfind.enablesound",
+ &isSoundEnabled);
+ nsXPIDLCString soundStr;
+ if (isSoundEnabled)
+ prefBranch->GetCharPref("accessibility.typeaheadfind.soundURL", getter_Copies(soundStr));
+
+ mNotFoundSoundURL = soundStr;
+
+ prefBranch->GetBoolPref("accessibility.browsewithcaret",
+ &mCaretBrowsingOn);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::SetCaseSensitive(bool isCaseSensitive)
+{
+ mCaseSensitive = isCaseSensitive;
+
+ if (mFind) {
+ mFind->SetCaseSensitive(mCaseSensitive);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::GetCaseSensitive(bool* isCaseSensitive)
+{
+ *isCaseSensitive = mCaseSensitive;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::SetEntireWord(bool isEntireWord)
+{
+ mEntireWord = isEntireWord;
+
+ if (mFind) {
+ mFind->SetEntireWord(mEntireWord);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::GetEntireWord(bool* isEntireWord)
+{
+ *isEntireWord = mEntireWord;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::SetDocShell(nsIDocShell* aDocShell)
+{
+ mDocShell = do_GetWeakReference(aDocShell);
+
+ mWebBrowserFind = do_GetInterface(aDocShell);
+ NS_ENSURE_TRUE(mWebBrowserFind, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIPresShell> presShell;
+ presShell = aDocShell->GetPresShell();
+ mPresShell = do_GetWeakReference(presShell);
+
+ ReleaseStrongMemberVariables();
+ return NS_OK;
+}
+
+void
+nsTypeAheadFind::ReleaseStrongMemberVariables()
+{
+ mStartFindRange = nullptr;
+ mStartPointRange = nullptr;
+ mSearchRange = nullptr;
+ mEndPointRange = nullptr;
+
+ mFoundLink = nullptr;
+ mFoundEditable = nullptr;
+ mFoundRange = nullptr;
+ mCurrentWindow = nullptr;
+
+ mSelectionController = nullptr;
+
+ mFind = nullptr;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::SetSelectionModeAndRepaint(int16_t aToggle)
+{
+ nsCOMPtr<nsISelectionController> selectionController =
+ do_QueryReferent(mSelectionController);
+ if (!selectionController) {
+ return NS_OK;
+ }
+
+ selectionController->SetDisplaySelection(aToggle);
+ selectionController->RepaintSelection(nsISelectionController::SELECTION_NORMAL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::CollapseSelection()
+{
+ nsCOMPtr<nsISelectionController> selectionController =
+ do_QueryReferent(mSelectionController);
+ if (!selectionController) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISelection> selection;
+ selectionController->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(selection));
+ if (selection)
+ selection->CollapseToStart();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ return PrefsReset();
+ } else if (!nsCRT::strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) &&
+ SameCOMIdentity(aSubject, mCurrentWindow)) {
+ ReleaseStrongMemberVariables();
+ }
+
+ return NS_OK;
+}
+
+void
+nsTypeAheadFind::SaveFind()
+{
+ if (mWebBrowserFind)
+ mWebBrowserFind->SetSearchString(mTypeAheadBuffer.get());
+
+ // save the length of this find for "not found" sound
+ mLastFindLength = mTypeAheadBuffer.Length();
+}
+
+void
+nsTypeAheadFind::PlayNotFoundSound()
+{
+ if (mNotFoundSoundURL.IsEmpty()) // no sound
+ return;
+
+ if (!mSoundInterface)
+ mSoundInterface = do_CreateInstance("@mozilla.org/sound;1");
+
+ if (mSoundInterface) {
+ mIsSoundInitialized = true;
+
+ if (mNotFoundSoundURL.EqualsLiteral("beep")) {
+ mSoundInterface->Beep();
+ return;
+ }
+
+ nsCOMPtr<nsIURI> soundURI;
+ if (mNotFoundSoundURL.EqualsLiteral("default"))
+ NS_NewURI(getter_AddRefs(soundURI), NS_LITERAL_CSTRING(TYPEAHEADFIND_NOTFOUND_WAV_URL));
+ else
+ NS_NewURI(getter_AddRefs(soundURI), mNotFoundSoundURL);
+
+ nsCOMPtr<nsIURL> soundURL(do_QueryInterface(soundURI));
+ if (soundURL)
+ mSoundInterface->Play(soundURL);
+ }
+}
+
+nsresult
+nsTypeAheadFind::FindItNow(nsIPresShell *aPresShell, bool aIsLinksOnly,
+ bool aIsFirstVisiblePreferred, bool aFindPrev,
+ uint16_t* aResult)
+{
+ *aResult = FIND_NOTFOUND;
+ mFoundLink = nullptr;
+ mFoundEditable = nullptr;
+ mFoundRange = nullptr;
+ mCurrentWindow = nullptr;
+ nsCOMPtr<nsIPresShell> startingPresShell (GetPresShell());
+ if (!startingPresShell) {
+ nsCOMPtr<nsIDocShell> ds = do_QueryReferent(mDocShell);
+ NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE);
+
+ startingPresShell = ds->GetPresShell();
+ mPresShell = do_GetWeakReference(startingPresShell);
+ }
+
+ nsCOMPtr<nsIPresShell> presShell(aPresShell);
+
+ if (!presShell) {
+ presShell = startingPresShell; // this is the current document
+
+ if (!presShell)
+ return NS_ERROR_FAILURE;
+ }
+
+ // There could be unflushed notifications which hide textareas or other
+ // elements that we don't want to find text in.
+ presShell->FlushPendingNotifications(Flush_Layout);
+
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+
+ if (!presContext)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISelection> selection;
+ nsCOMPtr<nsISelectionController> selectionController =
+ do_QueryReferent(mSelectionController);
+ if (!selectionController) {
+ GetSelection(presShell, getter_AddRefs(selectionController),
+ getter_AddRefs(selection)); // cache for reuse
+ mSelectionController = do_GetWeakReference(selectionController);
+ } else {
+ selectionController->GetSelection(
+ nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
+ }
+
+ nsCOMPtr<nsIDocShell> startingDocShell(presContext->GetDocShell());
+ NS_ASSERTION(startingDocShell, "Bug 175321 Crashes with Type Ahead Find [@ nsTypeAheadFind::FindItNow]");
+ if (!startingDocShell)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDocShellTreeItem> rootContentTreeItem;
+ nsCOMPtr<nsIDocShell> currentDocShell;
+
+ startingDocShell->GetSameTypeRootTreeItem(getter_AddRefs(rootContentTreeItem));
+ nsCOMPtr<nsIDocShell> rootContentDocShell =
+ do_QueryInterface(rootContentTreeItem);
+
+ if (!rootContentDocShell)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISimpleEnumerator> docShellEnumerator;
+ rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent,
+ nsIDocShell::ENUMERATE_FORWARDS,
+ getter_AddRefs(docShellEnumerator));
+
+ // Default: can start at the current document
+ nsCOMPtr<nsISupports> currentContainer =
+ do_QueryInterface(rootContentDocShell);
+
+ // Iterate up to current shell, if there's more than 1 that we're
+ // dealing with
+ bool hasMoreDocShells;
+
+ while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells)) && hasMoreDocShells) {
+ docShellEnumerator->GetNext(getter_AddRefs(currentContainer));
+ currentDocShell = do_QueryInterface(currentContainer);
+ if (!currentDocShell || currentDocShell == startingDocShell || aIsFirstVisiblePreferred)
+ break;
+ }
+
+ // ------------ Get ranges ready ----------------
+ nsCOMPtr<nsIDOMRange> returnRange;
+ if (NS_FAILED(GetSearchContainers(currentContainer,
+ (!aIsFirstVisiblePreferred ||
+ mStartFindRange) ?
+ selectionController.get() : nullptr,
+ aIsFirstVisiblePreferred, aFindPrev,
+ getter_AddRefs(presShell),
+ getter_AddRefs(presContext)))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int16_t rangeCompareResult = 0;
+ if (!mStartPointRange) {
+ mStartPointRange = new nsRange(presShell->GetDocument());
+ }
+
+ mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, mSearchRange, &rangeCompareResult);
+ // No need to wrap find in doc if starting at beginning
+ bool hasWrapped = (rangeCompareResult < 0);
+
+ if (mTypeAheadBuffer.IsEmpty() || !EnsureFind())
+ return NS_ERROR_FAILURE;
+
+ mFind->SetFindBackwards(aFindPrev);
+
+ while (true) { // ----- Outer while loop: go through all docs -----
+ while (true) { // === Inner while loop: go through a single doc ===
+ mFind->Find(mTypeAheadBuffer, mSearchRange, mStartPointRange,
+ mEndPointRange, getter_AddRefs(returnRange));
+
+ if (!returnRange)
+ break; // Nothing found in this doc, go to outer loop (try next doc)
+
+ // ------- Test resulting found range for success conditions ------
+ bool isInsideLink = false, isStartingLink = false;
+
+ if (aIsLinksOnly) {
+ // Don't check if inside link when searching all text
+ RangeStartsInsideLink(returnRange, presShell, &isInsideLink,
+ &isStartingLink);
+ }
+
+ bool usesIndependentSelection;
+ if (!IsRangeVisible(presShell, presContext, returnRange,
+ aIsFirstVisiblePreferred, false,
+ getter_AddRefs(mStartPointRange),
+ &usesIndependentSelection) ||
+ (aIsLinksOnly && !isInsideLink) ||
+ (mStartLinksOnlyPref && aIsLinksOnly && !isStartingLink)) {
+ // ------ Failure ------
+ // At this point mStartPointRange got updated to the first
+ // visible range in the viewport. We _may_ be able to just
+ // start there, if it's not taking us in the wrong direction.
+ if (aFindPrev) {
+ // We can continue at the end of mStartPointRange if its end is before
+ // the start of returnRange or coincides with it. Otherwise, we need
+ // to continue at the start of returnRange.
+ int16_t compareResult;
+ nsresult rv =
+ mStartPointRange->CompareBoundaryPoints(nsIDOMRange::START_TO_END,
+ returnRange, &compareResult);
+ if (NS_SUCCEEDED(rv) && compareResult <= 0) {
+ // OK to start at the end of mStartPointRange
+ mStartPointRange->Collapse(false);
+ } else {
+ // Start at the beginning of returnRange
+ returnRange->CloneRange(getter_AddRefs(mStartPointRange));
+ mStartPointRange->Collapse(true);
+ }
+ } else {
+ // We can continue at the start of mStartPointRange if its start is
+ // after the end of returnRange or coincides with it. Otherwise, we
+ // need to continue at the end of returnRange.
+ int16_t compareResult;
+ nsresult rv =
+ mStartPointRange->CompareBoundaryPoints(nsIDOMRange::END_TO_START,
+ returnRange, &compareResult);
+ if (NS_SUCCEEDED(rv) && compareResult >= 0) {
+ // OK to start at the start of mStartPointRange
+ mStartPointRange->Collapse(true);
+ } else {
+ // Start at the end of returnRange
+ returnRange->CloneRange(getter_AddRefs(mStartPointRange));
+ mStartPointRange->Collapse(false);
+ }
+ }
+ continue;
+ }
+
+ mFoundRange = returnRange;
+
+ // ------ Success! -------
+ // Hide old selection (new one may be on a different controller)
+ if (selection) {
+ selection->CollapseToStart();
+ SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ON);
+ }
+
+ // Make sure new document is selected
+ if (presShell != startingPresShell) {
+ // We are in a new document (because of frames/iframes)
+ mPresShell = do_GetWeakReference(presShell);
+ }
+
+ nsCOMPtr<nsIDocument> document =
+ do_QueryInterface(presShell->GetDocument());
+ NS_ASSERTION(document, "Wow, presShell doesn't have document!");
+ if (!document)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = document->GetInnerWindow();
+ NS_ASSERTION(window, "document has no window");
+ if (!window)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ if (usesIndependentSelection) {
+ /* If a search result is found inside an editable element, we'll focus
+ * the element only if focus is in our content window, i.e.
+ * |if (focusedWindow.top == ourWindow.top)| */
+ bool shouldFocusEditableElement = false;
+ if (fm) {
+ nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
+ nsresult rv = fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
+ if (NS_SUCCEEDED(rv) && focusedWindow) {
+ auto* fwPI = nsPIDOMWindowOuter::From(focusedWindow);
+ nsCOMPtr<nsIDocShellTreeItem> fwTreeItem
+ (do_QueryInterface(fwPI->GetDocShell(), &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIDocShellTreeItem> fwRootTreeItem;
+ rv = fwTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(fwRootTreeItem));
+ if (NS_SUCCEEDED(rv) && fwRootTreeItem == rootContentTreeItem)
+ shouldFocusEditableElement = true;
+ }
+ }
+ }
+
+ // We may be inside an editable element, and therefore the selection
+ // may be controlled by a different selection controller. Walk up the
+ // chain of parent nodes to see if we find one.
+ nsCOMPtr<nsIDOMNode> node;
+ returnRange->GetStartContainer(getter_AddRefs(node));
+ while (node) {
+ nsCOMPtr<nsIDOMNSEditableElement> editable = do_QueryInterface(node);
+ if (editable) {
+ // Inside an editable element. Get the correct selection
+ // controller and selection.
+ nsCOMPtr<nsIEditor> editor;
+ editable->GetEditor(getter_AddRefs(editor));
+ NS_ASSERTION(editor, "Editable element has no editor!");
+ if (!editor) {
+ break;
+ }
+ editor->GetSelectionController(
+ getter_AddRefs(selectionController));
+ if (selectionController) {
+ selectionController->GetSelection(
+ nsISelectionController::SELECTION_NORMAL,
+ getter_AddRefs(selection));
+ }
+ mFoundEditable = do_QueryInterface(node);
+
+ if (!shouldFocusEditableElement)
+ break;
+
+ // Otherwise move focus/caret to editable element
+ if (fm)
+ fm->SetFocus(mFoundEditable, 0);
+ break;
+ }
+ nsIDOMNode* tmp = node;
+ tmp->GetParentNode(getter_AddRefs(node));
+ }
+
+ // If we reach here without setting mFoundEditable, then something
+ // besides editable elements can cause us to have an independent
+ // selection controller. I don't know whether this is possible.
+ // Currently, we simply fall back to grabbing the document's selection
+ // controller in this case. Perhaps we should reject this find match
+ // and search again.
+ NS_ASSERTION(mFoundEditable, "Independent selection controller on "
+ "non-editable element!");
+ }
+
+ if (!mFoundEditable) {
+ // Not using a separate selection controller, so just get the
+ // document's controller and selection.
+ GetSelection(presShell, getter_AddRefs(selectionController),
+ getter_AddRefs(selection));
+ }
+ mSelectionController = do_GetWeakReference(selectionController);
+
+ // Select the found text
+ if (selection) {
+ selection->RemoveAllRanges();
+ selection->AddRange(returnRange);
+ }
+
+ if (!mFoundEditable && fm) {
+ fm->MoveFocus(window->GetOuterWindow(),
+ nullptr, nsIFocusManager::MOVEFOCUS_CARET,
+ nsIFocusManager::FLAG_NOSCROLL | nsIFocusManager::FLAG_NOSWITCHFRAME,
+ getter_AddRefs(mFoundLink));
+ }
+
+ // Change selection color to ATTENTION and scroll to it. Careful: we
+ // must wait until after we goof with focus above before changing to
+ // ATTENTION, or when we MoveFocus() and the selection is not on a
+ // link, we'll blur, which will lose the ATTENTION.
+ if (selectionController) {
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ATTENTION);
+ selectionController->ScrollSelectionIntoView(
+ nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_WHOLE_SELECTION,
+ nsISelectionController::SCROLL_CENTER_VERTICALLY |
+ nsISelectionController::SCROLL_SYNCHRONOUS);
+ }
+
+ mCurrentWindow = window;
+ *aResult = hasWrapped ? FIND_WRAPPED : FIND_FOUND;
+ return NS_OK;
+ }
+
+ // ======= end-inner-while (go through a single document) ==========
+
+ // ---------- Nothing found yet, try next document -------------
+ bool hasTriedFirstDoc = false;
+ do {
+ // ==== Second inner loop - get another while ====
+ if (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMoreDocShells))
+ && hasMoreDocShells) {
+ docShellEnumerator->GetNext(getter_AddRefs(currentContainer));
+ NS_ASSERTION(currentContainer, "HasMoreElements lied to us!");
+ currentDocShell = do_QueryInterface(currentContainer);
+
+ if (currentDocShell)
+ break;
+ }
+ else if (hasTriedFirstDoc) // Avoid potential infinite loop
+ return NS_ERROR_FAILURE; // No content doc shells
+
+ // Reached last doc shell, loop around back to first doc shell
+ rootContentDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeContent,
+ nsIDocShell::ENUMERATE_FORWARDS,
+ getter_AddRefs(docShellEnumerator));
+ hasTriedFirstDoc = true;
+ } while (docShellEnumerator); // ==== end second inner while ===
+
+ bool continueLoop = false;
+ if (currentDocShell != startingDocShell)
+ continueLoop = true; // Try next document
+ else if (!hasWrapped || aIsFirstVisiblePreferred) {
+ // Finished searching through docshells:
+ // If aFirstVisiblePreferred == true, we may need to go through all
+ // docshells twice -once to look for visible matches, the second time
+ // for any match
+ aIsFirstVisiblePreferred = false;
+ hasWrapped = true;
+ continueLoop = true; // Go through all docs again
+ }
+
+ if (continueLoop) {
+ if (NS_FAILED(GetSearchContainers(currentContainer, nullptr,
+ aIsFirstVisiblePreferred, aFindPrev,
+ getter_AddRefs(presShell),
+ getter_AddRefs(presContext)))) {
+ continue;
+ }
+
+ if (aFindPrev) {
+ // Reverse mode: swap start and end points, so that we start
+ // at end of document and go to beginning
+ nsCOMPtr<nsIDOMRange> tempRange;
+ mStartPointRange->CloneRange(getter_AddRefs(tempRange));
+ if (!mEndPointRange) {
+ mEndPointRange = new nsRange(presShell->GetDocument());
+ }
+
+ mStartPointRange = mEndPointRange;
+ mEndPointRange = tempRange;
+ }
+
+ continue;
+ }
+
+ // ------------- Failed --------------
+ break;
+ } // end-outer-while: go through all docs
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::GetSearchString(nsAString& aSearchString)
+{
+ aSearchString = mTypeAheadBuffer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::GetFoundLink(nsIDOMElement** aFoundLink)
+{
+ NS_ENSURE_ARG_POINTER(aFoundLink);
+ *aFoundLink = mFoundLink;
+ NS_IF_ADDREF(*aFoundLink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::GetFoundEditable(nsIDOMElement** aFoundEditable)
+{
+ NS_ENSURE_ARG_POINTER(aFoundEditable);
+ *aFoundEditable = mFoundEditable;
+ NS_IF_ADDREF(*aFoundEditable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::GetCurrentWindow(mozIDOMWindow** aCurrentWindow)
+{
+ NS_ENSURE_ARG_POINTER(aCurrentWindow);
+ *aCurrentWindow = mCurrentWindow;
+ NS_IF_ADDREF(*aCurrentWindow);
+ return NS_OK;
+}
+
+nsresult
+nsTypeAheadFind::GetSearchContainers(nsISupports *aContainer,
+ nsISelectionController *aSelectionController,
+ bool aIsFirstVisiblePreferred,
+ bool aFindPrev,
+ nsIPresShell **aPresShell,
+ nsPresContext **aPresContext)
+{
+ NS_ENSURE_ARG_POINTER(aContainer);
+ NS_ENSURE_ARG_POINTER(aPresShell);
+ NS_ENSURE_ARG_POINTER(aPresContext);
+
+ *aPresShell = nullptr;
+ *aPresContext = nullptr;
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
+ if (!docShell)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
+
+ RefPtr<nsPresContext> presContext;
+ docShell->GetPresContext(getter_AddRefs(presContext));
+
+ if (!presShell || !presContext)
+ return NS_ERROR_FAILURE;
+
+ nsIDocument* doc = presShell->GetDocument();
+
+ if (!doc)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIContent> rootContent;
+ nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(doc));
+ if (htmlDoc) {
+ nsCOMPtr<nsIDOMHTMLElement> bodyEl;
+ htmlDoc->GetBody(getter_AddRefs(bodyEl));
+ rootContent = do_QueryInterface(bodyEl);
+ }
+
+ if (!rootContent)
+ rootContent = doc->GetRootElement();
+
+ nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootContent));
+
+ if (!rootNode)
+ return NS_ERROR_FAILURE;
+
+ if (!mSearchRange) {
+ mSearchRange = new nsRange(doc);
+ }
+ nsCOMPtr<nsIDOMNode> searchRootNode = rootNode;
+
+ // Hack for XMLPrettyPrinter. nsFind can't handle complex anonymous content.
+ // If the root node has an XBL binding then there's not much we can do in
+ // in general, but we can try searching the binding's first child, which
+ // in the case of XMLPrettyPrinter contains the visible pretty-printed
+ // content.
+ nsXBLBinding* binding = rootContent->GetXBLBinding();
+ if (binding) {
+ nsIContent* anonContent = binding->GetAnonymousContent();
+ if (anonContent) {
+ searchRootNode = do_QueryInterface(anonContent->GetFirstChild());
+ }
+ }
+ mSearchRange->SelectNodeContents(searchRootNode);
+
+ if (!mStartPointRange) {
+ mStartPointRange = new nsRange(doc);
+ }
+ mStartPointRange->SetStart(searchRootNode, 0);
+ mStartPointRange->Collapse(true); // collapse to start
+
+ if (!mEndPointRange) {
+ mEndPointRange = new nsRange(doc);
+ }
+ nsCOMPtr<nsINode> searchRootTmp = do_QueryInterface(searchRootNode);
+ mEndPointRange->SetEnd(searchRootNode, searchRootTmp->Length());
+ mEndPointRange->Collapse(false); // collapse to end
+
+ // Consider current selection as null if
+ // it's not in the currently focused document
+ nsCOMPtr<nsIDOMRange> currentSelectionRange;
+ nsCOMPtr<nsIPresShell> selectionPresShell = GetPresShell();
+ if (aSelectionController && selectionPresShell && selectionPresShell == presShell) {
+ nsCOMPtr<nsISelection> selection;
+ aSelectionController->GetSelection(
+ nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
+ if (selection)
+ selection->GetRangeAt(0, getter_AddRefs(currentSelectionRange));
+ }
+
+ if (!currentSelectionRange) {
+ // Ensure visible range, move forward if necessary
+ // This uses ignores the return value, but usese the side effect of
+ // IsRangeVisible. It returns the first visible range after searchRange
+ IsRangeVisible(presShell, presContext, mSearchRange,
+ aIsFirstVisiblePreferred, true,
+ getter_AddRefs(mStartPointRange), nullptr);
+ }
+ else {
+ uint32_t startOffset;
+ nsCOMPtr<nsIDOMNode> startNode;
+ if (aFindPrev) {
+ currentSelectionRange->GetStartContainer(getter_AddRefs(startNode));
+ currentSelectionRange->GetStartOffset(&startOffset);
+ } else {
+ currentSelectionRange->GetEndContainer(getter_AddRefs(startNode));
+ currentSelectionRange->GetEndOffset(&startOffset);
+ }
+ if (!startNode)
+ startNode = rootNode;
+
+ // We need to set the start point this way, other methods haven't worked
+ mStartPointRange->SelectNode(startNode);
+ mStartPointRange->SetStart(startNode, startOffset);
+ }
+
+ mStartPointRange->Collapse(true); // collapse to start
+
+ presShell.forget(aPresShell);
+ presContext.forget(aPresContext);
+
+ return NS_OK;
+}
+
+void
+nsTypeAheadFind::RangeStartsInsideLink(nsIDOMRange *aRange,
+ nsIPresShell *aPresShell,
+ bool *aIsInsideLink,
+ bool *aIsStartingLink)
+{
+ *aIsInsideLink = false;
+ *aIsStartingLink = true;
+
+ // ------- Get nsIContent to test -------
+ nsCOMPtr<nsIDOMNode> startNode;
+ nsCOMPtr<nsIContent> startContent, origContent;
+ aRange->GetStartContainer(getter_AddRefs(startNode));
+ uint32_t startOffset;
+ aRange->GetStartOffset(&startOffset);
+
+ startContent = do_QueryInterface(startNode);
+ if (!startContent) {
+ NS_NOTREACHED("startContent should never be null");
+ return;
+ }
+ origContent = startContent;
+
+ if (startContent->IsElement()) {
+ nsIContent *childContent = startContent->GetChildAt(startOffset);
+ if (childContent) {
+ startContent = childContent;
+ }
+ }
+ else if (startOffset > 0) {
+ const nsTextFragment *textFrag = startContent->GetText();
+ if (textFrag) {
+ // look for non whitespace character before start offset
+ for (uint32_t index = 0; index < startOffset; index++) {
+ // FIXME: take content language into account when deciding whitespace.
+ if (!mozilla::dom::IsSpaceCharacter(
+ textFrag->CharAt(static_cast<int32_t>(index)))) {
+ *aIsStartingLink = false; // not at start of a node
+
+ break;
+ }
+ }
+ }
+ }
+
+ // ------- Check to see if inside link ---------
+
+ // We now have the correct start node for the range
+ // Search for links, starting with startNode, and going up parent chain
+
+ nsCOMPtr<nsIAtom> hrefAtom(NS_Atomize("href"));
+ nsCOMPtr<nsIAtom> typeAtom(NS_Atomize("type"));
+
+ while (true) {
+ // Keep testing while startContent is equal to something,
+ // eventually we'll run out of ancestors
+
+ if (startContent->IsHTMLElement()) {
+ nsCOMPtr<mozilla::dom::Link> link(do_QueryInterface(startContent));
+ if (link) {
+ // Check to see if inside HTML link
+ *aIsInsideLink = startContent->HasAttr(kNameSpaceID_None, hrefAtom);
+ return;
+ }
+ }
+ else {
+ // Any xml element can be an xlink
+ *aIsInsideLink = startContent->HasAttr(kNameSpaceID_XLink, hrefAtom);
+ if (*aIsInsideLink) {
+ if (!startContent->AttrValueIs(kNameSpaceID_XLink, typeAtom,
+ NS_LITERAL_STRING("simple"),
+ eCaseMatters)) {
+ *aIsInsideLink = false; // Xlink must be type="simple"
+ }
+
+ return;
+ }
+ }
+
+ // Get the parent
+ nsCOMPtr<nsIContent> parent = startContent->GetParent();
+ if (!parent)
+ break;
+
+ nsIContent* parentsFirstChild = parent->GetFirstChild();
+
+ // We don't want to look at a whitespace-only first child
+ if (parentsFirstChild && parentsFirstChild->TextIsOnlyWhitespace()) {
+ parentsFirstChild = parentsFirstChild->GetNextSibling();
+ }
+
+ if (parentsFirstChild != startContent) {
+ // startContent wasn't a first child, so we conclude that
+ // if this is inside a link, it's not at the beginning of it
+ *aIsStartingLink = false;
+ }
+
+ startContent = parent;
+ }
+
+ *aIsStartingLink = false;
+}
+
+/* Find another match in the page. */
+NS_IMETHODIMP
+nsTypeAheadFind::FindAgain(bool aFindBackwards, bool aLinksOnly,
+ uint16_t* aResult)
+
+{
+ *aResult = FIND_NOTFOUND;
+
+ if (!mTypeAheadBuffer.IsEmpty())
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ FindItNow(nullptr, aLinksOnly, false, aFindBackwards, aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::Find(const nsAString& aSearchString, bool aLinksOnly,
+ uint16_t* aResult)
+{
+ *aResult = FIND_NOTFOUND;
+
+ nsCOMPtr<nsIPresShell> presShell (GetPresShell());
+ if (!presShell) {
+ nsCOMPtr<nsIDocShell> ds (do_QueryReferent(mDocShell));
+ NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE);
+
+ presShell = ds->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+ mPresShell = do_GetWeakReference(presShell);
+ }
+
+ nsCOMPtr<nsISelection> selection;
+ nsCOMPtr<nsISelectionController> selectionController =
+ do_QueryReferent(mSelectionController);
+ if (!selectionController) {
+ GetSelection(presShell, getter_AddRefs(selectionController),
+ getter_AddRefs(selection)); // cache for reuse
+ mSelectionController = do_GetWeakReference(selectionController);
+ } else {
+ selectionController->GetSelection(
+ nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
+ }
+
+ if (selection)
+ selection->CollapseToStart();
+
+ if (aSearchString.IsEmpty()) {
+ mTypeAheadBuffer.Truncate();
+
+ // These will be initialized to their true values after the first character
+ // is typed
+ mStartFindRange = nullptr;
+ mSelectionController = nullptr;
+
+ *aResult = FIND_FOUND;
+ return NS_OK;
+ }
+
+ bool atEnd = false;
+ if (mTypeAheadBuffer.Length()) {
+ const nsAString& oldStr = Substring(mTypeAheadBuffer, 0, mTypeAheadBuffer.Length());
+ const nsAString& newStr = Substring(aSearchString, 0, mTypeAheadBuffer.Length());
+ if (oldStr.Equals(newStr))
+ atEnd = true;
+
+ const nsAString& newStr2 = Substring(aSearchString, 0, aSearchString.Length());
+ const nsAString& oldStr2 = Substring(mTypeAheadBuffer, 0, aSearchString.Length());
+ if (oldStr2.Equals(newStr2))
+ atEnd = true;
+
+ if (!atEnd)
+ mStartFindRange = nullptr;
+ }
+
+ if (!mIsSoundInitialized && !mNotFoundSoundURL.IsEmpty()) {
+ // This makes sure system sound library is loaded so that
+ // there's no lag before the first sound is played
+ // by waiting for the first keystroke, we still get the startup time benefits.
+ mIsSoundInitialized = true;
+ mSoundInterface = do_CreateInstance("@mozilla.org/sound;1");
+ if (mSoundInterface && !mNotFoundSoundURL.EqualsLiteral("beep")) {
+ mSoundInterface->Init();
+ }
+ }
+
+#ifdef XP_WIN
+ // After each keystroke, ensure sound object is destroyed, to free up memory
+ // allocated for error sound, otherwise Windows' nsISound impl
+ // holds onto the last played sound, using up memory.
+ mSoundInterface = nullptr;
+#endif
+
+ int32_t bufferLength = mTypeAheadBuffer.Length();
+
+ mTypeAheadBuffer = aSearchString;
+
+ bool isFirstVisiblePreferred = false;
+
+ // --------- Initialize find if 1st char ----------
+ if (bufferLength == 0) {
+ // If you can see the selection (not collapsed or thru caret browsing),
+ // or if already focused on a page element, start there.
+ // Otherwise we're going to start at the first visible element
+ bool isSelectionCollapsed = true;
+ if (selection)
+ selection->GetIsCollapsed(&isSelectionCollapsed);
+
+ // If true, we will scan from top left of visible area
+ // If false, we will scan from start of selection
+ isFirstVisiblePreferred = !atEnd && !mCaretBrowsingOn && isSelectionCollapsed;
+ if (isFirstVisiblePreferred) {
+ // Get the focused content. If there is a focused node, ensure the
+ // selection is at that point. Otherwise, we will just want to start
+ // from the caret position or the beginning of the document.
+ nsPresContext* presContext = presShell->GetPresContext();
+ NS_ENSURE_TRUE(presContext, NS_OK);
+
+ nsCOMPtr<nsIDocument> document =
+ do_QueryInterface(presShell->GetDocument());
+ if (!document)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ if (fm) {
+ nsPIDOMWindowOuter* window = document->GetWindow();
+ nsCOMPtr<nsIDOMElement> focusedElement;
+ nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
+ fm->GetFocusedElementForWindow(window, false,
+ getter_AddRefs(focusedWindow),
+ getter_AddRefs(focusedElement));
+ // If the root element is focused, then it's actually the document
+ // that has the focus, so ignore this.
+ if (focusedElement &&
+ !SameCOMIdentity(focusedElement, document->GetRootElement())) {
+ fm->MoveCaretToFocus(window);
+ isFirstVisiblePreferred = false;
+ }
+ }
+ }
+ }
+
+ // ----------- Find the text! ---------------------
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ nsresult rv = FindItNow(nullptr, aLinksOnly, isFirstVisiblePreferred,
+ false, aResult);
+
+ // ---------Handle success or failure ---------------
+ if (NS_SUCCEEDED(rv)) {
+ if (mTypeAheadBuffer.Length() == 1) {
+ // If first letter, store where the first find succeeded
+ // (mStartFindRange)
+
+ mStartFindRange = nullptr;
+ if (selection) {
+ nsCOMPtr<nsIDOMRange> startFindRange;
+ selection->GetRangeAt(0, getter_AddRefs(startFindRange));
+ if (startFindRange)
+ startFindRange->CloneRange(getter_AddRefs(mStartFindRange));
+ }
+ }
+ }
+ else {
+ // Error sound
+ if (mTypeAheadBuffer.Length() > mLastFindLength)
+ PlayNotFoundSound();
+ }
+
+ SaveFind();
+ return NS_OK;
+}
+
+void
+nsTypeAheadFind::GetSelection(nsIPresShell *aPresShell,
+ nsISelectionController **aSelCon,
+ nsISelection **aDOMSel)
+{
+ if (!aPresShell)
+ return;
+
+ // if aCurrentNode is nullptr, get selection for document
+ *aDOMSel = nullptr;
+
+ nsPresContext* presContext = aPresShell->GetPresContext();
+
+ nsIFrame *frame = aPresShell->GetRootFrame();
+
+ if (presContext && frame) {
+ frame->GetSelectionController(presContext, aSelCon);
+ if (*aSelCon) {
+ (*aSelCon)->GetSelection(nsISelectionController::SELECTION_NORMAL,
+ aDOMSel);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::GetFoundRange(nsIDOMRange** aFoundRange)
+{
+ NS_ENSURE_ARG_POINTER(aFoundRange);
+ if (mFoundRange == nullptr) {
+ *aFoundRange = nullptr;
+ return NS_OK;
+ }
+
+ mFoundRange->CloneRange(aFoundRange);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::IsRangeVisible(nsIDOMRange *aRange,
+ bool aMustBeInViewPort,
+ bool *aResult)
+{
+ // Jump through hoops to extract the docShell from the range.
+ nsCOMPtr<nsIDOMNode> node;
+ aRange->GetStartContainer(getter_AddRefs(node));
+ nsCOMPtr<nsIDOMDocument> document;
+ node->GetOwnerDocument(getter_AddRefs(document));
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ document->GetDefaultView(getter_AddRefs(window));
+ nsCOMPtr<nsIWebNavigation> navNav (do_GetInterface(window));
+ nsCOMPtr<nsIDocShell> docShell (do_GetInterface(navNav));
+
+ // Set up the arguments needed to check if a range is visible.
+ nsCOMPtr<nsIPresShell> presShell (docShell->GetPresShell());
+ RefPtr<nsPresContext> presContext = presShell->GetPresContext();
+ nsCOMPtr<nsIDOMRange> startPointRange = new nsRange(presShell->GetDocument());
+ *aResult = IsRangeVisible(presShell, presContext, aRange,
+ aMustBeInViewPort, false,
+ getter_AddRefs(startPointRange),
+ nullptr);
+ return NS_OK;
+}
+
+bool
+nsTypeAheadFind::IsRangeVisible(nsIPresShell *aPresShell,
+ nsPresContext *aPresContext,
+ nsIDOMRange *aRange, bool aMustBeInViewPort,
+ bool aGetTopVisibleLeaf,
+ nsIDOMRange **aFirstVisibleRange,
+ bool *aUsesIndependentSelection)
+{
+ NS_ASSERTION(aPresShell && aPresContext && aRange && aFirstVisibleRange,
+ "params are invalid");
+
+ // We need to know if the range start is visible.
+ // Otherwise, return the first visible range start
+ // in aFirstVisibleRange
+
+ aRange->CloneRange(aFirstVisibleRange);
+ nsCOMPtr<nsIDOMNode> node;
+ aRange->GetStartContainer(getter_AddRefs(node));
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(node));
+ if (!content)
+ return false;
+
+ nsIFrame *frame = content->GetPrimaryFrame();
+ if (!frame)
+ return false; // No frame! Not visible then.
+
+ if (!frame->StyleVisibility()->IsVisible())
+ return false;
+
+ // Detect if we are _inside_ a text control, or something else with its own
+ // selection controller.
+ if (aUsesIndependentSelection) {
+ *aUsesIndependentSelection =
+ (frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION);
+ }
+
+ // ---- We have a frame ----
+ if (!aMustBeInViewPort)
+ return true; // Don't need it to be on screen, just in rendering tree
+
+ // Get the next in flow frame that contains the range start
+ int32_t startFrameOffset, endFrameOffset;
+ uint32_t startRangeOffset;
+ aRange->GetStartOffset(&startRangeOffset);
+ while (true) {
+ frame->GetOffsets(startFrameOffset, endFrameOffset);
+ if (static_cast<int32_t>(startRangeOffset) < endFrameOffset) {
+ break;
+ }
+
+ nsIFrame *nextContinuationFrame = frame->GetNextContinuation();
+ if (nextContinuationFrame)
+ frame = nextContinuationFrame;
+ else
+ break;
+ }
+
+ // Set up the variables we need, return true if we can't get at them all
+ const uint16_t kMinPixels = 12;
+ nscoord minDistance = nsPresContext::CSSPixelsToAppUnits(kMinPixels);
+
+ // Get the bounds of the current frame, relative to the current view.
+ // We don't use the more accurate AccGetBounds, because that is
+ // more expensive and the STATE_OFFSCREEN flag that this is used
+ // for only needs to be a rough indicator
+ nsRectVisibility rectVisibility = nsRectVisibility_kAboveViewport;
+
+ if (!aGetTopVisibleLeaf && !frame->GetRect().IsEmpty()) {
+ rectVisibility =
+ aPresShell->GetRectVisibility(frame,
+ nsRect(nsPoint(0,0), frame->GetSize()),
+ minDistance);
+
+ if (rectVisibility != nsRectVisibility_kAboveViewport) {
+ return true;
+ }
+ }
+
+ // We know that the target range isn't usable because it's not in the
+ // view port. Move range forward to first visible point,
+ // this speeds us up a lot in long documents
+ nsCOMPtr<nsIFrameEnumerator> frameTraversal;
+ nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID));
+ if (trav)
+ trav->NewFrameTraversal(getter_AddRefs(frameTraversal),
+ aPresContext, frame,
+ eLeaf,
+ false, // aVisual
+ false, // aLockInScrollView
+ false, // aFollowOOFs
+ false // aSkipPopupChecks
+ );
+
+ if (!frameTraversal)
+ return false;
+
+ while (rectVisibility == nsRectVisibility_kAboveViewport) {
+ frameTraversal->Next();
+ frame = frameTraversal->CurrentItem();
+ if (!frame)
+ return false;
+
+ if (!frame->GetRect().IsEmpty()) {
+ rectVisibility =
+ aPresShell->GetRectVisibility(frame,
+ nsRect(nsPoint(0,0), frame->GetSize()),
+ minDistance);
+ }
+ }
+
+ if (frame) {
+ nsCOMPtr<nsIDOMNode> firstVisibleNode = do_QueryInterface(frame->GetContent());
+
+ if (firstVisibleNode) {
+ frame->GetOffsets(startFrameOffset, endFrameOffset);
+ (*aFirstVisibleRange)->SetStart(firstVisibleNode, startFrameOffset);
+ (*aFirstVisibleRange)->SetEnd(firstVisibleNode, endFrameOffset);
+ }
+ }
+
+ return false;
+}
+
+already_AddRefed<nsIPresShell>
+nsTypeAheadFind::GetPresShell()
+{
+ if (!mPresShell)
+ return nullptr;
+
+ nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShell);
+ if (shell) {
+ nsPresContext *pc = shell->GetPresContext();
+ if (!pc || !pc->GetContainerWeak()) {
+ return nullptr;
+ }
+ }
+
+ return shell.forget();
+}
diff --git a/components/typeaheadfind/nsTypeAheadFind.h b/components/typeaheadfind/nsTypeAheadFind.h
new file mode 100644
index 000000000..8ff5ad1bf
--- /dev/null
+++ b/components/typeaheadfind/nsTypeAheadFind.h
@@ -0,0 +1,134 @@
+/* -*- 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 "nsCycleCollectionParticipant.h"
+#include "nsISelectionController.h"
+#include "nsIController.h"
+#include "nsIControllers.h"
+#include "nsIObserver.h"
+#include "nsUnicharUtils.h"
+#include "nsIFind.h"
+#include "nsIWebBrowserFind.h"
+#include "nsWeakReference.h"
+#include "nsISelection.h"
+#include "nsIDOMRange.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsITypeAheadFind.h"
+#include "nsISound.h"
+
+class nsPIDOMWindowInner;
+class nsIPresShell;
+class nsPresContext;
+
+#define TYPEAHEADFIND_NOTFOUND_WAV_URL \
+ "chrome://global/content/notfound.wav"
+
+class nsTypeAheadFind : public nsITypeAheadFind,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ nsTypeAheadFind();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSITYPEAHEADFIND
+ NS_DECL_NSIOBSERVER
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTypeAheadFind, nsITypeAheadFind)
+
+protected:
+ virtual ~nsTypeAheadFind();
+
+ nsresult PrefsReset();
+
+ void SaveFind();
+ void PlayNotFoundSound();
+ nsresult GetWebBrowserFind(nsIDocShell *aDocShell,
+ nsIWebBrowserFind **aWebBrowserFind);
+
+ void RangeStartsInsideLink(nsIDOMRange *aRange, nsIPresShell *aPresShell,
+ bool *aIsInsideLink, bool *aIsStartingLink);
+
+ void GetSelection(nsIPresShell *aPresShell, nsISelectionController **aSelCon,
+ nsISelection **aDomSel);
+ // *aNewRange may not be collapsed. If you want to collapse it in a
+ // particular way, you need to do it yourself.
+ bool IsRangeVisible(nsIPresShell *aPresShell, nsPresContext *aPresContext,
+ nsIDOMRange *aRange, bool aMustBeVisible,
+ bool aGetTopVisibleLeaf, nsIDOMRange **aNewRange,
+ bool *aUsesIndependentSelection);
+ nsresult FindItNow(nsIPresShell *aPresShell, bool aIsLinksOnly,
+ bool aIsFirstVisiblePreferred, bool aFindPrev,
+ uint16_t* aResult);
+ nsresult GetSearchContainers(nsISupports *aContainer,
+ nsISelectionController *aSelectionController,
+ bool aIsFirstVisiblePreferred,
+ bool aFindPrev, nsIPresShell **aPresShell,
+ nsPresContext **aPresContext);
+
+ // Get the pres shell from mPresShell and return it only if it is still
+ // attached to the DOM window.
+ already_AddRefed<nsIPresShell> GetPresShell();
+
+ void ReleaseStrongMemberVariables();
+
+ // Current find state
+ nsString mTypeAheadBuffer;
+ nsCString mNotFoundSoundURL;
+
+ // PRBools are used instead of PRPackedBools because the address of the
+ // boolean variable is getting passed into a method.
+ bool mStartLinksOnlyPref;
+ bool mCaretBrowsingOn;
+ bool mDidAddObservers;
+ nsCOMPtr<nsIDOMElement> mFoundLink; // Most recent elem found, if a link
+ nsCOMPtr<nsIDOMElement> mFoundEditable; // Most recent elem found, if editable
+ nsCOMPtr<nsIDOMRange> mFoundRange; // Most recent range found
+ nsCOMPtr<nsPIDOMWindowInner> mCurrentWindow;
+ // mLastFindLength is the character length of the last find string. It is used for
+ // disabling the "not found" sound when using backspace or delete
+ uint32_t mLastFindLength;
+
+ // Sound is played asynchronously on some platforms.
+ // If we destroy mSoundInterface before sound has played, it won't play
+ nsCOMPtr<nsISound> mSoundInterface;
+ bool mIsSoundInitialized;
+
+ // where selection was when user started the find
+ nsCOMPtr<nsIDOMRange> mStartFindRange;
+ nsCOMPtr<nsIDOMRange> mSearchRange;
+ nsCOMPtr<nsIDOMRange> mStartPointRange;
+ nsCOMPtr<nsIDOMRange> mEndPointRange;
+
+ // Cached useful interfaces
+ nsCOMPtr<nsIFind> mFind;
+
+ bool mCaseSensitive;
+ bool mEntireWord;
+
+ bool EnsureFind() {
+ if (mFind) {
+ return true;
+ }
+
+ mFind = do_CreateInstance("@mozilla.org/embedcomp/rangefind;1");
+ if (!mFind) {
+ return false;
+ }
+
+ mFind->SetCaseSensitive(mCaseSensitive);
+ mFind->SetEntireWord(mEntireWord);
+
+ return true;
+ }
+
+ nsCOMPtr<nsIWebBrowserFind> mWebBrowserFind;
+
+ // The focused content window that we're listening to and its cached objects
+ nsWeakPtr mDocShell;
+ nsWeakPtr mPresShell;
+ nsWeakPtr mSelectionController;
+ // Most recent match's controller
+};
diff --git a/components/uriloader/moz.build b/components/uriloader/moz.build
new file mode 100644
index 000000000..96463f1ad
--- /dev/null
+++ b/components/uriloader/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsCURILoader.idl',
+ 'public/nsIContentHandler.idl',
+ 'public/nsIDocumentLoader.idl',
+ 'public/nsITransfer.idl',
+ 'public/nsIURIContentListener.idl',
+ 'public/nsIURILoader.idl',
+ 'public/nsIWebProgress.idl',
+ 'public/nsIWebProgressListener.idl',
+ 'public/nsIWebProgressListener2.idl',
+]
+
+EXPORTS += [
+ 'src/nsDocLoader.h',
+ 'src/nsURILoader.h',
+]
+
+SOURCES += [
+ 'src/nsDocLoader.cpp',
+ 'src/nsURILoader.cpp',
+]
+
+XPIDL_MODULE = 'uriloader'
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild') \ No newline at end of file
diff --git a/components/uriloader/public/nsCURILoader.idl b/components/uriloader/public/nsCURILoader.idl
new file mode 100644
index 000000000..5d9d8f013
--- /dev/null
+++ b/components/uriloader/public/nsCURILoader.idl
@@ -0,0 +1,49 @@
+/* -*- Mode: IDL; tab-width: 3; 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 "nsIURILoader.idl"
+
+/*
+nsCURILoader implements:
+-------------------------
+nsIURILoader
+*/
+
+%{ C++
+// {9F6D5D40-90E7-11d3-AF93-00A024FFC08C} -
+#define NS_URI_LOADER_CID \
+{ 0x9f6d5d40, 0x90e7, 0x11d3, { 0xaf, 0x80, 0x00, 0xa0, 0x24, 0xff, 0xc0, 0x8c } }
+#define NS_URI_LOADER_CONTRACTID \
+"@mozilla.org/uriloader;1"
+
+/* 057b04d0-0ccf-11d2-beba-00805f8a66dc */
+#define NS_DOCUMENTLOADER_SERVICE_CID \
+ { 0x057b04d0, 0x0ccf, 0x11d2,{0xbe, 0xba, 0x00, 0x80, 0x5f, 0x8a, 0x66, 0xdc}}
+
+#define NS_DOCUMENTLOADER_SERVICE_CONTRACTID \
+"@mozilla.org/docloaderservice;1"
+
+#define NS_CONTENT_HANDLER_CONTRACTID "@mozilla.org/uriloader/content-handler;1"
+#define NS_CONTENT_HANDLER_CONTRACTID_PREFIX NS_CONTENT_HANDLER_CONTRACTID "?type="
+
+/**
+ * A category where content listeners can register. The name of the entry must
+ * be the content that this listener wants to handle, the value must be a
+ * contract ID for the listener. It will be created using createInstance (not
+ * getService).
+ *
+ * Listeners added this way are tried after the initial target of the load and
+ * after explicitly registered listeners (nsIURILoader::registerContentListener).
+ *
+ * These listeners must implement at least nsIURIContentListener (and
+ * nsISupports).
+ *
+ * @see nsICategoryManager
+ * @see nsIURIContentListener
+ */
+#define NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY "external-uricontentlisteners"
+
+%}
diff --git a/components/uriloader/public/nsIContentHandler.idl b/components/uriloader/public/nsIContentHandler.idl
new file mode 100644
index 000000000..31ef87a8b
--- /dev/null
+++ b/components/uriloader/public/nsIContentHandler.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsISupports.idl"
+interface nsIRequest;
+interface nsIInterfaceRequestor;
+
+[scriptable, uuid(49439df2-b3d2-441c-bf62-866bdaf56fd2)]
+interface nsIContentHandler : nsISupports
+{
+ /**
+ * Tells the content handler to take over handling the content. If this
+ * function succeeds, the URI Loader will leave this request alone, ignoring
+ * progress notifications. Failure of this method will cause the request to be
+ * cancelled, unless the error code is NS_ERROR_WONT_HANDLE_CONTENT (see
+ * below).
+ *
+ * @param aWindowContext
+ * Window context, used to get things like the current nsIDOMWindow
+ * for this request. May be null.
+ * @param aContentType
+ * The content type of aRequest
+ * @param aRequest
+ * A request whose content type is already known.
+ *
+ * @throw NS_ERROR_WONT_HANDLE_CONTENT Indicates that this handler does not
+ * want to handle this content. A different way for handling this
+ * content should be tried.
+ */
+ void handleContent(in string aContentType,
+ in nsIInterfaceRequestor aWindowContext,
+ in nsIRequest aRequest);
+};
diff --git a/components/uriloader/public/nsIDocumentLoader.idl b/components/uriloader/public/nsIDocumentLoader.idl
new file mode 100644
index 000000000..3bd960ac8
--- /dev/null
+++ b/components/uriloader/public/nsIDocumentLoader.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsISupports.idl"
+interface nsILoadGroup;
+interface nsIChannel;
+interface nsIURI;
+interface nsIWebProgress;
+interface nsIRequest;
+
+/**
+ * An nsIDocumentLoader is an interface responsible for tracking groups of
+ * loads that belong together (images, external scripts, etc) and subdocuments
+ * (<iframe>, <frame>, etc). It is also responsible for sending
+ * nsIWebProgressListener notifications.
+ * XXXbz this interface should go away, we think...
+ */
+[scriptable, uuid(bbe961ee-59e9-42bb-be50-0331979bb79f)]
+interface nsIDocumentLoader : nsISupports
+{
+ // Stop all loads in the loadgroup of this docloader
+ void stop();
+
+ // XXXbz is this needed? For embedding? What this does is does is not
+ // defined by this interface!
+ readonly attribute nsISupports container;
+
+ // The loadgroup associated with this docloader
+ readonly attribute nsILoadGroup loadGroup;
+
+ // The defaultLoadRequest of the loadgroup associated with this docloader
+ readonly attribute nsIChannel documentChannel;
+};
+
diff --git a/components/uriloader/public/nsITransfer.idl b/components/uriloader/public/nsITransfer.idl
new file mode 100644
index 000000000..da34d4ac4
--- /dev/null
+++ b/components/uriloader/public/nsITransfer.idl
@@ -0,0 +1,106 @@
+/* -*- 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 "nsIWebProgressListener2.idl"
+
+interface nsIArray;
+interface nsIURI;
+interface nsICancelable;
+interface nsIMIMEInfo;
+interface nsIFile;
+
+[scriptable, uuid(37ec75d3-97ad-4da8-afaa-eabe5b4afd73)]
+interface nsITransfer : nsIWebProgressListener2 {
+
+ /**
+ * Initializes the transfer with certain properties. This function must
+ * be called prior to accessing any properties on this interface.
+ *
+ * @param aSource The source URI of the transfer. Must not be null.
+ *
+ * @param aTarget The target URI of the transfer. Must not be null.
+ *
+ * @param aDisplayName The user-readable description of the transfer.
+ * Can be empty.
+ *
+ * @param aMIMEInfo The MIME info associated with the target,
+ * including MIME type and helper app when appropriate.
+ * This parameter is optional.
+ *
+ * @param startTime Time when the download started (ie, when the first
+ * response from the server was received)
+ * XXX presumably wbp and exthandler do this differently
+ *
+ * @param aTempFile The location of a temporary file; i.e. a file in which
+ * the received data will be stored, but which is not
+ * equal to the target file. (will be moved to the real
+ * target by the caller, when the download is finished)
+ * May be null.
+ *
+ * @param aCancelable An object that can be used to abort the download.
+ * Must not be null.
+ * Implementations are expected to hold a strong
+ * reference to this object until the download is
+ * finished, at which point they should release the
+ * reference.
+ *
+ * @param aIsPrivate Used to determine the privacy status of the new transfer.
+ * If true, indicates that the transfer was initiated from
+ * a source that desires privacy.
+ */
+ void init(in nsIURI aSource,
+ in nsIURI aTarget,
+ in AString aDisplayName,
+ in nsIMIMEInfo aMIMEInfo,
+ in PRTime startTime,
+ in nsIFile aTempFile,
+ in nsICancelable aCancelable,
+ in boolean aIsPrivate);
+
+ /*
+ * Used to notify the transfer object of the hash of the downloaded file.
+ * Must be called on the main thread, only after the download has finished
+ * successfully.
+ * @param aHash The SHA-256 hash in raw bytes of the downloaded file.
+ */
+ void setSha256Hash(in ACString aHash);
+
+ /*
+ * Used to notify the transfer object of the signature of the downloaded
+ * file. Must be called on the main thread, only after the download has
+ * finished successfully.
+ * @param aSignatureInfo The nsIArray of nsIX509CertList of nsIX509Cert
+ * certificates of the downloaded file.
+ */
+ void setSignatureInfo(in nsIArray aSignatureInfo);
+
+ /*
+ * Used to notify the transfer object of the redirects associated with the
+ * channel that terminated in the downloaded file. Must be called on the
+ * main thread, only after the download has finished successfully.
+ * @param aRedirects The nsIArray of nsIPrincipal of redirected URIs
+ * associated with the downloaded file.
+ */
+ void setRedirects(in nsIArray aRedirects);
+};
+
+%{C++
+/**
+ * A component with this contract ID will be created each time a download is
+ * started, and nsITransfer::Init will be called on it and an observer will be set.
+ *
+ * Notifications of the download progress will happen via
+ * nsIWebProgressListener/nsIWebProgressListener2.
+ *
+ * INTERFACES THAT MUST BE IMPLEMENTED:
+ * nsITransfer
+ * nsIWebProgressListener
+ * nsIWebProgressListener2
+ *
+ * XXX move this to nsEmbedCID.h once the interfaces (and the contract ID) are
+ * frozen.
+ */
+#define NS_TRANSFER_CONTRACTID "@mozilla.org/transfer;1"
+%}
diff --git a/components/uriloader/public/nsIURIContentListener.idl b/components/uriloader/public/nsIURIContentListener.idl
new file mode 100644
index 000000000..9008cb61e
--- /dev/null
+++ b/components/uriloader/public/nsIURIContentListener.idl
@@ -0,0 +1,135 @@
+/* -*- Mode: IDL; 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 nsIRequest;
+interface nsIStreamListener;
+interface nsIURI;
+
+/**
+ * nsIURIContentListener is an interface used by components which
+ * want to know (and have a chance to handle) a particular content type.
+ * Typical usage scenarios will include running applications which register
+ * a nsIURIContentListener for each of its content windows with the uri
+ * dispatcher service.
+ */
+[scriptable, uuid(10a28f38-32e8-4c63-8aa1-12eaaebc369a)]
+interface nsIURIContentListener : nsISupports
+{
+ /**
+ * Gives the original content listener first crack at stopping a load before
+ * it happens.
+ *
+ * @param aURI URI that is being opened.
+ *
+ * @return <code>false</code> if the load can continue;
+ * <code>true</code> if the open should be aborted.
+ */
+ boolean onStartURIOpen(in nsIURI aURI);
+
+ /**
+ * Notifies the content listener to hook up an nsIStreamListener capable of
+ * consuming the data stream.
+ *
+ * @param aContentType Content type of the data.
+ * @param aIsContentPreferred Indicates whether the content should be
+ * preferred by this listener.
+ * @param aRequest Request that is providing the data.
+ * @param aContentHandler nsIStreamListener that will consume the data.
+ * This should be set to <code>nullptr</code> if
+ * this content listener can't handle the content
+ * type; in this case, doContent should also fail
+ * (i.e., return failure nsresult).
+ *
+ * @return <code>true</code> if the load should
+ * be aborted and consumer wants to
+ * handle the load completely by itself. This
+ * causes the URI Loader do nothing else...
+ * <code>false</code> if the URI Loader should
+ * continue handling the load and call the
+ * returned streamlistener's methods.
+ */
+ boolean doContent(in ACString aContentType,
+ in boolean aIsContentPreferred,
+ in nsIRequest aRequest,
+ out nsIStreamListener aContentHandler);
+
+ /**
+ * When given a uri to dispatch, if the URI is specified as 'preferred
+ * content' then the uri loader tries to find a preferred content handler
+ * for the content type. The thought is that many content listeners may
+ * be able to handle the same content type if they have to. i.e. the mail
+ * content window can handle text/html just like a browser window content
+ * listener. However, if the user clicks on a link with text/html content,
+ * then the browser window should handle that content and not the mail
+ * window where the user may have clicked the link. This is the difference
+ * between isPreferred and canHandleContent.
+ *
+ * @param aContentType Content type of the data.
+ * @param aDesiredContentType Indicates that aContentType must be converted
+ * to aDesiredContentType before processing the
+ * data. This causes a stream converted to be
+ * inserted into the nsIStreamListener chain.
+ * This argument can be <code>nullptr</code> if
+ * the content should be consumed directly as
+ * aContentType.
+ *
+ * @return <code>true</code> if this is a preferred
+ * content handler for aContentType;
+ * <code>false<code> otherwise.
+ */
+ boolean isPreferred(in string aContentType, out string aDesiredContentType);
+
+ /**
+ * When given a uri to dispatch, if the URI is not specified as 'preferred
+ * content' then the uri loader calls canHandleContent to see if the content
+ * listener is capable of handling the content.
+ *
+ * @param aContentType Content type of the data.
+ * @param aIsContentPreferred Indicates whether the content should be
+ * preferred by this listener.
+ * @param aDesiredContentType Indicates that aContentType must be converted
+ * to aDesiredContentType before processing the
+ * data. This causes a stream converted to be
+ * inserted into the nsIStreamListener chain.
+ * This argument can be <code>nullptr</code> if
+ * the content should be consumed directly as
+ * aContentType.
+ *
+ * @return <code>true</code> if the data can be consumed.
+ * <code>false</code> otherwise.
+ *
+ * Note: I really envision canHandleContent as a method implemented
+ * by the docshell as the implementation is generic to all doc
+ * shells. The isPreferred decision is a decision made by a top level
+ * application content listener that sits at the top of the docshell
+ * hierarchy.
+ */
+ boolean canHandleContent(in string aContentType,
+ in boolean aIsContentPreferred,
+ out string aDesiredContentType);
+
+ /**
+ * The load context associated with a particular content listener.
+ * The URI Loader stores and accesses this value as needed.
+ */
+ attribute nsISupports loadCookie;
+
+ /**
+ * The parent content listener if this particular listener is part of a chain
+ * of content listeners (i.e. a docshell!)
+ *
+ * @note If this attribute is set to an object that implements
+ * nsISupportsWeakReference, the implementation should get the
+ * nsIWeakReference and hold that. Otherwise, the implementation
+ * should not refcount this interface; it should assume that a non
+ * null value is always valid. In that case, the caller is
+ * responsible for explicitly setting this value back to null if the
+ * parent content listener is destroyed.
+ */
+ attribute nsIURIContentListener parentContentListener;
+};
+
diff --git a/components/uriloader/public/nsIURILoader.idl b/components/uriloader/public/nsIURILoader.idl
new file mode 100644
index 000000000..74f21e363
--- /dev/null
+++ b/components/uriloader/public/nsIURILoader.idl
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsISupports.idl"
+
+interface nsIURIContentListener;
+interface nsIURI;
+interface nsILoadGroup;
+interface nsIProgressEventSink;
+interface nsIChannel;
+interface nsIRequest;
+interface nsIStreamListener;
+interface nsIInputStream;
+interface nsIInterfaceRequestor;
+
+/**
+ * The uri dispatcher is responsible for taking uri's, determining
+ * the content and routing the opened url to the correct content
+ * handler.
+ *
+ * When you encounter a url you want to open, you typically call
+ * openURI, passing it the content listener for the window the uri is
+ * originating from. The uri dispatcher opens the url to discover the
+ * content type. It then gives the content listener first crack at
+ * handling the content. If it doesn't want it, the dispatcher tries
+ * to hand it off one of the registered content listeners. This allows
+ * running applications the chance to jump in and handle the content.
+ *
+ * If that also fails, then the uri dispatcher goes to the registry
+ * looking for the preferred content handler for the content type
+ * of the uri. The content handler may create an app instance
+ * or it may hand the contents off to a platform specific plugin
+ * or helper app. Or it may hand the url off to an OS registered
+ * application.
+ */
+[scriptable, uuid(8762c4e7-be35-4958-9b81-a05685bb516d)]
+interface nsIURILoader : nsISupports
+{
+ /**
+ * @name Flags for opening URIs.
+ */
+ /* @{ */
+ /**
+ * Should the content be displayed in a container that prefers the
+ * content-type, or will any container do.
+ */
+ const unsigned long IS_CONTENT_PREFERRED = 1 << 0;
+ /**
+ * If this flag is set, only the listener of the specified window context will
+ * be considered for content handling; if it refuses the load, an error will
+ * be indicated.
+ */
+ const unsigned long DONT_RETARGET = 1 << 1;
+ /* @} */
+
+ /**
+ * As applications such as messenger and the browser are instantiated,
+ * they register content listener's with the uri dispatcher corresponding
+ * to content windows within that application.
+ *
+ * Note to self: we may want to optimize things a bit more by requiring
+ * the content types the registered content listener cares about.
+ *
+ * @param aContentListener
+ * The listener to register. This listener must implement
+ * nsISupportsWeakReference.
+ *
+ * @see the nsIURILoader class description
+ */
+ void registerContentListener (in nsIURIContentListener aContentListener);
+ void unRegisterContentListener (in nsIURIContentListener aContentListener);
+
+ /**
+ * OpenURI requires the following parameters.....
+ * @param aChannel
+ * The channel that should be opened. This must not be asyncOpen'd yet!
+ * If a loadgroup is set on the channel, it will get replaced with a
+ * different one.
+ * @param aFlags
+ * Combination (bitwise OR) of the flags specified above. 0 indicates
+ * default handling.
+ * @param aWindowContext
+ * If you are running the url from a doc shell or a web shell, this is
+ * your window context. If you have a content listener you want to
+ * give first crack to, the uri loader needs to be able to get it
+ * from the window context. We will also be using the window context
+ * to get at the progress event sink interface.
+ * <b>Must not be null!</b>
+ */
+ void openURI(in nsIChannel aChannel,
+ in unsigned long aFlags,
+ in nsIInterfaceRequestor aWindowContext);
+
+ /**
+ * Loads data from a channel. This differs from openURI in that the channel
+ * may already be opened, and that it returns a stream listener into which the
+ * caller should pump data. The caller is responsible for opening the channel
+ * and pumping the channel's data into the returned stream listener.
+ *
+ * Note: If the channel already has a loadgroup, it will be replaced with the
+ * window context's load group, or null if the context doesn't have one.
+ *
+ * If the window context's nsIURIContentListener refuses the load immediately
+ * (e.g. in nsIURIContentListener::onStartURIOpen), this method will return
+ * NS_ERROR_WONT_HANDLE_CONTENT. At that point, the caller should probably
+ * cancel the channel if it's already open (this method will not cancel the
+ * channel).
+ *
+ * If flags include DONT_RETARGET, and the content listener refuses the load
+ * during onStartRequest (e.g. in canHandleContent/isPreferred), then the
+ * returned stream listener's onStartRequest method will return
+ * NS_ERROR_WONT_HANDLE_CONTENT.
+ *
+ * @param aChannel
+ * The channel that should be loaded. The channel may already be
+ * opened. It must not be closed (i.e. this must be called before the
+ * channel calls onStopRequest on its stream listener).
+ * @param aFlags
+ * Combination (bitwise OR) of the flags specified above. 0 indicates
+ * default handling.
+ * @param aWindowContext
+ * If you are running the url from a doc shell or a web shell, this is
+ * your window context. If you have a content listener you want to
+ * give first crack to, the uri loader needs to be able to get it
+ * from the window context. We will also be using the window context
+ * to get at the progress event sink interface.
+ * <b>Must not be null!</b>
+ */
+ nsIStreamListener openChannel(in nsIChannel aChannel,
+ in unsigned long aFlags,
+ in nsIInterfaceRequestor aWindowContext);
+
+ /**
+ * Stops an in progress load
+ */
+ void stop(in nsISupports aLoadCookie);
+};
+
diff --git a/components/uriloader/public/nsIWebProgress.idl b/components/uriloader/public/nsIWebProgress.idl
new file mode 100644
index 000000000..9d17d0a4d
--- /dev/null
+++ b/components/uriloader/public/nsIWebProgress.idl
@@ -0,0 +1,153 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIWebProgressListener;
+
+/**
+ * The nsIWebProgress interface is used to add or remove nsIWebProgressListener
+ * instances to observe the loading of asynchronous requests (usually in the
+ * context of a DOM window).
+ *
+ * nsIWebProgress instances may be arranged in a parent-child configuration,
+ * corresponding to the parent-child configuration of their respective DOM
+ * windows. However, in some cases a nsIWebProgress instance may not have an
+ * associated DOM window. The parent-child relationship of nsIWebProgress
+ * instances is not made explicit by this interface, but the relationship may
+ * exist in some implementations.
+ *
+ * A nsIWebProgressListener instance receives notifications for the
+ * nsIWebProgress instance to which it added itself, and it may also receive
+ * notifications from any nsIWebProgress instances that are children of that
+ * nsIWebProgress instance.
+ */
+[scriptable, uuid(c4d64640-b332-4db6-a2a5-e08566000dc9)]
+interface nsIWebProgress : nsISupports
+{
+ /**
+ * The following flags may be combined to form the aNotifyMask parameter for
+ * the addProgressListener method. They limit the set of events that are
+ * delivered to an nsIWebProgressListener instance.
+ */
+
+ /**
+ * These flags indicate the state transistions to observe, corresponding to
+ * nsIWebProgressListener::onStateChange.
+ *
+ * NOTIFY_STATE_REQUEST
+ * Only receive the onStateChange event if the aStateFlags parameter
+ * includes nsIWebProgressListener::STATE_IS_REQUEST.
+ *
+ * NOTIFY_STATE_DOCUMENT
+ * Only receive the onStateChange event if the aStateFlags parameter
+ * includes nsIWebProgressListener::STATE_IS_DOCUMENT.
+ *
+ * NOTIFY_STATE_NETWORK
+ * Only receive the onStateChange event if the aStateFlags parameter
+ * includes nsIWebProgressListener::STATE_IS_NETWORK.
+ *
+ * NOTIFY_STATE_WINDOW
+ * Only receive the onStateChange event if the aStateFlags parameter
+ * includes nsIWebProgressListener::STATE_IS_WINDOW.
+ *
+ * NOTIFY_STATE_ALL
+ * Receive all onStateChange events.
+ */
+ const unsigned long NOTIFY_STATE_REQUEST = 0x00000001;
+ const unsigned long NOTIFY_STATE_DOCUMENT = 0x00000002;
+ const unsigned long NOTIFY_STATE_NETWORK = 0x00000004;
+ const unsigned long NOTIFY_STATE_WINDOW = 0x00000008;
+ const unsigned long NOTIFY_STATE_ALL = 0x0000000f;
+
+ /**
+ * These flags indicate the other events to observe, corresponding to the
+ * other four methods defined on nsIWebProgressListener.
+ *
+ * NOTIFY_PROGRESS
+ * Receive onProgressChange events.
+ *
+ * NOTIFY_STATUS
+ * Receive onStatusChange events.
+ *
+ * NOTIFY_SECURITY
+ * Receive onSecurityChange events.
+ *
+ * NOTIFY_LOCATION
+ * Receive onLocationChange events.
+ *
+ * NOTIFY_REFRESH
+ * Receive onRefreshAttempted events.
+ * This is defined on nsIWebProgressListener2.
+ */
+ const unsigned long NOTIFY_PROGRESS = 0x00000010;
+ const unsigned long NOTIFY_STATUS = 0x00000020;
+ const unsigned long NOTIFY_SECURITY = 0x00000040;
+ const unsigned long NOTIFY_LOCATION = 0x00000080;
+ const unsigned long NOTIFY_REFRESH = 0x00000100;
+
+ /**
+ * This flag enables all notifications.
+ */
+ const unsigned long NOTIFY_ALL = 0x000001ff;
+
+ /**
+ * Registers a listener to receive web progress events.
+ *
+ * @param aListener
+ * The listener interface to be called when a progress event occurs.
+ * This object must also implement nsISupportsWeakReference.
+ * @param aNotifyMask
+ * The types of notifications to receive.
+ *
+ * @throw NS_ERROR_INVALID_ARG
+ * Indicates that aListener was either null or that it does not
+ * support weak references.
+ * @throw NS_ERROR_FAILURE
+ * Indicates that aListener was already registered.
+ */
+ void addProgressListener(in nsIWebProgressListener aListener,
+ in unsigned long aNotifyMask);
+
+ /**
+ * Removes a previously registered listener of progress events.
+ *
+ * @param aListener
+ * The listener interface previously registered with a call to
+ * addProgressListener.
+ *
+ * @throw NS_ERROR_FAILURE
+ * Indicates that aListener was not registered.
+ */
+ void removeProgressListener(in nsIWebProgressListener aListener);
+
+ /**
+ * The DOM window associated with this nsIWebProgress instance.
+ *
+ * @throw NS_ERROR_FAILURE
+ * Indicates that there is no associated DOM window.
+ */
+ readonly attribute mozIDOMWindowProxy DOMWindow;
+ readonly attribute uint64_t DOMWindowID;
+
+ /**
+ * Indicates whether DOMWindow.top == DOMWindow.
+ */
+ readonly attribute boolean isTopLevel;
+
+ /**
+ * Indicates whether or not a document is currently being loaded
+ * in the context of this nsIWebProgress instance.
+ */
+ readonly attribute boolean isLoadingDocument;
+
+ /**
+ * Contains a load type as specified by the load* constants in
+ * nsIDocShellLoadInfo.idl.
+ */
+ readonly attribute unsigned long loadType;
+};
diff --git a/components/uriloader/public/nsIWebProgressListener.idl b/components/uriloader/public/nsIWebProgressListener.idl
new file mode 100644
index 000000000..714b931a3
--- /dev/null
+++ b/components/uriloader/public/nsIWebProgressListener.idl
@@ -0,0 +1,425 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIWebProgress;
+interface nsIRequest;
+interface nsIURI;
+
+/**
+ * The nsIWebProgressListener interface is implemented by clients wishing to
+ * listen in on the progress associated with the loading of asynchronous
+ * requests in the context of a nsIWebProgress instance as well as any child
+ * nsIWebProgress instances. nsIWebProgress.idl describes the parent-child
+ * relationship of nsIWebProgress instances.
+ */
+[scriptable, uuid(a9df523b-efe2-421e-9d8e-3d7f807dda4c)]
+interface nsIWebProgressListener : nsISupports
+{
+ /**
+ * State Transition Flags
+ *
+ * These flags indicate the various states that requests may transition
+ * through as they are being loaded. These flags are mutually exclusive.
+ *
+ * For any given request, onStateChange is called once with the STATE_START
+ * flag, zero or more times with the STATE_TRANSFERRING flag or once with the
+ * STATE_REDIRECTING flag, and then finally once with the STATE_STOP flag.
+ * NOTE: For document requests, a second STATE_STOP is generated (see the
+ * description of STATE_IS_WINDOW for more details).
+ *
+ * STATE_START
+ * This flag indicates the start of a request. This flag is set when a
+ * request is initiated. The request is complete when onStateChange is
+ * called for the same request with the STATE_STOP flag set.
+ *
+ * STATE_REDIRECTING
+ * This flag indicates that a request is being redirected. The request
+ * passed to onStateChange is the request that is being redirected. When a
+ * redirect occurs, a new request is generated automatically to process the
+ * new request. Expect a corresponding STATE_START event for the new
+ * request, and a STATE_STOP for the redirected request.
+ *
+ * STATE_TRANSFERRING
+ * This flag indicates that data for a request is being transferred to an
+ * end consumer. This flag indicates that the request has been targeted,
+ * and that the user may start seeing content corresponding to the request.
+ *
+ * STATE_NEGOTIATING
+ * This flag is not used.
+ *
+ * STATE_STOP
+ * This flag indicates the completion of a request. The aStatus parameter
+ * to onStateChange indicates the final status of the request.
+ */
+ const unsigned long STATE_START = 0x00000001;
+ const unsigned long STATE_REDIRECTING = 0x00000002;
+ const unsigned long STATE_TRANSFERRING = 0x00000004;
+ const unsigned long STATE_NEGOTIATING = 0x00000008;
+ const unsigned long STATE_STOP = 0x00000010;
+
+
+ /**
+ * State Type Flags
+ *
+ * These flags further describe the entity for which the state transition is
+ * occuring. These flags are NOT mutually exclusive (i.e., an onStateChange
+ * event may indicate some combination of these flags).
+ *
+ * STATE_IS_REQUEST
+ * This flag indicates that the state transition is for a request, which
+ * includes but is not limited to document requests. (See below for a
+ * description of document requests.) Other types of requests, such as
+ * requests for inline content (e.g., images and stylesheets) are
+ * considered normal requests.
+ *
+ * STATE_IS_DOCUMENT
+ * This flag indicates that the state transition is for a document request.
+ * This flag is set in addition to STATE_IS_REQUEST. A document request
+ * supports the nsIChannel interface and its loadFlags attribute includes
+ * the nsIChannel::LOAD_DOCUMENT_URI flag.
+ *
+ * A document request does not complete until all requests associated with
+ * the loading of its corresponding document have completed. This includes
+ * other document requests (e.g., corresponding to HTML <iframe> elements).
+ * The document corresponding to a document request is available via the
+ * DOMWindow attribute of onStateChange's aWebProgress parameter.
+ *
+ * STATE_IS_NETWORK
+ * This flag indicates that the state transition corresponds to the start
+ * or stop of activity in the indicated nsIWebProgress instance. This flag
+ * is accompanied by either STATE_START or STATE_STOP, and it may be
+ * combined with other State Type Flags.
+ *
+ * Unlike STATE_IS_WINDOW, this flag is only set when activity within the
+ * nsIWebProgress instance being observed starts or stops. If activity
+ * only occurs in a child nsIWebProgress instance, then this flag will be
+ * set to indicate the start and stop of that activity.
+ *
+ * For example, in the case of navigation within a single frame of a HTML
+ * frameset, a nsIWebProgressListener instance attached to the
+ * nsIWebProgress of the frameset window will receive onStateChange calls
+ * with the STATE_IS_NETWORK flag set to indicate the start and stop of
+ * said navigation. In other words, an observer of an outer window can
+ * determine when activity, that may be constrained to a child window or
+ * set of child windows, starts and stops.
+ *
+ * STATE_IS_WINDOW
+ * This flag indicates that the state transition corresponds to the start
+ * or stop of activity in the indicated nsIWebProgress instance. This flag
+ * is accompanied by either STATE_START or STATE_STOP, and it may be
+ * combined with other State Type Flags.
+ *
+ * This flag is similar to STATE_IS_DOCUMENT. However, when a document
+ * request completes, two onStateChange calls with STATE_STOP are
+ * generated. The document request is passed as aRequest to both calls.
+ * The first has STATE_IS_REQUEST and STATE_IS_DOCUMENT set, and the second
+ * has the STATE_IS_WINDOW flag set (and possibly the STATE_IS_NETWORK flag
+ * set as well -- see above for a description of when the STATE_IS_NETWORK
+ * flag may be set). This second STATE_STOP event may be useful as a way
+ * to partition the work that occurs when a document request completes.
+ */
+ const unsigned long STATE_IS_REQUEST = 0x00010000;
+ const unsigned long STATE_IS_DOCUMENT = 0x00020000;
+ const unsigned long STATE_IS_NETWORK = 0x00040000;
+ const unsigned long STATE_IS_WINDOW = 0x00080000;
+
+
+ /**
+ * State Modifier Flags
+ *
+ * These flags further describe the transition which is occuring. These
+ * flags are NOT mutually exclusive (i.e., an onStateChange event may
+ * indicate some combination of these flags).
+ *
+ * STATE_RESTORING
+ * This flag indicates that the state transition corresponds to the start
+ * or stop of activity for restoring a previously-rendered presentation.
+ * As such, there is no actual network activity associated with this
+ * request, and any modifications made to the document or presentation
+ * when it was originally loaded will still be present.
+ */
+ const unsigned long STATE_RESTORING = 0x01000000;
+
+ /**
+ * State Security Flags
+ *
+ * These flags describe the security state reported by a call to the
+ * onSecurityChange method. These flags are mutually exclusive.
+ *
+ * STATE_IS_INSECURE
+ * This flag indicates that the data corresponding to the request
+ * was received over an insecure channel.
+ *
+ * STATE_IS_BROKEN
+ * This flag indicates an unknown security state. This may mean that the
+ * request is being loaded as part of a page in which some content was
+ * received over an insecure channel.
+ *
+ * STATE_IS_SECURE
+ * This flag indicates that the data corresponding to the request was
+ * received over a secure channel. The degree of security is expressed by
+ * STATE_SECURE_HIGH, STATE_SECURE_MED, or STATE_SECURE_LOW.
+ */
+ const unsigned long STATE_IS_INSECURE = 0x00000004;
+ const unsigned long STATE_IS_BROKEN = 0x00000001;
+ const unsigned long STATE_IS_SECURE = 0x00000002;
+
+ /**
+ * Mixed active content flags
+ *
+ * May be set in addition to the State Security Flags, to indicate that
+ * mixed active content has been encountered.
+ *
+ * STATE_BLOCKED_MIXED_ACTIVE_CONTENT
+ * Mixed active content has been blocked from loading.
+ *
+ * STATE_LOADED_MIXED_ACTIVE_CONTENT
+ * Mixed active content has been loaded. State should be STATE_IS_BROKEN.
+ */
+ const unsigned long STATE_BLOCKED_MIXED_ACTIVE_CONTENT = 0x00000010;
+ const unsigned long STATE_LOADED_MIXED_ACTIVE_CONTENT = 0x00000020;
+
+ /**
+ * Mixed display content flags
+ *
+ * May be set in addition to the State Security Flags, to indicate that
+ * mixed display content has been encountered.
+ *
+ * STATE_BLOCKED_MIXED_DISPLAY_CONTENT
+ * Mixed display content has been blocked from loading.
+ *
+ * STATE_LOADED_MIXED_DISPLAY_CONTENT
+ * Mixed display content has been loaded. State should be STATE_IS_BROKEN.
+ */
+ const unsigned long STATE_BLOCKED_MIXED_DISPLAY_CONTENT = 0x00000100;
+ const unsigned long STATE_LOADED_MIXED_DISPLAY_CONTENT = 0x00000200;
+
+ /**
+ * Tracking content flags
+ *
+ * May be set in addition to the State security Flags, to indicate that
+ * tracking content has been encountered.
+ *
+ * STATE_BLOCKED_TRACKING_CONTENT
+ * Tracking content has been blocked from loading.
+ *
+ * STATE_LOADED_TRACKING_CONTENT
+ * Tracking content has been loaded.
+ */
+ const unsigned long STATE_BLOCKED_TRACKING_CONTENT = 0x00001000;
+ const unsigned long STATE_LOADED_TRACKING_CONTENT = 0x00002000;
+
+ /**
+ * Security Strength Flags
+ *
+ * These flags describe the security strength and accompany STATE_IS_SECURE
+ * in a call to the onSecurityChange method. These flags are mutually
+ * exclusive.
+ *
+ * These flags are not meant to provide a precise description of data
+ * transfer security. These are instead intended as a rough indicator that
+ * may be used to, for example, color code a security indicator or otherwise
+ * provide basic data transfer security feedback to the user.
+ *
+ * STATE_SECURE_HIGH
+ * This flag indicates a high degree of security.
+ *
+ * STATE_SECURE_MED
+ * This flag indicates a medium degree of security.
+ *
+ * STATE_SECURE_LOW
+ * This flag indicates a low degree of security.
+ */
+ const unsigned long STATE_SECURE_HIGH = 0x00040000;
+ const unsigned long STATE_SECURE_MED = 0x00010000;
+ const unsigned long STATE_SECURE_LOW = 0x00020000;
+
+ /**
+ * State bits for EV == Extended Validation == High Assurance
+ *
+ * These flags describe the level of identity verification
+ * in a call to the onSecurityChange method.
+ *
+ * STATE_IDENTITY_EV_TOPLEVEL
+ * The topmost document uses an EV cert.
+ * NOTE: Available since Gecko 1.9
+ */
+
+ const unsigned long STATE_IDENTITY_EV_TOPLEVEL = 0x00100000;
+
+ /**
+ * Broken state flags
+ *
+ * These flags describe the reason of the broken state.
+ *
+ * STATE_USES_SSL_3
+ * The topmost document uses SSL 3.0.
+ *
+ * STATE_USES_WEAK_CRYPTO
+ * The topmost document uses a weak cipher suite such as RC4.
+ *
+ * STATE_CERT_USER_OVERRIDDEN
+ * The user has added a security exception for the site.
+ */
+ const unsigned long STATE_USES_SSL_3 = 0x01000000;
+ const unsigned long STATE_USES_WEAK_CRYPTO = 0x02000000;
+ const unsigned long STATE_CERT_USER_OVERRIDDEN = 0x04000000;
+
+ /**
+ * Notification indicating the state has changed for one of the requests
+ * associated with aWebProgress.
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification
+ * @param aRequest
+ * The nsIRequest that has changed state.
+ * @param aStateFlags
+ * Flags indicating the new state. This value is a combination of one
+ * of the State Transition Flags and one or more of the State Type
+ * Flags defined above. Any undefined bits are reserved for future
+ * use.
+ * @param aStatus
+ * Error status code associated with the state change. This parameter
+ * should be ignored unless aStateFlags includes the STATE_STOP bit.
+ * The status code indicates success or failure of the request
+ * associated with the state change. NOTE: aStatus may be a success
+ * code even for server generated errors, such as the HTTP 404 error.
+ * In such cases, the request itself should be queried for extended
+ * error information (e.g., for HTTP requests see nsIHttpChannel).
+ */
+ void onStateChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in unsigned long aStateFlags,
+ in nsresult aStatus);
+
+ /**
+ * Notification that the progress has changed for one of the requests
+ * associated with aWebProgress. Progress totals are reset to zero when all
+ * requests in aWebProgress complete (corresponding to onStateChange being
+ * called with aStateFlags including the STATE_STOP and STATE_IS_WINDOW
+ * flags).
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRequest
+ * The nsIRequest that has new progress.
+ * @param aCurSelfProgress
+ * The current progress for aRequest.
+ * @param aMaxSelfProgress
+ * The maximum progress for aRequest.
+ * @param aCurTotalProgress
+ * The current progress for all requests associated with aWebProgress.
+ * @param aMaxTotalProgress
+ * The total progress for all requests associated with aWebProgress.
+ *
+ * NOTE: If any progress value is unknown, or if its value would exceed the
+ * maximum value of type long, then its value is replaced with -1.
+ *
+ * NOTE: If the object also implements nsIWebProgressListener2 and the caller
+ * knows about that interface, this function will not be called. Instead,
+ * nsIWebProgressListener2::onProgressChange64 will be called.
+ */
+ void onProgressChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in long aCurSelfProgress,
+ in long aMaxSelfProgress,
+ in long aCurTotalProgress,
+ in long aMaxTotalProgress);
+
+ /**
+ * Flags for onLocationChange
+ *
+ * LOCATION_CHANGE_SAME_DOCUMENT
+ * This flag is on when |aWebProgress| did not load a new document.
+ * For example, the location change is due to an anchor scroll or a
+ * pushState/popState/replaceState.
+ *
+ * LOCATION_CHANGE_ERROR_PAGE
+ * This flag is on when |aWebProgress| redirected from the requested
+ * contents to an internal page to show error status, such as
+ * <about:neterror>, <about:certerror> and so on.
+ *
+ * Generally speaking, |aURI| and |aRequest| are the original data. DOM
+ * |window.location.href| is also the original location, while
+ * |document.documentURI| is the redirected location. Sometimes |aURI| is
+ * <about:blank> and |aRequest| is null when the original data does not
+ + remain.
+ *
+ * |aWebProgress| does NOT set this flag when it did not try to load a new
+ * document. In this case, it should set LOCATION_CHANGE_SAME_DOCUMENT.
+ */
+ const unsigned long LOCATION_CHANGE_SAME_DOCUMENT = 0x00000001;
+ const unsigned long LOCATION_CHANGE_ERROR_PAGE = 0x00000002;
+
+ /**
+ * Called when the location of the window being watched changes. This is not
+ * when a load is requested, but rather once it is verified that the load is
+ * going to occur in the given window. For instance, a load that starts in a
+ * window might send progress and status messages for the new site, but it
+ * will not send the onLocationChange until we are sure that we are loading
+ * this new page here.
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRequest
+ * The associated nsIRequest. This may be null in some cases.
+ * @param aLocation
+ * The URI of the location that is being loaded.
+ * @param aFlags
+ * This is a value which explains the situation or the reason why
+ * the location has changed.
+ */
+ void onLocationChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in nsIURI aLocation,
+ [optional] in unsigned long aFlags);
+
+ /**
+ * Notification that the status of a request has changed. The status message
+ * is intended to be displayed to the user (e.g., in the status bar of the
+ * browser).
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRequest
+ * The nsIRequest that has new status.
+ * @param aStatus
+ * This value is not an error code. Instead, it is a numeric value
+ * that indicates the current status of the request. This interface
+ * does not define the set of possible status codes. NOTE: Some
+ * status values are defined by nsITransport and nsISocketTransport.
+ * @param aMessage
+ * Localized text corresponding to aStatus.
+ */
+ void onStatusChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in nsresult aStatus,
+ in wstring aMessage);
+
+ /**
+ * Notification called for security progress. This method will be called on
+ * security transitions (eg HTTP -> HTTPS, HTTPS -> HTTP, FOO -> HTTPS) and
+ * after document load completion. It might also be called if an error
+ * occurs during network loading.
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRequest
+ * The nsIRequest that has new security state.
+ * @param aState
+ * A value composed of the Security State Flags and the Security
+ * Strength Flags listed above. Any undefined bits are reserved for
+ * future use.
+ *
+ * NOTE: These notifications will only occur if a security package is
+ * installed.
+ */
+ void onSecurityChange(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in unsigned long aState);
+};
diff --git a/components/uriloader/public/nsIWebProgressListener2.idl b/components/uriloader/public/nsIWebProgressListener2.idl
new file mode 100644
index 000000000..87701f8d2
--- /dev/null
+++ b/components/uriloader/public/nsIWebProgressListener2.idl
@@ -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 "nsIWebProgressListener.idl"
+
+/**
+ * An extended version of nsIWebProgressListener.
+ */
+[scriptable, uuid(dde39de0-e4e0-11da-8ad9-0800200c9a66)]
+interface nsIWebProgressListener2 : nsIWebProgressListener {
+ /**
+ * Notification that the progress has changed for one of the requests
+ * associated with aWebProgress. Progress totals are reset to zero when all
+ * requests in aWebProgress complete (corresponding to onStateChange being
+ * called with aStateFlags including the STATE_STOP and STATE_IS_WINDOW
+ * flags).
+ *
+ * This function is identical to nsIWebProgressListener::onProgressChange,
+ * except that this function supports 64-bit values.
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRequest
+ * The nsIRequest that has new progress.
+ * @param aCurSelfProgress
+ * The current progress for aRequest.
+ * @param aMaxSelfProgress
+ * The maximum progress for aRequest.
+ * @param aCurTotalProgress
+ * The current progress for all requests associated with aWebProgress.
+ * @param aMaxTotalProgress
+ * The total progress for all requests associated with aWebProgress.
+ *
+ * NOTE: If any progress value is unknown, then its value is replaced with -1.
+ *
+ * @see nsIWebProgressListener2::onProgressChange64
+ */
+ void onProgressChange64(in nsIWebProgress aWebProgress,
+ in nsIRequest aRequest,
+ in long long aCurSelfProgress,
+ in long long aMaxSelfProgress,
+ in long long aCurTotalProgress,
+ in long long aMaxTotalProgress);
+
+ /**
+ * Notification that a refresh or redirect has been requested in aWebProgress
+ * For example, via a <meta http-equiv="refresh"> or an HTTP Refresh: header
+ *
+ * @param aWebProgress
+ * The nsIWebProgress instance that fired the notification.
+ * @param aRefreshURI
+ * The new URI that aWebProgress has requested redirecting to.
+ * @param aMillis
+ * The delay (in milliseconds) before refresh.
+ * @param aSameURI
+ * True if aWebProgress is requesting a refresh of the
+ * current URI.
+ * False if aWebProgress is requesting a redirection to
+ * a different URI.
+ *
+ * @return True if the refresh may proceed.
+ * False if the refresh should be aborted.
+ */
+ boolean onRefreshAttempted(in nsIWebProgress aWebProgress,
+ in nsIURI aRefreshURI,
+ in long aMillis,
+ in boolean aSameURI);
+};
diff --git a/components/uriloader/src/nsDocLoader.cpp b/components/uriloader/src/nsDocLoader.cpp
new file mode 100644
index 000000000..a4beb4827
--- /dev/null
+++ b/components/uriloader/src/nsDocLoader.cpp
@@ -0,0 +1,1592 @@
+/* -*- 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 "nspr.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/Logging.h"
+
+#include "nsDocLoader.h"
+#include "nsCURILoader.h"
+#include "nsNetUtil.h"
+#include "nsIHttpChannel.h"
+#include "nsIWebProgressListener2.h"
+
+#include "nsIServiceManager.h"
+#include "nsXPIDLString.h"
+
+#include "nsIURL.h"
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsWeakPtr.h"
+#include "nsAutoPtr.h"
+#include "nsQueryObject.h"
+
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+
+#include "nsIStringBundle.h"
+#include "nsIScriptSecurityManager.h"
+
+#include "nsITransport.h"
+#include "nsISocketTransport.h"
+#include "nsIDocShell.h"
+#include "nsIDOMDocument.h"
+#include "nsIDocument.h"
+#include "nsPresContext.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+
+using mozilla::DebugOnly;
+using mozilla::eLoad;
+using mozilla::EventDispatcher;
+using mozilla::LogLevel;
+using mozilla::WidgetEvent;
+
+static NS_DEFINE_CID(kThisImplCID, NS_THIS_DOCLOADER_IMPL_CID);
+
+//
+// Log module for nsIDocumentLoader logging...
+//
+// To enable logging (see mozilla/Logging.h for full details):
+//
+// set MOZ_LOG=DocLoader:5
+// set MOZ_LOG_FILE=debug.log
+//
+// this enables LogLevel::Debug level information and places all output in
+// the file 'debug.log'.
+//
+mozilla::LazyLogModule gDocLoaderLog("DocLoader");
+
+
+#if defined(DEBUG)
+void GetURIStringFromRequest(nsIRequest* request, nsACString &name)
+{
+ if (request)
+ request->GetName(name);
+ else
+ name.AssignLiteral("???");
+}
+#endif /* DEBUG */
+
+
+
+void
+nsDocLoader::RequestInfoHashInitEntry(PLDHashEntryHdr* entry,
+ const void* key)
+{
+ // Initialize the entry with placement new
+ new (entry) nsRequestInfo(key);
+}
+
+void
+nsDocLoader::RequestInfoHashClearEntry(PLDHashTable* table,
+ PLDHashEntryHdr* entry)
+{
+ nsRequestInfo* info = static_cast<nsRequestInfo *>(entry);
+ info->~nsRequestInfo();
+}
+
+// this is used for mListenerInfoList.Contains()
+template <>
+class nsDefaultComparator <nsDocLoader::nsListenerInfo, nsIWebProgressListener*> {
+ public:
+ bool Equals(const nsDocLoader::nsListenerInfo& aInfo,
+ nsIWebProgressListener* const& aListener) const {
+ nsCOMPtr<nsIWebProgressListener> listener =
+ do_QueryReferent(aInfo.mWeakListener);
+ return aListener == listener;
+ }
+};
+
+/* static */ const PLDHashTableOps nsDocLoader::sRequestInfoHashOps =
+{
+ PLDHashTable::HashVoidPtrKeyStub,
+ PLDHashTable::MatchEntryStub,
+ PLDHashTable::MoveEntryStub,
+ nsDocLoader::RequestInfoHashClearEntry,
+ nsDocLoader::RequestInfoHashInitEntry
+};
+
+nsDocLoader::nsDocLoader()
+ : mParent(nullptr),
+ mCurrentSelfProgress(0),
+ mMaxSelfProgress(0),
+ mCurrentTotalProgress(0),
+ mMaxTotalProgress(0),
+ mRequestInfoHash(&sRequestInfoHashOps, sizeof(nsRequestInfo)),
+ mCompletedTotalProgress(0),
+ mIsLoadingDocument(false),
+ mIsRestoringDocument(false),
+ mDontFlushLayout(false),
+ mIsFlushingLayout(false),
+ mDocumentOpenedButNotLoaded(false)
+{
+ ClearInternalProgress();
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: created.\n", this));
+}
+
+nsresult
+nsDocLoader::SetDocLoaderParent(nsDocLoader *aParent)
+{
+ mParent = aParent;
+ return NS_OK;
+}
+
+nsresult
+nsDocLoader::Init()
+{
+ nsresult rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), this);
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: load group %x.\n", this, mLoadGroup.get()));
+
+ return NS_OK;
+}
+
+nsDocLoader::~nsDocLoader()
+{
+ /*
+ |ClearWeakReferences()| here is intended to prevent people holding weak references
+ from re-entering this destructor since |QueryReferent()| will |AddRef()| me, and the
+ subsequent |Release()| will try to destroy me. At this point there should be only
+ weak references remaining (otherwise, we wouldn't be getting destroyed).
+
+ An alternative would be incrementing our refcount (consider it a compressed flag
+ saying "Don't re-destroy."). I haven't yet decided which is better. [scc]
+ */
+ // XXXbz now that NS_IMPL_RELEASE stabilizes by setting refcount to 1, is
+ // this needed?
+ ClearWeakReferences();
+
+ Destroy();
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: deleted.\n", this));
+}
+
+
+/*
+ * Implementation of ISupports methods...
+ */
+NS_IMPL_ADDREF(nsDocLoader)
+NS_IMPL_RELEASE(nsDocLoader)
+
+NS_INTERFACE_MAP_BEGIN(nsDocLoader)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentLoader)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsISecurityEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ if (aIID.Equals(kThisImplCID))
+ foundInterface = static_cast<nsIDocumentLoader *>(this);
+ else
+NS_INTERFACE_MAP_END
+
+
+/*
+ * Implementation of nsIInterfaceRequestor methods...
+ */
+NS_IMETHODIMP nsDocLoader::GetInterface(const nsIID& aIID, void** aSink)
+{
+ nsresult rv = NS_ERROR_NO_INTERFACE;
+
+ NS_ENSURE_ARG_POINTER(aSink);
+
+ if(aIID.Equals(NS_GET_IID(nsILoadGroup))) {
+ *aSink = mLoadGroup;
+ NS_IF_ADDREF((nsISupports*)*aSink);
+ rv = NS_OK;
+ } else {
+ rv = QueryInterface(aIID, aSink);
+ }
+
+ return rv;
+}
+
+/* static */
+already_AddRefed<nsDocLoader>
+nsDocLoader::GetAsDocLoader(nsISupports* aSupports)
+{
+ RefPtr<nsDocLoader> ret = do_QueryObject(aSupports);
+ return ret.forget();
+}
+
+/* static */
+nsresult
+nsDocLoader::AddDocLoaderAsChildOfRoot(nsDocLoader* aDocLoader)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDocumentLoader> docLoaderService =
+ do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsDocLoader> rootDocLoader = GetAsDocLoader(docLoaderService);
+ NS_ENSURE_TRUE(rootDocLoader, NS_ERROR_UNEXPECTED);
+
+ return rootDocLoader->AddChildLoader(aDocLoader);
+}
+
+NS_IMETHODIMP
+nsDocLoader::Stop(void)
+{
+ nsresult rv = NS_OK;
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: Stop() called\n", this));
+
+ NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader, Stop, ());
+
+ if (mLoadGroup)
+ rv = mLoadGroup->Cancel(NS_BINDING_ABORTED);
+
+ // Don't report that we're flushing layout so IsBusy returns false after a
+ // Stop call.
+ mIsFlushingLayout = false;
+
+ // Clear out mChildrenInOnload. We want to make sure to fire our
+ // onload at this point, and there's no issue with mChildrenInOnload
+ // after this, since mDocumentRequest will be null after the
+ // DocLoaderIsEmpty() call.
+ mChildrenInOnload.Clear();
+
+ // Make sure to call DocLoaderIsEmpty now so that we reset mDocumentRequest,
+ // etc, as needed. We could be getting into here from a subframe onload, in
+ // which case the call to DocLoaderIsEmpty() is coming but hasn't quite
+ // happened yet, Canceling the loadgroup did nothing (because it was already
+ // empty), and we're about to start a new load (which is what triggered this
+ // Stop() call).
+
+ // XXXbz If the child frame loadgroups were requests in mLoadgroup, I suspect
+ // we wouldn't need the call here....
+
+ NS_ASSERTION(!IsBusy(), "Shouldn't be busy here");
+ DocLoaderIsEmpty(false);
+
+ return rv;
+}
+
+
+bool
+nsDocLoader::IsBusy()
+{
+ nsresult rv;
+
+ //
+ // A document loader is busy if either:
+ //
+ // 1. One of its children is in the middle of an onload handler. Note that
+ // the handler may have already removed this child from mChildList!
+ // 2. It is currently loading a document and either has parts of it still
+ // loading, or has a busy child docloader.
+ // 3. It's currently flushing layout in DocLoaderIsEmpty().
+ //
+
+ if (mChildrenInOnload.Count() || mIsFlushingLayout) {
+ return true;
+ }
+
+ /* Is this document loader busy? */
+ if (!IsBlockingLoadEvent()) {
+ return false;
+ }
+
+ bool busy;
+ rv = mLoadGroup->IsPending(&busy);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ if (busy) {
+ return true;
+ }
+
+ /* check its child document loaders... */
+ uint32_t count = mChildList.Length();
+ for (uint32_t i=0; i < count; i++) {
+ nsIDocumentLoader* loader = ChildAt(i);
+
+ // This is a safe cast, because we only put nsDocLoader objects into the
+ // array
+ if (loader && static_cast<nsDocLoader*>(loader)->IsBusy())
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetContainer(nsISupports** aResult)
+{
+ NS_ADDREF(*aResult = static_cast<nsIDocumentLoader*>(this));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetLoadGroup(nsILoadGroup** aResult)
+{
+ nsresult rv = NS_OK;
+
+ if (nullptr == aResult) {
+ rv = NS_ERROR_NULL_POINTER;
+ } else {
+ *aResult = mLoadGroup;
+ NS_IF_ADDREF(*aResult);
+ }
+ return rv;
+}
+
+void
+nsDocLoader::Destroy()
+{
+ Stop();
+
+ // Remove the document loader from the parent list of loaders...
+ if (mParent)
+ {
+ DebugOnly<nsresult> rv = mParent->RemoveChildLoader(this);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RemoveChildLoader failed");
+ }
+
+ // Release all the information about network requests...
+ ClearRequestInfoHash();
+
+ mListenerInfoList.Clear();
+ mListenerInfoList.Compact();
+
+ mDocumentRequest = nullptr;
+
+ if (mLoadGroup)
+ mLoadGroup->SetGroupObserver(nullptr);
+
+ DestroyChildren();
+}
+
+void
+nsDocLoader::DestroyChildren()
+{
+ uint32_t count = mChildList.Length();
+ // if the doc loader still has children...we need to enumerate the
+ // children and make them null out their back ptr to the parent doc
+ // loader
+ for (uint32_t i=0; i < count; i++)
+ {
+ nsIDocumentLoader* loader = ChildAt(i);
+
+ if (loader) {
+ // This is a safe cast, as we only put nsDocLoader objects into the
+ // array
+ DebugOnly<nsresult> rv =
+ static_cast<nsDocLoader*>(loader)->SetDocLoaderParent(nullptr);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetDocLoaderParent failed");
+ }
+ }
+ mChildList.Clear();
+}
+
+NS_IMETHODIMP
+nsDocLoader::OnStartRequest(nsIRequest *request, nsISupports *aCtxt)
+{
+ // called each time a request is added to the group.
+
+ if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) {
+ nsAutoCString name;
+ request->GetName(name);
+
+ uint32_t count = 0;
+ if (mLoadGroup)
+ mLoadGroup->GetActiveCount(&count);
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: OnStartRequest[%p](%s) mIsLoadingDocument=%s, %u active URLs",
+ this, request, name.get(),
+ (mIsLoadingDocument ? "true" : "false"),
+ count));
+ }
+
+ bool bJustStartedLoading = false;
+
+ nsLoadFlags loadFlags = 0;
+ request->GetLoadFlags(&loadFlags);
+
+ if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) {
+ bJustStartedLoading = true;
+ mIsLoadingDocument = true;
+ mDocumentOpenedButNotLoaded = false;
+ ClearInternalProgress(); // only clear our progress if we are starting a new load....
+ }
+
+ //
+ // Create a new nsRequestInfo for the request that is starting to
+ // load...
+ //
+ AddRequestInfo(request);
+
+ //
+ // Only fire a doStartDocumentLoad(...) if the document loader
+ // has initiated a load... Otherwise, this notification has
+ // resulted from a request being added to the load group.
+ //
+ if (mIsLoadingDocument) {
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
+ //
+ // Make sure that the document channel is null at this point...
+ // (unless its been redirected)
+ //
+ NS_ASSERTION((loadFlags & nsIChannel::LOAD_REPLACE) ||
+ !(mDocumentRequest.get()),
+ "Overwriting an existing document channel!");
+
+ // This request is associated with the entire document...
+ mDocumentRequest = request;
+ mLoadGroup->SetDefaultLoadRequest(request);
+
+ // Only fire the start document load notification for the first
+ // document URI... Do not fire it again for redirections
+ //
+ if (bJustStartedLoading) {
+ // Update the progress status state
+ mProgressStateFlags = nsIWebProgressListener::STATE_START;
+
+ // Fire the start document load notification
+ doStartDocumentLoad();
+ return NS_OK;
+ }
+ }
+ }
+
+ NS_ASSERTION(!mIsLoadingDocument || mDocumentRequest,
+ "mDocumentRequest MUST be set for the duration of a page load!");
+
+ doStartURLLoad(request);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::OnStopRequest(nsIRequest *aRequest,
+ nsISupports *aCtxt,
+ nsresult aStatus)
+{
+ nsresult rv = NS_OK;
+
+ if (MOZ_LOG_TEST(gDocLoaderLog, LogLevel::Debug)) {
+ nsAutoCString name;
+ aRequest->GetName(name);
+
+ uint32_t count = 0;
+ if (mLoadGroup)
+ mLoadGroup->GetActiveCount(&count);
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: OnStopRequest[%p](%s) status=%x"
+ "mIsLoadingDocument=%s, mDocumentOpenedButNotLoaded=%s, %u active URLs",
+ this, aRequest, name.get(),
+ aStatus, (mIsLoadingDocument ? "true" : "false"),
+ (mDocumentOpenedButNotLoaded ? "true" : "false"), count));
+ }
+
+ bool bFireTransferring = false;
+
+ //
+ // Set the Maximum progress to the same value as the current progress.
+ // Since the URI has finished loading, all the data is there. Also,
+ // this will allow a more accurate estimation of the max progress (in case
+ // the old value was unknown ie. -1)
+ //
+ nsRequestInfo *info = GetRequestInfo(aRequest);
+ if (info) {
+ // Null out mLastStatus now so we don't find it when looking for
+ // status from now on. This destroys the nsStatusInfo and hence
+ // removes it from our list.
+ info->mLastStatus = nullptr;
+
+ int64_t oldMax = info->mMaxProgress;
+
+ info->mMaxProgress = info->mCurrentProgress;
+
+ //
+ // If a request whose content-length was previously unknown has just
+ // finished loading, then use this new data to try to calculate a
+ // mMaxSelfProgress...
+ //
+ if ((oldMax < int64_t(0)) && (mMaxSelfProgress < int64_t(0))) {
+ mMaxSelfProgress = CalculateMaxProgress();
+ }
+
+ // As we know the total progress of this request now, save it to be part
+ // of CalculateMaxProgress() result. We need to remove the info from the
+ // hash, see bug 480713.
+ mCompletedTotalProgress += info->mMaxProgress;
+
+ //
+ // Determine whether a STATE_TRANSFERRING notification should be
+ // 'synthesized'.
+ //
+ // If nsRequestInfo::mMaxProgress (as stored in oldMax) and
+ // nsRequestInfo::mCurrentProgress are both 0, then the
+ // STATE_TRANSFERRING notification has not been fired yet...
+ //
+ if ((oldMax == 0) && (info->mCurrentProgress == 0)) {
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+
+ // Only fire a TRANSFERRING notification if the request is also a
+ // channel -- data transfer requires a nsIChannel!
+ //
+ if (channel) {
+ if (NS_SUCCEEDED(aStatus)) {
+ bFireTransferring = true;
+ }
+ //
+ // If the request failed (for any reason other than being
+ // redirected or retargeted), the TRANSFERRING notification can
+ // still be fired if a HTTP connection was established to a server.
+ //
+ else if (aStatus != NS_BINDING_REDIRECTED &&
+ aStatus != NS_BINDING_RETARGETED) {
+ //
+ // Only if the load has been targeted (see bug 268483)...
+ //
+ uint32_t lf;
+ channel->GetLoadFlags(&lf);
+ if (lf & nsIChannel::LOAD_TARGETED) {
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
+ if (httpChannel) {
+ uint32_t responseCode;
+ rv = httpChannel->GetResponseStatus(&responseCode);
+ if (NS_SUCCEEDED(rv)) {
+ //
+ // A valid server status indicates that a connection was
+ // established to the server... So, fire the notification
+ // even though a failure occurred later...
+ //
+ bFireTransferring = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (bFireTransferring) {
+ // Send a STATE_TRANSFERRING notification for the request.
+ int32_t flags;
+
+ flags = nsIWebProgressListener::STATE_TRANSFERRING |
+ nsIWebProgressListener::STATE_IS_REQUEST;
+ //
+ // Move the WebProgress into the STATE_TRANSFERRING state if necessary...
+ //
+ if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
+ mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
+
+ // Send STATE_TRANSFERRING for the document too...
+ flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
+ }
+
+ FireOnStateChange(this, aRequest, flags, NS_OK);
+ }
+
+ //
+ // Fire the OnStateChange(...) notification for stop request
+ //
+ doStopURLLoad(aRequest, aStatus);
+
+ // Clear this request out of the hash to avoid bypass of FireOnStateChange
+ // when address of the request is reused.
+ RemoveRequestInfo(aRequest);
+
+ //
+ // Only fire the DocLoaderIsEmpty(...) if we may need to fire onload.
+ //
+ if (IsBlockingLoadEvent()) {
+ nsCOMPtr<nsIDocShell> ds = do_QueryInterface(static_cast<nsIRequestObserver*>(this));
+ bool doNotFlushLayout = false;
+ if (ds) {
+ // Don't do unexpected layout flushes while we're in process of restoring
+ // a document from the bfcache.
+ ds->GetRestoringDocument(&doNotFlushLayout);
+ }
+ DocLoaderIsEmpty(!doNotFlushLayout);
+ }
+
+ return NS_OK;
+}
+
+
+nsresult nsDocLoader::RemoveChildLoader(nsDocLoader* aChild)
+{
+ nsresult rv = mChildList.RemoveElement(aChild) ? NS_OK : NS_ERROR_FAILURE;
+ if (NS_SUCCEEDED(rv)) {
+ rv = aChild->SetDocLoaderParent(nullptr);
+ }
+ return rv;
+}
+
+nsresult nsDocLoader::AddChildLoader(nsDocLoader* aChild)
+{
+ nsresult rv = mChildList.AppendElement(aChild) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ if (NS_SUCCEEDED(rv)) {
+ rv = aChild->SetDocLoaderParent(this);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsDocLoader::GetDocumentChannel(nsIChannel ** aChannel)
+{
+ if (!mDocumentRequest) {
+ *aChannel = nullptr;
+ return NS_OK;
+ }
+
+ return CallQueryInterface(mDocumentRequest, aChannel);
+}
+
+
+void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
+{
+ if (IsBlockingLoadEvent()) {
+ /* In the unimagineably rude circumstance that onload event handlers
+ triggered by this function actually kill the window ... ok, it's
+ not unimagineable; it's happened ... this deathgrip keeps this object
+ alive long enough to survive this function call. */
+ nsCOMPtr<nsIDocumentLoader> kungFuDeathGrip(this);
+
+ // Don't flush layout if we're still busy.
+ if (IsBusy()) {
+ return;
+ }
+
+ NS_ASSERTION(!mIsFlushingLayout, "Someone screwed up");
+ // We may not have a document request if we are in a document.open() situation.
+ NS_ASSERTION(mDocumentRequest || mDocumentOpenedButNotLoaded,
+ "No Document Request!");
+
+ // The load group for this DocumentLoader is idle. Flush if we need to.
+ if (aFlushLayout && !mDontFlushLayout) {
+ nsCOMPtr<nsIDOMDocument> domDoc = do_GetInterface(GetAsSupports(this));
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+ if (doc) {
+ // We start loads from style resolution, so we need to flush out style
+ // no matter what. If we have user fonts, we also need to flush layout,
+ // since the reflow is what starts font loads.
+ mozFlushType flushType = Flush_Style;
+ nsIPresShell* shell = doc->GetShell();
+ if (shell) {
+ // Be safe in case this presshell is in teardown now
+ nsPresContext* presContext = shell->GetPresContext();
+ if (presContext && presContext->GetUserFontSet()) {
+ flushType = Flush_Layout;
+ }
+ }
+ mDontFlushLayout = mIsFlushingLayout = true;
+ doc->FlushPendingNotifications(flushType);
+ mDontFlushLayout = mIsFlushingLayout = false;
+ }
+ }
+
+ // And now check whether we're really busy; that might have changed with
+ // the layout flush.
+ //
+ // Note, mDocumentRequest can be null while mDocumentOpenedButNotLoaded is
+ // false if the flushing above re-entered this method. Exit in that case.
+ if (IsBusy() || (!mDocumentRequest && !mDocumentOpenedButNotLoaded)) {
+ return;
+ }
+
+ if (mDocumentRequest) {
+ // Clear out our request info hash, now that our load really is done and
+ // we don't need it anymore to CalculateMaxProgress().
+ ClearInternalProgress();
+
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: Is now idle...\n", this));
+
+ nsCOMPtr<nsIRequest> docRequest = mDocumentRequest;
+
+ mDocumentRequest = nullptr;
+ mIsLoadingDocument = false;
+
+ // Update the progress status state - the document is done
+ mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
+
+
+ nsresult loadGroupStatus = NS_OK;
+ mLoadGroup->GetStatus(&loadGroupStatus);
+
+ //
+ // New code to break the circular reference between
+ // the load group and the docloader...
+ //
+ mLoadGroup->SetDefaultLoadRequest(nullptr);
+
+ // Take a ref to our parent now so that we can call ChildDoneWithOnload() on
+ // it even if our onload handler removes us from the docloader tree.
+ RefPtr<nsDocLoader> parent = mParent;
+
+ // Note that if calling ChildEnteringOnload() on the parent returns false
+ // then calling our onload handler is not safe. That can only happen on
+ // OOM, so that's ok.
+ if (!parent || parent->ChildEnteringOnload(this)) {
+ // Do nothing with our state after firing the
+ // OnEndDocumentLoad(...). The document loader may be loading a *new*
+ // document - if LoadDocument() was called from a handler!
+ //
+ doStopDocumentLoad(docRequest, loadGroupStatus);
+
+ if (parent) {
+ parent->ChildDoneWithOnload(this);
+ }
+ }
+ } else {
+ MOZ_ASSERT(mDocumentOpenedButNotLoaded);
+ mDocumentOpenedButNotLoaded = false;
+
+ // Make sure we do the ChildEnteringOnload/ChildDoneWithOnload even if we
+ // plan to skip firing our own load event, because otherwise we might
+ // never end up firing our parent's load event.
+ RefPtr<nsDocLoader> parent = mParent;
+ if (!parent || parent->ChildEnteringOnload(this)) {
+ nsresult loadGroupStatus = NS_OK;
+ mLoadGroup->GetStatus(&loadGroupStatus);
+ // Make sure we're not canceling the loadgroup. If we are, then we should
+ // not fire a load event just like in the normal navigation case.
+ if (NS_SUCCEEDED(loadGroupStatus) ||
+ loadGroupStatus == NS_ERROR_PARSED_DATA_CACHED) {
+ // Can "doc" or "window" ever come back null here? Our state machine
+ // is complicated enough, so I wouldn't bet against it...
+ nsCOMPtr<nsIDocument> doc = do_GetInterface(GetAsSupports(this));
+ if (doc) {
+ doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE,
+ /* updateTimingInformation = */ false);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow();
+ if (window && !doc->SkipLoadEventAfterClose()) {
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: Firing load event for document.open\n",
+ this));
+
+ // This is a very cut-down version of
+ // nsDocumentViewer::LoadComplete that doesn't do various things
+ // that are not relevant here because this wasn't an actual
+ // navigation.
+ WidgetEvent event(true, eLoad);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+ // Dispatching to |window|, but using |document| as the target,
+ // per spec.
+ event.mTarget = doc;
+ nsEventStatus unused = nsEventStatus_eIgnore;
+ doc->SetLoadEventFiring(true);
+ EventDispatcher::Dispatch(window, nullptr, &event, nullptr,
+ &unused);
+ doc->SetLoadEventFiring(false);
+
+ // Now unsuppress painting on the presshell, if we
+ // haven't done that yet.
+ nsCOMPtr<nsIPresShell> shell = doc->GetShell();
+ if (shell && !shell->IsDestroying()) {
+ shell->UnsuppressPainting();
+
+ if (!shell->IsDestroying()) {
+ shell->LoadComplete();
+ }
+ }
+ }
+ }
+ }
+ if (parent) {
+ parent->ChildDoneWithOnload(this);
+ }
+ }
+ }
+ }
+}
+
+void nsDocLoader::doStartDocumentLoad(void)
+{
+
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(mDocumentRequest, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange for start document load (...)."
+ "\tURI: %s \n",
+ this, buffer.get()));
+#endif /* DEBUG */
+
+ // Fire an OnStatus(...) notification STATE_START. This indicates
+ // that the document represented by mDocumentRequest has started to
+ // load...
+ FireOnStateChange(this,
+ mDocumentRequest,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_DOCUMENT |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK,
+ NS_OK);
+}
+
+void nsDocLoader::doStartURLLoad(nsIRequest *request)
+{
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(request, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange start url load (...)."
+ "\tURI: %s\n",
+ this, buffer.get()));
+#endif /* DEBUG */
+
+ FireOnStateChange(this,
+ request,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_REQUEST,
+ NS_OK);
+}
+
+void nsDocLoader::doStopURLLoad(nsIRequest *request, nsresult aStatus)
+{
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(request, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange for end url load (...)."
+ "\tURI: %s status=%x\n",
+ this, buffer.get(), aStatus));
+#endif /* DEBUG */
+
+ FireOnStateChange(this,
+ request,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_REQUEST,
+ aStatus);
+
+ // Fire a status change message for the most recent unfinished
+ // request to make sure that the displayed status is not outdated.
+ if (!mStatusInfoList.isEmpty()) {
+ nsStatusInfo* statusInfo = mStatusInfoList.getFirst();
+ FireOnStatusChange(this, statusInfo->mRequest,
+ statusInfo->mStatusCode,
+ statusInfo->mStatusMessage.get());
+ }
+}
+
+void nsDocLoader::doStopDocumentLoad(nsIRequest *request,
+ nsresult aStatus)
+{
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(request, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: ++ Firing OnStateChange for end document load (...)."
+ "\tURI: %s Status=%x\n",
+ this, buffer.get(), aStatus));
+#endif /* DEBUG */
+
+ // Firing STATE_STOP|STATE_IS_DOCUMENT will fire onload handlers.
+ // Grab our parent chain before doing that so we can still dispatch
+ // STATE_STOP|STATE_IS_WINDW_STATE_IS_NETWORK to them all, even if
+ // the onload handlers rearrange the docshell tree.
+ WebProgressList list;
+ GatherAncestorWebProgresses(list);
+
+ //
+ // Fire an OnStateChange(...) notification indicating the the
+ // current document has finished loading...
+ //
+ int32_t flags = nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_DOCUMENT;
+ for (uint32_t i = 0; i < list.Length(); ++i) {
+ list[i]->DoFireOnStateChange(this, request, flags, aStatus);
+ }
+
+ //
+ // Fire a final OnStateChange(...) notification indicating the the
+ // current document has finished loading...
+ //
+ flags = nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK;
+ for (uint32_t i = 0; i < list.Length(); ++i) {
+ list[i]->DoFireOnStateChange(this, request, flags, aStatus);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// The following section contains support for nsIWebProgress and related stuff
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsDocLoader::AddProgressListener(nsIWebProgressListener *aListener,
+ uint32_t aNotifyMask)
+{
+ if (mListenerInfoList.Contains(aListener)) {
+ // The listener is already registered!
+ return NS_ERROR_FAILURE;
+ }
+
+ nsWeakPtr listener = do_GetWeakReference(aListener);
+ if (!listener) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return mListenerInfoList.AppendElement(nsListenerInfo(listener, aNotifyMask)) ?
+ NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsDocLoader::RemoveProgressListener(nsIWebProgressListener *aListener)
+{
+ return mListenerInfoList.RemoveElement(aListener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetDOMWindow(mozIDOMWindowProxy **aResult)
+{
+ return CallGetInterface(this, aResult);
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetDOMWindowID(uint64_t *aResult)
+{
+ *aResult = 0;
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ nsresult rv = GetDOMWindow(getter_AddRefs(window));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPIDOMWindowOuter> piwindow = nsPIDOMWindowOuter::From(window);
+ NS_ENSURE_STATE(piwindow);
+
+ MOZ_ASSERT(piwindow->IsOuterWindow());
+ *aResult = piwindow->WindowID();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetIsTopLevel(bool *aResult)
+{
+ *aResult = false;
+
+ nsCOMPtr<mozIDOMWindowProxy> window;
+ GetDOMWindow(getter_AddRefs(window));
+ if (window) {
+ nsCOMPtr<nsPIDOMWindowOuter> piwindow = nsPIDOMWindowOuter::From(window);
+ NS_ENSURE_STATE(piwindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> topWindow = piwindow->GetTop();
+ *aResult = piwindow == topWindow;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetIsLoadingDocument(bool *aIsLoadingDocument)
+{
+ *aIsLoadingDocument = mIsLoadingDocument;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocLoader::GetLoadType(uint32_t *aLoadType)
+{
+ *aLoadType = 0;
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+int64_t nsDocLoader::GetMaxTotalProgress()
+{
+ int64_t newMaxTotal = 0;
+
+ uint32_t count = mChildList.Length();
+ for (uint32_t i=0; i < count; i++)
+ {
+ int64_t individualProgress = 0;
+ nsIDocumentLoader* docloader = ChildAt(i);
+ if (docloader)
+ {
+ // Cast is safe since all children are nsDocLoader too
+ individualProgress = ((nsDocLoader *) docloader)->GetMaxTotalProgress();
+ }
+ if (individualProgress < int64_t(0)) // if one of the elements doesn't know it's size
+ // then none of them do
+ {
+ newMaxTotal = int64_t(-1);
+ break;
+ }
+ else
+ newMaxTotal += individualProgress;
+ }
+
+ int64_t progress = -1;
+ if (mMaxSelfProgress >= int64_t(0) && newMaxTotal >= int64_t(0))
+ progress = newMaxTotal + mMaxSelfProgress;
+
+ return progress;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// The following section contains support for nsIProgressEventSink which is used to
+// pass progress and status between the actual request and the doc loader. The doc loader
+// then turns around and makes the right web progress calls based on this information.
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsDocLoader::OnProgress(nsIRequest *aRequest, nsISupports* ctxt,
+ int64_t aProgress, int64_t aProgressMax)
+{
+ int64_t progressDelta = 0;
+
+ //
+ // Update the RequestInfo entry with the new progress data
+ //
+ if (nsRequestInfo* info = GetRequestInfo(aRequest)) {
+ // Update info->mCurrentProgress before we call FireOnStateChange,
+ // since that can make the "info" pointer invalid.
+ int64_t oldCurrentProgress = info->mCurrentProgress;
+ progressDelta = aProgress - oldCurrentProgress;
+ info->mCurrentProgress = aProgress;
+
+ // suppress sending STATE_TRANSFERRING if this is upload progress (see bug 240053)
+ if (!info->mUploading && (int64_t(0) == oldCurrentProgress) && (int64_t(0) == info->mMaxProgress)) {
+ //
+ // If we receive an OnProgress event from a toplevel channel that the URI Loader
+ // has not yet targeted, then we must suppress the event. This is necessary to
+ // ensure that webprogresslisteners do not get confused when the channel is
+ // finally targeted. See bug 257308.
+ //
+ nsLoadFlags lf = 0;
+ aRequest->GetLoadFlags(&lf);
+ if ((lf & nsIChannel::LOAD_DOCUMENT_URI) && !(lf & nsIChannel::LOAD_TARGETED)) {
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p Ignoring OnProgress while load is not targeted\n", this));
+ return NS_OK;
+ }
+
+ //
+ // This is the first progress notification for the entry. If
+ // (aMaxProgress != -1) then the content-length of the data is known,
+ // so update mMaxSelfProgress... Otherwise, set it to -1 to indicate
+ // that the content-length is no longer known.
+ //
+ if (aProgressMax != -1) {
+ mMaxSelfProgress += aProgressMax;
+ info->mMaxProgress = aProgressMax;
+ } else {
+ mMaxSelfProgress = int64_t(-1);
+ info->mMaxProgress = int64_t(-1);
+ }
+
+ // Send a STATE_TRANSFERRING notification for the request.
+ int32_t flags;
+
+ flags = nsIWebProgressListener::STATE_TRANSFERRING |
+ nsIWebProgressListener::STATE_IS_REQUEST;
+ //
+ // Move the WebProgress into the STATE_TRANSFERRING state if necessary...
+ //
+ if (mProgressStateFlags & nsIWebProgressListener::STATE_START) {
+ mProgressStateFlags = nsIWebProgressListener::STATE_TRANSFERRING;
+
+ // Send STATE_TRANSFERRING for the document too...
+ flags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
+ }
+
+ FireOnStateChange(this, aRequest, flags, NS_OK);
+ }
+
+ // Update our overall current progress count.
+ mCurrentSelfProgress += progressDelta;
+ }
+ //
+ // The request is not part of the load group, so ignore its progress
+ // information...
+ //
+ else {
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(aRequest, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p OOPS - No Request Info for: %s\n",
+ this, buffer.get()));
+#endif /* DEBUG */
+
+ return NS_OK;
+ }
+
+ //
+ // Fire progress notifications out to any registered nsIWebProgressListeners
+ //
+ FireOnProgressChange(this, aRequest, aProgress, aProgressMax, progressDelta,
+ mCurrentTotalProgress, mMaxTotalProgress);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocLoader::OnStatus(nsIRequest* aRequest, nsISupports* ctxt,
+ nsresult aStatus, const char16_t* aStatusArg)
+{
+ //
+ // Fire progress notifications out to any registered nsIWebProgressListeners
+ //
+ if (aStatus != NS_OK) {
+ // Remember the current status for this request
+ nsRequestInfo *info;
+ info = GetRequestInfo(aRequest);
+ if (info) {
+ bool uploading = (aStatus == NS_NET_STATUS_WRITING ||
+ aStatus == NS_NET_STATUS_SENDING_TO);
+ // If switching from uploading to downloading (or vice versa), then we
+ // need to reset our progress counts. This is designed with HTTP form
+ // submission in mind, where an upload is performed followed by download
+ // of possibly several documents.
+ if (info->mUploading != uploading) {
+ mCurrentSelfProgress = mMaxSelfProgress = 0;
+ mCurrentTotalProgress = mMaxTotalProgress = 0;
+ mCompletedTotalProgress = 0;
+ info->mUploading = uploading;
+ info->mCurrentProgress = 0;
+ info->mMaxProgress = 0;
+ }
+ }
+
+ nsCOMPtr<nsIStringBundleService> sbs =
+ mozilla::services::GetStringBundleService();
+ if (!sbs)
+ return NS_ERROR_FAILURE;
+ nsXPIDLString msg;
+ nsresult rv = sbs->FormatStatusMessage(aStatus, aStatusArg,
+ getter_Copies(msg));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Keep around the message. In case a request finishes, we need to make sure
+ // to send the status message of another request to our user to that we
+ // don't display, for example, "Transferring" messages for requests that are
+ // already done.
+ if (info) {
+ if (!info->mLastStatus) {
+ info->mLastStatus = new nsStatusInfo(aRequest);
+ } else {
+ // We're going to move it to the front of the list, so remove
+ // it from wherever it is now.
+ info->mLastStatus->remove();
+ }
+ info->mLastStatus->mStatusMessage = msg;
+ info->mLastStatus->mStatusCode = aStatus;
+ // Put the info at the front of the list
+ mStatusInfoList.insertFront(info->mLastStatus);
+ }
+ FireOnStatusChange(this, aRequest, aStatus, msg);
+ }
+ return NS_OK;
+}
+
+void nsDocLoader::ClearInternalProgress()
+{
+ ClearRequestInfoHash();
+
+ mCurrentSelfProgress = mMaxSelfProgress = 0;
+ mCurrentTotalProgress = mMaxTotalProgress = 0;
+ mCompletedTotalProgress = 0;
+
+ mProgressStateFlags = nsIWebProgressListener::STATE_STOP;
+}
+
+/**
+ * |_code| is executed for every listener matching |_flag|
+ * |listener| should be used inside |_code| as the nsIWebProgressListener var.
+ */
+#define NOTIFY_LISTENERS(_flag, _code) \
+PR_BEGIN_MACRO \
+ nsCOMPtr<nsIWebProgressListener> listener; \
+ ListenerArray::BackwardIterator iter(mListenerInfoList); \
+ while (iter.HasMore()) { \
+ nsListenerInfo &info = iter.GetNext(); \
+ if (!(info.mNotifyMask & (_flag))) { \
+ continue; \
+ } \
+ listener = do_QueryReferent(info.mWeakListener); \
+ if (!listener) { \
+ iter.Remove(); \
+ continue; \
+ } \
+ _code \
+ } \
+ mListenerInfoList.Compact(); \
+PR_END_MACRO
+
+void nsDocLoader::FireOnProgressChange(nsDocLoader *aLoadInitiator,
+ nsIRequest *request,
+ int64_t aProgress,
+ int64_t aProgressMax,
+ int64_t aProgressDelta,
+ int64_t aTotalProgress,
+ int64_t aMaxTotalProgress)
+{
+ if (mIsLoadingDocument) {
+ mCurrentTotalProgress += aProgressDelta;
+ mMaxTotalProgress = GetMaxTotalProgress();
+
+ aTotalProgress = mCurrentTotalProgress;
+ aMaxTotalProgress = mMaxTotalProgress;
+ }
+
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(request, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: Progress (%s): curSelf: %d maxSelf: %d curTotal: %d maxTotal %d\n",
+ this, buffer.get(), aProgress, aProgressMax, aTotalProgress, aMaxTotalProgress));
+#endif /* DEBUG */
+
+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_PROGRESS,
+ // XXX truncates 64-bit to 32-bit
+ listener->OnProgressChange(aLoadInitiator,request,
+ int32_t(aProgress), int32_t(aProgressMax),
+ int32_t(aTotalProgress), int32_t(aMaxTotalProgress));
+ );
+
+ // Pass the notification up to the parent...
+ if (mParent) {
+ mParent->FireOnProgressChange(aLoadInitiator, request,
+ aProgress, aProgressMax,
+ aProgressDelta,
+ aTotalProgress, aMaxTotalProgress);
+ }
+}
+
+void nsDocLoader::GatherAncestorWebProgresses(WebProgressList& aList)
+{
+ for (nsDocLoader* loader = this; loader; loader = loader->mParent) {
+ aList.AppendElement(loader);
+ }
+}
+
+void nsDocLoader::FireOnStateChange(nsIWebProgress *aProgress,
+ nsIRequest *aRequest,
+ int32_t aStateFlags,
+ nsresult aStatus)
+{
+ WebProgressList list;
+ GatherAncestorWebProgresses(list);
+ for (uint32_t i = 0; i < list.Length(); ++i) {
+ list[i]->DoFireOnStateChange(aProgress, aRequest, aStateFlags, aStatus);
+ }
+}
+
+void nsDocLoader::DoFireOnStateChange(nsIWebProgress * const aProgress,
+ nsIRequest * const aRequest,
+ int32_t &aStateFlags,
+ const nsresult aStatus)
+{
+ //
+ // Remove the STATE_IS_NETWORK bit if necessary.
+ //
+ // The rule is to remove this bit, if the notification has been passed
+ // up from a child WebProgress, and the current WebProgress is already
+ // active...
+ //
+ if (mIsLoadingDocument &&
+ (aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK) &&
+ (this != aProgress)) {
+ aStateFlags &= ~nsIWebProgressListener::STATE_IS_NETWORK;
+ }
+
+ // Add the STATE_RESTORING bit if necessary.
+ if (mIsRestoringDocument)
+ aStateFlags |= nsIWebProgressListener::STATE_RESTORING;
+
+#if defined(DEBUG)
+ nsAutoCString buffer;
+
+ GetURIStringFromRequest(aRequest, buffer);
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: Status (%s): code: %x\n",
+ this, buffer.get(), aStateFlags));
+#endif /* DEBUG */
+
+ NS_ASSERTION(aRequest, "Firing OnStateChange(...) notification with a NULL request!");
+
+ NOTIFY_LISTENERS(((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL),
+ listener->OnStateChange(aProgress, aRequest, aStateFlags, aStatus);
+ );
+}
+
+
+
+void
+nsDocLoader::FireOnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI *aUri,
+ uint32_t aFlags)
+{
+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_LOCATION,
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug, ("DocLoader [%p] calling %p->OnLocationChange", this, listener.get()));
+ listener->OnLocationChange(aWebProgress, aRequest, aUri, aFlags);
+ );
+
+ // Pass the notification up to the parent...
+ if (mParent) {
+ mParent->FireOnLocationChange(aWebProgress, aRequest, aUri, aFlags);
+ }
+}
+
+void
+nsDocLoader::FireOnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_STATUS,
+ listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ );
+
+ // Pass the notification up to the parent...
+ if (mParent) {
+ mParent->FireOnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ }
+}
+
+bool
+nsDocLoader::RefreshAttempted(nsIWebProgress* aWebProgress,
+ nsIURI *aURI,
+ int32_t aDelay,
+ bool aSameURI)
+{
+ /*
+ * Returns true if the refresh may proceed,
+ * false if the refresh should be blocked.
+ */
+ bool allowRefresh = true;
+
+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_REFRESH,
+ nsCOMPtr<nsIWebProgressListener2> listener2 =
+ do_QueryReferent(info.mWeakListener);
+ if (!listener2)
+ continue;
+
+ bool listenerAllowedRefresh;
+ nsresult listenerRV = listener2->OnRefreshAttempted(
+ aWebProgress, aURI, aDelay, aSameURI, &listenerAllowedRefresh);
+ if (NS_FAILED(listenerRV))
+ continue;
+
+ allowRefresh = allowRefresh && listenerAllowedRefresh;
+ );
+
+ // Pass the notification up to the parent...
+ if (mParent) {
+ allowRefresh = allowRefresh &&
+ mParent->RefreshAttempted(aWebProgress, aURI, aDelay, aSameURI);
+ }
+
+ return allowRefresh;
+}
+
+nsresult nsDocLoader::AddRequestInfo(nsIRequest *aRequest)
+{
+ if (!mRequestInfoHash.Add(aRequest, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+void nsDocLoader::RemoveRequestInfo(nsIRequest *aRequest)
+{
+ mRequestInfoHash.Remove(aRequest);
+}
+
+nsDocLoader::nsRequestInfo* nsDocLoader::GetRequestInfo(nsIRequest* aRequest)
+{
+ return static_cast<nsRequestInfo*>(mRequestInfoHash.Search(aRequest));
+}
+
+void nsDocLoader::ClearRequestInfoHash(void)
+{
+ mRequestInfoHash.Clear();
+}
+
+int64_t nsDocLoader::CalculateMaxProgress()
+{
+ int64_t max = mCompletedTotalProgress;
+ for (auto iter = mRequestInfoHash.Iter(); !iter.Done(); iter.Next()) {
+ auto info = static_cast<const nsRequestInfo*>(iter.Get());
+
+ if (info->mMaxProgress < info->mCurrentProgress) {
+ return int64_t(-1);
+ }
+ max += info->mMaxProgress;
+ }
+ return max;
+}
+
+NS_IMETHODIMP nsDocLoader::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
+ nsIChannel *aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback *cb)
+{
+ if (aOldChannel)
+ {
+ nsLoadFlags loadFlags = 0;
+ int32_t stateFlags = nsIWebProgressListener::STATE_REDIRECTING |
+ nsIWebProgressListener::STATE_IS_REQUEST;
+
+ aOldChannel->GetLoadFlags(&loadFlags);
+ // If the document channel is being redirected, then indicate that the
+ // document is being redirected in the notification...
+ if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)
+ {
+ stateFlags |= nsIWebProgressListener::STATE_IS_DOCUMENT;
+
+#if defined(DEBUG)
+ nsCOMPtr<nsIRequest> request(do_QueryInterface(aOldChannel));
+ NS_ASSERTION(request == mDocumentRequest, "Wrong Document Channel");
+#endif /* DEBUG */
+ }
+
+ OnRedirectStateChange(aOldChannel, aNewChannel, aFlags, stateFlags);
+ FireOnStateChange(this, aOldChannel, stateFlags, NS_OK);
+ }
+
+ cb->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+/*
+ * Implementation of nsISecurityEventSink method...
+ */
+
+NS_IMETHODIMP nsDocLoader::OnSecurityChange(nsISupports * aContext,
+ uint32_t aState)
+{
+ //
+ // Fire progress notifications out to any registered nsIWebProgressListeners.
+ //
+
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(aContext);
+ nsIWebProgress* webProgress = static_cast<nsIWebProgress*>(this);
+
+ NOTIFY_LISTENERS(nsIWebProgress::NOTIFY_SECURITY,
+ listener->OnSecurityChange(webProgress, request, aState);
+ );
+
+ // Pass the notification up to the parent...
+ if (mParent) {
+ mParent->OnSecurityChange(aContext, aState);
+ }
+ return NS_OK;
+}
+
+/*
+ * Implementation of nsISupportsPriority methods...
+ *
+ * The priority of the DocLoader _is_ the priority of its LoadGroup.
+ *
+ * XXX(darin): Once we start storing loadgroups in loadgroups, this code will
+ * go away.
+ */
+
+NS_IMETHODIMP nsDocLoader::GetPriority(int32_t *aPriority)
+{
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
+ if (p)
+ return p->GetPriority(aPriority);
+
+ *aPriority = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocLoader::SetPriority(int32_t aPriority)
+{
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: SetPriority(%d) called\n", this, aPriority));
+
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
+ if (p)
+ p->SetPriority(aPriority);
+
+ NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader,
+ SetPriority, (aPriority));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocLoader::AdjustPriority(int32_t aDelta)
+{
+ MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
+ ("DocLoader:%p: AdjustPriority(%d) called\n", this, aDelta));
+
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mLoadGroup);
+ if (p)
+ p->AdjustPriority(aDelta);
+
+ NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mChildList, nsDocLoader,
+ AdjustPriority, (aDelta));
+
+ return NS_OK;
+}
+
+
+
+
+#if 0
+void nsDocLoader::DumpChannelInfo()
+{
+ nsChannelInfo *info;
+ int32_t i, count;
+ int32_t current=0, max=0;
+
+
+ printf("==== DocLoader=%x\n", this);
+
+ count = mChannelInfoList.Count();
+ for(i=0; i<count; i++) {
+ info = (nsChannelInfo *)mChannelInfoList.ElementAt(i);
+
+#if defined(DEBUG)
+ nsAutoCString buffer;
+ nsresult rv = NS_OK;
+ if (info->mURI) {
+ rv = info->mURI->GetSpec(buffer);
+ }
+
+ printf(" [%d] current=%d max=%d [%s]\n", i,
+ info->mCurrentProgress,
+ info->mMaxProgress, buffer.get());
+#endif /* DEBUG */
+
+ current += info->mCurrentProgress;
+ if (max >= 0) {
+ if (info->mMaxProgress < info->mCurrentProgress) {
+ max = -1;
+ } else {
+ max += info->mMaxProgress;
+ }
+ }
+ }
+
+ printf("\nCurrent=%d Total=%d\n====\n", current, max);
+}
+#endif /* 0 */
diff --git a/components/uriloader/src/nsDocLoader.h b/components/uriloader/src/nsDocLoader.h
new file mode 100644
index 000000000..b469b8e07
--- /dev/null
+++ b/components/uriloader/src/nsDocLoader.h
@@ -0,0 +1,356 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/*
+*/
+
+#ifndef nsDocLoader_h__
+#define nsDocLoader_h__
+
+#include "nsIDocumentLoader.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsIRequestObserver.h"
+#include "nsWeakReference.h"
+#include "nsILoadGroup.h"
+#include "nsCOMArray.h"
+#include "nsTObserverArray.h"
+#include "nsString.h"
+#include "nsIChannel.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIChannelEventSink.h"
+#include "nsISecurityEventSink.h"
+#include "nsISupportsPriority.h"
+#include "nsCOMPtr.h"
+#include "PLDHashTable.h"
+#include "nsAutoPtr.h"
+
+#include "mozilla/LinkedList.h"
+
+/****************************************************************************
+ * nsDocLoader implementation...
+ ****************************************************************************/
+
+#define NS_THIS_DOCLOADER_IMPL_CID \
+ { /* b4ec8387-98aa-4c08-93b6-6d23069c06f2 */ \
+ 0xb4ec8387, \
+ 0x98aa, \
+ 0x4c08, \
+ {0x93, 0xb6, 0x6d, 0x23, 0x06, 0x9c, 0x06, 0xf2} \
+ }
+
+class nsDocLoader : public nsIDocumentLoader,
+ public nsIRequestObserver,
+ public nsSupportsWeakReference,
+ public nsIProgressEventSink,
+ public nsIWebProgress,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsISecurityEventSink,
+ public nsISupportsPriority
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_THIS_DOCLOADER_IMPL_CID)
+
+ nsDocLoader();
+
+ virtual MOZ_MUST_USE nsresult Init();
+
+ static already_AddRefed<nsDocLoader> GetAsDocLoader(nsISupports* aSupports);
+ // Needed to deal with ambiguous inheritance from nsISupports...
+ static nsISupports* GetAsSupports(nsDocLoader* aDocLoader) {
+ return static_cast<nsIDocumentLoader*>(aDocLoader);
+ }
+
+ // Add aDocLoader as a child to the docloader service.
+ static MOZ_MUST_USE nsresult AddDocLoaderAsChildOfRoot(nsDocLoader* aDocLoader);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOCUMENTLOADER
+
+ // nsIProgressEventSink
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+ NS_DECL_NSISECURITYEVENTSINK
+
+ // nsIRequestObserver methods: (for observing the load group)
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIWEBPROGRESS
+
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSISUPPORTSPRIORITY
+
+ // Implementation specific methods...
+
+ // Remove aChild from our childlist. This nulls out the child's mParent
+ // pointer.
+ MOZ_MUST_USE nsresult RemoveChildLoader(nsDocLoader *aChild);
+ // Add aChild to our child list. This will set aChild's mParent pointer to
+ // |this|.
+ MOZ_MUST_USE nsresult AddChildLoader(nsDocLoader* aChild);
+ nsDocLoader* GetParent() const { return mParent; }
+
+ struct nsListenerInfo {
+ nsListenerInfo(nsIWeakReference *aListener, unsigned long aNotifyMask)
+ : mWeakListener(aListener),
+ mNotifyMask(aNotifyMask)
+ {
+ }
+
+ // Weak pointer for the nsIWebProgressListener...
+ nsWeakPtr mWeakListener;
+
+ // Mask indicating which notifications the listener wants to receive.
+ unsigned long mNotifyMask;
+ };
+
+ void SetDocumentOpenedButNotLoaded() { mDocumentOpenedButNotLoaded = true; }
+
+protected:
+ virtual ~nsDocLoader();
+
+ virtual MOZ_MUST_USE nsresult SetDocLoaderParent(nsDocLoader * aLoader);
+
+ bool IsBusy();
+
+ void Destroy();
+ virtual void DestroyChildren();
+
+ nsIDocumentLoader* ChildAt(int32_t i) {
+ return mChildList.SafeElementAt(i, nullptr);
+ }
+
+ void FireOnProgressChange(nsDocLoader* aLoadInitiator,
+ nsIRequest *request,
+ int64_t aProgress,
+ int64_t aProgressMax,
+ int64_t aProgressDelta,
+ int64_t aTotalProgress,
+ int64_t aMaxTotalProgress);
+
+ // This should be at least 2 long since we'll generally always
+ // have the current page and the global docloader on the ancestor
+ // list. But to deal with frames it's better to make it a bit
+ // longer, and it's always a stack temporary so there's no real
+ // reason not to.
+ typedef AutoTArray<RefPtr<nsDocLoader>, 8> WebProgressList;
+ void GatherAncestorWebProgresses(WebProgressList& aList);
+
+ void FireOnStateChange(nsIWebProgress *aProgress,
+ nsIRequest* request,
+ int32_t aStateFlags,
+ nsresult aStatus);
+
+ // The guts of FireOnStateChange, but does not call itself on our ancestors.
+ // The arguments that are const are const so that we can detect cases when
+ // DoFireOnStateChange wants to propagate changes to the next web progress
+ // at compile time. The ones that are not, are references so that such
+ // changes can be propagated.
+ void DoFireOnStateChange(nsIWebProgress * const aProgress,
+ nsIRequest* const request,
+ int32_t &aStateFlags,
+ const nsresult aStatus);
+
+ void FireOnStatusChange(nsIWebProgress *aWebProgress,
+ nsIRequest *aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage);
+
+ void FireOnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI *aUri,
+ uint32_t aFlags);
+
+ MOZ_MUST_USE bool RefreshAttempted(nsIWebProgress* aWebProgress,
+ nsIURI *aURI,
+ int32_t aDelay,
+ bool aSameURI);
+
+ // this function is overridden by the docshell, it is provided so that we
+ // can pass more information about redirect state (the normal OnStateChange
+ // doesn't get the new channel).
+ // @param aRedirectFlags The flags being sent to OnStateChange that
+ // indicate the type of redirect.
+ // @param aStateFlags The channel flags normally sent to OnStateChange.
+ virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aRedirectFlags,
+ uint32_t aStateFlags) {}
+
+ void doStartDocumentLoad();
+ void doStartURLLoad(nsIRequest *request);
+ void doStopURLLoad(nsIRequest *request, nsresult aStatus);
+ void doStopDocumentLoad(nsIRequest *request, nsresult aStatus);
+
+ // Inform a parent docloader that aChild is about to call its onload
+ // handler.
+ MOZ_MUST_USE bool ChildEnteringOnload(nsIDocumentLoader* aChild) {
+ // It's ok if we're already in the list -- we'll just be in there twice
+ // and then the RemoveObject calls from ChildDoneWithOnload will remove
+ // us.
+ return mChildrenInOnload.AppendObject(aChild);
+ }
+
+ // Inform a parent docloader that aChild is done calling its onload
+ // handler.
+ void ChildDoneWithOnload(nsIDocumentLoader* aChild) {
+ mChildrenInOnload.RemoveObject(aChild);
+ DocLoaderIsEmpty(true);
+ }
+
+protected:
+ struct nsStatusInfo : public mozilla::LinkedListElement<nsStatusInfo>
+ {
+ nsString mStatusMessage;
+ nsresult mStatusCode;
+ // Weak mRequest is ok; we'll be told if it decides to go away.
+ nsIRequest * const mRequest;
+
+ explicit nsStatusInfo(nsIRequest* aRequest) :
+ mRequest(aRequest)
+ {
+ MOZ_COUNT_CTOR(nsStatusInfo);
+ }
+ ~nsStatusInfo()
+ {
+ MOZ_COUNT_DTOR(nsStatusInfo);
+ }
+ };
+
+ struct nsRequestInfo : public PLDHashEntryHdr
+ {
+ explicit nsRequestInfo(const void* key)
+ : mKey(key), mCurrentProgress(0), mMaxProgress(0), mUploading(false)
+ , mLastStatus(nullptr)
+ {
+ MOZ_COUNT_CTOR(nsRequestInfo);
+ }
+
+ ~nsRequestInfo()
+ {
+ MOZ_COUNT_DTOR(nsRequestInfo);
+ }
+
+ nsIRequest* Request() {
+ return static_cast<nsIRequest*>(const_cast<void*>(mKey));
+ }
+
+ const void* mKey; // Must be first for the PLDHashTable stubs to work
+ int64_t mCurrentProgress;
+ int64_t mMaxProgress;
+ bool mUploading;
+
+ nsAutoPtr<nsStatusInfo> mLastStatus;
+ };
+
+ static void RequestInfoHashInitEntry(PLDHashEntryHdr* entry, const void* key);
+ static void RequestInfoHashClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry);
+
+ // IMPORTANT: The ownership implicit in the following member
+ // variables has been explicitly checked and set using nsCOMPtr
+ // for owning pointers and raw COM interface pointers for weak
+ // (ie, non owning) references. If you add any members to this
+ // class, please make the ownership explicit (pinkerton, scc).
+
+ nsCOMPtr<nsIRequest> mDocumentRequest; // [OWNER] ???compare with document
+
+ nsDocLoader* mParent; // [WEAK]
+
+ typedef nsAutoTObserverArray<nsListenerInfo, 8> ListenerArray;
+ ListenerArray mListenerInfoList;
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ // We hold weak refs to all our kids
+ nsTObserverArray<nsDocLoader*> mChildList;
+
+ // The following member variables are related to the new nsIWebProgress
+ // feedback interfaces that travis cooked up.
+ int32_t mProgressStateFlags;
+
+ int64_t mCurrentSelfProgress;
+ int64_t mMaxSelfProgress;
+
+ int64_t mCurrentTotalProgress;
+ int64_t mMaxTotalProgress;
+
+ PLDHashTable mRequestInfoHash;
+ int64_t mCompletedTotalProgress;
+
+ mozilla::LinkedList<nsStatusInfo> mStatusInfoList;
+
+ /*
+ * This flag indicates that the loader is loading a document. It is set
+ * from the call to LoadDocument(...) until the OnConnectionsComplete(...)
+ * notification is fired...
+ */
+ bool mIsLoadingDocument;
+
+ /* Flag to indicate that we're in the process of restoring a document. */
+ bool mIsRestoringDocument;
+
+ /* Flag to indicate that we're in the process of flushing layout
+ under DocLoaderIsEmpty() and should not do another flush. */
+ bool mDontFlushLayout;
+
+ /* Flag to indicate whether we should consider ourselves as currently
+ flushing layout for the purposes of IsBusy. For example, if Stop has
+ been called then IsBusy should return false even if we are still
+ flushing. */
+ bool mIsFlushingLayout;
+
+private:
+ /**
+ * This flag indicates that the loader is waiting for completion of
+ * a document.open-triggered "document load". This is set when
+ * document.open() happens and sets up a new parser and cleared out
+ * when we go to fire our load event or end up with a new document
+ * channel.
+ */
+ bool mDocumentOpenedButNotLoaded;
+
+ static const PLDHashTableOps sRequestInfoHashOps;
+
+ // A list of kids that are in the middle of their onload calls and will let
+ // us know once they're done. We don't want to fire onload for "normal"
+ // DocLoaderIsEmpty calls (those coming from requests finishing in our
+ // loadgroup) unless this is empty.
+ nsCOMArray<nsIDocumentLoader> mChildrenInOnload;
+
+ // DocLoaderIsEmpty should be called whenever the docloader may be empty.
+ // This method is idempotent and does nothing if the docloader is not in
+ // fact empty. This method _does_ make sure that layout is flushed if our
+ // loadgroup has no active requests before checking for "real" emptiness if
+ // aFlushLayout is true.
+ void DocLoaderIsEmpty(bool aFlushLayout);
+
+ int64_t GetMaxTotalProgress();
+
+ nsresult AddRequestInfo(nsIRequest* aRequest);
+ void RemoveRequestInfo(nsIRequest* aRequest);
+ nsRequestInfo *GetRequestInfo(nsIRequest* aRequest);
+ void ClearRequestInfoHash();
+ int64_t CalculateMaxProgress();
+ /// void DumpChannelInfo(void);
+
+ // used to clear our internal progress state between loads...
+ void ClearInternalProgress();
+
+ /**
+ * Used to test whether we might need to fire a load event. This
+ * can happen when we have a document load going on, or when we've
+ * had document.open() called and haven't fired the corresponding
+ * load event yet.
+ */
+ bool IsBlockingLoadEvent() const {
+ return mIsLoadingDocument || mDocumentOpenedButNotLoaded;
+ }
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsDocLoader, NS_THIS_DOCLOADER_IMPL_CID)
+
+#endif /* nsDocLoader_h__ */
diff --git a/components/uriloader/src/nsURILoader.cpp b/components/uriloader/src/nsURILoader.cpp
new file mode 100644
index 000000000..5a6b38028
--- /dev/null
+++ b/components/uriloader/src/nsURILoader.cpp
@@ -0,0 +1,956 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sts=2 sw=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsURILoader.h"
+#include "nsAutoPtr.h"
+#include "nsIURIContentListener.h"
+#include "nsIContentHandler.h"
+#include "nsILoadGroup.h"
+#include "nsIDocumentLoader.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsIIOService.h"
+#include "nsIServiceManager.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInputStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsWeakReference.h"
+#include "nsIHttpChannel.h"
+#include "nsIMultiPartChannel.h"
+#include "netCore.h"
+#include "nsCRT.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIThreadRetargetableStreamListener.h"
+
+#include "nsXPIDLString.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsReadableUtils.h"
+#include "nsError.h"
+
+#include "nsICategoryManager.h"
+#include "nsCExternalHandlerService.h" // contains contractids for the helper app service
+
+#include "nsIMIMEHeaderParam.h"
+#include "nsNetCID.h"
+
+#include "nsMimeTypes.h"
+
+#include "nsDocLoader.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+#include "nsContentUtils.h"
+
+mozilla::LazyLogModule nsURILoader::mLog("URILoader");
+
+#define LOG(args) MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Debug, args)
+#define LOG_ERROR(args) MOZ_LOG(nsURILoader::mLog, mozilla::LogLevel::Error, args)
+#define LOG_ENABLED() MOZ_LOG_TEST(nsURILoader::mLog, mozilla::LogLevel::Debug)
+
+#define NS_PREF_DISABLE_BACKGROUND_HANDLING \
+ "security.exthelperapp.disable_background_handling"
+
+/**
+ * The nsDocumentOpenInfo contains the state required when a single
+ * document is being opened in order to discover the content type...
+ * Each instance remains alive until its target URL has been loaded
+ * (or aborted).
+ */
+class nsDocumentOpenInfo final : public nsIStreamListener
+ , public nsIThreadRetargetableStreamListener
+{
+public:
+ // Needed for nsCOMPtr to work right... Don't call this!
+ nsDocumentOpenInfo();
+
+ // Real constructor
+ // aFlags is a combination of the flags on nsIURILoader
+ nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
+ uint32_t aFlags,
+ nsURILoader* aURILoader);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /**
+ * Prepares this object for receiving data. The stream
+ * listener methods of this class must not be called before calling this
+ * method.
+ */
+ nsresult Prepare();
+
+ // Call this (from OnStartRequest) to attempt to find an nsIStreamListener to
+ // take the data off our hands.
+ nsresult DispatchContent(nsIRequest *request, nsISupports * aCtxt);
+
+ // Call this if we need to insert a stream converter from aSrcContentType to
+ // aOutContentType into the StreamListener chain. DO NOT call it if the two
+ // types are the same, since no conversion is needed in that case.
+ nsresult ConvertData(nsIRequest *request,
+ nsIURIContentListener *aListener,
+ const nsACString & aSrcContentType,
+ const nsACString & aOutContentType);
+
+ /**
+ * Function to attempt to use aListener to handle the load. If
+ * true is returned, nothing else needs to be done; if false
+ * is returned, then a different way of handling the load should be
+ * tried.
+ */
+ bool TryContentListener(nsIURIContentListener* aListener,
+ nsIChannel* aChannel);
+
+ // nsIRequestObserver methods:
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // nsIStreamListener methods:
+ NS_DECL_NSISTREAMLISTENER
+
+ // nsIThreadRetargetableStreamListener
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+protected:
+ ~nsDocumentOpenInfo();
+
+protected:
+ /**
+ * The first content listener to try dispatching data to. Typically
+ * the listener associated with the entity that originated the load.
+ */
+ nsCOMPtr<nsIURIContentListener> m_contentListener;
+
+ /**
+ * The stream listener to forward nsIStreamListener notifications
+ * to. This is set once the load is dispatched.
+ */
+ nsCOMPtr<nsIStreamListener> m_targetStreamListener;
+
+ /**
+ * A pointer to the entity that originated the load. We depend on getting
+ * things like nsIURIContentListeners, nsIDOMWindows, etc off of it.
+ */
+ nsCOMPtr<nsIInterfaceRequestor> m_originalContext;
+
+ /**
+ * IS_CONTENT_PREFERRED is used for the boolean to pass to CanHandleContent
+ * (also determines whether we use CanHandleContent or IsPreferred).
+ * DONT_RETARGET means that we will only try m_originalContext, no other
+ * listeners.
+ */
+ uint32_t mFlags;
+
+ /**
+ * The type of the data we will be trying to dispatch.
+ */
+ nsCString mContentType;
+
+ /**
+ * Reference to the URILoader service so we can access its list of
+ * nsIURIContentListeners.
+ */
+ RefPtr<nsURILoader> mURILoader;
+};
+
+NS_IMPL_ADDREF(nsDocumentOpenInfo)
+NS_IMPL_RELEASE(nsDocumentOpenInfo)
+
+NS_INTERFACE_MAP_BEGIN(nsDocumentOpenInfo)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsDocumentOpenInfo::nsDocumentOpenInfo()
+{
+ NS_NOTREACHED("This should never be called\n");
+}
+
+nsDocumentOpenInfo::nsDocumentOpenInfo(nsIInterfaceRequestor* aWindowContext,
+ uint32_t aFlags,
+ nsURILoader* aURILoader)
+ : m_originalContext(aWindowContext),
+ mFlags(aFlags),
+ mURILoader(aURILoader)
+{
+}
+
+nsDocumentOpenInfo::~nsDocumentOpenInfo()
+{
+}
+
+nsresult nsDocumentOpenInfo::Prepare()
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::Prepare", this));
+
+ nsresult rv;
+
+ // ask our window context if it has a uri content listener...
+ m_contentListener = do_GetInterface(m_originalContext, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsDocumentOpenInfo::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::OnStartRequest", this));
+ MOZ_ASSERT(request);
+ if (!request) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+
+ //
+ // Deal with "special" HTTP responses:
+ //
+ // - In the case of a 204 (No Content) or 205 (Reset Content) response, do
+ // not try to find a content handler. Return NS_BINDING_ABORTED to cancel
+ // the request. This has the effect of ensuring that the DocLoader does
+ // not try to interpret this as a real request.
+ //
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request, &rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t responseCode = 0;
+
+ rv = httpChannel->GetResponseStatus(&responseCode);
+
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" Failed to get HTTP response status"));
+
+ // behave as in the canceled case
+ return NS_OK;
+ }
+
+ LOG((" HTTP response status: %d", responseCode));
+
+ if (204 == responseCode || 205 == responseCode) {
+ return NS_BINDING_ABORTED;
+ }
+
+ static bool sLargeAllocationHeaderEnabled = false;
+ static bool sCachedLargeAllocationPref = false;
+ if (!sCachedLargeAllocationPref) {
+ sCachedLargeAllocationPref = true;
+ mozilla::Preferences::AddBoolVarCache(&sLargeAllocationHeaderEnabled,
+ "dom.largeAllocationHeader.enabled");
+ }
+
+ if (sLargeAllocationHeaderEnabled) {
+ // If we have a Large-Allocation header, let's check if we should perform a process switch.
+ nsAutoCString largeAllocationHeader;
+ rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Large-Allocation"), largeAllocationHeader);
+ if (NS_SUCCEEDED(rv) && nsContentUtils::AttemptLargeAllocationLoad(httpChannel)) {
+ return NS_BINDING_ABORTED;
+ }
+ }
+ }
+
+ //
+ // Make sure that the transaction has succeeded, so far...
+ //
+ nsresult status;
+
+ rv = request->GetStatus(&status);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to get request status!");
+ if (NS_FAILED(rv)) return rv;
+
+ if (NS_FAILED(status)) {
+ LOG_ERROR((" Request failed, status: 0x%08X", rv));
+
+ //
+ // The transaction has already reported an error - so it will be torn
+ // down. Therefore, it is not necessary to return an error code...
+ //
+ return NS_OK;
+ }
+
+ rv = DispatchContent(request, aCtxt);
+
+ LOG((" After dispatch, m_targetStreamListener: 0x%p, rv: 0x%08X", m_targetStreamListener.get(), rv));
+
+ NS_ASSERTION(NS_SUCCEEDED(rv) || !m_targetStreamListener,
+ "Must not have an m_targetStreamListener with a failure return!");
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_targetStreamListener)
+ rv = m_targetStreamListener->OnStartRequest(request, aCtxt);
+
+ LOG((" OnStartRequest returning: 0x%08X", rv));
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocumentOpenInfo::CheckListenerChain()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(m_targetStreamListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ LOG(("[0x%p] nsDocumentOpenInfo::CheckListenerChain %s listener %p rv %x",
+ this, (NS_SUCCEEDED(rv) ? "success" : "failure"),
+ (nsIStreamListener*)m_targetStreamListener, rv));
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt,
+ nsIInputStream * inStr,
+ uint64_t sourceOffset, uint32_t count)
+{
+ // if we have retarged to the end stream listener, then forward the call....
+ // otherwise, don't do anything
+
+ nsresult rv = NS_OK;
+
+ if (m_targetStreamListener)
+ rv = m_targetStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
+ return rv;
+}
+
+NS_IMETHODIMP nsDocumentOpenInfo::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
+ nsresult aStatus)
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::OnStopRequest", this));
+
+ if ( m_targetStreamListener)
+ {
+ nsCOMPtr<nsIStreamListener> listener(m_targetStreamListener);
+
+ // If this is a multipart stream, we could get another
+ // OnStartRequest after this... reset state.
+ m_targetStreamListener = nullptr;
+ mContentType.Truncate();
+ listener->OnStopRequest(request, aCtxt, aStatus);
+ }
+
+ // Remember...
+ // In the case of multiplexed streams (such as multipart/x-mixed-replace)
+ // these stream listener methods could be called again :-)
+ //
+ return NS_OK;
+}
+
+nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest *request, nsISupports * aCtxt)
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::DispatchContent for type '%s'", this, mContentType.get()));
+
+ NS_PRECONDITION(!m_targetStreamListener,
+ "Why do we already have a target stream listener?");
+
+ nsresult rv;
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (!aChannel) {
+ LOG_ERROR((" Request is not a channel. Bailing."));
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_NAMED_LITERAL_CSTRING(anyType, "*/*");
+ if (mContentType.IsEmpty() || mContentType == anyType) {
+ rv = aChannel->GetContentType(mContentType);
+ if (NS_FAILED(rv)) return rv;
+ LOG((" Got type from channel: '%s'", mContentType.get()));
+ }
+
+ bool isGuessFromExt =
+ mContentType.LowerCaseEqualsASCII(APPLICATION_GUESS_FROM_EXT);
+ if (isGuessFromExt) {
+ // Reset to application/octet-stream for now; no one other than the
+ // external helper app service should see APPLICATION_GUESS_FROM_EXT.
+ mContentType = APPLICATION_OCTET_STREAM;
+ aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
+ }
+
+ // Check whether the data should be forced to be handled externally. This
+ // could happen because the Content-Disposition header is set so, or, in the
+ // future, because the user has specified external handling for the MIME
+ // type.
+ bool forceExternalHandling = false;
+ uint32_t disposition;
+ rv = aChannel->GetContentDisposition(&disposition);
+
+ bool allowContentDispositionToForceExternalHandling = true;
+
+ if (NS_SUCCEEDED(rv) && (disposition == nsIChannel::DISPOSITION_ATTACHMENT) &&
+ allowContentDispositionToForceExternalHandling) {
+ forceExternalHandling = true;
+ }
+
+ LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
+
+ // The type or data the contentListener wants.
+ nsXPIDLCString desiredContentType;
+
+ if (!forceExternalHandling)
+ {
+ //
+ // First step: See whether m_contentListener wants to handle this
+ // content type.
+ //
+ if (m_contentListener && TryContentListener(m_contentListener, aChannel)) {
+ LOG((" Success! Our default listener likes this type"));
+ // All done here
+ return NS_OK;
+ }
+
+ // If we aren't allowed to try other listeners, just skip through to
+ // trying to convert the data.
+ if (!(mFlags & nsIURILoader::DONT_RETARGET)) {
+
+ //
+ // Second step: See whether some other registered listener wants
+ // to handle this content type.
+ //
+ int32_t count = mURILoader->m_listeners.Count();
+ nsCOMPtr<nsIURIContentListener> listener;
+ for (int32_t i = 0; i < count; i++) {
+ listener = do_QueryReferent(mURILoader->m_listeners[i]);
+ if (listener) {
+ if (TryContentListener(listener, aChannel)) {
+ LOG((" Found listener registered on the URILoader"));
+ return NS_OK;
+ }
+ } else {
+ // remove from the listener list, reset i and update count
+ mURILoader->m_listeners.RemoveObjectAt(i--);
+ --count;
+ }
+ }
+
+ //
+ // Third step: Try to find a content listener that has not yet had
+ // the chance to register, as it is contained in a not-yet-loaded
+ // module, but which has registered a contract ID.
+ //
+ nsCOMPtr<nsICategoryManager> catman =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
+ if (catman) {
+ nsXPIDLCString contractidString;
+ rv = catman->GetCategoryEntry(NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY,
+ mContentType.get(),
+ getter_Copies(contractidString));
+ if (NS_SUCCEEDED(rv) && !contractidString.IsEmpty()) {
+ LOG((" Listener contractid for '%s' is '%s'",
+ mContentType.get(), contractidString.get()));
+
+ listener = do_CreateInstance(contractidString);
+ LOG((" Listener from category manager: 0x%p", listener.get()));
+
+ if (listener && TryContentListener(listener, aChannel)) {
+ LOG((" Listener from category manager likes this type"));
+ return NS_OK;
+ }
+ }
+ }
+
+ //
+ // Fourth step: try to find an nsIContentHandler for our type.
+ //
+ nsAutoCString handlerContractID (NS_CONTENT_HANDLER_CONTRACTID_PREFIX);
+ handlerContractID += mContentType;
+
+ nsCOMPtr<nsIContentHandler> contentHandler =
+ do_CreateInstance(handlerContractID.get());
+ if (contentHandler) {
+ LOG((" Content handler found"));
+ rv = contentHandler->HandleContent(mContentType.get(),
+ m_originalContext, request);
+ // XXXbz returning an error code to represent handling the
+ // content is just bizarre!
+ if (rv != NS_ERROR_WONT_HANDLE_CONTENT) {
+ if (NS_FAILED(rv)) {
+ // The content handler has unexpectedly failed. Cancel the request
+ // just in case the handler didn't...
+ LOG((" Content handler failed. Aborting load"));
+ request->Cancel(rv);
+ }
+ else {
+ LOG((" Content handler taking over load"));
+ }
+
+ return rv;
+ }
+ }
+ } else {
+ LOG((" DONT_RETARGET flag set, so skipped over random other content "
+ "listeners and content handlers"));
+ }
+
+ //
+ // Fifth step: If no listener prefers this type, see if any stream
+ // converters exist to transform this content type into
+ // some other.
+ //
+ // Don't do this if the server sent us a MIME type of "*/*" because they saw
+ // it in our Accept header and got confused.
+ // XXXbz have to be careful here; may end up in some sort of bizarre infinite
+ // decoding loop.
+ if (mContentType != anyType) {
+ rv = ConvertData(request, m_contentListener, mContentType, anyType);
+ if (NS_FAILED(rv)) {
+ m_targetStreamListener = nullptr;
+ } else if (m_targetStreamListener) {
+ // We found a converter for this MIME type. We'll just pump data into it
+ // and let the downstream nsDocumentOpenInfo handle things.
+ LOG((" Converter taking over now"));
+ return NS_OK;
+ }
+ }
+ }
+
+ NS_ASSERTION(!m_targetStreamListener,
+ "If we found a listener, why are we not using it?");
+
+ if (mFlags & nsIURILoader::DONT_RETARGET) {
+ LOG((" External handling forced or (listener not interested and no "
+ "stream converter exists), and retargeting disallowed -> aborting"));
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+
+ // Before dispatching to the external helper app service, check for an HTTP
+ // error page. If we got one, we don't want to handle it with a helper app,
+ // really.
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
+ if (httpChannel) {
+ bool requestSucceeded;
+ httpChannel->GetRequestSucceeded(&requestSucceeded);
+ if (!requestSucceeded) {
+ // returning error from OnStartRequest will cancel the channel
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+ }
+
+ // Sixth step:
+ //
+ // All attempts to dispatch this content have failed. Just pass it off to
+ // the helper app service.
+ //
+
+ //
+ // Optionally, we may want to disable background handling by the external
+ // helper application service.
+ //
+ if (mozilla::Preferences::GetBool(NS_PREF_DISABLE_BACKGROUND_HANDLING,
+ false)) {
+ // First, we will ensure that the parent docshell is in an active
+ // state as we will disallow all external application handling unless it is
+ // in the foreground.
+ nsCOMPtr<nsIDocShell> docShell(do_GetInterface(m_originalContext));
+ if (!docShell) {
+ // If we can't perform our security check we definitely don't want to go
+ // any further!
+ LOG(("Failed to get DocShell to ensure it is active before anding off to "
+ "helper app service. Aborting."));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Ensure the DocShell is active before continuing.
+ bool isActive = false;
+ docShell->GetIsActive(&isActive);
+ if (!isActive) {
+ LOG((" Check for active DocShell returned false. Aborting hand off to "
+ "helper app service."));
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ }
+
+ nsCOMPtr<nsIExternalHelperAppService> helperAppService =
+ do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
+ if (helperAppService) {
+ LOG((" Passing load off to helper app service"));
+
+ // Set these flags to indicate that the channel has been targeted and that
+ // we are not using the original consumer.
+ nsLoadFlags loadFlags = 0;
+ request->GetLoadFlags(&loadFlags);
+ request->SetLoadFlags(loadFlags | nsIChannel::LOAD_RETARGETED_DOCUMENT_URI
+ | nsIChannel::LOAD_TARGETED);
+
+ if (isGuessFromExt) {
+ mContentType = APPLICATION_GUESS_FROM_EXT;
+ aChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_GUESS_FROM_EXT));
+ }
+
+ rv = helperAppService->DoContent(mContentType,
+ request,
+ m_originalContext,
+ false,
+ nullptr,
+ getter_AddRefs(m_targetStreamListener));
+ if (NS_FAILED(rv)) {
+ request->SetLoadFlags(loadFlags);
+ m_targetStreamListener = nullptr;
+ }
+ }
+
+ NS_ASSERTION(m_targetStreamListener || NS_FAILED(rv),
+ "There is no way we should be successful at this point without a m_targetStreamListener");
+ return rv;
+}
+
+nsresult
+nsDocumentOpenInfo::ConvertData(nsIRequest *request,
+ nsIURIContentListener* aListener,
+ const nsACString& aSrcContentType,
+ const nsACString& aOutContentType)
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::ConvertData from '%s' to '%s'", this,
+ PromiseFlatCString(aSrcContentType).get(),
+ PromiseFlatCString(aOutContentType).get()));
+
+ NS_PRECONDITION(aSrcContentType != aOutContentType,
+ "ConvertData called when the two types are the same!");
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIStreamConverterService> StreamConvService =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG((" Got converter service"));
+
+ // When applying stream decoders, it is necessary to "insert" an
+ // intermediate nsDocumentOpenInfo instance to handle the targeting of
+ // the "final" stream or streams.
+ //
+ // For certain content types (ie. multi-part/x-mixed-replace) the input
+ // stream is split up into multiple destination streams. This
+ // intermediate instance is used to target these "decoded" streams...
+ //
+ RefPtr<nsDocumentOpenInfo> nextLink =
+ new nsDocumentOpenInfo(m_originalContext, mFlags, mURILoader);
+
+ LOG((" Downstream DocumentOpenInfo would be: 0x%p", nextLink.get()));
+
+ // Make sure nextLink starts with the contentListener that said it wanted the
+ // results of this decode.
+ nextLink->m_contentListener = aListener;
+ // Also make sure it has to look for a stream listener to pump data into.
+ nextLink->m_targetStreamListener = nullptr;
+
+ // Make sure that nextLink treats the data as aOutContentType when
+ // dispatching; that way even if the stream converters don't change the type
+ // on the channel we will still do the right thing. If aOutContentType is
+ // */*, that's OK -- that will just indicate to nextLink that it should get
+ // the type off the channel.
+ nextLink->mContentType = aOutContentType;
+
+ // The following call sets m_targetStreamListener to the input end of the
+ // stream converter and sets the output end of the stream converter to
+ // nextLink. As we pump data into m_targetStreamListener the stream
+ // converter will convert it and pass the converted data to nextLink.
+ return StreamConvService->AsyncConvertData(PromiseFlatCString(aSrcContentType).get(),
+ PromiseFlatCString(aOutContentType).get(),
+ nextLink,
+ request,
+ getter_AddRefs(m_targetStreamListener));
+}
+
+bool
+nsDocumentOpenInfo::TryContentListener(nsIURIContentListener* aListener,
+ nsIChannel* aChannel)
+{
+ LOG(("[0x%p] nsDocumentOpenInfo::TryContentListener; mFlags = 0x%x",
+ this, mFlags));
+
+ NS_PRECONDITION(aListener, "Must have a non-null listener");
+ NS_PRECONDITION(aChannel, "Must have a channel");
+
+ bool listenerWantsContent = false;
+ nsXPIDLCString typeToUse;
+
+ if (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) {
+ aListener->IsPreferred(mContentType.get(),
+ getter_Copies(typeToUse),
+ &listenerWantsContent);
+ } else {
+ aListener->CanHandleContent(mContentType.get(), false,
+ getter_Copies(typeToUse),
+ &listenerWantsContent);
+ }
+ if (!listenerWantsContent) {
+ LOG((" Listener is not interested"));
+ return false;
+ }
+
+ if (!typeToUse.IsEmpty() && typeToUse != mContentType) {
+ // Need to do a conversion here.
+
+ nsresult rv = ConvertData(aChannel, aListener, mContentType, typeToUse);
+
+ if (NS_FAILED(rv)) {
+ // No conversion path -- we don't want this listener, if we got one
+ m_targetStreamListener = nullptr;
+ }
+
+ LOG((" Found conversion: %s", m_targetStreamListener ? "yes" : "no"));
+
+ // m_targetStreamListener is now the input end of the converter, and we can
+ // just pump the data in there, if it exists. If it does not, we need to
+ // try other nsIURIContentListeners.
+ return m_targetStreamListener != nullptr;
+ }
+
+ // At this point, aListener wants data of type mContentType. Let 'em have
+ // it. But first, if we are retargeting, set an appropriate flag on the
+ // channel
+ nsLoadFlags loadFlags = 0;
+ aChannel->GetLoadFlags(&loadFlags);
+
+ // Set this flag to indicate that the channel has been targeted at a final
+ // consumer. This load flag is tested in nsDocLoader::OnProgress.
+ nsLoadFlags newLoadFlags = nsIChannel::LOAD_TARGETED;
+
+ nsCOMPtr<nsIURIContentListener> originalListener =
+ do_GetInterface(m_originalContext);
+ if (originalListener != aListener) {
+ newLoadFlags |= nsIChannel::LOAD_RETARGETED_DOCUMENT_URI;
+ }
+ aChannel->SetLoadFlags(loadFlags | newLoadFlags);
+
+ bool abort = false;
+ bool isPreferred = (mFlags & nsIURILoader::IS_CONTENT_PREFERRED) != 0;
+ nsresult rv = aListener->DoContent(mContentType,
+ isPreferred,
+ aChannel,
+ getter_AddRefs(m_targetStreamListener),
+ &abort);
+
+ if (NS_FAILED(rv)) {
+ LOG_ERROR((" DoContent failed"));
+
+ // Unset the RETARGETED_DOCUMENT_URI flag if we set it...
+ aChannel->SetLoadFlags(loadFlags);
+ m_targetStreamListener = nullptr;
+ return false;
+ }
+
+ if (abort) {
+ // Nothing else to do here -- aListener is handling it all. Make
+ // sure m_targetStreamListener is null so we don't do anything
+ // after this point.
+ LOG((" Listener has aborted the load"));
+ m_targetStreamListener = nullptr;
+ }
+
+ NS_ASSERTION(abort || m_targetStreamListener, "DoContent returned no listener?");
+
+ // aListener is handling the load from this point on.
+ return true;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+// Implementation of nsURILoader
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+nsURILoader::nsURILoader()
+{
+}
+
+nsURILoader::~nsURILoader()
+{
+}
+
+NS_IMPL_ADDREF(nsURILoader)
+NS_IMPL_RELEASE(nsURILoader)
+
+NS_INTERFACE_MAP_BEGIN(nsURILoader)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURILoader)
+ NS_INTERFACE_MAP_ENTRY(nsIURILoader)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP nsURILoader::RegisterContentListener(nsIURIContentListener * aContentListener)
+{
+ nsresult rv = NS_OK;
+
+ nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
+ NS_ASSERTION(weakListener, "your URIContentListener must support weak refs!\n");
+
+ if (weakListener)
+ m_listeners.AppendObject(weakListener);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsURILoader::UnRegisterContentListener(nsIURIContentListener * aContentListener)
+{
+ nsWeakPtr weakListener = do_GetWeakReference(aContentListener);
+ if (weakListener)
+ m_listeners.RemoveObject(weakListener);
+
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP nsURILoader::OpenURI(nsIChannel *channel,
+ uint32_t aFlags,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ NS_ENSURE_ARG_POINTER(channel);
+
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ LOG(("nsURILoader::OpenURI for %s", spec.get()));
+ }
+
+ nsCOMPtr<nsIStreamListener> loader;
+ nsresult rv = OpenChannel(channel,
+ aFlags,
+ aWindowContext,
+ false,
+ getter_AddRefs(loader));
+
+ if (NS_SUCCEEDED(rv)) {
+ // this method is not complete!!! Eventually, we should first go
+ // to the content listener and ask them for a protocol handler...
+ // if they don't give us one, we need to go to the registry and get
+ // the preferred protocol handler.
+
+ // But for now, I'm going to let necko do the work for us....
+ rv = channel->AsyncOpen2(loader);
+
+ // no content from this load - that's OK.
+ if (rv == NS_ERROR_NO_CONTENT) {
+ LOG((" rv is NS_ERROR_NO_CONTENT -- doing nothing"));
+ rv = NS_OK;
+ }
+ } else if (rv == NS_ERROR_WONT_HANDLE_CONTENT) {
+ // Not really an error, from this method's point of view
+ rv = NS_OK;
+ }
+ return rv;
+}
+
+nsresult nsURILoader::OpenChannel(nsIChannel* channel,
+ uint32_t aFlags,
+ nsIInterfaceRequestor* aWindowContext,
+ bool aChannelIsOpen,
+ nsIStreamListener** aListener)
+{
+ NS_ASSERTION(channel, "Trying to open a null channel!");
+ NS_ASSERTION(aWindowContext, "Window context must not be null");
+
+ if (LOG_ENABLED()) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsAutoCString spec;
+ uri->GetAsciiSpec(spec);
+ LOG(("nsURILoader::OpenChannel for %s", spec.get()));
+ }
+
+ // Let the window context's uriListener know that the open is starting. This
+ // gives that window a chance to abort the load process.
+ nsCOMPtr<nsIURIContentListener> winContextListener(do_GetInterface(aWindowContext));
+ if (winContextListener) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ bool doAbort = false;
+ winContextListener->OnStartURIOpen(uri, &doAbort);
+
+ if (doAbort) {
+ LOG((" OnStartURIOpen aborted load"));
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+ }
+ }
+
+ // we need to create a DocumentOpenInfo object which will go ahead and open
+ // the url and discover the content type....
+ RefPtr<nsDocumentOpenInfo> loader =
+ new nsDocumentOpenInfo(aWindowContext, aFlags, this);
+
+ // Set the correct loadgroup on the channel
+ nsCOMPtr<nsILoadGroup> loadGroup(do_GetInterface(aWindowContext));
+
+ if (!loadGroup) {
+ // XXXbz This context is violating what we'd like to be the new uriloader
+ // api.... Set up a nsDocLoader to handle the loadgroup for this context.
+ // This really needs to go away!
+ nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(aWindowContext));
+ if (listener) {
+ nsCOMPtr<nsISupports> cookie;
+ listener->GetLoadCookie(getter_AddRefs(cookie));
+ if (!cookie) {
+ RefPtr<nsDocLoader> newDocLoader = new nsDocLoader();
+ nsresult rv = newDocLoader->Init();
+ if (NS_FAILED(rv))
+ return rv;
+ rv = nsDocLoader::AddDocLoaderAsChildOfRoot(newDocLoader);
+ if (NS_FAILED(rv))
+ return rv;
+ cookie = nsDocLoader::GetAsSupports(newDocLoader);
+ listener->SetLoadCookie(cookie);
+ }
+ loadGroup = do_GetInterface(cookie);
+ }
+ }
+
+ // If the channel is pending, then we need to remove it from its current
+ // loadgroup
+ nsCOMPtr<nsILoadGroup> oldGroup;
+ channel->GetLoadGroup(getter_AddRefs(oldGroup));
+ if (aChannelIsOpen && !SameCOMIdentity(oldGroup, loadGroup)) {
+ // It is important to add the channel to the new group before
+ // removing it from the old one, so that the load isn't considered
+ // done as soon as the request is removed.
+ loadGroup->AddRequest(channel, nullptr);
+
+ if (oldGroup) {
+ oldGroup->RemoveRequest(channel, nullptr, NS_BINDING_RETARGETED);
+ }
+ }
+
+ channel->SetLoadGroup(loadGroup);
+
+ // prepare the loader for receiving data
+ nsresult rv = loader->Prepare();
+ if (NS_SUCCEEDED(rv))
+ NS_ADDREF(*aListener = loader);
+ return rv;
+}
+
+NS_IMETHODIMP nsURILoader::OpenChannel(nsIChannel* channel,
+ uint32_t aFlags,
+ nsIInterfaceRequestor* aWindowContext,
+ nsIStreamListener** aListener)
+{
+ bool pending;
+ if (NS_FAILED(channel->IsPending(&pending))) {
+ pending = false;
+ }
+
+ return OpenChannel(channel, aFlags, aWindowContext, pending, aListener);
+}
+
+NS_IMETHODIMP nsURILoader::Stop(nsISupports* aLoadCookie)
+{
+ nsresult rv;
+ nsCOMPtr<nsIDocumentLoader> docLoader;
+
+ NS_ENSURE_ARG_POINTER(aLoadCookie);
+
+ docLoader = do_GetInterface(aLoadCookie, &rv);
+ if (docLoader) {
+ rv = docLoader->Stop();
+ }
+ return rv;
+}
+
diff --git a/components/uriloader/src/nsURILoader.h b/components/uriloader/src/nsURILoader.h
new file mode 100644
index 000000000..2c5648dba
--- /dev/null
+++ b/components/uriloader/src/nsURILoader.h
@@ -0,0 +1,59 @@
+/* -*- 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 nsURILoader_h__
+#define nsURILoader_h__
+
+#include "nsCURILoader.h"
+#include "nsISupportsUtils.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsString.h"
+#include "nsIWeakReference.h"
+#include "mozilla/Attributes.h"
+
+#include "mozilla/Logging.h"
+
+class nsDocumentOpenInfo;
+
+class nsURILoader final : public nsIURILoader
+{
+public:
+ NS_DECL_NSIURILOADER
+ NS_DECL_ISUPPORTS
+
+ nsURILoader();
+
+protected:
+ ~nsURILoader();
+
+ /**
+ * Equivalent to nsIURILoader::openChannel, but allows specifying whether the
+ * channel is opened already.
+ */
+ MOZ_MUST_USE nsresult OpenChannel(nsIChannel* channel,
+ uint32_t aFlags,
+ nsIInterfaceRequestor* aWindowContext,
+ bool aChannelOpen,
+ nsIStreamListener** aListener);
+
+ /**
+ * we shouldn't need to have an owning ref count on registered
+ * content listeners because they are supposed to unregister themselves
+ * when they go away. This array stores weak references
+ */
+ nsCOMArray<nsIWeakReference> m_listeners;
+
+ /**
+ * Logging. The module is called "URILoader"
+ */
+ static mozilla::LazyLogModule mLog;
+
+ friend class nsDocumentOpenInfo;
+};
+
+#endif /* nsURILoader_h__ */
diff --git a/components/urlformatter/api_keys.in b/components/urlformatter/api_keys.in
new file mode 100644
index 000000000..0edbcf26a
--- /dev/null
+++ b/components/urlformatter/api_keys.in
@@ -0,0 +1,3 @@
+#define MOZ_MOZILLA_API_KEY @MOZ_MOZILLA_API_KEY@
+#define MOZ_BING_API_KEY @MOZ_BING_API_KEY@
+#define MOZ_BING_API_CLIENTID @MOZ_BING_API_CLIENTID@
diff --git a/components/urlformatter/moz.build b/components/urlformatter/moz.build
new file mode 100644
index 000000000..9fd996860
--- /dev/null
+++ b/components/urlformatter/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['nsIURLFormatter.idl']
+
+XPIDL_MODULE = 'urlformatter'
+
+EXTRA_COMPONENTS += ['nsURLFormatter.manifest']
+
+EXTRA_PP_COMPONENTS += ['nsURLFormatter.js']
+
+CONFIGURE_SUBST_FILES += ['api_keys']
+
+DEFINES['OBJDIR'] = OBJDIR
diff --git a/components/urlformatter/nsIURLFormatter.idl b/components/urlformatter/nsIURLFormatter.idl
new file mode 100644
index 000000000..a4a549d57
--- /dev/null
+++ b/components/urlformatter/nsIURLFormatter.idl
@@ -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/. */
+
+/**
+ * nsIURLFormatter
+ *
+ * nsIURLFormatter exposes methods to substitute variables in URL formats.
+ * Variable names can contain 'A-Z' letters and '_' characters.
+ *
+ * Mozilla Applications linking to Mozilla websites are strongly encouraged to use
+ * URLs of the following format:
+ *
+ * http[s]://%SERVICE%.mozilla.[com|org]/%LOCALE%/
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(4ab31d30-372d-11db-a98b-0800200c9a66)]
+interface nsIURLFormatter: nsISupports
+{
+ /**
+ * formatURL - Formats a string URL
+ *
+ * The set of known variables is predefined.
+ * If a variable is unknown, it is left unchanged and a non-fatal error is reported.
+ *
+ * @param aFormat string Unformatted URL.
+ *
+ * @return The formatted URL.
+ */
+ AString formatURL(in AString aFormat);
+
+ /**
+ * formatURLPref - Formats a string URL stored in a preference
+ *
+ * If the preference value cannot be retrieved, a fatal error is reported
+ * and the "about:blank" URL is returned.
+ *
+ * @param aPref string Preference name.
+ *
+ * @return The formatted URL returned by formatURL(), or "about:blank".
+ */
+ AString formatURLPref(in AString aPref);
+
+ /**
+ * Remove all of the sensitive query parameter strings from URLs in |aMsg|.
+ */
+ AString trimSensitiveURLs(in AString aMsg);
+};
diff --git a/components/urlformatter/nsURLFormatter.js b/components/urlformatter/nsURLFormatter.js
new file mode 100644
index 000000000..86ebf5961
--- /dev/null
+++ b/components/urlformatter/nsURLFormatter.js
@@ -0,0 +1,151 @@
+#filter substitution
+#include @OBJDIR@/api_keys
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ /**
+ * @class nsURLFormatterService
+ *
+ * nsURLFormatterService exposes methods to substitute variables in URL formats.
+ *
+ * Mozilla Applications linking to Mozilla websites are strongly encouraged to use
+ * URLs of the following format:
+ *
+ * http[s]://%SERVICE%.mozilla.[com|org]/%LOCALE%/
+ */
+
+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 PREF_APP_DISTRIBUTION = "distribution.id";
+const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
+
+XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
+ "resource://gre/modules/UpdateUtils.jsm");
+
+function nsURLFormatterService() {
+ XPCOMUtils.defineLazyGetter(this, "appInfo", function UFS_appInfo() {
+ return Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo).
+ QueryInterface(Ci.nsIXULRuntime);
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "ABI", function UFS_ABI() {
+ let ABI = "default";
+ try {
+ ABI = this.appInfo.XPCOMABI;
+
+ if ("@mozilla.org/xpcom/mac-utils;1" in Cc) {
+ // Mac universal build should report a different ABI than either macppc
+ // or mactel.
+ let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"]
+ .getService(Ci.nsIMacUtils);
+ if (macutils && macutils.isUniversalBinary) {
+ ABI = "Universal-gcc3";
+ }
+ }
+ } catch (e) {}
+
+ return ABI;
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "OSVersion", function UFS_OSVersion() {
+ let OSVersion = "default";
+ let sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ try {
+ OSVersion = sysInfo.getProperty("name") + " " +
+ sysInfo.getProperty("version");
+ OSVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
+ } catch (e) {}
+
+ return encodeURIComponent(OSVersion);
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "distribution", function UFS_distribution() {
+ let distribution = { id: "default", version: "default" };
+
+ let defaults = Services.prefs.getDefaultBranch(null);
+ try {
+ distribution.id = defaults.getCharPref(PREF_APP_DISTRIBUTION);
+ } catch (e) {}
+ try {
+ distribution.version = defaults.getCharPref(PREF_APP_DISTRIBUTION_VERSION);
+ } catch (e) {}
+
+ return distribution;
+ });
+}
+
+nsURLFormatterService.prototype = {
+ classID: Components.ID("{e6156350-2be8-11db-a98b-0800200c9a66}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIURLFormatter]),
+
+ _defaults: {
+ LOCALE: () => Cc["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Ci.nsIXULChromeRegistry).
+ getSelectedLocale('global'),
+ VENDOR: function() { return this.appInfo.vendor; },
+ NAME: function() { return this.appInfo.name; },
+ ID: function() { return this.appInfo.ID; },
+ VERSION: function() { return this.appInfo.version; },
+ MAJOR_VERSION: function() { return this.appInfo.version.replace(/^([^\.]+\.[0-9]+[a-z]*).*/gi, "$1"); },
+ APPBUILDID: function() { return this.appInfo.appBuildID; },
+ PLATFORMVERSION: function() { return this.appInfo.greVersion; },
+ PLATFORMBUILDID: function() { return this.appInfo.platformBuildID; },
+ APP: function() { return this.appInfo.name.toLowerCase().replace(/ /, ""); },
+ OS: function() { return this.appInfo.OS; },
+ XPCOMABI: function() { return this.ABI; },
+ BUILD_TARGET: function() { return this.appInfo.OS + "_" + this.ABI; },
+ OS_VERSION: function() { return this.OSVersion; },
+ CHANNEL: () => UpdateUtils.UpdateChannel,
+ MOZILLA_API_KEY: () => "@MOZ_MOZILLA_API_KEY@",
+ BING_API_CLIENTID:() => "@MOZ_BING_API_CLIENTID@",
+ BING_API_KEY: () => "@MOZ_BING_API_KEY@",
+ DISTRIBUTION: function() { return this.distribution.id; },
+ DISTRIBUTION_VERSION: function() { return this.distribution.version; }
+ },
+
+ formatURL: function uf_formatURL(aFormat) {
+ var _this = this;
+ var replacementCallback = function(aMatch, aKey) {
+ if (aKey in _this._defaults) {
+ return _this._defaults[aKey].call(_this);
+ }
+ Cu.reportError("formatURL: Couldn't find value for key: " + aKey);
+ return aMatch;
+ }
+ return aFormat.replace(/%([A-Z_]+)%/g, replacementCallback);
+ },
+
+ formatURLPref: function uf_formatURLPref(aPref) {
+ var format = null;
+ var PS = Cc['@mozilla.org/preferences-service;1'].
+ getService(Ci.nsIPrefBranch);
+
+ try {
+ format = PS.getComplexValue(aPref, Ci.nsISupportsString).data;
+ } catch(ex) {
+ Cu.reportError("formatURLPref: Couldn't get pref: " + aPref);
+ return "about:blank";
+ }
+
+ if (!PS.prefHasUserValue(aPref) &&
+ /^(data:text\/plain,.+=.+|chrome:\/\/.+\/locale\/.+\.properties)$/.test(format)) {
+ // This looks as if it might be a localised preference
+ try {
+ format = PS.getComplexValue(aPref, Ci.nsIPrefLocalizedString).data;
+ } catch(ex) {}
+ }
+
+ return this.formatURL(format);
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsURLFormatterService]);
diff --git a/components/urlformatter/nsURLFormatter.manifest b/components/urlformatter/nsURLFormatter.manifest
new file mode 100644
index 000000000..3f9123d73
--- /dev/null
+++ b/components/urlformatter/nsURLFormatter.manifest
@@ -0,0 +1,2 @@
+component {e6156350-2be8-11db-a98b-0800200c9a66} nsURLFormatter.js
+contract @mozilla.org/toolkit/URLFormatterService;1 {e6156350-2be8-11db-a98b-0800200c9a66}
diff --git a/components/utils/moz.build b/components/utils/moz.build
new file mode 100644
index 000000000..6cfa7a5c7
--- /dev/null
+++ b/components/utils/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_COMPONENTS += [
+ 'simpleServices.js',
+ 'utils.manifest',
+]
diff --git a/components/utils/simpleServices.js b/components/utils/simpleServices.js
new file mode 100644
index 000000000..0b8dfe877
--- /dev/null
+++ b/components/utils/simpleServices.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/. */
+
+/*
+ * Dumping ground for simple services for which the isolation of a full global
+ * is overkill. Be careful about namespace pollution, and be mindful about
+ * importing lots of JSMs in global scope, since this file will almost certainly
+ * be loaded from enough callsites that any such imports will always end up getting
+ * eagerly loaded at startup.
+ */
+
+"use strict";
+
+const Cc = Components.classes;
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+function AddonPolicyService()
+{
+ this.wrappedJSObject = this;
+ this.cspStrings = new Map();
+ this.backgroundPageUrlCallbacks = new Map();
+ this.checkHasPermissionCallbacks = new Map();
+ this.mayLoadURICallbacks = new Map();
+ this.localizeCallbacks = new Map();
+
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this, "baseCSP", "extensions.webextensions.base-content-security-policy",
+ "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " +
+ "object-src 'self' https://* moz-extension: blob: filesystem:;");
+
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this, "defaultCSP", "extensions.webextensions.default-content-security-policy",
+ "script-src 'self'; object-src 'self';");
+}
+
+AddonPolicyService.prototype = {
+ classID: Components.ID("{89560ed3-72e3-498d-a0e8-ffe50334d7c5}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonPolicyService]),
+
+ /**
+ * Returns the content security policy which applies to documents belonging
+ * to the extension with the given ID. This may be either a custom policy,
+ * if one was supplied, or the default policy if one was not.
+ */
+ getAddonCSP(aAddonId) {
+ let csp = this.cspStrings.get(aAddonId);
+ return csp || this.defaultCSP;
+ },
+
+ /**
+ * Returns the generated background page as a data-URI, if any. If the addon
+ * does not have an auto-generated background page, an empty string is
+ * returned.
+ */
+ getGeneratedBackgroundPageUrl(aAddonId) {
+ let cb = this.backgroundPageUrlCallbacks.get(aAddonId);
+ return cb && cb(aAddonId) || '';
+ },
+
+ /*
+ * Invokes a callback (if any) associated with the addon to determine whether
+ * the addon is granted the |aPerm| API permission.
+ *
+ * @see nsIAddonPolicyService.addonHasPermission
+ */
+ addonHasPermission(aAddonId, aPerm) {
+ let cb = this.checkHasPermissionCallbacks.get(aAddonId);
+ return cb ? cb(aPerm) : false;
+ },
+
+ /*
+ * Invokes a callback (if any) associated with the addon to determine whether
+ * unprivileged code running within the addon is allowed to perform loads from
+ * the given URI.
+ *
+ * @see nsIAddonPolicyService.addonMayLoadURI
+ */
+ addonMayLoadURI(aAddonId, aURI) {
+ let cb = this.mayLoadURICallbacks.get(aAddonId);
+ return cb ? cb(aURI) : false;
+ },
+
+ /*
+ * Invokes a callback (if any) associated with the addon to loclaize a
+ * resource belonging to that add-on.
+ */
+ localizeAddonString(aAddonId, aString) {
+ let cb = this.localizeCallbacks.get(aAddonId);
+ return cb ? cb(aString) : aString;
+ },
+
+ /*
+ * Invokes a callback (if any) to determine if an extension URI should be
+ * web-accessible.
+ *
+ * @see nsIAddonPolicyService.extensionURILoadableByAnyone
+ */
+ extensionURILoadableByAnyone(aURI) {
+ if (aURI.scheme != "moz-extension") {
+ throw new TypeError("non-extension URI passed");
+ }
+
+ let cb = this.extensionURILoadCallback;
+ return cb ? cb(aURI) : false;
+ },
+
+ /*
+ * Maps an extension URI to an addon ID.
+ *
+ * @see nsIAddonPolicyService.extensionURIToAddonId
+ */
+ extensionURIToAddonId(aURI) {
+ if (aURI.scheme != "moz-extension") {
+ throw new TypeError("non-extension URI passed");
+ }
+
+ let cb = this.extensionURIToAddonIdCallback;
+ if (!cb) {
+ throw new Error("no callback set to map extension URIs to addon Ids");
+ }
+ return cb(aURI);
+ },
+
+ /*
+ * Sets the callbacks used in addonHasPermission above. Not accessible over
+ * XPCOM - callers should use .wrappedJSObject on the service to call it
+ * directly.
+ */
+ setAddonHasPermissionCallback(aAddonId, aCallback) {
+ if (aCallback) {
+ this.checkHasPermissionCallbacks.set(aAddonId, aCallback);
+ } else {
+ this.checkHasPermissionCallbacks.delete(aAddonId);
+ }
+ },
+
+ /*
+ * Sets the callbacks used in addonMayLoadURI above. Not accessible over
+ * XPCOM - callers should use .wrappedJSObject on the service to call it
+ * directly.
+ */
+ setAddonLoadURICallback(aAddonId, aCallback) {
+ if (aCallback) {
+ this.mayLoadURICallbacks.set(aAddonId, aCallback);
+ } else {
+ this.mayLoadURICallbacks.delete(aAddonId);
+ }
+ },
+
+ /*
+ * Sets the custom CSP string to be used for the add-on. Not accessible over
+ * XPCOM - callers should use .wrappedJSObject on the service to call it
+ * directly.
+ */
+ setAddonCSP(aAddonId, aCSPString) {
+ if (aCSPString) {
+ this.cspStrings.set(aAddonId, aCSPString);
+ } else {
+ this.cspStrings.delete(aAddonId);
+ }
+ },
+
+ /**
+ * Set the callback that generates a data-URL for the background page.
+ */
+ setBackgroundPageUrlCallback(aAddonId, aCallback) {
+ if (aCallback) {
+ this.backgroundPageUrlCallbacks.set(aAddonId, aCallback);
+ } else {
+ this.backgroundPageUrlCallbacks.delete(aAddonId);
+ }
+ },
+
+ /*
+ * Sets the callbacks used by the stream converter service to localize
+ * add-on resources.
+ */
+ setAddonLocalizeCallback(aAddonId, aCallback) {
+ if (aCallback) {
+ this.localizeCallbacks.set(aAddonId, aCallback);
+ } else {
+ this.localizeCallbacks.delete(aAddonId);
+ }
+ },
+
+ /*
+ * Sets the callback used in extensionURILoadableByAnyone above. Not
+ * accessible over XPCOM - callers should use .wrappedJSObject on the
+ * service to call it directly.
+ */
+ setExtensionURILoadCallback(aCallback) {
+ var old = this.extensionURILoadCallback;
+ this.extensionURILoadCallback = aCallback;
+ return old;
+ },
+
+ /*
+ * Sets the callback used in extensionURIToAddonId above. Not accessible over
+ * XPCOM - callers should use .wrappedJSObject on the service to call it
+ * directly.
+ */
+ setExtensionURIToAddonIdCallback(aCallback) {
+ var old = this.extensionURIToAddonIdCallback;
+ this.extensionURIToAddonIdCallback = aCallback;
+ return old;
+ }
+};
+
+/*
+ * This class provides a stream filter for locale messages in CSS files served
+ * by the moz-extension: protocol handler.
+ *
+ * See SubstituteChannel in netwerk/protocol/res/ExtensionProtocolHandler.cpp
+ * for usage.
+ */
+function AddonLocalizationConverter()
+{
+ this.aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService)
+ .wrappedJSObject;
+}
+
+AddonLocalizationConverter.prototype = {
+ classID: Components.ID("{ded150e3-c92e-4077-a396-0dba9953e39f}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamConverter]),
+
+ FROM_TYPE: "application/vnd.mozilla.webext.unlocalized",
+ TO_TYPE: "text/css",
+
+ checkTypes(aFromType, aToType) {
+ if (aFromType != this.FROM_TYPE) {
+ throw Components.Exception("Invalid aFromType value", Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller.caller);
+ }
+ if (aToType != this.TO_TYPE) {
+ throw Components.Exception("Invalid aToType value", Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller.caller);
+ }
+ },
+
+ // aContext must be a nsIURI object for a valid moz-extension: URL.
+ getAddonId(aContext) {
+ // In this case, we want the add-on ID even if the URL is web accessible,
+ // so check the root rather than the exact path.
+ let uri = Services.io.newURI("/", null, aContext);
+
+ let id = this.aps.extensionURIToAddonId(uri);
+ if (id == undefined) {
+ throw new Components.Exception("Invalid context", Cr.NS_ERROR_INVALID_ARG);
+ }
+ return id;
+ },
+
+ convertToStream(aAddonId, aString) {
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+
+ stream.data = this.aps.localizeAddonString(aAddonId, aString);
+ return stream;
+ },
+
+ convert(aStream, aFromType, aToType, aContext) {
+ this.checkTypes(aFromType, aToType);
+ let addonId = this.getAddonId(aContext);
+
+ let string = (
+ aStream.available() ?
+ NetUtil.readInputStreamToString(aStream, aStream.available()): ""
+ );
+ return this.convertToStream(addonId, string);
+ },
+
+ asyncConvertData(aFromType, aToType, aListener, aContext) {
+ this.checkTypes(aFromType, aToType);
+ this.addonId = this.getAddonId(aContext);
+ this.listener = aListener;
+ },
+
+ onStartRequest(aRequest, aContext) {
+ this.parts = [];
+ },
+
+ onDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount) {
+ this.parts.push(NetUtil.readInputStreamToString(aInputStream, aCount));
+ },
+
+ onStopRequest(aRequest, aContext, aStatusCode) {
+ try {
+ this.listener.onStartRequest(aRequest, null);
+ if (Components.isSuccessCode(aStatusCode)) {
+ let string = this.parts.join("");
+ let stream = this.convertToStream(this.addonId, string);
+
+ this.listener.onDataAvailable(aRequest, null, stream, 0, stream.data.length);
+ }
+ } catch (e) {
+ aStatusCode = e.result || Cr.NS_ERROR_FAILURE;
+ }
+ this.listener.onStopRequest(aRequest, null, aStatusCode);
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AddonPolicyService,
+ AddonLocalizationConverter]);
diff --git a/components/utils/utils.manifest b/components/utils/utils.manifest
new file mode 100644
index 000000000..b2d30bd98
--- /dev/null
+++ b/components/utils/utils.manifest
@@ -0,0 +1,6 @@
+component {dfd07380-6083-11e4-9803-0800200c9a66} simpleServices.js
+contract @mozilla.org/addons/remote-tag-service;1 {dfd07380-6083-11e4-9803-0800200c9a66}
+component {89560ed3-72e3-498d-a0e8-ffe50334d7c5} simpleServices.js
+contract @mozilla.org/addons/policy-service;1 {89560ed3-72e3-498d-a0e8-ffe50334d7c5}
+component {ded150e3-c92e-4077-a396-0dba9953e39f} simpleServices.js
+contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.webext.unlocalized&to=text/css {ded150e3-c92e-4077-a396-0dba9953e39f}
diff --git a/components/viewconfig/content/config.js b/components/viewconfig/content/config.js
new file mode 100644
index 000000000..562962894
--- /dev/null
+++ b/components/viewconfig/content/config.js
@@ -0,0 +1,635 @@
+// -*- 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/Services.jsm");
+
+const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
+const nsISupportsString = Components.interfaces.nsISupportsString;
+const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
+const nsIClipboardHelper = Components.interfaces.nsIClipboardHelper;
+
+const nsSupportsString_CONTRACTID = "@mozilla.org/supports-string;1";
+const nsPrompt_CONTRACTID = "@mozilla.org/embedcomp/prompt-service;1";
+const nsPrefService_CONTRACTID = "@mozilla.org/preferences-service;1";
+const nsClipboardHelper_CONTRACTID = "@mozilla.org/widget/clipboardhelper;1";
+const nsAtomService_CONTRACTID = "@mozilla.org/atom-service;1";
+
+const gPrefBranch = Services.prefs;
+const gClipboardHelper = Components.classes[nsClipboardHelper_CONTRACTID].getService(nsIClipboardHelper);
+
+var gLockProps = ["default", "user", "locked"];
+// we get these from a string bundle
+var gLockStrs = [];
+var gTypeStrs = [];
+
+const PREF_IS_DEFAULT_VALUE = 0;
+const PREF_IS_USER_SET = 1;
+const PREF_IS_LOCKED = 2;
+
+var gPrefHash = {};
+var gPrefArray = [];
+var gPrefView = gPrefArray; // share the JS array
+var gSortedColumn = "prefCol";
+var gSortFunction = null;
+var gSortDirection = 1; // 1 is ascending; -1 is descending
+var gConfigBundle = null;
+var gFilter = null;
+
+var view = {
+ get rowCount() { return gPrefView.length; },
+ getCellText : function(index, col) {
+ if (!(index in gPrefView))
+ return "";
+
+ var value = gPrefView[index][col.id];
+
+ switch (col.id) {
+ case "lockCol":
+ return gLockStrs[value];
+ case "typeCol":
+ return gTypeStrs[value];
+ default:
+ return value;
+ }
+ },
+ getRowProperties : function(index) { return ""; },
+ getCellProperties : function(index, col) {
+ if (index in gPrefView)
+ return gLockProps[gPrefView[index].lockCol];
+
+ return "";
+ },
+ getColumnProperties : function(col) { return ""; },
+ treebox : null,
+ selection : null,
+ isContainer : function(index) { return false; },
+ isContainerOpen : function(index) { return false; },
+ isContainerEmpty : function(index) { return false; },
+ isSorted : function() { return true; },
+ canDrop : function(index, orientation) { return false; },
+ drop : function(row, orientation) {},
+ setTree : function(out) { this.treebox = out; },
+ getParentIndex: function(rowIndex) { return -1; },
+ hasNextSibling: function(rowIndex, afterIndex) { return false; },
+ getLevel: function(index) { return 1; },
+ getImageSrc: function(row, col) { return ""; },
+ toggleOpenState : function(index) {},
+ cycleHeader: function(col) {
+ var index = this.selection.currentIndex;
+ if (col.id == gSortedColumn) {
+ gSortDirection = -gSortDirection;
+ gPrefArray.reverse();
+ if (gPrefView != gPrefArray)
+ gPrefView.reverse();
+ if (index >= 0)
+ index = gPrefView.length - index - 1;
+ }
+ else {
+ var pref = null;
+ if (index >= 0)
+ pref = gPrefView[index];
+
+ var old = document.getElementById(gSortedColumn);
+ old.removeAttribute("sortDirection");
+ gPrefArray.sort(gSortFunction = gSortFunctions[col.id]);
+ if (gPrefView != gPrefArray)
+ gPrefView.sort(gSortFunction);
+ gSortedColumn = col.id;
+ if (pref)
+ index = getViewIndexOfPref(pref);
+ }
+ col.element.setAttribute("sortDirection", gSortDirection > 0 ? "ascending" : "descending");
+ this.treebox.invalidate();
+ if (index >= 0) {
+ this.selection.select(index);
+ this.treebox.ensureRowIsVisible(index);
+ }
+ },
+ 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) {},
+ isSeparator: function(index) { return false; }
+};
+
+// find the index in gPrefView of a pref object
+// or -1 if it does not exist in the filtered view
+function getViewIndexOfPref(pref)
+{
+ var low = -1, high = gPrefView.length;
+ var index = (low + high) >> 1;
+ while (index > low) {
+ var mid = gPrefView[index];
+ if (mid == pref)
+ return index;
+ if (gSortFunction(mid, pref) < 0)
+ low = index;
+ else
+ high = index;
+ index = (low + high) >> 1;
+ }
+ return -1;
+}
+
+// find the index in gPrefView where a pref object belongs
+function getNearestViewIndexOfPref(pref)
+{
+ var low = -1, high = gPrefView.length;
+ var index = (low + high) >> 1;
+ while (index > low) {
+ if (gSortFunction(gPrefView[index], pref) < 0)
+ low = index;
+ else
+ high = index;
+ index = (low + high) >> 1;
+ }
+ return high;
+}
+
+// find the index in gPrefArray of a pref object
+function getIndexOfPref(pref)
+{
+ var low = -1, high = gPrefArray.length;
+ var index = (low + high) >> 1;
+ while (index > low) {
+ var mid = gPrefArray[index];
+ if (mid == pref)
+ return index;
+ if (gSortFunction(mid, pref) < 0)
+ low = index;
+ else
+ high = index;
+ index = (low + high) >> 1;
+ }
+ return index;
+}
+
+function getNearestIndexOfPref(pref)
+{
+ var low = -1, high = gPrefArray.length;
+ var index = (low + high) >> 1;
+ while (index > low) {
+ if (gSortFunction(gPrefArray[index], pref) < 0)
+ low = index;
+ else
+ high = index;
+ index = (low + high) >> 1;
+ }
+ return high;
+}
+
+var gPrefListener =
+{
+ observe: function(subject, topic, prefName)
+ {
+ if (topic != "nsPref:changed")
+ return;
+
+ var arrayIndex = gPrefArray.length;
+ var viewIndex = arrayIndex;
+ var selectedIndex = view.selection.currentIndex;
+ var pref;
+ var updateView = false;
+ var updateArray = false;
+ var addedRow = false;
+ if (prefName in gPrefHash) {
+ pref = gPrefHash[prefName];
+ viewIndex = getViewIndexOfPref(pref);
+ arrayIndex = getIndexOfPref(pref);
+ fetchPref(prefName, arrayIndex);
+ // fetchPref replaces the existing pref object
+ pref = gPrefHash[prefName];
+ if (viewIndex >= 0) {
+ // Might need to update the filtered view
+ gPrefView[viewIndex] = gPrefHash[prefName];
+ view.treebox.invalidateRow(viewIndex);
+ }
+ if (gSortedColumn == "lockCol" || gSortedColumn == "valueCol") {
+ updateArray = true;
+ gPrefArray.splice(arrayIndex, 1);
+ if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
+ updateView = true;
+ gPrefView.splice(viewIndex, 1);
+ }
+ }
+ }
+ else {
+ fetchPref(prefName, arrayIndex);
+ pref = gPrefArray.pop();
+ updateArray = true;
+ addedRow = true;
+ if (gFilter && gFilter.test(pref.prefCol + ";" + pref.valueCol)) {
+ updateView = true;
+ }
+ }
+ if (updateArray) {
+ // Reinsert in the data array
+ var newIndex = getNearestIndexOfPref(pref);
+ gPrefArray.splice(newIndex, 0, pref);
+
+ if (updateView) {
+ // View is filtered, reinsert in the view separately
+ newIndex = getNearestViewIndexOfPref(pref);
+ gPrefView.splice(newIndex, 0, pref);
+ }
+ else if (gFilter) {
+ // View is filtered, but nothing to update
+ return;
+ }
+
+ if (addedRow)
+ view.treebox.rowCountChanged(newIndex, 1);
+
+ // Invalidate the changed range in the view
+ var low = Math.min(viewIndex, newIndex);
+ var high = Math.max(viewIndex, newIndex);
+ view.treebox.invalidateRange(low, high);
+
+ if (selectedIndex == viewIndex) {
+ selectedIndex = newIndex;
+ }
+ else if (selectedIndex >= low && selectedIndex <= high) {
+ selectedIndex += (newIndex > viewIndex) ? -1 : 1;
+ }
+ if (selectedIndex >= 0) {
+ view.selection.select(selectedIndex);
+ if (selectedIndex == newIndex)
+ view.treebox.ensureRowIsVisible(selectedIndex);
+ }
+ }
+ }
+};
+
+function prefObject(prefName, prefIndex)
+{
+ this.prefCol = prefName;
+}
+
+prefObject.prototype =
+{
+ lockCol: PREF_IS_DEFAULT_VALUE,
+ typeCol: nsIPrefBranch.PREF_STRING,
+ valueCol: ""
+};
+
+function fetchPref(prefName, prefIndex)
+{
+ var pref = new prefObject(prefName);
+
+ gPrefHash[prefName] = pref;
+ gPrefArray[prefIndex] = pref;
+
+ if (gPrefBranch.prefIsLocked(prefName))
+ pref.lockCol = PREF_IS_LOCKED;
+ else if (gPrefBranch.prefHasUserValue(prefName))
+ pref.lockCol = PREF_IS_USER_SET;
+
+ try {
+ switch (gPrefBranch.getPrefType(prefName)) {
+ case gPrefBranch.PREF_BOOL:
+ pref.typeCol = gPrefBranch.PREF_BOOL;
+ // convert to a string
+ pref.valueCol = gPrefBranch.getBoolPref(prefName).toString();
+ break;
+ case gPrefBranch.PREF_INT:
+ pref.typeCol = gPrefBranch.PREF_INT;
+ // convert to a string
+ pref.valueCol = gPrefBranch.getIntPref(prefName).toString();
+ break;
+ default:
+ case gPrefBranch.PREF_STRING:
+ pref.valueCol = gPrefBranch.getComplexValue(prefName, nsISupportsString).data;
+ // Try in case it's a localized string (will throw an exception if not)
+ if (pref.lockCol == PREF_IS_DEFAULT_VALUE &&
+ /^chrome:\/\/.+\/locale\/.+\.properties/.test(pref.valueCol))
+ pref.valueCol = gPrefBranch.getComplexValue(prefName, nsIPrefLocalizedString).data;
+ break;
+ }
+ } catch (e) {
+ // Also catch obscure cases in which you can't tell in advance
+ // that the pref exists but has no user or default value...
+ }
+}
+
+function onConfigLoad()
+{
+ // Load strings
+ gConfigBundle = document.getElementById("configBundle");
+
+ gLockStrs[PREF_IS_DEFAULT_VALUE] = gConfigBundle.getString("default");
+ gLockStrs[PREF_IS_USER_SET] = gConfigBundle.getString("user");
+ gLockStrs[PREF_IS_LOCKED] = gConfigBundle.getString("locked");
+
+ gTypeStrs[nsIPrefBranch.PREF_STRING] = gConfigBundle.getString("string");
+ gTypeStrs[nsIPrefBranch.PREF_INT] = gConfigBundle.getString("int");
+ gTypeStrs[nsIPrefBranch.PREF_BOOL] = gConfigBundle.getString("bool");
+
+ var showWarning = gPrefBranch.getBoolPref("general.warnOnAboutConfig");
+
+ if (showWarning)
+ document.getElementById("warningButton").focus();
+ else
+ ShowPrefs();
+}
+
+// Unhide the warning message
+function ShowPrefs()
+{
+ gPrefBranch.getChildList("").forEach(fetchPref);
+
+ var descending = document.getElementsByAttribute("sortDirection", "descending");
+ if (descending.item(0)) {
+ gSortedColumn = descending[0].id;
+ gSortDirection = -1;
+ }
+ else {
+ var ascending = document.getElementsByAttribute("sortDirection", "ascending");
+ if (ascending.item(0))
+ gSortedColumn = ascending[0].id;
+ else
+ document.getElementById(gSortedColumn).setAttribute("sortDirection", "ascending");
+ }
+ gSortFunction = gSortFunctions[gSortedColumn];
+ gPrefArray.sort(gSortFunction);
+
+ gPrefBranch.addObserver("", gPrefListener, false);
+
+ var configTree = document.getElementById("configTree");
+ configTree.view = view;
+ configTree.controllers.insertControllerAt(0, configController);
+
+ document.getElementById("configDeck").setAttribute("selectedIndex", 1);
+ document.getElementById("configTreeKeyset").removeAttribute("disabled");
+ if (!document.getElementById("showWarningNextTime").checked)
+ gPrefBranch.setBoolPref("general.warnOnAboutConfig", false);
+
+ // Process about:config?filter=<string>
+ var textbox = document.getElementById("textbox");
+ // About URIs don't support query params, so do this manually
+ var loc = document.location.href;
+ var matches = /[?&]filter\=([^&]+)/i.exec(loc);
+ if (matches)
+ textbox.value = decodeURIComponent(matches[1]);
+
+ // Even if we did not set the filter string via the URL query,
+ // textbox might have been set via some other mechanism
+ if (textbox.value)
+ FilterPrefs();
+ textbox.focus();
+}
+
+function onConfigUnload()
+{
+ if (document.getElementById("configDeck").getAttribute("selectedIndex") == 1) {
+ gPrefBranch.removeObserver("", gPrefListener);
+ var configTree = document.getElementById("configTree");
+ configTree.view = null;
+ configTree.controllers.removeController(configController);
+ }
+}
+
+function FilterPrefs()
+{
+ if (document.getElementById("configDeck").getAttribute("selectedIndex") != 1) {
+ return;
+ }
+
+ var substring = document.getElementById("textbox").value;
+ // Check for "/regex/[i]"
+ if (substring.charAt(0) == '/') {
+ var r = substring.match(/^\/(.*)\/(i?)$/);
+ try {
+ gFilter = RegExp(r[1], r[2]);
+ }
+ catch (e) {
+ return; // Do nothing on incomplete or bad RegExp
+ }
+ }
+ else if (substring) {
+ gFilter = RegExp(substring.replace(/([^* \w])/g, "\\$1")
+ .replace(/^\*+/, "").replace(/\*+/g, ".*"), "i");
+ } else {
+ gFilter = null;
+ }
+
+ var prefCol = (view.selection && view.selection.currentIndex < 0) ?
+ null : gPrefView[view.selection.currentIndex].prefCol;
+ var oldlen = gPrefView.length;
+ gPrefView = gPrefArray;
+ if (gFilter) {
+ gPrefView = [];
+ for (var i = 0; i < gPrefArray.length; ++i)
+ if (gFilter.test(gPrefArray[i].prefCol + ";" + gPrefArray[i].valueCol))
+ gPrefView.push(gPrefArray[i]);
+ }
+ view.treebox.invalidate();
+ view.treebox.rowCountChanged(oldlen, gPrefView.length - oldlen);
+ gotoPref(prefCol);
+}
+
+function prefColSortFunction(x, y)
+{
+ if (x.prefCol > y.prefCol)
+ return gSortDirection;
+ if (x.prefCol < y.prefCol)
+ return -gSortDirection;
+ return 0;
+}
+
+function lockColSortFunction(x, y)
+{
+ if (x.lockCol != y.lockCol)
+ return gSortDirection * (y.lockCol - x.lockCol);
+ return prefColSortFunction(x, y);
+}
+
+function typeColSortFunction(x, y)
+{
+ if (x.typeCol != y.typeCol)
+ return gSortDirection * (y.typeCol - x.typeCol);
+ return prefColSortFunction(x, y);
+}
+
+function valueColSortFunction(x, y)
+{
+ if (x.valueCol > y.valueCol)
+ return gSortDirection;
+ if (x.valueCol < y.valueCol)
+ return -gSortDirection;
+ return prefColSortFunction(x, y);
+}
+
+const gSortFunctions =
+{
+ prefCol: prefColSortFunction,
+ lockCol: lockColSortFunction,
+ typeCol: typeColSortFunction,
+ valueCol: valueColSortFunction
+};
+
+const configController = {
+ supportsCommand: function supportsCommand(command) {
+ return command == "cmd_copy";
+ },
+ isCommandEnabled: function isCommandEnabled(command) {
+ return view.selection && view.selection.currentIndex >= 0;
+ },
+ doCommand: function doCommand(command) {
+ copyPref();
+ },
+ onEvent: function onEvent(event) {
+ }
+}
+
+function updateContextMenu()
+{
+ var lockCol = PREF_IS_LOCKED;
+ var typeCol = nsIPrefBranch.PREF_STRING;
+ var valueCol = "";
+ var copyDisabled = true;
+ var prefSelected = view.selection.currentIndex >= 0;
+
+ if (prefSelected) {
+ var prefRow = gPrefView[view.selection.currentIndex];
+ lockCol = prefRow.lockCol;
+ typeCol = prefRow.typeCol;
+ valueCol = prefRow.valueCol;
+ copyDisabled = false;
+ }
+
+ var copyPref = document.getElementById("copyPref");
+ copyPref.setAttribute("disabled", copyDisabled);
+
+ var copyName = document.getElementById("copyName");
+ copyName.setAttribute("disabled", copyDisabled);
+
+ var copyValue = document.getElementById("copyValue");
+ copyValue.setAttribute("disabled", copyDisabled);
+
+ var resetSelected = document.getElementById("resetSelected");
+ resetSelected.setAttribute("disabled", lockCol != PREF_IS_USER_SET);
+
+ var canToggle = typeCol == nsIPrefBranch.PREF_BOOL && valueCol != "";
+ // indicates that a pref is locked or no pref is selected at all
+ var isLocked = lockCol == PREF_IS_LOCKED;
+
+ var modifySelected = document.getElementById("modifySelected");
+ modifySelected.setAttribute("disabled", isLocked);
+ modifySelected.hidden = canToggle;
+
+ var toggleSelected = document.getElementById("toggleSelected");
+ toggleSelected.setAttribute("disabled", isLocked);
+ toggleSelected.hidden = !canToggle;
+}
+
+function copyPref()
+{
+ var pref = gPrefView[view.selection.currentIndex];
+ gClipboardHelper.copyString(pref.prefCol + ';' + pref.valueCol);
+}
+
+function copyName()
+{
+ gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].prefCol);
+}
+
+function copyValue()
+{
+ gClipboardHelper.copyString(gPrefView[view.selection.currentIndex].valueCol);
+}
+
+function ModifySelected()
+{
+ if (view.selection.currentIndex >= 0)
+ ModifyPref(gPrefView[view.selection.currentIndex]);
+}
+
+function ResetSelected()
+{
+ var entry = gPrefView[view.selection.currentIndex];
+ gPrefBranch.clearUserPref(entry.prefCol);
+}
+
+function NewPref(type)
+{
+ var result = { value: "" };
+ var dummy = { value: 0 };
+ if (Services.prompt.prompt(window,
+ gConfigBundle.getFormattedString("new_title",
+ [gTypeStrs[type]]),
+ gConfigBundle.getString("new_prompt"),
+ result,
+ null,
+ dummy)) {
+ result.value = result.value.trim();
+ if (!result.value) {
+ return;
+ }
+
+ var pref;
+ if (result.value in gPrefHash)
+ pref = gPrefHash[result.value];
+ else
+ pref = { prefCol: result.value, lockCol: PREF_IS_DEFAULT_VALUE, typeCol: type, valueCol: "" };
+ if (ModifyPref(pref))
+ setTimeout(gotoPref, 0, result.value);
+ }
+}
+
+function gotoPref(pref)
+{
+ // make sure the pref exists and is displayed in the current view
+ var index = pref in gPrefHash ? getViewIndexOfPref(gPrefHash[pref]) : -1;
+ if (index >= 0) {
+ view.selection.select(index);
+ view.treebox.ensureRowIsVisible(index);
+ } else {
+ view.selection.clearSelection();
+ view.selection.currentIndex = -1;
+ }
+}
+
+function ModifyPref(entry)
+{
+ if (entry.lockCol == PREF_IS_LOCKED)
+ return false;
+ var title = gConfigBundle.getFormattedString("modify_title", [gTypeStrs[entry.typeCol]]);
+ if (entry.typeCol == nsIPrefBranch.PREF_BOOL) {
+ var check = { value: entry.valueCol == "false" };
+ if (!entry.valueCol && !Services.prompt.select(window, title, entry.prefCol, 2, [false, true], check))
+ return false;
+ gPrefBranch.setBoolPref(entry.prefCol, check.value);
+ } else {
+ var result = { value: entry.valueCol };
+ var dummy = { value: 0 };
+ if (!Services.prompt.prompt(window, title, entry.prefCol, result, null, dummy))
+ return false;
+ if (entry.typeCol == nsIPrefBranch.PREF_INT) {
+ // | 0 converts to integer or 0; - 0 to float or NaN.
+ // Thus, this check should catch all cases.
+ var val = result.value | 0;
+ if (val != result.value - 0) {
+ var err_title = gConfigBundle.getString("nan_title");
+ var err_text = gConfigBundle.getString("nan_text");
+ Services.prompt.alert(window, err_title, err_text);
+ return false;
+ }
+ gPrefBranch.setIntPref(entry.prefCol, val);
+ } else {
+ var supportsString = Components.classes[nsSupportsString_CONTRACTID].createInstance(nsISupportsString);
+ supportsString.data = result.value;
+ gPrefBranch.setComplexValue(entry.prefCol, nsISupportsString, supportsString);
+ }
+ }
+
+ Services.prefs.savePrefFile(null);
+ return true;
+}
diff --git a/components/viewconfig/content/config.xul b/components/viewconfig/content/config.xul
new file mode 100644
index 000000000..25ce39da5
--- /dev/null
+++ b/components/viewconfig/content/config.xul
@@ -0,0 +1,101 @@
+<?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/in-content/info-pages.css" type="text/css"?>
+<?xml-stylesheet href="chrome://global/skin/config.css" type="text/css"?>
+
+<!DOCTYPE window SYSTEM "chrome://global/locale/config.dtd">
+
+<window id="config"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&window.title;"
+ windowtype="Preferences:ConfigManager"
+ role="application"
+ aria-describedby="warningTitle warningText"
+ width="750"
+ height="500"
+ disablefastfind="true"
+ onunload="onConfigUnload();"
+ onload="onConfigLoad();">
+
+<script src="chrome://global/content/config.js"/>
+
+<stringbundle id="configBundle" src="chrome://global/locale/config.properties"/>
+
+<menupopup id="configContext" onpopupshowing="if (event.target == this) updateContextMenu();">
+ <menuitem id="toggleSelected" default="true"
+ label="&toggle.label;" accesskey="&toggle.accesskey;"
+ oncommand="ModifySelected();"/>
+ <menuitem id="modifySelected" default="true"
+ label="&modify.label;" accesskey="&modify.accesskey;"
+ oncommand="ModifySelected();"/>
+ <menuseparator/>
+ <menuitem id="copyPref" label="&copyPref.label;" accesskey="&copyPref.accesskey;" oncommand="copyPref();"/>
+ <menuitem id="copyName" label="&copyName.label;" accesskey="&copyName.accesskey;" oncommand="copyName();"/>
+ <menuitem id="copyValue" label="&copyValue.label;" accesskey="&copyValue.accesskey;" oncommand="copyValue();"/>
+ <menu label="&new.label;" accesskey="&new.accesskey;">
+ <menupopup>
+ <menuitem label="&string.label;" accesskey="&string.accesskey;" oncommand="NewPref(nsIPrefBranch.PREF_STRING);"/>
+ <menuitem label="&integer.label;" accesskey="&integer.accesskey;" oncommand="NewPref(nsIPrefBranch.PREF_INT);"/>
+ <menuitem label="&boolean.label;" accesskey="&boolean.accesskey;" oncommand="NewPref(nsIPrefBranch.PREF_BOOL);"/>
+ </menupopup>
+ </menu>
+ <menuitem id="resetSelected" label="&reset.label;" accesskey="&reset.accesskey;" oncommand="ResetSelected();"/>
+</menupopup>
+
+<keyset id="configTreeKeyset" disabled="true">
+ <key keycode="VK_RETURN" oncommand="ModifySelected();"/>
+ <key key="&focusSearch.key;" modifiers="accel" oncommand="document.getElementById('textbox').focus();"/>
+ <key key="&focusSearch2.key;" modifiers="accel" oncommand="document.getElementById('textbox').focus();"/>
+</keyset>
+<deck id="configDeck" flex="1">
+ <vbox id="warningScreen" flex="1" align="center" style="overflow: auto;">
+ <spacer flex="1"/>
+ <vbox id="warningBox" class="container">
+ <hbox class="title" flex="1">
+ <label id="warningTitle" class="title-text" flex="1">&aboutWarningTitle.label;</label>
+ </hbox>
+ <vbox class="description" flex="1">
+ <label id="warningText">&aboutWarningText.label;</label>
+ <checkbox id="showWarningNextTime" label="&aboutWarningCheckbox.label;" checked="true"/>
+ <hbox class="button-container">
+ <button id="warningButton" class="primary" oncommand="ShowPrefs();" label="&aboutWarningButton2.label;"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ <spacer flex="2"/>
+ </vbox>
+ <vbox flex="1">
+ <hbox id="filterRow" align="center">
+ <label value="&searchPrefs.label;" accesskey="&searchPrefs.accesskey;" control="textbox"/>
+ <textbox id="textbox" flex="1" type="search" class="compact"
+ aria-controls="configTree"
+ oncommand="FilterPrefs();"/>
+ </hbox>
+
+ <tree id="configTree" flex="1" seltype="single"
+ onselect="updateCommands('select');"
+ enableColumnDrag="true" context="configContext">
+ <treecols>
+ <treecol id="prefCol" label="&prefColumn.label;" flex="7"
+ ignoreincolumnpicker="true"
+ persist="hidden width ordinal sortDirection"/>
+ <splitter class="tree-splitter" />
+ <treecol id="lockCol" label="&lockColumn.label;" flex="1"
+ persist="hidden width ordinal sortDirection"/>
+ <splitter class="tree-splitter" />
+ <treecol id="typeCol" label="&typeColumn.label;" flex="1"
+ persist="hidden width ordinal sortDirection"/>
+ <splitter class="tree-splitter" />
+ <treecol id="valueCol" label="&valueColumn.label;" flex="10"
+ persist="hidden width ordinal sortDirection"/>
+ </treecols>
+
+ <treechildren id="configTreeBody" ondblclick="if (event.button == 0) ModifySelected();"/>
+ </tree>
+ </vbox>
+</deck>
+</window>
diff --git a/components/viewconfig/jar.mn b/components/viewconfig/jar.mn
new file mode 100644
index 000000000..372902d57
--- /dev/null
+++ b/components/viewconfig/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/.
+
+toolkit.jar:
+ content/global/config.xul (content/config.xul)
+ content/global/config.js (content/config.js)
diff --git a/components/viewconfig/moz.build b/components/viewconfig/moz.build
new file mode 100644
index 000000000..635fa39c9
--- /dev/null
+++ b/components/viewconfig/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/viewsource/ViewSourceBrowser.jsm b/components/viewsource/ViewSourceBrowser.jsm
new file mode 100644
index 000000000..0623e244a
--- /dev/null
+++ b/components/viewsource/ViewSourceBrowser.jsm
@@ -0,0 +1,331 @@
+// -*- 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 { utils: Cu, interfaces: Ci, classes: Cc } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+
+const BUNDLE_URL = "chrome://global/locale/viewSource.properties";
+
+const FRAME_SCRIPT = "chrome://global/content/viewSource-content.js";
+
+this.EXPORTED_SYMBOLS = ["ViewSourceBrowser"];
+
+// Keep a set of browsers we've seen before, so we can load our frame script as
+// needed into any new ones.
+var gKnownBrowsers = new WeakSet();
+
+/**
+ * ViewSourceBrowser manages the view source <browser> from the chrome side.
+ * It's companion frame script, viewSource-content.js, needs to be loaded as a
+ * frame script into the browser being managed.
+ *
+ * For a view source window using viewSource.xul, the script viewSource.js in
+ * the window extends an instance of this with more window specific functions.
+ * The page script takes care of loading the companion frame script.
+ *
+ * For a view source tab (or some other non-window case), an instance of this is
+ * created by viewSourceUtils.js to wrap the <browser>. The frame script will
+ * be loaded by this module at construction time.
+ */
+this.ViewSourceBrowser = function ViewSourceBrowser(aBrowser) {
+ this._browser = aBrowser;
+ this.init();
+}
+
+ViewSourceBrowser.prototype = {
+ /**
+ * The <browser> that will be displaying the view source content.
+ */
+ get browser() {
+ return this._browser;
+ },
+
+ /**
+ * Holds the value of the last line found via the "Go to line"
+ * command, to pre-populate the prompt the next time it is
+ * opened.
+ */
+ lastLineFound: null,
+
+ /**
+ * These are the messages that ViewSourceBrowser will listen for
+ * from the frame script it injects. Any message names added here
+ * will automatically have ViewSourceBrowser listen for those messages,
+ * and remove the listeners on teardown.
+ */
+ messages: [
+ "ViewSource:PromptAndGoToLine",
+ "ViewSource:GoToLine:Success",
+ "ViewSource:GoToLine:Failed",
+ "ViewSource:StoreWrapping",
+ "ViewSource:StoreSyntaxHighlighting",
+ ],
+
+ /**
+ * This should be called as soon as the script loads. When this function
+ * executes, we can assume the DOM content has not yet loaded.
+ */
+ init() {
+ this.messages.forEach((msgName) => {
+ this.mm.addMessageListener(msgName, this);
+ });
+
+ // If we have a known <browser> already, load the frame script here. This
+ // is not true for the window case, as the element does not exist until the
+ // XUL document loads. For that case, the frame script is loaded by
+ // viewSource.js.
+ if (this._browser) {
+ this.loadFrameScript();
+ }
+ },
+
+ /**
+ * This should be called when the window is closing. This function should
+ * clean up event and message listeners.
+ */
+ uninit() {
+ this.messages.forEach((msgName) => {
+ this.mm.removeMessageListener(msgName, this);
+ });
+ },
+
+ /**
+ * For a new browser we've not seen before, load the frame script.
+ */
+ loadFrameScript() {
+ if (!gKnownBrowsers.has(this.browser)) {
+ gKnownBrowsers.add(this.browser);
+ this.mm.loadFrameScript(FRAME_SCRIPT, false);
+ }
+ },
+
+ /**
+ * Anything added to the messages array will get handled here, and should
+ * get dispatched to a specific function for the message name.
+ */
+ receiveMessage(message) {
+ let data = message.data;
+
+ switch (message.name) {
+ case "ViewSource:PromptAndGoToLine":
+ this.promptAndGoToLine();
+ break;
+ case "ViewSource:GoToLine:Success":
+ this.onGoToLineSuccess(data.lineNumber);
+ break;
+ case "ViewSource:GoToLine:Failed":
+ this.onGoToLineFailed();
+ break;
+ case "ViewSource:StoreWrapping":
+ this.storeWrapping(data.state);
+ break;
+ case "ViewSource:StoreSyntaxHighlighting":
+ this.storeSyntaxHighlighting(data.state);
+ break;
+ }
+ },
+
+ /**
+ * Getter for the message manager of the view source browser.
+ */
+ get mm() {
+ return this.browser.messageManager;
+ },
+
+ /**
+ * Send a message to the view source browser.
+ */
+ sendAsyncMessage(...args) {
+ this.browser.messageManager.sendAsyncMessage(...args);
+ },
+
+ /**
+ * A getter for the view source string bundle.
+ */
+ get bundle() {
+ if (this._bundle) {
+ return this._bundle;
+ }
+ return this._bundle = Services.strings.createBundle(BUNDLE_URL);
+ },
+
+ /**
+ * Loads the source for a URL while applying some optional features if
+ * enabled.
+ *
+ * For the viewSource.xul window, this is called by onXULLoaded above.
+ * For view source in a specific browser, this is manually called after
+ * this object is constructed.
+ *
+ * This takes a single object argument containing:
+ *
+ * URL (required):
+ * A string URL for the page we'd like to view the source of.
+ * browser:
+ * The browser containing the document that we would like to view the
+ * source of. This argument is optional if outerWindowID is not passed.
+ * outerWindowID (optional):
+ * The outerWindowID of the content window containing the document that
+ * we want to view the source of. This is the only way of attempting to
+ * load the source out of the network cache.
+ * lineNumber (optional):
+ * The line number to focus on once the source is loaded.
+ */
+ loadViewSource({ URL, browser, outerWindowID, lineNumber }) {
+ if (!URL) {
+ throw new Error("Must supply a URL when opening view source.");
+ }
+
+ if (browser) {
+ this.browser.relatedBrowser = browser;
+
+ // If we're dealing with a remote browser, then the browser
+ // for view source needs to be remote as well.
+ this.updateBrowserRemoteness(browser.isRemoteBrowser);
+ } else if (outerWindowID) {
+ throw new Error("Must supply the browser if passing the outerWindowID");
+ }
+
+ this.sendAsyncMessage("ViewSource:LoadSource",
+ { URL, outerWindowID, lineNumber });
+ },
+
+ /**
+ * Loads a view source selection showing the given view-source url and
+ * highlight the selection.
+ *
+ * @param uri view-source uri to show
+ * @param drawSelection true to highlight the selection
+ * @param baseURI base URI of the original document
+ */
+ loadViewSourceFromSelection(URL, drawSelection, baseURI) {
+ this.sendAsyncMessage("ViewSource:LoadSourceWithSelection",
+ { URL, drawSelection, baseURI });
+ },
+
+ /**
+ * Updates the "remote" attribute of the view source browser. This
+ * will remove the browser from the DOM, and then re-add it in the
+ * same place it was taken from.
+ *
+ * @param shouldBeRemote
+ * True if the browser should be made remote. If the browsers
+ * remoteness already matches this value, this function does
+ * nothing.
+ */
+ updateBrowserRemoteness(shouldBeRemote) {
+ if (this.browser.isRemoteBrowser != shouldBeRemote) {
+ // In this base case, where we are handed a <browser> someone else is
+ // managing, we don't know for sure that it's safe to toggle remoteness.
+ // For view source in a window, this is overridden to actually do the
+ // flip if needed.
+ throw new Error("View source browser's remoteness mismatch");
+ }
+ },
+
+ /**
+ * Opens the "Go to line" prompt for a user to hop to a particular line
+ * of the source code they're viewing. This will keep prompting until the
+ * user either cancels out of the prompt, or enters a valid line number.
+ */
+ promptAndGoToLine() {
+ let input = { value: this.lastLineFound };
+ let window = Services.wm.getMostRecentWindow(null);
+
+ let ok = Services.prompt.prompt(
+ window,
+ this.bundle.GetStringFromName("goToLineTitle"),
+ this.bundle.GetStringFromName("goToLineText"),
+ input,
+ null,
+ {value:0});
+
+ if (!ok)
+ return;
+
+ let line = parseInt(input.value, 10);
+
+ if (!(line > 0)) {
+ Services.prompt.alert(window,
+ this.bundle.GetStringFromName("invalidInputTitle"),
+ this.bundle.GetStringFromName("invalidInputText"));
+ this.promptAndGoToLine();
+ } else {
+ this.goToLine(line);
+ }
+ },
+
+ /**
+ * Go to a particular line of the source code. This act is asynchronous.
+ *
+ * @param lineNumber
+ * The line number to try to go to to.
+ */
+ goToLine(lineNumber) {
+ this.sendAsyncMessage("ViewSource:GoToLine", { lineNumber });
+ },
+
+ /**
+ * Called when the frame script reports that a line was successfully gotten
+ * to.
+ *
+ * @param lineNumber
+ * The line number that we successfully got to.
+ */
+ onGoToLineSuccess(lineNumber) {
+ // We'll pre-populate the "Go to line" prompt with this value the next
+ // time it comes up.
+ this.lastLineFound = lineNumber;
+ },
+
+ /**
+ * Called when the frame script reports that we failed to go to a particular
+ * line. This informs the user that their selection was likely out of range,
+ * and then reprompts the user to try again.
+ */
+ onGoToLineFailed() {
+ let window = Services.wm.getMostRecentWindow(null);
+ Services.prompt.alert(window,
+ this.bundle.GetStringFromName("outOfRangeTitle"),
+ this.bundle.GetStringFromName("outOfRangeText"));
+ this.promptAndGoToLine();
+ },
+
+ /**
+ * Update the wrapping pref based on the child's current state.
+ * @param state
+ * Whether wrapping is currently enabled in the child.
+ */
+ storeWrapping(state) {
+ Services.prefs.setBoolPref("view_source.wrap_long_lines", state);
+ },
+
+ /**
+ * Update the syntax highlighting pref based on the child's current state.
+ * @param state
+ * Whether syntax highlighting is currently enabled in the child.
+ */
+ storeSyntaxHighlighting(state) {
+ Services.prefs.setBoolPref("view_source.syntax_highlight", state);
+ },
+
+};
+
+/**
+ * Helper to decide if a URI maps to view source content.
+ * @param uri
+ * String containing the URI
+ */
+ViewSourceBrowser.isViewSource = function(uri) {
+ return uri.startsWith("view-source:") ||
+ (uri.startsWith("data:") && uri.includes("MathML"));
+};
diff --git a/components/viewsource/content/viewPartialSource.js b/components/viewsource/content/viewPartialSource.js
new file mode 100644
index 000000000..0b069344a
--- /dev/null
+++ b/components/viewsource/content/viewPartialSource.js
@@ -0,0 +1,22 @@
+// -*- 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/Services.jsm");
+
+function onLoadViewPartialSource() {
+ // check the view_source.wrap_long_lines pref
+ // and set the menuitem's checked attribute accordingly
+ let wrapLongLines = Services.prefs.getBoolPref("view_source.wrap_long_lines");
+ document.getElementById("menu_wrapLongLines")
+ .setAttribute("checked", wrapLongLines);
+ document.getElementById("menu_highlightSyntax")
+ .setAttribute("checked",
+ Services.prefs.getBoolPref("view_source.syntax_highlight"));
+
+ let args = window.arguments[0];
+ viewSourceChrome.loadViewSourceFromSelection(args.URI, args.drawSelection, args.baseURI);
+ window.content.focus();
+}
diff --git a/components/viewsource/content/viewPartialSource.xul b/components/viewsource/content/viewPartialSource.xul
new file mode 100644
index 000000000..c51744fd2
--- /dev/null
+++ b/components/viewsource/content/viewPartialSource.xul
@@ -0,0 +1,161 @@
+<?xml version="1.0"?>
+<!-- -*- Mode: XML -*-
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.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://global/content/viewSource.css" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/viewsource/viewsource.css" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % sourceDTD SYSTEM "chrome://global/locale/viewSource.dtd" >
+%sourceDTD;
+]>
+
+<window id="viewSource"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoadViewPartialSource();"
+ contenttitlesetting="true"
+ title="&mainWindow.title;"
+ titlemodifier="&mainWindow.titlemodifier;"
+ titlepreface=""
+ titlemenuseparator ="&mainWindow.titlemodifierseparator;"
+ windowtype="navigator:view-source"
+ width="500" height="300"
+ screenX="10" screenY="10"
+ persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://global/content/printUtils.js"/>
+ <script type="application/javascript" src="chrome://global/content/viewSource.js"/>
+ <script type="application/javascript" src="chrome://global/content/viewPartialSource.js"/>
+ <script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+
+ <stringbundle id="viewSourceBundle" src="chrome://global/locale/viewSource.properties"/>
+
+ <command id="cmd_savePage" oncommand="ViewSourceSavePage();"/>
+ <command id="cmd_print" oncommand="PrintUtils.printWindow(gBrowser.outerWindowID, gBrowser);"/>
+ <command id="cmd_printpreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
+ <command id="cmd_pagesetup" oncommand="PrintUtils.showPageSetup();"/>
+ <command id="cmd_close" oncommand="window.close();"/>
+ <commandset id="editMenuCommands"/>
+ <command id="cmd_find"
+ oncommand="document.getElementById('FindToolbar').onFindCommand();"/>
+ <command id="cmd_findAgain"
+ oncommand="document.getElementById('FindToolbar').onFindAgainCommand(false);"/>
+ <command id="cmd_findPrevious"
+ oncommand="document.getElementById('FindToolbar').onFindAgainCommand(true);"/>
+ <command id="cmd_goToLine" oncommand="viewSourceChrome.promptAndGoToLine();" disabled="true"/>
+ <command id="cmd_highlightSyntax" oncommand="viewSourceChrome.toggleSyntaxHighlighting();"/>
+ <command id="cmd_wrapLongLines" oncommand="viewSourceChrome.toggleWrapping();"/>
+ <command id="cmd_textZoomReduce" oncommand="ZoomManager.reduce();"/>
+ <command id="cmd_textZoomEnlarge" oncommand="ZoomManager.enlarge();"/>
+ <command id="cmd_textZoomReset" oncommand="ZoomManager.reset();"/>
+
+ <keyset id="editMenuKeys"/>
+ <keyset id="viewSourceKeys">
+ <key id="key_savePage" key="&savePageCmd.commandkey;" modifiers="accel" command="cmd_savePage"/>
+ <key id="key_print" key="&printCmd.commandkey;" modifiers="accel" command="cmd_print"/>
+ <key id="key_close" key="&closeCmd.commandkey;" modifiers="accel" command="cmd_close"/>
+ <key keycode="VK_ESCAPE" command="cmd_close"/>
+
+ <key id="key_textZoomEnlarge" key="&textEnlarge.commandkey;" command="cmd_textZoomEnlarge" modifiers="accel"/>
+ <key id="key_textZoomEnlarge2" key="&textEnlarge.commandkey2;" command="cmd_textZoomEnlarge" modifiers="accel"/>
+ <key id="key_textZoomEnlarge3" key="&textEnlarge.commandkey3;" command="cmd_textZoomEnlarge" modifiers="accel"/>
+ <key id="key_textZoomReduce" key="&textReduce.commandkey;" command="cmd_textZoomReduce" modifiers="accel"/>
+ <key id="key_textZoomReduce2" key="&textReduce.commandkey2;" command="cmd_textZoomReduce" modifiers="accel"/>
+ <key id="key_textZoomReset" key="&textReset.commandkey;" command="cmd_textZoomReset" modifiers="accel"/>
+ <key id="key_textZoomReset2" key="&textReset.commandkey2;" command="cmd_textZoomReset" modifiers="accel"/>
+ </keyset>
+
+ <menupopup id="viewSourceContextMenu">
+ <menuitem id="cMenu_findAgain"/>
+ <menuseparator/>
+ <menuitem id="cMenu_copy"/>
+ <menuitem id="context-copyLink"
+ label="&copyLinkCmd.label;"
+ accesskey="&copyLinkCmd.accesskey;"
+ oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
+ <menuitem id="context-copyEmail"
+ label="&copyEmailCmd.label;"
+ accesskey="&copyEmailCmd.accesskey;"
+ oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
+ <menuseparator/>
+ <menuitem id="cMenu_selectAll"/>
+ </menupopup>
+
+ <!-- Menu -->
+ <toolbox id="viewSource-toolbox">
+ <menubar id="viewSource-main-menubar">
+
+ <menu id="menu_file" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
+ <menupopup id="menu_FilePopup">
+ <menuitem key="key_savePage" command="cmd_savePage" id="menu_savePage"
+ label="&savePageCmd.label;" accesskey="&savePageCmd.accesskey;"/>
+ <menuitem command="cmd_pagesetup" id="menu_pageSetup"
+ label="&pageSetupCmd.label;" accesskey="&pageSetupCmd.accesskey;"/>
+ <menuitem command="cmd_printpreview" id="menu_printPreview"
+ label="&printPreviewCmd.label;" accesskey="&printPreviewCmd.accesskey;"/>
+ <menuitem key="key_print" command="cmd_print" id="menu_print"
+ label="&printCmd.label;" accesskey="&printCmd.accesskey;"/>
+ <menuseparator/>
+ <menuitem key="key_close" command="cmd_close" id="menu_close"
+ label="&closeCmd.label;" accesskey="&closeCmd.accesskey;"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu_edit">
+ <menupopup id="editmenu-popup">
+ <menuitem id="menu_undo"/>
+ <menuitem id="menu_redo"/>
+ <menuseparator/>
+ <menuitem id="menu_cut"/>
+ <menuitem id="menu_copy"/>
+ <menuitem id="menu_paste"/>
+ <menuitem id="menu_delete"/>
+ <menuseparator/>
+ <menuitem id="menu_selectAll"/>
+ <menuseparator/>
+ <menuitem id="menu_find"/>
+ <menuitem id="menu_findAgain"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu_view" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;">
+ <menupopup id="viewmenu-popup">
+ <menu id="viewTextZoomMenu" label="&menu_textSize.label;" accesskey="&menu_textSize.accesskey;">
+ <menupopup>
+ <menuitem id="menu_textEnlarge" command="cmd_textZoomEnlarge"
+ label="&menu_textEnlarge.label;" accesskey="&menu_textEnlarge.accesskey;"
+ key="key_textZoomEnlarge"/>
+ <menuitem id="menu_textReduce" command="cmd_textZoomReduce"
+ label="&menu_textReduce.label;" accesskey="&menu_textReduce.accesskey;"
+ key="key_textZoomReduce"/>
+ <menuseparator/>
+ <menuitem id="menu_textReset" command="cmd_textZoomReset"
+ label="&menu_textReset.label;" accesskey="&menu_textReset.accesskey;"
+ key="key_textZoomReset"/>
+ </menupopup>
+ </menu>
+ <menuseparator/>
+ <menuitem id="menu_wrapLongLines" type="checkbox" command="cmd_wrapLongLines"
+ label="&menu_wrapLongLines.title;" accesskey="&menu_wrapLongLines.accesskey;"/>
+ <menuitem type="checkbox" id="menu_highlightSyntax" command="cmd_highlightSyntax"
+ label="&menu_highlightSyntax.label;" accesskey="&menu_highlightSyntax.accesskey;"/>
+ </menupopup>
+ </menu>
+ </menubar>
+ </toolbox>
+
+ <vbox id="appcontent" flex="1">
+ <browser id="content" type="content-primary" name="content" src="about:blank" flex="1"
+ disablehistory="true" context="viewSourceContextMenu" />
+ <findbar id="FindToolbar" browserid="content"/>
+ </vbox>
+
+</window>
diff --git a/components/viewsource/content/viewSource-content.js b/components/viewsource/content/viewSource-content.js
new file mode 100644
index 000000000..bad90febf
--- /dev/null
+++ b/components/viewsource/content/viewSource-content.js
@@ -0,0 +1,979 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { utils: Cu, interfaces: Ci, classes: Cc } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
+ "resource://gre/modules/DeferredTask.jsm");
+
+const NS_XHTML = "http://www.w3.org/1999/xhtml";
+const BUNDLE_URL = "chrome://global/locale/viewSource.properties";
+
+// These are markers used to delimit the selection during processing. They
+// are removed from the final rendering.
+// We use noncharacter Unicode codepoints to minimize the risk of clashing
+// with anything that might legitimately be present in the document.
+// U+FDD0..FDEF <noncharacters>
+const MARK_SELECTION_START = "\uFDD0";
+const MARK_SELECTION_END = "\uFDEF";
+
+var global = this;
+
+/**
+ * ViewSourceContent should be loaded in the <xul:browser> of the
+ * view source window, and initialized as soon as it has loaded.
+ */
+var ViewSourceContent = {
+ /**
+ * We'll act as an nsISelectionListener as well so that we can send
+ * updates to the view source window's status bar.
+ */
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISelectionListener]),
+
+ /**
+ * These are the messages that ViewSourceContent is prepared to listen
+ * for. If you need ViewSourceContent to handle more messages, add them
+ * here.
+ */
+ messages: [
+ "ViewSource:LoadSource",
+ "ViewSource:LoadSourceOriginal",
+ "ViewSource:LoadSourceWithSelection",
+ "ViewSource:GoToLine",
+ "ViewSource:ToggleWrapping",
+ "ViewSource:ToggleSyntaxHighlighting",
+ "ViewSource:SetCharacterSet",
+ ],
+
+ /**
+ * When showing selection source, chrome will construct a page fragment to
+ * show, and then instruct content to draw a selection after load. This is
+ * set true when there is a pending request to draw selection.
+ */
+ needsDrawSelection: false,
+
+ /**
+ * ViewSourceContent is attached as an nsISelectionListener on pageshow,
+ * and removed on pagehide. When the initial about:blank is transitioned
+ * away from, a pagehide is fired without us having attached ourselves
+ * first. We use this boolean to keep track of whether or not we're
+ * attached, so we don't attempt to remove our listener when it's not
+ * yet there (which throws).
+ */
+ selectionListenerAttached: false,
+
+ get isViewSource() {
+ let uri = content.document.documentURI;
+ return uri.startsWith("view-source:") ||
+ (uri.startsWith("data:") && uri.includes("MathML"));
+ },
+
+ get isAboutBlank() {
+ let uri = content.document.documentURI;
+ return uri == "about:blank";
+ },
+
+ /**
+ * This should be called as soon as this frame script has loaded.
+ */
+ init() {
+ this.messages.forEach((msgName) => {
+ addMessageListener(msgName, this);
+ });
+
+ addEventListener("pagehide", this, true);
+ addEventListener("pageshow", this, true);
+ addEventListener("click", this);
+ addEventListener("unload", this);
+ Services.els.addSystemEventListener(global, "contextmenu", this, false);
+ },
+
+ /**
+ * This should be called when the frame script is being unloaded,
+ * and the browser is tearing down.
+ */
+ uninit() {
+ this.messages.forEach((msgName) => {
+ removeMessageListener(msgName, this);
+ });
+
+ removeEventListener("pagehide", this, true);
+ removeEventListener("pageshow", this, true);
+ removeEventListener("click", this);
+ removeEventListener("unload", this);
+
+ Services.els.removeSystemEventListener(global, "contextmenu", this, false);
+
+ // Cancel any pending toolbar updates.
+ if (this.updateStatusTask) {
+ this.updateStatusTask.disarm();
+ }
+ },
+
+ /**
+ * Anything added to the messages array will get handled here, and should
+ * get dispatched to a specific function for the message name.
+ */
+ receiveMessage(msg) {
+ if (!this.isViewSource && !this.isAboutBlank) {
+ return;
+ }
+ let data = msg.data;
+ let objects = msg.objects;
+ switch (msg.name) {
+ case "ViewSource:LoadSource":
+ this.viewSource(data.URL, data.outerWindowID, data.lineNumber);
+ break;
+ case "ViewSource:LoadSourceOriginal":
+ this.viewSourceOriginal(data.URL, objects.pageDescriptor, data.lineNumber,
+ data.forcedCharSet);
+ break;
+ case "ViewSource:LoadSourceWithSelection":
+ this.viewSourceWithSelection(data.URL, data.drawSelection, data.baseURI);
+ break;
+ case "ViewSource:GoToLine":
+ this.goToLine(data.lineNumber);
+ break;
+ case "ViewSource:ToggleWrapping":
+ this.toggleWrapping();
+ break;
+ case "ViewSource:ToggleSyntaxHighlighting":
+ this.toggleSyntaxHighlighting();
+ break;
+ case "ViewSource:SetCharacterSet":
+ this.setCharacterSet(data.charset, data.doPageLoad);
+ break;
+ }
+ },
+
+ /**
+ * Any events should get handled here, and should get dispatched to
+ * a specific function for the event type.
+ */
+ handleEvent(event) {
+ if (!this.isViewSource) {
+ return;
+ }
+ switch (event.type) {
+ case "pagehide":
+ this.onPageHide(event);
+ break;
+ case "pageshow":
+ this.onPageShow(event);
+ break;
+ case "click":
+ this.onClick(event);
+ break;
+ case "unload":
+ this.uninit();
+ break;
+ case "contextmenu":
+ this.onContextMenu(event);
+ break;
+ }
+ },
+
+ /**
+ * A getter for the view source string bundle.
+ */
+ get bundle() {
+ delete this.bundle;
+ this.bundle = Services.strings.createBundle(BUNDLE_URL);
+ return this.bundle;
+ },
+
+ /**
+ * A shortcut to the nsISelectionController for the content.
+ */
+ get selectionController() {
+ return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISelectionDisplay)
+ .QueryInterface(Ci.nsISelectionController);
+ },
+
+ /**
+ * A shortcut to the nsIWebBrowserFind for the content.
+ */
+ get webBrowserFind() {
+ return docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebBrowserFind);
+ },
+
+ /**
+ * Called when the parent sends a message to view some source code.
+ *
+ * @param URL (required)
+ * The URL string of the source to be shown.
+ * @param outerWindowID (optional)
+ * The outerWindowID of the content window that has hosted
+ * the document, in case we want to retrieve it from the network
+ * cache.
+ * @param lineNumber (optional)
+ * The line number to focus as soon as the source has finished
+ * loading.
+ */
+ viewSource(URL, outerWindowID, lineNumber) {
+ let pageDescriptor, forcedCharSet;
+
+ if (outerWindowID) {
+ let contentWindow = Services.wm.getOuterWindowWithId(outerWindowID);
+ let requestor = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor);
+
+ try {
+ let otherWebNav = requestor.getInterface(Ci.nsIWebNavigation);
+ pageDescriptor = otherWebNav.QueryInterface(Ci.nsIWebPageDescriptor)
+ .currentDescriptor;
+ } catch (e) {
+ // We couldn't get the page descriptor, so we'll probably end up re-retrieving
+ // this document off of the network.
+ }
+
+ let utils = requestor.getInterface(Ci.nsIDOMWindowUtils);
+ let doc = contentWindow.document;
+ forcedCharSet = utils.docCharsetIsForced ? doc.characterSet
+ : null;
+ }
+
+ this.loadSource(URL, pageDescriptor, lineNumber, forcedCharSet);
+ },
+
+ /**
+ * Called when the parent is using the original API for viewSource.xul.
+ * This function will throw if it's called on a remote browser.
+ *
+ * @param URL (required)
+ * The URL string of the source to be shown.
+ * @param pageDescriptor (optional)
+ * The currentDescriptor off of an nsIWebPageDescriptor, in the
+ * event that the caller wants to try to load the source out of
+ * the network cache.
+ * @param lineNumber (optional)
+ * The line number to focus as soon as the source has finished
+ * loading.
+ * @param forcedCharSet (optional)
+ * The document character set to use instead of the default one.
+ */
+ viewSourceOriginal(URL, pageDescriptor, lineNumber, forcedCharSet) {
+ // This should not be called if this frame script is running
+ // in a content process!
+ if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
+ throw new Error("ViewSource original API should not be used with " +
+ "remote browsers.");
+ }
+
+ this.loadSource(URL, pageDescriptor, lineNumber, forcedCharSet);
+ },
+
+ /**
+ * Common utility function used by both the current and orginal APIs
+ * for loading source.
+ *
+ * @param URL (required)
+ * The URL string of the source to be shown.
+ * @param pageDescriptor (optional)
+ * The currentDescriptor off of an nsIWebPageDescriptor, in the
+ * event that the caller wants to try to load the source out of
+ * the network cache.
+ * @param lineNumber (optional)
+ * The line number to focus as soon as the source has finished
+ * loading.
+ * @param forcedCharSet (optional)
+ * The document character set to use instead of the default one.
+ */
+ loadSource(URL, pageDescriptor, lineNumber, forcedCharSet) {
+ const viewSrcURL = "view-source:" + URL;
+
+ if (forcedCharSet) {
+ try {
+ docShell.charset = forcedCharSet;
+ } catch (e) { /* invalid charset */ }
+ }
+
+ if (lineNumber && lineNumber > 0) {
+ let doneLoading = (event) => {
+ // Ignore possible initial load of about:blank
+ if (this.isAboutBlank ||
+ !content.document.body) {
+ return;
+ }
+ this.goToLine(lineNumber);
+ removeEventListener("pageshow", doneLoading);
+ };
+
+ addEventListener("pageshow", doneLoading);
+ }
+
+ if (!pageDescriptor) {
+ this.loadSourceFromURL(viewSrcURL);
+ return;
+ }
+
+ try {
+ let pageLoader = docShell.QueryInterface(Ci.nsIWebPageDescriptor);
+ pageLoader.loadPage(pageDescriptor,
+ Ci.nsIWebPageDescriptor.DISPLAY_AS_SOURCE);
+ } catch (e) {
+ // We were not able to load the source from the network cache.
+ this.loadSourceFromURL(viewSrcURL);
+ return;
+ }
+
+ let shEntrySource = pageDescriptor.QueryInterface(Ci.nsISHEntry);
+ let shEntry = Cc["@mozilla.org/browser/session-history-entry;1"]
+ .createInstance(Ci.nsISHEntry);
+ shEntry.setURI(BrowserUtils.makeURI(viewSrcURL, null, null));
+ shEntry.setTitle(viewSrcURL);
+ let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+ shEntry.triggeringPrincipal = systemPrincipal;
+ shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
+ shEntry.cacheKey = shEntrySource.cacheKey;
+ docShell.QueryInterface(Ci.nsIWebNavigation)
+ .sessionHistory
+ .QueryInterface(Ci.nsISHistoryInternal)
+ .addEntry(shEntry, true);
+ },
+
+ /**
+ * Load some URL in the browser.
+ *
+ * @param URL
+ * The URL string to load.
+ */
+ loadSourceFromURL(URL) {
+ let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNav.loadURI(URL, loadFlags, null, null, null);
+ },
+
+ /**
+ * This handler is for click events from:
+ * * error page content, which can show up if the user attempts to view the
+ * source of an attack page.
+ * * in-page context menu actions
+ */
+ onClick(event) {
+ let target = event.originalTarget;
+ // Check for content menu actions
+ if (target.id) {
+ this.contextMenuItems.forEach(itemSpec => {
+ if (itemSpec.id !== target.id) {
+ return;
+ }
+ itemSpec.handler.call(this, event);
+ event.stopPropagation();
+ });
+ }
+
+ // Don't trust synthetic events
+ if (!event.isTrusted || event.target.localName != "button")
+ return;
+
+ let errorDoc = target.ownerDocument;
+
+ if (/^about:blocked/.test(errorDoc.documentURI)) {
+ // The event came from a button on a malware/phishing block page
+
+ if (target == errorDoc.getElementById("getMeOutButton")) {
+ // Instead of loading some safe page, just close the window
+ sendAsyncMessage("ViewSource:Close");
+ } else if (target == errorDoc.getElementById("reportButton")) {
+ // This is the "Why is this site blocked" button. We redirect
+ // to the generic page describing phishing/malware protection.
+ let URL = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ sendAsyncMessage("ViewSource:OpenURL", { URL })
+ } else if (target == errorDoc.getElementById("ignoreWarningButton")) {
+ // Allow users to override and continue through to the site
+ docShell.QueryInterface(Ci.nsIWebNavigation)
+ .loadURIWithOptions(content.location.href,
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
+ null, Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ null, null, null);
+ }
+ }
+ },
+
+ /**
+ * Handler for the pageshow event.
+ *
+ * @param event
+ * The pageshow event being handled.
+ */
+ onPageShow(event) {
+ let selection = content.getSelection();
+ if (selection) {
+ selection.QueryInterface(Ci.nsISelectionPrivate)
+ .addSelectionListener(this);
+ this.selectionListenerAttached = true;
+ }
+ content.focus();
+
+ // If we need to draw the selection, wait until an actual view source page
+ // has loaded, instead of about:blank.
+ if (this.needsDrawSelection &&
+ content.document.documentURI.startsWith("view-source:")) {
+ this.needsDrawSelection = false;
+ this.drawSelection();
+ }
+
+ if (content.document.body) {
+ this.injectContextMenu();
+ }
+
+ sendAsyncMessage("ViewSource:SourceLoaded");
+ },
+
+ /**
+ * Handler for the pagehide event.
+ *
+ * @param event
+ * The pagehide event being handled.
+ */
+ onPageHide(event) {
+ // The initial about:blank will fire pagehide before we
+ // ever set a selectionListener, so we have a boolean around
+ // to keep track of when the listener is attached.
+ if (this.selectionListenerAttached) {
+ content.getSelection()
+ .QueryInterface(Ci.nsISelectionPrivate)
+ .removeSelectionListener(this);
+ this.selectionListenerAttached = false;
+ }
+ sendAsyncMessage("ViewSource:SourceUnloaded");
+ },
+
+ onContextMenu(event) {
+ let addonInfo = {};
+ let subject = {
+ event: event,
+ addonInfo: addonInfo,
+ };
+
+ subject.wrappedJSObject = subject;
+ Services.obs.notifyObservers(subject, "content-contextmenu", null);
+
+ let node = event.target;
+
+ let result = {
+ isEmail: false,
+ isLink: false,
+ href: "",
+ // We have to pass these in the event that we're running in
+ // a remote browser, so that ViewSourceChrome knows where to
+ // open the context menu.
+ screenX: event.screenX,
+ screenY: event.screenY,
+ };
+
+ if (node && node.localName == "a") {
+ result.isLink = node.href.startsWith("view-source:");
+ result.isEmail = node.href.startsWith("mailto:");
+ result.href = node.href.substring(node.href.indexOf(":") + 1);
+ }
+
+ sendSyncMessage("ViewSource:ContextMenuOpening", result);
+ },
+
+ /**
+ * Attempts to go to a particular line in the source code being
+ * shown. If it succeeds in finding the line, it will fire a
+ * "ViewSource:GoToLine:Success" message, passing up an object
+ * with the lineNumber we just went to. If it cannot find the line,
+ * it will fire a "ViewSource:GoToLine:Failed" message.
+ *
+ * @param lineNumber
+ * The line number to attempt to go to.
+ */
+ goToLine(lineNumber) {
+ let body = content.document.body;
+
+ // The source document is made up of a number of pre elements with
+ // id attributes in the format <pre id="line123">, meaning that
+ // the first line in the pre element is number 123.
+ // Do binary search to find the pre element containing the line.
+ // However, in the plain text case, we have only one pre without an
+ // attribute, so assume it begins on line 1.
+ let pre;
+ for (let lbound = 0, ubound = body.childNodes.length; ; ) {
+ let middle = (lbound + ubound) >> 1;
+ pre = body.childNodes[middle];
+
+ let firstLine = pre.id ? parseInt(pre.id.substring(4)) : 1;
+
+ if (lbound == ubound - 1) {
+ break;
+ }
+
+ if (lineNumber >= firstLine) {
+ lbound = middle;
+ } else {
+ ubound = middle;
+ }
+ }
+
+ let result = {};
+ let found = this.findLocation(pre, lineNumber, null, -1, false, result);
+
+ if (!found) {
+ sendAsyncMessage("ViewSource:GoToLine:Failed");
+ return;
+ }
+
+ let selection = content.getSelection();
+ selection.removeAllRanges();
+
+ // In our case, the range's startOffset is after "\n" on the previous line.
+ // Tune the selection at the beginning of the next line and do some tweaking
+ // to position the focusNode and the caret at the beginning of the line.
+ selection.QueryInterface(Ci.nsISelectionPrivate)
+ .interlinePosition = true;
+
+ selection.addRange(result.range);
+
+ if (!selection.isCollapsed) {
+ selection.collapseToEnd();
+
+ let offset = result.range.startOffset;
+ let node = result.range.startContainer;
+ if (offset < node.data.length) {
+ // The same text node spans across the "\n", just focus where we were.
+ selection.extend(node, offset);
+ }
+ else {
+ // There is another tag just after the "\n", hook there. We need
+ // to focus a safe point because there are edgy cases such as
+ // <span>...\n</span><span>...</span> vs.
+ // <span>...\n<span>...</span></span><span>...</span>
+ node = node.nextSibling ? node.nextSibling : node.parentNode.nextSibling;
+ selection.extend(node, 0);
+ }
+ }
+
+ let selCon = this.selectionController;
+ selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON);
+ selCon.setCaretVisibilityDuringSelection(true);
+
+ // Scroll the beginning of the line into view.
+ selCon.scrollSelectionIntoView(
+ Ci.nsISelectionController.SELECTION_NORMAL,
+ Ci.nsISelectionController.SELECTION_FOCUS_REGION,
+ true);
+
+ sendAsyncMessage("ViewSource:GoToLine:Success", { lineNumber });
+ },
+
+
+ /**
+ * Some old code from the original view source implementation. Original
+ * documentation follows:
+ *
+ * "Loops through the text lines in the pre element. The arguments are either
+ * (pre, line) or (node, offset, interlinePosition). result is an out
+ * argument. If (pre, line) are specified (and node == null), result.range is
+ * a range spanning the specified line. If the (node, offset,
+ * interlinePosition) are specified, result.line and result.col are the line
+ * and column number of the specified offset in the specified node relative to
+ * the whole file."
+ */
+ findLocation(pre, lineNumber, node, offset, interlinePosition, result) {
+ if (node && !pre) {
+ // Look upwards to find the current pre element.
+ for (pre = node;
+ pre.nodeName != "PRE";
+ pre = pre.parentNode);
+ }
+
+ // The source document is made up of a number of pre elements with
+ // id attributes in the format <pre id="line123">, meaning that
+ // the first line in the pre element is number 123.
+ // However, in the plain text case, there is only one <pre> without an id,
+ // so assume line 1.
+ let curLine = pre.id ? parseInt(pre.id.substring(4)) : 1;
+
+ // Walk through each of the text nodes and count newlines.
+ let treewalker = content.document
+ .createTreeWalker(pre, Ci.nsIDOMNodeFilter.SHOW_TEXT, null);
+
+ // The column number of the first character in the current text node.
+ let firstCol = 1;
+
+ let found = false;
+ for (let textNode = treewalker.firstChild();
+ textNode && !found;
+ textNode = treewalker.nextNode()) {
+
+ // \r is not a valid character in the DOM, so we only check for \n.
+ let lineArray = textNode.data.split(/\n/);
+ let lastLineInNode = curLine + lineArray.length - 1;
+
+ // Check if we can skip the text node without further inspection.
+ if (node ? (textNode != node) : (lastLineInNode < lineNumber)) {
+ if (lineArray.length > 1) {
+ firstCol = 1;
+ }
+ firstCol += lineArray[lineArray.length - 1].length;
+ curLine = lastLineInNode;
+ continue;
+ }
+
+ // curPos is the offset within the current text node of the first
+ // character in the current line.
+ for (var i = 0, curPos = 0;
+ i < lineArray.length;
+ curPos += lineArray[i++].length + 1) {
+
+ if (i > 0) {
+ curLine++;
+ }
+
+ if (node) {
+ if (offset >= curPos && offset <= curPos + lineArray[i].length) {
+ // If we are right after the \n of a line and interlinePosition is
+ // false, the caret looks as if it were at the end of the previous
+ // line, so we display that line and column instead.
+
+ if (i > 0 && offset == curPos && !interlinePosition) {
+ result.line = curLine - 1;
+ var prevPos = curPos - lineArray[i - 1].length;
+ result.col = (i == 1 ? firstCol : 1) + offset - prevPos;
+ } else {
+ result.line = curLine;
+ result.col = (i == 0 ? firstCol : 1) + offset - curPos;
+ }
+ found = true;
+
+ break;
+ }
+
+ } else if (curLine == lineNumber && !("range" in result)) {
+ result.range = content.document.createRange();
+ result.range.setStart(textNode, curPos);
+
+ // This will always be overridden later, except when we look for
+ // the very last line in the file (this is the only line that does
+ // not end with \n).
+ result.range.setEndAfter(pre.lastChild);
+
+ } else if (curLine == lineNumber + 1) {
+ result.range.setEnd(textNode, curPos - 1);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ return found || ("range" in result);
+ },
+
+ /**
+ * Toggles the "wrap" class on the document body, which sets whether
+ * or not long lines are wrapped. Notifies parent to update the pref.
+ */
+ toggleWrapping() {
+ let body = content.document.body;
+ let state = body.classList.toggle("wrap");
+ sendAsyncMessage("ViewSource:StoreWrapping", { state });
+ },
+
+ /**
+ * Toggles the "highlight" class on the document body, which sets whether
+ * or not syntax highlighting is displayed. Notifies parent to update the
+ * pref.
+ */
+ toggleSyntaxHighlighting() {
+ let body = content.document.body;
+ let state = body.classList.toggle("highlight");
+ sendAsyncMessage("ViewSource:StoreSyntaxHighlighting", { state });
+ },
+
+ /**
+ * Called when the parent has changed the character set to view the
+ * source with.
+ *
+ * @param charset
+ * The character set to use.
+ * @param doPageLoad
+ * Whether or not we should reload the page ourselves with the
+ * nsIWebPageDescriptor. Part of a workaround for bug 136322.
+ */
+ setCharacterSet(charset, doPageLoad) {
+ docShell.charset = charset;
+ if (doPageLoad) {
+ this.reload();
+ }
+ },
+
+ /**
+ * Reloads the content.
+ */
+ reload() {
+ let pageLoader = docShell.QueryInterface(Ci.nsIWebPageDescriptor);
+ try {
+ pageLoader.loadPage(pageLoader.currentDescriptor,
+ Ci.nsIWebPageDescriptor.DISPLAY_NORMAL);
+ } catch (e) {
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNav.reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
+ }
+ },
+
+ /**
+ * A reference to a DeferredTask that is armed every time the
+ * selection changes.
+ */
+ updateStatusTask: null,
+
+ /**
+ * Called once the DeferredTask fires. Sends a message up to the
+ * parent to update the status bar text.
+ */
+ updateStatus() {
+ let selection = content.getSelection();
+
+ if (!selection.focusNode) {
+ sendAsyncMessage("ViewSource:UpdateStatus", { label: "" });
+ return;
+ }
+ if (selection.focusNode.nodeType != Ci.nsIDOMNode.TEXT_NODE) {
+ return;
+ }
+
+ let selCon = this.selectionController;
+ selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON);
+ selCon.setCaretVisibilityDuringSelection(true);
+
+ let interlinePosition = selection.QueryInterface(Ci.nsISelectionPrivate)
+ .interlinePosition;
+
+ let result = {};
+ this.findLocation(null, -1,
+ selection.focusNode, selection.focusOffset, interlinePosition, result);
+
+ let label = this.bundle.formatStringFromName("statusBarLineCol",
+ [result.line, result.col], 2);
+ sendAsyncMessage("ViewSource:UpdateStatus", { label });
+ },
+
+ /**
+ * Loads a view source selection showing the given view-source url and
+ * highlight the selection.
+ *
+ * @param uri view-source uri to show
+ * @param drawSelection true to highlight the selection
+ * @param baseURI base URI of the original document
+ */
+ viewSourceWithSelection(uri, drawSelection, baseURI)
+ {
+ this.needsDrawSelection = drawSelection;
+
+ // all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
+ let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT;
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ webNav.loadURIWithOptions(uri, loadFlags,
+ null, referrerPolicy, // referrer
+ null, null, // postData, headers
+ Services.io.newURI(baseURI, null, null));
+ },
+
+ /**
+ * nsISelectionListener
+ */
+
+ /**
+ * Gets called every time the selection is changed. Coalesces frequent
+ * changes, and calls updateStatus after 100ms of no selection change
+ * activity.
+ */
+ notifySelectionChanged(doc, sel, reason) {
+ if (!this.updateStatusTask) {
+ this.updateStatusTask = new DeferredTask(() => {
+ this.updateStatus();
+ }, 100);
+ }
+
+ this.updateStatusTask.arm();
+ },
+
+ /**
+ * Using special markers left in the serialized source, this helper makes the
+ * underlying markup of the selected fragment to automatically appear as
+ * selected on the inflated view-source DOM.
+ */
+ drawSelection() {
+ content.document.title =
+ this.bundle.GetStringFromName("viewSelectionSourceTitle");
+
+ // find the special selection markers that we added earlier, and
+ // draw the selection between the two...
+ var findService = null;
+ try {
+ // get the find service which stores the global find state
+ findService = Cc["@mozilla.org/find/find_service;1"]
+ .getService(Ci.nsIFindService);
+ } catch (e) { }
+ if (!findService)
+ return;
+
+ // cache the current global find state
+ var matchCase = findService.matchCase;
+ var entireWord = findService.entireWord;
+ var wrapFind = findService.wrapFind;
+ var findBackwards = findService.findBackwards;
+ var searchString = findService.searchString;
+ var replaceString = findService.replaceString;
+
+ // setup our find instance
+ var findInst = this.webBrowserFind;
+ findInst.matchCase = true;
+ findInst.entireWord = false;
+ findInst.wrapFind = true;
+ findInst.findBackwards = false;
+
+ // ...lookup the start mark
+ findInst.searchString = MARK_SELECTION_START;
+ var startLength = MARK_SELECTION_START.length;
+ findInst.findNext();
+
+ var selection = content.getSelection();
+ if (!selection.rangeCount)
+ return;
+
+ var range = selection.getRangeAt(0);
+
+ var startContainer = range.startContainer;
+ var startOffset = range.startOffset;
+
+ // ...lookup the end mark
+ findInst.searchString = MARK_SELECTION_END;
+ var endLength = MARK_SELECTION_END.length;
+ findInst.findNext();
+
+ var endContainer = selection.anchorNode;
+ var endOffset = selection.anchorOffset;
+
+ // reset the selection that find has left
+ selection.removeAllRanges();
+
+ // delete the special markers now...
+ endContainer.deleteData(endOffset, endLength);
+ startContainer.deleteData(startOffset, startLength);
+ if (startContainer == endContainer)
+ endOffset -= startLength; // has shrunk if on same text node...
+ range.setEnd(endContainer, endOffset);
+
+ // show the selection and scroll it into view
+ selection.addRange(range);
+ // the default behavior of the selection is to scroll at the end of
+ // the selection, whereas in this situation, it is more user-friendly
+ // to scroll at the beginning. So we override the default behavior here
+ try {
+ this.selectionController.scrollSelectionIntoView(
+ Ci.nsISelectionController.SELECTION_NORMAL,
+ Ci.nsISelectionController.SELECTION_ANCHOR_REGION,
+ true);
+ }
+ catch (e) { }
+
+ // restore the current find state
+ findService.matchCase = matchCase;
+ findService.entireWord = entireWord;
+ findService.wrapFind = wrapFind;
+ findService.findBackwards = findBackwards;
+ findService.searchString = searchString;
+ findService.replaceString = replaceString;
+
+ findInst.matchCase = matchCase;
+ findInst.entireWord = entireWord;
+ findInst.wrapFind = wrapFind;
+ findInst.findBackwards = findBackwards;
+ findInst.searchString = searchString;
+ },
+
+ /**
+ * In-page context menu items that are injected after page load.
+ */
+ contextMenuItems: [
+ {
+ id: "goToLine",
+ accesskey: true,
+ handler() {
+ sendAsyncMessage("ViewSource:PromptAndGoToLine");
+ }
+ },
+ {
+ id: "wrapLongLines",
+ get checked() {
+ return Services.prefs.getBoolPref("view_source.wrap_long_lines");
+ },
+ handler() {
+ this.toggleWrapping();
+ }
+ },
+ {
+ id: "highlightSyntax",
+ get checked() {
+ return Services.prefs.getBoolPref("view_source.syntax_highlight");
+ },
+ handler() {
+ this.toggleSyntaxHighlighting();
+ }
+ },
+ ],
+
+ /**
+ * Add context menu items for view source specific actions.
+ */
+ injectContextMenu() {
+ let doc = content.document;
+
+ let menu = doc.createElementNS(NS_XHTML, "menu");
+ menu.setAttribute("type", "context");
+ menu.setAttribute("id", "actions");
+ doc.body.appendChild(menu);
+ doc.body.setAttribute("contextmenu", "actions");
+
+ this.contextMenuItems.forEach(itemSpec => {
+ let item = doc.createElementNS(NS_XHTML, "menuitem");
+ item.setAttribute("id", itemSpec.id);
+ let labelName = `context_${itemSpec.id}_label`;
+ let label = this.bundle.GetStringFromName(labelName);
+ item.setAttribute("label", label);
+ if ("checked" in itemSpec) {
+ item.setAttribute("type", "checkbox");
+ }
+ if (itemSpec.accesskey) {
+ let accesskeyName = `context_${itemSpec.id}_accesskey`;
+ item.setAttribute("accesskey",
+ this.bundle.GetStringFromName(accesskeyName))
+ }
+ menu.appendChild(item);
+ });
+
+ this.updateContextMenu();
+ },
+
+ /**
+ * Update state of checkbox-style context menu items.
+ */
+ updateContextMenu() {
+ let doc = content.document;
+ this.contextMenuItems.forEach(itemSpec => {
+ if (!("checked" in itemSpec)) {
+ return;
+ }
+ let item = doc.getElementById(itemSpec.id);
+ if (itemSpec.checked) {
+ item.setAttribute("checked", true);
+ } else {
+ item.removeAttribute("checked");
+ }
+ });
+ },
+};
+ViewSourceContent.init();
diff --git a/components/viewsource/content/viewSource.css b/components/viewsource/content/viewSource.css
new file mode 100644
index 000000000..d03efcc8c
--- /dev/null
+++ b/components/viewsource/content/viewSource.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/. */
+
+toolbar[printpreview="true"] {
+ -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
+}
+
+browser[remote="true"] {
+ -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
+} \ No newline at end of file
diff --git a/components/viewsource/content/viewSource.js b/components/viewsource/content/viewSource.js
new file mode 100644
index 000000000..234ffefc3
--- /dev/null
+++ b/components/viewsource/content/viewSource.js
@@ -0,0 +1,843 @@
+// -*- 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 { utils: Cu, interfaces: Ci, classes: Cc } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/ViewSourceBrowser.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
+ "resource://gre/modules/CharsetMenu.jsm");
+
+[
+ ["gBrowser", "content"],
+ ["gViewSourceBundle", "viewSourceBundle"],
+ ["gContextMenu", "viewSourceContextMenu"]
+].forEach(function ([name, id]) {
+ window.__defineGetter__(name, function () {
+ var element = document.getElementById(id);
+ if (!element)
+ return null;
+ delete window[name];
+ return window[name] = element;
+ });
+});
+
+/**
+ * ViewSourceChrome is the primary interface for interacting with
+ * the view source browser from a self-contained window. It extends
+ * ViewSourceBrowser with additional things needed inside the special window.
+ *
+ * It initializes itself on script load.
+ */
+function ViewSourceChrome() {
+ ViewSourceBrowser.call(this);
+}
+
+ViewSourceChrome.prototype = {
+ __proto__: ViewSourceBrowser.prototype,
+
+ /**
+ * The <browser> that will be displaying the view source content.
+ */
+ get browser() {
+ return gBrowser;
+ },
+
+ /**
+ * The context menu, when opened from the content process, sends
+ * up a chunk of serialized data describing the items that the
+ * context menu is being opened on. This allows us to avoid using
+ * CPOWs.
+ */
+ contextMenuData: {},
+
+ /**
+ * These are the messages that ViewSourceChrome will listen for
+ * from the frame script it injects. Any message names added here
+ * will automatically have ViewSourceChrome listen for those messages,
+ * and remove the listeners on teardown.
+ */
+ messages: ViewSourceBrowser.prototype.messages.concat([
+ "ViewSource:SourceLoaded",
+ "ViewSource:SourceUnloaded",
+ "ViewSource:Close",
+ "ViewSource:OpenURL",
+ "ViewSource:UpdateStatus",
+ "ViewSource:ContextMenuOpening",
+ ]),
+
+ /**
+ * This called via ViewSourceBrowser's constructor. This should be called as
+ * soon as the script loads. When this function executes, we can assume the
+ * DOM content has not yet loaded.
+ */
+ init() {
+ this.mm.loadFrameScript("chrome://global/content/viewSource-content.js", true);
+
+ this.shouldWrap = Services.prefs.getBoolPref("view_source.wrap_long_lines");
+ this.shouldHighlight =
+ Services.prefs.getBoolPref("view_source.syntax_highlight");
+
+ addEventListener("load", this);
+ addEventListener("unload", this);
+ addEventListener("AppCommand", this, true);
+ addEventListener("MozSwipeGesture", this, true);
+
+ ViewSourceBrowser.prototype.init.call(this);
+ },
+
+ /**
+ * This should be called when the window is closing. This function should
+ * clean up event and message listeners.
+ */
+ uninit() {
+ ViewSourceBrowser.prototype.uninit.call(this);
+
+ // "load" event listener is removed in its handler, to
+ // ensure we only fire it once.
+ removeEventListener("unload", this);
+ removeEventListener("AppCommand", this, true);
+ removeEventListener("MozSwipeGesture", this, true);
+ gContextMenu.removeEventListener("popupshowing", this);
+ gContextMenu.removeEventListener("popuphidden", this);
+ Services.els.removeSystemEventListener(this.browser, "dragover", this,
+ true);
+ Services.els.removeSystemEventListener(this.browser, "drop", this, true);
+ },
+
+ /**
+ * Anything added to the messages array will get handled here, and should
+ * get dispatched to a specific function for the message name.
+ */
+ receiveMessage(message) {
+ let data = message.data;
+
+ switch (message.name) {
+ // Begin messages from super class
+ case "ViewSource:PromptAndGoToLine":
+ this.promptAndGoToLine();
+ break;
+ case "ViewSource:GoToLine:Success":
+ this.onGoToLineSuccess(data.lineNumber);
+ break;
+ case "ViewSource:GoToLine:Failed":
+ this.onGoToLineFailed();
+ break;
+ case "ViewSource:StoreWrapping":
+ this.storeWrapping(data.state);
+ break;
+ case "ViewSource:StoreSyntaxHighlighting":
+ this.storeSyntaxHighlighting(data.state);
+ break;
+ // End messages from super class
+ case "ViewSource:SourceLoaded":
+ this.onSourceLoaded();
+ break;
+ case "ViewSource:SourceUnloaded":
+ this.onSourceUnloaded();
+ break;
+ case "ViewSource:Close":
+ this.close();
+ break;
+ case "ViewSource:OpenURL":
+ this.openURL(data.URL);
+ break;
+ case "ViewSource:UpdateStatus":
+ this.updateStatus(data.label);
+ break;
+ case "ViewSource:ContextMenuOpening":
+ this.onContextMenuOpening(data.isLink, data.isEmail, data.href);
+ if (this.browser.isRemoteBrowser) {
+ this.openContextMenu(data.screenX, data.screenY);
+ }
+ break;
+ }
+ },
+
+ /**
+ * Any events should get handled here, and should get dispatched to
+ * a specific function for the event type.
+ */
+ handleEvent(event) {
+ switch (event.type) {
+ case "unload":
+ this.uninit();
+ break;
+ case "load":
+ this.onXULLoaded();
+ break;
+ case "AppCommand":
+ this.onAppCommand(event);
+ break;
+ case "MozSwipeGesture":
+ this.onSwipeGesture(event);
+ break;
+ case "popupshowing":
+ this.onContextMenuShowing(event);
+ break;
+ case "popuphidden":
+ this.onContextMenuHidden(event);
+ break;
+ case "dragover":
+ this.onDragOver(event);
+ break;
+ case "drop":
+ this.onDrop(event);
+ break;
+ }
+ },
+
+ /**
+ * Getter that returns whether or not the view source browser
+ * has history enabled on it.
+ */
+ get historyEnabled() {
+ return !this.browser.hasAttribute("disablehistory");
+ },
+
+ /**
+ * Getter for the message manager used to communicate with the view source
+ * browser.
+ *
+ * In this window version of view source, we use the window message manager
+ * for loading scripts and listening for messages so that if we switch
+ * remoteness of the browser (which we might do if we're attempting to load
+ * the document source out of the network cache), we automatically re-load
+ * the frame script.
+ */
+ get mm() {
+ return window.messageManager;
+ },
+
+ /**
+ * Getter for the nsIWebNavigation of the view source browser.
+ */
+ get webNav() {
+ return this.browser.webNavigation;
+ },
+
+ /**
+ * Send the browser forward in its history.
+ */
+ goForward() {
+ this.browser.goForward();
+ },
+
+ /**
+ * Send the browser backward in its history.
+ */
+ goBack() {
+ this.browser.goBack();
+ },
+
+ /**
+ * This should be called once when the DOM has finished loading. Here we
+ * set the state of various menu items, and add event listeners to
+ * DOM nodes.
+ *
+ * This is also the place where we handle any arguments that have been
+ * passed to viewSource.xul.
+ *
+ * Modern consumers should pass a single object argument to viewSource.xul:
+ *
+ * URL (required):
+ * A string URL for the page we'd like to view the source of.
+ * browser:
+ * The browser containing the document that we would like to view the
+ * source of. This argument is optional if outerWindowID is not passed.
+ * outerWindowID (optional):
+ * The outerWindowID of the content window containing the document that
+ * we want to view the source of. This is the only way of attempting to
+ * load the source out of the network cache.
+ * lineNumber (optional):
+ * The line number to focus on once the source is loaded.
+ *
+ * The original API has the opener pass in a number of arguments:
+ *
+ * arg[0] - URL string.
+ * arg[1] - Charset value string in the form 'charset=xxx'.
+ * arg[2] - Page descriptor from nsIWebPageDescriptor used to load content
+ * from the cache.
+ * arg[3] - Line number to go to.
+ * arg[4] - Boolean for whether charset was forced by the user
+ */
+ onXULLoaded() {
+ // This handler should only ever run the first time the XUL is loaded.
+ removeEventListener("load", this);
+
+ let wrapMenuItem = document.getElementById("menu_wrapLongLines");
+ if (this.shouldWrap) {
+ wrapMenuItem.setAttribute("checked", "true");
+ }
+
+ let highlightMenuItem = document.getElementById("menu_highlightSyntax");
+ if (this.shouldHighlight) {
+ highlightMenuItem.setAttribute("checked", "true");
+ }
+
+ gContextMenu.addEventListener("popupshowing", this);
+ gContextMenu.addEventListener("popuphidden", this);
+
+ Services.els.addSystemEventListener(this.browser, "dragover", this, true);
+ Services.els.addSystemEventListener(this.browser, "drop", this, true);
+
+ if (!this.historyEnabled) {
+ // Disable the BACK and FORWARD commands and hide the related menu items.
+ let viewSourceNavigation = document.getElementById("viewSourceNavigation");
+ if (viewSourceNavigation) {
+ viewSourceNavigation.setAttribute("disabled", "true");
+ viewSourceNavigation.setAttribute("hidden", "true");
+ }
+ }
+
+ // We require the first argument to do any loading of source.
+ // otherwise, we're done.
+ if (!window.arguments[0]) {
+ return undefined;
+ }
+
+ if (typeof window.arguments[0] == "string") {
+ // We're using the original API
+ return this._loadViewSourceOriginal(window.arguments);
+ }
+
+ // We're using the modern API, which allows us to view the
+ // source of documents from out of process browsers.
+ let args = window.arguments[0];
+
+ // viewPartialSource.js will take care of loading the content in partial mode.
+ if (!args.partial) {
+ this.loadViewSource(args);
+ }
+
+ return undefined;
+ },
+
+ /**
+ * This is the original API for viewSource.xul, for old-timer consumers.
+ * This API might eventually go away.
+ */
+ _loadViewSourceOriginal(aArguments) {
+ // Parse the 'arguments' supplied with the dialog.
+ // arg[0] - URL string.
+ // arg[1] - Charset value in the form 'charset=xxx'.
+ // arg[2] - Page descriptor used to load content from the cache.
+ // arg[3] - Line number to go to.
+ // arg[4] - Whether charset was forced by the user
+
+ if (aArguments[2]) {
+ let pageDescriptor = aArguments[2];
+ if (Cu.isCrossProcessWrapper(pageDescriptor)) {
+ throw new Error("Cannot pass a CPOW as the page descriptor to viewSource.xul.");
+ }
+ }
+
+ if (this.browser.isRemoteBrowser) {
+ throw new Error("Original view source API should not use a remote browser.");
+ }
+
+ let forcedCharSet;
+ if (aArguments[4] && aArguments[1].startsWith("charset=")) {
+ forcedCharSet = aArguments[1].split("=")[1];
+ }
+
+ this.sendAsyncMessage("ViewSource:LoadSourceOriginal", {
+ URL: aArguments[0],
+ lineNumber: aArguments[3],
+ forcedCharSet,
+ }, {
+ pageDescriptor: aArguments[2],
+ });
+ },
+
+ /**
+ * Handler for the AppCommand event.
+ *
+ * @param event
+ * The AppCommand event being handled.
+ */
+ onAppCommand(event) {
+ event.stopPropagation();
+ switch (event.command) {
+ case "Back":
+ this.goBack();
+ break;
+ case "Forward":
+ this.goForward();
+ break;
+ }
+ },
+
+ /**
+ * Handler for the MozSwipeGesture event.
+ *
+ * @param event
+ * The MozSwipeGesture event being handled.
+ */
+ onSwipeGesture(event) {
+ event.stopPropagation();
+ switch (event.direction) {
+ case SimpleGestureEvent.DIRECTION_LEFT:
+ this.goBack();
+ break;
+ case SimpleGestureEvent.DIRECTION_RIGHT:
+ this.goForward();
+ break;
+ case SimpleGestureEvent.DIRECTION_UP:
+ goDoCommand("cmd_scrollTop");
+ break;
+ case SimpleGestureEvent.DIRECTION_DOWN:
+ goDoCommand("cmd_scrollBottom");
+ break;
+ }
+ },
+
+ /**
+ * Called as soon as the frame script reports that some source
+ * code has been loaded in the browser.
+ */
+ onSourceLoaded() {
+ document.getElementById("cmd_goToLine").removeAttribute("disabled");
+
+ if (this.historyEnabled) {
+ this.updateCommands();
+ }
+
+ this.browser.focus();
+ },
+
+ /**
+ * Called as soon as the frame script reports that some source
+ * code has been unloaded from the browser.
+ */
+ onSourceUnloaded() {
+ // Disable "go to line" while reloading due to e.g. change of charset
+ // or toggling of syntax highlighting.
+ document.getElementById("cmd_goToLine").setAttribute("disabled", "true");
+ },
+
+ /**
+ * Called by clicks on a menu populated by CharsetMenu.jsm to
+ * change the selected character set.
+ *
+ * @param event
+ * The click event on a character set menuitem.
+ */
+ onSetCharacterSet(event) {
+ if (event.target.hasAttribute("charset")) {
+ let charset = event.target.getAttribute("charset");
+
+ // If we don't have history enabled, we have to do a reload in order to
+ // show the character set change. See bug 136322.
+ this.sendAsyncMessage("ViewSource:SetCharacterSet", {
+ charset: charset,
+ doPageLoad: this.historyEnabled,
+ });
+
+ if (!this.historyEnabled) {
+ this.browser
+ .reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
+ }
+ }
+ },
+
+ /**
+ * Called from the frame script when the context menu is about to
+ * open. This tells ViewSourceChrome things about the item that
+ * the context menu is being opened on. This should be called before
+ * the popupshowing event handler fires.
+ */
+ onContextMenuOpening(isLink, isEmail, href) {
+ this.contextMenuData = { isLink, isEmail, href, isOpen: true };
+ },
+
+ /**
+ * Event handler for the popupshowing event on the context menu.
+ * This handler is responsible for setting the state on various
+ * menu items in the context menu, and reads values that were sent
+ * up from the frame script and stashed into this.contextMenuData.
+ *
+ * @param event
+ * The popupshowing event for the context menu.
+ */
+ onContextMenuShowing(event) {
+ let copyLinkMenuItem = document.getElementById("context-copyLink");
+ copyLinkMenuItem.hidden = !this.contextMenuData.isLink;
+
+ let copyEmailMenuItem = document.getElementById("context-copyEmail");
+ copyEmailMenuItem.hidden = !this.contextMenuData.isEmail;
+ },
+
+ /**
+ * Called when the user chooses the "Copy Link" or "Copy Email"
+ * menu items in the context menu. Copies the relevant selection
+ * into the system clipboard.
+ */
+ onContextMenuCopyLinkOrEmail() {
+ // It doesn't make any sense to call this if the context menu
+ // isn't open...
+ if (!this.contextMenuData.isOpen) {
+ return;
+ }
+
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(this.contextMenuData.href);
+ },
+
+ /**
+ * Called when the context menu closes, and invalidates any data
+ * that the frame script might have sent up about what the context
+ * menu was opened on.
+ */
+ onContextMenuHidden(event) {
+ this.contextMenuData = {
+ isOpen: false,
+ };
+ },
+
+ /**
+ * Called when the user drags something over the content browser.
+ */
+ onDragOver(event) {
+ // For drags that appear to be internal text (for example, tab drags),
+ // set the dropEffect to 'none'. This prevents the drop even if some
+ // other listener cancelled the event.
+ let types = event.dataTransfer.types;
+ if (types.includes("text/x-moz-text-internal") && !types.includes("text/plain")) {
+ event.dataTransfer.dropEffect = "none";
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+ let linkHandler = Cc["@mozilla.org/content/dropped-link-handler;1"]
+ .getService(Ci.nsIDroppedLinkHandler);
+
+ if (linkHandler.canDropLink(event, false)) {
+ event.preventDefault();
+ }
+ },
+
+ /**
+ * Called twhen the user drops something onto the content browser.
+ */
+ onDrop(event) {
+ if (event.defaultPrevented)
+ return;
+
+ let name = { };
+ let linkHandler = Cc["@mozilla.org/content/dropped-link-handler;1"]
+ .getService(Ci.nsIDroppedLinkHandler);
+ let uri;
+ try {
+ // Pass true to prevent the dropping of javascript:/data: URIs
+ uri = linkHandler.dropLink(event, name, true);
+ } catch (e) {
+ return;
+ }
+
+ if (uri) {
+ this.loadURL(uri);
+ }
+ },
+
+ /**
+ * For remote browsers, the contextmenu event is received in the
+ * content process, and a message is sent up from the frame script
+ * to ViewSourceChrome, but then it stops. The event does not bubble
+ * up to the point that the popup is opened in the parent process.
+ * ViewSourceChrome is responsible for opening up the context menu in
+ * that case. This is called when we receive the contextmenu message
+ * from the child, and we know that the browser is currently remote.
+ *
+ * @param screenX
+ * The screenX coordinate to open the popup at.
+ * @param screenY
+ * The screenY coordinate to open the popup at.
+ */
+ openContextMenu(screenX, screenY) {
+ gContextMenu.openPopupAtScreen(screenX, screenY, true);
+ },
+
+ /**
+ * Loads the source of a URL. This will probably end up hitting the
+ * network.
+ *
+ * @param URL
+ * A URL string to be opened in the view source browser.
+ */
+ loadURL(URL) {
+ this.sendAsyncMessage("ViewSource:LoadSource", { URL });
+ },
+
+ /**
+ * Updates any commands that are dependant on command broadcasters.
+ */
+ updateCommands() {
+ let backBroadcaster = document.getElementById("Browser:Back");
+ let forwardBroadcaster = document.getElementById("Browser:Forward");
+
+ if (this.webNav.canGoBack) {
+ backBroadcaster.removeAttribute("disabled");
+ } else {
+ backBroadcaster.setAttribute("disabled", "true");
+ }
+ if (this.webNav.canGoForward) {
+ forwardBroadcaster.removeAttribute("disabled");
+ } else {
+ forwardBroadcaster.setAttribute("disabled", "true");
+ }
+ },
+
+ /**
+ * Updates the status displayed in the status bar of the view source window.
+ *
+ * @param label
+ * The string to be displayed in the statusbar-lin-col element.
+ */
+ updateStatus(label) {
+ let statusBarField = document.getElementById("statusbar-line-col");
+ if (statusBarField) {
+ statusBarField.label = label;
+ }
+ },
+
+ /**
+ * Called when the frame script reports that a line was successfully gotten
+ * to.
+ *
+ * @param lineNumber
+ * The line number that we successfully got to.
+ */
+ onGoToLineSuccess(lineNumber) {
+ ViewSourceBrowser.prototype.onGoToLineSuccess.call(this, lineNumber);
+ document.getElementById("statusbar-line-col").label =
+ gViewSourceBundle.getFormattedString("statusBarLineCol", [lineNumber, 1]);
+ },
+
+ /**
+ * Reloads the browser, bypassing the network cache.
+ */
+ reload() {
+ this.browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ },
+
+ /**
+ * Closes the view source window.
+ */
+ close() {
+ window.close();
+ },
+
+ /**
+ * Called when the user clicks on the "Wrap Long Lines" menu item.
+ */
+ toggleWrapping() {
+ this.shouldWrap = !this.shouldWrap;
+ this.sendAsyncMessage("ViewSource:ToggleWrapping");
+ },
+
+ /**
+ * Called when the user clicks on the "Syntax Highlighting" menu item.
+ */
+ toggleSyntaxHighlighting() {
+ this.shouldHighlight = !this.shouldHighlight;
+ this.sendAsyncMessage("ViewSource:ToggleSyntaxHighlighting");
+ },
+
+ /**
+ * Updates the "remote" attribute of the view source browser. This
+ * will remove the browser from the DOM, and then re-add it in the
+ * same place it was taken from.
+ *
+ * @param shouldBeRemote
+ * True if the browser should be made remote. If the browsers
+ * remoteness already matches this value, this function does
+ * nothing.
+ */
+ updateBrowserRemoteness(shouldBeRemote) {
+ if (this.browser.isRemoteBrowser == shouldBeRemote) {
+ return;
+ }
+
+ let parentNode = this.browser.parentNode;
+ let nextSibling = this.browser.nextSibling;
+
+ // XX Removing and re-adding the browser from and to the DOM strips its
+ // XBL properties. Save and restore relatedBrowser. Note that when we
+ // restore relatedBrowser, there won't yet be a binding or setter. This
+ // works in conjunction with the hack in <xul:browser>'s constructor to
+ // re-get the weak reference to it.
+ let relatedBrowser = this.browser.relatedBrowser;
+
+ this.browser.remove();
+ if (shouldBeRemote) {
+ this.browser.setAttribute("remote", "true");
+ } else {
+ this.browser.removeAttribute("remote");
+ }
+
+ this.browser.relatedBrowser = relatedBrowser;
+
+ // If nextSibling was null, this will put the browser at
+ // the end of the list.
+ parentNode.insertBefore(this.browser, nextSibling);
+
+ if (shouldBeRemote) {
+ // We're going to send a message down to the remote browser
+ // to load the source content - however, in order for the
+ // contentWindowAsCPOW and contentDocumentAsCPOW values on
+ // the remote browser to be set, we must set up the
+ // RemoteWebProgress, which is lazily loaded. We only need
+ // contentWindowAsCPOW for the printing support, and this
+ // should go away once bug 1146454 is fixed, since we can
+ // then just pass the outerWindowID of the this.browser to
+ // PrintUtils.
+ this.browser.webProgress;
+ }
+ },
+};
+
+var viewSourceChrome = new ViewSourceChrome();
+
+/**
+ * PrintUtils uses this to make Print Preview work.
+ */
+var PrintPreviewListener = {
+ _ppBrowser: null,
+
+ getPrintPreviewBrowser() {
+ if (!this._ppBrowser) {
+ this._ppBrowser = document.createElement("browser");
+ this._ppBrowser.setAttribute("flex", "1");
+ this._ppBrowser.setAttribute("type", "content");
+ }
+
+ if (gBrowser.isRemoteBrowser) {
+ this._ppBrowser.setAttribute("remote", "true");
+ } else {
+ this._ppBrowser.removeAttribute("remote");
+ }
+
+ let findBar = document.getElementById("FindToolbar");
+ document.getElementById("appcontent")
+ .insertBefore(this._ppBrowser, findBar);
+
+ return this._ppBrowser;
+ },
+
+ getSourceBrowser() {
+ return gBrowser;
+ },
+
+ getNavToolbox() {
+ return document.getElementById("appcontent");
+ },
+
+ onEnter() {
+ let toolbox = document.getElementById("viewSource-toolbox");
+ toolbox.hidden = true;
+ gBrowser.collapsed = true;
+ },
+
+ onExit() {
+ this._ppBrowser.remove();
+ gBrowser.collapsed = false;
+ document.getElementById("viewSource-toolbox").hidden = false;
+ },
+
+ activateBrowser(browser) {
+ browser.docShellIsActive = true;
+ },
+};
+
+// viewZoomOverlay.js uses this
+function getBrowser() {
+ return gBrowser;
+}
+
+this.__defineGetter__("gPageLoader", function () {
+ var webnav = viewSourceChrome.webNav;
+ if (!webnav)
+ return null;
+ delete this.gPageLoader;
+ this.gPageLoader = (webnav instanceof Ci.nsIWebPageDescriptor) ? webnav
+ : null;
+ return this.gPageLoader;
+});
+
+// Strips the |view-source:| for internalSave()
+function ViewSourceSavePage()
+{
+ internalSave(gBrowser.currentURI.spec.replace(/^view-source:/i, ""),
+ null, null, null, null, null, "SaveLinkTitle",
+ null, null, gBrowser.contentDocumentAsCPOW, null,
+ gPageLoader);
+}
+
+// Below are original functions and variables left behind for
+// compatibility reasons. These will be removed soon via bug 1159293.
+
+this.__defineGetter__("gLastLineFound", function () {
+ return viewSourceChrome.lastLineFound;
+});
+
+function onLoadViewSource() {
+ viewSourceChrome.onXULLoaded();
+}
+
+function isHistoryEnabled() {
+ return viewSourceChrome.historyEnabled;
+}
+
+function ViewSourceClose() {
+ viewSourceChrome.close();
+}
+
+function ViewSourceReload() {
+ viewSourceChrome.reload();
+}
+
+function getWebNavigation()
+{
+ // The original implementation returned null if anything threw during
+ // the getting of the webNavigation.
+ try {
+ return viewSourceChrome.webNav;
+ } catch (e) {
+ return null;
+ }
+}
+
+function viewSource(url) {
+ viewSourceChrome.loadURL(url);
+}
+
+function ViewSourceGoToLine()
+{
+ viewSourceChrome.promptAndGoToLine();
+}
+
+function goToLine(line)
+{
+ viewSourceChrome.goToLine(line);
+}
+
+function BrowserForward(aEvent) {
+ viewSourceChrome.goForward();
+}
+
+function BrowserBack(aEvent) {
+ viewSourceChrome.goBack();
+}
+
+function UpdateBackForwardCommands() {
+ viewSourceChrome.updateCommands();
+}
diff --git a/components/viewsource/content/viewSource.xul b/components/viewsource/content/viewSource.xul
new file mode 100644
index 000000000..3ad45a9d9
--- /dev/null
+++ b/components/viewsource/content/viewSource.xul
@@ -0,0 +1,221 @@
+<?xml version="1.0"?>
+# -*- Mode: XML -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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://global/content/viewSource.css" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/viewsource/viewsource.css" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % sourceDTD SYSTEM "chrome://global/locale/viewSource.dtd" >
+%sourceDTD;
+<!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetMenu.dtd" >
+%charsetDTD;
+]>
+
+<window id="viewSource"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ contenttitlesetting="true"
+ title="&mainWindow.title;"
+ titlemodifier="&mainWindow.titlemodifier;"
+ titlepreface="&mainWindow.preface;"
+ titlemenuseparator ="&mainWindow.titlemodifierseparator;"
+ windowtype="navigator:view-source"
+ width="640" height="480"
+ screenX="10" screenY="10"
+ persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://global/content/printUtils.js"/>
+ <script type="application/javascript" src="chrome://global/content/viewSource.js"/>
+ <script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+
+ <stringbundle id="viewSourceBundle" src="chrome://global/locale/viewSource.properties"/>
+
+ <command id="cmd_savePage" oncommand="ViewSourceSavePage();"/>
+ <command id="cmd_print" oncommand="PrintUtils.printWindow(gBrowser.outerWindowID, gBrowser);"/>
+ <command id="cmd_printpreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
+ <command id="cmd_pagesetup" oncommand="PrintUtils.showPageSetup();"/>
+ <command id="cmd_close" oncommand="window.close();"/>
+ <commandset id="editMenuCommands"/>
+ <command id="cmd_find"
+ oncommand="document.getElementById('FindToolbar').onFindCommand();"/>
+ <command id="cmd_findAgain"
+ oncommand="document.getElementById('FindToolbar').onFindAgainCommand(false);"/>
+ <command id="cmd_findPrevious"
+ oncommand="document.getElementById('FindToolbar').onFindAgainCommand(true);"/>
+ <command id="cmd_reload" oncommand="viewSourceChrome.reload();"/>
+ <command id="cmd_goToLine" oncommand="viewSourceChrome.promptAndGoToLine();" disabled="true"/>
+ <command id="cmd_highlightSyntax" oncommand="viewSourceChrome.toggleSyntaxHighlighting();"/>
+ <command id="cmd_wrapLongLines" oncommand="viewSourceChrome.toggleWrapping();"/>
+ <command id="cmd_textZoomReduce" oncommand="ZoomManager.reduce();"/>
+ <command id="cmd_textZoomEnlarge" oncommand="ZoomManager.enlarge();"/>
+ <command id="cmd_textZoomReset" oncommand="ZoomManager.reset();"/>
+
+ <command id="Browser:Back" oncommand="viewSourceChrome.goBack()" observes="viewSourceNavigation"/>
+ <command id="Browser:Forward" oncommand="viewSourceChrome.goForward()" observes="viewSourceNavigation"/>
+
+ <broadcaster id="viewSourceNavigation"/>
+
+ <keyset id="editMenuKeys"/>
+ <keyset id="viewSourceKeys">
+ <key id="key_savePage" key="&savePageCmd.commandkey;" modifiers="accel" command="cmd_savePage"/>
+ <key id="key_print" key="&printCmd.commandkey;" modifiers="accel" command="cmd_print"/>
+ <key id="key_close" key="&closeCmd.commandkey;" modifiers="accel" command="cmd_close"/>
+ <key id="key_goToLine" key="&goToLineCmd.commandkey;" command="cmd_goToLine" modifiers="accel"/>
+
+ <key id="key_textZoomEnlarge" key="&textEnlarge.commandkey;" command="cmd_textZoomEnlarge" modifiers="accel"/>
+ <key id="key_textZoomEnlarge2" key="&textEnlarge.commandkey2;" command="cmd_textZoomEnlarge" modifiers="accel"/>
+ <key id="key_textZoomEnlarge3" key="&textEnlarge.commandkey3;" command="cmd_textZoomEnlarge" modifiers="accel"/>
+ <key id="key_textZoomReduce" key="&textReduce.commandkey;" command="cmd_textZoomReduce" modifiers="accel"/>
+ <key id="key_textZoomReduce2" key="&textReduce.commandkey2;" command="cmd_textZoomReduce" modifiers="accel"/>
+ <key id="key_textZoomReset" key="&textReset.commandkey;" command="cmd_textZoomReset" modifiers="accel"/>
+ <key id="key_textZoomReset2" key="&textReset.commandkey2;" command="cmd_textZoomReset" modifiers="accel"/>
+
+ <key id="key_reload" key="&reloadCmd.commandkey;" command="cmd_reload" modifiers="accel"/>
+ <key key="&reloadCmd.commandkey;" command="cmd_reload" modifiers="accel,shift"/>
+ <key keycode="VK_F5" command="cmd_reload"/>
+ <key keycode="VK_F5" command="cmd_reload" modifiers="accel"/>
+ <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 keycode="VK_BACK" command="Browser:Back"/>
+ <key keycode="VK_BACK" command="Browser:Forward" modifiers="shift"/>
+ <key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="alt"/>
+ <key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="alt"/>
+#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
+
+ </keyset>
+
+ <tooltip id="aHTMLTooltip" page="true"/>
+
+ <menupopup id="viewSourceContextMenu">
+ <menuitem id="context-back"
+ label="&backCmd.label;"
+ accesskey="&backCmd.accesskey;"
+ command="Browser:Back"
+ observes="viewSourceNavigation"/>
+ <menuitem id="context-forward"
+ label="&forwardCmd.label;"
+ accesskey="&forwardCmd.accesskey;"
+ command="Browser:Forward"
+ observes="viewSourceNavigation"/>
+ <menuseparator observes="viewSourceNavigation"/>
+ <menuitem id="cMenu_findAgain"/>
+ <menuseparator/>
+ <menuitem id="cMenu_copy"/>
+ <menuitem id="context-copyLink"
+ label="&copyLinkCmd.label;"
+ accesskey="&copyLinkCmd.accesskey;"
+ oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
+ <menuitem id="context-copyEmail"
+ label="&copyEmailCmd.label;"
+ accesskey="&copyEmailCmd.accesskey;"
+ oncommand="viewSourceChrome.onContextMenuCopyLinkOrEmail();"/>
+ <menuseparator/>
+ <menuitem id="cMenu_selectAll"/>
+ </menupopup>
+
+ <!-- Menu -->
+ <toolbox id="viewSource-toolbox">
+ <menubar id="viewSource-main-menubar">
+
+ <menu id="menu_file" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
+ <menupopup id="menu_FilePopup">
+ <menuitem key="key_savePage" command="cmd_savePage" id="menu_savePage"
+ label="&savePageCmd.label;" accesskey="&savePageCmd.accesskey;"/>
+ <menuitem command="cmd_pagesetup" id="menu_pageSetup"
+ label="&pageSetupCmd.label;" accesskey="&pageSetupCmd.accesskey;"/>
+ <menuitem command="cmd_printpreview" id="menu_printPreview"
+ label="&printPreviewCmd.label;" accesskey="&printPreviewCmd.accesskey;"/>
+ <menuitem key="key_print" command="cmd_print" id="menu_print"
+ label="&printCmd.label;" accesskey="&printCmd.accesskey;"/>
+ <menuseparator/>
+ <menuitem key="key_close" command="cmd_close" id="menu_close"
+ label="&closeCmd.label;" accesskey="&closeCmd.accesskey;"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu_edit">
+ <menupopup id="editmenu-popup">
+ <menuitem id="menu_undo"/>
+ <menuitem id="menu_redo"/>
+ <menuseparator/>
+ <menuitem id="menu_cut"/>
+ <menuitem id="menu_copy"/>
+ <menuitem id="menu_paste"/>
+ <menuitem id="menu_delete"/>
+ <menuseparator/>
+ <menuitem id="menu_selectAll"/>
+ <menuseparator/>
+ <menuitem id="menu_find"/>
+ <menuitem id="menu_findAgain"/>
+ <menuseparator/>
+ <menuitem id="menu_goToLine" key="key_goToLine" command="cmd_goToLine"
+ label="&goToLineCmd.label;" accesskey="&goToLineCmd.accesskey;"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu_view" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;">
+ <menupopup id="viewmenu-popup">
+ <menuitem id="menu_reload" command="cmd_reload" accesskey="&reloadCmd.accesskey;"
+ label="&reloadCmd.label;" key="key_reload"/>
+ <menuseparator />
+ <menu id="viewTextZoomMenu" label="&menu_textSize.label;" accesskey="&menu_textSize.accesskey;">
+ <menupopup>
+ <menuitem id="menu_textEnlarge" command="cmd_textZoomEnlarge"
+ label="&menu_textEnlarge.label;" accesskey="&menu_textEnlarge.accesskey;"
+ key="key_textZoomEnlarge"/>
+ <menuitem id="menu_textReduce" command="cmd_textZoomReduce"
+ label="&menu_textReduce.label;" accesskey="&menu_textReduce.accesskey;"
+ key="key_textZoomReduce"/>
+ <menuseparator/>
+ <menuitem id="menu_textReset" command="cmd_textZoomReset"
+ label="&menu_textReset.label;" accesskey="&menu_textReset.accesskey;"
+ key="key_textZoomReset"/>
+ </menupopup>
+ </menu>
+
+ <!-- Charset Menu -->
+ <menu id="charsetMenu"
+ label="&charsetMenu2.label;"
+ accesskey="&charsetMenu2.accesskey;"
+ oncommand="viewSourceChrome.onSetCharacterSet(event);"
+ onpopupshowing="CharsetMenu.build(event.target);
+ CharsetMenu.update(event.target, content.document.characterSet);">
+ <menupopup/>
+ </menu>
+ <menuseparator/>
+ <menuitem id="menu_wrapLongLines" type="checkbox" command="cmd_wrapLongLines"
+ label="&menu_wrapLongLines.title;" accesskey="&menu_wrapLongLines.accesskey;"/>
+ <menuitem type="checkbox" id="menu_highlightSyntax" command="cmd_highlightSyntax"
+ label="&menu_highlightSyntax.label;" accesskey="&menu_highlightSyntax.accesskey;"/>
+ </menupopup>
+ </menu>
+ </menubar>
+ </toolbox>
+
+ <vbox id="appcontent" flex="1">
+
+ <browser id="content" type="content-primary" name="content" src="about:blank" flex="1"
+ context="viewSourceContextMenu" showcaret="true" tooltip="aHTMLTooltip" />
+ <findbar id="FindToolbar" browserid="content"/>
+ </vbox>
+
+ <statusbar id="status-bar" class="chromeclass-status">
+ <statusbarpanel id="statusbar-line-col" label="" flex="1"/>
+ </statusbar>
+
+</window>
diff --git a/components/viewsource/content/viewSourceUtils.js b/components/viewsource/content/viewSourceUtils.js
new file mode 100644
index 000000000..72f5dd787
--- /dev/null
+++ b/components/viewsource/content/viewSourceUtils.js
@@ -0,0 +1,509 @@
+// -*- 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/. */
+
+/*
+ * To keep the global namespace safe, don't define global variables and
+ * functions in this file.
+ *
+ * This file silently depends on contentAreaUtils.js for
+ * getDefaultFileName, getNormalizedLeafName and getDefaultExtension
+ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ViewSourceBrowser",
+ "resource://gre/modules/ViewSourceBrowser.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+var gViewSourceUtils = {
+
+ mnsIWebBrowserPersist: Components.interfaces.nsIWebBrowserPersist,
+ mnsIWebProgress: Components.interfaces.nsIWebProgress,
+ mnsIWebPageDescriptor: Components.interfaces.nsIWebPageDescriptor,
+
+ /**
+ * Opens the view source window.
+ *
+ * @param aArgsOrURL (required)
+ * This is either an Object containing parameters, or a string
+ * URL for the page we want to view the source of. In the latter
+ * case we will be paying attention to the other parameters, as
+ * we will be supporting the old API for this method.
+ * If aArgsOrURL is an Object, the other parameters will be ignored.
+ * aArgsOrURL as an Object can include the following properties:
+ *
+ * URL (required):
+ * A string URL for the page we'd like to view the source of.
+ * browser (optional):
+ * The browser containing the document that we would like to view the
+ * source of. This is required if outerWindowID is passed.
+ * outerWindowID (optional):
+ * The outerWindowID of the content window containing the document that
+ * we want to view the source of. Pass this if you want to attempt to
+ * load the document source out of the network cache.
+ * lineNumber (optional):
+ * The line number to focus on once the source is loaded.
+ *
+ * @param aPageDescriptor (optional)
+ * Accepted for compatibility reasons, but is otherwise ignored.
+ * @param aDocument (optional)
+ * The content document we would like to view the source of. This
+ * function will throw if aDocument is a CPOW.
+ * @param aLineNumber (optional)
+ * The line number to focus on once the source is loaded.
+ */
+ viewSource: function(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber)
+ {
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ if (prefs.getBoolPref("view_source.editor.external")) {
+ this.openInExternalEditor(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber);
+ } else {
+ this._openInInternalViewer(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber);
+ }
+ },
+
+ /**
+ * Displays view source in the provided <browser>. This allows for non-window
+ * display methods, such as a tab from Firefox.
+ *
+ * @param aArgs
+ * An object with the following properties:
+ *
+ * URL (required):
+ * A string URL for the page we'd like to view the source of.
+ * viewSourceBrowser (required):
+ * The browser to display the view source in.
+ * browser (optional):
+ * The browser containing the document that we would like to view the
+ * source of. This is required if outerWindowID is passed.
+ * outerWindowID (optional):
+ * The outerWindowID of the content window containing the document that
+ * we want to view the source of. Pass this if you want to attempt to
+ * load the document source out of the network cache.
+ * lineNumber (optional):
+ * The line number to focus on once the source is loaded.
+ */
+ viewSourceInBrowser: function(aArgs) {
+ let viewSourceBrowser = new ViewSourceBrowser(aArgs.viewSourceBrowser);
+ viewSourceBrowser.loadViewSource(aArgs);
+ },
+
+ /**
+ * Displays view source for a selection from some document in the provided
+ * <browser>. This allows for non-window display methods, such as a tab from
+ * Firefox.
+ *
+ * @param aViewSourceInBrowser
+ * The browser containing the page to view the source of.
+ * @param aTarget
+ * Set to the target node for MathML. Null for other types of elements.
+ * @param aGetBrowserFn
+ * If set, a function that will return a browser to open the source in.
+ * If null, or this function returns null, opens the source in a new window.
+ */
+ viewPartialSourceInBrowser: function(aViewSourceInBrowser, aTarget, aGetBrowserFn) {
+ let mm = aViewSourceInBrowser.messageManager;
+ mm.addMessageListener("ViewSource:GetSelectionDone", function gotSelection(message) {
+ mm.removeMessageListener("ViewSource:GetSelectionDone", gotSelection);
+
+ if (!message.data)
+ return;
+
+ let browserToOpenIn = aGetBrowserFn ? aGetBrowserFn() : null;
+ if (browserToOpenIn) {
+ let viewSourceBrowser = new ViewSourceBrowser(browserToOpenIn);
+ viewSourceBrowser.loadViewSourceFromSelection(message.data.uri, message.data.drawSelection,
+ message.data.baseURI);
+ }
+ else {
+ window.openDialog("chrome://global/content/viewPartialSource.xul",
+ "_blank", "all,dialog=no",
+ {
+ URI: message.data.uri,
+ drawSelection: message.data.drawSelection,
+ baseURI: message.data.baseURI,
+ partial: true,
+ });
+ }
+ });
+
+ mm.sendAsyncMessage("ViewSource:GetSelection", { }, { target: aTarget });
+ },
+
+ // Opens the interval view source viewer
+ _openInInternalViewer: function(aArgsOrURL, aPageDescriptor, aDocument, aLineNumber)
+ {
+ // try to open a view-source window while inheriting the charset (if any)
+ var charset = null;
+ var isForcedCharset = false;
+ if (aDocument) {
+ if (Components.utils.isCrossProcessWrapper(aDocument)) {
+ throw new Error("View Source cannot accept a CPOW as a document.");
+ }
+
+ charset = "charset=" + aDocument.characterSet;
+ try {
+ isForcedCharset =
+ aDocument.defaultView
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .docCharsetIsForced;
+ } catch (ex) {
+ }
+ }
+ openDialog("chrome://global/content/viewSource.xul",
+ "_blank",
+ "all,dialog=no",
+ aArgsOrURL, charset, aPageDescriptor, aLineNumber, isForcedCharset);
+ },
+
+ buildEditorArgs: function(aPath, aLineNumber) {
+ // Determine the command line arguments to pass to the editor.
+ // We currently support a %LINE% placeholder which is set to the passed
+ // line number (or to 0 if there's none)
+ var editorArgs = [];
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ var args = prefs.getCharPref("view_source.editor.args");
+ if (args) {
+ args = args.replace("%LINE%", aLineNumber || "0");
+ // add the arguments to the array (keeping quoted strings intact)
+ const argumentRE = /"([^"]+)"|(\S+)/g;
+ while (argumentRE.test(args))
+ editorArgs.push(RegExp.$1 || RegExp.$2);
+ }
+ editorArgs.push(aPath);
+ return editorArgs;
+ },
+
+ /**
+ * Opens an external editor with the view source content.
+ *
+ * @param aArgsOrURL (required)
+ * This is either an Object containing parameters, or a string
+ * URL for the page we want to view the source of. In the latter
+ * case we will be paying attention to the other parameters, as
+ * we will be supporting the old API for this method.
+ * If aArgsOrURL is an Object, the other parameters will be ignored.
+ * aArgsOrURL as an Object can include the following properties:
+ *
+ * URL (required):
+ * A string URL for the page we'd like to view the source of.
+ * browser (optional):
+ * The browser containing the document that we would like to view the
+ * source of. This is required if outerWindowID is passed.
+ * outerWindowID (optional):
+ * The outerWindowID of the content window containing the document that
+ * we want to view the source of. Pass this if you want to attempt to
+ * load the document source out of the network cache.
+ * lineNumber (optional):
+ * The line number to focus on once the source is loaded.
+ *
+ * @param aPageDescriptor (optional)
+ * Accepted for compatibility reasons, but is otherwise ignored.
+ * @param aDocument (optional)
+ * The content document we would like to view the source of. This
+ * function will throw if aDocument is a CPOW.
+ * @param aLineNumber (optional)
+ * The line number to focus on once the source is loaded.
+ * @param aCallBack
+ * A function accepting two arguments:
+ * * result (true = success)
+ * * data object
+ * The function defaults to opening an internal viewer if external
+ * viewing fails.
+ */
+ openInExternalEditor: function(aArgsOrURL, aPageDescriptor, aDocument,
+ aLineNumber, aCallBack) {
+ let data;
+ if (typeof aArgsOrURL == "string") {
+ if (Components.utils.isCrossProcessWrapper(aDocument)) {
+ throw new Error("View Source cannot accept a CPOW as a document.");
+ }
+ data = {
+ url: aArgsOrURL,
+ pageDescriptor: aPageDescriptor,
+ doc: aDocument,
+ lineNumber: aLineNumber,
+ isPrivate: false,
+ };
+ if (aDocument) {
+ data.isPrivate =
+ PrivateBrowsingUtils.isWindowPrivate(aDocument.defaultView);
+ }
+ } else {
+ let { URL, browser, lineNumber } = aArgsOrURL;
+ data = {
+ url: URL,
+ lineNumber,
+ isPrivate: false,
+ };
+ if (browser) {
+ data.doc = {
+ characterSet: browser.characterSet,
+ contentType: browser.documentContentType,
+ title: browser.contentTitle,
+ };
+ data.isPrivate = PrivateBrowsingUtils.isBrowserPrivate(browser);
+ }
+ }
+
+ try {
+ var editor = this.getExternalViewSourceEditor();
+ if (!editor) {
+ this.handleCallBack(aCallBack, false, data);
+ return;
+ }
+
+ // make a uri
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var charset = data.doc ? data.doc.characterSet : null;
+ var uri = ios.newURI(data.url, charset, null);
+ data.uri = uri;
+
+ var path;
+ var contentType = data.doc ? data.doc.contentType : null;
+ if (uri.scheme == "file") {
+ // it's a local file; we can open it directly
+ path = uri.QueryInterface(Components.interfaces.nsIFileURL).file.path;
+
+ var editorArgs = this.buildEditorArgs(path, data.lineNumber);
+ editor.runw(false, editorArgs, editorArgs.length);
+ this.handleCallBack(aCallBack, true, data);
+ } else {
+ // set up the progress listener with what we know so far
+ this.viewSourceProgressListener.contentLoaded = false;
+ this.viewSourceProgressListener.editor = editor;
+ this.viewSourceProgressListener.callBack = aCallBack;
+ this.viewSourceProgressListener.data = data;
+ if (!data.pageDescriptor) {
+ // without a page descriptor, loadPage has no chance of working. download the file.
+ var file = this.getTemporaryFile(uri, data.doc, contentType);
+ this.viewSourceProgressListener.file = file;
+
+ var webBrowserPersist = Components
+ .classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+ .createInstance(this.mnsIWebBrowserPersist);
+ // the default setting is to not decode. we need to decode.
+ webBrowserPersist.persistFlags = this.mnsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
+ webBrowserPersist.progressListener = this.viewSourceProgressListener;
+ let referrerPolicy = Components.interfaces.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER;
+ webBrowserPersist.savePrivacyAwareURI(uri, null, null, referrerPolicy, null, null, file, data.isPrivate);
+
+ let helperService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"]
+ .getService(Components.interfaces.nsPIExternalAppLauncher);
+ if (data.isPrivate) {
+ // register the file to be deleted when possible
+ helperService.deleteTemporaryPrivateFileWhenPossible(file);
+ } else {
+ // register the file to be deleted on app exit
+ helperService.deleteTemporaryFileOnExit(file);
+ }
+ } else {
+ // we'll use nsIWebPageDescriptor to get the source because it may
+ // not have to refetch the file from the server
+ // XXXbz this is so broken... This code doesn't set up this docshell
+ // at all correctly; if somehow the view-source stuff managed to
+ // execute script we'd be in big trouble here, I suspect.
+ var webShell = Components.classes["@mozilla.org/docshell;1"].createInstance();
+ webShell.QueryInterface(Components.interfaces.nsIBaseWindow).create();
+ this.viewSourceProgressListener.webShell = webShell;
+ var progress = webShell.QueryInterface(this.mnsIWebProgress);
+ progress.addProgressListener(this.viewSourceProgressListener,
+ this.mnsIWebProgress.NOTIFY_STATE_DOCUMENT);
+ var pageLoader = webShell.QueryInterface(this.mnsIWebPageDescriptor);
+ pageLoader.loadPage(data.pageDescriptor, this.mnsIWebPageDescriptor.DISPLAY_AS_SOURCE);
+ }
+ }
+ } catch (ex) {
+ // we failed loading it with the external editor.
+ Components.utils.reportError(ex);
+ this.handleCallBack(aCallBack, false, data);
+ return;
+ }
+ },
+
+ // Default callback - opens the internal viewer if the external editor failed
+ internalViewerFallback: function(result, data)
+ {
+ if (!result) {
+ this._openInInternalViewer(data.url, data.pageDescriptor, data.doc, data.lineNumber);
+ }
+ },
+
+ // Calls the callback, keeping in mind undefined or null values.
+ handleCallBack: function(aCallBack, result, data)
+ {
+ // if callback is undefined, default to the internal viewer
+ if (aCallBack === undefined) {
+ this.internalViewerFallback(result, data);
+ } else if (aCallBack) {
+ aCallBack(result, data);
+ }
+ },
+
+ // Returns nsIProcess of the external view source editor or null
+ getExternalViewSourceEditor: function()
+ {
+ try {
+ let viewSourceAppPath =
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch)
+ .getComplexValue("view_source.editor.path",
+ Components.interfaces.nsIFile);
+ let editor = Components.classes['@mozilla.org/process/util;1']
+ .createInstance(Components.interfaces.nsIProcess);
+ editor.init(viewSourceAppPath);
+
+ return editor;
+ }
+ catch (ex) {
+ Components.utils.reportError(ex);
+ }
+
+ return null;
+ },
+
+ viewSourceProgressListener: {
+
+ mnsIWebProgressListener: Components.interfaces.nsIWebProgressListener,
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(this.mnsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ destroy: function() {
+ if (this.webShell) {
+ this.webShell.QueryInterface(Components.interfaces.nsIBaseWindow).destroy();
+ }
+ this.webShell = null;
+ this.editor = null;
+ this.callBack = null;
+ this.data = null;
+ this.file = null;
+ },
+
+ // This listener is used both for tracking the progress of an HTML parse
+ // in one case and for tracking the progress of nsIWebBrowserPersist in
+ // another case.
+ onStateChange: function(aProgress, aRequest, aFlag, aStatus) {
+ // once it's done loading...
+ if ((aFlag & this.mnsIWebProgressListener.STATE_STOP) && aStatus == 0) {
+ if (!this.webShell) {
+ // We aren't waiting for the parser. Instead, we are waiting for
+ // an nsIWebBrowserPersist.
+ this.onContentLoaded();
+ return 0;
+ }
+ var webNavigation = this.webShell.QueryInterface(Components.interfaces.nsIWebNavigation);
+ if (webNavigation.document.readyState == "complete") {
+ // This branch is probably never taken. Including it for completeness.
+ this.onContentLoaded();
+ } else {
+ webNavigation.document.addEventListener("DOMContentLoaded",
+ this.onContentLoaded.bind(this));
+ }
+ }
+ return 0;
+ },
+
+ onContentLoaded: function() {
+ // The progress listener may call this multiple times, so be sure we only
+ // run once.
+ if (this.contentLoaded) {
+ return;
+ }
+ try {
+ if (!this.file) {
+ // it's not saved to file yet, it's in the webshell
+
+ // get a temporary filename using the attributes from the data object that
+ // openInExternalEditor gave us
+ this.file = gViewSourceUtils.getTemporaryFile(this.data.uri, this.data.doc,
+ this.data.doc.contentType);
+
+ // we have to convert from the source charset.
+ var webNavigation = this.webShell.QueryInterface(Components.interfaces.nsIWebNavigation);
+ var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Components.interfaces.nsIFileOutputStream);
+ foStream.init(this.file, 0x02 | 0x08 | 0x20, -1, 0); // write | create | truncate
+ var coStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
+ .createInstance(Components.interfaces.nsIConverterOutputStream);
+ coStream.init(foStream, this.data.doc.characterSet, 0, null);
+
+ // write the source to the file
+ coStream.writeString(webNavigation.document.body.textContent);
+
+ // clean up
+ coStream.close();
+ foStream.close();
+
+ let helperService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"]
+ .getService(Components.interfaces.nsPIExternalAppLauncher);
+ if (this.data.isPrivate) {
+ // register the file to be deleted when possible
+ helperService.deleteTemporaryPrivateFileWhenPossible(this.file);
+ } else {
+ // register the file to be deleted on app exit
+ helperService.deleteTemporaryFileOnExit(this.file);
+ }
+ }
+
+ var editorArgs = gViewSourceUtils.buildEditorArgs(this.file.path,
+ this.data.lineNumber);
+ this.editor.runw(false, editorArgs, editorArgs.length);
+
+ this.contentLoaded = true;
+ gViewSourceUtils.handleCallBack(this.callBack, true, this.data);
+ } catch (ex) {
+ // we failed loading it with the external editor.
+ Components.utils.reportError(ex);
+ gViewSourceUtils.handleCallBack(this.callBack, false, this.data);
+ } finally {
+ this.destroy();
+ }
+ },
+
+ onLocationChange: function() { return 0; },
+ onProgressChange: function() { return 0; },
+ onStatusChange: function() { return 0; },
+ onSecurityChange: function() { return 0; },
+
+ webShell: null,
+ editor: null,
+ callBack: null,
+ data: null,
+ file: null
+ },
+
+ // returns an nsIFile for the passed document in the system temp directory
+ getTemporaryFile: function(aURI, aDocument, aContentType) {
+ // include contentAreaUtils.js in our own context when we first need it
+ if (!this._caUtils) {
+ var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Components.interfaces.mozIJSSubScriptLoader);
+ this._caUtils = {};
+ scriptLoader.loadSubScript("chrome://global/content/contentAreaUtils.js", this._caUtils);
+ }
+
+ var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ var tempFile = fileLocator.get("TmpD", Components.interfaces.nsIFile);
+ var fileName = this._caUtils.getDefaultFileName(null, aURI, aDocument, aContentType);
+ var extension = this._caUtils.getDefaultExtension(fileName, aURI, aContentType);
+ var leafName = this._caUtils.getNormalizedLeafName(fileName, extension);
+ tempFile.append(leafName);
+ return tempFile;
+ }
+}
diff --git a/components/viewsource/jar.mn b/components/viewsource/jar.mn
new file mode 100644
index 000000000..33818ae0d
--- /dev/null
+++ b/components/viewsource/jar.mn
@@ -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/.
+
+toolkit.jar:
+ content/global/viewSource.css (content/viewSource.css)
+ content/global/viewSource.js (content/viewSource.js)
+* content/global/viewSource.xul (content/viewSource.xul)
+ content/global/viewPartialSource.js (content/viewPartialSource.js)
+ content/global/viewPartialSource.xul (content/viewPartialSource.xul)
+ content/global/viewSourceUtils.js (content/viewSourceUtils.js)
+ content/global/viewSource-content.js (content/viewSource-content.js)
diff --git a/components/viewsource/moz.build b/components/viewsource/moz.build
new file mode 100644
index 000000000..d28658de4
--- /dev/null
+++ b/components/viewsource/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_JS_MODULES += ['ViewSourceBrowser.jsm']
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/weave/WeaveComponents.manifest b/components/weave/WeaveComponents.manifest
new file mode 100644
index 000000000..816a9e2b4
--- /dev/null
+++ b/components/weave/WeaveComponents.manifest
@@ -0,0 +1,13 @@
+# WeaveService.js
+component {74b89fb0-f200-4ae8-a3ec-dd164117f6de} WeaveService.js
+contract @mozilla.org/weave/service;1 {74b89fb0-f200-4ae8-a3ec-dd164117f6de}
+category app-startup WeaveService service,@mozilla.org/weave/service;1
+
+# WeaveService.js (about:sync-log)
+component {d28f8a0b-95da-48f4-b712-caf37097be41} WeaveService.js
+contract @mozilla.org/network/protocol/about;1?what=sync-log {d28f8a0b-95da-48f4-b712-caf37097be41}
+
+# Register resource aliases
+resource services-common resource://gre/modules/services-common/
+resource services-crypto resource://gre/modules/services-crypto/
+resource services-sync resource://gre/modules/services-sync/
diff --git a/components/weave/content/aboutSyncTabs-bindings.xml b/components/weave/content/aboutSyncTabs-bindings.xml
new file mode 100644
index 000000000..e6108209a
--- /dev/null
+++ b/components/weave/content/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/weave/content/aboutSyncTabs.css b/components/weave/content/aboutSyncTabs.css
new file mode 100644
index 000000000..2d71e254d
--- /dev/null
+++ b/components/weave/content/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://weave/content/aboutSyncTabs-bindings.xml#tab-listing);
+}
+
+richlistitem[type="client"] {
+ -moz-binding: url(chrome://weave/content/aboutSyncTabs-bindings.xml#client-listing);
+}
diff --git a/components/weave/content/aboutSyncTabs.js b/components/weave/content/aboutSyncTabs.js
new file mode 100644
index 000000000..410494b5b
--- /dev/null
+++ b/components/weave/content/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/weave/content/aboutSyncTabs.xul b/components/weave/content/aboutSyncTabs.xul
new file mode 100644
index 000000000..2300c0b42
--- /dev/null
+++ b/components/weave/content/aboutSyncTabs.xul
@@ -0,0 +1,67 @@
+<?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://weave/skin/aboutSyncTabs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://weave/content/aboutSyncTabs.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % aboutSyncTabsDTD SYSTEM "chrome://weave/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://weave/content/aboutSyncTabs.js"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <html:head>
+ <html:link rel="icon" href="chrome://weave/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/weave/content/addDevice.js b/components/weave/content/addDevice.js
new file mode 100644
index 000000000..98ac74d7d
--- /dev/null
+++ b/components/weave/content/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() {
+ 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() {
+ 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() {
+ switch (this.wizard.pageIndex) {
+ case ADD_DEVICE_PAGE:
+ this.startTransfer();
+ return false;
+ case DEVICE_CONNECTED_PAGE:
+ window.close();
+ return false;
+ }
+ return true;
+ },
+
+ startTransfer: function() {
+ 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() {
+ 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() {
+ 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(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() {
+ if (this.wizard.pageIndex != SYNC_KEY_PAGE)
+ return true;
+
+ this.wizard.pageIndex = ADD_DEVICE_PAGE;
+ return false;
+ },
+
+ onWizardCancel: function() {
+ if (this._jpakeclient) {
+ this._jpakeclient.abort();
+ delete this._jpakeclient;
+ }
+ return true;
+ },
+
+ onTextBoxInput: function(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() {
+ 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/weave/content/addDevice.xul b/components/weave/content/addDevice.xul
new file mode 100644
index 000000000..ab640a068
--- /dev/null
+++ b/components/weave/content/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://weave/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://weave/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://branding/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://weave/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://weave/content/addDevice.js"/>
+ <script type="application/javascript"
+ src="chrome://weave/content/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/weave/content/genericChange.js b/components/weave/content/genericChange.js
new file mode 100644
index 000000000..75adc4893
--- /dev/null
+++ b/components/weave/content/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() {
+ /* 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://weave/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() {
+ this._status.value = "";
+ this._statusIcon.removeAttribute("status");
+ },
+
+ _updateStatus: function(str, state) {
+ this._updateStatusWithString(this._str(str), state);
+ },
+
+ _updateStatusWithString: function(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() {
+ 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() {
+ 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(str) {
+ return this._stringBundle.GetStringFromName(str);
+ }
+};
diff --git a/components/weave/content/genericChange.xul b/components/weave/content/genericChange.xul
new file mode 100644
index 000000000..ab6e9b0a2
--- /dev/null
+++ b/components/weave/content/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://weave/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://weave/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://branding/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://weave/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://weave/content/genericChange.js"/>
+ <script type="application/javascript"
+ src="chrome://weave/content/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/weave/content/key.xhtml b/components/weave/content/key.xhtml
new file mode 100644
index 000000000..ca9d03d42
--- /dev/null
+++ b/components/weave/content/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://branding/locale/syncBrand.dtd">
+ %syncBrandDTD;
+ <!ENTITY % syncKeyDTD SYSTEM "chrome://weave/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/weave/content/notification.xml b/components/weave/content/notification.xml
new file mode 100644
index 000000000..8ac881e08
--- /dev/null
+++ b/components/weave/content/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/weave/content/progress.js b/components/weave/content/progress.js
new file mode 100644
index 000000000..101160fa8
--- /dev/null
+++ b/components/weave/content/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/weave/content/progress.xhtml b/components/weave/content/progress.xhtml
new file mode 100644
index 000000000..f2233e607
--- /dev/null
+++ b/components/weave/content/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://weave/locale/syncProgress.dtd">
+ %syncProgressDTD;
+ <!ENTITY % syncSetupDTD
+ SYSTEM "chrome://weave/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://weave/skin/syncProgress.css"/>
+
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://weave/skin/sync-16.png"/>
+
+ <script type="text/javascript;version=1.8"
+ src="chrome://weave/content/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://weave/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/weave/content/quota.js b/components/weave/content/quota.js
new file mode 100644
index 000000000..90b16f909
--- /dev/null
+++ b/components/weave/content/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() {
+ 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() {
+ 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() {
+ if (this._usage_req) {
+ this._usage_req.abort();
+ }
+ if (this._quota_req) {
+ this._quota_req.abort();
+ }
+ return true;
+ },
+
+ onAccept: function() {
+ 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(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() {
+ 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(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(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(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(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() {
+ // 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(row, col) {
+ let collection = this._collections[row];
+ switch (col.id) {
+ case "collection":
+ return collection.title;
+ case "size":
+ return collection.sizeLabel;
+ default:
+ return "";
+ }
+ },
+
+ setTree: function(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/weave/content/quota.xul b/components/weave/content/quota.xul
new file mode 100644
index 000000000..070736882
--- /dev/null
+++ b/components/weave/content/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://weave/skin/syncQuota.css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://branding/locale/syncBrand.dtd">
+<!ENTITY % syncQuotaDTD SYSTEM "chrome://weave/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://weave/content/quota.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="quotaStrings"
+ src="chrome://weave/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/weave/content/setup.js b/components/weave/content/setup.js
new file mode 100644
index 000000000..c44f2bed9
--- /dev/null
+++ b/components/weave/content/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() {
+ // 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() {
+ 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() {
+ // This method will never be called since SendCredentialsController
+ // will take over after the wizard completes.
+ },
+ onAbort: function(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() {
+ 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(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() {},
+
+ onComplete: function(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(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() {
+ 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://weave/locale/syncSetup.properties");
+});
diff --git a/components/weave/content/setup.xul b/components/weave/content/setup.xul
new file mode 100644
index 000000000..da7e26ff5
--- /dev/null
+++ b/components/weave/content/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://weave/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://weave/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://branding/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://weave/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://weave/content/setup.js"/>
+ <script type="application/javascript"
+ src="chrome://weave/content/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/weave/content/utils.js b/components/weave/content/utils.js
new file mode 100644
index 000000000..fa5d6a5ac
--- /dev/null
+++ b/components/weave/content/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://weave/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(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(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://weave/content/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://weave/content/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/components/weave/jar.mn b/components/weave/jar.mn
new file mode 100644
index 000000000..fee5c0889
--- /dev/null
+++ b/components/weave/jar.mn
@@ -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/.
+
+toolkit.jar:
+% content weave %content/weave/ contentaccessible=yes
+ content/weave/aboutSyncTabs.xul (content/aboutSyncTabs.xul)
+ content/weave/aboutSyncTabs.js (content/aboutSyncTabs.js)
+ content/weave/aboutSyncTabs.css (content/aboutSyncTabs.css)
+ content/weave/aboutSyncTabs-bindings.xml (content/aboutSyncTabs-bindings.xml)
+ content/weave/setup.xul (content/setup.xul)
+ content/weave/addDevice.js (content/addDevice.js)
+ content/weave/addDevice.xul (content/addDevice.xul)
+ content/weave/setup.js (content/setup.js)
+ content/weave/genericChange.xul (content/genericChange.xul)
+ content/weave/genericChange.js (content/genericChange.js)
+ content/weave/key.xhtml (content/key.xhtml)
+ content/weave/notification.xml (content/notification.xml)
+ content/weave/quota.xul (content/quota.xul)
+ content/weave/quota.js (content/quota.js)
+ content/weave/utils.js (content/utils.js)
+ content/weave/progress.js (content/progress.js)
+ content/weave/progress.xhtml (content/progress.xhtml)
+
+classic.jar:
+% skin weave classic/1.0 %skin/classic/weave/
+ skin/classic/weave/aboutSyncTabs.css (skin/aboutSyncTabs.css)
+ skin/classic/weave/sync-16.png (skin/sync-16.png)
+ skin/classic/weave/sync-32.png (skin/sync-32.png)
+ skin/classic/weave/sync-128.png (skin/sync-128.png)
+ skin/classic/weave/sync-bg.png (skin/sync-bg.png)
+ skin/classic/weave/sync-desktopIcon.png (skin/sync-desktopIcon.png)
+ skin/classic/weave/sync-mobileIcon.png (skin/sync-mobileIcon.png)
+* skin/classic/weave/syncSetup.css (skin/syncSetup.css)
+* skin/classic/weave/syncCommon.css (skin/syncCommon.css)
+ skin/classic/weave/syncQuota.css (skin/syncQuota.css)
+ skin/classic/weave/syncProgress.css (skin/syncProgress.css) \ No newline at end of file
diff --git a/components/weave/locales/en-US/aboutSyncTabs.dtd b/components/weave/locales/en-US/aboutSyncTabs.dtd
new file mode 100644
index 000000000..5865c1231
--- /dev/null
+++ b/components/weave/locales/en-US/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/components/weave/locales/en-US/errors.properties b/components/weave/locales/en-US/errors.properties
new file mode 100644
index 000000000..e51eb422c
--- /dev/null
+++ b/components/weave/locales/en-US/errors.properties
@@ -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/.
+
+error.login.reason.network = Failed to connect to the server
+error.login.reason.recoverykey = Wrong Recovery Key
+error.login.reason.account = Incorrect account name or password
+error.login.reason.no_username = Missing account name
+error.login.reason.no_password2 = Missing password
+error.login.reason.no_recoverykey= No saved Recovery Key to use
+error.login.reason.server = Server incorrectly configured
+
+error.sync.failed_partial = One or more data types could not be synced
+# LOCALIZATION NOTE (error.sync.reason.serverMaintenance): We removed the extraneous period from this string
+error.sync.reason.serverMaintenance = Sync server maintenance is underway; syncing will resume automatically
+
+invalid-captcha = Incorrect words, try again
+weak-password = Use a stronger password
+
+# this is the fallback, if we hit an error we didn't bother to localize
+error.reason.unknown = Unknown error
+
+change.password.pwSameAsPassword = Password can't match current password
+change.password.pwSameAsUsername = Password can't match your user name
+change.password.pwSameAsEmail = Password can't match your email address
+change.password.mismatch = The passwords entered do not match
+change.password.tooShort = The password entered is too short
diff --git a/components/weave/locales/en-US/sync.properties b/components/weave/locales/en-US/sync.properties
new file mode 100644
index 000000000..af40e125a
--- /dev/null
+++ b/components/weave/locales/en-US/sync.properties
@@ -0,0 +1,55 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# %1: the user name (Ed), %2: the app name (Firefox), %3: the operating system (Android)
+client.name2 = %1$S's %2$S on %3$S
+
+# %S is the date and time at which the last sync successfully completed
+lastSync2.label = Last sync: %S
+
+# signInToSync.description is the tooltip for the Sync buttons when Sync is
+# not configured.
+signInToSync.description = Sign In To Sync
+mobile.label = Mobile Bookmarks
+
+remote.pending.label = Remote tabs are being synced…
+remote.missing2.label = Sync your other devices again to access their tabs
+remote.opened.label = All remote tabs are already open
+remote.notification.label = Recent desktop tabs will be available once they sync
+
+error.login.title = Error While Signing In
+error.login.description = Sync encountered an error while connecting: %1$S. Please try again.
+error.login.prefs.label = Preferences…
+error.login.prefs.accesskey = P
+# should decide if we're going to show this
+error.logout.title = Error While Signing Out
+error.logout.description = Sync encountered an error while connecting. It's probably ok, and you don't have to do anything about it.
+error.sync.title = Error While Syncing
+error.sync.description = Sync encountered an error while syncing: %1$S. Sync will automatically retry this action.
+error.sync.prolonged_failure = Sync has not been able to complete during the last %1$S days. Please check your network settings.
+error.sync.serverStatusButton.label = Server Status
+error.sync.serverStatusButton.accesskey = V
+error.sync.needUpdate.description = You need to update Sync to continue syncing your data.
+error.sync.needUpdate.label = Update Sync
+error.sync.needUpdate.accesskey = U
+error.sync.tryAgainButton.label = Sync Now
+error.sync.tryAgainButton.accesskey = S
+warning.sync.quota.label = Approaching Server Quota
+warning.sync.quota.description = You are approaching the server quota. Please review which data to sync.
+error.sync.quota.label = Server Quota Exceeded
+error.sync.quota.description = Sync failed because it exceeded the server quota. Please review which data to sync.
+error.sync.viewQuotaButton.label = View Quota
+error.sync.viewQuotaButton.accesskey = V
+warning.sync.eol.label = Service Shutting Down
+# %1: the app name (Basilisk)
+warning.sync.eol.description = Your Sync service is shutting down soon. Upgrade %1$S to keep syncing.
+error.sync.eol.label = Service Unavailable
+# %1: the app name (Basilisk)
+error.sync.eol.description = Your Sync service is no longer available. You need to upgrade %1$S to keep syncing.
+sync.eol.learnMore.label = Learn more
+sync.eol.learnMore.accesskey = L
+
+syncnow.label = Sync Now
+syncing2.label = Syncing…
+setupsync.label = Set Up Sync
diff --git a/components/weave/locales/en-US/syncGenericChange.properties b/components/weave/locales/en-US/syncGenericChange.properties
new file mode 100644
index 000000000..aea86ad0e
--- /dev/null
+++ b/components/weave/locales/en-US/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/components/weave/locales/en-US/syncKey.dtd b/components/weave/locales/en-US/syncKey.dtd
new file mode 100644
index 000000000..f37f2c92e
--- /dev/null
+++ b/components/weave/locales/en-US/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/components/weave/locales/en-US/syncProgress.dtd b/components/weave/locales/en-US/syncProgress.dtd
new file mode 100644
index 000000000..db45cb935
--- /dev/null
+++ b/components/weave/locales/en-US/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/components/weave/locales/en-US/syncQuota.dtd b/components/weave/locales/en-US/syncQuota.dtd
new file mode 100644
index 000000000..71174f087
--- /dev/null
+++ b/components/weave/locales/en-US/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/components/weave/locales/en-US/syncQuota.properties b/components/weave/locales/en-US/syncQuota.properties
new file mode 100644
index 000000000..0e1b857ca
--- /dev/null
+++ b/components/weave/locales/en-US/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/components/weave/locales/en-US/syncSetup.dtd b/components/weave/locales/en-US/syncSetup.dtd
new file mode 100644
index 000000000..7ee938e5d
--- /dev/null
+++ b/components/weave/locales/en-US/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/components/weave/locales/en-US/syncSetup.properties b/components/weave/locales/en-US/syncSetup.properties
new file mode 100644
index 000000000..8a5170adb
--- /dev/null
+++ b/components/weave/locales/en-US/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/components/weave/locales/jar.mn b/components/weave/locales/jar.mn
new file mode 100644
index 000000000..aafcf4af6
--- /dev/null
+++ b/components/weave/locales/jar.mn
@@ -0,0 +1,18 @@
+#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 weave @AB_CD@ %locale/@AB_CD@/weave/
+ locale/@AB_CD@/weave/errors.properties (%errors.properties)
+ locale/@AB_CD@/weave/sync.properties (%sync.properties)
+ locale/@AB_CD@/weave/syncProgress.dtd (%syncProgress.dtd)
+ locale/@AB_CD@/weave/aboutSyncTabs.dtd (%aboutSyncTabs.dtd)
+ locale/@AB_CD@/weave/syncSetup.dtd (%syncSetup.dtd)
+ locale/@AB_CD@/weave/syncSetup.properties (%syncSetup.properties)
+ locale/@AB_CD@/weave/syncGenericChange.properties (%syncGenericChange.properties)
+ locale/@AB_CD@/weave/syncKey.dtd (%syncKey.dtd)
+ locale/@AB_CD@/weave/syncQuota.dtd (%syncQuota.dtd)
+ locale/@AB_CD@/weave/syncQuota.properties (%syncQuota.properties) \ No newline at end of file
diff --git a/components/weave/locales/l10n.ini b/components/weave/locales/l10n.ini
new file mode 100644
index 000000000..d9c1ef945
--- /dev/null
+++ b/components/weave/locales/l10n.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/.
+
+[general]
+depth = ../../..
+
+[compare]
+dirs = services/sync
diff --git a/components/weave/locales/moz.build b/components/weave/locales/moz.build
new file mode 100644
index 000000000..e3d80cf11
--- /dev/null
+++ b/components/weave/locales/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 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/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/weave/moz.build b/components/weave/moz.build
new file mode 100644
index 000000000..9f7f9590f
--- /dev/null
+++ b/components/weave/moz.build
@@ -0,0 +1,78 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 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/.
+
+DIRS += ['locales']
+
+DEFINES['weave_id'] = "{340c2bbc-ce74-4362-90b5-7c26312808ef}"
+DEFINES['weave_version'] = "1.45.0"
+
+XPIDL_SOURCES += ['public/nsISyncJPAKE.idl']
+
+SOURCES += ['src/nsSyncJPAKE.cpp']
+
+EXTRA_COMPONENTS += [
+ 'src/WeaveService.js',
+ 'WeaveComponents.manifest',
+]
+
+EXTRA_JS_MODULES['services-common'] += [
+ 'src/common/hawkclient.js',
+ 'src/common/hawkrequest.js',
+ 'src/common/logmanager.js',
+ 'src/common/observers.js',
+ 'src/common/rest.js',
+ 'src/common/stringbundle.js',
+ 'src/common/tokenserverclient.js',
+ 'src/common/utils.js',
+]
+
+EXTRA_JS_MODULES['services-crypto'] += [
+ 'src/crypto/utils.js',
+ 'src/crypto/WeaveCrypto.js',
+]
+
+EXTRA_JS_MODULES['services-sync'] += [
+ 'src/sync/addonsreconciler.js',
+ 'src/sync/addonutils.js',
+ 'src/sync/engines.js',
+ 'src/sync/identity.js',
+ 'src/sync/jpakeclient.js',
+ 'src/sync/keys.js',
+ 'src/sync/main.js',
+ 'src/sync/notifications.js',
+ 'src/sync/policies.js',
+ 'src/sync/record.js',
+ 'src/sync/resource.js',
+ 'src/sync/rest.js',
+ 'src/sync/service.js',
+ 'src/sync/status.js',
+ 'src/sync/userapi.js',
+ 'src/sync/util.js',
+]
+
+EXTRA_JS_MODULES['services-sync'].engines += [
+ 'src/engines/addons.js',
+ 'src/engines/bookmarks.js',
+ 'src/engines/clients.js',
+ 'src/engines/forms.js',
+ 'src/engines/history.js',
+ 'src/engines/passwords.js',
+ 'src/engines/prefs.js',
+ 'src/engines/tabs.js',
+]
+
+EXTRA_JS_MODULES['services-sync'].stages += [
+ 'src/stages/cluster.js',
+ 'src/stages/declined.js',
+ 'src/stages/enginesync.js',
+]
+
+FINAL_TARGET_PP_FILES['modules/services-sync'] += ['src/sync/constants.js']
+
+JS_PREFERENCE_FILES += ['weave-prefs.js']
+XPIDL_MODULE = 'weave'
+FINAL_LIBRARY = 'xul'
+JAR_MANIFESTS += ['jar.mn']
+
diff --git a/components/weave/public/nsISyncJPAKE.idl b/components/weave/public/nsISyncJPAKE.idl
new file mode 100644
index 000000000..864057235
--- /dev/null
+++ b/components/weave/public/nsISyncJPAKE.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(5ab02a98-5122-4b90-93cd-f259c4b42e3a)]
+interface nsISyncJPAKE : nsISupports
+{
+ /**
+ * Perform first round of the JPAKE exchange.
+ *
+ * @param aSignerID
+ * String identifying the signer.
+ * @param aGX1
+ * Schnorr signature value g^x1, in hex representation.
+ * @param aGV1
+ * Schnorr signature value g^v1 (v1 is a random value), in hex
+ * representation.
+ * @param aR1
+ * Schnorr signature value r1 = v1 - x1 * h, in hex representation.
+ * @param aGX2
+ * Schnorr signature value g^x2, in hex representation.
+ * @param aGV2
+ * Schnorr signature value g^v2 (v2 is a random value), in hex
+ * representation.
+ * @param aR2
+ * Schnorr signature value r2 = v2 - x2 * h, in hex representation.
+ */
+ void round1(in ACString aSignerID,
+ out ACString aGX1,
+ out ACString aGV1,
+ out ACString aR1,
+ out ACString aGX2,
+ out ACString aGV2,
+ out ACString aR2);
+
+ /**
+ * Perform second round of the JPAKE exchange.
+ *
+ * @param aPeerID
+ * String identifying the peer.
+ * @param aPIN
+ * String containing the weak secret (PIN).
+ * @param aGX3
+ * Schnorr signature value g^x3, in hex representation.
+ * @param aGV3
+ * Schnorr signature value g^v3 (v3 is a random value), in hex
+ * representation.
+ * @param aR3
+ * Schnorr signature value r3 = v3 - x3 * h, in hex representation.
+ * @param aGX4
+ * Schnorr signature value g^x4, in hex representation.
+ * @param aGV4
+ * Schnorr signature value g^v4 (v4 is a random value), in hex
+ * representation.
+ * @param aR4
+ * Schnorr signature value r4 = v4 - x4 * h, in hex representation.
+ * @param aA
+ * Schnorr signature value A, in hex representation.
+ * @param aGVA
+ * Schnorr signature value g^va (va is a random value), in hex
+ * representation.
+ * @param aRA
+ * Schnorr signature value ra = va - xa * h, in hex representation.
+ */
+ void round2(in ACString aPeerID,
+ in ACString aPIN,
+ in ACString aGX3,
+ in ACString aGV3,
+ in ACString aR3,
+ in ACString aGX4,
+ in ACString aGV4,
+ in ACString aR4,
+ out ACString aA,
+ out ACString aGVA,
+ out ACString aRA);
+
+ /**
+ * Perform the final step of the JPAKE exchange. This will compute
+ * the key and expand the key to two keys, an AES256 encryption key
+ * and a 256 bit HMAC key. It returns a key confirmation value
+ * (SHA256d of the key) and the encryption and HMAC keys.
+ *
+ * @param aB
+ * Schnorr signature value B, in hex representation.
+ * @param aGVB
+ * Schnorr signature value g^vb (vb is a random value), in hex
+ * representation.
+ * @param aRB
+ * Schnorr signature value rb = vb - xb * h, in hex representation.
+ * @param aAES256Key
+ * The AES 256 encryption key, in base64 representation.
+ * @param aHMAC256Key
+ * The 256 bit HMAC key, in base64 representation.
+ */
+ void final(in ACString aB,
+ in ACString aGVB,
+ in ACString aRB,
+ in ACString aHkdfInfo,
+ out ACString aAES256Key,
+ out ACString aHMAC256Key);
+};
diff --git a/components/weave/skin/aboutSyncTabs.css b/components/weave/skin/aboutSyncTabs.css
new file mode 100644
index 000000000..4796f7bcc
--- /dev/null
+++ b/components/weave/skin/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://weave/skin/sync-bg.png) repeat-x center -80px;
+}
+
+#headers {
+ background: url(chrome://weave/skin/sync-32.png) no-repeat;
+ margin-top: 4px;
+ width: 45em;
+ height: 32px;
+ margin-inline-start: 2em;
+ margin-inline-end: 2em;
+}
+
+#tabsListHeading {
+ font-size: 140%;
+ font-weight: bold;
+ margin-inline-start: 40px;
+}
+
+richlistitem {
+ margin-inline-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;
+ margin-inline-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;
+ margin-inline-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://weave/skin/sync-mobileIcon.png");
+}
+richlistitem.desktop[type="client"] {
+ list-style-image: url("chrome://weave/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 {
+ padding-inline-start: 2px;
+ padding-top: 2px;
+}
diff --git a/components/weave/skin/sync-128.png b/components/weave/skin/sync-128.png
new file mode 100644
index 000000000..1ea34818c
--- /dev/null
+++ b/components/weave/skin/sync-128.png
Binary files differ
diff --git a/components/weave/skin/sync-16.png b/components/weave/skin/sync-16.png
new file mode 100644
index 000000000..0afb1c719
--- /dev/null
+++ b/components/weave/skin/sync-16.png
Binary files differ
diff --git a/components/weave/skin/sync-32.png b/components/weave/skin/sync-32.png
new file mode 100644
index 000000000..7a762cb98
--- /dev/null
+++ b/components/weave/skin/sync-32.png
Binary files differ
diff --git a/components/weave/skin/sync-bg.png b/components/weave/skin/sync-bg.png
new file mode 100644
index 000000000..893a27d76
--- /dev/null
+++ b/components/weave/skin/sync-bg.png
Binary files differ
diff --git a/components/weave/skin/sync-desktopIcon.png b/components/weave/skin/sync-desktopIcon.png
new file mode 100644
index 000000000..d3d1e27c3
--- /dev/null
+++ b/components/weave/skin/sync-desktopIcon.png
Binary files differ
diff --git a/components/weave/skin/sync-mobileIcon.png b/components/weave/skin/sync-mobileIcon.png
new file mode 100644
index 000000000..a3bda5751
--- /dev/null
+++ b/components/weave/skin/sync-mobileIcon.png
Binary files differ
diff --git a/components/weave/skin/syncCommon.css b/components/weave/skin/syncCommon.css
new file mode 100644
index 000000000..05467c5c1
--- /dev/null
+++ b/components/weave/skin/syncCommon.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/. */
+
+/* The following are used by both sync/setup.xul and sync/genericChange.xul */
+.status {
+ color: -moz-dialogtext;
+}
+
+.statusIcon {
+ margin-inline-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"] {
+%ifdef XP_WIN
+ list-style-image: url("chrome://global/skin/icons/error-16.png");
+%else
+ list-style-image: url("moz-icon://stock/gtk-dialog-error?size=menu");
+%endif
+}
+
+.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://weave/skin/sync-32.png");
+}
+
+#introText {
+ margin-top: 2px;
+}
+
+#feedback {
+ height: 2em;
+}
diff --git a/components/weave/skin/syncProgress.css b/components/weave/skin/syncProgress.css
new file mode 100644
index 000000000..d7aa59976
--- /dev/null
+++ b/components/weave/skin/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/components/weave/skin/syncQuota.css b/components/weave/skin/syncQuota.css
new file mode 100644
index 000000000..1577de8a3
--- /dev/null
+++ b/components/weave/skin/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/components/weave/skin/syncSetup.css b/components/weave/skin/syncSetup.css
new file mode 100644
index 000000000..b423eca29
--- /dev/null
+++ b/components/weave/skin/syncSetup.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/. */
+
+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 {
+ margin-inline-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://weave/skin/sync-32.png");
+}
+
+%ifdef XP_WIN
+#tosDesc {
+ margin-left: -7px;
+ margin-bottom: 3px;
+}
+%endif
diff --git a/components/weave/src/WeaveService.js b/components/weave/src/WeaveService.js
new file mode 100644
index 000000000..5bafa07ad
--- /dev/null
+++ b/components/weave/src/WeaveService.js
@@ -0,0 +1,177 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://services-sync/util.js");
+
+const SYNC_PREFS_BRANCH = "services.sync.";
+
+
+/**
+ * Sync's XPCOM service.
+ *
+ * It is named "Weave" for historical reasons.
+ *
+ * It's worth noting how Sync is lazily loaded. We register a timer that
+ * loads Sync a few seconds after app startup. This is so Sync does not
+ * adversely affect application start time.
+ *
+ * If Sync is not configured, no extra Sync code is loaded. If an
+ * external component (say the UI) needs to interact with Sync, it
+ * should use the promise-base function whenLoaded() - something like the
+ * following:
+ *
+ * // 1. Grab a handle to the Sync XPCOM service.
+ * let service = Cc["@mozilla.org/weave/service;1"]
+ * .getService(Components.interfaces.nsISupports)
+ * .wrappedJSObject;
+ *
+ * // 2. Use the .then method of the promise.
+ * service.whenLoaded().then(() => {
+ * // You are free to interact with "Weave." objects.
+ * return;
+ * });
+ *
+ * And that's it! However, if you really want to avoid promises and do it
+ * old-school, then
+ *
+ * // 1. Get a reference to the service as done in (1) above.
+ *
+ * // 2. Check if the service has been initialized.
+ * if (service.ready) {
+ * // You are free to interact with "Weave." objects.
+ * return;
+ * }
+ *
+ * // 3. Install "ready" listener.
+ * Services.obs.addObserver(function onReady() {
+ * Services.obs.removeObserver(onReady, "weave:service:ready");
+ *
+ * // You are free to interact with "Weave." objects.
+ * }, "weave:service:ready", false);
+ *
+ * // 4. Trigger loading of Sync.
+ * service.ensureLoaded();
+ */
+function WeaveService() {
+ this.wrappedJSObject = this;
+ this.ready = false;
+}
+WeaveService.prototype = {
+ classID: Components.ID("{74b89fb0-f200-4ae8-a3ec-dd164117f6de}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ ensureLoaded: function () {
+ Components.utils.import("resource://services-sync/main.js");
+
+ // Side-effect of accessing the service is that it is instantiated.
+ Weave.Service;
+ },
+
+ whenLoaded: function() {
+ if (this.ready) {
+ return Promise.resolve();
+ }
+ let deferred = Promise.defer();
+
+ Services.obs.addObserver(function onReady() {
+ Services.obs.removeObserver(onReady, "weave:service:ready");
+ deferred.resolve();
+ }, "weave:service:ready", false);
+ this.ensureLoaded();
+ return deferred.promise;
+ },
+
+ /**
+ * Whether Sync appears to be enabled.
+ *
+ * This returns true if all the Sync preferences for storing account
+ * and server configuration are populated.
+ *
+ * It does *not* perform a robust check to see if the client is working.
+ * For that, you'll want to check Weave.Status.checkSetup().
+ */
+ get enabled() {
+ let prefs = Services.prefs.getBranch(SYNC_PREFS_BRANCH);
+ return prefs.prefHasUserValue("username") &&
+ prefs.prefHasUserValue("clusterURL");
+ },
+
+ observe: function (subject, topic, data) {
+ switch (topic) {
+ case "app-startup":
+ let os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(this, "final-ui-startup", true);
+ break;
+
+ case "final-ui-startup":
+ // Force Weave service to load if it hasn't triggered from overlays
+ this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.timer.initWithCallback({
+ notify: function() {
+ let isConfigured = false;
+ // We only load more if it looks like Sync is configured.
+ let prefs = Services.prefs.getBranch(SYNC_PREFS_BRANCH);
+ if (prefs.prefHasUserValue("username")) {
+ // We have a username. So, do a more thorough check. This will
+ // import a number of modules and thus increase memory
+ // accordingly. We could potentially copy code performed by
+ // this check into this file if our above code is yielding too
+ // many false positives.
+ Components.utils.import("resource://services-sync/main.js");
+ isConfigured = Weave.Status.checkSetup() != Weave.CLIENT_NOT_CONFIGURED;
+ }
+ if (isConfigured) {
+ this.ensureLoaded();
+ }
+ }.bind(this)
+ }, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
+ break;
+ }
+ }
+};
+
+function AboutWeaveLog() {}
+AboutWeaveLog.prototype = {
+ classID: Components.ID("{d28f8a0b-95da-48f4-b712-caf37097be41}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule,
+ Ci.nsISupportsWeakReference]),
+
+ getURIFlags: function(aURI) {
+ return 0;
+ },
+
+ newChannel: function(aURI, aLoadInfo) {
+ let dir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
+ let uri = Services.io.newFileURI(dir);
+ let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
+
+ channel.originalURI = aURI;
+
+ // Ensure that the about page has the same privileges as a regular directory
+ // view. That way links to files can be opened. make sure we use the correct
+ // origin attributes when creating the principal for accessing the
+ // about:sync-log data.
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let principal = ssm.createCodebasePrincipal(uri, aLoadInfo.originAttributes);
+
+ channel.owner = principal;
+ return channel;
+ }
+};
+
+const components = [WeaveService, AboutWeaveLog];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/components/weave/src/common/hawkclient.js b/components/weave/src/common/hawkclient.js
new file mode 100644
index 000000000..88e9c2f2d
--- /dev/null
+++ b/components/weave/src/common/hawkclient.js
@@ -0,0 +1,346 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+/*
+ * HAWK is an HTTP authentication scheme using a message authentication code
+ * (MAC) algorithm to provide partial HTTP request cryptographic verification.
+ *
+ * For details, see: https://github.com/hueniverse/hawk
+ *
+ * With HAWK, it is essential that the clocks on clients and server not have an
+ * absolute delta of greater than one minute, as the HAWK protocol uses
+ * timestamps to reduce the possibility of replay attacks. However, it is
+ * likely that some clients' clocks will be more than a little off, especially
+ * in mobile devices, which would break HAWK-based services (like sync and
+ * firefox accounts) for those clients.
+ *
+ * This library provides a stateful HAWK client that calculates (roughly) the
+ * clock delta on the client vs the server. The library provides an interface
+ * for deriving HAWK credentials and making HAWK-authenticated REST requests to
+ * a single remote server. Therefore, callers who want to interact with
+ * multiple HAWK services should instantiate one HawkClient per service.
+ */
+
+this.EXPORTED_SYMBOLS = ["HawkClient"];
+
+var {interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://services-crypto/utils.js");
+Cu.import("resource://services-common/hawkrequest.js");
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// log.appender.dump should be one of "Fatal", "Error", "Warn", "Info", "Config",
+// "Debug", "Trace" or "All". If none is specified, "Error" will be used by
+// default.
+// Note however that Sync will also add this log to *its* DumpAppender, so
+// in a Sync context it shouldn't be necessary to adjust this - however, that
+// also means error logs are likely to be dump'd twice but that's OK.
+const PREF_LOG_LEVEL = "services.common.hawk.log.appender.dump";
+
+// A pref that can be set so "sensitive" information (eg, personally
+// identifiable info, credentials, etc) will be logged.
+const PREF_LOG_SENSITIVE_DETAILS = "services.common.hawk.log.sensitive";
+
+XPCOMUtils.defineLazyGetter(this, "log", function() {
+ let log = Log.repository.getLogger("Hawk");
+ // We set the log itself to "debug" and set the level from the preference to
+ // the appender. This allows other things to send the logs to different
+ // appenders, while still allowing the pref to control what is seen via dump()
+ log.level = Log.Level.Debug;
+ let appender = new Log.DumpAppender();
+ log.addAppender(appender);
+ appender.level = Log.Level.Error;
+ try {
+ let level =
+ Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
+ && Services.prefs.getCharPref(PREF_LOG_LEVEL);
+ appender.level = Log.Level[level] || Log.Level.Error;
+ } catch (e) {
+ log.error(e);
+ }
+
+ return log;
+});
+
+// A boolean to indicate if personally identifiable information (or anything
+// else sensitive, such as credentials) should be logged.
+XPCOMUtils.defineLazyGetter(this, 'logPII', function() {
+ try {
+ return Services.prefs.getBoolPref(PREF_LOG_SENSITIVE_DETAILS);
+ } catch (_) {
+ return false;
+ }
+});
+
+/*
+ * A general purpose client for making HAWK authenticated requests to a single
+ * host. Keeps track of the clock offset between the client and the host for
+ * computation of the timestamp in the HAWK Authorization header.
+ *
+ * Clients should create one HawkClient object per each server they wish to
+ * interact with.
+ *
+ * @param host
+ * The url of the host
+ */
+this.HawkClient = function(host) {
+ this.host = host;
+
+ // Clock offset in milliseconds between our client's clock and the date
+ // reported in responses from our host.
+ this._localtimeOffsetMsec = 0;
+}
+
+this.HawkClient.prototype = {
+
+ /*
+ * A boolean for feature detection.
+ */
+ willUTF8EncodeRequests: HAWKAuthenticatedRESTRequest.prototype.willUTF8EncodeObjectRequests,
+
+ /*
+ * Construct an error message for a response. Private.
+ *
+ * @param restResponse
+ * A RESTResponse object from a RESTRequest
+ *
+ * @param error
+ * A string or object describing the error
+ */
+ _constructError: function(restResponse, error) {
+ let errorObj = {
+ error: error,
+ // This object is likely to be JSON.stringify'd, but neither Error()
+ // objects nor Components.Exception objects do the right thing there,
+ // so we add a new element which is simply the .toString() version of
+ // the error object, so it does appear in JSON'd values.
+ errorString: error.toString(),
+ message: restResponse.statusText,
+ code: restResponse.status,
+ errno: restResponse.status,
+ toString() {
+ return this.code + ": " + this.message;
+ },
+ };
+ let retryAfter = restResponse.headers && restResponse.headers["retry-after"];
+ retryAfter = retryAfter ? parseInt(retryAfter) : retryAfter;
+ if (retryAfter) {
+ errorObj.retryAfter = retryAfter;
+ // and notify observers of the retry interval
+ if (this.observerPrefix) {
+ Observers.notify(this.observerPrefix + ":backoff:interval", retryAfter);
+ }
+ }
+ return errorObj;
+ },
+
+ /*
+ *
+ * Update clock offset by determining difference from date gives in the (RFC
+ * 1123) Date header of a server response. Because HAWK tolerates a window
+ * of one minute of clock skew (so two minutes total since the skew can be
+ * positive or negative), the simple method of calculating offset here is
+ * probably good enough. We keep the value in milliseconds to make life
+ * easier, even though the value will not have millisecond accuracy.
+ *
+ * @param dateString
+ * An RFC 1123 date string (e.g., "Mon, 13 Jan 2014 21:45:06 GMT")
+ *
+ * For HAWK clock skew and replay protection, see
+ * https://github.com/hueniverse/hawk#replay-protection
+ */
+ _updateClockOffset: function(dateString) {
+ try {
+ let serverDateMsec = Date.parse(dateString);
+ this._localtimeOffsetMsec = serverDateMsec - this.now();
+ log.debug("Clock offset vs " + this.host + ": " + this._localtimeOffsetMsec);
+ } catch(err) {
+ log.warn("Bad date header in server response: " + dateString);
+ }
+ },
+
+ /*
+ * Get the current clock offset in milliseconds.
+ *
+ * The offset is the number of milliseconds that must be added to the client
+ * clock to make it equal to the server clock. For example, if the client is
+ * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
+ */
+ get localtimeOffsetMsec() {
+ return this._localtimeOffsetMsec;
+ },
+
+ /*
+ * return current time in milliseconds
+ */
+ now: function() {
+ return Date.now();
+ },
+
+ /* A general method for sending raw RESTRequest calls authorized using HAWK
+ *
+ * @param path
+ * API endpoint path
+ * @param method
+ * The HTTP request method
+ * @param credentials
+ * Hawk credentials
+ * @param payloadObj
+ * An object that can be encodable as JSON as the payload of the
+ * request
+ * @param extraHeaders
+ * An object with header/value pairs to send with the request.
+ * @return Promise
+ * Returns a promise that resolves to the response of the API call,
+ * or is rejected with an error. If the server response can be parsed
+ * as JSON and contains an 'error' property, the promise will be
+ * rejected with this JSON-parsed response.
+ */
+ request: function(path, method, credentials=null, payloadObj={}, extraHeaders = {},
+ retryOK=true) {
+ method = method.toLowerCase();
+
+ let deferred = Promise.defer();
+ let uri = this.host + path;
+ let self = this;
+
+ function _onComplete(error) {
+ // |error| can be either a normal caught error or an explicitly created
+ // Components.Exception() error. Log it now as it might not end up
+ // correctly in the logs by the time it's passed through _constructError.
+ if (error) {
+ log.warn("hawk request error", error);
+ }
+ // If there's no response there's nothing else to do.
+ if (!this.response) {
+ deferred.reject(error);
+ return;
+ }
+ let restResponse = this.response;
+ let status = restResponse.status;
+
+ log.debug("(Response) " + path + ": code: " + status +
+ " - Status text: " + restResponse.statusText);
+ if (logPII) {
+ log.debug("Response text: " + restResponse.body);
+ }
+
+ // All responses may have backoff headers, which are a server-side safety
+ // valve to allow slowing down clients without hurting performance.
+ self._maybeNotifyBackoff(restResponse, "x-weave-backoff");
+ self._maybeNotifyBackoff(restResponse, "x-backoff");
+
+ if (error) {
+ // When things really blow up, reconstruct an error object that follows
+ // the general format of the server on error responses.
+ return deferred.reject(self._constructError(restResponse, error));
+ }
+
+ self._updateClockOffset(restResponse.headers["date"]);
+
+ if (status === 401 && retryOK && !("retry-after" in restResponse.headers)) {
+ // Retry once if we were rejected due to a bad timestamp.
+ // Clock offset is adjusted already in the top of this function.
+ log.debug("Received 401 for " + path + ": retrying");
+ return deferred.resolve(
+ self.request(path, method, credentials, payloadObj, extraHeaders, false));
+ }
+
+ // If the server returned a json error message, use it in the rejection
+ // of the promise.
+ //
+ // In the case of a 401, in which we are probably being rejected for a
+ // bad timestamp, retry exactly once, during which time clock offset will
+ // be adjusted.
+
+ let jsonResponse = {};
+ try {
+ jsonResponse = JSON.parse(restResponse.body);
+ } catch(notJSON) {}
+
+ let okResponse = (200 <= status && status < 300);
+ if (!okResponse || jsonResponse.error) {
+ if (jsonResponse.error) {
+ return deferred.reject(jsonResponse);
+ }
+ return deferred.reject(self._constructError(restResponse, "Request failed"));
+ }
+ // It's up to the caller to know how to decode the response.
+ // We just return the whole response.
+ deferred.resolve(this.response);
+ };
+
+ function onComplete(error) {
+ try {
+ // |this| is the RESTRequest object and we need to ensure _onComplete
+ // gets the same one.
+ _onComplete.call(this, error);
+ } catch (ex) {
+ log.error("Unhandled exception processing response", ex);
+ deferred.reject(ex);
+ }
+ }
+
+ let extra = {
+ now: this.now(),
+ localtimeOffsetMsec: this.localtimeOffsetMsec,
+ headers: extraHeaders
+ };
+
+ let request = this.newHAWKAuthenticatedRESTRequest(uri, credentials, extra);
+ try {
+ if (method == "post" || method == "put" || method == "patch") {
+ request[method](payloadObj, onComplete);
+ } else {
+ request[method](onComplete);
+ }
+ } catch (ex) {
+ log.error("Failed to make hawk request", ex);
+ deferred.reject(ex);
+ }
+
+ return deferred.promise;
+ },
+
+ /*
+ * The prefix used for all notifications sent by this module. This
+ * allows the handler of notifications to be sure they are handling
+ * notifications for the service they expect.
+ *
+ * If not set, no notifications will be sent.
+ */
+ observerPrefix: null,
+
+ // Given an optional header value, notify that a backoff has been requested.
+ _maybeNotifyBackoff: function (response, headerName) {
+ if (!this.observerPrefix || !response.headers) {
+ return;
+ }
+ let headerVal = response.headers[headerName];
+ if (!headerVal) {
+ return;
+ }
+ let backoffInterval;
+ try {
+ backoffInterval = parseInt(headerVal, 10);
+ } catch (ex) {
+ log.error("hawkclient response had invalid backoff value in '" +
+ headerName + "' header: " + headerVal);
+ return;
+ }
+ Observers.notify(this.observerPrefix + ":backoff:interval", backoffInterval);
+ },
+
+ // override points for testing.
+ newHAWKAuthenticatedRESTRequest: function(uri, credentials, extra) {
+ return new HAWKAuthenticatedRESTRequest(uri, credentials, extra);
+ },
+
+}
diff --git a/components/weave/src/common/hawkrequest.js b/components/weave/src/common/hawkrequest.js
new file mode 100644
index 000000000..454960b7b
--- /dev/null
+++ b/components/weave/src/common/hawkrequest.js
@@ -0,0 +1,198 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = [
+ "HAWKAuthenticatedRESTRequest",
+ "deriveHawkCredentials"
+];
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/rest.js");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/Credentials.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
+ "resource://services-crypto/utils.js");
+
+const Prefs = new Preferences("services.common.rest.");
+
+/**
+ * Single-use HAWK-authenticated HTTP requests to RESTish resources.
+ *
+ * @param uri
+ * (String) URI for the RESTRequest constructor
+ *
+ * @param credentials
+ * (Object) Optional credentials for computing HAWK authentication
+ * header.
+ *
+ * @param payloadObj
+ * (Object) Optional object to be converted to JSON payload
+ *
+ * @param extra
+ * (Object) Optional extra params for HAWK header computation.
+ * Valid properties are:
+ *
+ * now: <current time in milliseconds>,
+ * localtimeOffsetMsec: <local clock offset vs server>,
+ * headers: <An object with header/value pairs to be sent
+ * as headers on the request>
+ *
+ * extra.localtimeOffsetMsec is the value in milliseconds that must be added to
+ * the local clock to make it agree with the server's clock. For instance, if
+ * the local clock is two minutes ahead of the server, the time offset in
+ * milliseconds will be -120000.
+ */
+
+this.HAWKAuthenticatedRESTRequest =
+ function HawkAuthenticatedRESTRequest(uri, credentials, extra={}) {
+ RESTRequest.call(this, uri);
+
+ this.credentials = credentials;
+ this.now = extra.now || Date.now();
+ this.localtimeOffsetMsec = extra.localtimeOffsetMsec || 0;
+ this._log.trace("local time, offset: " + this.now + ", " + (this.localtimeOffsetMsec));
+ this.extraHeaders = extra.headers || {};
+
+ // Expose for testing
+ this._intl = getIntl();
+};
+HAWKAuthenticatedRESTRequest.prototype = {
+ __proto__: RESTRequest.prototype,
+
+ dispatch: function dispatch(method, data, onComplete, onProgress) {
+ let contentType = "text/plain";
+ if (method == "POST" || method == "PUT" || method == "PATCH") {
+ contentType = "application/json";
+ }
+ if (this.credentials) {
+ let options = {
+ now: this.now,
+ localtimeOffsetMsec: this.localtimeOffsetMsec,
+ credentials: this.credentials,
+ payload: data && JSON.stringify(data) || "",
+ contentType: contentType,
+ };
+ let header = CryptoUtils.computeHAWK(this.uri, method, options);
+ this.setHeader("Authorization", header.field);
+ this._log.trace("hawk auth header: " + header.field);
+ }
+
+ for (let header in this.extraHeaders) {
+ this.setHeader(header, this.extraHeaders[header]);
+ }
+
+ this.setHeader("Content-Type", contentType);
+
+ this.setHeader("Accept-Language", this._intl.accept_languages);
+
+ return RESTRequest.prototype.dispatch.call(
+ this, method, data, onComplete, onProgress
+ );
+ }
+};
+
+
+/**
+ * Generic function to derive Hawk credentials.
+ *
+ * Hawk credentials are derived using shared secrets, which depend on the token
+ * in use.
+ *
+ * @param tokenHex
+ * The current session token encoded in hex
+ * @param context
+ * A context for the credentials. A protocol version will be prepended
+ * to the context, see Credentials.keyWord for more information.
+ * @param size
+ * The size in bytes of the expected derived buffer,
+ * defaults to 3 * 32.
+ * @return credentials
+ * Returns an object:
+ * {
+ * algorithm: sha256
+ * id: the Hawk id (from the first 32 bytes derived)
+ * key: the Hawk key (from bytes 32 to 64)
+ * extra: size - 64 extra bytes (if size > 64)
+ * }
+ */
+this.deriveHawkCredentials = function deriveHawkCredentials(tokenHex,
+ context,
+ size = 96,
+ hexKey = false) {
+ let token = CommonUtils.hexToBytes(tokenHex);
+ let out = CryptoUtils.hkdf(token, undefined, Credentials.keyWord(context), size);
+
+ let result = {
+ algorithm: "sha256",
+ key: hexKey ? CommonUtils.bytesAsHex(out.slice(32, 64)) : out.slice(32, 64),
+ id: CommonUtils.bytesAsHex(out.slice(0, 32))
+ };
+ if (size > 64) {
+ result.extra = out.slice(64);
+ }
+
+ return result;
+}
+
+// With hawk request, we send the user's accepted-languages with each request.
+// To keep the number of times we read this pref at a minimum, maintain the
+// preference in a stateful object that notices and updates itself when the
+// pref is changed.
+this.Intl = function Intl() {
+ // We won't actually query the pref until the first time we need it
+ this._accepted = "";
+ this._everRead = false;
+ this._log = Log.repository.getLogger("Services.common.RESTRequest");
+ this._log.level = Log.Level[Prefs.get("log.logger.rest.request")];
+ this.init();
+};
+
+this.Intl.prototype = {
+ init: function() {
+ Services.prefs.addObserver("intl.accept_languages", this, false);
+ },
+
+ uninit: function() {
+ Services.prefs.removeObserver("intl.accept_languages", this);
+ },
+
+ observe: function(subject, topic, data) {
+ this.readPref();
+ },
+
+ readPref: function() {
+ this._everRead = true;
+ try {
+ this._accepted = Services.prefs.getComplexValue(
+ "intl.accept_languages", Ci.nsIPrefLocalizedString).data;
+ } catch (err) {
+ this._log.error("Error reading intl.accept_languages pref", err);
+ }
+ },
+
+ get accept_languages() {
+ if (!this._everRead) {
+ this.readPref();
+ }
+ return this._accepted;
+ },
+};
+
+// Singleton getter for Intl, creating an instance only when we first need it.
+var intl = null;
+function getIntl() {
+ if (!intl) {
+ intl = new Intl();
+ }
+ return intl;
+}
+
diff --git a/components/weave/src/common/logmanager.js b/components/weave/src/common/logmanager.js
new file mode 100644
index 000000000..17e47f9e3
--- /dev/null
+++ b/components/weave/src/common/logmanager.js
@@ -0,0 +1,331 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Log",
+ "resource://gre/modules/Log.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
+ "resource://services-common/utils.js");
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+this.EXPORTED_SYMBOLS = [
+ "LogManager",
+];
+
+const DEFAULT_MAX_ERROR_AGE = 20 * 24 * 60 * 60; // 20 days
+
+// "shared" logs (ie, where the same log name is used by multiple LogManager
+// instances) are a fact of life here - eg, FirefoxAccounts logs are used by
+// both Sync and Reading List.
+// However, different instances have different pref branches, so we need to
+// handle when one pref branch says "Debug" and the other says "Error"
+// So we (a) keep singleton console and dump appenders and (b) keep track
+// of the minimum (ie, most verbose) level and use that.
+// This avoids (a) the most recent setter winning (as that is indeterminate)
+// and (b) multiple dump/console appenders being added to the same log multiple
+// times, which would cause messages to appear twice.
+
+// Singletons used by each instance.
+var formatter;
+var dumpAppender;
+var consoleAppender;
+
+// A set of all preference roots used by all instances.
+var allBranches = new Set();
+
+// A storage appender that is flushable to a file on disk. Policies for
+// when to flush, to what file, log rotation etc are up to the consumer
+// (although it does maintain a .sawError property to help the consumer decide
+// based on its policies)
+function FlushableStorageAppender(formatter) {
+ Log.StorageStreamAppender.call(this, formatter);
+ this.sawError = false;
+}
+
+FlushableStorageAppender.prototype = {
+ __proto__: Log.StorageStreamAppender.prototype,
+
+ append(message) {
+ if (message.level >= Log.Level.Error) {
+ this.sawError = true;
+ }
+ Log.StorageStreamAppender.prototype.append.call(this, message);
+ },
+
+ reset() {
+ Log.StorageStreamAppender.prototype.reset.call(this);
+ this.sawError = false;
+ },
+
+ // Flush the current stream to a file. Somewhat counter-intuitively, you
+ // must pass a log which will be written to with details of the operation.
+ flushToFile: Task.async(function* (subdirArray, filename, log) {
+ let inStream = this.getInputStream();
+ this.reset();
+ if (!inStream) {
+ log.debug("Failed to flush log to a file - no input stream");
+ return;
+ }
+ log.debug("Flushing file log");
+ log.trace("Beginning stream copy to " + filename + ": " + Date.now());
+ try {
+ yield this._copyStreamToFile(inStream, subdirArray, filename, log);
+ log.trace("onCopyComplete", Date.now());
+ } catch (ex) {
+ log.error("Failed to copy log stream to file", ex);
+ }
+ }),
+
+ /**
+ * Copy an input stream to the named file, doing everything off the main
+ * thread.
+ * subDirArray is an array of path components, relative to the profile
+ * directory, where the file will be created.
+ * outputFileName is the filename to create.
+ * Returns a promise that is resolved on completion or rejected with an error.
+ */
+ _copyStreamToFile: Task.async(function* (inputStream, subdirArray, outputFileName, log) {
+ // The log data could be large, so we don't want to pass it all in a single
+ // message, so use BUFFER_SIZE chunks.
+ const BUFFER_SIZE = 8192;
+
+ // get a binary stream
+ let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
+ binaryStream.setInputStream(inputStream);
+
+ let outputDirectory = OS.Path.join(OS.Constants.Path.profileDir, ...subdirArray);
+ yield OS.File.makeDir(outputDirectory, { ignoreExisting: true, from: OS.Constants.Path.profileDir });
+ let fullOutputFileName = OS.Path.join(outputDirectory, outputFileName);
+ let output = yield OS.File.open(fullOutputFileName, { write: true} );
+ try {
+ while (true) {
+ let available = binaryStream.available();
+ if (!available) {
+ break;
+ }
+ let chunk = binaryStream.readByteArray(Math.min(available, BUFFER_SIZE));
+ yield output.write(new Uint8Array(chunk));
+ }
+ } finally {
+ try {
+ binaryStream.close(); // inputStream is closed by the binaryStream
+ yield output.close();
+ } catch (ex) {
+ log.error("Failed to close the input stream", ex);
+ }
+ }
+ log.trace("finished copy to", fullOutputFileName);
+ }),
+}
+
+// The public LogManager object.
+function LogManager(prefRoot, logNames, logFilePrefix) {
+ this._prefObservers = [];
+ this.init(prefRoot, logNames, logFilePrefix);
+}
+
+LogManager.prototype = {
+ _cleaningUpFileLogs: false,
+
+ init(prefRoot, logNames, logFilePrefix) {
+ if (prefRoot instanceof Preferences) {
+ this._prefs = prefRoot;
+ } else {
+ this._prefs = new Preferences(prefRoot);
+ }
+
+ this.logFilePrefix = logFilePrefix;
+ if (!formatter) {
+ // Create a formatter and various appenders to attach to the logs.
+ formatter = new Log.BasicFormatter();
+ consoleAppender = new Log.ConsoleAppender(formatter);
+ dumpAppender = new Log.DumpAppender(formatter);
+ }
+
+ allBranches.add(this._prefs._branchStr);
+ // We create a preference observer for all our prefs so they are magically
+ // reflected if the pref changes after creation.
+ let setupAppender = (appender, prefName, defaultLevel, findSmallest = false) => {
+ let observer = newVal => {
+ let level = Log.Level[newVal] || defaultLevel;
+ if (findSmallest) {
+ // As some of our appenders have global impact (ie, there is only one
+ // place 'dump' goes to), we need to find the smallest value from all
+ // prefs controlling this appender.
+ // For example, if consumerA has dump=Debug then consumerB sets
+ // dump=Error, we need to keep dump=Debug so consumerA is respected.
+ for (let branch of allBranches) {
+ let lookPrefBranch = new Preferences(branch);
+ let lookVal = Log.Level[lookPrefBranch.get(prefName)];
+ if (lookVal && lookVal < level) {
+ level = lookVal;
+ }
+ }
+ }
+ appender.level = level;
+ }
+ this._prefs.observe(prefName, observer, this);
+ this._prefObservers.push([prefName, observer]);
+ // and call the observer now with the current pref value.
+ observer(this._prefs.get(prefName));
+ return observer;
+ }
+
+ this._observeConsolePref = setupAppender(consoleAppender, "log.appender.console", Log.Level.Fatal, true);
+ this._observeDumpPref = setupAppender(dumpAppender, "log.appender.dump", Log.Level.Error, true);
+
+ // The file appender doesn't get the special singleton behaviour.
+ let fapp = this._fileAppender = new FlushableStorageAppender(formatter);
+ // the stream gets a default of Debug as the user must go out of their way
+ // to see the stuff spewed to it.
+ this._observeStreamPref = setupAppender(fapp, "log.appender.file.level", Log.Level.Debug);
+
+ // now attach the appenders to all our logs.
+ for (let logName of logNames) {
+ let log = Log.repository.getLogger(logName);
+ for (let appender of [fapp, dumpAppender, consoleAppender]) {
+ log.addAppender(appender);
+ }
+ }
+ // and use the first specified log as a "root" for our log.
+ this._log = Log.repository.getLogger(logNames[0] + ".LogManager");
+ },
+
+ /**
+ * Cleanup this instance
+ */
+ finalize() {
+ for (let [name, pref] of this._prefObservers) {
+ this._prefs.ignore(name, pref, this);
+ }
+ this._prefObservers = [];
+ try {
+ allBranches.delete(this._prefs._branchStr);
+ } catch (e) {}
+ this._prefs = null;
+ },
+
+ get _logFileSubDirectoryEntries() {
+ // At this point we don't allow a custom directory for the logs, nor allow
+ // it to be outside the profile directory.
+ // This returns an array of the the relative directory entries below the
+ // profile dir, and is the directory about:sync-log uses.
+ return ["weave", "logs"];
+ },
+
+ get sawError() {
+ return this._fileAppender.sawError;
+ },
+
+ // Result values for resetFileLog.
+ SUCCESS_LOG_WRITTEN: "success-log-written",
+ ERROR_LOG_WRITTEN: "error-log-written",
+
+ /**
+ * Possibly generate a log file for all accumulated log messages and refresh
+ * the input & output streams.
+ * Whether a "success" or "error" log is written is determined based on
+ * whether an "Error" log entry was written to any of the logs.
+ * Returns a promise that resolves on completion with either null (for no
+ * file written or on error), SUCCESS_LOG_WRITTEN if a "success" log was
+ * written, or ERROR_LOG_WRITTEN if an "error" log was written.
+ */
+ resetFileLog: Task.async(function* () {
+ try {
+ let flushToFile;
+ let reasonPrefix;
+ let reason;
+ if (this._fileAppender.sawError) {
+ reason = this.ERROR_LOG_WRITTEN;
+ flushToFile = this._prefs.get("log.appender.file.logOnError", true);
+ reasonPrefix = "error";
+ } else {
+ reason = this.SUCCESS_LOG_WRITTEN;
+ flushToFile = this._prefs.get("log.appender.file.logOnSuccess", false);
+ reasonPrefix = "success";
+ }
+
+ // might as well avoid creating an input stream if we aren't going to use it.
+ if (!flushToFile) {
+ this._fileAppender.reset();
+ return null;
+ }
+
+ // We have reasonPrefix at the start of the filename so all "error"
+ // logs are grouped in about:sync-log.
+ let filename = reasonPrefix + "-" + this.logFilePrefix + "-" + Date.now() + ".txt";
+ yield this._fileAppender.flushToFile(this._logFileSubDirectoryEntries, filename, this._log);
+
+ // It's not completely clear to markh why we only do log cleanups
+ // for errors, but for now the Sync semantics have been copied...
+ // (one theory is that only cleaning up on error makes it less
+ // likely old error logs would be removed, but that's not true if
+ // there are occasional errors - let's address this later!)
+ if (reason == this.ERROR_LOG_WRITTEN && !this._cleaningUpFileLogs) {
+ this._log.trace("Scheduling cleanup.");
+ // Note we don't return/yield or otherwise wait on this promise - it
+ // continues in the background
+ this.cleanupLogs().catch(err => {
+ this._log.error("Failed to cleanup logs", err);
+ });
+ }
+ return reason;
+ } catch (ex) {
+ this._log.error("Failed to resetFileLog", ex);
+ return null;
+ }
+ }),
+
+ /**
+ * Finds all logs older than maxErrorAge and deletes them using async I/O.
+ */
+ cleanupLogs: Task.async(function* () {
+ this._cleaningUpFileLogs = true;
+ let logDir = FileUtils.getDir("ProfD", this._logFileSubDirectoryEntries);
+ let iterator = new OS.File.DirectoryIterator(logDir.path);
+ let maxAge = this._prefs.get("log.appender.file.maxErrorAge", DEFAULT_MAX_ERROR_AGE);
+ let threshold = Date.now() - 1000 * maxAge;
+
+ this._log.debug("Log cleanup threshold time: " + threshold);
+ yield iterator.forEach(Task.async(function* (entry) {
+ // Note that we don't check this.logFilePrefix is in the name - we cleanup
+ // all files in this directory regardless of that prefix so old logfiles
+ // for prefixes no longer in use are still cleaned up. See bug 1279145.
+ if (!entry.name.startsWith("error-") &&
+ !entry.name.startsWith("success-")) {
+ return;
+ }
+ try {
+ // need to call .stat() as the enumerator doesn't give that to us on *nix.
+ let info = yield OS.File.stat(entry.path);
+ if (info.lastModificationDate.getTime() >= threshold) {
+ return;
+ }
+ this._log.trace(" > Cleanup removing " + entry.name +
+ " (" + info.lastModificationDate.getTime() + ")");
+ yield OS.File.remove(entry.path);
+ this._log.trace("Deleted " + entry.name);
+ } catch (ex) {
+ this._log.debug("Encountered error trying to clean up old log file "
+ + entry.name, ex);
+ }
+ }.bind(this)));
+ iterator.close();
+ this._cleaningUpFileLogs = false;
+ this._log.debug("Done deleting files.");
+ // This notification is used only for tests.
+ Services.obs.notifyObservers(null, "services-tests:common:log-manager:cleanup-logs", null);
+ }),
+}
diff --git a/components/weave/src/common/observers.js b/components/weave/src/common/observers.js
new file mode 100644
index 000000000..c0b771048
--- /dev/null
+++ b/components/weave/src/common/observers.js
@@ -0,0 +1,150 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["Observers"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * A service for adding, removing and notifying observers of notifications.
+ * Wraps the nsIObserverService interface.
+ *
+ * @version 0.2
+ */
+this.Observers = {
+ /**
+ * Register the given callback as an observer of the given topic.
+ *
+ * @param topic {String}
+ * the topic to observe
+ *
+ * @param callback {Object}
+ * the callback; an Object that implements nsIObserver or a Function
+ * that gets called when the notification occurs
+ *
+ * @param thisObject {Object} [optional]
+ * the object to use as |this| when calling a Function callback
+ *
+ * @returns the observer
+ */
+ add: function(topic, callback, thisObject) {
+ let observer = new Observer(topic, callback, thisObject);
+ this._cache.push(observer);
+ this._service.addObserver(observer, topic, true);
+
+ return observer;
+ },
+
+ /**
+ * Unregister the given callback as an observer of the given topic.
+ *
+ * @param topic {String}
+ * the topic being observed
+ *
+ * @param callback {Object}
+ * the callback doing the observing
+ *
+ * @param thisObject {Object} [optional]
+ * the object being used as |this| when calling a Function callback
+ */
+ remove: function(topic, callback, thisObject) {
+ // This seems fairly inefficient, but I'm not sure how much better
+ // we can make it. We could index by topic, but we can't index by callback
+ // or thisObject, as far as I know, since the keys to JavaScript hashes
+ // (a.k.a. objects) can apparently only be primitive values.
+ let [observer] = this._cache.filter(v => v.topic == topic &&
+ v.callback == callback &&
+ v.thisObject == thisObject);
+ if (observer) {
+ this._service.removeObserver(observer, topic);
+ this._cache.splice(this._cache.indexOf(observer), 1);
+ }
+ },
+
+ /**
+ * Notify observers about something.
+ *
+ * @param topic {String}
+ * the topic to notify observers about
+ *
+ * @param subject {Object} [optional]
+ * some information about the topic; can be any JS object or primitive
+ *
+ * @param data {String} [optional] [deprecated]
+ * some more information about the topic; deprecated as the subject
+ * is sufficient to pass all needed information to the JS observers
+ * that this module targets; if you have multiple values to pass to
+ * the observer, wrap them in an object and pass them via the subject
+ * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
+ */
+ notify: function(topic, subject, data) {
+ subject = (typeof subject == "undefined") ? null : new Subject(subject);
+ data = (typeof data == "undefined") ? null : data;
+ this._service.notifyObservers(subject, topic, data);
+ },
+
+ _service: Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService),
+
+ /**
+ * A cache of observers that have been added.
+ *
+ * We use this to remove observers when a caller calls |remove|.
+ *
+ * XXX This might result in reference cycles, causing memory leaks,
+ * if we hold a reference to an observer that holds a reference to us.
+ * Could we fix that by making this an independent top-level object
+ * rather than a property of this object?
+ */
+ _cache: []
+};
+
+
+function Observer(topic, callback, thisObject) {
+ this.topic = topic;
+ this.callback = callback;
+ this.thisObject = thisObject;
+}
+
+Observer.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
+ observe: function(subject, topic, data) {
+ // Extract the wrapped object for subjects that are one of our wrappers
+ // around a JS object. This way we support both wrapped subjects created
+ // using this module and those that are real XPCOM components.
+ if (subject && typeof subject == "object" &&
+ ("wrappedJSObject" in subject) &&
+ ("observersModuleSubjectWrapper" in subject.wrappedJSObject))
+ subject = subject.wrappedJSObject.object;
+
+ if (typeof this.callback == "function") {
+ if (this.thisObject)
+ this.callback.call(this.thisObject, subject, data);
+ else
+ this.callback(subject, data);
+ }
+ else // typeof this.callback == "object" (nsIObserver)
+ this.callback.observe(subject, topic, data);
+ }
+}
+
+
+function Subject(object) {
+ // Double-wrap the object and set a property identifying the wrappedJSObject
+ // as one of our wrappers to distinguish between subjects that are one of our
+ // wrappers (which we should unwrap when notifying our observers) and those
+ // that are real JS XPCOM components (which we should pass through unaltered).
+ this.wrappedJSObject = { observersModuleSubjectWrapper: true, object: object };
+}
+
+Subject.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([]),
+ getScriptableHelper: function() {},
+ getInterfaces: function() {}
+};
diff --git a/components/weave/src/common/rest.js b/components/weave/src/common/rest.js
new file mode 100644
index 000000000..5474dd947
--- /dev/null
+++ b/components/weave/src/common/rest.js
@@ -0,0 +1,764 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+
+this.EXPORTED_SYMBOLS = [
+ "RESTRequest",
+ "RESTResponse",
+ "TokenAuthenticatedRESTRequest",
+];
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/utils.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
+ "resource://services-crypto/utils.js");
+
+const Prefs = new Preferences("services.common.");
+
+/**
+ * Single use HTTP requests to RESTish resources.
+ *
+ * @param uri
+ * URI for the request. This can be an nsIURI object or a string
+ * that can be used to create one. An exception will be thrown if
+ * the string is not a valid URI.
+ *
+ * Examples:
+ *
+ * (1) Quick GET request:
+ *
+ * new RESTRequest("http://server/rest/resource").get(function (error) {
+ * if (error) {
+ * // Deal with a network error.
+ * processNetworkErrorCode(error.result);
+ * return;
+ * }
+ * if (!this.response.success) {
+ * // Bail out if we're not getting an HTTP 2xx code.
+ * processHTTPError(this.response.status);
+ * return;
+ * }
+ * processData(this.response.body);
+ * });
+ *
+ * (2) Quick PUT request (non-string data is automatically JSONified)
+ *
+ * new RESTRequest("http://server/rest/resource").put(data, function (error) {
+ * ...
+ * });
+ *
+ * (3) Streaming GET
+ *
+ * let request = new RESTRequest("http://server/rest/resource");
+ * request.setHeader("Accept", "application/newlines");
+ * request.onComplete = function (error) {
+ * if (error) {
+ * // Deal with a network error.
+ * processNetworkErrorCode(error.result);
+ * return;
+ * }
+ * callbackAfterRequestHasCompleted()
+ * });
+ * request.onProgress = function () {
+ * if (!this.response.success) {
+ * // Bail out if we're not getting an HTTP 2xx code.
+ * return;
+ * }
+ * // Process body data and reset it so we don't process the same data twice.
+ * processIncrementalData(this.response.body);
+ * this.response.body = "";
+ * });
+ * request.get();
+ */
+this.RESTRequest = function RESTRequest(uri) {
+ this.status = this.NOT_SENT;
+
+ // If we don't have an nsIURI object yet, make one. This will throw if
+ // 'uri' isn't a valid URI string.
+ if (!(uri instanceof Ci.nsIURI)) {
+ uri = Services.io.newURI(uri, null, null);
+ }
+ this.uri = uri;
+
+ this._headers = {};
+ this._log = Log.repository.getLogger(this._logName);
+ this._log.level =
+ Log.Level[Prefs.get("log.logger.rest.request")];
+}
+RESTRequest.prototype = {
+
+ _logName: "Services.Common.RESTRequest",
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIBadCertListener2,
+ Ci.nsIInterfaceRequestor,
+ Ci.nsIChannelEventSink
+ ]),
+
+ /*** Public API: ***/
+
+ /**
+ * A constant boolean that indicates whether this object will automatically
+ * utf-8 encode request bodies passed as an object. Used for feature detection
+ * so, eg, loop can use the same source code for old and new Firefox versions.
+ */
+ willUTF8EncodeObjectRequests: true,
+
+ /**
+ * URI for the request (an nsIURI object).
+ */
+ uri: null,
+
+ /**
+ * HTTP method (e.g. "GET")
+ */
+ method: null,
+
+ /**
+ * RESTResponse object
+ */
+ response: null,
+
+ /**
+ * nsIRequest load flags. Don't do any caching by default. Don't send user
+ * cookies and such over the wire (Bug 644734).
+ */
+ loadFlags: Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING | Ci.nsIRequest.LOAD_ANONYMOUS,
+
+ /**
+ * nsIHttpChannel
+ */
+ channel: null,
+
+ /**
+ * Flag to indicate the status of the request.
+ *
+ * One of NOT_SENT, SENT, IN_PROGRESS, COMPLETED, ABORTED.
+ */
+ status: null,
+
+ NOT_SENT: 0,
+ SENT: 1,
+ IN_PROGRESS: 2,
+ COMPLETED: 4,
+ ABORTED: 8,
+
+ /**
+ * HTTP status text of response
+ */
+ statusText: null,
+
+ /**
+ * Request timeout (in seconds, though decimal values can be used for
+ * up to millisecond granularity.)
+ *
+ * 0 for no timeout.
+ */
+ timeout: null,
+
+ /**
+ * The encoding with which the response to this request must be treated.
+ * If a charset parameter is available in the HTTP Content-Type header for
+ * this response, that will always be used, and this value is ignored. We
+ * default to UTF-8 because that is a reasonable default.
+ */
+ charset: "utf-8",
+
+ /**
+ * Called when the request has been completed, including failures and
+ * timeouts.
+ *
+ * @param error
+ * Error that occurred while making the request, null if there
+ * was no error.
+ */
+ onComplete: function onComplete(error) {
+ },
+
+ /**
+ * Called whenever data is being received on the channel. If this throws an
+ * exception, the request is aborted and the exception is passed as the
+ * error to onComplete().
+ */
+ onProgress: function onProgress() {
+ },
+
+ /**
+ * Set a request header.
+ */
+ setHeader: function setHeader(name, value) {
+ this._headers[name.toLowerCase()] = value;
+ },
+
+ /**
+ * Perform an HTTP GET.
+ *
+ * @param onComplete
+ * Short-circuit way to set the 'onComplete' method. Optional.
+ * @param onProgress
+ * Short-circuit way to set the 'onProgress' method. Optional.
+ *
+ * @return the request object.
+ */
+ get: function get(onComplete, onProgress) {
+ return this.dispatch("GET", null, onComplete, onProgress);
+ },
+
+ /**
+ * Perform an HTTP PATCH.
+ *
+ * @param data
+ * Data to be used as the request body. If this isn't a string
+ * it will be JSONified automatically.
+ * @param onComplete
+ * Short-circuit way to set the 'onComplete' method. Optional.
+ * @param onProgress
+ * Short-circuit way to set the 'onProgress' method. Optional.
+ *
+ * @return the request object.
+ */
+ patch: function patch(data, onComplete, onProgress) {
+ return this.dispatch("PATCH", data, onComplete, onProgress);
+ },
+
+ /**
+ * Perform an HTTP PUT.
+ *
+ * @param data
+ * Data to be used as the request body. If this isn't a string
+ * it will be JSONified automatically.
+ * @param onComplete
+ * Short-circuit way to set the 'onComplete' method. Optional.
+ * @param onProgress
+ * Short-circuit way to set the 'onProgress' method. Optional.
+ *
+ * @return the request object.
+ */
+ put: function put(data, onComplete, onProgress) {
+ return this.dispatch("PUT", data, onComplete, onProgress);
+ },
+
+ /**
+ * Perform an HTTP POST.
+ *
+ * @param data
+ * Data to be used as the request body. If this isn't a string
+ * it will be JSONified automatically.
+ * @param onComplete
+ * Short-circuit way to set the 'onComplete' method. Optional.
+ * @param onProgress
+ * Short-circuit way to set the 'onProgress' method. Optional.
+ *
+ * @return the request object.
+ */
+ post: function post(data, onComplete, onProgress) {
+ return this.dispatch("POST", data, onComplete, onProgress);
+ },
+
+ /**
+ * Perform an HTTP DELETE.
+ *
+ * @param onComplete
+ * Short-circuit way to set the 'onComplete' method. Optional.
+ * @param onProgress
+ * Short-circuit way to set the 'onProgress' method. Optional.
+ *
+ * @return the request object.
+ */
+ delete: function delete_(onComplete, onProgress) {
+ return this.dispatch("DELETE", null, onComplete, onProgress);
+ },
+
+ /**
+ * Abort an active request.
+ */
+ abort: function abort() {
+ if (this.status != this.SENT && this.status != this.IN_PROGRESS) {
+ throw "Can only abort a request that has been sent.";
+ }
+
+ this.status = this.ABORTED;
+ this.channel.cancel(Cr.NS_BINDING_ABORTED);
+
+ if (this.timeoutTimer) {
+ // Clear the abort timer now that the channel is done.
+ this.timeoutTimer.clear();
+ }
+ },
+
+ /*** Implementation stuff ***/
+
+ dispatch: function dispatch(method, data, onComplete, onProgress) {
+ if (this.status != this.NOT_SENT) {
+ throw "Request has already been sent!";
+ }
+
+ this.method = method;
+ if (onComplete) {
+ this.onComplete = onComplete;
+ }
+ if (onProgress) {
+ this.onProgress = onProgress;
+ }
+
+ // Create and initialize HTTP channel.
+ let channel = NetUtil.newChannel({uri: this.uri, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIRequest)
+ .QueryInterface(Ci.nsIHttpChannel);
+ this.channel = channel;
+ channel.loadFlags |= this.loadFlags;
+ channel.notificationCallbacks = this;
+
+ this._log.debug(`${method} request to ${this.uri.spec}`);
+ // Set request headers.
+ let headers = this._headers;
+ for (let key in headers) {
+ if (key == 'authorization') {
+ this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
+ } else {
+ this._log.trace("HTTP Header " + key + ": " + headers[key]);
+ }
+ channel.setRequestHeader(key, headers[key], false);
+ }
+
+ // Set HTTP request body.
+ if (method == "PUT" || method == "POST" || method == "PATCH") {
+ // Convert non-string bodies into JSON with utf-8 encoding. If a string
+ // is passed we assume they've already encoded it.
+ let contentType = headers["content-type"];
+ if (typeof data != "string") {
+ data = JSON.stringify(data);
+ if (!contentType) {
+ contentType = "application/json";
+ }
+ if (!contentType.includes("charset")) {
+ data = CommonUtils.encodeUTF8(data);
+ contentType += "; charset=utf-8";
+ } else {
+ // If someone handed us an object but also a custom content-type
+ // it's probably confused. We could go to even further lengths to
+ // respect it, but this shouldn't happen in practice.
+ Cu.reportError("rest.js found an object to JSON.stringify but also a " +
+ "content-type header with a charset specification. " +
+ "This probably isn't going to do what you expect");
+ }
+ }
+ if (!contentType) {
+ contentType = "text/plain";
+ }
+
+ this._log.debug(method + " Length: " + data.length);
+ if (this._log.level <= Log.Level.Trace) {
+ this._log.trace(method + " Body: " + data);
+ }
+
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.setData(data, data.length);
+
+ channel.QueryInterface(Ci.nsIUploadChannel);
+ channel.setUploadStream(stream, contentType, data.length);
+ }
+ // We must set this after setting the upload stream, otherwise it
+ // will always be 'PUT'. Yeah, I know.
+ channel.requestMethod = method;
+
+ // Before opening the channel, set the charset that serves as a hint
+ // as to what the response might be encoded as.
+ channel.contentCharset = this.charset;
+
+ // Blast off!
+ try {
+ channel.asyncOpen2(this);
+ } catch (ex) {
+ // asyncOpen can throw in a bunch of cases -- e.g., a forbidden port.
+ this._log.warn("Caught an error in asyncOpen", ex);
+ CommonUtils.nextTick(onComplete.bind(this, ex));
+ }
+ this.status = this.SENT;
+ this.delayTimeout();
+ return this;
+ },
+
+ /**
+ * Create or push back the abort timer that kills this request.
+ */
+ delayTimeout: function delayTimeout() {
+ if (this.timeout) {
+ CommonUtils.namedTimer(this.abortTimeout, this.timeout * 1000, this,
+ "timeoutTimer");
+ }
+ },
+
+ /**
+ * Abort the request based on a timeout.
+ */
+ abortTimeout: function abortTimeout() {
+ this.abort();
+ let error = Components.Exception("Aborting due to channel inactivity.",
+ Cr.NS_ERROR_NET_TIMEOUT);
+ if (!this.onComplete) {
+ this._log.error("Unexpected error: onComplete not defined in " +
+ "abortTimeout.");
+ return;
+ }
+ this.onComplete(error);
+ },
+
+ /*** nsIStreamListener ***/
+
+ onStartRequest: function onStartRequest(channel) {
+ if (this.status == this.ABORTED) {
+ this._log.trace("Not proceeding with onStartRequest, request was aborted.");
+ return;
+ }
+
+ try {
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (ex) {
+ this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
+ this.status = this.ABORTED;
+ channel.cancel(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ this.status = this.IN_PROGRESS;
+
+ this._log.trace("onStartRequest: " + channel.requestMethod + " " +
+ channel.URI.spec);
+
+ // Create a response object and fill it with some data.
+ let response = this.response = new RESTResponse();
+ response.request = this;
+ response.body = "";
+
+ this.delayTimeout();
+ },
+
+ onStopRequest: function onStopRequest(channel, context, statusCode) {
+ if (this.timeoutTimer) {
+ // Clear the abort timer now that the channel is done.
+ this.timeoutTimer.clear();
+ }
+
+ // We don't want to do anything for a request that's already been aborted.
+ if (this.status == this.ABORTED) {
+ this._log.trace("Not proceeding with onStopRequest, request was aborted.");
+ return;
+ }
+
+ try {
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (ex) {
+ this._log.error("Unexpected error: channel not nsIHttpChannel!");
+ this.status = this.ABORTED;
+ return;
+ }
+ this.status = this.COMPLETED;
+
+ let statusSuccess = Components.isSuccessCode(statusCode);
+ let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
+ this._log.trace("Channel for " + channel.requestMethod + " " + uri +
+ " returned status code " + statusCode);
+
+ if (!this.onComplete) {
+ this._log.error("Unexpected error: onComplete not defined in " +
+ "abortRequest.");
+ this.onProgress = null;
+ return;
+ }
+
+ // Throw the failure code and stop execution. Use Components.Exception()
+ // instead of Error() so the exception is QI-able and can be passed across
+ // XPCOM borders while preserving the status code.
+ if (!statusSuccess) {
+ let message = Components.Exception("", statusCode).name;
+ let error = Components.Exception(message, statusCode);
+ this._log.debug(this.method + " " + uri + " failed: " + statusCode + " - " + message);
+ this.onComplete(error);
+ this.onComplete = this.onProgress = null;
+ return;
+ }
+
+ this._log.debug(this.method + " " + uri + " " + this.response.status);
+
+ // Additionally give the full response body when Trace logging.
+ if (this._log.level <= Log.Level.Trace) {
+ this._log.trace(this.method + " body: " + this.response.body);
+ }
+
+ delete this._inputStream;
+
+ this.onComplete(null);
+ this.onComplete = this.onProgress = null;
+ },
+
+ onDataAvailable: function onDataAvailable(channel, cb, stream, off, count) {
+ // We get an nsIRequest, which doesn't have contentCharset.
+ try {
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (ex) {
+ this._log.error("Unexpected error: channel not nsIHttpChannel!");
+ this.abort();
+
+ if (this.onComplete) {
+ this.onComplete(ex);
+ }
+
+ this.onComplete = this.onProgress = null;
+ return;
+ }
+
+ if (channel.contentCharset) {
+ this.response.charset = channel.contentCharset;
+
+ if (!this._converterStream) {
+ this._converterStream = Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Ci.nsIConverterInputStream);
+ }
+
+ this._converterStream.init(stream, channel.contentCharset, 0,
+ this._converterStream.DEFAULT_REPLACEMENT_CHARACTER);
+
+ try {
+ let str = {};
+ let num = this._converterStream.readString(count, str);
+ if (num != 0) {
+ this.response.body += str.value;
+ }
+ } catch (ex) {
+ this._log.warn("Exception thrown reading " + count + " bytes from " +
+ "the channel", ex);
+ throw ex;
+ }
+ } else {
+ this.response.charset = null;
+
+ if (!this._inputStream) {
+ this._inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ }
+
+ this._inputStream.init(stream);
+
+ this.response.body += this._inputStream.read(count);
+ }
+
+ try {
+ this.onProgress();
+ } catch (ex) {
+ this._log.warn("Got exception calling onProgress handler, aborting " +
+ this.method + " " + channel.URI.spec, ex);
+ this.abort();
+
+ if (!this.onComplete) {
+ this._log.error("Unexpected error: onComplete not defined in " +
+ "onDataAvailable.");
+ this.onProgress = null;
+ return;
+ }
+
+ this.onComplete(ex);
+ this.onComplete = this.onProgress = null;
+ return;
+ }
+
+ this.delayTimeout();
+ },
+
+ /*** nsIInterfaceRequestor ***/
+
+ getInterface: function(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ /*** nsIBadCertListener2 ***/
+
+ notifyCertProblem: function notifyCertProblem(socketInfo, sslStatus, targetHost) {
+ this._log.warn("Invalid HTTPS certificate encountered!");
+ // Suppress invalid HTTPS certificate warnings in the UI.
+ // (The request will still fail.)
+ return true;
+ },
+
+ /**
+ * Returns true if headers from the old channel should be
+ * copied to the new channel. Invoked when a channel redirect
+ * is in progress.
+ */
+ shouldCopyOnRedirect: function shouldCopyOnRedirect(oldChannel, newChannel, flags) {
+ let isInternal = !!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL);
+ let isSameURI = newChannel.URI.equals(oldChannel.URI);
+ this._log.debug("Channel redirect: " + oldChannel.URI.spec + ", " +
+ newChannel.URI.spec + ", internal = " + isInternal);
+ return isInternal && isSameURI;
+ },
+
+ /*** nsIChannelEventSink ***/
+ asyncOnChannelRedirect:
+ function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
+
+ let oldSpec = (oldChannel && oldChannel.URI) ? oldChannel.URI.spec : "<undefined>";
+ let newSpec = (newChannel && newChannel.URI) ? newChannel.URI.spec : "<undefined>";
+ this._log.debug("Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags);
+
+ try {
+ newChannel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (ex) {
+ this._log.error("Unexpected error: channel not nsIHttpChannel!");
+ callback.onRedirectVerifyCallback(Cr.NS_ERROR_NO_INTERFACE);
+ return;
+ }
+
+ // For internal redirects, copy the headers that our caller set.
+ try {
+ if (this.shouldCopyOnRedirect(oldChannel, newChannel, flags)) {
+ this._log.trace("Copying headers for safe internal redirect.");
+ for (let key in this._headers) {
+ newChannel.setRequestHeader(key, this._headers[key], false);
+ }
+ }
+ } catch (ex) {
+ this._log.error("Error copying headers", ex);
+ }
+
+ this.channel = newChannel;
+
+ // We let all redirects proceed.
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ }
+};
+
+/**
+ * Response object for a RESTRequest. This will be created automatically by
+ * the RESTRequest.
+ */
+this.RESTResponse = function RESTResponse() {
+ this._log = Log.repository.getLogger(this._logName);
+ this._log.level =
+ Log.Level[Prefs.get("log.logger.rest.response")];
+}
+RESTResponse.prototype = {
+
+ _logName: "Services.Common.RESTResponse",
+
+ /**
+ * Corresponding REST request
+ */
+ request: null,
+
+ /**
+ * HTTP status code
+ */
+ get status() {
+ let status;
+ try {
+ status = this.request.channel.responseStatus;
+ } catch (ex) {
+ this._log.debug("Caught exception fetching HTTP status code", ex);
+ return null;
+ }
+ Object.defineProperty(this, "status", {value: status});
+ return status;
+ },
+
+ /**
+ * HTTP status text
+ */
+ get statusText() {
+ let statusText;
+ try {
+ statusText = this.request.channel.responseStatusText;
+ } catch (ex) {
+ this._log.debug("Caught exception fetching HTTP status text", ex);
+ return null;
+ }
+ Object.defineProperty(this, "statusText", {value: statusText});
+ return statusText;
+ },
+
+ /**
+ * Boolean flag that indicates whether the HTTP status code is 2xx or not.
+ */
+ get success() {
+ let success;
+ try {
+ success = this.request.channel.requestSucceeded;
+ } catch (ex) {
+ this._log.debug("Caught exception fetching HTTP success flag", ex);
+ return null;
+ }
+ Object.defineProperty(this, "success", {value: success});
+ return success;
+ },
+
+ /**
+ * Object containing HTTP headers (keyed as lower case)
+ */
+ get headers() {
+ let headers = {};
+ try {
+ this._log.trace("Processing response headers.");
+ let channel = this.request.channel.QueryInterface(Ci.nsIHttpChannel);
+ channel.visitResponseHeaders(function (header, value) {
+ headers[header.toLowerCase()] = value;
+ });
+ } catch (ex) {
+ this._log.debug("Caught exception processing response headers", ex);
+ return null;
+ }
+
+ Object.defineProperty(this, "headers", {value: headers});
+ return headers;
+ },
+
+ /**
+ * HTTP body (string)
+ */
+ body: null
+
+};
+
+/**
+ * Single use MAC authenticated HTTP requests to RESTish resources.
+ *
+ * @param uri
+ * URI going to the RESTRequest constructor.
+ * @param authToken
+ * (Object) An auth token of the form {id: (string), key: (string)}
+ * from which the MAC Authentication header for this request will be
+ * derived. A token as obtained from
+ * TokenServerClient.getTokenFromBrowserIDAssertion is accepted.
+ * @param extra
+ * (Object) Optional extra parameters. Valid keys are: nonce_bytes, ts,
+ * nonce, and ext. See CrytoUtils.computeHTTPMACSHA1 for information on
+ * the purpose of these values.
+ */
+this.TokenAuthenticatedRESTRequest =
+ function TokenAuthenticatedRESTRequest(uri, authToken, extra) {
+ RESTRequest.call(this, uri);
+ this.authToken = authToken;
+ this.extra = extra || {};
+}
+TokenAuthenticatedRESTRequest.prototype = {
+ __proto__: RESTRequest.prototype,
+
+ dispatch: function dispatch(method, data, onComplete, onProgress) {
+ let sig = CryptoUtils.computeHTTPMACSHA1(
+ this.authToken.id, this.authToken.key, method, this.uri, this.extra
+ );
+
+ this.setHeader("Authorization", sig.getHeader());
+
+ return RESTRequest.prototype.dispatch.call(
+ this, method, data, onComplete, onProgress
+ );
+ },
+};
diff --git a/components/weave/src/common/services-common.js b/components/weave/src/common/services-common.js
new file mode 100644
index 000000000..bc37d4028
--- /dev/null
+++ b/components/weave/src/common/services-common.js
@@ -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/. */
+
+// This file contains default preference values for components in
+// services-common.
+
+pref("services.common.log.logger.rest.request", "Debug");
+pref("services.common.log.logger.rest.response", "Debug");
+
+pref("services.common.log.logger.tokenserverclient", "Debug");
diff --git a/components/weave/src/common/stringbundle.js b/components/weave/src/common/stringbundle.js
new file mode 100644
index 000000000..a07fa4831
--- /dev/null
+++ b/components/weave/src/common/stringbundle.js
@@ -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/. */
+
+this.EXPORTED_SYMBOLS = ["StringBundle"];
+
+var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+/**
+ * A string bundle.
+ *
+ * This object presents two APIs: a deprecated one that is equivalent to the API
+ * for the stringbundle XBL binding, to make it easy to switch from that binding
+ * to this module, and a new one that is simpler and easier to use.
+ *
+ * The benefit of this module over the XBL binding is that it can also be used
+ * in JavaScript modules and components, not only in chrome JS.
+ *
+ * To use this module, import it, create a new instance of StringBundle,
+ * and then use the instance's |get| and |getAll| methods to retrieve strings
+ * (you can get both plain and formatted strings with |get|):
+ *
+ * let strings =
+ * new StringBundle("chrome://example/locale/strings.properties");
+ * let foo = strings.get("foo");
+ * let barFormatted = strings.get("bar", [arg1, arg2]);
+ * for (let string of strings.getAll())
+ * dump (string.key + " = " + string.value + "\n");
+ *
+ * @param url {String}
+ * the URL of the string bundle
+ */
+this.StringBundle = function StringBundle(url) {
+ this.url = url;
+}
+
+StringBundle.prototype = {
+ /**
+ * the locale associated with the application
+ * @type nsILocale
+ * @private
+ */
+ get _appLocale() {
+ try {
+ return Cc["@mozilla.org/intl/nslocaleservice;1"].
+ getService(Ci.nsILocaleService).
+ getApplicationLocale();
+ }
+ catch(ex) {
+ return null;
+ }
+ },
+
+ /**
+ * the wrapped nsIStringBundle
+ * @type nsIStringBundle
+ * @private
+ */
+ get _stringBundle() {
+ let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(this.url, this._appLocale);
+ this.__defineGetter__("_stringBundle", () => stringBundle);
+ return this._stringBundle;
+ },
+
+
+ // the new API
+
+ /**
+ * the URL of the string bundle
+ * @type String
+ */
+ _url: null,
+ get url() {
+ return this._url;
+ },
+ set url(newVal) {
+ this._url = newVal;
+ delete this._stringBundle;
+ },
+
+ /**
+ * Get a string from the bundle.
+ *
+ * @param key {String}
+ * the identifier of the string to get
+ * @param args {array} [optional]
+ * an array of arguments that replace occurrences of %S in the string
+ *
+ * @returns {String} the value of the string
+ */
+ get: function(key, args) {
+ if (args)
+ return this.stringBundle.formatStringFromName(key, args, args.length);
+ else
+ return this.stringBundle.GetStringFromName(key);
+ },
+
+ /**
+ * Get all the strings in the bundle.
+ *
+ * @returns {Array}
+ * an array of objects with key and value properties
+ */
+ getAll: function() {
+ let strings = [];
+
+ // FIXME: for performance, return an enumerable array that wraps the string
+ // bundle's nsISimpleEnumerator (does JavaScript already support this?).
+
+ let enumerator = this.stringBundle.getSimpleEnumeration();
+
+ while (enumerator.hasMoreElements()) {
+ // We could simply return the nsIPropertyElement objects, but I think
+ // it's better to return standard JS objects that behave as consumers
+ // expect JS objects to behave (f.e. you can modify them dynamically).
+ let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+ strings.push({ key: string.key, value: string.value });
+ }
+
+ return strings;
+ },
+
+
+ // the deprecated XBL binding-compatible API
+
+ /**
+ * the URL of the string bundle
+ * @deprecated because its name doesn't make sense outside of an XBL binding
+ * @type String
+ */
+ get src() {
+ return this.url;
+ },
+ set src(newVal) {
+ this.url = newVal;
+ },
+
+ /**
+ * the locale associated with the application
+ * @deprecated because it has never been used outside the XBL binding itself,
+ * and consumers should obtain it directly from the locale service anyway.
+ * @type nsILocale
+ */
+ get appLocale() {
+ return this._appLocale;
+ },
+
+ /**
+ * the wrapped nsIStringBundle
+ * @deprecated because this module should provide all necessary functionality
+ * @type nsIStringBundle
+ *
+ * If you do ever need to use this, let the authors of this module know why
+ * so they can surface functionality for your use case in the module itself
+ * and you don't have to access this underlying XPCOM component.
+ */
+ get stringBundle() {
+ return this._stringBundle;
+ },
+
+ /**
+ * Get a string from the bundle.
+ * @deprecated use |get| instead
+ *
+ * @param key {String}
+ * the identifier of the string to get
+ *
+ * @returns {String}
+ * the value of the string
+ */
+ getString: function(key) {
+ return this.get(key);
+ },
+
+ /**
+ * Get a formatted string from the bundle.
+ * @deprecated use |get| instead
+ *
+ * @param key {string}
+ * the identifier of the string to get
+ * @param args {array}
+ * an array of arguments that replace occurrences of %S in the string
+ *
+ * @returns {String}
+ * the formatted value of the string
+ */
+ getFormattedString: function(key, args) {
+ return this.get(key, args);
+ },
+
+ /**
+ * Get an enumeration of the strings in the bundle.
+ * @deprecated use |getAll| instead
+ *
+ * @returns {nsISimpleEnumerator}
+ * a enumeration of the strings in the bundle
+ */
+ get strings() {
+ return this.stringBundle.getSimpleEnumeration();
+ }
+}
diff --git a/components/weave/src/common/tokenserverclient.js b/components/weave/src/common/tokenserverclient.js
new file mode 100644
index 000000000..ca40f7d93
--- /dev/null
+++ b/components/weave/src/common/tokenserverclient.js
@@ -0,0 +1,459 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [
+ "TokenServerClient",
+ "TokenServerClientError",
+ "TokenServerClientNetworkError",
+ "TokenServerClientServerError",
+];
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/rest.js");
+Cu.import("resource://services-common/observers.js");
+
+const PREF_LOG_LEVEL = "services.common.log.logger.tokenserverclient";
+
+/**
+ * Represents a TokenServerClient error that occurred on the client.
+ *
+ * This is the base type for all errors raised by client operations.
+ *
+ * @param message
+ * (string) Error message.
+ */
+this.TokenServerClientError = function TokenServerClientError(message) {
+ this.name = "TokenServerClientError";
+ this.message = message || "Client error.";
+ // Without explicitly setting .stack, all stacks from these errors will point
+ // to the "new Error()" call a few lines down, which isn't helpful.
+ this.stack = Error().stack;
+}
+TokenServerClientError.prototype = new Error();
+TokenServerClientError.prototype.constructor = TokenServerClientError;
+TokenServerClientError.prototype._toStringFields = function() {
+ return {message: this.message};
+}
+TokenServerClientError.prototype.toString = function() {
+ return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
+}
+TokenServerClientError.prototype.toJSON = function() {
+ let result = this._toStringFields();
+ result["name"] = this.name;
+ return result;
+}
+
+/**
+ * Represents a TokenServerClient error that occurred in the network layer.
+ *
+ * @param error
+ * The underlying error thrown by the network layer.
+ */
+this.TokenServerClientNetworkError =
+ function TokenServerClientNetworkError(error) {
+ this.name = "TokenServerClientNetworkError";
+ this.error = error;
+ this.stack = Error().stack;
+}
+TokenServerClientNetworkError.prototype = new TokenServerClientError();
+TokenServerClientNetworkError.prototype.constructor =
+ TokenServerClientNetworkError;
+TokenServerClientNetworkError.prototype._toStringFields = function() {
+ return {error: this.error};
+}
+
+/**
+ * Represents a TokenServerClient error that occurred on the server.
+ *
+ * This type will be encountered for all non-200 response codes from the
+ * server. The type of error is strongly enumerated and is stored in the
+ * `cause` property. This property can have the following string values:
+ *
+ * conditions-required -- The server is requesting that the client
+ * agree to service conditions before it can obtain a token. The
+ * conditions that must be presented to the user and agreed to are in
+ * the `urls` mapping on the instance. Keys of this mapping are
+ * identifiers. Values are string URLs.
+ *
+ * invalid-credentials -- A token could not be obtained because
+ * the credentials presented by the client were invalid.
+ *
+ * unknown-service -- The requested service was not found.
+ *
+ * malformed-request -- The server rejected the request because it
+ * was invalid. If you see this, code in this file is likely wrong.
+ *
+ * malformed-response -- The response from the server was not what was
+ * expected.
+ *
+ * general -- A general server error has occurred. Clients should
+ * interpret this as an opaque failure.
+ *
+ * @param message
+ * (string) Error message.
+ */
+this.TokenServerClientServerError =
+ function TokenServerClientServerError(message, cause="general") {
+ this.now = new Date().toISOString(); // may be useful to diagnose time-skew issues.
+ this.name = "TokenServerClientServerError";
+ this.message = message || "Server error.";
+ this.cause = cause;
+ this.stack = Error().stack;
+}
+TokenServerClientServerError.prototype = new TokenServerClientError();
+TokenServerClientServerError.prototype.constructor =
+ TokenServerClientServerError;
+
+TokenServerClientServerError.prototype._toStringFields = function() {
+ let fields = {
+ now: this.now,
+ message: this.message,
+ cause: this.cause,
+ };
+ if (this.response) {
+ fields.response_body = this.response.body;
+ fields.response_headers = this.response.headers;
+ fields.response_status = this.response.status;
+ }
+ return fields;
+};
+
+/**
+ * Represents a client to the Token Server.
+ *
+ * http://docs.services.mozilla.com/token/index.html
+ *
+ * The Token Server supports obtaining tokens for arbitrary apps by
+ * constructing URI paths of the form <app>/<app_version>. However, the service
+ * discovery mechanism emphasizes the use of full URIs and tries to not force
+ * the client to manipulate URIs. This client currently enforces this practice
+ * by not implementing an API which would perform URI manipulation.
+ *
+ * If you are tempted to implement this API in the future, consider this your
+ * warning that you may be doing it wrong and that you should store full URIs
+ * instead.
+ *
+ * Areas to Improve:
+ *
+ * - The server sends a JSON response on error. The client does not currently
+ * parse this. It might be convenient if it did.
+ * - Currently most non-200 status codes are rolled into one error type. It
+ * might be helpful if callers had a richer API that communicated who was
+ * at fault (e.g. differentiating a 503 from a 401).
+ */
+this.TokenServerClient = function TokenServerClient() {
+ this._log = Log.repository.getLogger("Common.TokenServerClient");
+ let level = Services.prefs.getCharPref(PREF_LOG_LEVEL, "Debug");
+ this._log.level = Log.Level[level];
+}
+TokenServerClient.prototype = {
+ /**
+ * Logger instance.
+ */
+ _log: null,
+
+ /**
+ * Obtain a token from a BrowserID assertion against a specific URL.
+ *
+ * This asynchronously obtains the token. The callback receives 2 arguments:
+ *
+ * (TokenServerClientError | null) If no token could be obtained, this
+ * will be a TokenServerClientError instance describing why. The
+ * type seen defines the type of error encountered. If an HTTP response
+ * was seen, a RESTResponse instance will be stored in the `response`
+ * property of this object. If there was no error and a token is
+ * available, this will be null.
+ *
+ * (map | null) On success, this will be a map containing the results from
+ * the server. If there was an error, this will be null. The map has the
+ * following properties:
+ *
+ * id (string) HTTP MAC public key identifier.
+ * key (string) HTTP MAC shared symmetric key.
+ * endpoint (string) URL where service can be connected to.
+ * uid (string) user ID for requested service.
+ * duration (string) the validity duration of the issued token.
+ *
+ * Terms of Service Acceptance
+ * ---------------------------
+ *
+ * Some services require users to accept terms of service before they can
+ * obtain a token. If a service requires ToS acceptance, the error passed
+ * to the callback will be a `TokenServerClientServerError` with the
+ * `cause` property set to "conditions-required". The `urls` property of that
+ * instance will be a map of string keys to string URL values. The user-agent
+ * should prompt the user to accept the content at these URLs.
+ *
+ * Clients signify acceptance of the terms of service by sending a token
+ * request with additional metadata. This is controlled by the
+ * `conditionsAccepted` argument to this function. Clients only need to set
+ * this flag once per service and the server remembers acceptance. If
+ * the conditions for the service change, the server may request
+ * clients agree to terms again. Therefore, clients should always be
+ * prepared to handle a conditions required response.
+ *
+ * Clients should not blindly send acceptance to conditions. Instead, clients
+ * should set `conditionsAccepted` if and only if the server asks for
+ * acceptance, the conditions are displayed to the user, and the user agrees
+ * to them.
+ *
+ * Example Usage
+ * -------------
+ *
+ * let client = new TokenServerClient();
+ * let assertion = getBrowserIDAssertionFromSomewhere();
+ * let url = "https://token.services.mozilla.com/1.0/sync/2.0";
+ *
+ * client.getTokenFromBrowserIDAssertion(url, assertion,
+ * function onResponse(error, result) {
+ * if (error) {
+ * if (error.cause == "conditions-required") {
+ * promptConditionsAcceptance(error.urls, function onAccept() {
+ * client.getTokenFromBrowserIDAssertion(url, assertion,
+ * onResponse, true);
+ * }
+ * return;
+ * }
+ *
+ * // Do other error handling.
+ * return;
+ * }
+ *
+ * let {
+ * id: id, key: key, uid: uid, endpoint: endpoint, duration: duration
+ * } = result;
+ * // Do stuff with data and carry on.
+ * });
+ *
+ * @param url
+ * (string) URL to fetch token from.
+ * @param assertion
+ * (string) BrowserID assertion to exchange token for.
+ * @param cb
+ * (function) Callback to be invoked with result of operation.
+ * @param conditionsAccepted
+ * (bool) Whether to send acceptance to service conditions.
+ */
+ getTokenFromBrowserIDAssertion:
+ function getTokenFromBrowserIDAssertion(url, assertion, cb, addHeaders={}) {
+ if (!url) {
+ throw new TokenServerClientError("url argument is not valid.");
+ }
+
+ if (!assertion) {
+ throw new TokenServerClientError("assertion argument is not valid.");
+ }
+
+ if (!cb) {
+ throw new TokenServerClientError("cb argument is not valid.");
+ }
+
+ this._log.debug("Beginning BID assertion exchange: " + url);
+
+ let req = this.newRESTRequest(url);
+ req.setHeader("Accept", "application/json");
+ req.setHeader("Authorization", "BrowserID " + assertion);
+
+ for (let header in addHeaders) {
+ req.setHeader(header, addHeaders[header]);
+ }
+
+ let client = this;
+ req.get(function onResponse(error) {
+ if (error) {
+ cb(new TokenServerClientNetworkError(error), null);
+ return;
+ }
+
+ let self = this;
+ function callCallback(error, result) {
+ if (!cb) {
+ self._log.warn("Callback already called! Did it throw?");
+ return;
+ }
+
+ try {
+ cb(error, result);
+ } catch (ex) {
+ self._log.warn("Exception when calling user-supplied callback", ex);
+ }
+
+ cb = null;
+ }
+
+ try {
+ client._processTokenResponse(this.response, callCallback);
+ } catch (ex) {
+ this._log.warn("Error processing token server response", ex);
+
+ let error = new TokenServerClientError(ex);
+ error.response = this.response;
+ callCallback(error, null);
+ }
+ });
+ },
+
+ /**
+ * Handler to process token request responses.
+ *
+ * @param response
+ * RESTResponse from token HTTP request.
+ * @param cb
+ * The original callback passed to the public API.
+ */
+ _processTokenResponse: function processTokenResponse(response, cb) {
+ this._log.debug("Got token response: " + response.status);
+
+ // Responses should *always* be JSON, even in the case of 4xx and 5xx
+ // errors. If we don't see JSON, the server is likely very unhappy.
+ let ct = response.headers["content-type"] || "";
+ if (ct != "application/json" && !ct.startsWith("application/json;")) {
+ this._log.warn("Did not receive JSON response. Misconfigured server?");
+ this._log.debug("Content-Type: " + ct);
+ this._log.debug("Body: " + response.body);
+
+ let error = new TokenServerClientServerError("Non-JSON response.",
+ "malformed-response");
+ error.response = response;
+ cb(error, null);
+ return;
+ }
+
+ let result;
+ try {
+ result = JSON.parse(response.body);
+ } catch (ex) {
+ this._log.warn("Invalid JSON returned by server: " + response.body);
+ let error = new TokenServerClientServerError("Malformed JSON.",
+ "malformed-response");
+ error.response = response;
+ cb(error, null);
+ return;
+ }
+
+ // Any response status can have X-Backoff or X-Weave-Backoff headers.
+ this._maybeNotifyBackoff(response, "x-weave-backoff");
+ this._maybeNotifyBackoff(response, "x-backoff");
+
+ // The service shouldn't have any 3xx, so we don't need to handle those.
+ if (response.status != 200) {
+ // We /should/ have a Cornice error report in the JSON. We log that to
+ // help with debugging.
+ if ("errors" in result) {
+ // This could throw, but this entire function is wrapped in a try. If
+ // the server is sending something not an array of objects, it has
+ // failed to keep its contract with us and there is little we can do.
+ for (let error of result.errors) {
+ this._log.info("Server-reported error: " + JSON.stringify(error));
+ }
+ }
+
+ let error = new TokenServerClientServerError();
+ error.response = response;
+
+ if (response.status == 400) {
+ error.message = "Malformed request.";
+ error.cause = "malformed-request";
+ } else if (response.status == 401) {
+ // Cause can be invalid-credentials, invalid-timestamp, or
+ // invalid-generation.
+ error.message = "Authentication failed.";
+ error.cause = result.status;
+ }
+
+ // 403 should represent a "condition acceptance needed" response.
+ //
+ // The extra validation of "urls" is important. We don't want to signal
+ // conditions required unless we are absolutely sure that is what the
+ // server is asking for.
+ else if (response.status == 403) {
+ if (!("urls" in result)) {
+ this._log.warn("403 response without proper fields!");
+ this._log.warn("Response body: " + response.body);
+
+ error.message = "Missing JSON fields.";
+ error.cause = "malformed-response";
+ } else if (typeof(result.urls) != "object") {
+ error.message = "urls field is not a map.";
+ error.cause = "malformed-response";
+ } else {
+ error.message = "Conditions must be accepted.";
+ error.cause = "conditions-required";
+ error.urls = result.urls;
+ }
+ } else if (response.status == 404) {
+ error.message = "Unknown service.";
+ error.cause = "unknown-service";
+ }
+
+ // A Retry-After header should theoretically only appear on a 503, but
+ // we'll look for it on any error response.
+ this._maybeNotifyBackoff(response, "retry-after");
+
+ cb(error, null);
+ return;
+ }
+
+ for (let k of ["id", "key", "api_endpoint", "uid", "duration"]) {
+ if (!(k in result)) {
+ let error = new TokenServerClientServerError("Expected key not " +
+ " present in result: " +
+ k);
+ error.cause = "malformed-response";
+ error.response = response;
+ cb(error, null);
+ return;
+ }
+ }
+
+ this._log.debug("Successful token response");
+ cb(null, {
+ id: result.id,
+ key: result.key,
+ endpoint: result.api_endpoint,
+ uid: result.uid,
+ duration: result.duration,
+ hashed_fxa_uid: result.hashed_fxa_uid,
+ });
+ },
+
+ /*
+ * The prefix used for all notifications sent by this module. This
+ * allows the handler of notifications to be sure they are handling
+ * notifications for the service they expect.
+ *
+ * If not set, no notifications will be sent.
+ */
+ observerPrefix: null,
+
+ // Given an optional header value, notify that a backoff has been requested.
+ _maybeNotifyBackoff: function (response, headerName) {
+ if (!this.observerPrefix) {
+ return;
+ }
+ let headerVal = response.headers[headerName];
+ if (!headerVal) {
+ return;
+ }
+ let backoffInterval;
+ try {
+ backoffInterval = parseInt(headerVal, 10);
+ } catch (ex) {
+ this._log.error("TokenServer response had invalid backoff value in '" +
+ headerName + "' header: " + headerVal);
+ return;
+ }
+ Observers.notify(this.observerPrefix + ":backoff:interval", backoffInterval);
+ },
+
+ // override points for testing.
+ newRESTRequest: function(url) {
+ return new RESTRequest(url);
+ }
+};
diff --git a/components/weave/src/common/utils.js b/components/weave/src/common/utils.js
new file mode 100644
index 000000000..c90600ef4
--- /dev/null
+++ b/components/weave/src/common/utils.js
@@ -0,0 +1,649 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+
+this.EXPORTED_SYMBOLS = ["CommonUtils"];
+
+Cu.import("resource://gre/modules/Promise.jsm");
+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/Log.jsm");
+
+this.CommonUtils = {
+ /*
+ * Set manipulation methods. These should be lifted into toolkit, or added to
+ * `Set` itself.
+ */
+
+ /**
+ * Return elements of `a` or `b`.
+ */
+ union: function (a, b) {
+ let out = new Set(a);
+ for (let x of b) {
+ out.add(x);
+ }
+ return out;
+ },
+
+ /**
+ * Return elements of `a` that are not present in `b`.
+ */
+ difference: function (a, b) {
+ let out = new Set(a);
+ for (let x of b) {
+ out.delete(x);
+ }
+ return out;
+ },
+
+ /**
+ * Return elements of `a` that are also in `b`.
+ */
+ intersection: function (a, b) {
+ let out = new Set();
+ for (let x of a) {
+ if (b.has(x)) {
+ out.add(x);
+ }
+ }
+ return out;
+ },
+
+ /**
+ * Return true if `a` and `b` are the same size, and
+ * every element of `a` is in `b`.
+ */
+ setEqual: function (a, b) {
+ if (a.size != b.size) {
+ return false;
+ }
+ for (let x of a) {
+ if (!b.has(x)) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ // Import these from Log.jsm for backward compatibility
+ exceptionStr: Log.exceptionStr,
+ stackTrace: Log.stackTrace,
+
+ /**
+ * Encode byte string as base64URL (RFC 4648).
+ *
+ * @param bytes
+ * (string) Raw byte string to encode.
+ * @param pad
+ * (bool) Whether to include padding characters (=). Defaults
+ * to true for historical reasons.
+ */
+ encodeBase64URL: function encodeBase64URL(bytes, pad=true) {
+ let s = btoa(bytes).replace(/\+/g, "-").replace(/\//g, "_");
+
+ if (!pad) {
+ return s.replace(/=+$/, "");
+ }
+
+ return s;
+ },
+
+ /**
+ * Create a nsIURI instance from a string.
+ */
+ makeURI: function makeURI(URIString) {
+ if (!URIString)
+ return null;
+ try {
+ return Services.io.newURI(URIString, null, null);
+ } catch (e) {
+ let log = Log.repository.getLogger("Common.Utils");
+ log.debug("Could not create URI", e);
+ return null;
+ }
+ },
+
+ /**
+ * Execute a function on the next event loop tick.
+ *
+ * @param callback
+ * Function to invoke.
+ * @param thisObj [optional]
+ * Object to bind the callback to.
+ */
+ nextTick: function nextTick(callback, thisObj) {
+ if (thisObj) {
+ callback = callback.bind(thisObj);
+ }
+ Services.tm.currentThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+
+ /**
+ * Return a promise resolving on some later tick.
+ *
+ * This a wrapper around Promise.resolve() that prevents stack
+ * accumulation and prevents callers from accidentally relying on
+ * same-tick promise resolution.
+ */
+ laterTickResolvingPromise: function (value, prototype) {
+ let deferred = Promise.defer(prototype);
+ this.nextTick(deferred.resolve.bind(deferred, value));
+ return deferred.promise;
+ },
+
+ /**
+ * Spin the event loop and return once the next tick is executed.
+ *
+ * This is an evil function and should not be used in production code. It
+ * exists in this module for ease-of-use.
+ */
+ waitForNextTick: function waitForNextTick() {
+ let cb = Async.makeSyncCallback();
+ this.nextTick(cb);
+ Async.waitForSyncCallback(cb);
+
+ return;
+ },
+
+ /**
+ * Return a timer that is scheduled to call the callback after waiting the
+ * provided time or as soon as possible. The timer will be set as a property
+ * of the provided object with the given timer name.
+ */
+ namedTimer: function namedTimer(callback, wait, thisObj, name) {
+ if (!thisObj || !name) {
+ throw "You must provide both an object and a property name for the timer!";
+ }
+
+ // Delay an existing timer if it exists
+ if (name in thisObj && thisObj[name] instanceof Ci.nsITimer) {
+ thisObj[name].delay = wait;
+ return;
+ }
+
+ // Create a special timer that we can add extra properties
+ let timer = Object.create(Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer));
+
+ // Provide an easy way to clear out the timer
+ timer.clear = function() {
+ thisObj[name] = null;
+ timer.cancel();
+ };
+
+ // Initialize the timer with a smart callback
+ timer.initWithCallback({
+ notify: function notify() {
+ // Clear out the timer once it's been triggered
+ timer.clear();
+ callback.call(thisObj, timer);
+ }
+ }, wait, timer.TYPE_ONE_SHOT);
+
+ return thisObj[name] = timer;
+ },
+
+ encodeUTF8: function encodeUTF8(str) {
+ try {
+ str = this._utf8Converter.ConvertFromUnicode(str);
+ return str + this._utf8Converter.Finish();
+ } catch (ex) {
+ return null;
+ }
+ },
+
+ decodeUTF8: function decodeUTF8(str) {
+ try {
+ str = this._utf8Converter.ConvertToUnicode(str);
+ return str + this._utf8Converter.Finish();
+ } catch (ex) {
+ return null;
+ }
+ },
+
+ byteArrayToString: function byteArrayToString(bytes) {
+ return bytes.map(byte => String.fromCharCode(byte)).join("");
+ },
+
+ stringToByteArray: function stringToByteArray(bytesString) {
+ return Array.prototype.slice.call(bytesString).map(c => c.charCodeAt(0));
+ },
+
+ bytesAsHex: function bytesAsHex(bytes) {
+ return Array.prototype.slice.call(bytes).map(c => ("0" + c.charCodeAt(0).toString(16)).slice(-2)).join("");
+ },
+
+ stringAsHex: function stringAsHex(str) {
+ return CommonUtils.bytesAsHex(CommonUtils.encodeUTF8(str));
+ },
+
+ stringToBytes: function stringToBytes(str) {
+ return CommonUtils.hexToBytes(CommonUtils.stringAsHex(str));
+ },
+
+ hexToBytes: function hexToBytes(str) {
+ let bytes = [];
+ for (let i = 0; i < str.length - 1; i += 2) {
+ bytes.push(parseInt(str.substr(i, 2), 16));
+ }
+ return String.fromCharCode.apply(String, bytes);
+ },
+
+ hexAsString: function hexAsString(hex) {
+ return CommonUtils.decodeUTF8(CommonUtils.hexToBytes(hex));
+ },
+
+ /**
+ * Base32 encode (RFC 4648) a string
+ */
+ encodeBase32: function encodeBase32(bytes) {
+ const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+ let quanta = Math.floor(bytes.length / 5);
+ let leftover = bytes.length % 5;
+
+ // Pad the last quantum with zeros so the length is a multiple of 5.
+ if (leftover) {
+ quanta += 1;
+ for (let i = leftover; i < 5; i++)
+ bytes += "\0";
+ }
+
+ // Chop the string into quanta of 5 bytes (40 bits). Each quantum
+ // is turned into 8 characters from the 32 character base.
+ let ret = "";
+ for (let i = 0; i < bytes.length; i += 5) {
+ let c = Array.prototype.slice.call(bytes.slice(i, i + 5)).map(byte => byte.charCodeAt(0));
+ ret += key[c[0] >> 3]
+ + key[((c[0] << 2) & 0x1f) | (c[1] >> 6)]
+ + key[(c[1] >> 1) & 0x1f]
+ + key[((c[1] << 4) & 0x1f) | (c[2] >> 4)]
+ + key[((c[2] << 1) & 0x1f) | (c[3] >> 7)]
+ + key[(c[3] >> 2) & 0x1f]
+ + key[((c[3] << 3) & 0x1f) | (c[4] >> 5)]
+ + key[c[4] & 0x1f];
+ }
+
+ switch (leftover) {
+ case 1:
+ return ret.slice(0, -6) + "======";
+ case 2:
+ return ret.slice(0, -4) + "====";
+ case 3:
+ return ret.slice(0, -3) + "===";
+ case 4:
+ return ret.slice(0, -1) + "=";
+ default:
+ return ret;
+ }
+ },
+
+ /**
+ * Base32 decode (RFC 4648) a string.
+ */
+ decodeBase32: function decodeBase32(str) {
+ const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+
+ let padChar = str.indexOf("=");
+ let chars = (padChar == -1) ? str.length : padChar;
+ let bytes = Math.floor(chars * 5 / 8);
+ let blocks = Math.ceil(chars / 8);
+
+ // Process a chunk of 5 bytes / 8 characters.
+ // The processing of this is known in advance,
+ // so avoid arithmetic!
+ function processBlock(ret, cOffset, rOffset) {
+ let c, val;
+
+ // N.B., this relies on
+ // undefined | foo == foo.
+ function accumulate(val) {
+ ret[rOffset] |= val;
+ }
+
+ function advance() {
+ c = str[cOffset++];
+ if (!c || c == "" || c == "=") // Easier than range checking.
+ throw "Done"; // Will be caught far away.
+ val = key.indexOf(c);
+ if (val == -1)
+ throw "Unknown character in base32: " + c;
+ }
+
+ // Handle a left shift, restricted to bytes.
+ function left(octet, shift) {
+ return (octet << shift) & 0xff;
+ }
+
+ advance();
+ accumulate(left(val, 3));
+ advance();
+ accumulate(val >> 2);
+ ++rOffset;
+ accumulate(left(val, 6));
+ advance();
+ accumulate(left(val, 1));
+ advance();
+ accumulate(val >> 4);
+ ++rOffset;
+ accumulate(left(val, 4));
+ advance();
+ accumulate(val >> 1);
+ ++rOffset;
+ accumulate(left(val, 7));
+ advance();
+ accumulate(left(val, 2));
+ advance();
+ accumulate(val >> 3);
+ ++rOffset;
+ accumulate(left(val, 5));
+ advance();
+ accumulate(val);
+ ++rOffset;
+ }
+
+ // Our output. Define to be explicit (and maybe the compiler will be smart).
+ let ret = new Array(bytes);
+ let i = 0;
+ let cOff = 0;
+ let rOff = 0;
+
+ for (; i < blocks; ++i) {
+ try {
+ processBlock(ret, cOff, rOff);
+ } catch (ex) {
+ // Handle the detection of padding.
+ if (ex == "Done")
+ break;
+ throw ex;
+ }
+ cOff += 8;
+ rOff += 5;
+ }
+
+ // Slice in case our shift overflowed to the right.
+ return CommonUtils.byteArrayToString(ret.slice(0, bytes));
+ },
+
+ /**
+ * Trim excess padding from a Base64 string and atob().
+ *
+ * See bug 562431 comment 4.
+ */
+ safeAtoB: function safeAtoB(b64) {
+ let len = b64.length;
+ let over = len % 4;
+ return over ? atob(b64.substr(0, len - over)) : atob(b64);
+ },
+
+ /**
+ * Parses a JSON file from disk using OS.File and promises.
+ *
+ * @param path the file to read. Will be passed to `OS.File.read()`.
+ * @return a promise that resolves to the JSON contents of the named file.
+ */
+ readJSON: function(path) {
+ return OS.File.read(path, { encoding: "utf-8" }).then((data) => {
+ return JSON.parse(data);
+ });
+ },
+
+ /**
+ * Write a JSON object to the named file using OS.File and promises.
+ *
+ * @param contents a JS object. Will be serialized.
+ * @param path the path of the file to write.
+ * @return a promise, as produced by OS.File.writeAtomic.
+ */
+ writeJSON: function(contents, path) {
+ let data = JSON.stringify(contents);
+ return OS.File.writeAtomic(path, data, {encoding: "utf-8", tmpPath: path + ".tmp"});
+ },
+
+
+ /**
+ * Ensure that the specified value is defined in integer milliseconds since
+ * UNIX epoch.
+ *
+ * This throws an error if the value is not an integer, is negative, or looks
+ * like seconds, not milliseconds.
+ *
+ * If the value is null or 0, no exception is raised.
+ *
+ * @param value
+ * Value to validate.
+ */
+ ensureMillisecondsTimestamp: function ensureMillisecondsTimestamp(value) {
+ if (!value) {
+ return;
+ }
+
+ if (!/^[0-9]+$/.test(value)) {
+ throw new Error("Timestamp value is not a positive integer: " + value);
+ }
+
+ let intValue = parseInt(value, 10);
+
+ if (!intValue) {
+ return;
+ }
+
+ // Catch what looks like seconds, not milliseconds.
+ if (intValue < 10000000000) {
+ throw new Error("Timestamp appears to be in seconds: " + intValue);
+ }
+ },
+
+ /**
+ * Read bytes from an nsIInputStream into a string.
+ *
+ * @param stream
+ * (nsIInputStream) Stream to read from.
+ * @param count
+ * (number) Integer number of bytes to read. If not defined, or
+ * 0, all available input is read.
+ */
+ readBytesFromInputStream: function readBytesFromInputStream(stream, count) {
+ let BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+ if (!count) {
+ count = stream.available();
+ }
+
+ return new BinaryInputStream(stream).readBytes(count);
+ },
+
+ /**
+ * Generate a new UUID using nsIUUIDGenerator.
+ *
+ * Example value: "1e00a2e2-1570-443e-bf5e-000354124234"
+ *
+ * @return string A hex-formatted UUID string.
+ */
+ generateUUID: function generateUUID() {
+ let uuid = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator)
+ .generateUUID()
+ .toString();
+
+ return uuid.substring(1, uuid.length - 1);
+ },
+
+ /**
+ * Obtain an epoch value from a preference.
+ *
+ * This reads a string preference and returns an integer. The string
+ * preference is expected to contain the integer milliseconds since epoch.
+ * For best results, only read preferences that have been saved with
+ * setDatePref().
+ *
+ * We need to store times as strings because integer preferences are only
+ * 32 bits and likely overflow most dates.
+ *
+ * If the pref contains a non-integer value, the specified default value will
+ * be returned.
+ *
+ * @param branch
+ * (Preferences) Branch from which to retrieve preference.
+ * @param pref
+ * (string) The preference to read from.
+ * @param def
+ * (Number) The default value to use if the preference is not defined.
+ * @param log
+ * (Log.Logger) Logger to write warnings to.
+ */
+ getEpochPref: function getEpochPref(branch, pref, def=0, log=null) {
+ if (!Number.isInteger(def)) {
+ throw new Error("Default value is not a number: " + def);
+ }
+
+ let valueStr = branch.get(pref, null);
+
+ if (valueStr !== null) {
+ let valueInt = parseInt(valueStr, 10);
+ if (Number.isNaN(valueInt)) {
+ if (log) {
+ log.warn("Preference value is not an integer. Using default. " +
+ pref + "=" + valueStr + " -> " + def);
+ }
+
+ return def;
+ }
+
+ return valueInt;
+ }
+
+ return def;
+ },
+
+ /**
+ * Obtain a Date from a preference.
+ *
+ * This is a wrapper around getEpochPref. It converts the value to a Date
+ * instance and performs simple range checking.
+ *
+ * The range checking ensures the date is newer than the oldestYear
+ * parameter.
+ *
+ * @param branch
+ * (Preferences) Branch from which to read preference.
+ * @param pref
+ * (string) The preference from which to read.
+ * @param def
+ * (Number) The default value (in milliseconds) if the preference is
+ * not defined or invalid.
+ * @param log
+ * (Log.Logger) Logger to write warnings to.
+ * @param oldestYear
+ * (Number) Oldest year to accept in read values.
+ */
+ getDatePref: function getDatePref(branch, pref, def=0, log=null,
+ oldestYear=2010) {
+
+ let valueInt = this.getEpochPref(branch, pref, def, log);
+ let date = new Date(valueInt);
+
+ if (valueInt == def || date.getFullYear() >= oldestYear) {
+ return date;
+ }
+
+ if (log) {
+ log.warn("Unexpected old date seen in pref. Returning default: " +
+ pref + "=" + date + " -> " + def);
+ }
+
+ return new Date(def);
+ },
+
+ /**
+ * Store a Date in a preference.
+ *
+ * This is the opposite of getDatePref(). The same notes apply.
+ *
+ * If the range check fails, an Error will be thrown instead of a default
+ * value silently being used.
+ *
+ * @param branch
+ * (Preference) Branch from which to read preference.
+ * @param pref
+ * (string) Name of preference to write to.
+ * @param date
+ * (Date) The value to save.
+ * @param oldestYear
+ * (Number) The oldest year to accept for values.
+ */
+ setDatePref: function setDatePref(branch, pref, date, oldestYear=2010) {
+ if (date.getFullYear() < oldestYear) {
+ throw new Error("Trying to set " + pref + " to a very old time: " +
+ date + ". The current time is " + new Date() +
+ ". Is the system clock wrong?");
+ }
+
+ branch.set(pref, "" + date.getTime());
+ },
+
+ /**
+ * Convert a string between two encodings.
+ *
+ * Output is only guaranteed if the input stream is composed of octets. If
+ * the input string has characters with values larger than 255, data loss
+ * will occur.
+ *
+ * The returned string is guaranteed to consist of character codes no greater
+ * than 255.
+ *
+ * @param s
+ * (string) The source string to convert.
+ * @param source
+ * (string) The current encoding of the string.
+ * @param dest
+ * (string) The target encoding of the string.
+ *
+ * @return string
+ */
+ convertString: function convertString(s, source, dest) {
+ if (!s) {
+ throw new Error("Input string must be defined.");
+ }
+
+ let is = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ is.setData(s, s.length);
+
+ let listener = Cc["@mozilla.org/network/stream-loader;1"]
+ .createInstance(Ci.nsIStreamLoader);
+
+ let result;
+
+ listener.init({
+ onStreamComplete: function onStreamComplete(loader, context, status,
+ length, data) {
+ result = String.fromCharCode.apply(this, data);
+ },
+ });
+
+ let converter = this._converterService.asyncConvertData(source, dest,
+ listener, null);
+ converter.onStartRequest(null, null);
+ converter.onDataAvailable(null, null, is, 0, s.length);
+ converter.onStopRequest(null, null, null);
+
+ return result;
+ },
+};
+
+XPCOMUtils.defineLazyGetter(CommonUtils, "_utf8Converter", function() {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ return converter;
+});
+
+XPCOMUtils.defineLazyGetter(CommonUtils, "_converterService", function() {
+ return Cc["@mozilla.org/streamConverters;1"]
+ .getService(Ci.nsIStreamConverterService);
+});
diff --git a/components/weave/src/crypto/WeaveCrypto.js b/components/weave/src/crypto/WeaveCrypto.js
new file mode 100644
index 000000000..aa82e4621
--- /dev/null
+++ b/components/weave/src/crypto/WeaveCrypto.js
@@ -0,0 +1,262 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["WeaveCrypto"];
+
+var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Async.jsm");
+
+Cu.importGlobalProperties(['crypto']);
+
+const CRYPT_ALGO = "AES-CBC";
+const CRYPT_ALGO_LENGTH = 256;
+const AES_CBC_IV_SIZE = 16;
+const OPERATIONS = { ENCRYPT: 0, DECRYPT: 1 };
+const UTF_LABEL = "utf-8";
+
+const KEY_DERIVATION_ALGO = "PBKDF2";
+const KEY_DERIVATION_HASHING_ALGO = "SHA-1";
+const KEY_DERIVATION_ITERATIONS = 4096; // PKCS#5 recommends at least 1000.
+const DERIVED_KEY_ALGO = CRYPT_ALGO;
+
+this.WeaveCrypto = function WeaveCrypto() {
+ this.init();
+};
+
+WeaveCrypto.prototype = {
+ prefBranch : null,
+ debug : true, // services.sync.log.cryptoDebug
+
+ observer : {
+ _self : null,
+
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ observe(subject, topic, data) {
+ let self = this._self;
+ self.log("Observed " + topic + " topic.");
+ if (topic == "nsPref:changed") {
+ self.debug = self.prefBranch.getBoolPref("cryptoDebug");
+ }
+ }
+ },
+
+ init() {
+ // Preferences. Add observer so we get notified of changes.
+ this.prefBranch = Services.prefs.getBranch("services.sync.log.");
+ this.prefBranch.addObserver("cryptoDebug", this.observer, false);
+ this.observer._self = this;
+ this.debug = this.prefBranch.getBoolPref("cryptoDebug", false);
+ XPCOMUtils.defineLazyGetter(this, 'encoder', () => new TextEncoder(UTF_LABEL));
+ XPCOMUtils.defineLazyGetter(this, 'decoder', () => new TextDecoder(UTF_LABEL, { fatal: true }));
+ },
+
+ log(message) {
+ if (!this.debug) {
+ return;
+ }
+ dump("WeaveCrypto: " + message + "\n");
+ Services.console.logStringMessage("WeaveCrypto: " + message);
+ },
+
+ // /!\ Only use this for tests! /!\
+ _getCrypto() {
+ return crypto;
+ },
+
+ encrypt(clearTextUCS2, symmetricKey, iv) {
+ this.log("encrypt() called");
+ let clearTextBuffer = this.encoder.encode(clearTextUCS2).buffer;
+ let encrypted = this._commonCrypt(clearTextBuffer, symmetricKey, iv, OPERATIONS.ENCRYPT);
+ return this.encodeBase64(encrypted);
+ },
+
+ decrypt(cipherText, symmetricKey, iv) {
+ this.log("decrypt() called");
+ if (cipherText.length) {
+ cipherText = atob(cipherText);
+ }
+ let cipherTextBuffer = this.byteCompressInts(cipherText);
+ let decrypted = this._commonCrypt(cipherTextBuffer, symmetricKey, iv, OPERATIONS.DECRYPT);
+ return this.decoder.decode(decrypted);
+ },
+
+ /**
+ * _commonCrypt
+ *
+ * @args
+ * data: data to encrypt/decrypt (ArrayBuffer)
+ * symKeyStr: symmetric key (Base64 String)
+ * ivStr: initialization vector (Base64 String)
+ * operation: operation to apply (either OPERATIONS.ENCRYPT or OPERATIONS.DECRYPT)
+ * @returns
+ * the encrypted/decrypted data (ArrayBuffer)
+ */
+ _commonCrypt(data, symKeyStr, ivStr, operation) {
+ this.log("_commonCrypt() called");
+ ivStr = atob(ivStr);
+
+ if (operation !== OPERATIONS.ENCRYPT && operation !== OPERATIONS.DECRYPT) {
+ throw new Error("Unsupported operation in _commonCrypt.");
+ }
+ // We never want an IV longer than the block size, which is 16 bytes
+ // for AES, neither do we want one smaller; throw in both cases.
+ if (ivStr.length !== AES_CBC_IV_SIZE) {
+ throw "Invalid IV size; must be " + AES_CBC_IV_SIZE + " bytes.";
+ }
+
+ let iv = this.byteCompressInts(ivStr);
+ let symKey = this.importSymKey(symKeyStr, operation);
+ let cryptMethod = (operation === OPERATIONS.ENCRYPT
+ ? crypto.subtle.encrypt
+ : crypto.subtle.decrypt)
+ .bind(crypto.subtle);
+ let algo = { name: CRYPT_ALGO, iv: iv };
+
+
+ return Async.promiseSpinningly(
+ cryptMethod(algo, symKey, data)
+ .then(keyBytes => new Uint8Array(keyBytes))
+ );
+ },
+
+
+ generateRandomKey() {
+ this.log("generateRandomKey() called");
+ let algo = {
+ name: CRYPT_ALGO,
+ length: CRYPT_ALGO_LENGTH
+ };
+ return Async.promiseSpinningly(
+ crypto.subtle.generateKey(algo, true, [])
+ .then(key => crypto.subtle.exportKey("raw", key))
+ .then(keyBytes => {
+ keyBytes = new Uint8Array(keyBytes);
+ return this.encodeBase64(keyBytes);
+ })
+ );
+ },
+
+ generateRandomIV() {
+ return this.generateRandomBytes(AES_CBC_IV_SIZE);
+ },
+
+ generateRandomBytes(byteCount) {
+ this.log("generateRandomBytes() called");
+
+ let randBytes = new Uint8Array(byteCount);
+ crypto.getRandomValues(randBytes);
+
+ return this.encodeBase64(randBytes);
+ },
+
+ //
+ // SymKey CryptoKey memoization.
+ //
+
+ // Memoize the import of symmetric keys. We do this by using the base64
+ // string itself as a key.
+ _encryptionSymKeyMemo: {},
+ _decryptionSymKeyMemo: {},
+ importSymKey(encodedKeyString, operation) {
+ let memo;
+
+ // We use two separate memos for thoroughness: operation is an input to
+ // key import.
+ switch (operation) {
+ case OPERATIONS.ENCRYPT:
+ memo = this._encryptionSymKeyMemo;
+ break;
+ case OPERATIONS.DECRYPT:
+ memo = this._decryptionSymKeyMemo;
+ break;
+ default:
+ throw "Unsupported operation in importSymKey.";
+ }
+
+ if (encodedKeyString in memo)
+ return memo[encodedKeyString];
+
+ let symmetricKeyBuffer = this.makeUint8Array(encodedKeyString, true);
+ let algo = { name: CRYPT_ALGO };
+ let usages = [operation === OPERATIONS.ENCRYPT ? "encrypt" : "decrypt"];
+
+ return Async.promiseSpinningly(
+ crypto.subtle.importKey("raw", symmetricKeyBuffer, algo, false, usages)
+ .then(symKey => {
+ memo[encodedKeyString] = symKey;
+ return symKey;
+ })
+ );
+ },
+
+
+ //
+ // Utility functions
+ //
+
+ /**
+ * Returns an Uint8Array filled with a JS string,
+ * which means we only keep utf-16 characters from 0x00 to 0xFF.
+ */
+ byteCompressInts(str) {
+ let arrayBuffer = new Uint8Array(str.length);
+ for (let i = 0; i < str.length; i++) {
+ arrayBuffer[i] = str.charCodeAt(i) & 0xFF;
+ }
+ return arrayBuffer;
+ },
+
+ expandData(data) {
+ let expanded = "";
+ for (let i = 0; i < data.length; i++) {
+ expanded += String.fromCharCode(data[i]);
+ }
+ return expanded;
+ },
+
+ encodeBase64(data) {
+ return btoa(this.expandData(data));
+ },
+
+ makeUint8Array(input, isEncoded) {
+ if (isEncoded) {
+ input = atob(input);
+ }
+ return this.byteCompressInts(input);
+ },
+
+ /**
+ * Returns the expanded data string for the derived key.
+ */
+ deriveKeyFromPassphrase(passphrase, saltStr, keyLength = 32) {
+ this.log("deriveKeyFromPassphrase() called.");
+ let keyData = this.makeUint8Array(passphrase, false);
+ let salt = this.makeUint8Array(saltStr, true);
+ let importAlgo = { name: KEY_DERIVATION_ALGO };
+ let deriveAlgo = {
+ name: KEY_DERIVATION_ALGO,
+ salt: salt,
+ iterations: KEY_DERIVATION_ITERATIONS,
+ hash: { name: KEY_DERIVATION_HASHING_ALGO },
+ };
+ let derivedKeyType = {
+ name: DERIVED_KEY_ALGO,
+ length: keyLength * 8,
+ };
+ return Async.promiseSpinningly(
+ crypto.subtle.importKey("raw", keyData, importAlgo, false, ["deriveKey"])
+ .then(key => crypto.subtle.deriveKey(deriveAlgo, key, derivedKeyType, true, []))
+ .then(derivedKey => crypto.subtle.exportKey("raw", derivedKey))
+ .then(keyBytes => {
+ keyBytes = new Uint8Array(keyBytes);
+ return this.expandData(keyBytes);
+ })
+ );
+ },
+};
diff --git a/components/weave/src/crypto/utils.js b/components/weave/src/crypto/utils.js
new file mode 100644
index 000000000..e3a77ad0a
--- /dev/null
+++ b/components/weave/src/crypto/utils.js
@@ -0,0 +1,588 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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, results: Cr, utils: Cu} = Components;
+
+this.EXPORTED_SYMBOLS = ["CryptoUtils"];
+
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+this.CryptoUtils = {
+ xor: function xor(a, b) {
+ let bytes = [];
+
+ if (a.length != b.length) {
+ throw new Error("can't xor unequal length strings: "+a.length+" vs "+b.length);
+ }
+
+ for (let i = 0; i < a.length; i++) {
+ bytes[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
+ }
+
+ return String.fromCharCode.apply(String, bytes);
+ },
+
+ /**
+ * Generate a string of random bytes.
+ */
+ generateRandomBytes: function generateRandomBytes(length) {
+ let rng = Cc["@mozilla.org/security/random-generator;1"]
+ .createInstance(Ci.nsIRandomGenerator);
+ let bytes = rng.generateRandomBytes(length);
+ return CommonUtils.byteArrayToString(bytes);
+ },
+
+ /**
+ * UTF8-encode a message and hash it with the given hasher. Returns a
+ * string containing bytes. The hasher is reset if it's an HMAC hasher.
+ */
+ digestUTF8: function digestUTF8(message, hasher) {
+ let data = this._utf8Converter.convertToByteArray(message, {});
+ hasher.update(data, data.length);
+ let result = hasher.finish(false);
+ if (hasher instanceof Ci.nsICryptoHMAC) {
+ hasher.reset();
+ }
+ return result;
+ },
+
+ /**
+ * Treat the given message as a bytes string and hash it with the given
+ * hasher. Returns a string containing bytes. The hasher is reset if it's
+ * an HMAC hasher.
+ */
+ digestBytes: function digestBytes(message, hasher) {
+ // No UTF-8 encoding for you, sunshine.
+ let bytes = Array.prototype.slice.call(message).map(b => b.charCodeAt(0));
+ hasher.update(bytes, bytes.length);
+ let result = hasher.finish(false);
+ if (hasher instanceof Ci.nsICryptoHMAC) {
+ hasher.reset();
+ }
+ return result;
+ },
+
+ /**
+ * Encode the message into UTF-8 and feed the resulting bytes into the
+ * given hasher. Does not return a hash. This can be called multiple times
+ * with a single hasher, but eventually you must extract the result
+ * yourself.
+ */
+ updateUTF8: function(message, hasher) {
+ let bytes = this._utf8Converter.convertToByteArray(message, {});
+ hasher.update(bytes, bytes.length);
+ },
+
+ /**
+ * Produce an HMAC key object from a key string.
+ */
+ makeHMACKey: function makeHMACKey(str) {
+ return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, str);
+ },
+
+ /**
+ * Produce an HMAC hasher and initialize it with the given HMAC key.
+ */
+ makeHMACHasher: function makeHMACHasher(type, key) {
+ let hasher = Cc["@mozilla.org/security/hmac;1"]
+ .createInstance(Ci.nsICryptoHMAC);
+ hasher.init(type, key);
+ return hasher;
+ },
+
+ /**
+ * HMAC-based Key Derivation (RFC 5869).
+ */
+ hkdf: function hkdf(ikm, xts, info, len) {
+ const BLOCKSIZE = 256 / 8;
+ if (typeof xts === undefined)
+ xts = String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0);
+ let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
+ CryptoUtils.makeHMACKey(xts));
+ let prk = CryptoUtils.digestBytes(ikm, h);
+ return CryptoUtils.hkdfExpand(prk, info, len);
+ },
+
+ /**
+ * HMAC-based Key Derivation Step 2 according to RFC 5869.
+ */
+ hkdfExpand: function hkdfExpand(prk, info, len) {
+ const BLOCKSIZE = 256 / 8;
+ let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
+ CryptoUtils.makeHMACKey(prk));
+ let T = "";
+ let Tn = "";
+ let iterations = Math.ceil(len/BLOCKSIZE);
+ for (let i = 0; i < iterations; i++) {
+ Tn = CryptoUtils.digestBytes(Tn + info + String.fromCharCode(i + 1), h);
+ T += Tn;
+ }
+ return T.slice(0, len);
+ },
+
+ /**
+ * PBKDF2 implementation in Javascript.
+ *
+ * The arguments to this function correspond to items in
+ * PKCS #5, v2.0 pp. 9-10
+ *
+ * P: the passphrase, an octet string: e.g., "secret phrase"
+ * S: the salt, an octet string: e.g., "DNXPzPpiwn"
+ * c: the number of iterations, a positive integer: e.g., 4096
+ * dkLen: the length in octets of the destination
+ * key, a positive integer: e.g., 16
+ * hmacAlg: The algorithm to use for hmac
+ * hmacLen: The hmac length
+ *
+ * The default value of 20 for hmacLen is appropriate for SHA1. For SHA256,
+ * hmacLen should be 32.
+ *
+ * The output is an octet string of length dkLen, which you
+ * can encode as you wish.
+ */
+ pbkdf2Generate : function pbkdf2Generate(P, S, c, dkLen,
+ hmacAlg=Ci.nsICryptoHMAC.SHA1, hmacLen=20) {
+
+ // We don't have a default in the algo itself, as NSS does.
+ if (!dkLen) {
+ throw new Error("dkLen should be defined");
+ }
+
+ function F(S, c, i, h) {
+
+ function XOR(a, b, isA) {
+ if (a.length != b.length) {
+ return false;
+ }
+
+ let val = [];
+ for (let i = 0; i < a.length; i++) {
+ if (isA) {
+ val[i] = a[i] ^ b[i];
+ } else {
+ val[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
+ }
+ }
+
+ return val;
+ }
+
+ let ret;
+ let U = [];
+
+ /* Encode i into 4 octets: _INT */
+ let I = [];
+ I[0] = String.fromCharCode((i >> 24) & 0xff);
+ I[1] = String.fromCharCode((i >> 16) & 0xff);
+ I[2] = String.fromCharCode((i >> 8) & 0xff);
+ I[3] = String.fromCharCode(i & 0xff);
+
+ U[0] = CryptoUtils.digestBytes(S + I.join(''), h);
+ for (let j = 1; j < c; j++) {
+ U[j] = CryptoUtils.digestBytes(U[j - 1], h);
+ }
+
+ ret = U[0];
+ for (let j = 1; j < c; j++) {
+ ret = CommonUtils.byteArrayToString(XOR(ret, U[j]));
+ }
+
+ return ret;
+ }
+
+ let l = Math.ceil(dkLen / hmacLen);
+ let r = dkLen - ((l - 1) * hmacLen);
+
+ // Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
+ let h = CryptoUtils.makeHMACHasher(hmacAlg,
+ CryptoUtils.makeHMACKey(P));
+
+ let T = [];
+ for (let i = 0; i < l;) {
+ T[i] = F(S, c, ++i, h);
+ }
+
+ let ret = "";
+ for (let i = 0; i < l-1;) {
+ ret += T[i++];
+ }
+ ret += T[l - 1].substr(0, r);
+
+ return ret;
+ },
+
+ deriveKeyFromPassphrase: function deriveKeyFromPassphrase(passphrase,
+ salt,
+ keyLength,
+ forceJS) {
+ if (Svc.Crypto.deriveKeyFromPassphrase && !forceJS) {
+ return Svc.Crypto.deriveKeyFromPassphrase(passphrase, salt, keyLength);
+ }
+ else {
+ // Fall back to JS implementation.
+ // 4096 is hardcoded in WeaveCrypto, so do so here.
+ return CryptoUtils.pbkdf2Generate(passphrase, atob(salt), 4096,
+ keyLength);
+ }
+ },
+
+ /**
+ * Compute the HTTP MAC SHA-1 for an HTTP request.
+ *
+ * @param identifier
+ * (string) MAC Key Identifier.
+ * @param key
+ * (string) MAC Key.
+ * @param method
+ * (string) HTTP request method.
+ * @param URI
+ * (nsIURI) HTTP request URI.
+ * @param extra
+ * (object) Optional extra parameters. Valid keys are:
+ * nonce_bytes - How many bytes the nonce should be. This defaults
+ * to 8. Note that this many bytes are Base64 encoded, so the
+ * string length of the nonce will be longer than this value.
+ * ts - Timestamp to use. Should only be defined for testing.
+ * nonce - String nonce. Should only be defined for testing as this
+ * function will generate a cryptographically secure random one
+ * if not defined.
+ * ext - Extra string to be included in MAC. Per the HTTP MAC spec,
+ * the format is undefined and thus application specific.
+ * @returns
+ * (object) Contains results of operation and input arguments (for
+ * symmetry). The object has the following keys:
+ *
+ * identifier - (string) MAC Key Identifier (from arguments).
+ * key - (string) MAC Key (from arguments).
+ * method - (string) HTTP request method (from arguments).
+ * hostname - (string) HTTP hostname used (derived from arguments).
+ * port - (string) HTTP port number used (derived from arguments).
+ * mac - (string) Raw HMAC digest bytes.
+ * getHeader - (function) Call to obtain the string Authorization
+ * header value for this invocation.
+ * nonce - (string) Nonce value used.
+ * ts - (number) Integer seconds since Unix epoch that was used.
+ */
+ computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method,
+ uri, extra) {
+ let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
+ let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;
+
+ // We are allowed to use more than the Base64 alphabet if we want.
+ let nonce = (extra && extra.nonce)
+ ? extra.nonce
+ : btoa(CryptoUtils.generateRandomBytes(nonce_bytes));
+
+ let host = uri.asciiHost;
+ let port;
+ let usedMethod = method.toUpperCase();
+
+ if (uri.port != -1) {
+ port = uri.port;
+ } else if (uri.scheme == "http") {
+ port = "80";
+ } else if (uri.scheme == "https") {
+ port = "443";
+ } else {
+ throw new Error("Unsupported URI scheme: " + uri.scheme);
+ }
+
+ let ext = (extra && extra.ext) ? extra.ext : "";
+
+ let requestString = ts.toString(10) + "\n" +
+ nonce + "\n" +
+ usedMethod + "\n" +
+ uri.path + "\n" +
+ host + "\n" +
+ port + "\n" +
+ ext + "\n";
+
+ let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
+ CryptoUtils.makeHMACKey(key));
+ let mac = CryptoUtils.digestBytes(requestString, hasher);
+
+ function getHeader() {
+ return CryptoUtils.getHTTPMACSHA1Header(this.identifier, this.ts,
+ this.nonce, this.mac, this.ext);
+ }
+
+ return {
+ identifier: identifier,
+ key: key,
+ method: usedMethod,
+ hostname: host,
+ port: port,
+ mac: mac,
+ nonce: nonce,
+ ts: ts,
+ ext: ext,
+ getHeader: getHeader
+ };
+ },
+
+
+ /**
+ * Obtain the HTTP MAC Authorization header value from fields.
+ *
+ * @param identifier
+ * (string) MAC key identifier.
+ * @param ts
+ * (number) Integer seconds since Unix epoch.
+ * @param nonce
+ * (string) Nonce value.
+ * @param mac
+ * (string) Computed HMAC digest (raw bytes).
+ * @param ext
+ * (optional) (string) Extra string content.
+ * @returns
+ * (string) Value to put in Authorization header.
+ */
+ getHTTPMACSHA1Header: function getHTTPMACSHA1Header(identifier, ts, nonce,
+ mac, ext) {
+ let header ='MAC id="' + identifier + '", ' +
+ 'ts="' + ts + '", ' +
+ 'nonce="' + nonce + '", ' +
+ 'mac="' + btoa(mac) + '"';
+
+ if (!ext) {
+ return header;
+ }
+
+ return header += ', ext="' + ext +'"';
+ },
+
+ /**
+ * Given an HTTP header value, strip out any attributes.
+ */
+
+ stripHeaderAttributes: function(value) {
+ value = value || "";
+ let i = value.indexOf(";");
+ return value.substring(0, (i >= 0) ? i : undefined).trim().toLowerCase();
+ },
+
+ /**
+ * Compute the HAWK client values (mostly the header) for an HTTP request.
+ *
+ * @param URI
+ * (nsIURI) HTTP request URI.
+ * @param method
+ * (string) HTTP request method.
+ * @param options
+ * (object) extra parameters (all but "credentials" are optional):
+ * credentials - (object, mandatory) HAWK credentials object.
+ * All three keys are required:
+ * id - (string) key identifier
+ * key - (string) raw key bytes
+ * algorithm - (string) which hash to use: "sha1" or "sha256"
+ * ext - (string) application-specific data, included in MAC
+ * localtimeOffsetMsec - (number) local clock offset (vs server)
+ * payload - (string) payload to include in hash, containing the
+ * HTTP request body. If not provided, the HAWK hash
+ * will not cover the request body, and the server
+ * should not check it either. This will be UTF-8
+ * encoded into bytes before hashing. This function
+ * cannot handle arbitrary binary data, sorry (the
+ * UTF-8 encoding process will corrupt any codepoints
+ * between U+0080 and U+00FF). Callers must be careful
+ * to use an HTTP client function which encodes the
+ * payload exactly the same way, otherwise the hash
+ * will not match.
+ * contentType - (string) payload Content-Type. This is included
+ * (without any attributes like "charset=") in the
+ * HAWK hash. It does *not* affect interpretation
+ * of the "payload" property.
+ * hash - (base64 string) pre-calculated payload hash. If
+ * provided, "payload" is ignored.
+ * ts - (number) pre-calculated timestamp, secs since epoch
+ * now - (number) current time, ms-since-epoch, for tests
+ * nonce - (string) pre-calculated nonce. Should only be defined
+ * for testing as this function will generate a
+ * cryptographically secure random one if not defined.
+ * @returns
+ * (object) Contains results of operation. The object has the
+ * following keys:
+ * field - (string) HAWK header, to use in Authorization: header
+ * artifacts - (object) other generated values:
+ * ts - (number) timestamp, in seconds since epoch
+ * nonce - (string)
+ * method - (string)
+ * resource - (string) path plus querystring
+ * host - (string)
+ * port - (number)
+ * hash - (string) payload hash (base64)
+ * ext - (string) app-specific data
+ * MAC - (string) request MAC (base64)
+ */
+ computeHAWK: function(uri, method, options) {
+ let credentials = options.credentials;
+ let ts = options.ts || Math.floor(((options.now || Date.now()) +
+ (options.localtimeOffsetMsec || 0))
+ / 1000);
+
+ let hash_algo, hmac_algo;
+ if (credentials.algorithm == "sha1") {
+ hash_algo = Ci.nsICryptoHash.SHA1;
+ hmac_algo = Ci.nsICryptoHMAC.SHA1;
+ } else if (credentials.algorithm == "sha256") {
+ hash_algo = Ci.nsICryptoHash.SHA256;
+ hmac_algo = Ci.nsICryptoHMAC.SHA256;
+ } else {
+ throw new Error("Unsupported algorithm: " + credentials.algorithm);
+ }
+
+ let port;
+ if (uri.port != -1) {
+ port = uri.port;
+ } else if (uri.scheme == "http") {
+ port = 80;
+ } else if (uri.scheme == "https") {
+ port = 443;
+ } else {
+ throw new Error("Unsupported URI scheme: " + uri.scheme);
+ }
+
+ let artifacts = {
+ ts: ts,
+ nonce: options.nonce || btoa(CryptoUtils.generateRandomBytes(8)),
+ method: method.toUpperCase(),
+ resource: uri.path, // This includes both path and search/queryarg.
+ host: uri.asciiHost.toLowerCase(), // This includes punycoding.
+ port: port.toString(10),
+ hash: options.hash,
+ ext: options.ext,
+ };
+
+ let contentType = CryptoUtils.stripHeaderAttributes(options.contentType);
+
+ if (!artifacts.hash && options.hasOwnProperty("payload")
+ && options.payload) {
+ let hasher = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ hasher.init(hash_algo);
+ CryptoUtils.updateUTF8("hawk.1.payload\n", hasher);
+ CryptoUtils.updateUTF8(contentType+"\n", hasher);
+ CryptoUtils.updateUTF8(options.payload, hasher);
+ CryptoUtils.updateUTF8("\n", hasher);
+ let hash = hasher.finish(false);
+ // HAWK specifies this .hash to use +/ (not _-) and include the
+ // trailing "==" padding.
+ let hash_b64 = btoa(hash);
+ artifacts.hash = hash_b64;
+ }
+
+ let requestString = ("hawk.1.header" + "\n" +
+ artifacts.ts.toString(10) + "\n" +
+ artifacts.nonce + "\n" +
+ artifacts.method + "\n" +
+ artifacts.resource + "\n" +
+ artifacts.host + "\n" +
+ artifacts.port + "\n" +
+ (artifacts.hash || "") + "\n");
+ if (artifacts.ext) {
+ requestString += artifacts.ext.replace("\\", "\\\\").replace("\n", "\\n");
+ }
+ requestString += "\n";
+
+ let hasher = CryptoUtils.makeHMACHasher(hmac_algo,
+ CryptoUtils.makeHMACKey(credentials.key));
+ artifacts.mac = btoa(CryptoUtils.digestBytes(requestString, hasher));
+ // The output MAC uses "+" and "/", and padded== .
+
+ function escape(attribute) {
+ // This is used for "x=y" attributes inside HTTP headers.
+ return attribute.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
+ }
+ let header = ('Hawk id="' + credentials.id + '", ' +
+ 'ts="' + artifacts.ts + '", ' +
+ 'nonce="' + artifacts.nonce + '", ' +
+ (artifacts.hash ? ('hash="' + artifacts.hash + '", ') : "") +
+ (artifacts.ext ? ('ext="' + escape(artifacts.ext) + '", ') : "") +
+ 'mac="' + artifacts.mac + '"');
+ return {
+ artifacts: artifacts,
+ field: header,
+ };
+ },
+
+};
+
+/**
+ * Hashing Algorithms SHA-X.
+ * These values map directly onto the values defined
+ * in netwerk/base/nsICryptoHash.idl.
+ */
+let shaX = ["1", "256", "384", "512", "224"];
+
+for (let shaIdx = 0, shaIdxLen = shaX.length; shaIdx < shaIdxLen; shaIdx++) {
+ let shaXIdx = shaX[shaIdx];
+
+ /**
+ * UTF-8 encode a message and perform a SHA-X over it.
+ *
+ * @param message
+ * (string) Buffer to perform operation on. Should be a JS string.
+ * It is possible to pass in a string representing an array
+ * of bytes. But, you probably don't want to UTF-8 encode
+ * such data and thus should not be using this function.
+ *
+ * @return string
+ * Raw bytes constituting SHA-X hash. Value is a JS string.
+ * Each character is the byte value for that offset.
+ */
+ CryptoUtils["UTF8AndSHA" + shaXIdx] = function (message) {
+ let hasher = Cc["@mozilla.org/security/hash;1"]
+ .createInstance(Ci.nsICryptoHash);
+ hasher.init(hasher["SHA" + shaXIdx]);
+
+ return CryptoUtils.digestUTF8(message, hasher);
+ };
+
+ CryptoUtils["sha" + shaXIdx] = function (message) {
+ return CommonUtils.bytesAsHex(
+ CryptoUtils["UTF8AndSHA" + shaXIdx](message));
+ };
+
+ CryptoUtils["sha" + shaXIdx + "Base32"] = function (message) {
+ return CommonUtils.encodeBase32(
+ CryptoUtils["UTF8AndSHA" + shaXIdx](message));
+ };
+}
+
+XPCOMUtils.defineLazyGetter(CryptoUtils, "_utf8Converter", function() {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+
+ return converter;
+});
+
+var Svc = {};
+
+XPCOMUtils.defineLazyServiceGetter(Svc,
+ "KeyFactory",
+ "@mozilla.org/security/keyobjectfactory;1",
+ "nsIKeyObjectFactory");
+
+Svc.__defineGetter__("Crypto", function() {
+ let ns = {};
+ Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
+
+ let wc = new ns.WeaveCrypto();
+ delete Svc.Crypto;
+ return Svc.Crypto = wc;
+});
+
+Observers.add("xpcom-shutdown", function unloadServices() {
+ Observers.remove("xpcom-shutdown", unloadServices);
+
+ for (let k in Svc) {
+ delete Svc[k];
+ }
+});
diff --git a/components/weave/src/engines/addons.js b/components/weave/src/engines/addons.js
new file mode 100644
index 000000000..86f94e80b
--- /dev/null
+++ b/components/weave/src/engines/addons.js
@@ -0,0 +1,706 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 defines the add-on sync functionality.
+ *
+ * There are currently a number of known limitations:
+ * - We only sync XPI extensions and themes available from addons.mozilla.org.
+ * We hope to expand support for other add-ons eventually.
+ * - We only attempt syncing of add-ons between applications of the same type.
+ * This means add-ons will not synchronize between Firefox desktop and
+ * Firefox mobile, for example. This is because of significant add-on
+ * incompatibility between application types.
+ *
+ * Add-on records exist for each known {add-on, app-id} pair in the Sync client
+ * set. Each record has a randomly chosen GUID. The records then contain
+ * basic metadata about the add-on.
+ *
+ * We currently synchronize:
+ *
+ * - Installations
+ * - Uninstallations
+ * - User enabling and disabling
+ *
+ * Synchronization is influenced by the following preferences:
+ *
+ * - services.sync.addons.ignoreRepositoryChecking
+ * - services.sync.addons.ignoreUserEnabledChanges
+ * - services.sync.addons.trustedSourceHostnames
+ *
+ * See the documentation in services-sync.js for the behavior of these prefs.
+ */
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://services-sync/addonutils.js");
+Cu.import("resource://services-sync/addonsreconciler.js");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/util.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://gre/modules/Async.jsm");
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+ "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
+ "resource://gre/modules/addons/AddonRepository.jsm");
+
+this.EXPORTED_SYMBOLS = ["AddonsEngine"];
+
+// 7 days in milliseconds.
+const PRUNE_ADDON_CHANGES_THRESHOLD = 60 * 60 * 24 * 7 * 1000;
+
+/**
+ * AddonRecord represents the state of an add-on in an application.
+ *
+ * Each add-on has its own record for each application ID it is installed
+ * on.
+ *
+ * The ID of add-on records is a randomly-generated GUID. It is random instead
+ * of deterministic so the URIs of the records cannot be guessed and so
+ * compromised server credentials won't result in disclosure of the specific
+ * add-ons present in a Sync account.
+ *
+ * The record contains the following fields:
+ *
+ * addonID
+ * ID of the add-on. This correlates to the "id" property on an Addon type.
+ *
+ * applicationID
+ * The application ID this record is associated with.
+ *
+ * enabled
+ * Boolean stating whether add-on is enabled or disabled by the user.
+ *
+ * source
+ * String indicating where an add-on is from. Currently, we only support
+ * the value "amo" which indicates that the add-on came from the official
+ * add-ons repository, addons.mozilla.org. In the future, we may support
+ * installing add-ons from other sources. This provides a future-compatible
+ * mechanism for clients to only apply records they know how to handle.
+ */
+function AddonRecord(collection, id) {
+ CryptoWrapper.call(this, collection, id);
+}
+AddonRecord.prototype = {
+ __proto__: CryptoWrapper.prototype,
+ _logName: "Record.Addon"
+};
+
+Utils.deferGetSet(AddonRecord, "cleartext", ["addonID",
+ "applicationID",
+ "enabled",
+ "source"]);
+
+/**
+ * The AddonsEngine handles synchronization of add-ons between clients.
+ *
+ * The engine maintains an instance of an AddonsReconciler, which is the entity
+ * maintaining state for add-ons. It provides the history and tracking APIs
+ * that AddonManager doesn't.
+ *
+ * The engine instance overrides a handful of functions on the base class. The
+ * rationale for each is documented by that function.
+ */
+this.AddonsEngine = function AddonsEngine(service) {
+ SyncEngine.call(this, "Addons", service);
+
+ this._reconciler = new AddonsReconciler();
+}
+AddonsEngine.prototype = {
+ __proto__: SyncEngine.prototype,
+ _storeObj: AddonsStore,
+ _trackerObj: AddonsTracker,
+ _recordObj: AddonRecord,
+ version: 1,
+
+ syncPriority: 5,
+
+ _reconciler: null,
+
+ /**
+ * Override parent method to find add-ons by their public ID, not Sync GUID.
+ */
+ _findDupe: function _findDupe(item) {
+ let id = item.addonID;
+
+ // The reconciler should have been updated at the top of the sync, so we
+ // can assume it is up to date when this function is called.
+ let addons = this._reconciler.addons;
+ if (!(id in addons)) {
+ return null;
+ }
+
+ let addon = addons[id];
+ if (addon.guid != item.id) {
+ return addon.guid;
+ }
+
+ return null;
+ },
+
+ /**
+ * Override getChangedIDs to pull in tracker changes plus changes from the
+ * reconciler log.
+ */
+ getChangedIDs: function getChangedIDs() {
+ let changes = {};
+ for (let [id, modified] of Object.entries(this._tracker.changedIDs)) {
+ changes[id] = modified;
+ }
+
+ let lastSyncDate = new Date(this.lastSync * 1000);
+
+ // The reconciler should have been refreshed at the beginning of a sync and
+ // we assume this function is only called from within a sync.
+ let reconcilerChanges = this._reconciler.getChangesSinceDate(lastSyncDate);
+ let addons = this._reconciler.addons;
+ for (let change of reconcilerChanges) {
+ let changeTime = change[0];
+ let id = change[2];
+
+ if (!(id in addons)) {
+ continue;
+ }
+
+ // Keep newest modified time.
+ if (id in changes && changeTime < changes[id]) {
+ continue;
+ }
+
+ if (!this._store.isAddonSyncable(addons[id])) {
+ continue;
+ }
+
+ this._log.debug("Adding changed add-on from changes log: " + id);
+ let addon = addons[id];
+ changes[addon.guid] = changeTime.getTime() / 1000;
+ }
+
+ return changes;
+ },
+
+ /**
+ * Override start of sync function to refresh reconciler.
+ *
+ * Many functions in this class assume the reconciler is refreshed at the
+ * top of a sync. If this ever changes, those functions should be revisited.
+ *
+ * Technically speaking, we don't need to refresh the reconciler on every
+ * sync since it is installed as an AddonManager listener. However, add-ons
+ * are complicated and we force a full refresh, just in case the listeners
+ * missed something.
+ */
+ _syncStartup: function _syncStartup() {
+ // We refresh state before calling parent because syncStartup in the parent
+ // looks for changed IDs, which is dependent on add-on state being up to
+ // date.
+ this._refreshReconcilerState();
+
+ SyncEngine.prototype._syncStartup.call(this);
+ },
+
+ /**
+ * Override end of sync to perform a little housekeeping on the reconciler.
+ *
+ * We prune changes to prevent the reconciler state from growing without
+ * bound. Even if it grows unbounded, there would have to be many add-on
+ * changes (thousands) for it to slow things down significantly. This is
+ * highly unlikely to occur. Still, we exercise defense just in case.
+ */
+ _syncCleanup: function _syncCleanup() {
+ let ms = 1000 * this.lastSync - PRUNE_ADDON_CHANGES_THRESHOLD;
+ this._reconciler.pruneChangesBeforeDate(new Date(ms));
+
+ SyncEngine.prototype._syncCleanup.call(this);
+ },
+
+ /**
+ * Helper function to ensure reconciler is up to date.
+ *
+ * This will synchronously load the reconciler's state from the file
+ * system (if needed) and refresh the state of the reconciler.
+ */
+ _refreshReconcilerState: function _refreshReconcilerState() {
+ this._log.debug("Refreshing reconciler state");
+ let cb = Async.makeSpinningCallback();
+ this._reconciler.refreshGlobalState(cb);
+ cb.wait();
+ }
+};
+
+/**
+ * This is the primary interface between Sync and the Addons Manager.
+ *
+ * In addition to the core store APIs, we provide convenience functions to wrap
+ * Add-on Manager APIs with Sync-specific semantics.
+ */
+function AddonsStore(name, engine) {
+ Store.call(this, name, engine);
+}
+AddonsStore.prototype = {
+ __proto__: Store.prototype,
+
+ // Define the add-on types (.type) that we support.
+ _syncableTypes: ["extension", "theme"],
+
+ _extensionsPrefs: new Preferences("extensions."),
+
+ get reconciler() {
+ return this.engine._reconciler;
+ },
+
+ /**
+ * Override applyIncoming to filter out records we can't handle.
+ */
+ applyIncoming: function applyIncoming(record) {
+ // The fields we look at aren't present when the record is deleted.
+ if (!record.deleted) {
+ // Ignore records not belonging to our application ID because that is the
+ // current policy.
+ if (record.applicationID != Services.appinfo.ID) {
+ this._log.info("Ignoring incoming record from other App ID: " +
+ record.id);
+ return;
+ }
+
+ // Ignore records that aren't from the official add-on repository, as that
+ // is our current policy.
+ if (record.source != "amo") {
+ this._log.info("Ignoring unknown add-on source (" + record.source + ")" +
+ " for " + record.id);
+ return;
+ }
+ }
+
+ Store.prototype.applyIncoming.call(this, record);
+ },
+
+
+ /**
+ * Provides core Store API to create/install an add-on from a record.
+ */
+ create: function create(record) {
+ let cb = Async.makeSpinningCallback();
+ AddonUtils.installAddons([{
+ id: record.addonID,
+ syncGUID: record.id,
+ enabled: record.enabled,
+ requireSecureURI: this._extensionsPrefs.get("install.requireSecureOrigin", true),
+ }], cb);
+
+ // This will throw if there was an error. This will get caught by the sync
+ // engine and the record will try to be applied later.
+ let results = cb.wait();
+
+ let addon;
+ for (let a of results.addons) {
+ if (a.id == record.addonID) {
+ addon = a;
+ break;
+ }
+ }
+
+ // This should never happen, but is present as a fail-safe.
+ if (!addon) {
+ throw new Error("Add-on not found after install: " + record.addonID);
+ }
+
+ this._log.info("Add-on installed: " + record.addonID);
+ },
+
+ /**
+ * Provides core Store API to remove/uninstall an add-on from a record.
+ */
+ remove: function remove(record) {
+ // If this is called, the payload is empty, so we have to find by GUID.
+ let addon = this.getAddonByGUID(record.id);
+ if (!addon) {
+ // We don't throw because if the add-on could not be found then we assume
+ // it has already been uninstalled and there is nothing for this function
+ // to do.
+ return;
+ }
+
+ this._log.info("Uninstalling add-on: " + addon.id);
+ let cb = Async.makeSpinningCallback();
+ AddonUtils.uninstallAddon(addon, cb);
+ cb.wait();
+ },
+
+ /**
+ * Provides core Store API to update an add-on from a record.
+ */
+ update: function update(record) {
+ let addon = this.getAddonByID(record.addonID);
+
+ // update() is called if !this.itemExists. And, since itemExists consults
+ // the reconciler only, we need to take care of some corner cases.
+ //
+ // First, the reconciler could know about an add-on that was uninstalled
+ // and no longer present in the add-ons manager.
+ if (!addon) {
+ this.create(record);
+ return;
+ }
+
+ // It's also possible that the add-on is non-restartless and has pending
+ // install/uninstall activity.
+ //
+ // We wouldn't get here if the incoming record was for a deletion. So,
+ // check for pending uninstall and cancel if necessary.
+ if (addon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
+ addon.cancelUninstall();
+
+ // We continue with processing because there could be state or ID change.
+ }
+
+ let cb = Async.makeSpinningCallback();
+ this.updateUserDisabled(addon, !record.enabled, cb);
+ cb.wait();
+ },
+
+ /**
+ * Provide core Store API to determine if a record exists.
+ */
+ itemExists: function itemExists(guid) {
+ let addon = this.reconciler.getAddonStateFromSyncGUID(guid);
+
+ return !!addon;
+ },
+
+ /**
+ * Create an add-on record from its GUID.
+ *
+ * @param guid
+ * Add-on GUID (from extensions DB)
+ * @param collection
+ * Collection to add record to.
+ *
+ * @return AddonRecord instance
+ */
+ createRecord: function createRecord(guid, collection) {
+ let record = new AddonRecord(collection, guid);
+ record.applicationID = Services.appinfo.ID;
+
+ let addon = this.reconciler.getAddonStateFromSyncGUID(guid);
+
+ // If we don't know about this GUID or if it has been uninstalled, we mark
+ // the record as deleted.
+ if (!addon || !addon.installed) {
+ record.deleted = true;
+ return record;
+ }
+
+ record.modified = addon.modified.getTime() / 1000;
+
+ record.addonID = addon.id;
+ record.enabled = addon.enabled;
+
+ // This needs to be dynamic when add-ons don't come from AddonRepository.
+ record.source = "amo";
+
+ return record;
+ },
+
+ /**
+ * Changes the id of an add-on.
+ *
+ * This implements a core API of the store.
+ */
+ changeItemID: function changeItemID(oldID, newID) {
+ // We always update the GUID in the reconciler because it will be
+ // referenced later in the sync process.
+ let state = this.reconciler.getAddonStateFromSyncGUID(oldID);
+ if (state) {
+ state.guid = newID;
+ let cb = Async.makeSpinningCallback();
+ this.reconciler.saveState(null, cb);
+ cb.wait();
+ }
+
+ let addon = this.getAddonByGUID(oldID);
+ if (!addon) {
+ this._log.debug("Cannot change item ID (" + oldID + ") in Add-on " +
+ "Manager because old add-on not present: " + oldID);
+ return;
+ }
+
+ addon.syncGUID = newID;
+ },
+
+ /**
+ * Obtain the set of all syncable add-on Sync GUIDs.
+ *
+ * This implements a core Store API.
+ */
+ getAllIDs: function getAllIDs() {
+ let ids = {};
+
+ let addons = this.reconciler.addons;
+ for (let id in addons) {
+ let addon = addons[id];
+ if (this.isAddonSyncable(addon)) {
+ ids[addon.guid] = true;
+ }
+ }
+
+ return ids;
+ },
+
+ /**
+ * Wipe engine data.
+ *
+ * This uninstalls all syncable addons from the application. In case of
+ * error, it logs the error and keeps trying with other add-ons.
+ */
+ wipe: function wipe() {
+ this._log.info("Processing wipe.");
+
+ this.engine._refreshReconcilerState();
+
+ // We only wipe syncable add-ons. Wipe is a Sync feature not a security
+ // feature.
+ for (let guid in this.getAllIDs()) {
+ let addon = this.getAddonByGUID(guid);
+ if (!addon) {
+ this._log.debug("Ignoring add-on because it couldn't be obtained: " +
+ guid);
+ continue;
+ }
+
+ this._log.info("Uninstalling add-on as part of wipe: " + addon.id);
+ Utils.catch(addon.uninstall)();
+ }
+ },
+
+ /***************************************************************************
+ * Functions below are unique to this store and not part of the Store API *
+ ***************************************************************************/
+
+ /**
+ * Synchronously obtain an add-on from its public ID.
+ *
+ * @param id
+ * Add-on ID
+ * @return Addon or undefined if not found
+ */
+ getAddonByID: function getAddonByID(id) {
+ let cb = Async.makeSyncCallback();
+ AddonManager.getAddonByID(id, cb);
+ return Async.waitForSyncCallback(cb);
+ },
+
+ /**
+ * Synchronously obtain an add-on from its Sync GUID.
+ *
+ * @param guid
+ * Add-on Sync GUID
+ * @return DBAddonInternal or null
+ */
+ getAddonByGUID: function getAddonByGUID(guid) {
+ let cb = Async.makeSyncCallback();
+ AddonManager.getAddonBySyncGUID(guid, cb);
+ return Async.waitForSyncCallback(cb);
+ },
+
+ /**
+ * Determines whether an add-on is suitable for Sync.
+ *
+ * @param addon
+ * Addon instance
+ * @return Boolean indicating whether it is appropriate for Sync
+ */
+ isAddonSyncable: function isAddonSyncable(addon) {
+ // Currently, we limit syncable add-ons to those that are:
+ // 1) In a well-defined set of types
+ // 2) Installed in the current profile
+ // 3) Not installed by a foreign entity (i.e. installed by the app)
+ // since they act like global extensions.
+ // 4) Is not a hotfix.
+ // 5) Are installed from AMO
+
+ // We could represent the test as a complex boolean expression. We go the
+ // verbose route so the failure reason is logged.
+ if (!addon) {
+ this._log.debug("Null object passed to isAddonSyncable.");
+ return false;
+ }
+
+ if (this._syncableTypes.indexOf(addon.type) == -1) {
+ this._log.debug(addon.id + " not syncable: type not in whitelist: " +
+ addon.type);
+ return false;
+ }
+
+ if (!(addon.scope & AddonManager.SCOPE_PROFILE)) {
+ this._log.debug(addon.id + " not syncable: not installed in profile.");
+ return false;
+ }
+
+ // This may be too aggressive. If an add-on is downloaded from AMO and
+ // manually placed in the profile directory, foreignInstall will be set.
+ // Arguably, that add-on should be syncable.
+ // TODO Address the edge case and come up with more robust heuristics.
+ if (addon.foreignInstall) {
+ this._log.debug(addon.id + " not syncable: is foreign install.");
+ return false;
+ }
+
+ // Ignore hotfix extensions (bug 741670). The pref may not be defined.
+ if (this._extensionsPrefs.get("hotfix.id", null) == addon.id) {
+ this._log.debug(addon.id + " not syncable: is a hotfix.");
+ return false;
+ }
+
+ // We provide a back door to skip the repository checking of an add-on.
+ // This is utilized by the tests to make testing easier. Users could enable
+ // this, but it would sacrifice security.
+ if (Svc.Prefs.get("addons.ignoreRepositoryChecking", false)) {
+ return true;
+ }
+
+ let cb = Async.makeSyncCallback();
+ AddonRepository.getCachedAddonByID(addon.id, cb);
+ let result = Async.waitForSyncCallback(cb);
+
+ if (!result) {
+ this._log.debug(addon.id + " not syncable: add-on not found in add-on " +
+ "repository.");
+ return false;
+ }
+
+ return this.isSourceURITrusted(result.sourceURI);
+ },
+
+ /**
+ * Determine whether an add-on's sourceURI field is trusted and the add-on
+ * can be installed.
+ *
+ * This function should only ever be called from isAddonSyncable(). It is
+ * exposed as a separate function to make testing easier.
+ *
+ * @param uri
+ * nsIURI instance to validate
+ * @return bool
+ */
+ isSourceURITrusted: function isSourceURITrusted(uri) {
+ // For security reasons, we currently limit synced add-ons to those
+ // installed from trusted hostname(s). We additionally require TLS with
+ // the add-ons site to help prevent forgeries.
+ let trustedHostnames = Svc.Prefs.get("addons.trustedSourceHostnames", "")
+ .split(",");
+
+ if (!uri) {
+ this._log.debug("Undefined argument to isSourceURITrusted().");
+ return false;
+ }
+
+ // Scheme is validated before the hostname because uri.host may not be
+ // populated for certain schemes. It appears to always be populated for
+ // https, so we avoid the potential NS_ERROR_FAILURE on field access.
+ if (uri.scheme != "https") {
+ this._log.debug("Source URI not HTTPS: " + uri.spec);
+ return false;
+ }
+
+ if (trustedHostnames.indexOf(uri.host) == -1) {
+ this._log.debug("Source hostname not trusted: " + uri.host);
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Update the userDisabled flag on an add-on.
+ *
+ * This will enable or disable an add-on and call the supplied callback when
+ * the action is complete. If no action is needed, the callback gets called
+ * immediately.
+ *
+ * @param addon
+ * Addon instance to manipulate.
+ * @param value
+ * Boolean to which to set userDisabled on the passed Addon.
+ * @param callback
+ * Function to be called when action is complete. Will receive 2
+ * arguments, a truthy value that signifies error, and the Addon
+ * instance passed to this function.
+ */
+ updateUserDisabled: function updateUserDisabled(addon, value, callback) {
+ if (addon.userDisabled == value) {
+ callback(null, addon);
+ return;
+ }
+
+ // A pref allows changes to the enabled flag to be ignored.
+ if (Svc.Prefs.get("addons.ignoreUserEnabledChanges", false)) {
+ this._log.info("Ignoring enabled state change due to preference: " +
+ addon.id);
+ callback(null, addon);
+ return;
+ }
+
+ AddonUtils.updateUserDisabled(addon, value, callback);
+ },
+};
+
+/**
+ * The add-ons tracker keeps track of real-time changes to add-ons.
+ *
+ * It hooks up to the reconciler and receives notifications directly from it.
+ */
+function AddonsTracker(name, engine) {
+ Tracker.call(this, name, engine);
+}
+AddonsTracker.prototype = {
+ __proto__: Tracker.prototype,
+
+ get reconciler() {
+ return this.engine._reconciler;
+ },
+
+ get store() {
+ return this.engine._store;
+ },
+
+ /**
+ * This callback is executed whenever the AddonsReconciler sends out a change
+ * notification. See AddonsReconciler.addChangeListener().
+ */
+ changeListener: function changeHandler(date, change, addon) {
+ this._log.debug("changeListener invoked: " + change + " " + addon.id);
+ // Ignore changes that occur during sync.
+ if (this.ignoreAll) {
+ return;
+ }
+
+ if (!this.store.isAddonSyncable(addon)) {
+ this._log.debug("Ignoring change because add-on isn't syncable: " +
+ addon.id);
+ return;
+ }
+
+ this.addChangedID(addon.guid, date.getTime() / 1000);
+ this.score += SCORE_INCREMENT_XLARGE;
+ },
+
+ startTracking: function() {
+ if (this.engine.enabled) {
+ this.reconciler.startListening();
+ }
+
+ this.reconciler.addChangeListener(this);
+ },
+
+ stopTracking: function() {
+ this.reconciler.removeChangeListener(this);
+ this.reconciler.stopListening();
+ },
+};
diff --git a/components/weave/src/engines/bookmarks.js b/components/weave/src/engines/bookmarks.js
new file mode 100644
index 000000000..61e771134
--- /dev/null
+++ b/components/weave/src/engines/bookmarks.js
@@ -0,0 +1,1542 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ['BookmarksEngine', "PlacesItem", "Bookmark",
+ "BookmarkFolder", "BookmarkQuery",
+ "Livemark", "BookmarkSeparator"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Async.jsm");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/util.js");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/PlacesBackups.jsm");
+
+const ALLBOOKMARKS_ANNO = "AllBookmarks";
+const DESCRIPTION_ANNO = "bookmarkProperties/description";
+const SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
+const MOBILEROOT_ANNO = "mobile/bookmarksRoot";
+const MOBILE_ANNO = "MobileBookmarks";
+const EXCLUDEBACKUP_ANNO = "places/excludeFromBackup";
+const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
+const PARENT_ANNO = "sync/parent";
+const ORGANIZERQUERY_ANNO = "PlacesOrganizer/OrganizerQuery";
+const ANNOS_TO_TRACK = [DESCRIPTION_ANNO, SIDEBAR_ANNO,
+ PlacesUtils.LMANNO_FEEDURI, PlacesUtils.LMANNO_SITEURI];
+
+const SERVICE_NOT_SUPPORTED = "Service not supported on this platform";
+const FOLDER_SORTINDEX = 1000000;
+
+this.PlacesItem = function PlacesItem(collection, id, type) {
+ CryptoWrapper.call(this, collection, id);
+ this.type = type || "item";
+}
+PlacesItem.prototype = {
+ decrypt: function PlacesItem_decrypt(keyBundle) {
+ // Do the normal CryptoWrapper decrypt, but change types before returning
+ let clear = CryptoWrapper.prototype.decrypt.call(this, keyBundle);
+
+ // Convert the abstract places item to the actual object type
+ if (!this.deleted)
+ this.__proto__ = this.getTypeObject(this.type).prototype;
+
+ return clear;
+ },
+
+ getTypeObject: function PlacesItem_getTypeObject(type) {
+ switch (type) {
+ case "bookmark":
+ case "microsummary":
+ return Bookmark;
+ case "query":
+ return BookmarkQuery;
+ case "folder":
+ return BookmarkFolder;
+ case "livemark":
+ return Livemark;
+ case "separator":
+ return BookmarkSeparator;
+ case "item":
+ return PlacesItem;
+ }
+ throw "Unknown places item object type: " + type;
+ },
+
+ __proto__: CryptoWrapper.prototype,
+ _logName: "Sync.Record.PlacesItem",
+};
+
+Utils.deferGetSet(PlacesItem,
+ "cleartext",
+ ["hasDupe", "parentid", "parentName", "type"]);
+
+this.Bookmark = function Bookmark(collection, id, type) {
+ PlacesItem.call(this, collection, id, type || "bookmark");
+}
+Bookmark.prototype = {
+ __proto__: PlacesItem.prototype,
+ _logName: "Sync.Record.Bookmark",
+};
+
+Utils.deferGetSet(Bookmark,
+ "cleartext",
+ ["title", "bmkUri", "description",
+ "loadInSidebar", "tags", "keyword"]);
+
+this.BookmarkQuery = function BookmarkQuery(collection, id) {
+ Bookmark.call(this, collection, id, "query");
+}
+BookmarkQuery.prototype = {
+ __proto__: Bookmark.prototype,
+ _logName: "Sync.Record.BookmarkQuery",
+};
+
+Utils.deferGetSet(BookmarkQuery,
+ "cleartext",
+ ["folderName", "queryId"]);
+
+this.BookmarkFolder = function BookmarkFolder(collection, id, type) {
+ PlacesItem.call(this, collection, id, type || "folder");
+}
+BookmarkFolder.prototype = {
+ __proto__: PlacesItem.prototype,
+ _logName: "Sync.Record.Folder",
+};
+
+Utils.deferGetSet(BookmarkFolder, "cleartext", ["description", "title",
+ "children"]);
+
+this.Livemark = function Livemark(collection, id) {
+ BookmarkFolder.call(this, collection, id, "livemark");
+}
+Livemark.prototype = {
+ __proto__: BookmarkFolder.prototype,
+ _logName: "Sync.Record.Livemark",
+};
+
+Utils.deferGetSet(Livemark, "cleartext", ["siteUri", "feedUri"]);
+
+this.BookmarkSeparator = function BookmarkSeparator(collection, id) {
+ PlacesItem.call(this, collection, id, "separator");
+}
+BookmarkSeparator.prototype = {
+ __proto__: PlacesItem.prototype,
+ _logName: "Sync.Record.Separator",
+};
+
+Utils.deferGetSet(BookmarkSeparator, "cleartext", "pos");
+
+
+let kSpecialIds = {
+
+ // Special IDs. Note that mobile can attempt to create a record on
+ // dereference; special accessors are provided to prevent recursion within
+ // observers.
+ guids: ["menu", "places", "tags", "toolbar", "unfiled", "mobile"],
+
+ // Create the special mobile folder to store mobile bookmarks.
+ createMobileRoot: function createMobileRoot() {
+ let root = PlacesUtils.placesRootId;
+ let mRoot = PlacesUtils.bookmarks.createFolder(root, "mobile", -1);
+ PlacesUtils.annotations.setItemAnnotation(
+ mRoot, MOBILEROOT_ANNO, 1, 0, PlacesUtils.annotations.EXPIRE_NEVER);
+ PlacesUtils.annotations.setItemAnnotation(
+ mRoot, EXCLUDEBACKUP_ANNO, 1, 0, PlacesUtils.annotations.EXPIRE_NEVER);
+ return mRoot;
+ },
+
+ findMobileRoot: function findMobileRoot(create) {
+ // Use the (one) mobile root if it already exists.
+ let root = PlacesUtils.annotations.getItemsWithAnnotation(MOBILEROOT_ANNO, {});
+ if (root.length != 0)
+ return root[0];
+
+ if (create)
+ return this.createMobileRoot();
+
+ return null;
+ },
+
+ // Accessors for IDs.
+ isSpecialGUID: function isSpecialGUID(g) {
+ return this.guids.indexOf(g) != -1;
+ },
+
+ specialIdForGUID: function specialIdForGUID(guid, create) {
+ if (guid == "mobile") {
+ return this.findMobileRoot(create);
+ }
+ return this[guid];
+ },
+
+ // Don't bother creating mobile: if it doesn't exist, this ID can't be it!
+ specialGUIDForId: function specialGUIDForId(id) {
+ for each (let guid in this.guids)
+ if (this.specialIdForGUID(guid, false) == id)
+ return guid;
+ return null;
+ },
+
+ get menu() {
+ return PlacesUtils.bookmarksMenuFolderId;
+ },
+ get places() {
+ return PlacesUtils.placesRootId;
+ },
+ get tags() {
+ return PlacesUtils.tagsFolderId;
+ },
+ get toolbar() {
+ return PlacesUtils.toolbarFolderId;
+ },
+ get unfiled() {
+ return PlacesUtils.unfiledBookmarksFolderId;
+ },
+ get mobile() {
+ return this.findMobileRoot(true);
+ },
+};
+
+this.BookmarksEngine = function BookmarksEngine(service) {
+ SyncEngine.call(this, "Bookmarks", service);
+}
+BookmarksEngine.prototype = {
+ __proto__: SyncEngine.prototype,
+ _recordObj: PlacesItem,
+ _storeObj: BookmarksStore,
+ _trackerObj: BookmarksTracker,
+ version: 2,
+ _defaultSort: "index",
+
+ syncPriority: 4,
+
+ _sync: function _sync() {
+ let engine = this;
+ let batchEx = null;
+
+ // Try running sync in batch mode
+ PlacesUtils.bookmarks.runInBatchMode({
+ runBatched: function wrappedSync() {
+ try {
+ SyncEngine.prototype._sync.call(engine);
+ }
+ catch(ex) {
+ batchEx = ex;
+ }
+ }
+ }, null);
+
+ // Expose the exception if something inside the batch failed
+ if (batchEx != null) {
+ throw batchEx;
+ }
+ },
+
+ _guidMapFailed: false,
+ _buildGUIDMap: function _buildGUIDMap() {
+ let guidMap = {};
+ for (let guid in this._store.getAllIDs()) {
+ // Figure out with which key to store the mapping.
+ let key;
+ let id = this._store.idForGUID(guid);
+ switch (PlacesUtils.bookmarks.getItemType(id)) {
+ case PlacesUtils.bookmarks.TYPE_BOOKMARK:
+
+ // Smart bookmarks map to their annotation value.
+ let queryId;
+ try {
+ queryId = PlacesUtils.annotations.getItemAnnotation(
+ id, SMART_BOOKMARKS_ANNO);
+ } catch(ex) {}
+
+ if (queryId)
+ key = "q" + queryId;
+ else
+ key = "b" + PlacesUtils.bookmarks.getBookmarkURI(id).spec + ":" +
+ PlacesUtils.bookmarks.getItemTitle(id);
+ break;
+ case PlacesUtils.bookmarks.TYPE_FOLDER:
+ key = "f" + PlacesUtils.bookmarks.getItemTitle(id);
+ break;
+ case PlacesUtils.bookmarks.TYPE_SEPARATOR:
+ key = "s" + PlacesUtils.bookmarks.getItemIndex(id);
+ break;
+ default:
+ continue;
+ }
+
+ // The mapping is on a per parent-folder-name basis.
+ let parent = PlacesUtils.bookmarks.getFolderIdForItem(id);
+ if (parent <= 0)
+ continue;
+
+ let parentName = PlacesUtils.bookmarks.getItemTitle(parent);
+ if (guidMap[parentName] == null)
+ guidMap[parentName] = {};
+
+ // If the entry already exists, remember that there are explicit dupes.
+ let entry = new String(guid);
+ entry.hasDupe = guidMap[parentName][key] != null;
+
+ // Remember this item's GUID for its parent-name/key pair.
+ guidMap[parentName][key] = entry;
+ this._log.trace("Mapped: " + [parentName, key, entry, entry.hasDupe]);
+ }
+
+ return guidMap;
+ },
+
+ // Helper function to get a dupe GUID for an item.
+ _mapDupe: function _mapDupe(item) {
+ // Figure out if we have something to key with.
+ let key;
+ let altKey;
+ switch (item.type) {
+ case "query":
+ // Prior to Bug 610501, records didn't carry their Smart Bookmark
+ // anno, so we won't be able to dupe them correctly. This altKey
+ // hack should get them to dupe correctly.
+ if (item.queryId) {
+ key = "q" + item.queryId;
+ altKey = "b" + item.bmkUri + ":" + item.title;
+ break;
+ }
+ // No queryID? Fall through to the regular bookmark case.
+ case "bookmark":
+ case "microsummary":
+ key = "b" + item.bmkUri + ":" + item.title;
+ break;
+ case "folder":
+ case "livemark":
+ key = "f" + item.title;
+ break;
+ case "separator":
+ key = "s" + item.pos;
+ break;
+ default:
+ return;
+ }
+
+ // Figure out if we have a map to use!
+ // This will throw in some circumstances. That's fine.
+ let guidMap = this._guidMap;
+
+ // Give the GUID if we have the matching pair.
+ this._log.trace("Finding mapping: " + item.parentName + ", " + key);
+ let parent = guidMap[item.parentName];
+
+ if (!parent) {
+ this._log.trace("No parent => no dupe.");
+ return undefined;
+ }
+
+ let dupe = parent[key];
+
+ if (dupe) {
+ this._log.trace("Mapped dupe: " + dupe);
+ return dupe;
+ }
+
+ if (altKey) {
+ dupe = parent[altKey];
+ if (dupe) {
+ this._log.trace("Mapped dupe using altKey " + altKey + ": " + dupe);
+ return dupe;
+ }
+ }
+
+ this._log.trace("No dupe found for key " + key + "/" + altKey + ".");
+ return undefined;
+ },
+
+ _syncStartup: function _syncStart() {
+ SyncEngine.prototype._syncStartup.call(this);
+
+ let cb = Async.makeSpinningCallback();
+ Task.spawn(function() {
+ // For first-syncs, make a backup for the user to restore
+ if (this.lastSync == 0) {
+ this._log.debug("Bookmarks backup starting.");
+ yield PlacesBackups.create(null, true);
+ this._log.debug("Bookmarks backup done.");
+ }
+ }.bind(this)).then(
+ cb, ex => {
+ // Failure to create a backup is somewhat bad, but probably not bad
+ // enough to prevent syncing of bookmarks - so just log the error and
+ // continue.
+ this._log.warn("Got exception backing up bookmarks, but continuing with sync.", ex);
+ cb();
+ }
+ );
+
+ cb.wait();
+
+ this.__defineGetter__("_guidMap", function() {
+ // Create a mapping of folder titles and separator positions to GUID.
+ // We do this lazily so that we don't do any work unless we reconcile
+ // incoming items.
+ let guidMap;
+ try {
+ guidMap = this._buildGUIDMap();
+ } catch (ex) {
+ this._log.warn("Got exception building GUID map." +
+ " Skipping all other incoming items.", ex);
+ throw {code: Engine.prototype.eEngineAbortApplyIncoming,
+ cause: ex};
+ }
+ delete this._guidMap;
+ return this._guidMap = guidMap;
+ });
+
+ this._store._childrenToOrder = {};
+ },
+
+ _processIncoming: function (newitems) {
+ try {
+ SyncEngine.prototype._processIncoming.call(this, newitems);
+ } finally {
+ // Reorder children.
+ this._tracker.ignoreAll = true;
+ this._store._orderChildren();
+ this._tracker.ignoreAll = false;
+ delete this._store._childrenToOrder;
+ }
+ },
+
+ _syncFinish: function _syncFinish() {
+ SyncEngine.prototype._syncFinish.call(this);
+ this._tracker._ensureMobileQuery();
+ },
+
+ _syncCleanup: function _syncCleanup() {
+ SyncEngine.prototype._syncCleanup.call(this);
+ delete this._guidMap;
+ },
+
+ _createRecord: function _createRecord(id) {
+ // Create the record as usual, but mark it as having dupes if necessary.
+ let record = SyncEngine.prototype._createRecord.call(this, id);
+ let entry = this._mapDupe(record);
+ if (entry != null && entry.hasDupe) {
+ record.hasDupe = true;
+ }
+ return record;
+ },
+
+ _findDupe: function _findDupe(item) {
+ this._log.trace("Finding dupe for " + item.id +
+ " (already duped: " + item.hasDupe + ").");
+
+ // Don't bother finding a dupe if the incoming item has duplicates.
+ if (item.hasDupe) {
+ this._log.trace(item.id + " already a dupe: not finding one.");
+ return;
+ }
+ let mapped = this._mapDupe(item);
+ this._log.debug(item.id + " mapped to " + mapped);
+ return mapped;
+ }
+};
+
+function BookmarksStore(name, engine) {
+ Store.call(this, name, engine);
+
+ // Explicitly nullify our references to our cached services so we don't leak
+ Svc.Obs.add("places-shutdown", function() {
+ for each (let [query, stmt] in Iterator(this._stmts)) {
+ stmt.finalize();
+ }
+ this._stmts = {};
+ }, this);
+}
+BookmarksStore.prototype = {
+ __proto__: Store.prototype,
+
+ itemExists: function BStore_itemExists(id) {
+ return this.idForGUID(id, true) > 0;
+ },
+
+ /*
+ * If the record is a tag query, rewrite it to refer to the local tag ID.
+ *
+ * Otherwise, just return.
+ */
+ preprocessTagQuery: function preprocessTagQuery(record) {
+ if (record.type != "query" ||
+ record.bmkUri == null ||
+ !record.folderName)
+ return;
+
+ // Yes, this works without chopping off the "place:" prefix.
+ let uri = record.bmkUri
+ let queriesRef = {};
+ let queryCountRef = {};
+ let optionsRef = {};
+ PlacesUtils.history.queryStringToQueries(uri, queriesRef, queryCountRef,
+ optionsRef);
+
+ // We only process tag URIs.
+ if (optionsRef.value.resultType != optionsRef.value.RESULTS_AS_TAG_CONTENTS)
+ return;
+
+ // Tag something to ensure that the tag exists.
+ let tag = record.folderName;
+ let dummyURI = Utils.makeURI("about:weave#BStore_preprocess");
+ PlacesUtils.tagging.tagURI(dummyURI, [tag]);
+
+ // Look for the id of the tag, which might just have been added.
+ let tags = this._getNode(PlacesUtils.tagsFolderId);
+ if (!(tags instanceof Ci.nsINavHistoryQueryResultNode)) {
+ this._log.debug("tags isn't an nsINavHistoryQueryResultNode; aborting.");
+ return;
+ }
+
+ tags.containerOpen = true;
+ try {
+ for (let i = 0; i < tags.childCount; i++) {
+ let child = tags.getChild(i);
+ if (child.title == tag) {
+ // Found the tag, so fix up the query to use the right id.
+ this._log.debug("Tag query folder: " + tag + " = " + child.itemId);
+
+ this._log.trace("Replacing folders in: " + uri);
+ for each (let q in queriesRef.value)
+ q.setFolders([child.itemId], 1);
+
+ record.bmkUri = PlacesUtils.history.queriesToQueryString(
+ queriesRef.value, queryCountRef.value, optionsRef.value);
+ return;
+ }
+ }
+ }
+ finally {
+ tags.containerOpen = false;
+ }
+ },
+
+ applyIncoming: function BStore_applyIncoming(record) {
+ this._log.debug("Applying record " + record.id);
+ let isSpecial = record.id in kSpecialIds;
+
+ if (record.deleted) {
+ if (isSpecial) {
+ this._log.warn("Ignoring deletion for special record " + record.id);
+ return;
+ }
+
+ // Don't bother with pre and post-processing for deletions.
+ Store.prototype.applyIncoming.call(this, record);
+ return;
+ }
+
+ // For special folders we're only interested in child ordering.
+ if (isSpecial && record.children) {
+ this._log.debug("Processing special node: " + record.id);
+ // Reorder children later
+ this._childrenToOrder[record.id] = record.children;
+ return;
+ }
+
+ // Skip malformed records. (Bug 806460.)
+ if (record.type == "query" &&
+ !record.bmkUri) {
+ this._log.warn("Skipping malformed query bookmark: " + record.id);
+ return;
+ }
+
+ // Preprocess the record before doing the normal apply.
+ this.preprocessTagQuery(record);
+
+ // Figure out the local id of the parent GUID if available
+ let parentGUID = record.parentid;
+ if (!parentGUID) {
+ throw "Record " + record.id + " has invalid parentid: " + parentGUID;
+ }
+ this._log.debug("Local parent is " + parentGUID);
+
+ let parentId = this.idForGUID(parentGUID);
+ if (parentId > 0) {
+ // Save the parent id for modifying the bookmark later
+ record._parent = parentId;
+ record._orphan = false;
+ this._log.debug("Record " + record.id + " is not an orphan.");
+ } else {
+ this._log.trace("Record " + record.id +
+ " is an orphan: could not find parent " + parentGUID);
+ record._orphan = true;
+ }
+
+ // Do the normal processing of incoming records
+ Store.prototype.applyIncoming.call(this, record);
+
+ // Do some post-processing if we have an item
+ let itemId = this.idForGUID(record.id);
+ if (itemId > 0) {
+ // Move any children that are looking for this folder as a parent
+ if (record.type == "folder") {
+ this._reparentOrphans(itemId);
+ // Reorder children later
+ if (record.children)
+ this._childrenToOrder[record.id] = record.children;
+ }
+
+ // Create an annotation to remember that it needs reparenting.
+ if (record._orphan) {
+ PlacesUtils.annotations.setItemAnnotation(
+ itemId, PARENT_ANNO, parentGUID, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+ }
+ },
+
+ /**
+ * Find all ids of items that have a given value for an annotation
+ */
+ _findAnnoItems: function BStore__findAnnoItems(anno, val) {
+ return PlacesUtils.annotations.getItemsWithAnnotation(anno, {})
+ .filter(function(id) {
+ return PlacesUtils.annotations.getItemAnnotation(id, anno) == val;
+ });
+ },
+
+ /**
+ * For the provided parent item, attach its children to it
+ */
+ _reparentOrphans: function _reparentOrphans(parentId) {
+ // Find orphans and reunite with this folder parent
+ let parentGUID = this.GUIDForId(parentId);
+ let orphans = this._findAnnoItems(PARENT_ANNO, parentGUID);
+
+ this._log.debug("Reparenting orphans " + orphans + " to " + parentId);
+ orphans.forEach(function(orphan) {
+ // Move the orphan to the parent and drop the missing parent annotation
+ if (this._reparentItem(orphan, parentId)) {
+ PlacesUtils.annotations.removeItemAnnotation(orphan, PARENT_ANNO);
+ }
+ }, this);
+ },
+
+ _reparentItem: function _reparentItem(itemId, parentId) {
+ this._log.trace("Attempting to move item " + itemId + " to new parent " +
+ parentId);
+ try {
+ if (parentId > 0) {
+ PlacesUtils.bookmarks.moveItem(itemId, parentId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ return true;
+ }
+ } catch(ex) {
+ this._log.debug("Failed to reparent item. ", ex);
+ }
+ return false;
+ },
+
+ // Turn a record's nsINavBookmarksService constant and other attributes into
+ // a granular type for comparison.
+ _recordType: function _recordType(itemId) {
+ let bms = PlacesUtils.bookmarks;
+ let type = bms.getItemType(itemId);
+
+ switch (type) {
+ case bms.TYPE_FOLDER:
+ if (PlacesUtils.annotations
+ .itemHasAnnotation(itemId, PlacesUtils.LMANNO_FEEDURI)) {
+ return "livemark";
+ }
+ return "folder";
+
+ case bms.TYPE_BOOKMARK:
+ let bmkUri = bms.getBookmarkURI(itemId).spec;
+ if (bmkUri.indexOf("place:") == 0) {
+ return "query";
+ }
+ return "bookmark";
+
+ case bms.TYPE_SEPARATOR:
+ return "separator";
+
+ default:
+ return null;
+ }
+ },
+
+ create: function BStore_create(record) {
+ // Default to unfiled if we don't have the parent yet.
+
+ // Valid parent IDs are all positive integers. Other values -- undefined,
+ // null, -1 -- all compare false for > 0, so this catches them all. We
+ // don't just use <= without the !, because undefined and null compare
+ // false for that, too!
+ if (!(record._parent > 0)) {
+ this._log.debug("Parent is " + record._parent + "; reparenting to unfiled.");
+ record._parent = kSpecialIds.unfiled;
+ }
+
+ let newId;
+ switch (record.type) {
+ case "bookmark":
+ case "query":
+ case "microsummary": {
+ let uri = Utils.makeURI(record.bmkUri);
+ newId = PlacesUtils.bookmarks.insertBookmark(
+ record._parent, uri, PlacesUtils.bookmarks.DEFAULT_INDEX, record.title);
+ this._log.debug("created bookmark " + newId + " under " + record._parent
+ + " as " + record.title + " " + record.bmkUri);
+
+ // Smart bookmark annotations are strings.
+ if (record.queryId) {
+ PlacesUtils.annotations.setItemAnnotation(
+ newId, SMART_BOOKMARKS_ANNO, record.queryId, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+
+ if (Array.isArray(record.tags)) {
+ this._tagURI(uri, record.tags);
+ }
+ PlacesUtils.bookmarks.setKeywordForBookmark(newId, record.keyword);
+ if (record.description) {
+ PlacesUtils.annotations.setItemAnnotation(
+ newId, DESCRIPTION_ANNO, record.description, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+
+ if (record.loadInSidebar) {
+ PlacesUtils.annotations.setItemAnnotation(
+ newId, SIDEBAR_ANNO, true, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+
+ } break;
+ case "folder":
+ newId = PlacesUtils.bookmarks.createFolder(
+ record._parent, record.title, PlacesUtils.bookmarks.DEFAULT_INDEX);
+ this._log.debug("created folder " + newId + " under " + record._parent
+ + " as " + record.title);
+
+ if (record.description) {
+ PlacesUtils.annotations.setItemAnnotation(
+ newId, DESCRIPTION_ANNO, record.description, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+
+ // record.children will be dealt with in _orderChildren.
+ break;
+ case "livemark":
+ let siteURI = null;
+ if (!record.feedUri) {
+ this._log.debug("No feed URI: skipping livemark record " + record.id);
+ return;
+ }
+ if (PlacesUtils.annotations
+ .itemHasAnnotation(record._parent, PlacesUtils.LMANNO_FEEDURI)) {
+ this._log.debug("Invalid parent: skipping livemark record " + record.id);
+ return;
+ }
+
+ if (record.siteUri != null)
+ siteURI = Utils.makeURI(record.siteUri);
+
+ // Until this engine can handle asynchronous error reporting, we need to
+ // detect errors on creation synchronously.
+ let spinningCb = Async.makeSpinningCallback();
+
+ let livemarkObj = {title: record.title,
+ parentId: record._parent,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ feedURI: Utils.makeURI(record.feedUri),
+ siteURI: siteURI,
+ guid: record.id};
+ PlacesUtils.livemarks.addLivemark(livemarkObj).then(
+ aLivemark => { spinningCb(null, [Components.results.NS_OK, aLivemark]) },
+ () => { spinningCb(null, [Components.results.NS_ERROR_UNEXPECTED, aLivemark]) }
+ );
+
+ let [status, livemark] = spinningCb.wait();
+ if (!Components.isSuccessCode(status)) {
+ throw status;
+ }
+
+ this._log.debug("Created livemark " + livemark.id + " under " +
+ livemark.parentId + " as " + livemark.title +
+ ", " + livemark.siteURI.spec + ", " +
+ livemark.feedURI.spec + ", GUID " +
+ livemark.guid);
+ break;
+ case "separator":
+ newId = PlacesUtils.bookmarks.insertSeparator(
+ record._parent, PlacesUtils.bookmarks.DEFAULT_INDEX);
+ this._log.debug("created separator " + newId + " under " + record._parent);
+ break;
+ case "item":
+ this._log.debug(" -> got a generic places item.. do nothing?");
+ return;
+ default:
+ this._log.error("_create: Unknown item type: " + record.type);
+ return;
+ }
+
+ if (newId) {
+ // Livemarks can set the GUID through the API, so there's no need to
+ // do that here.
+ this._log.trace("Setting GUID of new item " + newId + " to " + record.id);
+ this._setGUID(newId, record.id);
+ }
+ },
+
+ // Factored out of `remove` to avoid redundant DB queries when the Places ID
+ // is already known.
+ removeById: function removeById(itemId, guid) {
+ let type = PlacesUtils.bookmarks.getItemType(itemId);
+
+ switch (type) {
+ case PlacesUtils.bookmarks.TYPE_BOOKMARK:
+ this._log.debug(" -> removing bookmark " + guid);
+ PlacesUtils.bookmarks.removeItem(itemId);
+ break;
+ case PlacesUtils.bookmarks.TYPE_FOLDER:
+ this._log.debug(" -> removing folder " + guid);
+ PlacesUtils.bookmarks.removeItem(itemId);
+ break;
+ case PlacesUtils.bookmarks.TYPE_SEPARATOR:
+ this._log.debug(" -> removing separator " + guid);
+ PlacesUtils.bookmarks.removeItem(itemId);
+ break;
+ default:
+ this._log.error("remove: Unknown item type: " + type);
+ break;
+ }
+ },
+
+ remove: function BStore_remove(record) {
+ if (kSpecialIds.isSpecialGUID(record.id)) {
+ this._log.warn("Refusing to remove special folder " + record.id);
+ return;
+ }
+
+ let itemId = this.idForGUID(record.id);
+ if (itemId <= 0) {
+ this._log.debug("Item " + record.id + " already removed");
+ return;
+ }
+ this.removeById(itemId, record.id);
+ },
+
+ _taggableTypes: ["bookmark", "microsummary", "query"],
+ isTaggable: function isTaggable(recordType) {
+ return this._taggableTypes.indexOf(recordType) != -1;
+ },
+
+ update: function BStore_update(record) {
+ let itemId = this.idForGUID(record.id);
+
+ if (itemId <= 0) {
+ this._log.debug("Skipping update for unknown item: " + record.id);
+ return;
+ }
+
+ // Two items are the same type if they have the same ItemType in Places,
+ // and also share some key characteristics (e.g., both being livemarks).
+ // We figure this out by examining the item to find the equivalent granular
+ // (string) type.
+ // If they're not the same type, we can't just update attributes. Delete
+ // then recreate the record instead.
+ let localItemType = this._recordType(itemId);
+ let remoteRecordType = record.type;
+ this._log.trace("Local type: " + localItemType + ". " +
+ "Remote type: " + remoteRecordType + ".");
+
+ if (localItemType != remoteRecordType) {
+ this._log.debug("Local record and remote record differ in type. " +
+ "Deleting and recreating.");
+ this.removeById(itemId, record.id);
+ this.create(record);
+ return;
+ }
+
+ this._log.trace("Updating " + record.id + " (" + itemId + ")");
+
+ // Move the bookmark to a new parent or new position if necessary
+ if (record._parent > 0 &&
+ PlacesUtils.bookmarks.getFolderIdForItem(itemId) != record._parent) {
+ this._reparentItem(itemId, record._parent);
+ }
+
+ for (let [key, val] in Iterator(record.cleartext)) {
+ switch (key) {
+ case "title":
+ PlacesUtils.bookmarks.setItemTitle(itemId, val);
+ break;
+ case "bmkUri":
+ PlacesUtils.bookmarks.changeBookmarkURI(itemId, Utils.makeURI(val));
+ break;
+ case "tags":
+ if (Array.isArray(val)) {
+ if (this.isTaggable(remoteRecordType)) {
+ this._tagID(itemId, val);
+ } else {
+ this._log.debug("Remote record type is invalid for tags: " + remoteRecordType);
+ }
+ }
+ break;
+ case "keyword":
+ PlacesUtils.bookmarks.setKeywordForBookmark(itemId, val);
+ break;
+ case "description":
+ if (val) {
+ PlacesUtils.annotations.setItemAnnotation(
+ itemId, DESCRIPTION_ANNO, val, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ } else {
+ PlacesUtils.annotations.removeItemAnnotation(itemId, DESCRIPTION_ANNO);
+ }
+ break;
+ case "loadInSidebar":
+ if (val) {
+ PlacesUtils.annotations.setItemAnnotation(
+ itemId, SIDEBAR_ANNO, true, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ } else {
+ PlacesUtils.annotations.removeItemAnnotation(itemId, SIDEBAR_ANNO);
+ }
+ break;
+ case "queryId":
+ PlacesUtils.annotations.setItemAnnotation(
+ itemId, SMART_BOOKMARKS_ANNO, val, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ break;
+ }
+ }
+ },
+
+ _orderChildren: function _orderChildren() {
+ for (let [guid, children] in Iterator(this._childrenToOrder)) {
+ // Reorder children according to the GUID list. Gracefully deal
+ // with missing items, e.g. locally deleted.
+ let delta = 0;
+ let parent = null;
+ for (let idx = 0; idx < children.length; idx++) {
+ let itemid = this.idForGUID(children[idx]);
+ if (itemid == -1) {
+ delta += 1;
+ this._log.trace("Could not locate record " + children[idx]);
+ continue;
+ }
+ try {
+ // This code path could be optimized by caching the parent earlier.
+ // Doing so should take in count any edge case due to reparenting
+ // or parent invalidations though.
+ if (!parent) {
+ parent = PlacesUtils.bookmarks.getFolderIdForItem(itemid);
+ }
+ PlacesUtils.bookmarks.moveItem(itemid, parent, idx - delta);
+ } catch (ex) {
+ this._log.debug("Could not move item " + children[idx] + ": " + ex);
+ }
+ }
+ }
+ },
+
+ changeItemID: function BStore_changeItemID(oldID, newID) {
+ this._log.debug("Changing GUID " + oldID + " to " + newID);
+
+ // Make sure there's an item to change GUIDs
+ let itemId = this.idForGUID(oldID);
+ if (itemId <= 0)
+ return;
+
+ this._setGUID(itemId, newID);
+ },
+
+ _getNode: function BStore__getNode(folder) {
+ let query = PlacesUtils.history.getNewQuery();
+ query.setFolders([folder], 1);
+ return PlacesUtils.history.executeQuery(
+ query, PlacesUtils.history.getNewQueryOptions()).root;
+ },
+
+ _getTags: function BStore__getTags(uri) {
+ try {
+ if (typeof(uri) == "string")
+ uri = Utils.makeURI(uri);
+ } catch(e) {
+ this._log.warn("Could not parse URI \"" + uri + "\": " + e);
+ }
+ return PlacesUtils.tagging.getTagsForURI(uri, {});
+ },
+
+ _getDescription: function BStore__getDescription(id) {
+ try {
+ return PlacesUtils.annotations.getItemAnnotation(id, DESCRIPTION_ANNO);
+ } catch (e) {
+ return null;
+ }
+ },
+
+ _isLoadInSidebar: function BStore__isLoadInSidebar(id) {
+ return PlacesUtils.annotations.itemHasAnnotation(id, SIDEBAR_ANNO);
+ },
+
+ get _childGUIDsStm() {
+ return this._getStmt(
+ "SELECT id AS item_id, guid " +
+ "FROM moz_bookmarks " +
+ "WHERE parent = :parent " +
+ "ORDER BY position");
+ },
+ _childGUIDsCols: ["item_id", "guid"],
+
+ _getChildGUIDsForId: function _getChildGUIDsForId(itemid) {
+ let stmt = this._childGUIDsStm;
+ stmt.params.parent = itemid;
+ let rows = Async.querySpinningly(stmt, this._childGUIDsCols);
+ return rows.map(function (row) {
+ if (row.guid) {
+ return row.guid;
+ }
+ // A GUID hasn't been assigned to this item yet, do this now.
+ return this.GUIDForId(row.item_id);
+ }, this);
+ },
+
+ // Create a record starting from the weave id (places guid)
+ createRecord: function createRecord(id, collection) {
+ let placeId = this.idForGUID(id);
+ let record;
+ if (placeId <= 0) { // deleted item
+ record = new PlacesItem(collection, id);
+ record.deleted = true;
+ return record;
+ }
+
+ let parent = PlacesUtils.bookmarks.getFolderIdForItem(placeId);
+ switch (PlacesUtils.bookmarks.getItemType(placeId)) {
+ case PlacesUtils.bookmarks.TYPE_BOOKMARK:
+ let bmkUri = PlacesUtils.bookmarks.getBookmarkURI(placeId).spec;
+ if (bmkUri.indexOf("place:") == 0) {
+ record = new BookmarkQuery(collection, id);
+
+ // Get the actual tag name instead of the local itemId
+ let folder = bmkUri.match(/[:&]folder=(\d+)/);
+ try {
+ // There might not be the tag yet when creating on a new client
+ if (folder != null) {
+ folder = folder[1];
+ record.folderName = PlacesUtils.bookmarks.getItemTitle(folder);
+ this._log.trace("query id: " + folder + " = " + record.folderName);
+ }
+ }
+ catch(ex) {}
+
+ // Persist the Smart Bookmark anno, if found.
+ try {
+ let anno = PlacesUtils.annotations.getItemAnnotation(placeId, SMART_BOOKMARKS_ANNO);
+ if (anno != null) {
+ this._log.trace("query anno: " + SMART_BOOKMARKS_ANNO +
+ " = " + anno);
+ record.queryId = anno;
+ }
+ }
+ catch(ex) {}
+ }
+ else {
+ record = new Bookmark(collection, id);
+ }
+ record.title = PlacesUtils.bookmarks.getItemTitle(placeId);
+
+ record.parentName = PlacesUtils.bookmarks.getItemTitle(parent);
+ record.bmkUri = bmkUri;
+ record.tags = this._getTags(record.bmkUri);
+ record.keyword = PlacesUtils.bookmarks.getKeywordForBookmark(placeId);
+ record.description = this._getDescription(placeId);
+ record.loadInSidebar = this._isLoadInSidebar(placeId);
+ break;
+
+ case PlacesUtils.bookmarks.TYPE_FOLDER:
+ if (PlacesUtils.annotations
+ .itemHasAnnotation(placeId, PlacesUtils.LMANNO_FEEDURI)) {
+ record = new Livemark(collection, id);
+ let as = PlacesUtils.annotations;
+ record.feedUri = as.getItemAnnotation(placeId, PlacesUtils.LMANNO_FEEDURI);
+ try {
+ record.siteUri = as.getItemAnnotation(placeId, PlacesUtils.LMANNO_SITEURI);
+ } catch (ex) {}
+ } else {
+ record = new BookmarkFolder(collection, id);
+ }
+
+ if (parent > 0)
+ record.parentName = PlacesUtils.bookmarks.getItemTitle(parent);
+ record.title = PlacesUtils.bookmarks.getItemTitle(placeId);
+ record.description = this._getDescription(placeId);
+ record.children = this._getChildGUIDsForId(placeId);
+ break;
+
+ case PlacesUtils.bookmarks.TYPE_SEPARATOR:
+ record = new BookmarkSeparator(collection, id);
+ if (parent > 0)
+ record.parentName = PlacesUtils.bookmarks.getItemTitle(parent);
+ // Create a positioning identifier for the separator, used by _mapDupe
+ record.pos = PlacesUtils.bookmarks.getItemIndex(placeId);
+ break;
+
+ default:
+ record = new PlacesItem(collection, id);
+ this._log.warn("Unknown item type, cannot serialize: " +
+ PlacesUtils.bookmarks.getItemType(placeId));
+ }
+
+ record.parentid = this.GUIDForId(parent);
+ record.sortindex = this._calculateIndex(record);
+
+ return record;
+ },
+
+ _stmts: {},
+ _getStmt: function(query) {
+ if (query in this._stmts) {
+ return this._stmts[query];
+ }
+
+ this._log.trace("Creating SQL statement: " + query);
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ return this._stmts[query] = db.createAsyncStatement(query);
+ },
+
+ get _frecencyStm() {
+ return this._getStmt(
+ "SELECT frecency " +
+ "FROM moz_places " +
+ "WHERE url = :url " +
+ "LIMIT 1");
+ },
+ _frecencyCols: ["frecency"],
+
+ get _setGUIDStm() {
+ return this._getStmt(
+ "UPDATE moz_bookmarks " +
+ "SET guid = :guid " +
+ "WHERE id = :item_id");
+ },
+
+ // Some helper functions to handle GUIDs
+ _setGUID: function _setGUID(id, guid) {
+ if (!guid)
+ guid = Utils.makeGUID();
+
+ let stmt = this._setGUIDStm;
+ stmt.params.guid = guid;
+ stmt.params.item_id = id;
+ Async.querySpinningly(stmt);
+ return guid;
+ },
+
+ get _guidForIdStm() {
+ return this._getStmt(
+ "SELECT guid " +
+ "FROM moz_bookmarks " +
+ "WHERE id = :item_id");
+ },
+ _guidForIdCols: ["guid"],
+
+ GUIDForId: function GUIDForId(id) {
+ let special = kSpecialIds.specialGUIDForId(id);
+ if (special)
+ return special;
+
+ let stmt = this._guidForIdStm;
+ stmt.params.item_id = id;
+
+ // Use the existing GUID if it exists
+ let result = Async.querySpinningly(stmt, this._guidForIdCols)[0];
+ if (result && result.guid)
+ return result.guid;
+
+ // Give the uri a GUID if it doesn't have one
+ return this._setGUID(id);
+ },
+
+ get _idForGUIDStm() {
+ return this._getStmt(
+ "SELECT id AS item_id " +
+ "FROM moz_bookmarks " +
+ "WHERE guid = :guid");
+ },
+ _idForGUIDCols: ["item_id"],
+
+ // noCreate is provided as an optional argument to prevent the creation of
+ // non-existent special records, such as "mobile".
+ idForGUID: function idForGUID(guid, noCreate) {
+ if (kSpecialIds.isSpecialGUID(guid))
+ return kSpecialIds.specialIdForGUID(guid, !noCreate);
+
+ let stmt = this._idForGUIDStm;
+ // guid might be a String object rather than a string.
+ stmt.params.guid = guid.toString();
+
+ let results = Async.querySpinningly(stmt, this._idForGUIDCols);
+ this._log.trace("Number of rows matching GUID " + guid + ": "
+ + results.length);
+
+ // Here's the one we care about: the first.
+ let result = results[0];
+
+ if (!result)
+ return -1;
+
+ return result.item_id;
+ },
+
+ _calculateIndex: function _calculateIndex(record) {
+ // Ensure folders have a very high sort index so they're not synced last.
+ if (record.type == "folder")
+ return FOLDER_SORTINDEX;
+
+ // For anything directly under the toolbar, give it a boost of more than an
+ // unvisited bookmark
+ let index = 0;
+ if (record.parentid == "toolbar")
+ index += 150;
+
+ // Add in the bookmark's frecency if we have something.
+ if (record.bmkUri != null) {
+ this._frecencyStm.params.url = record.bmkUri;
+ let result = Async.querySpinningly(this._frecencyStm, this._frecencyCols);
+ if (result.length)
+ index += result[0].frecency;
+ }
+
+ return index;
+ },
+
+ _getChildren: function BStore_getChildren(guid, items) {
+ let node = guid; // the recursion case
+ if (typeof(node) == "string") { // callers will give us the guid as the first arg
+ let nodeID = this.idForGUID(guid, true);
+ if (!nodeID) {
+ this._log.debug("No node for GUID " + guid + "; returning no children.");
+ return items;
+ }
+ node = this._getNode(nodeID);
+ }
+
+ if (node.type == node.RESULT_TYPE_FOLDER) {
+ node.QueryInterface(Ci.nsINavHistoryQueryResultNode);
+ node.containerOpen = true;
+ try {
+ // Remember all the children GUIDs and recursively get more
+ for (let i = 0; i < node.childCount; i++) {
+ let child = node.getChild(i);
+ items[this.GUIDForId(child.itemId)] = true;
+ this._getChildren(child, items);
+ }
+ }
+ finally {
+ node.containerOpen = false;
+ }
+ }
+
+ return items;
+ },
+
+ /**
+ * Associates the URI of the item with the provided ID with the
+ * provided array of tags.
+ * If the provided ID does not identify an item with a URI,
+ * returns immediately.
+ */
+ _tagID: function _tagID(itemID, tags) {
+ if (!itemID || !tags) {
+ return;
+ }
+
+ try {
+ let u = PlacesUtils.bookmarks.getBookmarkURI(itemID);
+ this._tagURI(u, tags);
+ } catch (e) {
+ this._log.warn("Got exception fetching URI for " + itemID + ": not tagging. ", e);
+
+ // I guess it doesn't have a URI. Don't try to tag it.
+ return;
+ }
+ },
+
+ /**
+ * Associate the provided URI with the provided array of tags.
+ * If the provided URI is falsy, returns immediately.
+ */
+ _tagURI: function _tagURI(bookmarkURI, tags) {
+ if (!bookmarkURI || !tags) {
+ return;
+ }
+
+ // Filter out any null/undefined/empty tags.
+ tags = tags.filter(t => t);
+
+ // Temporarily tag a dummy URI to preserve tag ids when untagging.
+ let dummyURI = Utils.makeURI("about:weave#BStore_tagURI");
+ PlacesUtils.tagging.tagURI(dummyURI, tags);
+ PlacesUtils.tagging.untagURI(bookmarkURI, null);
+ PlacesUtils.tagging.tagURI(bookmarkURI, tags);
+ PlacesUtils.tagging.untagURI(dummyURI, null);
+ },
+
+ getAllIDs: function BStore_getAllIDs() {
+ let items = {"menu": true,
+ "toolbar": true};
+ for each (let guid in kSpecialIds.guids) {
+ if (guid != "places" && guid != "tags")
+ this._getChildren(guid, items);
+ }
+ return items;
+ },
+
+ wipe: function BStore_wipe() {
+ let cb = Async.makeSpinningCallback();
+ Task.spawn(function() {
+ // Save a backup before clearing out all bookmarks.
+ yield PlacesBackups.create(null, true);
+ for each (let guid in kSpecialIds.guids)
+ if (guid != "places") {
+ let id = kSpecialIds.specialIdForGUID(guid);
+ if (id)
+ PlacesUtils.bookmarks.removeFolderChildren(id);
+ }
+ cb();
+ });
+ cb.wait();
+ }
+};
+
+function BookmarksTracker(name, engine) {
+ Tracker.call(this, name, engine);
+
+ Svc.Obs.add("places-shutdown", this);
+}
+BookmarksTracker.prototype = {
+ __proto__: Tracker.prototype,
+
+ startTracking: function() {
+ PlacesUtils.bookmarks.addObserver(this, true);
+ Svc.Obs.add("bookmarks-restore-begin", this);
+ Svc.Obs.add("bookmarks-restore-success", this);
+ Svc.Obs.add("bookmarks-restore-failed", this);
+ },
+
+ stopTracking: function() {
+ PlacesUtils.bookmarks.removeObserver(this);
+ Svc.Obs.remove("bookmarks-restore-begin", this);
+ Svc.Obs.remove("bookmarks-restore-success", this);
+ Svc.Obs.remove("bookmarks-restore-failed", this);
+ },
+
+ observe: function observe(subject, topic, data) {
+ Tracker.prototype.observe.call(this, subject, topic, data);
+
+ switch (topic) {
+ case "bookmarks-restore-begin":
+ this._log.debug("Ignoring changes from importing bookmarks.");
+ this.ignoreAll = true;
+ break;
+ case "bookmarks-restore-success":
+ this._log.debug("Tracking all items on successful import.");
+ this.ignoreAll = false;
+
+ this._log.debug("Restore succeeded: wiping server and other clients.");
+ this.engine.service.resetClient([this.name]);
+ this.engine.service.wipeServer([this.name]);
+ this.engine.service.clientsEngine.sendCommand("wipeEngine", [this.name]);
+ break;
+ case "bookmarks-restore-failed":
+ this._log.debug("Tracking all items on failed import.");
+ this.ignoreAll = false;
+ break;
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver,
+ Ci.nsINavBookmarkObserver_MOZILLA_1_9_1_ADDITIONS,
+ Ci.nsISupportsWeakReference
+ ]),
+
+ /**
+ * Add a bookmark GUID to be uploaded and bump up the sync score.
+ *
+ * @param itemGuid
+ * GUID of the bookmark to upload.
+ */
+ _add: function BMT__add(itemId, guid) {
+ guid = kSpecialIds.specialGUIDForId(itemId) || guid;
+ if (this.addChangedID(guid))
+ this._upScore();
+ },
+
+ /* Every add/remove/change will trigger a sync for MULTI_DEVICE. */
+ _upScore: function BMT__upScore() {
+ this.score += SCORE_INCREMENT_XLARGE;
+ },
+
+ /**
+ * Determine if a change should be ignored.
+ *
+ * @param itemId
+ * Item under consideration to ignore
+ * @param folder (optional)
+ * Folder of the item being changed
+ */
+ _ignore: function BMT__ignore(itemId, folder, guid) {
+ // Ignore unconditionally if the engine tells us to.
+ if (this.ignoreAll)
+ return true;
+
+ // Get the folder id if we weren't given one.
+ if (folder == null) {
+ try {
+ folder = PlacesUtils.bookmarks.getFolderIdForItem(itemId);
+ } catch (ex) {
+ this._log.debug("getFolderIdForItem(" + itemId +
+ ") threw; calling _ensureMobileQuery.");
+ // I'm guessing that gFIFI can throw, and perhaps that's why
+ // _ensureMobileQuery is here at all. Try not to call it.
+ this._ensureMobileQuery();
+ folder = PlacesUtils.bookmarks.getFolderIdForItem(itemId);
+ }
+ }
+
+ // Ignore changes to tags (folders under the tags folder).
+ let tags = kSpecialIds.tags;
+ if (folder == tags)
+ return true;
+
+ // Ignore tag items (the actual instance of a tag for a bookmark).
+ if (PlacesUtils.bookmarks.getFolderIdForItem(folder) == tags)
+ return true;
+
+ // Make sure to remove items that have the exclude annotation.
+ if (PlacesUtils.annotations.itemHasAnnotation(itemId, EXCLUDEBACKUP_ANNO)) {
+ this.removeChangedID(guid);
+ return true;
+ }
+
+ return false;
+ },
+
+ onItemAdded: function BMT_onItemAdded(itemId, folder, index,
+ itemType, uri, title, dateAdded,
+ guid, parentGuid) {
+ if (this._ignore(itemId, folder, guid))
+ return;
+
+ this._log.trace("onItemAdded: " + itemId);
+ this._add(itemId, guid);
+ this._add(folder, parentGuid);
+ },
+
+ onItemRemoved: function (itemId, parentId, index, type, uri,
+ guid, parentGuid) {
+ if (this._ignore(itemId, parentId, guid)) {
+ return;
+ }
+
+ this._log.trace("onItemRemoved: " + itemId);
+ this._add(itemId, guid);
+ this._add(parentId, parentGuid);
+ },
+
+ _ensureMobileQuery: function _ensureMobileQuery() {
+ let find = val =>
+ PlacesUtils.annotations.getItemsWithAnnotation(ORGANIZERQUERY_ANNO, {}).filter(
+ id => PlacesUtils.annotations.getItemAnnotation(id, ORGANIZERQUERY_ANNO) == val
+ );
+
+ // Don't continue if the Library isn't ready
+ let all = find(ALLBOOKMARKS_ANNO);
+ if (all.length == 0)
+ return;
+
+ // Disable handling of notifications while changing the mobile query
+ this.ignoreAll = true;
+
+ let mobile = find(MOBILE_ANNO);
+ let queryURI = Utils.makeURI("place:folder=" + kSpecialIds.mobile);
+ let title = Str.sync.get("mobile.label");
+
+ // Don't add OR remove the mobile bookmarks if there's nothing.
+ if (PlacesUtils.bookmarks.getIdForItemAt(kSpecialIds.mobile, 0) == -1) {
+ if (mobile.length != 0)
+ PlacesUtils.bookmarks.removeItem(mobile[0]);
+ }
+ // Add the mobile bookmarks query if it doesn't exist
+ else if (mobile.length == 0) {
+ let query = PlacesUtils.bookmarks.insertBookmark(all[0], queryURI, -1, title);
+ PlacesUtils.annotations.setItemAnnotation(query, ORGANIZERQUERY_ANNO, MOBILE_ANNO, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ PlacesUtils.annotations.setItemAnnotation(query, EXCLUDEBACKUP_ANNO, 1, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+ // Make sure the existing title is correct
+ else if (PlacesUtils.bookmarks.getItemTitle(mobile[0]) != title) {
+ PlacesUtils.bookmarks.setItemTitle(mobile[0], title);
+ }
+
+ this.ignoreAll = false;
+ },
+
+ // This method is oddly structured, but the idea is to return as quickly as
+ // possible -- this handler gets called *every time* a bookmark changes, for
+ // *each change*.
+ onItemChanged: function BMT_onItemChanged(itemId, property, isAnno, value,
+ lastModified, itemType, parentId,
+ guid, parentGuid) {
+ // Quicker checks first.
+ if (this.ignoreAll)
+ return;
+
+ if (isAnno && (ANNOS_TO_TRACK.indexOf(property) == -1))
+ // Ignore annotations except for the ones that we sync.
+ return;
+
+ // Ignore favicon changes to avoid unnecessary churn.
+ if (property == "favicon")
+ return;
+
+ if (this._ignore(itemId, parentId, guid))
+ return;
+
+ this._log.trace("onItemChanged: " + itemId +
+ (", " + property + (isAnno? " (anno)" : "")) +
+ (value ? (" = \"" + value + "\"") : ""));
+ this._add(itemId, guid);
+ },
+
+ onItemMoved: function BMT_onItemMoved(itemId, oldParent, oldIndex,
+ newParent, newIndex, itemType,
+ guid, oldParentGuid, newParentGuid) {
+ if (this._ignore(itemId, newParent, guid))
+ return;
+
+ this._log.trace("onItemMoved: " + itemId);
+ this._add(oldParent, oldParentGuid);
+ if (oldParent != newParent) {
+ this._add(itemId, guid);
+ this._add(newParent, newParentGuid);
+ }
+
+ // Remove any position annotations now that the user moved the item
+ PlacesUtils.annotations.removeItemAnnotation(itemId, PARENT_ANNO);
+ },
+
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onItemVisited: function () {}
+};
diff --git a/components/weave/src/engines/clients.js b/components/weave/src/engines/clients.js
new file mode 100644
index 000000000..6c8e37a7b
--- /dev/null
+++ b/components/weave/src/engines/clients.js
@@ -0,0 +1,476 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [
+ "ClientEngine",
+ "ClientsRec"
+];
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://services-common/stringbundle.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/util.js");
+
+const CLIENTS_TTL = 1814400; // 21 days
+const CLIENTS_TTL_REFRESH = 604800; // 7 days
+
+const SUPPORTED_PROTOCOL_VERSIONS = ["1.1", "1.5"];
+
+this.ClientsRec = function ClientsRec(collection, id) {
+ CryptoWrapper.call(this, collection, id);
+}
+ClientsRec.prototype = {
+ __proto__: CryptoWrapper.prototype,
+ _logName: "Sync.Record.Clients",
+ ttl: CLIENTS_TTL
+};
+
+Utils.deferGetSet(ClientsRec,
+ "cleartext",
+ ["name", "type", "commands",
+ "version", "protocols",
+ "formfactor", "os", "appPackage", "application", "device"]);
+
+
+this.ClientEngine = function ClientEngine(service) {
+ SyncEngine.call(this, "Clients", service);
+
+ // Reset the client on every startup so that we fetch recent clients
+ this._resetClient();
+}
+ClientEngine.prototype = {
+ __proto__: SyncEngine.prototype,
+ _storeObj: ClientStore,
+ _recordObj: ClientsRec,
+ _trackerObj: ClientsTracker,
+
+ // Always sync client data as it controls other sync behavior
+ get enabled() true,
+
+ get lastRecordUpload() {
+ return Svc.Prefs.get(this.name + ".lastRecordUpload", 0);
+ },
+ set lastRecordUpload(value) {
+ Svc.Prefs.set(this.name + ".lastRecordUpload", Math.floor(value));
+ },
+
+ // Aggregate some stats on the composition of clients on this account
+ get stats() {
+ let stats = {
+ hasMobile: this.localType == "mobile",
+ names: [this.localName],
+ numClients: 1,
+ };
+
+ for (let id in this._store._remoteClients) {
+ let {name, type, stale} = this._store._remoteClients[id];
+ if (!stale) {
+ stats.hasMobile = stats.hasMobile || type == DEVICE_TYPE_MOBILE;
+ stats.names.push(name);
+ stats.numClients++;
+ }
+ }
+
+ return stats;
+ },
+
+ /**
+ * Obtain information about device types.
+ *
+ * Returns a Map of device types to integer counts.
+ */
+ get deviceTypes() {
+ let counts = new Map();
+
+ counts.set(this.localType, 1);
+
+ for (let id in this._store._remoteClients) {
+ let record = this._store._remoteClients[id];
+ if (record.stale) {
+ continue; // pretend "stale" records don't exist.
+ }
+ let type = record.type;
+ if (!counts.has(type)) {
+ counts.set(type, 0);
+ }
+
+ counts.set(type, counts.get(type) + 1);
+ }
+
+ return counts;
+ },
+
+ get localID() {
+ // Generate a random GUID id we don't have one
+ let localID = Svc.Prefs.get("client.GUID", "");
+ return localID == "" ? this.localID = Utils.makeGUID() : localID;
+ },
+ set localID(value) Svc.Prefs.set("client.GUID", value),
+
+ get brandName() {
+ let brand = new StringBundle("chrome://branding/locale/brand.properties");
+ return brand.get("brandShortName");
+ },
+
+ get localName() {
+ let localName = Svc.Prefs.get("client.name", "");
+ if (localName != "")
+ return localName;
+
+ return this.localName = Utils.getDefaultDeviceName();
+ },
+ set localName(value) Svc.Prefs.set("client.name", value),
+
+ get localType() Svc.Prefs.get("client.type", "desktop"),
+ set localType(value) Svc.Prefs.set("client.type", value),
+
+ isMobile: function isMobile(id) {
+ if (this._store._remoteClients[id])
+ return this._store._remoteClients[id].type == "mobile";
+ return false;
+ },
+
+ _syncStartup: function _syncStartup() {
+ // Reupload new client record periodically.
+ if (Date.now() / 1000 - this.lastRecordUpload > CLIENTS_TTL_REFRESH) {
+ this._tracker.addChangedID(this.localID);
+ this.lastRecordUpload = Date.now() / 1000;
+ }
+ SyncEngine.prototype._syncStartup.call(this);
+ },
+
+ // Always process incoming items because they might have commands
+ _reconcile: function _reconcile() {
+ return true;
+ },
+
+ // Treat reset the same as wiping for locally cached clients
+ _resetClient() {
+ this._wipeClient();
+ },
+
+ _wipeClient: function _wipeClient() {
+ SyncEngine.prototype._resetClient.call(this);
+ this._store.wipe();
+ },
+
+ removeClientData: function removeClientData() {
+ let res = this.service.resource(this.engineURL + "/" + this.localID);
+ res.delete();
+ },
+
+ // Override the default behavior to delete bad records from the server.
+ handleHMACMismatch: function handleHMACMismatch(item, mayRetry) {
+ this._log.debug("Handling HMAC mismatch for " + item.id);
+
+ let base = SyncEngine.prototype.handleHMACMismatch.call(this, item, mayRetry);
+ if (base != SyncEngine.kRecoveryStrategy.error)
+ return base;
+
+ // It's a bad client record. Save it to be deleted at the end of the sync.
+ this._log.debug("Bad client record detected. Scheduling for deletion.");
+ this._deleteId(item.id);
+
+ // Neither try again nor error; we're going to delete it.
+ return SyncEngine.kRecoveryStrategy.ignore;
+ },
+
+ /**
+ * A hash of valid commands that the client knows about. The key is a command
+ * and the value is a hash containing information about the command such as
+ * number of arguments and description.
+ */
+ _commands: {
+ resetAll: { args: 0, desc: "Clear temporary local data for all engines" },
+ resetEngine: { args: 1, desc: "Clear temporary local data for engine" },
+ wipeAll: { args: 0, desc: "Delete all client data for all engines" },
+ wipeEngine: { args: 1, desc: "Delete all client data for engine" },
+ logout: { args: 0, desc: "Log out client" },
+ displayURI: { args: 3, desc: "Instruct a client to display a URI" },
+ },
+
+ /**
+ * Remove any commands for the local client and mark it for upload.
+ */
+ clearCommands: function clearCommands() {
+ delete this.localCommands;
+ this._tracker.addChangedID(this.localID);
+ },
+
+ /**
+ * Sends a command+args pair to a specific client.
+ *
+ * @param command Command string
+ * @param args Array of arguments/data for command
+ * @param clientId Client to send command to
+ */
+ _sendCommandToClient: function sendCommandToClient(command, args, clientId) {
+ this._log.trace("Sending " + command + " to " + clientId);
+
+ let client = this._store._remoteClients[clientId];
+ if (!client) {
+ throw new Error("Unknown remote client ID: '" + clientId + "'.");
+ }
+
+ // notDupe compares two commands and returns if they are not equal.
+ let notDupe = function(other) {
+ return other.command != command || !Utils.deepEquals(other.args, args);
+ };
+
+ let action = {
+ command: command,
+ args: args,
+ };
+
+ if (!client.commands) {
+ client.commands = [action];
+ }
+ // Add the new action if there are no duplicates.
+ else if (client.commands.every(notDupe)) {
+ client.commands.push(action);
+ }
+ // It must be a dupe. Skip.
+ else {
+ return;
+ }
+
+ this._log.trace("Client " + clientId + " got a new action: " + [command, args]);
+ this._tracker.addChangedID(clientId);
+ },
+
+ /**
+ * Check if the local client has any remote commands and perform them.
+ *
+ * @return false to abort sync
+ */
+ processIncomingCommands: function processIncomingCommands() {
+ return this._notify("clients:process-commands", "", function() {
+ let commands = this.localCommands;
+
+ // Immediately clear out the commands as we've got them locally.
+ this.clearCommands();
+
+ // Process each command in order.
+ for each (let {command, args} in commands) {
+ this._log.debug("Processing command: " + command + "(" + args + ")");
+
+ let engines = [args[0]];
+ switch (command) {
+ case "resetAll":
+ engines = null;
+ // Fallthrough
+ case "resetEngine":
+ this.service.resetClient(engines);
+ break;
+ case "wipeAll":
+ engines = null;
+ // Fallthrough
+ case "wipeEngine":
+ this.service.wipeClient(engines);
+ break;
+ case "logout":
+ this.service.logout();
+ return false;
+ case "displayURI":
+ this._handleDisplayURI.apply(this, args);
+ break;
+ default:
+ this._log.debug("Received an unknown command: " + command);
+ break;
+ }
+ }
+
+ return true;
+ })();
+ },
+
+ /**
+ * Validates and sends a command to a client or all clients.
+ *
+ * Calling this does not actually sync the command data to the server. If the
+ * client already has the command/args pair, it won't receive a duplicate
+ * command.
+ *
+ * @param command
+ * Command to invoke on remote clients
+ * @param args
+ * Array of arguments to give to the command
+ * @param clientId
+ * Client ID to send command to. If undefined, send to all remote
+ * clients.
+ */
+ sendCommand: function sendCommand(command, args, clientId) {
+ let commandData = this._commands[command];
+ // Don't send commands that we don't know about.
+ if (!commandData) {
+ this._log.error("Unknown command to send: " + command);
+ return;
+ }
+ // Don't send a command with the wrong number of arguments.
+ else if (!args || args.length != commandData.args) {
+ this._log.error("Expected " + commandData.args + " args for '" +
+ command + "', but got " + args);
+ return;
+ }
+
+ if (clientId) {
+ this._sendCommandToClient(command, args, clientId);
+ } else {
+ for (let id in this._store._remoteClients) {
+ this._sendCommandToClient(command, args, id);
+ }
+ }
+ },
+
+ /**
+ * Send a URI to another client for display.
+ *
+ * A side effect is the score is increased dramatically to incur an
+ * immediate sync.
+ *
+ * If an unknown client ID is specified, sendCommand() will throw an
+ * Error object.
+ *
+ * @param uri
+ * URI (as a string) to send and display on the remote client
+ * @param clientId
+ * ID of client to send the command to. If not defined, will be sent
+ * to all remote clients.
+ * @param title
+ * Title of the page being sent.
+ */
+ sendURIToClientForDisplay: function sendURIToClientForDisplay(uri, clientId, title) {
+ this._log.info("Sending URI to client: " + uri + " -> " +
+ clientId + " (" + title + ")");
+ this.sendCommand("displayURI", [uri, this.localID, title], clientId);
+
+ this._tracker.score += SCORE_INCREMENT_XLARGE;
+ },
+
+ /**
+ * Handle a single received 'displayURI' command.
+ *
+ * Interested parties should observe the "weave:engine:clients:display-uri"
+ * topic. The callback will receive an object as the subject parameter with
+ * the following keys:
+ *
+ * uri URI (string) that is requested for display.
+ * clientId ID of client that sent the command.
+ * title Title of page that loaded URI (likely) corresponds to.
+ *
+ * The 'data' parameter to the callback will not be defined.
+ *
+ * @param uri
+ * String URI that was received
+ * @param clientId
+ * ID of client that sent URI
+ * @param title
+ * String title of page that URI corresponds to. Older clients may not
+ * send this.
+ */
+ _handleDisplayURI: function _handleDisplayURI(uri, clientId, title) {
+ this._log.info("Received a URI for display: " + uri + " (" + title +
+ ") from " + clientId);
+
+ let subject = {uri: uri, client: clientId, title: title};
+ Svc.Obs.notify("weave:engine:clients:display-uri", subject);
+ }
+};
+
+function ClientStore(name, engine) {
+ Store.call(this, name, engine);
+}
+ClientStore.prototype = {
+ __proto__: Store.prototype,
+
+ create(record) {
+ this.update(record)
+ },
+
+ update: function update(record) {
+ // Only grab commands from the server; local name/type always wins
+ if (record.id == this.engine.localID)
+ this.engine.localCommands = record.commands;
+ else
+ this._remoteClients[record.id] = record.cleartext;
+ },
+
+ createRecord: function createRecord(id, collection) {
+ let record = new ClientsRec(collection, id);
+
+ // Package the individual components into a record for the local client
+ if (id == this.engine.localID) {
+ record.name = this.engine.localName;
+ record.type = this.engine.localType;
+ record.commands = this.engine.localCommands;
+ record.version = Services.appinfo.version;
+ record.protocols = SUPPORTED_PROTOCOL_VERSIONS;
+
+ // Optional fields.
+ record.os = Services.appinfo.OS; // "Darwin"
+ record.appPackage = Services.appinfo.ID;
+ record.application = this.engine.brandName // "Nightly"
+
+ // We can't compute these yet.
+ // record.device = ""; // Bug 1100723
+ // record.formfactor = ""; // Bug 1100722
+ } else {
+ record.cleartext = this._remoteClients[id];
+ }
+
+ return record;
+ },
+
+ itemExists(id) {
+ return id in this.getAllIDs();
+ },
+
+ getAllIDs: function getAllIDs() {
+ let ids = {};
+ ids[this.engine.localID] = true;
+ for (let id in this._remoteClients)
+ ids[id] = true;
+ return ids;
+ },
+
+ wipe: function wipe() {
+ this._remoteClients = {};
+ },
+};
+
+function ClientsTracker(name, engine) {
+ Tracker.call(this, name, engine);
+ Svc.Obs.add("weave:engine:start-tracking", this);
+ Svc.Obs.add("weave:engine:stop-tracking", this);
+}
+ClientsTracker.prototype = {
+ __proto__: Tracker.prototype,
+
+ _enabled: false,
+
+ observe: function observe(subject, topic, data) {
+ switch (topic) {
+ case "weave:engine:start-tracking":
+ if (!this._enabled) {
+ Svc.Prefs.observe("client.name", this);
+ this._enabled = true;
+ }
+ break;
+ case "weave:engine:stop-tracking":
+ if (this._enabled) {
+ Svc.Prefs.ignore("clients.name", this);
+ this._enabled = false;
+ }
+ break;
+ case "nsPref:changed":
+ this._log.debug("client.name preference changed");
+ this.addChangedID(Svc.Prefs.get("client.GUID"));
+ this.score += SCORE_INCREMENT_XLARGE;
+ break;
+ }
+ }
+};
diff --git a/components/weave/src/engines/forms.js b/components/weave/src/engines/forms.js
new file mode 100644
index 000000000..b5b3f7732
--- /dev/null
+++ b/components/weave/src/engines/forms.js
@@ -0,0 +1,246 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ['FormEngine', 'FormRec'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://gre/modules/Async.jsm");
+Cu.import("resource://services-sync/util.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://gre/modules/Log.jsm");
+
+const FORMS_TTL = 5184000; // 60 days
+
+this.FormRec = function FormRec(collection, id) {
+ CryptoWrapper.call(this, collection, id);
+}
+FormRec.prototype = {
+ __proto__: CryptoWrapper.prototype,
+ _logName: "Sync.Record.Form",
+ ttl: FORMS_TTL
+};
+
+Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]);
+
+
+var FormWrapper = {
+ _log: Log.repository.getLogger("Sync.Engine.Forms"),
+
+ _getEntryCols: ["fieldname", "value"],
+ _guidCols: ["guid"],
+
+ // Do a "sync" search by spinning the event loop until it completes.
+ _searchSpinningly: function(terms, searchData) {
+ let results = [];
+ let cb = Async.makeSpinningCallback();
+ let callbacks = {
+ handleResult: function(result) {
+ results.push(result);
+ },
+ handleCompletion: function(reason) {
+ cb(null, results);
+ }
+ };
+ Svc.FormHistory.search(terms, searchData, callbacks);
+ return cb.wait();
+ },
+
+ _updateSpinningly: function(changes) {
+ if (!Svc.FormHistory.enabled) {
+ return; // update isn't going to do anything.
+ }
+ let cb = Async.makeSpinningCallback();
+ let callbacks = {
+ handleCompletion: function(reason) {
+ cb();
+ }
+ };
+ Svc.FormHistory.update(changes, callbacks);
+ return cb.wait();
+ },
+
+ getEntry: function (guid) {
+ let results = this._searchSpinningly(this._getEntryCols, {guid: guid});
+ if (!results.length) {
+ return null;
+ }
+ return {name: results[0].fieldname, value: results[0].value};
+ },
+
+ getGUID: function (name, value) {
+ // Query for the provided entry.
+ let query = { fieldname: name, value: value };
+ let results = this._searchSpinningly(this._guidCols, query);
+ return results.length ? results[0].guid : null;
+ },
+
+ hasGUID: function (guid) {
+ // We could probably use a count function here, but searchSpinningly exists...
+ return this._searchSpinningly(this._guidCols, {guid: guid}).length != 0;
+ },
+
+ replaceGUID: function (oldGUID, newGUID) {
+ let changes = {
+ op: "update",
+ guid: oldGUID,
+ newGuid: newGUID,
+ }
+ this._updateSpinningly(changes);
+ }
+
+};
+
+this.FormEngine = function FormEngine(service) {
+ SyncEngine.call(this, "Forms", service);
+}
+FormEngine.prototype = {
+ __proto__: SyncEngine.prototype,
+ _storeObj: FormStore,
+ _trackerObj: FormTracker,
+ _recordObj: FormRec,
+ applyIncomingBatchSize: FORMS_STORE_BATCH_SIZE,
+
+ syncPriority: 6,
+
+ get prefName() "history",
+
+ _findDupe: function _findDupe(item) {
+ return FormWrapper.getGUID(item.name, item.value);
+ }
+};
+
+function FormStore(name, engine) {
+ Store.call(this, name, engine);
+}
+FormStore.prototype = {
+ __proto__: Store.prototype,
+
+ _processChange: function (change) {
+ // If this._changes is defined, then we are applying a batch, so we
+ // can defer it.
+ if (this._changes) {
+ this._changes.push(change);
+ return;
+ }
+
+ // Otherwise we must handle the change synchronously, right now.
+ FormWrapper._updateSpinningly(change);
+ },
+
+ applyIncomingBatch: function (records) {
+ // We collect all the changes to be made then apply them all at once.
+ this._changes = [];
+ let failures = Store.prototype.applyIncomingBatch.call(this, records);
+ if (this._changes.length) {
+ FormWrapper._updateSpinningly(this._changes);
+ }
+ delete this._changes;
+ return failures;
+ },
+
+ getAllIDs: function () {
+ let results = FormWrapper._searchSpinningly(["guid"], [])
+ let guids = {};
+ for (let result of results) {
+ guids[result.guid] = true;
+ }
+ return guids;
+ },
+
+ changeItemID: function (oldID, newID) {
+ FormWrapper.replaceGUID(oldID, newID);
+ },
+
+ itemExists: function (id) {
+ return FormWrapper.hasGUID(id);
+ },
+
+ createRecord: function (id, collection) {
+ let record = new FormRec(collection, id);
+ let entry = FormWrapper.getEntry(id);
+ if (entry != null) {
+ record.name = entry.name;
+ record.value = entry.value;
+ } else {
+ record.deleted = true;
+ }
+ return record;
+ },
+
+ create: function (record) {
+ this._log.trace("Adding form record for " + record.name);
+ let change = {
+ op: "add",
+ fieldname: record.name,
+ value: record.value
+ };
+ this._processChange(change);
+ },
+
+ remove: function (record) {
+ this._log.trace("Removing form record: " + record.id);
+ let change = {
+ op: "remove",
+ guid: record.id
+ };
+ this._processChange(change);
+ },
+
+ update: function (record) {
+ this._log.trace("Ignoring form record update request!");
+ },
+
+ wipe: function () {
+ let change = {
+ op: "remove"
+ };
+ FormWrapper._updateSpinningly(change);
+ }
+};
+
+function FormTracker(name, engine) {
+ Tracker.call(this, name, engine);
+}
+FormTracker.prototype = {
+ __proto__: Tracker.prototype,
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ startTracking: function() {
+ Svc.Obs.add("satchel-storage-changed", this);
+ },
+
+ stopTracking: function() {
+ Svc.Obs.remove("satchel-storage-changed", this);
+ },
+
+ observe: function (subject, topic, data) {
+ Tracker.prototype.observe.call(this, subject, topic, data);
+ if (this.ignoreAll) {
+ return;
+ }
+
+ switch (topic) {
+ case "satchel-storage-changed":
+ if (data == "formhistory-add" || data == "formhistory-remove") {
+ let guid = subject.QueryInterface(Ci.nsISupportsString).toString();
+ this.trackEntry(guid);
+ }
+ break;
+ }
+ },
+
+ trackEntry: function (guid) {
+ this.addChangedID(guid);
+ this.score += SCORE_INCREMENT_MEDIUM;
+ },
+};
diff --git a/components/weave/src/engines/history.js b/components/weave/src/engines/history.js
new file mode 100644
index 000000000..35dfd7811
--- /dev/null
+++ b/components/weave/src/engines/history.js
@@ -0,0 +1,417 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ['HistoryEngine', 'HistoryRec'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+const HISTORY_TTL = 5184000; // 60 days
+
+Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Async.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/util.js");
+
+this.HistoryRec = function HistoryRec(collection, id) {
+ CryptoWrapper.call(this, collection, id);
+}
+HistoryRec.prototype = {
+ __proto__: CryptoWrapper.prototype,
+ _logName: "Sync.Record.History",
+ ttl: HISTORY_TTL
+};
+
+Utils.deferGetSet(HistoryRec, "cleartext", ["histUri", "title", "visits"]);
+
+
+this.HistoryEngine = function HistoryEngine(service) {
+ SyncEngine.call(this, "History", service);
+}
+HistoryEngine.prototype = {
+ __proto__: SyncEngine.prototype,
+ _recordObj: HistoryRec,
+ _storeObj: HistoryStore,
+ _trackerObj: HistoryTracker,
+ downloadLimit: MAX_HISTORY_DOWNLOAD,
+ applyIncomingBatchSize: HISTORY_STORE_BATCH_SIZE,
+
+ syncPriority: 7,
+};
+
+function HistoryStore(name, engine) {
+ Store.call(this, name, engine);
+
+ // Explicitly nullify our references to our cached services so we don't leak
+ Svc.Obs.add("places-shutdown", function() {
+ for (let query in this._stmts) {
+ let stmt = this._stmts;
+ stmt.finalize();
+ }
+ this._stmts = {};
+ }, this);
+}
+HistoryStore.prototype = {
+ __proto__: Store.prototype,
+
+ __asyncHistory: null,
+ get _asyncHistory() {
+ if (!this.__asyncHistory) {
+ this.__asyncHistory = Cc["@mozilla.org/browser/history;1"]
+ .getService(Ci.mozIAsyncHistory);
+ }
+ return this.__asyncHistory;
+ },
+
+ _stmts: {},
+ _getStmt: function(query) {
+ if (query in this._stmts) {
+ return this._stmts[query];
+ }
+
+ this._log.trace("Creating SQL statement: " + query);
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ return this._stmts[query] = db.createAsyncStatement(query);
+ },
+
+ get _setGUIDStm() {
+ return this._getStmt(
+ "UPDATE moz_places " +
+ "SET guid = :guid " +
+ "WHERE url = :page_url");
+ },
+
+ // Some helper functions to handle GUIDs
+ setGUID: function setGUID(uri, guid) {
+ uri = uri.spec ? uri.spec : uri;
+
+ if (!guid) {
+ guid = Utils.makeGUID();
+ }
+
+ let stmt = this._setGUIDStm;
+ stmt.params.guid = guid;
+ stmt.params.page_url = uri;
+ Async.querySpinningly(stmt);
+ return guid;
+ },
+
+ get _guidStm() {
+ return this._getStmt(
+ "SELECT guid " +
+ "FROM moz_places " +
+ "WHERE url = :page_url");
+ },
+ _guidCols: ["guid"],
+
+ GUIDForUri: function GUIDForUri(uri, create) {
+ let stm = this._guidStm;
+ stm.params.page_url = uri.spec ? uri.spec : uri;
+
+ // Use the existing GUID if it exists
+ let result = Async.querySpinningly(stm, this._guidCols)[0];
+ if (result && result.guid)
+ return result.guid;
+
+ // Give the uri a GUID if it doesn't have one
+ if (create)
+ return this.setGUID(uri);
+ },
+
+ get _visitStm() {
+ return this._getStmt(
+ "/* do not warn (bug 599936) */ " +
+ "SELECT visit_type type, visit_date date " +
+ "FROM moz_historyvisits " +
+ "WHERE place_id = (SELECT id FROM moz_places WHERE url = :url) " +
+ "ORDER BY date DESC LIMIT 10");
+ },
+ _visitCols: ["date", "type"],
+
+ get _urlStm() {
+ return this._getStmt(
+ "SELECT url, title, frecency " +
+ "FROM moz_places " +
+ "WHERE guid = :guid");
+ },
+ _urlCols: ["url", "title", "frecency"],
+
+ get _allUrlStm() {
+ return this._getStmt(
+ "SELECT url " +
+ "FROM moz_places " +
+ "WHERE last_visit_date > :cutoff_date " +
+ "ORDER BY frecency DESC " +
+ "LIMIT :max_results");
+ },
+ _allUrlCols: ["url"],
+
+ // See bug 320831 for why we use SQL here
+ _getVisits: function HistStore__getVisits(uri) {
+ this._visitStm.params.url = uri;
+ return Async.querySpinningly(this._visitStm, this._visitCols);
+ },
+
+ // See bug 468732 for why we use SQL here
+ _findURLByGUID: function HistStore__findURLByGUID(guid) {
+ this._urlStm.params.guid = guid;
+ return Async.querySpinningly(this._urlStm, this._urlCols)[0];
+ },
+
+ changeItemID: function HStore_changeItemID(oldID, newID) {
+ this.setGUID(this._findURLByGUID(oldID).url, newID);
+ },
+
+
+ getAllIDs: function HistStore_getAllIDs() {
+ // Only get places visited within the last 30 days (30*24*60*60*1000ms)
+ this._allUrlStm.params.cutoff_date = (Date.now() - 2592000000) * 1000;
+ this._allUrlStm.params.max_results = MAX_HISTORY_UPLOAD;
+
+ let urls = Async.querySpinningly(this._allUrlStm, this._allUrlCols);
+ let self = this;
+ return urls.reduce(function(ids, item) {
+ ids[self.GUIDForUri(item.url, true)] = item.url;
+ return ids;
+ }, {});
+ },
+
+ applyIncomingBatch: function applyIncomingBatch(records) {
+ let failed = [];
+
+ // Convert incoming records to mozIPlaceInfo objects. Some records can be
+ // ignored or handled directly, so we're rewriting the array in-place.
+ let i, k;
+ for (i = 0, k = 0; i < records.length; i++) {
+ let record = records[k] = records[i];
+ let shouldApply;
+
+ // This is still synchronous I/O for now.
+ try {
+ if (record.deleted) {
+ // Consider using nsIBrowserHistory::removePages() here.
+ this.remove(record);
+ // No further processing needed. Remove it from the list.
+ shouldApply = false;
+ } else {
+ shouldApply = this._recordToPlaceInfo(record);
+ }
+ } catch(ex) {
+ failed.push(record.id);
+ shouldApply = false;
+ }
+
+ if (shouldApply) {
+ k += 1;
+ }
+ }
+ records.length = k; // truncate array
+
+ // Nothing to do.
+ if (!records.length) {
+ return failed;
+ }
+
+ let updatePlacesCallback = {
+ handleResult: function handleResult() {},
+ handleError: function handleError(resultCode, placeInfo) {
+ failed.push(placeInfo.guid);
+ },
+ handleCompletion: Async.makeSyncCallback()
+ };
+ this._asyncHistory.updatePlaces(records, updatePlacesCallback);
+ Async.waitForSyncCallback(updatePlacesCallback.handleCompletion);
+ return failed;
+ },
+
+ /**
+ * Converts a Sync history record to a mozIPlaceInfo.
+ *
+ * Throws if an invalid record is encountered (invalid URI, etc.),
+ * returns true if the record is to be applied, false otherwise
+ * (no visits to add, etc.),
+ */
+ _recordToPlaceInfo: function _recordToPlaceInfo(record) {
+ // Sort out invalid URIs and ones Places just simply doesn't want.
+ record.uri = Utils.makeURI(record.histUri);
+ if (!record.uri) {
+ this._log.warn("Attempted to process invalid URI, skipping.");
+ throw "Invalid URI in record";
+ }
+
+ if (!Utils.checkGUID(record.id)) {
+ this._log.warn("Encountered record with invalid GUID: " + record.id);
+ return false;
+ }
+ record.guid = record.id;
+
+ if (!PlacesUtils.history.canAddURI(record.uri)) {
+ this._log.trace("Ignoring record " + record.id + " with URI "
+ + record.uri.spec + ": can't add this URI.");
+ return false;
+ }
+
+ // We dupe visits by date and type. So an incoming visit that has
+ // the same timestamp and type as a local one won't get applied.
+ // To avoid creating new objects, we rewrite the query result so we
+ // can simply check for containment below.
+ let curVisits = this._getVisits(record.histUri);
+ let i, k;
+ for (i = 0; i < curVisits.length; i++) {
+ curVisits[i] = curVisits[i].date + "," + curVisits[i].type;
+ }
+
+ // Walk through the visits, make sure we have sound data, and eliminate
+ // dupes. The latter is done by rewriting the array in-place.
+ for (i = 0, k = 0; i < record.visits.length; i++) {
+ let visit = record.visits[k] = record.visits[i];
+
+ if (!visit.date || typeof visit.date != "number") {
+ this._log.warn("Encountered record with invalid visit date: "
+ + visit.date);
+ throw "Visit has no date!";
+ }
+
+ if (!visit.type || !(visit.type >= PlacesUtils.history.TRANSITION_LINK &&
+ visit.type <= PlacesUtils.history.TRANSITION_RELOAD)) {
+ this._log.warn("Encountered record with invalid visit type: "
+ + visit.type);
+ throw "Invalid visit type!";
+ }
+
+ // Dates need to be integers.
+ visit.date = Math.round(visit.date);
+
+ if (curVisits.indexOf(visit.date + "," + visit.type) != -1) {
+ // Visit is a dupe, don't increment 'k' so the element will be
+ // overwritten.
+ continue;
+ }
+ visit.visitDate = visit.date;
+ visit.transitionType = visit.type;
+ k += 1;
+ }
+ record.visits.length = k; // truncate array
+
+ // No update if there aren't any visits to apply.
+ // mozIAsyncHistory::updatePlaces() wants at least one visit.
+ // In any case, the only thing we could change would be the title
+ // and that shouldn't change without a visit.
+ if (!record.visits.length) {
+ this._log.trace("Ignoring record " + record.id + " with URI "
+ + record.uri.spec + ": no visits to add.");
+ return false;
+ }
+
+ return true;
+ },
+
+ remove: function HistStore_remove(record) {
+ let page = this._findURLByGUID(record.id);
+ if (page == null) {
+ this._log.debug("Page already removed: " + record.id);
+ return;
+ }
+
+ let uri = Utils.makeURI(page.url);
+ PlacesUtils.history.removePage(uri);
+ this._log.trace("Removed page: " + [record.id, page.url, page.title]);
+ },
+
+ itemExists: function HistStore_itemExists(id) {
+ return !!this._findURLByGUID(id);
+ },
+
+ createRecord: function createRecord(id, collection) {
+ let foo = this._findURLByGUID(id);
+ let record = new HistoryRec(collection, id);
+ if (foo) {
+ record.histUri = foo.url;
+ record.title = foo.title;
+ record.sortindex = foo.frecency;
+ record.visits = this._getVisits(record.histUri);
+ } else {
+ record.deleted = true;
+ }
+
+ return record;
+ },
+
+ wipe: function HistStore_wipe() {
+ PlacesUtils.history.clear();
+ }
+};
+
+function HistoryTracker(name, engine) {
+ Tracker.call(this, name, engine);
+}
+HistoryTracker.prototype = {
+ __proto__: Tracker.prototype,
+
+ startTracking: function() {
+ this._log.info("Adding Places observer.");
+ PlacesUtils.history.addObserver(this, true);
+ },
+
+ stopTracking: function() {
+ this._log.info("Removing Places observer.");
+ PlacesUtils.history.removeObserver(this);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavHistoryObserver,
+ Ci.nsISupportsWeakReference
+ ]),
+
+ onDeleteAffectsGUID: function (uri, guid, reason, source, increment) {
+ if (this.ignoreAll || reason == Ci.nsINavHistoryObserver.REASON_EXPIRED) {
+ return;
+ }
+ this._log.trace(source + ": " + uri.spec + ", reason " + reason);
+ if (this.addChangedID(guid)) {
+ this.score += increment;
+ }
+ },
+
+ onDeleteVisits: function (uri, visitTime, guid, reason) {
+ this.onDeleteAffectsGUID(uri, guid, reason, "onDeleteVisits", SCORE_INCREMENT_SMALL);
+ },
+
+ onDeleteURI: function (uri, guid, reason) {
+ this.onDeleteAffectsGUID(uri, guid, reason, "onDeleteURI", SCORE_INCREMENT_XLARGE);
+ },
+
+ onVisit: function (uri, vid, time, session, referrer, trans, guid) {
+ if (this.ignoreAll) {
+ this._log.trace("ignoreAll: ignoring visit for " + guid);
+ return;
+ }
+
+ this._log.trace("onVisit: " + uri.spec);
+ if (this.addChangedID(guid)) {
+ this.score += SCORE_INCREMENT_SMALL;
+ }
+ },
+
+ onClearHistory: function () {
+ this._log.trace("onClearHistory");
+ // Note that we're going to trigger a sync, but none of the cleared
+ // pages are tracked, so the deletions will not be propagated.
+ // See Bug 578694.
+ this.score += SCORE_INCREMENT_XLARGE;
+ },
+
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onPageChanged: function () {},
+ onTitleChanged: function () {},
+ onBeforeDeleteURI: function () {},
+};
diff --git a/components/weave/src/engines/passwords.js b/components/weave/src/engines/passwords.js
new file mode 100644
index 000000000..0ccd2e7b0
--- /dev/null
+++ b/components/weave/src/engines/passwords.js
@@ -0,0 +1,305 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ['PasswordEngine', 'LoginRec'];
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/util.js");
+
+this.LoginRec = function LoginRec(collection, id) {
+ CryptoWrapper.call(this, collection, id);
+}
+LoginRec.prototype = {
+ __proto__: CryptoWrapper.prototype,
+ _logName: "Sync.Record.Login",
+};
+
+Utils.deferGetSet(LoginRec, "cleartext", [
+ "hostname", "formSubmitURL",
+ "httpRealm", "username", "password", "usernameField", "passwordField",
+ ]);
+
+
+this.PasswordEngine = function PasswordEngine(service) {
+ SyncEngine.call(this, "Passwords", service);
+}
+PasswordEngine.prototype = {
+ __proto__: SyncEngine.prototype,
+ _storeObj: PasswordStore,
+ _trackerObj: PasswordTracker,
+ _recordObj: LoginRec,
+
+ applyIncomingBatchSize: PASSWORDS_STORE_BATCH_SIZE,
+
+ syncPriority: 2,
+
+ _syncFinish: function () {
+ SyncEngine.prototype._syncFinish.call(this);
+
+ // Delete the Weave credentials from the server once.
+ if (!Svc.Prefs.get("deletePwdFxA", false)) {
+ try {
+ let ids = [];
+ for (let host of Utils.getSyncCredentialsHosts()) {
+ for (let info of Services.logins.findLogins({}, host, "", "")) {
+ ids.push(info.QueryInterface(Components.interfaces.nsILoginMetaInfo).guid);
+ }
+ }
+ if (ids.length) {
+ let coll = new Collection(this.engineURL, null, this.service);
+ coll.ids = ids;
+ let ret = coll.delete();
+ this._log.debug("Delete result: " + ret);
+ if (!ret.success && ret.status != 400) {
+ // A non-400 failure means try again next time.
+ return;
+ }
+ } else {
+ this._log.debug("Didn't find any passwords to delete");
+ }
+ // If there were no ids to delete, or we succeeded, or got a 400,
+ // record success.
+ Svc.Prefs.set("deletePwdFxA", true);
+ Svc.Prefs.reset("deletePwd"); // The old prefname we previously used.
+ } catch (ex) {
+ this._log.debug("Password deletes failed: ", ex);
+ }
+ }
+ },
+
+ _findDupe: function (item) {
+ let login = this._store._nsLoginInfoFromRecord(item);
+ if (!login) {
+ return;
+ }
+
+ let logins = Services.logins.findLogins({}, login.hostname, login.formSubmitURL, login.httpRealm);
+
+ this._store._sleep(0); // Yield back to main thread after synchronous operation.
+
+ // Look for existing logins that match the hostname, but ignore the password.
+ for (let local of logins) {
+ if (login.matches(local, true) && local instanceof Ci.nsILoginMetaInfo) {
+ return local.guid;
+ }
+ }
+ },
+};
+
+function PasswordStore(name, engine) {
+ Store.call(this, name, engine);
+ this._nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
+}
+PasswordStore.prototype = {
+ __proto__: Store.prototype,
+
+ _nsLoginInfoFromRecord: function (record) {
+ function nullUndefined(x) {
+ return (x == undefined) ? null : x;
+ }
+
+ if (record.formSubmitURL && record.httpRealm) {
+ this._log.warn("Record " + record.id + " has both formSubmitURL and httpRealm. Skipping.");
+ return null;
+ }
+
+ // Passing in "undefined" results in an empty string, which later
+ // counts as a value. Explicitly `|| null` these fields according to JS
+ // truthiness. Records with empty strings or null will be unmolested.
+ let info = new this._nsLoginInfo(record.hostname,
+ nullUndefined(record.formSubmitURL),
+ nullUndefined(record.httpRealm),
+ record.username,
+ record.password,
+ record.usernameField,
+ record.passwordField);
+ info.QueryInterface(Ci.nsILoginMetaInfo);
+ info.guid = record.id;
+ return info;
+ },
+
+ _getLoginFromGUID: function (id) {
+ let prop = Cc["@mozilla.org/hash-property-bag;1"].createInstance(Ci.nsIWritablePropertyBag2);
+ prop.setPropertyAsAUTF8String("guid", id);
+
+ let logins = Services.logins.searchLogins({}, prop);
+ this._sleep(0); // Yield back to main thread after synchronous operation.
+
+ if (logins.length > 0) {
+ this._log.trace(logins.length + " items matching " + id + " found.");
+ return logins[0];
+ }
+
+ this._log.trace("No items matching " + id + " found. Ignoring");
+ return null;
+ },
+
+ getAllIDs: function () {
+ let items = {};
+ let logins = Services.logins.getAllLogins({});
+
+ for (let i = 0; i < logins.length; i++) {
+ // Skip over Weave password/passphrase entries.
+ let metaInfo = logins[i].QueryInterface(Ci.nsILoginMetaInfo);
+ if (Utils.getSyncCredentialsHosts().has(metaInfo.hostname)) {
+ continue;
+ }
+
+ items[metaInfo.guid] = metaInfo;
+ }
+
+ return items;
+ },
+
+ changeItemID: function (oldID, newID) {
+ this._log.trace("Changing item ID: " + oldID + " to " + newID);
+
+ let oldLogin = this._getLoginFromGUID(oldID);
+ if (!oldLogin) {
+ this._log.trace("Can't change item ID: item doesn't exist");
+ return;
+ }
+ if (this._getLoginFromGUID(newID)) {
+ this._log.trace("Can't change item ID: new ID already in use");
+ return;
+ }
+
+ let prop = Cc["@mozilla.org/hash-property-bag;1"]
+ .createInstance(Ci.nsIWritablePropertyBag2);
+ prop.setPropertyAsAUTF8String("guid", newID);
+
+ Services.logins.modifyLogin(oldLogin, prop);
+ },
+
+ itemExists: function (id) {
+ return !!this._getLoginFromGUID(id);
+ },
+
+ createRecord: function (id, collection) {
+ let record = new LoginRec(collection, id);
+ let login = this._getLoginFromGUID(id);
+
+ if (!login) {
+ record.deleted = true;
+ return record;
+ }
+
+ record.hostname = login.hostname;
+ record.formSubmitURL = login.formSubmitURL;
+ record.httpRealm = login.httpRealm;
+ record.username = login.username;
+ record.password = login.password;
+ record.usernameField = login.usernameField;
+ record.passwordField = login.passwordField;
+
+ return record;
+ },
+
+ create: function (record) {
+ let login = this._nsLoginInfoFromRecord(record);
+ if (!login) {
+ return;
+ }
+
+ this._log.debug("Adding login for " + record.hostname);
+ this._log.trace("httpRealm: " + JSON.stringify(login.httpRealm) + "; " +
+ "formSubmitURL: " + JSON.stringify(login.formSubmitURL));
+ try {
+ Services.logins.addLogin(login);
+ } catch(ex) {
+ this._log.debug("Adding record " + record.id +
+ " resulted in exception ", ex);
+ }
+ },
+
+ remove: function (record) {
+ this._log.trace("Removing login " + record.id);
+
+ let loginItem = this._getLoginFromGUID(record.id);
+ if (!loginItem) {
+ this._log.trace("Asked to remove record that doesn't exist, ignoring");
+ return;
+ }
+
+ Services.logins.removeLogin(loginItem);
+ },
+
+ update: function (record) {
+ let loginItem = this._getLoginFromGUID(record.id);
+ if (!loginItem) {
+ this._log.debug("Skipping update for unknown item: " + record.hostname);
+ return;
+ }
+
+ this._log.debug("Updating " + record.hostname);
+ let newinfo = this._nsLoginInfoFromRecord(record);
+ if (!newinfo) {
+ return;
+ }
+
+ try {
+ Services.logins.modifyLogin(loginItem, newinfo);
+ } catch(ex) {
+ this._log.debug("Modifying record " + record.id +
+ " resulted in exception. Not modifying.", ex);
+ }
+ },
+
+ wipe: function () {
+ Services.logins.removeAllLogins();
+ },
+};
+
+function PasswordTracker(name, engine) {
+ Tracker.call(this, name, engine);
+ Svc.Obs.add("weave:engine:start-tracking", this);
+ Svc.Obs.add("weave:engine:stop-tracking", this);
+}
+PasswordTracker.prototype = {
+ __proto__: Tracker.prototype,
+
+ startTracking: function () {
+ Svc.Obs.add("passwordmgr-storage-changed", this);
+ },
+
+ stopTracking: function () {
+ Svc.Obs.remove("passwordmgr-storage-changed", this);
+ },
+
+ observe: function (subject, topic, data) {
+ Tracker.prototype.observe.call(this, subject, topic, data);
+
+ if (this.ignoreAll) {
+ return;
+ }
+
+ // A single add, remove or change or removing all items
+ // will trigger a sync for MULTI_DEVICE.
+ switch (data) {
+ case "modifyLogin":
+ subject = subject.QueryInterface(Ci.nsIArray).queryElementAt(1, Ci.nsILoginMetaInfo);
+ // Fall through.
+ case "addLogin":
+ case "removeLogin":
+ // Skip over Weave password/passphrase changes.
+ subject.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
+ if (Utils.getSyncCredentialsHosts().has(subject.hostname)) {
+ break;
+ }
+
+ this.score += SCORE_INCREMENT_XLARGE;
+ this._log.trace(data + ": " + subject.guid);
+ this.addChangedID(subject.guid);
+ break;
+ case "removeAllLogins":
+ this._log.trace(data);
+ this.score += SCORE_INCREMENT_XLARGE;
+ break;
+ }
+ },
+};
diff --git a/components/weave/src/engines/prefs.js b/components/weave/src/engines/prefs.js
new file mode 100644
index 000000000..792e0c66a
--- /dev/null
+++ b/components/weave/src/engines/prefs.js
@@ -0,0 +1,260 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ['PrefsEngine', 'PrefRec'];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+const SYNC_PREFS_PREFIX = "services.sync.prefs.sync.";
+
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/util.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+const PREFS_GUID = CommonUtils.encodeBase64URL(Services.appinfo.ID);
+
+this.PrefRec = function PrefRec(collection, id) {
+ CryptoWrapper.call(this, collection, id);
+}
+PrefRec.prototype = {
+ __proto__: CryptoWrapper.prototype,
+ _logName: "Sync.Record.Pref",
+};
+
+Utils.deferGetSet(PrefRec, "cleartext", ["value"]);
+
+
+this.PrefsEngine = function PrefsEngine(service) {
+ SyncEngine.call(this, "Prefs", service);
+}
+PrefsEngine.prototype = {
+ __proto__: SyncEngine.prototype,
+ _storeObj: PrefStore,
+ _trackerObj: PrefTracker,
+ _recordObj: PrefRec,
+ version: 2,
+
+ syncPriority: 1,
+
+ getChangedIDs: function () {
+ // No need for a proper timestamp (no conflict resolution needed).
+ let changedIDs = {};
+ if (this._tracker.modified)
+ changedIDs[PREFS_GUID] = 0;
+ return changedIDs;
+ },
+
+ _wipeClient: function () {
+ SyncEngine.prototype._wipeClient.call(this);
+ this.justWiped = true;
+ },
+
+ _reconcile: function (item) {
+ // Apply the incoming item if we don't care about the local data
+ if (this.justWiped) {
+ this.justWiped = false;
+ return true;
+ }
+ return SyncEngine.prototype._reconcile.call(this, item);
+ }
+};
+
+
+function PrefStore(name, engine) {
+ Store.call(this, name, engine);
+ Svc.Obs.add("profile-before-change", function () {
+ this.__prefs = null;
+ }, this);
+}
+PrefStore.prototype = {
+ __proto__: Store.prototype,
+
+ __prefs: null,
+ get _prefs() {
+ if (!this.__prefs) {
+ this.__prefs = new Preferences();
+ }
+ return this.__prefs;
+ },
+
+ _getSyncPrefs: function () {
+ let syncPrefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService)
+ .getBranch(SYNC_PREFS_PREFIX)
+ .getChildList("", {});
+ // Also sync preferences that determine which prefs get synced.
+ let controlPrefs = syncPrefs.map(pref => SYNC_PREFS_PREFIX + pref);
+ return controlPrefs.concat(syncPrefs);
+ },
+
+ _isSynced: function (pref) {
+ return pref.startsWith(SYNC_PREFS_PREFIX) ||
+ this._prefs.get(SYNC_PREFS_PREFIX + pref, false);
+ },
+
+ _getAllPrefs: function () {
+ let values = {};
+ for each (let pref in this._getSyncPrefs()) {
+ if (this._isSynced(pref)) {
+ // Missing prefs get the null value.
+ values[pref] = this._prefs.get(pref, null);
+ }
+ }
+ return values;
+ },
+
+ _setAllPrefs: function (values) {
+ let enabledPref = "lightweightThemes.isThemeSelected";
+ let enabledBefore = this._prefs.get(enabledPref, false);
+ let prevTheme = LightweightThemeManager.currentTheme;
+
+ // Update 'services.sync.prefs.sync.foo.pref' before 'foo.pref', otherwise
+ // _isSynced returns false when 'foo.pref' doesn't exist (e.g., on a new device).
+ let prefs = Object.keys(values).sort(a => -a.indexOf(SYNC_PREFS_PREFIX));
+ for (let pref of prefs) {
+ if (!this._isSynced(pref)) {
+ continue;
+ }
+
+ let value = values[pref];
+
+ // Pref has gone missing. The best we can do is reset it.
+ if (value == null) {
+ this._prefs.reset(pref);
+ continue;
+ }
+
+ try {
+ this._prefs.set(pref, value);
+ } catch(ex) {
+ this._log.trace("Failed to set pref: " + pref + ": " + ex);
+ }
+ }
+
+ // Notify the lightweight theme manager of all the new values
+ let enabledNow = this._prefs.get(enabledPref, false);
+ if (enabledBefore && !enabledNow) {
+ LightweightThemeManager.currentTheme = null;
+ } else if (enabledNow && LightweightThemeManager.usedThemes[0] != prevTheme) {
+ LightweightThemeManager.currentTheme = null;
+ LightweightThemeManager.currentTheme = LightweightThemeManager.usedThemes[0];
+ }
+ },
+
+ getAllIDs: function () {
+ /* We store all prefs in just one WBO, with just one GUID */
+ let allprefs = {};
+ allprefs[PREFS_GUID] = true;
+ return allprefs;
+ },
+
+ changeItemID: function (oldID, newID) {
+ this._log.trace("PrefStore GUID is constant!");
+ },
+
+ itemExists: function (id) {
+ return (id === PREFS_GUID);
+ },
+
+ createRecord: function (id, collection) {
+ let record = new PrefRec(collection, id);
+
+ if (id == PREFS_GUID) {
+ record.value = this._getAllPrefs();
+ } else {
+ record.deleted = true;
+ }
+
+ return record;
+ },
+
+ create: function (record) {
+ this._log.trace("Ignoring create request");
+ },
+
+ remove: function (record) {
+ this._log.trace("Ignoring remove request");
+ },
+
+ update: function (record) {
+ // Silently ignore pref updates that are for other apps.
+ if (record.id != PREFS_GUID)
+ return;
+
+ this._log.trace("Received pref updates, applying...");
+ this._setAllPrefs(record.value);
+ },
+
+ wipe: function () {
+ this._log.trace("Ignoring wipe request");
+ }
+};
+
+function PrefTracker(name, engine) {
+ Tracker.call(this, name, engine);
+ Svc.Obs.add("profile-before-change", this);
+ Svc.Obs.add("weave:engine:start-tracking", this);
+ Svc.Obs.add("weave:engine:stop-tracking", this);
+}
+PrefTracker.prototype = {
+ __proto__: Tracker.prototype,
+
+ get modified() {
+ return Svc.Prefs.get("engine.prefs.modified", false);
+ },
+ set modified(value) {
+ Svc.Prefs.set("engine.prefs.modified", value);
+ },
+
+ loadChangedIDs: function loadChangedIDs() {
+ // Don't read changed IDs from disk at start up.
+ },
+
+ clearChangedIDs: function clearChangedIDs() {
+ this.modified = false;
+ },
+
+ __prefs: null,
+ get _prefs() {
+ if (!this.__prefs) {
+ this.__prefs = new Preferences();
+ }
+ return this.__prefs;
+ },
+
+ startTracking: function () {
+ Services.prefs.addObserver("", this, false);
+ },
+
+ stopTracking: function () {
+ this.__prefs = null;
+ Services.prefs.removeObserver("", this);
+ },
+
+ observe: function (subject, topic, data) {
+ Tracker.prototype.observe.call(this, subject, topic, data);
+
+ switch (topic) {
+ case "profile-before-change":
+ this.stopTracking();
+ break;
+ case "nsPref:changed":
+ // Trigger a sync for MULTI-DEVICE for a change that determines
+ // which prefs are synced or a regular pref change.
+ if (data.indexOf(SYNC_PREFS_PREFIX) == 0 ||
+ this._prefs.get(SYNC_PREFS_PREFIX + data, false)) {
+ this.score += SCORE_INCREMENT_XLARGE;
+ this.modified = true;
+ this._log.trace("Preference " + data + " changed");
+ }
+ break;
+ }
+ }
+};
diff --git a/components/weave/src/engines/tabs.js b/components/weave/src/engines/tabs.js
new file mode 100644
index 000000000..167faf625
--- /dev/null
+++ b/components/weave/src/engines/tabs.js
@@ -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/. */
+
+this.EXPORTED_SYMBOLS = ["TabEngine", "TabSetRecord"];
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+const TABS_TTL = 604800; // 7 days.
+const TAB_ENTRIES_LIMIT = 25; // How many URLs to include in tab history.
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/engines/clients.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/util.js");
+Cu.import("resource://services-sync/constants.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+this.TabSetRecord = function TabSetRecord(collection, id) {
+ CryptoWrapper.call(this, collection, id);
+}
+TabSetRecord.prototype = {
+ __proto__: CryptoWrapper.prototype,
+ _logName: "Sync.Record.Tabs",
+ ttl: TABS_TTL,
+};
+
+Utils.deferGetSet(TabSetRecord, "cleartext", ["clientName", "tabs"]);
+
+
+this.TabEngine = function TabEngine(service) {
+ SyncEngine.call(this, "Tabs", service);
+
+ // Reset the client on every startup so that we fetch recent tabs.
+ this._resetClient();
+}
+TabEngine.prototype = {
+ __proto__: SyncEngine.prototype,
+ _storeObj: TabStore,
+ _trackerObj: TabTracker,
+ _recordObj: TabSetRecord,
+
+ syncPriority: 3,
+
+ getChangedIDs: function () {
+ // No need for a proper timestamp (no conflict resolution needed).
+ let changedIDs = {};
+ if (this._tracker.modified)
+ changedIDs[this.service.clientsEngine.localID] = 0;
+ return changedIDs;
+ },
+
+ // API for use by Sync UI code to give user choices of tabs to open.
+ getAllClients: function () {
+ return this._store._remoteClients;
+ },
+
+ getClientById: function (id) {
+ return this._store._remoteClients[id];
+ },
+
+ _resetClient: function () {
+ SyncEngine.prototype._resetClient.call(this);
+ this._store.wipe();
+ this._tracker.modified = true;
+ },
+
+ removeClientData: function () {
+ let url = this.engineURL + "/" + this.service.clientsEngine.localID;
+ this.service.resource(url).delete();
+ },
+
+ /**
+ * Return a Set of open URLs.
+ */
+ getOpenURLs: function () {
+ let urls = new Set();
+ for (let entry of this._store.getAllTabs()) {
+ urls.add(entry.urlHistory[0]);
+ }
+ return urls;
+ },
+
+ _reconcile: function (item) {
+ // Skip our own record.
+ // TabStore.itemExists tests only against our local client ID.
+ if (this._store.itemExists(item.id)) {
+ this._log.trace("Ignoring incoming tab item because of its id: " + item.id);
+ return false;
+ }
+
+ return SyncEngine.prototype._reconcile.call(this, item);
+ }
+};
+
+
+function TabStore(name, engine) {
+ Store.call(this, name, engine);
+}
+TabStore.prototype = {
+ __proto__: Store.prototype,
+
+ itemExists: function (id) {
+ return id == this.engine.service.clientsEngine.localID;
+ },
+
+ getWindowEnumerator: function () {
+ return Services.wm.getEnumerator("navigator:browser");
+ },
+
+ shouldSkipWindow: function (win) {
+ return win.closed ||
+ PrivateBrowsingUtils.isWindowPrivate(win);
+ },
+
+ getTabState: function (tab) {
+ return JSON.parse(Svc.Session.getTabState(tab));
+ },
+
+ getAllTabs: function (filter) {
+ let filteredUrls = new RegExp(Svc.Prefs.get("engine.tabs.filteredUrls"), "i");
+
+ let allTabs = [];
+
+ let winEnum = this.getWindowEnumerator();
+ while (winEnum.hasMoreElements()) {
+ let win = winEnum.getNext();
+ if (this.shouldSkipWindow(win)) {
+ continue;
+ }
+
+ for (let tab of win.gBrowser.tabs) {
+ tabState = this.getTabState(tab);
+
+ // Make sure there are history entries to look at.
+ if (!tabState || !tabState.entries.length) {
+ continue;
+ }
+
+ let acceptable = !filter ? (url) => url :
+ (url) => url && !filteredUrls.test(url);
+
+ let entries = tabState.entries;
+ let index = tabState.index;
+ let current = entries[index - 1];
+
+ // We ignore the tab completely if the current entry url is
+ // not acceptable (we need something accurate to open).
+ if (!acceptable(current.url)) {
+ continue;
+ }
+
+ // The element at `index` is the current page. Previous URLs were
+ // previously visited URLs; subsequent URLs are in the 'forward' stack,
+ // which we can't represent in Sync, so we truncate here.
+ let candidates = (entries.length == index) ?
+ entries :
+ entries.slice(0, index);
+
+ let urls = candidates.map((entry) => entry.url)
+ .filter(acceptable)
+ .reverse(); // Because Sync puts current at index 0, and history after.
+
+ // Truncate if necessary.
+ if (urls.length > TAB_ENTRIES_LIMIT) {
+ urls.length = TAB_ENTRIES_LIMIT;
+ }
+
+ allTabs.push({
+ title: current.title || "",
+ urlHistory: urls,
+ icon: tabState.attributes && tabState.attributes.image || "",
+ lastUsed: Math.floor((tabState.lastAccessed || 0) / 1000),
+ });
+ }
+ }
+
+ return allTabs;
+ },
+
+ createRecord: function (id, collection) {
+ let record = new TabSetRecord(collection, id);
+ record.clientName = this.engine.service.clientsEngine.localName;
+
+ // Sort tabs in descending-used order to grab the most recently used
+ let tabs = this.getAllTabs(true).sort(function (a, b) {
+ return b.lastUsed - a.lastUsed;
+ });
+
+ // Figure out how many tabs we can pack into a payload. Starting with a 28KB
+ // payload, we can estimate various overheads from encryption/JSON/WBO.
+ let size = JSON.stringify(tabs).length;
+ let origLength = tabs.length;
+ const MAX_TAB_SIZE = 20000;
+ if (size > MAX_TAB_SIZE) {
+ // Estimate a little more than the direct fraction to maximize packing
+ let cutoff = Math.ceil(tabs.length * MAX_TAB_SIZE / size);
+ tabs = tabs.slice(0, cutoff + 1);
+
+ // Keep dropping off the last entry until the data fits
+ while (JSON.stringify(tabs).length > MAX_TAB_SIZE)
+ tabs.pop();
+ }
+
+ this._log.trace("Created tabs " + tabs.length + " of " + origLength);
+ tabs.forEach(function (tab) {
+ this._log.trace("Wrapping tab: " + JSON.stringify(tab));
+ }, this);
+
+ record.tabs = tabs;
+ return record;
+ },
+
+ getAllIDs: function () {
+ // Don't report any tabs if all windows are in private browsing for
+ // first syncs.
+ let ids = {};
+ let allWindowsArePrivate = false;
+ let wins = Services.wm.getEnumerator("navigator:browser");
+ while (wins.hasMoreElements()) {
+ if (PrivateBrowsingUtils.isWindowPrivate(wins.getNext())) {
+ // Ensure that at least there is a private window.
+ allWindowsArePrivate = true;
+ } else {
+ // If there is a not private windown then finish and continue.
+ allWindowsArePrivate = false;
+ break;
+ }
+ }
+
+ if (allWindowsArePrivate &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ return ids;
+ }
+
+ ids[this.engine.service.clientsEngine.localID] = true;
+ return ids;
+ },
+
+ wipe: function () {
+ this._remoteClients = {};
+ },
+
+ create: function (record) {
+ this._log.debug("Adding remote tabs from " + record.clientName);
+ this._remoteClients[record.id] = record.cleartext;
+
+ // Lose some precision, but that's good enough (seconds).
+ let roundModify = Math.floor(record.modified / 1000);
+ let notifyState = Svc.Prefs.get("notifyTabState");
+
+ // If there's no existing pref, save this first modified time.
+ if (notifyState == null) {
+ Svc.Prefs.set("notifyTabState", roundModify);
+ return;
+ }
+
+ // Don't change notifyState if it's already 0 (don't notify).
+ if (notifyState == 0) {
+ return;
+ }
+
+ // We must have gotten a new tab that isn't the same as last time.
+ if (notifyState != roundModify) {
+ Svc.Prefs.set("notifyTabState", 0);
+ }
+ },
+
+ update: function (record) {
+ this._log.trace("Ignoring tab updates as local ones win");
+ },
+};
+
+
+function TabTracker(name, engine) {
+ Tracker.call(this, name, engine);
+ Svc.Obs.add("weave:engine:start-tracking", this);
+ Svc.Obs.add("weave:engine:stop-tracking", this);
+
+ // Make sure "this" pointer is always set correctly for event listeners.
+ this.onTab = Utils.bind2(this, this.onTab);
+ this._unregisterListeners = Utils.bind2(this, this._unregisterListeners);
+}
+TabTracker.prototype = {
+ __proto__: Tracker.prototype,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+ loadChangedIDs: function () {
+ // Don't read changed IDs from disk at start up.
+ },
+
+ clearChangedIDs: function () {
+ this.modified = false;
+ },
+
+ _topics: ["pageshow", "TabOpen", "TabClose", "TabSelect"],
+
+ _registerListenersForWindow: function (window) {
+ this._log.trace("Registering tab listeners in window");
+ for (let topic of this._topics) {
+ window.addEventListener(topic, this.onTab, false);
+ }
+ window.addEventListener("unload", this._unregisterListeners, false);
+ },
+
+ _unregisterListeners: function (event) {
+ this._unregisterListenersForWindow(event.target);
+ },
+
+ _unregisterListenersForWindow: function (window) {
+ this._log.trace("Removing tab listeners in window");
+ window.removeEventListener("unload", this._unregisterListeners, false);
+ for (let topic of this._topics) {
+ window.removeEventListener(topic, this.onTab, false);
+ }
+ },
+
+ startTracking: function () {
+ Svc.Obs.add("domwindowopened", this);
+ let wins = Services.wm.getEnumerator("navigator:browser");
+ while (wins.hasMoreElements()) {
+ this._registerListenersForWindow(wins.getNext());
+ }
+ },
+
+ stopTracking: function () {
+ Svc.Obs.remove("domwindowopened", this);
+ let wins = Services.wm.getEnumerator("navigator:browser");
+ while (wins.hasMoreElements()) {
+ this._unregisterListenersForWindow(wins.getNext());
+ }
+ },
+
+ observe: function (subject, topic, data) {
+ Tracker.prototype.observe.call(this, subject, topic, data);
+
+ switch (topic) {
+ case "domwindowopened":
+ let onLoad = () => {
+ subject.removeEventListener("load", onLoad, false);
+ // Only register after the window is done loading to avoid unloads.
+ this._registerListenersForWindow(subject);
+ };
+
+ // Add tab listeners now that a window has opened.
+ subject.addEventListener("load", onLoad, false);
+ break;
+ }
+ },
+
+ onTab: function (event) {
+ if (event.originalTarget.linkedBrowser) {
+ let browser = event.originalTarget.linkedBrowser;
+ if (PrivateBrowsingUtils.isBrowserPrivate(browser) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ this._log.trace("Ignoring tab event from private browsing.");
+ return;
+ }
+ }
+
+ this._log.trace("onTab event: " + event.type);
+ this.modified = true;
+
+ // For page shows, bump the score 10% of the time, emulating a partial
+ // score. We don't want to sync too frequently. For all other page
+ // events, always bump the score.
+ if (event.type != "pageshow" || Math.random() < .1) {
+ this.score += SCORE_INCREMENT_SMALL;
+ }
+ },
+};
diff --git a/components/weave/src/nsSyncJPAKE.cpp b/components/weave/src/nsSyncJPAKE.cpp
new file mode 100644
index 000000000..23378f56a
--- /dev/null
+++ b/components/weave/src/nsSyncJPAKE.cpp
@@ -0,0 +1,484 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsSyncJPAKE.h"
+
+#include "base64.h"
+#include "keyhi.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Move.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsString.h"
+#include "nscore.h"
+#include "pk11pub.h"
+#include "pkcs11.h"
+#include "secerr.h"
+#include "secmodt.h"
+#include "secport.h"
+
+using mozilla::fallible;
+
+static bool
+hex_from_2char(const unsigned char *c2, unsigned char *byteval)
+{
+ int i;
+ unsigned char offset;
+ *byteval = 0;
+ for (i=0; i<2; i++) {
+ if (c2[i] >= '0' && c2[i] <= '9') {
+ offset = c2[i] - '0';
+ *byteval |= offset << 4*(1-i);
+ } else if (c2[i] >= 'a' && c2[i] <= 'f') {
+ offset = c2[i] - 'a';
+ *byteval |= (offset + 10) << 4*(1-i);
+ } else if (c2[i] >= 'A' && c2[i] <= 'F') {
+ offset = c2[i] - 'A';
+ *byteval |= (offset + 10) << 4*(1-i);
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+fromHex(const char * str, unsigned char * p, size_t sLen)
+{
+ size_t i;
+ if (sLen & 1)
+ return false;
+
+ for (i = 0; i < sLen / 2; ++i) {
+ if (!hex_from_2char((const unsigned char *) str + (2*i),
+ (unsigned char *) p + i)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static nsresult
+fromHexString(const nsACString & str, unsigned char * p, size_t pMaxLen)
+{
+ char * strData = (char *) str.Data();
+ unsigned len = str.Length();
+ NS_ENSURE_ARG(len / 2 <= pMaxLen);
+ if (!fromHex(strData, p, len)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return NS_OK;
+}
+
+static bool
+toHexString(const unsigned char * str, unsigned len, nsACString & out)
+{
+ static const char digits[] = "0123456789ABCDEF";
+ if (!out.SetCapacity(2 * len, fallible))
+ return false;
+ out.SetLength(0);
+ for (unsigned i = 0; i < len; ++i) {
+ out.Append(digits[str[i] >> 4]);
+ out.Append(digits[str[i] & 0x0f]);
+ }
+ return true;
+}
+
+static nsresult
+mapErrno()
+{
+ int err = PORT_GetError();
+ switch (err) {
+ case SEC_ERROR_NO_MEMORY: return NS_ERROR_OUT_OF_MEMORY;
+ default: return NS_ERROR_UNEXPECTED;
+ }
+}
+
+#define NUM_ELEM(x) (sizeof(x) / sizeof (x)[0])
+
+static const char p[] =
+ "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C"
+ "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F"
+ "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1"
+ "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B"
+ "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394"
+ "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0"
+ "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E"
+ "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D"
+ "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F"
+ "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D"
+ "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E"
+ "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73";
+static const char q[] =
+ "CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D";
+static const char g[] =
+ "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37"
+ "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB"
+ "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1"
+ "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8"
+ "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17"
+ "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C"
+ "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3"
+ "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B"
+ "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8"
+ "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828"
+ "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33"
+ "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B";
+
+NS_IMETHODIMP nsSyncJPAKE::Round1(const nsACString & aSignerID,
+ nsACString & aGX1,
+ nsACString & aGV1,
+ nsACString & aR1,
+ nsACString & aGX2,
+ nsACString & aGV2,
+ nsACString & aR2)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ENSURE_STATE(round == JPAKENotStarted);
+ NS_ENSURE_STATE(key == nullptr);
+
+ static CK_MECHANISM_TYPE mechanisms[] = {
+ CKM_NSS_JPAKE_ROUND1_SHA256,
+ CKM_NSS_JPAKE_ROUND2_SHA256,
+ CKM_NSS_JPAKE_FINAL_SHA256
+ };
+
+ UniquePK11SlotInfo slot(PK11_GetBestSlotMultiple(mechanisms,
+ NUM_ELEM(mechanisms),
+ nullptr));
+ NS_ENSURE_STATE(slot != nullptr);
+
+ CK_BYTE pBuf[(NUM_ELEM(p) - 1) / 2];
+ CK_BYTE qBuf[(NUM_ELEM(q) - 1) / 2];
+ CK_BYTE gBuf[(NUM_ELEM(g) - 1) / 2];
+
+ CK_KEY_TYPE keyType = CKK_NSS_JPAKE_ROUND1;
+ NS_ENSURE_STATE(fromHex(p, pBuf, (NUM_ELEM(p) - 1)));
+ NS_ENSURE_STATE(fromHex(q, qBuf, (NUM_ELEM(q) - 1)));
+ NS_ENSURE_STATE(fromHex(g, gBuf, (NUM_ELEM(g) - 1)));
+ CK_ATTRIBUTE keyTemplate[] = {
+ { CKA_NSS_JPAKE_SIGNERID, (CK_BYTE *) aSignerID.Data(),
+ aSignerID.Length() },
+ { CKA_KEY_TYPE, &keyType, sizeof keyType },
+ { CKA_PRIME, pBuf, sizeof pBuf },
+ { CKA_SUBPRIME, qBuf, sizeof qBuf },
+ { CKA_BASE, gBuf, sizeof gBuf }
+ };
+
+ CK_BYTE gx1Buf[NUM_ELEM(p) / 2];
+ CK_BYTE gv1Buf[NUM_ELEM(p) / 2];
+ CK_BYTE r1Buf [NUM_ELEM(p) / 2];
+ CK_BYTE gx2Buf[NUM_ELEM(p) / 2];
+ CK_BYTE gv2Buf[NUM_ELEM(p) / 2];
+ CK_BYTE r2Buf [NUM_ELEM(p) / 2];
+ CK_NSS_JPAKERound1Params rp = {
+ { gx1Buf, sizeof gx1Buf, gv1Buf, sizeof gv1Buf, r1Buf, sizeof r1Buf },
+ { gx2Buf, sizeof gx2Buf, gv2Buf, sizeof gv2Buf, r2Buf, sizeof r2Buf }
+ };
+ SECItem paramsItem;
+ paramsItem.data = (unsigned char *) &rp;
+ paramsItem.len = sizeof rp;
+ key = UniquePK11SymKey(
+ PK11_KeyGenWithTemplate(slot.get(), CKM_NSS_JPAKE_ROUND1_SHA256,
+ CKM_NSS_JPAKE_ROUND1_SHA256, &paramsItem,
+ keyTemplate, NUM_ELEM(keyTemplate), nullptr));
+ nsresult rv = key != nullptr
+ ? NS_OK
+ : mapErrno();
+ if (rv == NS_OK) {
+ NS_ENSURE_TRUE(toHexString(rp.gx1.pGX, rp.gx1.ulGXLen, aGX1) &&
+ toHexString(rp.gx1.pGV, rp.gx1.ulGVLen, aGV1) &&
+ toHexString(rp.gx1.pR, rp.gx1.ulRLen, aR1) &&
+ toHexString(rp.gx2.pGX, rp.gx2.ulGXLen, aGX2) &&
+ toHexString(rp.gx2.pGV, rp.gx2.ulGVLen, aGV2) &&
+ toHexString(rp.gx2.pR, rp.gx2.ulRLen, aR2),
+ NS_ERROR_OUT_OF_MEMORY);
+ round = JPAKEBeforeRound2;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsSyncJPAKE::Round2(const nsACString & aPeerID,
+ const nsACString & aPIN,
+ const nsACString & aGX3,
+ const nsACString & aGV3,
+ const nsACString & aR3,
+ const nsACString & aGX4,
+ const nsACString & aGV4,
+ const nsACString & aR4,
+ nsACString & aA,
+ nsACString & aGVA,
+ nsACString & aRA)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ENSURE_STATE(round == JPAKEBeforeRound2);
+ NS_ENSURE_STATE(key != nullptr);
+ NS_ENSURE_ARG(!aPeerID.IsEmpty());
+
+ /* PIN cannot be equal to zero when converted to a bignum. NSS 3.12.9 J-PAKE
+ assumes that the caller has already done this check. Future versions of
+ NSS J-PAKE will do this check internally. See Bug 609068 Comment 4 */
+ bool foundNonZero = false;
+ for (size_t i = 0; i < aPIN.Length(); ++i) {
+ if (aPIN[i] != 0) {
+ foundNonZero = true;
+ break;
+ }
+ }
+ NS_ENSURE_ARG(foundNonZero);
+
+ CK_BYTE gx3Buf[NUM_ELEM(p)/2], gv3Buf[NUM_ELEM(p)/2], r3Buf [NUM_ELEM(p)/2];
+ CK_BYTE gx4Buf[NUM_ELEM(p)/2], gv4Buf[NUM_ELEM(p)/2], r4Buf [NUM_ELEM(p)/2];
+ CK_BYTE gxABuf[NUM_ELEM(p)/2], gvABuf[NUM_ELEM(p)/2], rABuf [NUM_ELEM(p)/2];
+ nsresult rv = fromHexString(aGX3, gx3Buf, sizeof gx3Buf);
+ if (rv == NS_OK) rv = fromHexString(aGV3, gv3Buf, sizeof gv3Buf);
+ if (rv == NS_OK) rv = fromHexString(aR3, r3Buf, sizeof r3Buf);
+ if (rv == NS_OK) rv = fromHexString(aGX4, gx4Buf, sizeof gx4Buf);
+ if (rv == NS_OK) rv = fromHexString(aGV4, gv4Buf, sizeof gv4Buf);
+ if (rv == NS_OK) rv = fromHexString(aR4, r4Buf, sizeof r4Buf);
+ if (rv != NS_OK)
+ return rv;
+
+ CK_NSS_JPAKERound2Params rp;
+ rp.pSharedKey = (CK_BYTE *) aPIN.Data();
+ rp.ulSharedKeyLen = aPIN.Length();
+ rp.gx3.pGX = gx3Buf; rp.gx3.ulGXLen = aGX3.Length() / 2;
+ rp.gx3.pGV = gv3Buf; rp.gx3.ulGVLen = aGV3.Length() / 2;
+ rp.gx3.pR = r3Buf; rp.gx3.ulRLen = aR3 .Length() / 2;
+ rp.gx4.pGX = gx4Buf; rp.gx4.ulGXLen = aGX4.Length() / 2;
+ rp.gx4.pGV = gv4Buf; rp.gx4.ulGVLen = aGV4.Length() / 2;
+ rp.gx4.pR = r4Buf; rp.gx4.ulRLen = aR4 .Length() / 2;
+ rp.A.pGX = gxABuf; rp.A .ulGXLen = sizeof gxABuf;
+ rp.A.pGV = gvABuf; rp.A .ulGVLen = sizeof gxABuf;
+ rp.A.pR = rABuf; rp.A .ulRLen = sizeof gxABuf;
+
+ // Bug 629090: NSS 3.12.9 J-PAKE fails to check that gx^4 != 1, so check here.
+ bool gx4Good = false;
+ for (unsigned i = 0; i < rp.gx4.ulGXLen; ++i) {
+ if (rp.gx4.pGX[i] > 1 || (rp.gx4.pGX[i] != 0 && i < rp.gx4.ulGXLen - 1)) {
+ gx4Good = true;
+ break;
+ }
+ }
+ NS_ENSURE_ARG(gx4Good);
+
+ SECItem paramsItem;
+ paramsItem.data = (unsigned char *) &rp;
+ paramsItem.len = sizeof rp;
+ CK_KEY_TYPE keyType = CKK_NSS_JPAKE_ROUND2;
+ CK_ATTRIBUTE keyTemplate[] = {
+ { CKA_NSS_JPAKE_PEERID, (CK_BYTE *) aPeerID.Data(), aPeerID.Length(), },
+ { CKA_KEY_TYPE, &keyType, sizeof keyType }
+ };
+ UniquePK11SymKey newKey(PK11_DeriveWithTemplate(key.get(),
+ CKM_NSS_JPAKE_ROUND2_SHA256,
+ &paramsItem,
+ CKM_NSS_JPAKE_FINAL_SHA256,
+ CKA_DERIVE, 0,
+ keyTemplate,
+ NUM_ELEM(keyTemplate),
+ false));
+ if (newKey != nullptr) {
+ if (toHexString(rp.A.pGX, rp.A.ulGXLen, aA) &&
+ toHexString(rp.A.pGV, rp.A.ulGVLen, aGVA) &&
+ toHexString(rp.A.pR, rp.A.ulRLen, aRA)) {
+ round = JPAKEAfterRound2;
+ key = Move(newKey);
+ return NS_OK;
+ } else {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ rv = mapErrno();
+ }
+
+ return rv;
+}
+
+static nsresult
+setBase64(const unsigned char * data, unsigned len, nsACString & out)
+{
+ nsresult rv = NS_OK;
+ const char * base64 = BTOA_DataToAscii(data, len);
+
+ if (base64 != nullptr) {
+ size_t len = PORT_Strlen(base64);
+ if (out.SetCapacity(len, fallible)) {
+ out.SetLength(0);
+ out.Append(base64, len);
+ } else {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ PORT_Free((void*) base64);
+ } else {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return rv;
+}
+
+static nsresult
+base64KeyValue(PK11SymKey * key, nsACString & keyString)
+{
+ nsresult rv = NS_OK;
+ if (PK11_ExtractKeyValue(key) == SECSuccess) {
+ const SECItem * value = PK11_GetKeyData(key);
+ rv = value != nullptr && value->data != nullptr && value->len > 0
+ ? setBase64(value->data, value->len, keyString)
+ : NS_ERROR_UNEXPECTED;
+ } else {
+ rv = mapErrno();
+ }
+ return rv;
+}
+
+static nsresult
+extractBase64KeyValue(UniquePK11SymKey & keyBlock, CK_ULONG bitPosition,
+ CK_MECHANISM_TYPE destMech, int keySize,
+ nsACString & keyString)
+{
+ SECItem paramsItem;
+ paramsItem.data = (CK_BYTE *) &bitPosition;
+ paramsItem.len = sizeof bitPosition;
+ PK11SymKey * key = PK11_Derive(keyBlock.get(), CKM_EXTRACT_KEY_FROM_KEY,
+ &paramsItem, destMech,
+ CKA_SIGN, keySize);
+ if (key == nullptr)
+ return mapErrno();
+ nsresult rv = base64KeyValue(key, keyString);
+ PK11_FreeSymKey(key);
+ return rv;
+}
+
+
+NS_IMETHODIMP nsSyncJPAKE::Final(const nsACString & aB,
+ const nsACString & aGVB,
+ const nsACString & aRB,
+ const nsACString & aHKDFInfo,
+ nsACString & aAES256Key,
+ nsACString & aHMAC256Key)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ static const unsigned AES256_KEY_SIZE = 256 / 8;
+ static const unsigned HMAC_SHA256_KEY_SIZE = 256 / 8;
+ CK_EXTRACT_PARAMS aesBitPosition = 0;
+ CK_EXTRACT_PARAMS hmacBitPosition = aesBitPosition + (AES256_KEY_SIZE * 8);
+
+ NS_ENSURE_STATE(round == JPAKEAfterRound2);
+ NS_ENSURE_STATE(key != nullptr);
+
+ CK_BYTE gxBBuf[NUM_ELEM(p)/2], gvBBuf[NUM_ELEM(p)/2], rBBuf [NUM_ELEM(p)/2];
+ nsresult rv = fromHexString(aB, gxBBuf, sizeof gxBBuf);
+ if (rv == NS_OK) rv = fromHexString(aGVB, gvBBuf, sizeof gvBBuf);
+ if (rv == NS_OK) rv = fromHexString(aRB, rBBuf, sizeof rBBuf);
+ if (rv != NS_OK)
+ return rv;
+
+ CK_NSS_JPAKEFinalParams rp;
+ rp.B.pGX = gxBBuf; rp.B.ulGXLen = aB .Length() / 2;
+ rp.B.pGV = gvBBuf; rp.B.ulGVLen = aGVB.Length() / 2;
+ rp.B.pR = rBBuf; rp.B.ulRLen = aRB .Length() / 2;
+ SECItem paramsItem;
+ paramsItem.data = (unsigned char *) &rp;
+ paramsItem.len = sizeof rp;
+ UniquePK11SymKey keyMaterial(PK11_Derive(key.get(), CKM_NSS_JPAKE_FINAL_SHA256,
+ &paramsItem, CKM_NSS_HKDF_SHA256,
+ CKA_DERIVE, 0));
+ UniquePK11SymKey keyBlock;
+
+ if (keyMaterial == nullptr)
+ rv = mapErrno();
+
+ if (rv == NS_OK) {
+ CK_NSS_HKDFParams hkdfParams;
+ hkdfParams.bExtract = CK_TRUE;
+ hkdfParams.pSalt = nullptr;
+ hkdfParams.ulSaltLen = 0;
+ hkdfParams.bExpand = CK_TRUE;
+ hkdfParams.pInfo = (CK_BYTE *) aHKDFInfo.Data();
+ hkdfParams.ulInfoLen = aHKDFInfo.Length();
+ paramsItem.data = (unsigned char *) &hkdfParams;
+ paramsItem.len = sizeof hkdfParams;
+ keyBlock = UniquePK11SymKey(
+ PK11_Derive(keyMaterial.get(), CKM_NSS_HKDF_SHA256, &paramsItem,
+ CKM_EXTRACT_KEY_FROM_KEY, CKA_DERIVE,
+ AES256_KEY_SIZE + HMAC_SHA256_KEY_SIZE));
+ if (keyBlock == nullptr)
+ rv = mapErrno();
+ }
+
+ if (rv == NS_OK) {
+ rv = extractBase64KeyValue(keyBlock, aesBitPosition, CKM_AES_CBC,
+ AES256_KEY_SIZE, aAES256Key);
+ }
+ if (rv == NS_OK) {
+ rv = extractBase64KeyValue(keyBlock, hmacBitPosition, CKM_SHA256_HMAC,
+ HMAC_SHA256_KEY_SIZE, aHMAC256Key);
+ }
+
+ if (rv == NS_OK) {
+ SECStatus srv = PK11_ExtractKeyValue(keyMaterial.get());
+ NS_ENSURE_TRUE(srv == SECSuccess, NS_ERROR_UNEXPECTED);
+ SECItem * keyMaterialBytes = PK11_GetKeyData(keyMaterial.get());
+ NS_ENSURE_TRUE(keyMaterialBytes != nullptr, NS_ERROR_UNEXPECTED);
+ }
+
+ return rv;
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSyncJPAKE)
+NS_DEFINE_NAMED_CID(NS_SYNCJPAKE_CID);
+
+nsSyncJPAKE::nsSyncJPAKE() : round(JPAKENotStarted), key(nullptr) { }
+
+nsSyncJPAKE::~nsSyncJPAKE()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown()) {
+ return;
+ }
+ destructorSafeDestroyNSSReference();
+ shutdown(ShutdownCalledFrom::Object);
+}
+
+void
+nsSyncJPAKE::virtualDestroyNSSReference()
+{
+ destructorSafeDestroyNSSReference();
+}
+
+void
+nsSyncJPAKE::destructorSafeDestroyNSSReference()
+{
+ key = nullptr;
+}
+
+static const mozilla::Module::CIDEntry kServicesCryptoCIDs[] = {
+ { &kNS_SYNCJPAKE_CID, false, nullptr, nsSyncJPAKEConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kServicesCryptoContracts[] = {
+ { NS_SYNCJPAKE_CONTRACTID, &kNS_SYNCJPAKE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kServicesCryptoModule = {
+ mozilla::Module::kVersion,
+ kServicesCryptoCIDs,
+ kServicesCryptoContracts
+};
+
+NSMODULE_DEFN(nsServicesCryptoModule) = &kServicesCryptoModule;
diff --git a/components/weave/src/nsSyncJPAKE.h b/components/weave/src/nsSyncJPAKE.h
new file mode 100644
index 000000000..0c737d997
--- /dev/null
+++ b/components/weave/src/nsSyncJPAKE.h
@@ -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/. */
+#ifndef nsSyncJPAKE_h__
+#define nsSyncJPAKE_h__
+
+#include "ScopedNSSTypes.h"
+#include "nsISyncJPAKE.h"
+#include "nsNSSShutDown.h"
+
+#define NS_SYNCJPAKE_CONTRACTID \
+ "@mozilla.org/services-crypto/sync-jpake;1"
+
+#define NS_SYNCJPAKE_CID \
+ {0x0b9721c0, 0x1805, 0x47c3, {0x86, 0xce, 0x68, 0x13, 0x79, 0x5a, 0x78, 0x3f}}
+
+using namespace mozilla;
+
+class nsSyncJPAKE : public nsISyncJPAKE
+ , public nsNSSShutDownObject
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISYNCJPAKE
+ nsSyncJPAKE();
+protected:
+ virtual ~nsSyncJPAKE();
+private:
+ virtual void virtualDestroyNSSReference() override;
+ void destructorSafeDestroyNSSReference();
+
+ enum { JPAKENotStarted, JPAKEBeforeRound2, JPAKEAfterRound2 } round;
+ UniquePK11SymKey key;
+};
+
+NS_IMPL_ISUPPORTS(nsSyncJPAKE, nsISyncJPAKE)
+
+#endif // nsSyncJPAKE_h__
diff --git a/components/weave/src/stages/cluster.js b/components/weave/src/stages/cluster.js
new file mode 100644
index 000000000..41afe61d8
--- /dev/null
+++ b/components/weave/src/stages/cluster.js
@@ -0,0 +1,111 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["ClusterManager"];
+
+var {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/policies.js");
+Cu.import("resource://services-sync/util.js");
+
+/**
+ * Contains code for managing the Sync cluster we are in.
+ */
+this.ClusterManager = function ClusterManager(service) {
+ this._log = Log.repository.getLogger("Sync.Service");
+ this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];
+
+ this.service = service;
+}
+ClusterManager.prototype = {
+ get identity() {
+ return this.service.identity;
+ },
+
+ /**
+ * Obtain the cluster for the current user.
+ *
+ * Returns the string URL of the cluster or null on error.
+ */
+ _findCluster: function _findCluster() {
+ this._log.debug("Finding cluster for user " + this.identity.username);
+
+ // This should ideally use UserAPI10Client but the legacy hackiness is
+ // strong with this code.
+ let fail;
+ let url = this.service.userAPIURI + this.identity.username + "/node/weave";
+ let res = this.service.resource(url);
+ try {
+ let node = res.get();
+ switch (node.status) {
+ case 400:
+ this.service.status.login = LOGIN_FAILED_LOGIN_REJECTED;
+ fail = "Find cluster denied: " + this.service.errorHandler.errorStr(node);
+ break;
+ case 404:
+ this._log.debug("Using serverURL as data cluster (multi-cluster support disabled)");
+ return this.service.serverURL;
+ case 0:
+ case 200:
+ if (node == "null") {
+ node = null;
+ }
+ this._log.trace("_findCluster successfully returning " + node);
+ return node;
+ default:
+ this.service.errorHandler.checkServerError(node);
+ fail = "Unexpected response code: " + node.status;
+ break;
+ }
+ } catch (e) {
+ this._log.debug("Network error on findCluster");
+ this.service.status.login = LOGIN_FAILED_NETWORK_ERROR;
+ this.service.errorHandler.checkServerError(e);
+ fail = e;
+ }
+ throw fail;
+ },
+
+ /**
+ * Determine the cluster for the current user and update state.
+ */
+ setCluster: function setCluster() {
+ // Make sure we didn't get some unexpected response for the cluster.
+ let cluster = this._findCluster();
+ this._log.debug("Cluster value = " + cluster);
+ if (cluster == null) {
+ return false;
+ }
+
+ // Don't update stuff if we already have the right cluster
+ if (cluster == this.service.clusterURL) {
+ return false;
+ }
+
+ this._log.debug("Setting cluster to " + cluster);
+ this.service.clusterURL = cluster;
+ Svc.Prefs.set("lastClusterUpdate", Date.now().toString());
+
+ return true;
+ },
+
+ getUserBaseURL: function getUserBaseURL() {
+ // Legacy Sync and FxA Sync construct the userBaseURL differently. Legacy
+ // Sync appends path components onto an empty path, and in FxA Sync, the
+ // token server constructs this for us in an opaque manner. Since the
+ // cluster manager already sets the clusterURL on Service and also has
+ // access to the current identity, we added this functionality here.
+
+ // If the clusterURL hasn't been set, the userBaseURL shouldn't be set
+ // either. Some tests expect "undefined" to be returned here.
+ if (!this.service.clusterURL) {
+ return undefined;
+ }
+ let storageAPI = this.service.clusterURL + SYNC_API_VERSION + "/";
+ return storageAPI + this.identity.username + "/";
+ }
+};
+Object.freeze(ClusterManager.prototype);
diff --git a/components/weave/src/stages/declined.js b/components/weave/src/stages/declined.js
new file mode 100644
index 000000000..ff8a14181
--- /dev/null
+++ b/components/weave/src/stages/declined.js
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains code for maintaining the set of declined engines,
+ * in conjunction with EngineManager.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["DeclinedEngines"];
+
+var {utils: Cu} = Components;
+
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://gre/modules/Preferences.jsm");
+
+
+
+this.DeclinedEngines = function (service) {
+ this._log = Log.repository.getLogger("Sync.Declined");
+ this._log.level = Log.Level[new Preferences(PREFS_BRANCH).get("log.logger.declined")];
+
+ this.service = service;
+}
+this.DeclinedEngines.prototype = {
+ updateDeclined: function (meta, engineManager=this.service.engineManager) {
+ let enabled = new Set(engineManager.getEnabled().map(e => e.name));
+ let known = new Set(engineManager.getAll().map(e => e.name));
+ let remoteDeclined = new Set(meta.payload.declined || []);
+ let localDeclined = new Set(engineManager.getDeclined());
+
+ this._log.debug("Handling remote declined: " + JSON.stringify([...remoteDeclined]));
+ this._log.debug("Handling local declined: " + JSON.stringify([...localDeclined]));
+
+ // Any engines that are locally enabled should be removed from the remote
+ // declined list.
+ //
+ // Any engines that are locally declined should be added to the remote
+ // declined list.
+ let newDeclined = CommonUtils.union(localDeclined, CommonUtils.difference(remoteDeclined, enabled));
+
+ // If our declined set has changed, put it into the meta object and mark
+ // it as changed.
+ let declinedChanged = !CommonUtils.setEqual(newDeclined, remoteDeclined);
+ this._log.debug("Declined changed? " + declinedChanged);
+ if (declinedChanged) {
+ meta.changed = true;
+ meta.payload.declined = [...newDeclined];
+ }
+
+ // Update the engine manager regardless.
+ engineManager.setDeclined(newDeclined);
+
+ // Any engines that are locally known, locally disabled, and not remotely
+ // or locally declined, are candidates for enablement.
+ let undecided = CommonUtils.difference(CommonUtils.difference(known, enabled), newDeclined);
+ if (undecided.size) {
+ let subject = {
+ declined: newDeclined,
+ enabled: enabled,
+ known: known,
+ undecided: undecided,
+ };
+ CommonUtils.nextTick(() => {
+ Observers.notify("weave:engines:notdeclined", subject);
+ });
+ }
+
+ return declinedChanged;
+ },
+};
diff --git a/components/weave/src/stages/enginesync.js b/components/weave/src/stages/enginesync.js
new file mode 100644
index 000000000..61f2005d8
--- /dev/null
+++ b/components/weave/src/stages/enginesync.js
@@ -0,0 +1,326 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 contains code for synchronizing engines.
+ */
+
+this.EXPORTED_SYMBOLS = ["EngineSynchronizer"];
+
+var {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/policies.js");
+Cu.import("resource://services-sync/util.js");
+
+/**
+ * Perform synchronization of engines.
+ *
+ * This was originally split out of service.js. The API needs lots of love.
+ */
+this.EngineSynchronizer = function EngineSynchronizer(service) {
+ this._log = Log.repository.getLogger("Sync.Synchronizer");
+ this._log.level = Log.Level[Svc.Prefs.get("log.logger.synchronizer")];
+
+ this.service = service;
+
+ this.onComplete = null;
+}
+
+EngineSynchronizer.prototype = {
+ sync: function sync() {
+ if (!this.onComplete) {
+ throw new Error("onComplete handler not installed.");
+ }
+
+ let startTime = Date.now();
+
+ this.service.status.resetSync();
+
+ // Make sure we should sync or record why we shouldn't.
+ let reason = this.service._checkSync();
+ if (reason) {
+ if (reason == kSyncNetworkOffline) {
+ this.service.status.sync = LOGIN_FAILED_NETWORK_ERROR;
+ }
+
+ // this is a purposeful abort rather than a failure, so don't set
+ // any status bits
+ reason = "Can't sync: " + reason;
+ this.onComplete(new Error("Can't sync: " + reason));
+ return;
+ }
+
+ // If we don't have a node, get one. If that fails, retry in 10 minutes.
+ if (!this.service.clusterURL && !this.service._clusterManager.setCluster()) {
+ this.service.status.sync = NO_SYNC_NODE_FOUND;
+ this._log.info("No cluster URL found. Cannot sync.");
+ this.onComplete(null);
+ return;
+ }
+
+ // Ping the server with a special info request once a day.
+ let infoURL = this.service.infoURL;
+ let now = Math.floor(Date.now() / 1000);
+ let lastPing = Svc.Prefs.get("lastPing", 0);
+ if (now - lastPing > 86400) { // 60 * 60 * 24
+ infoURL += "?v=" + WEAVE_VERSION;
+ Svc.Prefs.set("lastPing", now);
+ }
+
+ let engineManager = this.service.engineManager;
+
+ // Figure out what the last modified time is for each collection
+ let info = this.service._fetchInfo(infoURL);
+
+ // Convert the response to an object and read out the modified times
+ for (let engine of [this.service.clientsEngine].concat(engineManager.getAll())) {
+ engine.lastModified = info.obj[engine.name] || 0;
+ }
+
+ if (!(this.service._remoteSetup(info))) {
+ this.onComplete(new Error("Aborting sync, remote setup failed"));
+ return;
+ }
+
+ // Make sure we have an up-to-date list of clients before sending commands
+ this._log.debug("Refreshing client list.");
+ if (!this._syncEngine(this.service.clientsEngine)) {
+ // Clients is an engine like any other; it can fail with a 401,
+ // and we can elect to abort the sync.
+ this._log.warn("Client engine sync failed. Aborting.");
+ this.onComplete(null);
+ return;
+ }
+
+ // Wipe data in the desired direction if necessary
+ switch (Svc.Prefs.get("firstSync")) {
+ case "resetClient":
+ this.service.resetClient(engineManager.enabledEngineNames);
+ break;
+ case "wipeClient":
+ this.service.wipeClient(engineManager.enabledEngineNames);
+ break;
+ case "wipeRemote":
+ this.service.wipeRemote(engineManager.enabledEngineNames);
+ break;
+ }
+
+ if (this.service.clientsEngine.localCommands) {
+ try {
+ if (!(this.service.clientsEngine.processIncomingCommands())) {
+ this.service.status.sync = ABORT_SYNC_COMMAND;
+ this.onComplete(new Error("Processed command aborted sync."));
+ return;
+ }
+
+ // Repeat remoteSetup in-case the commands forced us to reset
+ if (!(this.service._remoteSetup(info))) {
+ this.onComplete(new Error("Remote setup failed after processing commands."));
+ return;
+ }
+ }
+ finally {
+ // Always immediately attempt to push back the local client (now
+ // without commands).
+ // Note that we don't abort here; if there's a 401 because we've
+ // been reassigned, we'll handle it around another engine.
+ this._syncEngine(this.service.clientsEngine);
+ }
+ }
+
+ // Update engines because it might change what we sync.
+ try {
+ this._updateEnabledEngines();
+ } catch (ex) {
+ this._log.debug("Updating enabled engines failed: ", ex);
+ this.service.errorHandler.checkServerError(ex);
+ this.onComplete(ex);
+ return;
+ }
+
+ try {
+ for (let engine of engineManager.getEnabled()) {
+ // If there's any problems with syncing the engine, report the failure
+ if (!(this._syncEngine(engine)) || this.service.status.enforceBackoff) {
+ this._log.info("Aborting sync for failure in " + engine.name);
+ break;
+ }
+ }
+
+ // If _syncEngine fails for a 401, we might not have a cluster URL here.
+ // If that's the case, break out of this immediately, rather than
+ // throwing an exception when trying to fetch metaURL.
+ if (!this.service.clusterURL) {
+ this._log.debug("Aborting sync, no cluster URL: " +
+ "not uploading new meta/global.");
+ this.onComplete(null);
+ return;
+ }
+
+ // Upload meta/global if any engines changed anything.
+ let meta = this.service.recordManager.get(this.service.metaURL);
+ if (meta.isNew || meta.changed) {
+ this._log.info("meta/global changed locally: reuploading.");
+ try {
+ this.service.uploadMetaGlobal(meta);
+ delete meta.isNew;
+ delete meta.changed;
+ } catch (error) {
+ this._log.error("Unable to upload meta/global. Leaving marked as new.");
+ }
+ }
+
+ // If there were no sync engine failures
+ if (this.service.status.service != SYNC_FAILED_PARTIAL) {
+ Svc.Prefs.set("lastSync", new Date().toString());
+ this.service.status.sync = SYNC_SUCCEEDED;
+ }
+ } finally {
+ Svc.Prefs.reset("firstSync");
+
+ let syncTime = ((Date.now() - startTime) / 1000).toFixed(2);
+ let dateStr = new Date().toLocaleFormat(LOG_DATE_FORMAT);
+ this._log.info("Sync completed at " + dateStr
+ + " after " + syncTime + " secs.");
+ }
+
+ this.onComplete(null);
+ },
+
+ // Returns true if sync should proceed.
+ // false / no return value means sync should be aborted.
+ _syncEngine: function _syncEngine(engine) {
+ try {
+ engine.sync();
+ }
+ catch(e) {
+ if (e.status == 401) {
+ // Maybe a 401, cluster update perhaps needed?
+ // We rely on ErrorHandler observing the sync failure notification to
+ // schedule another sync and clear node assignment values.
+ // Here we simply want to muffle the exception and return an
+ // appropriate value.
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ _updateEnabledFromMeta: function (meta, numClients, engineManager=this.service.engineManager) {
+ this._log.info("Updating enabled engines: " +
+ numClients + " clients.");
+
+ if (meta.isNew || !meta.payload.engines) {
+ this._log.debug("meta/global isn't new, or is missing engines. Not updating enabled state.");
+ return;
+ }
+
+ // If we're the only client, and no engines are marked as enabled,
+ // thumb our noses at the server data: it can't be right.
+ // Belt-and-suspenders approach to Bug 615926.
+ let hasEnabledEngines = false;
+ for (let e in meta.payload.engines) {
+ if (e != "clients") {
+ hasEnabledEngines = true;
+ break;
+ }
+ }
+
+ if ((numClients <= 1) && !hasEnabledEngines) {
+ this._log.info("One client and no enabled engines: not touching local engine status.");
+ return;
+ }
+
+ this.service._ignorePrefObserver = true;
+
+ let enabled = engineManager.enabledEngineNames;
+
+ let toDecline = new Set();
+ let toUndecline = new Set();
+
+ for (let engineName in meta.payload.engines) {
+ if (engineName == "clients") {
+ // Clients is special.
+ continue;
+ }
+ let index = enabled.indexOf(engineName);
+ if (index != -1) {
+ // The engine is enabled locally. Nothing to do.
+ enabled.splice(index, 1);
+ continue;
+ }
+ let engine = engineManager.get(engineName);
+ if (!engine) {
+ // The engine doesn't exist locally. Nothing to do.
+ continue;
+ }
+
+ let attemptedEnable = false;
+ // If the engine was enabled remotely, enable it locally.
+ if (!Svc.Prefs.get("engineStatusChanged." + engine.prefName, false)) {
+ this._log.trace("Engine " + engineName + " was enabled. Marking as non-declined.");
+ toUndecline.add(engineName);
+ this._log.trace(engineName + " engine was enabled remotely.");
+ engine.enabled = true;
+ // Note that setting engine.enabled to true might not have worked for
+ // the password engine if a master-password is enabled. However, it's
+ // still OK that we added it to undeclined - the user *tried* to enable
+ // it remotely - so it still winds up as not being flagged as declined
+ // even though it's disabled remotely.
+ attemptedEnable = true;
+ }
+
+ // If either the engine was disabled locally or enabling the engine
+ // failed (see above re master-password) then wipe server data and
+ // disable it everywhere.
+ if (!engine.enabled) {
+ this._log.trace("Wiping data for " + engineName + " engine.");
+ engine.wipeServer();
+ delete meta.payload.engines[engineName];
+ meta.changed = true; // the new enabled state must propagate
+ // We also here mark the engine as declined, because the pref
+ // was explicitly changed to false - unless we tried, and failed,
+ // to enable it - in which case we leave the declined state alone.
+ if (!attemptedEnable) {
+ // This will be reflected in meta/global in the next stage.
+ this._log.trace("Engine " + engineName + " was disabled locally. Marking as declined.");
+ toDecline.add(engineName);
+ }
+ }
+ }
+
+ // Any remaining engines were either enabled locally or disabled remotely.
+ for (let engineName of enabled) {
+ let engine = engineManager.get(engineName);
+ if (Svc.Prefs.get("engineStatusChanged." + engine.prefName, false)) {
+ this._log.trace("The " + engineName + " engine was enabled locally.");
+ toUndecline.add(engineName);
+ } else {
+ this._log.trace("The " + engineName + " engine was disabled remotely.");
+
+ // Don't automatically mark it as declined!
+ engine.enabled = false;
+ }
+ }
+
+ engineManager.decline(toDecline);
+ engineManager.undecline(toUndecline);
+
+ Svc.Prefs.resetBranch("engineStatusChanged.");
+ this.service._ignorePrefObserver = false;
+ },
+
+ _updateEnabledEngines: function () {
+ let meta = this.service.recordManager.get(this.service.metaURL);
+ let numClients = this.service.scheduler.numClients;
+ let engineManager = this.service.engineManager;
+
+ this._updateEnabledFromMeta(meta, numClients, engineManager);
+ },
+};
+Object.freeze(EngineSynchronizer.prototype);
diff --git a/components/weave/src/sync/addonsreconciler.js b/components/weave/src/sync/addonsreconciler.js
new file mode 100644
index 000000000..ec0896bb2
--- /dev/null
+++ b/components/weave/src/sync/addonsreconciler.js
@@ -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/. */
+
+/**
+ * This file contains middleware to reconcile state of AddonManager for
+ * purposes of tracking events for Sync. The content in this file exists
+ * because AddonManager does not have a getChangesSinceX() API and adding
+ * that functionality properly was deemed too time-consuming at the time
+ * add-on sync was originally written. If/when AddonManager adds this API,
+ * this file can go away and the add-ons engine can be rewritten to use it.
+ *
+ * It was decided to have this tracking functionality exist in a separate
+ * standalone file so it could be more easily understood, tested, and
+ * hopefully ported.
+ */
+
+"use strict";
+
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/util.js");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+
+const DEFAULT_STATE_FILE = "addonsreconciler";
+
+this.CHANGE_INSTALLED = 1;
+this.CHANGE_UNINSTALLED = 2;
+this.CHANGE_ENABLED = 3;
+this.CHANGE_DISABLED = 4;
+
+this.EXPORTED_SYMBOLS = ["AddonsReconciler", "CHANGE_INSTALLED",
+ "CHANGE_UNINSTALLED", "CHANGE_ENABLED",
+ "CHANGE_DISABLED"];
+/**
+ * Maintains state of add-ons.
+ *
+ * State is maintained in 2 data structures, an object mapping add-on IDs
+ * to metadata and an array of changes over time. The object mapping can be
+ * thought of as a minimal copy of data from AddonManager which is needed for
+ * Sync. The array is effectively a log of changes over time.
+ *
+ * The data structures are persisted to disk by serializing to a JSON file in
+ * the current profile. The data structures are updated by 2 mechanisms. First,
+ * they can be refreshed from the global state of the AddonManager. This is a
+ * sure-fire way of ensuring the reconciler is up to date. Second, the
+ * reconciler adds itself as an AddonManager listener. When it receives change
+ * notifications, it updates its internal state incrementally.
+ *
+ * The internal state is persisted to a JSON file in the profile directory.
+ *
+ * An instance of this is bound to an AddonsEngine instance. In reality, it
+ * likely exists as a singleton. To AddonsEngine, it functions as a store and
+ * an entity which emits events for tracking.
+ *
+ * The usage pattern for instances of this class is:
+ *
+ * let reconciler = new AddonsReconciler();
+ * reconciler.loadState(null, function(error) { ... });
+ *
+ * // At this point, your instance should be ready to use.
+ *
+ * When you are finished with the instance, please call:
+ *
+ * reconciler.stopListening();
+ * reconciler.saveState(...);
+ *
+ * There are 2 classes of listeners in the AddonManager: AddonListener and
+ * InstallListener. This class is a listener for both (member functions just
+ * get called directly).
+ *
+ * When an add-on is installed, listeners are called in the following order:
+ *
+ * IL.onInstallStarted, AL.onInstalling, IL.onInstallEnded, AL.onInstalled
+ *
+ * For non-restartless add-ons, an application restart may occur between
+ * IL.onInstallEnded and AL.onInstalled. Unfortunately, Sync likely will
+ * not be loaded when AL.onInstalled is fired shortly after application
+ * start, so it won't see this event. Therefore, for add-ons requiring a
+ * restart, Sync treats the IL.onInstallEnded event as good enough to
+ * indicate an install. For restartless add-ons, Sync assumes AL.onInstalled
+ * will follow shortly after IL.onInstallEnded and thus it ignores
+ * IL.onInstallEnded.
+ *
+ * The listeners can also see events related to the download of the add-on.
+ * This class isn't interested in those. However, there are failure events,
+ * IL.onDownloadFailed and IL.onDownloadCanceled which get called if a
+ * download doesn't complete successfully.
+ *
+ * For uninstalls, we see AL.onUninstalling then AL.onUninstalled. Like
+ * installs, the events could be separated by an application restart and Sync
+ * may not see the onUninstalled event. Again, if we require a restart, we
+ * react to onUninstalling. If not, we assume we'll get onUninstalled.
+ *
+ * Enabling and disabling work by sending:
+ *
+ * AL.onEnabling, AL.onEnabled
+ * AL.onDisabling, AL.onDisabled
+ *
+ * Again, they may be separated by a restart, so we heed the requiresRestart
+ * flag.
+ *
+ * Actions can be undone. All undoable actions notify the same
+ * AL.onOperationCancelled event. We treat this event like any other.
+ *
+ * Restartless add-ons have interesting behavior during uninstall. These
+ * add-ons are first disabled then they are actually uninstalled. So, we will
+ * see AL.onDisabling and AL.onDisabled. The onUninstalling and onUninstalled
+ * events only come after the Addon Manager is closed or another view is
+ * switched to. In the case of Sync performing the uninstall, the uninstall
+ * events will occur immediately. However, we still see disabling events and
+ * heed them like they were normal. In the end, the state is proper.
+ */
+this.AddonsReconciler = function AddonsReconciler() {
+ this._log = Log.repository.getLogger("Sync.AddonsReconciler");
+ let level = Svc.Prefs.get("log.logger.addonsreconciler", "Debug");
+ this._log.level = Log.Level[level];
+
+ Svc.Obs.add("xpcom-shutdown", this.stopListening, this);
+};
+AddonsReconciler.prototype = {
+ /** Flag indicating whether we are listening to AddonManager events. */
+ _listening: false,
+
+ /**
+ * Whether state has been loaded from a file.
+ *
+ * State is loaded on demand if an operation requires it.
+ */
+ _stateLoaded: false,
+
+ /**
+ * Define this as false if the reconciler should not persist state
+ * to disk when handling events.
+ *
+ * This allows test code to avoid spinning to write during observer
+ * notifications and xpcom shutdown, which appears to cause hangs on WinXP
+ * (Bug 873861).
+ */
+ _shouldPersist: true,
+
+ /** Log logger instance */
+ _log: null,
+
+ /**
+ * Container for add-on metadata.
+ *
+ * Keys are add-on IDs. Values are objects which describe the state of the
+ * add-on. This is a minimal mirror of data that can be queried from
+ * AddonManager. In some cases, we retain data longer than AddonManager.
+ */
+ _addons: {},
+
+ /**
+ * List of add-on changes over time.
+ *
+ * Each element is an array of [time, change, id].
+ */
+ _changes: [],
+
+ /**
+ * Objects subscribed to changes made to this instance.
+ */
+ _listeners: [],
+
+ /**
+ * Accessor for add-ons in this object.
+ *
+ * Returns an object mapping add-on IDs to objects containing metadata.
+ */
+ get addons() {
+ this._ensureStateLoaded();
+ return this._addons;
+ },
+
+ /**
+ * Load reconciler state from a file.
+ *
+ * The path is relative to the weave directory in the profile. If no
+ * path is given, the default one is used.
+ *
+ * If the file does not exist or there was an error parsing the file, the
+ * state will be transparently defined as empty.
+ *
+ * @param path
+ * Path to load. ".json" is appended automatically. If not defined,
+ * a default path will be consulted.
+ * @param callback
+ * Callback to be executed upon file load. The callback receives a
+ * truthy error argument signifying whether an error occurred and a
+ * boolean indicating whether data was loaded.
+ */
+ loadState: function loadState(path, callback) {
+ let file = path || DEFAULT_STATE_FILE;
+ Utils.jsonLoad(file, this, function(json) {
+ this._addons = {};
+ this._changes = [];
+
+ if (!json) {
+ this._log.debug("No data seen in loaded file: " + file);
+ if (callback) {
+ callback(null, false);
+ }
+
+ return;
+ }
+
+ let version = json.version;
+ if (!version || version != 1) {
+ this._log.error("Could not load JSON file because version not " +
+ "supported: " + version);
+ if (callback) {
+ callback(null, false);
+ }
+
+ return;
+ }
+
+ this._addons = json.addons;
+ for (let id in this._addons) {
+ let record = this._addons[id];
+ record.modified = new Date(record.modified);
+ }
+
+ for (let [time, change, id] of json.changes) {
+ this._changes.push([new Date(time), change, id]);
+ }
+
+ if (callback) {
+ callback(null, true);
+ }
+ });
+ },
+
+ /**
+ * Saves the current state to a file in the local profile.
+ *
+ * @param path
+ * String path in profile to save to. If not defined, the default
+ * will be used.
+ * @param callback
+ * Function to be invoked on save completion. No parameters will be
+ * passed to callback.
+ */
+ saveState: function saveState(path, callback) {
+ let file = path || DEFAULT_STATE_FILE;
+ let state = {version: 1, addons: {}, changes: []};
+
+ for (let [id, record] of Object.entries(this._addons)) {
+ state.addons[id] = {};
+ for (let [k, v] of Object.entries(record)) {
+ if (k == "modified") {
+ state.addons[id][k] = v.getTime();
+ }
+ else {
+ state.addons[id][k] = v;
+ }
+ }
+ }
+
+ for (let [time, change, id] of this._changes) {
+ state.changes.push([time.getTime(), change, id]);
+ }
+
+ this._log.info("Saving reconciler state to file: " + file);
+ Utils.jsonSave(file, this, state, callback);
+ },
+
+ /**
+ * Registers a change listener with this instance.
+ *
+ * Change listeners are called every time a change is recorded. The listener
+ * is an object with the function "changeListener" that takes 3 arguments,
+ * the Date at which the change happened, the type of change (a CHANGE_*
+ * constant), and the add-on state object reflecting the current state of
+ * the add-on at the time of the change.
+ *
+ * @param listener
+ * Object containing changeListener function.
+ */
+ addChangeListener: function addChangeListener(listener) {
+ if (this._listeners.indexOf(listener) == -1) {
+ this._log.debug("Adding change listener.");
+ this._listeners.push(listener);
+ }
+ },
+
+ /**
+ * Removes a previously-installed change listener from the instance.
+ *
+ * @param listener
+ * Listener instance to remove.
+ */
+ removeChangeListener: function removeChangeListener(listener) {
+ this._listeners = this._listeners.filter(function(element) {
+ if (element == listener) {
+ this._log.debug("Removing change listener.");
+ return false;
+ } else {
+ return true;
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Tells the instance to start listening for AddonManager changes.
+ *
+ * This is typically called automatically when Sync is loaded.
+ */
+ startListening: function startListening() {
+ if (this._listening) {
+ return;
+ }
+
+ this._log.info("Registering as Add-on Manager listener.");
+ AddonManager.addAddonListener(this);
+ AddonManager.addInstallListener(this);
+ this._listening = true;
+ },
+
+ /**
+ * Tells the instance to stop listening for AddonManager changes.
+ *
+ * The reconciler should always be listening. This should only be called when
+ * the instance is being destroyed.
+ *
+ * This function will get called automatically on XPCOM shutdown. However, it
+ * is a best practice to call it yourself.
+ */
+ stopListening: function stopListening() {
+ if (!this._listening) {
+ return;
+ }
+
+ this._log.debug("Stopping listening and removing AddonManager listeners.");
+ AddonManager.removeInstallListener(this);
+ AddonManager.removeAddonListener(this);
+ this._listening = false;
+ },
+
+ /**
+ * Refreshes the global state of add-ons by querying the AddonManager.
+ */
+ refreshGlobalState: function refreshGlobalState(callback) {
+ this._log.info("Refreshing global state from AddonManager.");
+ this._ensureStateLoaded();
+
+ let installs;
+
+ AddonManager.getAllAddons(function (addons) {
+ let ids = {};
+
+ for (let addon of addons) {
+ ids[addon.id] = true;
+ this.rectifyStateFromAddon(addon);
+ }
+
+ // Look for locally-defined add-ons that no longer exist and update their
+ // record.
+ for (let [id, addon] of Object.entries(this._addons)) {
+ if (id in ids) {
+ continue;
+ }
+
+ // If the id isn't in ids, it means that the add-on has been deleted or
+ // the add-on is in the process of being installed. We detect the
+ // latter by seeing if an AddonInstall is found for this add-on.
+
+ if (!installs) {
+ let cb = Async.makeSyncCallback();
+ AddonManager.getAllInstalls(cb);
+ installs = Async.waitForSyncCallback(cb);
+ }
+
+ let installFound = false;
+ for (let install of installs) {
+ if (install.addon && install.addon.id == id &&
+ install.state == AddonManager.STATE_INSTALLED) {
+
+ installFound = true;
+ break;
+ }
+ }
+
+ if (installFound) {
+ continue;
+ }
+
+ if (addon.installed) {
+ addon.installed = false;
+ this._log.debug("Adding change because add-on not present in " +
+ "Add-on Manager: " + id);
+ this._addChange(new Date(), CHANGE_UNINSTALLED, addon);
+ }
+ }
+
+ // See note for _shouldPersist.
+ if (this._shouldPersist) {
+ this.saveState(null, callback);
+ } else {
+ callback();
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Rectifies the state of an add-on from an Addon instance.
+ *
+ * This basically says "given an Addon instance, assume it is truth and
+ * apply changes to the local state to reflect it."
+ *
+ * This function could result in change listeners being called if the local
+ * state differs from the passed add-on's state.
+ *
+ * @param addon
+ * Addon instance being updated.
+ */
+ rectifyStateFromAddon: function rectifyStateFromAddon(addon) {
+ this._log.debug(`Rectifying state for addon ${addon.name} (version=${addon.version}, id=${addon.id})`);
+ this._ensureStateLoaded();
+
+ let id = addon.id;
+ let enabled = !addon.userDisabled;
+ let guid = addon.syncGUID;
+ let now = new Date();
+
+ if (!(id in this._addons)) {
+ let record = {
+ id: id,
+ guid: guid,
+ enabled: enabled,
+ installed: true,
+ modified: now,
+ type: addon.type,
+ scope: addon.scope,
+ foreignInstall: addon.foreignInstall
+ };
+ this._addons[id] = record;
+ this._log.debug("Adding change because add-on not present locally: " +
+ id);
+ this._addChange(now, CHANGE_INSTALLED, record);
+ return;
+ }
+
+ let record = this._addons[id];
+
+ if (!record.installed) {
+ // It is possible the record is marked as uninstalled because an
+ // uninstall is pending.
+ if (!(addon.pendingOperations & AddonManager.PENDING_UNINSTALL)) {
+ record.installed = true;
+ record.modified = now;
+ }
+ }
+
+ if (record.enabled != enabled) {
+ record.enabled = enabled;
+ record.modified = now;
+ let change = enabled ? CHANGE_ENABLED : CHANGE_DISABLED;
+ this._log.debug("Adding change because enabled state changed: " + id);
+ this._addChange(new Date(), change, record);
+ }
+
+ if (record.guid != guid) {
+ record.guid = guid;
+ // We don't record a change because the Sync engine rectifies this on its
+ // own. This is tightly coupled with Sync. If this code is ever lifted
+ // outside of Sync, this exception should likely be removed.
+ }
+ },
+
+ /**
+ * Record a change in add-on state.
+ *
+ * @param date
+ * Date at which the change occurred.
+ * @param change
+ * The type of the change. A CHANGE_* constant.
+ * @param state
+ * The new state of the add-on. From this.addons.
+ */
+ _addChange: function _addChange(date, change, state) {
+ this._log.info("Change recorded for " + state.id);
+ this._changes.push([date, change, state.id]);
+
+ for (let listener of this._listeners) {
+ try {
+ listener.changeListener.call(listener, date, change, state);
+ } catch (ex) {
+ this._log.warn("Exception calling change listener: ", ex);
+ }
+ }
+ },
+
+ /**
+ * Obtain the set of changes to add-ons since the date passed.
+ *
+ * This will return an array of arrays. Each entry in the array has the
+ * elements [date, change_type, id], where
+ *
+ * date - Date instance representing when the change occurred.
+ * change_type - One of CHANGE_* constants.
+ * id - ID of add-on that changed.
+ */
+ getChangesSinceDate: function getChangesSinceDate(date) {
+ this._ensureStateLoaded();
+
+ let length = this._changes.length;
+ for (let i = 0; i < length; i++) {
+ if (this._changes[i][0] >= date) {
+ return this._changes.slice(i);
+ }
+ }
+
+ return [];
+ },
+
+ /**
+ * Prunes all recorded changes from before the specified Date.
+ *
+ * @param date
+ * Entries older than this Date will be removed.
+ */
+ pruneChangesBeforeDate: function pruneChangesBeforeDate(date) {
+ this._ensureStateLoaded();
+
+ this._changes = this._changes.filter(function test_age(change) {
+ return change[0] >= date;
+ });
+ },
+
+ /**
+ * Obtains the set of all known Sync GUIDs for add-ons.
+ *
+ * @return Object with guids as keys and values of true.
+ */
+ getAllSyncGUIDs: function getAllSyncGUIDs() {
+ let result = {};
+ for (let id in this.addons) {
+ result[id] = true;
+ }
+
+ return result;
+ },
+
+ /**
+ * Obtain the add-on state record for an add-on by Sync GUID.
+ *
+ * If the add-on could not be found, returns null.
+ *
+ * @param guid
+ * Sync GUID of add-on to retrieve.
+ * @return Object on success on null on failure.
+ */
+ getAddonStateFromSyncGUID: function getAddonStateFromSyncGUID(guid) {
+ for (let id in this.addons) {
+ let addon = this.addons[id];
+ if (addon.guid == guid) {
+ return addon;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Ensures that state is loaded before continuing.
+ *
+ * This is called internally by anything that accesses the internal data
+ * structures. It effectively just-in-time loads serialized state.
+ */
+ _ensureStateLoaded: function _ensureStateLoaded() {
+ if (this._stateLoaded) {
+ return;
+ }
+
+ let cb = Async.makeSpinningCallback();
+ this.loadState(null, cb);
+ cb.wait();
+ this._stateLoaded = true;
+ },
+
+ /**
+ * Handler that is invoked as part of the AddonManager listeners.
+ */
+ _handleListener: function _handlerListener(action, addon, requiresRestart) {
+ // Since this is called as an observer, we explicitly trap errors and
+ // log them to ourselves so we don't see errors reported elsewhere.
+ try {
+ let id = addon.id;
+ this._log.debug("Add-on change: " + action + " to " + id);
+
+ // We assume that every event for non-restartless add-ons is
+ // followed by another event and that this follow-up event is the most
+ // appropriate to react to. Currently we ignore onEnabling, onDisabling,
+ // and onUninstalling for non-restartless add-ons.
+ if (requiresRestart === false) {
+ this._log.debug("Ignoring " + action + " for restartless add-on.");
+ return;
+ }
+
+ switch (action) {
+ case "onEnabling":
+ case "onEnabled":
+ case "onDisabling":
+ case "onDisabled":
+ case "onInstalled":
+ case "onInstallEnded":
+ case "onOperationCancelled":
+ this.rectifyStateFromAddon(addon);
+ break;
+
+ case "onUninstalling":
+ case "onUninstalled":
+ let id = addon.id;
+ let addons = this.addons;
+ if (id in addons) {
+ let now = new Date();
+ let record = addons[id];
+ record.installed = false;
+ record.modified = now;
+ this._log.debug("Adding change because of uninstall listener: " +
+ id);
+ this._addChange(now, CHANGE_UNINSTALLED, record);
+ }
+ }
+
+ // See note for _shouldPersist.
+ if (this._shouldPersist) {
+ let cb = Async.makeSpinningCallback();
+ this.saveState(null, cb);
+ cb.wait();
+ }
+ }
+ catch (ex) {
+ this._log.warn("Exception: ", ex);
+ }
+ },
+
+ // AddonListeners
+ onEnabling: function onEnabling(addon, requiresRestart) {
+ this._handleListener("onEnabling", addon, requiresRestart);
+ },
+ onEnabled: function onEnabled(addon) {
+ this._handleListener("onEnabled", addon);
+ },
+ onDisabling: function onDisabling(addon, requiresRestart) {
+ this._handleListener("onDisabling", addon, requiresRestart);
+ },
+ onDisabled: function onDisabled(addon) {
+ this._handleListener("onDisabled", addon);
+ },
+ onInstalling: function onInstalling(addon, requiresRestart) {
+ this._handleListener("onInstalling", addon, requiresRestart);
+ },
+ onInstalled: function onInstalled(addon) {
+ this._handleListener("onInstalled", addon);
+ },
+ onUninstalling: function onUninstalling(addon, requiresRestart) {
+ this._handleListener("onUninstalling", addon, requiresRestart);
+ },
+ onUninstalled: function onUninstalled(addon) {
+ this._handleListener("onUninstalled", addon);
+ },
+ onOperationCancelled: function onOperationCancelled(addon) {
+ this._handleListener("onOperationCancelled", addon);
+ },
+
+ // InstallListeners
+ onInstallEnded: function onInstallEnded(install, addon) {
+ this._handleListener("onInstallEnded", addon);
+ }
+};
diff --git a/components/weave/src/sync/addonutils.js b/components/weave/src/sync/addonutils.js
new file mode 100644
index 000000000..3332f4cfc
--- /dev/null
+++ b/components/weave/src/sync/addonutils.js
@@ -0,0 +1,474 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["AddonUtils"];
+
+var {interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/util.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+ "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
+ "resource://gre/modules/addons/AddonRepository.jsm");
+
+function AddonUtilsInternal() {
+ this._log = Log.repository.getLogger("Sync.AddonUtils");
+ this._log.Level = Log.Level[Svc.Prefs.get("log.logger.addonutils")];
+}
+AddonUtilsInternal.prototype = {
+ /**
+ * Obtain an AddonInstall object from an AddonSearchResult instance.
+ *
+ * The callback will be invoked with the result of the operation. The
+ * callback receives 2 arguments, error and result. Error will be falsy
+ * on success or some kind of error value otherwise. The result argument
+ * will be an AddonInstall on success or null on failure. It is possible
+ * for the error to be falsy but result to be null. This could happen if
+ * an install was not found.
+ *
+ * @param addon
+ * AddonSearchResult to obtain install from.
+ * @param cb
+ * Function to be called with result of operation.
+ */
+ getInstallFromSearchResult:
+ function getInstallFromSearchResult(addon, cb, requireSecureURI=true) {
+
+ this._log.debug("Obtaining install for " + addon.id);
+
+ // Verify that the source URI uses TLS. We don't allow installs from
+ // insecure sources for security reasons. The Addon Manager ensures that
+ // cert validation, etc is performed.
+ if (requireSecureURI) {
+ let scheme = addon.sourceURI.scheme;
+ if (scheme != "https") {
+ cb(new Error("Insecure source URI scheme: " + scheme), addon.install);
+ return;
+ }
+ }
+
+ // We should theoretically be able to obtain (and use) addon.install if
+ // it is available. However, the addon.sourceURI rewriting won't be
+ // reflected in the AddonInstall, so we can't use it. If we ever get rid
+ // of sourceURI rewriting, we can avoid having to reconstruct the
+ // AddonInstall.
+ AddonManager.getInstallForURL(
+ addon.sourceURI.spec,
+ function handleInstall(install) {
+ cb(null, install);
+ },
+ "application/x-xpinstall",
+ undefined,
+ addon.name,
+ addon.iconURL,
+ addon.version
+ );
+ },
+
+ /**
+ * Installs an add-on from an AddonSearchResult instance.
+ *
+ * The options argument defines extra options to control the install.
+ * Recognized keys in this map are:
+ *
+ * syncGUID - Sync GUID to use for the new add-on.
+ * enabled - Boolean indicating whether the add-on should be enabled upon
+ * install.
+ * requireSecureURI - Boolean indicating whether to require a secure
+ * URI to install from. This defaults to true.
+ *
+ * When complete it calls a callback with 2 arguments, error and result.
+ *
+ * If error is falsy, result is an object. If error is truthy, result is
+ * null.
+ *
+ * The result object has the following keys:
+ *
+ * id ID of add-on that was installed.
+ * install AddonInstall that was installed.
+ * addon Addon that was installed.
+ *
+ * @param addon
+ * AddonSearchResult to install add-on from.
+ * @param options
+ * Object with additional metadata describing how to install add-on.
+ * @param cb
+ * Function to be invoked with result of operation.
+ */
+ installAddonFromSearchResult:
+ function installAddonFromSearchResult(addon, options, cb) {
+ this._log.info("Trying to install add-on from search result: " + addon.id);
+
+ if (options.requireSecureURI === undefined) {
+ options.requireSecureURI = true;
+ }
+
+ this.getInstallFromSearchResult(addon, function onResult(error, install) {
+ if (error) {
+ cb(error, null);
+ return;
+ }
+
+ if (!install) {
+ cb(new Error("AddonInstall not available: " + addon.id), null);
+ return;
+ }
+
+ try {
+ this._log.info("Installing " + addon.id);
+ let log = this._log;
+
+ let listener = {
+ onInstallStarted: function onInstallStarted(install) {
+ if (!options) {
+ return;
+ }
+
+ if (options.syncGUID) {
+ log.info("Setting syncGUID of " + install.name +": " +
+ options.syncGUID);
+ install.addon.syncGUID = options.syncGUID;
+ }
+
+ // We only need to change userDisabled if it is disabled because
+ // enabled is the default.
+ if ("enabled" in options && !options.enabled) {
+ log.info("Marking add-on as disabled for install: " +
+ install.name);
+ install.addon.userDisabled = true;
+ }
+ },
+ onInstallEnded: function(install, addon) {
+ install.removeListener(listener);
+
+ cb(null, {id: addon.id, install: install, addon: addon});
+ },
+ onInstallFailed: function(install) {
+ install.removeListener(listener);
+
+ cb(new Error("Install failed: " + install.error), null);
+ },
+ onDownloadFailed: function(install) {
+ install.removeListener(listener);
+
+ cb(new Error("Download failed: " + install.error), null);
+ }
+ };
+ install.addListener(listener);
+ install.install();
+ }
+ catch (ex) {
+ this._log.error("Error installing add-on: ", ex);
+ cb(ex, null);
+ }
+ }.bind(this), options.requireSecureURI);
+ },
+
+ /**
+ * Uninstalls the Addon instance and invoke a callback when it is done.
+ *
+ * @param addon
+ * Addon instance to uninstall.
+ * @param cb
+ * Function to be invoked when uninstall has finished. It receives a
+ * truthy value signifying error and the add-on which was uninstalled.
+ */
+ uninstallAddon: function uninstallAddon(addon, cb) {
+ let listener = {
+ onUninstalling: function(uninstalling, needsRestart) {
+ if (addon.id != uninstalling.id) {
+ return;
+ }
+
+ // We assume restartless add-ons will send the onUninstalled event
+ // soon.
+ if (!needsRestart) {
+ return;
+ }
+
+ // For non-restartless add-ons, we issue the callback on uninstalling
+ // because we will likely never see the uninstalled event.
+ AddonManager.removeAddonListener(listener);
+ cb(null, addon);
+ },
+ onUninstalled: function(uninstalled) {
+ if (addon.id != uninstalled.id) {
+ return;
+ }
+
+ AddonManager.removeAddonListener(listener);
+ cb(null, addon);
+ }
+ };
+ AddonManager.addAddonListener(listener);
+ addon.uninstall();
+ },
+
+ /**
+ * Installs multiple add-ons specified by metadata.
+ *
+ * The first argument is an array of objects. Each object must have the
+ * following keys:
+ *
+ * id - public ID of the add-on to install.
+ * syncGUID - syncGUID for new add-on.
+ * enabled - boolean indicating whether the add-on should be enabled.
+ * requireSecureURI - Boolean indicating whether to require a secure
+ * URI when installing from a remote location. This defaults to
+ * true.
+ *
+ * The callback will be called when activity on all add-ons is complete. The
+ * callback receives 2 arguments, error and result.
+ *
+ * If error is truthy, it contains a string describing the overall error.
+ *
+ * The 2nd argument to the callback is always an object with details on the
+ * overall execution state. It contains the following keys:
+ *
+ * installedIDs Array of add-on IDs that were installed.
+ * installs Array of AddonInstall instances that were installed.
+ * addons Array of Addon instances that were installed.
+ * errors Array of errors encountered. Only has elements if error is
+ * truthy.
+ *
+ * @param installs
+ * Array of objects describing add-ons to install.
+ * @param cb
+ * Function to be called when all actions are complete.
+ */
+ installAddons: function installAddons(installs, cb) {
+ if (!cb) {
+ throw new Error("Invalid argument: cb is not defined.");
+ }
+
+ let ids = [];
+ for (let addon of installs) {
+ ids.push(addon.id);
+ }
+
+ AddonRepository.getAddonsByIDs(ids, {
+ searchSucceeded: function searchSucceeded(addons, addonsLength, total) {
+ this._log.info("Found " + addonsLength + "/" + ids.length +
+ " add-ons during repository search.");
+
+ let ourResult = {
+ installedIDs: [],
+ installs: [],
+ addons: [],
+ errors: []
+ };
+
+ if (!addonsLength) {
+ cb(null, ourResult);
+ return;
+ }
+
+ let expectedInstallCount = 0;
+ let finishedCount = 0;
+ let installCallback = function installCallback(error, result) {
+ finishedCount++;
+
+ if (error) {
+ ourResult.errors.push(error);
+ } else {
+ ourResult.installedIDs.push(result.id);
+ ourResult.installs.push(result.install);
+ ourResult.addons.push(result.addon);
+ }
+
+ if (finishedCount >= expectedInstallCount) {
+ if (ourResult.errors.length > 0) {
+ cb(new Error("1 or more add-ons failed to install"), ourResult);
+ } else {
+ cb(null, ourResult);
+ }
+ }
+ }.bind(this);
+
+ let toInstall = [];
+
+ // Rewrite the "src" query string parameter of the source URI to note
+ // that the add-on was installed by Sync and not something else so
+ // server-side metrics aren't skewed (bug 708134). The server should
+ // ideally send proper URLs, but this solution was deemed too
+ // complicated at the time the functionality was implemented.
+ for (let addon of addons) {
+ // sourceURI presence isn't enforced by AddonRepository. So, we skip
+ // add-ons without a sourceURI.
+ if (!addon.sourceURI) {
+ this._log.info("Skipping install of add-on because missing " +
+ "sourceURI: " + addon.id);
+ continue;
+ }
+
+ toInstall.push(addon);
+
+ // We should always be able to QI the nsIURI to nsIURL. If not, we
+ // still try to install the add-on, but we don't rewrite the URL,
+ // potentially skewing metrics.
+ try {
+ addon.sourceURI.QueryInterface(Ci.nsIURL);
+ } catch (ex) {
+ this._log.warn("Unable to QI sourceURI to nsIURL: " +
+ addon.sourceURI.spec);
+ continue;
+ }
+
+ let params = addon.sourceURI.query.split("&").map(
+ function rewrite(param) {
+
+ if (param.indexOf("src=") == 0) {
+ return "src=sync";
+ } else {
+ return param;
+ }
+ });
+
+ addon.sourceURI.query = params.join("&");
+ }
+
+ expectedInstallCount = toInstall.length;
+
+ if (!expectedInstallCount) {
+ cb(null, ourResult);
+ return;
+ }
+
+ // Start all the installs asynchronously. They will report back to us
+ // as they finish, eventually triggering the global callback.
+ for (let addon of toInstall) {
+ let options = {};
+ for (let install of installs) {
+ if (install.id == addon.id) {
+ options = install;
+ break;
+ }
+ }
+
+ this.installAddonFromSearchResult(addon, options, installCallback);
+ }
+
+ }.bind(this),
+
+ searchFailed: function searchFailed() {
+ cb(new Error("AddonRepository search failed"), null);
+ },
+ });
+ },
+
+ /**
+ * Update the user disabled flag for an add-on.
+ *
+ * The supplied callback will be called when the operation is
+ * complete. If the new flag matches the existing or if the add-on
+ * isn't currently active, the function will fire the callback
+ * immediately. Else, the callback is invoked when the AddonManager
+ * reports the change has taken effect or has been registered.
+ *
+ * The callback receives as arguments:
+ *
+ * (Error) Encountered error during operation or null on success.
+ * (Addon) The add-on instance being operated on.
+ *
+ * @param addon
+ * (Addon) Add-on instance to operate on.
+ * @param value
+ * (bool) New value for add-on's userDisabled property.
+ * @param cb
+ * (function) Callback to be invoked on completion.
+ */
+ updateUserDisabled: function updateUserDisabled(addon, value, cb) {
+ if (addon.userDisabled == value) {
+ cb(null, addon);
+ return;
+ }
+
+ let listener = {
+ onEnabling: function onEnabling(wrapper, needsRestart) {
+ this._log.debug("onEnabling: " + wrapper.id);
+ if (wrapper.id != addon.id) {
+ return;
+ }
+
+ // We ignore the restartless case because we'll get onEnabled shortly.
+ if (!needsRestart) {
+ return;
+ }
+
+ AddonManager.removeAddonListener(listener);
+ cb(null, wrapper);
+ }.bind(this),
+
+ onEnabled: function onEnabled(wrapper) {
+ this._log.debug("onEnabled: " + wrapper.id);
+ if (wrapper.id != addon.id) {
+ return;
+ }
+
+ AddonManager.removeAddonListener(listener);
+ cb(null, wrapper);
+ }.bind(this),
+
+ onDisabling: function onDisabling(wrapper, needsRestart) {
+ this._log.debug("onDisabling: " + wrapper.id);
+ if (wrapper.id != addon.id) {
+ return;
+ }
+
+ if (!needsRestart) {
+ return;
+ }
+
+ AddonManager.removeAddonListener(listener);
+ cb(null, wrapper);
+ }.bind(this),
+
+ onDisabled: function onDisabled(wrapper) {
+ this._log.debug("onDisabled: " + wrapper.id);
+ if (wrapper.id != addon.id) {
+ return;
+ }
+
+ AddonManager.removeAddonListener(listener);
+ cb(null, wrapper);
+ }.bind(this),
+
+ onOperationCancelled: function onOperationCancelled(wrapper) {
+ this._log.debug("onOperationCancelled: " + wrapper.id);
+ if (wrapper.id != addon.id) {
+ return;
+ }
+
+ AddonManager.removeAddonListener(listener);
+ cb(new Error("Operation cancelled"), wrapper);
+ }.bind(this)
+ };
+
+ // The add-on listeners are only fired if the add-on is active. If not, the
+ // change is silently updated and made active when/if the add-on is active.
+
+ if (!addon.appDisabled) {
+ AddonManager.addAddonListener(listener);
+ }
+
+ this._log.info("Updating userDisabled flag: " + addon.id + " -> " + value);
+ addon.userDisabled = !!value;
+
+ if (!addon.appDisabled) {
+ cb(null, addon);
+ return;
+ }
+ // Else the listener will handle invoking the callback.
+ },
+
+};
+
+XPCOMUtils.defineLazyGetter(this, "AddonUtils", function() {
+ return new AddonUtilsInternal();
+});
diff --git a/components/weave/src/sync/constants.js b/components/weave/src/sync/constants.js
new file mode 100644
index 000000000..c1a5effa0
--- /dev/null
+++ b/components/weave/src/sync/constants.js
@@ -0,0 +1,192 @@
+#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/. */
+
+// Process each item in the "constants hash" to add to "global" and give a name
+this.EXPORTED_SYMBOLS = [];
+for (let [key, val] of Object.entries({
+
+WEAVE_VERSION: "@weave_version@",
+
+// Sync Server API version that the client supports.
+SYNC_API_VERSION: "1.1",
+USER_API_VERSION: "1.0",
+MISC_API_VERSION: "1.0",
+
+// Version of the data format this client supports. The data format describes
+// how records are packaged; this is separate from the Server API version and
+// the per-engine cleartext formats.
+STORAGE_VERSION: 5,
+PREFS_BRANCH: "services.sync.",
+
+// Host "key" to access Weave Identity in the password manager
+PWDMGR_HOST: "chrome://weave",
+PWDMGR_PASSWORD_REALM: "Mozilla Services Password",
+PWDMGR_PASSPHRASE_REALM: "Mozilla Services Encryption Passphrase",
+PWDMGR_KEYBUNDLE_REALM: "Mozilla Services Key Bundles",
+
+// Put in [] because those aren't allowed in a collection name.
+DEFAULT_KEYBUNDLE_NAME: "[default]",
+
+// Our extra input to SHA256-HMAC in generateEntry.
+// This includes the full crypto spec; change this when our algo changes.
+HMAC_INPUT: "Sync-AES_256_CBC-HMAC256",
+
+// Key dimensions.
+SYNC_KEY_ENCODED_LENGTH: 26,
+SYNC_KEY_DECODED_LENGTH: 16,
+SYNC_KEY_HYPHENATED_LENGTH: 31, // 26 chars, 5 hyphens.
+
+NO_SYNC_NODE_INTERVAL: 10 * 60 * 1000, // 10 minutes
+
+MAX_ERROR_COUNT_BEFORE_BACKOFF: 3,
+MAX_IGNORE_ERROR_COUNT: 5,
+
+// Backoff intervals
+MINIMUM_BACKOFF_INTERVAL: 15 * 60 * 1000, // 15 minutes
+MAXIMUM_BACKOFF_INTERVAL: 8 * 60 * 60 * 1000, // 8 hours
+
+// HMAC event handling timeout.
+// 10 minutes: a compromise between the multi-desktop sync interval
+// and the mobile sync interval.
+HMAC_EVENT_INTERVAL: 600000,
+
+// How long to wait between sync attempts if the Master Password is locked.
+MASTER_PASSWORD_LOCKED_RETRY_INTERVAL: 15 * 60 * 1000, // 15 minutes
+
+// The default for how long we "block" sync from running when doing a migration.
+DEFAULT_BLOCK_PERIOD: 2 * 24 * 60 * 60 * 1000, // 2 days
+
+// Separate from the ID fetch batch size to allow tuning for mobile.
+MOBILE_BATCH_SIZE: 50,
+
+// 50 is hardcoded here because of URL length restrictions.
+// (GUIDs can be up to 64 chars long.)
+// Individual engines can set different values for their limit if their
+// identifiers are shorter.
+DEFAULT_GUID_FETCH_BATCH_SIZE: 50,
+DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE: 50,
+
+// Default batch size for applying incoming records.
+DEFAULT_STORE_BATCH_SIZE: 1,
+HISTORY_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE
+FORMS_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE
+PASSWORDS_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE
+ADDONS_STORE_BATCH_SIZE: 1000000, // process all addons at once
+APPS_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE
+
+// score thresholds for early syncs
+SINGLE_USER_THRESHOLD: 1000,
+MULTI_DEVICE_THRESHOLD: 300,
+
+// Other score increment constants
+SCORE_INCREMENT_SMALL: 1,
+SCORE_INCREMENT_MEDIUM: 10,
+
+// Instant sync score increment
+SCORE_INCREMENT_XLARGE: 300 + 1, //MULTI_DEVICE_THRESHOLD + 1
+
+// Delay before incrementing global score
+SCORE_UPDATE_DELAY: 100,
+
+// Delay for the back observer debouncer. This is chosen to be longer than any
+// observed spurious idle/back events and short enough to pre-empt user activity.
+IDLE_OBSERVER_BACK_DELAY: 100,
+
+// Number of records to upload in a single POST (multiple POSTS if exceeded)
+// FIXME: Record size limit is 256k (new cluster), so this can be quite large!
+// (Bug 569295)
+MAX_UPLOAD_RECORDS: 100,
+MAX_HISTORY_UPLOAD: 5000,
+MAX_HISTORY_DOWNLOAD: 5000,
+
+// Top-level statuses:
+STATUS_OK: "success.status_ok",
+SYNC_FAILED: "error.sync.failed",
+LOGIN_FAILED: "error.login.failed",
+SYNC_FAILED_PARTIAL: "error.sync.failed_partial",
+CLIENT_NOT_CONFIGURED: "service.client_not_configured",
+STATUS_DISABLED: "service.disabled",
+MASTER_PASSWORD_LOCKED: "service.master_password_locked",
+
+// success states
+LOGIN_SUCCEEDED: "success.login",
+SYNC_SUCCEEDED: "success.sync",
+ENGINE_SUCCEEDED: "success.engine",
+
+// login failure status codes:
+LOGIN_FAILED_NO_USERNAME: "error.login.reason.no_username",
+LOGIN_FAILED_NO_PASSWORD: "error.login.reason.no_password2",
+LOGIN_FAILED_NO_PASSPHRASE: "error.login.reason.no_recoverykey",
+LOGIN_FAILED_NETWORK_ERROR: "error.login.reason.network",
+LOGIN_FAILED_SERVER_ERROR: "error.login.reason.server",
+LOGIN_FAILED_INVALID_PASSPHRASE: "error.login.reason.recoverykey",
+LOGIN_FAILED_LOGIN_REJECTED: "error.login.reason.account",
+LOGIN_FAILED_NOT_READY: "error.login.reason.initializing",
+
+// sync failure status codes
+METARECORD_DOWNLOAD_FAIL: "error.sync.reason.metarecord_download_fail",
+VERSION_OUT_OF_DATE: "error.sync.reason.version_out_of_date",
+DESKTOP_VERSION_OUT_OF_DATE: "error.sync.reason.desktop_version_out_of_date",
+SETUP_FAILED_NO_PASSPHRASE: "error.sync.reason.setup_failed_no_passphrase",
+CREDENTIALS_CHANGED: "error.sync.reason.credentials_changed",
+ABORT_SYNC_COMMAND: "aborting sync, process commands said so",
+NO_SYNC_NODE_FOUND: "error.sync.reason.no_node_found",
+OVER_QUOTA: "error.sync.reason.over_quota",
+PROLONGED_SYNC_FAILURE: "error.sync.prolonged_failure",
+SERVER_MAINTENANCE: "error.sync.reason.serverMaintenance",
+
+RESPONSE_OVER_QUOTA: "14",
+
+// engine failure status codes
+ENGINE_UPLOAD_FAIL: "error.engine.reason.record_upload_fail",
+ENGINE_DOWNLOAD_FAIL: "error.engine.reason.record_download_fail",
+ENGINE_UNKNOWN_FAIL: "error.engine.reason.unknown_fail",
+ENGINE_APPLY_FAIL: "error.engine.reason.apply_fail",
+ENGINE_METARECORD_DOWNLOAD_FAIL: "error.engine.reason.metarecord_download_fail",
+ENGINE_METARECORD_UPLOAD_FAIL: "error.engine.reason.metarecord_upload_fail",
+
+JPAKE_ERROR_CHANNEL: "jpake.error.channel",
+JPAKE_ERROR_NETWORK: "jpake.error.network",
+JPAKE_ERROR_SERVER: "jpake.error.server",
+JPAKE_ERROR_TIMEOUT: "jpake.error.timeout",
+JPAKE_ERROR_INTERNAL: "jpake.error.internal",
+JPAKE_ERROR_INVALID: "jpake.error.invalid",
+JPAKE_ERROR_NODATA: "jpake.error.nodata",
+JPAKE_ERROR_KEYMISMATCH: "jpake.error.keymismatch",
+JPAKE_ERROR_WRONGMESSAGE: "jpake.error.wrongmessage",
+JPAKE_ERROR_USERABORT: "jpake.error.userabort",
+JPAKE_ERROR_DELAYUNSUPPORTED: "jpake.error.delayunsupported",
+
+// info types for Service.getStorageInfo
+INFO_COLLECTIONS: "collections",
+INFO_COLLECTION_USAGE: "collection_usage",
+INFO_COLLECTION_COUNTS: "collection_counts",
+INFO_QUOTA: "quota",
+
+// Ways that a sync can be disabled (messages only to be printed in debug log)
+kSyncMasterPasswordLocked: "User elected to leave Master Password locked",
+kSyncWeaveDisabled: "Weave is disabled",
+kSyncNetworkOffline: "Network is offline",
+kSyncBackoffNotMet: "Trying to sync before the server said it's okay",
+kFirstSyncChoiceNotMade: "User has not selected an action for first sync",
+
+// Application IDs
+FIREFOX_ID: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+FENNEC_ID: "{a23983c0-fd0e-11dc-95ff-0800200c9a66}",
+SEAMONKEY_ID: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
+TEST_HARNESS_ID: "xuth@mozilla.org",
+
+MIN_PP_LENGTH: 12,
+MIN_PASS_LENGTH: 8,
+
+DEVICE_TYPE_DESKTOP: "desktop",
+DEVICE_TYPE_MOBILE: "mobile",
+
+LOG_DATE_FORMAT: "%Y-%m-%d %H:%M:%S",
+
+})) {
+ this[key] = val;
+ this.EXPORTED_SYMBOLS.push(key);
+}
diff --git a/components/weave/src/sync/engines.js b/components/weave/src/sync/engines.js
new file mode 100644
index 000000000..edb406857
--- /dev/null
+++ b/components/weave/src/sync/engines.js
@@ -0,0 +1,1613 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [
+ "EngineManager",
+ "Engine",
+ "SyncEngine",
+ "Tracker",
+ "Store"
+];
+
+var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Async.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/identity.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/resource.js");
+Cu.import("resource://services-sync/util.js");
+
+/*
+ * Trackers are associated with a single engine and deal with
+ * listening for changes to their particular data type.
+ *
+ * There are two things they keep track of:
+ * 1) A score, indicating how urgently the engine wants to sync
+ * 2) A list of IDs for all the changed items that need to be synced
+ * and updating their 'score', indicating how urgently they
+ * want to sync.
+ *
+ */
+this.Tracker = function Tracker(name, engine) {
+ if (!engine) {
+ throw new Error("Tracker must be associated with an Engine instance.");
+ }
+
+ name = name || "Unnamed";
+ this.name = this.file = name.toLowerCase();
+ this.engine = engine;
+
+ this._log = Log.repository.getLogger("Sync.Tracker." + name);
+ let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
+ this._log.level = Log.Level[level];
+
+ this._score = 0;
+ this._ignored = [];
+ this.ignoreAll = false;
+ this.changedIDs = {};
+ this.loadChangedIDs();
+
+ Svc.Obs.add("weave:engine:start-tracking", this);
+ Svc.Obs.add("weave:engine:stop-tracking", this);
+
+ Svc.Prefs.observe("engine." + this.engine.prefName, this);
+};
+
+Tracker.prototype = {
+ /*
+ * Score can be called as often as desired to decide which engines to sync
+ *
+ * Valid values for score:
+ * -1: Do not sync unless the user specifically requests it (almost disabled)
+ * 0: Nothing has changed
+ * 100: Please sync me ASAP!
+ *
+ * Setting it to other values should (but doesn't currently) throw an exception
+ */
+ get score() {
+ return this._score;
+ },
+
+ set score(value) {
+ this._score = value;
+ Observers.notify("weave:engine:score:updated", this.name);
+ },
+
+ // Should be called by service everytime a sync has been done for an engine
+ resetScore: function () {
+ this._score = 0;
+ },
+
+ persistChangedIDs: true,
+
+ /**
+ * Persist changedIDs to disk at a later date.
+ * Optionally pass a callback to be invoked when the write has occurred.
+ */
+ saveChangedIDs: function (cb) {
+ if (!this.persistChangedIDs) {
+ this._log.debug("Not saving changedIDs.");
+ return;
+ }
+ Utils.namedTimer(function () {
+ this._log.debug("Saving changed IDs to " + this.file);
+ Utils.jsonSave("changes/" + this.file, this, this.changedIDs, cb);
+ }, 1000, this, "_lazySave");
+ },
+
+ loadChangedIDs: function (cb) {
+ Utils.jsonLoad("changes/" + this.file, this, function(json) {
+ if (json && (typeof(json) == "object")) {
+ this.changedIDs = json;
+ } else if (json !== null) {
+ this._log.warn("Changed IDs file " + this.file + " contains non-object value.");
+ json = null;
+ }
+ if (cb) {
+ cb.call(this, json);
+ }
+ });
+ },
+
+ // ignore/unignore specific IDs. Useful for ignoring items that are
+ // being processed, or that shouldn't be synced.
+ // But note: not persisted to disk
+
+ ignoreID: function (id) {
+ this.unignoreID(id);
+ this._ignored.push(id);
+ },
+
+ unignoreID: function (id) {
+ let index = this._ignored.indexOf(id);
+ if (index != -1)
+ this._ignored.splice(index, 1);
+ },
+
+ addChangedID: function (id, when) {
+ if (!id) {
+ this._log.warn("Attempted to add undefined ID to tracker");
+ return false;
+ }
+
+ if (this.ignoreAll || this._ignored.includes(id)) {
+ return false;
+ }
+
+ // Default to the current time in seconds if no time is provided.
+ if (when == null) {
+ when = Math.floor(Date.now() / 1000);
+ }
+
+ // Add/update the entry if we have a newer time.
+ if ((this.changedIDs[id] || -Infinity) < when) {
+ this._log.trace("Adding changed ID: " + id + ", " + when);
+ this.changedIDs[id] = when;
+ this.saveChangedIDs(this.onSavedChangedIDs);
+ }
+
+ return true;
+ },
+
+ removeChangedID: function (id) {
+ if (!id) {
+ this._log.warn("Attempted to remove undefined ID to tracker");
+ return false;
+ }
+ if (this.ignoreAll || this._ignored.includes(id)) {
+ return false;
+ }
+ if (this.changedIDs[id] != null) {
+ this._log.trace("Removing changed ID " + id);
+ delete this.changedIDs[id];
+ this.saveChangedIDs();
+ }
+ return true;
+ },
+
+ clearChangedIDs: function () {
+ this._log.trace("Clearing changed ID list");
+ this.changedIDs = {};
+ this.saveChangedIDs();
+ },
+
+ _isTracking: false,
+
+ // Override these in your subclasses.
+ startTracking: function () {
+ },
+
+ stopTracking: function () {
+ },
+
+ engineIsEnabled: function () {
+ if (!this.engine) {
+ // Can't tell -- we must be running in a test!
+ return true;
+ }
+ return this.engine.enabled;
+ },
+
+ onEngineEnabledChanged: function (engineEnabled) {
+ if (engineEnabled == this._isTracking) {
+ return;
+ }
+
+ if (engineEnabled) {
+ this.startTracking();
+ this._isTracking = true;
+ } else {
+ this.stopTracking();
+ this._isTracking = false;
+ this.clearChangedIDs();
+ }
+ },
+
+ observe: function (subject, topic, data) {
+ switch (topic) {
+ case "weave:engine:start-tracking":
+ if (!this.engineIsEnabled()) {
+ return;
+ }
+ this._log.trace("Got start-tracking.");
+ if (!this._isTracking) {
+ this.startTracking();
+ this._isTracking = true;
+ }
+ return;
+ case "weave:engine:stop-tracking":
+ this._log.trace("Got stop-tracking.");
+ if (this._isTracking) {
+ this.stopTracking();
+ this._isTracking = false;
+ }
+ return;
+ case "nsPref:changed":
+ if (data == PREFS_BRANCH + "engine." + this.engine.prefName) {
+ this.onEngineEnabledChanged(this.engine.enabled);
+ }
+ return;
+ }
+ }
+};
+
+
+
+/**
+ * The Store serves as the interface between Sync and stored data.
+ *
+ * The name "store" is slightly a misnomer because it doesn't actually "store"
+ * anything. Instead, it serves as a gateway to something that actually does
+ * the "storing."
+ *
+ * The store is responsible for record management inside an engine. It tells
+ * Sync what items are available for Sync, converts items to and from Sync's
+ * record format, and applies records from Sync into changes on the underlying
+ * store.
+ *
+ * Store implementations require a number of functions to be implemented. These
+ * are all documented below.
+ *
+ * For stores that deal with many records or which have expensive store access
+ * routines, it is highly recommended to implement a custom applyIncomingBatch
+ * and/or applyIncoming function on top of the basic APIs.
+ */
+
+this.Store = function Store(name, engine) {
+ if (!engine) {
+ throw new Error("Store must be associated with an Engine instance.");
+ }
+
+ name = name || "Unnamed";
+ this.name = name.toLowerCase();
+ this.engine = engine;
+
+ this._log = Log.repository.getLogger("Sync.Store." + name);
+ let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
+ this._log.level = Log.Level[level];
+
+ XPCOMUtils.defineLazyGetter(this, "_timer", function() {
+ return Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ });
+}
+Store.prototype = {
+
+ _sleep: function _sleep(delay) {
+ let cb = Async.makeSyncCallback();
+ this._timer.initWithCallback(cb, delay, Ci.nsITimer.TYPE_ONE_SHOT);
+ Async.waitForSyncCallback(cb);
+ },
+
+ /**
+ * Apply multiple incoming records against the store.
+ *
+ * This is called with a set of incoming records to process. The function
+ * should look at each record, reconcile with the current local state, and
+ * make the local changes required to bring its state in alignment with the
+ * record.
+ *
+ * The default implementation simply iterates over all records and calls
+ * applyIncoming(). Store implementations may overwrite this function
+ * if desired.
+ *
+ * @param records Array of records to apply
+ * @return Array of record IDs which did not apply cleanly
+ */
+ applyIncomingBatch: function (records) {
+ let failed = [];
+ for (let record of records) {
+ try {
+ this.applyIncoming(record);
+ } catch (ex if (ex.code == Engine.prototype.eEngineAbortApplyIncoming)) {
+ // This kind of exception should have a 'cause' attribute, which is an
+ // originating exception.
+ // ex.cause will carry its stack with it when rethrown.
+ throw ex.cause;
+ } catch (ex) {
+ this._log.warn("Failed to apply incoming record " + record.id);
+ this._log.warn("Encountered exception: ", ex);
+ failed.push(record.id);
+ }
+ };
+ return failed;
+ },
+
+ /**
+ * Apply a single record against the store.
+ *
+ * This takes a single record and makes the local changes required so the
+ * local state matches what's in the record.
+ *
+ * The default implementation calls one of remove(), create(), or update()
+ * depending on the state obtained from the store itself. Store
+ * implementations may overwrite this function if desired.
+ *
+ * @param record
+ * Record to apply
+ */
+ applyIncoming: function (record) {
+ if (record.deleted)
+ this.remove(record);
+ else if (!this.itemExists(record.id))
+ this.create(record);
+ else
+ this.update(record);
+ },
+
+ // override these in derived objects
+
+ /**
+ * Create an item in the store from a record.
+ *
+ * This is called by the default implementation of applyIncoming(). If using
+ * applyIncomingBatch(), this won't be called unless your store calls it.
+ *
+ * @param record
+ * The store record to create an item from
+ */
+ create: function (record) {
+ throw "override create in a subclass";
+ },
+
+ /**
+ * Remove an item in the store from a record.
+ *
+ * This is called by the default implementation of applyIncoming(). If using
+ * applyIncomingBatch(), this won't be called unless your store calls it.
+ *
+ * @param record
+ * The store record to delete an item from
+ */
+ remove: function (record) {
+ throw "override remove in a subclass";
+ },
+
+ /**
+ * Update an item from a record.
+ *
+ * This is called by the default implementation of applyIncoming(). If using
+ * applyIncomingBatch(), this won't be called unless your store calls it.
+ *
+ * @param record
+ * The record to use to update an item from
+ */
+ update: function (record) {
+ throw "override update in a subclass";
+ },
+
+ /**
+ * Determine whether a record with the specified ID exists.
+ *
+ * Takes a string record ID and returns a booleans saying whether the record
+ * exists.
+ *
+ * @param id
+ * string record ID
+ * @return boolean indicating whether record exists locally
+ */
+ itemExists: function (id) {
+ throw "override itemExists in a subclass";
+ },
+
+ /**
+ * Create a record from the specified ID.
+ *
+ * If the ID is known, the record should be populated with metadata from
+ * the store. If the ID is not known, the record should be created with the
+ * delete field set to true.
+ *
+ * @param id
+ * string record ID
+ * @param collection
+ * Collection to add record to. This is typically passed into the
+ * constructor for the newly-created record.
+ * @return record type for this engine
+ */
+ createRecord: function (id, collection) {
+ throw "override createRecord in a subclass";
+ },
+
+ /**
+ * Change the ID of a record.
+ *
+ * @param oldID
+ * string old/current record ID
+ * @param newID
+ * string new record ID
+ */
+ changeItemID: function (oldID, newID) {
+ throw "override changeItemID in a subclass";
+ },
+
+ /**
+ * Obtain the set of all known record IDs.
+ *
+ * @return Object with ID strings as keys and values of true. The values
+ * are ignored.
+ */
+ getAllIDs: function () {
+ throw "override getAllIDs in a subclass";
+ },
+
+ /**
+ * Wipe all data in the store.
+ *
+ * This function is called during remote wipes or when replacing local data
+ * with remote data.
+ *
+ * This function should delete all local data that the store is managing. It
+ * can be thought of as clearing out all state and restoring the "new
+ * browser" state.
+ */
+ wipe: function () {
+ throw "override wipe in a subclass";
+ }
+};
+
+this.EngineManager = function EngineManager(service) {
+ this.service = service;
+
+ this._engines = {};
+
+ // This will be populated by Service on startup.
+ this._declined = new Set();
+ this._log = Log.repository.getLogger("Sync.EngineManager");
+ this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.engines", "Debug")];
+}
+EngineManager.prototype = {
+ get: function (name) {
+ // Return an array of engines if we have an array of names
+ if (Array.isArray(name)) {
+ let engines = [];
+ name.forEach(function(name) {
+ let engine = this.get(name);
+ if (engine) {
+ engines.push(engine);
+ }
+ }, this);
+ return engines;
+ }
+
+ let engine = this._engines[name];
+ if (!engine) {
+ this._log.debug("Could not get engine: " + name);
+ if (Object.keys) {
+ this._log.debug("Engines are: " + JSON.stringify(Object.keys(this._engines)));
+ }
+ }
+ return engine;
+ },
+
+ getAll: function () {
+ let engines = [];
+ for (let [, engine] of Object.entries(this._engines)) {
+ engines.push(engine);
+ }
+ return engines;
+ },
+
+ /**
+ * N.B., does not pay attention to the declined list.
+ */
+ getEnabled: function () {
+ return this.getAll()
+ .filter((engine) => engine.enabled)
+ .sort((a, b) => a.syncPriority - b.syncPriority);
+ },
+
+ get enabledEngineNames() {
+ return this.getEnabled().map(e => e.name);
+ },
+
+ persistDeclined: function () {
+ Svc.Prefs.set("declinedEngines", [...this._declined].join(","));
+ },
+
+ /**
+ * Returns an array.
+ */
+ getDeclined: function () {
+ return [...this._declined];
+ },
+
+ setDeclined: function (engines) {
+ this._declined = new Set(engines);
+ this.persistDeclined();
+ },
+
+ isDeclined: function (engineName) {
+ return this._declined.has(engineName);
+ },
+
+ /**
+ * Accepts a Set or an array.
+ */
+ decline: function (engines) {
+ for (let e of engines) {
+ this._declined.add(e);
+ }
+ this.persistDeclined();
+ },
+
+ undecline: function (engines) {
+ for (let e of engines) {
+ this._declined.delete(e);
+ }
+ this.persistDeclined();
+ },
+
+ /**
+ * Mark any non-enabled engines as declined.
+ *
+ * This is useful after initial customization during setup.
+ */
+ declineDisabled: function () {
+ for (let e of this.getAll()) {
+ if (!e.enabled) {
+ this._log.debug("Declining disabled engine " + e.name);
+ this._declined.add(e.name);
+ }
+ }
+ this.persistDeclined();
+ },
+
+ /**
+ * Register an Engine to the service. Alternatively, give an array of engine
+ * objects to register.
+ *
+ * @param engineObject
+ * Engine object used to get an instance of the engine
+ * @return The engine object if anything failed
+ */
+ register: function (engineObject) {
+ if (Array.isArray(engineObject)) {
+ return engineObject.map(this.register, this);
+ }
+
+ try {
+ let engine = new engineObject(this.service);
+ let name = engine.name;
+ if (name in this._engines) {
+ this._log.error("Engine '" + name + "' is already registered!");
+ } else {
+ this._engines[name] = engine;
+ }
+ } catch (ex) {
+ this._log.error("Engine init error: ", ex);
+
+ let mesg = ex.message ? ex.message : ex;
+ let name = engineObject || "";
+ name = name.prototype || "";
+ name = name.name || "";
+
+ let out = "Could not initialize engine '" + name + "': " + mesg;
+ this._log.error(out);
+
+ return engineObject;
+ }
+ },
+
+ unregister: function (val) {
+ let name = val;
+ if (val instanceof Engine) {
+ name = val.name;
+ }
+ delete this._engines[name];
+ },
+
+ clear: function () {
+ for (let name in this._engines) {
+ delete this._engines[name];
+ }
+ },
+};
+
+this.Engine = function Engine(name, service) {
+ if (!service) {
+ throw new Error("Engine must be associated with a Service instance.");
+ }
+
+ this.Name = name || "Unnamed";
+ this.name = name.toLowerCase();
+ this.service = service;
+
+ this._notify = Utils.notify("weave:engine:");
+ this._log = Log.repository.getLogger("Sync.Engine." + this.Name);
+ let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
+ this._log.level = Log.Level[level];
+
+ this._tracker; // initialize tracker to load previously changed IDs
+ this._log.debug("Engine initialized");
+}
+Engine.prototype = {
+ // _storeObj, and _trackerObj should to be overridden in subclasses
+ _storeObj: Store,
+ _trackerObj: Tracker,
+
+ // Local 'constant'.
+ // Signal to the engine that processing further records is pointless.
+ eEngineAbortApplyIncoming: "error.engine.abort.applyincoming",
+
+ get prefName() {
+ return this.name;
+ },
+
+ get enabled() {
+ // XXX: Disable non-functional add-ons syncing for the time being
+ // This check can go away when add-on syncing is addressed
+ if (this.prefName == "addons")
+ return false;
+
+ return Svc.Prefs.get("engine." + this.prefName, false);
+ },
+
+ set enabled(val) {
+ Svc.Prefs.set("engine." + this.prefName, !!val);
+ },
+
+ get score() {
+ return this._tracker.score;
+ },
+
+ get _store() {
+ let store = new this._storeObj(this.Name, this);
+ this.__defineGetter__("_store", () => store);
+ return store;
+ },
+
+ get _tracker() {
+ let tracker = new this._trackerObj(this.Name, this);
+ this.__defineGetter__("_tracker", () => tracker);
+ return tracker;
+ },
+
+ sync: function () {
+ if (!this.enabled) {
+ return;
+ }
+
+ if (!this._sync) {
+ throw "engine does not implement _sync method";
+ }
+
+ this._notify("sync", this.name, this._sync)();
+ },
+
+ /**
+ * Get rid of any local meta-data.
+ */
+ resetClient: function () {
+ if (!this._resetClient) {
+ throw "engine does not implement _resetClient method";
+ }
+
+ this._notify("reset-client", this.name, this._resetClient)();
+ },
+
+ _wipeClient: function () {
+ this.resetClient();
+ this._log.debug("Deleting all local data");
+ this._tracker.ignoreAll = true;
+ this._store.wipe();
+ this._tracker.ignoreAll = false;
+ this._tracker.clearChangedIDs();
+ },
+
+ wipeClient: function () {
+ this._notify("wipe-client", this.name, this._wipeClient)();
+ }
+};
+
+this.SyncEngine = function SyncEngine(name, service) {
+ Engine.call(this, name || "SyncEngine", service);
+
+ this.loadToFetch();
+ this.loadPreviousFailed();
+}
+
+// Enumeration to define approaches to handling bad records.
+// Attached to the constructor to allow use as a kind of static enumeration.
+SyncEngine.kRecoveryStrategy = {
+ ignore: "ignore",
+ retry: "retry",
+ error: "error"
+};
+
+SyncEngine.prototype = {
+ __proto__: Engine.prototype,
+ _recordObj: CryptoWrapper,
+ version: 1,
+
+ // Which sortindex to use when retrieving records for this engine.
+ _defaultSort: undefined,
+
+ // A relative priority to use when computing an order
+ // for engines to be synced. Higher-priority engines
+ // (lower numbers) are synced first.
+ // It is recommended that a unique value be used for each engine,
+ // in order to guarantee a stable sequence.
+ syncPriority: 0,
+
+ // How many records to pull in a single sync. This is primarily to avoid very
+ // long first syncs against profiles with many history records.
+ downloadLimit: null,
+
+ // How many records to pull at one time when specifying IDs. This is to avoid
+ // URI length limitations.
+ guidFetchBatchSize: DEFAULT_GUID_FETCH_BATCH_SIZE,
+ mobileGUIDFetchBatchSize: DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE,
+
+ // How many records to process in a single batch.
+ applyIncomingBatchSize: DEFAULT_STORE_BATCH_SIZE,
+
+ get storageURL() {
+ return this.service.storageURL;
+ },
+
+ get engineURL() {
+ return this.storageURL + this.name;
+ },
+
+ get cryptoKeysURL() {
+ return this.storageURL + "crypto/keys";
+ },
+
+ get metaURL() {
+ return this.storageURL + "meta/global";
+ },
+
+ get syncID() {
+ // Generate a random syncID if we don't have one
+ let syncID = Svc.Prefs.get(this.name + ".syncID", "");
+ return syncID == "" ? this.syncID = Utils.makeGUID() : syncID;
+ },
+ set syncID(value) {
+ Svc.Prefs.set(this.name + ".syncID", value);
+ },
+
+ /*
+ * lastSync is a timestamp in server time.
+ */
+ get lastSync() {
+ return parseFloat(Svc.Prefs.get(this.name + ".lastSync", "0"));
+ },
+ set lastSync(value) {
+ // Reset the pref in-case it's a number instead of a string
+ Svc.Prefs.reset(this.name + ".lastSync");
+ // Store the value as a string to keep floating point precision
+ Svc.Prefs.set(this.name + ".lastSync", value.toString());
+ },
+ resetLastSync: function () {
+ this._log.debug("Resetting " + this.name + " last sync time");
+ Svc.Prefs.reset(this.name + ".lastSync");
+ Svc.Prefs.set(this.name + ".lastSync", "0");
+ this.lastSyncLocal = 0;
+ },
+
+ get toFetch() {
+ return this._toFetch;
+ },
+ set toFetch(val) {
+ let cb = (error) => this._log.error("Failed to read JSON records to fetch: ", error);
+ // Coerce the array to a string for more efficient comparison.
+ if (val + "" == this._toFetch) {
+ return;
+ }
+ this._toFetch = val;
+ Utils.namedTimer(function () {
+ Utils.jsonSave("toFetch/" + this.name, this, val, cb);
+ }, 0, this, "_toFetchDelay");
+ },
+
+ loadToFetch: function () {
+ // Initialize to empty if there's no file.
+ this._toFetch = [];
+ Utils.jsonLoad("toFetch/" + this.name, this, function(toFetch) {
+ if (toFetch) {
+ this._toFetch = toFetch;
+ }
+ });
+ },
+
+ get previousFailed() {
+ return this._previousFailed;
+ },
+ set previousFailed(val) {
+ let cb = (error) => {
+ if (error) {
+ this._log.error("Failed to set previousFailed", error);
+ } else {
+ this._log.debug("Successfully wrote previousFailed.");
+ }
+ }
+ // Coerce the array to a string for more efficient comparison.
+ if (val + "" == this._previousFailed) {
+ return;
+ }
+ this._previousFailed = val;
+ Utils.namedTimer(function () {
+ Utils.jsonSave("failed/" + this.name, this, val, cb);
+ }, 0, this, "_previousFailedDelay");
+ },
+
+ loadPreviousFailed: function () {
+ // Initialize to empty if there's no file
+ this._previousFailed = [];
+ Utils.jsonLoad("failed/" + this.name, this, function(previousFailed) {
+ if (previousFailed) {
+ this._previousFailed = previousFailed;
+ }
+ });
+ },
+
+ /*
+ * lastSyncLocal is a timestamp in local time.
+ */
+ get lastSyncLocal() {
+ return parseInt(Svc.Prefs.get(this.name + ".lastSyncLocal", "0"), 10);
+ },
+ set lastSyncLocal(value) {
+ // Store as a string because pref can only store C longs as numbers.
+ Svc.Prefs.set(this.name + ".lastSyncLocal", value.toString());
+ },
+
+ /*
+ * Returns a mapping of IDs -> changed timestamp. Engine implementations
+ * can override this method to bypass the tracker for certain or all
+ * changed items.
+ */
+ getChangedIDs: function () {
+ return this._tracker.changedIDs;
+ },
+
+ // Create a new record using the store and add in crypto fields.
+ _createRecord: function (id) {
+ let record = this._store.createRecord(id, this.name);
+ record.id = id;
+ record.collection = this.name;
+ return record;
+ },
+
+ // Any setup that needs to happen at the beginning of each sync.
+ _syncStartup: function () {
+
+ // Determine if we need to wipe on outdated versions
+ let metaGlobal = this.service.recordManager.get(this.metaURL);
+ let engines = metaGlobal.payload.engines || {};
+ let engineData = engines[this.name] || {};
+
+ let needsWipe = false;
+
+ // Assume missing versions are 0 and wipe the server
+ if ((engineData.version || 0) < this.version) {
+ this._log.debug("Old engine data: " + [engineData.version, this.version]);
+
+ // Prepare to clear the server and upload everything
+ needsWipe = true;
+ this.syncID = "";
+
+ // Set the newer version and newly generated syncID
+ engineData.version = this.version;
+ engineData.syncID = this.syncID;
+
+ // Put the new data back into meta/global and mark for upload
+ engines[this.name] = engineData;
+ metaGlobal.payload.engines = engines;
+ metaGlobal.changed = true;
+ }
+ // Don't sync this engine if the server has newer data
+ else if (engineData.version > this.version) {
+ let error = new String("New data: " + [engineData.version, this.version]);
+ error.failureCode = VERSION_OUT_OF_DATE;
+ throw error;
+ }
+ // Changes to syncID mean we'll need to upload everything
+ else if (engineData.syncID != this.syncID) {
+ this._log.debug("Engine syncIDs: " + [engineData.syncID, this.syncID]);
+ this.syncID = engineData.syncID;
+ this._resetClient();
+ };
+
+ // Delete any existing data and reupload on bad version or missing meta.
+ // No crypto component here...? We could regenerate per-collection keys...
+ if (needsWipe) {
+ this.wipeServer();
+ }
+
+ // Save objects that need to be uploaded in this._modified. We also save
+ // the timestamp of this fetch in this.lastSyncLocal. As we successfully
+ // upload objects we remove them from this._modified. If an error occurs
+ // or any objects fail to upload, they will remain in this._modified. At
+ // the end of a sync, or after an error, we add all objects remaining in
+ // this._modified to the tracker.
+ this.lastSyncLocal = Date.now();
+ if (this.lastSync) {
+ this._modified = this.getChangedIDs();
+ } else {
+ // Mark all items to be uploaded, but treat them as changed from long ago
+ this._log.debug("First sync, uploading all items");
+ this._modified = {};
+ for (let id in this._store.getAllIDs()) {
+ this._modified[id] = 0;
+ }
+ }
+ // Clear the tracker now. If the sync fails we'll add the ones we failed
+ // to upload back.
+ this._tracker.clearChangedIDs();
+
+ this._log.info(Object.keys(this._modified).length +
+ " outgoing items pre-reconciliation");
+
+ // Keep track of what to delete at the end of sync
+ this._delete = {};
+ },
+
+ /**
+ * A tiny abstraction to make it easier to test incoming record
+ * application.
+ */
+ _itemSource: function () {
+ return new Collection(this.engineURL, this._recordObj, this.service);
+ },
+
+ /**
+ * Process incoming records.
+ * In the most awful and untestable way possible.
+ * This now accepts something that makes testing vaguely less impossible.
+ */
+ _processIncoming: function (newitems) {
+ this._log.trace("Downloading & applying server changes");
+
+ // Figure out how many total items to fetch this sync; do less on mobile.
+ let batchSize = this.downloadLimit || Infinity;
+ let isMobile = (Svc.Prefs.get("client.type") == "mobile");
+
+ if (!newitems) {
+ newitems = this._itemSource();
+ }
+
+ if (this._defaultSort) {
+ newitems.sort = this._defaultSort;
+ }
+
+ if (isMobile) {
+ batchSize = MOBILE_BATCH_SIZE;
+ }
+ newitems.newer = this.lastSync;
+ newitems.full = true;
+ newitems.limit = batchSize;
+
+ // applied => number of items that should be applied.
+ // failed => number of items that failed in this sync.
+ // newFailed => number of items that failed for the first time in this sync.
+ // reconciled => number of items that were reconciled.
+ let count = {applied: 0, failed: 0, newFailed: 0, reconciled: 0};
+ let handled = [];
+ let applyBatch = [];
+ let failed = [];
+ let failedInPreviousSync = this.previousFailed;
+ let fetchBatch = Utils.arrayUnion(this.toFetch, failedInPreviousSync);
+ // Reset previousFailed for each sync since previously failed items may not fail again.
+ this.previousFailed = [];
+
+ // Used (via exceptions) to allow the record handler/reconciliation/etc.
+ // methods to signal that they would like processing of incoming records to
+ // cease.
+ let aborting = undefined;
+
+ function doApplyBatch() {
+ this._tracker.ignoreAll = true;
+ try {
+ failed = failed.concat(this._store.applyIncomingBatch(applyBatch));
+ } catch (ex) {
+ // Catch any error that escapes from applyIncomingBatch. At present
+ // those will all be abort events.
+ this._log.warn("Got exception, aborting processIncoming. ", ex);
+ aborting = ex;
+ }
+ this._tracker.ignoreAll = false;
+ applyBatch = [];
+ }
+
+ function doApplyBatchAndPersistFailed() {
+ // Apply remaining batch.
+ if (applyBatch.length) {
+ doApplyBatch.call(this);
+ }
+ // Persist failed items so we refetch them.
+ if (failed.length) {
+ this.previousFailed = Utils.arrayUnion(failed, this.previousFailed);
+ count.failed += failed.length;
+ this._log.debug("Records that failed to apply: " + failed);
+ failed = [];
+ }
+ }
+
+ let key = this.service.collectionKeys.keyForCollection(this.name);
+
+ // Not binding this method to 'this' for performance reasons. It gets
+ // called for every incoming record.
+ let self = this;
+
+ newitems.recordHandler = function(item) {
+ if (aborting) {
+ return;
+ }
+
+ // Grab a later last modified if possible
+ if (self.lastModified == null || item.modified > self.lastModified)
+ self.lastModified = item.modified;
+
+ // Track the collection for the WBO.
+ item.collection = self.name;
+
+ // Remember which records were processed
+ handled.push(item.id);
+
+ try {
+ try {
+ item.decrypt(key);
+ } catch (ex if Utils.isHMACMismatch(ex)) {
+ let strategy = self.handleHMACMismatch(item, true);
+ if (strategy == SyncEngine.kRecoveryStrategy.retry) {
+ // You only get one retry.
+ try {
+ // Try decrypting again, typically because we've got new keys.
+ self._log.info("Trying decrypt again...");
+ key = self.service.collectionKeys.keyForCollection(self.name);
+ item.decrypt(key);
+ strategy = null;
+ } catch (ex if Utils.isHMACMismatch(ex)) {
+ strategy = self.handleHMACMismatch(item, false);
+ }
+ }
+
+ switch (strategy) {
+ case null:
+ // Retry succeeded! No further handling.
+ break;
+ case SyncEngine.kRecoveryStrategy.retry:
+ self._log.debug("Ignoring second retry suggestion.");
+ // Fall through to error case.
+ case SyncEngine.kRecoveryStrategy.error:
+ self._log.warn("Error decrypting record: ", ex);
+ failed.push(item.id);
+ return;
+ case SyncEngine.kRecoveryStrategy.ignore:
+ self._log.debug("Ignoring record " + item.id +
+ " with bad HMAC: already handled.");
+ return;
+ }
+ }
+ } catch (ex) {
+ self._log.warn("Error decrypting record: ", ex);
+ failed.push(item.id);
+ return;
+ }
+
+ let shouldApply;
+ try {
+ shouldApply = self._reconcile(item);
+ } catch (ex if (ex.code == Engine.prototype.eEngineAbortApplyIncoming)) {
+ self._log.warn("Reconciliation failed: aborting incoming processing.");
+ failed.push(item.id);
+ aborting = ex.cause;
+ } catch (ex) {
+ self._log.warn("Failed to reconcile incoming record " + item.id);
+ self._log.warn("Encountered exception: ", ex);
+ failed.push(item.id);
+ return;
+ }
+
+ if (shouldApply) {
+ count.applied++;
+ applyBatch.push(item);
+ } else {
+ count.reconciled++;
+ self._log.trace("Skipping reconciled incoming item " + item.id);
+ }
+
+ if (applyBatch.length == self.applyIncomingBatchSize) {
+ doApplyBatch.call(self);
+ }
+ self._store._sleep(0);
+ };
+
+ // Only bother getting data from the server if there's new things
+ if (this.lastModified == null || this.lastModified > this.lastSync) {
+ let resp = newitems.get();
+ doApplyBatchAndPersistFailed.call(this);
+ if (!resp.success) {
+ resp.failureCode = ENGINE_DOWNLOAD_FAIL;
+ throw resp;
+ }
+
+ if (aborting) {
+ throw aborting;
+ }
+ }
+
+ // Mobile: check if we got the maximum that we requested; get the rest if so.
+ if (handled.length == newitems.limit) {
+ let guidColl = new Collection(this.engineURL, null, this.service);
+
+ // Sort and limit so that on mobile we only get the last X records.
+ guidColl.limit = this.downloadLimit;
+ guidColl.newer = this.lastSync;
+
+ // index: Orders by the sortindex descending (highest weight first).
+ guidColl.sort = "index";
+
+ let guids = guidColl.get();
+ if (!guids.success)
+ throw guids;
+
+ // Figure out which guids weren't just fetched then remove any guids that
+ // were already waiting and prepend the new ones
+ let extra = Utils.arraySub(guids.obj, handled);
+ if (extra.length > 0) {
+ fetchBatch = Utils.arrayUnion(extra, fetchBatch);
+ this.toFetch = Utils.arrayUnion(extra, this.toFetch);
+ }
+ }
+
+ // Fast-foward the lastSync timestamp since we have stored the
+ // remaining items in toFetch.
+ if (this.lastSync < this.lastModified) {
+ this.lastSync = this.lastModified;
+ }
+
+ // Process any backlog of GUIDs.
+ // At this point we impose an upper limit on the number of items to fetch
+ // in a single request, even for desktop, to avoid hitting URI limits.
+ batchSize = isMobile ? this.mobileGUIDFetchBatchSize :
+ this.guidFetchBatchSize;
+
+ while (fetchBatch.length && !aborting) {
+ // Reuse the original query, but get rid of the restricting params
+ // and batch remaining records.
+ newitems.limit = 0;
+ newitems.newer = 0;
+ newitems.ids = fetchBatch.slice(0, batchSize);
+
+ // Reuse the existing record handler set earlier
+ let resp = newitems.get();
+ if (!resp.success) {
+ resp.failureCode = ENGINE_DOWNLOAD_FAIL;
+ throw resp;
+ }
+
+ // This batch was successfully applied. Not using
+ // doApplyBatchAndPersistFailed() here to avoid writing toFetch twice.
+ fetchBatch = fetchBatch.slice(batchSize);
+ this.toFetch = Utils.arraySub(this.toFetch, newitems.ids);
+ this.previousFailed = Utils.arrayUnion(this.previousFailed, failed);
+ if (failed.length) {
+ count.failed += failed.length;
+ this._log.debug("Records that failed to apply: " + failed);
+ }
+ failed = [];
+
+ if (aborting) {
+ throw aborting;
+ }
+
+ if (this.lastSync < this.lastModified) {
+ this.lastSync = this.lastModified;
+ }
+ }
+
+ // Apply remaining items.
+ doApplyBatchAndPersistFailed.call(this);
+
+ count.newFailed = Utils.arraySub(this.previousFailed, failedInPreviousSync).length;
+ count.succeeded = Math.max(0, count.applied - count.failed);
+ this._log.info(["Records:",
+ count.applied, "applied,",
+ count.succeeded, "successfully,",
+ count.failed, "failed to apply,",
+ count.newFailed, "newly failed to apply,",
+ count.reconciled, "reconciled."].join(" "));
+ Observers.notify("weave:engine:sync:applied", count, this.name);
+ },
+
+ /**
+ * Find a GUID of an item that is a duplicate of the incoming item but happens
+ * to have a different GUID
+ *
+ * @return GUID of the similar item; falsy otherwise
+ */
+ _findDupe: function (item) {
+ // By default, assume there's no dupe items for the engine
+ },
+
+ _deleteId: function (id) {
+ this._tracker.removeChangedID(id);
+
+ // Remember this id to delete at the end of sync
+ if (this._delete.ids == null)
+ this._delete.ids = [id];
+ else
+ this._delete.ids.push(id);
+ },
+
+ /**
+ * Reconcile incoming record with local state.
+ *
+ * This function essentially determines whether to apply an incoming record.
+ *
+ * @param item
+ * Record from server to be tested for application.
+ * @return boolean
+ * Truthy if incoming record should be applied. False if not.
+ */
+ _reconcile: function (item) {
+ if (this._log.level <= Log.Level.Trace) {
+ this._log.trace("Incoming: " + item);
+ }
+
+ // We start reconciling by collecting a bunch of state. We do this here
+ // because some state may change during the course of this function and we
+ // need to operate on the original values.
+ let existsLocally = this._store.itemExists(item.id);
+ let locallyModified = item.id in this._modified;
+
+ // TODO Handle clock drift better. Tracked in bug 721181.
+ let remoteAge = AsyncResource.serverTime - item.modified;
+ let localAge = locallyModified ?
+ (Date.now() / 1000 - this._modified[item.id]) : null;
+ let remoteIsNewer = remoteAge < localAge;
+
+ this._log.trace("Reconciling " + item.id + ". exists=" +
+ existsLocally + "; modified=" + locallyModified +
+ "; local age=" + localAge + "; incoming age=" +
+ remoteAge);
+
+ // We handle deletions first so subsequent logic doesn't have to check
+ // deleted flags.
+ if (item.deleted) {
+ // If the item doesn't exist locally, there is nothing for us to do. We
+ // can't check for duplicates because the incoming record has no data
+ // which can be used for duplicate detection.
+ if (!existsLocally) {
+ this._log.trace("Ignoring incoming item because it was deleted and " +
+ "the item does not exist locally.");
+ return false;
+ }
+
+ // We decide whether to process the deletion by comparing the record
+ // ages. If the item is not modified locally, the remote side wins and
+ // the deletion is processed. If it is modified locally, we take the
+ // newer record.
+ if (!locallyModified) {
+ this._log.trace("Applying incoming delete because the local item " +
+ "exists and isn't modified.");
+ return true;
+ }
+
+ // TODO As part of bug 720592, determine whether we should do more here.
+ // In the case where the local changes are newer, it is quite possible
+ // that the local client will restore data a remote client had tried to
+ // delete. There might be a good reason for that delete and it might be
+ // enexpected for this client to restore that data.
+ this._log.trace("Incoming record is deleted but we had local changes. " +
+ "Applying the youngest record.");
+ return remoteIsNewer;
+ }
+
+ // At this point the incoming record is not for a deletion and must have
+ // data. If the incoming record does not exist locally, we check for a local
+ // duplicate existing under a different ID. The default implementation of
+ // _findDupe() is empty, so engines have to opt in to this functionality.
+ //
+ // If we find a duplicate, we change the local ID to the incoming ID and we
+ // refresh the metadata collected above. See bug 710448 for the history
+ // of this logic.
+ if (!existsLocally) {
+ let dupeID = this._findDupe(item);
+ if (dupeID) {
+ this._log.trace("Local item " + dupeID + " is a duplicate for " +
+ "incoming item " + item.id);
+
+ // The local, duplicate ID is always deleted on the server.
+ this._deleteId(dupeID);
+
+ // The current API contract does not mandate that the ID returned by
+ // _findDupe() actually exists. Therefore, we have to perform this
+ // check.
+ existsLocally = this._store.itemExists(dupeID);
+
+ // We unconditionally change the item's ID in case the engine knows of
+ // an item but doesn't expose it through itemExists. If the API
+ // contract were stronger, this could be changed.
+ this._log.debug("Switching local ID to incoming: " + dupeID + " -> " +
+ item.id);
+ this._store.changeItemID(dupeID, item.id);
+
+ // If the local item was modified, we carry its metadata forward so
+ // appropriate reconciling can be performed.
+ if (dupeID in this._modified) {
+ locallyModified = true;
+ localAge = Date.now() / 1000 - this._modified[dupeID];
+ remoteIsNewer = remoteAge < localAge;
+
+ this._modified[item.id] = this._modified[dupeID];
+ delete this._modified[dupeID];
+ } else {
+ locallyModified = false;
+ localAge = null;
+ }
+
+ this._log.debug("Local item after duplication: age=" + localAge +
+ "; modified=" + locallyModified + "; exists=" +
+ existsLocally);
+ } else {
+ this._log.trace("No duplicate found for incoming item: " + item.id);
+ }
+ }
+
+ // At this point we've performed duplicate detection. But, nothing here
+ // should depend on duplicate detection as the above should have updated
+ // state seamlessly.
+
+ if (!existsLocally) {
+ // If the item doesn't exist locally and we have no local modifications
+ // to the item (implying that it was not deleted), always apply the remote
+ // item.
+ if (!locallyModified) {
+ this._log.trace("Applying incoming because local item does not exist " +
+ "and was not deleted.");
+ return true;
+ }
+
+ // If the item was modified locally but isn't present, it must have
+ // been deleted. If the incoming record is younger, we restore from
+ // that record.
+ if (remoteIsNewer) {
+ this._log.trace("Applying incoming because local item was deleted " +
+ "before the incoming item was changed.");
+ delete this._modified[item.id];
+ return true;
+ }
+
+ this._log.trace("Ignoring incoming item because the local item's " +
+ "deletion is newer.");
+ return false;
+ }
+
+ // If the remote and local records are the same, there is nothing to be
+ // done, so we don't do anything. In the ideal world, this logic wouldn't
+ // be here and the engine would take a record and apply it. The reason we
+ // want to defer this logic is because it would avoid a redundant and
+ // possibly expensive dip into the storage layer to query item state.
+ // This should get addressed in the async rewrite, so we ignore it for now.
+ let localRecord = this._createRecord(item.id);
+ let recordsEqual = Utils.deepEquals(item.cleartext,
+ localRecord.cleartext);
+
+ // If the records are the same, we don't need to do anything. This does
+ // potentially throw away a local modification time. But, if the records
+ // are the same, does it matter?
+ if (recordsEqual) {
+ this._log.trace("Ignoring incoming item because the local item is " +
+ "identical.");
+
+ delete this._modified[item.id];
+ return false;
+ }
+
+ // At this point the records are different.
+
+ // If we have no local modifications, always take the server record.
+ if (!locallyModified) {
+ this._log.trace("Applying incoming record because no local conflicts.");
+ return true;
+ }
+
+ // At this point, records are different and the local record is modified.
+ // We resolve conflicts by record age, where the newest one wins. This does
+ // result in data loss and should be handled by giving the engine an
+ // opportunity to merge the records. Bug 720592 tracks this feature.
+ this._log.warn("DATA LOSS: Both local and remote changes to record: " +
+ item.id);
+ return remoteIsNewer;
+ },
+
+ // Upload outgoing records.
+ _uploadOutgoing: function () {
+ this._log.trace("Uploading local changes to server.");
+
+ let modifiedIDs = Object.keys(this._modified);
+ if (modifiedIDs.length) {
+ this._log.trace("Preparing " + modifiedIDs.length +
+ " outgoing records");
+
+ // collection we'll upload
+ let up = new Collection(this.engineURL, null, this.service);
+ let count = 0;
+
+ // Upload what we've got so far in the collection
+ let doUpload = Utils.bind2(this, function(desc) {
+ this._log.info("Uploading " + desc + " of " + modifiedIDs.length +
+ " records");
+ let resp = up.post();
+ if (!resp.success) {
+ this._log.debug("Uploading records failed: " + resp);
+ resp.failureCode = ENGINE_UPLOAD_FAIL;
+ throw resp;
+ }
+
+ // Update server timestamp from the upload.
+ let modified = resp.headers["x-weave-timestamp"];
+ if (modified > this.lastSync)
+ this.lastSync = modified;
+
+ let failed_ids = Object.keys(resp.obj.failed);
+ if (failed_ids.length)
+ this._log.debug("Records that will be uploaded again because "
+ + "the server couldn't store them: "
+ + failed_ids.join(", "));
+
+ // Clear successfully uploaded objects.
+ for (let id of resp.obj.success) {
+ delete this._modified[id];
+ }
+
+ up.clearRecords();
+ });
+
+ for (let id of modifiedIDs) {
+ try {
+ let out = this._createRecord(id);
+ if (this._log.level <= Log.Level.Trace)
+ this._log.trace("Outgoing: " + out);
+
+ out.encrypt(this.service.collectionKeys.keyForCollection(this.name));
+ up.pushData(out);
+ }
+ catch(ex) {
+ this._log.warn("Error creating record: ", ex);
+ }
+
+ // Partial upload
+ if ((++count % MAX_UPLOAD_RECORDS) == 0)
+ doUpload((count - MAX_UPLOAD_RECORDS) + " - " + count + " out");
+
+ this._store._sleep(0);
+ }
+
+ // Final upload
+ if (count % MAX_UPLOAD_RECORDS > 0)
+ doUpload(count >= MAX_UPLOAD_RECORDS ? "last batch" : "all");
+ }
+ },
+
+ // Any cleanup necessary.
+ // Save the current snapshot so as to calculate changes at next sync
+ _syncFinish: function () {
+ this._log.trace("Finishing up sync");
+ this._tracker.resetScore();
+
+ let doDelete = Utils.bind2(this, function(key, val) {
+ let coll = new Collection(this.engineURL, this._recordObj, this.service);
+ coll[key] = val;
+ coll.delete();
+ });
+
+ for (let [key, val] of Object.entries(this._delete)) {
+ // Remove the key for future uses
+ delete this._delete[key];
+
+ // Send a simple delete for the property
+ if (key != "ids" || val.length <= 100)
+ doDelete(key, val);
+ else {
+ // For many ids, split into chunks of at most 100
+ while (val.length > 0) {
+ doDelete(key, val.slice(0, 100));
+ val = val.slice(100);
+ }
+ }
+ }
+ },
+
+ _syncCleanup: function () {
+ if (!this._modified) {
+ return;
+ }
+
+ // Mark failed WBOs as changed again so they are reuploaded next time.
+ for (let [id, when] in Iterator(this._modified)) {
+ this._tracker.addChangedID(id, when);
+ }
+ this._modified = {};
+ },
+
+ _sync: function () {
+ try {
+ this._syncStartup();
+ Observers.notify("weave:engine:sync:status", "process-incoming");
+ this._processIncoming();
+ Observers.notify("weave:engine:sync:status", "upload-outgoing");
+ this._uploadOutgoing();
+ this._syncFinish();
+ } finally {
+ this._syncCleanup();
+ }
+ },
+
+ canDecrypt: function () {
+ // Report failure even if there's nothing to decrypt
+ let canDecrypt = false;
+
+ // Fetch the most recently uploaded record and try to decrypt it
+ let test = new Collection(this.engineURL, this._recordObj, this.service);
+ test.limit = 1;
+ test.sort = "newest";
+ test.full = true;
+
+ let key = this.service.collectionKeys.keyForCollection(this.name);
+ test.recordHandler = function recordHandler(record) {
+ record.decrypt(key);
+ canDecrypt = true;
+ }.bind(this);
+
+ // Any failure fetching/decrypting will just result in false
+ try {
+ this._log.trace("Trying to decrypt a record from the server..");
+ test.get();
+ }
+ catch(ex) {
+ this._log.debug("Failed test decrypt: ", ex);
+ }
+
+ return canDecrypt;
+ },
+
+ _resetClient: function () {
+ this.resetLastSync();
+ this.previousFailed = [];
+ this.toFetch = [];
+ },
+
+ wipeServer: function () {
+ let response = this.service.resource(this.engineURL).delete();
+ if (response.status != 200 && response.status != 404) {
+ throw response;
+ }
+ this._resetClient();
+ },
+
+ removeClientData: function () {
+ // Implement this method in engines that store client specific data
+ // on the server.
+ },
+
+ /*
+ * Decide on (and partially effect) an error-handling strategy.
+ *
+ * Asks the Service to respond to an HMAC error, which might result in keys
+ * being downloaded. That call returns true if an action which might allow a
+ * retry to occur.
+ *
+ * If `mayRetry` is truthy, and the Service suggests a retry,
+ * handleHMACMismatch returns kRecoveryStrategy.retry. Otherwise, it returns
+ * kRecoveryStrategy.error.
+ *
+ * Subclasses of SyncEngine can override this method to allow for different
+ * behavior -- e.g., to delete and ignore erroneous entries.
+ *
+ * All return values will be part of the kRecoveryStrategy enumeration.
+ */
+ handleHMACMismatch: function (item, mayRetry) {
+ // By default we either try again, or bail out noisily.
+ return (this.service.handleHMACEvent() && mayRetry) ?
+ SyncEngine.kRecoveryStrategy.retry :
+ SyncEngine.kRecoveryStrategy.error;
+ }
+};
diff --git a/components/weave/src/sync/identity.js b/components/weave/src/sync/identity.js
new file mode 100644
index 000000000..795901f89
--- /dev/null
+++ b/components/weave/src/sync/identity.js
@@ -0,0 +1,603 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["IdentityManager"];
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/util.js");
+
+// Lazy import to prevent unnecessary load on startup.
+for (let symbol of ["BulkKeyBundle", "SyncKeyBundle"]) {
+ XPCOMUtils.defineLazyModuleGetter(this, symbol,
+ "resource://services-sync/keys.js",
+ symbol);
+}
+
+/**
+ * Manages identity and authentication for Sync.
+ *
+ * The following entities are managed:
+ *
+ * account - The main Sync/services account. This is typically an email
+ * address.
+ * username - A normalized version of your account. This is what's
+ * transmitted to the server.
+ * basic password - UTF-8 password used for authenticating when using HTTP
+ * basic authentication.
+ * sync key - The main encryption key used by Sync.
+ * sync key bundle - A representation of your sync key.
+ *
+ * When changes are made to entities that are stored in the password manager
+ * (basic password, sync key), those changes are merely staged. To commit them
+ * to the password manager, you'll need to call persistCredentials().
+ *
+ * This type also manages authenticating Sync's network requests. Sync's
+ * network code calls into getRESTRequestAuthenticator and
+ * getResourceAuthenticator (depending on the network layer being used). Each
+ * returns a function which can be used to add authentication information to an
+ * outgoing request.
+ *
+ * In theory, this type supports arbitrary identity and authentication
+ * mechanisms. You can add support for them by monkeypatching the global
+ * instance of this type. Specifically, you'll need to redefine the
+ * aforementioned network code functions to do whatever your authentication
+ * mechanism needs them to do. In addition, you may wish to install custom
+ * functions to support your API. Although, that is certainly not required.
+ * If you do monkeypatch, please be advised that Sync expects the core
+ * attributes to have values. You will need to carry at least account and
+ * username forward. If you do not wish to support one of the built-in
+ * authentication mechanisms, you'll probably want to redefine currentAuthState
+ * and any other function that involves the built-in functionality.
+ */
+this.IdentityManager = function IdentityManager() {
+ this._log = Log.repository.getLogger("Sync.Identity");
+ this._log.Level = Log.Level[Svc.Prefs.get("log.logger.identity")];
+
+ this._basicPassword = null;
+ this._basicPasswordAllowLookup = true;
+ this._basicPasswordUpdated = false;
+ this._syncKey = null;
+ this._syncKeyAllowLookup = true;
+ this._syncKeySet = false;
+ this._syncKeyBundle = null;
+}
+IdentityManager.prototype = {
+ _log: null,
+
+ _basicPassword: null,
+ _basicPasswordAllowLookup: true,
+ _basicPasswordUpdated: false,
+
+ _syncKey: null,
+ _syncKeyAllowLookup: true,
+ _syncKeySet: false,
+
+ _syncKeyBundle: null,
+
+ /**
+ * Initialize the identity provider. Returns a promise that is resolved
+ * when initialization is complete and the provider can be queried for
+ * its state
+ */
+ initialize: function() {
+ // Nothing to do for this identity provider.
+ return Promise.resolve();
+ },
+
+ finalize: function() {
+ // Nothing to do for this identity provider.
+ return Promise.resolve();
+ },
+
+ /**
+ * Called whenever Service.logout() is called.
+ */
+ logout: function() {
+ // nothing to do for this identity provider.
+ },
+
+ /**
+ * Ensure the user is logged in. Returns a promise that resolves when
+ * the user is logged in, or is rejected if the login attempt has failed.
+ */
+ ensureLoggedIn: function() {
+ // nothing to do for this identity provider
+ return Promise.resolve();
+ },
+
+ /**
+ * Indicates if the identity manager is still initializing
+ */
+ get readyToAuthenticate() {
+ // We initialize in a fully sync manner, so we are always finished.
+ return true;
+ },
+
+ get account() {
+ return Svc.Prefs.get("account", this.username);
+ },
+
+ /**
+ * Sets the active account name.
+ *
+ * This should almost always be called in favor of setting username, as
+ * username is derived from account.
+ *
+ * Changing the account name has the side-effect of wiping out stored
+ * credentials. Keep in mind that persistCredentials() will need to be called
+ * to flush the changes to disk.
+ *
+ * Set this value to null to clear out identity information.
+ */
+ set account(value) {
+ if (value) {
+ value = value.toLowerCase();
+ Svc.Prefs.set("account", value);
+ } else {
+ Svc.Prefs.reset("account");
+ }
+
+ this.username = this.usernameFromAccount(value);
+ },
+
+ get username() {
+ return Svc.Prefs.get("username", null);
+ },
+
+ /**
+ * Set the username value.
+ *
+ * Changing the username has the side-effect of wiping credentials.
+ */
+ set username(value) {
+ if (value) {
+ value = value.toLowerCase();
+
+ if (value == this.username) {
+ return;
+ }
+
+ Svc.Prefs.set("username", value);
+ } else {
+ Svc.Prefs.reset("username");
+ }
+
+ // If we change the username, we interpret this as a major change event
+ // and wipe out the credentials.
+ this._log.info("Username changed. Removing stored credentials.");
+ this.resetCredentials();
+ },
+
+ /**
+ * Resets/Drops all credentials we hold for the current user.
+ */
+ resetCredentials: function() {
+ this.basicPassword = null;
+ this.resetSyncKey();
+ },
+
+ /**
+ * Resets/Drops the sync key we hold for the current user.
+ */
+ resetSyncKey: function() {
+ this.syncKey = null;
+ // syncKeyBundle cleared as a result of setting syncKey.
+ },
+
+ /**
+ * Obtains the HTTP Basic auth password.
+ *
+ * Returns a string if set or null if it is not set.
+ */
+ get basicPassword() {
+ if (this._basicPasswordAllowLookup) {
+ // We need a username to find the credentials.
+ let username = this.username;
+ if (!username) {
+ return null;
+ }
+
+ for (let login of this._getLogins(PWDMGR_PASSWORD_REALM)) {
+ if (login.username.toLowerCase() == username) {
+ // It should already be UTF-8 encoded, but we don't take any chances.
+ this._basicPassword = Utils.encodeUTF8(login.password);
+ }
+ }
+
+ this._basicPasswordAllowLookup = false;
+ }
+
+ return this._basicPassword;
+ },
+
+ /**
+ * Set the HTTP basic password to use.
+ *
+ * Changes will not persist unless persistSyncCredentials() is called.
+ */
+ set basicPassword(value) {
+ // Wiping out value.
+ if (!value) {
+ this._log.info("Basic password has no value. Removing.");
+ this._basicPassword = null;
+ this._basicPasswordUpdated = true;
+ this._basicPasswordAllowLookup = false;
+ return;
+ }
+
+ let username = this.username;
+ if (!username) {
+ throw new Error("basicPassword cannot be set before username.");
+ }
+
+ this._log.info("Basic password being updated.");
+ this._basicPassword = Utils.encodeUTF8(value);
+ this._basicPasswordUpdated = true;
+ },
+
+ /**
+ * Obtain the Sync Key.
+ *
+ * This returns a 26 character "friendly" Base32 encoded string on success or
+ * null if no Sync Key could be found.
+ *
+ * If the Sync Key hasn't been set in this session, this will look in the
+ * password manager for the sync key.
+ */
+ get syncKey() {
+ if (this._syncKeyAllowLookup) {
+ let username = this.username;
+ if (!username) {
+ return null;
+ }
+
+ for (let login of this._getLogins(PWDMGR_PASSPHRASE_REALM)) {
+ if (login.username.toLowerCase() == username) {
+ this._syncKey = login.password;
+ }
+ }
+
+ this._syncKeyAllowLookup = false;
+ }
+
+ return this._syncKey;
+ },
+
+ /**
+ * Set the active Sync Key.
+ *
+ * If being set to null, the Sync Key and its derived SyncKeyBundle are
+ * removed. However, the Sync Key won't be deleted from the password manager
+ * until persistSyncCredentials() is called.
+ *
+ * If a value is provided, it should be a 26 or 32 character "friendly"
+ * Base32 string for which Utils.isPassphrase() returns true.
+ *
+ * A side-effect of setting the Sync Key is that a SyncKeyBundle is
+ * generated. For historical reasons, this will silently error out if the
+ * value is not a proper Sync Key (!Utils.isPassphrase()). This should be
+ * fixed in the future (once service.js is more sane) to throw if the passed
+ * value is not valid.
+ */
+ set syncKey(value) {
+ if (!value) {
+ this._log.info("Sync Key has no value. Deleting.");
+ this._syncKey = null;
+ this._syncKeyBundle = null;
+ this._syncKeyUpdated = true;
+ return;
+ }
+
+ if (!this.username) {
+ throw new Error("syncKey cannot be set before username.");
+ }
+
+ this._log.info("Sync Key being updated.");
+ this._syncKey = value;
+
+ // Clear any cached Sync Key Bundle and regenerate it.
+ this._syncKeyBundle = null;
+ let bundle = this.syncKeyBundle;
+
+ this._syncKeyUpdated = true;
+ },
+
+ /**
+ * Obtain the active SyncKeyBundle.
+ *
+ * This returns a SyncKeyBundle representing a key pair derived from the
+ * Sync Key on success. If no Sync Key is present or if the Sync Key is not
+ * valid, this returns null.
+ *
+ * The SyncKeyBundle should be treated as immutable.
+ */
+ get syncKeyBundle() {
+ // We can't obtain a bundle without a username set.
+ if (!this.username) {
+ this._log.warn("Attempted to obtain Sync Key Bundle with no username set!");
+ return null;
+ }
+
+ if (!this.syncKey) {
+ this._log.warn("Attempted to obtain Sync Key Bundle with no Sync Key " +
+ "set!");
+ return null;
+ }
+
+ if (!this._syncKeyBundle) {
+ try {
+ this._syncKeyBundle = new SyncKeyBundle(this.username, this.syncKey);
+ } catch (ex) {
+ this._log.warn("Failed to create sync key bundle", ex);
+ return null;
+ }
+ }
+
+ return this._syncKeyBundle;
+ },
+
+ /**
+ * The current state of the auth credentials.
+ *
+ * This essentially validates that enough credentials are available to use
+ * Sync.
+ */
+ get currentAuthState() {
+ if (!this.username) {
+ return LOGIN_FAILED_NO_USERNAME;
+ }
+
+ if (Utils.mpLocked()) {
+ return STATUS_OK;
+ }
+
+ if (!this.basicPassword) {
+ return LOGIN_FAILED_NO_PASSWORD;
+ }
+
+ if (!this.syncKey) {
+ return LOGIN_FAILED_NO_PASSPHRASE;
+ }
+
+ // If we have a Sync Key but no bundle, bundle creation failed, which
+ // implies a bad Sync Key.
+ if (!this.syncKeyBundle) {
+ return LOGIN_FAILED_INVALID_PASSPHRASE;
+ }
+
+ return STATUS_OK;
+ },
+
+ /**
+ * Verify the current auth state, unlocking the master-password if necessary.
+ *
+ * Returns a promise that resolves with the current auth state after
+ * attempting to unlock.
+ */
+ unlockAndVerifyAuthState: function() {
+ // Try to fetch the passphrase - this will prompt for MP unlock as a
+ // side-effect...
+ try {
+ this.syncKey;
+ } catch (ex) {
+ this._log.debug("Fetching passphrase threw " + ex +
+ "; assuming master password locked.");
+ return Promise.resolve(MASTER_PASSWORD_LOCKED);
+ }
+ return Promise.resolve(STATUS_OK);
+ },
+
+ /**
+ * Persist credentials to password store.
+ *
+ * When credentials are updated, they are changed in memory only. This will
+ * need to be called to save them to the underlying password store.
+ *
+ * If the password store is locked (e.g. if the master password hasn't been
+ * entered), this could throw an exception.
+ */
+ persistCredentials: function persistCredentials(force) {
+ if (this._basicPasswordUpdated || force) {
+ if (this._basicPassword) {
+ this._setLogin(PWDMGR_PASSWORD_REALM, this.username,
+ this._basicPassword);
+ } else {
+ for (let login of this._getLogins(PWDMGR_PASSWORD_REALM)) {
+ Services.logins.removeLogin(login);
+ }
+ }
+
+ this._basicPasswordUpdated = false;
+ }
+
+ if (this._syncKeyUpdated || force) {
+ if (this._syncKey) {
+ this._setLogin(PWDMGR_PASSPHRASE_REALM, this.username, this._syncKey);
+ } else {
+ for (let login of this._getLogins(PWDMGR_PASSPHRASE_REALM)) {
+ Services.logins.removeLogin(login);
+ }
+ }
+
+ this._syncKeyUpdated = false;
+ }
+
+ },
+
+ /**
+ * Deletes the Sync Key from the system.
+ */
+ deleteSyncKey: function deleteSyncKey() {
+ this.syncKey = null;
+ this.persistCredentials();
+ },
+
+ hasBasicCredentials: function hasBasicCredentials() {
+ // Because JavaScript.
+ return this.username && this.basicPassword && true;
+ },
+
+ /**
+ * Pre-fetches any information that might help with migration away from this
+ * identity. Called after every sync and is really just an optimization that
+ * allows us to avoid a network request for when we actually need the
+ * migration info.
+ */
+ prefetchMigrationSentinel: function(service) {
+ // Try and fetch the migration sentinel - it will end up in the recordManager
+ // cache.
+ try {
+ service.recordManager.get(service.storageURL + "meta/fxa_credentials");
+ } catch (ex) {
+ this._log.warn("Failed to pre-fetch the migration sentinel", ex);
+ }
+ },
+
+ /**
+ * Obtains the array of basic logins from nsiPasswordManager.
+ */
+ _getLogins: function _getLogins(realm) {
+ return Services.logins.findLogins({}, PWDMGR_HOST, null, realm);
+ },
+
+ /**
+ * Set a login in the password manager.
+ *
+ * This has the side-effect of deleting any other logins for the specified
+ * realm.
+ */
+ _setLogin: function _setLogin(realm, username, password) {
+ let exists = false;
+ for (let login of this._getLogins(realm)) {
+ if (login.username == username && login.password == password) {
+ exists = true;
+ } else {
+ this._log.debug("Pruning old login for " + username + " from " + realm);
+ Services.logins.removeLogin(login);
+ }
+ }
+
+ if (exists) {
+ return;
+ }
+
+ this._log.debug("Updating saved password for " + username + " in " +
+ realm);
+
+ let loginInfo = new Components.Constructor(
+ "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
+ let login = new loginInfo(PWDMGR_HOST, null, realm, username,
+ password, "", "");
+ Services.logins.addLogin(login);
+ },
+
+ /**
+ * Return credentials hosts for this identity only.
+ */
+ _getSyncCredentialsHosts: function() {
+ return Utils.getSyncCredentialsHostsLegacy();
+ },
+
+ /**
+ * Deletes Sync credentials from the password manager.
+ */
+ deleteSyncCredentials: function deleteSyncCredentials() {
+ for (let host of this._getSyncCredentialsHosts()) {
+ let logins = Services.logins.findLogins({}, host, "", "");
+ for (let login of logins) {
+ Services.logins.removeLogin(login);
+ }
+ }
+
+ // Wait until after store is updated in case it fails.
+ this._basicPassword = null;
+ this._basicPasswordAllowLookup = true;
+ this._basicPasswordUpdated = false;
+
+ this._syncKey = null;
+ // this._syncKeyBundle is nullified as part of _syncKey setter.
+ this._syncKeyAllowLookup = true;
+ this._syncKeyUpdated = false;
+ },
+
+ usernameFromAccount: function usernameFromAccount(value) {
+ // If we encounter characters not allowed by the API (as found for
+ // instance in an email address), hash the value.
+ if (value && value.match(/[^A-Z0-9._-]/i)) {
+ return Utils.sha1Base32(value.toLowerCase()).toLowerCase();
+ }
+
+ return value ? value.toLowerCase() : value;
+ },
+
+ /**
+ * Obtain a function to be used for adding auth to Resource HTTP requests.
+ */
+ getResourceAuthenticator: function getResourceAuthenticator() {
+ if (this.hasBasicCredentials()) {
+ return this._onResourceRequestBasic.bind(this);
+ }
+
+ return null;
+ },
+
+ /**
+ * Helper method to return an authenticator for basic Resource requests.
+ */
+ getBasicResourceAuthenticator:
+ function getBasicResourceAuthenticator(username, password) {
+
+ return function basicAuthenticator(resource) {
+ let value = "Basic " + btoa(username + ":" + password);
+ return {headers: {authorization: value}};
+ };
+ },
+
+ _onResourceRequestBasic: function _onResourceRequestBasic(resource) {
+ let value = "Basic " + btoa(this.username + ":" + this.basicPassword);
+ return {headers: {authorization: value}};
+ },
+
+ _onResourceRequestMAC: function _onResourceRequestMAC(resource, method) {
+ // TODO Get identifier and key from somewhere.
+ let identifier;
+ let key;
+ let result = Utils.computeHTTPMACSHA1(identifier, key, method, resource.uri);
+
+ return {headers: {authorization: result.header}};
+ },
+
+ /**
+ * Obtain a function to be used for adding auth to RESTRequest instances.
+ */
+ getRESTRequestAuthenticator: function getRESTRequestAuthenticator() {
+ if (this.hasBasicCredentials()) {
+ return this.onRESTRequestBasic.bind(this);
+ }
+
+ return null;
+ },
+
+ onRESTRequestBasic: function onRESTRequestBasic(request) {
+ let up = this.username + ":" + this.basicPassword;
+ request.setHeader("authorization", "Basic " + btoa(up));
+ },
+
+ createClusterManager: function(service) {
+ Cu.import("resource://services-sync/stages/cluster.js");
+ return new ClusterManager(service);
+ },
+
+ offerSyncOptions: function () {
+ // Do nothing for Sync 1.1.
+ return {accepted: true};
+ },
+};
diff --git a/components/weave/src/sync/jpakeclient.js b/components/weave/src/sync/jpakeclient.js
new file mode 100644
index 000000000..625dc91b6
--- /dev/null
+++ b/components/weave/src/sync/jpakeclient.js
@@ -0,0 +1,773 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["JPAKEClient", "SendCredentialsController"];
+
+var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/rest.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/util.js");
+
+const REQUEST_TIMEOUT = 60; // 1 minute
+const KEYEXCHANGE_VERSION = 3;
+
+const JPAKE_SIGNERID_SENDER = "sender";
+const JPAKE_SIGNERID_RECEIVER = "receiver";
+const JPAKE_LENGTH_SECRET = 8;
+const JPAKE_LENGTH_CLIENTID = 256;
+const JPAKE_VERIFY_VALUE = "0123456789ABCDEF";
+
+
+/**
+ * Client to exchange encrypted data using the J-PAKE algorithm.
+ * The exchange between two clients of this type looks like this:
+ *
+ *
+ * Mobile Server Desktop
+ * ===================================================================
+ * |
+ * retrieve channel <---------------|
+ * generate random secret |
+ * show PIN = secret + channel | ask user for PIN
+ * upload Mobile's message 1 ------>|
+ * |----> retrieve Mobile's message 1
+ * |<----- upload Desktop's message 1
+ * retrieve Desktop's message 1 <---|
+ * upload Mobile's message 2 ------>|
+ * |----> retrieve Mobile's message 2
+ * | compute key
+ * |<----- upload Desktop's message 2
+ * retrieve Desktop's message 2 <---|
+ * compute key |
+ * encrypt known value ------------>|
+ * |-------> retrieve encrypted value
+ * | verify against local known value
+ *
+ * At this point Desktop knows whether the PIN was entered correctly.
+ * If it wasn't, Desktop deletes the session. If it was, the account
+ * setup can proceed. If Desktop doesn't yet have an account set up,
+ * it will keep the channel open and let the user connect to or
+ * create an account.
+ *
+ * | encrypt credentials
+ * |<------------- upload credentials
+ * retrieve credentials <-----------|
+ * verify HMAC |
+ * decrypt credentials |
+ * delete session ----------------->|
+ * start syncing |
+ *
+ *
+ * Create a client object like so:
+ *
+ * let client = new JPAKEClient(controller);
+ *
+ * The 'controller' object must implement the following methods:
+ *
+ * displayPIN(pin) -- Called when a PIN has been generated and is ready to
+ * be displayed to the user. Only called on the client where the pairing
+ * was initiated with 'receiveNoPIN()'.
+ *
+ * onPairingStart() -- Called when the pairing has started and messages are
+ * being sent back and forth over the channel. Only called on the client
+ * where the pairing was initiated with 'receiveNoPIN()'.
+ *
+ * onPaired() -- Called when the device pairing has been established and
+ * we're ready to send the credentials over. To do that, the controller
+ * must call 'sendAndComplete()' while the channel is active.
+ *
+ * onComplete(data) -- Called after transfer has been completed. On
+ * the sending side this is called with no parameter and as soon as the
+ * data has been uploaded. This does not mean the receiving side has
+ * actually retrieved them yet.
+ *
+ * onAbort(error) -- Called whenever an error is encountered. All errors lead
+ * to an abort and the process has to be started again on both sides.
+ *
+ * To start the data transfer on the receiving side, call
+ *
+ * client.receiveNoPIN();
+ *
+ * This will allocate a new channel on the server, generate a PIN, have it
+ * displayed and then do the transfer once the protocol has been completed
+ * with the sending side.
+ *
+ * To initiate the transfer from the sending side, call
+ *
+ * client.pairWithPIN(pin, true);
+ *
+ * Once the pairing has been established, the controller's 'onPaired()' method
+ * will be called. To then transmit the data, call
+ *
+ * client.sendAndComplete(data);
+ *
+ * To abort the process, call
+ *
+ * client.abort();
+ *
+ * Note that after completion or abort, the 'client' instance may not be reused.
+ * You will have to create a new one in case you'd like to restart the process.
+ */
+this.JPAKEClient = function JPAKEClient(controller) {
+ this.controller = controller;
+
+ this._log = Log.repository.getLogger("Sync.JPAKEClient");
+ this._log.level = Log.Level[Svc.Prefs.get(
+ "log.logger.service.jpakeclient", "Debug")];
+
+ this._serverURL = Svc.Prefs.get("jpake.serverURL");
+ this._pollInterval = Svc.Prefs.get("jpake.pollInterval");
+ this._maxTries = Svc.Prefs.get("jpake.maxTries");
+ if (this._serverURL.slice(-1) != "/") {
+ this._serverURL += "/";
+ }
+
+ this._jpake = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
+ .createInstance(Ci.nsISyncJPAKE);
+
+ this._setClientID();
+}
+JPAKEClient.prototype = {
+
+ _chain: Async.chain,
+
+ /*
+ * Public API
+ */
+
+ /**
+ * Initiate pairing and receive data without providing a PIN. The PIN will
+ * be generated and passed on to the controller to be displayed to the user.
+ *
+ * This is typically called on mobile devices where typing is tedious.
+ */
+ receiveNoPIN: function receiveNoPIN() {
+ this._my_signerid = JPAKE_SIGNERID_RECEIVER;
+ this._their_signerid = JPAKE_SIGNERID_SENDER;
+
+ this._secret = this._createSecret();
+
+ // Allow a large number of tries first while we wait for the PIN
+ // to be entered on the other device.
+ this._maxTries = Svc.Prefs.get("jpake.firstMsgMaxTries");
+ this._chain(this._getChannel,
+ this._computeStepOne,
+ this._putStep,
+ this._getStep,
+ function(callback) {
+ // We fetched the first response from the other client.
+ // Notify controller of the pairing starting.
+ Utils.nextTick(this.controller.onPairingStart,
+ this.controller);
+
+ // Now we can switch back to the smaller timeout.
+ this._maxTries = Svc.Prefs.get("jpake.maxTries");
+ callback();
+ },
+ this._computeStepTwo,
+ this._putStep,
+ this._getStep,
+ this._computeFinal,
+ this._computeKeyVerification,
+ this._putStep,
+ function(callback) {
+ // Allow longer time-out for the last message.
+ this._maxTries = Svc.Prefs.get("jpake.lastMsgMaxTries");
+ callback();
+ },
+ this._getStep,
+ this._decryptData,
+ this._complete)();
+ },
+
+ /**
+ * Initiate pairing based on the PIN entered by the user.
+ *
+ * This is typically called on desktop devices where typing is easier than
+ * on mobile.
+ *
+ * @param pin
+ * 12 character string (in human-friendly base32) containing the PIN
+ * entered by the user.
+ * @param expectDelay
+ * Flag that indicates that a significant delay between the pairing
+ * and the sending should be expected. v2 and earlier of the protocol
+ * did not allow for this and the pairing to a v2 or earlier client
+ * will be aborted if this flag is 'true'.
+ */
+ pairWithPIN: function pairWithPIN(pin, expectDelay) {
+ this._my_signerid = JPAKE_SIGNERID_SENDER;
+ this._their_signerid = JPAKE_SIGNERID_RECEIVER;
+
+ this._channel = pin.slice(JPAKE_LENGTH_SECRET);
+ this._channelURL = this._serverURL + this._channel;
+ this._secret = pin.slice(0, JPAKE_LENGTH_SECRET);
+
+ this._chain(this._computeStepOne,
+ this._getStep,
+ function (callback) {
+ // Ensure that the other client can deal with a delay for
+ // the last message if that's requested by the caller.
+ if (!expectDelay) {
+ return callback();
+ }
+ if (!this._incoming.version || this._incoming.version < 3) {
+ return this.abort(JPAKE_ERROR_DELAYUNSUPPORTED);
+ }
+ return callback();
+ },
+ this._putStep,
+ this._computeStepTwo,
+ this._getStep,
+ this._putStep,
+ this._computeFinal,
+ this._getStep,
+ this._verifyPairing)();
+ },
+
+ /**
+ * Send data after a successful pairing.
+ *
+ * @param obj
+ * Object containing the data to send. It will be serialized as JSON.
+ */
+ sendAndComplete: function sendAndComplete(obj) {
+ if (!this._paired || this._finished) {
+ this._log.error("Can't send data, no active pairing!");
+ throw "No active pairing!";
+ }
+ this._data = JSON.stringify(obj);
+ this._chain(this._encryptData,
+ this._putStep,
+ this._complete)();
+ },
+
+ /**
+ * Abort the current pairing. The channel on the server will be deleted
+ * if the abort wasn't due to a network or server error. The controller's
+ * 'onAbort()' method is notified in all cases.
+ *
+ * @param error [optional]
+ * Error constant indicating the reason for the abort. Defaults to
+ * user abort.
+ */
+ abort: function abort(error) {
+ this._log.debug("Aborting...");
+ this._finished = true;
+ let self = this;
+
+ // Default to "user aborted".
+ if (!error) {
+ error = JPAKE_ERROR_USERABORT;
+ }
+
+ if (error == JPAKE_ERROR_CHANNEL ||
+ error == JPAKE_ERROR_NETWORK ||
+ error == JPAKE_ERROR_NODATA) {
+ Utils.nextTick(function() { this.controller.onAbort(error); }, this);
+ } else {
+ this._reportFailure(error, function() { self.controller.onAbort(error); });
+ }
+ },
+
+ /*
+ * Utilities
+ */
+
+ _setClientID: function _setClientID() {
+ let rng = Cc["@mozilla.org/security/random-generator;1"]
+ .createInstance(Ci.nsIRandomGenerator);
+ let bytes = rng.generateRandomBytes(JPAKE_LENGTH_CLIENTID / 2);
+ this._clientID = bytes.map(byte => ("0" + byte.toString(16)).slice(-2)).join("");
+ },
+
+ _createSecret: function _createSecret() {
+ // 0-9a-z without 1,l,o,0
+ const key = "23456789abcdefghijkmnpqrstuvwxyz";
+ let rng = Cc["@mozilla.org/security/random-generator;1"]
+ .createInstance(Ci.nsIRandomGenerator);
+ let bytes = rng.generateRandomBytes(JPAKE_LENGTH_SECRET);
+ return bytes.map(byte => key[Math.floor(byte * key.length / 256)]).join("");
+ },
+
+ _newRequest: function _newRequest(uri) {
+ let request = new RESTRequest(uri);
+ request.setHeader("X-KeyExchange-Id", this._clientID);
+ request.timeout = REQUEST_TIMEOUT;
+ return request;
+ },
+
+ /*
+ * Steps of J-PAKE procedure
+ */
+
+ _getChannel: function _getChannel(callback) {
+ this._log.trace("Requesting channel.");
+ let request = this._newRequest(this._serverURL + "new_channel");
+ request.get(Utils.bind2(this, function handleChannel(error) {
+ if (this._finished) {
+ return;
+ }
+
+ if (error) {
+ this._log.error("Error acquiring channel ID. " + error);
+ this.abort(JPAKE_ERROR_CHANNEL);
+ return;
+ }
+ if (request.response.status != 200) {
+ this._log.error("Error acquiring channel ID. Server responded with HTTP "
+ + request.response.status);
+ this.abort(JPAKE_ERROR_CHANNEL);
+ return;
+ }
+
+ try {
+ this._channel = JSON.parse(request.response.body);
+ } catch (ex) {
+ this._log.error("Server responded with invalid JSON.");
+ this.abort(JPAKE_ERROR_CHANNEL);
+ return;
+ }
+ this._log.debug("Using channel " + this._channel);
+ this._channelURL = this._serverURL + this._channel;
+
+ // Don't block on UI code.
+ let pin = this._secret + this._channel;
+ Utils.nextTick(function() { this.controller.displayPIN(pin); }, this);
+ callback();
+ }));
+ },
+
+ // Generic handler for uploading data.
+ _putStep: function _putStep(callback) {
+ this._log.trace("Uploading message " + this._outgoing.type);
+ let request = this._newRequest(this._channelURL);
+ if (this._their_etag) {
+ request.setHeader("If-Match", this._their_etag);
+ } else {
+ request.setHeader("If-None-Match", "*");
+ }
+ request.put(this._outgoing, Utils.bind2(this, function (error) {
+ if (this._finished) {
+ return;
+ }
+
+ if (error) {
+ this._log.error("Error uploading data. " + error);
+ this.abort(JPAKE_ERROR_NETWORK);
+ return;
+ }
+ if (request.response.status != 200) {
+ this._log.error("Could not upload data. Server responded with HTTP "
+ + request.response.status);
+ this.abort(JPAKE_ERROR_SERVER);
+ return;
+ }
+ // There's no point in returning early here since the next step will
+ // always be a GET so let's pause for twice the poll interval.
+ this._my_etag = request.response.headers["etag"];
+ Utils.namedTimer(function () { callback(); }, this._pollInterval * 2,
+ this, "_pollTimer");
+ }));
+ },
+
+ // Generic handler for polling for and retrieving data.
+ _pollTries: 0,
+ _getStep: function _getStep(callback) {
+ this._log.trace("Retrieving next message.");
+ let request = this._newRequest(this._channelURL);
+ if (this._my_etag) {
+ request.setHeader("If-None-Match", this._my_etag);
+ }
+
+ request.get(Utils.bind2(this, function (error) {
+ if (this._finished) {
+ return;
+ }
+
+ if (error) {
+ this._log.error("Error fetching data. " + error);
+ this.abort(JPAKE_ERROR_NETWORK);
+ return;
+ }
+
+ if (request.response.status == 304) {
+ this._log.trace("Channel hasn't been updated yet. Will try again later.");
+ if (this._pollTries >= this._maxTries) {
+ this._log.error("Tried for " + this._pollTries + " times, aborting.");
+ this.abort(JPAKE_ERROR_TIMEOUT);
+ return;
+ }
+ this._pollTries += 1;
+ Utils.namedTimer(function() { this._getStep(callback); },
+ this._pollInterval, this, "_pollTimer");
+ return;
+ }
+ this._pollTries = 0;
+
+ if (request.response.status == 404) {
+ this._log.error("No data found in the channel.");
+ this.abort(JPAKE_ERROR_NODATA);
+ return;
+ }
+ if (request.response.status != 200) {
+ this._log.error("Could not retrieve data. Server responded with HTTP "
+ + request.response.status);
+ this.abort(JPAKE_ERROR_SERVER);
+ return;
+ }
+
+ this._their_etag = request.response.headers["etag"];
+ if (!this._their_etag) {
+ this._log.error("Server did not supply ETag for message: "
+ + request.response.body);
+ this.abort(JPAKE_ERROR_SERVER);
+ return;
+ }
+
+ try {
+ this._incoming = JSON.parse(request.response.body);
+ } catch (ex) {
+ this._log.error("Server responded with invalid JSON.");
+ this.abort(JPAKE_ERROR_INVALID);
+ return;
+ }
+ this._log.trace("Fetched message " + this._incoming.type);
+ callback();
+ }));
+ },
+
+ _reportFailure: function _reportFailure(reason, callback) {
+ this._log.debug("Reporting failure to server.");
+ let request = this._newRequest(this._serverURL + "report");
+ request.setHeader("X-KeyExchange-Cid", this._channel);
+ request.setHeader("X-KeyExchange-Log", reason);
+ request.post("", Utils.bind2(this, function (error) {
+ if (error) {
+ this._log.warn("Report failed: " + error);
+ } else if (request.response.status != 200) {
+ this._log.warn("Report failed. Server responded with HTTP "
+ + request.response.status);
+ }
+
+ // Do not block on errors, we're done or aborted by now anyway.
+ callback();
+ }));
+ },
+
+ _computeStepOne: function _computeStepOne(callback) {
+ this._log.trace("Computing round 1.");
+ let gx1 = {};
+ let gv1 = {};
+ let r1 = {};
+ let gx2 = {};
+ let gv2 = {};
+ let r2 = {};
+ try {
+ this._jpake.round1(this._my_signerid, gx1, gv1, r1, gx2, gv2, r2);
+ } catch (ex) {
+ this._log.error("JPAKE round 1 threw: " + ex);
+ this.abort(JPAKE_ERROR_INTERNAL);
+ return;
+ }
+ let one = {gx1: gx1.value,
+ gx2: gx2.value,
+ zkp_x1: {gr: gv1.value, b: r1.value, id: this._my_signerid},
+ zkp_x2: {gr: gv2.value, b: r2.value, id: this._my_signerid}};
+ this._outgoing = {type: this._my_signerid + "1",
+ version: KEYEXCHANGE_VERSION,
+ payload: one};
+ this._log.trace("Generated message " + this._outgoing.type);
+ callback();
+ },
+
+ _computeStepTwo: function _computeStepTwo(callback) {
+ this._log.trace("Computing round 2.");
+ if (this._incoming.type != this._their_signerid + "1") {
+ this._log.error("Invalid round 1 message: "
+ + JSON.stringify(this._incoming));
+ this.abort(JPAKE_ERROR_WRONGMESSAGE);
+ return;
+ }
+
+ let step1 = this._incoming.payload;
+ if (!step1 || !step1.zkp_x1 || step1.zkp_x1.id != this._their_signerid
+ || !step1.zkp_x2 || step1.zkp_x2.id != this._their_signerid) {
+ this._log.error("Invalid round 1 payload: " + JSON.stringify(step1));
+ this.abort(JPAKE_ERROR_WRONGMESSAGE);
+ return;
+ }
+
+ let A = {};
+ let gvA = {};
+ let rA = {};
+
+ try {
+ this._jpake.round2(this._their_signerid, this._secret,
+ step1.gx1, step1.zkp_x1.gr, step1.zkp_x1.b,
+ step1.gx2, step1.zkp_x2.gr, step1.zkp_x2.b,
+ A, gvA, rA);
+ } catch (ex) {
+ this._log.error("JPAKE round 2 threw: " + ex);
+ this.abort(JPAKE_ERROR_INTERNAL);
+ return;
+ }
+ let two = {A: A.value,
+ zkp_A: {gr: gvA.value, b: rA.value, id: this._my_signerid}};
+ this._outgoing = {type: this._my_signerid + "2",
+ version: KEYEXCHANGE_VERSION,
+ payload: two};
+ this._log.trace("Generated message " + this._outgoing.type);
+ callback();
+ },
+
+ _computeFinal: function _computeFinal(callback) {
+ if (this._incoming.type != this._their_signerid + "2") {
+ this._log.error("Invalid round 2 message: "
+ + JSON.stringify(this._incoming));
+ this.abort(JPAKE_ERROR_WRONGMESSAGE);
+ return;
+ }
+
+ let step2 = this._incoming.payload;
+ if (!step2 || !step2.zkp_A || step2.zkp_A.id != this._their_signerid) {
+ this._log.error("Invalid round 2 payload: " + JSON.stringify(step1));
+ this.abort(JPAKE_ERROR_WRONGMESSAGE);
+ return;
+ }
+
+ let aes256Key = {};
+ let hmac256Key = {};
+
+ try {
+ this._jpake.final(step2.A, step2.zkp_A.gr, step2.zkp_A.b, HMAC_INPUT,
+ aes256Key, hmac256Key);
+ } catch (ex) {
+ this._log.error("JPAKE final round threw: " + ex);
+ this.abort(JPAKE_ERROR_INTERNAL);
+ return;
+ }
+
+ this._crypto_key = aes256Key.value;
+ let hmac_key = Utils.makeHMACKey(Utils.safeAtoB(hmac256Key.value));
+ this._hmac_hasher = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, hmac_key);
+
+ callback();
+ },
+
+ _computeKeyVerification: function _computeKeyVerification(callback) {
+ this._log.trace("Encrypting key verification value.");
+ let iv, ciphertext;
+ try {
+ iv = Svc.Crypto.generateRandomIV();
+ ciphertext = Svc.Crypto.encrypt(JPAKE_VERIFY_VALUE,
+ this._crypto_key, iv);
+ } catch (ex) {
+ this._log.error("Failed to encrypt key verification value.");
+ this.abort(JPAKE_ERROR_INTERNAL);
+ return;
+ }
+ this._outgoing = {type: this._my_signerid + "3",
+ version: KEYEXCHANGE_VERSION,
+ payload: {ciphertext: ciphertext, IV: iv}};
+ this._log.trace("Generated message " + this._outgoing.type);
+ callback();
+ },
+
+ _verifyPairing: function _verifyPairing(callback) {
+ this._log.trace("Verifying their key.");
+ if (this._incoming.type != this._their_signerid + "3") {
+ this._log.error("Invalid round 3 data: " +
+ JSON.stringify(this._incoming));
+ this.abort(JPAKE_ERROR_WRONGMESSAGE);
+ return;
+ }
+ let step3 = this._incoming.payload;
+ let ciphertext;
+ try {
+ ciphertext = Svc.Crypto.encrypt(JPAKE_VERIFY_VALUE,
+ this._crypto_key, step3.IV);
+ if (ciphertext != step3.ciphertext) {
+ throw "Key mismatch!";
+ }
+ } catch (ex) {
+ this._log.error("Keys don't match!");
+ this.abort(JPAKE_ERROR_KEYMISMATCH);
+ return;
+ }
+
+ this._log.debug("Verified pairing!");
+ this._paired = true;
+ Utils.nextTick(function () { this.controller.onPaired(); }, this);
+ callback();
+ },
+
+ _encryptData: function _encryptData(callback) {
+ this._log.trace("Encrypting data.");
+ let iv, ciphertext, hmac;
+ try {
+ iv = Svc.Crypto.generateRandomIV();
+ ciphertext = Svc.Crypto.encrypt(this._data, this._crypto_key, iv);
+ hmac = Utils.bytesAsHex(Utils.digestUTF8(ciphertext, this._hmac_hasher));
+ } catch (ex) {
+ this._log.error("Failed to encrypt data.");
+ this.abort(JPAKE_ERROR_INTERNAL);
+ return;
+ }
+ this._outgoing = {type: this._my_signerid + "3",
+ version: KEYEXCHANGE_VERSION,
+ payload: {ciphertext: ciphertext, IV: iv, hmac: hmac}};
+ this._log.trace("Generated message " + this._outgoing.type);
+ callback();
+ },
+
+ _decryptData: function _decryptData(callback) {
+ this._log.trace("Verifying their key.");
+ if (this._incoming.type != this._their_signerid + "3") {
+ this._log.error("Invalid round 3 data: "
+ + JSON.stringify(this._incoming));
+ this.abort(JPAKE_ERROR_WRONGMESSAGE);
+ return;
+ }
+ let step3 = this._incoming.payload;
+ try {
+ let hmac = Utils.bytesAsHex(
+ Utils.digestUTF8(step3.ciphertext, this._hmac_hasher));
+ if (hmac != step3.hmac) {
+ throw "HMAC validation failed!";
+ }
+ } catch (ex) {
+ this._log.error("HMAC validation failed.");
+ this.abort(JPAKE_ERROR_KEYMISMATCH);
+ return;
+ }
+
+ this._log.trace("Decrypting data.");
+ let cleartext;
+ try {
+ cleartext = Svc.Crypto.decrypt(step3.ciphertext, this._crypto_key,
+ step3.IV);
+ } catch (ex) {
+ this._log.error("Failed to decrypt data.");
+ this.abort(JPAKE_ERROR_INTERNAL);
+ return;
+ }
+
+ try {
+ this._newData = JSON.parse(cleartext);
+ } catch (ex) {
+ this._log.error("Invalid data data: " + JSON.stringify(cleartext));
+ this.abort(JPAKE_ERROR_INVALID);
+ return;
+ }
+
+ this._log.trace("Decrypted data.");
+ callback();
+ },
+
+ _complete: function _complete() {
+ this._log.debug("Exchange completed.");
+ this._finished = true;
+ Utils.nextTick(function () { this.controller.onComplete(this._newData); },
+ this);
+ }
+
+};
+
+
+/**
+ * Send credentials over an active J-PAKE channel.
+ *
+ * This object is designed to take over as the JPAKEClient controller,
+ * presumably replacing one that is UI-based which would either cause
+ * DOM objects to leak or the JPAKEClient to be GC'ed when the DOM
+ * context disappears. This object stays alive for the duration of the
+ * transfer by being strong-ref'ed as an nsIObserver.
+ *
+ * Credentials are sent after the first sync has been completed
+ * (successfully or not.)
+ *
+ * Usage:
+ *
+ * jpakeclient.controller = new SendCredentialsController(jpakeclient,
+ * service);
+ *
+ */
+this.SendCredentialsController =
+ function SendCredentialsController(jpakeclient, service) {
+ this._log = Log.repository.getLogger("Sync.SendCredentialsController");
+ this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];
+
+ this._log.trace("Loading.");
+ this.jpakeclient = jpakeclient;
+ this.service = service;
+
+ // Register ourselves as observers the first Sync finishing (either
+ // successfully or unsuccessfully, we don't care) or for removing
+ // this device's sync configuration, in case that happens while we
+ // haven't finished the first sync yet.
+ Services.obs.addObserver(this, "weave:service:sync:finish", false);
+ Services.obs.addObserver(this, "weave:service:sync:error", false);
+ Services.obs.addObserver(this, "weave:service:start-over", false);
+}
+SendCredentialsController.prototype = {
+
+ unload: function unload() {
+ this._log.trace("Unloading.");
+ try {
+ Services.obs.removeObserver(this, "weave:service:sync:finish");
+ Services.obs.removeObserver(this, "weave:service:sync:error");
+ Services.obs.removeObserver(this, "weave:service:start-over");
+ } catch (ex) {
+ // Ignore.
+ }
+ },
+
+ observe: function observe(subject, topic, data) {
+ switch (topic) {
+ case "weave:service:sync:finish":
+ case "weave:service:sync:error":
+ Utils.nextTick(this.sendCredentials, this);
+ break;
+ case "weave:service:start-over":
+ // This will call onAbort which will call unload().
+ this.jpakeclient.abort();
+ break;
+ }
+ },
+
+ sendCredentials: function sendCredentials() {
+ this._log.trace("Sending credentials.");
+ let credentials = {account: this.service.identity.account,
+ password: this.service.identity.basicPassword,
+ synckey: this.service.identity.syncKey,
+ serverURL: this.service.serverURL};
+ this.jpakeclient.sendAndComplete(credentials);
+ },
+
+ // JPAKEClient controller API
+
+ onComplete: function onComplete() {
+ this._log.debug("Exchange was completed successfully!");
+ this.unload();
+
+ // Schedule a Sync for soonish to fetch the data uploaded by the
+ // device with which we just paired.
+ this.service.scheduler.scheduleNextSync(this.service.scheduler.activeInterval);
+ },
+
+ onAbort: function onAbort(error) {
+ // It doesn't really matter why we aborted, but the channel is closed
+ // for sure, so we won't be able to do anything with it.
+ this._log.debug("Exchange was aborted with error: " + error);
+ this.unload();
+ },
+
+ // Irrelevant methods for this controller:
+ displayPIN: function displayPIN() {},
+ onPairingStart: function onPairingStart() {},
+ onPaired: function onPaired() {},
+};
diff --git a/components/weave/src/sync/keys.js b/components/weave/src/sync/keys.js
new file mode 100644
index 000000000..b93de7f31
--- /dev/null
+++ b/components/weave/src/sync/keys.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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "BulkKeyBundle",
+ "SyncKeyBundle"
+];
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/util.js");
+
+/**
+ * Represents a pair of keys.
+ *
+ * Each key stored in a key bundle is 256 bits. One key is used for symmetric
+ * encryption. The other is used for HMAC.
+ *
+ * A KeyBundle by itself is just an anonymous pair of keys. Other types
+ * deriving from this one add semantics, such as associated collections or
+ * generating a key bundle via HKDF from another key.
+ */
+function KeyBundle() {
+ this._encrypt = null;
+ this._encryptB64 = null;
+ this._hmac = null;
+ this._hmacB64 = null;
+ this._hmacObj = null;
+ this._sha256HMACHasher = null;
+}
+KeyBundle.prototype = {
+ _encrypt: null,
+ _encryptB64: null,
+ _hmac: null,
+ _hmacB64: null,
+ _hmacObj: null,
+ _sha256HMACHasher: null,
+
+ equals: function equals(bundle) {
+ return bundle &&
+ (bundle.hmacKey == this.hmacKey) &&
+ (bundle.encryptionKey == this.encryptionKey);
+ },
+
+ /*
+ * Accessors for the two keys.
+ */
+ get encryptionKey() {
+ return this._encrypt;
+ },
+
+ set encryptionKey(value) {
+ if (!value || typeof value != "string") {
+ throw new Error("Encryption key can only be set to string values.");
+ }
+
+ if (value.length < 16) {
+ throw new Error("Encryption key must be at least 128 bits long.");
+ }
+
+ this._encrypt = value;
+ this._encryptB64 = btoa(value);
+ },
+
+ get encryptionKeyB64() {
+ return this._encryptB64;
+ },
+
+ get hmacKey() {
+ return this._hmac;
+ },
+
+ set hmacKey(value) {
+ if (!value || typeof value != "string") {
+ throw new Error("HMAC key can only be set to string values.");
+ }
+
+ if (value.length < 16) {
+ throw new Error("HMAC key must be at least 128 bits long.");
+ }
+
+ this._hmac = value;
+ this._hmacB64 = btoa(value);
+ this._hmacObj = value ? Utils.makeHMACKey(value) : null;
+ this._sha256HMACHasher = value ? Utils.makeHMACHasher(
+ Ci.nsICryptoHMAC.SHA256, this._hmacObj) : null;
+ },
+
+ get hmacKeyB64() {
+ return this._hmacB64;
+ },
+
+ get hmacKeyObject() {
+ return this._hmacObj;
+ },
+
+ get sha256HMACHasher() {
+ return this._sha256HMACHasher;
+ },
+
+ /**
+ * Populate this key pair with 2 new, randomly generated keys.
+ */
+ generateRandom: function generateRandom() {
+ let generatedHMAC = Svc.Crypto.generateRandomKey();
+ let generatedEncr = Svc.Crypto.generateRandomKey();
+ this.keyPairB64 = [generatedEncr, generatedHMAC];
+ },
+
+};
+
+/**
+ * Represents a KeyBundle associated with a collection.
+ *
+ * This is just a KeyBundle with a collection attached.
+ */
+this.BulkKeyBundle = function BulkKeyBundle(collection) {
+ let log = Log.repository.getLogger("Sync.BulkKeyBundle");
+ log.info("BulkKeyBundle being created for " + collection);
+ KeyBundle.call(this);
+
+ this._collection = collection;
+}
+
+BulkKeyBundle.prototype = {
+ __proto__: KeyBundle.prototype,
+
+ get collection() {
+ return this._collection;
+ },
+
+ /**
+ * Obtain the key pair in this key bundle.
+ *
+ * The returned keys are represented as raw byte strings.
+ */
+ get keyPair() {
+ return [this.encryptionKey, this.hmacKey];
+ },
+
+ set keyPair(value) {
+ if (!Array.isArray(value) || value.length != 2) {
+ throw new Error("BulkKeyBundle.keyPair value must be array of 2 keys.");
+ }
+
+ this.encryptionKey = value[0];
+ this.hmacKey = value[1];
+ },
+
+ get keyPairB64() {
+ return [this.encryptionKeyB64, this.hmacKeyB64];
+ },
+
+ set keyPairB64(value) {
+ if (!Array.isArray(value) || value.length != 2) {
+ throw new Error("BulkKeyBundle.keyPairB64 value must be an array of 2 " +
+ "keys.");
+ }
+
+ this.encryptionKey = Utils.safeAtoB(value[0]);
+ this.hmacKey = Utils.safeAtoB(value[1]);
+ },
+};
+
+/**
+ * Represents a key pair derived from a Sync Key via HKDF.
+ *
+ * Instances of this type should be considered immutable. You create an
+ * instance by specifying the username and 26 character "friendly" Base32
+ * encoded Sync Key. The Sync Key is derived at instance creation time.
+ *
+ * If the username or Sync Key is invalid, an Error will be thrown.
+ */
+this.SyncKeyBundle = function SyncKeyBundle(username, syncKey) {
+ let log = Log.repository.getLogger("Sync.SyncKeyBundle");
+ log.info("SyncKeyBundle being created.");
+ KeyBundle.call(this);
+
+ this.generateFromKey(username, syncKey);
+}
+SyncKeyBundle.prototype = {
+ __proto__: KeyBundle.prototype,
+
+ /*
+ * If we've got a string, hash it into keys and store them.
+ */
+ generateFromKey: function generateFromKey(username, syncKey) {
+ if (!username || (typeof username != "string")) {
+ throw new Error("Sync Key cannot be generated from non-string username.");
+ }
+
+ if (!syncKey || (typeof syncKey != "string")) {
+ throw new Error("Sync Key cannot be generated from non-string key.");
+ }
+
+ if (!Utils.isPassphrase(syncKey)) {
+ throw new Error("Provided key is not a passphrase, cannot derive Sync " +
+ "Key Bundle.");
+ }
+
+ // Expand the base32 Sync Key to an AES 256 and 256 bit HMAC key.
+ let prk = Utils.decodeKeyBase32(syncKey);
+ let info = HMAC_INPUT + username;
+ let okm = Utils.hkdfExpand(prk, info, 32 * 2);
+ this.encryptionKey = okm.slice(0, 32);
+ this.hmacKey = okm.slice(32, 64);
+ },
+};
+
diff --git a/components/weave/src/sync/main.js b/components/weave/src/sync/main.js
new file mode 100644
index 000000000..e8e705e72
--- /dev/null
+++ b/components/weave/src/sync/main.js
@@ -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/. */
+
+this.EXPORTED_SYMBOLS = ['Weave'];
+
+this.Weave = {};
+Components.utils.import("resource://services-sync/constants.js", Weave);
+var lazies = {
+ "jpakeclient.js": ["JPAKEClient", "SendCredentialsController"],
+ "notifications.js": ["Notifications", "Notification", "NotificationButton"],
+ "service.js": ["Service"],
+ "status.js": ["Status"],
+ "util.js": ['Utils', 'Svc']
+};
+
+function lazyImport(module, dest, props) {
+ function getter(prop) {
+ return function() {
+ let ns = {};
+ Components.utils.import(module, ns);
+ delete dest[prop];
+ return dest[prop] = ns[prop];
+ };
+ }
+ props.forEach(function (prop) { dest.__defineGetter__(prop, getter(prop)); });
+}
+
+for (let mod in lazies) {
+ lazyImport("resource://services-sync/" + mod, Weave, lazies[mod]);
+}
diff --git a/components/weave/src/sync/notifications.js b/components/weave/src/sync/notifications.js
new file mode 100644
index 000000000..5a67a7414
--- /dev/null
+++ b/components/weave/src/sync/notifications.js
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["Notifications", "Notification", "NotificationButton"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/util.js");
+
+this.Notifications = {
+ // Match the referenced values in toolkit/content/widgets/notification.xml.
+ get PRIORITY_INFO() { return 1; }, // PRIORITY_INFO_LOW
+ get PRIORITY_WARNING() { return 4; }, // PRIORITY_WARNING_LOW
+ get PRIORITY_ERROR() { return 7; }, // PRIORITY_CRITICAL_LOW
+
+ // FIXME: instead of making this public, dress the Notifications object
+ // to behave like an iterator (using generators?) and have callers access
+ // this array through the Notifications object.
+ notifications: [],
+
+ _observers: [],
+
+ // XXX Should we have a helper method for adding a simple notification?
+ // I.e. something like |function notify(title, description, priority)|.
+
+ add: function Notifications_add(notification) {
+ this.notifications.push(notification);
+ Observers.notify("weave:notification:added", notification, null);
+ },
+
+ remove: function Notifications_remove(notification) {
+ let index = this.notifications.indexOf(notification);
+
+ if (index != -1) {
+ this.notifications.splice(index, 1);
+ Observers.notify("weave:notification:removed", notification, null);
+ }
+ },
+
+ /**
+ * Replace an existing notification.
+ */
+ replace: function Notifications_replace(oldNotification, newNotification) {
+ let index = this.notifications.indexOf(oldNotification);
+
+ if (index != -1)
+ this.notifications.splice(index, 1, newNotification);
+ else {
+ this.notifications.push(newNotification);
+ // XXX Should we throw because we didn't find the existing notification?
+ // XXX Should we notify observers about weave:notification:added?
+ }
+
+ // XXX Should we notify observers about weave:notification:replaced?
+ },
+
+ /**
+ * Remove all notifications that match a title. If no title is provided, all
+ * notifications are removed.
+ *
+ * @param title [optional]
+ * Title of notifications to remove; falsy value means remove all
+ */
+ removeAll: function Notifications_removeAll(title) {
+ this.notifications.filter(old => (old.title == title || !title)).
+ forEach(old => { this.remove(old); }, this);
+ },
+
+ // replaces all existing notifications with the same title as the new one
+ replaceTitle: function Notifications_replaceTitle(notification) {
+ this.removeAll(notification.title);
+ this.add(notification);
+ }
+};
+
+
+/**
+ * A basic notification. Subclass this to create more complex notifications.
+ */
+this.Notification =
+function Notification(title, description, iconURL, priority, buttons, link) {
+ this.title = title;
+ this.description = description;
+
+ if (iconURL)
+ this.iconURL = iconURL;
+
+ if (priority)
+ this.priority = priority;
+
+ if (buttons)
+ this.buttons = buttons;
+
+ if (link)
+ this.link = link;
+}
+
+// We set each prototype property individually instead of redefining
+// the entire prototype to avoid blowing away existing properties
+// of the prototype like the the "constructor" property, which we use
+// to bind notification objects to their XBL representations.
+Notification.prototype.priority = Notifications.PRIORITY_INFO;
+Notification.prototype.iconURL = null;
+Notification.prototype.buttons = [];
+
+/**
+ * A button to display in a notification.
+ */
+this.NotificationButton =
+ function NotificationButton(label, accessKey, callback) {
+ function callbackWrapper() {
+ try {
+ callback.apply(this, arguments);
+ } catch (e) {
+ let logger = Log.repository.getLogger("Sync.Notifications");
+ logger.error("An exception occurred: ", e);
+ logger.info(Utils.stackTrace(e));
+ throw e;
+ }
+ }
+
+ this.label = label;
+ this.accessKey = accessKey;
+ this.callback = callbackWrapper;
+}
diff --git a/components/weave/src/sync/policies.js b/components/weave/src/sync/policies.js
new file mode 100644
index 000000000..48acbe2e6
--- /dev/null
+++ b/components/weave/src/sync/policies.js
@@ -0,0 +1,967 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [
+ "ErrorHandler",
+ "SyncScheduler",
+];
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/util.js");
+Cu.import("resource://services-common/logmanager.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Status",
+ "resource://services-sync/status.js");
+
+this.SyncScheduler = function SyncScheduler(service) {
+ this.service = service;
+ this.init();
+}
+SyncScheduler.prototype = {
+ _log: Log.repository.getLogger("Sync.SyncScheduler"),
+
+ _fatalLoginStatus: [LOGIN_FAILED_NO_USERNAME,
+ LOGIN_FAILED_NO_PASSWORD,
+ LOGIN_FAILED_NO_PASSPHRASE,
+ LOGIN_FAILED_INVALID_PASSPHRASE,
+ LOGIN_FAILED_LOGIN_REJECTED],
+
+ /**
+ * The nsITimer object that schedules the next sync. See scheduleNextSync().
+ */
+ syncTimer: null,
+
+ setDefaults: function setDefaults() {
+ this._log.trace("Setting SyncScheduler policy values to defaults.");
+
+ let service = Cc["@mozilla.org/weave/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject;
+
+ let prefSDInterval = "scheduler.sync11.singleDeviceInterval";
+ this.singleDeviceInterval = Svc.Prefs.get(prefSDInterval) * 1000;
+
+ this.idleInterval = Svc.Prefs.get("scheduler.idleInterval") * 1000;
+ this.activeInterval = Svc.Prefs.get("scheduler.activeInterval") * 1000;
+ this.immediateInterval = Svc.Prefs.get("scheduler.immediateInterval") * 1000;
+ this.eolInterval = Svc.Prefs.get("scheduler.eolInterval") * 1000;
+
+ // A user is non-idle on startup by default.
+ this.idle = false;
+
+ this.hasIncomingItems = false;
+
+ this.clearSyncTriggers();
+ },
+
+ // nextSync is in milliseconds, but prefs can't hold that much
+ get nextSync() {
+ if (Svc.Prefs) {
+ return Svc.Prefs.get("nextSync", 0) * 1000
+ }
+ },
+ set nextSync(value) {
+ if (Svc.Prefs) {
+ Svc.Prefs.set("nextSync", Math.floor(value / 1000))
+ }
+ },
+
+ get syncInterval() {
+ if (Svc.Prefs) {
+ return Svc.Prefs.get("syncInterval", this.singleDeviceInterval)
+ }
+ },
+ set syncInterval(value) {
+ if (Svc.Prefs) {
+ Svc.Prefs.set("syncInterval", value)
+ }
+ },
+
+ get syncThreshold() {
+ if (Svc.Prefs) {
+ return Svc.Prefs.get("syncThreshold", SINGLE_USER_THRESHOLD)
+ }
+ },
+ set syncThreshold(value) {
+ if (Svc.Prefs) {
+ Svc.Prefs.set("syncThreshold", value)
+ }
+ },
+
+ get globalScore() {
+ if (Svc.Prefs) {
+ return Svc.Prefs.get("globalScore", 0)
+ }
+ },
+ set globalScore(value) {
+ if (Svc.Prefs) {
+ Svc.Prefs.set("globalScore", value)
+ }
+ },
+
+ get numClients() {
+ if (Svc.Prefs) {
+ return Svc.Prefs.get("numClients", 0)
+ }
+ },
+ set numClients(value) {
+ if (Svc.Prefs) {
+ Svc.Prefs.set("numClients", value)
+ }
+ },
+
+ init: function init() {
+ this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];
+ this.setDefaults();
+ Svc.Obs.add("weave:engine:score:updated", this);
+ Svc.Obs.add("network:offline-status-changed", this);
+ Svc.Obs.add("weave:service:sync:start", this);
+ Svc.Obs.add("weave:service:sync:finish", this);
+ Svc.Obs.add("weave:engine:sync:finish", this);
+ Svc.Obs.add("weave:engine:sync:error", this);
+ Svc.Obs.add("weave:service:login:error", this);
+ Svc.Obs.add("weave:service:logout:finish", this);
+ Svc.Obs.add("weave:service:sync:error", this);
+ Svc.Obs.add("weave:service:backoff:interval", this);
+ Svc.Obs.add("weave:service:ready", this);
+ Svc.Obs.add("weave:engine:sync:applied", this);
+ Svc.Obs.add("weave:service:setup-complete", this);
+ Svc.Obs.add("weave:service:start-over", this);
+ Svc.Obs.add("FxA:hawk:backoff:interval", this);
+
+ if (Status.checkSetup() == STATUS_OK) {
+ Svc.Obs.add("wake_notification", this);
+ Svc.Idle.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
+ }
+ },
+
+ observe: function observe(subject, topic, data) {
+ this._log.trace("Handling " + topic);
+ switch(topic) {
+ case "weave:engine:score:updated":
+ if (Status.login == LOGIN_SUCCEEDED) {
+ Utils.namedTimer(this.calculateScore, SCORE_UPDATE_DELAY, this,
+ "_scoreTimer");
+ }
+ break;
+ case "network:offline-status-changed":
+ // Whether online or offline, we'll reschedule syncs
+ this._log.trace("Network offline status change: " + data);
+ this.checkSyncStatus();
+ break;
+ case "weave:service:sync:start":
+ // Clear out any potentially pending syncs now that we're syncing
+ this.clearSyncTriggers();
+
+ // reset backoff info, if the server tells us to continue backing off,
+ // we'll handle that later
+ Status.resetBackoff();
+
+ this.globalScore = 0;
+ break;
+ case "weave:service:sync:finish":
+ this.nextSync = 0;
+ this.adjustSyncInterval();
+
+ if (Status.service == SYNC_FAILED_PARTIAL && this.requiresBackoff) {
+ this.requiresBackoff = false;
+ this.handleSyncError();
+ return;
+ }
+
+ let sync_interval;
+ this._syncErrors = 0;
+ if (Status.sync == NO_SYNC_NODE_FOUND) {
+ this._log.trace("Scheduling a sync at interval NO_SYNC_NODE_FOUND.");
+ sync_interval = NO_SYNC_NODE_INTERVAL;
+ }
+ this.scheduleNextSync(sync_interval);
+ break;
+ case "weave:engine:sync:finish":
+ if (data == "clients") {
+ // Update the client mode because it might change what we sync.
+ this.updateClientMode();
+ }
+ break;
+ case "weave:engine:sync:error":
+ // `subject` is the exception thrown by an engine's sync() method.
+ let exception = subject;
+ if (exception.status >= 500 && exception.status <= 504) {
+ this.requiresBackoff = true;
+ }
+ break;
+ case "weave:service:login:error":
+ this.clearSyncTriggers();
+
+ if (Status.login == MASTER_PASSWORD_LOCKED) {
+ // Try again later, just as if we threw an error... only without the
+ // error count.
+ this._log.debug("Couldn't log in: master password is locked.");
+ this._log.trace("Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL");
+ this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
+ } else if (this._fatalLoginStatus.indexOf(Status.login) == -1) {
+ // Not a fatal login error, just an intermittent network or server
+ // issue. Keep on syncin'.
+ this.checkSyncStatus();
+ }
+ break;
+ case "weave:service:logout:finish":
+ // Start or cancel the sync timer depending on if
+ // logged in or logged out
+ this.checkSyncStatus();
+ break;
+ case "weave:service:sync:error":
+ // There may be multiple clients but if the sync fails, client mode
+ // should still be updated so that the next sync has a correct interval.
+ this.updateClientMode();
+ this.adjustSyncInterval();
+ this.nextSync = 0;
+ this.handleSyncError();
+ break;
+ case "FxA:hawk:backoff:interval":
+ case "weave:service:backoff:interval":
+ let requested_interval = subject * 1000;
+ this._log.debug("Got backoff notification: " + requested_interval + "ms");
+ // Leave up to 25% more time for the back off.
+ let interval = requested_interval * (1 + Math.random() * 0.25);
+ Status.backoffInterval = interval;
+ Status.minimumNextSync = Date.now() + requested_interval;
+ this._log.debug("Fuzzed minimum next sync: " + Status.minimumNextSync);
+ break;
+ case "weave:service:ready":
+ // Applications can specify this preference if they want autoconnect
+ // to happen after a fixed delay.
+ let delay = Svc.Prefs.get("autoconnectDelay");
+ if (delay) {
+ this.delayedAutoConnect(delay);
+ }
+ break;
+ case "weave:engine:sync:applied":
+ let numItems = subject.succeeded;
+ this._log.trace("Engine " + data + " successfully applied " + numItems +
+ " items.");
+ if (numItems) {
+ this.hasIncomingItems = true;
+ }
+ break;
+ case "weave:service:setup-complete":
+ Services.prefs.savePrefFile(null);
+ Svc.Idle.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
+ Svc.Obs.add("wake_notification", this);
+ break;
+ case "weave:service:start-over":
+ this.setDefaults();
+ try {
+ Svc.Idle.removeIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
+ } catch (ex if (ex.result == Cr.NS_ERROR_FAILURE)) {
+ // In all likelihood we didn't have an idle observer registered yet.
+ // It's all good.
+ }
+ break;
+ case "idle":
+ this._log.trace("We're idle.");
+ this.idle = true;
+ // Adjust the interval for future syncs. This won't actually have any
+ // effect until the next pending sync (which will happen soon since we
+ // were just active.)
+ this.adjustSyncInterval();
+ break;
+ case "active":
+ this._log.trace("Received notification that we're back from idle.");
+ this.idle = false;
+ Utils.namedTimer(function onBack() {
+ if (this.idle) {
+ this._log.trace("... and we're idle again. " +
+ "Ignoring spurious back notification.");
+ return;
+ }
+
+ this._log.trace("Genuine return from idle. Syncing.");
+ // Trigger a sync if we have multiple clients.
+ if (this.numClients > 1) {
+ this.scheduleNextSync(0);
+ }
+ }, IDLE_OBSERVER_BACK_DELAY, this, "idleDebouncerTimer");
+ break;
+ case "wake_notification":
+ this._log.debug("Woke from sleep.");
+ Utils.nextTick(() => {
+ // Trigger a sync if we have multiple clients.
+ if (this.numClients > 1) {
+ this._log.debug("More than 1 client. Syncing.");
+ this.scheduleNextSync(0);
+ }
+ });
+ break;
+ }
+ },
+
+ adjustSyncInterval: function adjustSyncInterval() {
+ if (Status.eol) {
+ this._log.debug("Server status is EOL; using eolInterval.");
+ this.syncInterval = this.eolInterval;
+ return;
+ }
+
+ if (this.numClients <= 1) {
+ this._log.trace("Adjusting syncInterval to singleDeviceInterval.");
+ this.syncInterval = this.singleDeviceInterval;
+ return;
+ }
+
+ // Only MULTI_DEVICE clients will enter this if statement
+ // since SINGLE_USER clients will be handled above.
+ if (this.idle) {
+ this._log.trace("Adjusting syncInterval to idleInterval.");
+ this.syncInterval = this.idleInterval;
+ return;
+ }
+
+ if (this.hasIncomingItems) {
+ this._log.trace("Adjusting syncInterval to immediateInterval.");
+ this.hasIncomingItems = false;
+ this.syncInterval = this.immediateInterval;
+ } else {
+ this._log.trace("Adjusting syncInterval to activeInterval.");
+ this.syncInterval = this.activeInterval;
+ }
+ },
+
+ calculateScore: function calculateScore() {
+ let engines = [this.service.clientsEngine].concat(this.service.engineManager.getEnabled());
+ for (let i = 0;i < engines.length;i++) {
+ this._log.trace(engines[i].name + ": score: " + engines[i].score);
+ this.globalScore += engines[i].score;
+ engines[i]._tracker.resetScore();
+ }
+
+ this._log.trace("Global score updated: " + this.globalScore);
+ this.checkSyncStatus();
+ },
+
+ /**
+ * Process the locally stored clients list to figure out what mode to be in
+ */
+ updateClientMode: function updateClientMode() {
+ // Nothing to do if it's the same amount
+ let numClients = this.service.clientsEngine.stats.numClients;
+ if (this.numClients == numClients)
+ return;
+
+ this._log.debug("Client count: " + this.numClients + " -> " + numClients);
+ this.numClients = numClients;
+
+ if (numClients <= 1) {
+ this._log.trace("Adjusting syncThreshold to SINGLE_USER_THRESHOLD");
+ this.syncThreshold = SINGLE_USER_THRESHOLD;
+ } else {
+ this._log.trace("Adjusting syncThreshold to MULTI_DEVICE_THRESHOLD");
+ this.syncThreshold = MULTI_DEVICE_THRESHOLD;
+ }
+ this.adjustSyncInterval();
+ },
+
+ /**
+ * Check if we should be syncing and schedule the next sync, if it's not scheduled
+ */
+ checkSyncStatus: function checkSyncStatus() {
+ // Should we be syncing now, if not, cancel any sync timers and return
+ // if we're in backoff, we'll schedule the next sync.
+ let ignore = [kSyncBackoffNotMet, kSyncMasterPasswordLocked];
+ let skip = this.service._checkSync(ignore);
+ this._log.trace("_checkSync returned \"" + skip + "\".");
+ if (skip) {
+ this.clearSyncTriggers();
+ return;
+ }
+
+ // Only set the wait time to 0 if we need to sync right away
+ let wait;
+ if (this.globalScore > this.syncThreshold) {
+ this._log.debug("Global Score threshold hit, triggering sync.");
+ wait = 0;
+ }
+ this.scheduleNextSync(wait);
+ },
+
+ /**
+ * Call sync() if Master Password is not locked.
+ *
+ * Otherwise, reschedule a sync for later.
+ */
+ syncIfMPUnlocked: function syncIfMPUnlocked() {
+ // No point if we got kicked out by the master password dialog.
+ if (Status.login == MASTER_PASSWORD_LOCKED &&
+ Utils.mpLocked()) {
+ this._log.debug("Not initiating sync: Login status is " + Status.login);
+
+ // If we're not syncing now, we need to schedule the next one.
+ this._log.trace("Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL");
+ this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
+ return;
+ }
+
+ Utils.nextTick(this.service.sync, this.service);
+ },
+
+ /**
+ * Set a timer for the next sync
+ */
+ scheduleNextSync: function scheduleNextSync(interval) {
+ // If no interval was specified, use the current sync interval.
+ if (interval == null) {
+ interval = this.syncInterval;
+ }
+
+ // Ensure the interval is set to no less than the backoff.
+ if (Status.backoffInterval && interval < Status.backoffInterval) {
+ this._log.trace("Requested interval " + interval +
+ " ms is smaller than the backoff interval. " +
+ "Using backoff interval " +
+ Status.backoffInterval + " ms instead.");
+ interval = Status.backoffInterval;
+ }
+
+ if (this.nextSync != 0) {
+ // There's already a sync scheduled. Don't reschedule if there's already
+ // a timer scheduled for sooner than requested.
+ let currentInterval = this.nextSync - Date.now();
+ this._log.trace("There's already a sync scheduled in " +
+ currentInterval + " ms.");
+ if (currentInterval < interval && this.syncTimer) {
+ this._log.trace("Ignoring scheduling request for next sync in " +
+ interval + " ms.");
+ return;
+ }
+ }
+
+ // Start the sync right away if we're already late.
+ if (interval <= 0) {
+ this._log.trace("Requested sync should happen right away.");
+ this.syncIfMPUnlocked();
+ return;
+ }
+
+ this._log.debug("Next sync in " + interval + " ms.");
+ Utils.namedTimer(this.syncIfMPUnlocked, interval, this, "syncTimer");
+
+ // Save the next sync time in-case sync is disabled (logout/offline/etc.)
+ this.nextSync = Date.now() + interval;
+ },
+
+
+ /**
+ * Incorporates the backoff/retry logic used in error handling and elective
+ * non-syncing.
+ */
+ scheduleAtInterval: function scheduleAtInterval(minimumInterval) {
+ let interval = Utils.calculateBackoff(this._syncErrors,
+ MINIMUM_BACKOFF_INTERVAL,
+ Status.backoffInterval);
+ if (minimumInterval) {
+ interval = Math.max(minimumInterval, interval);
+ }
+
+ this._log.debug("Starting client-initiated backoff. Next sync in " +
+ interval + " ms.");
+ this.scheduleNextSync(interval);
+ },
+
+ /**
+ * Automatically start syncing after the given delay (in seconds).
+ *
+ * Applications can define the `services.sync.autoconnectDelay` preference
+ * to have this called automatically during start-up with the pref value as
+ * the argument. Alternatively, they can call it themselves to control when
+ * Sync should first start to sync.
+ */
+ delayedAutoConnect: function delayedAutoConnect(delay) {
+ if (this.service._checkSetup() == STATUS_OK) {
+ Utils.namedTimer(this.autoConnect, delay * 1000, this, "_autoTimer");
+ }
+ },
+
+ autoConnect: function autoConnect() {
+ if (this.service._checkSetup() == STATUS_OK && !this.service._checkSync()) {
+ // Schedule a sync based on when a previous sync was scheduled.
+ // scheduleNextSync() will do the right thing if that time lies in
+ // the past.
+ this.scheduleNextSync(this.nextSync - Date.now());
+ }
+
+ // Once autoConnect is called we no longer need _autoTimer.
+ if (this._autoTimer) {
+ this._autoTimer.clear();
+ }
+ },
+
+ _syncErrors: 0,
+ /**
+ * Deal with sync errors appropriately
+ */
+ handleSyncError: function handleSyncError() {
+ this._log.trace("In handleSyncError. Error count: " + this._syncErrors);
+ this._syncErrors++;
+
+ // Do nothing on the first couple of failures, if we're not in
+ // backoff due to 5xx errors.
+ if (!Status.enforceBackoff) {
+ if (this._syncErrors < MAX_ERROR_COUNT_BEFORE_BACKOFF) {
+ this.scheduleNextSync();
+ return;
+ }
+ this._log.debug("Sync error count has exceeded " +
+ MAX_ERROR_COUNT_BEFORE_BACKOFF + "; enforcing backoff.");
+ Status.enforceBackoff = true;
+ }
+
+ this.scheduleAtInterval();
+ },
+
+
+ /**
+ * Remove any timers/observers that might trigger a sync
+ */
+ clearSyncTriggers: function clearSyncTriggers() {
+ this._log.debug("Clearing sync triggers and the global score.");
+ this.globalScore = this.nextSync = 0;
+
+ // Clear out any scheduled syncs
+ if (this.syncTimer)
+ this.syncTimer.clear();
+ },
+
+ /**
+ * Prevent new syncs from starting. This is used by the FxA migration code
+ * where we can't afford to have a sync start partway through the migration.
+ * To handle the edge-case of a sync starting and not stopping, we store
+ * this state in a pref, so on the next startup we remain blocked (and thus
+ * sync will never start) so the migration can complete.
+ *
+ * As a safety measure, we only block for some period of time, and after
+ * that it will automatically unblock. This ensures that if things go
+ * really pear-shaped and we never end up calling unblockSync() we haven't
+ * completely broken the world.
+ */
+ blockSync: function(until = null) {
+ if (!until) {
+ until = Date.now() + DEFAULT_BLOCK_PERIOD;
+ }
+ // until is specified in ms, but Prefs can't hold that much
+ Svc.Prefs.set("scheduler.blocked-until", Math.floor(until / 1000));
+ },
+
+ unblockSync: function() {
+ Svc.Prefs.reset("scheduler.blocked-until");
+ // the migration code should be ready to roll, so resume normal operations.
+ this.checkSyncStatus();
+ },
+
+ get isBlocked() {
+ let until = Svc.Prefs ? Svc.Prefs.get("scheduler.blocked-until") : undefined;
+ if (until === undefined) {
+ return false;
+ }
+ if (until <= Math.floor(Date.now() / 1000)) {
+ // we were previously blocked but the time has expired.
+ Svc.Prefs.reset("scheduler.blocked-until");
+ return false;
+ }
+ // we remain blocked.
+ return true;
+ },
+};
+
+this.ErrorHandler = function ErrorHandler(service) {
+ this.service = service;
+ this.init();
+}
+ErrorHandler.prototype = {
+ MINIMUM_ALERT_INTERVAL_MSEC: 604800000, // One week.
+
+ /**
+ * Flag that turns on error reporting for all errors, incl. network errors.
+ */
+ dontIgnoreErrors: false,
+
+ /**
+ * Flag that indicates if we have already reported a prolonged failure.
+ * Once set, we don't report it again, meaning this error is only reported
+ * one per run.
+ */
+ didReportProlongedError: false,
+
+ init: function init() {
+ Svc.Obs.add("weave:engine:sync:applied", this);
+ Svc.Obs.add("weave:engine:sync:error", this);
+ Svc.Obs.add("weave:service:login:error", this);
+ Svc.Obs.add("weave:service:sync:error", this);
+ Svc.Obs.add("weave:service:sync:finish", this);
+
+ this.initLogs();
+ },
+
+ initLogs: function initLogs() {
+ this._log = Log.repository.getLogger("Sync.ErrorHandler");
+ this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.main")];
+
+ let root = Log.repository.getLogger("Sync");
+ root.level = Log.Level[Svc.Prefs.get("log.rootLogger")];
+
+ let logs = ["Sync", "FirefoxAccounts", "Hawk", "Common.TokenServerClient",
+ "Sync.SyncMigration"];
+
+ this._logManager = new LogManager(Svc.Prefs, logs, "sync");
+ },
+
+ observe: function observe(subject, topic, data) {
+ this._log.trace("Handling " + topic);
+ switch(topic) {
+ case "weave:engine:sync:applied":
+ if (subject.newFailed) {
+ // An engine isn't able to apply one or more incoming records.
+ // We don't fail hard on this, but it usually indicates a bug,
+ // so for now treat it as sync error (c.f. Service._syncEngine())
+ Status.engines = [data, ENGINE_APPLY_FAIL];
+ this._log.debug(data + " failed to apply some records.");
+ }
+ break;
+ case "weave:engine:sync:error":
+ let exception = subject; // exception thrown by engine's sync() method
+ let engine_name = data; // engine name that threw the exception
+
+ this.checkServerError(exception);
+
+ Status.engines = [engine_name, exception.failureCode || ENGINE_UNKNOWN_FAIL];
+ this._log.debug(engine_name + " failed: ", exception);
+ break;
+ case "weave:service:login:error":
+ this.resetFileLog(this._logManager.REASON_ERROR);
+
+ if (this.shouldReportError()) {
+ this.notifyOnNextTick("weave:ui:login:error");
+ } else {
+ this.notifyOnNextTick("weave:ui:clear-error");
+ }
+
+ this.dontIgnoreErrors = false;
+ break;
+ case "weave:service:sync:error":
+ if (Status.sync == CREDENTIALS_CHANGED) {
+ this.service.logout();
+ }
+
+ this.resetFileLog(this._logManager.REASON_ERROR);
+
+ if (this.shouldReportError()) {
+ this.notifyOnNextTick("weave:ui:sync:error");
+ } else {
+ this.notifyOnNextTick("weave:ui:sync:finish");
+ }
+
+ this.dontIgnoreErrors = false;
+ break;
+ case "weave:service:sync:finish":
+ this._log.trace("Status.service is " + Status.service);
+
+ // Check both of these status codes: in the event of a failure in one
+ // engine, Status.service will be SYNC_FAILED_PARTIAL despite
+ // Status.sync being SYNC_SUCCEEDED.
+ // *facepalm*
+ if (Status.sync == SYNC_SUCCEEDED &&
+ Status.service == STATUS_OK) {
+ // Great. Let's clear our mid-sync 401 note.
+ this._log.trace("Clearing lastSyncReassigned.");
+ Svc.Prefs.reset("lastSyncReassigned");
+ }
+
+ if (Status.service == SYNC_FAILED_PARTIAL) {
+ this._log.debug("Some engines did not sync correctly.");
+ this.resetFileLog(this._logManager.REASON_ERROR);
+
+ if (this.shouldReportError()) {
+ this.dontIgnoreErrors = false;
+ this.notifyOnNextTick("weave:ui:sync:error");
+ break;
+ }
+ } else {
+ this.resetFileLog(this._logManager.REASON_SUCCESS);
+ }
+ this.dontIgnoreErrors = false;
+ this.notifyOnNextTick("weave:ui:sync:finish");
+ break;
+ }
+ },
+
+ notifyOnNextTick: function notifyOnNextTick(topic) {
+ Utils.nextTick(function() {
+ this._log.trace("Notifying " + topic +
+ ". Status.login is " + Status.login +
+ ". Status.sync is " + Status.sync);
+ Svc.Obs.notify(topic);
+ }, this);
+ },
+
+ /**
+ * Trigger a sync and don't muffle any errors, particularly network errors.
+ */
+ syncAndReportErrors: function syncAndReportErrors() {
+ this._log.debug("Beginning user-triggered sync.");
+
+ this.dontIgnoreErrors = true;
+ Utils.nextTick(this.service.sync, this.service);
+ },
+
+ /**
+ * Generate a log file for the sync that just completed
+ * and refresh the input & output streams.
+ *
+ * @param reason
+ * A constant from the LogManager that indicates the reason for the
+ * reset.
+ */
+ resetFileLog: function resetFileLog(reason) {
+ let onComplete = () => {
+ Svc.Obs.notify("weave:service:reset-file-log");
+ this._log.trace("Notified: " + Date.now());
+ };
+ // Note we do not return the promise here - the caller doesn't need to wait
+ // for this to complete.
+ this._logManager.resetFileLog(reason).then(onComplete, onComplete);
+ },
+
+ /**
+ * Translates server error codes to meaningful strings.
+ *
+ * @param code
+ * server error code as an integer
+ */
+ errorStr: function errorStr(code) {
+ switch (code.toString()) {
+ case "1":
+ return "illegal-method";
+ case "2":
+ return "invalid-captcha";
+ case "3":
+ return "invalid-username";
+ case "4":
+ return "cannot-overwrite-resource";
+ case "5":
+ return "userid-mismatch";
+ case "6":
+ return "json-parse-failure";
+ case "7":
+ return "invalid-password";
+ case "8":
+ return "invalid-record";
+ case "9":
+ return "weak-password";
+ default:
+ return "generic-server-error";
+ }
+ },
+
+ shouldReportError: function shouldReportError() {
+ if (Status.login == MASTER_PASSWORD_LOCKED) {
+ this._log.trace("shouldReportError: false (master password locked).");
+ return false;
+ }
+
+ if (this.dontIgnoreErrors) {
+ return true;
+ }
+
+ if (Status.login == LOGIN_FAILED_LOGIN_REJECTED) {
+ // An explicit LOGIN_REJECTED state is always reported (bug 1081158)
+ this._log.trace("shouldReportError: true (login was rejected)");
+ return true;
+ }
+
+ let lastSync = Svc.Prefs.get("lastSync");
+ if (lastSync && ((Date.now() - Date.parse(lastSync)) >
+ Svc.Prefs.get("errorhandler.networkFailureReportTimeout") * 1000)) {
+ Status.sync = PROLONGED_SYNC_FAILURE;
+ if (this.didReportProlongedError) {
+ this._log.trace("shouldReportError: false (prolonged sync failure, but" +
+ " we've already reported it).");
+ return false;
+ }
+ this._log.trace("shouldReportError: true (first prolonged sync failure).");
+ this.didReportProlongedError = true;
+ return true;
+ }
+
+ // We got a 401 mid-sync. Wait for the next sync before actually handling
+ // an error. This assumes that we'll get a 401 again on a login fetch in
+ // order to report the error.
+ if (!this.service.clusterURL) {
+ this._log.trace("shouldReportError: false (no cluster URL; " +
+ "possible node reassignment).");
+ return false;
+ }
+
+ return ([Status.login, Status.sync].indexOf(SERVER_MAINTENANCE) == -1 &&
+ [Status.login, Status.sync].indexOf(LOGIN_FAILED_NETWORK_ERROR) == -1);
+ },
+
+ get currentAlertMode() {
+ return Svc.Prefs ? Svc.Prefs.get("errorhandler.alert.mode") : undefined;
+ },
+
+ set currentAlertMode(str) {
+ return Svc.Prefs ? Svc.Prefs.set("errorhandler.alert.mode", str) : undefined;
+ },
+
+ get earliestNextAlert() {
+ return Svc.Prefs ? Svc.Prefs.get("errorhandler.alert.earliestNext", 0) * 1000 : undefined;
+ },
+
+ set earliestNextAlert(msec) {
+ return Svc.Prefs ? Svc.Prefs.set("errorhandler.alert.earliestNext", msec / 1000) : undefined;
+ },
+
+ clearServerAlerts: function () {
+ // If we have any outstanding alerts, apparently they're no longer relevant.
+ Svc.Prefs.resetBranch("errorhandler.alert");
+ },
+
+ /**
+ * X-Weave-Alert headers can include a JSON object:
+ *
+ * {
+ * "code": // One of "hard-eol", "soft-eol".
+ * "url": // For "Learn more" link.
+ * "message": // Logged in Sync logs.
+ * }
+ */
+ handleServerAlert: function (xwa) {
+ if (!xwa.code) {
+ this._log.warn("Got structured X-Weave-Alert, but no alert code.");
+ return;
+ }
+
+ switch (xwa.code) {
+ // Gently and occasionally notify the user that this service will be
+ // shutting down.
+ case "soft-eol":
+ // Fall through.
+
+ // Tell the user that this service has shut down, and drop our syncing
+ // frequency dramatically.
+ case "hard-eol":
+ // Note that both of these alerts should be subservient to future "sign
+ // in with your Firefox Account" storage alerts.
+ if ((this.currentAlertMode != xwa.code) ||
+ (this.earliestNextAlert < Date.now())) {
+ Utils.nextTick(function() {
+ Svc.Obs.notify("weave:eol", xwa);
+ }, this);
+ this._log.error("X-Weave-Alert: " + xwa.code + ": " + xwa.message);
+ this.earliestNextAlert = Date.now() + this.MINIMUM_ALERT_INTERVAL_MSEC;
+ this.currentAlertMode = xwa.code;
+ }
+ break;
+ default:
+ this._log.debug("Got unexpected X-Weave-Alert code: " + xwa.code);
+ }
+ },
+
+ /**
+ * Handle HTTP response results or exceptions and set the appropriate
+ * Status.* bits.
+ *
+ * This method also looks for "side-channel" warnings.
+ */
+ checkServerError: function (resp) {
+ switch (resp.status) {
+ case 200:
+ case 404:
+ case 513:
+ let xwa = resp.headers['x-weave-alert'];
+
+ // Only process machine-readable alerts.
+ if (!xwa || !xwa.startsWith("{")) {
+ this.clearServerAlerts();
+ return;
+ }
+
+ try {
+ xwa = JSON.parse(xwa);
+ } catch (ex) {
+ this._log.warn("Malformed X-Weave-Alert from server: " + xwa);
+ return;
+ }
+
+ this.handleServerAlert(xwa);
+ break;
+
+ case 400:
+ if (resp == RESPONSE_OVER_QUOTA) {
+ Status.sync = OVER_QUOTA;
+ }
+ break;
+
+ case 401:
+ this.service.logout();
+ this._log.info("Got 401 response; resetting clusterURL.");
+ Svc.Prefs.reset("clusterURL");
+
+ let delay = 0;
+ if (Svc.Prefs.get("lastSyncReassigned")) {
+ // We got a 401 in the middle of the previous sync, and we just got
+ // another. Login must have succeeded in order for us to get here, so
+ // the password should be correct.
+ // This is likely to be an intermittent server issue, so back off and
+ // give it time to recover.
+ this._log.warn("Last sync also failed for 401. Delaying next sync.");
+ delay = MINIMUM_BACKOFF_INTERVAL;
+ } else {
+ this._log.debug("New mid-sync 401 failure. Making a note.");
+ Svc.Prefs.set("lastSyncReassigned", true);
+ }
+ this._log.info("Attempting to schedule another sync.");
+ this.service.scheduler.scheduleNextSync(delay);
+ break;
+
+ case 500:
+ case 502:
+ case 503:
+ case 504:
+ Status.enforceBackoff = true;
+ if (resp.status == 503 && resp.headers["retry-after"]) {
+ let retryAfter = resp.headers["retry-after"];
+ this._log.debug("Got Retry-After: " + retryAfter);
+ if (this.service.isLoggedIn) {
+ Status.sync = SERVER_MAINTENANCE;
+ } else {
+ Status.login = SERVER_MAINTENANCE;
+ }
+ Svc.Obs.notify("weave:service:backoff:interval",
+ parseInt(retryAfter, 10));
+ }
+ break;
+ }
+
+ switch (resp.result) {
+ case Cr.NS_ERROR_UNKNOWN_HOST:
+ case Cr.NS_ERROR_CONNECTION_REFUSED:
+ case Cr.NS_ERROR_NET_TIMEOUT:
+ case Cr.NS_ERROR_NET_RESET:
+ case Cr.NS_ERROR_NET_INTERRUPT:
+ case Cr.NS_ERROR_PROXY_CONNECTION_REFUSED:
+ // The constant says it's about login, but in fact it just
+ // indicates general network error.
+ if (this.service.isLoggedIn) {
+ Status.sync = LOGIN_FAILED_NETWORK_ERROR;
+ } else {
+ Status.login = LOGIN_FAILED_NETWORK_ERROR;
+ }
+ break;
+ }
+ },
+};
diff --git a/components/weave/src/sync/record.js b/components/weave/src/sync/record.js
new file mode 100644
index 000000000..5dc1c012c
--- /dev/null
+++ b/components/weave/src/sync/record.js
@@ -0,0 +1,628 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [
+ "WBORecord",
+ "RecordManager",
+ "CryptoWrapper",
+ "CollectionKeyManager",
+ "Collection",
+];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+const CRYPTO_COLLECTION = "crypto";
+const KEYS_WBO = "keys";
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/keys.js");
+Cu.import("resource://services-sync/resource.js");
+Cu.import("resource://services-sync/util.js");
+
+this.WBORecord = function WBORecord(collection, id) {
+ this.data = {};
+ this.payload = {};
+ this.collection = collection; // Optional.
+ this.id = id; // Optional.
+}
+WBORecord.prototype = {
+ _logName: "Sync.Record.WBO",
+
+ get sortindex() {
+ if (this.data.sortindex)
+ return this.data.sortindex;
+ return 0;
+ },
+
+ // Get thyself from your URI, then deserialize.
+ // Set thine 'response' field.
+ fetch: function fetch(resource) {
+ if (!(resource instanceof Resource)) {
+ throw new Error("First argument must be a Resource instance.");
+ }
+
+ let r = resource.get();
+ if (r.success) {
+ this.deserialize(r); // Warning! Muffles exceptions!
+ }
+ this.response = r;
+ return this;
+ },
+
+ upload: function upload(resource) {
+ if (!(resource instanceof Resource)) {
+ throw new Error("First argument must be a Resource instance.");
+ }
+
+ return resource.put(this);
+ },
+
+ // Take a base URI string, with trailing slash, and return the URI of this
+ // WBO based on collection and ID.
+ uri: function(base) {
+ if (this.collection && this.id) {
+ let url = Utils.makeURI(base + this.collection + "/" + this.id);
+ url.QueryInterface(Ci.nsIURL);
+ return url;
+ }
+ return null;
+ },
+
+ deserialize: function deserialize(json) {
+ this.data = json.constructor.toString() == String ? JSON.parse(json) : json;
+
+ try {
+ // The payload is likely to be JSON, but if not, keep it as a string
+ this.payload = JSON.parse(this.payload);
+ } catch(ex) {}
+ },
+
+ toJSON: function toJSON() {
+ // Copy fields from data to be stringified, making sure payload is a string
+ let obj = {};
+ for (let [key, val] of Object.entries(this.data))
+ obj[key] = key == "payload" ? JSON.stringify(val) : val;
+ if (this.ttl)
+ obj.ttl = this.ttl;
+ return obj;
+ },
+
+ toString: function toString() {
+ return "{ " +
+ "id: " + this.id + " " +
+ "index: " + this.sortindex + " " +
+ "modified: " + this.modified + " " +
+ "ttl: " + this.ttl + " " +
+ "payload: " + JSON.stringify(this.payload) +
+ " }";
+ }
+};
+
+Utils.deferGetSet(WBORecord, "data", ["id", "modified", "sortindex", "payload"]);
+
+this.CryptoWrapper = function CryptoWrapper(collection, id) {
+ this.cleartext = {};
+ WBORecord.call(this, collection, id);
+ this.ciphertext = null;
+ this.id = id;
+}
+CryptoWrapper.prototype = {
+ __proto__: WBORecord.prototype,
+ _logName: "Sync.Record.CryptoWrapper",
+
+ ciphertextHMAC: function ciphertextHMAC(keyBundle) {
+ let hasher = keyBundle.sha256HMACHasher;
+ if (!hasher) {
+ throw "Cannot compute HMAC without an HMAC key.";
+ }
+
+ return Utils.bytesAsHex(Utils.digestUTF8(this.ciphertext, hasher));
+ },
+
+ /*
+ * Don't directly use the sync key. Instead, grab a key for this
+ * collection, which is decrypted with the sync key.
+ *
+ * Cache those keys; invalidate the cache if the time on the keys collection
+ * changes, or other auth events occur.
+ *
+ * Optional key bundle overrides the collection key lookup.
+ */
+ encrypt: function encrypt(keyBundle) {
+ if (!keyBundle) {
+ throw new Error("A key bundle must be supplied to encrypt.");
+ }
+
+ this.IV = Svc.Crypto.generateRandomIV();
+ this.ciphertext = Svc.Crypto.encrypt(JSON.stringify(this.cleartext),
+ keyBundle.encryptionKeyB64, this.IV);
+ this.hmac = this.ciphertextHMAC(keyBundle);
+ this.cleartext = null;
+ },
+
+ // Optional key bundle.
+ decrypt: function decrypt(keyBundle) {
+ if (!this.ciphertext) {
+ throw "No ciphertext: nothing to decrypt?";
+ }
+
+ if (!keyBundle) {
+ throw new Error("A key bundle must be supplied to decrypt.");
+ }
+
+ // Authenticate the encrypted blob with the expected HMAC
+ let computedHMAC = this.ciphertextHMAC(keyBundle);
+
+ if (computedHMAC != this.hmac) {
+ Utils.throwHMACMismatch(this.hmac, computedHMAC);
+ }
+
+ // Handle invalid data here. Elsewhere we assume that cleartext is an object.
+ let cleartext = Svc.Crypto.decrypt(this.ciphertext,
+ keyBundle.encryptionKeyB64, this.IV);
+ let json_result = JSON.parse(cleartext);
+
+ if (json_result && (json_result instanceof Object)) {
+ this.cleartext = json_result;
+ this.ciphertext = null;
+ } else {
+ throw "Decryption failed: result is <" + json_result + ">, not an object.";
+ }
+
+ // Verify that the encrypted id matches the requested record's id.
+ if (this.cleartext.id != this.id)
+ throw "Record id mismatch: " + this.cleartext.id + " != " + this.id;
+
+ return this.cleartext;
+ },
+
+ toString: function toString() {
+ let payload = this.deleted ? "DELETED" : JSON.stringify(this.cleartext);
+
+ return "{ " +
+ "id: " + this.id + " " +
+ "index: " + this.sortindex + " " +
+ "modified: " + this.modified + " " +
+ "ttl: " + this.ttl + " " +
+ "payload: " + payload + " " +
+ "collection: " + (this.collection || "undefined") +
+ " }";
+ },
+
+ // The custom setter below masks the parent's getter, so explicitly call it :(
+ get id() WBORecord.prototype.__lookupGetter__("id").call(this),
+
+ // Keep both plaintext and encrypted versions of the id to verify integrity
+ set id(val) {
+ WBORecord.prototype.__lookupSetter__("id").call(this, val);
+ return this.cleartext.id = val;
+ },
+};
+
+Utils.deferGetSet(CryptoWrapper, "payload", ["ciphertext", "IV", "hmac"]);
+Utils.deferGetSet(CryptoWrapper, "cleartext", "deleted");
+
+/**
+ * An interface and caching layer for records.
+ */
+this.RecordManager = function RecordManager(service) {
+ this.service = service;
+
+ this._log = Log.repository.getLogger(this._logName);
+ this._records = {};
+}
+RecordManager.prototype = {
+ _recordType: CryptoWrapper,
+ _logName: "Sync.RecordManager",
+
+ import: function RecordMgr_import(url) {
+ this._log.trace("Importing record: " + (url.spec ? url.spec : url));
+ try {
+ // Clear out the last response with empty object if GET fails
+ this.response = {};
+ this.response = this.service.resource(url).get();
+
+ // Don't parse and save the record on failure
+ if (!this.response.success)
+ return null;
+
+ let record = new this._recordType(url);
+ record.deserialize(this.response);
+
+ return this.set(url, record);
+ } catch(ex) {
+ this._log.debug("Failed to import record: ", ex);
+ return null;
+ }
+ },
+
+ get: function RecordMgr_get(url) {
+ // Use a url string as the key to the hash
+ let spec = url.spec ? url.spec : url;
+ if (spec in this._records)
+ return this._records[spec];
+ return this.import(url);
+ },
+
+ set: function RecordMgr_set(url, record) {
+ let spec = url.spec ? url.spec : url;
+ return this._records[spec] = record;
+ },
+
+ contains: function RecordMgr_contains(url) {
+ if ((url.spec || url) in this._records)
+ return true;
+ return false;
+ },
+
+ clearCache: function recordMgr_clearCache() {
+ this._records = {};
+ },
+
+ del: function RecordMgr_del(url) {
+ delete this._records[url];
+ }
+};
+
+/**
+ * Keeps track of mappings between collection names ('tabs') and KeyBundles.
+ *
+ * You can update this thing simply by giving it /info/collections. It'll
+ * use the last modified time to bring itself up to date.
+ */
+this.CollectionKeyManager = function CollectionKeyManager() {
+ this.lastModified = 0;
+ this._collections = {};
+ this._default = null;
+
+ this._log = Log.repository.getLogger("Sync.CollectionKeyManager");
+}
+
+// TODO: persist this locally as an Identity. Bug 610913.
+// Note that the last modified time needs to be preserved.
+CollectionKeyManager.prototype = {
+
+ // Return information about old vs new keys:
+ // * same: true if two collections are equal
+ // * changed: an array of collection names that changed.
+ _compareKeyBundleCollections: function _compareKeyBundleCollections(m1, m2) {
+ let changed = [];
+
+ function process(m1, m2) {
+ for (let k1 in m1) {
+ let v1 = m1[k1];
+ let v2 = m2[k1];
+ if (!(v1 && v2 && v1.equals(v2)))
+ changed.push(k1);
+ }
+ }
+
+ // Diffs both ways.
+ process(m1, m2);
+ process(m2, m1);
+
+ // Return a sorted, unique array.
+ changed.sort();
+ let last;
+ changed = changed.filter(x => (x != last) && (last = x));
+ return {same: changed.length == 0,
+ changed: changed};
+ },
+
+ get isClear() {
+ return !this._default;
+ },
+
+ clear: function clear() {
+ this._log.info("Clearing collection keys...");
+ this.lastModified = 0;
+ this._collections = {};
+ this._default = null;
+ },
+
+ keyForCollection: function(collection) {
+ if (collection && this._collections[collection])
+ return this._collections[collection];
+
+ return this._default;
+ },
+
+ /**
+ * If `collections` (an array of strings) is provided, iterate
+ * over it and generate random keys for each collection.
+ * Create a WBO for the given data.
+ */
+ _makeWBO: function(collections, defaultBundle) {
+ let wbo = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
+ let c = {};
+ for (let k in collections) {
+ c[k] = collections[k].keyPairB64;
+ }
+ wbo.cleartext = {
+ "default": defaultBundle ? defaultBundle.keyPairB64 : null,
+ "collections": c,
+ "collection": CRYPTO_COLLECTION,
+ "id": KEYS_WBO
+ };
+ return wbo;
+ },
+
+ /**
+ * Create a WBO for the current keys.
+ */
+ asWBO: function(collection, id)
+ this._makeWBO(this._collections, this._default),
+
+ /**
+ * Compute a new default key, and new keys for any specified collections.
+ */
+ newKeys: function(collections) {
+ let newDefaultKey = new BulkKeyBundle(DEFAULT_KEYBUNDLE_NAME);
+ newDefaultKey.generateRandom();
+
+ let newColls = {};
+ if (collections) {
+ collections.forEach(function (c) {
+ let b = new BulkKeyBundle(c);
+ b.generateRandom();
+ newColls[c] = b;
+ });
+ }
+ return [newDefaultKey, newColls];
+ },
+
+ /**
+ * Generates new keys, but does not replace our local copy. Use this to
+ * verify an upload before storing.
+ */
+ generateNewKeysWBO: function(collections) {
+ let newDefaultKey, newColls;
+ [newDefaultKey, newColls] = this.newKeys(collections);
+
+ return this._makeWBO(newColls, newDefaultKey);
+ },
+
+ // Take the fetched info/collections WBO, checking the change
+ // time of the crypto collection.
+ updateNeeded: function(info_collections) {
+
+ this._log.info("Testing for updateNeeded. Last modified: " + this.lastModified);
+
+ // No local record of modification time? Need an update.
+ if (!this.lastModified)
+ return true;
+
+ // No keys on the server? We need an update, though our
+ // update handling will be a little more drastic...
+ if (!(CRYPTO_COLLECTION in info_collections))
+ return true;
+
+ // Otherwise, we need an update if our modification time is stale.
+ return (info_collections[CRYPTO_COLLECTION] > this.lastModified);
+ },
+
+ //
+ // Set our keys and modified time to the values fetched from the server.
+ // Returns one of three values:
+ //
+ // * If the default key was modified, return true.
+ // * If the default key was not modified, but per-collection keys were,
+ // return an array of such.
+ // * Otherwise, return false -- we were up-to-date.
+ //
+ setContents: function setContents(payload, modified) {
+
+ if (!modified)
+ throw "No modified time provided to setContents.";
+
+ let self = this;
+
+ this._log.info("Setting collection keys contents. Our last modified: " +
+ this.lastModified + ", input modified: " + modified + ".");
+
+ if (!payload)
+ throw "No payload in CollectionKeyManager.setContents().";
+
+ if (!payload.default) {
+ this._log.warn("No downloaded default key: this should not occur.");
+ this._log.warn("Not clearing local keys.");
+ throw "No default key in CollectionKeyManager.setContents(). Cannot proceed.";
+ }
+
+ // Process the incoming default key.
+ let b = new BulkKeyBundle(DEFAULT_KEYBUNDLE_NAME);
+ b.keyPairB64 = payload.default;
+ let newDefault = b;
+
+ // Process the incoming collections.
+ let newCollections = {};
+ if ("collections" in payload) {
+ this._log.info("Processing downloaded per-collection keys.");
+ let colls = payload.collections;
+ for (let k in colls) {
+ let v = colls[k];
+ if (v) {
+ let keyObj = new BulkKeyBundle(k);
+ keyObj.keyPairB64 = v;
+ if (keyObj) {
+ newCollections[k] = keyObj;
+ }
+ }
+ }
+ }
+
+ // Check to see if these are already our keys.
+ let sameDefault = (this._default && this._default.equals(newDefault));
+ let collComparison = this._compareKeyBundleCollections(newCollections, this._collections);
+ let sameColls = collComparison.same;
+
+ if (sameDefault && sameColls) {
+ self._log.info("New keys are the same as our old keys! Bumped local modified time.");
+ self.lastModified = modified;
+ return false;
+ }
+
+ // Make sure things are nice and tidy before we set.
+ this.clear();
+
+ this._log.info("Saving downloaded keys.");
+ this._default = newDefault;
+ this._collections = newCollections;
+
+ // Always trust the server.
+ self._log.info("Bumping last modified to " + modified);
+ self.lastModified = modified;
+
+ return sameDefault ? collComparison.changed : true;
+ },
+
+ updateContents: function updateContents(syncKeyBundle, storage_keys) {
+ let log = this._log;
+ log.info("Updating collection keys...");
+
+ // storage_keys is a WBO, fetched from storage/crypto/keys.
+ // Its payload is the default key, and a map of collections to keys.
+ // We lazily compute the key objects from the strings we're given.
+
+ let payload;
+ try {
+ payload = storage_keys.decrypt(syncKeyBundle);
+ } catch (ex) {
+ log.warn("Got exception \"" + ex + "\" decrypting storage keys with sync key.");
+ log.info("Aborting updateContents. Rethrowing.");
+ throw ex;
+ }
+
+ let r = this.setContents(payload, storage_keys.modified);
+ log.info("Collection keys updated.");
+ return r;
+ }
+}
+
+this.Collection = function Collection(uri, recordObj, service) {
+ if (!service) {
+ throw new Error("Collection constructor requires a service.");
+ }
+
+ Resource.call(this, uri);
+
+ // This is a bit hacky, but gets the job done.
+ let res = service.resource(uri);
+ this.authenticator = res.authenticator;
+
+ this._recordObj = recordObj;
+ this._service = service;
+
+ this._full = false;
+ this._ids = null;
+ this._limit = 0;
+ this._older = 0;
+ this._newer = 0;
+ this._data = [];
+}
+Collection.prototype = {
+ __proto__: Resource.prototype,
+ _logName: "Sync.Collection",
+
+ _rebuildURL: function Coll__rebuildURL() {
+ // XXX should consider what happens if it's not a URL...
+ this.uri.QueryInterface(Ci.nsIURL);
+
+ let args = [];
+ if (this.older)
+ args.push('older=' + this.older);
+ else if (this.newer) {
+ args.push('newer=' + this.newer);
+ }
+ if (this.full)
+ args.push('full=1');
+ if (this.sort)
+ args.push('sort=' + this.sort);
+ if (this.ids != null)
+ args.push("ids=" + this.ids);
+ if (this.limit > 0 && this.limit != Infinity)
+ args.push("limit=" + this.limit);
+
+ this.uri.query = (args.length > 0)? '?' + args.join('&') : '';
+ },
+
+ // get full items
+ get full() { return this._full; },
+ set full(value) {
+ this._full = value;
+ this._rebuildURL();
+ },
+
+ // Apply the action to a certain set of ids
+ get ids() this._ids,
+ set ids(value) {
+ this._ids = value;
+ this._rebuildURL();
+ },
+
+ // Limit how many records to get
+ get limit() this._limit,
+ set limit(value) {
+ this._limit = value;
+ this._rebuildURL();
+ },
+
+ // get only items modified before some date
+ get older() { return this._older; },
+ set older(value) {
+ this._older = value;
+ this._rebuildURL();
+ },
+
+ // get only items modified since some date
+ get newer() { return this._newer; },
+ set newer(value) {
+ this._newer = value;
+ this._rebuildURL();
+ },
+
+ // get items sorted by some criteria. valid values:
+ // oldest (oldest first)
+ // newest (newest first)
+ // index
+ get sort() { return this._sort; },
+ set sort(value) {
+ this._sort = value;
+ this._rebuildURL();
+ },
+
+ pushData: function Coll_pushData(data) {
+ this._data.push(data);
+ },
+
+ clearRecords: function Coll_clearRecords() {
+ this._data = [];
+ },
+
+ set recordHandler(onRecord) {
+ // Save this because onProgress is called with this as the ChannelListener
+ let coll = this;
+
+ // Switch to newline separated records for incremental parsing
+ coll.setHeader("Accept", "application/newlines");
+
+ this._onProgress = function() {
+ let newline;
+ while ((newline = this._data.indexOf("\n")) > 0) {
+ // Split the json record from the rest of the data
+ let json = this._data.slice(0, newline);
+ this._data = this._data.slice(newline + 1);
+
+ // Deserialize a record from json and give it to the callback
+ let record = new coll._recordObj();
+ record.deserialize(json);
+ onRecord(record);
+ }
+ };
+ },
+};
diff --git a/components/weave/src/sync/resource.js b/components/weave/src/sync/resource.js
new file mode 100644
index 000000000..f670a42a9
--- /dev/null
+++ b/components/weave/src/sync/resource.js
@@ -0,0 +1,678 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [
+ "AsyncResource",
+ "Resource"
+];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Async.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/util.js");
+
+const DEFAULT_LOAD_FLAGS =
+ // Always validate the cache:
+ Ci.nsIRequest.LOAD_BYPASS_CACHE |
+ Ci.nsIRequest.INHIBIT_CACHING |
+ // Don't send user cookies over the wire (Bug 644734).
+ Ci.nsIRequest.LOAD_ANONYMOUS;
+
+/*
+ * AsyncResource represents a remote network resource, identified by a URI.
+ * Create an instance like so:
+ *
+ * let resource = new AsyncResource("http://foobar.com/path/to/resource");
+ *
+ * The 'resource' object has the following methods to issue HTTP requests
+ * of the corresponding HTTP methods:
+ *
+ * get(callback)
+ * put(data, callback)
+ * post(data, callback)
+ * delete(callback)
+ *
+ * 'callback' is a function with the following signature:
+ *
+ * function callback(error, result) {...}
+ *
+ * 'error' will be null on successful requests. Likewise, result will not be
+ * passed (=undefined) when an error occurs. Note that this is independent of
+ * the status of the HTTP response.
+ */
+this.AsyncResource = function AsyncResource(uri) {
+ this._log = Log.repository.getLogger(this._logName);
+ this._log.level =
+ Log.Level[Svc.Prefs.get("log.logger.network.resources")];
+ this.uri = uri;
+ this._headers = {};
+ this._onComplete = Utils.bind2(this, this._onComplete);
+}
+AsyncResource.prototype = {
+ _logName: "Sync.AsyncResource",
+
+ // ** {{{ AsyncResource.serverTime }}} **
+ //
+ // Caches the latest server timestamp (X-Weave-Timestamp header).
+ serverTime: null,
+
+ /**
+ * Callback to be invoked at request time to add authentication details.
+ *
+ * By default, a global authenticator is provided. If this is set, it will
+ * be used instead of the global one.
+ */
+ authenticator: null,
+
+ // The string to use as the base User-Agent in Sync requests.
+ // These strings will look something like
+ //
+ // Firefox/4.0 FxSync/1.8.0.20100101.mobile
+ //
+ // or
+ //
+ // Firefox Aurora/5.0a1 FxSync/1.9.0.20110409.desktop
+ //
+ _userAgent:
+ Services.appinfo.name + "/" + Services.appinfo.version + // Product.
+ " FxSync/" + WEAVE_VERSION + "." + // Sync.
+ Services.appinfo.appBuildID + ".", // Build.
+
+ // Wait 5 minutes before killing a request.
+ ABORT_TIMEOUT: 300000,
+
+ // ** {{{ AsyncResource.headers }}} **
+ //
+ // Headers to be included when making a request for the resource.
+ // Note: Header names should be all lower case, there's no explicit
+ // check for duplicates due to case!
+ get headers() {
+ return this._headers;
+ },
+ set headers(value) {
+ this._headers = value;
+ },
+ setHeader: function Res_setHeader(header, value) {
+ this._headers[header.toLowerCase()] = value;
+ },
+ get headerNames() {
+ return Object.keys(this.headers);
+ },
+
+ // ** {{{ AsyncResource.uri }}} **
+ //
+ // URI representing this resource.
+ get uri() {
+ return this._uri;
+ },
+ set uri(value) {
+ if (typeof value == 'string')
+ this._uri = CommonUtils.makeURI(value);
+ else
+ this._uri = value;
+ },
+
+ // ** {{{ AsyncResource.spec }}} **
+ //
+ // Get the string representation of the URI.
+ get spec() {
+ if (this._uri)
+ return this._uri.spec;
+ return null;
+ },
+
+ // ** {{{ AsyncResource.data }}} **
+ //
+ // Get and set the data encapulated in the resource.
+ _data: null,
+ get data() this._data,
+ set data(value) {
+ this._data = value;
+ },
+
+ // ** {{{ AsyncResource._createRequest }}} **
+ //
+ // This method returns a new IO Channel for requests to be made
+ // through. It is never called directly, only {{{_doRequest}}} uses it
+ // to obtain a request channel.
+ //
+ _createRequest: function Res__createRequest(method) {
+ let channel = Services.io.newChannel2(this.spec,
+ null,
+ null,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_NORMAL,
+ Ci.nsIContentPolicy.TYPE_OTHER)
+ .QueryInterface(Ci.nsIRequest)
+ .QueryInterface(Ci.nsIHttpChannel);
+
+ channel.loadFlags |= DEFAULT_LOAD_FLAGS;
+
+ // Setup a callback to handle channel notifications.
+ let listener = new ChannelNotificationListener(this.headerNames);
+ channel.notificationCallbacks = listener;
+
+ // Compose a UA string fragment from the various available identifiers.
+ if (Svc.Prefs.get("sendVersionInfo", true)) {
+ let ua = this._userAgent + Svc.Prefs.get("client.type", "desktop");
+ channel.setRequestHeader("user-agent", ua, false);
+ }
+
+ let headers = this.headers;
+
+ if (this.authenticator) {
+ let result = this.authenticator(this, method);
+ if (result && result.headers) {
+ for (let [k, v] of Object.entries(result.headers)) {
+ headers[k.toLowerCase()] = v;
+ }
+ }
+ } else {
+ this._log.debug("No authenticator found.");
+ }
+
+ for (let [key, value] of Object.entries(headers)) {
+ if (key == 'authorization')
+ this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
+ else
+ this._log.trace("HTTP Header " + key + ": " + headers[key]);
+ channel.setRequestHeader(key, headers[key], false);
+ }
+ return channel;
+ },
+
+ _onProgress: function Res__onProgress(channel) {},
+
+ _doRequest: function _doRequest(action, data, callback) {
+ this._log.trace("In _doRequest.");
+ this._callback = callback;
+ let channel = this._createRequest(action);
+
+ if ("undefined" != typeof(data))
+ this._data = data;
+
+ // PUT and POST are treated differently because they have payload data.
+ if ("PUT" == action || "POST" == action) {
+ // Convert non-string bodies into JSON
+ if (this._data.constructor.toString() != String)
+ this._data = JSON.stringify(this._data);
+
+ this._log.debug(action + " Length: " + this._data.length);
+ this._log.trace(action + " Body: " + this._data);
+
+ let type = ('content-type' in this._headers) ?
+ this._headers['content-type'] : 'text/plain';
+
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stream.setData(this._data, this._data.length);
+
+ channel.QueryInterface(Ci.nsIUploadChannel);
+ channel.setUploadStream(stream, type, this._data.length);
+ }
+
+ // Setup a channel listener so that the actual network operation
+ // is performed asynchronously.
+ let listener = new ChannelListener(this._onComplete, this._onProgress,
+ this._log, this.ABORT_TIMEOUT);
+ channel.requestMethod = action;
+ try {
+ channel.asyncOpen(listener, null);
+ } catch (ex) {
+ // asyncOpen can throw in a bunch of cases -- e.g., a forbidden port.
+ this._log.warn("Caught an error in asyncOpen: ", ex);
+ CommonUtils.nextTick(callback.bind(this, ex));
+ }
+ },
+
+ _onComplete: function _onComplete(error, data, channel) {
+ this._log.trace("In _onComplete. Error is " + error + ".");
+
+ if (error) {
+ this._callback(error);
+ return;
+ }
+
+ this._data = data;
+ let action = channel.requestMethod;
+
+ this._log.trace("Channel: " + channel);
+ this._log.trace("Action: " + action);
+
+ // Process status and success first. This way a problem with headers
+ // doesn't fail to include accurate status information.
+ let status = 0;
+ let success = false;
+
+ try {
+ status = channel.responseStatus;
+ success = channel.requestSucceeded; // HTTP status.
+
+ this._log.trace("Status: " + status);
+ this._log.trace("Success: " + success);
+
+ // Log the status of the request.
+ let mesg = [action, success ? "success" : "fail", status,
+ channel.URI.spec].join(" ");
+ this._log.debug("mesg: " + mesg);
+
+ if (mesg.length > 200)
+ mesg = mesg.substr(0, 200) + "…";
+ this._log.debug(mesg);
+
+ // Additionally give the full response body when Trace logging.
+ if (this._log.level <= Log.Level.Trace)
+ this._log.trace(action + " body: " + data);
+
+ } catch(ex) {
+ // Got a response, but an exception occurred during processing.
+ // This shouldn't occur.
+ this._log.warn("Caught unexpected exception in _onComplete. ", ex);
+ this._log.debug(CommonUtils.stackTrace(ex));
+ }
+
+ // Process headers. They can be empty, or the call can otherwise fail, so
+ // put this in its own try block.
+ let headers = {};
+ try {
+ this._log.trace("Processing response headers.");
+
+ // Read out the response headers if available.
+ channel.visitResponseHeaders({
+ visitHeader: function visitHeader(header, value) {
+ headers[header.toLowerCase()] = value;
+ }
+ });
+
+ // This is a server-side safety valve to allow slowing down
+ // clients without hurting performance.
+ if (headers["x-weave-backoff"]) {
+ let backoff = headers["x-weave-backoff"];
+ this._log.debug("Got X-Weave-Backoff: " + backoff);
+ Observers.notify("weave:service:backoff:interval",
+ parseInt(backoff, 10));
+ }
+
+ if (success && headers["x-weave-quota-remaining"]) {
+ Observers.notify("weave:service:quota:remaining",
+ parseInt(headers["x-weave-quota-remaining"], 10));
+ }
+
+ let contentLength = headers["content-length"];
+ if (success && contentLength && data &&
+ contentLength != data.length) {
+ this._log.warn("The response body's length of: " + data.length +
+ " doesn't match the header's content-length of: " +
+ contentLength + ".");
+ }
+ } catch (ex) {
+ this._log.debug("Caught exception visiting headers in _onComplete", ex);
+ this._log.debug(CommonUtils.stackTrace(ex));
+ }
+
+ let ret = new String(data);
+ ret.status = status;
+ ret.success = success;
+ ret.headers = headers;
+
+ // Make a lazy getter to convert the json response into an object.
+ // Note that this can cause a parse error to be thrown far away from the
+ // actual fetch, so be warned!
+ XPCOMUtils.defineLazyGetter(ret, "obj", function() {
+ try {
+ return JSON.parse(ret);
+ } catch (ex) {
+ this._log.warn("Got exception parsing response body", ex);
+ // Stringify to avoid possibly printing non-printable characters.
+ this._log.debug("Parse fail: Response body starts: \"" +
+ JSON.stringify((ret + "").slice(0, 100)) +
+ "\".");
+ throw ex;
+ }
+ }.bind(this));
+
+ this._callback(null, ret);
+ },
+
+ get: function get(callback) {
+ this._doRequest("GET", undefined, callback);
+ },
+
+ put: function put(data, callback) {
+ if (typeof data == "function")
+ [data, callback] = [undefined, data];
+ this._doRequest("PUT", data, callback);
+ },
+
+ post: function post(data, callback) {
+ if (typeof data == "function")
+ [data, callback] = [undefined, data];
+ this._doRequest("POST", data, callback);
+ },
+
+ delete: function delete_(callback) {
+ this._doRequest("DELETE", undefined, callback);
+ }
+};
+
+
+/*
+ * Represent a remote network resource, identified by a URI, with a
+ * synchronous API.
+ *
+ * 'Resource' is not recommended for new code. Use the asynchronous API of
+ * 'AsyncResource' instead.
+ */
+this.Resource = function Resource(uri) {
+ AsyncResource.call(this, uri);
+}
+Resource.prototype = {
+
+ __proto__: AsyncResource.prototype,
+
+ _logName: "Sync.Resource",
+
+ // ** {{{ Resource._request }}} **
+ //
+ // Perform a particular HTTP request on the resource. This method
+ // is never called directly, but is used by the high-level
+ // {{{get}}}, {{{put}}}, {{{post}}} and {{delete}} methods.
+ _request: function Res__request(action, data) {
+ let cb = Async.makeSyncCallback();
+ function callback(error, ret) {
+ if (error)
+ cb.throw(error);
+ else
+ cb(ret);
+ }
+
+ // The channel listener might get a failure code
+ try {
+ this._doRequest(action, data, callback);
+ return Async.waitForSyncCallback(cb);
+ } catch(ex) {
+ // Combine the channel stack with this request stack. Need to create
+ // a new error object for that.
+ let error = Error(ex.message);
+ error.result = ex.result;
+ let chanStack = [];
+ if (ex.stack)
+ chanStack = ex.stack.trim().split(/\n/).slice(1);
+ let requestStack = error.stack.split(/\n/).slice(1);
+
+ // Strip out the args for the last 2 frames because they're usually HUGE!
+ for (let i = 0; i <= 1; i++)
+ requestStack[i] = requestStack[i].replace(/\(".*"\)@/, "(...)@");
+
+ error.stack = chanStack.concat(requestStack).join("\n");
+ throw error;
+ }
+ },
+
+ // ** {{{ Resource.get }}} **
+ //
+ // Perform an asynchronous HTTP GET for this resource.
+ get: function Res_get() {
+ return this._request("GET");
+ },
+
+ // ** {{{ Resource.put }}} **
+ //
+ // Perform a HTTP PUT for this resource.
+ put: function Res_put(data) {
+ return this._request("PUT", data);
+ },
+
+ // ** {{{ Resource.post }}} **
+ //
+ // Perform a HTTP POST for this resource.
+ post: function Res_post(data) {
+ return this._request("POST", data);
+ },
+
+ // ** {{{ Resource.delete }}} **
+ //
+ // Perform a HTTP DELETE for this resource.
+ delete: function Res_delete() {
+ return this._request("DELETE");
+ }
+};
+
+// = ChannelListener =
+//
+// This object implements the {{{nsIStreamListener}}} interface
+// and is called as the network operation proceeds.
+function ChannelListener(onComplete, onProgress, logger, timeout) {
+ this._onComplete = onComplete;
+ this._onProgress = onProgress;
+ this._log = logger;
+ this._timeout = timeout;
+ this.delayAbort();
+}
+ChannelListener.prototype = {
+
+ onStartRequest: function Channel_onStartRequest(channel) {
+ this._log.trace("onStartRequest called for channel " + channel + ".");
+
+ try {
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (ex) {
+ this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
+ channel.cancel(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+
+ // Save the latest server timestamp when possible.
+ try {
+ AsyncResource.serverTime = channel.getResponseHeader("X-Weave-Timestamp") - 0;
+ }
+ catch(ex) {}
+
+ this._log.trace("onStartRequest: " + channel.requestMethod + " " +
+ channel.URI.spec);
+ this._data = '';
+ this.delayAbort();
+ },
+
+ onStopRequest: function Channel_onStopRequest(channel, context, status) {
+ // Clear the abort timer now that the channel is done.
+ this.abortTimer.clear();
+
+ if (!this._onComplete) {
+ this._log.error("Unexpected error: _onComplete not defined in onStopRequest.");
+ this._onProgress = null;
+ return;
+ }
+
+ try {
+ channel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (ex) {
+ this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
+
+ this._onComplete(ex, this._data, channel);
+ this._onComplete = this._onProgress = null;
+ return;
+ }
+
+ let statusSuccess = Components.isSuccessCode(status);
+ let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
+ this._log.trace("Channel for " + channel.requestMethod + " " + uri + ": " +
+ "isSuccessCode(" + status + ")? " + statusSuccess);
+
+ if (this._data == '') {
+ this._data = null;
+ }
+
+ // Pass back the failure code and stop execution. Use Components.Exception()
+ // instead of Error() so the exception is QI-able and can be passed across
+ // XPCOM borders while preserving the status code.
+ if (!statusSuccess) {
+ let message = Components.Exception("", status).name;
+ let error = Components.Exception(message, status);
+
+ this._onComplete(error, undefined, channel);
+ this._onComplete = this._onProgress = null;
+ return;
+ }
+
+ this._log.trace("Channel: flags = " + channel.loadFlags +
+ ", URI = " + uri +
+ ", HTTP success? " + channel.requestSucceeded);
+ this._onComplete(null, this._data, channel);
+ this._onComplete = this._onProgress = null;
+ },
+
+ onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) {
+ let siStream;
+ try {
+ siStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+ siStream.init(stream);
+ } catch (ex) {
+ this._log.warn("Exception creating nsIScriptableInputStream", ex);
+ this._log.debug("Parameters: " + req.URI.spec + ", " + stream + ", " + off + ", " + count);
+ // Cannot proceed, so rethrow and allow the channel to cancel itself.
+ throw ex;
+ }
+
+ try {
+ this._data += siStream.read(count);
+ } catch (ex) {
+ this._log.warn("Exception thrown reading " + count + " bytes from " + siStream + ".");
+ throw ex;
+ }
+
+ try {
+ this._onProgress();
+ } catch (ex) {
+ this._log.warn("Got exception calling onProgress handler during fetch of "
+ + req.URI.spec, ex);
+ this._log.trace("Rethrowing; expect a failure code from the HTTP channel.");
+ throw ex;
+ }
+
+ this.delayAbort();
+ },
+
+ /**
+ * Create or push back the abort timer that kills this request.
+ */
+ delayAbort: function delayAbort() {
+ try {
+ CommonUtils.namedTimer(this.abortRequest, this._timeout, this, "abortTimer");
+ } catch (ex) {
+ this._log.warn("Got exception extending abort timer: ", ex);
+ }
+ },
+
+ abortRequest: function abortRequest() {
+ // Ignore any callbacks if we happen to get any now
+ this.onStopRequest = function() {};
+ let error = Components.Exception("Aborting due to channel inactivity.",
+ Cr.NS_ERROR_NET_TIMEOUT);
+ if (!this._onComplete) {
+ this._log.error("Unexpected error: _onComplete not defined in " +
+ "abortRequest.");
+ return;
+ }
+ this._onComplete(error);
+ }
+};
+
+/**
+ * This class handles channel notification events.
+ *
+ * An instance of this class is bound to each created channel.
+ *
+ * Optionally pass an array of header names. Each header named
+ * in this array will be copied between the channels in the
+ * event of a redirect.
+ */
+function ChannelNotificationListener(headersToCopy) {
+ this._headersToCopy = headersToCopy;
+
+ this._log = Log.repository.getLogger(this._logName);
+ this._log.level = Log.Level[Svc.Prefs.get("log.logger.network.resources")];
+}
+ChannelNotificationListener.prototype = {
+ _logName: "Sync.Resource",
+
+ getInterface: function(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIBadCertListener2) ||
+ aIID.equals(Ci.nsIInterfaceRequestor) ||
+ aIID.equals(Ci.nsISupports) ||
+ aIID.equals(Ci.nsIChannelEventSink))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ notifyCertProblem: function certProblem(socketInfo, sslStatus, targetHost) {
+ let log = Log.repository.getLogger("Sync.CertListener");
+ log.warn("Invalid HTTPS certificate encountered!");
+
+ // This suppresses the UI warning only. The request is still cancelled.
+ return true;
+ },
+
+ asyncOnChannelRedirect:
+ function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
+
+ let oldSpec = (oldChannel && oldChannel.URI) ? oldChannel.URI.spec : "<undefined>";
+ let newSpec = (newChannel && newChannel.URI) ? newChannel.URI.spec : "<undefined>";
+ this._log.debug("Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags);
+
+ this._log.debug("Ensuring load flags are set.");
+ newChannel.loadFlags |= DEFAULT_LOAD_FLAGS;
+
+ // For internal redirects, copy the headers that our caller set.
+ try {
+ if ((flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL) &&
+ newChannel.URI.equals(oldChannel.URI)) {
+ this._log.debug("Copying headers for safe internal redirect.");
+
+ // QI the channel so we can set headers on it.
+ try {
+ newChannel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (ex) {
+ this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
+ throw ex;
+ }
+
+ for (let header of this._headersToCopy) {
+ let value = oldChannel.getRequestHeader(header);
+ if (value) {
+ let printed = (header == "authorization") ? "****" : value;
+ this._log.debug("Header: " + header + " = " + printed);
+ newChannel.setRequestHeader(header, value, false);
+ } else {
+ this._log.warn("No value for header " + header);
+ }
+ }
+ }
+ } catch (ex) {
+ this._log.error("Error copying headers: ", ex);
+ }
+
+ // We let all redirects proceed.
+ try {
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ } catch (ex) {
+ this._log.error("onRedirectVerifyCallback threw!" + CommonUtils.exceptionStr(ex));
+ }
+ }
+};
diff --git a/components/weave/src/sync/rest.js b/components/weave/src/sync/rest.js
new file mode 100644
index 000000000..106ece222
--- /dev/null
+++ b/components/weave/src/sync/rest.js
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/Log.jsm");
+Cu.import("resource://services-common/rest.js");
+Cu.import("resource://services-sync/util.js");
+Cu.import("resource://services-sync/constants.js");
+
+this.EXPORTED_SYMBOLS = ["SyncStorageRequest"];
+
+const STORAGE_REQUEST_TIMEOUT = 5 * 60; // 5 minutes
+
+/**
+ * RESTRequest variant for use against a Sync storage server.
+ */
+this.SyncStorageRequest = function SyncStorageRequest(uri) {
+ RESTRequest.call(this, uri);
+
+ this.authenticator = null;
+}
+SyncStorageRequest.prototype = {
+
+ __proto__: RESTRequest.prototype,
+
+ _logName: "Sync.StorageRequest",
+
+ /**
+ * The string to use as the base User-Agent in Sync requests.
+ * These strings will look something like
+ *
+ * Firefox/4.0 FxSync/1.8.0.20100101.mobile
+ *
+ * or
+ *
+ * Firefox Aurora/5.0a1 FxSync/1.9.0.20110409.desktop
+ */
+ userAgent:
+ Services.appinfo.name + "/" + Services.appinfo.version + // Product.
+ " FxSync/" + WEAVE_VERSION + "." + // Sync.
+ Services.appinfo.appBuildID + ".", // Build.
+
+ /**
+ * Wait 5 minutes before killing a request.
+ */
+ timeout: STORAGE_REQUEST_TIMEOUT,
+
+ dispatch: function dispatch(method, data, onComplete, onProgress) {
+ // Compose a UA string fragment from the various available identifiers.
+ if (Svc.Prefs.get("sendVersionInfo", true)) {
+ let ua = this.userAgent + Svc.Prefs.get("client.type", "desktop");
+ this.setHeader("user-agent", ua);
+ }
+
+ if (this.authenticator) {
+ this.authenticator(this);
+ } else {
+ this._log.debug("No authenticator found.");
+ }
+
+ return RESTRequest.prototype.dispatch.apply(this, arguments);
+ },
+
+ onStartRequest: function onStartRequest(channel) {
+ RESTRequest.prototype.onStartRequest.call(this, channel);
+ if (this.status == this.ABORTED) {
+ return;
+ }
+
+ let headers = this.response.headers;
+ // Save the latest server timestamp when possible.
+ if (headers["x-weave-timestamp"]) {
+ SyncStorageRequest.serverTime = parseFloat(headers["x-weave-timestamp"]);
+ }
+
+ // This is a server-side safety valve to allow slowing down
+ // clients without hurting performance.
+ if (headers["x-weave-backoff"]) {
+ Svc.Obs.notify("weave:service:backoff:interval",
+ parseInt(headers["x-weave-backoff"], 10));
+ }
+
+ if (this.response.success && headers["x-weave-quota-remaining"]) {
+ Svc.Obs.notify("weave:service:quota:remaining",
+ parseInt(headers["x-weave-quota-remaining"], 10));
+ }
+ },
+
+ onStopRequest: function onStopRequest(channel, context, statusCode) {
+ if (this.status != this.ABORTED) {
+ let resp = this.response;
+ let contentLength = resp.headers ? resp.headers["content-length"] : "";
+
+ if (resp.success && contentLength &&
+ contentLength != resp.body.length) {
+ this._log.warn("The response body's length of: " + resp.body.length +
+ " doesn't match the header's content-length of: " +
+ contentLength + ".");
+ }
+ }
+
+ RESTRequest.prototype.onStopRequest.apply(this, arguments);
+ }
+};
diff --git a/components/weave/src/sync/service.js b/components/weave/src/sync/service.js
new file mode 100644
index 000000000..804eb20cd
--- /dev/null
+++ b/components/weave/src/sync/service.js
@@ -0,0 +1,1586 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["Service"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+// How long before refreshing the cluster
+const CLUSTER_BACKOFF = 5 * 60 * 1000; // 5 minutes
+
+// How long a key to generate from an old passphrase.
+const PBKDF2_KEY_BYTES = 16;
+
+const CRYPTO_COLLECTION = "crypto";
+const KEYS_WBO = "keys";
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://services-sync/engines.js");
+Cu.import("resource://services-sync/engines/clients.js");
+Cu.import("resource://services-sync/identity.js");
+Cu.import("resource://services-sync/policies.js");
+Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/resource.js");
+Cu.import("resource://services-sync/rest.js");
+Cu.import("resource://services-sync/stages/enginesync.js");
+Cu.import("resource://services-sync/stages/declined.js");
+Cu.import("resource://services-sync/status.js");
+Cu.import("resource://services-sync/userapi.js");
+Cu.import("resource://services-sync/util.js");
+
+const ENGINE_MODULES = {
+ Addons: "addons.js",
+ Bookmarks: "bookmarks.js",
+ Form: "forms.js",
+ History: "history.js",
+ Password: "passwords.js",
+ Prefs: "prefs.js",
+ Tab: "tabs.js",
+};
+
+const STORAGE_INFO_TYPES = [INFO_COLLECTIONS,
+ INFO_COLLECTION_USAGE,
+ INFO_COLLECTION_COUNTS,
+ INFO_QUOTA];
+
+function Sync11Service() {
+ this._notify = Utils.notify("weave:service:");
+}
+Sync11Service.prototype = {
+
+ _lock: Utils.lock,
+ _locked: false,
+ _loggedIn: false,
+
+ infoURL: null,
+ storageURL: null,
+ metaURL: null,
+ cryptoKeyURL: null,
+
+ get serverURL() Svc.Prefs.get("serverURL"),
+ set serverURL(value) {
+ if (!value.endsWith("/")) {
+ value += "/";
+ }
+
+ // Only do work if it's actually changing
+ if (value == this.serverURL)
+ return;
+
+ // A new server most likely uses a different cluster, so clear that
+ Svc.Prefs.set("serverURL", value);
+ Svc.Prefs.reset("clusterURL");
+ },
+
+ get clusterURL() Svc.Prefs.get("clusterURL", ""),
+ set clusterURL(value) {
+ Svc.Prefs.set("clusterURL", value);
+ this._updateCachedURLs();
+ },
+
+ get miscAPI() {
+ // Append to the serverURL if it's a relative fragment
+ let misc = Svc.Prefs.get("miscURL");
+ if (misc.indexOf(":") == -1)
+ misc = this.serverURL + misc;
+ return misc + MISC_API_VERSION + "/";
+ },
+
+ /**
+ * The URI of the User API service.
+ *
+ * This is the base URI of the service as applicable to all users up to
+ * and including the server version path component, complete with trailing
+ * forward slash.
+ */
+ get userAPIURI() {
+ // Append to the serverURL if it's a relative fragment.
+ let url = Svc.Prefs.get("userURL");
+ if (!url.includes(":")) {
+ url = this.serverURL + url;
+ }
+
+ return url + USER_API_VERSION + "/";
+ },
+
+ get pwResetURL() {
+ return this.serverURL + "weave-password-reset";
+ },
+
+ get syncID() {
+ // Generate a random syncID id we don't have one
+ let syncID = Svc.Prefs.get("client.syncID", "");
+ return syncID == "" ? this.syncID = Utils.makeGUID() : syncID;
+ },
+ set syncID(value) {
+ Svc.Prefs.set("client.syncID", value);
+ },
+
+ get isLoggedIn() { return this._loggedIn; },
+
+ get locked() { return this._locked; },
+ lock: function lock() {
+ if (this._locked)
+ return false;
+ this._locked = true;
+ return true;
+ },
+ unlock: function unlock() {
+ this._locked = false;
+ },
+
+ // A specialized variant of Utils.catch.
+ // This provides a more informative error message when we're already syncing:
+ // see Bug 616568.
+ _catch: function _catch(func) {
+ function lockExceptions(ex) {
+ if (Utils.isLockException(ex)) {
+ // This only happens if we're syncing already.
+ this._log.info("Cannot start sync: already syncing?");
+ }
+ }
+
+ return Utils.catch.call(this, func, lockExceptions);
+ },
+
+ get userBaseURL() {
+ if (!this._clusterManager) {
+ return null;
+ }
+ return this._clusterManager.getUserBaseURL();
+ },
+
+ _updateCachedURLs: function _updateCachedURLs() {
+ // Nothing to cache yet if we don't have the building blocks
+ if (!this.clusterURL || !this.identity.username)
+ return;
+
+ this._log.debug("Caching URLs under storage user base: " + this.userBaseURL);
+
+ // Generate and cache various URLs under the storage API for this user
+ this.infoURL = this.userBaseURL + "info/collections";
+ this.storageURL = this.userBaseURL + "storage/";
+ this.metaURL = this.storageURL + "meta/global";
+ this.cryptoKeysURL = this.storageURL + CRYPTO_COLLECTION + "/" + KEYS_WBO;
+ },
+
+ _checkCrypto: function _checkCrypto() {
+ let ok = false;
+
+ try {
+ let iv = Svc.Crypto.generateRandomIV();
+ if (iv.length == 24)
+ ok = true;
+
+ } catch (e) {
+ this._log.debug("Crypto check failed: " + e);
+ }
+
+ return ok;
+ },
+
+ /**
+ * Here is a disgusting yet reasonable way of handling HMAC errors deep in
+ * the guts of Sync. The astute reader will note that this is a hacky way of
+ * implementing something like continuable conditions.
+ *
+ * A handler function is glued to each engine. If the engine discovers an
+ * HMAC failure, we fetch keys from the server and update our keys, just as
+ * we would on startup.
+ *
+ * If our key collection changed, we signal to the engine (via our return
+ * value) that it should retry decryption.
+ *
+ * If our key collection did not change, it means that we already had the
+ * correct keys... and thus a different client has the wrong ones. Reupload
+ * the bundle that we fetched, which will bump the modified time on the
+ * server and (we hope) prompt a broken client to fix itself.
+ *
+ * We keep track of the time at which we last applied this reasoning, because
+ * thrashing doesn't solve anything. We keep a reasonable interval between
+ * these remedial actions.
+ */
+ lastHMACEvent: 0,
+
+ /*
+ * Returns whether to try again.
+ */
+ handleHMACEvent: function handleHMACEvent() {
+ let now = Date.now();
+
+ // Leave a sizable delay between HMAC recovery attempts. This gives us
+ // time for another client to fix themselves if we touch the record.
+ if ((now - this.lastHMACEvent) < HMAC_EVENT_INTERVAL)
+ return false;
+
+ this._log.info("Bad HMAC event detected. Attempting recovery " +
+ "or signaling to other clients.");
+
+ // Set the last handled time so that we don't act again.
+ this.lastHMACEvent = now;
+
+ // Fetch keys.
+ let cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
+ try {
+ let cryptoResp = cryptoKeys.fetch(this.resource(this.cryptoKeysURL)).response;
+
+ // Save out the ciphertext for when we reupload. If there's a bug in
+ // CollectionKeyManager, this will prevent us from uploading junk.
+ let cipherText = cryptoKeys.ciphertext;
+
+ if (!cryptoResp.success) {
+ this._log.warn("Failed to download keys.");
+ return false;
+ }
+
+ let keysChanged = this.handleFetchedKeys(this.identity.syncKeyBundle,
+ cryptoKeys, true);
+ if (keysChanged) {
+ // Did they change? If so, carry on.
+ this._log.info("Suggesting retry.");
+ return true; // Try again.
+ }
+
+ // If not, reupload them and continue the current sync.
+ cryptoKeys.ciphertext = cipherText;
+ cryptoKeys.cleartext = null;
+
+ let uploadResp = cryptoKeys.upload(this.resource(this.cryptoKeysURL));
+ if (uploadResp.success)
+ this._log.info("Successfully re-uploaded keys. Continuing sync.");
+ else
+ this._log.warn("Got error response re-uploading keys. " +
+ "Continuing sync; let's try again later.");
+
+ return false; // Don't try again: same keys.
+
+ } catch (ex) {
+ this._log.warn("Got exception \"" + ex + "\" fetching and handling " +
+ "crypto keys. Will try again later.");
+ return false;
+ }
+ },
+
+ handleFetchedKeys: function handleFetchedKeys(syncKey, cryptoKeys, skipReset) {
+ // Don't want to wipe if we're just starting up!
+ let wasBlank = this.collectionKeys.isClear;
+ let keysChanged = this.collectionKeys.updateContents(syncKey, cryptoKeys);
+
+ if (keysChanged && !wasBlank) {
+ this._log.debug("Keys changed: " + JSON.stringify(keysChanged));
+
+ if (!skipReset) {
+ this._log.info("Resetting client to reflect key change.");
+
+ if (keysChanged.length) {
+ // Collection keys only. Reset individual engines.
+ this.resetClient(keysChanged);
+ }
+ else {
+ // Default key changed: wipe it all.
+ this.resetClient();
+ }
+
+ this._log.info("Downloaded new keys, client reset. Proceeding.");
+ }
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Prepare to initialize the rest of Weave after waiting a little bit
+ */
+ onStartup: function onStartup() {
+ this._migratePrefs();
+
+ // Status is instantiated before us and is the first to grab an instance of
+ // the IdentityManager. We use that instance because IdentityManager really
+ // needs to be a singleton. Ideally, the longer-lived object would spawn
+ // this service instance.
+ if (!Status || !Status._authManager) {
+ throw new Error("Status or Status._authManager not initialized.");
+ }
+
+ this.status = Status;
+ this.identity = Status._authManager;
+ this.collectionKeys = new CollectionKeyManager();
+
+ this.errorHandler = new ErrorHandler(this);
+
+ this._log = Log.repository.getLogger("Sync.Service");
+ this._log.level =
+ Log.Level[Svc.Prefs.get("log.logger.service.main")];
+
+ this._log.info("Loading Weave " + WEAVE_VERSION);
+
+ this._clusterManager = this.identity.createClusterManager(this);
+ this.recordManager = new RecordManager(this);
+
+ this.enabled = true;
+
+ this._registerEngines();
+
+ let ua = Cc["@mozilla.org/network/protocol;1?name=http"].
+ getService(Ci.nsIHttpProtocolHandler).userAgent;
+ this._log.info(ua);
+
+ if (!this._checkCrypto()) {
+ this.enabled = false;
+ this._log.info("Could not load the Weave crypto component. Disabling " +
+ "Weave, since it will not work correctly.");
+ }
+
+ Svc.Obs.add("weave:service:setup-complete", this);
+ Svc.Prefs.observe("engine.", this);
+
+ this.scheduler = new SyncScheduler(this);
+
+ if (!this.enabled) {
+ this._log.info("Firefox Sync disabled.");
+ }
+
+ this._updateCachedURLs();
+
+ let status = this._checkSetup();
+ if (status != STATUS_DISABLED && status != CLIENT_NOT_CONFIGURED) {
+ Svc.Obs.notify("weave:engine:start-tracking");
+ }
+
+ // Send an event now that Weave service is ready. We don't do this
+ // synchronously so that observers can import this module before
+ // registering an observer.
+ Utils.nextTick(function onNextTick() {
+ this.status.ready = true;
+
+ // UI code uses the flag on the XPCOM service so it doesn't have
+ // to load a bunch of modules.
+ let xps = Cc["@mozilla.org/weave/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject;
+ xps.ready = true;
+
+ Svc.Obs.notify("weave:service:ready");
+ }.bind(this));
+ },
+
+ _checkSetup: function _checkSetup() {
+ if (!this.enabled) {
+ return this.status.service = STATUS_DISABLED;
+ }
+ return this.status.checkSetup();
+ },
+
+ _migratePrefs: function _migratePrefs() {
+ // Migrate old debugLog prefs.
+ let logLevel = Svc.Prefs.get("log.appender.debugLog");
+ if (logLevel) {
+ Svc.Prefs.set("log.appender.file.level", logLevel);
+ Svc.Prefs.reset("log.appender.debugLog");
+ }
+ if (Svc.Prefs.get("log.appender.debugLog.enabled")) {
+ Svc.Prefs.set("log.appender.file.logOnSuccess", true);
+ Svc.Prefs.reset("log.appender.debugLog.enabled");
+ }
+
+ // Migrate old extensions.weave.* prefs if we haven't already tried.
+ if (Svc.Prefs.get("migrated", false))
+ return;
+
+ // Grab the list of old pref names
+ let oldPrefBranch = "extensions.weave.";
+ let oldPrefNames = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).
+ getBranch(oldPrefBranch).
+ getChildList("", {});
+
+ // Map each old pref to the current pref branch
+ let oldPref = new Preferences(oldPrefBranch);
+ for (let pref of oldPrefNames)
+ Svc.Prefs.set(pref, oldPref.get(pref));
+
+ // Remove all the old prefs and remember that we've migrated
+ oldPref.resetBranch("");
+ Svc.Prefs.set("migrated", true);
+ },
+
+ /**
+ * Register the built-in engines for certain applications
+ */
+ _registerEngines: function _registerEngines() {
+ this.engineManager = new EngineManager(this);
+
+ let engines = [];
+ // Applications can provide this preference (comma-separated list)
+ // to specify which engines should be registered on startup.
+ let pref = Svc.Prefs.get("registerEngines");
+ if (pref) {
+ engines = pref.split(",");
+ }
+
+ let declined = [];
+ pref = Svc.Prefs.get("declinedEngines");
+ if (pref) {
+ declined = pref.split(",");
+ }
+
+ this.clientsEngine = new ClientEngine(this);
+
+ for (let name of engines) {
+ if (!(name in ENGINE_MODULES)) {
+ this._log.info("Do not know about engine: " + name);
+ continue;
+ }
+
+ let ns = {};
+ try {
+ Cu.import("resource://services-sync/engines/" + ENGINE_MODULES[name], ns);
+
+ let engineName = name + "Engine";
+ if (!(engineName in ns)) {
+ this._log.warn("Could not find exported engine instance: " + engineName);
+ continue;
+ }
+
+ this.engineManager.register(ns[engineName]);
+ } catch (ex) {
+ this._log.warn("Could not register engine " + name + ": ", ex);
+ }
+ }
+
+ this.engineManager.setDeclined(declined);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ // nsIObserver
+
+ observe: function observe(subject, topic, data) {
+ switch (topic) {
+ case "weave:service:setup-complete":
+ let status = this._checkSetup();
+ if (status != STATUS_DISABLED && status != CLIENT_NOT_CONFIGURED)
+ Svc.Obs.notify("weave:engine:start-tracking");
+ break;
+ case "nsPref:changed":
+ if (this._ignorePrefObserver)
+ return;
+ let engine = data.slice((PREFS_BRANCH + "engine.").length);
+ this._handleEngineStatusChanged(engine);
+ break;
+ }
+ },
+
+ _handleEngineStatusChanged: function handleEngineDisabled(engine) {
+ this._log.trace("Status for " + engine + " engine changed.");
+ if (Svc.Prefs.get("engineStatusChanged." + engine, false)) {
+ // The enabled status being changed back to what it was before.
+ Svc.Prefs.reset("engineStatusChanged." + engine);
+ } else {
+ // Remember that the engine status changed locally until the next sync.
+ Svc.Prefs.set("engineStatusChanged." + engine, true);
+ }
+ },
+
+ /**
+ * Obtain a Resource instance with authentication credentials.
+ */
+ resource: function resource(url) {
+ let res = new Resource(url);
+ res.authenticator = this.identity.getResourceAuthenticator();
+
+ return res;
+ },
+
+ /**
+ * Obtain a SyncStorageRequest instance with authentication credentials.
+ */
+ getStorageRequest: function getStorageRequest(url) {
+ let request = new SyncStorageRequest(url);
+ request.authenticator = this.identity.getRESTRequestAuthenticator();
+
+ return request;
+ },
+
+ /**
+ * Perform the info fetch as part of a login or key fetch, or
+ * inside engine sync.
+ */
+ _fetchInfo: function (url) {
+ let infoURL = url || this.infoURL;
+
+ this._log.trace("In _fetchInfo: " + infoURL);
+ let info;
+ try {
+ info = this.resource(infoURL).get();
+ } catch (ex) {
+ this.errorHandler.checkServerError(ex);
+ throw ex;
+ }
+
+ // Always check for errors; this is also where we look for X-Weave-Alert.
+ this.errorHandler.checkServerError(info);
+ if (!info.success) {
+ throw "Aborting sync: failed to get collections.";
+ }
+ return info;
+ },
+
+ verifyAndFetchSymmetricKeys: function verifyAndFetchSymmetricKeys(infoResponse) {
+
+ this._log.debug("Fetching and verifying -- or generating -- symmetric keys.");
+
+ // Don't allow empty/missing passphrase.
+ // Furthermore, we assume that our sync key is already upgraded,
+ // and fail if that assumption is invalidated.
+
+ if (!this.identity.syncKey) {
+ this.status.login = LOGIN_FAILED_NO_PASSPHRASE;
+ this.status.sync = CREDENTIALS_CHANGED;
+ return false;
+ }
+
+ let syncKeyBundle = this.identity.syncKeyBundle;
+ if (!syncKeyBundle) {
+ this._log.error("Sync Key Bundle not set. Invalid Sync Key?");
+
+ this.status.login = LOGIN_FAILED_INVALID_PASSPHRASE;
+ this.status.sync = CREDENTIALS_CHANGED;
+ return false;
+ }
+
+ try {
+ if (!infoResponse)
+ infoResponse = this._fetchInfo(); // Will throw an exception on failure.
+
+ // This only applies when the server is already at version 4.
+ if (infoResponse.status != 200) {
+ this._log.warn("info/collections returned non-200 response. Failing key fetch.");
+ this.status.login = LOGIN_FAILED_SERVER_ERROR;
+ this.errorHandler.checkServerError(infoResponse);
+ return false;
+ }
+
+ let infoCollections = infoResponse.obj;
+
+ this._log.info("Testing info/collections: " + JSON.stringify(infoCollections));
+
+ if (this.collectionKeys.updateNeeded(infoCollections)) {
+ this._log.info("collection keys reports that a key update is needed.");
+
+ // Don't always set to CREDENTIALS_CHANGED -- we will probably take care of this.
+
+ // Fetch storage/crypto/keys.
+ let cryptoKeys;
+
+ if (infoCollections && (CRYPTO_COLLECTION in infoCollections)) {
+ try {
+ cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
+ let cryptoResp = cryptoKeys.fetch(this.resource(this.cryptoKeysURL)).response;
+
+ if (cryptoResp.success) {
+ let keysChanged = this.handleFetchedKeys(syncKeyBundle, cryptoKeys);
+ return true;
+ }
+ else if (cryptoResp.status == 404) {
+ // On failure, ask to generate new keys and upload them.
+ // Fall through to the behavior below.
+ this._log.warn("Got 404 for crypto/keys, but 'crypto' in info/collections. Regenerating.");
+ cryptoKeys = null;
+ }
+ else {
+ // Some other problem.
+ this.status.login = LOGIN_FAILED_SERVER_ERROR;
+ this.errorHandler.checkServerError(cryptoResp);
+ this._log.warn("Got status " + cryptoResp.status + " fetching crypto keys.");
+ return false;
+ }
+ }
+ catch (ex) {
+ this._log.warn("Got exception \"" + ex + "\" fetching cryptoKeys.");
+ // TODO: Um, what exceptions might we get here? Should we re-throw any?
+
+ // One kind of exception: HMAC failure.
+ if (Utils.isHMACMismatch(ex)) {
+ this.status.login = LOGIN_FAILED_INVALID_PASSPHRASE;
+ this.status.sync = CREDENTIALS_CHANGED;
+ }
+ else {
+ // In the absence of further disambiguation or more precise
+ // failure constants, just report failure.
+ this.status.login = LOGIN_FAILED;
+ }
+ return false;
+ }
+ }
+ else {
+ this._log.info("... 'crypto' is not a reported collection. Generating new keys.");
+ }
+
+ if (!cryptoKeys) {
+ this._log.info("No keys! Generating new ones.");
+
+ // Better make some and upload them, and wipe the server to ensure
+ // consistency. This is all achieved via _freshStart.
+ // If _freshStart fails to clear the server or upload keys, it will
+ // throw.
+ this._freshStart();
+ return true;
+ }
+
+ // Last-ditch case.
+ return false;
+ }
+ else {
+ // No update needed: we're good!
+ return true;
+ }
+
+ } catch (ex) {
+ // This means no keys are present, or there's a network error.
+ this._log.debug("Failed to fetch and verify keys: ", ex);
+ this.errorHandler.checkServerError(ex);
+ return false;
+ }
+ },
+
+ verifyLogin: function verifyLogin(allow40XRecovery = true) {
+ // If the identity isn't ready it might not know the username...
+ if (!this.identity.readyToAuthenticate) {
+ this._log.info("Not ready to authenticate in verifyLogin.");
+ this.status.login = LOGIN_FAILED_NOT_READY;
+ return false;
+ }
+
+ if (!this.identity.username) {
+ this._log.warn("No username in verifyLogin.");
+ this.status.login = LOGIN_FAILED_NO_USERNAME;
+ return false;
+ }
+
+ // Attaching auth credentials to a request requires access to
+ // passwords, which means that Resource.get can throw MP-related
+ // exceptions!
+ // So we ask the identity to verify the login state after unlocking the
+ // master password (ie, this call is expected to prompt for MP unlock
+ // if necessary) while we still have control.
+ let cb = Async.makeSpinningCallback();
+ this.identity.unlockAndVerifyAuthState().then(
+ result => cb(null, result),
+ cb
+ );
+ let unlockedState = cb.wait();
+ this._log.debug("Fetching unlocked auth state returned " + unlockedState);
+ if (unlockedState != STATUS_OK) {
+ this.status.login = unlockedState;
+ return false;
+ }
+
+ try {
+ // Make sure we have a cluster to verify against.
+ // This is a little weird, if we don't get a node we pretend
+ // to succeed, since that probably means we just don't have storage.
+ if (this.clusterURL == "" && !this._clusterManager.setCluster()) {
+ this.status.sync = NO_SYNC_NODE_FOUND;
+ return true;
+ }
+
+ // Fetch collection info on every startup.
+ let test = this.resource(this.infoURL).get();
+
+ switch (test.status) {
+ case 200:
+ // The user is authenticated.
+
+ // We have no way of verifying the passphrase right now,
+ // so wait until remoteSetup to do so.
+ // Just make the most trivial checks.
+ if (!this.identity.syncKey) {
+ this._log.warn("No passphrase in verifyLogin.");
+ this.status.login = LOGIN_FAILED_NO_PASSPHRASE;
+ return false;
+ }
+
+ // Go ahead and do remote setup, so that we can determine
+ // conclusively that our passphrase is correct.
+ if (this._remoteSetup(test)) {
+ // Username/password verified.
+ this.status.login = LOGIN_SUCCEEDED;
+ return true;
+ }
+
+ this._log.warn("Remote setup failed.");
+ // Remote setup must have failed.
+ return false;
+
+ case 401:
+ this._log.warn("401: login failed.");
+ // Fall through to the 404 case.
+
+ case 404:
+ // Check that we're verifying with the correct cluster
+ if (allow40XRecovery && this._clusterManager.setCluster()) {
+ return this.verifyLogin(false);
+ }
+
+ // We must have the right cluster, but the server doesn't expect us
+ this.status.login = LOGIN_FAILED_LOGIN_REJECTED;
+ return false;
+
+ default:
+ // Server didn't respond with something that we expected
+ this.status.login = LOGIN_FAILED_SERVER_ERROR;
+ this.errorHandler.checkServerError(test);
+ return false;
+ }
+ } catch (ex) {
+ // Must have failed on some network issue
+ this._log.debug("verifyLogin failed: ", ex);
+ this.status.login = LOGIN_FAILED_NETWORK_ERROR;
+ this.errorHandler.checkServerError(ex);
+ return false;
+ }
+ },
+
+ generateNewSymmetricKeys: function generateNewSymmetricKeys() {
+ this._log.info("Generating new keys WBO...");
+ let wbo = this.collectionKeys.generateNewKeysWBO();
+ this._log.info("Encrypting new key bundle.");
+ wbo.encrypt(this.identity.syncKeyBundle);
+
+ this._log.info("Uploading...");
+ let uploadRes = wbo.upload(this.resource(this.cryptoKeysURL));
+ if (uploadRes.status != 200) {
+ this._log.warn("Got status " + uploadRes.status + " uploading new keys. What to do? Throw!");
+ this.errorHandler.checkServerError(uploadRes);
+ throw new Error("Unable to upload symmetric keys.");
+ }
+ this._log.info("Got status " + uploadRes.status + " uploading keys.");
+ let serverModified = uploadRes.obj; // Modified timestamp according to server.
+ this._log.debug("Server reports crypto modified: " + serverModified);
+
+ // Now verify that info/collections shows them!
+ this._log.debug("Verifying server collection records.");
+ let info = this._fetchInfo();
+ this._log.debug("info/collections is: " + info);
+
+ if (info.status != 200) {
+ this._log.warn("Non-200 info/collections response. Aborting.");
+ throw new Error("Unable to upload symmetric keys.");
+ }
+
+ info = info.obj;
+ if (!(CRYPTO_COLLECTION in info)) {
+ this._log.error("Consistency failure: info/collections excludes " +
+ "crypto after successful upload.");
+ throw new Error("Symmetric key upload failed.");
+ }
+
+ // Can't check against local modified: clock drift.
+ if (info[CRYPTO_COLLECTION] < serverModified) {
+ this._log.error("Consistency failure: info/collections crypto entry " +
+ "is stale after successful upload.");
+ throw new Error("Symmetric key upload failed.");
+ }
+
+ // Doesn't matter if the timestamp is ahead.
+
+ // Download and install them.
+ let cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
+ let cryptoResp = cryptoKeys.fetch(this.resource(this.cryptoKeysURL)).response;
+ if (cryptoResp.status != 200) {
+ this._log.warn("Failed to download keys.");
+ throw new Error("Symmetric key download failed.");
+ }
+ let keysChanged = this.handleFetchedKeys(this.identity.syncKeyBundle,
+ cryptoKeys, true);
+ if (keysChanged) {
+ this._log.info("Downloaded keys differed, as expected.");
+ }
+ },
+
+ changePassword: function changePassword(newPassword) {
+ let client = new UserAPI10Client(this.userAPIURI);
+ let cb = Async.makeSpinningCallback();
+ client.changePassword(this.identity.username,
+ this.identity.basicPassword, newPassword, cb);
+
+ try {
+ cb.wait();
+ } catch (ex) {
+ this._log.debug("Password change failed: ", ex);
+ return false;
+ }
+
+ // Save the new password for requests and login manager.
+ this.identity.basicPassword = newPassword;
+ this.persistLogin();
+ return true;
+ },
+
+ changePassphrase: function changePassphrase(newphrase) {
+ return this._catch(function doChangePasphrase() {
+ /* Wipe. */
+ this.wipeServer();
+
+ this.logout();
+
+ /* Set this so UI is updated on next run. */
+ this.identity.syncKey = newphrase;
+ this.persistLogin();
+
+ /* We need to re-encrypt everything, so reset. */
+ this.resetClient();
+ this.collectionKeys.clear();
+
+ /* Login and sync. This also generates new keys. */
+ this.sync();
+
+ Svc.Obs.notify("weave:service:change-passphrase", true);
+
+ return true;
+ })();
+ },
+
+ startOver: function startOver() {
+ this._log.trace("Invoking Service.startOver.");
+ Svc.Obs.notify("weave:engine:stop-tracking");
+ this.status.resetSync();
+
+ // Deletion doesn't make sense if we aren't set up yet!
+ if (this.clusterURL != "") {
+ // Clear client-specific data from the server, including disabled engines.
+ for (let engine of [this.clientsEngine].concat(this.engineManager.getAll())) {
+ try {
+ engine.removeClientData();
+ } catch(ex) {
+ this._log.warn("Deleting client data for " + engine.name + " failed:", ex);
+ }
+ }
+ this._log.debug("Finished deleting client data.");
+ } else {
+ this._log.debug("Skipping client data removal: no cluster URL.");
+ }
+
+ // We want let UI consumers of the following notification know as soon as
+ // possible, so let's fake for the CLIENT_NOT_CONFIGURED status for now
+ // by emptying the passphrase (we still need the password).
+ this._log.info("Service.startOver dropping sync key and logging out.");
+ this.identity.resetSyncKey();
+ this.status.login = LOGIN_FAILED_NO_PASSPHRASE;
+ this.logout();
+ Svc.Obs.notify("weave:service:start-over");
+
+ // Reset all engines and clear keys.
+ this.resetClient();
+ this.collectionKeys.clear();
+ this.status.resetBackoff();
+
+ // Reset Weave prefs.
+ this._ignorePrefObserver = true;
+ Svc.Prefs.resetBranch("");
+ this._ignorePrefObserver = false;
+
+ Svc.Prefs.set("lastversion", WEAVE_VERSION);
+
+ this.identity.deleteSyncCredentials();
+
+ // If necessary, reset the identity manager, then re-initialize it so the
+ // FxA manager is used. This is configurable via a pref - mainly for tests.
+ let keepIdentity = false;
+ try {
+ keepIdentity = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity");
+ } catch (_) { /* no such pref */ }
+ if (keepIdentity) {
+ Svc.Obs.notify("weave:service:start-over:finish");
+ return;
+ }
+
+ this.identity.finalize().then(
+ () => {
+ // an observer so the FxA migration code can take some action before
+ // the new identity is created.
+ Svc.Obs.notify("weave:service:start-over:init-identity");
+ this.identity.username = "";
+ this.status.__authManager = null;
+ this.identity = Status._authManager;
+ this._clusterManager = this.identity.createClusterManager(this);
+ Svc.Obs.notify("weave:service:start-over:finish");
+ }
+ ).then(null,
+ err => {
+ this._log.error("startOver failed to re-initialize the identity manager: " + err);
+ // Still send the observer notification so the current state is
+ // reflected in the UI.
+ Svc.Obs.notify("weave:service:start-over:finish");
+ }
+ );
+ },
+
+ persistLogin: function persistLogin() {
+ try {
+ this.identity.persistCredentials(true);
+ } catch (ex) {
+ this._log.info("Unable to persist credentials: " + ex);
+ }
+ },
+
+ login: function login(username, password, passphrase) {
+ function onNotify() {
+ this._loggedIn = false;
+ if (Services.io.offline) {
+ this.status.login = LOGIN_FAILED_NETWORK_ERROR;
+ throw "Application is offline, login should not be called";
+ }
+
+ let initialStatus = this._checkSetup();
+ if (username) {
+ this.identity.username = username;
+ }
+ if (password) {
+ this.identity.basicPassword = password;
+ }
+ if (passphrase) {
+ this.identity.syncKey = passphrase;
+ }
+
+ if (this._checkSetup() == CLIENT_NOT_CONFIGURED) {
+ throw "Aborting login, client not configured.";
+ }
+
+ // Ask the identity manager to explicitly login now.
+ let cb = Async.makeSpinningCallback();
+ this.identity.ensureLoggedIn().then(cb, cb);
+
+ // Just let any errors bubble up - they've more context than we do!
+ cb.wait();
+
+ // Calling login() with parameters when the client was
+ // previously not configured means setup was completed.
+ if (initialStatus == CLIENT_NOT_CONFIGURED
+ && (username || password || passphrase)) {
+ Svc.Obs.notify("weave:service:setup-complete");
+ }
+ this._log.info("Logging in the user.");
+ this._updateCachedURLs();
+
+ if (!this.verifyLogin()) {
+ // verifyLogin sets the failure states here.
+ throw "Login failed: " + this.status.login;
+ }
+
+ this._loggedIn = true;
+
+ return true;
+ }
+
+ let notifier = this._notify("login", "", onNotify.bind(this));
+ return this._catch(this._lock("service.js: login", notifier))();
+ },
+
+ logout: function logout() {
+ // If we failed during login, we aren't going to have this._loggedIn set,
+ // but we still want to ask the identity to logout, so it doesn't try and
+ // reuse any old credentials next time we sync.
+ this._log.info("Logging out");
+ this.identity.logout();
+ this._loggedIn = false;
+
+ Svc.Obs.notify("weave:service:logout:finish");
+ },
+
+ checkAccount: function checkAccount(account) {
+ let client = new UserAPI10Client(this.userAPIURI);
+ let cb = Async.makeSpinningCallback();
+
+ let username = this.identity.usernameFromAccount(account);
+ client.usernameExists(username, cb);
+
+ try {
+ let exists = cb.wait();
+ return exists ? "notAvailable" : "available";
+ } catch (ex) {
+ // TODO fix API convention.
+ return this.errorHandler.errorStr(ex);
+ }
+ },
+
+ createAccount: function createAccount(email, password,
+ captchaChallenge, captchaResponse) {
+ let client = new UserAPI10Client(this.userAPIURI);
+
+ // Hint to server to allow scripted user creation or otherwise
+ // ignore captcha.
+ if (Svc.Prefs.isSet("admin-secret")) {
+ client.adminSecret = Svc.Prefs.get("admin-secret", "");
+ }
+
+ let cb = Async.makeSpinningCallback();
+
+ client.createAccount(email, password, captchaChallenge, captchaResponse,
+ cb);
+
+ try {
+ cb.wait();
+ return null;
+ } catch (ex) {
+ return this.errorHandler.errorStr(ex.body);
+ }
+ },
+
+ // Stuff we need to do after login, before we can really do
+ // anything (e.g. key setup).
+ _remoteSetup: function _remoteSetup(infoResponse) {
+ let reset = false;
+
+ this._log.debug("Fetching global metadata record");
+ let meta = this.recordManager.get(this.metaURL);
+
+ // Checking modified time of the meta record.
+ if (infoResponse &&
+ (infoResponse.obj.meta != this.metaModified) &&
+ (!meta || !meta.isNew)) {
+
+ // Delete the cached meta record...
+ this._log.debug("Clearing cached meta record. metaModified is " +
+ JSON.stringify(this.metaModified) + ", setting to " +
+ JSON.stringify(infoResponse.obj.meta));
+
+ this.recordManager.del(this.metaURL);
+
+ // ... fetch the current record from the server, and COPY THE FLAGS.
+ let newMeta = this.recordManager.get(this.metaURL);
+
+ // If we got a 401, we do not want to create a new meta/global - we
+ // should be able to get the existing meta after we get a new node.
+ if (this.recordManager.response.status == 401) {
+ this._log.debug("Fetching meta/global record on the server returned 401.");
+ this.errorHandler.checkServerError(this.recordManager.response);
+ return false;
+ }
+
+ if (!this.recordManager.response.success || !newMeta) {
+ this._log.debug("No meta/global record on the server. Creating one.");
+ newMeta = new WBORecord("meta", "global");
+ newMeta.payload.syncID = this.syncID;
+ newMeta.payload.storageVersion = STORAGE_VERSION;
+ newMeta.payload.declined = this.engineManager.getDeclined();
+
+ newMeta.isNew = true;
+
+ this.recordManager.set(this.metaURL, newMeta);
+ if (!newMeta.upload(this.resource(this.metaURL)).success) {
+ this._log.warn("Unable to upload new meta/global. Failing remote setup.");
+ return false;
+ }
+ } else {
+ // If newMeta, then it stands to reason that meta != null.
+ newMeta.isNew = meta.isNew;
+ newMeta.changed = meta.changed;
+ }
+
+ // Switch in the new meta object and record the new time.
+ meta = newMeta;
+ this.metaModified = infoResponse.obj.meta;
+ }
+
+ let remoteVersion = (meta && meta.payload.storageVersion)?
+ meta.payload.storageVersion : "";
+
+ this._log.debug(["Weave Version:", WEAVE_VERSION, "Local Storage:",
+ STORAGE_VERSION, "Remote Storage:", remoteVersion].join(" "));
+
+ // Check for cases that require a fresh start. When comparing remoteVersion,
+ // we need to convert it to a number as older clients used it as a string.
+ if (!meta || !meta.payload.storageVersion || !meta.payload.syncID ||
+ STORAGE_VERSION > parseFloat(remoteVersion)) {
+
+ this._log.info("One of: no meta, no meta storageVersion, or no meta syncID. Fresh start needed.");
+
+ // abort the server wipe if the GET status was anything other than 404 or 200
+ let status = this.recordManager.response.status;
+ if (status != 200 && status != 404) {
+ this.status.sync = METARECORD_DOWNLOAD_FAIL;
+ this.errorHandler.checkServerError(this.recordManager.response);
+ this._log.warn("Unknown error while downloading metadata record. " +
+ "Aborting sync.");
+ return false;
+ }
+
+ if (!meta)
+ this._log.info("No metadata record, server wipe needed");
+ if (meta && !meta.payload.syncID)
+ this._log.warn("No sync id, server wipe needed");
+
+ reset = true;
+
+ this._log.info("Wiping server data");
+ this._freshStart();
+
+ if (status == 404)
+ this._log.info("Metadata record not found, server was wiped to ensure " +
+ "consistency.");
+ else // 200
+ this._log.info("Wiped server; incompatible metadata: " + remoteVersion);
+
+ return true;
+ }
+ else if (remoteVersion > STORAGE_VERSION) {
+ this.status.sync = VERSION_OUT_OF_DATE;
+ this._log.warn("Upgrade required to access newer storage version.");
+ return false;
+ }
+ else if (meta.payload.syncID != this.syncID) {
+
+ this._log.info("Sync IDs differ. Local is " + this.syncID + ", remote is " + meta.payload.syncID);
+ this.resetClient();
+ this.collectionKeys.clear();
+ this.syncID = meta.payload.syncID;
+ this._log.debug("Clear cached values and take syncId: " + this.syncID);
+
+ if (!this.upgradeSyncKey(meta.payload.syncID)) {
+ this._log.warn("Failed to upgrade sync key. Failing remote setup.");
+ return false;
+ }
+
+ if (!this.verifyAndFetchSymmetricKeys(infoResponse)) {
+ this._log.warn("Failed to fetch symmetric keys. Failing remote setup.");
+ return false;
+ }
+
+ // bug 545725 - re-verify creds and fail sanely
+ if (!this.verifyLogin()) {
+ this.status.sync = CREDENTIALS_CHANGED;
+ this._log.info("Credentials have changed, aborting sync and forcing re-login.");
+ return false;
+ }
+
+ return true;
+ }
+ else {
+ if (!this.upgradeSyncKey(meta.payload.syncID)) {
+ this._log.warn("Failed to upgrade sync key. Failing remote setup.");
+ return false;
+ }
+
+ if (!this.verifyAndFetchSymmetricKeys(infoResponse)) {
+ this._log.warn("Failed to fetch symmetric keys. Failing remote setup.");
+ return false;
+ }
+
+ return true;
+ }
+ },
+
+ /**
+ * Return whether we should attempt login at the start of a sync.
+ *
+ * Note that this function has strong ties to _checkSync: callers
+ * of this function should typically use _checkSync to verify that
+ * any necessary login took place.
+ */
+ _shouldLogin: function _shouldLogin() {
+ return this.enabled &&
+ !Services.io.offline &&
+ !this.isLoggedIn;
+ },
+
+ /**
+ * Determine if a sync should run.
+ *
+ * @param ignore [optional]
+ * array of reasons to ignore when checking
+ *
+ * @return Reason for not syncing; not-truthy if sync should run
+ */
+ _checkSync: function _checkSync(ignore) {
+ let reason = "";
+ if (!this.enabled)
+ reason = kSyncWeaveDisabled;
+ else if (Services.io.offline)
+ reason = kSyncNetworkOffline;
+ else if (this.status.minimumNextSync > Date.now())
+ reason = kSyncBackoffNotMet;
+ else if ((this.status.login == MASTER_PASSWORD_LOCKED) &&
+ Utils.mpLocked())
+ reason = kSyncMasterPasswordLocked;
+ else if (Svc.Prefs.get("firstSync") == "notReady")
+ reason = kFirstSyncChoiceNotMade;
+
+ if (ignore && ignore.indexOf(reason) != -1)
+ return "";
+
+ return reason;
+ },
+
+ sync: function sync() {
+ let dateStr = new Date().toLocaleFormat(LOG_DATE_FORMAT);
+ this._log.debug("User-Agent: " + SyncStorageRequest.prototype.userAgent);
+ this._log.info("Starting sync at " + dateStr);
+ this._catch(function () {
+ // Make sure we're logged in.
+ if (this._shouldLogin()) {
+ this._log.debug("In sync: should login.");
+ if (!this.login()) {
+ this._log.debug("Not syncing: login returned false.");
+ return;
+ }
+ }
+ else {
+ this._log.trace("In sync: no need to login.");
+ }
+ return this._lockedSync.apply(this, arguments);
+ })();
+ },
+
+ /**
+ * Sync up engines with the server.
+ */
+ _lockedSync: function _lockedSync() {
+ return this._lock("service.js: sync",
+ this._notify("sync", "", function onNotify() {
+
+ let synchronizer = new EngineSynchronizer(this);
+ let cb = Async.makeSpinningCallback();
+ synchronizer.onComplete = cb;
+
+ synchronizer.sync();
+ // wait() throws if the first argument is truthy, which is exactly what
+ // we want.
+ let result = cb.wait();
+
+ // We successfully synchronized.
+ // Check if the identity wants to pre-fetch a migration sentinel from
+ // the server.
+ // If we have no clusterURL, we are probably doing a node reassignment
+ // so don't attempt to get it in that case.
+ //if (this.clusterURL) {
+ // this.identity.prefetchMigrationSentinel(this);
+ //}
+
+ // Now let's update our declined engines.
+ let meta = this.recordManager.get(this.metaURL);
+ if (!meta) {
+ this._log.warn("No meta/global; can't update declined state.");
+ return;
+ }
+
+ let declinedEngines = new DeclinedEngines(this);
+ let didChange = declinedEngines.updateDeclined(meta, this.engineManager);
+ if (!didChange) {
+ this._log.info("No change to declined engines. Not reuploading meta/global.");
+ return;
+ }
+
+ this.uploadMetaGlobal(meta);
+ }))();
+ },
+
+ /**
+ * Upload meta/global, throwing the response on failure.
+ */
+ uploadMetaGlobal: function (meta) {
+ this._log.debug("Uploading meta/global: " + JSON.stringify(meta));
+
+ // It would be good to set the X-If-Unmodified-Since header to `timestamp`
+ // for this PUT to ensure at least some level of transactionality.
+ // Unfortunately, the servers don't support it after a wipe right now
+ // (bug 693893), so we're going to defer this until bug 692700.
+ let res = this.resource(this.metaURL);
+ let response = res.put(meta);
+ if (!response.success) {
+ throw response;
+ }
+ this.recordManager.set(this.metaURL, meta);
+ },
+
+ /**
+ * If we have a passphrase, rather than a 25-alphadigit sync key,
+ * use the provided sync ID to bootstrap it using PBKDF2.
+ *
+ * Store the new 'passphrase' back into the identity manager.
+ *
+ * We can check this as often as we want, because once it's done the
+ * check will no longer succeed. It only matters that it happens after
+ * we decide to bump the server storage version.
+ */
+ upgradeSyncKey: function upgradeSyncKey(syncID) {
+ let p = this.identity.syncKey;
+
+ if (!p) {
+ return false;
+ }
+
+ // Check whether it's already a key that we generated.
+ if (Utils.isPassphrase(p)) {
+ this._log.info("Sync key is up-to-date: no need to upgrade.");
+ return true;
+ }
+
+ // Otherwise, let's upgrade it.
+ // N.B., we persist the sync key without testing it first...
+
+ let s = btoa(syncID); // It's what WeaveCrypto expects. *sigh*
+ let k = Utils.derivePresentableKeyFromPassphrase(p, s, PBKDF2_KEY_BYTES); // Base 32.
+
+ if (!k) {
+ this._log.error("No key resulted from derivePresentableKeyFromPassphrase. Failing upgrade.");
+ return false;
+ }
+
+ this._log.info("Upgrading sync key...");
+ this.identity.syncKey = k;
+ this._log.info("Saving upgraded sync key...");
+ this.persistLogin();
+ this._log.info("Done saving.");
+ return true;
+ },
+
+ _freshStart: function _freshStart() {
+ this._log.info("Fresh start. Resetting client and considering key upgrade.");
+ this.resetClient();
+ this.collectionKeys.clear();
+ this.upgradeSyncKey(this.syncID);
+
+ // Wipe the server.
+ let wipeTimestamp = this.wipeServer();
+
+ // Upload a new meta/global record.
+ let meta = new WBORecord("meta", "global");
+ meta.payload.syncID = this.syncID;
+ meta.payload.storageVersion = STORAGE_VERSION;
+ meta.payload.declined = this.engineManager.getDeclined();
+ meta.isNew = true;
+
+ // uploadMetaGlobal throws on failure -- including race conditions.
+ // If we got into a race condition, we'll abort the sync this way, too.
+ // That's fine. We'll just wait till the next sync. The client that we're
+ // racing is probably busy uploading stuff right now anyway.
+ this.uploadMetaGlobal(meta);
+
+ // Wipe everything we know about except meta because we just uploaded it
+ let engines = [this.clientsEngine].concat(this.engineManager.getAll());
+ let collections = engines.map(engine => engine.name);
+ // TODO: there's a bug here. We should be calling resetClient, no?
+
+ // Generate, upload, and download new keys. Do this last so we don't wipe
+ // them...
+ this.generateNewSymmetricKeys();
+ },
+
+ /**
+ * Wipe user data from the server.
+ *
+ * @param collections [optional]
+ * Array of collections to wipe. If not given, all collections are
+ * wiped by issuing a DELETE request for `storageURL`.
+ *
+ * @return the server's timestamp of the (last) DELETE.
+ */
+ wipeServer: function wipeServer(collections) {
+ let response;
+ if (!collections) {
+ // Strip the trailing slash.
+ let res = this.resource(this.storageURL.slice(0, -1));
+ res.setHeader("X-Confirm-Delete", "1");
+ try {
+ response = res.delete();
+ } catch (ex) {
+ this._log.debug("Failed to wipe server: ", ex);
+ throw ex;
+ }
+ if (response.status != 200 && response.status != 404) {
+ this._log.debug("Aborting wipeServer. Server responded with " +
+ response.status + " response for " + this.storageURL);
+ throw response;
+ }
+ return response.headers["x-weave-timestamp"];
+ }
+
+ let timestamp;
+ for (let name of collections) {
+ let url = this.storageURL + name;
+ try {
+ response = this.resource(url).delete();
+ } catch (ex) {
+ this._log.debug("Failed to wipe '" + name + "' collection: ", ex);
+ throw ex;
+ }
+
+ if (response.status != 200 && response.status != 404) {
+ this._log.debug("Aborting wipeServer. Server responded with " +
+ response.status + " response for " + url);
+ throw response;
+ }
+
+ if ("x-weave-timestamp" in response.headers) {
+ timestamp = response.headers["x-weave-timestamp"];
+ }
+ }
+
+ return timestamp;
+ },
+
+ /**
+ * Wipe all local user data.
+ *
+ * @param engines [optional]
+ * Array of engine names to wipe. If not given, all engines are used.
+ */
+ wipeClient: function wipeClient(engines) {
+ // If we don't have any engines, reset the service and wipe all engines
+ if (!engines) {
+ // Clear out any service data
+ this.resetService();
+
+ engines = [this.clientsEngine].concat(this.engineManager.getAll());
+ }
+ // Convert the array of names into engines
+ else {
+ engines = this.engineManager.get(engines);
+ }
+
+ // Fully wipe each engine if it's able to decrypt data
+ for each (let engine in engines) {
+ if (engine.canDecrypt()) {
+ engine.wipeClient();
+ }
+ }
+
+ // Save the password/passphrase just in-case they aren't restored by sync
+ this.persistLogin();
+ },
+
+ /**
+ * Wipe all remote user data by wiping the server then telling each remote
+ * client to wipe itself.
+ *
+ * @param engines [optional]
+ * Array of engine names to wipe. If not given, all engines are used.
+ */
+ wipeRemote: function wipeRemote(engines) {
+ try {
+ // Make sure stuff gets uploaded.
+ this.resetClient(engines);
+
+ // Clear out any server data.
+ this.wipeServer(engines);
+
+ // Only wipe the engines provided.
+ if (engines) {
+ engines.forEach(function(e) {
+ this.clientsEngine.sendCommand("wipeEngine", [e]);
+ }, this);
+ }
+ // Tell the remote machines to wipe themselves.
+ else {
+ this.clientsEngine.sendCommand("wipeAll", []);
+ }
+
+ // Make sure the changed clients get updated.
+ this.clientsEngine.sync();
+ } catch (ex) {
+ this.errorHandler.checkServerError(ex);
+ throw ex;
+ }
+ },
+
+ /**
+ * Reset local service information like logs, sync times, caches.
+ */
+ resetService: function resetService() {
+ this._catch(function reset() {
+ this._log.info("Service reset.");
+
+ // Pretend we've never synced to the server and drop cached data
+ this.syncID = "";
+ this.recordManager.clearCache();
+ })();
+ },
+
+ /**
+ * Reset the client by getting rid of any local server data and client data.
+ *
+ * @param engines [optional]
+ * Array of engine names to reset. If not given, all engines are used.
+ */
+ resetClient: function resetClient(engines) {
+ this._catch(function doResetClient() {
+ // If we don't have any engines, reset everything including the service
+ if (!engines) {
+ // Clear out any service data
+ this.resetService();
+
+ engines = [this.clientsEngine].concat(this.engineManager.getAll());
+ }
+ // Convert the array of names into engines
+ else {
+ engines = this.engineManager.get(engines);
+ }
+
+ // Have each engine drop any temporary meta data
+ for (let engine of engines) {
+ engine.resetClient();
+ }
+ })();
+ },
+
+ /**
+ * Fetch storage info from the server.
+ *
+ * @param type
+ * String specifying what info to fetch from the server. Must be one
+ * of the INFO_* values. See Sync Storage Server API spec for details.
+ * @param callback
+ * Callback function with signature (error, data) where `data' is
+ * the return value from the server already parsed as JSON.
+ *
+ * @return RESTRequest instance representing the request, allowing callers
+ * to cancel the request.
+ */
+ getStorageInfo: function getStorageInfo(type, callback) {
+ if (STORAGE_INFO_TYPES.indexOf(type) == -1) {
+ throw "Invalid value for 'type': " + type;
+ }
+
+ let info_type = "info/" + type;
+ this._log.trace("Retrieving '" + info_type + "'...");
+ let url = this.userBaseURL + info_type;
+ return this.getStorageRequest(url).get(function onComplete(error) {
+ // Note: 'this' is the request.
+ if (error) {
+ this._log.debug("Failed to retrieve '" + info_type + "': ", error);
+ return callback(error);
+ }
+ if (this.response.status != 200) {
+ this._log.debug("Failed to retrieve '" + info_type +
+ "': server responded with HTTP" +
+ this.response.status);
+ return callback(this.response);
+ }
+
+ let result;
+ try {
+ result = JSON.parse(this.response.body);
+ } catch (ex) {
+ this._log.debug("Server returned invalid JSON for '" + info_type +
+ "': " + this.response.body);
+ return callback(ex);
+ }
+ this._log.trace("Successfully retrieved '" + info_type + "'.");
+ return callback(null, result);
+ });
+ },
+};
+
+this.Service = new Sync11Service();
+Service.onStartup();
diff --git a/components/weave/src/sync/status.js b/components/weave/src/sync/status.js
new file mode 100644
index 000000000..27243de0d
--- /dev/null
+++ b/components/weave/src/sync/status.js
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = ["Status"];
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-sync/identity.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Async.jsm");
+
+this.Status = {
+ _log: Log.repository.getLogger("Sync.Status"),
+ __authManager: null,
+ ready: false,
+
+ get _authManager() {
+ if (this.__authManager) {
+ return this.__authManager;
+ }
+ let service = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ let idClass = IdentityManager;
+ this.__authManager = new idClass();
+ // .initialize returns a promise, so we need to spin until it resolves.
+ let cb = Async.makeSpinningCallback();
+ this.__authManager.initialize().then(cb, cb);
+ cb.wait();
+ return this.__authManager;
+ },
+
+ get service() {
+ return this._service;
+ },
+
+ set service(code) {
+ this._log.debug("Status.service: " + (this._service || undefined) + " => " + code);
+ this._service = code;
+ },
+
+ get login() {
+ return this._login;
+ },
+
+ set login(code) {
+ this._log.debug("Status.login: " + this._login + " => " + code);
+ this._login = code;
+
+ if (code == LOGIN_FAILED_NO_USERNAME ||
+ code == LOGIN_FAILED_NO_PASSWORD ||
+ code == LOGIN_FAILED_NO_PASSPHRASE) {
+ this.service = CLIENT_NOT_CONFIGURED;
+ } else if (code != LOGIN_SUCCEEDED) {
+ this.service = LOGIN_FAILED;
+ } else {
+ this.service = STATUS_OK;
+ }
+ },
+
+ get sync() {
+ return this._sync;
+ },
+
+ set sync(code) {
+ this._log.debug("Status.sync: " + this._sync + " => " + code);
+ this._sync = code;
+ this.service = code == SYNC_SUCCEEDED ? STATUS_OK : SYNC_FAILED;
+ },
+
+ get eol() {
+ let modePref = PREFS_BRANCH + "errorhandler.alert.mode";
+ try {
+ return Services.prefs.getCharPref(modePref) == "hard-eol";
+ } catch (ex) {
+ return false;
+ }
+ },
+
+ get engines() {
+ return this._engines;
+ },
+
+ set engines([name, code]) {
+ this._log.debug("Status for engine " + name + ": " + code);
+ this._engines[name] = code;
+
+ if (code != ENGINE_SUCCEEDED) {
+ this.service = SYNC_FAILED_PARTIAL;
+ }
+ },
+
+ // Implement toString because adding a logger introduces a cyclic object
+ // value, so we can't trivially debug-print Status as JSON.
+ toString: function toString() {
+ return "<Status" +
+ ": login: " + Status.login +
+ ", service: " + Status.service +
+ ", sync: " + Status.sync + ">";
+ },
+
+ checkSetup: function checkSetup() {
+ let result = this._authManager.currentAuthState;
+ if (result == STATUS_OK) {
+ Status.service = result;
+ return result;
+ }
+
+ Status.login = result;
+ return Status.service;
+ },
+
+ resetBackoff: function resetBackoff() {
+ this.enforceBackoff = false;
+ this.backoffInterval = 0;
+ this.minimumNextSync = 0;
+ },
+
+ resetSync: function resetSync() {
+ // Logger setup.
+ let logPref = PREFS_BRANCH + "log.logger.status";
+ let logLevel = Services.prefs.getCharPref(logPref, "Trace");
+ this._log.level = Log.Level[logLevel];
+
+ this._log.info("Resetting Status.");
+ this.service = STATUS_OK;
+ this._login = LOGIN_SUCCEEDED;
+ this._sync = SYNC_SUCCEEDED;
+ this._engines = {};
+ this.partial = false;
+ }
+};
+
+// Initialize various status values.
+Status.resetBackoff();
+Status.resetSync();
diff --git a/components/weave/src/sync/userapi.js b/components/weave/src/sync/userapi.js
new file mode 100644
index 000000000..e906440bd
--- /dev/null
+++ b/components/weave/src/sync/userapi.js
@@ -0,0 +1,224 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = [
+ "UserAPI10Client",
+];
+
+var {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/rest.js");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-sync/identity.js");
+Cu.import("resource://services-sync/util.js");
+
+/**
+ * A generic client for the user API 1.0 service.
+ *
+ * http://docs.services.mozilla.com/reg/apis.html
+ *
+ * Instances are constructed with the base URI of the service.
+ */
+this.UserAPI10Client = function UserAPI10Client(baseURI) {
+ this._log = Log.repository.getLogger("Sync.UserAPI");
+ this._log.level = Log.Level[Svc.Prefs.get("log.logger.userapi")];
+
+ this.baseURI = baseURI;
+}
+UserAPI10Client.prototype = {
+ USER_CREATE_ERROR_CODES: {
+ 2: "Incorrect or missing captcha.",
+ 4: "User exists.",
+ 6: "JSON parse failure.",
+ 7: "Missing password field.",
+ 9: "Requested password not strong enough.",
+ 12: "No email address on file.",
+ },
+
+ /**
+ * Determine whether a specified username exists.
+ *
+ * Callback receives the following arguments:
+ *
+ * (Error) Describes error that occurred or null if request was
+ * successful.
+ * (boolean) True if user exists. False if not. null if there was an error.
+ */
+ usernameExists: function usernameExists(username, cb) {
+ if (typeof(cb) != "function") {
+ throw new Error("cb must be a function.");
+ }
+
+ let url = this.baseURI + username;
+ let request = new RESTRequest(url);
+ request.get(this._onUsername.bind(this, cb, request));
+ },
+
+ /**
+ * Obtain the Weave (Sync) node for a specified user.
+ *
+ * The callback receives the following arguments:
+ *
+ * (Error) Describes error that occurred or null if request was successful.
+ * (string) Username request is for.
+ * (string) URL of user's node. If null and there is no error, no node could
+ * be assigned at the time of the request.
+ */
+ getWeaveNode: function getWeaveNode(username, password, cb) {
+ if (typeof(cb) != "function") {
+ throw new Error("cb must be a function.");
+ }
+
+ let request = this._getRequest(username, "/node/weave", password);
+ request.get(this._onWeaveNode.bind(this, cb, request));
+ },
+
+ /**
+ * Change a password for the specified user.
+ *
+ * @param username
+ * (string) The username whose password to change.
+ * @param oldPassword
+ * (string) The old, current password.
+ * @param newPassword
+ * (string) The new password to switch to.
+ */
+ changePassword: function changePassword(username, oldPassword, newPassword, cb) {
+ let request = this._getRequest(username, "/password", oldPassword);
+ request.onComplete = this._onChangePassword.bind(this, cb, request);
+ request.post(CommonUtils.encodeUTF8(newPassword));
+ },
+
+ createAccount: function createAccount(email, password, captchaChallenge,
+ captchaResponse, cb) {
+ let username = IdentityManager.prototype.usernameFromAccount(email);
+ let body = JSON.stringify({
+ "email": email,
+ "password": Utils.encodeUTF8(password),
+ "captcha-challenge": captchaChallenge,
+ "captcha-response": captchaResponse
+ });
+
+ let url = this.baseURI + username;
+ let request = new RESTRequest(url);
+
+ if (this.adminSecret) {
+ request.setHeader("X-Weave-Secret", this.adminSecret);
+ }
+
+ request.onComplete = this._onCreateAccount.bind(this, cb, request);
+ request.put(body);
+ },
+
+ _getRequest: function _getRequest(username, path, password=null) {
+ let url = this.baseURI + username + path;
+ let request = new RESTRequest(url);
+
+ if (password) {
+ let up = username + ":" + password;
+ request.setHeader("authorization", "Basic " + btoa(up));
+ }
+
+ return request;
+ },
+
+ _onUsername: function _onUsername(cb, request, error) {
+ if (error) {
+ cb(error, null);
+ return;
+ }
+
+ let body = request.response.body;
+ if (body == "0") {
+ cb(null, false);
+ return;
+ } else if (body == "1") {
+ cb(null, true);
+ return;
+ } else {
+ cb(new Error("Unknown response from server: " + body), null);
+ return;
+ }
+ },
+
+ _onWeaveNode: function _onWeaveNode(cb, request, error) {
+ if (error) {
+ cb.network = true;
+ cb(error, null);
+ return;
+ }
+
+ let response = request.response;
+
+ if (response.status == 200) {
+ let body = response.body;
+ if (body == "null") {
+ cb(null, null);
+ return;
+ }
+
+ cb(null, body);
+ return;
+ }
+
+ error = new Error("Sync node retrieval failed.");
+ switch (response.status) {
+ case 400:
+ error.denied = true;
+ break;
+ case 404:
+ error.notFound = true;
+ break;
+ default:
+ error.message = "Unexpected response code: " + response.status;
+ }
+
+ cb(error, null);
+ return;
+ },
+
+ _onChangePassword: function _onChangePassword(cb, request, error) {
+ this._log.info("Password change response received: " +
+ request.response.status);
+ if (error) {
+ cb(error);
+ return;
+ }
+
+ let response = request.response;
+ if (response.status != 200) {
+ cb(new Error("Password changed failed: " + response.body));
+ return;
+ }
+
+ cb(null);
+ },
+
+ _onCreateAccount: function _onCreateAccount(cb, request, error) {
+ let response = request.response;
+
+ this._log.info("Create account response: " + response.status + " " +
+ response.body);
+
+ if (error) {
+ cb(new Error("HTTP transport error."), null);
+ return;
+ }
+
+ if (response.status == 200) {
+ cb(null, response.body);
+ return;
+ }
+
+ error = new Error("Could not create user.");
+ error.body = response.body;
+
+ cb(error, null);
+ return;
+ },
+};
+Object.freeze(UserAPI10Client.prototype);
diff --git a/components/weave/src/sync/util.js b/components/weave/src/sync/util.js
new file mode 100644
index 000000000..08e4e1dfc
--- /dev/null
+++ b/components/weave/src/sync/util.js
@@ -0,0 +1,693 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["XPCOMUtils", "Services", "Utils", "Async", "Svc", "Str"];
+
+var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://services-common/stringbundle.js");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/Async.jsm", this);
+Cu.import("resource://services-crypto/utils.js");
+Cu.import("resource://services-sync/constants.js");
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+
+/*
+ * Utility functions
+ */
+
+this.Utils = {
+ // Alias in functions from CommonUtils. These previously were defined here.
+ // In the ideal world, references to these would be removed.
+ nextTick: CommonUtils.nextTick,
+ namedTimer: CommonUtils.namedTimer,
+ exceptionStr: CommonUtils.exceptionStr,
+ stackTrace: CommonUtils.stackTrace,
+ makeURI: CommonUtils.makeURI,
+ encodeUTF8: CommonUtils.encodeUTF8,
+ decodeUTF8: CommonUtils.decodeUTF8,
+ safeAtoB: CommonUtils.safeAtoB,
+ byteArrayToString: CommonUtils.byteArrayToString,
+ bytesAsHex: CommonUtils.bytesAsHex,
+ hexToBytes: CommonUtils.hexToBytes,
+ encodeBase32: CommonUtils.encodeBase32,
+ decodeBase32: CommonUtils.decodeBase32,
+
+ // Aliases from CryptoUtils.
+ generateRandomBytes: CryptoUtils.generateRandomBytes,
+ computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
+ digestUTF8: CryptoUtils.digestUTF8,
+ digestBytes: CryptoUtils.digestBytes,
+ sha1: CryptoUtils.sha1,
+ sha1Base32: CryptoUtils.sha1Base32,
+ makeHMACKey: CryptoUtils.makeHMACKey,
+ makeHMACHasher: CryptoUtils.makeHMACHasher,
+ hkdfExpand: CryptoUtils.hkdfExpand,
+ pbkdf2Generate: CryptoUtils.pbkdf2Generate,
+ deriveKeyFromPassphrase: CryptoUtils.deriveKeyFromPassphrase,
+ getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header,
+
+ /**
+ * Wrap a function to catch all exceptions and log them
+ *
+ * @usage MyObj._catch = Utils.catch;
+ * MyObj.foo = function() { this._catch(func)(); }
+ *
+ * Optionally pass a function which will be called if an
+ * exception occurs.
+ */
+ catch: function Utils_catch(func, exceptionCallback) {
+ let thisArg = this;
+ return function WrappedCatch() {
+ try {
+ return func.call(thisArg);
+ }
+ catch(ex) {
+ thisArg._log.debug("Exception: ", ex);
+ if (exceptionCallback) {
+ return exceptionCallback.call(thisArg, ex);
+ }
+ return null;
+ }
+ };
+ },
+
+ /**
+ * Wrap a function to call lock before calling the function then unlock.
+ *
+ * @usage MyObj._lock = Utils.lock;
+ * MyObj.foo = function() { this._lock(func)(); }
+ */
+ lock: function lock(label, func) {
+ let thisArg = this;
+ return function WrappedLock() {
+ if (!thisArg.lock()) {
+ throw "Could not acquire lock. Label: \"" + label + "\".";
+ }
+
+ try {
+ return func.call(thisArg);
+ }
+ finally {
+ thisArg.unlock();
+ }
+ };
+ },
+
+ isLockException: function isLockException(ex) {
+ return ex && ex.indexOf && ex.indexOf("Could not acquire lock.") == 0;
+ },
+
+ /**
+ * Wrap functions to notify when it starts and finishes executing or if it
+ * threw an error.
+ *
+ * The message is a combination of a provided prefix, the local name, and
+ * the event. Possible events are: "start", "finish", "error". The subject
+ * is the function's return value on "finish" or the caught exception on
+ * "error". The data argument is the predefined data value.
+ *
+ * Example:
+ *
+ * @usage function MyObj(name) {
+ * this.name = name;
+ * this._notify = Utils.notify("obj:");
+ * }
+ * MyObj.prototype = {
+ * foo: function() this._notify("func", "data-arg", function () {
+ * //...
+ * }(),
+ * };
+ */
+ notify: function Utils_notify(prefix) {
+ return function NotifyMaker(name, data, func) {
+ let thisArg = this;
+ let notify = function(state, subject) {
+ let mesg = prefix + name + ":" + state;
+ thisArg._log.trace("Event: " + mesg);
+ Observers.notify(mesg, subject, data);
+ };
+
+ return function WrappedNotify() {
+ try {
+ notify("start", null);
+ let ret = func.call(thisArg);
+ notify("finish", ret);
+ return ret;
+ }
+ catch(ex) {
+ notify("error", ex);
+ throw ex;
+ }
+ };
+ };
+ },
+
+ /**
+ * GUIDs are 9 random bytes encoded with base64url (RFC 4648).
+ * That makes them 12 characters long with 72 bits of entropy.
+ */
+ makeGUID: function makeGUID() {
+ return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9));
+ },
+
+ _base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
+ checkGUID: function checkGUID(guid) {
+ return !!guid && this._base64url_regex.test(guid);
+ },
+
+ /**
+ * Add a simple getter/setter to an object that defers access of a property
+ * to an inner property.
+ *
+ * @param obj
+ * Object to add properties to defer in its prototype
+ * @param defer
+ * Property of obj to defer to
+ * @param prop
+ * Property name to defer (or an array of property names)
+ */
+ deferGetSet: function Utils_deferGetSet(obj, defer, prop) {
+ if (Array.isArray(prop))
+ return prop.map(prop => Utils.deferGetSet(obj, defer, prop));
+
+ let prot = obj.prototype;
+
+ // Create a getter if it doesn't exist yet
+ if (!prot.__lookupGetter__(prop)) {
+ prot.__defineGetter__(prop, function () {
+ return this[defer][prop];
+ });
+ }
+
+ // Create a setter if it doesn't exist yet
+ if (!prot.__lookupSetter__(prop)) {
+ prot.__defineSetter__(prop, function (val) {
+ this[defer][prop] = val;
+ });
+ }
+ },
+
+ lazyStrings: function Weave_lazyStrings(name) {
+ let bundle = "chrome://weave/locale/" + name + ".properties";
+ return () => new StringBundle(bundle);
+ },
+
+ deepEquals: function eq(a, b) {
+ // If they're triple equals, then it must be equals!
+ if (a === b)
+ return true;
+
+ // If they weren't equal, they must be objects to be different
+ if (typeof a != "object" || typeof b != "object")
+ return false;
+
+ // But null objects won't have properties to compare
+ if (a === null || b === null)
+ return false;
+
+ // Make sure all of a's keys have a matching value in b
+ for (let k in a)
+ if (!eq(a[k], b[k]))
+ return false;
+
+ // Do the same for b's keys but skip those that we already checked
+ for (let k in b)
+ if (!(k in a) && !eq(a[k], b[k]))
+ return false;
+
+ return true;
+ },
+
+ // Generator and discriminator for HMAC exceptions.
+ // Split these out in case we want to make them richer in future, and to
+ // avoid inevitable confusion if the message changes.
+ throwHMACMismatch: function throwHMACMismatch(shouldBe, is) {
+ throw "Record SHA256 HMAC mismatch: should be " + shouldBe + ", is " + is;
+ },
+
+ isHMACMismatch: function isHMACMismatch(ex) {
+ const hmacFail = "Record SHA256 HMAC mismatch: ";
+ return ex && ex.indexOf && (ex.indexOf(hmacFail) == 0);
+ },
+
+ /**
+ * Turn RFC 4648 base32 into our own user-friendly version.
+ * ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
+ * becomes
+ * abcdefghijk8mn9pqrstuvwxyz234567
+ */
+ base32ToFriendly: function base32ToFriendly(input) {
+ return input.toLowerCase()
+ .replace("l", '8', "g")
+ .replace("o", '9', "g");
+ },
+
+ base32FromFriendly: function base32FromFriendly(input) {
+ return input.toUpperCase()
+ .replace("8", 'L', "g")
+ .replace("9", 'O', "g");
+ },
+
+ /**
+ * Key manipulation.
+ */
+
+ // Return an octet string in friendly base32 *with no trailing =*.
+ encodeKeyBase32: function encodeKeyBase32(keyData) {
+ return Utils.base32ToFriendly(
+ Utils.encodeBase32(keyData))
+ .slice(0, SYNC_KEY_ENCODED_LENGTH);
+ },
+
+ decodeKeyBase32: function decodeKeyBase32(encoded) {
+ return Utils.decodeBase32(
+ Utils.base32FromFriendly(
+ Utils.normalizePassphrase(encoded)))
+ .slice(0, SYNC_KEY_DECODED_LENGTH);
+ },
+
+ base64Key: function base64Key(keyData) {
+ return btoa(keyData);
+ },
+
+ /**
+ * N.B., salt should be base64 encoded, even though we have to decode
+ * it later!
+ */
+ derivePresentableKeyFromPassphrase : function derivePresentableKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
+ let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
+ forceJS);
+ return Utils.encodeKeyBase32(k);
+ },
+
+ /**
+ * N.B., salt should be base64 encoded, even though we have to decode
+ * it later!
+ */
+ deriveEncodedKeyFromPassphrase : function deriveEncodedKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
+ let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
+ forceJS);
+ return Utils.base64Key(k);
+ },
+
+ /**
+ * Take a base64-encoded 128-bit AES key, returning it as five groups of five
+ * uppercase alphanumeric characters, separated by hyphens.
+ * A.K.A. base64-to-base32 encoding.
+ */
+ presentEncodedKeyAsSyncKey : function presentEncodedKeyAsSyncKey(encodedKey) {
+ return Utils.encodeKeyBase32(atob(encodedKey));
+ },
+
+ /**
+ * Load a JSON file from disk in the profile directory.
+ *
+ * @param filePath
+ * JSON file path load from profile. Loaded file will be
+ * <profile>/<filePath>.json. i.e. Do not specify the ".json"
+ * extension.
+ * @param that
+ * Object to use for logging and "this" for callback.
+ * @param callback
+ * Function to process json object as its first argument. If the file
+ * could not be loaded, the first argument will be undefined.
+ */
+ jsonLoad: Task.async(function*(filePath, that, callback) {
+ let path;
+ try {
+ path = OS.Path.normalize(OS.Path.join(OS.Constants.Path.profileDir, "weave", filePath + ".json"));
+ } catch (e) {
+ if (that._log) {
+ that._log.debug("Path join error: " + e);
+ }
+ }
+
+ if (that._log) {
+ that._log.trace("Loading json from disk: " + path);
+ }
+
+ let json;
+
+ try {
+ json = yield CommonUtils.readJSON(path);
+ } catch (e) {
+ if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
+ // Ignore non-existent files, but explicitly return null.
+ json = null;
+ } else {
+ if (that._log) {
+ that._log.debug("Failed to load json", e);
+ }
+ }
+ }
+ if (callback) {
+ callback.call(that, json);
+ }
+ }),
+
+ /**
+ * Save a json-able object to disk in the profile directory.
+ *
+ * @param filePath
+ * JSON file path save to <filePath>.json
+ * @param that
+ * Object to use for logging and "this" for callback
+ * @param obj
+ * Function to provide json-able object to save. If this isn't a
+ * function, it'll be used as the object to make a json string.
+ * @param callback
+ * Function called when the write has been performed. Optional.
+ * The first argument will be a Components.results error
+ * constant on error or null if no error was encountered (and
+ * the file saved successfully).
+ */
+ jsonSave: Task.async(function*(filePath, that, obj, callback) {
+ let path = OS.Path.join(OS.Constants.Path.profileDir, "weave",
+ ...(filePath + ".json").split("/"));
+ let dir = OS.Path.dirname(path);
+ let error = null;
+
+ try {
+ yield OS.File.makeDir(dir, { from: OS.Constants.Path.profileDir });
+
+ if (that._log) {
+ that._log.trace("Saving json to disk: " + path);
+ }
+
+ let json = typeof obj == "function" ? obj.call(that) : obj;
+
+ yield CommonUtils.writeJSON(json, path);
+ } catch (e) {
+ error = e
+ }
+
+ if (typeof callback == "function") {
+ callback.call(that, error);
+ }
+ }),
+
+ getErrorString: function Utils_getErrorString(error, args) {
+ try {
+ return Str.errors.get(error, args || null);
+ } catch (e) {}
+
+ // basically returns "Unknown Error"
+ return Str.errors.get("error.reason.unknown");
+ },
+
+ /**
+ * Generate 26 characters.
+ */
+ generatePassphrase: function generatePassphrase() {
+ // Note that this is a different base32 alphabet to the one we use for
+ // other tasks. It's lowercase, uses different letters, and needs to be
+ // decoded with decodeKeyBase32, not just decodeBase32.
+ return Utils.encodeKeyBase32(CryptoUtils.generateRandomBytes(16));
+ },
+
+ /**
+ * The following are the methods supported for UI use:
+ *
+ * * isPassphrase:
+ * determines whether a string is either a normalized or presentable
+ * passphrase.
+ * * hyphenatePassphrase:
+ * present a normalized passphrase for display. This might actually
+ * perform work beyond just hyphenation; sorry.
+ * * hyphenatePartialPassphrase:
+ * present a fragment of a normalized passphrase for display.
+ * * normalizePassphrase:
+ * take a presentable passphrase and reduce it to a normalized
+ * representation for storage. normalizePassphrase can safely be called
+ * on normalized input.
+ * * normalizeAccount:
+ * take user input for account/username, cleaning up appropriately.
+ */
+
+ isPassphrase: function(s) {
+ if (s) {
+ return /^[abcdefghijkmnpqrstuvwxyz23456789]{26}$/.test(Utils.normalizePassphrase(s));
+ }
+ return false;
+ },
+
+ /**
+ * Hyphenate a passphrase (26 characters) into groups.
+ * abbbbccccddddeeeeffffggggh
+ * =>
+ * a-bbbbc-cccdd-ddeee-effff-ggggh
+ */
+ hyphenatePassphrase: function hyphenatePassphrase(passphrase) {
+ // For now, these are the same.
+ return Utils.hyphenatePartialPassphrase(passphrase, true);
+ },
+
+ hyphenatePartialPassphrase: function hyphenatePartialPassphrase(passphrase, omitTrailingDash) {
+ if (!passphrase)
+ return null;
+
+ // Get the raw data input. Just base32.
+ let data = passphrase.toLowerCase().replace(/[^abcdefghijkmnpqrstuvwxyz23456789]/g, "");
+
+ // This is the neatest way to do this.
+ if ((data.length == 1) && !omitTrailingDash)
+ return data + "-";
+
+ // Hyphenate it.
+ let y = data.substr(0,1);
+ let z = data.substr(1).replace(/(.{1,5})/g, "-$1");
+
+ // Correct length? We're done.
+ if ((z.length == 30) || omitTrailingDash)
+ return y + z;
+
+ // Add a trailing dash if appropriate.
+ return (y + z.replace(/([^-]{5})$/, "$1-")).substr(0, SYNC_KEY_HYPHENATED_LENGTH);
+ },
+
+ normalizePassphrase: function normalizePassphrase(pp) {
+ // Short var name... have you seen the lines below?!
+ // Allow leading and trailing whitespace.
+ pp = pp.trim().toLowerCase();
+
+ // 20-char sync key.
+ if (pp.length == 23 &&
+ [5, 11, 17].every(function(i) pp[i] == '-')) {
+
+ return pp.slice(0, 5) + pp.slice(6, 11)
+ + pp.slice(12, 17) + pp.slice(18, 23);
+ }
+
+ // "Modern" 26-char key.
+ if (pp.length == 31 &&
+ [1, 7, 13, 19, 25].every(function(i) pp[i] == '-')) {
+
+ return pp.slice(0, 1) + pp.slice(2, 7)
+ + pp.slice(8, 13) + pp.slice(14, 19)
+ + pp.slice(20, 25) + pp.slice(26, 31);
+ }
+
+ // Something else -- just return.
+ return pp;
+ },
+
+ normalizeAccount: function normalizeAccount(acc) {
+ return acc.trim();
+ },
+
+ /**
+ * Create an array like the first but without elements of the second. Reuse
+ * arrays if possible.
+ */
+ arraySub: function arraySub(minuend, subtrahend) {
+ if (!minuend.length || !subtrahend.length)
+ return minuend;
+ return minuend.filter(i => subtrahend.indexOf(i) == -1);
+ },
+
+ /**
+ * Build the union of two arrays. Reuse arrays if possible.
+ */
+ arrayUnion: function arrayUnion(foo, bar) {
+ if (!foo.length)
+ return bar;
+ if (!bar.length)
+ return foo;
+ return foo.concat(Utils.arraySub(bar, foo));
+ },
+
+ bind2: function Async_bind2(object, method) {
+ return function innerBind() { return method.apply(object, arguments); };
+ },
+
+ /**
+ * Is there a master password configured, regardless of current lock state?
+ */
+ mpEnabled: function mpEnabled() {
+ let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"]
+ .getService(Ci.nsIPKCS11ModuleDB);
+ let sdrSlot = modules.findSlotByName("");
+ let status = sdrSlot.status;
+ let slots = Ci.nsIPKCS11Slot;
+
+ return status != slots.SLOT_UNINITIALIZED && status != slots.SLOT_READY;
+ },
+
+ /**
+ * Is there a master password configured and currently locked?
+ */
+ mpLocked: function mpLocked() {
+ let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"]
+ .getService(Ci.nsIPKCS11ModuleDB);
+ let sdrSlot = modules.findSlotByName("");
+ let status = sdrSlot.status;
+ let slots = Ci.nsIPKCS11Slot;
+
+ if (status == slots.SLOT_READY || status == slots.SLOT_LOGGED_IN
+ || status == slots.SLOT_UNINITIALIZED)
+ return false;
+
+ if (status == slots.SLOT_NOT_LOGGED_IN)
+ return true;
+
+ // something wacky happened, pretend MP is locked
+ return true;
+ },
+
+ // If Master Password is enabled and locked, present a dialog to unlock it.
+ // Return whether the system is unlocked.
+ ensureMPUnlocked: function ensureMPUnlocked() {
+ if (!Utils.mpLocked()) {
+ return true;
+ }
+ let sdr = Cc["@mozilla.org/security/sdr;1"]
+ .getService(Ci.nsISecretDecoderRing);
+ try {
+ sdr.encryptString("bacon");
+ return true;
+ } catch(e) {}
+ return false;
+ },
+
+ /**
+ * Return a value for a backoff interval. Maximum is eight hours, unless
+ * Status.backoffInterval is higher.
+ *
+ */
+ calculateBackoff: function calculateBackoff(attempts, baseInterval,
+ statusInterval) {
+ let backoffInterval = attempts *
+ (Math.floor(Math.random() * baseInterval) +
+ baseInterval);
+ return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL),
+ statusInterval);
+ },
+
+ /**
+ * Return a set of hostnames (including the protocol) which may have
+ * credentials for sync itself stored in the login manager.
+ *
+ * In general, these hosts will not have their passwords synced, will be
+ * reset when we drop sync credentials, etc.
+ */
+ getSyncCredentialsHosts: function() {
+ let result = new Set(this.getSyncCredentialsHostsLegacy());
+ return result;
+ },
+
+ /*
+ * Get the "legacy" identity hosts.
+ */
+ getSyncCredentialsHostsLegacy: function() {
+ // the legacy sync host
+ return new Set([PWDMGR_HOST]);
+ },
+
+ getDefaultDeviceName() {
+ // Generate a client name if we don't have a useful one yet
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let user = env.get("USER") || env.get("USERNAME") ||
+ Svc.Prefs.get("account") || Svc.Prefs.get("username");
+ // A little hack for people using the the moz-build environment on Windows
+ // which sets USER to the literal "%USERNAME%" (yes, really)
+ if (user == "%USERNAME%" && env.get("USERNAME")) {
+ user = env.get("USERNAME");
+ }
+
+ let brand = new StringBundle("chrome://branding/locale/brand.properties");
+ let brandName = brand.get("brandShortName");
+
+ let appName;
+ try {
+ let syncStrings = new StringBundle("chrome://weave/locale/sync.properties");
+ appName = syncStrings.getFormattedString("sync.defaultAccountApplication", [brandName]);
+ } catch (ex) {}
+ appName = appName || brandName;
+
+ let system =
+ // 'device' is defined on unix systems
+ Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("device") ||
+ // hostname of the system, usually assigned by the user or admin
+ Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("host") ||
+ // fall back on ua info string
+ Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
+
+ return Str.sync.get("client.name2", [user, appName, system]);
+ }
+};
+
+XPCOMUtils.defineLazyGetter(Utils, "_utf8Converter", function() {
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ return converter;
+});
+
+/*
+ * Commonly-used services
+ */
+this.Svc = {};
+Svc.Prefs = new Preferences(PREFS_BRANCH);
+Svc.DefaultPrefs = new Preferences({branch: PREFS_BRANCH, defaultBranch: true});
+Svc.Obs = Observers;
+
+var _sessionCID = Services.appinfo.ID == SEAMONKEY_ID ?
+ "@mozilla.org/suite/sessionstore;1" :
+ "@mozilla.org/browser/sessionstore;1";
+
+[
+ ["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"],
+ ["Session", _sessionCID, "nsISessionStore"]
+].forEach(function([name, contract, iface]) {
+ XPCOMUtils.defineLazyServiceGetter(Svc, name, contract, iface);
+});
+
+XPCOMUtils.defineLazyModuleGetter(Svc, "FormHistory", "resource://gre/modules/FormHistory.jsm");
+
+Svc.__defineGetter__("Crypto", function() {
+ let cryptoSvc;
+ let ns = {};
+ Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
+ cryptoSvc = new ns.WeaveCrypto();
+ delete Svc.Crypto;
+ return Svc.Crypto = cryptoSvc;
+});
+
+this.Str = {};
+["errors", "sync"].forEach(function(lazy) {
+ XPCOMUtils.defineLazyGetter(Str, lazy, Utils.lazyStrings(lazy));
+});
+
+Svc.Obs.add("xpcom-shutdown", function () {
+ for (let name in Svc)
+ delete Svc[name];
+});
diff --git a/components/weave/weave-prefs.js b/components/weave/weave-prefs.js
new file mode 100644
index 000000000..dfce84767
--- /dev/null
+++ b/components/weave/weave-prefs.js
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pref("services.sync.serverURL", "https://pmsync.palemoon.org/sync/index.php/");
+pref("services.sync.userURL", "user/");
+pref("services.sync.miscURL", "misc/");
+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/keyhelp.shtml");
+
+pref("services.sync.lastversion", "firstrun");
+pref("services.sync.sendVersionInfo", true);
+
+pref("services.sync.scheduler.eolInterval", 604800); // 1 week
+pref("services.sync.scheduler.idleInterval", 3600); // 1 hour
+pref("services.sync.scheduler.activeInterval", 600); // 10 minutes
+pref("services.sync.scheduler.immediateInterval", 90); // 1.5 minutes
+pref("services.sync.scheduler.idleTime", 300); // 5 minutes
+
+pref("services.sync.scheduler.fxa.singleDeviceInterval", 3600); // 1 hour
+pref("services.sync.scheduler.sync11.singleDeviceInterval", 86400); // 1 day
+
+pref("services.sync.errorhandler.networkFailureReportTimeout", 1209600); // 2 weeks
+
+// Our engines.
+pref("services.sync.engine.addons", false);
+pref("services.sync.engine.bookmarks", true);
+pref("services.sync.engine.history", true);
+pref("services.sync.engine.passwords", true);
+pref("services.sync.engine.prefs", true);
+pref("services.sync.engine.tabs", true);
+pref("services.sync.engine.tabs.filteredUrls", "^(about:.*|chrome://weave/.*|wyciwyg:.*|file:.*|blob:.*)$");
+
+pref("services.sync.jpake.serverURL", "https://keyserver.palemoon.org/");
+pref("services.sync.jpake.pollInterval", 1000);
+pref("services.sync.jpake.firstMsgMaxTries", 300); // 5 minutes
+pref("services.sync.jpake.lastMsgMaxTries", 300); // 5 minutes
+pref("services.sync.jpake.maxTries", 10);
+
+// Allow add-ons to be synced from non-trusted sources.
+pref("services.sync.addons.ignoreRepositoryChecking", true);
+
+// If true, add-on sync ignores changes to the user-enabled flag. This
+// allows people to have the same set of add-ons installed across all
+// profiles while maintaining different enabled states.
+pref("services.sync.addons.ignoreUserEnabledChanges", false);
+
+// Comma-delimited list of hostnames to trust for add-on install.
+pref("services.sync.addons.trustedSourceHostnames", "addons.palemoon.org,addons.mozilla.org");
+
+pref("services.sync.log.appender.console", "Warn");
+pref("services.sync.log.appender.dump", "Error");
+pref("services.sync.log.appender.file.level", "Trace");
+pref("services.sync.log.appender.file.logOnError", true);
+pref("services.sync.log.appender.file.logOnSuccess", false);
+pref("services.sync.log.appender.file.maxErrorAge", 864000); // 10 days
+pref("services.sync.log.rootLogger", "Debug");
+pref("services.sync.log.logger.addonutils", "Debug");
+pref("services.sync.log.logger.declined", "Debug");
+pref("services.sync.log.logger.service.main", "Debug");
+pref("services.sync.log.logger.status", "Debug");
+pref("services.sync.log.logger.authenticator", "Debug");
+pref("services.sync.log.logger.network.resources", "Debug");
+pref("services.sync.log.logger.service.jpakeclient", "Debug");
+pref("services.sync.log.logger.engine.bookmarks", "Debug");
+pref("services.sync.log.logger.engine.clients", "Debug");
+pref("services.sync.log.logger.engine.forms", "Debug");
+pref("services.sync.log.logger.engine.history", "Debug");
+pref("services.sync.log.logger.engine.passwords", "Debug");
+pref("services.sync.log.logger.engine.prefs", "Debug");
+pref("services.sync.log.logger.engine.tabs", "Debug");
+pref("services.sync.log.logger.engine.addons", "Debug");
+pref("services.sync.log.logger.engine.apps", "Debug");
+pref("services.sync.log.logger.identity", "Debug");
+pref("services.sync.log.logger.userapi", "Debug");
+pref("services.sync.log.cryptoDebug", false);
+
+pref("services.sync.tokenServerURI", "https://token.services.mozilla.com/1.0/sync/1.5");
+
+pref("services.sync.fxa.termsURL", "https://accounts.firefox.com/legal/terms");
+pref("services.sync.fxa.privacyURL", "https://accounts.firefox.com/legal/privacy");
diff --git a/components/webbrowser/moz.build b/components/webbrowser/moz.build
new file mode 100644
index 000000000..c92c5e2b8
--- /dev/null
+++ b/components/webbrowser/moz.build
@@ -0,0 +1,46 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsCWebBrowser.idl',
+ 'public/nsICommandHandler.idl',
+ 'public/nsIEmbeddingSiteWindow.idl',
+ 'public/nsIWebBrowser.idl',
+ 'public/nsIWebBrowserChrome.idl',
+ 'public/nsIWebBrowserChrome2.idl',
+ 'public/nsIWebBrowserChrome3.idl',
+ 'public/nsIWebBrowserChromeFocus.idl',
+ 'public/nsIWebBrowserFocus.idl',
+ 'public/nsIWebBrowserSetup.idl',
+ 'public/nsIWebBrowserStream.idl',
+]
+
+if CONFIG['NS_PRINTING']:
+ XPIDL_SOURCES += [
+ 'public/nsIPrintingPromptService.idl',
+ 'public/nsIWebBrowserPrint.idl',
+ ]
+
+SOURCES += [
+ 'src/nsCommandHandler.cpp',
+ 'src/nsEmbedStream.cpp',
+ 'src/nsWebBrowser.cpp',
+ 'src/nsWebBrowserContentPolicy.cpp',
+ 'src/nsWebBrowserModule.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/svg',
+ '/layout/style',
+ '/system/docshell/base',
+ 'src',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+XPIDL_MODULE = 'webBrowser_core'
+FINAL_LIBRARY = 'xul'
diff --git a/components/webbrowser/public/nsCWebBrowser.idl b/components/webbrowser/public/nsCWebBrowser.idl
new file mode 100644
index 000000000..21927f7c7
--- /dev/null
+++ b/components/webbrowser/public/nsCWebBrowser.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsIWebBrowser.idl"
+#include "nsIBaseWindow.idl"
+#include "nsIScrollable.idl"
+#include "nsITextScroll.idl"
+
+/*
+nsCWebBrowser implements:
+-------------------------
+nsIWebBrowser
+nsIDocShellTreeItem
+nsIWebNavigation
+nsIWebProgress
+nsIBaseWindow
+nsIScrollable
+nsITextScroll
+nsIInterfaceRequestor
+
+
+Outwardly communicates with:
+----------------------------
+nsIWebBrowserChrome
+nsIBaseWindow
+nsIInterfaceRequestor
+*/
+
+%{ C++
+#include "nsEmbedCID.h"
+%}
diff --git a/components/webbrowser/public/nsICommandHandler.idl b/components/webbrowser/public/nsICommandHandler.idl
new file mode 100644
index 000000000..15a2fd23b
--- /dev/null
+++ b/components/webbrowser/public/nsICommandHandler.idl
@@ -0,0 +1,40 @@
+/* -*- 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 "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+[scriptable, uuid(08aed3cc-69f7-47ba-a110-f2efa8a6d7ea)]
+interface nsICommandHandlerInit : nsISupports
+{
+ attribute mozIDOMWindowProxy window;
+};
+
+[scriptable, uuid(34A4FCF0-66FC-11d4-9528-0020183BF181)]
+interface nsICommandHandler : nsISupports
+{
+ /*
+ * Execute the specified command with the specified parameters and return
+ * the result to the caller. The format of the command, parameters and
+ * the result are determined by the acutal implementation.
+ */
+ string exec(in string aCommand, in string aParameters);
+ /*
+ * Query the status of the specified command with the specified parameters
+ * and return the result to the caller. The format of the command,
+ * parameters and the result are determined by the implementation.
+ */
+ string query(in string aCommand, in string aParameters);
+};
+
+%{ C++
+// {3A449110-66FD-11d4-9528-0020183BF181} -
+#define NS_COMMANDHANDLER_CID \
+{ 0x3a449110, 0x66fd, 0x11d4, { 0x95, 0x28, 0x0, 0x20, 0x18, 0x3b, 0xf1, 0x81 } }
+#define NS_COMMANDHANDLER_CONTRACTID \
+"@mozilla.org/embedding/browser/nsCommandHandler;1"
+%}
+
diff --git a/components/webbrowser/public/nsIEmbeddingSiteWindow.idl b/components/webbrowser/public/nsIEmbeddingSiteWindow.idl
new file mode 100644
index 000000000..a8eefacb4
--- /dev/null
+++ b/components/webbrowser/public/nsIEmbeddingSiteWindow.idl
@@ -0,0 +1,157 @@
+/* -*- 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 "nsISupports.idl"
+
+/* THIS IS A PUBLIC EMBEDDING API */
+
+/**
+ * The nsIEmbeddingSiteWindow is implemented by the embedder to provide
+ * Gecko with the means to call up to the host to resize the window,
+ * hide or show it and set/get its title.
+ */
+[scriptable, uuid(0b976267-4aaa-4f36-a2d4-27b5ca8d73bb)]
+interface nsIEmbeddingSiteWindow : nsISupports
+{
+ /**
+ * Flag indicates that position of the top left corner of the outer area
+ * is required/specified.
+ *
+ * @see setDimensions
+ * @see getDimensions
+ */
+ const unsigned long DIM_FLAGS_POSITION = 1;
+
+ /**
+ * Flag indicates that the size of the inner area is required/specified.
+ *
+ * @note The inner and outer flags are mutually exclusive and it is
+ * invalid to combine them.
+ *
+ * @see setDimensions
+ * @see getDimensions
+ * @see DIM_FLAGS_SIZE_OUTER
+ */
+ const unsigned long DIM_FLAGS_SIZE_INNER = 2;
+
+ /**
+ * Flag indicates that the size of the outer area is required/specified.
+ *
+ * @see setDimensions
+ * @see getDimensions
+ * @see DIM_FLAGS_SIZE_INNER
+ */
+ const unsigned long DIM_FLAGS_SIZE_OUTER = 4;
+
+ /**
+ * Flag indicates that the x parameter should be ignored.
+ *
+ * @see setDimensions
+ */
+ const unsigned long DIM_FLAGS_IGNORE_X = 8;
+
+ /**
+ * Flag indicates that the y parameter should be ignored.
+ *
+ * @see setDimensions
+ */
+ const unsigned long DIM_FLAGS_IGNORE_Y = 16;
+
+ /**
+ * Flag indicates that the cx parameter should be ignored.
+ *
+ * @see setDimensions
+ */
+ const unsigned long DIM_FLAGS_IGNORE_CX = 32;
+
+ /**
+ * Flag indicates that the cy parameter should be ignored.
+ *
+ * @see setDimensions
+ */
+ const unsigned long DIM_FLAGS_IGNORE_CY = 64;
+
+
+ /**
+ * Sets the dimensions for the window; the position & size. The
+ * flags to indicate what the caller wants to set and whether the size
+ * refers to the inner or outer area. The inner area refers to just
+ * the embedded area, wheras the outer area can also include any
+ * surrounding chrome, window frame, title bar, and so on.
+ *
+ * @param flags Combination of position, inner and outer size flags.
+ * The ignore flags are telling the parent to use the
+ * current values for those dimensions and ignore the
+ * corresponding parameters the child sends.
+ * @param x Left hand corner of the outer area.
+ * @param y Top corner of the outer area.
+ * @param cx Width of the inner or outer area.
+ * @param cy Height of the inner or outer area.
+ *
+ * @return <code>NS_OK</code> if operation was performed correctly;
+ * <code>NS_ERROR_UNEXPECTED</code> if window could not be
+ * destroyed;
+ * <code>NS_ERROR_INVALID_ARG</code> for bad flag combination
+ * or illegal dimensions.
+ *
+ * @see getDimensions
+ * @see DIM_FLAGS_POSITION
+ * @see DIM_FLAGS_SIZE_OUTER
+ * @see DIM_FLAGS_SIZE_INNER
+ */
+ void setDimensions(in unsigned long flags, in long x, in long y, in long cx, in long cy);
+
+ /**
+ * Gets the dimensions of the window. The caller may pass
+ * <CODE>nullptr</CODE> for any value it is uninterested in receiving.
+ *
+ * @param flags Combination of position, inner and outer size flag .
+ * @param x Left hand corner of the outer area; or <CODE>nullptr</CODE>.
+ * @param y Top corner of the outer area; or <CODE>nullptr</CODE>.
+ * @param cx Width of the inner or outer area; or <CODE>nullptr</CODE>.
+ * @param cy Height of the inner or outer area; or <CODE>nullptr</CODE>.
+ *
+ * @see setDimensions
+ * @see DIM_FLAGS_POSITION
+ * @see DIM_FLAGS_SIZE_OUTER
+ * @see DIM_FLAGS_SIZE_INNER
+ */
+ void getDimensions(in unsigned long flags, out long x, out long y, out long cx, out long cy);
+
+ /**
+ * Give the window focus.
+ */
+ void setFocus();
+
+ /**
+ * Visibility of the window.
+ */
+ attribute boolean visibility;
+
+ /**
+ * Title of the window.
+ */
+ attribute wstring title;
+
+ /**
+ * Native window for the site's window. The implementor should copy the
+ * native window object into the address supplied by the caller. The
+ * type of the native window that the address refers to is platform
+ * and OS specific as follows:
+ *
+ * <ul>
+ * <li>On Win32 it is an <CODE>HWND</CODE>.</li>
+ * <li>On MacOS this is a <CODE>WindowPtr</CODE>.</li>
+ * <li>On GTK this is a <CODE>GtkWidget*</CODE>.</li>
+ * </ul>
+ */
+ [noscript] readonly attribute voidPtr siteWindow;
+
+ /**
+ * Blur the window. This should unfocus the window and send an onblur event.
+ */
+ void blur();
+};
diff --git a/components/webbrowser/public/nsIPrintPreviewNavigation.idl b/components/webbrowser/public/nsIPrintPreviewNavigation.idl
new file mode 100644
index 000000000..87a39ff72
--- /dev/null
+++ b/components/webbrowser/public/nsIPrintPreviewNavigation.idl
@@ -0,0 +1,52 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+
+/**
+ * The nsIPrintPreviewNavigation
+ */
+[scriptable, uuid(8148E3F1-2E8B-11d5-A86C-00105A183419)]
+interface nsIPrintPreviewNavigation : nsISupports
+{
+
+ readonly attribute long pageCount;
+
+
+ /**
+ * Preview the next Page
+ *
+ * Return - PR_TRUE if success
+ */
+ boolean nextPage();
+
+ /**
+ * Preview the previous Page
+ *
+ * Return - PR_TRUE if success
+ */
+ boolean previousPage();
+
+ /**
+ * Go to a page to preview
+ *
+ * aPageNumber - Page to go preview
+ * Return - PR_TRUE if success
+ */
+ boolean goToPage(unsigned long aPageNumber);
+
+
+ /**
+ * Skip pages
+ *
+ * aNumPages - number of pages to skip including the current page. Neg. goes back
+ * Return - true if success
+ */
+ boolean skipPages(long aNumPages);
+
+
+};
diff --git a/components/webbrowser/public/nsIPrintingPromptService.idl b/components/webbrowser/public/nsIPrintingPromptService.idl
new file mode 100644
index 000000000..81d79b57c
--- /dev/null
+++ b/components/webbrowser/public/nsIPrintingPromptService.idl
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/* Doc interface here */
+
+#include "nsISupports.idl"
+#include "nsIWebBrowserPrint.idl"
+#include "nsIWebProgressListener.idl"
+#include "nsIPrintProgressParams.idl"
+#include "nsIPrintSettings.idl"
+#include "nsIObserver.idl"
+
+interface nsIDOMWindow;
+
+[scriptable, uuid(328daa3e-09e4-455f-bb6f-0a921766042f)]
+interface nsIPrintingPromptService : nsISupports
+{
+ /**
+ * This service enables embedders to implement their own Print and Progress Dialogs.
+ * Each platform has a "base" or "basckstop" implementation of the service. The
+ * service is automatically registered at start up.
+ *
+ * Historically, platform toolkits with native dialogs have implemented them in the GFX layer
+ * Usually they were displayed when a new DeviceContextSpec specific to that platform
+ * was created.
+ *
+ * Windows: The GFX layer no longers supports default toolkit behavior for displaying the
+ * native Print Dialog.
+ * If an embedder implemented service returns any error code (other than NS_ERROR_ABORT)
+ * printing will terminate.
+ *
+ * Returning NS_OK assumes that the PrintSettings object was correctly filled in and
+ * if it does not have valid fields for printer name, etc. it may also terminate.
+ *
+ * Defaults for platform service:
+ * showPrintDialog - displays a native dialog
+ * showPageSetup - displays a XUL dialog
+ * showProgress - displays a XUL dialog
+ * showPrinterProperties - n/a
+ *
+ * Summary for Windows Embedders:
+ * Stated once again: There is no "fallback" native platform support in GFX for the
+ * displaying of the native print dialog. The current default implementation for Windows
+ * display a native print dialog but a XUL-based progress dialog.
+ * If you wish to have a native progress dialog on Windows you will have to create and
+ * register your own service.
+ *
+ * Note: The Windows version Mozilla implements this service which is
+ * automatically built and registered for you. You can use it as an example.
+ * It is located at "mozilla/embedding/components/printingui/win". That service
+ * is capable of displaying a native print dialog and a XUL progress dialog.
+ *
+ * To fly your own dialog you may:
+ *
+ * 1) Implement this service to display at least the Print Dialog and a Print Progress Dialog
+ * or you may implement just one of the dialogs and pass back NS_ERROR_NOT_IMPLEMENTED
+ * for any of the others.
+ *
+ * 2) For the Print Dialog:
+ * You may stub out this service by having all the methods return NS_ERROR_NOT_IMPLEMENTED.
+ * You can then fly you own dialog and then properly fill in the PrintSettings object
+ * before calling nsIWebBrowserPrint's Print method. If you stub out this service
+ * you MUST set "printSilent" to true, if you do not, Printing will terminate and an
+ * error dialog will be displayed.
+ *
+ * Mac: The GFX layer still supports default toolkit behavior for displaying the Print Dialog.
+ * If an embedder implemented service returns NS_ERROR_NOT_IMPLEMENTED for "showPrintDialog"
+ * The toolkit will display the native print dialog.
+ *
+ * Defaults for platform service:
+ * Mac OS9: showPrintDialog - displays a native dialog
+ * showPageSetup - displays a native dialog
+ * showProgress - displays a XUL dialog
+ * showPrinterProperties - n/a
+ *
+ * Mac OSX: showPrintDialog - displays a native dialog
+ * showPageSetup - displays a native dialog
+ * showProgress - not implemented (provided by OS)
+ * showPrinterProperties - n/a
+ *
+ * GTK: There are no native dialog for GTK.
+ *
+ * Defaults for platform service:
+ * showPrintDialog - displays a XUL dialog
+ * showPageSetup - displays a XUL dialog
+ * showProgress - displays a XUL dialog
+ * showPrinterProperties - displays a XUL dialog
+ *
+ */
+
+
+
+ /**
+ * Show the Print Dialog
+ *
+ * @param parent - a DOM windows the dialog will be parented to (required)
+ * @param webBrowserPrint - represents the document to be printed (required)
+ * @param printSettings - PrintSettings for print "job" (required)
+ *
+ */
+ void showPrintDialog(in mozIDOMWindowProxy parent,
+ in nsIWebBrowserPrint webBrowserPrint,
+ in nsIPrintSettings printSettings);
+
+ /**
+ * Shows the print progress dialog
+ *
+ * @param parent - a DOM windows the dialog will be parented to
+ * @param webBrowserPrint - represents the document to be printed
+ * @param printSettings - PrintSettings for print "job"
+ * @param openDialogObserver - an observer that will be notifed when the dialog is opened
+ * @param isForPrinting - true - for printing, false for print preview
+ * @param webProgressListener - additional listener can be registered for progress notifications
+ * @param printProgressParams - parameter object for passing progress state
+ * @param notifyOnOpen - this indicates that the observer will be notified when the progress
+ * dialog has been opened. If false is returned it means the observer
+ * (usually the caller) shouldn't wait
+ * For Print Preview Progress there is intermediate progress
+ */
+ void showProgress(in mozIDOMWindowProxy parent,
+ in nsIWebBrowserPrint webBrowserPrint,
+ in nsIPrintSettings printSettings,
+ in nsIObserver openDialogObserver,
+ in boolean isForPrinting,
+ out nsIWebProgressListener webProgressListener,
+ out nsIPrintProgressParams printProgressParams,
+ out boolean notifyOnOpen);
+
+ /**
+ * Shows the print progress dialog
+ *
+ * @param parent - a DOM windows the dialog will be parented to (required)
+ * @param printSettings - PrintSettings for page setup (required)
+ * @param aObs - An observer to know if the contents of the Print Settings
+ * object has changed while the dialog is being shown.
+ * For example, some platforms may implement an "Apply" button (not required)
+ */
+ void showPageSetup(in mozIDOMWindowProxy parent,
+ in nsIPrintSettings printSettings,
+ in nsIObserver aObs);
+
+ /**
+ * Sometimes platforms need to bring up a special properties dialog for showing
+ * print specific properties. Although the PrintSettings has a place to set the
+ * printer name, here is is an argument to be clear as to what printer is being
+ * asked to have the properties set for it. The Printer name in the PS is ignored.
+ *
+ * @param parent - a DOM windows the dialog will be parented to (required)
+ * @param printerName - name of printer (required)
+ * @param printSettings - PrintSettings for page setup (required)
+ */
+ void showPrinterProperties(in mozIDOMWindowProxy parent,
+ in wstring printerName,
+ in nsIPrintSettings printSettings);
+
+};
+
+%{C++
+// {260FEDC5-524D-4aa6-9A41-E829F4C78B92}
+#define NS_PRINTINGPROMPTSERVICE_IID \
+ {0x260fedc5, 0x524d, 0x4aa6, { 0x9a, 0x41, 0xe8, 0x29, 0xf4, 0xc7, 0x8b, 0x92}}
+%}
+
diff --git a/components/webbrowser/public/nsIWebBrowser.idl b/components/webbrowser/public/nsIWebBrowser.idl
new file mode 100644
index 000000000..e6ff70ae7
--- /dev/null
+++ b/components/webbrowser/public/nsIWebBrowser.idl
@@ -0,0 +1,163 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIInterfaceRequestor;
+interface nsIWebBrowserChrome;
+interface nsIURIContentListener;
+interface nsIDOMWindow;
+interface mozIDOMWindowProxy;
+interface nsIWeakReference;
+
+%{C++
+namespace mozilla {
+class DocShellOriginAttributes;
+}
+%}
+
+[ref] native const_OriginAttributesRef(const mozilla::DocShellOriginAttributes);
+
+/**
+ * The nsIWebBrowser interface is implemented by web browser objects.
+ * Embedders use this interface during initialisation to associate
+ * the new web browser instance with the embedders chrome and
+ * to register any listeners. The interface may also be used at runtime
+ * to obtain the content DOM window and from that the rest of the DOM.
+ */
+[scriptable, uuid(4052b6da-4faa-4646-b3a1-7e16a01c2dc2)]
+interface nsIWebBrowser : nsISupports
+{
+ /**
+ * Registers a listener of the type specified by the iid to receive
+ * callbacks. The browser stores a weak reference to the listener
+ * to avoid any circular dependencies.
+ * Typically this method will be called to register an object
+ * to receive <CODE>nsIWebProgressListener</CODE> or
+ * <CODE>nsISHistoryListener</CODE> notifications in which case the
+ * the IID is that of the interface.
+ *
+ * @param aListener The listener to be added.
+ * @param aIID The IID of the interface that will be called
+ * on the listener as appropriate.
+ * @return <CODE>NS_OK</CODE> for successful registration;
+ * <CODE>NS_ERROR_INVALID_ARG</CODE> if aIID is not
+ * supposed to be registered using this method;
+ * <CODE>NS_ERROR_FAILURE</CODE> either aListener did not
+ * expose the interface specified by the IID, or some
+ * other internal error occurred.
+ *
+ * @see removeWebBrowserListener
+ * @see nsIWeakReference
+ * @see nsIWebProgressListener
+ * @see nsISHistoryListener
+ *
+ * @return <CODE>NS_OK</CODE>, listener was successfully added;
+ * <CODE>NS_ERROR_INVALID_ARG</CODE>, one of the arguments was
+ * invalid or the object did not implement the interface
+ * specified by the IID.
+ */
+ void addWebBrowserListener(in nsIWeakReference aListener, in nsIIDRef aIID);
+
+ /**
+ * Removes a previously registered listener.
+ *
+ * @param aListener The listener to be removed.
+ * @param aIID The IID of the interface on the listener that will
+ * no longer be called.
+ *
+ * @return <CODE>NS_OK</CODE>, listener was successfully removed;
+ * <CODE>NS_ERROR_INVALID_ARG</CODE> arguments was invalid or
+ * the object did not implement the interface specified by the IID.
+ *
+ * @see addWebBrowserListener
+ * @see nsIWeakReference
+ */
+ void removeWebBrowserListener(in nsIWeakReference aListener, in nsIIDRef aIID);
+
+ /**
+ * The chrome object associated with the browser instance. The embedder
+ * must create one chrome object for <I>each</I> browser object
+ * that is instantiated. The embedder must associate the two by setting
+ * this property to point to the chrome object before creating the browser
+ * window via the browser's <CODE>nsIBaseWindow</CODE> interface.
+ *
+ * The chrome object must also implement <CODE>nsIEmbeddingSiteWindow</CODE>.
+ *
+ * The chrome may optionally implement <CODE>nsIInterfaceRequestor</CODE>,
+ * <CODE>nsIWebBrowserChromeFocus</CODE>,
+ * <CODE>nsIContextMenuListener</CODE> and
+ * <CODE>nsITooltipListener</CODE> to receive additional notifications
+ * from the browser object.
+ *
+ * The chrome object may optionally implement <CODE>nsIWebProgressListener</CODE>
+ * instead of explicitly calling <CODE>addWebBrowserListener</CODE> and
+ * <CODE>removeWebBrowserListener</CODE> to register a progress listener
+ * object. If the implementation does this, it must also implement
+ * <CODE>nsIWeakReference</CODE>.
+ *
+ * @note The implementation should not refcount the supplied chrome
+ * object; it should assume that a non <CODE>nullptr</CODE> value is
+ * always valid. The embedder must explicitly set this value back
+ * to nullptr if the chrome object is destroyed before the browser
+ * object.
+ *
+ * @see nsIBaseWindow
+ * @see nsIWebBrowserChrome
+ * @see nsIEmbeddingSiteWindow
+ * @see nsIInterfaceRequestor
+ * @see nsIWebBrowserChromeFocus
+ * @see nsIContextMenuListener
+ * @see nsITooltipListener
+ * @see nsIWeakReference
+ * @see nsIWebProgressListener
+ */
+ attribute nsIWebBrowserChrome containerWindow;
+
+ /**
+ * URI content listener parent. The embedder may set this property to
+ * their own implementation if they intend to override or prevent
+ * how certain kinds of content are loaded.
+ *
+ * @note If this attribute is set to an object that implements
+ * nsISupportsWeakReference, the implementation should get the
+ * nsIWeakReference and hold that. Otherwise, the implementation
+ * should not refcount this interface; it should assume that a non
+ * null value is always valid. In that case, the embedder should
+ * explicitly set this value back to null if the parent content
+ * listener is destroyed before the browser object.
+ *
+ * @see nsIURIContentListener
+ */
+ attribute nsIURIContentListener parentURIContentListener;
+
+ /**
+ * The top-level DOM window. The embedder may walk the entire
+ * DOM starting from this value.
+ *
+ * @see nsIDOMWindow
+ */
+ readonly attribute mozIDOMWindowProxy contentDOMWindow;
+
+ /**
+ * Whether this web browser is active. Active means that it's visible
+ * enough that we want to avoid certain optimizations like discarding
+ * decoded image data and throttling the refresh driver. In Firefox,
+ * this corresponds to the visible tab.
+ *
+ * Defaults to true. For optimal performance, set it to false when
+ * appropriate.
+ */
+ attribute boolean isActive;
+
+ /**
+ * Set Origin Attributes on the nsIWebBrowser.
+ * The Origin Attributes will be passed to the docshell once it has been
+ * created
+ */
+ [noscript, notxpcom, nostdcall, binaryname(SetOriginAttributes)]
+ void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs);
+};
diff --git a/components/webbrowser/public/nsIWebBrowserChrome.idl b/components/webbrowser/public/nsIWebBrowserChrome.idl
new file mode 100644
index 000000000..32dba4219
--- /dev/null
+++ b/components/webbrowser/public/nsIWebBrowserChrome.idl
@@ -0,0 +1,144 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIWebBrowser;
+interface nsIDocShellTreeItem;
+
+/**
+ * nsIWebBrowserChrome corresponds to the top-level, outermost window
+ * containing an embedded Gecko web browser.
+ */
+
+[scriptable, uuid(E8C414C4-DC38-4BA3-AB4E-EC4CBBE22907)]
+interface nsIWebBrowserChrome : nsISupports
+{
+ const unsigned long STATUS_SCRIPT = 0x00000001;
+ const unsigned long STATUS_LINK = 0x00000003;
+
+ /**
+ * Called when the status text in the chrome needs to be updated.
+ * @param statusType indicates what is setting the text
+ * @param status status string. null is an acceptable value meaning
+ * no status.
+ */
+ void setStatus(in unsigned long statusType, in wstring status);
+
+ /**
+ * The currently loaded WebBrowser. The browser chrome may be
+ * told to set the WebBrowser object to a new object by setting this
+ * attribute. In this case the implementer is responsible for taking the
+ * new WebBrowser object and doing any necessary initialization or setup
+ * as if it had created the WebBrowser itself. This includes positioning
+ * setting up listeners etc.
+ */
+ attribute nsIWebBrowser webBrowser;
+
+ /**
+ * Definitions for the chrome flags
+ */
+ const unsigned long CHROME_DEFAULT = 0x00000001;
+ const unsigned long CHROME_WINDOW_BORDERS = 0x00000002;
+ const unsigned long CHROME_WINDOW_CLOSE = 0x00000004;
+ const unsigned long CHROME_WINDOW_RESIZE = 0x00000008;
+ const unsigned long CHROME_MENUBAR = 0x00000010;
+ const unsigned long CHROME_TOOLBAR = 0x00000020;
+ const unsigned long CHROME_LOCATIONBAR = 0x00000040;
+ const unsigned long CHROME_STATUSBAR = 0x00000080;
+ const unsigned long CHROME_PERSONAL_TOOLBAR = 0x00000100;
+ const unsigned long CHROME_SCROLLBARS = 0x00000200;
+ const unsigned long CHROME_TITLEBAR = 0x00000400;
+ const unsigned long CHROME_EXTRA = 0x00000800;
+
+ // createBrowserWindow specific flags
+ const unsigned long CHROME_WITH_SIZE = 0x00001000;
+ const unsigned long CHROME_WITH_POSITION = 0x00002000;
+
+ // special cases
+ const unsigned long CHROME_WINDOW_MIN = 0x00004000;
+ const unsigned long CHROME_WINDOW_POPUP = 0x00008000;
+
+ // whether to open a new private window. CHROME_NON_PRIVATE_WINDOW
+ // forces the opened window to be non-private, and overrides
+ // CHROME_PRIVATE_WINDOW if it's set. CHROME_PRIVATE_WINDOW
+ // forces the opened window to be private. If neither of these
+ // flags are specified, the opened window will inherit the privacy
+ // status of its opener. If there is no opener window, the new
+ // window will be non-private.
+ //
+ // CHROME_PRIVATE_LIFETIME causes the docshell to affect private-browsing
+ // session lifetime. This flag is currently respected only for remote
+ // docshells.
+ const unsigned long CHROME_PRIVATE_WINDOW = 0x00010000;
+ const unsigned long CHROME_NON_PRIVATE_WINDOW = 0x00020000;
+ const unsigned long CHROME_PRIVATE_LIFETIME = 0x00040000;
+
+ // Whether this window should use remote (out-of-process) tabs.
+ const unsigned long CHROME_REMOTE_WINDOW = 0x00100000;
+
+ // Prevents new window animations on Mac OS X Lion. Ignored on other
+ // platforms.
+ const unsigned long CHROME_MAC_SUPPRESS_ANIMATION = 0x01000000;
+
+ const unsigned long CHROME_WINDOW_RAISED = 0x02000000;
+ const unsigned long CHROME_WINDOW_LOWERED = 0x04000000;
+ const unsigned long CHROME_CENTER_SCREEN = 0x08000000;
+
+ // Make the new window dependent on the parent. This flag is only
+ // meaningful if CHROME_OPENAS_CHROME is set; content windows should not be
+ // dependent.
+ const unsigned long CHROME_DEPENDENT = 0x10000000;
+
+ // Note: The modal style bit just affects the way the window looks and does
+ // mean it's actually modal.
+ const unsigned long CHROME_MODAL = 0x20000000;
+ const unsigned long CHROME_OPENAS_DIALOG = 0x40000000;
+ const unsigned long CHROME_OPENAS_CHROME = 0x80000000;
+
+ const unsigned long CHROME_ALL = 0x00000ffe;
+
+ /**
+ * The chrome flags for this browser chrome. The implementation should
+ * reflect the value of this attribute by hiding or showing its chrome
+ * appropriately.
+ */
+ attribute unsigned long chromeFlags;
+
+ /**
+ * Asks the implementer to destroy the window associated with this
+ * WebBrowser object.
+ */
+ void destroyBrowserWindow();
+
+ /**
+ * Tells the chrome to size itself such that the browser will be the
+ * specified size.
+ * @param aCX new width of the browser
+ * @param aCY new height of the browser
+ */
+ void sizeBrowserTo(in long aCX, in long aCY);
+
+ /**
+ * Shows the window as a modal window.
+ * @return (the function error code) the status value specified by
+ * in exitModalEventLoop.
+ */
+ void showAsModal();
+
+ /**
+ * Is the window modal (that is, currently executing a modal loop)?
+ * @return true if it's a modal window
+ */
+ boolean isWindowModal();
+
+ /**
+ * Exit a modal event loop if we're in one. The implementation
+ * should also exit out of the loop if the window is destroyed.
+ * @param aStatus - the result code to return from showAsModal
+ */
+ void exitModalEventLoop(in nsresult aStatus);
+};
diff --git a/components/webbrowser/public/nsIWebBrowserChrome2.idl b/components/webbrowser/public/nsIWebBrowserChrome2.idl
new file mode 100644
index 000000000..4b479b400
--- /dev/null
+++ b/components/webbrowser/public/nsIWebBrowserChrome2.idl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIWebBrowserChrome.idl"
+
+/**
+ * nsIWebBrowserChrome2 is an extension to nsIWebBrowserChrome.
+ */
+[scriptable, uuid(2585a7b1-7b47-43c4-bf17-c6bf84e09b7b)]
+interface nsIWebBrowserChrome2 : nsIWebBrowserChrome
+{
+ /**
+ * Called when the status text in the chrome needs to be updated. This
+ * method may be called instead of nsIWebBrowserChrome::SetStatus. An
+ * implementor of this method, should still implement SetStatus.
+ *
+ * @param statusType
+ * Indicates what is setting the text.
+ * @param status
+ * Status string. Null is an acceptable value meaning no status.
+ * @param contextNode
+ * An object that provides context pertaining to the status type.
+ * If statusType is STATUS_LINK, then statusContext may be a DOM
+ * node corresponding to the source of the link. This value can
+ * be null if there is no context.
+ */
+ void setStatusWithContext(in unsigned long statusType,
+ in AString statusText,
+ in nsISupports statusContext);
+};
diff --git a/components/webbrowser/public/nsIWebBrowserChrome3.idl b/components/webbrowser/public/nsIWebBrowserChrome3.idl
new file mode 100644
index 000000000..d78a1d63b
--- /dev/null
+++ b/components/webbrowser/public/nsIWebBrowserChrome3.idl
@@ -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/. */
+
+#include "nsIWebBrowserChrome2.idl"
+#include "nsIURI.idl"
+#include "nsIDOMNode.idl"
+
+interface nsIDocShell;
+interface nsIInputStream;
+interface nsIPrincipal;
+
+/**
+ * nsIWebBrowserChrome3 is an extension to nsIWebBrowserChrome2.
+ */
+[scriptable, uuid(542b6625-35a9-426a-8257-c12a345383b0)]
+interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2
+{
+ /**
+ * Determines the appropriate target for a link.
+ *
+ * @param originalTarget
+ * The original link target.
+ * @param linkURI
+ * Link destination URI.
+ * @param aDOMNode
+ * Link DOM node.
+ * @param isAppTab
+ * Whether or not the link is in an app tab.
+ * @returns A new link target, if appropriate.
+ * Otherwise returns originalTarget.
+ */
+ AString onBeforeLinkTraversal(in AString originalTarget,
+ in nsIURI linkURI,
+ in nsIDOMNode linkNode,
+ in boolean isAppTab);
+
+ /**
+ * Determines whether a load should continue.
+ *
+ * @param aDocShell
+ * The docshell performing the load.
+ * @param aURI
+ * The URI being loaded.
+ * @param aReferrer
+ * The referrer of the load.
+ * @param aTriggeringPrincipal
+ * The principal that initiated the load of aURI.
+ */
+ bool shouldLoadURI(in nsIDocShell aDocShell,
+ in nsIURI aURI,
+ in nsIURI aReferrer,
+ in nsIPrincipal aTriggeringPrincipal);
+
+ /**
+ * Attempts to load the currently loaded page into a fresh process to increase
+ * available memory.
+ *
+ * @param aDocShell
+ * The docshell performing the load.
+ */
+ bool reloadInFreshProcess(in nsIDocShell aDocShell,
+ in nsIURI aURI,
+ in nsIURI aReferrer,
+ in nsIPrincipal aTriggeringPrincipal);
+};
diff --git a/components/webbrowser/public/nsIWebBrowserChromeFocus.idl b/components/webbrowser/public/nsIWebBrowserChromeFocus.idl
new file mode 100644
index 000000000..8e0b849b9
--- /dev/null
+++ b/components/webbrowser/public/nsIWebBrowserChromeFocus.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * The nsIWebBrowserChromeFocus is implemented by the same object as the
+ * nsIEmbeddingSiteWindow. It represents the focus up-calls from mozilla
+ * to the embedding chrome. See mozilla bug #70224 for gratuitous info.
+ */
+
+[scriptable, uuid(947B2EE6-51ED-4C2B-9F45-426C27CA84C6)]
+interface nsIWebBrowserChromeFocus : nsISupports
+{
+ /**
+ * Set the focus at the next focusable element in the chrome. If
+ * aForDocumentNavigation is true, this was a document navigation, so
+ * focus the parent window.
+ */
+
+ void focusNextElement(in bool aForDocumentNavigation);
+
+ /**
+ * Set the focus at the previous focusable element in the chrome.
+ */
+
+ void focusPrevElement(in bool aForDocumentNavigation);
+
+};
diff --git a/components/webbrowser/public/nsIWebBrowserFocus.idl b/components/webbrowser/public/nsIWebBrowserFocus.idl
new file mode 100644
index 000000000..e2b4a2788
--- /dev/null
+++ b/components/webbrowser/public/nsIWebBrowserFocus.idl
@@ -0,0 +1,76 @@
+/* -*- Mode: IDL; 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/. */
+
+interface mozIDOMWindowProxy;
+interface nsIDOMElement;
+
+#include "nsISupports.idl"
+
+/**
+ * nsIWebBrowserFocus
+ * Interface that embedders use for controlling and interacting
+ * with the browser focus management. The embedded browser can be focused by
+ * clicking in it or tabbing into it. If the browser is currently focused and
+ * the embedding application's top level window is disabled, deactivate() must
+ * be called, and activate() called again when the top level window is
+ * reactivated for the browser's focus memory to work correctly.
+ */
+
+[scriptable, uuid(7f8c754e-5b36-44be-bc96-191b49f08ea6)]
+interface nsIWebBrowserFocus : nsISupports
+{
+ /**
+ * MANDATORY
+ * activate() is a mandatory call that must be made to the browser
+ * when the embedding application's window is activated *and* the
+ * browser area was the last thing in focus. This method can also be called
+ * if the embedding application wishes to give the browser area focus,
+ * without affecting the currently focused element within the browser.
+ *
+ * @note
+ * If you fail to make this call, mozilla focus memory will not work
+ * correctly.
+ */
+ void activate();
+
+ /**
+ * MANDATORY
+ * deactivate() is a mandatory call that must be made to the browser
+ * when the embedding application's window is deactivated *and* the
+ * browser area was the last thing in focus. On non-windows platforms,
+ * deactivate() should also be called when focus moves from the browser
+ * to the embedding chrome.
+ *
+ * @note
+ * If you fail to make this call, mozilla focus memory will not work
+ * correctly.
+ */
+ void deactivate();
+
+ /**
+ * Give the first element focus within mozilla
+ * (i.e. TAB was pressed and focus should enter mozilla)
+ */
+ void setFocusAtFirstElement();
+
+ /**
+ * Give the last element focus within mozilla
+ * (i.e. SHIFT-TAB was pressed and focus should enter mozilla)
+ */
+ void setFocusAtLastElement();
+
+ /**
+ * The currently focused nsDOMWindow when the browser is active,
+ * or the last focused nsDOMWindow when the browser is inactive.
+ */
+ attribute mozIDOMWindowProxy focusedWindow;
+
+ /**
+ * The currently focused nsDOMElement when the browser is active,
+ * or the last focused nsDOMElement when the browser is inactive.
+ */
+ attribute nsIDOMElement focusedElement;
+};
diff --git a/components/webbrowser/public/nsIWebBrowserPrint.idl b/components/webbrowser/public/nsIWebBrowserPrint.idl
new file mode 100644
index 000000000..c5a014e6a
--- /dev/null
+++ b/components/webbrowser/public/nsIWebBrowserPrint.idl
@@ -0,0 +1,152 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIPrintSettings;
+interface nsIWebProgressListener;
+
+/**
+ * nsIWebBrowserPrint corresponds to the main interface
+ * for printing an embedded Gecko web browser window/document
+ */
+[scriptable, uuid(c9a934ed-fff1-4971-bfba-6c25ad70e1e6)]
+interface nsIWebBrowserPrint : nsISupports
+{
+ /**
+ * PrintPreview Navigation Constants
+ */
+ const short PRINTPREVIEW_GOTO_PAGENUM = 0;
+ const short PRINTPREVIEW_PREV_PAGE = 1;
+ const short PRINTPREVIEW_NEXT_PAGE = 2;
+ const short PRINTPREVIEW_HOME = 3;
+ const short PRINTPREVIEW_END = 4;
+
+ /**
+ * Returns a "global" PrintSettings object
+ * Creates a new the first time, if one doesn't exist.
+ *
+ * Then returns the same object each time after that.
+ *
+ * Initializes the globalPrintSettings from the default printer
+ */
+ readonly attribute nsIPrintSettings globalPrintSettings;
+
+ /**
+ * Returns a pointer to the PrintSettings object that
+ * that was passed into either "print" or "print preview"
+ *
+ * This enables any consumers of the interface to have access
+ * to the "current" PrintSetting at later points in the execution
+ */
+ readonly attribute nsIPrintSettings currentPrintSettings;
+
+ /**
+ * Returns a pointer to the current child DOMWindow
+ * that is being print previewed. (FrameSet Frames)
+ *
+ * Returns null if parent document is not a frameset or the entire FrameSet
+ * document is being print previewed
+ *
+ * This enables any consumers of the interface to have access
+ * to the "current" child DOMWindow at later points in the execution
+ */
+ readonly attribute mozIDOMWindowProxy currentChildDOMWindow;
+
+ /**
+ * Returns whether it is in Print mode
+ */
+ readonly attribute boolean doingPrint;
+
+ /**
+ * Returns whether it is in Print Preview mode
+ */
+ readonly attribute boolean doingPrintPreview;
+
+ /**
+ * This returns whether the current document is a frameset document
+ */
+ readonly attribute boolean isFramesetDocument;
+
+ /**
+ * This returns whether the current document is a frameset document
+ */
+ readonly attribute boolean isFramesetFrameSelected;
+
+ /**
+ * This returns whether there is an IFrame selected
+ */
+ readonly attribute boolean isIFrameSelected;
+
+ /**
+ * This returns whether there is a "range" selection
+ */
+ readonly attribute boolean isRangeSelection;
+
+ /**
+ * This returns the total number of pages for the Print Preview
+ */
+ readonly attribute long printPreviewNumPages;
+
+ /**
+ * Print the specified DOM window
+ *
+ * @param aThePrintSettings - Printer Settings for the print job, if aThePrintSettings is null
+ * then the global PS will be used.
+ * @param aWPListener - is updated during the print
+ * @return void
+ */
+ void print(in nsIPrintSettings aThePrintSettings,
+ in nsIWebProgressListener aWPListener);
+
+ /**
+ * Print Preview the specified DOM window
+ *
+ * @param aThePrintSettings - Printer Settings for the print preview, if aThePrintSettings is null
+ * then the global PS will be used.
+ * @param aChildDOMWin - DOM Window to be print previewed.
+ * @param aWPListener - is updated during the printpreview
+ * @return void
+ */
+ void printPreview(in nsIPrintSettings aThePrintSettings,
+ in mozIDOMWindowProxy aChildDOMWin,
+ in nsIWebProgressListener aWPListener);
+
+ /**
+ * Print Preview - Navigates within the window
+ *
+ * @param aNavType - navigation enum
+ * @param aPageNum - page num to navigate to when aNavType = ePrintPreviewGoToPageNum
+ * @return void
+ */
+ void printPreviewNavigate(in short aNavType, in long aPageNum);
+
+ /**
+ * Cancels the current print
+ * @return void
+ */
+ void cancel();
+
+ /**
+ * Returns an array of the names of all documents names (Title or URL)
+ * and sub-documents. This will return a single item if the attr "isFramesetDocument" is false
+ * and may return any number of items is "isFramesetDocument" is true
+ *
+ * @param aCount - returns number of printers returned
+ * @param aResult - returns array of names
+ * @return void
+ */
+ void enumerateDocumentNames(out uint32_t aCount,[retval, array, size_is(aCount)] out wstring aResult);
+
+ /**
+ * This exists PrintPreview mode and returns browser window to galley mode
+ * @return void
+ */
+ void exitPrintPreview();
+
+};
+
diff --git a/components/webbrowser/public/nsIWebBrowserSetup.idl b/components/webbrowser/public/nsIWebBrowserSetup.idl
new file mode 100644
index 000000000..d30645126
--- /dev/null
+++ b/components/webbrowser/public/nsIWebBrowserSetup.idl
@@ -0,0 +1,109 @@
+/* -*- Mode: IDL; 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 "nsISupports.idl"
+
+/**
+ * The nsIWebBrowserSetup interface lets you set properties on a browser
+ * object; you can do so at any time during the life cycle of the browser.
+ *
+ * @note Unless stated otherwise, settings are presumed to be enabled by
+ * default.
+ */
+[scriptable, uuid(F15398A0-8018-11d3-AF70-00A024FFC08C)]
+interface nsIWebBrowserSetup : nsISupports
+{
+ /**
+ * Boolean. Enables/disables plugin support for this browser.
+ *
+ * @see setProperty
+ */
+ const unsigned long SETUP_ALLOW_PLUGINS = 1;
+
+ /**
+ * Boolean. Enables/disables Javascript support for this browser.
+ *
+ * @see setProperty
+ */
+ const unsigned long SETUP_ALLOW_JAVASCRIPT = 2;
+
+ /**
+ * Boolean. Enables/disables meta redirect support for this browser.
+ * Meta redirect timers will be ignored if this option is disabled.
+ *
+ * @see setProperty
+ */
+ const unsigned long SETUP_ALLOW_META_REDIRECTS = 3;
+
+ /**
+ * Boolean. Enables/disables subframes within the browser
+ *
+ * @see setProperty
+ */
+ const unsigned long SETUP_ALLOW_SUBFRAMES = 4;
+
+ /**
+ * Boolean. Enables/disables image loading for this browser
+ * window. If you disable the images, load a page, then enable the images,
+ * the page will *not* automatically load the images for the previously
+ * loaded page. This flag controls the state of a webBrowser at load time
+ * and does not automatically re-load a page when the state is toggled.
+ * Reloading must be done by hand, or by walking through the DOM tree and
+ * re-setting the src attributes.
+ *
+ * @see setProperty
+ */
+ const unsigned long SETUP_ALLOW_IMAGES = 5;
+
+ /**
+ * Boolean. Enables/disables whether the document as a whole gets focus before
+ * traversing the document's content, or after traversing its content.
+ *
+ * NOTE: this property is obsolete and now has no effect
+ *
+ * @see setProperty
+ */
+ const unsigned long SETUP_FOCUS_DOC_BEFORE_CONTENT = 6;
+
+ /**
+ * Boolean. Enables/disables the use of global history in the browser. Visited
+ * URLs will not be recorded in the global history when it is disabled.
+ *
+ * @see setProperty
+ */
+ const unsigned long SETUP_USE_GLOBAL_HISTORY = 256;
+
+ /**
+ * Boolean. A value of PR_TRUE makes the browser a chrome wrapper.
+ * Default is PR_FALSE.
+ *
+ * @since mozilla1.0
+ *
+ * @see setProperty
+ */
+ const unsigned long SETUP_IS_CHROME_WRAPPER = 7;
+
+
+ /**
+ * Boolean. Enables/disables DNS prefetch for HTML anchors in this browser.
+ * This takes effect starting with the next pageload after the property is
+ * set. The default is to not allow DNS prefetch, for backwards
+ * compatibility.
+ *
+ * @see setProperty
+ */
+ const unsigned long SETUP_ALLOW_DNS_PREFETCH = 8;
+
+ /**
+ * Sets an integer or boolean property on the new web browser object.
+ * Only PR_TRUE and PR_FALSE are legal boolean values.
+ *
+ * @param aId The identifier of the property to be set.
+ * @param aValue The value of the property.
+ */
+ void setProperty(in unsigned long aId, in unsigned long aValue);
+};
+
diff --git a/components/webbrowser/public/nsIWebBrowserStream.idl b/components/webbrowser/public/nsIWebBrowserStream.idl
new file mode 100644
index 000000000..9326b2eaa
--- /dev/null
+++ b/components/webbrowser/public/nsIWebBrowserStream.idl
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 2; 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 "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * This interface provides a way to stream data to the web browser. This allows
+ * loading of data from sources which can not be accessed using URIs and
+ * nsIWebNavigation.
+ */
+[scriptable, uuid(86d02f0e-219b-4cfc-9c88-bd98d2cce0b8)]
+interface nsIWebBrowserStream : nsISupports
+{
+ /**
+ * Prepare to load a stream of data. When this function returns successfully,
+ * it must be paired by a call to closeStream.
+ *
+ * @param aBaseURI
+ * The base URI of the data. Must not be null. Relative
+ * URIs will be resolved relative to this URI.
+ * @param aContentType
+ * ASCII string giving the content type of the data. If rendering
+ * content of this type is not supported, this method fails.
+ * This string may include a charset declaration, for example:
+ * text/html;charset=ISO-8859-1
+ *
+ * @throw NS_ERROR_NOT_AVAILABLE
+ * The requested content type is not supported.
+ * @throw NS_ERROR_IN_PROGRESS
+ * openStream was called twice without an intermediate closeStream.
+ */
+ void openStream(in nsIURI aBaseURI, in ACString aContentType);
+
+ /**
+ * Append data to this stream.
+ * @param aData The data to append
+ * @param aLen Length of the data to append.
+ *
+ * @note To append more than 4 GB of data, call this method multiple times.
+ */
+ void appendToStream([const, array, size_is(aLen)] in octet aData,
+ in unsigned long aLen);
+
+ /**
+ * Notifies the browser that all the data has been appended. This may notify
+ * the user that the browser is "done loading" in some form.
+ *
+ * @throw NS_ERROR_UNEXPECTED
+ * This method was called without a preceding openStream.
+ */
+ void closeStream();
+};
diff --git a/components/webbrowser/src/nsCommandHandler.cpp b/components/webbrowser/src/nsCommandHandler.cpp
new file mode 100644
index 000000000..2e1a2d464
--- /dev/null
+++ b/components/webbrowser/src/nsCommandHandler.cpp
@@ -0,0 +1,138 @@
+/* -*- 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 "nsCommandHandler.h"
+#include "nsWebBrowser.h"
+#include "nsDocShellTreeOwner.h"
+
+#include "nsMemory.h"
+#include "nsPIDOMWindow.h"
+
+nsCommandHandler::nsCommandHandler()
+ : mWindow(nullptr)
+{
+}
+
+nsCommandHandler::~nsCommandHandler()
+{
+}
+
+nsresult
+nsCommandHandler::GetCommandHandler(nsICommandHandler** aCommandHandler)
+{
+ NS_ENSURE_ARG_POINTER(aCommandHandler);
+
+ *aCommandHandler = nullptr;
+ if (!mWindow) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the document tree owner
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsTreeItem =
+ do_QueryInterface(mWindow->GetDocShell());
+ nsIDocShellTreeOwner* treeOwner = nullptr;
+ docShellAsTreeItem->GetTreeOwner(&treeOwner);
+
+ // Make sure the tree owner is an an nsDocShellTreeOwner object
+ // by QI'ing for a hidden interface. If it doesn't have the interface
+ // then it's not safe to do the casting.
+
+ nsCOMPtr<nsICDocShellTreeOwner> realTreeOwner(do_QueryInterface(treeOwner));
+ if (realTreeOwner) {
+ nsDocShellTreeOwner* tree = static_cast<nsDocShellTreeOwner*>(treeOwner);
+ if (tree->mTreeOwner) {
+ nsresult rv;
+ rv = tree->mTreeOwner->QueryInterface(NS_GET_IID(nsICommandHandler),
+ (void**)aCommandHandler);
+ NS_RELEASE(treeOwner);
+ return rv;
+ }
+
+ NS_RELEASE(treeOwner);
+ }
+
+ *aCommandHandler = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMPL_ADDREF(nsCommandHandler)
+NS_IMPL_RELEASE(nsCommandHandler)
+
+NS_INTERFACE_MAP_BEGIN(nsCommandHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsICommandHandler)
+ NS_INTERFACE_MAP_ENTRY(nsICommandHandlerInit)
+ NS_INTERFACE_MAP_ENTRY(nsICommandHandler)
+NS_INTERFACE_MAP_END
+
+///////////////////////////////////////////////////////////////////////////////
+// nsICommandHandlerInit implementation
+
+NS_IMETHODIMP
+nsCommandHandler::GetWindow(mozIDOMWindowProxy** aWindow)
+{
+ *aWindow = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandHandler::SetWindow(mozIDOMWindowProxy* aWindow)
+{
+ if (!aWindow) {
+ return NS_ERROR_FAILURE;
+ }
+ mWindow = nsPIDOMWindowOuter::From(aWindow);
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsICommandHandler implementation
+
+NS_IMETHODIMP
+nsCommandHandler::Exec(const char* aCommand, const char* aStatus,
+ char** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aCommand);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsICommandHandler> commandHandler;
+ GetCommandHandler(getter_AddRefs(commandHandler));
+
+ // Call the client's command handler to deal with this command
+ if (commandHandler) {
+ *aResult = nullptr;
+ return commandHandler->Exec(aCommand, aStatus, aResult);
+ }
+
+ // Return an empty string
+ const char szEmpty[] = "";
+ *aResult = (char*)nsMemory::Clone(szEmpty, sizeof(szEmpty));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCommandHandler::Query(const char* aCommand, const char* aStatus,
+ char** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aCommand);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsICommandHandler> commandHandler;
+ GetCommandHandler(getter_AddRefs(commandHandler));
+
+ // Call the client's command handler to deal with this command
+ if (commandHandler) {
+ *aResult = nullptr;
+ return commandHandler->Query(aCommand, aStatus, aResult);
+ }
+
+ // Return an empty string
+ const char szEmpty[] = "";
+ *aResult = (char*)nsMemory::Clone(szEmpty, sizeof(szEmpty));
+
+ return NS_OK;
+}
diff --git a/components/webbrowser/src/nsCommandHandler.h b/components/webbrowser/src/nsCommandHandler.h
new file mode 100644
index 000000000..8a422d87b
--- /dev/null
+++ b/components/webbrowser/src/nsCommandHandler.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+#ifndef NSCOMMANDHANDLER_H
+#define NSCOMMANDHANDLER_H
+
+#include "nsISupports.h"
+#include "nsICommandHandler.h"
+
+class nsPIDOMWindowOuter;
+
+class nsCommandHandler
+ : public nsICommandHandlerInit
+ , public nsICommandHandler
+{
+public:
+ nsCommandHandler();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOMMANDHANDLERINIT
+ NS_DECL_NSICOMMANDHANDLER
+
+protected:
+ virtual ~nsCommandHandler();
+
+private:
+ nsresult GetCommandHandler(nsICommandHandler** aCommandHandler);
+
+ nsPIDOMWindowOuter* mWindow;
+};
+
+#endif
diff --git a/components/webbrowser/src/nsEmbedStream.cpp b/components/webbrowser/src/nsEmbedStream.cpp
new file mode 100644
index 000000000..40b0cf5bb
--- /dev/null
+++ b/components/webbrowser/src/nsEmbedStream.cpp
@@ -0,0 +1,100 @@
+/* -*- 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 "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPipe.h"
+
+#include "nsEmbedStream.h"
+#include "nsError.h"
+#include "nsString.h"
+
+NS_IMPL_ISUPPORTS0(nsEmbedStream)
+
+nsEmbedStream::nsEmbedStream()
+{
+ mOwner = nullptr;
+}
+
+nsEmbedStream::~nsEmbedStream()
+{
+}
+
+void
+nsEmbedStream::InitOwner(nsIWebBrowser* aOwner)
+{
+ mOwner = aOwner;
+}
+
+nsresult
+nsEmbedStream::Init(void)
+{
+ return NS_OK;
+}
+
+nsresult
+nsEmbedStream::OpenStream(nsIURI* aBaseURI, const nsACString& aContentType)
+{
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aBaseURI);
+ NS_ENSURE_TRUE(IsASCII(aContentType), NS_ERROR_INVALID_ARG);
+
+ // if we're already doing a stream, return an error
+ if (mOutputStream) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ nsCOMPtr<nsIAsyncOutputStream> outputStream;
+ rv = NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(outputStream),
+ true, false, 0, UINT32_MAX);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = do_GetInterface(mOwner);
+ rv = docShell->LoadStream(inputStream, aBaseURI, aContentType,
+ EmptyCString(), nullptr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mOutputStream = outputStream;
+ return rv;
+}
+
+nsresult
+nsEmbedStream::AppendToStream(const uint8_t* aData, uint32_t aLen)
+{
+ nsresult rv;
+ NS_ENSURE_STATE(mOutputStream);
+
+ uint32_t bytesWritten = 0;
+ rv = mOutputStream->Write(reinterpret_cast<const char*>(aData),
+ aLen, &bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ NS_ASSERTION(bytesWritten == aLen,
+ "underlying buffer couldn't handle the write");
+ return rv;
+}
+
+nsresult
+nsEmbedStream::CloseStream(void)
+{
+ nsresult rv = NS_OK;
+
+ // NS_ENSURE_STATE returns NS_ERROR_UNEXPECTED if the condition isn't
+ // satisfied; this is exactly what we want to return.
+ NS_ENSURE_STATE(mOutputStream);
+ mOutputStream->Close();
+ mOutputStream = nullptr;
+
+ return rv;
+}
diff --git a/components/webbrowser/src/nsEmbedStream.h b/components/webbrowser/src/nsEmbedStream.h
new file mode 100644
index 000000000..885ed625b
--- /dev/null
+++ b/components/webbrowser/src/nsEmbedStream.h
@@ -0,0 +1,36 @@
+/* -*- 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/. */
+
+#ifndef nsEmbedStream_h__
+#define nsEmbedStream_h__
+
+#include "nsCOMPtr.h"
+#include "nsIOutputStream.h"
+#include "nsIURI.h"
+#include "nsIWebBrowser.h"
+
+class nsEmbedStream : public nsISupports
+{
+public:
+ nsEmbedStream();
+
+ void InitOwner(nsIWebBrowser* aOwner);
+ nsresult Init(void);
+
+ nsresult OpenStream(nsIURI* aBaseURI, const nsACString& aContentType);
+ nsresult AppendToStream(const uint8_t* aData, uint32_t aLen);
+ nsresult CloseStream(void);
+
+ NS_DECL_ISUPPORTS
+
+protected:
+ virtual ~nsEmbedStream();
+
+private:
+ nsIWebBrowser* mOwner;
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+};
+
+#endif // nsEmbedStream_h__
diff --git a/components/webbrowser/src/nsWebBrowser.cpp b/components/webbrowser/src/nsWebBrowser.cpp
new file mode 100644
index 000000000..0fca52bbb
--- /dev/null
+++ b/components/webbrowser/src/nsWebBrowser.cpp
@@ -0,0 +1,1952 @@
+/* -*- 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/. */
+
+// Local Includes
+#include "nsWebBrowser.h"
+
+// Helper Classes
+#include "nsGfxCIID.h"
+#include "nsWidgetsCID.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+
+// Interfaces Needed
+#include "nsReadableUtils.h"
+#include "nsIComponentManager.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMElement.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsIWebBrowserFocus.h"
+#include "nsIWebBrowserStream.h"
+#include "nsIPresShell.h"
+#include "nsIURIContentListener.h"
+#include "nsISHistoryListener.h"
+#include "nsIURI.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsCWebBrowserPersist.h"
+#include "nsIServiceManager.h"
+#include "nsFocusManager.h"
+#include "Layers.h"
+#include "gfxContext.h"
+#include "nsILoadContext.h"
+#include "nsDocShell.h"
+
+// for painting the background window
+#include "mozilla/LookAndFeel.h"
+
+// Printing Includes
+#ifdef NS_PRINTING
+#include "nsIWebBrowserPrint.h"
+#include "nsIContentViewer.h"
+#endif
+
+// PSM2 includes
+#include "nsISecureBrowserUI.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+static NS_DEFINE_CID(kChildCID, NS_CHILD_CID);
+
+nsWebBrowser::nsWebBrowser()
+ : mInitInfo(new nsWebBrowserInitInfo())
+ , mContentType(typeContentWrapper)
+ , mActivating(false)
+ , mShouldEnableHistory(true)
+ , mIsActive(true)
+ , mParentNativeWindow(nullptr)
+ , mProgressListener(nullptr)
+ , mWidgetListenerDelegate(this)
+ , mBackgroundColor(0)
+ , mPersistCurrentState(nsIWebBrowserPersist::PERSIST_STATE_READY)
+ , mPersistResult(NS_OK)
+ , mPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_NONE)
+ , mParentWidget(nullptr)
+{
+ mWWatch = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
+ NS_ASSERTION(mWWatch, "failed to get WindowWatcher");
+}
+
+nsWebBrowser::~nsWebBrowser()
+{
+ InternalDestroy();
+}
+
+NS_IMETHODIMP
+nsWebBrowser::InternalDestroy()
+{
+ if (mInternalWidget) {
+ mInternalWidget->SetWidgetListener(nullptr);
+ mInternalWidget->Destroy();
+ mInternalWidget = nullptr; // Force release here.
+ }
+
+ SetDocShell(nullptr);
+
+ if (mDocShellTreeOwner) {
+ mDocShellTreeOwner->WebBrowser(nullptr);
+ mDocShellTreeOwner = nullptr;
+ }
+
+ mInitInfo = nullptr;
+
+ mListenerArray = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMPL_ADDREF(nsWebBrowser)
+NS_IMPL_RELEASE(nsWebBrowser)
+
+NS_INTERFACE_MAP_BEGIN(nsWebBrowser)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowser)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowser)
+ NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
+ NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIScrollable)
+ NS_INTERFACE_MAP_ENTRY(nsITextScroll)
+ NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserSetup)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist)
+ NS_INTERFACE_MAP_ENTRY(nsICancelable)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserFocus)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+///*****************************************************************************
+// nsWebBrowser::nsIInterfaceRequestor
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::GetInterface(const nsIID& aIID, void** aSink)
+{
+ NS_ENSURE_ARG_POINTER(aSink);
+
+ if (NS_SUCCEEDED(QueryInterface(aIID, aSink))) {
+ return NS_OK;
+ }
+
+ if (mDocShell) {
+#ifdef NS_PRINTING
+ if (aIID.Equals(NS_GET_IID(nsIWebBrowserPrint))) {
+ nsCOMPtr<nsIContentViewer> viewer;
+ mDocShell->GetContentViewer(getter_AddRefs(viewer));
+ if (!viewer) {
+ return NS_NOINTERFACE;
+ }
+
+ nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint(do_QueryInterface(viewer));
+ nsIWebBrowserPrint* print = (nsIWebBrowserPrint*)webBrowserPrint.get();
+ NS_ASSERTION(print, "This MUST support this interface!");
+ NS_ADDREF(print);
+ *aSink = print;
+ return NS_OK;
+ }
+#endif
+ return mDocShellAsReq->GetInterface(aIID, aSink);
+ }
+
+ return NS_NOINTERFACE;
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsIWebBrowser
+//*****************************************************************************
+
+// listeners that currently support registration through AddWebBrowserListener:
+// - nsIWebProgressListener
+NS_IMETHODIMP
+nsWebBrowser::AddWebBrowserListener(nsIWeakReference* aListener,
+ const nsIID& aIID)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ nsresult rv = NS_OK;
+ if (!mWebProgress) {
+ // The window hasn't been created yet, so queue up the listener. They'll be
+ // registered when the window gets created.
+ if (!mListenerArray) {
+ mListenerArray = new nsTArray<nsWebBrowserListenerState>();
+ }
+
+ nsWebBrowserListenerState* state = mListenerArray->AppendElement();
+ state->mWeakPtr = aListener;
+ state->mID = aIID;
+ } else {
+ nsCOMPtr<nsISupports> supports(do_QueryReferent(aListener));
+ if (!supports) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = BindListener(supports, aIID);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::BindListener(nsISupports* aListener, const nsIID& aIID)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ NS_ASSERTION(mWebProgress,
+ "this should only be called after we've retrieved a progress iface");
+ nsresult rv = NS_OK;
+
+ // register this listener for the specified interface id
+ if (aIID.Equals(NS_GET_IID(nsIWebProgressListener))) {
+ nsCOMPtr<nsIWebProgressListener> listener = do_QueryInterface(aListener, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ NS_ENSURE_STATE(mWebProgress);
+ rv = mWebProgress->AddProgressListener(listener, nsIWebProgress::NOTIFY_ALL);
+ } else if (aIID.Equals(NS_GET_IID(nsISHistoryListener))) {
+ nsCOMPtr<nsISHistory> shistory(do_GetInterface(mDocShell, &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsISHistoryListener> listener(do_QueryInterface(aListener, &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = shistory->AddSHistoryListener(listener);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::RemoveWebBrowserListener(nsIWeakReference* aListener,
+ const nsIID& aIID)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ nsresult rv = NS_OK;
+ if (!mWebProgress) {
+ // if there's no-one to register the listener w/, and we don't have a queue
+ // going, the the called is calling Remove before an Add which doesn't make
+ // sense.
+ if (!mListenerArray) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // iterate the array and remove the queued listener
+ int32_t count = mListenerArray->Length();
+ while (count > 0) {
+ if (mListenerArray->ElementAt(count-1).Equals(aListener, aIID)) {
+ mListenerArray->RemoveElementAt(count-1);
+ break;
+ }
+ count--;
+ }
+
+ // if we've emptied the array, get rid of it.
+ if (0 >= mListenerArray->Length()) {
+ mListenerArray = nullptr;
+ }
+
+ } else {
+ nsCOMPtr<nsISupports> supports(do_QueryReferent(aListener));
+ if (!supports) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = UnBindListener(supports, aIID);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::UnBindListener(nsISupports* aListener, const nsIID& aIID)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ NS_ASSERTION(mWebProgress,
+ "this should only be called after we've retrieved a progress iface");
+ nsresult rv = NS_OK;
+
+ // remove the listener for the specified interface id
+ if (aIID.Equals(NS_GET_IID(nsIWebProgressListener))) {
+ nsCOMPtr<nsIWebProgressListener> listener = do_QueryInterface(aListener, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ NS_ENSURE_STATE(mWebProgress);
+ rv = mWebProgress->RemoveProgressListener(listener);
+ } else if (aIID.Equals(NS_GET_IID(nsISHistoryListener))) {
+ nsCOMPtr<nsISHistory> shistory(do_GetInterface(mDocShell, &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsISHistoryListener> listener(do_QueryInterface(aListener, &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = shistory->RemoveSHistoryListener(listener);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::EnableGlobalHistory(bool aEnable)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShell->SetUseGlobalHistory(aEnable);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetContainerWindow(nsIWebBrowserChrome** aTopWindow)
+{
+ NS_ENSURE_ARG_POINTER(aTopWindow);
+
+ nsCOMPtr<nsIWebBrowserChrome> top;
+ if (mDocShellTreeOwner) {
+ top = mDocShellTreeOwner->GetWebBrowserChrome();
+ }
+
+ top.forget(aTopWindow);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetContainerWindow(nsIWebBrowserChrome* aTopWindow)
+{
+ NS_ENSURE_SUCCESS(EnsureDocShellTreeOwner(), NS_ERROR_FAILURE);
+ return mDocShellTreeOwner->SetWebBrowserChrome(aTopWindow);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetParentURIContentListener(
+ nsIURIContentListener** aParentContentListener)
+{
+ NS_ENSURE_ARG_POINTER(aParentContentListener);
+ *aParentContentListener = nullptr;
+
+ // get the interface from the docshell
+ nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(mDocShell));
+
+ if (listener) {
+ return listener->GetParentContentListener(aParentContentListener);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetParentURIContentListener(
+ nsIURIContentListener* aParentContentListener)
+{
+ // get the interface from the docshell
+ nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(mDocShell));
+
+ if (listener) {
+ return listener->SetParentContentListener(aParentContentListener);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetContentDOMWindow(mozIDOMWindowProxy** aResult)
+{
+ if (!mDocShell) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> retval = mDocShell->GetWindow();
+ retval.forget(aResult);
+ return *aResult ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetIsActive(bool* aResult)
+{
+ *aResult = mIsActive;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetIsActive(bool aIsActive)
+{
+ // Set our copy of the value
+ mIsActive = aIsActive;
+
+ // If we have a docshell, pass on the request
+ if (mDocShell) {
+ return mDocShell->SetIsActive(aIsActive);
+ }
+ return NS_OK;
+}
+
+void
+nsWebBrowser::SetOriginAttributes(const DocShellOriginAttributes& aAttrs)
+{
+ mOriginAttributes = aAttrs;
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsIDocShellTreeItem
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::GetName(nsAString& aName)
+{
+ if (mDocShell) {
+ mDocShell->GetName(aName);
+ } else {
+ aName = mInitInfo->name;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetName(const nsAString& aName)
+{
+ if (mDocShell) {
+ return mDocShell->SetName(aName);
+ } else {
+ mInitInfo->name = aName;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::NameEquals(const nsAString& aName, bool* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (mDocShell) {
+ return mDocShell->NameEquals(aName, aResult);
+ } else {
+ *aResult = mInitInfo->name.Equals(aName);
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ int32_t
+nsWebBrowser::ItemType()
+{
+ return mContentType;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetItemType(int32_t* aItemType)
+{
+ NS_ENSURE_ARG_POINTER(aItemType);
+
+ *aItemType = ItemType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetItemType(int32_t aItemType)
+{
+ NS_ENSURE_TRUE(
+ aItemType == typeContentWrapper || aItemType == typeChromeWrapper,
+ NS_ERROR_FAILURE);
+ mContentType = aItemType;
+ if (mDocShell) {
+ mDocShell->SetItemType(mContentType == typeChromeWrapper ?
+ static_cast<int32_t>(typeChrome) :
+ static_cast<int32_t>(typeContent));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetParent(nsIDocShellTreeItem** aParent)
+{
+ *aParent = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetSameTypeParent(nsIDocShellTreeItem** aParent)
+{
+ *aParent = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetRootTreeItem(nsIDocShellTreeItem** aRootTreeItem)
+{
+ NS_ENSURE_ARG_POINTER(aRootTreeItem);
+ *aRootTreeItem = static_cast<nsIDocShellTreeItem*>(this);
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ NS_ENSURE_SUCCESS(GetParent(getter_AddRefs(parent)), NS_ERROR_FAILURE);
+ while (parent) {
+ *aRootTreeItem = parent;
+ NS_ENSURE_SUCCESS((*aRootTreeItem)->GetParent(getter_AddRefs(parent)),
+ NS_ERROR_FAILURE);
+ }
+ NS_ADDREF(*aRootTreeItem);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetSameTypeRootTreeItem(nsIDocShellTreeItem** aRootTreeItem)
+{
+ NS_ENSURE_ARG_POINTER(aRootTreeItem);
+ *aRootTreeItem = static_cast<nsIDocShellTreeItem*>(this);
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ NS_ENSURE_SUCCESS(GetSameTypeParent(getter_AddRefs(parent)),
+ NS_ERROR_FAILURE);
+ while (parent) {
+ *aRootTreeItem = parent;
+ NS_ENSURE_SUCCESS((*aRootTreeItem)->GetSameTypeParent(getter_AddRefs(parent)),
+ NS_ERROR_FAILURE);
+ }
+ NS_ADDREF(*aRootTreeItem);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::FindItemWithName(const nsAString& aName,
+ nsISupports* aRequestor,
+ nsIDocShellTreeItem* aOriginalRequestor,
+ nsIDocShellTreeItem** aResult)
+{
+ NS_ENSURE_STATE(mDocShell);
+ NS_ASSERTION(mDocShellTreeOwner,
+ "This should always be set when in this situation");
+
+ return mDocShell->FindItemWithName(
+ aName, static_cast<nsIDocShellTreeOwner*>(mDocShellTreeOwner),
+ aOriginalRequestor, aResult);
+}
+
+nsIDocument*
+nsWebBrowser::GetDocument()
+{
+ return mDocShell ? mDocShell->GetDocument() : nullptr;
+}
+
+nsPIDOMWindowOuter*
+nsWebBrowser::GetWindow()
+{
+ return mDocShell ? mDocShell->GetWindow() : nullptr;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetTreeOwner(nsIDocShellTreeOwner** aTreeOwner)
+{
+ NS_ENSURE_ARG_POINTER(aTreeOwner);
+ *aTreeOwner = nullptr;
+ if (mDocShellTreeOwner) {
+ if (mDocShellTreeOwner->mTreeOwner) {
+ *aTreeOwner = mDocShellTreeOwner->mTreeOwner;
+ } else {
+ *aTreeOwner = mDocShellTreeOwner;
+ }
+ }
+ NS_IF_ADDREF(*aTreeOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner)
+{
+ NS_ENSURE_SUCCESS(EnsureDocShellTreeOwner(), NS_ERROR_FAILURE);
+ return mDocShellTreeOwner->SetTreeOwner(aTreeOwner);
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsIDocShellTreeItem
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::GetChildCount(int32_t* aChildCount)
+{
+ NS_ENSURE_ARG_POINTER(aChildCount);
+ *aChildCount = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::AddChild(nsIDocShellTreeItem* aChild)
+{
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::RemoveChild(nsIDocShellTreeItem* aChild)
+{
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild)
+{
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::FindChildWithName(const nsAString& aName,
+ bool aRecurse,
+ bool aSameType,
+ nsIDocShellTreeItem* aRequestor,
+ nsIDocShellTreeItem* aOriginalRequestor,
+ nsIDocShellTreeItem** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsIWebNavigation
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::GetCanGoBack(bool* aCanGoBack)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->GetCanGoBack(aCanGoBack);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetCanGoForward(bool* aCanGoForward)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->GetCanGoForward(aCanGoForward);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GoBack()
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->GoBack();
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GoForward()
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->GoForward();
+}
+
+NS_IMETHODIMP
+nsWebBrowser::LoadURIWithOptions(const char16_t* aURI, uint32_t aLoadFlags,
+ nsIURI* aReferringURI,
+ uint32_t aReferrerPolicy,
+ nsIInputStream* aPostDataStream,
+ nsIInputStream* aExtraHeaderStream,
+ nsIURI* aBaseURI,
+ nsIPrincipal* aTriggeringPrincipal)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->LoadURIWithOptions(
+ aURI, aLoadFlags, aReferringURI, aReferrerPolicy, aPostDataStream,
+ aExtraHeaderStream, aBaseURI, aTriggeringPrincipal);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetOriginAttributesBeforeLoading(JS::Handle<JS::Value> aOriginAttributes)
+{
+ return mDocShellAsNav->SetOriginAttributesBeforeLoading(aOriginAttributes);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::LoadURI(const char16_t* aURI, uint32_t aLoadFlags,
+ nsIURI* aReferringURI,
+ nsIInputStream* aPostDataStream,
+ nsIInputStream* aExtraHeaderStream)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->LoadURI(
+ aURI, aLoadFlags, aReferringURI, aPostDataStream, aExtraHeaderStream);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::Reload(uint32_t aReloadFlags)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->Reload(aReloadFlags);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GotoIndex(int32_t aIndex)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->GotoIndex(aIndex);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::Stop(uint32_t aStopFlags)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->Stop(aStopFlags);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetCurrentURI(nsIURI** aURI)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->GetCurrentURI(aURI);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetReferringURI(nsIURI** aURI)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->GetReferringURI(aURI);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetSessionHistory(nsISHistory* aSessionHistory)
+{
+ if (mDocShell) {
+ return mDocShellAsNav->SetSessionHistory(aSessionHistory);
+ } else {
+ mInitInfo->sessionHistory = aSessionHistory;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetSessionHistory(nsISHistory** aSessionHistory)
+{
+ NS_ENSURE_ARG_POINTER(aSessionHistory);
+ if (mDocShell) {
+ return mDocShellAsNav->GetSessionHistory(aSessionHistory);
+ } else {
+ *aSessionHistory = mInitInfo->sessionHistory;
+ }
+
+ NS_IF_ADDREF(*aSessionHistory);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetDocument(nsIDOMDocument** aDocument)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsNav->GetDocument(aDocument);
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsIWebBrowserSetup
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::SetProperty(uint32_t aId, uint32_t aValue)
+{
+ nsresult rv = NS_OK;
+
+ switch (aId) {
+ case nsIWebBrowserSetup::SETUP_ALLOW_PLUGINS: {
+ NS_ENSURE_STATE(mDocShell);
+ NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
+ aValue == static_cast<uint32_t>(false)),
+ NS_ERROR_INVALID_ARG);
+ mDocShell->SetAllowPlugins(!!aValue);
+ break;
+ }
+ case nsIWebBrowserSetup::SETUP_ALLOW_JAVASCRIPT: {
+ NS_ENSURE_STATE(mDocShell);
+ NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
+ aValue == static_cast<uint32_t>(false)),
+ NS_ERROR_INVALID_ARG);
+ mDocShell->SetAllowJavascript(!!aValue);
+ break;
+ }
+ case nsIWebBrowserSetup::SETUP_ALLOW_META_REDIRECTS: {
+ NS_ENSURE_STATE(mDocShell);
+ NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
+ aValue == static_cast<uint32_t>(false)),
+ NS_ERROR_INVALID_ARG);
+ mDocShell->SetAllowMetaRedirects(!!aValue);
+ break;
+ }
+ case nsIWebBrowserSetup::SETUP_ALLOW_SUBFRAMES: {
+ NS_ENSURE_STATE(mDocShell);
+ NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
+ aValue == static_cast<uint32_t>(false)),
+ NS_ERROR_INVALID_ARG);
+ mDocShell->SetAllowSubframes(!!aValue);
+ break;
+ }
+ case nsIWebBrowserSetup::SETUP_ALLOW_IMAGES: {
+ NS_ENSURE_STATE(mDocShell);
+ NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
+ aValue == static_cast<uint32_t>(false)),
+ NS_ERROR_INVALID_ARG);
+ mDocShell->SetAllowImages(!!aValue);
+ break;
+ }
+ case nsIWebBrowserSetup::SETUP_ALLOW_DNS_PREFETCH: {
+ NS_ENSURE_STATE(mDocShell);
+ NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
+ aValue == static_cast<uint32_t>(false)),
+ NS_ERROR_INVALID_ARG);
+ mDocShell->SetAllowDNSPrefetch(!!aValue);
+ break;
+ }
+ case nsIWebBrowserSetup::SETUP_USE_GLOBAL_HISTORY: {
+ NS_ENSURE_STATE(mDocShell);
+ NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
+ aValue == static_cast<uint32_t>(false)),
+ NS_ERROR_INVALID_ARG);
+ rv = EnableGlobalHistory(!!aValue);
+ mShouldEnableHistory = aValue;
+ break;
+ }
+ case nsIWebBrowserSetup::SETUP_FOCUS_DOC_BEFORE_CONTENT: {
+ // obsolete
+ break;
+ }
+ case nsIWebBrowserSetup::SETUP_IS_CHROME_WRAPPER: {
+ NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) ||
+ aValue == static_cast<uint32_t>(false)),
+ NS_ERROR_INVALID_ARG);
+ SetItemType(aValue ? static_cast<int32_t>(typeChromeWrapper) :
+ static_cast<int32_t>(typeContentWrapper));
+ break;
+ }
+ default:
+ rv = NS_ERROR_INVALID_ARG;
+ }
+ return rv;
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsIWebProgressListener
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ if (mPersist) {
+ mPersist->GetCurrentState(&mPersistCurrentState);
+ }
+ if (aStateFlags & STATE_IS_NETWORK && aStateFlags & STATE_STOP) {
+ mPersist = nullptr;
+ }
+ if (mProgressListener) {
+ return mProgressListener->OnStateChange(aWebProgress, aRequest, aStateFlags,
+ aStatus);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ if (mPersist) {
+ mPersist->GetCurrentState(&mPersistCurrentState);
+ }
+ if (mProgressListener) {
+ return mProgressListener->OnProgressChange(
+ aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI* aLocation,
+ uint32_t aFlags)
+{
+ if (mProgressListener) {
+ return mProgressListener->OnLocationChange(aWebProgress, aRequest, aLocation,
+ aFlags);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ if (mProgressListener) {
+ return mProgressListener->OnStatusChange(aWebProgress, aRequest, aStatus,
+ aMessage);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aState)
+{
+ if (mProgressListener) {
+ return mProgressListener->OnSecurityChange(aWebProgress, aRequest, aState);
+ }
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsIWebBrowserPersist
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::GetPersistFlags(uint32_t* aPersistFlags)
+{
+ NS_ENSURE_ARG_POINTER(aPersistFlags);
+ nsresult rv = NS_OK;
+ if (mPersist) {
+ rv = mPersist->GetPersistFlags(&mPersistFlags);
+ }
+ *aPersistFlags = mPersistFlags;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetPersistFlags(uint32_t aPersistFlags)
+{
+ nsresult rv = NS_OK;
+ mPersistFlags = aPersistFlags;
+ if (mPersist) {
+ rv = mPersist->SetPersistFlags(mPersistFlags);
+ mPersist->GetPersistFlags(&mPersistFlags);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetCurrentState(uint32_t* aCurrentState)
+{
+ NS_ENSURE_ARG_POINTER(aCurrentState);
+ if (mPersist) {
+ mPersist->GetCurrentState(&mPersistCurrentState);
+ }
+ *aCurrentState = mPersistCurrentState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetResult(nsresult* aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (mPersist) {
+ mPersist->GetResult(&mPersistResult);
+ }
+ *aResult = mPersistResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetProgressListener(nsIWebProgressListener** aProgressListener)
+{
+ NS_ENSURE_ARG_POINTER(aProgressListener);
+ *aProgressListener = mProgressListener;
+ NS_IF_ADDREF(*aProgressListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetProgressListener(nsIWebProgressListener* aProgressListener)
+{
+ mProgressListener = aProgressListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SaveURI(nsIURI* aURI,
+ nsISupports* aCacheKey,
+ nsIURI* aReferrer,
+ uint32_t aReferrerPolicy,
+ nsIInputStream* aPostData,
+ const char* aExtraHeaders,
+ nsISupports* aFile,
+ nsILoadContext* aPrivacyContext)
+{
+ return SavePrivacyAwareURI(
+ aURI, aCacheKey, aReferrer, aReferrerPolicy, aPostData, aExtraHeaders,
+ aFile, aPrivacyContext && aPrivacyContext->UsePrivateBrowsing());
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SavePrivacyAwareURI(nsIURI* aURI,
+ nsISupports* aCacheKey,
+ nsIURI* aReferrer,
+ uint32_t aReferrerPolicy,
+ nsIInputStream* aPostData,
+ const char* aExtraHeaders,
+ nsISupports* aFile,
+ bool aIsPrivate)
+{
+ if (mPersist) {
+ uint32_t currentState;
+ mPersist->GetCurrentState(&currentState);
+ if (currentState == PERSIST_STATE_FINISHED) {
+ mPersist = nullptr;
+ } else {
+ // You can't save again until the last save has completed
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (aURI) {
+ uri = aURI;
+ } else {
+ nsresult rv = GetCurrentURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // Create a throwaway persistence object to do the work
+ nsresult rv;
+ mPersist = do_CreateInstance(NS_WEBBROWSERPERSIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mPersist->SetProgressListener(this);
+ mPersist->SetPersistFlags(mPersistFlags);
+ mPersist->GetCurrentState(&mPersistCurrentState);
+
+ rv = mPersist->SavePrivacyAwareURI(uri, aCacheKey, aReferrer, aReferrerPolicy,
+ aPostData, aExtraHeaders, aFile, aIsPrivate);
+ if (NS_FAILED(rv)) {
+ mPersist = nullptr;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SaveChannel(nsIChannel* aChannel, nsISupports* aFile)
+{
+ if (mPersist) {
+ uint32_t currentState;
+ mPersist->GetCurrentState(&currentState);
+ if (currentState == PERSIST_STATE_FINISHED) {
+ mPersist = nullptr;
+ } else {
+ // You can't save again until the last save has completed
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // Create a throwaway persistence object to do the work
+ nsresult rv;
+ mPersist = do_CreateInstance(NS_WEBBROWSERPERSIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mPersist->SetProgressListener(this);
+ mPersist->SetPersistFlags(mPersistFlags);
+ mPersist->GetCurrentState(&mPersistCurrentState);
+ rv = mPersist->SaveChannel(aChannel, aFile);
+ if (NS_FAILED(rv)) {
+ mPersist = nullptr;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SaveDocument(nsISupports* aDocumentish,
+ nsISupports* aFile,
+ nsISupports* aDataPath,
+ const char* aOutputContentType,
+ uint32_t aEncodingFlags,
+ uint32_t aWrapColumn)
+{
+ if (mPersist) {
+ uint32_t currentState;
+ mPersist->GetCurrentState(&currentState);
+ if (currentState == PERSIST_STATE_FINISHED) {
+ mPersist = nullptr;
+ } else {
+ // You can't save again until the last save has completed
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // Use the specified DOM document, or if none is specified, the one
+ // attached to the web browser.
+
+ nsCOMPtr<nsISupports> doc;
+ if (aDocumentish) {
+ doc = aDocumentish;
+ } else {
+ nsCOMPtr<nsIDOMDocument> domDoc;
+ GetDocument(getter_AddRefs(domDoc));
+ doc = domDoc.forget();
+ }
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Create a throwaway persistence object to do the work
+ nsresult rv;
+ mPersist = do_CreateInstance(NS_WEBBROWSERPERSIST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mPersist->SetProgressListener(this);
+ mPersist->SetPersistFlags(mPersistFlags);
+ mPersist->GetCurrentState(&mPersistCurrentState);
+ rv = mPersist->SaveDocument(doc, aFile, aDataPath, aOutputContentType,
+ aEncodingFlags, aWrapColumn);
+ if (NS_FAILED(rv)) {
+ mPersist = nullptr;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::CancelSave()
+{
+ if (mPersist) {
+ return mPersist->CancelSave();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::Cancel(nsresult aReason)
+{
+ if (mPersist) {
+ return mPersist->Cancel(aReason);
+ }
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsIBaseWindow
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::InitWindow(nativeWindow aParentNativeWindow,
+ nsIWidget* aParentWidget,
+ int32_t aX, int32_t aY,
+ int32_t aCX, int32_t aCY)
+{
+ NS_ENSURE_ARG(aParentNativeWindow || aParentWidget);
+ NS_ENSURE_STATE(!mDocShell || mInitInfo);
+
+ if (aParentWidget) {
+ NS_ENSURE_SUCCESS(SetParentWidget(aParentWidget), NS_ERROR_FAILURE);
+ } else
+ NS_ENSURE_SUCCESS(SetParentNativeWindow(aParentNativeWindow),
+ NS_ERROR_FAILURE);
+
+ NS_ENSURE_SUCCESS(SetPositionAndSize(aX, aY, aCX, aCY, 0),
+ NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::Create()
+{
+ NS_ENSURE_STATE(!mDocShell && (mParentNativeWindow || mParentWidget));
+
+ nsresult rv = EnsureDocShellTreeOwner();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIWidget> docShellParentWidget(mParentWidget);
+ if (!mParentWidget) {
+ // Create the widget
+ mInternalWidget = do_CreateInstance(kChildCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ docShellParentWidget = mInternalWidget;
+ nsWidgetInitData widgetInit;
+
+ widgetInit.clipChildren = true;
+
+ widgetInit.mWindowType = eWindowType_child;
+ LayoutDeviceIntRect bounds(mInitInfo->x, mInitInfo->y,
+ mInitInfo->cx, mInitInfo->cy);
+
+ mInternalWidget->SetWidgetListener(&mWidgetListenerDelegate);
+ rv = mInternalWidget->Create(nullptr, mParentNativeWindow, bounds,
+ &widgetInit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIDocShell> docShell(
+ do_CreateInstance("@mozilla.org/docshell;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsDocShell::Cast(docShell)->SetOriginAttributes(mOriginAttributes);
+ rv = SetDocShell(docShell);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the system default window background colour
+ LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground,
+ &mBackgroundColor);
+
+ // the docshell has been set so we now have our listener registrars.
+ if (mListenerArray) {
+ // we had queued up some listeners, let's register them now.
+ uint32_t count = mListenerArray->Length();
+ uint32_t i = 0;
+ NS_ASSERTION(count > 0, "array construction problem");
+ while (i < count) {
+ nsWebBrowserListenerState& state = mListenerArray->ElementAt(i);
+ nsCOMPtr<nsISupports> listener = do_QueryReferent(state.mWeakPtr);
+ NS_ASSERTION(listener, "bad listener");
+ (void)BindListener(listener, state.mID);
+ i++;
+ }
+ mListenerArray = nullptr;
+ }
+
+ // HACK ALERT - this registration registers the nsDocShellTreeOwner as a
+ // nsIWebBrowserListener so it can setup its MouseListener in one of the
+ // progress callbacks. If we can register the MouseListener another way, this
+ // registration can go away, and nsDocShellTreeOwner can stop implementing
+ // nsIWebProgressListener.
+ nsCOMPtr<nsISupports> supports = nullptr;
+ (void)mDocShellTreeOwner->QueryInterface(
+ NS_GET_IID(nsIWebProgressListener),
+ static_cast<void**>(getter_AddRefs(supports)));
+ (void)BindListener(supports, NS_GET_IID(nsIWebProgressListener));
+
+ NS_ENSURE_SUCCESS(mDocShellAsWin->InitWindow(nullptr, docShellParentWidget,
+ mInitInfo->x, mInitInfo->y,
+ mInitInfo->cx, mInitInfo->cy),
+ NS_ERROR_FAILURE);
+
+ mDocShell->SetName(mInitInfo->name);
+ if (mContentType == typeChromeWrapper) {
+ mDocShell->SetItemType(nsIDocShellTreeItem::typeChrome);
+ } else {
+ mDocShell->SetItemType(nsIDocShellTreeItem::typeContent);
+ }
+ mDocShell->SetTreeOwner(mDocShellTreeOwner);
+
+ // If the webbrowser is a content docshell item then we won't hear any
+ // events from subframes. To solve that we install our own chrome event
+ // handler that always gets called (even for subframes) for any bubbling
+ // event.
+
+ if (!mInitInfo->sessionHistory) {
+ mInitInfo->sessionHistory = do_CreateInstance(NS_SHISTORY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mDocShellAsNav->SetSessionHistory(mInitInfo->sessionHistory);
+
+ if (XRE_IsParentProcess()) {
+ // Hook up global history. Do not fail if we can't - just warn.
+ rv = EnableGlobalHistory(mShouldEnableHistory);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EnableGlobalHistory() failed");
+ }
+
+ NS_ENSURE_SUCCESS(mDocShellAsWin->Create(), NS_ERROR_FAILURE);
+
+ // Hook into the OnSecurityChange() notification for lock/unlock icon
+ // updates
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ rv = GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (NS_SUCCEEDED(rv)) {
+ // this works because the implementation of nsISecureBrowserUI
+ // (nsSecureBrowserUIImpl) gets a docShell from the domWindow,
+ // and calls docShell->SetSecurityUI(this);
+ nsCOMPtr<nsISecureBrowserUI> securityUI =
+ do_CreateInstance(NS_SECURE_BROWSER_UI_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ securityUI->Init(domWindow);
+ }
+ }
+
+ mDocShellTreeOwner->AddToWatcher(); // evil twin of Remove in SetDocShell(0)
+ mDocShellTreeOwner->AddChromeListeners();
+
+ mInitInfo = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::Destroy()
+{
+ InternalDestroy();
+
+ if (!mInitInfo) {
+ mInitInfo = new nsWebBrowserInitInfo();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetUnscaledDevicePixelsPerCSSPixel(double* aScale)
+{
+ *aScale = mParentWidget ? mParentWidget->GetDefaultScale().scale : 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetDevicePixelsPerDesktopPixel(double* aScale)
+{
+ *aScale = mParentWidget ? mParentWidget->GetDesktopToDeviceScale().scale
+ : 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetPositionDesktopPix(int32_t aX, int32_t aY)
+{
+ // XXX jfkthame
+ // It's not clear to me whether this will be fully correct across
+ // potential multi-screen, mixed-DPI configurations for all platforms;
+ // we might need to add code paths that make it possible to pass the
+ // desktop-pix parameters all the way through to the native widget,
+ // to avoid the risk of device-pixel coords mapping to the wrong
+ // display on OS X with mixed retina/non-retina screens.
+ double scale = 1.0;
+ GetDevicePixelsPerDesktopPixel(&scale);
+ return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale));
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetPosition(int32_t aX, int32_t aY)
+{
+ int32_t cx = 0;
+ int32_t cy = 0;
+
+ GetSize(&cx, &cy);
+
+ return SetPositionAndSize(aX, aY, cx, cy, 0);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetPosition(int32_t* aX, int32_t* aY)
+{
+ return GetPositionAndSize(aX, aY, nullptr, nullptr);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetSize(int32_t aCX, int32_t aCY, bool aRepaint)
+{
+ int32_t x = 0;
+ int32_t y = 0;
+
+ GetPosition(&x, &y);
+
+ return SetPositionAndSize(x, y, aCX, aCY,
+ aRepaint ? nsIBaseWindow::eRepaint : 0);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetSize(int32_t* aCX, int32_t* aCY)
+{
+ return GetPositionAndSize(nullptr, nullptr, aCX, aCY);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetPositionAndSize(int32_t aX, int32_t aY,
+ int32_t aCX, int32_t aCY, uint32_t aFlags)
+{
+ if (!mDocShell) {
+ mInitInfo->x = aX;
+ mInitInfo->y = aY;
+ mInitInfo->cx = aCX;
+ mInitInfo->cy = aCY;
+ } else {
+ int32_t doc_x = aX;
+ int32_t doc_y = aY;
+
+ // If there is an internal widget we need to make the docShell coordinates
+ // relative to the internal widget rather than the calling app's parent.
+ // We also need to resize our widget then.
+ if (mInternalWidget) {
+ doc_x = doc_y = 0;
+ NS_ENSURE_SUCCESS(mInternalWidget->Resize(aX, aY, aCX, aCY,
+ !!(aFlags & nsIBaseWindow::eRepaint)),
+ NS_ERROR_FAILURE);
+ }
+ // Now reposition/ resize the doc
+ NS_ENSURE_SUCCESS(
+ mDocShellAsWin->SetPositionAndSize(doc_x, doc_y, aCX, aCY, aFlags),
+ NS_ERROR_FAILURE);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetPositionAndSize(int32_t* aX, int32_t* aY,
+ int32_t* aCX, int32_t* aCY)
+{
+ if (!mDocShell) {
+ if (aX) {
+ *aX = mInitInfo->x;
+ }
+ if (aY) {
+ *aY = mInitInfo->y;
+ }
+ if (aCX) {
+ *aCX = mInitInfo->cx;
+ }
+ if (aCY) {
+ *aCY = mInitInfo->cy;
+ }
+ } else if (mInternalWidget) {
+ LayoutDeviceIntRect bounds = mInternalWidget->GetBounds();
+
+ if (aX) {
+ *aX = bounds.x;
+ }
+ if (aY) {
+ *aY = bounds.y;
+ }
+ if (aCX) {
+ *aCX = bounds.width;
+ }
+ if (aCY) {
+ *aCY = bounds.height;
+ }
+ return NS_OK;
+ } else {
+ // Can directly return this as it is the
+ // same interface, thus same returns.
+ return mDocShellAsWin->GetPositionAndSize(aX, aY, aCX, aCY);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::Repaint(bool aForce)
+{
+ NS_ENSURE_STATE(mDocShell);
+ // Can directly return this as it is the
+ // same interface, thus same returns.
+ return mDocShellAsWin->Repaint(aForce);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetParentWidget(nsIWidget** aParentWidget)
+{
+ NS_ENSURE_ARG_POINTER(aParentWidget);
+
+ *aParentWidget = mParentWidget;
+
+ NS_IF_ADDREF(*aParentWidget);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetParentWidget(nsIWidget* aParentWidget)
+{
+ NS_ENSURE_STATE(!mDocShell);
+
+ mParentWidget = aParentWidget;
+ if (mParentWidget) {
+ mParentNativeWindow = mParentWidget->GetNativeData(NS_NATIVE_WIDGET);
+ } else {
+ mParentNativeWindow = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetParentNativeWindow(nativeWindow* aParentNativeWindow)
+{
+ NS_ENSURE_ARG_POINTER(aParentNativeWindow);
+
+ *aParentNativeWindow = mParentNativeWindow;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetParentNativeWindow(nativeWindow aParentNativeWindow)
+{
+ NS_ENSURE_STATE(!mDocShell);
+
+ mParentNativeWindow = aParentNativeWindow;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetNativeHandle(nsAString& aNativeHandle)
+{
+ // the nativeHandle should be accessed from nsIXULWindow
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetVisibility(bool* aVisibility)
+{
+ NS_ENSURE_ARG_POINTER(aVisibility);
+
+ if (!mDocShell) {
+ *aVisibility = mInitInfo->visible;
+ } else {
+ NS_ENSURE_SUCCESS(mDocShellAsWin->GetVisibility(aVisibility),
+ NS_ERROR_FAILURE);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetVisibility(bool aVisibility)
+{
+ if (!mDocShell) {
+ mInitInfo->visible = aVisibility;
+ } else {
+ NS_ENSURE_SUCCESS(mDocShellAsWin->SetVisibility(aVisibility),
+ NS_ERROR_FAILURE);
+ if (mInternalWidget) {
+ mInternalWidget->Show(aVisibility);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetEnabled(bool* aEnabled)
+{
+ if (mInternalWidget) {
+ *aEnabled = mInternalWidget->IsEnabled();
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetEnabled(bool aEnabled)
+{
+ if (mInternalWidget) {
+ return mInternalWidget->Enable(aEnabled);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetMainWidget(nsIWidget** aMainWidget)
+{
+ NS_ENSURE_ARG_POINTER(aMainWidget);
+
+ if (mInternalWidget) {
+ *aMainWidget = mInternalWidget;
+ } else {
+ *aMainWidget = mParentWidget;
+ }
+
+ NS_IF_ADDREF(*aMainWidget);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetFocus()
+{
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ return fm ? fm->SetFocusedWindow(window) : NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetTitle(char16_t** aTitle)
+{
+ NS_ENSURE_ARG_POINTER(aTitle);
+ NS_ENSURE_STATE(mDocShell);
+
+ NS_ENSURE_SUCCESS(mDocShellAsWin->GetTitle(aTitle), NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetTitle(const char16_t* aTitle)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ NS_ENSURE_SUCCESS(mDocShellAsWin->SetTitle(aTitle), NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsIScrollable
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::GetDefaultScrollbarPreferences(int32_t aScrollOrientation,
+ int32_t* aScrollbarPref)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsScrollable->GetDefaultScrollbarPreferences(
+ aScrollOrientation, aScrollbarPref);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetDefaultScrollbarPreferences(int32_t aScrollOrientation,
+ int32_t aScrollbarPref)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsScrollable->SetDefaultScrollbarPreferences(
+ aScrollOrientation, aScrollbarPref);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetScrollbarVisibility(bool* aVerticalVisible,
+ bool* aHorizontalVisible)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsScrollable->GetScrollbarVisibility(aVerticalVisible,
+ aHorizontalVisible);
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsITextScroll
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::ScrollByLines(int32_t aNumLines)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsTextScroll->ScrollByLines(aNumLines);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::ScrollByPages(int32_t aNumPages)
+{
+ NS_ENSURE_STATE(mDocShell);
+
+ return mDocShellAsTextScroll->ScrollByPages(aNumPages);
+}
+
+//*****************************************************************************
+// nsWebBrowser: Listener Helpers
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::SetDocShell(nsIDocShell* aDocShell)
+{
+ // We need to keep the docshell alive while we perform the changes, but we
+ // don't need to call any methods on it.
+ nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocShell);
+ mozilla::Unused << kungFuDeathGrip;
+
+ if (aDocShell) {
+ NS_ENSURE_TRUE(!mDocShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(aDocShell));
+ nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(aDocShell));
+ nsCOMPtr<nsIWebNavigation> nav(do_QueryInterface(aDocShell));
+ nsCOMPtr<nsIScrollable> scrollable(do_QueryInterface(aDocShell));
+ nsCOMPtr<nsITextScroll> textScroll(do_QueryInterface(aDocShell));
+ nsCOMPtr<nsIWebProgress> progress(do_GetInterface(aDocShell));
+ NS_ENSURE_TRUE(req && baseWin && nav && scrollable && textScroll && progress,
+ NS_ERROR_FAILURE);
+
+ mDocShell = aDocShell;
+ mDocShellAsReq = req;
+ mDocShellAsWin = baseWin;
+ mDocShellAsNav = nav;
+ mDocShellAsScrollable = scrollable;
+ mDocShellAsTextScroll = textScroll;
+ mWebProgress = progress;
+
+ // By default, do not allow DNS prefetch, so we don't break our frozen
+ // API. Embeddors who decide to enable it should do so manually.
+ mDocShell->SetAllowDNSPrefetch(false);
+
+ // It's possible to call setIsActive() on us before we have a docshell.
+ // If we're getting a docshell now, pass along our desired value. The
+ // default here (true) matches the default of the docshell, so this is
+ // a no-op unless setIsActive(false) has been called on us.
+ mDocShell->SetIsActive(mIsActive);
+ } else {
+ if (mDocShellTreeOwner) {
+ mDocShellTreeOwner->RemoveFromWatcher(); // evil twin of Add in Create()
+ }
+ if (mDocShellAsWin) {
+ mDocShellAsWin->Destroy();
+ }
+
+ mDocShell = nullptr;
+ mDocShellAsReq = nullptr;
+ mDocShellAsWin = nullptr;
+ mDocShellAsNav = nullptr;
+ mDocShellAsScrollable = nullptr;
+ mDocShellAsTextScroll = nullptr;
+ mWebProgress = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::EnsureDocShellTreeOwner()
+{
+ if (mDocShellTreeOwner) {
+ return NS_OK;
+ }
+
+ mDocShellTreeOwner = new nsDocShellTreeOwner();
+ mDocShellTreeOwner->WebBrowser(this);
+
+ return NS_OK;
+}
+
+static void
+DrawPaintedLayer(PaintedLayer* aLayer,
+ gfxContext* aContext,
+ const nsIntRegion& aRegionToDraw,
+ const nsIntRegion& aDirtyRegion,
+ DrawRegionClip aClip,
+ const nsIntRegion& aRegionToInvalidate,
+ void* aCallbackData)
+{
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ ColorPattern color(ToDeviceColor(*static_cast<nscolor*>(aCallbackData)));
+ nsIntRect dirtyRect = aRegionToDraw.GetBounds();
+ aDrawTarget.FillRect(
+ Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height), color);
+}
+
+void
+nsWebBrowser::WindowRaised(nsIWidget* aWidget)
+{
+#if defined(DEBUG_smaug)
+ nsCOMPtr<nsIDocument> document = mDocShell->GetDocument();
+ nsAutoString documentURI;
+ document->GetDocumentURI(documentURI);
+ printf("nsWebBrowser::NS_ACTIVATE %p %s\n", (void*)this,
+ NS_ConvertUTF16toUTF8(documentURI).get());
+#endif
+ Activate();
+}
+
+void
+nsWebBrowser::WindowLowered(nsIWidget* aWidget)
+{
+#if defined(DEBUG_smaug)
+ nsCOMPtr<nsIDocument> document = mDocShell->GetDocument();
+ nsAutoString documentURI;
+ document->GetDocumentURI(documentURI);
+ printf("nsWebBrowser::NS_DEACTIVATE %p %s\n", (void*)this,
+ NS_ConvertUTF16toUTF8(documentURI).get());
+#endif
+ Deactivate();
+}
+
+bool
+nsWebBrowser::PaintWindow(nsIWidget* aWidget, LayoutDeviceIntRegion aRegion)
+{
+ LayerManager* layerManager = aWidget->GetLayerManager();
+ NS_ASSERTION(layerManager, "Must be in paint event");
+
+ layerManager->BeginTransaction();
+ RefPtr<PaintedLayer> root = layerManager->CreatePaintedLayer();
+ if (root) {
+ nsIntRect dirtyRect = aRegion.GetBounds().ToUnknownRect();
+ root->SetVisibleRegion(LayerIntRegion::FromUnknownRegion(dirtyRect));
+ layerManager->SetRoot(root);
+ }
+
+ layerManager->EndTransaction(DrawPaintedLayer, &mBackgroundColor);
+ return true;
+}
+/*
+NS_IMETHODIMP
+nsWebBrowser::GetPrimaryContentWindow(mozIDOMWindowProxy** aDOMWindow)
+{
+ *aDOMWindow = nullptr;
+
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ NS_ENSURE_TRUE(mDocShellTreeOwner, NS_ERROR_FAILURE);
+ mDocShellTreeOwner->GetPrimaryContentShell(getter_AddRefs(item));
+ NS_ENSURE_TRUE(item, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocShell> docShell;
+ docShell = do_QueryInterface(item);
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = docShell->GetWindow();
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+
+ *aDOMWindow = domWindow;
+ NS_ADDREF(*aDOMWindow);
+ return NS_OK;
+}
+*/
+//*****************************************************************************
+// nsWebBrowser::nsIWebBrowserFocus
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::Activate(void)
+{
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (fm && window) {
+ return fm->WindowRaised(window);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::Deactivate(void)
+{
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+ if (fm && window) {
+ return fm->WindowLowered(window);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetFocusAtFirstElement(void)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetFocusAtLastElement(void)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow)
+{
+ NS_ENSURE_ARG_POINTER(aFocusedWindow);
+ *aFocusedWindow = nullptr;
+
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow();
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDOMElement> focusedElement;
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ return fm ? fm->GetFocusedElementForWindow(window, true, aFocusedWindow,
+ getter_AddRefs(focusedElement)) :
+ NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetFocusedWindow(mozIDOMWindowProxy* aFocusedWindow)
+{
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ return fm ? fm->SetFocusedWindow(aFocusedWindow) : NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::GetFocusedElement(nsIDOMElement** aFocusedElement)
+{
+ NS_ENSURE_ARG_POINTER(aFocusedElement);
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow();
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ return
+ fm ? fm->GetFocusedElementForWindow(window, true, nullptr, aFocusedElement) :
+ NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebBrowser::SetFocusedElement(nsIDOMElement* aFocusedElement)
+{
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ return fm ? fm->SetFocus(aFocusedElement, 0) : NS_OK;
+}
+
+//*****************************************************************************
+// nsWebBrowser::nsIWebBrowserStream
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsWebBrowser::OpenStream(nsIURI* aBaseURI, const nsACString& aContentType)
+{
+ nsresult rv;
+
+ if (!mStream) {
+ mStream = new nsEmbedStream();
+ mStream->InitOwner(this);
+ rv = mStream->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return mStream->OpenStream(aBaseURI, aContentType);
+}
+
+
+NS_IMETHODIMP
+nsWebBrowser::AppendToStream(const uint8_t* aData, uint32_t aLen)
+{
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mStream->AppendToStream(aData, aLen);
+}
+
+NS_IMETHODIMP
+nsWebBrowser::CloseStream()
+{
+ nsresult rv;
+
+ if (!mStream) {
+ return NS_ERROR_FAILURE;
+ }
+ rv = mStream->CloseStream();
+
+ mStream = nullptr;
+
+ return rv;
+}
+
+bool
+nsWebBrowser::WidgetListenerDelegate::PaintWindow(
+ nsIWidget* aWidget, mozilla::LayoutDeviceIntRegion aRegion)
+{
+ RefPtr<nsWebBrowser> holder = mWebBrowser;
+ return holder->PaintWindow(aWidget, aRegion);
+}
diff --git a/components/webbrowser/src/nsWebBrowser.h b/components/webbrowser/src/nsWebBrowser.h
new file mode 100644
index 000000000..10ea56307
--- /dev/null
+++ b/components/webbrowser/src/nsWebBrowser.h
@@ -0,0 +1,184 @@
+/* -*- 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/. */
+
+#ifndef nsWebBrowser_h__
+#define nsWebBrowser_h__
+
+// Local Includes
+#include "nsDocShellTreeOwner.h"
+
+// Core Includes
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+
+// Interfaces needed
+#include "nsCWebBrowser.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIScrollable.h"
+#include "nsISHistory.h"
+#include "nsITextScroll.h"
+#include "nsIWidget.h"
+#include "nsIWebProgress.h"
+#include "nsISecureBrowserUI.h"
+#include "nsIWebBrowser.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebBrowserSetup.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsIWebBrowserFocus.h"
+#include "nsIWebBrowserStream.h"
+#include "nsIWindowWatcher.h"
+#include "nsIPrintSettings.h"
+#include "nsEmbedStream.h"
+#include "nsIWidgetListener.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "nsTArray.h"
+#include "nsWeakPtr.h"
+
+class nsWebBrowserInitInfo
+{
+public:
+ // nsIBaseWindow Stuff
+ int32_t x;
+ int32_t y;
+ int32_t cx;
+ int32_t cy;
+ bool visible;
+ nsCOMPtr<nsISHistory> sessionHistory;
+ nsString name;
+};
+
+class nsWebBrowserListenerState
+{
+public:
+ bool Equals(nsIWeakReference* aListener, const nsIID& aID)
+ {
+ return mWeakPtr.get() == aListener && mID.Equals(aID);
+ }
+
+ nsWeakPtr mWeakPtr;
+ nsIID mID;
+};
+
+// {cda5863a-aa9c-411e-be49-ea0d525ab4b5} -
+#define NS_WEBBROWSER_CID \
+ { 0xcda5863a, 0xaa9c, 0x411e, { 0xbe, 0x49, 0xea, 0x0d, 0x52, 0x5a, 0xb4, 0xb5 } }
+
+
+class nsWebBrowser final : public nsIWebBrowser,
+ public nsIWebNavigation,
+ public nsIWebBrowserSetup,
+ public nsIDocShellTreeItem,
+ public nsIBaseWindow,
+ public nsIScrollable,
+ public nsITextScroll,
+ public nsIInterfaceRequestor,
+ public nsIWebBrowserPersist,
+ public nsIWebBrowserFocus,
+ public nsIWebProgressListener,
+ public nsIWebBrowserStream,
+ public nsSupportsWeakReference
+{
+ friend class nsDocShellTreeOwner;
+
+public:
+
+ // The implementation of non-refcounted nsIWidgetListener, which would hold a
+ // strong reference on stack before calling nsWebBrowser.
+ class WidgetListenerDelegate : public nsIWidgetListener
+ {
+ public:
+ explicit WidgetListenerDelegate(nsWebBrowser* aWebBrowser)
+ : mWebBrowser(aWebBrowser) {}
+ virtual bool PaintWindow(
+ nsIWidget* aWidget, mozilla::LayoutDeviceIntRegion aRegion) override;
+
+ private:
+ // The lifetime of WidgetListenerDelegate is bound to nsWebBrowser so we
+ // just use raw pointer here.
+ nsWebBrowser* mWebBrowser;
+ };
+
+ nsWebBrowser();
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIBASEWINDOW
+ NS_DECL_NSIDOCSHELLTREEITEM
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSISCROLLABLE
+ NS_DECL_NSITEXTSCROLL
+ NS_DECL_NSIWEBBROWSER
+ NS_DECL_NSIWEBNAVIGATION
+ NS_DECL_NSIWEBBROWSERSETUP
+ NS_DECL_NSIWEBBROWSERPERSIST
+ NS_DECL_NSICANCELABLE
+ NS_DECL_NSIWEBBROWSERFOCUS
+ NS_DECL_NSIWEBBROWSERSTREAM
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+protected:
+ virtual ~nsWebBrowser();
+ NS_IMETHOD InternalDestroy();
+
+ // XXXbz why are these NS_IMETHOD? They're not interface methods!
+ NS_IMETHOD SetDocShell(nsIDocShell* aDocShell);
+ NS_IMETHOD EnsureDocShellTreeOwner();
+ NS_IMETHOD BindListener(nsISupports* aListener, const nsIID& aIID);
+ NS_IMETHOD UnBindListener(nsISupports* aListener, const nsIID& aIID);
+ NS_IMETHOD EnableGlobalHistory(bool aEnable);
+
+ // nsIWidgetListener
+ virtual void WindowRaised(nsIWidget* aWidget);
+ virtual void WindowLowered(nsIWidget* aWidget);
+ bool PaintWindow(nsIWidget* aWidget, mozilla::LayoutDeviceIntRegion aRegion);
+
+protected:
+ RefPtr<nsDocShellTreeOwner> mDocShellTreeOwner;
+ nsCOMPtr<nsIDocShell> mDocShell;
+ nsCOMPtr<nsIInterfaceRequestor> mDocShellAsReq;
+ nsCOMPtr<nsIBaseWindow> mDocShellAsWin;
+ nsCOMPtr<nsIWebNavigation> mDocShellAsNav;
+ nsCOMPtr<nsIScrollable> mDocShellAsScrollable;
+ nsCOMPtr<nsITextScroll> mDocShellAsTextScroll;
+ mozilla::DocShellOriginAttributes mOriginAttributes;
+
+ nsCOMPtr<nsIWidget> mInternalWidget;
+ nsCOMPtr<nsIWindowWatcher> mWWatch;
+ nsAutoPtr<nsWebBrowserInitInfo> mInitInfo;
+ uint32_t mContentType;
+ bool mActivating;
+ bool mShouldEnableHistory;
+ bool mIsActive;
+ nativeWindow mParentNativeWindow;
+ nsIWebProgressListener* mProgressListener;
+ nsCOMPtr<nsIWebProgress> mWebProgress;
+
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+
+ WidgetListenerDelegate mWidgetListenerDelegate;
+
+ // cached background color
+ nscolor mBackgroundColor;
+
+ // persistence object
+ nsCOMPtr<nsIWebBrowserPersist> mPersist;
+ uint32_t mPersistCurrentState;
+ nsresult mPersistResult;
+ uint32_t mPersistFlags;
+
+ // stream
+ RefPtr<nsEmbedStream> mStream;
+
+ // Weak Reference interfaces...
+ nsIWidget* mParentWidget;
+ nsAutoPtr<nsTArray<nsWebBrowserListenerState> > mListenerArray;
+};
+
+#endif /* nsWebBrowser_h__ */
diff --git a/components/webbrowser/src/nsWebBrowserContentPolicy.cpp b/components/webbrowser/src/nsWebBrowserContentPolicy.cpp
new file mode 100644
index 000000000..7e00c1437
--- /dev/null
+++ b/components/webbrowser/src/nsWebBrowserContentPolicy.cpp
@@ -0,0 +1,107 @@
+/* -*- 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 "nsWebBrowserContentPolicy.h"
+#include "nsIDocShell.h"
+#include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIContentViewer.h"
+
+nsWebBrowserContentPolicy::nsWebBrowserContentPolicy()
+{
+ MOZ_COUNT_CTOR(nsWebBrowserContentPolicy);
+}
+
+nsWebBrowserContentPolicy::~nsWebBrowserContentPolicy()
+{
+ MOZ_COUNT_DTOR(nsWebBrowserContentPolicy);
+}
+
+NS_IMPL_ISUPPORTS(nsWebBrowserContentPolicy, nsIContentPolicy)
+
+NS_IMETHODIMP
+nsWebBrowserContentPolicy::ShouldLoad(uint32_t aContentType,
+ nsIURI* aContentLocation,
+ nsIURI* aRequestingLocation,
+ nsISupports* aRequestingContext,
+ const nsACString& aMimeGuess,
+ nsISupports* aExtra,
+ nsIPrincipal* aRequestPrincipal,
+ int16_t* aShouldLoad)
+{
+ NS_PRECONDITION(aShouldLoad, "Null out param");
+
+ MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
+ "We should only see external content policy types here.");
+
+ *aShouldLoad = nsIContentPolicy::ACCEPT;
+
+ nsIDocShell* shell = NS_CP_GetDocShellFromContext(aRequestingContext);
+ /* We're going to dereference shell, so make sure it isn't null */
+ if (!shell) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ bool allowed = true;
+
+ switch (aContentType) {
+ case nsIContentPolicy::TYPE_SCRIPT:
+ rv = shell->GetAllowJavascript(&allowed);
+ break;
+ case nsIContentPolicy::TYPE_SUBDOCUMENT:
+ rv = shell->GetAllowSubframes(&allowed);
+ break;
+#if 0
+ /* XXXtw: commented out in old code; add during conpol phase 2 */
+ case nsIContentPolicy::TYPE_REFRESH:
+ rv = shell->GetAllowMetaRedirects(&allowed); /* meta _refresh_ */
+ break;
+#endif
+ case nsIContentPolicy::TYPE_IMAGE:
+ case nsIContentPolicy::TYPE_IMAGESET:
+ rv = shell->GetAllowImages(&allowed);
+ break;
+ default:
+ return NS_OK;
+ }
+
+ if (NS_SUCCEEDED(rv) && !allowed) {
+ *aShouldLoad = nsIContentPolicy::REJECT_TYPE;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWebBrowserContentPolicy::ShouldProcess(uint32_t aContentType,
+ nsIURI* aContentLocation,
+ nsIURI* aRequestingLocation,
+ nsISupports* aRequestingContext,
+ const nsACString& aMimeGuess,
+ nsISupports* aExtra,
+ nsIPrincipal* aRequestPrincipal,
+ int16_t* aShouldProcess)
+{
+ NS_PRECONDITION(aShouldProcess, "Null out param");
+
+ MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
+ "We should only see external content policy types here.");
+
+ *aShouldProcess = nsIContentPolicy::ACCEPT;
+
+ // Object tags will always open channels with TYPE_OBJECT, but may end up
+ // loading with TYPE_IMAGE or TYPE_DOCUMENT as their final type, so we block
+ // actual-plugins at the process stage
+ if (aContentType != nsIContentPolicy::TYPE_OBJECT) {
+ return NS_OK;
+ }
+
+ nsIDocShell* shell = NS_CP_GetDocShellFromContext(aRequestingContext);
+ if (shell && (!shell->PluginsAllowedInCurrentDoc())) {
+ *aShouldProcess = nsIContentPolicy::REJECT_TYPE;
+ }
+
+ return NS_OK;
+}
diff --git a/components/webbrowser/src/nsWebBrowserContentPolicy.h b/components/webbrowser/src/nsWebBrowserContentPolicy.h
new file mode 100644
index 000000000..0431a26a8
--- /dev/null
+++ b/components/webbrowser/src/nsWebBrowserContentPolicy.h
@@ -0,0 +1,25 @@
+/* -*- 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 "nsIContentPolicy.h"
+
+/* f66bc334-1dd1-11b2-bab2-90e04fe15c19 */
+#define NS_WEBBROWSERCONTENTPOLICY_CID \
+ { 0xf66bc334, 0x1dd1, 0x11b2, { 0xba, 0xb2, 0x90, 0xe0, 0x4f, 0xe1, 0x5c, 0x19 } }
+
+#define NS_WEBBROWSERCONTENTPOLICY_CONTRACTID \
+ "@mozilla.org/embedding/browser/content-policy;1"
+
+class nsWebBrowserContentPolicy : public nsIContentPolicy
+{
+protected:
+ virtual ~nsWebBrowserContentPolicy();
+
+public:
+ nsWebBrowserContentPolicy();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+};
diff --git a/components/webbrowser/src/nsWebBrowserModule.cpp b/components/webbrowser/src/nsWebBrowserModule.cpp
new file mode 100644
index 000000000..bec5af703
--- /dev/null
+++ b/components/webbrowser/src/nsWebBrowserModule.cpp
@@ -0,0 +1,52 @@
+/* -*- 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 "mozilla/ModuleUtils.h"
+#include "nsIServiceManager.h"
+#include "nsXPIDLString.h"
+
+#include "nsEmbedCID.h"
+
+#include "nsWebBrowser.h"
+#include "nsCommandHandler.h"
+#include "nsWebBrowserContentPolicy.h"
+
+// Factory Constructors
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowser)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowserContentPolicy)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandHandler)
+
+NS_DEFINE_NAMED_CID(NS_WEBBROWSER_CID);
+NS_DEFINE_NAMED_CID(NS_COMMANDHANDLER_CID);
+NS_DEFINE_NAMED_CID(NS_WEBBROWSERCONTENTPOLICY_CID);
+
+static const mozilla::Module::CIDEntry kWebBrowserCIDs[] = {
+ { &kNS_WEBBROWSER_CID, false, nullptr, nsWebBrowserConstructor },
+ { &kNS_COMMANDHANDLER_CID, false, nullptr, nsCommandHandlerConstructor },
+ { &kNS_WEBBROWSERCONTENTPOLICY_CID, false, nullptr, nsWebBrowserContentPolicyConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWebBrowserContracts[] = {
+ { NS_WEBBROWSER_CONTRACTID, &kNS_WEBBROWSER_CID },
+ { NS_COMMANDHANDLER_CONTRACTID, &kNS_COMMANDHANDLER_CID },
+ { NS_WEBBROWSERCONTENTPOLICY_CONTRACTID, &kNS_WEBBROWSERCONTENTPOLICY_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kWebBrowserCategories[] = {
+ { "content-policy", NS_WEBBROWSERCONTENTPOLICY_CONTRACTID, NS_WEBBROWSERCONTENTPOLICY_CONTRACTID },
+ { nullptr }
+};
+
+static const mozilla::Module kWebBrowserModule = {
+ mozilla::Module::kVersion,
+ kWebBrowserCIDs,
+ kWebBrowserContracts,
+ kWebBrowserCategories
+};
+
+NSMODULE_DEFN(Browser_Embedding_Module) = &kWebBrowserModule;
diff --git a/components/windowcreator/moz.build b/components/windowcreator/moz.build
new file mode 100644
index 000000000..a3de23359
--- /dev/null
+++ b/components/windowcreator/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsIWindowCreator.idl',
+ 'public/nsIWindowCreator2.idl',
+ 'public/nsIWindowProvider.idl',
+]
+
+XPIDL_MODULE = 'windowcreator'
diff --git a/components/windowcreator/public/nsIWindowCreator.idl b/components/windowcreator/public/nsIWindowCreator.idl
new file mode 100644
index 000000000..ade2dc467
--- /dev/null
+++ b/components/windowcreator/public/nsIWindowCreator.idl
@@ -0,0 +1,41 @@
+/* -*- 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/. */
+
+/**
+ * nsIWindowCreator is a callback interface used by Gecko to create
+ * new browser windows. The application, either Mozilla or an embedding app,
+ * must provide an implementation of the Window Watcher component and
+ * notify the WindowWatcher during application initialization.
+ * @see nsIWindowWatcher
+ */
+
+#include "nsISupports.idl"
+
+interface nsIWebBrowserChrome;
+
+[scriptable, uuid(30465632-A777-44cc-90F9-8145475EF999)]
+
+interface nsIWindowCreator : nsISupports {
+
+ /** Create a new window. Gecko will/may call this method, if made
+ available to it, to create new windows.
+ @param parent parent window, if any. null if not. the newly created
+ window should be made a child/dependent window of
+ the parent, if any (and if the concept applies
+ to the underlying OS).
+ @param chromeFlags chrome features from nsIWebBrowserChrome
+ @return the new window
+ */
+ nsIWebBrowserChrome createChromeWindow(in nsIWebBrowserChrome parent,
+ in uint32_t chromeFlags);
+};
+
+%{C++
+// {30465632-A777-44cc-90F9-8145475EF999}
+#define NS_WINDOWCREATOR_IID \
+ {0x30465632, 0xa777, 0x44cc, {0x90, 0xf9, 0x81, 0x45, 0x47, 0x5e, 0xf9, 0x99}}
+%}
+
diff --git a/components/windowcreator/public/nsIWindowCreator2.idl b/components/windowcreator/public/nsIWindowCreator2.idl
new file mode 100644
index 000000000..c07a794a0
--- /dev/null
+++ b/components/windowcreator/public/nsIWindowCreator2.idl
@@ -0,0 +1,70 @@
+/* -*- 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/. */
+
+/**
+ * nsIWindowCreator2 is an extension of nsIWindowCreator which allows
+ * additional information about the context of the window creation to
+ * be passed.
+ *
+ * @see nsIWindowCreator
+ * @see nsIWindowWatcher
+ *
+ * @status
+ */
+
+#include "nsIWindowCreator.idl"
+
+interface nsITabParent;
+interface nsIURI;
+interface nsIWebBrowserChrome;
+interface mozIDOMWindowProxy;
+
+[scriptable, uuid(b6c44689-f97e-4f32-a723-29eeddfbdc53)]
+
+interface nsIWindowCreator2 : nsIWindowCreator {
+
+ /**
+ * Definitions for contextFlags
+ */
+
+ // Likely that the window is an advertising popup.
+ const unsigned long PARENT_IS_LOADING_OR_RUNNING_TIMEOUT = 0x00000001;
+
+ /** Create a new window. Gecko will/may call this method, if made
+ available to it, to create new windows.
+ @param parent Parent window, if any. Null if not. The newly created
+ window should be made a child/dependent window of
+ the parent, if any (and if the concept applies
+ to the underlying OS).
+ @param chromeFlags Chrome features from nsIWebBrowserChrome
+ @param contextFlags Flags about the context of the window being created.
+ @param aOpeningTab The TabParent that is trying to open this new chrome
+ window. Can be nullptr.
+ @param aOpener The window which is trying to open this new chrome window.
+ Can be nullptr
+ @param cancel Return |true| to reject window creation. If true the
+ implementation has determined the window should not
+ be created at all. The caller should not default
+ to any possible backup scheme for creating the window.
+ @return the new window. Will be null if canceled or an error occurred.
+ */
+ nsIWebBrowserChrome createChromeWindow2(in nsIWebBrowserChrome parent,
+ in uint32_t chromeFlags,
+ in uint32_t contextFlags,
+ in nsITabParent aOpeningTab,
+ in mozIDOMWindowProxy aOpener,
+ out boolean cancel);
+
+ /**
+ * B2G multi-screen support. When open another top-level window on b2g,
+ * a screen ID is needed for identifying which screen this window is
+ * opened to.
+ * @param aScreenId Differentiate screens of windows. It is platform-
+ * specific due to the hardware limitation for now.
+ */
+ [noscript]
+ void setScreenId(in uint32_t aScreenId);
+};
diff --git a/components/windowcreator/public/nsIWindowProvider.idl b/components/windowcreator/public/nsIWindowProvider.idl
new file mode 100644
index 000000000..34c517312
--- /dev/null
+++ b/components/windowcreator/public/nsIWindowProvider.idl
@@ -0,0 +1,103 @@
+/* -*- 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/. */
+
+/**
+ * nsIWindowProvider is a callback interface used by Gecko when it needs to
+ * open a new window. This interface can be implemented by Gecko consumers who
+ * wish to provide a custom "new window" of their own (for example by returning
+ * a new tab, an existing window, etc) instead of just having a real new
+ * toplevel window open.
+ */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIURI;
+
+/**
+ * The nsIWindowProvider interface exists so that the window watcher's default
+ * behavior of opening a new window can be easly modified. When the window
+ * watcher needs to open a new window, it will first check with the
+ * nsIWindowProvider it gets from the parent window. If there is no provider
+ * or the provider does not provide a window, the window watcher will proceed
+ * to actually open a new window.
+ */
+[scriptable, uuid(e97a3830-15ef-499b-8372-c22d128091c1)]
+interface nsIWindowProvider : nsISupports
+{
+ /**
+ * A method to request that this provider provide a window. The window
+ * returned need not to have the right name or parent set on it; setting
+ * those is the caller's responsibility. The provider can always return null
+ * to have the caller create a brand-new window.
+ *
+ * @param aParent Must not be null. This is the window that the caller wants
+ * to use as the parent for the new window. Generally,
+ * nsIWindowProvider implementors can expect to be somehow related to
+ * aParent; the relationship may depend on the nsIWindowProvider
+ * implementation.
+ *
+ * @param aChromeFlags The chrome flags the caller will use to create a new
+ * window if this provider returns null. See nsIWebBrowserChrome for
+ * the possible values of this field.
+ *
+ * @param aPositionSpecified Whether the attempt to create a window is trying
+ * to specify a position for the new window.
+ *
+ * @param aSizeSpecified Whether the attempt to create a window is trying to
+ * specify a size for the new window.
+ *
+ * @param aURI The URI to be loaded in the new window (may be NULL). The
+ * nsIWindowProvider implementation must not load this URI into the
+ * window it returns. This URI is provided solely to help the
+ * nsIWindowProvider implementation make decisions; the caller will
+ * handle loading the URI in the window returned if provideWindow
+ * returns a window.
+ *
+ * When making decisions based on aURI, note that even when it's not
+ * null, aURI may not represent all relevant information about the
+ * load. For example, the load may have extra load flags, POST data,
+ * etc.
+ *
+ * @param aName The name of the window being opened. Setting the name on the
+ * return value of provideWindow will be handled by the caller; aName
+ * is provided solely to help the nsIWindowProvider implementation
+ * make decisions.
+ *
+ * @param aFeatures The feature string for the window being opened. This may
+ * be empty. The nsIWindowProvider implementation is allowed to apply
+ * the feature string to the window it returns in any way it sees fit.
+ * See the nsIWindowWatcher interface for details on feature strings.
+ *
+ * @param aWindowIsNew [out] Whether the window being returned was just
+ * created by the window provider implementation. This can be used by
+ * callers to keep track of which windows were opened by the user as
+ * opposed to being opened programmatically. This should be set to
+ * false if the window being returned existed before the
+ * provideWindow() call. The value of this out parameter is
+ * meaningless if provideWindow() returns null.
+
+ * @return A window the caller should use or null if the caller should just
+ * create a new window. The returned window may be newly opened by
+ * the nsIWindowProvider implementation or may be a window that
+ * already existed.
+ *
+ * @throw NS_ERROR_ABORT if the caller should cease its attempt to open a new
+ * window.
+ *
+ * @see nsIWindowWatcher for more information on aFeatures.
+ * @see nsIWebBrowserChrome for more information on aChromeFlags.
+ */
+ mozIDOMWindowProxy provideWindow(in mozIDOMWindowProxy aParent,
+ in unsigned long aChromeFlags,
+ in boolean aCalledFromJS,
+ in boolean aPositionSpecified,
+ in boolean aSizeSpecified,
+ in nsIURI aURI,
+ in AString aName,
+ in AUTF8String aFeatures,
+ in boolean aForceNoOpener,
+ out boolean aWindowIsNew);
+};
diff --git a/components/windowds/moz.build b/components/windowds/moz.build
new file mode 100644
index 000000000..d0e8fa8d6
--- /dev/null
+++ b/components/windowds/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['public/nsIWindowDataSource.idl']
+
+SOURCES += ['src/nsWindowDataSource.cpp']
+
+XPIDL_MODULE = 'windowds'
+FINAL_LIBRARY = 'xul'
diff --git a/components/windowds/public/nsIWindowDataSource.idl b/components/windowds/public/nsIWindowDataSource.idl
new file mode 100644
index 000000000..6143a4317
--- /dev/null
+++ b/components/windowds/public/nsIWindowDataSource.idl
@@ -0,0 +1,17 @@
+/* -*- 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 "nsISupports.idl"
+#include "nsIDOMWindow.idl"
+
+// interface for accessing RDF-specific data from the window datasource
+[scriptable, uuid(3722A5B9-5323-4ed0-BB1A-8299F27A4E89)]
+interface nsIWindowDataSource : nsISupports
+{
+ /**
+ * for the given resource name, return the window
+ */
+ nsIDOMWindow getWindowForResource(in string inResource);
+};
diff --git a/components/windowds/src/nsWindowDataSource.cpp b/components/windowds/src/nsWindowDataSource.cpp
new file mode 100644
index 000000000..3e7a42060
--- /dev/null
+++ b/components/windowds/src/nsWindowDataSource.cpp
@@ -0,0 +1,519 @@
+/* -*- 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 "nsWindowDataSource.h"
+#include "nsIXULWindow.h"
+#include "rdf.h"
+#include "nsIRDFContainerUtils.h"
+#include "nsIServiceManager.h"
+#include "nsReadableUtils.h"
+#include "nsIObserverService.h"
+#include "nsIWindowMediator.h"
+#include "nsXPCOMCID.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsString.h"
+
+// just to do the reverse-lookup! sheesh.
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDocShell.h"
+
+uint32_t nsWindowDataSource::windowCount = 0;
+
+nsIRDFResource* nsWindowDataSource::kNC_Name = nullptr;
+nsIRDFResource* nsWindowDataSource::kNC_WindowRoot = nullptr;
+nsIRDFResource* nsWindowDataSource::kNC_KeyIndex = nullptr;
+
+nsIRDFService* nsWindowDataSource::gRDFService = nullptr;
+
+uint32_t nsWindowDataSource::gRefCnt = 0;
+
+#define URINC_WINDOWROOT "NC:WindowMediatorRoot"
+#define URINC_NAME NC_NAMESPACE_URI "Name"
+#define URINC_KEYINDEX NC_NAMESPACE_URI "KeyIndex"
+
+nsresult
+nsWindowDataSource::Init()
+{
+ nsresult rv;
+
+ if (gRefCnt++ == 0) {
+ rv = CallGetService("@mozilla.org/rdf/rdf-service;1", &gRDFService);
+ if (NS_FAILED(rv)) return rv;
+
+ gRDFService->GetResource(NS_LITERAL_CSTRING(URINC_WINDOWROOT), &kNC_WindowRoot);
+ gRDFService->GetResource(NS_LITERAL_CSTRING(URINC_NAME), &kNC_Name);
+ gRDFService->GetResource(NS_LITERAL_CSTRING(URINC_KEYINDEX), &kNC_KeyIndex);
+ }
+
+ mInner = do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRDFContainerUtils> rdfc =
+ do_GetService("@mozilla.org/rdf/container-utils;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = rdfc->MakeSeq(this, kNC_WindowRoot, getter_AddRefs(mContainer));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = windowMediator->AddListener(this);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+ false);
+ }
+ return NS_OK;
+}
+
+nsWindowDataSource::~nsWindowDataSource()
+{
+ if (--gRefCnt == 0) {
+ NS_IF_RELEASE(kNC_Name);
+ NS_IF_RELEASE(kNC_KeyIndex);
+ NS_IF_RELEASE(kNC_WindowRoot);
+ NS_IF_RELEASE(gRDFService);
+ }
+}
+
+NS_IMETHODIMP
+nsWindowDataSource::Observe(nsISupports *aSubject, const char* aTopic, const char16_t *aData)
+{
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ // release these objects so that they release their reference
+ // to us
+ mContainer = nullptr;
+ mInner = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsWindowDataSource)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsWindowDataSource)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsWindowDataSource)
+ // XXX mContainer?
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsWindowDataSource)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsWindowDataSource)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsWindowDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIWindowMediatorListener)
+ NS_INTERFACE_MAP_ENTRY(nsIWindowDataSource)
+ NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+NS_INTERFACE_MAP_END
+
+// nsIWindowMediatorListener implementation
+// handle notifications from the window mediator and reflect them into
+// RDF
+
+NS_IMETHODIMP
+nsWindowDataSource::OnWindowTitleChange(nsIXULWindow *window,
+ const char16_t *newTitle)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIRDFResource> windowResource;
+ mWindowResources.Get(window, getter_AddRefs(windowResource));
+
+ // oops, make sure this window is in the hashtable!
+ if (!windowResource) {
+ OnOpenWindow(window);
+ mWindowResources.Get(window, getter_AddRefs(windowResource));
+ }
+
+ NS_ENSURE_TRUE(windowResource, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIRDFLiteral> newTitleLiteral;
+ rv = gRDFService->GetLiteral(newTitle, getter_AddRefs(newTitleLiteral));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the old title
+ nsCOMPtr<nsIRDFNode> oldTitleNode;
+ rv = GetTarget(windowResource, kNC_Name, true,
+ getter_AddRefs(oldTitleNode));
+
+ // assert the change
+ if (NS_SUCCEEDED(rv) && oldTitleNode)
+ // has an existing window title, update it
+ rv = Change(windowResource, kNC_Name, oldTitleNode, newTitleLiteral);
+ else
+ // removed from the tasklist
+ rv = Assert(windowResource, kNC_Name, newTitleLiteral, true);
+
+ if (rv != NS_RDF_ASSERTION_ACCEPTED)
+ {
+ NS_ERROR("unable to set window name");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowDataSource::OnOpenWindow(nsIXULWindow *window)
+{
+ nsAutoCString windowId(NS_LITERAL_CSTRING("window-"));
+ windowId.AppendInt(windowCount++, 10);
+
+ nsCOMPtr<nsIRDFResource> windowResource;
+ gRDFService->GetResource(windowId, getter_AddRefs(windowResource));
+
+ mWindowResources.Put(window, windowResource);
+
+ // assert the new window
+ if (mContainer)
+ mContainer->AppendElement(windowResource);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowDataSource::OnCloseWindow(nsIXULWindow *window)
+{
+ nsresult rv;
+ nsCOMPtr<nsIRDFResource> resource;
+ mWindowResources.Get(window, getter_AddRefs(resource));
+ if (!resource) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mWindowResources.Remove(window);
+
+ // make sure we're not shutting down
+ if (!mContainer) return NS_OK;
+
+ nsCOMPtr<nsIRDFNode> oldKeyNode;
+ nsCOMPtr<nsIRDFInt> oldKeyInt;
+
+ // get the old keyIndex, if any
+ rv = GetTarget(resource, kNC_KeyIndex, true,
+ getter_AddRefs(oldKeyNode));
+ if (NS_SUCCEEDED(rv) && (rv != NS_RDF_NO_VALUE))
+ oldKeyInt = do_QueryInterface(oldKeyNode);
+
+
+ // update RDF and keyindex - from this point forward we'll ignore
+ // errors, because they just indicate some kind of RDF inconsistency
+ int32_t winIndex = -1;
+ rv = mContainer->IndexOf(resource, &winIndex);
+
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ // unassert the old window, ignore any error
+ mContainer->RemoveElement(resource, true);
+
+ nsCOMPtr<nsISimpleEnumerator> children;
+ rv = mContainer->GetElements(getter_AddRefs(children));
+ if (NS_FAILED(rv))
+ return NS_OK;
+
+ bool more = false;
+
+ while (NS_SUCCEEDED(rv = children->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> sup;
+ rv = children->GetNext(getter_AddRefs(sup));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIRDFResource> windowResource = do_QueryInterface(sup, &rv);
+ if (NS_FAILED(rv))
+ continue;
+
+ int32_t currentIndex = -1;
+ mContainer->IndexOf(windowResource, &currentIndex);
+
+ // can skip updating windows with lower indexes
+ // than the window that was removed
+ if (currentIndex < winIndex)
+ continue;
+
+ nsCOMPtr<nsIRDFNode> newKeyNode;
+ nsCOMPtr<nsIRDFInt> newKeyInt;
+
+ rv = GetTarget(windowResource, kNC_KeyIndex, true,
+ getter_AddRefs(newKeyNode));
+ if (NS_SUCCEEDED(rv) && (rv != NS_RDF_NO_VALUE))
+ newKeyInt = do_QueryInterface(newKeyNode);
+
+ // changing from one key index to another
+ if (oldKeyInt && newKeyInt)
+ Change(windowResource, kNC_KeyIndex, oldKeyInt, newKeyInt);
+ // creating a new keyindex - probably window going
+ // from (none) to "9"
+ else if (newKeyInt)
+ Assert(windowResource, kNC_KeyIndex, newKeyInt, true);
+
+ // somehow inserting a window above this one,
+ // "9" to (none)
+ else if (oldKeyInt)
+ Unassert(windowResource, kNC_KeyIndex, oldKeyInt);
+
+ }
+ return NS_OK;
+}
+
+// nsIWindowDataSource implementation
+
+NS_IMETHODIMP
+nsWindowDataSource::GetWindowForResource(const char *aResourceString,
+ nsIDOMWindow** aResult)
+{
+ if (NS_WARN_IF(!aResourceString)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIRDFResource> windowResource;
+ gRDFService->GetResource(nsDependentCString(aResourceString),
+ getter_AddRefs(windowResource));
+
+ // now reverse-lookup in the hashtable
+ for (auto iter = mWindowResources.Iter(); !iter.Done(); iter.Next()) {
+ nsIXULWindow* window = iter.Key();
+ nsIRDFResource* resource = iter.UserData();
+
+ if (resource == windowResource) {
+ // This sucks, we have to jump through docshell to go from
+ // nsIXULWindow -> nsIDOMWindow.
+ nsCOMPtr<nsIDocShell> docShell;
+ window->GetDocShell(getter_AddRefs(docShell));
+
+ if (docShell) {
+ nsCOMPtr<nsIDOMWindow> result = do_GetInterface(docShell);
+
+ *aResult = result;
+ NS_IF_ADDREF(*aResult);
+ }
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+// nsIRDFDataSource implementation
+// mostly, we just forward to mInner, except:
+// GetURI() - need to return "rdf:window-mediator"
+// GetTarget() - need to handle kNC_KeyIndex
+
+
+NS_IMETHODIMP nsWindowDataSource::GetURI(char * *aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ *aURI = ToNewCString(NS_LITERAL_CSTRING("rdf:window-mediator"));
+
+ if (!*aURI)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetTarget(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsIRDFNode **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // add extra nullptr checking for top-crash bug # 146466
+ if (!gRDFService) return NS_RDF_NO_VALUE;
+ if (!mInner) return NS_RDF_NO_VALUE;
+ if (!mContainer) return NS_RDF_NO_VALUE;
+ // special case kNC_KeyIndex before we forward to mInner
+ if (aProperty == kNC_KeyIndex) {
+
+ int32_t theIndex = 0;
+ nsresult rv = mContainer->IndexOf(aSource, &theIndex);
+ if (NS_FAILED(rv)) return rv;
+
+ // only allow the range of 1 to 9 for single key access
+ if (theIndex < 1 || theIndex > 9) return(NS_RDF_NO_VALUE);
+
+ nsCOMPtr<nsIRDFInt> indexInt;
+ rv = gRDFService->GetIntLiteral(theIndex, getter_AddRefs(indexInt));
+ if (NS_FAILED(rv)) return(rv);
+ if (!indexInt) return(NS_ERROR_FAILURE);
+
+ indexInt.forget(_retval);
+ return NS_OK;
+ }
+
+ return mInner->GetTarget(aSource, aProperty, aTruthValue, _retval);
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetSource(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsIRDFResource **_retval)
+{
+ if (mInner)
+ return mInner->GetSource(aProperty, aTarget, aTruthValue, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->GetSources(aProperty, aTarget, aTruthValue, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetTargets(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->GetTargets(aSource, aProperty, aTruthValue, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::Assert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue)
+{
+ if (mInner)
+ return mInner->Assert(aSource, aProperty, aTarget, aTruthValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::Unassert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget)
+{
+ if (mInner)
+ return mInner->Unassert(aSource, aProperty, aTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::Change(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aOldTarget, nsIRDFNode *aNewTarget)
+{
+ if (mInner)
+ return mInner->Change(aSource, aProperty, aOldTarget, aNewTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::Move(nsIRDFResource *aOldSource, nsIRDFResource *aNewSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget)
+{
+ if (mInner)
+ return mInner->Move(aOldSource, aNewSource, aProperty, aTarget);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, bool *_retval)
+{
+ if (mInner)
+ return mInner->HasAssertion(aSource, aProperty, aTarget, aTruthValue, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::AddObserver(nsIRDFObserver *aObserver)
+{
+ if (mInner)
+ return mInner->AddObserver(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::RemoveObserver(nsIRDFObserver *aObserver)
+{
+ if (mInner)
+ return mInner->RemoveObserver(aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::ArcLabelsIn(nsIRDFNode *aNode, nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->ArcLabelsIn(aNode, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::ArcLabelsOut(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->ArcLabelsOut(aSource, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetAllResources(nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->GetAllResources(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::IsCommandEnabled(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments, bool *_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsWindowDataSource::DoCommand(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsWindowDataSource::GetAllCmds(nsIRDFResource *aSource, nsISimpleEnumerator **_retval)
+{
+ if (mInner)
+ return mInner->GetAllCmds(aSource, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *_retval)
+{
+ if (mInner)
+ return mInner->HasArcIn(aNode, aArc, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *_retval)
+{
+ if (mInner)
+ return mInner->HasArcOut(aSource, aArc, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::BeginUpdateBatch()
+{
+ if (mInner)
+ return mInner->BeginUpdateBatch();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindowDataSource::EndUpdateBatch()
+{
+ if (mInner)
+ return mInner->EndUpdateBatch();
+ return NS_OK;
+}
+
+// The module goop
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowDataSource, Init)
+
+NS_DEFINE_NAMED_CID(NS_WINDOWDATASOURCE_CID);
+
+static const mozilla::Module::CIDEntry kWindowDSCIDs[] = {
+ { &kNS_WINDOWDATASOURCE_CID, false, nullptr, nsWindowDataSourceConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWindowDSContracts[] = {
+ { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "window-mediator", &kNS_WINDOWDATASOURCE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kWindowDSCategories[] = {
+ { "app-startup", "Window Data Source", "service," NS_RDF_DATASOURCE_CONTRACTID_PREFIX "window-mediator" },
+ { nullptr }
+};
+
+static const mozilla::Module kWindowDSModule = {
+ mozilla::Module::kVersion,
+ kWindowDSCIDs,
+ kWindowDSContracts,
+ kWindowDSCategories
+};
+
+NSMODULE_DEFN(nsWindowDataSourceModule) = &kWindowDSModule;
diff --git a/components/windowds/src/nsWindowDataSource.h b/components/windowds/src/nsWindowDataSource.h
new file mode 100644
index 000000000..351ff951b
--- /dev/null
+++ b/components/windowds/src/nsWindowDataSource.h
@@ -0,0 +1,59 @@
+/* -*- 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 "nsIRDFDataSource.h"
+#include "nsIWindowMediatorListener.h"
+#include "nsIWindowDataSource.h"
+#include "nsIObserver.h"
+
+#include "nsHashKeys.h"
+#include "nsIRDFService.h"
+#include "nsIRDFContainer.h"
+#include "nsInterfaceHashtable.h"
+#include "nsCycleCollectionParticipant.h"
+
+// {C744CA3D-840B-460a-8D70-7CE63C51C958}
+#define NS_WINDOWDATASOURCE_CID \
+{ 0xc744ca3d, 0x840b, 0x460a, \
+ { 0x8d, 0x70, 0x7c, 0xe6, 0x3c, 0x51, 0xc9, 0x58 } }
+
+
+class nsWindowDataSource final : public nsIRDFDataSource,
+ public nsIObserver,
+ public nsIWindowMediatorListener,
+ public nsIWindowDataSource
+{
+ public:
+ nsWindowDataSource() { }
+
+ nsresult Init();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsWindowDataSource,
+ nsIRDFDataSource)
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIWINDOWMEDIATORLISTENER
+ NS_DECL_NSIWINDOWDATASOURCE
+ NS_DECL_NSIRDFDATASOURCE
+
+ protected:
+ virtual ~nsWindowDataSource();
+
+ private:
+
+ // mapping of window -> RDF resource
+ nsInterfaceHashtable<nsPtrHashKey<nsIXULWindow>, nsIRDFResource> mWindowResources;
+
+ static uint32_t windowCount;
+ static uint32_t gRefCnt;
+
+ nsCOMPtr<nsIRDFDataSource> mInner;
+ nsCOMPtr<nsIRDFContainer> mContainer;
+
+ static nsIRDFResource* kNC_Name;
+ static nsIRDFResource* kNC_KeyIndex;
+ static nsIRDFResource* kNC_WindowRoot;
+ static nsIRDFService* gRDFService;
+};
diff --git a/components/windowwatcher/moz.build b/components/windowwatcher/moz.build
new file mode 100644
index 000000000..096f39cbd
--- /dev/null
+++ b/components/windowwatcher/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+XPIDL_SOURCES += [
+ 'public/nsIDialogParamBlock.idl',
+ 'public/nsIPromptFactory.idl',
+ 'public/nsIPromptService.idl',
+ 'public/nsIPromptService2.idl',
+ 'public/nsIWindowWatcher.idl',
+ 'public/nsPIPromptService.idl',
+ 'public/nsPIWindowWatcher.idl',
+]
+
+EXPORTS += [
+ 'src/nsPromptUtils.h',
+ 'src/nsWindowWatcher.h',
+]
+
+SOURCES += [
+ 'src/nsAutoWindowStateHelper.cpp',
+ 'src/nsDialogParamBlock.cpp',
+ 'src/nsWindowWatcher.cpp',
+]
+
+# For nsJSUtils
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/system/docshell/base',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+XPIDL_MODULE = 'windowwatcher'
+FINAL_LIBRARY = 'xul' \ No newline at end of file
diff --git a/components/windowwatcher/public/nsIDialogParamBlock.idl b/components/windowwatcher/public/nsIDialogParamBlock.idl
new file mode 100644
index 000000000..d917776ec
--- /dev/null
+++ b/components/windowwatcher/public/nsIDialogParamBlock.idl
@@ -0,0 +1,42 @@
+/* -*- 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 "nsISupports.idl"
+interface nsIMutableArray;
+
+/**
+ * An interface to pass strings, integers and nsISupports to a dialog
+ */
+
+[scriptable, uuid(f76c0901-437a-11d3-b7a0-e35db351b4bc)]
+interface nsIDialogParamBlock: nsISupports {
+
+ /** Get or set an integer to pass.
+ * Index must be in the range 0..7
+ */
+ int32_t GetInt( in int32_t inIndex );
+ void SetInt( in int32_t inIndex, in int32_t inInt );
+
+ /** Set the maximum number of strings to pass. Default is 16.
+ * Use before setting any string (If you want to change it from the default).
+ */
+ void SetNumberStrings( in int32_t inNumStrings );
+
+ /** Get or set an string to pass.
+ * Index starts at 0
+ */
+ wstring GetString( in int32_t inIndex );
+ void SetString( in int32_t inIndex, in wstring inString);
+
+ /**
+ * A place where you can store an nsIMutableArray to pass nsISupports
+ */
+ attribute nsIMutableArray objects;
+};
+
+%{C++
+#define NS_DIALOGPARAMBLOCK_CONTRACTID "@mozilla.org/embedcomp/dialogparam;1"
+%}
+
diff --git a/components/windowwatcher/public/nsIPromptFactory.idl b/components/windowwatcher/public/nsIPromptFactory.idl
new file mode 100644
index 000000000..b16db3d49
--- /dev/null
+++ b/components/windowwatcher/public/nsIPromptFactory.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+/**
+ * This interface allows creating various prompts that have a specific parent.
+ */
+[scriptable, uuid(2803541c-c96a-4ff1-bd7c-9cb566d46aeb)]
+interface nsIPromptFactory : nsISupports
+{
+ /**
+ * Returns an object implementing the specified interface that creates
+ * prompts parented to aParent.
+ */
+ void getPrompt(in mozIDOMWindowProxy aParent, in nsIIDRef iid,
+ [iid_is(iid),retval] out nsQIResult result);
+};
+
diff --git a/components/windowwatcher/public/nsIPromptService.idl b/components/windowwatcher/public/nsIPromptService.idl
new file mode 100644
index 000000000..24c75e3a9
--- /dev/null
+++ b/components/windowwatcher/public/nsIPromptService.idl
@@ -0,0 +1,346 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+/**
+ * This is the interface to the embeddable prompt service; the service that
+ * implements nsIPrompt. Its interface is designed to be just nsIPrompt, each
+ * method modified to take a parent window parameter.
+ *
+ * Accesskeys can be attached to buttons and checkboxes by inserting an &
+ * before the accesskey character in the checkbox message or button title. For
+ * a real &, use && instead. (A "button title" generally refers to the text
+ * label of a button.)
+ *
+ * One note: in all cases, the parent window parameter can be null. However,
+ * these windows are all intended to have parents. So when no parent is
+ * specified, the implementation should try hard to find a suitable foster
+ * parent.
+ *
+ * Implementations are free to choose how they present the various button
+ * types. For example, while prompts that give the user a choice between OK
+ * and Cancel are required to return a boolean value indicating whether or not
+ * the user accepted the prompt (pressed OK) or rejected the prompt (pressed
+ * Cancel), the implementation of this interface could very well speak the
+ * prompt to the user instead of rendering any visual user-interface. The
+ * standard button types are merely idioms used to convey the nature of the
+ * choice the user is to make.
+ *
+ * Because implementations of this interface may loosely interpret the various
+ * button types, it is advised that text messages passed to these prompts do
+ * not refer to the button types by name. For example, it is inadvisable to
+ * tell the user to "Press OK to proceed." Instead, such a prompt might be
+ * rewritten to ask the user: "Would you like to proceed?"
+ */
+[scriptable, uuid(404ebfa2-d8f4-4c94-8416-e65a55f9df5a)]
+interface nsIPromptService : nsISupports
+{
+ /**
+ * Puts up an alert dialog with an OK button.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ */
+ void alert(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText);
+
+ /**
+ * Puts up an alert dialog with an OK button and a labeled checkbox.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aCheckMsg
+ * Text to appear with the checkbox.
+ * @param aCheckState
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ */
+ void alertCheck(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+
+ /**
+ * Puts up a dialog with OK and Cancel buttons.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ *
+ * @return true for OK, false for Cancel
+ */
+ boolean confirm(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText);
+
+ /**
+ * Puts up a dialog with OK and Cancel buttons and a labeled checkbox.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aCheckMsg
+ * Text to appear with the checkbox.
+ * @param aCheckState
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ *
+ * @return true for OK, false for Cancel
+ */
+ boolean confirmCheck(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+
+ /**
+ * Button Flags
+ *
+ * The following flags are combined to form the aButtonFlags parameter passed
+ * to confirmEx. See confirmEx for more information on how the flags may be
+ * combined.
+ */
+
+ /**
+ * Button Position Flags
+ */
+ const unsigned long BUTTON_POS_0 = 1;
+ const unsigned long BUTTON_POS_1 = 1 << 8;
+ const unsigned long BUTTON_POS_2 = 1 << 16;
+
+ /**
+ * Button Title Flags (used to set the labels of buttons in the prompt)
+ */
+ const unsigned long BUTTON_TITLE_OK = 1;
+ const unsigned long BUTTON_TITLE_CANCEL = 2;
+ const unsigned long BUTTON_TITLE_YES = 3;
+ const unsigned long BUTTON_TITLE_NO = 4;
+ const unsigned long BUTTON_TITLE_SAVE = 5;
+ const unsigned long BUTTON_TITLE_DONT_SAVE = 6;
+ const unsigned long BUTTON_TITLE_REVERT = 7;
+ const unsigned long BUTTON_TITLE_IS_STRING = 127;
+
+ /**
+ * Button Default Flags (used to select which button is the default one)
+ */
+ const unsigned long BUTTON_POS_0_DEFAULT = 0;
+ const unsigned long BUTTON_POS_1_DEFAULT = 1 << 24;
+ const unsigned long BUTTON_POS_2_DEFAULT = 1 << 25;
+
+ /**
+ * Causes the buttons to be initially disabled. They are enabled after a
+ * timeout expires. The implementation may interpret this loosely as the
+ * intent is to ensure that the user does not click through a security dialog
+ * too quickly. Strictly speaking, the implementation could choose to ignore
+ * this flag.
+ */
+ const unsigned long BUTTON_DELAY_ENABLE = 1 << 26;
+
+ /**
+ * Selects the standard set of OK/Cancel buttons.
+ */
+ const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) +
+ (BUTTON_TITLE_CANCEL * BUTTON_POS_1);
+
+ /**
+ * Selects the standard set of Yes/No buttons.
+ */
+ const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) +
+ (BUTTON_TITLE_NO * BUTTON_POS_1);
+
+
+ /**
+ * Puts up a dialog with up to 3 buttons and an optional, labeled checkbox.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aButtonFlags
+ * A combination of Button Flags.
+ * @param aButton0Title
+ * Used when button 0 uses TITLE_IS_STRING
+ * @param aButton1Title
+ * Used when button 1 uses TITLE_IS_STRING
+ * @param aButton2Title
+ * Used when button 2 uses TITLE_IS_STRING
+ * @param aCheckMsg
+ * Text to appear with the checkbox. Null if no checkbox.
+ * @param aCheckState
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ *
+ * @return index of the button pressed.
+ *
+ * Buttons are numbered 0 - 2. The implementation can decide whether the
+ * sequence goes from right to left or left to right. Button 0 is the
+ * default button unless one of the Button Default Flags is specified.
+ *
+ * A button may use a predefined title, specified by one of the Button Title
+ * Flags values. Each title value can be multiplied by a position value to
+ * assign the title to a particular button. If BUTTON_TITLE_IS_STRING is
+ * used for a button, the string parameter for that button will be used. If
+ * the value for a button position is zero, the button will not be shown.
+ *
+ * In general, aButtonFlags is constructed per the following example:
+ *
+ * aButtonFlags = (BUTTON_POS_0) * (BUTTON_TITLE_AAA) +
+ * (BUTTON_POS_1) * (BUTTON_TITLE_BBB) +
+ * BUTTON_POS_1_DEFAULT;
+ *
+ * where "AAA" and "BBB" correspond to one of the button titles.
+ */
+ int32_t confirmEx(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in unsigned long aButtonFlags,
+ in wstring aButton0Title,
+ in wstring aButton1Title,
+ in wstring aButton2Title,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+
+ /**
+ * Puts up a dialog with an edit field and an optional, labeled checkbox.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aValue
+ * Contains the default value for the dialog field when this method
+ * is called (null value is ok). Upon return, if the user pressed
+ * OK, then this parameter contains a newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ * @param aCheckMsg
+ * Text to appear with the checkbox. If null, check box will not be shown.
+ * @param aCheckState
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ *
+ * @return true for OK, false for Cancel.
+ */
+ boolean prompt(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ inout wstring aValue,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+
+ /**
+ * Puts up a dialog with an edit field, a password field, and an optional,
+ * labeled checkbox.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aUsername
+ * Contains the default value for the username field when this method
+ * is called (null value is ok). Upon return, if the user pressed OK,
+ * then this parameter contains a newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ * @param aPassword
+ * Contains the default value for the password field when this method
+ * is called (null value is ok). Upon return, if the user pressed OK,
+ * then this parameter contains a newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ * @param aCheckMsg
+ * Text to appear with the checkbox. If null, check box will not be shown.
+ * @param aCheckState
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ *
+ * @return true for OK, false for Cancel.
+ */
+ boolean promptUsernameAndPassword(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ inout wstring aUsername,
+ inout wstring aPassword,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+
+ /**
+ * Puts up a dialog with a password field and an optional, labeled checkbox.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aPassword
+ * Contains the default value for the password field when this method
+ * is called (null value is ok). Upon return, if the user pressed OK,
+ * then this parameter contains a newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ * @param aCheckMsg
+ * Text to appear with the checkbox. If null, check box will not be shown.
+ * @param aCheckState
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ *
+ * @return true for OK, false for Cancel.
+ */
+ boolean promptPassword(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ inout wstring aPassword,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+
+ /**
+ * Puts up a dialog box which has a list box of strings from which the user
+ * may make a single selection.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aCount
+ * The length of the aSelectList array parameter.
+ * @param aSelectList
+ * The list of strings to display.
+ * @param aOutSelection
+ * Contains the index of the selected item in the list when this
+ * method returns true.
+ *
+ * @return true for OK, false for Cancel.
+ */
+ boolean select(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in uint32_t aCount,
+ [array, size_is(aCount)] in wstring aSelectList,
+ out long aOutSelection);
+};
diff --git a/components/windowwatcher/public/nsIPromptService2.idl b/components/windowwatcher/public/nsIPromptService2.idl
new file mode 100644
index 000000000..4afa0975b
--- /dev/null
+++ b/components/windowwatcher/public/nsIPromptService2.idl
@@ -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/. */
+
+#include "nsIPromptService.idl"
+
+interface nsIAuthInformation;
+interface nsIAuthPromptCallback;
+interface nsICancelable;
+interface nsIChannel;
+interface mozIDOMWindowProxy;
+
+/**
+ * This is an improved version of nsIPromptService that is less prescriptive
+ * about the resulting user interface.
+ *
+ * @status INCOMPLETE do not freeze before fixing bug 228207
+ */
+[scriptable, uuid(3775ad32-8326-422b-9ff3-87ef1d3f9f0e)]
+interface nsIPromptService2 : nsIPromptService {
+ // NOTE: These functions differ from their nsIAuthPrompt counterparts by
+ // having additional checkbox parameters
+ // checkValue can be null meaning to show no checkbox
+ // checkboxLabel is a wstring so that it can be null from both JS and C++ in
+ // a convenient way
+ //
+ // See nsIAuthPrompt2 for documentation on the semantics of the other
+ // parameters.
+ boolean promptAuth(in mozIDOMWindowProxy aParent,
+ in nsIChannel aChannel,
+ in uint32_t level,
+ in nsIAuthInformation authInfo,
+ in wstring checkboxLabel,
+ inout boolean checkValue);
+
+ nsICancelable asyncPromptAuth(in mozIDOMWindowProxy aParent,
+ in nsIChannel aChannel,
+ in nsIAuthPromptCallback aCallback,
+ in nsISupports aContext,
+ in uint32_t level,
+ in nsIAuthInformation authInfo,
+ in wstring checkboxLabel,
+ inout boolean checkValue);
+};
+
diff --git a/components/windowwatcher/public/nsIWindowWatcher.idl b/components/windowwatcher/public/nsIWindowWatcher.idl
new file mode 100644
index 000000000..deea268c2
--- /dev/null
+++ b/components/windowwatcher/public/nsIWindowWatcher.idl
@@ -0,0 +1,167 @@
+/* -*- 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 "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIObserver;
+interface nsIPrompt;
+interface nsIAuthPrompt;
+interface nsISimpleEnumerator;
+interface nsIWebBrowserChrome;
+interface nsIWindowCreator;
+
+
+
+/**
+ * nsIWindowWatcher is the keeper of Gecko/DOM Windows. It maintains
+ * a list of open top-level windows, and allows some operations on them.
+
+ * Usage notes:
+
+ * This component has an |activeWindow| property. Clients may expect
+ * this property to be always current, so to properly integrate this component
+ * the application will need to keep it current by setting the property
+ * as the active window changes.
+ * This component should not keep a (XPCOM) reference to any windows;
+ * the implementation will claim no ownership. Windows must notify
+ * this component when they are created or destroyed, so only a weak
+ * reference is kept. Note that there is no interface for such notifications
+ * (not a public one, anyway). This is taken care of both in Mozilla and
+ * by common embedding code. Embedding clients need do nothing special
+ * about that requirement.
+ * This component must be initialized at application startup by calling
+ * setWindowCreator.
+ */
+[scriptable, uuid(641fe945-6902-4b3f-87c2-0daef32499b3)]
+interface nsIWindowWatcher : nsISupports {
+
+ /** Create a new window. It will automatically be added to our list
+ (via addWindow()).
+ @param aParent parent window, if any. Null if no parent. If it is
+ impossible to get to an nsIWebBrowserChrome from aParent, this
+ method will effectively act as if aParent were null.
+ @param aURL url to which to open the new window. Must already be
+ escaped, if applicable. can be null.
+ @param aName window name from JS window.open. can be null. If a window
+ with this name already exists, the openWindow call may just load
+ aUrl in it (if aUrl is not null) and return it.
+ @param aFeatures window features from JS window.open. can be null.
+ @param aArguments extra argument(s) to the new window, to be attached
+ as the |arguments| property. An nsIArray will be
+ unwound into multiple arguments (but not recursively!).
+ can be null.
+ @return the new window
+
+ @note This method may examine the JS context stack for purposes of
+ determining the security context to use for the search for a given
+ window named aName.
+ @note This method should try to set the default charset for the new
+ window to the default charset of aParent. This is not guaranteed,
+ however.
+ @note This method may dispatch a "toplevel-window-ready" notification
+ via nsIObserverService if the window did not already exist.
+ */
+ mozIDOMWindowProxy openWindow(in mozIDOMWindowProxy aParent, in string aUrl,
+ in string aName, in string aFeatures,
+ in nsISupports aArguments);
+
+ /** Clients of this service can register themselves to be notified
+ when a window is opened or closed (added to or removed from this
+ service). This method adds an aObserver to the list of objects
+ to be notified.
+ @param aObserver the object to be notified when windows are
+ opened or closed. Its Observe method will be
+ called with the following parameters:
+
+ aObserver::Observe interprets its parameters so:
+ aSubject the window being opened or closed, sent as an nsISupports
+ which can be QIed to an nsIDOMWindow.
+ aTopic a wstring, either "domwindowopened" or "domwindowclosed".
+ someData not used.
+ */
+ void registerNotification(in nsIObserver aObserver);
+
+ /** Clients of this service can register themselves to be notified
+ when a window is opened or closed (added to or removed from this
+ service). This method removes an aObserver from the list of objects
+ to be notified.
+ @param aObserver the observer to be removed.
+ */
+ void unregisterNotification(in nsIObserver aObserver);
+
+ /** Get an iterator for currently open windows in the order they were opened,
+ guaranteeing that each will be visited exactly once.
+ @return an enumerator which will itself return nsISupports objects which
+ can be QIed to an nsIDOMWindow
+ */
+
+ nsISimpleEnumerator getWindowEnumerator();
+
+ /** Return a newly created nsIPrompt implementation.
+ @param aParent the parent window used for posing alerts. can be null.
+ @return a new nsIPrompt object
+ */
+
+ nsIPrompt getNewPrompter(in mozIDOMWindowProxy aParent);
+
+ /** Return a newly created nsIAuthPrompt implementation.
+ @param aParent the parent window used for posing alerts. can be null.
+ @return a new nsIAuthPrompt object
+ */
+
+ nsIAuthPrompt getNewAuthPrompter(in mozIDOMWindowProxy aParent);
+
+ /** Set the window creator callback. It must be filled in by the app.
+ openWindow will use it to create new windows.
+ @param creator the callback. if null, the callback will be cleared
+ and window creation capabilities lost.
+ */
+ void setWindowCreator(in nsIWindowCreator creator);
+
+ /** Returns true if a window creator callback has been set, false otherwise.
+ */
+ boolean hasWindowCreator();
+
+
+ /** Retrieve the chrome window mapped to the given DOM window. Window
+ Watcher keeps a list of all top-level DOM windows currently open,
+ along with their corresponding chrome interfaces. Since DOM Windows
+ lack a (public) means of retrieving their corresponding chrome,
+ this method will do that.
+ @param aWindow the DOM window whose chrome window the caller needs
+ @return the corresponding chrome window
+ */
+ nsIWebBrowserChrome getChromeForWindow(in mozIDOMWindowProxy aWindow);
+
+ /**
+ Retrieve an existing window (or frame).
+ @param aTargetName the window name
+ @param aCurrentWindow a starting point in the window hierarchy to
+ begin the search. If null, each toplevel window
+ will be searched.
+
+ Note: This method will search all open windows for any window or
+ frame with the given window name. Make sure you understand the
+ security implications of this before using this method!
+ */
+ mozIDOMWindowProxy getWindowByName(in AString aTargetName,
+ in mozIDOMWindowProxy aCurrentWindow);
+
+ /** The Watcher serves as a global storage facility for the current active
+ (frontmost non-floating-palette-type) window, storing and returning
+ it on demand. Users must keep this attribute current, including after
+ the topmost window is closed. This attribute obviously can return null
+ if no windows are open, but should otherwise always return a valid
+ window.
+ */
+ attribute mozIDOMWindowProxy activeWindow;
+
+};
+
+%{C++
+#define NS_WINDOWWATCHER_CONTRACTID "@mozilla.org/embedcomp/window-watcher;1"
+%}
diff --git a/components/windowwatcher/public/nsPIPromptService.idl b/components/windowwatcher/public/nsPIPromptService.idl
new file mode 100644
index 000000000..32459b26b
--- /dev/null
+++ b/components/windowwatcher/public/nsPIPromptService.idl
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/* The general dialog posing function within nsPromptService, for
+ private consumption, only. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMWindow;
+interface nsIDialogParamBlock;
+
+[uuid(C60A1955-6CB3-4827-8EF8-4F5C668AF0B3)]
+interface nsPIPromptService : nsISupports
+{
+%{C++
+ // eOpeningSound is obsolete but we need to support it for the compatibility.
+ // The implementers should use eSoundEventId instead.
+ enum {eMsg=0, eCheckboxMsg=1, eIconClass=2, eTitleMessage=3, eEditfield1Msg=4,
+ eEditfield2Msg=5, eEditfield1Value=6, eEditfield2Value=7,
+ eButton0Text=8, eButton1Text=9, eButton2Text=10, eButton3Text=11,
+ eDialogTitle=12, eOpeningSound=13};
+ enum {eButtonPressed=0, eCheckboxState=1, eNumberButtons=2,
+ eNumberEditfields=3, eEditField1Password=4, eDefaultButton=5,
+ eDelayButtonEnable=6, eSoundEventId=7};
+%}
+
+ void doDialog(in nsIDOMWindow aParent, in nsIDialogParamBlock aParamBlock, in string aChromeURL);
+};
+
diff --git a/components/windowwatcher/public/nsPIWindowWatcher.idl b/components/windowwatcher/public/nsPIWindowWatcher.idl
new file mode 100644
index 000000000..47b243364
--- /dev/null
+++ b/components/windowwatcher/public/nsPIWindowWatcher.idl
@@ -0,0 +1,146 @@
+/* -*- 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/. */
+
+/* Private "control" methods on the Window Watcher. These are annoying
+ bookkeeping methods, not part of the public (embedding) interface.
+*/
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIDOMWindow;
+interface nsISimpleEnumerator;
+interface nsIWebBrowserChrome;
+interface nsIDocShellTreeItem;
+interface nsIArray;
+interface nsITabParent;
+interface nsIDocShellLoadInfo;
+
+[uuid(d162f9c4-19d5-4723-931f-f1e51bfa9f68)]
+
+interface nsPIWindowWatcher : nsISupports
+{
+ /** A window has been created. Add it to our list.
+ @param aWindow the window to add
+ @param aChrome the corresponding chrome window. The DOM window
+ and chrome will be mapped together, and the corresponding
+ chrome can be retrieved using the (not private)
+ method getChromeForWindow. If null, any extant mapping
+ will be cleared.
+ */
+ void addWindow(in mozIDOMWindowProxy aWindow,
+ in nsIWebBrowserChrome aChrome);
+
+ /** A window has been closed. Remove it from our list.
+ @param aWindow the window to remove
+ */
+ void removeWindow(in mozIDOMWindowProxy aWindow);
+
+ /** Like the public interface's open(), but can handle openDialog-style
+ arguments and calls which shouldn't result in us navigating the window.
+
+ @param aParent parent window, if any. Null if no parent. If it is
+ impossible to get to an nsIWebBrowserChrome from aParent, this
+ method will effectively act as if aParent were null.
+ @param aURL url to which to open the new window. Must already be
+ escaped, if applicable. can be null.
+ @param aName window name from JS window.open. can be null. If a window
+ with this name already exists, the openWindow call may just load
+ aUrl in it (if aUrl is not null) and return it.
+ @param aFeatures window features from JS window.open. can be null.
+ @param aCalledFromScript true if we were called from script.
+ @param aDialog use dialog defaults (see nsIDOMWindow::openDialog)
+ @param aNavigate true if we should navigate the new window to the
+ specified URL.
+ @param aArgs Window argument
+ @param aIsPopupSpam true if the window is a popup spam window; used for
+ popup blocker internals.
+ @param aForceNoOpener If true, force noopener behavior. This means not
+ looking for existing windows with the given name,
+ not setting an opener on the newly opened window,
+ and returning null from this method.
+ @param aLoadInfo if aNavigate is true, this allows the caller to pass in
+ an nsIDocShellLoadInfo to use for the navigation.
+ Callers can pass in null if they want the windowwatcher
+ to just construct a loadinfo itself. If aNavigate is
+ false, this argument is ignored.
+
+ @return the new window
+
+ @note This method may examine the JS context stack for purposes of
+ determining the security context to use for the search for a given
+ window named aName.
+ @note This method should try to set the default charset for the new
+ window to the default charset of the document in the calling window
+ (which is determined based on the JS stack and the value of
+ aParent). This is not guaranteed, however.
+ */
+ mozIDOMWindowProxy openWindow2(in mozIDOMWindowProxy aParent, in string aUrl,
+ in string aName, in string aFeatures,
+ in boolean aCalledFromScript,
+ in boolean aDialog,
+ in boolean aNavigate,
+ in nsISupports aArgs,
+ in boolean aIsPopupSpam,
+ in boolean aForceNoOpener,
+ in nsIDocShellLoadInfo aLoadInfo);
+
+ /**
+ * Opens a new window using the most recent non-private browser
+ * window as its parent.
+ *
+ * @return the nsITabParent of the initial browser for the newly opened
+ * window.
+ */
+ nsITabParent openWindowWithoutParent();
+
+ /**
+ * Opens a new window so that the window that aOpeningTab belongs to
+ * is set as the parent window. The newly opened window will also
+ * inherit load context information from aOpeningTab.
+ *
+ * @param aOpeningTab
+ * The nsITabParent that is requesting the new window be opened.
+ * @param aFeatures
+ * Window features if called with window.open or similar.
+ * @param aCalledFromJS
+ * True if called via window.open or similar.
+ * @param aOpenerFullZoom
+ * The current zoom multiplier for the opener tab. This is then
+ * applied to the newly opened window.
+ *
+ * @return the nsITabParent of the initial browser for the newly opened
+ * window.
+ */
+ nsITabParent openWindowWithTabParent(in nsITabParent aOpeningTab,
+ in ACString aFeatures,
+ in boolean aCalledFromJS,
+ in float aOpenerFullZoom);
+
+ /**
+ * Find a named docshell tree item amongst all windows registered
+ * with the window watcher. This may be a subframe in some window,
+ * for example.
+ *
+ * @param aName the name of the window. Must not be null.
+ * @param aRequestor the tree item immediately making the request.
+ * We should make sure to not recurse down into its findItemWithName
+ * method.
+ * @param aOriginalRequestor the original treeitem that made the request.
+ * Used for security checks.
+ * @return the tree item with aName as the name, or null if there
+ * isn't one. "Special" names, like _self, _top, etc, will be
+ * treated specially only if aRequestor is null; in that case they
+ * will be resolved relative to the first window the windowwatcher
+ * knows about.
+ * @see findItemWithName methods on nsIDocShellTreeItem and
+ * nsIDocShellTreeOwner
+ */
+ nsIDocShellTreeItem findItemWithName(in AString aName,
+ in nsIDocShellTreeItem aRequestor,
+ in nsIDocShellTreeItem aOriginalRequestor);
+};
+
diff --git a/components/windowwatcher/src/nsAutoWindowStateHelper.cpp b/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
new file mode 100644
index 000000000..e7c66e83a
--- /dev/null
+++ b/components/windowwatcher/src/nsAutoWindowStateHelper.cpp
@@ -0,0 +1,72 @@
+/* -*- 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 "nsAutoWindowStateHelper.h"
+
+#include "mozilla/dom/Event.h"
+#include "nsIDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/****************************************************************
+ ****************** nsAutoWindowStateHelper *********************
+ ****************************************************************/
+
+nsAutoWindowStateHelper::nsAutoWindowStateHelper(nsPIDOMWindowOuter* aWindow)
+ : mWindow(aWindow)
+ , mDefaultEnabled(DispatchEventToChrome("DOMWillOpenModalDialog"))
+{
+ if (mWindow) {
+ mWindow->EnterModalState();
+ }
+}
+
+nsAutoWindowStateHelper::~nsAutoWindowStateHelper()
+{
+ if (mWindow) {
+ mWindow->LeaveModalState();
+ }
+
+ if (mDefaultEnabled) {
+ DispatchEventToChrome("DOMModalDialogClosed");
+ }
+}
+
+bool
+nsAutoWindowStateHelper::DispatchEventToChrome(const char* aEventName)
+{
+ // XXXbz should we skip dispatching the event if the inner changed?
+ // That is, should we store both the inner and the outer?
+ if (!mWindow) {
+ return true;
+ }
+
+ // The functions of nsContentUtils do not provide the required behavior,
+ // so the following is inlined.
+ nsIDocument* doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ return true;
+ }
+
+ ErrorResult rv;
+ RefPtr<Event> event = doc->CreateEvent(NS_LITERAL_STRING("Events"), rv);
+ if (rv.Failed()) {
+ rv.SuppressException();
+ return false;
+ }
+ event->InitEvent(NS_ConvertASCIItoUTF16(aEventName), true, true);
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ nsCOMPtr<EventTarget> target = do_QueryInterface(mWindow);
+ bool defaultActionEnabled;
+ target->DispatchEvent(event, &defaultActionEnabled);
+ return defaultActionEnabled;
+}
diff --git a/components/windowwatcher/src/nsAutoWindowStateHelper.h b/components/windowwatcher/src/nsAutoWindowStateHelper.h
new file mode 100644
index 000000000..c23c637d7
--- /dev/null
+++ b/components/windowwatcher/src/nsAutoWindowStateHelper.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+#ifndef __nsAutoWindowStateHelper_h
+#define __nsAutoWindowStateHelper_h
+
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+
+/**
+ * Helper class for dealing with notifications around opening modal
+ * windows.
+ */
+
+class nsPIDOMWindowOuter;
+
+class nsAutoWindowStateHelper
+{
+public:
+ explicit nsAutoWindowStateHelper(nsPIDOMWindowOuter* aWindow);
+ ~nsAutoWindowStateHelper();
+
+ bool DefaultEnabled() { return mDefaultEnabled; }
+
+protected:
+ bool DispatchEventToChrome(const char* aEventName);
+
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ bool mDefaultEnabled;
+};
+
+#endif
diff --git a/components/windowwatcher/src/nsDialogParamBlock.cpp b/components/windowwatcher/src/nsDialogParamBlock.cpp
new file mode 100644
index 000000000..573082db1
--- /dev/null
+++ b/components/windowwatcher/src/nsDialogParamBlock.cpp
@@ -0,0 +1,100 @@
+/* -*- 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 "nsDialogParamBlock.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+
+NS_IMPL_ISUPPORTS(nsDialogParamBlock, nsIDialogParamBlock)
+
+nsDialogParamBlock::nsDialogParamBlock()
+ : mNumStrings(0)
+ , mString(nullptr)
+{
+ for (int32_t i = 0; i < kNumInts; i++) {
+ mInt[i] = 0;
+ }
+}
+
+nsDialogParamBlock::~nsDialogParamBlock()
+{
+ delete[] mString;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::SetNumberStrings(int32_t aNumStrings)
+{
+ if (mString) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ mString = new nsString[aNumStrings];
+ if (!mString) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mNumStrings = aNumStrings;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::GetInt(int32_t aIndex, int32_t* aResult)
+{
+ nsresult rv = InBounds(aIndex, kNumInts);
+ if (rv == NS_OK) {
+ *aResult = mInt[aIndex];
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::SetInt(int32_t aIndex, int32_t aInt)
+{
+ nsresult rv = InBounds(aIndex, kNumInts);
+ if (rv == NS_OK) {
+ mInt[aIndex] = aInt;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::GetString(int32_t aIndex, char16_t** aResult)
+{
+ if (mNumStrings == 0) {
+ SetNumberStrings(kNumStrings);
+ }
+ nsresult rv = InBounds(aIndex, mNumStrings);
+ if (rv == NS_OK) {
+ *aResult = ToNewUnicode(mString[aIndex]);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::SetString(int32_t aIndex, const char16_t* aString)
+{
+ if (mNumStrings == 0) {
+ SetNumberStrings(kNumStrings);
+ }
+ nsresult rv = InBounds(aIndex, mNumStrings);
+ if (rv == NS_OK) {
+ mString[aIndex] = aString;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::GetObjects(nsIMutableArray** aObjects)
+{
+ NS_ENSURE_ARG_POINTER(aObjects);
+ NS_IF_ADDREF(*aObjects = mObjects);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::SetObjects(nsIMutableArray* aObjects)
+{
+ mObjects = aObjects;
+ return NS_OK;
+}
diff --git a/components/windowwatcher/src/nsDialogParamBlock.h b/components/windowwatcher/src/nsDialogParamBlock.h
new file mode 100644
index 000000000..a34ba251f
--- /dev/null
+++ b/components/windowwatcher/src/nsDialogParamBlock.h
@@ -0,0 +1,44 @@
+/* -*- 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/. */
+
+#ifndef __nsDialogParamBlock_h
+#define __nsDialogParamBlock_h
+
+#include "nsIDialogParamBlock.h"
+#include "nsIMutableArray.h"
+#include "nsCOMPtr.h"
+
+// {4E4AAE11-8901-46cc-8217-DAD7C5415873}
+#define NS_DIALOGPARAMBLOCK_CID \
+ {0x4e4aae11, 0x8901, 0x46cc, {0x82, 0x17, 0xda, 0xd7, 0xc5, 0x41, 0x58, 0x73}}
+
+class nsString;
+
+class nsDialogParamBlock : public nsIDialogParamBlock
+{
+public:
+ nsDialogParamBlock();
+
+ NS_DECL_NSIDIALOGPARAMBLOCK
+ NS_DECL_ISUPPORTS
+
+protected:
+ virtual ~nsDialogParamBlock();
+
+private:
+ enum { kNumInts = 8, kNumStrings = 16 };
+
+ nsresult InBounds(int32_t aIndex, int32_t aMax)
+ {
+ return aIndex >= 0 && aIndex < aMax ? NS_OK : NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ int32_t mInt[kNumInts];
+ int32_t mNumStrings;
+ nsString* mString;
+ nsCOMPtr<nsIMutableArray> mObjects;
+};
+
+#endif
diff --git a/components/windowwatcher/src/nsPromptUtils.h b/components/windowwatcher/src/nsPromptUtils.h
new file mode 100644
index 000000000..80589b9cb
--- /dev/null
+++ b/components/windowwatcher/src/nsPromptUtils.h
@@ -0,0 +1,140 @@
+/* -*- 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/. */
+
+#ifndef NSPROMPTUTILS_H_
+#define NSPROMPTUTILS_H_
+
+#include "nsIHttpChannel.h"
+
+/**
+ * @file
+ * This file defines some helper functions that simplify interaction
+ * with authentication prompts.
+ */
+
+/**
+ * Given a username (possibly in DOMAIN\user form) and password, parses the
+ * domain out of the username if necessary and sets domain, username and
+ * password on the auth information object.
+ */
+inline void
+NS_SetAuthInfo(nsIAuthInformation* aAuthInfo, const nsString& aUser,
+ const nsString& aPassword)
+{
+ uint32_t flags;
+ aAuthInfo->GetFlags(&flags);
+ if (flags & nsIAuthInformation::NEED_DOMAIN) {
+ // Domain is separated from username by a backslash
+ int32_t idx = aUser.FindChar(char16_t('\\'));
+ if (idx == kNotFound) {
+ aAuthInfo->SetUsername(aUser);
+ } else {
+ aAuthInfo->SetDomain(Substring(aUser, 0, idx));
+ aAuthInfo->SetUsername(Substring(aUser, idx + 1));
+ }
+ } else {
+ aAuthInfo->SetUsername(aUser);
+ }
+ aAuthInfo->SetPassword(aPassword);
+}
+
+/**
+ * Gets the host and port from a channel and authentication info. This is the
+ * "logical" host and port for this authentication, i.e. for a proxy
+ * authentication it refers to the proxy, while for a host authentication it
+ * is the actual host.
+ *
+ * @param machineProcessing
+ * When this parameter is true, the host will be returned in ASCII
+ * (instead of UTF-8; this is relevant when IDN is used). In addition,
+ * the port will be returned as the real port even when it was not
+ * explicitly specified (when false, the port will be returned as -1 in
+ * this case)
+ */
+inline void
+NS_GetAuthHostPort(nsIChannel* aChannel, nsIAuthInformation* aAuthInfo,
+ bool aMachineProcessing, nsCString& aHost, int32_t* aPort)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Have to distinguish proxy auth and host auth here...
+ uint32_t flags;
+ aAuthInfo->GetFlags(&flags);
+ if (flags & nsIAuthInformation::AUTH_PROXY) {
+ nsCOMPtr<nsIProxiedChannel> proxied(do_QueryInterface(aChannel));
+ NS_ASSERTION(proxied, "proxy auth needs nsIProxiedChannel");
+
+ nsCOMPtr<nsIProxyInfo> info;
+ proxied->GetProxyInfo(getter_AddRefs(info));
+ NS_ASSERTION(info, "proxy auth needs nsIProxyInfo");
+
+ nsAutoCString idnhost;
+ info->GetHost(idnhost);
+ info->GetPort(aPort);
+
+ if (aMachineProcessing) {
+ nsCOMPtr<nsIIDNService> idnService =
+ do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (idnService) {
+ idnService->ConvertUTF8toACE(idnhost, aHost);
+ } else {
+ // Not much we can do here...
+ aHost = idnhost;
+ }
+ } else {
+ aHost = idnhost;
+ }
+ } else {
+ if (aMachineProcessing) {
+ uri->GetAsciiHost(aHost);
+ *aPort = NS_GetRealPort(uri);
+ } else {
+ uri->GetHost(aHost);
+ uri->GetPort(aPort);
+ }
+ }
+}
+
+/**
+ * Creates the key for looking up passwords in the password manager. This
+ * function uses the same format that Gecko functions have always used, thus
+ * ensuring backwards compatibility.
+ */
+inline void
+NS_GetAuthKey(nsIChannel* aChannel, nsIAuthInformation* aAuthInfo,
+ nsCString& aKey)
+{
+ // HTTP does this differently from other protocols
+ nsCOMPtr<nsIHttpChannel> http(do_QueryInterface(aChannel));
+ if (!http) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ uri->GetPrePath(aKey);
+ return;
+ }
+
+ // NOTE: For backwards-compatibility reasons, this must be the ASCII host.
+ nsCString host;
+ int32_t port = -1;
+
+ NS_GetAuthHostPort(aChannel, aAuthInfo, true, host, &port);
+
+ nsAutoString realm;
+ aAuthInfo->GetRealm(realm);
+
+ // Now assemble the key: host:port (realm)
+ aKey.Append(host);
+ aKey.Append(':');
+ aKey.AppendInt(port);
+ aKey.AppendLiteral(" (");
+ AppendUTF16toUTF8(realm, aKey);
+ aKey.Append(')');
+}
+
+#endif
diff --git a/components/windowwatcher/src/nsWindowWatcher.cpp b/components/windowwatcher/src/nsWindowWatcher.cpp
new file mode 100644
index 000000000..bd931233b
--- /dev/null
+++ b/components/windowwatcher/src/nsWindowWatcher.cpp
@@ -0,0 +1,2576 @@
+/* -*- 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/. */
+
+//#define USEWEAKREFS // (haven't quite figured that out yet)
+
+#include "nsWindowWatcher.h"
+#include "nsAutoWindowStateHelper.h"
+
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsJSUtils.h"
+#include "plstr.h"
+
+#include "nsDocShell.h"
+#include "nsGlobalWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsIBrowserDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIDocumentLoader.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMChromeWindow.h"
+#include "nsIDOMModalContentWindow.h"
+#include "nsIPrompt.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "nsIScriptContext.h"
+#include "nsIObserverService.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsXPCOM.h"
+#include "nsIURI.h"
+#include "nsIWebBrowser.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIWebNavigation.h"
+#include "nsIWindowCreator.h"
+#include "nsIWindowCreator2.h"
+#include "nsIXPConnect.h"
+#include "nsIXULRuntime.h"
+#include "nsPIDOMWindow.h"
+#include "nsIContentViewer.h"
+#include "nsIWindowProvider.h"
+#include "nsIMutableArray.h"
+#include "nsIDOMStorageManager.h"
+#include "nsIWidget.h"
+#include "nsFocusManager.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsContentUtils.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsSandboxFlags.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/DOMStorage.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/TabGroup.h"
+#include "nsIXULWindow.h"
+#include "nsIXULBrowserWindow.h"
+#include "nsGlobalWindow.h"
+
+#ifdef USEWEAKREFS
+#include "nsIWeakReference.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/****************************************************************
+ ******************** nsWatcherWindowEntry **********************
+ ****************************************************************/
+
+class nsWindowWatcher;
+
+struct nsWatcherWindowEntry
+{
+
+ nsWatcherWindowEntry(mozIDOMWindowProxy* aWindow, nsIWebBrowserChrome* aChrome)
+ : mChrome(nullptr)
+ {
+#ifdef USEWEAKREFS
+ mWindow = do_GetWeakReference(aWindow);
+#else
+ mWindow = aWindow;
+#endif
+ nsCOMPtr<nsISupportsWeakReference> supportsweak(do_QueryInterface(aChrome));
+ if (supportsweak) {
+ supportsweak->GetWeakReference(getter_AddRefs(mChromeWeak));
+ } else {
+ mChrome = aChrome;
+ mChromeWeak = nullptr;
+ }
+ ReferenceSelf();
+ }
+ ~nsWatcherWindowEntry() {}
+
+ void InsertAfter(nsWatcherWindowEntry* aOlder);
+ void Unlink();
+ void ReferenceSelf();
+
+#ifdef USEWEAKREFS
+ nsCOMPtr<nsIWeakReference> mWindow;
+#else // still not an owning ref
+ mozIDOMWindowProxy* mWindow;
+#endif
+ nsIWebBrowserChrome* mChrome;
+ nsWeakPtr mChromeWeak;
+ // each struct is in a circular, doubly-linked list
+ nsWatcherWindowEntry* mYounger; // next younger in sequence
+ nsWatcherWindowEntry* mOlder;
+};
+
+void
+nsWatcherWindowEntry::InsertAfter(nsWatcherWindowEntry* aOlder)
+{
+ if (aOlder) {
+ mOlder = aOlder;
+ mYounger = aOlder->mYounger;
+ mOlder->mYounger = this;
+ if (mOlder->mOlder == mOlder) {
+ mOlder->mOlder = this;
+ }
+ mYounger->mOlder = this;
+ if (mYounger->mYounger == mYounger) {
+ mYounger->mYounger = this;
+ }
+ }
+}
+
+void
+nsWatcherWindowEntry::Unlink()
+{
+ mOlder->mYounger = mYounger;
+ mYounger->mOlder = mOlder;
+ ReferenceSelf();
+}
+
+void
+nsWatcherWindowEntry::ReferenceSelf()
+{
+
+ mYounger = this;
+ mOlder = this;
+}
+
+/****************************************************************
+ ****************** nsWatcherWindowEnumerator *******************
+ ****************************************************************/
+
+class nsWatcherWindowEnumerator : public nsISimpleEnumerator
+{
+
+public:
+ explicit nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher);
+ NS_IMETHOD HasMoreElements(bool* aResult) override;
+ NS_IMETHOD GetNext(nsISupports** aResult) override;
+
+ NS_DECL_ISUPPORTS
+
+protected:
+ virtual ~nsWatcherWindowEnumerator();
+
+private:
+ friend class nsWindowWatcher;
+
+ nsWatcherWindowEntry* FindNext();
+ void WindowRemoved(nsWatcherWindowEntry* aInfo);
+
+ nsWindowWatcher* mWindowWatcher;
+ nsWatcherWindowEntry* mCurrentPosition;
+};
+
+NS_IMPL_ADDREF(nsWatcherWindowEnumerator)
+NS_IMPL_RELEASE(nsWatcherWindowEnumerator)
+NS_IMPL_QUERY_INTERFACE(nsWatcherWindowEnumerator, nsISimpleEnumerator)
+
+nsWatcherWindowEnumerator::nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher)
+ : mWindowWatcher(aWatcher)
+ , mCurrentPosition(aWatcher->mOldestWindow)
+{
+ mWindowWatcher->AddEnumerator(this);
+ mWindowWatcher->AddRef();
+}
+
+nsWatcherWindowEnumerator::~nsWatcherWindowEnumerator()
+{
+ mWindowWatcher->RemoveEnumerator(this);
+ mWindowWatcher->Release();
+}
+
+NS_IMETHODIMP
+nsWatcherWindowEnumerator::HasMoreElements(bool* aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = !!mCurrentPosition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWatcherWindowEnumerator::GetNext(nsISupports** aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = nullptr;
+
+#ifdef USEWEAKREFS
+ while (mCurrentPosition) {
+ CallQueryReferent(mCurrentPosition->mWindow, aResult);
+ if (*aResult) {
+ mCurrentPosition = FindNext();
+ break;
+ } else { // window is gone!
+ mWindowWatcher->RemoveWindow(mCurrentPosition);
+ }
+ }
+ NS_IF_ADDREF(*aResult);
+#else
+ if (mCurrentPosition) {
+ CallQueryInterface(mCurrentPosition->mWindow, aResult);
+ mCurrentPosition = FindNext();
+ }
+#endif
+ return NS_OK;
+}
+
+nsWatcherWindowEntry*
+nsWatcherWindowEnumerator::FindNext()
+{
+ nsWatcherWindowEntry* info;
+
+ if (!mCurrentPosition) {
+ return 0;
+ }
+
+ info = mCurrentPosition->mYounger;
+ return info == mWindowWatcher->mOldestWindow ? 0 : info;
+}
+
+// if a window is being removed adjust the iterator's current position
+void
+nsWatcherWindowEnumerator::WindowRemoved(nsWatcherWindowEntry* aInfo)
+{
+
+ if (mCurrentPosition == aInfo) {
+ mCurrentPosition =
+ mCurrentPosition != aInfo->mYounger ? aInfo->mYounger : 0;
+ }
+}
+
+/****************************************************************
+ *********************** nsWindowWatcher ************************
+ ****************************************************************/
+
+NS_IMPL_ADDREF(nsWindowWatcher)
+NS_IMPL_RELEASE(nsWindowWatcher)
+NS_IMPL_QUERY_INTERFACE(nsWindowWatcher,
+ nsIWindowWatcher,
+ nsIPromptFactory,
+ nsPIWindowWatcher)
+
+nsWindowWatcher::nsWindowWatcher()
+ : mEnumeratorList()
+ , mOldestWindow(0)
+ , mListLock("nsWindowWatcher.mListLock")
+{
+}
+
+nsWindowWatcher::~nsWindowWatcher()
+{
+ // delete data
+ while (mOldestWindow) {
+ RemoveWindow(mOldestWindow);
+ }
+}
+
+nsresult
+nsWindowWatcher::Init()
+{
+ return NS_OK;
+}
+
+/**
+ * Convert aArguments into either an nsIArray or nullptr.
+ *
+ * - If aArguments is nullptr, return nullptr.
+ * - If aArguments is an nsArray, return nullptr if it's empty, or otherwise
+ * return the array.
+ * - If aArguments is an nsIArray, return nullptr if it's empty, or
+ * otherwise just return the array.
+ * - Otherwise, return an nsIArray with one element: aArguments.
+ */
+static already_AddRefed<nsIArray>
+ConvertArgsToArray(nsISupports* aArguments)
+{
+ if (!aArguments) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIArray> array = do_QueryInterface(aArguments);
+ if (array) {
+ uint32_t argc = 0;
+ array->GetLength(&argc);
+ if (argc == 0) {
+ return nullptr;
+ }
+
+ return array.forget();
+ }
+
+ nsCOMPtr<nsIMutableArray> singletonArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+ NS_ENSURE_TRUE(singletonArray, nullptr);
+
+ nsresult rv = singletonArray->AppendElement(aArguments, /* aWeak = */ false);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return singletonArray.forget();
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::OpenWindow(mozIDOMWindowProxy* aParent,
+ const char* aUrl,
+ const char* aName,
+ const char* aFeatures,
+ nsISupports* aArguments,
+ mozIDOMWindowProxy** aResult)
+{
+ nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments);
+
+ uint32_t argc = 0;
+ if (argv) {
+ argv->GetLength(&argc);
+ }
+ bool dialog = (argc != 0);
+
+ return OpenWindowInternal(aParent, aUrl, aName, aFeatures,
+ /* calledFromJS = */ false, dialog,
+ /* navigate = */ true, argv,
+ /* aIsPopupSpam = */ false,
+ /* aForceNoOpener = */ false,
+ /* aLoadInfo = */ nullptr,
+ aResult);
+}
+
+struct SizeSpec
+{
+ SizeSpec()
+ : mLeft(0)
+ , mTop(0)
+ , mOuterWidth(0)
+ , mOuterHeight(0)
+ , mInnerWidth(0)
+ , mInnerHeight(0)
+ , mLeftSpecified(false)
+ , mTopSpecified(false)
+ , mOuterWidthSpecified(false)
+ , mOuterHeightSpecified(false)
+ , mInnerWidthSpecified(false)
+ , mInnerHeightSpecified(false)
+ , mUseDefaultWidth(false)
+ , mUseDefaultHeight(false)
+ {
+ }
+
+ int32_t mLeft;
+ int32_t mTop;
+ int32_t mOuterWidth; // Total window width
+ int32_t mOuterHeight; // Total window height
+ int32_t mInnerWidth; // Content area width
+ int32_t mInnerHeight; // Content area height
+
+ bool mLeftSpecified;
+ bool mTopSpecified;
+ bool mOuterWidthSpecified;
+ bool mOuterHeightSpecified;
+ bool mInnerWidthSpecified;
+ bool mInnerHeightSpecified;
+
+ // If these booleans are true, don't look at the corresponding width values
+ // even if they're specified -- they'll be bogus
+ bool mUseDefaultWidth;
+ bool mUseDefaultHeight;
+
+ bool PositionSpecified() const
+ {
+ return mLeftSpecified || mTopSpecified;
+ }
+
+ bool SizeSpecified() const
+ {
+ return mOuterWidthSpecified || mOuterHeightSpecified ||
+ mInnerWidthSpecified || mInnerHeightSpecified;
+ }
+};
+
+NS_IMETHODIMP
+nsWindowWatcher::OpenWindow2(mozIDOMWindowProxy* aParent,
+ const char* aUrl,
+ const char* aName,
+ const char* aFeatures,
+ bool aCalledFromScript,
+ bool aDialog,
+ bool aNavigate,
+ nsISupports* aArguments,
+ bool aIsPopupSpam,
+ bool aForceNoOpener,
+ nsIDocShellLoadInfo* aLoadInfo,
+ mozIDOMWindowProxy** aResult)
+{
+ nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments);
+
+ uint32_t argc = 0;
+ if (argv) {
+ argv->GetLength(&argc);
+ }
+
+ // This is extremely messed up, but this behavior is necessary because
+ // callers lie about whether they're a dialog window and whether they're
+ // called from script. Fixing this is bug 779939.
+ bool dialog = aDialog;
+ if (!aCalledFromScript) {
+ dialog = argc > 0;
+ }
+
+ return OpenWindowInternal(aParent, aUrl, aName, aFeatures,
+ aCalledFromScript, dialog,
+ aNavigate, argv, aIsPopupSpam,
+ aForceNoOpener, aLoadInfo, aResult);
+}
+
+// This static function checks if the aDocShell uses an UserContextId equal to
+// the userContextId of subjectPrincipal, if not null.
+static bool
+CheckUserContextCompatibility(nsIDocShell* aDocShell)
+{
+ MOZ_ASSERT(aDocShell);
+
+ uint32_t userContextId =
+ static_cast<nsDocShell*>(aDocShell)->GetOriginAttributes().mUserContextId;
+
+ nsCOMPtr<nsIPrincipal> subjectPrincipal =
+ nsContentUtils::GetCurrentJSContext()
+ ? nsContentUtils::SubjectPrincipal() : nullptr;
+
+ // If we don't have a valid principal, probably we are in e10s mode, parent
+ // side.
+ if (!subjectPrincipal) {
+ return true;
+ }
+
+ // DocShell can have UsercontextID set but loading a document with system
+ // principal. In this case, we consider everything ok.
+ if (nsContentUtils::IsSystemPrincipal(subjectPrincipal)) {
+ return true;
+ }
+
+ return subjectPrincipal->GetUserContextId() == userContextId;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::OpenWindowWithoutParent(nsITabParent** aResult)
+{
+ return OpenWindowWithTabParent(nullptr, EmptyCString(), true, 1.0f, aResult);
+}
+
+nsresult
+nsWindowWatcher::CreateChromeWindow(const nsACString& aFeatures,
+ nsIWebBrowserChrome* aParentChrome,
+ uint32_t aChromeFlags,
+ uint32_t aContextFlags,
+ nsITabParent* aOpeningTabParent,
+ mozIDOMWindowProxy* aOpener,
+ nsIWebBrowserChrome** aResult)
+{
+ nsCOMPtr<nsIWindowCreator2> windowCreator2(do_QueryInterface(mWindowCreator));
+ if (NS_WARN_IF(!windowCreator2)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool cancel = false;
+ nsCOMPtr<nsIWebBrowserChrome> newWindowChrome;
+ nsresult rv =
+ windowCreator2->CreateChromeWindow2(aParentChrome, aChromeFlags, aContextFlags,
+ aOpeningTabParent, aOpener, &cancel,
+ getter_AddRefs(newWindowChrome));
+
+ if (NS_SUCCEEDED(rv) && cancel) {
+ newWindowChrome = nullptr;
+ return NS_ERROR_ABORT;
+ }
+
+ newWindowChrome.forget(aResult);
+ return NS_OK;
+}
+
+/**
+ * Disable persistence of size/position in popups (determined by
+ * determining whether the features parameter specifies width or height
+ * in any way). We consider any overriding of the window's size or position
+ * in the open call as disabling persistence of those attributes.
+ * Popup windows (which should not persist size or position) generally set
+ * the size.
+ *
+ * @param aFeatures
+ * The features string that was used to open the window.
+ * @param aTreeOwner
+ * The nsIDocShellTreeOwner of the newly opened window. If null,
+ * this function is a no-op.
+ */
+void
+nsWindowWatcher::MaybeDisablePersistence(const nsACString& aFeatures,
+ nsIDocShellTreeOwner* aTreeOwner)
+{
+ if (!aTreeOwner) {
+ return;
+ }
+
+ // At the moment, the strings "height=" or "width=" never happen
+ // outside a size specification, so we can do this the Q&D way.
+ if (PL_strcasestr(aFeatures.BeginReading(), "width=") ||
+ PL_strcasestr(aFeatures.BeginReading(), "height=")) {
+ aTreeOwner->SetPersistence(false, false, false);
+ }
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::OpenWindowWithTabParent(nsITabParent* aOpeningTabParent,
+ const nsACString& aFeatures,
+ bool aCalledFromJS,
+ float aOpenerFullZoom,
+ nsITabParent** aResult)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(mWindowCreator);
+
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsContentUtils::WarnScriptWasIgnored(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!mWindowCreator)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool isPrivateBrowsingWindow =
+ Preferences::GetBool("browser.privatebrowsing.autostart");
+
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindowOuter;
+ if (aOpeningTabParent) {
+ // We need to examine the window that aOpeningTabParent belongs to in
+ // order to inform us of what kind of window we're going to open.
+ TabParent* openingTab = TabParent::GetFrom(aOpeningTabParent);
+ parentWindowOuter = openingTab->GetParentWindowOuter();
+
+ // Propagate the privacy status of the parent window, if
+ // available, to the child.
+ if (!isPrivateBrowsingWindow) {
+ nsCOMPtr<nsILoadContext> parentContext = openingTab->GetLoadContext();
+ if (parentContext) {
+ isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing();
+ }
+ }
+ }
+
+ if (!parentWindowOuter) {
+ // We couldn't find a browser window for the opener, so either we
+ // never were passed aOpeningTabParent, the window is closed,
+ // or it's in the process of closing. Either way, we'll use
+ // the most recently opened browser window instead.
+ parentWindowOuter = nsContentUtils::GetMostRecentNonPBWindow();
+ }
+
+ if (NS_WARN_IF(!parentWindowOuter)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
+ GetWindowTreeOwner(parentWindowOuter, getter_AddRefs(parentTreeOwner));
+ if (NS_WARN_IF(!parentTreeOwner)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIWindowCreator2> windowCreator2(do_QueryInterface(mWindowCreator));
+ if (NS_WARN_IF(!windowCreator2)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t contextFlags = 0;
+ if (parentWindowOuter->IsLoadingOrRunningTimeout()) {
+ contextFlags |=
+ nsIWindowCreator2::PARENT_IS_LOADING_OR_RUNNING_TIMEOUT;
+ }
+
+ uint32_t chromeFlags = CalculateChromeFlagsForChild(aFeatures);
+
+ // A content process has asked for a new window, which implies
+ // that the new window will need to be remote.
+ chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW;
+
+ nsCOMPtr<nsIWebBrowserChrome> parentChrome(do_GetInterface(parentTreeOwner));
+ nsCOMPtr<nsIWebBrowserChrome> newWindowChrome;
+
+ CreateChromeWindow(aFeatures, parentChrome, chromeFlags, contextFlags,
+ aOpeningTabParent, nullptr, getter_AddRefs(newWindowChrome));
+
+ if (NS_WARN_IF(!newWindowChrome)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> chromeTreeItem = do_GetInterface(newWindowChrome);
+ if (NS_WARN_IF(!chromeTreeItem)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> chromeTreeOwner;
+ chromeTreeItem->GetTreeOwner(getter_AddRefs(chromeTreeOwner));
+ if (NS_WARN_IF(!chromeTreeOwner)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsILoadContext> chromeContext = do_QueryInterface(chromeTreeItem);
+ if (NS_WARN_IF(!chromeContext)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ chromeContext->SetPrivateBrowsing(isPrivateBrowsingWindow);
+
+ // Tabs opened from a content process can only open new windows
+ // that will also run with out-of-process tabs.
+ chromeContext->SetRemoteTabs(true);
+
+ MaybeDisablePersistence(aFeatures, chromeTreeOwner);
+
+ SizeSpec sizeSpec;
+ CalcSizeSpec(aFeatures, sizeSpec);
+ SizeOpenedWindow(chromeTreeOwner, parentWindowOuter, false, sizeSpec,
+ Some(aOpenerFullZoom));
+
+ nsCOMPtr<nsITabParent> newTabParent;
+ chromeTreeOwner->GetPrimaryTabParent(getter_AddRefs(newTabParent));
+ if (NS_WARN_IF(!newTabParent)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ newTabParent.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+nsWindowWatcher::OpenWindowInternal(mozIDOMWindowProxy* aParent,
+ const char* aUrl,
+ const char* aName,
+ const char* aFeatures,
+ bool aCalledFromJS,
+ bool aDialog,
+ bool aNavigate,
+ nsIArray* aArgv,
+ bool aIsPopupSpam,
+ bool aForceNoOpener,
+ nsIDocShellLoadInfo* aLoadInfo,
+ mozIDOMWindowProxy** aResult)
+{
+ nsresult rv = NS_OK;
+ bool isNewToplevelWindow = false;
+ bool windowIsNew = false;
+ bool windowNeedsName = false;
+ bool windowIsModal = false;
+ bool uriToLoadIsChrome = false;
+
+ uint32_t chromeFlags;
+ nsAutoString name; // string version of aName
+ nsAutoCString features; // string version of aFeatures
+ nsCOMPtr<nsIURI> uriToLoad; // from aUrl, if any
+ nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner; // from the parent window, if any
+ nsCOMPtr<nsIDocShellTreeItem> newDocShellItem; // from the new window
+
+ nsCOMPtr<nsPIDOMWindowOuter> parent =
+ aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr;
+
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = 0;
+
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsContentUtils::WarnScriptWasIgnored(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+
+ GetWindowTreeOwner(parent, getter_AddRefs(parentTreeOwner));
+
+ // We expect TabParent to have provided us the absolute URI of the window
+ // we're to open, so there's no need to call URIfromURL (or more importantly,
+ // to check for a chrome URI, which cannot be opened from a remote tab).
+ if (aUrl) {
+ rv = URIfromURL(aUrl, aParent, getter_AddRefs(uriToLoad));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ uriToLoad->SchemeIs("chrome", &uriToLoadIsChrome);
+ }
+
+ bool nameSpecified = false;
+ if (aName) {
+ CopyUTF8toUTF16(aName, name);
+ nameSpecified = true;
+ } else {
+ name.SetIsVoid(true);
+ }
+
+ if (aFeatures) {
+ features.Assign(aFeatures);
+ features.StripWhitespace();
+ } else {
+ features.SetIsVoid(true);
+ }
+
+ // try to find an extant window with the given name
+ nsCOMPtr<nsPIDOMWindowOuter> foundWindow =
+ SafeGetWindowByName(name, aForceNoOpener, aParent);
+ GetWindowTreeItem(foundWindow, getter_AddRefs(newDocShellItem));
+
+ // Do sandbox checks here, instead of waiting until nsIDocShell::LoadURI.
+ // The state of the window can change before this call and if we are blocked
+ // because of sandboxing, we wouldn't want that to happen.
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow =
+ aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr;
+ nsCOMPtr<nsIDocShell> parentDocShell;
+ if (parentWindow) {
+ parentDocShell = parentWindow->GetDocShell();
+ if (parentDocShell) {
+ nsCOMPtr<nsIDocShell> foundDocShell = do_QueryInterface(newDocShellItem);
+ if (parentDocShell->IsSandboxedFrom(foundDocShell)) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+ }
+ }
+
+ // no extant window? make a new one.
+
+ // If no parent, consider it chrome when running in the parent process.
+ bool hasChromeParent = XRE_IsContentProcess() ? false : true;
+ if (aParent) {
+ // Check if the parent document has chrome privileges.
+ nsIDocument* doc = parentWindow->GetDoc();
+ hasChromeParent = doc && nsContentUtils::IsChromeDoc(doc);
+ }
+
+ bool isCallerChrome = nsContentUtils::LegacyIsCallerChromeOrNativeCode();
+
+ // Make sure we calculate the chromeFlags *before* we push the
+ // callee context onto the context stack so that
+ // the calculation sees the actual caller when doing its
+ // security checks.
+ if (isCallerChrome && XRE_IsParentProcess()) {
+ chromeFlags = CalculateChromeFlagsForParent(aParent, features,
+ aDialog, uriToLoadIsChrome,
+ hasChromeParent, aCalledFromJS);
+ } else {
+ chromeFlags = CalculateChromeFlagsForChild(features);
+
+ if (aDialog) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG;
+ }
+ }
+
+ SizeSpec sizeSpec;
+ CalcSizeSpec(features, sizeSpec);
+
+ nsCOMPtr<nsIScriptSecurityManager> sm(
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID));
+
+
+ // XXXbz Why is an AutoJSAPI good enough here? Wouldn't AutoEntryScript (so
+ // we affect the entry global) make more sense? Or do we just want to affect
+ // GetSubjectPrincipal()?
+ dom::AutoJSAPI jsapiChromeGuard;
+
+ bool windowTypeIsChrome =
+ chromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME;
+ if (isCallerChrome && !hasChromeParent && !windowTypeIsChrome) {
+ // open() is called from chrome on a non-chrome window, initialize an
+ // AutoJSAPI with the callee to prevent the caller's privileges from leaking
+ // into code that runs while opening the new window.
+ //
+ // The reasoning for this is in bug 289204. Basically, chrome sometimes does
+ // someContentWindow.open(untrustedURL), and wants to be insulated from nasty
+ // javascript: URLs and such. But there are also cases where we create a
+ // window parented to a content window (such as a download dialog), usually
+ // directly with nsIWindowWatcher. In those cases, we want the principal of
+ // the initial about:blank document to be system, so that the subsequent XUL
+ // load can reuse the inner window and avoid blowing away expandos. As such,
+ // we decide whether to load with the principal of the caller or of the parent
+ // based on whether the docshell type is chrome or content.
+
+ nsCOMPtr<nsIGlobalObject> parentGlobalObject = do_QueryInterface(aParent);
+ if (!aParent) {
+ jsapiChromeGuard.Init();
+ } else if (NS_WARN_IF(!jsapiChromeGuard.Init(parentGlobalObject))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ uint32_t activeDocsSandboxFlags = 0;
+ if (!newDocShellItem) {
+ // We're going to either open up a new window ourselves or ask a
+ // nsIWindowProvider for one. In either case, we'll want to set the right
+ // name on it.
+ windowNeedsName = true;
+
+ // If the parent trying to open a new window is sandboxed
+ // without 'allow-popups', this is not allowed and we fail here.
+ if (aParent) {
+ if (nsIDocument* doc = parentWindow->GetDoc()) {
+ // Save sandbox flags for copying to new browsing context (docShell).
+ activeDocsSandboxFlags = doc->GetSandboxFlags();
+ if (activeDocsSandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+ }
+ }
+
+ // Now check whether it's ok to ask a window provider for a window. Don't
+ // do it if we're opening a dialog or if our parent is a chrome window or
+ // if we're opening something that has modal, dialog, or chrome flags set.
+ nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(aParent);
+ if (!aDialog && !chromeWin &&
+ !(chromeFlags & (nsIWebBrowserChrome::CHROME_MODAL |
+ nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
+ nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) {
+ nsCOMPtr<nsIWindowProvider> provider;
+ if (parentTreeOwner) {
+ provider = do_GetInterface(parentTreeOwner);
+ } else if (XRE_IsContentProcess()) {
+ // we're in a content process but we don't have a tabchild we can
+ // use.
+ provider = nsContentUtils::GetWindowProviderForContentProcess();
+ }
+
+ if (provider) {
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ rv = provider->ProvideWindow(aParent, chromeFlags, aCalledFromJS,
+ sizeSpec.PositionSpecified(),
+ sizeSpec.SizeSpecified(),
+ uriToLoad, name, features, aForceNoOpener,
+ &windowIsNew, getter_AddRefs(newWindow));
+
+ if (NS_SUCCEEDED(rv)) {
+ GetWindowTreeItem(newWindow, getter_AddRefs(newDocShellItem));
+ if (windowIsNew && newDocShellItem) {
+ // Make sure to stop any loads happening in this window that the
+ // window provider might have started. Otherwise if our caller
+ // manipulates the window it just opened and then the load
+ // completes their stuff will get blown away.
+ nsCOMPtr<nsIWebNavigation> webNav =
+ do_QueryInterface(newDocShellItem);
+ webNav->Stop(nsIWebNavigation::STOP_NETWORK);
+ }
+
+ // If this is a new window, but it's incompatible with the current
+ // userContextId, we ignore it and we pretend that nothing has been
+ // returned by ProvideWindow.
+ if (!windowIsNew && newDocShellItem) {
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(newDocShellItem);
+ if (!CheckUserContextCompatibility(docShell)) {
+ newWindow = nullptr;
+ newDocShellItem = nullptr;
+ windowIsNew = false;
+ }
+ }
+
+ } else if (rv == NS_ERROR_ABORT) {
+ // NS_ERROR_ABORT means the window provider has flat-out rejected
+ // the open-window call and we should bail. Don't return an error
+ // here, because our caller may propagate that error, which might
+ // cause e.g. window.open to throw! Just return null for our out
+ // param.
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ bool newWindowShouldBeModal = false;
+ bool parentIsModal = false;
+ if (!newDocShellItem) {
+ windowIsNew = true;
+ isNewToplevelWindow = true;
+
+ nsCOMPtr<nsIWebBrowserChrome> parentChrome(do_GetInterface(parentTreeOwner));
+
+ // is the parent (if any) modal? if so, we must be, too.
+ bool weAreModal = (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL) != 0;
+ newWindowShouldBeModal = weAreModal;
+ if (!weAreModal && parentChrome) {
+ parentChrome->IsWindowModal(&weAreModal);
+ parentIsModal = weAreModal;
+ }
+
+ if (weAreModal) {
+ windowIsModal = true;
+ // in case we added this because weAreModal
+ chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL |
+ nsIWebBrowserChrome::CHROME_DEPENDENT;
+ }
+
+ // Make sure to not create modal windows if our parent is invisible and
+ // isn't a chrome window. Otherwise we can end up in a bizarre situation
+ // where we can't shut down because an invisible window is open. If
+ // someone tries to do this, throw.
+ if (!hasChromeParent && (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL)) {
+ nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(parentTreeOwner));
+ nsCOMPtr<nsIWidget> parentWidget;
+ if (parentWindow) {
+ parentWindow->GetMainWidget(getter_AddRefs(parentWidget));
+ }
+ // NOTE: the logic for this visibility check is duplicated in
+ // nsIDOMWindowUtils::isParentWindowMainWidgetVisible - if we change
+ // how a window is determined "visible" in this context then we should
+ // also adjust that attribute and/or any consumers of it...
+ if (parentWidget && !parentWidget->IsVisible()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ NS_ASSERTION(mWindowCreator,
+ "attempted to open a new window with no WindowCreator");
+ rv = NS_ERROR_FAILURE;
+ if (mWindowCreator) {
+ nsCOMPtr<nsIWebBrowserChrome> newChrome;
+
+ nsCOMPtr<nsPIDOMWindowInner> parentTopInnerWindow;
+ if (parentWindow) {
+ nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow = parentWindow->GetTop();
+ if (parentTopWindow) {
+ parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow();
+ }
+ }
+
+ if (parentTopInnerWindow) {
+ parentTopInnerWindow->Suspend();
+ }
+
+ /* If the window creator is an nsIWindowCreator2, we can give it
+ some hints. The only hint at this time is whether the opening window
+ is in a situation that's likely to mean this is an unrequested
+ popup window we're creating. However we're not completely honest:
+ we clear that indicator if the opener is chrome, so that the
+ downstream consumer can treat the indicator to mean simply
+ that the new window is subject to popup control. */
+ nsCOMPtr<nsIWindowCreator2> windowCreator2(
+ do_QueryInterface(mWindowCreator));
+ if (windowCreator2) {
+ uint32_t contextFlags = 0;
+ bool popupConditions = false;
+
+ // is the parent under popup conditions?
+ if (parentWindow) {
+ popupConditions = parentWindow->IsLoadingOrRunningTimeout();
+ }
+
+ // chrome is always allowed, so clear the flag if the opener is chrome
+ if (popupConditions) {
+ popupConditions = !isCallerChrome;
+ }
+
+ if (popupConditions) {
+ contextFlags |=
+ nsIWindowCreator2::PARENT_IS_LOADING_OR_RUNNING_TIMEOUT;
+ }
+
+ mozIDOMWindowProxy* openerWindow = aForceNoOpener ? nullptr : aParent;
+ rv = CreateChromeWindow(features, parentChrome, chromeFlags, contextFlags,
+ nullptr, openerWindow, getter_AddRefs(newChrome));
+
+ } else {
+ rv = mWindowCreator->CreateChromeWindow(parentChrome, chromeFlags,
+ getter_AddRefs(newChrome));
+ }
+
+ if (parentTopInnerWindow) {
+ parentTopInnerWindow->Resume();
+ }
+
+ if (newChrome) {
+ nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(newChrome);
+ if (xulWin) {
+ nsCOMPtr<nsIXULBrowserWindow> xulBrowserWin;
+ xulWin->GetXULBrowserWindow(getter_AddRefs(xulBrowserWin));
+ if (xulBrowserWin) {
+ nsPIDOMWindowOuter* openerWindow = aForceNoOpener ? nullptr : parentWindow.get();
+ xulBrowserWin->ForceInitialBrowserNonRemote(openerWindow);
+ }
+ }
+ /* It might be a chrome nsXULWindow, in which case it won't have
+ an nsIDOMWindow (primary content shell). But in that case, it'll
+ be able to hand over an nsIDocShellTreeItem directly. */
+ nsCOMPtr<nsPIDOMWindowOuter> newWindow(do_GetInterface(newChrome));
+ if (newWindow) {
+ GetWindowTreeItem(newWindow, getter_AddRefs(newDocShellItem));
+ }
+ if (!newDocShellItem) {
+ newDocShellItem = do_GetInterface(newChrome);
+ }
+ if (!newDocShellItem) {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+
+ // better have a window to use by this point
+ if (!newDocShellItem) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIDocShell> newDocShell(do_QueryInterface(newDocShellItem));
+ NS_ENSURE_TRUE(newDocShell, NS_ERROR_UNEXPECTED);
+
+ // If our parent is sandboxed, set it as the one permitted sandboxed navigator
+ // on the new window we're opening.
+ if (activeDocsSandboxFlags && parentWindow) {
+ newDocShell->SetOnePermittedSandboxedNavigator(
+ parentWindow->GetDocShell());
+ }
+
+ // Copy sandbox flags to the new window if activeDocsSandboxFlags says to do
+ // so. Note that it's only nonzero if the window is new, so clobbering
+ // sandbox flags on the window makes sense in that case.
+ if (activeDocsSandboxFlags &
+ SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS) {
+ newDocShell->SetSandboxFlags(activeDocsSandboxFlags);
+ }
+
+ rv = ReadyOpenedDocShellItem(newDocShellItem, parentWindow, windowIsNew,
+ aForceNoOpener, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (isNewToplevelWindow) {
+ nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner;
+ newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner));
+ MaybeDisablePersistence(features, newTreeOwner);
+ }
+
+ if (aDialog && aArgv) {
+ // Set the args on the new window.
+ nsCOMPtr<nsPIDOMWindowOuter> piwin(do_QueryInterface(*aResult));
+ NS_ENSURE_TRUE(piwin, NS_ERROR_UNEXPECTED);
+
+ rv = piwin->SetArguments(aArgv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ /* allow a window that we found by name to keep its name (important for cases
+ like _self where the given name is different (and invalid)). Also, _blank
+ is not a window name. */
+ if (windowNeedsName) {
+ if (nameSpecified && !name.LowerCaseEqualsLiteral("_blank")) {
+ newDocShellItem->SetName(name);
+ } else {
+ newDocShellItem->SetName(EmptyString());
+ }
+ }
+
+ // Now we have to set the right opener principal on the new window. Note
+ // that we have to do this _before_ starting any URI loads, thanks to the
+ // sync nature of javascript: loads.
+ //
+ // Note: The check for the current JSContext isn't necessarily sensical.
+ // It's just designed to preserve old semantics during a mass-conversion
+ // patch.
+ nsCOMPtr<nsIPrincipal> subjectPrincipal =
+ nsContentUtils::GetCurrentJSContext() ? nsContentUtils::SubjectPrincipal() :
+ nullptr;
+
+ bool isPrivateBrowsingWindow = false;
+
+ if (windowIsNew) {
+ auto* docShell = static_cast<nsDocShell*>(newDocShell.get());
+
+ // If this is not a chrome docShell, we apply originAttributes from the
+ // subjectPrincipal unless if it's an expanded or system principal.
+ if (subjectPrincipal &&
+ !nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal) &&
+ docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
+ DocShellOriginAttributes attrs;
+ attrs.InheritFromDocToChildDocShell(BasePrincipal::Cast(subjectPrincipal)->OriginAttributesRef());
+ isPrivateBrowsingWindow = !!attrs.mPrivateBrowsingId;
+ docShell->SetOriginAttributes(attrs);
+ } else {
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ GetWindowTreeItem(aParent, getter_AddRefs(parentItem));
+ nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(parentItem);
+ if (parentContext) {
+ isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing();
+ }
+ }
+
+ bool autoPrivateBrowsing =
+ Preferences::GetBool("browser.privatebrowsing.autostart");
+
+ if (!autoPrivateBrowsing &&
+ (chromeFlags & nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW)) {
+ isPrivateBrowsingWindow = false;
+ } else if (autoPrivateBrowsing ||
+ (chromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW)) {
+ isPrivateBrowsingWindow = true;
+ }
+
+ // Now set the opener principal on the new window. Note that we need to do
+ // this no matter whether we were opened from JS; if there is nothing on
+ // the JS stack, just use the principal of our parent window. In those
+ // cases we do _not_ set the parent window principal as the owner of the
+ // load--since we really don't know who the owner is, just leave it null.
+ nsCOMPtr<nsPIDOMWindowOuter> newWindow = do_QueryInterface(*aResult);
+ NS_ASSERTION(newWindow == newDocShell->GetWindow(), "Different windows??");
+
+ // The principal of the initial about:blank document gets set up in
+ // nsWindowWatcher::AddWindow. Make sure to call it. In the common case
+ // this call already happened when the window was created, but
+ // SetInitialPrincipalToSubject is safe to call multiple times.
+ if (newWindow) {
+ newWindow->SetInitialPrincipalToSubject();
+ if (aIsPopupSpam) {
+ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(newWindow);
+ MOZ_ASSERT(!globalWin->IsPopupSpamWindow(),
+ "Who marked it as popup spam already???");
+ if (!globalWin->IsPopupSpamWindow()) { // Make sure we don't mess up our
+ // counter even if the above
+ // assert fails.
+ globalWin->SetIsPopupSpamWindow(true);
+ }
+ }
+ }
+ }
+
+ // We rely on CalculateChromeFlags to decide whether remote (out-of-process)
+ // tabs should be used.
+ bool isRemoteWindow =
+ !!(chromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW);
+
+ if (isNewToplevelWindow) {
+ nsCOMPtr<nsIDocShellTreeItem> childRoot;
+ newDocShellItem->GetRootTreeItem(getter_AddRefs(childRoot));
+ nsCOMPtr<nsILoadContext> childContext = do_QueryInterface(childRoot);
+ if (childContext) {
+ childContext->SetPrivateBrowsing(isPrivateBrowsingWindow);
+ childContext->SetRemoteTabs(isRemoteWindow);
+ }
+ } else if (windowIsNew) {
+ nsCOMPtr<nsILoadContext> childContext = do_QueryInterface(newDocShellItem);
+ if (childContext) {
+ childContext->SetPrivateBrowsing(isPrivateBrowsingWindow);
+ childContext->SetRemoteTabs(isRemoteWindow);
+ }
+ }
+
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo = aLoadInfo;
+ if (uriToLoad && aNavigate && !loadInfo) {
+ newDocShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE);
+
+ if (subjectPrincipal) {
+ loadInfo->SetTriggeringPrincipal(subjectPrincipal);
+ }
+
+ /* use the URL from the *extant* document, if any. The usual accessor
+ GetDocument will synchronously create an about:blank document if
+ it has no better answer, and we only care about a real document.
+ Also using GetDocument to force document creation seems to
+ screw up focus in the hidden window; see bug 36016.
+ */
+ nsCOMPtr<nsIDocument> doc = GetEntryDocument();
+ if (!doc && parentWindow) {
+ doc = parentWindow->GetExtantDoc();
+ }
+ if (doc) {
+ // Set the referrer
+ loadInfo->SetReferrer(doc->GetDocumentURI());
+ loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy());
+ }
+ }
+
+ if (isNewToplevelWindow) {
+ // Notify observers that the window is open and ready.
+ // The window has not yet started to load a document.
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->NotifyObservers(*aResult, "toplevel-window-ready", nullptr);
+ }
+ }
+
+ // Before loading the URI we want to be 100% sure that we use the correct
+ // userContextId.
+ MOZ_ASSERT(CheckUserContextCompatibility(newDocShell));
+
+ if (uriToLoad && aNavigate) {
+ newDocShell->LoadURI(
+ uriToLoad,
+ loadInfo,
+ windowIsNew ?
+ static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) :
+ static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_NONE),
+ true);
+ }
+
+ // Copy the current session storage for the current domain.
+ if (subjectPrincipal && parentDocShell) {
+ nsCOMPtr<nsIDOMStorageManager> parentStorageManager =
+ do_QueryInterface(parentDocShell);
+ nsCOMPtr<nsIDOMStorageManager> newStorageManager =
+ do_QueryInterface(newDocShell);
+
+ if (parentStorageManager && newStorageManager) {
+ nsCOMPtr<nsIDOMStorage> storage;
+ nsCOMPtr<nsPIDOMWindowInner> pInnerWin = parentWindow->GetCurrentInnerWindow();
+
+ parentStorageManager->GetStorage(pInnerWin, subjectPrincipal,
+ isPrivateBrowsingWindow,
+ getter_AddRefs(storage));
+ if (storage) {
+ newStorageManager->CloneStorage(storage);
+ }
+ }
+ }
+
+ if (isNewToplevelWindow) {
+ nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner;
+ newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner));
+ SizeOpenedWindow(newTreeOwner, aParent, isCallerChrome, sizeSpec);
+ }
+
+ if (windowIsModal) {
+ nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner;
+ newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner));
+ nsCOMPtr<nsIWebBrowserChrome> newChrome(do_GetInterface(newTreeOwner));
+
+ // Throw an exception here if no web browser chrome is available,
+ // we need that to show a modal window.
+ NS_ENSURE_TRUE(newChrome, NS_ERROR_NOT_AVAILABLE);
+
+ // Dispatch dialog events etc, but we only want to do that if
+ // we're opening a modal content window (the helper classes are
+ // no-ops if given no window), for chrome dialogs we don't want to
+ // do any of that (it's done elsewhere for us).
+ // Make sure we maintain the state on an outer window, because
+ // that's where it lives; inner windows assert if you try to
+ // maintain the state on them.
+ nsAutoWindowStateHelper windowStateHelper(
+ parentWindow ? parentWindow->GetOuterWindow() : nullptr);
+
+ if (!windowStateHelper.DefaultEnabled()) {
+ // Default to cancel not opening the modal window.
+ NS_RELEASE(*aResult);
+
+ return NS_OK;
+ }
+
+ bool isAppModal = false;
+ nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(newTreeOwner));
+ nsCOMPtr<nsIWidget> parentWidget;
+ if (parentWindow) {
+ parentWindow->GetMainWidget(getter_AddRefs(parentWidget));
+ if (parentWidget) {
+ isAppModal = parentWidget->IsRunningAppModal();
+ }
+ }
+ if (parentWidget &&
+ ((!newWindowShouldBeModal && parentIsModal) || isAppModal)) {
+ parentWidget->SetFakeModal(true);
+ } else {
+ // Reset popup state while opening a modal dialog, and firing
+ // events about the dialog, to prevent the current state from
+ // being active the whole time a modal dialog is open.
+ nsAutoPopupStatePusher popupStatePusher(openAbused);
+
+ newChrome->ShowAsModal();
+ }
+ }
+
+ // If a website opens a popup exit DOM fullscreen
+ if (windowIsNew && aCalledFromJS && !hasChromeParent && !isCallerChrome &&
+ parentWindow) {
+ nsIDocument::AsyncExitFullscreen(parentWindow->GetDoc());
+ }
+
+ if (aForceNoOpener && windowIsNew) {
+ NS_RELEASE(*aResult);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::RegisterNotification(nsIObserver* aObserver)
+{
+ // just a convenience method; it delegates to nsIObserverService
+
+ if (!aObserver) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (!os) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = os->AddObserver(aObserver, "domwindowopened", false);
+ if (NS_SUCCEEDED(rv)) {
+ rv = os->AddObserver(aObserver, "domwindowclosed", false);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::UnregisterNotification(nsIObserver* aObserver)
+{
+ // just a convenience method; it delegates to nsIObserverService
+
+ if (!aObserver) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (!os) {
+ return NS_ERROR_FAILURE;
+ }
+
+ os->RemoveObserver(aObserver, "domwindowopened");
+ os->RemoveObserver(aObserver, "domwindowclosed");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::GetWindowEnumerator(nsISimpleEnumerator** aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MutexAutoLock lock(mListLock);
+ nsWatcherWindowEnumerator* enumerator = new nsWatcherWindowEnumerator(this);
+ if (enumerator) {
+ return CallQueryInterface(enumerator, aResult);
+ }
+
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::GetNewPrompter(mozIDOMWindowProxy* aParent, nsIPrompt** aResult)
+{
+ // This is for backwards compat only. Callers should just use the prompt
+ // service directly.
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> factory =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return factory->GetPrompt(aParent, NS_GET_IID(nsIPrompt),
+ reinterpret_cast<void**>(aResult));
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::GetNewAuthPrompter(mozIDOMWindowProxy* aParent,
+ nsIAuthPrompt** aResult)
+{
+ // This is for backwards compat only. Callers should just use the prompt
+ // service directly.
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> factory =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return factory->GetPrompt(aParent, NS_GET_IID(nsIAuthPrompt),
+ reinterpret_cast<void**>(aResult));
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::GetPrompt(mozIDOMWindowProxy* aParent, const nsIID& aIID,
+ void** aResult)
+{
+ // This is for backwards compat only. Callers should just use the prompt
+ // service directly.
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> factory =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = factory->GetPrompt(aParent, aIID, aResult);
+
+ // Allow for an embedding implementation to not support nsIAuthPrompt2.
+ if (rv == NS_NOINTERFACE && aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsCOMPtr<nsIAuthPrompt> oldPrompt;
+ rv = factory->GetPrompt(
+ aParent, NS_GET_IID(nsIAuthPrompt), getter_AddRefs(oldPrompt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_WrapAuthPrompt(oldPrompt, reinterpret_cast<nsIAuthPrompt2**>(aResult));
+ if (!*aResult) {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::SetWindowCreator(nsIWindowCreator* aCreator)
+{
+ mWindowCreator = aCreator;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::HasWindowCreator(bool* aResult)
+{
+ *aResult = mWindowCreator;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::GetActiveWindow(mozIDOMWindowProxy** aActiveWindow)
+{
+ *aActiveWindow = nullptr;
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ if (fm) {
+ return fm->GetActiveWindow(aActiveWindow);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::SetActiveWindow(mozIDOMWindowProxy* aActiveWindow)
+{
+ nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
+ if (fm) {
+ return fm->SetActiveWindow(aActiveWindow);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::AddWindow(mozIDOMWindowProxy* aWindow, nsIWebBrowserChrome* aChrome)
+{
+ if (!aWindow) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsPIDOMWindowOuter> win(do_QueryInterface(aWindow));
+
+ NS_ASSERTION(win->IsOuterWindow(),
+ "Uh, the active window must be an outer window!");
+ }
+#endif
+
+ {
+ nsWatcherWindowEntry* info;
+ MutexAutoLock lock(mListLock);
+
+ // if we already have an entry for this window, adjust
+ // its chrome mapping and return
+ info = FindWindowEntry(aWindow);
+ if (info) {
+ nsCOMPtr<nsISupportsWeakReference> supportsweak(
+ do_QueryInterface(aChrome));
+ if (supportsweak) {
+ supportsweak->GetWeakReference(getter_AddRefs(info->mChromeWeak));
+ } else {
+ info->mChrome = aChrome;
+ info->mChromeWeak = nullptr;
+ }
+ return NS_OK;
+ }
+
+ // create a window info struct and add it to the list of windows
+ info = new nsWatcherWindowEntry(aWindow, aChrome);
+ if (!info) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mOldestWindow) {
+ info->InsertAfter(mOldestWindow->mOlder);
+ } else {
+ mOldestWindow = info;
+ }
+ } // leave the mListLock
+
+ // a window being added to us signifies a newly opened window.
+ // send notifications.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (!os) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISupports> domwin(do_QueryInterface(aWindow));
+ return os->NotifyObservers(domwin, "domwindowopened", 0);
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::RemoveWindow(mozIDOMWindowProxy* aWindow)
+{
+ // find the corresponding nsWatcherWindowEntry, remove it
+
+ if (!aWindow) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsWatcherWindowEntry* info = FindWindowEntry(aWindow);
+ if (info) {
+ RemoveWindow(info);
+ return NS_OK;
+ }
+ NS_WARNING("requested removal of nonexistent window");
+ return NS_ERROR_INVALID_ARG;
+}
+
+nsWatcherWindowEntry*
+nsWindowWatcher::FindWindowEntry(mozIDOMWindowProxy* aWindow)
+{
+ // find the corresponding nsWatcherWindowEntry
+ nsWatcherWindowEntry* info;
+ nsWatcherWindowEntry* listEnd;
+#ifdef USEWEAKREFS
+ nsresult rv;
+ bool found;
+#endif
+
+ info = mOldestWindow;
+ listEnd = 0;
+#ifdef USEWEAKREFS
+ rv = NS_OK;
+ found = false;
+ while (info != listEnd && NS_SUCCEEDED(rv)) {
+ nsCOMPtr<mozIDOMWindowProxy> infoWindow(do_QueryReferent(info->mWindow));
+ if (!infoWindow) { // clean up dangling reference, while we're here
+ rv = RemoveWindow(info);
+ } else if (infoWindow.get() == aWindow) {
+ return info;
+ }
+
+ info = info->mYounger;
+ listEnd = mOldestWindow;
+ }
+ return 0;
+#else
+ while (info != listEnd) {
+ if (info->mWindow == aWindow) {
+ return info;
+ }
+ info = info->mYounger;
+ listEnd = mOldestWindow;
+ }
+ return 0;
+#endif
+}
+
+nsresult
+nsWindowWatcher::RemoveWindow(nsWatcherWindowEntry* aInfo)
+{
+ uint32_t count = mEnumeratorList.Length();
+
+ {
+ // notify the enumerators
+ MutexAutoLock lock(mListLock);
+ for (uint32_t ctr = 0; ctr < count; ++ctr) {
+ mEnumeratorList[ctr]->WindowRemoved(aInfo);
+ }
+
+ // remove the element from the list
+ if (aInfo == mOldestWindow) {
+ mOldestWindow = aInfo->mYounger == mOldestWindow ? 0 : aInfo->mYounger;
+ }
+ aInfo->Unlink();
+ }
+
+ // a window being removed from us signifies a newly closed window.
+ // send notifications.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+#ifdef USEWEAKREFS
+ nsCOMPtr<nsISupports> domwin(do_QueryReferent(aInfo->mWindow));
+ if (domwin) {
+ os->NotifyObservers(domwin, "domwindowclosed", 0);
+ }
+ // else bummer. since the window is gone, there's nothing to notify with.
+#else
+ nsCOMPtr<nsISupports> domwin(do_QueryInterface(aInfo->mWindow));
+ os->NotifyObservers(domwin, "domwindowclosed", 0);
+#endif
+ }
+
+ delete aInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::GetChromeForWindow(mozIDOMWindowProxy* aWindow,
+ nsIWebBrowserChrome** aResult)
+{
+ if (!aWindow || !aResult) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = 0;
+
+ MutexAutoLock lock(mListLock);
+ nsWatcherWindowEntry* info = FindWindowEntry(aWindow);
+ if (info) {
+ if (info->mChromeWeak) {
+ return info->mChromeWeak->QueryReferent(
+ NS_GET_IID(nsIWebBrowserChrome), reinterpret_cast<void**>(aResult));
+ }
+ *aResult = info->mChrome;
+ NS_IF_ADDREF(*aResult);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowWatcher::GetWindowByName(const nsAString& aTargetName,
+ mozIDOMWindowProxy* aCurrentWindow,
+ mozIDOMWindowProxy** aResult)
+{
+ if (!aResult) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = nullptr;
+
+ nsPIDOMWindowOuter* currentWindow =
+ aCurrentWindow ? nsPIDOMWindowOuter::From(aCurrentWindow) : nullptr;
+
+ nsCOMPtr<nsIDocShellTreeItem> treeItem;
+
+ nsCOMPtr<nsIDocShellTreeItem> startItem;
+ GetWindowTreeItem(currentWindow, getter_AddRefs(startItem));
+ if (startItem) {
+ // Note: original requestor is null here, per idl comments
+ startItem->FindItemWithName(aTargetName, nullptr, nullptr,
+ getter_AddRefs(treeItem));
+ } else {
+ // Note: original requestor is null here, per idl comments
+ FindItemWithName(aTargetName, nullptr, nullptr, getter_AddRefs(treeItem));
+ }
+
+ if (treeItem) {
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = treeItem->GetWindow();
+ domWindow.forget(aResult);
+ }
+
+ return NS_OK;
+}
+
+bool
+nsWindowWatcher::AddEnumerator(nsWatcherWindowEnumerator* aEnumerator)
+{
+ // (requires a lock; assumes it's called by someone holding the lock)
+ return mEnumeratorList.AppendElement(aEnumerator) != nullptr;
+}
+
+bool
+nsWindowWatcher::RemoveEnumerator(nsWatcherWindowEnumerator* aEnumerator)
+{
+ // (requires a lock; assumes it's called by someone holding the lock)
+ return mEnumeratorList.RemoveElement(aEnumerator);
+}
+
+nsresult
+nsWindowWatcher::URIfromURL(const char* aURL,
+ mozIDOMWindowProxy* aParent,
+ nsIURI** aURI)
+{
+ // Build the URI relative to the entry global.
+ nsCOMPtr<nsPIDOMWindowInner> baseWindow = do_QueryInterface(GetEntryGlobal());
+
+ // failing that, build it relative to the parent window, if possible
+ if (!baseWindow && aParent) {
+ baseWindow = nsPIDOMWindowOuter::From(aParent)->GetCurrentInnerWindow();
+ }
+
+ // failing that, use the given URL unmodified. It had better not be relative.
+
+ nsIURI* baseURI = nullptr;
+
+ // get baseWindow's document URI
+ if (baseWindow) {
+ if (nsIDocument* doc = baseWindow->GetDoc()) {
+ baseURI = doc->GetDocBaseURI();
+ }
+ }
+
+ // build and return the absolute URI
+ return NS_NewURI(aURI, aURL, baseURI);
+}
+
+#define NS_CALCULATE_CHROME_FLAG_FOR(feature, flag) \
+ prefBranch->GetBoolPref(feature, &forceEnable); \
+ if (forceEnable && !aDialog && !aHasChromeParent && !aChromeURL) { \
+ chromeFlags |= flag; \
+ } else { \
+ chromeFlags |= \
+ WinHasOption(aFeatures, feature, 0, &presenceFlag) ? flag : 0; \
+ }
+
+// static
+uint32_t
+nsWindowWatcher::CalculateChromeFlagsHelper(uint32_t aInitialFlags,
+ const nsACString& aFeatures,
+ bool& presenceFlag,
+ bool aDialog,
+ bool aHasChromeParent,
+ bool aChromeURL)
+{
+ uint32_t chromeFlags = aInitialFlags;
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ nsCOMPtr<nsIPrefService> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+
+ NS_ENSURE_SUCCESS(rv, nsIWebBrowserChrome::CHROME_DEFAULT);
+
+ rv = prefs->GetBranch("dom.disable_window_open_feature.",
+ getter_AddRefs(prefBranch));
+
+ NS_ENSURE_SUCCESS(rv, nsIWebBrowserChrome::CHROME_DEFAULT);
+
+ // NS_CALCULATE_CHROME_FLAG_FOR requires aFeatures, forceEnable, aDialog
+ // aHasChromeParent, aChromeURL, presenceFlag and chromeFlags to be in
+ // scope.
+ bool forceEnable = false;
+
+ NS_CALCULATE_CHROME_FLAG_FOR("titlebar",
+ nsIWebBrowserChrome::CHROME_TITLEBAR);
+ NS_CALCULATE_CHROME_FLAG_FOR("close",
+ nsIWebBrowserChrome::CHROME_WINDOW_CLOSE);
+ NS_CALCULATE_CHROME_FLAG_FOR("toolbar",
+ nsIWebBrowserChrome::CHROME_TOOLBAR);
+ NS_CALCULATE_CHROME_FLAG_FOR("location",
+ nsIWebBrowserChrome::CHROME_LOCATIONBAR);
+ NS_CALCULATE_CHROME_FLAG_FOR("personalbar",
+ nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR);
+ NS_CALCULATE_CHROME_FLAG_FOR("status",
+ nsIWebBrowserChrome::CHROME_STATUSBAR);
+ NS_CALCULATE_CHROME_FLAG_FOR("menubar",
+ nsIWebBrowserChrome::CHROME_MENUBAR);
+ NS_CALCULATE_CHROME_FLAG_FOR("resizable",
+ nsIWebBrowserChrome::CHROME_WINDOW_RESIZE);
+ NS_CALCULATE_CHROME_FLAG_FOR("minimizable",
+ nsIWebBrowserChrome::CHROME_WINDOW_MIN);
+
+ // default scrollbar to "on," unless explicitly turned off
+ if (WinHasOption(aFeatures, "scrollbars", 1, &presenceFlag) || !presenceFlag) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_SCROLLBARS;
+ }
+
+ return chromeFlags;
+}
+
+// static
+uint32_t
+nsWindowWatcher::EnsureFlagsSafeForContent(uint32_t aChromeFlags,
+ bool aChromeURL)
+{
+ aChromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR;
+ aChromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE;
+ aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_LOWERED;
+ aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_RAISED;
+ aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_POPUP;
+ /* Untrusted script is allowed to pose modal windows with a chrome
+ scheme. This check could stand to be better. But it effectively
+ prevents untrusted script from opening modal windows in general
+ while still allowing alerts and the like. */
+ if (!aChromeURL) {
+ aChromeFlags &= ~(nsIWebBrowserChrome::CHROME_MODAL |
+ nsIWebBrowserChrome::CHROME_OPENAS_CHROME);
+ }
+
+ if (!(aChromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME)) {
+ aChromeFlags &= ~nsIWebBrowserChrome::CHROME_DEPENDENT;
+ }
+
+ return aChromeFlags;
+}
+
+/**
+ * Calculate the chrome bitmask from a string list of features requested
+ * from a child process. Feature strings that are restricted to the parent
+ * process are ignored here.
+ * @param aFeatures a string containing a list of named features
+ * @return the chrome bitmask
+ */
+// static
+uint32_t
+nsWindowWatcher::CalculateChromeFlagsForChild(const nsACString& aFeatures)
+{
+ if (aFeatures.IsVoid()) {
+ return nsIWebBrowserChrome::CHROME_ALL;
+ }
+
+ bool presenceFlag = false;
+ uint32_t chromeFlags = CalculateChromeFlagsHelper(
+ nsIWebBrowserChrome::CHROME_WINDOW_BORDERS, aFeatures, presenceFlag);
+
+ return EnsureFlagsSafeForContent(chromeFlags);
+}
+
+/**
+ * Calculate the chrome bitmask from a string list of features for a new
+ * privileged window.
+ * @param aParent the opener window
+ * @param aFeatures a string containing a list of named chrome features
+ * @param aDialog affects the assumptions made about unnamed features
+ * @param aChromeURL true if the window is being sent to a chrome:// URL
+ * @param aHasChromeParent true if the parent window is privileged
+ * @param aCalledFromJS true if the window open request came from script.
+ * @return the chrome bitmask
+ */
+// static
+uint32_t
+nsWindowWatcher::CalculateChromeFlagsForParent(mozIDOMWindowProxy* aParent,
+ const nsACString& aFeatures,
+ bool aDialog,
+ bool aChromeURL,
+ bool aHasChromeParent,
+ bool aCalledFromJS)
+{
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(nsContentUtils::LegacyIsCallerChromeOrNativeCode());
+
+ uint32_t chromeFlags = 0;
+
+ // The features string is made void by OpenWindowInternal
+ // if nullptr was originally passed as the features string.
+ if (aFeatures.IsVoid()) {
+ chromeFlags = nsIWebBrowserChrome::CHROME_ALL;
+ if (aDialog) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
+ nsIWebBrowserChrome::CHROME_OPENAS_CHROME;
+ }
+ } else {
+ chromeFlags = nsIWebBrowserChrome::CHROME_WINDOW_BORDERS;
+ }
+
+ /* This function has become complicated since browser windows and
+ dialogs diverged. The difference is, browser windows assume all
+ chrome not explicitly mentioned is off, if the features string
+ is not null. Exceptions are some OS border chrome new with Mozilla.
+ Dialogs interpret a (mostly) empty features string to mean
+ "OS's choice," and also support an "all" flag explicitly disallowed
+ in the standards-compliant window.(normal)open. */
+
+ bool presenceFlag = false;
+ if (aDialog && WinHasOption(aFeatures, "all", 0, &presenceFlag)) {
+ chromeFlags = nsIWebBrowserChrome::CHROME_ALL;
+ }
+
+ /* Next, allow explicitly named options to override the initial settings */
+ chromeFlags = CalculateChromeFlagsHelper(chromeFlags, aFeatures, presenceFlag,
+ aDialog, aHasChromeParent, aChromeURL);
+
+ // Determine whether the window is a private browsing window
+ chromeFlags |= WinHasOption(aFeatures, "private", 0, &presenceFlag) ?
+ nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW : 0;
+ chromeFlags |= WinHasOption(aFeatures, "non-private", 0, &presenceFlag) ?
+ nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW : 0;
+
+ // Check if it's a popup window
+ chromeFlags |= WinHasOption(aFeatures, "popup", 0, &presenceFlag) ?
+ nsIWebBrowserChrome::CHROME_WINDOW_POPUP : 0;
+
+ /* OK.
+ Normal browser windows, in spite of a stated pattern of turning off
+ all chrome not mentioned explicitly, will want the new OS chrome (window
+ borders, titlebars, closebox) on, unless explicitly turned off.
+ Dialogs, on the other hand, take the absence of any explicit settings
+ to mean "OS' choice." */
+
+ // default titlebar and closebox to "on," if not mentioned at all
+ if (!(chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_POPUP)) {
+ if (!PL_strcasestr(aFeatures.BeginReading(), "titlebar")) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR;
+ }
+ if (!PL_strcasestr(aFeatures.BeginReading(), "close")) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE;
+ }
+ }
+
+ if (aDialog && !aFeatures.IsVoid() && !presenceFlag) {
+ chromeFlags = nsIWebBrowserChrome::CHROME_DEFAULT;
+ }
+
+ /* Finally, once all the above normal chrome has been divined, deal
+ with the features that are more operating hints than appearance
+ instructions. (Note modality implies dependence.) */
+
+ if (WinHasOption(aFeatures, "alwaysLowered", 0, nullptr) ||
+ WinHasOption(aFeatures, "z-lock", 0, nullptr)) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_LOWERED;
+ } else if (WinHasOption(aFeatures, "alwaysRaised", 0, nullptr)) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RAISED;
+ }
+
+ chromeFlags |= WinHasOption(aFeatures, "macsuppressanimation", 0, nullptr) ?
+ nsIWebBrowserChrome::CHROME_MAC_SUPPRESS_ANIMATION : 0;
+
+ chromeFlags |= WinHasOption(aFeatures, "chrome", 0, nullptr) ?
+ nsIWebBrowserChrome::CHROME_OPENAS_CHROME : 0;
+ chromeFlags |= WinHasOption(aFeatures, "extrachrome", 0, nullptr) ?
+ nsIWebBrowserChrome::CHROME_EXTRA : 0;
+ chromeFlags |= WinHasOption(aFeatures, "centerscreen", 0, nullptr) ?
+ nsIWebBrowserChrome::CHROME_CENTER_SCREEN : 0;
+ chromeFlags |= WinHasOption(aFeatures, "dependent", 0, nullptr) ?
+ nsIWebBrowserChrome::CHROME_DEPENDENT : 0;
+ chromeFlags |= WinHasOption(aFeatures, "modal", 0, nullptr) ?
+ (nsIWebBrowserChrome::CHROME_MODAL | nsIWebBrowserChrome::CHROME_DEPENDENT) : 0;
+
+ /* On mobile we want to ignore the dialog window feature, since the mobile UI
+ does not provide any affordance for dialog windows. This does not interfere
+ with dialog windows created through openDialog. */
+ bool disableDialogFeature = false;
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ branch->GetBoolPref("dom.disable_window_open_dialog_feature",
+ &disableDialogFeature);
+
+ if (!disableDialogFeature) {
+ chromeFlags |= WinHasOption(aFeatures, "dialog", 0, nullptr) ?
+ nsIWebBrowserChrome::CHROME_OPENAS_DIALOG : 0;
+ }
+
+ /* and dialogs need to have the last word. assume dialogs are dialogs,
+ and opened as chrome, unless explicitly told otherwise. */
+ if (aDialog) {
+ if (!PL_strcasestr(aFeatures.BeginReading(), "dialog")) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG;
+ }
+ if (!PL_strcasestr(aFeatures.BeginReading(), "chrome")) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_CHROME;
+ }
+ }
+
+ /* missing
+ chromeFlags->copy_history
+ */
+
+ // Check security state for use in determing window dimensions
+ if (!aHasChromeParent) {
+ chromeFlags = EnsureFlagsSafeForContent(chromeFlags, aChromeURL);
+ }
+
+ // Disable CHROME_OPENAS_DIALOG if the window is inside <iframe mozbrowser>.
+ // It's up to the embedder to interpret what dialog=1 means.
+ nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent);
+ if (docshell && docshell->GetIsInMozBrowserOrApp()) {
+ chromeFlags &= ~nsIWebBrowserChrome::CHROME_OPENAS_DIALOG;
+ }
+
+ return chromeFlags;
+}
+
+// static
+int32_t
+nsWindowWatcher::WinHasOption(const nsACString& aOptions, const char* aName,
+ int32_t aDefault, bool* aPresenceFlag)
+{
+ if (aOptions.IsEmpty()) {
+ return 0;
+ }
+
+ const char* options = aOptions.BeginReading();
+ char* comma;
+ char* equal;
+ int32_t found = 0;
+
+#ifdef DEBUG
+ NS_ASSERTION(nsAutoCString(aOptions).FindCharInSet(" \n\r\t") == kNotFound,
+ "There should be no whitespace in this string!");
+#endif
+
+ while (true) {
+ comma = PL_strchr(options, ',');
+ if (comma) {
+ *comma = '\0';
+ }
+ equal = PL_strchr(options, '=');
+ if (equal) {
+ *equal = '\0';
+ }
+ if (nsCRT::strcasecmp(options, aName) == 0) {
+ if (aPresenceFlag) {
+ *aPresenceFlag = true;
+ }
+ if (equal)
+ if (*(equal + 1) == '*') {
+ found = aDefault;
+ } else if (nsCRT::strcasecmp(equal + 1, "yes") == 0) {
+ found = 1;
+ } else {
+ found = atoi(equal + 1);
+ }
+ else {
+ found = 1;
+ }
+ }
+ if (equal) {
+ *equal = '=';
+ }
+ if (comma) {
+ *comma = ',';
+ }
+ if (found || !comma) {
+ break;
+ }
+ options = comma + 1;
+ }
+ return found;
+}
+
+/* try to find an nsIDocShellTreeItem with the given name in any
+ known open window. a failure to find the item will not
+ necessarily return a failure method value. check aFoundItem.
+*/
+NS_IMETHODIMP
+nsWindowWatcher::FindItemWithName(const nsAString& aName,
+ nsIDocShellTreeItem* aRequestor,
+ nsIDocShellTreeItem* aOriginalRequestor,
+ nsIDocShellTreeItem** aFoundItem)
+{
+ *aFoundItem = nullptr;
+ if (aName.IsEmpty()) {
+ return NS_OK;
+ }
+
+ if (aName.LowerCaseEqualsLiteral("_blank") ||
+ aName.LowerCaseEqualsLiteral("_top") ||
+ aName.LowerCaseEqualsLiteral("_parent") ||
+ aName.LowerCaseEqualsLiteral("_self")) {
+ return NS_OK;
+ }
+
+ // If we are looking for an item and we don't have a docshell we are checking
+ // on, let's just look in the chrome tab group!
+ return TabGroup::GetChromeTabGroup()->FindItemWithName(aName,
+ aRequestor,
+ aOriginalRequestor,
+ aFoundItem);
+}
+
+already_AddRefed<nsIDocShellTreeItem>
+nsWindowWatcher::GetCallerTreeItem(nsIDocShellTreeItem* aParentItem)
+{
+ nsCOMPtr<nsIWebNavigation> callerWebNav = do_GetInterface(GetEntryGlobal());
+ nsCOMPtr<nsIDocShellTreeItem> callerItem = do_QueryInterface(callerWebNav);
+ if (!callerItem) {
+ callerItem = aParentItem;
+ }
+
+ return callerItem.forget();
+}
+
+nsPIDOMWindowOuter*
+nsWindowWatcher::SafeGetWindowByName(const nsAString& aName,
+ bool aForceNoOpener,
+ mozIDOMWindowProxy* aCurrentWindow)
+{
+ if (aForceNoOpener) {
+ if (!aName.LowerCaseEqualsLiteral("_self") &&
+ !aName.LowerCaseEqualsLiteral("_top") &&
+ !aName.LowerCaseEqualsLiteral("_parent")) {
+ // Ignore all other names in the noopener case.
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> startItem;
+ GetWindowTreeItem(aCurrentWindow, getter_AddRefs(startItem));
+
+ nsCOMPtr<nsIDocShellTreeItem> callerItem = GetCallerTreeItem(startItem);
+
+ nsCOMPtr<nsIDocShellTreeItem> foundItem;
+ if (startItem) {
+ startItem->FindItemWithName(aName, nullptr, callerItem,
+ getter_AddRefs(foundItem));
+ } else {
+ FindItemWithName(aName, nullptr, callerItem,
+ getter_AddRefs(foundItem));
+ }
+
+ return foundItem ? foundItem->GetWindow() : nullptr;
+}
+
+/* Fetch the nsIDOMWindow corresponding to the given nsIDocShellTreeItem.
+ This forces the creation of a script context, if one has not already
+ been created. Note it also sets the window's opener to the parent,
+ if applicable -- because it's just convenient, that's all. null aParent
+ is acceptable. */
+nsresult
+nsWindowWatcher::ReadyOpenedDocShellItem(nsIDocShellTreeItem* aOpenedItem,
+ nsPIDOMWindowOuter* aParent,
+ bool aWindowIsNew,
+ bool aForceNoOpener,
+ mozIDOMWindowProxy** aOpenedWindow)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ NS_ENSURE_ARG(aOpenedWindow);
+
+ *aOpenedWindow = 0;
+ nsCOMPtr<nsPIDOMWindowOuter> piOpenedWindow = aOpenedItem->GetWindow();
+ if (piOpenedWindow) {
+ if (!aForceNoOpener) {
+ piOpenedWindow->SetOpenerWindow(aParent, aWindowIsNew); // damnit
+ } else if (aParent && aParent != piOpenedWindow) {
+ MOZ_ASSERT(piOpenedWindow->TabGroup() != aParent->TabGroup(),
+ "If we're forcing no opener, they should be in different tab groups");
+ }
+
+ if (aWindowIsNew) {
+#ifdef DEBUG
+ // Assert that we're not loading things right now. If we are, when
+ // that load completes it will clobber whatever principals we set up
+ // on this new window!
+ nsCOMPtr<nsIDocumentLoader> docloader = do_QueryInterface(aOpenedItem);
+ NS_ASSERTION(docloader, "How can we not have a docloader here?");
+
+ nsCOMPtr<nsIChannel> chan;
+ docloader->GetDocumentChannel(getter_AddRefs(chan));
+ NS_ASSERTION(!chan, "Why is there a document channel?");
+#endif
+
+ nsCOMPtr<nsIDocument> doc = piOpenedWindow->GetExtantDoc();
+ if (doc) {
+ doc->SetIsInitialDocument(true);
+ }
+ }
+ rv = CallQueryInterface(piOpenedWindow, aOpenedWindow);
+ }
+ return rv;
+}
+
+// static
+void
+nsWindowWatcher::CalcSizeSpec(const nsACString& aFeatures, SizeSpec& aResult)
+{
+ // Parse position spec, if any, from aFeatures
+ bool present;
+ int32_t temp;
+
+ present = false;
+ if ((temp = WinHasOption(aFeatures, "left", 0, &present)) || present) {
+ aResult.mLeft = temp;
+ } else if ((temp = WinHasOption(aFeatures, "screenX", 0, &present)) ||
+ present) {
+ aResult.mLeft = temp;
+ }
+ aResult.mLeftSpecified = present;
+
+ present = false;
+ if ((temp = WinHasOption(aFeatures, "top", 0, &present)) || present) {
+ aResult.mTop = temp;
+ } else if ((temp = WinHasOption(aFeatures, "screenY", 0, &present)) ||
+ present) {
+ aResult.mTop = temp;
+ }
+ aResult.mTopSpecified = present;
+
+ // Parse size spec, if any. Chrome size overrides content size.
+ if ((temp = WinHasOption(aFeatures, "outerWidth", INT32_MIN, nullptr))) {
+ if (temp == INT32_MIN) {
+ aResult.mUseDefaultWidth = true;
+ } else {
+ aResult.mOuterWidth = temp;
+ }
+ aResult.mOuterWidthSpecified = true;
+ } else if ((temp = WinHasOption(aFeatures, "width", INT32_MIN, nullptr)) ||
+ (temp = WinHasOption(aFeatures, "innerWidth", INT32_MIN,
+ nullptr))) {
+ if (temp == INT32_MIN) {
+ aResult.mUseDefaultWidth = true;
+ } else {
+ aResult.mInnerWidth = temp;
+ }
+ aResult.mInnerWidthSpecified = true;
+ }
+
+ if ((temp = WinHasOption(aFeatures, "outerHeight", INT32_MIN, nullptr))) {
+ if (temp == INT32_MIN) {
+ aResult.mUseDefaultHeight = true;
+ } else {
+ aResult.mOuterHeight = temp;
+ }
+ aResult.mOuterHeightSpecified = true;
+ } else if ((temp = WinHasOption(aFeatures, "height", INT32_MIN,
+ nullptr)) ||
+ (temp = WinHasOption(aFeatures, "innerHeight", INT32_MIN,
+ nullptr))) {
+ if (temp == INT32_MIN) {
+ aResult.mUseDefaultHeight = true;
+ } else {
+ aResult.mInnerHeight = temp;
+ }
+ aResult.mInnerHeightSpecified = true;
+ }
+}
+
+/* Size and position a new window according to aSizeSpec. This method
+ is assumed to be called after the window has already been given
+ a default position and size; thus its current position and size are
+ accurate defaults. The new window is made visible at method end.
+ @param aTreeOwner
+ The top-level nsIDocShellTreeOwner of the newly opened window.
+ @param aParent (optional)
+ The parent window from which to inherit zoom factors from if
+ aOpenerFullZoom is none.
+ @param aIsCallerChrome
+ True if the code requesting the new window is privileged.
+ @param aSizeSpec
+ The size that the new window should be.
+ @param aOpenerFullZoom
+ If not nothing, a zoom factor to scale the content to.
+*/
+void
+nsWindowWatcher::SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner,
+ mozIDOMWindowProxy* aParent,
+ bool aIsCallerChrome,
+ const SizeSpec& aSizeSpec,
+ Maybe<float> aOpenerFullZoom)
+{
+ // We should only be sizing top-level windows if we're in the parent
+ // process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // position and size of window
+ int32_t left = 0, top = 0, width = 100, height = 100;
+ // difference between chrome and content size
+ int32_t chromeWidth = 0, chromeHeight = 0;
+ // whether the window size spec refers to chrome or content
+ bool sizeChromeWidth = true, sizeChromeHeight = true;
+
+ // get various interfaces for aDocShellItem, used throughout this method
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(aTreeOwner));
+ if (!treeOwnerAsWin) { // we'll need this to actually size the docshell
+ return;
+ }
+
+ double openerZoom = aOpenerFullZoom.valueOr(1.0);
+ if (aParent && aOpenerFullZoom.isNothing()) {
+ nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(aParent);
+ if (nsIDocument* doc = piWindow->GetDoc()) {
+ if (nsIPresShell* shell = doc->GetShell()) {
+ if (nsPresContext* presContext = shell->GetPresContext()) {
+ openerZoom = presContext->GetFullZoom();
+ }
+ }
+ }
+ }
+
+ double scale = 1.0;
+ treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale);
+
+ /* The current position and size will be unchanged if not specified
+ (and they fit entirely onscreen). Also, calculate the difference
+ between chrome and content sizes on aDocShellItem's window.
+ This latter point becomes important if chrome and content
+ specifications are mixed in aFeatures, and when bringing the window
+ back from too far off the right or bottom edges of the screen. */
+
+ treeOwnerAsWin->GetPositionAndSize(&left, &top, &width, &height);
+ left = NSToIntRound(left / scale);
+ top = NSToIntRound(top / scale);
+ width = NSToIntRound(width / scale);
+ height = NSToIntRound(height / scale);
+ {
+ int32_t contentWidth, contentHeight;
+ bool hasPrimaryContent = false;
+ aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent);
+ if (hasPrimaryContent) {
+ aTreeOwner->GetPrimaryContentSize(&contentWidth, &contentHeight);
+ } else {
+ aTreeOwner->GetRootShellSize(&contentWidth, &contentHeight);
+ }
+ chromeWidth = width - contentWidth;
+ chromeHeight = height - contentHeight;
+ }
+
+ // Set up left/top
+ if (aSizeSpec.mLeftSpecified) {
+ left = NSToIntRound(aSizeSpec.mLeft * openerZoom);
+ }
+
+ if (aSizeSpec.mTopSpecified) {
+ top = NSToIntRound(aSizeSpec.mTop * openerZoom);
+ }
+
+ // Set up width
+ if (aSizeSpec.mOuterWidthSpecified) {
+ if (!aSizeSpec.mUseDefaultWidth) {
+ width = NSToIntRound(aSizeSpec.mOuterWidth * openerZoom);
+ } // Else specified to default; just use our existing width
+ } else if (aSizeSpec.mInnerWidthSpecified) {
+ sizeChromeWidth = false;
+ if (aSizeSpec.mUseDefaultWidth) {
+ width = width - chromeWidth;
+ } else {
+ width = NSToIntRound(aSizeSpec.mInnerWidth * openerZoom);
+ }
+ }
+
+ // Set up height
+ if (aSizeSpec.mOuterHeightSpecified) {
+ if (!aSizeSpec.mUseDefaultHeight) {
+ height = NSToIntRound(aSizeSpec.mOuterHeight * openerZoom);
+ } // Else specified to default; just use our existing height
+ } else if (aSizeSpec.mInnerHeightSpecified) {
+ sizeChromeHeight = false;
+ if (aSizeSpec.mUseDefaultHeight) {
+ height = height - chromeHeight;
+ } else {
+ height = NSToIntRound(aSizeSpec.mInnerHeight * openerZoom);
+ }
+ }
+
+ bool positionSpecified = aSizeSpec.PositionSpecified();
+
+ // Check security state for use in determing window dimensions
+ bool enabled = false;
+ if (aIsCallerChrome) {
+ // Only enable special priveleges for chrome when chrome calls
+ // open() on a chrome window
+ nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(aParent));
+ enabled = !aParent || chromeWin;
+ }
+
+ if (!enabled) {
+ // Security check failed. Ensure all args meet minimum reqs.
+
+ int32_t oldTop = top, oldLeft = left;
+
+ // We'll also need the screen dimensions
+ nsCOMPtr<nsIScreen> screen;
+ nsCOMPtr<nsIScreenManager> screenMgr(
+ do_GetService("@mozilla.org/gfx/screenmanager;1"));
+ if (screenMgr)
+ screenMgr->ScreenForRect(left, top, width, height,
+ getter_AddRefs(screen));
+ if (screen) {
+ int32_t screenLeft, screenTop, screenWidth, screenHeight;
+ int32_t winWidth = width + (sizeChromeWidth ? 0 : chromeWidth),
+ winHeight = height + (sizeChromeHeight ? 0 : chromeHeight);
+
+ // Get screen dimensions (in device pixels)
+ screen->GetAvailRect(&screenLeft, &screenTop, &screenWidth,
+ &screenHeight);
+ // Convert them to CSS pixels
+ screenLeft = NSToIntRound(screenLeft / scale);
+ screenTop = NSToIntRound(screenTop / scale);
+ screenWidth = NSToIntRound(screenWidth / scale);
+ screenHeight = NSToIntRound(screenHeight / scale);
+
+ if (aSizeSpec.SizeSpecified()) {
+ /* Unlike position, force size out-of-bounds check only if
+ size actually was specified. Otherwise, intrinsically sized
+ windows are broken. */
+ if (height < 100) {
+ height = 100;
+ winHeight = height + (sizeChromeHeight ? 0 : chromeHeight);
+ }
+ if (winHeight > screenHeight) {
+ height = screenHeight - (sizeChromeHeight ? 0 : chromeHeight);
+ }
+ if (width < 100) {
+ width = 100;
+ winWidth = width + (sizeChromeWidth ? 0 : chromeWidth);
+ }
+ if (winWidth > screenWidth) {
+ width = screenWidth - (sizeChromeWidth ? 0 : chromeWidth);
+ }
+ }
+
+ if (left + winWidth > screenLeft + screenWidth ||
+ left + winWidth < left) {
+ left = screenLeft + screenWidth - winWidth;
+ }
+ if (left < screenLeft) {
+ left = screenLeft;
+ }
+ if (top + winHeight > screenTop + screenHeight || top + winHeight < top) {
+ top = screenTop + screenHeight - winHeight;
+ }
+ if (top < screenTop) {
+ top = screenTop;
+ }
+ if (top != oldTop || left != oldLeft) {
+ positionSpecified = true;
+ }
+ }
+ }
+
+ // size and position the window
+
+ if (positionSpecified) {
+ // Get the scale factor appropriate for the screen we're actually
+ // positioning on.
+ nsCOMPtr<nsIScreen> screen;
+ nsCOMPtr<nsIScreenManager> screenMgr(
+ do_GetService("@mozilla.org/gfx/screenmanager;1"));
+ if (screenMgr) {
+ screenMgr->ScreenForRect(left, top, 1, 1, getter_AddRefs(screen));
+ }
+ if (screen) {
+ double cssToDevPixScale, desktopToDevPixScale;
+ screen->GetDefaultCSSScaleFactor(&cssToDevPixScale);
+ screen->GetContentsScaleFactor(&desktopToDevPixScale);
+ double cssToDesktopScale = cssToDevPixScale / desktopToDevPixScale;
+ int32_t screenLeft, screenTop, screenWd, screenHt;
+ screen->GetRectDisplayPix(&screenLeft, &screenTop, &screenWd, &screenHt);
+ // Adjust by desktop-pixel origin of the target screen when scaling
+ // to convert from per-screen CSS-px coords to global desktop coords.
+ treeOwnerAsWin->SetPositionDesktopPix(
+ (left - screenLeft) * cssToDesktopScale + screenLeft,
+ (top - screenTop) * cssToDesktopScale + screenTop);
+ } else {
+ // Couldn't find screen? This shouldn't happen.
+ treeOwnerAsWin->SetPosition(left * scale, top * scale);
+ }
+ // This shouldn't be necessary, given the screen check above, but in case
+ // moving the window didn't put it where we expected (e.g. due to issues
+ // at the widget level, or whatever), let's re-fetch the scale factor for
+ // wherever it really ended up
+ treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale);
+ }
+ if (aSizeSpec.SizeSpecified()) {
+ /* Prefer to trust the interfaces, which think in terms of pure
+ chrome or content sizes. If we have a mix, use the chrome size
+ adjusted by the chrome/content differences calculated earlier. */
+ if (!sizeChromeWidth && !sizeChromeHeight) {
+ bool hasPrimaryContent = false;
+ aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent);
+ if (hasPrimaryContent) {
+ aTreeOwner->SetPrimaryContentSize(width * scale, height * scale);
+ } else {
+ aTreeOwner->SetRootShellSize(width * scale, height * scale);
+ }
+ } else {
+ if (!sizeChromeWidth) {
+ width += chromeWidth;
+ }
+ if (!sizeChromeHeight) {
+ height += chromeHeight;
+ }
+ treeOwnerAsWin->SetSize(width * scale, height * scale, false);
+ }
+ }
+ treeOwnerAsWin->SetVisibility(true);
+}
+
+void
+nsWindowWatcher::GetWindowTreeItem(mozIDOMWindowProxy* aWindow,
+ nsIDocShellTreeItem** aResult)
+{
+ *aResult = 0;
+
+ if (aWindow) {
+ nsIDocShell* docshell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
+ if (docshell) {
+ CallQueryInterface(docshell, aResult);
+ }
+ }
+}
+
+void
+nsWindowWatcher::GetWindowTreeOwner(nsPIDOMWindowOuter* aWindow,
+ nsIDocShellTreeOwner** aResult)
+{
+ *aResult = 0;
+
+ nsCOMPtr<nsIDocShellTreeItem> treeItem;
+ GetWindowTreeItem(aWindow, getter_AddRefs(treeItem));
+ if (treeItem) {
+ treeItem->GetTreeOwner(aResult);
+ }
+}
+
+/* static */
+int32_t
+nsWindowWatcher::GetWindowOpenLocation(nsPIDOMWindowOuter* aParent,
+ uint32_t aChromeFlags,
+ bool aCalledFromJS,
+ bool aPositionSpecified,
+ bool aSizeSpecified)
+{
+ bool isFullScreen = aParent->GetFullScreen();
+
+ // Where should we open this?
+ int32_t containerPref;
+ if (NS_FAILED(Preferences::GetInt("browser.link.open_newwindow",
+ &containerPref))) {
+ // We couldn't read the user preference, so fall back on the default.
+ return nsIBrowserDOMWindow::OPEN_NEWTAB;
+ }
+
+ bool isDisabledOpenNewWindow =
+ isFullScreen &&
+ Preferences::GetBool("browser.link.open_newwindow.disabled_in_fullscreen");
+
+ if (isDisabledOpenNewWindow &&
+ (containerPref == nsIBrowserDOMWindow::OPEN_NEWWINDOW)) {
+ containerPref = nsIBrowserDOMWindow::OPEN_NEWTAB;
+ }
+
+ if (containerPref != nsIBrowserDOMWindow::OPEN_NEWTAB &&
+ containerPref != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) {
+ // Just open a window normally
+ return nsIBrowserDOMWindow::OPEN_NEWWINDOW;
+ }
+
+ if (aCalledFromJS) {
+ /* Now check our restriction pref. The restriction pref is a power-user's
+ fine-tuning pref. values:
+ 0: no restrictions - divert everything
+ 1: don't divert window.open at all
+ 2: don't divert window.open with features
+ */
+ int32_t restrictionPref =
+ Preferences::GetInt("browser.link.open_newwindow.restriction", 2);
+ if (restrictionPref < 0 || restrictionPref > 2) {
+ restrictionPref = 2; // Sane default behavior
+ }
+
+ if (isDisabledOpenNewWindow) {
+ // In browser fullscreen, the window should be opened
+ // in the current window with no features (see bug 803675)
+ restrictionPref = 0;
+ }
+
+ if (restrictionPref == 1) {
+ return nsIBrowserDOMWindow::OPEN_NEWWINDOW;
+ }
+
+ if (restrictionPref == 2) {
+ // Only continue if there are no size/position features and no special
+ // chrome flags - with the exception of the remoteness and private flags,
+ // which might have been automatically flipped by Gecko.
+ int32_t uiChromeFlags = aChromeFlags;
+ uiChromeFlags &= ~(nsIWebBrowserChrome::CHROME_REMOTE_WINDOW |
+ nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW |
+ nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW |
+ nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME);
+ if (uiChromeFlags != nsIWebBrowserChrome::CHROME_ALL ||
+ aPositionSpecified || aSizeSpecified) {
+ return nsIBrowserDOMWindow::OPEN_NEWWINDOW;
+ }
+ }
+ }
+
+ return containerPref;
+}
diff --git a/components/windowwatcher/src/nsWindowWatcher.h b/components/windowwatcher/src/nsWindowWatcher.h
new file mode 100644
index 000000000..e09eadb31
--- /dev/null
+++ b/components/windowwatcher/src/nsWindowWatcher.h
@@ -0,0 +1,155 @@
+/* -*- 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/. */
+
+#ifndef __nsWindowWatcher_h__
+#define __nsWindowWatcher_h__
+
+// {a21bfa01-f349-4394-a84c-8de5cf0737d0}
+#define NS_WINDOWWATCHER_CID \
+ {0xa21bfa01, 0xf349, 0x4394, {0xa8, 0x4c, 0x8d, 0xe5, 0xcf, 0x7, 0x37, 0xd0}}
+
+#include "nsCOMPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Maybe.h"
+#include "nsIWindowCreator.h" // for stupid compilers
+#include "nsIWindowWatcher.h"
+#include "nsIPromptFactory.h"
+#include "nsITabParent.h"
+#include "nsPIWindowWatcher.h"
+#include "nsTArray.h"
+
+class nsIURI;
+class nsIDocShellTreeItem;
+class nsIDocShellTreeOwner;
+class nsPIDOMWindowOuter;
+class nsWatcherWindowEnumerator;
+class nsPromptService;
+struct nsWatcherWindowEntry;
+struct SizeSpec;
+
+class nsWindowWatcher
+ : public nsIWindowWatcher
+ , public nsPIWindowWatcher
+ , public nsIPromptFactory
+{
+ friend class nsWatcherWindowEnumerator;
+
+public:
+ nsWindowWatcher();
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIWINDOWWATCHER
+ NS_DECL_NSPIWINDOWWATCHER
+ NS_DECL_NSIPROMPTFACTORY
+
+ static int32_t GetWindowOpenLocation(nsPIDOMWindowOuter* aParent,
+ uint32_t aChromeFlags,
+ bool aCalledFromJS,
+ bool aPositionSpecified,
+ bool aSizeSpecified);
+
+protected:
+ virtual ~nsWindowWatcher();
+
+ friend class nsPromptService;
+ bool AddEnumerator(nsWatcherWindowEnumerator* aEnumerator);
+ bool RemoveEnumerator(nsWatcherWindowEnumerator* aEnumerator);
+
+ nsWatcherWindowEntry* FindWindowEntry(mozIDOMWindowProxy* aWindow);
+ nsresult RemoveWindow(nsWatcherWindowEntry* aInfo);
+
+ // Get the caller tree item. Look on the JS stack, then fall back
+ // to the parent if there's nothing there.
+ already_AddRefed<nsIDocShellTreeItem> GetCallerTreeItem(
+ nsIDocShellTreeItem* aParentItem);
+
+ // Unlike GetWindowByName this will look for a caller on the JS
+ // stack, and then fall back on aCurrentWindow if it can't find one.
+ // It also knows to not look for things if aForceNoOpener is set.
+ nsPIDOMWindowOuter* SafeGetWindowByName(const nsAString& aName,
+ bool aForceNoOpener,
+ mozIDOMWindowProxy* aCurrentWindow);
+
+ // Just like OpenWindowJS, but knows whether it got called via OpenWindowJS
+ // (which means called from script) or called via OpenWindow.
+ nsresult OpenWindowInternal(mozIDOMWindowProxy* aParent,
+ const char* aUrl,
+ const char* aName,
+ const char* aFeatures,
+ bool aCalledFromJS,
+ bool aDialog,
+ bool aNavigate,
+ nsIArray* aArgv,
+ bool aIsPopupSpam,
+ bool aForceNoOpener,
+ nsIDocShellLoadInfo* aLoadInfo,
+ mozIDOMWindowProxy** aResult);
+
+ static nsresult URIfromURL(const char* aURL,
+ mozIDOMWindowProxy* aParent,
+ nsIURI** aURI);
+
+ static uint32_t CalculateChromeFlagsForChild(const nsACString& aFeaturesStr);
+
+ static uint32_t CalculateChromeFlagsForParent(mozIDOMWindowProxy* aParent,
+ const nsACString& aFeaturesStr,
+ bool aDialog,
+ bool aChromeURL,
+ bool aHasChromeParent,
+ bool aCalledFromJS);
+
+ static int32_t WinHasOption(const nsACString& aOptions, const char* aName,
+ int32_t aDefault, bool* aPresenceFlag);
+ /* Compute the right SizeSpec based on aFeatures */
+ static void CalcSizeSpec(const nsACString& aFeatures, SizeSpec& aResult);
+ static nsresult ReadyOpenedDocShellItem(nsIDocShellTreeItem* aOpenedItem,
+ nsPIDOMWindowOuter* aParent,
+ bool aWindowIsNew,
+ bool aForceNoOpener,
+ mozIDOMWindowProxy** aOpenedWindow);
+ static void SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner,
+ mozIDOMWindowProxy* aParent,
+ bool aIsCallerChrome,
+ const SizeSpec& aSizeSpec,
+ mozilla::Maybe<float> aOpenerFullZoom =
+ mozilla::Nothing());
+ static void GetWindowTreeItem(mozIDOMWindowProxy* aWindow,
+ nsIDocShellTreeItem** aResult);
+ static void GetWindowTreeOwner(nsPIDOMWindowOuter* aWindow,
+ nsIDocShellTreeOwner** aResult);
+
+private:
+ nsresult CreateChromeWindow(const nsACString& aFeatures,
+ nsIWebBrowserChrome* aParentChrome,
+ uint32_t aChromeFlags,
+ uint32_t aContextFlags,
+ nsITabParent* aOpeningTabParent,
+ mozIDOMWindowProxy* aOpener,
+ nsIWebBrowserChrome** aResult);
+
+ void MaybeDisablePersistence(const nsACString& aFeatures,
+ nsIDocShellTreeOwner* aTreeOwner);
+
+ static uint32_t CalculateChromeFlagsHelper(uint32_t aInitialFlags,
+ const nsACString& aFeatures,
+ bool &presenceFlag,
+ bool aDialog = false,
+ bool aHasChromeParent = false,
+ bool aChromeURL = false);
+ static uint32_t EnsureFlagsSafeForContent(uint32_t aChromeFlags,
+ bool aChromeURL = false);
+
+protected:
+ nsTArray<nsWatcherWindowEnumerator*> mEnumeratorList;
+ nsWatcherWindowEntry* mOldestWindow;
+ mozilla::Mutex mListLock;
+
+ nsCOMPtr<nsIWindowCreator> mWindowCreator;
+};
+
+#endif
diff --git a/components/workerloader/moz.build b/components/workerloader/moz.build
new file mode 100644
index 000000000..82b6a758c
--- /dev/null
+++ b/components/workerloader/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+EXTRA_JS_MODULES.workers += ['require.js']
diff --git a/components/workerloader/require.js b/components/workerloader/require.js
new file mode 100644
index 000000000..223132f7e
--- /dev/null
+++ b/components/workerloader/require.js
@@ -0,0 +1,161 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/**
+ * Implementation of a CommonJS module loader for workers.
+ *
+ * Use:
+ * // in the .js file loaded by the constructor of the worker
+ * importScripts("resource://gre/modules/workers/require.js");
+ * let module = require("resource://gre/modules/worker/myModule.js");
+ *
+ * // in myModule.js
+ * // Load dependencies
+ * let SimpleTest = require("resource://gre/modules/workers/SimpleTest.js");
+ * let Logger = require("resource://gre/modules/workers/Logger.js");
+ *
+ * // Define things that will not be exported
+ * let someValue = // ...
+ *
+ * // Export symbols
+ * exports.foo = // ...
+ * exports.bar = // ...
+ *
+ *
+ * Note #1:
+ * Properties |fileName| and |stack| of errors triggered from a module
+ * contain file names that do not correspond to human-readable module paths.
+ * Human readers should rather use properties |moduleName| and |moduleStack|.
+ *
+ * Note #2:
+ * The current version of |require()| only accepts absolute URIs.
+ *
+ * Note #3:
+ * By opposition to some other module loader implementations, this module
+ * loader does not enforce separation of global objects. Consequently, if
+ * a module modifies a global object (e.g. |String.prototype|), all other
+ * modules in the same worker may be affected.
+ */
+
+
+(function(exports) {
+ "use strict";
+
+ if (exports.require) {
+ // Avoid double-imports
+ return;
+ }
+
+ // Simple implementation of |require|
+ let require = (function() {
+
+ /**
+ * Mapping from module paths to module exports.
+ *
+ * @keys {string} The absolute path to a module.
+ * @values {object} The |exports| objects for that module.
+ */
+ let modules = new Map();
+
+ /**
+ * A human-readable version of |stack|.
+ *
+ * @type {string}
+ */
+ Object.defineProperty(Error.prototype, "moduleStack",
+ {
+ get: function() {
+ return this.stack;
+ }
+ });
+ /**
+ * A human-readable version of |fileName|.
+ *
+ * @type {string}
+ */
+ Object.defineProperty(Error.prototype, "moduleName",
+ {
+ get: function() {
+ let match = this.stack.match(/\@(.*):.*:/);
+ if (match) {
+ return match[1];
+ }
+ return "(unknown module)";
+ }
+ });
+
+ /**
+ * Import a module
+ *
+ * @param {string} path The path to the module.
+ * @return {*} An object containing the properties exported by the module.
+ */
+ return function require(path) {
+ if (typeof path != "string" || path.indexOf("://") == -1) {
+ throw new TypeError("The argument to require() must be a string uri, got " + path);
+ }
+ // Automatically add ".js" if there is no extension
+ let uri;
+ if (path.lastIndexOf(".") <= path.lastIndexOf("/")) {
+ uri = path + ".js";
+ } else {
+ uri = path;
+ }
+
+ // Exports provided by the module
+ let exports = Object.create(null);
+
+ // Identification of the module
+ let module = {
+ id: path,
+ uri: uri,
+ exports: exports
+ };
+
+ // Make module available immediately
+ // (necessary in case of circular dependencies)
+ if (modules.has(path)) {
+ return modules.get(path).exports;
+ }
+ modules.set(path, module);
+
+ try {
+ // Load source of module, synchronously
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", uri, false);
+ xhr.responseType = "text";
+ xhr.send();
+
+
+ let source = xhr.responseText;
+ if (source == "") {
+ // There doesn't seem to be a better way to detect that the file couldn't be found
+ throw new Error("Could not find module " + path);
+ }
+ let code = new Function("exports", "require", "module",
+ source + "\n//# sourceURL=" + uri + "\n"
+ );
+ code(exports, require, module);
+ } catch (ex) {
+ // Module loading has failed, exports should not be made available
+ // after all.
+ modules.delete(path);
+ throw ex;
+ }
+
+ Object.freeze(module.exports);
+ Object.freeze(module);
+ return module.exports;
+ };
+ })();
+
+ Object.freeze(require);
+
+ Object.defineProperty(exports, "require", {
+ value: require,
+ enumerable: true,
+ configurable: false
+ });
+})(this);
diff --git a/components/xmlparser/moz.build b/components/xmlparser/moz.build
new file mode 100644
index 000000000..4a17c2266
--- /dev/null
+++ b/components/xmlparser/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += [
+ 'public/nsIMozSAXXMLDeclarationHandler.idl',
+ 'public/nsISAXAttributes.idl',
+ 'public/nsISAXContentHandler.idl',
+ 'public/nsISAXDTDHandler.idl',
+ 'public/nsISAXErrorHandler.idl',
+ 'public/nsISAXLexicalHandler.idl',
+ 'public/nsISAXLocator.idl',
+ 'public/nsISAXMutableAttributes.idl',
+ 'public/nsISAXXMLFilter.idl',
+ 'public/nsISAXXMLReader.idl',
+]
+
+EXPORTS += [
+ 'src/nsSAXAttributes.h',
+ 'src/nsSAXLocator.h',
+ 'src/nsSAXXMLReader.h',
+]
+
+SOURCES += [
+ 'src/nsSAXAttributes.cpp',
+ 'src/nsSAXLocator.cpp',
+ 'src/nsSAXXMLReader.cpp',
+]
+
+XPIDL_MODULE = 'saxparser'
+FINAL_LIBRARY = 'xul'
diff --git a/components/xmlparser/public/nsIMozSAXXMLDeclarationHandler.idl b/components/xmlparser/public/nsIMozSAXXMLDeclarationHandler.idl
new file mode 100644
index 000000000..2e9c0d6d6
--- /dev/null
+++ b/components/xmlparser/public/nsIMozSAXXMLDeclarationHandler.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 "nsISupports.idl"
+
+/* This is a helper for the XML declaration in a document:
+ * <?xml version='1.0' encoding='UTF-8' standalone='yes'?>
+ */
+
+[scriptable, function, uuid(c0e461cb-0e5e-284c-b97d-cffeec467eba)]
+interface nsIMozSAXXMLDeclarationHandler: nsISupports {
+ void handleXMLDeclaration(in AString version, in AString encoding, in boolean standalone);
+};
diff --git a/components/xmlparser/public/nsISAXAttributes.idl b/components/xmlparser/public/nsISAXAttributes.idl
new file mode 100644
index 000000000..c9b0a8a7e
--- /dev/null
+++ b/components/xmlparser/public/nsISAXAttributes.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Interface for a list of XML attributes.
+ *
+ * This interface allows access to a list of attributes in
+ * three different ways:
+ *
+ * 1.) by attribute index;
+ * 2.) by Namespace-qualified name; or
+ * 3.) by XML qualified name.
+ *
+ * The list will not contain attributes that were declared #IMPLIED
+ * but not specified in the start tag. It will also not contain
+ * attributes used as Namespace declarations (xmlns*) unless the
+ * http://xml.org/sax/features/namespace-prefixes feature
+ * is set to true (it is false by default).
+ *
+ * The order of attributes in the list is unspecified.
+ */
+[scriptable, uuid(e347005e-6cd0-11da-be43-001422106990)]
+interface nsISAXAttributes : nsISupports
+{
+ /**
+ * Look up the index of an attribute by Namespace name.
+ * @param uri The Namespace URI, or the empty string
+ * if the name has no Namespace URI.
+ * @param localName The attribute's local name.
+ * @return The index of the attribute, or -1
+ * if it does not appear in the list.
+ */
+ long getIndexFromName(in AString uri, in AString localName);
+
+ /**
+ * Look up the index of an attribute by XML qualified name.
+ * @param qName The qualified name.
+ * @return The index of the attribute, or -1
+ * if it does not appear in the list.
+ */
+ long getIndexFromQName(in AString qName);
+
+ /**
+ * Return the number of attributes in the list. Once you know the
+ * number of attributes, you can iterate through the list.
+ *
+ * @return The number of attributes in the list.
+ */
+ readonly attribute long length;
+
+ /**
+ * Look up an attribute's local name by index.
+ * @param index The attribute index (zero-based).
+ * @return The local name, or null if the index is out of range.
+ */
+ AString getLocalName(in unsigned long index);
+
+ /**
+ * Look up an attribute's XML qualified name by index.
+ * @param index The attribute index (zero-based).
+ * @return The XML qualified name, or the empty string if none is
+ * available, or null if the index is out of range.
+ */
+ AString getQName(in unsigned long index);
+
+ /**
+ * Look up an attribute's type by index. The attribute type is one
+ * of the strings "CDATA", "ID", "IDREF", "IDREFS", "NMTOKEN",
+ * "NMTOKENS", "ENTITY", "ENTITIES", or "NOTATION" (always in upper
+ * case). If the parser has not read a declaration for the
+ * attribute, or if the parser does not report attribute types, then
+ * it must return the value "CDATA" as stated in the XML 1.0
+ * Recommendation (clause 3.3.3, "Attribute-Value
+ * Normalization"). For an enumerated attribute that is not a
+ * notation, the parser will report the type as "NMTOKEN".
+ *
+ * @param index The attribute index (zero-based).
+ * @return The attribute's type as a string, or null if the index is
+ * out of range.
+ */
+ AString getType(in unsigned long index);
+
+ /**
+ * Look up an attribute's type by Namespace name.
+ * @param uri The Namespace URI, or the empty string
+ * if the name has no Namespace URI.
+ * @param localName The attribute's local name.
+ * @return The attribute type as a string, or null if the attribute
+ * is not in the list.
+ */
+ AString getTypeFromName(in AString uri, in AString localName);
+
+ /**
+ * Look up an attribute's type by XML qualified name.
+ * @param qName The qualified name.
+ * @return The attribute type as a string, or null if the attribute
+ * is not in the list.
+ */
+ AString getTypeFromQName(in AString qName);
+
+ /**
+ * Look up an attribute's Namespace URI by index.
+ * @param index The attribute index (zero-based).
+ * @return The Namespace URI, or the empty string if none is available,
+ * or null if the index is out of range.
+ */
+ AString getURI(in unsigned long index);
+
+ /**
+ * Look up an attribute's value by index. If the attribute value is
+ * a list of tokens (IDREFS, ENTITIES, or NMTOKENS), the tokens will
+ * be concatenated into a single string with each token separated by
+ * a single space.
+ *
+ * @param index The attribute index (zero-based).
+ * @return The attribute's value as a string, or null if the index is
+ * out of range.
+ */
+ AString getValue(in unsigned long index);
+
+ /**
+ * Look up an attribute's value by Namespace name. If the attribute
+ * value is a list of tokens (IDREFS, ENTITIES, or NMTOKENS), the
+ * tokens will be concatenated into a single string with each token
+ * separated by a single space.
+ *
+ * @param uri The Namespace URI, or the empty string
+ * if the name has no Namespace URI.
+ * @param localName The attribute's local name.
+ * @return The attribute's value as a string, or null if the attribute is
+ * not in the list.
+ */
+ AString getValueFromName(in AString uri, in AString localName);
+
+ /**
+ * Look up an attribute's value by XML qualified (prefixed) name.
+ * If the attribute value is a list of tokens (IDREFS, ENTITIES, or
+ * NMTOKENS), the tokens will be concatenated into a single string
+ * with each token separated by a single space.
+ *
+ * @param qName The qualified (prefixed) name.
+ * @return The attribute's value as a string, or null if the attribute is
+ * not in the list.
+ */
+ AString getValueFromQName(in AString qName);
+};
diff --git a/components/xmlparser/public/nsISAXContentHandler.idl b/components/xmlparser/public/nsISAXContentHandler.idl
new file mode 100644
index 000000000..43b7e48c5
--- /dev/null
+++ b/components/xmlparser/public/nsISAXContentHandler.idl
@@ -0,0 +1,225 @@
+/* -*- 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 nsISAXAttributes;
+
+/**
+ * Receive notification of the logical content of a document.
+ *
+ * This is the main interface that most SAX applications implement: if
+ * the application needs to be informed of basic parsing events, it
+ * implements this interface and registers an instance with the SAX
+ * parser. The parser uses the instance to report basic
+ * document-related events like the start and end of elements and
+ * character data.
+ *
+ * The order of events in this interface is very important, and
+ * mirrors the order of information in the document itself. For
+ * example, all of an element's content (character data, processing
+ * instructions, and/or subelements) will appear, in order, between
+ * the startElement event and the corresponding endElement event.
+ */
+[scriptable, uuid(2a99c757-dfee-4806-bff3-f721440412e0)]
+interface nsISAXContentHandler : nsISupports
+{
+ /**
+ * Receive notification of the beginning of a document.
+ *
+ * The SAX parser will invoke this method only once, before any
+ * other event callbacks.
+ */
+ void startDocument();
+
+ /**
+ * Receive notification of the end of a document.
+ *
+ * There is an apparent contradiction between the documentation for
+ * this method and the documentation for ErrorHandler.fatalError().
+ * Until this ambiguity is resolved in a future major release,
+ * clients should make no assumptions about whether endDocument()
+ * will or will not be invoked when the parser has reported a
+ * fatalError() or thrown an exception.
+ *
+ * The SAX parser will invoke this method only once, and it will be
+ * the last method invoked during the parse. The parser shall not
+ * invoke this method until it has either abandoned parsing (because
+ * of an unrecoverable error) or reached the end of input.
+ */
+ void endDocument();
+
+ /**
+ * Receive notification of the beginning of an element.
+ *
+ * The Parser will invoke this method at the beginning of every
+ * element in the XML document; there will be a corresponding
+ * endElement event for every startElement event (even when the
+ * element is empty). All of the element's content will be reported,
+ * in order, before the corresponding endElement event.
+ *
+ * This event allows up to three name components for each element:
+ *
+ * 1.) the Namespace URI;
+ * 2.) the local name; and
+ * 3.) the qualified (prefixed) name.
+ *
+ * Any or all of these may be provided, depending on the values of
+ * the http://xml.org/sax/features/namespaces and the
+ * http://xml.org/sax/features/namespace-prefixes properties:
+ *
+ * The Namespace URI and local name are required when the namespaces
+ * property is true (the default), and are optional when the
+ * namespaces property is false (if one is specified, both must be);
+ *
+ * The qualified name is required when the namespace-prefixes
+ * property is true, and is optional when the namespace-prefixes
+ * property is false (the default).
+ *
+ * Note that the attribute list provided will contain only
+ * attributes with explicit values (specified or defaulted):
+ * #IMPLIED attributes will be omitted. The attribute list will
+ * contain attributes used for Namespace declarations (xmlns*
+ * attributes) only if the
+ * http://xml.org/sax/features/namespace-prefixes property is true
+ * (it is false by default, and support for a true value is
+ * optional).
+ *
+ * @param uri the Namespace URI, or the empty string if the
+ * element has no Namespace URI or if Namespace
+ * processing is not being performed
+ * @param localName the local name (without prefix), or the
+ * empty string if Namespace processing is not being
+ * performed
+ * @param qName the qualified name (with prefix), or the
+ * empty string if qualified names are not available
+ * @param atts the attributes attached to the element. If
+ * there are no attributes, it shall be an empty
+ * SAXAttributes object. The value of this object after
+ * startElement returns is undefined
+ */
+ void startElement(in AString uri, in AString localName,
+ in AString qName, in nsISAXAttributes attributes);
+
+ /**
+ * Receive notification of the end of an element.
+ *
+ * The SAX parser will invoke this method at the end of every
+ * element in the XML document; there will be a corresponding
+ * startElement event for every endElement event (even when the
+ * element is empty).
+ *
+ * For information on the names, see startElement.
+ *
+ * @param uri the Namespace URI, or the empty string if the
+ * element has no Namespace URI or if Namespace
+ * processing is not being performed
+ * @param localName the local name (without prefix), or the
+ * empty string if Namespace processing is not being
+ * performed
+ * @param qName the qualified XML name (with prefix), or the
+ * empty string if qualified names are not available
+ */
+ void endElement(in AString uri, in AString localName, in AString qName);
+
+ /**
+ * Receive notification of character data.
+ *
+ * The Parser will call this method to report each chunk of
+ * character data. SAX parsers may return all contiguous character
+ * data in a single chunk, or they may split it into several chunks;
+ * however, all of the characters in any single event must come from
+ * the same external entity so that the Locator provides useful
+ * information.
+ *
+ * Note that some parsers will report whitespace in element
+ * content using the ignorableWhitespace method rather than this one
+ * (validating parsers must do so).
+ *
+ * @param value the characters from the XML document
+ */
+ void characters(in AString value);
+
+ /**
+ * Receive notification of a processing instruction.
+ *
+ * The Parser will invoke this method once for each processing
+ * instruction found: note that processing instructions may occur
+ * before or after the main document element.
+ *
+ * A SAX parser must never report an XML declaration (XML 1.0,
+ * section 2.8) or a text declaration (XML 1.0, section 4.3.1) using
+ * this method.
+ *
+ * @param target the processing instruction target
+ * @param data the processing instruction data, or null if
+ * none was supplied. The data does not include any
+ * whitespace separating it from the target
+ */
+ void processingInstruction(in AString target, in AString data);
+
+ /**
+ * Receive notification of ignorable whitespace in element content.
+ *
+ * Validating Parsers must use this method to report each chunk of
+ * whitespace in element content (see the W3C XML 1.0
+ * recommendation, section 2.10): non-validating parsers may also
+ * use this method if they are capable of parsing and using content
+ * models.
+ *
+ * SAX parsers may return all contiguous whitespace in a single
+ * chunk, or they may split it into several chunks; however, all of
+ * the characters in any single event must come from the same
+ * external entity, so that the Locator provides useful information.
+ *
+ * @param whitespace the characters from the XML document
+ */
+ void ignorableWhitespace(in AString whitespace);
+
+ /**
+ * Begin the scope of a prefix-URI Namespace mapping.
+ *
+ * The information from this event is not necessary for normal
+ * Namespace processing: the SAX XML reader will automatically
+ * replace prefixes for element and attribute names when the
+ * http://xml.org/sax/features/namespaces feature is
+ * true (the default).
+ *
+ * There are cases, however, when applications need to use prefixes
+ * in character data or in attribute values, where they cannot
+ * safely be expanded automatically; the start/endPrefixMapping
+ * event supplies the information to the application to expand
+ * prefixes in those contexts itself, if necessary.
+ *
+ * Note that start/endPrefixMapping events are not guaranteed to be
+ * properly nested relative to each other: all startPrefixMapping
+ * events will occur immediately before the corresponding
+ * startElement event, and all endPrefixMapping events will occur
+ * immediately after the corresponding endElement event, but their
+ * order is not otherwise guaranteed.
+ *
+ * There should never be start/endPrefixMapping events for the
+ * "xml" prefix, since it is predeclared and immutable.
+ *
+ * @param prefix The Namespace prefix being declared. An empty
+ * string is used for the default element namespace,
+ * which has no prefix.
+ * @param uri The Namespace URI the prefix is mapped to.
+ */
+ void startPrefixMapping(in AString prefix, in AString uri);
+
+ /**
+ * End the scope of a prefix-URI mapping.
+ *
+ * See startPrefixMapping for details. These events will always
+ * occur immediately after the corresponding endElement event, but
+ * the order of endPrefixMapping events is not otherwise guaranteed.
+ *
+ * @param prefix The prefix that was being mapped. This is the empty
+ * string when a default mapping scope ends.
+ */
+ void endPrefixMapping(in AString prefix);
+ //XXX documentLocator
+};
diff --git a/components/xmlparser/public/nsISAXDTDHandler.idl b/components/xmlparser/public/nsISAXDTDHandler.idl
new file mode 100644
index 000000000..b4cb51d1b
--- /dev/null
+++ b/components/xmlparser/public/nsISAXDTDHandler.idl
@@ -0,0 +1,77 @@
+/* -*- 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"
+
+/**
+ * Receive notification of basic DTD-related events.
+ *
+ * If a SAX application needs information about notations and
+ * unparsed entities, then the application implements this interface
+ * and registers an instance with the SAX parser using the parser's
+ * setDTDHandler method. The parser uses the instance to report
+ * notation and unparsed entity declarations to the application.
+ *
+ * Note that this interface includes only those DTD events that the
+ * XML recommendation requires processors to report: notation and
+ * unparsed entity declarations.
+ *
+ * The SAX parser may report these events in any order, regardless
+ * of the order in which the notations and unparsed entities were
+ * declared; however, all DTD events must be reported after the
+ * document handler's startDocument event, and before the first
+ * startElement event. (If the LexicalHandler is used, these events
+ * must also be reported before the endDTD event.)
+ */
+[scriptable, uuid(4d01f225-6cc5-11da-be43-001422106990)]
+interface nsISAXDTDHandler : nsISupports {
+
+ /**
+ * Receive notification of a notation declaration event.
+ *
+ * It is up to the application to record the notation for later
+ * reference, if necessary; notations may appear as attribute values
+ * and in unparsed entity declarations, and are sometime used with
+ * processing instruction target names.
+ *
+ * At least one of publicId and systemId must be non-null. If a
+ * system identifier is present, and it is a URL, the SAX parser
+ * must resolve it fully before passing it to the application
+ * through this event.
+ *
+ * There is no guarantee that the notation declaration will be
+ * reported before any unparsed entities that use it.
+ *
+ * @param name The notation name.
+ * @param publicId The notation's public identifier, or null if none was
+ * given.
+ * @param systemId The notation's system identifier, or null if none was
+ * given.
+ */
+ void notationDecl(in AString name,
+ in AString publicId,
+ in AString systemId);
+
+ /**
+ * Receive notification of an unparsed entity declaration event.
+ *
+ * Note that the notation name corresponds to a notation reported
+ * by the notationDecl event. It is up to the application to record
+ * the entity for later reference, if necessary; unparsed entities
+ * may appear as attribute values.
+ *
+ * If the system identifier is a URL, the parser must resolve it
+ * fully before passing it to the application.
+ *
+ * @param name The unparsed entity's name.
+ * @param publicId The entity's public identifier, or null if none was
+ * given.
+ * @param systemId The entity's system identifier, or null if none was
+ * given.
+ * @param notationName The name of the associated notation.
+ */
+ void unparsedEntityDecl(in AString name, in AString publicId,
+ in AString systemId, in AString notationName);
+};
diff --git a/components/xmlparser/public/nsISAXErrorHandler.idl b/components/xmlparser/public/nsISAXErrorHandler.idl
new file mode 100644
index 000000000..ea8af79ce
--- /dev/null
+++ b/components/xmlparser/public/nsISAXErrorHandler.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 nsISAXLocator;
+
+/**
+ * Basic interface for SAX error handlers.
+ *
+ * If a SAX application needs to implement customized error
+ * handling, it must implement this interface and then register an
+ * instance with the XML reader. The parser will then report all
+ * errors and warnings through this interface.
+ *
+ * WARNING: If an application does not register an ErrorHandler,
+ * XML parsing errors will go unreported. In order to detect validity
+ * errors, an ErrorHandler that does something with error() calls must
+ * be registered.
+ *
+ */
+[scriptable, uuid(e02b6693-6cca-11da-be43-001422106990)]
+interface nsISAXErrorHandler: nsISupports {
+
+ /**
+ * Receive notification of a recoverable error.
+ *
+ * This corresponds to the definition of "error" in section 1.2
+ * of the W3C XML 1.0 Recommendation. For example, a validating
+ * parser would use this callback to report the violation of a
+ * validity constraint. The default behaviour is to take no
+ * action.
+ *
+ * The SAX parser must continue to provide normal parsing events
+ * after invoking this method: it should still be possible for the
+ * application to process the document through to the end. If the
+ * application cannot do so, then the parser should report a fatal
+ * error even if the XML recommendation does not require it to do
+ * so.
+ *
+ * Filters may use this method to report other, non-XML errors as
+ * well.
+ *
+ * @param locator The locator object for the error (may be null).
+ * @param error The error message.
+ */
+ void error(in nsISAXLocator locator, in AString error);
+
+ /**
+ * Receive notification of a non-recoverable error.
+ *
+ * There is an apparent contradiction between the documentation
+ * for this method and the documentation for
+ * ContentHandler.endDocument(). Until this ambiguity is resolved in
+ * a future major release, clients should make no assumptions about
+ * whether endDocument() will or will not be invoked when the parser
+ * has reported a fatalError() or thrown an exception.
+ *
+ * This corresponds to the definition of "fatal error" in section
+ * 1.2 of the W3C XML 1.0 Recommendation. For example, a parser
+ * would use this callback to report the violation of a
+ * well-formedness constraint.
+ *
+ * The application must assume that the document is unusable
+ * after the parser has invoked this method, and should continue (if
+ * at all) only for the sake of collecting additional error
+ * messages: in fact, SAX parsers are free to stop reporting any
+ * other events once this method has been invoked.
+ *
+ * @param locator The locator object for the error (may be null).
+ * @param error The error message.
+ */
+ void fatalError(in nsISAXLocator locator, in AString error);
+
+ /**
+ * Receive notification of a warning.
+ *
+ * SAX parsers will use this method to report conditions that are
+ * not errors or fatal errors as defined by the XML
+ * recommendation. The default behaviour is to take no action.
+ *
+ * The SAX parser must continue to provide normal parsing events
+ * after invoking this method: it should still be possible for the
+ * application to process the document through to the end.
+ *
+ * Filters may use this method to report other, non-XML warnings
+ * as well.
+ *
+ * @param locator The locator object for the warning (may be null).
+ * @param error The warning message.
+ */
+ void ignorableWarning(in nsISAXLocator locator, in AString error);
+};
diff --git a/components/xmlparser/public/nsISAXLexicalHandler.idl b/components/xmlparser/public/nsISAXLexicalHandler.idl
new file mode 100644
index 000000000..ed50de2b7
--- /dev/null
+++ b/components/xmlparser/public/nsISAXLexicalHandler.idl
@@ -0,0 +1,118 @@
+/* -*- 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"
+
+/**
+ * SAX2 extension handler for lexical events.
+ *
+ * This is an extension handler for SAX2 to provide lexical
+ * information about an XML document, such as comments and CDATA
+ * section boundaries.
+ *
+ * The events in the lexical handler apply to the entire document,
+ * not just to the document element, and all lexical handler events
+ * must appear between the content handler's startDocument and
+ * endDocument events.
+ */
+[scriptable, uuid(23c26a56-adff-440c-8caf-95c2dc2e399b)]
+interface nsISAXLexicalHandler : nsISupports {
+
+ /**
+ * Report an XML comment anywhere in the document.
+ *
+ * This callback will be used for comments inside or outside the
+ * document element, including comments in the external DTD subset
+ * (if read). Comments in the DTD must be properly nested inside
+ * start/endDTD and start/endEntity events (if used).
+ *
+ * @param chars The characters in the comment.
+ */
+ void comment(in AString chars);
+
+ /**
+ * Report the start of DTD declarations, if any.
+ *
+ * This method is intended to report the beginning of the
+ * DOCTYPE declaration; if the document has no DOCTYPE declaration,
+ * this method will not be invoked.
+ *
+ * All declarations reported through DTDHandler or DeclHandler
+ * events must appear between the startDTD and endDTD events.
+ * Declarations are assumed to belong to the internal DTD subset
+ * unless they appear between startEntity and endEntity events.
+ * Comments and processing instructions from the DTD should also be
+ * reported between the startDTD and endDTD events, in their
+ * original order of (logical) occurrence; they are not required to
+ * appear in their correct locations relative to DTDHandler or
+ * DeclHandler events, however.
+ *
+ * Note that the start/endDTD events will appear within the
+ * start/endDocument events from ContentHandler and before the first
+ * startElement event.
+ *
+ * @param name The document type name.
+ * @param publicId The declared public identifier for the
+ * external DTD subset, or null if none was declared.
+ * @param systemId The declared system identifier for the
+ * external DTD subset, or null if none was declared.
+ * (Note that this is not resolved against the document
+ * base URI.)
+ */
+ void startDTD(in AString name, in AString publicId, in AString systemId);
+
+ /**
+ * Report the end of DTD declarations.
+ *
+ * This method is intended to report the end of the
+ * DOCTYPE declaration; if the document has no DOCTYPE declaration,
+ * this method will not be invoked.
+ */
+ void endDTD();
+
+ /**
+ * Report the start of a CDATA section.
+ *
+ * The contents of the CDATA section will be reported through the
+ * regular characters event; this event is intended only to report
+ * the boundary.
+ */
+ void startCDATA();
+
+ /**
+ * Report the end of a CDATA section.
+ */
+ void endCDATA();
+
+ /**
+ * Report the beginning of some internal and external XML entities.
+ *
+ * Because of the streaming event model that SAX uses, some
+ * entity boundaries cannot be reported under any circumstances:
+ *
+ * 1.) general entities within attribute values
+ * 2.) parameter entities within declarations
+ *
+ * These will be silently expanded, with no indication of where
+ * the original entity boundaries were.
+ *
+ * Note also that the boundaries of character references (which
+ * are not really entities anyway) are not reported.
+ *
+ * All start/endEntity events must be properly nested.
+ *
+ * @param name The name of the entity. If it is a parameter
+ * entity, the name will begin with '%', and if it is the
+ * external DTD subset, it will be "[dtd]".
+ */
+ void startEntity(in AString name);
+
+ /**
+ * Report the end of an entity.
+ *
+ * @param name The name of the entity that is ending.
+ */
+ void endEntity(in AString name);
+};
diff --git a/components/xmlparser/public/nsISAXLocator.idl b/components/xmlparser/public/nsISAXLocator.idl
new file mode 100644
index 000000000..a5808313f
--- /dev/null
+++ b/components/xmlparser/public/nsISAXLocator.idl
@@ -0,0 +1,89 @@
+/* -*- 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 for associating a SAX event with a document location.
+ *
+ * Note that the results returned by the object will be valid only
+ * during the scope of each callback method: the application will
+ * receive unpredictable results if it attempts to use the locator at
+ * any other time, or after parsing completes.
+ */
+[scriptable, uuid(7a307c6c-6cc9-11da-be43-001422106990)]
+interface nsISAXLocator: nsISupports {
+
+ /**
+ * Return the column number where the current document event ends.
+ *
+ * Warning: The return value from the method is intended only as an
+ * approximation for the sake of diagnostics; it is not intended to
+ * provide sufficient information to edit the character content of
+ * the original XML document. For example, when lines contain
+ * combining character sequences, wide characters, surrogate pairs,
+ * or bi-directional text, the value may not correspond to the
+ * column in a text editor's display.
+ *
+ * The return value is an approximation of the column number in the
+ * document entity or external parsed entity where the markup
+ * triggering the event appears.
+ *
+ * If possible, the SAX driver should provide the line position of
+ * the first character after the text associated with the document
+ * event. The first column in each line is column 1.
+ *
+ * @return The column number, or -1 if none is available.
+ */
+ readonly attribute long columnNumber;
+
+ /**
+ * Return the line number where the current document event ends.
+ * Lines are delimited by line ends, which are defined in the XML
+ * specification.
+ *
+ * Warning: The return value from the method is intended only as an
+ * approximation for the sake of diagnostics; it is not intended to
+ * provide sufficient information to edit the character content of
+ * the original XML document. In some cases, these "line" numbers
+ * match what would be displayed as columns, and in others they may
+ * not match the source text due to internal entity expansion.
+ *
+ * The return value is an approximation of the line number in the
+ * document entity or external parsed entity where the markup
+ * triggering the event appears.
+ *
+ * If possible, the SAX driver should provide the line position of
+ * the first character after the text associated with the document
+ * event. The first line is line 1.
+ *
+ * @return The line number, or -1 if none is available.
+ */
+ readonly attribute long lineNumber;
+
+ /**
+ * Return the public identifier for the current document event.
+ *
+ * The return value is the public identifier of the document entity
+ * or of the external parsed entity in which the markup triggering
+ * the event appears.
+ *
+ * @return A string containing the public identifier, or
+ * null if none is available.
+ */
+ readonly attribute AString publicId;
+
+ /**
+ * Return the system identifier for the current document event.
+ *
+ * The return value is the system identifier of the document entity
+ * or of the external parsed entity in which the markup triggering
+ * the event appears.
+ *
+ * @return A string containing the system identifier, or null
+ * if none is available.
+ */
+ readonly attribute AString systemId;
+};
diff --git a/components/xmlparser/public/nsISAXMutableAttributes.idl b/components/xmlparser/public/nsISAXMutableAttributes.idl
new file mode 100644
index 000000000..c3c205005
--- /dev/null
+++ b/components/xmlparser/public/nsISAXMutableAttributes.idl
@@ -0,0 +1,127 @@
+/* -*- 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"
+#include "nsISAXAttributes.idl"
+
+/**
+ * This interface extends the nsISAXAttributes interface with
+ * manipulators so that the list can be modified or reused.
+ */
+[scriptable, uuid(8b1de83d-cebb-49fa-8245-c0fe319eb7b6)]
+interface nsISAXMutableAttributes : nsISAXAttributes {
+
+ /**
+ * Add an attribute to the end of the list.
+ *
+ * For the sake of speed, this method does no checking
+ * to see if the attribute is already in the list: that is
+ * the responsibility of the application.
+ *
+ * @param uri The Namespace URI, or the empty string if
+ * none is available or Namespace processing is not
+ * being performed.
+ * @param localName The local name, or the empty string if
+ * Namespace processing is not being performed.
+ * @param qName The qualified (prefixed) name, or the empty string
+ * if qualified names are not available.
+ * @param type The attribute type as a string.
+ * @param value The attribute value.
+ */
+ void addAttribute(in AString uri,
+ in AString localName,
+ in AString qName,
+ in AString type,
+ in AString value);
+
+ /**
+ * Clear the attribute list for reuse.
+ */
+ void clear();
+
+ /**
+ * Remove an attribute from the list.
+ *
+ * @param index The index of the attribute (zero-based).
+ */
+ void removeAttribute(in unsigned long index);
+
+ /**
+ * Set the attributes list. This method will clear any attributes in
+ * the list before adding the attributes from the argument.
+ *
+ * @param attributes The attributes object to replace populate the
+ * list with.
+ */
+ void setAttributes(in nsISAXAttributes attributes);
+
+ /**
+ * Set an attribute in the list.
+ *
+ * For the sake of speed, this method does no checking for name
+ * conflicts or well-formedness: such checks are the responsibility
+ * of the application.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param uri The Namespace URI, or the empty string if
+ * none is available or Namespace processing is not
+ * being performed.
+ * @param localName The local name, or the empty string if
+ * Namespace processing is not being performed.
+ * @param qName The qualified name, or the empty string
+ * if qualified names are not available.
+ * @param type The attribute type as a string.
+ * @param value The attribute value.
+ */
+ void setAttribute(in unsigned long index,
+ in AString uri,
+ in AString localName,
+ in AString qName,
+ in AString type,
+ in AString value);
+
+ /**
+ * Set the local name of a specific attribute.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param localName The attribute's local name, or the empty
+ * string for none.
+ */
+ void setLocalName(in unsigned long index, in AString localName);
+
+ /**
+ * Set the qualified name of a specific attribute.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param qName The attribute's qualified name, or the empty
+ * string for none.
+ */
+ void setQName(in unsigned long index, in AString qName);
+
+ /**
+ * Set the type of a specific attribute.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param type The attribute's type.
+ */
+ void setType(in unsigned long index, in AString type);
+
+ /**
+ * Set the Namespace URI of a specific attribute.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param uri The attribute's Namespace URI, or the empty
+ * string for none.
+ */
+ void setURI(in unsigned long index, in AString uri);
+
+ /**
+ * Set the value of a specific attribute.
+ *
+ * @param index The index of the attribute (zero-based).
+ * @param value The attribute's value.
+ */
+ void setValue(in unsigned long index, in AString value);
+};
diff --git a/components/xmlparser/public/nsISAXXMLFilter.idl b/components/xmlparser/public/nsISAXXMLFilter.idl
new file mode 100644
index 000000000..44b637db9
--- /dev/null
+++ b/components/xmlparser/public/nsISAXXMLFilter.idl
@@ -0,0 +1,29 @@
+/* -*- 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"
+#include "nsISAXXMLReader.idl"
+
+/**
+ * Interface for an XML filter.
+ *
+ * An XML filter is like an XML reader, except that it obtains its
+ * events from another XML reader rather than a primary source like an
+ * XML document or database. Filters can modify a stream of events as
+ * they pass on to the final application.
+ */
+[scriptable, uuid(77a22cf0-6cdf-11da-be43-001422106990)]
+interface nsISAXXMLFilter : nsISAXXMLReader {
+
+ /**
+ * The parent reader.
+ *
+ * Allows the application to query the parent reader (which may be
+ * another filter). It is generally a bad idea to perform any
+ * operations on the parent reader directly: they should all pass
+ * through this filter.
+ */
+ attribute nsISAXXMLReader parent;
+};
diff --git a/components/xmlparser/public/nsISAXXMLReader.idl b/components/xmlparser/public/nsISAXXMLReader.idl
new file mode 100644
index 000000000..8dedcc3f6
--- /dev/null
+++ b/components/xmlparser/public/nsISAXXMLReader.idl
@@ -0,0 +1,207 @@
+/* -*- 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 "nsIStreamListener.idl"
+
+interface nsIInputStream;
+interface nsIRequestObserver;
+interface nsIURI;
+
+interface nsISAXContentHandler;
+interface nsISAXDTDHandler;
+interface nsISAXEntityResolver;
+interface nsISAXErrorHandler;
+interface nsISAXLexicalHandler;
+interface nsIMozSAXXMLDeclarationHandler;
+
+/**
+ * Interface for reading an XML document using callbacks.
+ *
+ * nsISAXXMLReader is the interface that an XML parser's SAX2
+ * driver must implement. This interface allows an application to set
+ * and query features and properties in the parser, to register event
+ * handlers for document processing, and to initiate a document
+ * parse.
+ */
+[scriptable, uuid(5b1de802-9091-454f-9972-5753c0d0c70e)]
+interface nsISAXXMLReader : nsIStreamListener {
+
+ /**
+ * The base URI.
+ */
+ attribute nsIURI baseURI;
+
+ /**
+ * If the application does not register a content handler, all
+ * content events reported by the SAX parser will be silently
+ * ignored.
+ *
+ * Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.
+ */
+ attribute nsISAXContentHandler contentHandler;
+
+ /**
+ * If the application does not register a DTD handler, all DTD
+ * events reported by the SAX parser will be silently ignored.
+ *
+ * Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.
+ */
+ attribute nsISAXDTDHandler dtdHandler;
+
+
+ /**
+ * If the application does not register an error handler, all
+ * error events reported by the SAX parser will be silently ignored;
+ * however, normal processing may not continue. It is highly
+ * recommended that all SAX applications implement an error handler
+ * to avoid unexpected bugs.
+ *
+ * Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.
+ */
+ attribute nsISAXErrorHandler errorHandler;
+
+ /**
+ * A handler for the (optional) XML declaration of a document.
+ * <?xml version='1.0'?>
+ *
+ * @note This is not part of the SAX standard.
+ */
+ attribute nsIMozSAXXMLDeclarationHandler declarationHandler;
+
+ /**
+ * If the application does not register a lexical handler, all
+ * lexical events (e.g. startDTD) reported by the SAX parser will be
+ * silently ignored.
+ *
+ * Applications may register a new or different handler in the
+ * middle of a parse, and the SAX parser must begin using the new
+ * handler immediately.
+ */
+ attribute nsISAXLexicalHandler lexicalHandler;
+
+ /**
+ * Set the value of a feature flag.
+ *
+ * The feature name is any fully-qualified URI. It is possible
+ * for an XMLReader to expose a feature value but to be unable to
+ * change the current value. Some feature values may be immutable
+ * or mutable only in specific contexts, such as before, during, or
+ * after a parse.
+ *
+ * All XMLReaders are required to support setting
+ * http://xml.org/sax/features/namespaces to true and
+ * http://xml.org/sax/features/namespace-prefixes to false.
+ *
+ * @param name String flag for a parser feature.
+ * @param value Turn the feature on/off.
+ *
+ * @note This is currently supported only for
+ * http://xml.org/sax/features/namespace-prefixes . All other
+ * features will result in a NOT_IMPLEMENTED exception.
+ */
+ void setFeature(in AString name, in boolean value);
+
+ /**
+ * Look up the value of a feature flag.
+ *
+ * The feature name is any fully-qualified URI. It is
+ * possible for an XMLReader to recognize a feature name but
+ * temporarily be unable to return its value.
+ * Some feature values may be available only in specific
+ * contexts, such as before, during, or after a parse.
+ *
+ * All XMLReaders are required to recognize the
+ * http://xml.org/sax/features/namespaces and the
+ * http://xml.org/sax/features/namespace-prefixes feature names.
+ *
+ * @param name String flag for a parser feature.
+ *
+ * @note This is currently supported only for
+ * http://xml.org/sax/features/namespace-prefixes . All other
+ * features will result in a NOT_IMPLEMENTED exception.
+ */
+ boolean getFeature(in AString name);
+
+ /**
+ * Set the value of a property. NOT CURRENTLY IMPLEMENTED.
+ *
+ * The property name is any fully-qualified URI. It is possible
+ * for an XMLReader to recognize a property name but to be unable to
+ * change the current value. Some property values may be immutable
+ * or mutable only in specific contexts, such as before, during, or
+ * after a parse.
+ *
+ * XMLReaders are not required to recognize setting any specific
+ * property names, though a core set is defined by SAX2.
+ *
+ * This method is also the standard mechanism for setting
+ * extended handlers.
+ *
+ * @param name String flag for a parser feature
+ * @param value Turn the feature on/off.
+ */
+ void setProperty(in AString name, in nsISupports value);
+
+ /**
+ * Look up the value of a property. NOT CURRENTLY IMPLEMENTED.
+ *
+ * The property name is any fully-qualified URI. It is
+ * possible for an XMLReader to recognize a property name but
+ * temporarily be unable to return its value.
+ * Some property values may be available only in specific
+ * contexts, such as before, during, or after a parse.
+ *
+ * XMLReaders are not required to recognize any specific
+ * property names, though an initial core set is documented for
+ * SAX2.
+ *
+ * Implementors are free (and encouraged) to invent their own properties,
+ * using names built on their own URIs.
+ *
+ * @param name The property name, which is a fully-qualified URI.
+ * @return The current value of the property.
+ */
+ boolean getProperty(in AString name);
+
+ /**
+ *
+ * @param str The UTF16 string to be parsed
+ * @param contentType The content type of the string (see parseFromStream)
+ *
+ */
+ void parseFromString(in AString str, in string contentType);
+
+ /**
+ *
+ * @param stream The byte stream whose contents are parsed
+ * @param charset The character set that was used to encode the byte
+ * stream. NULL if not specified.
+ * @param contentType The content type of the string - either text/xml,
+ * application/xml, or application/xhtml+xml.
+ * Must not be NULL.
+ *
+ */
+ void parseFromStream(in nsIInputStream stream,
+ in string charset,
+ in string contentType);
+
+ /**
+ * Begin an asynchronous parse. This method initializes the parser,
+ * and must be called before any nsIStreamListener methods. It is
+ * then the caller's duty to call nsIStreamListener methods to drive
+ * the parser. Once this method is called, the caller must not call
+ * one of the other parse methods.
+ *
+ * @param observer The nsIRequestObserver to notify upon start or stop.
+ * Can be NULL.
+ */
+ void parseAsync(in nsIRequestObserver observer);
+};
diff --git a/components/xmlparser/src/nsSAXAttributes.cpp b/components/xmlparser/src/nsSAXAttributes.cpp
new file mode 100644
index 000000000..3984186e4
--- /dev/null
+++ b/components/xmlparser/src/nsSAXAttributes.cpp
@@ -0,0 +1,332 @@
+/* -*- 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 "nsSAXAttributes.h"
+
+NS_IMPL_ISUPPORTS(nsSAXAttributes, nsISAXAttributes, nsISAXMutableAttributes)
+
+NS_IMETHODIMP
+nsSAXAttributes::GetIndexFromName(const nsAString &aURI,
+ const nsAString &aLocalName,
+ int32_t *aResult)
+{
+ int32_t len = mAttrs.Length();
+ int32_t i;
+ for (i = 0; i < len; ++i) {
+ const SAXAttr &att = mAttrs[i];
+ if (att.localName.Equals(aLocalName) && att.uri.Equals(aURI)) {
+ *aResult = i;
+ return NS_OK;
+ }
+ }
+ *aResult = -1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetIndexFromQName(const nsAString &aQName, int32_t *aResult)
+{
+ int32_t len = mAttrs.Length();
+ int32_t i;
+ for (i = 0; i < len; ++i) {
+ const SAXAttr &att = mAttrs[i];
+ if (att.qName.Equals(aQName)) {
+ *aResult = i;
+ return NS_OK;
+ }
+ }
+ *aResult = -1;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetLength(int32_t *aResult)
+{
+ *aResult = mAttrs.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetLocalName(uint32_t aIndex, nsAString &aResult)
+{
+ uint32_t len = mAttrs.Length();
+ if (aIndex >= len) {
+ aResult.SetIsVoid(true);
+ } else {
+ const SAXAttr &att = mAttrs[aIndex];
+ aResult = att.localName;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetQName(uint32_t aIndex, nsAString &aResult)
+{
+ uint32_t len = mAttrs.Length();
+ if (aIndex >= len) {
+ aResult.SetIsVoid(true);
+ } else {
+ const SAXAttr &att = mAttrs[aIndex];
+ aResult = att.qName;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetType(uint32_t aIndex, nsAString &aResult)
+{
+ uint32_t len = mAttrs.Length();
+ if (aIndex >= len) {
+ aResult.SetIsVoid(true);
+ } else {
+ const SAXAttr &att = mAttrs[aIndex];
+ aResult = att.type;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetTypeFromName(const nsAString &aURI,
+ const nsAString &aLocalName,
+ nsAString &aResult)
+{
+ int32_t index = -1;
+ GetIndexFromName(aURI, aLocalName, &index);
+ if (index >= 0) {
+ aResult = mAttrs[index].type;
+ } else {
+ aResult.SetIsVoid(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetTypeFromQName(const nsAString &aQName, nsAString &aResult)
+{
+ int32_t index = -1;
+ GetIndexFromQName(aQName, &index);
+ if (index >= 0) {
+ aResult = mAttrs[index].type;
+ } else {
+ aResult.SetIsVoid(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetURI(uint32_t aIndex, nsAString &aResult)
+{
+ uint32_t len = mAttrs.Length();
+ if (aIndex >= len) {
+ aResult.SetIsVoid(true);
+ } else {
+ const SAXAttr &att = mAttrs[aIndex];
+ aResult = att.uri;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetValue(uint32_t aIndex, nsAString &aResult)
+{
+ uint32_t len = mAttrs.Length();
+ if (aIndex >= len) {
+ aResult.SetIsVoid(true);
+ } else {
+ const SAXAttr &att = mAttrs[aIndex];
+ aResult = att.value;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetValueFromName(const nsAString &aURI,
+ const nsAString &aLocalName,
+ nsAString &aResult)
+{
+ int32_t index = -1;
+ GetIndexFromName(aURI, aLocalName, &index);
+ if (index >= 0) {
+ aResult = mAttrs[index].value;
+ } else {
+ aResult.SetIsVoid(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::GetValueFromQName(const nsAString &aQName,
+ nsAString &aResult)
+{
+ int32_t index = -1;
+ GetIndexFromQName(aQName, &index);
+ if (index >= 0) {
+ aResult = mAttrs[index].value;
+ } else {
+ aResult.SetIsVoid(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::AddAttribute(const nsAString &aURI,
+ const nsAString &aLocalName,
+ const nsAString &aQName,
+ const nsAString &aType,
+ const nsAString &aValue)
+{
+ SAXAttr *att = mAttrs.AppendElement();
+ if (!att) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ att->uri = aURI;
+ att->localName = aLocalName;
+ att->qName = aQName;
+ att->type = aType;
+ att->value = aValue;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::Clear()
+{
+ mAttrs.Clear();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::RemoveAttribute(uint32_t aIndex)
+{
+ if (aIndex >= mAttrs.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+ mAttrs.RemoveElementAt(aIndex);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::SetAttributes(nsISAXAttributes *aAttributes)
+{
+ NS_ENSURE_ARG(aAttributes);
+
+ nsresult rv;
+ int32_t len;
+ rv = aAttributes->GetLength(&len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mAttrs.Clear();
+ SAXAttr *att;
+ int32_t i;
+ for (i = 0; i < len; ++i) {
+ att = mAttrs.AppendElement();
+ if (!att) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = aAttributes->GetURI(i, att->uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aAttributes->GetLocalName(i, att->localName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aAttributes->GetQName(i, att->qName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aAttributes->GetType(i, att->type);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aAttributes->GetValue(i, att->value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::SetAttribute(uint32_t aIndex,
+ const nsAString &aURI,
+ const nsAString &aLocalName,
+ const nsAString &aQName,
+ const nsAString &aType,
+ const nsAString &aValue)
+{
+ if (aIndex >= mAttrs.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SAXAttr &att = mAttrs[aIndex];
+ att.uri = aURI;
+ att.localName = aLocalName;
+ att.qName = aQName;
+ att.type = aType;
+ att.value = aValue;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::SetLocalName(uint32_t aIndex, const nsAString &aLocalName)
+{
+ if (aIndex >= mAttrs.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+ mAttrs[aIndex].localName = aLocalName;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::SetQName(uint32_t aIndex, const nsAString &aQName)
+{
+ if (aIndex >= mAttrs.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+ mAttrs[aIndex].qName = aQName;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::SetType(uint32_t aIndex, const nsAString &aType)
+{
+ if (aIndex >= mAttrs.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+ mAttrs[aIndex].type = aType;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::SetURI(uint32_t aIndex, const nsAString &aURI)
+{
+ if (aIndex >= mAttrs.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+ mAttrs[aIndex].uri = aURI;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXAttributes::SetValue(uint32_t aIndex, const nsAString &aValue)
+{
+ if (aIndex >= mAttrs.Length()) {
+ return NS_ERROR_FAILURE;
+ }
+ mAttrs[aIndex].value = aValue;
+
+ return NS_OK;
+}
diff --git a/components/xmlparser/src/nsSAXAttributes.h b/components/xmlparser/src/nsSAXAttributes.h
new file mode 100644
index 000000000..f8da6f8a1
--- /dev/null
+++ b/components/xmlparser/src/nsSAXAttributes.h
@@ -0,0 +1,43 @@
+/* -*- 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 nsSAXAttributes_h__
+#define nsSAXAttributes_h__
+
+#include "nsISupports.h"
+#include "nsISAXAttributes.h"
+#include "nsISAXMutableAttributes.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+#define NS_SAXATTRIBUTES_CONTRACTID "@mozilla.org/saxparser/attributes;1"
+#define NS_SAXATTRIBUTES_CID \
+{/* {7bb40992-77eb-43db-9a4e-39d3bcc483ae}*/ \
+0x7bb40992, 0x77eb, 0x43db, \
+{ 0x9a, 0x4e, 0x39, 0xd3, 0xbc, 0xc3, 0x83, 0xae} }
+
+struct SAXAttr
+{
+ nsString uri;
+ nsString localName;
+ nsString qName;
+ nsString type;
+ nsString value;
+};
+
+class nsSAXAttributes final : public nsISAXMutableAttributes
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISAXATTRIBUTES
+ NS_DECL_NSISAXMUTABLEATTRIBUTES
+
+private:
+ ~nsSAXAttributes() {}
+ nsTArray<SAXAttr> mAttrs;
+};
+
+#endif // nsSAXAttributes_h__
diff --git a/components/xmlparser/src/nsSAXLocator.cpp b/components/xmlparser/src/nsSAXLocator.cpp
new file mode 100644
index 000000000..16a056ac6
--- /dev/null
+++ b/components/xmlparser/src/nsSAXLocator.cpp
@@ -0,0 +1,47 @@
+/* -*- 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 "nsSAXLocator.h"
+
+NS_IMPL_ISUPPORTS(nsSAXLocator, nsISAXLocator)
+
+nsSAXLocator::nsSAXLocator(nsString& aPublicId,
+ nsString& aSystemId,
+ int32_t aLineNumber,
+ int32_t aColumnNumber) :
+ mPublicId(aPublicId),
+ mSystemId(aSystemId),
+ mLineNumber(aLineNumber),
+ mColumnNumber(aColumnNumber)
+{
+}
+
+NS_IMETHODIMP
+nsSAXLocator::GetColumnNumber(int32_t *aColumnNumber)
+{
+ *aColumnNumber = mColumnNumber;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXLocator::GetLineNumber(int32_t *aLineNumber)
+{
+ *aLineNumber = mLineNumber;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXLocator::GetPublicId(nsAString &aPublicId)
+{
+ aPublicId = mPublicId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXLocator::GetSystemId(nsAString &aSystemId)
+{
+ aSystemId = mSystemId;
+ return NS_OK;
+}
diff --git a/components/xmlparser/src/nsSAXLocator.h b/components/xmlparser/src/nsSAXLocator.h
new file mode 100644
index 000000000..14b9ef063
--- /dev/null
+++ b/components/xmlparser/src/nsSAXLocator.h
@@ -0,0 +1,39 @@
+/* -*- 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 nsSAXLocator_h__
+#define nsSAXLocator_h__
+
+#include "nsISAXLocator.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+#define NS_SAXLOCATOR_CONTRACTID "@mozilla.org/saxparser/locator;1"
+#define NS_SAXLOCATOR_CID \
+{/* {c1cd4045-846b-43bb-a95e-745a3d7b40e0}*/ \
+0xc1cd4045, 0x846b, 0x43bb, \
+{ 0xa9, 0x5e, 0x74, 0x5a, 0x3d, 0x7b, 0x40, 0xe0} }
+
+class nsSAXLocator final : public nsISAXLocator
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISAXLOCATOR
+
+ nsSAXLocator(nsString& aPublicId,
+ nsString& aSystemId,
+ int32_t aLineNumber,
+ int32_t aColumnNumber);
+
+private:
+ ~nsSAXLocator() {}
+
+ nsString mPublicId;
+ nsString mSystemId;
+ int32_t mLineNumber;
+ int32_t mColumnNumber;
+};
+
+#endif //nsSAXLocator_h__
diff --git a/components/xmlparser/src/nsSAXXMLReader.cpp b/components/xmlparser/src/nsSAXXMLReader.cpp
new file mode 100644
index 000000000..a84e0d63b
--- /dev/null
+++ b/components/xmlparser/src/nsSAXXMLReader.cpp
@@ -0,0 +1,719 @@
+/* -*- 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 "nsIInputStream.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsNullPrincipal.h"
+#include "nsIParser.h"
+#include "nsParserCIID.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsIScriptError.h"
+#include "nsSAXAttributes.h"
+#include "nsSAXLocator.h"
+#include "nsSAXXMLReader.h"
+#include "nsCharsetSource.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+
+using mozilla::dom::EncodingUtils;
+
+#define XMLNS_URI "http://www.w3.org/2000/xmlns/"
+
+static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);
+
+NS_IMPL_CYCLE_COLLECTION(nsSAXXMLReader,
+ mContentHandler,
+ mDTDHandler,
+ mErrorHandler,
+ mLexicalHandler,
+ mDeclarationHandler,
+ mBaseURI,
+ mListener,
+ mParserObserver)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSAXXMLReader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSAXXMLReader)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSAXXMLReader)
+ NS_INTERFACE_MAP_ENTRY(nsISAXXMLReader)
+ NS_INTERFACE_MAP_ENTRY(nsIExpatSink)
+ NS_INTERFACE_MAP_ENTRY(nsIExtendedExpatSink)
+ NS_INTERFACE_MAP_ENTRY(nsIContentSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISAXXMLReader)
+NS_INTERFACE_MAP_END
+
+nsSAXXMLReader::nsSAXXMLReader() :
+ mIsAsyncParse(false),
+ mEnableNamespacePrefixes(false)
+{
+}
+
+// nsIContentSink
+NS_IMETHODIMP
+nsSAXXMLReader::WillBuildModel(nsDTDMode)
+{
+ if (mContentHandler)
+ return mContentHandler->StartDocument();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::DidBuildModel(bool aTerminated)
+{
+ if (mContentHandler)
+ return mContentHandler->EndDocument();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::SetParser(nsParserBase *aParser)
+{
+ return NS_OK;
+}
+
+// nsIExtendedExpatSink
+NS_IMETHODIMP
+nsSAXXMLReader::HandleStartElement(const char16_t *aName,
+ const char16_t **aAtts,
+ uint32_t aAttsCount,
+ uint32_t aLineNumber)
+{
+ if (!mContentHandler)
+ return NS_OK;
+
+ RefPtr<nsSAXAttributes> atts = new nsSAXAttributes();
+ if (!atts)
+ return NS_ERROR_OUT_OF_MEMORY;
+ nsAutoString uri, localName, qName;
+ for (; *aAtts; aAtts += 2) {
+ SplitExpatName(aAtts[0], uri, localName, qName);
+ // XXX don't have attr type information
+ NS_NAMED_LITERAL_STRING(cdataType, "CDATA");
+ // could support xmlns reporting, it's a standard SAX feature
+ if (mEnableNamespacePrefixes || !uri.EqualsLiteral(XMLNS_URI)) {
+ NS_ASSERTION(aAtts[1], "null passed to handler");
+ atts->AddAttribute(uri, localName, qName, cdataType,
+ nsDependentString(aAtts[1]));
+ }
+ }
+
+ // Deal with the element name
+ SplitExpatName(aName, uri, localName, qName);
+ return mContentHandler->StartElement(uri, localName, qName, atts);
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleEndElement(const char16_t *aName)
+{
+ if (mContentHandler) {
+ nsAutoString uri, localName, qName;
+ SplitExpatName(aName, uri, localName, qName);
+ return mContentHandler->EndElement(uri, localName, qName);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleComment(const char16_t *aName)
+{
+ NS_ASSERTION(aName, "null passed to handler");
+ if (mLexicalHandler)
+ return mLexicalHandler->Comment(nsDependentString(aName));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleCDataSection(const char16_t *aData,
+ uint32_t aLength)
+{
+ nsresult rv;
+ if (mLexicalHandler) {
+ rv = mLexicalHandler->StartCDATA();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mContentHandler) {
+ rv = mContentHandler->Characters(Substring(aData, aData+aLength));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mLexicalHandler) {
+ rv = mLexicalHandler->EndCDATA();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleStartDTD(const char16_t *aName,
+ const char16_t *aSystemId,
+ const char16_t *aPublicId)
+{
+ char16_t nullChar = char16_t(0);
+ if (!aName)
+ aName = &nullChar;
+ if (!aSystemId)
+ aSystemId = &nullChar;
+ if (!aPublicId)
+ aPublicId = &nullChar;
+
+ mSystemId = aSystemId;
+ mPublicId = aPublicId;
+ if (mLexicalHandler) {
+ return mLexicalHandler->StartDTD(nsDependentString(aName),
+ nsDependentString(aPublicId),
+ nsDependentString(aSystemId));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleDoctypeDecl(const nsAString & aSubset,
+ const nsAString & aName,
+ const nsAString & aSystemId,
+ const nsAString & aPublicId,
+ nsISupports* aCatalogData)
+{
+ if (mLexicalHandler)
+ return mLexicalHandler->EndDTD();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleCharacterData(const char16_t *aData,
+ uint32_t aLength)
+{
+ if (mContentHandler)
+ return mContentHandler->Characters(Substring(aData, aData+aLength));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleStartNamespaceDecl(const char16_t *aPrefix,
+ const char16_t *aUri)
+{
+ if (!mContentHandler)
+ return NS_OK;
+
+ char16_t nullChar = char16_t(0);
+ if (!aPrefix)
+ aPrefix = &nullChar;
+ if (!aUri)
+ aUri = &nullChar;
+
+ return mContentHandler->StartPrefixMapping(nsDependentString(aPrefix),
+ nsDependentString(aUri));
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleEndNamespaceDecl(const char16_t *aPrefix)
+{
+ if (!mContentHandler)
+ return NS_OK;
+
+ if (aPrefix)
+ return mContentHandler->EndPrefixMapping(nsDependentString(aPrefix));
+
+ return mContentHandler->EndPrefixMapping(EmptyString());
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleProcessingInstruction(const char16_t *aTarget,
+ const char16_t *aData)
+{
+ NS_ASSERTION(aTarget && aData, "null passed to handler");
+ if (mContentHandler) {
+ return mContentHandler->ProcessingInstruction(nsDependentString(aTarget),
+ nsDependentString(aData));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleNotationDecl(const char16_t *aNotationName,
+ const char16_t *aSystemId,
+ const char16_t *aPublicId)
+{
+ NS_ASSERTION(aNotationName, "null passed to handler");
+ if (mDTDHandler) {
+ char16_t nullChar = char16_t(0);
+ if (!aSystemId)
+ aSystemId = &nullChar;
+ if (!aPublicId)
+ aPublicId = &nullChar;
+
+ return mDTDHandler->NotationDecl(nsDependentString(aNotationName),
+ nsDependentString(aSystemId),
+ nsDependentString(aPublicId));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleUnparsedEntityDecl(const char16_t *aEntityName,
+ const char16_t *aSystemId,
+ const char16_t *aPublicId,
+ const char16_t *aNotationName)
+{
+ NS_ASSERTION(aEntityName && aNotationName, "null passed to handler");
+ if (mDTDHandler) {
+ char16_t nullChar = char16_t(0);
+ if (!aSystemId)
+ aSystemId = &nullChar;
+ if (!aPublicId)
+ aPublicId = &nullChar;
+
+ return mDTDHandler->UnparsedEntityDecl(nsDependentString(aEntityName),
+ nsDependentString(aSystemId),
+ nsDependentString(aPublicId),
+ nsDependentString(aNotationName));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::HandleXMLDeclaration(const char16_t *aVersion,
+ const char16_t *aEncoding,
+ int32_t aStandalone)
+{
+ NS_ASSERTION(aVersion, "null passed to handler");
+ if (mDeclarationHandler) {
+ char16_t nullChar = char16_t(0);
+ if (!aEncoding)
+ aEncoding = &nullChar;
+ mDeclarationHandler->HandleXMLDeclaration(nsDependentString(aVersion),
+ nsDependentString(aEncoding),
+ aStandalone > 0);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::ReportError(const char16_t* aErrorText,
+ const char16_t* aSourceText,
+ nsIScriptError *aError,
+ bool *_retval)
+{
+ NS_PRECONDITION(aError && aSourceText && aErrorText, "Check arguments!!!");
+ // Normally, the expat driver should report the error.
+ *_retval = true;
+
+ if (mErrorHandler) {
+ uint32_t lineNumber;
+ nsresult rv = aError->GetLineNumber(&lineNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t columnNumber;
+ rv = aError->GetColumnNumber(&columnNumber);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISAXLocator> locator = new nsSAXLocator(mPublicId,
+ mSystemId,
+ lineNumber,
+ columnNumber);
+ if (!locator)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = mErrorHandler->FatalError(locator, nsDependentString(aErrorText));
+ if (NS_SUCCEEDED(rv)) {
+ // The error handler has handled the script error. Don't log to console.
+ *_retval = false;
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsISAXXMLReader
+
+NS_IMETHODIMP
+nsSAXXMLReader::GetBaseURI(nsIURI **aBaseURI)
+{
+ NS_IF_ADDREF(*aBaseURI = mBaseURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::SetBaseURI(nsIURI *aBaseURI)
+{
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::GetContentHandler(nsISAXContentHandler **aContentHandler)
+{
+ NS_IF_ADDREF(*aContentHandler = mContentHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::SetContentHandler(nsISAXContentHandler *aContentHandler)
+{
+ mContentHandler = aContentHandler;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::GetDtdHandler(nsISAXDTDHandler **aDtdHandler)
+{
+ NS_IF_ADDREF(*aDtdHandler = mDTDHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::SetDtdHandler(nsISAXDTDHandler *aDtdHandler)
+{
+ mDTDHandler = aDtdHandler;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::GetErrorHandler(nsISAXErrorHandler **aErrorHandler)
+{
+ NS_IF_ADDREF(*aErrorHandler = mErrorHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::SetErrorHandler(nsISAXErrorHandler *aErrorHandler)
+{
+ mErrorHandler = aErrorHandler;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::SetFeature(const nsAString &aName, bool aValue)
+{
+ if (aName.EqualsLiteral("http://xml.org/sax/features/namespace-prefixes")) {
+ mEnableNamespacePrefixes = aValue;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::GetFeature(const nsAString &aName, bool *aResult)
+{
+ if (aName.EqualsLiteral("http://xml.org/sax/features/namespace-prefixes")) {
+ *aResult = mEnableNamespacePrefixes;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::GetDeclarationHandler(nsIMozSAXXMLDeclarationHandler **aDeclarationHandler) {
+ NS_IF_ADDREF(*aDeclarationHandler = mDeclarationHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::SetDeclarationHandler(nsIMozSAXXMLDeclarationHandler *aDeclarationHandler) {
+ mDeclarationHandler = aDeclarationHandler;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::GetLexicalHandler(nsISAXLexicalHandler **aLexicalHandler)
+{
+ NS_IF_ADDREF(*aLexicalHandler = mLexicalHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::SetLexicalHandler(nsISAXLexicalHandler *aLexicalHandler)
+{
+ mLexicalHandler = aLexicalHandler;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::SetProperty(const nsAString &aName, nsISupports* aValue)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::GetProperty(const nsAString &aName, bool *aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::ParseFromString(const nsAString &aStr,
+ const char *aContentType)
+{
+ // Don't call this in the middle of an async parse
+ NS_ENSURE_TRUE(!mIsAsyncParse, NS_ERROR_FAILURE);
+
+ NS_ConvertUTF16toUTF8 data(aStr);
+
+ // The new stream holds a reference to the buffer
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
+ data.get(), data.Length(),
+ NS_ASSIGNMENT_DEPEND);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseFromStream(stream, "UTF-8", aContentType);
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::ParseFromStream(nsIInputStream *aStream,
+ const char *aCharset,
+ const char *aContentType)
+{
+ // Don't call this in the middle of an async parse
+ NS_ENSURE_TRUE(!mIsAsyncParse, NS_ERROR_FAILURE);
+
+ NS_ENSURE_ARG(aStream);
+ NS_ENSURE_ARG(aContentType);
+
+ // Put the nsCOMPtr out here so we hold a ref to the stream as needed
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ if (!NS_InputStreamIsBuffered(aStream)) {
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+ aStream, 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aStream = bufferedStream;
+ }
+
+ rv = EnsureBaseURI();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
+
+ // The following channel is never openend, so it does not matter what
+ // securityFlags we pass; let's follow the principle of least privilege.
+ nsCOMPtr<nsIChannel> parserChannel;
+ rv = NS_NewInputStreamChannel(getter_AddRefs(parserChannel),
+ mBaseURI,
+ aStream,
+ nullPrincipal,
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ nsIContentPolicy::TYPE_OTHER,
+ nsDependentCString(aContentType));
+ if (!parserChannel || NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ if (aCharset)
+ parserChannel->SetContentCharset(nsDependentCString(aCharset));
+
+ rv = InitParser(nullptr, parserChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mListener->OnStartRequest(parserChannel, nullptr);
+ if (NS_FAILED(rv))
+ parserChannel->Cancel(rv);
+
+ /* When parsing a new document, we need to clear the XML identifiers.
+ HandleStartDTD will set these values from the DTD declaration tag.
+ We won't have them, of course, if there's a well-formedness error
+ before the DTD tag (such as a space before an XML declaration).
+ */
+ mSystemId.Truncate();
+ mPublicId.Truncate();
+
+ nsresult status;
+ parserChannel->GetStatus(&status);
+
+ uint64_t offset = 0;
+ while (NS_SUCCEEDED(rv) && NS_SUCCEEDED(status)) {
+ uint64_t available;
+ rv = aStream->Available(&available);
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ rv = NS_OK;
+ available = 0;
+ }
+ if (NS_FAILED(rv)) {
+ parserChannel->Cancel(rv);
+ break;
+ }
+ if (! available)
+ break; // blocking input stream has none available when done
+
+ if (available > UINT32_MAX)
+ available = UINT32_MAX;
+
+ rv = mListener->OnDataAvailable(parserChannel, nullptr,
+ aStream,
+ offset,
+ (uint32_t)available);
+ if (NS_SUCCEEDED(rv))
+ offset += available;
+ else
+ parserChannel->Cancel(rv);
+ parserChannel->GetStatus(&status);
+ }
+ rv = mListener->OnStopRequest(parserChannel, nullptr, status);
+ mListener = nullptr;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::ParseAsync(nsIRequestObserver *aObserver)
+{
+ mParserObserver = aObserver;
+ mIsAsyncParse = true;
+ return NS_OK;
+}
+
+// nsIRequestObserver
+
+NS_IMETHODIMP
+nsSAXXMLReader::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+ NS_ENSURE_TRUE(mIsAsyncParse, NS_ERROR_FAILURE);
+ nsresult rv;
+ rv = EnsureBaseURI();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ rv = InitParser(mParserObserver, channel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // we don't need or want this anymore
+ mParserObserver = nullptr;
+ return mListener->OnStartRequest(aRequest, aContext);
+}
+
+NS_IMETHODIMP
+nsSAXXMLReader::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
+ nsresult status)
+{
+ NS_ENSURE_TRUE(mIsAsyncParse, NS_ERROR_FAILURE);
+ NS_ENSURE_STATE(mListener);
+ nsresult rv = mListener->OnStopRequest(aRequest, aContext, status);
+ mListener = nullptr;
+ mIsAsyncParse = false;
+ return rv;
+}
+
+// nsIStreamListener
+
+NS_IMETHODIMP
+nsSAXXMLReader::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
+ nsIInputStream *aInputStream, uint64_t offset,
+ uint32_t count)
+{
+ NS_ENSURE_TRUE(mIsAsyncParse, NS_ERROR_FAILURE);
+ NS_ENSURE_STATE(mListener);
+ return mListener->OnDataAvailable(aRequest, aContext, aInputStream, offset,
+ count);
+}
+
+nsresult
+nsSAXXMLReader::InitParser(nsIRequestObserver *aObserver, nsIChannel *aChannel)
+{
+ nsresult rv;
+
+ // setup the parser
+ nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ parser->SetContentSink(this);
+
+ int32_t charsetSource = kCharsetFromDocTypeDefault;
+ nsAutoCString charset(NS_LITERAL_CSTRING("UTF-8"));
+ TryChannelCharset(aChannel, charsetSource, charset);
+ parser->SetDocumentCharset(charset, charsetSource);
+
+ rv = parser->Parse(mBaseURI, aObserver);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mListener = do_QueryInterface(parser, &rv);
+
+ return rv;
+}
+
+// from nsDocument.cpp
+bool
+nsSAXXMLReader::TryChannelCharset(nsIChannel *aChannel,
+ int32_t& aCharsetSource,
+ nsACString& aCharset)
+{
+ if (aCharsetSource >= kCharsetFromChannel)
+ return true;
+
+ if (aChannel) {
+ nsAutoCString charsetVal;
+ nsresult rv = aChannel->GetContentCharset(charsetVal);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString preferred;
+ if (!EncodingUtils::FindEncodingForLabel(charsetVal, preferred))
+ return false;
+
+ aCharset = preferred;
+ aCharsetSource = kCharsetFromChannel;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+nsresult
+nsSAXXMLReader::EnsureBaseURI()
+{
+ if (mBaseURI)
+ return NS_OK;
+
+ return NS_NewURI(getter_AddRefs(mBaseURI), "about:blank");
+}
+
+nsresult
+nsSAXXMLReader::SplitExpatName(const char16_t *aExpatName,
+ nsString &aURI,
+ nsString &aLocalName,
+ nsString &aQName)
+{
+ /**
+ * Adapted from RDFContentSinkImpl
+ *
+ * Expat can send the following:
+ * localName
+ * namespaceURI<separator>localName
+ * namespaceURI<separator>localName<separator>prefix
+ *
+ * and we use 0xFFFF for the <separator>.
+ *
+ */
+
+ NS_ASSERTION(aExpatName, "null passed to handler");
+ nsDependentString expatStr(aExpatName);
+ int32_t break1, break2 = kNotFound;
+ break1 = expatStr.FindChar(char16_t(0xFFFF));
+
+ if (break1 == kNotFound) {
+ aLocalName = expatStr; // no namespace
+ aURI.Truncate();
+ aQName = expatStr;
+ } else {
+ aURI = StringHead(expatStr, break1);
+ break2 = expatStr.FindChar(char16_t(0xFFFF), break1 + 1);
+ if (break2 == kNotFound) { // namespace, but no prefix
+ aLocalName = Substring(expatStr, break1 + 1);
+ aQName = aLocalName;
+ } else { // namespace with prefix
+ aLocalName = Substring(expatStr, break1 + 1, break2 - break1 - 1);
+ aQName = Substring(expatStr, break2 + 1) +
+ NS_LITERAL_STRING(":") + aLocalName;
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/components/xmlparser/src/nsSAXXMLReader.h b/components/xmlparser/src/nsSAXXMLReader.h
new file mode 100644
index 000000000..763f787e5
--- /dev/null
+++ b/components/xmlparser/src/nsSAXXMLReader.h
@@ -0,0 +1,105 @@
+/* -*- 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 nsSAXXMLReader_h__
+#define nsSAXXMLReader_h__
+
+#include "nsCOMPtr.h"
+#include "nsIContentSink.h"
+#include "nsIExtendedExpatSink.h"
+#include "nsIParser.h"
+#include "nsIURI.h"
+#include "nsISAXXMLReader.h"
+#include "nsISAXContentHandler.h"
+#include "nsISAXDTDHandler.h"
+#include "nsISAXErrorHandler.h"
+#include "nsISAXLexicalHandler.h"
+#include "nsIMozSAXXMLDeclarationHandler.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+#define NS_SAXXMLREADER_CONTRACTID "@mozilla.org/saxparser/xmlreader;1"
+#define NS_SAXXMLREADER_CID \
+{ 0xab1da296, 0x6125, 0x40ba, \
+{ 0x96, 0xd0, 0x47, 0xa8, 0x28, 0x2a, 0xe3, 0xdb} }
+
+class nsSAXXMLReader final : public nsISAXXMLReader,
+ public nsIExtendedExpatSink,
+ public nsIContentSink
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsSAXXMLReader, nsISAXXMLReader)
+ NS_DECL_NSIEXPATSINK
+ NS_DECL_NSIEXTENDEDEXPATSINK
+ NS_DECL_NSISAXXMLREADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsSAXXMLReader();
+
+ //nsIContentSink
+ NS_IMETHOD WillParse() override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override;
+ NS_IMETHOD DidBuildModel(bool aTerminated) override;
+ NS_IMETHOD SetParser(nsParserBase* aParser) override;
+
+ NS_IMETHOD WillInterrupt() override
+ {
+ return NS_OK;
+ }
+
+ NS_IMETHOD WillResume() override
+ {
+ return NS_OK;
+ }
+
+ virtual void FlushPendingNotifications(mozFlushType aType) override
+ {
+ }
+
+ NS_IMETHOD SetDocumentCharset(nsACString& aCharset) override
+ {
+ return NS_OK;
+ }
+
+ virtual nsISupports *GetTarget() override
+ {
+ return nullptr;
+ }
+
+private:
+ ~nsSAXXMLReader() {}
+
+ nsCOMPtr<nsISAXContentHandler> mContentHandler;
+ nsCOMPtr<nsISAXDTDHandler> mDTDHandler;
+ nsCOMPtr<nsISAXErrorHandler> mErrorHandler;
+ nsCOMPtr<nsISAXLexicalHandler> mLexicalHandler;
+ nsCOMPtr<nsIMozSAXXMLDeclarationHandler> mDeclarationHandler;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIRequestObserver> mParserObserver;
+ bool mIsAsyncParse;
+ static bool TryChannelCharset(nsIChannel *aChannel,
+ int32_t& aCharsetSource,
+ nsACString& aCharset);
+ nsresult EnsureBaseURI();
+ nsresult InitParser(nsIRequestObserver *aListener, nsIChannel *aChannel);
+ nsresult SplitExpatName(const char16_t *aExpatName,
+ nsString &aURI,
+ nsString &aLocalName,
+ nsString &aQName);
+ nsString mPublicId;
+ nsString mSystemId;
+
+ // Feature flags
+ bool mEnableNamespacePrefixes;
+};
+
+#endif // nsSAXXMLReader_h__
diff --git a/components/xulstore/XULStore.js b/components/xulstore/XULStore.js
new file mode 100644
index 000000000..8b5bc1313
--- /dev/null
+++ b/components/xulstore/XULStore.js
@@ -0,0 +1,346 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+
+// Enables logging and shorter save intervals.
+const debugMode = false;
+
+// Delay when a change is made to when the file is saved.
+// 30 seconds normally, or 3 seconds for testing
+const WRITE_DELAY_MS = (debugMode ? 3 : 30) * 1000;
+
+const XULSTORE_CONTRACTID = "@mozilla.org/xul/xulstore;1";
+const XULSTORE_CID = Components.ID("{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}");
+const STOREDB_FILENAME = "xulstore.json";
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
+
+function XULStore() {
+ if (!Services.appinfo.inSafeMode)
+ this.load();
+}
+
+XULStore.prototype = {
+ classID: XULSTORE_CID,
+ classInfo: XPCOMUtils.generateCI({classID: XULSTORE_CID,
+ contractID: XULSTORE_CONTRACTID,
+ classDescription: "XULStore",
+ interfaces: [Ci.nsIXULStore]}),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIXULStore,
+ Ci.nsISupportsWeakReference]),
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(XULStore),
+
+ /* ---------- private members ---------- */
+
+ /*
+ * The format of _data is _data[docuri][elementid][attribute]. For example:
+ * {
+ * "chrome://blah/foo.xul" : {
+ * "main-window" : { aaa : 1, bbb : "c" },
+ * "barColumn" : { ddd : 9, eee : "f" },
+ * },
+ *
+ * "chrome://foopy/b.xul" : { ... },
+ * ...
+ * }
+ */
+ _data: {},
+ _storeFile: null,
+ _needsSaving: false,
+ _saveAllowed: true,
+ _writeTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
+
+ load: function () {
+ Services.obs.addObserver(this, "profile-before-change", true);
+
+ let profileType = "ProfD";
+ try {
+ this._storeFile = Services.dirsvc.get(profileType, Ci.nsIFile);
+ } catch (ex) {
+ try {
+ profileType = "ProfDS";
+ this._storeFile = Services.dirsvc.get(profileType, Ci.nsIFile);
+ } catch (ex) {
+ throw new Error("Can't find profile directory.");
+ }
+ }
+ this._storeFile.append(STOREDB_FILENAME);
+
+ if (!this._storeFile.exists()) {
+ this.import(profileType);
+ } else {
+ this.readFile();
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ this.writeFile();
+ if (topic == "profile-before-change") {
+ this._saveAllowed = false;
+ }
+ },
+
+ /*
+ * Internal function for logging debug messages to the Error Console window
+ */
+ log: function (message) {
+ if (!debugMode)
+ return;
+ dump("XULStore: " + message + "\n");
+ Services.console.logStringMessage("XULStore: " + message);
+ },
+
+ import(profileType) {
+ let localStoreFile = Services.dirsvc.get(profileType || "ProfD", Ci.nsIFile);
+
+ localStoreFile.append("localstore.rdf");
+ if (!localStoreFile.exists()) {
+ return;
+ }
+
+ const RDF = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
+ const persistKey = RDF.GetResource("http://home.netscape.com/NC-rdf#persist");
+
+ this.log("Import localstore from " + localStoreFile.path);
+
+ let localStoreURI = Services.io.newFileURI(localStoreFile).spec;
+ let localStore = RDF.GetDataSourceBlocking(localStoreURI);
+ let resources = localStore.GetAllResources();
+
+ while (resources.hasMoreElements()) {
+ let resource = resources.getNext().QueryInterface(Ci.nsIRDFResource);
+ let uri;
+
+ try {
+ uri = NetUtil.newURI(resource.ValueUTF8);
+ } catch (ex) {
+ continue; // skip invalid uris
+ }
+
+ // If this has a ref, then this is an attribute reference. Otherwise,
+ // this is a document reference.
+ if (!uri.hasRef)
+ continue;
+
+ // Verify that there the persist key is connected up.
+ let docURI = uri.specIgnoringRef;
+
+ if (!localStore.HasAssertion(RDF.GetResource(docURI), persistKey, resource, true))
+ continue;
+
+ let id = uri.ref;
+ let attrs = localStore.ArcLabelsOut(resource);
+
+ while (attrs.hasMoreElements()) {
+ let attr = attrs.getNext().QueryInterface(Ci.nsIRDFResource);
+ let value = localStore.GetTarget(resource, attr, true);
+
+ if (value instanceof Ci.nsIRDFLiteral) {
+ this.setValue(docURI, id, attr.ValueUTF8, value.Value);
+ }
+ }
+ }
+ },
+
+ readFile: function() {
+ const MODE_RDONLY = 0x01;
+ const FILE_PERMS = 0o600;
+
+ let stream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
+ try {
+ stream.init(this._storeFile, MODE_RDONLY, FILE_PERMS, 0);
+ this._data = json.decodeFromStream(stream, stream.available());
+ } catch (e) {
+ this.log("Error reading JSON: " + e);
+ // Ignore problem, we'll just continue on with an empty dataset.
+ } finally {
+ stream.close();
+ }
+ },
+
+ writeFile: Task.async(function* () {
+ if (!this._needsSaving)
+ return;
+
+ this._needsSaving = false;
+
+ this.log("Writing to xulstore.json");
+
+ try {
+ let data = JSON.stringify(this._data);
+ let encoder = new TextEncoder();
+
+ data = encoder.encode(data);
+ yield OS.File.writeAtomic(this._storeFile.path, data,
+ { tmpPath: this._storeFile.path + ".tmp" });
+ } catch (e) {
+ this.log("Failed to write xulstore.json: " + e);
+ throw e;
+ }
+ }),
+
+ markAsChanged: function() {
+ if (this._needsSaving || !this._storeFile)
+ return;
+
+ // Don't write the file more than once every 30 seconds.
+ this._needsSaving = true;
+ this._writeTimer.init(this, WRITE_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ /* ---------- interface implementation ---------- */
+
+ setValue: function (docURI, id, attr, value) {
+ this.log("Saving " + attr + "=" + value + " for id=" + id + ", doc=" + docURI);
+
+ if (!this._saveAllowed) {
+ Services.console.logStringMessage("XULStore: Changes after profile-before-change are ignored!");
+ return;
+ }
+
+ // bug 319846 -- don't save really long attributes or values.
+ if (id.length > 512 || attr.length > 512) {
+ throw Components.Exception("id or attribute name too long", Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ if (value.length > 4096) {
+ Services.console.logStringMessage("XULStore: Warning, truncating long attribute value")
+ value = value.substr(0, 4096);
+ }
+
+ let obj = this._data;
+ if (!(docURI in obj)) {
+ obj[docURI] = {};
+ }
+ obj = obj[docURI];
+ if (!(id in obj)) {
+ obj[id] = {};
+ }
+ obj = obj[id];
+
+ // Don't set the value if it is already set to avoid saving the file.
+ if (attr in obj && obj[attr] == value)
+ return;
+
+ obj[attr] = value; // IE, this._data[docURI][id][attr] = value;
+
+ this.markAsChanged();
+ },
+
+ hasValue: function (docURI, id, attr) {
+ this.log("has store value for id=" + id + ", attr=" + attr + ", doc=" + docURI);
+
+ let ids = this._data[docURI];
+ if (ids) {
+ let attrs = ids[id];
+ if (attrs) {
+ return attr in attrs;
+ }
+ }
+
+ return false;
+ },
+
+ getValue: function (docURI, id, attr) {
+ this.log("get store value for id=" + id + ", attr=" + attr + ", doc=" + docURI);
+
+ let ids = this._data[docURI];
+ if (ids) {
+ let attrs = ids[id];
+ if (attrs) {
+ return attrs[attr] || "";
+ }
+ }
+
+ return "";
+ },
+
+ removeValue: function (docURI, id, attr) {
+ this.log("remove store value for id=" + id + ", attr=" + attr + ", doc=" + docURI);
+
+ if (!this._saveAllowed) {
+ Services.console.logStringMessage("XULStore: Changes after profile-before-change are ignored!");
+ return;
+ }
+
+ let ids = this._data[docURI];
+ if (ids) {
+ let attrs = ids[id];
+ if (attrs && attr in attrs) {
+ delete attrs[attr];
+
+ if (Object.getOwnPropertyNames(attrs).length == 0) {
+ delete ids[id];
+
+ if (Object.getOwnPropertyNames(ids).length == 0) {
+ delete this._data[docURI];
+ }
+ }
+
+ this.markAsChanged();
+ }
+ }
+ },
+
+ getIDsEnumerator: function (docURI) {
+ this.log("Getting ID enumerator for doc=" + docURI);
+
+ if (!(docURI in this._data))
+ return new nsStringEnumerator([]);
+
+ let result = [];
+ let ids = this._data[docURI];
+ if (ids) {
+ for (let id in this._data[docURI]) {
+ result.push(id);
+ }
+ }
+
+ return new nsStringEnumerator(result);
+ },
+
+ getAttributeEnumerator: function (docURI, id) {
+ this.log("Getting attribute enumerator for id=" + id + ", doc=" + docURI);
+
+ if (!(docURI in this._data) || !(id in this._data[docURI]))
+ return new nsStringEnumerator([]);
+
+ let attrs = [];
+ for (let attr in this._data[docURI][id]) {
+ attrs.push(attr);
+ }
+
+ return new nsStringEnumerator(attrs);
+ }
+};
+
+function nsStringEnumerator(items) {
+ this._items = items;
+}
+
+nsStringEnumerator.prototype = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIStringEnumerator]),
+ _nextIndex : 0,
+ hasMore: function() {
+ return this._nextIndex < this._items.length;
+ },
+ getNext : function() {
+ if (!this.hasMore())
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ return this._items[this._nextIndex++];
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([XULStore]);
diff --git a/components/xulstore/XULStore.manifest b/components/xulstore/XULStore.manifest
new file mode 100644
index 000000000..249f0c447
--- /dev/null
+++ b/components/xulstore/XULStore.manifest
@@ -0,0 +1,2 @@
+component {6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea} XULStore.js
+contract @mozilla.org/xul/xulstore;1 {6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}
diff --git a/components/xulstore/moz.build b/components/xulstore/moz.build
new file mode 100644
index 000000000..4e36d474a
--- /dev/null
+++ b/components/xulstore/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 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/.
+
+XPIDL_SOURCES += ['nsIXULStore.idl']
+
+XPIDL_MODULE = 'toolkit_xulstore'
+
+EXTRA_COMPONENTS += [
+ 'XULStore.js',
+ 'XULStore.manifest',
+]
diff --git a/components/xulstore/nsIXULStore.idl b/components/xulstore/nsIXULStore.idl
new file mode 100644
index 000000000..57050b31b
--- /dev/null
+++ b/components/xulstore/nsIXULStore.idl
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsIStringEnumerator;
+
+/**
+ * The XUL store is used to store information related to a XUL document/application.
+ * Typically it is used to store the persisted state for the document, such as
+ * window location, toolbars that are open and nodes that are open and closed in a tree.
+ *
+ * The data is serialized to [profile directory]/xulstore.json
+ */
+[scriptable, uuid(987c4b35-c426-4dd7-ad49-3c9fa4c65d20)]
+
+interface nsIXULStore: nsISupports
+{
+ /**
+ * Sets a value in the store.
+ *
+ * @param doc - document URI
+ * @param id - identifier of the node
+ * @param attr - attribute to store
+ * @param value - value of the attribute
+ */
+ void setValue(in AString doc, in AString id, in AString attr, in AString value);
+
+ /**
+ * Returns true if the store contains a value for attr.
+ *
+ * @param doc - URI of the document
+ * @param id - identifier of the node
+ * @param attr - attribute
+ */
+ bool hasValue(in AString doc, in AString id, in AString attr);
+
+ /**
+ * Retrieves a value in the store, or an empty string if it does not exist.
+ *
+ * @param doc - document URI
+ * @param id - identifier of the node
+ * @param attr - attribute to retrieve
+ *
+ * @returns the value of the attribute
+ */
+ AString getValue(in AString doc, in AString id, in AString attr);
+
+ /**
+ * Removes a value in the store.
+ *
+ * @param doc - document URI
+ * @param id - identifier of the node
+ * @param attr - attribute to remove
+ */
+ void removeValue(in AString doc, in AString id, in AString attr);
+
+ /**
+ * Iterates over all of the ids associated with a given document uri that
+ * have stored data.
+ *
+ * @param doc - document URI
+ */
+ nsIStringEnumerator getIDsEnumerator(in AString doc);
+
+ /**
+ * Iterates over all of the attributes associated with a given document uri
+ * and id that have stored data.
+ *
+ * @param doc - document URI
+ * @param id - identifier of the node
+ */
+ nsIStringEnumerator getAttributeEnumerator(in AString doc, in AString id);
+};